# 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. In Camunda Hub, in the left navigation, click **Organization > Manage organization**. 2. In the **Administration API** tab, click **Create new credentials**. 3. Name the client, and add permissions to this client for [the needed scopes](#client-credentials-and-scopes). 4. Click **Create**. 5. 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. ::: 6. 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 } ``` 7. 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, [create client credentials](#generate-a-token) in the organization settings. 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: ![createConsoleApiClient](../../components/saas/organization/img/create-console-api-client.png) A client can have one or multiple permissions from the following groups: - **Cluster**: [Manage your clusters](/components/saas/clusters/create-cluster.md). - **Zeebe Client**: [Manage API clients](/components/saas/clusters/manage-api-clients.md) for your cluster. - **Hub API**: Interact with the [Camunda Hub API](../hub-api-saas/overview.md). - **IP allowlist**: Configure [IP allowlist](/components/saas/clusters/manage-ip-allowlists.md) rules. - **Connector Secrets**: [Manage secrets](/components/saas/clusters/manage-secrets.md) of your clusters. - **Members**: [Manage users](/components/hub/organization/users-and-roles.md) in 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 in [Camunda Hub](/components/hub/index.md) by navigating to **Organization > Manage 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 Camunda Hub: 1. In Camunda Hub, in the left navigation, click **Environments**, and then click **Clusters**. 1. Select a cluster. 1. Under **Cluster Details**, copy your **Cluster Id**. ::: 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). --- ## 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 ``` --- ## camunda.json A `camunda.json` file in the root of a folder marks the folder as a [Camunda project](/components/concepts/projects.md), the unit you build, test, and deploy together. Desktop Modeler and `c8ctl` recognize the file. ## File location and format The file is named `camunda.json`, lives in the root of your project folder, and contains a single JSON object. There is one project per folder, and projects don't nest. In a monorepo, use sibling folders with one `camunda.json` each. ## Fields | Field | Type | Description | | :-------------- | :----- | :------------------------------------------------- | | `hub` | object | Optional. Camunda Hub connection settings. | | `hub.projectId` | string | Optional. ID of the linked project in Camunda Hub. | All fields are optional. An empty `camunda.json` (`{}`) is valid and simply marks the folder as a project. ### hub.projectId Links the local project to a project in [Camunda Hub](/components/hub/index.md). A project does not need a Hub link. You can create a folder with `camunda.json`, build, and deploy locally with `c8 deploy`, and connect it to Hub later. ## Example ```json { "hub": { "projectId": "" } } ``` ## Related resources - [Development workflows](development-workflows.md): deploy, run, and watch projects with `c8ctl`. - [Projects](/components/concepts/projects.md): the Camunda project concept across Hub and Desktop Modeler. --- ## Cluster inspection and process management `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`, `await`, `delete`, `set`, `cancel`, `complete`, `fail`, `activate`, `update`, `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 Business IDs require Camunda 8.9 or newer. ### 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 # Filter by Business ID c8 list pi --businessId=order-123 ``` ### 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}' # With variables read from a file (avoids shell quoting issues) c8 create pi --id=order-process --variables=@vars.json # With variables read from stdin cat vars.json | c8 create pi --id=order-process --variables=@- # With a Business ID for business-level correlation c8 create pi --id=order-process --businessId=order-123 # 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 --businessId=claim-456 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 # Include custom headers and fetch specific variables in the output c8 activate jobs email-service --customHeaders --fetchVariable=orderId,amount ``` Use `--customHeaders` to include each job's custom headers in the output, and `--fetchVariable` to fetch a comma-separated list of variable names from the server and include them. ### 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" ``` ### Update a job Update a job's retries or timeout. At least one of `--retries` or `--timeout` is required: ```bash # Reset the retry count (for example, to make a failed job activatable again) c8 update job 2251799813685252 --retries=3 # Extend the job timeout to 60 seconds c8 update job 2251799813685252 --timeout=60000 ``` ## 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 --businessId=order-123 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. ### Search wait states Wait states are the points where a process instance is waiting — an open job, a message subscription, a timer, a condition, a user task, or a signal. Use `search wait-state` (alias `ws`) to find them: ```bash # All wait states for a process instance c8 search ws --processInstanceKey=2251799813685249 # Filter by wait state type (JOB, MESSAGE, TIMER, CONDITION, USER_TASK, SIGNAL) c8 search ws --waitStateType=JOB # Filter by BPMN element type, or by element ID (supports wildcards) c8 search ws --elementType=SERVICE_TASK c8 search ws --elementId='*Approve*' ``` ## Variables ### Set variables Set variables on a process instance or a specific flow element scope using its element instance key: ```bash # Set variables on a process instance (propagated to the outermost scope by default) c8 set variable 2251799813685249 --variables='{"status":"approved","amount":100}' # Set variables in the local scope only (not propagated to the parent scope) c8 set variable 2251799813685249 --variables='{"localCounter":1}' --local ``` The `--variables` flag accepts a JSON object. Use `--local` to restrict the update to the specified element instance scope instead of propagating to the outermost scope. Every command that takes `--variables` also accepts `@file.json` to read the JSON from a file and `@-` to read it from stdin. Prefer these when the payload is large or contains quotes — some shells (notably PowerShell) strip or re-split the quotes of inline JSON before the CLI ever sees it: ```bash c8 complete job 2251799813685252 --variables=@vars.json Get-Content vars.json | c8 complete job 2251799813685252 --variables=@- ``` When inline JSON arrives with all of its quotes stripped (`{a:b}` instead of `{"a":"b"}`), c8ctl restores them and prints a warning showing the payload it recovered. Restoration is best-effort: it cannot tell `{"a":1}` from `{"a":"1"}`, and it fails outright when a stripped string contained a `:` or `,` of its own. Use `@file.json` or `@-` for anything non-trivial. The element instance key is the key of the process instance or the specific flow element scope you want to update. You can retrieve these keys from `c8 get pi` or `c8 search pi`. ## 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 Use `correlate` to correlate a message to waiting process instances. It is a separate command from `publish` and, like `publish`, accepts a `--correlationKey` and optional `--variables`: ```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 ## 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) | | `--yes` / `-y` | boolean | | Skip confirmation prompts | ## 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` | | `ws` | `wait-state` | ## 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. 2024-01-01..2024-12-31, ..2024-12-31, 2024-01-01..) | | `--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 | |------|------|----------|-------------| | `--businessId` | string | | Filter by Business ID (Camunda 8.9+) | | `--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), ws (wait-state) **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 | |------|------|----------|-------------| | `--businessId` | string | | Filter by Business ID (Camunda 8.9+) | | `--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 |
wait-state (ws) | Flag | Type | Required | Description | |------|------|----------|-------------| | `--processInstanceKey` / `-k` | string | | Filter by process instance key | | `--rootProcessInstanceKey` / `-r` | string | | Filter by root process instance key | | `--elementInstanceKey` / `-e` | string | | Filter by element instance key | | `--elementId` | string | | Filter by element ID (supports wildcards, e.g. `*Task*`) | | `--elementType` | string | | Filter by BPMN element type (e.g. SERVICE_TASK, USER_TASK, CALL_ACTIVITY) | | `--waitStateType` | string | | Filter by wait state type (JOB, MESSAGE, TIMER, CONDITION, USER_TASK, SIGNAL) |
**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 c8ctl search ws --waitStateType=JOB # Search wait states of type JOB c8ctl search ws --elementType=SERVICE_TASK # Search wait states on service tasks ``` --- ### `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 **Resource-specific flags:**
process-instance (pi) | 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) | | `--businessId` | string | | Business ID for the process instance (Camunda 8.9+) | | `--variables` | string | | JSON variables (or @file.json / @- to read from file/stdin) | | `--awaitCompletion` | boolean | | Wait for process to complete | | `--fetchVariables` | boolean | | Fetch result variables on completion | | `--requestTimeout` | string | | Await timeout in milliseconds |
user | Flag | Type | Required | Description | |------|------|----------|-------------| | `--username` | string | | Username | | `--name` | string | | Display name | | `--email` | string | | Email address | | `--password` | string | | Password |
role | Flag | Type | Required | Description | |------|------|----------|-------------| | `--roleId` | string | | Role ID | | `--name` | string | | Display name |
group | Flag | Type | Required | Description | |------|------|----------|-------------| | `--groupId` | string | | Group ID | | `--name` | string | | Display name |
tenant | Flag | Type | Required | Description | |------|------|----------|-------------| | `--tenantId` | string | | Tenant ID | | `--name` | string | | Display name |
mapping-rule (mr) | Flag | Type | Required | Description | |------|------|----------|-------------| | `--mappingRuleId` | string | | Mapping rule ID | | `--name` | string | | Display name | | `--claimName` | string | | Claim name | | `--claimValue` | string | | Claim value |
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 --businessId=order-123 # Create a process instance with a Business ID 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) --- ### `suspend` Suspend a process instance (Camunda 8.10+) **Usage:** `c8ctl suspend ` **Resources:** pi (process-instance) **Positional arguments:** - **process-instance:** `` (required) --- ### `resume` Resume a suspended process instance (Camunda 8.10+) **Usage:** `c8ctl resume ` **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) | | `--businessId` | string | | Business ID for the process instance (Camunda 8.9+) | | `--variables` | string | | JSON variables (or @file.json / @- to read from file/stdin) | | `--fetchVariables` | boolean | | Fetch result variables on completion | | `--requestTimeout` | string | | Await timeout in milliseconds | **Examples:** ```bash c8ctl await pi --id=myProcess --businessId=claim-456 # Create with a Business ID 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 (or @file.json / @- to read from file/stdin) | --- ### `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 | --- ### `update` Update the retries or timeout of a job. At least one of --retries or --timeout must be provided. **Resources:** job **Positional arguments:** - **job:** `` (required) **Flags:** | Flag | Type | Required | Description | |------|------|----------|-------------| | `--retries` | string | | New number of retries for the job | | `--timeout` | string | | New job timeout in milliseconds | | `--operationReference` | string | | Optional operation reference (long integer) | **Examples:** ```bash c8ctl update job 12345 --retries 3 # Set the retry count for a job c8ctl update job 12345 --timeout 60000 # Set the job timeout to 60 seconds ``` --- ### `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 | | `--customHeaders` | boolean | | Include custom headers in output | | `--fetchVariable` | string | | Comma-separated variable names to fetch from the server and include in output | --- ### `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 (or @file.json / @- to read from file/stdin) | | `--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 (or @file.json / @- to read from file/stdin) | | `--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, or @file.json / @- to read from file/stdin (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). When deploying a directory that is inside a process application (a parent directory contains a .process-application marker), the entire application root is deployed. Explicit file paths are not expanded. **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 c8ctl deploy # Deploy from current directory (detects process application root) ``` --- ### `run` Deploy and start a process instance from a BPMN file **Usage:** `c8ctl run ` **Flags:** | Flag | Type | Required | Description | |------|------|----------|-------------| | `--businessId` | string | | Business ID for the process instance (Camunda 8.9+) | | `--variables` | string | | JSON variables (or @file.json / @- to read from file/stdin) | | `--force` | boolean | | Deploy any file type, ignoring the default extension allow-list | **Examples:** ```bash c8ctl run ./my-process.bpmn --businessId=order-123 # Deploy and start a process with a Business ID ``` --- ### `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 | | `--process-application` | boolean | | Watch and deploy the entire process application (requires .process-application marker) | | `--pa` | boolean | | Alias for --process-application | **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 | | `--scope` | string | | OAuth scope (space-separated) | | `--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 | | `--header` | string | | Custom HTTP header attached to every request made under this profile (format: "Name: value", repeatable) | | `--exactBaseUrl` | boolean | | Use --baseUrl exactly as given for every request, without appending /v2 | --- ### `remove` Remove a profile (alias: rm) **Usage:** `c8ctl remove profile ` **Aliases:** `rm` **Resources:** profile **Positional arguments:** - **profile:** `` (optional) **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 `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. :::note When more than one profile is configured and you don't pass `--profile`, `c8ctl` prompts you to confirm which cluster to deploy to — a safety check against deploying to the wrong environment. Pass `--yes` (or `-y`) to skip the prompt in scripts and CI. ::: ### 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` Use `--extensions` to add more types to the directory scan (merged with the defaults): ```bash c8 deploy ./my-project --extensions=.md,.txt ``` Use `--all-extensions` to include every server-supported type (`.md`, `.txt`, `.xml`, `.rpa`, `.json`, `.config`, `.yml`, `.yaml`) without naming each one: ```bash c8 deploy ./my-project --all-extensions ``` Explicitly named files are always deployed regardless of extension — the extension filter only applies when scanning directories: ```bash c8 deploy ./custom-resource.unsupported ``` Use `--force` to disable extension filtering during directory discovery, deploying every file found regardless of extension: ```bash c8 deploy ./my-project --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}' # With a Business ID c8 run ./order-process.bpmn --businessId=order-123 # 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. When watching inside a process application (a folder tree containing a `.process-application` marker file), use `--process-application` (or its alias `--pa`) to watch and redeploy the entire application on each change: ```bash c8 watch ./my-app --pa ``` ### 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/saas/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=$(" } } ``` :::tip Recommended workflow for mutations 1. Run the command with `--dry-run` and show the would-be API call. 2. Wait for confirmation. 3. Re-run without `--dry-run` to execute. ::: ```bash # Preview creating a process instance c8 create pi --id=my-process --dry-run # Preview a deployment c8 deploy ./my-process.bpmn --dry-run # Preview cancelling a process instance c8 cancel pi 2251799813685249 --dry-run # Inspect the filter body a search would send c8 search pi --state ACTIVE --between 2024-01-01..2024-12-31 --dry-run ``` ### Machine-readable help In JSON output mode, `c8ctl help` emits structured JSON describing the full command tree, flags (with types), and agent flags: ```bash c8 output json c8 help # JSON with commands[], globalFlags[], agentFlags[], and resourceAliases c8 help list # JSON for a specific command ``` ## Verbose mode Use the `--verbose` flag to see detailed information about credential resolution, plugin loading, and other internal operations: ```bash c8 deploy ./process.bpmn --verbose c8 list pi --verbose ``` ## Debug mode Enable debug logging with environment variables for even more detailed output: ```bash DEBUG=1 c8 deploy ./process.bpmn C8CTL_DEBUG=true c8 list pi ``` Debug output is written to stderr and does not interfere with normal command output. --- ## c8ctl CLI ## 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. If `cluster start` exits nonzero while Camunda or connector processes survive, status reports **`running after failed startup`**. Review the startup error or c8run logs, then run `c8 cluster stop` before retrying. A responding shared health endpoint does not mean every physical tenant is ready. The original startup exit code is preserved; stopping clears the failure record, and a successful restart replaces any stale failure state. This includes readiness timeouts after c8run itself exits successfully, which make `cluster start` exit 1. A retry while processes survive without an active marker also exits 1; stop those processes before starting again. **`running (untracked)`** means processes are alive without an active c8ctl marker and the startup outcome is unknown. Use `c8 cluster stop` to recover, including when an installation directory was replaced or deleted while running. JSON status (`c8 cluster status --json`) includes the same `status` and `recovery` guidance. ### 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 # Delete a version's runtime data but keep the binary (the next start is fresh) c8 cluster purge 8.8 # Or stop the running cluster and purge its runtime data in one step c8 cluster stop --purge ``` ### Manage secrets `c8 cluster secrets` forwards to the `secrets` command of the c8run binary c8ctl already downloads and manages. c8ctl adds no storage of its own and never sees a secret value: `set` prompts for the value without echoing it (or reads exactly one value from stdin with `--stdin`), and the store is c8run's, shared across versions and projects for the current OS user. ```bash # Store a secret — prompts, no-echo c8 cluster secrets set OPENAI_API_KEY # List secret names (values are never shown) c8 cluster secrets list # Import multiple secrets from a dotenv file c8 cluster secrets import .env.secrets # Delete a secret without an interactive prompt c8 cluster secrets delete OPENAI_API_KEY --yes # Target a specific installed version instead of the running/highest one c8 cluster secrets --c8-version 8.10 list ``` Everything after `secrets` is passed to c8run unchanged, so any verb or flag c8run supports works here too, including ones added after this was written — run `c8 cluster secrets help` for c8run's own help (`--help` on the `c8ctl` command itself belongs to c8ctl). This requires a c8run build that includes the `secrets` command; older cached versions print a hint if it is missing. Until then, or as an ephemeral alternative for a single run, pass secrets as environment variables when starting instead: ```bash SECRET_OPENAI_API_KEY=sk-... c8 cluster start ``` ### Local physical tenants Physical tenants are isolated engines inside one local Camunda process, each with its own data, users, secrets, and Connectors runtime. They require **Camunda 8.10 or newer and a c8run build that includes the physical-tenant CLI**. Older cached 8.10 builds can predate this feature. `cluster start` reuses cached installations, so upgrading c8ctl alone does not upgrade the cached c8run; `cluster install 8.10` checks for an updated distribution. Run `c8 cluster physical-tenants help` to confirm support. ```bash # Install a supporting distribution, then save physical tenants without starting Java c8 cluster install 8.10 c8 cluster physical-tenants --c8-version 8.10 add sales c8 cluster physical-tenants --c8-version 8.10 add hr --username alice # c8run prompts for alice's password; automation can use --password-stdin # Start default plus all saved physical tenants c8 cluster start 8.10 c8 cluster physical-tenants list # One start only: overrides the saved physical tenant selection without changing it c8 cluster stop c8 cluster start 8.10 --physical-tenants sales,hr # Manage a physical tenant's own secrets c8 cluster secrets --physical-tenant sales set OPENAI_API_KEY c8 cluster secrets --physical-tenant sales import .env.secrets c8 cluster secrets --physical-tenant sales delete OPENAI_API_KEY --yes # Removing a physical tenant keeps its data; restart to apply the change c8 cluster physical-tenants remove sales --yes c8 cluster physical-tenants path c8 cluster physical-tenants help # Stop default and all physical tenants c8 cluster stop ``` `cluster physical-tenants` delegates to c8run, which owns physical tenant validation, password prompting, persistence, configuration, and readiness checks. It also supports `reset --yes` and `add --no-connectors`. `remove` and `reset` ask for confirmation; non-interactive use requires `--yes`. `--physical-tenants` is start-only, accepts comma-separated IDs, and can be repeated. The startup output includes per-physical-tenant endpoints and readiness. A failed physical tenant can leave healthy engines running; the command returns a failure and `c8 cluster stop` stops the surviving processes. Management commands select an explicit leading `--c8-version`, otherwise the running cluster's version, otherwise the highest locally installed version. They never download a distribution automatically. Keep the same version selected when configuring and starting a cluster. Both secrets and physical-tenant commands preserve c8run's output and exit status; `--json` does not convert that output to JSON. Use the `help` subcommand for c8run help, because `--help` belongs to c8ctl. `--dry-run` previews these delegated calls and cluster starts without launching c8run or reading stdin. `C8RUN_TENANTS_FILE` selects the saved physical tenants file; `C8RUN_SECRETS_DIR` selects the default secrets directory. Relative values and secret import filenames are resolved against the caller's working directory, including on startup. Without overrides, c8run's per-OS-user defaults apply. Each physical tenant's secret directory is a sibling under `tenant-secrets/`. Both `--physical-tenant ` and `--physical-tenant=` are supported. c8ctl adds no physical tenant or secret store of its own. Physical tenant IDs contain lowercase letters and digits. With the bundled H2 or other RDBMS storage, IDs are limited to eight characters. Removing and re-adding an ID restores its data, including its existing users. Each enabled physical tenant's Connectors runtime uses another JVM and port. See the [physical-tenant documentation](/self-managed/concepts/physical-tenants/index.md) for prerequisites and limitations. #### Inspect a physical tenant with a profile Physical tenants use a distinct REST base path. For a tenant using the default `demo` login: ```bash c8 add profile local-sales \ --baseUrl=http://localhost:8080/physical-tenants/sales/v2 \ --username=demo --password=demo \ --exactBaseUrl c8 --profile local-sales list process-definitions ``` Configure the profile's authentication to match a dedicated tenant login when used. Existing `create tenant`, `list tenants`, and `use tenant` commands manage or select **logical tenants inside an engine**; they do not create or select physical engines. Profiles select the physical tenant's API endpoint. No profiles are created or switched automatically by `cluster physical-tenants`. ### 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. ### Download interruptions The c8run archive is several hundred MB. If the connection drops, `c8 cluster start` and `c8 cluster install` retry up to three times and resume from where the download stopped when the server supports it. An attempt that receives no data for 60 seconds is aborted and retried. If the download still fails, the error names the cause and suggests what to try: - Behind a proxy, set `HTTPS_PROXY` together with `NODE_USE_ENV_PROXY=1` (Node.js 22.21+ or 24.5+). - Set `C8CTL_C8RUN_DOWNLOAD_URL` to download from a mirror with the same layout as the Camunda Download Center. - Add `--verbose` to print HTTP details for each download attempt: request and response headers, timing, throughput, and the full error cause chain. ## 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, audience, and scope 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 \ --scope="my-oauth-scope" # With a default tenant c8 add profile dev \ --baseUrl=https://dev.example.com \ --clientId=dev-client \ --clientSecret=dev-secret \ --defaultTenantId=dev-tenant # Import settings from a .env file c8 add profile staging --from-file .env.staging # Import settings from the current CAMUNDA_* environment variables source .env.prod c8 add profile prod --from-env ``` ### Gateway-fronted clusters For a cluster reached through an API gateway or reverse proxy, a profile can attach a custom header to every request and target `--baseUrl` exactly, without `c8ctl`'s automatic `/v2` suffixing: ```bash # Attach a header (e.g. an API key) to every REST request made under this profile. # Repeat --header to attach more than one. c8 add profile gateway \ --baseUrl=https://gateway.example.com/camunda-api \ --header "X-Api-Key: your-api-key" \ --header "X-Correlation-Id: your-correlation-id" # --exactBaseUrl: use --baseUrl as the exact request path instead of # appending /v2 (the default for self-managed profiles). c8 add profile gateway-exact \ --baseUrl=https://gateway.example.com/camunda-api \ --exactBaseUrl ``` Both flags are optional and independent of each other. A profile that sets neither behaves exactly as before. ### 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 at most once an hour, in a detached background process, so an unreachable or blocked npm registry never delays a command. When that check finds an update, the next command displays a one-time notification. The check is suppressed in CI environments and development versions; the notification is also suppressed in JSON output mode. ## 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) ``` These commands save the output preference. Use `--json` to select JSON for one invocation without changing the saved preference. For scripts that invoke multiple commands, set `C8CTL_OUTPUT_MODE=json` in their environment rather than adding `--json` to every invocation. `C8CTL_OUTPUT_MODE=text` also allows a temporary text override when the saved preference is JSON, without changing session state. An explicit `--json` flag takes precedence over the environment override, which takes precedence over the saved preference. ### Version output `c8ctl --version` (or `c8ctl -v`) always writes its result to stdout and exits `0`: | Effective output mode | stdout | | :-------------------- | :----------------------------------------------- | | Text | `c8ctl v` | | JSON | `{"status":"info","message":"c8ctl v"}` | This applies whether JSON mode comes from `--json`, `C8CTL_OUTPUT_MODE`, or the saved preference. The version is primary command output, not a diagnostic, despite the JSON envelope's `status: "info"`. Diagnostics remain on stderr in JSON mode. The version payload is not filtered by `--fields`, and requesting the version does not change the saved preference. ## 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_OAUTH_SCOPE` | OAuth scope (space-separated) | | `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 `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 --roleId=my-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' --claimName=email --claimValue=user@example.com ``` ### 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 `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 ``` ### Diagnose plugin issues Use `doctor plugin` to inspect the plugin loading state and surface any command collisions (for example, when two plugins register the same command) or version incompatibilities. The report always exits `0` — it describes state rather than failing. ```bash # Human-readable summary of loaded plugins, their requirements, and any problems c8 doctor plugin # Machine-readable output for scripts and agents c8 doctor plugin --json ``` Built-in commands always take precedence over plugin commands, and the first plugin to register a given command wins — with one exception: a plugin disabled by its declared `engines.c8ctl` (see below) yields the command name to a compatible plugin regardless of load order, because its own copy could only ever refuse to run. `doctor plugin` shows which registrations were kept and which were shadowed, plus each plugin's declared requirement and whether this c8ctl satisfies it. ## The c8ctl version a plugin needs The plugin runtime grows over time — `c8ctl.npm()`, for instance, does not exist in every published c8ctl. A plugin that uses a newer runtime API declares the floor it needs in its own `package.json`: ```json { "name": "c8ctl-plugin-example", "engines": { "c8ctl": ">=4.0.0-alpha.1" } } ``` The field is optional — a plugin that declares nothing is treated exactly as it was before. When it *is* declared and this c8ctl does not satisfy it: - `c8 load plugin`, `c8 upgrade plugin`, and `c8 downgrade plugin` **fail** instead of reporting success. The plugin is still installed; what you don't get is a green exit code on an install that cannot work. (Pinning an older release is the likeliest way to land on one built for an older c8ctl, so `downgrade` matters most here.) - The plugin still loads and still appears in `c8 help`, but every one of its commands refuses to run and prints which c8ctl it needs. A visible command that explains itself beats a missing one that reads as a typo. - `c8 doctor plugin` lists it under incompatible plugins, in text and in `--json`. Range evaluation is npm's own `semver` — the same library `npm install` uses for `engines` fields — so the full range grammar is supported: comparators (`>=`, `>`, `<=`, `<`, `^`, `~`, an exact version, `*`), set unions (`"^3 || ^4"`), hyphen ranges (`"3.3.0 - 4.0.0"`), and partial or wildcard versions (`"4"`, `"4.x"`). Prereleases order as semver specifies, so `>=4.0.0` is *not* satisfied by `4.0.0-alpha.1` — declare `>=4.0.0-alpha.1` when an alpha is enough — and `^`/`~` exclude prereleases of the version they bound (`^4.1.0` does not admit `5.0.0-alpha.1`). **Prereleases count.** Unlike `npm install`, which excludes prerelease versions from a range unless you opt in, this check treats them as ordinary versions: a host on `4.1.0-alpha.3` satisfies `>=4.0.0-alpha.1`. That is deliberate — c8ctl publishes alphas, and refusing to run a plugin on the very channel its requirement was written for would disable working setups. One consequence to know: `^`/`~` still desugar the way npm does, to an upper bound that excludes prereleases (`^4.1.0` means `>=4.1.0 <5.0.0-0`), while a hand-written `<5.0.0` means exactly what it says and does admit `5.0.0-alpha.1`. Spell the bound explicitly if that distinction matters to your plugin. Two cases deliberately disable nothing, because c8ctl could not evaluate the requirement rather than having found it unmet: - c8ctl running from an unpublished development build, whose version says nothing about its API surface. - A range c8ctl cannot parse (`"latest"`, `">=four"`), which is reported as a warning pointing at npm's semver range syntax. To resolve a genuine incompatibility, either upgrade c8ctl (`npm install -g @camunda8/cli@latest`) or install a plugin release that supports the c8ctl you have. ## 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). | | `getUserDataDir()` | Absolute path of the `c8ctl` user data directory (honours `C8CTL_DATA_DIR`). | | `npm({ args, stdout?, stdio? })` | Run npm the way `c8ctl` does, portably. See [Running npm from a plugin](#running-npm-from-a-plugin). | | `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. | ### Logger output `logger.info(message)` formats messages for the current output mode: plain text on stdout in text mode, or `{"status":"info","message":"..."}` on stderr in JSON mode. For a message that is the command's primary result, use `logger.info(message, { stream: "stdout" })`. Use `{ stream: "stderr" }` to keep a diagnostic on stderr in either mode. The override affects only that call, preserves formatting, and does not apply `--fields` filtering. `logger.output(content)` writes raw content to stdout without formatting it, while `logger.json(data)` writes structured data with `--fields` filtering. ### Running npm from a plugin Spawning npm directly is not portable. On Windows npm is a `npm.cmd` shim: a bare `npm` spawn fails with `ENOENT`, and `npm.cmd` alone fails with `EINVAL` under the CVE-2024-27980 hardening in Node 18.20.2 / 20.12.2 / 21.7.3 and later. `c8ctl.npm()` is the same helper the CLI uses for its own plugin installs — it routes the call through `cmd.exe` with every argument quoted (plugin paths routinely contain spaces) and rejects arguments that cannot be passed safely: an embedded `"`, a line break, or a `%VAR%` reference. ```typescript const c8ctl: C8ctlPluginRuntime | undefined = globalThis.c8ctl; if (!c8ctl) throw new Error("c8ctl runtime is not available"); // Capture stdout const { stdout } = c8ctl.npm({ args: ["view", "c8ctl-plugin-foo", "version"], stdout: true }); // Stream to the terminal instead c8ctl.npm({ args: ["install", "c8ctl-plugin-foo"], stdio: "inherit" }); ``` `stdout: true` returns `{ stdout: string }`; omitting it returns `undefined`. A non-zero npm exit throws. #### Installing into a directory with `--prefix` `npm install --prefix ` with no package spec misbehaves on Windows: npm applies the CLI `--prefix` to the global prefix too, and because the Windows global install root is `\node_modules` (rather than `/lib/node_modules` as on POSIX) npm decides the install is global, rewrites the empty argument list to `.`, and resolves that against the process cwd — so it reads the wrong `package.json` ([#526](https://github.com/camunda/c8ctl/issues/526)). `c8ctl.npm()` detects this exact shape on Windows and instead runs npm with its cwd set to the prefix directory, which is equivalent and correct. Plugins do not need to change their cwd or use an npm-specific option form, and POSIX invocations are untouched. The re-scope is bounded (`--workspaces=false`) so npm resolves exactly that directory rather than promoting the install to a workspace root above it, and a prefix that is itself a workspace root is left untouched. ```typescript // Works the same on Linux, macOS and Windows c8ctl.npm({ args: ["install", "--prefix", projectDir], stdout: true }); ``` ### 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 (for example, [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. ### Multi-client configuration (Physical Tenants) A single `camunda.client.*` configuration targets exactly one [Physical Tenant](/self-managed/concepts/physical-tenants/index.md) (the default tenant, unless `physical-tenant-id` is set). To target multiple Physical Tenants from one Spring application, configure multiple named clients under `camunda.clients..*`: ```yaml camunda: clients: finance: physical-tenant-id: finance primary: true risk: physical-tenant-id: risk ``` The map key is a free-form client name; it does not have to match the Physical Tenant ID. A `physical-tenant-id` must be lowercase alphanumeric and at most 64 characters — an invalid value fails application startup. Each named client is a sparse overlay on top of the base `camunda.client.*` configuration — any property you don't set per client (address, auth, and so on) falls back to the shared `camunda.client.*` value. At most one client may be marked `primary: true` — configuring more than one throws `IllegalArgumentException` at startup. If you configure only one client, it's implicitly primary. If you configure more than one client and mark none of them `primary: true`, there is no primary client at all: looking one up throws `IllegalStateException`, and the `camundaClientConfiguration` bean isn't registered. Each client's resolved `auth.method` (its own `camunda.clients..auth.method`, overlaid on the base `camunda.client.auth.method`) must be the same across all clients — mixing resolved authentication methods in one application throws an `IllegalArgumentException` at startup. An unset method normalizes to `none`, so leaving it unset on one client and setting `none` explicitly on another is fine; unset and `basic` together is a conflict. Credentials themselves (client ID, secret, and so on) can still differ per client. If you don't configure any `camunda.clients.*` entries, your application has a single, implicitly-named `default` client — existing single-client configuration is unaffected. #### Access a named client Each named client is also available as a Spring bean, named `CamundaClient` (for example, `financeCamundaClient`). Kebab-case client names are camel-cased for the bean name (`camunda.clients.risk-eu` → `riskEuCamundaClient`). The primary client's bean is `@Primary`, so a plain `@Autowired CamundaClient` still resolves it, and the historical `camundaClient` bean name is preserved as an alias for the primary client — `@Qualifier("camundaClient")` and `getBean("camundaClient")` keep working. To look up clients by name at runtime, inject `CamundaClientRegistry`. Injecting the registry does not eagerly instantiate every client — beans are resolved lazily on first lookup: ```java @Autowired private CamundaClientRegistry clientRegistry; public void useFinanceClient() { CamundaClient financeClient = clientRegistry.get("finance"); // ... } ``` `CamundaClientRegistry` also provides `find(String)` (returns an `Optional`), `getPrimary()`, `clientNames()`, and `all()` (a `Map` of every configured client, keyed by name). ## 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 By default, the Java client caches OAuth credentials in memory only. To persist credentials across JVM restarts, opt in to a file-based cache by setting `camunda.client.auth.credentials-cache-path` to a writeable file location (directory path and file name): ```yaml camunda: client: auth: credentials-cache-path: /tmp/credentials ``` When this property is unset or empty, no cache file is created and tokens are fetched fresh after each restart. #### 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 By default, the **method name** is used as the job type, keeping your code self-documenting without additional configuration: ```java @JobWorker public void checkPayment() { // handles jobs of type 'checkPayment' } ``` To use a different job type, set the `type` attribute on the annotation: ```java @JobWorker(type = "payment-check") public void checkPayment() { // handles jobs of type 'payment-check' } ``` To override the job type externally without modifying the code — for example, when deploying a shared worker implementation under a different type name — use an application property: ```yaml camunda: client: worker: override: checkPayment: type: payment-check ``` To set a fallback job type for all workers that don't define a type via annotation or property override: ```yaml camunda: client: worker: defaults: type: my-default-type ``` ### Control variable fetching By default, a job worker fetches **all** process variables when activating a job. To improve performance and keep your code clean, you should fetch only the variables your worker actually needs. See [writing good workers](/components/best-practices/development/writing-good-workers.md#data-minimization-in-workers) for more guidance on minimizing data transfer. #### Using `@Variable` (recommended) The recommended approach is to declare each variable you need as a typed method parameter annotated with `@Variable`. The SDK automatically fetches only those variables and injects them directly — no type casting required: ```java @JobWorker public void checkPayment(@Variable String orderId, @Variable BigDecimal amount) { // only 'orderId' and 'amount' are fetched; types are enforced automatically } ``` With the [`-parameters` compiler flag](./getting-started.md#enable-the-java-compiler--parameters-flag) enabled, the parameter name is used as the variable name automatically. To use a different variable name, set it explicitly on the annotation: ```java @JobWorker public void checkPayment(@Variable(name = "order_id") String orderId) { // fetches the process variable 'order_id' into the 'orderId' parameter } ``` :::note This adds the variable name to the list of variables fetched from the process. ::: #### Using `@VariablesAsType` For workers that operate on multiple related variables, `@VariablesAsType` maps process variables to your own class, eliminating individual type casts. Jackson's `@JsonProperty` annotation is respected. Return the updated object to write changes back to the process: ```java @JobWorker public PaymentVariables checkPayment(@VariablesAsType PaymentVariables vars) { // access typed fields directly — no casting needed vars.setApproved(vars.getAmount().compareTo(BigDecimal.valueOf(100)) <= 0); return vars; // return the object to write updated fields back to the process } ``` :::note This adds the names of the fields of the used type to the list of variables fetched from the process. ::: #### Provide an explicit list of variables to fetch If you need access to the raw `ActivatedJob` or `JobClient` objects, you can specify an explicit list of variable names to avoid fetching all variables: ```java @JobWorker(fetchVariables = {"orderId", "amount"}) public void checkPayment(final JobClient client, final ActivatedJob job) { String orderId = (String) job.getVariablesAsMap().get("orderId"); // ... } ``` You can also override the variables to fetch in your properties: ```yml camunda: client: worker: override: checkPayment: fetch-variables: - orderId - amount ``` :::caution Using the properties-defined way of fetching variables will override **all** other detection strategies. ::: #### Fetch all variables If your worker genuinely needs every process variable, you can force fetching all variables: ```java @JobWorker(fetchAllVariables = true) public void checkPayment(final ActivatedJob job) { // all variables are available via job.getVariablesAsMap() } ``` You can also set this in your properties: ```yml camunda: client: worker: override: checkPayment: force-fetch-all-variables: true ``` ### Define job worker function parameters The method signature you use to define job worker functions determines what data is available in your worker. For fetching process variables, use [`@Variable`](#using-variable-recommended) or [`@VariablesAsType`](#using-variablesastype) — both are covered in the [variable fetching section](#control-variable-fetching) above. 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 public void processOrder(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 public void processOrder(final ActivatedJob job) { String orderId = (String) job.getVariablesAsMap().get("orderId"); // ... } ``` :::note Only explicit variable fetching will be effective when using the `ActivatedJob` as a parameter. ::: #### 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 processOrder(@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 processOrder( @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 public void processOrder() { // 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 public Map processOrder() { // 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); } ``` ##### Completing ad-hoc sub-process jobs with a result When your job worker handles an [ad-hoc sub-process](/reference/glossary.md#ad-hoc-sub-process) job, you can return an `AdHocSubProcessResultFunction` to specify which element to activate within the sub-process. The starter automatically applies the result when you complete the job. Return a lambda that calls `activateElement` with the target element ID: ```java @JobWorker(type = "myAdHocSubprocessJob") public AdHocSubProcessResultFunction handleAdHocSubprocess() { return r -> r.activateElement("myElementId"); } ``` To also submit process variables with the result, use the `AdHocSubProcessResultFunction.withVariables` factory method: ```java @JobWorker(type = "myAdHocSubprocessJob") public AdHocSubProcessResultFunction handleAdHocSubprocess() { Map variables = Map.of("decision", "approved"); return AdHocSubProcessResultFunction.withVariables(variables, r -> r.activateElement("approvalTask")); } ``` ##### Completing user task listener jobs with a result When your job worker handles a user task listener job, you can return a `UserTaskResultFunction` to control the outcome of the listener. The starter automatically applies the result when you complete the job. Return a lambda that configures the result, for example to correct the assignee: ```java @JobWorker(type = "io.camunda:userTaskListener:complete") public UserTaskResultFunction handleUserTaskListener() { return r -> r.correctAssignee("newAssignee"); } ``` #### Programmatically completing jobs Your job worker code can also complete the job itself. This gives you more control over when you want to complete the job (for example, allowing you to move the completion to reactive callbacks): ```java @JobWorker(autoComplete = false) public void processOrder(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: processOrder: 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 public void processOrder() { // 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 public void processOrder() { 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` _(optional)_, defaults to the configured retry backoff; function input is the retries value 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 processOrder() { // worker's code - now disabled } ``` You can also override this setting via your `application.yaml` file: ```yaml camunda: client: worker: override: processOrder: 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 processOrder() { // worker's code } ``` This can also be configured as property: ```yaml camunda: client: worker: override: processOrder: 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 processOrder() { // worker's code } ``` This can also be configured as property: ```yaml camunda: client: worker: override: processOrder: 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 processOrder() { // 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: processOrder: 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 processOrder() { // worker's code } ``` You can also override the `tenant-ids` for each worker: ```yaml camunda: client: worker: override: processOrder: tenants-ids: - - foo ``` ##### Physical Tenant fan-out for multi-client applications The `@JobWorker` annotation has no attribute to bind a worker to one named client. In a [multi-client](#multi-client-configuration-physical-tenants) application, every `@JobWorker` method registers against **all** configured clients — it fans out across the whole `CamundaClientRegistry`, not just the primary or default one. In an application with only one client, this has no visible effect — there's only one client to fan out to. Per-client worker overrides don't restrict this fan-out either: `worker.*`/`worker.override.*` properties set under `camunda.clients.` have no effect, since the customizer that applies those overrides is built from the single, global `camunda.client.*` properties bean. Setting `camunda.clients.risk.worker.override.shipOrder.enabled: false`, for example, silently does nothing. If you need a worker to run against only some of your configured Physical Tenants, filter inside the handler using the tenant identifier available to you at runtime, rather than relying on annotation-level scoping or per-client worker configuration. #### Define the job timeout To define the job timeout, you can set the annotation (`long` in milliseconds): ```java @JobWorker(timeout=60000) public void processOrder() { // worker's code } ``` Moreover, you can override the timeout for the worker (as ISO 8601 duration expression): ```yaml camunda: client: worker: override: processOrder: 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 processOrder() { // worker's code } ``` Moreover, you can override the retry backoff for the worker (as ISO 8601 duration expression): ```yaml camunda: client: worker: override: processOrder: 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 at application startup, use the `@Deployment` annotation: ```java @Deployment(resources = "classpath:demoProcess.bpmn") public class MyRandomBean { // make sure this bean is registered } ``` :::note Multi-client applications In a [multi-client](#multi-client-configuration-physical-tenants) application, every `@Deployment` resource is deployed to **all** configured clients — one `CamundaPostDeploymentSpringEvent` is published per client. For a multi-tenant application, this means your BPMN lands in every configured Physical Tenant, which is often what you want, but shouldn't come as a surprise. The same applies to `@ClusterVariables` processing. ::: ### 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 ``` ## Set cluster variables at startup To set cluster variables at application startup, use the `@ClusterVariables` annotation. Cluster variables are set when the Camunda client starts. There are three ways to provide the variables: ### From JSON resource files Provide one or more JSON resource files using the `resources` attribute: ```java @ClusterVariables(resources = "classpath:cluster-variables.json") @SpringBootApplication public class MyApplication { } ``` Multiple files can be provided at once: ```java @ClusterVariables(resources = {"classpath:vars-a.json", "classpath:vars-b.json"}) @SpringBootApplication public class MyApplication { } ``` ### From a method Annotate a method with `@ClusterVariables`. The return value is serialized to JSON and set as cluster variables. Any type the configured `JsonMapper` can serialize is supported, for example `Map`, a POJO, or a record: ```java @ClusterVariables public MyConfig clusterVariables() { return new MyConfig("production", 3); } ``` ### From application properties Define variables directly in your `application.yaml`: ```yaml camunda: client: cluster-variables: global: environment: production maxRetries: 3 ``` Variables defined in properties are applied in addition to any annotation-defined variables. ### Specify the tenant to set variables for To set cluster variables scoped to a specific tenant, use the `tenantId` property of the `@ClusterVariables` annotation: ```java @ClusterVariables(resources = "classpath:cluster-variables.json", tenantId = "myTenant") @SpringBootApplication public class MyApplication { } ``` Or use the `tenant` property in your `application.yaml`: ```yaml camunda: client: cluster-variables: tenant: myTenant: environment: staging maxRetries: 5 ``` By default, the annotation and `global` property set variables in the global scope. ### Disable cluster variable processing To disable all cluster variable processing (both annotation-based and property-based), set: ```yaml camunda: client: cluster-variables: 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 } } ``` In a [multi-client](#multi-client-configuration-physical-tenants) application, one `CamundaClientCreatedSpringEvent`/`CamundaClientClosingSpringEvent` fires per configured client, each carrying that client's name — listen for these instead if you need to tell clients apart. ### 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. In a [multi-client](#multi-client-configuration-physical-tenants) application, one event fires per configured client, since `@Deployment` resources are deployed to every configured client. ## 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.10.x | ≥ 17 | 4.1.x | | | `camunda-spring-boot-4-starter` | 8.10.x | ≥ 17 | 4.1.x | | | `camunda-spring-boot-3-starter` | 8.10.x | ≥ 17 | 3.5.x | | For Spring Boot OSS and Commercial support dates, see the [Spring Boot support timeline](https://spring.io/projects/spring-boot#support). ### Dedicated Spring Boot 3 and 4 modules Starting with Camunda 8.10, the default `camunda-spring-boot-starter` artifact is bundled with **Spring Boot 4.1.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.1.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.1.x. :::caution Spring Boot 3.5.x OSS support window Spring's open source support for Spring Boot 3.5.x ended in June 2026 (see [Spring Boot support timeline](https://spring.io/projects/spring-boot#support)). Camunda continues to support and maintain `camunda-spring-boot-3-starter` until the end of Spring Commercial support for Spring Boot 3.x. What this means for your application: - **Camunda's starter:** Camunda continues to release patches for `camunda-spring-boot-3-starter` until the end of Spring Commercial support. - **Spring framework patches:** After June 2026, Spring will no longer provide open-source security or bug-fix patches for Spring Boot 3.x. Security patches remain available through Spring Commercial support (Broadcom). ::: For details on how Camunda handles major version transitions and end-of-support windows, see [Spring Boot/Framework/Security updates](/reference/announcements-release-notes/release-policy.md#spring-bootframeworksecurity-updates). To use the Spring Boot 3 module, replace the default dependency in your project: ```xml io.camunda camunda-spring-boot-3-starter 8.9.x ``` :::caution Micrometer version requirement `camunda-spring-boot-3-starter` requires **Micrometer 1.16 or newer** if you enable Micrometer-based metrics. Spring Boot 3.5.x's own dependency management defaults to an older Micrometer version (~1.15.x), which does not satisfy this requirement. If your application resolves a Micrometer version below 1.16 while metrics are enabled, job workers appear to activate jobs normally, but handler methods never run — jobs keep expiring and getting re-activated indefinitely, with no error in the logs. To avoid this, explicitly pin Micrometer to 1.16 or newer: **Maven**, import the Micrometer BOM as the first entry in ``, before your Spring Boot BOM import (import order matters when two BOMs manage the same dependency): ```xml io.micrometer micrometer-bom 1.16.6 import pom ``` **Gradle**, add a constraint: ```groovy dependencies { constraints { implementation("io.micrometer:micrometer-core:1.16.6") } } ``` ::: ## Get started ### Step 1: Add the dependency Add the Camunda Spring Boot Starter to your project: **Maven:** ```xml io.camunda camunda-spring-boot-starter 8.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.plugins maven-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 on a bean. By default, the method name is used as the job type, so you only need the annotation: ```java @JobWorker public void processOrder() { // handles jobs of type 'processOrder' } ``` To inject specific process variables as typed parameters, use `@Variable`: ```java @JobWorker public void processOrder(@Variable String orderId, @Variable BigDecimal amount) { // only 'orderId' and 'amount' are fetched; types are enforced automatically } ``` 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: string null Enable or disable the Camunda client. If disabled, the client bean is not created. Type: boolean true The number of threads for invocation of job workers. Type: integer 1 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: duration "PT45S" The maximum number of concurrent HTTP connections the client can open. Type: integer 100 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: string null The physical tenant ID sent as the `camunda-physical-tenant` gRPC header on every outgoing call. When `null` the header is omitted. Type: string null If `true`, prefers REST over gRPC for operations supported by both protocols. Type: boolean true If true, prefixes the REST base path with the physical tenant path when a physical tenant ID is set. Set to false to use the configured REST address as is, for example behind a reverse proxy that already routes to the physical tenant. Type: boolean true 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: boolean false ### `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: string null The client ID to use when requesting an access token from the OAuth authorization server. Type: string null The client secret to use when requesting an access token from the OAuth authorization server. Type: string null 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: string null 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: url null The keystore key password for the OAuth identity provider. Type: string null The keystore password for the OAuth identity provider. Type: string null The path to the keystore for the OAuth identity provider. Type: file null 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: string null Controls how far before token expiry a background refresh is triggered. The token remains valid within this window, so callers don't block on a synchronous refresh at expiry. Must be strictly greater than the internal expiry grace period. Type: duration "PT30S" 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: string null The scopes of the access token. Type: string null The multiplier applied to the backoff duration between successive token fetch retry attempts. Must be greater than or equal to 1.0. Type: double 2 The initial backoff duration applied between token fetch retry attempts. Each subsequent delay is multiplied by `camunda.client.auth.token-fetch-backoff-multiplier`. Type: duration "PT1S" 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: integer 5 If the token endpoint returns a non-retryable response, subsequent token fetch attempts fail immediately without making a request. This property specifies the duration of this cooldown period. After the cooldown period elapses, the next request retries; if it also fails with a non-retryable error, the cooldown resets. Set to `duration.zero` to disable the cooldown. Type: duration "PT5M" The set of HTTP status codes from the token endpoint that are retried with backoff. Any other non-200 status code triggers the `camunda.client.auth.token-fetch-non-retryable-cooldown` cooldown. Type: array[integer] [404,429,500,502,503,504] The authorization server URL from which to request the access token. A default is set by `camunda.client.mode: saas`. Type: url null The truststore password for the OAuth identity provider. Type: string null The path to the truststore for the OAuth identity provider. Type: file null The username to use for basic authentication. A default is set by `camunda.client.auth.method: basic`. Type: string null 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: url null ### `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: string null The password of the key referenced by the alias. If not set, the keystore password is used. Type: string null The password of the referenced keystore. Type: string null The path to the keystore where the client assertion certificate is stored. Type: file null ### `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: string null The domain the Camunda client connects to. Change this to connect to a non-production instance of Camunda Cloud. Type: string null The port the Camunda client connects to. Type: integer null The region the Camunda client connects to. Type: string null ### `camunda.client.cluster-variables` Properties for setting cluster variables at startup. Property Description Default value Indicates if cluster variable processing is enabled. When `true`, variables configured via `@ClusterVariables` annotations and via the `variables` property are applied at startup. When `false`, all cluster variable processing is skipped. Type: boolean true ### `camunda.client.deployment` Properties for automatic deployment at startup. Property Description Default value Indicates if the `@Deployment` annotation is processed. Type: boolean true 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: boolean false ### `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: boolean true Enable or disable the job worker. Type: boolean true 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: boolean false The maximum number of jobs exclusively activated for this worker at the same time. Type: integer 32 The maximum number of retries before automatic responses (complete, fail, bpmn error) for jobs are no longer attempted. Type: integer 0 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: boolean false If streaming is enabled, sets the maximum duration the worker will wait without receiving any job on the open stream before canceling 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: string null Activate the jobs polled by this worker with a lease. When enabled, each activated job is assigned a distinct lease token, fencing the complete, fail, and throw-error commands against a superseded activation of the same job. Only applies to the polling path. If not set, jobs are activated without a lease. Type: boolean null ### `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.cluster-variables.variables` Cluster variables to set at startup. Each entry carries a name, a value and optionally metadata, a kind and a tenant ID. Entries without a tenant ID are globally scoped. Property Description Default value The name of the cluster variable. Type: string null The value of the cluster variable. Type: object null The metadata of the cluster variable. Type: map[string,object] null The kind of the cluster variable. Type: enum[json, secretReference] null The tenant ID of the cluster variable. Type: string null ### `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: boolean null Enable or disable the job worker. Type: boolean null 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: boolean null The maximum number of jobs exclusively activated for this worker at the same time. Type: integer null The maximum number of retries before automatic responses (complete, fail, bpmn error) for jobs are no longer attempted. Type: integer null The name of the worker owner. If set to default, it is generated as `${beanName}#${methodName}`. Type: string null The maximal interval between polls for new jobs. Type: duration null The request timeout for the activate job request used to poll for new jobs. Type: duration null The backoff before a retry of a failed job is possible. Type: duration null 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: boolean null If streaming is enabled, sets the maximum duration the worker will wait without receiving any job on the open stream before canceling 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 null 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 null 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: duration null The type of jobs to work on. Type: string null Activate the jobs polled by this worker with a lease. When enabled, each activated job is assigned a distinct lease token, fencing the complete, fail, and throw-error commands against a superseded activation of the same job. Only applies to the polling path. If not set, jobs are activated without a lease. Type: boolean null ## 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.cluster-variables` Deprecated properties for setting cluster variables at startup. Property Replacement Hint Cluster variables are now set in a single list instead of separate maps for global and tenant variables. Cluster variables are now set in a single list instead of separate maps for global and tenant variables. ### `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 ## 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 `CamundaClient` in an `DependencyInjection.IServiceCollection`. ### AddCamundaClient(IServiceCollection) ```csharp public static IServiceCollection AddCamundaClient(this IServiceCollection services) ``` Registers a singleton `CamundaClient` 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 `CamundaClient` using an `Configuration.IConfiguration` 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 `CamundaClient` 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, IEngineClockTarget, 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. #### CancelClusterRebalanceAsync(CancellationToken) ```csharp public Task CancelClusterRebalanceAsync(CancellationToken ct = default) ``` Stop the running rebalance Asks the running rebalance to stop once the transfer in flight has finished. Partitions already transferred keep their new leaders, and those the rebalance had not yet reached keep their current ones. Cancellation requests are idempotent and always accepted. The `wasRunning` response field can be used to distinguish a cancellation that found a running rebalance from one that did not. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task CancelClusterRebalanceExample() { using var client = CamundaClient.Create(); // Asks the running rebalance to stop once the in-flight transfer finishes. // Cancellation is idempotent. Requires cluster-admin credentials. var result = await client.CancelClusterRebalanceAsync(); if (result.WasRunning) { Console.WriteLine("Cancellation requested; rebalance will stop after the in-flight transfer finishes."); } else { Console.WriteLine("No rebalance was running."); } } ``` #### ChangeClusterModeAsync(Mode, bool?, CancellationToken) ```csharp public Task ChangeClusterModeAsync(Mode mode, bool? dryRun = null, CancellationToken ct = default) ``` Change cluster mode Transitions the cluster between processing and recovery mode. This is a non-blocking operation: the request is acknowledged once the change has been accepted, before the transition itself has completed. Entering recovery mode deactivates all partitions so that only a restricted set of read-only operations remains available; exiting recovery mode returns the cluster to normal processing. Returns the planned cluster change so its progress can be monitored via the topology. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `mode` | `Mode` | | | `dryRun` | `Nullable` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### ChangeClusterModeAsClusterAdminAsync(Mode, string?, bool?, CancellationToken) ```csharp public Task ChangeClusterModeAsClusterAdminAsync(Mode mode, string? physicalTenantId = null, bool? dryRun = null, CancellationToken ct = default) ``` Change the cluster mode of one or every physical tenant Transitions physical tenants between processing and recovery mode. If the `physicalTenantId` parameter is not provided, all available physical tenants are transitioned individually. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. | Parameter | Type | Description | | ------------------ | ------------------- | ----------- | | `mode` | `Mode` | | | `physicalTenantId` | `String` | | | `dryRun` | `Nullable` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task ChangeClusterModeAsClusterAdminExample() { using var client = CamundaClient.Create(); // The cluster-admin variant can target a single physical tenant. Omit // physicalTenantId to apply the change to every physical tenant. var change = await client.ChangeClusterModeAsClusterAdminAsync( Mode.RECOVERING, physicalTenantId: "default", dryRun: true); Console.WriteLine($"Cluster change {change.ChangeId}:"); foreach (var group in change.PlannedChanges) { var tenant = group.PhysicalTenantId is null ? "cluster-wide" : group.PhysicalTenantId; Console.WriteLine($" {tenant}:"); foreach (var operation in group.Operations) { var suffix = operation.Mode is null ? "" : $" -> {operation.Mode}"; Console.WriteLine($" {operation.Operation}{suffix}"); } } } ``` #### 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, JobKey jobKey, JobLeaseToken jobLease) { using var client = CamundaClient.Create(); // The agent's model, provider, system prompt and limits are supplied as a // CONFIGURATION history item; a create request must open the conversation // with at least one such item. var result = await client.CreateAgentInstanceAsync(new AgentInstanceCreationRequest { ElementInstanceKey = elementInstanceKey, JobKey = jobKey, JobLeaseToken = jobLease, History = new List { new AgentInstanceHistoryItem { HistoryItemId = HistoryItemId.AssumeExists("configuration-1"), LoopIteration = LoopIterationId.AssumeExists(1), Role = AgentInstanceHistoryRoleEnum.CONFIGURATION, ProducedAt = DateTimeOffset.UtcNow, Content = new List(), Model = "gpt-4o", Provider = "openai", SystemPrompt = new List { new AgentInstanceTextContent { Text = "You are a helpful assistant." }, }, Limits = new AgentInstanceLimits { MaxModelCalls = 20, MaxToolCalls = 20, MaxTokens = 100_000, }, }, }, }); 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); } ``` #### DeleteHistoryBackupAsync(BackupId, CancellationToken) ```csharp public Task DeleteHistoryBackupAsync(BackupId backupId, CancellationToken ct = default) ``` Delete history backup Deletes the history backup with the given id, by deleting every snapshot that makes it up. Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. | Parameter | Type | Description | | ---------- | ------------------- | ----------- | | `backupId` | `BackupId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task DeleteHistoryBackupExample(BackupId backupId) { using var client = CamundaClient.Create(); await client.DeleteHistoryBackupAsync(backupId); } ``` #### DeleteHistoryBackupAsClusterAdminAsync(BackupId, string?, CancellationToken) ```csharp public Task DeleteHistoryBackupAsClusterAdminAsync(BackupId backupId, string? physicalTenantId = null, CancellationToken ct = default) ``` Delete a history backup across physical tenants Deletes the history backup with the given id from every physical tenant of the cluster, or from the one named by `physicalTenantId`. A tenant that does not hold the backup has already reached the requested end state, so it counts as deleted rather than as a failure. The request is all-or-nothing: a physical tenant the backup cannot be deleted from fails the whole request, and the deletions that already succeeded on other tenants are not undone. Narrow the request with `physicalTenantId` to delete from the tenants that can still be reached. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. Use `DELETE /v2/backups/history/{backupId}` to act as a single physical tenant. | Parameter | Type | Description | | ------------------ | ------------------- | ----------- | | `backupId` | `BackupId` | | | `physicalTenantId` | `String` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### DeleteRuntimeBackupAsync(BackupId, CancellationToken) ```csharp public Task DeleteRuntimeBackupAsync(BackupId backupId, CancellationToken ct = default) ``` Delete runtime backup Deletes the runtime backup with the given id. | Parameter | Type | Description | | ---------- | ------------------- | ----------- | | `backupId` | `BackupId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task DeleteRuntimeBackupExample(BackupId backupId) { using var client = CamundaClient.Create(); await client.DeleteRuntimeBackupAsync(backupId); } ``` #### DeleteRuntimeBackupAsClusterAdminAsync(BackupId, string?, CancellationToken) ```csharp public Task DeleteRuntimeBackupAsClusterAdminAsync(BackupId backupId, string? physicalTenantId = null, CancellationToken ct = default) ``` Delete a runtime backup across physical tenants Deletes the runtime backup with the given id from every physical tenant of the cluster, or from the one named by `physicalTenantId`. A tenant that does not hold the backup has already reached the requested end state, so it counts as deleted rather than as a failure — the same as deleting an unknown backup id through the per-physical-tenant endpoint. The request is all-or-nothing: a physical tenant the backup cannot be deleted from fails the whole request, and the deletions that already succeeded on other tenants are not undone. Narrow the request with `physicalTenantId` to delete from the tenants that can still be reached. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Use `DELETE /v2/backups/runtime/{backupId}` to act as a single physical tenant. | Parameter | Type | Description | | ------------------ | ------------------- | ----------- | | `backupId` | `BackupId` | | | `physicalTenantId` | `String` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### DeleteRuntimeBackupStateAsync(CancellationToken) ```csharp public Task DeleteRuntimeBackupStateAsync(CancellationToken ct = default) ``` Delete runtime backup state Resets the runtime backup state of every partition of the physical tenant, clearing all checkpoint info, backup info, checkpoint metadata, and backup ranges. Used when switching backup stores. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task DeleteRuntimeBackupStateExample() { using var client = CamundaClient.Create(); // Clears all checkpoint info, backup info, checkpoint metadata, and backup // ranges on every partition. Used when switching backup stores. await client.DeleteRuntimeBackupStateAsync(); } ``` #### DeleteRuntimeBackupStateAsClusterAdminAsync(string?, CancellationToken) ```csharp public Task DeleteRuntimeBackupStateAsClusterAdminAsync(string? physicalTenantId = null, CancellationToken ct = default) ``` Delete runtime backup state across physical tenants Resets the runtime backup state of every partition of every physical tenant of the cluster, or of the one named by `physicalTenantId`, clearing all checkpoint info, backup info, checkpoint metadata, and backup ranges. Used when switching backup stores. The request is all-or-nothing: a physical tenant whose state cannot be reset fails the whole request, and the resets that already succeeded on other tenants are not undone. Narrow the request with `physicalTenantId` to reset the tenants that can still be reached. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Use `DELETE /v2/backups/runtime/state` to act as a single physical tenant. | Parameter | Type | Description | | ------------------ | ------------------- | ----------- | | `physicalTenantId` | `String` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### 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. Optionally, provide a `scopeKey` to make the variables of a specific process instance or element instance visible while evaluating the expression. | 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}"); } ``` #### GetAgentDefinitionAsync(AgentDefinitionKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetAgentDefinitionAsync(AgentDefinitionKey agentDefinitionKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get agent definition Returns an agent definition by key. | Parameter | Type | Description | | -------------------- | ------------------------------------------- | ----------- | | `agentDefinitionKey` | `AgentDefinitionKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetAgentDefinitionExample(AgentDefinitionKey agentDefinitionKey) { using var client = CamundaClient.Create(); var result = await client.GetAgentDefinitionAsync(agentDefinitionKey); Console.WriteLine($"Agent definition: {result.AgentDefinitionKey}, name: {result.Name}, type: {result.AgentType}"); } ``` #### 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}"); } ``` #### GetClusterExportingStatusAsync(CancellationToken) ```csharp public Task GetClusterExportingStatusAsync(CancellationToken ct = default) ``` Get exporting status of the whole cluster Returns the exporting status of the whole cluster, folded over the exporting status of every physical tenant. Only `PAUSED` and `SOFT_PAUSED` confirm that exporting is paused cluster-wide; every other value means at least one physical tenant is not paused, so callers should keep polling. A physical tenant that itself reports `MIXED` makes the whole cluster `MIXED`. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetClusterExportingStatusExample() { using var client = CamundaClient.Create(); // Returns the aggregated exporting status across all physical tenants in the cluster. var result = await client.GetClusterExportingStatusAsync(); Console.WriteLine($"Cluster exporting status: {result.Status}"); } ``` #### GetClusterRebalanceAsync(CancellationToken) ```csharp public Task GetClusterRebalanceAsync(CancellationToken ct = default) ``` Report the cluster's current leadership balance Reports whether the cluster is currently balanced, the current leadership state of every partition, and what became of the last rebalance to finish. The last completed rebalance is held in memory by the coordinating broker, so none will be reported if the coordinator has moved or restarted since the last rebalance. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetClusterRebalanceExample() { using var client = CamundaClient.Create(); // Reports whether the cluster is currently balanced and the current leadership // state of each partition. Requires cluster-admin credentials. var result = await client.GetClusterRebalanceAsync(); Console.WriteLine($"Balance state: {result.State}"); foreach (var partition in result.Partitions) { Console.WriteLine($" Partition {partition.PartitionId}: leader={partition.CurrentLeader}"); } if (result.RunningRebalance is not null) { Console.WriteLine($"Rebalance in progress: started {result.RunningRebalance.StartedAt}"); } } ``` #### GetClusterStatusAsync(CancellationToken) ```csharp public Task GetClusterStatusAsync(CancellationToken ct = default) ``` Get the status of the whole cluster Checks the health status of the whole cluster, aggregated over all physical tenants. Returns `HEALTHY` when every physical tenant is healthy, `DOWN` when no physical tenant can process work, and `DEGRADED` in every other case. No per-tenant detail is reported; use `GET /cluster/v2/topology` for that. This endpoint is public and requires no authentication, unlike `PATCH /cluster/v2/mode` below, which needs cluster-admin credentials. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetClusterStatusExample() { using var client = CamundaClient.Create(); var status = await client.GetClusterStatusAsync(); Console.WriteLine($"Cluster status: {status.Status}"); } ``` #### GetClusterUpgradeStatusAsync(CancellationToken) ```csharp public Task GetClusterUpgradeStatusAsync(CancellationToken ct = default) ``` Get the upgrade-readiness status of the whole cluster Reports one overall upgrade-readiness status for the whole cluster, folded over every physical tenant and condition. `MIGRATED` only once every known condition has migrated for every known physical tenant; `MIGRATION_IN_PROGRESS` when at least one is confirmed not yet migrated; `UNKNOWN` otherwise (including before anything has been reported yet). No per-tenant or per-condition detail is reported here; see the `upgradeReadiness` actuator endpoint for that. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetClusterUpgradeStatusExample() { using var client = CamundaClient.Create(); // Reports one overall upgrade-readiness status for the whole cluster, // folded over every physical tenant and condition. MIGRATED only once // everything has migrated; MIGRATION_IN_PROGRESS while at least one // condition is confirmed not yet migrated. var status = await client.GetClusterUpgradeStatusAsync(); Console.WriteLine($"Cluster upgrade-readiness: {status.Status}"); } ``` #### GetExportingStatusAsync(CancellationToken) ```csharp public Task GetExportingStatusAsync(CancellationToken ct = default) ``` Get exporting status Returns the exporting status of the physical tenant, aggregated over every replica of every one of its partitions. Because pause and resume are applied to all replicas, the status is only a single phase if every replica reports that phase; otherwise it is `MIXED`, which means a pause or resume is still in flight or was only partially applied. Backup tooling should treat only `PAUSED` and `SOFT_PAUSED` as confirmation that exporting is paused. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetExportingStatusExample() { using var client = CamundaClient.Create(); var result = await client.GetExportingStatusAsync(); Console.WriteLine($"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}"); } ``` #### GetHistoryBackupAsync(BackupId, CancellationToken) ```csharp public Task GetHistoryBackupAsync(BackupId backupId, CancellationToken ct = default) ``` Get history backup Returns detailed status of the history backup with the given id. Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. | Parameter | Type | Description | | ---------- | ------------------- | ----------- | | `backupId` | `BackupId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetHistoryBackupExample(BackupId backupId) { using var client = CamundaClient.Create(); var backup = await client.GetHistoryBackupAsync(backupId); // The aggregated state is derived from the state of every expected snapshot. Console.WriteLine($"History backup {backup.BackupId}: {backup.State}"); } ``` #### GetHistoryBackupAsClusterAdminAsync(BackupId, string?, CancellationToken) ```csharp public Task GetHistoryBackupAsClusterAdminAsync(BackupId backupId, string? physicalTenantId = null, CancellationToken ct = default) ``` Get a history backup across physical tenants Reports what every physical tenant of the cluster, or the one named by `physicalTenantId`, holds for the given backup id. There is no aggregated cluster-level state: a tenant that was reached and does not hold this backup reports `NOT_FOUND`, which is a successful observation rather than a failure. The request is all-or-nothing: a physical tenant whose state cannot be read fails the whole request. Narrow the request with `physicalTenantId` to read the tenants that can still be reached. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. Use `GET /v2/backups/history/{backupId}` to act as a single physical tenant. | Parameter | Type | Description | | ------------------ | ------------------- | ----------- | | `backupId` | `BackupId` | | | `physicalTenantId` | `String` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### GetRestoreStatusAsync(CancellationToken) ```csharp public Task GetRestoreStatusAsync(CancellationToken ct = default) ``` Get the status of the restore that is currently in progress Returns the status of the restore that is currently in progress, reported per broker and per partition. There is at most one restore in flight at any time. Once the restore has finished this endpoint returns 404; the per-partition detail is not retained after completion. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetRestoreStatusExample() { using var client = CamundaClient.Create(); // Poll this endpoint while the cluster is in recovery mode to track progress. var status = await client.GetRestoreStatusAsync(); Console.WriteLine($"Restore {status.ChangeId}: {status.Status}"); } ``` #### GetRuntimeBackupAsync(BackupId, CancellationToken) ```csharp public Task GetRuntimeBackupAsync(BackupId backupId, CancellationToken ct = default) ``` Get runtime backup Returns detailed status of the runtime backup with the given id. | Parameter | Type | Description | | ---------- | ------------------- | ----------- | | `backupId` | `BackupId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetRuntimeBackupExample(BackupId backupId) { using var client = CamundaClient.Create(); var backup = await client.GetRuntimeBackupAsync(backupId); Console.WriteLine($"Backup {backup.BackupId}: {backup.State}"); foreach (var partition in backup.Details) { Console.WriteLine($" Partition {partition.PartitionId}: {partition.State}"); } } ``` #### GetRuntimeBackupAsClusterAdminAsync(BackupId, string?, CancellationToken) ```csharp public Task GetRuntimeBackupAsClusterAdminAsync(BackupId backupId, string? physicalTenantId = null, CancellationToken ct = default) ``` Get a runtime backup across physical tenants Reports what every physical tenant of the cluster, or the one named by `physicalTenantId`, holds for the given backup id, plus the state aggregated over all of them. A tenant that was reached and does not hold this backup reports `DOES_NOT_EXIST`, which is a successful observation rather than a failure — so a backup only some tenants hold aggregates to `INCOMPLETE`, the same way a backup only some partitions hold does within one tenant. The request is all-or-nothing: a physical tenant whose state cannot be read fails the whole request. Narrow the request with `physicalTenantId` to read the tenants that can still be reached. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Use `GET /v2/backups/runtime/{backupId}` to act as a single physical tenant. | Parameter | Type | Description | | ------------------ | ------------------- | ----------- | | `backupId` | `BackupId` | | | `physicalTenantId` | `String` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### GetRuntimeBackupStateAsync(CancellationToken) ```csharp public Task GetRuntimeBackupStateAsync(CancellationToken ct = default) ``` Get runtime backup state Returns the current checkpoint and backup state of every partition of the physical tenant. Unlike the `backupRuntime` actuator, this fails the whole request if the checkpoint state or the backup ranges cannot be retrieved from any partition, instead of silently returning an empty section. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetRuntimeBackupStateExample() { using var client = CamundaClient.Create(); var state = await client.GetRuntimeBackupStateAsync(); foreach (var checkpoint in state.CheckpointStates) { Console.WriteLine( $"Partition {checkpoint.PartitionId} checkpoint {checkpoint.CheckpointId} ({checkpoint.CheckpointType})"); } foreach (var range in state.Ranges) { Console.WriteLine( $"Partition {range.PartitionId} range: {range.Start?.CheckpointId} -> {range.End?.CheckpointId}"); } } ``` #### GetRuntimeBackupStateAsClusterAdminAsync(string?, CancellationToken) ```csharp public Task GetRuntimeBackupStateAsClusterAdminAsync(string? physicalTenantId = null, CancellationToken ct = default) ``` Get runtime backup state across physical tenants Reports the checkpoint and backup state of every partition of every physical tenant of the cluster, or of the one named by `physicalTenantId`, grouped by physical tenant. Checkpoint ids and log positions only mean anything within one physical tenant's partitions, so nothing is aggregated across tenants. The request is all-or-nothing: a physical tenant whose state cannot be read fails the whole request rather than contributing an empty section, which an operator making a delete or restore decision could not tell apart from "nothing to report yet". Narrow the request with `physicalTenantId` to read the tenants that can still be reached. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Use `GET /v2/backups/runtime/state` to act as a single physical tenant. | Parameter | Type | Description | | ------------------ | ------------------- | ----------- | | `physicalTenantId` | `String` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### GetStatusAsync(CancellationToken) ```csharp public Task GetStatusAsync(CancellationToken ct = default) ``` Get physical tenant status Checks the health status of the default physical tenant by verifying if there's at least one partition of its group with a healthy leader. This endpoint is scoped to the default physical tenant only: it is available unprefixed and at `/physical-tenants/default/v2/status`, but not for any other physical tenant id (`/physical-tenants/{id}/v2/status` returns 404 for every other id, whether or not a physical tenant with that id exists). On a cluster with only the default physical tenant this endpoint answers the same question as `/cluster/v2/status`, though not with the same response: `/cluster/v2/status` reports its status in a body and so also distinguishes a degraded tenant from a healthy one. Use `/cluster/v2/status` for the aggregated status of the whole cluster, or `/physical-tenants/{id}/v2/topology` for the health of a specific physical tenant's partitions. | 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` #### ListHistoryBackupsAsync(BackupIdPrefix?, bool?, CancellationToken) ```csharp public Task ListHistoryBackupsAsync(BackupIdPrefix? prefix = null, bool? verbose = null, CancellationToken ct = default) ``` List history backups Returns a list of all available history backups of the physical tenant, with their state and additional info, most recent first by snapshot start time. Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. | Parameter | Type | Description | | --------- | -------------------------- | ----------- | | `prefix` | `Nullable` | | | `verbose` | `Nullable` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task ListHistoryBackupsExample() { using var client = CamundaClient.Create(); // `prefix` must end in a single '*'. Omit it to list every history backup. var backups = await client.ListHistoryBackupsAsync( BackupIdPrefix.AssumeExists("10*")); Console.WriteLine($"History backups: {backups}"); } ``` #### ListHistoryBackupsAsClusterAdminAsync(string?, BackupIdPrefix?, bool?, CancellationToken) ```csharp public Task ListHistoryBackupsAsClusterAdminAsync(string? physicalTenantId = null, BackupIdPrefix? prefix = null, bool? verbose = null, CancellationToken ct = default) ``` List history backups across physical tenants Lists the history backups of every physical tenant of the cluster, or of the one named by `physicalTenantId`, grouped by backup id. A backup id that only some physical tenants hold is a supported outcome rather than a degraded one, so only the tenants that hold it are listed under it. The request is all-or-nothing: a physical tenant whose backups cannot be read fails the whole request rather than silently dropping out of the listing. Narrow the request with `physicalTenantId` to list the backups of the tenants that can still be read. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. Use `GET /v2/backups/history` to act as a single physical tenant. | Parameter | Type | Description | | ------------------ | -------------------------- | ----------- | | `physicalTenantId` | `String` | | | `prefix` | `Nullable` | | | `verbose` | `Nullable` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### ListRuntimeBackupsAsync(BackupIdPrefix?, CancellationToken) ```csharp public Task ListRuntimeBackupsAsync(BackupIdPrefix? prefix = null, CancellationToken ct = default) ``` List runtime backups Returns a list of all available runtime backups of the physical tenant, with their state and additional info, sorted in descending order of backupId. | Parameter | Type | Description | | --------- | -------------------------- | ----------- | | `prefix` | `Nullable` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task ListRuntimeBackupsExample() { using var client = CamundaClient.Create(); // `prefix` must end in a single '*'. Omit it to list every backup. var backups = await client.ListRuntimeBackupsAsync( BackupIdPrefix.AssumeExists("10*")); Console.WriteLine($"Runtime backups: {backups}"); } ``` #### ListRuntimeBackupsAsClusterAdminAsync(string?, BackupIdPrefix?, CancellationToken) ```csharp public Task ListRuntimeBackupsAsClusterAdminAsync(string? physicalTenantId = null, BackupIdPrefix? prefix = null, CancellationToken ct = default) ``` List runtime backups across physical tenants Lists the runtime backups of every physical tenant of the cluster, or of the one named by `physicalTenantId`, grouped by backup id. Every group reports every targeted tenant, including the ones holding nothing for that id, so a backup only some tenants hold aggregates to `INCOMPLETE` here exactly as it does when looked up directly — the state of a listed group can be trusted to say whether the cluster can be restored from it. A backup id that only some physical tenants hold is a supported outcome rather than a degraded one; tenants that generate their own backup ids never share one, so in that mode each backup forms its own group and the other tenants report `DOES_NOT_EXIST` under it. The request is all-or-nothing: a physical tenant whose backups cannot be read fails the whole request rather than silently dropping out of the listing. Narrow the request with `physicalTenantId` to list the backups of the tenants that can still be read. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Use `GET /v2/backups/runtime` to act as a single physical tenant. | Parameter | Type | Description | | ------------------ | -------------------------- | ----------- | | `physicalTenantId` | `String` | | | `prefix` | `Nullable` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### ListSecretsAsync(SecretListRequest, CancellationToken) ```csharp public Task ListSecretsAsync(SecretListRequest body, CancellationToken ct = default) ``` List secrets List the `camunda.secrets.*` references known for the caller's physical tenant. Only references the caller holds `SECRET:READ` on are returned. This endpoint never returns secret values, only the reference names. The references are read from the secret stores configured for the caller's physical tenant. A store may hold names outside the reference name charset (for example one containing a dot); those are omitted, since `/secrets/resolve` would reject them and no permission can be granted on them. A returned reference is usable verbatim with `/secrets/resolve`. In a FEEL expression, however, a name that is not a bare identifier has to be backtick-escaped, since FEEL reads a bare dash as the minus operator: a listed `camunda.secrets.db-password` is written `` =camunda.secrets.`db-password` `` in a BPMN input mapping. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `body` | `SecretListRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task ListSecretsExample() { using var client = CamundaClient.Create(); // The request body is reserved for future filtering options and currently // takes no properties. var result = await client.ListSecretsAsync(new SecretListRequest()); // Only the references are returned — never the secret values. Use // ResolveSecretsAsync to fetch a value when one is actually needed. foreach (var reference in result.References) { Console.WriteLine($"Secret available: {reference}"); } } ``` #### PauseClusterExportingAsync(bool?, CancellationToken) ```csharp public Task PauseClusterExportingAsync(bool? soft = null, CancellationToken ct = default) ``` Pause exporting across the whole cluster Pauses exporting on every physical tenant of the cluster in one call. With `soft=true`, every physical tenant is soft-paused instead. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `soft` | `Nullable` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task PauseClusterExportingExample() { using var client = CamundaClient.Create(); // With `soft: true` exporting keeps running but its position is not committed, // so the log is still not compacted — use it when exporting must keep // progressing across all physical tenants, for example while a cluster backup is taken. await client.PauseClusterExportingAsync(soft: true); } ``` #### PauseExportingAsync(bool?, CancellationToken) ```csharp public Task PauseExportingAsync(bool? soft = null, CancellationToken ct = default) ``` Pause exporting Pauses exporting on all partitions of the physical tenant. While paused, exported records are not committed, so the log is not compacted for the affected partitions. With `soft=true`, exporting continues to run but its position is not committed, so the state after resuming is identical to a hard pause; use this variant when exporting must keep progressing (e.g. to avoid falling behind) while still preventing log compaction, such as during a backup. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `soft` | `Nullable` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task PauseExportingExample() { using var client = CamundaClient.Create(); // With `soft: true` exporting keeps running but its position is not committed, // so the log is still not compacted — use it when exporting must keep // progressing, for example while a backup is taken. await client.PauseExportingAsync(soft: true); } ``` #### ResolveSecretsAsync(SecretResolveRequest, CancellationToken) ```csharp public Task ResolveSecretsAsync(SecretResolveRequest body, CancellationToken ct = default) ``` Resolve secrets Resolve a deduplicated batch of `camunda.secrets.*` references for the caller's physical tenant in a single round-trip. Each reference is authorized and resolved independently. For valid requests, the endpoint always responds with HTTP 200: successfully resolved references are returned in `resolved`, while references that could not be resolved (for example not found, malformed or over-long, or the caller lacks `SECRET:REVEAL` on that reference) are returned in `errors`. A failure of one reference never fails the others. Only structurally invalid requests are rejected with HTTP 400: a missing or non-array `references` field, more than 20 references, or a null entry. References are resolved against the secret stores configured for the caller's physical tenant, served from the gateway's secret cache when the value is already cached and read from the store otherwise. | Parameter | Type | Description | | --------- | ---------------------- | ----------- | | `body` | `SecretResolveRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task ResolveSecretsExample() { using var client = CamundaClient.Create(); var result = await client.ResolveSecretsAsync(new SecretResolveRequest { References = new List { "camunda.secrets.myApiToken", "camunda.secrets.dbPassword", }, }); // Successfully resolved references are returned in Resolved; references that // could not be resolved are returned in Errors, each with a typed error code. // Never log resolved.Value — it holds secret material. Pass it directly to the // consumer that needs it (HTTP client, DB driver, ...) instead. foreach (var resolved in result.Resolved) { Console.WriteLine($"Resolved {resolved.Reference} (value redacted)"); UseSecret(resolved.Value); } foreach (var error in result.Errors) { Console.WriteLine($"Failed to resolve {error.Reference}: {error.Code} - {error.Message}"); } } // Hands the resolved secret to whatever needs it, without logging it. private static void UseSecret(string value) { } ``` #### RestoreAsync(RestoreRequest, bool?, CancellationToken) ```csharp public Task RestoreAsync(RestoreRequest body, bool? dryRun = null, CancellationToken ct = default) ``` Restore from a backup Restores the cluster from a backup. The restore is described either by a single backup ID or by a time range (`from`/`to`) that selects the backups to restore. This endpoint is only accessible while the cluster is in recovery mode; requests are rejected otherwise. The request is validated and acknowledged, but the restore itself is performed asynchronously. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `body` | `RestoreRequest` | | | `dryRun` | `Nullable` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### RestoreAsClusterAdminAsync(ClusterRestoreRequest, string?, bool?, CancellationToken) ```csharp public Task RestoreAsClusterAdminAsync(ClusterRestoreRequest body, string? physicalTenantId = null, bool? dryRun = null, CancellationToken ct = default) ``` Restore one or every physical tenant from a backup Restores physical tenants from backups. The restore is described either by a list of backup IDs or by a time range (`from`/`to`) that selects the backups to restore. Restores are only accepted while the targeted physical tenants are in recovery mode; requests are rejected otherwise. The request is validated and acknowledged, but the restore itself is performed asynchronously. If the `physicalTenantId` parameter is provided, only that physical tenant is restored and `overrides` must be omitted. If it is not provided, every physical tenant of the cluster is restored: those named in `overrides` with their own backup selection, all others with the selection at the top level of the request body. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. | Parameter | Type | Description | | ------------------ | ----------------------- | ----------- | | `body` | `ClusterRestoreRequest` | | | `physicalTenantId` | `String` | | | `dryRun` | `Nullable` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task RestoreAsClusterAdminExample() { using var client = CamundaClient.Create(); // The cluster must be in recovery mode before a restore is accepted. // Use physicalTenantId to restore a single physical tenant; omit it to // restore every physical tenant. Pass dryRun: true to validate the // request and inspect the plan without applying it. // Provide either a list of backup IDs (one per partition) or a time // range (From/To), but not both. var change = await client.RestoreAsClusterAdminAsync( new ClusterRestoreRequest { BackupIds = new List { 100, 101 }, }, physicalTenantId: "default", dryRun: true); Console.WriteLine($"Cluster change {change.ChangeId}:"); foreach (var group in change.PlannedChanges) { var tenant = group.PhysicalTenantId is null ? "cluster-wide" : group.PhysicalTenantId; Console.WriteLine($" {tenant}: {group.Operations.Count} operation(s)"); } } ``` #### ResumeClusterExportingAsync(CancellationToken) ```csharp public Task ResumeClusterExportingAsync(CancellationToken ct = default) ``` Resume exporting across the whole cluster Resumes exporting on every physical tenant of the cluster in one call, after a pause or soft pause. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task ResumeClusterExportingExample() { using var client = CamundaClient.Create(); await client.ResumeClusterExportingAsync(); } ``` #### ResumeExportingAsync(CancellationToken) ```csharp public Task ResumeExportingAsync(CancellationToken ct = default) ``` Resume exporting Resumes exporting on all partitions of the physical tenant after a pause or soft pause. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task ResumeExportingExample() { using var client = CamundaClient.Create(); await client.ResumeExportingAsync(); } ``` #### SearchAgentDefinitionsAsync(AgentDefinitionSearchQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchAgentDefinitionsAsync(AgentDefinitionSearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search agent definitions Search for agent definitions based on given criteria. | Parameter | Type | Description | | ------------- | ------------------------------------------------------ | ----------- | | `body` | `AgentDefinitionSearchQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchAgentDefinitionsExample() { using var client = CamundaClient.Create(); var result = await client.SearchAgentDefinitionsAsync(new AgentDefinitionSearchQuery { Filter = new AgentDefinitionFilter { AgentType = AgentDefinitionTypeEnum.AIAGENTTASK, }, Page = new LimitPagination { Limit = 50, }, }); foreach (var def in result.Items) { Console.WriteLine($"Agent definition: {def.AgentDefinitionKey}, name: {def.Name}, type: {def.AgentType}"); } } ``` #### SearchAgentInstanceHistoryAsync(AgentInstanceKey, AgentInstanceHistorySearchQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchAgentInstanceHistoryAsync(AgentInstanceKey agentInstanceKey, AgentInstanceHistorySearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search agent instance history Searches the conversation history of an agent instance. Committed items are returned by default. | Parameter | Type | Description | | ------------------ | ----------------------------------------------------------- | ----------- | | `agentInstanceKey` | `AgentInstanceKey` | | | `body` | `AgentInstanceHistorySearchQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchAgentInstanceHistoryExample(AgentInstanceKey agentInstanceKey) { using var client = CamundaClient.Create(); var result = await client.SearchAgentInstanceHistoryAsync( agentInstanceKey, new AgentInstanceHistorySearchQuery { Sort = new List { new AgentInstanceHistorySearchQuerySortRequest { Field = AgentInstanceHistorySearchQuerySortRequestField.ProducedAt, Order = SortOrderEnum.ASC, }, }, Page = new LimitPagination { Limit = 20 }, }); foreach (var item in result.Items) { Console.WriteLine($"{item.HistoryItemKey} ({item.Role})"); } } ``` #### 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` #### SyncRuntimeBackupStateAsync(CancellationToken) ```csharp public Task SyncRuntimeBackupStateAsync(CancellationToken ct = default) ``` Force-write runtime backup state Force-writes the checkpoint and backup metadata of every partition of the physical tenant to the backup store, independent of any backup being taken or confirmed, and returns the updated state. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SyncRuntimeBackupStateExample() { using var client = CamundaClient.Create(); // Force-writes checkpoint and backup metadata of every partition to the backup // store, independent of any backup being taken, and returns the updated state. var state = await client.SyncRuntimeBackupStateAsync(); Console.WriteLine($"Synced {state.BackupStates.Count} partition backup states"); } ``` #### SyncRuntimeBackupStateAsClusterAdminAsync(string?, CancellationToken) ```csharp public Task SyncRuntimeBackupStateAsClusterAdminAsync(string? physicalTenantId = null, CancellationToken ct = default) ``` Force-write runtime backup state across physical tenants Force-writes the checkpoint and backup metadata of every partition of every physical tenant of the cluster, or of the one named by `physicalTenantId`, to that tenant's backup store, independent of any backup being taken or confirmed, and returns the updated state per physical tenant. The request is all-or-nothing: a physical tenant whose metadata cannot be written fails the whole request, and the writes that already succeeded on other tenants are not undone. The operation is idempotent, so retrying the same call is the correct remedy. Narrow the request with `physicalTenantId` to write the tenants that can still be reached. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Use `POST /v2/backups/runtime/state/sync` to act as a single physical tenant. | Parameter | Type | Description | | ------------------ | ------------------- | ----------- | | `physicalTenantId` | `String` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### TakeHistoryBackupAsync(TakeHistoryBackupRequest, CancellationToken) ```csharp public Task TakeHistoryBackupAsync(TakeHistoryBackupRequest body, CancellationToken ct = default) ``` Take a history backup Triggers a backup of the physical tenant's history, by scheduling a snapshot of every secondary storage index it owns. Unlike runtime backups, history backups have no generated-id mode: `backupId` is always required. Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. | Parameter | Type | Description | | --------- | -------------------------- | ----------- | | `body` | `TakeHistoryBackupRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task TakeHistoryBackupExample(BackupId backupId) { using var client = CamundaClient.Create(); // Backups are logically ordered by id, so each successive backup must use a // higher id than the previous one. var backup = await client.TakeHistoryBackupAsync( new TakeHistoryBackupRequest { BackupId = backupId }); Console.WriteLine($"Scheduled history backup {backup.BackupId}"); foreach (var snapshot in backup.ScheduledSnapshots) { Console.WriteLine($" {snapshot}"); } } ``` #### TakeHistoryBackupAsClusterAdminAsync(TakeHistoryBackupRequest, string?, CancellationToken) ```csharp public Task TakeHistoryBackupAsClusterAdminAsync(TakeHistoryBackupRequest body, string? physicalTenantId = null, CancellationToken ct = default) ``` Take a history backup on one or every physical tenant Triggers a history backup on every physical tenant of the cluster, or on the one named by `physicalTenantId`. Every targeted tenant uses the same caller-supplied `backupId`, but the backups are independent: they are neither coordinated nor rolled back together. The request is all-or-nothing: the `backupId` is checked on every targeted tenant before any snapshot is scheduled, so a tenant that already holds this id, or that cannot be reached, fails the whole request and no backup is started anywhere. There is no aggregated cluster-level state in the response. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. Use `POST /v2/backups/history` to act as a single physical tenant. | Parameter | Type | Description | | ------------------ | -------------------------- | ----------- | | `body` | `TakeHistoryBackupRequest` | | | `physicalTenantId` | `String` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### TakeRuntimeBackupAsync(TakeRuntimeBackupRequest, CancellationToken) ```csharp public Task TakeRuntimeBackupAsync(TakeRuntimeBackupRequest body, CancellationToken ct = default) ``` Take a runtime backup Triggers a backup of runtime data on all partitions of the physical tenant. The `backupId` must be omitted if continuous backups and/or a backup or checkpoint schedule is enabled for the physical tenant, as the id is generated automatically. Otherwise, `backupId` is required. | Parameter | Type | Description | | --------- | -------------------------- | ----------- | | `body` | `TakeRuntimeBackupRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task TakeRuntimeBackupExample(BackupId backupId) { using var client = CamundaClient.Create(); // Omit `BackupId` when continuous backups or a backup/checkpoint schedule is // enabled for the physical tenant — the id is then generated by the cluster. // Otherwise `BackupId` is required and must be higher than any existing one. var backup = await client.TakeRuntimeBackupAsync( new TakeRuntimeBackupRequest { BackupId = backupId }); Console.WriteLine($"Scheduled backup {backup.BackupId}"); } ``` #### TakeRuntimeBackupAsClusterAdminAsync(TakeRuntimeBackupRequest, string?, CancellationToken) ```csharp public Task TakeRuntimeBackupAsClusterAdminAsync(TakeRuntimeBackupRequest body, string? physicalTenantId = null, CancellationToken ct = default) ``` Take a runtime backup on one or every physical tenant Triggers a runtime backup on every physical tenant of the cluster, or on the one named by `physicalTenantId`. A cluster-wide backup is a set of independent per-tenant backups, not an atomic snapshot of the cluster: they are neither coordinated nor rolled back together, and each tenant stores its own, so the same `backupId` can be used for all of them. Every targeted physical tenant must be in the same backup-id mode. `backupId` must be omitted when every targeted tenant generates its own ids (because continuous backups and/or a backup or checkpoint schedule is enabled for it), and is required when none of them does. A cluster whose targeted tenants mix the two modes is rejected with 400 and has to be driven one tenant at a time through `POST /v2/backups/runtime`. In generated-id mode each tenant generates its own id, so the response reports an id per physical tenant rather than one for the cluster. The trigger is all-or-error, and never silent about a partial trigger: if any targeted tenant cannot be triggered the response carries an error status, but its body still lists every targeted tenant — which ones were triggered, under which `backupId` to monitor or delete them, and why the others failed. Nothing is rolled back, so the backups that were triggered keep running and have to be deleted explicitly. A request rejected before any tenant was triggered answers with a problem detail instead, and nothing is running. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Use `POST /v2/backups/runtime` to act as a single physical tenant. | Parameter | Type | Description | | ------------------ | -------------------------- | ----------- | | `body` | `TakeRuntimeBackupRequest` | | | `physicalTenantId` | `String` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### TriggerClusterRebalanceAsync(ClusterRebalanceRequest, bool?, CancellationToken) ```csharp public Task TriggerClusterRebalanceAsync(ClusterRebalanceRequest body, bool? dryRun = null, CancellationToken ct = default) ``` Trigger a cluster-wide leadership rebalance Transfers leadership of every partition that is not led by its highest-priority replica towards that replica, one partition at a time. Returns as soon as the rebalance has been accepted (poll `GET /cluster/v2/rebalance` to monitor progress). Each rebalance can specify overrides for the configured rebalance settings (e.g. maximum replication lag to allow). An absent request body means "use the configured settings". Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. | Parameter | Type | Description | | --------- | ------------------------- | ----------- | | `body` | `ClusterRebalanceRequest` | | | `dryRun` | `Nullable` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task TriggerClusterRebalanceExample() { using var client = CamundaClient.Create(); // Transfers leadership of every partition towards its highest-priority replica, // one at a time. Requires cluster-admin credentials, not Orchestration Cluster // user credentials. Poll GetClusterRebalanceAsync to monitor progress. var result = await client.TriggerClusterRebalanceAsync( new ClusterRebalanceRequest { ReplicationLagThreshold = 1_000_000, MaxTransferAttempts = 3, }); Console.WriteLine($"Rebalance state: {result.State}"); foreach (var partition in result.Partitions) { Console.WriteLine($" Partition {partition.PartitionId}: leader={partition.CurrentLeader}"); } } ``` #### UpdateAgentInstanceAsync(AgentInstanceKey, AgentInstanceUpdateRequest, CancellationToken) ```csharp public Task UpdateAgentInstanceAsync(AgentInstanceKey agentInstanceKey, AgentInstanceUpdateRequest body, CancellationToken ct = default) ``` Update agent instance Updates the status of an agent instance and appends a batch of history items to its conversation history. Each history item created for this request is echoed back in the response. | Parameter | Type | Description | | ------------------ | ---------------------------- | ----------- | | `agentInstanceKey` | `AgentInstanceKey` | | | `body` | `AgentInstanceUpdateRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task UpdateAgentInstanceExample( AgentInstanceKey agentInstanceKey, ElementInstanceKey elementInstanceKey, JobKey jobKey, JobLeaseToken jobLease) { using var client = CamundaClient.Create(); // Conversation turns are appended through the same history batch used at // creation time; per-item metrics describe the model call that produced them. await client.UpdateAgentInstanceAsync( agentInstanceKey, new AgentInstanceUpdateRequest { ElementInstanceKey = elementInstanceKey, JobKey = jobKey, JobLeaseToken = jobLease, Status = AgentInstanceUpdateStatusEnum.THINKING, History = new List { new AgentInstanceHistoryItem { HistoryItemId = HistoryItemId.AssumeExists("assistant-1"), LoopIteration = LoopIterationId.AssumeExists(1), Role = AgentInstanceHistoryRoleEnum.ASSISTANT, ProducedAt = DateTimeOffset.UtcNow, Content = new List { new AgentInstanceTextContent { Text = "How can I help you today?" }, }, Metrics = new AgentInstanceHistoryItemMetricsRequest { InputTokens = 150, OutputTokens = 50, DurationMs = 1_200, }, }, }, }); 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}"); } ``` #### GetClusterTopologyAsync(CancellationToken) ```csharp public Task GetClusterTopologyAsync(CancellationToken ct = default) ``` Get the topology of the whole cluster Obtains the topology of the whole cluster, aggregated over all physical tenants. Cluster-level information is reported once; partition layout, replication and per-partition role, health and state are reported per physical tenant. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Use `GET /v2/topology` for the topology of a single physical tenant. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetClusterTopologyExample() { using var client = CamundaClient.Create(); // Returns the topology of the whole cluster aggregated over all physical // tenants. Requires cluster-admin credentials, not Orchestration Cluster // user credentials. Use GetTopologyAsync for single-tenant topology. var topology = await client.GetClusterTopologyAsync(); Console.WriteLine($"Cluster {topology.ClusterId}: {topology.ClusterSize} broker(s), gateway {topology.GatewayVersion}"); foreach (var tenant in topology.PhysicalTenants) { Console.WriteLine($" Tenant {tenant.PhysicalTenantId}: {tenant.PartitionsCount} partition(s), replication {tenant.ReplicationFactor}"); } } ``` #### 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 `CamundaClient.CreateDeploymentAsync` 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 `ExtendedDeploymentResponse` 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`. History deletion is supported for process definitions and decision requirements definitions; for other resource types (forms, generic resources) the flag is ignored and no history is deleted. The two supported types differ in how the history is removed. For a decision requirements definition the history is deleted asynchronously via a batch operation whose details are returned in the `batchOperation` field of the response. For a process definition that still exists in the runtime state, the definition first drains its running instances and its history is deleted asynchronously once the definition is fully removed cluster-wide; no batch operation is returned in the response. If the process definition has already been removed from the runtime state and the deletion is later re-triggered with `deleteHistory` set to `true`, a batch operation is created immediately and returned in the `batchOperation` field. | 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\?, CancellationToken) ```csharp public Task GetResourceContentAsync(ResourceKey resourceKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get RPA resource content (deprecated) **Deprecated** — use `/resources/{resourceKey}/content/binary` instead, which supports all resource types and returns content as binary (octet-stream). Returns the content of a deployed RPA resource as JSON. :::info This endpoint only supports RPA resources. For generic resource content in binary format, use the `/resources/{resourceKey}/content/binary` endpoint. ::: | Parameter | Type | Description | | ------------- | ---------------------------- | ----------- | | `resourceKey` | `ResourceKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### GetResourceContentBinaryAsync(ResourceKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetResourceContentBinaryAsync(ResourceKey resourceKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get resource content as binary Returns the content of a deployed resource in binary format (octet-stream). :::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` **Example** ```csharp public static async Task GetResourceContentBinaryExample(ResourceKey resourceKey) { using var client = CamundaClient.Create(); byte[] content = await client.GetResourceContentBinaryAsync(resourceKey); Console.WriteLine($"Binary content length: {content.Length} bytes"); } ``` #### SearchResourcesAsync(ResourceSearchQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchResourcesAsync(ResourceSearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search resources Search for deployed resources based on given criteria. :::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 search APIs. ::: | Parameter | Type | Description | | ------------- | ----------------------------------------------- | ----------- | | `body` | `ResourceSearchQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchResourcesExample() { using var client = CamundaClient.Create(); var result = await client.SearchResourcesAsync(new ResourceSearchQuery()); foreach (var resource in result.Items!) { Console.WriteLine($"Resource: {resource.ResourceName}"); } } ``` ### Process Instances #### SearchVariablesAsDtoAsync\(ProcessInstanceKey, ScopeKey?, TenantId?, int, CancellationToken) ```csharp public Task> SearchVariablesAsDtoAsync(ProcessInstanceKey processInstanceKey, ScopeKey? scopeKey = null, TenantId? tenantId = null, int pageSize = 100, CancellationToken ct = default) where T : class ``` Fetch the variables declared by a DTO type for a process instance, mapping them onto a strongly-typed result. The query is derived from the DTO's members (honouring `[JsonPropertyName]`): only the declared variable names are fetched via a `name $in [...]` filter, so memory is bounded by the DTO shape rather than the total number of variables on the process instance. Results are paged to exhaustion over the filtered set, collapsed by name, and parsed into a `VariableMap`. Access modes on the returned map: - Lenient — `VariableMap.Get` / `VariableMap.Get` tolerate absent variables. - Strict — `VariableMap.Validate` constructs the DTO and throws if a required member is absent. ```csharp public record OrderVars(string OrderId, decimal? Amount); var vars = await client.SearchVariablesAsDtoAsync(processInstanceKey); var amount = vars.Get("amount"); // lenient var typed = vars.Validate(); // strict: throws if OrderId missing ``` | Parameter | Type | Description | | -------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `processInstanceKey` | `ProcessInstanceKey` | The process instance whose variables to search. | | `scopeKey` | `Nullable` | Optional scope key to disambiguate variables that exist at multiple scopes. When omitted and a declared variable resolves to more than one scope, a `VariableScopeCollisionException` is thrown. | | `tenantId` | `Nullable` | Optional tenant ID filter. | | `pageSize` | `Int32` | The page size used while paging the filtered result set. | | `ct` | `CancellationToken` | Cancellation token. | **Returns:** `Task>` — A `VariableMap` over the declared variables. **Example** ```csharp public record OrderVariables(string OrderId, decimal Amount, string? Notes); public static async Task SearchVariablesAsDtoExample(ProcessInstanceKey processInstanceKey) { using var client = CamundaClient.Create(); // Search a process instance for exactly the variables declared on the DTO, // pages and all, and collapse them into a single typed object. var map = await client.SearchVariablesAsDtoAsync(processInstanceKey); // Read individual values lazily without materializing the whole DTO. if (map.Contains("amount")) { var amount = map.Get("amount"); Console.WriteLine($"Amount: {amount}"); } // Validate() enforces that every non-nullable member is present, // throwing VariableValidationException if a required variable is missing. OrderVariables order = map.Validate(); Console.WriteLine($"Order {order.OrderId}: {order.Amount}"); } ``` #### AssignProcessInstanceBusinessIdAsync(ProcessInstanceKey, ProcessInstanceBusinessIdAssignmentInstruction, CancellationToken) ```csharp public Task AssignProcessInstanceBusinessIdAsync(ProcessInstanceKey processInstanceKey, ProcessInstanceBusinessIdAssignmentInstruction body, CancellationToken ct = default) ``` Assign business id to process instance Assigns a business id to an already-running process instance that currently has none. The assignment is single and irreversible: only artifacts created after the assignment (for example future jobs, user tasks, decision instances, and message subscriptions) carry the business id, while existing artifacts are not retroactively enriched. Re-sending the same business id succeeds as a no-op. This endpoint is only useful while business id uniqueness enforcement is disabled; when it is enabled, the request is rejected with a 409 response. | Parameter | Type | Description | | -------------------- | ------------------------------------------------ | ----------- | | `processInstanceKey` | `ProcessInstanceKey` | | | `body` | `ProcessInstanceBusinessIdAssignmentInstruction` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task AssignProcessInstanceBusinessIdExample(ProcessInstanceKey processInstanceKey, BusinessId businessId) { using var client = CamundaClient.Create(); await client.AssignProcessInstanceBusinessIdAsync( processInstanceKey, new ProcessInstanceBusinessIdAssignmentInstruction { BusinessId = businessId, }); } ``` #### CancelProcessInstanceAsync(ProcessInstanceKey, CancelProcessInstanceRequest, CancellationToken) ```csharp public Task CancelProcessInstanceAsync(ProcessInstanceKey processInstanceKey, CancelProcessInstanceRequest body, CancellationToken ct = default) ``` Cancel process instance Cancels a running process instance. As a cancellation includes more than just the removal of the process instance resource, the cancellation resource must be posted. Cancellation can wait on listener-related processing; when that processing does not complete in time, this endpoint can return 504. Other gateway timeout causes are also possible. Retry with backoff and inspect listener worker availability and logs when this repeats. | Parameter | Type | Description | | -------------------- | ------------------------------ | ----------- | | `processInstanceKey` | `ProcessInstanceKey` | | | `body` | `CancelProcessInstanceRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task CancelProcessInstanceExample(ProcessInstanceKey processInstanceKey) { using var client = CamundaClient.Create(); await client.CancelProcessInstanceAsync( processInstanceKey, new CancelProcessInstanceRequest()); } ``` #### CancelProcessInstancesBatchOperationAsync(ProcessInstanceCancellationBatchOperationRequest, CancellationToken) ```csharp public Task CancelProcessInstancesBatchOperationAsync(ProcessInstanceCancellationBatchOperationRequest body, CancellationToken ct = default) ``` Cancel process instances (batch) Cancels multiple active or suspended process instances. Only ACTIVE and SUSPENDED root instances can be cancelled. A state filter narrows the batch to the given states. Requesting any state other than ACTIVE or SUSPENDED through the `$eq` or `$in` operators is rejected. Other state operators (`$neq`, `$exists`, `$like`) are applied as given, and the batch remains limited to ACTIVE and SUSPENDED instances. Without a state filter, both ACTIVE and SUSPENDED instances are selected. Any given filter for parentProcessInstanceKey is ignored and overridden during this batch operation. This is done asynchronously, the progress can be tracked using the batchOperationKey from the response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). | Parameter | Type | Description | | --------- | -------------------------------------------------- | ----------- | | `body` | `ProcessInstanceCancellationBatchOperationRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task CancelProcessInstancesBatchOperationExample() { using var client = CamundaClient.Create(); var result = await client.CancelProcessInstancesBatchOperationAsync( new ProcessInstanceCancellationBatchOperationRequest()); Console.WriteLine($"Batch operation key: {result.BatchOperationKey}"); } ``` #### CreateProcessInstanceAsync(ProcessInstanceCreationInstruction, CancellationToken) ```csharp public Task CreateProcessInstanceAsync(ProcessInstanceCreationInstruction body, CancellationToken ct = default) ``` Create process instance Creates and starts an instance of the specified process. The process definition to use to create the instance can be specified either using its unique key (as returned by Deploy resources), or using the BPMN process id and a version. If only the process definition id is given, the latest ACTIVE version is used. If no ACTIVE version exists, the request is rejected as not found. Waits for the completion of the process instance before returning a result when awaitCompletion is enabled. | Parameter | Type | Description | | --------- | ------------------------------------ | ----------- | | `body` | `ProcessInstanceCreationInstruction` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task CreateProcessInstanceByIdExample(ProcessDefinitionId processDefinitionId) { using var client = CamundaClient.Create(); var result = await client.CreateProcessInstanceAsync(new ProcessInstanceCreationInstructionById { ProcessDefinitionId = processDefinitionId, }); Console.WriteLine($"Process instance key: {result.ProcessInstanceKey}"); } public static async Task CreateProcessInstanceByKeyExample(ProcessDefinitionKey processDefinitionKey) { using var client = CamundaClient.Create(); var result = await client.CreateProcessInstanceAsync(new ProcessInstanceCreationInstructionByKey { ProcessDefinitionKey = processDefinitionKey, }); Console.WriteLine($"Process instance key: {result.ProcessInstanceKey}"); } ``` #### DeleteProcessInstanceAsync(ProcessInstanceKey, DeleteProcessInstanceRequest, CancellationToken) ```csharp public Task DeleteProcessInstanceAsync(ProcessInstanceKey processInstanceKey, DeleteProcessInstanceRequest body, CancellationToken ct = default) ``` Delete process instance Deletes a process instance. Only instances that are completed or terminated can be deleted. | Parameter | Type | Description | | -------------------- | ------------------------------ | ----------- | | `processInstanceKey` | `ProcessInstanceKey` | | | `body` | `DeleteProcessInstanceRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task DeleteProcessInstanceExample(ProcessInstanceKey processInstanceKey) { using var client = CamundaClient.Create(); await client.DeleteProcessInstanceAsync( processInstanceKey, new DeleteProcessInstanceRequest()); } ``` #### DeleteProcessInstancesBatchOperationAsync(ProcessInstanceDeletionBatchOperationRequest, CancellationToken) ```csharp public Task DeleteProcessInstancesBatchOperationAsync(ProcessInstanceDeletionBatchOperationRequest body, CancellationToken ct = default) ``` Delete process instances (batch) Delete multiple process instances. This will delete the historic data from secondary storage. Only process instances in a final state (COMPLETED or TERMINATED) can be deleted. This is done asynchronously, the progress can be tracked using the batchOperationKey from the response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). | Parameter | Type | Description | | --------- | ---------------------------------------------- | ----------- | | `body` | `ProcessInstanceDeletionBatchOperationRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task DeleteProcessInstancesBatchOperationExample() { using var client = CamundaClient.Create(); var result = await client.DeleteProcessInstancesBatchOperationAsync( new ProcessInstanceDeletionBatchOperationRequest()); Console.WriteLine($"Batch operation key: {result.BatchOperationKey}"); } ``` #### GetProcessInstanceAsync(ProcessInstanceKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetProcessInstanceAsync(ProcessInstanceKey processInstanceKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get process instance Get the process instance by the process instance key. | Parameter | Type | Description | | -------------------- | ------------------------------------------- | ----------- | | `processInstanceKey` | `ProcessInstanceKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetProcessInstanceExample(ProcessInstanceKey processInstanceKey) { using var client = CamundaClient.Create(); var result = await client.GetProcessInstanceAsync(processInstanceKey); Console.WriteLine($"Process instance: {result.ProcessDefinitionId}"); } ``` #### GetProcessInstanceCallHierarchyAsync(ProcessInstanceKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetProcessInstanceCallHierarchyAsync(ProcessInstanceKey processInstanceKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get call hierarchy Returns the call hierarchy for a given process instance, showing its ancestry up to the root instance. | Parameter | Type | Description | | -------------------- | ---------------------------- | ----------- | | `processInstanceKey` | `ProcessInstanceKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetProcessInstanceCallHierarchyExample(ProcessInstanceKey processInstanceKey) { using var client = CamundaClient.Create(); var result = await client.GetProcessInstanceCallHierarchyAsync( processInstanceKey); Console.WriteLine($"Call hierarchy: {result}"); } ``` #### GetProcessInstanceSequenceFlowsAsync(ProcessInstanceKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetProcessInstanceSequenceFlowsAsync(ProcessInstanceKey processInstanceKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get sequence flows Get sequence flows taken by the process instance. | Parameter | Type | Description | | -------------------- | ------------------------------------------------------------- | ----------- | | `processInstanceKey` | `ProcessInstanceKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetProcessInstanceSequenceFlowsExample(ProcessInstanceKey processInstanceKey) { using var client = CamundaClient.Create(); var result = await client.GetProcessInstanceSequenceFlowsAsync( processInstanceKey); foreach (var flow in result.Items) { Console.WriteLine($"Sequence flow: {flow}"); } } ``` #### GetProcessInstanceStatisticsAsync(ProcessInstanceKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetProcessInstanceStatisticsAsync(ProcessInstanceKey processInstanceKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get element instance statistics Get statistics about elements by the process instance key. | Parameter | Type | Description | | -------------------- | ----------------------------------------------------------------- | ----------- | | `processInstanceKey` | `ProcessInstanceKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetProcessInstanceStatisticsExample(ProcessInstanceKey processInstanceKey) { using var client = CamundaClient.Create(); var result = await client.GetProcessInstanceStatisticsAsync( processInstanceKey); foreach (var stat in result.Items) { Console.WriteLine($"Element: {stat.ElementId}"); } } ``` #### GetProcessInstanceStatisticsByDefinitionAsync(IncidentProcessInstanceStatisticsByDefinitionQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetProcessInstanceStatisticsByDefinitionAsync(IncidentProcessInstanceStatisticsByDefinitionQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get process instance statistics by definition Returns statistics for active process instances with incidents, grouped by process definition. The result set is scoped to a specific incident error hash code, which must be provided as a filter in the request body. | Parameter | Type | Description | | ------------- | ------------------------------------------------------------------------------ | ----------- | | `body` | `IncidentProcessInstanceStatisticsByDefinitionQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetProcessInstanceStatisticsByDefinitionExample() { using var client = CamundaClient.Create(); var result = await client.GetProcessInstanceStatisticsByDefinitionAsync( new IncidentProcessInstanceStatisticsByDefinitionQuery()); foreach (var stat in result.Items) { Console.WriteLine($"Definition: {stat.ProcessDefinitionKey}"); } } ``` #### GetProcessInstanceStatisticsByErrorAsync(IncidentProcessInstanceStatisticsByErrorQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetProcessInstanceStatisticsByErrorAsync(IncidentProcessInstanceStatisticsByErrorQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get process instance statistics by error Returns statistics for active process instances that currently have active incidents, grouped by incident error hash code. | Parameter | Type | Description | | ------------- | ------------------------------------------------------------------------- | ----------- | | `body` | `IncidentProcessInstanceStatisticsByErrorQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetProcessInstanceStatisticsByErrorExample() { using var client = CamundaClient.Create(); var result = await client.GetProcessInstanceStatisticsByErrorAsync( new IncidentProcessInstanceStatisticsByErrorQuery()); foreach (var stat in result.Items) { Console.WriteLine($"Error: {stat.ErrorMessage}"); } } ``` #### GetProcessInstanceWaitStateStatisticsAsync(ProcessInstanceKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetProcessInstanceWaitStateStatisticsAsync(ProcessInstanceKey processInstanceKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get wait state statistics Get statistics about waiting element instances by the process instance key, grouped by element id. | Parameter | Type | Description | | -------------------- | ------------------------------------------------------------------- | ----------- | | `processInstanceKey` | `ProcessInstanceKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetProcessInstanceWaitStateStatisticsExample(ProcessInstanceKey processInstanceKey) { using var client = CamundaClient.Create(); var result = await client.GetProcessInstanceWaitStateStatisticsAsync( processInstanceKey); foreach (var stat in result.Items) { Console.WriteLine($"Element: {stat.ElementId}, waiting: {stat.WaitingCount}"); } } ``` #### MigrateProcessInstanceAsync(ProcessInstanceKey, ProcessInstanceMigrationInstruction, CancellationToken) ```csharp public Task MigrateProcessInstanceAsync(ProcessInstanceKey processInstanceKey, ProcessInstanceMigrationInstruction body, CancellationToken ct = default) ``` Migrate process instance Migrates a process instance to a new process definition. This request can contain multiple mapping instructions to define mapping between the active process instance's elements and target process definition elements. Use this to upgrade a process instance to a new version of a process or to a different process definition, e.g. to keep your running instances up-to-date with the latest process improvements. | Parameter | Type | Description | | -------------------- | ------------------------------------- | ----------- | | `processInstanceKey` | `ProcessInstanceKey` | | | `body` | `ProcessInstanceMigrationInstruction` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task MigrateProcessInstanceExample(ProcessInstanceKey processInstanceKey, ProcessDefinitionKey targetProcessDefinitionKey) { using var client = CamundaClient.Create(); await client.MigrateProcessInstanceAsync( processInstanceKey, new ProcessInstanceMigrationInstruction { TargetProcessDefinitionKey = targetProcessDefinitionKey, }); } ``` #### MigrateProcessInstancesBatchOperationAsync(ProcessInstanceMigrationBatchOperationRequest, CancellationToken) ```csharp public Task MigrateProcessInstancesBatchOperationAsync(ProcessInstanceMigrationBatchOperationRequest body, CancellationToken ct = default) ``` Migrate process instances (batch) Migrate multiple process instances. Since only process instances with ACTIVE state can be migrated, any given filters for state are ignored and overridden during this batch operation. This is done asynchronously, the progress can be tracked using the batchOperationKey from the response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). | Parameter | Type | Description | | --------- | ----------------------------------------------- | ----------- | | `body` | `ProcessInstanceMigrationBatchOperationRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task MigrateProcessInstancesBatchOperationExample(ProcessDefinitionKey targetProcessDefinitionKey) { using var client = CamundaClient.Create(); var result = await client.MigrateProcessInstancesBatchOperationAsync( new ProcessInstanceMigrationBatchOperationRequest { Filter = new ProcessInstanceFilter(), MigrationPlan = new ProcessInstanceMigrationBatchOperationPlan { TargetProcessDefinitionKey = targetProcessDefinitionKey, }, }); Console.WriteLine($"Batch operation key: {result.BatchOperationKey}"); } ``` #### ModifyProcessInstanceAsync(ProcessInstanceKey, ProcessInstanceModificationInstruction, CancellationToken) ```csharp public Task ModifyProcessInstanceAsync(ProcessInstanceKey processInstanceKey, ProcessInstanceModificationInstruction body, CancellationToken ct = default) ``` Modify process instance Modifies a running process instance. This request can contain multiple instructions to activate an element of the process or to terminate an active instance of an element. Use this to repair a process instance that is stuck on an element or took an unintended path. For example, because an external system is not available or doesn't respond as expected. | Parameter | Type | Description | | -------------------- | ---------------------------------------- | ----------- | | `processInstanceKey` | `ProcessInstanceKey` | | | `body` | `ProcessInstanceModificationInstruction` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task ModifyProcessInstanceExample(ProcessInstanceKey processInstanceKey) { using var client = CamundaClient.Create(); await client.ModifyProcessInstanceAsync( processInstanceKey, new ProcessInstanceModificationInstruction()); } ``` #### ModifyProcessInstancesBatchOperationAsync(ProcessInstanceModificationBatchOperationRequest, CancellationToken) ```csharp public Task ModifyProcessInstancesBatchOperationAsync(ProcessInstanceModificationBatchOperationRequest body, CancellationToken ct = default) ``` Modify process instances (batch) Modify multiple process instances. Since only process instances with ACTIVE state can be modified, any given filters for state are ignored and overridden during this batch operation. In contrast to single modification operation, it is not possible to add variable instructions or modify by element key. It is only possible to use the element id of the source and target. This is done asynchronously, the progress can be tracked using the batchOperationKey from the response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). | Parameter | Type | Description | | --------- | -------------------------------------------------- | ----------- | | `body` | `ProcessInstanceModificationBatchOperationRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task ModifyProcessInstancesBatchOperationExample() { using var client = CamundaClient.Create(); var result = await client.ModifyProcessInstancesBatchOperationAsync( new ProcessInstanceModificationBatchOperationRequest()); Console.WriteLine($"Batch operation key: {result.BatchOperationKey}"); } ``` #### ResolveIncidentsBatchOperationAsync(ProcessInstanceIncidentResolutionBatchOperationRequest, CancellationToken) ```csharp public Task ResolveIncidentsBatchOperationAsync(ProcessInstanceIncidentResolutionBatchOperationRequest body, CancellationToken ct = default) ``` Resolve related incidents (batch) Resolves multiple instances of process instances. Since only process instances with ACTIVE state can have unresolved incidents, any given filters for state are ignored and overridden during this batch operation. This is done asynchronously, the progress can be tracked using the batchOperationKey from the response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). | Parameter | Type | Description | | --------- | -------------------------------------------------------- | ----------- | | `body` | `ProcessInstanceIncidentResolutionBatchOperationRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task ResolveIncidentsBatchOperationExample() { using var client = CamundaClient.Create(); var result = await client.ResolveIncidentsBatchOperationAsync( new ProcessInstanceIncidentResolutionBatchOperationRequest()); Console.WriteLine($"Batch operation key: {result.BatchOperationKey}"); } ``` #### ResolveProcessInstanceIncidentsAsync(ProcessInstanceKey, CancellationToken) ```csharp public Task ResolveProcessInstanceIncidentsAsync(ProcessInstanceKey processInstanceKey, CancellationToken ct = default) ``` Resolve related incidents Creates a batch operation to resolve multiple incidents of a process instance. | Parameter | Type | Description | | -------------------- | -------------------- | ----------- | | `processInstanceKey` | `ProcessInstanceKey` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task ResolveProcessInstanceIncidentsExample(ProcessInstanceKey processInstanceKey) { using var client = CamundaClient.Create(); var result = await client.ResolveProcessInstanceIncidentsAsync( processInstanceKey); Console.WriteLine($"Batch operation key: {result.BatchOperationKey}"); } ``` #### ResumeProcessInstanceAsync(ProcessInstanceKey, ResumeProcessInstanceRequest, CancellationToken) ```csharp public Task ResumeProcessInstanceAsync(ProcessInstanceKey processInstanceKey, ResumeProcessInstanceRequest body, CancellationToken ct = default) ``` Resume process instance Resumes a suspended process instance, returning it to the ACTIVE state and continuing processing. Only process instances in the SUSPENDED state can be resumed. A child process instance can be resumed independently of its parent or root process instance; resumption does not cascade to or from related instances. | Parameter | Type | Description | | -------------------- | ------------------------------ | ----------- | | `processInstanceKey` | `ProcessInstanceKey` | | | `body` | `ResumeProcessInstanceRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task ResumeProcessInstanceExample(ProcessInstanceKey processInstanceKey) { using var client = CamundaClient.Create(); await client.ResumeProcessInstanceAsync( processInstanceKey, new ResumeProcessInstanceRequest()); } ``` #### ResumeProcessInstancesBatchOperationAsync(ProcessInstanceResumptionBatchOperationRequest, CancellationToken) ```csharp public Task ResumeProcessInstancesBatchOperationAsync(ProcessInstanceResumptionBatchOperationRequest body, CancellationToken ct = default) ``` Resume process instances (batch) Resumes multiple suspended process instances. Any given filter for state or parentProcessInstanceKey is ignored and overridden, as only SUSPENDED process instances can be resumed and resumption does not cascade between parent and child instances, so child instances are resumed independently of their parent or root instance. This is done asynchronously, the progress can be tracked using the batchOperationKey from the response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). | Parameter | Type | Description | | --------- | ------------------------------------------------ | ----------- | | `body` | `ProcessInstanceResumptionBatchOperationRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task ResumeProcessInstancesBatchOperationExample() { using var client = CamundaClient.Create(); var result = await client.ResumeProcessInstancesBatchOperationAsync( new ProcessInstanceResumptionBatchOperationRequest { Filter = new ProcessInstanceFilter(), }); Console.WriteLine($"Batch operation key: {result.BatchOperationKey}"); } ``` #### SearchProcessInstanceIncidentsAsync(ProcessInstanceKey, IncidentSearchQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchProcessInstanceIncidentsAsync(ProcessInstanceKey processInstanceKey, IncidentSearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search related incidents Search for incidents caused by the process instance or any of its called process or decision instances. Although the `processInstanceKey` is provided as a path parameter to indicate the root process instance, you may also include a `processInstanceKey` within the filter object to narrow results to specific child process instances. This is useful, for example, if you want to isolate incidents associated with subprocesses or called processes under the root instance while excluding incidents directly tied to the root. | Parameter | Type | Description | | -------------------- | ----------------------------------------------- | ----------- | | `processInstanceKey` | `ProcessInstanceKey` | | | `body` | `IncidentSearchQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchProcessInstanceIncidentsExample(ProcessInstanceKey processInstanceKey) { using var client = CamundaClient.Create(); var result = await client.SearchProcessInstanceIncidentsAsync( processInstanceKey, new IncidentSearchQuery()); foreach (var incident in result.Items) { Console.WriteLine($"Incident: {incident.IncidentKey}"); } } ``` #### SearchProcessInstancesAsync(ProcessInstanceSearchQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchProcessInstancesAsync(ProcessInstanceSearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search process instances Search for process instances based on given criteria. | Parameter | Type | Description | | ------------- | ------------------------------------------------------ | ----------- | | `body` | `ProcessInstanceSearchQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchProcessInstancesExample() { using var client = CamundaClient.Create(); var result = await client.SearchProcessInstancesAsync(new ProcessInstanceSearchQuery()); foreach (var instance in result.Items) { Console.WriteLine($"Process instance: {instance.ProcessInstanceKey}"); } } ``` #### SuspendProcessInstanceAsync(ProcessInstanceKey, SuspendProcessInstanceRequest, CancellationToken) ```csharp public Task SuspendProcessInstanceAsync(ProcessInstanceKey processInstanceKey, SuspendProcessInstanceRequest body, CancellationToken ct = default) ``` Suspend process instance Suspends a running process instance, pausing further processing until it is resumed. Only process instances in the ACTIVE state can be suspended. A child process instance can be suspended independently of its parent or root process instance; suspension does not cascade to or from related instances. | Parameter | Type | Description | | -------------------- | ------------------------------- | ----------- | | `processInstanceKey` | `ProcessInstanceKey` | | | `body` | `SuspendProcessInstanceRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SuspendProcessInstanceExample(ProcessInstanceKey processInstanceKey) { using var client = CamundaClient.Create(); await client.SuspendProcessInstanceAsync( processInstanceKey, new SuspendProcessInstanceRequest()); } ``` #### SuspendProcessInstancesBatchOperationAsync(ProcessInstanceSuspensionBatchOperationRequest, CancellationToken) ```csharp public Task SuspendProcessInstancesBatchOperationAsync(ProcessInstanceSuspensionBatchOperationRequest body, CancellationToken ct = default) ``` Suspend process instances (batch) Suspends multiple running process instances. Any given filter for state or parentProcessInstanceKey is ignored and overridden, as only ACTIVE process instances can be suspended and suspension does not cascade between parent and child instances, so child instances are suspended independently of their parent or root instance. This is done asynchronously, the progress can be tracked using the batchOperationKey from the response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). | Parameter | Type | Description | | --------- | ------------------------------------------------ | ----------- | | `body` | `ProcessInstanceSuspensionBatchOperationRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SuspendProcessInstancesBatchOperationExample() { using var client = CamundaClient.Create(); var result = await client.SuspendProcessInstancesBatchOperationAsync( new ProcessInstanceSuspensionBatchOperationRequest { Filter = new ProcessInstanceFilter(), }); Console.WriteLine($"Batch operation key: {result.BatchOperationKey}"); } ``` ### Jobs #### CreateJobWorker(JobWorkerConfig, JobHandler) ```csharp public JobWorker CreateJobWorker(JobWorkerConfig config, JobHandler handler) ``` Create a job worker that polls for and processes jobs of the specified type. The handler receives an `ActivatedJob` and returns variables to auto-complete. Throw `BpmnErrorException` for BPMN errors, `JobFailureException` for explicit failures, or any other exception to auto-fail with `retries - 1`. | Parameter | Type | Description | | --------- | ----------------- | ------------------------------------------------------------------------------------- | | `config` | `JobWorkerConfig` | Worker configuration (job type, timeout, concurrency). | | `handler` | `JobHandler` | Async handler that processes each job. Return output variables (or null) to complete. | **Returns:** `JobWorker` — The running `JobWorker` instance. **Example** ```csharp public static void CreateJobWorkerExample() { using var client = CamundaClient.Create(); var worker = client.CreateJobWorker( new JobWorkerConfig { JobType = "payment-service" }, async (job, ct) => { Console.WriteLine($"Processing job {job.JobKey}"); return new { Success = true }; }); } ``` #### CreateJobWorker(JobWorkerConfig, Func\) ```csharp public JobWorker CreateJobWorker(JobWorkerConfig config, Func handler) ``` Create a job worker with a handler that doesn't return output variables. The job is auto-completed with no variables on success. | Parameter | Type | Description | | --------- | --------------------------------------------- | ----------- | | `config` | `JobWorkerConfig` | | | `handler` | `Func` | | **Returns:** `JobWorker` **Example** ```csharp public static void CreateJobWorkerExample() { using var client = CamundaClient.Create(); var worker = client.CreateJobWorker( new JobWorkerConfig { JobType = "payment-service" }, async (job, ct) => { Console.WriteLine($"Processing job {job.JobKey}"); return new { Success = true }; }); } ``` #### ActivateJobsAsync(JobActivationRequest, CancellationToken) ```csharp public Task ActivateJobsAsync(JobActivationRequest body, CancellationToken ct = default) ``` Activate jobs Iterate through all known partitions and activate jobs up to the requested maximum. | Parameter | Type | Description | | --------- | ---------------------- | ----------- | | `body` | `JobActivationRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task ActivateJobsExample() { using var client = CamundaClient.Create(); var result = await client.ActivateJobsAsync(new JobActivationRequest { Type = "my-job-type", MaxJobsToActivate = 10, Timeout = 300000, Worker = "my-worker", }); foreach (var job in result.Jobs) { Console.WriteLine($"Job: {job.JobKey}"); } } ``` #### CompleteJobAsync(JobKey, JobCompletionRequest, CancellationToken) ```csharp public Task CompleteJobAsync(JobKey jobKey, JobCompletionRequest body, CancellationToken ct = default) ``` Complete job Complete a job with the given payload, which allows completing the associated service task. | Parameter | Type | Description | | --------- | ---------------------- | ----------- | | `jobKey` | `JobKey` | | | `body` | `JobCompletionRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task CompleteJobExample(JobKey jobKey) { using var client = CamundaClient.Create(); await client.CompleteJobAsync( jobKey, new JobCompletionRequest()); } ``` #### FailJobAsync(JobKey, JobFailRequest, CancellationToken) ```csharp public Task FailJobAsync(JobKey jobKey, JobFailRequest body, CancellationToken ct = default) ``` Fail job Mark the job as failed. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `jobKey` | `JobKey` | | | `body` | `JobFailRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task FailJobExample(JobKey jobKey) { using var client = CamundaClient.Create(); await client.FailJobAsync( jobKey, new JobFailRequest { Retries = 3, RetryBackOff = 5000, ErrorMessage = "Something went wrong", }); } ``` #### GetGlobalJobStatisticsAsync(DateTimeOffset, DateTimeOffset, string?, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetGlobalJobStatisticsAsync(DateTimeOffset from, DateTimeOffset to, string? jobType = null, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Global job statistics Returns global aggregated counts for jobs. Filter by the creation time window (required) and optionally by jobType. | Parameter | Type | Description | | ------------- | ---------------------------------------------------- | ----------- | | `from` | `DateTimeOffset` | | | `to` | `DateTimeOffset` | | | `jobType` | `String` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetGlobalJobStatisticsExample() { using var client = CamundaClient.Create(); var result = await client.GetGlobalJobStatisticsAsync( from: new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero), to: new DateTimeOffset(2024, 12, 31, 23, 59, 59, TimeSpan.Zero)); Console.WriteLine($"Global job stats: {result}"); } ``` #### GetJobErrorStatisticsAsync(JobErrorStatisticsQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetJobErrorStatisticsAsync(JobErrorStatisticsQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get error metrics for a job type Returns aggregated metrics per error for the given jobType. | Parameter | Type | Description | | ------------- | --------------------------------------------------- | ----------- | | `body` | `JobErrorStatisticsQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetJobErrorStatisticsExample() { using var client = CamundaClient.Create(); var result = await client.GetJobErrorStatisticsAsync( new JobErrorStatisticsQuery()); foreach (var stat in result.Items) { Console.WriteLine($"Error: {stat.ErrorCode}"); } } ``` #### GetJobTimeSeriesStatisticsAsync(JobTimeSeriesStatisticsQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetJobTimeSeriesStatisticsAsync(JobTimeSeriesStatisticsQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get time-series metrics for a job type Returns a list of time-bucketed metrics ordered ascending by time. The `from` and `to` fields select the time window of interest. Each item in the response corresponds to one time bucket of the requested resolution. | Parameter | Type | Description | | ------------- | -------------------------------------------------------- | ----------- | | `body` | `JobTimeSeriesStatisticsQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetJobTimeSeriesStatisticsExample() { using var client = CamundaClient.Create(); var result = await client.GetJobTimeSeriesStatisticsAsync( new JobTimeSeriesStatisticsQuery()); foreach (var stat in result.Items) { Console.WriteLine($"Time series: {stat}"); } } ``` #### GetJobTypeStatisticsAsync(JobTypeStatisticsQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetJobTypeStatisticsAsync(JobTypeStatisticsQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get job statistics by type Get statistics about jobs, grouped by job type. | Parameter | Type | Description | | ------------- | -------------------------------------------------- | ----------- | | `body` | `JobTypeStatisticsQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetJobTypeStatisticsExample() { using var client = CamundaClient.Create(); var result = await client.GetJobTypeStatisticsAsync( new JobTypeStatisticsQuery()); foreach (var stat in result.Items) { Console.WriteLine($"Job type: {stat.JobType}"); } } ``` #### GetJobWorkerStatisticsAsync(JobWorkerStatisticsQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetJobWorkerStatisticsAsync(JobWorkerStatisticsQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get job statistics by worker Get statistics about jobs, grouped by worker, for a given job type. | Parameter | Type | Description | | ------------- | ---------------------------------------------------- | ----------- | | `body` | `JobWorkerStatisticsQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetJobWorkerStatisticsExample() { using var client = CamundaClient.Create(); var result = await client.GetJobWorkerStatisticsAsync( new JobWorkerStatisticsQuery()); foreach (var stat in result.Items) { Console.WriteLine($"Worker: {stat.Worker}"); } } ``` #### SearchJobsAsync(JobSearchQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchJobsAsync(JobSearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search jobs Search for jobs based on given criteria. | Parameter | Type | Description | | ------------- | ------------------------------------------ | ----------- | | `body` | `JobSearchQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchJobsExample() { using var client = CamundaClient.Create(); var result = await client.SearchJobsAsync(new JobSearchQuery()); foreach (var job in result.Items) { Console.WriteLine($"Job: {job.JobKey}"); } } ``` #### ThrowJobErrorAsync(JobKey, JobErrorRequest, CancellationToken) ```csharp public Task ThrowJobErrorAsync(JobKey jobKey, JobErrorRequest body, CancellationToken ct = default) ``` Throw error for job Reports a business error (i.e. non-technical) that occurs while processing a job. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `jobKey` | `JobKey` | | | `body` | `JobErrorRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task ThrowJobErrorExample(JobKey jobKey) { using var client = CamundaClient.Create(); await client.ThrowJobErrorAsync( jobKey, new JobErrorRequest { ErrorCode = "VALIDATION_ERROR", ErrorMessage = "Input validation failed", }); } ``` #### UpdateJobAsync(JobKey, JobUpdateRequest, CancellationToken) ```csharp public Task UpdateJobAsync(JobKey jobKey, JobUpdateRequest body, CancellationToken ct = default) ``` Update job Update a job with the given key. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `jobKey` | `JobKey` | | | `body` | `JobUpdateRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task UpdateJobExample(JobKey jobKey) { using var client = CamundaClient.Create(); await client.UpdateJobAsync( jobKey, new JobUpdateRequest { Changeset = new JobChangeset { Retries = 3 }, }); } ``` #### UpdateJobsBatchOperationAsync(JobBatchUpdateRequest, CancellationToken) ```csharp public Task UpdateJobsBatchOperationAsync(JobBatchUpdateRequest body, CancellationToken ct = default) ``` Update jobs (batch) Creates a batch operation to update jobs matching the given filter. At least one changeset field must be non-null. This is done asynchronously; the progress can be tracked using the batchOperationKey from the response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). | Parameter | Type | Description | | --------- | ----------------------- | ----------- | | `body` | `JobBatchUpdateRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task UpdateJobsBatchOperationExample() { using var client = CamundaClient.Create(); var result = await client.UpdateJobsBatchOperationAsync( new JobBatchUpdateRequest { Filter = new JobFilter { Type = new StringFilterProperty { Eq = "my-job-type" }, }, Changeset = new JobChangeset { Retries = 3 }, }); Console.WriteLine($"Batch operation key: {result.BatchOperationKey}"); } ``` ### Job Workers #### RunWorkersAsync(TimeSpan?, CancellationToken) ```csharp public Task RunWorkersAsync(TimeSpan? gracePeriod = null, CancellationToken ct = default) ``` Block until cancellation is requested, keeping all registered workers alive. This is the typical entry point for worker-only applications. When the token is cancelled, all workers are stopped gracefully. | Parameter | Type | Description | | ------------- | -------------------- | ------------------------------------------------------------------------------- | | `gracePeriod` | `Nullable` | Time to wait for in-flight jobs to finish during shutdown. Default: 10 seconds. | | `ct` | `CancellationToken` | Cancellation token that signals shutdown. | **Returns:** `Task` **Example** ```csharp public static async Task RunWorkersExample(CancellationToken ct) { using var client = CamundaClient.Create(); client.CreateJobWorker( new JobWorkerConfig { JobType = "payment-service" }, async (job, jobCt) => { Console.WriteLine($"Processing job {job.JobKey}"); return null; }); await client.RunWorkersAsync(gracePeriod: TimeSpan.FromSeconds(10), ct); } ``` #### StopAllWorkersAsync(TimeSpan?) ```csharp public Task StopAllWorkersAsync(TimeSpan? gracePeriod = null) ``` Stop all registered workers and wait for in-flight jobs to drain. | Parameter | Type | Description | | ------------- | -------------------- | ----------- | | `gracePeriod` | `Nullable` | | **Returns:** `Task` **Example** ```csharp public static async Task StopAllWorkersExample() { using var client = CamundaClient.Create(); client.CreateJobWorker( new JobWorkerConfig { JobType = "payment-service" }, async (job, ct) => { Console.WriteLine($"Processing job {job.JobKey}"); return null; }); await client.StopAllWorkersAsync(gracePeriod: TimeSpan.FromSeconds(5)); } ``` #### GetWorkers() ```csharp public IReadOnlyList GetWorkers() ``` Returns a snapshot of all registered workers. **Returns:** `IReadOnlyList` **Example** ```csharp public static void GetWorkersExample() { using var client = CamundaClient.Create(); client.CreateJobWorker( new JobWorkerConfig { JobType = "payment-service" }, async (job, ct) => { Console.WriteLine($"Processing job {job.JobKey}"); return null; }); var workers = client.GetWorkers(); foreach (var worker in workers) { Console.WriteLine($"Worker: {worker.Name}, Active: {worker.ActiveJobs}"); } } ``` ### Elements #### ActivateAdHocSubProcessActivitiesAsync(ElementInstanceKey, AdHocSubProcessActivateActivitiesInstruction, CancellationToken) ```csharp public Task ActivateAdHocSubProcessActivitiesAsync(ElementInstanceKey adHocSubProcessInstanceKey, AdHocSubProcessActivateActivitiesInstruction body, CancellationToken ct = default) ``` Activate activities within an ad-hoc sub-process Activates selected activities within an ad-hoc sub-process identified by element ID. The provided element IDs must exist within the ad-hoc sub-process instance identified by the provided adHocSubProcessInstanceKey. | Parameter | Type | Description | | ---------------------------- | ---------------------------------------------- | ----------- | | `adHocSubProcessInstanceKey` | `ElementInstanceKey` | | | `body` | `AdHocSubProcessActivateActivitiesInstruction` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task ActivateAdHocSubProcessActivitiesExample(ElementInstanceKey elementInstanceKey) { using var client = CamundaClient.Create(); await client.ActivateAdHocSubProcessActivitiesAsync( elementInstanceKey, new AdHocSubProcessActivateActivitiesInstruction()); } ``` #### GetElementInstanceAsync(ElementInstanceKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetElementInstanceAsync(ElementInstanceKey elementInstanceKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get element instance Returns element instance as JSON. | Parameter | Type | Description | | -------------------- | ------------------------------------------- | ----------- | | `elementInstanceKey` | `ElementInstanceKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetElementInstanceExample(ElementInstanceKey elementInstanceKey) { using var client = CamundaClient.Create(); var result = await client.GetElementInstanceAsync( elementInstanceKey); Console.WriteLine($"Element: {result.ElementId}"); } ``` #### SearchElementInstanceWaitStatesAsync(ElementInstanceWaitStateQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchElementInstanceWaitStatesAsync(ElementInstanceWaitStateQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search element instance wait states Returns the wait states for element instances matching the given filter. | Parameter | Type | Description | | ------------- | --------------------------------------------------------- | ----------- | | `body` | `ElementInstanceWaitStateQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchElementInstanceWaitStatesExample(ProcessInstanceKey processInstanceKey) { using var client = CamundaClient.Create(); var result = await client.SearchElementInstanceWaitStatesAsync( new ElementInstanceWaitStateQuery { Filter = new ElementInstanceWaitStateFilter { ProcessInstanceKey = new ProcessInstanceKeyFilterProperty { Eq = processInstanceKey, }, }, }); foreach (var waitState in result.Items) { var details = waitState.Details switch { JobWaitStateDetails job => $"waiting on job '{job.JobType}'", MessageWaitStateDetails message => $"waiting for message '{message.MessageName}'", _ => "waiting", }; Console.WriteLine($"{waitState.ElementId}: {details}"); } } ``` #### SearchElementInstancesAsync(ElementInstanceSearchQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchElementInstancesAsync(ElementInstanceSearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search element instances Search for element instances based on given criteria. | Parameter | Type | Description | | ------------- | ------------------------------------------------------ | ----------- | | `body` | `ElementInstanceSearchQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchElementInstancesExample() { using var client = CamundaClient.Create(); var result = await client.SearchElementInstancesAsync( new ElementInstanceSearchQuery()); foreach (var ei in result.Items) { Console.WriteLine($"Element instance: {ei.ElementInstanceKey}"); } } ``` ### Groups #### AssignClientToGroupAsync(GroupId, ClientId, CancellationToken) ```csharp public Task AssignClientToGroupAsync(GroupId groupId, ClientId clientId, CancellationToken ct = default) ``` Assign a client to a group Assigns a client to a group, making it a member of the group. Members of the group inherit the group authorizations, roles, and tenant assignments. | Parameter | Type | Description | | ---------- | ------------------- | ----------- | | `groupId` | `GroupId` | | | `clientId` | `ClientId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### AssignMappingRuleToGroupAsync(GroupId, MappingRuleId, CancellationToken) ```csharp public Task AssignMappingRuleToGroupAsync(GroupId groupId, MappingRuleId mappingRuleId, CancellationToken ct = default) ``` Assign a mapping rule to a group Assigns a mapping rule to a group. | Parameter | Type | Description | | --------------- | ------------------- | ----------- | | `groupId` | `GroupId` | | | `mappingRuleId` | `MappingRuleId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### AssignUserToGroupAsync(GroupId, Username, CancellationToken) ```csharp public Task AssignUserToGroupAsync(GroupId groupId, Username username, CancellationToken ct = default) ``` Assign a user to a group Assigns a user to a group, making the user a member of the group. Group members inherit the group authorizations, roles, and tenant assignments. | Parameter | Type | Description | | ---------- | ------------------- | ----------- | | `groupId` | `GroupId` | | | `username` | `Username` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### CreateGroupAsync(GroupCreateRequest, CancellationToken) ```csharp public Task CreateGroupAsync(GroupCreateRequest body, CancellationToken ct = default) ``` Create group Create a new group. The supplied `groupId` is validated against `^[a-zA-Z0-9_~@.+-]+$` (max 256 characters) by `IdentifierValidator.validateId` in the runtime. This strict validation applies wherever the Groups API is available: in OIDC deployments that set `camunda.security.authentication.oidc.groupsClaim` the Groups API (including this endpoint) is disabled entirely, so group CRUD never sees externally-minted IdP IDs. The BYOG relaxation only loosens validation when a group is referenced _as a member_ of a role or tenant (`assignRoleToGroup`, `assignGroupToTenant`); group CRUD itself always uses the strict default-id regex. The constraint is not advertised on the `GroupId` schema so that the same schema can be reused at member-reference sites without falsely rejecting externally-minted IdP group IDs there. | Parameter | Type | Description | | --------- | -------------------- | ----------- | | `body` | `GroupCreateRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task CreateGroupExample(GroupId groupId) { using var client = CamundaClient.Create(); var result = await client.CreateGroupAsync(new GroupCreateRequest { GroupId = groupId, Name = "Engineering", }); Console.WriteLine($"Group key: {result.GroupId}"); } ``` #### DeleteGroupAsync(GroupId, CancellationToken) ```csharp public Task DeleteGroupAsync(GroupId groupId, CancellationToken ct = default) ``` Delete group Deletes the group with the given ID. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `groupId` | `GroupId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### GetGroupAsync(GroupId, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetGroupAsync(GroupId groupId, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get group Get a group by its ID. | Parameter | Type | Description | | ------------- | --------------------------------- | ----------- | | `groupId` | `GroupId` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### SearchClientsForGroupAsync(GroupId, GroupClientSearchQueryRequest, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchClientsForGroupAsync(GroupId groupId, GroupClientSearchQueryRequest body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search group clients Search clients assigned to a group. | Parameter | Type | Description | | ------------- | --------------------------------------------- | ----------- | | `groupId` | `GroupId` | | | `body` | `GroupClientSearchQueryRequest` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### SearchGroupsAsync(GroupSearchQueryRequest, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchGroupsAsync(GroupSearchQueryRequest body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search groups Search for groups based on given criteria. | Parameter | Type | Description | | ------------- | -------------------------------------------- | ----------- | | `body` | `GroupSearchQueryRequest` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchGroupsExample() { using var client = CamundaClient.Create(); var result = await client.SearchGroupsAsync(new GroupSearchQueryRequest()); foreach (var group in result.Items) { Console.WriteLine($"Group: {group.Name}"); } } ``` #### SearchMappingRulesForGroupAsync(GroupId, MappingRuleSearchQueryRequest, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchMappingRulesForGroupAsync(GroupId groupId, MappingRuleSearchQueryRequest body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search group mapping rules Search mapping rules assigned to a group. | Parameter | Type | Description | | ------------- | -------------------------------------------------- | ----------- | | `groupId` | `GroupId` | | | `body` | `MappingRuleSearchQueryRequest` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### SearchUsersForGroupAsync(GroupId, GroupUserSearchQueryRequest, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchUsersForGroupAsync(GroupId groupId, GroupUserSearchQueryRequest body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search group users Search users assigned to a group. | Parameter | Type | Description | | ------------- | ------------------------------------------- | ----------- | | `groupId` | `GroupId` | | | `body` | `GroupUserSearchQueryRequest` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### UnassignClientFromGroupAsync(GroupId, ClientId, CancellationToken) ```csharp public Task UnassignClientFromGroupAsync(GroupId groupId, ClientId clientId, CancellationToken ct = default) ``` Unassign a client from a group Unassigns a client from a group. The client is removed as a group member, with associated authorizations, roles, and tenant assignments no longer applied. | Parameter | Type | Description | | ---------- | ------------------- | ----------- | | `groupId` | `GroupId` | | | `clientId` | `ClientId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### UnassignMappingRuleFromGroupAsync(GroupId, MappingRuleId, CancellationToken) ```csharp public Task UnassignMappingRuleFromGroupAsync(GroupId groupId, MappingRuleId mappingRuleId, CancellationToken ct = default) ``` Unassign a mapping rule from a group Unassigns a mapping rule from a group. | Parameter | Type | Description | | --------------- | ------------------- | ----------- | | `groupId` | `GroupId` | | | `mappingRuleId` | `MappingRuleId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### UnassignUserFromGroupAsync(GroupId, Username, CancellationToken) ```csharp public Task UnassignUserFromGroupAsync(GroupId groupId, Username username, CancellationToken ct = default) ``` Unassign a user from a group Unassigns a user from a group. The user is removed as a group member, with associated authorizations, roles, and tenant assignments no longer applied. | Parameter | Type | Description | | ---------- | ------------------- | ----------- | | `groupId` | `GroupId` | | | `username` | `Username` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### UpdateGroupAsync(GroupId, GroupUpdateRequest, CancellationToken) ```csharp public Task UpdateGroupAsync(GroupId groupId, GroupUpdateRequest body, CancellationToken ct = default) ``` Update group Update a group with the given ID. | Parameter | Type | Description | | --------- | -------------------- | ----------- | | `groupId` | `GroupId` | | | `body` | `GroupUpdateRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` ### Tenants #### AssignClientToTenantAsync(TenantId, ClientId, CancellationToken) ```csharp public Task AssignClientToTenantAsync(TenantId tenantId, ClientId clientId, CancellationToken ct = default) ``` Assign a client to a tenant Assign the client to the specified tenant. The client can then access tenant data and perform authorized actions. | Parameter | Type | Description | | ---------- | ------------------- | ----------- | | `tenantId` | `TenantId` | | | `clientId` | `ClientId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### AssignGroupToTenantAsync(TenantId, GroupId, CancellationToken) ```csharp public Task AssignGroupToTenantAsync(TenantId tenantId, GroupId groupId, CancellationToken ct = default) ``` Assign a group to a tenant Assigns a group to a specified tenant. Group members (users, clients) can then access tenant data and perform authorized actions. | Parameter | Type | Description | | ---------- | ------------------- | ----------- | | `tenantId` | `TenantId` | | | `groupId` | `GroupId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### AssignMappingRuleToTenantAsync(TenantId, MappingRuleId, CancellationToken) ```csharp public Task AssignMappingRuleToTenantAsync(TenantId tenantId, MappingRuleId mappingRuleId, CancellationToken ct = default) ``` Assign a mapping rule to a tenant Assign a single mapping rule to a specified tenant. | Parameter | Type | Description | | --------------- | ------------------- | ----------- | | `tenantId` | `TenantId` | | | `mappingRuleId` | `MappingRuleId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### AssignRoleToTenantAsync(TenantId, RoleId, CancellationToken) ```csharp public Task AssignRoleToTenantAsync(TenantId tenantId, RoleId roleId, CancellationToken ct = default) ``` Assign a role to a tenant Assigns a role to a specified tenant. Users, Clients or Groups, that have the role assigned, will get access to the tenant's data and can perform actions according to their authorizations. | Parameter | Type | Description | | ---------- | ------------------- | ----------- | | `tenantId` | `TenantId` | | | `roleId` | `RoleId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### AssignUserToTenantAsync(TenantId, Username, CancellationToken) ```csharp public Task AssignUserToTenantAsync(TenantId tenantId, Username username, CancellationToken ct = default) ``` Assign a user to a tenant Assign a single user to a specified tenant. The user can then access tenant data and perform authorized actions. | Parameter | Type | Description | | ---------- | ------------------- | ----------- | | `tenantId` | `TenantId` | | | `username` | `Username` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task AssignUserToTenantExample(TenantId tenantId, Username username) { using var client = CamundaClient.Create(); await client.AssignUserToTenantAsync( tenantId, username); } ``` #### CreateTenantAsync(TenantCreateRequest, CancellationToken) ```csharp public Task CreateTenantAsync(TenantCreateRequest body, CancellationToken ct = default) ``` Create tenant Creates a new tenant. | Parameter | Type | Description | | --------- | --------------------- | ----------- | | `body` | `TenantCreateRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task CreateTenantExample(TenantId tenantId) { using var client = CamundaClient.Create(); var result = await client.CreateTenantAsync(new TenantCreateRequest { TenantId = tenantId, Name = "Acme Corporation", }); Console.WriteLine($"Tenant key: {result.TenantId}"); } ``` #### DeleteTenantAsync(TenantId, CancellationToken) ```csharp public Task DeleteTenantAsync(TenantId tenantId, CancellationToken ct = default) ``` Delete tenant Deletes an existing tenant. | Parameter | Type | Description | | ---------- | ------------------- | ----------- | | `tenantId` | `TenantId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task DeleteTenantExample(TenantId tenantId) { using var client = CamundaClient.Create(); await client.DeleteTenantAsync(tenantId); } ``` #### GetTenantAsync(TenantId, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetTenantAsync(TenantId tenantId, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get tenant Retrieves a single tenant by tenant ID. | Parameter | Type | Description | | ------------- | ---------------------------------- | ----------- | | `tenantId` | `TenantId` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetTenantExample(TenantId tenantId) { using var client = CamundaClient.Create(); var result = await client.GetTenantAsync(tenantId); Console.WriteLine($"Tenant: {result.Name}"); } ``` #### GetUsageMetricsAsync(DateTimeOffset, DateTimeOffset, TenantId?, bool?, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetUsageMetricsAsync(DateTimeOffset startTime, DateTimeOffset endTime, TenantId? tenantId = null, bool? withTenants = null, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get usage metrics Retrieve the usage metrics based on given criteria. | Parameter | Type | Description | | ------------- | ------------------------------------------ | ----------- | | `startTime` | `DateTimeOffset` | | | `endTime` | `DateTimeOffset` | | | `tenantId` | `Nullable` | | | `withTenants` | `Nullable` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetUsageMetricsExample() { using var client = CamundaClient.Create(); var result = await client.GetUsageMetricsAsync( startTime: new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero), endTime: new DateTimeOffset(2024, 12, 31, 23, 59, 59, TimeSpan.Zero)); Console.WriteLine($"Metrics: {result}"); } ``` #### SearchClientsForTenantAsync(TenantId, TenantClientSearchQueryRequest, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchClientsForTenantAsync(TenantId tenantId, TenantClientSearchQueryRequest body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search clients for tenant Retrieves a filtered and sorted list of clients for a specified tenant. | Parameter | Type | Description | | ------------- | ---------------------------------------------- | ----------- | | `tenantId` | `TenantId` | | | `body` | `TenantClientSearchQueryRequest` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### SearchGroupIdsForTenantAsync(TenantId, TenantGroupSearchQueryRequest, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchGroupIdsForTenantAsync(TenantId tenantId, TenantGroupSearchQueryRequest body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search groups for tenant Retrieves a filtered and sorted list of groups for a specified tenant. | Parameter | Type | Description | | ------------- | --------------------------------------------- | ----------- | | `tenantId` | `TenantId` | | | `body` | `TenantGroupSearchQueryRequest` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchGroupIdsForTenantExample(TenantId tenantId) { using var client = CamundaClient.Create(); var result = await client.SearchGroupIdsForTenantAsync( tenantId, new TenantGroupSearchQueryRequest()); foreach (var group in result.Items) { Console.WriteLine($"Group: {group.GroupId}"); } } ``` #### SearchMappingRulesForTenantAsync(TenantId, MappingRuleSearchQueryRequest, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchMappingRulesForTenantAsync(TenantId tenantId, MappingRuleSearchQueryRequest body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search mapping rules for tenant Retrieves a filtered and sorted list of MappingRules for a specified tenant. | Parameter | Type | Description | | ------------- | --------------------------------------------------- | ----------- | | `tenantId` | `TenantId` | | | `body` | `MappingRuleSearchQueryRequest` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### SearchRolesForTenantAsync(TenantId, RoleSearchQueryRequest, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchRolesForTenantAsync(TenantId tenantId, RoleSearchQueryRequest body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search roles for tenant Retrieves a filtered and sorted list of roles for a specified tenant. | Parameter | Type | Description | | ------------- | -------------------------------------------- | ----------- | | `tenantId` | `TenantId` | | | `body` | `RoleSearchQueryRequest` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### SearchTenantsAsync(TenantSearchQueryRequest, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchTenantsAsync(TenantSearchQueryRequest body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search tenants Retrieves a filtered and sorted list of tenants. | Parameter | Type | Description | | ------------- | --------------------------------------------- | ----------- | | `body` | `TenantSearchQueryRequest` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchTenantsExample() { using var client = CamundaClient.Create(); var result = await client.SearchTenantsAsync(new TenantSearchQueryRequest()); foreach (var tenant in result.Items) { Console.WriteLine($"Tenant: {tenant.Name}"); } } ``` #### SearchUsersForTenantAsync(TenantId, TenantUserSearchQueryRequest, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchUsersForTenantAsync(TenantId tenantId, TenantUserSearchQueryRequest body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search users for tenant Retrieves a filtered and sorted list of users for a specified tenant. | Parameter | Type | Description | | ------------- | -------------------------------------------- | ----------- | | `tenantId` | `TenantId` | | | `body` | `TenantUserSearchQueryRequest` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### UnassignClientFromTenantAsync(TenantId, ClientId, CancellationToken) ```csharp public Task UnassignClientFromTenantAsync(TenantId tenantId, ClientId clientId, CancellationToken ct = default) ``` Unassign a client from a tenant Unassigns the client from the specified tenant. The client can no longer access tenant data. | Parameter | Type | Description | | ---------- | ------------------- | ----------- | | `tenantId` | `TenantId` | | | `clientId` | `ClientId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### UnassignGroupFromTenantAsync(TenantId, GroupId, CancellationToken) ```csharp public Task UnassignGroupFromTenantAsync(TenantId tenantId, GroupId groupId, CancellationToken ct = default) ``` Unassign a group from a tenant Unassigns a group from a specified tenant. Members of the group (users, clients) will no longer have access to the tenant's data - except they are assigned directly to the tenant. | Parameter | Type | Description | | ---------- | ------------------- | ----------- | | `tenantId` | `TenantId` | | | `groupId` | `GroupId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### UnassignMappingRuleFromTenantAsync(TenantId, MappingRuleId, CancellationToken) ```csharp public Task UnassignMappingRuleFromTenantAsync(TenantId tenantId, MappingRuleId mappingRuleId, CancellationToken ct = default) ``` Unassign a mapping rule from a tenant Unassigns a single mapping rule from a specified tenant without deleting the rule. | Parameter | Type | Description | | --------------- | ------------------- | ----------- | | `tenantId` | `TenantId` | | | `mappingRuleId` | `MappingRuleId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### UnassignRoleFromTenantAsync(TenantId, RoleId, CancellationToken) ```csharp public Task UnassignRoleFromTenantAsync(TenantId tenantId, RoleId roleId, CancellationToken ct = default) ``` Unassign a role from a tenant Unassigns a role from a specified tenant. Users, Clients or Groups, that have the role assigned, will no longer have access to the tenant's data - unless they are assigned directly to the tenant. | Parameter | Type | Description | | ---------- | ------------------- | ----------- | | `tenantId` | `TenantId` | | | `roleId` | `RoleId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### UnassignUserFromTenantAsync(TenantId, Username, CancellationToken) ```csharp public Task UnassignUserFromTenantAsync(TenantId tenantId, Username username, CancellationToken ct = default) ``` Unassign a user from a tenant Unassigns the user from the specified tenant. The user can no longer access tenant data. | Parameter | Type | Description | | ---------- | ------------------- | ----------- | | `tenantId` | `TenantId` | | | `username` | `Username` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task UnassignUserFromTenantExample(TenantId tenantId, Username username) { using var client = CamundaClient.Create(); await client.UnassignUserFromTenantAsync( tenantId, username); } ``` #### UpdateTenantAsync(TenantId, TenantUpdateRequest, CancellationToken) ```csharp public Task UpdateTenantAsync(TenantId tenantId, TenantUpdateRequest body, CancellationToken ct = default) ``` Update tenant Updates an existing tenant. | Parameter | Type | Description | | ---------- | --------------------- | ----------- | | `tenantId` | `TenantId` | | | `body` | `TenantUpdateRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task UpdateTenantExample(TenantId tenantId) { using var client = CamundaClient.Create(); await client.UpdateTenantAsync( tenantId, new TenantUpdateRequest { Name = "Acme Corp International", }); } ``` ### Roles #### AssignRoleToClientAsync(RoleId, ClientId, CancellationToken) ```csharp public Task AssignRoleToClientAsync(RoleId roleId, ClientId clientId, CancellationToken ct = default) ``` Assign a role to a client Assigns the specified role to the client. The client will inherit the authorizations associated with this role. | Parameter | Type | Description | | ---------- | ------------------- | ----------- | | `roleId` | `RoleId` | | | `clientId` | `ClientId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### AssignRoleToGroupAsync(RoleId, GroupId, CancellationToken) ```csharp public Task AssignRoleToGroupAsync(RoleId roleId, GroupId groupId, CancellationToken ct = default) ``` Assign a role to a group Assigns the specified role to the group. Every member of the group (user or client) will inherit the authorizations associated with this role. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `roleId` | `RoleId` | | | `groupId` | `GroupId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### AssignRoleToMappingRuleAsync(RoleId, MappingRuleId, CancellationToken) ```csharp public Task AssignRoleToMappingRuleAsync(RoleId roleId, MappingRuleId mappingRuleId, CancellationToken ct = default) ``` Assign a role to a mapping rule Assigns a role to a mapping rule. | Parameter | Type | Description | | --------------- | ------------------- | ----------- | | `roleId` | `RoleId` | | | `mappingRuleId` | `MappingRuleId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### AssignRoleToUserAsync(RoleId, Username, CancellationToken) ```csharp public Task AssignRoleToUserAsync(RoleId roleId, Username username, CancellationToken ct = default) ``` Assign a role to a user Assigns the specified role to the user. The user will inherit the authorizations associated with this role. | Parameter | Type | Description | | ---------- | ------------------- | ----------- | | `roleId` | `RoleId` | | | `username` | `Username` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### CreateRoleAsync(RoleCreateRequest, CancellationToken) ```csharp public Task CreateRoleAsync(RoleCreateRequest body, CancellationToken ct = default) ``` Create role Create a new role. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `body` | `RoleCreateRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task CreateRoleExample() { using var client = CamundaClient.Create(); var result = await client.CreateRoleAsync(new RoleCreateRequest { Name = "developer", }); Console.WriteLine($"Role key: {result.RoleId}"); } ``` #### DeleteRoleAsync(RoleId, CancellationToken) ```csharp public Task DeleteRoleAsync(RoleId roleId, CancellationToken ct = default) ``` Delete role Deletes the role with the given ID. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `roleId` | `RoleId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### GetRoleAsync(RoleId, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetRoleAsync(RoleId roleId, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get role Get a role by its ID. | Parameter | Type | Description | | ------------- | -------------------------------- | ----------- | | `roleId` | `RoleId` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### SearchClientsForRoleAsync(RoleId, RoleClientSearchQueryRequest, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchClientsForRoleAsync(RoleId roleId, RoleClientSearchQueryRequest body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search role clients Search clients with assigned role. | Parameter | Type | Description | | ------------- | -------------------------------------------- | ----------- | | `roleId` | `RoleId` | | | `body` | `RoleClientSearchQueryRequest` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### SearchGroupsForRoleAsync(RoleId, RoleGroupSearchQueryRequest, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchGroupsForRoleAsync(RoleId roleId, RoleGroupSearchQueryRequest body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search role groups Search groups with assigned role. | Parameter | Type | Description | | ------------- | ------------------------------------------- | ----------- | | `roleId` | `RoleId` | | | `body` | `RoleGroupSearchQueryRequest` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### SearchMappingRulesForRoleAsync(RoleId, MappingRuleSearchQueryRequest, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchMappingRulesForRoleAsync(RoleId roleId, MappingRuleSearchQueryRequest body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search role mapping rules Search mapping rules with assigned role. | Parameter | Type | Description | | ------------- | ------------------------------------------------- | ----------- | | `roleId` | `RoleId` | | | `body` | `MappingRuleSearchQueryRequest` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### SearchRolesAsync(RoleSearchQueryRequest, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchRolesAsync(RoleSearchQueryRequest body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search roles Search for roles based on given criteria. | Parameter | Type | Description | | ------------- | ------------------------------------------- | ----------- | | `body` | `RoleSearchQueryRequest` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchRolesExample() { using var client = CamundaClient.Create(); var result = await client.SearchRolesAsync(new RoleSearchQueryRequest()); foreach (var role in result.Items) { Console.WriteLine($"Role: {role.Name}"); } } ``` #### SearchRolesForGroupAsync(GroupId, RoleSearchQueryRequest, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchRolesForGroupAsync(GroupId groupId, RoleSearchQueryRequest body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search group roles Search roles assigned to a group. | Parameter | Type | Description | | ------------- | ------------------------------------------- | ----------- | | `groupId` | `GroupId` | | | `body` | `RoleSearchQueryRequest` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### SearchUsersForRoleAsync(RoleId, RoleUserSearchQueryRequest, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchUsersForRoleAsync(RoleId roleId, RoleUserSearchQueryRequest body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search role users Search users with assigned role. | Parameter | Type | Description | | ------------- | ------------------------------------------ | ----------- | | `roleId` | `RoleId` | | | `body` | `RoleUserSearchQueryRequest` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### UnassignRoleFromClientAsync(RoleId, ClientId, CancellationToken) ```csharp public Task UnassignRoleFromClientAsync(RoleId roleId, ClientId clientId, CancellationToken ct = default) ``` Unassign a role from a client Unassigns the specified role from the client. The client will no longer inherit the authorizations associated with this role. | Parameter | Type | Description | | ---------- | ------------------- | ----------- | | `roleId` | `RoleId` | | | `clientId` | `ClientId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### UnassignRoleFromGroupAsync(RoleId, GroupId, CancellationToken) ```csharp public Task UnassignRoleFromGroupAsync(RoleId roleId, GroupId groupId, CancellationToken ct = default) ``` Unassign a role from a group Unassigns the specified role from the group. All group members (user or client) no longer inherit the authorizations associated with this role. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `roleId` | `RoleId` | | | `groupId` | `GroupId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### UnassignRoleFromMappingRuleAsync(RoleId, MappingRuleId, CancellationToken) ```csharp public Task UnassignRoleFromMappingRuleAsync(RoleId roleId, MappingRuleId mappingRuleId, CancellationToken ct = default) ``` Unassign a role from a mapping rule Unassigns a role from a mapping rule. | Parameter | Type | Description | | --------------- | ------------------- | ----------- | | `roleId` | `RoleId` | | | `mappingRuleId` | `MappingRuleId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### UnassignRoleFromUserAsync(RoleId, Username, CancellationToken) ```csharp public Task UnassignRoleFromUserAsync(RoleId roleId, Username username, CancellationToken ct = default) ``` Unassign a role from a user Unassigns a role from a user. The user will no longer inherit the authorizations associated with this role. | Parameter | Type | Description | | ---------- | ------------------- | ----------- | | `roleId` | `RoleId` | | | `username` | `Username` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### UpdateRoleAsync(RoleId, RoleUpdateRequest, CancellationToken) ```csharp public Task UpdateRoleAsync(RoleId roleId, RoleUpdateRequest body, CancellationToken ct = default) ``` Update role Update a role with the given ID. | Parameter | Type | Description | | --------- | ------------------- | ----------- | | `roleId` | `RoleId` | | | `body` | `RoleUpdateRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` ### User Tasks #### AssignUserTaskAsync(UserTaskKey, UserTaskAssignmentRequest, CancellationToken) ```csharp public Task AssignUserTaskAsync(UserTaskKey userTaskKey, UserTaskAssignmentRequest body, CancellationToken ct = default) ``` Assign user task Assigns a user task with the given key to the given assignee. Assignment waits for blocking task listeners on this lifecycle transition. If listener processing is delayed beyond the request timeout, this endpoint can return 504. Other gateway timeout causes are also possible. Retry with backoff and inspect listener worker availability and logs when this repeats. | Parameter | Type | Description | | ------------- | --------------------------- | ----------- | | `userTaskKey` | `UserTaskKey` | | | `body` | `UserTaskAssignmentRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task AssignUserTaskExample(UserTaskKey userTaskKey) { using var client = CamundaClient.Create(); await client.AssignUserTaskAsync( userTaskKey, new UserTaskAssignmentRequest { Assignee = "user@example.com", }); } ``` #### CompleteUserTaskAsync(UserTaskKey, UserTaskCompletionRequest, CancellationToken) ```csharp public Task CompleteUserTaskAsync(UserTaskKey userTaskKey, UserTaskCompletionRequest body, CancellationToken ct = default) ``` Complete user task Completes a user task with the given key. Completion waits for blocking task listeners on this lifecycle transition. If listener processing is delayed beyond the request timeout, this endpoint can return 504. Other gateway timeout causes are also possible. Retry with backoff and inspect listener worker availability and logs when this repeats. | Parameter | Type | Description | | ------------- | --------------------------- | ----------- | | `userTaskKey` | `UserTaskKey` | | | `body` | `UserTaskCompletionRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task CompleteUserTaskExample(UserTaskKey userTaskKey) { using var client = CamundaClient.Create(); await client.CompleteUserTaskAsync( userTaskKey, new UserTaskCompletionRequest()); } ``` #### GetUserTaskAsync(UserTaskKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetUserTaskAsync(UserTaskKey userTaskKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get user task Get the user task by the user task key. | Parameter | Type | Description | | ------------- | ------------------------------------ | ----------- | | `userTaskKey` | `UserTaskKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetUserTaskExample(UserTaskKey userTaskKey) { using var client = CamundaClient.Create(); var result = await client.GetUserTaskAsync(userTaskKey); Console.WriteLine($"User task: {result.UserTaskKey}"); } ``` #### GetUserTaskFormAsync(UserTaskKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetUserTaskFormAsync(UserTaskKey userTaskKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get user task form Get the form of a user task. Note that this endpoint will only return linked forms. This endpoint does not support embedded forms. | Parameter | Type | Description | | ------------- | -------------------------------- | ----------- | | `userTaskKey` | `UserTaskKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetUserTaskFormExample(UserTaskKey userTaskKey) { using var client = CamundaClient.Create(); var result = await client.GetUserTaskFormAsync(userTaskKey); Console.WriteLine($"Form: {result.FormKey}"); } ``` #### SearchUserTaskAuditLogsAsync(UserTaskKey, UserTaskAuditLogSearchQueryRequest, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchUserTaskAuditLogsAsync(UserTaskKey userTaskKey, UserTaskAuditLogSearchQueryRequest body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search user task audit logs Search for user task audit logs based on given criteria. | Parameter | Type | Description | | ------------- | ----------------------------------------------- | ----------- | | `userTaskKey` | `UserTaskKey` | | | `body` | `UserTaskAuditLogSearchQueryRequest` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchUserTaskAuditLogsExample(UserTaskKey userTaskKey) { using var client = CamundaClient.Create(); var result = await client.SearchUserTaskAuditLogsAsync( userTaskKey, new UserTaskAuditLogSearchQueryRequest()); foreach (var log in result.Items) { Console.WriteLine($"Audit log: {log.AuditLogKey}"); } } ``` #### SearchUserTaskEffectiveVariablesAsync(UserTaskKey, UserTaskEffectiveVariableSearchQueryRequest, bool?, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchUserTaskEffectiveVariablesAsync(UserTaskKey userTaskKey, UserTaskEffectiveVariableSearchQueryRequest body, bool? truncateValues = null, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search user task effective variables Search for the effective variables of a user task. This endpoint returns deduplicated variables where each variable name appears at most once. When the same variable name exists at multiple scope levels in the scope hierarchy, the value from the innermost scope (closest to the user task) takes precedence. This is useful for retrieving the actual runtime state of variables as seen by the user task. By default, long variable values in the response are truncated. | Parameter | Type | Description | | ---------------- | ----------------------------------------------- | ----------- | | `userTaskKey` | `UserTaskKey` | | | `body` | `UserTaskEffectiveVariableSearchQueryRequest` | | | `truncateValues` | `Nullable` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### SearchUserTaskVariablesAsync(UserTaskKey, UserTaskVariableSearchQueryRequest, bool?, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchUserTaskVariablesAsync(UserTaskKey userTaskKey, UserTaskVariableSearchQueryRequest body, bool? truncateValues = null, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search user task variables Search for user task variables based on given criteria. This endpoint returns all variable documents visible from the user task's scope, including variables from parent scopes in the scope hierarchy. If the same variable name exists at multiple scope levels, each scope's variable is returned as a separate result. Use the `/user-tasks/{userTaskKey}/effective-variables/search` endpoint to get deduplicated variables where the innermost scope takes precedence. By default, long variable values in the response are truncated. | Parameter | Type | Description | | ---------------- | ----------------------------------------------- | ----------- | | `userTaskKey` | `UserTaskKey` | | | `body` | `UserTaskVariableSearchQueryRequest` | | | `truncateValues` | `Nullable` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### SearchUserTasksAsync(UserTaskSearchQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchUserTasksAsync(UserTaskSearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search user tasks Search for user tasks based on given criteria. | Parameter | Type | Description | | ------------- | ----------------------------------------------- | ----------- | | `body` | `UserTaskSearchQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchUserTasksExample() { using var client = CamundaClient.Create(); var result = await client.SearchUserTasksAsync(new UserTaskSearchQuery()); foreach (var task in result.Items) { Console.WriteLine($"User task: {task.UserTaskKey}"); } } ``` #### UnassignUserTaskAsync(UserTaskKey, CancellationToken) ```csharp public Task UnassignUserTaskAsync(UserTaskKey userTaskKey, CancellationToken ct = default) ``` Unassign user task Removes the assignee of a task with the given key. Unassignment waits for blocking task listeners on this lifecycle transition. If listener processing is delayed beyond the request timeout, this endpoint can return 504. Other gateway timeout causes are also possible. Retry with backoff and inspect listener worker availability and logs when this repeats. | Parameter | Type | Description | | ------------- | ------------------- | ----------- | | `userTaskKey` | `UserTaskKey` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task UnassignUserTaskExample(UserTaskKey userTaskKey) { using var client = CamundaClient.Create(); await client.UnassignUserTaskAsync(userTaskKey); } ``` #### UpdateUserTaskAsync(UserTaskKey, UserTaskUpdateRequest, CancellationToken) ```csharp public Task UpdateUserTaskAsync(UserTaskKey userTaskKey, UserTaskUpdateRequest body, CancellationToken ct = default) ``` Update user task Update a user task with the given key. Updates wait for blocking task listeners on this lifecycle transition. If listener processing is delayed beyond the request timeout, this endpoint can return 504. Other gateway timeout causes are also possible. Retry with backoff and inspect listener worker availability and logs when this repeats. | Parameter | Type | Description | | ------------- | ----------------------- | ----------- | | `userTaskKey` | `UserTaskKey` | | | `body` | `UserTaskUpdateRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task UpdateUserTaskExample(UserTaskKey userTaskKey) { using var client = CamundaClient.Create(); await client.UpdateUserTaskAsync( userTaskKey, new UserTaskUpdateRequest()); } ``` ### Signals #### BroadcastSignalAsync(SignalBroadcastRequest, CancellationToken) ```csharp public Task BroadcastSignalAsync(SignalBroadcastRequest body, CancellationToken ct = default) ``` Broadcast signal Broadcasts a signal. | Parameter | Type | Description | | --------- | ------------------------ | ----------- | | `body` | `SignalBroadcastRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task BroadcastSignalExample() { using var client = CamundaClient.Create(); var result = await client.BroadcastSignalAsync(new SignalBroadcastRequest { SignalName = "orderCancelled", }); Console.WriteLine($"Signal key: {result.SignalKey}"); } ``` ### Batch Operations #### CancelBatchOperationAsync(BatchOperationKey, CancellationToken) ```csharp public Task CancelBatchOperationAsync(BatchOperationKey batchOperationKey, CancellationToken ct = default) ``` Cancel Batch operation Cancels a running batch operation. This is done asynchronously, the progress can be tracked using the batch operation status endpoint (/batch-operations/{batchOperationKey}). | Parameter | Type | Description | | ------------------- | ------------------- | ----------- | | `batchOperationKey` | `BatchOperationKey` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task CancelBatchOperationExample(BatchOperationKey batchOperationKey) { using var client = CamundaClient.Create(); await client.CancelBatchOperationAsync(batchOperationKey); } ``` #### GetBatchOperationAsync(BatchOperationKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetBatchOperationAsync(BatchOperationKey batchOperationKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get batch operation Get batch operation by key. | Parameter | Type | Description | | ------------------- | -------------------------------------------- | ----------- | | `batchOperationKey` | `BatchOperationKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetBatchOperationExample(BatchOperationKey batchOperationKey) { using var client = CamundaClient.Create(); var result = await client.GetBatchOperationAsync( batchOperationKey); Console.WriteLine($"Batch operation: {result.BatchOperationKey}"); } ``` #### ResumeBatchOperationAsync(BatchOperationKey, CancellationToken) ```csharp public Task ResumeBatchOperationAsync(BatchOperationKey batchOperationKey, CancellationToken ct = default) ``` Resume Batch operation Resumes a suspended batch operation. This is done asynchronously, the progress can be tracked using the batch operation status endpoint (/batch-operations/{batchOperationKey}). | Parameter | Type | Description | | ------------------- | ------------------- | ----------- | | `batchOperationKey` | `BatchOperationKey` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task ResumeBatchOperationExample(BatchOperationKey batchOperationKey) { using var client = CamundaClient.Create(); await client.ResumeBatchOperationAsync(batchOperationKey); } ``` #### SearchBatchOperationItemsAsync(BatchOperationItemSearchQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchBatchOperationItemsAsync(BatchOperationItemSearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search batch operation items Search for batch operation items based on given criteria. | Parameter | Type | Description | | ------------- | --------------------------------------------------------- | ----------- | | `body` | `BatchOperationItemSearchQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchBatchOperationItemsExample() { using var client = CamundaClient.Create(); var result = await client.SearchBatchOperationItemsAsync( new BatchOperationItemSearchQuery()); foreach (var item in result.Items) { Console.WriteLine($"Item: {item.ItemKey}"); } } ``` #### SearchBatchOperationsAsync(BatchOperationSearchQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchBatchOperationsAsync(BatchOperationSearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search batch operations Search for batch operations based on given criteria. | Parameter | Type | Description | | ------------- | ----------------------------------------------------- | ----------- | | `body` | `BatchOperationSearchQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchBatchOperationsExample() { using var client = CamundaClient.Create(); var result = await client.SearchBatchOperationsAsync( new BatchOperationSearchQuery()); foreach (var op in result.Items) { Console.WriteLine($"Batch operation: {op.BatchOperationKey}"); } } ``` #### SuspendBatchOperationAsync(BatchOperationKey, CancellationToken) ```csharp public Task SuspendBatchOperationAsync(BatchOperationKey batchOperationKey, CancellationToken ct = default) ``` Suspend Batch operation Suspends a running batch operation. This is done asynchronously, the progress can be tracked using the batch operation status endpoint (/batch-operations/{batchOperationKey}). | Parameter | Type | Description | | ------------------- | ------------------- | ----------- | | `batchOperationKey` | `BatchOperationKey` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SuspendBatchOperationExample(BatchOperationKey batchOperationKey) { using var client = CamundaClient.Create(); await client.SuspendBatchOperationAsync(batchOperationKey); } ``` ### Messages #### CorrelateMessageAsync(MessageCorrelationRequest, CancellationToken) ```csharp public Task CorrelateMessageAsync(MessageCorrelationRequest body, CancellationToken ct = default) ``` Correlate message Publishes a message and correlates it to a subscription. If correlation is successful it will return the first process instance key the message correlated with. The message is not buffered. Use the publish message endpoint to send messages that can be buffered. | Parameter | Type | Description | | --------- | --------------------------- | ----------- | | `body` | `MessageCorrelationRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task CorrelateMessageExample() { using var client = CamundaClient.Create(); var result = await client.CorrelateMessageAsync(new MessageCorrelationRequest { Name = "paymentReceived", CorrelationKey = "order-123", }); Console.WriteLine($"Message key: {result.MessageKey}"); } ``` #### PublishMessageAsync(MessagePublicationRequest, CancellationToken) ```csharp public Task PublishMessageAsync(MessagePublicationRequest body, CancellationToken ct = default) ``` Publish message Publishes a single message. Messages are published to specific partitions computed from their correlation keys. Messages can be buffered. The endpoint does not wait for a correlation result. Use the message correlation endpoint for such use cases. | Parameter | Type | Description | | --------- | --------------------------- | ----------- | | `body` | `MessagePublicationRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task PublishMessageExample() { using var client = CamundaClient.Create(); var result = await client.PublishMessageAsync(new MessagePublicationRequest { Name = "paymentReceived", CorrelationKey = "order-123", TimeToLive = 60000, }); Console.WriteLine($"Message key: {result.MessageKey}"); } ``` #### SearchCorrelatedMessageSubscriptionsAsync(CorrelatedMessageSubscriptionSearchQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchCorrelatedMessageSubscriptionsAsync(CorrelatedMessageSubscriptionSearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search correlated message subscriptions Search correlated message subscriptions based on given criteria. | Parameter | Type | Description | | ------------- | -------------------------------------------------------------------- | ----------- | | `body` | `CorrelatedMessageSubscriptionSearchQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchCorrelatedMessageSubscriptionsExample() { using var client = CamundaClient.Create(); var result = await client.SearchCorrelatedMessageSubscriptionsAsync( new CorrelatedMessageSubscriptionSearchQuery()); foreach (var sub in result.Items) { Console.WriteLine($"Correlated subscription: {sub.MessageName}"); } } ``` #### SearchMessageSubscriptionsAsync(MessageSubscriptionSearchQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchMessageSubscriptionsAsync(MessageSubscriptionSearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search message subscriptions Search for message subscriptions based on given criteria. By default, both start and intermediate event subscriptions are returned. Use the `messageSubscriptionType` filter to restrict results to a single type. **Version notes:** - Start event subscriptions are only captured for deployments made with 8.10 or later. - The `messageSubscriptionType` field is only populated for data created with Camunda 8.10 or later. For pre-8.10 data, intermediate event entries have no `messageSubscriptionType` value stored. For convenience, the API returns `PROCESS_EVENT` as a default for such search results, though. - Searching for intermediate event subscriptions **including legacy data** can be achieved by filtering for `messageSubscriptionType` not matching `START_EVENT`. | Parameter | Type | Description | | ------------- | ---------------------------------------------------------- | ----------- | | `body` | `MessageSubscriptionSearchQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchMessageSubscriptionsExample() { using var client = CamundaClient.Create(); var result = await client.SearchMessageSubscriptionsAsync( new MessageSubscriptionSearchQuery()); foreach (var sub in result.Items) { Console.WriteLine($"Subscription: {sub.MessageName}"); } } ``` ### Authorizations #### CreateAuthorizationAsync(AuthorizationRequest, CancellationToken) ```csharp public Task CreateAuthorizationAsync(AuthorizationRequest body, CancellationToken ct = default) ``` Create authorization Create the authorization. | Parameter | Type | Description | | --------- | ---------------------- | ----------- | | `body` | `AuthorizationRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task CreateAuthorizationExample() { using var client = CamundaClient.Create(); var result = await client.CreateAuthorizationAsync(new AuthorizationPropertyBasedRequest { ResourceType = ResourceTypeEnum.PROCESSDEFINITION, PermissionTypes = new List { PermissionTypeEnum.READ, PermissionTypeEnum.UPDATE }, ResourcePropertyName = "my-process", OwnerType = OwnerTypeEnum.USER, OwnerId = "user@example.com", }); Console.WriteLine($"Authorization key: {result.AuthorizationKey}"); } ``` #### DeleteAuthorizationAsync(AuthorizationKey, CancellationToken) ```csharp public Task DeleteAuthorizationAsync(AuthorizationKey authorizationKey, CancellationToken ct = default) ``` Delete authorization Deletes the authorization with the given key. | Parameter | Type | Description | | ------------------ | ------------------- | ----------- | | `authorizationKey` | `AuthorizationKey` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task DeleteAuthorizationExample(AuthorizationKey authorizationKey) { using var client = CamundaClient.Create(); await client.DeleteAuthorizationAsync(authorizationKey); } ``` #### GetAuthorizationAsync(AuthorizationKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetAuthorizationAsync(AuthorizationKey authorizationKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get authorization Get authorization by the given key. | Parameter | Type | Description | | ------------------ | ----------------------------------------- | ----------- | | `authorizationKey` | `AuthorizationKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetAuthorizationExample(AuthorizationKey authorizationKey) { using var client = CamundaClient.Create(); var result = await client.GetAuthorizationAsync( authorizationKey); Console.WriteLine($"Resource type: {result.ResourceType}"); } ``` #### SearchAuthorizationsAsync(AuthorizationSearchQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchAuthorizationsAsync(AuthorizationSearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search authorizations Search for authorizations based on given criteria. | Parameter | Type | Description | | ------------- | ----------------------------------------------- | ----------- | | `body` | `AuthorizationSearchQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchAuthorizationsExample() { using var client = CamundaClient.Create(); var result = await client.SearchAuthorizationsAsync( new AuthorizationSearchQuery()); foreach (var auth in result.Items) { Console.WriteLine($"Authorization: {auth.AuthorizationKey}"); } } ``` #### SearchOwnAuthorizationsAsync(AuthorizationSearchQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchOwnAuthorizationsAsync(AuthorizationSearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search own authorizations Search for the current authenticated principal's own authorization records — including authorizations granted directly to the user or client, as well as those granted via a group, role, or mapping rule the principal belongs to. | Parameter | Type | Description | | ------------- | -------------------------------------------------- | ----------- | | `body` | `AuthorizationSearchQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### UpdateAuthorizationAsync(AuthorizationKey, AuthorizationRequest, CancellationToken) ```csharp public Task UpdateAuthorizationAsync(AuthorizationKey authorizationKey, AuthorizationRequest body, CancellationToken ct = default) ``` Update authorization Update the authorization with the given key. | Parameter | Type | Description | | ------------------ | ---------------------- | ----------- | | `authorizationKey` | `AuthorizationKey` | | | `body` | `AuthorizationRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task UpdateAuthorizationExample(AuthorizationKey authorizationKey) { using var client = CamundaClient.Create(); await client.UpdateAuthorizationAsync( authorizationKey, new AuthorizationPropertyBasedRequest { ResourceType = ResourceTypeEnum.PROCESSDEFINITION, PermissionTypes = new List { PermissionTypeEnum.READ, PermissionTypeEnum.UPDATE, PermissionTypeEnum.DELETE }, ResourcePropertyName = "my-process", OwnerType = OwnerTypeEnum.USER, OwnerId = "user@example.com", }); } ``` ### Deployments #### CreateDeploymentAsync(MultipartFormDataContent, CancellationToken) ```csharp public Task CreateDeploymentAsync(MultipartFormDataContent content, CancellationToken ct = default) ``` Deploy resources Deploys one or more resources, including BPMN processes, DMN decision models, forms, RPA resources, and generic files. A deployment can contain any file type. Files that are not interpreted as BPMN, DMN, form, or RPA resources are stored as deployable generic resources in the engine. This is an atomic call, i.e. either all resources are deployed or none of them are. | Parameter | Type | Description | | --------- | -------------------------- | ----------- | | `content` | `MultipartFormDataContent` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task CreateDeploymentExample() { using var client = CamundaClient.Create(); var content = new MultipartFormDataContent(); var fileContent = new ByteArrayContent(File.ReadAllBytes("process.bpmn")); content.Add(fileContent, "resources", "process.bpmn"); var result = await client.CreateDeploymentAsync(content); Console.WriteLine($"Deployment key: {result.DeploymentKey}"); } ``` ### Documents #### CreateDocumentAsync(MultipartFormDataContent, string?, DocumentId?, CancellationToken) ```csharp public Task CreateDocumentAsync(MultipartFormDataContent content, string? storeId = null, DocumentId? documentId = null, CancellationToken ct = default) ``` Upload document Upload a document to the Camunda 8 cluster. Note that this is currently supported for document stores of type: AWS, Azure, GCP, in-memory (non-production), local (non-production) | Parameter | Type | Description | | ------------ | -------------------------- | ----------- | | `content` | `MultipartFormDataContent` | | | `storeId` | `String` | | | `documentId` | `Nullable` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task CreateDocumentExample() { using var client = CamundaClient.Create(); using var content = new MultipartFormDataContent(); content.Add(new ByteArrayContent(System.Text.Encoding.UTF8.GetBytes("Hello, world!")), "file", "hello.txt"); var result = await client.CreateDocumentAsync(content); Console.WriteLine($"Document ID: {result.DocumentId}"); } ``` #### CreateDocumentLinkAsync(DocumentId, DocumentLinkRequest, string?, string?, CancellationToken) ```csharp public Task CreateDocumentLinkAsync(DocumentId documentId, DocumentLinkRequest body, string? storeId = null, string? contentHash = null, CancellationToken ct = default) ``` Create document link Create a link to a document in the Camunda 8 cluster. Note that this is currently supported for document stores of type: AWS, Azure, GCP | Parameter | Type | Description | | ------------- | --------------------- | ----------- | | `documentId` | `DocumentId` | | | `body` | `DocumentLinkRequest` | | | `storeId` | `String` | | | `contentHash` | `String` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task CreateDocumentLinkExample(DocumentId documentId) { using var client = CamundaClient.Create(); var result = await client.CreateDocumentLinkAsync( documentId, new DocumentLinkRequest()); Console.WriteLine($"Document link: {result.Url}"); } ``` #### CreateDocumentsAsync(MultipartFormDataContent, string?, CancellationToken) ```csharp public Task CreateDocumentsAsync(MultipartFormDataContent content, string? storeId = null, CancellationToken ct = default) ``` Upload multiple documents Upload multiple documents to the Camunda 8 cluster. The caller must provide a file name for each document, which will be used in case of a multi-status response to identify which documents failed to upload. The file name can be provided in the `Content-Disposition` header of the file part or in the `fileName` field of the metadata. You can add a parallel array of metadata objects. These are matched with the files based on index, and must have the same length as the files array. To pass homogenous metadata for all files, spread the metadata over the metadata array. A filename value provided explicitly via the metadata array in the request overrides the `Content-Disposition` header of the file part. In case of a multi-status response, the response body will contain a list of `DocumentBatchProblemDetail` objects, each of which contains the file name of the document that failed to upload and the reason for the failure. The client can choose to retry the whole batch or individual documents based on the response. Note that this is currently supported for document stores of type: AWS, Azure, GCP, in-memory (non-production), local (non-production) | Parameter | Type | Description | | --------- | -------------------------- | ----------- | | `content` | `MultipartFormDataContent` | | | `storeId` | `String` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task CreateDocumentsExample() { using var client = CamundaClient.Create(); using var content = new MultipartFormDataContent(); content.Add(new ByteArrayContent(System.Text.Encoding.UTF8.GetBytes("File one")), "files", "one.txt"); content.Add(new ByteArrayContent(System.Text.Encoding.UTF8.GetBytes("File two")), "files", "two.txt"); var result = await client.CreateDocumentsAsync(content); foreach (var doc in result.CreatedDocuments) { Console.WriteLine($"Created: {doc.DocumentId}"); } } ``` #### DeleteDocumentAsync(DocumentId, string?, CancellationToken) ```csharp public Task DeleteDocumentAsync(DocumentId documentId, string? storeId = null, CancellationToken ct = default) ``` Delete document Delete a document from the Camunda 8 cluster. Note that this is currently supported for document stores of type: AWS, Azure, GCP, in-memory (non-production), local (non-production) | Parameter | Type | Description | | ------------ | ------------------- | ----------- | | `documentId` | `DocumentId` | | | `storeId` | `String` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task DeleteDocumentExample(DocumentId documentId) { using var client = CamundaClient.Create(); await client.DeleteDocumentAsync(documentId); } ``` #### GetDocumentAsync(DocumentId, string?, string?, CancellationToken) ```csharp public Task GetDocumentAsync(DocumentId documentId, string? storeId = null, string? contentHash = null, CancellationToken ct = default) ``` Download document Download a document from the Camunda 8 cluster. Note that this is currently supported for document stores of type: AWS, Azure, GCP, in-memory (non-production), local (non-production) | Parameter | Type | Description | | ------------- | ------------------- | ----------- | | `documentId` | `DocumentId` | | | `storeId` | `String` | | | `contentHash` | `String` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetDocumentExample(DocumentId documentId) { using var client = CamundaClient.Create(); var content = await client.GetDocumentAsync(documentId); Console.WriteLine($"Downloaded document: {documentId}"); } ``` ### Variables #### CreateElementInstanceVariablesAsync(ElementInstanceKey, SetVariableRequest, CancellationToken) ```csharp public Task CreateElementInstanceVariablesAsync(ElementInstanceKey elementInstanceKey, SetVariableRequest body, CancellationToken ct = default) ``` Update element instance variables Updates all the variables of a particular scope (for example, process instance, element instance) with the given variable data. Specify the element instance in the `elementInstanceKey` parameter. Variable updates can be delayed by listener-related processing; if processing exceeds the request timeout, this endpoint can return 504. Other gateway timeout causes are also possible. Retry with backoff and inspect listener worker availability and logs when this repeats. | Parameter | Type | Description | | -------------------- | -------------------- | ----------- | | `elementInstanceKey` | `ElementInstanceKey` | | | `body` | `SetVariableRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task CreateElementInstanceVariablesExample(ElementInstanceKey elementInstanceKey) { using var client = CamundaClient.Create(); await client.CreateElementInstanceVariablesAsync( elementInstanceKey, new SetVariableRequest()); } ``` #### CreateGlobalClusterVariableAsync(CreateClusterVariableRequest, CancellationToken) ```csharp public Task CreateGlobalClusterVariableAsync(CreateClusterVariableRequest body, CancellationToken ct = default) ``` Create a global-scoped cluster variable Create a global-scoped cluster variable. | Parameter | Type | Description | | --------- | ------------------------------ | ----------- | | `body` | `CreateClusterVariableRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task CreateGlobalClusterVariableExample(ClusterVariableName name) { using var client = CamundaClient.Create(); var result = await client.CreateGlobalClusterVariableAsync( new CreateClusterVariableRequest { Name = name, Value = "my-value", }); Console.WriteLine($"Created variable: {result.Name}"); } ``` #### CreateTenantClusterVariableAsync(TenantId, CreateClusterVariableRequest, CancellationToken) ```csharp public Task CreateTenantClusterVariableAsync(TenantId tenantId, CreateClusterVariableRequest body, CancellationToken ct = default) ``` Create a tenant-scoped cluster variable Create a new cluster variable for the given tenant. | Parameter | Type | Description | | ---------- | ------------------------------ | ----------- | | `tenantId` | `TenantId` | | | `body` | `CreateClusterVariableRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task CreateTenantClusterVariableExample(TenantId tenantId, ClusterVariableName name) { using var client = CamundaClient.Create(); var result = await client.CreateTenantClusterVariableAsync( tenantId, new CreateClusterVariableRequest { Name = name, Value = "tenant-value", }); Console.WriteLine($"Created variable: {result.Name}"); } ``` #### DeleteGlobalClusterVariableAsync(ClusterVariableName, CancellationToken) ```csharp public Task DeleteGlobalClusterVariableAsync(ClusterVariableName name, CancellationToken ct = default) ``` Delete a global-scoped cluster variable Delete a global-scoped cluster variable. | Parameter | Type | Description | | --------- | --------------------- | ----------- | | `name` | `ClusterVariableName` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### DeleteTenantClusterVariableAsync(TenantId, ClusterVariableName, CancellationToken) ```csharp public Task DeleteTenantClusterVariableAsync(TenantId tenantId, ClusterVariableName name, CancellationToken ct = default) ``` Delete a tenant-scoped cluster variable Delete a tenant-scoped cluster variable. | Parameter | Type | Description | | ---------- | --------------------- | ----------- | | `tenantId` | `TenantId` | | | `name` | `ClusterVariableName` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### GetGlobalClusterVariableAsync(ClusterVariableName, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetGlobalClusterVariableAsync(ClusterVariableName name, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get a global-scoped cluster variable Get a global-scoped cluster variable. | Parameter | Type | Description | | ------------- | ------------------------------------------- | ----------- | | `name` | `ClusterVariableName` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### GetTenantClusterVariableAsync(TenantId, ClusterVariableName, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetTenantClusterVariableAsync(TenantId tenantId, ClusterVariableName name, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get a tenant-scoped cluster variable Get a tenant-scoped cluster variable. | Parameter | Type | Description | | ------------- | ------------------------------------------- | ----------- | | `tenantId` | `TenantId` | | | `name` | `ClusterVariableName` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### GetVariableAsync(VariableKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetVariableAsync(VariableKey variableKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get variable Get a variable by its key. This endpoint returns both process-level and local (element-scoped) variables. The variable's scopeKey indicates whether it's a process-level variable or scoped to a specific element instance. | Parameter | Type | Description | | ------------- | ------------------------------------ | ----------- | | `variableKey` | `VariableKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetVariableExample(VariableKey variableKey) { using var client = CamundaClient.Create(); var result = await client.GetVariableAsync(variableKey); Console.WriteLine($"Variable: {result.Name} = {result.Value}"); } ``` #### SearchClusterVariablesAsync(ClusterVariableSearchQueryRequest, bool?, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchClusterVariablesAsync(ClusterVariableSearchQueryRequest body, bool? truncateValues = null, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search for cluster variables based on given criteria. By default, long variable values in the response are truncated. | Parameter | Type | Description | | ---------------- | ------------------------------------------------------ | ----------- | | `body` | `ClusterVariableSearchQueryRequest` | | | `truncateValues` | `Nullable` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchClusterVariablesExample() { using var client = CamundaClient.Create(); var result = await client.SearchClusterVariablesAsync( new ClusterVariableSearchQueryRequest()); foreach (var variable in result.Items) { Console.WriteLine($"Variable: {variable.Name}"); } } ``` #### SearchVariablesAsync(VariableSearchQuery, bool?, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchVariablesAsync(VariableSearchQuery body, bool? truncateValues = null, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search variables Search for variables based on given criteria. This endpoint returns variables that exist directly at the specified scopes - it does not include variables from parent scopes that would be visible through the scope hierarchy. Variables can be process-level (scoped to the process instance) or local (scoped to specific BPMN elements like tasks, subprocesses, etc.). By default, long variable values in the response are truncated. | Parameter | Type | Description | | ---------------- | ----------------------------------------------- | ----------- | | `body` | `VariableSearchQuery` | | | `truncateValues` | `Nullable` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### UpdateGlobalClusterVariableAsync(ClusterVariableName, UpdateClusterVariableRequest, CancellationToken) ```csharp public Task UpdateGlobalClusterVariableAsync(ClusterVariableName name, UpdateClusterVariableRequest body, CancellationToken ct = default) ``` Update a global-scoped cluster variable Updates the value of an existing global cluster variable. The variable must exist, otherwise a 404 error is returned. | Parameter | Type | Description | | --------- | ------------------------------ | ----------- | | `name` | `ClusterVariableName` | | | `body` | `UpdateClusterVariableRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### UpdateTenantClusterVariableAsync(TenantId, ClusterVariableName, UpdateClusterVariableRequest, CancellationToken) ```csharp public Task UpdateTenantClusterVariableAsync(TenantId tenantId, ClusterVariableName name, UpdateClusterVariableRequest body, CancellationToken ct = default) ``` Update a tenant-scoped cluster variable Updates the value of an existing tenant-scoped cluster variable. The variable must exist, otherwise a 404 error is returned. | Parameter | Type | Description | | ---------- | ------------------------------ | ----------- | | `tenantId` | `TenantId` | | | `name` | `ClusterVariableName` | | | `body` | `UpdateClusterVariableRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` ### Mappings #### CreateMappingRuleAsync(MappingRuleCreateRequest, CancellationToken) ```csharp public Task CreateMappingRuleAsync(MappingRuleCreateRequest body, CancellationToken ct = default) ``` Create mapping rule Create a new mapping rule | Parameter | Type | Description | | --------- | -------------------------- | ----------- | | `body` | `MappingRuleCreateRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task CreateMappingRuleExample() { using var client = CamundaClient.Create(); var result = await client.CreateMappingRuleAsync(new MappingRuleCreateRequest { ClaimName = "groups", ClaimValue = "engineering", Name = "Engineering Group Mapping", }); Console.WriteLine($"Mapping rule: {result.MappingRuleId}"); } ``` #### DeleteMappingRuleAsync(MappingRuleId, CancellationToken) ```csharp public Task DeleteMappingRuleAsync(MappingRuleId mappingRuleId, CancellationToken ct = default) ``` Delete a mapping rule Deletes the mapping rule with the given ID. | Parameter | Type | Description | | --------------- | ------------------- | ----------- | | `mappingRuleId` | `MappingRuleId` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### GetMappingRuleAsync(MappingRuleId, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetMappingRuleAsync(MappingRuleId mappingRuleId, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get a mapping rule Gets the mapping rule with the given ID. | Parameter | Type | Description | | --------------- | --------------------------------------- | ----------- | | `mappingRuleId` | `MappingRuleId` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### SearchMappingRuleAsync(MappingRuleSearchQueryRequest, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchMappingRuleAsync(MappingRuleSearchQueryRequest body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search mapping rules Search for mapping rules based on given criteria. | Parameter | Type | Description | | ------------- | -------------------------------------------------- | ----------- | | `body` | `MappingRuleSearchQueryRequest` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` #### UpdateMappingRuleAsync(MappingRuleId, MappingRuleUpdateRequest, CancellationToken) ```csharp public Task UpdateMappingRuleAsync(MappingRuleId mappingRuleId, MappingRuleUpdateRequest body, CancellationToken ct = default) ``` Update mapping rule Update a mapping rule. | Parameter | Type | Description | | --------------- | -------------------------- | ----------- | | `mappingRuleId` | `MappingRuleId` | | | `body` | `MappingRuleUpdateRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` ### Decision Instances #### DeleteDecisionInstanceAsync(DecisionEvaluationKey, DeleteDecisionInstanceRequest, CancellationToken) ```csharp public Task DeleteDecisionInstanceAsync(DecisionEvaluationKey decisionEvaluationKey, DeleteDecisionInstanceRequest body, CancellationToken ct = default) ``` Delete decision instance Delete all associated decision evaluations based on provided key. | Parameter | Type | Description | | ----------------------- | ------------------------------- | ----------- | | `decisionEvaluationKey` | `DecisionEvaluationKey` | | | `body` | `DeleteDecisionInstanceRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task DeleteDecisionInstanceExample(DecisionEvaluationKey decisionEvaluationKey) { using var client = CamundaClient.Create(); await client.DeleteDecisionInstanceAsync( decisionEvaluationKey, new DeleteDecisionInstanceRequest()); } ``` #### DeleteDecisionInstancesBatchOperationAsync(DecisionInstanceDeletionBatchOperationRequest, CancellationToken) ```csharp public Task DeleteDecisionInstancesBatchOperationAsync(DecisionInstanceDeletionBatchOperationRequest body, CancellationToken ct = default) ``` Delete decision instances (batch) Delete multiple decision instances. This will delete the historic data from secondary storage. This is done asynchronously, the progress can be tracked using the batchOperationKey from the response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). | Parameter | Type | Description | | --------- | ----------------------------------------------- | ----------- | | `body` | `DecisionInstanceDeletionBatchOperationRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task DeleteDecisionInstancesBatchOperationExample() { using var client = CamundaClient.Create(); var result = await client.DeleteDecisionInstancesBatchOperationAsync( new DecisionInstanceDeletionBatchOperationRequest()); Console.WriteLine($"Batch operation key: {result.BatchOperationKey}"); } ``` #### GetDecisionInstanceAsync(DecisionEvaluationInstanceKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetDecisionInstanceAsync(DecisionEvaluationInstanceKey decisionEvaluationInstanceKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get decision instance Returns a decision instance. | Parameter | Type | Description | | ------------------------------- | ---------------------------------------------------- | ----------- | | `decisionEvaluationInstanceKey` | `DecisionEvaluationInstanceKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetDecisionInstanceExample(DecisionEvaluationInstanceKey decisionEvaluationInstanceKey) { using var client = CamundaClient.Create(); var result = await client.GetDecisionInstanceAsync( decisionEvaluationInstanceKey); Console.WriteLine($"Decision instance: {result.DecisionDefinitionId}"); } ``` #### SearchDecisionInstancesAsync(DecisionInstanceSearchQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchDecisionInstancesAsync(DecisionInstanceSearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search decision instances Search for decision instances based on given criteria. | Parameter | Type | Description | | ------------- | ------------------------------------------------------- | ----------- | | `body` | `DecisionInstanceSearchQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchDecisionInstancesExample() { using var client = CamundaClient.Create(); var result = await client.SearchDecisionInstancesAsync( new DecisionInstanceSearchQuery()); foreach (var di in result.Items) { Console.WriteLine($"Decision instance: {di.DecisionDefinitionId}"); } } ``` ### Decisions #### EvaluateDecisionAsync(DecisionEvaluationInstruction, CancellationToken) ```csharp public Task EvaluateDecisionAsync(DecisionEvaluationInstruction body, CancellationToken ct = default) ``` Evaluate decision Evaluates a decision. You specify the decision to evaluate either by using its unique key (as returned by DeployResource), or using the decision ID. When using the decision ID, the latest deployed version of the decision is used. | Parameter | Type | Description | | --------- | ------------------------------- | ----------- | | `body` | `DecisionEvaluationInstruction` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task EvaluateDecisionByIdExample(DecisionDefinitionId decisionDefinitionId) { using var client = CamundaClient.Create(); var result = await client.EvaluateDecisionAsync(new DecisionEvaluationById { DecisionDefinitionId = decisionDefinitionId, }); Console.WriteLine($"Decision output: {result.Output}"); } public static async Task EvaluateDecisionByKeyExample(DecisionDefinitionKey decisionDefinitionKey) { using var client = CamundaClient.Create(); var result = await client.EvaluateDecisionAsync(new DecisionEvaluationByKey { DecisionDefinitionKey = decisionDefinitionKey, }); Console.WriteLine($"Decision output: {result.Output}"); } ``` ### Audit Logs #### GetAuditLogAsync(AuditLogKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetAuditLogAsync(AuditLogKey auditLogKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get audit log Get an audit log entry by auditLogKey. | Parameter | Type | Description | | ------------- | ------------------------------------ | ----------- | | `auditLogKey` | `AuditLogKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetAuditLogExample(AuditLogKey auditLogKey) { using var client = CamundaClient.Create(); var result = await client.GetAuditLogAsync(auditLogKey); Console.WriteLine($"Audit log: {result.AuditLogKey}"); } ``` #### SearchAuditLogsAsync(AuditLogSearchQueryRequest, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchAuditLogsAsync(AuditLogSearchQueryRequest body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search audit logs Search for audit logs based on given criteria. | Parameter | Type | Description | | ------------- | ----------------------------------------------- | ----------- | | `body` | `AuditLogSearchQueryRequest` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchAuditLogsExample() { using var client = CamundaClient.Create(); var result = await client.SearchAuditLogsAsync( new AuditLogSearchQueryRequest()); foreach (var log in result.Items) { Console.WriteLine($"Audit log: {log.AuditLogKey}"); } } ``` ### Decision Definitions #### GetDecisionDefinitionAsync(DecisionDefinitionKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetDecisionDefinitionAsync(DecisionDefinitionKey decisionDefinitionKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get decision definition Returns a decision definition by key. | Parameter | Type | Description | | ----------------------- | ---------------------------------------------- | ----------- | | `decisionDefinitionKey` | `DecisionDefinitionKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetDecisionDefinitionExample(DecisionDefinitionKey decisionDefinitionKey) { using var client = CamundaClient.Create(); var result = await client.GetDecisionDefinitionAsync( decisionDefinitionKey); Console.WriteLine($"Decision definition: {result.Name}"); } ``` #### GetDecisionDefinitionXmlAsync(DecisionDefinitionKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetDecisionDefinitionXmlAsync(DecisionDefinitionKey decisionDefinitionKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get decision definition XML Returns decision definition as XML. | Parameter | Type | Description | | ----------------------- | ---------------------------- | ----------- | | `decisionDefinitionKey` | `DecisionDefinitionKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetDecisionDefinitionXmlExample(DecisionDefinitionKey decisionDefinitionKey) { using var client = CamundaClient.Create(); var result = await client.GetDecisionDefinitionXmlAsync( decisionDefinitionKey); Console.WriteLine($"XML: {result}"); } ``` #### SearchDecisionDefinitionsAsync(DecisionDefinitionSearchQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchDecisionDefinitionsAsync(DecisionDefinitionSearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search decision definitions Search for decision definitions based on given criteria. | Parameter | Type | Description | | ------------- | --------------------------------------------------------- | ----------- | | `body` | `DecisionDefinitionSearchQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchDecisionDefinitionsExample() { using var client = CamundaClient.Create(); var result = await client.SearchDecisionDefinitionsAsync( new DecisionDefinitionSearchQuery()); foreach (var dd in result.Items) { Console.WriteLine($"Decision definition: {dd.Name}"); } } ``` ### Decision Requirements #### GetDecisionRequirementsAsync(DecisionRequirementsKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetDecisionRequirementsAsync(DecisionRequirementsKey decisionRequirementsKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get decision requirements Returns Decision Requirements as JSON. | Parameter | Type | Description | | ------------------------- | ------------------------------------------------ | ----------- | | `decisionRequirementsKey` | `DecisionRequirementsKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetDecisionRequirementsExample(DecisionRequirementsKey decisionRequirementsKey) { using var client = CamundaClient.Create(); var result = await client.GetDecisionRequirementsAsync( decisionRequirementsKey); Console.WriteLine($"DRD: {result.DecisionRequirementsName}"); } ``` #### GetDecisionRequirementsXmlAsync(DecisionRequirementsKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetDecisionRequirementsXmlAsync(DecisionRequirementsKey decisionRequirementsKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get decision requirements XML Returns decision requirements as XML. | Parameter | Type | Description | | ------------------------- | ---------------------------- | ----------- | | `decisionRequirementsKey` | `DecisionRequirementsKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetDecisionRequirementsXmlExample(DecisionRequirementsKey decisionRequirementsKey) { using var client = CamundaClient.Create(); var result = await client.GetDecisionRequirementsXmlAsync( decisionRequirementsKey); Console.WriteLine($"XML: {result}"); } ``` #### SearchDecisionRequirementsAsync(DecisionRequirementsSearchQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchDecisionRequirementsAsync(DecisionRequirementsSearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search decision requirements Search for decision requirements based on given criteria. | Parameter | Type | Description | | ------------- | ----------------------------------------------------------- | ----------- | | `body` | `DecisionRequirementsSearchQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchDecisionRequirementsExample() { using var client = CamundaClient.Create(); var result = await client.SearchDecisionRequirementsAsync( new DecisionRequirementsSearchQuery()); foreach (var drd in result.Items) { Console.WriteLine($"DRD: {drd.DecisionRequirementsName}"); } } ``` ### Incidents #### GetIncidentAsync(IncidentKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetIncidentAsync(IncidentKey incidentKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get incident Returns incident as JSON. | Parameter | Type | Description | | ------------- | ------------------------------------ | ----------- | | `incidentKey` | `IncidentKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetIncidentExample(IncidentKey incidentKey) { using var client = CamundaClient.Create(); var result = await client.GetIncidentAsync(incidentKey); Console.WriteLine($"Incident: {result.IncidentKey}"); } ``` #### ResolveIncidentAsync(IncidentKey, IncidentResolutionRequest, CancellationToken) ```csharp public Task ResolveIncidentAsync(IncidentKey incidentKey, IncidentResolutionRequest body, CancellationToken ct = default) ``` Resolve incident Marks the incident as resolved; most likely a call to Update job will be necessary to reset the job's retries, followed by this call. | Parameter | Type | Description | | ------------- | --------------------------- | ----------- | | `incidentKey` | `IncidentKey` | | | `body` | `IncidentResolutionRequest` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task ResolveIncidentExample(IncidentKey incidentKey) { using var client = CamundaClient.Create(); await client.ResolveIncidentAsync( incidentKey, new IncidentResolutionRequest()); } ``` #### SearchElementInstanceIncidentsAsync(ElementInstanceKey, IncidentSearchQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchElementInstanceIncidentsAsync(ElementInstanceKey elementInstanceKey, IncidentSearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search for incidents of a specific element instance Search for incidents caused by the specified element instance, including incidents of any child instances created from this element instance. Although the `elementInstanceKey` is provided as a path parameter to indicate the root element instance, you may also include an `elementInstanceKey` within the filter object to narrow results to specific child element instances. This is useful, for example, if you want to isolate incidents associated with nested or subordinate elements within the given element instance while excluding incidents directly tied to the root element itself. | Parameter | Type | Description | | -------------------- | ----------------------------------------------- | ----------- | | `elementInstanceKey` | `ElementInstanceKey` | | | `body` | `IncidentSearchQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchElementInstanceIncidentsExample(ElementInstanceKey elementInstanceKey) { using var client = CamundaClient.Create(); var result = await client.SearchElementInstanceIncidentsAsync( elementInstanceKey, new IncidentSearchQuery()); foreach (var incident in result.Items) { Console.WriteLine($"Incident: {incident.IncidentKey}"); } } ``` #### SearchIncidentsAsync(IncidentSearchQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchIncidentsAsync(IncidentSearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search incidents Search for incidents based on given criteria. | Parameter | Type | Description | | ------------- | ----------------------------------------------- | ----------- | | `body` | `IncidentSearchQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchIncidentsExample() { using var client = CamundaClient.Create(); var result = await client.SearchIncidentsAsync(new IncidentSearchQuery()); foreach (var incident in result.Items) { Console.WriteLine($"Incident: {incident.IncidentKey}"); } } ``` ### Process Definitions #### GetProcessDefinitionAsync(ProcessDefinitionKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetProcessDefinitionAsync(ProcessDefinitionKey processDefinitionKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get process definition Returns process definition as JSON. | Parameter | Type | Description | | ---------------------- | --------------------------------------------- | ----------- | | `processDefinitionKey` | `ProcessDefinitionKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetProcessDefinitionExample(ProcessDefinitionKey processDefinitionKey) { using var client = CamundaClient.Create(); var result = await client.GetProcessDefinitionAsync( processDefinitionKey); Console.WriteLine($"Process definition: {result.Name}"); } ``` #### GetProcessDefinitionInstanceStatisticsAsync(ProcessDefinitionInstanceStatisticsQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetProcessDefinitionInstanceStatisticsAsync(ProcessDefinitionInstanceStatisticsQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get process instance statistics Get statistics about process instances, grouped by process definition and tenant. | Parameter | Type | Description | | ------------- | -------------------------------------------------------------------- | ----------- | | `body` | `ProcessDefinitionInstanceStatisticsQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetProcessDefinitionInstanceStatisticsExample() { using var client = CamundaClient.Create(); var result = await client.GetProcessDefinitionInstanceStatisticsAsync( new ProcessDefinitionInstanceStatisticsQuery()); foreach (var stat in result.Items) { Console.WriteLine($"Definition: {stat.ProcessDefinitionId}"); } } ``` #### GetProcessDefinitionInstanceVersionStatisticsAsync(ProcessDefinitionInstanceVersionStatisticsQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetProcessDefinitionInstanceVersionStatisticsAsync(ProcessDefinitionInstanceVersionStatisticsQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get process instance statistics by version Get statistics about process instances, grouped by version for a given process definition. The process definition ID must be provided as a required field in the request body filter. | Parameter | Type | Description | | ------------- | --------------------------------------------------------------------------- | ----------- | | `body` | `ProcessDefinitionInstanceVersionStatisticsQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetProcessDefinitionInstanceVersionStatisticsExample(ProcessDefinitionId processDefinitionId) { using var client = CamundaClient.Create(); var result = await client.GetProcessDefinitionInstanceVersionStatisticsAsync( new ProcessDefinitionInstanceVersionStatisticsQuery { Filter = new ProcessDefinitionInstanceVersionStatisticsFilter { ProcessDefinitionId = processDefinitionId, }, }); foreach (var stat in result.Items) { Console.WriteLine($"Version: {stat.ProcessDefinitionVersion}"); } } ``` #### GetProcessDefinitionMessageSubscriptionStatisticsAsync(ProcessDefinitionMessageSubscriptionStatisticsQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetProcessDefinitionMessageSubscriptionStatisticsAsync(ProcessDefinitionMessageSubscriptionStatisticsQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get message subscription statistics Get message subscription statistics, grouped by process definition. | Parameter | Type | Description | | ------------- | ------------------------------------------------------------------------------- | ----------- | | `body` | `ProcessDefinitionMessageSubscriptionStatisticsQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetProcessDefinitionMessageSubscriptionStatisticsExample() { using var client = CamundaClient.Create(); var result = await client.GetProcessDefinitionMessageSubscriptionStatisticsAsync( new ProcessDefinitionMessageSubscriptionStatisticsQuery()); foreach (var stat in result.Items) { Console.WriteLine($"Message subscriptions: {stat.ActiveSubscriptions}"); } } ``` #### GetProcessDefinitionStatisticsAsync(ProcessDefinitionKey, ProcessDefinitionElementStatisticsQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetProcessDefinitionStatisticsAsync(ProcessDefinitionKey processDefinitionKey, ProcessDefinitionElementStatisticsQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get process definition statistics Get statistics about elements in currently running process instances by process definition key and search filter. | Parameter | Type | Description | | ---------------------- | ------------------------------------------------------------------- | ----------- | | `processDefinitionKey` | `ProcessDefinitionKey` | | | `body` | `ProcessDefinitionElementStatisticsQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetProcessDefinitionStatisticsExample(ProcessDefinitionKey processDefinitionKey) { using var client = CamundaClient.Create(); var result = await client.GetProcessDefinitionStatisticsAsync( processDefinitionKey, new ProcessDefinitionElementStatisticsQuery()); foreach (var stat in result.Items) { Console.WriteLine($"Element: {stat.ElementId}"); } } ``` #### GetProcessDefinitionXmlAsync(ProcessDefinitionKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetProcessDefinitionXmlAsync(ProcessDefinitionKey processDefinitionKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get process definition XML Returns process definition as XML. | Parameter | Type | Description | | ---------------------- | ---------------------------- | ----------- | | `processDefinitionKey` | `ProcessDefinitionKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetProcessDefinitionXmlExample(ProcessDefinitionKey processDefinitionKey) { using var client = CamundaClient.Create(); var result = await client.GetProcessDefinitionXmlAsync( processDefinitionKey); Console.WriteLine($"XML: {result}"); } ``` #### GetStartProcessFormAsync(ProcessDefinitionKey, ConsistencyOptions\?, CancellationToken) ```csharp public Task GetStartProcessFormAsync(ProcessDefinitionKey processDefinitionKey, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Get process start form Get the start form of a process. Note that this endpoint will only return linked forms. This endpoint does not support embedded forms. | Parameter | Type | Description | | ---------------------- | -------------------------------- | ----------- | | `processDefinitionKey` | `ProcessDefinitionKey` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task GetStartProcessFormExample(ProcessDefinitionKey processDefinitionKey) { using var client = CamundaClient.Create(); var result = await client.GetStartProcessFormAsync( processDefinitionKey); Console.WriteLine($"Form: {result.FormKey}"); } ``` #### SearchProcessDefinitionVariableNamesAsync(ProcessDefinitionKey, ProcessDefinitionVariableNameSearchQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchProcessDefinitionVariableNamesAsync(ProcessDefinitionKey processDefinitionKey, ProcessDefinitionVariableNameSearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search process definition variable names Search for distinct variable names defined on a process definition, optionally narrowed by the name filter. | Parameter | Type | Description | | ---------------------- | -------------------------------------------------------------------- | ----------- | | `processDefinitionKey` | `ProcessDefinitionKey` | | | `body` | `ProcessDefinitionVariableNameSearchQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchProcessDefinitionVariableNamesExample(ProcessDefinitionKey processDefinitionKey) { using var client = CamundaClient.Create(); var result = await client.SearchProcessDefinitionVariableNamesAsync( processDefinitionKey, new ProcessDefinitionVariableNameSearchQuery()); foreach (var variable in result.Items) { Console.WriteLine($"Variable name: {variable.Name}"); } } ``` #### SearchProcessDefinitionsAsync(ProcessDefinitionSearchQuery, ConsistencyOptions\?, CancellationToken) ```csharp public Task SearchProcessDefinitionsAsync(ProcessDefinitionSearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default) ``` Search process definitions Search for process definitions based on given criteria. | Parameter | Type | Description | | ------------- | -------------------------------------------------------- | ----------- | | `body` | `ProcessDefinitionSearchQuery` | | | `consistency` | `ConsistencyOptions` | | | `ct` | `CancellationToken` | | **Returns:** `Task` **Example** ```csharp public static async Task SearchProcessDefinitionsExample() { using var client = CamundaClient.Create(); var result = await client.SearchProcessDefinitionsAsync( new ProcessDefinitionSearchQuery()); foreach (var pd in result.Items) { Console.WriteLine($"Process definition: {pd.Name}"); } } ``` --- ## Configuration(Api-reference) Configuration and authentication types for the Camunda C# SDK. ## CamundaOptions Options for constructing a `CamundaClient`. Mirrors the JS SDK's CamundaOptions with idiomatic C# conventions. ```csharp public sealed class CamundaOptions ``` ### Properties | Property | Type | Description | | -------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Config` | `Dictionary` | Strongly typed env-style overrides (CAMUNDA_* keys). | | `Configuration` | `IConfiguration` | An `Configuration.IConfiguration` section (typically `configuration.GetSection("Camunda")`) to bind settings from `appsettings.json` or any other configuration provider. Keys use PascalCase property names (e.g. `RestAddress`, `Auth:Strategy`) and are mapped to the canonical `CAMUNDA_*` env-var names internally. Precedence (highest wins): `CamundaOptions.Config` > `CamundaOptions.Configuration` > environment variables > defaults. | | `HttpClient` | `HttpClient` | Custom HttpClient factory. If not provided, a default HttpClient is created. | | `HttpMessageHandler` | `HttpMessageHandler` | Custom HttpMessageHandler for the internal HttpClient (ignored if HttpClient is set). Useful for tests (e.g., MockHttpMessageHandler). | | `Env` | `Dictionary` | Provide a custom env map (mainly for tests). Defaults to Environment.GetEnvironmentVariable. | | `LoggerFactory` | `ILoggerFactory` | Logger factory for SDK logging. | | `TimeProvider` | `TimeProvider` | Clock used for all SDK runtime cadence: worker poll loops, eventual consistency polling, retry backoff, backpressure decay, and OAuth refresh. Defaults to `CamundaTimeProvider.Live` — the system clock, clamped so it cannot move backwards. Supply a `FakeTimeProvider` to make the client's own timing virtual in tests. Note that this pins the client clock only; pinning the engine clock is a separate, explicit step via `PinClockAsync`. | | `RandomSource` | `IRandomSource` | Randomness used for all SDK runtime jitter: HTTP retry backoff, OAuth retry backoff, and worker startup staggering. Defaults to `CamundaRandomSource.Live`, so production jitter stays random. Supply a `SeededRandomSource` alongside a fake `CamundaOptions.TimeProvider` to make the client's timing reproducible as well as virtual. | ## CamundaConfig Hydrated Camunda configuration. Immutable after construction. ```csharp public sealed class CamundaConfig ``` ### Properties | Property | Type | Description | | ----------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `RestAddress` | `String` | | | `TokenAudience` | `String` | | | `DefaultTenantId` | `String` | | | `TenantIds` | `IReadOnlyList` | Tenant ids job workers activate for, from `CAMUNDA_TENANT_IDS` (comma-separated) or the `TenantIds` configuration path. `null` when unset, in which case activation falls back to `CamundaConfig.DefaultTenantId`. | | `HttpRetry` | `HttpRetryConfig` | | | `Backpressure` | `BackpressureConfig` | | | `OAuth` | `OAuthConfig` | | | `Auth` | `AuthConfig` | | | `Validation` | `ValidationConfig` | | | `LogLevel` | `String` | | | `Eventual` | `EventualConfig` | | | `WorkerDefaults` | `WorkerDefaultsConfig` | | | `Tls` | `TlsConfig` | | ## ConfigurationHydrator Hydrates a `CamundaConfig` from environment variables and overrides. Mirrors the JS SDK's hydrateConfig function. ```csharp public static class ConfigurationHydrator ``` ## AuthConfig ```csharp public sealed class AuthConfig ``` ### Properties | Property | Type | Description | | ---------- | ----------------- | ----------- | | `Strategy` | `AuthStrategy` | | | `Basic` | `BasicAuthConfig` | | ## AuthStrategy Supported authentication strategies. ```csharp public enum AuthStrategy ``` | Value | Description | | ------- | ----------- | | `None` | | | `OAuth` | | | `Basic` | | ## BasicAuthConfig ```csharp public sealed class BasicAuthConfig ``` ### Properties | Property | Type | Description | | ---------- | -------- | ----------- | | `Username` | `String` | | | `Password` | `String` | | ## OAuthConfig ```csharp public sealed class OAuthConfig ``` ### Properties | Property | Type | Description | | -------------- | ------------------ | ----------- | | `ClientId` | `String` | | | `ClientSecret` | `String` | | | `OAuthUrl` | `String` | | | `GrantType` | `String` | | | `Scope` | `String` | | | `TimeoutMs` | `Int32` | | | `Retry` | `OAuthRetryConfig` | | ## OAuthRetryConfig ```csharp public sealed class OAuthRetryConfig ``` ### Properties | Property | Type | Description | | ------------- | ------- | ----------- | | `Max` | `Int32` | | | `BaseDelayMs` | `Int32` | | ## HttpRetryConfig ```csharp public sealed class HttpRetryConfig ``` ### Properties | Property | Type | Description | | ------------- | ------- | ----------- | | `MaxAttempts` | `Int32` | | | `BaseDelayMs` | `Int32` | | | `MaxDelayMs` | `Int32` | | ## BackpressureConfig ```csharp public sealed class BackpressureConfig ``` ### Properties | Property | Type | Description | | -------------------- | --------- | ----------- | | `Enabled` | `Boolean` | | | `Profile` | `String` | | | `ObserveOnly` | `Boolean` | | | `InitialMax` | `Int32` | | | `SoftFactor` | `Double` | | | `SevereFactor` | `Double` | | | `RecoveryIntervalMs` | `Int32` | | | `RecoveryStep` | `Int32` | | | `DecayQuietMs` | `Int32` | | | `Floor` | `Int32` | | | `SevereThreshold` | `Int32` | | ## EventualConfig ```csharp public sealed class EventualConfig ``` ### Properties | Property | Type | Description | | --------------- | ------- | ----------- | | `PollDefaultMs` | `Int32` | | ## ValidationConfig ```csharp public sealed class ValidationConfig ``` ### Properties | Property | Type | Description | | ---------- | ---------------- | ----------- | | `Request` | `ValidationMode` | | | `Response` | `ValidationMode` | | | `Raw` | `String` | | ## ValidationMode Validation modes for request/response validation. ```csharp public enum ValidationMode ``` | Value | Description | | ----------- | ----------- | | `None` | | | `Warn` | | | `Strict` | | | `Fanatical` | | ## JobWorkerConfig Configuration for a `JobWorker`. ```csharp public sealed class JobWorkerConfig ``` ### Properties | Property | Type | Description | | ------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `JobType` | `String` | The BPMN job type to subscribe to (e.g. `"payment-service"`). | | `JobTimeoutMs` | `Nullable` | How long (in ms) the job is reserved for this worker before the broker makes it available to other workers. Falls back to `CAMUNDA_WORKER_TIMEOUT` environment variable. | | `MaxConcurrentJobs` | `Nullable` | Maximum number of jobs that may be in-flight (activated and being handled) concurrently by this worker. Controls how many jobs are requested per poll and how many handler tasks run in parallel. For I/O-bound handlers (HTTP calls, database queries), higher values (32–128) improve throughput because async handlers release threads during awaits. For CPU-bound handlers, set to `Environment.ProcessorCount` or lower to avoid over-subscribing the thread pool. Set to `1` for sequential (single-job-at-a-time) processing. Falls back to `CAMUNDA_WORKER_MAX_CONCURRENT_JOBS` environment variable, then `10`. | | `PollIntervalMs` | `Int32` | Delay (in ms) between poll cycles when no jobs are available or when at capacity. Default: 500 ms. | | `PollTimeoutMs` | `Nullable` | Long-poll timeout (in ms) sent to the broker. The broker holds the activation request open until jobs are available or this timeout elapses. `null` or `0` = broker default; negative = long polling disabled. | | `FetchVariables` | `List` | Variable names to fetch from the process instance scope. `null` = fetch all. | | `WorkerName` | `String` | Worker name sent to the broker for logging and diagnostics. Auto-generated if not set. | | `AutoStart` | `Boolean` | Whether to start polling immediately on creation. Default: `true`. | | `StartupJitterMaxSeconds` | `Double` | Maximum random delay (in seconds) before the worker starts polling. When multiple application instances restart simultaneously, this spreads out initial activation requests to avoid saturating the server. `0` (the default) means no delay. | | `TenantIds` | `IReadOnlyList` | Restrict job activation to the given tenant IDs (multi-tenant setups). Cannot be combined with `JobWorkerConfig.TenantId` — setting both is rejected with `ArgumentException`. If neither `JobWorkerConfig.TenantIds` nor `JobWorkerConfig.TenantId` is set (or `JobWorkerConfig.TenantIds` is empty), the activation request falls back to `CamundaConfig.TenantIds` (from the `CAMUNDA_TENANT_IDS` environment variable, comma-separated), then to `[CamundaConfig.DefaultTenantId]` (which itself defaults to `""` and can be overridden via the `CAMUNDA_DEFAULT_TENANT_ID` environment variable). | | `TenantId` | `String` | Convenience for the common single-tenant case. Equivalent to setting `JobWorkerConfig.TenantIds` to `[TenantId]`. Cannot be combined with `JobWorkerConfig.TenantIds`. | | `TenantFilter` | `Nullable` | Tenant filtering strategy for job activation. `TenantFilterEnum.PROVIDED` (the server default when this is `null`) activates jobs for the tenants named in `JobWorkerConfig.TenantIds` / `JobWorkerConfig.TenantId`, falling back to the default tenant. `TenantFilterEnum.ASSIGNED` activates jobs for whichever tenants are currently assigned to the authenticated client, re-evaluated by the server on every activation request — so tenant assignment changes take effect without restarting the worker. No tenant IDs are sent, and `JobWorkerConfig.TenantIds` / `JobWorkerConfig.TenantId` must not be set (the server would silently ignore them). Requires multi-tenancy to be enabled on the cluster; otherwise the server rejects the activation request with HTTP 400. Requires Camunda 8.9 or later. | | `WithLease` | `Boolean` | Activate jobs with a lease. Each job then carries a lease token that the worker sends back on complete, fail, and throw-error, so the engine can fence the command against a superseded activation (for example after the job timed out and another worker picked it up). Off by default, matching the engine. Requires a server that returns lease tokens: rather than degrade to unfenced commands, a worker that asked for a lease and is handed a job whose token is missing or invalid stops with `LeaseNotHonoredException`, surfaced through `CamundaClient.RunWorkersAsync`. | ## ConfigErrorCode Configuration hydration errors. ```csharp public enum ConfigErrorCode ``` | Value | Description | | ------------------------- | ----------- | | `MissingRequired` | | | `InvalidEnum` | | | `InvalidBoolean` | | | `InvalidInteger` | | | `InvalidValidationSyntax` | | ## ConfigErrorDetail ```csharp public sealed class ConfigErrorDetail ``` ### Properties | Property | Type | Description | | --------- | ----------------- | ----------- | | `Key` | `String` | | | `Code` | `ConfigErrorCode` | | | `Message` | `String` | | --- ## Enums Enumeration types (110 enums). ## AgentDefinitionSearchQuerySortRequestField The field to sort by. | Value | Description | | ----------------------------- | ----------- | | `AgentDefinitionKey` | | | `AgentType` | | | `Name` | | | `ElementId` | | | `ProcessDefinitionId` | | | `ProcessDefinitionKey` | | | `ProcessDefinitionVersion` | | | `ProcessDefinitionVersionTag` | | | `TenantId` | | ## AgentDefinitionTypeEnum The kind of agent an agent definition describes. | Value | Description | | ------------------- | ----------- | | `AIAGENTSUBPROCESS` | | | `AIAGENTTASK` | | | `EXTERNALAGENT` | | ## AgentInstanceHistoryCommitStatusEnum The commit status of a history item. COMMITTED: the producing job completed successfully. PENDING: the producing job is still active (in-flight). DISCARDED: the producing job failed; this item was superseded by a later activation. | Value | Description | | ----------- | ----------- | | `COMMITTED` | | | `PENDING` | | | `DISCARDED` | | ## AgentInstanceHistoryRoleEnum The role of a history item in the agent conversation. | Value | Description | | --------------- | ----------- | | `USER` | | | `ASSISTANT` | | | `TOOLRESULT` | | | `CONFIGURATION` | | ## AgentInstanceHistorySearchQuerySortRequestField The field to sort by. | Value | Description | | ---------------- | ----------- | | `ProducedAt` | | | `HistoryItemKey` | | | `LoopIteration` | | ## AgentInstanceMessageContentTypeEnum The content type discriminator for a history item content block. | Value | Description | | ---------- | ----------- | | `TEXT` | | | `DOCUMENT` | | | `OBJECT` | | ## AgentInstanceSearchQuerySortRequestField The field to sort by. | Value | Description | | ------------------------ | ----------- | | `AgentInstanceKey` | | | `AgentDefinitionKey` | | | `Status` | | | `ElementId` | | | `ProcessInstanceKey` | | | `RootProcessInstanceKey` | | | `ProcessDefinitionKey` | | | `TenantId` | | | `CreationDate` | | | `LastUpdatedDate` | | | `CompletionDate` | | ## AgentInstanceStatusEnum The current status of an agent instance. | Value | Description | | --------------- | ----------- | | `UNKNOWN` | | | `COMPLETED` | | | `IDLE` | | | `INITIALIZING` | | | `THINKING` | | | `TOOLCALLING` | | | `TOOLDISCOVERY` | | ## AgentInstanceUpdateStatusEnum The status values that can be set on an agent instance via an update request. | Value | Description | | --------------- | ----------- | | `IDLE` | | | `THINKING` | | | `TOOLCALLING` | | | `TOOLDISCOVERY` | | ## AuditLogActorTypeEnum The type of actor who performed the operation. | Value | Description | | ----------- | ----------- | | `ANONYMOUS` | | | `CLIENT` | | | `UNKNOWN` | | | `USER` | | ## AuditLogCategoryEnum The category of the audit log operation. | Value | Description | | ------------------- | ----------- | | `ADMIN` | | | `DEPLOYEDRESOURCES` | | | `USERTASKS` | | ## AuditLogEntityTypeEnum The type of entity affected by the operation. | Value | Description | | ----------------- | ----------- | | `AUTHORIZATION` | | | `BATCH` | | | `DECISION` | | | `GROUP` | | | `INCIDENT` | | | `JOB` | | | `MAPPINGRULE` | | | `PROCESSINSTANCE` | | | `RESOURCE` | | | `ROLE` | | | `TENANT` | | | `USER` | | | `USERTASK` | | | `VARIABLE` | | | `CLIENT` | | ## AuditLogOperationTypeEnum The type of operation performed. | Value | Description | | ---------- | ----------- | | `ASSIGN` | | | `CANCEL` | | | `COMPLETE` | | | `CREATE` | | | `DELETE` | | | `EVALUATE` | | | `MIGRATE` | | | `MODIFY` | | | `RESOLVE` | | | `RESUME` | | | `SUSPEND` | | | `UNASSIGN` | | | `UNKNOWN` | | | `UPDATE` | | ## AuditLogResultEnum The result status of the operation. | Value | Description | | --------- | ----------- | | `FAIL` | | | `SUCCESS` | | ## AuditLogSearchQuerySortRequestField The field to sort by. | Value | Description | | ------------------------- | ----------- | | `ActorId` | | | `ActorType` | | | `AuditLogKey` | | | `BatchOperationKey` | | | `BatchOperationType` | | | `Category` | | | `DecisionDefinitionId` | | | `DecisionDefinitionKey` | | | `DecisionEvaluationKey` | | | `DecisionRequirementsId` | | | `DecisionRequirementsKey` | | | `ElementInstanceKey` | | | `EntityKey` | | | `EntityType` | | | `JobKey` | | | `OperationType` | | | `ProcessDefinitionId` | | | `ProcessDefinitionKey` | | | `ProcessInstanceKey` | | | `InboundChannelType` | | | `InboundChannelToolName` | | | `Result` | | | `TenantId` | | | `Timestamp` | | | `UserTaskKey` | | ## AuthorizationSearchQuerySortRequestField The field to sort by. | Value | Description | | ---------------------- | ----------- | | `OwnerId` | | | `OwnerType` | | | `ResourceId` | | | `ResourcePropertyName` | | | `ResourceType` | | ## BackupType The type of the backup. | Value | Description | | ----------------- | ----------- | | `MANUALBACKUP` | | | `SCHEDULEDBACKUP` | | ## BatchOperationErrorType The type of the error that occurred during the batch operation. | Value | Description | | -------------------------- | ----------- | | `QUERYFAILED` | | | `RESULTBUFFERSIZEEXCEEDED` | | ## BatchOperationItemResponseState State of the item. | Value | Description | | ----------- | ----------- | | `ACTIVE` | | | `COMPLETED` | | | `SKIPPED` | | | `CANCELED` | | | `FAILED` | | ## BatchOperationItemSearchQuerySortRequestField The field to sort by. | Value | Description | | -------------------- | ----------- | | `BatchOperationKey` | | | `ItemKey` | | | `ProcessInstanceKey` | | | `ProcessedDate` | | | `State` | | ## BatchOperationItemStateEnum The batch operation item state. | Value | Description | | ----------- | ----------- | | `ACTIVE` | | | `COMPLETED` | | | `CANCELED` | | | `FAILED` | | ## BatchOperationSearchQuerySortRequestField The field to sort by. | Value | Description | | ------------------- | ----------- | | `BatchOperationKey` | | | `OperationType` | | | `State` | | | `StartDate` | | | `EndDate` | | | `ActorType` | | | `ActorId` | | ## BatchOperationStateEnum The batch operation state. | Value | Description | | -------------------- | ----------- | | `ACTIVE` | | | `CANCELED` | | | `COMPLETED` | | | `CREATED` | | | `FAILED` | | | `PARTIALLYCOMPLETED` | | | `SUSPENDED` | | ## BatchOperationTypeEnum The type of the batch operation. | Value | Description | | -------------------------- | ----------- | | `ADDVARIABLE` | | | `CANCELPROCESSINSTANCE` | | | `DELETEDECISIONDEFINITION` | | | `DELETEDECISIONINSTANCE` | | | `DELETEPROCESSDEFINITION` | | | `DELETEPROCESSINSTANCE` | | | `MIGRATEPROCESSINSTANCE` | | | `MODIFYPROCESSINSTANCE` | | | `RESOLVEINCIDENT` | | | `RESUMEPROCESSINSTANCE` | | | `SUSPENDPROCESSINSTANCE` | | | `UPDATEJOB` | | | `UPDATEVARIABLE` | | ## CamundaAuthErrorCode Auth error codes matching the JS SDK. | Value | Description | | ------------------------- | ----------- | | `TokenFetchFailed` | | | `TokenParseFailed` | | | `TokenExpired` | | | `OAuthConfigMissing` | | | `BasicCredentialsMissing` | | ## CheckpointType The type of the checkpoint. | Value | Description | | ----------------- | ----------- | | `MARKER` | | | `SCHEDULEDBACKUP` | | | `MANUALBACKUP` | | ## CloudStage The cloud deployment stage. | Value | Description | | ------ | ----------- | | `Dev` | | | `Int` | | | `Prod` | | ## ClusterBalanceResponseState The cluster's aggregate balance state as of the time of the request. | Value | Description | | ------------ | ----------- | | `BALANCED` | | | `BALANCING` | | | `UNBALANCED` | | ## ClusterCompletedRebalanceResult How the rebalance ended. | Value | Description | | ----------- | ----------- | | `COMPLETED` | | | `CANCELLED` | | | `FAILED` | | ## ClusterHistoryBackupTenantState What a physical tenant reports for a history backup id: the per-tenant `HistoryBackupStateCode` extended with `NOT_FOUND` for a tenant that was read and does not hold the backup. `NOT_FOUND` is a successful observation, not a failure — a backup that only some physical tenants hold is a supported outcome. There is no state for a tenant that could not be read at all, because such a tenant fails the whole request. | Value | Description | | -------------- | ----------- | | `INPROGRESS` | | | `COMPLETED` | | | `FAILED` | | | `INCOMPLETE` | | | `INCOMPATIBLE` | | | `NOTFOUND` | | ## ClusterRebalanceOperationPartitionProgress Where this rebalance has reached for the partition. | Value | Description | | -------------- | ----------- | | `PENDING` | | | `TRANSFERRING` | | | `COMPLETED` | | ## ClusterRebalanceOperationPartitionResult The terminal outcome, present only when progress is COMPLETED. | Value | Description | | ------------------------------- | ----------- | | `TRANSFERRED` | | | `ALREADYLEADER` | | | `NOTMEMBER` | | | `NOTREPLICATING` | | | `UNREACHABLE` | | | `NOTCOORDINATOR` | | | `STALECONFIGURATION` | | | `TRANSFERINPROGRESS` | | | `LAGTOOHIGH` | | | `LEADERINITIALIZING` | | | `CONFIGURATIONCHANGEINPROGRESS` | | | `PAUSEFAILED` | | | `REPLICATIONTIMEDOUT` | | | `TIMEOUTNOWEXHAUSTED` | | | `LEADERCHANGED` | | | `NOLEADER` | | | `NORESPONSE` | | | `CANCELLED` | | | `PHYSICALTENANTDISABLED` | | | `PHYSICALTENANTRECOVERING` | | ## ClusterRebalancePartitionState Whether this partition is being actively transferred, unbalanced, or balanced. | Value | Description | | -------------- | ----------- | | `TRANSFERRING` | | | `UNBALANCED` | | | `BALANCED` | | ## ClusterRuntimeBackupTakeOutcome What a physical tenant did with the trigger. `TRIGGERED` says the backup is running, not that it completed — poll `GET /cluster/v2/backups/runtime/{backupId}` for that. A `FAILED` tenant is running no backup for this request and needs no cleanup. `UNKNOWN` means the broker may or may not have accepted the request — the connection was cut mid-flight, or the gateway timed out waiting — so that tenant's backups have to be checked before retrying; it is reported separately from `FAILED` precisely because calling it failed would claim nothing is running there. Tenants that were triggered are never rolled back. | Value | Description | | ----------- | ----------- | | `TRIGGERED` | | | `FAILED` | | | `UNKNOWN` | | ## ClusterStatusResponseStatus `HEALTHY` when every physical tenant is healthy, `DOWN` when no physical tenant can process work, `DEGRADED` in every other case. | Value | Description | | ---------- | ----------- | | `HEALTHY` | | | `DEGRADED` | | | `DOWN` | | ## ClusterUpgradeStatusResponseStatus `MIGRATED` once every known upgrade-readiness condition is met for every known physical tenant; `MIGRATION_IN_PROGRESS` when at least one is confirmed not yet migrated; `UNKNOWN` otherwise. | Value | Description | | --------------------- | ----------- | | `MIGRATED` | | | `MIGRATIONINPROGRESS` | | | `UNKNOWN` | | ## ClusterVariableKindEnum The kind of a cluster variable. JSON is the default. SECRET_REFERENCE allows the value to contain camunda.secrets.X references that are resolved at job activation time. | Value | Description | | ----------------- | ----------- | | `JSON` | | | `SECRETREFERENCE` | | ## ClusterVariableScopeEnum The scope of a cluster variable. | Value | Description | | -------- | ----------- | | `GLOBAL` | | | `TENANT` | | ## ClusterVariableSearchQuerySortRequestField The field to sort by. | Value | Description | | ---------- | ----------- | | `Name` | | | `Value` | | | `TenantId` | | | `Scope` | | ## CorrelatedMessageSubscriptionSearchQuerySortRequestField The field to sort by. | Value | Description | | ---------------------- | ----------- | | `BusinessId` | | | `CorrelationKey` | | | `CorrelationTime` | | | `ElementId` | | | `ElementInstanceKey` | | | `MessageKey` | | | `MessageName` | | | `PartitionId` | | | `ProcessDefinitionId` | | | `ProcessDefinitionKey` | | | `ProcessInstanceKey` | | | `SubscriptionKey` | | | `TenantId` | | ## DecisionDefinitionSearchQuerySortRequestField The field to sort by. | Value | Description | | ----------------------------- | ----------- | | `DecisionDefinitionKey` | | | `DecisionDefinitionId` | | | `Name` | | | `Version` | | | `DecisionRequirementsId` | | | `DecisionRequirementsKey` | | | `DecisionRequirementsName` | | | `DecisionRequirementsVersion` | | | `TenantId` | | ## DecisionDefinitionTypeEnum The type of the decision. UNSPECIFIED is deprecated and should not be used anymore, for removal in 8.10 | Value | Description | | ------------------- | ----------- | | `DECISIONTABLE` | | | `LITERALEXPRESSION` | | | `UNSPECIFIED` | | | `UNKNOWN` | | ## DecisionInstanceSearchQuerySortRequestField The field to sort by. | Value | Description | | ------------------------------- | ----------- | | `BusinessId` | | | `DecisionDefinitionId` | | | `DecisionDefinitionKey` | | | `DecisionDefinitionName` | | | `DecisionDefinitionType` | | | `DecisionDefinitionVersion` | | | `DecisionEvaluationInstanceKey` | | | `DecisionEvaluationKey` | | | `ElementInstanceKey` | | | `EvaluationDate` | | | `EvaluationFailure` | | | `ProcessDefinitionKey` | | | `ProcessInstanceKey` | | | `RootDecisionDefinitionKey` | | | `State` | | | `TenantId` | | ## DecisionInstanceStateEnum The state of the decision instance. UNSPECIFIED and UNKNOWN are deprecated and should not be used anymore, for removal in 8.10 | Value | Description | | ------------- | ----------- | | `EVALUATED` | | | `FAILED` | | | `UNSPECIFIED` | | | `UNKNOWN` | | ## DecisionRequirementsSearchQuerySortRequestField The field to sort by. | Value | Description | | -------------------------- | ----------- | | `DecisionRequirementsKey` | | | `DecisionRequirementsName` | | | `Version` | | | `DecisionRequirementsId` | | | `TenantId` | | ## DocumentReferenceCamundaDocumentType Document discriminator. Always set to "camunda". | Value | Description | | --------- | ----------- | | `Camunda` | | ## ElementInstanceFilterFieldsType Type of element as defined set of values. | Value | Description | | ------------------------------ | ----------- | | `UNSPECIFIED` | | | `PROCESS` | | | `SUBPROCESS` | | | `EVENTSUBPROCESS` | | | `ADHOCSUBPROCESS` | | | `ADHOCSUBPROCESSINNERINSTANCE` | | | `STARTEVENT` | | | `INTERMEDIATECATCHEVENT` | | | `INTERMEDIATETHROWEVENT` | | | `BOUNDARYEVENT` | | | `ENDEVENT` | | | `SERVICETASK` | | | `RECEIVETASK` | | | `USERTASK` | | | `MANUALTASK` | | | `TASK` | | | `EXCLUSIVEGATEWAY` | | | `INCLUSIVEGATEWAY` | | | `PARALLELGATEWAY` | | | `EVENTBASEDGATEWAY` | | | `SEQUENCEFLOW` | | | `MULTIINSTANCEBODY` | | | `CALLACTIVITY` | | | `BUSINESSRULETASK` | | | `SCRIPTTASK` | | | `SENDTASK` | | | `UNKNOWN` | | ## ElementInstanceFilterType Type of element as defined set of values. | Value | Description | | ------------------------------ | ----------- | | `UNSPECIFIED` | | | `PROCESS` | | | `SUBPROCESS` | | | `EVENTSUBPROCESS` | | | `ADHOCSUBPROCESS` | | | `ADHOCSUBPROCESSINNERINSTANCE` | | | `STARTEVENT` | | | `INTERMEDIATECATCHEVENT` | | | `INTERMEDIATETHROWEVENT` | | | `BOUNDARYEVENT` | | | `ENDEVENT` | | | `SERVICETASK` | | | `RECEIVETASK` | | | `USERTASK` | | | `MANUALTASK` | | | `TASK` | | | `EXCLUSIVEGATEWAY` | | | `INCLUSIVEGATEWAY` | | | `PARALLELGATEWAY` | | | `EVENTBASEDGATEWAY` | | | `SEQUENCEFLOW` | | | `MULTIINSTANCEBODY` | | | `CALLACTIVITY` | | | `BUSINESSRULETASK` | | | `SCRIPTTASK` | | | `SENDTASK` | | | `UNKNOWN` | | ## ElementInstanceResultType Type of element as defined set of values. | Value | Description | | ------------------------------ | ----------- | | `UNSPECIFIED` | | | `PROCESS` | | | `SUBPROCESS` | | | `EVENTSUBPROCESS` | | | `ADHOCSUBPROCESS` | | | `ADHOCSUBPROCESSINNERINSTANCE` | | | `STARTEVENT` | | | `INTERMEDIATECATCHEVENT` | | | `INTERMEDIATETHROWEVENT` | | | `BOUNDARYEVENT` | | | `ENDEVENT` | | | `SERVICETASK` | | | `RECEIVETASK` | | | `USERTASK` | | | `MANUALTASK` | | | `TASK` | | | `EXCLUSIVEGATEWAY` | | | `INCLUSIVEGATEWAY` | | | `PARALLELGATEWAY` | | | `EVENTBASEDGATEWAY` | | | `SEQUENCEFLOW` | | | `MULTIINSTANCEBODY` | | | `CALLACTIVITY` | | | `BUSINESSRULETASK` | | | `SCRIPTTASK` | | | `SENDTASK` | | | `UNKNOWN` | | ## ElementInstanceSearchQuerySortRequestField The field to sort by. | Value | Description | | ---------------------- | ----------- | | `ElementInstanceKey` | | | `ProcessInstanceKey` | | | `ProcessDefinitionKey` | | | `ProcessDefinitionId` | | | `StartDate` | | | `EndDate` | | | `ElementId` | | | `ElementName` | | | `Type` | | | `State` | | | `IncidentKey` | | | `TenantId` | | ## ElementInstanceStateEnum Element states | Value | Description | | ------------ | ----------- | | `ACTIVE` | | | `COMPLETED` | | | `TERMINATED` | | ## ElementInstanceWaitStateQuerySortRequestField The field to sort by. | Value | Description | | ------------------------ | ----------- | | `ElementInstanceKey` | | | `ProcessInstanceKey` | | | `RootProcessInstanceKey` | | | `ElementId` | | ## ExportingStatusCode The exporting status of a physical tenant, aggregated over every replica of every one of its partitions: - `EXPORTING`: all replicas are exporting and committing their position. - `PAUSED`: all replicas are paused, nothing is being exported. - `SOFT_PAUSED`: all replicas keep exporting but do not commit their position. - `MIXED`: replicas report different phases, so the tenant is in no single phase. | Value | Description | | ------------ | ----------- | | `EXPORTING` | | | `PAUSED` | | | `SOFTPAUSED` | | | `MIXED` | | ## GlobalListenerSourceEnum How the global listener was defined. | Value | Description | | --------------- | ----------- | | `CONFIGURATION` | | | `API` | | ## GlobalTaskListenerEventTypeEnum The event type that triggers the user task listener. | Value | Description | | ------------ | ----------- | | `All` | | | `Creating` | | | `Assigning` | | | `Updating` | | | `Completing` | | | `Canceling` | | ## GlobalTaskListenerSearchQuerySortRequestField The field to sort by. | Value | Description | | ---------------- | ----------- | | `Id` | | | `Type` | | | `AfterNonGlobal` | | | `Priority` | | | `Source` | | ## GroupClientSearchQuerySortRequestField The field to sort by. | Value | Description | | ---------- | ----------- | | `ClientId` | | ## GroupSearchQuerySortRequestField The field to sort by. | Value | Description | | --------- | ----------- | | `Name` | | | `GroupId` | | ## GroupUserSearchQuerySortRequestField The field to sort by. | Value | Description | | ---------- | ----------- | | `Username` | | ## HistoryBackupStateCode The aggregated state of a history backup, computed from the state of each of its snapshots. | Value | Description | | -------------- | ----------- | | `INPROGRESS` | | | `COMPLETED` | | | `FAILED` | | | `INCOMPLETE` | | | `INCOMPATIBLE` | | ## IncidentErrorTypeEnum Incident error type with a defined set of values. | Value | Description | | ---------------------------- | ----------- | | `ADHOCSUBPROCESSNORETRIES` | | | `CALLEDDECISIONERROR` | | | `CALLEDELEMENTERROR` | | | `CONDITIONERROR` | | | `DECISIONEVALUATIONERROR` | | | `EXECUTIONLISTENERNORETRIES` | | | `EXTRACTVALUEERROR` | | | `FORMNOTFOUND` | | | `IOMAPPINGERROR` | | | `JOBNORETRIES` | | | `MESSAGESIZEEXCEEDED` | | | `RESOURCENOTFOUND` | | | `SECRETRESOLUTIONERROR` | | | `TASKLISTENERNORETRIES` | | | `UNHANDLEDERROREVENT` | | | `UNKNOWN` | | | `UNSPECIFIED` | | ## IncidentProcessInstanceStatisticsByDefinitionQuerySortRequestField The aggregated field by which the process instance statistics are sorted. | Value | Description | | ------------------------------- | ----------- | | `ActiveInstancesWithErrorCount` | | | `ProcessDefinitionKey` | | | `TenantId` | | ## IncidentProcessInstanceStatisticsByErrorQuerySortRequestField The field to sort the incident error statistics by. | Value | Description | | ------------------------------- | ----------- | | `ErrorMessage` | | | `ActiveInstancesWithErrorCount` | | ## IncidentSearchQuerySortRequestField The field to sort by. | Value | Description | | ---------------------- | ----------- | | `IncidentKey` | | | `ProcessDefinitionKey` | | | `ProcessDefinitionId` | | | `ProcessInstanceKey` | | | `ErrorType` | | | `ElementId` | | | `ElementInstanceKey` | | | `CreationTime` | | | `State` | | | `JobKey` | | | `TenantId` | | ## IncidentStateEnum Incident states with a defined set of values. | Value | Description | | ---------- | ----------- | | `ACTIVE` | | | `MIGRATED` | | | `PENDING` | | | `RESOLVED` | | | `UNKNOWN` | | ## JobKindEnum The job kind. | Value | Description | | ------------------- | ----------- | | `BPMNELEMENT` | | | `EXECUTIONLISTENER` | | | `TASKLISTENER` | | | `ADHOCSUBPROCESS` | | ## JobListenerEventTypeEnum The listener event type of the job. | Value | Description | | ------------- | ----------- | | `ASSIGNING` | | | `BEFOREALL` | | | `CANCEL` | | | `CANCELING` | | | `COMPLETING` | | | `CREATING` | | | `END` | | | `START` | | | `UNSPECIFIED` | | | `UPDATING` | | ## JobSearchQuerySortRequestField The field to sort by. | Value | Description | | -------------------------- | ----------- | | `CreationTime` | | | `Deadline` | | | `DeniedReason` | | | `ElementId` | | | `ElementInstanceKey` | | | `EndTime` | | | `ErrorCode` | | | `ErrorMessage` | | | `HasFailedWithRetriesLeft` | | | `IsDenied` | | | `JobKey` | | | `Kind` | | | `ListenerEventType` | | | `Priority` | | | `ProcessDefinitionId` | | | `ProcessDefinitionKey` | | | `ProcessInstanceKey` | | | `Retries` | | | `State` | | | `TenantId` | | | `Type` | | | `Worker` | | ## JobStateEnum The state of the job. | Value | Description | | ----------------- | ----------- | | `CANCELED` | | | `COMPLETED` | | | `CREATED` | | | `ERRORTHROWN` | | | `FAILED` | | | `MIGRATED` | | | `PRIORITYUPDATED` | | | `RETRIESUPDATED` | | | `TIMEOUTUPDATED` | | | `TIMEDOUT` | | ## MappingRuleSearchQuerySortRequestField The field to sort by. | Value | Description | | --------------- | ----------- | | `MappingRuleId` | | | `ClaimName` | | | `ClaimValue` | | | `Name` | | ## MessageSubscriptionSearchQuerySortRequestField The field to sort by. | Value | Description | | -------------------------- | ----------- | | `BusinessId` | | | `MessageSubscriptionKey` | | | `ProcessDefinitionId` | | | `ProcessDefinitionName` | | | `ProcessDefinitionVersion` | | | `ProcessInstanceKey` | | | `ElementId` | | | `ElementInstanceKey` | | | `MessageSubscriptionState` | | | `MessageSubscriptionType` | | | `LastUpdatedDate` | | | `MessageName` | | | `CorrelationKey` | | | `TenantId` | | | `ToolName` | | | `InboundConnectorType` | | ## MessageSubscriptionStateEnum The state of message subscription. **Note for `START_EVENT` subscriptions:** The `CORRELATED` and `MIGRATED` states are not tracked for these subscriptions. To query correlation history for process start events, use the `/correlated-message-subscriptions/search` endpoint. | Value | Description | | ------------ | ----------- | | `CORRELATED` | | | `CREATED` | | | `DELETED` | | | `MIGRATED` | | ## MessageSubscriptionTypeEnum The type of message subscription. `START_EVENT` is definition-scoped (process start events). Always has a value; only captured from Camunda 8.10 onwards. `PROCESS_EVENT` is instance-scoped (intermediate catch events). Pre-8.10 entries have no value stored; the API returns `PROCESS_EVENT` as a default for those entries. | Value | Description | | -------------- | ----------- | | `STARTEVENT` | | | `PROCESSEVENT` | | ## Mode The operating mode of a cluster's partitions. | Value | Description | | ------------ | ----------- | | `PROCESSING` | | | `RECOVERING` | | ## OwnerTypeEnum The type of the owner of permissions. | Value | Description | | ------------- | ----------- | | `USER` | | | `CLIENT` | | | `ROLE` | | | `GROUP` | | | `MAPPINGRULE` | | | `UNSPECIFIED` | | ## PartitionHealth Describes the current health of the partition. | Value | Description | | ----------- | ----------- | | `Healthy` | | | `Unhealthy` | | | `Dead` | | ## PartitionRole Describes the Raft role of the broker for a given partition. | Value | Description | | ---------- | ----------- | | `Leader` | | | `Follower` | | | `Inactive` | | ## PartitionState Describes the current operational state of the partition within the cluster configuration. | Value | Description | | ------------ | ----------- | | `Unknown` | | | `Joining` | | | `Active` | | | `Leaving` | | | `Recovering` | | | `Learner` | | ## PermissionTypeEnum Specifies the type of permissions. | Value | Description | | ---------------------------------------------- | ----------- | | `ACCESS` | | | `CANCELPROCESSINSTANCE` | | | `CLAIM` | | | `CLAIMUSERTASK` | | | `COMPLETE` | | | `COMPLETEUSERTASK` | | | `CREATE` | | | `CREATEBATCHOPERATIONCANCELPROCESSINSTANCE` | | | `CREATEBATCHOPERATIONDELETEDECISIONDEFINITION` | | | `CREATEBATCHOPERATIONDELETEDECISIONINSTANCE` | | | `CREATEBATCHOPERATIONDELETEPROCESSDEFINITION` | | | `CREATEBATCHOPERATIONDELETEPROCESSINSTANCE` | | | `CREATEBATCHOPERATIONMIGRATEPROCESSINSTANCE` | | | `CREATEBATCHOPERATIONMODIFYPROCESSINSTANCE` | | | `CREATEBATCHOPERATIONRESOLVEINCIDENT` | | | `CREATEBATCHOPERATIONSUSPENDPROCESSINSTANCE` | | | `CREATEBATCHOPERATIONUPDATEJOB` | | | `CREATEDECISIONINSTANCE` | | | `CREATEPROCESSINSTANCE` | | | `CREATETASKLISTENER` | | | `DELETE` | | | `DELETEDECISIONINSTANCE` | | | `DELETEDRD` | | | `DELETEFORM` | | | `DELETEPROCESS` | | | `DELETEPROCESSINSTANCE` | | | `DELETERESOURCE` | | | `DELETETASKLISTENER` | | | `EVALUATE` | | | `MODIFYPROCESSINSTANCE` | | | `PAUSE` | | | `READ` | | | `READDECISIONDEFINITION` | | | `READDECISIONINSTANCE` | | | `READJOBMETRIC` | | | `READPROCESSDEFINITION` | | | `READPROCESSINSTANCE` | | | `READUSAGEMETRIC` | | | `READUSERTASK` | | | `READTASKLISTENER` | | | `RESTORE` | | | `REVEAL` | | | `SUSPENDPROCESSINSTANCE` | | | `UPDATE` | | | `UPDATEPROCESSINSTANCE` | | | `UPDATEUSERTASK` | | | `UPDATETASKLISTENER` | | ## ProcessDefinitionFilterState Filter by the process definition's state. When not set, process definitions in any state are returned. Set to `ACTIVE` to exclude draining and deleted definitions (recommended for most use cases). Set to `DRAINING` to return only definitions that are being deleted but still have active process instances draining. Set to `DELETED` to return only definitions that have been deleted but are still retained in secondary storage. | Value | Description | | ---------- | ----------- | | `ACTIVE` | | | `DRAINING` | | | `DELETED` | | ## ProcessDefinitionInstanceStatisticsQuerySortRequestField The field to sort by. | Value | Description | | ------------------------------------- | ----------- | | `ProcessDefinitionId` | | | `ActiveInstancesWithIncidentCount` | | | `ActiveInstancesWithoutIncidentCount` | | ## ProcessDefinitionInstanceVersionStatisticsQuerySortRequestField The field to sort by. | Value | Description | | ------------------------------------- | ----------- | | `ProcessDefinitionId` | | | `ProcessDefinitionKey` | | | `ProcessDefinitionName` | | | `ProcessDefinitionVersion` | | | `ActiveInstancesWithIncidentCount` | | | `ActiveInstancesWithoutIncidentCount` | | ## ProcessDefinitionResultState The state of this process definition. `DRAINING` indicates the definition is being deleted but still has active process instances draining before it is removed. | Value | Description | | ---------- | ----------- | | `ACTIVE` | | | `DRAINING` | | | `DELETED` | | ## ProcessDefinitionSearchQuerySortRequestField The field to sort by. | Value | Description | | ---------------------- | ----------- | | `ProcessDefinitionKey` | | | `Name` | | | `ResourceName` | | | `Version` | | | `VersionTag` | | | `ProcessDefinitionId` | | | `TenantId` | | ## ProcessInstanceSearchQuerySortRequestField The field to sort by. | Value | Description | | ----------------------------- | ----------- | | `ProcessInstanceKey` | | | `ProcessDefinitionId` | | | `ProcessDefinitionName` | | | `ProcessDefinitionVersion` | | | `ProcessDefinitionVersionTag` | | | `ProcessDefinitionKey` | | | `ParentProcessInstanceKey` | | | `ParentElementInstanceKey` | | | `StartDate` | | | `EndDate` | | | `SuspendedDate` | | | `State` | | | `HasIncident` | | | `TenantId` | | | `BusinessId` | | ## ProcessInstanceStateEnum Process instance states | Value | Description | | ------------ | ----------- | | `ACTIVE` | | | `COMPLETED` | | | `SUSPENDED` | | | `TERMINATED` | | ## ResourceSearchQuerySortRequestField The field to sort by. | Value | Description | | --------------- | ----------- | | `ResourceKey` | | | `ResourceName` | | | `ResourceId` | | | `Version` | | | `VersionTag` | | | `DeploymentKey` | | | `TenantId` | | ## ResourceTypeEnum The type of resource to add/remove permissions to/from. | Value | Description | | -------------------------------- | ----------- | | `AUDITLOG` | | | `AUTHORIZATION` | | | `BACKUP` | | | `BATCH` | | | `CLUSTERVARIABLE` | | | `COMPONENT` | | | `DECISIONDEFINITION` | | | `DECISIONREQUIREMENTSDEFINITION` | | | `DOCUMENT` | | | `EXPORTER` | | | `EXPRESSION` | | | `GLOBALLISTENER` | | | `GROUP` | | | `MAPPINGRULE` | | | `MESSAGE` | | | `PROCESSDEFINITION` | | | `RESOURCE` | | | `ROLE` | | | `SECRET` | | | `SYSTEM` | | | `TENANT` | | | `USER` | | | `USERTASK` | | ## RestorePartitionStatusState The restore state of the partition. | Value | Description | | ----------- | ----------- | | `PENDING` | | | `RESTORING` | | | `RESTORED` | | ## RestoreStatusResponseStatus The overall status of the restore. | Value | Description | | ------------ | ----------- | | `INPROGRESS` | | | `COMPLETED` | | | `FAILED` | | | `CANCELLED` | | ## RoleClientSearchQuerySortRequestField The field to sort by. | Value | Description | | ---------- | ----------- | | `ClientId` | | ## RoleGroupSearchQuerySortRequestField The field to sort by. | Value | Description | | --------- | ----------- | | `GroupId` | | ## RoleSearchQuerySortRequestField The field to sort by. | Value | Description | | -------- | ----------- | | `Name` | | | `RoleId` | | ## RoleUserSearchQuerySortRequestField The field to sort by. | Value | Description | | ---------- | ----------- | | `Username` | | ## SecretErrorCode The typed reason a reference could not be resolved. - `NOT_FOUND`: no secret exists for the reference. - `ACCESS_DENIED`: the caller lacks `SECRET:REVEAL` on the reference. - `INVALID_REFERENCE`: the reference is malformed, or the configured store rejected it as an invalid secret identifier. - `UNREADABLE`: the configured store could not return a value for the reference, for example because it rejected the cluster's own store credentials or the stored value could not be read. Whether the secret exists is not implied. | Value | Description | | ------------------ | ----------- | | `NOTFOUND` | | | `ACCESSDENIED` | | | `INVALIDREFERENCE` | | | `UNREADABLE` | | ## SortOrderEnum The order in which to sort the related field. | Value | Description | | ------ | ----------- | | `ASC` | | | `DESC` | | ## StateCode The aggregated state of the backup, computed from the state of each partition. | Value | Description | | -------------- | ----------- | | `DOESNOTEXIST` | | | `INPROGRESS` | | | `COMPLETED` | | | `FAILED` | | | `INCOMPLETE` | | | `DELETED` | | ## TenantClientSearchQuerySortRequestField The field to sort by. | Value | Description | | ---------- | ----------- | | `ClientId` | | ## TenantFilterEnum The tenant filtering strategy for job activation. Determines whether to use tenant IDs provided in the request or tenant IDs assigned to the authenticated principal. | Value | Description | | ---------- | ----------- | | `PROVIDED` | | | `ASSIGNED` | | ## TenantGroupSearchQuerySortRequestField The field to sort by. | Value | Description | | --------- | ----------- | | `GroupId` | | ## TenantSearchQuerySortRequestField The field to sort by. `key` is deprecated and should not be used anymore. | Value | Description | | ---------- | ----------- | | `Key` | | | `Name` | | | `TenantId` | | ## TenantUserSearchQuerySortRequestField The field to sort by. | Value | Description | | ---------- | ----------- | | `Username` | | ## UserSearchQuerySortRequestField The field to sort by. | Value | Description | | ---------- | ----------- | | `Username` | | | `Name` | | | `Email` | | ## UserTaskSearchQuerySortRequestField The field to sort by. | Value | Description | | ---------------- | ----------- | | `CreationDate` | | | `CompletionDate` | | | `FollowUpDate` | | | `DueDate` | | | `Priority` | | | `Name` | | | `BusinessId` | | ## UserTaskStateEnum The state of the user task. Note: FAILED state is only for legacy job-worker-based tasks. | Value | Description | | ------------ | ----------- | | `CREATING` | | | `CREATED` | | | `ASSIGNING` | | | `UPDATING` | | | `COMPLETING` | | | `COMPLETED` | | | `CANCELING` | | | `CANCELED` | | | `FAILED` | | ## UserTaskVariableSearchQuerySortRequestField The field to sort by. | Value | Description | | -------------------- | ----------- | | `Value` | | | `Name` | | | `TenantId` | | | `VariableKey` | | | `ScopeKey` | | | `ProcessInstanceKey` | | ## VariableSearchQuerySortRequestField The field to sort by. | Value | Description | | -------------------- | ----------- | | `Value` | | | `Name` | | | `TenantId` | | | `VariableKey` | | | `ScopeKey` | | | `ProcessInstanceKey` | | ## WaitStateElementTypeEnum The BPMN element type of a waiting element instance. | Value | Description | | ------------------------------ | ----------- | | `ADHOCSUBPROCESS` | | | `ADHOCSUBPROCESSINNERINSTANCE` | | | `BOUNDARYEVENT` | | | `BUSINESSRULETASK` | | | `CALLACTIVITY` | | | `ENDEVENT` | | | `EVENTBASEDGATEWAY` | | | `EVENTSUBPROCESS` | | | `EXCLUSIVEGATEWAY` | | | `INCLUSIVEGATEWAY` | | | `INTERMEDIATECATCHEVENT` | | | `INTERMEDIATETHROWEVENT` | | | `MANUALTASK` | | | `MULTIINSTANCEBODY` | | | `PARALLELGATEWAY` | | | `PROCESS` | | | `RECEIVETASK` | | | `SCRIPTTASK` | | | `SENDTASK` | | | `SEQUENCEFLOW` | | | `SERVICETASK` | | | `STARTEVENT` | | | `SUBPROCESS` | | | `TASK` | | | `UNKNOWN` | | | `UNSPECIFIED` | | | `USERTASK` | | ## WaitStateTypeEnum The type of waiting state an element instance is in. | Value | Description | | ----------- | ----------- | | `JOB` | | | `MESSAGE` | | | `USERTASK` | | | `TIMER` | | | `SIGNAL` | | | `CONDITION` | | ## WebappComponent A Camunda webapp component name. | Value | Description | | ---------- | ----------- | | `Operate` | | | `Tasklist` | | | `Admin` | | --- ## C# SDK API Reference Auto-generated from the Camunda C# SDK source code. ## Sections - [CamundaClient](camunda-client.md) — Main client class with all API methods (3 types) - [Configuration](configuration.md) — SDK configuration, authentication, and options (16 types) - [Runtime](runtime.md) — Runtime infrastructure: job workers, backpressure, polling, errors (0 types) - [Models](models.md) — Request and response model classes (715 types) - [Enums](enums.md) — Enumeration types (110 types) - [Keys](keys.md) — Strongly-typed domain key types (28 types) --- ## Keys # Key Types Strongly-typed domain key types provide compile-time safety for entity identifiers. Each key wraps a string value and ensures type-safe API calls. ## Overview | Key Type | Description | | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `AgentDefinitionKey` | System-generated key for an agent definition. | | `AgentHistoryItemKey` | System-generated key for an agent history item. | | `AgentInstanceKey` | System-generated key for an agent instance. | | `AuditLogEntityKey` | System-generated entity key for an audit log entry. | | `AuditLogKey` | System-generated key for an audit log entry. | | `AuthorizationKey` | System-generated key for an authorization. | | `BatchOperationKey` | System-generated key for an batch operation. | | `ConditionalEvaluationKey` | System-generated key for a conditional evaluation. | | `DecisionDefinitionKey` | System-generated key for a decision definition. | | `DecisionEvaluationInstanceKey` | System-generated identifier for a decision evaluation instance. It is composed of the parent decision evaluation key and the 1-based index of the evaluated decision within that evaluation, joined by a hyphen (format: `-`). | | `DecisionEvaluationKey` | System-generated key for a decision evaluation. | | `DecisionInstanceKey` | System-generated key for a deployed decision instance. | | `DecisionRequirementsKey` | System-generated key for a deployed decision requirements definition. | | `DeploymentKey` | Key for a deployment. | | `ElementInstanceKey` | System-generated key for a element instance. | | `FormKey` | System-generated key for a deployed form. | | `IncidentKey` | System-generated key for a incident. | | `JobKey` | System-generated key for a job. | | `LongKey` | Zeebe Engine resource key (Java long serialized as string) | | `MessageKey` | System-generated key for an message. | | `MessageSubscriptionKey` | System-generated key for a message subscription. | | `ProcessDefinitionKey` | System-generated key for a deployed process definition. | | `ProcessInstanceKey` | System-generated key for a process instance. | | `ResourceKey` | The system-assigned key for this resource. | | `ScopeKey` | System-generated key for a scope. A scope can hold variables and represents either an element instance in a BPMN process or the process instance itself. | | `SignalKey` | System-generated key for an signal. | | `UserTaskKey` | System-generated key for a user task. | | `VariableKey` | System-generated key for a variable. | ## Common Methods All key types share these methods: | Method | Description | | ---------------------- | ------------------------------------------------ | | `AssumeExists(string)` | Creates a key from a known-valid string value. | | `IsValid(string)` | Validates whether a string is a valid key value. | | `Value` | Gets the underlying string value. | | `ToString()` | Returns the string representation. | ## Details ### AgentDefinitionKey System-generated key for an agent definition. ```csharp public readonly record struct AgentDefinitionKey : ICamundaKey, IEquatable ``` ### AgentHistoryItemKey System-generated key for an agent history item. ```csharp public readonly record struct AgentHistoryItemKey : ICamundaKey, IEquatable ``` ### AgentInstanceKey System-generated key for an agent instance. ```csharp public readonly record struct AgentInstanceKey : ICamundaKey, IEquatable ``` ### AuditLogEntityKey System-generated entity key for an audit log entry. ```csharp public readonly record struct AuditLogEntityKey : ICamundaKey, IEquatable ``` ### AuditLogKey System-generated key for an audit log entry. ```csharp public readonly record struct AuditLogKey : ICamundaKey, IEquatable ``` ### AuthorizationKey System-generated key for an authorization. ```csharp public readonly record struct AuthorizationKey : ICamundaKey, IEquatable ``` ### BatchOperationKey System-generated key for an batch operation. ```csharp public readonly record struct BatchOperationKey : ICamundaKey, IEquatable ``` ### ConditionalEvaluationKey System-generated key for a conditional evaluation. ```csharp public readonly record struct ConditionalEvaluationKey : ICamundaKey, IEquatable ``` ### DecisionDefinitionKey System-generated key for a decision definition. ```csharp public readonly record struct DecisionDefinitionKey : ICamundaKey, IEquatable ``` ### DecisionEvaluationInstanceKey System-generated identifier for a decision evaluation instance. It is composed of the parent decision evaluation key and the 1-based index of the evaluated decision within that evaluation, joined by a hyphen (format: `-`). ```csharp public readonly record struct DecisionEvaluationInstanceKey : ICamundaKey, IEquatable ``` ### DecisionEvaluationKey System-generated key for a decision evaluation. ```csharp public readonly record struct DecisionEvaluationKey : ICamundaKey, IEquatable ``` ### DecisionInstanceKey System-generated key for a deployed decision instance. ```csharp public readonly record struct DecisionInstanceKey : ICamundaKey, IEquatable ``` ### DecisionRequirementsKey System-generated key for a deployed decision requirements definition. ```csharp public readonly record struct DecisionRequirementsKey : ICamundaKey, IEquatable ``` ### DeploymentKey Key for a deployment. ```csharp public readonly record struct DeploymentKey : ICamundaKey, IEquatable ``` ### ElementInstanceKey System-generated key for a element instance. ```csharp public readonly record struct ElementInstanceKey : ICamundaKey, IEquatable ``` ### FormKey System-generated key for a deployed form. ```csharp public readonly record struct FormKey : ICamundaKey, IEquatable ``` ### IncidentKey System-generated key for a incident. ```csharp public readonly record struct IncidentKey : ICamundaKey, IEquatable ``` ### JobKey System-generated key for a job. ```csharp public readonly record struct JobKey : ICamundaKey, IEquatable ``` ### LongKey Zeebe Engine resource key (Java long serialized as string) ```csharp public readonly record struct LongKey : ICamundaKey, IEquatable ``` ### MessageKey System-generated key for an message. ```csharp public readonly record struct MessageKey : ICamundaKey, IEquatable ``` ### MessageSubscriptionKey System-generated key for a message subscription. ```csharp public readonly record struct MessageSubscriptionKey : ICamundaKey, IEquatable ``` ### ProcessDefinitionKey System-generated key for a deployed process definition. ```csharp public readonly record struct ProcessDefinitionKey : ICamundaKey, IEquatable ``` ### ProcessInstanceKey System-generated key for a process instance. ```csharp public readonly record struct ProcessInstanceKey : ICamundaKey, IEquatable ``` ### ResourceKey The system-assigned key for this resource. ```csharp public readonly record struct ResourceKey : ICamundaKey, IEquatable ``` ### ScopeKey System-generated key for a scope. A scope can hold variables and represents either an element instance in a BPMN process or the process instance itself. ```csharp public readonly record struct ScopeKey : ICamundaKey, IEquatable ``` ### SignalKey System-generated key for an signal. ```csharp public readonly record struct SignalKey : ICamundaKey, IEquatable ``` ### UserTaskKey System-generated key for a user task. ```csharp public readonly record struct UserTaskKey : ICamundaKey, IEquatable ``` ### VariableKey System-generated key for a variable. ```csharp public readonly record struct VariableKey : ICamundaKey, IEquatable ``` --- ## Models Request and response model classes (715 types). ## Quick Reference - [ActivatedJob](#activatedjob) — An activated job received from the Camunda broker, with typed variable access - [ActivatedJobResult](#activatedjobresult) — ActivatedJobResult - [AdHocSubProcessActivateActivitiesInstruction](#adhocsubprocessactivateactivitiesinstruction) — AdHocSubProcessActivateActivitiesInstruction - [AdHocSubProcessActivateActivityReference](#adhocsubprocessactivateactivityreference) — AdHocSubProcessActivateActivityReference - [AdvancedActorTypeFilter](#advancedactortypefilter) — Advanced AuditLogActorTypeEnum filter - [AdvancedAgentDefinitionKeyFilter](#advancedagentdefinitionkeyfilter) — Advanced AgentDefinitionKey filter - [AdvancedAgentDefinitionTypeFilter](#advancedagentdefinitiontypefilter) — Advanced AgentDefinitionTypeEnum filter - [AdvancedAgentHistoryItemKeyFilter](#advancedagenthistoryitemkeyfilter) — Advanced AgentHistoryItemKey filter - [AdvancedAgentInstanceHistoryCommitStatusFilter](#advancedagentinstancehistorycommitstatusfilter) — Advanced AgentInstanceHistoryCommitStatusEnum filter - [AdvancedAgentInstanceHistoryRoleFilter](#advancedagentinstancehistoryrolefilter) — Advanced AgentInstanceHistoryRoleEnum filter - [AdvancedAgentInstanceKeyFilter](#advancedagentinstancekeyfilter) — Advanced AgentInstanceKey filter - [AdvancedAgentInstanceStatusFilter](#advancedagentinstancestatusfilter) — Advanced AgentInstanceStatusEnum filter - [AdvancedAuditLogEntityKeyFilter](#advancedauditlogentitykeyfilter) — Advanced entityKey filter - [AdvancedAuditLogKeyFilter](#advancedauditlogkeyfilter) — Advanced AuditLogKey filter - [AdvancedBatchOperationItemStateFilter](#advancedbatchoperationitemstatefilter) — Advanced BatchOperationItemStateEnum filter - [AdvancedBatchOperationStateFilter](#advancedbatchoperationstatefilter) — Advanced BatchOperationStateEnum filter - [AdvancedBatchOperationTypeFilter](#advancedbatchoperationtypefilter) — Advanced BatchOperationTypeEnum filter - [AdvancedCategoryFilter](#advancedcategoryfilter) — Advanced AuditLogCategoryEnum filter - [AdvancedClusterVariableKindFilter](#advancedclustervariablekindfilter) — Advanced ClusterVariableKindEnum filter - [AdvancedClusterVariableScopeFilter](#advancedclustervariablescopefilter) — Advanced ClusterVariableScopeEnum filter - [AdvancedDateTimeFilter](#advanceddatetimefilter) — Advanced date-time filter - [AdvancedDecisionDefinitionKeyFilter](#advanceddecisiondefinitionkeyfilter) — Advanced DecisionDefinitionKey filter - [AdvancedDecisionEvaluationInstanceKeyFilter](#advanceddecisionevaluationinstancekeyfilter) — Advanced DecisionEvaluationInstanceKey filter - [AdvancedDecisionEvaluationKeyFilter](#advanceddecisionevaluationkeyfilter) — Advanced DecisionEvaluationKey filter - [AdvancedDecisionInstanceStateFilter](#advanceddecisioninstancestatefilter) — Advanced DecisionInstanceStateEnum filter - [AdvancedDecisionRequirementsKeyFilter](#advanceddecisionrequirementskeyfilter) — Advanced DecisionRequirementsKey filter - [AdvancedDeploymentKeyFilter](#advanceddeploymentkeyfilter) — Advanced DeploymentKey filter - [AdvancedElementIdFilter](#advancedelementidfilter) — Advanced ElementId filter - [AdvancedElementInstanceKeyFilter](#advancedelementinstancekeyfilter) — Advanced ElementInstanceKey filter - [AdvancedElementInstanceStateFilter](#advancedelementinstancestatefilter) — Advanced ElementInstanceStateEnum filter - [AdvancedEntityTypeFilter](#advancedentitytypefilter) — Advanced AuditLogEntityTypeEnum filter - [AdvancedFormKeyFilter](#advancedformkeyfilter) — Advanced FormKey filter - [AdvancedGlobalListenerSourceFilter](#advancedgloballistenersourcefilter) — Advanced global listener source filter - [AdvancedGlobalTaskListenerEventTypeFilter](#advancedglobaltasklistenereventtypefilter) — Advanced global listener event type filter - [AdvancedIncidentErrorTypeFilter](#advancedincidenterrortypefilter) — Advanced IncidentErrorTypeEnum filter - [AdvancedIncidentStateFilter](#advancedincidentstatefilter) — Advanced IncidentStateEnum filter - [AdvancedIntegerFilter](#advancedintegerfilter) — Advanced integer (int32) filter - [AdvancedJobKeyFilter](#advancedjobkeyfilter) — Advanced JobKey filter - [AdvancedJobKindFilter](#advancedjobkindfilter) — Advanced JobKindEnum filter - [AdvancedJobListenerEventTypeFilter](#advancedjoblistenereventtypefilter) — Advanced JobListenerEventTypeEnum filter - [AdvancedJobStateFilter](#advancedjobstatefilter) — Advanced JobStateEnum filter - [AdvancedMessageSubscriptionKeyFilter](#advancedmessagesubscriptionkeyfilter) — Advanced MessageSubscriptionKey filter - [AdvancedMessageSubscriptionStateFilter](#advancedmessagesubscriptionstatefilter) — Advanced MessageSubscriptionStateEnum filter - [AdvancedMessageSubscriptionTypeFilter](#advancedmessagesubscriptiontypefilter) — Advanced MessageSubscriptionTypeEnum filter - [AdvancedMetadataValueFilter](#advancedmetadatavaluefilter) — Advanced filter on a metadata value (string or number) - [AdvancedOperationTypeFilter](#advancedoperationtypefilter) — Advanced AuditLogOperationTypeEnum filter - [AdvancedProcessDefinitionIdFilter](#advancedprocessdefinitionidfilter) — Advanced ProcessDefinitionId filter - [AdvancedProcessDefinitionKeyFilter](#advancedprocessdefinitionkeyfilter) — Advanced ProcessDefinitionKey filter - [AdvancedProcessInstanceKeyFilter](#advancedprocessinstancekeyfilter) — Advanced ProcessInstanceKey filter - [AdvancedProcessInstanceStateFilter](#advancedprocessinstancestatefilter) — Advanced ProcessInstanceStateEnum filter - [AdvancedResourceKeyFilter](#advancedresourcekeyfilter) — Advanced ResourceKey filter - [AdvancedResultFilter](#advancedresultfilter) — Advanced AuditLogResultEnum filter - [AdvancedScopeKeyFilter](#advancedscopekeyfilter) — Advanced ScopeKey filter - [AdvancedStringFilter](#advancedstringfilter) — Advanced string filter - [AdvancedUserTaskStateFilter](#advancedusertaskstatefilter) — Advanced UserTaskStateEnum filter - [AdvancedVariableKeyFilter](#advancedvariablekeyfilter) — Advanced VariableKey filter - [AdvancedWaitStateElementTypeFilter](#advancedwaitstateelementtypefilter) — Advanced element type filter - [AdvancedWaitStateTypeFilter](#advancedwaitstatetypefilter) — Advanced wait state type filter - [AgentDefinitionFilter](#agentdefinitionfilter) — Agent definition search filter - [AgentDefinitionKeyExactMatch](#agentdefinitionkeyexactmatch) — Matches the value exactly - [AgentDefinitionKeyFilterProperty](#agentdefinitionkeyfilterproperty) — AgentDefinitionKey property with full advanced search capabilities - [AgentDefinitionResult](#agentdefinitionresult) — An agent definition, created at deploy time for the process element it belongs to - [AgentDefinitionSearchQuery](#agentdefinitionsearchquery) — Agent definition search request - [AgentDefinitionSearchQueryResult](#agentdefinitionsearchqueryresult) — Agent definition search response - [AgentDefinitionSearchQuerySortRequest](#agentdefinitionsearchquerysortrequest) — AgentDefinitionSearchQuerySortRequest - [AgentDefinitionTypeExactMatch](#agentdefinitiontypeexactmatch) — Matches the value exactly - [AgentDefinitionTypeFilterProperty](#agentdefinitiontypefilterproperty) — AgentDefinitionTypeEnum property with full advanced search capabilities - [AgentHistoryItemKeyExactMatch](#agenthistoryitemkeyexactmatch) — Matches the value exactly - [AgentHistoryItemKeyFilterProperty](#agenthistoryitemkeyfilterproperty) — AgentHistoryItemKey property with full advanced search capabilities - [AgentInstanceCreatedHistoryItem](#agentinstancecreatedhistoryitem) — The outcome of appending a single history item from an update request's history batch - [AgentInstanceCreationRequest](#agentinstancecreationrequest) — Request to create a new agent instance - [AgentInstanceCreationResult](#agentinstancecreationresult) — Response returned after successfully creating an agent instance - [AgentInstanceDefinitionResult](#agentinstancedefinitionresult) — The definition of an agent instance - [AgentInstanceDocumentContent](#agentinstancedocumentcontent) — A Camunda Document Store reference content block - [AgentInstanceFilter](#agentinstancefilter) — Agent instance search filter - [AgentInstanceHistoryCommitStatusExactMatch](#agentinstancehistorycommitstatusexactmatch) — Matches the value exactly - [AgentInstanceHistoryCommitStatusFilterProperty](#agentinstancehistorycommitstatusfilterproperty) — AgentInstanceHistoryCommitStatusEnum property with full advanced search capabilities - [AgentInstanceHistoryFilter](#agentinstancehistoryfilter) — Agent instance history item search filter - [AgentInstanceHistoryItem](#agentinstancehistoryitem) — A single history item to append to the agent instance's conversation history, submitted as part of the batch on an agent instance update request - [AgentInstanceHistoryItemMetrics](#agentinstancehistoryitemmetrics) — Per-call token and latency metrics for an ASSISTANT history item - [AgentInstanceHistoryItemMetricsRequest](#agentinstancehistoryitemmetricsrequest) — Per-call token and latency metrics for an ASSISTANT history item, as submitted on a create/update request - [AgentInstanceHistoryItemResult](#agentinstancehistoryitemresult) — A single conversation history item belonging to an agent instance - [AgentInstanceHistoryRoleExactMatch](#agentinstancehistoryroleexactmatch) — Matches the value exactly - [AgentInstanceHistoryRoleFilterProperty](#agentinstancehistoryrolefilterproperty) — AgentInstanceHistoryRoleEnum property with full advanced search capabilities - [AgentInstanceHistorySearchQuery](#agentinstancehistorysearchquery) — Agent instance history search request - [AgentInstanceHistorySearchQueryResult](#agentinstancehistorysearchqueryresult) — Agent instance history search response - [AgentInstanceHistorySearchQuerySortRequest](#agentinstancehistorysearchquerysortrequest) — AgentInstanceHistorySearchQuerySortRequest - [AgentInstanceKeyExactMatch](#agentinstancekeyexactmatch) — Matches the value exactly - [AgentInstanceKeyFilterProperty](#agentinstancekeyfilterproperty) — AgentInstanceKey property with full advanced search capabilities - [AgentInstanceLimits](#agentinstancelimits) — The configured limits for an agent instance, set once at creation - [AgentInstanceMessageContent](#agentinstancemessagecontent) — A single content block within a history item - [AgentInstanceMetrics](#agentinstancemetrics) — Aggregated metrics for an agent instance across all model calls - [AgentInstanceObjectContent](#agentinstanceobjectcontent) — An arbitrary structured content block - [AgentInstanceResult](#agentinstanceresult) — AgentInstanceResult - [AgentInstanceSearchQuery](#agentinstancesearchquery) — Agent instance search request - [AgentInstanceSearchQueryResult](#agentinstancesearchqueryresult) — Agent instance search response - [AgentInstanceSearchQuerySortRequest](#agentinstancesearchquerysortrequest) — AgentInstanceSearchQuerySortRequest - [AgentInstanceStatusExactMatch](#agentinstancestatusexactmatch) — Matches the value exactly - [AgentInstanceStatusFilterProperty](#agentinstancestatusfilterproperty) — AgentInstanceStatusEnum property with full advanced search capabilities - [AgentInstanceTextContent](#agentinstancetextcontent) — A plain-text content block - [AgentInstanceToolCall](#agentinstancetoolcall) — A tool call associated with a history item - [AgentInstanceUpdateRequest](#agentinstanceupdaterequest) — Request to update the mutable state of an agent instance - [AgentInstanceUpdateResult](#agentinstanceupdateresult) — Response returned after successfully updating an agent instance - [AgentTool](#agenttool) — A tool available to the agent - [AncestorScopeInstruction](#ancestorscopeinstruction) — Defines the ancestor scope for the created element instances - [AuditLogActorTypeExactMatch](#auditlogactortypeexactmatch) — Matches the value exactly - [AuditLogActorTypeFilterProperty](#auditlogactortypefilterproperty) — AuditLogActorTypeEnum property with full advanced search capabilities - [AuditLogEntityKeyExactMatch](#auditlogentitykeyexactmatch) — Matches the value exactly - [AuditLogEntityKeyFilterProperty](#auditlogentitykeyfilterproperty) — EntityKey property with full advanced search capabilities - [AuditLogFilter](#auditlogfilter) — Audit log filter request - [AuditLogKeyExactMatch](#auditlogkeyexactmatch) — Matches the value exactly - [AuditLogKeyFilterProperty](#auditlogkeyfilterproperty) — AuditLogKey property with full advanced search capabilities - [AuditLogResult](#auditlogresult) — Audit log item - [AuditLogResultExactMatch](#auditlogresultexactmatch) — Matches the value exactly - [AuditLogResultFilterProperty](#auditlogresultfilterproperty) — AuditLogResultEnum property with full advanced search capabilities - [AuditLogSearchQueryRequest](#auditlogsearchqueryrequest) — Audit log search request - [AuditLogSearchQueryResult](#auditlogsearchqueryresult) — Audit log search response - [AuditLogSearchQuerySortRequest](#auditlogsearchquerysortrequest) — AuditLogSearchQuerySortRequest - [AuthenticationConfigurationResponse](#authenticationconfigurationresponse) — Configuration for authentication and session management - [AuthorizationCreateResult](#authorizationcreateresult) — AuthorizationCreateResult - [AuthorizationFilter](#authorizationfilter) — Authorization search filter - [AuthorizationIdBasedRequest](#authorizationidbasedrequest) — AuthorizationIdBasedRequest - [AuthorizationPropertyBasedRequest](#authorizationpropertybasedrequest) — AuthorizationPropertyBasedRequest - [AuthorizationRequest](#authorizationrequest) — Defines an authorization request - [AuthorizationResult](#authorizationresult) — AuthorizationResult - [AuthorizationSearchQuery](#authorizationsearchquery) — AuthorizationSearchQuery - [AuthorizationSearchQuerySortRequest](#authorizationsearchquerysortrequest) — AuthorizationSearchQuerySortRequest - [AuthorizationSearchResult](#authorizationsearchresult) — AuthorizationSearchResult - [BackpressureState](#backpressurestate) - [BackupId](#backupid) — The id of the backup - [BackupIdPrefix](#backupidprefix) — A prefix of a backup id, followed by a single '*' as a wildcard, matching any backup id starting with the given prefix - [BackupInfo](#backupinfo) — Detailed status of a runtime backup - [BaseProcessInstanceFilterFields](#baseprocessinstancefilterfields) — Base process instance search filter - [BasicStringFilter](#basicstringfilter) — Basic advanced string filter - [BasicStringFilterProperty](#basicstringfilterproperty) — String property with basic advanced search capabilities - [BatchOperationCreatedResult](#batchoperationcreatedresult) — The created batch operation - [BatchOperationError](#batchoperationerror) — BatchOperationError - [BatchOperationFilter](#batchoperationfilter) — Batch operation filter request - [BatchOperationItemFilter](#batchoperationitemfilter) — Batch operation item filter request - [BatchOperationItemResponse](#batchoperationitemresponse) — BatchOperationItemResponse - [BatchOperationItemSearchQuery](#batchoperationitemsearchquery) — Batch operation item search request - [BatchOperationItemSearchQueryResult](#batchoperationitemsearchqueryresult) — BatchOperationItemSearchQueryResult - [BatchOperationItemSearchQuerySortRequest](#batchoperationitemsearchquerysortrequest) — BatchOperationItemSearchQuerySortRequest - [BatchOperationItemStateExactMatch](#batchoperationitemstateexactmatch) — Matches the value exactly - [BatchOperationItemStateFilterProperty](#batchoperationitemstatefilterproperty) — BatchOperationItemStateEnum property with full advanced search capabilities - [BatchOperationResponse](#batchoperationresponse) — BatchOperationResponse - [BatchOperationSearchQuery](#batchoperationsearchquery) — Batch operation search request - [BatchOperationSearchQueryResult](#batchoperationsearchqueryresult) — The batch operation search query result - [BatchOperationSearchQuerySortRequest](#batchoperationsearchquerysortrequest) — BatchOperationSearchQuerySortRequest - [BatchOperationStateExactMatch](#batchoperationstateexactmatch) — Matches the value exactly - [BatchOperationStateFilterProperty](#batchoperationstatefilterproperty) — BatchOperationStateEnum property with full advanced search capabilities - [BatchOperationTypeExactMatch](#batchoperationtypeexactmatch) — Matches the value exactly - [BatchOperationTypeFilterProperty](#batchoperationtypefilterproperty) — BatchOperationTypeEnum property with full advanced search capabilities - [BpmnErrorException](#bpmnerrorexception) — Throw from a job handler to trigger a BPMN error boundary event on the job's task - [BrokerInfo](#brokerinfo) — Provides information on a broker node - [BusinessId](#businessid) — An optional, user-defined string identifier that identifies the process instance within the scope of a process definition (scoped by tenant) - [CamundaAuthException](#camundaauthexception) — Authentication-specific exception - [CamundaConfigurationException](#camundaconfigurationexception) — Thrown when configuration hydration encounters validation errors - [CamundaKeyJsonConverterFactory](#camundakeyjsonconverterfactory) — JSON converter factory that handles any `ICamundaKey` struct - [CamundaKeyValidation](#camundakeyvalidation) — Validation helpers for domain key constraints - [CamundaLongKeyJsonConverterFactory](#camundalongkeyjsonconverterfactory) — JSON converter factory that handles any `ICamundaLongKey` struct - [CamundaRandomSource](#camundarandomsource) — The live `IRandomSource`: the platform's shared, unseeded generator - [CamundaSdkException](#camundasdkexception) — SDK error types mirroring the JS SDK's error structure - [CamundaTimeProvider](#camundatimeprovider) — A `TimeProvider` decorator whose `CamundaTimeProvider - [CamundaUserResult](#camundauserresult) — CamundaUserResult - [CancelProcessInstanceRequest](#cancelprocessinstancerequest) — CancelProcessInstanceRequest - [CancelSdkException](#cancelsdkexception) — Thrown when a cancellable operation is cancelled - [CategoryExactMatch](#categoryexactmatch) — Matches the value exactly - [CategoryFilterProperty](#categoryfilterproperty) — AuditLogCategoryEnum property with full advanced search capabilities - [Changeset](#changeset) — JSON object with changed task attribute values - [CheckpointId](#checkpointid) — The id of the checkpoint - [ClientId](#clientid) — The unique identifier of an OAuth client - [ClockPinRequest](#clockpinrequest) — ClockPinRequest - [CloudConfigurationResponse](#cloudconfigurationresponse) — Configuration for SaaS/cloud-specific settings - [ClusterBalanceResponse](#clusterbalanceresponse) — The cluster's current per-partition balance state, the running rebalance, and the last completed rebalance - [ClusterBrokerInfo](#clusterbrokerinfo) — Provides information on a broker node, independent of any physical tenant - [ClusterCompletedRebalance](#clustercompletedrebalance) — The last completed rebalance - [ClusterHistoryBackupInfo](#clusterhistorybackupinfo) — A history backup id and what each physical tenant reports for it - [ClusterHistoryBackupTakeResult](#clusterhistorybackuptakeresult) — The snapshots scheduled on a single physical tenant - [ClusterHistoryBackupTenantInfo](#clusterhistorybackuptenantinfo) — What a single physical tenant reports for a history backup id - [ClusterModeChangeOperation](#clustermodechangeoperation) — A single operation that is part of a cluster mode change - [ClusterModeChangePlannedChange](#clustermodechangeplannedchange) — The operations of a cluster mode change that apply to one physical tenant - [ClusterModeChangeResponse](#clustermodechangeresponse) — The planned changes resulting from a cluster mode transition request - [ClusterRebalance](#clusterrebalance) — The fields common to a running and a completed rebalance - [ClusterRebalanceOperationPartition](#clusterrebalanceoperationpartition) — One partition's plan, progress, and outcome within a rebalance - [ClusterRebalancePartition](#clusterrebalancepartition) — One partition's leadership/balance status - its current leader, its desired leader, and whether a rebalance is currently moving it - [ClusterRebalanceRequest](#clusterrebalancerequest) — The settings to run a given rebalance with - [ClusterRestoreAwaitModeChangeOperation](#clusterrestoreawaitmodechangeoperation) — The operation that awaits the transition of a broker to a mode - [ClusterRestoreBrokerOperation](#clusterrestorebrokeroperation) — A restore operation that applies to a broker as a whole, such as the one that updates its incarnation number - [ClusterRestoreModeChangeOperation](#clusterrestoremodechangeoperation) — The operation that transitions a broker to a mode once its partitions are restored - [ClusterRestoreOperation](#clusterrestoreoperation) — A single operation that is part of a restore - [ClusterRestorePartitionOperation](#clusterrestorepartitionoperation) — A restore operation that targets a single partition without restoring it, such as the one that prepares the partition for its restore - [ClusterRestorePartitionRestoreOperation](#clusterrestorepartitionrestoreoperation) — The operation that restores a single partition from the backups resolved for it - [ClusterRestorePlannedChange](#clusterrestoreplannedchange) — The operations of a restore that apply to one physical tenant - [ClusterRestoreRequest](#clusterrestorerequest) — Describes a restore request issued by a cluster admin - [ClusterRestoreResponse](#clusterrestoreresponse) — The planned changes resulting from a restore request - [ClusterRunningRebalance](#clusterrunningrebalance) — The rebalance currently running - [ClusterRuntimeBackupInfo](#clusterruntimebackupinfo) — A runtime backup id, what each physical tenant reports for it, and the state aggregated over every targeted tenant — folded from the per-tenant states by the same rules a per-tenant state is folded from its partitions - [ClusterRuntimeBackupState](#clusterruntimebackupstate) — The checkpoint and backup state of each physical tenant - [ClusterRuntimeBackupTakeResult](#clusterruntimebackuptakeresult) — Whether one physical tenant's runtime backup was triggered, and under which id it can be monitored and deleted - [ClusterRuntimeBackupTenantInfo](#clusterruntimebackuptenantinfo) — What a single physical tenant reports for a runtime backup id - [ClusterRuntimeBackupTenantState](#clusterruntimebackuptenantstate) — The checkpoint and backup state of one physical tenant - [ClusterStatusResponse](#clusterstatusresponse) — The aggregated status of the whole cluster - [ClusterTakeHistoryBackupResponse](#clustertakehistorybackupresponse) — The snapshots scheduled on every targeted physical tenant - [ClusterTakeRuntimeBackupResponse](#clustertakeruntimebackupresponse) — The outcome of triggering a runtime backup on every targeted physical tenant - [ClusterTopologyResponse](#clustertopologyresponse) — The topology of the whole cluster, aggregated over all physical tenants - [ClusterUpgradeStatusResponse](#clusterupgradestatusresponse) — The upgrade-readiness status of the whole cluster - [ClusterVariableKindExactMatch](#clustervariablekindexactmatch) — Matches the value exactly - [ClusterVariableKindFilterProperty](#clustervariablekindfilterproperty) — ClusterVariableKindEnum property with full advanced search capabilities - [ClusterVariableName](#clustervariablename) — The name of a cluster variable - [ClusterVariableResult](#clustervariableresult) — ClusterVariableResult - [ClusterVariableResultBase](#clustervariableresultbase) — Cluster variable response item - [ClusterVariableScopeExactMatch](#clustervariablescopeexactmatch) — Matches the value exactly - [ClusterVariableScopeFilterProperty](#clustervariablescopefilterproperty) — ClusterVariableScopeEnum property with full advanced search capabilities - [ClusterVariableSearchQueryFilterRequest](#clustervariablesearchqueryfilterrequest) — Cluster variable filter request - [ClusterVariableSearchQueryRequest](#clustervariablesearchqueryrequest) — Cluster variable search query request - [ClusterVariableSearchQueryResult](#clustervariablesearchqueryresult) — Cluster variable search query response - [ClusterVariableSearchQuerySortRequest](#clustervariablesearchquerysortrequest) — ClusterVariableSearchQuerySortRequest - [ClusterVariableSearchResult](#clustervariablesearchresult) — Cluster variable search response item - [ComponentsConfigurationResponse](#componentsconfigurationresponse) — Configuration for active Camunda components in the deployment - [ConditionWaitStateDetails](#conditionwaitstatedetails) — ConditionWaitStateDetails - [ConditionalEvaluationInstruction](#conditionalevaluationinstruction) — ConditionalEvaluationInstruction - [ConsistencyOptions](#consistencyoptions) — Options for eventual consistency polling behavior - [CorrelatedMessageSubscriptionFilter](#correlatedmessagesubscriptionfilter) — Correlated message subscriptions search filter - [CorrelatedMessageSubscriptionResult](#correlatedmessagesubscriptionresult) — CorrelatedMessageSubscriptionResult - [CorrelatedMessageSubscriptionSearchQuery](#correlatedmessagesubscriptionsearchquery) — CorrelatedMessageSubscriptionSearchQuery - [CorrelatedMessageSubscriptionSearchQueryResult](#correlatedmessagesubscriptionsearchqueryresult) — CorrelatedMessageSubscriptionSearchQueryResult - [CorrelatedMessageSubscriptionSearchQuerySortRequest](#correlatedmessagesubscriptionsearchquerysortrequest) — CorrelatedMessageSubscriptionSearchQuerySortRequest - [CreateClusterVariableRequest](#createclustervariablerequest) — CreateClusterVariableRequest - [CreateGlobalTaskListenerRequest](#createglobaltasklistenerrequest) — CreateGlobalTaskListenerRequest - [CreateProcessInstanceResult](#createprocessinstanceresult) — CreateProcessInstanceResult - [CursorBackwardPagination](#cursorbackwardpagination) — CursorBackwardPagination - [CursorForwardPagination](#cursorforwardpagination) — CursorForwardPagination - [DateTimeFilterProperty](#datetimefilterproperty) — Date-time property with full advanced search capabilities - [DecisionDefinitionFilter](#decisiondefinitionfilter) — Decision definition search filter - [DecisionDefinitionId](#decisiondefinitionid) — Id of a decision definition, from the model - [DecisionDefinitionKeyExactMatch](#decisiondefinitionkeyexactmatch) — Matches the value exactly - [DecisionDefinitionKeyFilterProperty](#decisiondefinitionkeyfilterproperty) — DecisionDefinitionKey property with full advanced search capabilities - [DecisionDefinitionResult](#decisiondefinitionresult) — DecisionDefinitionResult - [DecisionDefinitionSearchQuery](#decisiondefinitionsearchquery) — DecisionDefinitionSearchQuery - [DecisionDefinitionSearchQueryResult](#decisiondefinitionsearchqueryresult) — DecisionDefinitionSearchQueryResult - [DecisionDefinitionSearchQuerySortRequest](#decisiondefinitionsearchquerysortrequest) — DecisionDefinitionSearchQuerySortRequest - [DecisionEvaluationById](#decisionevaluationbyid) — DecisionEvaluationById - [DecisionEvaluationByKey](#decisionevaluationbykey) — DecisionEvaluationByKey - [DecisionEvaluationInstanceKeyExactMatch](#decisionevaluationinstancekeyexactmatch) — Matches the value exactly - [DecisionEvaluationInstanceKeyFilterProperty](#decisionevaluationinstancekeyfilterproperty) — DecisionEvaluationInstanceKey property with full advanced search capabilities - [DecisionEvaluationInstruction](#decisionevaluationinstruction) — DecisionEvaluationInstruction - [DecisionEvaluationKeyExactMatch](#decisionevaluationkeyexactmatch) — Matches the value exactly - [DecisionEvaluationKeyFilterProperty](#decisionevaluationkeyfilterproperty) — DecisionEvaluationKey property with full advanced search capabilities - [DecisionInstanceDeletionBatchOperationRequest](#decisioninstancedeletionbatchoperationrequest) — The decision instance filter that defines which decision instances should be deleted - [DecisionInstanceFilter](#decisioninstancefilter) — Decision instance search filter - [DecisionInstanceGetQueryResult](#decisioninstancegetqueryresult) — DecisionInstanceGetQueryResult - [DecisionInstanceResult](#decisioninstanceresult) — DecisionInstanceResult - [DecisionInstanceSearchQuery](#decisioninstancesearchquery) — DecisionInstanceSearchQuery - [DecisionInstanceSearchQueryResult](#decisioninstancesearchqueryresult) — DecisionInstanceSearchQueryResult - [DecisionInstanceSearchQuerySortRequest](#decisioninstancesearchquerysortrequest) — DecisionInstanceSearchQuerySortRequest - [DecisionInstanceStateExactMatch](#decisioninstancestateexactmatch) — Matches the value exactly - [DecisionInstanceStateFilterProperty](#decisioninstancestatefilterproperty) — DecisionInstanceStateEnum property with full advanced search capabilities - [DecisionRequirementsFilter](#decisionrequirementsfilter) — Decision requirements search filter - [DecisionRequirementsKeyExactMatch](#decisionrequirementskeyexactmatch) — Matches the value exactly - [DecisionRequirementsKeyFilterProperty](#decisionrequirementskeyfilterproperty) — DecisionRequirementsKey property with full advanced search capabilities - [DecisionRequirementsResult](#decisionrequirementsresult) — DecisionRequirementsResult - [DecisionRequirementsSearchQuery](#decisionrequirementssearchquery) — DecisionRequirementsSearchQuery - [DecisionRequirementsSearchQueryResult](#decisionrequirementssearchqueryresult) — DecisionRequirementsSearchQueryResult - [DecisionRequirementsSearchQuerySortRequest](#decisionrequirementssearchquerysortrequest) — DecisionRequirementsSearchQuerySortRequest - [DeleteDecisionInstanceRequest](#deletedecisioninstancerequest) — DeleteDecisionInstanceRequest - [DeleteProcessInstanceRequest](#deleteprocessinstancerequest) — DeleteProcessInstanceRequest - [DeleteResourceRequest](#deleteresourcerequest) — DeleteResourceRequest - [DeleteResourceResponse](#deleteresourceresponse) — DeleteResourceResponse - [DeploymentConfigurationResponse](#deploymentconfigurationresponse) — Configuration for deployment characteristics - [DeploymentDecisionRequirementsResult](#deploymentdecisionrequirementsresult) — Deployed decision requirements - [DeploymentDecisionResult](#deploymentdecisionresult) — A deployed decision - [DeploymentFormResult](#deploymentformresult) — A deployed form - [DeploymentKeyExactMatch](#deploymentkeyexactmatch) — Matches the value exactly - [DeploymentKeyFilterProperty](#deploymentkeyfilterproperty) — DeploymentKey property with full advanced search capabilities - [DeploymentMetadataResult](#deploymentmetadataresult) — DeploymentMetadataResult - [DeploymentProcessResult](#deploymentprocessresult) — A deployed process - [DeploymentResourceResult](#deploymentresourceresult) — A deployed Resource - [DeploymentResult](#deploymentresult) — DeploymentResult - [DirectAncestorKeyInstruction](#directancestorkeyinstruction) — Provides a concrete key to use as ancestor scope for the created element instance - [DocumentCreationBatchResponse](#documentcreationbatchresponse) — DocumentCreationBatchResponse - [DocumentCreationFailureDetail](#documentcreationfailuredetail) — DocumentCreationFailureDetail - [DocumentId](#documentid) — Document Id that uniquely identifies a document - [DocumentLink](#documentlink) — DocumentLink - [DocumentLinkRequest](#documentlinkrequest) — DocumentLinkRequest - [DocumentMetadata](#documentmetadata) — Information about the document - [DocumentMetadataResponse](#documentmetadataresponse) — Information about the document that is returned in responses - [DocumentReference](#documentreference) — DocumentReference - [ElementId](#elementid) — The model-defined id of an element - [ElementIdExactMatch](#elementidexactmatch) — Matches the value exactly - [ElementIdFilterProperty](#elementidfilterproperty) — ElementId property with full advanced search capabilities - [ElementInstanceFilter](#elementinstancefilter) — Element instance search filter - [ElementInstanceFilterFields](#elementinstancefilterfields) — Element instance filter fields - [ElementInstanceKeyExactMatch](#elementinstancekeyexactmatch) — Matches the value exactly - [ElementInstanceKeyFilterProperty](#elementinstancekeyfilterproperty) — ElementInstanceKey property with full advanced search capabilities - [ElementInstanceResult](#elementinstanceresult) — ElementInstanceResult - [ElementInstanceSearchQuery](#elementinstancesearchquery) — Element instance search request - [ElementInstanceSearchQueryResult](#elementinstancesearchqueryresult) — ElementInstanceSearchQueryResult - [ElementInstanceSearchQuerySortRequest](#elementinstancesearchquerysortrequest) — ElementInstanceSearchQuerySortRequest - [ElementInstanceStateExactMatch](#elementinstancestateexactmatch) — Matches the value exactly - [ElementInstanceStateFilterProperty](#elementinstancestatefilterproperty) — ElementInstanceStateEnum property with full advanced search capabilities - [ElementInstanceWaitStateFilter](#elementinstancewaitstatefilter) — Filters for the element instance inspection - [ElementInstanceWaitStateQuery](#elementinstancewaitstatequery) — Element instance inspection request - [ElementInstanceWaitStateQueryResult](#elementinstancewaitstatequeryresult) — ElementInstanceWaitStateQueryResult - [ElementInstanceWaitStateQuerySortRequest](#elementinstancewaitstatequerysortrequest) — ElementInstanceWaitStateQuerySortRequest - [ElementInstanceWaitStateResult](#elementinstancewaitstateresult) — An element instance waiting state - [EndCursor](#endcursor) — The end cursor in a search query result set - [EngineTimeProvider](#enginetimeprovider) — A `TimeProvider` bound to the engine's own clock, so client cadence and engine time advance together - [EntityTypeExactMatch](#entitytypeexactmatch) — Matches the value exactly - [EntityTypeFilterProperty](#entitytypefilterproperty) — AuditLogEntityTypeEnum property with full advanced search capabilities - [EvaluateConditionalResult](#evaluateconditionalresult) — EvaluateConditionalResult - [EvaluateDecisionResult](#evaluatedecisionresult) — EvaluateDecisionResult - [EvaluatedDecisionInputItem](#evaluateddecisioninputitem) — A decision input that was evaluated within this decision evaluation - [EvaluatedDecisionOutputItem](#evaluateddecisionoutputitem) — The evaluated decision outputs - [EvaluatedDecisionResult](#evaluateddecisionresult) — A decision that was evaluated - [EventualConsistencyTimeoutException](#eventualconsistencytimeoutexception) — Thrown when an eventually consistent endpoint times out waiting for data - [ExportingStatusResponse](#exportingstatusresponse) — Response body for the exporting status of a physical tenant - [ExpressionEvaluationRequest](#expressionevaluationrequest) — ExpressionEvaluationRequest - [ExpressionEvaluationResult](#expressionevaluationresult) — ExpressionEvaluationResult - [ExpressionEvaluationWarningItem](#expressionevaluationwarningitem) — ExpressionEvaluationWarningItem - [ExpressionSecretReferenceItem](#expressionsecretreferenceitem) — ExpressionSecretReferenceItem - [ExtendedDeploymentResponse](#extendeddeploymentresponse) — Extended deployment result with typed convenience properties for direct access to deployed artifacts by category (processes, decisions, forms, etc - [FormId](#formid) — The user-defined id for the form - [FormKeyExactMatch](#formkeyexactmatch) — Matches the value exactly - [FormKeyFilterProperty](#formkeyfilterproperty) — FormKey property with full advanced search capabilities - [FormResult](#formresult) — FormResult - [GlobalJobStatisticsQueryResult](#globaljobstatisticsqueryresult) — Global job statistics query result - [GlobalListenerBase](#globallistenerbase) — GlobalListenerBase - [GlobalListenerId](#globallistenerid) — The user-defined id for the global listener - [GlobalListenerSourceExactMatch](#globallistenersourceexactmatch) — Matches the value exactly - [GlobalListenerSourceFilterProperty](#globallistenersourcefilterproperty) — Global listener source property with full advanced search capabilities - [GlobalTaskListenerBase](#globaltasklistenerbase) — GlobalTaskListenerBase - [GlobalTaskListenerEventTypeExactMatch](#globaltasklistenereventtypeexactmatch) — Matches the value exactly - [GlobalTaskListenerEventTypeFilterProperty](#globaltasklistenereventtypefilterproperty) — Global listener event type property with full advanced search capabilities - [GlobalTaskListenerResult](#globaltasklistenerresult) — GlobalTaskListenerResult - [GlobalTaskListenerSearchQueryFilterRequest](#globaltasklistenersearchqueryfilterrequest) — Global listener filter request - [GlobalTaskListenerSearchQueryRequest](#globaltasklistenersearchqueryrequest) — Global listener search query request - [GlobalTaskListenerSearchQueryResult](#globaltasklistenersearchqueryresult) — Global listener search query response - [GlobalTaskListenerSearchQuerySortRequest](#globaltasklistenersearchquerysortrequest) — GlobalTaskListenerSearchQuerySortRequest - [GroupClientResult](#groupclientresult) — GroupClientResult - [GroupClientSearchQueryRequest](#groupclientsearchqueryrequest) — GroupClientSearchQueryRequest - [GroupClientSearchQuerySortRequest](#groupclientsearchquerysortrequest) — GroupClientSearchQuerySortRequest - [GroupClientSearchResult](#groupclientsearchresult) — GroupClientSearchResult - [GroupCreateRequest](#groupcreaterequest) — GroupCreateRequest - [GroupCreateResult](#groupcreateresult) — GroupCreateResult - [GroupFilter](#groupfilter) — Group filter request - [GroupFilterFields](#groupfilterfields) — Group filter request - [GroupId](#groupid) — The unique identifier of a group - [GroupMappingRuleSearchResult](#groupmappingrulesearchresult) — GroupMappingRuleSearchResult - [GroupResult](#groupresult) — Group search response item - [GroupRoleSearchResult](#grouprolesearchresult) — GroupRoleSearchResult - [GroupSearchQueryRequest](#groupsearchqueryrequest) — Group search request - [GroupSearchQueryResult](#groupsearchqueryresult) — Group search response - [GroupSearchQuerySortRequest](#groupsearchquerysortrequest) — GroupSearchQuerySortRequest - [GroupUpdateRequest](#groupupdaterequest) — GroupUpdateRequest - [GroupUpdateResult](#groupupdateresult) — GroupUpdateResult - [GroupUserResult](#groupuserresult) — GroupUserResult - [GroupUserSearchQueryRequest](#groupusersearchqueryrequest) — GroupUserSearchQueryRequest - [GroupUserSearchQuerySortRequest](#groupusersearchquerysortrequest) — GroupUserSearchQuerySortRequest - [GroupUserSearchResult](#groupusersearchresult) — GroupUserSearchResult - [HistoryBackupInfo](#historybackupinfo) — Detailed status of a history backup - [HistoryBackupSnapshotInfo](#historybackupsnapshotinfo) — Detailed info of a single snapshot making up a history backup - [HistoryItemId](#historyitemid) — The client-supplied identifier this item was created with - [HttpSdkException](#httpsdkexception) — HTTP-specific SDK error with RFC 7807 Problem Details - [ICamundaKey](#icamundakey) — Marker interface for all Camunda domain key types - [ICamundaLongKey](#icamundalongkey) — Marker interface for Camunda domain types backed by a long (int64) value - [IEngineClockTarget](#iengineclocktarget) — The engine clock operations `EngineTimeProvider` drives - [IRandomSource](#irandomsource) — Source of the randomness behind SDK runtime jitter: retry backoff, OAuth retry backoff, and worker startup staggering - [ITenantIdSettable](#itenantidsettable) — Implemented by request body types that have an optional tenantId property - [ITenantIdsSettable](#itenantidssettable) — Implemented by request body types that have an optional `tenantIds` array property (e - [IncidentErrorTypeExactMatch](#incidenterrortypeexactmatch) — Matches the value exactly - [IncidentErrorTypeFilterProperty](#incidenterrortypefilterproperty) — IncidentErrorTypeEnum with full advanced search capabilities - [IncidentFilter](#incidentfilter) — Incident search filter - [IncidentProcessInstanceStatisticsByDefinitionFilter](#incidentprocessinstancestatisticsbydefinitionfilter) — Filter for the incident process instance statistics by definition query - [IncidentProcessInstanceStatisticsByDefinitionQuery](#incidentprocessinstancestatisticsbydefinitionquery) — IncidentProcessInstanceStatisticsByDefinitionQuery - [IncidentProcessInstanceStatisticsByDefinitionQueryResult](#incidentprocessinstancestatisticsbydefinitionqueryresult) — IncidentProcessInstanceStatisticsByDefinitionQueryResult - [IncidentProcessInstanceStatisticsByDefinitionQuerySortRequest](#incidentprocessinstancestatisticsbydefinitionquerysortrequest) — IncidentProcessInstanceStatisticsByDefinitionQuerySortRequest - [IncidentProcessInstanceStatisticsByDefinitionResult](#incidentprocessinstancestatisticsbydefinitionresult) — IncidentProcessInstanceStatisticsByDefinitionResult - [IncidentProcessInstanceStatisticsByErrorQuery](#incidentprocessinstancestatisticsbyerrorquery) — IncidentProcessInstanceStatisticsByErrorQuery - [IncidentProcessInstanceStatisticsByErrorQueryResult](#incidentprocessinstancestatisticsbyerrorqueryresult) — IncidentProcessInstanceStatisticsByErrorQueryResult - [IncidentProcessInstanceStatisticsByErrorQuerySortRequest](#incidentprocessinstancestatisticsbyerrorquerysortrequest) — IncidentProcessInstanceStatisticsByErrorQuerySortRequest - [IncidentProcessInstanceStatisticsByErrorResult](#incidentprocessinstancestatisticsbyerrorresult) — IncidentProcessInstanceStatisticsByErrorResult - [IncidentResolutionRequest](#incidentresolutionrequest) — IncidentResolutionRequest - [IncidentResult](#incidentresult) — IncidentResult - [IncidentSearchQuery](#incidentsearchquery) — IncidentSearchQuery - [IncidentSearchQueryResult](#incidentsearchqueryresult) — IncidentSearchQueryResult - [IncidentSearchQuerySortRequest](#incidentsearchquerysortrequest) — IncidentSearchQuerySortRequest - [IncidentStateExactMatch](#incidentstateexactmatch) — Matches the value exactly - [IncidentStateFilterProperty](#incidentstatefilterproperty) — IncidentStateEnum with full advanced search capabilities - [InferredAncestorKeyInstruction](#inferredancestorkeyinstruction) — Instructs the engine to derive the ancestor scope key from the source element's hierarchy - [IntegerFilterProperty](#integerfilterproperty) — Integer property with advanced search capabilities - [JobActivationRequest](#jobactivationrequest) — JobActivationRequest - [JobActivationResult](#jobactivationresult) — The list of activated jobs - [JobBatchUpdateRequest](#jobbatchupdaterequest) — The filter and changeset for a batch job update operation - [JobChangeset](#jobchangeset) — JSON object with changed job attribute values - [JobCompletionRequest](#jobcompletionrequest) — JobCompletionRequest - [JobErrorRequest](#joberrorrequest) — JobErrorRequest - [JobErrorStatisticsFilter](#joberrorstatisticsfilter) — Job error statistics search filter - [JobErrorStatisticsItem](#joberrorstatisticsitem) — Aggregated error metrics for a single error type and message combination - [JobErrorStatisticsQuery](#joberrorstatisticsquery) — Job error statistics query - [JobErrorStatisticsQueryResult](#joberrorstatisticsqueryresult) — Job error statistics query result - [JobFailRequest](#jobfailrequest) — JobFailRequest - [JobFailureException](#jobfailureexception) — Throw from a job handler to explicitly fail a job with custom retry settings - [JobFilter](#jobfilter) — Job search filter - [JobHandler](#jobhandler) — Delegate for job handler functions - [JobKeyExactMatch](#jobkeyexactmatch) — Matches the value exactly - [JobKeyFilterProperty](#jobkeyfilterproperty) — JobKey property with full advanced search capabilities - [JobKindExactMatch](#jobkindexactmatch) — Matches the value exactly - [JobKindFilterProperty](#jobkindfilterproperty) — JobKindEnum property with full advanced search capabilities - [JobLeaseToken](#jobleasetoken) — An opaque, engine-minted fencing token identifying a single activation of a job - [JobListenerEventTypeExactMatch](#joblistenereventtypeexactmatch) — Matches the value exactly - [JobListenerEventTypeFilterProperty](#joblistenereventtypefilterproperty) — JobListenerEventTypeEnum property with full advanced search capabilities - [JobMetricsConfigurationResponse](#jobmetricsconfigurationresponse) — Configuration for job metrics collection and export - [JobResult](#jobresult) — The result of the completed job as determined by the worker - [JobResultActivateElement](#jobresultactivateelement) — Instruction to activate a single BPMN element within an ad‑hoc sub‑process, optionally providing variables scoped to that element - [JobResultAdHocSubProcess](#jobresultadhocsubprocess) — Job result details for an ad‑hoc sub‑process, including elements to activate and flags indicating completion or cancellation behavior - [JobResultCorrections](#jobresultcorrections) — JSON object with attributes that were corrected by the worker - [JobResultUserTask](#jobresultusertask) — Job result details for a user task completion, optionally including a denial reason and corrected task properties - [JobSearchQuery](#jobsearchquery) — Job search request - [JobSearchQueryResult](#jobsearchqueryresult) — Job search response - [JobSearchQuerySortRequest](#jobsearchquerysortrequest) — JobSearchQuerySortRequest - [JobSearchResult](#jobsearchresult) — JobSearchResult - [JobStateExactMatch](#jobstateexactmatch) — Matches the value exactly - [JobStateFilterProperty](#jobstatefilterproperty) — JobStateEnum property with full advanced search capabilities - [JobTimeSeriesStatisticsFilter](#jobtimeseriesstatisticsfilter) — Job time-series statistics search filter - [JobTimeSeriesStatisticsItem](#jobtimeseriesstatisticsitem) — Aggregated job metrics for a single time bucket - [JobTimeSeriesStatisticsQuery](#jobtimeseriesstatisticsquery) — Job time-series statistics query - [JobTimeSeriesStatisticsQueryResult](#jobtimeseriesstatisticsqueryresult) — Job time-series statistics query result - [JobTypeStatisticsFilter](#jobtypestatisticsfilter) — Job type statistics search filter - [JobTypeStatisticsItem](#jobtypestatisticsitem) — Statistics for a single job type - [JobTypeStatisticsQuery](#jobtypestatisticsquery) — Job type statistics query - [JobTypeStatisticsQueryResult](#jobtypestatisticsqueryresult) — Job type statistics query result - [JobUpdateRequest](#jobupdaterequest) — JobUpdateRequest - [JobWaitStateDetails](#jobwaitstatedetails) — JobWaitStateDetails - [JobWorker](#jobworker) — A long-running worker that polls the Camunda broker for jobs of a specific type, dispatches them to a handler, and auto-completes or auto-fails based on the outcome - [JobWorkerStatisticsFilter](#jobworkerstatisticsfilter) — Job worker statistics search filter - [JobWorkerStatisticsItem](#jobworkerstatisticsitem) — Statistics for a single worker within a job type - [JobWorkerStatisticsQuery](#jobworkerstatisticsquery) — Job worker statistics query - [JobWorkerStatisticsQueryResult](#jobworkerstatisticsqueryresult) — Job worker statistics query result - [LeaseNotHonoredException](#leasenothonoredexception) — Thrown when a worker activated jobs with a lease but the server returned a job whose lease token is missing or invalid - [LicenseResponse](#licenseresponse) — The response of a license request - [LikeFilter](#likefilter) — Checks if the property matches the provided like value - [LimitPagination](#limitpagination) — LimitPagination - [LoopIterationId](#loopiterationid) — A client-provided sequential integer identifying a loop iteration: one pass through an AI agent's loop, during which the model reasons, selects tools, evaluates the result, and decides whether to continue - [MappingRuleCreateRequest](#mappingrulecreaterequest) — MappingRuleCreateRequest - [MappingRuleCreateResult](#mappingrulecreateresult) — MappingRuleCreateResult - [MappingRuleCreateUpdateRequest](#mappingrulecreateupdaterequest) — MappingRuleCreateUpdateRequest - [MappingRuleCreateUpdateResult](#mappingrulecreateupdateresult) — MappingRuleCreateUpdateResult - [MappingRuleFilter](#mappingrulefilter) — Mapping rule search filter - [MappingRuleFilterFields](#mappingrulefilterfields) — Mapping rule search filter fields - [MappingRuleId](#mappingruleid) — The unique identifier of a mapping rule - [MappingRuleResult](#mappingruleresult) — MappingRuleResult - [MappingRuleSearchQueryRequest](#mappingrulesearchqueryrequest) — MappingRuleSearchQueryRequest - [MappingRuleSearchQueryResult](#mappingrulesearchqueryresult) — MappingRuleSearchQueryResult - [MappingRuleSearchQuerySortRequest](#mappingrulesearchquerysortrequest) — MappingRuleSearchQuerySortRequest - [MappingRuleUpdateRequest](#mappingruleupdaterequest) — MappingRuleUpdateRequest - [MappingRuleUpdateResult](#mappingruleupdateresult) — MappingRuleUpdateResult - [MatchedDecisionRuleItem](#matcheddecisionruleitem) — A decision rule that matched within this decision evaluation - [MessageCorrelationRequest](#messagecorrelationrequest) — MessageCorrelationRequest - [MessageCorrelationResult](#messagecorrelationresult) — The message key of the correlated message, as well as the first process instance key it correlated with - [MessagePublicationRequest](#messagepublicationrequest) — MessagePublicationRequest - [MessagePublicationResult](#messagepublicationresult) — The message key of the published message - [MessageSubscriptionFilter](#messagesubscriptionfilter) — Message subscription search filter - [MessageSubscriptionKeyExactMatch](#messagesubscriptionkeyexactmatch) — Matches the value exactly - [MessageSubscriptionKeyFilterProperty](#messagesubscriptionkeyfilterproperty) — MessageSubscriptionKey property with full advanced search capabilities - [MessageSubscriptionResult](#messagesubscriptionresult) — MessageSubscriptionResult - [MessageSubscriptionSearchQuery](#messagesubscriptionsearchquery) — MessageSubscriptionSearchQuery - [MessageSubscriptionSearchQueryResult](#messagesubscriptionsearchqueryresult) — MessageSubscriptionSearchQueryResult - [MessageSubscriptionSearchQuerySortRequest](#messagesubscriptionsearchquerysortrequest) — MessageSubscriptionSearchQuerySortRequest - [MessageSubscriptionStateExactMatch](#messagesubscriptionstateexactmatch) — Matches the value exactly - [MessageSubscriptionStateFilterProperty](#messagesubscriptionstatefilterproperty) — MessageSubscriptionStateEnum with full advanced search capabilities - [MessageSubscriptionTypeExactMatch](#messagesubscriptiontypeexactmatch) — Matches the value exactly - [MessageSubscriptionTypeFilterProperty](#messagesubscriptiontypefilterproperty) — MessageSubscriptionTypeEnum with full advanced search capabilities - [MessageWaitStateDetails](#messagewaitstatedetails) — MessageWaitStateDetails - [MigrateProcessInstanceMappingInstruction](#migrateprocessinstancemappinginstruction) — The mapping instructions describe how to map elements from the source process definition to the target process definition - [ModifyProcessInstanceVariableInstruction](#modifyprocessinstancevariableinstruction) — Instruction describing which variables to create or update - [OffsetPagination](#offsetpagination) — OffsetPagination - [OperationReference](#operationreference) — A reference key chosen by the user that will be part of all records resulting from this operation - [OperationTypeExactMatch](#operationtypeexactmatch) — Matches the value exactly - [OperationTypeFilterProperty](#operationtypefilterproperty) — AuditLogOperationTypeEnum property with full advanced search capabilities - [OwnAuthorizationSearchResult](#ownauthorizationsearchresult) — OwnAuthorizationSearchResult - [Partition](#partition) — Provides information on a partition within a broker node - [PartitionBackupInfo](#partitionbackupinfo) — Detailed info of the backup for a given partition - [PartitionBackupRange](#partitionbackuprange) — Information about one backup range for a partition - [PartitionBackupState](#partitionbackupstate) — Detailed information about the backup state for a given partition - [PartitionCheckpointState](#partitioncheckpointstate) — Detailed information about the checkpoint state for a given partition - [PartitionId](#partitionid) — The id of a partition - [PhysicalTenantBrokerTopology](#physicaltenantbrokertopology) — The partitions of one physical tenant that one broker manages or replicates - [PhysicalTenantTopology](#physicaltenanttopology) — The topology of a single physical tenant - [ProblemDetail](#problemdetail) — A Problem detail object as described in [RFC 9457](https://www - [ProcessDefinitionElementStatisticsQuery](#processdefinitionelementstatisticsquery) — Process definition element statistics request - [ProcessDefinitionElementStatisticsQueryResult](#processdefinitionelementstatisticsqueryresult) — Process definition element statistics query response - [ProcessDefinitionFilter](#processdefinitionfilter) — Process definition search filter - [ProcessDefinitionId](#processdefinitionid) — Id of a process definition, from the model - [ProcessDefinitionIdExactMatch](#processdefinitionidexactmatch) — Matches the value exactly - [ProcessDefinitionIdFilterProperty](#processdefinitionidfilterproperty) — ProcessDefinitionId property with full advanced search capabilities - [ProcessDefinitionInstanceStatisticsQuery](#processdefinitioninstancestatisticsquery) — ProcessDefinitionInstanceStatisticsQuery - [ProcessDefinitionInstanceStatisticsQueryResult](#processdefinitioninstancestatisticsqueryresult) — ProcessDefinitionInstanceStatisticsQueryResult - [ProcessDefinitionInstanceStatisticsQuerySortRequest](#processdefinitioninstancestatisticsquerysortrequest) — ProcessDefinitionInstanceStatisticsQuerySortRequest - [ProcessDefinitionInstanceStatisticsResult](#processdefinitioninstancestatisticsresult) — Process definition instance statistics response - [ProcessDefinitionInstanceVersionStatisticsFilter](#processdefinitioninstanceversionstatisticsfilter) — Process definition instance version statistics search filter - [ProcessDefinitionInstanceVersionStatisticsQuery](#processdefinitioninstanceversionstatisticsquery) — ProcessDefinitionInstanceVersionStatisticsQuery - [ProcessDefinitionInstanceVersionStatisticsQueryResult](#processdefinitioninstanceversionstatisticsqueryresult) — ProcessDefinitionInstanceVersionStatisticsQueryResult - [ProcessDefinitionInstanceVersionStatisticsQuerySortRequest](#processdefinitioninstanceversionstatisticsquerysortrequest) — ProcessDefinitionInstanceVersionStatisticsQuerySortRequest - [ProcessDefinitionInstanceVersionStatisticsResult](#processdefinitioninstanceversionstatisticsresult) — Process definition instance version statistics response - [ProcessDefinitionKeyExactMatch](#processdefinitionkeyexactmatch) — Matches the value exactly - [ProcessDefinitionKeyFilterProperty](#processdefinitionkeyfilterproperty) — ProcessDefinitionKey property with full advanced search capabilities - [ProcessDefinitionMessageSubscriptionStatisticsQuery](#processdefinitionmessagesubscriptionstatisticsquery) — ProcessDefinitionMessageSubscriptionStatisticsQuery - [ProcessDefinitionMessageSubscriptionStatisticsQueryResult](#processdefinitionmessagesubscriptionstatisticsqueryresult) — ProcessDefinitionMessageSubscriptionStatisticsQueryResult - [ProcessDefinitionMessageSubscriptionStatisticsResult](#processdefinitionmessagesubscriptionstatisticsresult) — ProcessDefinitionMessageSubscriptionStatisticsResult - [ProcessDefinitionResult](#processdefinitionresult) — ProcessDefinitionResult - [ProcessDefinitionSearchQuery](#processdefinitionsearchquery) — ProcessDefinitionSearchQuery - [ProcessDefinitionSearchQueryResult](#processdefinitionsearchqueryresult) — ProcessDefinitionSearchQueryResult - [ProcessDefinitionSearchQuerySortRequest](#processdefinitionsearchquerysortrequest) — ProcessDefinitionSearchQuerySortRequest - [ProcessDefinitionStatisticsFilter](#processdefinitionstatisticsfilter) — Process definition statistics search filter - [ProcessDefinitionVariableNameFilter](#processdefinitionvariablenamefilter) — Process definition variable name filter request - [ProcessDefinitionVariableNameSearchQuery](#processdefinitionvariablenamesearchquery) — Process definition variable name search query request - [ProcessDefinitionVariableNameSearchQueryResult](#processdefinitionvariablenamesearchqueryresult) — Process definition variable name search query response - [ProcessDefinitionVariableNameSearchResult](#processdefinitionvariablenamesearchresult) — Process definition variable name search response item - [ProcessElementStatisticsResult](#processelementstatisticsresult) — Process element statistics response - [ProcessInstanceBusinessIdAssignmentInstruction](#processinstancebusinessidassignmentinstruction) — The instruction describing the business id to assign to a running process instance - [ProcessInstanceCallHierarchyEntry](#processinstancecallhierarchyentry) — ProcessInstanceCallHierarchyEntry - [ProcessInstanceCancellationBatchOperationRequest](#processinstancecancellationbatchoperationrequest) — The process instance filter that defines which process instances should be canceled - [ProcessInstanceCreationInstruction](#processinstancecreationinstruction) — Instructions for creating a process instance - [ProcessInstanceCreationInstructionById](#processinstancecreationinstructionbyid) — ProcessInstanceCreationInstructionById - [ProcessInstanceCreationInstructionByKey](#processinstancecreationinstructionbykey) — ProcessInstanceCreationInstructionByKey - [ProcessInstanceCreationRuntimeInstruction](#processinstancecreationruntimeinstruction) — ProcessInstanceCreationRuntimeInstruction - [ProcessInstanceCreationStartInstruction](#processinstancecreationstartinstruction) — ProcessInstanceCreationStartInstruction - [ProcessInstanceCreationTerminateInstruction](#processinstancecreationterminateinstruction) — Terminates the process instance after a specific BPMN element is completed or terminated - [ProcessInstanceDeletionBatchOperationRequest](#processinstancedeletionbatchoperationrequest) — The process instance filter that defines which process instances should be deleted - [ProcessInstanceElementStatisticsQueryResult](#processinstanceelementstatisticsqueryresult) — Process instance element statistics query response - [ProcessInstanceFilter](#processinstancefilter) — Process instance search filter - [ProcessInstanceFilterFields](#processinstancefilterfields) — Process instance search filter - [ProcessInstanceIncidentResolutionBatchOperationRequest](#processinstanceincidentresolutionbatchoperationrequest) — The process instance filter that defines which process instances should have their incidents resolved - [ProcessInstanceKeyExactMatch](#processinstancekeyexactmatch) — Matches the value exactly - [ProcessInstanceKeyFilterProperty](#processinstancekeyfilterproperty) — ProcessInstanceKey property with full advanced search capabilities - [ProcessInstanceMigrationBatchOperationPlan](#processinstancemigrationbatchoperationplan) — The migration instructions describe how to migrate a process instance from one process definition to another - [ProcessInstanceMigrationBatchOperationRequest](#processinstancemigrationbatchoperationrequest) — ProcessInstanceMigrationBatchOperationRequest - [ProcessInstanceMigrationInstruction](#processinstancemigrationinstruction) — The migration instructions describe how to migrate a process instance from one process definition to another - [ProcessInstanceModificationActivateInstruction](#processinstancemodificationactivateinstruction) — Instruction describing an element to activate - [ProcessInstanceModificationBatchOperationRequest](#processinstancemodificationbatchoperationrequest) — The process instance filter to define on which process instances tokens should be moved, and new element instances should be activated or terminated - [ProcessInstanceModificationInstruction](#processinstancemodificationinstruction) — ProcessInstanceModificationInstruction - [ProcessInstanceModificationMoveBatchOperationInstruction](#processinstancemodificationmovebatchoperationinstruction) — Instructions describing a move operation - [ProcessInstanceModificationMoveInstruction](#processinstancemodificationmoveinstruction) — Instruction describing a move operation - [ProcessInstanceModificationTerminateByIdInstruction](#processinstancemodificationterminatebyidinstruction) — Instruction describing which elements to terminate - [ProcessInstanceModificationTerminateByKeyInstruction](#processinstancemodificationterminatebykeyinstruction) — Instruction providing the key of the element instance to terminate - [ProcessInstanceModificationTerminateInstruction](#processinstancemodificationterminateinstruction) — Instruction describing which elements to terminate - [ProcessInstanceReference](#processinstancereference) — ProcessInstanceReference - [ProcessInstanceResult](#processinstanceresult) — Process instance search response item - [ProcessInstanceResumptionBatchOperationRequest](#processinstanceresumptionbatchoperationrequest) — The process instance filter that defines which process instances should be resumed - [ProcessInstanceSearchQuery](#processinstancesearchquery) — Process instance search request - [ProcessInstanceSearchQueryResult](#processinstancesearchqueryresult) — Process instance search response - [ProcessInstanceSearchQuerySortRequest](#processinstancesearchquerysortrequest) — ProcessInstanceSearchQuerySortRequest - [ProcessInstanceSequenceFlowResult](#processinstancesequenceflowresult) — Process instance sequence flow result - [ProcessInstanceSequenceFlowsQueryResult](#processinstancesequenceflowsqueryresult) — Process instance sequence flows query response - [ProcessInstanceStateExactMatch](#processinstancestateexactmatch) — Matches the value exactly - [ProcessInstanceStateFilterProperty](#processinstancestatefilterproperty) — ProcessInstanceStateEnum property with full advanced search capabilities - [ProcessInstanceSuspensionBatchOperationRequest](#processinstancesuspensionbatchoperationrequest) — The process instance filter that defines which process instances should be suspended - [ProcessInstanceWaitStateStatisticsQueryResult](#processinstancewaitstatestatisticsqueryresult) — Process instance wait state statistics query response - [ProcessInstanceWaitStateStatisticsResult](#processinstancewaitstatestatisticsresult) — Process instance wait state statistics response item - [RebalanceCancellationResponse](#rebalancecancellationresponse) — Response to a rebalance cancellation request - [ResolvedSecret](#resolvedsecret) — ResolvedSecret - [ResourceFilter](#resourcefilter) — Resource search filter - [ResourceKeyExactMatch](#resourcekeyexactmatch) — Matches the value exactly - [ResourceKeyFilterProperty](#resourcekeyfilterproperty) — ResourceKey property with full advanced search capabilities - [ResourceResult](#resourceresult) — ResourceResult - [ResourceSearchQuery](#resourcesearchquery) — ResourceSearchQuery - [ResourceSearchQueryResult](#resourcesearchqueryresult) — ResourceSearchQueryResult - [ResourceSearchQuerySortRequest](#resourcesearchquerysortrequest) — ResourceSearchQuerySortRequest - [RestoreBrokerStatus](#restorebrokerstatus) — The restore status of a single broker - [RestorePartitionStatus](#restorepartitionstatus) — The restore status of a single partition on a broker - [RestoreRequest](#restorerequest) — Describes a restore request - [RestoreStatusResponse](#restorestatusresponse) — The status of the restore that is currently in progress - [ResumeProcessInstanceRequest](#resumeprocessinstancerequest) — ResumeProcessInstanceRequest - [RetryDecision](#retrydecision) - [RoleClientResult](#roleclientresult) — RoleClientResult - [RoleClientSearchQueryRequest](#roleclientsearchqueryrequest) — RoleClientSearchQueryRequest - [RoleClientSearchQuerySortRequest](#roleclientsearchquerysortrequest) — RoleClientSearchQuerySortRequest - [RoleClientSearchResult](#roleclientsearchresult) — RoleClientSearchResult - [RoleCreateRequest](#rolecreaterequest) — RoleCreateRequest - [RoleCreateResult](#rolecreateresult) — RoleCreateResult - [RoleFilter](#rolefilter) — Role filter request - [RoleFilterFields](#rolefilterfields) — Role filter request - [RoleGroupResult](#rolegroupresult) — RoleGroupResult - [RoleGroupSearchQueryRequest](#rolegroupsearchqueryrequest) — RoleGroupSearchQueryRequest - [RoleGroupSearchQuerySortRequest](#rolegroupsearchquerysortrequest) — RoleGroupSearchQuerySortRequest - [RoleGroupSearchResult](#rolegroupsearchresult) — RoleGroupSearchResult - [RoleId](#roleid) — The unique identifier of a role - [RoleMappingRuleSearchResult](#rolemappingrulesearchresult) — RoleMappingRuleSearchResult - [RoleResult](#roleresult) — Role search response item - [RoleSearchQueryRequest](#rolesearchqueryrequest) — Role search request - [RoleSearchQueryResult](#rolesearchqueryresult) — Role search response - [RoleSearchQuerySortRequest](#rolesearchquerysortrequest) — RoleSearchQuerySortRequest - [RoleUpdateRequest](#roleupdaterequest) — RoleUpdateRequest - [RoleUpdateResult](#roleupdateresult) — RoleUpdateResult - [RoleUserFilterRequest](#roleuserfilterrequest) — Role user filter request - [RoleUserResult](#roleuserresult) — RoleUserResult - [RoleUserSearchQueryRequest](#roleusersearchqueryrequest) — RoleUserSearchQueryRequest - [RoleUserSearchQuerySortRequest](#roleusersearchquerysortrequest) — RoleUserSearchQuerySortRequest - [RoleUserSearchResult](#roleusersearchresult) — RoleUserSearchResult - [RuntimeBackupState](#runtimebackupstate) — Information about the checkpoint and backup state of the physical tenant - [ScopeKeyExactMatch](#scopekeyexactmatch) — Matches the value exactly - [ScopeKeyFilterProperty](#scopekeyfilterproperty) — ScopeKey property with full advanced search capabilities - [SearchQueryPageRequest](#searchquerypagerequest) — Pagination criteria - [SearchQueryPageResponse](#searchquerypageresponse) — Pagination information about the search results - [SearchQueryRequest](#searchqueryrequest) — SearchQueryRequest - [SearchQueryResponse](#searchqueryresponse) — SearchQueryResponse - [SecretListRequest](#secretlistrequest) — Reserved for future filtering options - [SecretListResult](#secretlistresult) — The secret references the caller is authorized to see - [SecretResolutionError](#secretresolutionerror) — SecretResolutionError - [SecretResolveRequest](#secretresolverequest) — SecretResolveRequest - [SecretResolveResult](#secretresolveresult) — The per-reference outcome of a resolve request - [SeededRandomSource](#seededrandomsource) — A deterministic `IRandomSource` for tests: the same seed yields the same sequence of draws on every run, and in every Camunda SDK - [SetVariableRequest](#setvariablerequest) — SetVariableRequest - [SignalBroadcastRequest](#signalbroadcastrequest) — SignalBroadcastRequest - [SignalBroadcastResult](#signalbroadcastresult) — SignalBroadcastResult - [SignalWaitStateDetails](#signalwaitstatedetails) — SignalWaitStateDetails - [SourceElementIdInstruction](#sourceelementidinstruction) — Defines an instruction with a sourceElementId - [SourceElementInstanceKeyInstruction](#sourceelementinstancekeyinstruction) — Defines an instruction with a sourceElementInstanceKey - [SourceElementInstruction](#sourceelementinstruction) — Defines the source element identifier for the move instruction - [StartCursor](#startcursor) — The start cursor in a search query result set - [StatusMetric](#statusmetric) — Metric for a single job status - [StopResult](#stopresult) — Result of a `JobWorker - [StringFilterProperty](#stringfilterproperty) — String property with full advanced search capabilities - [SuspendProcessInstanceRequest](#suspendprocessinstancerequest) — SuspendProcessInstanceRequest - [SystemConfigurationResponse](#systemconfigurationresponse) — Envelope for all system configuration sections - [Tag](#tag) — A tag - [TakeHistoryBackupRequest](#takehistorybackuprequest) — Request body for taking a history backup - [TakeHistoryBackupResponse](#takehistorybackupresponse) — Response body for taking a history backup - [TakeRuntimeBackupRequest](#takeruntimebackuprequest) — Request body for taking a runtime backup - [TakeRuntimeBackupResponse](#takeruntimebackupresponse) — Response body for taking a runtime backup - [TenantClientResult](#tenantclientresult) — TenantClientResult - [TenantClientSearchQueryRequest](#tenantclientsearchqueryrequest) — TenantClientSearchQueryRequest - [TenantClientSearchQuerySortRequest](#tenantclientsearchquerysortrequest) — TenantClientSearchQuerySortRequest - [TenantClientSearchResult](#tenantclientsearchresult) — TenantClientSearchResult - [TenantCreateRequest](#tenantcreaterequest) — TenantCreateRequest - [TenantCreateResult](#tenantcreateresult) — TenantCreateResult - [TenantFilter](#tenantfilter) — Tenant filter request - [TenantGroupResult](#tenantgroupresult) — TenantGroupResult - [TenantGroupSearchQueryRequest](#tenantgroupsearchqueryrequest) — TenantGroupSearchQueryRequest - [TenantGroupSearchQuerySortRequest](#tenantgroupsearchquerysortrequest) — TenantGroupSearchQuerySortRequest - [TenantGroupSearchResult](#tenantgroupsearchresult) — TenantGroupSearchResult - [TenantId](#tenantid) — The unique identifier of the tenant - [TenantMappingRuleSearchResult](#tenantmappingrulesearchresult) — TenantMappingRuleSearchResult - [TenantResult](#tenantresult) — Tenant search response item - [TenantRoleSearchResult](#tenantrolesearchresult) — TenantRoleSearchResult - [TenantSearchQueryRequest](#tenantsearchqueryrequest) — Tenant search request - [TenantSearchQueryResult](#tenantsearchqueryresult) — Tenant search response - [TenantSearchQuerySortRequest](#tenantsearchquerysortrequest) — TenantSearchQuerySortRequest - [TenantUpdateRequest](#tenantupdaterequest) — TenantUpdateRequest - [TenantUpdateResult](#tenantupdateresult) — TenantUpdateResult - [TenantUserResult](#tenantuserresult) — TenantUserResult - [TenantUserSearchQueryRequest](#tenantusersearchqueryrequest) — TenantUserSearchQueryRequest - [TenantUserSearchQuerySortRequest](#tenantusersearchquerysortrequest) — TenantUserSearchQuerySortRequest - [TenantUserSearchResult](#tenantusersearchresult) — TenantUserSearchResult - [TimerWaitStateDetails](#timerwaitstatedetails) — TimerWaitStateDetails - [TlsConfig](#tlsconfig) — TLS / mTLS configuration for custom certificates - [TopologyResponse](#topologyresponse) — The response of a topology request - [TypedVariables](#typedvariables) — Extension methods for deserializing Camunda variable and custom header payloads from untyped `object` properties into strongly-typed DTOs - [TypedVariablesException](#typedvariablesexception) — Base class for all errors raised by the DTO-driven typed variable map feature (`CamundaClient - [UpdateClusterVariableRequest](#updateclustervariablerequest) — UpdateClusterVariableRequest - [UpdateGlobalTaskListenerRequest](#updateglobaltasklistenerrequest) — UpdateGlobalTaskListenerRequest - [UsageMetricsResponse](#usagemetricsresponse) — UsageMetricsResponse - [UsageMetricsResponseItem](#usagemetricsresponseitem) — UsageMetricsResponseItem - [UseSourceParentKeyInstruction](#usesourceparentkeyinstruction) — Instructs the engine to use the source's direct parent key as the ancestor scope key for the target element - [UserCreateResult](#usercreateresult) — UserCreateResult - [UserFilter](#userfilter) — User search filter - [UserFilterFields](#userfilterfields) — User search filter fields - [UserRequest](#userrequest) — UserRequest - [UserResult](#userresult) — UserResult - [UserSearchQueryRequest](#usersearchqueryrequest) — UserSearchQueryRequest - [UserSearchQuerySortRequest](#usersearchquerysortrequest) — UserSearchQuerySortRequest - [UserSearchResult](#usersearchresult) — UserSearchResult - [UserTaskAssignmentRequest](#usertaskassignmentrequest) — UserTaskAssignmentRequest - [UserTaskAuditLogFilter](#usertaskauditlogfilter) — The user task audit log search filters - [UserTaskAuditLogSearchQueryRequest](#usertaskauditlogsearchqueryrequest) — User task search query request - [UserTaskCompletionRequest](#usertaskcompletionrequest) — UserTaskCompletionRequest - [UserTaskEffectiveVariableSearchQueryRequest](#usertaskeffectivevariablesearchqueryrequest) — User task effective variable search query request - [UserTaskFilter](#usertaskfilter) — User task filter request - [UserTaskFilterFields](#usertaskfilterfields) — User task filter fields - [UserTaskProperties](#usertaskproperties) — Contains properties of a user task - [UserTaskResult](#usertaskresult) — UserTaskResult - [UserTaskSearchQuery](#usertasksearchquery) — User task search query request - [UserTaskSearchQueryResult](#usertasksearchqueryresult) — User task search query response - [UserTaskSearchQuerySortRequest](#usertasksearchquerysortrequest) — UserTaskSearchQuerySortRequest - [UserTaskStateExactMatch](#usertaskstateexactmatch) — Matches the value exactly - [UserTaskStateFilterProperty](#usertaskstatefilterproperty) — UserTaskStateEnum property with full advanced search capabilities - [UserTaskUpdateRequest](#usertaskupdaterequest) — UserTaskUpdateRequest - [UserTaskVariableFilter](#usertaskvariablefilter) — The user task variable search filters - [UserTaskVariableSearchQueryRequest](#usertaskvariablesearchqueryrequest) — User task search query request - [UserTaskVariableSearchQuerySortRequest](#usertaskvariablesearchquerysortrequest) — UserTaskVariableSearchQuerySortRequest - [UserTaskWaitStateDetails](#usertaskwaitstatedetails) — UserTaskWaitStateDetails - [UserUpdateRequest](#userupdaterequest) — UserUpdateRequest - [UserUpdateResult](#userupdateresult) — UserUpdateResult - [Username](#username) — The unique name of a user - [VariableDeserializationException](#variabledeserializationexception) — Raised when a present variable value cannot be deserialized - [VariableFilter](#variablefilter) — Variable filter request - [VariableKeyExactMatch](#variablekeyexactmatch) — Matches the value exactly - [VariableKeyFilterProperty](#variablekeyfilterproperty) — VariableKey property with full advanced search capabilities - [VariableMap](#variablemap) — Result of a DTO-driven variable search (`CamundaClient - [VariableResult](#variableresult) — Variable search response item - [VariableResultBase](#variableresultbase) — Variable response item - [VariableScopeCollisionException](#variablescopecollisionexception) — Raised when a declared variable name is returned at more than one scope - [VariableSearchQuery](#variablesearchquery) — Variable search query request - [VariableSearchQueryResult](#variablesearchqueryresult) — Variable search query response - [VariableSearchQuerySortRequest](#variablesearchquerysortrequest) — VariableSearchQuerySortRequest - [VariableSearchResult](#variablesearchresult) — Variable search response item - [VariableValidationException](#variablevalidationexception) — Raised by `VariableMap - [VariableValueFilterProperty](#variablevaluefilterproperty) — VariableValueFilterProperty - [WaitStateDetails](#waitstatedetails) — Wait-state-specific details of an element instance - [WaitStateElementTypeExactMatch](#waitstateelementtypeexactmatch) — Matches the value exactly - [WaitStateElementTypeFilterProperty](#waitstateelementtypefilterproperty) — Element type property with full advanced search capabilities - [WaitStateTypeExactMatch](#waitstatetypeexactmatch) — Matches the value exactly - [WaitStateTypeFilterProperty](#waitstatetypefilterproperty) — Wait state type property with full advanced search capabilities - [WorkerDefaultsConfig](#workerdefaultsconfig) --- ## ActivatedJob An activated job received from the Camunda broker, with typed variable access. This is what job handler functions receive. ```csharp public sealed class ActivatedJob ``` | Property | Type | Description | | -------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Clock` | `TimeProvider` | The clock the worker's own cadence runs on. Handlers should read time and wait through this rather than `DateTimeOffset.UtcNow` or `Task.Delay(ms, ct)`, so that in-handler timing is virtual whenever the client's clock is. `csharp var startedAt = job.Clock.GetUtcNow(); await Task.Delay(TimeSpan.FromMilliseconds(200), job.Clock, ct); ` This is for short in-handler coordination — poll again shortly, back off before a retry, debounce. It is not for business waits: "wait three days, then escalate" belongs in the BPMN model as a timer event, which is durable, owned by the engine, and survives worker restarts. | | `Type` | `String` | The job type (matches the BPMN task definition type). | | `ProcessDefinitionId` | `ProcessDefinitionId` | The BPMN process ID of the job's process definition. | | `ProcessDefinitionVersion` | `Int32` | The version of the job's process definition. | | `ElementId` | `ElementId` | The associated task element ID. | | `CustomHeaders` | `Object` | Raw custom headers (typically a `Json.JsonElement` at runtime). | | `Worker` | `String` | The name of the worker that activated this job. | | `Retries` | `Int32` | Retries remaining for this job. | | `Deadline` | `Int64` | UNIX epoch timestamp (ms) when the job lock expires. | | `Variables` | `Object` | Raw variables (typically a `Json.JsonElement` at runtime). | | `TenantId` | `TenantId` | The tenant that owns this job. | | `JobKey` | `JobKey` | Unique identifier for this job. | | `JobLeaseToken` | `Nullable` | The lease token for this job, or `null` if it was not activated with a lease. Present exactly when the worker set `JobWorkerConfig.WithLease`; the worker threads it back onto the fenced commands automatically. | | `ProcessInstanceKey` | `ProcessInstanceKey` | The process instance this job belongs to. | | `ProcessDefinitionKey` | `ProcessDefinitionKey` | The process definition key. | | `ElementInstanceKey` | `ElementInstanceKey` | The element instance key. | | `Kind` | `JobKindEnum` | The job kind. | | `ListenerEventType` | `JobListenerEventTypeEnum` | The listener event type. | | `UserTask` | `UserTaskProperties` | User task properties (if this is a user task job). | | `Tags` | `List` | Tags associated with this job. | ## ActivatedJobResult ```csharp public sealed class ActivatedJobResult ``` | Property | Type | Description | | -------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Type` | `String` | The type of the job (should match what was requested). | | `ProcessDefinitionId` | `ProcessDefinitionId` | The bpmn process ID of the job's process definition. | | `ProcessDefinitionVersion` | `Int32` | The version of the job's process definition. | | `ElementId` | `ElementId` | The associated task element ID. | | `CustomHeaders` | `Object` | A set of custom headers defined during modelling; returned as a serialized JSON document. | | `Worker` | `String` | The name of the worker which activated this job. | | `Retries` | `Int32` | The amount of retries left to this job (should always be positive). | | `Deadline` | `Int64` | When the job can be activated again, sent as a UNIX epoch timestamp. | | `Variables` | `Object` | All variables visible to the task scope, computed at activation time. | | `TenantId` | `TenantId` | The ID of the tenant that owns the job. | | `PhysicalTenantId` | `String` | The ID of the physical tenant that the job-activation request was routed to; the default physical tenant when the request did not specify one. | | `JobKey` | `JobKey` | The key, a unique identifier for the job. | | `ProcessInstanceKey` | `ProcessInstanceKey` | The job's process instance key. | | `ProcessDefinitionKey` | `ProcessDefinitionKey` | The key of the job's process definition. | | `ElementInstanceKey` | `ElementInstanceKey` | The element instance key of the task. | | `Kind` | `JobKindEnum` | The job kind. | | `ListenerEventType` | `JobListenerEventTypeEnum` | The listener event type of the job. | | `UserTask` | `UserTaskProperties` | User task properties, if the job is a user task. This is `null` if the job is not a user task. | | `Tags` | `List` | List of tags. Tags need to start with a letter; then alphanumerics, `_`, `-`, `:`, or `.`; length ≤ 100. | | `RootProcessInstanceKey` | `Nullable` | The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. | | `BusinessId` | `Nullable` | The business ID of the owning process instance, inherited when the job was created. This is `null` for jobs created before version 8.10 and for jobs whose owning process instance has no business ID. | | `Priority` | `Int32` | The priority of the job. Higher values indicate higher priority. Jobs created before 8.10 have no stored priority; the API returns 0 for such jobs. | | `JobLeaseToken` | `Nullable` | The lease token identifying this activation. This is `null` when the job was activated without a lease. | ## AdHocSubProcessActivateActivitiesInstruction ```csharp public sealed class AdHocSubProcessActivateActivitiesInstruction ``` | Property | Type | Description | | -------------------------- | ------------------------------------------------ | ---------------------------------------------------------------- | | `Elements` | `List` | Activities to activate. | | `CancelRemainingInstances` | `Nullable` | Whether to cancel remaining instances of the ad-hoc sub-process. | ## AdHocSubProcessActivateActivityReference ```csharp public sealed class AdHocSubProcessActivateActivityReference ``` | Property | Type | Description | | ----------- | ----------- | ------------------------------------------------ | | `ElementId` | `ElementId` | The ID of the element that should be activated. | | `Variables` | `Object` | Variables to be set when activating the element. | ## AdvancedActorTypeFilter Advanced AuditLogActorTypeEnum filter. ```csharp public sealed class AdvancedActorTypeFilter ``` | Property | Type | Description | | -------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedAgentDefinitionKeyFilter Advanced AgentDefinitionKey filter. ```csharp public sealed class AdvancedAgentDefinitionKeyFilter ``` | Property | Type | Description | | -------- | ------------------------------ | ----------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AdvancedAgentDefinitionTypeFilter Advanced AgentDefinitionTypeEnum filter. ```csharp public sealed class AdvancedAgentDefinitionTypeFilter ``` | Property | Type | Description | | -------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedAgentHistoryItemKeyFilter Advanced AgentHistoryItemKey filter. ```csharp public sealed class AdvancedAgentHistoryItemKeyFilter ``` | Property | Type | Description | | -------- | ------------------------------- | ----------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AdvancedAgentInstanceHistoryCommitStatusFilter Advanced AgentInstanceHistoryCommitStatusEnum filter. ```csharp public sealed class AdvancedAgentInstanceHistoryCommitStatusFilter ``` | Property | Type | Description | | -------- | ------------------------------------------------ | ---------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | ## AdvancedAgentInstanceHistoryRoleFilter Advanced AgentInstanceHistoryRoleEnum filter. ```csharp public sealed class AdvancedAgentInstanceHistoryRoleFilter ``` | Property | Type | Description | | -------- | ---------------------------------------- | ---------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | ## AdvancedAgentInstanceKeyFilter Advanced AgentInstanceKey filter. ```csharp public sealed class AdvancedAgentInstanceKeyFilter ``` | Property | Type | Description | | -------- | ---------------------------- | ----------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AdvancedAgentInstanceStatusFilter Advanced AgentInstanceStatusEnum filter. ```csharp public sealed class AdvancedAgentInstanceStatusFilter ``` | Property | Type | Description | | -------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedAuditLogEntityKeyFilter Advanced entityKey filter. ```csharp public sealed class AdvancedAuditLogEntityKeyFilter ``` | Property | Type | Description | | -------- | ----------------------------- | ----------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AdvancedAuditLogKeyFilter Advanced AuditLogKey filter. ```csharp public sealed class AdvancedAuditLogKeyFilter ``` | Property | Type | Description | | -------- | ----------------------- | ----------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AdvancedBatchOperationItemStateFilter Advanced BatchOperationItemStateEnum filter. ```csharp public sealed class AdvancedBatchOperationItemStateFilter ``` | Property | Type | Description | | -------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedBatchOperationStateFilter Advanced BatchOperationStateEnum filter. ```csharp public sealed class AdvancedBatchOperationStateFilter ``` | Property | Type | Description | | -------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedBatchOperationTypeFilter Advanced BatchOperationTypeEnum filter. ```csharp public sealed class AdvancedBatchOperationTypeFilter ``` | Property | Type | Description | | -------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedCategoryFilter Advanced AuditLogCategoryEnum filter. ```csharp public sealed class AdvancedCategoryFilter ``` | Property | Type | Description | | -------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedClusterVariableKindFilter Advanced ClusterVariableKindEnum filter. ```csharp public sealed class AdvancedClusterVariableKindFilter ``` | Property | Type | Description | | -------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedClusterVariableScopeFilter Advanced ClusterVariableScopeEnum filter. ```csharp public sealed class AdvancedClusterVariableScopeFilter ``` | Property | Type | Description | | -------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedDateTimeFilter Advanced date-time filter. ```csharp public sealed class AdvancedDateTimeFilter ``` | Property | Type | Description | | -------- | -------------------------- | ---------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `Gt` | `Nullable` | Greater than comparison with the provided value. | | `Gte` | `Nullable` | Greater than or equal comparison with the provided value. | | `Lt` | `Nullable` | Lower than comparison with the provided value. | | `Lte` | `Nullable` | Lower than or equal comparison with the provided value. | | `In` | `List` | Checks if the property matches any of the provided values. | ## AdvancedDecisionDefinitionKeyFilter Advanced DecisionDefinitionKey filter. ```csharp public sealed class AdvancedDecisionDefinitionKeyFilter ``` | Property | Type | Description | | -------- | --------------------------------- | ----------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AdvancedDecisionEvaluationInstanceKeyFilter Advanced DecisionEvaluationInstanceKey filter. ```csharp public sealed class AdvancedDecisionEvaluationInstanceKeyFilter ``` | Property | Type | Description | | -------- | ----------------------------------------- | ----------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AdvancedDecisionEvaluationKeyFilter Advanced DecisionEvaluationKey filter. ```csharp public sealed class AdvancedDecisionEvaluationKeyFilter ``` | Property | Type | Description | | -------- | --------------------------------- | ----------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AdvancedDecisionInstanceStateFilter Advanced DecisionInstanceStateEnum filter. ```csharp public sealed class AdvancedDecisionInstanceStateFilter ``` | Property | Type | Description | | -------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedDecisionRequirementsKeyFilter Advanced DecisionRequirementsKey filter. ```csharp public sealed class AdvancedDecisionRequirementsKeyFilter ``` | Property | Type | Description | | -------- | ----------------------------------- | ----------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AdvancedDeploymentKeyFilter Advanced DeploymentKey filter. ```csharp public sealed class AdvancedDeploymentKeyFilter ``` | Property | Type | Description | | -------- | ------------------------- | ----------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AdvancedElementIdFilter Advanced ElementId filter. ```csharp public sealed class AdvancedElementIdFilter ``` | Property | Type | Description | | -------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedElementInstanceKeyFilter Advanced ElementInstanceKey filter. ```csharp public sealed class AdvancedElementInstanceKeyFilter ``` | Property | Type | Description | | -------- | ------------------------------ | ----------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AdvancedElementInstanceStateFilter Advanced ElementInstanceStateEnum filter. ```csharp public sealed class AdvancedElementInstanceStateFilter ``` | Property | Type | Description | | -------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedEntityTypeFilter Advanced AuditLogEntityTypeEnum filter. ```csharp public sealed class AdvancedEntityTypeFilter ``` | Property | Type | Description | | -------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedFormKeyFilter Advanced FormKey filter. ```csharp public sealed class AdvancedFormKeyFilter ``` | Property | Type | Description | | -------- | ------------------- | ----------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AdvancedGlobalListenerSourceFilter Advanced global listener source filter. ```csharp public sealed class AdvancedGlobalListenerSourceFilter ``` | Property | Type | Description | | -------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedGlobalTaskListenerEventTypeFilter Advanced global listener event type filter. ```csharp public sealed class AdvancedGlobalTaskListenerEventTypeFilter ``` | Property | Type | Description | | -------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedIncidentErrorTypeFilter Advanced IncidentErrorTypeEnum filter ```csharp public sealed class AdvancedIncidentErrorTypeFilter ``` | Property | Type | Description | | -------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property does not match any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedIncidentStateFilter Advanced IncidentStateEnum filter ```csharp public sealed class AdvancedIncidentStateFilter ``` | Property | Type | Description | | -------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property does not match any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedIntegerFilter Advanced integer (int32) filter. ```csharp public sealed class AdvancedIntegerFilter ``` | Property | Type | Description | | -------- | ------------------- | ---------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `Gt` | `Nullable` | Greater than comparison with the provided value. | | `Gte` | `Nullable` | Greater than or equal comparison with the provided value. | | `Lt` | `Nullable` | Lower than comparison with the provided value. | | `Lte` | `Nullable` | Lower than or equal comparison with the provided value. | | `In` | `List` | Checks if the property matches any of the provided values. | ## AdvancedJobKeyFilter Advanced JobKey filter. ```csharp public sealed class AdvancedJobKeyFilter ``` | Property | Type | Description | | -------- | ------------------- | ----------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AdvancedJobKindFilter Advanced JobKindEnum filter. ```csharp public sealed class AdvancedJobKindFilter ``` | Property | Type | Description | | -------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedJobListenerEventTypeFilter Advanced JobListenerEventTypeEnum filter. ```csharp public sealed class AdvancedJobListenerEventTypeFilter ``` | Property | Type | Description | | -------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedJobStateFilter Advanced JobStateEnum filter. ```csharp public sealed class AdvancedJobStateFilter ``` | Property | Type | Description | | -------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedMessageSubscriptionKeyFilter Advanced MessageSubscriptionKey filter. ```csharp public sealed class AdvancedMessageSubscriptionKeyFilter ``` | Property | Type | Description | | -------- | ---------------------------------- | ----------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for equality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AdvancedMessageSubscriptionStateFilter Advanced MessageSubscriptionStateEnum filter ```csharp public sealed class AdvancedMessageSubscriptionStateFilter ``` | Property | Type | Description | | -------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedMessageSubscriptionTypeFilter Advanced MessageSubscriptionTypeEnum filter ```csharp public sealed class AdvancedMessageSubscriptionTypeFilter ``` | Property | Type | Description | | -------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedMetadataValueFilter Advanced filter on a metadata value (string or number). ```csharp public sealed class AdvancedMetadataValueFilter ``` | Property | Type | Description | | -------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Object` | Checks for equality with the provided value. | | `Neq` | `Object` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the metadata key exists. | | `Gt` | `Nullable` | Greater than comparison with the provided value. | | `Gte` | `Nullable` | Greater than or equal comparison with the provided value. | | `Lt` | `Nullable` | Lower than comparison with the provided value. | | `Lte` | `Nullable` | Lower than or equal comparison with the provided value. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedOperationTypeFilter Advanced AuditLogOperationTypeEnum filter. ```csharp public sealed class AdvancedOperationTypeFilter ``` | Property | Type | Description | | -------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedProcessDefinitionIdFilter Advanced ProcessDefinitionId filter. ```csharp public sealed class AdvancedProcessDefinitionIdFilter ``` | Property | Type | Description | | -------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedProcessDefinitionKeyFilter Advanced ProcessDefinitionKey filter. ```csharp public sealed class AdvancedProcessDefinitionKeyFilter ``` | Property | Type | Description | | -------- | -------------------------------- | ----------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AdvancedProcessInstanceKeyFilter Advanced ProcessInstanceKey filter. ```csharp public sealed class AdvancedProcessInstanceKeyFilter ``` | Property | Type | Description | | -------- | ------------------------------ | ----------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AdvancedProcessInstanceStateFilter Advanced ProcessInstanceStateEnum filter. ```csharp public sealed class AdvancedProcessInstanceStateFilter ``` | Property | Type | Description | | -------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedResourceKeyFilter Advanced ResourceKey filter. ```csharp public sealed class AdvancedResourceKeyFilter ``` | Property | Type | Description | | -------- | ----------------------- | ----------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AdvancedResultFilter Advanced AuditLogResultEnum filter. ```csharp public sealed class AdvancedResultFilter ``` | Property | Type | Description | | -------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedScopeKeyFilter Advanced ScopeKey filter. ```csharp public sealed class AdvancedScopeKeyFilter ``` | Property | Type | Description | | -------- | -------------------- | ----------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AdvancedStringFilter Advanced string filter. ```csharp public sealed class AdvancedStringFilter ``` | Property | Type | Description | | -------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `String` | Checks for equality with the provided value. | | `Neq` | `String` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedUserTaskStateFilter Advanced UserTaskStateEnum filter. ```csharp public sealed class AdvancedUserTaskStateFilter ``` | Property | Type | Description | | -------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedVariableKeyFilter Advanced VariableKey filter. ```csharp public sealed class AdvancedVariableKeyFilter ``` | Property | Type | Description | | -------- | ----------------------- | ----------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AdvancedWaitStateElementTypeFilter Advanced element type filter. ```csharp public sealed class AdvancedWaitStateElementTypeFilter ``` | Property | Type | Description | | -------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AdvancedWaitStateTypeFilter Advanced wait state type filter. ```csharp public sealed class AdvancedWaitStateTypeFilter ``` | Property | Type | Description | | -------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AgentDefinitionFilter Agent definition search filter. ```csharp public sealed class AgentDefinitionFilter ``` | Property | Type | Description | | ----------------------------- | ------------------------------------ | ------------------------------------------------------------------------------ | | `AgentDefinitionKey` | `AgentDefinitionKeyFilterProperty` | The unique key of the agent definition. | | `AgentType` | `AgentDefinitionTypeFilterProperty` | The kind of agent this agent definition describes. | | `Name` | `StringFilterProperty` | The human-readable name of the process element that owns the agent definition. | | `ElementId` | `ElementIdFilterProperty` | The BPMN element ID of the process element that owns the agent definition. | | `ProcessDefinitionId` | `ProcessDefinitionIdFilterProperty` | The BPMN process ID of the process definition that owns the agent definition. | | `ProcessDefinitionKey` | `ProcessDefinitionKeyFilterProperty` | The key of the process definition that owns the agent definition. | | `ProcessDefinitionVersion` | `IntegerFilterProperty` | The version of the process definition that owns the agent definition. | | `ProcessDefinitionVersionTag` | `StringFilterProperty` | The version tag of the process definition that owns the agent definition. | | `TenantId` | `StringFilterProperty` | The tenant ID of the agent definition. | ## AgentDefinitionKeyExactMatch Matches the value exactly. ```csharp public readonly record struct AgentDefinitionKeyExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## AgentDefinitionKeyFilterProperty AgentDefinitionKey property with full advanced search capabilities. ```csharp public sealed class AgentDefinitionKeyFilterProperty ``` | Property | Type | Description | | ------------ | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AgentDefinitionResult An agent definition, created at deploy time for the process element it belongs to. ```csharp public sealed class AgentDefinitionResult ``` | Property | Type | Description | | ----------------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `AgentDefinitionKey` | `AgentDefinitionKey` | The unique key for this agent definition. Unique across process definition versions. | | `AgentType` | `AgentDefinitionTypeEnum` | The kind of agent an agent definition describes. | | `Name` | `String` | The human-readable name of the process element that owns the agent definition. Falls back to elementId when the element has no BPMN name configured. | | `ElementId` | `ElementId` | The BPMN element ID of the process element that owns the agent definition. | | `ProcessDefinitionId` | `ProcessDefinitionId` | The BPMN process ID of the process definition that owns the agent definition. | | `ProcessDefinitionKey` | `ProcessDefinitionKey` | The key of the process definition that owns the agent definition. | | `ProcessDefinitionVersion` | `Int32` | The version of the process definition that owns the agent definition. | | `ProcessDefinitionVersionTag` | `String` | The version tag of the process definition that owns the agent definition. | | `TenantId` | `TenantId` | The tenant ID of this agent definition. | ## AgentDefinitionSearchQuery Agent definition search request. ```csharp public sealed class AgentDefinitionSearchQuery ``` | Property | Type | Description | | -------- | --------------------------------------------- | ------------------------------------ | | `Sort` | `List` | Sort field criteria. | | `Filter` | `AgentDefinitionFilter` | The agent definition search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## AgentDefinitionSearchQueryResult Agent definition search response. ```csharp public sealed class AgentDefinitionSearchQueryResult ``` | Property | Type | Description | | -------- | ----------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching agent definitions. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## AgentDefinitionSearchQuerySortRequest ```csharp public sealed class AgentDefinitionSearchQuerySortRequest ``` | Property | Type | Description | | -------- | -------------------------------------------- | --------------------------------------------- | | `Field` | `AgentDefinitionSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## AgentDefinitionTypeExactMatch Matches the value exactly. ```csharp public readonly record struct AgentDefinitionTypeExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## AgentDefinitionTypeFilterProperty AgentDefinitionTypeEnum property with full advanced search capabilities. ```csharp public sealed class AgentDefinitionTypeFilterProperty ``` | Property | Type | Description | | ------------ | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AgentHistoryItemKeyExactMatch Matches the value exactly. ```csharp public readonly record struct AgentHistoryItemKeyExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## AgentHistoryItemKeyFilterProperty AgentHistoryItemKey property with full advanced search capabilities. ```csharp public sealed class AgentHistoryItemKeyFilterProperty ``` | Property | Type | Description | | ------------ | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AgentInstanceCreatedHistoryItem The outcome of appending a single history item from an update request's history batch. ```csharp public sealed class AgentInstanceCreatedHistoryItem ``` | Property | Type | Description | | ---------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `HistoryItemId` | `HistoryItemId` | The historyItemId of the corresponding item in the request, echoed back so callers can correlate response entries with request items by id. | | `HistoryItemKey` | `AgentHistoryItemKey` | The system-generated key for the history item. When isDuplicate is true, this is the key of the original entry, not a new one. | | `IsDuplicate` | `Boolean` | True if this item had already been recorded and no new AGENT_HISTORY event was created for it; false if a new event was created. | ## AgentInstanceCreationRequest Request to create a new agent instance. ```csharp public sealed class AgentInstanceCreationRequest ``` | Property | Type | Description | | -------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ElementInstanceKey` | `ElementInstanceKey` | The key of the AI Agent Sub-process or AI Agent Task element instance. The engine uses this key to infer processInstanceKey, elementId, processDefinitionKey, and tenantId. | | `JobKey` | `JobKey` | The key of the job activation during which this creation is being made. A creation must always be attributed to the active job that produced it. | | `JobLeaseToken` | `JobLeaseToken` | Opaque lease token received from the job activation response. Disambiguates this activation from any other activation of the same job: if the job is later retried, history items submitted under a superseded lease are discarded rather than committed. | | `History` | `List` | A batch of history items to append to the agent instance's conversation history, in request order. Each created item is echoed back in the response's createdHistory, positionally correlated. Must include a CONFIGURATION item establishing model, provider, and systemPrompt (and, if needed, limits). Every item's role must be CONFIGURATION or USER, and no item may carry non-zero usage-token metrics (inputTokens, outputTokens, reasoningTokenCount, cacheCreationTokenCount, cacheReadTokenCount); durationMs is exempt and may be non-zero. | ## AgentInstanceCreationResult Response returned after successfully creating an agent instance. ```csharp public sealed class AgentInstanceCreationResult ``` | Property | Type | Description | | ------------------ | --------------------------------------- | ---------------------------------------------------------------------- | | `AgentInstanceKey` | `AgentInstanceKey` | The system-generated key for the created agent instance. | | `CreatedHistory` | `List` | One entry per history item submitted in the request, in request order. | ## AgentInstanceDefinitionResult The definition of an agent instance. Set at creation, but can change later via a CONFIGURATION history item. ```csharp public sealed class AgentInstanceDefinitionResult ``` | Property | Type | Description | | -------------- | ----------------------------------- | ------------------------------------------------------------------------ | | `Model` | `String` | The LLM model identifier (for example, gpt-4o). | | `Provider` | `String` | The LLM provider (for example, openai or anthropic). | | `SystemPrompt` | `List` | The system prompt configured for this agent instance, as content blocks. | ## AgentInstanceDocumentContent A Camunda Document Store reference content block. ```csharp public sealed class AgentInstanceDocumentContent : AgentInstanceMessageContent ``` | Property | Type | Description | | ------------------- | ------------------- | --------------------------------------------------------------- | | `DocumentReference` | `DocumentReference` | A reference to a document stored in the Camunda Document Store. | ## AgentInstanceFilter Agent instance search filter. ```csharp public sealed class AgentInstanceFilter ``` | Property | Type | Description | | ----------------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `AgentInstanceKey` | `AgentInstanceKeyFilterProperty` | The unique key of the agent instance. | | `AgentDefinitionKey` | `AgentDefinitionKeyFilterProperty` | The key of the agent definition this agent instance is an instance of. | | `Status` | `AgentInstanceStatusFilterProperty` | The current status of the agent instance. | | `ElementId` | `ElementIdFilterProperty` | The BPMN element ID of the agent task. | | `ProcessInstanceKey` | `ProcessInstanceKeyFilterProperty` | The key of the process instance that owns this agent instance. | | `RootProcessInstanceKey` | `ProcessInstanceKeyFilterProperty` | The key of the root process instance. Filters agent instances belonging to a specific call hierarchy. The root process instance is the top-level ancestor in the process instance hierarchy. | | `ProcessDefinitionKey` | `ProcessDefinitionKeyFilterProperty` | The key of the process definition associated with this agent instance. | | `TenantId` | `StringFilterProperty` | The tenant ID of the agent instance. | | `CreationDate` | `DateTimeFilterProperty` | The creation date of the agent instance. | | `LastUpdatedDate` | `DateTimeFilterProperty` | The date the agent instance was last updated. | | `CompletionDate` | `DateTimeFilterProperty` | The completion date of the agent instance. | | `ElementInstanceKeys` | `List` | The keys of element instances associated with this agent instance. If multiple keys are provided, the filter matches agent instances associated with all of the provided keys at the same time. | | `ProcessDefinitionId` | `StringFilterProperty` | The BPMN process ID of the process definition associated with this agent instance. | | `ProcessDefinitionVersion` | `IntegerFilterProperty` | The version of the process definition associated with this agent instance. | | `ProcessDefinitionVersionTag` | `StringFilterProperty` | The version tag of the process definition associated with this agent instance. | ## AgentInstanceHistoryCommitStatusExactMatch Matches the value exactly. ```csharp public readonly record struct AgentInstanceHistoryCommitStatusExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## AgentInstanceHistoryCommitStatusFilterProperty AgentInstanceHistoryCommitStatusEnum property with full advanced search capabilities. ```csharp public sealed class AgentInstanceHistoryCommitStatusFilterProperty ``` | Property | Type | Description | | ------------ | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | ## AgentInstanceHistoryFilter Agent instance history item search filter. ```csharp public sealed class AgentInstanceHistoryFilter ``` | Property | Type | Description | | -------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `HistoryItemKey` | `AgentHistoryItemKeyFilterProperty` | The unique key of the history item. | | `Role` | `AgentInstanceHistoryRoleFilterProperty` | The role of the history item. | | `ElementInstanceKey` | `ElementInstanceKeyFilterProperty` | The key of the element instance under which the history item was produced. | | `JobKey` | `JobKeyFilterProperty` | The key of the job activation that produced the history item. | | `LoopIteration` | `IntegerFilterProperty` | Filter by loop iteration number. | | `CommitStatus` | `AgentInstanceHistoryCommitStatusFilterProperty` | The commit status of the history item. Defaults to COMMITTED only. Include PENDING or DISCARDED explicitly to debug in-flight or failed activations. | | `ProducedAt` | `DateTimeFilterProperty` | The timestamp when the history item was produced. | ## AgentInstanceHistoryItem A single history item to append to the agent instance's conversation history, submitted as part of the batch on an agent instance update request. ```csharp public sealed class AgentInstanceHistoryItem ``` | Property | Type | Description | | --------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `HistoryItemId` | `HistoryItemId` | Caller-assigned identifier used to detect and dedupe retries of the same item. For example, when a retried job activation resubmits history items it already sent in an earlier attempt, those items are not rejected; they are flagged via isDuplicate in the response instead. Must be non-blank. | | `LoopIteration` | `LoopIterationId` | The loop iteration this item belongs to. | | `Role` | `AgentInstanceHistoryRoleEnum` | The role of this history item in the conversation. | | `Content` | `List` | The content blocks of this history item. | | `ToolCalls` | `List` | Tool calls associated with this history item. For ASSISTANT items: tool calls dispatched by this LLM response. For TOOL_RESULT items: single-entry array referencing the originating tool call. Omit for USER items. | | `Metrics` | `AgentInstanceHistoryItemMetricsRequest` | Per-call token and latency metrics. Present on ASSISTANT items only. | | `ProducedAt` | `DateTimeOffset` | The agent-side timestamp of when this message was produced. | | `Tools` | `List` | The complete list of tools available to the agent as of this entry. CONFIGURATION items only; omit for other roles. Omit to leave the tool list unchanged; send an empty array to clear it. | | `Model` | `String` | The LLM model identifier as of this entry. CONFIGURATION items only; omit for other roles. | | `Provider` | `String` | The LLM provider as of this entry. CONFIGURATION items only; omit for other roles. | | `Limits` | `AgentInstanceLimits` | The operational limits as of this entry. CONFIGURATION items only; omit for other roles. | | `SystemPrompt` | `List` | The system prompt, as content blocks, as of this entry. CONFIGURATION items only; omit for other roles. Omit to leave the system prompt unchanged; when present, must be non-empty. | ## AgentInstanceHistoryItemMetrics Per-call token and latency metrics for an ASSISTANT history item. ```csharp public sealed class AgentInstanceHistoryItemMetrics ``` | Property | Type | Description | | ------------------------- | ----------------- | ---------------------------------------------------------------------------- | | `InputTokens` | `Nullable` | Input tokens consumed by this LLM call. Null when not provided. | | `OutputTokens` | `Nullable` | Output tokens produced by this LLM call. Null when not provided. | | `ReasoningTokenCount` | `Nullable` | Reasoning tokens consumed by this LLM call. Null when not provided. | | `CacheCreationTokenCount` | `Nullable` | Cache-creation tokens consumed by this LLM call. Null when not provided. | | `CacheReadTokenCount` | `Nullable` | Cache-read tokens consumed by this LLM call. Null when not provided. | | `DurationMs` | `Nullable` | Wall-clock duration of the LLM call in milliseconds. Null when not provided. | ## AgentInstanceHistoryItemMetricsRequest Per-call token and latency metrics for an ASSISTANT history item, as submitted on a create/update request. All fields are optional: omit a field the caller has no value for rather than sending it as an explicit null. ```csharp public sealed class AgentInstanceHistoryItemMetricsRequest ``` | Property | Type | Description | | ------------------------- | ----------------- | ---------------------------------------------------------------------------- | | `InputTokens` | `Nullable` | Input tokens consumed by this LLM call. Null when not provided. | | `OutputTokens` | `Nullable` | Output tokens produced by this LLM call. Null when not provided. | | `ReasoningTokenCount` | `Nullable` | Reasoning tokens consumed by this LLM call. Null when not provided. | | `CacheCreationTokenCount` | `Nullable` | Cache-creation tokens consumed by this LLM call. Null when not provided. | | `CacheReadTokenCount` | `Nullable` | Cache-read tokens consumed by this LLM call. Null when not provided. | | `DurationMs` | `Nullable` | Wall-clock duration of the LLM call in milliseconds. Null when not provided. | ## AgentInstanceHistoryItemResult A single conversation history item belonging to an agent instance. ```csharp public sealed class AgentInstanceHistoryItemResult ``` | Property | Type | Description | | -------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `HistoryItemKey` | `AgentHistoryItemKey` | The unique key for this history item. Stable and sortable by creation order. | | `HistoryItemId` | `HistoryItemId` | The client-supplied identifier this item was created with. Empty for items that don't carry one. Not unique: a job can be re-activated under a superseded lease any number of times before it completes, so one historyItemId can have zero or more DISCARDED records and at most one COMMITTED record, since only historyItemKey is guaranteed unique. Filter by commitStatus rather than assuming one record per historyItemId. | | `AgentInstanceKey` | `AgentInstanceKey` | The key of the agent instance this item belongs to. | | `ElementInstanceKey` | `ElementInstanceKey` | The key of the AI Agent Task or ad-hoc sub-process element instance under which this item was produced. | | `JobKey` | `JobKey` | The key of the job activation during which this item was produced. | | `JobLeaseToken` | `JobLeaseToken` | The lease token of the activation that produced this item. | | `LoopIteration` | `LoopIterationId` | The loop iteration this item belongs to. | | `Role` | `AgentInstanceHistoryRoleEnum` | The role of this history item in the conversation. | | `Content` | `List` | The content blocks of this history item. | | `ToolCalls` | `List` | Tool calls for this item. Empty for USER items and ASSISTANT items with no tool dispatches. ASSISTANT items: dispatched tool calls. TOOL_RESULT items: single-entry array referencing the originating tool call. | | `Metrics` | `AgentInstanceHistoryItemMetrics` | Per-call token and latency metrics. Null when metrics were not provided at creation time. | | `CommitStatus` | `AgentInstanceHistoryCommitStatusEnum` | The commit status of this history item. | | `ProducedAt` | `DateTimeOffset` | The agent-side timestamp of when this message was produced. | | `Tools` | `List` | The complete list of tools available to the agent as of this entry. CONFIGURATION items only; empty for other roles. | | `Model` | `String` | The LLM model identifier as of this entry. CONFIGURATION items only; null for other roles. | | `Provider` | `String` | The LLM provider as of this entry. CONFIGURATION items only; null for other roles. | | `Limits` | `AgentInstanceLimits` | The operational limits as of this entry. CONFIGURATION items only; -1 on any field means "no limit configured" for other roles. | | `SystemPrompt` | `List` | The system prompt, as content blocks, as of this entry. CONFIGURATION items only; empty for other roles. | ## AgentInstanceHistoryRoleExactMatch Matches the value exactly. ```csharp public readonly record struct AgentInstanceHistoryRoleExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## AgentInstanceHistoryRoleFilterProperty AgentInstanceHistoryRoleEnum property with full advanced search capabilities. ```csharp public sealed class AgentInstanceHistoryRoleFilterProperty ``` | Property | Type | Description | | ------------ | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | ## AgentInstanceHistorySearchQuery Agent instance history search request. ```csharp public sealed class AgentInstanceHistorySearchQuery ``` | Property | Type | Description | | -------- | -------------------------------------------------- | -------------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `AgentInstanceHistoryFilter` | The history item search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## AgentInstanceHistorySearchQueryResult Agent instance history search response. ```csharp public sealed class AgentInstanceHistorySearchQueryResult ``` | Property | Type | Description | | -------- | -------------------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching history items. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## AgentInstanceHistorySearchQuerySortRequest ```csharp public sealed class AgentInstanceHistorySearchQuerySortRequest ``` | Property | Type | Description | | -------- | ------------------------------------------------- | --------------------------------------------- | | `Field` | `AgentInstanceHistorySearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## AgentInstanceKeyExactMatch Matches the value exactly. ```csharp public readonly record struct AgentInstanceKeyExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## AgentInstanceKeyFilterProperty AgentInstanceKey property with full advanced search capabilities. ```csharp public sealed class AgentInstanceKeyFilterProperty ``` | Property | Type | Description | | ------------ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AgentInstanceLimits The configured limits for an agent instance, set once at creation. ```csharp public sealed class AgentInstanceLimits ``` | Property | Type | Description | | --------------- | ------- | ----------------------------------------------------------- | | `MaxModelCalls` | `Int32` | Maximum LLM calls allowed. -1 if no limit is configured. | | `MaxToolCalls` | `Int32` | Maximum tool calls allowed. -1 if no limit is configured. | | `MaxTokens` | `Int64` | Maximum total tokens allowed. -1 if no limit is configured. | ## AgentInstanceMessageContent A single content block within a history item. Discriminated by `contentType`. ```csharp public abstract class AgentInstanceMessageContent ``` ## AgentInstanceMetrics Aggregated metrics for an agent instance across all model calls. ```csharp public sealed class AgentInstanceMetrics ``` | Property | Type | Description | | ------------------------- | ------- | ------------------------------------------------------------------------ | | `InputTokens` | `Int64` | Total input tokens consumed across all model calls. | | `OutputTokens` | `Int64` | Total output tokens produced across all model calls. | | `ReasoningTokenCount` | `Int64` | Total reasoning tokens consumed across all model calls. | | `CacheCreationTokenCount` | `Int64` | Total tokens used to create prompt cache entries across all model calls. | | `CacheReadTokenCount` | `Int64` | Total tokens read from prompt cache across all model calls. | | `ModelCalls` | `Int32` | Total number of LLM calls made. | | `ToolCalls` | `Int32` | Total number of tool calls made. | ## AgentInstanceObjectContent An arbitrary structured content block. Accepts any valid JSON value: objects, arrays, numbers, booleans, or strings. Use TEXT content for human-readable natural language; use OBJECT content for machine-readable structured data. ```csharp public sealed class AgentInstanceObjectContent : AgentInstanceMessageContent ``` | Property | Type | Description | | -------- | -------- | ------------------------------------------------------------------------------------------------ | | `Object` | `Object` | Arbitrary structured content — any valid JSON value (object, array, number, boolean, or string). | ## AgentInstanceResult ```csharp public sealed class AgentInstanceResult ``` | Property | Type | Description | | ----------------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `AgentInstanceKey` | `AgentInstanceKey` | The unique key for this agent instance. | | `AgentDefinitionKey` | `AgentDefinitionKey` | The key of the agent definition this agent instance is an instance of. | | `Status` | `AgentInstanceStatusEnum` | The current status of an agent instance. | | `Definition` | `AgentInstanceDefinitionResult` | The definition of the agent, including model, provider, and system prompt. Set at creation, but can change later via a CONFIGURATION history item. | | `Metrics` | `AgentInstanceMetrics` | Aggregated metrics across all loopIterations of this agent instance. Includes history items later discarded: metrics are counted when an item is accepted, not when it's committed. | | `Limits` | `AgentInstanceLimits` | The configured limits for this agent instance, set once at creation. | | `Tools` | `List` | The tools available to the agent. | | `ElementId` | `ElementId` | The BPMN element ID of the ad-hoc sub-process or AI agent task that owns this agent instance. | | `ProcessInstanceKey` | `ProcessInstanceKey` | The key of the process instance that owns this agent instance. | | `RootProcessInstanceKey` | `ProcessInstanceKey` | The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. | | `ProcessDefinitionKey` | `ProcessDefinitionKey` | The key of the process definition associated with this agent instance. | | `ProcessDefinitionId` | `ProcessDefinitionId` | The BPMN process ID of the process definition associated with this agent instance. | | `ProcessDefinitionVersion` | `Int32` | The version of the process definition associated with this agent instance. | | `ProcessDefinitionVersionTag` | `String` | The version tag of the process definition associated with this agent instance. | | `TenantId` | `TenantId` | The tenant ID of this agent instance. | | `CreationDate` | `DateTimeOffset` | The date when this agent instance was created. | | `LastUpdatedDate` | `DateTimeOffset` | The date when this agent instance was last updated. | | `CompletionDate` | `Nullable` | The date when this agent instance completed. Null while the agent is still running. | | `ElementInstanceKeys` | `List` | The keys of all element instances associated with this agent instance. | ## AgentInstanceSearchQuery Agent instance search request. ```csharp public sealed class AgentInstanceSearchQuery ``` | Property | Type | Description | | -------- | ------------------------------------------- | ---------------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `AgentInstanceFilter` | The agent instance search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## AgentInstanceSearchQueryResult Agent instance search response. ```csharp public sealed class AgentInstanceSearchQueryResult ``` | Property | Type | Description | | -------- | --------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching agent instances. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## AgentInstanceSearchQuerySortRequest ```csharp public sealed class AgentInstanceSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ------------------------------------------ | --------------------------------------------- | | `Field` | `AgentInstanceSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## AgentInstanceStatusExactMatch Matches the value exactly. ```csharp public readonly record struct AgentInstanceStatusExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## AgentInstanceStatusFilterProperty AgentInstanceStatusEnum property with full advanced search capabilities. ```csharp public sealed class AgentInstanceStatusFilterProperty ``` | Property | Type | Description | | ------------ | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AgentInstanceTextContent A plain-text content block. ```csharp public sealed class AgentInstanceTextContent : AgentInstanceMessageContent ``` | Property | Type | Description | | -------- | -------- | ----------------- | | `Text` | `String` | The text content. | ## AgentInstanceToolCall A tool call associated with a history item. Used in both ASSISTANT and TOOL_RESULT items. ```csharp public sealed class AgentInstanceToolCall ``` | Property | Type | Description | | ------------ | --------------------- | ------------------------------------------------------------------------------------------------------------ | | `ToolCallId` | `String` | The LLM-assigned tool call ID. Correlates ASSISTANT items to their matching TOOL_RESULT items. | | `ToolName` | `String` | The LLM-visible tool name. | | `ElementId` | `Nullable` | The BPMN element ID handling this tool. | | `Arguments` | `Object` | The tool call arguments as provided by the LLM. May be null or populated on any item, including TOOL_RESULT. | ## AgentInstanceUpdateRequest Request to update the mutable state of an agent instance. ```csharp public sealed class AgentInstanceUpdateRequest ``` | Property | Type | Description | | -------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ElementInstanceKey` | `ElementInstanceKey` | The key of the currently-active element instance for this agent instance. Used for ownership/equality validation against the stored agent instance and, when the supplied key differs from the previous association (re-entry of an ad-hoc sub-process or AI Agent task), appended to elementInstanceKeys with the reverse link updated on the supplied element instance. Only one element instance may hold this write claim at a time: any update from a different element instance is rejected while the current writer's job is still active. | | `Status` | `Nullable` | The new status of the agent instance. | | `JobKey` | `JobKey` | The key of the job activation during which this update is being made. An update must always be attributed to the active job that produced it. | | `JobLeaseToken` | `JobLeaseToken` | Opaque lease token received from the job activation response. Disambiguates this activation from any other activation of the same job: if the job is later retried, history items submitted under a superseded lease are discarded rather than committed. | | `History` | `List` | A batch of history items to append to the agent instance's conversation history, in request order. Each created item is echoed back in the response's createdHistory, positionally correlated. | ## AgentInstanceUpdateResult Response returned after successfully updating an agent instance. ```csharp public sealed class AgentInstanceUpdateResult ``` | Property | Type | Description | | ---------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | `CreatedHistory` | `List` | One entry per history item submitted in the request, in request order. Empty when no history items were submitted. | ## AgentTool A tool available to the agent. ```csharp public sealed class AgentTool ``` | Property | Type | Description | | ------------- | --------------------- | ---------------------------------------------------------------------- | | `Name` | `String` | The tool name as visible to the LLM. | | `Description` | `String` | A human-readable description of the tool. | | `ElementId` | `Nullable` | The BPMN element ID of the tool element within the ad-hoc sub-process. | ## AncestorScopeInstruction Defines the ancestor scope for the created element instances. The default behavior resembles a "direct" scope instruction with an `ancestorElementInstanceKey` of `"-1"`. ```csharp public abstract class AncestorScopeInstruction ``` ## AuditLogActorTypeExactMatch Matches the value exactly. ```csharp public readonly record struct AuditLogActorTypeExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## AuditLogActorTypeFilterProperty AuditLogActorTypeEnum property with full advanced search capabilities. ```csharp public sealed class AuditLogActorTypeFilterProperty ``` | Property | Type | Description | | ------------ | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AuditLogEntityKeyExactMatch Matches the value exactly. ```csharp public readonly record struct AuditLogEntityKeyExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## AuditLogEntityKeyFilterProperty EntityKey property with full advanced search capabilities. ```csharp public sealed class AuditLogEntityKeyFilterProperty ``` | Property | Type | Description | | ------------ | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AuditLogFilter Audit log filter request ```csharp public sealed class AuditLogFilter ``` | Property | Type | Description | | ------------------------- | --------------------------------------- | -------------------------------------------------- | | `AuditLogKey` | `AuditLogKeyFilterProperty` | The audit log key search filter. | | `ProcessDefinitionKey` | `ProcessDefinitionKeyFilterProperty` | The process definition key search filter. | | `ProcessInstanceKey` | `ProcessInstanceKeyFilterProperty` | The process instance key search filter. | | `ElementInstanceKey` | `ElementInstanceKeyFilterProperty` | The element instance key search filter. | | `OperationType` | `OperationTypeFilterProperty` | The operation type search filter. | | `Result` | `AuditLogResultFilterProperty` | The result search filter. | | `Timestamp` | `DateTimeFilterProperty` | The timestamp search filter. | | `ActorId` | `StringFilterProperty` | The actor ID search filter. | | `ActorType` | `AuditLogActorTypeFilterProperty` | The actor type search filter. | | `AgentElementId` | `StringFilterProperty` | The agent element ID search filter. | | `EntityKey` | `AuditLogEntityKeyFilterProperty` | The entity key search filter. | | `EntityType` | `EntityTypeFilterProperty` | The entity type search filter. | | `TenantId` | `StringFilterProperty` | The tenant ID search filter. | | `Category` | `CategoryFilterProperty` | The category search filter. | | `DeploymentKey` | `DeploymentKeyFilterProperty` | The deployment key search filter. | | `FormKey` | `FormKeyFilterProperty` | The form key search filter. | | `ResourceKey` | `ResourceKeyFilterProperty` | The resource key search filter. | | `BatchOperationType` | `BatchOperationTypeFilterProperty` | The batch operation type search filter. | | `ProcessDefinitionId` | `StringFilterProperty` | The process definition ID search filter. | | `JobKey` | `JobKeyFilterProperty` | The job key search filter. | | `UserTaskKey` | `BasicStringFilterProperty` | The user task key search filter. | | `DecisionRequirementsId` | `StringFilterProperty` | The decision requirements ID search filter. | | `DecisionRequirementsKey` | `DecisionRequirementsKeyFilterProperty` | The decision requirements key search filter. | | `DecisionDefinitionId` | `StringFilterProperty` | The decision definition ID search filter. | | `DecisionDefinitionKey` | `DecisionDefinitionKeyFilterProperty` | The decision definition key search filter. | | `DecisionEvaluationKey` | `DecisionEvaluationKeyFilterProperty` | The decision evaluation key search filter. | | `RelatedEntityKey` | `AuditLogEntityKeyFilterProperty` | The related entity key search filter. | | `RelatedEntityType` | `EntityTypeFilterProperty` | The related entity type search filter. | | `EntityDescription` | `StringFilterProperty` | The entity description filter. | | `InboundChannelType` | `StringFilterProperty` | The inbound channel type search filter (e.g. MCP). | | `InboundChannelToolName` | `StringFilterProperty` | The inbound channel tool name search filter. | ## AuditLogKeyExactMatch Matches the value exactly. ```csharp public readonly record struct AuditLogKeyExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## AuditLogKeyFilterProperty AuditLogKey property with full advanced search capabilities. ```csharp public sealed class AuditLogKeyFilterProperty ``` | Property | Type | Description | | ------------ | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## AuditLogResult Audit log item. ```csharp public sealed class AuditLogResult ``` | Property | Type | Description | | ------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `AuditLogKey` | `AuditLogKey` | The unique key of the audit log entry. | | `EntityKey` | `AuditLogEntityKey` | System-generated entity key for an audit log entry. | | `EntityType` | `AuditLogEntityTypeEnum` | The type of entity affected by the operation. | | `OperationType` | `AuditLogOperationTypeEnum` | The type of operation performed. | | `BatchOperationKey` | `Nullable` | Key of the batch operation. | | `BatchOperationType` | `Nullable` | The type of batch operation performed, if this is part of a batch. | | `Timestamp` | `DateTimeOffset` | The timestamp when the operation occurred. | | `ActorId` | `String` | The ID of the actor who performed the operation. | | `ActorType` | `Nullable` | The type of the actor who performed the operation. | | `AgentElementId` | `String` | The element ID of the agent that performed the operation (e.g. ad-hoc subprocess element ID). | | `TenantId` | `Nullable` | The tenant ID of the audit log. | | `Result` | `AuditLogResultEnum` | The result status of the operation. | | `Category` | `AuditLogCategoryEnum` | The category of the audit log operation. | | `ProcessDefinitionId` | `Nullable` | The process definition ID. | | `ProcessDefinitionKey` | `Nullable` | The key of the process definition. | | `ProcessInstanceKey` | `Nullable` | The key of the process instance. | | `RootProcessInstanceKey` | `Nullable` | The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. | | `ElementInstanceKey` | `Nullable` | The key of the element instance. | | `JobKey` | `Nullable` | The key of the job. | | `UserTaskKey` | `Nullable` | The key of the user task. | | `DecisionRequirementsId` | `String` | The decision requirements ID. | | `DecisionRequirementsKey` | `Nullable` | The assigned key of the decision requirements. | | `DecisionDefinitionId` | `Nullable` | The decision definition ID. | | `DecisionDefinitionKey` | `Nullable` | The key of the decision definition. | | `DecisionEvaluationKey` | `Nullable` | The key of the decision evaluation. | | `DeploymentKey` | `Nullable` | The key of the deployment. | | `FormKey` | `Nullable` | The key of the form. | | `ResourceKey` | `Nullable` | The system-assigned key for this resource. | | `RelatedEntityKey` | `Nullable` | The key of the related entity. The content depends on the operation type and entity type. For example, for authorization operations, this will contain the ID of the owner (e.g., user or group) the authorization belongs to. | | `RelatedEntityType` | `Nullable` | The type of the related entity. The content depends on the operation type and entity type. For example, for authorization operations, this will contain the type of the owner (e.g., USER or GROUP) the authorization belongs to. | | `EntityDescription` | `String` | Additional description of the entity affected by the operation. For example, for variable operations, this will contain the variable name. | | `InboundChannelType` | `String` | The type of the inbound channel that triggered the operation (e.g. MCP). | | `InboundChannelToolName` | `String` | The tool name of the inbound channel (e.g. the MCP tool that triggered the operation). | ## AuditLogResultExactMatch Matches the value exactly. ```csharp public readonly record struct AuditLogResultExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## AuditLogResultFilterProperty AuditLogResultEnum property with full advanced search capabilities. ```csharp public sealed class AuditLogResultFilterProperty ``` | Property | Type | Description | | ------------ | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## AuditLogSearchQueryRequest Audit log search request. ```csharp public sealed class AuditLogSearchQueryRequest ``` | Property | Type | Description | | -------- | -------------------------------------- | ----------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `AuditLogFilter` | The audit log search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## AuditLogSearchQueryResult Audit log search response. ```csharp public sealed class AuditLogSearchQueryResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching audit logs. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## AuditLogSearchQuerySortRequest ```csharp public sealed class AuditLogSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ------------------------------------- | --------------------------------------------- | | `Field` | `AuditLogSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## AuthenticationConfigurationResponse Configuration for authentication and session management. ```csharp public sealed class AuthenticationConfigurationResponse ``` | Property | Type | Description | | ------------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------- | | `CanLogout` | `Boolean` | Whether users can log out (false for SaaS deployments). | | `IsLoginDelegated` | `Boolean` | Whether login is delegated to an external identity provider. | | `IsCamundaGroupsEnabled` | `Boolean` | Whether Camunda manages group membership locally, as opposed to delegating it to the identity provider via a groups claim. | ## AuthorizationCreateResult ```csharp public sealed class AuthorizationCreateResult ``` | Property | Type | Description | | ------------------ | ------------------ | ------------------------------------- | | `AuthorizationKey` | `AuthorizationKey` | The key of the created authorization. | ## AuthorizationFilter Authorization search filter. ```csharp public sealed class AuthorizationFilter ``` | Property | Type | Description | | ----------------------- | ---------------------------- | --------------------------------------------------------------- | | `OwnerId` | `String` | The ID of the owner of permissions. | | `OwnerType` | `Nullable` | The type of the owner of permissions. | | `ResourceIds` | `List` | The IDs of the resource to search permissions for. | | `ResourcePropertyNames` | `List` | The names of the resource properties to search permissions for. | | `ResourceType` | `Nullable` | The type of resource to search permissions for. | ## AuthorizationIdBasedRequest ```csharp public sealed class AuthorizationIdBasedRequest : AuthorizationRequest ``` | Property | Type | Description | | ----------------- | -------------------------- | --------------------------------------------- | | `OwnerId` | `String` | The ID of the owner of the permissions. | | `OwnerType` | `OwnerTypeEnum` | The type of the owner of permissions. | | `ResourceId` | `String` | The ID of the resource to add permissions to. | | `ResourceType` | `ResourceTypeEnum` | The type of resource to add permissions to. | | `PermissionTypes` | `List` | The permission types to add. | ## AuthorizationPropertyBasedRequest ```csharp public sealed class AuthorizationPropertyBasedRequest : AuthorizationRequest ``` | Property | Type | Description | | ---------------------- | -------------------------- | ----------------------------------------------------------------------- | | `OwnerId` | `String` | The ID of the owner of the permissions. | | `OwnerType` | `OwnerTypeEnum` | The type of the owner of permissions. | | `ResourcePropertyName` | `String` | The name of the resource property on which this authorization is based. | | `ResourceType` | `ResourceTypeEnum` | The type of resource to add permissions to. | | `PermissionTypes` | `List` | The permission types to add. | ## AuthorizationRequest Defines an authorization request. Either an id-based or a property-based authorization can be provided. ```csharp public abstract class AuthorizationRequest ``` ## AuthorizationResult ```csharp public sealed class AuthorizationResult ``` | Property | Type | Description | | ---------------------- | -------------------------- | --------------------------------------------------------------------------------------------------- | | `OwnerId` | `String` | The ID of the owner of permissions. | | `OwnerType` | `OwnerTypeEnum` | The type of the owner of permissions. | | `ResourceType` | `ResourceTypeEnum` | The type of resource that the permissions relate to. | | `ResourceId` | `String` | ID of the resource the permission relates to (mutually exclusive with `resourcePropertyName`). | | `ResourcePropertyName` | `String` | The name of the resource property the permission relates to (mutually exclusive with `resourceId`). | | `PermissionTypes` | `List` | Specifies the types of the permissions. | | `AuthorizationKey` | `AuthorizationKey` | The key of the authorization. | ## AuthorizationSearchQuery ```csharp public sealed class AuthorizationSearchQuery ``` | Property | Type | Description | | -------- | ------------------------------------------- | --------------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `AuthorizationFilter` | The authorization search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## AuthorizationSearchQuerySortRequest ```csharp public sealed class AuthorizationSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ------------------------------------------ | --------------------------------------------- | | `Field` | `AuthorizationSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## AuthorizationSearchResult ```csharp public sealed class AuthorizationSearchResult ``` | Property | Type | Description | | -------- | --------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching authorizations. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## BackpressureState ```csharp public sealed class BackpressureState ``` | Property | Type | Description | | ------------- | ----------------- | ----------- | | `Severity` | `String` | | | `PermitsMax` | `Nullable` | | | `Consecutive` | `Int32` | | ## BackupId The id of the backup. Must be a positive numerical value. As backups are logically ordered by their ids (ascending), each successive backup must use a higher id than the previous one. ```csharp public readonly record struct BackupId : ICamundaLongKey, IEquatable ``` | Property | Type | Description | | -------- | ------- | -------------------------- | | `Value` | `Int64` | The underlying long value. | ## BackupIdPrefix A prefix of a backup id, followed by a single '*' as a wildcard, matching any backup id starting with the given prefix. ```csharp public readonly record struct BackupIdPrefix : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## BackupInfo Detailed status of a runtime backup. The aggregated state is computed from the backup state of each partition as: - If the backup of all partitions is 'COMPLETED', the overall state is 'COMPLETED'. - If one partition is 'FAILED', the overall state is 'FAILED'. - Otherwise, if one partition is 'DOES_NOT_EXIST', the overall state is 'INCOMPLETE'. - Otherwise, if one partition is 'IN_PROGRESS', the overall state is 'IN_PROGRESS'. ```csharp public sealed class BackupInfo ``` | Property | Type | Description | | --------------- | --------------------------- | ---------------------------------------------------------------------------------------------------- | | `BackupId` | `BackupId` | The id of the backup. | | `State` | `StateCode` | The aggregated state of the backup. | | `FailureReason` | `String` | Reason for failure if the state is 'FAILED'. | | `Details` | `List` | Detailed status of the backup per partition. Always contains every partition of the physical tenant. | ## BaseProcessInstanceFilterFields Base process instance search filter. ```csharp public sealed class BaseProcessInstanceFilterFields ``` | Property | Type | Description | | ---------------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `StartDate` | `DateTimeFilterProperty` | The start date. | | `EndDate` | `DateTimeFilterProperty` | The end date. | | `State` | `ProcessInstanceStateFilterProperty` | The process instance state. | | `HasIncident` | `Nullable` | Whether this process instance has a related incident or not. | | `SuspendedDate` | `DateTimeFilterProperty` | The time this process instance most recently entered the SUSPENDED state. This is cleared (null) again once the process instance is resumed. | | `TenantId` | `StringFilterProperty` | The tenant id. | | `Variables` | `List` | The process instance variables. | | `ProcessInstanceKey` | `ProcessInstanceKeyFilterProperty` | The key of this process instance. | | `ParentProcessInstanceKey` | `ProcessInstanceKeyFilterProperty` | The parent process instance key. | | `ParentElementInstanceKey` | `ElementInstanceKeyFilterProperty` | The parent element instance key. | | `BatchOperationId` | `StringFilterProperty` | The batch operation id. **Deprecated**: Use `batchOperationKey` instead. This field will be removed in a future release. If both `batchOperationId` and `batchOperationKey` are provided, the request will be rejected with a 400 error. | | `BatchOperationKey` | `StringFilterProperty` | The batch operation key. | | `ErrorMessage` | `StringFilterProperty` | The error message related to the process. | | `HasRetriesLeft` | `Nullable` | Whether the process has failed jobs with retries left. | | `ElementInstanceState` | `ElementInstanceStateFilterProperty` | The state of the element instances associated with the process instance. | | `ElementId` | `StringFilterProperty` | The element id associated with the process instance. | | `HasElementInstanceIncident` | `Nullable` | Whether the element instance has an incident or not. | | `IncidentErrorHashCode` | `IntegerFilterProperty` | The incident error hash code, associated with this process. | | `Tags` | `List` | List of tags. Tags need to start with a letter; then alphanumerics, `_`, `-`, `:`, or `.`; length ≤ 100. | | `BusinessId` | `StringFilterProperty` | The business id associated with the process instance. | ## BasicStringFilter Basic advanced string filter. ```csharp public sealed class BasicStringFilter ``` | Property | Type | Description | | -------- | ------------------- | ----------------------------------------------------------- | | `Eq` | `String` | Checks for equality with the provided value. | | `Neq` | `String` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## BasicStringFilterProperty String property with basic advanced search capabilities. ```csharp public sealed class BasicStringFilterProperty ``` | Property | Type | Description | | ------------ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `String` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `String` | Checks for equality with the provided value. | | `Neq` | `String` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## BatchOperationCreatedResult The created batch operation. ```csharp public sealed class BatchOperationCreatedResult ``` | Property | Type | Description | | -------------------- | ------------------------ | -------------------------------- | | `BatchOperationKey` | `BatchOperationKey` | Key of the batch operation. | | `BatchOperationType` | `BatchOperationTypeEnum` | The type of the batch operation. | ## BatchOperationError ```csharp public sealed class BatchOperationError ``` | Property | Type | Description | | ------------- | ------------------------- | --------------------------------------------------------------- | | `PartitionId` | `Int32` | The partition ID where the error occurred. | | `Type` | `BatchOperationErrorType` | The type of the error that occurred during the batch operation. | | `Message` | `String` | The error message that occurred during the batch operation. | ## BatchOperationFilter Batch operation filter request. ```csharp public sealed class BatchOperationFilter ``` | Property | Type | Description | | ------------------- | ----------------------------------- | ------------------------------------------------------ | | `BatchOperationKey` | `BasicStringFilterProperty` | The key (or operate legacy ID) of the batch operation. | | `OperationType` | `BatchOperationTypeFilterProperty` | The type of the batch operation. | | `State` | `BatchOperationStateFilterProperty` | The state of the batch operation. | | `ActorType` | `Nullable` | The type of the actor who performed the operation. | | `ActorId` | `StringFilterProperty` | The ID of the actor who performed the operation. | ## BatchOperationItemFilter Batch operation item filter request. ```csharp public sealed class BatchOperationItemFilter ``` | Property | Type | Description | | -------------------- | ---------------------------------- | ------------------------------------------------------ | | `BatchOperationKey` | `BasicStringFilterProperty` | The key (or operate legacy ID) of the batch operation. | | `ItemKey` | `BasicStringFilterProperty` | The key of the item, e.g. a process instance key. | | `ProcessInstanceKey` | `ProcessInstanceKeyFilterProperty` | The process instance key of the processed item. | | `State` | `String` | The state of the batch operation. | | `OperationType` | `BatchOperationTypeFilterProperty` | The type of the batch operation. | ## BatchOperationItemResponse ```csharp public sealed class BatchOperationItemResponse ``` | Property | Type | Description | | ------------------------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `OperationType` | `BatchOperationTypeEnum` | The type of the batch operation. | | `BatchOperationKey` | `BatchOperationKey` | The key (or operate legacy ID) of the batch operation. | | `ItemKey` | `String` | Key of the item, e.g. a process instance key. | | `ProcessInstanceKey` | `Nullable` | The process instance key of the processed item. Null for batch-op types whose targets are not process instances (e.g. DELETE_DECISION_INSTANCE, DELETE_DECISION_DEFINITION, DELETE_PROCESS_DEFINITION). | | `RootProcessInstanceKey` | `Nullable` | The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. | | `State` | `BatchOperationItemResponseState` | State of the item. | | `ProcessedDate` | `Nullable` | The date this item was processed. This is `null` if the item has not yet been processed. | | `ErrorMessage` | `String` | The error message from the engine in case of a failed operation. | ## BatchOperationItemSearchQuery Batch operation item search request. ```csharp public sealed class BatchOperationItemSearchQuery ``` | Property | Type | Description | | -------- | ------------------------------------------------ | ---------------------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `BatchOperationItemFilter` | The batch operation item search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## BatchOperationItemSearchQueryResult ```csharp public sealed class BatchOperationItemSearchQueryResult ``` | Property | Type | Description | | -------- | ---------------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching batch operation items. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## BatchOperationItemSearchQuerySortRequest ```csharp public sealed class BatchOperationItemSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ----------------------------------------------- | --------------------------------------------- | | `Field` | `BatchOperationItemSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## BatchOperationItemStateExactMatch Matches the value exactly. ```csharp public readonly record struct BatchOperationItemStateExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## BatchOperationItemStateFilterProperty BatchOperationItemStateEnum property with full advanced search capabilities. ```csharp public sealed class BatchOperationItemStateFilterProperty ``` | Property | Type | Description | | ------------ | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## BatchOperationResponse ```csharp public sealed class BatchOperationResponse ``` | Property | Type | Description | | -------------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `BatchOperationKey` | `BatchOperationKey` | Key or (Operate Legacy ID = UUID) of the batch operation. | | `State` | `BatchOperationStateEnum` | The batch operation state. | | `BatchOperationType` | `Nullable` | The type of the batch operation. This is `null` for batch operations whose type was never recorded in secondary storage, such as legacy Operate batch operations. | | `StartDate` | `Nullable` | The start date of the batch operation. This is `null` if the batch operation has not yet started. | | `EndDate` | `Nullable` | The end date of the batch operation. This is `null` if the batch operation is still running. | | `ActorType` | `Nullable` | The type of the actor who performed the operation. This is `null` if the batch operation was created before 8.9, or if the actor information is not available. | | `ActorId` | `String` | The ID of the actor who performed the operation. Available for batch operations created since 8.9. | | `OperationsTotalCount` | `Int32` | The total number of items contained in this batch operation. | | `OperationsFailedCount` | `Int32` | The number of items which failed during execution of the batch operation. (e.g. because they are rejected by the Zeebe engine). | | `OperationsCompletedCount` | `Int32` | The number of successfully completed tasks. | | `Errors` | `List` | The errors that occurred per partition during the batch operation. | ## BatchOperationSearchQuery Batch operation search request. ```csharp public sealed class BatchOperationSearchQuery ``` | Property | Type | Description | | -------- | -------------------------------------------- | ----------------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `BatchOperationFilter` | The batch operation search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## BatchOperationSearchQueryResult The batch operation search query result. ```csharp public sealed class BatchOperationSearchQueryResult ``` | Property | Type | Description | | -------- | ------------------------------ | ------------------------------------------------ | | `Items` | `List` | The matching batch operations. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## BatchOperationSearchQuerySortRequest ```csharp public sealed class BatchOperationSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ------------------------------------------- | --------------------------------------------- | | `Field` | `BatchOperationSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## BatchOperationStateExactMatch Matches the value exactly. ```csharp public readonly record struct BatchOperationStateExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## BatchOperationStateFilterProperty BatchOperationStateEnum property with full advanced search capabilities. ```csharp public sealed class BatchOperationStateFilterProperty ``` | Property | Type | Description | | ------------ | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## BatchOperationTypeExactMatch Matches the value exactly. ```csharp public readonly record struct BatchOperationTypeExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## BatchOperationTypeFilterProperty BatchOperationTypeEnum property with full advanced search capabilities. ```csharp public sealed class BatchOperationTypeFilterProperty ``` | Property | Type | Description | | ------------ | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## BpmnErrorException Throw from a job handler to trigger a BPMN error boundary event on the job's task. The error code is matched against error catch events in the process model. ```csharp public sealed class BpmnErrorException : Exception, ISerializable ``` | Property | Type | Description | | -------------- | -------- | --------------------------------------------------------- | | `ErrorCode` | `String` | The error code matched against BPMN error catch events. | | `ErrorMessage` | `String` | Optional additional context message. | | `Variables` | `Object` | Optional variables to set at the error catch event scope. | ## BrokerInfo Provides information on a broker node. ```csharp public sealed class BrokerInfo ``` | Property | Type | Description | | ------------ | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `NodeId` | `Int32` | The node ID for the broker. The uniqueness of this identifier depends if the cluster is zone-aware or not. - non zone-aware: (default) nodeId is unique across the cluster - zone-aware: (opt-in) nodeId is unique only within its zone. If you are migrating to a zone aware cluster, you must use `brokerId` instead. This property is deprecated, as it's been replaced by `brokerId`. | | `BrokerId` | `String` | The unique (within a cluster) broker identifier. When the cluster is not zoned, then it's a string that represents the nodeId (an integer). When the cluster is zoned, instead, it's of the form "$zoneName_$nodeId", providing uniqueness even across zones. | | `Host` | `String` | The hostname for reaching the broker. | | `Port` | `Int32` | The port for reaching the broker. | | `Partitions` | `List` | A list of partitions managed or replicated on this broker. | | `Version` | `String` | The broker version. | ## BusinessId An optional, user-defined string identifier that identifies the process instance within the scope of a process definition (scoped by tenant). If provided and uniqueness enforcement is enabled, the engine will reject creation if another root process instance with the same business id is already active for the same process definition. Note that any active child process instances with the same business id are not taken into account. ```csharp public readonly record struct BusinessId : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## CamundaAuthException Authentication-specific exception. ```csharp public sealed class CamundaAuthException : Exception, ISerializable ``` | Property | Type | Description | | -------- | ---------------------- | ----------- | | `Code` | `CamundaAuthErrorCode` | | ## CamundaConfigurationException Thrown when configuration hydration encounters validation errors. ```csharp public sealed class CamundaConfigurationException : Exception, ISerializable ``` | Property | Type | Description | | -------- | ---------------------------------- | ----------- | | `Errors` | `IReadOnlyList` | | ## CamundaKeyJsonConverterFactory JSON converter factory that handles any `ICamundaKey` struct. Serializes as a plain JSON string; deserializes by calling the static AssumeExists factory. ```csharp public sealed class CamundaKeyJsonConverterFactory : JsonConverterFactory ``` ## CamundaKeyValidation Validation helpers for domain key constraints. ```csharp public static class CamundaKeyValidation ``` ## CamundaLongKeyJsonConverterFactory JSON converter factory that handles any `ICamundaLongKey` struct. Serializes as a JSON number; deserializes by calling the static AssumeExists factory. ```csharp public sealed class CamundaLongKeyJsonConverterFactory : JsonConverterFactory ``` ## CamundaRandomSource The live `IRandomSource`: the platform's shared, unseeded generator. This is the one place in the SDK runtime allowed to read ambient randomness. Production jitter stays random so that a fleet of workers does not retry or start in lockstep. ```csharp public sealed class CamundaRandomSource : IRandomSource ``` | Property | Type | Description | | -------- | --------------------- | ----------------------------------- | | `Live` | `CamundaRandomSource` | The default live randomness source. | ## CamundaSdkException SDK error types mirroring the JS SDK's error structure. ```csharp public class CamundaSdkException : Exception, ISerializable ``` | Property | Type | Description | | ------------- | ----------------- | ----------- | | `OperationId` | `String` | | | `Status` | `Nullable` | | ## CamundaTimeProvider A `TimeProvider` decorator whose `CamundaTimeProvider.GetUtcNow` never moves backwards. The SDK resolves all runtime cadence — worker poll loops, eventual consistency polling, retry backoff, backpressure decay, and OAuth refresh — through wall-clock time rather than a monotonic source, so that pinning the clock in a test also pins the client's own timing. Wall clocks can jump backwards (NTP correction, VM suspend and resume, manual adjustment), and a deadline computed against a backwards-moving clock waits longer than it was asked to. A backward step is absorbed and then paid back gradually out of forward progress, so readings never decrease, keep advancing immediately after a jump, and converge back to the underlying clock rather than staying ahead of it forever. This decorator wraps the live clock only. A test clock such as `FakeTimeProvider` is used as supplied, so a test remains free to move time backwards deliberately. ```csharp public sealed class CamundaTimeProvider : TimeProvider ``` | Property | Type | Description | | -------------------- | --------------------- | ------------------------------------------------------------------------------------ | | `Live` | `CamundaTimeProvider` | The default live clock: the system clock, made non-decreasing. | | `LocalTimeZone` | `TimeZoneInfo` | Gets the local time zone according to this `TimeProvider`'s notion of time. | | `TimestampFrequency` | `Int64` | Gets the frequency of `TimeProvider.GetTimestamp` as the number of ticks per second. | ## CamundaUserResult ```csharp public sealed class CamundaUserResult ``` | Property | Type | Description | | ---------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Username` | `Username` | The username of the user. | | `DisplayName` | `String` | The display name of the user. | | `Email` | `String` | The email of the user. | | `AuthorizedComponents` | `List` | The web components the user is authorized to use. When authorizations are disabled for the cluster, this always returns `["*"]`, regardless of the user's actual permissions, since access is not restricted in that case. | | `Tenants` | `List` | The tenants the user is a member of. | | `Groups` | `List` | The groups assigned to the user. | | `Roles` | `List` | The roles assigned to the user. | | `SalesPlanType` | `String` | The plan of the user. | | `C8Links` | `Dictionary` | The links to the components in the C8 stack. | | `CanLogout` | `Boolean` | Flag for understanding if the user is able to perform logout. | ## CancelProcessInstanceRequest ```csharp public sealed class CancelProcessInstanceRequest ``` | Property | Type | Description | | -------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | ## CancelSdkException Thrown when a cancellable operation is cancelled. ```csharp public sealed class CancelSdkException : CamundaSdkException, ISerializable ``` ## CategoryExactMatch Matches the value exactly. ```csharp public readonly record struct CategoryExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## CategoryFilterProperty AuditLogCategoryEnum property with full advanced search capabilities. ```csharp public sealed class CategoryFilterProperty ``` | Property | Type | Description | | ------------ | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## Changeset JSON object with changed task attribute values. The following attributes can be adjusted with this endpoint, additional attributes will be ignored: - `candidateGroups` - reset by providing an empty list - `candidateUsers` - reset by providing an empty list - `dueDate` - reset by providing an empty String - `followUpDate` - reset by providing an empty String - `priority` - minimum 0, maximum 100, default 50 Providing any of those attributes with a `null` value or omitting it preserves the persisted attribute's value. The assignee cannot be adjusted with this endpoint, use the Assign task endpoint. This ensures correct event emission for assignee changes. ```csharp public sealed class Changeset ``` | Property | Type | Description | | ----------------- | -------------------------- | --------------------------------------------------------------------------- | | `DueDate` | `Nullable` | The due date of the task. Reset by providing an empty String. | | `FollowUpDate` | `Nullable` | The follow-up date of the task. Reset by providing an empty String. | | `CandidateUsers` | `List` | The list of candidate users of the task. Reset by providing an empty list. | | `CandidateGroups` | `List` | The list of candidate groups of the task. Reset by providing an empty list. | | `Priority` | `Nullable` | The priority of the task. | ## CheckpointId The id of the checkpoint. Must be a non-negative numerical value. As checkpoints are logically ordered by their ids (ascending), each successive checkpoint must use a higher id than the previous one. ```csharp public readonly record struct CheckpointId : ICamundaLongKey, IEquatable ``` | Property | Type | Description | | -------- | ------- | -------------------------- | | `Value` | `Int64` | The underlying long value. | ## ClientId The unique identifier of an OAuth client. Minted outside the Camunda REST API: in SaaS by Console, in Self-Managed with OIDC by the external identity provider (e.g. EntraID, Keycloak, Okta). In Self-Managed with Basic authentication, machine-to-machine applications are modelled as users instead — see the user identifier. ```csharp public readonly record struct ClientId : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## ClockPinRequest ```csharp public sealed class ClockPinRequest ``` | Property | Type | Description | | ----------- | ------- | ------------------------------------------------------------------------- | | `Timestamp` | `Int64` | The exact time in epoch milliseconds to which the clock should be pinned. | ## CloudConfigurationResponse Configuration for SaaS/cloud-specific settings. ```csharp public sealed class CloudConfigurationResponse ``` | Property | Type | Description | | -------- | -------- | --------------------------- | | `Stage` | `String` | The cloud deployment stage. | ## ClusterBalanceResponse The cluster's current per-partition balance state, the running rebalance, and the last completed rebalance. ```csharp public sealed class ClusterBalanceResponse ``` | Property | Type | Description | | ------------------------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `State` | `ClusterBalanceResponseState` | The cluster's aggregate balance state as of the time of the request. | | `Partitions` | `List` | The balance state of each partition as of the time of the request. | | `RunningRebalance` | `ClusterRunningRebalance` | Normally the rebalance currently running, or absent if no rebalance is running. For a dry-run response, this is instead the unexecuted plan of that dry run. | | `LastCompletedRebalance` | `ClusterCompletedRebalance` | The last completed non-dry-run rebalance this coordinator finished. | ## ClusterBrokerInfo Provides information on a broker node, independent of any physical tenant. ```csharp public sealed class ClusterBrokerInfo ``` | Property | Type | Description | | ---------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `BrokerId` | `String` | The unique (within a cluster) broker identifier. When the cluster is not zoned, then it's a string that represents the nodeId (an integer). When the cluster is zoned, instead, it's of the form "$zoneName_$nodeId", providing uniqueness even across zones. | | `Host` | `String` | The hostname for reaching the broker. | | `Port` | `Int32` | The port for reaching the broker. | | `Version` | `String` | The broker version. | ## ClusterCompletedRebalance The last completed rebalance. ```csharp public sealed class ClusterCompletedRebalance ``` | Property | Type | Description | | ------------- | ------------------------------------------ | ----------------------------------------------------------------------------- | | `RebalanceId` | `Int64` | The ID of this rebalance. | | `Partitions` | `List` | Every partition in the rebalance plan and its progress within this rebalance. | | `StartedAt` | `DateTimeOffset` | When this rebalance was created. | | `FinishedAt` | `DateTimeOffset` | When this rebalance finished. | | `Result` | `ClusterCompletedRebalanceResult` | How the rebalance ended. | ## ClusterHistoryBackupInfo A history backup id and what each physical tenant reports for it. No cluster-level state is aggregated from the per-tenant states. ```csharp public sealed class ClusterHistoryBackupInfo ``` | Property | Type | Description | | ----------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `BackupId` | `BackupId` | The id of the backup. | | `PhysicalTenants` | `List` | What each physical tenant reports for this backup id, ordered by physical tenant id. When looking a backup id up directly, every targeted tenant is listed, including the ones reporting `NOT_FOUND`. Within a listing, only the tenants that hold the id are listed. | ## ClusterHistoryBackupTakeResult The snapshots scheduled on a single physical tenant. Only successfully scheduled tenants are reported: the request fails as a whole if any targeted tenant could not schedule the backup. ```csharp public sealed class ClusterHistoryBackupTakeResult ``` | Property | Type | Description | | -------------------- | -------------- | ------------------------------------------------------------- | | `PhysicalTenantId` | `String` | The id of the physical tenant. | | `ScheduledSnapshots` | `List` | The names of the snapshots scheduled on this physical tenant. | ## ClusterHistoryBackupTenantInfo What a single physical tenant reports for a history backup id. ```csharp public sealed class ClusterHistoryBackupTenantInfo ``` | Property | Type | Description | | ------------------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | `PhysicalTenantId` | `String` | The id of the physical tenant. | | `State` | `ClusterHistoryBackupTenantState` | The state of the backup on this physical tenant. | | `FailureReason` | `String` | Reason for failure if the state is 'FAILED'. | | `Details` | `List` | Detailed status of the backup per snapshot on this physical tenant. Empty when the tenant does not hold the backup. | ## ClusterModeChangeOperation A single operation that is part of a cluster mode change. ```csharp public sealed class ClusterModeChangeOperation ``` | Property | Type | Description | | ----------- | -------- | ------------------------------------------------ | | `Operation` | `String` | The type of the operation. | | `Mode` | `String` | The target mode of the operation, if applicable. | ## ClusterModeChangePlannedChange The operations of a cluster mode change that apply to one physical tenant. ```csharp public sealed class ClusterModeChangePlannedChange ``` | Property | Type | Description | | ------------------ | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `PhysicalTenantId` | `String` | The physical tenant the operations apply to; null for operations that are not scoped to a single physical tenant, such as broker lifecycle operations. | | `Operations` | `List` | The ordered list of operations that will be applied to the physical tenant. | ## ClusterModeChangeResponse The planned changes resulting from a cluster mode transition request. ```csharp public sealed class ClusterModeChangeResponse ``` | Property | Type | Description | | ---------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ChangeId` | `String` | The ID of the cluster change that was triggered by the request. | | `PlannedChanges` | `List` | The operations that will be applied to complete the change, grouped by the physical tenant they belong to. Groups are transitioned in parallel; the operations within a group are applied in the given order. | ## ClusterRebalance The fields common to a running and a completed rebalance. ```csharp public sealed class ClusterRebalance ``` | Property | Type | Description | | ------------- | ------------------------------------------ | ----------------------------------------------------------------------------- | | `RebalanceId` | `Int64` | The ID of this rebalance. | | `Partitions` | `List` | Every partition in the rebalance plan and its progress within this rebalance. | | `StartedAt` | `DateTimeOffset` | When this rebalance was created. | ## ClusterRebalanceOperationPartition One partition's plan, progress, and outcome within a rebalance. ```csharp public sealed class ClusterRebalanceOperationPartition ``` | Property | Type | Description | | ------------------ | ---------------------------------------------------- | ----------------------------------------------------------------------------- | | `PartitionId` | `Int32` | The unique ID of this partition, within its physical tenant. | | `PhysicalTenantId` | `String` | The partition group this partition belongs to. | | `CurrentLeader` | `String` | The leader last observed by this rebalance, or absent if there was no leader. | | `DesiredLeader` | `String` | The leader selected when this rebalance was planned. | | `Progress` | `ClusterRebalanceOperationPartitionProgress` | Where this rebalance has reached for the partition. | | `Result` | `Nullable` | The terminal outcome, present only when progress is COMPLETED. | ## ClusterRebalancePartition One partition's leadership/balance status - its current leader, its desired leader, and whether a rebalance is currently moving it. ```csharp public sealed class ClusterRebalancePartition ``` | Property | Type | Description | | ------------------ | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `PartitionId` | `Int32` | The unique ID of this partition, within its physical tenant. | | `PhysicalTenantId` | `String` | The partition group this partition belongs to. Partition IDs are unique only within a group, so this is needed to identify the partition. | | `CurrentLeader` | `String` | The broker ID currently leading this partition, or absent if it has no leader. | | `DesiredLeader` | `String` | The broker ID the current configuration wants to lead this partition. | | `State` | `ClusterRebalancePartitionState` | Whether this partition is being actively transferred, unbalanced, or balanced. | ## ClusterRebalanceRequest The settings to run a given rebalance with. Every setting is optional; an absent request body is equivalent to a body with every field absent, and means "use the configured settings". ```csharp public sealed class ClusterRebalanceRequest ``` | Property | Type | Description | | ------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `ReplicationLagThreshold` | `Nullable` | The highest replication lag (in bytes) that a desired leader may have for its transfer to be accepted. | | `ReplicationTimeout` | `String` | How long a partition may stay frozen waiting for its desired leader to catch up (as a positive ISO-8601 duration). | | `MaxTransferAttempts` | `Nullable` | How many times a current leader may prompt the desired leader to take over leadership before giving up. | | `LeaderWaitTimeout` | `String` | How long the coordinator waits for a partition without a leader to acquire one before reporting `NO_LEADER` and moving on (as a positive ISO-8601 duration). | ## ClusterRestoreAwaitModeChangeOperation The operation that awaits the transition of a broker to a mode. ```csharp public sealed class ClusterRestoreAwaitModeChangeOperation : ClusterRestoreOperation ``` | Property | Type | Description | | ---------- | -------- | ----------------------------------------------------------------------------------------- | | `BrokerId` | `String` | The ID of the broker that applies the operation, including its zone if it belongs to one. | | `Mode` | `String` | The mode the broker is awaited to have transitioned to. | ## ClusterRestoreBrokerOperation A restore operation that applies to a broker as a whole, such as the one that updates its incarnation number. ```csharp public sealed class ClusterRestoreBrokerOperation : ClusterRestoreOperation ``` | Property | Type | Description | | ---------- | -------- | ----------------------------------------------------------------------------------------- | | `BrokerId` | `String` | The ID of the broker that applies the operation, including its zone if it belongs to one. | ## ClusterRestoreModeChangeOperation The operation that transitions a broker to a mode once its partitions are restored. ```csharp public sealed class ClusterRestoreModeChangeOperation : ClusterRestoreOperation ``` | Property | Type | Description | | ---------- | -------- | ----------------------------------------------------------------------------------------- | | `BrokerId` | `String` | The ID of the broker that applies the operation, including its zone if it belongs to one. | | `Mode` | `String` | The mode the broker is transitioned to. | ## ClusterRestoreOperation A single operation that is part of a restore. Every operation names the broker that applies it; the rest of its properties depend on what the operation does, so it is reported as one of the variants below, distinguished by `operation`. A property a variant does not declare is absent from the response rather than reported as null. ```csharp public abstract class ClusterRestoreOperation ``` ## ClusterRestorePartitionOperation A restore operation that targets a single partition without restoring it, such as the one that prepares the partition for its restore. ```csharp public sealed class ClusterRestorePartitionOperation : ClusterRestoreOperation ``` | Property | Type | Description | | ------------- | -------- | ----------------------------------------------------------------------------------------- | | `BrokerId` | `String` | The ID of the broker that applies the operation, including its zone if it belongs to one. | | `PartitionId` | `Int32` | The partition the operation applies to. | ## ClusterRestorePartitionRestoreOperation The operation that restores a single partition from the backups resolved for it. ```csharp public sealed class ClusterRestorePartitionRestoreOperation : ClusterRestoreOperation ``` | Property | Type | Description | | ------------- | ------------- | ----------------------------------------------------------------------------------------- | | `BrokerId` | `String` | The ID of the broker that applies the operation, including its zone if it belongs to one. | | `PartitionId` | `Int32` | The partition the operation restores. | | `BackupIds` | `List` | The IDs of the backups the partition is restored from. | ## ClusterRestorePlannedChange The operations of a restore that apply to one physical tenant. ```csharp public sealed class ClusterRestorePlannedChange ``` | Property | Type | Description | | ------------------ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `PhysicalTenantId` | `String` | The physical tenant the operations apply to; null for operations that are not scoped to a single physical tenant, such as broker lifecycle operations. | | `Operations` | `List` | The ordered list of operations that will be applied to the physical tenant. | ## ClusterRestoreRequest Describes a restore request issued by a cluster admin. The backup selection at the top level applies to every targeted physical tenant, except for the ones listed in `overrides`. ```csharp public sealed class ClusterRestoreRequest ``` | Property | Type | Description | | ----------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Overrides` | `Dictionary` | The backup selection to apply to individual physical tenants, keyed by physical tenant id. Only allowed for a cluster-wide restore, that is when no `physicalTenantId` parameter is given. | | `From` | `Nullable` | The start of the time range to restore from, as an ISO 8601 timestamp. | | `To` | `Nullable` | The end of the time range to restore from, as an ISO 8601 timestamp. | | `BackupIds` | `List` | The IDs of the backups to restore from, one per partition. | ## ClusterRestoreResponse The planned changes resulting from a restore request. ```csharp public sealed class ClusterRestoreResponse ``` | Property | Type | Description | | ---------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ChangeId` | `String` | The ID of the cluster change that was triggered by the request. | | `PlannedChanges` | `List` | The operations that will be applied to complete the restore, grouped by the physical tenant they belong to. Groups are restored in parallel; the operations within a group are applied in the given order. | ## ClusterRunningRebalance The rebalance currently running. ```csharp public sealed class ClusterRunningRebalance ``` | Property | Type | Description | | ----------------- | ------------------------------------------ | ----------------------------------------------------------------------------- | | `RebalanceId` | `Int64` | The ID of this rebalance. | | `Partitions` | `List` | Every partition in the rebalance plan and its progress within this rebalance. | | `StartedAt` | `DateTimeOffset` | When this rebalance was created. | | `DryRun` | `Boolean` | Whether this rebalance is a dry run. | | `CancelRequested` | `Boolean` | Whether cancellation has been requested. | ## ClusterRuntimeBackupInfo A runtime backup id, what each physical tenant reports for it, and the state aggregated over every targeted tenant — folded from the per-tenant states by the same rules a per-tenant state is folded from its partitions. ```csharp public sealed class ClusterRuntimeBackupInfo ``` | Property | Type | Description | | ----------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `BackupId` | `BackupId` | The id of the backup. | | `State` | `StateCode` | The state aggregated over every targeted physical tenant, whether the backup id was looked up directly or listed. A tenant holding nothing for this id counts as `DOES_NOT_EXIST`, so the aggregate is `INCOMPLETE` unless every targeted tenant holds the backup. | | `FailureReason` | `String` | Reason for failure if the aggregated state is 'FAILED'. | | `PhysicalTenants` | `List` | What each physical tenant reports for this backup id, ordered by physical tenant id. Every targeted tenant is listed, including the ones reporting `DOES_NOT_EXIST`. | ## ClusterRuntimeBackupState The checkpoint and backup state of each physical tenant. Nothing is aggregated across tenants: checkpoint ids and log positions only mean anything within one tenant's partitions. ```csharp public sealed class ClusterRuntimeBackupState ``` | Property | Type | Description | | ----------------- | --------------------------------------- | ----------------------------------------------------------------------------------------- | | `PhysicalTenants` | `List` | The runtime backup state of each targeted physical tenant, ordered by physical tenant id. | ## ClusterRuntimeBackupTakeResult Whether one physical tenant's runtime backup was triggered, and under which id it can be monitored and deleted. ```csharp public sealed class ClusterRuntimeBackupTakeResult ``` | Property | Type | Description | | ------------------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PhysicalTenantId` | `String` | The id of the physical tenant. | | `BackupId` | `Nullable` | The id to monitor or delete this physical tenant's backup by: the id it is running under when `TRIGGERED` — the requested one, or the one the tenant generated when ids are generated — and the requested id to check when `UNKNOWN`. Null when the tenant is known to be running no backup, and also when an `UNKNOWN` tenant generates its own ids, because the id it may be running under was never reported; list that tenant's backups to find it. | | `Outcome` | `ClusterRuntimeBackupTakeOutcome` | What this physical tenant did with the trigger. | | `Reason` | `String` | Why this physical tenant reported no triggered backup. Null when it was triggered. | ## ClusterRuntimeBackupTenantInfo What a single physical tenant reports for a runtime backup id. ```csharp public sealed class ClusterRuntimeBackupTenantInfo ``` | Property | Type | Description | | ------------------ | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PhysicalTenantId` | `String` | The id of the physical tenant. | | `State` | `StateCode` | The state of the backup on this physical tenant, aggregated over its partitions. | | `FailureReason` | `String` | Reason for failure if the state is 'FAILED'. | | `Details` | `List` | Detailed status of the backup per partition of this physical tenant. Contains every partition of the tenant when the backup id was looked up directly, including for a tenant that holds no such backup. Empty for a tenant that holds nothing for a listed id: a listing asks each tenant for the backups it has, so there is nothing to report per partition for one it does not. | ## ClusterRuntimeBackupTenantState The checkpoint and backup state of one physical tenant. ```csharp public sealed class ClusterRuntimeBackupTenantState ``` | Property | Type | Description | | ------------------ | -------------------- | --------------------------------------------------------------------- | | `PhysicalTenantId` | `String` | The id of the physical tenant. | | `State` | `RuntimeBackupState` | The checkpoint and backup state of this physical tenant's partitions. | ## ClusterStatusResponse The aggregated status of the whole cluster. ```csharp public sealed class ClusterStatusResponse ``` | Property | Type | Description | | -------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `Status` | `ClusterStatusResponseStatus` | `HEALTHY` when every physical tenant is healthy, `DOWN` when no physical tenant can process work, `DEGRADED` in every other case. | ## ClusterTakeHistoryBackupResponse The snapshots scheduled on every targeted physical tenant. No cluster-level state is aggregated from the per-tenant outcomes. ```csharp public sealed class ClusterTakeHistoryBackupResponse ``` | Property | Type | Description | | ----------------- | -------------------------------------- | ----------------------------------------------------------------------------- | | `BackupId` | `BackupId` | The id requested for the backup on every targeted physical tenant. | | `PhysicalTenants` | `List` | The outcome for each targeted physical tenant, ordered by physical tenant id. | ## ClusterTakeRuntimeBackupResponse The outcome of triggering a runtime backup on every targeted physical tenant. Returned both when every tenant was triggered and when only some were, so a partial trigger is never silent: the status code says whether the request succeeded, the body says what is running. ```csharp public sealed class ClusterTakeRuntimeBackupResponse ``` | Property | Type | Description | | ----------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PhysicalTenants` | `List` | The outcome for each targeted physical tenant, ordered by physical tenant id. Carries no cluster-level backup id: in generated-id mode each tenant generates its own. | ## ClusterTopologyResponse The topology of the whole cluster, aggregated over all physical tenants. ```csharp public sealed class ClusterTopologyResponse ``` | Property | Type | Description | | ----------------- | ------------------------------ | ----------------------------------------------------------------------- | | `Brokers` | `List` | The brokers that are part of this cluster, across all physical tenants. | | `ClusterId` | `String` | The cluster Id. | | `ClusterSize` | `Int32` | The number of brokers in the cluster. | | `GatewayVersion` | `String` | The version of the Orchestration Cluster Gateway. | | `PhysicalTenants` | `List` | The topology of each physical tenant of this cluster. | ## ClusterUpgradeStatusResponse The upgrade-readiness status of the whole cluster. ```csharp public sealed class ClusterUpgradeStatusResponse ``` | Property | Type | Description | | -------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Status` | `ClusterUpgradeStatusResponseStatus` | `MIGRATED` once every known upgrade-readiness condition is met for every known physical tenant; `MIGRATION_IN_PROGRESS` when at least one is confirmed not yet migrated; `UNKNOWN` otherwise. | ## ClusterVariableKindExactMatch Matches the value exactly. ```csharp public readonly record struct ClusterVariableKindExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## ClusterVariableKindFilterProperty ClusterVariableKindEnum property with full advanced search capabilities. ```csharp public sealed class ClusterVariableKindFilterProperty ``` | Property | Type | Description | | ------------ | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## ClusterVariableName The name of a cluster variable. Unique within its scope (global or tenant-specific). ```csharp public readonly record struct ClusterVariableName : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## ClusterVariableResult ```csharp public sealed class ClusterVariableResult ``` | Property | Type | Description | | ---------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Value` | `String` | Full value of this cluster variable. | | `Name` | `ClusterVariableName` | The name of the cluster variable. Unique within its scope (global or tenant-specific). | | `Scope` | `ClusterVariableScopeEnum` | The scope of a cluster variable. | | `TenantId` | `String` | Only provided if the cluster variable scope is TENANT. Null for global scope variables. | | `Metadata` | `Dictionary` | A generic key-value metadata bag attached to the cluster variable. Values are strings or numbers. | | `Kind` | `ClusterVariableKindEnum` | The kind of a cluster variable. JSON is the default. SECRET_REFERENCE allows the value to contain camunda.secrets.X references that are resolved at job activation time. | ## ClusterVariableResultBase Cluster variable response item. ```csharp public sealed class ClusterVariableResultBase ``` | Property | Type | Description | | ---------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Name` | `ClusterVariableName` | The name of the cluster variable. Unique within its scope (global or tenant-specific). | | `Scope` | `ClusterVariableScopeEnum` | The scope of a cluster variable. | | `TenantId` | `String` | Only provided if the cluster variable scope is TENANT. Null for global scope variables. | | `Metadata` | `Dictionary` | A generic key-value metadata bag attached to the cluster variable. Values are strings or numbers. | | `Kind` | `ClusterVariableKindEnum` | The kind of a cluster variable. JSON is the default. SECRET_REFERENCE allows the value to contain camunda.secrets.X references that are resolved at job activation time. | ## ClusterVariableScopeExactMatch Matches the value exactly. ```csharp public readonly record struct ClusterVariableScopeExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## ClusterVariableScopeFilterProperty ClusterVariableScopeEnum property with full advanced search capabilities. ```csharp public sealed class ClusterVariableScopeFilterProperty ``` | Property | Type | Description | | ------------ | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## ClusterVariableSearchQueryFilterRequest Cluster variable filter request. ```csharp public sealed class ClusterVariableSearchQueryFilterRequest ``` | Property | Type | Description | | ------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Name` | `StringFilterProperty` | Name of the cluster variable. | | `Value` | `StringFilterProperty` | The value of the cluster variable. | | `Scope` | `ClusterVariableScopeFilterProperty` | The scope filter for cluster variables. | | `TenantId` | `StringFilterProperty` | Tenant ID of this variable. | | `IsTruncated` | `Nullable` | Filter cluster variables by truncation status of their stored values. When true, returns only variables whose stored values are truncated (i.e., the value exceeds the storage size limit and is truncated in storage). When false, returns only variables with non-truncated stored values. This filter is based on the underlying storage characteristic, not the response format. | | `Metadata` | `Dictionary` | Filter by metadata entries. A map of metadata key to an advanced filter on that key's value. Metadata values are strings or numbers. | | `Kind` | `ClusterVariableKindFilterProperty` | The kind filter for cluster variables. | ## ClusterVariableSearchQueryRequest Cluster variable search query request. ```csharp public sealed class ClusterVariableSearchQueryRequest ``` | Property | Type | Description | | -------- | --------------------------------------------- | ------------------------------------ | | `Sort` | `List` | Sort field criteria. | | `Filter` | `ClusterVariableSearchQueryFilterRequest` | The cluster variable search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## ClusterVariableSearchQueryResult Cluster variable search query response. ```csharp public sealed class ClusterVariableSearchQueryResult ``` | Property | Type | Description | | -------- | ----------------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching cluster variables. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## ClusterVariableSearchQuerySortRequest ```csharp public sealed class ClusterVariableSearchQuerySortRequest ``` | Property | Type | Description | | -------- | -------------------------------------------- | --------------------------------------------- | | `Field` | `ClusterVariableSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## ClusterVariableSearchResult Cluster variable search response item. ```csharp public sealed class ClusterVariableSearchResult ``` | Property | Type | Description | | ------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Value` | `String` | Value of this cluster variable. Can be truncated. | | `IsTruncated` | `Boolean` | Whether the value is truncated or not. | | `Name` | `ClusterVariableName` | The name of the cluster variable. Unique within its scope (global or tenant-specific). | | `Scope` | `ClusterVariableScopeEnum` | The scope of a cluster variable. | | `TenantId` | `String` | Only provided if the cluster variable scope is TENANT. Null for global scope variables. | | `Metadata` | `Dictionary` | A generic key-value metadata bag attached to the cluster variable. Values are strings or numbers. | | `Kind` | `ClusterVariableKindEnum` | The kind of a cluster variable. JSON is the default. SECRET_REFERENCE allows the value to contain camunda.secrets.X references that are resolved at job activation time. | ## ComponentsConfigurationResponse Configuration for active Camunda components in the deployment. ```csharp public sealed class ComponentsConfigurationResponse ``` | Property | Type | Description | | -------- | ----------------------- | ----------------------------------------------------------------- | | `Active` | `List` | List of webapp components whose UI is enabled in this deployment. | ## ConditionWaitStateDetails ```csharp public sealed class ConditionWaitStateDetails : WaitStateDetails ``` | Property | Type | Description | | ------------ | -------------- | --------------------------------------------------------------------------------- | | `Expression` | `String` | The condition expression that must evaluate to true to proceed. | | `Events` | `List` | The variable events that trigger condition re-evaluation. Empty means all events. | ## ConditionalEvaluationInstruction ```csharp public sealed class ConditionalEvaluationInstruction : ITenantIdSettable ``` | Property | Type | Description | | ---------------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `TenantId` | `Nullable` | Used to evaluate root-level conditional start events for a tenant with the given ID. This will only evaluate root-level conditional start events of process definitions which belong to the tenant. | | `ProcessDefinitionKey` | `Nullable` | Used to evaluate root-level conditional start events of the process definition with the given key. | | `Variables` | `Object` | JSON object representing the variables to use for evaluation of the conditions and to pass to the process instances that have been triggered. | ## ConsistencyOptions Options for eventual consistency polling behavior. ```csharp public sealed class ConsistencyOptions ``` | Property | Type | Description | | ---------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `WaitUpToMs` | `Int32` | Maximum time to wait for the data to become consistent, in milliseconds. Set to 0 to skip eventual consistency handling. | | `PollIntervalMs` | `Int32` | Poll interval in milliseconds (default: 500). | | `IsConsistent` | `Func<, Boolean>` | Optional predicate: when true, the response is considered consistent. If not set, any non-null response with items (where applicable) is accepted. | ## CorrelatedMessageSubscriptionFilter Correlated message subscriptions search filter. ```csharp public sealed class CorrelatedMessageSubscriptionFilter ``` | Property | Type | Description | | ---------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `BusinessId` | `StringFilterProperty` | Filter by the business id stored on the correlated message subscription — for message start event correlations the correlating message's business id, and for catch, boundary, or intermediate event correlations the subscribing process instance's business id. Supports advanced string filtering, including `$like` with `*`/`?` wildcards. | | `CorrelationKey` | `StringFilterProperty` | The correlation key of the message. | | `CorrelationTime` | `DateTimeFilterProperty` | The time when the message was correlated. | | `ElementId` | `StringFilterProperty` | The element ID that received the message. | | `ElementInstanceKey` | `ElementInstanceKeyFilterProperty` | The element instance key that received the message. | | `MessageKey` | `BasicStringFilterProperty` | The message key. | | `MessageName` | `StringFilterProperty` | The name of the message. | | `PartitionId` | `IntegerFilterProperty` | The partition ID that correlated the message. | | `ProcessDefinitionId` | `StringFilterProperty` | The process definition ID associated with this correlated message subscription. | | `ProcessDefinitionKey` | `ProcessDefinitionKeyFilterProperty` | The process definition key associated with this correlated message subscription. For intermediate message events, this only works for data created with 8.9 and later. | | `ProcessInstanceKey` | `ProcessInstanceKeyFilterProperty` | The process instance key associated with this correlated message subscription. | | `SubscriptionKey` | `MessageSubscriptionKeyFilterProperty` | The subscription key that received the message. | | `TenantId` | `StringFilterProperty` | The tenant ID associated with this correlated message subscription. | ## CorrelatedMessageSubscriptionResult ```csharp public sealed class CorrelatedMessageSubscriptionResult ``` | Property | Type | Description | | ------------------------ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `BusinessId` | `Nullable` | The business id associated with this correlated message subscription. For a message start event correlation, it is the business id carried by the correlating message that was stamped on the started process instance to enforce its uniqueness. For a catch, boundary, or intermediate event correlation, it is the business id of the subscribing process instance, captured when the subscription was opened. It is `null` when the relevant process instance has no business id. | | `CorrelationKey` | `String` | The correlation key of the message. | | `CorrelationTime` | `DateTimeOffset` | The time when the message was correlated. | | `ElementId` | `ElementId` | The element ID that received the message. | | `ElementInstanceKey` | `Nullable` | The element instance key that received the message. It is `null` for start event subscriptions. | | `MessageKey` | `MessageKey` | The message key. | | `MessageName` | `String` | The name of the message. | | `PartitionId` | `Int32` | The partition ID that correlated the message. | | `ProcessDefinitionId` | `ProcessDefinitionId` | The process definition ID associated with this correlated message subscription. | | `ProcessDefinitionKey` | `ProcessDefinitionKey` | The process definition key associated with this correlated message subscription. | | `ProcessInstanceKey` | `ProcessInstanceKey` | The process instance key associated with this correlated message subscription. | | `RootProcessInstanceKey` | `Nullable` | The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. | | `SubscriptionKey` | `MessageSubscriptionKey` | The subscription key that received the message. | | `TenantId` | `TenantId` | The tenant ID associated with this correlated message subscription. | ## CorrelatedMessageSubscriptionSearchQuery ```csharp public sealed class CorrelatedMessageSubscriptionSearchQuery ``` | Property | Type | Description | | -------- | ----------------------------------------------------------- | ---------------------------------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `CorrelatedMessageSubscriptionFilter` | The correlated message subscriptions search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## CorrelatedMessageSubscriptionSearchQueryResult ```csharp public sealed class CorrelatedMessageSubscriptionSearchQueryResult ``` | Property | Type | Description | | -------- | ------------------------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching correlated message subscriptions. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## CorrelatedMessageSubscriptionSearchQuerySortRequest ```csharp public sealed class CorrelatedMessageSubscriptionSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ---------------------------------------------------------- | --------------------------------------------- | | `Field` | `CorrelatedMessageSubscriptionSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## CreateClusterVariableRequest ```csharp public sealed class CreateClusterVariableRequest ``` | Property | Type | Description | | ---------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Name` | `ClusterVariableName` | The name of the cluster variable. Must be unique within its scope (global or tenant-specific). | | `Value` | `Object` | The value of the cluster variable. Can be any JSON object or primitive value. Will be serialized as a JSON string in responses. | | `Metadata` | `Dictionary` | A generic key-value metadata bag attached to the cluster variable. Values must be strings or numbers. Limited to 100 entries and a configurable maximum serialized size (default: 100 entries at max key length of a cluster variable name (256 chars) plus the maximum value length, 8192 characters). | | `Kind` | `Nullable` | The kind of the cluster variable. Defaults to JSON if not specified. | ## CreateGlobalTaskListenerRequest ```csharp public sealed class CreateGlobalTaskListenerRequest ``` | Property | Type | Description | | ---------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | `Id` | `GlobalListenerId` | The user-defined id for the global listener | | `EventTypes` | `List` | List of user task event types that trigger the listener. | | `Type` | `String` | The name of the job type, used as a reference to specify which job workers request the respective listener job. | | `Retries` | `Nullable` | Number of retries for the listener job. | | `AfterNonGlobal` | `Nullable` | Whether the listener should run after model-level listeners. | | `Priority` | `Nullable` | The priority of the listener. Higher priority listeners are executed before lower priority ones. | ## CreateProcessInstanceResult ```csharp public sealed class CreateProcessInstanceResult ``` | Property | Type | Description | | -------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `ProcessDefinitionId` | `ProcessDefinitionId` | The BPMN process id of the process definition which was used to create the process. instance | | `ProcessDefinitionVersion` | `Int32` | The version of the process definition which was used to create the process instance. | | `TenantId` | `TenantId` | The tenant id of the created process instance. | | `Variables` | `Object` | All the variables visible in the root scope. | | `ProcessDefinitionKey` | `ProcessDefinitionKey` | The key of the process definition which was used to create the process instance. | | `ProcessInstanceKey` | `ProcessInstanceKey` | The unique identifier of the created process instance; to be used wherever a request needs a process instance key (e.g. CancelProcessInstanceRequest). | | `Tags` | `List` | List of tags. Tags need to start with a letter; then alphanumerics, `_`, `-`, `:`, or `.`; length ≤ 100. | | `BusinessId` | `Nullable` | Business id as provided on creation. | ## CursorBackwardPagination ```csharp public sealed class CursorBackwardPagination : SearchQueryPageRequest ``` | Property | Type | Description | | -------- | ----------------------- | --------------------------------------------------------------------------------------------- | | `Before` | `Nullable` | Use the `startCursor` value from the previous response to fetch the previous page of results. | | `Limit` | `Nullable` | The maximum number of items to return in one request. | ## CursorForwardPagination ```csharp public sealed class CursorForwardPagination : SearchQueryPageRequest ``` | Property | Type | Description | | -------- | --------------------- | --------------------------------------------------------------------------------------- | | `After` | `Nullable` | Use the `endCursor` value from the previous response to fetch the next page of results. | | `Limit` | `Nullable` | The maximum number of items to return in one request. | ## DateTimeFilterProperty Date-time property with full advanced search capabilities. ```csharp public sealed class DateTimeFilterProperty ``` | Property | Type | Description | | ------------ | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `Gt` | `Nullable` | Greater than comparison with the provided value. | | `Gte` | `Nullable` | Greater than or equal comparison with the provided value. | | `Lt` | `Nullable` | Lower than comparison with the provided value. | | `Lte` | `Nullable` | Lower than or equal comparison with the provided value. | | `In` | `List` | Checks if the property matches any of the provided values. | ## DecisionDefinitionFilter Decision definition search filter. ```csharp public sealed class DecisionDefinitionFilter ``` | Property | Type | Description | | ----------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `DecisionDefinitionId` | `Nullable` | The DMN ID of the decision definition. | | `Name` | `String` | The DMN name of the decision definition. | | `IsLatestVersion` | `Nullable` | Whether to only return the latest version of each decision definition. When using this filter, pagination functionality is limited, you can only paginate forward using `after` and `limit`. The response contains no `startCursor` in the `page`, and requests ignore the `from` and `before` in the `page`. | | `Version` | `Nullable` | The assigned version of the decision definition. | | `DecisionRequirementsId` | `String` | the DMN ID of the decision requirements graph that the decision definition is part of. | | `TenantId` | `Nullable` | The tenant ID of the decision definition. | | `DecisionDefinitionKey` | `Nullable` | The assigned key, which acts as a unique identifier for this decision definition. | | `DecisionRequirementsKey` | `Nullable` | The assigned key of the decision requirements graph that the decision definition is part of. | | `DecisionRequirementsName` | `String` | The DMN name of the decision requirements that the decision definition is part of. | | `DecisionRequirementsVersion` | `Nullable` | The assigned version of the decision requirements that the decision definition is part of. | ## DecisionDefinitionId Id of a decision definition, from the model. Only ids of decision definitions that are deployed are useful. ```csharp public readonly record struct DecisionDefinitionId : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## DecisionDefinitionKeyExactMatch Matches the value exactly. ```csharp public readonly record struct DecisionDefinitionKeyExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## DecisionDefinitionKeyFilterProperty DecisionDefinitionKey property with full advanced search capabilities. ```csharp public sealed class DecisionDefinitionKeyFilterProperty ``` | Property | Type | Description | | ------------ | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## DecisionDefinitionResult ```csharp public sealed class DecisionDefinitionResult ``` | Property | Type | Description | | ----------------------------- | ------------------------- | -------------------------------------------------------------------------------------------- | | `DecisionDefinitionId` | `DecisionDefinitionId` | The DMN ID of the decision definition. | | `DecisionDefinitionKey` | `DecisionDefinitionKey` | The assigned key, which acts as a unique identifier for this decision definition. | | `DecisionRequirementsId` | `String` | the DMN ID of the decision requirements graph that the decision definition is part of. | | `DecisionRequirementsKey` | `DecisionRequirementsKey` | The assigned key of the decision requirements graph that the decision definition is part of. | | `DecisionRequirementsName` | `String` | The DMN name of the decision requirements that the decision definition is part of. | | `DecisionRequirementsVersion` | `Int32` | The assigned version of the decision requirements that the decision definition is part of. | | `Name` | `String` | The DMN name of the decision definition. | | `TenantId` | `TenantId` | The tenant ID of the decision definition. | | `Version` | `Int32` | The assigned version of the decision definition. | ## DecisionDefinitionSearchQuery ```csharp public sealed class DecisionDefinitionSearchQuery ``` | Property | Type | Description | | -------- | ------------------------------------------------ | --------------------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `DecisionDefinitionFilter` | The decision definition search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## DecisionDefinitionSearchQueryResult ```csharp public sealed class DecisionDefinitionSearchQueryResult ``` | Property | Type | Description | | -------- | -------------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching decision definitions. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## DecisionDefinitionSearchQuerySortRequest ```csharp public sealed class DecisionDefinitionSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ----------------------------------------------- | --------------------------------------------- | | `Field` | `DecisionDefinitionSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## DecisionEvaluationById ```csharp public sealed class DecisionEvaluationById : DecisionEvaluationInstruction, ITenantIdSettable ``` | Property | Type | Description | | ---------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------ | | `DecisionDefinitionId` | `DecisionDefinitionId` | The ID of the decision to be evaluated. When using the decision ID, the latest deployed version of the decision is used. | | `Variables` | `Object` | The decision evaluation variables as JSON document. | | `TenantId` | `Nullable` | The tenant ID of the decision. | ## DecisionEvaluationByKey ```csharp public sealed class DecisionEvaluationByKey : DecisionEvaluationInstruction, ITenantIdSettable ``` | Property | Type | Description | | ----------------------- | ----------------------- | --------------------------------------------------- | | `DecisionDefinitionKey` | `DecisionDefinitionKey` | System-generated key for a decision definition. | | `Variables` | `Object` | The decision evaluation variables as JSON document. | | `TenantId` | `Nullable` | The tenant ID of the decision. | ## DecisionEvaluationInstanceKeyExactMatch Matches the value exactly. ```csharp public readonly record struct DecisionEvaluationInstanceKeyExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## DecisionEvaluationInstanceKeyFilterProperty DecisionEvaluationInstanceKey property with full advanced search capabilities. ```csharp public sealed class DecisionEvaluationInstanceKeyFilterProperty ``` | Property | Type | Description | | ------------ | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## DecisionEvaluationInstruction ```csharp public abstract class DecisionEvaluationInstruction ``` ## DecisionEvaluationKeyExactMatch Matches the value exactly. ```csharp public readonly record struct DecisionEvaluationKeyExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## DecisionEvaluationKeyFilterProperty DecisionEvaluationKey property with full advanced search capabilities. ```csharp public sealed class DecisionEvaluationKeyFilterProperty ``` | Property | Type | Description | | ------------ | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## DecisionInstanceDeletionBatchOperationRequest The decision instance filter that defines which decision instances should be deleted. ```csharp public sealed class DecisionInstanceDeletionBatchOperationRequest ``` | Property | Type | Description | | -------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | | `Filter` | `DecisionInstanceFilter` | The decision instance filter. | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | ## DecisionInstanceFilter Decision instance search filter. ```csharp public sealed class DecisionInstanceFilter ``` | Property | Type | Description | | ------------------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `DecisionEvaluationInstanceKey` | `DecisionEvaluationInstanceKeyFilterProperty` | The key of the decision evaluation instance. | | `State` | `DecisionInstanceStateFilterProperty` | The state of the decision instance. | | `EvaluationFailure` | `String` | The evaluation failure of the decision instance. | | `EvaluationDate` | `DateTimeFilterProperty` | The evaluation date of the decision instance. | | `DecisionDefinitionId` | `Nullable` | The ID of the DMN decision. | | `DecisionDefinitionName` | `String` | The name of the DMN decision. | | `DecisionDefinitionVersion` | `Nullable` | The version of the decision. | | `DecisionDefinitionType` | `Nullable` | The type of the decision. UNSPECIFIED is deprecated and should not be used anymore, for removal in 8.10 | | `TenantId` | `Nullable` | The tenant ID of the decision instance. | | `DecisionEvaluationKey` | `Nullable` | The key of the parent decision evaluation. Note that this is not the identifier of an individual decision instance; the `decisionEvaluationInstanceKey` is the identifier for a decision instance. | | `ProcessDefinitionKey` | `Nullable` | The key of the process definition. | | `ProcessInstanceKey` | `Nullable` | The key of the process instance. | | `BusinessId` | `StringFilterProperty` | The business ID of the owning process instance the decision instance belongs to. This only works for decision instances created with 8.10 and onwards. Decision instances from prior versions and standalone evaluations don't contain this data and cannot be found. | | `DecisionDefinitionKey` | `DecisionDefinitionKeyFilterProperty` | The key of the decision. | | `ElementInstanceKey` | `ElementInstanceKeyFilterProperty` | The key of the element instance this decision instance is linked to. | | `RootDecisionDefinitionKey` | `DecisionDefinitionKeyFilterProperty` | The key of the root decision definition. | | `DecisionRequirementsKey` | `DecisionRequirementsKeyFilterProperty` | The key of the decision requirements definition. | ## DecisionInstanceGetQueryResult ```csharp public sealed class DecisionInstanceGetQueryResult ``` | Property | Type | Description | | ------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `BusinessId` | `Nullable` | The business ID of the owning process instance, inherited when the decision instance was evaluated. This is `null` for decision instances created before version 8.10, for standalone decision evaluations, and for decision instances whose owning process instance has no business ID. | | `DecisionDefinitionId` | `DecisionDefinitionId` | The ID of the DMN decision. | | `DecisionDefinitionKey` | `DecisionDefinitionKey` | The key of the decision. | | `DecisionDefinitionName` | `String` | The name of the DMN decision. | | `DecisionDefinitionType` | `DecisionDefinitionTypeEnum` | The type of the decision. UNSPECIFIED is deprecated and should not be used anymore, for removal in 8.10 | | `DecisionDefinitionVersion` | `Int32` | The version of the decision. | | `DecisionEvaluationInstanceKey` | `DecisionEvaluationInstanceKey` | System-generated identifier for a decision evaluation instance. It is composed of the parent decision evaluation key and the 1-based index of the evaluated decision within that evaluation, joined by a hyphen (format: `-`). | | `DecisionEvaluationKey` | `DecisionEvaluationKey` | The key of the decision evaluation where this instance was created. | | `ElementInstanceKey` | `Nullable` | The key of the element instance this decision instance is linked to. | | `EvaluationDate` | `DateTimeOffset` | The evaluation date of the decision instance. | | `EvaluationFailure` | `String` | The evaluation failure of the decision instance. | | `ProcessDefinitionKey` | `Nullable` | The key of the process definition. | | `ProcessInstanceKey` | `Nullable` | The key of the process instance. | | `Result` | `String` | The result of the decision instance. | | `RootDecisionDefinitionKey` | `DecisionDefinitionKey` | The key of the root decision definition. | | `RootProcessInstanceKey` | `Nullable` | The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. | | `State` | `DecisionInstanceStateEnum` | The state of the decision instance. UNSPECIFIED and UNKNOWN are deprecated and should not be used anymore, for removal in 8.10 | | `TenantId` | `TenantId` | The tenant ID of the decision instance. | | `EvaluatedInputs` | `List` | The evaluated inputs of the decision instance. | | `MatchedRules` | `List` | The matched rules of the decision instance. | ## DecisionInstanceResult ```csharp public sealed class DecisionInstanceResult ``` | Property | Type | Description | | ------------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `BusinessId` | `Nullable` | The business ID of the owning process instance, inherited when the decision instance was evaluated. This is `null` for decision instances created before version 8.10, for standalone decision evaluations, and for decision instances whose owning process instance has no business ID. | | `DecisionDefinitionId` | `DecisionDefinitionId` | The ID of the DMN decision. | | `DecisionDefinitionKey` | `DecisionDefinitionKey` | The key of the decision. | | `DecisionDefinitionName` | `String` | The name of the DMN decision. | | `DecisionDefinitionType` | `DecisionDefinitionTypeEnum` | The type of the decision. UNSPECIFIED is deprecated and should not be used anymore, for removal in 8.10 | | `DecisionDefinitionVersion` | `Int32` | The version of the decision. | | `DecisionEvaluationInstanceKey` | `DecisionEvaluationInstanceKey` | System-generated identifier for a decision evaluation instance. It is composed of the parent decision evaluation key and the 1-based index of the evaluated decision within that evaluation, joined by a hyphen (format: `-`). | | `DecisionEvaluationKey` | `DecisionEvaluationKey` | The key of the decision evaluation where this instance was created. | | `ElementInstanceKey` | `Nullable` | The key of the element instance this decision instance is linked to. | | `EvaluationDate` | `DateTimeOffset` | The evaluation date of the decision instance. | | `EvaluationFailure` | `String` | The evaluation failure of the decision instance. | | `ProcessDefinitionKey` | `Nullable` | The key of the process definition. | | `ProcessInstanceKey` | `Nullable` | The key of the process instance. | | `Result` | `String` | The result of the decision instance. | | `RootDecisionDefinitionKey` | `DecisionDefinitionKey` | The key of the root decision definition. | | `RootProcessInstanceKey` | `Nullable` | The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. | | `State` | `DecisionInstanceStateEnum` | The state of the decision instance. UNSPECIFIED and UNKNOWN are deprecated and should not be used anymore, for removal in 8.10 | | `TenantId` | `TenantId` | The tenant ID of the decision instance. | ## DecisionInstanceSearchQuery ```csharp public sealed class DecisionInstanceSearchQuery ``` | Property | Type | Description | | -------- | ---------------------------------------------- | ------------------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `DecisionInstanceFilter` | The decision instance search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## DecisionInstanceSearchQueryResult ```csharp public sealed class DecisionInstanceSearchQueryResult ``` | Property | Type | Description | | -------- | ------------------------------ | ------------------------------------------------ | | `Items` | `List` | The matching decision instances. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## DecisionInstanceSearchQuerySortRequest ```csharp public sealed class DecisionInstanceSearchQuerySortRequest ``` | Property | Type | Description | | -------- | --------------------------------------------- | --------------------------------------------- | | `Field` | `DecisionInstanceSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## DecisionInstanceStateExactMatch Matches the value exactly. ```csharp public readonly record struct DecisionInstanceStateExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## DecisionInstanceStateFilterProperty DecisionInstanceStateEnum property with full advanced search capabilities. ```csharp public sealed class DecisionInstanceStateFilterProperty ``` | Property | Type | Description | | ------------ | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## DecisionRequirementsFilter Decision requirements search filter. ```csharp public sealed class DecisionRequirementsFilter ``` | Property | Type | Description | | -------------------------- | ----------------------------------- | ------------------------------------------------------------------------- | | `DecisionRequirementsName` | `String` | The DMN name of the decision requirements. | | `DecisionRequirementsId` | `String` | the DMN ID of the decision requirements. | | `DecisionRequirementsKey` | `Nullable` | System-generated key for a deployed decision requirements definition. | | `Version` | `Nullable` | The assigned version of the decision requirements. | | `TenantId` | `Nullable` | The tenant ID of the decision requirements. | | `ResourceName` | `String` | The name of the resource from which the decision requirements were parsed | ## DecisionRequirementsKeyExactMatch Matches the value exactly. ```csharp public readonly record struct DecisionRequirementsKeyExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## DecisionRequirementsKeyFilterProperty DecisionRequirementsKey property with full advanced search capabilities. ```csharp public sealed class DecisionRequirementsKeyFilterProperty ``` | Property | Type | Description | | ------------ | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## DecisionRequirementsResult ```csharp public sealed class DecisionRequirementsResult ``` | Property | Type | Description | | -------------------------- | ------------------------- | ----------------------------------------------------------------------------------- | | `DecisionRequirementsId` | `String` | The DMN ID of the decision requirements. | | `DecisionRequirementsKey` | `DecisionRequirementsKey` | The assigned key, which acts as a unique identifier for this decision requirements. | | `DecisionRequirementsName` | `String` | The DMN name of the decision requirements. | | `ResourceName` | `String` | The name of the resource from which this decision requirements was parsed. | | `TenantId` | `TenantId` | The tenant ID of the decision requirements. | | `Version` | `Int32` | The assigned version of the decision requirements. | ## DecisionRequirementsSearchQuery ```csharp public sealed class DecisionRequirementsSearchQuery ``` | Property | Type | Description | | -------- | -------------------------------------------------- | --------------------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `DecisionRequirementsFilter` | The decision definition search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## DecisionRequirementsSearchQueryResult ```csharp public sealed class DecisionRequirementsSearchQueryResult ``` | Property | Type | Description | | -------- | ---------------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching decision requirements. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## DecisionRequirementsSearchQuerySortRequest ```csharp public sealed class DecisionRequirementsSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ------------------------------------------------- | --------------------------------------------- | | `Field` | `DecisionRequirementsSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## DeleteDecisionInstanceRequest ```csharp public sealed class DeleteDecisionInstanceRequest ``` | Property | Type | Description | | -------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | ## DeleteProcessInstanceRequest ```csharp public sealed class DeleteProcessInstanceRequest ``` | Property | Type | Description | | -------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | ## DeleteResourceRequest ```csharp public sealed class DeleteResourceRequest ``` | Property | Type | Description | | -------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | | `DeleteHistory` | `Nullable` | Indicates if the historic data associated with the resource should also be deleted asynchronously. This flag is effective for process definitions and decision requirements definitions. For other resource types (forms, generic resources) it is ignored and no history is deleted. For a decision requirements definition the `batchOperation` field in the response carries the created batch operation. For a process definition the history is deleted as part of the definition's draining/deletion lifecycle and no batch operation is returned. | ## DeleteResourceResponse ```csharp public sealed class DeleteResourceResponse ``` | Property | Type | Description | | ---------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ResourceKey` | `ResourceKey` | The system-assigned key for this resource, requested to be deleted. | | `BatchOperation` | `BatchOperationCreatedResult` | The batch operation created for asynchronously deleting the historic data. Populated when `deleteHistory` is `true` and either the resource is a decision requirements definition, or the resource is a process definition that is already fully deleted from the runtime state (its history is purged directly by a batch operation). For a process definition that still exists in the runtime state, deletion first drains the definition and its history is removed asynchronously as part of that lifecycle, so no batch operation is returned and this field is `null`. It is also `null` for forms and generic resources. | ## DeploymentConfigurationResponse Configuration for deployment characteristics. ```csharp public sealed class DeploymentConfigurationResponse ``` | Property | Type | Description | | ----------------------- | --------- | ------------------------------------------------------ | | `IsMultiTenancyEnabled` | `Boolean` | Whether multi-tenancy is enabled. | | `IsTenantsApiEnabled` | `Boolean` | Whether the tenants management API and UI are enabled. | | `MaxRequestSize` | `Int64` | The maximum HTTP request size in bytes. | ## DeploymentDecisionRequirementsResult Deployed decision requirements. ```csharp public sealed class DeploymentDecisionRequirementsResult ``` | Property | Type | Description | | -------------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------- | | `DecisionRequirementsId` | `String` | The id of the deployed decision requirements. | | `DecisionRequirementsName` | `String` | The name of the deployed decision requirements. | | `Version` | `Int32` | The version of the deployed decision requirements. | | `ResourceName` | `String` | The name of the resource. | | `TenantId` | `TenantId` | The tenant ID of the deployed decision requirements. | | `DecisionRequirementsKey` | `DecisionRequirementsKey` | The assigned decision requirements key, which acts as a unique identifier for this decision requirements. | ## DeploymentDecisionResult A deployed decision. ```csharp public sealed class DeploymentDecisionResult ``` | Property | Type | Description | | ------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `DecisionDefinitionId` | `DecisionDefinitionId` | The dmn decision ID, as parsed during deployment, together with the version forms a unique identifier for a specific decision. | | `Version` | `Int32` | The assigned decision version. | | `Name` | `String` | The DMN name of the decision, as parsed during deployment. | | `TenantId` | `TenantId` | The tenant ID of the deployed decision. | | `DecisionRequirementsId` | `String` | The dmn ID of the decision requirements graph that this decision is part of, as parsed during deployment. | | `DecisionDefinitionKey` | `DecisionDefinitionKey` | The assigned decision key, which acts as a unique identifier for this decision. | | `DecisionRequirementsKey` | `DecisionRequirementsKey` | The assigned key of the decision requirements graph that this decision is part of. | ## DeploymentFormResult A deployed form. ```csharp public sealed class DeploymentFormResult ``` | Property | Type | Description | | -------------- | ---------- | ------------------------------------------------------------------------------------------------------------------ | | `FormId` | `FormId` | The form ID, as parsed during deployment, together with the version forms a unique identifier for a specific form. | | `Version` | `Int32` | The version of the deployed form. | | `ResourceName` | `String` | The name of the resource. | | `TenantId` | `TenantId` | The unique identifier of the tenant. | | `FormKey` | `FormKey` | The assigned key, which acts as a unique identifier for this form. | ## DeploymentKeyExactMatch Matches the value exactly. ```csharp public readonly record struct DeploymentKeyExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## DeploymentKeyFilterProperty DeploymentKey property with full advanced search capabilities. ```csharp public sealed class DeploymentKeyFilterProperty ``` | Property | Type | Description | | ------------ | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## DeploymentMetadataResult ```csharp public sealed class DeploymentMetadataResult ``` | Property | Type | Description | | ---------------------- | -------------------------------------- | ----------------------------------------- | | `ProcessDefinition` | `DeploymentProcessResult` | Deployed process. | | `DecisionDefinition` | `DeploymentDecisionResult` | Deployed decision. | | `DecisionRequirements` | `DeploymentDecisionRequirementsResult` | Deployed decision requirement definition. | | `Form` | `DeploymentFormResult` | Deployed form. | | `Resource` | `DeploymentResourceResult` | Deployed resource. | ## DeploymentProcessResult A deployed process. ```csharp public sealed class DeploymentProcessResult ``` | Property | Type | Description | | -------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `ProcessDefinitionId` | `ProcessDefinitionId` | The bpmn process ID, as parsed during deployment, together with the version forms a unique identifier for a specific process definition. | | `ProcessDefinitionVersion` | `Int32` | The assigned process version. | | `ResourceName` | `String` | The resource name from which this process was parsed. | | `TenantId` | `TenantId` | The tenant ID of the deployed process. | | `ProcessDefinitionKey` | `ProcessDefinitionKey` | The assigned key, which acts as a unique identifier for this process. | ## DeploymentResourceResult A deployed Resource. ```csharp public sealed class DeploymentResourceResult ``` | Property | Type | Description | | -------------- | ------------- | ---------------------------------------------------------------------- | | `ResourceId` | `String` | The resource id of the deployed resource. | | `ResourceName` | `String` | The name of the deployed resource. | | `Version` | `Int32` | The description of the deployed resource. | | `TenantId` | `TenantId` | The unique identifier of the tenant. | | `ResourceKey` | `ResourceKey` | The assigned key, which acts as a unique identifier for this Resource. | ## DeploymentResult ```csharp public sealed class DeploymentResult ``` | Property | Type | Description | | --------------- | -------------------------------- | --------------------------------------------- | | `DeploymentKey` | `DeploymentKey` | The unique key identifying the deployment. | | `TenantId` | `TenantId` | The tenant ID associated with the deployment. | | `Deployments` | `List` | Items deployed by the request. | ## DirectAncestorKeyInstruction Provides a concrete key to use as ancestor scope for the created element instance. ```csharp public sealed class DirectAncestorKeyInstruction : AncestorScopeInstruction ``` | Property | Type | Description | | ---------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `AncestorElementInstanceKey` | `ElementInstanceKey` | The key of the ancestor scope the element instance should be created in. Set to -1 to create the new element instance within an existing element instance of the flow scope. If multiple instances of the target element's flow scope exist, choose one specifically with this property by providing its key. | ## DocumentCreationBatchResponse ```csharp public sealed class DocumentCreationBatchResponse ``` | Property | Type | Description | | ------------------ | ------------------------------------- | ----------------------------------------- | | `FailedDocuments` | `List` | Documents that were successfully created. | | `CreatedDocuments` | `List` | Documents that failed creation. | ## DocumentCreationFailureDetail ```csharp public sealed class DocumentCreationFailureDetail ``` | Property | Type | Description | | ---------- | -------- | ------------------------------------------------------------------------ | | `FileName` | `String` | The name of the file that failed to upload. | | `Status` | `Int32` | The HTTP status code of the failure. | | `Title` | `String` | A short, human-readable summary of the problem type. | | `Detail` | `String` | A human-readable explanation specific to this occurrence of the problem. | ## DocumentId Document Id that uniquely identifies a document. ```csharp public readonly record struct DocumentId : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## DocumentLink ```csharp public sealed class DocumentLink ``` | Property | Type | Description | | ----------- | ---------------- | ---------------------------------------- | | `Url` | `String` | The link to the document. | | `ExpiresAt` | `DateTimeOffset` | The date and time when the link expires. | ## DocumentLinkRequest ```csharp public sealed class DocumentLinkRequest ``` | Property | Type | Description | | ------------ | ----------------- | -------------------------------------------- | | `TimeToLive` | `Nullable` | The time-to-live of the document link in ms. | ## DocumentMetadata Information about the document. ```csharp public sealed class DocumentMetadata ``` | Property | Type | Description | | --------------------- | ------------------------------- | ----------------------------------------------------------- | | `ContentType` | `String` | The content type of the document. | | `FileName` | `String` | The name of the file. | | `ExpiresAt` | `Nullable` | The date and time when the document expires. | | `Size` | `Nullable` | The size of the document in bytes. | | `ProcessDefinitionId` | `Nullable` | The ID of the process definition that created the document. | | `ProcessInstanceKey` | `Nullable` | The key of the process instance that created the document. | | `CustomProperties` | `Object` | Custom properties of the document. | ## DocumentMetadataResponse Information about the document that is returned in responses. ```csharp public sealed class DocumentMetadataResponse ``` | Property | Type | Description | | --------------------- | ------------------------------- | ----------------------------------------------------------- | | `ContentType` | `String` | The content type of the document. | | `FileName` | `String` | The name of the file. | | `ExpiresAt` | `Nullable` | The date and time when the document expires. | | `Size` | `Int64` | The size of the document in bytes. | | `ProcessDefinitionId` | `Nullable` | The ID of the process definition that created the document. | | `ProcessInstanceKey` | `Nullable` | The key of the process instance that created the document. | | `CustomProperties` | `Object` | Custom properties of the document. | ## DocumentReference ```csharp public sealed class DocumentReference ``` | Property | Type | Description | | --------------------- | -------------------------------------- | ------------------------------------------------------------- | | `CamundaDocumentType` | `DocumentReferenceCamundaDocumentType` | Document discriminator. Always set to "camunda". | | `StoreId` | `String` | The ID of the document store. | | `DocumentId` | `DocumentId` | The ID of the document. | | `ContentHash` | `String` | The hash of the document. | | `Metadata` | `DocumentMetadataResponse` | Information about the document that is returned in responses. | ## ElementId The model-defined id of an element. ```csharp public readonly record struct ElementId : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## ElementIdExactMatch Matches the value exactly. ```csharp public readonly record struct ElementIdExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## ElementIdFilterProperty ElementId property with full advanced search capabilities. ```csharp public sealed class ElementIdFilterProperty ``` | Property | Type | Description | | ------------ | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## ElementInstanceFilter Element instance search filter. ```csharp public sealed class ElementInstanceFilter ``` | Property | Type | Description | | ------------------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ProcessDefinitionId` | `Nullable` | The process definition ID associated to this element instance. | | `State` | `ElementInstanceStateFilterProperty` | State of element instance as defined set of values. | | `Type` | `Nullable` | Type of element as defined set of values. | | `ElementId` | `ElementIdFilterProperty` | The element ID for this element instance. | | `ElementName` | `StringFilterProperty` | The element name. This only works for data created with 8.8 and onwards. Instances from prior versions don't contain this data and cannot be found. | | `HasIncident` | `Nullable` | Shows whether this element instance has an incident related to. | | `TenantId` | `Nullable` | The unique identifier of the tenant. | | `ElementInstanceKey` | `Nullable` | The assigned key, which acts as a unique identifier for this element instance. | | `ProcessInstanceKey` | `Nullable` | The process instance key associated to this element instance. | | `ProcessDefinitionKey` | `Nullable` | The process definition key associated to this element instance. | | `IncidentKey` | `Nullable` | The key of incident if field incident is true. | | `StartDate` | `DateTimeFilterProperty` | The start date of this element instance. | | `EndDate` | `DateTimeFilterProperty` | The end date of this element instance. | | `ElementInstanceScopeKey` | `String` | The scope key of this element instance. If provided with a process instance key it will return element instances that are immediate children of the process instance. If provided with an element instance key it will return element instances that are immediate children of the element instance. | | `Or` | `List` | Defines a list of alternative filter groups combined using OR logic. Each object in the array is evaluated independently, and the filter matches if any one of them is satisfied. Top-level fields and the `$or` clause are combined using AND logic — meaning: (top-level filters) AND (any of the `$or` filters) must match. Example: `json { "processInstanceKey": "2251799813685323", "$or": [ { "elementName": { "$like": "*Order*" } }, { "elementId": { "$like": "*Order*" } } ] } ` This matches element instances scoped to the given process instance whose: elementName contains Order, or elementId contains Order Note: Using complex $or conditions may impact performance, use with caution in high-volume environments. | ## ElementInstanceFilterFields Element instance filter fields. ```csharp public sealed class ElementInstanceFilterFields ``` | Property | Type | Description | | ------------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ProcessDefinitionId` | `Nullable` | The process definition ID associated to this element instance. | | `State` | `ElementInstanceStateFilterProperty` | State of element instance as defined set of values. | | `Type` | `Nullable` | Type of element as defined set of values. | | `ElementId` | `ElementIdFilterProperty` | The element ID for this element instance. | | `ElementName` | `StringFilterProperty` | The element name. This only works for data created with 8.8 and onwards. Instances from prior versions don't contain this data and cannot be found. | | `HasIncident` | `Nullable` | Shows whether this element instance has an incident related to. | | `TenantId` | `Nullable` | The unique identifier of the tenant. | | `ElementInstanceKey` | `Nullable` | The assigned key, which acts as a unique identifier for this element instance. | | `ProcessInstanceKey` | `Nullable` | The process instance key associated to this element instance. | | `ProcessDefinitionKey` | `Nullable` | The process definition key associated to this element instance. | | `IncidentKey` | `Nullable` | The key of incident if field incident is true. | | `StartDate` | `DateTimeFilterProperty` | The start date of this element instance. | | `EndDate` | `DateTimeFilterProperty` | The end date of this element instance. | | `ElementInstanceScopeKey` | `String` | The scope key of this element instance. If provided with a process instance key it will return element instances that are immediate children of the process instance. If provided with an element instance key it will return element instances that are immediate children of the element instance. | ## ElementInstanceKeyExactMatch Matches the value exactly. ```csharp public readonly record struct ElementInstanceKeyExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## ElementInstanceKeyFilterProperty ElementInstanceKey property with full advanced search capabilities. ```csharp public sealed class ElementInstanceKeyFilterProperty ``` | Property | Type | Description | | ------------ | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## ElementInstanceResult ```csharp public sealed class ElementInstanceResult ``` | Property | Type | Description | | ------------------------ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ProcessDefinitionId` | `ProcessDefinitionId` | The process definition ID associated to this element instance. | | `StartDate` | `DateTimeOffset` | Date when element instance started. | | `EndDate` | `Nullable` | Date when element instance finished. | | `ElementId` | `ElementId` | The element ID for this element instance. | | `ElementName` | `String` | The element name for this element instance. | | `Type` | `ElementInstanceResultType` | Type of element as defined set of values. | | `State` | `ElementInstanceStateEnum` | State of element instance as defined set of values. | | `HasIncident` | `Boolean` | Shows whether this element instance has an incident. If true also an incidentKey is provided. | | `TenantId` | `TenantId` | The tenant ID of the incident. | | `ElementInstanceKey` | `ElementInstanceKey` | The assigned key, which acts as a unique identifier for this element instance. | | `ProcessInstanceKey` | `ProcessInstanceKey` | The process instance key associated to this element instance. | | `RootProcessInstanceKey` | `Nullable` | The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. | | `ProcessDefinitionKey` | `ProcessDefinitionKey` | The process definition key associated to this element instance. | | `IncidentKey` | `Nullable` | Incident key associated with this element instance. | ## ElementInstanceSearchQuery Element instance search request. ```csharp public sealed class ElementInstanceSearchQuery ``` | Property | Type | Description | | -------- | --------------------------------------------- | ------------------------------------ | | `Sort` | `List` | Sort field criteria. | | `Filter` | `ElementInstanceFilter` | The element instance search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## ElementInstanceSearchQueryResult ```csharp public sealed class ElementInstanceSearchQueryResult ``` | Property | Type | Description | | -------- | ----------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching element instances. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## ElementInstanceSearchQuerySortRequest ```csharp public sealed class ElementInstanceSearchQuerySortRequest ``` | Property | Type | Description | | -------- | -------------------------------------------- | --------------------------------------------- | | `Field` | `ElementInstanceSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## ElementInstanceStateExactMatch Matches the value exactly. ```csharp public readonly record struct ElementInstanceStateExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## ElementInstanceStateFilterProperty ElementInstanceStateEnum property with full advanced search capabilities. ```csharp public sealed class ElementInstanceStateFilterProperty ``` | Property | Type | Description | | ------------ | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## ElementInstanceWaitStateFilter Filters for the element instance inspection. ```csharp public sealed class ElementInstanceWaitStateFilter ``` | Property | Type | Description | | ------------------------ | ------------------------------------ | ------------------------------------ | | `ElementInstanceKey` | `ElementInstanceKeyFilterProperty` | Filter by element instance key. | | `ProcessInstanceKey` | `ProcessInstanceKeyFilterProperty` | Filter by process instance key. | | `RootProcessInstanceKey` | `ProcessInstanceKeyFilterProperty` | Filter by root process instance key. | | `ElementId` | `ElementIdFilterProperty` | Filter by element ID. | | `ElementType` | `WaitStateElementTypeFilterProperty` | Filter by element type. | | `WaitStateType` | `WaitStateTypeFilterProperty` | Filter by wait state type. | ## ElementInstanceWaitStateQuery Element instance inspection request. ```csharp public sealed class ElementInstanceWaitStateQuery ``` | Property | Type | Description | | -------- | ------------------------------------------------ | ----------------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `ElementInstanceWaitStateFilter` | Filter criteria for the inspection. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## ElementInstanceWaitStateQueryResult ```csharp public sealed class ElementInstanceWaitStateQueryResult ``` | Property | Type | Description | | -------- | -------------------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching waiting states. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## ElementInstanceWaitStateQuerySortRequest ```csharp public sealed class ElementInstanceWaitStateQuerySortRequest ``` | Property | Type | Description | | -------- | ----------------------------------------------- | --------------------------------------------- | | `Field` | `ElementInstanceWaitStateQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## ElementInstanceWaitStateResult An element instance waiting state. ```csharp public sealed class ElementInstanceWaitStateResult ``` | Property | Type | Description | | ------------------------ | ------------------------------ | ---------------------------------------------------------------------------------- | | `RootProcessInstanceKey` | `Nullable` | Key of the root process instance. | | `ProcessInstanceKey` | `ProcessInstanceKey` | The process instance key associated to this element instance. | | `ElementInstanceKey` | `ElementInstanceKey` | The element instance key associated to this element instance. | | `ElementId` | `ElementId` | The element ID for this element instance. | | `ElementType` | `WaitStateElementTypeEnum` | The BPMN element type of this element instance. | | `TenantId` | `TenantId` | The tenant ID of the element instance. | | `BpmnProcessId` | `String` | The BPMN process ID of the process definition associated to this element instance. | | `Details` | `WaitStateDetails` | Wait-state-specific details, resolved by waitStateType. | ## EndCursor The end cursor in a search query result set. ```csharp public readonly record struct EndCursor : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## EngineTimeProvider A `TimeProvider` bound to the engine's own clock, so client cadence and engine time advance together. The engine has long been pinnable through `PUT /clock`, and since the injected `TimeProvider` landed the client's cadence is virtualisable too — but the two were separate. Pinning the engine left worker poll loops on their own timeline, so a worker waiting on something that never became ready burned real seconds inside a test that was otherwise deterministic. This provider closes that. Every delay taken through it — the worker poll loop, eventual-consistency polling, retry backoff, OAuth refresh — moves engine time forward instead of waiting, so cadence drives the engine rather than racing it and a test that would have taken a real minute finishes as fast as the requests complete. Intended for tests and embedded scenarios that own the engine. Pinning is global to the cluster, so never point one of these at a shared environment, and always `EngineTimeProvider.ResetAsync` when finished. The target must be a client that is not itself using this provider. HTTP retry backs off on its client's clock, so a self-referential setup would have a failed pin retry through a delay, which issues another pin. ```csharp public sealed class EngineTimeProvider : TimeProvider, IDisposable ``` ## EntityTypeExactMatch Matches the value exactly. ```csharp public readonly record struct EntityTypeExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## EntityTypeFilterProperty AuditLogEntityTypeEnum property with full advanced search capabilities. ```csharp public sealed class EntityTypeFilterProperty ``` | Property | Type | Description | | ------------ | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## EvaluateConditionalResult ```csharp public sealed class EvaluateConditionalResult ``` | Property | Type | Description | | -------------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `ConditionalEvaluationKey` | `ConditionalEvaluationKey` | The unique key of the conditional evaluation operation. | | `TenantId` | `TenantId` | The tenant ID of the conditional evaluation operation. | | `ProcessInstances` | `List` | List of process instances created. If no root-level conditional start events evaluated to true, the list will be empty. | ## EvaluateDecisionResult ```csharp public sealed class EvaluateDecisionResult ``` | Property | Type | Description | | ---------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------ | | `DecisionDefinitionId` | `DecisionDefinitionId` | The ID of the decision which was evaluated. | | `DecisionDefinitionKey` | `DecisionDefinitionKey` | The unique key identifying the decision which was evaluated. | | `DecisionDefinitionName` | `String` | The name of the decision which was evaluated. | | `DecisionDefinitionVersion` | `Int32` | The version of the decision which was evaluated. | | `DecisionEvaluationKey` | `DecisionEvaluationKey` | The unique key identifying this decision evaluation. | | `DecisionInstanceKey` | `DecisionInstanceKey` | Deprecated, please refer to `decisionEvaluationKey`. | | `DecisionRequirementsId` | `String` | The ID of the decision requirements graph that the decision which was evaluated is part of. | | `DecisionRequirementsKey` | `DecisionRequirementsKey` | The unique key identifying the decision requirements graph that the decision which was evaluated is part of. | | `EvaluatedDecisions` | `List` | Decisions that were evaluated within the requested decision evaluation. | | `FailedDecisionDefinitionId` | `Nullable` | The ID of the decision which failed during evaluation. | | `FailureMessage` | `String` | Message describing why the decision which was evaluated failed. | | `Output` | `String` | JSON document that will instantiate the result of the decision which was evaluated. | | `TenantId` | `TenantId` | The tenant ID of the evaluated decision. | ## EvaluatedDecisionInputItem A decision input that was evaluated within this decision evaluation. ```csharp public sealed class EvaluatedDecisionInputItem ``` | Property | Type | Description | | ------------ | -------- | ------------------------------------- | | `InputId` | `String` | The identifier of the decision input. | | `InputName` | `String` | The name of the decision input. | | `InputValue` | `String` | The value of the decision input. | ## EvaluatedDecisionOutputItem The evaluated decision outputs. ```csharp public sealed class EvaluatedDecisionOutputItem ``` | Property | Type | Description | | ------------- | ----------------- | ----------------------------------------------------- | | `OutputId` | `String` | The ID of the evaluated decison output item. | | `OutputName` | `String` | The name of the of the evaluated decison output item. | | `OutputValue` | `String` | The value of the evaluated decison output item. | | `RuleId` | `String` | The ID of the matched rule. | | `RuleIndex` | `Nullable` | The index of the matched rule. | ## EvaluatedDecisionResult A decision that was evaluated. ```csharp public sealed class EvaluatedDecisionResult ``` | Property | Type | Description | | ------------------------------- | ---------------------------------- | ----------------------------------------------------------------------------------- | | `DecisionDefinitionId` | `DecisionDefinitionId` | The ID of the decision which was evaluated. | | `DecisionDefinitionName` | `String` | The name of the decision which was evaluated. | | `DecisionDefinitionVersion` | `Int32` | The version of the decision which was evaluated. | | `DecisionDefinitionType` | `String` | The type of the decision which was evaluated. | | `Output` | `String` | JSON document that will instantiate the result of the decision which was evaluated. | | `TenantId` | `TenantId` | The tenant ID of the evaluated decision. | | `MatchedRules` | `List` | The decision rules that matched within this decision evaluation. | | `EvaluatedInputs` | `List` | The decision inputs that were evaluated within this decision evaluation. | | `DecisionDefinitionKey` | `DecisionDefinitionKey` | The unique key identifying the decision which was evaluate. | | `DecisionEvaluationInstanceKey` | `DecisionEvaluationInstanceKey` | The unique key identifying this decision evaluation instance. | ## EventualConsistencyTimeoutException Thrown when an eventually consistent endpoint times out waiting for data. ```csharp public sealed class EventualConsistencyTimeoutException : CamundaSdkException, ISerializable ``` | Property | Type | Description | | ---------- | ------- | ----------- | | `WaitedMs` | `Int32` | | ## ExportingStatusResponse Response body for the exporting status of a physical tenant. ```csharp public sealed class ExportingStatusResponse ``` | Property | Type | Description | | -------- | --------------------- | ------------------------------------------------------- | | `Status` | `ExportingStatusCode` | The aggregated exporting status of the physical tenant. | ## ExpressionEvaluationRequest ```csharp public sealed class ExpressionEvaluationRequest : ITenantIdSettable ``` | Property | Type | Description | | ------------ | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Expression` | `String` | The expression to evaluate (e.g., "=x + y") | | `TenantId` | `String` | Required when the expression references tenant-scoped cluster variables | | `ScopeKey` | `Nullable` | Key of the process instance or element instance whose variables should be made visible to the expression. Use a process instance key to evaluate against the process instance scope, or an element instance key to evaluate against that element instance scope. If omitted, the expression is evaluated unscoped, using only cluster variables and request-body variables. | | `Variables` | `Object` | Optional variables for expression evaluation. These variables are only used for the current evaluation and do not persist beyond it. | ## ExpressionEvaluationResult ```csharp public sealed class ExpressionEvaluationResult ``` | Property | Type | Description | | ------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Expression` | `String` | The evaluated expression | | `Result` | `Object` | The result value. Its type can vary. | | `Warnings` | `List` | List of warnings generated during expression evaluation | | `ReferencedSecrets` | `List` | The secret references resolved from trusted sources while evaluating the expression: a `camunda.secrets.` reference used directly in the expression, or a reference carried by a `SECRET_REFERENCE`-kind cluster variable the expression read. References appearing only in request-body variables or plain cluster variables are excluded. Callers use this to know which `camunda.secrets.` occurrences in the result they may safely resolve. | ## ExpressionEvaluationWarningItem ```csharp public sealed class ExpressionEvaluationWarningItem ``` | Property | Type | Description | | --------- | -------- | ------------------- | | `Message` | `String` | The warning message | ## ExpressionSecretReferenceItem ```csharp public sealed class ExpressionSecretReferenceItem ``` | Property | Type | Description | | ------------ | -------- | ------------------------------------------------------------------- | | `StoreId` | `String` | The identifier of the secret store that holds the referenced secret | | `SecretName` | `String` | The secret name, e.g. "token" for "camunda.secrets.token" | ## ExtendedDeploymentResponse Extended deployment result with typed convenience properties for direct access to deployed artifacts by category (processes, decisions, forms, etc.). ```csharp public sealed class ExtendedDeploymentResponse ``` | Property | Type | Description | | ---------------------- | -------------------------------------------- | --------------------------------------------- | | `Raw` | `DeploymentResult` | The underlying raw deployment response. | | `DeploymentKey` | `DeploymentKey` | The unique key identifying the deployment. | | `TenantId` | `TenantId` | The tenant ID associated with the deployment. | | `Deployments` | `List` | All items deployed by the request. | | `Processes` | `List` | Deployed process definitions. | | `Decisions` | `List` | Deployed decision definitions. | | `DecisionRequirements` | `List` | Deployed decision requirements. | | `Forms` | `List` | Deployed forms. | | `Resources` | `List` | Deployed resources. | ## FormId The user-defined id for the form ```csharp public readonly record struct FormId : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## FormKeyExactMatch Matches the value exactly. ```csharp public readonly record struct FormKeyExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## FormKeyFilterProperty FormKey property with full advanced search capabilities. ```csharp public sealed class FormKeyFilterProperty ``` | Property | Type | Description | | ------------ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## FormResult ```csharp public sealed class FormResult ``` | Property | Type | Description | | ---------- | ---------- | ------------------------------------------------------------------ | | `TenantId` | `TenantId` | The tenant ID of the form. | | `FormId` | `FormId` | The user-provided identifier of the form. | | `Schema` | `String` | The form schema as a JSON document serialized as a string. | | `Version` | `Int64` | The version of the the deployed form. | | `FormKey` | `FormKey` | The assigned key, which acts as a unique identifier for this form. | ## GlobalJobStatisticsQueryResult Global job statistics query result. ```csharp public sealed class GlobalJobStatisticsQueryResult ``` | Property | Type | Description | | -------------- | -------------- | ----------------------------------------------------------------------------------------------------- | | `Created` | `StatusMetric` | Metric for a single job status. | | `Completed` | `StatusMetric` | Metric for a single job status. | | `Failed` | `StatusMetric` | Metric for a single job status. | | `IsIncomplete` | `Boolean` | True if some data is missing because internal limits were reached and some metrics were not recorded. | ## GlobalListenerBase ```csharp public sealed class GlobalListenerBase ``` | Property | Type | Description | | ---------------- | ------------------- | --------------------------------------------------------------------------------------------------------------- | | `Type` | `String` | The name of the job type, used as a reference to specify which job workers request the respective listener job. | | `Retries` | `Nullable` | Number of retries for the listener job. | | `AfterNonGlobal` | `Nullable` | Whether the listener should run after model-level listeners. | | `Priority` | `Nullable` | The priority of the listener. Higher priority listeners are executed before lower priority ones. | ## GlobalListenerId The user-defined id for the global listener ```csharp public readonly record struct GlobalListenerId : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## GlobalListenerSourceExactMatch Matches the value exactly. ```csharp public readonly record struct GlobalListenerSourceExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## GlobalListenerSourceFilterProperty Global listener source property with full advanced search capabilities. ```csharp public sealed class GlobalListenerSourceFilterProperty ``` | Property | Type | Description | | ------------ | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## GlobalTaskListenerBase ```csharp public sealed class GlobalTaskListenerBase ``` | Property | Type | Description | | ---------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | `EventTypes` | `List` | List of user task event types that trigger the listener. | | `Type` | `String` | The name of the job type, used as a reference to specify which job workers request the respective listener job. | | `Retries` | `Nullable` | Number of retries for the listener job. | | `AfterNonGlobal` | `Nullable` | Whether the listener should run after model-level listeners. | | `Priority` | `Nullable` | The priority of the listener. Higher priority listeners are executed before lower priority ones. | ## GlobalTaskListenerEventTypeExactMatch Matches the value exactly. ```csharp public readonly record struct GlobalTaskListenerEventTypeExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## GlobalTaskListenerEventTypeFilterProperty Global listener event type property with full advanced search capabilities. ```csharp public sealed class GlobalTaskListenerEventTypeFilterProperty ``` | Property | Type | Description | | ------------ | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## GlobalTaskListenerResult ```csharp public sealed class GlobalTaskListenerResult ``` | Property | Type | Description | | ---------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | `Id` | `GlobalListenerId` | The user-defined id for the global listener | | `Source` | `GlobalListenerSourceEnum` | How the global listener was defined. | | `EventTypes` | `List` | List of user task event types that trigger the listener. | | `Type` | `String` | The name of the job type, used as a reference to specify which job workers request the respective listener job. | | `Retries` | `Int32` | Number of retries for the listener job. | | `AfterNonGlobal` | `Boolean` | Whether the listener should run after model-level listeners. | | `Priority` | `Int32` | The priority of the listener. Higher priority listeners are executed before lower priority ones. | ## GlobalTaskListenerSearchQueryFilterRequest Global listener filter request. ```csharp public sealed class GlobalTaskListenerSearchQueryFilterRequest ``` | Property | Type | Description | | ---------------- | ------------------------------------------------- | ------------------------------------------------------ | | `Id` | `StringFilterProperty` | Id of the global listener. | | `Type` | `StringFilterProperty` | Job type of the global listener. | | `Retries` | `IntegerFilterProperty` | Number of retries of the global listener. | | `EventTypes` | `List` | Event types of the global listener. | | `AfterNonGlobal` | `Nullable` | Whether the listener runs after model-level listeners. | | `Priority` | `IntegerFilterProperty` | Priority of the global listener. | | `Source` | `GlobalListenerSourceFilterProperty` | How the global listener was defined. | ## GlobalTaskListenerSearchQueryRequest Global listener search query request. ```csharp public sealed class GlobalTaskListenerSearchQueryRequest ``` | Property | Type | Description | | -------- | ------------------------------------------------ | ----------------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `GlobalTaskListenerSearchQueryFilterRequest` | The global listener search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## GlobalTaskListenerSearchQueryResult Global listener search query response. ```csharp public sealed class GlobalTaskListenerSearchQueryResult ``` | Property | Type | Description | | -------- | -------------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching global listeners. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## GlobalTaskListenerSearchQuerySortRequest ```csharp public sealed class GlobalTaskListenerSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ----------------------------------------------- | --------------------------------------------- | | `Field` | `GlobalTaskListenerSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## GroupClientResult ```csharp public sealed class GroupClientResult ``` | Property | Type | Description | | ---------- | ---------- | --------------------- | | `ClientId` | `ClientId` | The ID of the client. | ## GroupClientSearchQueryRequest ```csharp public sealed class GroupClientSearchQueryRequest ``` | Property | Type | Description | | -------- | ----------------------------------------- | -------------------- | | `Sort` | `List` | Sort field criteria. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## GroupClientSearchQuerySortRequest ```csharp public sealed class GroupClientSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ---------------------------------------- | --------------------------------------------- | | `Field` | `GroupClientSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## GroupClientSearchResult ```csharp public sealed class GroupClientSearchResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching client IDs. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## GroupCreateRequest ```csharp public sealed class GroupCreateRequest ``` | Property | Type | Description | | ------------- | --------- | ---------------------------------- | | `GroupId` | `GroupId` | The ID of the new group. | | `Name` | `String` | The display name of the new group. | | `Description` | `String` | The description of the new group. | ## GroupCreateResult ```csharp public sealed class GroupCreateResult ``` | Property | Type | Description | | ------------- | --------- | -------------------------------------- | | `GroupId` | `GroupId` | The ID of the created group. | | `Name` | `String` | The display name of the created group. | | `Description` | `String` | The description of the created group. | ## GroupFilter Group filter request ```csharp public sealed class GroupFilter ``` | Property | Type | Description | | --------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `GroupId` | `StringFilterProperty` | The group ID search filters. | | `Name` | `StringFilterProperty` | The group name search filters. | | `Or` | `List` | Defines a list of alternative filter groups combined using OR logic. Each object in the array is evaluated independently, and the filter matches if any one of them is satisfied. Top-level fields and the `$or` clause are combined using AND logic — meaning: (top-level filters) AND (any of the `$or` filters) must match. Example: `json { "$or": [ { "groupId": "group-1" }, { "groupId": "group-2" } ] } ` This matches groups whose groupId is group-1 or group-2. Note: Using complex $or conditions may impact performance, use with caution in high-volume environments. | ## GroupFilterFields Group filter request ```csharp public sealed class GroupFilterFields ``` | Property | Type | Description | | --------- | ---------------------- | ------------------------------ | | `GroupId` | `StringFilterProperty` | The group ID search filters. | | `Name` | `StringFilterProperty` | The group name search filters. | ## GroupId The unique identifier of a group. ```csharp public readonly record struct GroupId : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## GroupMappingRuleSearchResult ```csharp public sealed class GroupMappingRuleSearchResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching mapping rules. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## GroupResult Group search response item. ```csharp public sealed class GroupResult ``` | Property | Type | Description | | ------------- | --------- | ---------------------- | | `Name` | `String` | The group name. | | `GroupId` | `GroupId` | The group ID. | | `Description` | `String` | The group description. | ## GroupRoleSearchResult ```csharp public sealed class GroupRoleSearchResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching roles. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## GroupSearchQueryRequest Group search request. ```csharp public sealed class GroupSearchQueryRequest ``` | Property | Type | Description | | -------- | ----------------------------------- | ------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `GroupFilter` | The group search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## GroupSearchQueryResult Group search response. ```csharp public sealed class GroupSearchQueryResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching groups. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## GroupSearchQuerySortRequest ```csharp public sealed class GroupSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ---------------------------------- | --------------------------------------------- | | `Field` | `GroupSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## GroupUpdateRequest ```csharp public sealed class GroupUpdateRequest ``` | Property | Type | Description | | ------------- | -------- | --------------------------------- | | `Name` | `String` | The new name of the group. | | `Description` | `String` | The new description of the group. | ## GroupUpdateResult ```csharp public sealed class GroupUpdateResult ``` | Property | Type | Description | | ------------- | --------- | ----------------------------- | | `GroupId` | `GroupId` | The unique group ID. | | `Name` | `String` | The name of the group. | | `Description` | `String` | The description of the group. | ## GroupUserResult ```csharp public sealed class GroupUserResult ``` | Property | Type | Description | | ---------- | ---------- | -------------------------- | | `Username` | `Username` | The unique name of a user. | ## GroupUserSearchQueryRequest ```csharp public sealed class GroupUserSearchQueryRequest ``` | Property | Type | Description | | -------- | --------------------------------------- | -------------------- | | `Sort` | `List` | Sort field criteria. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## GroupUserSearchQuerySortRequest ```csharp public sealed class GroupUserSearchQuerySortRequest ``` | Property | Type | Description | | -------- | -------------------------------------- | --------------------------------------------- | | `Field` | `GroupUserSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## GroupUserSearchResult ```csharp public sealed class GroupUserSearchResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching members. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## HistoryBackupInfo Detailed status of a history backup. The aggregated state is computed from the state of each of its snapshots as: - If every expected snapshot exists and all are complete, the overall state is 'COMPLETED'. - If one snapshot failed or is partial, the overall state is 'FAILED'. - Otherwise, if one snapshot is incompatible, the overall state is 'INCOMPATIBLE'. - Otherwise, if one snapshot is still running, the overall state is 'IN_PROGRESS'. - Otherwise, if snapshots are missing and the backup has not progressed within the configured timeout, the overall state is 'INCOMPLETE'. ```csharp public sealed class HistoryBackupInfo ``` | Property | Type | Description | | --------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `BackupId` | `BackupId` | The id of the backup. | | `State` | `HistoryBackupStateCode` | The aggregated state of the backup. | | `FailureReason` | `String` | Reason for failure if the state is 'FAILED'. | | `Details` | `List` | Detailed status of the backup per snapshot. Always lists every snapshot found for the backup; when the backup was read without snapshot detail, each entry carries only its name. | ## HistoryBackupSnapshotInfo Detailed info of a single snapshot making up a history backup. ```csharp public sealed class HistoryBackupSnapshotInfo ``` | Property | Type | Description | | -------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SnapshotName` | `String` | The name of the snapshot. | | `State` | `String` | The state of the snapshot, reported verbatim by the secondary storage (for example 'SUCCESS', 'IN_PROGRESS' or 'PARTIAL'). Deliberately not a closed set: Elasticsearch and OpenSearch report different vocabularies. Not reported when the backup was listed without snapshot detail. | | `StartTime` | `Nullable` | The timestamp at which the snapshot was started. Not reported when the backup was listed without snapshot detail. | | `Failures` | `List` | The failures reported for this snapshot. Empty if there were none. | ## HistoryItemId The client-supplied identifier this item was created with. ```csharp public readonly record struct HistoryItemId : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## HttpSdkException HTTP-specific SDK error with RFC 7807 Problem Details. ```csharp public sealed class HttpSdkException : CamundaSdkException, ISerializable ``` | Property | Type | Description | | ---------------- | --------- | ----------- | | `Type` | `String` | | | `Title` | `String` | | | `Detail` | `String` | | | `Instance` | `String` | | | `IsBackpressure` | `Boolean` | | ## ICamundaKey Marker interface for all Camunda domain key types. Enables generic constraints and JSON converter discovery. ```csharp public interface ICamundaKey ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## ICamundaLongKey Marker interface for Camunda domain types backed by a long (int64) value. ```csharp public interface ICamundaLongKey ``` | Property | Type | Description | | -------- | ------- | -------------------------- | | `Value` | `Int64` | The underlying long value. | ## IEngineClockTarget The engine clock operations `EngineTimeProvider` drives. Declared as an interface so the provider does not depend on the generated client surface; `CamundaClient` implements it with its existing methods. ```csharp public interface IEngineClockTarget ``` ## IRandomSource Source of the randomness behind SDK runtime jitter: retry backoff, OAuth retry backoff, and worker startup staggering. The randomness counterpart of `CamundaOptions.TimeProvider`. Pinning the clock makes cadence virtual; supplying a `SeededRandomSource` as well makes it reproducible, so a timing-dependent failure can be replayed draw for draw. Implementations must be safe to call from multiple threads. ```csharp public interface IRandomSource ``` ## ITenantIdSettable Implemented by request body types that have an optional tenantId property. The SDK uses this to inject the configured default tenant ID when the caller does not supply one explicitly. ```csharp public interface ITenantIdSettable ``` ## ITenantIdsSettable Implemented by request body types that have an optional `tenantIds` array property (e.g. `JobActivationRequest`). The SDK uses this to inject `[DefaultTenantId]` when the caller does not supply a tenant list explicitly. Mirrors `ITenantIdSettable` for the plural array shape. ```csharp public interface ITenantIdsSettable ``` ## IncidentErrorTypeExactMatch Matches the value exactly. ```csharp public readonly record struct IncidentErrorTypeExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## IncidentErrorTypeFilterProperty IncidentErrorTypeEnum with full advanced search capabilities. ```csharp public sealed class IncidentErrorTypeFilterProperty ``` | Property | Type | Description | | ------------ | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property does not match any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## IncidentFilter Incident search filter. ```csharp public sealed class IncidentFilter ``` | Property | Type | Description | | ---------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ProcessDefinitionId` | `StringFilterProperty` | The process definition ID associated to this incident. | | `ErrorType` | `IncidentErrorTypeFilterProperty` | Incident error type with a defined set of values. | | `ErrorMessage` | `StringFilterProperty` | The error message of this incident. For `$eq`, `$neq`, `$in`, and `$notIn`, matching is case-insensitive and matches if the incident's error message contains the given value as a phrase, not necessarily the entire error message. `$like` matches on individual words of the error message and does not support multi-word patterns. | | `ElementId` | `StringFilterProperty` | The element ID associated to this incident. | | `CreationTime` | `DateTimeFilterProperty` | Date of incident creation. | | `State` | `IncidentStateFilterProperty` | State of this incident with a defined set of values. | | `TenantId` | `StringFilterProperty` | The tenant ID of the incident. | | `IncidentKey` | `BasicStringFilterProperty` | The assigned key, which acts as a unique identifier for this incident. | | `ProcessDefinitionKey` | `ProcessDefinitionKeyFilterProperty` | The process definition key associated to this incident. | | `ProcessInstanceKey` | `ProcessInstanceKeyFilterProperty` | The process instance key associated to this incident. | | `ElementInstanceKey` | `ElementInstanceKeyFilterProperty` | The element instance key associated to this incident. | | `JobKey` | `JobKeyFilterProperty` | The job key, if exists, associated with this incident. | ## IncidentProcessInstanceStatisticsByDefinitionFilter Filter for the incident process instance statistics by definition query. ```csharp public sealed class IncidentProcessInstanceStatisticsByDefinitionFilter ``` | Property | Type | Description | | --------------- | ------- | ---------------------------------------------------------------------------------- | | `ErrorHashCode` | `Int32` | The error hash code of the incidents to filter the process instance statistics by. | ## IncidentProcessInstanceStatisticsByDefinitionQuery ```csharp public sealed class IncidentProcessInstanceStatisticsByDefinitionQuery ``` | Property | Type | Description | | -------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------- | | `Filter` | `IncidentProcessInstanceStatisticsByDefinitionFilter` | Filter criteria for the aggregated process instance statistics. | | `Page` | `OffsetPagination` | Pagination parameters for the aggregated process instance statistics. | | `Sort` | `List` | Sorting criteria for process instance statistics grouped by process definition. | ## IncidentProcessInstanceStatisticsByDefinitionQueryResult ```csharp public sealed class IncidentProcessInstanceStatisticsByDefinitionQueryResult ``` | Property | Type | Description | | -------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `Items` | `List` | Statistics of active process instances with incidents, grouped by process definition for the specified error hash code. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## IncidentProcessInstanceStatisticsByDefinitionQuerySortRequest ```csharp public sealed class IncidentProcessInstanceStatisticsByDefinitionQuerySortRequest ``` | Property | Type | Description | | -------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------- | | `Field` | `IncidentProcessInstanceStatisticsByDefinitionQuerySortRequestField` | The aggregated field by which the process instance statistics are sorted. | | `Order` | `Nullable` | The order in which to sort the related field. | ## IncidentProcessInstanceStatisticsByDefinitionResult ```csharp public sealed class IncidentProcessInstanceStatisticsByDefinitionResult ``` | Property | Type | Description | | ------------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------- | | `ProcessDefinitionId` | `ProcessDefinitionId` | Id of a process definition, from the model. Only ids of process definitions that are deployed are useful. | | `ProcessDefinitionKey` | `ProcessDefinitionKey` | System-generated key for a deployed process definition. | | `ProcessDefinitionName` | `String` | The name of the process definition. | | `ProcessDefinitionVersion` | `Int32` | The version of the process definition. | | `TenantId` | `TenantId` | The unique identifier of the tenant. | | `ActiveInstancesWithErrorCount` | `Int64` | The number of active process instances that currently have an incident with the specified error hash code. | ## IncidentProcessInstanceStatisticsByErrorQuery ```csharp public sealed class IncidentProcessInstanceStatisticsByErrorQuery ``` | Property | Type | Description | | -------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------------- | | `Page` | `OffsetPagination` | Pagination parameters for process instance statistics grouped by incident error. | | `Sort` | `List` | Sorting criteria for process instance statistics grouped by incident error. | ## IncidentProcessInstanceStatisticsByErrorQueryResult ```csharp public sealed class IncidentProcessInstanceStatisticsByErrorQueryResult ``` | Property | Type | Description | | -------- | ------------------------------------------------------ | ----------------------------------------------------------------- | | `Items` | `List` | Statistics of active process instances grouped by incident error. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## IncidentProcessInstanceStatisticsByErrorQuerySortRequest ```csharp public sealed class IncidentProcessInstanceStatisticsByErrorQuerySortRequest ``` | Property | Type | Description | | -------- | --------------------------------------------------------------- | --------------------------------------------------- | | `Field` | `IncidentProcessInstanceStatisticsByErrorQuerySortRequestField` | The field to sort the incident error statistics by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## IncidentProcessInstanceStatisticsByErrorResult ```csharp public sealed class IncidentProcessInstanceStatisticsByErrorResult ``` | Property | Type | Description | | ------------------------------- | -------- | ---------------------------------------------------------------------------------------------- | | `ErrorHashCode` | `Int32` | The hash code identifying a specific incident error.. | | `ErrorMessage` | `String` | The error message associated with the incident error hash code. | | `ActiveInstancesWithErrorCount` | `Int64` | The number of active process instances that currently have an active incident with this error. | ## IncidentResolutionRequest ```csharp public sealed class IncidentResolutionRequest ``` | Property | Type | Description | | -------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | ## IncidentResult ```csharp public sealed class IncidentResult ``` | Property | Type | Description | | ------------------------ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ProcessDefinitionId` | `ProcessDefinitionId` | The process definition ID associated to this incident. | | `ErrorType` | `IncidentErrorTypeEnum` | The type of the incident error. | | `ErrorMessage` | `String` | Error message which describes the error in more detail. | | `ElementId` | `ElementId` | The element ID associated to this incident. | | `CreationTime` | `DateTimeOffset` | The creation time of the incident. | | `State` | `IncidentStateEnum` | The incident state. | | `TenantId` | `TenantId` | The tenant ID of the incident. | | `IncidentKey` | `IncidentKey` | The assigned key, which acts as a unique identifier for this incident. | | `ProcessDefinitionKey` | `ProcessDefinitionKey` | The process definition key associated to this incident. | | `ProcessInstanceKey` | `ProcessInstanceKey` | The process instance key associated to this incident. | | `RootProcessInstanceKey` | `Nullable` | The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. | | `ElementInstanceKey` | `ElementInstanceKey` | The element instance key associated to this incident. | | `JobKey` | `Nullable` | The job key, if exists, associated with this incident. | ## IncidentSearchQuery ```csharp public sealed class IncidentSearchQuery ``` | Property | Type | Description | | -------- | -------------------------------------- | ---------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `IncidentFilter` | The incident search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## IncidentSearchQueryResult ```csharp public sealed class IncidentSearchQueryResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching incidents. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## IncidentSearchQuerySortRequest ```csharp public sealed class IncidentSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ------------------------------------- | --------------------------------------------- | | `Field` | `IncidentSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## IncidentStateExactMatch Matches the value exactly. ```csharp public readonly record struct IncidentStateExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## IncidentStateFilterProperty IncidentStateEnum with full advanced search capabilities. ```csharp public sealed class IncidentStateFilterProperty ``` | Property | Type | Description | | ------------ | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property does not match any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## InferredAncestorKeyInstruction Instructs the engine to derive the ancestor scope key from the source element's hierarchy. The engine traverses the source element's ancestry to find an instance that matches one of the target element's flow scopes, ensuring the target is activated in the correct scope. ```csharp public sealed class InferredAncestorKeyInstruction : AncestorScopeInstruction ``` ## IntegerFilterProperty Integer property with advanced search capabilities. ```csharp public sealed class IntegerFilterProperty ``` | Property | Type | Description | | ------------ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `Gt` | `Nullable` | Greater than comparison with the provided value. | | `Gte` | `Nullable` | Greater than or equal comparison with the provided value. | | `Lt` | `Nullable` | Lower than comparison with the provided value. | | `Lte` | `Nullable` | Lower than or equal comparison with the provided value. | | `In` | `List` | Checks if the property matches any of the provided values. | ## JobActivationRequest ```csharp public sealed class JobActivationRequest : ITenantIdsSettable ``` | Property | Type | Description | | ------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Type` | `String` | The job type, as defined in the BPMN process (e.g. ) | | `Worker` | `String` | The name of the worker activating the jobs, mostly used for logging purposes. | | `Timeout` | `Int64` | A job returned after this call will not be activated by another call until the timeout (in ms) has been reached. | | `MaxJobsToActivate` | `Int32` | The maximum jobs to activate by this request. | | `FetchVariable` | `List` | A list of variables to fetch as the job variables; if empty, all visible variables at the time of activation for the scope of the job will be returned. | | `RequestTimeout` | `Nullable` | The request will be completed when at least one job is activated or after the requestTimeout (in ms). If the requestTimeout = 0, a default timeout is used. If the requestTimeout < 0, long polling is disabled and the request is completed immediately, even when no job is activated. | | `TenantIds` | `List` | A list of IDs of tenants for which to activate jobs. | | `TenantFilter` | `Nullable` | The tenant filtering strategy - determines whether to use provided tenant IDs or assigned tenant IDs from the authenticated principal's authorized tenants. | | `WithLease` | `Nullable` | Whether to activate the jobs with a lease. When true, each activated job is assigned a distinct, opaque lease token, returned as ActivatedJobResult.jobLeaseToken. The lease fences the complete, fail, and throw-error commands against a superseded activation of the same job (for example, after the job timed out or failed and was re-activated by another worker): a command carrying a stale lease token is rejected rather than racing with the newer activation. Once a job has been activated with a lease, it is served only to leasing workers of that job type; a homogeneous fleet per job type is recommended. Omit or set to false to activate jobs without a lease. | ## JobActivationResult The list of activated jobs ```csharp public sealed class JobActivationResult ``` | Property | Type | Description | | -------- | -------------------------- | ------------------- | | `Jobs` | `List` | The activated jobs. | ## JobBatchUpdateRequest The filter and changeset for a batch job update operation. The filter defines which jobs are updated; the changeset defines what to update. At least one changeset field must be non-null. ```csharp public sealed class JobBatchUpdateRequest ``` | Property | Type | Description | | -------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | | `Filter` | `JobFilter` | The job filter. At least one dimension must be set. | | `Changeset` | `JobChangeset` | The fields to update. At least one field must be non-null. | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | ## JobChangeset JSON object with changed job attribute values. The job cannot be completed or failed with this endpoint, use the complete job or fail job endpoints instead. ```csharp public sealed class JobChangeset ``` | Property | Type | Description | | ---------- | ----------------- | --------------------------------------------------------------------- | | `Retries` | `Nullable` | The new number of retries for the job. | | `Timeout` | `Nullable` | The new timeout for the job in milliseconds. | | `Priority` | `Nullable` | The new priority for the job. Higher values indicate higher priority. | ## JobCompletionRequest ```csharp public sealed class JobCompletionRequest ``` | Property | Type | Description | | --------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Variables` | `Object` | The variables to complete the job with. | | `Result` | `JobResult` | The result of the completed job as determined by the worker. | | `JobLeaseToken` | `Nullable` | The token identifying a leased job's activation, obtained from `ActivatedJobResult.jobLeaseToken`. For a leased job, the matching token must be supplied to prove the command comes from the worker that holds the current lease; a command with no token is rejected. A command carrying a stale token is likewise rejected, fencing the job against a superseded activation (for example, after the job timed out or failed and was re-activated by another worker). A job that was activated without a lease requires no token. | | `BusinessId` | `Nullable` | An optional business id to assign to the process instance the job belongs to, as part of completing the job, letting a worker set the identifier from work it just performed. The business id can only be assigned to a root process instance: if the job belongs to a child process instance (one started by a call activity), the completion is rejected. An empty business id is likewise rejected. The assignment is single and irreversible and is only accepted while business id uniqueness is disabled. Only artifacts created after the assignment carry the business id; already-existing ones are not enriched. Completing with a business id that differs from one already assigned rejects the whole completion, leaving the job open; re-sending the identical business id is an idempotent no-op. | ## JobErrorRequest ```csharp public sealed class JobErrorRequest ``` | Property | Type | Description | | --------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ErrorCode` | `String` | The error code that will be matched with an error catch event. | | `ErrorMessage` | `String` | An error message that provides additional context. | | `Variables` | `Object` | JSON object that will instantiate the variables at the local scope of the error catch event that catches the thrown error. | | `JobLeaseToken` | `Nullable` | The token identifying a leased job's activation, obtained from `ActivatedJobResult.jobLeaseToken`. For a leased job, the matching token must be supplied to prove the command comes from the worker that holds the current lease; a command with no token is rejected. A command carrying a stale token is likewise rejected, fencing the job against a superseded activation (for example, after the job timed out or failed and was re-activated by another worker). A job that was activated without a lease requires no token. | ## JobErrorStatisticsFilter Job error statistics search filter. ```csharp public sealed class JobErrorStatisticsFilter ``` | Property | Type | Description | | -------------- | ---------------------- | ---------------------------------------------------------------------- | | `From` | `DateTimeOffset` | Start of the time window to filter metrics. ISO 8601 date-time format. | | `To` | `DateTimeOffset` | End of the time window to filter metrics. ISO 8601 date-time format. | | `JobType` | `String` | Job type to return error metrics for. | | `ErrorCode` | `StringFilterProperty` | Optional error code filter with advanced search capabilities. | | `ErrorMessage` | `StringFilterProperty` | Optional error message filter with advanced search capabilities. | ## JobErrorStatisticsItem Aggregated error metrics for a single error type and message combination. ```csharp public sealed class JobErrorStatisticsItem ``` | Property | Type | Description | | -------------- | -------- | ------------------------------------------------------- | | `ErrorCode` | `String` | The error code identifier. | | `ErrorMessage` | `String` | The error message. | | `Workers` | `Int32` | Number of distinct workers that encountered this error. | ## JobErrorStatisticsQuery Job error statistics query. ```csharp public sealed class JobErrorStatisticsQuery ``` | Property | Type | Description | | -------- | -------------------------- | ----------------------------------- | | `Filter` | `JobErrorStatisticsFilter` | Job error statistics search filter. | | `Page` | `CursorForwardPagination` | Search cursor pagination. | ## JobErrorStatisticsQueryResult Job error statistics query result. ```csharp public sealed class JobErrorStatisticsQueryResult ``` | Property | Type | Description | | -------- | ------------------------------ | ------------------------------------------------ | | `Items` | `List` | The list of per-error statistics items. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## JobFailRequest ```csharp public sealed class JobFailRequest ``` | Property | Type | Description | | --------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Retries` | `Nullable` | The amount of retries the job should have left | | `ErrorMessage` | `String` | An optional error message describing why the job failed; if not provided, an empty string is used. | | `RetryBackOff` | `Nullable` | An optional retry back off for the failed job. The job will not be retryable before the current time plus the back off time. The default is 0 which means the job is retryable immediately. | | `Variables` | `Object` | JSON object that will instantiate the variables at the local scope of the job's associated task. | | `JobLeaseToken` | `Nullable` | The token identifying a leased job's activation, obtained from `ActivatedJobResult.jobLeaseToken`. For a leased job, the matching token must be supplied to prove the command comes from the worker that holds the current lease; a command with no token is rejected. A command carrying a stale token is likewise rejected, fencing the job against a superseded activation (for example, after the job timed out or failed and was re-activated by another worker). A job that was activated without a lease requires no token. | ## JobFailureException Throw from a job handler to explicitly fail a job with custom retry settings. ```csharp public sealed class JobFailureException : Exception, ISerializable ``` | Property | Type | Description | | ---------------- | ----------------- | ------------------------------------------------------------------------ | | `Retries` | `Nullable` | How many retries the job should have remaining. `null` = server decides. | | `RetryBackOffMs` | `Nullable` | Retry back-off in milliseconds. `null` = immediate retry. | ## JobFilter Job search filter. ```csharp public sealed class JobFilter ``` | Property | Type | Description | | -------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | | `Deadline` | `DateTimeFilterProperty` | When the job can next be activated. | | `DeniedReason` | `StringFilterProperty` | The reason provided by the user task listener for denying the work. | | `ElementId` | `StringFilterProperty` | The element ID associated with the job. | | `ElementInstanceKey` | `ElementInstanceKeyFilterProperty` | The element instance key associated with the job. | | `EndTime` | `DateTimeFilterProperty` | When the job ended. | | `ErrorCode` | `StringFilterProperty` | The error code provided for the failed job. | | `ErrorMessage` | `StringFilterProperty` | The error message that provides additional context for a failed job. | | `HasFailedWithRetriesLeft` | `Nullable` | Indicates whether the job has failed with retries left. | | `IsDenied` | `Nullable` | Indicates whether the user task listener denies the work. | | `JobKey` | `JobKeyFilterProperty` | The key, a unique identifier for the job. | | `Kind` | `JobKindFilterProperty` | The kind of the job. | | `ListenerEventType` | `JobListenerEventTypeFilterProperty` | The listener event type of the job. | | `Priority` | `IntegerFilterProperty` | The priority of the job. Jobs created before 8.10 have no stored priority and are excluded from results when this filter is applied. | | `ProcessDefinitionId` | `StringFilterProperty` | The process definition ID associated with the job. | | `ProcessDefinitionKey` | `ProcessDefinitionKeyFilterProperty` | The process definition key associated with the job. | | `ProcessInstanceKey` | `ProcessInstanceKeyFilterProperty` | The process instance key associated with the job. | | `Retries` | `IntegerFilterProperty` | The number of retries left. | | `State` | `JobStateFilterProperty` | The state of the job. | | `TenantId` | `StringFilterProperty` | The tenant ID. | | `Type` | `StringFilterProperty` | The type of the job. | | `Worker` | `StringFilterProperty` | The name of the worker for this job. | | `CreationTime` | `DateTimeFilterProperty` | When the job was created. Field is present for jobs created after 8.9. | | `LastUpdateTime` | `DateTimeFilterProperty` | When the job was last updated. Field is present for jobs created after 8.9. | ## JobHandler Delegate for job handler functions. Return the output variables to complete the job with, or `null` to complete with no output variables. Return a `JobCompletionRequest` to send a structured completion (e.g. with job corrections or a task denial). To signal a BPMN error, throw `BpmnErrorException`. To explicitly fail a job with custom retries, throw `JobFailureException`. Any other unhandled exception auto-fails the job with `retries - 1`. ```csharp public delegate Task JobHandler(ActivatedJob job, CancellationToken ct) ``` ## JobKeyExactMatch Matches the value exactly. ```csharp public readonly record struct JobKeyExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## JobKeyFilterProperty JobKey property with full advanced search capabilities. ```csharp public sealed class JobKeyFilterProperty ``` | Property | Type | Description | | ------------ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## JobKindExactMatch Matches the value exactly. ```csharp public readonly record struct JobKindExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## JobKindFilterProperty JobKindEnum property with full advanced search capabilities. ```csharp public sealed class JobKindFilterProperty ``` | Property | Type | Description | | ------------ | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## JobLeaseToken An opaque, engine-minted fencing token identifying a single activation of a job. Returned by Activate Jobs as `ActivatedJobResult.jobLeaseToken` when the job is activated with a lease, and passed back under the same name on fenced job commands and on agent-instance creation/updates, to prove the caller holds the current lease. The token is opaque: clients may rely on its presence and equality only, and must never construct, parse, or otherwise interpret it beyond equality checks. It cannot be minted client-side; only the engine produces it, exactly once per leased activation, and clients must not depend on any particular internal format. ```csharp public readonly record struct JobLeaseToken : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## JobListenerEventTypeExactMatch Matches the value exactly. ```csharp public readonly record struct JobListenerEventTypeExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## JobListenerEventTypeFilterProperty JobListenerEventTypeEnum property with full advanced search capabilities. ```csharp public sealed class JobListenerEventTypeFilterProperty ``` | Property | Type | Description | | ------------ | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## JobMetricsConfigurationResponse Configuration for job metrics collection and export. ```csharp public sealed class JobMetricsConfigurationResponse ``` | Property | Type | Description | | --------------------- | --------- | ------------------------------------------------------------------------ | | `Enabled` | `Boolean` | Whether job metrics export is enabled. | | `ExportInterval` | `String` | The interval at which job metrics are exported, as an ISO 8601 duration. | | `MaxWorkerNameLength` | `Int32` | The maximum length of the worker name used in job metrics labels. | | `MaxJobTypeLength` | `Int32` | The maximum length of the job type used in job metrics labels. | | `MaxTenantIdLength` | `Int32` | The maximum length of the tenant ID used in job metrics labels. | | `MaxUniqueKeys` | `Int32` | The maximum number of unique metric keys tracked for job metrics. | ## JobResult The result of the completed job as determined by the worker. ```csharp public abstract class JobResult ``` ## JobResultActivateElement Instruction to activate a single BPMN element within an ad‑hoc sub‑process, optionally providing variables scoped to that element. ```csharp public sealed class JobResultActivateElement ``` | Property | Type | Description | | ----------- | --------------------- | --------------------------- | | `ElementId` | `Nullable` | The element ID to activate. | | `Variables` | `Object` | Variables for the element. | ## JobResultAdHocSubProcess Job result details for an ad‑hoc sub‑process, including elements to activate and flags indicating completion or cancellation behavior. ```csharp public sealed class JobResultAdHocSubProcess : JobResult ``` | Property | Type | Description | | -------------------------------- | -------------------------------- | -------------------------------------------------------------------------------------- | | `ActivateElements` | `List` | Indicates which elements need to be activated in the ad-hoc subprocess. | | `IsCompletionConditionFulfilled` | `Nullable` | Indicates whether the completion condition of the ad-hoc subprocess is fulfilled. | | `IsCancelRemainingInstances` | `Nullable` | Indicates whether the remaining instances of the ad-hoc subprocess should be canceled. | ## JobResultCorrections JSON object with attributes that were corrected by the worker. The following attributes can be corrected, additional attributes will be ignored: - `assignee` - clear by providing an empty String - `dueDate` - clear by providing an empty String - `followUpDate` - clear by providing an empty String - `candidateGroups` - clear by providing an empty list - `candidateUsers` - clear by providing an empty list - `priority` - minimum 0, maximum 100, default 50 Providing any of those attributes with a `null` value or omitting it preserves the persisted attribute's value. ```csharp public sealed class JobResultCorrections ``` | Property | Type | Description | | ----------------- | -------------------------- | ----------------------------------------- | | `Assignee` | `String` | Assignee of the task. | | `DueDate` | `Nullable` | The due date of the task. | | `FollowUpDate` | `Nullable` | The follow-up date of the task. | | `CandidateUsers` | `List` | The list of candidate users of the task. | | `CandidateGroups` | `List` | The list of candidate groups of the task. | | `Priority` | `Nullable` | The priority of the task. | ## JobResultUserTask Job result details for a user task completion, optionally including a denial reason and corrected task properties. ```csharp public sealed class JobResultUserTask : JobResult ``` | Property | Type | Description | | -------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Denied` | `Nullable` | Indicates whether the worker denies the work, i.e. explicitly doesn't approve it. For example, a user task listener can deny the completion of a task by setting this flag to true. In this example, the completion of a task is represented by a job that the worker can complete as denied. As a result, the completion request is rejected and the task remains active. Defaults to false. | | `DeniedReason` | `String` | The reason provided by the user task listener for denying the work. | | `Corrections` | `JobResultCorrections` | JSON object with attributes that were corrected by the worker. The following attributes can be corrected, additional attributes will be ignored: * `assignee` - clear by providing an empty String * `dueDate` - clear by providing an empty String * `followUpDate` - clear by providing an empty String * `candidateGroups` - clear by providing an empty list * `candidateUsers` - clear by providing an empty list * `priority` - minimum 0, maximum 100, default 50 Providing any of those attributes with a `null` value or omitting it preserves the persisted attribute's value. | ## JobSearchQuery Job search request. ```csharp public sealed class JobSearchQuery ``` | Property | Type | Description | | -------- | --------------------------------- | ----------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `JobFilter` | The job search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## JobSearchQueryResult Job search response. ```csharp public sealed class JobSearchQueryResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching jobs. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## JobSearchQuerySortRequest ```csharp public sealed class JobSearchQuerySortRequest ``` | Property | Type | Description | | -------- | -------------------------------- | --------------------------------------------- | | `Field` | `JobSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## JobSearchResult ```csharp public sealed class JobSearchResult ``` | Property | Type | Description | | -------------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `CustomHeaders` | `Dictionary` | A set of custom headers defined during modelling. | | `Deadline` | `Nullable` | If the job has been activated, when it will next be available to be activated. | | `DeniedReason` | `String` | The reason provided by the user task listener for denying the work. | | `ElementId` | `ElementId` | The element ID associated with the job. | | `ElementInstanceKey` | `ElementInstanceKey` | The element instance key associated with the job. | | `EndTime` | `Nullable` | End date of the job. This is `null` if the job is not in an end state yet. | | `ErrorCode` | `String` | The error code provided for a failed job. | | `ErrorMessage` | `String` | The error message that provides additional context for a failed job. | | `HasFailedWithRetriesLeft` | `Boolean` | Indicates whether the job has failed with retries left. | | `IsDenied` | `Nullable` | Indicates whether the user task listener denies the work. | | `JobKey` | `JobKey` | The key, a unique identifier for the job. | | `Kind` | `JobKindEnum` | The job kind. | | `ListenerEventType` | `JobListenerEventTypeEnum` | The listener event type of the job. | | `ProcessDefinitionId` | `ProcessDefinitionId` | The process definition ID associated with the job. | | `ProcessDefinitionKey` | `ProcessDefinitionKey` | The process definition key associated with the job. | | `ProcessInstanceKey` | `ProcessInstanceKey` | The process instance key associated with the job. | | `RootProcessInstanceKey` | `Nullable` | The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. | | `BusinessId` | `Nullable` | The business ID of the owning process instance, inherited when the job was created. This is `null` for jobs created before version 8.10 and for jobs whose owning process instance has no business ID. | | `Retries` | `Int32` | The amount of retries left to this job. | | `State` | `JobStateEnum` | The state of the job. | | `TenantId` | `TenantId` | The unique identifier of the tenant. | | `Type` | `String` | The type of the job. | | `Worker` | `String` | The name of the worker of this job. | | `CreationTime` | `Nullable` | When the job was created. Field is present for jobs created after 8.9. | | `LastUpdateTime` | `Nullable` | When the job was last updated. Field is present for jobs created after 8.9. | | `Priority` | `Int32` | The priority of the job. Higher values indicate higher priority. Jobs created before 8.10 have no stored priority; they appear last when sorting by this field and are excluded when filtering by this field. The API returns 0 for such jobs. | ## JobStateExactMatch Matches the value exactly. ```csharp public readonly record struct JobStateExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## JobStateFilterProperty JobStateEnum property with full advanced search capabilities. ```csharp public sealed class JobStateFilterProperty ``` | Property | Type | Description | | ------------ | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## JobTimeSeriesStatisticsFilter Job time-series statistics search filter. ```csharp public sealed class JobTimeSeriesStatisticsFilter ``` | Property | Type | Description | | ------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `From` | `DateTimeOffset` | Start of the time window to filter metrics. ISO 8601 date-time format. | | `To` | `DateTimeOffset` | End of the time window to filter metrics. ISO 8601 date-time format. | | `JobType` | `String` | Job type to return time-series metrics for. | | `Resolution` | `String` | Time bucket resolution as an ISO 8601 duration (for example `PT1M` for 1 minute, `PT1H` for 1 hour). If omitted, the server chooses a sensible default. | ## JobTimeSeriesStatisticsItem Aggregated job metrics for a single time bucket. ```csharp public sealed class JobTimeSeriesStatisticsItem ``` | Property | Type | Description | | ----------- | ---------------- | -------------------------------------------------------------- | | `Time` | `DateTimeOffset` | ISO 8601 timestamp representing the start of this time bucket. | | `Created` | `StatusMetric` | Metric for a single job status. | | `Completed` | `StatusMetric` | Metric for a single job status. | | `Failed` | `StatusMetric` | Metric for a single job status. | ## JobTimeSeriesStatisticsQuery Job time-series statistics query. ```csharp public sealed class JobTimeSeriesStatisticsQuery ``` | Property | Type | Description | | -------- | ------------------------------- | ----------------------------------------- | | `Filter` | `JobTimeSeriesStatisticsFilter` | Job time-series statistics search filter. | | `Page` | `CursorForwardPagination` | Search cursor pagination. | ## JobTimeSeriesStatisticsQueryResult Job time-series statistics query result. ```csharp public sealed class JobTimeSeriesStatisticsQueryResult ``` | Property | Type | Description | | -------- | ----------------------------------- | ---------------------------------------------------------------------- | | `Items` | `List` | The list of time-bucketed statistics items, ordered ascending by time. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## JobTypeStatisticsFilter Job type statistics search filter. ```csharp public sealed class JobTypeStatisticsFilter ``` | Property | Type | Description | | --------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------ | | `From` | `DateTimeOffset` | Start of the time window to filter metrics. ISO 8601 date-time format. | | `To` | `DateTimeOffset` | End of the time window to filter metrics. ISO 8601 date-time format. | | `JobType` | `StringFilterProperty` | Optional job type filter with advanced search capabilities. Supports exact match, pattern matching, and other operators. | ## JobTypeStatisticsItem Statistics for a single job type. ```csharp public sealed class JobTypeStatisticsItem ``` | Property | Type | Description | | ----------- | -------------- | ------------------------------------------------------ | | `JobType` | `String` | The job type identifier. | | `Created` | `StatusMetric` | Metric for a single job status. | | `Completed` | `StatusMetric` | Metric for a single job status. | | `Failed` | `StatusMetric` | Metric for a single job status. | | `Workers` | `Int32` | Number of distinct workers observed for this job type. | ## JobTypeStatisticsQuery Job type statistics query. ```csharp public sealed class JobTypeStatisticsQuery ``` | Property | Type | Description | | -------- | ------------------------- | ---------------------------------- | | `Filter` | `JobTypeStatisticsFilter` | Job type statistics search filter. | | `Page` | `CursorForwardPagination` | Search cursor pagination. | ## JobTypeStatisticsQueryResult Job type statistics query result. ```csharp public sealed class JobTypeStatisticsQueryResult ``` | Property | Type | Description | | -------- | ----------------------------- | ------------------------------------------------ | | `Items` | `List` | The list of job type statistics items. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## JobUpdateRequest ```csharp public sealed class JobUpdateRequest ``` | Property | Type | Description | | -------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Changeset` | `JobChangeset` | JSON object with changed job attribute values. The job cannot be completed or failed with this endpoint, use the complete job or fail job endpoints instead. | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | | `JobLeaseToken` | `Nullable` | The token identifying a leased job's activation, obtained from `ActivatedJobResult.jobLeaseToken`. For a leased job, a supplied token is validated to prove the command comes from the worker that holds the current lease; a command carrying a stale token is rejected, fencing the job against a superseded activation (for example, after the job timed out or failed and was re-activated by another worker). An update without a token always applies to support operator and bulk updates of leased jobs. Note that this is different from lifecycle requests like complete, fail, and throw-error that always require a token for leased jobs. A job that was activated without a lease requires no token. | ## JobWaitStateDetails ```csharp public sealed class JobWaitStateDetails : WaitStateDetails ``` | Property | Type | Description | | ------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------- | | `JobKey` | `JobKey` | The key of the job. | | `JobType` | `String` | The job type (worker subscription identifier). | | `JobKind` | `JobKindEnum` | The kind of job. | | `ListenerEventType` | `Nullable` | The listener event type of the job (only set for execution listener and task listener jobs). | | `Retries` | `Nullable` | The number of retries remaining for the job. | ## JobWorker A long-running worker that polls the Camunda broker for jobs of a specific type, dispatches them to a handler, and auto-completes or auto-fails based on the outcome. Concurrency model: jobs are dispatched as concurrent `Tasks.Task`s on the .NET thread pool. `JobWorkerConfig.MaxConcurrentJobs` controls how many jobs may be in-flight simultaneously. For async handlers (the typical case), the thread pool thread is released during `await` points, so many jobs can be handled by a small number of OS threads. For CPU-bound handlers, set `MaxConcurrentJobs` to `Environment.ProcessorCount` to match available cores. ```csharp public sealed class JobWorker : IAsyncDisposable, IDisposable ``` | Property | Type | Description | | ------------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ActiveJobs` | `Int32` | Number of jobs currently being processed. | | `IsRunning` | `Boolean` | Whether the poll loop is currently running. | | `Completion` | `Task` | The poll loop task, or `null` before `JobWorker.Start` has run. Completes when the worker stops; faults with `LeaseNotHonoredException` if the server does not honor a requested lease. `CamundaClient.RunWorkersAsync` observes this so that terminal fault reaches the caller instead of being lost on a background task. | | `Name` | `String` | The worker's name (auto-generated or from config). | ## JobWorkerStatisticsFilter Job worker statistics search filter. ```csharp public sealed class JobWorkerStatisticsFilter ``` | Property | Type | Description | | --------- | ---------------- | ---------------------------------------------------------------------- | | `From` | `DateTimeOffset` | Start of the time window to filter metrics. ISO 8601 date-time format. | | `To` | `DateTimeOffset` | End of the time window to filter metrics. ISO 8601 date-time format. | | `JobType` | `String` | Job type to return worker metrics for. | ## JobWorkerStatisticsItem Statistics for a single worker within a job type. ```csharp public sealed class JobWorkerStatisticsItem ``` | Property | Type | Description | | ----------- | -------------- | ----------------------------------------------------------------------------- | | `Worker` | `String` | The name of the worker activating the jobs, mostly used for logging purposes. | | `Created` | `StatusMetric` | Metric for a single job status. | | `Completed` | `StatusMetric` | Metric for a single job status. | | `Failed` | `StatusMetric` | Metric for a single job status. | ## JobWorkerStatisticsQuery Job worker statistics query. ```csharp public sealed class JobWorkerStatisticsQuery ``` | Property | Type | Description | | -------- | --------------------------- | ------------------------------------ | | `Filter` | `JobWorkerStatisticsFilter` | Job worker statistics search filter. | | `Page` | `CursorForwardPagination` | Search cursor pagination. | ## JobWorkerStatisticsQueryResult Job worker statistics query result. ```csharp public sealed class JobWorkerStatisticsQueryResult ``` | Property | Type | Description | | -------- | ------------------------------- | ------------------------------------------------ | | `Items` | `List` | The list of per-worker statistics items. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## LeaseNotHonoredException Thrown when a worker activated jobs with a lease but the server returned a job whose lease token is missing or invalid. The specification declares the token present exactly when the activation sets the lease flag (see `PresentWhen.cs`). A server that predates job leases, ignores the flag, or returns an unusable token breaks that quietly: the worker would go on to complete, fail, or throw an error for the job without a usable token, so the engine could not fence the command against a superseded activation. The caller asked for fencing and would not be getting it, which is worth failing over rather than proceeding. ```csharp public sealed class LeaseNotHonoredException : CamundaSdkException, ISerializable ``` | Property | Type | Description | | ------------- | -------- | ------------------------------------------------------------------------- | | `JobKey` | `String` | The key of the job whose lease token was missing or invalid. | | `RequestFlag` | `String` | The activation flag the specification couples the token to (`withLease`). | ## LicenseResponse The response of a license request. ```csharp public sealed class LicenseResponse ``` | Property | Type | Description | | -------------- | -------------------------- | -------------------------------------------------------------------- | | `ValidLicense` | `Boolean` | True if the Camunda license is valid, false if otherwise | | `LicenseType` | `String` | Will return the license type property of the Camunda license | | `IsCommercial` | `Boolean` | Will be false when a license contains a non-commerical=true property | | `ExpiresAt` | `Nullable` | The date when the Camunda license expires | ## LikeFilter Checks if the property matches the provided like value. Supported wildcard characters are: - `*`: matches zero, one, or multiple characters. - `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. ```csharp public readonly record struct LikeFilter : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## LimitPagination ```csharp public sealed class LimitPagination : SearchQueryPageRequest ``` | Property | Type | Description | | -------- | ----------------- | ----------------------------------------------------- | | `Limit` | `Nullable` | The maximum number of items to return in one request. | ## LoopIterationId A client-provided sequential integer identifying a loop iteration: one pass through an AI agent's loop, during which the model reasons, selects tools, evaluates the result, and decides whether to continue. One iteration covers the input for the LLM call, the call itself, and the tools it dispatches; the results of those tool calls are input to the next iteration. Must be a positive integer, increasing with each loopIteration. Established by the connector when appending the first history item of a loopIteration. ```csharp public readonly record struct LoopIterationId : ICamundaLongKey, IEquatable ``` | Property | Type | Description | | -------- | ------- | -------------------------- | | `Value` | `Int64` | The underlying long value. | ## MappingRuleCreateRequest ```csharp public sealed class MappingRuleCreateRequest ``` | Property | Type | Description | | --------------- | --------------- | ---------------------------------- | | `MappingRuleId` | `MappingRuleId` | The unique ID of the mapping rule. | | `ClaimName` | `String` | The name of the claim to map. | | `ClaimValue` | `String` | The value of the claim to map. | | `Name` | `String` | The name of the mapping rule. | ## MappingRuleCreateResult ```csharp public sealed class MappingRuleCreateResult ``` | Property | Type | Description | | --------------- | --------------- | ---------------------------------- | | `ClaimName` | `String` | The name of the claim to map. | | `ClaimValue` | `String` | The value of the claim to map. | | `Name` | `String` | The name of the mapping rule. | | `MappingRuleId` | `MappingRuleId` | The unique ID of the mapping rule. | ## MappingRuleCreateUpdateRequest ```csharp public sealed class MappingRuleCreateUpdateRequest ``` | Property | Type | Description | | ------------ | -------- | ------------------------------ | | `ClaimName` | `String` | The name of the claim to map. | | `ClaimValue` | `String` | The value of the claim to map. | | `Name` | `String` | The name of the mapping rule. | ## MappingRuleCreateUpdateResult ```csharp public sealed class MappingRuleCreateUpdateResult ``` | Property | Type | Description | | --------------- | --------------- | ---------------------------------- | | `ClaimName` | `String` | The name of the claim to map. | | `ClaimValue` | `String` | The value of the claim to map. | | `Name` | `String` | The name of the mapping rule. | | `MappingRuleId` | `MappingRuleId` | The unique ID of the mapping rule. | ## MappingRuleFilter Mapping rule search filter. ```csharp public sealed class MappingRuleFilter ``` | Property | Type | Description | | --------------- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ClaimName` | `String` | The claim name to match against a token. | | `ClaimValue` | `String` | The value of the claim to match. | | `Name` | `StringFilterProperty` | The name of the mapping rule. | | `MappingRuleId` | `StringFilterProperty` | The ID of the mapping rule. | | `Or` | `List` | Defines a list of alternative filter groups combined using OR logic. Each object in the array is evaluated independently, and the filter matches if any one of them is satisfied. Top-level fields and the `$or` clause are combined using AND logic — meaning: (top-level filters) AND (any of the `$or` filters) must match. Example: `json { "$or": [ { "mappingRuleId": "rule-1" }, { "mappingRuleId": "rule-2" } ] } ` This matches mapping rules whose mappingRuleId is rule-1 or rule-2. Note: Using complex $or conditions may impact performance, use with caution in high-volume environments. | ## MappingRuleFilterFields Mapping rule search filter fields. ```csharp public sealed class MappingRuleFilterFields ``` | Property | Type | Description | | --------------- | ---------------------- | ---------------------------------------- | | `ClaimName` | `String` | The claim name to match against a token. | | `ClaimValue` | `String` | The value of the claim to match. | | `Name` | `StringFilterProperty` | The name of the mapping rule. | | `MappingRuleId` | `StringFilterProperty` | The ID of the mapping rule. | ## MappingRuleId The unique identifier of a mapping rule. ```csharp public readonly record struct MappingRuleId : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## MappingRuleResult ```csharp public sealed class MappingRuleResult ``` | Property | Type | Description | | --------------- | --------------- | ------------------------------ | | `ClaimName` | `String` | The name of the claim to map. | | `ClaimValue` | `String` | The value of the claim to map. | | `Name` | `String` | The name of the mapping rule. | | `MappingRuleId` | `MappingRuleId` | The ID of the mapping rule. | ## MappingRuleSearchQueryRequest ```csharp public sealed class MappingRuleSearchQueryRequest ``` | Property | Type | Description | | -------- | ----------------------------------------- | -------------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `MappingRuleFilter` | The mapping rule search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## MappingRuleSearchQueryResult ```csharp public sealed class MappingRuleSearchQueryResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching mapping rules. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## MappingRuleSearchQuerySortRequest ```csharp public sealed class MappingRuleSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ---------------------------------------- | --------------------------------------------- | | `Field` | `MappingRuleSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## MappingRuleUpdateRequest ```csharp public sealed class MappingRuleUpdateRequest ``` | Property | Type | Description | | ------------ | -------- | ------------------------------ | | `ClaimName` | `String` | The name of the claim to map. | | `ClaimValue` | `String` | The value of the claim to map. | | `Name` | `String` | The name of the mapping rule. | ## MappingRuleUpdateResult ```csharp public sealed class MappingRuleUpdateResult ``` | Property | Type | Description | | --------------- | --------------- | ---------------------------------- | | `ClaimName` | `String` | The name of the claim to map. | | `ClaimValue` | `String` | The value of the claim to map. | | `Name` | `String` | The name of the mapping rule. | | `MappingRuleId` | `MappingRuleId` | The unique ID of the mapping rule. | ## MatchedDecisionRuleItem A decision rule that matched within this decision evaluation. ```csharp public sealed class MatchedDecisionRuleItem ``` | Property | Type | Description | | ------------------ | ----------------------------------- | ------------------------------- | | `RuleId` | `String` | The ID of the matched rule. | | `RuleIndex` | `Int32` | The index of the matched rule. | | `EvaluatedOutputs` | `List` | The evaluated decision outputs. | ## MessageCorrelationRequest ```csharp public sealed class MessageCorrelationRequest : ITenantIdSettable ``` | Property | Type | Description | | ---------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Name` | `String` | The message name as defined in the BPMN process | | `CorrelationKey` | `String` | The correlation key of the message. | | `Variables` | `Object` | The message variables as JSON document | | `TenantId` | `Nullable` | the tenant for which the message is published | | `BusinessId` | `Nullable` | An optional business id used to enforce uniqueness of the process instance that a message start event would create. If provided and uniqueness enforcement is enabled, the engine rejects starting a new process instance when another root process instance with the same business id is already active for the same process definition. It has no effect when the message correlates to a catch, boundary, or intermediate event. | ## MessageCorrelationResult The message key of the correlated message, as well as the first process instance key it correlated with. ```csharp public sealed class MessageCorrelationResult ``` | Property | Type | Description | | -------------------- | -------------------- | ----------------------------------------------------------------- | | `TenantId` | `TenantId` | The tenant ID of the correlated message | | `MessageKey` | `MessageKey` | The key of the correlated message. | | `ProcessInstanceKey` | `ProcessInstanceKey` | The key of the first process instance the message correlated with | ## MessagePublicationRequest ```csharp public sealed class MessagePublicationRequest : ITenantIdSettable ``` | Property | Type | Description | | ---------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Name` | `String` | The name of the message. | | `CorrelationKey` | `String` | The correlation key of the message. | | `TimeToLive` | `Nullable` | Timespan (in ms) to buffer the message on the broker. | | `MessageId` | `String` | The unique ID of the message. This is used to ensure only one message with the given ID will be published during the lifetime of the message (if `timeToLive` is set). | | `Variables` | `Object` | The message variables as JSON document. | | `TenantId` | `Nullable` | The tenant of the message sender. | | `BusinessId` | `Nullable` | An optional business id used to enforce uniqueness of the process instance that a message start event would create. If provided and uniqueness enforcement is enabled, the engine rejects starting a new process instance when another root process instance with the same business id is already active for the same process definition. It has no effect when the message correlates to a catch, boundary, or intermediate event. | ## MessagePublicationResult The message key of the published message. ```csharp public sealed class MessagePublicationResult ``` | Property | Type | Description | | ------------ | ------------ | --------------------------------- | | `TenantId` | `TenantId` | The tenant ID of the message. | | `MessageKey` | `MessageKey` | The key of the published message. | ## MessageSubscriptionFilter Message subscription search filter. ```csharp public sealed class MessageSubscriptionFilter ``` | Property | Type | Description | | -------------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `BusinessId` | `StringFilterProperty` | Filter by the business id inherited from the subscribing process instance when the subscription was opened. Supports advanced string filtering, including `$like` with `*`/`?` wildcards. | | `MessageSubscriptionKey` | `MessageSubscriptionKeyFilterProperty` | The message subscription key associated with this message subscription. | | `ProcessDefinitionKey` | `ProcessDefinitionKeyFilterProperty` | The process definition key associated with this correlated message subscription. This only works for data created with 8.9 and later. | | `ProcessDefinitionId` | `StringFilterProperty` | The process definition ID associated with this message subscription. | | `ProcessInstanceKey` | `ProcessInstanceKeyFilterProperty` | The process instance key associated with this message subscription. | | `ElementId` | `StringFilterProperty` | The element ID associated with this message subscription. | | `ElementInstanceKey` | `ElementInstanceKeyFilterProperty` | The element instance key associated with this message subscription. | | `MessageSubscriptionState` | `MessageSubscriptionStateFilterProperty` | The message subscription state. | | `LastUpdatedDate` | `DateTimeFilterProperty` | The last updated date of the message subscription. | | `MessageName` | `StringFilterProperty` | The name of the message associated with the message subscription. | | `CorrelationKey` | `StringFilterProperty` | The correlation key of the message subscription. | | `TenantId` | `StringFilterProperty` | The unique external tenant ID. | | `MessageSubscriptionType` | `MessageSubscriptionTypeFilterProperty` | The type of message subscription to filter by. When omitted, both `START_EVENT` and `PROCESS_EVENT` are returned. Only available for data created with Camunda 8.10 or later. | | `ProcessDefinitionName` | `StringFilterProperty` | The name of the process definition associated with this message subscription. | | `ProcessDefinitionVersion` | `IntegerFilterProperty` | The version of the process definition associated with this message subscription. | | `ToolName` | `StringFilterProperty` | Filter by tool name extracted from the `io.camunda.tool:name` zeebe:property. | | `InboundConnectorType` | `StringFilterProperty` | Filter by inbound connector type extracted from the `inbound.type` zeebe:property. | ## MessageSubscriptionKeyExactMatch Matches the value exactly. ```csharp public readonly record struct MessageSubscriptionKeyExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## MessageSubscriptionKeyFilterProperty MessageSubscriptionKey property with full advanced search capabilities. ```csharp public sealed class MessageSubscriptionKeyFilterProperty ``` | Property | Type | Description | | ------------ | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for equality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## MessageSubscriptionResult ```csharp public sealed class MessageSubscriptionResult ``` | Property | Type | Description | | -------------------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `BusinessId` | `Nullable` | The business id inherited from the subscribing process instance when this message subscription was opened. It is `null` when the process instance has no business id, and for message start event subscriptions, which are not tied to a process instance. | | `MessageSubscriptionKey` | `MessageSubscriptionKey` | The message subscription key associated with this message subscription. | | `ProcessDefinitionId` | `ProcessDefinitionId` | The process definition ID associated with this message subscription. | | `ProcessDefinitionKey` | `Nullable` | The process definition key associated with this message subscription. | | `ProcessInstanceKey` | `Nullable` | The process instance key associated with this message subscription. Only populated for intermediate event entities. | | `RootProcessInstanceKey` | `Nullable` | The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. | | `ElementId` | `ElementId` | The element ID associated with this message subscription. | | `ElementInstanceKey` | `Nullable` | The element instance key associated with this message subscription. Only populated for intermediate event entities. | | `MessageSubscriptionState` | `MessageSubscriptionStateEnum` | The state of message subscription. **Note for `START_EVENT` subscriptions:** The `CORRELATED` and `MIGRATED` states are not tracked for these subscriptions. To query correlation history for process start events, use the `/correlated-message-subscriptions/search` endpoint. | | `LastUpdatedDate` | `DateTimeOffset` | The last updated date of the message subscription. | | `MessageName` | `String` | The name of the message associated with the message subscription. | | `CorrelationKey` | `String` | The correlation key of the message subscription. | | `MessageSubscriptionType` | `MessageSubscriptionTypeEnum` | The type of message subscription. `START_EVENT` is definition-scoped (process start events). Always has a value; only captured from Camunda 8.10 onwards. `PROCESS_EVENT` is instance-scoped (intermediate catch events). Pre-8.10 entries have no value stored; the API returns `PROCESS_EVENT` as a default for those entries. | | `ToolProperties` | `Dictionary` | The subset of `zeebe:properties` extension properties whose keys start with the `io.camunda.tool:` prefix, extracted from the BPMN element associated with this subscription. Empty object when no matching properties are defined. | | `ProcessDefinitionName` | `String` | The name of the process definition associated with this message subscription. | | `ProcessDefinitionVersion` | `Nullable` | The version of the process definition associated with this message subscription. | | `ToolName` | `String` | Tool name extracted from the `io.camunda.tool:name` zeebe:property. Null when the property is absent. | | `InboundConnectorType` | `String` | Inbound connector type extracted from the `inbound.type` zeebe:property. Null when the property is absent. | | `TenantId` | `TenantId` | The unique identifier of the tenant. | ## MessageSubscriptionSearchQuery ```csharp public sealed class MessageSubscriptionSearchQuery ``` | Property | Type | Description | | -------- | ------------------------------------------------- | ---------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `MessageSubscriptionFilter` | The incident search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## MessageSubscriptionSearchQueryResult ```csharp public sealed class MessageSubscriptionSearchQueryResult ``` | Property | Type | Description | | -------- | --------------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching message subscriptions. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## MessageSubscriptionSearchQuerySortRequest ```csharp public sealed class MessageSubscriptionSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ------------------------------------------------ | --------------------------------------------- | | `Field` | `MessageSubscriptionSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## MessageSubscriptionStateExactMatch Matches the value exactly. ```csharp public readonly record struct MessageSubscriptionStateExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## MessageSubscriptionStateFilterProperty MessageSubscriptionStateEnum with full advanced search capabilities. ```csharp public sealed class MessageSubscriptionStateFilterProperty ``` | Property | Type | Description | | ------------ | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## MessageSubscriptionTypeExactMatch Matches the value exactly. ```csharp public readonly record struct MessageSubscriptionTypeExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## MessageSubscriptionTypeFilterProperty MessageSubscriptionTypeEnum with full advanced search capabilities. ```csharp public sealed class MessageSubscriptionTypeFilterProperty ``` | Property | Type | Description | | ------------ | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## MessageWaitStateDetails ```csharp public sealed class MessageWaitStateDetails : WaitStateDetails ``` | Property | Type | Description | | ---------------- | -------- | ------------------------------------------------------------------------- | | `MessageName` | `String` | The name of the message being awaited. | | `CorrelationKey` | `String` | The correlation key for the message subscription (null for start events). | ## MigrateProcessInstanceMappingInstruction The mapping instructions describe how to map elements from the source process definition to the target process definition. ```csharp public sealed class MigrateProcessInstanceMappingInstruction ``` | Property | Type | Description | | ----------------- | ----------- | ------------------------------- | | `SourceElementId` | `ElementId` | The element id to migrate from. | | `TargetElementId` | `ElementId` | The element id to migrate into. | ## ModifyProcessInstanceVariableInstruction Instruction describing which variables to create or update. ```csharp public sealed class ModifyProcessInstanceVariableInstruction ``` | Property | Type | Description | | ----------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Variables` | `Object` | JSON document that will instantiate the variables at the scope defined by the scopeId. It must be a JSON object, as variables will be mapped in a key-value fashion. | | `ScopeId` | `String` | The id of the element in which scope the variables should be created. Leave empty to create the variables in the global scope of the process instance. | ## OffsetPagination ```csharp public sealed class OffsetPagination : SearchQueryPageRequest ``` | Property | Type | Description | | -------- | ----------------- | ----------------------------------------------------- | | `From` | `Nullable` | The index of items to start searching from. | | `Limit` | `Nullable` | The maximum number of items to return in one request. | ## OperationReference A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. ```csharp public readonly record struct OperationReference : ICamundaLongKey, IEquatable ``` | Property | Type | Description | | -------- | ------- | -------------------------- | | `Value` | `Int64` | The underlying long value. | ## OperationTypeExactMatch Matches the value exactly. ```csharp public readonly record struct OperationTypeExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## OperationTypeFilterProperty AuditLogOperationTypeEnum property with full advanced search capabilities. ```csharp public sealed class OperationTypeFilterProperty ``` | Property | Type | Description | | ------------ | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## OwnAuthorizationSearchResult ```csharp public sealed class OwnAuthorizationSearchResult ``` | Property | Type | Description | | ----------------------- | --------------------------- | ------------------------------------------------------------------- | | `AuthorizationsEnabled` | `Boolean` | Indicates whether authorization checks are enabled for the cluster. | | `Items` | `List` | The matching authorizations. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## Partition Provides information on a partition within a broker node. ```csharp public sealed class Partition ``` | Property | Type | Description | | ------------- | ----------------- | ------------------------------------------------------------------------------------------ | | `PartitionId` | `Int32` | The unique ID of this partition. | | `Role` | `PartitionRole` | Describes the Raft role of the broker for a given partition. | | `Health` | `PartitionHealth` | Describes the current health of the partition. | | `State` | `PartitionState` | Describes the current operational state of the partition within the cluster configuration. | ## PartitionBackupInfo Detailed info of the backup for a given partition. ```csharp public sealed class PartitionBackupInfo ``` | Property | Type | Description | | -------------------- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `PartitionId` | `PartitionId` | The id of the partition. | | `State` | `StateCode` | The state of the backup on this partition. | | `FailureReason` | `String` | Failure reason if the state is 'FAILED'. | | `CreatedAt` | `Nullable` | The timestamp at which the backup was started on this partition. | | `LastUpdatedAt` | `Nullable` | The timestamp at which the backup was last updated on this partition, e.g. changed state from 'IN_PROGRESS' to 'COMPLETED'. | | `SnapshotId` | `String` | The id of the snapshot which is included in this backup. | | `FirstLogPosition` | `Nullable` | The first log position included in this backup. | | `CheckpointPosition` | `Nullable` | The position of the checkpoint for this backup. | | `BrokerId` | `Nullable` | The id of the broker from which the backup was taken for this partition. | | `BrokerVersion` | `String` | The version of the broker from which the backup was taken for this partition. | ## PartitionBackupRange Information about one backup range for a partition. ```csharp public sealed class PartitionBackupRange ``` | Property | Type | Description | | ------------- | ---------------------- | ------------------------------- | | `PartitionId` | `PartitionId` | The id of the partition. | | `Start` | `PartitionBackupState` | The oldest backup in the range. | | `End` | `PartitionBackupState` | The newest backup in the range. | ## PartitionBackupState Detailed information about the backup state for a given partition. ```csharp public sealed class PartitionBackupState ``` | Property | Type | Description | | --------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `CheckpointId` | `CheckpointId` | The id of the checkpoint this backup is based on. | | `CheckpointType` | `BackupType` | The type of the backup. | | `PartitionId` | `Nullable` | The id of the partition. Omitted when nested inside a backup range's `start`/`end`, where the partition is already identified by the enclosing range. | | `CheckpointPosition` | `Int64` | The log position of the checkpoint this backup is based on. | | `FirstLogPosition` | `Int64` | The first log position included in this backup. | | `CheckpointTimestamp` | `DateTimeOffset` | The timestamp at which the checkpoint was created. | ## PartitionCheckpointState Detailed information about the checkpoint state for a given partition. ```csharp public sealed class PartitionCheckpointState ``` | Property | Type | Description | | --------------------- | ---------------- | -------------------------------------------------- | | `CheckpointId` | `CheckpointId` | The id of the checkpoint. | | `CheckpointType` | `CheckpointType` | The type of the checkpoint. | | `PartitionId` | `PartitionId` | The id of the partition. | | `CheckpointPosition` | `Int64` | The log position of the checkpoint. | | `CheckpointTimestamp` | `DateTimeOffset` | The timestamp at which the checkpoint was created. | ## PartitionId The id of a partition. Always a positive number greater than or equal to 1. ```csharp public readonly record struct PartitionId : ICamundaLongKey, IEquatable ``` | Property | Type | Description | | -------- | ------- | -------------------------- | | `Value` | `Int64` | The underlying long value. | ## PhysicalTenantBrokerTopology The partitions of one physical tenant that one broker manages or replicates. ```csharp public sealed class PhysicalTenantBrokerTopology ``` | Property | Type | Description | | ------------ | ----------------- | ----------------------------------------------------------------------------------------------------- | | `BrokerId` | `String` | The unique (within a cluster) identifier of the broker, as reported in the cluster-level broker list. | | `Partitions` | `List` | The partitions of this physical tenant managed or replicated on this broker. | ## PhysicalTenantTopology The topology of a single physical tenant. ```csharp public sealed class PhysicalTenantTopology ``` | Property | Type | Description | | ----------------------- | ------------------------------------ | ------------------------------------------------------------ | | `PhysicalTenantId` | `String` | The id of the physical tenant. | | `PartitionsCount` | `Int32` | The number of partitions spread across this physical tenant. | | `ReplicationFactor` | `Int32` | The configured replication factor for this physical tenant. | | `LastCompletedChangeId` | `String` | ID of the last completed change of this physical tenant. | | `Brokers` | `List` | The brokers holding partitions of this physical tenant. | ## ProblemDetail A Problem detail object as described in [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457). There may be additional properties specific to the problem type. ```csharp public sealed class ProblemDetail ``` | Property | Type | Description | | ---------- | -------- | ------------------------------------------------- | | `Type` | `String` | A URI identifying the problem type. | | `Title` | `String` | A summary of the problem type. | | `Status` | `Int32` | The HTTP status code for this problem. | | `Detail` | `String` | An explanation of the problem in more detail. | | `Instance` | `String` | A URI path identifying the origin of the problem. | ## ProcessDefinitionElementStatisticsQuery Process definition element statistics request. ```csharp public sealed class ProcessDefinitionElementStatisticsQuery ``` | Property | Type | Description | | -------- | ----------------------------------- | ------------------------------------------------- | | `Filter` | `ProcessDefinitionStatisticsFilter` | The process definition statistics search filters. | ## ProcessDefinitionElementStatisticsQueryResult Process definition element statistics query response. ```csharp public sealed class ProcessDefinitionElementStatisticsQueryResult ``` | Property | Type | Description | | -------- | -------------------------------------- | ----------------------- | | `Items` | `List` | The element statistics. | ## ProcessDefinitionFilter Process definition search filter. ```csharp public sealed class ProcessDefinitionFilter ``` | Property | Type | Description | | ---------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Name` | `StringFilterProperty` | Name of this process definition. | | `IsLatestVersion` | `Nullable` | Whether to only return the latest version of each process definition. When using this filter, pagination functionality is limited, you can only paginate forward using `after` and `limit`. The response contains no `startCursor` in the `page`, and requests ignore the `from` and `before` in the `page`. When using this filter, sorting is limited to `processDefinitionId` and `tenantId` fields only. | | `ResourceName` | `String` | Resource name of this process definition. | | `Version` | `Nullable` | Version of this process definition. | | `VersionTag` | `String` | Version tag of this process definition. | | `ProcessDefinitionId` | `StringFilterProperty` | Process definition ID of this process definition. | | `TenantId` | `Nullable` | Tenant ID of this process definition. | | `ProcessDefinitionKey` | `Nullable` | The key for this process definition. | | `HasStartForm` | `Nullable` | Indicates whether the start event of the process has an associated Form Key. | | `State` | `Nullable` | Filter by the process definition's state. When not set, process definitions in any state are returned. Set to `ACTIVE` to exclude draining and deleted definitions (recommended for most use cases). Set to `DRAINING` to return only definitions that are being deleted but still have active process instances draining. Set to `DELETED` to return only definitions that have been deleted but are still retained in secondary storage. | ## ProcessDefinitionId Id of a process definition, from the model. Only ids of process definitions that are deployed are useful. ```csharp public readonly record struct ProcessDefinitionId : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## ProcessDefinitionIdExactMatch Matches the value exactly. ```csharp public readonly record struct ProcessDefinitionIdExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## ProcessDefinitionIdFilterProperty ProcessDefinitionId property with full advanced search capabilities. ```csharp public sealed class ProcessDefinitionIdFilterProperty ``` | Property | Type | Description | | ------------ | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## ProcessDefinitionInstanceStatisticsQuery ```csharp public sealed class ProcessDefinitionInstanceStatisticsQuery ``` | Property | Type | Description | | -------- | ----------------------------------------------------------- | ------------------------- | | `Page` | `OffsetPagination` | Search cursor pagination. | | `Sort` | `List` | Sort field criteria. | ## ProcessDefinitionInstanceStatisticsQueryResult ```csharp public sealed class ProcessDefinitionInstanceStatisticsQueryResult ``` | Property | Type | Description | | -------- | ------------------------------------------------- | -------------------------------------------------- | | `Items` | `List` | The process definition instance statistics result. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## ProcessDefinitionInstanceStatisticsQuerySortRequest ```csharp public sealed class ProcessDefinitionInstanceStatisticsQuerySortRequest ``` | Property | Type | Description | | -------- | ---------------------------------------------------------- | --------------------------------------------- | | `Field` | `ProcessDefinitionInstanceStatisticsQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## ProcessDefinitionInstanceStatisticsResult Process definition instance statistics response. ```csharp public sealed class ProcessDefinitionInstanceStatisticsResult ``` | Property | Type | Description | | ------------------------------------- | --------------------- | --------------------------------------------------------------------------------------------------------- | | `ProcessDefinitionId` | `ProcessDefinitionId` | Id of a process definition, from the model. Only ids of process definitions that are deployed are useful. | | `TenantId` | `TenantId` | The unique identifier of the tenant. | | `LatestProcessDefinitionName` | `String` | Name of the latest deployed process definition instance version. | | `HasMultipleVersions` | `Boolean` | Indicates whether multiple versions of this process definition instance are deployed. | | `ActiveInstancesWithoutIncidentCount` | `Int64` | Total number of currently active process instances of this definition that do not have incidents. | | `ActiveInstancesWithIncidentCount` | `Int64` | Total number of currently active process instances of this definition that have at least one incident. | ## ProcessDefinitionInstanceVersionStatisticsFilter Process definition instance version statistics search filter. ```csharp public sealed class ProcessDefinitionInstanceVersionStatisticsFilter ``` | Property | Type | Description | | --------------------- | --------------------- | -------------------------------------------------------------------- | | `ProcessDefinitionId` | `ProcessDefinitionId` | The ID of the process definition to retrieve version statistics for. | | `TenantId` | `Nullable` | Tenant ID of this process definition. | ## ProcessDefinitionInstanceVersionStatisticsQuery ```csharp public sealed class ProcessDefinitionInstanceVersionStatisticsQuery ``` | Property | Type | Description | | -------- | ------------------------------------------------------------------ | ------------------------------------------------------------------ | | `Page` | `OffsetPagination` | Pagination criteria. | | `Sort` | `List` | Sort field criteria. | | `Filter` | `ProcessDefinitionInstanceVersionStatisticsFilter` | The process definition instance version statistics search filters. | ## ProcessDefinitionInstanceVersionStatisticsQueryResult ```csharp public sealed class ProcessDefinitionInstanceVersionStatisticsQueryResult ``` | Property | Type | Description | | -------- | -------------------------------------------------------- | ---------------------------------------------------------- | | `Items` | `List` | The process definition instance version statistics result. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## ProcessDefinitionInstanceVersionStatisticsQuerySortRequest ```csharp public sealed class ProcessDefinitionInstanceVersionStatisticsQuerySortRequest ``` | Property | Type | Description | | -------- | ----------------------------------------------------------------- | --------------------------------------------- | | `Field` | `ProcessDefinitionInstanceVersionStatisticsQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## ProcessDefinitionInstanceVersionStatisticsResult Process definition instance version statistics response. ```csharp public sealed class ProcessDefinitionInstanceVersionStatisticsResult ``` | Property | Type | Description | | ------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------- | | `ProcessDefinitionId` | `ProcessDefinitionId` | The ID associated with the process definition. | | `ProcessDefinitionKey` | `ProcessDefinitionKey` | The unique key of the process definition. | | `ProcessDefinitionName` | `String` | The name of the process definition. | | `TenantId` | `TenantId` | The tenant ID associated with the process definition. | | `ProcessDefinitionVersion` | `Int32` | The version number of the process definition. | | `ActiveInstancesWithIncidentCount` | `Int64` | The number of active process instances for this version that currently have incidents. | | `ActiveInstancesWithoutIncidentCount` | `Int64` | The number of active process instances for this version that do not have any incidents. | ## ProcessDefinitionKeyExactMatch Matches the value exactly. ```csharp public readonly record struct ProcessDefinitionKeyExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## ProcessDefinitionKeyFilterProperty ProcessDefinitionKey property with full advanced search capabilities. ```csharp public sealed class ProcessDefinitionKeyFilterProperty ``` | Property | Type | Description | | ------------ | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## ProcessDefinitionMessageSubscriptionStatisticsQuery ```csharp public sealed class ProcessDefinitionMessageSubscriptionStatisticsQuery ``` | Property | Type | Description | | -------- | --------------------------- | --------------------------------- | | `Page` | `CursorForwardPagination` | Search cursor pagination. | | `Filter` | `MessageSubscriptionFilter` | The message subscription filters. | ## ProcessDefinitionMessageSubscriptionStatisticsQueryResult ```csharp public sealed class ProcessDefinitionMessageSubscriptionStatisticsQueryResult ``` | Property | Type | Description | | -------- | ------------------------------------------------------------ | ---------------------------------------------------------------- | | `Items` | `List` | The matching process definition message subscription statistics. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## ProcessDefinitionMessageSubscriptionStatisticsResult ```csharp public sealed class ProcessDefinitionMessageSubscriptionStatisticsResult ``` | Property | Type | Description | | ----------------------------------------- | ---------------------- | --------------------------------------------------------------------------------- | | `ProcessDefinitionId` | `ProcessDefinitionId` | The process definition ID associated with this message subscription. | | `TenantId` | `TenantId` | The tenant ID associated with this message subscription. | | `ProcessDefinitionKey` | `ProcessDefinitionKey` | The process definition key associated with this message subscription. | | `ProcessInstancesWithActiveSubscriptions` | `Int64` | The number of process instances with active message subscriptions. | | `ActiveSubscriptions` | `Int64` | The total number of active message subscriptions for this process definition key. | ## ProcessDefinitionResult ```csharp public sealed class ProcessDefinitionResult ``` | Property | Type | Description | | ---------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Name` | `String` | Name of this process definition. | | `ResourceName` | `String` | Resource name for this process definition. | | `Version` | `Int32` | Version of this process definition. | | `VersionTag` | `String` | Version tag of this process definition. | | `ProcessDefinitionId` | `ProcessDefinitionId` | Process definition ID of this process definition. | | `TenantId` | `TenantId` | Tenant ID of this process definition. | | `ProcessDefinitionKey` | `ProcessDefinitionKey` | The key for this process definition. | | `HasStartForm` | `Boolean` | Indicates whether the start event of the process has an associated Form Key. | | `State` | `ProcessDefinitionResultState` | The state of this process definition. `DRAINING` indicates the definition is being deleted but still has active process instances draining before it is removed. | ## ProcessDefinitionSearchQuery ```csharp public sealed class ProcessDefinitionSearchQuery ``` | Property | Type | Description | | -------- | ----------------------------------------------- | -------------------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `ProcessDefinitionFilter` | The process definition search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## ProcessDefinitionSearchQueryResult ```csharp public sealed class ProcessDefinitionSearchQueryResult ``` | Property | Type | Description | | -------- | ------------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching process definitions. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## ProcessDefinitionSearchQuerySortRequest ```csharp public sealed class ProcessDefinitionSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ---------------------------------------------- | --------------------------------------------- | | `Field` | `ProcessDefinitionSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## ProcessDefinitionStatisticsFilter Process definition statistics search filter. ```csharp public sealed class ProcessDefinitionStatisticsFilter ``` | Property | Type | Description | | ---------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `StartDate` | `DateTimeFilterProperty` | The start date. | | `EndDate` | `DateTimeFilterProperty` | The end date. | | `State` | `ProcessInstanceStateFilterProperty` | The process instance state. | | `HasIncident` | `Nullable` | Whether this process instance has a related incident or not. | | `SuspendedDate` | `DateTimeFilterProperty` | The time this process instance most recently entered the SUSPENDED state. This is cleared (null) again once the process instance is resumed. | | `TenantId` | `StringFilterProperty` | The tenant id. | | `Variables` | `List` | The process instance variables. | | `ProcessInstanceKey` | `ProcessInstanceKeyFilterProperty` | The key of this process instance. | | `ParentProcessInstanceKey` | `ProcessInstanceKeyFilterProperty` | The parent process instance key. | | `ParentElementInstanceKey` | `ElementInstanceKeyFilterProperty` | The parent element instance key. | | `BatchOperationId` | `StringFilterProperty` | The batch operation id. **Deprecated**: Use `batchOperationKey` instead. This field will be removed in a future release. If both `batchOperationId` and `batchOperationKey` are provided, the request will be rejected with a 400 error. | | `BatchOperationKey` | `StringFilterProperty` | The batch operation key. | | `ErrorMessage` | `StringFilterProperty` | The error message related to the process. | | `HasRetriesLeft` | `Nullable` | Whether the process has failed jobs with retries left. | | `ElementInstanceState` | `ElementInstanceStateFilterProperty` | The state of the element instances associated with the process instance. | | `ElementId` | `StringFilterProperty` | The element id associated with the process instance. | | `HasElementInstanceIncident` | `Nullable` | Whether the element instance has an incident or not. | | `IncidentErrorHashCode` | `IntegerFilterProperty` | The incident error hash code, associated with this process. | | `Tags` | `List` | List of tags. Tags need to start with a letter; then alphanumerics, `_`, `-`, `:`, or `.`; length ≤ 100. | | `BusinessId` | `StringFilterProperty` | The business id associated with the process instance. | | `Or` | `List` | Defines a list of alternative filter groups combined using OR logic. Each object in the array is evaluated independently, and the filter matches if any one of them is satisfied. Top-level fields and the `$or` clause are combined using AND logic — meaning: (top-level filters) AND (any of the `$or` filters) must match. Example: `json { "state": "ACTIVE", "tenantId": 123, "$or": [ { "processDefinitionId": "process_v1" }, { "processDefinitionId": "process_v2", "hasIncident": true } ] } ` This matches process instances that: are in ACTIVE state have tenant id equal to 123 and match either: processDefinitionId is process_v1, or processDefinitionId is process_v2 and hasIncident is true Note: Using complex $or conditions may impact performance, use with caution in high-volume environments. | ## ProcessDefinitionVariableNameFilter Process definition variable name filter request. ```csharp public sealed class ProcessDefinitionVariableNameFilter ``` | Property | Type | Description | | -------- | ---------------------- | -------------------------------- | | `Name` | `StringFilterProperty` | The variable name search filter. | ## ProcessDefinitionVariableNameSearchQuery Process definition variable name search query request. ```csharp public sealed class ProcessDefinitionVariableNameSearchQuery ``` | Property | Type | Description | | -------- | ------------------------------------- | ---------------------------------------------------- | | `Filter` | `ProcessDefinitionVariableNameFilter` | The process definition variable name search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## ProcessDefinitionVariableNameSearchQueryResult Process definition variable name search query response. ```csharp public sealed class ProcessDefinitionVariableNameSearchQueryResult ``` | Property | Type | Description | | -------- | ------------------------------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching variable names. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## ProcessDefinitionVariableNameSearchResult Process definition variable name search response item. ```csharp public sealed class ProcessDefinitionVariableNameSearchResult ``` | Property | Type | Description | | -------- | -------- | ------------------ | | `Name` | `String` | The variable name. | ## ProcessElementStatisticsResult Process element statistics response. ```csharp public sealed class ProcessElementStatisticsResult ``` | Property | Type | Description | | ----------- | ----------- | ------------------------------------------------------- | | `ElementId` | `ElementId` | The element ID for which the results are aggregated. | | `Active` | `Int64` | The total number of active instances of the element. | | `Canceled` | `Int64` | The total number of canceled instances of the element. | | `Incidents` | `Int64` | The total number of incidents for the element. | | `Completed` | `Int64` | The total number of completed instances of the element. | ## ProcessInstanceBusinessIdAssignmentInstruction The instruction describing the business id to assign to a running process instance. ```csharp public sealed class ProcessInstanceBusinessIdAssignmentInstruction ``` | Property | Type | Description | | ------------ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `BusinessId` | `BusinessId` | An optional, user-defined string identifier that identifies the process instance within the scope of a process definition (scoped by tenant). If provided and uniqueness enforcement is enabled, the engine will reject creation if another root process instance with the same business id is already active for the same process definition. Note that any active child process instances with the same business id are not taken into account. | ## ProcessInstanceCallHierarchyEntry ```csharp public sealed class ProcessInstanceCallHierarchyEntry ``` | Property | Type | Description | | ----------------------- | ---------------------- | ---------------------------------------------------------------------------------------------- | | `ProcessInstanceKey` | `ProcessInstanceKey` | The key of the process instance. | | `ProcessDefinitionKey` | `ProcessDefinitionKey` | The key of the process definition. | | `ProcessDefinitionName` | `String` | The name of the process definition (fall backs to the process definition id if not available). | ## ProcessInstanceCancellationBatchOperationRequest The process instance filter that defines which process instances should be canceled. ```csharp public sealed class ProcessInstanceCancellationBatchOperationRequest ``` | Property | Type | Description | | -------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | | `Filter` | `ProcessInstanceFilter` | The process instance filter. | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | ## ProcessInstanceCreationInstruction Instructions for creating a process instance. The process definition can be specified either by id or by key. ```csharp public abstract class ProcessInstanceCreationInstruction ``` ## ProcessInstanceCreationInstructionById ```csharp public sealed class ProcessInstanceCreationInstructionById : ProcessInstanceCreationInstruction, ITenantIdSettable ``` | Property | Type | Description | | -------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ProcessDefinitionId` | `ProcessDefinitionId` | The BPMN process id of the process definition to start an instance of. | | `ProcessDefinitionVersion` | `Nullable` | The version of the process. If omitted, the latest active version is used. | | `Variables` | `Object` | JSON object that will instantiate the variables for the root variable scope of the process instance. | | `TenantId` | `Nullable` | The tenant id of the process definition. If multi-tenancy is enabled, provide the tenant id of the process definition to start a process instance of. If multi-tenancy is disabled, don't provide this parameter. | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | | `StartInstructions` | `List` | List of start instructions. By default, the process instance will start at the start event. If provided, the process instance will apply start instructions after it has been created. | | `RuntimeInstructions` | `List` | Runtime instructions (alpha). List of instructions that affect the runtime behavior of the process instance. Refer to specific instruction types for more details. This parameter is an alpha feature and may be subject to change in future releases. | | `AwaitCompletion` | `Nullable` | Wait for the process instance to complete. If the process instance does not complete within the request timeout limit, a 504 response status will be returned. The process instance will continue to run in the background regardless of the timeout. Disabled by default. | | `FetchVariables` | `List` | List of variables by name to be included in the response when awaitCompletion is set to true. If empty, all visible variables in the root scope will be returned. | | `RequestTimeout` | `Nullable` | Timeout (in ms) the request waits for the process to complete. By default or when set to 0, the generic request timeout configured in the cluster is applied. | | `Tags` | `List` | List of tags. Tags need to start with a letter; then alphanumerics, `_`, `-`, `:`, or `.`; length ≤ 100. | | `BusinessId` | `Nullable` | An optional, user-defined string identifier that identifies the process instance within the scope of a process definition (scoped by tenant). If provided and uniqueness enforcement is enabled, the engine will reject creation if another root process instance with the same business id is already active for the same process definition. Note that any active child process instances with the same business id are not taken into account. | ## ProcessInstanceCreationInstructionByKey ```csharp public sealed class ProcessInstanceCreationInstructionByKey : ProcessInstanceCreationInstruction, ITenantIdSettable ``` | Property | Type | Description | | -------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ProcessDefinitionKey` | `ProcessDefinitionKey` | The unique key identifying the process definition, for example, returned for a process in the deploy resources endpoint. | | `ProcessDefinitionVersion` | `Nullable` | As the version is already identified by the `processDefinitionKey`, the value of this field is ignored. It's here for backwards-compatibility only as previous releases accepted it in request bodies. | | `Variables` | `Object` | Set of variables as JSON object to instantiate in the root variable scope of the process instance. Can include nested complex objects. | | `StartInstructions` | `List` | List of start instructions. By default, the process instance will start at the start event. If provided, the process instance will apply start instructions after it has been created. | | `RuntimeInstructions` | `List` | Runtime instructions (alpha). List of instructions that affect the runtime behavior of the process instance. Refer to specific instruction types for more details. This parameter is an alpha feature and may be subject to change in future releases. | | `TenantId` | `Nullable` | The tenant id of the process definition. If multi-tenancy is enabled, provide the tenant id of the process definition to start a process instance of. If multi-tenancy is disabled, don't provide this parameter. | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | | `AwaitCompletion` | `Nullable` | Wait for the process instance to complete. If the process instance does not complete within the request timeout limit, a 504 response status will be returned. The process instance will continue to run in the background regardless of the timeout. Disabled by default. | | `RequestTimeout` | `Nullable` | Timeout (in ms) the request waits for the process to complete. By default or when set to 0, the generic request timeout configured in the cluster is applied. | | `FetchVariables` | `List` | List of variables by name to be included in the response when awaitCompletion is set to true. If empty, all visible variables in the root scope will be returned. | | `Tags` | `List` | List of tags. Tags need to start with a letter; then alphanumerics, `_`, `-`, `:`, or `.`; length ≤ 100. | | `BusinessId` | `Nullable` | An optional, user-defined string identifier that identifies the process instance within the scope of a process definition (scoped by tenant). If provided and uniqueness enforcement is enabled, the engine will reject creation if another root process instance with the same business id is already active for the same process definition. Note that any active child process instances with the same business id are not taken into account. | ## ProcessInstanceCreationRuntimeInstruction ```csharp public abstract class ProcessInstanceCreationRuntimeInstruction ``` ## ProcessInstanceCreationStartInstruction ```csharp public sealed class ProcessInstanceCreationStartInstruction ``` | Property | Type | Description | | ----------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ElementId` | `ElementId` | Future extensions might include: - different types of start instructions - ability to set local variables for different flow scopes For now, however, the start instruction is implicitly a "startBeforeElement" instruction | ## ProcessInstanceCreationTerminateInstruction Terminates the process instance after a specific BPMN element is completed or terminated. ```csharp public sealed class ProcessInstanceCreationTerminateInstruction : ProcessInstanceCreationRuntimeInstruction ``` | Property | Type | Description | | ---------------- | ----------- | -------------------------------------------------------------------------------------------------- | | `AfterElementId` | `ElementId` | The id of the element that, once completed or terminated, will cause the process to be terminated. | ## ProcessInstanceDeletionBatchOperationRequest The process instance filter that defines which process instances should be deleted. ```csharp public sealed class ProcessInstanceDeletionBatchOperationRequest ``` | Property | Type | Description | | -------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | | `Filter` | `ProcessInstanceFilter` | The process instance filter. | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | ## ProcessInstanceElementStatisticsQueryResult Process instance element statistics query response. ```csharp public sealed class ProcessInstanceElementStatisticsQueryResult ``` | Property | Type | Description | | -------- | -------------------------------------- | ----------------------- | | `Items` | `List` | The element statistics. | ## ProcessInstanceFilter Process instance search filter. ```csharp public sealed class ProcessInstanceFilter ``` | Property | Type | Description | | ----------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `ProcessDefinitionId` | `StringFilterProperty` | The process definition id. | | `ProcessDefinitionName` | `StringFilterProperty` | The process definition name. | | `ProcessDefinitionVersion` | `IntegerFilterProperty` | The process definition version. | | `ProcessDefinitionVersionTag` | `StringFilterProperty` | The process definition version tag. | | `ProcessDefinitionKey` | `ProcessDefinitionKeyFilterProperty` | The process definition key. | | `StartDate` | `DateTimeFilterProperty` | The start date. | | `EndDate` | `DateTimeFilterProperty` | The end date. | | `State` | `ProcessInstanceStateFilterProperty` | The process instance state. | | `HasIncident` | `Nullable` | Whether this process instance has a related incident or not. | | `SuspendedDate` | `DateTimeFilterProperty` | The time this process instance most recently entered the SUSPENDED state. This is cleared (null) again once the process instance is resumed. | | `TenantId` | `StringFilterProperty` | The tenant id. | | `Variables` | `List` | The process instance variables. | | `ProcessInstanceKey` | `ProcessInstanceKeyFilterProperty` | The key of this process instance. | | `ParentProcessInstanceKey` | `ProcessInstanceKeyFilterProperty` | The parent process instance key. | | `ParentElementInstanceKey` | `ElementInstanceKeyFilterProperty` | The parent element instance key. | | `BatchOperationId` | `StringFilterProperty` | The batch operation id. **Deprecated**: Use `batchOperationKey` instead. This field will be removed in a future release. If both `batchOperationId` and `batchOperationKey` are provided, the request will be rejected with a 400 error. | | `BatchOperationKey` | `StringFilterProperty` | The batch operation key. | | `ErrorMessage` | `StringFilterProperty` | The error message related to the process. | | `HasRetriesLeft` | `Nullable` | Whether the process has failed jobs with retries left. | | `ElementInstanceState` | `ElementInstanceStateFilterProperty` | The state of the element instances associated with the process instance. | | `ElementId` | `StringFilterProperty` | The element id associated with the process instance. | | `HasElementInstanceIncident` | `Nullable` | Whether the element instance has an incident or not. | | `IncidentErrorHashCode` | `IntegerFilterProperty` | The incident error hash code, associated with this process. | | `Tags` | `List` | List of tags. Tags need to start with a letter; then alphanumerics, `_`, `-`, `:`, or `.`; length ≤ 100. | | `BusinessId` | `StringFilterProperty` | The business id associated with the process instance. | | `Or` | `List` | Defines a list of alternative filter groups combined using OR logic. Each object in the array is evaluated independently, and the filter matches if any one of them is satisfied. Top-level fields and the `$or` clause are combined using AND logic — meaning: (top-level filters) AND (any of the `$or` filters) must match. Example: `json { "state": "ACTIVE", "tenantId": 123, "$or": [ { "processDefinitionId": "process_v1" }, { "processDefinitionId": "process_v2", "hasIncident": true } ] } ` This matches process instances that: are in ACTIVE state have tenant id equal to 123 and match either: processDefinitionId is process_v1, or processDefinitionId is process_v2 and hasIncident is true Note: Using complex $or conditions may impact performance, use with caution in high-volume environments. | ## ProcessInstanceFilterFields Process instance search filter. ```csharp public sealed class ProcessInstanceFilterFields ``` | Property | Type | Description | | ----------------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ProcessDefinitionId` | `StringFilterProperty` | The process definition id. | | `ProcessDefinitionName` | `StringFilterProperty` | The process definition name. | | `ProcessDefinitionVersion` | `IntegerFilterProperty` | The process definition version. | | `ProcessDefinitionVersionTag` | `StringFilterProperty` | The process definition version tag. | | `ProcessDefinitionKey` | `ProcessDefinitionKeyFilterProperty` | The process definition key. | | `StartDate` | `DateTimeFilterProperty` | The start date. | | `EndDate` | `DateTimeFilterProperty` | The end date. | | `State` | `ProcessInstanceStateFilterProperty` | The process instance state. | | `HasIncident` | `Nullable` | Whether this process instance has a related incident or not. | | `SuspendedDate` | `DateTimeFilterProperty` | The time this process instance most recently entered the SUSPENDED state. This is cleared (null) again once the process instance is resumed. | | `TenantId` | `StringFilterProperty` | The tenant id. | | `Variables` | `List` | The process instance variables. | | `ProcessInstanceKey` | `ProcessInstanceKeyFilterProperty` | The key of this process instance. | | `ParentProcessInstanceKey` | `ProcessInstanceKeyFilterProperty` | The parent process instance key. | | `ParentElementInstanceKey` | `ElementInstanceKeyFilterProperty` | The parent element instance key. | | `BatchOperationId` | `StringFilterProperty` | The batch operation id. **Deprecated**: Use `batchOperationKey` instead. This field will be removed in a future release. If both `batchOperationId` and `batchOperationKey` are provided, the request will be rejected with a 400 error. | | `BatchOperationKey` | `StringFilterProperty` | The batch operation key. | | `ErrorMessage` | `StringFilterProperty` | The error message related to the process. | | `HasRetriesLeft` | `Nullable` | Whether the process has failed jobs with retries left. | | `ElementInstanceState` | `ElementInstanceStateFilterProperty` | The state of the element instances associated with the process instance. | | `ElementId` | `StringFilterProperty` | The element id associated with the process instance. | | `HasElementInstanceIncident` | `Nullable` | Whether the element instance has an incident or not. | | `IncidentErrorHashCode` | `IntegerFilterProperty` | The incident error hash code, associated with this process. | | `Tags` | `List` | List of tags. Tags need to start with a letter; then alphanumerics, `_`, `-`, `:`, or `.`; length ≤ 100. | | `BusinessId` | `StringFilterProperty` | The business id associated with the process instance. | ## ProcessInstanceIncidentResolutionBatchOperationRequest The process instance filter that defines which process instances should have their incidents resolved. ```csharp public sealed class ProcessInstanceIncidentResolutionBatchOperationRequest ``` | Property | Type | Description | | -------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | | `Filter` | `ProcessInstanceFilter` | The process instance filter. | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | ## ProcessInstanceKeyExactMatch Matches the value exactly. ```csharp public readonly record struct ProcessInstanceKeyExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## ProcessInstanceKeyFilterProperty ProcessInstanceKey property with full advanced search capabilities. ```csharp public sealed class ProcessInstanceKeyFilterProperty ``` | Property | Type | Description | | ------------ | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## ProcessInstanceMigrationBatchOperationPlan The migration instructions describe how to migrate a process instance from one process definition to another. ```csharp public sealed class ProcessInstanceMigrationBatchOperationPlan ``` | Property | Type | Description | | ---------------------------- | ------------------------------------------------ | ---------------------------------- | | `TargetProcessDefinitionKey` | `ProcessDefinitionKey` | The target process definition key. | | `MappingInstructions` | `List` | The mapping instructions. | ## ProcessInstanceMigrationBatchOperationRequest ```csharp public sealed class ProcessInstanceMigrationBatchOperationRequest ``` | Property | Type | Description | | -------------------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `Filter` | `ProcessInstanceFilter` | The process instance filter. | | `MigrationPlan` | `ProcessInstanceMigrationBatchOperationPlan` | The migration plan. | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | ## ProcessInstanceMigrationInstruction The migration instructions describe how to migrate a process instance from one process definition to another. ```csharp public sealed class ProcessInstanceMigrationInstruction ``` | Property | Type | Description | | ---------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | | `TargetProcessDefinitionKey` | `ProcessDefinitionKey` | The key of process definition to migrate the process instance to. | | `MappingInstructions` | `List` | Element mappings from the source process instance to the target process instance. | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | ## ProcessInstanceModificationActivateInstruction Instruction describing an element to activate. ```csharp public sealed class ProcessInstanceModificationActivateInstruction ``` | Property | Type | Description | | ---------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ElementId` | `ElementId` | The id of the element to activate. | | `VariableInstructions` | `List` | Instructions describing which variables to create or update. | | `AncestorElementInstanceKey` | `Nullable` | The key of the ancestor scope the element instance should be created in. Set to -1 to create the new element instance within an existing element instance of the flow scope. If multiple instances of the target element's flow scope exist, choose one specifically with this property by providing its key. | ## ProcessInstanceModificationBatchOperationRequest The process instance filter to define on which process instances tokens should be moved, and new element instances should be activated or terminated. ```csharp public sealed class ProcessInstanceModificationBatchOperationRequest ``` | Property | Type | Description | | -------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `Filter` | `ProcessInstanceFilter` | The process instance filter. | | `MoveInstructions` | `List` | Instructions for moving tokens between elements. | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | ## ProcessInstanceModificationInstruction ```csharp public sealed class ProcessInstanceModificationInstruction ``` | Property | Type | Description | | ----------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | | `ActivateInstructions` | `List` | Instructions describing which elements to activate in which scopes and which variables to create or update. | | `MoveInstructions` | `List` | Instructions describing which elements to move from one scope to another. | | `TerminateInstructions` | `List` | Instructions describing which elements to terminate. | ## ProcessInstanceModificationMoveBatchOperationInstruction Instructions describing a move operation. This instruction will terminate all active element instances at `sourceElementId` and activate a new element instance for each terminated one at `targetElementId`. The new element instances are created in the parent scope of the source element instances. ```csharp public sealed class ProcessInstanceModificationMoveBatchOperationInstruction ``` | Property | Type | Description | | ----------------- | ----------- | ---------------------- | | `SourceElementId` | `ElementId` | The source element ID. | | `TargetElementId` | `ElementId` | The target element ID. | ## ProcessInstanceModificationMoveInstruction Instruction describing a move operation. This instruction will terminate active element instances based on the sourceElementInstruction and activate a new element instance for each terminated one at targetElementId. Note that, for multi-instance activities, only the multi-instance body instances will activate new element instances at the target id. ```csharp public sealed class ProcessInstanceModificationMoveInstruction ``` | Property | Type | Description | | -------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SourceElementInstruction` | `SourceElementInstruction` | Defines the source element identifier for the move instruction. It can either be a sourceElementId, or sourceElementInstanceKey. | | `TargetElementId` | `ElementId` | The target element id. | | `AncestorScopeInstruction` | `AncestorScopeInstruction` | Defines the ancestor scope for the created element instances. The default behavior resembles a "direct" scope instruction with an `ancestorElementInstanceKey` of `"-1"`. | | `VariableInstructions` | `List` | Instructions describing which variables to create or update. | ## ProcessInstanceModificationTerminateByIdInstruction Instruction describing which elements to terminate. The element instances are determined at runtime by the given id. ```csharp public sealed class ProcessInstanceModificationTerminateByIdInstruction : ProcessInstanceModificationTerminateInstruction ``` | Property | Type | Description | | ----------- | ----------- | ------------------------------------------------------------------------------------- | | `ElementId` | `ElementId` | The id of the elements to terminate. The element instances are determined at runtime. | ## ProcessInstanceModificationTerminateByKeyInstruction Instruction providing the key of the element instance to terminate. ```csharp public sealed class ProcessInstanceModificationTerminateByKeyInstruction : ProcessInstanceModificationTerminateInstruction ``` | Property | Type | Description | | -------------------- | -------------------- | --------------------------------------------- | | `ElementInstanceKey` | `ElementInstanceKey` | The key of the element instance to terminate. | ## ProcessInstanceModificationTerminateInstruction Instruction describing which elements to terminate. ```csharp public abstract class ProcessInstanceModificationTerminateInstruction ``` ## ProcessInstanceReference ```csharp public sealed class ProcessInstanceReference ``` | Property | Type | Description | | ---------------------- | ---------------------- | ---------------------------------------- | | `ProcessDefinitionKey` | `ProcessDefinitionKey` | The key of the process definition. | | `ProcessInstanceKey` | `ProcessInstanceKey` | The key of the created process instance. | ## ProcessInstanceResult Process instance search response item. ```csharp public sealed class ProcessInstanceResult ``` | Property | Type | Description | | ----------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ProcessDefinitionId` | `ProcessDefinitionId` | Id of a process definition, from the model. Only ids of process definitions that are deployed are useful. | | `ProcessDefinitionName` | `String` | The process definition name. | | `ProcessDefinitionVersion` | `Int32` | The process definition version. | | `ProcessDefinitionVersionTag` | `String` | The process definition version tag. | | `StartDate` | `DateTimeOffset` | The start time of the process instance. | | `EndDate` | `Nullable` | The completion or termination time of the process instance. | | `State` | `ProcessInstanceStateEnum` | Process instance states | | `SuspendedDate` | `Nullable` | The time this process instance most recently entered the `SUSPENDED` state. This is `null` if the process instance is not currently suspended. | | `HasIncident` | `Boolean` | Whether this process instance has a related incident or not. | | `TenantId` | `TenantId` | The unique identifier of the tenant. | | `ProcessInstanceKey` | `ProcessInstanceKey` | The key of this process instance. | | `ProcessDefinitionKey` | `ProcessDefinitionKey` | The process definition key. | | `ParentProcessInstanceKey` | `Nullable` | The parent process instance key. | | `ParentElementInstanceKey` | `Nullable` | The parent element instance key. | | `RootProcessInstanceKey` | `Nullable` | The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. | | `Tags` | `List` | List of tags. Tags need to start with a letter; then alphanumerics, `_`, `-`, `:`, or `.`; length ≤ 100. | | `BusinessId` | `Nullable` | The business id associated with this process instance. | ## ProcessInstanceResumptionBatchOperationRequest The process instance filter that defines which process instances should be resumed. ```csharp public sealed class ProcessInstanceResumptionBatchOperationRequest ``` | Property | Type | Description | | -------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | | `Filter` | `ProcessInstanceFilter` | The process instance filter. | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | ## ProcessInstanceSearchQuery Process instance search request. ```csharp public sealed class ProcessInstanceSearchQuery ``` | Property | Type | Description | | -------- | --------------------------------------------- | ------------------------------------ | | `Sort` | `List` | Sort field criteria. | | `Filter` | `ProcessInstanceFilter` | The process instance search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## ProcessInstanceSearchQueryResult Process instance search response. ```csharp public sealed class ProcessInstanceSearchQueryResult ``` | Property | Type | Description | | -------- | ----------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching process instances. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## ProcessInstanceSearchQuerySortRequest ```csharp public sealed class ProcessInstanceSearchQuerySortRequest ``` | Property | Type | Description | | -------- | -------------------------------------------- | --------------------------------------------- | | `Field` | `ProcessInstanceSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## ProcessInstanceSequenceFlowResult Process instance sequence flow result. ```csharp public sealed class ProcessInstanceSequenceFlowResult ``` | Property | Type | Description | | ------------------------ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SequenceFlowId` | `String` | The sequence flow id. | | `ProcessInstanceKey` | `ProcessInstanceKey` | The key of this process instance. | | `RootProcessInstanceKey` | `Nullable` | The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. | | `ProcessDefinitionKey` | `ProcessDefinitionKey` | The process definition key. | | `ProcessDefinitionId` | `ProcessDefinitionId` | The process definition id. | | `ElementId` | `ElementId` | The element id for this sequence flow, as provided in the BPMN process. | | `TenantId` | `TenantId` | The unique identifier of the tenant. | ## ProcessInstanceSequenceFlowsQueryResult Process instance sequence flows query response. ```csharp public sealed class ProcessInstanceSequenceFlowsQueryResult ``` | Property | Type | Description | | -------- | ----------------------------------------- | ------------------- | | `Items` | `List` | The sequence flows. | ## ProcessInstanceStateExactMatch Matches the value exactly. ```csharp public readonly record struct ProcessInstanceStateExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## ProcessInstanceStateFilterProperty ProcessInstanceStateEnum property with full advanced search capabilities. ```csharp public sealed class ProcessInstanceStateFilterProperty ``` | Property | Type | Description | | ------------ | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## ProcessInstanceSuspensionBatchOperationRequest The process instance filter that defines which process instances should be suspended. ```csharp public sealed class ProcessInstanceSuspensionBatchOperationRequest ``` | Property | Type | Description | | -------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | | `Filter` | `ProcessInstanceFilter` | The process instance filter. | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | ## ProcessInstanceWaitStateStatisticsQueryResult Process instance wait state statistics query response. ```csharp public sealed class ProcessInstanceWaitStateStatisticsQueryResult ``` | Property | Type | Description | | -------- | ------------------------------------------------ | -------------------------- | | `Items` | `List` | The wait state statistics. | ## ProcessInstanceWaitStateStatisticsResult Process instance wait state statistics response item. ```csharp public sealed class ProcessInstanceWaitStateStatisticsResult ``` | Property | Type | Description | | -------------- | ----------- | -------------------------------------------------------- | | `ElementId` | `ElementId` | The element id for which the wait states are aggregated. | | `WaitingCount` | `Int64` | The total number of waiting instances of the element. | ## RebalanceCancellationResponse Response to a rebalance cancellation request. ```csharp public sealed class RebalanceCancellationResponse ``` | Property | Type | Description | | ------------ | --------- | -------------------------------------- | | `WasRunning` | `Boolean` | Whether there was a rebalance to stop. | ## ResolvedSecret ```csharp public sealed class ResolvedSecret ``` | Property | Type | Description | | ----------- | -------- | ------------------------------------------------------------- | | `Reference` | `String` | The resolved secret reference of the form `camunda.secrets.`. | | `Value` | `String` | The resolved secret value. | ## ResourceFilter Resource search filter. ```csharp public sealed class ResourceFilter ``` | Property | Type | Description | | --------------- | ----------------------------- | -------------------------------- | | `ResourceKey` | `ResourceKeyFilterProperty` | The key for this resource. | | `ResourceName` | `StringFilterProperty` | Resource name of this resource. | | `ResourceId` | `StringFilterProperty` | Resource ID of this resource. | | `Version` | `IntegerFilterProperty` | Version of this resource. | | `VersionTag` | `StringFilterProperty` | Version tag of this resource. | | `DeploymentKey` | `DeploymentKeyFilterProperty` | Deployment key of this resource. | | `TenantId` | `Nullable` | Tenant ID of this resource. | ## ResourceKeyExactMatch Matches the value exactly. ```csharp public readonly record struct ResourceKeyExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## ResourceKeyFilterProperty ResourceKey property with full advanced search capabilities. ```csharp public sealed class ResourceKeyFilterProperty ``` | Property | Type | Description | | ------------ | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## ResourceResult ```csharp public sealed class ResourceResult ``` | Property | Type | Description | | -------------- | ------------- | ------------------------------------------------------ | | `ResourceName` | `String` | The resource name from which this resource was parsed. | | `Version` | `Int32` | The assigned resource version. | | `VersionTag` | `String` | The version tag of this resource. | | `ResourceId` | `String` | The resource ID of this resource. | | `TenantId` | `TenantId` | The tenant ID of this resource. | | `ResourceKey` | `ResourceKey` | The unique key of this resource. | ## ResourceSearchQuery ```csharp public sealed class ResourceSearchQuery ``` | Property | Type | Description | | -------- | -------------------------------------- | ---------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `ResourceFilter` | The resource search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## ResourceSearchQueryResult ```csharp public sealed class ResourceSearchQueryResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching resources. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## ResourceSearchQuerySortRequest ```csharp public sealed class ResourceSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ------------------------------------- | --------------------------------------------- | | `Field` | `ResourceSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## RestoreBrokerStatus The restore status of a single broker. ```csharp public sealed class RestoreBrokerStatus ``` | Property | Type | Description | | --------------------- | ------------------------------ | --------------------------------------------------------------------- | | `BrokerId` | `String` | The ID of the broker, including its zone if it belongs to one. | | `PartitionsRestored` | `Int32` | The number of the broker's partitions that have been restored so far. | | `PartitionsToRestore` | `Int32` | The total number of the broker's partitions to restore. | | `Partitions` | `List` | The per-partition restore status for this broker. | ## RestorePartitionStatus The restore status of a single partition on a broker. ```csharp public sealed class RestorePartitionStatus ``` | Property | Type | Description | | ------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------- | | `PartitionId` | `Int32` | The ID of the partition. | | `State` | `RestorePartitionStatusState` | The restore state of the partition. | | `BackupIds` | `List` | The IDs of the backups this partition is restored from. | | `CompletedAt` | `Nullable` | The time the partition was restored, as an ISO 8601 timestamp; null unless the partition state is `RESTORED`. | ## RestoreRequest Describes a restore request. Provide either a list of backup IDs or a time range (`from`/`to`) that selects the backups to restore; the two are mutually exclusive. ```csharp public sealed class RestoreRequest ``` | Property | Type | Description | | ----------- | -------------------------- | ---------------------------------------------------------------------- | | `From` | `Nullable` | The start of the time range to restore from, as an ISO 8601 timestamp. | | `To` | `Nullable` | The end of the time range to restore from, as an ISO 8601 timestamp. | | `BackupIds` | `List` | The IDs of the backups to restore from, one per partition. | ## RestoreStatusResponse The status of the restore that is currently in progress. ```csharp public sealed class RestoreStatusResponse ``` | Property | Type | Description | | ----------- | ----------------------------- | ------------------------------------------------------- | | `Status` | `RestoreStatusResponseStatus` | The overall status of the restore. | | `ChangeId` | `String` | The ID of the cluster change that performs the restore. | | `StartedAt` | `Nullable` | The time the restore started, as an ISO 8601 timestamp. | | `Brokers` | `List` | The per-broker restore status. | ## ResumeProcessInstanceRequest ```csharp public sealed class ResumeProcessInstanceRequest ``` | Property | Type | Description | | -------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | ## RetryDecision ```csharp public readonly record struct RetryDecision : IEquatable ``` | Property | Type | Description | | ----------- | --------- | ----------- | | `Retryable` | `Boolean` | | | `Reason` | `String` | | ## RoleClientResult ```csharp public sealed class RoleClientResult ``` | Property | Type | Description | | ---------- | ---------- | --------------------- | | `ClientId` | `ClientId` | The ID of the client. | ## RoleClientSearchQueryRequest ```csharp public sealed class RoleClientSearchQueryRequest ``` | Property | Type | Description | | -------- | ---------------------------------------- | -------------------- | | `Sort` | `List` | Sort field criteria. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## RoleClientSearchQuerySortRequest ```csharp public sealed class RoleClientSearchQuerySortRequest ``` | Property | Type | Description | | -------- | --------------------------------------- | --------------------------------------------- | | `Field` | `RoleClientSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## RoleClientSearchResult ```csharp public sealed class RoleClientSearchResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching clients. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## RoleCreateRequest ```csharp public sealed class RoleCreateRequest ``` | Property | Type | Description | | ------------- | -------- | --------------------------------- | | `RoleId` | `RoleId` | The ID of the new role. | | `Name` | `String` | The display name of the new role. | | `Description` | `String` | The description of the new role. | ## RoleCreateResult ```csharp public sealed class RoleCreateResult ``` | Property | Type | Description | | ------------- | -------- | ------------------------------------- | | `RoleId` | `RoleId` | The ID of the created role. | | `Name` | `String` | The display name of the created role. | | `Description` | `String` | The description of the created role. | ## RoleFilter Role filter request ```csharp public sealed class RoleFilter ``` | Property | Type | Description | | -------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `RoleId` | `StringFilterProperty` | The role ID search filters. | | `Name` | `StringFilterProperty` | The role name search filters. | | `Or` | `List` | Defines a list of alternative filter groups combined using OR logic. Each object in the array is evaluated independently, and the filter matches if any one of them is satisfied. Top-level fields and the `$or` clause are combined using AND logic — meaning: (top-level filters) AND (any of the `$or` filters) must match. Example: `json { "name": "Admin", "$or": [ { "roleId": "role-1" }, { "roleId": "role-2" } ] } ` This matches roles that: have name equal to Admin and match either: roleId is role-1, or roleId is role-2 Note: Using complex $or conditions may impact performance, use with caution in high-volume environments. | ## RoleFilterFields Role filter request ```csharp public sealed class RoleFilterFields ``` | Property | Type | Description | | -------- | ---------------------- | ----------------------------- | | `RoleId` | `StringFilterProperty` | The role ID search filters. | | `Name` | `StringFilterProperty` | The role name search filters. | ## RoleGroupResult ```csharp public sealed class RoleGroupResult ``` | Property | Type | Description | | --------- | --------- | -------------------- | | `GroupId` | `GroupId` | The id of the group. | ## RoleGroupSearchQueryRequest ```csharp public sealed class RoleGroupSearchQueryRequest ``` | Property | Type | Description | | -------- | --------------------------------------- | -------------------- | | `Sort` | `List` | Sort field criteria. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## RoleGroupSearchQuerySortRequest ```csharp public sealed class RoleGroupSearchQuerySortRequest ``` | Property | Type | Description | | -------- | -------------------------------------- | --------------------------------------------- | | `Field` | `RoleGroupSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## RoleGroupSearchResult ```csharp public sealed class RoleGroupSearchResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching groups. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## RoleId The unique identifier of a role. ```csharp public readonly record struct RoleId : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## RoleMappingRuleSearchResult ```csharp public sealed class RoleMappingRuleSearchResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching mapping rules. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## RoleResult Role search response item. ```csharp public sealed class RoleResult ``` | Property | Type | Description | | ------------- | -------- | ---------------------------- | | `Name` | `String` | The role name. | | `RoleId` | `RoleId` | The role id. | | `Description` | `String` | The description of the role. | ## RoleSearchQueryRequest Role search request. ```csharp public sealed class RoleSearchQueryRequest ``` | Property | Type | Description | | -------- | ---------------------------------- | ------------------------ | | `Sort` | `List` | Sort field criteria. | | `Filter` | `RoleFilter` | The role search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## RoleSearchQueryResult Role search response. ```csharp public sealed class RoleSearchQueryResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching roles. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## RoleSearchQuerySortRequest ```csharp public sealed class RoleSearchQuerySortRequest ``` | Property | Type | Description | | -------- | --------------------------------- | --------------------------------------------- | | `Field` | `RoleSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## RoleUpdateRequest ```csharp public sealed class RoleUpdateRequest ``` | Property | Type | Description | | ------------- | -------- | --------------------------------- | | `Name` | `String` | The display name of the new role. | | `Description` | `String` | The description of the new role. | ## RoleUpdateResult ```csharp public sealed class RoleUpdateResult ``` | Property | Type | Description | | ------------- | -------- | ------------------------------------- | | `Name` | `String` | The display name of the updated role. | | `Description` | `String` | The description of the updated role. | | `RoleId` | `RoleId` | The ID of the updated role. | ## RoleUserFilterRequest Role user filter request ```csharp public sealed class RoleUserFilterRequest ``` | Property | Type | Description | | ---------- | ---------------------- | ---------------------------- | | `Username` | `StringFilterProperty` | The username search filters. | ## RoleUserResult ```csharp public sealed class RoleUserResult ``` | Property | Type | Description | | ---------- | ---------- | -------------------------- | | `Username` | `Username` | The unique name of a user. | ## RoleUserSearchQueryRequest ```csharp public sealed class RoleUserSearchQueryRequest ``` | Property | Type | Description | | -------- | -------------------------------------- | ------------------------- | | `Filter` | `RoleUserFilterRequest` | The user filter criteria. | | `Sort` | `List` | Sort field criteria. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## RoleUserSearchQuerySortRequest ```csharp public sealed class RoleUserSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ------------------------------------- | --------------------------------------------- | | `Field` | `RoleUserSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## RoleUserSearchResult ```csharp public sealed class RoleUserSearchResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching users. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## RuntimeBackupState Information about the checkpoint and backup state of the physical tenant. ```csharp public sealed class RuntimeBackupState ``` | Property | Type | Description | | ------------------ | -------------------------------- | ------------------------------------ | | `CheckpointStates` | `List` | List of partition checkpoint states. | | `BackupStates` | `List` | List of partition backup states. | | `Ranges` | `List` | List of partition backup ranges. | ## ScopeKeyExactMatch Matches the value exactly. ```csharp public readonly record struct ScopeKeyExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## ScopeKeyFilterProperty ScopeKey property with full advanced search capabilities. Filter by the key of the element instance or process instance that defines the scope of a variable. ```csharp public sealed class ScopeKeyFilterProperty ``` | Property | Type | Description | | ------------ | -------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## SearchQueryPageRequest Pagination criteria. Can use offset-based pagination (from/limit) OR cursor-based pagination (after/before + limit), but not both. ```csharp public abstract class SearchQueryPageRequest ``` ## SearchQueryPageResponse Pagination information about the search results. ```csharp public sealed class SearchQueryPageResponse ``` | Property | Type | Description | | ------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `TotalItems` | `Int64` | Total items matching the criteria. | | `HasMoreTotalItems` | `Boolean` | Indicates whether the `totalItems` value has been capped due to system limits. When true, `totalItems` is a lower bound and the actual number of matching items is greater than the reported value. | | `StartCursor` | `Nullable` | The cursor value for getting the previous page of results. Use this in the `before` field of an ensuing request. | | `EndCursor` | `Nullable` | The cursor value for getting the next page of results. Use this in the `after` field of an ensuing request. | ## SearchQueryRequest ```csharp public sealed class SearchQueryRequest ``` | Property | Type | Description | | -------- | ------------------------ | -------------------- | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## SearchQueryResponse ```csharp public sealed class SearchQueryResponse ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## SecretListRequest Reserved for future filtering options. Currently takes no properties. The request body is optional: omitting it (or sending an empty object) applies no filters. ```csharp public sealed class SecretListRequest ``` ## SecretListResult The secret references the caller is authorized to see. Unbounded: the response carries the configured stores' full enumeration for the physical tenant. ```csharp public sealed class SecretListResult ``` | Property | Type | Description | | ------------ | -------------- | ----------------------------------------------------------- | | `References` | `List` | The secret references, each of the form `camunda.secrets.`. | ## SecretResolutionError ```csharp public sealed class SecretResolutionError ``` | Property | Type | Description | | ----------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Reference` | `String` | The secret reference that could not be resolved. | | `Code` | `SecretErrorCode` | The typed reason a reference could not be resolved. - `NOT_FOUND`: no secret exists for the reference. - `ACCESS_DENIED`: the caller lacks `SECRET:REVEAL` on the reference. - `INVALID_REFERENCE`: the reference is malformed, or the configured store rejected it as an invalid secret identifier. - `UNREADABLE`: the configured store could not return a value for the reference, for example because it rejected the cluster's own store credentials or the stored value could not be read. Whether the secret exists is not implied. | | `Message` | `String` | A human-readable description of the failure. Never contains the secret value; only error metadata (codes, names) is included. | ## SecretResolveRequest ```csharp public sealed class SecretResolveRequest ``` | Property | Type | Description | | ------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `References` | `List` | The secret references to resolve, each of the form `camunda.secrets.`. Duplicate references are deduplicated by the server and resolved once. At most 20 references may be requested in a single batch. | ## SecretResolveResult The per-reference outcome of a resolve request. ```csharp public sealed class SecretResolveResult ``` | Property | Type | Description | | ---------- | ----------------------------- | ------------------------------------------------------------------------ | | `Resolved` | `List` | The references that were successfully resolved. | | `Errors` | `List` | The references that could not be resolved, each with a typed error code. | ## SeededRandomSource A deterministic `IRandomSource` for tests: the same seed yields the same sequence of draws on every run, and in every Camunda SDK. The generator is SplitMix64, taking the top 53 bits of each output as a double. It is specified by the cross-SDK contract (camunda/sdk-infra#50) rather than borrowed from the platform, so a seed reproduces the same jitter regardless of runtime version or language. Draws are thread-safe. Reproducibility holds for draws made in a fixed order; if several workers draw concurrently, which of them receives which value is up to the scheduler. ```csharp public sealed class SeededRandomSource : IRandomSource ``` | Property | Type | Description | | -------- | -------- | --------------------------------------------------------------------------------------- | | `Seed` | `UInt64` | The seed this source was created with. Report it on failure to make the run replayable. | ## SetVariableRequest ```csharp public sealed class SetVariableRequest ``` | Property | Type | Description | | -------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Variables` | `Object` | JSON object representing the variables to set in the element’s scope. | | `Local` | `Nullable` | If set to `true`, the variables are merged strictly into the local scope (as specified by the `elementInstanceKey`). Otherwise, the variables are propagated to upper scopes and set at the outermost one. Let's consider the following example: There are two scopes '1' and '2'. Scope '1' is the parent scope of '2'. The effective variables of the scopes are: 1 => { "foo" : 2 } 2 => { "bar" : 1 } An update request with elementInstanceKey as '2', variables { "foo": 5 }, and local set to `true` leaves scope '1' unchanged and adjusts scope '2' to { "bar": 1, "foo": 5 }. By default, with local set to `false`, scope '1' will be { "foo": 5 } and scope '2' will be { "bar": 1 }. | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | ## SignalBroadcastRequest ```csharp public sealed class SignalBroadcastRequest : ITenantIdSettable ``` | Property | Type | Description | | ------------ | -------------------- | ------------------------------------------ | | `SignalName` | `String` | The name of the signal to broadcast. | | `Variables` | `Object` | The signal variables as a JSON object. | | `TenantId` | `Nullable` | The ID of the tenant that owns the signal. | ## SignalBroadcastResult ```csharp public sealed class SignalBroadcastResult ``` | Property | Type | Description | | ----------- | ----------- | ----------------------------------------------- | | `TenantId` | `TenantId` | The tenant ID of the signal that was broadcast. | | `SignalKey` | `SignalKey` | The key of the broadcasted signal. | ## SignalWaitStateDetails ```csharp public sealed class SignalWaitStateDetails : WaitStateDetails ``` | Property | Type | Description | | ------------ | -------- | ------------------------------------- | | `SignalName` | `String` | The name of the signal being awaited. | ## SourceElementIdInstruction Defines an instruction with a sourceElementId. The move instruction with this sourceType will terminate all active element instances with the sourceElementId and activate a new element instance for each terminated one at targetElementId. ```csharp public sealed class SourceElementIdInstruction : SourceElementInstruction ``` | Property | Type | Description | | ----------------- | ----------- | ------------------------------------------------------ | | `SourceElementId` | `ElementId` | The id of the source element for the move instruction. | ## SourceElementInstanceKeyInstruction Defines an instruction with a sourceElementInstanceKey. The move instruction with this sourceType will terminate one active element instance with the sourceElementInstanceKey and activate a new element instance at targetElementId. ```csharp public sealed class SourceElementInstanceKeyInstruction : SourceElementInstruction ``` | Property | Type | Description | | -------------------------- | -------------------- | --------------------------------------------------------- | | `SourceElementInstanceKey` | `ElementInstanceKey` | The source element instance key for the move instruction. | ## SourceElementInstruction Defines the source element identifier for the move instruction. It can either be a sourceElementId, or sourceElementInstanceKey. ```csharp public abstract class SourceElementInstruction ``` ## StartCursor The start cursor in a search query result set. ```csharp public readonly record struct StartCursor : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## StatusMetric Metric for a single job status. ```csharp public sealed class StatusMetric ``` | Property | Type | Description | | --------------- | -------------------------- | ------------------------------------------------------ | | `Count` | `Int64` | Number of jobs in this status. | | `LastUpdatedAt` | `Nullable` | ISO 8601 timestamp of the last update for this status. | ## StopResult Result of a `JobWorker.StopAsync` call. ```csharp public readonly record struct StopResult : IEquatable ``` | Property | Type | Description | | --------------- | --------- | ------------------------------------------------------------- | | `RemainingJobs` | `Int32` | Number of jobs still in-flight when stop completed. | | `TimedOut` | `Boolean` | Whether the grace period was exceeded with jobs still active. | ## StringFilterProperty String property with full advanced search capabilities. ```csharp public sealed class StringFilterProperty ``` | Property | Type | Description | | ------------ | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `String` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `String` | Checks for equality with the provided value. | | `Neq` | `String` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## SuspendProcessInstanceRequest ```csharp public sealed class SuspendProcessInstanceRequest ``` | Property | Type | Description | | -------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | | `OperationReference` | `Nullable` | A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. | ## SystemConfigurationResponse Envelope for all system configuration sections. Each property represents a feature area. ```csharp public sealed class SystemConfigurationResponse ``` | Property | Type | Description | | ---------------- | ------------------------------------- | -------------------------------------------------------------- | | `JobMetrics` | `JobMetricsConfigurationResponse` | Configuration for job metrics collection and export. | | `Components` | `ComponentsConfigurationResponse` | Configuration for active Camunda components in the deployment. | | `Deployment` | `DeploymentConfigurationResponse` | Configuration for deployment characteristics. | | `Authentication` | `AuthenticationConfigurationResponse` | Configuration for authentication and session management. | | `Cloud` | `CloudConfigurationResponse` | Configuration for SaaS/cloud-specific settings. | ## Tag A tag. Needs to start with a letter; then alphanumerics, `_`, `-`, `:`, or `.`; length ≤ 100. ```csharp public readonly record struct Tag : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## TakeHistoryBackupRequest Request body for taking a history backup. ```csharp public sealed class TakeHistoryBackupRequest ``` | Property | Type | Description | | ---------- | ---------- | ----------------------------- | | `BackupId` | `BackupId` | The id of the backup to take. | ## TakeHistoryBackupResponse Response body for taking a history backup. ```csharp public sealed class TakeHistoryBackupResponse ``` | Property | Type | Description | | -------------------- | -------------- | -------------------------------------------------------------------- | | `BackupId` | `BackupId` | The id of the backup that has been scheduled. | | `ScheduledSnapshots` | `List` | The names of the snapshots that have been scheduled for this backup. | ## TakeRuntimeBackupRequest Request body for taking a runtime backup. ```csharp public sealed class TakeRuntimeBackupRequest ``` | Property | Type | Description | | ---------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `BackupId` | `Nullable` | The id of the backup to take. Must be omitted if continuous backups and/or a backup or checkpoint schedule is enabled for the physical tenant. | ## TakeRuntimeBackupResponse Response body for taking a runtime backup. ```csharp public sealed class TakeRuntimeBackupResponse ``` | Property | Type | Description | | ---------- | ---------- | --------------------------------------------- | | `BackupId` | `BackupId` | The id of the backup that has been scheduled. | ## TenantClientResult ```csharp public sealed class TenantClientResult ``` | Property | Type | Description | | ---------- | ---------- | --------------------- | | `ClientId` | `ClientId` | The ID of the client. | ## TenantClientSearchQueryRequest ```csharp public sealed class TenantClientSearchQueryRequest ``` | Property | Type | Description | | -------- | ------------------------------------------ | -------------------- | | `Sort` | `List` | Sort field criteria. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## TenantClientSearchQuerySortRequest ```csharp public sealed class TenantClientSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ----------------------------------------- | --------------------------------------------- | | `Field` | `TenantClientSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## TenantClientSearchResult ```csharp public sealed class TenantClientSearchResult ``` | Property | Type | Description | | -------- | -------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching clients. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## TenantCreateRequest ```csharp public sealed class TenantCreateRequest ``` | Property | Type | Description | | ------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `TenantId` | `TenantId` | The unique ID for the tenant. Must be 31 characters or less and match `^[\w.-]{1,31}$` (word characters, `.`, `-`). The literal `` is also accepted as the default-tenant alias. | | `Name` | `String` | The name of the tenant. | | `Description` | `String` | The description of the tenant. | ## TenantCreateResult ```csharp public sealed class TenantCreateResult ``` | Property | Type | Description | | ------------- | ---------- | -------------------------------------------- | | `TenantId` | `TenantId` | The unique identifier of the created tenant. | | `Name` | `String` | The name of the tenant. | | `Description` | `String` | The description of the tenant. | ## TenantFilter Tenant filter request ```csharp public sealed class TenantFilter ``` | Property | Type | Description | | ---------- | -------------------- | ------------------------------------ | | `TenantId` | `Nullable` | The unique identifier of the tenant. | | `Name` | `String` | The name of the tenant. | ## TenantGroupResult ```csharp public sealed class TenantGroupResult ``` | Property | Type | Description | | --------- | --------- | ------------- | | `GroupId` | `GroupId` | The group ID. | ## TenantGroupSearchQueryRequest ```csharp public sealed class TenantGroupSearchQueryRequest ``` | Property | Type | Description | | -------- | ----------------------------------------- | -------------------- | | `Sort` | `List` | Sort field criteria. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## TenantGroupSearchQuerySortRequest ```csharp public sealed class TenantGroupSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ---------------------------------------- | --------------------------------------------- | | `Field` | `TenantGroupSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## TenantGroupSearchResult ```csharp public sealed class TenantGroupSearchResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching groups. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## TenantId The unique identifier of the tenant. ```csharp public readonly record struct TenantId : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## TenantMappingRuleSearchResult ```csharp public sealed class TenantMappingRuleSearchResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching mapping rules. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## TenantResult Tenant search response item. ```csharp public sealed class TenantResult ``` | Property | Type | Description | | ------------- | ---------- | ------------------------------------ | | `Name` | `String` | The tenant name. | | `TenantId` | `TenantId` | The unique identifier of the tenant. | | `Description` | `String` | The tenant description. | ## TenantRoleSearchResult ```csharp public sealed class TenantRoleSearchResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching roles. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## TenantSearchQueryRequest Tenant search request ```csharp public sealed class TenantSearchQueryRequest ``` | Property | Type | Description | | -------- | ------------------------------------ | -------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `TenantFilter` | The tenant search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## TenantSearchQueryResult Tenant search response. ```csharp public sealed class TenantSearchQueryResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching tenants. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## TenantSearchQuerySortRequest ```csharp public sealed class TenantSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ----------------------------------- | ------------------------------------------------------------------------- | | `Field` | `TenantSearchQuerySortRequestField` | The field to sort by. `key` is deprecated and should not be used anymore. | | `Order` | `Nullable` | The order in which to sort the related field. | ## TenantUpdateRequest ```csharp public sealed class TenantUpdateRequest ``` | Property | Type | Description | | ------------- | -------- | ---------------------------------- | | `Name` | `String` | The new name of the tenant. | | `Description` | `String` | The new description of the tenant. | ## TenantUpdateResult ```csharp public sealed class TenantUpdateResult ``` | Property | Type | Description | | ------------- | ---------- | -------------------------------------------- | | `TenantId` | `TenantId` | The unique identifier of the updated tenant. | | `Name` | `String` | The name of the tenant. | | `Description` | `String` | The description of the tenant. | ## TenantUserResult ```csharp public sealed class TenantUserResult ``` | Property | Type | Description | | ---------- | ---------- | -------------------------- | | `Username` | `Username` | The unique name of a user. | ## TenantUserSearchQueryRequest ```csharp public sealed class TenantUserSearchQueryRequest ``` | Property | Type | Description | | -------- | ---------------------------------------- | -------------------- | | `Sort` | `List` | Sort field criteria. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## TenantUserSearchQuerySortRequest ```csharp public sealed class TenantUserSearchQuerySortRequest ``` | Property | Type | Description | | -------- | --------------------------------------- | --------------------------------------------- | | `Field` | `TenantUserSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## TenantUserSearchResult ```csharp public sealed class TenantUserSearchResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching users. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## TimerWaitStateDetails ```csharp public sealed class TimerWaitStateDetails : WaitStateDetails ``` | Property | Type | Description | | ------------- | ----------------- | --------------------------------------------------------------------------------- | | `DueDate` | `Nullable` | When the timer is due, as a UNIX epoch timestamp in milliseconds. | | `Repetitions` | `Nullable` | The number of remaining timer repetitions (-1 for infinite, 0 for non-repeating). | ## TlsConfig TLS / mTLS configuration for custom certificates. ```csharp public sealed class TlsConfig ``` | Property | Type | Description | | --------------- | -------- | --------------------------------------------------- | | `Cert` | `String` | Inline PEM client certificate (overrides CertPath). | | `CertPath` | `String` | Path to PEM client certificate file. | | `Key` | `String` | Inline PEM client private key (overrides KeyPath). | | `KeyPath` | `String` | Path to PEM client private key file. | | `Ca` | `String` | Inline PEM CA bundle (overrides CaPath). | | `CaPath` | `String` | Path to PEM CA certificate bundle file. | | `KeyPassphrase` | `String` | Passphrase for an encrypted private key. | ## TopologyResponse The response of a topology request. ```csharp public sealed class TopologyResponse ``` | Property | Type | Description | | ----------------------- | ------------------ | ------------------------------------------------------- | | `Brokers` | `List` | A list of brokers that are part of this cluster. | | `ClusterId` | `String` | The cluster Id. | | `ClusterSize` | `Int32` | The number of brokers in the cluster. | | `PartitionsCount` | `Int32` | The number of partitions are spread across the cluster. | | `ReplicationFactor` | `Int32` | The configured replication factor for this cluster. | | `GatewayVersion` | `String` | The version of the Zeebe Gateway. | | `LastCompletedChangeId` | `String` | ID of the last completed change | ## TypedVariables Extension methods for deserializing Camunda variable and custom header payloads from untyped `object` properties into strongly-typed DTOs. Camunda API responses return `variables` and `customHeaders` as `object` properties which, at runtime, are `Json.JsonElement` values. These extensions let you opt in to typed deserialization: ```csharp // Define your domain DTO public record OrderVars(string OrderId, decimal Amount); // Deserialize variables from a process instance result var result = await client.CreateProcessInstanceAsync( new ProcessInstanceCreationInstructionById { ProcessDefinitionId = ProcessDefinitionId.AssumeExists("order-process"), Variables = new OrderVars("ord-123", 99.99m), // input: just assign your DTO }); var vars = result.Variables.DeserializeAs(); // output: typed extraction ``` For input (sending variables), simply assign your DTO to the `Variables` property — `System.Text.Json` serializes the runtime type automatically. For output (receiving variables), call `TypedVariables.DeserializeAs` on the `Variables` or `CustomHeaders` property to deserialize the underlying `Json.JsonElement` into your DTO type. ```csharp public static class TypedVariables ``` ## TypedVariablesException Base class for all errors raised by the DTO-driven typed variable map feature (`CamundaClient.SearchVariablesAsDtoAsync`). ```csharp public class TypedVariablesException : Exception, ISerializable ``` ## UpdateClusterVariableRequest ```csharp public sealed class UpdateClusterVariableRequest ``` | Property | Type | Description | | ---------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Value` | `Object` | The new value of the cluster variable. Can be any JSON object or primitive value. Will be serialized as a JSON string in responses. | | `Metadata` | `Dictionary` | A generic key-value metadata bag attached to the cluster variable. Values must be strings or numbers. Limited to 100 entries and a configurable maximum serialized size (default: 100 entries at max key length of a cluster variable name (256 chars) plus the maximum value length, 8192 characters). | ## UpdateGlobalTaskListenerRequest ```csharp public sealed class UpdateGlobalTaskListenerRequest ``` | Property | Type | Description | | ---------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | `EventTypes` | `List` | List of user task event types that trigger the listener. | | `Type` | `String` | The name of the job type, used as a reference to specify which job workers request the respective listener job. | | `Retries` | `Nullable` | Number of retries for the listener job. | | `AfterNonGlobal` | `Nullable` | Whether the listener should run after model-level listeners. | | `Priority` | `Nullable` | The priority of the listener. Higher priority listeners are executed before lower priority ones. | ## UsageMetricsResponse ```csharp public sealed class UsageMetricsResponse ``` | Property | Type | Description | | ------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------- | | `ActiveTenants` | `Int64` | The amount of active tenants. | | `Tenants` | `Dictionary` | The usage metrics by tenants. Only available if request `withTenants` query parameter was `true`. | | `ProcessInstances` | `Int64` | The amount of created root process instances. | | `DecisionInstances` | `Int64` | The amount of executed decision instances. | | `Assignees` | `Int64` | The amount of unique active task users. | ## UsageMetricsResponseItem ```csharp public sealed class UsageMetricsResponseItem ``` | Property | Type | Description | | ------------------- | ------- | --------------------------------------------- | | `ProcessInstances` | `Int64` | The amount of created root process instances. | | `DecisionInstances` | `Int64` | The amount of executed decision instances. | | `Assignees` | `Int64` | The amount of unique active task users. | ## UseSourceParentKeyInstruction Instructs the engine to use the source's direct parent key as the ancestor scope key for the target element. This is a simpler alternative to `inferred` that skips hierarchy traversal and directly uses the source's parent key. This is useful when the source and target elements are siblings within the same flow scope. ```csharp public sealed class UseSourceParentKeyInstruction : AncestorScopeInstruction ``` ## UserCreateResult ```csharp public sealed class UserCreateResult ``` | Property | Type | Description | | ---------- | ---------- | --------------------------------- | | `Username` | `Username` | The username of the created user. | | `Name` | `String` | The name of the user. | | `Email` | `String` | The email of the user. | ## UserFilter User search filter. ```csharp public sealed class UserFilter ``` | Property | Type | Description | | ---------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Username` | `StringFilterProperty` | The username of the user. | | `Name` | `StringFilterProperty` | The name of the user. | | `Email` | `StringFilterProperty` | The email of the user. | | `Or` | `List` | Defines a list of alternative filter groups combined using OR logic. Each object in the array is evaluated independently, and the filter matches if any one of them is satisfied. Top-level fields and the `$or` clause are combined using AND logic — meaning: (top-level filters) AND (any of the `$or` filters) must match. Example: `json { "$or": [ { "username": "user-1" }, { "username": "user-2" } ] } ` This matches users whose username is user-1 or user-2. Note: Using complex $or conditions may impact performance, use with caution in high-volume environments. | ## UserFilterFields User search filter fields. ```csharp public sealed class UserFilterFields ``` | Property | Type | Description | | ---------- | ---------------------- | ------------------------- | | `Username` | `StringFilterProperty` | The username of the user. | | `Name` | `StringFilterProperty` | The name of the user. | | `Email` | `StringFilterProperty` | The email of the user. | ## UserRequest ```csharp public sealed class UserRequest ``` | Property | Type | Description | | ---------- | ---------- | ----------------------------- | | `Password` | `String` | The password of the user. | | `Username` | `Username` | The username of the new user. | | `Name` | `String` | The name of the user. | | `Email` | `String` | The email of the user. | ## UserResult ```csharp public sealed class UserResult ``` | Property | Type | Description | | ---------- | ---------- | ------------------------- | | `Username` | `Username` | The username of the user. | | `Name` | `String` | The name of the user. | | `Email` | `String` | The email of the user. | ## UserSearchQueryRequest ```csharp public sealed class UserSearchQueryRequest ``` | Property | Type | Description | | -------- | ---------------------------------- | ------------------------ | | `Sort` | `List` | Sort field criteria. | | `Filter` | `UserFilter` | The user search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## UserSearchQuerySortRequest ```csharp public sealed class UserSearchQuerySortRequest ``` | Property | Type | Description | | -------- | --------------------------------- | --------------------------------------------- | | `Field` | `UserSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## UserSearchResult ```csharp public sealed class UserSearchResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching users. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## UserTaskAssignmentRequest ```csharp public sealed class UserTaskAssignmentRequest ``` | Property | Type | Description | | --------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Assignee` | `String` | The assignee for the user task. The assignee must not be empty or `null`. | | `AllowOverride` | `Nullable` | By default, the task is reassigned if it was already assigned. Set this to `false` to return an error in such cases. The task must then first be unassigned to be assigned again. Use this when you have users picking from group task queues to prevent race conditions. | | `Action` | `String` | A custom action value that will be accessible from user task events resulting from this endpoint invocation. If not provided, it will default to "assign". | ## UserTaskAuditLogFilter The user task audit log search filters. ```csharp public sealed class UserTaskAuditLogFilter ``` | Property | Type | Description | | --------------- | --------------------------------- | ------------------------------------------- | | `OperationType` | `OperationTypeFilterProperty` | The audit log operation type search filter. | | `Result` | `AuditLogResultFilterProperty` | The audit log result search filter. | | `Timestamp` | `DateTimeFilterProperty` | The audit log timestamp filter. | | `ActorType` | `AuditLogActorTypeFilterProperty` | The actor type search filter. | | `ActorId` | `StringFilterProperty` | The actor ID search filter. | ## UserTaskAuditLogSearchQueryRequest User task search query request. ```csharp public sealed class UserTaskAuditLogSearchQueryRequest ``` | Property | Type | Description | | -------- | -------------------------------------- | --------------------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `UserTaskAuditLogFilter` | The user task audit log search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## UserTaskCompletionRequest ```csharp public sealed class UserTaskCompletionRequest ``` | Property | Type | Description | | ----------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Variables` | `Object` | The variables to complete the user task with. | | `Action` | `String` | A custom action value that will be accessible from user task events resulting from this endpoint invocation. If not provided, it will default to "complete". | ## UserTaskEffectiveVariableSearchQueryRequest User task effective variable search query request. Uses offset-based pagination only. ```csharp public sealed class UserTaskEffectiveVariableSearchQueryRequest ``` | Property | Type | Description | | -------- | ---------------------------------------------- | -------------------------------------- | | `Page` | `OffsetPagination` | Pagination parameters. | | `Sort` | `List` | Sort field criteria. | | `Filter` | `UserTaskVariableFilter` | The user task variable search filters. | ## UserTaskFilter User task filter request. ```csharp public sealed class UserTaskFilter ``` | Property | Type | Description | | -------------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `State` | `UserTaskStateFilterProperty` | The user task state. | | `IsSuspended` | `Nullable` | Whether the user task is suspended because its owning process instance is suspended. | | `Assignee` | `StringFilterProperty` | The assignee of the user task. | | `BusinessId` | `StringFilterProperty` | The business ID of the owning process instance the user task belongs to. This only works for user tasks created with 8.10 and onwards. Tasks from prior versions don't contain this data and cannot be found. | | `Priority` | `IntegerFilterProperty` | The priority of the user task. | | `ElementId` | `Nullable` | The element ID of the user task. | | `Name` | `StringFilterProperty` | The task name. This only works for data created with 8.8 and onwards. Instances from prior versions don't contain this data and cannot be found. | | `CandidateGroup` | `StringFilterProperty` | The candidate group for this user task. | | `CandidateUser` | `StringFilterProperty` | The candidate user for this user task. | | `TenantId` | `StringFilterProperty` | Tenant ID of this user task. | | `ProcessDefinitionId` | `ProcessDefinitionIdFilterProperty` | The ID of the process definition. | | `CreationDate` | `DateTimeFilterProperty` | The user task creation date. | | `CompletionDate` | `DateTimeFilterProperty` | The user task completion date. | | `FollowUpDate` | `DateTimeFilterProperty` | The user task follow-up date. | | `DueDate` | `DateTimeFilterProperty` | The user task due date. | | `ProcessInstanceVariables` | `List` | The variables of the process instance. | | `LocalVariables` | `List` | The local variables of the user task. | | `UserTaskKey` | `Nullable` | The key for this user task. | | `ProcessDefinitionKey` | `ProcessDefinitionKeyFilterProperty` | The key of the process definition. | | `ProcessInstanceKey` | `ProcessInstanceKeyFilterProperty` | The key of the process instance. | | `ElementInstanceKey` | `Nullable` | The key of the element instance. | | `Tags` | `List` | List of tags. Tags need to start with a letter; then alphanumerics, `_`, `-`, `:`, or `.`; length ≤ 100. | | `Or` | `List` | Defines a list of alternative filter groups combined using OR logic. Each object in the array is evaluated independently, and the filter matches if any one of them is satisfied. Top-level fields and the `$or` clause are combined using AND logic — meaning: (top-level filters) AND (any of the `$or` filters) must match. Example: `json { "assignee": "user1", "$or": [ { "candidateGroup": "groupA" }, { "candidateUser": "user2" } ] } ` This matches user tasks that: are assigned to user1 and match either: candidateGroup is groupA, or candidateUser is user2 Note: Using complex $or conditions may impact performance, use with caution in high-volume environments. | ## UserTaskFilterFields User task filter fields. ```csharp public sealed class UserTaskFilterFields ``` | Property | Type | Description | | -------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `State` | `UserTaskStateFilterProperty` | The user task state. | | `IsSuspended` | `Nullable` | Whether the user task is suspended because its owning process instance is suspended. | | `Assignee` | `StringFilterProperty` | The assignee of the user task. | | `BusinessId` | `StringFilterProperty` | The business ID of the owning process instance the user task belongs to. This only works for user tasks created with 8.10 and onwards. Tasks from prior versions don't contain this data and cannot be found. | | `Priority` | `IntegerFilterProperty` | The priority of the user task. | | `ElementId` | `Nullable` | The element ID of the user task. | | `Name` | `StringFilterProperty` | The task name. This only works for data created with 8.8 and onwards. Instances from prior versions don't contain this data and cannot be found. | | `CandidateGroup` | `StringFilterProperty` | The candidate group for this user task. | | `CandidateUser` | `StringFilterProperty` | The candidate user for this user task. | | `TenantId` | `StringFilterProperty` | Tenant ID of this user task. | | `ProcessDefinitionId` | `ProcessDefinitionIdFilterProperty` | The ID of the process definition. | | `CreationDate` | `DateTimeFilterProperty` | The user task creation date. | | `CompletionDate` | `DateTimeFilterProperty` | The user task completion date. | | `FollowUpDate` | `DateTimeFilterProperty` | The user task follow-up date. | | `DueDate` | `DateTimeFilterProperty` | The user task due date. | | `ProcessInstanceVariables` | `List` | The variables of the process instance. | | `LocalVariables` | `List` | The local variables of the user task. | | `UserTaskKey` | `Nullable` | The key for this user task. | | `ProcessDefinitionKey` | `ProcessDefinitionKeyFilterProperty` | The key of the process definition. | | `ProcessInstanceKey` | `ProcessInstanceKeyFilterProperty` | The key of the process instance. | | `ElementInstanceKey` | `Nullable` | The key of the element instance. | | `Tags` | `List` | List of tags. Tags need to start with a letter; then alphanumerics, `_`, `-`, `:`, or `.`; length ≤ 100. | ## UserTaskProperties Contains properties of a user task. ```csharp public sealed class UserTaskProperties ``` | Property | Type | Description | | ------------------- | ----------------------- | ------------------------------------------------------- | | `Action` | `String` | The action performed on the user task. | | `Assignee` | `String` | The user assigned to the task. | | `CandidateGroups` | `List` | The groups eligible to claim the task. | | `CandidateUsers` | `List` | The users eligible to claim the task. | | `ChangedAttributes` | `List` | The attributes that were changed in the task. | | `DueDate` | `String` | The due date of the user task in ISO 8601 format. | | `FollowUpDate` | `String` | The follow-up date of the user task in ISO 8601 format. | | `FormKey` | `Nullable` | The key of the form associated with the user task. | | `Priority` | `Nullable` | The priority of the user task. | | `UserTaskKey` | `Nullable` | The unique key identifying the user task. | ## UserTaskResult ```csharp public sealed class UserTaskResult ``` | Property | Type | Description | | -------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Name` | `String` | The name for this user task. | | `State` | `UserTaskStateEnum` | The state of the user task. Note: FAILED state is only for legacy job-worker-based tasks. | | `Assignee` | `String` | The assignee of the user task. | | `ElementId` | `ElementId` | The element ID of the user task. | | `CandidateGroups` | `List` | The candidate groups for this user task. | | `CandidateUsers` | `List` | The candidate users for this user task. | | `ProcessDefinitionId` | `ProcessDefinitionId` | The ID of the process definition. | | `CreationDate` | `DateTimeOffset` | The creation date of a user task. | | `CompletionDate` | `Nullable` | The completion date of a user task. | | `FollowUpDate` | `Nullable` | The follow date of a user task. | | `DueDate` | `Nullable` | The due date of a user task. | | `TenantId` | `TenantId` | The unique identifier of the tenant. | | `ExternalFormReference` | `String` | The external form reference. | | `ProcessDefinitionVersion` | `Int32` | The version of the process definition. | | `CustomHeaders` | `Dictionary` | Custom headers for the user task. | | `Priority` | `Int32` | The priority of a user task. The higher the value the higher the priority. | | `IsSuspended` | `Boolean` | Whether the user task is suspended because its owning process instance is suspended. | | `UserTaskKey` | `UserTaskKey` | The key of the user task. | | `ElementInstanceKey` | `ElementInstanceKey` | The key of the element instance. | | `ProcessName` | `String` | The name of the process definition. This is `null` if the process has no name defined. | | `ProcessDefinitionKey` | `ProcessDefinitionKey` | The key of the process definition. | | `ProcessInstanceKey` | `ProcessInstanceKey` | The key of the process instance. | | `RootProcessInstanceKey` | `Nullable` | The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. | | `BusinessId` | `Nullable` | The business ID of the owning process instance, inherited when the user task was created. This is `null` for user tasks created before version 8.10, and for user tasks whose owning process instance has no business ID. | | `FormKey` | `Nullable` | The key of the form. | | `Tags` | `List` | List of tags. Tags need to start with a letter; then alphanumerics, `_`, `-`, `:`, or `.`; length ≤ 100. | ## UserTaskSearchQuery User task search query request. ```csharp public sealed class UserTaskSearchQuery ``` | Property | Type | Description | | -------- | -------------------------------------- | ----------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `UserTaskFilter` | The user task search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## UserTaskSearchQueryResult User task search query response. ```csharp public sealed class UserTaskSearchQueryResult ``` | Property | Type | Description | | -------- | ------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching user tasks. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## UserTaskSearchQuerySortRequest ```csharp public sealed class UserTaskSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ------------------------------------- | --------------------------------------------- | | `Field` | `UserTaskSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## UserTaskStateExactMatch Matches the value exactly. ```csharp public readonly record struct UserTaskStateExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## UserTaskStateFilterProperty UserTaskStateEnum property with full advanced search capabilities. ```csharp public sealed class UserTaskStateFilterProperty ``` | Property | Type | Description | | ------------ | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## UserTaskUpdateRequest ```csharp public sealed class UserTaskUpdateRequest ``` | Property | Type | Description | | ----------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Changeset` | `Changeset` | JSON object with changed task attribute values. The following attributes can be adjusted with this endpoint, additional attributes will be ignored: * `candidateGroups` - reset by providing an empty list * `candidateUsers` - reset by providing an empty list * `dueDate` - reset by providing an empty String * `followUpDate` - reset by providing an empty String * `priority` - minimum 0, maximum 100, default 50 Providing any of those attributes with a `null` value or omitting it preserves the persisted attribute's value. The assignee cannot be adjusted with this endpoint, use the Assign task endpoint. This ensures correct event emission for assignee changes. | | `Action` | `String` | A custom action value that will be accessible from user task events resulting from this endpoint invocation. If not provided, it will default to "update". | ## UserTaskVariableFilter The user task variable search filters. ```csharp public sealed class UserTaskVariableFilter ``` | Property | Type | Description | | -------- | ---------------------- | --------------------- | | `Name` | `StringFilterProperty` | Name of the variable. | ## UserTaskVariableSearchQueryRequest User task search query request. ```csharp public sealed class UserTaskVariableSearchQueryRequest ``` | Property | Type | Description | | -------- | ---------------------------------------------- | -------------------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `UserTaskVariableFilter` | The user task variable search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## UserTaskVariableSearchQuerySortRequest ```csharp public sealed class UserTaskVariableSearchQuerySortRequest ``` | Property | Type | Description | | -------- | --------------------------------------------- | --------------------------------------------- | | `Field` | `UserTaskVariableSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## UserTaskWaitStateDetails ```csharp public sealed class UserTaskWaitStateDetails : WaitStateDetails ``` | Property | Type | Description | | --------- | -------------------------- | -------------------------------------- | | `TaskKey` | `UserTaskKey` | The key of the user task. | | `DueDate` | `Nullable` | The due date of the user task, if set. | ## UserUpdateRequest ```csharp public sealed class UserUpdateRequest ``` | Property | Type | Description | | ---------- | -------- | -------------------------------------------------------------- | | `Password` | `String` | The password of the user. If blank, the password is unchanged. | | `Name` | `String` | The name of the user. | | `Email` | `String` | The email of the user. | ## UserUpdateResult ```csharp public sealed class UserUpdateResult ``` | Property | Type | Description | | ---------- | ---------- | --------------------------------- | | `Username` | `Username` | The username of the updated user. | | `Name` | `String` | The name of the user. | | `Email` | `String` | The email of the user. | ## Username The unique name of a user. ```csharp public readonly record struct Username : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## VariableDeserializationException Raised when a present variable value cannot be deserialized. This covers both a value that is not parseable as JSON and a syntactically valid value that cannot be bound to the requested CLR type. A missing variable is not an error (it simply does not appear in the map); a present but undeserializable value is, and is surfaced here rather than silently dropped. ```csharp public sealed class VariableDeserializationException : TypedVariablesException, ISerializable ``` | Property | Type | Description | | -------------- | -------- | -------------------------------------------------- | | `VariableName` | `String` | The variable name whose value could not be parsed. | ## VariableFilter Variable filter request. ```csharp public sealed class VariableFilter ``` | Property | Type | Description | | -------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Name` | `StringFilterProperty` | Name of the variable. | | `Value` | `StringFilterProperty` | The value of the variable. Variable values in filters need to be in serialized JSON format. For example, a variable with string value `myValue` can be found with the filter value `"myValue"`. Consider appropriate escaping for special characters in JSON strings when constructing filter values. | | `TenantId` | `Nullable` | Tenant ID of this variable. | | `IsTruncated` | `Nullable` | Whether the value is truncated or not. | | `VariableKey` | `VariableKeyFilterProperty` | The key for this variable. | | `ScopeKey` | `ScopeKeyFilterProperty` | The key of the scope that defines where this variable is directly defined. This can be a process instance key (for process-level variables) or an element instance key (for local variables scoped to tasks, subprocesses, gateways, events, etc.). Use this filter to find variables directly defined in specific scopes. Note that this does not include variables from parent scopes that would be visible through the scope hierarchy. | | `ProcessInstanceKey` | `ProcessInstanceKeyFilterProperty` | The key of the process instance of this variable. | ## VariableKeyExactMatch Matches the value exactly. ```csharp public readonly record struct VariableKeyExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## VariableKeyFilterProperty VariableKey property with full advanced search capabilities. ```csharp public sealed class VariableKeyFilterProperty ``` | Property | Type | Description | | ------------ | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `NotIn` | `List` | Checks if the property matches none of the provided values. | ## VariableMap Result of a DTO-driven variable search (`CamundaClient.SearchVariablesAsDtoAsync`). Holds the parsed variable values keyed by their query name (the DTO member's `[JsonPropertyName]` value, or the member name transformed by the serializer's naming policy). Provides lenient, defensive access via `VariableMap.Get` / `VariableMap.Get` and a strict `VariableMap.Validate` that constructs the declared DTO and enforces required members. ```csharp public sealed class VariableMap where T : class ``` | Property | Type | Description | | -------- | ------------------------------------------ | --------------------------------------------------- | | `Raw` | `IReadOnlyDictionary` | The parsed variable values, keyed by variable name. | ## VariableResult Variable search response item. ```csharp public sealed class VariableResult ``` | Property | Type | Description | | ------------------------ | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Value` | `String` | Full value of this variable. | | `Name` | `String` | Name of this variable. | | `TenantId` | `TenantId` | Tenant ID of this variable. | | `VariableKey` | `VariableKey` | The key for this variable. | | `ScopeKey` | `ScopeKey` | The key of the scope where this variable is directly defined. For process-level variables, this is the process instance key. For local variables, this is the key of the specific element instance (task, subprocess, gateway, event, etc.) where the variable is directly defined. | | `ProcessInstanceKey` | `ProcessInstanceKey` | The key of the process instance of this variable. | | `RootProcessInstanceKey` | `Nullable` | The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. | ## VariableResultBase Variable response item. ```csharp public sealed class VariableResultBase ``` | Property | Type | Description | | ------------------------ | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Name` | `String` | Name of this variable. | | `TenantId` | `TenantId` | Tenant ID of this variable. | | `VariableKey` | `VariableKey` | The key for this variable. | | `ScopeKey` | `ScopeKey` | The key of the scope where this variable is directly defined. For process-level variables, this is the process instance key. For local variables, this is the key of the specific element instance (task, subprocess, gateway, event, etc.) where the variable is directly defined. | | `ProcessInstanceKey` | `ProcessInstanceKey` | The key of the process instance of this variable. | | `RootProcessInstanceKey` | `Nullable` | The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. | ## VariableScopeCollisionException Raised when a declared variable name is returned at more than one scope. The DTO is a flat name-to-value map, but BPMN variables are scoped (process-level vs. local element scopes). When a declared variable resolves to multiple scopes the SDK cannot deterministically choose one, so it raises rather than guessing. Pass `scopeKey` to the search call to disambiguate. ```csharp public sealed class VariableScopeCollisionException : TypedVariablesException, ISerializable ``` | Property | Type | Description | | -------------- | ----------------------- | ----------------------------------------------------------------------- | | `VariableName` | `String` | The variable name that was found at multiple scopes. | | `ScopeKeys` | `IReadOnlyList` | The distinct scope keys the variable was observed at, sorted ascending. | ## VariableSearchQuery Variable search query request. ```csharp public sealed class VariableSearchQuery ``` | Property | Type | Description | | -------- | -------------------------------------- | ---------------------------- | | `Sort` | `List` | Sort field criteria. | | `Filter` | `VariableFilter` | The variable search filters. | | `Page` | `SearchQueryPageRequest` | Pagination criteria. | ## VariableSearchQueryResult Variable search query response. ```csharp public sealed class VariableSearchQueryResult ``` | Property | Type | Description | | -------- | ---------------------------- | ------------------------------------------------ | | `Items` | `List` | The matching variables. | | `Page` | `SearchQueryPageResponse` | Pagination information about the search results. | ## VariableSearchQuerySortRequest ```csharp public sealed class VariableSearchQuerySortRequest ``` | Property | Type | Description | | -------- | ------------------------------------- | --------------------------------------------- | | `Field` | `VariableSearchQuerySortRequestField` | The field to sort by. | | `Order` | `Nullable` | The order in which to sort the related field. | ## VariableSearchResult Variable search response item. ```csharp public sealed class VariableSearchResult ``` | Property | Type | Description | | ------------------------ | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Value` | `String` | Value of this variable. Can be truncated. | | `IsTruncated` | `Boolean` | Whether the value is truncated or not. | | `Name` | `String` | Name of this variable. | | `TenantId` | `TenantId` | Tenant ID of this variable. | | `VariableKey` | `VariableKey` | The key for this variable. | | `ScopeKey` | `ScopeKey` | The key of the scope where this variable is directly defined. For process-level variables, this is the process instance key. For local variables, this is the key of the specific element instance (task, subprocess, gateway, event, etc.) where the variable is directly defined. | | `ProcessInstanceKey` | `ProcessInstanceKey` | The key of the process instance of this variable. | | `RootProcessInstanceKey` | `Nullable` | The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. | ## VariableValidationException Raised by `VariableMap.Validate` when one or more required DTO members (non-nullable members, or members marked with the `required` modifier) are absent from the search result. ```csharp public sealed class VariableValidationException : TypedVariablesException, ISerializable ``` | Property | Type | Description | | ---------------------- | ----------------------- | ------------------------------------------------------------- | | `DtoType` | `Type` | The DTO type that failed validation. | | `MissingVariableNames` | `IReadOnlyList` | The variable names of the required members that were missing. | ## VariableValueFilterProperty ```csharp public sealed class VariableValueFilterProperty ``` | Property | Type | Description | | -------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Name` | `String` | Name of the variable. | | `Value` | `StringFilterProperty` | The value of the variable. Variable values in filters need to be in serialized JSON format. For example, a variable with string value `myValue` can be found with the filter value `"myValue"`. Consider appropriate escaping for special characters in JSON strings when constructing filter values. | ## WaitStateDetails Wait-state-specific details of an element instance. ```csharp public abstract class WaitStateDetails ``` ## WaitStateElementTypeExactMatch Matches the value exactly. ```csharp public readonly record struct WaitStateElementTypeExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## WaitStateElementTypeFilterProperty Element type property with full advanced search capabilities. ```csharp public sealed class WaitStateElementTypeFilterProperty ``` | Property | Type | Description | | ------------ | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## WaitStateTypeExactMatch Matches the value exactly. ```csharp public readonly record struct WaitStateTypeExactMatch : ICamundaKey, IEquatable ``` | Property | Type | Description | | -------- | -------- | ---------------------------- | | `Value` | `String` | The underlying string value. | ## WaitStateTypeFilterProperty Wait state type property with full advanced search capabilities. ```csharp public sealed class WaitStateTypeFilterProperty ``` | Property | Type | Description | | ------------ | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ExactMatch` | `Nullable` | Matches the value exactly. Serialized as the bare value — the form servers that predate advanced filtering on this field accept. | | `Eq` | `Nullable` | Checks for equality with the provided value. | | `Neq` | `Nullable` | Checks for inequality with the provided value. | | `Exists` | `Nullable` | Checks if the current property exists. | | `In` | `List` | Checks if the property matches any of the provided values. | | `Like` | `Nullable` | Checks if the property matches the provided like value. Supported wildcard characters are: * `*`: matches zero, one, or multiple characters. * `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. | ## WorkerDefaultsConfig ```csharp public sealed class WorkerDefaultsConfig ``` | Property | Type | Description | | ------------------------- | ------------------ | ----------- | | `JobTimeoutMs` | `Nullable` | | | `MaxConcurrentJobs` | `Nullable` | | | `PollTimeoutMs` | `Nullable` | | | `WorkerName` | `String` | | | `StartupJitterMaxSeconds` | `Nullable` | | --- ## Runtime Runtime infrastructure types: job workers, backpressure management, eventual consistency polling, error handling, and key serialization. --- ## Authentication(Csharp-sdk) - **OAuth** — Automatic token management with singleflight refresh, caching, and retry - **Basic** — HTTP Basic Authentication - **None** — No authentication (local development) Auth strategy is auto-detected from environment variables when not explicitly set. --- ## Configuration Reference The SDK uses environment variables for configuration, matching the [JS SDK](https://github.com/camunda/orchestration-cluster-api-js) conventions: | Variable | Description | Default | | -------------------------------------- | ------------------------------------------------------------------------------------------------- | -------------------- | | `CAMUNDA_REST_ADDRESS` | Cluster REST API address | — | | `CAMUNDA_REST_ADDRESS_EXACT` | Use `CAMUNDA_REST_ADDRESS` verbatim, skipping the automatic `/v2` suffix (e.g. behind a gateway) | `false` | | `CAMUNDA_AUTH_STRATEGY` | `NONE`, `OAUTH`, or `BASIC` | Auto-detected | | `CAMUNDA_CLIENT_ID` | OAuth client ID | — | | `CAMUNDA_CLIENT_SECRET` | OAuth client secret | — | | `CAMUNDA_OAUTH_URL` | OAuth token endpoint | — | | `CAMUNDA_TOKEN_AUDIENCE` | OAuth audience | — | | `CAMUNDA_OAUTH_GRANT_TYPE` | OAuth grant type | `client_credentials` | | `CAMUNDA_OAUTH_SCOPE` | OAuth scope | — | | `CAMUNDA_OAUTH_TIMEOUT_MS` | OAuth token request timeout (ms) | `5000` | | `CAMUNDA_OAUTH_RETRY_MAX` | Max OAuth token fetch retries | `5` | | `CAMUNDA_OAUTH_RETRY_BASE_DELAY_MS` | OAuth retry base delay (ms) | `1000` | | `CAMUNDA_BASIC_AUTH_USERNAME` | Basic auth username | — | | `CAMUNDA_BASIC_AUTH_PASSWORD` | Basic auth password | — | | `CAMUNDA_DEFAULT_TENANT_ID` | Default tenant ID | `` | | `CAMUNDA_TENANT_IDS` | Comma-separated tenant IDs for job activation (overrides `CAMUNDA_DEFAULT_TENANT_ID` for workers) | — | | `CAMUNDA_SDK_LOG_LEVEL` | Log level (`error`, `warn`, `info`, `debug`, `trace`, `silent`) | `error` | | `CAMUNDA_SDK_VALIDATION` | Validation mode (see below) | `req:none,res:none` | | `CAMUNDA_SDK_HTTP_RETRY_MAX_ATTEMPTS` | Total HTTP retry attempts (initial + retries) | `3` | | `CAMUNDA_SDK_HTTP_RETRY_BASE_DELAY_MS` | HTTP retry base backoff (ms) | `100` | | `CAMUNDA_SDK_HTTP_RETRY_MAX_DELAY_MS` | HTTP retry max backoff cap (ms) | `2000` | | `CAMUNDA_SDK_EVENTUAL_POLL_DEFAULT_MS` | Default eventual consistency poll interval (ms) | `500` | | `ZEEBE_REST_ADDRESS` | Alias for `CAMUNDA_REST_ADDRESS` | — | | `CAMUNDA_MTLS_CERT` | Inline PEM client certificate | — | | `CAMUNDA_MTLS_KEY` | Inline PEM client private key | — | | `CAMUNDA_MTLS_CA` | Inline PEM CA bundle | — | | `CAMUNDA_MTLS_CERT_PATH` | Path to client certificate (PEM) | — | | `CAMUNDA_MTLS_KEY_PATH` | Path to client private key (PEM) | — | | `CAMUNDA_MTLS_CA_PATH` | Path to CA bundle (PEM) | — | | `CAMUNDA_MTLS_KEY_PASSPHRASE` | Passphrase for encrypted private key | — | For backpressure configuration variables, see [Global Backpressure](resilience.md#global-backpressure-adaptive-concurrency). --- ## Creating a Process Instance The recommended pattern is to obtain keys from a prior API response (e.g. a deployment) and pass them directly — no manual conversion needed: ```csharp using Camunda.Orchestration.Sdk; using var client = CamundaClient.Create(); var deployment = await client.DeployResourcesFromFilesAsync(["process.bpmn"]); var processKey = deployment.Processes[0].ProcessDefinitionKey; var result = await client.CreateProcessInstanceAsync( new ProcessInstanceCreationInstructionByKey { ProcessDefinitionKey = processKey, }); Console.WriteLine($"Process instance key: {result.ProcessInstanceKey}"); ``` If you need to restore a key from external storage (database, message queue, config file), wrap the raw value with the domain key constructor: ```csharp using Camunda.Orchestration.Sdk; using var client = CamundaClient.Create(); var storedKey = "2251799813685249"; // from a DB row or config var result = await client.CreateProcessInstanceAsync( new ProcessInstanceCreationInstructionByKey { ProcessDefinitionKey = ProcessDefinitionKey.AssumeExists(storedKey), }); Console.WriteLine($"Process instance key: {result.ProcessInstanceKey}"); ``` You can also start a process instance by BPMN process ID (which uses the latest deployed version): ```csharp var result = await client.CreateProcessInstanceAsync( new ProcessInstanceCreationInstructionById { ProcessDefinitionId = ProcessDefinitionId.AssumeExists("my-process-id"), }); ``` --- ## Deploying Resources Deploy BPMN, DMN, or Form files from disk: ```csharp using Camunda.Orchestration.Sdk; using var client = CamundaClient.Create(); var result = await client.DeployResourcesFromFilesAsync(["process.bpmn", "decision.dmn"]); Console.WriteLine($"Deployment key: {result.DeploymentKey}"); foreach (var process in result.Processes) { Console.WriteLine($" Process: {process.ProcessDefinitionId} (key: {process.ProcessDefinitionKey})"); } ``` --- ## Installation ```bash dotnet add package Camunda.Orchestration.Sdk ``` ## Versioning This SDK has a different release cadence from the Camunda server. Features and fixes land in the SDK during a server release. The major version of the SDK signals a 1:1 type coherence with the server API for a Camunda minor release. SDK version `n.y.z` -> server version `8.n`, so the type surface of SDK version 9.y.z matches the API surface of Camunda 8.9. Using a later SDK version, for example: SDK version 10.y.z with Camunda 8.9, means that the SDK contains additive surfaces that are not guaranteed at runtime, and the compiler cannot warn of unsupported operations. Using an earlier SDK version, for example: SDK version 9.y.z with Camunda 8.10, results in slightly degraded compiler reasoning: exhaustiveness checks cannot be guaranteed by the compiler for any extended surfaces (principally, enums with added members). In the vast majority of use-cases, this will not be an issue; but you should be aware that using the matching SDK major version for the server minor version provides the strongest compiler guarantees about runtime reliability. **Release lines** map to branches. Stable releases for a given server minor come from the `stable/` branch — `stable/10` publishes the `10.x` line for Camunda 8.10, and `stable/9` the `9.x` line for Camunda 8.9. The `main` branch publishes alpha prereleases for the **next** server minor — currently the `11.0.0-alpha.N` line for Camunda 8.11. Pick the line that matches your server: the latest `10.x` for 8.10 (the line starts at `10.0.1`), the latest `9.x` for 8.9, or opt into `11.x-alpha` to preview 8.11. **Recommended approach**: - Check the [CHANGELOG](https://github.com/camunda/orchestration-cluster-api-csharp/releases). - As a sanity check during server version upgrade, rebuild applications with the matching SDK major version to identify any affected runtime surfaces. --- ## Job Workers Job workers subscribe to a specific job type and process jobs as they become available. The worker handles polling, concurrent dispatch, auto-completion, and error handling. ## Basic Worker ```csharp using Camunda.Orchestration.Sdk; // Define input/output DTOs public record OrderOutput(bool Processed, string InvoiceNumber); using var client = CamundaClient.Create(); client.CreateJobWorker( new JobWorkerConfig { JobType = "process-order", JobTimeoutMs = 30_000, }, async (job, ct) => { var input = job.GetVariables(); var invoice = await ProcessOrder(input!, ct); // Return value auto-completes the job with these output variables return new OrderOutput(true, invoice); }); // Block until Ctrl+C using var cts = new CancellationTokenSource(); Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); }; await client.RunWorkersAsync(ct: cts.Token); ``` ## Handler Contract The handler return value determines the job outcome: | Handler behavior | Job outcome | | ----------------------------- | ----------------------------------------------------- | | Return `object` | Auto-complete with those variables | | Return `null` | Auto-complete with no variables | | Return `JobCompletionRequest` | Complete with structured result (corrections, denial) | | Throw `BpmnErrorException` | Trigger a BPMN error boundary event | | Throw `JobFailureException` | Fail with custom retries / back-off | | Throw any other exception | Auto-fail with `retries - 1` | ```csharp // BPMN error — caught by error boundary events in the process model throw new BpmnErrorException("INVALID_ORDER", "Order not found"); // Explicit failure with retry control throw new JobFailureException("Service unavailable", retries: 2, retryBackOffMs: 5000); ``` ## Job Corrections (User Task Listeners) When handling jobs from [user task listeners](../../components/concepts/user-task-listeners.md), you can return a `JobCompletionRequest` to apply corrections to the task or deny the action. Return a `JobCompletionRequest` from the handler instead of a plain variables object: ```csharp client.CreateJobWorker(config, async (job, ct) => { // Apply corrections to the user task return new JobCompletionRequest { Variables = new { reviewed = true }, Result = new JobResultUserTask { Corrections = new JobResultCorrections { Assignee = "new-assignee", Priority = 75, CandidateGroups = new List { "managers" }, }, }, }; }); ``` To deny the user task action (e.g. reject a completion): ```csharp client.CreateJobWorker(config, async (job, ct) => { return new JobCompletionRequest { Result = new JobResultUserTask { Denied = true, DeniedReason = "Missing required fields", }, }; }); ``` ## Void Handler (No Output Variables) For handlers that don't return output variables, use the void overload: ```csharp public record NotificationInput(string Message); client.CreateJobWorker(config, async (job, ct) => { await SendNotification(job.GetVariables()!, ct); // Auto-completes with no variables }); ``` ## Configuration | Property | Default | Description | | ------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | | `JobType` | _(required)_ | BPMN task type to subscribe to | | `JobTimeoutMs` | _(env / required)_ | Job lock duration (ms). Falls back to `CAMUNDA_WORKER_TIMEOUT` env var. | | `MaxConcurrentJobs` | `10` | Max in-flight jobs per worker. Falls back to `CAMUNDA_WORKER_MAX_CONCURRENT_JOBS` env var, then `10`. | | `PollIntervalMs` | `500` | Delay between polls when idle | | `PollTimeoutMs` | `null` | Long-poll timeout (null = broker default). Falls back to `CAMUNDA_WORKER_REQUEST_TIMEOUT` env var. | | `FetchVariables` | `null` | Variable names to fetch (null = all) | | `WorkerName` | auto | Worker name for logging. Falls back to `CAMUNDA_WORKER_NAME` env var. | | `AutoStart` | `true` | Start polling on creation | | `StartupJitterMaxSeconds` | `0` | Max random delay (seconds) before first poll. Falls back to `CAMUNDA_WORKER_STARTUP_JITTER_MAX_SECONDS` env var. | | `TenantIds` | `null` | Tenant IDs to activate jobs for. Falls back to `CAMUNDA_TENANT_IDS`, then `[CAMUNDA_DEFAULT_TENANT_ID]`. Mutually exclusive with `TenantId`. | | `TenantId` | `null` | Single-tenant convenience for `TenantIds`. Mutually exclusive with `TenantIds`. | | `TenantFilter` | `null` | Tenant filtering strategy (`PROVIDED` / `ASSIGNED`). See [Multi-Tenant Workers](#multi-tenant-workers). Requires Camunda 8.9+. | ## Multi-Tenant Workers A worker activates jobs for a fixed tenant set (`TenantIds` / `TenantId`), or delegates the choice to the server with `TenantFilter`: | `TenantFilter` | Behavior | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | `null` / `PROVIDED` | Activate jobs for the tenants named in `TenantIds` / `TenantId`, falling back to `CAMUNDA_TENANT_IDS`, then the default tenant. | | `ASSIGNED` | Activate jobs for whichever tenants are currently assigned to the authenticated client. No tenant IDs are sent. | The fixed tenant set can also come from configuration, so a fleet of workers shares one tenant list: ```bash export CAMUNDA_TENANT_IDS=acme,globex ``` ```json // appsettings.json — array or comma-separated string { "Camunda": { "TenantIds": ["acme", "globex"] } } ``` **Tenant precedence** (highest wins): `JobWorkerConfig.TenantIds` / `TenantId` > `CAMUNDA_TENANT_IDS` > `[CAMUNDA_DEFAULT_TENANT_ID]`. An empty `TenantIds` list counts as unset. `TenantFilter = ASSIGNED` opts out of the chain entirely — the configured tenant IDs are ignored, not sent. ```csharp // Fixed tenant set — activate jobs for these tenants only client.CreateJobWorker( new JobWorkerConfig { JobType = "process-order", JobTimeoutMs = 30_000, TenantIds = new[] { "acme", "globex" }, }, async (job, ct) => null); // Dynamic tenant set — activate jobs for whichever tenants are currently // assigned to the authenticated client. The server re-evaluates the // assignment on every activation request, so tenants added or removed in // Camunda take effect without restarting the worker. client.CreateJobWorker( new JobWorkerConfig { JobType = "process-order", JobTimeoutMs = 30_000, TenantFilter = TenantFilterEnum.ASSIGNED, }, async (job, ct) => null); ``` `ASSIGNED` re-reads the assignment on every activation request, so the worker picks up newly assigned tenants and drops removed ones without restarting and without the application querying or caching the tenant list. Notes: - `ASSIGNED` cannot be combined with `TenantIds` or `TenantId` — the server would ignore them, so `CreateJobWorker` throws `ArgumentException` instead. - `ASSIGNED` requires multi-tenancy to be enabled on the cluster; otherwise the activation request is rejected with HTTP 400. ## Heritable Worker Defaults When running many workers with the same base configuration, you can set global defaults via environment variables. These apply to every worker created by the client unless the individual `JobWorkerConfig` explicitly overrides them. | Environment Variable | Config Property | Type | | ------------------------------------------- | ------------------------- | ------ | | `CAMUNDA_WORKER_TIMEOUT` | `JobTimeoutMs` | long | | `CAMUNDA_WORKER_MAX_CONCURRENT_JOBS` | `MaxConcurrentJobs` | int | | `CAMUNDA_WORKER_REQUEST_TIMEOUT` | `PollTimeoutMs` | long | | `CAMUNDA_WORKER_NAME` | `WorkerName` | string | | `CAMUNDA_WORKER_STARTUP_JITTER_MAX_SECONDS` | `StartupJitterMaxSeconds` | int | **Precedence:** explicit `JobWorkerConfig` value > environment variable > hardcoded default. ```bash export CAMUNDA_WORKER_TIMEOUT=30000 export CAMUNDA_WORKER_MAX_CONCURRENT_JOBS=8 export CAMUNDA_WORKER_NAME=order-service ``` ```csharp // Workers inherit timeout, concurrency, and name from environment client.CreateJobWorker( new JobWorkerConfig { JobType = "validate-order" }, async (job, ct) => null); client.CreateJobWorker( new JobWorkerConfig { JobType = "ship-order" }, async (job, ct) => null); // Per-worker override: this worker uses 32 concurrent jobs instead of the global 8 client.CreateJobWorker( new JobWorkerConfig { JobType = "bulk-import", MaxConcurrentJobs = 32 }, async (job, ct) => null); ``` You can also pass defaults programmatically via the client constructor: ```csharp var client = CamundaClient.Create(new CamundaOptions { Config = new Dictionary { ["CAMUNDA_WORKER_TIMEOUT"] = "30000", ["CAMUNDA_WORKER_MAX_CONCURRENT_JOBS"] = "8", }, }); ``` ## Concurrency Jobs are dispatched as concurrent `Task`s on the .NET thread pool. `MaxConcurrentJobs` controls how many jobs may be in-flight simultaneously. - **I/O-bound handlers** (HTTP calls, database queries): higher values like 32–128 improve throughput because `async` handlers release threads during `await` points — many jobs, few OS threads. - **CPU-bound handlers**: set `MaxConcurrentJobs` to `Environment.ProcessorCount` to match cores. - **Sequential processing**: set `MaxConcurrentJobs = 1`. ## Lifecycle ```csharp // Manual start/stop var worker = client.CreateJobWorker(new JobWorkerConfig { JobType = "example", JobTimeoutMs = 30_000, AutoStart = false }, handler); worker.Start(); // Graceful stop — waits up to 10s for in-flight jobs to finish var result = await worker.StopAsync(gracePeriod: TimeSpan.FromSeconds(10)); // result.RemainingJobs, result.TimedOut // Or stop all workers at once await client.StopAllWorkersAsync(TimeSpan.FromSeconds(10)); // DisposeAsync stops workers automatically await using var disposableClient = CamundaClient.Create(); ``` --- ## Logging The SDK uses `Microsoft.Extensions.Logging` — the standard .NET logging abstraction. This means it integrates with any logging framework that supports `ILoggerFactory` (Serilog, NLog, the built-in console logger, etc.). ## Default Behavior When no logger is injected, the SDK uses a built-in console logger filtered by `CAMUNDA_SDK_LOG_LEVEL`: | `CAMUNDA_SDK_LOG_LEVEL` | What is logged | | ----------------------- | ---------------------------------------------------------------- | | `error` (default) | Errors only | | `warn` | Errors + warnings | | `info` | + OAuth token events, worker start/stop | | `debug` | + HTTP requests/responses, retry decisions, backpressure changes | | `trace` | + tenant injection, internal diagnostics | | `silent` | Nothing (same as `NullLoggerFactory`) | Output uses a tagged format matching the JS SDK: ``` [camunda-sdk][info][CamundaClient] CamundaClient constructed with auth strategy OAuth [camunda-sdk][debug][CamundaClient] HTTP POST process-instances/search -> 200 [camunda-sdk][info][JobWorker.worker-process-order-1] JobWorker 'worker-process-order-1' started for type 'process-order' ``` ## Injecting Your Own Logger Pass an `ILoggerFactory` via `CamundaOptions` to integrate with your application's logging: ```csharp using Camunda.Orchestration.Sdk; using var loggerFactory = LoggerFactory.Create(builder => { builder .AddConsole() .SetMinimumLevel(LogLevel.Debug); }); using var client = CamundaClient.Create(new CamundaOptions { LoggerFactory = loggerFactory, }); ``` When an `ILoggerFactory` is provided, `CAMUNDA_SDK_LOG_LEVEL` is ignored — filtering is controlled entirely by the injected factory. ## ASP.NET Core / Dependency Injection When using `AddCamundaClient()`, the SDK automatically resolves `ILoggerFactory` from the DI container — no manual wiring needed: ```csharp using Camunda.Orchestration.Sdk; var builder = WebApplication.CreateBuilder(args); // Logging configuration builder.Logging.SetMinimumLevel(LogLevel.Debug); // SDK automatically uses the host's ILoggerFactory builder.Services.AddCamundaClient(builder.Configuration.GetSection("Camunda")); ``` All SDK log entries appear alongside your application logs with proper category names (`Camunda.Orchestration.Sdk.CamundaClient`, `Camunda.Orchestration.Sdk.JobWorker.*`, etc.). ## Serilog Integration ```csharp Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.Console() .CreateLogger(); using var loggerFactory = new SerilogLoggerFactory(); using var client = CamundaClient.Create(new CamundaOptions { LoggerFactory = loggerFactory, }); ``` ## What Gets Logged | Component | Level | Events | | --------------------- | ------- | ------------------------------------------------- | | `CamundaClient` | Debug | HTTP request method + path, response status codes | | `CamundaClient` | Warning | HTTP request failures (non-2xx) | | `CamundaClient` | Trace | Default tenant ID injection | | `OAuthManager` | Debug | Token request attempts | | `OAuthManager` | Info | Token acquired (with effective expiry) | | `BackpressureManager` | Debug | Permit reduction/recovery | | `HttpRetryExecutor` | Debug | Retry attempts with delay and reason | | `JobWorker.*` | Info | Worker started, worker stopped | | `JobWorker.*` | Debug | Job completed | | `JobWorker.*` | Error | Handler exceptions, poll failures | | `EventualPoller` | Debug | Consistency polling progress | --- ## Migration Guide: v9 → v10 Upgrading from `Camunda.Orchestration.Sdk` v9 (Camunda 8.9) to v10 (Camunda 8.10)? **→ See [MIGRATION.md](https://github.com/camunda/orchestration-cluster-api-csharp/blob/main/MIGRATION.md) for the full guide.** In short: 55 client method parameters moved from `string` to semantic key types (`GroupId`, `RoleId`, `MappingRuleId`, `ClientId`, `ClusterVariableName`), 26 types were renamed, 45 bare-`string` properties became typed enums, and binary responses now return `byte[]`. The compiler will point you at every affected call site. ```xml ``` --- ## Quick Start (Zero-Config — Recommended) Keep configuration out of application code. Let the factory read `CAMUNDA_*` variables from the environment (12-factor style). This makes rotation, secret management, and environment promotion safer and simpler. ```csharp using Camunda.Orchestration.Sdk; // Zero-config construction: reads CAMUNDA_* from environment variables. // If no configuration is present, defaults to Camunda 8 Run on localhost. using var client = CamundaClient.Create(); var topology = await client.GetTopologyAsync(); Console.WriteLine($"Brokers: {topology.Brokers?.Count ?? 0}"); ``` Typical environment (example): ```bash CAMUNDA_REST_ADDRESS=https://cluster.example # SDK appends /v2 automatically CAMUNDA_REST_ADDRESS_EXACT=false # optional: true = use address verbatim (no /v2), e.g. behind a gateway CAMUNDA_AUTH_STRATEGY=OAUTH CAMUNDA_CLIENT_ID=*** CAMUNDA_CLIENT_SECRET=*** CAMUNDA_OAUTH_URL=https://login.cloud.camunda.io/oauth/token CAMUNDA_DEFAULT_TENANT_ID= # optional: override default tenant ``` > **Why zero-config?** > > - **Separation of concerns**: business code depends on an interface, not on secrets/constants wiring. > - **12-Factor alignment**: config lives in the environment → simpler promotion (dev → staging → prod). > - **Secret rotation**: rotate credentials without a code change or redeploy. > - **Immutable start**: single hydration pass prevents drift / mid-request mutations. > - **Test ergonomics**: swap env vars per test without touching source; create multiple clients for multi-tenant tests. > - **Security review**: fewer code paths handling secrets; scanners & vault tooling work at the boundary. > - **Deploy portability**: same artifact runs everywhere; only the environment differs. > - **Cross-SDK consistency**: identical variable names across JavaScript, C#, and Python SDKs. ## Programmatic Overrides (Advanced) Use only when you must supply or mutate configuration dynamically (e.g. multi-tenant routing, tests, ephemeral preview environments). Keys mirror their `CAMUNDA_*` env names: ```csharp using Camunda.Orchestration.Sdk; using var client = CamundaClient.Create(new CamundaOptions { Config = new Dictionary { ["CAMUNDA_REST_ADDRESS"] = "https://my-cluster.camunda.io", ["CAMUNDA_AUTH_STRATEGY"] = "OAUTH", ["CAMUNDA_CLIENT_ID"] = "my-client-id", ["CAMUNDA_CLIENT_SECRET"] = "my-secret", ["CAMUNDA_OAUTH_URL"] = "https://login.cloud.camunda.io/oauth/token", ["CAMUNDA_TOKEN_AUDIENCE"] = "zeebe.camunda.io", }, }); ``` ## Configuration via `appsettings.json` The SDK can read configuration from any `IConfiguration` source (appsettings.json, user secrets, Azure Key Vault, etc.) using idiomatic .NET PascalCase section keys: ```json { "Camunda": { "RestAddress": "https://cluster.example.com", "Auth": { "Strategy": "OAUTH", "ClientId": "my-client-id", "ClientSecret": "my-secret" }, "OAuth": { "Url": "https://login.cloud.camunda.io/oauth/token" }, "Backpressure": { "Profile": "CONSERVATIVE" } } } ``` Pass the section to the client: ```csharp using Camunda.Orchestration.Sdk; var builder = WebApplication.CreateBuilder(args); using var client = CamundaClient.Create(new CamundaOptions { Configuration = builder.Configuration.GetSection("Camunda"), }); ``` Precedence (highest wins): `Config` dictionary > `IConfiguration` section > environment variables > defaults. This means you can set secrets via environment variables (or a vault) and non-sensitive settings via `appsettings.json` — they layer naturally: ```json // appsettings.json — non-sensitive, checked into source control { "Camunda": { "RestAddress": "https://cluster.example.com", "Backpressure": { "Profile": "CONSERVATIVE" } } } ``` ```bash # Secrets injected via environment (vault, CI, container orchestrator) CAMUNDA_CLIENT_ID=*** CAMUNDA_CLIENT_SECRET=*** CAMUNDA_OAUTH_URL=https://login.cloud.camunda.io/oauth/token ```
appsettings.json key reference | appsettings.json key | Maps to env var | | --------------------------------- | ------------------------------------------------------------------------------------------------- | | `RestAddress` | `CAMUNDA_REST_ADDRESS` | | `RestAddressExact` | `CAMUNDA_REST_ADDRESS_EXACT` (true = use `RestAddress` verbatim, skip the automatic `/v2` suffix) | | `TokenAudience` | `CAMUNDA_TOKEN_AUDIENCE` | | `DefaultTenantId` | `CAMUNDA_DEFAULT_TENANT_ID` | | `TenantIds` | `CAMUNDA_TENANT_IDS` (JSON array or comma-separated string) | | `LogLevel` | `CAMUNDA_SDK_LOG_LEVEL` | | `Validation` | `CAMUNDA_SDK_VALIDATION` | | `Auth:Strategy` | `CAMUNDA_AUTH_STRATEGY` | | `Auth:ClientId` | `CAMUNDA_CLIENT_ID` | | `Auth:ClientSecret` | `CAMUNDA_CLIENT_SECRET` | | `Auth:BasicUsername` | `CAMUNDA_BASIC_AUTH_USERNAME` | | `Auth:BasicPassword` | `CAMUNDA_BASIC_AUTH_PASSWORD` | | `OAuth:Url` | `CAMUNDA_OAUTH_URL` | | `OAuth:ClientId` | `CAMUNDA_CLIENT_ID` | | `OAuth:ClientSecret` | `CAMUNDA_CLIENT_SECRET` | | `OAuth:GrantType` | `CAMUNDA_OAUTH_GRANT_TYPE` | | `OAuth:Scope` | `CAMUNDA_OAUTH_SCOPE` | | `OAuth:TimeoutMs` | `CAMUNDA_OAUTH_TIMEOUT_MS` | | `OAuth:RetryMax` | `CAMUNDA_OAUTH_RETRY_MAX` | | `OAuth:RetryBaseDelayMs` | `CAMUNDA_OAUTH_RETRY_BASE_DELAY_MS` | | `HttpRetry:MaxAttempts` | `CAMUNDA_SDK_HTTP_RETRY_MAX_ATTEMPTS` | | `HttpRetry:BaseDelayMs` | `CAMUNDA_SDK_HTTP_RETRY_BASE_DELAY_MS` | | `HttpRetry:MaxDelayMs` | `CAMUNDA_SDK_HTTP_RETRY_MAX_DELAY_MS` | | `Backpressure:Profile` | `CAMUNDA_SDK_BACKPRESSURE_PROFILE` | | `Backpressure:InitialMax` | `CAMUNDA_SDK_BACKPRESSURE_INITIAL_MAX` | | `Backpressure:SoftFactor` | `CAMUNDA_SDK_BACKPRESSURE_SOFT_FACTOR` | | `Backpressure:SevereFactor` | `CAMUNDA_SDK_BACKPRESSURE_SEVERE_FACTOR` | | `Backpressure:RecoveryIntervalMs` | `CAMUNDA_SDK_BACKPRESSURE_RECOVERY_INTERVAL_MS` | | `Backpressure:RecoveryStep` | `CAMUNDA_SDK_BACKPRESSURE_RECOVERY_STEP` | | `Backpressure:DecayQuietMs` | `CAMUNDA_SDK_BACKPRESSURE_DECAY_QUIET_MS` | | `Backpressure:Floor` | `CAMUNDA_SDK_BACKPRESSURE_FLOOR` | | `Backpressure:SevereThreshold` | `CAMUNDA_SDK_BACKPRESSURE_SEVERE_THRESHOLD` | | `Eventual:PollDefaultMs` | `CAMUNDA_SDK_EVENTUAL_POLL_DEFAULT_MS` |
## Dependency Injection (`AddCamundaClient`) For ASP.NET Core and other DI-based applications, use the `AddCamundaClient()` extension method on `IServiceCollection`. The client is registered as a singleton and automatically picks up `ILoggerFactory` from the container. **Zero-config** (environment variables only): ```csharp using Camunda.Orchestration.Sdk; var builder = WebApplication.CreateBuilder(args); builder.Services.AddCamundaClient(); ``` **With `appsettings.json`**: ```csharp using Camunda.Orchestration.Sdk; var builder = WebApplication.CreateBuilder(args); builder.Services.AddCamundaClient(builder.Configuration.GetSection("Camunda")); ``` **With options callback** (full control): ```csharp using Camunda.Orchestration.Sdk; builder.Services.AddCamundaClient(options => { options.Configuration = builder.Configuration.GetSection("Camunda"); // or: options.Config = new Dictionary { ... }; }); ``` Inject the client anywhere via constructor injection: ```csharp public class OrderController(CamundaClient camunda) : ControllerBase { [HttpPost] public async Task StartProcess() { var result = await camunda.CreateProcessInstanceAsync( new ProcessInstanceCreationInstructionById { ProcessDefinitionId = ProcessDefinitionId.AssumeExists("order-process"), }); return Ok(result); } } ``` ## Custom HttpClient ```csharp using Camunda.Orchestration.Sdk; var httpClient = new HttpClient { BaseAddress = new Uri("https://my-cluster/v2/") }; using var client = CamundaClient.Create(new CamundaOptions { HttpClient = httpClient, }); ``` --- ## Resilience ## HTTP Retry Automatic retry with exponential backoff and jitter for transient failures (429, 503, 500, timeouts). | Variable | Default | Description | | -------------------------------------- | ------- | ---------------------------------- | | `CAMUNDA_SDK_HTTP_RETRY_MAX_ATTEMPTS` | `3` | Total attempts (initial + retries) | | `CAMUNDA_SDK_HTTP_RETRY_BASE_DELAY_MS` | `100` | Base backoff delay (ms) | | `CAMUNDA_SDK_HTTP_RETRY_MAX_DELAY_MS` | `2000` | Maximum backoff cap (ms) | ## Global Backpressure (Adaptive Concurrency) The client includes an adaptive backpressure manager that throttles the number of in-flight operations when the cluster signals resource exhaustion. It complements (not replaces) per-request HTTP retry. ### Signals Considered An HTTP response is treated as a backpressure signal when it matches one of: - `429` (Too Many Requests) — always - `503` with `title === "RESOURCE_EXHAUSTED"` - `500` whose RFC 9457 / 7807 `detail` text contains `RESOURCE_EXHAUSTED` All other 5xx variants are treated as non-retryable (fail fast) and do **not** influence the adaptive gate. ### How It Works 1. Normal state starts with the concurrency cap from `CAMUNDA_SDK_BACKPRESSURE_INITIAL_MAX` (default 16). 2. On backpressure signals the manager reduces available permits using the soft factor (70% by default). 3. Repeated consecutive signals escalate severity to `severe`, applying a stronger reduction factor (50%). 4. Successful (non-backpressure) completions trigger passive recovery checks that gradually restore permits over time if the system stays quiet. 5. Quiet periods (no signals for a configurable decay interval) downgrade severity and reset the consecutive counter. The policy is intentionally conservative: it only engages after genuine pressure signals and recovers gradually to avoid oscillation. ### Configuration | Variable | Default | Description | | ----------------------------------------------- | ---------- | ------------------------------------------------------- | | `CAMUNDA_SDK_BACKPRESSURE_PROFILE` | `BALANCED` | Preset profile (see below) | | `CAMUNDA_SDK_BACKPRESSURE_INITIAL_MAX` | `16` | Bootstrap concurrency cap | | `CAMUNDA_SDK_BACKPRESSURE_SOFT_FACTOR` | `70` | Percentage multiplier on soft backpressure (70 → 0.70×) | | `CAMUNDA_SDK_BACKPRESSURE_SEVERE_FACTOR` | `50` | Percentage multiplier on severe backpressure | | `CAMUNDA_SDK_BACKPRESSURE_RECOVERY_INTERVAL_MS` | `1000` | Interval between passive recovery checks (ms) | | `CAMUNDA_SDK_BACKPRESSURE_RECOVERY_STEP` | `1` | Permits regained per recovery interval | | `CAMUNDA_SDK_BACKPRESSURE_DECAY_QUIET_MS` | `2000` | Quiet period to downgrade severity (ms) | | `CAMUNDA_SDK_BACKPRESSURE_FLOOR` | `1` | Minimum concurrency floor while degraded | | `CAMUNDA_SDK_BACKPRESSURE_SEVERE_THRESHOLD` | `3` | Consecutive signals required to enter severe state | ### Profiles Profiles supply coordinated defaults. Any explicitly set env var overrides the profile value. | Profile | initialMax | softFactor% | severeFactor% | recoveryMs | recoveryStep | quietDecayMs | floor | severeThreshold | Use case | | -------------- | ---------- | ----------- | ------------- | ---------- | ------------ | ------------ | ----- | --------------- | ---------------------------- | | `BALANCED` | 16 | 70 | 50 | 1000 | 1 | 2000 | 1 | 3 | General workloads | | `CONSERVATIVE` | 12 | 60 | 40 | 1200 | 1 | 2500 | 1 | 2 | Tighter capacity constraints | | `AGGRESSIVE` | 24 | 80 | 60 | 800 | 2 | 1500 | 2 | 4 | High throughput scenarios | | `LEGACY` | — | — | — | — | — | — | — | — | Observe-only (no gating) | Select via environment: ```bash CAMUNDA_SDK_BACKPRESSURE_PROFILE=AGGRESSIVE ``` Override individual knobs on top of a profile: ```bash CAMUNDA_SDK_BACKPRESSURE_PROFILE=AGGRESSIVE CAMUNDA_SDK_BACKPRESSURE_INITIAL_MAX=32 ``` The `LEGACY` profile disables adaptive gating entirely — signals are still tracked for observability but no concurrency limits are applied. Use this to opt out of backpressure management while retaining per-request retry. ### Inspecting State Programmatically ```csharp var state = client.GetBackpressureState(); // state.Severity: "healthy", "soft", or "severe" // state.Consecutive: consecutive backpressure signals observed // state.PermitsMax: current concurrency cap (null when LEGACY / not engaged) ``` ## Eventual Consistency Built-in polling for eventually consistent endpoints with configurable wait times and predicates. --- ## Self-signed TLS / mTLS The SDK supports custom TLS certificates via environment variables. This is useful for: - **Self-signed server certificates** — trust a CA that signed your server's certificate, without presenting a client identity. - **Mutual TLS (mTLS)** — present a client certificate and key to prove the client's identity. - **Both** — trust a custom CA _and_ present client credentials. ## Trusting a self-signed server certificate Set only the CA certificate to trust the server's self-signed certificate: ```bash # Path to PEM file: CAMUNDA_MTLS_CA_PATH=/path/to/ca.pem # Or inline PEM: CAMUNDA_MTLS_CA="-----BEGIN CERTIFICATE-----\n..." ``` ## Mutual TLS (client certificate) To present a client certificate for mutual TLS, provide both the certificate and private key: ```bash CAMUNDA_MTLS_CERT_PATH=/path/to/client.crt CAMUNDA_MTLS_KEY_PATH=/path/to/client.key # Optional — passphrase if the key is encrypted: # CAMUNDA_MTLS_KEY_PASSPHRASE=secret ``` ## Full mTLS with custom CA Combine a custom CA with client credentials: ```bash CAMUNDA_MTLS_CA_PATH=/path/to/ca.pem CAMUNDA_MTLS_CERT_PATH=/path/to/client.crt CAMUNDA_MTLS_KEY_PATH=/path/to/client.key ``` Inline PEM values (`CAMUNDA_MTLS_CERT`, `CAMUNDA_MTLS_KEY`, `CAMUNDA_MTLS_CA`) take precedence over their `_PATH` counterparts. TLS is applied to all outbound calls, including OAuth token requests. No code changes are needed — the SDK picks up TLS configuration from environment variables automatically: ```csharp using Camunda.Orchestration.Sdk; var client = CamundaClient.Create(); // TLS configured from env vars ``` --- ## Strongly-Typed Domain Keys All domain identifiers (process definition keys, job keys, user task keys, etc.) are `readonly record struct` types rather than plain strings. This prevents accidentally mixing different key types at compile time — the same pattern as the JS SDK's branded types. ```csharp using Camunda.Orchestration.Sdk; // Lift a raw value into the correct nominal type var defKey = ProcessDefinitionKey.AssumeExists("2251799813686749"); // Type safety — compiler prevents mixing key types var taskKey = UserTaskKey.AssumeExists("123456"); // await client.GetProcessDefinitionAsync(taskKey); // ← compile error // Validation — constraints (pattern, length) checked at construction ProcessDefinitionKey.IsValid("2251799813686749"); // true // Values returned from API calls are already typed var result = await client.GetProcessDefinitionAsync(defKey); // result.ProcessDefinitionKey is ProcessDefinitionKey, not string // Transparent JSON serialization — no special handling needed ``` Key types implement `ICamundaKey` (string-backed) or `ICamundaLongKey` (long-backed) and serialize as plain JSON values. Constraint validation (regex pattern, min/max length) is enforced in `AssumeExists()` and queryable via `IsValid()`. --- ## Support status The C# SDK is fully supported from Camunda 8.10. [Report an issue or share feedback](https://github.com/camunda/orchestration-cluster-api-csharp/issues) on GitHub. --- ## Typed Variables with DTOs Camunda API operations use dynamic `variables` and `customHeaders` payloads. By default these are untyped (`object`), but you can opt in to compile-time type safety using your own DTOs. ## Sending Variables (Input) Assign any DTO or dictionary to the `Variables` property — `System.Text.Json` serializes the runtime type automatically: ```csharp using Camunda.Orchestration.Sdk; // Define your application domain models public record OrderInput(string OrderId, decimal Amount); // Assign the DTO directly await client.CreateProcessInstanceAsync(new ProcessInstanceCreationInstructionById { ProcessDefinitionId = processDefinitionId, Variables = new OrderInput("ord-123", 99.99m), }); // Dictionaries also work — no DTO required await client.CompleteJobAsync(jobKey, new JobCompletionRequest { Variables = new Dictionary { ["processed"] = true }, }); ``` ## Receiving Variables (Output) Use `DeserializeAs()` to extract typed DTOs from API responses: ```csharp using Camunda.Orchestration.Sdk; public record OrderResult(bool Processed, string InvoiceNumber); // Deserialize variables from any API response var result = await client.CreateProcessInstanceAsync( new ProcessInstanceCreationInstructionById { ProcessDefinitionId = processDefinitionId, }); var output = result.Variables.DeserializeAs(); // output.Processed, output.InvoiceNumber — fully typed ``` `DeserializeAs()` handles the common runtime shapes: - `JsonElement` (standard API response) → deserialized via `System.Text.Json` - Already the target type → returned as-is (zero-copy) - `null` → returns `default(T)` Custom `JsonSerializerOptions` can be passed for non-standard naming conventions. ## Searching Variables as a DTO `SearchVariablesAsDtoAsync()` queries a process instance for exactly the variables declared on your DTO, pages through all results, and collapses them into a typed `VariableMap`. Variable names are derived from the same `JsonSerializerOptions` used to deserialize (camelCase by default, overridable with `[JsonPropertyName]`), so the query filter, the raw keys, and DTO binding always agree. ```csharp using Camunda.Orchestration.Sdk; public record OrderVariables(string OrderId, decimal Amount, string? Notes); // Query only the variables declared on the DTO, across all pages, and // collapse them into a single typed object. var map = await client.SearchVariablesAsDtoAsync(processInstanceKey); // Inspect individual values without materializing the whole DTO if (map.Contains("amount")) { var amount = map.Get("amount"); } // Validate() enforces that every non-nullable DTO member is present, // throwing VariableValidationException if any required variable is missing. OrderVariables order = map.Validate(); // order.OrderId, order.Amount — fully typed; order.Notes is optional ``` Behavior notes: - **Scope collision**: if the same variable name appears at more than one scope (e.g. a local and a parent scope), `SearchVariablesAsDtoAsync` throws `VariableScopeCollisionException` rather than guessing. Narrow the query with the optional `scopeKey` parameter. - **`Validate()`** throws `VariableValidationException` listing every missing required member; nullable members (`string?`, `int?`) are optional. - **`Get(name)`** and **`Get(name)`** read individual values lazily and return `default`/`null` when absent. --- ## C# SDK The C# client SDK for the [Camunda 8 Orchestration Cluster REST API](../apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). Unified configuration, OAuth/Basic auth, automatic retry, backpressure management, strongly-typed domain keys, and opt-in typed variables. --- ## Introduction to task applications Task applications are the interface between humans and Camunda processes to orchestrate human work. Learn key concepts of the architecture of task applications before you build your own. ## What are task applications? Task applications are end-user applications that allow humans to perform work orchestrated with a process. A [user task](/components/modeler/bpmn/user-tasks/user-tasks.md#user-task-forms) (for [human task orchestration](/guides/getting-started-orchestrate-human-tasks.md)) represents a single **work item** to be performed by an individual or a group, whether the preceding step ran automatically or an [AI agent](/reference/glossary.md#ai-agent) escalated it for human input. The jobs of a task application include: - Listing available tasks and allowing users to select a task to work on. - Providing filter and search options for users so they can more easily find the right next task to work on. - Presenting the selected task and an interface for completing the task, usually via a form. - Providing an interface to create new tasks, e.g. by starting a new process. - Provide insight into the progress of work tasks, including processes and cases. - Aggregate information so users and their managers can assess the impact on process goals, such as KPIs and SLAs. - Ensure tasks are visible only to authorized users. Task applications play a key role in the orchestration of business processes. They enable the orchestration of processes that still contain manual work without automating each process step in advance. This unlocks the potential for continuous improvement and for identifying opportunities for process optimization and automation. :::tip Not sure if you should use Camunda Tasklist, build your custom task application, or use a third-party application? Read the [guide to understand human task management](/components/best-practices/architecture/understanding-human-tasks-management.md#deciding-about-your-task-list-frontend) first. ::: ## Tasklist layout Camunda 8 comes with a ready-to-use Tasklist UI that implements all key concepts of a task application. The Tasklist UI is a generic task application; your custom task application should probably be tailored to your specific use case and also include external data sources and tools. The Tasklist UI is split into two main pages: the [tasks page](#task-page) and the [processes page](#processes-page). ### Task page The task page lists all tasks pending for a user or user group, and allows users to pick and claim a task from that queue to work on. On the same page, the details of a selected task are displayed including the form that the user must submit in order to execute and complete the task. The task page is optimized for efficient workflows, where the most important tasks should be worked on first. The task page is divided into two main areas: - Left side showing the tasks queue. - Right side showing the details of the selected task. #### Tasks queue The **tasks queue side panel** lists all tasks pending for a user or user group. It comes with filter and sort options that allow users to identify the right task to work on next. The tasks can be sorted by the creation date, due date, follow-up date, or priority. Learn more how to work with the task queue in the [Tasklist user guide](/components/tasklist/userguide/using-tasklist.md). #### Task details Task details are shown when a task is selected from the queue. A [form](/components/modeler/desktop-modeler/utilize-forms.md) is displayed as the task content, which must be filled out to complete the task. :::tip Typically, a task application utilizes forms to capture information from the user, to make a decision, to collect the results from a real-world task, or to provide task instructions to the user. However, a [user task](/components/modeler/bpmn/user-tasks/user-tasks.md#user-task-forms) is not limited to forms. A user task could also represent navigating to an external desktop or web application, where a task is to be performed, such as updating a record in a CRM. You can even use them to track physical work or actions using sensors, IoT devices, or any interface that can talk to the web, by using the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). For these cases, utilize the flexible [custom form key](/components/hub/workspace/modeler/modeling/advanced-modeling/form-linking.md#custom-form-key). ::: On the top of the form, a header shows the title of the task to work on, and the current assignee. Depending on the status of the assignment, a button allows you to assign the task to yourself or release it to the queue. At the bottom of the form there is a button with which you can complete the task. To the right of the task, you find additional information about the task, such as the [due date](/components/modeler/bpmn/user-tasks/user-tasks.md#scheduling) of the task, or the authorization data that controls who can work on the task. Potential extensions are dependent on your use case. You can consider adding more buttons to the bottom of the panel to indicate different task outcomes such as "approve" or "reject", or you could add a list of attachments to the right panel. Learn more how to work with the task details panel in the [Tasklist user guide](/components/tasklist/userguide/using-tasklist.md). ### Processes page The **Processes** page lists all processes available to the logged in user, and allows the user to start a process from there. Potential extensions are dependent on your use case. You can consider grouping processes by apps, domains, or teams, showing a process history, or adding a list of open process instances or cases. Learn more about the **Processes** page in the [Tasklist documentation](/components/tasklist/userguide/starting-processes.md). :::tip Alternative layouts There are many alternative layouts that you can choose for creating your task application. Design the layout based on the use case. For longer running processes and tasks with a lot of hierarchy between the tasks and the associated data, for example, tabular views together with multi-part detail views are more suitable. ::: ## Task lifecycle Every task follows a task life cycle. In the typical task life cycle, a task can, for example: - Be **created**, but not yet assigned - Be **assigned** and ready to work - Be **open** or **started** - Be **paused** and marked with a follow-up date - Be **delegated** to another user - Be **completed** or **canceled** Before you create your task application, you should be clear about [which task lifecycle is suitable for your use case](./02-user-task-lifecycle.md). ```mermaid flowchart subgraph Assignment Unassigned(Unassigned) -->|assign/claim| Assigned(fa:fa-user Assigned) Assigned -->|return| Unassigned Assigned -->|reassign| Assigned end subgraph Work state New(( )) -->|create| A(Open) A -->|start| B(In progress) B -->|complete| C(fa:fa-check Completed) B -->|pause| D(Paused) D -->|resume| B(In progress) B -->|return| A style New fill:black style C stroke-width:2px end ``` The lifecycle of human task orchestration is mostly a generic issue. There is no need to model common aspects into all your processes, as this often makes models unreadable. Use Camunda task management features or implement your requirements in a generic way. Learn how to define and implement your task lifecycle on the [user task lifecycle](./02-user-task-lifecycle.md) page. ## Task assignment Every task can be assigned to either a group of people, or a specific individual. An individual can **claim** a task, indicating that they are picking the task from the pool (to avoid multiple people working on the same task). As a general rule, you should assign user tasks in your business process to groups of people instead of specific individuals. This avoids bottlenecks (such as high workloads on single individuals or employees being on sick leave) and can greatly improve your process performance. In the [XML of a user task](/components/modeler/bpmn/user-tasks/user-tasks.md#xml-representations), this is represented as follows: ```xml ``` Then, require individual members of that group to explicitly claim tasks before working on them. This way, you avoid different people trying to work on the same task at the same time, which can cause a race condition. ## Additional elements and alternative use cases Often, task applications support the collaborative work on tasks, generally done using **comments**. **Document management** is a common use case of task applications, allowing users to upload, manage, and review **attachments**. Task applications are also the right place to browse, reference, and manage **case management data**. Task applications are not limited to web applications to be worked on desktops. Camunda has been used successfully for the development of omnichannel customer-facing applications, such as **mobile banking apps**, often via a **backend-for-frontend** implementation. ## Next steps You learned the basic concepts of a task application. Your possible next steps are: - Learn how to [embed or customize Camunda Forms](/apis-tools/frontend-development/03-forms/01-introduction-to-forms.md) to render tailored forms that can be designed by business users. - Learn how to utilize the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) to query and execute tasks in your task application, and to enrich it with process execution data. --- ## User task lifecycle The user task lifecycle in Camunda defines how users interact with tasks and perform work. Define it before implementing your application logic and user interface. ## Define your task lifecycle Define your task lifecycle based on your use case, the users interacting with the task, and the data you want to track. Use the following task lifecycle as a starting point. ## Task lifecycle example [Tasklist](/components/tasklist/introduction-to-tasklist.md) implements a lifecycle optimized for tracking work on individual tasks using [forms](../03-forms/01-introduction-to-forms.md). It separates assignment from task state to support collaborative processes. In a typical flow, users can: - Get assigned to a task, or assign the task to themselves. - Start working on the task. - Complete the task when the work is done. - Pause, resume, or return the task if they can't continue work, depending on how your task application handles interrupted work. If your application supports interrupted work, make sure it explicitly persists any draft or intermediate data before users leave the form. ```mermaid stateDiagram-v2 direction LR [*] --> creating creating --> created created --> assigning assigning --> created created --> updating updating --> created created --> completing completing --> created completing --> completed creating --> canceling created --> canceling assigning --> canceling updating --> canceling completing --> canceling canceling --> canceled classDef listenerEvent fill:#fc5d0d,color:white,font-weight:bold class creating listenerEvent class assigning listenerEvent class updating listenerEvent class completing listenerEvent class canceling listenerEvent ``` The engine derives the task state using a CQRS pattern. [Zeebe](/components/zeebe/zeebe-overview.md), Camunda's process execution engine, manages a stream of events. There is no single status attribute on tasks. Instead, the task status is derived from these events. User task listeners run in a blocking manner. The lifecycle transition pauses until all listeners complete. Listeners can also deny certain transitions. During `completing`, a listener can reject the transition and return the task to its previous state. :::tip Optimize currently tracks assigned and unassigned time for user tasks. If you need more detailed reporting, such as work started, paused, resumed, or returned, model these as custom `action` values and process them in your own reporting or audit logic. ::: ### Task assignment Assignment runs independently of the work state, so tasks can be reassigned while work is in progress. A task may be assigned but remain open for some time, indicating that the assigned user is not available to work on it immediately. The assignee can also change while work is in progress. In the Tasklist user interface, a task can be claimed by the logged-in user, which assigns the task to that user. Managers can assign unassigned tasks to team members and reassign them as needed. ```mermaid flowchart subgraph Assignment Unassigned(Unassigned) -->|assign/claim| Assigned(fa:fa-user Assigned) Assigned -->|return| Unassigned Assigned -->|reassign| Assigned end ``` The execution engine does not validate user authorization. Your application must enforce access control. Tasklist allows only the assigned user or another authorized user to update and complete a task. You can implement different rules in your application, such as allowing a user to complete a task on behalf of another user. In Camunda 8.9 and later, you can use [user task authorizations](../../../components/tasklist/user-task-authorization.md) to control who can read, update, assign, or complete user tasks. The following best practices are implemented in Tasklist: - `update` and `complete` operations can only be performed by the assigned user or an admin or manager. - Users can only see tasks assigned to them and tasks assigned to their candidate groups. - When a task is returned to the queue, the assignee is cleared so another user can pick it up. - Only authorized users can reassign tasks. - Users can return tasks, but they must provide a comment explaining why. - Users can mark tasks with a follow-up date. Depending on the assignment, the task remains assigned to the user or becomes unassigned. Define validation logic that matches your use case. ## Lifecycle events Camunda emits lifecycle events that can be triggered by REST API operations. User task listeners react to them to execute custom logic. For details, see [user task listeners](/components/concepts/user-task-listeners.md). Supported events: - `creating` - `assigning` - `updating` - `completing` - `canceling` Lifecycle events represent engine-level transitions. Supported API calls can include an `action` value to add application-specific meaning to the resulting user task listener event. For example, your application can use actions such as `start`, `pause`, or `resume` to represent application-level work progress. ### `creating` The `creating` event is emitted when a task instance is created. If the `creating` event already contains an assignee, no additional `assigning` event is fired. ### `assigning` The engine emits the `assigning` event when a task assignment changes. The resulting event can include an `action` value, such as `claim`, `assign`, `return`, or `unassign`. ### `updating` The engine emits the `updating` event when task data changes, except assignment changes. This can include changes to variables, candidate users, candidate groups, or other supported task fields. The update API can also include an `action` value. Use this value to add application-specific meaning to the resulting event, such as `start`, `pause`, or `resume`. ### `completing` The engine emits the `completing` event when a task is being completed. It can contain a custom action to indicate the outcome, such as `approved` or `rejected`. ### `canceling` The engine emits the `canceling` event when a user task is terminated by the process. This happens when the process instance is canceled or an interrupting catch event ends the user task. ## Implement the lifecycle Use the Orchestration Cluster REST API to implement task lifecycle operations in your application. See the [full API reference](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). ### Perform lifecycle operations You interact with user tasks through the following endpoints: - Assign or unassign a task: - [`POST /user-tasks/:userTaskKey/assignment`](/apis-tools/orchestration-cluster-api-rest/specifications/assign-user-task.api.mdx) - [`DELETE /user-tasks/:userTaskKey/assignee`](/apis-tools/orchestration-cluster-api-rest/specifications/unassign-user-task.api.mdx) - Update a task: - [`PATCH /user-tasks/:userTaskKey`](/apis-tools/orchestration-cluster-api-rest/specifications/update-user-task.api.mdx) - Complete a task: - [`POST /user-tasks/:userTaskKey/completion`](/apis-tools/orchestration-cluster-api-rest/specifications/complete-user-task.api.mdx) These operations trigger lifecycle events such as `assigning`, `updating`, and `completing`. ### Assign a task Use the assignment endpoint to assign, reassign, or unassign a task. - `POST /user-tasks/:userTaskKey/assignment` assigns or reassigns a task. - `DELETE /user-tasks/:userTaskKey/assignee` removes the current assignee. Use the `action` attribute to describe the reason for the change, such as `claim`, `assign`, or `reassign`. ### Update a task Use the update endpoint to modify task data or provide an application-specific `action` value. You can: - Update fields such as candidate users, candidate groups, due date, or follow-up date using a `changeset`. - Add application-specific meaning to the resulting event by providing an `action` value, such as `start`, `pause`, or `resume`. You can also send custom actions for audit or business logic purposes, such as `escalate`, `requestFurtherInformation`, `uploadDocument`, or `openExternalApp`. Example request: ```json { "changeset": { "dueDate": "2024-03-18T20:47:20.340Z" }, "action": "escalate" } ``` ### Complete a task Use `POST /user-tasks/:userTaskKey/completion` to complete a task. You can include an `action` to indicate the outcome, such as `approve` or `reject`. ## Reporting Use lifecycle events to build audit logs or productivity reports. ### Task lifecycle reporting in Optimize Optimize supports task productivity reports but currently measures only assigned vs. unassigned time. It does not calculate: - **Idle time:** Time a task was open (time to `start`). - **Net working time:** Time during which a task was processed from a custom `start` action to completion, excluding time between custom `pause` and `resume` actions. ### Export task lifecycle information Use user task listeners and job workers to send lifecycle event data to external systems such as analytics or monitoring tools. --- ## Task application architecture A typical task application architecture consists of a task application frontend, a backend-for-frontend, and one or more data sources or services that contain business data relevant for the application users to perform their work. The backend implements Camunda Zeebe and Tasklist clients to retrieve and interact with tasks via Camunda APIs. For historical process instance data, Operate is also required. Depending on the user task implementation type (job worker-based vs Camunda user task) you use in your processes, you need to run either the Tasklist or Zeebe client to run operations on tasks. Task, form, and variable retrieval happens via the API. Learn more about the differences of the task implementation types in the [migration guide for Camunda user tasks](/apis-tools/migration-manuals/migrate-to-camunda-user-tasks.md), and complete that migration before upgrading to 8.10 if you still use job worker-based user tasks. :::tip Starting a new project? Use Camunda user tasks to simplify your implementation. ::: Click on any element of this diagram to jump to the documentation page for the respective component: ```mermaid %%{init: {"flowchart": {"htmlLabels": true}} }%% flowchart LR subgraph Architecture direction LR subgraph Custom Task Application direction LR Frontend --- BFF B --- ExtData subgraph BFF[ ] direction TB B[BFF \n Backend for Frontend] ExtData[Business Data] end end subgraph Camunda 8 direction LR subgraph Tasklist Rest[Rest API] Forms end subgraph Zeebe ZeebeRest[Rest API] end Tasklist <-.-> Job[Job worker-based tasks] Tasklist <-.-> ZeebeTasks Rest <-.-> Forms Zeebe[Zeebe REST API] <-.-> ZeebeTasks[Zeebe-based tasks] end BFF -->|Query Tasks\nJob-Based or Zeebe| Tasklist BFF -->|Operation\nJob-based| Tasklist BFF -->|Operation\nZeebe| Zeebe end style Frontend fill:#2272c9,color:#fff style B fill:#2272c9,color:#fff style Rest fill:#10c95d,color:#fff style ZeebeRest fill:#ed7d31,color:#fff style Zeebe stroke:#ed7d31 style Job fill:#10c95d,color:#fff style ZeebeTasks fill:#ed7d31,color:#fff style subGraph1 fill:#e4eef8,stroke:#2272c9 style BFF fill:#c8dcf0,stroke:#2272c9 style ExtData fill:#a5caef,stroke:#2272c9 style Tasklist stroke:#10c95d,color:#000 click Forms "../../forms/introduction-to-forms" click Rest "../../../tasklist-api-rest/tasklist-api-rest-overview" click Job "../../../migration-manuals/migrate-to-camunda-user-tasks" click ZeebeTasks "../../../migration-manuals/migrate-to-camunda-user-tasks" click ZeebeRest "../../../zeebe-api-rest/zeebe-api-rest-overview" ``` Follow these resources to learn more about the individual components: - Learn how to use the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/specifications/assign-user-task.api.mdx) for task, variable, and form retrieval, and to run operations on Camunda user tasks. - If you still use older job worker-based user tasks, review [migrating to Camunda user tasks](/apis-tools/migration-manuals/migrate-to-camunda-user-tasks.md) and complete that migration before upgrading to 8.10, because this capability relied on the removed V1 Tasklist API. - Understand how to design, embed, and customize [forms](/apis-tools/frontend-development/03-forms/01-introduction-to-forms.md). - Understand how this architecture fits into the overall Camunda architecture with the [Java greenfield stack](/components/best-practices/architecture/deciding-about-your-stack.md). --- ## Introduction to forms Forms play a key role in giving work instructions, collecting information and making decisions within human task orchestration. Forms are lightweight user interfaces, tailored for focused data input in specific steps of a process, rendering the orchestration of human tasks more efficient than simply routing users to the applications that are orchestrated. Forms are commonly used in [user tasks](/components/modeler/bpmn/user-tasks/user-tasks.md#user-task-forms), but also as [start forms](/components/tasklist/userguide/starting-processes.md) to start a new process instance. ## Camunda Forms In Camunda 8, you can design forms using a drag'n'drop editor. The form editor is available in both Desktop and Camunda Hub. Learn more about Camunda Forms and available components in the [Camunda Forms reference documentation](/components/modeler/forms/camunda-forms-reference.md), and learn how to design a human workflow with forms in the [getting started guide](/guides/getting-started-orchestrate-human-tasks.md). ## form-js Camunda Forms present a flexible, open solution to form creation. Camunda Forms are based on the [form-js library](https://github.com/bpmn-io/form-js) , maintained by Camunda. As a result, the form editor and renderer are non-proprietary, open-source technology, and can be used everywhere, also outside the context of Camunda 8. This unlocks a world of use cases, and eliminates any doubt around vendor lock-in or technical barriers. Form-js is a vanilla JavaScript library with [Preact](https://preactjs.com/) in the background, and can be used in any framework, from Angular to React. The resulting forms are serialized as a JSON document. The JSON document conforms to an open form schema that allows you to render the form using both the built-in form render, or even with a custom render. The form schema is extensible, allowing you to build your own extensions or custom components. :::tip Want to Contribute? We welcome your contributions to form-js! Whether it's fixing a bug, adding a feature or a new component, your input is valuable. You can also provide your ideas by opening issues for us or the community. **How to Contribute:** 1. 🌐 Visit our [GitHub Repository](https://github.com/bpmn-io/form-js). 2. 🛠️ Check out the issues or open a new one. 3. 💻 Fork the repository and submit a pull request. Let's make form-js better together! 👩‍💻👨‍💻 ::: Continue reading to learn how to setup, embed, and extend form-js to build form-based task applications for any use case. --- ## Concepts Use form-js, the open-source library that powers Camunda Forms, to embed forms anywhere from vanilla JavaScript to low-code application platforms. With form-js, you can view, visually edit, and simulate forms that are based on pure JSON. ## form-js basics The form-js project is made of three core libraries: the [form editor](https://github.com/bpmn-io/form-js/tree/develop/packages/form-js-editor) , the [form viewer](https://github.com/bpmn-io/form-js/tree/develop/packages/form-js-viewer) , and the [form playground](https://github.com/bpmn-io/form-js/tree/develop/packages/form-js-playground) . ### Form editor You can use the [form editor](https://github.com/bpmn-io/form-js/tree/develop/packages/form-js-editor) to design forms with a drag-and-drop interface. It uses [FEEL expressions](/components/modeler/feel/what-is-feel.md) to execute form logic in realtime, such as visibility conditions. Learn more about using the form editor in the [getting started guide](/components/modeler/desktop-modeler/utilize-forms.md). :::note The Camunda 8 form editor uses the [form playground](#form-playground) as it provides real-time preview and validation functionality. ::: ### Form viewer The [form viewer](https://github.com/bpmn-io/form-js/tree/develop/packages/form-js-viewer) renders a form built using the form editor. It is versatile and can be embedded in any JavaScript application to render a form and capture user interactions. Learn more about embedding the form viewer on the following pages. See the following example form using the form viewer, and interact with it: ### Form playground The [form playground](https://github.com/bpmn-io/form-js/tree/develop/packages/form-js-playground) is a tool to preview forms, simulate their behavior, and explore form-js in a playful manner. It combines the [editor](#form-editor) and the [viewer](#form-viewer) with mock data input and output panels to test a form and form editor features instantly. There is also a [Camunda-flavored version of the form playground](https://github.com/camunda/form-playground) , which closely resembles the form editor experience in [Camunda Hub](/components/hub/workspace/modeler/index.md) and [Desktop Modeler](/components/modeler/desktop-modeler/index.md), and supports rapid development. The form playground mainly comprises the following areas: - The **component palette** to search and add components. - The **editor canvas**, allowing to compose a form by dragging components. - The **preview pane**, which shows an interactive preview of the form. The preview updates in real-time when a change happens in the editor, properties panel, or mock input data. - The **properties panel**, which is used to configure the properties of a component. - The **data input panel**, which allows to simulate the form preview using mock input data. - The **output panel**, which calculates and shows the current form output in real-time, based on the interactions with the preview. The input and output panel, together with the preview, come in handy to simulate the behavior of a form, and to validate or debug the configuration of one or multiple components, especially when using expressions extensively. Use the input data panel to simulate process variables, business objects, or static data used in your form. Try form playground Try the form playground yourself directly on the web, no log in needed. ## The form schema A form is serialized as plain JSON with a simple, flat structure to maximize flexibility and versatility. In the root, a form contains some metadata attributes. The main form is defined by a list of components, where the components carry their layout properties themselves (e.g. which row a component belongs to). This is in contrast to markup languages such as HTML, where the arrangement of the nodes determines the layout. This enables backward compatibility and compatibility with user-defined renderers. See this simple form schema for example, and the resulting form: ```json { "components": [ { "label": "First name", "type": "textfield", "layout": { "row": "Row_0hqc9xn", "columns": null }, "id": "Field_05l2s7c", "key": "firstName" }, { "label": "Last name", "type": "textfield", "layout": { "row": "Row_0hqc9xn", "columns": null }, "id": "Field_0nw7e1c", "key": "lastName" }, { "label": "Income", "type": "number", "layout": { "row": "Row_1ggwq2d", "columns": 8 }, "id": "Field_12yshuy", "key": "monthlyNetIncome", "description": "Monthly net income", "appearance": { "prefixAdorner": "USD" }, "increment": "100", "validate": { "required": true, "min": 0 } } ], "type": "default", "id": "ExampleForm", "executionPlatform": "Camunda Cloud", "executionPlatformVersion": "8.4.0", "exporter": { "name": "Camunda Modeler", "version": "5.18.0" }, "schemaVersion": 12 } ``` All form-js packages share the same [JSON schema](https://github.com/bpmn-io/form-js/tree/develop/packages/form-json-schema) for forms. This enables the interoperability of the created forms between the form editor and the viewer and possibly also between custom-made form renderers, or translating from a Camunda Form to another form. Using the form schema, you can write extensions to existing components, while still receiving benefits from updates made to the core form-js libraries. The schema abstracts the form model from the viewer, and allows you to inject another expression or templating language as an alternative to FEEL, since expressions are simply stored as strings. The schema is built on top of and validated by [`json-schema@draft-07`](https://json-schema.org/draft-07/json-schema-release-notes.html). :::tip You can use tools like this [JSON Schema Viewer](https://navneethg.github.io/jsonschemaviewer/) to explore the schema visually, or this [tool from Atlassian](https://json-schema.app/view/%23?url=https%3A%2F%2Funpkg.com%2F%40bpmn-io%2Fform-json-schema%401.6.0%2Fresources%2Fschema.json) to validate a form against the schema. ::: ### Schema variables Form-js comes with versatile methods to extract the expected input and output variables from a form schema. This makes it easy to validate the input and output of a form, and you can combine it with data validation libraries like [joi](https://github.com/hapijs/joi) to ensure type and schema safety. Learn more about schema variables in the [embedding guide](./02-embed-in-javascript.md). ## Examples Visit the [form-js examples repository](https://github.com/bpmn-io/form-js-examples) to explore form-js by playing with the toolkit. --- ## Embed forms in JavaScript Learn how to embed the form viewer in your own applications and web pages using JavaScript. ## Set up form-js Set up the [form viewer](https://github.com/bpmn-io/form-js/tree/develop/packages/form-js-viewer) in your own JavaScript projects by importing the library from NPM or a CDN. Alternatively, you can fork the code and [build it yourself](https://github.com/bpmn-io/form-js?tab=readme-ov-file#build-and-run) . ### NPM If you use [NPM](https://docs.npmjs.com/getting-started/what-is-npm), install the form viewer as follows: ```sh npm install @bpmn-io/form-js-viewer ``` ### CDN You can import the form viewer from a content delivery network (CDN), for example when you want to use the form viewer directly in a browser environment without bundling it with your application. Form-js is served via unpkg. Specify the version you want to reference in the URL. ```js ``` If you want to automatically use the latest version of the form viewer, you can specify only the major version. ```js ``` Make sure to import the stylesheets as well, and ensure that the version matches: ```js ``` ## Embed into your application Embedding a form with the form viewer requires only a few steps. 1. Import the library 2. Specify the render target div and render the form 3. Import the [form schema](./01-concepts.md#the-form-schema) ```js const form = new Form({ container: document.querySelector("#form"), }); // schema of the form to embed const schema = { type: "default", id: "TestForm", components: [ { key: "name", label: "Name", type: "textfield", validate: { required: true, }, }, ], }; await form.importSchema(schema); ``` This results in: You can also detach a form from a container and attach to another during form runtime. Learn more about that in the [API documentation](https://github.com/bpmn-io/form-js/tree/develop/packages/form-js-viewer#formattachtoparentnode-htmlelement--void) . ### Input form context data To provide data to your form, such as process variables or business objects, pass a JSON object containing this data to the `importSchema` function. ```js ... const schema = { ... }; // form context/input data const data = { name: 'ACME Corp' }; await form.importSchema(schema, data); ``` This results in: You can use context data not just to populate field values, but also to control form behavior, to provide options for select fields, or even to provide localization to your forms. You can fetch business data via an API first, and inject it via the data object. The following example demonstrates how to provide select options via context data, by using a `valuesExpression`. ```js ... const schema = { components: [ { label: "Business domain", type: "select", key: "domain", valuesExpression: "=businessDomains" } ], type: "default", id: "TestForm", schemaVersion: 12 }; // form context/input data const data = { businessDomains: ["Software development", "Consulting"] }; await form.importSchema(schema, data); ``` This results in: ### Validate a form Before you allow a user to submit a form, you can use the `validate` function to ensure that all validation rules of your form are met and that all required fields are completed. Learn more in the [form API documentation](https://github.com/bpmn-io/form-js/tree/develop/packages/form-js-viewer#formvalidate--errors) . ```js const errors = form.validate(); if (Object.keys(errors).length) { console.error("Form has errors", errors); } ``` ### Trigger and listen to form events Form-js provides a comprehensive set of events and APIs to react on form state changes. You can listen for - [form state changes](https://github.com/bpmn-io/form-js/tree/develop/packages/form-js-viewer#changed---data-errors-) , - [form submissions](https://github.com/bpmn-io/form-js/tree/develop/packages/form-js-viewer#submit---data-errors-) , - [form layout changes](https://github.com/bpmn-io/form-js/tree/develop/packages/form-js-viewer#layouting-events) . In addition, hook into [lifecycle events](https://github.com/bpmn-io/form-js/tree/develop/packages/form-js-viewer#lifecycle-events) to add custom logic (e.g. initialize listeners on form fields after the form loaded). Learn more about the full API in the [GitHub repository documentation](https://github.com/bpmn-io/form-js/tree/develop/packages/form-js-viewer#api) . ### Retrieve form output data To retrieve the current form output data on any form state change, listen to the [`changed`](https://github.com/bpmn-io/form-js/tree/develop/packages/form-js-viewer#changed---data-errors--) event. To retrieve the data on submit, listen to the [`submit`](https://github.com/bpmn-io/form-js/tree/develop/packages/form-js-viewer#formsubmit---data-data-errors-errors-) event. ### Retrieve schema variables Use the `getSchemaVariables` util to retrieve the [variables defined in a form schema](./01-concepts.md#schema-variables). This is useful to gather what data is consumed and produced by a form. ```javascript const variables = getSchemaVariables(schema); console.log("Schema variables", variables); ``` It is also possible to distinct between input and output variables: ```javascript const outputVariables = getSchemaVariables(schema, { inputs: false }); const inputVariables = getSchemaVariables(schema, { outputs: false }); ``` :::note form-js does not enforce typing. Retrieving schema variables returns the variable names, but not the type or whether the variable is optional (i.e. whether the field is required or not). To retrieve the expected type of the variable, parse the form schema manually. To enforce the typing of input variables, use validation libraries such as [joi](https://github.com/hapijs/joi) . ::: ### Next steps - [Style forms](../03-customize-and-extend/01-styling.md) using CSS and custom renderers. - [Integrate external data via APIs](../03-customize-and-extend/03-integrate-api-data.md) into your forms and task applications. - Create [custom form components](../03-customize-and-extend/02-custom-components.md) to design flexible forms tailored to your individual use case. --- ## Styling Forms can be easily styled by combining defining own CSS rules and overriding a set of CSS variables. If you want to go beyond CSS, you can fork the [form viewer](https://github.com/bpmn-io/form-js/tree/develop/packages/form-js-viewer) and change the HTML returned by the individual form component renderers. ## Styling via CSS Form styling is built on two layers of CSS variables: | Layer | Variables | Purpose | | ----------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | **Semantic tokens** | `--bio-*` | Name a role rather than a component — surface, text, border, accent, radius. Rebinding one restyles every element in that role. | | **Component variables** | `--color-*`, `--font-*`, `--border-*`, … | What the form actually reads. Each color variable derives from a token; fonts, sizes, and geometry stand on their own. | Rebinding the tokens restyles the whole form, including the properties panel embedded in the form editor. Reach for a component variable only where you want to deviate from the shared semantics. ### Theming with semantic tokens form-js declares the tokens on the `bio-theme-parent` class, which it adds to every root it renders — the form container and any popup it attaches to `document.body`. Override them from an ancestor, and apply your theme class at the **application root** rather than around the form. Popups and tooltips are appended to the end of ``, outside the element that opened them, and a theme only reaches what it contains. ```css .my-theme .bio-theme-parent, .my-theme.bio-theme-parent { --bio-primary: #0f62fe; --bio-surface: #ffffff; --bio-border: #8d8d8d; --bio-text: #161616; } ``` Both selectors are required. A custom property declared on an element always beats one inherited from an ancestor, so a theme has to match the element form-js declared its tokens on — sitting above it is not enough. The complete, always-current list of tokens and the component variables derived from them lives in the stylesheets themselves: - [`form-js-base.css`](https://github.com/bpmn-io/form-js/blob/develop/packages/form-js-viewer/assets/form-js-base.css) — viewer - [`form-js-editor-base.css`](https://github.com/bpmn-io/form-js/blob/develop/packages/form-js-editor/assets/form-js-editor-base.css) — editor In both files the `.bio-theme-parent` block declares the tokens, and the `.fjs-container` block below it maps them onto component variables. ### Component variables Where a token does not express what you need, override the component variable directly on the form container: ```css .fjs-container { --color-background-disabled: #f4f4f4; --font-family: "IBM Plex Sans", sans-serif; --font-size-base: 14px; --form-field-height: 36px; } ``` Fonts, sizes, spacing, and field geometry are only available as component variables — they have no token equivalent. ### Styleable classes The simplest way to find the right styleable elements to override is inspecting form-js using your browser's developer tools. Scope rules with the `.fjs-container` class to prevent CSS conflicts. For example, to override field borders for single-line fields: ```css .fjs-container .fjs-input-group { border-width: 0 0 1px 0; } ``` ### Example To theme a form after another design system, bind the tokens to its palette. This is how the **Custom style (Material-like)** preview below is built: ```css .materialized .bio-theme-parent, .materialized.bio-theme-parent { --bio-surface: rgba(0, 0, 0, 0.06); --bio-text: rgba(0, 0, 0, 0.87); --bio-text-subtle: rgba(0, 0, 0, 0.6); --bio-border: rgba(0, 0, 0, 0.42); --bio-border-subtle: rgba(0, 0, 0, 0.12); --bio-primary: #5453d3; --bio-focus: #5453d3; --bio-danger: #d32f2f; } ``` That covers color. Anything the tokens don't express is defined by a component variable or a plain rule. This includes typography and Material's underlined field, which uses a border shape rather than a color: ```css .materialized .fjs-container { --font-family: Roboto, Helvetica, Arial, sans-serif; --line-height-input: 24px; } .materialized .fjs-container .fjs-input-group { border: none; border-bottom: 1px solid var(--bio-border); border-radius: 4px 4px 0 0; } ``` Basic style Custom style (Material-like) ## Styling via form viewer customization The [form viewer](https://github.com/bpmn-io/form-js/tree/develop/packages/form-js-viewer) contains all [basic form components](https://github.com/bpmn-io/form-js/tree/develop/packages/form-js-viewer/src/render/components/form-fields) shipped in Camunda Forms. For full flexibility, fork the library and change the returned HTML of the individual components, or override existing components via [custom form components](02-custom-components.md). ### Example The following example demonstrates replacing the default rendering of the [text field component](https://github.com/bpmn-io/form-js/blob/develop/packages/form-js-viewer/src/render/components/form-fields/Textfield.js) with [Material UI](https://mui.com/material-ui/react-text-field/). ```js title="packages/form-js-viewer/src/render/components/form-fields/Textfield.js" ... export default function Textfield(props) { const { ... } = props; ... const onInputBlur = () => { ... }; return // using MUI TextField instead of default { ... }} /> ; ... } ``` --- ## Custom components Form-js comes with an extension point to hook in custom components. You can define the renderer, the configuration options of the component in the properties panel, and the palette entry. Custom components are built and distributed separately from the form viewer and renderer, and can be plugged in on demand by registering them as `additionalModules`. ```js new Form({ container, schema, data, additionalModules: [MyCustomComponent], }); ``` Read the [step-by-step guide](https://github.com/bpmn-io/form-js-examples/tree/master/custom-components) and inspect the example component to learn how to write your own custom components. :::note Custom components currently cannot be imported into [Camunda Hub](/components/hub/workspace/modeler/index.md) or [Desktop Modeler](/components/modeler/desktop-modeler/index.md). If you use custom components, you need to host the form editor yourself. ::: ## Use cases for custom components - **Integration with external APIs:** create components that integrate with external APIs to fetch real-time data or perform specific actions. For example, a location input component could connect to a mapping API to suggest locations as users type. - **Tailored services architecture:** write your own backend (micro-)services coupled to your components, and let the components communicate with these services to fetch domain-specifc or internal data in a secure fashion. - **File upload:** develop a component that allows users to upload a file, which is stored in a document storage system and returns the reference ID or URL of the document as the component/form output. - **Data visualization:** build components that visualize data directly within the form. This could include charts, graphs, or other visual representations of information relevant to the form's purpose. - **Geolocation services:** create components that leverage geolocation services to capture or display location-based information. This can be helpful for forms that require location-specific data. - **Payment processing:** develop secure components that integrate with payment gateways to handle financial transactions within a form. --- ## Integrate API data Read this page to learn how to integrate external business data into your forms via APIs. ## Load data on form initation Before you initiate your form with data, make sure to fetch external business data and merge it with the process variables first. Data that is not bound to a form field using a key will not be submitted, keeping process instance data clean. As an example, use a `valuesExpression` in your form to populate the options of a select field. ```js //... const schema = { components: [ { label: "Opportunities", type: "select", key: "opportunity", valuesExpression: "=external.salesforce.opportunities", }, ], type: "default", id: "TestForm", schemaVersion: 12, }; const response = await fetch(url, fetchOptions); const opportunities = await response.json(); //... // form context/input data const data = { ...processVariables, external: { salesforce: { opportunities, }, // ... }, }; await form.importSchema(schema, data); ``` ## Load data on runtime with form events :::info Workaround Currently, there is no built-in way to update a form's context data on runtime. However, a workaround exists. ::: To load and update data on runtime (e.g. when searching in a searchable select box, or entering a query in a text field), follow these steps: 1. Listen to the `changed`, `formField.blur`, or `formField.search` event. 2. Gather the current form state from the `changed` event, or call the `submit` function to retrieve the data. 3. Find the query term in the changed state that is relevant for your API calls. 4. Run your API call, e.g. fetch records based on the query term. 5. Re-import the form schema but with the updated data (the current form state you obtained earlier, merged with the API results). Don't forget to block the UI, e.g. using a loading spinner. ## Load data on runtime with a custom component A convenient way to provide realtime data fetching capabilities to your form designers is to design a custom component. For example, you can create a searcheable select that allows users to search and select a record from a CRM system. With custom components, you can create any logic for data retrieval without limitations. You could consider writing your own backend (micro-)services coupled to your components, and let the components communicate with these services to fetch domain-specifc or internal data in a secure fashion. Learn how to develop a custom component in the [custom component guide](./02-custom-components.md). :::note Custom components currently cannot be imported into [Camunda Hub](/components/hub/workspace/modeler/index.md) or [Desktop Modeler](/components/modeler/desktop-modeler/index.md). If you use custom components, you need to host the form editor yourself. ::: --- ## CamundaClient(Api-reference) :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: CamundaClient is the ergonomic entry point to the Camunda 8 Orchestration Cluster API. It wraps the generated REST client with configuration, authentication, adaptive backpressure, and transient retry. Its per-operation methods are generated in facade_generated.go. `CamundaClient` exposes **252** methods covering the full Orchestration Cluster REST API surface, with authentication, retries, and backpressure applied automatically. ```go ``` ## Constructors ### New ```go func New(opts ...Option) (*CamundaClient, error) ``` New resolves configuration from environment variables and options, then builds a ready-to-use client. Options take precedence over the environment. ## Methods | Method | Description | | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [`ActivateAdHocSubProcessActivities`](#activateadhocsubprocessactivities) | ActivateAdHocSubProcessActivities calls the ActivateAdHocSubProcessActivities operation. | | [`ActivateJobs`](#activatejobs) | ActivateJobs calls the ActivateJobs operation. | | [`AssignClientToGroup`](#assignclienttogroup) | AssignClientToGroup calls the AssignClientToGroup operation. | | [`AssignClientToTenant`](#assignclienttotenant) | AssignClientToTenant calls the AssignClientToTenant operation. | | [`AssignGroupToTenant`](#assigngrouptotenant) | AssignGroupToTenant calls the AssignGroupToTenant operation. | | [`AssignMappingRuleToGroup`](#assignmappingruletogroup) | AssignMappingRuleToGroup calls the AssignMappingRuleToGroup operation. | | [`AssignMappingRuleToTenant`](#assignmappingruletotenant) | AssignMappingRuleToTenant calls the AssignMappingRuleToTenant operation. | | [`AssignProcessInstanceBusinessId`](#assignprocessinstancebusinessid) | AssignProcessInstanceBusinessId calls the AssignProcessInstanceBusinessId operation. | | [`AssignRoleToClient`](#assignroletoclient) | AssignRoleToClient calls the AssignRoleToClient operation. | | [`AssignRoleToGroup`](#assignroletogroup) | AssignRoleToGroup calls the AssignRoleToGroup operation. | | [`AssignRoleToMappingRule`](#assignroletomappingrule) | AssignRoleToMappingRule calls the AssignRoleToMappingRule operation. | | [`AssignRoleToTenant`](#assignroletotenant) | AssignRoleToTenant calls the AssignRoleToTenant operation. | | [`AssignRoleToUser`](#assignroletouser) | AssignRoleToUser calls the AssignRoleToUser operation. | | [`AssignUserTask`](#assignusertask) | AssignUserTask calls the AssignUserTask operation. | | [`AssignUserToGroup`](#assignusertogroup) | AssignUserToGroup calls the AssignUserToGroup operation. | | [`AssignUserToTenant`](#assignusertotenant) | AssignUserToTenant calls the AssignUserToTenant operation. | | [`BroadcastSignal`](#broadcastsignal) | BroadcastSignal calls the BroadcastSignal operation. | | [`CancelBatchOperation`](#cancelbatchoperation) | CancelBatchOperation calls the CancelBatchOperation operation. | | [`CancelClusterRebalance`](#cancelclusterrebalance) | CancelClusterRebalance calls the CancelClusterRebalance operation. | | [`CancelProcessInstance`](#cancelprocessinstance) | CancelProcessInstance calls the CancelProcessInstance operation. | | [`CancelProcessInstancesBatchOperation`](#cancelprocessinstancesbatchoperation) | CancelProcessInstancesBatchOperation calls the CancelProcessInstancesBatchOperation operation. | | [`ChangeClusterMode`](#changeclustermode) | ChangeClusterMode calls the ChangeClusterMode operation. | | [`ChangeClusterModeAsClusterAdmin`](#changeclustermodeasclusteradmin) | ChangeClusterModeAsClusterAdmin calls the ChangeClusterModeAsClusterAdmin operation. | | [`Clock`](#clock) | Clock returns the clock this client resolves cadence through. | | [`CompleteJob`](#completejob) | CompleteJob calls the CompleteJob operation. | | [`CompleteUserTask`](#completeusertask) | CompleteUserTask calls the CompleteUserTask operation. | | [`Config`](#config) | Config returns the resolved configuration. | | [`CorrelateMessage`](#correlatemessage) | CorrelateMessage calls the CorrelateMessage operation. | | [`CreateAdminUser`](#createadminuser) | CreateAdminUser calls the CreateAdminUser operation. | | [`CreateAgentInstance`](#createagentinstance) | CreateAgentInstance calls the CreateAgentInstance operation. | | [`CreateAuthorization`](#createauthorization) | CreateAuthorization calls the CreateAuthorization operation. | | [`CreateDeployment`](#createdeployment) | CreateDeployment calls the CreateDeployment operation. | | [`CreateDocument`](#createdocument) | CreateDocument calls the CreateDocument operation. | | [`CreateDocumentLink`](#createdocumentlink) | CreateDocumentLink calls the CreateDocumentLink operation. | | [`CreateDocuments`](#createdocuments) | CreateDocuments calls the CreateDocuments operation. | | [`CreateElementInstanceVariables`](#createelementinstancevariables) | CreateElementInstanceVariables calls the CreateElementInstanceVariables operation. | | [`CreateGlobalClusterVariable`](#createglobalclustervariable) | CreateGlobalClusterVariable calls the CreateGlobalClusterVariable operation. | | [`CreateGlobalTaskListener`](#createglobaltasklistener) | CreateGlobalTaskListener calls the CreateGlobalTaskListener operation. | | [`CreateGroup`](#creategroup) | CreateGroup calls the CreateGroup operation. | | [`CreateMappingRule`](#createmappingrule) | CreateMappingRule calls the CreateMappingRule operation. | | [`CreateProcessInstance`](#createprocessinstance) | CreateProcessInstance creates (starts) a process instance. | | [`CreateRole`](#createrole) | CreateRole calls the CreateRole operation. | | [`CreateTenant`](#createtenant) | CreateTenant calls the CreateTenant operation. | | [`CreateTenantClusterVariable`](#createtenantclustervariable) | CreateTenantClusterVariable calls the CreateTenantClusterVariable operation. | | [`CreateUser`](#createuser) | CreateUser calls the CreateUser operation. | | [`DeleteAuthorization`](#deleteauthorization) | DeleteAuthorization calls the DeleteAuthorization operation. | | [`DeleteDecisionInstance`](#deletedecisioninstance) | DeleteDecisionInstance calls the DeleteDecisionInstance operation. | | [`DeleteDecisionInstancesBatchOperation`](#deletedecisioninstancesbatchoperation) | DeleteDecisionInstancesBatchOperation calls the DeleteDecisionInstancesBatchOperation operation. | | [`DeleteDocument`](#deletedocument) | DeleteDocument calls the DeleteDocument operation. | | [`DeleteGlobalClusterVariable`](#deleteglobalclustervariable) | DeleteGlobalClusterVariable calls the DeleteGlobalClusterVariable operation. | | [`DeleteGlobalTaskListener`](#deleteglobaltasklistener) | DeleteGlobalTaskListener calls the DeleteGlobalTaskListener operation. | | [`DeleteGroup`](#deletegroup) | DeleteGroup calls the DeleteGroup operation. | | [`DeleteHistoryBackup`](#deletehistorybackup) | DeleteHistoryBackup calls the DeleteHistoryBackup operation. | | [`DeleteHistoryBackupAsClusterAdmin`](#deletehistorybackupasclusteradmin) | DeleteHistoryBackupAsClusterAdmin calls the DeleteHistoryBackupAsClusterAdmin operation. | | [`DeleteMappingRule`](#deletemappingrule) | DeleteMappingRule calls the DeleteMappingRule operation. | | [`DeleteProcessInstance`](#deleteprocessinstance) | DeleteProcessInstance calls the DeleteProcessInstance operation. | | [`DeleteProcessInstancesBatchOperation`](#deleteprocessinstancesbatchoperation) | DeleteProcessInstancesBatchOperation calls the DeleteProcessInstancesBatchOperation operation. | | [`DeleteResource`](#deleteresource) | DeleteResource calls the DeleteResource operation. | | [`DeleteRole`](#deleterole) | DeleteRole calls the DeleteRole operation. | | [`DeleteRuntimeBackup`](#deleteruntimebackup) | DeleteRuntimeBackup calls the DeleteRuntimeBackup operation. | | [`DeleteRuntimeBackupAsClusterAdmin`](#deleteruntimebackupasclusteradmin) | DeleteRuntimeBackupAsClusterAdmin calls the DeleteRuntimeBackupAsClusterAdmin operation. | | [`DeleteRuntimeBackupState`](#deleteruntimebackupstate) | DeleteRuntimeBackupState calls the DeleteRuntimeBackupState operation. | | [`DeleteRuntimeBackupStateAsClusterAdmin`](#deleteruntimebackupstateasclusteradmin) | DeleteRuntimeBackupStateAsClusterAdmin calls the DeleteRuntimeBackupStateAsClusterAdmin operation. | | [`DeleteTenant`](#deletetenant) | DeleteTenant calls the DeleteTenant operation. | | [`DeleteTenantClusterVariable`](#deletetenantclustervariable) | DeleteTenantClusterVariable calls the DeleteTenantClusterVariable operation. | | [`DeleteUser`](#deleteuser) | DeleteUser calls the DeleteUser operation. | | [`EvaluateConditionals`](#evaluateconditionals) | EvaluateConditionals calls the EvaluateConditionals operation. | | [`EvaluateDecision`](#evaluatedecision) | EvaluateDecision calls the EvaluateDecision operation. | | [`EvaluateExpression`](#evaluateexpression) | EvaluateExpression calls the EvaluateExpression operation. | | [`FailJob`](#failjob) | FailJob calls the FailJob operation. | | [`GetAgentDefinition`](#getagentdefinition) | GetAgentDefinition calls the GetAgentDefinition operation. | | [`GetAgentInstance`](#getagentinstance) | GetAgentInstance calls the GetAgentInstance operation. | | [`GetAuditLog`](#getauditlog) | GetAuditLog calls the GetAuditLog operation. | | [`GetAuthentication`](#getauthentication) | GetAuthentication calls the GetAuthentication operation. | | [`GetAuthorization`](#getauthorization) | GetAuthorization calls the GetAuthorization operation. | | [`GetBatchOperation`](#getbatchoperation) | GetBatchOperation calls the GetBatchOperation operation. | | [`GetClusterExportingStatus`](#getclusterexportingstatus) | GetClusterExportingStatus calls the GetClusterExportingStatus operation. | | [`GetClusterRebalance`](#getclusterrebalance) | GetClusterRebalance calls the GetClusterRebalance operation. | | [`GetClusterStatus`](#getclusterstatus) | GetClusterStatus calls the GetClusterStatus operation. | | [`GetClusterTopology`](#getclustertopology) | GetClusterTopology calls the GetClusterTopology operation. | | [`GetClusterUpgradeStatus`](#getclusterupgradestatus) | GetClusterUpgradeStatus calls the GetClusterUpgradeStatus operation. | | [`GetDecisionDefinition`](#getdecisiondefinition) | GetDecisionDefinition calls the GetDecisionDefinition operation. | | [`GetDecisionDefinitionXML`](#getdecisiondefinitionxml) | GetDecisionDefinitionXML calls the GetDecisionDefinitionXML operation. | | [`GetDecisionInstance`](#getdecisioninstance) | GetDecisionInstance calls the GetDecisionInstance operation. | | [`GetDecisionRequirements`](#getdecisionrequirements) | GetDecisionRequirements calls the GetDecisionRequirements operation. | | [`GetDecisionRequirementsXML`](#getdecisionrequirementsxml) | GetDecisionRequirementsXML calls the GetDecisionRequirementsXML operation. | | [`GetDocument`](#getdocument) | GetDocument calls the GetDocument operation. | | [`GetElementInstance`](#getelementinstance) | GetElementInstance calls the GetElementInstance operation. | | [`GetExportingStatus`](#getexportingstatus) | GetExportingStatus calls the GetExportingStatus operation. | | [`GetFormByKey`](#getformbykey) | GetFormByKey calls the GetFormByKey operation. | | [`GetGlobalClusterVariable`](#getglobalclustervariable) | GetGlobalClusterVariable calls the GetGlobalClusterVariable operation. | | [`GetGlobalJobStatistics`](#getglobaljobstatistics) | GetGlobalJobStatistics calls the GetGlobalJobStatistics operation. | | [`GetGlobalTaskListener`](#getglobaltasklistener) | GetGlobalTaskListener calls the GetGlobalTaskListener operation. | | [`GetGroup`](#getgroup) | GetGroup calls the GetGroup operation. | | [`GetHistoryBackup`](#gethistorybackup) | GetHistoryBackup calls the GetHistoryBackup operation. | | [`GetHistoryBackupAsClusterAdmin`](#gethistorybackupasclusteradmin) | GetHistoryBackupAsClusterAdmin calls the GetHistoryBackupAsClusterAdmin operation. | | [`GetIncident`](#getincident) | GetIncident calls the GetIncident operation. | | [`GetJobErrorStatistics`](#getjoberrorstatistics) | GetJobErrorStatistics calls the GetJobErrorStatistics operation. | | [`GetJobTimeSeriesStatistics`](#getjobtimeseriesstatistics) | GetJobTimeSeriesStatistics calls the GetJobTimeSeriesStatistics operation. | | [`GetJobTypeStatistics`](#getjobtypestatistics) | GetJobTypeStatistics calls the GetJobTypeStatistics operation. | | [`GetJobWorkerStatistics`](#getjobworkerstatistics) | GetJobWorkerStatistics calls the GetJobWorkerStatistics operation. | | [`GetLicense`](#getlicense) | GetLicense calls the GetLicense operation. | | [`GetMappingRule`](#getmappingrule) | GetMappingRule calls the GetMappingRule operation. | | [`GetProcessDefinition`](#getprocessdefinition) | GetProcessDefinition calls the GetProcessDefinition operation. | | [`GetProcessDefinitionInstanceStatistics`](#getprocessdefinitioninstancestatistics) | GetProcessDefinitionInstanceStatistics calls the GetProcessDefinitionInstanceStatistics operation. | | [`GetProcessDefinitionInstanceVersionStatistics`](#getprocessdefinitioninstanceversionstatistics) | GetProcessDefinitionInstanceVersionStatistics calls the GetProcessDefinitionInstanceVersionStatistics operation. | | [`GetProcessDefinitionMessageSubscriptionStatistics`](#getprocessdefinitionmessagesubscriptionstatistics) | GetProcessDefinitionMessageSubscriptionStatistics calls the GetProcessDefinitionMessageSubscriptionStatistics operation. | | [`GetProcessDefinitionStatistics`](#getprocessdefinitionstatistics) | GetProcessDefinitionStatistics calls the GetProcessDefinitionStatistics operation. | | [`GetProcessDefinitionXML`](#getprocessdefinitionxml) | GetProcessDefinitionXML calls the GetProcessDefinitionXML operation. | | [`GetProcessInstance`](#getprocessinstance) | GetProcessInstance calls the GetProcessInstance operation. | | [`GetProcessInstanceCallHierarchy`](#getprocessinstancecallhierarchy) | GetProcessInstanceCallHierarchy calls the GetProcessInstanceCallHierarchy operation. | | [`GetProcessInstanceSequenceFlows`](#getprocessinstancesequenceflows) | GetProcessInstanceSequenceFlows calls the GetProcessInstanceSequenceFlows operation. | | [`GetProcessInstanceStatistics`](#getprocessinstancestatistics) | GetProcessInstanceStatistics calls the GetProcessInstanceStatistics operation. | | [`GetProcessInstanceStatisticsByDefinition`](#getprocessinstancestatisticsbydefinition) | GetProcessInstanceStatisticsByDefinition calls the GetProcessInstanceStatisticsByDefinition operation. | | [`GetProcessInstanceStatisticsByError`](#getprocessinstancestatisticsbyerror) | GetProcessInstanceStatisticsByError calls the GetProcessInstanceStatisticsByError operation. | | [`GetProcessInstanceWaitStateStatistics`](#getprocessinstancewaitstatestatistics) | GetProcessInstanceWaitStateStatistics calls the GetProcessInstanceWaitStateStatistics operation. | | [`GetResource`](#getresource) | GetResource calls the GetResource operation. | | [`GetResourceContent`](#getresourcecontent) | GetResourceContent calls the GetResourceContent operation. | | [`GetResourceContentBinary`](#getresourcecontentbinary) | GetResourceContentBinary calls the GetResourceContentBinary operation. | | [`GetRestoreStatus`](#getrestorestatus) | GetRestoreStatus calls the GetRestoreStatus operation. | | [`GetRole`](#getrole) | GetRole calls the GetRole operation. | | [`GetRuntimeBackup`](#getruntimebackup) | GetRuntimeBackup calls the GetRuntimeBackup operation. | | [`GetRuntimeBackupAsClusterAdmin`](#getruntimebackupasclusteradmin) | GetRuntimeBackupAsClusterAdmin calls the GetRuntimeBackupAsClusterAdmin operation. | | [`GetRuntimeBackupState`](#getruntimebackupstate) | GetRuntimeBackupState calls the GetRuntimeBackupState operation. | | [`GetRuntimeBackupStateAsClusterAdmin`](#getruntimebackupstateasclusteradmin) | GetRuntimeBackupStateAsClusterAdmin calls the GetRuntimeBackupStateAsClusterAdmin operation. | | [`GetStartProcessForm`](#getstartprocessform) | GetStartProcessForm calls the GetStartProcessForm operation. | | [`GetStatus`](#getstatus) | GetStatus calls the GetStatus operation. | | [`GetSystemConfiguration`](#getsystemconfiguration) | GetSystemConfiguration calls the GetSystemConfiguration operation. | | [`GetTenant`](#gettenant) | GetTenant calls the GetTenant operation. | | [`GetTenantClusterVariable`](#gettenantclustervariable) | GetTenantClusterVariable calls the GetTenantClusterVariable operation. | | [`GetTopology`](#gettopology) | GetTopology calls the GetTopology operation. | | [`GetUsageMetrics`](#getusagemetrics) | GetUsageMetrics calls the GetUsageMetrics operation. | | [`GetUser`](#getuser) | GetUser calls the GetUser operation. | | [`GetUserTask`](#getusertask) | GetUserTask calls the GetUserTask operation. | | [`GetUserTaskForm`](#getusertaskform) | GetUserTaskForm calls the GetUserTaskForm operation. | | [`GetVariable`](#getvariable) | GetVariable calls the GetVariable operation. | | [`ListHistoryBackups`](#listhistorybackups) | ListHistoryBackups calls the ListHistoryBackups operation. | | [`ListHistoryBackupsAsClusterAdmin`](#listhistorybackupsasclusteradmin) | ListHistoryBackupsAsClusterAdmin calls the ListHistoryBackupsAsClusterAdmin operation. | | [`ListRuntimeBackups`](#listruntimebackups) | ListRuntimeBackups calls the ListRuntimeBackups operation. | | [`ListRuntimeBackupsAsClusterAdmin`](#listruntimebackupsasclusteradmin) | ListRuntimeBackupsAsClusterAdmin calls the ListRuntimeBackupsAsClusterAdmin operation. | | [`ListSecrets`](#listsecrets) | ListSecrets calls the ListSecrets operation. | | [`MigrateProcessInstance`](#migrateprocessinstance) | MigrateProcessInstance calls the MigrateProcessInstance operation. | | [`MigrateProcessInstancesBatchOperation`](#migrateprocessinstancesbatchoperation) | MigrateProcessInstancesBatchOperation calls the MigrateProcessInstancesBatchOperation operation. | | [`ModifyProcessInstance`](#modifyprocessinstance) | ModifyProcessInstance calls the ModifyProcessInstance operation. | | [`ModifyProcessInstancesBatchOperation`](#modifyprocessinstancesbatchoperation) | ModifyProcessInstancesBatchOperation calls the ModifyProcessInstancesBatchOperation operation. | | [`NewJobWorker`](#newjobworker) | NewJobWorker creates a worker for jobType. Defaults are seeded from the client's CAMUNDA_WORKER_* configuration and can be overridden with options. | | [`NewStreamJobWorker`](#newstreamjobworker) | NewStreamJobWorker creates a gRPC streaming worker for jobType. Defaults are seeded from the client's CAMUNDA_WORKER_* configuration and can be overridden with options. | | [`PauseClusterExporting`](#pauseclusterexporting) | PauseClusterExporting calls the PauseClusterExporting operation. | | [`PauseExporting`](#pauseexporting) | PauseExporting calls the PauseExporting operation. | | [`PinAt`](#pinat) | PinAt moves the engine clock to t. | | [`PinClock`](#pinclock) | PinClock calls the PinClock operation. | | [`PublishMessage`](#publishmessage) | PublishMessage calls the PublishMessage operation. | | [`Random`](#random) | Random returns the source this client draws jitter from. | | [`Raw`](#raw) | Raw returns the underlying generated client for operations or options not yet surfaced on the ergonomic facade. | | [`ResetClock`](#resetclock) | ResetClock calls the ResetClock operation. | | [`ResetToLive`](#resettolive) | ResetToLive returns the engine clock to real time. | | [`ResolveIncident`](#resolveincident) | ResolveIncident calls the ResolveIncident operation. | | [`ResolveIncidentsBatchOperation`](#resolveincidentsbatchoperation) | ResolveIncidentsBatchOperation calls the ResolveIncidentsBatchOperation operation. | | [`ResolveProcessInstanceIncidents`](#resolveprocessinstanceincidents) | ResolveProcessInstanceIncidents calls the ResolveProcessInstanceIncidents operation. | | [`ResolveSecrets`](#resolvesecrets) | ResolveSecrets calls the ResolveSecrets operation. | | [`Restore`](#restore) | Restore calls the Restore operation. | | [`RestoreAsClusterAdmin`](#restoreasclusteradmin) | RestoreAsClusterAdmin calls the RestoreAsClusterAdmin operation. | | [`ResumeBatchOperation`](#resumebatchoperation) | ResumeBatchOperation calls the ResumeBatchOperation operation. | | [`ResumeClusterExporting`](#resumeclusterexporting) | ResumeClusterExporting calls the ResumeClusterExporting operation. | | [`ResumeExporting`](#resumeexporting) | ResumeExporting calls the ResumeExporting operation. | | [`ResumeProcessInstance`](#resumeprocessinstance) | ResumeProcessInstance calls the ResumeProcessInstance operation. | | [`ResumeProcessInstancesBatchOperation`](#resumeprocessinstancesbatchoperation) | ResumeProcessInstancesBatchOperation calls the ResumeProcessInstancesBatchOperation operation. | | [`SearchAgentDefinitions`](#searchagentdefinitions) | SearchAgentDefinitions calls the SearchAgentDefinitions operation. | | [`SearchAgentInstanceHistory`](#searchagentinstancehistory) | SearchAgentInstanceHistory calls the SearchAgentInstanceHistory operation. | | [`SearchAgentInstances`](#searchagentinstances) | SearchAgentInstances calls the SearchAgentInstances operation. | | [`SearchAuditLogs`](#searchauditlogs) | SearchAuditLogs calls the SearchAuditLogs operation. | | [`SearchAuthorizations`](#searchauthorizations) | SearchAuthorizations calls the SearchAuthorizations operation. | | [`SearchBatchOperationItems`](#searchbatchoperationitems) | SearchBatchOperationItems calls the SearchBatchOperationItems operation. | | [`SearchBatchOperations`](#searchbatchoperations) | SearchBatchOperations calls the SearchBatchOperations operation. | | [`SearchClientsForGroup`](#searchclientsforgroup) | SearchClientsForGroup calls the SearchClientsForGroup operation. | | [`SearchClientsForRole`](#searchclientsforrole) | SearchClientsForRole calls the SearchClientsForRole operation. | | [`SearchClientsForTenant`](#searchclientsfortenant) | SearchClientsForTenant calls the SearchClientsForTenant operation. | | [`SearchClusterVariables`](#searchclustervariables) | SearchClusterVariables calls the SearchClusterVariables operation. | | [`SearchCorrelatedMessageSubscriptions`](#searchcorrelatedmessagesubscriptions) | SearchCorrelatedMessageSubscriptions calls the SearchCorrelatedMessageSubscriptions operation. | | [`SearchDecisionDefinitions`](#searchdecisiondefinitions) | SearchDecisionDefinitions calls the SearchDecisionDefinitions operation. | | [`SearchDecisionInstances`](#searchdecisioninstances) | SearchDecisionInstances calls the SearchDecisionInstances operation. | | [`SearchDecisionRequirements`](#searchdecisionrequirements) | SearchDecisionRequirements calls the SearchDecisionRequirements operation. | | [`SearchElementInstanceIncidents`](#searchelementinstanceincidents) | SearchElementInstanceIncidents calls the SearchElementInstanceIncidents operation. | | [`SearchElementInstanceWaitStates`](#searchelementinstancewaitstates) | SearchElementInstanceWaitStates calls the SearchElementInstanceWaitStates operation. | | [`SearchElementInstances`](#searchelementinstances) | SearchElementInstances calls the SearchElementInstances operation. | | [`SearchGlobalTaskListeners`](#searchglobaltasklisteners) | SearchGlobalTaskListeners calls the SearchGlobalTaskListeners operation. | | [`SearchGroupIdsForTenant`](#searchgroupidsfortenant) | SearchGroupIdsForTenant calls the SearchGroupIdsForTenant operation. | | [`SearchGroups`](#searchgroups) | SearchGroups calls the SearchGroups operation. | | [`SearchGroupsForRole`](#searchgroupsforrole) | SearchGroupsForRole calls the SearchGroupsForRole operation. | | [`SearchIncidents`](#searchincidents) | SearchIncidents calls the SearchIncidents operation. | | [`SearchJobs`](#searchjobs) | SearchJobs calls the SearchJobs operation. | | [`SearchMappingRule`](#searchmappingrule) | SearchMappingRule calls the SearchMappingRule operation. | | [`SearchMappingRulesForGroup`](#searchmappingrulesforgroup) | SearchMappingRulesForGroup calls the SearchMappingRulesForGroup operation. | | [`SearchMappingRulesForRole`](#searchmappingrulesforrole) | SearchMappingRulesForRole calls the SearchMappingRulesForRole operation. | | [`SearchMappingRulesForTenant`](#searchmappingrulesfortenant) | SearchMappingRulesForTenant calls the SearchMappingRulesForTenant operation. | | [`SearchMessageSubscriptions`](#searchmessagesubscriptions) | SearchMessageSubscriptions calls the SearchMessageSubscriptions operation. | | [`SearchOwnAuthorizations`](#searchownauthorizations) | SearchOwnAuthorizations calls the SearchOwnAuthorizations operation. | | [`SearchProcessDefinitionVariableNames`](#searchprocessdefinitionvariablenames) | SearchProcessDefinitionVariableNames calls the SearchProcessDefinitionVariableNames operation. | | [`SearchProcessDefinitions`](#searchprocessdefinitions) | SearchProcessDefinitions calls the SearchProcessDefinitions operation. | | [`SearchProcessInstanceIncidents`](#searchprocessinstanceincidents) | SearchProcessInstanceIncidents calls the SearchProcessInstanceIncidents operation. | | [`SearchProcessInstances`](#searchprocessinstances) | SearchProcessInstances calls the SearchProcessInstances operation. | | [`SearchResources`](#searchresources) | SearchResources calls the SearchResources operation. | | [`SearchRoles`](#searchroles) | SearchRoles calls the SearchRoles operation. | | [`SearchRolesForGroup`](#searchrolesforgroup) | SearchRolesForGroup calls the SearchRolesForGroup operation. | | [`SearchRolesForTenant`](#searchrolesfortenant) | SearchRolesForTenant calls the SearchRolesForTenant operation. | | [`SearchTenants`](#searchtenants) | SearchTenants calls the SearchTenants operation. | | [`SearchUserTaskAuditLogs`](#searchusertaskauditlogs) | SearchUserTaskAuditLogs calls the SearchUserTaskAuditLogs operation. | | [`SearchUserTaskEffectiveVariables`](#searchusertaskeffectivevariables) | SearchUserTaskEffectiveVariables calls the SearchUserTaskEffectiveVariables operation. | | [`SearchUserTaskVariables`](#searchusertaskvariables) | SearchUserTaskVariables calls the SearchUserTaskVariables operation. | | [`SearchUserTasks`](#searchusertasks) | SearchUserTasks calls the SearchUserTasks operation. | | [`SearchUsers`](#searchusers) | SearchUsers calls the SearchUsers operation. | | [`SearchUsersForGroup`](#searchusersforgroup) | SearchUsersForGroup calls the SearchUsersForGroup operation. | | [`SearchUsersForRole`](#searchusersforrole) | SearchUsersForRole calls the SearchUsersForRole operation. | | [`SearchUsersForTenant`](#searchusersfortenant) | SearchUsersForTenant calls the SearchUsersForTenant operation. | | [`SearchVariables`](#searchvariables) | SearchVariables calls the SearchVariables operation. | | [`SuspendBatchOperation`](#suspendbatchoperation) | SuspendBatchOperation calls the SuspendBatchOperation operation. | | [`SuspendProcessInstance`](#suspendprocessinstance) | SuspendProcessInstance calls the SuspendProcessInstance operation. | | [`SuspendProcessInstancesBatchOperation`](#suspendprocessinstancesbatchoperation) | SuspendProcessInstancesBatchOperation calls the SuspendProcessInstancesBatchOperation operation. | | [`SyncRuntimeBackupState`](#syncruntimebackupstate) | SyncRuntimeBackupState calls the SyncRuntimeBackupState operation. | | [`SyncRuntimeBackupStateAsClusterAdmin`](#syncruntimebackupstateasclusteradmin) | SyncRuntimeBackupStateAsClusterAdmin calls the SyncRuntimeBackupStateAsClusterAdmin operation. | | [`TakeHistoryBackup`](#takehistorybackup) | TakeHistoryBackup calls the TakeHistoryBackup operation. | | [`TakeHistoryBackupAsClusterAdmin`](#takehistorybackupasclusteradmin) | TakeHistoryBackupAsClusterAdmin calls the TakeHistoryBackupAsClusterAdmin operation. | | [`TakeRuntimeBackup`](#takeruntimebackup) | TakeRuntimeBackup calls the TakeRuntimeBackup operation. | | [`TakeRuntimeBackupAsClusterAdmin`](#takeruntimebackupasclusteradmin) | TakeRuntimeBackupAsClusterAdmin calls the TakeRuntimeBackupAsClusterAdmin operation. | | [`ThrowJobError`](#throwjoberror) | ThrowJobError calls the ThrowJobError operation. | | [`TriggerClusterRebalance`](#triggerclusterrebalance) | TriggerClusterRebalance calls the TriggerClusterRebalance operation. | | [`UnassignClientFromGroup`](#unassignclientfromgroup) | UnassignClientFromGroup calls the UnassignClientFromGroup operation. | | [`UnassignClientFromTenant`](#unassignclientfromtenant) | UnassignClientFromTenant calls the UnassignClientFromTenant operation. | | [`UnassignGroupFromTenant`](#unassigngroupfromtenant) | UnassignGroupFromTenant calls the UnassignGroupFromTenant operation. | | [`UnassignMappingRuleFromGroup`](#unassignmappingrulefromgroup) | UnassignMappingRuleFromGroup calls the UnassignMappingRuleFromGroup operation. | | [`UnassignMappingRuleFromTenant`](#unassignmappingrulefromtenant) | UnassignMappingRuleFromTenant calls the UnassignMappingRuleFromTenant operation. | | [`UnassignRoleFromClient`](#unassignrolefromclient) | UnassignRoleFromClient calls the UnassignRoleFromClient operation. | | [`UnassignRoleFromGroup`](#unassignrolefromgroup) | UnassignRoleFromGroup calls the UnassignRoleFromGroup operation. | | [`UnassignRoleFromMappingRule`](#unassignrolefrommappingrule) | UnassignRoleFromMappingRule calls the UnassignRoleFromMappingRule operation. | | [`UnassignRoleFromTenant`](#unassignrolefromtenant) | UnassignRoleFromTenant calls the UnassignRoleFromTenant operation. | | [`UnassignRoleFromUser`](#unassignrolefromuser) | UnassignRoleFromUser calls the UnassignRoleFromUser operation. | | [`UnassignUserFromGroup`](#unassignuserfromgroup) | UnassignUserFromGroup calls the UnassignUserFromGroup operation. | | [`UnassignUserFromTenant`](#unassignuserfromtenant) | UnassignUserFromTenant calls the UnassignUserFromTenant operation. | | [`UnassignUserTask`](#unassignusertask) | UnassignUserTask calls the UnassignUserTask operation. | | [`UpdateAgentInstance`](#updateagentinstance) | UpdateAgentInstance calls the UpdateAgentInstance operation. | | [`UpdateAuthorization`](#updateauthorization) | UpdateAuthorization calls the UpdateAuthorization operation. | | [`UpdateGlobalClusterVariable`](#updateglobalclustervariable) | UpdateGlobalClusterVariable calls the UpdateGlobalClusterVariable operation. | | [`UpdateGlobalTaskListener`](#updateglobaltasklistener) | UpdateGlobalTaskListener calls the UpdateGlobalTaskListener operation. | | [`UpdateGroup`](#updategroup) | UpdateGroup calls the UpdateGroup operation. | | [`UpdateJob`](#updatejob) | UpdateJob calls the UpdateJob operation. | | [`UpdateJobsBatchOperation`](#updatejobsbatchoperation) | UpdateJobsBatchOperation calls the UpdateJobsBatchOperation operation. | | [`UpdateMappingRule`](#updatemappingrule) | UpdateMappingRule calls the UpdateMappingRule operation. | | [`UpdateRole`](#updaterole) | UpdateRole calls the UpdateRole operation. | | [`UpdateTenant`](#updatetenant) | UpdateTenant calls the UpdateTenant operation. | | [`UpdateTenantClusterVariable`](#updatetenantclustervariable) | UpdateTenantClusterVariable calls the UpdateTenantClusterVariable operation. | | [`UpdateUser`](#updateuser) | UpdateUser calls the UpdateUser operation. | | [`UpdateUserTask`](#updateusertask) | UpdateUserTask calls the UpdateUserTask operation. | ## Method details ### ActivateAdHocSubProcessActivities ```go func (c *CamundaClient) ActivateAdHocSubProcessActivities(ctx context.Context, adHocSubProcessInstanceKey ElementInstanceKey, body AdHocSubProcessActivateActivitiesInstruction, opts ...func(ApiActivateAdHocSubProcessActivitiesRequest) ApiActivateAdHocSubProcessActivitiesRequest) error ``` **Types:** [`ElementInstanceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ElementInstanceKey), [`AdHocSubProcessActivateActivitiesInstruction`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AdHocSubProcessActivateActivitiesInstruction), [`ApiActivateAdHocSubProcessActivitiesRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiActivateAdHocSubProcessActivitiesRequest) ActivateAdHocSubProcessActivities calls the ActivateAdHocSubProcessActivities operation. Example: ```go instruction := camunda.NewAdHocSubProcessActivateActivitiesInstruction( []camunda.AdHocSubProcessActivateActivityReference{ *camunda.NewAdHocSubProcessActivateActivityReference("review-task"), }) return client.ActivateAdHocSubProcessActivities(ctx, camunda.MustElementInstanceKey("2251799813685360"), *instruction) ``` ### ActivateJobs ```go func (c *CamundaClient) ActivateJobs(ctx context.Context, body JobActivationRequest, opts ...func(ApiActivateJobsRequest) ApiActivateJobsRequest) (*JobActivationResult, error) ``` **Types:** [`JobActivationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#JobActivationRequest), [`ApiActivateJobsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiActivateJobsRequest), [`JobActivationResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#JobActivationResult) ActivateJobs calls the ActivateJobs operation. Example: ```go // Activate up to 10 "greet" jobs with a 60s activation timeout. req := camunda.NewJobActivationRequest("greet", 60_000, 10) req.SetWorker("greet-worker") result, err := client.ActivateJobs(ctx, *req) if err != nil { return err } for _, job := range result.GetJobs() { fmt.Printf("activated job %v\n", job.GetJobKey()) } ``` ### AssignClientToGroup ```go func (c *CamundaClient) AssignClientToGroup(ctx context.Context, groupId string, clientId string, opts ...func(ApiAssignClientToGroupRequest) ApiAssignClientToGroupRequest) error ``` **Types:** [`ApiAssignClientToGroupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiAssignClientToGroupRequest) AssignClientToGroup calls the AssignClientToGroup operation. Example: ```go return client.AssignClientToGroup(ctx, "finance", "reporting-service") ``` ### AssignClientToTenant ```go func (c *CamundaClient) AssignClientToTenant(ctx context.Context, tenantId string, clientId string, opts ...func(ApiAssignClientToTenantRequest) ApiAssignClientToTenantRequest) error ``` **Types:** [`ApiAssignClientToTenantRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiAssignClientToTenantRequest) AssignClientToTenant calls the AssignClientToTenant operation. Example: ```go return client.AssignClientToTenant(ctx, "tenant-a", "reporting-service") ``` ### AssignGroupToTenant ```go func (c *CamundaClient) AssignGroupToTenant(ctx context.Context, tenantId string, groupId string, opts ...func(ApiAssignGroupToTenantRequest) ApiAssignGroupToTenantRequest) error ``` **Types:** [`ApiAssignGroupToTenantRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiAssignGroupToTenantRequest) AssignGroupToTenant calls the AssignGroupToTenant operation. Example: ```go return client.AssignGroupToTenant(ctx, "tenant-a", "finance") ``` ### AssignMappingRuleToGroup ```go func (c *CamundaClient) AssignMappingRuleToGroup(ctx context.Context, groupId string, mappingRuleId string, opts ...func(ApiAssignMappingRuleToGroupRequest) ApiAssignMappingRuleToGroupRequest) error ``` **Types:** [`ApiAssignMappingRuleToGroupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiAssignMappingRuleToGroupRequest) AssignMappingRuleToGroup calls the AssignMappingRuleToGroup operation. Example: ```go return client.AssignMappingRuleToGroup(ctx, "finance", "sso-auditors") ``` ### AssignMappingRuleToTenant ```go func (c *CamundaClient) AssignMappingRuleToTenant(ctx context.Context, tenantId string, mappingRuleId string, opts ...func(ApiAssignMappingRuleToTenantRequest) ApiAssignMappingRuleToTenantRequest) error ``` **Types:** [`ApiAssignMappingRuleToTenantRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiAssignMappingRuleToTenantRequest) AssignMappingRuleToTenant calls the AssignMappingRuleToTenant operation. Example: ```go return client.AssignMappingRuleToTenant(ctx, "tenant-a", "sso-auditors") ``` ### AssignProcessInstanceBusinessId ```go func (c *CamundaClient) AssignProcessInstanceBusinessId(ctx context.Context, processInstanceKey ProcessInstanceKey, body ProcessInstanceBusinessIdAssignmentInstruction, opts ...func(ApiAssignProcessInstanceBusinessIdRequest) ApiAssignProcessInstanceBusinessIdRequest) error ``` **Types:** [`ProcessInstanceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceKey), [`ProcessInstanceBusinessIdAssignmentInstruction`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceBusinessIdAssignmentInstruction), [`ApiAssignProcessInstanceBusinessIdRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiAssignProcessInstanceBusinessIdRequest) AssignProcessInstanceBusinessId calls the AssignProcessInstanceBusinessId operation. Example: ```go return client.AssignProcessInstanceBusinessId(ctx, camunda.MustProcessInstanceKey("2251799813685340"), *camunda.NewProcessInstanceBusinessIdAssignmentInstruction("order-42")) ``` ### AssignRoleToClient ```go func (c *CamundaClient) AssignRoleToClient(ctx context.Context, roleId string, clientId string, opts ...func(ApiAssignRoleToClientRequest) ApiAssignRoleToClientRequest) error ``` **Types:** [`ApiAssignRoleToClientRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiAssignRoleToClientRequest) AssignRoleToClient calls the AssignRoleToClient operation. Example: ```go return client.AssignRoleToClient(ctx, "auditor", "reporting-service") ``` ### AssignRoleToGroup ```go func (c *CamundaClient) AssignRoleToGroup(ctx context.Context, roleId string, groupId string, opts ...func(ApiAssignRoleToGroupRequest) ApiAssignRoleToGroupRequest) error ``` **Types:** [`ApiAssignRoleToGroupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiAssignRoleToGroupRequest) AssignRoleToGroup calls the AssignRoleToGroup operation. Example: ```go return client.AssignRoleToGroup(ctx, "auditor", "finance") ``` ### AssignRoleToMappingRule ```go func (c *CamundaClient) AssignRoleToMappingRule(ctx context.Context, roleId string, mappingRuleId string, opts ...func(ApiAssignRoleToMappingRuleRequest) ApiAssignRoleToMappingRuleRequest) error ``` **Types:** [`ApiAssignRoleToMappingRuleRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiAssignRoleToMappingRuleRequest) AssignRoleToMappingRule calls the AssignRoleToMappingRule operation. Example: ```go return client.AssignRoleToMappingRule(ctx, "auditor", "sso-auditors") ``` ### AssignRoleToTenant ```go func (c *CamundaClient) AssignRoleToTenant(ctx context.Context, tenantId string, roleId string, opts ...func(ApiAssignRoleToTenantRequest) ApiAssignRoleToTenantRequest) error ``` **Types:** [`ApiAssignRoleToTenantRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiAssignRoleToTenantRequest) AssignRoleToTenant calls the AssignRoleToTenant operation. Example: ```go return client.AssignRoleToTenant(ctx, "tenant-a", "auditor") ``` ### AssignRoleToUser ```go func (c *CamundaClient) AssignRoleToUser(ctx context.Context, roleId string, username string, opts ...func(ApiAssignRoleToUserRequest) ApiAssignRoleToUserRequest) error ``` **Types:** [`ApiAssignRoleToUserRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiAssignRoleToUserRequest) AssignRoleToUser calls the AssignRoleToUser operation. Example: ```go return client.AssignRoleToUser(ctx, "auditor", "alice") ``` ### AssignUserTask ```go func (c *CamundaClient) AssignUserTask(ctx context.Context, userTaskKey UserTaskKey, body UserTaskAssignmentRequest, opts ...func(ApiAssignUserTaskRequest) ApiAssignUserTaskRequest) error ``` **Types:** [`UserTaskKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserTaskKey), [`UserTaskAssignmentRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserTaskAssignmentRequest), [`ApiAssignUserTaskRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiAssignUserTaskRequest) AssignUserTask calls the AssignUserTask operation. Example: ```go req := camunda.NewUserTaskAssignmentRequest("alice") return client.AssignUserTask(ctx, camunda.MustUserTaskKey("2251799813685380"), *req) ``` ### AssignUserToGroup ```go func (c *CamundaClient) AssignUserToGroup(ctx context.Context, groupId string, username string, opts ...func(ApiAssignUserToGroupRequest) ApiAssignUserToGroupRequest) error ``` **Types:** [`ApiAssignUserToGroupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiAssignUserToGroupRequest) AssignUserToGroup calls the AssignUserToGroup operation. Example: ```go return client.AssignUserToGroup(ctx, "finance", "alice") ``` ### AssignUserToTenant ```go func (c *CamundaClient) AssignUserToTenant(ctx context.Context, tenantId string, username string, opts ...func(ApiAssignUserToTenantRequest) ApiAssignUserToTenantRequest) error ``` **Types:** [`ApiAssignUserToTenantRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiAssignUserToTenantRequest) AssignUserToTenant calls the AssignUserToTenant operation. Example: ```go return client.AssignUserToTenant(ctx, "tenant-a", "alice") ``` ### BroadcastSignal ```go func (c *CamundaClient) BroadcastSignal(ctx context.Context, body SignalBroadcastRequest, opts ...func(ApiBroadcastSignalRequest) ApiBroadcastSignalRequest) (*SignalBroadcastResult, error) ``` **Types:** [`SignalBroadcastRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#SignalBroadcastRequest), [`ApiBroadcastSignalRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiBroadcastSignalRequest), [`SignalBroadcastResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#SignalBroadcastResult) BroadcastSignal calls the BroadcastSignal operation. Example: ```go req := camunda.NewSignalBroadcastRequest("cancel-all-orders") req.SetVariables(map[string]any{"reason": "maintenance"}) result, err := client.BroadcastSignal(ctx, *req) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### CancelBatchOperation ```go func (c *CamundaClient) CancelBatchOperation(ctx context.Context, batchOperationKey string, opts ...func(ApiCancelBatchOperationRequest) ApiCancelBatchOperationRequest) error ``` **Types:** [`ApiCancelBatchOperationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCancelBatchOperationRequest) CancelBatchOperation calls the CancelBatchOperation operation. Example: ```go return client.CancelBatchOperation(ctx, "2251799813685290") ``` ### CancelClusterRebalance ```go func (c *CamundaClient) CancelClusterRebalance(ctx context.Context, opts ...func(ApiCancelClusterRebalanceRequest) ApiCancelClusterRebalanceRequest) (*RebalanceCancellationResponse, error) ``` **Types:** [`ApiCancelClusterRebalanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCancelClusterRebalanceRequest), [`RebalanceCancellationResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#RebalanceCancellationResponse) CancelClusterRebalance calls the CancelClusterRebalance operation. Example: ```go // Requires cluster-admin credentials (a separate cluster-admin security chain) — // calling this with standard Orchestration credentials will fail authorization. resp, err := client.CancelClusterRebalance(ctx) if err != nil { return err } if resp.GetWasRunning() { fmt.Println("rebalance cancelled") } else { fmt.Println("no rebalance was running") } ``` ### CancelProcessInstance ```go func (c *CamundaClient) CancelProcessInstance(ctx context.Context, processInstanceKey ProcessInstanceKey, body CancelProcessInstanceRequest, opts ...func(ApiCancelProcessInstanceRequest) ApiCancelProcessInstanceRequest) error ``` **Types:** [`ProcessInstanceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceKey), [`CancelProcessInstanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#CancelProcessInstanceRequest), [`ApiCancelProcessInstanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCancelProcessInstanceRequest) CancelProcessInstance calls the CancelProcessInstance operation. Example: ```go return client.CancelProcessInstance(ctx, camunda.MustProcessInstanceKey("2251799813685340"), *camunda.NewCancelProcessInstanceRequest()) ``` ### CancelProcessInstancesBatchOperation ```go func (c *CamundaClient) CancelProcessInstancesBatchOperation(ctx context.Context, body ProcessInstanceCancellationBatchOperationRequest, opts ...func(ApiCancelProcessInstancesBatchOperationRequest) ApiCancelProcessInstancesBatchOperationRequest) (*BatchOperationCreatedResult, error) ``` **Types:** [`ProcessInstanceCancellationBatchOperationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceCancellationBatchOperationRequest), [`ApiCancelProcessInstancesBatchOperationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCancelProcessInstancesBatchOperationRequest), [`BatchOperationCreatedResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#BatchOperationCreatedResult) CancelProcessInstancesBatchOperation calls the CancelProcessInstancesBatchOperation operation. Example: ```go // Cancel every instance matching a filter in a single batch operation. req := camunda.NewProcessInstanceCancellationBatchOperationRequest(*camunda.NewProcessInstanceFilter()) result, err := client.CancelProcessInstancesBatchOperation(ctx, *req) if err != nil { return err } fmt.Printf("created batch operation %v\n", result.GetBatchOperationKey()) ``` ### ChangeClusterMode ```go func (c *CamundaClient) ChangeClusterMode(ctx context.Context, opts ...func(ApiChangeClusterModeRequest) ApiChangeClusterModeRequest) (*ClusterModeChangeResponse, error) ``` **Types:** [`ApiChangeClusterModeRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiChangeClusterModeRequest), [`ClusterModeChangeResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterModeChangeResponse) ChangeClusterMode calls the ChangeClusterMode operation. Example: ```go result, err := client.ChangeClusterMode(ctx) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### ChangeClusterModeAsClusterAdmin ```go func (c *CamundaClient) ChangeClusterModeAsClusterAdmin(ctx context.Context, opts ...func(ApiChangeClusterModeAsClusterAdminRequest) ApiChangeClusterModeAsClusterAdminRequest) (*ClusterModeChangeResponse, error) ``` **Types:** [`ApiChangeClusterModeAsClusterAdminRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiChangeClusterModeAsClusterAdminRequest), [`ClusterModeChangeResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterModeChangeResponse) ChangeClusterModeAsClusterAdmin calls the ChangeClusterModeAsClusterAdmin operation. Example: ```go // Changes the cluster mode as a cluster-level admin (cross-tenant authority). result, err := client.ChangeClusterModeAsClusterAdmin(ctx, func(r camunda.ApiChangeClusterModeAsClusterAdminRequest) camunda.ApiChangeClusterModeAsClusterAdminRequest { return r.Mode(camunda.MODE_RECOVERING) }) if err != nil { return err } fmt.Printf("change %s: %d planned operation group(s)\n", result.GetChangeId(), len(result.GetPlannedChanges())) ``` ### Clock ```go func (c *CamundaClient) Clock() Clock ``` Clock returns the clock this client resolves cadence through. ### CompleteJob ```go func (c *CamundaClient) CompleteJob(ctx context.Context, jobKey JobKey, body JobCompletionRequest, opts ...func(ApiCompleteJobRequest) ApiCompleteJobRequest) error ``` **Types:** [`JobKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#JobKey), [`JobCompletionRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#JobCompletionRequest), [`ApiCompleteJobRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCompleteJobRequest) CompleteJob calls the CompleteJob operation. Example: ```go req := camunda.NewJobCompletionRequest() req.SetVariables(map[string]any{"greeting": "Hello!"}) return client.CompleteJob(ctx, camunda.MustJobKey("2251799813685424"), *req) ``` ### CompleteUserTask ```go func (c *CamundaClient) CompleteUserTask(ctx context.Context, userTaskKey UserTaskKey, body UserTaskCompletionRequest, opts ...func(ApiCompleteUserTaskRequest) ApiCompleteUserTaskRequest) error ``` **Types:** [`UserTaskKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserTaskKey), [`UserTaskCompletionRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserTaskCompletionRequest), [`ApiCompleteUserTaskRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCompleteUserTaskRequest) CompleteUserTask calls the CompleteUserTask operation. Example: ```go req := camunda.NewUserTaskCompletionRequest() req.SetVariables(map[string]any{"approved": true}) return client.CompleteUserTask(ctx, camunda.MustUserTaskKey("2251799813685380"), *req) ``` ### Config ```go func (c *CamundaClient) Config() *Config ``` Config returns the resolved configuration. ### CorrelateMessage ```go func (c *CamundaClient) CorrelateMessage(ctx context.Context, body MessageCorrelationRequest, opts ...func(ApiCorrelateMessageRequest) ApiCorrelateMessageRequest) (*MessageCorrelationResult, error) ``` **Types:** [`MessageCorrelationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#MessageCorrelationRequest), [`ApiCorrelateMessageRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCorrelateMessageRequest), [`MessageCorrelationResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#MessageCorrelationResult) CorrelateMessage calls the CorrelateMessage operation. Example: ```go req := camunda.NewMessageCorrelationRequest("order-confirmed") req.SetCorrelationKey("order-42") req.SetVariables(map[string]any{"confirmedBy": "payment-service"}) result, err := client.CorrelateMessage(ctx, *req) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### CreateAdminUser ```go func (c *CamundaClient) CreateAdminUser(ctx context.Context, body UserRequest, opts ...func(ApiCreateAdminUserRequest) ApiCreateAdminUserRequest) (*UserCreateResult, error) ``` **Types:** [`UserRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserRequest), [`ApiCreateAdminUserRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCreateAdminUserRequest), [`UserCreateResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserCreateResult) CreateAdminUser calls the CreateAdminUser operation. Example: ```go // One-time setup: create the initial administrator on a fresh cluster. // "admin-password-123" is a placeholder — don't hardcode passwords in production. result, err := client.CreateAdminUser(ctx, *camunda.NewUserRequest("admin-password-123", "admin")) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### CreateAgentInstance ```go func (c *CamundaClient) CreateAgentInstance(ctx context.Context, body AgentInstanceCreationRequest, opts ...func(ApiCreateAgentInstanceRequest) ApiCreateAgentInstanceRequest) (*AgentInstanceCreationResult, error) ``` **Types:** [`AgentInstanceCreationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AgentInstanceCreationRequest), [`ApiCreateAgentInstanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCreateAgentInstanceRequest), [`AgentInstanceCreationResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AgentInstanceCreationResult) CreateAgentInstance calls the CreateAgentInstance operation. Example: ```go systemPrompt := []camunda.AgentInstanceMessageContent{ camunda.AgentInstanceTextContentAsAgentInstanceMessageContent( camunda.NewAgentInstanceTextContent("TEXT", "You are a helpful assistant.")), } configItem := camunda.NewAgentInstanceHistoryItem( "config-1", camunda.MustLoopIterationId(1), camunda.AGENTINSTANCEHISTORYROLEENUM_CONFIGURATION, nil, time.Now()) configItem.SetModel("gpt-4o") configItem.SetProvider("openai") configItem.SetSystemPrompt(systemPrompt) req := camunda.NewAgentInstanceCreationRequest( camunda.ElementInstanceKey("2251799813685360"), // elementInstanceKey camunda.JobKey("2251799813685424"), // jobKey "lease-token", []camunda.AgentInstanceHistoryItem{*configItem}, // history ) result, err := client.CreateAgentInstance(ctx, *req) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### CreateAuthorization ```go func (c *CamundaClient) CreateAuthorization(ctx context.Context, body AuthorizationRequest, opts ...func(ApiCreateAuthorizationRequest) ApiCreateAuthorizationRequest) (*AuthorizationCreateResult, error) ``` **Types:** [`AuthorizationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AuthorizationRequest), [`ApiCreateAuthorizationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCreateAuthorizationRequest), [`AuthorizationCreateResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AuthorizationCreateResult) CreateAuthorization calls the CreateAuthorization operation. Example: ```go // AuthorizationRequest is a union; grant an id-based authorization here. grant := camunda.NewAuthorizationIdBasedRequest( "user@example.com", camunda.OWNERTYPEENUM_USER, "order-process", camunda.RESOURCETYPEENUM_PROCESS_DEFINITION, []camunda.PermissionTypeEnum{ camunda.PERMISSIONTYPEENUM_READ_PROCESS_DEFINITION, camunda.PERMISSIONTYPEENUM_CREATE_PROCESS_INSTANCE, }, ) result, err := client.CreateAuthorization(ctx, camunda.AuthorizationIdBasedRequestAsAuthorizationRequest(grant)) if err != nil { return err } fmt.Printf("created authorization %v\n", result.GetAuthorizationKey()) ``` ### CreateDeployment ```go func (c *CamundaClient) CreateDeployment(ctx context.Context, opts ...func(ApiCreateDeploymentRequest) ApiCreateDeploymentRequest) (*DeploymentResult, error) ``` **Types:** [`ApiCreateDeploymentRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCreateDeploymentRequest), [`DeploymentResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DeploymentResult) CreateDeployment calls the CreateDeployment operation. Example: ```go // Multipart resource upload is done through the Raw() generated client. f, err := os.Open("order-process.bpmn") if err != nil { return err } defer func() { _ = f.Close() }() deployment, _, err := client.Raw().ResourceAPI.CreateDeployment(ctx). Resources([]*os.File{f}). Execute() if err != nil { return err } fmt.Printf("deployment key %v\n", deployment.GetDeploymentKey()) ``` ### CreateDocument ```go func (c *CamundaClient) CreateDocument(ctx context.Context, opts ...func(ApiCreateDocumentRequest) ApiCreateDocumentRequest) (*DocumentReference, error) ``` **Types:** [`ApiCreateDocumentRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCreateDocumentRequest), [`DocumentReference`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DocumentReference) CreateDocument calls the CreateDocument operation. Example: ```go // The document payload is attached via request options (functional opts) or // the Raw() client; here we call the ergonomic facade method. ref, err := client.CreateDocument(ctx) if err != nil { return err } fmt.Printf("%v\n", ref) ``` ### CreateDocumentLink ```go func (c *CamundaClient) CreateDocumentLink(ctx context.Context, documentId string, body DocumentLinkRequest, opts ...func(ApiCreateDocumentLinkRequest) ApiCreateDocumentLinkRequest) (*DocumentLink, error) ``` **Types:** [`DocumentLinkRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DocumentLinkRequest), [`ApiCreateDocumentLinkRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCreateDocumentLinkRequest), [`DocumentLink`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DocumentLink) CreateDocumentLink calls the CreateDocumentLink operation. Example: ```go // Create a short-lived, shareable download link for a stored document. link, err := client.CreateDocumentLink(ctx, "doc-123", *camunda.NewDocumentLinkRequest()) if err != nil { return err } fmt.Printf("%v\n", link) ``` ### CreateDocuments ```go func (c *CamundaClient) CreateDocuments(ctx context.Context, opts ...func(ApiCreateDocumentsRequest) ApiCreateDocumentsRequest) (*DocumentCreationBatchResponse, error) ``` **Types:** [`ApiCreateDocumentsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCreateDocumentsRequest), [`DocumentCreationBatchResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DocumentCreationBatchResponse) CreateDocuments calls the CreateDocuments operation. Example: ```go // Batch upload multiple documents in a single multipart request. result, err := client.CreateDocuments(ctx) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### CreateElementInstanceVariables ```go func (c *CamundaClient) CreateElementInstanceVariables(ctx context.Context, elementInstanceKey ElementInstanceKey, body SetVariableRequest, opts ...func(ApiCreateElementInstanceVariablesRequest) ApiCreateElementInstanceVariablesRequest) error ``` **Types:** [`ElementInstanceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ElementInstanceKey), [`SetVariableRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#SetVariableRequest), [`ApiCreateElementInstanceVariablesRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCreateElementInstanceVariablesRequest) CreateElementInstanceVariables calls the CreateElementInstanceVariables operation. Example: ```go // Set local variables on a specific element instance scope. req := camunda.NewSetVariableRequest(map[string]any{"approved": true}) return client.CreateElementInstanceVariables(ctx, camunda.MustElementInstanceKey("2251799813685360"), *req) ``` ### CreateGlobalClusterVariable ```go func (c *CamundaClient) CreateGlobalClusterVariable(ctx context.Context, body CreateClusterVariableRequest, opts ...func(ApiCreateGlobalClusterVariableRequest) ApiCreateGlobalClusterVariableRequest) (*ClusterVariableResult, error) ``` **Types:** [`CreateClusterVariableRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#CreateClusterVariableRequest), [`ApiCreateGlobalClusterVariableRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCreateGlobalClusterVariableRequest), [`ClusterVariableResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterVariableResult) CreateGlobalClusterVariable calls the CreateGlobalClusterVariable operation. Example: ```go result, err := client.CreateGlobalClusterVariable(ctx, *camunda.NewCreateClusterVariableRequest("region", map[string]any{"value": "eu-1"})) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### CreateGlobalTaskListener ```go func (c *CamundaClient) CreateGlobalTaskListener(ctx context.Context, body CreateGlobalTaskListenerRequest, opts ...func(ApiCreateGlobalTaskListenerRequest) ApiCreateGlobalTaskListenerRequest) (*GlobalTaskListenerResult, error) ``` **Types:** [`CreateGlobalTaskListenerRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#CreateGlobalTaskListenerRequest), [`ApiCreateGlobalTaskListenerRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCreateGlobalTaskListenerRequest), [`GlobalTaskListenerResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#GlobalTaskListenerResult) CreateGlobalTaskListener calls the CreateGlobalTaskListener operation. Example: ```go result, err := client.CreateGlobalTaskListener(ctx, *camunda.NewCreateGlobalTaskListenerRequest("audit-listener")) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### CreateGroup ```go func (c *CamundaClient) CreateGroup(ctx context.Context, body GroupCreateRequest, opts ...func(ApiCreateGroupRequest) ApiCreateGroupRequest) (*GroupCreateResult, error) ``` **Types:** [`GroupCreateRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#GroupCreateRequest), [`ApiCreateGroupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCreateGroupRequest), [`GroupCreateResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#GroupCreateResult) CreateGroup calls the CreateGroup operation. Example: ```go result, err := client.CreateGroup(ctx, *camunda.NewGroupCreateRequest("finance", "Finance")) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### CreateMappingRule ```go func (c *CamundaClient) CreateMappingRule(ctx context.Context, body MappingRuleCreateRequest, opts ...func(ApiCreateMappingRuleRequest) ApiCreateMappingRuleRequest) (*MappingRuleCreateResult, error) ``` **Types:** [`MappingRuleCreateRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#MappingRuleCreateRequest), [`ApiCreateMappingRuleRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCreateMappingRuleRequest), [`MappingRuleCreateResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#MappingRuleCreateResult) CreateMappingRule calls the CreateMappingRule operation. Example: ```go // Map the IdP claim `groups=auditors` to a Camunda mapping-rule identity. result, err := client.CreateMappingRule(ctx, *camunda.NewMappingRuleCreateRequest("groups", "auditors", "SSO Auditors", "sso-auditors")) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### CreateProcessInstance ```go func (c *CamundaClient) CreateProcessInstance(ctx context.Context, body ProcessInstanceCreationInstruction, opts ...func(ApiCreateProcessInstanceRequest) ApiCreateProcessInstanceRequest) (*CreateProcessInstanceResult, error) ``` **Types:** [`ProcessInstanceCreationInstruction`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceCreationInstruction), [`ApiCreateProcessInstanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCreateProcessInstanceRequest), [`CreateProcessInstanceResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#CreateProcessInstanceResult) CreateProcessInstance creates (starts) a process instance. When the gateway advertises the FALCON command stream (a nanobpmn gateway) and FALCON is enabled, the create is routed over the credit-metered WebSocket command stream: a flood of creates queues on the client's submission-credit window instead of being shed with 503s. Against stock Camunda — or if the stream cannot be established — it falls back transparently to the REST endpoint. The variadic request-builder options apply only on the REST path. Example: ```go instruction := camunda.ProcessInstanceCreationInstructionByIdAsProcessInstanceCreationInstruction( camunda.NewProcessInstanceCreationInstructionById("order-process")) result, err := client.CreateProcessInstance(ctx, instruction) ``` ### CreateRole ```go func (c *CamundaClient) CreateRole(ctx context.Context, body RoleCreateRequest, opts ...func(ApiCreateRoleRequest) ApiCreateRoleRequest) (*RoleCreateResult, error) ``` **Types:** [`RoleCreateRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#RoleCreateRequest), [`ApiCreateRoleRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCreateRoleRequest), [`RoleCreateResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#RoleCreateResult) CreateRole calls the CreateRole operation. Example: ```go result, err := client.CreateRole(ctx, *camunda.NewRoleCreateRequest("auditor", "Auditor")) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### CreateTenant ```go func (c *CamundaClient) CreateTenant(ctx context.Context, body TenantCreateRequest, opts ...func(ApiCreateTenantRequest) ApiCreateTenantRequest) (*TenantCreateResult, error) ``` **Types:** [`TenantCreateRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TenantCreateRequest), [`ApiCreateTenantRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCreateTenantRequest), [`TenantCreateResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TenantCreateResult) CreateTenant calls the CreateTenant operation. Example: ```go result, err := client.CreateTenant(ctx, *camunda.NewTenantCreateRequest("tenant-a", "Tenant A")) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### CreateTenantClusterVariable ```go func (c *CamundaClient) CreateTenantClusterVariable(ctx context.Context, tenantId string, body CreateClusterVariableRequest, opts ...func(ApiCreateTenantClusterVariableRequest) ApiCreateTenantClusterVariableRequest) (*ClusterVariableResult, error) ``` **Types:** [`CreateClusterVariableRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#CreateClusterVariableRequest), [`ApiCreateTenantClusterVariableRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCreateTenantClusterVariableRequest), [`ClusterVariableResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterVariableResult) CreateTenantClusterVariable calls the CreateTenantClusterVariable operation. Example: ```go result, err := client.CreateTenantClusterVariable(ctx, "tenant-a", *camunda.NewCreateClusterVariableRequest("region", map[string]any{"value": "eu-1"})) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### CreateUser ```go func (c *CamundaClient) CreateUser(ctx context.Context, body UserRequest, opts ...func(ApiCreateUserRequest) ApiCreateUserRequest) (*UserCreateResult, error) ``` **Types:** [`UserRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserRequest), [`ApiCreateUserRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiCreateUserRequest), [`UserCreateResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserCreateResult) CreateUser calls the CreateUser operation. Example: ```go // "secure-password-123" is a placeholder — don't hardcode passwords in production. req := camunda.NewUserRequest("secure-password-123", "alice") req.SetName("Alice Example") req.SetEmail("alice@example.com") result, err := client.CreateUser(ctx, *req) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### DeleteAuthorization ```go func (c *CamundaClient) DeleteAuthorization(ctx context.Context, authorizationKey AuthorizationKey, opts ...func(ApiDeleteAuthorizationRequest) ApiDeleteAuthorizationRequest) error ``` **Types:** [`AuthorizationKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AuthorizationKey), [`ApiDeleteAuthorizationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiDeleteAuthorizationRequest) DeleteAuthorization calls the DeleteAuthorization operation. Example: ```go return client.DeleteAuthorization(ctx, camunda.MustAuthorizationKey("2251799813685280")) ``` ### DeleteDecisionInstance ```go func (c *CamundaClient) DeleteDecisionInstance(ctx context.Context, decisionEvaluationKey DecisionEvaluationKey, body DeleteDecisionInstanceRequest, opts ...func(ApiDeleteDecisionInstanceRequest) ApiDeleteDecisionInstanceRequest) error ``` **Types:** [`DecisionEvaluationKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DecisionEvaluationKey), [`DeleteDecisionInstanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DeleteDecisionInstanceRequest), [`ApiDeleteDecisionInstanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiDeleteDecisionInstanceRequest) DeleteDecisionInstance calls the DeleteDecisionInstance operation. Example: ```go return client.DeleteDecisionInstance(ctx, camunda.MustDecisionEvaluationKey("2251799813685310"), *camunda.NewDeleteDecisionInstanceRequest()) ``` ### DeleteDecisionInstancesBatchOperation ```go func (c *CamundaClient) DeleteDecisionInstancesBatchOperation(ctx context.Context, body DecisionInstanceDeletionBatchOperationRequest, opts ...func(ApiDeleteDecisionInstancesBatchOperationRequest) ApiDeleteDecisionInstancesBatchOperationRequest) (*BatchOperationCreatedResult, error) ``` **Types:** [`DecisionInstanceDeletionBatchOperationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DecisionInstanceDeletionBatchOperationRequest), [`ApiDeleteDecisionInstancesBatchOperationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiDeleteDecisionInstancesBatchOperationRequest), [`BatchOperationCreatedResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#BatchOperationCreatedResult) DeleteDecisionInstancesBatchOperation calls the DeleteDecisionInstancesBatchOperation operation. Example: ```go req := camunda.NewDecisionInstanceDeletionBatchOperationRequest(*camunda.NewDecisionInstanceFilter()) result, err := client.DeleteDecisionInstancesBatchOperation(ctx, *req) if err != nil { return err } fmt.Printf("created batch operation %v\n", result.GetBatchOperationKey()) ``` ### DeleteDocument ```go func (c *CamundaClient) DeleteDocument(ctx context.Context, documentId string, opts ...func(ApiDeleteDocumentRequest) ApiDeleteDocumentRequest) error ``` **Types:** [`ApiDeleteDocumentRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiDeleteDocumentRequest) DeleteDocument calls the DeleteDocument operation. Example: ```go return client.DeleteDocument(ctx, "doc-123") ``` ### DeleteGlobalClusterVariable ```go func (c *CamundaClient) DeleteGlobalClusterVariable(ctx context.Context, name string, opts ...func(ApiDeleteGlobalClusterVariableRequest) ApiDeleteGlobalClusterVariableRequest) error ``` **Types:** [`ApiDeleteGlobalClusterVariableRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiDeleteGlobalClusterVariableRequest) DeleteGlobalClusterVariable calls the DeleteGlobalClusterVariable operation. Example: ```go return client.DeleteGlobalClusterVariable(ctx, "region") ``` ### DeleteGlobalTaskListener ```go func (c *CamundaClient) DeleteGlobalTaskListener(ctx context.Context, id string, opts ...func(ApiDeleteGlobalTaskListenerRequest) ApiDeleteGlobalTaskListenerRequest) error ``` **Types:** [`ApiDeleteGlobalTaskListenerRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiDeleteGlobalTaskListenerRequest) DeleteGlobalTaskListener calls the DeleteGlobalTaskListener operation. Example: ```go return client.DeleteGlobalTaskListener(ctx, "audit-listener") ``` ### DeleteGroup ```go func (c *CamundaClient) DeleteGroup(ctx context.Context, groupId string, opts ...func(ApiDeleteGroupRequest) ApiDeleteGroupRequest) error ``` **Types:** [`ApiDeleteGroupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiDeleteGroupRequest) DeleteGroup calls the DeleteGroup operation. Example: ```go return client.DeleteGroup(ctx, "finance") ``` ### DeleteHistoryBackup ```go func (c *CamundaClient) DeleteHistoryBackup(ctx context.Context, backupId int64, opts ...func(ApiDeleteHistoryBackupRequest) ApiDeleteHistoryBackupRequest) error ``` **Types:** [`ApiDeleteHistoryBackupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiDeleteHistoryBackupRequest) DeleteHistoryBackup calls the DeleteHistoryBackup operation. Example: ```go if err := client.DeleteHistoryBackup(ctx, 42); err != nil { return err } ``` ### DeleteHistoryBackupAsClusterAdmin ```go func (c *CamundaClient) DeleteHistoryBackupAsClusterAdmin(ctx context.Context, backupId int64, opts ...func(ApiDeleteHistoryBackupAsClusterAdminRequest) ApiDeleteHistoryBackupAsClusterAdminRequest) error ``` **Types:** [`ApiDeleteHistoryBackupAsClusterAdminRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiDeleteHistoryBackupAsClusterAdminRequest) DeleteHistoryBackupAsClusterAdmin calls the DeleteHistoryBackupAsClusterAdmin operation. Example: ```go if err := client.DeleteHistoryBackupAsClusterAdmin(ctx, 42); err != nil { return err } ``` ### DeleteMappingRule ```go func (c *CamundaClient) DeleteMappingRule(ctx context.Context, mappingRuleId string, opts ...func(ApiDeleteMappingRuleRequest) ApiDeleteMappingRuleRequest) error ``` **Types:** [`ApiDeleteMappingRuleRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiDeleteMappingRuleRequest) DeleteMappingRule calls the DeleteMappingRule operation. Example: ```go return client.DeleteMappingRule(ctx, "sso-auditors") ``` ### DeleteProcessInstance ```go func (c *CamundaClient) DeleteProcessInstance(ctx context.Context, processInstanceKey ProcessInstanceKey, body DeleteProcessInstanceRequest, opts ...func(ApiDeleteProcessInstanceRequest) ApiDeleteProcessInstanceRequest) error ``` **Types:** [`ProcessInstanceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceKey), [`DeleteProcessInstanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DeleteProcessInstanceRequest), [`ApiDeleteProcessInstanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiDeleteProcessInstanceRequest) DeleteProcessInstance calls the DeleteProcessInstance operation. Example: ```go return client.DeleteProcessInstance(ctx, camunda.MustProcessInstanceKey("2251799813685340"), *camunda.NewDeleteProcessInstanceRequest()) ``` ### DeleteProcessInstancesBatchOperation ```go func (c *CamundaClient) DeleteProcessInstancesBatchOperation(ctx context.Context, body ProcessInstanceDeletionBatchOperationRequest, opts ...func(ApiDeleteProcessInstancesBatchOperationRequest) ApiDeleteProcessInstancesBatchOperationRequest) (*BatchOperationCreatedResult, error) ``` **Types:** [`ProcessInstanceDeletionBatchOperationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceDeletionBatchOperationRequest), [`ApiDeleteProcessInstancesBatchOperationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiDeleteProcessInstancesBatchOperationRequest), [`BatchOperationCreatedResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#BatchOperationCreatedResult) DeleteProcessInstancesBatchOperation calls the DeleteProcessInstancesBatchOperation operation. Example: ```go req := camunda.NewProcessInstanceDeletionBatchOperationRequest(*camunda.NewProcessInstanceFilter()) result, err := client.DeleteProcessInstancesBatchOperation(ctx, *req) if err != nil { return err } fmt.Printf("created batch operation %v\n", result.GetBatchOperationKey()) ``` ### DeleteResource ```go func (c *CamundaClient) DeleteResource(ctx context.Context, resourceKey ResourceKey, body DeleteResourceRequest, opts ...func(ApiDeleteResourceRequest) ApiDeleteResourceRequest) (*DeleteResourceResponse, error) ``` **Types:** [`ResourceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ResourceKey), [`DeleteResourceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DeleteResourceRequest), [`ApiDeleteResourceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiDeleteResourceRequest), [`DeleteResourceResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DeleteResourceResponse) DeleteResource calls the DeleteResource operation. Example: ```go result, err := client.DeleteResource(ctx, camunda.MustResourceKey("2251799813685350"), *camunda.NewDeleteResourceRequest()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### DeleteRole ```go func (c *CamundaClient) DeleteRole(ctx context.Context, roleId string, opts ...func(ApiDeleteRoleRequest) ApiDeleteRoleRequest) error ``` **Types:** [`ApiDeleteRoleRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiDeleteRoleRequest) DeleteRole calls the DeleteRole operation. Example: ```go return client.DeleteRole(ctx, "auditor") ``` ### DeleteRuntimeBackup ```go func (c *CamundaClient) DeleteRuntimeBackup(ctx context.Context, backupId int64, opts ...func(ApiDeleteRuntimeBackupRequest) ApiDeleteRuntimeBackupRequest) error ``` **Types:** [`ApiDeleteRuntimeBackupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiDeleteRuntimeBackupRequest) DeleteRuntimeBackup calls the DeleteRuntimeBackup operation. Example: ```go if err := client.DeleteRuntimeBackup(ctx, 42); err != nil { return err } ``` ### DeleteRuntimeBackupAsClusterAdmin ```go func (c *CamundaClient) DeleteRuntimeBackupAsClusterAdmin(ctx context.Context, backupId int64, opts ...func(ApiDeleteRuntimeBackupAsClusterAdminRequest) ApiDeleteRuntimeBackupAsClusterAdminRequest) error ``` **Types:** [`ApiDeleteRuntimeBackupAsClusterAdminRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiDeleteRuntimeBackupAsClusterAdminRequest) DeleteRuntimeBackupAsClusterAdmin calls the DeleteRuntimeBackupAsClusterAdmin operation. Example: ```go // Deletes the runtime backup with the given id from all physical tenants. if err := client.DeleteRuntimeBackupAsClusterAdmin(ctx, 42); err != nil { return err } ``` ### DeleteRuntimeBackupState ```go func (c *CamundaClient) DeleteRuntimeBackupState(ctx context.Context, opts ...func(ApiDeleteRuntimeBackupStateRequest) ApiDeleteRuntimeBackupStateRequest) error ``` **Types:** [`ApiDeleteRuntimeBackupStateRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiDeleteRuntimeBackupStateRequest) DeleteRuntimeBackupState calls the DeleteRuntimeBackupState operation. Example: ```go if err := client.DeleteRuntimeBackupState(ctx); err != nil { return err } ``` ### DeleteRuntimeBackupStateAsClusterAdmin ```go func (c *CamundaClient) DeleteRuntimeBackupStateAsClusterAdmin(ctx context.Context, opts ...func(ApiDeleteRuntimeBackupStateAsClusterAdminRequest) ApiDeleteRuntimeBackupStateAsClusterAdminRequest) error ``` **Types:** [`ApiDeleteRuntimeBackupStateAsClusterAdminRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiDeleteRuntimeBackupStateAsClusterAdminRequest) DeleteRuntimeBackupStateAsClusterAdmin calls the DeleteRuntimeBackupStateAsClusterAdmin operation. Example: ```go // Clears the persisted runtime backup state across all physical tenants. if err := client.DeleteRuntimeBackupStateAsClusterAdmin(ctx); err != nil { return err } ``` ### DeleteTenant ```go func (c *CamundaClient) DeleteTenant(ctx context.Context, tenantId string, opts ...func(ApiDeleteTenantRequest) ApiDeleteTenantRequest) error ``` **Types:** [`ApiDeleteTenantRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiDeleteTenantRequest) DeleteTenant calls the DeleteTenant operation. Example: ```go return client.DeleteTenant(ctx, "tenant-a") ``` ### DeleteTenantClusterVariable ```go func (c *CamundaClient) DeleteTenantClusterVariable(ctx context.Context, tenantId string, name string, opts ...func(ApiDeleteTenantClusterVariableRequest) ApiDeleteTenantClusterVariableRequest) error ``` **Types:** [`ApiDeleteTenantClusterVariableRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiDeleteTenantClusterVariableRequest) DeleteTenantClusterVariable calls the DeleteTenantClusterVariable operation. Example: ```go return client.DeleteTenantClusterVariable(ctx, "tenant-a", "region") ``` ### DeleteUser ```go func (c *CamundaClient) DeleteUser(ctx context.Context, username string, opts ...func(ApiDeleteUserRequest) ApiDeleteUserRequest) error ``` **Types:** [`ApiDeleteUserRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiDeleteUserRequest) DeleteUser calls the DeleteUser operation. Example: ```go return client.DeleteUser(ctx, "alice") ``` ### EvaluateConditionals ```go func (c *CamundaClient) EvaluateConditionals(ctx context.Context, body ConditionalEvaluationInstruction, opts ...func(ApiEvaluateConditionalsRequest) ApiEvaluateConditionalsRequest) (*EvaluateConditionalResult, error) ``` **Types:** [`ConditionalEvaluationInstruction`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ConditionalEvaluationInstruction), [`ApiEvaluateConditionalsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiEvaluateConditionalsRequest), [`EvaluateConditionalResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#EvaluateConditionalResult) EvaluateConditionals calls the EvaluateConditionals operation. Example: ```go // Evaluate which conditional start events match the given variables. req := camunda.NewConditionalEvaluationInstruction(map[string]any{"temperature": 42}) result, err := client.EvaluateConditionals(ctx, *req) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### EvaluateDecision ```go func (c *CamundaClient) EvaluateDecision(ctx context.Context, body DecisionEvaluationInstruction, opts ...func(ApiEvaluateDecisionRequest) ApiEvaluateDecisionRequest) (*EvaluateDecisionResult, error) ``` **Types:** [`DecisionEvaluationInstruction`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DecisionEvaluationInstruction), [`ApiEvaluateDecisionRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiEvaluateDecisionRequest), [`EvaluateDecisionResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#EvaluateDecisionResult) EvaluateDecision calls the EvaluateDecision operation. Example: ```go // DecisionEvaluationInstruction is a union; evaluate by decision id here. byID := camunda.NewDecisionEvaluationById("dish-decision") byID.SetVariables(map[string]any{"season": "Winter", "guestCount": 4}) result, err := client.EvaluateDecision(ctx, camunda.DecisionEvaluationByIdAsDecisionEvaluationInstruction(byID)) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### EvaluateExpression ```go func (c *CamundaClient) EvaluateExpression(ctx context.Context, body ExpressionEvaluationRequest, opts ...func(ApiEvaluateExpressionRequest) ApiEvaluateExpressionRequest) (*ExpressionEvaluationResult, error) ``` **Types:** [`ExpressionEvaluationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ExpressionEvaluationRequest), [`ApiEvaluateExpressionRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiEvaluateExpressionRequest), [`ExpressionEvaluationResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ExpressionEvaluationResult) EvaluateExpression calls the EvaluateExpression operation. Example: ```go // Evaluate a FEEL expression against a set of variables. req := camunda.NewExpressionEvaluationRequest("a + b") req.SetVariables(map[string]any{"a": 2, "b": 3}) result, err := client.EvaluateExpression(ctx, *req) if err != nil { return err } fmt.Printf("result: %v\n", result.GetResult()) ``` ### FailJob ```go func (c *CamundaClient) FailJob(ctx context.Context, jobKey JobKey, body JobFailRequest, opts ...func(ApiFailJobRequest) ApiFailJobRequest) error ``` **Types:** [`JobKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#JobKey), [`JobFailRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#JobFailRequest), [`ApiFailJobRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiFailJobRequest) FailJob calls the FailJob operation. Example: ```go req := camunda.NewJobFailRequest() req.SetRetries(2) req.SetErrorMessage("inventory service unavailable") return client.FailJob(ctx, camunda.MustJobKey("2251799813685424"), *req) ``` ### GetAgentDefinition ```go func (c *CamundaClient) GetAgentDefinition(ctx context.Context, agentDefinitionKey AgentDefinitionKey, opts ...func(ApiGetAgentDefinitionRequest) ApiGetAgentDefinitionRequest) (*AgentDefinitionResult, error) ``` **Types:** [`AgentDefinitionKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AgentDefinitionKey), [`ApiGetAgentDefinitionRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetAgentDefinitionRequest), [`AgentDefinitionResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AgentDefinitionResult) GetAgentDefinition calls the GetAgentDefinition operation. Example: ```go definition, err := client.GetAgentDefinition(ctx, camunda.MustAgentDefinitionKey("2251799813691958")) if err != nil { return err } fmt.Printf("%v\n", definition) ``` ### GetAgentInstance ```go func (c *CamundaClient) GetAgentInstance(ctx context.Context, agentInstanceKey AgentInstanceKey, opts ...func(ApiGetAgentInstanceRequest) ApiGetAgentInstanceRequest) (*AgentInstanceResult, error) ``` **Types:** [`AgentInstanceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AgentInstanceKey), [`ApiGetAgentInstanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetAgentInstanceRequest), [`AgentInstanceResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AgentInstanceResult) GetAgentInstance calls the GetAgentInstance operation. Example: ```go agent, err := client.GetAgentInstance(ctx, camunda.MustAgentInstanceKey("2251799813685370")) if err != nil { return err } fmt.Printf("%v\n", agent) ``` ### GetAuditLog ```go func (c *CamundaClient) GetAuditLog(ctx context.Context, auditLogKey AuditLogKey, opts ...func(ApiGetAuditLogRequest) ApiGetAuditLogRequest) (*AuditLogResult, error) ``` **Types:** [`AuditLogKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AuditLogKey), [`ApiGetAuditLogRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetAuditLogRequest), [`AuditLogResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AuditLogResult) GetAuditLog calls the GetAuditLog operation. Example: ```go entry, err := client.GetAuditLog(ctx, camunda.MustAuditLogKey("2251799813685270")) if err != nil { return err } fmt.Printf("%v\n", entry) ``` ### GetAuthentication ```go func (c *CamundaClient) GetAuthentication(ctx context.Context, opts ...func(ApiGetAuthenticationRequest) ApiGetAuthenticationRequest) (*CamundaUserResult, error) ``` **Types:** [`ApiGetAuthenticationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetAuthenticationRequest), [`CamundaUserResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#CamundaUserResult) GetAuthentication calls the GetAuthentication operation. Example: ```go // Return the authenticated user derived from the current credentials. me, err := client.GetAuthentication(ctx) if err != nil { return err } fmt.Printf("authenticated as %s\n", me.GetUsername()) ``` ### GetAuthorization ```go func (c *CamundaClient) GetAuthorization(ctx context.Context, authorizationKey AuthorizationKey, opts ...func(ApiGetAuthorizationRequest) ApiGetAuthorizationRequest) (*AuthorizationResult, error) ``` **Types:** [`AuthorizationKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AuthorizationKey), [`ApiGetAuthorizationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetAuthorizationRequest), [`AuthorizationResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AuthorizationResult) GetAuthorization calls the GetAuthorization operation. Example: ```go auth, err := client.GetAuthorization(ctx, camunda.MustAuthorizationKey("2251799813685280")) if err != nil { return err } fmt.Printf("%v\n", auth) ``` ### GetBatchOperation ```go func (c *CamundaClient) GetBatchOperation(ctx context.Context, batchOperationKey string, opts ...func(ApiGetBatchOperationRequest) ApiGetBatchOperationRequest) (*BatchOperationResponse, error) ``` **Types:** [`ApiGetBatchOperationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetBatchOperationRequest), [`BatchOperationResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#BatchOperationResponse) GetBatchOperation calls the GetBatchOperation operation. Example: ```go op, err := client.GetBatchOperation(ctx, "2251799813685290") if err != nil { return err } fmt.Printf("%v\n", op) ``` ### GetClusterExportingStatus ```go func (c *CamundaClient) GetClusterExportingStatus(ctx context.Context, opts ...func(ApiGetClusterExportingStatusRequest) ApiGetClusterExportingStatusRequest) (*ExportingStatusResponse, error) ``` **Types:** [`ApiGetClusterExportingStatusRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetClusterExportingStatusRequest), [`ExportingStatusResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ExportingStatusResponse) GetClusterExportingStatus calls the GetClusterExportingStatus operation. Example: ```go // Retrieves the exporting status aggregated across all physical tenants in the cluster. status, err := client.GetClusterExportingStatus(ctx) if err != nil { return err } fmt.Printf("cluster exporting status: %s\n", status.GetStatus()) ``` ### GetClusterRebalance ```go func (c *CamundaClient) GetClusterRebalance(ctx context.Context, opts ...func(ApiGetClusterRebalanceRequest) ApiGetClusterRebalanceRequest) (*ClusterBalanceResponse, error) ``` **Types:** [`ApiGetClusterRebalanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetClusterRebalanceRequest), [`ClusterBalanceResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterBalanceResponse) GetClusterRebalance calls the GetClusterRebalance operation. Example: ```go // Requires cluster-admin credentials (a separate cluster-admin security chain) — // calling this with standard Orchestration credentials will fail authorization. balance, err := client.GetClusterRebalance(ctx) if err != nil { return err } fmt.Printf("cluster balance state: %s, %d partition(s)\n", balance.GetState(), len(balance.GetPartitions())) if running, ok := balance.GetRunningRebalanceOk(); ok && running != nil { fmt.Printf("rebalance in progress: %v\n", running) } ``` ### GetClusterStatus ```go func (c *CamundaClient) GetClusterStatus(ctx context.Context, opts ...func(ApiGetClusterStatusRequest) ApiGetClusterStatusRequest) (*ClusterStatusResponse, error) ``` **Types:** [`ApiGetClusterStatusRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetClusterStatusRequest), [`ClusterStatusResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterStatusResponse) GetClusterStatus calls the GetClusterStatus operation. Example: ```go // Aggregated over every physical tenant: HEALTHY, DEGRADED, or DOWN. status, err := client.GetClusterStatus(ctx) if err != nil { return err } fmt.Printf("cluster status: %s\n", status.GetStatus()) ``` ### GetClusterTopology ```go func (c *CamundaClient) GetClusterTopology(ctx context.Context, opts ...func(ApiGetClusterTopologyRequest) ApiGetClusterTopologyRequest) (*ClusterTopologyResponse, error) ``` **Types:** [`ApiGetClusterTopologyRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetClusterTopologyRequest), [`ClusterTopologyResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterTopologyResponse) GetClusterTopology calls the GetClusterTopology operation. Example: ```go // Returns the topology of all brokers across every physical tenant. // Requires cluster-admin credentials (a separate cluster-admin security chain) — // calling this with standard Orchestration credentials will fail authorization. topology, err := client.GetClusterTopology(ctx) if err != nil { return err } fmt.Printf("cluster %s — %d broker(s), %d physical tenant(s)\n", topology.GetClusterId(), len(topology.GetBrokers()), len(topology.GetPhysicalTenants())) ``` ### GetClusterUpgradeStatus ```go func (c *CamundaClient) GetClusterUpgradeStatus(ctx context.Context, opts ...func(ApiGetClusterUpgradeStatusRequest) ApiGetClusterUpgradeStatusRequest) (*ClusterUpgradeStatusResponse, error) ``` **Types:** [`ApiGetClusterUpgradeStatusRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetClusterUpgradeStatusRequest), [`ClusterUpgradeStatusResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterUpgradeStatusResponse) GetClusterUpgradeStatus calls the GetClusterUpgradeStatus operation. Example: ```go // One overall status folded over every physical tenant and condition: // MIGRATED, MIGRATION_IN_PROGRESS, or UNKNOWN before anything has been reported yet. status, err := client.GetClusterUpgradeStatus(ctx) if err != nil { return err } fmt.Printf("cluster upgrade status: %s\n", status.GetStatus()) ``` ### GetDecisionDefinition ```go func (c *CamundaClient) GetDecisionDefinition(ctx context.Context, decisionDefinitionKey DecisionDefinitionKey, opts ...func(ApiGetDecisionDefinitionRequest) ApiGetDecisionDefinitionRequest) (*DecisionDefinitionResult, error) ``` **Types:** [`DecisionDefinitionKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DecisionDefinitionKey), [`ApiGetDecisionDefinitionRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetDecisionDefinitionRequest), [`DecisionDefinitionResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DecisionDefinitionResult) GetDecisionDefinition calls the GetDecisionDefinition operation. Example: ```go def, err := client.GetDecisionDefinition(ctx, camunda.MustDecisionDefinitionKey("2251799813685310")) if err != nil { return err } fmt.Printf("%v\n", def) ``` ### GetDecisionDefinitionXML ```go func (c *CamundaClient) GetDecisionDefinitionXML(ctx context.Context, decisionDefinitionKey DecisionDefinitionKey, opts ...func(ApiGetDecisionDefinitionXMLRequest) ApiGetDecisionDefinitionXMLRequest) (string, error) ``` **Types:** [`DecisionDefinitionKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DecisionDefinitionKey), [`ApiGetDecisionDefinitionXMLRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetDecisionDefinitionXMLRequest) GetDecisionDefinitionXML calls the GetDecisionDefinitionXML operation. Example: ```go xml, err := client.GetDecisionDefinitionXML(ctx, camunda.MustDecisionDefinitionKey("2251799813685310")) if err != nil { return err } fmt.Println(xml) ``` ### GetDecisionInstance ```go func (c *CamundaClient) GetDecisionInstance(ctx context.Context, decisionEvaluationInstanceKey string, opts ...func(ApiGetDecisionInstanceRequest) ApiGetDecisionInstanceRequest) (*DecisionInstanceGetQueryResult, error) ``` **Types:** [`ApiGetDecisionInstanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetDecisionInstanceRequest), [`DecisionInstanceGetQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DecisionInstanceGetQueryResult) GetDecisionInstance calls the GetDecisionInstance operation. Example: ```go instance, err := client.GetDecisionInstance(ctx, "2251799813685310-1") if err != nil { return err } fmt.Printf("%v\n", instance) ``` ### GetDecisionRequirements ```go func (c *CamundaClient) GetDecisionRequirements(ctx context.Context, decisionRequirementsKey DecisionRequirementsKey, opts ...func(ApiGetDecisionRequirementsRequest) ApiGetDecisionRequirementsRequest) (*DecisionRequirementsResult, error) ``` **Types:** [`DecisionRequirementsKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DecisionRequirementsKey), [`ApiGetDecisionRequirementsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetDecisionRequirementsRequest), [`DecisionRequirementsResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DecisionRequirementsResult) GetDecisionRequirements calls the GetDecisionRequirements operation. Example: ```go drd, err := client.GetDecisionRequirements(ctx, camunda.MustDecisionRequirementsKey("2251799813685320")) if err != nil { return err } fmt.Printf("%v\n", drd) ``` ### GetDecisionRequirementsXML ```go func (c *CamundaClient) GetDecisionRequirementsXML(ctx context.Context, decisionRequirementsKey DecisionRequirementsKey, opts ...func(ApiGetDecisionRequirementsXMLRequest) ApiGetDecisionRequirementsXMLRequest) (string, error) ``` **Types:** [`DecisionRequirementsKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DecisionRequirementsKey), [`ApiGetDecisionRequirementsXMLRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetDecisionRequirementsXMLRequest) GetDecisionRequirementsXML calls the GetDecisionRequirementsXML operation. Example: ```go xml, err := client.GetDecisionRequirementsXML(ctx, camunda.MustDecisionRequirementsKey("2251799813685320")) if err != nil { return err } fmt.Println(xml) ``` ### GetDocument ```go func (c *CamundaClient) GetDocument(ctx context.Context, documentId string, opts ...func(ApiGetDocumentRequest) ApiGetDocumentRequest) (*os.File, error) ``` **Types:** [`ApiGetDocumentRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetDocumentRequest) GetDocument calls the GetDocument operation. Example: ```go file, err := client.GetDocument(ctx, "doc-123") if err != nil { return err } fmt.Printf("downloaded to %s\n", file.Name()) ``` ### GetElementInstance ```go func (c *CamundaClient) GetElementInstance(ctx context.Context, elementInstanceKey ElementInstanceKey, opts ...func(ApiGetElementInstanceRequest) ApiGetElementInstanceRequest) (*ElementInstanceResult, error) ``` **Types:** [`ElementInstanceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ElementInstanceKey), [`ApiGetElementInstanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetElementInstanceRequest), [`ElementInstanceResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ElementInstanceResult) GetElementInstance calls the GetElementInstance operation. Example: ```go element, err := client.GetElementInstance(ctx, camunda.MustElementInstanceKey("2251799813685360")) if err != nil { return err } fmt.Printf("%v\n", element) ``` ### GetExportingStatus ```go func (c *CamundaClient) GetExportingStatus(ctx context.Context, opts ...func(ApiGetExportingStatusRequest) ApiGetExportingStatusRequest) (*ExportingStatusResponse, error) ``` **Types:** [`ApiGetExportingStatusRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetExportingStatusRequest), [`ExportingStatusResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ExportingStatusResponse) GetExportingStatus calls the GetExportingStatus operation. Example: ```go // Aggregated over every replica of the physical tenant. status, err := client.GetExportingStatus(ctx) if err != nil { return err } fmt.Printf("exporting status: %s\n", status.GetStatus()) ``` ### GetFormByKey ```go func (c *CamundaClient) GetFormByKey(ctx context.Context, formKey FormKey, opts ...func(ApiGetFormByKeyRequest) ApiGetFormByKeyRequest) (*FormResult, error) ``` **Types:** [`FormKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#FormKey), [`ApiGetFormByKeyRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetFormByKeyRequest), [`FormResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#FormResult) GetFormByKey calls the GetFormByKey operation. Example: ```go form, err := client.GetFormByKey(ctx, camunda.MustFormKey("2251799813685260")) if err != nil { return err } fmt.Printf("form %v version %d\n", form.GetFormId(), form.GetVersion()) ``` ### GetGlobalClusterVariable ```go func (c *CamundaClient) GetGlobalClusterVariable(ctx context.Context, name string, opts ...func(ApiGetGlobalClusterVariableRequest) ApiGetGlobalClusterVariableRequest) (*ClusterVariableResult, error) ``` **Types:** [`ApiGetGlobalClusterVariableRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetGlobalClusterVariableRequest), [`ClusterVariableResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterVariableResult) GetGlobalClusterVariable calls the GetGlobalClusterVariable operation. Example: ```go result, err := client.GetGlobalClusterVariable(ctx, "region") if err != nil { return err } fmt.Printf("%v\n", result) ``` ### GetGlobalJobStatistics ```go func (c *CamundaClient) GetGlobalJobStatistics(ctx context.Context, opts ...func(ApiGetGlobalJobStatisticsRequest) ApiGetGlobalJobStatisticsRequest) (*GlobalJobStatisticsQueryResult, error) ``` **Types:** [`ApiGetGlobalJobStatisticsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetGlobalJobStatisticsRequest), [`GlobalJobStatisticsQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#GlobalJobStatisticsQueryResult) GetGlobalJobStatistics calls the GetGlobalJobStatistics operation. Example: ```go result, err := client.GetGlobalJobStatistics(ctx) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### GetGlobalTaskListener ```go func (c *CamundaClient) GetGlobalTaskListener(ctx context.Context, id string, opts ...func(ApiGetGlobalTaskListenerRequest) ApiGetGlobalTaskListenerRequest) (*GlobalTaskListenerResult, error) ``` **Types:** [`ApiGetGlobalTaskListenerRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetGlobalTaskListenerRequest), [`GlobalTaskListenerResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#GlobalTaskListenerResult) GetGlobalTaskListener calls the GetGlobalTaskListener operation. Example: ```go result, err := client.GetGlobalTaskListener(ctx, "audit-listener") if err != nil { return err } fmt.Printf("%v\n", result) ``` ### GetGroup ```go func (c *CamundaClient) GetGroup(ctx context.Context, groupId string, opts ...func(ApiGetGroupRequest) ApiGetGroupRequest) (*GroupResult, error) ``` **Types:** [`ApiGetGroupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetGroupRequest), [`GroupResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#GroupResult) GetGroup calls the GetGroup operation. Example: ```go group, err := client.GetGroup(ctx, "finance") if err != nil { return err } fmt.Printf("%v\n", group) ``` ### GetHistoryBackup ```go func (c *CamundaClient) GetHistoryBackup(ctx context.Context, backupId int64, opts ...func(ApiGetHistoryBackupRequest) ApiGetHistoryBackupRequest) (*HistoryBackupInfo, error) ``` **Types:** [`ApiGetHistoryBackupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetHistoryBackupRequest), [`HistoryBackupInfo`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#HistoryBackupInfo) GetHistoryBackup calls the GetHistoryBackup operation. Example: ```go backup, err := client.GetHistoryBackup(ctx, 42) if err != nil { return err } fmt.Printf("history backup %d state=%v\n", backup.GetBackupId(), backup.GetState()) for _, snapshot := range backup.GetDetails() { fmt.Printf(" snapshot %v\n", snapshot) } ``` ### GetHistoryBackupAsClusterAdmin ```go func (c *CamundaClient) GetHistoryBackupAsClusterAdmin(ctx context.Context, backupId int64, opts ...func(ApiGetHistoryBackupAsClusterAdminRequest) ApiGetHistoryBackupAsClusterAdminRequest) (*ClusterHistoryBackupInfo, error) ``` **Types:** [`ApiGetHistoryBackupAsClusterAdminRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetHistoryBackupAsClusterAdminRequest), [`ClusterHistoryBackupInfo`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterHistoryBackupInfo) GetHistoryBackupAsClusterAdmin calls the GetHistoryBackupAsClusterAdmin operation. Example: ```go backup, err := client.GetHistoryBackupAsClusterAdmin(ctx, 42) if err != nil { return err } for _, tenant := range backup.GetPhysicalTenants() { fmt.Printf("tenant %s: state=%v\n", tenant.GetPhysicalTenantId(), tenant.GetState()) } ``` ### GetIncident ```go func (c *CamundaClient) GetIncident(ctx context.Context, incidentKey IncidentKey, opts ...func(ApiGetIncidentRequest) ApiGetIncidentRequest) (*IncidentResult, error) ``` **Types:** [`IncidentKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#IncidentKey), [`ApiGetIncidentRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetIncidentRequest), [`IncidentResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#IncidentResult) GetIncident calls the GetIncident operation. Example: ```go incident, err := client.GetIncident(ctx, camunda.MustIncidentKey("2251799813685300")) if err != nil { return err } fmt.Printf("%v\n", incident) ``` ### GetJobErrorStatistics ```go func (c *CamundaClient) GetJobErrorStatistics(ctx context.Context, body JobErrorStatisticsQuery, opts ...func(ApiGetJobErrorStatisticsRequest) ApiGetJobErrorStatisticsRequest) (*JobErrorStatisticsQueryResult, error) ``` **Types:** [`JobErrorStatisticsQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#JobErrorStatisticsQuery), [`ApiGetJobErrorStatisticsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetJobErrorStatisticsRequest), [`JobErrorStatisticsQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#JobErrorStatisticsQueryResult) GetJobErrorStatistics calls the GetJobErrorStatistics operation. Example: ```go from, to := time.Now().Add(-24*time.Hour), time.Now() query := camunda.NewJobErrorStatisticsQuery(*camunda.NewJobErrorStatisticsFilter(from, to, "greet")) result, err := client.GetJobErrorStatistics(ctx, *query) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### GetJobTimeSeriesStatistics ```go func (c *CamundaClient) GetJobTimeSeriesStatistics(ctx context.Context, body JobTimeSeriesStatisticsQuery, opts ...func(ApiGetJobTimeSeriesStatisticsRequest) ApiGetJobTimeSeriesStatisticsRequest) (*JobTimeSeriesStatisticsQueryResult, error) ``` **Types:** [`JobTimeSeriesStatisticsQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#JobTimeSeriesStatisticsQuery), [`ApiGetJobTimeSeriesStatisticsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetJobTimeSeriesStatisticsRequest), [`JobTimeSeriesStatisticsQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#JobTimeSeriesStatisticsQueryResult) GetJobTimeSeriesStatistics calls the GetJobTimeSeriesStatistics operation. Example: ```go from, to := time.Now().Add(-24*time.Hour), time.Now() query := camunda.NewJobTimeSeriesStatisticsQuery(*camunda.NewJobTimeSeriesStatisticsFilter(from, to, "greet")) result, err := client.GetJobTimeSeriesStatistics(ctx, *query) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### GetJobTypeStatistics ```go func (c *CamundaClient) GetJobTypeStatistics(ctx context.Context, body JobTypeStatisticsQuery, opts ...func(ApiGetJobTypeStatisticsRequest) ApiGetJobTypeStatisticsRequest) (*JobTypeStatisticsQueryResult, error) ``` **Types:** [`JobTypeStatisticsQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#JobTypeStatisticsQuery), [`ApiGetJobTypeStatisticsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetJobTypeStatisticsRequest), [`JobTypeStatisticsQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#JobTypeStatisticsQueryResult) GetJobTypeStatistics calls the GetJobTypeStatistics operation. Example: ```go result, err := client.GetJobTypeStatistics(ctx, *camunda.NewJobTypeStatisticsQuery()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### GetJobWorkerStatistics ```go func (c *CamundaClient) GetJobWorkerStatistics(ctx context.Context, body JobWorkerStatisticsQuery, opts ...func(ApiGetJobWorkerStatisticsRequest) ApiGetJobWorkerStatisticsRequest) (*JobWorkerStatisticsQueryResult, error) ``` **Types:** [`JobWorkerStatisticsQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#JobWorkerStatisticsQuery), [`ApiGetJobWorkerStatisticsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetJobWorkerStatisticsRequest), [`JobWorkerStatisticsQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#JobWorkerStatisticsQueryResult) GetJobWorkerStatistics calls the GetJobWorkerStatistics operation. Example: ```go from, to := time.Now().Add(-24*time.Hour), time.Now() query := camunda.NewJobWorkerStatisticsQuery(*camunda.NewJobWorkerStatisticsFilter(from, to, "greet")) result, err := client.GetJobWorkerStatistics(ctx, *query) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### GetLicense ```go func (c *CamundaClient) GetLicense(ctx context.Context, opts ...func(ApiGetLicenseRequest) ApiGetLicenseRequest) (*LicenseResponse, error) ``` **Types:** [`ApiGetLicenseRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetLicenseRequest), [`LicenseResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#LicenseResponse) GetLicense calls the GetLicense operation. Example: ```go license, err := client.GetLicense(ctx) if err != nil { return err } fmt.Printf("license type=%s valid=%v\n", license.GetLicenseType(), license.GetValidLicense()) ``` ### GetMappingRule ```go func (c *CamundaClient) GetMappingRule(ctx context.Context, mappingRuleId string, opts ...func(ApiGetMappingRuleRequest) ApiGetMappingRuleRequest) (*MappingRuleResult, error) ``` **Types:** [`ApiGetMappingRuleRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetMappingRuleRequest), [`MappingRuleResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#MappingRuleResult) GetMappingRule calls the GetMappingRule operation. Example: ```go rule, err := client.GetMappingRule(ctx, "sso-auditors") if err != nil { return err } fmt.Printf("%v\n", rule) ``` ### GetProcessDefinition ```go func (c *CamundaClient) GetProcessDefinition(ctx context.Context, processDefinitionKey ProcessDefinitionKey, opts ...func(ApiGetProcessDefinitionRequest) ApiGetProcessDefinitionRequest) (*ProcessDefinitionResult, error) ``` **Types:** [`ProcessDefinitionKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessDefinitionKey), [`ApiGetProcessDefinitionRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetProcessDefinitionRequest), [`ProcessDefinitionResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessDefinitionResult) GetProcessDefinition calls the GetProcessDefinition operation. Example: ```go def, err := client.GetProcessDefinition(ctx, camunda.MustProcessDefinitionKey("2251799813685330")) if err != nil { return err } fmt.Printf("%v\n", def) ``` ### GetProcessDefinitionInstanceStatistics ```go func (c *CamundaClient) GetProcessDefinitionInstanceStatistics(ctx context.Context, body ProcessDefinitionInstanceStatisticsQuery, opts ...func(ApiGetProcessDefinitionInstanceStatisticsRequest) ApiGetProcessDefinitionInstanceStatisticsRequest) (*ProcessDefinitionInstanceStatisticsQueryResult, error) ``` **Types:** [`ProcessDefinitionInstanceStatisticsQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessDefinitionInstanceStatisticsQuery), [`ApiGetProcessDefinitionInstanceStatisticsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetProcessDefinitionInstanceStatisticsRequest), [`ProcessDefinitionInstanceStatisticsQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessDefinitionInstanceStatisticsQueryResult) GetProcessDefinitionInstanceStatistics calls the GetProcessDefinitionInstanceStatistics operation. Example: ```go result, err := client.GetProcessDefinitionInstanceStatistics(ctx, *camunda.NewProcessDefinitionInstanceStatisticsQuery()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### GetProcessDefinitionInstanceVersionStatistics ```go func (c *CamundaClient) GetProcessDefinitionInstanceVersionStatistics(ctx context.Context, body ProcessDefinitionInstanceVersionStatisticsQuery, opts ...func(ApiGetProcessDefinitionInstanceVersionStatisticsRequest) ApiGetProcessDefinitionInstanceVersionStatisticsRequest) (*ProcessDefinitionInstanceVersionStatisticsQueryResult, error) ``` **Types:** [`ProcessDefinitionInstanceVersionStatisticsQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessDefinitionInstanceVersionStatisticsQuery), [`ApiGetProcessDefinitionInstanceVersionStatisticsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetProcessDefinitionInstanceVersionStatisticsRequest), [`ProcessDefinitionInstanceVersionStatisticsQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessDefinitionInstanceVersionStatisticsQueryResult) GetProcessDefinitionInstanceVersionStatistics calls the GetProcessDefinitionInstanceVersionStatistics operation. Example: ```go query := camunda.NewProcessDefinitionInstanceVersionStatisticsQuery( *camunda.NewProcessDefinitionInstanceVersionStatisticsFilter("order-process")) result, err := client.GetProcessDefinitionInstanceVersionStatistics(ctx, *query) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### GetProcessDefinitionMessageSubscriptionStatistics ```go func (c *CamundaClient) GetProcessDefinitionMessageSubscriptionStatistics(ctx context.Context, body ProcessDefinitionMessageSubscriptionStatisticsQuery, opts ...func(ApiGetProcessDefinitionMessageSubscriptionStatisticsRequest) ApiGetProcessDefinitionMessageSubscriptionStatisticsRequest) (*ProcessDefinitionMessageSubscriptionStatisticsQueryResult, error) ``` **Types:** [`ProcessDefinitionMessageSubscriptionStatisticsQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessDefinitionMessageSubscriptionStatisticsQuery), [`ApiGetProcessDefinitionMessageSubscriptionStatisticsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetProcessDefinitionMessageSubscriptionStatisticsRequest), [`ProcessDefinitionMessageSubscriptionStatisticsQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessDefinitionMessageSubscriptionStatisticsQueryResult) GetProcessDefinitionMessageSubscriptionStatistics calls the GetProcessDefinitionMessageSubscriptionStatistics operation. Example: ```go result, err := client.GetProcessDefinitionMessageSubscriptionStatistics(ctx, *camunda.NewProcessDefinitionMessageSubscriptionStatisticsQuery()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### GetProcessDefinitionStatistics ```go func (c *CamundaClient) GetProcessDefinitionStatistics(ctx context.Context, processDefinitionKey ProcessDefinitionKey, body ProcessDefinitionElementStatisticsQuery, opts ...func(ApiGetProcessDefinitionStatisticsRequest) ApiGetProcessDefinitionStatisticsRequest) (*ProcessDefinitionElementStatisticsQueryResult, error) ``` **Types:** [`ProcessDefinitionKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessDefinitionKey), [`ProcessDefinitionElementStatisticsQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessDefinitionElementStatisticsQuery), [`ApiGetProcessDefinitionStatisticsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetProcessDefinitionStatisticsRequest), [`ProcessDefinitionElementStatisticsQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessDefinitionElementStatisticsQueryResult) GetProcessDefinitionStatistics calls the GetProcessDefinitionStatistics operation. Example: ```go result, err := client.GetProcessDefinitionStatistics(ctx, camunda.MustProcessDefinitionKey("2251799813685330"), *camunda.NewProcessDefinitionElementStatisticsQuery()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### GetProcessDefinitionXML ```go func (c *CamundaClient) GetProcessDefinitionXML(ctx context.Context, processDefinitionKey ProcessDefinitionKey, opts ...func(ApiGetProcessDefinitionXMLRequest) ApiGetProcessDefinitionXMLRequest) (string, error) ``` **Types:** [`ProcessDefinitionKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessDefinitionKey), [`ApiGetProcessDefinitionXMLRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetProcessDefinitionXMLRequest) GetProcessDefinitionXML calls the GetProcessDefinitionXML operation. Example: ```go xml, err := client.GetProcessDefinitionXML(ctx, camunda.MustProcessDefinitionKey("2251799813685330")) if err != nil { return err } fmt.Println(xml) ``` ### GetProcessInstance ```go func (c *CamundaClient) GetProcessInstance(ctx context.Context, processInstanceKey ProcessInstanceKey, opts ...func(ApiGetProcessInstanceRequest) ApiGetProcessInstanceRequest) (*ProcessInstanceResult, error) ``` **Types:** [`ProcessInstanceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceKey), [`ApiGetProcessInstanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetProcessInstanceRequest), [`ProcessInstanceResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceResult) GetProcessInstance calls the GetProcessInstance operation. Example: ```go instance, err := client.GetProcessInstance(ctx, camunda.MustProcessInstanceKey("2251799813685340")) if err != nil { return err } fmt.Printf("state=%v definition=%q\n", instance.GetState(), instance.GetProcessDefinitionId()) ``` ### GetProcessInstanceCallHierarchy ```go func (c *CamundaClient) GetProcessInstanceCallHierarchy(ctx context.Context, processInstanceKey ProcessInstanceKey, opts ...func(ApiGetProcessInstanceCallHierarchyRequest) ApiGetProcessInstanceCallHierarchyRequest) ([]ProcessInstanceCallHierarchyEntry, error) ``` **Types:** [`ProcessInstanceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceKey), [`ApiGetProcessInstanceCallHierarchyRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetProcessInstanceCallHierarchyRequest), [`ProcessInstanceCallHierarchyEntry`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceCallHierarchyEntry) GetProcessInstanceCallHierarchy calls the GetProcessInstanceCallHierarchy operation. Example: ```go hierarchy, err := client.GetProcessInstanceCallHierarchy(ctx, camunda.MustProcessInstanceKey("2251799813685340")) if err != nil { return err } for _, entry := range hierarchy { fmt.Printf("%v\n", entry) } ``` ### GetProcessInstanceSequenceFlows ```go func (c *CamundaClient) GetProcessInstanceSequenceFlows(ctx context.Context, processInstanceKey ProcessInstanceKey, opts ...func(ApiGetProcessInstanceSequenceFlowsRequest) ApiGetProcessInstanceSequenceFlowsRequest) (*ProcessInstanceSequenceFlowsQueryResult, error) ``` **Types:** [`ProcessInstanceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceKey), [`ApiGetProcessInstanceSequenceFlowsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetProcessInstanceSequenceFlowsRequest), [`ProcessInstanceSequenceFlowsQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceSequenceFlowsQueryResult) GetProcessInstanceSequenceFlows calls the GetProcessInstanceSequenceFlows operation. Example: ```go result, err := client.GetProcessInstanceSequenceFlows(ctx, camunda.MustProcessInstanceKey("2251799813685340")) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### GetProcessInstanceStatistics ```go func (c *CamundaClient) GetProcessInstanceStatistics(ctx context.Context, processInstanceKey ProcessInstanceKey, opts ...func(ApiGetProcessInstanceStatisticsRequest) ApiGetProcessInstanceStatisticsRequest) (*ProcessInstanceElementStatisticsQueryResult, error) ``` **Types:** [`ProcessInstanceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceKey), [`ApiGetProcessInstanceStatisticsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetProcessInstanceStatisticsRequest), [`ProcessInstanceElementStatisticsQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceElementStatisticsQueryResult) GetProcessInstanceStatistics calls the GetProcessInstanceStatistics operation. Example: ```go result, err := client.GetProcessInstanceStatistics(ctx, camunda.MustProcessInstanceKey("2251799813685340")) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### GetProcessInstanceStatisticsByDefinition ```go func (c *CamundaClient) GetProcessInstanceStatisticsByDefinition(ctx context.Context, body IncidentProcessInstanceStatisticsByDefinitionQuery, opts ...func(ApiGetProcessInstanceStatisticsByDefinitionRequest) ApiGetProcessInstanceStatisticsByDefinitionRequest) (*IncidentProcessInstanceStatisticsByDefinitionQueryResult, error) ``` **Types:** [`IncidentProcessInstanceStatisticsByDefinitionQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#IncidentProcessInstanceStatisticsByDefinitionQuery), [`ApiGetProcessInstanceStatisticsByDefinitionRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetProcessInstanceStatisticsByDefinitionRequest), [`IncidentProcessInstanceStatisticsByDefinitionQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#IncidentProcessInstanceStatisticsByDefinitionQueryResult) GetProcessInstanceStatisticsByDefinition calls the GetProcessInstanceStatisticsByDefinition operation. Example: ```go query := camunda.NewIncidentProcessInstanceStatisticsByDefinitionQuery( *camunda.NewIncidentProcessInstanceStatisticsByDefinitionFilter(0)) result, err := client.GetProcessInstanceStatisticsByDefinition(ctx, *query) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### GetProcessInstanceStatisticsByError ```go func (c *CamundaClient) GetProcessInstanceStatisticsByError(ctx context.Context, body IncidentProcessInstanceStatisticsByErrorQuery, opts ...func(ApiGetProcessInstanceStatisticsByErrorRequest) ApiGetProcessInstanceStatisticsByErrorRequest) (*IncidentProcessInstanceStatisticsByErrorQueryResult, error) ``` **Types:** [`IncidentProcessInstanceStatisticsByErrorQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#IncidentProcessInstanceStatisticsByErrorQuery), [`ApiGetProcessInstanceStatisticsByErrorRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetProcessInstanceStatisticsByErrorRequest), [`IncidentProcessInstanceStatisticsByErrorQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#IncidentProcessInstanceStatisticsByErrorQueryResult) GetProcessInstanceStatisticsByError calls the GetProcessInstanceStatisticsByError operation. Example: ```go result, err := client.GetProcessInstanceStatisticsByError(ctx, *camunda.NewIncidentProcessInstanceStatisticsByErrorQuery()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### GetProcessInstanceWaitStateStatistics ```go func (c *CamundaClient) GetProcessInstanceWaitStateStatistics(ctx context.Context, processInstanceKey ProcessInstanceKey, opts ...func(ApiGetProcessInstanceWaitStateStatisticsRequest) ApiGetProcessInstanceWaitStateStatisticsRequest) (*ProcessInstanceWaitStateStatisticsQueryResult, error) ``` **Types:** [`ProcessInstanceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceKey), [`ApiGetProcessInstanceWaitStateStatisticsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetProcessInstanceWaitStateStatisticsRequest), [`ProcessInstanceWaitStateStatisticsQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceWaitStateStatisticsQueryResult) GetProcessInstanceWaitStateStatistics calls the GetProcessInstanceWaitStateStatistics operation. Example: ```go result, err := client.GetProcessInstanceWaitStateStatistics(ctx, camunda.MustProcessInstanceKey("2251799813685340")) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### GetResource ```go func (c *CamundaClient) GetResource(ctx context.Context, resourceKey ResourceKey, opts ...func(ApiGetResourceRequest) ApiGetResourceRequest) (*ResourceResult, error) ``` **Types:** [`ResourceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ResourceKey), [`ApiGetResourceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetResourceRequest), [`ResourceResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ResourceResult) GetResource calls the GetResource operation. Example: ```go resource, err := client.GetResource(ctx, camunda.MustResourceKey("2251799813685350")) if err != nil { return err } fmt.Printf("%v\n", resource) ``` ### GetResourceContent ```go func (c *CamundaClient) GetResourceContent(ctx context.Context, resourceKey ResourceKey, opts ...func(ApiGetResourceContentRequest) ApiGetResourceContentRequest) (map[string]interface{}, error) ``` **Types:** [`ResourceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ResourceKey), [`ApiGetResourceContentRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetResourceContentRequest) GetResourceContent calls the GetResourceContent operation. Example: ```go content, err := client.GetResourceContent(ctx, camunda.MustResourceKey("2251799813685350")) if err != nil { return err } fmt.Printf("%v\n", content) ``` ### GetResourceContentBinary ```go func (c *CamundaClient) GetResourceContentBinary(ctx context.Context, resourceKey ResourceKey, opts ...func(ApiGetResourceContentBinaryRequest) ApiGetResourceContentBinaryRequest) (*os.File, error) ``` **Types:** [`ResourceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ResourceKey), [`ApiGetResourceContentBinaryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetResourceContentBinaryRequest) GetResourceContentBinary calls the GetResourceContentBinary operation. Example: ```go file, err := client.GetResourceContentBinary(ctx, camunda.MustResourceKey("2251799813685350")) if err != nil { return err } fmt.Printf("downloaded to %s\n", file.Name()) ``` ### GetRestoreStatus ```go func (c *CamundaClient) GetRestoreStatus(ctx context.Context, opts ...func(ApiGetRestoreStatusRequest) ApiGetRestoreStatusRequest) (*RestoreStatusResponse, error) ``` **Types:** [`ApiGetRestoreStatusRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetRestoreStatusRequest), [`RestoreStatusResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#RestoreStatusResponse) GetRestoreStatus calls the GetRestoreStatus operation. Example: ```go // Reports the in-flight restore only — 404 once it has finished. status, err := client.GetRestoreStatus(ctx) if err != nil { return err } fmt.Printf("restore %s: %s\n", status.GetChangeId(), status.GetStatus()) for _, broker := range status.GetBrokers() { fmt.Printf("%v\n", broker) } ``` ### GetRole ```go func (c *CamundaClient) GetRole(ctx context.Context, roleId string, opts ...func(ApiGetRoleRequest) ApiGetRoleRequest) (*RoleResult, error) ``` **Types:** [`ApiGetRoleRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetRoleRequest), [`RoleResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#RoleResult) GetRole calls the GetRole operation. Example: ```go role, err := client.GetRole(ctx, "auditor") if err != nil { return err } fmt.Printf("%v\n", role) ``` ### GetRuntimeBackup ```go func (c *CamundaClient) GetRuntimeBackup(ctx context.Context, backupId int64, opts ...func(ApiGetRuntimeBackupRequest) ApiGetRuntimeBackupRequest) (*BackupInfo, error) ``` **Types:** [`ApiGetRuntimeBackupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetRuntimeBackupRequest), [`BackupInfo`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#BackupInfo) GetRuntimeBackup calls the GetRuntimeBackup operation. Example: ```go backup, err := client.GetRuntimeBackup(ctx, 42) if err != nil { return err } // Details cover every partition of the physical tenant. for _, partition := range backup.GetDetails() { fmt.Printf("%v\n", partition) } ``` ### GetRuntimeBackupAsClusterAdmin ```go func (c *CamundaClient) GetRuntimeBackupAsClusterAdmin(ctx context.Context, backupId int64, opts ...func(ApiGetRuntimeBackupAsClusterAdminRequest) ApiGetRuntimeBackupAsClusterAdminRequest) (*ClusterRuntimeBackupInfo, error) ``` **Types:** [`ApiGetRuntimeBackupAsClusterAdminRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetRuntimeBackupAsClusterAdminRequest), [`ClusterRuntimeBackupInfo`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterRuntimeBackupInfo) GetRuntimeBackupAsClusterAdmin calls the GetRuntimeBackupAsClusterAdmin operation. Example: ```go backup, err := client.GetRuntimeBackupAsClusterAdmin(ctx, 42) if err != nil { return err } fmt.Printf("cluster runtime backup %d: state=%v\n", backup.GetBackupId(), backup.GetState()) for _, tenant := range backup.GetPhysicalTenants() { fmt.Printf(" tenant %v\n", tenant) } ``` ### GetRuntimeBackupState ```go func (c *CamundaClient) GetRuntimeBackupState(ctx context.Context, opts ...func(ApiGetRuntimeBackupStateRequest) ApiGetRuntimeBackupStateRequest) (*RuntimeBackupState, error) ``` **Types:** [`ApiGetRuntimeBackupStateRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetRuntimeBackupStateRequest), [`RuntimeBackupState`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#RuntimeBackupState) GetRuntimeBackupState calls the GetRuntimeBackupState operation. Example: ```go state, err := client.GetRuntimeBackupState(ctx) if err != nil { return err } for _, checkpoint := range state.GetCheckpointStates() { fmt.Printf("%v\n", checkpoint) } ``` ### GetRuntimeBackupStateAsClusterAdmin ```go func (c *CamundaClient) GetRuntimeBackupStateAsClusterAdmin(ctx context.Context, opts ...func(ApiGetRuntimeBackupStateAsClusterAdminRequest) ApiGetRuntimeBackupStateAsClusterAdminRequest) (*ClusterRuntimeBackupState, error) ``` **Types:** [`ApiGetRuntimeBackupStateAsClusterAdminRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetRuntimeBackupStateAsClusterAdminRequest), [`ClusterRuntimeBackupState`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterRuntimeBackupState) GetRuntimeBackupStateAsClusterAdmin calls the GetRuntimeBackupStateAsClusterAdmin operation. Example: ```go // Returns the runtime backup state for every physical tenant in the cluster. state, err := client.GetRuntimeBackupStateAsClusterAdmin(ctx) if err != nil { return err } for _, tenant := range state.GetPhysicalTenants() { fmt.Printf("%v\n", tenant) } ``` ### GetStartProcessForm ```go func (c *CamundaClient) GetStartProcessForm(ctx context.Context, processDefinitionKey ProcessDefinitionKey, opts ...func(ApiGetStartProcessFormRequest) ApiGetStartProcessFormRequest) (*FormResult, error) ``` **Types:** [`ProcessDefinitionKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessDefinitionKey), [`ApiGetStartProcessFormRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetStartProcessFormRequest), [`FormResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#FormResult) GetStartProcessForm calls the GetStartProcessForm operation. Example: ```go form, err := client.GetStartProcessForm(ctx, camunda.MustProcessDefinitionKey("2251799813685330")) if err != nil { return err } fmt.Printf("%v\n", form) ``` ### GetStatus ```go func (c *CamundaClient) GetStatus(ctx context.Context, opts ...func(ApiGetStatusRequest) ApiGetStatusRequest) error ``` **Types:** [`ApiGetStatusRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetStatusRequest) GetStatus calls the GetStatus operation. Example: ```go // Readiness probe: returns a non-nil error when the cluster is not ready. if err := client.GetStatus(ctx); err != nil { return err } fmt.Println("cluster is ready") ``` ### GetSystemConfiguration ```go func (c *CamundaClient) GetSystemConfiguration(ctx context.Context, opts ...func(ApiGetSystemConfigurationRequest) ApiGetSystemConfigurationRequest) (*SystemConfigurationResponse, error) ``` **Types:** [`ApiGetSystemConfigurationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetSystemConfigurationRequest), [`SystemConfigurationResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#SystemConfigurationResponse) GetSystemConfiguration calls the GetSystemConfiguration operation. Example: ```go config, err := client.GetSystemConfiguration(ctx) if err != nil { return err } fmt.Printf("%v\n", config) ``` ### GetTenant ```go func (c *CamundaClient) GetTenant(ctx context.Context, tenantId string, opts ...func(ApiGetTenantRequest) ApiGetTenantRequest) (*TenantResult, error) ``` **Types:** [`ApiGetTenantRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetTenantRequest), [`TenantResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TenantResult) GetTenant calls the GetTenant operation. Example: ```go tenant, err := client.GetTenant(ctx, "tenant-a") if err != nil { return err } fmt.Printf("%v\n", tenant) ``` ### GetTenantClusterVariable ```go func (c *CamundaClient) GetTenantClusterVariable(ctx context.Context, tenantId string, name string, opts ...func(ApiGetTenantClusterVariableRequest) ApiGetTenantClusterVariableRequest) (*ClusterVariableResult, error) ``` **Types:** [`ApiGetTenantClusterVariableRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetTenantClusterVariableRequest), [`ClusterVariableResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterVariableResult) GetTenantClusterVariable calls the GetTenantClusterVariable operation. Example: ```go result, err := client.GetTenantClusterVariable(ctx, "tenant-a", "region") if err != nil { return err } fmt.Printf("%v\n", result) ``` ### GetTopology ```go func (c *CamundaClient) GetTopology(ctx context.Context, opts ...func(ApiGetTopologyRequest) ApiGetTopologyRequest) (*TopologyResponse, error) ``` **Types:** [`ApiGetTopologyRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetTopologyRequest), [`TopologyResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TopologyResponse) GetTopology calls the GetTopology operation. Example: ```go topology, err := client.GetTopology(ctx) if err != nil { return err } fmt.Printf("gateway %s — %d broker(s), %d partition(s)\n", topology.GetGatewayVersion(), len(topology.GetBrokers()), topology.GetPartitionsCount()) ``` ### GetUsageMetrics ```go func (c *CamundaClient) GetUsageMetrics(ctx context.Context, opts ...func(ApiGetUsageMetricsRequest) ApiGetUsageMetricsRequest) (*UsageMetricsResponse, error) ``` **Types:** [`ApiGetUsageMetricsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetUsageMetricsRequest), [`UsageMetricsResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UsageMetricsResponse) GetUsageMetrics calls the GetUsageMetrics operation. Example: ```go metrics, err := client.GetUsageMetrics(ctx) if err != nil { return err } fmt.Printf("%v\n", metrics) ``` ### GetUser ```go func (c *CamundaClient) GetUser(ctx context.Context, username string, opts ...func(ApiGetUserRequest) ApiGetUserRequest) (*UserResult, error) ``` **Types:** [`ApiGetUserRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetUserRequest), [`UserResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserResult) GetUser calls the GetUser operation. Example: ```go user, err := client.GetUser(ctx, "alice") if err != nil { return err } fmt.Printf("%v\n", user) ``` ### GetUserTask ```go func (c *CamundaClient) GetUserTask(ctx context.Context, userTaskKey UserTaskKey, opts ...func(ApiGetUserTaskRequest) ApiGetUserTaskRequest) (*UserTaskResult, error) ``` **Types:** [`UserTaskKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserTaskKey), [`ApiGetUserTaskRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetUserTaskRequest), [`UserTaskResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserTaskResult) GetUserTask calls the GetUserTask operation. Example: ```go task, err := client.GetUserTask(ctx, camunda.MustUserTaskKey("2251799813685380")) if err != nil { return err } fmt.Printf("%v\n", task) ``` ### GetUserTaskForm ```go func (c *CamundaClient) GetUserTaskForm(ctx context.Context, userTaskKey UserTaskKey, opts ...func(ApiGetUserTaskFormRequest) ApiGetUserTaskFormRequest) (*FormResult, error) ``` **Types:** [`UserTaskKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserTaskKey), [`ApiGetUserTaskFormRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetUserTaskFormRequest), [`FormResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#FormResult) GetUserTaskForm calls the GetUserTaskForm operation. Example: ```go form, err := client.GetUserTaskForm(ctx, camunda.MustUserTaskKey("2251799813685380")) if err != nil { return err } fmt.Printf("%v\n", form) ``` ### GetVariable ```go func (c *CamundaClient) GetVariable(ctx context.Context, variableKey VariableKey, opts ...func(ApiGetVariableRequest) ApiGetVariableRequest) (*VariableResult, error) ``` **Types:** [`VariableKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#VariableKey), [`ApiGetVariableRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiGetVariableRequest), [`VariableResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#VariableResult) GetVariable calls the GetVariable operation. Example: ```go variable, err := client.GetVariable(ctx, camunda.MustVariableKey("2251799813685390")) if err != nil { return err } fmt.Printf("%v\n", variable) ``` ### ListHistoryBackups ```go func (c *CamundaClient) ListHistoryBackups(ctx context.Context, opts ...func(ApiListHistoryBackupsRequest) ApiListHistoryBackupsRequest) ([]HistoryBackupInfo, error) ``` **Types:** [`ApiListHistoryBackupsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiListHistoryBackupsRequest), [`HistoryBackupInfo`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#HistoryBackupInfo) ListHistoryBackups calls the ListHistoryBackups operation. Example: ```go backups, err := client.ListHistoryBackups(ctx) if err != nil { return err } for _, backup := range backups { fmt.Printf("history backup %d is %v\n", backup.GetBackupId(), backup.GetState()) } ``` ### ListHistoryBackupsAsClusterAdmin ```go func (c *CamundaClient) ListHistoryBackupsAsClusterAdmin(ctx context.Context, opts ...func(ApiListHistoryBackupsAsClusterAdminRequest) ApiListHistoryBackupsAsClusterAdminRequest) ([]ClusterHistoryBackupInfo, error) ``` **Types:** [`ApiListHistoryBackupsAsClusterAdminRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiListHistoryBackupsAsClusterAdminRequest), [`ClusterHistoryBackupInfo`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterHistoryBackupInfo) ListHistoryBackupsAsClusterAdmin calls the ListHistoryBackupsAsClusterAdmin operation. Example: ```go // Lists history backups across all physical tenants in the cluster. backups, err := client.ListHistoryBackupsAsClusterAdmin(ctx) if err != nil { return err } for _, backup := range backups { fmt.Printf("cluster history backup %d: %d tenant(s)\n", backup.GetBackupId(), len(backup.GetPhysicalTenants())) } ``` ### ListRuntimeBackups ```go func (c *CamundaClient) ListRuntimeBackups(ctx context.Context, opts ...func(ApiListRuntimeBackupsRequest) ApiListRuntimeBackupsRequest) ([]BackupInfo, error) ``` **Types:** [`ApiListRuntimeBackupsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiListRuntimeBackupsRequest), [`BackupInfo`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#BackupInfo) ListRuntimeBackups calls the ListRuntimeBackups operation. Example: ```go backups, err := client.ListRuntimeBackups(ctx) if err != nil { return err } for _, backup := range backups { fmt.Printf("backup %v is %v\n", backup.GetBackupId(), backup.GetState()) } ``` ### ListRuntimeBackupsAsClusterAdmin ```go func (c *CamundaClient) ListRuntimeBackupsAsClusterAdmin(ctx context.Context, opts ...func(ApiListRuntimeBackupsAsClusterAdminRequest) ApiListRuntimeBackupsAsClusterAdminRequest) ([]ClusterRuntimeBackupInfo, error) ``` **Types:** [`ApiListRuntimeBackupsAsClusterAdminRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiListRuntimeBackupsAsClusterAdminRequest), [`ClusterRuntimeBackupInfo`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterRuntimeBackupInfo) ListRuntimeBackupsAsClusterAdmin calls the ListRuntimeBackupsAsClusterAdmin operation. Example: ```go // Lists runtime backups across all physical tenants in the cluster. backups, err := client.ListRuntimeBackupsAsClusterAdmin(ctx) if err != nil { return err } for _, backup := range backups { fmt.Printf("cluster runtime backup %d: state=%v, %d tenant(s)\n", backup.GetBackupId(), backup.GetState(), len(backup.GetPhysicalTenants())) } ``` ### ListSecrets ```go func (c *CamundaClient) ListSecrets(ctx context.Context, opts ...func(ApiListSecretsRequest) ApiListSecretsRequest) (*SecretListResult, error) ``` **Types:** [`ApiListSecretsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiListSecretsRequest), [`SecretListResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#SecretListResult) ListSecrets calls the ListSecrets operation. Example: ```go // Returns only the references the caller is authorized to see — never values. result, err := client.ListSecrets(ctx) if err != nil { return err } for _, reference := range result.GetReferences() { fmt.Printf("%v\n", reference) } ``` ### MigrateProcessInstance ```go func (c *CamundaClient) MigrateProcessInstance(ctx context.Context, processInstanceKey ProcessInstanceKey, body ProcessInstanceMigrationInstruction, opts ...func(ApiMigrateProcessInstanceRequest) ApiMigrateProcessInstanceRequest) error ``` **Types:** [`ProcessInstanceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceKey), [`ProcessInstanceMigrationInstruction`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceMigrationInstruction), [`ApiMigrateProcessInstanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiMigrateProcessInstanceRequest) MigrateProcessInstance calls the MigrateProcessInstance operation. Example: ```go instruction := camunda.NewProcessInstanceMigrationInstruction( camunda.ProcessDefinitionKey("2251799813685399"), []camunda.MigrateProcessInstanceMappingInstruction{ *camunda.NewMigrateProcessInstanceMappingInstruction("review", "review-v2"), }) return client.MigrateProcessInstance(ctx, camunda.MustProcessInstanceKey("2251799813685340"), *instruction) ``` ### MigrateProcessInstancesBatchOperation ```go func (c *CamundaClient) MigrateProcessInstancesBatchOperation(ctx context.Context, body ProcessInstanceMigrationBatchOperationRequest, opts ...func(ApiMigrateProcessInstancesBatchOperationRequest) ApiMigrateProcessInstancesBatchOperationRequest) (*BatchOperationCreatedResult, error) ``` **Types:** [`ProcessInstanceMigrationBatchOperationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceMigrationBatchOperationRequest), [`ApiMigrateProcessInstancesBatchOperationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiMigrateProcessInstancesBatchOperationRequest), [`BatchOperationCreatedResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#BatchOperationCreatedResult) MigrateProcessInstancesBatchOperation calls the MigrateProcessInstancesBatchOperation operation. Example: ```go plan := camunda.NewProcessInstanceMigrationBatchOperationPlan( camunda.ProcessDefinitionKey("2251799813685399"), []camunda.MigrateProcessInstanceMappingInstruction{ *camunda.NewMigrateProcessInstanceMappingInstruction("review", "review-v2"), }) req := camunda.NewProcessInstanceMigrationBatchOperationRequest(*camunda.NewProcessInstanceFilter(), *plan) result, err := client.MigrateProcessInstancesBatchOperation(ctx, *req) if err != nil { return err } fmt.Printf("created batch operation %v\n", result.GetBatchOperationKey()) ``` ### ModifyProcessInstance ```go func (c *CamundaClient) ModifyProcessInstance(ctx context.Context, processInstanceKey ProcessInstanceKey, body ProcessInstanceModificationInstruction, opts ...func(ApiModifyProcessInstanceRequest) ApiModifyProcessInstanceRequest) error ``` **Types:** [`ProcessInstanceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceKey), [`ProcessInstanceModificationInstruction`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceModificationInstruction), [`ApiModifyProcessInstanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiModifyProcessInstanceRequest) ModifyProcessInstance calls the ModifyProcessInstance operation. Example: ```go return client.ModifyProcessInstance(ctx, camunda.MustProcessInstanceKey("2251799813685340"), *camunda.NewProcessInstanceModificationInstruction()) ``` ### ModifyProcessInstancesBatchOperation ```go func (c *CamundaClient) ModifyProcessInstancesBatchOperation(ctx context.Context, body ProcessInstanceModificationBatchOperationRequest, opts ...func(ApiModifyProcessInstancesBatchOperationRequest) ApiModifyProcessInstancesBatchOperationRequest) (*BatchOperationCreatedResult, error) ``` **Types:** [`ProcessInstanceModificationBatchOperationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceModificationBatchOperationRequest), [`ApiModifyProcessInstancesBatchOperationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiModifyProcessInstancesBatchOperationRequest), [`BatchOperationCreatedResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#BatchOperationCreatedResult) ModifyProcessInstancesBatchOperation calls the ModifyProcessInstancesBatchOperation operation. Example: ```go req := camunda.NewProcessInstanceModificationBatchOperationRequest( *camunda.NewProcessInstanceFilter(), []camunda.ProcessInstanceModificationMoveBatchOperationInstruction{ *camunda.NewProcessInstanceModificationMoveBatchOperationInstruction("review", "approve"), }) result, err := client.ModifyProcessInstancesBatchOperation(ctx, *req) if err != nil { return err } fmt.Printf("created batch operation %v\n", result.GetBatchOperationKey()) ``` ### NewJobWorker ```go func (c *CamundaClient) NewJobWorker(jobType string, handler JobHandler, opts ...WorkerOption) *JobWorker ``` NewJobWorker creates a worker for jobType. Defaults are seeded from the client's CAMUNDA_WORKER_* configuration and can be overridden with options. ### NewStreamJobWorker ```go func (c *CamundaClient) NewStreamJobWorker(jobType string, handler JobHandler, opts ...StreamWorkerOption) *StreamJobWorker ``` NewStreamJobWorker creates a gRPC streaming worker for jobType. Defaults are seeded from the client's CAMUNDA_WORKER_* configuration and can be overridden with options. ### PauseClusterExporting ```go func (c *CamundaClient) PauseClusterExporting(ctx context.Context, opts ...func(ApiPauseClusterExportingRequest) ApiPauseClusterExportingRequest) error ``` **Types:** [`ApiPauseClusterExportingRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiPauseClusterExportingRequest) PauseClusterExporting calls the PauseClusterExporting operation. Example: ```go // Pauses exporting across all physical tenants in the cluster. // While paused, reads from secondary storage stop advancing for every tenant. if err := client.PauseClusterExporting(ctx); err != nil { return err } ``` ### PauseExporting ```go func (c *CamundaClient) PauseExporting(ctx context.Context, opts ...func(ApiPauseExportingRequest) ApiPauseExportingRequest) error ``` **Types:** [`ApiPauseExportingRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiPauseExportingRequest) PauseExporting calls the PauseExporting operation. Example: ```go // While exporting is paused, reads from secondary storage stop advancing. if err := client.PauseExporting(ctx); err != nil { return err } ``` ### PinAt ```go func (c *CamundaClient) PinAt(ctx context.Context, t time.Time) error ``` PinAt moves the engine clock to t. ### PinClock ```go func (c *CamundaClient) PinClock(ctx context.Context, body ClockPinRequest, opts ...func(ApiPinClockRequest) ApiPinClockRequest) error ``` **Types:** [`ClockPinRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClockPinRequest), [`ApiPinClockRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiPinClockRequest) PinClock calls the PinClock operation. Example: ```go // Pin the cluster clock to a fixed instant (epoch milliseconds). pinned := time.Date(2025, time.January, 1, 0, 0, 0, 0, time.UTC) return client.PinClock(ctx, *camunda.NewClockPinRequest(pinned.UnixMilli())) ``` ### PublishMessage ```go func (c *CamundaClient) PublishMessage(ctx context.Context, body MessagePublicationRequest, opts ...func(ApiPublishMessageRequest) ApiPublishMessageRequest) (*MessagePublicationResult, error) ``` **Types:** [`MessagePublicationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#MessagePublicationRequest), [`ApiPublishMessageRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiPublishMessageRequest), [`MessagePublicationResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#MessagePublicationResult) PublishMessage calls the PublishMessage operation. Example: ```go req := camunda.NewMessagePublicationRequest("order-confirmed") req.SetCorrelationKey("order-42") req.SetVariables(map[string]any{"confirmedBy": "payment-service"}) result, err := client.PublishMessage(ctx, *req) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### Random ```go func (c *CamundaClient) Random() Random ``` Random returns the source this client draws jitter from. ### Raw ```go func (c *CamundaClient) Raw() *camundaapi.APIClient ``` Raw returns the underlying generated client for operations or options not yet surfaced on the ergonomic facade. ### ResetClock ```go func (c *CamundaClient) ResetClock(ctx context.Context, opts ...func(ApiResetClockRequest) ApiResetClockRequest) error ``` **Types:** [`ApiResetClockRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiResetClockRequest) ResetClock calls the ResetClock operation. Example: ```go // Release a previously pinned clock back to system time. return client.ResetClock(ctx) ``` ### ResetToLive ```go func (c *CamundaClient) ResetToLive(ctx context.Context) error ``` ResetToLive returns the engine clock to real time. ### ResolveIncident ```go func (c *CamundaClient) ResolveIncident(ctx context.Context, incidentKey IncidentKey, body IncidentResolutionRequest, opts ...func(ApiResolveIncidentRequest) ApiResolveIncidentRequest) error ``` **Types:** [`IncidentKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#IncidentKey), [`IncidentResolutionRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#IncidentResolutionRequest), [`ApiResolveIncidentRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiResolveIncidentRequest) ResolveIncident calls the ResolveIncident operation. Example: ```go // After fixing the root cause (e.g. correcting a variable), resolve the // incident so the engine retries the failed element. return client.ResolveIncident(ctx, camunda.MustIncidentKey("2251799813685300"), *camunda.NewIncidentResolutionRequest()) ``` ### ResolveIncidentsBatchOperation ```go func (c *CamundaClient) ResolveIncidentsBatchOperation(ctx context.Context, body ProcessInstanceIncidentResolutionBatchOperationRequest, opts ...func(ApiResolveIncidentsBatchOperationRequest) ApiResolveIncidentsBatchOperationRequest) (*BatchOperationCreatedResult, error) ``` **Types:** [`ProcessInstanceIncidentResolutionBatchOperationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceIncidentResolutionBatchOperationRequest), [`ApiResolveIncidentsBatchOperationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiResolveIncidentsBatchOperationRequest), [`BatchOperationCreatedResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#BatchOperationCreatedResult) ResolveIncidentsBatchOperation calls the ResolveIncidentsBatchOperation operation. Example: ```go req := camunda.NewProcessInstanceIncidentResolutionBatchOperationRequest(*camunda.NewProcessInstanceFilter()) result, err := client.ResolveIncidentsBatchOperation(ctx, *req) if err != nil { return err } fmt.Printf("created batch operation %v\n", result.GetBatchOperationKey()) ``` ### ResolveProcessInstanceIncidents ```go func (c *CamundaClient) ResolveProcessInstanceIncidents(ctx context.Context, processInstanceKey ProcessInstanceKey, opts ...func(ApiResolveProcessInstanceIncidentsRequest) ApiResolveProcessInstanceIncidentsRequest) (*BatchOperationCreatedResult, error) ``` **Types:** [`ProcessInstanceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceKey), [`ApiResolveProcessInstanceIncidentsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiResolveProcessInstanceIncidentsRequest), [`BatchOperationCreatedResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#BatchOperationCreatedResult) ResolveProcessInstanceIncidents calls the ResolveProcessInstanceIncidents operation. Example: ```go result, err := client.ResolveProcessInstanceIncidents(ctx, camunda.MustProcessInstanceKey("2251799813685340")) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### ResolveSecrets ```go func (c *CamundaClient) ResolveSecrets(ctx context.Context, body SecretResolveRequest, opts ...func(ApiResolveSecretsRequest) ApiResolveSecretsRequest) (*SecretResolveResult, error) ``` **Types:** [`SecretResolveRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#SecretResolveRequest), [`ApiResolveSecretsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiResolveSecretsRequest), [`SecretResolveResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#SecretResolveResult) ResolveSecrets calls the ResolveSecrets operation. Example: ```go // References take the form `camunda.secrets.`. req := camunda.NewSecretResolveRequest([]string{"camunda.secrets.MY_API_KEY", "camunda.secrets.MY_TOKEN"}) result, err := client.ResolveSecrets(ctx, *req) if err != nil { return err } for _, secret := range result.GetResolved() { fmt.Printf("%v = %v\n", secret.GetReference(), secret.GetValue()) } ``` ### Restore ```go func (c *CamundaClient) Restore(ctx context.Context, body RestoreRequest, opts ...func(ApiRestoreRequest) ApiRestoreRequest) (*ClusterRestoreResponse, error) ``` **Types:** [`RestoreRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#RestoreRequest), [`ApiRestoreRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiRestoreRequest), [`ClusterRestoreResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterRestoreResponse) Restore calls the Restore operation. Example: ```go result, err := client.Restore(ctx, *camunda.NewRestoreRequest()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### RestoreAsClusterAdmin ```go func (c *CamundaClient) RestoreAsClusterAdmin(ctx context.Context, body ClusterRestoreRequest, opts ...func(ApiRestoreAsClusterAdminRequest) ApiRestoreAsClusterAdminRequest) (*ClusterRestoreResponse, error) ``` **Types:** [`ClusterRestoreRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterRestoreRequest), [`ApiRestoreAsClusterAdminRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiRestoreAsClusterAdminRequest), [`ClusterRestoreResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterRestoreResponse) RestoreAsClusterAdmin calls the RestoreAsClusterAdmin operation. Example: ```go // Triggers a cluster-level restore (cluster-admin authority), restoring from the given backup IDs. // backupIds are one per partition, so the placeholder slice below must be extended to // match the actual partition count of the target cluster (shown here for a 2-partition cluster). restoreRequest := camunda.NewClusterRestoreRequestWithDefaults() restoreRequest.SetBackupIds([]int64{1, 2}) result, err := client.RestoreAsClusterAdmin(ctx, *restoreRequest) if err != nil { return err } fmt.Printf("restore change id: %s\n", result.GetChangeId()) ``` ### ResumeBatchOperation ```go func (c *CamundaClient) ResumeBatchOperation(ctx context.Context, batchOperationKey string, opts ...func(ApiResumeBatchOperationRequest) ApiResumeBatchOperationRequest) error ``` **Types:** [`ApiResumeBatchOperationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiResumeBatchOperationRequest) ResumeBatchOperation calls the ResumeBatchOperation operation. Example: ```go return client.ResumeBatchOperation(ctx, "2251799813685290") ``` ### ResumeClusterExporting ```go func (c *CamundaClient) ResumeClusterExporting(ctx context.Context, opts ...func(ApiResumeClusterExportingRequest) ApiResumeClusterExportingRequest) error ``` **Types:** [`ApiResumeClusterExportingRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiResumeClusterExportingRequest) ResumeClusterExporting calls the ResumeClusterExporting operation. Example: ```go // Resumes exporting across all physical tenants in the cluster. if err := client.ResumeClusterExporting(ctx); err != nil { return err } ``` ### ResumeExporting ```go func (c *CamundaClient) ResumeExporting(ctx context.Context, opts ...func(ApiResumeExportingRequest) ApiResumeExportingRequest) error ``` **Types:** [`ApiResumeExportingRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiResumeExportingRequest) ResumeExporting calls the ResumeExporting operation. Example: ```go if err := client.ResumeExporting(ctx); err != nil { return err } ``` ### ResumeProcessInstance ```go func (c *CamundaClient) ResumeProcessInstance(ctx context.Context, processInstanceKey ProcessInstanceKey, body ResumeProcessInstanceRequest, opts ...func(ApiResumeProcessInstanceRequest) ApiResumeProcessInstanceRequest) error ``` **Types:** [`ProcessInstanceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceKey), [`ResumeProcessInstanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ResumeProcessInstanceRequest), [`ApiResumeProcessInstanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiResumeProcessInstanceRequest) ResumeProcessInstance calls the ResumeProcessInstance operation. Example: ```go return client.ResumeProcessInstance(ctx, camunda.MustProcessInstanceKey("2251799813685340"), *camunda.NewResumeProcessInstanceRequest()) ``` ### ResumeProcessInstancesBatchOperation ```go func (c *CamundaClient) ResumeProcessInstancesBatchOperation(ctx context.Context, body ProcessInstanceResumptionBatchOperationRequest, opts ...func(ApiResumeProcessInstancesBatchOperationRequest) ApiResumeProcessInstancesBatchOperationRequest) (*BatchOperationCreatedResult, error) ``` **Types:** [`ProcessInstanceResumptionBatchOperationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceResumptionBatchOperationRequest), [`ApiResumeProcessInstancesBatchOperationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiResumeProcessInstancesBatchOperationRequest), [`BatchOperationCreatedResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#BatchOperationCreatedResult) ResumeProcessInstancesBatchOperation calls the ResumeProcessInstancesBatchOperation operation. Example: ```go // Resume every previously-suspended instance matching a filter. req := camunda.NewProcessInstanceResumptionBatchOperationRequest(*camunda.NewProcessInstanceFilter()) result, err := client.ResumeProcessInstancesBatchOperation(ctx, *req) if err != nil { return err } fmt.Printf("created batch operation %v\n", result.GetBatchOperationKey()) ``` ### SearchAgentDefinitions ```go func (c *CamundaClient) SearchAgentDefinitions(ctx context.Context, body AgentDefinitionSearchQuery, opts ...func(ApiSearchAgentDefinitionsRequest) ApiSearchAgentDefinitionsRequest) (*AgentDefinitionSearchQueryResult, error) ``` **Types:** [`AgentDefinitionSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AgentDefinitionSearchQuery), [`ApiSearchAgentDefinitionsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchAgentDefinitionsRequest), [`AgentDefinitionSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AgentDefinitionSearchQueryResult) SearchAgentDefinitions calls the SearchAgentDefinitions operation. Example: ```go result, err := client.SearchAgentDefinitions(ctx, *camunda.NewAgentDefinitionSearchQuery()) if err != nil { return err } for _, d := range result.GetItems() { fmt.Printf("%v\n", d) } ``` ### SearchAgentInstanceHistory ```go func (c *CamundaClient) SearchAgentInstanceHistory(ctx context.Context, agentInstanceKey AgentInstanceKey, body AgentInstanceHistorySearchQuery, opts ...func(ApiSearchAgentInstanceHistoryRequest) ApiSearchAgentInstanceHistoryRequest) (*AgentInstanceHistorySearchQueryResult, error) ``` **Types:** [`AgentInstanceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AgentInstanceKey), [`AgentInstanceHistorySearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AgentInstanceHistorySearchQuery), [`ApiSearchAgentInstanceHistoryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchAgentInstanceHistoryRequest), [`AgentInstanceHistorySearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AgentInstanceHistorySearchQueryResult) SearchAgentInstanceHistory calls the SearchAgentInstanceHistory operation. Example: ```go result, err := client.SearchAgentInstanceHistory(ctx, camunda.MustAgentInstanceKey("2251799813685370"), *camunda.NewAgentInstanceHistorySearchQuery()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### SearchAgentInstances ```go func (c *CamundaClient) SearchAgentInstances(ctx context.Context, body AgentInstanceSearchQuery, opts ...func(ApiSearchAgentInstancesRequest) ApiSearchAgentInstancesRequest) (*AgentInstanceSearchQueryResult, error) ``` **Types:** [`AgentInstanceSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AgentInstanceSearchQuery), [`ApiSearchAgentInstancesRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchAgentInstancesRequest), [`AgentInstanceSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AgentInstanceSearchQueryResult) SearchAgentInstances calls the SearchAgentInstances operation. Example: ```go result, err := client.SearchAgentInstances(ctx, *camunda.NewAgentInstanceSearchQuery()) if err != nil { return err } for _, a := range result.GetItems() { fmt.Printf("%v\n", a) } ``` ### SearchAuditLogs ```go func (c *CamundaClient) SearchAuditLogs(ctx context.Context, body AuditLogSearchQueryRequest, opts ...func(ApiSearchAuditLogsRequest) ApiSearchAuditLogsRequest) (*AuditLogSearchQueryResult, error) ``` **Types:** [`AuditLogSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AuditLogSearchQueryRequest), [`ApiSearchAuditLogsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchAuditLogsRequest), [`AuditLogSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AuditLogSearchQueryResult) SearchAuditLogs calls the SearchAuditLogs operation. Example: ```go result, err := client.SearchAuditLogs(ctx, *camunda.NewAuditLogSearchQueryRequest()) if err != nil { return err } for _, entry := range result.GetItems() { fmt.Printf("%v\n", entry) } ``` ### SearchAuthorizations ```go func (c *CamundaClient) SearchAuthorizations(ctx context.Context, body AuthorizationSearchQuery, opts ...func(ApiSearchAuthorizationsRequest) ApiSearchAuthorizationsRequest) (*AuthorizationSearchResult, error) ``` **Types:** [`AuthorizationSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AuthorizationSearchQuery), [`ApiSearchAuthorizationsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchAuthorizationsRequest), [`AuthorizationSearchResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AuthorizationSearchResult) SearchAuthorizations calls the SearchAuthorizations operation. Example: ```go result, err := client.SearchAuthorizations(ctx, *camunda.NewAuthorizationSearchQuery()) if err != nil { return err } for _, a := range result.GetItems() { fmt.Printf("%v\n", a) } ``` ### SearchBatchOperationItems ```go func (c *CamundaClient) SearchBatchOperationItems(ctx context.Context, body BatchOperationItemSearchQuery, opts ...func(ApiSearchBatchOperationItemsRequest) ApiSearchBatchOperationItemsRequest) (*BatchOperationItemSearchQueryResult, error) ``` **Types:** [`BatchOperationItemSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#BatchOperationItemSearchQuery), [`ApiSearchBatchOperationItemsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchBatchOperationItemsRequest), [`BatchOperationItemSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#BatchOperationItemSearchQueryResult) SearchBatchOperationItems calls the SearchBatchOperationItems operation. Example: ```go result, err := client.SearchBatchOperationItems(ctx, *camunda.NewBatchOperationItemSearchQuery()) if err != nil { return err } for _, item := range result.GetItems() { fmt.Printf("%v\n", item) } ``` ### SearchBatchOperations ```go func (c *CamundaClient) SearchBatchOperations(ctx context.Context, body BatchOperationSearchQuery, opts ...func(ApiSearchBatchOperationsRequest) ApiSearchBatchOperationsRequest) (*BatchOperationSearchQueryResult, error) ``` **Types:** [`BatchOperationSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#BatchOperationSearchQuery), [`ApiSearchBatchOperationsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchBatchOperationsRequest), [`BatchOperationSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#BatchOperationSearchQueryResult) SearchBatchOperations calls the SearchBatchOperations operation. Example: ```go result, err := client.SearchBatchOperations(ctx, *camunda.NewBatchOperationSearchQuery()) if err != nil { return err } for _, op := range result.GetItems() { fmt.Printf("%v\n", op) } ``` ### SearchClientsForGroup ```go func (c *CamundaClient) SearchClientsForGroup(ctx context.Context, groupId string, body GroupClientSearchQueryRequest, opts ...func(ApiSearchClientsForGroupRequest) ApiSearchClientsForGroupRequest) (*GroupClientSearchResult, error) ``` **Types:** [`GroupClientSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#GroupClientSearchQueryRequest), [`ApiSearchClientsForGroupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchClientsForGroupRequest), [`GroupClientSearchResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#GroupClientSearchResult) SearchClientsForGroup calls the SearchClientsForGroup operation. Example: ```go result, err := client.SearchClientsForGroup(ctx, "finance", *camunda.NewGroupClientSearchQueryRequest()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### SearchClientsForRole ```go func (c *CamundaClient) SearchClientsForRole(ctx context.Context, roleId string, body RoleClientSearchQueryRequest, opts ...func(ApiSearchClientsForRoleRequest) ApiSearchClientsForRoleRequest) (*RoleClientSearchResult, error) ``` **Types:** [`RoleClientSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#RoleClientSearchQueryRequest), [`ApiSearchClientsForRoleRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchClientsForRoleRequest), [`RoleClientSearchResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#RoleClientSearchResult) SearchClientsForRole calls the SearchClientsForRole operation. Example: ```go result, err := client.SearchClientsForRole(ctx, "auditor", *camunda.NewRoleClientSearchQueryRequest()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### SearchClientsForTenant ```go func (c *CamundaClient) SearchClientsForTenant(ctx context.Context, tenantId string, body TenantClientSearchQueryRequest, opts ...func(ApiSearchClientsForTenantRequest) ApiSearchClientsForTenantRequest) (*TenantClientSearchResult, error) ``` **Types:** [`TenantClientSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TenantClientSearchQueryRequest), [`ApiSearchClientsForTenantRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchClientsForTenantRequest), [`TenantClientSearchResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TenantClientSearchResult) SearchClientsForTenant calls the SearchClientsForTenant operation. Example: ```go result, err := client.SearchClientsForTenant(ctx, "tenant-a", *camunda.NewTenantClientSearchQueryRequest()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### SearchClusterVariables ```go func (c *CamundaClient) SearchClusterVariables(ctx context.Context, body ClusterVariableSearchQueryRequest, opts ...func(ApiSearchClusterVariablesRequest) ApiSearchClusterVariablesRequest) (*ClusterVariableSearchQueryResult, error) ``` **Types:** [`ClusterVariableSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterVariableSearchQueryRequest), [`ApiSearchClusterVariablesRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchClusterVariablesRequest), [`ClusterVariableSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterVariableSearchQueryResult) SearchClusterVariables calls the SearchClusterVariables operation. Example: ```go result, err := client.SearchClusterVariables(ctx, *camunda.NewClusterVariableSearchQueryRequest()) if err != nil { return err } for _, v := range result.GetItems() { fmt.Printf("%v\n", v) } ``` ### SearchCorrelatedMessageSubscriptions ```go func (c *CamundaClient) SearchCorrelatedMessageSubscriptions(ctx context.Context, body CorrelatedMessageSubscriptionSearchQuery, opts ...func(ApiSearchCorrelatedMessageSubscriptionsRequest) ApiSearchCorrelatedMessageSubscriptionsRequest) (*CorrelatedMessageSubscriptionSearchQueryResult, error) ``` **Types:** [`CorrelatedMessageSubscriptionSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#CorrelatedMessageSubscriptionSearchQuery), [`ApiSearchCorrelatedMessageSubscriptionsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchCorrelatedMessageSubscriptionsRequest), [`CorrelatedMessageSubscriptionSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#CorrelatedMessageSubscriptionSearchQueryResult) SearchCorrelatedMessageSubscriptions calls the SearchCorrelatedMessageSubscriptions operation. Example: ```go result, err := client.SearchCorrelatedMessageSubscriptions(ctx, *camunda.NewCorrelatedMessageSubscriptionSearchQuery()) if err != nil { return err } for _, s := range result.GetItems() { fmt.Printf("%v\n", s) } ``` ### SearchDecisionDefinitions ```go func (c *CamundaClient) SearchDecisionDefinitions(ctx context.Context, body DecisionDefinitionSearchQuery, opts ...func(ApiSearchDecisionDefinitionsRequest) ApiSearchDecisionDefinitionsRequest) (*DecisionDefinitionSearchQueryResult, error) ``` **Types:** [`DecisionDefinitionSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DecisionDefinitionSearchQuery), [`ApiSearchDecisionDefinitionsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchDecisionDefinitionsRequest), [`DecisionDefinitionSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DecisionDefinitionSearchQueryResult) SearchDecisionDefinitions calls the SearchDecisionDefinitions operation. Example: ```go result, err := client.SearchDecisionDefinitions(ctx, *camunda.NewDecisionDefinitionSearchQuery()) if err != nil { return err } for _, d := range result.GetItems() { fmt.Printf("%v\n", d) } ``` ### SearchDecisionInstances ```go func (c *CamundaClient) SearchDecisionInstances(ctx context.Context, body DecisionInstanceSearchQuery, opts ...func(ApiSearchDecisionInstancesRequest) ApiSearchDecisionInstancesRequest) (*DecisionInstanceSearchQueryResult, error) ``` **Types:** [`DecisionInstanceSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DecisionInstanceSearchQuery), [`ApiSearchDecisionInstancesRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchDecisionInstancesRequest), [`DecisionInstanceSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DecisionInstanceSearchQueryResult) SearchDecisionInstances calls the SearchDecisionInstances operation. Example: ```go result, err := client.SearchDecisionInstances(ctx, *camunda.NewDecisionInstanceSearchQuery()) if err != nil { return err } for _, d := range result.GetItems() { fmt.Printf("%v\n", d) } ``` ### SearchDecisionRequirements ```go func (c *CamundaClient) SearchDecisionRequirements(ctx context.Context, body DecisionRequirementsSearchQuery, opts ...func(ApiSearchDecisionRequirementsRequest) ApiSearchDecisionRequirementsRequest) (*DecisionRequirementsSearchQueryResult, error) ``` **Types:** [`DecisionRequirementsSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DecisionRequirementsSearchQuery), [`ApiSearchDecisionRequirementsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchDecisionRequirementsRequest), [`DecisionRequirementsSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#DecisionRequirementsSearchQueryResult) SearchDecisionRequirements calls the SearchDecisionRequirements operation. Example: ```go result, err := client.SearchDecisionRequirements(ctx, *camunda.NewDecisionRequirementsSearchQuery()) if err != nil { return err } for _, d := range result.GetItems() { fmt.Printf("%v\n", d) } ``` ### SearchElementInstanceIncidents ```go func (c *CamundaClient) SearchElementInstanceIncidents(ctx context.Context, elementInstanceKey ElementInstanceKey, body IncidentSearchQuery, opts ...func(ApiSearchElementInstanceIncidentsRequest) ApiSearchElementInstanceIncidentsRequest) (*IncidentSearchQueryResult, error) ``` **Types:** [`ElementInstanceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ElementInstanceKey), [`IncidentSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#IncidentSearchQuery), [`ApiSearchElementInstanceIncidentsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchElementInstanceIncidentsRequest), [`IncidentSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#IncidentSearchQueryResult) SearchElementInstanceIncidents calls the SearchElementInstanceIncidents operation. Example: ```go result, err := client.SearchElementInstanceIncidents(ctx, camunda.MustElementInstanceKey("2251799813685360"), *camunda.NewIncidentSearchQuery()) if err != nil { return err } for _, inc := range result.GetItems() { fmt.Printf("%v\n", inc) } ``` ### SearchElementInstanceWaitStates ```go func (c *CamundaClient) SearchElementInstanceWaitStates(ctx context.Context, body ElementInstanceWaitStateQuery, opts ...func(ApiSearchElementInstanceWaitStatesRequest) ApiSearchElementInstanceWaitStatesRequest) (*ElementInstanceWaitStateQueryResult, error) ``` **Types:** [`ElementInstanceWaitStateQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ElementInstanceWaitStateQuery), [`ApiSearchElementInstanceWaitStatesRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchElementInstanceWaitStatesRequest), [`ElementInstanceWaitStateQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ElementInstanceWaitStateQueryResult) SearchElementInstanceWaitStates calls the SearchElementInstanceWaitStates operation. Example: ```go result, err := client.SearchElementInstanceWaitStates(ctx, *camunda.NewElementInstanceWaitStateQuery()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### SearchElementInstances ```go func (c *CamundaClient) SearchElementInstances(ctx context.Context, body ElementInstanceSearchQuery, opts ...func(ApiSearchElementInstancesRequest) ApiSearchElementInstancesRequest) (*ElementInstanceSearchQueryResult, error) ``` **Types:** [`ElementInstanceSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ElementInstanceSearchQuery), [`ApiSearchElementInstancesRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchElementInstancesRequest), [`ElementInstanceSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ElementInstanceSearchQueryResult) SearchElementInstances calls the SearchElementInstances operation. Example: ```go result, err := client.SearchElementInstances(ctx, *camunda.NewElementInstanceSearchQuery()) if err != nil { return err } for _, e := range result.GetItems() { fmt.Printf("%v\n", e) } ``` ### SearchGlobalTaskListeners ```go func (c *CamundaClient) SearchGlobalTaskListeners(ctx context.Context, body GlobalTaskListenerSearchQueryRequest, opts ...func(ApiSearchGlobalTaskListenersRequest) ApiSearchGlobalTaskListenersRequest) (*GlobalTaskListenerSearchQueryResult, error) ``` **Types:** [`GlobalTaskListenerSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#GlobalTaskListenerSearchQueryRequest), [`ApiSearchGlobalTaskListenersRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchGlobalTaskListenersRequest), [`GlobalTaskListenerSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#GlobalTaskListenerSearchQueryResult) SearchGlobalTaskListeners calls the SearchGlobalTaskListeners operation. Example: ```go result, err := client.SearchGlobalTaskListeners(ctx, *camunda.NewGlobalTaskListenerSearchQueryRequest()) if err != nil { return err } for _, l := range result.GetItems() { fmt.Printf("%v\n", l) } ``` ### SearchGroupIdsForTenant ```go func (c *CamundaClient) SearchGroupIdsForTenant(ctx context.Context, tenantId string, body TenantGroupSearchQueryRequest, opts ...func(ApiSearchGroupIdsForTenantRequest) ApiSearchGroupIdsForTenantRequest) (*TenantGroupSearchResult, error) ``` **Types:** [`TenantGroupSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TenantGroupSearchQueryRequest), [`ApiSearchGroupIdsForTenantRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchGroupIdsForTenantRequest), [`TenantGroupSearchResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TenantGroupSearchResult) SearchGroupIdsForTenant calls the SearchGroupIdsForTenant operation. Example: ```go result, err := client.SearchGroupIdsForTenant(ctx, "tenant-a", *camunda.NewTenantGroupSearchQueryRequest()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### SearchGroups ```go func (c *CamundaClient) SearchGroups(ctx context.Context, body GroupSearchQueryRequest, opts ...func(ApiSearchGroupsRequest) ApiSearchGroupsRequest) (*GroupSearchQueryResult, error) ``` **Types:** [`GroupSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#GroupSearchQueryRequest), [`ApiSearchGroupsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchGroupsRequest), [`GroupSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#GroupSearchQueryResult) SearchGroups calls the SearchGroups operation. Example: ```go result, err := client.SearchGroups(ctx, *camunda.NewGroupSearchQueryRequest()) if err != nil { return err } for _, g := range result.GetItems() { fmt.Printf("%v\n", g) } ``` ### SearchGroupsForRole ```go func (c *CamundaClient) SearchGroupsForRole(ctx context.Context, roleId string, body RoleGroupSearchQueryRequest, opts ...func(ApiSearchGroupsForRoleRequest) ApiSearchGroupsForRoleRequest) (*RoleGroupSearchResult, error) ``` **Types:** [`RoleGroupSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#RoleGroupSearchQueryRequest), [`ApiSearchGroupsForRoleRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchGroupsForRoleRequest), [`RoleGroupSearchResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#RoleGroupSearchResult) SearchGroupsForRole calls the SearchGroupsForRole operation. Example: ```go result, err := client.SearchGroupsForRole(ctx, "auditor", *camunda.NewRoleGroupSearchQueryRequest()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### SearchIncidents ```go func (c *CamundaClient) SearchIncidents(ctx context.Context, body IncidentSearchQuery, opts ...func(ApiSearchIncidentsRequest) ApiSearchIncidentsRequest) (*IncidentSearchQueryResult, error) ``` **Types:** [`IncidentSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#IncidentSearchQuery), [`ApiSearchIncidentsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchIncidentsRequest), [`IncidentSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#IncidentSearchQueryResult) SearchIncidents calls the SearchIncidents operation. Example: ```go result, err := client.SearchIncidents(ctx, *camunda.NewIncidentSearchQuery()) if err != nil { return err } for _, inc := range result.GetItems() { fmt.Printf("incident %v: %s\n", inc.GetIncidentKey(), inc.GetErrorType()) } ``` ### SearchJobs ```go func (c *CamundaClient) SearchJobs(ctx context.Context, body JobSearchQuery, opts ...func(ApiSearchJobsRequest) ApiSearchJobsRequest) (*JobSearchQueryResult, error) ``` **Types:** [`JobSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#JobSearchQuery), [`ApiSearchJobsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchJobsRequest), [`JobSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#JobSearchQueryResult) SearchJobs calls the SearchJobs operation. Example: ```go result, err := client.SearchJobs(ctx, *camunda.NewJobSearchQuery()) if err != nil { return err } for _, job := range result.GetItems() { fmt.Printf("%v\n", job) } ``` ### SearchMappingRule ```go func (c *CamundaClient) SearchMappingRule(ctx context.Context, body MappingRuleSearchQueryRequest, opts ...func(ApiSearchMappingRuleRequest) ApiSearchMappingRuleRequest) (*MappingRuleSearchQueryResult, error) ``` **Types:** [`MappingRuleSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#MappingRuleSearchQueryRequest), [`ApiSearchMappingRuleRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchMappingRuleRequest), [`MappingRuleSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#MappingRuleSearchQueryResult) SearchMappingRule calls the SearchMappingRule operation. Example: ```go result, err := client.SearchMappingRule(ctx, *camunda.NewMappingRuleSearchQueryRequest()) if err != nil { return err } for _, r := range result.GetItems() { fmt.Printf("%v\n", r) } ``` ### SearchMappingRulesForGroup ```go func (c *CamundaClient) SearchMappingRulesForGroup(ctx context.Context, groupId string, body MappingRuleSearchQueryRequest, opts ...func(ApiSearchMappingRulesForGroupRequest) ApiSearchMappingRulesForGroupRequest) (*GroupMappingRuleSearchResult, error) ``` **Types:** [`MappingRuleSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#MappingRuleSearchQueryRequest), [`ApiSearchMappingRulesForGroupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchMappingRulesForGroupRequest), [`GroupMappingRuleSearchResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#GroupMappingRuleSearchResult) SearchMappingRulesForGroup calls the SearchMappingRulesForGroup operation. Example: ```go result, err := client.SearchMappingRulesForGroup(ctx, "finance", *camunda.NewMappingRuleSearchQueryRequest()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### SearchMappingRulesForRole ```go func (c *CamundaClient) SearchMappingRulesForRole(ctx context.Context, roleId string, body MappingRuleSearchQueryRequest, opts ...func(ApiSearchMappingRulesForRoleRequest) ApiSearchMappingRulesForRoleRequest) (*RoleMappingRuleSearchResult, error) ``` **Types:** [`MappingRuleSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#MappingRuleSearchQueryRequest), [`ApiSearchMappingRulesForRoleRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchMappingRulesForRoleRequest), [`RoleMappingRuleSearchResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#RoleMappingRuleSearchResult) SearchMappingRulesForRole calls the SearchMappingRulesForRole operation. Example: ```go result, err := client.SearchMappingRulesForRole(ctx, "auditor", *camunda.NewMappingRuleSearchQueryRequest()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### SearchMappingRulesForTenant ```go func (c *CamundaClient) SearchMappingRulesForTenant(ctx context.Context, tenantId string, body MappingRuleSearchQueryRequest, opts ...func(ApiSearchMappingRulesForTenantRequest) ApiSearchMappingRulesForTenantRequest) (*TenantMappingRuleSearchResult, error) ``` **Types:** [`MappingRuleSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#MappingRuleSearchQueryRequest), [`ApiSearchMappingRulesForTenantRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchMappingRulesForTenantRequest), [`TenantMappingRuleSearchResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TenantMappingRuleSearchResult) SearchMappingRulesForTenant calls the SearchMappingRulesForTenant operation. Example: ```go result, err := client.SearchMappingRulesForTenant(ctx, "tenant-a", *camunda.NewMappingRuleSearchQueryRequest()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### SearchMessageSubscriptions ```go func (c *CamundaClient) SearchMessageSubscriptions(ctx context.Context, body MessageSubscriptionSearchQuery, opts ...func(ApiSearchMessageSubscriptionsRequest) ApiSearchMessageSubscriptionsRequest) (*MessageSubscriptionSearchQueryResult, error) ``` **Types:** [`MessageSubscriptionSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#MessageSubscriptionSearchQuery), [`ApiSearchMessageSubscriptionsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchMessageSubscriptionsRequest), [`MessageSubscriptionSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#MessageSubscriptionSearchQueryResult) SearchMessageSubscriptions calls the SearchMessageSubscriptions operation. Example: ```go result, err := client.SearchMessageSubscriptions(ctx, *camunda.NewMessageSubscriptionSearchQuery()) if err != nil { return err } for _, s := range result.GetItems() { fmt.Printf("%v\n", s) } ``` ### SearchOwnAuthorizations ```go func (c *CamundaClient) SearchOwnAuthorizations(ctx context.Context, body AuthorizationSearchQuery, opts ...func(ApiSearchOwnAuthorizationsRequest) ApiSearchOwnAuthorizationsRequest) (*OwnAuthorizationSearchResult, error) ``` **Types:** [`AuthorizationSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AuthorizationSearchQuery), [`ApiSearchOwnAuthorizationsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchOwnAuthorizationsRequest), [`OwnAuthorizationSearchResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#OwnAuthorizationSearchResult) SearchOwnAuthorizations calls the SearchOwnAuthorizations operation. Example: ```go // Scoped to the authenticated principal: direct grants plus those inherited // from a group, role, or mapping rule. result, err := client.SearchOwnAuthorizations(ctx, *camunda.NewAuthorizationSearchQuery()) if err != nil { return err } for _, a := range result.GetItems() { fmt.Printf("%v\n", a) } ``` ### SearchProcessDefinitionVariableNames ```go func (c *CamundaClient) SearchProcessDefinitionVariableNames(ctx context.Context, processDefinitionKey ProcessDefinitionKey, body ProcessDefinitionVariableNameSearchQuery, opts ...func(ApiSearchProcessDefinitionVariableNamesRequest) ApiSearchProcessDefinitionVariableNamesRequest) (*ProcessDefinitionVariableNameSearchQueryResult, error) ``` **Types:** [`ProcessDefinitionKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessDefinitionKey), [`ProcessDefinitionVariableNameSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessDefinitionVariableNameSearchQuery), [`ApiSearchProcessDefinitionVariableNamesRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchProcessDefinitionVariableNamesRequest), [`ProcessDefinitionVariableNameSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessDefinitionVariableNameSearchQueryResult) SearchProcessDefinitionVariableNames calls the SearchProcessDefinitionVariableNames operation. Example: ```go result, err := client.SearchProcessDefinitionVariableNames(ctx, camunda.MustProcessDefinitionKey("2251799813685330"), *camunda.NewProcessDefinitionVariableNameSearchQuery()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### SearchProcessDefinitions ```go func (c *CamundaClient) SearchProcessDefinitions(ctx context.Context, body ProcessDefinitionSearchQuery, opts ...func(ApiSearchProcessDefinitionsRequest) ApiSearchProcessDefinitionsRequest) (*ProcessDefinitionSearchQueryResult, error) ``` **Types:** [`ProcessDefinitionSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessDefinitionSearchQuery), [`ApiSearchProcessDefinitionsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchProcessDefinitionsRequest), [`ProcessDefinitionSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessDefinitionSearchQueryResult) SearchProcessDefinitions calls the SearchProcessDefinitions operation. Example: ```go result, err := client.SearchProcessDefinitions(ctx, *camunda.NewProcessDefinitionSearchQuery()) if err != nil { return err } for _, d := range result.GetItems() { fmt.Printf("%v\n", d) } ``` ### SearchProcessInstanceIncidents ```go func (c *CamundaClient) SearchProcessInstanceIncidents(ctx context.Context, processInstanceKey ProcessInstanceKey, body IncidentSearchQuery, opts ...func(ApiSearchProcessInstanceIncidentsRequest) ApiSearchProcessInstanceIncidentsRequest) (*IncidentSearchQueryResult, error) ``` **Types:** [`ProcessInstanceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceKey), [`IncidentSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#IncidentSearchQuery), [`ApiSearchProcessInstanceIncidentsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchProcessInstanceIncidentsRequest), [`IncidentSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#IncidentSearchQueryResult) SearchProcessInstanceIncidents calls the SearchProcessInstanceIncidents operation. Example: ```go result, err := client.SearchProcessInstanceIncidents(ctx, camunda.MustProcessInstanceKey("2251799813685340"), *camunda.NewIncidentSearchQuery()) if err != nil { return err } for _, inc := range result.GetItems() { fmt.Printf("%v\n", inc) } ``` ### SearchProcessInstances ```go func (c *CamundaClient) SearchProcessInstances(ctx context.Context, body ProcessInstanceSearchQuery, opts ...func(ApiSearchProcessInstancesRequest) ApiSearchProcessInstancesRequest) (*ProcessInstanceSearchQueryResult, error) ``` **Types:** [`ProcessInstanceSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceSearchQuery), [`ApiSearchProcessInstancesRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchProcessInstancesRequest), [`ProcessInstanceSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceSearchQueryResult) SearchProcessInstances calls the SearchProcessInstances operation. Example: ```go result, err := client.SearchProcessInstances(ctx, *camunda.NewProcessInstanceSearchQuery()) if err != nil { return err } for _, pi := range result.GetItems() { fmt.Printf("%v: %v\n", pi.GetProcessInstanceKey(), pi.GetState()) } ``` ### SearchResources ```go func (c *CamundaClient) SearchResources(ctx context.Context, body ResourceSearchQuery, opts ...func(ApiSearchResourcesRequest) ApiSearchResourcesRequest) (*ResourceSearchQueryResult, error) ``` **Types:** [`ResourceSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ResourceSearchQuery), [`ApiSearchResourcesRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchResourcesRequest), [`ResourceSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ResourceSearchQueryResult) SearchResources calls the SearchResources operation. Example: ```go result, err := client.SearchResources(ctx, *camunda.NewResourceSearchQuery()) if err != nil { return err } for _, r := range result.GetItems() { fmt.Printf("%v\n", r) } ``` ### SearchRoles ```go func (c *CamundaClient) SearchRoles(ctx context.Context, body RoleSearchQueryRequest, opts ...func(ApiSearchRolesRequest) ApiSearchRolesRequest) (*RoleSearchQueryResult, error) ``` **Types:** [`RoleSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#RoleSearchQueryRequest), [`ApiSearchRolesRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchRolesRequest), [`RoleSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#RoleSearchQueryResult) SearchRoles calls the SearchRoles operation. Example: ```go result, err := client.SearchRoles(ctx, *camunda.NewRoleSearchQueryRequest()) if err != nil { return err } for _, r := range result.GetItems() { fmt.Printf("%v\n", r) } ``` ### SearchRolesForGroup ```go func (c *CamundaClient) SearchRolesForGroup(ctx context.Context, groupId string, body RoleSearchQueryRequest, opts ...func(ApiSearchRolesForGroupRequest) ApiSearchRolesForGroupRequest) (*GroupRoleSearchResult, error) ``` **Types:** [`RoleSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#RoleSearchQueryRequest), [`ApiSearchRolesForGroupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchRolesForGroupRequest), [`GroupRoleSearchResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#GroupRoleSearchResult) SearchRolesForGroup calls the SearchRolesForGroup operation. Example: ```go result, err := client.SearchRolesForGroup(ctx, "finance", *camunda.NewRoleSearchQueryRequest()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### SearchRolesForTenant ```go func (c *CamundaClient) SearchRolesForTenant(ctx context.Context, tenantId string, body RoleSearchQueryRequest, opts ...func(ApiSearchRolesForTenantRequest) ApiSearchRolesForTenantRequest) (*TenantRoleSearchResult, error) ``` **Types:** [`RoleSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#RoleSearchQueryRequest), [`ApiSearchRolesForTenantRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchRolesForTenantRequest), [`TenantRoleSearchResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TenantRoleSearchResult) SearchRolesForTenant calls the SearchRolesForTenant operation. Example: ```go result, err := client.SearchRolesForTenant(ctx, "tenant-a", *camunda.NewRoleSearchQueryRequest()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### SearchTenants ```go func (c *CamundaClient) SearchTenants(ctx context.Context, body TenantSearchQueryRequest, opts ...func(ApiSearchTenantsRequest) ApiSearchTenantsRequest) (*TenantSearchQueryResult, error) ``` **Types:** [`TenantSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TenantSearchQueryRequest), [`ApiSearchTenantsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchTenantsRequest), [`TenantSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TenantSearchQueryResult) SearchTenants calls the SearchTenants operation. Example: ```go result, err := client.SearchTenants(ctx, *camunda.NewTenantSearchQueryRequest()) if err != nil { return err } for _, t := range result.GetItems() { fmt.Printf("%v\n", t) } ``` ### SearchUserTaskAuditLogs ```go func (c *CamundaClient) SearchUserTaskAuditLogs(ctx context.Context, userTaskKey UserTaskKey, body UserTaskAuditLogSearchQueryRequest, opts ...func(ApiSearchUserTaskAuditLogsRequest) ApiSearchUserTaskAuditLogsRequest) (*AuditLogSearchQueryResult, error) ``` **Types:** [`UserTaskKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserTaskKey), [`UserTaskAuditLogSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserTaskAuditLogSearchQueryRequest), [`ApiSearchUserTaskAuditLogsRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchUserTaskAuditLogsRequest), [`AuditLogSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AuditLogSearchQueryResult) SearchUserTaskAuditLogs calls the SearchUserTaskAuditLogs operation. Example: ```go result, err := client.SearchUserTaskAuditLogs(ctx, camunda.MustUserTaskKey("2251799813685380"), *camunda.NewUserTaskAuditLogSearchQueryRequest()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### SearchUserTaskEffectiveVariables ```go func (c *CamundaClient) SearchUserTaskEffectiveVariables(ctx context.Context, userTaskKey UserTaskKey, body UserTaskEffectiveVariableSearchQueryRequest, opts ...func(ApiSearchUserTaskEffectiveVariablesRequest) ApiSearchUserTaskEffectiveVariablesRequest) (*VariableSearchQueryResult, error) ``` **Types:** [`UserTaskKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserTaskKey), [`UserTaskEffectiveVariableSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserTaskEffectiveVariableSearchQueryRequest), [`ApiSearchUserTaskEffectiveVariablesRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchUserTaskEffectiveVariablesRequest), [`VariableSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#VariableSearchQueryResult) SearchUserTaskEffectiveVariables calls the SearchUserTaskEffectiveVariables operation. Example: ```go result, err := client.SearchUserTaskEffectiveVariables(ctx, camunda.MustUserTaskKey("2251799813685380"), *camunda.NewUserTaskEffectiveVariableSearchQueryRequest()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### SearchUserTaskVariables ```go func (c *CamundaClient) SearchUserTaskVariables(ctx context.Context, userTaskKey UserTaskKey, body UserTaskVariableSearchQueryRequest, opts ...func(ApiSearchUserTaskVariablesRequest) ApiSearchUserTaskVariablesRequest) (*VariableSearchQueryResult, error) ``` **Types:** [`UserTaskKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserTaskKey), [`UserTaskVariableSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserTaskVariableSearchQueryRequest), [`ApiSearchUserTaskVariablesRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchUserTaskVariablesRequest), [`VariableSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#VariableSearchQueryResult) SearchUserTaskVariables calls the SearchUserTaskVariables operation. Example: ```go result, err := client.SearchUserTaskVariables(ctx, camunda.MustUserTaskKey("2251799813685380"), *camunda.NewUserTaskVariableSearchQueryRequest()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### SearchUserTasks ```go func (c *CamundaClient) SearchUserTasks(ctx context.Context, body UserTaskSearchQuery, opts ...func(ApiSearchUserTasksRequest) ApiSearchUserTasksRequest) (*UserTaskSearchQueryResult, error) ``` **Types:** [`UserTaskSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserTaskSearchQuery), [`ApiSearchUserTasksRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchUserTasksRequest), [`UserTaskSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserTaskSearchQueryResult) SearchUserTasks calls the SearchUserTasks operation. Example: ```go result, err := client.SearchUserTasks(ctx, *camunda.NewUserTaskSearchQuery()) if err != nil { return err } for _, t := range result.GetItems() { fmt.Printf("%v\n", t) } ``` ### SearchUsers ```go func (c *CamundaClient) SearchUsers(ctx context.Context, body UserSearchQueryRequest, opts ...func(ApiSearchUsersRequest) ApiSearchUsersRequest) (*UserSearchResult, error) ``` **Types:** [`UserSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserSearchQueryRequest), [`ApiSearchUsersRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchUsersRequest), [`UserSearchResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserSearchResult) SearchUsers calls the SearchUsers operation. Example: ```go result, err := client.SearchUsers(ctx, *camunda.NewUserSearchQueryRequest()) if err != nil { return err } for _, u := range result.GetItems() { fmt.Printf("%v\n", u) } ``` ### SearchUsersForGroup ```go func (c *CamundaClient) SearchUsersForGroup(ctx context.Context, groupId string, body GroupUserSearchQueryRequest, opts ...func(ApiSearchUsersForGroupRequest) ApiSearchUsersForGroupRequest) (*GroupUserSearchResult, error) ``` **Types:** [`GroupUserSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#GroupUserSearchQueryRequest), [`ApiSearchUsersForGroupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchUsersForGroupRequest), [`GroupUserSearchResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#GroupUserSearchResult) SearchUsersForGroup calls the SearchUsersForGroup operation. Example: ```go result, err := client.SearchUsersForGroup(ctx, "finance", *camunda.NewGroupUserSearchQueryRequest()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### SearchUsersForRole ```go func (c *CamundaClient) SearchUsersForRole(ctx context.Context, roleId string, body RoleUserSearchQueryRequest, opts ...func(ApiSearchUsersForRoleRequest) ApiSearchUsersForRoleRequest) (*RoleUserSearchResult, error) ``` **Types:** [`RoleUserSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#RoleUserSearchQueryRequest), [`ApiSearchUsersForRoleRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchUsersForRoleRequest), [`RoleUserSearchResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#RoleUserSearchResult) SearchUsersForRole calls the SearchUsersForRole operation. Example: ```go result, err := client.SearchUsersForRole(ctx, "auditor", *camunda.NewRoleUserSearchQueryRequest()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### SearchUsersForTenant ```go func (c *CamundaClient) SearchUsersForTenant(ctx context.Context, tenantId string, body TenantUserSearchQueryRequest, opts ...func(ApiSearchUsersForTenantRequest) ApiSearchUsersForTenantRequest) (*TenantUserSearchResult, error) ``` **Types:** [`TenantUserSearchQueryRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TenantUserSearchQueryRequest), [`ApiSearchUsersForTenantRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchUsersForTenantRequest), [`TenantUserSearchResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TenantUserSearchResult) SearchUsersForTenant calls the SearchUsersForTenant operation. Example: ```go result, err := client.SearchUsersForTenant(ctx, "tenant-a", *camunda.NewTenantUserSearchQueryRequest()) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### SearchVariables ```go func (c *CamundaClient) SearchVariables(ctx context.Context, body VariableSearchQuery, opts ...func(ApiSearchVariablesRequest) ApiSearchVariablesRequest) (*VariableSearchQueryResult, error) ``` **Types:** [`VariableSearchQuery`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#VariableSearchQuery), [`ApiSearchVariablesRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSearchVariablesRequest), [`VariableSearchQueryResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#VariableSearchQueryResult) SearchVariables calls the SearchVariables operation. Example: ```go result, err := client.SearchVariables(ctx, *camunda.NewVariableSearchQuery()) if err != nil { return err } for _, v := range result.GetItems() { fmt.Printf("%v\n", v) } ``` ### SuspendBatchOperation ```go func (c *CamundaClient) SuspendBatchOperation(ctx context.Context, batchOperationKey string, opts ...func(ApiSuspendBatchOperationRequest) ApiSuspendBatchOperationRequest) error ``` **Types:** [`ApiSuspendBatchOperationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSuspendBatchOperationRequest) SuspendBatchOperation calls the SuspendBatchOperation operation. Example: ```go return client.SuspendBatchOperation(ctx, "2251799813685290") ``` ### SuspendProcessInstance ```go func (c *CamundaClient) SuspendProcessInstance(ctx context.Context, processInstanceKey ProcessInstanceKey, body SuspendProcessInstanceRequest, opts ...func(ApiSuspendProcessInstanceRequest) ApiSuspendProcessInstanceRequest) error ``` **Types:** [`ProcessInstanceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceKey), [`SuspendProcessInstanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#SuspendProcessInstanceRequest), [`ApiSuspendProcessInstanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSuspendProcessInstanceRequest) SuspendProcessInstance calls the SuspendProcessInstance operation. Example: ```go return client.SuspendProcessInstance(ctx, camunda.MustProcessInstanceKey("2251799813685340"), *camunda.NewSuspendProcessInstanceRequest()) ``` ### SuspendProcessInstancesBatchOperation ```go func (c *CamundaClient) SuspendProcessInstancesBatchOperation(ctx context.Context, body ProcessInstanceSuspensionBatchOperationRequest, opts ...func(ApiSuspendProcessInstancesBatchOperationRequest) ApiSuspendProcessInstancesBatchOperationRequest) (*BatchOperationCreatedResult, error) ``` **Types:** [`ProcessInstanceSuspensionBatchOperationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ProcessInstanceSuspensionBatchOperationRequest), [`ApiSuspendProcessInstancesBatchOperationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSuspendProcessInstancesBatchOperationRequest), [`BatchOperationCreatedResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#BatchOperationCreatedResult) SuspendProcessInstancesBatchOperation calls the SuspendProcessInstancesBatchOperation operation. Example: ```go // Suspend every instance matching a filter in a single batch operation. req := camunda.NewProcessInstanceSuspensionBatchOperationRequest(*camunda.NewProcessInstanceFilter()) result, err := client.SuspendProcessInstancesBatchOperation(ctx, *req) if err != nil { return err } fmt.Printf("created batch operation %v\n", result.GetBatchOperationKey()) ``` ### SyncRuntimeBackupState ```go func (c *CamundaClient) SyncRuntimeBackupState(ctx context.Context, opts ...func(ApiSyncRuntimeBackupStateRequest) ApiSyncRuntimeBackupStateRequest) (*RuntimeBackupState, error) ``` **Types:** [`ApiSyncRuntimeBackupStateRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSyncRuntimeBackupStateRequest), [`RuntimeBackupState`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#RuntimeBackupState) SyncRuntimeBackupState calls the SyncRuntimeBackupState operation. Example: ```go // Re-reads the backup store so the reported state matches what is stored. state, err := client.SyncRuntimeBackupState(ctx) if err != nil { return err } for _, backup := range state.GetBackupStates() { fmt.Printf("%v\n", backup) } ``` ### SyncRuntimeBackupStateAsClusterAdmin ```go func (c *CamundaClient) SyncRuntimeBackupStateAsClusterAdmin(ctx context.Context, opts ...func(ApiSyncRuntimeBackupStateAsClusterAdminRequest) ApiSyncRuntimeBackupStateAsClusterAdminRequest) (*ClusterRuntimeBackupState, error) ``` **Types:** [`ApiSyncRuntimeBackupStateAsClusterAdminRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiSyncRuntimeBackupStateAsClusterAdminRequest), [`ClusterRuntimeBackupState`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterRuntimeBackupState) SyncRuntimeBackupStateAsClusterAdmin calls the SyncRuntimeBackupStateAsClusterAdmin operation. Example: ```go // Force-writes the current checkpoint/backup metadata to each physical tenant's // backup store and returns the updated state. Use this to repair stale or missing // state entries without triggering a new backup. state, err := client.SyncRuntimeBackupStateAsClusterAdmin(ctx) if err != nil { return err } for _, tenant := range state.GetPhysicalTenants() { fmt.Printf("%v\n", tenant) } ``` ### TakeHistoryBackup ```go func (c *CamundaClient) TakeHistoryBackup(ctx context.Context, body TakeHistoryBackupRequest, opts ...func(ApiTakeHistoryBackupRequest) ApiTakeHistoryBackupRequest) (*TakeHistoryBackupResponse, error) ``` **Types:** [`TakeHistoryBackupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TakeHistoryBackupRequest), [`ApiTakeHistoryBackupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiTakeHistoryBackupRequest), [`TakeHistoryBackupResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TakeHistoryBackupResponse) TakeHistoryBackup calls the TakeHistoryBackup operation. Example: ```go result, err := client.TakeHistoryBackup(ctx, *camunda.NewTakeHistoryBackupRequest(42)) if err != nil { return err } fmt.Printf("backup %d scheduled %d snapshot(s)\n", result.GetBackupId(), len(result.GetScheduledSnapshots())) ``` ### TakeHistoryBackupAsClusterAdmin ```go func (c *CamundaClient) TakeHistoryBackupAsClusterAdmin(ctx context.Context, body TakeHistoryBackupRequest, opts ...func(ApiTakeHistoryBackupAsClusterAdminRequest) ApiTakeHistoryBackupAsClusterAdminRequest) (*ClusterTakeHistoryBackupResponse, error) ``` **Types:** [`TakeHistoryBackupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TakeHistoryBackupRequest), [`ApiTakeHistoryBackupAsClusterAdminRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiTakeHistoryBackupAsClusterAdminRequest), [`ClusterTakeHistoryBackupResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterTakeHistoryBackupResponse) TakeHistoryBackupAsClusterAdmin calls the TakeHistoryBackupAsClusterAdmin operation. Example: ```go // Takes a history backup for every physical tenant in the cluster simultaneously. result, err := client.TakeHistoryBackupAsClusterAdmin(ctx, *camunda.NewTakeHistoryBackupRequest(42)) if err != nil { return err } fmt.Printf("cluster history backup %d across %d tenant(s)\n", result.GetBackupId(), len(result.GetPhysicalTenants())) ``` ### TakeRuntimeBackup ```go func (c *CamundaClient) TakeRuntimeBackup(ctx context.Context, body TakeRuntimeBackupRequest, opts ...func(ApiTakeRuntimeBackupRequest) ApiTakeRuntimeBackupRequest) (*TakeRuntimeBackupResponse, error) ``` **Types:** [`TakeRuntimeBackupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TakeRuntimeBackupRequest), [`ApiTakeRuntimeBackupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiTakeRuntimeBackupRequest), [`TakeRuntimeBackupResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TakeRuntimeBackupResponse) TakeRuntimeBackup calls the TakeRuntimeBackup operation. Example: ```go req := camunda.NewTakeRuntimeBackupRequest() // The id is required here, and must be omitted instead when continuous backups // or a backup/checkpoint schedule is enabled for the tenant — the server // generates it in that case. req.SetBackupId(42) result, err := client.TakeRuntimeBackup(ctx, *req) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### TakeRuntimeBackupAsClusterAdmin ```go func (c *CamundaClient) TakeRuntimeBackupAsClusterAdmin(ctx context.Context, body TakeRuntimeBackupRequest, opts ...func(ApiTakeRuntimeBackupAsClusterAdminRequest) ApiTakeRuntimeBackupAsClusterAdminRequest) (*ClusterTakeRuntimeBackupResponse, error) ``` **Types:** [`TakeRuntimeBackupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TakeRuntimeBackupRequest), [`ApiTakeRuntimeBackupAsClusterAdminRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiTakeRuntimeBackupAsClusterAdminRequest), [`ClusterTakeRuntimeBackupResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterTakeRuntimeBackupResponse) TakeRuntimeBackupAsClusterAdmin calls the TakeRuntimeBackupAsClusterAdmin operation. Example: ```go // Takes a runtime backup across every physical tenant in the cluster simultaneously. // Pass SetBackupId to use an explicit backup ID; omit it to let the cluster // generate one automatically (generated-id mode). Do not mix modes: sending a // backup ID when the cluster is configured for generated IDs will be rejected. req := camunda.NewTakeRuntimeBackupRequest() req.SetBackupId(42) result, err := client.TakeRuntimeBackupAsClusterAdmin(ctx, *req) if err != nil { return err } for _, tenant := range result.GetPhysicalTenants() { fmt.Printf("%v\n", tenant) } ``` ### ThrowJobError ```go func (c *CamundaClient) ThrowJobError(ctx context.Context, jobKey JobKey, body JobErrorRequest, opts ...func(ApiThrowJobErrorRequest) ApiThrowJobErrorRequest) error ``` **Types:** [`JobKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#JobKey), [`JobErrorRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#JobErrorRequest), [`ApiThrowJobErrorRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiThrowJobErrorRequest) ThrowJobError calls the ThrowJobError operation. Example: ```go req := camunda.NewJobErrorRequest("OUT_OF_STOCK") req.SetErrorMessage("item is out of stock") return client.ThrowJobError(ctx, camunda.MustJobKey("2251799813685424"), *req) ``` ### TriggerClusterRebalance ```go func (c *CamundaClient) TriggerClusterRebalance(ctx context.Context, body ClusterRebalanceRequest, opts ...func(ApiTriggerClusterRebalanceRequest) ApiTriggerClusterRebalanceRequest) (*ClusterBalanceResponse, error) ``` **Types:** [`ClusterRebalanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterRebalanceRequest), [`ApiTriggerClusterRebalanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiTriggerClusterRebalanceRequest), [`ClusterBalanceResponse`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterBalanceResponse) TriggerClusterRebalance calls the TriggerClusterRebalance operation. Example: ```go // Starts a cluster rebalance, redistributing partition leadership to the preferred nodes. // Requires cluster-admin credentials (a separate cluster-admin security chain) — // calling this with standard Orchestration credentials will fail authorization. req := camunda.NewClusterRebalanceRequest() req.SetReplicationLagThreshold(1024 * 1024) // 1 MiB max lag for leader transfer balance, err := client.TriggerClusterRebalance(ctx, *req) if err != nil { return err } fmt.Printf("cluster balance state: %s, %d partition(s)\n", balance.GetState(), len(balance.GetPartitions())) ``` ### UnassignClientFromGroup ```go func (c *CamundaClient) UnassignClientFromGroup(ctx context.Context, groupId string, clientId string, opts ...func(ApiUnassignClientFromGroupRequest) ApiUnassignClientFromGroupRequest) error ``` **Types:** [`ApiUnassignClientFromGroupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUnassignClientFromGroupRequest) UnassignClientFromGroup calls the UnassignClientFromGroup operation. Example: ```go return client.UnassignClientFromGroup(ctx, "finance", "reporting-service") ``` ### UnassignClientFromTenant ```go func (c *CamundaClient) UnassignClientFromTenant(ctx context.Context, tenantId string, clientId string, opts ...func(ApiUnassignClientFromTenantRequest) ApiUnassignClientFromTenantRequest) error ``` **Types:** [`ApiUnassignClientFromTenantRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUnassignClientFromTenantRequest) UnassignClientFromTenant calls the UnassignClientFromTenant operation. Example: ```go return client.UnassignClientFromTenant(ctx, "tenant-a", "reporting-service") ``` ### UnassignGroupFromTenant ```go func (c *CamundaClient) UnassignGroupFromTenant(ctx context.Context, tenantId string, groupId string, opts ...func(ApiUnassignGroupFromTenantRequest) ApiUnassignGroupFromTenantRequest) error ``` **Types:** [`ApiUnassignGroupFromTenantRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUnassignGroupFromTenantRequest) UnassignGroupFromTenant calls the UnassignGroupFromTenant operation. Example: ```go return client.UnassignGroupFromTenant(ctx, "tenant-a", "finance") ``` ### UnassignMappingRuleFromGroup ```go func (c *CamundaClient) UnassignMappingRuleFromGroup(ctx context.Context, groupId string, mappingRuleId string, opts ...func(ApiUnassignMappingRuleFromGroupRequest) ApiUnassignMappingRuleFromGroupRequest) error ``` **Types:** [`ApiUnassignMappingRuleFromGroupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUnassignMappingRuleFromGroupRequest) UnassignMappingRuleFromGroup calls the UnassignMappingRuleFromGroup operation. Example: ```go return client.UnassignMappingRuleFromGroup(ctx, "finance", "sso-auditors") ``` ### UnassignMappingRuleFromTenant ```go func (c *CamundaClient) UnassignMappingRuleFromTenant(ctx context.Context, tenantId string, mappingRuleId string, opts ...func(ApiUnassignMappingRuleFromTenantRequest) ApiUnassignMappingRuleFromTenantRequest) error ``` **Types:** [`ApiUnassignMappingRuleFromTenantRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUnassignMappingRuleFromTenantRequest) UnassignMappingRuleFromTenant calls the UnassignMappingRuleFromTenant operation. Example: ```go return client.UnassignMappingRuleFromTenant(ctx, "tenant-a", "sso-auditors") ``` ### UnassignRoleFromClient ```go func (c *CamundaClient) UnassignRoleFromClient(ctx context.Context, roleId string, clientId string, opts ...func(ApiUnassignRoleFromClientRequest) ApiUnassignRoleFromClientRequest) error ``` **Types:** [`ApiUnassignRoleFromClientRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUnassignRoleFromClientRequest) UnassignRoleFromClient calls the UnassignRoleFromClient operation. Example: ```go return client.UnassignRoleFromClient(ctx, "auditor", "reporting-service") ``` ### UnassignRoleFromGroup ```go func (c *CamundaClient) UnassignRoleFromGroup(ctx context.Context, roleId string, groupId string, opts ...func(ApiUnassignRoleFromGroupRequest) ApiUnassignRoleFromGroupRequest) error ``` **Types:** [`ApiUnassignRoleFromGroupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUnassignRoleFromGroupRequest) UnassignRoleFromGroup calls the UnassignRoleFromGroup operation. Example: ```go return client.UnassignRoleFromGroup(ctx, "auditor", "finance") ``` ### UnassignRoleFromMappingRule ```go func (c *CamundaClient) UnassignRoleFromMappingRule(ctx context.Context, roleId string, mappingRuleId string, opts ...func(ApiUnassignRoleFromMappingRuleRequest) ApiUnassignRoleFromMappingRuleRequest) error ``` **Types:** [`ApiUnassignRoleFromMappingRuleRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUnassignRoleFromMappingRuleRequest) UnassignRoleFromMappingRule calls the UnassignRoleFromMappingRule operation. Example: ```go return client.UnassignRoleFromMappingRule(ctx, "auditor", "sso-auditors") ``` ### UnassignRoleFromTenant ```go func (c *CamundaClient) UnassignRoleFromTenant(ctx context.Context, tenantId string, roleId string, opts ...func(ApiUnassignRoleFromTenantRequest) ApiUnassignRoleFromTenantRequest) error ``` **Types:** [`ApiUnassignRoleFromTenantRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUnassignRoleFromTenantRequest) UnassignRoleFromTenant calls the UnassignRoleFromTenant operation. Example: ```go return client.UnassignRoleFromTenant(ctx, "tenant-a", "auditor") ``` ### UnassignRoleFromUser ```go func (c *CamundaClient) UnassignRoleFromUser(ctx context.Context, roleId string, username string, opts ...func(ApiUnassignRoleFromUserRequest) ApiUnassignRoleFromUserRequest) error ``` **Types:** [`ApiUnassignRoleFromUserRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUnassignRoleFromUserRequest) UnassignRoleFromUser calls the UnassignRoleFromUser operation. Example: ```go return client.UnassignRoleFromUser(ctx, "auditor", "alice") ``` ### UnassignUserFromGroup ```go func (c *CamundaClient) UnassignUserFromGroup(ctx context.Context, groupId string, username string, opts ...func(ApiUnassignUserFromGroupRequest) ApiUnassignUserFromGroupRequest) error ``` **Types:** [`ApiUnassignUserFromGroupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUnassignUserFromGroupRequest) UnassignUserFromGroup calls the UnassignUserFromGroup operation. Example: ```go return client.UnassignUserFromGroup(ctx, "finance", "alice") ``` ### UnassignUserFromTenant ```go func (c *CamundaClient) UnassignUserFromTenant(ctx context.Context, tenantId string, username string, opts ...func(ApiUnassignUserFromTenantRequest) ApiUnassignUserFromTenantRequest) error ``` **Types:** [`ApiUnassignUserFromTenantRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUnassignUserFromTenantRequest) UnassignUserFromTenant calls the UnassignUserFromTenant operation. Example: ```go return client.UnassignUserFromTenant(ctx, "tenant-a", "alice") ``` ### UnassignUserTask ```go func (c *CamundaClient) UnassignUserTask(ctx context.Context, userTaskKey UserTaskKey, opts ...func(ApiUnassignUserTaskRequest) ApiUnassignUserTaskRequest) error ``` **Types:** [`UserTaskKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserTaskKey), [`ApiUnassignUserTaskRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUnassignUserTaskRequest) UnassignUserTask calls the UnassignUserTask operation. Example: ```go return client.UnassignUserTask(ctx, camunda.MustUserTaskKey("2251799813685380")) ``` ### UpdateAgentInstance ```go func (c *CamundaClient) UpdateAgentInstance(ctx context.Context, agentInstanceKey AgentInstanceKey, body AgentInstanceUpdateRequest, opts ...func(ApiUpdateAgentInstanceRequest) ApiUpdateAgentInstanceRequest) (*AgentInstanceUpdateResult, error) ``` **Types:** [`AgentInstanceKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AgentInstanceKey), [`AgentInstanceUpdateRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AgentInstanceUpdateRequest), [`ApiUpdateAgentInstanceRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUpdateAgentInstanceRequest), [`AgentInstanceUpdateResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AgentInstanceUpdateResult) UpdateAgentInstance calls the UpdateAgentInstance operation. Example: ```go req := camunda.NewAgentInstanceUpdateRequest( camunda.ElementInstanceKey("2251799813685360"), // elementInstanceKey camunda.JobKey("2251799813685424"), // jobKey "lease-token", ) result, err := client.UpdateAgentInstance(ctx, camunda.MustAgentInstanceKey("2251799813685370"), *req) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### UpdateAuthorization ```go func (c *CamundaClient) UpdateAuthorization(ctx context.Context, authorizationKey AuthorizationKey, body AuthorizationRequest, opts ...func(ApiUpdateAuthorizationRequest) ApiUpdateAuthorizationRequest) error ``` **Types:** [`AuthorizationKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AuthorizationKey), [`AuthorizationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#AuthorizationRequest), [`ApiUpdateAuthorizationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUpdateAuthorizationRequest) UpdateAuthorization calls the UpdateAuthorization operation. Example: ```go updated := camunda.NewAuthorizationIdBasedRequest( "user@example.com", camunda.OWNERTYPEENUM_USER, "order-process", camunda.RESOURCETYPEENUM_PROCESS_DEFINITION, []camunda.PermissionTypeEnum{camunda.PERMISSIONTYPEENUM_READ_PROCESS_DEFINITION}, ) return client.UpdateAuthorization(ctx, camunda.MustAuthorizationKey("2251799813685280"), camunda.AuthorizationIdBasedRequestAsAuthorizationRequest(updated)) ``` ### UpdateGlobalClusterVariable ```go func (c *CamundaClient) UpdateGlobalClusterVariable(ctx context.Context, name string, body UpdateClusterVariableRequest, opts ...func(ApiUpdateGlobalClusterVariableRequest) ApiUpdateGlobalClusterVariableRequest) (*ClusterVariableResult, error) ``` **Types:** [`UpdateClusterVariableRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UpdateClusterVariableRequest), [`ApiUpdateGlobalClusterVariableRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUpdateGlobalClusterVariableRequest), [`ClusterVariableResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterVariableResult) UpdateGlobalClusterVariable calls the UpdateGlobalClusterVariable operation. Example: ```go result, err := client.UpdateGlobalClusterVariable(ctx, "region", *camunda.NewUpdateClusterVariableRequest(map[string]any{"value": "eu-2"})) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### UpdateGlobalTaskListener ```go func (c *CamundaClient) UpdateGlobalTaskListener(ctx context.Context, id string, body UpdateGlobalTaskListenerRequest, opts ...func(ApiUpdateGlobalTaskListenerRequest) ApiUpdateGlobalTaskListenerRequest) (*GlobalTaskListenerResult, error) ``` **Types:** [`UpdateGlobalTaskListenerRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UpdateGlobalTaskListenerRequest), [`ApiUpdateGlobalTaskListenerRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUpdateGlobalTaskListenerRequest), [`GlobalTaskListenerResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#GlobalTaskListenerResult) UpdateGlobalTaskListener calls the UpdateGlobalTaskListener operation. Example: ```go result, err := client.UpdateGlobalTaskListener(ctx, "audit-listener", *camunda.NewUpdateGlobalTaskListenerRequest( "audit-worker", []camunda.GlobalTaskListenerEventTypeEnum{camunda.GLOBALTASKLISTENEREVENTTYPEENUM_ALL}, )) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### UpdateGroup ```go func (c *CamundaClient) UpdateGroup(ctx context.Context, groupId string, body GroupUpdateRequest, opts ...func(ApiUpdateGroupRequest) ApiUpdateGroupRequest) (*GroupUpdateResult, error) ``` **Types:** [`GroupUpdateRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#GroupUpdateRequest), [`ApiUpdateGroupRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUpdateGroupRequest), [`GroupUpdateResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#GroupUpdateResult) UpdateGroup calls the UpdateGroup operation. Example: ```go result, err := client.UpdateGroup(ctx, "finance", *camunda.NewGroupUpdateRequest("Finance & Accounting")) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### UpdateJob ```go func (c *CamundaClient) UpdateJob(ctx context.Context, jobKey JobKey, body JobUpdateRequest, opts ...func(ApiUpdateJobRequest) ApiUpdateJobRequest) error ``` **Types:** [`JobKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#JobKey), [`JobUpdateRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#JobUpdateRequest), [`ApiUpdateJobRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUpdateJobRequest) UpdateJob calls the UpdateJob operation. Example: ```go changeset := camunda.NewJobChangeset() changeset.SetRetries(3) return client.UpdateJob(ctx, camunda.MustJobKey("2251799813685424"), *camunda.NewJobUpdateRequest(*changeset)) ``` ### UpdateJobsBatchOperation ```go func (c *CamundaClient) UpdateJobsBatchOperation(ctx context.Context, body JobBatchUpdateRequest, opts ...func(ApiUpdateJobsBatchOperationRequest) ApiUpdateJobsBatchOperationRequest) (*BatchOperationCreatedResult, error) ``` **Types:** [`JobBatchUpdateRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#JobBatchUpdateRequest), [`ApiUpdateJobsBatchOperationRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUpdateJobsBatchOperationRequest), [`BatchOperationCreatedResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#BatchOperationCreatedResult) UpdateJobsBatchOperation calls the UpdateJobsBatchOperation operation. Example: ```go changeset := camunda.NewJobChangeset() changeset.SetRetries(3) req := camunda.NewJobBatchUpdateRequest(*camunda.NewJobFilter(), *changeset) result, err := client.UpdateJobsBatchOperation(ctx, *req) if err != nil { return err } fmt.Printf("created batch operation %v\n", result.GetBatchOperationKey()) ``` ### UpdateMappingRule ```go func (c *CamundaClient) UpdateMappingRule(ctx context.Context, mappingRuleId string, body MappingRuleUpdateRequest, opts ...func(ApiUpdateMappingRuleRequest) ApiUpdateMappingRuleRequest) (*MappingRuleUpdateResult, error) ``` **Types:** [`MappingRuleUpdateRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#MappingRuleUpdateRequest), [`ApiUpdateMappingRuleRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUpdateMappingRuleRequest), [`MappingRuleUpdateResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#MappingRuleUpdateResult) UpdateMappingRule calls the UpdateMappingRule operation. Example: ```go result, err := client.UpdateMappingRule(ctx, "sso-auditors", *camunda.NewMappingRuleUpdateRequest("groups", "senior-auditors", "SSO Senior Auditors")) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### UpdateRole ```go func (c *CamundaClient) UpdateRole(ctx context.Context, roleId string, body RoleUpdateRequest, opts ...func(ApiUpdateRoleRequest) ApiUpdateRoleRequest) (*RoleUpdateResult, error) ``` **Types:** [`RoleUpdateRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#RoleUpdateRequest), [`ApiUpdateRoleRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUpdateRoleRequest), [`RoleUpdateResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#RoleUpdateResult) UpdateRole calls the UpdateRole operation. Example: ```go result, err := client.UpdateRole(ctx, "auditor", *camunda.NewRoleUpdateRequest("Senior Auditor")) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### UpdateTenant ```go func (c *CamundaClient) UpdateTenant(ctx context.Context, tenantId string, body TenantUpdateRequest, opts ...func(ApiUpdateTenantRequest) ApiUpdateTenantRequest) (*TenantUpdateResult, error) ``` **Types:** [`TenantUpdateRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TenantUpdateRequest), [`ApiUpdateTenantRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUpdateTenantRequest), [`TenantUpdateResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#TenantUpdateResult) UpdateTenant calls the UpdateTenant operation. Example: ```go result, err := client.UpdateTenant(ctx, "tenant-a", *camunda.NewTenantUpdateRequest("Tenant A (renamed)")) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### UpdateTenantClusterVariable ```go func (c *CamundaClient) UpdateTenantClusterVariable(ctx context.Context, tenantId string, name string, body UpdateClusterVariableRequest, opts ...func(ApiUpdateTenantClusterVariableRequest) ApiUpdateTenantClusterVariableRequest) (*ClusterVariableResult, error) ``` **Types:** [`UpdateClusterVariableRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UpdateClusterVariableRequest), [`ApiUpdateTenantClusterVariableRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUpdateTenantClusterVariableRequest), [`ClusterVariableResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ClusterVariableResult) UpdateTenantClusterVariable calls the UpdateTenantClusterVariable operation. Example: ```go result, err := client.UpdateTenantClusterVariable(ctx, "tenant-a", "region", *camunda.NewUpdateClusterVariableRequest(map[string]any{"value": "eu-2"})) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### UpdateUser ```go func (c *CamundaClient) UpdateUser(ctx context.Context, username string, body UserUpdateRequest, opts ...func(ApiUpdateUserRequest) ApiUpdateUserRequest) (*UserUpdateResult, error) ``` **Types:** [`UserUpdateRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserUpdateRequest), [`ApiUpdateUserRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUpdateUserRequest), [`UserUpdateResult`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserUpdateResult) UpdateUser calls the UpdateUser operation. Example: ```go req := camunda.NewUserUpdateRequest() req.SetName("Alice Updated") result, err := client.UpdateUser(ctx, "alice", *req) if err != nil { return err } fmt.Printf("%v\n", result) ``` ### UpdateUserTask ```go func (c *CamundaClient) UpdateUserTask(ctx context.Context, userTaskKey UserTaskKey, body UserTaskUpdateRequest, opts ...func(ApiUpdateUserTaskRequest) ApiUpdateUserTaskRequest) error ``` **Types:** [`UserTaskKey`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserTaskKey), [`UserTaskUpdateRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#UserTaskUpdateRequest), [`ApiUpdateUserTaskRequest`](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client#ApiUpdateUserTaskRequest) UpdateUserTask calls the UpdateUserTask operation. Example: ```go // Update fields (priority, due/follow-up dates, ...) via the request's // changeset. An empty request is a no-op. req := camunda.NewUserTaskUpdateRequest() return client.UpdateUserTask(ctx, camunda.MustUserTaskKey("2251799813685380"), *req) ``` --- ## Configuration(3) :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: Configuration is resolved from explicit options first, then environment variables, then built-in defaults, and validated fail-fast at construction. ## AuthStrategy ```go type AuthStrategy int ``` AuthStrategy selects the authentication mechanism. ### Constants ```go const ( AuthNone AuthStrategy = iota AuthBasic AuthOAuth ) ``` Authentication strategies. ### Functions #### ParseAuthStrategy ```go func ParseAuthStrategy(s string) (AuthStrategy, error) ``` ParseAuthStrategy parses a CAMUNDA_AUTH_STRATEGY value. ### Methods #### String ```go func (s AuthStrategy) String() string ``` ## BackpressureProfile ```go type BackpressureProfile int ``` BackpressureProfile selects the backpressure controller behavior. ### Constants ```go const ( ProfileBalanced BackpressureProfile = iota ProfileLegacy ) ``` Backpressure profiles. ### Functions #### ParseBackpressureProfile ```go func ParseBackpressureProfile(s string) (BackpressureProfile, error) ``` ParseBackpressureProfile parses a CAMUNDA_SDK_BACKPRESSURE_PROFILE value. ### Methods #### String ```go func (p BackpressureProfile) String() string ``` ## Config Config is the resolved SDK configuration. ### Fields | Field | Type | Description | | --------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `RestAddress` | `string` | | | `GrpcAddress` | `string` | | | `ExactRestAddress` | `bool` | ExactRestAddress opts out of the automatic /v2 suffix normally appended to RestAddress (CAMUNDA_REST_ADDRESS_EXACT). When true, the SDK targets RestAddress exactly as provided — useful behind an API gateway / reverse proxy that mounts the REST API under a prefix that does not follow the .../v2 convention. | | `AuthStrategy` | `AuthStrategy` | | | `ClientID` | `string` | | | `ClientSecret` | `string` | | | `OAuthURL` | `string` | | | `TokenAudience` | `string` | | | `OAuthScope` | `string` | | | `OAuthCacheDir` | `string` | | | `BasicAuthUsername` | `string` | | | `BasicAuthPassword` | `string` | | | `DefaultTenantID` | `string` | | | `Falcon` | `bool` | Falcon enables the FALCON (nanobpmn command-stream) transport upgrade when the gateway advertises it (CAMUNDA_FALCON, default true). ForceREST forces the pure-REST path even when FALCON is advertised (CAMUNDA_FORCE_REST), e.g. where WebSockets are blocked. Use FalconEnabled for the resolved state. | | `ForceREST` | `bool` | | | `BackpressureProfile` | `BackpressureProfile` | | | `LogLevel` | `LogLevel` | | | `EventualPollDefault` | `time.Duration` | | | `Retry` | `RetryConfig` | | | `TLS` | `TLSConfig` | | | `WorkerDefaults` | `WorkerDefaults` | | | `Clock` | `Clock` | Clock resolves runtime cadence. Nil selects `LiveClock`. | | `Random` | `Random` | Random is the source jitter is drawn from. Nil selects `LiveRandom`. | ### Functions #### LoadConfig ```go func LoadConfig(opts ...Option) (*Config, error) ``` LoadConfig resolves configuration from environment variables, applies opts (which take precedence over the environment), and validates the result. ### Methods #### FalconEnabled ```go func (c *Config) FalconEnabled() bool ``` FalconEnabled reports whether the FALCON command-stream transport may be used: it must be enabled (CAMUNDA_FALCON) and not force-disabled (CAMUNDA_FORCE_REST). It only engages when the gateway actually advertises FALCON support; against stock Camunda the SDK stays on REST regardless. #### Validate ```go func (c *Config) Validate() error ``` Validate performs fail-fast validation, returning an actionable error (wrapping ErrConfig) when the configuration cannot support the selected strategy. ## ConfigField ConfigField documents a single environment variable the SDK reads while resolving configuration. It carries the variable's alias precedence, default, and whether it holds credential material. ### Fields | Field | Type | Description | | ------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Keys` | `[]string` | Keys are the environment variable names checked in precedence order; the first non-empty value wins. The first entry is the canonical CAMUNDA_* name; later entries are accepted aliases (e.g. legacy ZEEBE_* names). | | `Default` | `string` | Default is the value applied when none of Keys is set. Empty means the field has no built-in default (it stays unset / zero). | | `Secret` | `bool` | Secret marks credential material that must be redacted in diagnostics. | | `Description` | `string` | Description is a one-line human-readable summary. | ## LogLevel ```go type LogLevel int ``` LogLevel controls SDK log verbosity. ### Constants ```go const ( LogOff LogLevel = iota LogError LogWarn LogInfo LogDebug LogTrace ) ``` Log levels. ### Functions #### ParseLogLevel ```go func ParseLogLevel(s string) (LogLevel, error) ``` ParseLogLevel parses a CAMUNDA_SDK_LOG_LEVEL value. ### Methods #### String ```go func (l LogLevel) String() string ``` ## Option ```go type Option func(*Config) ``` Option configures a Config. Options are applied after environment resolution and therefore take precedence over environment variables. ### Functions #### WithBackpressureProfile ```go func WithBackpressureProfile(p BackpressureProfile) Option ``` WithBackpressureProfile sets the adaptive backpressure profile. #### WithBasicAuth ```go func WithBasicAuth(username, password string) Option ``` WithBasicAuth selects HTTP Basic authentication with the given credentials. #### WithClock ```go func WithClock(c Clock) Option ``` WithClock sets the clock the client will resolve cadence through. Defaults to `LiveClock`. Runtime call sites are being migrated onto the injected clock (see camunda/orchestration-cluster-api-go#40); until that lands the clock is stored and reachable via CamundaClient.Clock, but retry backoff, the backpressure gate, token refresh, worker polling and consistency polling still use real time. A nil Clock selects the default. A _typed_ nil -- a nil pointer boxed in a non-nil interface, such as (*myClock)(nil) -- is rejected by `New` with a configuration error instead: unlike an untyped nil it claims to be a usable clock, and would panic on first use deep inside the runtime. #### WithDefaultTenantID ```go func WithDefaultTenantID(id string) Option ``` WithDefaultTenantID sets the default tenant id applied to operations that accept one. #### WithExactRestAddress ```go func WithExactRestAddress(exact bool) Option ``` WithExactRestAddress controls whether the SDK uses RestAddress exactly as provided. When exact is true, the automatic /v2 suffix is not appended — the address is used verbatim. This is intended for deployments behind an API gateway / reverse proxy whose base path does not follow the .../v2 convention. It maps to the CAMUNDA_REST_ADDRESS_EXACT environment variable. #### WithFalcon ```go func WithFalcon(enabled bool) Option ``` WithFalcon enables or disables the FALCON (nanobpmn command-stream) transport upgrade. It is enabled by default and only engages when the gateway advertises FALCON support; against stock Camunda the SDK stays on REST regardless. #### WithForceREST ```go func WithForceREST(force bool) Option ``` WithForceREST forces the pure-REST path even when the gateway advertises FALCON support (useful where WebSockets are blocked by a proxy). #### WithGrpcAddress ```go func WithGrpcAddress(addr string) Option ``` WithGrpcAddress sets the Zeebe gRPC gateway address (host:port) used by the gRPC streaming job worker. #### WithLogLevel ```go func WithLogLevel(l LogLevel) Option ``` WithLogLevel sets the SDK log level. #### WithNoAuth ```go func WithNoAuth() Option ``` WithNoAuth selects the no-authentication strategy (e.g. local development). #### WithOAuth ```go func WithOAuth(clientID, clientSecret, tokenURL string) Option ``` WithOAuth selects the OAuth 2.0 client-credentials strategy with the given client id, secret, and token endpoint URL. #### WithOAuthAudience ```go func WithOAuthAudience(audience string) Option ``` WithOAuthAudience sets the OAuth token audience. #### WithOAuthCacheDir ```go func WithOAuthCacheDir(dir string) Option ``` WithOAuthCacheDir enables the on-disk OAuth token cache at dir. #### WithOAuthScope ```go func WithOAuthScope(scope string) Option ``` WithOAuthScope sets the OAuth token scope. #### WithRandom ```go func WithRandom(r Random) Option ``` WithRandom sets the source of randomness the client draws jitter from: retry backoff, worker startup delay and FALCON endpoint selection. Defaults to `LiveRandom`. Pass a `SeededRandom` to make those draws reproducible. A nil Random selects the default; a typed nil is rejected by `New`, as for `WithClock`. #### WithRestAddress ```go func WithRestAddress(addr string) Option ``` WithRestAddress sets the Orchestration Cluster REST base address. #### WithRetry ```go func WithRetry(rc RetryConfig) Option ``` WithRetry sets the transient-error retry policy. ## RetryConfig RetryConfig is the transient-error HTTP retry policy. ### Fields | Field | Type | Description | | ------------- | --------------- | ----------- | | `MaxAttempts` | `int` | | | `BaseDelay` | `time.Duration` | | | `MaxDelay` | `time.Duration` | | ## TLSConfig TLSConfig holds TLS / mutual-TLS material. Inline PEM values take precedence over the *Path file locations. ### Fields | Field | Type | Description | | --------------- | -------- | ----------- | | `Cert` | `string` | | | `Key` | `string` | | | `CA` | `string` | | | `CertPath` | `string` | | | `KeyPath` | `string` | | | `CAPath` | `string` | | | `KeyPassphrase` | `string` | | ### Methods #### IsConfigured ```go func (t TLSConfig) IsConfigured() bool ``` IsConfigured reports whether any TLS material has been supplied. ## WorkerDefaults WorkerDefaults holds default job-worker settings sourced from CAMUNDA_WORKER_*. ### Fields | Field | Type | Description | | ------------------------- | -------- | ----------- | | `TimeoutMs` | `int64` | | | `MaxConcurrentJobs` | `int` | | | `RequestTimeoutMs` | `int64` | | | `Name` | `string` | | | `StartupJitterMaxSeconds` | `int` | | ## ConfigSchema ConfigSchema is the canonical registry of every environment variable the SDK consumes during configuration resolution (see loadConfig in config.go). It is the single source of truth for the SDK's configuration surface: it documents the accepted variables, their aliases, and their defaults, and it is kept in lock-step with the actual resolution code by TestConfigSchemaMatchesReads (configschema_test.go), which fails if a variable is read but unregistered or registered but never read. It intentionally mirrors the JS SDK's configSchema so the SDKs expose the same configuration contract. --- ## Domain keys :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: The Camunda Domain Type System replaces the bare `string` identifiers emitted by the OpenAPI generator with validated named types. Passing a `ProcessInstanceKey` where a `JobKey` is expected is a compile error, so whole classes of identifier mix-ups are caught before the request is sent. ```go ``` Every key type exposes the same surface, shown here for `AgentDefinitionKey`: | Function or method | Description | | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | `MustAgentDefinitionKey(s string) AgentDefinitionKey` | MustAgentDefinitionKey is like NewAgentDefinitionKey but panics if s is invalid. | | `NewAgentDefinitionKey(s string) (AgentDefinitionKey, error)` | NewAgentDefinitionKey validates s against the AgentDefinitionKey constraints and returns a AgentDefinitionKey. | | `(k AgentDefinitionKey) String() string` | String returns the underlying string value. | | `(k AgentDefinitionKey) Validate() error` | Validate reports whether k satisfies the AgentDefinitionKey constraints. | ## Key types | Key type | Underlying | Description | | ------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `AgentDefinitionKey` | `string` | Identifier for an agent definition. | | `AgentHistoryItemKey` | `string` | Identifier for an agent history item. | | `AgentInstanceKey` | `string` | Identifier for an agent instance. | | `AuditLogEntityKey` | `string` | Identifier for an audit log entity. | | `AuditLogKey` | `string` | Identifier for an audit log. | | `AuthorizationKey` | `string` | Identifier for an authorization. | | `BatchOperationKey` | `string` | Identifier for a batch operation. | | `BusinessId` | `string` | Identifier for a business. | | `ClientId` | `string` | Identifier for a client. | | `ClusterVariableName` | `string` | Name of a cluster variable. | | `ConditionalEvaluationKey` | `string` | Identifier for a conditional evaluation. | | `DecisionDefinitionId` | `string` | Identifier for a decision definition. | | `DecisionDefinitionKey` | `string` | Identifier for a decision definition. | | `DecisionEvaluationInstanceKey` | `string` | Identifier for a decision evaluation instance. | | `DecisionEvaluationKey` | `string` | Identifier for a decision evaluation. | | `DecisionInstanceKey` | `string` | Identifier for a decision instance. | | `DecisionRequirementsKey` | `string` | Identifier for a decision requirements. | | `DeploymentKey` | `string` | Identifier for a deployment. | | `DocumentId` | `string` | Identifier for a document. | | `ElementId` | `string` | Identifier for an element. | | `ElementInstanceKey` | `string` | Identifier for an element instance. | | `EndCursor` | `string` | Pagination cursor marking the end of a result page. | | `FormId` | `string` | Identifier for a form. | | `FormKey` | `string` | Identifier for a form. | | `GlobalListenerId` | `string` | Identifier for a global listener. | | `GroupId` | `string` | Identifier for a group. | | `HistoryItemId` | `string` | Identifier for a history item. | | `IncidentKey` | `string` | Identifier for an incident. | | `JobKey` | `string` | Identifier for a job. | | `LoopIterationId` | `int32` | LoopIterationId is a Camunda semantic integer identifier. Construct it with NewLoopIterationId (validated) or MustLoopIterationId (panics on invalid input). | | `MappingRuleId` | `string` | Identifier for a mapping rule. | | `MessageKey` | `string` | Identifier for a message. | | `MessageSubscriptionKey` | `string` | Identifier for a message subscription. | | `ProcessDefinitionId` | `string` | Identifier for a process definition. | | `ProcessDefinitionKey` | `string` | Identifier for a process definition. | | `ProcessInstanceKey` | `string` | Identifier for a process instance. | | `ResourceKey` | `string` | Identifier for a resource. | | `RoleId` | `string` | Identifier for a role. | | `ScopeKey` | `string` | Identifier for a scope. | | `SignalKey` | `string` | Identifier for a signal. | | `StartCursor` | `string` | Pagination cursor marking the start of a result page. | | `Tag` | `string` | Validated tag value. | | `TenantId` | `string` | Identifier for a tenant. | | `UserTaskKey` | `string` | Identifier for a user task. | | `Username` | `string` | Validated username value. | | `VariableKey` | `string` | Identifier for a variable. | Each key also has a matching `ExactMatch` wrapper used by the search filter models to express an exact-value match. --- ## API reference :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: This reference covers the hand-written ergonomic surface of the Go SDK: the client, its configuration, the job workers, and the error and polling helpers. | Page | Contents | | ---------------------------------- | -------------------------------------------------------------------- | | [CamundaClient](camunda-client.md) | The client and its 252 API methods. | | [Configuration](configuration.md) | Client configuration, authentication, TLS, and retry policy. | | [Job workers](job-workers.md) | REST and gRPC job workers, their options, and the handler contract. | | [Runtime](runtime.md) | Error types, error classification, and eventual-consistency polling. | | [Domain keys](domain-keys.md) | 46 validated identifier types. | The generated request and response models, enums and builders are not reproduced here — there are several hundred of them. Import them from the `camunda` package like everything else (for example `camunda.ProcessInstanceResult`); their fields and methods are documented on [pkg.go.dev](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11/client). Each method on the [CamundaClient](camunda-client.md) page links the generated types in its signature to that documentation. --- ## Job workers(Api-reference) :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: Job workers obtain jobs of a given type — by polling the REST activation endpoint or over the gRPC job stream — run a handler, and report the outcome back to the cluster. ## Job Job is an activated job passed to a JobHandler. It is transport-agnostic: the same type is produced by the REST job worker and the gRPC streaming worker. ### Methods #### Clock ```go func (j *Job) Clock() Clock ``` Clock returns the worker's clock. A handler that needs to wait should use this rather than time.Sleep, so an injected clock controls it. For short in-handler coordination only -- spacing a retry, waiting for a resource to settle. A long or business wait belongs in the process as a BPMN timer event: a handler holding a job for minutes occupies a worker slot, risks the job timeout expiring underneath it, and hides the wait from the process model where it cannot be seen or changed. #### CustomHeaders ```go func (j *Job) CustomHeaders() map[string]any ``` CustomHeaders returns the job's custom headers. #### ElementID ```go func (j *Job) ElementID() string ``` ElementID returns the BPMN element id that created the job. #### Key ```go func (j *Job) Key() string ``` Key returns the job key. #### LeaseToken ```go func (j *Job) LeaseToken() string ``` LeaseToken returns the activation lease token, or "" if the job was not leased. #### ProcessInstanceKey ```go func (j *Job) ProcessInstanceKey() string ``` ProcessInstanceKey returns the key of the owning process instance. #### RawVariables ```go func (j *Job) RawVariables() map[string]any ``` RawVariables returns the job variables as a decoded map. #### Retries ```go func (j *Job) Retries() int32 ``` Retries returns the job's remaining retries. #### Type ```go func (j *Job) Type() string ``` Type returns the job type. #### Variables ```go func (j *Job) Variables(v any) error ``` Variables unmarshals the job variables into v (a pointer to a struct or map). ## JobHandler ```go type JobHandler func(ctx context.Context, job *Job) (map[string]any, error) ``` JobHandler processes an activated job: - returning (variables, nil) completes the job with those variables; - returning a *BpmnError throws a BPMN error; - returning any other error fails the job (decrementing its retries). ## JobWorker JobWorker polls for jobs of a given type and dispatches them to a handler with bounded concurrency. Job completion, failure, and BPMN-error operations are drain operations and bypass the client-side backpressure gate. ### Methods #### Run ```go func (w *JobWorker) Run(ctx context.Context) error ``` Run polls and dispatches jobs until ctx is canceled, then waits for in-flight handlers to finish and returns ctx.Err(). Run blocks; call it in a goroutine to run alongside other work. A worker configured with a startup jitter (CAMUNDA_WORKER_STARTUP_JITTER_MAX_SECONDS) first waits out its delay on the client's clock. When the gateway advertises the FALCON command stream (a nanobpmn gateway) and FALCON is enabled, jobs are pushed over a WebSocket subscription instead of REST long-polling. If the subscription cannot be established (e.g. a proxy blocks WebSockets) the worker transparently falls back to REST polling. ## StreamJobWorker StreamJobWorker activates jobs over the Zeebe gRPC StreamActivatedJobs stream and completes, fails, or throws BPMN errors over gRPC. Unlike the REST JobWorker it does not poll: the engine pushes jobs as they become available. ### Methods #### Run ```go func (w *StreamJobWorker) Run(ctx context.Context) error ``` Run opens the job stream and dispatches jobs until ctx is canceled. The gRPC connection is held for the worker's lifetime and the stream is reopened (after reconnectBackoff) whenever it ends, so in-flight acknowledgements are never cut off by a reconnect. Run blocks; call it in a goroutine to run alongside other work. A worker configured with a startup jitter (CAMUNDA_WORKER_STARTUP_JITTER_MAX_SECONDS) first waits out its delay on the client's clock. ## StreamWorkerOption ```go type StreamWorkerOption func(*StreamJobWorker) ``` StreamWorkerOption customizes a StreamJobWorker. ### Functions #### WithStreamFetchVariables ```go func WithStreamFetchVariables(vars ...string) StreamWorkerOption ``` WithStreamFetchVariables restricts the variables fetched with each job. Empty fetches all. #### WithStreamJobLease ```go func WithStreamJobLease(enabled bool) StreamWorkerOption ``` WithStreamJobLease requests leased jobs. Each job then carries a distinct lease token, which this worker sends back on complete, fail, and throw-error. The engine rejects a command bearing a stale token, fencing the job against a superseded activation — for example after the job timed out and another worker picked it up. Off by default, matching the gateway's own default. Enabling it requires an engine that supports job leases: a gateway that ignores the field would leave every acknowledgement unfenced, so a job arriving without a token fails the activation with ErrLeaseNotHonored rather than being handled unfenced. It covers both channels: the gRPC stream and the REST sidecar poll (see WithStreamPollInterval). #### WithStreamJobTimeout ```go func WithStreamJobTimeout(d time.Duration) StreamWorkerOption ``` WithStreamJobTimeout sets how long a streamed job is exclusively locked to this worker. #### WithStreamMaxConcurrentJobs ```go func WithStreamMaxConcurrentJobs(n int) StreamWorkerOption ``` WithStreamMaxConcurrentJobs caps the number of jobs handled concurrently. #### WithStreamPollInterval ```go func WithStreamPollInterval(d time.Duration) StreamWorkerOption ``` WithStreamPollInterval sets the interval between REST sidecar-poll cycles. The sidecar poll is a low-frequency safety net that picks up jobs the stream may have missed (e.g. jobs re-queued after a timeout or during a brief reconnect). A value <= 0 disables the sidecar poll entirely (pure gRPC streaming). #### WithStreamPollMaxJobs ```go func WithStreamPollMaxJobs(n int) StreamWorkerOption ``` WithStreamPollMaxJobs caps the number of jobs activated per REST sidecar-poll cycle. #### WithStreamReconnectBackoff ```go func WithStreamReconnectBackoff(d time.Duration) StreamWorkerOption ``` WithStreamReconnectBackoff sets the pause before reopening the stream after it ends. #### WithStreamTenantIDs ```go func WithStreamTenantIDs(ids ...string) StreamWorkerOption ``` WithStreamTenantIDs restricts job activation to the given tenant ids, overriding the client's default tenant. #### WithStreamWorkerName ```go func WithStreamWorkerName(name string) StreamWorkerOption ``` WithStreamWorkerName sets the worker name reported to the engine. ## WorkerOption ```go type WorkerOption func(*JobWorker) ``` WorkerOption customizes a JobWorker. ### Functions #### WithFetchVariables ```go func WithFetchVariables(vars ...string) WorkerOption ``` WithFetchVariables restricts the variables fetched with each job. Empty fetches all. #### WithJobLease ```go func WithJobLease(enabled bool) WorkerOption ``` WithJobLease activates jobs with a lease. Each job then carries a lease token, which this worker sends back on complete, fail, and throw-error. The engine rejects a command bearing a stale token, fencing the job against a superseded activation — for example after the job timed out and another worker picked it up. Off by default, matching the engine's own default. Enabling it requires an engine that supports job leases: a server that ignores the flag would leave every acknowledgement unfenced, so a job arriving without a token fails the activation with ErrLeaseNotHonored rather than being handled unfenced. It has no effect when jobs arrive over the FALCON command stream, which activates them outside the REST activation API. #### WithJobTimeout ```go func WithJobTimeout(d time.Duration) WorkerOption ``` WithJobTimeout sets how long an activated job is exclusively locked to this worker. #### WithMaxConcurrentJobs ```go func WithMaxConcurrentJobs(n int) WorkerOption ``` WithMaxConcurrentJobs caps the number of jobs handled concurrently. #### WithPollInterval ```go func WithPollInterval(d time.Duration) WorkerOption ``` WithPollInterval sets the pause between polls when idle, at capacity, or after an activation error. #### WithRequestTimeout ```go func WithRequestTimeout(d time.Duration) WorkerOption ``` WithRequestTimeout sets the long-poll request timeout for job activation. #### WithWorkerName ```go func WithWorkerName(name string) WorkerOption ``` WithWorkerName sets the worker name reported to the engine. #### WithWorkerTenantIDs ```go func WithWorkerTenantIDs(ids ...string) WorkerOption ``` WithWorkerTenantIDs restricts job activation to the given tenant ids, overriding the client's default tenant. --- ## Runtime(Api-reference) :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: Error types returned by every SDK call, the helpers that classify them, and the polling helper that absorbs eventual consistency. ## APIError APIError is returned when the server responds with a non-success HTTP status. It carries the status code and the (often RFC 7807 problem-detail) response body. ### Fields | Field | Type | Description | | -------- | -------- | -------------------------------------- | | `Status` | `int` | Status is the HTTP status code. | | `Body` | `string` | Body is the raw response body, if any. | ### Methods #### Error ```go func (e *APIError) Error() string ``` ## BpmnError BpmnError is an error that, when returned by a JobHandler, makes the worker throw a BPMN error (raising a catch event) instead of failing the job. ### Fields | Field | Type | Description | | ----------- | ---------------- | ----------- | | `Code` | `string` | | | `Message` | `string` | | | `Variables` | `map[string]any` | | ### Methods #### Error ```go func (e *BpmnError) Error() string ``` ## Clock Clock is time and waiting, as an injectable dependency. It exists so that runtime cadence can be resolved through a clock a test controls rather than by calling the time package directly. Inject one with `WithClock`; the default is `LiveClock`. Runtime call sites are being migrated onto it (see camunda/orchestration-cluster-api-go#40); until that lands, an injected clock is stored and reachable via CamundaClient.Clock but does not yet drive retry, backpressure, worker or consistency cadence. Implementations must be safe for concurrent use. ## ClockController ClockController is the engine-side clock an `EngineClock` drives. Implemented by `CamundaClient` in terms of PUT /clock and POST /clock/reset. It is an interface so the pin semantics can be tested without a running engine. ## EngineClock EngineClock is a clock bound to the engine's own clock. A wait does not pass time locally: it moves the _engine_ forward and reports the new instant. Process instances, timers and the SDK therefore agree on what time it is, which a purely local test clock cannot achieve. A wait resolves against an instant read before the engine is contacted, so waits that overlap -- those that read the clock before any of them lands -- settle at a single instant instead of summing. A wait that begins after an earlier one has landed reads the new time and composes from it, which is the intended behaviour: it really did start later. Clock pinning is an alpha engine endpoint intended for tests, not production clusters. Pass the client the pin requests should travel on; it keeps real time, so the requests themselves are unaffected by the pinning. ### Functions #### NewEngineClock ```go func NewEngineClock(engine ClockController) *EngineClock ``` NewEngineClock binds to an engine. The clock starts unpinned, following real time until the first wait or `EngineClock.PinTo`. engine must not be a client using this clock: a client captures its clock when it is built, so the one passed here always predates this clock. ### Methods #### After ```go func (c *EngineClock) After(d time.Duration) <-chan time.Time ``` After returns immediately with a channel that receives once the engine clock has been advanced by d. The advance runs in the background, so After stays usable in a select rather than blocking the caller for an engine round-trip. A failed pin panics in that goroutine. Because the panic is not on the caller's goroutine it cannot be recovered, and terminates the program: After has no way to report an error, and reporting a time the engine never moved to would be worse. The panic value is an error wrapping the cause so the crash names it. Use `EngineClock.Sleep` wherever failure needs handling; it returns the error. #### IsPinned ```go func (c *EngineClock) IsPinned() bool ``` IsPinned reports whether this clock currently holds the engine clock pinned. #### Now ```go func (c *EngineClock) Now() time.Time ``` Now reports the pinned instant, or live time when unpinned. #### PinTo ```go func (c *EngineClock) PinTo(ctx context.Context, t time.Time) error ``` PinTo moves the engine clock to an absolute instant, and reports that instant from `EngineClock.Now` once the engine accepts it -- including when t is in the past, since the SDK's reading has to match the engine's. A no-op when the clock already sits at or past t, which is what makes overlapping waits settle at a single instant. The local reading is published only after the engine accepts the pin, so a failed request leaves the clock untouched. Waits never move the clock backwards: `EngineClock.Sleep` derives its instant by adding to the current reading. #### Reset ```go func (c *EngineClock) Reset(ctx context.Context) error ``` Reset returns the engine to real time. Readings follow live time again afterwards, rather than freezing at the last pinned instant. #### Sleep ```go func (c *EngineClock) Sleep(ctx context.Context, d time.Duration) error ``` Sleep advances the engine clock by d rather than waiting for it to pass. ## LiveClock LiveClock is real time, backed by the time package. It is the clock used when none is injected. ### Methods #### After ```go func (LiveClock) After(d time.Duration) <-chan time.Time ``` After returns a channel that receives once d has elapsed. #### Now ```go func (LiveClock) Now() time.Time ``` Now reports the current system time. #### Sleep ```go func (LiveClock) Sleep(ctx context.Context, d time.Duration) error ``` Sleep waits for d or until ctx is canceled. ## LiveRandom LiveRandom draws from the process-wide generator. It is the default when no `Random` is injected. ### Methods #### Float64 ```go func (LiveRandom) Float64() float64 ``` Float64 implements `Random`. ## PollOption ```go type PollOption func(*pollConfig) ``` PollOption customizes Poll. ### Functions #### WithPollClock ```go func WithPollClock(clock Clock) PollOption ``` WithPollClock resolves the poll interval and timeout through clock. Poll is a package-level function with no client to inherit one from, so pass the client's clock (CamundaClient.Clock) to keep a test on a single timeline. #### WithPollRetryInterval ```go func WithPollRetryInterval(d time.Duration) PollOption ``` WithPollRetryInterval sets the delay between polling attempts. #### WithPollTimeout ```go func WithPollTimeout(d time.Duration) PollOption ``` WithPollTimeout sets the overall polling deadline. #### WithRetryOn ```go func WithRetryOn(pred func(error) bool) PollOption ``` WithRetryOn overrides the predicate that decides whether an error is retryable (the entity is not yet consistent). The default retries on 404. ## Random Random is the source of randomness the SDK draws jitter from: retry backoff, worker startup delay and FALCON endpoint selection. Inject one with `WithRandom`; the default is `LiveRandom`. Implementations must be safe for concurrent use, since retries and workers draw from one source on many goroutines. ## SeededRandom SeededRandom is a deterministic `Random`. Two sources with the same seed produce the same sequence, so a test can assert the exact delays the SDK schedules and replay a failure. It is safe for concurrent use, though concurrent callers then share one sequence in nondeterministic order. The generator is SplitMix64, taking the top 53 bits of each output. It is specified across the Camunda SDKs, so one seed yields the same sequence in every language. ### Functions #### NewSeededRandom ```go func NewSeededRandom(seed uint64) *SeededRandom ``` NewSeededRandom returns a source that replays the sequence for seed. #### SeededRandomFromEnv ```go func SeededRandomFromEnv() (*SeededRandom, error) ``` SeededRandomFromEnv seeds from CAMUNDA_TEST_SEED when it is set, otherwise from a fresh random seed. Either way the source reports its seed through `SeededRandom.Seed` and `SeededRandom.String`, so a failing test can be replayed. Only an unset variable means absent. Any other value that is not a decimal integer in the uint64 range, including an empty string, returns an error wrapping `ErrConfig` rather than silently running with a different seed. ### Methods #### Float64 ```go func (r *SeededRandom) Float64() float64 ``` Float64 implements `Random`. #### Seed ```go func (r *SeededRandom) Seed() uint64 ``` Seed returns the seed this source was created with. #### String ```go func (r *SeededRandom) String() string ``` String names the seed and how to replay it, so a test that prints the source on failure says how to reproduce the run. ## Package functions ### IsEventuallyConsistent ```go func IsEventuallyConsistent(operationID string) bool ``` IsEventuallyConsistent reports whether the REST operation with the given operationId is eventually consistent: a read issued immediately after a related write may not observe the write yet. Wrap such reads in Poll to tolerate propagation delay. The operationId is the OpenAPI operation id (camelCase), e.g. "getProcessInstance". The set is generated from the spec metadata. ### IsNotFound ```go func IsNotFound(err error) bool ``` IsNotFound reports whether err is (or wraps) an *APIError with HTTP 404. ### Poll ```go func Poll[T any](ctx context.Context, fn func(context.Context) (T, error), opts ...PollOption) (T, error) ``` Poll repeatedly calls fn until it succeeds, the retry predicate returns false, the timeout elapses, or ctx is canceled. It is intended for eventually-consistent reads: newly created or modified entities may not be immediately visible in the cluster's secondary storage, surfacing as a 404. By default Poll retries while fn returns a 404 and gives up after the timeout with ErrEventualConsistencyTimeout (wrapping the last error). A non-retryable error is returned immediately. Example: ```go pi, err := camunda.Poll(ctx, func(ctx context.Context) (*camunda.ProcessInstanceResult, error) { return client.GetProcessInstance(ctx, key) }) ``` ### StatusCode ```go func StatusCode(err error) (status int, ok bool) ``` StatusCode returns the HTTP status code carried by err if it is (or wraps) an *APIError, and ok reports whether it was found. ## SeedEnvVar ```go const SeedEnvVar = "CAMUNDA_TEST_SEED" ``` SeedEnvVar names the environment variable `SeededRandomFromEnv` reads. A test that fails under a seeded source can be replayed by setting it to the seed the failure reported. ## ErrConfig, ErrAuth, ErrBackpressureQueueFull, ErrEventualConsistencyTimeout, ErrLeaseNotHonored ```go var ( // ErrConfig indicates configuration was invalid or incomplete. ErrConfig = errors.New("camunda: configuration error") // ErrAuth indicates a failure obtaining or refreshing an auth token. ErrAuth = errors.New("camunda: authentication error") // ErrBackpressureQueueFull indicates the client-side backpressure controller // rejected the request because its waiter queue is at capacity. It is the same // value the backpressure gate returns, so errors.Is matches it on any request // rejected for this reason (facade, Raw client, or job workers). ErrBackpressureQueueFull = backpressure.ErrQueueFull // ErrEventualConsistencyTimeout indicates an eventual-consistency polling // helper timed out before its predicate was met. ErrEventualConsistencyTimeout = errors.New("camunda: eventual consistency timeout") // ErrLeaseNotHonored indicates a worker activated jobs with a lease but the // server returned a job carrying no lease token. // // The specification declares the token present exactly when the activation sets // the lease flag (see presentwhen.go). A server that predates job leases, or one // that ignores the flag, breaks that quietly: the worker would go on to complete, // fail, or throw an error for the job with no token, so the engine could not fence // the command against a superseded activation. The caller asked for fencing and // would not be getting it, which is worth failing over rather than logging. ErrLeaseNotHonored = errors.New("camunda: activation requested a job lease but the server returned no lease token") ) ``` Sentinel errors. Use errors.Is to test for them. --- ## Architecture :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: ``` OpenAPI spec ──▶ openapi-generator ──▶ client/ (generated REST client, never hand-edited) gateway.proto ──▶ buf ──▶ pb/ (generated gRPC stubs, never hand-edited) │ ergonomic runtime ───┤ config · auth · backpressure · retry · (hand-written) │ eventual consistency · job workers ▼ CamundaClient (the facade you use) ``` Cross-cutting concerns are implemented as a composable `http.RoundTripper` chain (`backpressure → retry → auth → base`) injected into the generated client, so the generated code stays pure and regenerable. - **Configuration** — resolved from `CAMUNDA_*` environment variables (with `ZEEBE_*` fallbacks) and overridable via functional options. Validated fail-fast at construction. - **Authentication** — OAuth 2.0 client-credentials (with in-memory + on-disk token cache), HTTP Basic, or None. - **Adaptive backpressure** — an AIMD concurrency limiter that reacts to broker backpressure (HTTP 429 / 503 / `RESOURCE_EXHAUSTED`). `BALANCED` (default) gates; `LEGACY` observes only. - **Transient retry** — exponential backoff with full jitter on 429/502/503/504 and network errors. - **Job workers** — a REST activate-jobs worker (`NewJobWorker`) and a gRPC `StreamActivatedJobs` streaming worker (`NewStreamJobWorker`). Both share one `JobHandler` contract: returning variables completes the job, returning a `*BpmnError` throws a BPMN error, and returning any other error fails the job (decrementing its retries). The streaming worker also runs a low-frequency REST sidecar poll (a safety net for jobs re-queued after a timeout or a brief reconnect); poll-activated jobs are acknowledged over REST, streamed jobs over gRPC. Set `WithStreamPollInterval` to tune or disable it. --- ## Authentication(Go-sdk) :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: Three strategies are supported: OAuth 2.0 client credentials, HTTP Basic, and none. The strategy is inferred from the credentials you supply, or set explicitly with `CAMUNDA_AUTH_STRATEGY=OAUTH|BASIC|NONE`. ```go // OAuth 2.0 client credentials. Tokens are cached in memory and on disk, and // refreshed before expiry; concurrent refreshes are collapsed into one. oauthClient, err := camunda.New( camunda.WithOAuth( "my-client-id", "my-client-secret", "https://login.cloud.camunda.io/oauth/token", ), camunda.WithOAuthAudience("zeebe.camunda.io"), camunda.WithOAuthScope("camunda:read"), camunda.WithOAuthCacheDir("/var/cache/camunda"), ) // HTTP Basic — typical for a Self-Managed cluster behind basic auth. basicClient, err := camunda.New( camunda.WithBasicAuth("demo", "demo"), ) // No authentication — a local development cluster with auth disabled. openClient, err := camunda.New(camunda.WithNoAuth()) ``` The OAuth token cache is two-tier: an in-memory cache backed by an on-disk cache (`CAMUNDA_OAUTH_CACHE_DIR`), so short-lived processes and CLI invocations reuse a valid token instead of re-authenticating on every start. Concurrent refreshes are collapsed into a single in-flight request. --- ## Backpressure :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: A Camunda broker sheds load by rejecting commands (HTTP 429 / 503, `RESOURCE_EXHAUSTED`). Rather than surfacing those to your code as a wall of errors, the SDK gates outbound requests through an AIMD concurrency limiter — the same additive-increase/multiplicative-decrease shape TCP uses. Throughput rises while the cluster is healthy and backs off the moment it pushes back, so a burst queues client-side instead of being shed: ```go // BALANCED (the default) gates outbound requests through an AIMD concurrency // limiter that reacts to broker backpressure (HTTP 429/503). LEGACY observes // and reports, but never gates — use it to compare against older SDKs. client, err := camunda.New( camunda.WithBackpressureProfile(camunda.ProfileLegacy), ) ``` `BALANCED` is the default and is what you want in production. `LEGACY` keeps the controller's observability but never gates, which is useful when comparing behavior against an older SDK or when an external system already governs concurrency. --- ## Clocks :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: Retry backoff, the backpressure gate, token refresh, job-worker polling and consistency polling all resolve through an injected clock rather than the `time` package, so a test can control cadence instead of waiting for it. Two places deliberately stay on real time, each marked with a `//nolint:forbidigo` naming the reason: `LiveClock` itself, which is the adapter onto the `time` package, and `internal/falcon`, whose timers are mostly I/O bounds — read-idle detection and create-ack budgets — that would misfire if bound to engine time. | Clock | Use | | ---------------- | ------------------------------------------------------------------------- | | `LiveClock` | real time; the default when nothing is injected | | your own `Clock` | tests; return whatever `Now` you like and make `Sleep` return immediately | | `EngineClock` | drives the Camunda engine's clock and the SDK's together | A local test clock makes the _SDK_ wait instantly, but the engine carries on in real time — so a process that only completes once a BPMN timer fires still takes as long as the timer says. `EngineClock` is for that case. ## Waiting inside a handler A `Job` carries its worker's clock, so a handler that needs to wait can do it on the same clock as everything else: ```go worker := client.NewJobWorker("payment", func(ctx context.Context, job *camunda.Job) (map[string]any, error) { // Short coordination only -- a business wait belongs in the process as a // BPMN timer event. if err := job.Clock().Sleep(ctx, 500*time.Millisecond); err != nil { return nil, err } return map[string]any{"paid": true}, nil }) ``` Keep those waits short — spacing a retry, letting a resource settle. **A long or business wait belongs in the process as a BPMN timer event, not in a handler.** A handler that sleeps for minutes holds a worker slot for the duration, risks the job timeout expiring underneath it, and hides the delay from the process model, where it would otherwise be visible and changeable without a redeploy. ## Driving the engine's clock `EngineClock` pins the engine's clock instead of passing time locally: ```go // The control client issues the pin requests and keeps real time itself. control, err := camunda.New(camunda.WithRestAddress(addr)) if err != nil { return err } clock := camunda.NewEngineClock(control) // Anything this client waits on now advances the engine instead of real time. client, err := camunda.New(camunda.WithRestAddress(addr), camunda.WithClock(clock)) if err != nil { return err } ``` A wait now moves the engine forward and reports the new instant, so the SDK and the engine agree on what time it is. Waits that overlap — those that read the clock before any of them lands — settle at a single instant rather than summing; a wait that begins after an earlier one has landed reads the new time and composes from it. `PinTo` and `Reset` are available directly for tests that need to move the engine without waiting. Clock pinning is an alpha engine endpoint, intended for tests rather than production clusters. Pass the control client the pin requests should travel on: it keeps real time, so the requests themselves are unaffected by the pinning. ## Writing your own `Clock` is a public interface (`Now`, `Sleep`, `After`); implement it and pass it to `camunda.WithClock`. `ClockController` is the engine-side half, if you want `EngineClock` to drive something other than a `CamundaClient`. Ambient time is banned in the runtime by `.golangci.yml` — `time.Now`, `time.Sleep`, `time.NewTimer` and friends — so cadence cannot quietly drift back onto real time. --- ## Configuration reference(Go-sdk) :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: `CAMUNDA_*` variables are canonical; the `ZEEBE_*` names are accepted as fallbacks for compatibility with older tooling. Functional options take precedence over both. ## Connection | Variable | Default | Description | | ------------------------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `CAMUNDA_REST_ADDRESS` / `ZEEBE_REST_ADDRESS` | `http://localhost:8080` | Orchestration Cluster REST base address. | | `CAMUNDA_REST_ADDRESS_EXACT` | — | Use `CAMUNDA_REST_ADDRESS` exactly as provided, without appending the `/v2` suffix (for gateway/reverse-proxy fronted deployments). | | `CAMUNDA_GRPC_ADDRESS` / `ZEEBE_GRPC_ADDRESS` | `localhost:26500` | Zeebe gRPC gateway address (`host:port`) for the streaming job worker. | | `CAMUNDA_DEFAULT_TENANT_ID` / `CAMUNDA_TENANT_ID` | — | Default tenant id applied to operations that accept one. | ## Authentication | Variable | Default | Description | | ------------------------------------------------------ | -------- | ------------------------------------------------------------------------------- | | `CAMUNDA_AUTH_STRATEGY` | inferred | `OAUTH`, `BASIC`, or `NONE`. Inferred from the supplied credentials when unset. | | `CAMUNDA_CLIENT_ID` / `ZEEBE_CLIENT_ID` | — | OAuth 2.0 client id (client-credentials grant). | | `CAMUNDA_CLIENT_SECRET` / `ZEEBE_CLIENT_SECRET` | — | OAuth 2.0 client secret. | | `CAMUNDA_OAUTH_URL` / `ZEEBE_AUTHORIZATION_SERVER_URL` | — | OAuth 2.0 token endpoint URL. | | `CAMUNDA_TOKEN_AUDIENCE` | — | OAuth token audience. | | `CAMUNDA_TOKEN_SCOPE` | — | OAuth token scope. | | `CAMUNDA_OAUTH_CACHE_DIR` | — | Directory for the on-disk OAuth token cache. | | `CAMUNDA_BASIC_AUTH_USERNAME` | — | HTTP Basic authentication username. | | `CAMUNDA_BASIC_AUTH_PASSWORD` | — | HTTP Basic authentication password. | ## Reliability | Variable | Default | Description | | -------------------------------------- | ---------- | ----------------------------------------------------------- | | `CAMUNDA_SDK_BACKPRESSURE_PROFILE` | `BALANCED` | `BALANCED` (gates) or `LEGACY` (observe-only). | | `CAMUNDA_SDK_HTTP_RETRY_MAX_ATTEMPTS` | — | Max transient-error retry attempts. | | `CAMUNDA_SDK_HTTP_RETRY_BASE_DELAY_MS` | — | Base backoff delay for retries, in milliseconds. | | `CAMUNDA_SDK_HTTP_RETRY_MAX_DELAY_MS` | — | Max backoff delay for retries, in milliseconds. | | `CAMUNDA_SDK_EVENTUAL_POLL_DEFAULT_MS` | — | Default eventual-consistency poll timeout, in milliseconds. | | `CAMUNDA_SDK_LOG_LEVEL` | `info` | `off`, `error`, `warn`, `info`, `debug`, or `trace`. | ## Job workers | Variable | Default | Description | | ----------------------------------------------------------------------- | ---------------- | ------------------------------------------------------- | | `CAMUNDA_WORKER_NAME` | hostname-derived | Default worker name. | | `CAMUNDA_WORKER_TIMEOUT` | — | Default job activation timeout, in milliseconds. | | `CAMUNDA_WORKER_MAX_CONCURRENT_JOBS` / `CAMUNDA_WORKER_MAX_JOBS_ACTIVE` | — | Default max concurrently-activated jobs per worker. | | `CAMUNDA_WORKER_REQUEST_TIMEOUT` | — | Default activate-jobs request timeout, in milliseconds. | | `CAMUNDA_WORKER_STARTUP_JITTER_MAX_SECONDS` | — | Max random startup delay for workers, in seconds. | ## Transport | Variable | Default | Description | | -------------------- | ------- | ---------------------------------------------------------------------------------- | | `CAMUNDA_FALCON` | `true` | Enable the FALCON command-stream transport upgrade when the gateway advertises it. | | `CAMUNDA_FORCE_REST` | — | Force the pure-REST path even when the gateway advertises FALCON. | Invalid values are rejected at construction with a configuration error rather than being silently coerced, so a typo in a deployment manifest fails the process at startup instead of at first request. --- ## Configuration(Go-sdk) :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: Every setting is resolved from the environment and overridable with functional options. Options take precedence over environment variables: ```go // Functional options override the environment. Here: OAuth 2.0 // client-credentials against a SaaS cluster. client, err := camunda.New( camunda.WithRestAddress("https://my-cluster.region.camunda.io"), camunda.WithOAuth( "my-client-id", "my-client-secret", "https://login.cloud.camunda.io/oauth/token", ), camunda.WithOAuthAudience("zeebe.camunda.io"), ) ``` --- ## Deploying and starting processes :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: The facade exposes request bodies as first-class parameters; the `Raw()` escape hatch covers anything the facade doesn't (such as multipart resource upload): ```go // Deploy a BPMN process. Multipart resource upload goes through the Raw() // generated client (the escape hatch for anything the facade doesn't cover). f, err := os.Open("greet.bpmn") if err != nil { return err } defer func() { _ = f.Close() }() if _, _, err := client.Raw().ResourceAPI.CreateDeployment(ctx). Resources([]*os.File{f}). Execute(); err != nil { return err } // Start an instance by process id. The request body is a first-class facade // parameter — no Raw() needed. byID := camunda.NewProcessInstanceCreationInstructionById(camunda.ProcessDefinitionId("demo-process")) byID.SetVariables(map[string]any{"name": "Camunda"}) instruction := camunda.ProcessInstanceCreationInstructionByIdAsProcessInstanceCreationInstruction(byID) instance, err := client.CreateProcessInstance(ctx, instruction) if err != nil { return err } fmt.Printf("started process instance %v\n", instance.GetProcessInstanceKey()) ``` --- ## Error handling :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: A server-side 4xx/5xx surfaces as a typed `*APIError` carrying the HTTP status and response body; anything else is a transport-level error: ```go _, err := client.GetTopology(ctx) var apiErr *camunda.APIError if errors.As(err, &apiErr) { // The server returned a 4xx/5xx — inspect the status and response body. fmt.Printf("API error: HTTP %d — %s\n", apiErr.Status, apiErr.Body) } else if err != nil { // Transport-level failure (DNS, TLS, connection refused, ...). fmt.Println("request failed:", err) } ``` Two helpers cover the common classifications without unwrapping by hand: ```go key := camunda.MustProcessInstanceKey("2251799813685249") _, err := client.GetProcessInstance(ctx, key) // IsNotFound is the idiomatic 404 check — the common case when reading an // entity that has not yet propagated to secondary storage. if camunda.IsNotFound(err) { fmt.Println("not visible yet") return nil } // StatusCode reports the HTTP status for any server-side error, and // ok == false for transport-level failures. if status, ok := camunda.StatusCode(err); ok { fmt.Printf("server rejected the request: HTTP %d\n", status) } ``` Errors are values throughout: use `errors.Is` for sentinels and `errors.As` for typed errors. A function that cannot honour its contract returns an error rather than a zero value or a best-guess default. --- ## Eventual consistency :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: Reads are served from the cluster's secondary storage and are eventually consistent. `Poll` retries a read (by default while it returns 404) until the entity is visible or a timeout elapses: ```go // Reads are eventually consistent: a just-created entity may briefly 404. // Poll retries 404s until the entity is visible or the timeout elapses. key := camunda.MustProcessInstanceKey("2251799813685249") instance, err := camunda.Poll(ctx, func(ctx context.Context) (*camunda.ProcessInstanceResult, error) { return client.GetProcessInstance(ctx, key) }, camunda.WithPollTimeout(10*time.Second)) if err != nil { return err } fmt.Printf("instance state: %v\n", instance.GetState()) ``` --- ## Installation(Go-sdk) :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: ```sh go get github.com/camunda/orchestration-cluster-api-go/v11 ``` Requires Go 1.25+. The `/v11` suffix is the Camunda server line this SDK targets (8.11); see [versioning](versioning.md#versioning). --- ## Job workers(Go-sdk) :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: Register a REST activate-jobs worker with `NewJobWorker`. The handler's return value decides the job outcome: ```go // One JobHandler contract for both workers: returning variables completes the // job; returning a *camunda.BpmnError throws a BPMN error; returning any other // error fails the job (decrementing its retries). worker := client.NewJobWorker("greet", func(ctx context.Context, job *camunda.Job) (map[string]any, error) { var in struct { Name string `json:"name"` } if err := job.Variables(&in); err != nil { return nil, err } return map[string]any{"greeting": "Hello, " + in.Name + "!"}, nil }, camunda.WithMaxConcurrentJobs(10), camunda.WithPollInterval(500*time.Millisecond), ) ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Run blocks until ctx is canceled, draining in-flight jobs on shutdown. if err := worker.Run(ctx); err != nil { fmt.Println("worker stopped:", err) } ``` For high-throughput, low-latency work, use the gRPC streaming worker (`NewStreamJobWorker`) — a capability unique to this SDK among the Camunda Orchestration Cluster SDKs: ```go // The gRPC streaming worker activates jobs over a StreamActivatedJobs stream // and acknowledges them over gRPC. A low-frequency REST sidecar poll backs it // up (a safety net for jobs re-queued after a timeout or brief reconnect). worker := client.NewStreamJobWorker("greet", func(ctx context.Context, job *camunda.Job) (map[string]any, error) { return map[string]any{"greeting": "Hello!"}, nil }, camunda.WithStreamPollInterval(30*time.Second), // -1 disables the sidecar poll ) ctx, cancel := context.WithCancel(context.Background()) defer cancel() if err := worker.Run(ctx); err != nil { fmt.Println("stream worker stopped:", err) } ``` Pass `camunda.WithStreamJobLease(true)` to activate jobs with a lease. Each job then carries a lease token that the worker sends back when it completes, fails, or throws — so if the job timed out and another worker picked it up, the stale acknowledgement is rejected instead of racing the newer activation. It covers both the gRPC stream and the REST sidecar poll. The REST worker takes the same option as `camunda.WithJobLease(true)`. Leases are off by default (matching the engine) and need an engine that supports them: rather than degrade to unfenced commands, a worker that asked for a lease and is handed a job without a token rejects the activation with `camunda.ErrLeaseNotHonored`. --- ## License :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: Apache-2.0. See [LICENSE](https://github.com/camunda/orchestration-cluster-api-go/blob/main/LICENSE). --- ## Logging(Go-sdk) :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: The SDK logs through a level-gated internal logger, off by default at anything above `info`: ```go // Levels: LogOff, LogError, LogWarn, LogInfo (default), LogDebug, LogTrace. // LogDebug reports auth-token refreshes, retries, and backpressure decisions; // LogTrace adds per-request detail. Credentials are never logged. client, err := camunda.New(camunda.WithLogLevel(camunda.LogDebug)) ``` Set `CAMUNDA_SDK_LOG_LEVEL=debug` to get the same effect without a code change — useful when diagnosing an authentication or backpressure problem in a deployed service. Secrets (client secrets, passwords, bearer tokens, TLS key material) are never written to the log at any level. --- ## Quick start :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: Construct a client (configuration comes from `CAMUNDA_*` environment variables) and call an ergonomic facade method: ```go // Configuration is resolved from CAMUNDA_* environment variables (with ZEEBE_* // fallbacks) and validated fail-fast at construction. client, err := camunda.New() if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() topology, err := client.GetTopology(ctx) if err != nil { return err } fmt.Printf("Camunda 8 %s — %d broker(s), %d partition(s)\n", topology.GetGatewayVersion(), len(topology.GetBrokers()), topology.GetPartitionsCount()) ``` For production-shaped, runnable workflows, see the [advanced examples](https://github.com/camunda/orchestration-cluster-api-go/blob/main/examples/advanced/README.md): bounded load with adaptive backpressure, resilient job handling, and idempotent message correlation. --- ## Reproducible jitter :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: Retry backoff, worker startup delay (`CAMUNDA_WORKER_STARTUP_JITTER_MAX_SECONDS`) and FALCON endpoint selection draw from an injected `Random` rather than `math/rand`. The default, `LiveRandom`, is the process-wide generator. A `SeededRandom` replays the same sequence for the same seed, so a test can assert the exact delays the SDK schedules instead of a range: ```go // Seeds from CAMUNDA_TEST_SEED when it is set, otherwise from a fresh seed. random, err := camunda.SeededRandomFromEnv() if err != nil { return err } // Log the source: it names the seed and how to replay this run. fmt.Println(random) // SeededRandom(seed=...; replay with CAMUNDA_TEST_SEED=...) client, err := camunda.New(camunda.WithRandom(random)) ``` The seeded generator is specified across the Camunda SDKs, so one seed produces the same draws in every language. A worker draws its startup delay when it is built, not when `Run` starts, so the delays follow construction order however the goroutines are scheduled. `math/rand` is banned in the runtime by `.golangci.yml`, alongside ambient time. --- ## Semantic keys :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: Identifier types (`JobKey`, `ProcessInstanceKey`, …) are validated named string types rather than bare strings: ```go // Semantic key types validate their format at construction. key, err := camunda.NewJobKey("2251799813685424") // validates pattern & length if err != nil { return err } fmt.Println(key.String()) // Side-load a key you already trust, without validation: loose := camunda.MustJobKey("2251799813685424") _ = loose ``` --- ## The full API surface :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: `CamundaClient` exposes one ergonomic method per operation in the OpenAPI specification, generated from the same spec as the low-level client so the two can never diverge. Each facade method flattens the generated builder into first-class parameters and returns the deserialized result. Every request, response, key and enum type is available from the `camunda` package, so one import covers normal use. Only the raw HTTP-client machinery — the client `Raw()` returns, its configuration, and its error type — stays in the generated `github.com/camunda/orchestration-cluster-api-go/v11/client` package. When you need something the facade deliberately does not model — multipart uploads, unusual query-parameter combinations, or the raw `*http.Response` — `Raw()` hands you the generated client directly: ```go // Raw() exposes the generated client: every operation, with the full builder // surface. Use it for anything the facade does not cover, and for access to // the raw *http.Response. result, resp, err := client.Raw().ProcessDefinitionAPI. SearchProcessDefinitions(ctx). Execute() if err != nil { return err } fmt.Printf("HTTP %d — %d process definition(s)\n", resp.StatusCode, len(result.GetItems())) ``` Requests made through `Raw()` still traverse the full runtime — backpressure, retry, and authentication all apply, because those are `http.RoundTripper` layers on the transport rather than facade-level wrappers. Full API documentation is published on [pkg.go.dev](https://pkg.go.dev/github.com/camunda/orchestration-cluster-api-go/v11). --- ## TLS and mutual TLS :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: TLS is derived from the scheme of `CAMUNDA_REST_ADDRESS`. Mutual TLS and custom certificate authorities — for a Self-Managed cluster behind a private CA, or one requiring client certificates — are configured by environment variable. Inline PEM values take precedence over the corresponding `*_PATH` file locations. | Variable | Description | | ----------------------------- | -------------------------------------------------------------------------------------------------------------- | | `CAMUNDA_MTLS_CERT` | Inline client certificate PEM. | | `CAMUNDA_MTLS_KEY` | Inline client private key PEM. | | `CAMUNDA_MTLS_CA` | Inline CA certificate PEM for verifying the server. | | `CAMUNDA_MTLS_CERT_PATH` | Path to the client certificate PEM. | | `CAMUNDA_MTLS_KEY_PATH` | Path to the client private key PEM. | | `CAMUNDA_MTLS_CA_PATH` | Path to the CA certificate PEM. | | `CAMUNDA_MTLS_KEY_PASSPHRASE` | Recognized but **not supported yet** — setting it fails client construction. Supply an unencrypted client key. | The same material is applied to both the REST transport and the gRPC streaming worker, so a single configuration covers every connection the SDK opens. --- ## Transient retry :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: Retries are layered _below_ backpressure in the `http.RoundTripper` chain, so a retried request is still subject to the concurrency limiter and cannot amplify a load spike: ```go // Transient failures (429, 502, 503, 504 and network errors) are retried with // exponential backoff and full jitter. Non-transient 4xx are never retried. client, err := camunda.New( camunda.WithRetry(camunda.RetryConfig{ MaxAttempts: 5, BaseDelay: 100 * time.Millisecond, MaxDelay: 5 * time.Second, }), ) ``` Only transient failures are retried. A `400`, `403`, or `404` is returned to you immediately — retrying a request the server has definitively rejected wastes time and hides the real error. --- ## Versioning :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: The SDK major version tracks the Camunda server minor version: use the SDK line that matches your server. Go requires majors ≥ 2 to carry a `/vN` module-path suffix, so the import path names the server line: | Camunda server | Module path suffix | Branch | | --------------------- | ------------------ | ----------- | | 8.10 | `/v10` | `stable/10` | | 8.11 (in development) | `/v11` | `main` | Moving to a new server minor means changing the import path. Releases before v10 (`v0.x`) used the module path without a suffix. --- ## Go SDK (Technical Preview) :::caution Technical Preview The Go SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: An idiomatic Go client for the [Camunda 8](https://camunda.io) Orchestration Cluster REST API, with a gRPC job-streaming worker. It pairs a **generated low-level REST client** (produced from the upstream OpenAPI specification) with a hand-written **ergonomic runtime** that handles the concerns real integrations need: configuration, authentication, adaptive backpressure, transient retry, and eventual-consistency handling. This is a sibling of the [Rust](https://github.com/camunda/orchestration-cluster-api-rust), [TypeScript](https://github.com/camunda/orchestration-cluster-api-js), [Python](https://github.com/camunda/orchestration-cluster-api-python), and [C#](https://github.com/camunda/orchestration-cluster-api-csharp) SDKs and follows the same two-layer architecture. --- ## Authentication(Hub-api-saas) ## The process Generate a [JSON Web Token (JWT)](https://jwt.io/introduction/), and include it in every request. If you already have a client or token for Web Modeler API v1, you can reuse it for this API. ## Prerequisites Before you begin, make sure you have the **Admin** user role. ## Create new client credentials Create an API client with Hub API permissions. 1. In Camunda Hub, in the left navigation, click **Organization > Manage organization**. 2. In the **Administration API** tab, click **Create new credentials**. 3. Name the client, and grant it access to the **Hub API** with the necessary permissions. 4. Click **Create**, and 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 you create client credentials, the client secret is only shown once. Save the client secret somewhere safe. ::: ## Generate a token After [creating new client credentials](#create-a-new-application), generate an access token: ```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 response looks like this: ```json { "access_token": "", "expires_in": 300, "refresh_expires_in": 0, "token_type": "Bearer", "not-before-policy": 0 } ``` With this `access_token`, you're ready to [authenticate with the Camunda Hub API](#authenticate-with-your-token). ## Authenticate with your token Once you have [generated a token](#generate-a-token), use it in the authorization header in every Camunda Hub API request: `Authorization: Bearer `. For example, send a request to the Camunda Hub API's `/info` endpoint: ```shell curl --header "Authorization: Bearer ${TOKEN}" \ https://hub.camunda.io/api/v2/info ``` ## Organization-level access API tokens are granted to organization-level _clients_ rather than individual _users_. With an API token, you can read, edit, and delete all workspaces and workspace resources in the organization, as long as the application has the required permissions for the Camunda Hub API. This is true even if you aren't a member of the workspace and you can't see it in the Camunda Hub user interface. ## Token expiration Access tokens expire according to the `expires_in` property of an authenticated response. After this duration, in seconds, you must request a new access token. --- ## Camunda Hub API (SaaS) ## Authentication See the [authentication guide](/apis-tools/hub-api-saas/authentication.md) for setup instructions. ## Migrating from Web Modeler API v1 Web Modeler API v1 is deprecated in Camunda 8.10 and will be removed in 8.12. [Migrate](/apis-tools/hub-api-saas/overview.md) to Camunda Hub REST API v2 before upgrading to 8.12. --- ## Authentication(Hub-api-sm) ## The process Generate a [JSON Web Token (JWT)](https://jwt.io/introduction/), and include it in every request. If you already have a Web Modeler API v1 token, you can use the same token for this API. ## Create a new application Create an application with Hub API permissions. 1. [Add an M2M application in Management Identity](/self-managed/components/management-identity/application-user-group-role-management/applications.md#add-an-application). 2. [Grant this application access](/self-managed/components/management-identity/access-management/manage-permissions.md#assign-a-permission-to-an-application) to the **Hub API** with the necessary permissions. This authorization also adds the required `web-modeler-public-api` audience to tokens issued for this application, so no `audience` parameter is needed in the token request. 3. Capture the `Client ID` and `Client Secret` from the application in Management Identity. ## Generate a token After [creating a new application](#create-a-new-application), use its client ID and secret to [generate an access token](/self-managed/components/management-identity/authentication.md#generate-a-token): ```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 this: ```json { "access_token": "", "expires_in": 300, "refresh_expires_in": 0, "token_type": "Bearer", "not-before-policy": 0 } ``` With this `access_token`, you're ready to [authenticate with the Camunda Hub API](#authenticate-with-your-token). ## Authenticate with your token Once you have [generated a token](#generate-a-token), use it in the authorization header in every Camunda Hub API request: `Authorization: Bearer `. For example, send a request to the Camunda Hub API `/info` endpoint: ```shell curl --header "Authorization: Bearer ${TOKEN}" \ ${CAMUNDA_HUB_REST_URL}/api/v2/info ``` In this example, `${CAMUNDA_HUB_REST_URL}` represents the URL of the Camunda Hub API. You can configure this value in your Self-Managed installation. The default value is `http://localhost:8088`. The Camunda Hub API validates both the token's audience and the application's permissions: - A `401 Unauthorized` response means the token is missing the `web-modeler-public-api` audience. This audience is added when the application is authorized for the **Camunda Hub API** (see step 2), so confirm that authorization is in place. - A `403 Forbidden` response means the application is missing the permissions required for the operation on the **Camunda Hub API** (for example, `create`, `update`, or `delete`). ## Organization-level access API tokens are granted to organization-level _applications_ rather than individual _users_. With an API token, you can read, edit, and delete all workspaces and workspace resources in the organization, as long as the application has the required permissions for the Camunda Hub API. This is true even if you aren't a member of the workspace and you can't see it in the Camunda Hub user interface. ## Token expiration Access tokens expire according to the `expires_in` property of an authenticated response. After this duration, in seconds, you must request a new access token. --- ## Camunda Hub API (Self-Managed) ## Authentication See the [authentication guide](/apis-tools/hub-api-sm/authentication.md) for setup instructions. ## Migrating from Web Modeler API v1 Web Modeler API v1 is deprecated in Camunda 8.10 and will be removed in 8.12. [Migrate](/apis-tools/hub-api-sm/overview.md) to Camunda Hub REST API v2 before upgrading to 8.12. --- ## Java client ## About The Camunda Java Client is the official Java library for building process applications that integrate with Camunda 8. This client provides everything needed to interact with the Orchestration Cluster programmatically, such as orchestrating microservices, managing human tasks, or visualizing process data, and so on. For example, you can use it to build a job worker that handles polling for available jobs, use SLF4J for logging useful notes. :::info Public API The Camunda Java Client 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. ::: ## What is the Camunda Java Client? The Camunda Java Client is a comprehensive library enabling Java developers to: - **Deploy processes and decisions** to Camunda 8 clusters - **Start and manage processes** programmatically - **Implement job workers** to handle automated tasks within your processes - **Query and manage process data** via the Orchestration Cluster API It supports both REST and gRPC protocols, authentication setup, and provides robust error handling with retry mechanisms. :::info Migration from Zeebe Java Client **The Camunda Java Client replaces the Zeebe Java Client as of version 8.8.** - Provides improved structure and full Orchestration Cluster API support - Uses **REST** as default communication protocol (gRPC configurable) - The Zeebe Java Client will be **removed in version 8.10** - **Migrate before upgrading to 8.10** to avoid breaking changes See our [migration guide](../migration-manuals/migrate-to-camunda-java-client.md) for details. ::: ## What can you build with it? Use the Camunda Java Client to 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 leverage process data for visualization, analytics, or business intelligence ## Get started ### Step 1: Add the dependency Add the Camunda Java Client to your project: **Maven:** ```xml io.camunda camunda-client-java ${camunda.version} ``` **Gradle:** ```groovy implementation 'io.camunda:camunda-client-java:${camunda.version}' ``` Use the latest version from [Maven Central](https://search.maven.org/artifact/io.camunda/camunda-client-java). ### Step 2a: Connect to a Self-Managed Orchestration Cluster Create a client instance to connect to your Self-Managed Camunda 8 cluster. Select the appropriate [authentication method](../orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md) for your environment: **Use for:** Local development when security is not required. ```java private static final String CAMUNDA_GRPC_ADDRESS = "[Address of Zeebe API (gRPC) - default: http://localhost:26500]"; private static final String CAMUNDA_REST_ADDRESS = "[Address of the Orchestration Cluster API - default: http://localhost:8080]"; public static void main(String[] args) { try (CamundaClient client = CamundaClient.newClientBuilder() .grpcAddress(URI.create(CAMUNDA_GRPC_ADDRESS)) .restAddress(URI.create(CAMUNDA_REST_ADDRESS)) .build()) { // Test the connection client.newTopologyRequest().execute(); System.out.println("Connected to Camunda 8!"); } } ``` **What this code does** 1. **Creates a no-authentication provider** – Configures the client to skip authentication. 2. **Builds a client using the protocol-specified transport** – Uses plaintext or TLS depending on whether the addresses use `http` or `https`. 3. **Connects to both APIs** – Configures access to the Zeebe gRPC and Orchestration Cluster REST APIs. 4. **Tests the connection** – Verifies connectivity by requesting cluster topology information. **Environment variables option** You can also configure the client using environment variables: ```bash export CAMUNDA_GRPC_ADDRESS='[Address of Zeebe API (gRPC) - default: http://localhost:26500]' export CAMUNDA_REST_ADDRESS='[Address of the Orchestration Cluster API - default: http://localhost:8080]' ``` ```java CamundaClient client = CamundaClient.newClientBuilder().build(); ``` The client will automatically read these environment variables and configure the appropriate authentication method. Ensure addresses are in absolute URI format: `scheme://host(:port)`. The protocol (`http` or `https`) determines whether the connection is encrypted. **Use for:** Development or testing environments with username/password protection. ```java private static final String CAMUNDA_GRPC_ADDRESS = "[Address of Zeebe API (gRPC) - default: http://localhost:26500]"; private static final String CAMUNDA_REST_ADDRESS = "[Address of the Orchestration Cluster API - default: http://localhost:8080]"; private static final String CAMUNDA_BASIC_AUTH_USERNAME = "[Your username - default: demo]"; private static final String CAMUNDA_BASIC_AUTH_PASSWORD = "[Your password - default: demo]"; public static void main(String[] args) { CredentialsProvider credentialsProvider = new BasicAuthCredentialsProviderBuilder() .username(CAMUNDA_BASIC_AUTH_USERNAME) .password(CAMUNDA_BASIC_AUTH_PASSWORD) .build(); try (CamundaClient client = CamundaClient.newClientBuilder() .grpcAddress(URI.create(CAMUNDA_GRPC_ADDRESS)) .restAddress(URI.create(CAMUNDA_REST_ADDRESS)) .credentialsProvider(credentialsProvider) .build()) { // Test the connection client.newTopologyRequest().execute(); System.out.println("Connected to Camunda 8!"); } } ``` **What this code does** 1. **Sets up username/password authentication** – Configures the client to use basic credentials. 2. **Builds a client using the protocol-specified transport** – Establishes an unencrypted connection if the addresses use `http` or an encrypted connection if they use `https`. 3. **Connects to both APIs** – Configures access to the Zeebe gRPC and Orchestration Cluster REST APIs. 4. **Tests the connection** – Verifies authentication by requesting cluster topology information. **Environment variables option** You can also set connection details via environment variables to create the client more simply: ```bash export CAMUNDA_GRPC_ADDRESS='[Address of Zeebe API (gRPC) - default: http://localhost:26500]' export CAMUNDA_REST_ADDRESS='[Address of the Orchestration Cluster API - default: http://localhost:8080]' export CAMUNDA_BASIC_AUTH_USERNAME='[Your username - default: demo]' export CAMUNDA_BASIC_AUTH_PASSWORD='[Your password - default: demo]' ``` ```java CamundaClient client = CamundaClient.newClientBuilder().build(); ``` The client will automatically read the environment variables and configure the appropriate authentication method. :::note - Ensure addresses use absolute URI format: `scheme://host(:port)`. - By default, environment variables override any values provided in Java code. To give Java code values precedence, use the `.applyEnvironmentOverrides(false)` method on `BasicAuthCredentialsProviderBuilder`. - The client adds an `Authorization` header to each request with the value `Basic username:password` (where `username:password` is base64 encoded). ::: **Use for:** Self-Managed production environments with OIDC-based authentication. Standard `client_secret_basic` authentication method. ```java private static final String CAMUNDA_GRPC_ADDRESS = "[Address of Zeebe API (gRPC) - default: http://localhost:26500]"; private static final String CAMUNDA_REST_ADDRESS = "[Address of the Orchestration Cluster API - default: http://localhost:8080]"; // There are three ways to define the authorization server URL private static final String CAMUNDA_AUTHORIZATION_SERVER_URL = "[OAuth URL e.g. http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token]"; private static final String CAMUNDA_WELL_KNOWN_CONFIGURATION_URL = "[OAuth URL e.g. http://localhost:18080/auth/realms/camunda-platform/.well-known/openid-configuration]"; private static final String CAMUNDA_ISSUER_URL = "[OAuth URL e.g. http://localhost:18080/auth/realms/camunda-platform]"; // Audience is the API that will receive the token, such as the Orchestration Cluster for example private static final String AUDIENCE = "[Orchestration Cluster audience]"; // Scope is the permission requested from the IdP (or leave empty) private static final String SCOPE = "[optional additional scopes]"; private static final String CLIENT_ID = "[Client ID registered in your IdP]"; private static final String CLIENT_SECRET = "[Client Secret]"; public static void main(String[] args) { CredentialsProvider credentialsProvider = new OAuthCredentialsProviderBuilder() // Select the authorization server configuration option according to your properties from above .authorizationServerUrl(CAMUNDA_AUTHORIZATION_SERVER_URL) .issuerUrl(CAMUNDA_ISSUER_URL) .wellKnownConfigurationUrl(CAMUNDA_WELL_KNOWN_CONFIGURATION_URL) // End authorization server .audience(AUDIENCE) .scope(SCOPE) .clientId(CLIENT_ID) .clientSecret(CLIENT_SECRET) .build(); try (CamundaClient client = CamundaClient.newClientBuilder() .grpcAddress(URI.create(CAMUNDA_GRPC_ADDRESS)) .restAddress(URI.create(CAMUNDA_REST_ADDRESS)) .credentialsProvider(credentialsProvider) .build()) { // Test the connection client.newTopologyRequest().execute(); System.out.println("Connected to Camunda 8!"); } } ``` **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. ::: **What this code does** 1. **Sets up OAuth2 authentication** – Configures the client to use OAuth tokens from your identity provider. 2. **Builds a secure client** – Establishes an encrypted connection to your self-managed cluster (default). 3. **Connects to both APIs** – Configures access to the Zeebe gRPC and Orchestration Cluster REST APIs. 4. **Tests the connection** – Verifies OAuth authentication by requesting cluster topology information. **Environment variables option** You can also set connection details via environment variables to create the client more simply: ```bash export CAMUNDA_GRPC_ADDRESS='[Address of Zeebe API (gRPC) - default: http://localhost:26500]' export CAMUNDA_REST_ADDRESS='[Address of the Orchestration Cluster API - default: http://localhost:8080]' # There are three ways to define the authorization server URL export CAMUNDA_AUTHORIZATION_SERVER_URL='[OAuth URL e.g. http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token]' export CAMUNDA_WELL_KNOWN_CONFIGURATION_URL='[OAuth URL e.g. http://localhost:18080/auth/realms/camunda-platform/.well-known/openid-configuration]' export CAMUNDA_ISSUER_URL='[OAuth URL e.g. http://localhost:18080/auth/realms/camunda-platform]' export CAMUNDA_TOKEN_AUDIENCE='[Audience]' export CAMUNDA_CLIENT_ID='[Client ID]' export CAMUNDA_CLIENT_SECRET='[Client Secret]' ``` ```java CamundaClient client = CamundaClient.newClientBuilder().build(); ``` The client will automatically read the environment variables and configure the appropriate authentication method. :::note - Ensure addresses use absolute URI format: `scheme://host(:port)`. - By default, environment variables override any values provided in Java code. To give Java code values precedence, use the `.applyEnvironmentOverrides(false)` method on `OAuthCredentialsProviderBuilder`. - The client adds an `Authorization` header to each request with the value `Bearer `. The token is obtained from the authorization server, cached to avoid unnecessary requests, and refreshed lazily upon expiration. - 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. ::: **Use for:** Production environments with mTLS certificate-based client authentication. Several identity providers, such as Keycloak, support client mTLS authentication as an alternative to `client_secret_basic`. **Prerequisites** - Properly configured KeyStore and TrustStore - Both your application and identity provider share the same CA trust certificates - Certificates for the identity provider are signed by a trusted CA - The application DN is registered in the identity provider client authorization details ```java private static final String CAMUNDA_GRPC_ADDRESS = "[Address of Zeebe API (gRPC) - default: http://localhost:26500]"; private static final String CAMUNDA_REST_ADDRESS = "[Address of the Orchestration Cluster API - default: http://localhost:8080]"; // There are three ways to define the authorization server URL private static final String CAMUNDA_AUTHORIZATION_SERVER_URL = "[OAuth URL e.g. http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token]"; private static final String CAMUNDA_WELL_KNOWN_CONFIGURATION_URL = "[OAuth URL e.g. http://localhost:18080/auth/realms/camunda-platform/.well-known/openid-configuration]", private static final String CAMUNDA_ISSUER_URL = "[OAuth URL e.g. http://localhost:18080/auth/realms/camunda-platform]"; private static final String AUDIENCE = "[Audience - default: zeebe-api]"; private static final String CLIENT_ID = "[Client ID]"; private static final Path KEYSTORE_PATH = Paths.get("/path/to/keystore.p12"); private static final String KEYSTORE_PASSWORD = "password"; private static final String KEYSTORE_KEY_PASSWORD = "password"; private static final Path TRUSTSTORE_PATH = Paths.get("/path/to/truststore.jks"); private static final String TRUSTSTORE_PASSWORD = "password"; public static void main(String[] args) { CredentialsProvider credentialsProvider = new OAuthCredentialsProviderBuilder() // Select the authorization server configuration option according to your properties from above .authorizationServerUrl(CAMUNDA_AUTHORIZATION_SERVER_URL) .issuerUrl(CAMUNDA_ISSUER_URL) .wellKnownConfigurationUrl(CAMUNDA_WELL_KNOWN_CONFIGURATION_URL) // End authorization server .audience(AUDIENCE) .clientId(CLIENT_ID) .keystorePath(KEYSTORE_PATH) .keystorePassword(KEYSTORE_PASSWORD) .keystoreKeyPassword(KEYSTORE_KEY_PASSWORD) .truststorePath(TRUSTSTORE_PATH) .truststorePassword(TRUSTSTORE_PASSWORD) .build(); try (CamundaClient client = CamundaClient.newClientBuilder() .grpcAddress(URI.create(CAMUNDA_GRPC_ADDRESS)) .restAddress(URI.create(CAMUNDA_REST_ADDRESS)) .credentialsProvider(credentialsProvider) .build()) { // Test the connection client.newTopologyRequest().execute(); System.out.println("Connected to Camunda 8!"); } } ``` **What this code does** 1. **Sets up mTLS certificate authentication** – Configures the client to authenticate using client certificates with OAuth. 2. **Builds a secure client** – Establishes an encrypted connection using mutual TLS authentication. 3. **Connects to both APIs** – Configures access to the Zeebe gRPC and Orchestration Cluster REST APIs. 4. **Tests the connection** – Verifies certificate authentication by requesting cluster topology information. **Environment variables option** You can also set connection details via environment variables to create the client more simply: ```bash export CAMUNDA_GRPC_ADDRESS='[Address of Zeebe API (gRPC) - default: http://localhost:26500]' export CAMUNDA_REST_ADDRESS='[Address of the Orchestration Cluster API - default: http://localhost:8080]' # There are three ways to define the authorization server URL export CAMUNDA_AUTHORIZATION_SERVER_URL='[OAuth URL e.g. http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token]' export CAMUNDA_WELL_KNOWN_CONFIGURATION_URL='[OAuth URL e.g. http://localhost:18080/auth/realms/camunda-platform/.well-known/openid-configuration]' export CAMUNDA_ISSUER_URL='[OAuth URL e.g. http://localhost:18080/auth/realms/camunda-platform]' export CAMUNDA_TOKEN_AUDIENCE='[Audience - default: zeebe-api]' export CAMUNDA_CLIENT_ID='[Client ID]' export CAMUNDA_CLIENT_SECRET='[Client Secret]' export CAMUNDA_SSL_CLIENT_KEYSTORE_PATH='[Keystore path]' export CAMUNDA_SSL_CLIENT_KEYSTORE_SECRET='[Keystore password]' export CAMUNDA_SSL_CLIENT_KEYSTORE_KEY_SECRET='[Keystore material password]' export CAMUNDA_SSL_CLIENT_TRUSTSTORE_PATH='[Truststore path]' export CAMUNDA_SSL_CLIENT_TRUSTSTORE_SECRET='[Truststore password]' ``` ```java CamundaClient client = CamundaClient.newClientBuilder().build(); ``` The client automatically reads environment variables and configures the appropriate authentication method. Refer to your identity provider documentation for configuring mutual TLS authentication. For example, see [Keycloak](https://www.keycloak.org/server/mutual-tls). :::note - Ensure addresses use absolute URI format: `scheme://host(:port)`. - By default, environment variables override any values provided in Java code. To give Java code values precedence, use the `.applyEnvironmentOverrides(false)` method on `OAuthCredentialsProviderBuilder`. - The client adds an `Authorization` header to each request with the value `Bearer `. The token is obtained from the authorization server, cached to avoid unnecessary requests, and refreshed lazily upon expiration. - 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. ::: ### Step 2b: Configure the Orchestration Cluster connection for SaaS **Use for:** Camunda 8 SaaS environments. Get the values below from your [Camunda Hub client credentials](/components/saas/clusters/manage-api-clients.md#create-a-client). ```java private static final String CAMUNDA_CLUSTER_ID = "[Cluster ID from Hub]"; private static final String CAMUNDA_CLIENT_ID = "[Client ID from Hub]"; private static final String CAMUNDA_CLIENT_SECRET = "[Client Secret from Hub]"; private static final String CAMUNDA_CLUSTER_REGION = "[Cluster Region from Hub]"; public static void main(String[] args) { try (CamundaClient client = CamundaClient.newCloudClientBuilder() .withClusterId(CAMUNDA_CLUSTER_ID) .withClientId(CAMUNDA_CLIENT_ID) .withClientSecret(CAMUNDA_CLIENT_SECRET) .withRegion(CAMUNDA_CLUSTER_REGION) .build()) { // Test the connection client.newTopologyRequest().execute(); System.out.println("Connected to Camunda 8!"); } } ``` **What this code does** 1. **Sets up SaaS authentication** – Configures the client to connect to Camunda 8 SaaS using your cluster credentials. 2. **Builds a cloud client** – Creates a client optimized for SaaS with automatic endpoint discovery. 3. **Connects to your cluster** – Uses your cluster ID and region to locate and connect to the correct SaaS instance. 4. **Tests the connection** – Verifies SaaS authentication by requesting cluster topology information. **Environment variables option** You can also set connection details via environment variables to create the client more simply: ```bash export CAMUNDA_GRPC_ADDRESS='[Orchestration Cluster gRPC Address from Hub]' export CAMUNDA_REST_ADDRESS='[Orchestration Cluster REST Address from Hub]' export CAMUNDA_OAUTH_URL='[OAuth URL from Hub]' export CAMUNDA_TOKEN_AUDIENCE='[Audience from Hub - default: zeebe.camunda.io]' export CAMUNDA_CLIENT_ID='[Client ID from Hub]' export CAMUNDA_CLIENT_SECRET='[Client Secret from Hub]' ``` ```java CamundaClient client = CamundaClient.newClientBuilder().build(); ``` The client will automatically read the environment variables and configure the appropriate authentication method. :::note Ensure addresses are in absolute URI format: `scheme://host(:port)`. ::: ### Step 3: Start building your process application With a connected client, you are ready to build your process application. Below are the core operations you’ll typically perform, along with guidance on the next steps. #### Essential operations **Deploy a process:** ```java final DeploymentEvent deploymentEvent = client.newDeployResourceCommand() .addResourceFromClasspath("process.bpmn") .execute(); ``` This deploys your BPMN process definition to the cluster. Place your `.bpmn` files in `src/main/resources` and reference them by filename. **Start a process instance:** ```java final ProcessInstanceEvent processInstanceEvent = client.newCreateInstanceCommand() .bpmnProcessId("my-process") .latestVersion() .variables(Map.of("orderId", "12345", "amount", 100.0)) .execute(); ``` This creates a new instance of your process. The `bpmnProcessId` should match the Process ID from your BPMN file, and you can pass initial variables as a Map. For a comprehensive example demonstrating these steps, see the [DeployAndComplete example](https://github.com/camunda-community-hub/camunda-8-examples/blob/main/camunda-client-plain-java/src/main/java/io/camunda/example/e2e/process/DeployAndComplete.java) in the Camunda 8 examples repository. This example illustrates a complete workflow from process deployment to job completion. ## Key features and capabilities - **Full Orchestration Cluster 8 API support:** Access all Orchestration Cluster API capabilities, including process deployment, management, job handling, and querying process data. - **Multiple authentication methods:** Supports no authentication (development), basic authentication, and OIDC access tokens for production environments. - **Automatic token management:** Handles authentication token acquisition and renewal automatically—no manual token management required. - **Protocol flexibility:** Choose between REST and gRPC protocols depending on your requirements and infrastructure. ## Next steps and resources **Learn the fundamentals** - [Job worker implementation](job-worker.md) – Build workers to handle automated tasks - [Process testing](../testing/getting-started.md) – Test your processes with Camunda Process Test - [Getting Started Tutorial](../../guides/getting-started-example.md) – Complete walkthrough with Desktop Modeler, Operate, and Spring SDK **Advanced topics** - [Logging configuration](logging.md) – Set up proper logging for your application - [Client documentation](https://javadoc.io/doc/io.camunda/camunda-client-java) – Complete Javadoc reference. Make sure you select the relevant Javadoc version as the latest version is shown by default at this URL. **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 --- ## Job worker **Job workers are the backbone of process automation in Camunda 8.** They handle automated tasks (service tasks) in your BPMN processes by continuously polling for available jobs and executing your business logic when jobs become available. This guide covers everything you need to know about implementing and configuring job workers with the Camunda Java Client, from basic concepts to advanced features such as streaming, metrics, and multi-tenancy. ## Quick start Before diving into the details, here is a simple example of creating a job worker: ```java try (final JobWorker workerRegistration = client.newWorker() .jobType(jobType) .handler(new EmailJobHandler()) .open()) { System.out.println("Job worker opened and receiving jobs of type: " + jobType); // Keep the worker running Thread.sleep(Duration.ofMinutes(10)); } catch (InterruptedException e) { throw new RuntimeException(e); } private static class EmailJobHandler implements JobHandler { @Override public void handle(final JobClient client, final ActivatedJob job) { // Perform your business logic here System.out.println("Processing job: " + job.getKey() + " for a process instance: " + job.getProcessInstanceKey()); // Complete the job (or use client.newFailCommand() if something goes wrong) client.newCompleteCommand(job.getKey()) .variables(Map.of("emailSent", true)) .send() .join(); } } ``` For a complete walkthrough, see the [getting started guide](getting-started.md). ## What are job workers? A job worker is a service that: - **Polls for jobs** of a specific type from the Camunda cluster - **Executes your business logic** when jobs are activated - **Reports job completion or failure** back to the cluster - **Handles retries and error scenarios** automatically When you model a service task in your BPMN process and assign it a job type (e.g., `send-email`, `process-payment`), job workers subscribe to these job types and process them as they become available. ## Key benefits - **Decoupled architecture**: Workers run independently from the process engine - **Scalable processing**: Add more workers to handle increased load - **Fault tolerance**: Built-in retry mechanisms and error handling - **Language flexibility**: Implement workers in any language with a Camunda client ## Related resources - [Job worker basics](/components/concepts/job-workers.md) ## How job workers work The Java client provides a job worker that handles polling for available jobs. This allows you to focus on writing code to handle the activated jobs. :::caution REST API limitation The Java client cannot keep the long-lived polling connections required for job polling via the Orchestration Cluster REST API in the following cases: - Performing long-polling job activation when activating jobs larger than the [maximum message size](../../self-managed/components/orchestration-cluster/zeebe/configuration/gateway.md#zeebegatewaynetwork). - Issuing any additional job activation requests while a long-polling connection is open - whether from the same client instance, another client in the same JVM, or a client in a different JVM. When the cases above occurs, the open long-polling request will be interrupted. You may observe workers intermittently stop receiving jobs and cause reduced throughput due to wasted I/O and connection churn. If you encounter this issue, consider using job activation via the Orchestration Cluster REST API with long polling disabled, or switching to the Zeebe gRPC protocol for job activation. Additionally, the long-polling connection might still receive jobs after the Java client is closed. As the Java client does not process these jobs they will time out. This means some jobs are not processed and will become available again after their timeout has elapsed. ::: On `open`, the job worker waits `pollInterval` milliseconds and then polls for `maxJobsActive` jobs. It then continues with the following schedule: 1. If a poll did not activate any jobs, it waits for `pollInterval` milliseconds and then polls for more jobs. 2. If a poll activated jobs, the worker submits each job to the job handler. 3. Every time a job is handled, the worker checks whether the number of unhandled jobs have dropped below 30% (rounded up) of `maxJobsActive`. The first time that happens, it will poll for more jobs. 4. If a poll fails with an error response, a backoff strategy is applied. This strategy waits for the delay provided by the `backoffSupplier` and polls for more jobs. For example, imagine you have 10 process instances and a single job worker configured with `maxJobsActive = 3`. The job worker will first pull three jobs and begin executing them. The threshold to poll for new jobs is 1 (30% of 3 rounded up). After two jobs have completed, the threshold is reached and the job worker will poll for up to 2 additional jobs. This process repeats until the jobs from all 10 process instances are completed. If streaming is enabled (via `streamEnabled`), it will also open a long-living stream over which jobs will be pushed without having to be polled. In such cases, a worker will only buffer up to `maxJobsActive` jobs at the same time. You can then estimate its memory usage as `maxJobsActive` times the max message size. ## Backoff configuration When a poll fails with an error response, the job worker applies a backoff strategy. It waits for some time, after which it polls again for more jobs. This gives a Zeebe cluster some time to recover from a failure. In some cases, you may want to configure this backoff strategy to better fit your situation. The retry delay (i.e. the time the job worker waits after an error before the next poll for new jobs) is provided by the [`BackoffSupplier`](https://github.com/camunda/camunda/blob/main/clients/java/src/main/java/io/camunda/client/api/worker/BackoffSupplier.java). You can replace it using the `.backoffSupplier()` method on the [`JobWorkerBuilder`](https://github.com/camunda/camunda/blob/main/clients/java/src/main/java/io/camunda/client/api/worker/JobWorkerBuilderStep1.java). By default, the job worker uses an exponential backoff implementation, which you can configure using `BackoffSupplier.newBackoffBuilder()`. The backoff strategy is especially useful for dealing with the `GRPC_STATUS_RESOURCE_EXHAUSTED` error response (refer to [gRPC Technical Error Handling](/apis-tools/zeebe-api/technical-error-handling.md)). This error code indicates the Zeebe cluster is currently under too large of a load and has decided to reject this request. By backing off, the job worker helps Zeebe by reducing the load. :::note Zeebe's [backpressure mechanism](../../../self-managed/components/orchestration-cluster/zeebe/operations/backpressure) can also be configured. ::: ## Metrics The job worker exposes metrics through a custom interface: [JobWorkerMetrics](https://github.com/camunda/camunda/blob/main/clients/java/src/main/java/io/camunda/client/api/worker/JobWorkerMetrics.java). These represent specific callbacks used by the job worker to keep track of various internals, e.g. count of jobs activated, count of jobs handled, etc. :::note By default, job workers will not track any metrics, and it's up to the caller to specify an implementation if they wish to make use of this feature. ::: ### Available metrics The API currently supports two metrics: the count of jobs activated, and the count of jobs handled. - **The count of jobs activated** is incremented every time a worker activates new jobs. This is done by calling `JobWorkerMetrics#jobActivated(int)`, with the first argument being the count of jobs newly activated. The method is called before the job is passed to the job handler. - **The count of jobs handled** is incremented every time a worker's `JobHandler` (passed to the builder via `JobWorkerBuilderStep2#handler(JobHandler)`) returns (regardless of whether it was successful). This is done by calling `JobWorkerMetrics#jobHandled(int)`, with the first argument being the count of jobs newly handled. For both counters, the expectation is that implementations will simply increment an underlying counter, and track the rate or increase of this counter to derive the speed at which jobs are activated/handled by a given worker. Additionally, by subtracting both counters, you can derive the count of queued or buffered jobs - jobs which have yet to be handled by the worker. This can help you tune your workers, e.g. scaling in or out, tuning the amount of jobs activated, etc. ### Usage To use job worker metrics, create a new instance of a `JobWorkerMetrics` implementation, and pass it along to the builder: ```java public final JobWorker openWorker(final CamundaClient client, final JobHandler handler) { final JobWorkerMetrics metrics = new MyCustomJobWorkerMetrics(); return client.newJobWorker() .jobType("foo") .handler(handler) .metrics(metrics) .open(); } ``` #### Micrometer implementation The Java client comes with an optional, built-in [Micrometer](https://micrometer.io/) implementation of `JobWorkerMetrics`. :::note [Micrometer](https://micrometer.io/) is a popular metrics facade in the Java ecosystem - what SLF4J is to logging. It can be configured to export metrics to many other systems, such as OpenTelemetry, Prometheus, StatsD, Datadog, etc. ::: If your project does not yet use Micrometer, you need to add it to your dependencies, and wire it up to your metrics backend, [as described in the Micrometer docs](https://micrometer.io/docs). Once Micrometer is set up in your project, you can start using the implementation. For example: ```java public final JobWorker openWorker(final CamundaClient client, final JobHandler handler) { final MeterRegistry meterRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); final JobWorkerMetrics metrics = JobWorkerMetrics .micrometer() .withMeterRegistry(meterRegistry) .withTags(Tags.of("zeebe.client.worker.jobType", "foo", "zeebe.client.worker.name", "bee")) .build(); return client.newJobWorker() .jobType("foo") .handler(handler) .metrics(metrics) .name("bee") .open(); } ``` :::note There are currently no built-in tags, primarily because these are likely to be high cardinality, which can become an issue with some metric registries. If you want per-worker tags, create a different `JobWorkerMetrics` instance per worker. ::: This implementation creates four metrics: | Metric name | Description | Notes | | ----------------------------------- | ------------------------------------- | ------------------------------------------------------------------------- | | camunda.client.worker.job.activated | Counts the number of jobs activated | New | | camunda.client.worker.job.handled | Counts the number of jobs handled | New | | zeebe.client.worker.job.activated | Deprecated counter for jobs activated | Will be removed at 8.10. Use camunda.client.worker.job.activated instead. | | zeebe.client.worker.job.handled | Deprecated counter for jobs handled | Will be removed at 8.10. Use camunda.client.worker.job.handled instead. | :::warning Deprecated metrics The following metrics are deprecated and will be removed in version 8.10: - `zeebe.client.worker.job.activated`, replace with `camunda.client.worker.job.activated` - `zeebe.client.worker.job.handled`, replace with `camunda.client.worker.job.handled` Please update your monitoring integrations to use the new metrics before upgrading to version 8.10. ::: ### Workarounds for additional metrics The decision to track a small set of metrics directly in the client is a conscious one. The idea is we should only be tracking what is not possible for users to track themselves. If you believe a specific metric should be tracked by us, do open a feature request for it. In the meantime, here is a list of workarounds to help you track additional job worker-related metrics that you can already use: #### Job polling count You can use a gRPC [ClientInterceptor](https://grpc.github.io/grpc-java/javadoc/io/grpc/ClientInterceptor.html) or an Apache HttpClient [AsyncExecChainHandler](https://hc.apache.org/httpcomponents-client-5.3.x/current/httpclient5/apidocs/org/apache/hc/client5/http/async/AsyncExecChainHandler.html) to track any client calls, including the `ActivateJobsCommand` call that is sent every time a worker polls for more jobs. Here's an example using Micrometer APIs that integrate a gRPC [ClientInterceptor](https://javadoc.io/doc/io.micrometer/micrometer-core/1.7.2/io/micrometer/core/instrument/binder/grpc/MetricCollectingServerInterceptor.html) and Apache HttpClient [AsyncExecChainHandler](https://javadoc.io/doc/io.micrometer/micrometer-core/1.12.0/io/micrometer/core/instrument/binder/httpcomponents/hc5/ObservationExecChainHandler.html): ```java public CamundaClientBuilder configureClientMetrics(final CamundaClientBuilder builder, final MeterRegistry meterRegistry, final ObservationRegistry observationRegistry) { final ClientInterceptor monitoringInterceptor = new MetricCollectingClientInterceptor(meterRegistry); final AsyncExecChainHandler monitoringHandler = new ObservationExecChainHandler(observationRegistry); return builder.withInterceptors(monitoringInterceptor).withChainHandlers(monitoringHandler); } ``` #### Executor metrics If you wish to tune your job worker executor, you can pass a custom, instrumented executor to the client builder. For example, if we use Micrometer: ```java public CamundaClientBuilder configureClientMetrics( final CamundaClientBuilder builder, final ScheduledExecutorService executor, final MeterRegistry meterRegistry) { final ScheduledExecutorService instrumentedExecutor = ExecutorServiceMetrics.monitor(meterRegistry, executor, "job-worker-executor"); return builder.jobWorkerExecutor(instrumentedExecutor); } ``` ## Job streaming Job workers are designed to regularly poll and activate jobs. It's also possible to use them in a streaming fashion, such that jobs are automatically activated and pushed downstream to workers without requiring an extra round of polling. This greatly cuts down on overall activation latency by completely removing the poll request. ### Usage Enabling job streaming consists of toggling a single flag in the job worker builder: ```java public JobWorkerBuilderStep3 enableStreaming(final JobWorkerBuilderStep3 builder) { return builder.streamEnabled(true); } ``` This configures the job worker to open a long-living stream between itself and a gateway, through which activated jobs will be pushed. **If the stream is closed for any reason - e.g. the gateway crashed, there is a temporary network issue, etc. - it is automatically recreated.** :::note It's also possible to set an overall timeout - so called `streamTimeout` - which ensures the underlying long-living stream is refreshed once the timeout is reached. This is useful to trigger load balancing of your workers overtime, instead of having workers pinned to the same gateway. ::: #### Backfilling Even with streaming enabled, job workers still occasionally poll the cluster for jobs. Due to implementation constraints, when a job is made activate-able, it is pushed out only if there exists a stream for it; if not, it remains untouched. However, if a stream exists, then streaming is always prioritized over polling. This ensures polling will not activate any new jobs, and the worker will back off and poll less often as long as it receives empty responses overtime. #### Backpressure To avoid your workers being overloaded with too many jobs, e.g. running out of memory, the Java job worker relies on the [built-in gRPC flow control mechanism](https://grpc.io/docs/guides/flow-control/). If streaming is enabled, this means the worker will never work on more jobs than the configured `maxJobsActive` parameter. For example, if `maxJobsActive = 32`, then your worker will only work on at most 32 jobs concurrently. If this is already the case, and a 33rd job comes in, the gRPC thread will block, thus signaling the gateway to stop sending more jobs. **If streaming is enabled, back pressure applies to both pushing and polling**. You can then use `maxJobsActive` as a way to soft-bound the memory usage of your worker. For example, if your max message size is 4MB, and `maxJobsActive = 32`, then a single worker could use up to 128MB of memory in the worst case. :::note If the worker blocks longer than the job's deadline, the job will **not** be passed to the worker, but will be dropped. As it will time out on the broker side, it will be pushed again. ::: #### Proxying If you're using a reverse proxy or a load balancer between your worker and your gateway, you may need to configure additional parameters to ensure the job stream is not closed unexpectedly with an error. If you observe regular 504 timeouts, read our guide on [job streaming](../../../self-managed/components/orchestration-cluster/zeebe/zeebe-gateway/job-streaming). By default, the Java job workers have a stream timeout of one hour. You can overwrite this by calling the `streamTimeout` of the job worker builder: ```java final JobWorkerBuilderStep3 builder = ...; builder.streamTimeout(Duration.ofMinutes(30)); ``` ## Multi-tenancy You can configure a job worker to pick up jobs belonging to one or more tenants. The job worker builder provides two ways to control which tenants a worker retrieves jobs for: explicitly providing tenant IDs, or using the tenants assigned to the worker in the engine. ### Filtering by assigned tenants Use `.tenantFilter()` to control how the worker resolves tenants. It accepts a `TenantFilter` enum with two options: - `TenantFilter.PROVIDED` _(default)_: The worker retrieves jobs for the tenant IDs explicitly provided via `.tenantId()` or `.tenantIds()`. See [Filtering by provided tenant IDs](#filtering-by-provided-tenant-ids) below. - `TenantFilter.ASSIGNED`: The worker retrieves jobs for the tenants assigned to it in the engine. When this option is set, any tenant IDs configured via `.tenantId()` or `.tenantIds()` are ignored. Using `TenantFilter.ASSIGNED`: ```java client.newWorker() .jobType("myJobType") .handler(new MyJobTypeHandler()) .tenantFilter(TenantFilter.ASSIGNED) .open(); ``` ### Filtering by provided tenant IDs When using `TenantFilter.PROVIDED` (the default), you must also specify the tenant IDs the worker should retrieve jobs for. :::note The client must be authorized for **all** the provided tenants. If it is not, the job worker will not work on any jobs. ::: Opening a job worker for a single tenant: ```java client.newWorker() .jobType("myJobType") .handler(new MyJobTypeHandler()) .tenantId("myTenant") .open(); ``` Opening a job worker for multiple tenants: ```java client.newWorker() .jobType("myJobType") .handler(new MyJobTypeHandler()) .tenantIds("myTenant", "myOtherTenant") .open(); ``` ### Default tenant You can configure the default tenant(s) using environment variables or system properties. It's configured using `CAMUNDA_DEFAULT_JOB_WORKER_TENANT_IDS` or `camunda.client.worker.tenantIds` respectively. --- ## Logging(Java-client) The client uses SLF4J for logging useful notes, such as exception stack traces when a job handler fails execution. Using the SLF4J API, any SLF4J implementation can be plugged in. The following example uses Log4J 2: ## Maven dependencies ```xml org.apache.logging.log4j log4j-slf4j-impl 2.8.1 org.apache.logging.log4j log4j-core 2.8.1 ``` ## Configuration First, add a file called `log4j2.xml` to the classpath of your application. Then, add the following content: ```xml ``` This will log every log message to the console. ## MDC context Job workers include an MDC context that contains the following: - `processDefinitionKey` - `processInstanceKey` - `elementInstanceKey` - `jobKey` See [the example above](#configuration), which includes `%X` in the pattern to print the entire MDC context. --- ## Physical Tenants **Physical Tenants are strongly isolated execution units within a single Orchestration Cluster**, distinct from the [logical tenant](./job-worker.md#multi-tenancy) mechanism. This page covers how the Java client targets a Physical Tenant, and how to work with multiple Physical Tenants from a single application. For the server-side isolation model, see [Physical Tenant isolation model](/self-managed/concepts/physical-tenants/index.md). For API routing details, see [API routing for Physical Tenants](/self-managed/concepts/physical-tenants/api-routing.md). ## Physical Tenants vs. logical tenants These two mechanisms are independent and can be combined: | | Logical tenant | Physical Tenant | | ----------------------- | ----------------------------------------------- | -------------------------------------------------------------------- | | **Configured on** | Job worker or API call (`tenantId`/`tenantIds`) | The client itself (`physicalTenantId`) | | **Scope** | A subdivision within one Physical Tenant | A separate, isolated execution unit within the Orchestration Cluster | | **Isolation** | Logical only — shared engine, shared storage | Strong — separate primary/secondary storage, separate authorization | | **Targeting mechanism** | API parameter, per call or per worker | gRPC header and REST path prefix, per client instance | A Physical Tenant can have its own set of logical tenants. For example, Physical Tenant `teama` and Physical Tenant `teamb` can each have a logical tenant called `foo` — `teama`'s `foo` and `teamb`'s `foo` are completely isolated from each other. ## Target a Physical Tenant Set the Physical Tenant on the client builder: ```java CamundaClient client = CamundaClient.newClientBuilder() .physicalTenantId("teama") .build(); ``` A Physical Tenant ID must be lowercase alphanumeric and at most 64 characters — an invalid value fails application startup rather than failing at request time. The same setting is also reachable via plain client properties and environment variables, without going through the builder. The environment variable names differ depending on how you configure the client, which is an easy trap if you mix the two: - Bare Java client: `CAMUNDA_PHYSICAL_TENANT_ID` and `CAMUNDA_PREFIX_PHYSICAL_TENANT_PATH`. - [Camunda Spring Boot Starter](/apis-tools/camunda-spring-boot-starter/getting-started.md): `CAMUNDA_CLIENT_PHYSICALTENANTID` (see the [properties reference](/apis-tools/camunda-spring-boot-starter/properties-reference.md)). Setting `physicalTenantId` does three things: - Adds the `Camunda-Physical-Tenant` gRPC metadata header to every gRPC call. - Prefixes the REST base path with `/physical-tenants/teama` (for example, `https://your-cluster/physical-tenants/teama/v2/...`) for the per-tenant API. - Leaves the cluster-scoped REST API (`/cluster/v2/...`, for example `newStatusRequest()`) unprefixed — it's never physical-tenant-scoped, since it reports on the cluster as a whole. If your configured REST address already carries a `/physical-tenants/` segment, the client strips it again when building the cluster-scoped base. If `physicalTenantId` is not set, the client targets the **default** Physical Tenant on both protocols. ### Using a pre-prefixed REST address If your REST address already points at a tenant-specific path — for example, behind a reverse proxy that performs the routing — disable the automatic path prefix so the client does not insert the tenant segment a second time: ```java CamundaClient client = CamundaClient.newClientBuilder() .physicalTenantId("teama") .restAddress(URI.create("https://your-proxy/teama")) .prefixPhysicalTenantPath(false) .build(); ``` The client still appends the `/v2` API path to whatever address you configure, so leave it off your configured `restAddress`. `prefixPhysicalTenantPath(false)` only affects the REST base path. The gRPC header is always sent when `physicalTenantId` is set, regardless of this setting. With a blank (whitespace-only) `physicalTenantId`, the two protocols diverge in the bare client: the REST prefix is skipped, but the gRPC interceptor is still installed and sends an empty header value, since it's added whenever the configured value is non-null. The Spring Boot Starter normalizes blank to `null` before it reaches the client, so this only affects applications wiring the builder directly from their own configuration. Set the value to `null`, not an empty string, to target the default tenant. ## Targeting multiple Physical Tenants A single `CamundaClient` instance targets exactly one Physical Tenant. Multiplicity lives at the application layer, not inside the client — to interact with multiple Physical Tenants, create one client instance per tenant: ```mermaid graph LR app["Your application"] app --> clientA["CamundaClient\nphysicalTenantId: teama"] app --> clientB["CamundaClient\nphysicalTenantId: teamb"] subgraph cluster["Single orchestration cluster"] teama["Physical Tenant teama"] teamb["Physical Tenant teamb"] end clientA -->|"gRPC header +\nREST path prefix"| teama clientB -->|"gRPC header +\nREST path prefix"| teamb ``` ```java CamundaClient teamaClient = CamundaClient.newClientBuilder() .physicalTenantId("teama") .build(); CamundaClient teambClient = CamundaClient.newClientBuilder() .physicalTenantId("teamb") .build(); ``` If you're using the [Camunda Spring Boot Starter](/apis-tools/camunda-spring-boot-starter/getting-started.md), it manages multiple named client instances for you — see [multi-client configuration](/apis-tools/camunda-spring-boot-starter/configuration.md#multi-client-configuration-physical-tenants) — rather than constructing and managing `CamundaClient` instances directly. ## Code examples ### Create a process instance scoped to a Physical Tenant ```java teamaClient.newCreateInstanceCommand() .bpmnProcessId("order-process") .latestVersion() .send() .join(); ``` The process instance is created within Physical Tenant `teama`. No additional API parameter is needed — the client's `physicalTenantId` determines the target. ### Register a job worker ```java teamaClient.newWorker() .jobType("shipOrder") .handler(new ShipOrderHandler()) .open(); ``` This worker only polls Physical Tenant `teama` — the targeting comes from the client, on whichever protocol it uses. There's no single worker registration that spans Physical Tenants. Open the same job type on each client instance: ```java Stream.of(teamaClient, teambClient) .forEach(client -> client.newWorker() .jobType("shipOrder") .handler(new ShipOrderHandler()) .open()); ``` Each registration opens an independent polling loop against its own client's Physical Tenant. Inside the handler, call `activatedJob.getPhysicalTenantId()` to find out which Physical Tenant the job belongs to — this is populated on every `ActivatedJob`, whether the job arrived over gRPC or REST, and returns the ID of the Physical Tenant the activation request was routed to, which is the default Physical Tenant when the request did not target one: ```java public class ShipOrderHandler implements JobHandler { @Override public void handle(JobClient client, ActivatedJob job) { String physicalTenantId = job.getPhysicalTenantId(); // route or log based on physicalTenantId as needed } } ``` ### Update a variable scoped to a Physical Tenant ```java teamaClient.newSetVariablesCommand(processInstanceKey) .variables(Map.of("status", "shipped")) .send() .join(); ``` `processInstanceKey` values are only valid within the Physical Tenant that created them — using a key from `teamb` against `teamaClient` fails, since the instance doesn't exist from `teama`'s perspective. ## Best practices ### One client vs. multiple clients Use a single client (default Physical Tenant, no `physicalTenantId` set) unless your application genuinely needs to operate across more than one Physical Tenant. If it does, prefer the [Spring Boot Starter's multi-client support](/apis-tools/camunda-spring-boot-starter/configuration.md#multi-client-configuration-physical-tenants) over manually constructing and tracking `CamundaClient` instances — it centralizes lookup (`CamundaClientRegistry`) and lifecycle management for you. ### Connection pooling Each `CamundaClient` instance maintains its own gRPC channel and REST connection pool. Running multiple clients (one per Physical Tenant) means multiple independent connection pools — there's no pooling shared across clients. Size your application's resource expectations accordingly when targeting many Physical Tenants from one process. ### Error handling for unauthorized tenant access There is no dedicated exception type for Physical Tenant errors — the Java client surfaces them as its regular per-protocol exception, carrying the underlying status: - **REST:** `ProblemException` (extends `ClientHttpException`) — call `code()` for the HTTP status and `details()` for the `ProblemDetail` body. - **gRPC:** `ClientStatusException` — call `getStatus()` / `getStatusCode()`. The status code for an unrecognized Physical Tenant ID differs by protocol, which matters more than the exception type: - **REST** always returns `404 Not Found` for an unknown tenant. This comes from a pre-security filter that leaves unknown tenants to the catch-all routing chain, so it's returned regardless of whether the caller is authenticated. - **gRPC** returns `404 Not Found` only when the API is unprotected (no authentication configured). With authentication enabled, an unknown tenant instead returns `UNAUTHENTICATED`, deliberately without echoing the tenant ID — so tenant existence isn't revealed to an unauthenticated caller. If you're debugging a gRPC call that fails with `UNAUTHENTICATED`, don't assume it's a credentials problem — a typo'd or nonexistent Physical Tenant ID produces the same status on an authenticated cluster. For the full breakdown of authorization outcomes at the API level (missing/invalid credentials vs. insufficient permission), see [How to determine who can access a tenant](/self-managed/concepts/physical-tenants/authorization-model.md#how-to-determine-who-can-access-a-tenant). ### Monitoring and logging across tenants Built-in job worker metrics are tagged only by job type and action — not by client name or Physical Tenant. Running the same job type across several clients doesn't just lack a tenant dimension in these metrics: the series for each client collapse into one, since there's nothing to tell them apart by. The job worker actuator endpoint has the same gap — its response carries no client name, so a multi-client application sees indistinguishable duplicate entries for the same job type. This is usually the first place people look when a multi-tenant application's metrics don't add up. If you run multiple client instances and need to distinguish their metrics, tag your own application-level metrics using `ActivatedJob.getPhysicalTenantId()` inside job handlers, or the client/tenant identifier you already have at hand when issuing other commands. There is no built-in tracing for multi-tenant operations today — this is left entirely to the application layer. --- ## Secrets For what a `camunda.secrets.` reference is and how it's resolved, see [Secret resolution](/components/concepts/secret-resolution.md). For the request and response contract shared by these commands, see [Secrets](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-secrets.md). ## Resolve a batch of references ```java ResolveSecretsResponse response = client.newResolveSecretsCommand() .references("camunda.secrets.API_TOKEN", "camunda.secrets.db-password") .send() .join(); ``` A per-reference failure does not throw. `send()` only raises when the whole request is rejected. For example, more than 20 references in one call. Read the outcome from the response instead: ```java if (!response.isFullyResolved()) { response.getErrors().forEach(error -> log.warn("Could not resolve {}: {}", error.getReference(), error.getCode())); } Optional token = response.getValue("camunda.secrets.API_TOKEN"); ``` - `getResolved()` returns the references that resolved, each with its value. - `getErrors()` returns the references that didn't, each with a typed `SecretErrorCode` (`NOT_FOUND`, `ACCESS_DENIED`, `INVALID_REFERENCE`, `UNREADABLE`) and a server-provided message. - `isFullyResolved()` is `true` only when every requested reference resolved with no errors. - `getValue(reference)` looks up a single resolved value without scanning `getResolved()` yourself. `ResolveSecretsResponse`, `ResolvedSecret`, and `ResolutionError` all omit resolved values and error messages from their `toString()`. Logging the response itself or an object holding one never prints a secret value. Log `getReference()` and `getCode()` instead of the response object. ## List known references ```java ListSecretsResponse response = client.newListSecretsCommand().send().join(); List references = response.getReferences(); ``` `getReferences()` returns reference names only, never values, and only the references the caller holds `SECRET:READ` on. ## Physical-tenant scoping Both commands resolve and list against the secret stores of the client's own [Physical Tenant](./physical-tenants.md). Neither command takes a separate tenant parameter. ## Testing To supply cluster secrets in tests, see [cluster secrets in Camunda Process Test](/apis-tools/testing/connectors.md#cluster-secrets). --- ## Cluster management API The cluster management API lets you manage a Self-Managed Orchestration Cluster at runtime, including its topology, exporters, backups, and upgrade readiness. ## About this API The API is a set of custom endpoints available through [Spring Boot Actuator](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints) on the management port (default `9600`). It's separate from the [Orchestration Cluster API](../orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). :::note The management port is typically not publicly exposed. If the machine where you run requests can't reach the gateway, create a private connection such as `kubectl port-forward svc/camunda-zeebe-gateway 9600:9600`, then use `localhost` as the gateway host. ::: ## Reference | API | Base path | | :--------------------------------------------------------------------------------------------- | :--------------------------------------------------- | | [Cluster topology management](specifications/cluster/cluster-topology-management-api.info.mdx) | `/actuator/cluster` | | [Exporters](specifications/exporters/exporters-management-api.info.mdx) | `/actuator/exporters` | | [Backups](specifications/backups/backup-management-api.info.mdx) | `/actuator/backupRuntime`, `/actuator/backupHistory` | | [Upgrade readiness](specifications/upgrade-readiness/upgrade-readiness-api.info.mdx) | `/actuator/upgradeReadiness` | For usage guidance and examples, see [management API operations](../../self-managed/components/orchestration-cluster/zeebe/operations/management-api.md). --- ## Migrate Component V1 APIs :::note Have you already migrated? You do not need to perform this migration again if you already did this when upgrading to version 8.8. This guide is retained to help customers migrate before upgrading from 8.9 to 8.10. See [API and SDK changes to migrate before Camunda 8.10](../migration-manuals/migrate-to-810.md#api-and-sdk-changes-to-migrate-before-camunda-810). ::: ## About This document outlines the changes required to migrate from the component REST APIs before upgrading to Camunda 8.10, where the V1 component APIs are removed. Use it if migration to the new [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) was not yet possible during your 8.8 or 8.9 upgrade. In this context, **components** refer to the standalone Camunda applications **Operate** and **Tasklist**, each exposing its own V1 REST API. :::note As of version 8.8, the V1 component APIs are deprecated. They are removed in 8.10, so complete this migration before upgrading. We strongly recommend [migrating to the Orchestration Cluster REST API](/apis-tools/migration-manuals/migrate-to-camunda-api.md) where possible. ::: ## Migrate V1 APIs With Camunda 8.8, permissions for resource access have been reworked. For the V1 APIs, this means that access to endpoints now depends on specific read and write permissions for related resources. To continue using the V1 APIs, users and clients must be assigned the appropriate permissions under [the new authorization model](/components/concepts/access-control/authorizations.md). Users now require wildcard (`*`) permissions for the resource type and permission type being accessed. :::info For guidance on assigning permissions in Admin, see the [Admin authorization guide](../../components/admin/authorization.md). ::: ### Mapping Operate permissions to new authorizations To maintain the same access level for the Operate V1 API, apply the following authorizations: **`operate-api:read`** is replaced by: - `PROCESS_DEFINITION:*:READ_PROCESS_DEFINITION,READ_PROCESSINSTANCE` - `DECISION_DEFINITION:*:READ_DECISION_DEFINITION` - `DECISION_REQUIREMENTS_DEFINITION:*:READ` **`operate-api:write`** is replaced by: - `PROCESS_DEFINITION:*:DELETE_PROCESS_INSTANCES` ### Operate V1 API permission matrix To enable more fine-grained access control, the matrix below details the required permissions for each Operate V1 API endpoint. Ensure the user has general access (resource ID `*`) for each listed resource and permission type. | Endpoint | Resource Type | Permission type | | ----------------------------------------------- | -------------------------------- | ------------------------ | | `POST /v1/process-definitions/search` | PROCESS_DEFINITION | READ_PROCESS_DEFINITION | | `GET /v1/process-definitions/:key` | PROCESS_DEFINITION | READ_PROCESS_DEFINITION | | `GET v1/process-definitions/:key/xml` | PROCESS_DEFINITION | READ_PROCESS_DEFINITION | | `POST /v1/decision-definitions/search` | DECISION_DEFINITION | READ_DECISION_DEFINITION | | `GET /v1/decision-definitions/:key` | DECISION_DEFINITION | READ_DECISION_DEFINITION | | `POST /v1/decision-instances/search` | DECISION_DEFINITION | READ_DECISION_INSTANCE | | `GET /v1/decision-instances/:id` | DECISION_DEFINITION | READ_DECISION_INSTANCE | | `POST /v1/flownode-instances/search` | PROCESS_DEFINITION | READ_PROCESS_INSTANCE | | `GET /v1/flownode-instances/:key` | PROCESS_DEFINITION | READ_PROCESS_INSTANCE | | `POST /v1/variables/search` | PROCESS_DEFINITION | READ_PROCESS_INSTANCE | | `GET /v1/variables/:key` | PROCESS_DEFINITION | READ_PROCESS_INSTANCE | | `POST /v1/process-instances/search` | PROCESS_DEFINITION | READ_PROCESS_INSTANCE | | `GET /v1/process-instances/:key` | PROCESS_DEFINITION | READ_PROCESS_INSTANCE | | `GET /v1/process-instances/:key/statistics` | PROCESS_DEFINITION | READ_PROCESS_INSTANCE | | `GET /v1/process-instances/:key/sequence-flows` | PROCESS_DEFINITION | READ_PROCESS_INSTANCE | | `DEL /v1/process-instances/:key` | PROCESS_DEFINITION | DELETE_PROCESS_INSTANCE | | `POST /v1/drd/search` | DECISION_REQUIREMENTS_DEFINITION | READ | | `GET /v1/drd/:key` | DECISION_REQUIREMENTS_DEFINITION | READ | | `GET /v1/drd/:key/xml` | DECISION_REQUIREMENTS_DEFINITION | READ | | `POST /v1/incidents/search` | PROCESS_DEFINITION | READ_PROCESS_INSTANCE | | `GET /v1/incidents/:key` | PROCESS_DEFINITION | READ_PROCESS_INSTANCE | ### Mapping Tasklist permissions to new authorizations To maintain the same access level for the Tasklist V1 API, apply the following authorizations: **`tasklist-api:read`** is replaced by: - `PROCESS_DEFINITION:*:READ_PROCESS_DEFINITION,READ_USER_TASK` **`taslist-api:write`** is replaced by: - `PROCESS_DEFINITION:*:UPDATE_USER_TASK` ### Tasklist V1 API permission matrix To enable more fine-grained access control, the matrix below details the required permissions for each Tasklist V1 API endpoint. Ensure the user has general access (resource ID `*`) for each listed resource and permission type. | Endpoint | Resource Type | Permission type | | ----------------------------------------- | ------------------ | ---------------- | | `GET /v1/forms/:formId` | PROCESS_DEFINITION | READ_USER_TASK | | `POST /v1/tasks/search` | PROCESS_DEFINITION | READ_USER_TASK | | `GET /v1/tasks/:taskId` | PROCESS_DEFINITION | READ_USER_TASK | | `PATCH /v1/tasks/:taskId/assign` | PROCESS_DEFINITION | UPDATE_USER_TASK | | `PATCH /v1/tasks/:taskId/unassign` | PROCESS_DEFINITION | UPDATE_USER_TASK | | `PATCH /v1/tasks/:taskId/complete` | PROCESS_DEFINITION | UPDATE_USER_TASK | | `POST /v1/tasks/:taskId/variables` | PROCESS_DEFINTION | UPDATE_USER_TASK | | `POST /v1/tasks/:taskId/variables/search` | PROCESS_DEFINITION | READ_USER_TASK | | `GET /v1/variables/:variableId` | PROCESS_DEFINITION | READ_USER_TASK | --- ## Migrate from gRPC to the Orchestration Cluster API :::note Have you already migrated? You do not need to perform this migration again if you already did this when upgrading to version 8.8. This guide is retained to help customers migrate before upgrading from 8.9 to 8.10. See [API and SDK changes to migrate before Camunda 8.10](../migration-manuals/migrate-to-810.md#api-and-sdk-changes-to-migrate-before-camunda-810). ::: ## About This guide provides an overview of the process for migrating to the Orchestration Cluster REST API. The [Orchestration Cluster API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) is the official REST API for connecting to Orchestration Cluster, automating processes, and implementing job workers. ## Camunda Java Client In version 8.8.0, the [Camunda Java Client](/apis-tools/java-client/getting-started.md) changes to use the Orchestration Cluster API as a default cluster communication method. :::info Refer to the [Camunda Java Client migration guide](migrate-to-camunda-java-client.md#protocol-and-connection-rest-vs-grpc-selection) for details on how you can continue using gRPC. ::: ## gRPC vs REST mapping reference The following table provides a mapping reference between gRPC methods and their equivalent REST API endpoints in the Orchestration Cluster API. :::info For detailed information on each REST endpoint, see [Orchestration Cluster API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). ::: | gRPC Method Name (Gateway.proto) | Orchestration Cluster REST API Endpoint | Notes | | --------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------ | | `ActivateJobs` | `POST /v2/jobs/activation` | Batch job activation via long polling (streaming not available in REST). | | `BroadcastSignal` | `POST /v2/signals/broadcast` | Triggers signal events. | | `CancelProcessInstance` | `POST /v2/process-instances/{processInstanceKey}/cancellation` | Cancels a process instance. | | `CompleteJob` | `POST /v2/jobs/{jobKey}/completion` | Completes a job. | | `CreateProcessInstance` | `POST /v2/process-instances` | Starts a new process instance. | | `CreateProcessInstanceWithResult` | `POST /v2/process-instances?awaitCompletion=true` | Starts a process instance, waits for completion. | | `DeleteResource` | `POST /v2/resources/{resourceKey}/deletion` | Deletes a resource. | | `DeployResource` | `POST /v2/deployments` | Deploys BPMN, DMN, or form resources (multipart upload). | | `EvaluateDecision` | `POST /v2/decisions/evaluation` | Evaluates a DMN decision by key or id. | | `FailJob` | `POST /v2/jobs/{jobKey}/failure` | Marks a job as failed. | | `MigrateProcessInstance` | `POST /v2/process-instances/{processInstanceKey}/migration` | Migrates a process instance (phase 1 only). | | `ModifyProcessInstance` | `POST /v2/process-instances/{processInstanceKey}/modification` | Modifies a running process instance. | | `PublishMessage` | `POST /v2/messages/publication` | Publishes a message asynchronously. | | `ResolveIncident` | `POST /v2/incidents/{incidentKey}/resolution` | Resolves an incident. | | `SetVariables` | `PUT /v2/element-instances/{elementInstanceKey}/variables` | Sets variables (local/global by param). | | `ThrowError` | `POST /v2/jobs/{jobKey}/error` | Throws BPMN error from worker to engine. | | `Topology` | `GET /v2/topology` | Returns cluster info. | | `UpdateJobRetries` | `PATCH /v2/jobs/{jobKey}` | Updates job retries, PATCH can update multiple job properties. | | `UpdateJobTimeout` | `PATCH /v2/jobs/{jobKey}` | Updates job timeout, PATCH can update multiple job properties. | --- ## Migrate from Web Modeler to the Camunda Hub API :::warning Deprecation notice Web Modeler API v1 is deprecated in Camunda 8.10 and will be removed in 8.12. Migrate to [Camunda Hub API v2](/apis-tools/hub-api-saas/overview.md) before upgrading to 8.12. ::: ## About this migration Web Modeler API v1 is the REST API for Web Modeler, a standalone product for modeling and managing process diagrams. It exposes resources like projects, folders, files, and collaborators as they exist within Web Modeler. [Camunda Hub API v2](/apis-tools/hub-api-saas/overview.md) is the successor API for the broader Camunda Hub platform. Camunda Hub unifies organizational management, workspace governance, and process modeling into a single platform. As a result, the conceptual model and architecture of the API have changed. :::tip Camunda Hub API v2 adopts the [Orchestration Cluster API v2](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) conventions. If you're already familiar with the Orchestration Cluster API v2, you will recognize patterns such as the offset-based pagination model, explicit filter operators, and flat response structures used throughout Camunda Hub API v2. ::: Before migrating, familiarize yourself with the structural and terminology changes introduced in Camunda 8.10. ## Structure and terminology Camunda 8.10 changes how resources are organized. Before Camunda 8.10, Web Modeler resources were organized like this: ``` Organization ├─ Project │ ├─ Process application │ │ ├─ File │ │ └─ Folder │ │ └─ File │ ├─ Folder │ │ └─ File │ └─ File └─ Project ``` Organizations had projects. Projects optionally contained process applications, folders, and files. Starting with Camunda 8.10, Camunda Hub resources are organized like this: ``` Organization └─ Workspace ├─ Project │ ├─ Folder │ │ ├─ File │ │ └─ Folder │ └─ File └─ Project ``` Organizations have workspaces. Workspaces contain projects. Projects optionally contain folders and files. The new structure introduces the following terminology changes: | Web Modeler (\<8.10) | Camunda Hub (8.10+) | Notes | | :------------------- | :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Project | Workspace | Files and folders can no longer be created at the workspace level. | | Process application | Project | Process applications weren't explicitly exposed in Web Modeler API v1. In Camunda Hub API v2, there is a dedicated [project API](/apis-tools/hub-api-saas/specifications/create-project.api.mdx). | In Camunda Hub API v2, the endpoint paths, field names, and underlying data all reflect the structural and terminology changes. In Web Modeler API v1 running on Camunda 8.10+, only the underlying data reflects the new organization. The sections below identify all affected endpoints and fields. ## Deprecation timeline | Version | Action | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **8.10** | Camunda Hub API v2 ships alongside Web Modeler API v1. Web Modeler API v1 is documented as **deprecated**, and its OpenAPI spec is marked `deprecated: true`. | | **8.11** | Web Modeler API v1 remains available but isn't extended. No new features are added to deprecated endpoints. | | **8.12** | Web Modeler API v1 endpoints are removed. Applications still using v1 receive `404`. | ## General changes The following sections cover changes that apply across the entire API, regardless of which resource you're working with. Review these before making any endpoint-specific changes. ### Base URLs The base URL has changed for both SaaS and Self-Managed deployments. Update any hardcoded URLs or environment variables in your integration. | Environment | Web Modeler API v1 | Camunda Hub API v2 | | ------------ | ----------------------------------------- | ------------------------------- | | SaaS | `https://modeler.cloud.camunda.io/api/v1` | `https://hub.camunda.io/api/v2` | | Self-Managed | `http://localhost:8070/api/v1` | `http://localhost:8088/api/v2` | In Camunda 8 Self-Managed, the URLs depend on your configuration. The URLs and ports provided in this table are examples based on a [no-domain Helm deployment to a local kind cluster](/self-managed/deployment/helm/cloud-providers/kind.md#no-domain-mode-deployment). ### Authentication See the Camunda Hub API authentication guide for [SaaS](/apis-tools/hub-api-saas/authentication.md) or [Self-Managed](/apis-tools/hub-api-sm/authentication.md) for setup instructions. ### Pagination Offset pagination in Camunda Hub API v2 is different from Web Modeler API v1. In Web Modeler API v1, you use two fields to paginate items: - `page` specifies the page to return, starting with page 0. - `size` specifies the number of items per page. For example: ```json title="Web Modeler API v1" { "page": 3, "size": 20 } ``` This request skips the first three _pages_ of 20 items (pages 0–2 and item indexes 0–59, inclusive) and returns the fourth page of 20 items (indexes 60–79). If there aren't enough items to fill the fourth page, you receive all remaining items. The response includes two fields, `items` and `total`: ```json title="Web Modeler API v1" { "items": [ ... ], "total": 141 } ``` In Camunda Hub API v2, you use a `page` object with two fields: - `page.from` specifies the offset, the item index to start from, starting with index 0. - `page.limit` limits the number of items returned. For example: ```json title="Camunda Hub API v2" { "page": { "from": 60, "limit": 20 } } ``` Instead of specifying the number of pages to skip, you specify the index to start _from_ (60) and the maximum number, or _limit_, of items to return (20). This request returns the items at indexes 60–79. As in v1, if there are fewer items than the limit, you receive all remaining items. The new response replaces `total` with a new `page` object that includes two fields, `totalItems` and `hasMoreTotalItems`: ```json title="Camunda Hub API v2" { "items": [ ... ], "page": { "totalItems": 360, "hasMoreTotalItems": false } } ``` In addition to the different pagination model, the default page size has changed. In v1, the default page size is 10. In v2, the default limit is 100. ### Date filters Web Modeler API v1 supports a custom date precision syntax that encodes a comparison operator, timestamp, and truncation unit into a single string. Camunda Hub API v2 uses explicit operators instead. You compute period boundaries yourself. The following examples show equivalent date filters in Web Modeler API v1 and Camunda Hub API v2: | Web Modeler API v1 | Camunda Hub API v2 | Explanation | | ------------------------------ | ------------------------------------------------------------------------ | ------------------------------------ | | `2023-09-20T00:00:00Z\|\|/y` | `{ "$gte": "2023-01-01T00:00:00Z", "$lte": "2023-12-31T23:59:59.999Z" }` | Within year 2023 | | `2023-09-20T00:00:00Z\|\|/M` | `{ "$gte": "2023-09-01T00:00:00Z", "$lte": "2023-09-30T23:59:59.999Z" }` | Within September 2023 | | `>=2023-09-20T00:00:00Z\|\|/y` | `{ "$gte": "2023-01-01T00:00:00Z" }` | On or after start of 2023 | | `<2023-09-20T00:00:00Z\|\|/M` | `{ "$lt": "2023-09-01T00:00:00Z" }` | Before September 2023 | | `2023-09-20T11:31:20Z` | `{ "$eq": "2023-09-20T11:31:20Z" }` | Exact match | | `>=2023-09-20T11:31:20Z` | `{ "$gte": "2023-09-20T11:31:20Z" }` | On or after a specific date and time | The following date filter operators are available in Camunda Hub API v2: | Operator | Description | | -------- | --------------------------- | | `$eq` | Equals (same as v1 default) | | `$gt` | Greater than | | `$gte` | Greater than or equal to | | `$lt` | Less than | | `$lte` | Less than or equal to | ### Search filters Web Modeler API v1 uses equality-only filters, except for dates: ```json title="Web Modeler API v1" { "filter": { "name": "my-process", "type": "bpmn" } } ``` You can still use simple equality filters in Camunda Hub API v2, and you can also use more advanced explicit filter operators: ```json title="Camunda Hub API v2" { "filter": { "name": { "$eq": "my-process" }, "type": { "$in": ["bpmn"] } } } ``` The following advanced filter operators are available in Camunda Hub API v2: | Operator | Description | Example | | ---------------- | --------------------------------------- | --------------------------------------------------------------------------- | | `$eq` | Equals (same as v1 default) | `{ "name": { "$eq": "my-process" } }` | | `$neq` | Not equals | `{ "type": { "$neq": "dmn" } }` | | `$gt` / `$gte` | Greater than / greater than or equal to | `{ "created": { "$gte": "2024-01-01T00:00:00Z" } }` | | `$lt` / `$lte` | Less than / less than or equal to | `{ "created": { "$lt": "2024-06-01T00:00:00Z" } }` | | `$like` | Pattern match (SQL LIKE) | `{ "name": { "$like": "%order%" } }` | | `$in` / `$notIn` | In / not in list | `{ "type": { "$in": ["bpmn", "form"] } }` | | `$exists` | Null check | `{ "folderKey": { "$exists": false } }` | | `$or` | Logical OR | `{ "$or": [{ "type": { "$eq": "bpmn" } }, { "type": { "$eq": "form" } }] }` | ## Dropped endpoints The following v1 endpoints have no v2 equivalent: | Web Modeler API v1 | Notes | | ------------------------------------------------------ | ---------------------------------------------------------------------------------------- | | `POST /v1/milestones` | Milestones were deprecated in Camunda 8.7. Use the [Versions API](#version-api) instead. | | `GET /v1/milestones/{milestoneId}` | Use the [Versions API](#version-api) instead. | | `PATCH /v1/milestones/{milestoneId}` | Use the [Versions API](#version-api) instead. | | `DELETE /v1/milestones/{milestoneId}` | Use the [Versions API](#version-api) instead. | | `GET /v1/versions/compare/{version1Id}...{version2Id}` | See [Compare two versions](#compare-two-versions). | ## File API The following sections cover changes that apply to file API endpoints. ### Endpoint mapping All file API endpoints have a Camunda Hub API v2 equivalent: | Operation | Web Modeler API v1 | Camunda Hub API v2 | | ------------- | --------------------------- | ---------------------------- | | Create a file | `POST /v1/files` | `POST /v2/files` | | Get a file | `GET /v1/files/{fileId}` | `GET /v2/files/{fileKey}` | | Update a file | `PATCH /v1/files/{fileId}` | `PATCH /v2/files/{fileKey}` | | Delete a file | `DELETE /v1/files/{fileId}` | `DELETE /v2/files/{fileKey}` | | Search files | `POST /v1/files/search` | `POST /v2/files/search` | ### Field mapping {#file-api-field-mapping} The following fields have changed across all file endpoints: | Web Modeler API v1 | Camunda Hub API v2 | Application | Notes | | ------------------ | ------------------ | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `fileType` | `type` | Request/response | Renamed. `element_template` is now `element-template`. File creation no longer supports `connector_template`. Existing connector template files are reported as `element-template`. | | `folderId` | `folderKey` | Request/response | Renamed. If the file isn't in a folder, v1 endpoints return the project ID (["process application" before Camunda 8.10](#structure-and-terminology)), and v2 endpoints return `null`. | | `projectId` | `projectKey` | Request/response | In v1, `projectId` refers to the ID of the "workspace" ([called the "project" before Camunda 8.10](#structure-and-terminology)). In v2, `projectKey` refers to the key of the "project". The concept of a "project" was [called a "process application" before Camunda 8.10](#structure-and-terminology), but process application data is not explicitly exposed in v1. | | - | `content` | Response | In v1, `content` is returned as a separate top-level field alongside `metadata`. In v2, it's included in the flat file object. | | `id` | `fileKey` | Response | Renamed | | `canonicalPath` | `canonicalPath` | Response | In v1, `canonicalPath` is a list of objects. In v2, it's a [string](#canonical-path). | ### Canonical path In Web Modeler API v1, `canonicalPath` is an array of objects containing an `id` and a `name` for each path element in the file's unique path: ```json title="Web Modeler API v1" { "canonicalPath": [ { "id": "1abf0198-3462-4fd2-a0e9-362f213d81d0", "name": "Process application" }, { "id": "b06c97f5-7e39-4108-b947-2848fdc023f0", "name": "Parent folder" }, { "id": "62132025-57ff-4077-8c80-fc4ebe84aebe", "name": "Child folder" } ] } ``` In Camunda Hub API v2, `canonicalPath` expresses the file's unique path as a `/`-delimited string. Unlike Web Modeler API v1, which includes `projects` ([called `process applications` before Camunda 8.10](#structure-and-terminology)), Camunda Hub API v2 only includes folder keys. The project is given in a separate field, called `projectKey`: ```json title="Camunda Hub API v2" { "projectKey": "1abf0198-3462-4fd2-a0e9-362f213d81d0", "canonicalPath": "b06c97f5-7e39-4108-b947-2848fdc023f0/62132025-57ff-4077-8c80-fc4ebe84aebe" } ``` ### Get a file The v1 response returns a nested structure, with `metadata` and `content` as separate top-level fields: ```json title="Web Modeler API v1" { "metadata": { "id": "57f4635b-5452-44a5-9020-bfce455484ab", "name": "process", "projectId": "b9b57035-fbce-4412-a7d5-9f0df61ed74d", "folderId": "62132025-57ff-4077-8c80-fc4ebe84aebe", "simplePath": "Process application/Parent folder/Child folder/process.bpmn", "canonicalPath": [ { "id": "1abf0198-3462-4fd2-a0e9-362f213d81d0", "name": "Process application" }, { "id": "b06c97f5-7e39-4108-b947-2848fdc023f0", "name": "Parent folder" }, { "id": "62132025-57ff-4077-8c80-fc4ebe84aebe", "name": "Child folder" } ], "revision": 5, "type": "BPMN", "created": "2026-07-08T09:59:34.262858Z", "createdBy": { "name": "...", "email": "..." }, "updated": "2026-07-08T10:05:09.045782Z", "updatedBy": { "name": "...", "email": "..." } }, "content": "..." } ``` The v2 response is a flat object: ```json title="Camunda Hub API v2" { "fileKey": "57f4635b-5452-44a5-9020-bfce455484ab", "name": "process", "projectKey": "1abf0198-3462-4fd2-a0e9-362f213d81d0", "folderKey": "62132025-57ff-4077-8c80-fc4ebe84aebe", "simplePath": "Parent folder/Child folder/process.bpmn", "canonicalPath": "b06c97f5-7e39-4108-b947-2848fdc023f0/62132025-57ff-4077-8c80-fc4ebe84aebe", "revision": 5, "type": "bpmn", "content": "...", "created": "2026-07-08T09:59:34.262858Z", "createdBy": { "name": "...", "email": "..." }, "updated": "2026-07-08T10:05:09.045782Z", "updatedBy": { "name": "...", "email": "..." } } ``` ### Update a file A `revision` is now required to prevent overwriting concurrent changes. Fetch the current revision from a get or create response, and include it in your update request: ```json title="Camunda Hub API v2" { "name": "process", "projectKey": "1abf0198-3462-4fd2-a0e9-362f213d81d0", "folderKey": "62132025-57ff-4077-8c80-fc4ebe84aebe", // highlight-next-line "revision": 5, "content": "..." } ``` ### Search files In addition to the [general field changes](#file-api-field-mapping), the following request fields have changed: | Web Modeler API v1 | Camunda Hub API v2 | Notes | | ------------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------- | | `filter` | `filter` | Now uses [advanced operators](#search-filters), including `$eq`, `$in`, and `$like` | | `filter.folderId` | `filter.folderKey` | Renamed | | `filter.createdBy.email` | `filter.createdBy` | In v1, `createdBy` is an object. In v2, it's a string representing the creator's email address. | | `filter.updatedBy.email` | `filter.updatedBy` | In v1, `updatedBy` is an object. In v2, it's a string representing the updater's email address. | | `sort.direction` | `sort.order` | Renamed | | `filter.projectId` | - | Removed. You can no longer filter files by workspace (["project" before Camunda 8.10](#structure-and-terminology)). | `content` is `null` on all items in the search response. Fetch individual files to retrieve content. The following example shows a v1 request: ```json title="Web Modeler API v1" { "filter": { "folderId": "16b0beb0-e6c0-494b-9953-c3ff461975f7", "createdBy": { "email": "jane.doe@email.com" } }, "sort": { "field": "name", "direction": "DESC" } } ``` The equivalent v2 request: ```json title="Camunda Hub API v2" { "filter": { "folderKey": { "$eq": "16b0beb0-e6c0-494b-9953-c3ff461975f7" }, "createdBy": "jane.doe@email.com" }, "sort": { "field": "name", "order": "DESC" } } ``` ## Folder API The following sections cover changes that apply to folder API endpoints. ### Endpoint mapping All folder API endpoints have a Camunda Hub API v2 equivalent: | Operation | Web Modeler API v1 | Camunda Hub API v2 | | --------------- | ------------------------------- | -------------------------------- | | Create a folder | `POST /v1/folders` | `POST /v2/folders` | | Get a folder | `GET /v1/folders/{folderId}` | `GET /v2/folders/{folderKey}` | | Update a folder | `PATCH /v1/folders/{folderId}` | `PATCH /v2/folders/{folderKey}` | | Delete a folder | `DELETE /v1/folders/{folderId}` | `DELETE /v2/folders/{folderKey}` | ### Field mapping {#folder-api-field-mapping} The following fields have changed across all folder endpoints: | Web Modeler API v1 | Camunda Hub API v2 | Application | Notes | | ------------------ | ------------------ | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `parentId` | `parentFolderKey` | Request/response | Renamed. If the folder is at the root of a project (["process application" before Camunda 8.10](#structure-and-terminology)), v1 endpoints return the project ID, and v2 endpoints return `null`. | | `projectId` | `projectKey` | Request/response | In v1, `projectId` refers to the ID of the workspace ([called "project" before Camunda 8.10](#structure-and-terminology)). In v2, the workspace key is no longer available. Instead, `projectKey` refers to the key of the project ([called "process application" before Camunda 8.10](#structure-and-terminology)). | | `id` | `folderKey` | Response | Renamed | ### Get a folder In Web Modeler API v1, folder data in the response is nested under a `metadata` key: ```json title="Web Modeler API v1" { "metadata": { "id": "b06c97f5-7e39-4108-b947-2848fdc023f0", "name": "Parent folder", "projectId": "b9b57035-fbce-4412-a7d5-9f0df61ed74d", "parentId": "1abf0198-3462-4fd2-a0e9-362f213d81d0", "created": "2026-07-08T09:59:30.719344Z", "updated": "2026-07-08T10:04:56.47393Z", "createdBy": { "name": "...", "email": "..." }, "updatedBy": { "name": "...", "email": "..." } }, "content": { "folders": [], "files": [] } } ``` In Camunda Hub API v2, folder data is nested under a `folder` key: ```json title="Camunda Hub API v2" { "folder": { "folderKey": "b06c97f5-7e39-4108-b947-2848fdc023f0", "name": "Parent folder", "projectKey": "1abf0198-3462-4fd2-a0e9-362f213d81d0", "parentFolderKey": null, "created": "2026-07-08T09:59:30.719344Z", "createdBy": { "name": "...", "email": "..." }, "updated": "2026-07-08T10:04:56.47393Z", "updatedBy": { "name": "...", "email": "..." } }, "content": { "folders": [], "files": [] } } ``` ## Workspace API In Web Modeler API v1, you manage workspaces ([called "projects" before Camunda 8.10](#structure-and-terminology)) with the `projects` resource. In Camunda Hub API v2, you manage them with the `workspaces` resource. The UUID values are the same. The concept, field names, and API paths have changed. ### Endpoint mapping All workspace API endpoints have a Camunda Hub API v2 equivalent: | Operation | Web Modeler API v1 | Camunda Hub API v2 | | ------------------ | --------------------------------- | -------------------------------------- | | Create a workspace | `POST /v1/projects` | `POST /v2/workspaces` | | Get a workspace | `GET /v1/projects/{projectId}` | `GET /v2/workspaces/{workspaceKey}` | | Update a workspace | `PATCH /v1/projects/{projectId}` | `PATCH /v2/workspaces/{workspaceKey}` | | Delete a workspace | `DELETE /v1/projects/{projectId}` | `DELETE /v2/workspaces/{workspaceKey}` | | Search workspaces | `POST /v1/projects/search` | `POST /v2/workspaces/search` | ### Field mapping {#workspace-api-field-mapping} The following fields have changed across all workspace endpoints: | Web Modeler API v1 | Camunda Hub API v2 | Application | Notes | | ------------------ | ------------------ | ----------- | -------- | | `id` | `workspaceKey` | Response | Renamed. | ### Get a workspace In Web Modeler API v1, workspace data in the response is nested under a `metadata` key with `folders` and `files` in the `content`: ```json title="Web Modeler API v1" { "metadata": { "id": "b9b57035-fbce-4412-a7d5-9f0df61ed74d", "name": "Orders", "created": "2026-07-08T07:58:57.601559Z", "createdBy": { "name": "...", "email": "..." }, "updated": "2026-07-08T07:59:13.386407Z", "updatedBy": { "name": "...", "email": "..." } }, "content": { "folders": [ ... ], "files": [ ... ] } } ``` In Camunda Hub API v2, workspace data is nested under a `workspace` key with `projects` in the `content`: ```json title="Camunda Hub API v2" { "workspace": { "workspaceKey": "b9b57035-fbce-4412-a7d5-9f0df61ed74d", "name": "Orders", "description": null, "created": "2026-07-08T07:58:57.601559Z", "createdBy": { "name": "...", "email": "..." }, "updated": "2026-07-08T07:59:13.386407Z", "updatedBy": { "name": "...", "email": "..." } }, "content": { "projects": [ ... ] } } ``` ### Search workspaces In addition to the [general field changes](#workspace-api-field-mapping), the following request fields have changed: | Web Modeler API v1 | Camunda Hub API v2 | Notes | | ------------------------ | ------------------ | ----------------------------------------------------------------------------------------------- | | `filter` | `filter` | Now uses [advanced operators](#search-filters), including `$eq`, `$in`, and `$like` | | `filter.id` | - | Removed | | `filter.description` | - | Removed | | `filter.createdBy.email` | `filter.createdBy` | In v1, `createdBy` is an object. In v2, it's a string representing the creator's email address. | | `filter.updatedBy.email` | `filter.updatedBy` | In v1, `updatedBy` is an object. In v2, it's a string representing the updater's email address. | | `sort.direction` | `sort.order` | Renamed | The following example shows a v1 request: ```json title="Web Modeler API v1" { "filter": { "createdBy": { "email": "jane.doe@email.com" } }, "sort": { "field": "name", "direction": "DESC" } } ``` The equivalent v2 request: ```json title="Camunda Hub API v2" { "filter": { "createdBy": "jane.doe@email.com" }, "sort": { "field": "name", "order": "DESC" } } ``` ## Member API In Camunda Hub API v2, the collaborators API has been renamed to `members`. The following sections cover changes that apply to the member API endpoints. ### Endpoint mapping All member API endpoints have a Camunda Hub API v2 equivalent: | Operation | Web Modeler API v1 | Camunda Hub API v2 | | --------------------- | ------------------------------------------------------- | ------------------------------------------------------ | | Add a collaborator | `PUT /v1/collaborators` | `POST /v2/workspaces/{workspaceKey}/members` | | Remove a collaborator | `DELETE /v1/projects/{projectId}/collaborators/{email}` | `DELETE /v2/workspaces/{workspaceKey}/members/{email}` | | Search collaborators | `POST /v1/collaborators/search` | `POST /v2/members/search` | ### Field mapping {#member-api-field-mapping} The following fields have changed across all member endpoints: | Web Modeler API v1 | Camunda Hub API v2 | Application | Notes | | ------------------ | ------------------ | ---------------- | -------- | | `projectId` | `workspaceKey` | Request/response | Renamed. | ### Add a member In Web Modeler API v1, the method is `PUT`, and the `projectId` is in the request body: ```bash title="Web Modeler API v1" PUT /api/v1/collaborators { "email": "jane.doe@email.com", "projectId": "b9b57035-fbce-4412-a7d5-9f0df61ed74d", "role": "viewer" } ``` In Camunda Hub API v2, the method is `POST`, and the workspace key is in the path: ```bash title="Camunda Hub API v2" POST /api/v2/workspaces/b9b57035-fbce-4412-a7d5-9f0df61ed74d/members { "email": "jane.doe@email.com", "role": "viewer" } ``` ### Search members In addition to the [general field changes](#member-api-field-mapping), the following request fields have changed: | Web Modeler API v1 | Camunda Hub API v2 | Notes | | ------------------ | --------------------- | ----------------------------------------------------------------------------------------------- | | `filter` | `filter` | Now uses [advanced operators](#search-filters), including `$eq`, `$in`, and `$like` | | `filter.projectId` | `filter.workspaceKey` | Renamed. In v2, `filter.workspaceKey` is required. Members can't be searched across workspaces. | | `sort.direction` | `sort.order` | Renamed | The following example shows a v1 request: ```json title="Web Modeler API v1" { "filter": { "projectId": "b9b57035-fbce-4412-a7d5-9f0df61ed74d", "role": "project_admin" }, "sort": { "field": "name", "direction": "DESC" } } ``` The equivalent v2 request: ```json title="Camunda Hub API v2" { "filter": { "workspaceKey": { "$eq": "b9b57035-fbce-4412-a7d5-9f0df61ed74d" }, "role": { "$eq": "workspace_admin" } }, "sort": { "field": "name", "order": "DESC" } } ``` ### Role enum | Web Modeler API v1 | Camunda Hub API v2 | Notes | | ------------------ | ------------------ | -------------------------------------- | | `project_admin` | `workspace_admin` | Renamed to match workspace terminology | | `editor` | `editor` | Unchanged | | `commenter` | `commenter` | Unchanged | | `viewer` | `viewer` | Unchanged | ## Version API The following sections cover changes that apply to version API endpoints. ### Endpoint mapping All version API endpoints have a Camunda Hub API v2 equivalent, except the compare versions endpoint: | Operation | Web Modeler API v1 | Camunda Hub API v2 | | -------------------- | ------------------------------------------------------ | -------------------------------------------- | | Create a version | `POST /v1/versions` | `POST /v2/versions` | | Get a version | `GET /v1/versions/{versionId}` | `GET /v2/versions/{versionKey}` | | Update a version | `PATCH /v1/versions/{versionId}` | `PATCH /v2/versions/{versionKey}` | | Delete a version | `DELETE /v1/versions/{versionId}` | `DELETE /v2/versions/{versionKey}` | | Search versions | `POST /v1/versions/search` | `POST /v2/versions/search` | | Restore a version | `POST /v1/versions/{versionId}/restore` | `POST /v2/versions/{versionKey}/restoration` | | Compare two versions | `GET /v1/versions/compare/{version1Id}...{version2Id}` | [Does not exist](#compare-two-versions) | ### Field mapping {#version-api-field-mapping} The following fields have changed across all version endpoints: | Web Modeler API v1 | Camunda Hub API v2 | Application | Notes | | ------------------ | ------------------ | ---------------- | ------- | | `fileId` | `fileKey` | Request/response | Renamed | | `id`/`versionId` | `versionKey` | Response | Renamed | ### Get a version In Web Modeler API v1, version data in the response is nested under a `metadata` key: ```json title="Web Modeler API v1" { "metadata": { "id": "c3e3a091-513e-4911-94d0-32aca88c80b9", "name": "V2", "description": "...", "fileId": "0ade583b-4022-47b5-8982-93ddd849ee6b", "created": "2026-06-09T16:50:37.186742Z", "createdBy": { "name": "...", "email": "..." }, "updated": "2026-06-09T16:50:58.356172Z", "updatedBy": { "name": "...", "email": "..." }, "organizationPublic": false }, "content": "..." } ``` In Camunda Hub API v2, version data is at the top level of the response body: ```json title="Camunda Hub API v2" { "versionKey": "c3e3a091-513e-4911-94d0-32aca88c80b9", "name": "V2", "description": "...", "fileKey": "0ade583b-4022-47b5-8982-93ddd849ee6b", "organizationPublic": false, "created": "2026-06-09T16:50:37.186742Z", "createdBy": { "name": "...", "email": "..." }, "updated": "2026-06-09T16:50:58.356172Z", "updatedBy": { "name": "...", "email": "..." }, "content": "..." } ``` ### Search versions In addition to the [general field changes](#version-api-field-mapping), the following request fields have changed: | Web Modeler API v1 | Camunda Hub API v2 | Notes | | ------------------ | ------------------ | -------------------------------------------------------------------------------------- | | `filter` | `filter` | Now uses [advanced operators](#search-filters), including `$eq`, `$in`, and `$like` | | `filter.fileId` | `filter.fileKey` | Renamed. In v2, `filter.fileKey` is required. Versions can't be searched across files. | | `sort.direction` | `sort.order` | Renamed | The following example shows a v1 request: ```json title="Web Modeler API v1" { "filter": { "fileId": "0ade583b-4022-47b5-8982-93ddd849ee6b" }, "sort": { "field": "name", "direction": "DESC" } } ``` The equivalent v2 request: ```json title="Camunda Hub API v2" { "filter": { "fileKey": { "$eq": "0ade583b-4022-47b5-8982-93ddd849ee6b" } }, "sort": { "field": "name", "order": "DESC" } } ``` ### Restore a version In Web Modeler API v1, the endpoint path uses `/restore`, and you pass the `versionId` in both the path and the request body: ```bash title="Web Modeler API v1" POST /api/v1/versions/{versionId}/restore { "versionId": {versionId} } ``` In Camunda Hub API v2, the endpoint path uses `/restoration`, and you identify the version using the path parameter: ```bash title="Camunda Hub API v2" POST /api/v2/versions/{versionKey}/restoration (no body) ``` For element template files, include a `version` integer in the request body to set the target version number in the restored content: ```bash title="Camunda Hub API v2" POST /api/v2/versions/{versionKey}/restoration { "version": 2 } ``` ### Compare two versions The compare versions endpoint `GET /versions/compare/{version1Id}...{version2Id}` no longer exists in Camunda Hub API v2. In Web Modeler API v1, the compare versions endpoint returns a link to a visual comparison between two versions, with `version1Id` as the baseline and `version2Id` as the version being compared. Instead of making an API request for this link, you can construct it yourself: 1. Get the file and version keys from the search or get version API. 2. Insert the keys into one of the following URL patterns, and open the URL in your browser: | Resource type | Template URL | | :--------------- | :------------------------------------------------------------------------------- | | BPMN | `{baseURL}/diagrams/{fileKey}/versions/{versionKey1}...{versionKey2}` | | Element template | `{baseURL}/connector-templates/{fileKey}/versions/{versionKey1}...{versionKey2}` | | Form | `{baseURL}/forms/{fileKey}/versions/{versionKey1}...{versionKey2}` | | RPA | `{baseURL}/rpa-scripts/{fileKey}/versions/{versionKey1}...{versionKey2}` | Replace `{baseURL}` with the Camunda Hub base URL. The version keys must be for the same file. For example: ```bash https://hub.camunda.io/diagrams/98634f96-52e9-4c00-8702-893a12803771/versions/2b6fd548-e107-4338-ae59-37a609f65202...78e0f8c5-0462-4521-bff9-5f432d689925 ``` ## Info API ### Endpoint mapping The info API endpoint has a Camunda Hub API v2 equivalent: | Operation | Web Modeler API v1 | Camunda Hub API v2 | | --------- | ------------------ | ------------------ | | Get info | `GET /v1/info` | `GET /v2/info` | ### Get info The following response fields have changed: | Web Modeler API v1 | Camunda Hub API v2 | Notes | | -------------------------- | -------------------------- | --------- | | `version` (returns `"v1"`) | `version` (returns `"v2"`) | New value | | `createPermission` | - | Removed | | `readPermission` | - | Removed | | `updatePermission` | - | Removed | | `deletePermission` | - | Removed | To determine your permissions, check the scopes you configured when creating your API token. If a request lacks the required permission, the API returns `403 Forbidden` with a `ProblemDetail` body explaining which permission is missing. --- ## Camunda 8.10 APIs & Tools migration guide ## About This guide details the API and SDK changes introduced in Camunda 8.10 that require customer action, including breaking changes, deprecations, and step-by-step migration actions. Details are provided for each integration type, including what changed, why, and what action you must take. | Integration type | Description | | :--------------------- | :-------------------------------------------------------- | | Official SDK users | Java client, TypeScript SDK, Python SDK, and C# SDK. | | Generated-client users | Clients generated from the Camunda OpenAPI specification. | | Custom integrations | Custom code that calls the Camunda REST API directly. | ## Upgrade steps Complete the following steps in this guide: 1. Upgrade to the latest official Camunda SDK versions. 1. If you generate clients from OpenAPI, regenerate them from the 8.10 specification. 1. Re-run compilation/type checks and address any errors. 1. Review and apply fixes for the breaking changes, deprecations, and supported environment changes below. ### API and SDK changes to migrate before Camunda 8.10 If you did not already migrate to the following APIs and SDKs during your 8.8 or 8.9 upgrade, Camunda recommends you perform these migrations before you upgrade to 8.10. If you already performed these migrations, proceed to [Camunda 8.10 breaking changes, deprecations, and supported environment changes](#camunda-810-breaking-changes-deprecations-and-supported-environment-changes). | 8.9 status | Component/Use | Migrate to | Migrate by | | :---------------------------------------------------------- | :---------------------------------------------------------------------------------- | :-------------------------- | :------------------ | | Deprecated | [V1 component APIs](../migration-manuals/migrate-to-camunda-api.md) | Orchestration Cluster API | Before Camunda 8.10 | | Deprecated | [ZeebeClient](../migration-manuals/migrate-to-camunda-java-client.md) | Camunda Java Client | Before Camunda 8.10 | | Deprecated | [Spring Zeebe SDK](../migration-manuals/migrate-to-camunda-spring-boot-starter.md) | Camunda Spring Boot Starter | Before Camunda 8.10 | | Deprecated | [Zeebe Process Test (ZPT)](../migration-manuals/migrate-to-camunda-process-test.md) | Camunda Process Test (CPT) | Before Camunda 8.10 | | Deprecated | [Job-based user tasks](../migration-manuals/migrate-to-camunda-user-tasks.md) | Camunda user tasks | Before Camunda 8.10 | :::tip Learn more about API changes in the blog post [Upcoming API Changes in Camunda 8: A Unified and Streamlined Experience](https://camunda.com/blog/2024/12/api-changes-in-camunda-8-a-unified-and-streamlined-experience/). ::: ### Camunda 8.10 breaking changes, deprecations, and supported environment changes Review the actions required for the following 8.10 changes: | Type | Change | | :-------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | | Breaking change | [Search filters: `UserTaskFilter` process filters converted into advanced search filters](#usertask-process-filter) | | Breaking change | [`POST /v2/message-subscriptions/search` returns start event subscriptions](#message-subscription-type) | | Breaking change | [Administration API (Self-Managed) migrated](#administration-api-self-managed-migrated) | | Behavioral change | [Element instance search: advanced filters on `elementId` / `elementName` and `$or` support](#element-instance-advanced-or) | | Behavioral change | [Resource API now uses eventual consistency](#resource-eventual-consistency) | | Behavioral change | [Deleting a process definition with running instances defers history deletion](#delete-draining) | | Deprecated | [Deprecated: GET resource content API](#deprecated-get-resource-content) | ## Breaking changes Review actions required for the following breaking changes: ### Search filters: `UserTaskFilter` process filters converted into advanced search filters {#usertask-process-filter} #### Change The search filter criteria for `processDefinitionKey`, `processInstanceKey`, and `bpmnProcessId` in `UserTaskFilter` have been converted into advanced search filters. #### Why As a result of the V1 API removal, advanced process filtering for user tasks was no longer supported. These changes let you use advanced process filters with the V2 User Tasks API again. #### Impact This affects the Java client because `io.camunda.client.api.search.filter.UserTaskFilter` now accepts advanced filters for `processDefinitionKey`, `processInstanceKey`, and `bpmnProcessId`. #### Action Update to the latest SDK version. The new SDK version includes advanced filters for `processDefinitionKey`, `processInstanceKey`, and `bpmnProcessId` in `UserTaskFilter`. Regenerate your client. No change is needed if your code already uses the exact-match filters for `processDefinitionKey`, `processInstanceKey`, and `bpmnProcessId` in `UserTaskFilter`. ### `POST /v2/message-subscriptions/search` returns start event subscriptions {#message-subscription-type} #### Change The `POST /v2/message-subscriptions/search` endpoint now returns both start event and intermediate event message subscriptions. Previously, only intermediate event subscriptions were returned. #### Why This change provides complete visibility into all active message subscriptions for a process, including start event subscriptions that were previously excluded. #### New field Each result includes a new `messageSubscriptionType` enum field: | Value | Description | | :-------------- | :------------------------------------------------ | | `START_EVENT` | A start event message subscription. | | `PROCESS_EVENT` | An intermediate catch event message subscription. | In existing legacy data, this field is `NULL`. #### Impact Integrations that consume results from `POST /v2/message-subscriptions/search` will now receive start event subscriptions in addition to intermediate event subscriptions. Code that assumes only intermediate events may produce unexpected behavior. #### Action Update to the latest SDK version. If your code relies on the endpoint returning only intermediate event subscriptions, add a filter to exclude start events when constructing your search query. Regenerate your client from the 8.10 OpenAPI specification to include the new `messageSubscriptionType` field. If your code expects only intermediate event subscriptions, add the filter shown in the **Custom integrations** tab to your request payload. If your code relies on the endpoint returning only intermediate event subscriptions, add the following filter to restore the previous behavior: ```json title="Before (no filter needed — endpoint returned only intermediate events)" { "filter": {} } ``` ```json title="After (filter required to exclude start events)" { "filter": { "messageSubscriptionType": { "$neq": "START_EVENT" } } } ``` This filter works correctly for both new data and legacy data (which has `NULL` in the `messageSubscriptionType` field). ### Administration API (Self-Managed) migrated The Administration API endpoints for Self-Managed have been migrated to the now-deprecated [Web Modeler API v1](../web-modeler-api/index.md): | Admin API (Self-Managed) | Web Modeler API v1 | | :----------------------------- | :----------------------------------- | | `GET /admin-api/usage-metrics` | `GET /api/v1/clusters/usage-metrics` | | `GET /admin-api/clusters` | `GET /api/v1/clusters` | For both endpoints, you need a [token with read permissions](../web-modeler-api/authentication.md#generate-a-token). These endpoints return the same data as the original Administration APIs, but the response format matches the other Web Modeler APIs. ## Behavioral changes ### Element instance search: advanced filters on `elementId` / `elementName` and `$or` support {#element-instance-advanced-or} #### Change The element instance search endpoint (`POST /v2/element-instances/search`) gained two filtering capabilities: - The `elementId` and `elementName` filter fields now accept [advanced search filter objects](../orchestration-cluster-api-rest/orchestration-cluster-api-rest-data-fetching.md#advanced-search-filters) in addition to plain string equality. Supported operators: `$eq`, `$neq`, `$exists`, `$in`, `$notIn`, `$like` (wildcard pattern with `*` and `?`). - The request body's `filter` object now accepts a top-level `$or` property that takes an array of alternative filter groups combined with OR logic. Top-level filter fields and `$or` are combined with AND logic. #### Why These additions let you express the queries the user interface (UI) needs (for example, "match any element whose name or ID contains a substring") in a single request, avoiding multiple round trips and client-side merging. #### Impact The change is additive and backward compatible — existing exact-match requests continue to work unchanged. New requests can now use advanced operators and `$or` to express richer queries: ```json { "filter": { "processInstanceKey": "2251799813685323", "$or": [ { "elementName": { "$like": "*Order*" } }, { "elementId": { "$like": "*Order*" } } ] } } ``` The example matches element instances where `processInstanceKey` equals the given value AND either `elementName` or `elementId` contains the substring `Order`. :::note Complex `$or` conditions may impact performance in high-volume environments; use them with care. ::: The `elementName` filter only matches instances created in 8.8 or later, since earlier runtimes did not persist this field on element instances. #### Action Update to the latest SDK version. The new SDK exposes advanced filters for `elementId` and `elementName`, and the `$or` filter on `ElementInstanceFilter`. Regenerate your client from the 8.10 OpenAPI specification to pick up the advanced filter and `$or` types on `ElementInstanceFilter`. No change is needed for existing requests. To use the new operators, send advanced filter objects on `elementId` / `elementName`, or a top-level `$or` array, as shown above. ### Resource API now uses eventual consistency {#resource-eventual-consistency} The [Get resource] and [Get resource content] APIs now retrieve from secondary storage, resulting in eventual consistency. After a resource is deployed, there may be a brief delay before it becomes retrievable via these endpoints. If your application assumes immediate resource retrieval after deployment, add retry logic or a short delay before querying resources. ### Deleting a process definition with running instances defers history deletion {#delete-draining} The [delete resource](/apis-tools/orchestration-cluster-api-rest/specifications/delete-resource.api.mdx) endpoint now accepts process definition deletion when the definition still has running instances. Instead of rejecting the request or waiting for physical removal, the definition [drains](/components/concepts/resource-deletion.md#draining): new instances are blocked immediately, running instances continue to completion, and the definition is removed automatically afterwards. As a result, when `deleteHistory` is `true`, the `batchOperation` field in the response is `null` for such a definition. Its history is removed as part of the draining lifecycle rather than through an immediately-returned batch operation. The field is still populated for decision requirements definitions and for process definitions that are already fully deleted from the runtime state. If you read `batchOperation` from the delete response to track history deletion, handle a `null` value: the definition is draining. Track progress through the process definition `state` (`DRAINING`) or the `zeebe_process_definitions_draining_count` metric instead. ## Deprecations Review the actions required for the following deprecations: ### Deprecated: GET resource content API {#deprecated-get-resource-content} The [Get resource content] endpoint is deprecated. Use [Get resource content binary] instead, which provides the same functionality and also returns generic resources. ## Next steps Once you have completed the [upgrade steps](#upgrade-steps) in this guide, you should: 1. Re-compile and run your test suite against the 8.10 API. [Get resource]: ../orchestration-cluster-api-rest/specifications/get-resource.api.mdx [Get resource content]: ../orchestration-cluster-api-rest/specifications/get-resource-content.api.mdx [Get resource content binary]: ../orchestration-cluster-api-rest/specifications/get-resource-content-binary.api.mdx --- ## Migrate to the Orchestration Cluster API :::note Have you already migrated? You do not need to perform this migration again if you already did this when upgrading to version 8.8. This guide is retained to help customers migrate before upgrading from 8.9 to 8.10. See [API and SDK changes to migrate before Camunda 8.10](../migration-manuals/migrate-to-810.md#api-and-sdk-changes-to-migrate-before-camunda-810). ::: ## About This guide covers how to migrate to the V2 [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) before upgrading to Camunda 8.10, where the V1 component REST APIs are removed. It covers all public endpoints in the component REST APIs and their Orchestration Cluster API counterparts or required migration changes. - Camunda is streamlining the developer experience by creating a unified REST API for Zeebe, Operate, Tasklist, and the Identity components with endpoint parity. This is the single Orchestration Cluster REST API. - Individual component APIs (starting with the former Operate and Tasklist APIs) were deprecated before their removal in 8.10. Use this guide to complete the migration before upgrading. :::info To learn more about the unified REST API, see [the official blog announcement](https://camunda.com/blog/2024/12/api-changes-in-camunda-8-a-unified-and-streamlined-experience/). ::: :::note The Administration and Web Modeler APIs are not part of the Orchestration Cluster REST API, as these are platform APIs outside the cluster’s scope. ::: ## Migration steps To successfully migrate to the V2 Orchestration Cluster API, perform the following steps: 1. **Identify your current V1 endpoints**: Audit your application to catalog all V1 API calls currently in use. 1. **Map V1 endpoints to V2 equivalents**: Use the tables in this guide to find the corresponding V2 endpoints for each request. 1. **Update request and response structure**: Adapt your code to handle the new formats, renamed attributes, and data type changes as outlined in this guide. 1. **Update pagination logic**: Replace old pagination parameters with the new `page` object structure and cursor-based navigation. ## General endpoint changes - The new API can be found at `/v2/…>` instead of `/v1/…>`. - All endpoints are no longer separated by component concerns and all endpoints receive similar support. For example, process definitions, user tasks, and user authorizations were previously spread across separate Tasklist, Operate, and Identity APIs. - All endpoints support the [authorization-based access control model](../../components/concepts/access-control/authorizations.md). Component endpoints only support the configuration of full (wildcard) access or no access. - Naming, response codes, and type handling have been streamlined for all endpoints to provide a consistent UX. - Endpoints with similar concerns (variable search, for example) have been consolidated into single endpoints. - The request and response payload of every new endpoint might contain new attributes that are not necessarily needed for a migration from a V1 endpoint to V2 but might still be useful. Please consult the V2 API guides for access to all new attributes. - Unified search request structure. - Attributes `filter`, `page`, and `sort` on root level. - Endpoint-specific filter attributes in the filter object, not at the root level. - Pagination information in the `page` object. For example, the attributes `from`, `limit`, `before`, and `after`. - Sorting configuration in sort object array, each object containing the field name and order (descending or ascending). - Unified search response structure. - Attributes `items` and `page` on root level. - List of endpoint-specific response items in `items` attribute. - Page information in `page` attribute, for example the attributes `totalItems`, `startCursor`, and `endCursor` to use in `before` and `after` in follow-up requests. ## Name changes and mappings The following table shows key attribute name changes from V1 to V2: | **V1** | **V2** | **Notes** | | ---------------- | ----------------------- | ----------------------------------------------------------------------------------- | | `id` | `[entity]Id` | Keys now include entity prefix (for example, `userTaskKey`, `processDefinitionId`). | | `key` | `[entity]Key` | Converted from `int64` to `string` with entity prefix. | | `bpmnProcessId` | `processDefinitionId` | Unified naming convention. | | `processName` | `processDefinitionId` | Unified naming convention. | | `decisionKey` | `decisionDefinitionKey` | Unified naming convention. | | `dmnDecisionKey` | `decisionDefinitionKey` | Unified naming convention. | | `decisionId` | `decisionDefinitionId` | Unified naming convention. | | `dmnDecisionId` | `decisionDefinitionId` | Unified naming convention. | **General naming conventions:** - Keys and IDs contain the full entity name as prefix to avoid confusion (for example, `processDefinitionKey` instead of `processKey`). - Entity attributes have no prefix within their own entity, but use prefixes when referenced from other entities. - All key fields are now `string` type instead of `int64`. ## Tasklist REST API ### Form #### Get a form V1 V2 GET `/v1/forms/{formId}` GET [`/v2/user-tasks/{userTaskKey}/form`](../orchestration-cluster-api-rest/specifications/get-user-task-form.api.mdx) GET [`/v2/process-definitions/{processDefinitionKey}/form`](../orchestration-cluster-api-rest/specifications/get-start-process-form.api.mdx) - You cannot fetch forms directly anymore. Instead, fetch them by user task or process definition to get the respective form data. - The respective endpoint only takes the key of the resource the form is related to as input parameter. Embedded forms are no longer returned as Camunda user tasks don't support them. | **Field** | **Change Type** | **Notes** | | ---------------------- | --------------- | ----------------------------------------------------------- | | `id` | Renamed | Now `formKey` (unique system identifier of the form). | | `title` | Renamed | Now `formId` (aligns with form schema attribute). | | `isDeleted` | Removed | No longer provided by endpoint. | | `processDefinitionKey` | Removed | Can be identified from endpoint resource and key parameter. | ### Task #### Save task draft variables V1 V2 POST `/v1/tasks/{taskId}/variables` This feature is not supported in V2 anymore. Use [setting variables][] as `local` to the user task's `elementInstanceKey` as a replacement. #### Search task variables V1 V2 POST `/v1/tasks/{taskId}/variables/search` POST [`/v2/user-tasks/{userTaskKey}/variables/search`](../orchestration-cluster-api-rest/specifications/search-user-task-variables.api.mdx) Request structure changes as outlined in [general changes][]. | **Field** | **Change Type** | **Notes** | | ------------------ | --------------- | ---------------------------------------------------------------------------- | | `variableNames` | Renamed | Now `name` in `filter` object (plain string or `{ "$in": [ "xyz", ... ] }`). | | `includeVariables` | Removed | Endpoint returns all variables associated with the user task. | Response structure changes as outlined in [general changes][]. | **Field** | **Change Type** | **Notes** | | ------------------ | --------------- | ------------------------------------------------------------------------ | | `id` | Renamed | Now `variableKey` (unique system identifier of the variable). | | `previewValue` | Renamed | Now `value` (always represents variable value, may be truncated). | | `isValueTruncated` | Renamed | Now `isTruncated` (see get variable endpoint for full value if needed). | | `draft` | Removed | Draft variables not supported in V2 (see save draft variables endpoint). | For completed tasks, the V1 API returned snapshot variable values as they existed at completion time. The V2 API always returns the current runtime value of variables. #### Search tasks V1 V2 POST `/v1/tasks/search` POST [`/v2/user-tasks/search`](../orchestration-cluster-api-rest/specifications/search-user-tasks.api.mdx) Request structure changes as outlined in [general changes][]. | **Field** | **Change Type** | **Notes** | | ------------------------- | --------------- | --------------------------------------------------------------- | | `pageSize` | Renamed | Now `limit` in the `page` object. | | `searchAfter` | Renamed | Now `after` in the `page` object. | | `searchBefore` | Renamed | Now `before` in the `page` object. | | `taskDefinitionId` | Renamed | Now `elementId` (user-provided identifier of the BPMN element). | | `assigned` | Renamed | Now `assignee` with `{ "$exists": false }`. | | `assignees` | Renamed | Now `assignee` with `{ "$in": [ "xyz", ... ] }`. | | `candidateGroups` | Renamed | Now `candidateGroup` with `{ "$in": [ "xyz", ... ] }`. | | `candidateUsers` | Renamed | Now `candidateUser` with `{ "$in": [ "xyz", ... ] }`. | | `tenantIds` | Renamed | Now `tenantId` with `{ "$in": [ "xyz", ... ] }`. | | `followUpDate`, `dueDate` | Changed | Use `$gte` and `$lte` instead of `from` and `to`. | | `priority` | Changed | Filter keys need `$` prefix, supports new comparison options. | | `taskVariables` | Split | Now `localVariables` and `processInstanceVariables`. | | `searchAfterOrEqual` | Removed | No longer supported. | | `searchBeforeOrEqual` | Removed | No longer supported. | | `includeVariables` | Removed | Use separate search task variables endpoint. | | `implementation` | Removed | V2 API supports only Camunda user tasks. | Response structure changes as outlined in [general changes][]. | **Field** | **Change Type** | **Notes** | | ------------------ | --------------- | ------------------------------------------------------------------------------------- | | `sortValues` | Removed | No longer exist per result item - use `startCursor` and `endCursor` in `page` object. | | `id` | Renamed | Now `userTaskKey` (unique system identifier of the user task). | | `taskDefinitionId` | Renamed | Now `elementId` (user-provided identifier of the BPMN element). | | `taskState` | Renamed | Now `state` (user task's current state). | | `processName` | Renamed | Now `processDefinitionId` (user-provided identifier of the process). | | `formKey` | Changed | Now unique system identifier referencing linked Camunda form in specific version. | | `isFirst` | Removed | No longer identifies if task was first in process. | | `variables` | Removed | Use search user task variables endpoint. | | `implementation` | Removed | V2 API supports only Camunda user tasks. | | `isFormEmbedded` | Removed | V2 API does not support embedded forms. | | `formVersion` | Removed | Use get user task form endpoint. | | `formId` | Removed | Use get user task form endpoint. | #### Unassign a task V1 V2 PATCH `/v1/tasks/{taskId}/unassign` DELETE [`/v2/user-tasks/{userTaskKey}/assignee`](../orchestration-cluster-api-rest/specifications/unassign-user-task.api.mdx) - No input adjustments. - Response object removed - The V2 API returns a 204 status, indicating that the task was unassigned. Fetching the updated data of the user task should be done through the respective API since the data can change concurrently at any time. #### Complete a task V1 V2 PATCH `/v1/tasks/{taskId}/complete` POST [`/v2/user-tasks/{userTaskKey}/completion`](../orchestration-cluster-api-rest/specifications/complete-user-task.api.mdx) - Adjusted attributes - `variables` - Provide the variables as a proper JSON object instead of an array of objects with a `name` and a serialized JSON string `value`. - Response object removed - The V2 API returns a 204 status, indicating that the task was completed. Fetching the updated data of the user task should be done through the respective API since the data can change concurrently at any time. #### Assign a task V1 V2 PATCH `/v1/tasks/{taskId}/assign` POST [`/v2/user-tasks/{userTaskKey}/assignment`](../orchestration-cluster-api-rest/specifications/assign-user-task.api.mdx) - Renamed attributes - `allowOverrideAssignment` - Use `allowOverride`, this still refers to allowing to override any existing assignee. - Response object removed - The V2 API returns a 204 status, indicating that the task was assigned. Fetching the updated data of the user task should be done through the respective API since the data can change concurrently at any time. #### Get a task V1 V2 GET `/v1/tasks/{taskId}` GET [`/v2/user-tasks/{userTaskKey}`](../orchestration-cluster-api-rest/specifications/get-user-task.api.mdx) - No input adjustments. - Except for the response structure changes, all adjustments from [search tasks](#search-tasks) apply. ### Variables #### Get a variable V1 V2 GET `/v1/variables/{variableId}` GET [`/v2/variables/{variableKey}`](../orchestration-cluster-api-rest/specifications/get-variable.api.mdx) - `variableId` - Use `variableKey` as this refers to the unique system identifier of the variable. - Renamed attributes - `id` - Use `variableKey` as this refers to the unique system identifier of the variable. - Removed attributes - `draft` - Draft variables are not supported in V2 anymore, see also the [save draft variables](#save-task-draft-variables) endpoint for further details. ## Operate REST API ### Decision definition #### Search decision definitions V1 V2 POST `/v1/decision-definitions/search` POST [`/v2/decision-definitions/search`](../orchestration-cluster-api-rest/specifications/search-decision-definitions.api.mdx) Request structure changes as outlined in [general changes][]. | **Field** | **Change Type** | **Notes** | | ----------------------------- | --------------- | --------------------------------------------------------------- | | `searchAfter` | Renamed | Now `after` in the `page` object. | | `size` | Renamed | Now `limit` in the `page` object. | | `id` | Renamed | Now `decisionDefinitionKey` in filter object. | | `key` | Renamed | Now `decisionDefinitionKey` (changed from `int64` to `string`). | | `decisionId` | Renamed | Now `decisionDefinitionId` in filter object. | | `decisionRequirementsKey` | Changed | Now `string` type instead of `int64`. | | `decisionRequirementsName` | Removed | Can no longer be used for filtering. | | `decisionRequirementsVersion` | Removed | Can no longer be used for filtering. | Response structure changes as outlined in [general changes][]. | **Field** | **Change Type** | **Notes** | | ----------------------------- | --------------- | --------------------------------------------------------------- | | `total` | Moved | Now `totalItems` in `page` object. | | `sortValues` | Replaced | Now use `endCursor` in `page` object. | | `id` | Renamed | Now `decisionDefinitionKey`. | | `key` | Renamed | Now `decisionDefinitionKey` (changed from `int64` to `string`). | | `decisionId` | Renamed | Now `decisionDefinitionId`. | | `decisionRequirementsKey` | Changed | Now `string` type instead of `int64`. | | `decisionRequirementsName` | Removed | Fetch using get decision requirements endpoint. | | `decisionRequirementsVersion` | Removed | Fetch using get decision requirements endpoint. | #### Get decision definition by key V1 V2 GET `/v1/decision-definitions/{key}` GET [`/v2/decision-definitions/{decisionDefinitionKey}`](../orchestration-cluster-api-rest/specifications/get-decision-definition.api.mdx) - No input adjustments. - Except for the response structure changes, all adjustments from [search decision definitions](#search-decision-definitions) apply. #### Search decision instances V1 V2 POST `/v1/decision-instances/search` POST [`/v2/decision-instances/search`](../orchestration-cluster-api-rest/specifications/search-decision-instances.api.mdx) Request structure changes as outlined in [general changes][]. | **Field** | **Change Type** | **Notes** | | ---------------------- | --------------- | ------------------------------------------------------------- | | `searchAfter` | Renamed | Now `after` in the `page` object. | | `size` | Renamed | Now `limit` in the `page` object. | | `id` | Renamed | Now `decisionInstanceId` in filter object. | | `key` | Renamed | Now `decisionInstanceKey` (changed from `int64` to `string`). | | `processDefinitionKey` | Changed | Now `string` type instead of `int64`. | | `processInstanceKey` | Changed | Now `string` type instead of `int64`. | | `decisionId` | Renamed | Now `decisionDefinitionId`. | | `decisionName` | Renamed | Now `decisionDefinitionName`. | | `decisionVersion` | Renamed | Now `decisionDefinitionVersion`. | | `decisionType` | Renamed | Now `decisionDefinitionType`. | | `result` | Removed | Can no longer be used for filtering. | | `evaluatedInputs` | Removed | Can no longer be used for filtering. | | `evaluatedOutputs` | Removed | Can no longer be used for filtering. | Response structure changes as outlined in [general changes][]. | **Field** | **Change Type** | **Notes** | | ---------------------- | --------------- | ------------------------------------------------------------- | | `total` | Moved | Now `totalItems` in `page` object. | | `sortValues` | Replaced | Now use `endCursor` in `page` object. | | `id` | Renamed | Now `decisionInstanceId`. | | `key` | Renamed | Now `decisionInstanceKey` (changed from `int64` to `string`). | | `processDefinitionKey` | Changed | Now `string` type instead of `int64`. | | `processInstanceKey` | Changed | Now `string` type instead of `int64`. | | `decisionId` | Renamed | Now `decisionDefinitionId`. | | `decisionName` | Renamed | Now `decisionDefinitionName`. | | `decisionVersion` | Renamed | Now `decisionDefinitionVersion`. | | `decisionType` | Renamed | Now `decisionDefinitionType`. | | `evaluatedInputs` | Removed | No longer provided by endpoint. | | `evaluatedOutputs` | Removed | No longer provided by endpoint. | #### Get decision instance by id V1 V2 GET `/v1/decision-instances/{id}` GET [`/v2/decision-instances/{decisionInstanceId}`](../orchestration-cluster-api-rest/specifications/search-decision-instances.api.mdx) - No input adjustments. The adjustments from [search decision instances](#search-decision-instances) apply, with the following exceptions: `evaluatedInputs` and `evaluatedOutputs` are present in the response payload (with `evaluatedOutputs` moved under `matchedRules`). | **Field** | **Change Type** | **Notes** | | --------------------------- | --------------- | --------------------------------------- | | **evaluatedInputs object** | | | | `id` | Renamed | Now `inputId`. | | `name` | Renamed | Now `inputName`. | | `value` | Renamed | Now `inputValue`. | | **evaluatedOutputs object** | | | | `id` | Renamed | Now `outputId`. | | `name` | Renamed | Now `outputName`. | | `value` | Renamed | Now `outputValue`. | | `ruleId` | Moved | Now under `matchedRules` array objects. | | `ruleIndex` | Moved | Now under `matchedRules` array objects. | #### Search decision requirements V1 V2 POST `/v1/drd/search` POST [`/v2/decision-requirements/search`](../orchestration-cluster-api-rest/specifications/search-decision-requirements.api.mdx) Request structure changes as outlined in [general changes][]. | **Field** | **Change Type** | **Notes** | | ------------- | --------------- | ----------------------------------------------------------------- | | `searchAfter` | Renamed | Now `after` in the `page` object. | | `size` | Renamed | Now `limit` in the `page` object. | | `id` | Renamed | Now `decisionRequirementsKey` in filter object. | | `key` | Renamed | Now `decisionRequirementsKey` (changed from `int64` to `string`). | | `name` | Renamed | Now `decisionRequirementsName`. | Response structure changes as outlined in [general changes][]. | **Field** | **Change Type** | **Notes** | | ------------ | --------------- | ----------------------------------------------------------------- | | `total` | Moved | Now `totalItems` in `page` object. | | `sortValues` | Replaced | Now use `endCursor` in `page` object. | | `id` | Renamed | Now `decisionRequirementsKey`. | | `key` | Renamed | Now `decisionRequirementsKey` (changed from `int64` to `string`). | | `name` | Renamed | Now `decisionRequirementsName`. | #### Get decision requirements by key V1 V2 GET `/v1/drd/{key}` GET [`/v2/decision-requirements/{decisionRequirementsKey}`](../orchestration-cluster-api-rest/specifications/get-decision-requirements.api.mdx) - No input adjustments. - Except for the response structure changes, all adjustments from [search decision requirements](#search-decision-requirements) apply. #### Get decision requirements as XML by key V1 V2 GET `/v1/drd/{key}/xml` GET [`/v2/decision-requirements/{decisionRequirementsKey}/xml`](../orchestration-cluster-api-rest/specifications/get-decision-requirements-xml.api.mdx) - No input adjustments. - No output adjustments. ### Variable #### Search variables for process instances V1 V2 POST `/v1/variables/search` POST [`/v2/variables/search`](../orchestration-cluster-api-rest/specifications/search-variables.api.mdx) Request structure changes as outlined in [general changes][]. | **Field** | **Change Type** | **Notes** | | -------------------- | --------------- | ----------------------------------------------------- | | `searchAfter` | Renamed | Now `after` in the `page` object. | | `size` | Renamed | Now `limit` in the `page` object. | | `key` | Renamed | Now `variableKey` (changed from `int64` to `string`). | | `processInstanceKey` | Changed | Now `string` type instead of `int64`. | | `scopeKey` | Changed | Now `string` type instead of `int64`. | | `truncated` | Renamed | Now `isTruncated`. | Response structure changes as outlined in [general changes][]. | **Field** | **Change Type** | **Notes** | | -------------------- | --------------- | ----------------------------------------------------- | | `total` | Moved | Now `totalItems` in `page` object. | | `sortValues` | Replaced | Now use `endCursor` in `page` object. | | `key` | Renamed | Now `variableKey` (changed from `int64` to `string`). | | `processInstanceKey` | Changed | Now `string` type instead of `int64`. | | `scopeKey` | Changed | Now `string` type instead of `int64`. | | `truncated` | Renamed | Now `isTruncated`. | #### Get variable by key V1 V2 GET `/v1/variables/{key}` GET [`/v2/variables/{variableKey}`](../orchestration-cluster-api-rest/specifications/get-variable.api.mdx) - No input adjustments. - All adjustments from [search variables for process instances](#search-variables-for-process-instances) apply, with the following exceptions: - Response structure changes. - `truncated` is removed because this endpoint always returns the full variable value. [setting variables]: /apis-tools/orchestration-cluster-api-rest/specifications/create-element-instance-variables.api.mdx [general changes]: #general-endpoint-changes [multi-tenancy]: /components/concepts/multi-tenancy.md ### Process definition #### Search process definitions V1 V2 POST `/v1/process-definitions/search` POST [`/v2/process-definitions/search`](../orchestration-cluster-api-rest/specifications/search-process-definitions.api.mdx) Request structure changes as outlined in [general changes][]. | **Field** | **Change Type** | **Notes** | | --------------- | --------------- | -------------------------------------------------------------- | | `searchAfter` | Renamed | Now `after` in the `page` object. | | `size` | Renamed | Now `limit` in the `page` object. | | `key` | Renamed | Now `processDefinitionKey` (changed from `int64` to `string`). | | `bpmnProcessId` | Renamed | Now `processDefinitionId`. | Response structure changes as outlined in [general changes][]. | **Field** | **Change Type** | **Notes** | | --------------- | --------------- | -------------------------------------------------------------- | | `total` | Moved | Now `totalItems` in `page` object. | | `sortValues` | Replaced | Now use `endCursor` in `page` object. | | `key` | Renamed | Now `processDefinitionKey` (changed from `int64` to `string`). | | `bpmnProcessId` | Renamed | Now `processDefinitionId`. | #### Get process definition by key V1 V2 GET `/v1/process-definitions/{key}` GET [`/v2/process-definitions/{processDefinitionKey}`](../orchestration-cluster-api-rest/specifications/get-process-definition.api.mdx) - No input adjustments. - Except for the response structure changes, all adjustments from [search process definitions](#search-process-definitions) apply. #### Get process definition as XML by key V1 V2 GET `/v1/process-definitions/{key}/xml` GET [`/v2/process-definitions/{processDefinitionKey}/xml`](../orchestration-cluster-api-rest/specifications/get-process-definition-xml.api.mdx) - No input adjustments. - No output adjustments. ### Process instance #### Search process instances V1 V2 POST `/v1/process-instances/search` POST [`/v2/process-instances/search`](../orchestration-cluster-api-rest/specifications/search-process-instances.api.mdx) Request structure changes as outlined in [general changes][]. | **Field** | **Change Type** | **Notes** | | --------------------------- | --------------- | ------------------------------------------------------------ | | `searchAfter` | Renamed | Now `after` in the `page` object. | | `size` | Renamed | Now `limit` in the `page` object. | | `key` | Renamed | Now `processInstanceKey` (changed from `int64` to `string`). | | `processVersion` | Renamed | Now `processDefinitionVersion`. | | `processVersionTag` | Renamed | Now `processDefinitionVersionTag`. | | `bpmnProcessId` | Renamed | Now `processDefinitionId`. | | `parentFlowNodeInstanceKey` | Renamed | Now `parentElementInstanceKey` (changed to `string`). | | `parentKey` | Renamed | Now `parentProcessInstanceKey` (changed to `string`). | | `state` | Changed | Use `TERMINATED` instead of `CANCELED`. | | `incident` | Renamed | Now `hasIncident`. | | `parentProcessInstanceKey` | Changed | Now `string` type instead of `int64`. | | `processDefinitionKey` | Changed | Now `string` type instead of `int64`. | Response structure changes as outlined in [general changes][]. | **Field** | **Change Type** | **Notes** | | --------------------------- | --------------- | ------------------------------------------------------------ | | `total` | Moved | Now `totalItems` in `page` object. | | `sortValues` | Replaced | Now use `endCursor` in `page` object. | | `key` | Renamed | Now `processInstanceKey` (changed from `int64` to `string`). | | `processVersion` | Renamed | Now `processDefinitionVersion`. | | `processVersionTag` | Renamed | Now `processDefinitionVersionTag`. | | `bpmnProcessId` | Renamed | Now `processDefinitionId`. | | `parentFlowNodeInstanceKey` | Renamed | Now `parentElementInstanceKey` (changed to `string`). | | `parentKey` | Renamed | Now `parentProcessInstanceKey` (changed to `string`). | | `state` | Changed | Use `TERMINATED` instead of `CANCELED`. | | `incident` | Renamed | Now `hasIncident`. | | `parentProcessInstanceKey` | Changed | Now `string` type instead of `int64`. | | `processDefinitionKey` | Changed | Now `string` type instead of `int64`. | #### Get process instance by key V1 V2 GET `/v1/process-instances/{key}` GET [`/v2/process-instances/{processInstanceKey}`](../orchestration-cluster-api-rest/specifications/get-process-instance.api.mdx) - No input adjustments. - Except for the response structure changes, all adjustments from [search process instances](#search-process-instances) apply. #### Delete process instance and all dependant data by key V1 V2 DELETE `/v1/process-instances/{key}` This feature is not yet available in V2. It will be added in a future version. #### Get flow node statistic by process instance key V1 V2 GET `/v1/process-instances/{key}/statistics` GET [`/v2/process-instances/{processInstanceKey}/statistics/element-instances`](../orchestration-cluster-api-rest/specifications/get-process-instance-statistics.api.mdx) - No input adjustments. Response structure changes. | **Field** | **Change Type** | **Notes** | | -------------- | --------------- | ----------------------- | | Response items | Moved | Now under `items` array | | `activityId` | Renamed | Now `elementId` | #### Get sequence flows of process instance by key V1 V2 GET `/v1/process-instances/{key}/sequence-flows` GET [`/v2/process-instances/{processInstanceKey}/sequence-flows`](../orchestration-cluster-api-rest/specifications/get-process-instance-sequence-flows.api.mdx) - No input adjustments. Response structure changes. | **Field** | **Change Type** | **Notes** | | -------------- | --------------- | --------------------------------------------------------------------------------- | | Response items | Changed | Now type `object` instead of `string`. | | Response items | Moved | Now under `items` array. | | V1 recreation | Info | Collect `sequenceFlowId` of type `string` from all objects to recreate V1 result. | ### Flownode instances #### Search flownode instances V1 V2 POST `/v1/flownode-instances/search` POST [`/v2/element-instances/search`](../orchestration-cluster-api-rest/specifications/search-element-instances.api.mdx) Request structure changes as outlined in [general changes][]. | **Field** | **Change Type** | **Notes** | | ---------------------- | --------------- | ------------------------------------------------------------ | | `searchAfter` | Renamed | Now `after` in the `page` object. | | `size` | Renamed | Now `limit` in the `page` object. | | `key` | Renamed | Now `elementInstanceKey` (changed from `int64` to `string`). | | `flowNodeId` | Renamed | Now `elementId`. | | `flowNodeName` | Renamed | Now `elementName`. | | `incident` | Renamed | Now `hasIncident`. | | `processInstanceKey` | Changed | Now `string` type instead of `int64`. | | `processDefinitionKey` | Changed | Now `string` type instead of `int64`. | | `incidentKey` | Changed | Now `string` type instead of `int64`. | | `startDate` | Removed | Can no longer be used for filtering. | | `endDate` | Removed | Can no longer be used for filtering. | Response structure changes as outlined in [general changes][]. | **Field** | **Change Type** | **Notes** | | ---------------------- | --------------- | ------------------------------------------------------------ | | `total` | Moved | Now `totalItems` in `page` object. | | `sortValues` | Replaced | Now use `endCursor` in `page` object. | | `key` | Renamed | Now `elementInstanceKey` (changed from `int64` to `string`). | | `flowNodeId` | Renamed | Now `elementId`. | | `flowNodeName` | Renamed | Now `elementName`. | | `incident` | Renamed | Now `hasIncident`. | | `processInstanceKey` | Changed | Now `string` type instead of `int64`. | | `processDefinitionKey` | Changed | Now `string` type instead of `int64`. | | `incidentKey` | Changed | Now `string` type instead of `int64`. | #### Get flownode instance by key V1 V2 GET `/v1/flownode-instances/{key}` GET [`/v2/element-instances/{elementInstanceKey}`](../orchestration-cluster-api-rest/specifications/get-element-instance.api.mdx) - No input adjustments. - Except for the response structure changes, all adjustments from [search flownode instances](#search-flownode-instances) apply. ### Incidents #### Search incidents V1 V2 POST `/v1/incidents/search` POST [`/v2/incidents/search`](../orchestration-cluster-api-rest/specifications/search-incidents.api.mdx) Request structure changes as outlined in [general changes][]. | **Field** | **Change Type** | **Notes** | | ---------------------- | --------------- | ----------------------------------------------------- | | `searchAfter` | Renamed | Now `after` in the `page` object. | | `size` | Renamed | Now `limit` in the `page` object. | | `key` | Renamed | Now `incidentKey` (changed from `int64` to `string`). | | `type` | Renamed | Now `errorType`. | | `message` | Renamed | Now `errorMessage`. | | `processInstanceKey` | Changed | Now `string` type instead of `int64`. | | `processDefinitionKey` | Changed | Now `string` type instead of `int64`. | | `jobKey` | Changed | Now `string` type instead of `int64`. | Response structure changes as outlined in [general changes][]. | **Field** | **Change Type** | **Notes** | | ---------------------- | --------------- | ----------------------------------------------------- | | `total` | Moved | Now `totalItems` in `page` object. | | `sortValues` | Replaced | Now use `endCursor` in `page` object. | | `key` | Renamed | Now `incidentKey` (changed from `int64` to `string`). | | `type` | Renamed | Now `errorType`. | | `message` | Renamed | Now `errorMessage`. | | `processInstanceKey` | Changed | Now `string` type instead of `int64`. | | `processDefinitionKey` | Changed | Now `string` type instead of `int64`. | | `jobKey` | Changed | Now `string` type instead of `int64`. | #### Get incident by key V1 V2 GET `/v1/incidents/{key}` GET [`/v2/incidents/{incidentKey}`](../orchestration-cluster-api-rest/specifications/get-incident.api.mdx) - No input adjustments. - Except for the response structure changes, all adjustments from [search incidents](#search-incidents) apply. --- ## Migrate to the Camunda Java Client :::note Using Spring Boot? If you are migrating from the Spring Zeebe SDK, use [migrate to Camunda Spring Boot Starter](migrate-to-camunda-spring-boot-starter.md) instead. This guide covers migrations from the Zeebe Java Client to the Camunda Java Client. ::: :::note Have you already migrated? You do not need to perform this migration again if you already did this when upgrading to version 8.8. This guide is retained to help customers migrate before upgrading from 8.9 to 8.10. See [API and SDK changes to migrate before Camunda 8.10](../migration-manuals/migrate-to-810.md#api-and-sdk-changes-to-migrate-before-camunda-810). ::: ## About This guide provides an overview of the process for migrating to the Camunda Java Client. - The [Camunda Java Client](../java-client/getting-started.md) is the official Java library for connecting to Orchestration Cluster, automating processes, and implementing job workers. - The Zeebe Java Client remains available until Camunda 8.10. :::tip Plan and start your migration early to ensure compatibility, access to latest features, and future support. ::: ## Before you begin - Review project dependencies and identify where `io.camunda.zeebe:zeebe-client-java` is used. - Catalog code referencing Zeebe classes, interfaces, and APIs (for example, ZeebeClient, Zeebe workers). ## Update Maven/Gradle dependencies Replace the Zeebe Java Client dependency with the Camunda Java Client dependency in your `pom.xml` or `build.gradle` file. Maven: ```xml io.camunda camunda-client-java ${camunda.version} ``` Gradle: ```groovy implementation 'io.camunda:camunda-client-java:${camunda.version}' ``` ## Update imports Update all imports statement in your Java files to use the new Camunda Java Client package structure. Change from: ```java ``` to: ```java ``` ## Configuration and environment variable changes - All old Java client property names are refactored to more general ones. For example, `zeebe.client.tenantId` to `camunda.client.tenantId`. - Environment variables are also updated accordingly. For example, `ZEEBE_CLIENT_TENANT_ID` to `CAMUNDA_CLIENT_TENANT_ID`. - The former deprecated `gatewayAddress` property and `usePlainText` have been **removed and superseded by `restAddress` and `grpcAddress` which require explicit URI schemes** (for example, `http://` or `https://`). ## Update client initialization Update the client initialization code to use the new `CamundaClient` class. For example, change: ```java ZeebeClient client = ZeebeClient.newClientBuilder() .gatewayAddress("localhost:26500") .usePlaintext() .build(); ``` to: ```java CamundaClient client = CamundaClient.newClientBuilder() .grpcAddress(URI.create("http://localhost:26500")) .restAddress(URI.create("http://localhost:8080")) .build(); ``` :::info - Refer to the [CamundaClientBuilder documentation](https://javadoc.io/doc/io.camunda/camunda-client-java/latest/io/camunda/client/CamundaClientBuilder.html) for more details on available configuration options. - The construction for OAuth, Basic Auth, or custom providers remains conceptually the same, but you must ensure you use the classes from the new package. Refer to the [Camunda Java Client bootstrapping](../java-client/getting-started.md#bootstrapping) for more details. ::: ## Renamed API classes and commands The following API classes have been changed in the Camunda Java Client: | Old | New | | :------------------------------ | :-------------------------------- | | `ZeebeClientBuilder` | `CamundaClientBuilder` | | `ZeebeClientClouldBuilderStep1` | `CamundaClientClouldBuilderStep1` | | `ZeebeClientConfiguration` | `CamundaClientConfiguration` | | `ZeebeFuture` | `CamundaFuture` | The following commands have been renamed in the Camunda Java Client: | Old | New | | :----------------------------- | :----------------------------- | | `newClockPinCommand()` | `newPinClockCommand()` | | `newClockResetCommand()` | `newResetClockCommand()` | | `newUserCreateCommand()` | `newCreateUserCommand()` | | `newUserTaskAssignCommand()` | `newAssignUserTaskCommand()` | | `newUserTaskCompleteCommand()` | `newCompleteUserTaskCommand()` | | `newUserTaskUnassignCommand()` | `newUnassignUserTaskCommand()` | | `newUserTaskUpdateCommand()` | `newUpdateUserTaskCommand()` | ## Protocol and connection: REST vs gRPC selection Zeebe Java Client used **gRPC by default**. The Camunda Java Client uses **REST by default**. If you want to use gRPC, you need to explicitly set the `grpcAddress` in the client builder and configure `preferRestOverGrpc=false` to make gRPC the default. To use gRPC, add the following to your client builder: ```java CamundaClient client = CamundaClient.newClientBuilder() .grpcAddress(URI.create("http://localhost:26500")) .restAddress(URI.create("http://localhost:8080")) .preferRestOverGrpc(false) .build(); ``` --- ## Migrate to Camunda Process Test :::note Have you already migrated? You do not need to perform this migration again if you already did this when upgrading to version 8.8. This guide is retained to help customers migrate before upgrading from 8.9 to 8.10. See [API and SDK changes to migrate before Camunda 8.10](../migration-manuals/migrate-to-810.md#api-and-sdk-changes-to-migrate-before-camunda-810). ::: ## About [Camunda Process Test](/apis-tools/testing/getting-started.md) (CPT) is a library to test your BPMN processes and your projects. - It is the successor to Zeebe Process Test (ZPT). - Starting with version **8.8**, ZPT is deprecated and was removed in version **8.10**. See [release announcement](https://camunda.com/blog/2025/04/camunda-process-test-the-next-generation-testing-library/). This guide walks you through migrating your existing test cases from ZPT to CPT step-by-step. ### Key differences There are key differences between ZPT and CPT in both API and behavior, which may increase migration effort depending on your existing test cases. | Aspect | Zeebe Process Test (ZPT) | Camunda Process Test (CPT) | | :--------------------------- | :------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------- | | **Underlying engine** | Uses only Camunda's workflow engine (Zeebe) with access to internal components. | Runs the full Camunda distribution and interacts with the Orchestration Cluster API. | | **Assertions and utilities** | Uses specific naming conventions. | Uses different names to align with the API; not all ZPT assertions/utilities have equivalents in CPT. | | **Startup time** | Faster startup. | Takes longer to start as it runs the full Orchestration Cluster distribution. | ### Key advantages of CPT - Access to Camunda’s Orchestration Cluster API and Connectors - Support for Camunda user tasks - Blocking assertions for asynchronous processing - Enhanced mocking utilities ## Update your dependency First, update your Maven dependency. - **If you use ZPT with Camunda Spring Boot Starter integration** (`artifactId: spring-boot-starter-camunda-test` or `spring-boot-starter-camunda-test-testcontainer`), replace it with **CPT’s Spring integration module**. - **If you use ZPT without Spring** (`artifactId: zeebe-process-test-extension` or `zeebe-process-test-extension-testcontainer`), replace it with **CPT’s Java module**. In your Maven `pom.xml`, add the dependency: ```xml io.camunda camunda-process-test-spring test ``` In your Maven `pom.xml`, add the dependency: ```xml io.camunda camunda-process-test-java test ``` ## Choose your runtime Next, choose how you want to run CPT, considering your environment. CPT can be used in two modes: - CPT with Testcontainers (as equivalent to ZPT with Testcontainers) - CPT with remote engine (as equivalent to ZPT's embedded runtime) ### ZPT with Testcontainers If you use ZPT with Testcontainers ( `artifactId: zeebe-process-test-extension-testcontainer` or `spring-boot-starter-camunda-test-testcontainer`), then you can use CPT's default [Testcontainers runtime](/apis-tools/testing/configuration.md#testcontainers-runtime) without additional changes. ### ZPT's embedded runtime If you use ZPT’s embedded runtime (`artifactId: zeebe-process-test-extension` or `spring-boot-starter-camunda-test`), switch to CPT’s [remote runtime](/apis-tools/testing/configuration.md#remote-runtime). Choose this option only if you cannot install a Docker-API compatible container runtime (e.g., Docker on Linux or Docker Desktop). In this mode, CPT connects to a remote runtime, such as a local Camunda 8 Run running on your machine. Prepare your remote runtime: 1. **Install Camunda 8 Run** Follow the [installation guide](/self-managed/quickstart/developer-quickstart/c8run/install-start.md#install-and-start-camunda-8-run) on your machine. Use the same version as CPT or later. See the [remote runtime prerequisites](/apis-tools/testing/configuration.md#prerequisites-1) for the required port and permissions. 2. **Start Camunda 8 Run**. 3. **Switch CPT’s runtime mode** to `remote` in your project configuration. In your `application.yml` (or `application.properties`): ```yaml camunda: process-test: runtime-mode: remote ``` In your `/camunda-container-runtime.properties` file: ``` runtimeMode=remote ``` ## Migrate your process tests Now, it's time to migrate your process tests. First, migrate the general test class structure: 1. **Replace annotations and types** - Replace `@ZeebeSpringTest` with `@CamundaSpringProcessTest` - Replace the type `ZeebeTestEngine` with `CamundaProcessTestContext` 2. **Remove record stream fields** CPT does not provide direct access to records. Instead, use the SDK to request data from the [API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). Below is an example of a ZPT test class: ```java @SpringBootTest @ZeebeSpringTest class MyProcessTest { @Autowired private CamundaClient client; @Autowired private ZeebeTestEngine engine; @Autowired private RecordStream recordStream; @Test void shouldCompleteProcess() { // given final ProcessInstanceEvent processInstance = client .newCreateInstanceCommand() .bpmnProcessId("my-process") .latestVersion() .send() .join(); // when: drive the process forward // then BpmnAssert.assertThat(processInstance) .hasPassedElementsInOrder("start", "task1", "task2", "task3", "end") .isCompleted(); } } ``` This is the equivalent CPT test class: ```java @SpringBootTest @CamundaSpringProcessTest class MyProcessTest { @Autowired private CamundaClient client; @Autowired private CamundaProcessTestContext processTestContext; @Test void shouldCompleteProcess() { // given final ProcessInstanceEvent processInstance = client .newCreateInstanceCommand() .bpmnProcessId("my-process") .latestVersion() .send() .join(); // when: drive the process forward // then CamundaAssert.assertThat(processInstance) .hasCompletedElementsInOrder("start", "task1", "task2", "task3", "end") .isCompleted(); } } ``` First, migrate the general test class structure: 1. **Replace annotations and types** - Replace `@ZeebeProcessTest` with `@CamundaProcessTest` - Replace the type `ZeebeTestEngine` with `CamundaProcessTestContext` 2. **Remove record stream fields** CPT does not provide direct access to records. Instead, use the SDK to request data from the [API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). Below is an example of a ZPT test class: ```java @ZeebeProcessTest class MyProcessTest { private CamundaClient client; private ZeebeTestEngine engine; private RecordStream recordStream; @Test void shouldCompleteProcess() { // given: the processes are deployed final ProcessInstanceEvent processInstance = client .newCreateInstanceCommand() .bpmnProcessId("my-process") .latestVersion() .send() .join(); // when: drive the process forward // then BpmnAssert.assertThat(processInstance) .hasPassedElementsInOrder("start", "task1", "task2", "task3", "end") .isCompleted(); } } ``` This is the equivalent CPT test class: ```java @CamundaProcessTest class MyProcessTest { private CamundaClient client; private CamundaProcessTestContext processTestContext; @Test void shouldCompleteProcess() { // given: the processes are deployed final ProcessInstanceEvent processInstance = client .newCreateInstanceCommand() .bpmnProcessId("my-process") .latestVersion() .send() .join(); // when: drive the process forward // then CamundaAssert.assertThat(processInstance) .hasCompletedElementsInOrder("start", "task1", "task2", "task3", "end") .isCompleted(); } } ``` Then, review all test methods and migrate the assertions and utilities. See the following sections for detailed instructions. ### Process instance assertions ZPT has assertions for a process instance using `BpmnAssert.assertThat()` with the `ProcessInstanceEvent` or the `ProcessInstanceResult`. CPT has equivalent assertions using `CamundaAssert.assertThat()`. ```java // given ProcessInstanceEvent processInstance = // // ZPT: BpmnAssert.assertThat(processInstance).isCompleted(); // CPT: CamundaAssert.assertThat(processInstance).isCompleted(); ``` Some of CPT's assertions have different method names or signatures. Check the following list for the equivalent CPT assertion: ZPT: BpmnAssert.assertThat(processInstance) CPT: CamundaAssert.assertThat(processInstance) isStarted() [isCreated()](/apis-tools/testing/assertions.md#iscreated) isActive() [isActive()](/apis-tools/testing/assertions.md#isactive) isCompleted() [isCompleted()](/apis-tools/testing/assertions.md#iscompleted) isNotCompleted() Not supported Instead, use [isActive()](/apis-tools/testing/assertions.md#isactive) or [isTerminated()](/apis-tools/testing/assertions.md#isterminated). isTerminated() [isTerminated()](/apis-tools/testing/assertions.md#isterminated) isNotTerminated() Not supported Instead, use [isActive()](/apis-tools/testing/assertions.md#isactive) or [isCompleted()](/apis-tools/testing/assertions.md#iscompleted). isWaitingAtElements() [hasActiveElements()](/apis-tools/testing/assertions.md#hasactiveelements) isWaitingExactlyAtElements() [hasActiveElementsExactly()](/apis-tools/testing/assertions.md#hasactiveelementsexactly) isNotWaitingAtElements() [hasNoActiveElements()](/apis-tools/testing/assertions.md#hasnoactiveelements) hasPassedElement() [hasCompletedElement()](/apis-tools/testing/assertions.md#hascompletedelement) hasPassedElementsInOrder() [hasCompletedElementsInOrder()](/apis-tools/testing/assertions.md#hascompletedelementsinorder) hasNotPassedElement() [hasNotActivatedElements()](/apis-tools/testing/assertions.md#hasnotactivatedelements) hasVariable() [hasVariableNames()](/apis-tools/testing/assertions.md#hasvariablenames) hasVariableWithValue() [hasVariable()](/apis-tools/testing/assertions.md#hasvariable) hasAnyIncidents() [hasActiveIncidents()](/apis-tools/testing/assertions.md#hasactiveincidents) hasNoIncidents() [hasNoActiveIncidents()](/apis-tools/testing/assertions.md#hasnoactiveincidents) isWaitingForMessages() [isWaitingForMessage()](/apis-tools/testing/assertions.md#iswaitingformessage) isNotWaitingForMessages() [isNotWaitingForMessage()](/apis-tools/testing/assertions.md#isnotwaitingformessage) hasCorrelatedMessageByName() [hasCorrelatedMessage()](/apis-tools/testing/assertions.md#hascorrelatedmessage) hasCorrelatedMessageByCorrelationKey() [hasCorrelatedMessage()](/apis-tools/testing/assertions.md#hascorrelatedmessage) hasCalledProcess() Not supported Instead, use a [ProcessInstanceSelector](/apis-tools/testing/assertions.md#with-process-instance-selector) to assert the child process instance. hasNotCalledProcess() Not supported Instead, assert the call activity or the child process instance. ### Deployment assertions ZPT has assertions for a deployment using `BpmnAssert.assertThat()` with the `DeploymentEvent`. CPT has no equivalent assertions. Instead, you could write a [custom assertion](/apis-tools/testing/assertions.md#custom-assertions) with AssertJ to verify the properties of the deployment event. ```java // given DeploymentEvent deploymentEvent = // // ZPT: BpmnAssert.assertThat(deploymentEvent).containsProcessesByResourceName("my-process.bpmn"); // CPT: Assertions.assertThat(deploymentEvent.getProcesses()) .extracting(Process::getResourceName) .contains("my-process.bpmn"); ``` ### Job assertions ZPT has assertions for an activated job using `BpmnAssert.assertThat()` with the `ActivatedJob`. CPT has no equivalent assertions. Instead, you could write a [custom assertion](/apis-tools/testing/assertions.md#custom-assertions) with AssertJ to verify the properties of the activated job. ```java // given ActivatedJob activatedJob = // // ZPT: BpmnAssert.assertThat(activatedJob).hasElementId("elementId"); // CPT: Assertions.assertThat(activatedJob.getElementId()).isEqualTo("elementId"); ``` ### Message assertions ZPT has assertions for a published message using `BpmnAssert.assertThat()` with the `PublishMessageResponse`. CPT has no equivalent assertions. Instead, you could use a [ProcessInstanceSelector](/apis-tools/testing/assertions.md#with-process-instance-selector) to find the correlated process instance and verify the correlation using a [message subscription assertion](/apis-tools/testing/assertions.md#hascorrelatedmessage). Alternatively, you could use the [correlate message API](/apis-tools/orchestration-cluster-api-rest/specifications/correlate-message.api.mdx) that returns the process instance key in the response. ```java // ZPT: final PublishMessageResponse publishMessageResponse = // BpmnAssert.assertThat(publishMessageResponse) .hasCreatedProcessInstance() .extractingProcessInstance() .isCompleted(); // CPT: final CorrelateMessageResponse correlateMessageResponse = // // The correlate command would fail if the message could not be correlated CamundaAssert.assertThatProcessInstance(byKey(correlateMessageResponse.getProcessInstanceKey())) .isCompleted(); ``` ### Inspection utilities ZPT provides the `InspectionUtility` to locate process instances and pass them to assertions. Some assertions also include methods to extract related entities, such as `extractingProcessInstance()` or `extractingLatestIncident()`. CPT offers a similar utility via the [ProcessInstanceSelector](/apis-tools/testing/assertions.md#with-process-instance-selector), which can be used with `CamundaAssert.assertThatProcessInstance()`. For other entities, you can use the Camunda client to search for the entity and implement a [custom assertion](/apis-tools/testing/assertions.md#custom-assertions). ```java // ZPT: InspectedProcessInstance childProcessInstance = InspectionUtility.findProcessInstances() .withBpmnProcessId("child-process") .findFirstProcessInstance() .get(); BpmnAssert.assertThat(childProcessInstance).isCompleted(); // CPT: CamundaAssert.assertThatProcessInstance(byProcessId("child-process")) .isCompleted(); ``` ### ZeebeTestEngine utilities ZPT provides the `ZeebeTestEngine` utilities to interact with the runtime, for example, to advance time. CPT offers a similar utility via the [CamundaProcessTestContext](/apis-tools/testing/utilities.md), but the following utilities are **not supported**: - `waitForIdleState(duration)` - `waitForBusyState(duration)` CPT does not require these utilities because it provides [blocking assertions](/apis-tools/testing/assertions.md) that wait until the expected condition is fulfilled. ```java // ZPT engine.waitForIdleState(duration); engine.increaseTime(Duration.ofDays(1)); // CPT: assertThat(processInstance).hasActiveElements("timer_event"); processTestContext.increaseTime(Duration.ofDays(1)); ``` ## Next steps Congratulations! Your process tests should now be fully migrated to CPT and running successfully. When you’re ready, take the next steps to continue your journey: - Explore new [assertions](/apis-tools/testing/assertions.md). - Simplify your tests with new [utilities](/apis-tools/testing/utilities.md). - Generate [process test coverage reports](/apis-tools/testing/getting-started.md#process-test-coverage). - Refer to the [API documentation](https://javadoc.io/doc/io.camunda/camunda-process-test-java/latest/io/camunda/process/test/api/package-summary.html) for details. ## Troubleshooting If you encounter issues with the migration or notice missing features, report them in the [Camunda GitHub repository](https://github.com/camunda/camunda/issues). --- ## Migrate to Camunda Spring Boot Starter :::note Have you already migrated? You do not need to perform this migration again if you already did this when upgrading to version 8.8. This guide is retained to help customers migrate before upgrading from 8.9 to 8.10. See [API and SDK changes to migrate before Camunda 8.10](../migration-manuals/migrate-to-810.md#api-and-sdk-changes-to-migrate-before-camunda-810). ::: ## About This guide provides an overview of the process for migrating to the Camunda Spring Boot Starter. - The [Camunda Spring Boot Starter](../camunda-spring-boot-starter/getting-started.md) is the official Spring library for connecting to Orchestration Cluster, automating processes, and implementing job workers. :::tip Plan and start your migration early to ensure compatibility, access to latest features, and future support. ::: ## Maven/Gradle dependencies Replace the Zeebe Spring SDK dependency with the Camunda Spring Boot Starter dependency in your `pom.xml` or `build.gradle` file. Maven: ```xml io.camunda camunda-spring-boot-starter 8.8.x ``` Gradle: ```groovy implementation 'io.camunda:camunda-spring-boot-starter:${8.8.x}' ``` ## Deprecated classes and methods Please refer to the [Camunda Java Client migration guide](migrate-to-camunda-java-client.md) for details on deprecated classes and methods. --- ## Migrate to Camunda user tasks :::note Have you already migrated? You do not need to perform this migration again if you already did this when upgrading to version 8.8. This guide is retained to help customers migrate before upgrading from 8.9 to 8.10. See [API and SDK changes to migrate before Camunda 8.10](../migration-manuals/migrate-to-810.md#api-and-sdk-changes-to-migrate-before-camunda-810). ::: ## About Camunda 8.7 introduced a new [user task](/components/modeler/bpmn/user-tasks/user-tasks.md) implementation type: Camunda user task (formerly named Zeebe user task). Camunda user tasks have several benefits compared to Job worked-based user tasks, including: - Running directly on the automation engine for high performance. - Removing dependencies and round trips to Tasklist. - A powerful API that supports the full task lifecycle. In this guide, you will learn: - Under which circumstances and when you should migrate. - How to estimate the impact on a project. - Steps you need to take for a successful migration without interrupting your operations. ## Decide on your migration path Camunda user tasks require migration of the user tasks in both your diagrams and the task API. With this in mind, you can migrate at your own pace. If you should migrate now or later, and what is required to migrate depends on your current setup and future plans. ### Task type differences To make an informed decision, you should understand the differences between both task types and the new capabilities of Camunda user tasks. Refer to this table for important high-level differences between the two task types: Camunda user tasks Recommended for new and existing projects Job worker-based user tasks Existing implementation Implementation location Zeebe Does not require Tasklist to run Tasklist Compatible versions 8.5 + 8.0 + Supports Tasklist UI API Supports Orchestration Cluster REST API Full support Supports Tasklist API (deprecated) Partially Queries, GET tasks, forms, variables ℹ You must use Zeebe and Tasklist APIs to manage Camunda user tasks Full support Supports job workers Supports task lifecycle events Full lifecycle events including custom actions Basic only: created/completed/canceled Supports task listeners Extras Custom actions/outcomes Custom actions can be defined on any operation excluding unassign (DELETE assignment, send update beforehand) Supports task reports in Optimize Recommendations Recommended for existing and new projects when you run Tasklist. Migrate existing projects and task applications/clients to this task type when you require one of the features above, or the following use cases: Implement a full task lifecycle React on any change/events in tasks, such as assignments, escalations, due date updates, or any custom actions Send notifications Track task or team performance Build an audit log on task events Enrich tasks with business data You can continue to use this task type on existing projects when you have a custom task application running on it and do not require any of the above features. ## Change the implementation type of user tasks We recommend you migrate process-by-process, allowing you to thoroughly test the processes in your test environments or via your [CI/CD](/components/hub/workspace/modeler/integrate-modeler-in-ci-cd.md). To do this, take the following steps: 1. Open a diagram you want to migrate. 2. Click on a user task. 3. Check if the task has an embedded form. - If a form is embedded, [transform it into a linked form](/components/modeler/bpmn/user-tasks/user-tasks.md#camunda-form-linked) before you change the task type implementation. Press `Ctrl+Z` or `⌘+Z` to undo if you accidentally removed your embedded form. 4. Open the **Implementation** section in the properties panel. 5. Click the **Type** dropdown and select **Camunda user task**. The linked form or external form reference will be preserved. Repeat these steps for all user tasks in the process. Then, deploy the process to your development cluster and test it by running the process and ensuring your custom task applications work. ## How Tasklist API (V1) compares to Orchestration Cluster REST API (V2) :::note The Tasklist REST API is [deprecated with the 8.8 release and will be deleted with the 8.10 release](/reference/announcements-release-notes/880/880-announcements.md#deprecated-operate-and-tasklist-v1-rest-apis). ::: The following table provides a breakdown of which operations are supported in which API, and for which user tasks. Operation Tasklist API Orchestration Cluster REST API Query tasks ✔ All types ✔ Camunda user tasks Get task ✔ All types ✔ Camunda user tasks Retrieve task variables ✔ All types ✔ Camunda user tasks Get task form ✔ All types ✔ Camunda user tasks Change task assignment ✔ Job worker-based tasks ✔ Camunda user tasks Complete task ✔ Job worker-based tasks ✔ Camunda user tasks Update task Not supported ✔ Camunda user tasks Safe and retrieve draft variables ✔ Job worker-based tasks Not supported The following table outlines the respective endpoints. Click the endpoints to follow to the API documentation and inspect the differences in the request and response objects. Operation Tasklist API Orchestration Cluster REST API Query user tasks POST /tasks/search POST /user-tasks/search Get user task GET /tasks/:taskId GET /user-tasks/:userTaskKey Retrieve task variables GET /variables/:variableId POST /tasks/:taskId/variables/search Get task form GET /forms/:formId GET /user-tasks/:userTaskKey/form Assign a task PATCH /tasks/:taskId/assign POST /user-tasks/:userTaskKey/assignment Unassign a task PATCH /tasks/:taskId/unassign DELETE /user-tasks/:userTaskKey/assignee Complete task PATCH /tasks/:taskId/complete POST /user-tasks/:userTaskKey/completion Update task Not supported PATCH /user-tasks/:userTaskKey Save and retrieve draft variables POST /tasks/:taskId/variables - ### Zeebe Java client Use the Zeebe Java client when you are building your task application in Java. The client assists with managing authentication and request/response objects. ### API differences Refer to the dedicated sections and API explorers to learn details about the APIs. ## Troubleshooting and common issues If your task application does not work properly after migration, check the following: - **The endpoints return specific error messages when you run them on the wrong task type**: Ensure to call the right endpoint for the right task type, c.f. above [table](#use-the-new-camunda-8-api). - **Forms do not appear**: Ensure you have extracted embedded forms, if any, and [transformed them into linked forms](/components/modeler/bpmn/user-tasks/user-tasks.md#camunda-form-linked), before you change the task type implementation. - **Task update operation does not work**: The update operation is only available to Camunda user tasks. --- ## SaaS orchestration architecture ## About Camunda 8.9 introduces a streamlined SaaS orchestration architecture. The runtime for Operate, Tasklist, Identity (Admin), and the Zeebe REST API is unified into a single orchestration service. Zeebe brokers continue to run separately and execute workflows as before. This is a topology change in SaaS only and does not affect Self-Managed deployments. What's new: - [Unified API domain for Orchestration Clusters](#unified-api-domain-for-orchestration-clusters). Legacy hostnames are deprecated but will remain available throughout 8.9 and are scheduled for removal in 8.10. - [Web app URLs require an explicit application path.](#web-app-urls-require-an-explicit-application-path) The cluster base URL no longer redirects to Operate, Tasklist, or Admin automatically. - [Client credentials for new clusters use unified API URLs.](#client-credentials-and-legacy-hostnames) - [Cluster Metrics endpoint: `service` labels have changed on Orchestration Cluster metrics.](#service-label-changes) What didn't change: - The same UIs for Operate, Tasklist, and Admin/Identity and the REST API remain available, although web app URLs now [require an explicit application path](#web-app-urls-require-an-explicit-application-path). - The Zeebe gRPC endpoint is unchanged. ## Unified API domain for Orchestration Clusters Camunda 8.9 introduces a unified API domain for Orchestration Clusters. All services are now accessible under a single base URL: | Service | Unified URL (8.9) | | :------------------ | :------------------------------------------------------------ | | Base / REST API | `https://.api./` | | Operate UI | `https://.api.//operate` | | Tasklist UI | `https://.api.//tasklist` | | Admin (Identity) UI | `https://.api.//admin` | Legacy hostnames (`*.zeebe.`, `*.operate.`, `*.tasklist.`, and `*.identity.`) continue to work in 8.9 and are internally routed to the unified service, but are deprecated and scheduled for removal in 8.10. After the 8.10 release, only the Zeebe gRPC endpoint and the unified `*.api.*` endpoints will remain. ### Web app URLs require an explicit application path From 8.9, you must include the application path (`/operate`, `/tasklist`, or `/admin`) when opening a web app. Requesting the cluster base URL on its own no longer opens a web app. Before 8.9, Operate, Tasklist, and Admin ran as standalone applications, so the runtime could determine which frontend to serve and redirected the cluster base URL to the matching web app automatically. For example, `https://.operate.camunda.io/` redirected to `https://.operate.camunda.io//operate`. From 8.9, Operate, Tasklist, Admin, and the REST API are served by the same unified application, so the runtime cannot infer which frontend a request is for. The automatic redirect no longer happens, and the cluster base URL does not resolve to a web app. Update any stored links that rely on the previous redirect, including browser bookmarks, links saved in external systems, and links embedded in processes or documentation created before 8.9. Each must point at the full path: - `https://.api.//operate` - `https://.api.//tasklist` - `https://.api.//admin` To confirm the correct URL for a cluster, open the cluster in Camunda Console and use the launch links for Operate, Tasklist, or Admin. If your organization uses [IP allowlisting](/components/saas/clusters/manage-ip-allowlists.md), a request to the cluster base URL without an application path can return `403 Forbidden` rather than an obvious routing error, because the base path is subject to allowlist restrictions. A `403` on the base URL is therefore an expected symptom of a stored link that is missing its application path, not necessarily an allowlist misconfiguration. :::note URLs extracted from the HTML of Camunda web UIs are not a stable interface and can change between releases. Use the documented URL formats above or the Console launch links instead of scraping links from the UI. ::: ### Deprecation Legacy hostnames are deprecated as of 8.9 and will be removed in 8.10. Migrate any hard-coded URLs for Operate, Tasklist, or Identity to the new unified `*.api.*` URLs during the 8.9 lifecycle to ensure readiness before the 8.10 release. ## Client credentials and legacy hostnames Existing client credentials downloaded from Console for pre-8.9 clusters may reference legacy hostnames. Because those hostnames remain available in 8.9, existing credentials continue to work after a cluster is upgraded to 8.9. No immediate action is required. ### Recommended action For long-lived automation and CI/CD systems, Camunda recommends updating credentials to use the new unified `*.api.*` URLs and corresponding token audience values before 8.10. You have two options: - **Update existing credentials manually:** Edit the hostnames and token audience values in your existing credential configuration to reference the new unified URLs. For example: - **Legacy (pre-8.9):** - **Base URL:** `https://bru-2.operate.camunda.io/abc123-def456-ghi789` - **Audience:** `operate.camunda.io` - **New (from 8.9):** - **Base URL:** `https://bru-2.api.camunda.io/abc123-def456-ghi789/operate` - **Audience:** `api.camunda.io` - **Create new credentials:** Create a fresh set of client credentials in Camunda Console, which will be generated with the unified API URLs and correct audience values by default. ## Service label changes Breaking change Due to the streamlined orchestration architecture introduced in 8.9, metrics previously emitted by the individual Operate, Tasklist, and Identity services are now emitted by the unified orchestration service. As a result, the `service` label on affected metrics will change to reflect the new source service. This applies to Camunda 8 SaaS clusters using the [Cluster Metrics endpoint](/components/saas/monitoring/cluster-metrics-endpoint/set-up-cluster-metrics-endpoint.md). ### Impact Monitoring dashboards, alerting rules, or queries that filter or group by the `service` label on Orchestration Cluster metrics may stop matching expected values after upgrading to 8.9, as some metrics will now be emitted by a different source service. ### Action required After your cluster is upgraded to 8.9, review your dashboards and alerting rules and verify which `service` label values your metrics return. Update any Prometheus queries or alert definitions that no longer match. ## Upgrade behavior and expected downtime When a Camunda 8 SaaS Orchestration Cluster is upgraded from 8.8 to 8.9: 1. The existing Operate and Tasklist components are shut down. 2. The unified orchestration service starts in their place. 3. Endpoint routing is updated so legacy hostnames continue to resolve while the new `*.api.*` endpoints become active. ### Impact during upgrade - **Web UIs and REST API:** You should expect a short interruption while the old services are removed and the unified service becomes ready. This affects all services, but not the Zeebe brokers. - **Workflow execution (Zeebe brokers):** Designed to remain running throughout the upgrade. The primary impact is limited to UI and API availability, not to engine execution state. To upgrade, initiate the cluster upgrade to 8.9 from Camunda Console. No additional steps are required beyond the URL and credential recommendations above. --- ## Process application management in Web Modeler API v1 ## About these changes In Camunda 8.9, process applications were not explicitly exposed in Web Modeler API v1. There were no process application endpoints. However, process applications were implicitly accessible as folders. For example: - You could pass a process application ID to `DELETE /api/v1/folders/{folderId}` to delete the process application. - The response for `GET /api/v1/folders/{folderId}` returns a `parentId`, representing the folder's parent folder. If the parent is in a process application, rather than another folder, the process application ID is returned. Camunda 8.10 changes how resources are organized. Review [structure and terminology](./migrate-from-web-modeler-to-hub-api.md#structure-and-terminology) for an explanation of these changes. To support the new file hierarchy, two major changes have been introduced to Web Modeler API v1: - Process applications are no longer implicitly accessible via v1 folders, files, and project APIs. - New process application APIs and changes to existing APIs are introduced to make access explicit. :::note DEPRECATED Web Modeler API v1 is deprecated in Camunda 8.10 and will be removed in 8.12. [Migrate to Camunda Hub REST API v2](../migration-manuals/migrate-from-web-modeler-to-hub-api.md). ::: ## Folders API You can no longer access process applications as folders in any folders API context. Instead, process applications are now explicitly represented. ### Folder IDs reference folders In folders API requests, you'll receive a `404 NOT FOUND` if you pass a process application ID as the `folderId`. Previously, you could pass a process application's ID when deleting, reading, and updating folders. Example: ```shell GET /api/v1/folders/1ef492f5-7ddc-43a7-b5e2-f5ad5c14b676 # must be a folder, not a process application ``` Affected endpoints: - `DELETE /api/v1/folders/{folderId}` - `GET /api/v1/folders/{folderId}` - `PATCH /api/v1/folders/{folderId}` Instead of passing the process application ID to the folders endpoints, use the new process application endpoints: - [`DELETE /api/v1/process-applications/{processApplicationId}`](https://modeler.camunda.io/swagger-ui/index.html#/Process%20Applications/deleteProcessApplication) - [`GET /api/v1/process-applications/{processApplicationId}`](https://modeler.camunda.io/swagger-ui/index.html#/Process%20Applications/getProcessApplication) - [`PATCH /api/v1/process-applications/{processApplicationId}`](https://modeler.camunda.io/swagger-ui/index.html#/Process%20Applications/updateProcessApplication) Example: ```shell GET /api/v1/process-applications/e005e49a-dce8-42ee-b0db-30b1d5555ebd # must be a process application, not a folder ``` ### Parent IDs reference folders In folders API requests, you'll receive a `404 NOT FOUND` if you pass a process application ID as the `parentId`. Previously, you could pass a process application's ID when writing data. Use the new `processApplicationId` field to specify the target process application and, optionally, the `parentId` field to specify a target folder. Example: ```shell POST /api/v1/folders { "name": "Nested folder", "projectId": "fb928277-6268-44bb-b3e6-1925fa730ecf", "parentId": "1ef492f5-7ddc-43a7-b5e2-f5ad5c14b676", # must be a folder, not a process application "processApplicationId": "e005e49a-dce8-42ee-b0db-30b1d5555ebd" # new process application field } ``` Affected endpoints: - `PATCH /api/v1/folders/{folderId}` - `POST /api/v1/folders` ### Process applications are never returned as parent folders In folders API responses, the `parentId` is null for any folder stored at the root of a process application. Previously, `parentId` would return the process application ID. The process application ID is, instead, returned in a new `processApplicationId` field. Example: ```json { "id": "f169a3d4-056b-463c-b4c9-6c3600c2213a", "name": "Root folder", "projectId": "58a93bf7-4ea0-4e56-85fa-5c8fccc3877d", "parentId": null, // null if the parent container is a process application "processApplicationId": "e005e49a-dce8-42ee-b0db-30b1d5555ebd", // new process application field "created": "2026-08-24T14:17:33.889317Z", "createdBy": { "name": "Jane Doe", "email": "jane.doe@email.com" }, "updated": "2026-08-24T14:23:48.371161659Z", "updatedBy": { "name": "Jane Doe", "email": "jane.doe@email.com" } } ``` Affected endpoints: - `GET /api/v1/folders/{folderId}` - `PATCH /api/v1/folders/{folderId}` - `POST /api/v1/folders` ## Files API You can no longer access process applications as folders in any files API context. Instead, process applications are now explicitly represented. ### Folder ID must reference a folder In files API requests, you'll receive a `404 NOT FOUND` if you pass a process application ID as the `folderId`. Previously, you could pass a process application's ID to place the file in the process application. Use the new `processApplicationId` field to specify the target process application and, optionally, the `folderId` field to specify a target folder. ```shell POST /api/v1/files { "name": "New BPMN diagram", "folderId": "cdcf3895-1061-4084-b97e-c0abaab59b6f", # must be a folder, not a process application "processApplicationId": "e005e49a-dce8-42ee-b0db-30b1d5555ebd", # new process application field "projectId": "fb928277-6268-44bb-b3e6-1925fa730ecf", "content": "...", "fileType": "BPMN" } ``` Affected endpoints: - `PATCH /api/v1/files/{fileId}` - `POST /api/v1/files` ### Process applications are never returned as folders In files API responses, the `folderId` is null for any file stored at the root of a process application. Previously, `folderId` would return the process application ID. The process application ID is, instead, returned in a new `processApplicationId` field. Example: ```json { "id": "5cafbf6a-d5d8-4ed2-8dae-b950ce3597c3", "name": "New BPMN diagram", "projectId": "fb928277-6268-44bb-b3e6-1925fa730ecf", "folderId": null, // null if the parent container is a process application "processApplicationId": "e005e49a-dce8-42ee-b0db-30b1d5555ebd", // new process application field "simplePath": "New BPMN diagram.bpmn", "canonicalPath": [], "revision": 2, "type": "BPMN", "created": "2026-08-24T14:17:33.889317Z", "createdBy": { "name": "Jane Doe", "email": "jane.doe@email.com" }, "updated": "2026-08-24T14:23:48.371161659Z", "updatedBy": { "name": "Jane Doe", "email": "jane.doe@email.com" } } ``` Affected endpoints: - `GET /api/v1/files/{fileId}` - `PATCH /api/v1/files/{fileId}` - `POST /api/v1/files/search` - `POST /api/v1/files` ### File paths exclude process applications In files API endpoints, the `simplePath` and `canonicalPath` omit the container process application. Previously, the process application was included if it was on the path. Example: ```json { "metadata": { "id": "ed043868-556f-4a93-97dc-3cba1652363c", "name": "New BPMN diagram", "projectId": "fb928277-6268-44bb-b3e6-1925fa730ecf", "folderId": "cdcf3895-1061-4084-b97e-c0abaab59b6f", "processApplicationId": "e005e49a-dce8-42ee-b0db-30b1d5555ebd", // new process application field "simplePath": "Root folder/New BPMN diagram.bpmn", // excludes process applications "canonicalPath": [ // excludes process applications { "id": "cdcf3895-1061-4084-b97e-c0abaab59b6f", "name": "Root folder" } ], "revision": 2, "type": "BPMN", "created": "2026-08-24T17:35:46.329162Z", "createdBy": { "name": "Jane Doe", "email": "jane.doe@email.com" }, "updated": "2026-08-24T17:35:46.330218Z", "updatedBy": { "name": "Jane Doe", "email": "jane.doe@email.com" } }, "content": "..." } ``` Affected endpoints: - `GET /api/v1/files/{fileId}` - `PATCH /api/v1/files/{fileId}` - `POST /api/v1/files/search` (the process application is excluded from these paths in both the response and the request `filter`) - `POST /api/v1/files` ### Search for files in a process application With `POST /api/v1/files/search`, you can now use a new `processApplicationId` filter. Example: ```shell POST /api/v1/files/search { "filter": { "processApplicationId": "e005e49a-dce8-42ee-b0db-30b1d5555ebd" } } ``` ## Projects API You can no longer access process applications as folders in any projects API context. Instead, process applications are now explicitly represented. ### Process applications are never returned as parent folders In the `GET /api/v1/projects/{projectId}` response, the `content.folders[i].parentId` and `content.files[i].folderId` are null for any folder or file stored at the root of a process application. Previously, `parentId` and `folderId` would return the process application ID. The process application ID is, instead, returned in a new `processApplicationId` field. Example: ```json { "metadata": { "id": "fb928277-6268-44bb-b3e6-1925fa730ecf", "name": "Project", "created": "2026-08-24T17:35:46.329162Z", "createdBy": { "name": "Jane Doe", "email": "jane.doe@email.com" }, "updated": "2026-08-24T17:35:46.330218Z", "updatedBy": { "name": "Jane Doe", "email": "jane.doe@email.com" } }, "content": { "folders": [ { "id": "cdcf3895-1061-4084-b97e-c0abaab59b6f", "name": "Root folder", "projectId": "fb928277-6268-44bb-b3e6-1925fa730ecf", "parentId": null, // null if the parent container is a process application "processApplicationId": "e005e49a-dce8-42ee-b0db-30b1d5555ebd", // new process application field "created": "2026-08-24T14:37:04.36503Z", "createdBy": { "name": "Jane Doe", "email": "jane.doe@email.com" }, "updated": "2026-08-24T14:37:07.470563Z", "updatedBy": { "name": "Jane Doe", "email": "jane.doe@email.com" } } ], "files": [ { "id": "5a6aa24f-844b-4da2-9118-007f5c1a2df7", "name": "Root file", "projectId": "fb928277-6268-44bb-b3e6-1925fa730ecf", "folderId": null, // null if the parent container is a process application "processApplicationId": "e005e49a-dce8-42ee-b0db-30b1d5555ebd", // new process application field "simplePath": "Root file.bpmn", "canonicalPath": [], "revision": 2, "type": "BPMN", "created": "2026-08-24T14:17:33.889317Z", "createdBy": { "name": "Jane Doe", "email": "jane.doe@email.com" }, "updated": "2026-08-24T14:23:48.371161659Z", "updatedBy": { "name": "Jane Doe", "email": "jane.doe@email.com" } }, { "id": "ed043868-556f-4a93-97dc-3cba1652363c", "name": "Nested file", "projectId": "fb928277-6268-44bb-b3e6-1925fa730ecf", "folderId": "cdcf3895-1061-4084-b97e-c0abaab59b6f", "processApplicationId": "e005e49a-dce8-42ee-b0db-30b1d5555ebd", // new process application field "simplePath": "Root folder/Nested file.bpmn", "canonicalPath": [ { "id": "cdcf3895-1061-4084-b97e-c0abaab59b6f", "name": "Root folder" } ], "revision": 3, "type": "BPMN", "created": "2026-08-24T14:17:33.889317Z", "createdBy": { "name": "Jane Doe", "email": "jane.doe@email.com" }, "updated": "2026-08-24T14:23:48.371161659Z", "updatedBy": { "name": "Jane Doe", "email": "jane.doe@email.com" } } ], "processApplications": [ { "id": "e005e49a-dce8-42ee-b0db-30b1d5555ebd", "name": "Process application", "projectId": "fb928277-6268-44bb-b3e6-1925fa730ecf", "created": "2026-08-24T14:17:33.889317Z", "createdBy": { "name": "Jane Doe", "email": "jane.doe@email.com" }, "updated": "2026-08-24T14:23:48.371161659Z", "updatedBy": { "name": "Jane Doe", "email": "jane.doe@email.com" } } ] } } ``` ### File paths exclude process applications In the `GET /api/v1/projects/{projectId}` response, under `content.files`, the `simplePath` and `canonicalPath` omit the container process application. Previously, the process application was included if it was on the path. Example: ```json { "metadata": { "id": "fb928277-6268-44bb-b3e6-1925fa730ecf", "name": "Project", "created": "2026-08-24T14:17:33.889317Z", "createdBy": { "name": "Jane Doe", "email": "jane.doe@email.com" }, "updated": "2026-08-24T14:23:48.371161659Z", "updatedBy": { "name": "Jane Doe", "email": "jane.doe@email.com" } }, "content": { "folders": [ { "id": "cdcf3895-1061-4084-b97e-c0abaab59b6f", "name": "Root folder", "projectId": "fb928277-6268-44bb-b3e6-1925fa730ecf", "parentId": null, "processApplicationId": "e005e49a-dce8-42ee-b0db-30b1d5555ebd", "created": "2026-08-24T14:37:04.36503Z", "createdBy": { "name": "Jane Doe", "email": "jane.doe@email.com" }, "updated": "2026-08-24T14:37:07.470563Z", "updatedBy": { "name": "Jane Doe", "email": "jane.doe@email.com" } } ], "files": [ { "id": "5a6aa24f-844b-4da2-9118-007f5c1a2df7", "name": "Root file", "projectId": "fb928277-6268-44bb-b3e6-1925fa730ecf", "folderId": null, "processApplicationId": "e005e49a-dce8-42ee-b0db-30b1d5555ebd", "simplePath": "Root file.bpmn", // excludes process applications "canonicalPath": [], // excludes process applications "revision": 2, "type": "BPMN", "created": "2026-08-24T14:17:33.889317Z", "createdBy": { "name": "Jane Doe", "email": "jane.doe@email.com" }, "updated": "2026-08-24T14:23:48.371161659Z", "updatedBy": { "name": "Jane Doe", "email": "jane.doe@email.com" } }, { "id": "ed043868-556f-4a93-97dc-3cba1652363c", "name": "Nested file", "projectId": "fb928277-6268-44bb-b3e6-1925fa730ecf", "folderId": "cdcf3895-1061-4084-b97e-c0abaab59b6f", "processApplicationId": "e005e49a-dce8-42ee-b0db-30b1d5555ebd", "simplePath": "Root folder/Nested file.bpmn", // excludes process applications "canonicalPath": [ // excludes process applications { "id": "cdcf3895-1061-4084-b97e-c0abaab59b6f", "name": "Root folder" } ], "revision": 3, "type": "BPMN", "created": "2026-08-24T14:17:33.889317Z", "createdBy": { "name": "Jane Doe", "email": "jane.doe@email.com" }, "updated": "2026-08-24T14:23:48.371161659Z", "updatedBy": { "name": "Jane Doe", "email": "jane.doe@email.com" } } ], "processApplications": [ { "id": "e005e49a-dce8-42ee-b0db-30b1d5555ebd", "name": "Process application", "projectId": "fb928277-6268-44bb-b3e6-1925fa730ecf", "created": "2026-08-24T14:17:33.889317Z", "createdBy": { "name": "Jane Doe", "email": "jane.doe@email.com" }, "updated": "2026-08-24T14:23:48.371161659Z", "updatedBy": { "name": "Jane Doe", "email": "jane.doe@email.com" } } ] } } ``` ### Project folders exclude process applications In the `GET /api/v1/projects/{projectId}` response, `content.folders` excludes process applications. Previously, process applications were included in this list. The process applications are, instead, returned in a new `content.processApplications` field. ```json { "metadata": { "id": "fb928277-6268-44bb-b3e6-1925fa730ecf", "name": "Project", "created": "2026-08-24T14:17:33.889317Z", "createdBy": { "name": "Jane Doe", "email": "jane.doe@email.com" }, "updated": "2026-08-24T14:23:48.371161659Z", "updatedBy": { "name": "Jane Doe", "email": "jane.doe@email.com" } }, "content": { "folders": [ // excludes process applications { "id": "cdcf3895-1061-4084-b97e-c0abaab59b6f", "name": "Root folder", "projectId": "fb928277-6268-44bb-b3e6-1925fa730ecf", "parentId": null, "processApplicationId": "e005e49a-dce8-42ee-b0db-30b1d5555ebd", "created": "2026-08-24T14:37:04.36503Z", "createdBy": { "name": "Jane Doe", "email": "jane.doe@email.com" }, "updated": "2026-08-24T14:37:07.470563Z", "updatedBy": { "name": "Jane Doe", "email": "jane.doe@email.com" } } ], "files": [ { "id": "5a6aa24f-844b-4da2-9118-007f5c1a2df7", "name": "Root file", "projectId": "fb928277-6268-44bb-b3e6-1925fa730ecf", "folderId": null, "processApplicationId": "e005e49a-dce8-42ee-b0db-30b1d5555ebd", "simplePath": "Root file.bpmn", "canonicalPath": [], "revision": 2, "type": "BPMN", "created": "2026-08-24T14:17:33.889317Z", "createdBy": { "name": "Jane Doe", "email": "jane.doe@email.com" }, "updated": "2026-08-24T14:23:48.371161659Z", "updatedBy": { "name": "Jane Doe", "email": "jane.doe@email.com" } }, { "id": "ed043868-556f-4a93-97dc-3cba1652363c", "name": "Nested file", "projectId": "fb928277-6268-44bb-b3e6-1925fa730ecf", "folderId": "cdcf3895-1061-4084-b97e-c0abaab59b6f", "processApplicationId": "e005e49a-dce8-42ee-b0db-30b1d5555ebd", "simplePath": "Root folder/Nested file.bpmn", "canonicalPath": [ { "id": "cdcf3895-1061-4084-b97e-c0abaab59b6f", "name": "Root folder" } ], "revision": 3, "type": "BPMN", "created": "2026-08-24T14:17:33.889317Z", "createdBy": { "name": "Jane Doe", "email": "jane.doe@email.com" }, "updated": "2026-08-24T14:23:48.371161659Z", "updatedBy": { "name": "Jane Doe", "email": "jane.doe@email.com" } } ], "processApplications": [ // new process applications list { "id": "e005e49a-dce8-42ee-b0db-30b1d5555ebd", "name": "Process application", "projectId": "fb928277-6268-44bb-b3e6-1925fa730ecf", "created": "2026-08-24T14:17:33.889317Z", "createdBy": { "name": "Jane Doe", "email": "jane.doe@email.com" }, "updated": "2026-08-24T14:23:48.371161659Z", "updatedBy": { "name": "Jane Doe", "email": "jane.doe@email.com" } } ] } } ``` ## Versions API ### Version individual process application files With `POST /api/v1/versions`, you can now publish a new version for process application files. Previously, this endpoint returned a `400 BAD REQUEST` because files were intended to be versioned as part of the process application. See [process application versioning model](/reference/announcements-release-notes/8100/whats-new-in-810.md#process-application-versioning-model) for a deeper explanation of this change. A project snapshot references each element template's last published version, not any unpublished draft changes, so creating a snapshot never creates a new element template version. ## Create a process application Create process applications with the new [`POST /api/v1/process-applications` endpoint](https://modeler.camunda.io/swagger-ui/index.html#/Process%20Applications/createProcessApplication). Previously, you could create files and folders at the project root without a process application. Now, files and folders must be stored in a process application. With this endpoint, you can create an entire file structure with the API, including the container process application. --- ## Migrate from the removed Operate API :::warning The Operate API was removed in Camunda 8.10 and is no longer part of the current documentation set. ::: For the release-level summary of this removal, see the [8.10 release announcement](/reference/announcements-release-notes/8100/8100-announcements.md#removal-of-legacy-apis-tasklist-v1-dependent-features-and-zeebe-process-test). Use the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) for current integrations, and review [migrating to the Orchestration Cluster REST API](/apis-tools/migration-manuals/migrate-to-camunda-api.md) if you still have clients that call the removed Operate API. If you need legacy Operate API behavior as migration context, use the migration manuals in the current docs rather than building new integrations against the removed endpoints. --- ## Disable sharing This API allows users to disable the sharing functionality for all reports and dashboards in Optimize. Note that this setting will be permanently persisted in memory and will take precedence over any other previous configurations (e.g. configuration files). When sharing is disabled, previously shared URLs will no longer be accessible. Upon re-enabling sharing, the previously shared URLs will work once again under the same address as before. Calling this endpoint when sharing is already disabled will have no effect. ## Method & HTTP target resource POST `api/public/share/disable` ## Request headers The following request headers must be provided with every request: | Header | Constraints | Value | | ------------- | ----------- | ------------------------------------------------------- | | Authorization | REQUIRED | See [authentication](../optimize-api-authentication.md) | ## Query parameters No query parameters necessary. ## Request body An empty request body should be sent. ## Response codes Possible HTTP Response Status codes: | Code | Description | | ---- | -------------------------------------------------------------------------------------------------------------------------- | | 204 | Request successful. | | 401 | Token incorrect or missing in HTTP header. See [authentication](../optimize-api-authentication.md) on how to authenticate. | | 500 | Some error occurred while processing the request, best check the Optimize log. | ## Example ### Disable sharing POST `api/public/share/disable` #### Request header `Authorization: Bearer ` #### Response Status 204 (Successful) #### Response content ``` no content ``` --- ## Enable sharing This API allows users to enable the sharing functionality for all reports and dashboards in Optimize. Note that this setting will be permanently persisted in memory and will take precedence over any other previous configurations (e.g. configuration files). If sharing had been previously enabled and then disabled, re-enabling sharing will allow users to access previously shared URLs under the same address as before. Calling this endpoint when sharing is already enabled will have no effect. ## Method & HTTP target resource POST `api/public/share/enable` ## Request headers The following request headers must be provided with every request: | Header | Constraints | Value | | ------------- | ----------- | ------------------------------------------------------- | | Authorization | REQUIRED | See [authentication](../optimize-api-authentication.md) | ## Query parameters No query parameters necessary. ## Request body An empty request body should be sent. ## Response codes Possible HTTP Response Status codes: | Code | Description | | ---- | -------------------------------------------------------------------------------------------------------------------------- | | 204 | Request successful. | | 401 | Token incorrect or missing in HTTP header. See [authentication](../optimize-api-authentication.md) on how to authenticate. | | 500 | Some error occurred while processing the request, best check the Optimize log. | ## Example ### Enable sharing POST `api/public/share/enable` #### Request header `Authorization: Bearer ` #### Response Status 204 (Successful) #### Response content ``` no content ``` --- ## Delete dashboards The dashboards deletion API allows you to delete dashboards by ID from Optimize. :::note Heads up! The deletion of a dashboard does not affect the referenced reports. ::: ## Method & HTTP target resource DELETE `/api/public/dashboard/{dashboard-ID}` Where `dashboard-ID` is the ID of the dashboard you wish to delete. ## Request headers The following request headers have to be provided with every delete request: | Header | Constraints | Value | | ------------- | ----------- | ------------------------------------------------------- | | Authorization | REQUIRED | See [authentication](../optimize-api-authentication.md) | ## Query parameters No query parameters available. ## Request body No request body is required. ## Result No response body. ## Response codes Possible HTTP Response status codes: | Code | Description | | ---- | -------------------------------------------------------------------------------------------------------------------------- | | 204 | Request successful. | | 401 | Token incorrect or missing in HTTP header. See [authentication](../optimize-api-authentication.md) on how to authenticate. | | 404 | The requested dashboard was not found, please check the provided dashboard-ID. | | 500 | Some error occurred while processing the request, best check the Optimize log. | ## Example ### Delete a dashboard To delete the dashboard with the ID `e6c5abb1-6a18-44e7-8480-d562d511ba62`, send the following request: DELETE `/api/public/dashboard/e6c5aaa1-6a18-44e7-8480-d562d511ba62` #### Request header `Authorization: Bearer ` #### Response Status 204. --- ## Export dashboard definitions This API allows users to export dashboard definitions which can later be imported into another Optimize system. Note that exporting a dashboard also exports all reports contained within the dashboard. The dashboards to be exported may be within a Collection or private entities, the API has access to both. The obtained list of entity exports can be imported into other Optimize systems either using the dedicated [import API](../import-entities.md) or [via UI](components/optimize/userguide/additional-features/export-import.md#importing-entities). ## Method & HTTP target resource POST `/api/public/export/dashboard/definition/json` ## Request headers The following request headers have to be provided with every request: | Header | Constraints | Value | | ------------- | ----------- | --------------------------------------------------- | | Authorization | REQUIRED | [Authentication](../optimize-api-authentication.md) | ## Query parameters No query parameters available. ## Request body The request body should contain a JSON array of dashboard IDs to be exported. ## Result The response contains a list of exported dashboard definitions as well as all report definitions contained within the dashboards. ## Response codes Possible HTTP response status codes: | Code | Description | | ---- | -------------------------------------------------------------------------------------------------------------------------- | | 204 | Request successful. | | 401 | Token incorrect or missing in HTTP header. See [authentication](../optimize-api-authentication.md) on how to authenticate. | | 404 | At least one of the given dashboard IDs does not exist. | | 500 | Some error occurred while processing the request, best check the Optimize log. | ## Example ### Export two dashboards To export the two dashboards with IDs `123` and `456`, send the following request: POST `/api/public/export/dashboard/definition/json` #### Request header `Authorization: Bearer ` #### Request body ``` [ "123", "456" ] ``` #### Response Status 200. #### Response content The response contains the two exported dashboard definitions as well as all three process reports contained within the two dashboards. ``` [ { "id": "61ae2232-51e1-4c35-b72c-c7152ba264f9", "exportEntityType": "single_process_report", "name": "Number: Process instance duration", "description": "This report shows the average instance duration", "sourceIndexVersion": 11, "collectionId": null, "data": {...} }, { "id": "625c2411-b95f-4442-936b-1976b9511d4a", "exportEntityType": "single_process_report", "name": "Heatmap: Flownode count", "description": "This report shows a heatmap of the number of instances", "sourceIndexVersion": 11, "collectionId": null, "data": {...} }, { "id": "94a7252e-d5c3-45ea-9906-75271cc0cac2", "exportEntityType": "single_process_report", "name": "Data Table: User task count", "description": "This report shows number of user tasks", "sourceIndexVersion": 11, "collectionId": null, "data": {...} }, { "id": "123", "exportEntityType": "dashboard", "name": "Dashboard 1", "description": "A dashboard showing possible automation candidates", "sourceIndexVersion": 8, "reports": [ { "id": "61ae2232-51e1-4c35-b72c-c7152ba264f9", ... }, { "id": "625c2411-b95f-4442-936b-1976b9511d4a", ... } ], "availableFilters": [...], "collectionId": null }, { "id": "456", "exportEntityType": "dashboard", "name": "Dashboard 2", "description": "A dashboard showing user task data", "sourceIndexVersion": 8, "reports": [ { "id": "94a7252e-d5c3-45ea-9906-75271cc0cac2", ... } ], "availableFilters": [...], "collectionId": null } ] ``` --- ## Get dashboard IDs This API allows users to retrieve all dashboard IDs from a given collection. ## Method & HTTP target resource GET `/api/public/dashboard` ## Request headers The following request headers have to be provided with every request: | Header | Constraints | Value | | ------------- | ----------- | --------------------------------------------------- | | Authorization | REQUIRED | [Authentication](../optimize-api-authentication.md) | ## Query parameters The following query parameters have to be provided with every request: | Parameter | Constraints | Value | | ------------ | ----------- | ----------------------------------------------------------------- | | collectionId | REQUIRED | The ID of the collection for which to retrieve the dashboard IDs. | ## Request body No request body is required. ## Result The response contains a list of IDs of the dashboards existing in the collection with the given collection ID. ## Response codes Possible HTTP response status codes: | Code | Description | | ---- | -------------------------------------------------------------------------------------------------------------------------- | | 200 | Request successful. | | 401 | Token incorrect or missing in HTTP header. See [authentication](../optimize-api-authentication.md) on how to authenticate. | | 500 | Some error occurred while processing the request, best check the Optimize log. | ## Example ### Retrieve all dashboard IDs from a collection To get all dashboard IDs in the collection with the ID `1234`, send the following request: GET `/api/public/dashboard?collectionId=1234` #### Request header `Authorization: Bearer ` #### Response Status 200. #### Response content ``` [ { "id": "9b0eb845-e8ed-4824-bd85-8cd69038f2f5" }, { "id": "1a866c7c-563e-4f6b-adf1-c4648531f7d4" } ] ``` --- ## Delete process definition data With the process definition data deletion API, you can delete all Optimize analytics data associated with a specific process definition, identified by its numeric process definition key. ## Functionality This endpoint deletes Optimize's own data (process instances and the process definition) for the given process definition key. If this is the last remaining version of the process for its tenant, it also clears any cached process definition BPMN XML from reports that reference it. It does not delete the process definition from the cluster. To remove a process definition from the cluster, use the [Delete resource](/apis-tools/orchestration-cluster-api-rest/specifications/delete-resource.api.mdx) endpoint. It also does not delete the underlying Elasticsearch or OpenSearch indices. The request is processed asynchronously. The deletion is queued until a background job performs the actual data deletion. ## Configuration In Self-Managed, enable the job registry dispatcher so queued deletion requests get processed. See [Process definition data deletion](/self-managed/components/optimize/configuration/process-definition-deletion.md) for the required configuration. ## Usage notes Deleting a process definition's data does not automatically update reports, dashboards, or alerts that reference it. Aside from clearing any cached process definition BPMN XML on a report, these entities are left as-is. Manually update or remove any reports, dashboards, or alerts that reference a deleted process definition. ## Method & HTTP target resource DELETE `/api/public/process-definition/{processDefinitionKey}` Where `processDefinitionKey` is the numeric key of the process definition whose Optimize data you want to delete. ## Request headers The following request headers have to be provided with every delete request: | Header | Constraints | Value | | ------------- | ----------- | ------------------------------------------------------ | | Authorization | REQUIRED | See [authentication](./optimize-api-authentication.md) | ## Path parameters | Parameter | Constraints | Value | | ---------------------- | ----------- | ------------------------------------------------------------- | | `processDefinitionKey` | REQUIRED | The numeric key of the process definition to delete data for. | ## Query parameters No query parameters available. ## Request body No request body is required. ## Result No response body. A `202` response confirms the deletion request was accepted and queued. ## Response codes Possible HTTP response status codes: | Code | Description | | ---- | ------------------------------------------------------------------------------------------------------------------------ | | 202 | Request accepted. The deletion has been queued for asynchronous processing. | | 400 | The provided `processDefinitionKey` is not numeric. | | 401 | Authentication credentials are incorrect or missing. See [authentication](./optimize-api-authentication.md) for details. | | 404 | No process definition was found for the provided `processDefinitionKey`. | | 409 | A deletion request for this `processDefinitionKey` is already queued. | ## Example ### Delete a process definition's data Deleting the Optimize data for the process definition with the key `2251799813685247`: DELETE `/api/public/process-definition/2251799813685247` #### Request header `Authorization: Bearer ` #### Response Status 202. --- ## External variable ingestion With the external variable ingestion API, variable data held in external systems can be ingested into Optimize directly, without the need for these variables to be present in your Camunda platform data. This can be useful when external business data, which is relevant for process analysis in Optimize, is to be associated with specific process instances. Especially if this data changes over time, it is advisable to use this REST API to persist external variable updates to Optimize, as otherwise Optimize may not be aware of data changes in the external system. ## Functionality The external variable ingestion API allows users to ingest batches of variable data which Optimize stores in a dedicated index. All variable data includes a reference to the process instance each variable belongs to, this reference then enables Optimize to import external variable data from the dedicated index to their respective process instances at regular intervals. Once Optimize has updated the process instance data, the external variables are available for report evaluations in Optimize. ## Limitations Note that external variables should be treated as separate from engine variables. If you ingest variables that are already present in the engine, engine imports may override the ingested data and vice versa, leading to unreliable report results. Similarly, if the same ingested batch contains variables with duplicate IDs, you may experience unexpected report results because Optimize will assume only one of the updates per ID and batch to be the most up to date one. Additionally, ensure the reference information (process instance ID and process definition key) is accurate, as otherwise Optimize will not be able to correctly associate variables with instance data and may create new instance indices, resulting in data which will not be usable in reports. External variables can only be ingested for process instances and will not be affected by any configured variable plugin. ## Configuration Refer to the [configuration section](../../self-managed/components/optimize/configuration/system-configuration.md) to learn more about how to set up external variable ingestion. ## Method & HTTP target resource POST `/api/ingestion/variable` ## Request headers The following request headers have to be provided with every variable ingestion request: | Header | Constraints | Value | | ------------- | ----------- | ------------------------------------------------------- | | Authorization | REQUIRED | See [authentication](./optimize-api-authentication.md). | | Content-Type | REQUIRED | `application/json` | ## Request body The request body contains an array of variable JSON Objects: | Name | Type | Constraints | Description | | -------------------- | ------ | ----------- | ------------------------------------------------------------------------------------------------- | | id | String | REQUIRED | The unique identifier of this variable. | | name | String | REQUIRED | The name of the variable. | | type | String | REQUIRED | The type of the variable. Must be one of: String, Short, Long, Double, Integer, Boolean, or Date. | | value | String | REQUIRED | The current value of the variable. | | processInstanceId | String | REQUIRED | The ID of the process instance this variable is to be associated with. | | processDefinitionKey | String | REQUIRED | The definition key of the process instance this variable is to be associated with. | ## Result This method returns no content. ## Response codes Possible HTTP response status codes: | Code | Description | | ---- | ------------------------------------------------------------------------------------------------------- | | 204 | Request successful. | | 400 | Returned if some properties in the request body are invalid or missing. | | 401 | Token incorrect or missing. See [authentication](./optimize-api-authentication.md) on how to authorize. | ## Example ### Request POST `/api/ingestion/variable` Request Body: ``` [ { "id": "7689fced-2639-4408-9de1-cf8f72769f43", "name": "address", "type": "string", "value": "Main Street 1", "processInstanceId": "c6393461-02bb-4f62-a4b7-f2f8d9bbbac1", "processDefinitionKey": "shippingProcess" }, { "id": "993f4e73-7f6a-46a6-bd45-f4f8e3470ba1", "name": "amount", "type": "integer", "value": "500", "processInstanceId": "8282ed49-2243-44df-be5e-1bf893755d8f", "processDefinitionKey": "orderProcess" } ] ``` ### Response Status 204. --- ## Health readiness The purpose of Health-Readiness REST API is to return information indicating whether Optimize is ready to be used. :::note The Health-Readiness REST API does not require an [`Authorization` header](./optimize-api-authentication.md). If a request includes one, the header is ignored. ::: ## Method & HTTP target resource GET `/api/readyz` ## Response The response is an empty body with the status code indicating the readiness of Optimize. The following responses are available: - `200`: This indicates that Optimize is ready to use. It is connected to both Elasticsearch and at least one of its configured engines. - `503`: This indicates that Optimize is not ready to use. It cannot connect to either Elasticsearch or any of its configured engines. --- ## Import entities This API allows users to import entity definitions such as reports and dashboards into existing collections. These entity definitions may be obtained either using the [report](../report/export-report-definitions/) or [dashboard](../dashboard/export-dashboard-definitions) export API or [via the UI](components/optimize/userguide/additional-features/export-import.md#exporting-entities). ## Prerequisites For importing via API, the following prerequisites must be met: - All definitions the entities require exist in the target Optimize. - The target collection, identified using the `collectionId` query parameter, must exist in the target system. - The collection data sources must include all relevant definitions for the entities. - The entity data structures match. To ensure matching data structures, confirm that the Optimize version of the source is the same as the version of the target Optimize. If any of the above conditions are not met, the import will fail with an error response; refer to the error message in the response for more information. ## Method & HTTP target resource POST `/api/public/import` ## Request headers The following request headers have to be provided with every request: | Header | Constraints | Value | | ------------- | ----------- | -------------------------------------------------- | | Authorization | REQUIRED | [Authentication](./optimize-api-authentication.md) | ## Query parameters The following query parameters have to be provided with every request: | Parameter | Constraints | Value | | ------------ | ----------- | -------------------------------------------------------------- | | collectionId | REQUIRED | The ID of the collection for which to retrieve the report IDs. | ## Request body The request body should contain a JSON array of entity definitions to be imported. These entity definitions may be obtained by using the [report](../report/export-report-definitions) or [dashboard](../dashboard/export-dashboard-definitions) export APIs or by [manually exporting entities](components/optimize/userguide/additional-features/export-import.md#exporting-entities) via the Optimize UI. ## Result The response contains a list of DTOs that specify the ID and entity type (`report` or `dashboard`) of each newly created entity in the target system. ## Response codes Possible HTTP response status codes: | Code | Description | | ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 200 | Request successful. | | 400 | The provided list of entities is invalid. This can occur if any of the above listed [prerequisites](#prerequisites) are not met. Check the `detailedMessage` of the error response for more information. | | 401 | Token incorrect or missing in HTTP header. See [authentication](./optimize-api-authentication.md) on how to authenticate. | | 404 | The given target collection ID does not exist. | | 500 | Some error occurred while processing the request, best check the Optimize log. | ## Example ### Import two entities To import a report and a dashboard into the collection with the ID `123`, send the following request: POST `/api/public/import?collectionId=123` #### Request header `Authorization: Bearer ` #### Request body ``` [ { "id": "61ae2232-51e1-4c35-b72c-c7152ba264f9", "exportEntityType": "single_process_report", "name": "Number: Process instance duration", "description": "This report shows the average instance duration", "sourceIndexVersion": 11, "collectionId": null, "data": {...} }, { "id": "b0eb845-e8ed-4824-bd85-8cd69038f2f5", "exportEntityType": "dashboard", "name": "Dashboard 1", "description": "This dashboard displays reports relating to process durations", "sourceIndexVersion": 8, "reports": [ { "id": "61ae2232-51e1-4c35-b72c-c7152ba264f9", ... } ], "availableFilters": [...], "collectionId": null } ] ``` #### Response Status 200. #### Response Content ``` [ { "id": "e8ca18b9-e637-45c8-87da-0a2b08b34d6e", "entityType": "dashboard" }, { "id": "290b3425-ba33-4fbb-b20b-a4f236036847", "entityType": "report" } ] ``` --- ## Authentication(Optimize-api) All Optimize API requests except [the health readiness](./health-readiness.md) endpoint 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](/components/saas/clusters/manage-api-clients.md#create-a-client) in Camunda Hub. 2. Add permissions to this client for **Optimize**. 3. Once you have created the client, capture the following values required to generate a token: | Name | Environment variable name | Default value | | ------------------------ | -------------------------------- | -------------------------------------------- | | Client ID | `ZEEBE_CLIENT_ID` | - | | Client Secret | `ZEEBE_CLIENT_SECRET` | - | | Authorization Server URL | `ZEEBE_AUTHORIZATION_SERVER_URL` | `https://login.cloud.camunda.io/oauth/token` | | Optimize REST Address | `CAMUNDA_OPTIMIZE_BASE_URL` | - | :::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 ${ZEEBE_AUTHORIZATION_SERVER_URL} \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'grant_type=client_credentials' \ --data-urlencode 'audience=optimize.camunda.io' \ --data-urlencode "client_id=${ZEEBE_CLIENT_ID}" \ --data-urlencode "client_secret=${ZEEBE_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. 1. [Configure the `camunda.security.authentication.oidc.audiences` setting](/self-managed/concepts/authentication/authentication-to-optimize.md#configure-oidc-for-optimize) in your Optimize installation to match the audience property of the **Optimize API** in [Management Identity](/self-managed/components/management-identity/access-management/access-management-overview.md). 2. [Add an M2M application in Management Identity](/self-managed/components/management-identity/application-user-group-role-management/applications.md). 3. [Add permissions to this application](/self-managed/components/management-identity/application-user-group-role-management/applications.md) for **Optimize API**. 4. Capture the `Client ID` and `Client Secret` from the application in Management Identity. 5. [Generate a token](/self-managed/components/management-identity/authentication.md) to access the Optimize 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 } ``` 6. Capture the value of the `access_token` property and store it as your token. Through Camunda 8.9, Self-Managed also accepted a single shared access token on this API. Camunda 8.10 no longer accepts it. The token works only if you opt into the 8.9 component-specific configuration fallback, which Camunda plans to remove in a future release. See [Optimize static API access token is no longer supported](/reference/announcements-release-notes/8100/8100-announcements.md#optimize-static-api-access-token-is-no-longer-supported). ## 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 Optimize API's ["Get dashboard IDs"](./dashboard/get-dashboard-ids.md) endpoint: :::tip The `${CAMUNDA_OPTIMIZE_BASE_URL}` variable below represents the URL of the Optimize API. You can capture this URL when creating an API client. You can also construct it as `https://${REGION}.optimize.camunda.io/${CLUSTER_ID}`. ::: :::tip The `${CAMUNDA_OPTIMIZE_BASE_URL}` variable below represents the URL of the Optimize API. You can configure this value in your Self-Managed installation. The default value is `http://localhost:8083`. ::: ```shell curl --header "Authorization: Bearer ${TOKEN}" \ -G --data-urlencode "collectionId=${COLLECTION_ID}" \ ${CAMUNDA_OPTIMIZE_BASE_URL}/api/public/dashboard ``` A successful response includes [dashboard IDs](./dashboard/get-dashboard-ids.md). For example: ```json [ { "id": "11111111-1111-1111-1111-111111111111" }, { "id": "22222222-2222-2222-2222-222222222222" } ] ``` ## 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. --- ## Optimize API ## About You can use the Optimize API to: - Retrieve, create, update, and delete reports and dashboards - Export dashboards and reports for sharing or backup - Enable or disable sharing links ## Authentication All Optimize API requests, except [the health readiness](./health-readiness.md) endpoint, require authentication. To authenticate, generate a [JSON Web Token (JWT)](https://jwt.io/introduction/) and include it in each request. For more details, see the [Authentication](./optimize-api-authentication.md) section. ## API Postman collection To get started quickly, consider using the [Postman collection](https://www.postman.com/camundateam/workspace/camunda-8-postman/collection/24684262-a1103c05-7ed8-4fd4-8716-9005583ce23a?action=share&creator=11465105). ## Usage notes Deleting a file, folder, or project via the API is immediate and cannot be undone. Use caution. ## Further resources - [Authentication](./optimize-api-authentication.md) - [Camunda Optimize documentation](/components/optimize/what-is-optimize.md) - [Postman collection](https://www.postman.com/camundateam/workspace/camunda-8-postman/collection/24684262-a1103c05-7ed8-4fd4-8716-9005583ce23a?action=share&creator=11465105) --- ## Delete reports The report deletion API allows you to delete reports by ID from Optimize. :::note Heads up! During deletion a report will get removed from any dashboard or combined process report it is referenced by. In case a report is referenced by an alert, the corresponding alert will get deleted too. ::: ## Method & HTTP target resource DELETE `/api/public/report/{report-ID}` Where `report-ID` is the ID of the report you wish to delete. ## Request headers The following request headers have to be provided with every delete request: | Header | Constraints | Value | | ------------- | ----------- | ------------------------------------------------------- | | Authorization | REQUIRED | See [authentication](../optimize-api-authentication.md) | ## Query parameters No query parameters available. ## Request body No request body is required. ## Result No response body. ## Response codes Possible HTTP response status codes: | Code | Description | | ---- | -------------------------------------------------------------------------------------------------------------------------- | | 204 | Request successful. | | 401 | Token incorrect or missing in HTTP header. See [authentication](../optimize-api-authentication.md) on how to authenticate. | | 404 | The requested report was not found, please check the provided report-ID. | | 500 | Some error occurred while processing the request, best check the Optimize log. | ## Example ### Delete a report To delete the report with the ID `e6c5abb1-6a18-44e7-8480-d562d511ba62`, send the following request: DELETE `/api/public/report/e6c5aaa1-6a18-44e7-8480-d562d511ba62` #### Request header `Authorization: Bearer ` #### Response Status 204. --- ## Export report definitions This API allows users to export report definitions which can later be imported into another Optimize system. The reports to be exported may be within a collection or private entities, the API has access to both. The obtained list of entity exports can be imported into other Optimize systems either using the dedicated [import API](../import-entities.md) or [via UI](components/optimize/userguide/additional-features/export-import.md#importing-entities). ## Method & HTTP target resource POST `/api/public/export/report/definition/json` ## Request headers The following request headers have to be provided with every request: | Header | Constraints | Value | | ------------- | ----------- | --------------------------------------------------- | | Authorization | REQUIRED | [Authentication](../optimize-api-authentication.md) | ## Query parameters No query parameters available. ## Request body The request body should contain a JSON array of report IDs to be exported. ## Result The response contains a list of exported report definitions. ## Response codes Possible HTTP response status codes: | Code | Description | | ---- | -------------------------------------------------------------------------------------------------------------------------- | | 204 | Request successful. | | 401 | Token incorrect or missing in HTTP header. See [authentication](../optimize-api-authentication.md) on how to authenticate. | | 404 | At least one of the given report IDs does not exist. | | 500 | Some error occurred while processing the request, best check the Optimize log. | ## Example ### Export two reports To export the two reports with IDs `123` and `456`, send the following request: POST `/api/public/export/report/definition/json` #### Request header `Authorization: Bearer ` #### Request body ``` [ "123", "456" ] ``` #### Response Status 200. #### Response content ``` [ { "id": "123", "exportEntityType": "single_process_report", "name": "Number: Process instance duration", "sourceIndexVersion": 8, "collectionId": "40cb3657-bdcb-459d-93ce-06877ac7244a", "data": {...} }, { "id": "456", "exportEntityType": "single_process_report", "name": "Heatmap: Flownode count", "sourceIndexVersion": 8, "collectionId": "40cb3657-bdcb-459d-93ce-06877ac7244a", "data": {...} } ] ``` --- ## Export report result data The data export API allows users to export large amounts of data in a machine-readable format (JSON) from Optimize. ## Functionality Users can export all report types (except combined process reports) from `Optimize` using the Data Export API. Moreover, raw data reports will include additional data relating to the executed flow nodes and can be exported in a paginated fashion, so that large amounts of data can be consumed in chunks by the client. ### Pagination The simplest way to paginate through the results is to perform a search request with all the `REQUIRED` header/query parameters as described in the sections below (but without `searchRequestId`), then pass the `searchRequestId` returned in each response to the next request, until no more documents are returned. Note that it's often the case, but not guaranteed, that the `searchRequestId` remains stable through the entire pagination, so always use the `searchRequestId` from the most current response to make your next request. ## Method & HTTP target resource GET `/api/public/export/report/{report-ID}/result/json` Where `report-ID` is the ID of the report you wish to export. ## Request headers The following request headers have to be provided with every data export request: | Header | Constraints | Value | | ------------- | ----------- | --------------------------------------------------- | | Authorization | REQUIRED | [Authentication](../optimize-api-authentication.md) | ## Query parameters The following query parameters have to be provided with every data export request: | Parameter | Constraints | Value | | ----------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | limit | REQUIRED | Maximum number of records per page. Please note that the limit will only be considered when performing the request for the first page of a raw data report. The following requests for a given searchRequestId will have the same page size as the first request. | | paginationTimeout | REQUIRED | The amount of time (in seconds) for which a search context will be held in memory, so that the remaining pages of the result can be retrieved. For more information on how to paginate through the results, please refer to the section [Pagination](#pagination). | | searchRequestId | Optional | The ID of a previous search for which you wish to retrieve the next page of results. For more information on how to get and use a searchRequestId please refer to the section [Pagination](#pagination). | ## Request body No request body is required. ## Result | Content | Value | | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | searchRequestId | The ID of the performed search. The following pages from this search can be retrieved by using this ID. For more information please refer to the section [Pagination](#pagination). | | numberOfRecordsInResponse | Number of records in the JSON Response. This is a number between [0, limit] | | totalNumberOfRecords | The total number of records (from all pages) for this report export | | reportId | The ID of the exported report | | message | In case there is additional information relevant to this request, this field will contain a message describing it. The response will only contain this field if there is a message to be shown | | data [Array] | An array containing numberOfRecordsInResponse report data records in JSON Format | ## Response codes Possible HTTP response status codes: | Code | Description | | ---- | -------------------------------------------------------------------------------------------------------------------------- | | 200 | Request successful. | | 400 | Returned if some of the properties from the request are invalid or missing. | | 401 | Token incorrect or missing in HTTP header. See [authentication](../optimize-api-authentication.md) on how to authenticate. | | 404 | The requested report was not found, please check the provided report-ID. | | 500 | Some error occurred while processing the export request, best check the Optimize log. | ## Example ### Export a raw data report To export the report with the ID `e6c5abb1-6a18-44e7-8480-d562d511ba62`, send the following request. It uses a maximum of two records per page and a pagination timeout of 60s. #### Initial API call GET `/api/public/export/report/e6c5aaa1-6a18-44e7-8480-d562d511ba62/result/json? paginationTimeout=60&limit=2` ##### Request header `Authorization: Bearer ` ##### Response content ``` { "searchRequestId": "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ", "numberOfRecordsInResponse": 2, "totalNumberOfRecords": 11, "reportId": "e6c5abb1-6a18-44e7-8480-d562d511ba62", "data": [ { "processDefinitionKey": "aProcess", "processDefinitionId": "aProcess:1:1801", "processInstanceId": "1809", "businessKey": "aBusinessKey", "startDate": "2021-12-02T17:21:49.330+0200", "endDate": "2021-12-02T17:21:49.330+0200", "duration": 0, "engineName": "camunda-bpm", "tenantId": null, "variables": {} }, { "processDefinitionKey": "aProcess", "processDefinitionId": "aProcess:1:1801", "processInstanceId": "1804", "businessKey": "aBusinessKey", "startDate": "2021-12-02T17:21:49.297+0200", "endDate": "2021-12-02T17:21:49.298+0200", "duration": 1, "engineName": "camunda-bpm", "tenantId": null, "variables": {} } ] } ``` ##### Response Status 200. #### Subsequent API calls Note here the use of the query parameter `searchRequestId` to retrieve further pages from the initial search. `GET /api/public/export/report/e6c5aaa1-6a18-44e7-8480-d562d511ba62/result/json?paginationTimeout=60&searchRequestId=FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ&limit=2` ##### Request header `Authorization: Bearer ` ##### Response content ``` { "searchRequestId": "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ", "numberOfRecordsInResponse": 2, "totalNumberOfRecords": 11, "reportId": "e6c5abb1-6a18-44e7-8480-d562d511ba62", "data": [ { "processDefinitionKey": "aProcess", "processDefinitionId": "aProcess:1:1bc9474d-5762-11ec-8b2c-0242ac120003", "processInstanceId": "1bdafab8-5762-11ec-8b2c-0242ac120003", "businessKey": "aBusinessKey", "startDate": "2021-12-07T15:32:22.739+0200", "endDate": "2021-12-07T15:32:22.740+0200", "duration": 1, "engineName": "camunda-bpm", "tenantId": null, "variables": {} }, { "processDefinitionKey": "aProcess", "processDefinitionId": "aProcess:1:1bc9474d-5762-11ec-8b2c-0242ac120003", "processInstanceId": "1bda3763-5762-11ec-8b2c-0242ac120003", "businessKey": "aBusinessKey", "startDate": "2021-12-07T15:32:22.735+0200", "endDate": "2021-12-07T15:32:22.735+0200", "duration": 0, "engineName": "camunda-bpm", "tenantId": null, "variables": {} } ] } ``` ##### Response Status 200. --- ## Get report IDs This API allows users to retrieve all report IDs from a given collection. ## Method & HTTP target resource GET `/api/public/report` ## Request headers The following request headers have to be provided with every request: | Header | Constraints | Value | | ------------- | ----------- | --------------------------------------------------- | | Authorization | REQUIRED | [Authentication](../optimize-api-authentication.md) | ## Query parameters The following query parameters have to be provided with every request: | Parameter | Constraints | Value | | ------------ | ----------- | -------------------------------------------------------------- | | collectionId | REQUIRED | The ID of the Collection for which to retrieve the report IDs. | ## Request body No request body is required. ## Result The response contains a list of IDs of the reports existing in the collection with the given collection ID. ## Response codes Possible HTTP response status codes: | Code | Description | | ---- | -------------------------------------------------------------------------------------------------------------------------- | | 200 | Request successful. | | 401 | Token incorrect or missing in HTTP header. See [authentication](../optimize-api-authentication.md) on how to authenticate. | | 500 | Some error occurred while processing the request, best check the Optimize log. | ## Example ### Retrieve all report IDs from a collection To get all report IDs in the collection with the ID `1234`, send the following request: GET `/api/public/report?collectionId=1234` #### Request header `Authorization: Bearer ` ##### Response Status 200. ##### Response content ``` [ { "id": "9b0eb845-e8ed-4824-bd85-8cd69038f2f5" }, { "id": "1a866c7c-563e-4f6b-adf1-c4648531f7d4" } ] ``` --- ## Tutorial(Optimize-api) In this tutorial, we'll step through examples to highlight the capabilities of the Optimize API, such as listing your existing dashboard IDs, or deleting a dashboard. ## Prerequisites - If you haven't done so already, [create a cluster](/components/saas/clusters/create-cluster.md). - Upon cluster creation, [create your first client](/components/saas/clusters/manage-api-clients.md#create-a-client). Ensure you check the `Optimize` client scope box. :::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 - You need authentication to access the API endpoints. Find more information [here](./optimize-api-authentication.md). - To properly execute the commands to list existing dashboard IDs and delete a dashboard, ensure you have [created a collection](/components/optimize/userguide/collections-dashboards-reports.md) containing a [dashboard](/components/optimize/userguide/creating-dashboards.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 `OPTIMIZE_CLIENT_ID`, `OPTIMIZE_CLIENT_SECRET`, `OPTIMIZE_BASE_URL`, and `OPTIMIZE_AUDIENCE`, which is `optimize.camunda.io` in a Camunda 8 SaaS environment. For example, your audience may be defined as `OPTIMIZE_AUDIENCE=optimize.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_CLIENT_ID`, `CAMUNDA_CLIENT_SECRET`, and `CAMUNDA_OPTIMIZE_BASE_URL`. ::: 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 list existing dashboard IDs and delete a dashboard. You can examine the framework for processing these arguments in the `cli.js` file before getting started. ::: ## GET a list of existing dashboard IDs First, let's script an API call to list our existing dashboard IDs. To do this, take the following steps: 1. In the file named `optimize.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.OPTIMIZE_CLIENT_ID, clientSecret: process.env.OPTIMIZE_CLIENT_SECRET, audience: process.env.OPTIMIZE_AUDIENCE, }; ``` 2. Examine the function `async function listDashboards([collectionId])` 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 listDashboards([collectionId]) { const accessToken = await getAccessToken(authorizationConfiguration); } ``` 4. Using your generated client credentials from [prerequisites](#prerequisites), capture your Optimize API URL beneath your call for an access token by defining `optimizeApiUrl`: `const optimizeApiUrl = process.env.OPTIMIZE_BASE_URL;` 5. On the next line, script the API endpoint to list your existing dashboard IDs for a particular collection: ```javascript const url = `${optimizeApiUrl}/api/public/dashboard?collectionId=${collectionId}`; ``` 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 collection's endpoint, process the results from the API call, emit the dashboard IDs to output, and emit an error message from the server if necessary: ```javascript try { const response = await axios(options); const results = response.data; results.forEach((x) => console.log(`ID: ${x.id}`)); } catch (error) { // Emit an error from the server. console.error(error.message); } ``` 8. In your terminal, run `node cli.js optimize list `, where `` is where you can paste the ID of your collection for a list of your existing dashboard IDs within this particular collection. If you have any existing dashboards within a collection, you will see an output similar to the following: `ID: 12345` :::note This `list` command is connected to the `listDashboards` function at the bottom of the `optimize.js` file, and executed by the `cli.js` file. While we will view dashboard IDs and delete a dashboard in this tutorial, you may add additional arguments depending on the API calls you would like to make. ::: If you have any existing dashboards, the `ID: ${x.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. ## DELETE a dashboard To delete a dashboard, capture its ID from the previous exercise and take the following steps: 1. Outline your function, similar to the steps above. Note that the URL endpoint will look different, as you are accessing a different endpoint in this request (using a dashboard ID) than in the prior request (using a collection ID): ```javascript async function deleteDashboard([dashboardId]) { console.log(`deleting dashboard ${dashboardId}`); const accessToken = await getAccessToken(authorizationConfiguration); const optimizeApiUrl = process.env.OPTIMIZE_BASE_URL; const url = `${optimizeApiUrl}/api/public/dashboard/${dashboardId}`; } ``` 2. Configure the API call using the DELETE method: ```javascript const options = { method: "DELETE", url, headers: { Accept: "application/json", Authorization: `Bearer ${accessToken}`, }, }; ``` 3. Process the results from the API call. For example: ```javascript try { // Call the delete endpoint. const response = await axios(options); // Process the results from the API call. if (response.status === 204) { console.log(`Dashboard ${clientId} was deleted!`); } else { // Emit an unexpected error message. console.error("Unable to delete dashboard!"); } } catch (error) { // Emit an error from the server. console.error(error.message); } ``` 4. In your terminal, run `node cli.js optimize delete `, where `` is where you can paste the ID of the dashboard you would like to delete. You will see a response similar to the following: `Dashboard 12345 was deleted!` ## 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 `optimize.js` file. ## Next steps You can script several additional API calls as outlined in the [Optimize API reference material](./overview.md). --- ## Variable labeling With the variable labeling endpoint, variable labels can be added, updated, and deleted from Optimize. ## Functionality The variable labeling API allows users to add, update, and delete batches of variable label data, which Optimize stores in a dedicated index. All variable label data includes a reference to the process definition each variable belongs to, which allows Optimize to display a variable's label instead of its original name anywhere the given process definition is being used. Some examples of that would be in reports, configuring filters, report grouping, dashboard filters, and event-based processes. ## Limitations Note that this feature is currently not supported in task analysis. This means that during task analysis, the original name of a variable will be displayed. ## Authentication Every request requires [authentication](./optimize-api-authentication.md). ## Method & HTTP target resource POST `/api/public/variables/labels` ## Request headers The following request headers must be provided with every variable labeling request: | Header | Constraints | Value | | ------------- | ----------- | -------------------------------------------------- | | Authorization | REQUIRED | [Authentication](./optimize-api-authentication.md) | ## Request body The request body should contain a reference to the process definition using its key, as well as an array of variable labels. Each variable label object in the array must specify the name and type of the variable for which a label is being added, as well as the value of the label itself. ## Result This method returns no content. ## Response codes Possible HTTP Response Status codes: | Code | Description | | ---- | -------------------------------------------------------------------------------------- | | 204 | Request successful. | | 400 | Returned if some of the properties in the request body are invalid or missing. | | 401 | Token incorrect or missing. See [authentication](#authentication) on how to authorize. | | 404 | The process definition with the given definition key doesn't exist. | ## Example 1 Insert three labels for three variable for a given process definition :::note If the label exists already in the index, its value will be overridden. ::: ### Request POST `/api/public/variables/labels` Request Body: ``` { "definitionKey": "bookrequest-1-tenant", "labels" : [ { "variableName": "bookAvailable", "variableType": "Boolean", "variableLabel": "book availability" }, { "variableName": "person.name", "variableType": "String", "variableLabel": "first and last name" }, { "variableName": "person.hobbies._listSize", "variableType": "Long", "variableLabel": "amount of hobbies" } ] } ``` ### Response Status 204. ## Example 2 Delete a label for a variable belonging to a given process definition by inputting an empty string for its value. If there is no label for the given variable in Elasticsearch, no operation is being conducted. ### Request POST `/api/public/variables/labels` Request Body: ``` { "definitionKey": "bookrequest-1-tenant", "labels" : [ { "variableName": "bookAvailable", "variableType": "Boolean", "variableLabel": "" } ] } ``` ### Response Status 204. ## Example 3 Insert and delete labels for two variables belonging to a given process definition. The following example adds a label for the variable with name **bookAvailable** and deletes a label for the variable with name **person.name**. ### Request POST `/api/public/variables/labels` Request Body: ``` { "definitionKey": "bookrequest-1-tenant", "labels" : [ { "variableName": "bookAvailable", "variableType": "Boolean", "variableLabel": "book availability" }, { "variableName": "person.name", "variableType": "String", "variableLabel": "" }, ] } ``` ### Response Status 204. ## Example 4 Attempting to insert multiple labels for the same variable will result to a 400 response code. ### Request POST `/api/public/variables/labels` Request Body: ``` { "definitionKey": "someProcessDefinitionKey", "labels" : [ { "variableName": "bookAvailable", "variableType": "Boolean", "variableLabel": "book availability" }, { "variableName": "bookAvailable", "variableType": "Boolean", "variableLabel": "is book available" }, ] } ``` ### Response Status 400. --- ## Orchestration Cluster MCP Server ## About The Orchestration Cluster MCP Server is an API surface of the Orchestration Cluster that exposes Camunda's operational capabilities through the [Model Context Protocol](https://modelcontextprotocol.io/) (MCP). - It enables [AI agents](/reference/glossary.md#ai-agent) and LLM-powered applications to discover and invoke Camunda tools using a standardized interface, without custom API integration code. - Similar to the [Orchestration Cluster API](../orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md), the MCP server is built into the Orchestration Cluster and shares the same [authentication](../orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md) and [authorization](/components/concepts/access-control/authorizations.md) model. It can be enabled independently. :::important Camunda 8 public API The Orchestration Cluster MCP Server is not part of the [Camunda 8 public API](/reference/public-api.md). ::: :::note This is the Orchestration Cluster MCP Server documentation. If you are looking to: - Expose your own BPMN processes as callable MCP tools for AI agents, see the [Processes MCP Server](../processes-mcp/processes-mcp-overview.md). - Connect an AI agent running in a BPMN process to an external MCP server, see the [MCP Client connector](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-client.md). ::: ### Key features Building AI-powered applications that interact with Camunda traditionally requires writing custom client code to call REST APIs, handle authentication, parse responses, and format data for AI consumption. The MCP server removes this by providing: | Benefit | Description | | :------------------ | :------------------------------------------------------------------------------------------------------------------------ | | Standardized access | AI agents discover and invoke Camunda capabilities through the MCP protocol, without bespoke integration code. | | Tool discovery | MCP clients automatically discover available tools and their schemas at runtime. | | Broad compatibility | Works with any MCP-compliant client, including VS Code (GitHub Copilot), Claude Code, Cursor, and custom AI applications. | | Consistent security | Inherits the same authentication and authorization model as the REST API. | ### Authentication The MCP server uses the same authentication model as the [Orchestration Cluster REST API](../orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md). OAuth tokens obtained for the REST API work without changes. For SaaS environments: 1. [Create API client credentials](/components/saas/clusters/manage-api-clients.md#create-a-client) in Camunda Hub. Ensure the **Orchestration Cluster API** scope is enabled. 2. Use the generated **Client ID**, **Client secret**, **OAuth token endpoint**, and **audience** to obtain an access token via the [OAuth 2.0 client credentials flow](../orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md#using-a-token-oidcjwt). 3. Pass the token in the `Authorization: Bearer ` header, or use [`c8ctl mcp-proxy`](./orchestration-cluster-api-mcp-setup.md#using-c8ctl-mcp-proxy) to handle this automatically. For the full authentication reference, including Self-Managed OIDC and basic authentication setup, see [Authentication](../orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md). ### Transport The MCP server uses the [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http) transport and is served at the `/mcp/cluster` endpoint. It is stateless and no session management is required. ## Get started :::important Camunda 8.9 The MCP server is only available from Camunda 8.9 onwards. ::: If you have a local Orchestration Cluster running with [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) or [Docker Compose](/self-managed/quickstart/developer-quickstart/docker-compose.md), the MCP server is enabled by default. Connect any MCP client using this configuration: ```json { "servers": { "camunda": { "type": "http", "url": "http://localhost:8080/mcp/cluster" } } } ``` For production environments and other deployment types, the MCP server must be explicitly enabled on your cluster before use. See [Enable and connect](./orchestration-cluster-api-mcp-setup.md) for more details. ## Available tools The MCP server exposes tools across the following domains: | Domain | Capabilities | | :------------------ | :------------------------------------------------------------------------ | | Cluster | Check cluster health and retrieve topology information. | | Incidents | Search, retrieve, and resolve incidents. | | Process definitions | Search process definitions and retrieve BPMN XML. | | Process instances | Search, retrieve, and create process instances. | | User tasks | Search, retrieve, assign, and complete user tasks. Search task variables. | | Variables | Search and retrieve variables. | For the full list of available tools, see [Available tools](./orchestration-cluster-api-mcp-tools.md). --- ## Enable and connect Enable the Orchestration Cluster MCP Server and configure MCP clients to connect. ## Enable the Orchestration Cluster MCP Server The MCP server is enabled by default in [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) and [Docker Compose](/self-managed/quickstart/developer-quickstart/docker-compose.md). For other deployment types, it must be explicitly enabled before MCP clients can connect. Depending on your deployment, enable it as follows: The MCP server is **enabled by default** in Camunda 8 Run. No additional configuration is needed. The MCP server is **enabled by default** in the Docker Compose distribution. No additional configuration is needed. Set the following [`extraConfiguration`](/self-managed/deployment/helm/configure/application-configs.md#configuration-options) value in your Helm chart values: ```yaml orchestration: extraConfiguration: - file: mcp-gateway.yaml content: | camunda: mcp: enabled: true ``` In Camunda Hub: 1. In the left navigation, under **Clusters**, select a cluster. 1. Open the **Settings** tab 1. Enable MCP support. :::info MCP server support is available on SaaS clusters running Camunda 8.9.0 or later. ::: For a full reference of MCP configuration properties, see the [property reference](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#api---mcp). ## Connect an MCP client Once the MCP server is enabled, you can connect any MCP-compliant client. The approach depends on your client's capabilities and authentication requirements. :::important When you [create API client credentials](/components/saas/clusters/manage-api-clients.md#create-a-client) in Camunda Hub, all required connection details, including the base URL, OAuth endpoint, client ID, and audience, are displayed on the credentials page. ::: ### MCP endpoint URL The MCP server is served at `/mcp/cluster` on the Orchestration Cluster. The full endpoint URL depends on your deployment type: | Deployment | MCP endpoint URL | | :------------------------- | :------------------------------------------------------------------------------ | | Camunda 8 Run | `http://localhost:8080/mcp/cluster` | | Docker Compose | `http://localhost:8080/mcp/cluster` | | SaaS – public connectivity | `https://${REGION_ID}.api.camunda.io/${CLUSTER_ID}/mcp/cluster` | | SaaS – secure connectivity | `https://${CLUSTER_ID}.${REGION_ID}.privateconnectivity.camunda.io/mcp/cluster` | | Self-Managed (custom) | `https:///mcp/cluster` | For SaaS, find your **Region Id** and **Cluster Id** in [Camunda Hub](/components/saas/clusters/manage-api-clients.md#view-connection-information). ### Direct HTTP connection If your Orchestration Cluster does not require authentication, for example, when running locally with [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) or [Docker Compose](/self-managed/quickstart/developer-quickstart/docker-compose.md), you can connect directly to the MCP server endpoint without any additional tooling. Any MCP client that supports [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http) can be used. For authenticated environments, [use c8ctl `mcp-proxy`](#use-c8ctl-mcp-proxy) instead. ```json { "servers": { "camunda": { "type": "http", "url": "http://localhost:8080/mcp/cluster" } } } ``` ### Use c8ctl `mcp-proxy` Many MCP clients, such as VS Code (GitHub Copilot) and Claude Code, do not natively support the OAuth 2.0 client credentials flow required for authenticated environments. The [c8ctl](https://github.com/camunda/c8ctl) `mcp-proxy` command bridges this gap by providing a local STDIO-to-Remote HTTP proxy that handles authentication transparently. The proxy authenticates to the MCP server using OAuth 2.0 client credentials, and exposes a local STDIO MCP interface that your client connects to. #### Prerequisites - [Node.js](https://nodejs.org/) 18 or later. - [Client credentials](/components/saas/clusters/manage-api-clients.md#create-a-client) for your Camunda cluster with the **Orchestration Cluster API** scope enabled. #### Configuration Add the following to your MCP client configuration. For example, use the following in `claude_desktop_config.json` for Claude Code: ```json { "mcpServers": { "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 (for example, the public `api.camunda.io` URL or the private `privateconnectivity.camunda.io` URL when using Secure connectivity). | | `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 Where to find these values When you [create API client credentials](/components/saas/clusters/manage-api-clients.md#create-a-client) in Camunda Hub, all required connection details are displayed on the credentials page. You can also copy a ready-to-use c8ctl configuration snippet directly from the **MCP** tab on the credentials screen. ::: For the full list of supported environment variables, see the [c8ctl documentation](https://github.com/camunda/c8ctl). ### Use with the MCP Client connectors You can also connect to the MCP server from within a BPMN process using Camunda's [MCP Client connectors](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-client.md). This allows an AI agent running in an agentic orchestration workflow to interact with Camunda's own operational data. For example, you can query incidents or start processes as part of an automated workflow. The [MCP Remote Client connector](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-remote-client-connector.md) connects to remote MCP servers over HTTP. Configure it in the properties panel with the following settings: - **Transport type**: Streamable HTTP. - **URL**: Your MCP endpoint URL (see [above](#mcp-endpoint-url)). - **Authentication**: OAuth 2.0. | Field | Value | | :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | | OAuth 2.0 token endpoint | Your OAuth token endpoint (`https://login.cloud.camunda.io/oauth/token` for SaaS). | | Client ID | Your OAuth client ID. | | Client secret | Your OAuth client secret. Use [secrets](/components/saas/clusters/manage-secrets.md) (for example, `{{secrets.MCP_CLIENT_SECRET}}`). | | Audience | The audience for your cluster API (`zeebe.camunda.io` for SaaS). | | Client authentication | Send client credentials in body. | For more details, see [MCP Remote Client connector](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-remote-client-connector.md). The [MCP Client connector](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-client-connector.md) manages persistent MCP connections through the connector runtime. Configure the Camunda MCP server as a remote HTTP client in your connector runtime configuration (for example, `application.yml`): ```yaml camunda: connector: agenticai: mcp: client: enabled: true clients: camunda-mcp: type: http http: url: https://${REGION_ID}.api.camunda.io/${CLUSTER_ID}/mcp/cluster authentication: type: oauth oauth: oauth-token-endpoint: https://login.cloud.camunda.io/oauth/token client-id: client-secret: audience: zeebe.camunda.io client-authentication: credentials-body ``` The example above shows a SaaS configuration using the public endpoint. For clusters with Secure connectivity (AWS PrivateLink), set `url` to the private MCP endpoint URL shown in Camunda Hub instead of the public `zeebe.camunda.io` host (the path still ends with `/mcp/cluster`). For local unauthenticated setups, you can omit the `authentication` block and use `http://localhost:8080/mcp/cluster` as the URL. Reference the client ID `camunda-mcp` in the MCP Client connector element template in your BPMN process. For more details, see [MCP Client connector](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-client-connector.md). --- ## Available tools The following tools are available through the Orchestration Cluster MCP server, grouped by domain. :::info Tool names, parameters, and response schemas are fully discoverable by MCP clients at runtime. The exact tool signatures may evolve across versions. ::: ## Cluster | Tool | Description | | :----------------- | :------------------------------------------------------------------------------------ | | `getClusterStatus` | Returns whether the cluster is healthy (at least one partition has a healthy leader). | | `getTopology` | Returns cluster topology including brokers, partitions, roles, health, and versions. | ## Incidents | Tool | Description | | :---------------- | :------------------------------------------------------------------------------------------------------------------------------------------ | | `searchIncidents` | Search incidents with filters such as state, error type, element ID, creation time range, process definition key, and process instance key. | | `getIncident` | Retrieve an incident by key. | | `resolveIncident` | Resolve an incident. For job-related incidents, job retries are automatically updated. | ## Process definitions | Tool | Description | | :------------------------- | :------------------------------------------------------------ | | `searchProcessDefinitions` | Search process definitions with filters such as name and key. | | `getProcessDefinition` | Retrieve a process definition by key. | | `getProcessDefinitionXml` | Retrieve the BPMN XML of a process definition. | ## Process instances | Tool | Description | | :----------------------- | :------------------------------------------------------------------------------- | | `searchProcessInstances` | Search process instances with filters. | | `getProcessInstance` | Retrieve a process instance by key. | | `createProcessInstance` | Create a new process instance, optionally with variables or awaiting completion. | ## User tasks | Tool | Description | | :------------------------ | :---------------------------------------------------------------------------------- | | `searchUserTasks` | Search user tasks with filters such as assignee, state, and process definition key. | | `getUserTask` | Retrieve a user task by key. | | `completeUserTask` | Complete a user task, optionally providing variables or a custom action. | | `assignUserTask` | Update the assignment of a user task. | | `searchUserTaskVariables` | Search variables scoped to a specific user task. | ## Variables | Tool | Description | | :---------------- | :----------------------------- | | `searchVariables` | Search variables with filters. | | `getVariable` | Retrieve a variable by key. | --- ## Intermediate tutorial Intermediate In this tutorial, we'll step through examples to highlight the capabilities of the Orchestration Cluster REST API, such as deploying resources, creating and starting a process instance, and viewing a process instance by its key. This tutorial is intended for intermediate users of the Orchestration Cluster REST API, using more sophisticated API calls and multipart requests. If you are new to the Orchestration Cluster REST API, we recommend starting with the [beginner tutorial](/apis-tools/orchestration-cluster-api-rest/tutorial.md). ## Prerequisites | Requirement | Description | | :------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Create a cluster](/components/saas/clusters/create-cluster.md) | If you haven't done so already, create a cluster. | | [Create your first client](/components/saas/clusters/manage-api-clients.md#create-a-client) | Upon cluster creation, create your first client. Ensure you check the `Orchestration Cluster API` client scope box. 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. | | Clone the [GitHub repository](https://github.com/camunda/camunda-api-tutorials) | In this tutorial, we use a JavaScript-written [GitHub repository](https://github.com/camunda/camunda-api-tutorials) to write and run requests. Clone this repo before getting started. | | Prepare resources | The first request we will run is a request to deploy one or more resources (such as processes, decision models, or forms). For the purposes of this tutorial, we have preconfigured a BPMN diagram and converted this into XML. This diagram, `calculate-sales-tax.bpmn`, can be found in the GitHub repository above within the `resources` folder. The BPMN diagram itself represents a process to calculate the total sales tax for a given purchase. You can take a closer look at this diagram by opening it in [Camunda Hub](/components/hub/workspace/modeler/index.md) or [Desktop Modeler](/components/modeler/desktop-modeler/index.md). | | [Node.js](https://nodejs.org/en/download) | 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. | | Authenticate | You need authentication to access the API endpoints. Find more information in [Camunda 8 authentication](./orchestration-cluster-api-rest-authentication.md), and the section below. | ### 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. Add the following environment variables: - `CAMUNDA_CLIENT_ID` - `CAMUNDA_CLIENT_SECRET` - `CAMUNDA_REST_ADDRESS` (after creating a client and downloading the .env variables, this is reflected in the [Hub UI](/components/saas/clusters/manage-api-clients.md#create-a-client) as `ZEEBE_REST_ADDRESS`) - `CAMUNDA_TOKEN_AUDIENCE` (represented as `ZEEBE_TOKEN_AUDIENCE` in the [Hub UI](/components/saas/clusters/manage-api-clients.md#create-a-client)), which is `zeebe.camunda.io` in a Camunda 8 SaaS environment. For example, your audience may be defined as `CAMUNDA_TOKEN_AUDIENCE=zeebe.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). See 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 deploy a resource, create and start a process instance, and view a process instance by its key. You can examine the framework for processing these arguments in the `cli.js` file before getting started. ::: ## Deploy resources (POST) First, let's script an API call to deploy a resource. To do this, take the following steps: 1. In the file named `camunda-process-instances.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.CAMUNDA_CLIENT_ID, clientSecret: process.env.CAMUNDA_CLIENT_SECRET, // These settings come from your .env file. Note that CAMUNDA_TOKEN_AUDIENCE is represented by ZEEBE_TOKEN_AUDIENCE in the Hub UI. audience: process.env.CAMUNDA_TOKEN_AUDIENCE, }; ``` 2. Examine the function `async function deployResources()` below this configuration. This is where you will script out your API call. 3. Within the function, you must first generate an access token for this request, so your function should now look like the following: ```javascript async function deployResources() { const accessToken = await getAccessToken(authorizationConfiguration); } ``` 4. Using your generated client credentials from [prerequisites](#prerequisites), capture your Orchestration Cluster REST API URL beneath your call for an access token by defining `camundaApiUrl`: ```javascript const camundaApiUrl = process.env.CAMUNDA_REST_ADDRESS; ``` 5. On the next line, script the API endpoint to deploy the resources: ```javascript const url = `${camundaApiUrl}/deployments`; ``` 6. We will now configure the variables representing the BPMN file and its form data. This may look different depending on which resources you choose to deploy, but reflects the block-scoped local variables and append method to insert a set of objects for the BPMN resource of this tutorial: ```javascript const formData = new FormData(); // Read the BPMN file and add it to the form data const bpmnFilePath = path.resolve("resources/calculate-sales-tax.bpmn"); const fileContent = fs.readFileSync(bpmnFilePath); formData.append("resources", fileContent, { filename: "calculate-sales-tax.bpmn", contentType: "application/xml", }); ``` :::note The `resources` name must be exact according to the API requirements, the path to the file (`const bpmnFilePath = path.resolve("resources/calculate-sales-tax.bpmn");`) must be correct, and `contentType` must be `application/xml` to ensure the upload will not fail. ::: 7. Call the endpoint, process the results from the API call, and emit an error message from the server if necessary: ```javascript try { const response = await axios.post(url, formData, { headers: { Accept: "application/json", Authorization: `Bearer ${accessToken}`, ...formData.getHeaders(), }, }); const deployedResources = response.data.deployments || []; // Emit deployed resources deployedResources.forEach((x) => console.log( `Process Definition Key: ${x.processDefinition.processDefinitionKey}; Process Definition Id: ${x.processDefinition.processDefinitionId}` ) ); } catch (error) { // Emit an error from the server. console.error(`Error deploying resources: ${error.message}`); } ``` 8. In your terminal, run `node cli.js processInstances deploy`. :::note This `deploy` command is connected to the `deployResources` function at the bottom of the `camunda-process-instances.js` file, and executed by the `cli.js` file. While we will work with process instances in this tutorial, you may add additional arguments depending on the API requests you want to make. ::: The existing process definition key and 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. ## Create and start a process instance (POST) To create and start a process instance based on the process instance key obtained in the request above, take the following steps: 1. Outline your function, similar to the steps above: ```javascript async function createInstance([processDefinitionKey]) { const accessToken = await getAccessToken(authorizationConfiguration); const camundaApiUrl = process.env.CAMUNDA_REST_ADDRESS; const url = `${camundaApiUrl}/process-instances`; } ``` 2. Build the payload you will send to the endpoint: ```javascript const payload = { processDefinitionKey, variables: { total: 90.0, }, }; ``` :::note The request will succeed if the variable names are different, but the process instance itself will not function as expected. ::: 3. Call the endpoint, process the results from the API call, and emit an error message from the server if necessary: ```javascript try { const response = await axios.post(url, payload, { headers: { Accept: "application/json", Authorization: `Bearer ${accessToken}`, }, }); const processInstance = response.data; console.log(`Process Instance Key: ${processInstance.processInstanceKey}`); } catch (error) { console.error(`Error creating process instance: ${error.message}`); } ``` 4. In your terminal, run `node cli.js processInstances create `, where `` is the process definition key. The `processInstanceKey` will now display in the output. Capture this key for a future method. ## Retrieve a process instance (GET) To retrieve a process instance by the process instance key, take the following steps: 1. Outline your function, similar to the steps above: ```javascript async function viewInstance([processInstanceKey]) { const accessToken = await getAccessToken(authorizationConfiguration); const camundaApiUrl = process.env.CAMUNDA_REST_ADDRESS; const url = `${camundaApiUrl}/process-instances/${processInstanceKey}`; } ``` 2. Call the endpoint, process the results from the API call, and emit an error message from the server if necessary: ```javascript try { const response = await axios.get(url, { headers: { Accept: "application/json", Authorization: `Bearer ${accessToken}`, }, }); const results = response.data; console.log( `Process instance name: ${results.processDefinitionName}; State: ${results.state};` ); } catch (error) { console.error(`Error retrieving process instance: ${error.message}`); } ``` 3. In your terminal, run `node cli.js processInstances view `, where `` is the process instance key. The `processDefinitionName` and `state` will then display in the output. ## Troubleshooting 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 `camunda-process-instances.js` file. ## Next steps You can script several additional API calls as outlined in the [Orchestration Cluster REST API reference material](./orchestration-cluster-api-rest-overview.md). --- ## Authentication(Orchestration-cluster-api-rest) This page explains how to authenticate requests to the Orchestration Cluster REST API across different deployment environments. ## Authentication support matrix | Distribution | Default Authentication | No auth support | Basic auth support | OIDC-based auth support | | --------------------------------------------------------------------------------- | ---------------------- | ----------------------- | ------------------ | ----------------------- | | [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) | None | ✅ (default) | ✅ (when enabled) | ✅ (when configured) | | [Docker Compose](/self-managed/quickstart/developer-quickstart/docker-compose.md) | None | ✅ (default) | ✅ (when enabled) | ✅ (when configured) | | [Helm](/self-managed/deployment/helm/install/quick-install.md) | Basic Auth | ✅ (when auth disabled) | ✅ (default) | ✅ (when configured) | | SaaS | OIDC-based Auth | ❌ | ❌ | ✅ (required) | :::info Authentication vs. authorization Authentication establishes who is calling the Orchestration Cluster REST API (for example, using basic authentication or an OIDC access token). Authorization determines what that caller can do, based on authorizations configured in Admin. To learn more about authorization resources, permissions, and precedence (including user task permissions), see [Orchestration Cluster authorization](../../components/concepts/access-control/authorizations.md). ::: ## Authenticate API calls ### No authentication (local development) By default, Camunda 8 Run and Docker Compose expose the Orchestration Cluster REST API without authentication for local development. You can make API requests directly: ```shell curl http://localhost:8080/v2/topology ``` ### Basic Authentication Basic Authentication uses username and password credentials. **For Camunda 8 Run:** Enable Basic Auth by configuring authentication in your `application.yaml`. See [Camunda 8 Run documentation](/self-managed/quickstart/developer-quickstart/c8run/configuration.md#enable-authentication-and-authorization) for details. **For Helm:** Basic Auth is enabled by default for the Orchestration Cluster API. Include your username and password in each API request: ```shell curl --user username:password \ http://localhost:8080/v2/topology ``` :::note Basic Authentication checks the password with every request, limiting the number of requests per second. It may not be suitable for production. See [Camunda components troubleshooting](/self-managed/operational-guides/troubleshooting.md) ::: ## Using a token (OIDC/JWT) OIDC-based authentication is recommended for production and required for SaaS. Obtain an access token and pass it as an OAuth 2.0 Bearer Token in the `Authorization` header of each request. The token's subject (user or client) must also have the required authorizations. Otherwise, requests fail with `403 Forbidden` even if authentication succeeds. 1. [Create client credentials](/components/saas/clusters/manage-api-clients.md#create-a-client) in Camunda Hub. 2. Request an access token using the credentials: ```shell curl --request POST ${CAMUNDA_OAUTH_URL} \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'grant_type=client_credentials' \ --data-urlencode "audience=${CAMUNDA_TOKEN_AUDIENCE}" \ --data-urlencode "client_id=${CAMUNDA_CLIENT_ID}" \ --data-urlencode "client_secret=${CAMUNDA_CLIENT_SECRET}" ``` 3. Use the access token from the response in your API requests: ```shell curl --header "Authorization: Bearer ${ACCESS_TOKEN}" \ ${BASE_URL}/topology ``` **Prerequisites for OIDC-based authentication** - Your Orchestration Cluster must already be configured with your Identity Provider. See [Set up OIDC-based Authentication](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md). - You must have a registered client in your IdP with a **client ID**, **client secret**, and authorization endpoint. - Note the configured **audience** and **scope** for token requests (variables `OC_AUDIENCE` and `SCOPE`). Depends on IdP configuration. **Request an access token using client credentials** Example for Keycloak; adjust the authorization URI and parameters for your IdP: ```shell curl --location --request POST 'http:///auth/realms//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 "audience=${OC_AUDIENCE}" \ --data-urlencode "scope=${SCOPE}" \ --data-urlencode 'grant_type=client_credentials' ``` > **Microsoft Entra ID**: Use `scope=${SCOPE}/.default` instead of `scope=${SCOPE}`. The Authorization URI is typically `https://login.microsoftonline.com//oauth2/v2.0/token`. **Use the access token in API requests** ```shell curl --header "Authorization: Bearer ${ACCESS_TOKEN}" \ ${BASE_URL}/topology ``` ### OIDC-based authentication using X.509 client certificates For advanced security scenarios, you can obtain OIDC access tokens using X.509 client certificates. This is typically required in Self-Managed environments where your IdP enforces mutual TLS (mTLS). **For Java applications** The Java client supports automatic OIDC access token retrieval using X.509 client certificates. Configure the necessary keystore and truststore via code or environment variables. See [Java client authentication](../java-client/getting-started.md#oidc-access-token-authentication-with-x509-client-certificate) for details. **For other clients** Refer to your IdP documentation for obtaining tokens using X.509 certificates. ### Automatic token management in official clients Official Camunda clients (Java client or Spring Boot Starter) handle token acquisition and renewal automatically. You do not need to manually obtain or refresh tokens. ### Troubleshooting - Check logs for authentication errors. - Verify your access token includes the correct audience if audience validation is enabled. ### Learn more - [Camunda Java client authentication and token management](../java-client/getting-started.md) - [Camunda Spring Boot Starter: Configuring the Camunda 8 connection](../camunda-spring-boot-starter/getting-started.md#configuring-the-camunda-8-connection) - [Orchestration Cluster authorization: Resources, permissions, and configuration](../../components/concepts/access-control/authorizations.md) --- ## Data fetching The Orchestration Cluster REST API allows you to retrieve data from key resources like process definitions, user tasks, users, and tenants. Each search-enabled endpoint supports rich filtering, sorting, and pagination so you can quickly find the data that matters most. The sections below explain how to structure a search request and interpret the response format. ## Searchable resources The following examples support search via POST endpoints, each with its own set of filterable fields: - Process instances (`POST /v2/process-instances/search`) - User tasks (`POST /v2/user-tasks/search`) - Users (`POST /v2/users/search`) - Batch operations (`POST /v2/batch-operations/search`) Refer to the [interactive Orchestration Cluster REST API Explorer](./specifications/orchestration-cluster-api.info.mdx) for the full attribute lists. ## Supported operations Most searchable resources allow: - Filtering based on properties or variables - Sorting results - Paginating with either offset or cursor methods - Accessing nested resources (e.g., group users) > Example: You can search for groups using `POST /v2/groups/search`, and for the users in a group using `POST /v2/groups/:groupId/users/search`. You can also fetch single resources using `GET` endpoints with unique identifiers, such as: ```shell GET /v2/user-tasks/:userTaskKey ``` ## Data consistency Endpoints in the Orchestration Cluster API are classified as either **strongly consistent** or **eventually consistent**. This distinction applies to the _data behind the endpoint_, not the endpoint's functionality itself. - **Strongly consistent endpoints** return data that reflects the real-time state of the system. - **Eventually consistent endpoints** return data exported by the [Camunda Exporter](../../self-managed/components/orchestration-cluster/zeebe/exporters/camunda-exporter.md). This data may lag behind the real-time state until the exporter processes it, so it becomes consistent only after a delay. Each endpoint is clearly labeled with its consistency type so you can account for this behavior in your applications. ### Why consistency matters If eventual consistency is not handled properly, it can lead to unexpected results. For example: 1. A resource is created using a strongly consistent endpoint. 2. An _immediate_ request to an eventually consistent endpoint for the same resource might return: - `404 Not Found` for a `GET` request, or - an empty result set for a search request. This happens because the eventually consistent endpoint has not yet synced the new data. A later request will return the correct result once the data export completes. If your application does not account for eventual consistency, you may encounter **non-deterministic runtime behavior**. Code paths that work reliably during development or testing may fail intermittently in production, especially under load, if this characteristic is ignored. ## User task support The Orchestration Cluster REST API only supports Camunda user tasks (previously referred to as [Zeebe user tasks](../migration-manuals/migrate-to-camunda-user-tasks.md), which may still appear as `zeebe:userTask` in your XML content). ## Search requests Search requests consist of the components for **filter**, **sort**, and **page**. ### Filter The filter object defines which fields should match. Only items that match the given fields will be returned. The available fields vary by object and are described in the respective search endpoint. Filtering by a unique identifier is usually available in filtering options. Beyond that, the filter options don’t have to comprise all the returned items’ attributes.
Example ``` POST /v2/user-tasks/search { "filter": { "assignee": "demo", "processInstanceKey": "22456786958" } } ``` This filters by the attributes `assignee` and `processInstanceKey`, looking for exact matches with the provided values.
### Sort The sort array specifies by which `field`s to sort the result items and whether this happens in ascending (ASC) or descending (DESC) `order`.
Example ``` POST /v2/user-tasks/search { "sort": [ { "field": "state", "order": "ASC" } ] } ``` This sorts the overall result set by the `state` attribute in ascending order.
### Page The page object details how to slice the result set. An initial search request can omit the page object or define the `limit`. This specifies the maximum number of results to retrieve per request. Subsequent requests can either use **cursor** or **offset pagination** to iterate through the result set. Cursor pagination bases on the value of the [search response's](#search-responses) `startCursor` and `endCursor`. Copy `startCursor` into `before` or `endCursor` into `after` to page through results respectively. The [search example](#search-example) showcases how to use these attributes for cursor pagination. Offset pagination uses the `from` attribute to define the starting point of the next set of items in the overall result set. :::note Choosing the right pagination type depends on the specific use case. The expected result set size and intended usage of the results have the biggest influence. The expected reliability and performance of the search request affect this decision as well. Consider using cursor pagination for larger result sets and displaying result list that scroll infinitely. Paged result sets can be realized with offset pagination in a straightforward way but come with performance penalties for larger result sets. :::
Example ``` POST /v2/user-tasks/search { "page": { "limit": 3 } } ``` This limits the result set returned in the response to 3 items, no matter how many overall results exist.
### Advanced search filters To provide an easy yet expressive way for users to search for and filter resources, search requests can contain more advanced filter criteria than fields being _equal_ to a target value. For example, this allows searching using logical (and, in) and comparison operators (greater than, less than). The list of generally supported advanced filter operators is described below. The supported operators depend on the endpoint and the type of the filter attribute. All endpoints document available operators for each attribute in the Orchestration Cluster REST API specification. #### Conditional Operators | Operator | Syntax | Description | | --------- | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `$eq` | `field: { "$eq": value }` | Filter where `field` is equal to `value`. Abbreviated form `field: value` is also allowed. | | `$neq` | `field: { "$neq": value }` | Filter where `field` is not equal to `value`. | | `$exists` | `field: { "$exists": value }` | Filter where `field` does or does not exist. The `value` is a boolean and can be either `true` or `false`. | | `$gt` | `field: { "$gt": value }` | Filter where `field` is greater than `value`. | | `$gte` | `field: { "$gte": value }` | Filter where `field` is greater than or equal to `value`. | | `$lt` | `field: { "$lt": value }` | Filter where `field` is less than `value`. | | `$lte` | `field: { "$lte": value }` | Filter where `field` is less than or equal to `value`. | | `$like` | `field: { "$like": value }` | Filter where `field` contains a string like `value`. The wildcard characters `*` (zero, one, or multiple characters) and `?` (a single character) are allowed in `value`. They can be escaped with a backslash, like in `my \*`. | | `$in` | `field: { "$in": [ value1, value2, ... ] }` | Filter where `field` is equal to at least one of the `value`s in the provided array. | | `$notIn` | `field: { "$notIn": [ value1, value2, ... ] }` | Filter where `field` is not equal to any one of the `value`s in the provided array. |
Example ``` POST /v2/user-tasks/search { "filter": { "candidateGroups": { "$like": "external-*", "$neq": "external-supervisor" } } } ``` This filters by `candidateGroups` that start with `"external-"` but do not match `"external-supervisor"`.
#### Logical Operators | Operator | Syntax | Description | | -------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | `$or` | `"$or": [ { condition1 }, { condition2 }, ... ]` | Filter where at least one of the conditions is true. | | and | `{ field: { "$lt": value1 }, field: { "$gt": value2 }, ... }` | All conditions outside of `$or` operators will be considered as combined by an `AND` operator. There is no explicit operator. |
Example ``` POST /v2/user-tasks/search { "filter": { "assignee": "demo", "processInstanceKey": "22456786958", "candidateGroups": { "$neq": "external-supervisor", "$like": "external-*" } } } ``` The top-level filters `assignee`, `processInstanceKey`, and `candidateGroups` are connected by an AND operator. Likewise, the `$neq` and `$like` advanced filter operators inside the top-level `candidateGroups` filter are combined by an AND operator.
#### Variables Search endpoints can support filtering by variable values. This allows querying for process-related resources based on the values of specific variables that exist in their respective scope. For example, user task search supports filtering using the `localVariables` array and defining filter criteria for specific variables. For variable values, the advanced filter criteria outlined above for fields apply.
Example ``` POST /v2/user-tasks/search { "filter": { "localVariables" : [ { "name": "orderVolume", "value": "10000" }, { "name": "price", "value": { "$lt": "500" } }, { "name": "skipped", "value": { "$exists": false } } ] } } ``` This filters for user tasks containing at least the variables `orderVolume` with a value of `10000` and `price` with a value lower than `500`, not containing variable `skipped`.
## Search responses Search responses consist of two components: **`items`** and **`page`**. - The **`items`** array contains instances of the respective endpoint’s resource. The structure and attributes of these instances vary by endpoint and are detailed in the corresponding endpoint documentation. - The **`page`** object includes pagination details for navigating through results in subsequent search requests: - **`totalItems`**: Indicates the total number of results for the query. > **Note:** In Elasticsearch/OpenSearch, this value is capped at **10,000**, even if more results are available. - **`startCursor`**: A reference to the **first** entry on the current page. Use this value in the `before` parameter to page **backward** in a subsequent [search request](#search-requests). - **`endCursor`**: A reference to the **last** entry on the current page. Use this value in the `after` parameter to page **forward** in a subsequent [search request](#search-requests).
Example ``` { "items": [ { "state": "CREATED", "processInstanceKey": "22456786958", "userTaskKey": "22456786345", ... }, { "state": "CREATED", "processInstanceKey": "22456786958", "userTaskKey": "22456786456", ... }, { "state": "COMPLETED", "processInstanceKey": "22456786958", "userTaskKey": "22456786678", ... } ], "page": { "totalItems": 345, "startCursor": "jfenj8vhekgj98uzfafhu7", "endCursor": "negbkjeh84tzh4gk0kwegj" } } ```
## Search example Querying for the first three user tasks with certain criteria and sorted by state could look as follows: ``` POST /v2/user-tasks/search { "filter": { "assignee": "demo", "processInstanceKey": "22456786958", "candidateGroups": { "$like": "external-*", "$neq": "external-supervisor" }, "localVariables" : [ { "name": "orderVolume", "value": "10000" }, { "name": "price", "value": { "$lt": "500" } }, { "name": "skipped", "value": { "$exists": false } } ], }, "sort": [ { "field": "state", "order": "ASC" } ], "page": { "limit": 3 } } ``` This could yield the following example result: ``` 200 OK { "items": [ { "state": "CREATED", "processInstanceKey": "22456786958", "userTaskKey": "22456786345", ... }, { "state": "CREATED", "processInstanceKey": "22456786958", "userTaskKey": "22456786456", ... }, { "state": "COMPLETED", "processInstanceKey": "22456786958", "userTaskKey": "22456786678", ... } ], "page": { "totalItems": 345, "startCursor": "jfenj8vhekgj98uzfafhu7", "endCursor": "negbkjeh84tzh4gk0kwegj" } } ``` A follow-up request to receive the next three items could then look as follows: ``` POST /v2/user-tasks/search { "filter": { "assignee": "demo", "processInstanceKey": "22456786958", "candidateGroups": { "$like": "external-*", "$neq": "external-supervisor" }, "localVariables" : [ { "name": "orderVolume", "value": "10000" }, { "name": "price", "value": { "$lt": "500" } }, { "name": "skipped", "value": { "$exists": false } } ], }, "sort": [ { "field": "state", "order": "ASC" } ], "page": { "limit": 3, "after": "negbkjeh84tzh4gk0kwegj" } } ``` This yields the next three user task instances after the last one from the first search request’s result. --- ## Orchestration Cluster REST API ## About You can use the Orchestration Cluster REST API to interact programmatically with process orchestration capabilities in Camunda 8. For example, you can start, manage, and query process instances, complete user tasks, resolve incidents, and manage variables. Use this API to: | Use case | Description | | :------------------------------------------ | :-------------------------------------------------------------------------------- | | Build process-driven applications | Create applications to orchestrate processes and integrate with existing systems. | | Integrate user tasks into custom UIs | Build task management interfaces that connect to Camunda's user task engine. | | Start and monitor external system processes | Trigger process instances and track progress from any application or service. | ## Key features This API is designed to make it easy to [find resources](./orchestration-cluster-api-rest-data-fetching.md#advanced-search-filters) with a consistent experience, while ensuring all endpoints are secure with [authentication](./orchestration-cluster-api-rest-authentication.md) and fine-grained [resource authorization](/components/concepts/access-control/authorizations.md). Key features include: | Feature | Description | | :-------------------------------- | :---------------------------------------------------- | | Full process lifecycle management | Deploy, start, and monitor BPMN processes. | | User task operations | Claim, complete, and manage human tasks. | | Variable management | Read and update process variables. | | Incident resolution | Handle and resolve process incidents. | | Advanced search and filtering | Query process data with powerful search capabilities. | :::info - This API is part of the Camunda 8 [public API](/reference/public-api.md) and is covered by our SemVer stability guarantees (except for clearly marked alpha endpoints). You can rely on backward compatibility for production use. - To learn more about the Orchestration Cluster, see [Orchestration Cluster](/components/orchestration-cluster.md). - In Self-Managed clusters running [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md), most endpoints below are addressed per tenant by prefixing the path with `/physical-tenants/{physicalTenantId}`, while cluster-wide operations use the `/cluster/v2/...` prefix instead. See [API routing for Physical Tenants](/self-managed/concepts/physical-tenants/api-routing.md). ::: ## Getting started This section helps you get up and running in minutes. ### Prerequisites - **A Camunda 8 Orchestration Cluster** - For local development, use [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) or [Docker Compose](/self-managed/quickstart/developer-quickstart/docker-compose.md), which expose the API without requiring credentials or tokens by default. - For production or advanced development, use [Helm/Kubernetes](/self-managed/deployment/helm/install/quick-install.md) or [manual installation](/self-managed/deployment/manual/install.md). - Alternatively, sign up for a free [Camunda 8 SaaS trial](https://accounts.camunda.io/signup) to get a managed cluster with the API enabled. - **A client to send API requests** - Quick testing: Use the [Swagger](../orchestration-cluster-api-rest-swagger) interface - Programmatic access: Use the [Java client](/apis-tools/java-client/getting-started.md) or [Camunda Spring Boot Starter](/apis-tools/camunda-spring-boot-starter/getting-started.md) - Custom client: [Download the OpenAPI spec](https://github.com/camunda/camunda/blob/main/zeebe/gateway-protocol/src/main/proto/rest-api.yaml) to generate your own client - Universal client: [Postman collection](https://www.postman.com/camundateam/camunda-8-postman/collection/apl78x9/camunda-8-api-rest) ### Authentication Authentication for the Orchestration Cluster REST API depends on your environment and how you deploy Camunda 8. Authenticate your Client requests based on your setup. **Supported authentication methods** - No authentication – For local development only - Basic authentication – Username/password for simple setups - OIDC-based authentication – Use OAuth2/OIDC tokens for production environments **Quick reference** - See the [authentication support matrix](./orchestration-cluster-api-rest-authentication.md#authentication-support-matrix) for details on supported methods by deployment type - If you're using the Java or Spring clients, token management is handled automatically. See [client authentication configuration](../camunda-spring-boot-starter/getting-started.md#configuring-the-camunda-8-connection) For detailed authentication setup, follow the step-by-step guide in [Authentication](./orchestration-cluster-api-rest-authentication.md) based on your deployment type. ### Test your connection Once you're set up, verify your connection works by making your first API call: #### Using curl Local (Camunda 8 Run / Docker Compose): ```bash curl http://localhost:8080/v2/topology ``` SaaS, public connectivity: ```bash curl https://${REGION_ID}.api.camunda.io/${CLUSTER_ID}/v2/topology ``` SaaS, secure connectivity (AWS PrivateLink): ```bash curl https://${CLUSTER_ID}.${REGION_ID}.privateconnectivity.camunda.io/api/v2/topology ``` Replace the placeholders with the values for your environment. See [base URLs](#base-urls) for details on SaaS (public and secure connectivity) and self-managed setups. #### Using Postman Try the [get cluster topology](https://www.postman.com/camundateam/camunda-8-postman/request/en495q6/get-cluster-typology) request or browse the full collection. This request returns information about your cluster topology, confirming that your setup is working correctly. ### Try your first workflow If you're just getting started with process automation, try this simple workflow: 1. **Model a process** – Create a simple BPMN process with a user task using [Camunda Hub](/components/hub/workspace/modeler/index.md) or [Desktop Modeler](/components/modeler/desktop-modeler/index.md) 2. **Deploy the process** – Use [`POST /deployments`](./specifications/create-deployment.api.mdx) to deploy your BPMN file 3. **Start a process instance** – Use [`POST /process-instances`](./specifications/create-process-instance.api.mdx) to create a new process instance 4. **Complete a user task** – Use [`POST /user-tasks/{userTaskKey}/completion`](./specifications/complete-user-task.api.mdx) to complete the task For a complete walkthrough with code examples, see our [getting started tutorial](/guides/getting-started-example.md). ### Explore the API ## API reference This section covers the technical details and conventions you need to understand when working with the Orchestration Cluster REST API. ### Base URLs #### SaaS In Camunda Hub: 1. In the left navigation, under **Clusters**, select a cluster. 1. Under **Cluster Details**, find your **Region Id** and **Cluster Id**. For public connectivity (default), use this pattern as your `${BASE_URL}`: `https://${REGION_ID}.api.camunda.io/${CLUSTER_ID}/v2/` For secure connectivity (AWS PrivateLink), use the private base URL shown in Hub. For the Orchestration Cluster REST API, the pattern is: `${BASE_URL} = https://${CLUSTER_ID}.${REGION_ID}.privateconnectivity.camunda.io/api/v2/` For example: `https://b4102386-6818-43c6-a880-d21c968a883f.ork-1.privateconnectivity.camunda.io/api/v2/topology` #### Self-Managed Use the host and path defined for your [Zeebe Gateway](/reference/glossary.md#zeebe-gateway). For Ingress and routing details, see the [configuration guide](/self-managed/deployment/helm/configure/ingress/ingress-setup.md). If you're using the default setup, the `${BASE_URL}` is `http://localhost:8080/v2/`. ### Versioning Camunda uses semantic versioning (SemVer) to ensure API changes are predictable and compatible. This helps you upgrade safely without unexpected breaking changes. The API version is determined by the API version number (`v2`) and the product version—for example, `POST /v2/user-tasks/search` in Camunda 8.8.0. Camunda versions the entire API rather than individual endpoints. If a breaking change occurs in any endpoint, the entire API is versioned. During migration periods, multiple API versions may coexist—for example, both `v2` and `v3` versions of `/user-tasks/search` may be available in the same release. :::note Adding new endpoints or attributes to existing responses is **not** considered a breaking change. ::: ### Request size limits The default maximum request size for all requests (for example, to `POST /v2/deployments`) is 4MB. It can be configured in the Zeebe Gateway configuration using the `maxMessageSize` property. For more information, see the [Zeebe Gateway configuration reference](/self-managed/components/orchestration-cluster/zeebe/configuration/configuration.md#gateway-configuration). ### Naming conventions Naming across the Orchestration Cluster REST API is simple, intuitive, and consistent to reduce friction when working with multiple endpoints. The conventions include: - **Nouns over verbs** – e.g., `assignment` instead of `assign` - **Plural terms** for top-level resources – e.g., `user-tasks` - **Kebab-case** for multiple words in path parameters – e.g., `user-tasks` - **camelCase** for multiple words in query parameters – e.g., `userTaskKey` These conventions are illustrated in the following endpoint example: `POST /user-tasks/{userTaskKey}/assignment` For IDs or similar short 2- or 3-letter words or acronyms, Camunda only capitalizes the first letter. If standalone, all letters are lowercase. | Term | Usage | | ---- | ------------------------------------------ | | ID | `id` (standalone) or `processDefinitionId` | | URL | `url` (standalone) or `externalUrl` | | UUID | `uuid` (standalone) or `clusterUuid` | Identifiers follow a naming convention for parameters and data attributes alike: - Unique technical identifiers are suffixed with **key**, for example, `userTaskKey`, `processInstanceKey`, or `userKey`. These are usually numeric values. - Other identifiers, such as those copied from the BPMN XML, are typically suffixed with **id**, for example, `processDefinitionId`. - Key and id fields contain the entity as a prefix, for example, `userTaskKey` or `processDefinitionId`. This applies when referencing other resources like `formKey` in the user task entity and the respective entities themselves like `userTaskKey` in the user task entity. - The full entity name is used as the prefix to avoid confusion, for example, `processDefinitionKey` instead of `processKey`, which could be interpreted as a process instance or process definition. - Other entity attributes do not have a prefix to avoid clutter, such as `version` in the process definition entity. However, references to other resources require a prefix, like `processDefinitionVersion` in the process instance entity. ### HTTP status codes & error handling The Orchestration Cluster REST API uses standard HTTP status codes and returns error responses following the [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) format. Each error response includes the following fields: - `type`: A URI identifier for the error type - `status`: The HTTP status code (e.g., 400, 404) - `title`: A short, human-readable summary of the error - `detail`: A detailed explanation of the issue - `instance`: A URI reference identifying the specific occurrence of the problem #### Common error codes | Error code | Meaning | | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 200 | OK | | 204 | No content | | 400 | Bad request. Generic error with further description in the problem detail. | | 401 | Unauthorized. The client is not authenticated. Retry with a modified authorization header. | | 403 | Forbidden. The client has insufficient permissions for the request. | | 404 | Not found | | 409 | Conflict. The request attempts to modify a resource that is not in the correct state. | | 412 | Precondition failed. The client should check the cluster status. | | 500 | Internal server error. Generic error with further description in the problem detail. | | 503 | The service is currently unavailable. This may happen when the system signals backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism [here](../../components/zeebe/technical-concepts/internal-processing.md) | ### Date formats Date values in the Orchestration Cluster REST API follow the [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339) notation. This applies to all requests and responses. The endpoints validate requests and transform responses accordingly. ### Variables Variables in the Orchestration Cluster REST API are JSON objects, where `key` defines the variable name and `value` specifies the variable value. For full details on variable filtering and structure, see [search requests](orchestration-cluster-api-rest-data-fetching.md#variables). ## What's next? Now that you're familiar with the Orchestration Cluster REST API, here are some useful next steps: - [Build a job worker using the Spring SDK](../camunda-spring-boot-starter/getting-started.md) - [Test your process definitions using Camunda Process Test](../testing/getting-started.md) - [Migrate from v1 component REST APIs to the v2 Orchestration Cluster REST API](../migration-manuals/migrate-to-camunda-api.md) - [Download the OpenAPI spec](https://github.com/camunda/camunda/blob/main/zeebe/gateway-protocol/src/main/proto/rest-api.yaml) to generate a client or explore the raw schema --- ## Secrets(Orchestration-cluster-api-rest) `POST /v2/secrets/resolve` and `POST /v2/secrets/list` use a shared request and response contract that differs from most of the Orchestration Cluster REST API. For the complete request and response schema for each endpoint, see [Resolve secrets](./specifications/resolve-secrets.api.mdx) and [List secrets](./specifications/list-secrets.api.mdx). To understand what a `camunda.secrets.` reference is and how Camunda resolves it elsewhere in the cluster, see [Secret resolution](../../components/concepts/secret-resolution.md). For Java client usage, see [Secrets](../java-client/secrets.md). ## Understand resolve responses `POST /v2/secrets/resolve` accepts a batch of references and resolves each reference independently. A structurally valid request always returns HTTP 200, even if every reference in the batch fails. Successfully resolved references are returned in `resolved`. References that cannot be resolved are returned in `errors`. A failure for one reference does not affect the others in the same batch. ### Review per-reference errors Each entry in `errors` uses one of the following codes: | Code | Meaning | | ------------------- | ----------------------------------------------------------------------------- | | `NOT_FOUND` | No secret exists for this reference. | | `ACCESS_DENIED` | The caller lacks `SECRET:REVEAL` for this reference. | | `INVALID_REFERENCE` | The reference is malformed or exceeds the length limit. | | `UNREADABLE` | The store contains a secret for this reference, but its value cannot be read. | `UNREADABLE` does not mean that the secret is absent. Handle it differently from `NOT_FOUND` when responding to a failure. ### Check batch limits A request can contain up to 20 references, with a maximum length of 256 characters per reference. The server deduplicates duplicate references within the same request and resolves each unique reference once. ### Understand HTTP 400 responses `POST /v2/secrets/resolve` returns HTTP 400 only when the request itself is malformed. Examples include: - A missing or non-array `references` field. - More than 20 references. - A `null` entry. A well-formed request that fails to resolve every reference still returns HTTP 200 with `errors` populated. ## List secrets `POST /v2/secrets/list` returns the names of references the caller is authorized to view. It never returns secret values. Unauthorized references are omitted from the response rather than causing the request to fail. The request body is optional and can be empty. The endpoint does not currently support filtering or pagination. Pagination is marked in the OpenAPI specification as a pre-GA follow-up. ## Grant required permissions Resolving a reference requires `SECRET:REVEAL`. Listing requires `SECRET:READ`. Neither permission grants the other. See [Authorizations](../../components/concepts/access-control/authorizations.md) for details about permissions on the `SECRET` resource and who receives them by default. --- ## Try with Swagger The Orchestration Cluster REST API is documented using the [OpenAPI specification](https://github.com/camunda/camunda/blob/main/zeebe/gateway-protocol/src/main/proto/rest-api.yaml). You can explore, test, and interact with all API endpoints directly using [Swagger UI](https://swagger.io/tools/swagger-ui/) - an interactive documentation interface. **Use Swagger UI to:** - Explore available endpoints - Browse all REST API operations with detailed parameter descriptions - Test API calls interactively - Execute real API requests directly from your browser - Understand request/response formats - See example payloads and response schemas - Authenticate and authorise - Test with your actual credentials in a secure environment Swagger UI is particularly useful for API discovery, development, and testing workflows before integrating the API into your applications. ## Prerequisites Before using Swagger UI, ensure you have: - **A running Camunda 8 Orchestration Cluster:** SaaS or Self-Managed - **Appropriate [access permissions](../../components/concepts/access-control/authorizations.md)** to the resources you want to manage via the API (if authorizations are enabled). ## Accessing Swagger UI ### SaaS For SaaS clusters, Swagger UI is accessible through your cluster's dedicated endpoint. 1. Open Camunda Hub. 1. In the left navigation under **Clusters**, select a cluster. 1. If the cluster is paused, you must [resume](/components/saas/clusters/manage-cluster.md#resume-a-cluster) it before accessing the Swagger UI. 1. Under **Cluster Details**, click **Open Swagger UI**. :::note Swagger UI is protected with CSRF. If you are logged into Camunda Hub, you can access Swagger UI directly. If not, you may need to log in first. ::: ### Self-Managed For Self-Managed deployments, Swagger UI is available at your configured [Zeebe Gateway](/reference/glossary.md#zeebe-gateway) endpoint. **Default setup:** `http://localhost:8080/swagger` **Custom configuration:** Use the host and path defined for your Zeebe Gateway in the [configuration guide](/self-managed/deployment/helm/configure/ingress/ingress-setup.md), then append `/swagger`. **Example with custom domain:** `https://your-zeebe-gateway.company.com/swagger` ## Authentication in Swagger UI Swagger UI supports the same authentication methods as the REST API. Choose the method that matches your deployment: ### Automatic authentication - **Session-based authentication**: If you're already logged into Camunda (for example, through Operate), Swagger UI automatically authenticates you using your session cookie ### Manual authentication Click the **Authorize** button in Swagger UI to manually configure authentication: #### Bearer Token (Recommended for production) 1. Click **Authorize** in Swagger UI 2. In the **Bearer** section, enter your JWT access token 3. Click **Authorize** to apply **To obtain a Bearer token:** - **SaaS**: Follow the [OIDC-based Authentication guide](./orchestration-cluster-api-rest-authentication.md#using-a-token-oidcjwt) for SaaS - **Self-Managed**: Follow the [OIDC-based Authentication guide](./orchestration-cluster-api-rest-authentication.md#using-a-token-oidcjwt) for Self-Managed #### Basic Authentication 1. Click **Authorize** in Swagger UI 2. In the **Basic** section, enter your username and password 3. Click **Authorize** to apply **Note:** Basic Authentication is only available for Self-Managed deployments. For detailed authentication setup instructions, see the [Authentication guide](./orchestration-cluster-api-rest-authentication.md). ## Using Swagger UI effectively ### Making your first API call 1. **Test connectivity**: Try the `GET /topology` endpoint to verify your connection and authentication 2. **Explore endpoints**: Browse the available operations organized by category (processes, user tasks, variables, etc.) 3. **Try sample requests**: Click "Try it out" on any endpoint to see the request form 4. **Execute requests**: Fill in parameters and click "Execute" to see real responses ### Understanding the interface - **Endpoints are grouped by resource type** - Find process-related operations under "Process Instances", task operations under "User Tasks", etc. - **Required parameters are marked** - Look for the red asterisk (\*) next to required fields - **Example values are provided** - Use the "Example Value" links to populate request bodies quickly - **Response schemas are documented** - Expand the response sections to understand the data structure ### Testing workflows Use Swagger UI to test complete workflows: 1. **Deploy a process** - Use `POST /deployments` to upload a BPMN file 2. **Start a process instance** - Use `POST /process-instances` with your process definition 3. **Query and manage** - Use search endpoints to find and interact with your data 4. **Complete tasks** - Use `POST /user-tasks/{userTaskKey}/completion` to progress workflows ## Managing Swagger UI availability ### SaaS Control Swagger UI access in [Camunda Hub](/components/saas/clusters/settings.md): 1. Open Camunda Hub. 1. In the left navigation under **Clusters**, select a cluster. 1. Click the **Settings** tab. 1. Toggle **Enable Swagger** on or off. 1. Changes apply automatically to your orchestration cluster. ### Self-Managed Configure Swagger UI availability using environment variables: **Enable Swagger UI (default):** ```bash CAMUNDA_REST_SWAGGER_ENABLED=true ``` **Disable Swagger UI:** ```bash CAMUNDA_REST_SWAGGER_ENABLED=false ``` **Alternative property format:** ```yaml camunda: rest: swagger: enabled: true ``` **Security consideration:** In production environments, consider disabling Swagger UI and using it only in development environments. ## Next steps After exploring the API with Swagger UI: - **Build production integrations** using the [Java client](/apis-tools/java-client/getting-started.md) or [Camunda Spring Boot Starter](/apis-tools/camunda-spring-boot-starter/getting-started.md) - **Review the complete API reference** in the [Overview](./orchestration-cluster-api-rest-overview.md) - **Set up proper authentication** following the [Authentication guide](./orchestration-cluster-api-rest-authentication.md) - **Learn advanced querying** with [Data Fetching and Search](./orchestration-cluster-api-rest-data-fetching.md) - **Download the OpenAPI specification** for [custom client generation](https://github.com/camunda/camunda/blob/main/zeebe/gateway-protocol/src/main/proto/rest-api.yaml) **Need help?** Try the [Getting Started Tutorial](/guides/getting-started-example.md) for a complete workflow walkthrough, or browse the [Postman collection](https://www.postman.com/camundateam/camunda-8-postman/collection/apl78x9/camunda-8-api-rest) for additional examples. --- ## Beginner tutorial Beginner In this tutorial, we'll step through examples to highlight the capabilities of the Orchestration Cluster REST API, such as listing all roles, creating a role, retrieving a role, and deleting a role. ## Prerequisites - If you haven't done so already, [create a cluster](/components/saas/clusters/create-cluster.md). - Upon cluster creation, [create your first client](/components/saas/clusters/manage-api-clients.md#create-a-client). Ensure you check the `Orchestration Cluster API` client scope box. :::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 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 - You need authentication to access the API endpoints. Find more information [here](./orchestration-cluster-api-rest-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 the following: - `CAMUNDA_CLIENT_ID` - `CAMUNDA_CLIENT_SECRET` - `CAMUNDA_REST_ADDRESS` (after creating a client and downloading the .env variables, this is reflected in the [Hub UI](/components/saas/clusters/manage-api-clients.md#create-a-client) as `ZEEBE_REST_ADDRESS`) - `CAMUNDA_TOKEN_AUDIENCE` (represented as `ZEEBE_TOKEN_AUDIENCE` in the [Hub UI](/components/saas/clusters/manage-api-clients.md#create-a-client)), which is `zeebe.camunda.io` in a Camunda 8 SaaS environment. For example, your audience may be defined as `CAMUNDA_TOKEN_AUDIENCE=zeebe.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). 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 list all roles, create a role, retrieve a role, and delete a role. You can examine the framework for processing these arguments in the `cli.js` file before getting started. ::: ## List all roles (POST) First, let's script an API call to list all existing roles. To do this, take the following steps: 1. In the file named `camunda-8.js`, outline the authentication 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.CAMUNDA_CLIENT_ID, clientSecret: process.env.CAMUNDA_CLIENT_SECRET, audience: process.env.CAMUNDA_TOKEN_AUDIENCE, }; ``` 2. Examine the function `async function listRoles()` below this configuration. This is where you will script out your API call. 3. Within the function, you must first generate an access token for this request, so your function should now look like the following: ```javascript async function listRoles() { const accessToken = await getAccessToken(authorizationConfiguration); } ``` 4. Using your generated client credentials from [prerequisites](#prerequisites), capture your Orchestration Cluster REST API URL beneath your call for an access token by defining `camundaApiUrl`: ```javascript const camundaApiUrl = process.env.CAMUNDA_REST_ADDRESS; ``` On the next line, script the API endpoint to list the existing roles: ```javascript const url = `${camundaApiUrl}/roles/search`; ``` 5. Configure your POST request to the appropriate endpoint, including an authorization header based on the previously acquired `accessToken`: ```javascript const options = { method: "POST", url, headers: { Accept: "application/json", Authorization: `Bearer ${accessToken}`, }, data: {}, }; ``` 6. Call the endpoint, process the results from the API call, and emit an error message from the server if necessary: ```javascript try { const response = await axios(options); const results = response.data; results.items.forEach((x) => console.log(`Role Name: ${x.name}; key: ${x.key}`) ); } catch (error) { console.error(error.message); } ``` 7. In your terminal, run `node cli.js camunda8 list`. :::note This `list` command is connected to the `listRoles` function at the bottom of the `camunda-8.js` file, and executed by the `cli.js` file. While we will work with roles in this tutorial, you may add additional arguments depending on the API calls you would like to make. ::: The existing roles (if any) 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. If no action is provided, it will default to "assign" everywhere, except when unassigning a user. ## Create a role (POST) 1. Outline your function: ```javascript async function createRole([roleName]) { const accessToken = await getAccessToken(authorizationConfiguration); const camundaApiUrl = process.env.CAMUNDA_REST_ADDRESS; const url = `${camundaApiUrl}/roles`; } ``` 2. Configure the API call: ```javascript const options = { method: "POST", url, headers: { Accept: "application/json", Authorization: `Bearer ${accessToken}`, }, data: { name: roleName, }, }; ``` 3. Process the results: ```javascript try { const response = await axios(options); const newRole = response.data; console.log(`Role added! Name: ${roleName}. Key: ${newRole.roleKey}.`); } catch (error) { console.error(error.message); } ``` 4. Run in your terminal `node cli.js camunda8 create `. ## Retrieve a role (GET) 1. Outline your function: ```javascript async function getRole([roleKey]) { const accessToken = await getAccessToken(authorizationConfiguration); const camundaApiUrl = process.env.CAMUNDA_REST_ADDRESS; const url = `${camundaApiUrl}/roles/${roleKey}`; } ``` 2. Configure the API call. ```javascript const options = { method: "GET", url, headers: { Accept: "application/json", Authorization: `Bearer ${accessToken}`, }, }; ``` 3. Process the results: ```javascript try { const response = await axios(options); const results = response.data; console.log(`Role Name: ${results.name}; Key: ${results.key};`); } catch (error) { console.error(error.message); } ``` 4. Run in your terminal `node cli.js camunda8 view `. ## Delete a role (DELETE) 1. Outline your function: ```javascript async function deleteRole([roleKey]) { const accessToken = await getAccessToken(authorizationConfiguration); const camundaApiUrl = process.env.CAMUNDA_REST_ADDRESS; const url = `${camundaApiUrl}/roles/${roleKey}`; } ``` 2. Configure the API call: ```javascript const options = { method: "DELETE", url, headers: { Accept: "application/json", Authorization: `Bearer ${accessToken}`, }, }; ``` 3. Process the results: ```javascript try { const response = await axios(options); if (response.status === 204) { console.log("Role deleted!"); } else { console.error("Unable to delete this role!"); } } catch (error) { console.error(error.message); } ``` 4. Run in your terminal `node cli.js camunda8 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 `camunda-8.js` file. ## Next steps You can script several additional API calls as outlined in the [Orchestration Cluster REST API reference material](./orchestration-cluster-api-rest-overview.md). --- ## Accessing every operation :::caution Technical Preview The PHP SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: Every one of the 244 API operations is exposed as a method directly on the client — the flat facade — so you rarely need to reach for an API group: ```php function readme_flat_facade(): void { $client = CamundaClient::fromEnvironment(); $instruction = new ProcessInstanceCreationInstructionById([ 'processDefinitionId' => 'order-process', ]); $topology = $client->getTopology(); $result = $client->createProcessInstance($instruction); $async = CamundaAsyncClient::fromEnvironment(); $async->getTopology() ->then(static function ($asyncTopology): void { // handle the asynchronous topology response }) ->wait(); } ``` Beyond the ergonomic helpers and the flat facade, every API group is also reachable through the typed `api()` accessor: ```php function readme_api_accessor(): void { $client = CamundaClient::fromEnvironment(); $instruction = new ProcessInstanceCreationInstructionById([ 'processDefinitionId' => 'order-process', ]); $processInstances = $client->api(ProcessInstanceApi::class); $result = $processInstances->createProcessInstance($instruction); } ``` See the [`examples/`](https://github.com/camunda/orchestration-cluster-api-php/tree/main/examples/) directory for compilable, static-analysed usage of every REST API operation. --- ## Advanced runnable examples :::caution Technical Preview The PHP SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: The [`examples/advanced/`](https://github.com/camunda/orchestration-cluster-api-php/tree/main/examples/advanced/) directory contains self-verifying local-cluster scenarios for a test drive, resilient worker behavior, idempotent message correlation, and PHP process-forking. --- ## Ad Hoc Sub Process Api `AdHocSubProcessApi` — 1 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\AdHocSubProcessApi::class)`. - `activateAdHocSubProcessActivities()` --- ## Agent Definition Api `AgentDefinitionApi` — 2 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\AgentDefinitionApi::class)`. - `getAgentDefinition()` - `searchAgentDefinitions()` --- ## Agent Instance Api `AgentInstanceApi` — 5 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\AgentInstanceApi::class)`. - `createAgentInstance()` - `getAgentInstance()` - `searchAgentInstanceHistory()` - `searchAgentInstances()` - `updateAgentInstance()` --- ## Audit Log Api `AuditLogApi` — 2 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\AuditLogApi::class)`. - `getAuditLog()` - `searchAuditLogs()` --- ## Authentication Api `AuthenticationApi` — 2 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\AuthenticationApi::class)`. - `getAuthentication()` - `searchOwnAuthorizations()` --- ## Authorization Api `AuthorizationApi` — 5 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\AuthorizationApi::class)`. - `createAuthorization()` - `deleteAuthorization()` - `getAuthorization()` - `searchAuthorizations()` - `updateAuthorization()` --- ## Backup Api `BackupApi` — 22 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\BackupApi::class)`. - `deleteHistoryBackup()` - `deleteHistoryBackupAsClusterAdmin()` - `deleteRuntimeBackup()` - `deleteRuntimeBackupAsClusterAdmin()` - `deleteRuntimeBackupState()` - `deleteRuntimeBackupStateAsClusterAdmin()` - `getHistoryBackup()` - `getHistoryBackupAsClusterAdmin()` - `getRuntimeBackup()` - `getRuntimeBackupAsClusterAdmin()` - `getRuntimeBackupState()` - `getRuntimeBackupStateAsClusterAdmin()` - `listHistoryBackups()` - `listHistoryBackupsAsClusterAdmin()` - `listRuntimeBackups()` - `listRuntimeBackupsAsClusterAdmin()` - `syncRuntimeBackupState()` - `syncRuntimeBackupStateAsClusterAdmin()` - `takeHistoryBackup()` - `takeHistoryBackupAsClusterAdmin()` - `takeRuntimeBackup()` - `takeRuntimeBackupAsClusterAdmin()` --- ## Batch Operation Api `BatchOperationApi` — 6 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\BatchOperationApi::class)`. - `cancelBatchOperation()` - `getBatchOperation()` - `resumeBatchOperation()` - `searchBatchOperationItems()` - `searchBatchOperations()` - `suspendBatchOperation()` --- ## Clock Api `ClockApi` — 2 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\ClockApi::class)`. - `pinClock()` - `resetClock()` --- ## Cluster Api `ClusterApi` — 8 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\ClusterApi::class)`. - `cancelClusterRebalance()` - `getClusterRebalance()` - `getClusterStatus()` - `getClusterTopology()` - `getClusterUpgradeStatus()` - `getStatus()` - `getTopology()` - `triggerClusterRebalance()` --- ## Cluster Variable Api `ClusterVariableApi` — 9 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\ClusterVariableApi::class)`. - `createGlobalClusterVariable()` - `createTenantClusterVariable()` - `deleteGlobalClusterVariable()` - `deleteTenantClusterVariable()` - `getGlobalClusterVariable()` - `getTenantClusterVariable()` - `searchClusterVariables()` - `updateGlobalClusterVariable()` - `updateTenantClusterVariable()` --- ## Conditional Api `ConditionalApi` — 1 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\ConditionalApi::class)`. - `evaluateConditionals()` --- ## Decision Definition Api `DecisionDefinitionApi` — 4 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\DecisionDefinitionApi::class)`. - `evaluateDecision()` - `getDecisionDefinition()` - `getDecisionDefinitionXML()` - `searchDecisionDefinitions()` --- ## Decision Instance Api `DecisionInstanceApi` — 4 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\DecisionInstanceApi::class)`. - `deleteDecisionInstance()` - `deleteDecisionInstancesBatchOperation()` - `getDecisionInstance()` - `searchDecisionInstances()` --- ## Decision Requirements Api `DecisionRequirementsApi` — 3 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\DecisionRequirementsApi::class)`. - `getDecisionRequirements()` - `getDecisionRequirementsXML()` - `searchDecisionRequirements()` --- ## Document Api `DocumentApi` — 5 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\DocumentApi::class)`. - `createDocument()` - `createDocumentLink()` - `createDocuments()` - `deleteDocument()` - `getDocument()` --- ## Domain type system Each identifier is a distinct, self-validating value object in the `Camunda\Orchestration\Semantic` namespace. Concrete identifier types implement `Stringable` and `JsonSerializable` and expose `::of()`, `->value()`, and `->equals()`. `ResourceKey` and `ScopeKey` are factories that lift a raw string into the matching concrete type via `::of()`. | Type | Pattern | Length | Description | | ------------------------------- | --------------------------------------------------------------------------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `AgentDefinitionKey` | /^-?[0-9]+$/u | 1–25 | System-generated key for an agent definition. | | `AgentHistoryItemKey` | /^-?[0-9]+$/u | 1–25 | System-generated key for an agent history item. | | `AgentInstanceKey` | /^-?[0-9]+$/u | 1–25 | System-generated key for an agent instance. | | `AuditLogEntityKey` | — | — | System-generated entity key for an audit log entry. | | `AuditLogKey` | /^-?[0-9]+$/u | 1–25 | System-generated key for an audit log entry. | | `AuthorizationKey` | /^-?[0-9]+$/u | 1–25 | System-generated key for an authorization. | | `BatchOperationKey` | — | — | System-generated key for an batch operation. | | `BusinessId` | — | 1–256 | An optional, user-defined string identifier that identifies the process instance within the scope of a process definition (scoped by tenant). If provided and uniqueness enforcement is enabled, the engine will reject creation if another root process instance with the same business id is already active for the same process definition. Note that any active child process instances with the same business id are not taken into account. | | `ClientId` | /^[a-zA-Z0-9_~@.+-]+$/u | 1–256 | The unique identifier of an OAuth client. Minted outside the Camunda REST API: in SaaS by Console, in Self-Managed with OIDC by the external identity provider (e.g. EntraID, Keycloak, Okta). In Self-Managed with Basic authentication, machine-to-machine applications are modelled as users instead — see the user identifier. | | `ClusterVariableName` | /^[a-zA-Z0-9_~@.+-]+$/u | 1–256 | The name of a cluster variable. Unique within its scope (global or tenant-specific). | | `ConditionalEvaluationKey` | /^-?[0-9]+$/u | 1–25 | System-generated key for a conditional evaluation. | | `DecisionDefinitionId` | /^[\p{L}_][\p{L}\p{N}_\-\.]*$/u | ≥ 1 | Id of a decision definition, from the model. Only ids of decision definitions that are deployed are useful. | | `DecisionDefinitionKey` | /^-?[0-9]+$/u | 1–25 | System-generated key for a decision definition. | | `DecisionEvaluationInstanceKey` | /^[0-9]+-[0-9]+$/u | 3–30 | System-generated identifier for a decision evaluation instance. It is composed of the parent decision evaluation key and the 1-based index of the evaluated decision within that evaluation, joined by a hyphen (format: `-`). | | `DecisionEvaluationKey` | /^-?[0-9]+$/u | 1–25 | System-generated key for a decision evaluation. | | `DecisionInstanceKey` | /^-?[0-9]+$/u | 1–25 | System-generated key for a deployed decision instance. | | `DecisionRequirementsKey` | /^-?[0-9]+$/u | 1–25 | System-generated key for a deployed decision requirements definition. | | `DeploymentKey` | /^-?[0-9]+$/u | 1–25 | Key for a deployment. | | `DocumentId` | — | — | Document Id that uniquely identifies a document. | | `ElementId` | — | — | The model-defined id of an element. | | `ElementInstanceKey` | /^-?[0-9]+$/u | 1–25 | System-generated key for a element instance. | | `EndCursor` | /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}(?:==)?|[A-Za-z0-9+\/]{3}=)?$/u | — | The end cursor in a search query result set. | | `FormId` | — | — | The user-defined id for the form | | `FormKey` | /^-?[0-9]+$/u | 1–25 | System-generated key for a deployed form. | | `GlobalListenerId` | /^[a-zA-Z0-9_~@.+\-]+$/u | 1–256 | The user-defined id for the global listener | | `GroupId` | — | 1–256 | The unique identifier of a group. | | `HistoryItemId` | — | 1–256 | The client-supplied identifier this item was created with. | | `IncidentKey` | /^-?[0-9]+$/u | 1–25 | System-generated key for a incident. | | `JobKey` | /^-?[0-9]+$/u | 1–25 | System-generated key for a job. | | `JobLeaseToken` | — | ≥ 1 | An opaque, engine-minted fencing token identifying a single activation of a job. Returned by Activate Jobs as `ActivatedJobResult.jobLeaseToken` when the job is activated with a lease, and passed back under the same name on fenced job commands and on agent-instance creation/updates, to prove the caller holds the current lease. The token is opaque: clients may rely on its presence and equality only, and must never construct, parse, or otherwise interpret it beyond equality checks. It cannot be minted client-side; only the engine produces it, exactly once per leased activation, and clients must not depend on any particular internal format. | | `MappingRuleId` | /^[a-zA-Z0-9_~@.+-]+$/u | 1–256 | The unique identifier of a mapping rule. | | `MessageKey` | /^-?[0-9]+$/u | 1–25 | System-generated key for an message. | | `MessageSubscriptionKey` | /^-?[0-9]+$/u | 1–25 | System-generated key for a message subscription. | | `ProcessDefinitionId` | /^[\p{L}_][\p{L}\p{N}_\-\.]*$/u | ≥ 1 | Id of a process definition, from the model. Only ids of process definitions that are deployed are useful. | | `ProcessDefinitionKey` | /^-?[0-9]+$/u | 1–25 | System-generated key for a deployed process definition. | | `ProcessInstanceKey` | /^-?[0-9]+$/u | 1–25 | System-generated key for a process instance. | | `ResourceKey` | — | — | Semantic identifier that accepts any of: ProcessDefinitionKey, DecisionRequirementsKey, FormKey, DecisionDefinitionKey. | | `RoleId` | /^[a-zA-Z0-9_~@.+-]+$/u | 1–256 | The unique identifier of a role. | | `ScopeKey` | — | — | Semantic identifier that accepts any of: ProcessInstanceKey, ElementInstanceKey. | | `SignalKey` | /^-?[0-9]+$/u | 1–25 | System-generated key for an signal. | | `StartCursor` | /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}(?:==)?|[A-Za-z0-9+\/]{3}=)?$/u | — | The start cursor in a search query result set. | | `Tag` | /^[A-Za-z][A-Za-z0-9_\-:.]{0,99}$/u | 1–100 | A tag. Needs to start with a letter; then alphanumerics, `_`, `-`, `:`, or `.`; length ≤ 100. | | `TenantId` | /^(<default>|[\w\.\-]{1,31})$/u | 1–31 | The unique identifier of the tenant. | | `UserTaskKey` | /^-?[0-9]+$/u | 1–25 | System-generated key for a user task. | | `Username` | /^[a-zA-Z0-9_~@.+-]+$/u | 1–256 | The unique name of a user. | | `VariableKey` | /^-?[0-9]+$/u | 1–25 | System-generated key for a variable. | --- ## Element Instance Api `ElementInstanceApi` — 5 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\ElementInstanceApi::class)`. - `createElementInstanceVariables()` - `getElementInstance()` - `searchElementInstanceIncidents()` - `searchElementInstanceWaitStates()` - `searchElementInstances()` --- ## Exporting Api `ExportingApi` — 6 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\ExportingApi::class)`. - `getClusterExportingStatus()` - `getExportingStatus()` - `pauseClusterExporting()` - `pauseExporting()` - `resumeClusterExporting()` - `resumeExporting()` --- ## Expression Api `ExpressionApi` — 1 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\ExpressionApi::class)`. - `evaluateExpression()` --- ## Form Api `FormApi` — 1 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\FormApi::class)`. - `getFormByKey()` --- ## Global Listener Api `GlobalListenerApi` — 5 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\GlobalListenerApi::class)`. - `createGlobalTaskListener()` - `deleteGlobalTaskListener()` - `getGlobalTaskListener()` - `searchGlobalTaskListeners()` - `updateGlobalTaskListener()` --- ## Group Api `GroupApi` — 15 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\GroupApi::class)`. - `assignClientToGroup()` - `assignMappingRuleToGroup()` - `assignUserToGroup()` - `createGroup()` - `deleteGroup()` - `getGroup()` - `searchClientsForGroup()` - `searchGroups()` - `searchMappingRulesForGroup()` - `searchRolesForGroup()` - `searchUsersForGroup()` - `unassignClientFromGroup()` - `unassignMappingRuleFromGroup()` - `unassignUserFromGroup()` - `updateGroup()` --- ## Incident Api `IncidentApi` — 5 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\IncidentApi::class)`. - `getIncident()` - `getProcessInstanceStatisticsByDefinition()` - `getProcessInstanceStatisticsByError()` - `resolveIncident()` - `searchIncidents()` --- ## Overview # API reference :::caution Technical Preview The PHP SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: The SDK exposes **244 operations** across **41 API groups**. Every operation is available directly on the client (the flat facade) and through the typed `api()` accessor: ```php $client = CamundaClient::fromEnvironment(); // Flat facade — every operation is a method on the client: $topology = $client->getTopology(); // Or via the typed API-group accessor: $jobs = $client->api(\Camunda\Orchestration\Api\Api\JobApi::class); ``` | API group | Operations | | --------------------------------------------------------- | ---------- | | [Ad Hoc Sub Process Api](ad-hoc-sub-process-api.md) | 1 | | [Agent Definition Api](agent-definition-api.md) | 2 | | [Agent Instance Api](agent-instance-api.md) | 5 | | [Audit Log Api](audit-log-api.md) | 2 | | [Authentication Api](authentication-api.md) | 2 | | [Authorization Api](authorization-api.md) | 5 | | [Backup Api](backup-api.md) | 22 | | [Batch Operation Api](batch-operation-api.md) | 6 | | [Clock Api](clock-api.md) | 2 | | [Cluster Api](cluster-api.md) | 8 | | [Cluster Variable Api](cluster-variable-api.md) | 9 | | [Conditional Api](conditional-api.md) | 1 | | [Decision Definition Api](decision-definition-api.md) | 4 | | [Decision Instance Api](decision-instance-api.md) | 4 | | [Decision Requirements Api](decision-requirements-api.md) | 3 | | [Document Api](document-api.md) | 5 | | [Element Instance Api](element-instance-api.md) | 5 | | [Exporting Api](exporting-api.md) | 6 | | [Expression Api](expression-api.md) | 1 | | [Form Api](form-api.md) | 1 | | [Global Listener Api](global-listener-api.md) | 5 | | [Group Api](group-api.md) | 15 | | [Incident Api](incident-api.md) | 5 | | [Job Api](job-api.md) | 12 | | [License Api](license-api.md) | 1 | | [Mapping Rule Api](mapping-rule-api.md) | 5 | | [Message Api](message-api.md) | 2 | | [Message Subscription Api](message-subscription-api.md) | 2 | | [Process Definition Api](process-definition-api.md) | 9 | | [Process Instance Api](process-instance-api.md) | 23 | | [Recovery Api](recovery-api.md) | 5 | | [Resource Api](resource-api.md) | 6 | | [Role Api](role-api.md) | 17 | | [Secret Api](secret-api.md) | 2 | | [Setup Api](setup-api.md) | 1 | | [Signal Api](signal-api.md) | 1 | | [System Api](system-api.md) | 2 | | [Tenant Api](tenant-api.md) | 20 | | [User Api](user-api.md) | 5 | | [User Task Api](user-task-api.md) | 10 | | [Variable Api](variable-api.md) | 2 | | [Domain type system](domain-type-system.md) | — | --- ## Job Api `JobApi` — 12 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\JobApi::class)`. - `activateJobs()` - `completeJob()` - `failJob()` - `getGlobalJobStatistics()` - `getJobErrorStatistics()` - `getJobTimeSeriesStatistics()` - `getJobTypeStatistics()` - `getJobWorkerStatistics()` - `searchJobs()` - `throwJobError()` - `updateJob()` - `updateJobsBatchOperation()` --- ## License Api `LicenseApi` — 1 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\LicenseApi::class)`. - `getLicense()` --- ## Mapping Rule Api `MappingRuleApi` — 5 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\MappingRuleApi::class)`. - `createMappingRule()` - `deleteMappingRule()` - `getMappingRule()` - `searchMappingRule()` - `updateMappingRule()` --- ## Message Api `MessageApi` — 2 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\MessageApi::class)`. - `correlateMessage()` - `publishMessage()` --- ## Message Subscription Api `MessageSubscriptionApi` — 2 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\MessageSubscriptionApi::class)`. - `searchCorrelatedMessageSubscriptions()` - `searchMessageSubscriptions()` --- ## Process Definition Api `ProcessDefinitionApi` — 9 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\ProcessDefinitionApi::class)`. - `getProcessDefinition()` - `getProcessDefinitionInstanceStatistics()` - `getProcessDefinitionInstanceVersionStatistics()` - `getProcessDefinitionMessageSubscriptionStatistics()` - `getProcessDefinitionStatistics()` - `getProcessDefinitionXML()` - `getStartProcessForm()` - `searchProcessDefinitionVariableNames()` - `searchProcessDefinitions()` --- ## Process Instance Api `ProcessInstanceApi` — 23 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\ProcessInstanceApi::class)`. - `assignProcessInstanceBusinessId()` - `cancelProcessInstance()` - `cancelProcessInstancesBatchOperation()` - `createProcessInstance()` - `deleteProcessInstance()` - `deleteProcessInstancesBatchOperation()` - `getProcessInstance()` - `getProcessInstanceCallHierarchy()` - `getProcessInstanceSequenceFlows()` - `getProcessInstanceStatistics()` - `getProcessInstanceWaitStateStatistics()` - `migrateProcessInstance()` - `migrateProcessInstancesBatchOperation()` - `modifyProcessInstance()` - `modifyProcessInstancesBatchOperation()` - `resolveIncidentsBatchOperation()` - `resolveProcessInstanceIncidents()` - `resumeProcessInstance()` - `resumeProcessInstancesBatchOperation()` - `searchProcessInstanceIncidents()` - `searchProcessInstances()` - `suspendProcessInstance()` - `suspendProcessInstancesBatchOperation()` --- ## Recovery Api `RecoveryApi` — 5 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\RecoveryApi::class)`. - `changeClusterMode()` - `changeClusterModeAsClusterAdmin()` - `getRestoreStatus()` - `restore()` - `restoreAsClusterAdmin()` --- ## Resource Api `ResourceApi` — 6 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\ResourceApi::class)`. - `createDeployment()` - `deleteResource()` - `getResource()` - `getResourceContent()` - `getResourceContentBinary()` - `searchResources()` --- ## Role Api `RoleApi` — 17 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\RoleApi::class)`. - `assignRoleToClient()` - `assignRoleToGroup()` - `assignRoleToMappingRule()` - `assignRoleToUser()` - `createRole()` - `deleteRole()` - `getRole()` - `searchClientsForRole()` - `searchGroupsForRole()` - `searchMappingRulesForRole()` - `searchRoles()` - `searchUsersForRole()` - `unassignRoleFromClient()` - `unassignRoleFromGroup()` - `unassignRoleFromMappingRule()` - `unassignRoleFromUser()` - `updateRole()` --- ## Secret Api `SecretApi` — 2 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\SecretApi::class)`. - `listSecrets()` - `resolveSecrets()` --- ## Setup Api `SetupApi` — 1 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\SetupApi::class)`. - `createAdminUser()` --- ## Signal Api `SignalApi` — 1 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\SignalApi::class)`. - `broadcastSignal()` --- ## System Api `SystemApi` — 2 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\SystemApi::class)`. - `getSystemConfiguration()` - `getUsageMetrics()` --- ## Tenant Api `TenantApi` — 20 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\TenantApi::class)`. - `assignClientToTenant()` - `assignGroupToTenant()` - `assignMappingRuleToTenant()` - `assignRoleToTenant()` - `assignUserToTenant()` - `createTenant()` - `deleteTenant()` - `getTenant()` - `searchClientsForTenant()` - `searchGroupIdsForTenant()` - `searchMappingRulesForTenant()` - `searchRolesForTenant()` - `searchTenants()` - `searchUsersForTenant()` - `unassignClientFromTenant()` - `unassignGroupFromTenant()` - `unassignMappingRuleFromTenant()` - `unassignRoleFromTenant()` - `unassignUserFromTenant()` - `updateTenant()` --- ## User Api `UserApi` — 5 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\UserApi::class)`. - `createUser()` - `deleteUser()` - `getUser()` - `searchUsers()` - `updateUser()` --- ## User Task Api `UserTaskApi` — 10 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\UserTaskApi::class)`. - `assignUserTask()` - `completeUserTask()` - `getUserTask()` - `getUserTaskForm()` - `searchUserTaskAuditLogs()` - `searchUserTaskEffectiveVariables()` - `searchUserTaskVariables()` - `searchUserTasks()` - `unassignUserTask()` - `updateUserTask()` --- ## Variable Api `VariableApi` — 2 operations. Call any of these directly on the client, or via `$client->api(\Camunda\Orchestration\Api\Api\VariableApi::class)`. - `getVariable()` - `searchVariables()` --- ## Configuration(Php-sdk) :::caution Technical Preview The PHP SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: ## Zero-config from the environment The client reads the standard `CAMUNDA_*` environment variables and auto-detects the authentication strategy (`NONE`, `BASIC`, or `OAUTH`). ```php function readme_zero_config(): void { // Reads CAMUNDA_REST_ADDRESS and auto-detects the auth strategy from the // ambient environment (NONE / BASIC / OAUTH). $client = CamundaClient::fromEnvironment(); } ``` ## Programmatic configuration ```php function readme_programmatic_config(): void { $config = new CamundaConfiguration( restAddress: 'https://my-cluster.example.com/v2', authStrategy: 'OAUTH', clientId: 'my-client-id', clientSecret: 'my-client-secret', ); $client = CamundaClient::fromConfiguration($config); } ``` ## Basic auth ```php function readme_basic_auth(): void { $client = CamundaClient::fromEnvironment([ 'CAMUNDA_AUTH_STRATEGY' => 'BASIC', 'CAMUNDA_BASIC_AUTH_USERNAME' => 'demo', 'CAMUNDA_BASIC_AUTH_PASSWORD' => 'demo', ]); } ``` ## Loading a `.env` file Set `CAMUNDA_LOAD_ENVFILE=true` to read `.env` in the working directory, or set it to an explicit path. This optional capability requires [`vlucas/phpdotenv`](https://packagist.org/packages/vlucas/phpdotenv). ```php function env_file_client(): CamundaClient { // Set CAMUNDA_LOAD_ENVFILE=true (or a path) before starting PHP. Real // environment variables and explicit overrides still take precedence. return CamundaClient::fromEnvironment(); } ``` ## Mutual TLS ```php function mtls_client(): CamundaClient { return CamundaClient::fromConfiguration(new CamundaConfiguration( restAddress: 'https://my-cluster.example.com/v2', authStrategy: 'OAUTH', clientId: 'my-client-id', clientSecret: 'my-client-secret', mtlsCertPath: '/run/secrets/client.crt', mtlsKeyPath: '/run/secrets/client.key', mtlsCaPath: '/run/secrets/cluster-ca.pem', )); } ``` ## Custom Guzzle middleware ```php function custom_http_client(CamundaConfiguration $configuration): CamundaClient { // Supplying a Guzzle client replaces the SDK-built stack. Add the SDK auth // middleware and any proxy, tracing, or mTLS options your application needs. $stack = HandlerStack::create(); $stack->push(new AuthMiddleware(AuthProviderFactory::fromConfiguration($configuration)), 'camunda_auth'); return CamundaClient::fromConfiguration( $configuration, new GuzzleClient(['handler' => $stack, 'http_errors' => false]), ); } ``` ## Supported environment variables | Variable | Default | Description | | ------------------------------------ | -------------------------------------------- | ------------------------------------------------------------------------------------ | | `CAMUNDA_REST_ADDRESS` | `http://localhost:8080/v2` | Cluster REST endpoint. `/v2` is appended automatically when absent. | | `ZEEBE_REST_ADDRESS` | — | Legacy alias for `CAMUNDA_REST_ADDRESS` (used only when the latter is unset). | | `CAMUNDA_AUTH_STRATEGY` | — | `NONE`, `BASIC`, or `OAUTH`. Auto-detected from the supplied credentials when unset. | | `CAMUNDA_CLIENT_ID` | — | OAuth client id. | | `CAMUNDA_CLIENT_SECRET` | — | OAuth client secret. | | `CAMUNDA_CLIENT_AUTH_CLIENTID` | — | Legacy alias for `CAMUNDA_CLIENT_ID`. | | `CAMUNDA_CLIENT_AUTH_CLIENTSECRET` | — | Legacy alias for `CAMUNDA_CLIENT_SECRET`. | | `CAMUNDA_OAUTH_URL` | `https://login.cloud.camunda.io/oauth/token` | OAuth token endpoint. | | `CAMUNDA_TOKEN_AUDIENCE` | `zeebe.camunda.io` | OAuth token audience. | | `CAMUNDA_BASIC_AUTH_USERNAME` | — | Basic-auth username. | | `CAMUNDA_BASIC_AUTH_PASSWORD` | — | Basic-auth password. | | `CAMUNDA_TENANT_ID` | — | Default tenant id applied to tenant-aware operations. | | `CAMUNDA_TENANT_IDS` | — | Comma-separated default tenant ids (e.g. for job activation). | | `CAMUNDA_SDK_LOG_LEVEL` | `warn` | SDK log level (`error`, `warn`, `info`, `debug`). | | `CAMUNDA_WORKER_MAX_CONCURRENT_JOBS` | `32` | Default maximum number of jobs a worker activates at once. | | `CAMUNDA_WORKER_TIMEOUT` | `60000` | Default job activation timeout, in milliseconds. | | `CAMUNDA_WORKER_REQUEST_TIMEOUT` | `10000` | Default long-poll request timeout, in milliseconds. | | `CAMUNDA_WORKER_NAME` | — | Default worker name reported when activating jobs. | | `CAMUNDA_MTLS_CERT_PATH` | — | Path to the client certificate for mutual TLS. | | `CAMUNDA_MTLS_KEY_PATH` | — | Path to the client private key for mutual TLS. | | `CAMUNDA_MTLS_CA_PATH` | — | Path to the CA bundle used to verify the server certificate. | | `CAMUNDA_MTLS_KEY_PASSPHRASE` | — | Passphrase protecting the mTLS client key, if any. | | `CAMUNDA_LOAD_ENVFILE` | — | Load configuration from a `.env` file. Set to `true` or a file path. | --- ## Deploying resources(Php-sdk) :::caution Technical Preview The PHP SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: Pass a readable filesystem path for every BPMN, DMN, or Form resource. Anchor paths with `__DIR__` so deployment is independent of the shell's current working directory. The repository includes [runnable deployment resources](https://github.com/camunda/orchestration-cluster-api-php/tree/main/examples/resources/); replace those paths with your application's models. ```php function readme_deploy_resources(): void { $client = CamundaClient::fromEnvironment(); $result = $client->deployResourcesFromFiles( __DIR__ . '/resources/order-process.bpmn', __DIR__ . '/resources/pricing.dmn', ); // $result is a DeploymentResult (or ProblemDetail on a handled error). } ``` `deployResourcesFromFiles()` returns a `DeploymentResult` on success and a `ProblemDetail` for a handled API error. It throws a `ConfigurationException` when a local resource path cannot be read. --- ## Installing the SDK to your project :::caution Technical Preview The PHP SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: ## Requirements - PHP 8.2 or later - [Composer](https://getcomposer.org/) - `ext-json`; `ext-pcntl` is optional (enables forked job workers) ## Stable release (recommended for production) The stable version tracks the latest supported Camunda server release. ```bash composer require camunda8/orchestration-cluster-api ``` ## Versioning This SDK has a different release cadence from the Camunda server. The major version of the SDK signals a 1:1 type coherence with the server API for a Camunda minor release. SDK version `n.y.z` → server version `8.n`, so the type surface of SDK version `10.y.z` matches the API surface of Camunda `8.10`. Using the matching SDK major version for the server minor version provides the strongest guarantees about runtime reliability. --- ## Job workers(Php-sdk) :::caution Technical Preview The PHP SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: A job worker long-polls for jobs of a given type and dispatches each to your handler. Returning an array auto-completes the job with those variables; you can also complete, fail, or raise a BPMN error explicitly via the handler's `JobActionClient`. ```php function readme_job_worker(): void { $client = CamundaClient::fromEnvironment(); $worker = $client->createJobWorker(new JobWorkerOptions( type: 'payment-processing', maxJobs: 5, timeoutMs: 30_000, )); $worker->run(function (ActivatedJobResult $job, JobActionClient $action): array { // ... perform the work ... return ['status' => 'paid']; }); } ``` When `ext-pcntl` is available and `forked: true` is set, each job is processed in its own child process, bounded to `maxJobs` concurrent children. ## Object-oriented handlers ```php final class PaymentJobHandler implements JobHandler { public function handle(ActivatedJobResult $job, JobActionClient $action): ?array { $variables = $job->getVariables(); if (!isset($variables['paymentId'])) { $action->error('MISSING_PAYMENT_ID', 'The payment job has no payment id.'); return null; } return ['paymentStatus' => 'approved']; } } function object_job_handler(CamundaClient $client): void { $worker = $client->createJobWorker(new JobWorkerOptions(type: 'process-payment')); $worker->run(new PaymentJobHandler()); } ``` --- ## Live example showcase :::caution Technical Preview The PHP SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: With a disposable Camunda cluster already running, execute the six scenario showcase and create its HTML proof report: ```sh CAMUNDA_REST_ADDRESS=http://localhost:8080 \ CAMUNDA_AUTH_STRATEGY=NONE \ make example-showcase ``` The showcase runs focused workflow scenarios for deployment, process lifecycle, job workers, user tasks, incidents, message correlation, and management operations. The report indexes every source-backed snippet alongside the live scenario that demonstrates its API area. It intentionally performs mutations and cluster-management requests; never run it against a shared, staging, or production cluster. Run one scenario with `make example-showcase SCENARIO=`. Open [`docs/example-validation.html`](https://github.com/camunda/orchestration-cluster-api-php/blob/main/docs/example-validation.html) after a run for the six outcomes and a searchable gallery of all source-backed snippets. --- ## Semantic Types :::caution Technical Preview The PHP SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: The SDK uses distinct value objects for identifiers like `ProcessDefinitionId`, `ProcessInstanceKey`, `JobKey`, `TenantId`, and so on, defined in the `Camunda\Orchestration\Semantic` namespace. ## Why they exist Camunda's API has many operations that accept string keys. Without semantic types it is easy to accidentally pass a process-instance key where a process-definition id is expected. When everything is a `string`, static analysis cannot help you. Semantic types make these identifiers **distinct at the type level**. PHPStan flags an error if you pass the wrong identifier type, catching bugs before runtime. Each value object validates its format on construction and implements `Stringable` and `JsonSerializable`, so it serializes transparently to and from JSON. ## How to use them ```php function readme_semantic_types(): void { // Identifiers are distinct value objects — you cannot accidentally pass a // process-definition id where a tenant id is expected. $instruction = (new ProcessInstanceCreationInstructionById()) ->setProcessDefinitionId(ProcessDefinitionId::of('order-process')) ->setVariables(['orderId' => 'ORD-42']); // Value objects validate their format on construction and stringify cleanly. $definitionId = new ProcessDefinitionId('order-process'); echo (string) $definitionId, "\n"; } ``` When you construct a model from an array, raw strings are automatically lifted into their semantic value objects, so you can stay ergonomic where you want to. --- ## Support status(Php-sdk) :::caution Technical Preview The PHP SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: This is a **Technical Preview** of the PHP client for the Camunda 8 Orchestration Cluster API, provided for evaluation and feedback. It gives you a stable foundation to build on now, with a clear path to full support: we do not intend to make breaking changes to the application integration surface, but we do not guarantee that we will not. The SDK will become fully supported with an SLA in a future release. We don't anticipate major changes — and [your feedback](https://github.com/camunda/orchestration-cluster-api-php/issues) is what helps close that gap. > As a Technical Preview, the API surface may still evolve before it is declared fully > supported. Pin a specific version if you need stability. --- ## Using the SDK :::caution Technical Preview The PHP SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: The SDK provides two clients with matching surfaces: - **`CamundaClient`** — synchronous. Every method blocks until the response arrives. Use it in scripts, CLI tools, and traditional request/response applications. - **`CamundaAsyncClient`** — asynchronous. Every operation returns a Guzzle `PromiseInterface`. Use it when you want to issue concurrent requests. ```php function readme_sync_client(): void { $client = CamundaClient::fromEnvironment(); $result = $client->deployResourcesFromFiles(__DIR__ . '/resources/order-process.bpmn'); // ... } ``` ```php function readme_async_client(): void { $client = CamundaAsyncClient::fromEnvironment(); $client->deployResourcesFromFilesAsync(__DIR__ . '/resources/order-process.bpmn') ->then(static function ($result): void { // handle the DeploymentResult once the request resolves }) ->wait(); } ``` ## Parallel async reads ```php function parallel_async_reads(CamundaAsyncClient $client): void { // Requests are issued before either promise is awaited. $topologyPromise = $client->getTopology(); $definitionsPromise = $client->searchProcessDefinitions(); $topology = $topologyPromise->wait(); $definitions = $definitionsPromise->wait(); if ($topology instanceof TopologyResponse) { printf("Connected to %d broker(s).\n", count($topology->getBrokers())); } if ($definitions instanceof ProcessDefinitionSearchQueryResult) { printf("Found %d process definitions.\n", count($definitions->getItems())); } } ``` --- ## PHP SDK (Technical Preview) :::caution Technical Preview The PHP SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: A fully typed PHP client for the [Camunda 8 Orchestration Cluster REST API](../apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). Fully compliant with the Camunda OpenAPI spec, with a hand-written runtime for authentication, configuration, and job workers. - **Sync and async** — `CamundaClient` (synchronous) and `CamundaAsyncClient` (promise-based) - **Semantic value objects** — distinct types for every identifier, checked at `PHPStan` level `max` - **Zero-config** — reads `CAMUNDA_*` environment variables (12-factor style) - **Job workers** — long-poll workers with optional `pcntl` process forking - **OAuth & Basic auth** — pluggable authentication with automatic token management - **PSR standards** — PSR-4 autoloading, PSR-18/PSR-7 HTTP via Guzzle --- ## Processes MCP Server ## About The Processes MCP Server is a capability of the Orchestration Cluster that exposes your deployed BPMN processes as callable tools through the [Model Context Protocol](https://modelcontextprotocol.io/) (MCP). - Any process equipped with an [MCP start event](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-start-event.md) element template is automatically registered as an MCP tool when deployed. - MCP clients discover these tools at runtime and invoke them by name. Each invocation starts a new process instance and returns the started process instance key immediately. - The server shares the same [authentication](../orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md) and [authorization](/components/concepts/access-control/authorizations.md) model as the Orchestration Cluster REST API. :::important Camunda 8 public API The Processes MCP Server is not part of the [Camunda 8 public API](/reference/public-api.md). ::: :::note This is the Processes MCP Server documentation. If you are looking to: - Give [AI agents](/reference/glossary.md#ai-agent) access to Camunda's operational capabilities, such as incidents, user tasks, and process instances, see the [Orchestration Cluster MCP Server](../orchestration-cluster-api-mcp/orchestration-cluster-api-mcp-overview.md). - Connect an AI agent running inside a BPMN process to an external MCP server, see the [MCP Client connector](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-client.md). ::: ### Key features | Feature | Description | | :------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Process tool registration | Processes with an MCP start event are automatically registered as MCP tools on deployment. | | Tool discovery | MCP clients discover available process tools and their schemas at runtime. | | Static tools | The server also exposes a set of [static tools](#static-tools) for inspecting running process instances. | | Version binding | Only the latest deployed version of a process is exposed. See [Version binding](./processes-mcp-version-binding.md). | | Standard transport | Uses [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http), compatible with any MCP-compliant client. | ### Authentication The Processes MCP Server uses the same authentication model as the [Orchestration Cluster REST API](../orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md). OAuth tokens obtained for the REST API work without changes. For the full authentication reference, including SaaS and Self-Managed setup, see [Authentication](../orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md). ### Transport The Processes MCP Server uses [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http) and is served at the `/mcp/processes` endpoint on the Orchestration Cluster. It is stateless; no session management is required. ### Audit logging for MCP operations Operations triggered through the Processes MCP Server are recorded in the [audit log](/components/audit-log/overview.md), like any other operation. Because the tool call enters through MCP, each resulting record has an inbound channel of `MCP` and captures the name of the MCP tool that triggered it, so you can distinguish operations initiated by AI agents through MCP from those performed directly by users or clients. See [inbound channel](/components/audit-log/overview/operation-structure.md#inbound-channel) for details on how this is presented in the applications and the REST API. :::note The Processes MCP Server uses the same authentication as the REST API, so a tool call is attributed to the authenticated user or client. By default, [only user operations are recorded](/components/audit-log/overview/recorded-operations.md#limitations-and-constraints), not client operations. To capture MCP tool calls made by a client, configure the audit log to also track client operations. ::: ## Get started :::important Camunda 8.10 The Processes MCP Server is only available from Camunda 8.10 onwards. ::: To expose a BPMN process as an MCP tool, see [Expose a process as an MCP tool](/components/agentic-orchestration/expose-process-as-mcp-tool.md). If you have a local Orchestration Cluster running with [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) or [Docker Compose](/self-managed/quickstart/developer-quickstart/docker-compose.md), the Processes MCP Server is enabled by default. Connect any MCP client using this configuration: ```json { "servers": { "camunda-processes": { "type": "http", "url": "http://localhost:8080/mcp/processes" } } } ``` For production environments and other deployment types, the Processes MCP Server must be explicitly enabled before use. See [Enable and connect](./processes-mcp-setup.md) for more details. ## Static tools In addition to dynamically registered process tools, the Processes MCP Server exposes a set of static tools for inspecting the process instances it starts. See [static tools](./processes-mcp-static-tools.md) for more details. --- ## Enable and connect(Processes-mcp) Enable the Processes MCP Server and configure MCP clients to connect. ## Enable the Processes MCP Server The Processes MCP Server is enabled by default in [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) and [Docker Compose](/self-managed/quickstart/developer-quickstart/docker-compose.md). For other deployment types, it must be explicitly enabled before MCP clients can connect. Depending on your deployment, enable it as follows: The Processes MCP Server is **enabled by default** in Camunda 8 Run. No additional configuration is needed. The Processes MCP Server is **enabled by default** in the Docker Compose distribution. No additional configuration is needed. Set the following [`extraConfiguration`](/self-managed/deployment/helm/configure/application-configs.md#configuration-options) value in your Helm chart values: ```yaml orchestration: extraConfiguration: - file: mcp-gateway.yaml content: | camunda: mcp: enabled: true ``` In Camunda Hub: 1. In the left navigation, under **Clusters**, select a cluster. 1. Open the **Settings** tab 1. Enable MCP support. :::info MCP server support is available on SaaS clusters running Camunda 8.10.0 or later. ::: For a full reference of MCP configuration properties, see [Property reference](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#api---mcp). ## Connect an MCP client Once the Processes MCP Server is enabled, you can connect any MCP-compliant client. The approach depends on your client's capabilities and authentication requirements. :::important When you [create API client credentials](/components/saas/clusters/manage-api-clients.md#create-a-client) in Camunda Hub, all required connection details, including the base URL, OAuth endpoint, client ID, and audience, are displayed on the credentials page. ::: ### MCP endpoint URL The Processes MCP Server is served at `/mcp/processes` on the Orchestration Cluster. The full endpoint URL depends on your deployment type: | Deployment | MCP endpoint URL | | :------------------------- | :-------------------------------------------------------------------------------- | | Camunda 8 Run | `http://localhost:8080/mcp/processes` | | Docker Compose | `http://localhost:8080/mcp/processes` | | SaaS – public connectivity | `https://${REGION_ID}.api.camunda.io/${CLUSTER_ID}/mcp/processes` | | SaaS – secure connectivity | `https://${CLUSTER_ID}.${REGION_ID}.privateconnectivity.camunda.io/mcp/processes` | | Self-Managed (custom) | `https:///mcp/processes` | For SaaS, find your **Region Id** and **Cluster Id** in [Camunda Hub](/components/saas/clusters/manage-api-clients.md#view-connection-information). ### Direct HTTP connection If your Orchestration Cluster does not require authentication, for example, when running locally with [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) or [Docker Compose](/self-managed/quickstart/developer-quickstart/docker-compose.md), you can connect directly to the MCP server endpoint without any additional tooling. Any MCP client that supports [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http) can be used. For authenticated environments, [use c8ctl `mcp-proxy`](#use-c8ctl-mcp-proxy) instead. ```json { "servers": { "camunda-processes": { "type": "http", "url": "http://localhost:8080/mcp/processes" } } } ``` ### Use c8ctl `mcp-proxy` Many MCP clients, such as VS Code (GitHub Copilot) and Claude Code, do not natively support the OAuth 2.0 client credentials flow required for authenticated environments. The [c8ctl](https://github.com/camunda/c8ctl) `mcp-proxy` command bridges this gap by providing a local STDIO-to-Remote HTTP proxy that handles authentication transparently. The proxy authenticates to the MCP server using OAuth 2.0 client credentials, and exposes a local STDIO MCP interface that your client connects to. #### Prerequisites - [Node.js](https://nodejs.org/) 18 or later. - [Client credentials](/components/saas/clusters/manage-api-clients.md#create-a-client) for your Camunda cluster with the **Orchestration Cluster API** scope enabled. #### Configuration Add the following to your MCP client configuration. For example, use the following in `claude_desktop_config.json` for Claude Code: ```json { "mcpServers": { "camunda-processes": { "type": "stdio", "command": "npx", "args": ["-y", "@camunda8/cli", "mcp-proxy", "/mcp/processes"], "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/processes` path (for example, the public `api.camunda.io` URL or the private `privateconnectivity.camunda.io` URL when using Secure connectivity). | | `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 Where to find these values When you [create API client credentials](/components/saas/clusters/manage-api-clients.md#create-a-client) in Camunda Hub, all required connection details are displayed on the credentials page. You can also copy a ready-to-use c8ctl configuration snippet directly from the **MCP** tab on the credentials screen. ::: For the full list of supported environment variables, see the [c8ctl documentation](https://github.com/camunda/c8ctl). ### Use with the MCP Client connectors You can also connect to the Processes MCP Server from within a BPMN process using Camunda's [MCP Client connectors](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-client.md). This allows an AI agent running in an agentic orchestration workflow to invoke your deployed processes as MCP tools. The [MCP Remote Client connector](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-remote-client-connector.md) connects to remote MCP servers over HTTP. Configure it in the properties panel with the following settings: - **Transport type**: Streamable HTTP. - **URL**: Your MCP endpoint URL (see [above](#mcp-endpoint-url)). - **Authentication**: OAuth 2.0. | Field | Value | | :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | | OAuth 2.0 token endpoint | Your OAuth token endpoint (`https://login.cloud.camunda.io/oauth/token` for SaaS). | | Client ID | Your OAuth client ID. | | Client secret | Your OAuth client secret. Use [secrets](/components/saas/clusters/manage-secrets.md) (for example, `{{secrets.MCP_CLIENT_SECRET}}`). | | Audience | The audience for your cluster API (`zeebe.camunda.io` for SaaS). | | Client authentication | Send client credentials in body. | For more details, see [MCP Remote Client connector](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-remote-client-connector.md). The [MCP Client connector](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-client-connector.md) manages persistent MCP connections through the connector runtime. Configure the Processes MCP Server as a remote HTTP client in your connector runtime configuration (for example, `application.yml`): ```yaml camunda: connector: agenticai: mcp: client: enabled: true clients: camunda-processes: type: http http: url: https://${REGION_ID}.api.camunda.io/${CLUSTER_ID}/mcp/processes authentication: type: oauth oauth: oauth-token-endpoint: https://login.cloud.camunda.io/oauth/token client-id: client-secret: audience: zeebe.camunda.io client-authentication: credentials-body ``` The example above shows a SaaS configuration using the public endpoint. For clusters with Secure connectivity (AWS PrivateLink), set `url` to the private MCP endpoint URL shown in Camunda Hub instead of the public `zeebe.camunda.io` host (the path still ends with `/mcp/processes`). For local unauthenticated setups, you can omit the `authentication` block and use `http://localhost:8080/mcp/processes` as the URL. Reference the client ID `camunda-processes` in the MCP Client connector element template in your BPMN process. For more details, see [MCP Client connector](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-client-connector.md). --- ## Static tools The Processes MCP Server exposes the following static tools alongside dynamically registered process tools. These tools let an MCP agent inspect the process instance it just started, including its variables, state, and incidents, without switching to a different MCP server. | Tool | Description | | :------------------- | :---------------------------------------------------------- | | `searchVariables` | Search and inspect variables of a running process instance. | | `getProcessInstance` | Retrieve the state and details of a process instance. | | `searchIncidents` | Inspect incidents raised on a running process instance. | :::note These tools are a subset of the [Orchestration Cluster MCP Server tools](../orchestration-cluster-api-mcp/orchestration-cluster-api-mcp-tools.md). Refer to that page for full parameter and response schema details. ::: --- ## Version binding Understand how the Processes MCP Server handles process version binding, stale tool references after redeployment, and best practices for managing breaking changes. ## Which version is exposed The Processes MCP Server always exposes only the **latest deployed version** of a process. When you deploy a new version of a process, it replaces the previous version's tool registration. There is no mechanism to pin an MCP client to a specific process version. ## Stale tool references MCP clients typically cache the tool list after connecting. If a client cached the tool list before a redeployment and then attempts to call the now-replaced tool, it receives an error instructing it to refresh its tool list by running tool discovery again. MCP clients must therefore: 1. Handle the stale-tool error gracefully. 2. Re-fetch the tool list by running tool discovery again before retrying. ## Implications for process owners Redeploying a process with a changed interface, such as a different tool name, input parameters, or output variables, is a breaking change for any MCP client currently holding a reference to that tool. To reduce disruption: - Communicate planned redeployments to teams operating MCP clients that use your process tool. - When making a significant interface change, consider deploying a new process with a different tool name rather than redeploying over the existing one. This lets existing clients continue using the old version while new clients adopt the updated tool. --- ## CamundaAsyncClient ## CamundaAsyncClient ```python class CamundaAsyncClient(configuration=None, auth_provider=None, logger=None, clock=None, random=None, **kwargs) ``` Bases: `object` **Parameters:** | Parameter | Type | Description | | --------------- | ---------------------------------------------------------------------------------------------------------------------- | ----------- | | `configuration` | [CamundaSdkConfiguration](runtime.md#camunda_orchestration_sdk.runtime.configuration_resolver.CamundaSdkConfiguration) | | | `auth_provider` | [AuthProvider](runtime.md#camunda_orchestration_sdk.runtime.auth.AuthProvider) | | | `logger` | [CamundaLogger](runtime.md#camunda_orchestration_sdk.runtime.logging.CamundaLogger) \| `None` | | | `clock` | `Clock` \| `None` | | | `random` | `RandomSource` \| `None` | | | `kwargs` | `Any` | | ### aclose() ```python async def aclose() ``` Close underlying HTTP clients. This closes both the API client’s async httpx client and, when available, the auth provider’s token client. - **Return type:** None ### activate_ad_hoc_sub_process_activities() ```python async def activate_ad_hoc_sub_process_activities(ad_hoc_sub_process_instance_key, *, data, **kwargs) ``` Activate activities within an ad-hoc sub-process > Activates selected activities within an ad-hoc sub-process identified by element ID. > > The provided element IDs must exist within the ad-hoc sub-process instance identified by the > provided adHocSubProcessInstanceKey. **Parameters:** | Parameter | Type | Description | | --------------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------- | | `ad_hoc_sub_process_instance_key` | `str` | System-generated key for a element instance. Example: 2251799813686789. | | `data` | `AdHocSubProcessActivateActivitiesInstruction` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The ad-hoc sub-process instance is not found or the provided key does not identify an ad-hoc sub-process. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Activate ad-hoc sub-process activities:** ```python def activate_ad_hoc_sub_process_activities_example(element_id: ElementId) -> None: client = CamundaClient() client.activate_ad_hoc_sub_process_activities( ad_hoc_sub_process_instance_key="123456", data=AdHocSubProcessActivateActivitiesInstruction( elements=[ AdHocSubProcessActivateActivityReference(element_id=element_id), AdHocSubProcessActivateActivityReference(element_id=element_id), ], ), ) ``` ### activate_jobs() ```python async def activate_jobs(*, data, **kwargs) ``` Activate jobs > Iterate through all known partitions and activate jobs up to the requested maximum. **Parameters:** | Parameter | Type | Description | | --------- | ---------------------- | ----------- | | `data` | `JobActivationRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** JobActivationResult - **Return type:** JobActivationResult #### Examples **Activate and process jobs:** ```python async def activate_jobs_example() -> None: async with CamundaAsyncClient() as client: result = await client.activate_jobs( data=JobActivationRequest( type_="payment-processing", timeout=30000, max_jobs_to_activate=5, ) ) for job in result.jobs: print(f"Job {job.job_key}: {job.type_}") ``` ### assign_client_to_group() ```python async def assign_client_to_group(group_id, client_id, **kwargs) ``` Assign a client to a group > Assigns a client to a group, making it a member of the group. > > Members of the group inherit the group authorizations, roles, and tenant assignments. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `client_id` | str) – | | The unique identifier of an OAuth client. Minted outside the Camunda REST API: in SaaS by Console, in Self-Managed with OIDC by the external identity provider (e.g. EntraID, Keycloak, Okta). In Self-Managed with Basic authentication, machine-to-machine applications are modelled as users instead — see the user identifier. > Example: my-application. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group with the given ID was not found. - **errors.ConflictError** – If the response status code is 409. The client with the given ID is already assigned to the group. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a client to a group:** ```python def assign_client_to_group_example(group_id: GroupId, client_id: ClientId) -> None: client = CamundaClient() client.assign_client_to_group( group_id=group_id, client_id=client_id, ) ``` ### assign_client_to_tenant() ```python async def assign_client_to_tenant(tenant_id, client_id, **kwargs) ``` Assign a client to a tenant > Assign the client to the specified tenant. > > The client can then access tenant data and perform authorized actions. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `client_id` | str) – | | The unique identifier of an OAuth client. Minted outside the Camunda REST API: in SaaS by Console, in Self-Managed with OIDC by the external identity provider (e.g. EntraID, Keycloak, Okta). In Self-Managed with Basic authentication, machine-to-machine applications are modelled as users instead — see the user identifier. > Example: my-application. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The tenant was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a client to a tenant:** ```python def assign_client_to_tenant_example(tenant_id: TenantId, client_id: ClientId) -> None: client = CamundaClient() client.assign_client_to_tenant( tenant_id=tenant_id, client_id=client_id, ) ``` ### assign_group_to_tenant() ```python async def assign_group_to_tenant(tenant_id, group_id, **kwargs) ``` Assign a group to a tenant > Assigns a group to a specified tenant. > > Group members (users, clients) can then access tenant data and perform authorized actions. **Parameters:** | Parameter | Type | Description | | ----------- | ----- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The tenant or group was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a group to a tenant:** ```python def assign_group_to_tenant_example(tenant_id: TenantId, group_id: GroupId) -> None: client = CamundaClient() client.assign_group_to_tenant( tenant_id=tenant_id, group_id=group_id, ) ``` ### assign_mapping_rule_to_group() ```python async def assign_mapping_rule_to_group(group_id, mapping_rule_id, **kwargs) ``` Assign a mapping rule to a group > Assigns a mapping rule to a group. **Parameters:** | Parameter | Type | Description | | ----------------- | ----- | ------------------------------------------------------------------ | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `mapping_rule_id` | `str` | The unique identifier of a mapping rule. Example: my-mapping-rule. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group or mapping rule with the given ID was not found. - **errors.ConflictError** – If the response status code is 409. The mapping rule with the given ID is already assigned to the group. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a mapping rule to a group:** ```python def assign_mapping_rule_to_group_example(group_id: GroupId, mapping_rule_id: MappingRuleId) -> None: client = CamundaClient() client.assign_mapping_rule_to_group( group_id=group_id, mapping_rule_id=mapping_rule_id, ) ``` ### assign_mapping_rule_to_tenant() ```python async def assign_mapping_rule_to_tenant(tenant_id, mapping_rule_id, **kwargs) ``` Assign a mapping rule to a tenant > Assign a single mapping rule to a specified tenant. **Parameters:** | Parameter | Type | Description | | ----------------- | ----- | ------------------------------------------------------------------ | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `mapping_rule_id` | `str` | The unique identifier of a mapping rule. Example: my-mapping-rule. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The tenant or mapping rule was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a mapping rule to a tenant:** ```python def assign_mapping_rule_to_tenant_example(tenant_id: TenantId, mapping_rule_id: MappingRuleId) -> None: client = CamundaClient() client.assign_mapping_rule_to_tenant( tenant_id=tenant_id, mapping_rule_id=mapping_rule_id, ) ``` ### assign_process_instance_business_id() ```python async def assign_process_instance_business_id(process_instance_key, *, data, **kwargs) ``` Assign business id to process instance > Assigns a business id to an already-running process instance that currently has none. > > The assignment is single and irreversible: only artifacts created after the assignment > (for example future jobs, user tasks, decision instances, and message subscriptions) carry > the business id, while existing artifacts are not retroactively enriched. Re-sending the > same business id succeeds as a no-op. This endpoint is only useful while business id > uniqueness enforcement is disabled; when it is enabled, the request is rejected with a 409 > response. **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `data` | `ProcessInstanceBusinessIdAssignmentInstruction` | The instruction describing the business id to assign to a running process instance. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The process instance is not found. - **errors.ConflictError** – If the response status code is 409. The business id assignment failed because the process instance is not eligible, for example it already has a different business id, it is a call-activity child, or business id uniqueness enforcement is enabled. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a business id to a process instance:** ```python def assign_process_instance_business_id_example(process_instance_key: ProcessInstanceKey) -> None: client = CamundaClient() client.assign_process_instance_business_id( process_instance_key=process_instance_key, data=ProcessInstanceBusinessIdAssignmentInstruction( business_id="order-12345", ), ) ``` ### assign_role_to_client() ```python async def assign_role_to_client(role_id, client_id, **kwargs) ``` Assign a role to a client > Assigns the specified role to the client. The client will inherit the authorizations associated with > this role. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `client_id` | str) – | | The unique identifier of an OAuth client. Minted outside the Camunda REST API: in SaaS by Console, in Self-Managed with OIDC by the external identity provider (e.g. EntraID, Keycloak, Okta). In Self-Managed with Basic authentication, machine-to-machine applications are modelled as users instead — see the user identifier. > Example: my-application. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role with the given ID was not found. - **errors.ConflictError** – If the response status code is 409. The role was already assigned to the client with the given ID. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a role to a client:** ```python def assign_role_to_client_example(role_id: RoleId, client_id: ClientId) -> None: client = CamundaClient() client.assign_role_to_client( role_id=role_id, client_id=client_id, ) ``` ### assign_role_to_group() ```python async def assign_role_to_group(role_id, group_id, **kwargs) ``` Assign a role to a group > Assigns the specified role to the group. Every member of the group (user or client) will inherit the > authorizations associated with this role. **Parameters:** | Parameter | Type | Description | | ---------- | ----- | ------------------------------------------------------- | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role or group with the given ID was not found. - **errors.ConflictError** – If the response status code is 409. The role is already assigned to the group with the given ID. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a role to a group:** ```python def assign_role_to_group_example(role_id: RoleId, group_id: GroupId) -> None: client = CamundaClient() client.assign_role_to_group( role_id=role_id, group_id=group_id, ) ``` ### assign_role_to_mapping_rule() ```python async def assign_role_to_mapping_rule(role_id, mapping_rule_id, **kwargs) ``` Assign a role to a mapping rule > Assigns a role to a mapping rule. **Parameters:** | Parameter | Type | Description | | ----------------- | ----- | ------------------------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `mapping_rule_id` | `str` | The unique identifier of a mapping rule. Example: my-mapping-rule. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role or mapping rule with the given ID was not found. - **errors.ConflictError** – If the response status code is 409. The role is already assigned to the mapping rule with the given ID. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a role to a mapping rule:** ```python def assign_role_to_mapping_rule_example(role_id: RoleId, mapping_rule_id: MappingRuleId) -> None: client = CamundaClient() client.assign_role_to_mapping_rule( role_id=role_id, mapping_rule_id=mapping_rule_id, ) ``` ### assign_role_to_tenant() ```python async def assign_role_to_tenant(tenant_id, role_id, **kwargs) ``` Assign a role to a tenant > Assigns a role to a specified tenant. > > Users, Clients or Groups, that have the role assigned, will get access to the tenant’s data and can > perform actions according to their authorizations. **Parameters:** | Parameter | Type | Description | | ----------- | ----- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The tenant or role was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a role to a tenant:** ```python def assign_role_to_tenant_example(tenant_id: TenantId, role_id: RoleId) -> None: client = CamundaClient() client.assign_role_to_tenant( tenant_id=tenant_id, role_id=role_id, ) ``` ### assign_role_to_user() ```python async def assign_role_to_user(role_id, username, **kwargs) ``` Assign a role to a user > Assigns the specified role to the user. The user will inherit the authorizations associated with > this role. **Parameters:** | Parameter | Type | Description | | ---------- | ----- | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `username` | `str` | The unique name of a user. Example: swillis. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role or user with the given ID or username was not found. - **errors.ConflictError** – If the response status code is 409. The role is already assigned to the user with the given ID. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a role to a user:** ```python def assign_role_to_user_example(role_id: RoleId, username: Username) -> None: client = CamundaClient() client.assign_role_to_user( role_id=role_id, username=username, ) ``` ### assign_user_task() ```python async def assign_user_task(user_task_key, *, data, **kwargs) ``` Assign user task > Assigns a user task with the given key to the given assignee. Assignment waits for blocking task > listeners on this lifecycle transition. If listener processing is delayed beyond the request > timeout, this endpoint can return 504. Other gateway timeout causes are also possible. Retry with > backoff and inspect listener worker availability and logs when this repeats. **Parameters:** | Parameter | Type | Description | | --------------- | --------------------------- | ------------------------------------- | | `user_task_key` | `str` | System-generated key for a user task. | | `data` | `UserTaskAssignmentRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The user task with the given key was not found. - **errors.ConflictError** – If the response status code is 409. The user task with the given key is in the wrong state currently. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.GatewayTimeoutError** – If the response status code is 504. The request timed out between the gateway and the broker. For these endpoints, this often happens when user task listeners are configured and the corresponding listener job is not completed within the request timeout. Common causes include no available job workers for the listener type, busy or crashed job workers, or delayed job completion. As with any gateway timeout, general timeout causes (for example transient network issues) can also result in a 504 response. Troubleshooting: - verify that job workers for the listener type are running and healthy - check worker logs for crashes, retries, and completion failures - check network connectivity between workers, gateway, and broker - retry with backoff after transient failures - fail without retries if a problem persists - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a user task:** ```python def assign_user_task_example(user_task_key: UserTaskKey) -> None: client = CamundaClient() client.assign_user_task( user_task_key=user_task_key, data=UserTaskAssignmentRequest( assignee="user@example.com", ), ) ``` ### assign_user_to_group() ```python async def assign_user_to_group(group_id, username, **kwargs) ``` Assign a user to a group > Assigns a user to a group, making the user a member of the group. > > Group members inherit the group authorizations, roles, and tenant assignments. **Parameters:** | Parameter | Type | Description | | ---------- | ----- | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `username` | `str` | The unique name of a user. Example: swillis. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group or user with the given ID or username was not found. - **errors.ConflictError** – If the response status code is 409. The user with the given ID is already assigned to the group. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a user to a group:** ```python def assign_user_to_group_example(group_id: GroupId, username: Username) -> None: client = CamundaClient() client.assign_user_to_group( group_id=group_id, username=username, ) ``` ### assign_user_to_tenant() ```python async def assign_user_to_tenant(tenant_id, username, **kwargs) ``` Assign a user to a tenant > Assign a single user to a specified tenant. The user can then access tenant data and perform > authorized actions. **Parameters:** | Parameter | Type | Description | | ----------- | ----- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `username` | `str` | The unique name of a user. Example: swillis. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The tenant or user was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a user to a tenant:** ```python def assign_user_to_tenant_example(tenant_id: TenantId, username: Username) -> None: client = CamundaClient() client.assign_user_to_tenant( tenant_id=tenant_id, username=username, ) ``` ### auth_provider ```python auth_provider: [AuthProvider](runtime.md#camunda_orchestration_sdk.runtime.auth.AuthProvider) ``` ### broadcast_signal() ```python async def broadcast_signal(*, data, **kwargs) ``` Broadcast signal > Broadcasts a signal. **Parameters:** | Parameter | Type | Description | | --------- | ------------------------ | ----------- | | `data` | `SignalBroadcastRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The signal is not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** SignalBroadcastResult - **Return type:** SignalBroadcastResult #### Examples **Broadcast a signal:** ```python def broadcast_signal_example() -> None: client = CamundaClient() result = client.broadcast_signal( data=SignalBroadcastRequest( signal_name="order-cancelled", ) ) print(f"Signal key: {result.signal_key}") ``` ### cancel_batch_operation() ```python async def cancel_batch_operation(batch_operation_key, *, data=, **kwargs) ``` Cancel Batch operation > Cancels a running batch operation. > > This is done asynchronously, the progress can be tracked using the batch operation status endpoint > (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------------------- | ---------------- | ----------------------------------------------------------------------- | | `batch_operation_key` | `str` | System-generated key for an batch operation. Example: 2251799813684321. | | `data` | `Any` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The batch operation was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Cancel a batch operation:** ```python def cancel_batch_operation_example(batch_operation_key: BatchOperationKey) -> None: client = CamundaClient() client.cancel_batch_operation( batch_operation_key=batch_operation_key, ) ``` ### cancel_cluster_rebalance() ```python async def cancel_cluster_rebalance(**kwargs) ``` Stop the running rebalance > Asks the running rebalance to stop once the transfer in flight has finished. Partitions already > transferred keep their new leaders, and those the rebalance had not yet reached keep their current > ones. > > Cancellation requests are idempotent and always accepted. The wasRunning response field can be > used to distinguish a cancellation that found a running rebalance from one that did not. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.BadGatewayError** – If the response status code is 502. The coordinator was reached, but its response was absent or unusable. - **errors.ServiceUnavailableError** – If the response status code is 503. No coordinator is currently available or reachable. - **errors.GatewayTimeoutError** – If the response status code is 504. The coordinator did not answer before the request timeout. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RebalanceCancellationResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** RebalanceCancellationResponse #### Examples **Cancel the running cluster rebalance:** ```python def cancel_cluster_rebalance_example() -> None: client = CamundaClient() # Asks the running rebalance to stop after the in-flight transfer finishes. # Partitions already rebalanced keep their new leaders. result = client.cancel_cluster_rebalance() if result.was_running: print("Rebalance was running and has been asked to stop.") else: print("No rebalance was running.") ``` ### cancel_process_instance() ```python async def cancel_process_instance(process_instance_key, *, data=, **kwargs) ``` Cancel process instance > Cancels a running process instance. As a cancellation includes more than just the removal of the > process instance resource, the cancellation resource must be posted. Cancellation can wait on > listener-related processing; when that processing does not complete in time, this endpoint can > return 504. Other gateway timeout causes are also possible. Retry with backoff and inspect listener > worker availability and logs when this repeats. **Parameters:** | Parameter | Type | Description | | ---------------------- | --------------------------------------------------- | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `data` | `CancelProcessInstanceRequest` \| `None` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The process instance is not found. - **errors.ConflictError** – If the response status code is 409. The process instance cannot be canceled, for example because it is already being terminated or because it is a child process instance. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.GatewayTimeoutError** – If the response status code is 504. The request timed out between the gateway and the broker. For these endpoints, this often happens when user task listeners are configured and the corresponding listener job is not completed within the request timeout. Common causes include no available job workers for the listener type, busy or crashed job workers, or delayed job completion. As with any gateway timeout, general timeout causes (for example transient network issues) can also result in a 504 response. Troubleshooting: - verify that job workers for the listener type are running and healthy - check worker logs for crashes, retries, and completion failures - check network connectivity between workers, gateway, and broker - retry with backoff after transient failures - fail without retries if a problem persists - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Cancel a process instance:** ```python def cancel_process_instance_example(process_definition_id: ProcessDefinitionId) -> None: client = CamundaClient() # Create a process instance and get its key from the response created = client.create_process_instance( data=ProcessCreationById(process_definition_id=process_definition_id) ) # Cancel it using the key from the creation response client.cancel_process_instance( process_instance_key=created.process_instance_key, ) ``` ### cancel_process_instances_batch_operation() ```python async def cancel_process_instances_batch_operation(*, data, **kwargs) ``` Cancel process instances (batch) > Cancels multiple active or suspended process instances. > > Only ACTIVE and SUSPENDED root instances can be cancelled. A state filter narrows the batch > to the given states. Requesting any state other than ACTIVE or SUSPENDED through the $eq or > $in operators is rejected. Other state operators ($neq, $exists, $like) are applied as > given, and the batch remains limited to ACTIVE and SUSPENDED instances. Without a state filter, > both ACTIVE and SUSPENDED instances are selected. Any given filter for parentProcessInstanceKey > is ignored and overridden during this batch operation. > This is done asynchronously, the progress can be tracked using the batchOperationKey from the > response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------- | -------------------------------------------------- | ------------------------------------------------------------------------------------ | | `data` | `ProcessInstanceCancellationBatchOperationRequest` | The process instance filter that defines which process instances should be canceled. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The process instance batch operation failed. More details are provided in the response body. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationCreatedResult - **Return type:** BatchOperationCreatedResult #### Examples **Cancel process instances in batch:** ```python def cancel_process_instances_batch_operation_example() -> None: client = CamundaClient() result = client.cancel_process_instances_batch_operation( data=ProcessInstanceCancellationBatchOperationRequest( filter_=ProcessInstanceCancellationBatchOperationRequestFilter(), ), ) print(f"Batch operation key: {result.batch_operation_key}") ``` ### change_cluster_mode() ```python async def change_cluster_mode(*, mode, dry_run=, **kwargs) ``` Change cluster mode > Transitions the cluster between processing and recovery mode. This is a non-blocking operation: the > request is acknowledged once the change has been accepted, before the transition itself has > completed. Entering recovery mode deactivates all partitions so that only a restricted set of read- > only operations remains available; exiting recovery mode returns the cluster to normal processing. > Returns the planned cluster change so its progress can be monitored via the topology. **Parameters:** | Parameter | Type | Description | | --------- | ----------------- | --------------------------------------------- | | `mode` | `Mode` | The operating mode of a cluster’s partitions. | | `dry_run` | `bool` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterModeChangeResponse - **Return type:** ClusterModeChangeResponse #### Examples **Change cluster mode:** ```python def change_cluster_mode_example() -> None: client = CamundaClient() # Pass dry_run=True to validate the request and inspect the resulting plan # without applying it. Omit it (or set it to False) to trigger the transition. result = client.change_cluster_mode( mode=Mode.RECOVERING, dry_run=True, ) # Operations are grouped by physical tenant; a null tenant means the operation # is not scoped to one, such as a broker lifecycle operation. print(f"Cluster change {result.change_id}:") for group in result.planned_changes: print(f" {group.physical_tenant_id or 'cluster-wide'}:") for operation in group.operations: mode = getattr(operation, "mode", None) suffix = f" -> {mode}" if mode else "" print(f" {operation.operation}{suffix}") ``` ### change_cluster_mode_as_cluster_admin() ```python async def change_cluster_mode_as_cluster_admin(*, mode, physical_tenant_id=, dry_run=, **kwargs) ``` Change the cluster mode of one or every physical tenant > Transitions physical tenants between processing and recovery mode. > > If the physicalTenantId parameter is not provided, all available physical tenants are transitioned > individually. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. **Parameters:** | Parameter | Type | Description | | -------------------- | ----------------- | --------------------------------------------- | | `mode` | `Mode` | The operating mode of a cluster’s partitions. | | `physical_tenant_id` | `str` \| `Unset` | Example: default. | | `dry_run` | `bool` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster. - **errors.ConflictError** – If the response status code is 409. The mode change conflicts with the cluster state, for example because another configuration change is in progress. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterModeChangeResponse - **Return type:** ClusterModeChangeResponse #### Examples **Change cluster mode as cluster admin:** ```python def change_cluster_mode_as_cluster_admin_example() -> None: client = CamundaClient() # The cluster-admin variant can target a single physical tenant. Omit # physical_tenant_id to apply the change to every physical tenant. result = client.change_cluster_mode_as_cluster_admin( mode=Mode.RECOVERING, physical_tenant_id="default", dry_run=True, ) print(f"Cluster change {result.change_id}:") for group in result.planned_changes: print(f" {group.physical_tenant_id or 'cluster-wide'}:") for operation in group.operations: mode = getattr(operation, "mode", None) suffix = f" -> {mode}" if mode else "" print(f" {operation.operation}{suffix}") ``` ### client ```python client: [Client](configuration.md#camunda_orchestration_sdk.Client) | [AuthenticatedClient](configuration.md#camunda_orchestration_sdk.AuthenticatedClient) ``` ### _property_ clock _: Clock_ the injected one when supplied, else the live clock. - **Type:** Clock backing SDK cadence ### complete_job() ```python async def complete_job(job_key, *, data=, **kwargs) ``` Complete job > Complete a job with the given payload, which allows completing the associated service task. **Parameters:** | Parameter | Type | Description | | --------- | --------------------------------- | ---------------------------------------------------------- | | `job_key` | `str` | System-generated key for a job. Example: 2251799813653498. | | `data` | `JobCompletionRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The job with the given key was not found. - **errors.ConflictError** – If the response status code is 409. The job with the given key is in the wrong state currently. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Complete a job:** ```python def complete_job_example(job_key: JobKey) -> None: client = CamundaClient() client.complete_job( job_key=job_key, data=JobCompletionRequest( variables=JobCompletionRequestVariables.from_dict( {"paymentId": "PAY-123", "status": "completed"} ) ), ) ``` ### complete_user_task() ```python async def complete_user_task(user_task_key, *, data=, **kwargs) ``` Complete user task > Completes a user task with the given key. Completion waits for blocking task listeners on this > lifecycle transition. If listener processing is delayed beyond the request timeout, this endpoint > can return 504. Other gateway timeout causes are also possible. Retry with backoff and inspect > listener worker availability and logs when this repeats. **Parameters:** | Parameter | Type | Description | | --------------- | -------------------------------------- | ------------------------------------- | | `user_task_key` | `str` | System-generated key for a user task. | | `data` | `UserTaskCompletionRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The user task with the given key was not found. - **errors.ConflictError** – If the response status code is 409. The user task with the given key is in the wrong state currently. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.GatewayTimeoutError** – If the response status code is 504. The request timed out between the gateway and the broker. For these endpoints, this often happens when user task listeners are configured and the corresponding listener job is not completed within the request timeout. Common causes include no available job workers for the listener type, busy or crashed job workers, or delayed job completion. As with any gateway timeout, general timeout causes (for example transient network issues) can also result in a 504 response. Troubleshooting: - verify that job workers for the listener type are running and healthy - check worker logs for crashes, retries, and completion failures - check network connectivity between workers, gateway, and broker - retry with backoff after transient failures - fail without retries if a problem persists - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Complete a user task:** ```python def complete_user_task_example(user_task_key: UserTaskKey) -> None: client = CamundaClient() variables = UserTaskCompletionRequestVariables() variables["approved"] = True client.complete_user_task( user_task_key=user_task_key, data=UserTaskCompletionRequest( variables=variables, ), ) ``` ### configuration ```python configuration: [CamundaSdkConfiguration](runtime.md#camunda_orchestration_sdk.runtime.configuration_resolver.CamundaSdkConfiguration) ``` ### correlate_message() ```python async def correlate_message(*, data, **kwargs) ``` Correlate message > Publishes a message and correlates it to a subscription. > > If correlation is successful it will return the first process instance key the message correlated > with. > The message is not buffered. > Use the publish message endpoint to send messages that can be buffered. **Parameters:** | Parameter | Type | Description | | --------- | --------------------------- | ----------- | | `data` | `MessageCorrelationRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** MessageCorrelationResult - **Return type:** MessageCorrelationResult #### Examples **Correlate a message:** ```python def correlate_message_example() -> None: client = CamundaClient() result = client.correlate_message( data=MessageCorrelationRequest( name="payment-received", correlation_key="order-12345", ) ) print(f"Message key: {result.message_key}") ``` ### create_admin_user() ```python async def create_admin_user(*, data, **kwargs) ``` 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. **Parameters:** | Parameter | Type | Description | | --------- | ------------- | ----------- | | `data` | `UserRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.ConflictError** – If the response status code is 409. A user with this username already exists. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** UserCreateResult - **Return type:** UserCreateResult #### Examples **Create an admin user:** ```python def create_admin_user_example(username: Username) -> None: client = CamundaClient() result = client.create_admin_user( data=UserRequest( username=username, name="Admin User", email="admin@example.com", password="admin-password", ), ) print(f"Admin user: {result.username}") ``` ### create_agent_instance() ```python async def create_agent_instance(*, data, **kwargs) ``` Create agent instance > Creates a new agent instance. The returned key identifies the instance and must > be used in subsequent update and query calls. **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------ | --------------------------------------- | | `data` | `AgentInstanceCreationRequest` | Request to create a new agent instance. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The elementInstanceKey does not correspond to an active element instance. More details are provided in the response body. - **errors.ConflictError** – If the response status code is 409. An agent instance already exists for the given element instance. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AgentInstanceCreationResult - **Return type:** AgentInstanceCreationResult #### Examples **Create an agent instance:** ```python def create_agent_instance_example( element_instance_key: ElementInstanceKey, job_key: JobKey, # Engine-minted: take the token from the job activation response, never build one. job_lease_token: JobLeaseToken, ) -> None: client = CamundaClient() result = client.create_agent_instance( data=AgentInstanceCreationRequest( element_instance_key=element_instance_key, job_key=job_key, job_lease_token=job_lease_token, history=[ # A CONFIGURATION item is mandatory on creation; it carries the model, # provider and system prompt in role-specific fields, not in content. AgentInstanceHistoryItem( history_item_id=HistoryItemId("configuration-1"), loop_iteration=1, role=AgentInstanceHistoryItemRole.CONFIGURATION, content=[], produced_at=datetime.datetime.now(datetime.timezone.utc), model="gpt-4o", provider="openai", system_prompt=[ TextContent(content_type="TEXT", text="You are a helpful assistant."), ], limits=AgentInstanceHistoryItemLimits( max_model_calls=10, max_tool_calls=20, max_tokens=100_000, ), ), ], ), ) print(f"Created agent instance: {result.agent_instance_key}") ``` ### create_authorization() ```python async def create_authorization(*, data, **kwargs) ``` Create authorization > Create the authorization. **Parameters:** | Parameter | Type | Description | | --------- | -------------------------------------------------------------------- | ----------- | | `data` | `AuthorizationIdBasedRequest` \| `AuthorizationPropertyBasedRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The owner was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AuthorizationCreateResult - **Return type:** AuthorizationCreateResult #### Examples **Create an authorization:** ```python def create_authorization_example() -> None: client = CamundaClient() result = client.create_authorization( data=AuthorizationIdBasedRequest( resource_type=AuthorizationIdBasedRequestResourceType.PROCESS_DEFINITION, permission_types=[ AuthorizationIdBasedRequestPermissionTypesItem.READ, AuthorizationIdBasedRequestPermissionTypesItem.UPDATE, ], resource_id="my-process", owner_type=OwnerTypeEnum.USER, owner_id="user@example.com", ), ) print(f"Authorization key: {result.authorization_key}") ``` ### create_deployment() ```python async def create_deployment(*, data, **kwargs) ``` Deploy resources > Deploys one or more resources, including BPMN processes, DMN decision models, forms, RPA resources, > and generic files. > A deployment can contain any file type. Files that are not interpreted as BPMN, DMN, form, or RPA > resources are stored as deployable generic resources in the engine. > This is an atomic call, i.e. either all resources are deployed or none of them are. **Parameters:** | Parameter | Type | Description | | --------- | ---------------------- | ----------- | | `data` | `CreateDeploymentData` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DeploymentResult - **Return type:** DeploymentResult #### Examples **From files:** ```python def deploy_resources_example() -> None: client = CamundaClient() result = client.deploy_resources_from_files( ["order-process.bpmn", "decision.dmn"] ) print(f"Deployment key: {result.deployment_key}") for process in result.processes: print( f" Process: {process.process_definition_id} v{process.process_definition_version}" ) for decision in result.decisions: print(f" Decision: {decision.decision_definition_id}") ``` **With tenant ID:** ```python def deploy_resources_with_tenant_example() -> None: client = CamundaClient() result = client.deploy_resources_from_files( ["order-process.bpmn"], tenant_id="my-tenant", ) print(f"Deployment key: {result.deployment_key}") print(f"Tenant: {result.tenant_id}") ``` ### create_document() ```python async def create_document(*, data, store_id=, document_id=, **kwargs) ``` Upload document > Upload a document to the Camunda 8 cluster. > > Note that this is currently supported for document stores of type: AWS, Azure, GCP, in-memory (non- > production), local (non-production) **Parameters:** | Parameter | Type | Description | | ------------- | -------------------- | ------------------------------------------------ | | `store_id` | `str` \| `Unset` | | | `document_id` | `str` \| `Unset` | Document Id that uniquely identifies a document. | | `data` | `CreateDocumentData` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnsupportedMediaTypeError** – If the response status code is 415. The server cannot process the request because the media type (Content-Type) of the request payload is not supported by the server for the requested resource and method. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DocumentReference - **Return type:** DocumentReference #### Examples **Create a document:** ```python def create_document_example() -> None: client = CamundaClient() result = client.create_document( data=CreateDocumentData( file=File(payload=io.BytesIO(b"hello world"), file_name="example.txt"), ), ) print(f"Document ID: {result.document_id}") ``` ### create_document_link() ```python async def create_document_link(document_id, *, data, store_id=, content_hash=, **kwargs) ``` Create document link > Create a link to a document in the Camunda 8 cluster. > > Note that this is currently supported for document stores of type: AWS, Azure, GCP **Parameters:** | Parameter | Type | Description | | -------------- | --------------------- | ------------------------------------------------ | | `document_id` | `str` | Document Id that uniquely identifies a document. | | `store_id` | `str` \| `Unset` | | | `content_hash` | `str` \| `Unset` | | | `data` | `DocumentLinkRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DocumentLink - **Return type:** DocumentLink #### Examples **Create a document link:** ```python def create_document_link_example(document_id: DocumentId) -> None: client = CamundaClient() result = client.create_document_link( document_id=document_id, data=DocumentLinkRequest(), ) print(f"Document link: {result.url}") ``` ### create_documents() ```python async def create_documents(*, data, store_id=, **kwargs) ``` Upload multiple documents > Upload multiple documents to the Camunda 8 cluster. > > The caller must provide a file name for each document, which will be used in case of a multi-status > response > to identify which documents failed to upload. The file name can be provided in the Content- > Disposition header > of the file part or in the fileName field of the metadata. You can add a parallel array of > metadata objects. These > are matched with the files based on index, and must have the same length as the files array. > To pass homogenous metadata for all files, spread the metadata over the metadata array. > A filename value provided explicitly via the metadata array in the request overrides the Content- > Disposition header > of the file part. > > In case of a multi-status response, the response body will contain a list of > DocumentBatchProblemDetail objects, > each of which contains the file name of the document that failed to upload and the reason for the > failure. > The client can choose to retry the whole batch or individual documents based on the response. > > Note that this is currently supported for document stores of type: AWS, Azure, GCP, in-memory (non- > production), local (non-production) **Parameters:** | Parameter | Type | Description | | ---------- | --------------------- | ----------- | | `store_id` | `str` \| `Unset` | | | `data` | `CreateDocumentsData` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnsupportedMediaTypeError** – If the response status code is 415. The server cannot process the request because the media type (Content-Type) of the request payload is not supported by the server for the requested resource and method. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DocumentCreationBatchResponse - **Return type:** DocumentCreationBatchResponse #### Examples **Create documents:** ```python def create_documents_example() -> None: client = CamundaClient() result = client.create_documents( data=CreateDocumentsData( files=[ File(payload=io.BytesIO(b"file one"), file_name="one.txt"), File(payload=io.BytesIO(b"file two"), file_name="two.txt"), ], ), ) if not isinstance(result.created_documents, Unset): for doc in result.created_documents: print(f"Created document: {doc.document_id}") ``` ### create_element_instance_variables() ```python async def create_element_instance_variables(element_instance_key, *, data, **kwargs) ``` Update element instance variables > Updates all the variables of a particular scope (for example, process instance, element instance) > with the given variable data. > Specify the element instance in the elementInstanceKey parameter. > Variable updates can be delayed by listener-related processing; if processing exceeds the > request timeout, this endpoint can return 504. Other gateway timeout causes are also > possible. Retry with backoff and inspect listener worker availability and logs when this > repeats. **Parameters:** | Parameter | Type | Description | | ---------------------- | -------------------- | ----------------------------------------------------------------------- | | `element_instance_key` | `str` | System-generated key for a element instance. Example: 2251799813686789. | | `data` | `SetVariableRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.GatewayTimeoutError** – If the response status code is 504. The request timed out between the gateway and the broker. For these endpoints, this often happens when user task listeners are configured and the corresponding listener job is not completed within the request timeout. Common causes include no available job workers for the listener type, busy or crashed job workers, or delayed job completion. As with any gateway timeout, general timeout causes (for example transient network issues) can also result in a 504 response. Troubleshooting: - verify that job workers for the listener type are running and healthy - check worker logs for crashes, retries, and completion failures - check network connectivity between workers, gateway, and broker - retry with backoff after transient failures - fail without retries if a problem persists - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Create element instance variables:** ```python def create_element_instance_variables_example( element_instance_key: ElementInstanceKey, ) -> None: client = CamundaClient() variables = SetVariableRequestVariables.from_dict({"myVar": "myValue"}) client.create_element_instance_variables( element_instance_key=element_instance_key, data=SetVariableRequest( variables=variables, ), ) ``` ### create_global_cluster_variable() ```python async def create_global_cluster_variable(*, data, **kwargs) ``` Create a global-scoped cluster variable > Create a global-scoped cluster variable. **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------ | ----------- | | `data` | `CreateClusterVariableRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.ConflictError** – If the response status code is 409. A cluster variable with this name already exists. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterVariableResult - **Return type:** ClusterVariableResult #### Examples **Create a global cluster variable:** ```python def create_global_cluster_variable_example(name: ClusterVariableName) -> None: client = CamundaClient() result = client.create_global_cluster_variable( data=CreateClusterVariableRequest( name=name, value={"key": "my-value"}, ), ) print(f"Created variable: {result.name}") ``` ### create_global_task_listener() ```python async def create_global_task_listener(*, data, **kwargs) ``` Create global user task listener > Create a new global user task listener. **Parameters:** | Parameter | Type | Description | | --------- | --------------------------------- | ----------- | | `data` | `CreateGlobalTaskListenerRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.ConflictError** – If the response status code is 409. A global listener with this id already exists. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GlobalTaskListenerResult - **Return type:** GlobalTaskListenerResult #### Examples **Create a global task listener:** ```python def create_global_task_listener_example() -> None: client = CamundaClient() result = client.create_global_task_listener( data=CreateGlobalTaskListenerRequest( id="audit-log-listener", event_types=[GlobalTaskListenerEventTypeEnum.COMPLETING], type_="my-task-listener", ), ) print(f"Task listener: {result.id}") ``` ### create_group() ```python async def create_group(*, data=, **kwargs) ``` Create group > Create a new group. > > The supplied groupId is validated against \`\` ^[a-zA-Z0-9_~@.+-]+$ \`\` > (max 256 characters) by IdentifierValidator.validateId in the > runtime. This strict validation applies wherever the Groups API > is available: in OIDC deployments that set > camunda.security.authentication.oidc.groupsClaim the Groups > API (including this endpoint) is disabled entirely, so group > CRUD never sees externally-minted IdP IDs. The BYOG relaxation > only loosens validation when a group is referenced _as a member_ > of a role or tenant (assignRoleToGroup, > assignGroupToTenant); group CRUD itself always uses the strict > default-id regex. The constraint is not advertised on the > GroupId schema so that the same schema can be reused at > member-reference sites without falsely rejecting > externally-minted IdP group IDs there. **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------- | ----------- | | `data` | `GroupCreateRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.ConflictError** – If the response status code is 409. Group with this id already exists. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GroupCreateResult - **Return type:** GroupCreateResult #### Examples **Create a group:** ```python def create_group_example(group_id: GroupId) -> None: client = CamundaClient() result = client.create_group( data=GroupCreateRequest(group_id=group_id, name="Engineering"), ) print(f"Group: {result.group_id}") ``` ### create_job_worker(config: [WorkerConfig](runtime.md#camunda_orchestration_sdk.runtime.job_worker.WorkerConfig), callback: Callable[[[ConnectedJobContext](runtime.md#camunda_orchestration_sdk.runtime.job_worker.ConnectedJobContext)], Coroutine[Any, Any, dict[str, Any] | JobCompletionRequest | None]] | Callable[[[SyncJobContext](runtime.md#camunda_orchestration_sdk.runtime.job_worker.SyncJobContext)], dict[str, Any] | JobCompletionRequest | None], auto_start: bool = True, , execution_strategy: Literal['auto', 'async', 'thread'] = 'auto', startup_jitter_max_seconds: float | None = None) → [JobWorker](runtime.md#camunda_orchestration_sdk.runtime.job_worker.JobWorker) ### create_job_worker(config: [WorkerConfig](runtime.md#camunda_orchestration_sdk.runtime.job_worker.WorkerConfig), callback: Callable[[[JobContext](runtime.md#camunda_orchestration_sdk.runtime.job_worker.JobContext)], Coroutine[Any, Any, dict[str, Any] | JobCompletionRequest | None]] | Callable[[[JobContext](runtime.md#camunda_orchestration_sdk.runtime.job_worker.JobContext)], dict[str, Any] | JobCompletionRequest | None], auto_start: bool = True, , execution_strategy: Literal['process'], startup_jitter_max_seconds: float | None = None) → [JobWorker](runtime.md#camunda_orchestration_sdk.runtime.job_worker.JobWorker) **Parameters:** | Parameter | Type | Description | | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | | `config` | [WorkerConfig](runtime.md#camunda_orchestration_sdk.runtime.job_worker.WorkerConfig) | | | `callback` | Callable [ [[ConnectedJobContext](runtime.md#camunda_orchestration_sdk.runtime.job_worker.ConnectedJobContext) ] , Coroutine [Any , Any , dict [str , Any ] \| `JobCompletionRequest` \| None ] ] \| Callable [ [[SyncJobContext](runtime.md#camunda_orchestration_sdk.runtime.job_worker.SyncJobContext) ] , dict [str , Any ] \| `JobCompletionRequest` \| None ] \| Callable [ [[JobContext](runtime.md#camunda_orchestration_sdk.runtime.job_worker.JobContext) ] , Coroutine [Any , Any , dict [str , Any ] \| `JobCompletionRequest` \| None ] ] \| Callable [ [[JobContext](runtime.md#camunda_orchestration_sdk.runtime.job_worker.JobContext) ] , dict [str , Any ] \| `JobCompletionRequest` \| None ] | | | `auto_start` | `bool` | | | `execution_strategy` | Literal [ 'auto' , 'async' , 'thread' , 'process' ] | | | `startup_jitter_max_seconds` | `float` \| `None` | | - **Return type:** [_JobWorker_](runtime.md#camunda_orchestration_sdk.runtime.job_worker.JobWorker) ### create_mapping_rule() ```python async def create_mapping_rule(*, data=, **kwargs) ``` Create mapping rule > Create a new mapping rule **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------------- | ----------- | | `data` | `MappingRuleCreateRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. The request to create a mapping rule was denied. More details are provided in the response body. - **errors.NotFoundError** – If the response status code is 404. The request to create a mapping rule was denied. - **errors.ConflictError** – If the response status code is 409. Mapping rule with this id already exists. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** MappingRuleCreateResult - **Return type:** MappingRuleCreateResult #### Examples **Create a mapping rule:** ```python def create_mapping_rule_example(mapping_rule_id: MappingRuleId) -> None: client = CamundaClient() result = client.create_mapping_rule( data=MappingRuleCreateRequest( mapping_rule_id=mapping_rule_id, claim_name="groups", claim_value="engineering", name="Engineering Group Mapping", ), ) print(f"Mapping rule: {result.mapping_rule_id}") ``` ### create_process_instance() ```python async def create_process_instance(*, data, **kwargs) ``` Create process instance > Creates and starts an instance of the specified process. > > The process definition to use to create the instance can be specified either using its unique key > (as returned by Deploy resources), or using the BPMN process id and a version. > If only the process definition id is given, the latest ACTIVE version is used. > If no ACTIVE version exists, the request is rejected as not found. > > Waits for the completion of the process instance before returning a result > when awaitCompletion is enabled. **Parameters:** | Parameter | Type | Description | | --------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | `data` | `ProcessCreationById` \| `ProcessCreationByKey` | Instructions for creating a process instance. The process definition can be specified either by id or by key. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ConflictError** – If the response status code is 409. The process instance creation was rejected due to a business ID uniqueness conflict. This can happen only when Business ID Uniqueness Control is enabled and an active root process instance with the provided business ID already exists for the same process definition and tenant. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.GatewayTimeoutError** – If the response status code is 504. The process instance creation request timed out in the gateway. This can happen if the awaitCompletion request parameter is set to true and the created process instance did not complete within the defined request timeout. This often happens when the created instance is not fully automated or contains wait states. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** CreateProcessInstanceResult - **Return type:** CreateProcessInstanceResult #### Examples **By key:** ```python def create_process_instance_by_key_example() -> None: client = CamundaClient() # Deploy a process and obtain the typed key from the response deployment = client.deploy_resources_from_files(["order-process.bpmn"]) process_key = deployment.processes[0].process_definition_key # Use the typed key directly — no manual string lifting needed result = client.create_process_instance( data=ProcessCreationByKey( process_definition_key=process_key, ) ) print(f"Process instance key: {result.process_instance_key}") ``` **By stored key:** ```python def create_process_instance_by_key_from_storage_example() -> None: client = CamundaClient() # When restoring a key from a database or message queue, # wrap the raw string with the semantic type constructor: stored_key = "2251799813685249" # e.g. from a DB row result = client.create_process_instance( data=ProcessCreationByKey( process_definition_key=ProcessDefinitionKey(stored_key), ) ) print(f"Process instance key: {result.process_instance_key}") ``` **By ID:** ```python def create_process_instance_by_id_example(process_definition_id: ProcessDefinitionId) -> None: client = CamundaClient() result = client.create_process_instance( data=ProcessCreationById( process_definition_id=process_definition_id, ) ) print(f"Process instance key: {result.process_instance_key}") ``` ### create_role() ```python async def create_role(*, data=, **kwargs) ``` Create role > Create a new role. **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------ | ----------- | | `data` | `RoleCreateRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.ConflictError** – If the response status code is 409. Role with this id already exists. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RoleCreateResult - **Return type:** RoleCreateResult #### Examples **Create a role:** ```python def create_role_example(role_id: RoleId) -> None: client = CamundaClient() result = client.create_role( data=RoleCreateRequest(role_id=role_id, name="Developer"), ) print(f"Role: {result.role_id}") ``` ### create_tenant() ```python async def create_tenant(*, data, **kwargs) ``` Create tenant > Creates a new tenant. **Parameters:** | Parameter | Type | Description | | --------- | --------------------- | ----------- | | `data` | `TenantCreateRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The resource was not found. - **errors.ConflictError** – If the response status code is 409. Tenant with this id already exists. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TenantCreateResult - **Return type:** TenantCreateResult #### Examples **Create a tenant:** ```python def create_tenant_example(tenant_id: TenantId) -> None: client = CamundaClient() result = client.create_tenant( data=TenantCreateRequest( tenant_id=tenant_id, name="Acme Corporation", ), ) print(f"Tenant: {result.tenant_id}") ``` ### create_tenant_cluster_variable() ```python async def create_tenant_cluster_variable(tenant_id, *, data, **kwargs) ``` Create a tenant-scoped cluster variable > Create a new cluster variable for the given tenant. **Parameters:** | Parameter | Type | Description | | ----------- | ------------------------------ | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `data` | `CreateClusterVariableRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The tenant with the given ID was not found. - **errors.ConflictError** – If the response status code is 409. A cluster variable with this name already exists for the given tenant. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterVariableResult - **Return type:** ClusterVariableResult #### Examples **Create a tenant cluster variable:** ```python def create_tenant_cluster_variable_example(tenant_id: TenantId, name: ClusterVariableName) -> None: client = CamundaClient() result = client.create_tenant_cluster_variable( tenant_id=tenant_id, data=CreateClusterVariableRequest( name=name, value={"key": "tenant-value"}, ), ) print(f"Created variable: {result.name}") ``` ### create_user() ```python async def create_user(*, data, **kwargs) ``` Create user > Create a new user. **Parameters:** | Parameter | Type | Description | | --------- | ------------- | ----------- | | `data` | `UserRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.ConflictError** – If the response status code is 409. A user with this username already exists. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** UserCreateResult - **Return type:** UserCreateResult #### Examples **Create a user:** ```python def create_user_example(username: Username) -> None: client = CamundaClient() result = client.create_user( data=UserRequest( username=username, name="Jane Doe", email="jdoe@example.com", password="secure-password", ), ) print(f"Created user: {result.username}") ``` ### delete_authorization() ```python async def delete_authorization(authorization_key, **kwargs) ``` Delete authorization > Deletes the authorization with the given key. **Parameters:** | Parameter | Type | Description | | ------------------- | ----- | --------------------------------------------------------------------- | | `authorization_key` | `str` | System-generated key for an authorization. Example: 2251799813684332. | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The authorization with the authorizationKey was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete an authorization:** ```python def delete_authorization_example(authorization_key: AuthorizationKey) -> None: client = CamundaClient() client.delete_authorization( authorization_key=authorization_key, ) ``` ### delete_decision_instance() ```python async def delete_decision_instance(decision_evaluation_key, *, data=, **kwargs) ``` Delete decision instance > Delete all associated decision evaluations based on provided key. **Parameters:** | Parameter | Type | Description | | ------------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------- | | `decision_evaluation_key` | `str` | System-generated key for a decision evaluation. Example: 2251792362345323. | | `data` | `DeleteDecisionInstanceRequest` \| `None` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The decision instance is not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a decision instance:** ```python def delete_decision_instance_example(decision_evaluation_key: DecisionEvaluationKey) -> None: client = CamundaClient() client.delete_decision_instance( decision_evaluation_key=decision_evaluation_key, ) ``` ### delete_decision_instances_batch_operation() ```python async def delete_decision_instances_batch_operation(*, data, **kwargs) ``` Delete decision instances (batch) > Delete multiple decision instances. This will delete the historic data from secondary storage. > > This is done asynchronously, the progress can be tracked using the batchOperationKey from the > response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------- | ----------------------------------------------- | ------------------------------------------------------------------------------------- | | `data` | `DecisionInstanceDeletionBatchOperationRequest` | The decision instance filter that defines which decision instances should be deleted. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The decision instance batch operation failed. More details are provided in the response body. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationCreatedResult - **Return type:** BatchOperationCreatedResult #### Examples **Delete decision instances in batch:** ```python def delete_decision_instances_batch_operation_example() -> None: client = CamundaClient() result = client.delete_decision_instances_batch_operation( data=DecisionInstanceDeletionBatchOperationRequest( filter_=DecisionInstanceDeletionBatchOperationRequestFilter(), ), ) print(f"Batch operation key: {result.batch_operation_key}") ``` ### delete_document() ```python async def delete_document(document_id, *, store_id=, **kwargs) ``` Delete document > Delete a document from the Camunda 8 cluster. > > Note that this is currently supported for document stores of type: AWS, Azure, GCP, in-memory (non- > production), local (non-production) **Parameters:** | Parameter | Type | Description | | ------------- | ---------------- | ------------------------------------------------ | | `document_id` | `str` | Document Id that uniquely identifies a document. | | `store_id` | `str` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.NotFoundError** – If the response status code is 404. The document with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a document:** ```python def delete_document_example(document_id: DocumentId) -> None: client = CamundaClient() client.delete_document(document_id=document_id) ``` ### delete_global_cluster_variable() ```python async def delete_global_cluster_variable(name, **kwargs) ``` Delete a global-scoped cluster variable > Delete a global-scoped cluster variable. **Parameters:** | Parameter | Type | Description | | --------- | ----- | --------------------------------------------------------------------------------------------------------------------- | | `name` | `str` | The name of a cluster variable. Unique within its scope (global or tenant- specific). Example: feature-flag-checkout. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Cluster variable not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a global cluster variable:** ```python def delete_global_cluster_variable_example(name: ClusterVariableName) -> None: client = CamundaClient() client.delete_global_cluster_variable(name=name) ``` ### delete_global_task_listener() ```python async def delete_global_task_listener(id, **kwargs) ``` Delete global user task listener > Deletes a global user task listener. **Parameters:** | Parameter | Type | Description | | --------- | ----- | ---------------------------------------------------------------------- | | `id` | `str` | The user-defined id for the global listener Example: GlobalListener_1. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The global user task listener was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a global task listener:** ```python def delete_global_task_listener_example(listener_id: GlobalListenerId) -> None: client = CamundaClient() client.delete_global_task_listener(id=listener_id) ``` ### delete_group() ```python async def delete_group(group_id, **kwargs) ``` Delete group > Deletes the group with the given ID. **Parameters:** | Parameter | Type | Description | | ---------- | ----- | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The group with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a group:** ```python def delete_group_example(group_id: GroupId) -> None: client = CamundaClient() client.delete_group(group_id=group_id) ``` ### delete_history_backup() ```python async def delete_history_backup(backup_id, **kwargs) ``` Delete history backup > Deletes the history backup with the given id, by deleting every snapshot that makes it > up. > > Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ----------- | | `backup_id` | int) – | | The id of the backup. Must be a positive numerical value. As backups are logically ordered by their ids (ascending), each successive backup must use a higher id than the previous one. > Example: 1. - **kwargs** (_Any_) - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. The request is forbidden for one of three reasons: the authenticated caller lacks the required BACKUP permission; the cluster’s secondary storage is neither Elasticsearch nor OpenSearch and therefore cannot serve history backups; or the physical tenant’s snapshot repository is absent from the store — configured under a name the store does not have, or not configured at all. The problem detail says which applies. The latter two are deployment faults the caller cannot correct by changing its request. - **errors.NotFoundError** – If the response status code is 404. A backup with the given id does not exist. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a history backup:** ```python def delete_history_backup_example(backup_id: int) -> None: client = CamundaClient() client.delete_history_backup(backup_id=backup_id) ``` ### delete_history_backup_as_cluster_admin() ```python async def delete_history_backup_as_cluster_admin(backup_id, *, physical_tenant_id=, **kwargs) ``` Delete a history backup across physical tenants > Deletes the history backup with the given id from every physical tenant of the cluster, or from the > one named by physicalTenantId. A tenant that does not hold the backup has already reached the > requested end state, so it counts as deleted rather than as a failure. > > The request is all-or-nothing: a physical tenant the backup cannot be deleted from fails the whole > request, and the deletions that already succeeded on other tenants are not undone. Narrow the > request with physicalTenantId to delete from the tenants that can still be reached. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Only available on clusters > whose secondary storage is Elasticsearch or OpenSearch. Use DELETE /v2/backups/history/{backupId} > to act as a single physical tenant. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ----------- | | `backup_id` | int) – | | The id of the backup. Must be a positive numerical value. As backups are logically ordered by their ids (ascending), each successive backup must use a higher id than the previous one. > Example: 1. - **physical_tenant_id** (_str_ _|_ _Unset_) – Example: default. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. The cluster’s secondary storage cannot serve history backups, or a targeted physical tenant’s snapshot repository is absent from the store. Unlike the per-physical-tenant backup endpoints, the cluster-admin surface performs no fine-grained authorization, so a missing BACKUP permission is never the reason. Deletion fans out with no preceding check, so an absent repository is found only once that tenant is reached, by which time the backup may already be deleted from the others; those deletions are not undone. Narrow the request with physicalTenantId to work with the tenants whose repository is usable. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster, or every targeted physical tenant was reached and none of them holds a backup with the given id. - **errors.InternalServerErrorError** – If the response status code is 500. The backup could not be deleted from every targeted physical tenant, because one of them hit an internal error, so it may still exist on some of them. The deletions that already succeeded are not undone, so a retry has only the remaining tenants left to reach. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a history backup across physical tenants:** ```python def delete_history_backup_as_cluster_admin_example(backup_id: int) -> None: client = CamundaClient() # Deletes the backup from every physical tenant. A tenant that does not hold # it already counts as deleted, so this is idempotent when all tenants are # reachable. Use `physical_tenant_id` to narrow to a single tenant. client.delete_history_backup_as_cluster_admin(backup_id=backup_id) ``` ### delete_mapping_rule() ```python async def delete_mapping_rule(mapping_rule_id, **kwargs) ``` Delete a mapping rule > Deletes the mapping rule with the given ID. **Parameters:** | Parameter | Type | Description | | ----------------- | ----- | ------------------------------------------------------------------ | | `mapping_rule_id` | `str` | The unique identifier of a mapping rule. Example: my-mapping-rule. | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The mapping rule with the mappingRuleId was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a mapping rule:** ```python def delete_mapping_rule_example(mapping_rule_id: MappingRuleId) -> None: client = CamundaClient() client.delete_mapping_rule(mapping_rule_id=mapping_rule_id) ``` ### delete_process_instance() ```python async def delete_process_instance(process_instance_key, *, data=, **kwargs) ``` Delete process instance > Deletes a process instance. Only instances that are completed or terminated can be deleted. **Parameters:** | Parameter | Type | Description | | ---------------------- | --------------------------------------------------- | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `data` | `DeleteProcessInstanceRequest` \| `None` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The process instance is not found. - **errors.ConflictError** – If the response status code is 409. The process instance is not in a completed or terminated state and cannot be deleted. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a process instance:** ```python def delete_process_instance_example(process_instance_key: ProcessInstanceKey) -> None: client = CamundaClient() client.delete_process_instance( process_instance_key=process_instance_key, ) ``` ### delete_process_instances_batch_operation() ```python async def delete_process_instances_batch_operation(*, data, **kwargs) ``` Delete process instances (batch) > Delete multiple process instances. This will delete the historic data from secondary storage. > > Only process instances in a final state (COMPLETED or TERMINATED) can be deleted. > This is done asynchronously, the progress can be tracked using the batchOperationKey from the > response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------- | ---------------------------------------------- | ----------------------------------------------------------------------------------- | | `data` | `ProcessInstanceDeletionBatchOperationRequest` | The process instance filter that defines which process instances should be deleted. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The process instance batch operation failed. More details are provided in the response body. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationCreatedResult - **Return type:** BatchOperationCreatedResult #### Examples **Delete process instances in batch:** ```python def delete_process_instances_batch_operation_example() -> None: client = CamundaClient() result = client.delete_process_instances_batch_operation( data=ProcessInstanceDeletionBatchOperationRequest( filter_=ProcessInstanceCancellationBatchOperationRequestFilter(), ), ) print(f"Batch operation key: {result.batch_operation_key}") ``` ### delete_resource() ```python async def delete_resource(resource_key, *, data=, **kwargs) ``` 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. History deletion is supported for process definitions and decision requirements > definitions; for other resource types (forms, generic resources) the flag is ignored and no > history is deleted. > > The two supported types differ in how the history is removed. For a decision requirements > definition the history is deleted asynchronously via a batch operation whose details are > returned in the batchOperation field of the response. For a process definition that still > exists in the runtime state, the definition first drains its running instances and its > history is deleted asynchronously once the definition is fully removed cluster-wide; no batch > operation is returned in the response. If the process definition has already been removed > from the runtime state and the deletion is later re-triggered with deleteHistory set to > true, a batch operation is created immediately and returned in the batchOperation field. **Parameters:** | Parameter | Type | Description | | -------------- | -------------------------------------------- | ------------------------------------------ | | `resource_key` | `str` | The system-assigned key for this resource. | | `data` | `DeleteResourceRequest` \| `None` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The resource is not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DeleteResourceResponse - **Return type:** DeleteResourceResponse #### Examples **Delete a resource:** ```python def delete_resource_example() -> None: client = CamundaClient() # Use a resource key from a previous deployment response client.delete_resource(resource_key="2251799813685249") ``` ### delete_role() ```python async def delete_role(role_id, **kwargs) ``` Delete role > Deletes the role with the given ID. **Parameters:** | Parameter | Type | Description | | --------- | ----- | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The role with the ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a role:** ```python def delete_role_example(role_id: RoleId) -> None: client = CamundaClient() client.delete_role(role_id=role_id) ``` ### delete_runtime_backup() ```python async def delete_runtime_backup(backup_id, **kwargs) ``` Delete runtime backup > Deletes the runtime backup with the given id. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ----------- | | `backup_id` | int) – | | The id of the backup. Must be a positive numerical value. As backups are logically ordered by their ids (ascending), each successive backup must use a higher id than the previous one. > Example: 1. - **kwargs** (_Any_) - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a runtime backup:** ```python def delete_runtime_backup_example(backup_id: int) -> None: client = CamundaClient() client.delete_runtime_backup(backup_id=backup_id) ``` ### delete_runtime_backup_as_cluster_admin() ```python async def delete_runtime_backup_as_cluster_admin(backup_id, *, physical_tenant_id=, **kwargs) ``` Delete a runtime backup across physical tenants > Deletes the runtime backup with the given id from every physical tenant of the cluster, or from the > one named by physicalTenantId. A tenant that does not hold the backup has already reached the > requested end state, so it counts as deleted rather than as a failure — the same as deleting an > unknown backup id through the per-physical-tenant endpoint. > > The request is all-or-nothing: a physical tenant the backup cannot be deleted from fails the whole > request, and the deletions that already succeeded on other tenants are not undone. Narrow the > request with physicalTenantId to delete from the tenants that can still be reached. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Use DELETE > /v2/backups/runtime/{backupId} to act as a single physical tenant. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ----------- | | `backup_id` | int) – | | The id of the backup. Must be a positive numerical value. As backups are logically ordered by their ids (ascending), each successive backup must use a higher id than the previous one. > Example: 1. - **physical_tenant_id** (_str_ _|_ _Unset_) – Example: default. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster. - **errors.InternalServerErrorError** – If the response status code is 500. The backup could not be deleted from every targeted physical tenant, so it may still exist on some of them. The deletions that already succeeded are not undone, so a retry has only the remaining tenants left to reach. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a runtime backup across physical tenants:** ```python def delete_runtime_backup_as_cluster_admin_example(backup_id: int) -> None: client = CamundaClient() # Deletes the backup from every physical tenant. A tenant that does not hold # it already counts as deleted, so this is idempotent when all tenants are # reachable. Use `physical_tenant_id` to narrow to a single tenant. client.delete_runtime_backup_as_cluster_admin(backup_id=backup_id) ``` ### delete_runtime_backup_state() ```python async def delete_runtime_backup_state(**kwargs) ``` Delete runtime backup state > Resets the runtime backup state of every partition of the physical tenant, clearing > all checkpoint info, backup info, checkpoint metadata, and backup ranges. Used when > switching backup stores. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Parameters:** **kwargs** (_Any_) - **Return type:** None #### Examples **Delete the runtime backup state:** ```python def delete_runtime_backup_state_example() -> None: client = CamundaClient() # Clears all checkpoint info, backup info, checkpoint metadata, and backup # ranges of every partition. Used when switching backup stores. client.delete_runtime_backup_state() ``` ### delete_runtime_backup_state_as_cluster_admin() ```python async def delete_runtime_backup_state_as_cluster_admin(*, physical_tenant_id=, **kwargs) ``` Delete runtime backup state across physical tenants > Resets the runtime backup state of every partition of every physical tenant of the cluster, or of > the one named by physicalTenantId, clearing all checkpoint info, backup info, checkpoint metadata, > and backup ranges. Used when switching backup stores. > > The request is all-or-nothing: a physical tenant whose state cannot be reset fails the whole > request, and the resets that already succeeded on other tenants are not undone. Narrow the request > with physicalTenantId to reset the tenants that can still be reached. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Use DELETE > /v2/backups/runtime/state to act as a single physical tenant. **Parameters:** | Parameter | Type | Description | | -------------------- | ---------------- | ----------------- | | `physical_tenant_id` | `str` \| `Unset` | Example: default. | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster. - **errors.InternalServerErrorError** – If the response status code is 500. The state could not be reset on every targeted physical tenant, so it may still be set on some of them. The resets that already succeeded are not undone, so a retry has only the remaining tenants left to reach. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete runtime backup state across physical tenants:** ```python def delete_runtime_backup_state_as_cluster_admin_example() -> None: client = CamundaClient() # Clears all checkpoint info, backup info, checkpoint metadata, and backup # ranges of every partition of every physical tenant. Used when switching # backup stores. Use `physical_tenant_id` to narrow to a single tenant. client.delete_runtime_backup_state_as_cluster_admin() ``` ### delete_tenant() ```python async def delete_tenant(tenant_id, **kwargs) ``` Delete tenant > Deletes an existing tenant. **Parameters:** | Parameter | Type | Description | | ----------- | ----- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The tenant was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a tenant:** ```python def delete_tenant_example(tenant_id: TenantId) -> None: client = CamundaClient() client.delete_tenant(tenant_id=tenant_id) ``` ### delete_tenant_cluster_variable() ```python async def delete_tenant_cluster_variable(tenant_id, name, **kwargs) ``` Delete a tenant-scoped cluster variable > Delete a tenant-scoped cluster variable. **Parameters:** | Parameter | Type | Description | | ----------- | ----- | --------------------------------------------------------------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `name` | `str` | The name of a cluster variable. Unique within its scope (global or tenant- specific). Example: feature-flag-checkout. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Cluster variable not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a tenant cluster variable:** ```python def delete_tenant_cluster_variable_example(tenant_id: TenantId, name: ClusterVariableName) -> None: client = CamundaClient() client.delete_tenant_cluster_variable( tenant_id=tenant_id, name=name, ) ``` ### delete_user() ```python async def delete_user(username, **kwargs) ``` Delete user > Deletes a user. **Parameters:** | Parameter | Type | Description | | ---------- | ----- | -------------------------------------------- | | `username` | `str` | The unique name of a user. Example: swillis. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The user is not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a user:** ```python def delete_user_example(username: Username) -> None: client = CamundaClient() client.delete_user(username=username) ``` ### deploy_resources_from_files() ```python async def deploy_resources_from_files(files, tenant_id=None) ``` Deploy BPMN/DMN/Form resources from local files. Async variant of [`CamundaClient.deploy_resources_from_files()`](client.md#camunda_orchestration_sdk.CamundaClient.deploy_resources_from_files). This reads each file path in `files` as bytes, wraps them into `camunda_orchestration_sdk.types.File`, calls [`create_deployment()`](#create_deployment), and returns an `ExtendedDeploymentResult`. Note: file reads are currently performed using blocking I/O (`open(...).read()`). If you need fully non-blocking file access, load the bytes yourself and call [`create_deployment()`](#create_deployment). **Parameters:** | Parameter | Type | Description | | ----------- | ------------------- | ------------------------------------------------------------------------ | | `files` | list [str \| Path ] | File paths (`str` or `Path`) to deploy. | | `tenant_id` | `str` \| `None` | Optional tenant identifier. If not provided, the default tenant is used. | - **Returns:** The deployment result with extracted resource lists. - **Return type:** ExtendedDeploymentResult - **Raises:** - **FileNotFoundError** – If any file path does not exist. - **PermissionError** – If any file path cannot be read. - **IsADirectoryError** – If any file path is a directory. - **OSError** – For other I/O failures while reading files. - **Exception** – Propagates any exception raised by [`create_deployment()`](#create_deployment) (including typed API errors in `camunda_orchestration_sdk.errors` and `httpx.TimeoutException`). ### evaluate_conditionals() ```python async def evaluate_conditionals(*, data, **kwargs) ``` 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. **Parameters:** | Parameter | Type | Description | | --------- | ---------------------------------- | ----------- | | `data` | `ConditionalEvaluationInstruction` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. The client is not authorized to start process instances for the specified process definition. If a processDefinitionKey is not provided, this indicates that the client is not authorized to start process instances for at least one of the matched process definitions. - **errors.NotFoundError** – If the response status code is 404. The process definition was not found for the given processDefinitionKey. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** EvaluateConditionalResult - **Return type:** EvaluateConditionalResult #### Examples **Evaluate conditionals:** ```python def evaluate_conditionals_example() -> None: client = CamundaClient() result = client.evaluate_conditionals( data=ConditionalEvaluationInstruction( variables=ConditionalEvaluationInstructionVariables.from_dict({"orderReady": True}), ), ) print(f"Result: {result}") ``` ### evaluate_decision() ```python async def evaluate_decision(*, data, **kwargs) ``` Evaluate decision > Evaluates a decision. > > You specify the decision to evaluate either by using its unique key (as returned by > DeployResource), or using the decision ID. When using the decision ID, the latest deployed > version of the decision is used. **Parameters:** | Parameter | Type | Description | | --------- | ----------------------------------------------------- | ----------- | | `data` | `DecisionEvaluationByID` \| `DecisionEvaluationByKey` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The decision is not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** EvaluateDecisionResult - **Return type:** EvaluateDecisionResult #### Examples **By key:** ```python def evaluate_decision_by_key_example(decision_definition_key: DecisionDefinitionKey) -> None: client = CamundaClient() result = client.evaluate_decision( data=DecisionEvaluationByKey( decision_definition_key=decision_definition_key, ) ) print(f"Decision key: {result.decision_definition_key}") ``` **By ID:** ```python def evaluate_decision_by_id_example(decision_definition_id: DecisionDefinitionId) -> None: client = CamundaClient() result = client.evaluate_decision( data=DecisionEvaluationByID( decision_definition_id=decision_definition_id, ) ) print(f"Decision key: {result.decision_definition_key}") ``` ### evaluate_expression() ```python async def evaluate_expression(*, data, **kwargs) ``` Evaluate an expression > Evaluates a FEEL expression and returns the result. Supports references to tenant scoped > cluster variables when a tenant ID is provided. Optionally, provide a scopeKey to make the > variables of a specific process instance or element instance visible while evaluating the > expression. **Parameters:** | Parameter | Type | Description | | --------- | ----------------------------- | ----------- | | `data` | `ExpressionEvaluationRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ExpressionEvaluationResult - **Return type:** ExpressionEvaluationResult #### Examples **Evaluate an expression:** ```python def evaluate_expression_example() -> None: client = CamundaClient() result = client.evaluate_expression( data=ExpressionEvaluationRequest( expression="= 1 + 2", ), ) print(f"Result: {result.result}") ``` ### fail_job() ```python async def fail_job(job_key, *, data=, **kwargs) ``` Fail job > Mark the job as failed. **Parameters:** | Parameter | Type | Description | | --------- | --------------------------- | ---------------------------------------------------------- | | `job_key` | `str` | System-generated key for a job. Example: 2251799813653498. | | `data` | `JobFailRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The job with the given jobKey is not found. It was completed by another worker, or the process instance itself was canceled. - **errors.ConflictError** – If the response status code is 409. The job with the given key is in the wrong state (i.e: not ACTIVATED or ACTIVATABLE). The job was failed by another worker with retries = 0, and the process is now in an incident state. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Fail a job with retry:** ```python def fail_job_example(job_key: JobKey) -> None: client = CamundaClient() client.fail_job( job_key=job_key, data=JobFailRequest( retries=2, error_message="Payment gateway timeout", retry_back_off=5000, ), ) ``` ### get_agent_definition() ```python async def get_agent_definition(agent_definition_key, *, consistency=None, **kwargs) ``` Get agent definition > Returns an agent definition by key. **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------ | ------------------------------------------------------------------------ | | `agent_definition_key` | `str` | System-generated key for an agent definition. Example: 2251799813691958. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The agent definition with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AgentDefinitionResult - **Return type:** AgentDefinitionResult #### Examples **Get an agent definition:** ```python def get_agent_definition_example(agent_definition_key: AgentDefinitionKey) -> None: client = CamundaClient() agent_definition = client.get_agent_definition( agent_definition_key=agent_definition_key ) print(f"Agent definition name: {agent_definition.name}") ``` ### get_agent_instance() ```python async def get_agent_instance(agent_instance_key, *, consistency=None, **kwargs) ``` Get agent instance > Returns agent instance as JSON. **Parameters:** | Parameter | Type | Description | | -------------------- | ------------------------------ | ---------------------------------------------------------------------- | | `agent_instance_key` | `str` | System-generated key for an agent instance. Example: 4503599627370496. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The agent instance with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AgentInstanceResult - **Return type:** AgentInstanceResult #### Examples **Get an agent instance:** ```python def get_agent_instance_example(agent_instance_key: AgentInstanceKey) -> None: client = CamundaClient() agent_instance = client.get_agent_instance(agent_instance_key=agent_instance_key) print(f"Agent instance status: {agent_instance.status}") ``` ### get_audit_log() ```python async def get_audit_log(audit_log_key, *, consistency=None, **kwargs) ``` Get audit log > Get an audit log entry by auditLogKey. **Parameters:** | Parameter | Type | Description | | --------------- | ------------------------------ | ------------------------------------------------------------------------ | | `audit_log_key` | `str` | System-generated key for an audit log entry. Example: 22517998136843567. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The audit log with the given key was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AuditLogResult - **Return type:** AuditLogResult #### Examples **Get an audit log entry:** ```python def get_audit_log_example(audit_log_key: AuditLogKey) -> None: client = CamundaClient() result = client.get_audit_log(audit_log_key=audit_log_key) print(f"Audit log: {result.audit_log_key}") ``` ### get_authentication() ```python async def get_authentication(**kwargs) ``` Get current user > Retrieves the current authenticated user. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** CamundaUserResult - **Parameters:** **kwargs** (_Any_) - **Return type:** CamundaUserResult #### Examples **Get authentication info:** ```python def get_authentication_example() -> None: client = CamundaClient() result = client.get_authentication() print(f"Authenticated user: {result.username}") ``` ### get_authorization() ```python async def get_authorization(authorization_key, *, consistency=None, **kwargs) ``` Get authorization > Get authorization by the given key. **Parameters:** | Parameter | Type | Description | | ------------------- | ------------------------------ | --------------------------------------------------------------------- | | `authorization_key` | `str` | System-generated key for an authorization. Example: 2251799813684332. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The authorization with the given key was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AuthorizationResult - **Return type:** AuthorizationResult #### Examples **Get an authorization:** ```python def get_authorization_example(authorization_key: AuthorizationKey) -> None: client = CamundaClient() result = client.get_authorization( authorization_key=authorization_key, ) print(f"Resource type: {result.resource_type}") ``` ### get_batch_operation() ```python async def get_batch_operation(batch_operation_key, *, consistency=None, **kwargs) ``` Get batch operation > Get batch operation by key. **Parameters:** | Parameter | Type | Description | | --------------------- | ------------------------------ | ----------------------------------------------------------------------- | | `batch_operation_key` | `str` | System-generated key for an batch operation. Example: 2251799813684321. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The batch operation is not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationResponse - **Return type:** BatchOperationResponse #### Examples **Get a batch operation:** ```python def get_batch_operation_example(batch_operation_key: BatchOperationKey) -> None: client = CamundaClient() result = client.get_batch_operation( batch_operation_key=batch_operation_key, ) print(f"Batch operation: {result.batch_operation_key}") ``` ### get_cluster_exporting_status() ```python async def get_cluster_exporting_status(**kwargs) ``` Get exporting status of the whole cluster > Returns the exporting status of the whole cluster, folded over the exporting status of every > physical tenant. Only PAUSED and SOFT_PAUSED confirm that exporting is paused cluster-wide; > every other value means at least one physical tenant is not paused, so callers should keep polling. > A physical tenant that itself reports MIXED makes the whole cluster MIXED. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ExportingStatusResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** ExportingStatusResponse #### Examples **Get exporting status for the whole cluster:** ```python def get_cluster_exporting_status_example() -> None: client = CamundaClient() # Requires the cluster-admin security chain — not the Orchestration Cluster # user credentials. Only `PAUSED` and `SOFT_PAUSED` confirm a cluster-wide # pause; any other value means at least one physical tenant is still active. result = client.get_cluster_exporting_status() print(f"Cluster exporting status: {result.status}") ``` ### get_cluster_rebalance() ```python async def get_cluster_rebalance(**kwargs) ``` Report the cluster’s current leadership balance > Reports whether the cluster is currently balanced, the current leadership state of every partition, > and what became of the last rebalance to finish. The last completed rebalance is held in memory by > the coordinating broker, so none will be reported if the coordinator has moved or restarted since > the last rebalance. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.BadGatewayError** – If the response status code is 502. The coordinator was reached, but its response was absent or unusable. - **errors.ServiceUnavailableError** – If the response status code is 503. No coordinator is currently available or reachable. - **errors.GatewayTimeoutError** – If the response status code is 504. The coordinator did not answer before the request timeout. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterBalanceResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** ClusterBalanceResponse #### Examples **Get the current cluster rebalance status:** ```python def get_cluster_rebalance_example() -> None: client = CamundaClient() # Poll this endpoint after triggering a rebalance to monitor progress. result: ClusterBalanceResponse = client.get_cluster_rebalance() print(f"Cluster balance state: {result.state}") if result.running_rebalance is not None: print(f"Running rebalance in progress: {result.running_rebalance}") if result.last_completed_rebalance is not None: print(f"Last completed rebalance: {result.last_completed_rebalance}") for partition in result.partitions: print(f" Partition {partition.partition_id}: {partition.state}") ``` ### get_cluster_status() ```python async def get_cluster_status(**kwargs) ``` Get the status of the whole cluster > Checks the health status of the whole cluster, aggregated over all physical tenants. Returns > HEALTHY when every physical tenant is healthy, DOWN when no physical tenant can process work, > and DEGRADED in every other case. No per-tenant detail is reported; use GET /cluster/v2/topology > for that. > > This endpoint is public and requires no authentication, unlike PATCH /cluster/v2/mode below, which > needs cluster-admin credentials. - **Raises:** - **errors.ServiceUnavailableError** – If the response status code is 503. The cluster is DOWN because no physical tenant can process work. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterStatusResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** ClusterStatusResponse #### Examples **Get cluster status:** ```python def get_cluster_status_example() -> None: client = CamundaClient() result = client.get_cluster_status() print(f"Cluster status: {result.status}") ``` ### get_cluster_topology() ```python async def get_cluster_topology(**kwargs) ``` Get the topology of the whole cluster > Obtains the topology of the whole cluster, aggregated over all physical tenants. Cluster-level > information is reported once; partition layout, replication and per-partition role, health and state > are reported per physical tenant. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Use GET /v2/topology for > the topology of a single physical tenant. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterTopologyResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** ClusterTopologyResponse #### Examples **Get cluster topology (cluster admin):** ```python def get_cluster_topology_example() -> None: client = CamundaClient() # Returns cluster-wide topology aggregated over all physical tenants. # Use GET /v2/topology for the topology of a single physical tenant. result = client.get_cluster_topology() print(f"Cluster {result.cluster_id or 'unknown'}: {result.cluster_size} brokers") print(f"Gateway version: {result.gateway_version}") for tenant in result.physical_tenants: print(f" Physical tenant: {tenant.physical_tenant_id}") ``` ### get_cluster_upgrade_status() ```python async def get_cluster_upgrade_status(**kwargs) ``` Get the upgrade-readiness status of the whole cluster > Reports one overall upgrade-readiness status for the whole cluster, folded over every physical > tenant and condition. MIGRATED only once every known condition has migrated for every known > physical tenant; MIGRATION_IN_PROGRESS when at least one is confirmed not yet migrated; UNKNOWN > otherwise (including before anything has been reported yet). No per-tenant or per-condition detail > is reported here; see the upgradeReadiness actuator endpoint for that. - **Raises:** - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterUpgradeStatusResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** ClusterUpgradeStatusResponse #### Examples **Get cluster upgrade readiness status:** ```python def get_cluster_upgrade_status_example() -> None: client = CamundaClient() result = client.get_cluster_upgrade_status() print(f"Cluster upgrade readiness: {result.status.value}") ``` ### get_decision_definition() ```python async def get_decision_definition(decision_definition_key, *, consistency=None, **kwargs) ``` Get decision definition > Returns a decision definition by key. **Parameters:** | Parameter | Type | Description | | ------------------------- | ------------------------------ | -------------------------------------------------------------------------- | | `decision_definition_key` | `str` | System-generated key for a decision definition. Example: 2251799813326547. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The decision definition with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DecisionDefinitionResult - **Return type:** DecisionDefinitionResult #### Examples **Get a decision definition:** ```python def get_decision_definition_example(decision_definition_key: DecisionDefinitionKey) -> None: client = CamundaClient() definition = client.get_decision_definition( decision_definition_key=decision_definition_key, ) print(f"Decision: {definition.decision_definition_id}") ``` ### get_decision_definition_xml() ```python async def get_decision_definition_xml(decision_definition_key, *, consistency=None, **kwargs) ``` Get decision definition XML > Returns decision definition as XML. **Parameters:** | Parameter | Type | Description | | ------------------------- | ------------------------------ | -------------------------------------------------------------------------- | | `decision_definition_key` | `str` | System-generated key for a decision definition. Example: 2251799813326547. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The decision definition with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** str - **Return type:** str #### Examples **Get decision definition XML:** ```python def get_decision_definition_xml_example(decision_definition_key: DecisionDefinitionKey) -> None: client = CamundaClient() xml = client.get_decision_definition_xml( decision_definition_key=decision_definition_key, ) print(f"XML length: {len(xml)}") ``` ### get_decision_instance() ```python async def get_decision_instance(decision_evaluation_instance_key, *, consistency=None, **kwargs) ``` Get decision instance > Returns a decision instance. **Parameters:** | Parameter | Type | Description | | ---------------------------------- | ------ | ----------- | | `decision_evaluation_instance_key` | str) – | | System-generated identifier for a decision evaluation instance. It is composed of the parent decision evaluation key and the 1-based index of the evaluated decision within that evaluation, joined by a hyphen (format: -). > Example: 2251799813684367-1. - **consistency** (_ConsistencyOptions_ _|_ _None_) - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The decision instance with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DecisionInstanceGetQueryResult - **Return type:** DecisionInstanceGetQueryResult #### Examples **Get a decision instance:** ```python def get_decision_instance_example(decision_evaluation_instance_key: DecisionEvaluationInstanceKey) -> None: client = CamundaClient() result = client.get_decision_instance( decision_evaluation_instance_key=decision_evaluation_instance_key, ) print(f"Decision instance: {result.decision_definition_id}") ``` ### get_decision_requirements() ```python async def get_decision_requirements(decision_requirements_key, *, consistency=None, **kwargs) ``` Get decision requirements > Returns Decision Requirements as JSON. **Parameters:** | Parameter | Type | Description | | --------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------ | | `decision_requirements_key` | `str` | System-generated key for a deployed decision requirements definition. Example: 2251799813683346. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The decision requirements with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DecisionRequirementsResult - **Return type:** DecisionRequirementsResult #### Examples **Get decision requirements:** ```python def get_decision_requirements_example(decision_requirements_key: DecisionRequirementsKey) -> None: client = CamundaClient() result = client.get_decision_requirements( decision_requirements_key=decision_requirements_key, ) print(f"DRD: {result.decision_requirements_name}") ``` ### get_decision_requirements_xml() ```python async def get_decision_requirements_xml(decision_requirements_key, *, consistency=None, **kwargs) ``` Get decision requirements XML > Returns decision requirements as XML. **Parameters:** | Parameter | Type | Description | | --------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------ | | `decision_requirements_key` | `str` | System-generated key for a deployed decision requirements definition. Example: 2251799813683346. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The decision requirements with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** str - **Return type:** str #### Examples **Get decision requirements XML:** ```python def get_decision_requirements_xml_example(decision_requirements_key: DecisionRequirementsKey) -> None: client = CamundaClient() xml = client.get_decision_requirements_xml( decision_requirements_key=decision_requirements_key, ) print(f"XML length: {len(xml)}") ``` ### get_document() ```python async def get_document(document_id, *, store_id=, content_hash=, **kwargs) ``` Download document > Download a document from the Camunda 8 cluster. > > Note that this is currently supported for document stores of type: AWS, Azure, GCP, in-memory (non- > production), local (non-production) **Parameters:** | Parameter | Type | Description | | -------------- | ---------------- | ------------------------------------------------ | | `document_id` | `str` | Document Id that uniquely identifies a document. | | `store_id` | `str` \| `Unset` | | | `content_hash` | `str` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.NotFoundError** – If the response status code is 404. The document with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** File - **Return type:** File #### Examples **Get a document:** ```python def get_document_example(document_id: DocumentId) -> None: client = CamundaClient() result = client.get_document(document_id=document_id) print(f"File name: {result.file_name}") ``` ### get_element_instance() ```python async def get_element_instance(element_instance_key, *, consistency=None, **kwargs) ``` Get element instance > Returns element instance as JSON. **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------ | ----------------------------------------------------------------------- | | `element_instance_key` | `str` | System-generated key for a element instance. Example: 2251799813686789. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The element instance with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ElementInstanceResult - **Return type:** ElementInstanceResult #### Examples **Get an element instance:** ```python def get_element_instance_example(element_instance_key: ElementInstanceKey) -> None: client = CamundaClient() result = client.get_element_instance( element_instance_key=element_instance_key, ) print(f"Element: {result.element_id}") ``` ### get_exporting_status() ```python async def get_exporting_status(**kwargs) ``` Get exporting status > Returns the exporting status of the physical tenant, aggregated over every replica of > every one of its partitions. > > Because pause and resume are applied to all replicas, the status is only a single phase > if every replica reports that phase; otherwise it is MIXED, which means a pause or > resume is still in flight or was only partially applied. Backup tooling should treat > only PAUSED and SOFT_PAUSED as confirmation that exporting is paused. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ExportingStatusResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** ExportingStatusResponse #### Examples **Get exporting status:** ```python def get_exporting_status_example() -> None: client = CamundaClient() result = client.get_exporting_status() # The status is aggregated over every replica of every partition, so `MIXED` # means a pause or resume is still in flight or was only partially applied. # Only `PAUSED` and `SOFT_PAUSED` confirm that exporting has stopped. print(f"Status: {result.status}") ``` ### get_form_by_key() ```python async def get_form_by_key(form_key, *, consistency=None, **kwargs) ``` Get form by key > Get a form by its unique form key. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | -------------------------------------------------------------------- | | `form_key` | `str` | System-generated key for a deployed form. Example: 2251799813684365. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The form with the given key was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** FormResult - **Return type:** FormResult #### Examples **Get a form by key:** ```python def get_form_by_key_example(form_key: FormKey) -> None: client = CamundaClient() result = client.get_form_by_key(form_key=form_key) print(f"Form: {result.form_id}") ``` ### get_global_cluster_variable() ```python async def get_global_cluster_variable(name, *, consistency=None, **kwargs) ``` Get a global-scoped cluster variable > Get a global-scoped cluster variable. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------- | | `name` | `str` | The name of a cluster variable. Unique within its scope (global or tenant- specific). Example: feature-flag-checkout. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Cluster variable not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterVariableResult - **Return type:** ClusterVariableResult #### Examples **Get a global cluster variable:** ```python def get_global_cluster_variable_example(name: ClusterVariableName) -> None: client = CamundaClient() result = client.get_global_cluster_variable(name=name) print(f"Variable: {result.name} = {result.value}") ``` ### get_global_job_statistics() ```python async def get_global_job_statistics(*, from_, to, job_type=, consistency=None, **kwargs) ``` Global job statistics > Returns global aggregated counts for jobs. Filter by the creation time window (required) and > optionally by jobType. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | ----------- | | `from` | `datetime.datetime` | | | `to` | `datetime.datetime` | | | `job_type` | `str` \| `Unset` | | | `from_` | `datetime.datetime` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GlobalJobStatisticsQueryResult - **Return type:** GlobalJobStatisticsQueryResult #### Examples **Get global job statistics:** ```python def get_global_job_statistics_example() -> None: client = CamundaClient() result = client.get_global_job_statistics( from_=datetime.datetime(2024, 1, 1), to=datetime.datetime(2024, 12, 31), ) print(f"Global job stats: {result}") ``` ### get_global_task_listener() ```python async def get_global_task_listener(id, *, consistency=None, **kwargs) ``` Get global user task listener > Get a global user task listener by its id. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | ---------------------------------------------------------------------- | | `id` | `str` | The user-defined id for the global listener Example: GlobalListener_1. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The global user task listener with the given id was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GlobalTaskListenerResult - **Return type:** GlobalTaskListenerResult #### Examples **Get a global task listener:** ```python def get_global_task_listener_example(listener_id: GlobalListenerId) -> None: client = CamundaClient() result = client.get_global_task_listener(id=listener_id) print(f"Task listener: {result.event_types}") ``` ### get_group() ```python async def get_group(group_id, *, consistency=None, **kwargs) ``` Get group > Get a group by its ID. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GroupResult - **Return type:** GroupResult #### Examples **Get a group:** ```python def get_group_example(group_id: GroupId) -> None: client = CamundaClient() result = client.get_group(group_id=group_id) print(f"Group: {result.name}") ``` ### get_history_backup() ```python async def get_history_backup(backup_id, **kwargs) ``` Get history backup > Returns detailed status of the history backup with the given id. > > Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ----------- | | `backup_id` | int) – | | The id of the backup. Must be a positive numerical value. As backups are logically ordered by their ids (ascending), each successive backup must use a higher id than the previous one. > Example: 1. - **kwargs** (_Any_) - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. The request is forbidden for one of three reasons: the authenticated caller lacks the required BACKUP permission; the cluster’s secondary storage is neither Elasticsearch nor OpenSearch and therefore cannot serve history backups; or the physical tenant’s snapshot repository is absent from the store — configured under a name the store does not have, or not configured at all. The problem detail says which applies. The latter two are deployment faults the caller cannot correct by changing its request. - **errors.NotFoundError** – If the response status code is 404. A backup with the given id does not exist. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** HistoryBackupInfo - **Return type:** HistoryBackupInfo #### Examples **Get a history backup:** ```python def get_history_backup_example(backup_id: int) -> None: client = CamundaClient() result = client.get_history_backup(backup_id=backup_id) # The aggregated state is derived from the state of every expected snapshot. print(f"History backup {result.backup_id} is {result.state.value}") ``` ### get_history_backup_as_cluster_admin() ```python async def get_history_backup_as_cluster_admin(backup_id, *, physical_tenant_id=, **kwargs) ``` Get a history backup across physical tenants > Reports what every physical tenant of the cluster, or the one named by physicalTenantId, holds for > the given backup id. There is no aggregated cluster-level state: a tenant that was reached and does > not hold this backup reports NOT_FOUND, which is a successful observation rather than a failure. > > The request is all-or-nothing: a physical tenant whose state cannot be read fails the whole request. > Narrow the request with physicalTenantId to read the tenants that can still be reached. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Only available on clusters > whose secondary storage is Elasticsearch or OpenSearch. Use GET /v2/backups/history/{backupId} to > act as a single physical tenant. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ----------- | | `backup_id` | int) – | | The id of the backup. Must be a positive numerical value. As backups are logically ordered by their ids (ascending), each successive backup must use a higher id than the previous one. > Example: 1. - **physical_tenant_id** (_str_ _|_ _Unset_) – Example: default. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. The cluster’s secondary storage is neither Elasticsearch nor OpenSearch and therefore cannot serve history backups, or a targeted physical tenant’s snapshot repository is absent from the store — configured under a name the store does not have, or not configured at all. Both are deployment faults the caller cannot correct by changing its request; narrow the request with physicalTenantId to work with the tenants whose repository is usable. Unlike the per-physical-tenant backup endpoints, the cluster-admin surface performs no fine-grained authorization, so a missing BACKUP permission is never the reason. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster, or every targeted physical tenant was read and none of them holds a backup with the given id. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterHistoryBackupInfo - **Return type:** ClusterHistoryBackupInfo #### Examples **Get a history backup across physical tenants:** ```python def get_history_backup_as_cluster_admin_example(backup_id: int) -> None: client = CamundaClient() # Returns what each physical tenant reports for the given backup id. # A tenant reporting `NOT_FOUND` is a successful observation, not an error. result = client.get_history_backup_as_cluster_admin(backup_id=backup_id) print(f"Cluster history backup {result.backup_id}") for tenant in result.physical_tenants: print(f" physical tenant {tenant.physical_tenant_id}: {tenant.state.value}") ``` ### get_incident() ```python async def get_incident(incident_key, *, consistency=None, **kwargs) ``` Get incident > Returns incident as JSON. **Parameters:** | Parameter | Type | Description | | -------------- | ------------------------------ | --------------------------------------------------------------- | | `incident_key` | `str` | System-generated key for a incident. Example: 2251799813689432. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The incident with the given key was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** IncidentResult - **Return type:** IncidentResult #### Examples **Get an incident:** ```python def get_incident_example(incident_key: IncidentKey) -> None: client = CamundaClient() incident = client.get_incident(incident_key=incident_key) print(f"Incident error type: {incident.error_type}") ``` ### get_job_error_statistics() ```python async def get_job_error_statistics(*, data, consistency=None, **kwargs) ``` Get error metrics for a job type > Returns aggregated metrics per error for the given jobType. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | --------------------------- | | `data` | `JobErrorStatisticsQuery` | Job error statistics query. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** JobErrorStatisticsQueryResult - **Return type:** JobErrorStatisticsQueryResult #### Examples **Get job error statistics:** ```python def get_job_error_statistics_example() -> None: client = CamundaClient() result = client.get_job_error_statistics( data=JobErrorStatisticsQuery( filter_=JobErrorStatisticsFilter( from_=datetime.datetime(2024, 1, 1), to=datetime.datetime(2024, 12, 31), job_type="payment-processing", ), ), ) if not isinstance(result.items, Unset): for stat in result.items: print(f"Error: {stat.error_code}") ``` ### get_job_time_series_statistics() ```python async def get_job_time_series_statistics(*, data, consistency=None, **kwargs) ``` Get time-series metrics for a job type > Returns a list of time-bucketed metrics ordered ascending by time. > > The from and to fields select the time window of interest. > Each item in the response corresponds to one time bucket of the requested resolution. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | --------------------------------- | | `data` | `JobTimeSeriesStatisticsQuery` | Job time-series statistics query. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** JobTimeSeriesStatisticsQueryResult - **Return type:** JobTimeSeriesStatisticsQueryResult #### Examples **Get job time series statistics:** ```python def get_job_time_series_statistics_example() -> None: client = CamundaClient() result = client.get_job_time_series_statistics( data=JobTimeSeriesStatisticsQuery( filter_=JobTimeSeriesStatisticsFilter( from_=datetime.datetime(2024, 1, 1), to=datetime.datetime(2024, 12, 31), job_type="payment-processing", ), ), ) if not isinstance(result.items, Unset): for stat in result.items: print(f"Time series: {stat}") ``` ### get_job_type_statistics() ```python async def get_job_type_statistics(*, data, consistency=None, **kwargs) ``` Get job statistics by type > Get statistics about jobs, grouped by job type. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | -------------------------- | | `data` | `JobTypeStatisticsQuery` | Job type statistics query. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** JobTypeStatisticsQueryResult - **Return type:** JobTypeStatisticsQueryResult #### Examples **Get job type statistics:** ```python def get_job_type_statistics_example() -> None: client = CamundaClient() result = client.get_job_type_statistics( data=JobTypeStatisticsQuery(), ) if not isinstance(result.items, Unset): for stat in result.items: print(f"Job type: {stat.job_type}") ``` ### get_job_worker_statistics() ```python async def get_job_worker_statistics(*, data, consistency=None, **kwargs) ``` Get job statistics by worker > Get statistics about jobs, grouped by worker, for a given job type. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | ---------------------------- | | `data` | `JobWorkerStatisticsQuery` | Job worker statistics query. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** JobWorkerStatisticsQueryResult - **Return type:** JobWorkerStatisticsQueryResult #### Examples **Get job worker statistics:** ```python def get_job_worker_statistics_example() -> None: client = CamundaClient() result = client.get_job_worker_statistics( data=JobWorkerStatisticsQuery( filter_=JobWorkerStatisticsFilter( from_=datetime.datetime(2024, 1, 1), to=datetime.datetime(2024, 12, 31), job_type="payment-processing", ), ), ) if not isinstance(result.items, Unset): for stat in result.items: print(f"Worker: {stat.worker}") ``` ### get_license() ```python async def get_license(**kwargs) ``` Get license status > Obtains the status of the current Camunda license. - **Raises:** - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** LicenseResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** LicenseResponse #### Examples **Get license information:** ```python def get_license_example() -> None: client = CamundaClient() result = client.get_license() print(f"License type: {result.license_type}") ``` ### get_mapping_rule() ```python async def get_mapping_rule(mapping_rule_id, *, consistency=None, **kwargs) ``` Get a mapping rule > Gets the mapping rule with the given ID. **Parameters:** | Parameter | Type | Description | | ----------------- | ------------------------------ | ------------------------------------------------------------------ | | `mapping_rule_id` | `str` | The unique identifier of a mapping rule. Example: my-mapping-rule. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The mapping rule with the mappingRuleId was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** MappingRuleResult - **Return type:** MappingRuleResult #### Examples **Get a mapping rule:** ```python def get_mapping_rule_example(mapping_rule_id: MappingRuleId) -> None: client = CamundaClient() result = client.get_mapping_rule(mapping_rule_id=mapping_rule_id) print(f"Mapping rule: {result.name}") ``` ### get_process_definition() ```python async def get_process_definition(process_definition_key, *, consistency=None, **kwargs) ``` Get process definition > Returns process definition as JSON. **Parameters:** | Parameter | Type | Description | | ------------------------ | ------------------------------ | ---------------------------------------------------------------------------------- | | `process_definition_key` | `str` | System-generated key for a deployed process definition. Example: 2251799813686749. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The process definition with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessDefinitionResult - **Return type:** ProcessDefinitionResult #### Examples **Get a process definition:** ```python def get_process_definition_example( process_definition_key: ProcessDefinitionKey, ) -> None: client = CamundaClient() result = client.get_process_definition( process_definition_key=process_definition_key, ) print(f"Process definition: {result.name}") ``` ### get_process_definition_instance_statistics() ```python async def get_process_definition_instance_statistics(*, data=, consistency=None, **kwargs) ``` Get process instance statistics > Get statistics about process instances, grouped by process definition and tenant. **Parameters:** | Parameter | Type | Description | | ------------- | ----------------------------------------------------- | ----------- | | `data` | `ProcessDefinitionInstanceStatisticsQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessDefinitionInstanceStatisticsQueryResult - **Return type:** ProcessDefinitionInstanceStatisticsQueryResult #### Examples **Get process definition instance statistics:** ```python def get_process_definition_instance_statistics_example() -> None: client = CamundaClient() result = client.get_process_definition_instance_statistics( data=ProcessDefinitionInstanceStatisticsQuery(), ) if not isinstance(result.items, Unset): for stat in result.items: print(f"Definition: {stat.process_definition_id}") ``` ### get_process_definition_instance_version_statistics() ```python async def get_process_definition_instance_version_statistics(*, data, consistency=None, **kwargs) ``` Get process instance statistics by version > Get statistics about process instances, grouped by version for a given process definition. > > The process definition ID must be provided as a required field in the request body filter. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------------- | ----------- | | `data` | `ProcessDefinitionInstanceVersionStatisticsQuery` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessDefinitionInstanceVersionStatisticsQueryResult - **Return type:** ProcessDefinitionInstanceVersionStatisticsQueryResult #### Examples **Get version statistics:** ```python def get_process_definition_instance_version_statistics_example( process_definition_id: ProcessDefinitionId, ) -> None: client = CamundaClient() result = client.get_process_definition_instance_version_statistics( data=ProcessDefinitionInstanceVersionStatisticsQuery( filter_=ProcessDefinitionInstanceVersionStatisticsQueryFilter( process_definition_id=process_definition_id, ), ), ) if not isinstance(result.items, Unset): for stat in result.items: print(f"Version: {stat.process_definition_version}") ``` ### get_process_definition_message_subscription_statistics() ```python async def get_process_definition_message_subscription_statistics(*, data=, consistency=None, **kwargs) ``` Get message subscription statistics > Get message subscription statistics, grouped by process definition. **Parameters:** | Parameter | Type | Description | | ------------- | ---------------------------------------------------------------- | ----------- | | `data` | `ProcessDefinitionMessageSubscriptionStatisticsQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessDefinitionMessageSubscriptionStatisticsQueryResult - **Return type:** ProcessDefinitionMessageSubscriptionStatisticsQueryResult #### Examples **Get message subscription statistics:** ```python def get_process_definition_message_subscription_statistics_example() -> None: client = CamundaClient() result = client.get_process_definition_message_subscription_statistics( data=ProcessDefinitionMessageSubscriptionStatisticsQuery(), ) if not isinstance(result.items, Unset): for stat in result.items: print( f"Definition: {stat.process_definition_id}, subscriptions: {stat.active_subscriptions}" ) ``` ### get_process_definition_statistics() ```python async def get_process_definition_statistics(process_definition_key, *, data=, consistency=None, **kwargs) ``` Get process definition statistics > Get statistics about elements in currently running process instances by process definition key and > search filter. **Parameters:** | Parameter | Type | Description | | ------------------------ | ---------------------------------------------------- | ---------------------------------------------------------------------------------- | | `process_definition_key` | `str` | System-generated key for a deployed process definition. Example: 2251799813686749. | | `data` | `ProcessDefinitionElementStatisticsQuery` \| `Unset` | Process definition element statistics request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessDefinitionElementStatisticsQueryResult - **Return type:** ProcessDefinitionElementStatisticsQueryResult #### Examples **Get process definition element statistics:** ```python def get_process_definition_statistics_example( process_definition_key: ProcessDefinitionKey, ) -> None: client = CamundaClient() result = client.get_process_definition_statistics( process_definition_key=process_definition_key, ) if not isinstance(result.items, Unset): for stat in result.items: print(f"Element: {stat.element_id}") ``` ### get_process_definition_xml() ```python async def get_process_definition_xml(process_definition_key, *, consistency=None, **kwargs) ``` Get process definition XML > Returns process definition as XML. **Parameters:** | Parameter | Type | Description | | ------------------------ | ------------------------------ | ---------------------------------------------------------------------------------- | | `process_definition_key` | `str` | System-generated key for a deployed process definition. Example: 2251799813686749. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The process definition with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** str - **Return type:** str #### Examples **Get process definition XML:** ```python def get_process_definition_xml_example( process_definition_key: ProcessDefinitionKey, ) -> None: client = CamundaClient() xml = client.get_process_definition_xml( process_definition_key=process_definition_key, ) print(f"XML length: {len(xml)}") ``` ### get_process_instance() ```python async def get_process_instance(process_instance_key, *, consistency=None, **kwargs) ``` Get process instance > Get the process instance by the process instance key. **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------ | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The process instance with the given key was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessInstanceResult - **Return type:** ProcessInstanceResult #### Examples **Get a process instance:** ```python def get_process_instance_example(process_instance_key: ProcessInstanceKey) -> None: client = CamundaClient() result = client.get_process_instance( process_instance_key=process_instance_key, ) print(f"Process instance: {result.process_definition_id}") ``` ### get_process_instance_call_hierarchy() ```python async def get_process_instance_call_hierarchy(process_instance_key, *, consistency=None, **kwargs) ``` Get call hierarchy > Returns the call hierarchy for a given process instance, showing its ancestry up to the root > instance. **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------ | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The process instance is not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** list[Any] - **Return type:** list[Any] #### Examples **Get process instance call hierarchy:** ```python def get_process_instance_call_hierarchy_example( process_instance_key: ProcessInstanceKey, ) -> None: client = CamundaClient() result = client.get_process_instance_call_hierarchy( process_instance_key=process_instance_key, ) for entry in result: print(f"Call hierarchy entry: {entry}") ``` ### get_process_instance_sequence_flows() ```python async def get_process_instance_sequence_flows(process_instance_key, *, consistency=None, **kwargs) ``` Get sequence flows > Get sequence flows taken by the process instance. **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------ | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessInstanceSequenceFlowsQueryResult - **Return type:** ProcessInstanceSequenceFlowsQueryResult #### Examples **Get process instance sequence flows:** ```python def get_process_instance_sequence_flows_example( process_instance_key: ProcessInstanceKey, ) -> None: client = CamundaClient() result = client.get_process_instance_sequence_flows( process_instance_key=process_instance_key, ) if not isinstance(result.items, Unset): for flow in result.items: print(f"Sequence flow: {flow}") ``` ### get_process_instance_statistics() ```python async def get_process_instance_statistics(process_instance_key, *, consistency=None, **kwargs) ``` Get element instance statistics > Get statistics about elements by the process instance key. **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------ | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessInstanceElementStatisticsQueryResult - **Return type:** ProcessInstanceElementStatisticsQueryResult #### Examples **Get process instance statistics:** ```python def get_process_instance_statistics_example( process_instance_key: ProcessInstanceKey, ) -> None: client = CamundaClient() result = client.get_process_instance_statistics( process_instance_key=process_instance_key, ) if not isinstance(result.items, Unset): for stat in result.items: print(f"Element: {stat.element_id}, Active: {stat.active}") ``` ### get_process_instance_statistics_by_definition() ```python async def get_process_instance_statistics_by_definition(*, data, consistency=None, **kwargs) ``` Get process instance statistics by definition > Returns statistics for active process instances with incidents, grouped by process > definition. The result set is scoped to a specific incident error hash code, which must be > provided as a filter in the request body. **Parameters:** | Parameter | Type | Description | | ------------- | ---------------------------------------------------- | ----------- | | `data` | `IncidentProcessInstanceStatisticsByDefinitionQuery` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** IncidentProcessInstanceStatisticsByDefinitionQueryResult - **Return type:** IncidentProcessInstanceStatisticsByDefinitionQueryResult #### Examples **Get instance statistics by definition:** ```python def get_process_instance_statistics_by_definition_example() -> None: client = CamundaClient() result = client.get_process_instance_statistics_by_definition( data=IncidentProcessInstanceStatisticsByDefinitionQuery( filter_=IncidentProcessInstanceStatisticsByDefinitionQueryFilter( error_hash_code=12345, ), ), ) if not isinstance(result.items, Unset): for stat in result.items: print(f"Definition: {stat.process_definition_key}") ``` ### get_process_instance_statistics_by_error() ```python async def get_process_instance_statistics_by_error(*, data=, consistency=None, **kwargs) ``` Get process instance statistics by error > Returns statistics for active process instances that currently have active incidents, > grouped by incident error hash code. **Parameters:** | Parameter | Type | Description | | ------------- | ---------------------------------------------------------- | ----------- | | `data` | `IncidentProcessInstanceStatisticsByErrorQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** IncidentProcessInstanceStatisticsByErrorQueryResult - **Return type:** IncidentProcessInstanceStatisticsByErrorQueryResult #### Examples **Get instance statistics by error:** ```python def get_process_instance_statistics_by_error_example() -> None: client = CamundaClient() result = client.get_process_instance_statistics_by_error( data=IncidentProcessInstanceStatisticsByErrorQuery(), ) if not isinstance(result.items, Unset): for stat in result.items: print(f"Error: {stat.error_message}") ``` ### get_process_instance_wait_state_statistics() ```python async def get_process_instance_wait_state_statistics(process_instance_key, *, consistency=None, **kwargs) ``` Get wait state statistics > Get statistics about waiting element instances by the process instance key, grouped by element id. **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------ | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessInstanceWaitStateStatisticsQueryResult - **Return type:** ProcessInstanceWaitStateStatisticsQueryResult #### Examples **Get process instance wait state statistics:** ```python def get_process_instance_wait_state_statistics_example( process_instance_key: ProcessInstanceKey, ) -> None: client = CamundaClient() result = client.get_process_instance_wait_state_statistics( process_instance_key=process_instance_key, ) for stat in result.items: print(f"Element: {stat.element_id}, Waiting: {stat.waiting_count}") ``` ### get_resource() ```python async def get_resource(resource_key, *, consistency=None, **kwargs) ``` 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. ::: - **resource_key**: The system-assigned key for this resource. ```` * **Raises:** * **errors.NotFoundError** – If the response status code is 404. A resource with the given key was not found. * **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. * **errors.UnexpectedStatus** – If the response status code is not documented. * **httpx.TimeoutException** – If the request takes longer than Client.timeout. * **Returns:** ResourceResult **Parameters:** | Parameter | Type | Description | | --- | --- | --- | | `resource_key` | `str` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | * **Return type:** ResourceResult #### Examples **Get a resource:** ```python def get_resource_example() -> None: client = CamundaClient() result = client.get_resource(resource_key="123456") print(f"Resource: {result.resource_name}") ```` ### get_resource_content() ```python async def get_resource_content(resource_key, *, consistency=None, **kwargs) ``` Get RPA resource content (deprecated) > **Deprecated** — use /resources/{resourceKey}/content/binary instead, which supports all > resource types and returns content as binary (octet-stream). > > Returns the content of a deployed RPA resource as JSON. :::info This endpoint only supports RPA resources. For generic resource content in binary format, use the /resources/{resourceKey}/content/binary endpoint. ::: - **resource_key**: The system-assigned key for this resource. ```` * **Raises:** * **errors.NotFoundError** – If the response status code is 404. A resource with the given key was not found. * **errors.NotAcceptableError** – If the response status code is 406. The resource exists but is not an RPA resource. * **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. * **errors.UnexpectedStatus** – If the response status code is not documented. * **httpx.TimeoutException** – If the request takes longer than Client.timeout. * **Returns:** GetResourceContentResponse200 **Parameters:** | Parameter | Type | Description | | --- | --- | --- | | `resource_key` | `str` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | * **Return type:** GetResourceContentResponse200 #### Examples **Get resource content:** ```python def get_resource_content_example() -> None: client = CamundaClient() content = client.get_resource_content(resource_key="123456") print(f"Content: {content}") ```` ### get_resource_content_binary() ```python async def get_resource_content_binary(resource_key, *, consistency=None, **kwargs) ``` Get resource content as binary > Returns the content of a deployed resource in binary format (octet-stream). :::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. ::: - **resource_key**: The system-assigned key for this resource. ```` * **Raises:** * **errors.NotFoundError** – If the response status code is 404. A resource with the given key was not found. * **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. * **errors.UnexpectedStatus** – If the response status code is not documented. * **httpx.TimeoutException** – If the request takes longer than Client.timeout. * **Returns:** File **Parameters:** | Parameter | Type | Description | | --- | --- | --- | | `resource_key` | `str` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | * **Return type:** File #### Examples **Get resource content as binary:** ```python def get_resource_content_binary_example() -> None: client = CamundaClient() content = client.get_resource_content_binary(resource_key="123456") print(f"Binary content size: {len(content.payload.read())}") ```` ### get_restore_status() ```python async def get_restore_status(**kwargs) ``` Get the status of the restore that is currently in progress > Returns the status of the restore that is currently in progress, reported per broker and per > partition. There is at most one restore in flight at any time. Once the restore has finished this > endpoint returns 404; the per-partition detail is not retained after completion. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. No restore is currently in progress. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RestoreStatusResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** RestoreStatusResponse #### Examples **Get restore status:** ```python def get_restore_status_example() -> None: client = CamundaClient() result = client.get_restore_status() print(f"Restore status: {result.status}") ``` ### get_role() ```python async def get_role(role_id, *, consistency=None, **kwargs) ``` Get role > Get a role by its ID. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RoleResult - **Return type:** RoleResult #### Examples **Get a role:** ```python def get_role_example(role_id: RoleId) -> None: client = CamundaClient() result = client.get_role(role_id=role_id) print(f"Role: {result.name}") ``` ### get_runtime_backup() ```python async def get_runtime_backup(backup_id, **kwargs) ``` Get runtime backup > Returns detailed status of the runtime backup with the given id. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ----------- | | `backup_id` | int) – | | The id of the backup. Must be a positive numerical value. As backups are logically ordered by their ids (ascending), each successive backup must use a higher id than the previous one. > Example: 1. - **kwargs** (_Any_) - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. A backup with the given id does not exist. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BackupInfo - **Return type:** BackupInfo #### Examples **Get a runtime backup:** ```python def get_runtime_backup_example(backup_id: int) -> None: client = CamundaClient() result = client.get_runtime_backup(backup_id=backup_id) print(f"Backup {result.backup_id} is {result.state.value}") for partition in result.details: print(f" partition {partition.partition_id}: {partition.state.value}") ``` ### get_runtime_backup_as_cluster_admin() ```python async def get_runtime_backup_as_cluster_admin(backup_id, *, physical_tenant_id=, **kwargs) ``` Get a runtime backup across physical tenants > Reports what every physical tenant of the cluster, or the one named by physicalTenantId, holds for > the given backup id, plus the state aggregated over all of them. A tenant that was reached and does > not hold this backup reports DOES_NOT_EXIST, which is a successful observation rather than a > failure — so a backup only some tenants hold aggregates to INCOMPLETE, the same way a backup only > some partitions hold does within one tenant. > > The request is all-or-nothing: a physical tenant whose state cannot be read fails the whole request. > Narrow the request with physicalTenantId to read the tenants that can still be reached. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Use GET > /v2/backups/runtime/{backupId} to act as a single physical tenant. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ----------- | | `backup_id` | int) – | | The id of the backup. Must be a positive numerical value. As backups are logically ordered by their ids (ascending), each successive backup must use a higher id than the previous one. > Example: 1. - **physical_tenant_id** (_str_ _|_ _Unset_) – Example: default. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster, or every targeted physical tenant was read and none of them holds a backup with the given id. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterRuntimeBackupInfo - **Return type:** ClusterRuntimeBackupInfo #### Examples **Get a runtime backup across physical tenants:** ```python def get_runtime_backup_as_cluster_admin_example(backup_id: int) -> None: client = CamundaClient() # Returns what each physical tenant reports for the given backup id. # A tenant reporting `DOES_NOT_EXIST` is a successful observation, not an error. result = client.get_runtime_backup_as_cluster_admin(backup_id=backup_id) print(f"Cluster runtime backup {result.backup_id} is {result.state.value}") for tenant in result.physical_tenants: print(f" physical tenant {tenant.physical_tenant_id}: {tenant.state.value}") ``` ### get_runtime_backup_state() ```python async def get_runtime_backup_state(**kwargs) ``` Get runtime backup state > Returns the current checkpoint and backup state of every partition of the physical > tenant. Unlike the backupRuntime actuator, this fails the whole request if the > checkpoint state or the backup ranges cannot be retrieved from any partition, instead > of silently returning an empty section. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RuntimeBackupState - **Parameters:** **kwargs** (_Any_) - **Return type:** RuntimeBackupState #### Examples **Get the runtime backup state:** ```python def get_runtime_backup_state_example() -> None: client = CamundaClient() result = client.get_runtime_backup_state() for checkpoint in result.checkpoint_states: print( f"Partition {checkpoint.partition_id} checkpoint {checkpoint.checkpoint_id}" f" at position {checkpoint.checkpoint_position}" ) for backup_range in result.ranges: print( f"Partition {backup_range.partition_id} range:" f" {backup_range.start} - {backup_range.end}" ) ``` ### get_runtime_backup_state_as_cluster_admin() ```python async def get_runtime_backup_state_as_cluster_admin(*, physical_tenant_id=, **kwargs) ``` Get runtime backup state across physical tenants > Reports the checkpoint and backup state of every partition of every physical tenant of the cluster, > or of the one named by physicalTenantId, grouped by physical tenant. Checkpoint ids and log > positions only mean anything within one physical tenant’s partitions, so nothing is aggregated > across tenants. > > The request is all-or-nothing: a physical tenant whose state cannot be read fails the whole request > rather than contributing an empty section, which an operator making a delete or restore decision > could not tell apart from “nothing to report yet”. Narrow the request with physicalTenantId to > read the tenants that can still be reached. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Use GET > /v2/backups/runtime/state to act as a single physical tenant. **Parameters:** | Parameter | Type | Description | | -------------------- | ---------------- | ----------------- | | `physical_tenant_id` | `str` \| `Unset` | Example: default. | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterRuntimeBackupState - **Return type:** ClusterRuntimeBackupState #### Examples **Get runtime backup state across physical tenants:** ```python def get_runtime_backup_state_as_cluster_admin_example() -> None: client = CamundaClient() # Reports the checkpoint and backup state of every partition of every # physical tenant. Use `physical_tenant_id` to narrow to a single tenant. result = client.get_runtime_backup_state_as_cluster_admin() for tenant in result.physical_tenants: print(f"Physical tenant {tenant.physical_tenant_id}:") for checkpoint in tenant.state.checkpoint_states: print( f" partition {checkpoint.partition_id} checkpoint" f" {checkpoint.checkpoint_id}" ) ``` ### get_start_process_form() ```python async def get_start_process_form(process_definition_key, *, consistency=None, **kwargs) ``` Get process start form > Get the start form of a process. > > Note that this endpoint will only return linked forms. This endpoint does not support embedded > forms. **Parameters:** | Parameter | Type | Description | | ------------------------ | ------------------------------ | ---------------------------------------------------------------------------------- | | `process_definition_key` | `str` | System-generated key for a deployed process definition. Example: 2251799813686749. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** FormResult - **Return type:** FormResult #### Examples **Get start process form:** ```python def get_start_process_form_example( process_definition_key: ProcessDefinitionKey, ) -> None: client = CamundaClient() result = client.get_start_process_form( process_definition_key=process_definition_key, ) print(f"Form: {result.form_key}") ``` ### get_status() ```python async def get_status(**kwargs) ``` Get physical tenant status - **Raises:** - **errors.ServiceUnavailableError** – If the response status code is 503. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Parameters:** **kwargs** (_Any_) - **Return type:** None #### Examples **Check cluster status:** ```python def get_status_example() -> None: client = CamundaClient() client.get_status() print("Cluster is healthy") ``` ### get_system_configuration() ```python async def get_system_configuration(**kwargs) ``` 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. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** SystemConfigurationResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** SystemConfigurationResponse #### Examples **Get system configuration:** ```python def get_system_configuration_example() -> None: client = CamundaClient() result = client.get_system_configuration() print(f"System config: {result}") ``` ### get_tenant() ```python async def get_tenant(tenant_id, *, consistency=None, **kwargs) ``` Get tenant > Retrieves a single tenant by tenant ID. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Tenant not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TenantResult - **Return type:** TenantResult #### Examples **Get a tenant:** ```python def get_tenant_example(tenant_id: TenantId) -> None: client = CamundaClient() result = client.get_tenant(tenant_id=tenant_id) print(f"Tenant: {result.name}") ``` ### get_tenant_cluster_variable() ```python async def get_tenant_cluster_variable(tenant_id, name, *, consistency=None, **kwargs) ``` Get a tenant-scoped cluster variable > Get a tenant-scoped cluster variable. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `name` | `str` | The name of a cluster variable. Unique within its scope (global or tenant- specific). Example: feature-flag-checkout. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Cluster variable not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterVariableResult - **Return type:** ClusterVariableResult #### Examples **Get a tenant cluster variable:** ```python def get_tenant_cluster_variable_example(tenant_id: TenantId, name: ClusterVariableName) -> None: client = CamundaClient() result = client.get_tenant_cluster_variable( tenant_id=tenant_id, name=name, ) print(f"Variable: {result.name} = {result.value}") ``` ### get_topology() ```python async def get_topology(**kwargs) ``` Get cluster topology > Obtains the current topology of the cluster the gateway is part of. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TopologyResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** TopologyResponse #### Examples **Get cluster topology:** ```python def get_topology_example() -> None: client = CamundaClient() result = client.get_topology() print(f"Topology: {result}") ``` ### get_usage_metrics() ```python async def get_usage_metrics(*, start_time, end_time, tenant_id=, with_tenants=, consistency=None, **kwargs) ``` Get usage metrics > Retrieve the usage metrics based on given criteria. **Parameters:** | Parameter | Type | Description | | -------------- | ------------------------------ | --------------------------------------------------------------- | | `start_time` | `datetime.datetime` | Example: 2025-06-07T13:14:15Z. | | `end_time` | `datetime.datetime` | Example: 2025-06-07T13:14:15Z. | | `tenant_id` | `str` \| `Unset` | The unique identifier of the tenant. Example: customer-service. | | `with_tenants` | `bool` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** UsageMetricsResponse - **Return type:** UsageMetricsResponse #### Examples **Get usage metrics:** ```python def get_usage_metrics_example() -> None: client = CamundaClient() result = client.get_usage_metrics( start_time=datetime.datetime(2024, 1, 1), end_time=datetime.datetime(2024, 12, 31), ) print(f"Metrics: {result}") ``` ### get_user() ```python async def get_user(username, *, consistency=None, **kwargs) ``` Get user > Get a user by its username. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | -------------------------------------------- | | `username` | `str` | The unique name of a user. Example: swillis. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The user with the given username was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** UserResult - **Return type:** UserResult #### Examples **Get a user:** ```python def get_user_example(username: Username) -> None: client = CamundaClient() result = client.get_user(username=username) print(f"User: {result.username}") ``` ### get_user_task() ```python async def get_user_task(user_task_key, *, consistency=None, **kwargs) ``` Get user task > Get the user task by the user task key. **Parameters:** | Parameter | Type | Description | | --------------- | ------------------------------ | ------------------------------------- | | `user_task_key` | `str` | System-generated key for a user task. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The user task with the given key was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** UserTaskResult - **Return type:** UserTaskResult #### Examples **Get a user task:** ```python def get_user_task_example(user_task_key: UserTaskKey) -> None: client = CamundaClient() task = client.get_user_task(user_task_key=user_task_key) print(f"Task: {task.user_task_key}") ``` ### get_user_task_form() ```python async def get_user_task_form(user_task_key, *, consistency=None, **kwargs) ``` Get user task form > Get the form of a user task. > > Note that this endpoint will only return linked forms. This endpoint does not support embedded > forms. **Parameters:** | Parameter | Type | Description | | --------------- | ------------------------------ | ------------------------------------- | | `user_task_key` | `str` | System-generated key for a user task. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** FormResult - **Return type:** FormResult #### Examples **Get a user task form:** ```python def get_user_task_form_example(user_task_key: UserTaskKey) -> None: client = CamundaClient() result = client.get_user_task_form( user_task_key=user_task_key, ) print(f"Form: {result.form_key}") ``` ### get_variable() ```python async def get_variable(variable_key, *, consistency=None, **kwargs) ``` Get variable > Get a variable by its key. > > This endpoint returns both process-level and local (element-scoped) variables. > The variable’s scopeKey indicates whether it’s a process-level variable or scoped to a > specific element instance. **Parameters:** | Parameter | Type | Description | | -------------- | ------------------------------ | --------------------------------------------------------------- | | `variable_key` | `str` | System-generated key for a variable. Example: 2251799813683287. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** VariableResult - **Return type:** VariableResult #### Examples **Get a variable:** ```python def get_variable_example(variable_key: VariableKey) -> None: client = CamundaClient() result = client.get_variable( variable_key=variable_key, ) print(f"Variable: {result.name} = {result.value}") ``` ### list_history_backups() ```python async def list_history_backups(*, prefix=, verbose=, **kwargs) ``` List history backups > Returns a list of all available history backups of the physical tenant, with their state > and additional info, most recent first by snapshot start time. > > Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. **Parameters:** | Parameter | Type | Description | | --------- | ----------------- | ----------- | | `prefix` | `str` \| Unset) – | | A prefix of a backup id, followed by a single ‘\*’ as a wildcard, matching any backup id starting with the given prefix. > Example: 17567\*. - **verbose** (_bool_ _|_ _Unset_) - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. The request is forbidden for one of three reasons: the authenticated caller lacks the required BACKUP permission; the cluster’s secondary storage is neither Elasticsearch nor OpenSearch and therefore cannot serve history backups; or the physical tenant’s snapshot repository is absent from the store — configured under a name the store does not have, or not configured at all. The problem detail says which applies. The latter two are deployment faults the caller cannot correct by changing its request. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** list[Any] - **Return type:** list[_Any_] #### Examples **List history backups:** ```python def list_history_backups_example() -> None: client = CamundaClient() # `prefix` is a backup id prefix followed by a single `*` wildcard. result = client.list_history_backups(prefix="17567*") for backup in result: print(f"History backup: {backup}") ``` ### list_history_backups_as_cluster_admin() ```python async def list_history_backups_as_cluster_admin(*, physical_tenant_id=, prefix=, verbose=, **kwargs) ``` List history backups across physical tenants > Lists the history backups of every physical tenant of the cluster, or of the one named by > physicalTenantId, grouped by backup id. A backup id that only some physical tenants hold is a > supported outcome rather than a degraded one, so only the tenants that hold it are listed under it. > > The request is all-or-nothing: a physical tenant whose backups cannot be read fails the whole > request rather than silently dropping out of the listing. Narrow the request with physicalTenantId > to list the backups of the tenants that can still be read. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Only available on clusters > whose secondary storage is Elasticsearch or OpenSearch. Use GET /v2/backups/history to act as a > single physical tenant. **Parameters:** | Parameter | Type | Description | | -------------------- | ----------------- | ----------------- | | `physical_tenant_id` | `str` \| `Unset` | Example: default. | | `prefix` | `str` \| Unset) – | | A prefix of a backup id, followed by a single ‘\*’ as a wildcard, matching any backup id starting with the given prefix. > Example: 17567\*. - **verbose** (_bool_ _|_ _Unset_) - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. The cluster’s secondary storage is neither Elasticsearch nor OpenSearch and therefore cannot serve history backups, or a targeted physical tenant’s snapshot repository is absent from the store — configured under a name the store does not have, or not configured at all. Both are deployment faults the caller cannot correct by changing its request; narrow the request with physicalTenantId to work with the tenants whose repository is usable. Unlike the per-physical-tenant backup endpoints, the cluster-admin surface performs no fine-grained authorization, so a missing BACKUP permission is never the reason. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** list[Any] - **Return type:** list[_Any_] #### Examples **List history backups across physical tenants:** ```python def list_history_backups_as_cluster_admin_example() -> None: client = CamundaClient() # Lists backups across every physical tenant. Pass `physical_tenant_id` to # restrict to one tenant. `prefix` filters to backup ids starting with the # given value (end with a `*` wildcard, e.g. "17567*"). backups = client.list_history_backups_as_cluster_admin(prefix="17567*") for backup in backups: print(f"History backup: {backup}") ``` ### list_runtime_backups() ```python async def list_runtime_backups(*, prefix=, **kwargs) ``` List runtime backups > Returns a list of all available runtime backups of the physical tenant, with their > state and additional info, sorted in descending order of backupId. **Parameters:** | Parameter | Type | Description | | --------- | ----------------- | ----------- | | `prefix` | `str` \| Unset) – | | A prefix of a backup id, followed by a single ‘\*’ as a wildcard, matching any backup id starting with the given prefix. > Example: 17567\*. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** list[Any] - **Return type:** list[_Any_] #### Examples **List runtime backups:** ```python def list_runtime_backups_example() -> None: client = CamundaClient() # `prefix` is a backup id prefix followed by a single `*` wildcard. result = client.list_runtime_backups(prefix="17567*") for backup in result: print(f"Runtime backup: {backup}") ``` ### list_runtime_backups_as_cluster_admin() ```python async def list_runtime_backups_as_cluster_admin(*, physical_tenant_id=, prefix=, **kwargs) ``` List runtime backups across physical tenants > Lists the runtime backups of every physical tenant of the cluster, or of the one named by > physicalTenantId, grouped by backup id. Every group reports every targeted tenant, including the > ones holding nothing for that id, so a backup only some tenants hold aggregates to INCOMPLETE here > exactly as it does when looked up directly — the state of a listed group can be trusted to say > whether the cluster can be restored from it. A backup id that only some physical tenants hold is a > supported outcome rather than a degraded one; tenants that generate their own backup ids never share > one, so in that mode each backup forms its own group and the other tenants report DOES_NOT_EXIST > under it. > > The request is all-or-nothing: a physical tenant whose backups cannot be read fails the whole > request rather than silently dropping out of the listing. Narrow the request with physicalTenantId > to list the backups of the tenants that can still be read. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Use GET > /v2/backups/runtime to act as a single physical tenant. **Parameters:** | Parameter | Type | Description | | -------------------- | ----------------- | ----------------- | | `physical_tenant_id` | `str` \| `Unset` | Example: default. | | `prefix` | `str` \| Unset) – | | A prefix of a backup id, followed by a single ‘\*’ as a wildcard, matching any backup id starting with the given prefix. > Example: 17567\*. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** list[Any] - **Return type:** list[_Any_] #### Examples **List runtime backups across physical tenants:** ```python def list_runtime_backups_as_cluster_admin_example() -> None: client = CamundaClient() # Lists backups across every physical tenant. Pass `physical_tenant_id` to # restrict to one tenant. `prefix` filters to backup ids starting with the # given value (end with a `*` wildcard, e.g. "17567*"). backups = client.list_runtime_backups_as_cluster_admin(prefix="17567*") for backup in backups: print(f"Runtime backup: {backup}") ``` ### list_secrets() ```python async def list_secrets(*, data=, **kwargs) ``` List secrets > List the camunda.secrets.\* references known for the caller’s physical tenant. > > Only references the caller holds SECRET:READ on are returned. This endpoint never > returns secret values, only the reference names. > > The references are read from the secret stores configured for the caller’s physical tenant. > A store may hold names outside the reference name charset (for example one containing a > dot); those are omitted, since /secrets/resolve would reject them and no permission can > be granted on them. > > A returned reference is usable verbatim with /secrets/resolve. In a FEEL expression, > however, a name that is not a bare identifier has to be backtick-escaped, since FEEL reads > a bare dash as the minus operator: a listed camunda.secrets.db-password is written > \`\` =camunda.secrets.\`db-password\` \`\` in a BPMN input mapping. **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | `SecretListRequest` \| `Unset` | Reserved for future filtering options. Currently takes no properties. The request body is optional: omitting it (or sending an empty object) applies no filters. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** SecretListResult - **Return type:** SecretListResult #### Examples **List secrets:** ```python def list_secrets_example() -> None: client = CamundaClient() # Lists the `camunda.secrets.*` references visible to the caller's physical # tenant. Only references the caller holds `SECRET:READ` on are returned, and # the response carries reference names only -- never the secret values. # The request body is optional; an empty one applies no filters. result = client.list_secrets(data=SecretListRequest()) for reference in result.references: print(f"Known secret reference: {reference}") ``` ### migrate_process_instance() ```python async def migrate_process_instance(process_instance_key, *, data, **kwargs) ``` Migrate process instance > Migrates a process instance to a new process definition. > > This request can contain multiple mapping instructions to define mapping between the active > process instance’s elements and target process definition elements. > > Use this to upgrade a process instance to a new version of a process or to > a different process definition, e.g. to keep your running instances up-to-date with the > latest process improvements. **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `data` | `ProcessInstanceMigrationInstruction` | The migration instructions describe how to migrate a process instance from one process definition to another. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The process instance is not found. - **errors.ConflictError** – If the response status code is 409. The process instance migration failed. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Migrate a process instance:** ```python def migrate_process_instance_example( process_instance_key: ProcessInstanceKey, target_process_definition_key: ProcessDefinitionKey, source_element_id: ElementId, target_element_id: ElementId, ) -> None: client = CamundaClient() client.migrate_process_instance( process_instance_key=process_instance_key, data=ProcessInstanceMigrationInstruction( target_process_definition_key=target_process_definition_key, mapping_instructions=[ MigrateProcessInstanceMappingInstruction( source_element_id=source_element_id, target_element_id=target_element_id, ), ], ), ) ``` ### migrate_process_instances_batch_operation() ```python async def migrate_process_instances_batch_operation(*, data, **kwargs) ``` Migrate process instances (batch) > Migrate multiple process instances. > > Since only process instances with ACTIVE state can be migrated, any given > filters for state are ignored and overridden during this batch operation. > This is done asynchronously, the progress can be tracked using the batchOperationKey from the > response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------- | ----------------------------------------------- | ----------- | | `data` | `ProcessInstanceMigrationBatchOperationRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The process instance batch operation failed. More details are provided in the response body. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationCreatedResult - **Return type:** BatchOperationCreatedResult #### Examples **Migrate process instances in batch:** ```python def migrate_process_instances_batch_operation_example(target_process_definition_key: ProcessDefinitionKey, source_element_id: ElementId, target_element_id: ElementId) -> None: client = CamundaClient() result = client.migrate_process_instances_batch_operation( data=ProcessInstanceMigrationBatchOperationRequest( filter_=ProcessInstanceCancellationBatchOperationRequestFilter(), migration_plan=ProcessInstanceMigrationBatchOperationRequestMigrationPlan( target_process_definition_key=target_process_definition_key, mapping_instructions=[ MigrateProcessInstanceMappingInstruction( source_element_id=source_element_id, target_element_id=target_element_id, ), ], ), ), ) print(f"Batch operation key: {result.batch_operation_key}") ``` ### modify_process_instance() ```python async def modify_process_instance(process_instance_key, *, data, **kwargs) ``` Modify process instance > Modifies a running process instance. > > This request can contain multiple instructions to activate an element of the process or > to terminate an active instance of an element. > > Use this to repair a process instance that is stuck on an element or took an unintended path. > For example, because an external system is not available or doesn’t respond as expected. **Parameters:** | Parameter | Type | Description | | ---------------------- | ---------------------------------------- | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `data` | `ProcessInstanceModificationInstruction` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The process instance is not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Modify a process instance:** ```python def modify_process_instance_example(process_instance_key: ProcessInstanceKey) -> None: client = CamundaClient() client.modify_process_instance( process_instance_key=process_instance_key, data=ProcessInstanceModificationInstruction(), ) ``` ### modify_process_instances_batch_operation() ```python async def modify_process_instances_batch_operation(*, data, **kwargs) ``` Modify process instances (batch) > Modify multiple process instances. > > Since only process instances with ACTIVE state can be modified, any given > filters for state are ignored and overridden during this batch operation. > In contrast to single modification operation, it is not possible to add variable instructions or > modify by element key. > It is only possible to use the element id of the source and target. > This is done asynchronously, the progress can be tracked using the batchOperationKey from the > response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | `ProcessInstanceModificationBatchOperationRequest` | The process instance filter to define on which process instances tokens should be moved, and new element instances should be activated or terminated. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The process instance batch operation failed. More details are provided in the response body. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationCreatedResult - **Return type:** BatchOperationCreatedResult #### Examples **Modify process instances in batch:** ```python def modify_process_instances_batch_operation_example(source_element_id: ElementId, target_element_id: ElementId) -> None: client = CamundaClient() result = client.modify_process_instances_batch_operation( data=ProcessInstanceModificationBatchOperationRequest( filter_=ProcessInstanceCancellationBatchOperationRequestFilter(), move_instructions=[ ProcessInstanceModificationMoveBatchOperationInstruction( source_element_id=source_element_id, target_element_id=target_element_id, ), ], ), ) print(f"Batch operation key: {result.batch_operation_key}") ``` ### pause_cluster_exporting() ```python async def pause_cluster_exporting(*, soft=, **kwargs) ``` Pause exporting across the whole cluster > Pauses exporting on every physical tenant of the cluster in one call. With soft=true, every > physical tenant is soft-paused instead. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. **Parameters:** | Parameter | Type | Description | | --------- | ----------------- | ----------- | | `soft` | `bool` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Pause exporting across the whole cluster:** ```python def pause_cluster_exporting_example() -> None: client = CamundaClient() # Pauses exporting on every physical tenant in one call. # With `soft=True` the position is not committed, so the log is not compacted, # which is the right mode for taking a consistent backup without stopping # real processing work. client.pause_cluster_exporting(soft=True) ``` ### pause_exporting() ```python async def pause_exporting(*, soft=, **kwargs) ``` Pause exporting > Pauses exporting on all partitions of the physical tenant. While paused, exported records > are not committed, so the log is not compacted for the affected partitions. > > With soft=true, exporting continues to run but its position is not committed, so the > state after resuming is identical to a hard pause; use this variant when exporting must > keep progressing (e.g. to avoid falling behind) while still preventing log compaction, > such as during a backup. **Parameters:** | Parameter | Type | Description | | --------- | ----------------- | ----------- | | `soft` | `bool` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Pause exporting:** ```python def pause_exporting_example() -> None: client = CamundaClient() # With `soft=True` exporting keeps running but its position is not committed, # so the log is still not compacted. Use it when exporting must keep # progressing -- for example while a backup is taken. client.pause_exporting(soft=True) ``` ### pin_clock() ```python async def pin_clock(*, data, **kwargs) ``` 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. **Parameters:** | Parameter | Type | Description | | --------- | ----------------- | ----------- | | `data` | `ClockPinRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Pin the cluster clock:** ```python def pin_clock_example() -> None: client = CamundaClient() client.pin_clock( data=ClockPinRequest( timestamp=1700000000000, ), ) ``` ### publish_message() ```python async def publish_message(*, data, **kwargs) ``` Publish message > Publishes a single message. > > Messages are published to specific partitions computed from their correlation keys. > Messages can be buffered. > The endpoint does not wait for a correlation result. > Use the message correlation endpoint for such use cases. **Parameters:** | Parameter | Type | Description | | --------- | --------------------------- | ----------- | | `data` | `MessagePublicationRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** MessagePublicationResult - **Return type:** MessagePublicationResult #### Examples **Publish a message:** ```python def publish_message_example() -> None: client = CamundaClient() result = client.publish_message( data=MessagePublicationRequest( name="order-created", correlation_key="order-12345", time_to_live=60000, ) ) print(f"Message key: {result.message_key}") ``` ### _property_ random _: RandomSource_ the injected source when supplied, else the live one. - **Type:** Randomness behind SDK jitter ### reset_clock() ```python async def reset_clock(**kwargs) ``` 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. - **Raises:** - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Parameters:** **kwargs** (_Any_) - **Return type:** None #### Examples **Reset the cluster clock:** ```python def reset_clock_example() -> None: client = CamundaClient() client.reset_clock() ``` ### resolve_incident() ```python async def resolve_incident(incident_key, *, data=, **kwargs) ``` Resolve incident > Marks the incident as resolved; most likely a call to Update job will be necessary > to reset the job’s retries, followed by this call. **Parameters:** | Parameter | Type | Description | | -------------- | -------------------------------------- | --------------------------------------------------------------- | | `incident_key` | `str` | System-generated key for a incident. Example: 2251799813689432. | | `data` | `IncidentResolutionRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The incident with the incidentKey is not found. - **errors.ConflictError** – If the response status code is 409. The incident cannot be resolved due to an invalid state. For example, the associated job may have no retries left. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Resolve an incident:** ```python def resolve_incident_example(incident_key: IncidentKey) -> None: client = CamundaClient() client.resolve_incident(incident_key=incident_key) ``` ### resolve_incidents_batch_operation() ```python async def resolve_incidents_batch_operation(*, data, **kwargs) ``` Resolve related incidents (batch) > Resolves multiple instances of process instances. > > Since only process instances with ACTIVE state can have unresolved incidents, any given > filters for state are ignored and overridden during this batch operation. > This is done asynchronously, the progress can be tracked using the batchOperationKey from the > response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | `data` | `ProcessInstanceIncidentResolutionBatchOperationRequest` | The process instance filter that defines which process instances should have their incidents resolved. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The process instance batch operation failed. More details are provided in the response body. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationCreatedResult - **Return type:** BatchOperationCreatedResult #### Examples **Resolve incidents in batch:** ```python def resolve_incidents_batch_operation_example() -> None: client = CamundaClient() result = client.resolve_incidents_batch_operation( data=ProcessInstanceIncidentResolutionBatchOperationRequest( filter_=ProcessInstanceCancellationBatchOperationRequestFilter(), ), ) print(f"Batch operation key: {result.batch_operation_key}") ``` ### resolve_process_instance_incidents() ```python async def resolve_process_instance_incidents(process_instance_key, **kwargs) ``` Resolve related incidents > Creates a batch operation to resolve multiple incidents of a process instance. **Parameters:** | Parameter | Type | Description | | ---------------------- | ----- | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The process instance is not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationCreatedResult - **Return type:** BatchOperationCreatedResult #### Examples **Resolve process instance incidents:** ```python def resolve_process_instance_incidents_example( process_instance_key: ProcessInstanceKey, ) -> None: client = CamundaClient() result = client.resolve_process_instance_incidents( process_instance_key=process_instance_key, ) print(f"Batch operation key: {result.batch_operation_key}") ``` ### resolve_secrets() ```python async def resolve_secrets(*, data, **kwargs) ``` Resolve secrets > Resolve a deduplicated batch of camunda.secrets.\* references for the caller’s > physical tenant in a single round-trip. > > Each reference is authorized and resolved independently. For valid requests, the endpoint > always responds with HTTP 200: successfully resolved references are returned in resolved, > while references that could not be resolved (for example not found, malformed or over-long, > or the caller lacks SECRET:REVEAL on that reference) are returned in errors. A failure of > one reference never fails the others. Only structurally invalid requests are rejected with > HTTP 400: a missing or non-array references field, more than 20 references, or a null entry. > > References are resolved against the secret stores configured for the caller’s physical > tenant, served from the gateway’s secret cache when the value is already cached and read > from the store otherwise. **Parameters:** | Parameter | Type | Description | | --------- | ---------------------- | ----------- | | `data` | `SecretResolveRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** SecretResolveResult - **Return type:** SecretResolveResult #### Examples **Resolve secrets:** ```python def resolve_secrets_example() -> None: client = CamundaClient() # Hands the resolved secret to whatever needs it (an HTTP client, a DB # driver, ...) without logging it. def use_secret(value: str) -> None: ... result = client.resolve_secrets( data=SecretResolveRequest( references=[ "camunda.secrets.my_api_token", "camunda.secrets.db_password", ], ) ) # Successfully resolved references are returned in `resolved`; references that # could not be resolved are returned in `errors`, each with a typed error code. # Never log a resolved value -- it holds secret material. Pass it straight to # the consumer that needs it instead. for resolved in result.resolved: print(f"Resolved {resolved.reference} (value redacted)") use_secret(resolved.value) for error in result.errors: print(f"Failed to resolve {error.reference}: {error.code.value} - {error.message}") ``` ### restore() ```python async def restore(*, data, dry_run=, **kwargs) ``` Restore from a backup > Restores the cluster from a backup. The restore is described either by a single backup ID or by a > time range (from/to) that selects the backups to restore. This endpoint is only accessible while > the cluster is in recovery mode; requests are rejected otherwise. The request is validated and > acknowledged, but the restore itself is performed asynchronously. **Parameters:** | Parameter | Type | Description | | --------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `dry_run` | `bool` \| `Unset` | | | `data` | `RestoreRequest` | Describes a restore request. Provide either a list of backup IDs or a time range (from/to) that selects the backups to restore; the two are mutually exclusive. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.ConflictError** – If the response status code is 409. The cluster is not in recovery mode, so the restore cannot be accepted. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterRestoreResponse - **Return type:** ClusterRestoreResponse #### Examples **Restore from a backup:** ```python def restore_example() -> None: client = CamundaClient() # The cluster must be in recovery mode before a restore is accepted. Provide # either a list of backup IDs (one per partition) or a time range (from/to) # that selects the backups to restore, but not both. result = client.restore( data=RestoreRequest(backup_ids=[100, 101]), ) print(f"Cluster change {result.change_id}:") for group in result.planned_changes: print(f" {group.physical_tenant_id or 'cluster-wide'}:") for operation in group.operations: mode = getattr(operation, "mode", None) suffix = f" -> {mode}" if mode else "" print(f" {operation.operation}{suffix}") ``` ### restore_as_cluster_admin() ```python async def restore_as_cluster_admin(*, data, physical_tenant_id=, dry_run=, **kwargs) ``` Restore one or every physical tenant from a backup > Restores physical tenants from backups. The restore is described either by a list of backup IDs or > by a time range (from/to) that selects the backups to restore. Restores are only accepted while > the targeted physical tenants are in recovery mode; requests are rejected otherwise. The request is > validated and acknowledged, but the restore itself is performed asynchronously. > > If the physicalTenantId parameter is provided, only that physical tenant is restored and > overrides must be omitted. > > If it is not provided, every physical tenant of the cluster is restored: those named in overrides > with their own backup selection, all others with the selection at the top level of the request body. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. **Parameters:** | Parameter | Type | Description | | -------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `physical_tenant_id` | `str` \| `Unset` | Example: default. | | `dry_run` | `bool` \| `Unset` | | | `data` | `ClusterRestoreRequest` | Describes a restore request issued by a cluster admin. The backup selection at the top level applies to every targeted physical tenant, except for the ones listed in overrides. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId, or a physical tenant named in overrides, does not exist in this cluster. - **errors.ConflictError** – If the response status code is 409. A targeted physical tenant is not in recovery mode, so the restore cannot be accepted. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterRestoreResponse - **Return type:** ClusterRestoreResponse #### Examples **Restore physical tenants from backup as cluster admin:** ```python def restore_as_cluster_admin_example() -> None: client = CamundaClient() # The targeted physical tenants must be in recovery mode before a restore is # accepted. Provide either backup_ids (one per partition) or a time range # (from_/to), but not both. # # Omit physical_tenant_id to restore every physical tenant. Supply it to # scope the restore to a single tenant (overrides must then be omitted). result = client.restore_as_cluster_admin( data=ClusterRestoreRequest( backup_ids=[100, 101], ), dry_run=True, ) print(f"Cluster change {result.change_id}:") for group in result.planned_changes: print(f" {group.physical_tenant_id or 'cluster-wide'}:") for operation in group.operations: mode = getattr(operation, "mode", None) suffix = f" -> {mode}" if mode else "" print(f" {operation.operation}{suffix}") ``` ### resume_batch_operation() ```python async def resume_batch_operation(batch_operation_key, *, data=, **kwargs) ``` Resume Batch operation > Resumes a suspended batch operation. > > This is done asynchronously, the progress can be tracked using the batch operation status endpoint > (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------------------- | ---------------- | ----------------------------------------------------------------------- | | `batch_operation_key` | `str` | System-generated key for an batch operation. Example: 2251799813684321. | | `data` | `Any` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The batch operation was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Resume a batch operation:** ```python def resume_batch_operation_example(batch_operation_key: BatchOperationKey) -> None: client = CamundaClient() client.resume_batch_operation( batch_operation_key=batch_operation_key, ) ``` ### resume_cluster_exporting() ```python async def resume_cluster_exporting(**kwargs) ``` Resume exporting across the whole cluster > Resumes exporting on every physical tenant of the cluster in one call, after a pause or soft pause. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Parameters:** **kwargs** (_Any_) - **Return type:** None #### Examples **Resume exporting across the whole cluster:** ```python def resume_cluster_exporting_example() -> None: client = CamundaClient() # Resumes exporting on every physical tenant after a pause or soft pause. client.resume_cluster_exporting() ``` ### resume_exporting() ```python async def resume_exporting(**kwargs) ``` Resume exporting > Resumes exporting on all partitions of the physical tenant after a pause or soft pause. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Parameters:** **kwargs** (_Any_) - **Return type:** None #### Examples **Resume exporting:** ```python def resume_exporting_example() -> None: client = CamundaClient() client.resume_exporting() ``` ### resume_process_instance() ```python async def resume_process_instance(process_instance_key, *, data=, **kwargs) ``` Resume process instance > Resumes a suspended process instance, returning it to the ACTIVE state and continuing processing. > > Only process instances in the SUSPENDED state can be resumed. > A child process instance can be resumed independently of its parent or root process > instance; resumption does not cascade to or from related instances. **Parameters:** | Parameter | Type | Description | | ---------------------- | --------------------------------------------------- | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `data` | `None` \| `ResumeProcessInstanceRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The process instance is not found. - **errors.ConflictError** – If the response status code is 409. The process instance is not in the SUSPENDED state and cannot be resumed. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Resume a process instance:** ```python def resume_process_instance_example(process_instance_key: ProcessInstanceKey) -> None: client = CamundaClient() client.resume_process_instance( process_instance_key=process_instance_key, ) ``` ### resume_process_instances_batch_operation() ```python async def resume_process_instances_batch_operation(*, data, **kwargs) ``` Resume process instances (batch) > Resumes multiple suspended process instances. > > Any given filter for state or parentProcessInstanceKey is ignored and overridden, as only > SUSPENDED process instances can be resumed and resumption does not cascade between parent > and child instances, so child instances are resumed independently of their parent or root > instance. > This is done asynchronously, the progress can be tracked using the batchOperationKey from the > response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------------------------ | ----------------------------------------------------------------------------------- | | `data` | `ProcessInstanceResumptionBatchOperationRequest` | The process instance filter that defines which process instances should be resumed. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The process instance batch operation failed. More details are provided in the response body. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationCreatedResult - **Return type:** BatchOperationCreatedResult #### Examples **Resume process instances in batch:** ```python def resume_process_instances_batch_operation_example() -> None: client = CamundaClient() result = client.resume_process_instances_batch_operation( data=ProcessInstanceResumptionBatchOperationRequest( filter_=ProcessInstanceCancellationBatchOperationRequestFilter(), ), ) print(f"Batch operation key: {result.batch_operation_key}") ``` ### run_workers() ```python async def run_workers() ``` ### search_agent_definitions() ```python async def search_agent_definitions(*, data=, consistency=None, **kwargs) ``` Search agent definitions > Search for agent definitions based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | --------------------------------------- | -------------------------------- | | `data` | `AgentDefinitionSearchQuery` \| `Unset` | Agent definition search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AgentDefinitionSearchQueryResult - **Return type:** AgentDefinitionSearchQueryResult #### Examples **Search agent definitions:** ```python def search_agent_definitions_example() -> None: client = CamundaClient() result = client.search_agent_definitions(data=AgentDefinitionSearchQuery()) for agent_definition in result.items: print(f"Agent definition key: {agent_definition.agent_definition_key}") ``` ### search_agent_instance_history() ```python async def search_agent_instance_history(agent_instance_key, *, data=, consistency=None, **kwargs) ``` Search agent instance history > Searches the conversation history of an agent instance. Committed items > are returned by default. **Parameters:** | Parameter | Type | Description | | -------------------- | -------------------------------------------- | ---------------------------------------------------------------------- | | `agent_instance_key` | `str` | System-generated key for an agent instance. Example: 4503599627370496. | | `data` | `AgentInstanceHistorySearchQuery` \| `Unset` | Agent instance history search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The agent instance with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AgentInstanceHistorySearchQueryResult - **Return type:** AgentInstanceHistorySearchQueryResult #### Examples **Search agent instance history:** ```python def search_agent_instance_history_example(agent_instance_key: AgentInstanceKey) -> None: client = CamundaClient() result = client.search_agent_instance_history( agent_instance_key=agent_instance_key, data=AgentInstanceHistorySearchQuery(), ) print(f"Found {len(result.items)} history items") ``` ### search_agent_instances() ```python async def search_agent_instances(*, data=, consistency=None, **kwargs) ``` Search agent instances > Search for agent instances based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------- | ------------------------------ | | `data` | `AgentInstanceSearchQuery` \| `Unset` | Agent instance search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AgentInstanceSearchQueryResult - **Return type:** AgentInstanceSearchQueryResult #### Examples **Search agent instances:** ```python def search_agent_instances_example() -> None: client = CamundaClient() result = client.search_agent_instances(data=AgentInstanceSearchQuery()) if not isinstance(result.items, Unset): for agent_instance in result.items: print(f"Agent instance key: {agent_instance.agent_instance_key}") ``` ### search_audit_logs() ```python async def search_audit_logs(*, data=, consistency=None, **kwargs) ``` Search audit logs > Search for audit logs based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | --------------------------------------- | ------------------------- | | `data` | `AuditLogSearchQueryRequest` \| `Unset` | Audit log search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AuditLogSearchQueryResult - **Return type:** AuditLogSearchQueryResult #### Examples **Search audit logs:** ```python def search_audit_logs_example() -> None: client = CamundaClient() result = client.search_audit_logs( data=AuditLogSearchQueryRequest(), ) if not isinstance(result.items, Unset): for log in result.items: print(f"Audit log: {log.audit_log_key}") ``` ### search_authorizations() ```python async def search_authorizations(*, data=, consistency=None, **kwargs) ``` Search authorizations > Search for authorizations based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------- | ----------- | | `data` | `AuthorizationSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AuthorizationSearchResult - **Return type:** AuthorizationSearchResult #### Examples **Search authorizations:** ```python def search_authorizations_example() -> None: client = CamundaClient() result = client.search_authorizations( data=AuthorizationSearchQuery(), ) if not isinstance(result.items, Unset): for auth in result.items: print(f"Authorization: {auth.authorization_key}") ``` ### search_batch_operation_items() ```python async def search_batch_operation_items(*, data=, consistency=None, **kwargs) ``` Search batch operation items > Search for batch operation items based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------ | ------------------------------------ | | `data` | `BatchOperationItemSearchQuery` \| `Unset` | Batch operation item search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationItemSearchQueryResult - **Return type:** BatchOperationItemSearchQueryResult #### Examples **Search batch operation items:** ```python def search_batch_operation_items_example(batch_operation_key: BatchOperationKey) -> None: client = CamundaClient() result = client.search_batch_operation_items( batch_operation_key=batch_operation_key, data=BatchOperationItemSearchQuery(), ) if not isinstance(result.items, Unset): for item in result.items: print(f"Item: {item.item_key}") ``` ### search_batch_operations() ```python async def search_batch_operations(*, data=, consistency=None, **kwargs) ``` Search batch operations > Search for batch operations based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | -------------------------------------- | ------------------------------- | | `data` | `BatchOperationSearchQuery` \| `Unset` | Batch operation search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationSearchQueryResult - **Return type:** BatchOperationSearchQueryResult #### Examples **Search batch operations:** ```python def search_batch_operations_example() -> None: client = CamundaClient() result = client.search_batch_operations( data=BatchOperationSearchQuery(), ) if not isinstance(result.items, Unset): for op in result.items: print(f"Batch operation: {op.batch_operation_key}") ``` ### search_clients_for_group() ```python async def search_clients_for_group(group_id, *, data=, consistency=None, **kwargs) ``` Search group clients > Search clients assigned to a group. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------ | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `data` | `GroupClientSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GroupClientSearchResult - **Return type:** GroupClientSearchResult #### Examples **Search clients in a group:** ```python def search_clients_for_group_example(group_id: GroupId) -> None: client = CamundaClient() result = client.search_clients_for_group( group_id=group_id, ) if not isinstance(result.items, Unset): for c in result.items: print(f"Client: {c.client_id}") ``` ### search_clients_for_role() ```python async def search_clients_for_role(role_id, *, data=, consistency=None, **kwargs) ``` Search role clients > Search clients with assigned role. **Parameters:** | Parameter | Type | Description | | ------------- | ----------------------------------------- | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `data` | `RoleClientSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RoleClientSearchResult - **Return type:** RoleClientSearchResult #### Examples **Search clients for a role:** ```python def search_clients_for_role_example(role_id: RoleId) -> None: client = CamundaClient() result = client.search_clients_for_role( role_id=role_id, ) if not isinstance(result.items, Unset): for c in result.items: print(f"Client: {c.client_id}") ``` ### search_clients_for_tenant() ```python async def search_clients_for_tenant(tenant_id, *, data=, consistency=None, **kwargs) ``` Search clients for tenant > Retrieves a filtered and sorted list of clients for a specified tenant. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `data` | `TenantClientSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TenantClientSearchResult - **Return type:** TenantClientSearchResult #### Examples **Search clients for a tenant:** ```python def search_clients_for_tenant_example(tenant_id: TenantId) -> None: client = CamundaClient() result = client.search_clients_for_tenant( tenant_id=tenant_id, ) if not isinstance(result.items, Unset): for c in result.items: print(f"Client: {c.client_id}") ``` ### search_cluster_variables() ```python async def search_cluster_variables(*, data=, truncate_values=, consistency=None, **kwargs) ``` Search for cluster variables based on given criteria. By default, long variable values in the response are truncated. **Parameters:** | Parameter | Type | Description | | ----------------- | ---------------------------------------------- | -------------------------------------- | | `truncate_values` | `bool` \| `Unset` | | | `data` | `ClusterVariableSearchQueryRequest` \| `Unset` | Cluster variable search query request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterVariableSearchQueryResult - **Return type:** ClusterVariableSearchQueryResult #### Examples **Search cluster variables:** ```python def search_cluster_variables_example() -> None: client = CamundaClient() result = client.search_cluster_variables( data=ClusterVariableSearchQueryRequest(), ) if not isinstance(result.items, Unset): for var in result.items: print(f"Variable: {var.name}") ``` ### search_correlated_message_subscriptions() ```python async def search_correlated_message_subscriptions(*, data=, consistency=None, **kwargs) ``` Search correlated message subscriptions > Search correlated message subscriptions based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ----------------------------------------------------- | ----------- | | `data` | `CorrelatedMessageSubscriptionSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** CorrelatedMessageSubscriptionSearchQueryResult - **Return type:** CorrelatedMessageSubscriptionSearchQueryResult #### Examples **Search correlated message subscriptions:** ```python def search_correlated_message_subscriptions_example() -> None: client = CamundaClient() result = client.search_correlated_message_subscriptions( data=CorrelatedMessageSubscriptionSearchQuery(), ) if not isinstance(result.items, Unset): for sub in result.items: print(f"Correlated subscription: {sub.message_name}") ``` ### search_decision_definitions() ```python async def search_decision_definitions(*, data=, consistency=None, **kwargs) ``` Search decision definitions > Search for decision definitions based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------ | ----------- | | `data` | `DecisionDefinitionSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DecisionDefinitionSearchQueryResult - **Return type:** DecisionDefinitionSearchQueryResult #### Examples **Search decision definitions:** ```python def search_decision_definitions_example() -> None: client = CamundaClient() result = client.search_decision_definitions( data=DecisionDefinitionSearchQuery() ) if not isinstance(result.items, Unset): for definition in result.items: print(f"Decision: {definition.decision_definition_id}") ``` ### search_decision_instances() ```python async def search_decision_instances(*, data=, consistency=None, **kwargs) ``` Search decision instances > Search for decision instances based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ---------------------------------------- | ----------- | | `data` | `DecisionInstanceSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DecisionInstanceSearchQueryResult - **Return type:** DecisionInstanceSearchQueryResult #### Examples **Search decision instances:** ```python def search_decision_instances_example() -> None: client = CamundaClient() result = client.search_decision_instances( data=DecisionInstanceSearchQuery(), ) if not isinstance(result.items, Unset): for di in result.items: print(f"Decision instance: {di.decision_definition_id}") ``` ### search_decision_requirements() ```python async def search_decision_requirements(*, data=, consistency=None, **kwargs) ``` Search decision requirements > Search for decision requirements based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | -------------------------------------------- | ----------- | | `data` | `DecisionRequirementsSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DecisionRequirementsSearchQueryResult - **Return type:** DecisionRequirementsSearchQueryResult #### Examples **Search decision requirements:** ```python def search_decision_requirements_example() -> None: client = CamundaClient() result = client.search_decision_requirements( data=DecisionRequirementsSearchQuery(), ) if not isinstance(result.items, Unset): for drd in result.items: print(f"DRD: {drd.decision_requirements_name}") ``` ### search_element_instance_incidents() ```python async def search_element_instance_incidents(element_instance_key, *, data, consistency=None, **kwargs) ``` Search for incidents of a specific element instance > Search for incidents caused by the specified element instance, including incidents of any child > instances created from this element instance. > > Although the elementInstanceKey is provided as a path parameter to indicate the root element > instance, > you may also include an elementInstanceKey within the filter object to narrow results to specific > child element instances. This is useful, for example, if you want to isolate incidents associated > with > nested or subordinate elements within the given element instance while excluding incidents directly > tied > to the root element itself. **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------ | ----------------------------------------------------------------------- | | `element_instance_key` | `str` | System-generated key for a element instance. Example: 2251799813686789. | | `data` | `IncidentSearchQuery` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The element instance with the given key was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** IncidentSearchQueryResult - **Return type:** IncidentSearchQueryResult #### Examples **Search element instance incidents:** ```python def search_element_instance_incidents_example( element_instance_key: ElementInstanceKey, ) -> None: client = CamundaClient() result = client.search_element_instance_incidents( element_instance_key=element_instance_key, data=IncidentSearchQuery(), ) if not isinstance(result.items, Unset): for incident in result.items: print(f"Incident: {incident.incident_key}") ``` ### search_element_instance_wait_states() ```python async def search_element_instance_wait_states(*, data=, consistency=None, **kwargs) ``` Search element instance wait states > Returns the wait states for element instances matching the given filter. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------ | ------------------------------------ | | `data` | `ElementInstanceWaitStateQuery` \| `Unset` | Element instance inspection request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ElementInstanceWaitStateQueryResult - **Return type:** ElementInstanceWaitStateQueryResult #### Examples **Search element instance wait states:** ```python def search_element_instance_wait_states_example() -> None: client = CamundaClient() result = client.search_element_instance_wait_states( data=ElementInstanceWaitStateQuery(), ) for wait_state in result.items: details = wait_state.details if isinstance(details, JobWaitStateDetails): info = f"waiting on job '{details.job_type}'" elif isinstance(details, MessageWaitStateDetails): info = f"waiting for message '{details.message_name}'" else: info = f"waiting ({details.wait_state_type})" print( f"Element {wait_state.element_id} " f"(instance {wait_state.element_instance_key}) {info}" ) ``` ### search_element_instances() ```python async def search_element_instances(*, data=, consistency=None, **kwargs) ``` Search element instances > Search for element instances based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | --------------------------------------- | -------------------------------- | | `data` | `ElementInstanceSearchQuery` \| `Unset` | Element instance search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ElementInstanceSearchQueryResult - **Return type:** ElementInstanceSearchQueryResult #### Examples **Search element instances:** ```python def search_element_instances_example() -> None: client = CamundaClient() result = client.search_element_instances( data=ElementInstanceSearchQuery(), ) if not isinstance(result.items, Unset): for ei in result.items: print(f"Element instance: {ei.element_instance_key}") ``` ### search_global_task_listeners() ```python async def search_global_task_listeners(*, data=, consistency=None, **kwargs) ``` Search global user task listeners > Search for global user task listeners based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------------- | ------------------------------------- | | `data` | `GlobalTaskListenerSearchQueryRequest` \| `Unset` | Global listener search query request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GlobalTaskListenerSearchQueryResult - **Return type:** GlobalTaskListenerSearchQueryResult #### Examples **Search global task listeners:** ```python def search_global_task_listeners_example() -> None: client = CamundaClient() result = client.search_global_task_listeners( data=GlobalTaskListenerSearchQueryRequest(), ) if not isinstance(result.items, Unset): for listener in result.items: print(f"Listener: {listener.id}") ``` ### search_group_ids_for_tenant() ```python async def search_group_ids_for_tenant(tenant_id, *, data=, consistency=None, **kwargs) ``` Search groups for tenant > Retrieves a filtered and sorted list of groups for a specified tenant. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------ | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `data` | `TenantGroupSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TenantGroupSearchResult - **Return type:** TenantGroupSearchResult #### Examples **Search groups for a tenant:** ```python def search_group_ids_for_tenant_example(tenant_id: TenantId) -> None: client = CamundaClient() result = client.search_group_ids_for_tenant( tenant_id=tenant_id, data=TenantGroupSearchQueryRequest(), ) if not isinstance(result.items, Unset): for group in result.items: print(f"Group: {group.group_id}") ``` ### search_groups() ```python async def search_groups(*, data=, consistency=None, **kwargs) ``` Search groups > Search for groups based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------ | --------------------- | | `data` | `GroupSearchQueryRequest` \| `Unset` | Group search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GroupSearchQueryResult - **Return type:** GroupSearchQueryResult #### Examples **Search groups:** ```python def search_groups_example() -> None: client = CamundaClient() result = client.search_groups( data=GroupSearchQueryRequest(), ) if not isinstance(result.items, Unset): for group in result.items: print(f"Group: {group.name}") ``` ### search_groups_for_role() ```python async def search_groups_for_role(role_id, *, data=, consistency=None, **kwargs) ``` Search role groups > Search groups with assigned role. **Parameters:** | Parameter | Type | Description | | ------------- | ---------------------------------------- | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `data` | `RoleGroupSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RoleGroupSearchResult - **Return type:** RoleGroupSearchResult #### Examples **Search groups for a role:** ```python def search_groups_for_role_example(role_id: RoleId) -> None: client = CamundaClient() result = client.search_groups_for_role( role_id=role_id, data=RoleGroupSearchQueryRequest(), ) if not isinstance(result.items, Unset): for group in result.items: print(f"Group: {group.group_id}") ``` ### search_incidents() ```python async def search_incidents(*, data=, consistency=None, **kwargs) ``` Search incidents > Search for incidents based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | -------------------------------- | ----------- | | `data` | `IncidentSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** IncidentSearchQueryResult - **Return type:** IncidentSearchQueryResult #### Examples **Search incidents:** ```python def search_incidents_example() -> None: client = CamundaClient() result = client.search_incidents( data=IncidentSearchQuery() ) if not isinstance(result.items, Unset): for incident in result.items: print(f"Incident key: {incident.incident_key}") ``` ### search_jobs() ```python async def search_jobs(*, data=, consistency=None, **kwargs) ``` Search jobs > Search for jobs based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | ------------------- | | `data` | `JobSearchQuery` \| `Unset` | Job search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** JobSearchQueryResult - **Return type:** JobSearchQueryResult #### Examples **Search jobs:** ```python def search_jobs_example() -> None: client = CamundaClient() result = client.search_jobs( data=JobSearchQuery(), ) if not isinstance(result.items, Unset): for job in result.items: print(f"Job: {job.job_key}") ``` ### search_mapping_rule() ```python async def search_mapping_rule(*, data=, consistency=None, **kwargs) ``` Search mapping rules > Search for mapping rules based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------ | ----------- | | `data` | `MappingRuleSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** MappingRuleSearchQueryResult - **Return type:** MappingRuleSearchQueryResult #### Examples **Search mapping rules:** ```python def search_mapping_rule_example() -> None: client = CamundaClient() result = client.search_mapping_rule( data=MappingRuleSearchQueryRequest(), ) if not isinstance(result.items, Unset): for rule in result.items: print(f"Mapping rule: {rule.name}") ``` ### search_mapping_rules_for_group() ```python async def search_mapping_rules_for_group(group_id, *, data=, consistency=None, **kwargs) ``` Search group mapping rules > Search mapping rules assigned to a group. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------ | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `data` | `MappingRuleSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GroupMappingRuleSearchResult - **Return type:** GroupMappingRuleSearchResult #### Examples **Search mapping rules for a group:** ```python def search_mapping_rules_for_group_example(group_id: GroupId) -> None: client = CamundaClient() result = client.search_mapping_rules_for_group( group_id=group_id, data=MappingRuleSearchQueryRequest(), ) if not isinstance(result.items, Unset): for rule in result.items: print(f"Mapping rule: {rule.mapping_rule_id}") ``` ### search_mapping_rules_for_role() ```python async def search_mapping_rules_for_role(role_id, *, data=, consistency=None, **kwargs) ``` Search role mapping rules > Search mapping rules with assigned role. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------ | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `data` | `MappingRuleSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RoleMappingRuleSearchResult - **Return type:** RoleMappingRuleSearchResult #### Examples **Search mapping rules for a role:** ```python def search_mapping_rules_for_role_example(role_id: RoleId) -> None: client = CamundaClient() result = client.search_mapping_rules_for_role( role_id=role_id, data=MappingRuleSearchQueryRequest(), ) if not isinstance(result.items, Unset): for rule in result.items: print(f"Mapping rule: {rule.mapping_rule_id}") ``` ### search_mapping_rules_for_tenant() ```python async def search_mapping_rules_for_tenant(tenant_id, *, data=, consistency=None, **kwargs) ``` Search mapping rules for tenant > Retrieves a filtered and sorted list of MappingRules for a specified tenant. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------ | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `data` | `MappingRuleSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TenantMappingRuleSearchResult - **Return type:** TenantMappingRuleSearchResult #### Examples **Search mapping rules for a tenant:** ```python def search_mapping_rules_for_tenant_example(tenant_id: TenantId) -> None: client = CamundaClient() result = client.search_mapping_rules_for_tenant( tenant_id=tenant_id, data=MappingRuleSearchQueryRequest(), ) if not isinstance(result.items, Unset): for rule in result.items: print(f"Mapping rule: {rule.mapping_rule_id}") ``` ### search_message_subscriptions() ```python async def search_message_subscriptions(*, data=, consistency=None, **kwargs) ``` Search message subscriptions > Search for message subscriptions based on given criteria. > > By default, both start and intermediate event subscriptions are returned. Use the > messageSubscriptionType filter to restrict results to a single type. > > **Version notes:** - Start event subscriptions are only captured for deployments made with 8.10 or later. - The messageSubscriptionType field is only populated for data created > with Camunda 8.10 or later. For pre-8.10 data, intermediate event entries have no > messageSubscriptionType value stored. For convenience, the API returns PROCESS_EVENT > as a default for such search results, though. - Searching for intermediate event subscriptions **including legacy data** can be achieved by filtering for messageSubscriptionType not matching START_EVENT. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------- | ----------- | | `data` | `MessageSubscriptionSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** MessageSubscriptionSearchQueryResult - **Return type:** MessageSubscriptionSearchQueryResult #### Examples **Search message subscriptions:** ```python def search_message_subscriptions_example() -> None: client = CamundaClient() result = client.search_message_subscriptions( data=MessageSubscriptionSearchQuery(), ) if not isinstance(result.items, Unset): for sub in result.items: print(f"Subscription: {sub.message_name}") ``` ### search_own_authorizations() ```python async def search_own_authorizations(*, data=, consistency=None, **kwargs) ``` Search own authorizations > Search for the current authenticated principal’s own authorization records — including > authorizations granted directly to the user or client, as well as those granted via a group, role, > or mapping rule the principal belongs to. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------- | ----------- | | `data` | `AuthorizationSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** OwnAuthorizationSearchResult - **Return type:** OwnAuthorizationSearchResult #### Examples **Search own authorizations:** ```python def search_own_authorizations_example() -> None: client = CamundaClient() result = client.search_own_authorizations( data=AuthorizationSearchQuery( filter_=AuthorizationSearchQueryFilter( resource_type=AuthorizationSearchQueryFilterResourceType.PROCESS_DEFINITION, ), page=LimitBasedPagination(limit=20), ) ) if not isinstance(result.items, Unset): for auth in result.items: print(f"Resource: {auth.resource_id}, permissions: {auth.permission_types}") ``` ### search_process_definition_variable_names() ```python async def search_process_definition_variable_names(process_definition_key, *, data=, consistency=None, **kwargs) ``` Search process definition variable names > Search for distinct variable names defined on a process definition, optionally narrowed by the name > filter. **Parameters:** | Parameter | Type | Description | | ------------------------ | ----------------------------------------------------- | ---------------------------------------------------------------------------------- | | `process_definition_key` | `str` | System-generated key for a deployed process definition. Example: 2251799813686749. | | `data` | `ProcessDefinitionVariableNameSearchQuery` \| `Unset` | Process definition variable name search query request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessDefinitionVariableNameSearchQueryResult - **Return type:** ProcessDefinitionVariableNameSearchQueryResult #### Examples **Search process definition variable names:** ```python def search_process_definition_variable_names_example( process_definition_key: ProcessDefinitionKey, ) -> None: client = CamundaClient() result = client.search_process_definition_variable_names( process_definition_key=process_definition_key, data=ProcessDefinitionVariableNameSearchQuery(), ) if not isinstance(result.items, Unset): for variable in result.items: print(f"Variable name: {variable.name}") ``` ### search_process_definitions() ```python async def search_process_definitions(*, data=, consistency=None, **kwargs) ``` Search process definitions > Search for process definitions based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ----------------------------------------- | ----------- | | `data` | `ProcessDefinitionSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessDefinitionSearchQueryResult - **Return type:** ProcessDefinitionSearchQueryResult #### Examples **Search process definitions:** ```python def search_process_definitions_example() -> None: client = CamundaClient() result = client.search_process_definitions( data=ProcessDefinitionSearchQuery(), ) if not isinstance(result.items, Unset): for pd in result.items: print(f"Process definition: {pd.name}") ``` ### search_process_instance_incidents() ```python async def search_process_instance_incidents(process_instance_key, *, data=, consistency=None, **kwargs) ``` Search related incidents > Search for incidents caused by the process instance or any of its called process or decision > instances. > > Although the processInstanceKey is provided as a path parameter to indicate the root process > instance, > you may also include a processInstanceKey within the filter object to narrow results to specific > child process instances. This is useful, for example, if you want to isolate incidents associated > with > subprocesses or called processes under the root instance while excluding incidents directly tied to > the root. **Parameters:** | Parameter | Type | Description | | ---------------------- | -------------------------------- | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `data` | `IncidentSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The process instance with the given key was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** IncidentSearchQueryResult - **Return type:** IncidentSearchQueryResult #### Examples **Search process instance incidents:** ```python def search_process_instance_incidents_example( process_instance_key: ProcessInstanceKey, ) -> None: client = CamundaClient() result = client.search_process_instance_incidents( process_instance_key=process_instance_key, data=IncidentSearchQuery(), ) if not isinstance(result.items, Unset): for incident in result.items: print(f"Incident: {incident.incident_key}") ``` ### search_process_instances() ```python async def search_process_instances(*, data=, consistency=None, **kwargs) ``` Search process instances > Search for process instances based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | --------------------------------------- | -------------------------------- | | `data` | `ProcessInstanceSearchQuery` \| `Unset` | Process instance search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessInstanceSearchQueryResult - **Return type:** ProcessInstanceSearchQueryResult #### Examples **Search process instances:** ```python def search_process_instances_example() -> None: client = CamundaClient() result = client.search_process_instances( data=ProcessInstanceSearchQuery( filter_=ProcessInstanceSearchQueryFilter( process_definition_id="order-process", ), sort=[ ProcessInstanceSearchQuerySortRequest( field=ProcessInstanceSearchQuerySortRequestField.STARTDATE, order=SortOrderEnum.DESC, ) ], page=LimitBasedPagination(limit=10), ) ) for instance in result.items: print(f"{instance.process_instance_key}: {instance.state}") print(f"Total: {result.page.total_items}") ``` ### search_resources() ```python async def search_resources(*, data=, consistency=None, **kwargs) ``` Search resources > Search for deployed resources based on given criteria. :::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 search APIs. ::: - **data**: :type data: ResourceSearchQuery | Unset ```` * **Raises:** * **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. * **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. * **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. * **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. * **errors.UnexpectedStatus** – If the response status code is not documented. * **httpx.TimeoutException** – If the request takes longer than Client.timeout. * **Returns:** ResourceSearchQueryResult **Parameters:** | Parameter | Type | Description | | --- | --- | --- | | `data` | `ResourceSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | * **Return type:** ResourceSearchQueryResult #### Examples **Search resources:** ```python def search_resources_example() -> None: client = CamundaClient() result = client.search_resources( data=ResourceSearchQuery(), ) if not isinstance(result.items, Unset): for resource in result.items: print(f"Resource: {resource.resource_name}") ```` ### search_roles() ```python async def search_roles(*, data=, consistency=None, **kwargs) ``` Search roles > Search for roles based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ----------------------------------- | -------------------- | | `data` | `RoleSearchQueryRequest` \| `Unset` | Role search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RoleSearchQueryResult - **Return type:** RoleSearchQueryResult #### Examples **Search roles:** ```python def search_roles_example() -> None: client = CamundaClient() result = client.search_roles( data=RoleSearchQueryRequest(), ) if not isinstance(result.items, Unset): for role in result.items: print(f"Role: {role.name}") ``` ### search_roles_for_group() ```python async def search_roles_for_group(group_id, *, data=, consistency=None, **kwargs) ``` Search group roles > Search roles assigned to a group. **Parameters:** | Parameter | Type | Description | | ------------- | ----------------------------------- | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `data` | `RoleSearchQueryRequest` \| `Unset` | Role search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GroupRoleSearchResult - **Return type:** GroupRoleSearchResult #### Examples **Search roles for a group:** ```python def search_roles_for_group_example(group_id: GroupId) -> None: client = CamundaClient() result = client.search_roles_for_group( group_id=group_id, data=RoleSearchQueryRequest(), ) if not isinstance(result.items, Unset): for role in result.items: print(f"Role: {role.name}") ``` ### search_roles_for_tenant() ```python async def search_roles_for_tenant(tenant_id, *, data=, consistency=None, **kwargs) ``` Search roles for tenant > Retrieves a filtered and sorted list of roles for a specified tenant. **Parameters:** | Parameter | Type | Description | | ------------- | ----------------------------------- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `data` | `RoleSearchQueryRequest` \| `Unset` | Role search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TenantRoleSearchResult - **Return type:** TenantRoleSearchResult #### Examples **Search roles for a tenant:** ```python def search_roles_for_tenant_example(tenant_id: TenantId) -> None: client = CamundaClient() result = client.search_roles_for_tenant( tenant_id=tenant_id, data=RoleSearchQueryRequest(), ) if not isinstance(result.items, Unset): for role in result.items: print(f"Role: {role.name}") ``` ### search_tenants() ```python async def search_tenants(*, data=, consistency=None, **kwargs) ``` Search tenants > Retrieves a filtered and sorted list of tenants. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------- | --------------------- | | `data` | `TenantSearchQueryRequest` \| `Unset` | Tenant search request | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TenantSearchQueryResult - **Return type:** TenantSearchQueryResult #### Examples **Search tenants:** ```python def search_tenants_example() -> None: client = CamundaClient() result = client.search_tenants( data=TenantSearchQueryRequest(), ) if not isinstance(result.items, Unset): for tenant in result.items: print(f"Tenant: {tenant.name}") ``` ### search_user_task_audit_logs() ```python async def search_user_task_audit_logs(user_task_key, *, data=, consistency=None, **kwargs) ``` Search user task audit logs > Search for user task audit logs based on given criteria. **Parameters:** | Parameter | Type | Description | | --------------- | ----------------------------------------------- | ------------------------------------- | | `user_task_key` | `str` | System-generated key for a user task. | | `data` | `UserTaskAuditLogSearchQueryRequest` \| `Unset` | User task search query request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AuditLogSearchQueryResult - **Return type:** AuditLogSearchQueryResult #### Examples **Search user task audit logs:** ```python def search_user_task_audit_logs_example(user_task_key: UserTaskKey) -> None: client = CamundaClient() result = client.search_user_task_audit_logs( user_task_key=user_task_key, data=UserTaskAuditLogSearchQueryRequest(), ) if not isinstance(result.items, Unset): for log in result.items: print(f"Audit log: {log.audit_log_key}") ``` ### search_user_task_effective_variables() ```python async def search_user_task_effective_variables(user_task_key, *, data=, truncate_values=, consistency=None, **kwargs) ``` Search user task effective variables > Search for the effective variables of a user task. This endpoint returns deduplicated > variables where each variable name appears at most once. When the same variable name exists > at multiple scope levels in the scope hierarchy, the value from the innermost scope (closest > to the user task) takes precedence. This is useful for retrieving the actual runtime state > of variables as seen by the user task. By default, long variable values in the response are > truncated. **Parameters:** | Parameter | Type | Description | | ----------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------- | | `user_task_key` | `str` | System-generated key for a user task. | | `truncate_values` | `bool` \| `Unset` | | | `data` | `UserTaskEffectiveVariableSearchQueryRequest` \| `Unset` | User task effective variable search query request. Uses offset-based pagination only. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** VariableSearchQueryResult - **Return type:** VariableSearchQueryResult #### Examples **Search user task effective variables:** ```python def search_user_task_effective_variables_example(user_task_key: UserTaskKey) -> None: client = CamundaClient() result = client.search_user_task_effective_variables( user_task_key=user_task_key, ) if not isinstance(result.items, Unset): for var in result.items: print(f"Variable: {var.name}") ``` ### search_user_task_variables() ```python async def search_user_task_variables(user_task_key, *, data=, truncate_values=, consistency=None, **kwargs) ``` Search user task variables > Search for user task variables based on given criteria. This endpoint returns all variable > documents visible from the user task’s scope, including variables from parent scopes in the > scope hierarchy. If the same variable name exists at multiple scope levels, each scope’s > variable is returned as a separate result. Use the > /user-tasks/{userTaskKey}/effective-variables/search endpoint to get deduplicated variables > where the innermost scope takes precedence. By default, long variable values in the response > are truncated. **Parameters:** | Parameter | Type | Description | | ----------------- | ----------------------------------------------- | ------------------------------------- | | `user_task_key` | `str` | System-generated key for a user task. | | `truncate_values` | `bool` \| `Unset` | | | `data` | `UserTaskVariableSearchQueryRequest` \| `Unset` | User task search query request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** VariableSearchQueryResult - **Return type:** VariableSearchQueryResult #### Examples **Search user task variables:** ```python def search_user_task_variables_example(user_task_key: UserTaskKey) -> None: client = CamundaClient() result = client.search_user_task_variables( user_task_key=user_task_key, ) if not isinstance(result.items, Unset): for var in result.items: print(f"Variable: {var.name}") ``` ### search_user_tasks() ```python async def search_user_tasks(*, data=, consistency=None, **kwargs) ``` Search user tasks > Search for user tasks based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | -------------------------------- | ------------------------------- | | `data` | `UserTaskSearchQuery` \| `Unset` | User task search query request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** UserTaskSearchQueryResult - **Return type:** UserTaskSearchQueryResult #### Examples **Search user tasks:** ```python def search_user_tasks_example() -> None: client = CamundaClient() result = client.search_user_tasks( data=UserTaskSearchQuery() ) if not isinstance(result.items, Unset): for task in result.items: print(f"Task: {task.user_task_key}") ``` ### search_users() ```python async def search_users(*, data=, consistency=None, **kwargs) ``` Search users > Search for users based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ----------------------------------- | ----------- | | `data` | `UserSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** UserSearchResult - **Return type:** UserSearchResult #### Examples **Search users:** ```python def search_users_example() -> None: client = CamundaClient() result = client.search_users( data=UserSearchQueryRequest(), ) if not isinstance(result.items, Unset): for user in result.items: print(f"User: {user.username}") ``` ### search_users_for_group() ```python async def search_users_for_group(group_id, *, data=, consistency=None, **kwargs) ``` Search group users > Search users assigned to a group. **Parameters:** | Parameter | Type | Description | | ------------- | ---------------------------------------- | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `data` | `GroupUserSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GroupUserSearchResult - **Return type:** GroupUserSearchResult #### Examples **Search users in a group:** ```python def search_users_for_group_example(group_id: GroupId) -> None: client = CamundaClient() result = client.search_users_for_group( group_id=group_id, ) if not isinstance(result.items, Unset): for user in result.items: print(f"User: {user.username}") ``` ### search_users_for_role() ```python async def search_users_for_role(role_id, *, data=, consistency=None, **kwargs) ``` Search role users > Search users with assigned role. **Parameters:** | Parameter | Type | Description | | ------------- | --------------------------------------- | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `data` | `RoleUserSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RoleUserSearchResult - **Return type:** RoleUserSearchResult #### Examples **Search users for a role:** ```python def search_users_for_role_example(role_id: RoleId) -> None: client = CamundaClient() result = client.search_users_for_role( role_id=role_id, ) if not isinstance(result.items, Unset): for user in result.items: print(f"User: {user.username}") ``` ### search_users_for_tenant() ```python async def search_users_for_tenant(tenant_id, *, data=, consistency=None, **kwargs) ``` Search users for tenant > Retrieves a filtered and sorted list of users for a specified tenant. **Parameters:** | Parameter | Type | Description | | ------------- | ----------------------------------------- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `data` | `TenantUserSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TenantUserSearchResult - **Return type:** TenantUserSearchResult #### Examples **Search users for a tenant:** ```python def search_users_for_tenant_example(tenant_id: TenantId) -> None: client = CamundaClient() result = client.search_users_for_tenant( tenant_id=tenant_id, ) if not isinstance(result.items, Unset): for user in result.items: print(f"User: {user.username}") ``` ### search_variables() ```python async def search_variables(*, data=, truncate_values=, consistency=None, **kwargs) ``` Search variables > Search for variables based on given criteria. > > This endpoint returns variables that exist directly at the specified scopes - it does not > include variables from parent scopes that would be visible through the scope hierarchy. > > Variables can be process-level (scoped to the process instance) or local (scoped to specific > BPMN elements like tasks, subprocesses, etc.). > > By default, long variable values in the response are truncated. **Parameters:** | Parameter | Type | Description | | ----------------- | -------------------------------- | ------------------------------ | | `truncate_values` | `bool` \| `Unset` | | | `data` | `VariableSearchQuery` \| `Unset` | Variable search query request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** VariableSearchQueryResult - **Return type:** VariableSearchQueryResult #### Examples **Search variables:** ```python def search_variables_example() -> None: client = CamundaClient() result = client.search_variables() if not isinstance(result.items, Unset): for var in result.items: print(f"Variable: {var.name}") ``` ### search_variables_as_dto() ```python async def search_variables_as_dto(dto, *, process_instance_key, scope_key=None, tenant_id=None, page_size=100, consistency=None) ``` Fetch the variables declared by a Pydantic model for a process instance. Async variant of [`CamundaClient.search_variables_as_dto()`](client.md#camunda_orchestration_sdk.CamundaClient.search_variables_as_dto). **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------ | ----------- | | `dto` | type [ \_VarDtoT ] | | | `process_instance_key` | `str` | | | `scope_key` | `str` \| `None` | | | `tenant_id` | `str` \| `None` | | | `page_size` | `int` | | | `consistency` | `ConsistencyOptions` \| `None` | | - **Return type:** _VariableMap_[ _\_VarDtoT_] ### suspend_batch_operation() ```python async def suspend_batch_operation(batch_operation_key, *, data=, **kwargs) ``` Suspend Batch operation > Suspends a running batch operation. > > This is done asynchronously, the progress can be tracked using the batch operation status endpoint > (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------------------- | ---------------- | ----------------------------------------------------------------------- | | `batch_operation_key` | `str` | System-generated key for an batch operation. Example: 2251799813684321. | | `data` | `Any` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The batch operation was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Suspend a batch operation:** ```python def suspend_batch_operation_example(batch_operation_key: BatchOperationKey) -> None: client = CamundaClient() client.suspend_batch_operation( batch_operation_key=batch_operation_key, ) ``` ### suspend_process_instance() ```python async def suspend_process_instance(process_instance_key, *, data=, **kwargs) ``` Suspend process instance > Suspends a running process instance, pausing further processing until it is resumed. > > Only process instances in the ACTIVE state can be suspended. > A child process instance can be suspended independently of its parent or root process > instance; suspension does not cascade to or from related instances. **Parameters:** | Parameter | Type | Description | | ---------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `data` | `None` \| `SuspendProcessInstanceRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The process instance is not found. - **errors.ConflictError** – If the response status code is 409. The process instance is not in the ACTIVE state and cannot be suspended. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Suspend a process instance:** ```python def suspend_process_instance_example(process_instance_key: ProcessInstanceKey) -> None: client = CamundaClient() client.suspend_process_instance( process_instance_key=process_instance_key, ) ``` ### suspend_process_instances_batch_operation() ```python async def suspend_process_instances_batch_operation(*, data, **kwargs) ``` Suspend process instances (batch) > Suspends multiple running process instances. > > Any given filter for state or parentProcessInstanceKey is ignored and overridden, as only > ACTIVE process instances can be suspended and suspension does not cascade between parent > and child instances, so child instances are suspended independently of their parent or > root instance. > This is done asynchronously, the progress can be tracked using the batchOperationKey from the > response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------------------------ | ------------------------------------------------------------------------------------- | | `data` | `ProcessInstanceSuspensionBatchOperationRequest` | The process instance filter that defines which process instances should be suspended. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The process instance batch operation failed. More details are provided in the response body. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationCreatedResult - **Return type:** BatchOperationCreatedResult #### Examples **Suspend process instances in batch:** ```python def suspend_process_instances_batch_operation_example() -> None: client = CamundaClient() result = client.suspend_process_instances_batch_operation( data=ProcessInstanceSuspensionBatchOperationRequest( filter_=ProcessInstanceCancellationBatchOperationRequestFilter(), ), ) print(f"Batch operation key: {result.batch_operation_key}") ``` ### sync_runtime_backup_state() ```python async def sync_runtime_backup_state(**kwargs) ``` Force-write runtime backup state > Force-writes the checkpoint and backup metadata of every partition of the physical > tenant to the backup store, independent of any backup being taken or confirmed, and > returns the updated state. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.GatewayTimeoutError** – If the response status code is 504. The request from gateway to broker timed out. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RuntimeBackupState - **Parameters:** **kwargs** (_Any_) - **Return type:** RuntimeBackupState #### Examples **Force-write the runtime backup state:** ```python def sync_runtime_backup_state_example() -> None: client = CamundaClient() # Force-writes the checkpoint and backup metadata of every partition to the # backup store, independent of any backup being taken or confirmed. result = client.sync_runtime_backup_state() print(f"Synced {len(result.backup_states)} partition backup states") ``` ### sync_runtime_backup_state_as_cluster_admin() ```python async def sync_runtime_backup_state_as_cluster_admin(*, physical_tenant_id=, **kwargs) ``` Force-write runtime backup state across physical tenants > Force-writes the checkpoint and backup metadata of every partition of every physical tenant of the > cluster, or of the one named by physicalTenantId, to that tenant’s backup store, independent of > any backup being taken or confirmed, and returns the updated state per physical tenant. > > The request is all-or-nothing: a physical tenant whose metadata cannot be written fails the whole > request, and the writes that already succeeded on other tenants are not undone. The operation is > idempotent, so retrying the same call is the correct remedy. Narrow the request with > physicalTenantId to write the tenants that can still be reached. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Use POST > /v2/backups/runtime/state/sync to act as a single physical tenant. **Parameters:** | Parameter | Type | Description | | -------------------- | ---------------- | ----------------- | | `physical_tenant_id` | `str` \| `Unset` | Example: default. | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.GatewayTimeoutError** – If the response status code is 504. The request from gateway to broker timed out on at least one targeted physical tenant. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterRuntimeBackupState - **Return type:** ClusterRuntimeBackupState #### Examples **Force-write runtime backup state across physical tenants:** ```python def sync_runtime_backup_state_as_cluster_admin_example() -> None: client = CamundaClient() # Force-writes the checkpoint and backup metadata of every partition of # every physical tenant to the backup store, independent of any backup being # taken or confirmed. Use `physical_tenant_id` to narrow to a single tenant. result = client.sync_runtime_backup_state_as_cluster_admin() print(f"Synced {len(result.physical_tenants)} physical tenant backup states") ``` ### take_history_backup() ```python async def take_history_backup(*, data, **kwargs) ``` Take a history backup > Triggers a backup of the physical tenant’s history, by scheduling a snapshot of every > secondary storage index it owns. > > Unlike runtime backups, history backups have no generated-id mode: backupId is always > required. > > Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. **Parameters:** | Parameter | Type | Description | | --------- | -------------------------- | ----------------------------------------- | | `data` | `TakeHistoryBackupRequest` | Request body for taking a history backup. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. The request is forbidden for one of three reasons: the authenticated caller lacks the required BACKUP permission; the cluster’s secondary storage is neither Elasticsearch nor OpenSearch and therefore cannot serve history backups; or the physical tenant’s snapshot repository is absent from the store — configured under a name the store does not have, or not configured at all. The problem detail says which applies. The latter two are deployment faults the caller cannot correct by changing its request. - **errors.ConflictError** – If the response status code is 409. A backup with the given id already exists, or another backup is already running. The “already running” check is best-effort and node-local: it only observes backups started by the gateway that serves the request. Two concurrent requests reaching different gateways are narrowed by the duplicate-id check alone. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TakeHistoryBackupResponse - **Return type:** TakeHistoryBackupResponse #### Examples **Take a history backup:** ```python def take_history_backup_example(backup_id: int) -> None: client = CamundaClient() # Backups are logically ordered by id, so each successive backup must use a # higher id than the previous one. result = client.take_history_backup( data=TakeHistoryBackupRequest( backup_id=backup_id, ) ) print(f"Scheduled history backup {result.backup_id}") for snapshot in result.scheduled_snapshots: print(f" {snapshot}") ``` ### take_history_backup_as_cluster_admin() ```python async def take_history_backup_as_cluster_admin(*, data, physical_tenant_id=, **kwargs) ``` Take a history backup on one or every physical tenant > Triggers a history backup on every physical tenant of the cluster, or on the one named by > physicalTenantId. Every targeted tenant uses the same caller-supplied backupId, but the backups > are independent: they are neither coordinated nor rolled back together. > > The request is all-or-nothing: the backupId is checked on every targeted tenant before any > snapshot is scheduled, so a tenant that already holds this id, or that cannot be reached, fails the > whole request and no backup is started anywhere. There is no aggregated cluster-level state in the > response. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Only available on clusters > whose secondary storage is Elasticsearch or OpenSearch. Use POST /v2/backups/history to act as a > single physical tenant. **Parameters:** | Parameter | Type | Description | | -------------------- | -------------------------- | ----------------------------------------- | | `physical_tenant_id` | `str` \| `Unset` | Example: default. | | `data` | `TakeHistoryBackupRequest` | Request body for taking a history backup. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. The cluster’s secondary storage is neither Elasticsearch nor OpenSearch and therefore cannot serve history backups, or a targeted physical tenant’s snapshot repository is absent from the store — configured under a name the store does not have, or not configured at all. Both are deployment faults the caller cannot correct by changing its request; narrow the request with physicalTenantId to work with the tenants whose repository is usable. Unlike the per-physical-tenant backup endpoints, the cluster-admin surface performs no fine-grained authorization, so a missing BACKUP permission is never the reason. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster. - **errors.ConflictError** – If the response status code is 409. At least one targeted physical tenant already holds a backup with this id, or already has another backup running. The check that precedes the fan-out normally rejects the request before anything is scheduled; a tenant that takes the id in between rejects it during the fan-out instead, which can leave snapshots behind on the tenants already reached, so delete this backup id before retrying. - **errors.InternalServerErrorError** – If the response status code is 500. The backup could not be scheduled on every targeted physical tenant, because one of them hit an internal error. The check that precedes the fan-out rejects the request before anything is scheduled, but a failure during the fan-out itself can leave snapshots behind on the tenants already reached, so delete this backup id before retrying. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterTakeHistoryBackupResponse - **Return type:** ClusterTakeHistoryBackupResponse #### Examples **Take a history backup on every physical tenant:** ```python def take_history_backup_as_cluster_admin_example(backup_id: int) -> None: client = CamundaClient() # Requires the cluster-admin security chain. Triggers the backup on every # physical tenant; the backup id must be higher than any previously used id. # Use `physical_tenant_id` to target a single physical tenant instead. result = client.take_history_backup_as_cluster_admin( data=TakeHistoryBackupRequest( backup_id=backup_id, ) ) print(f"Scheduled history backup {result.backup_id}") for tenant in result.physical_tenants: print(f" physical tenant {tenant.physical_tenant_id}: scheduled") ``` ### take_runtime_backup() ```python async def take_runtime_backup(*, data=, **kwargs) ``` Take a runtime backup > Triggers a backup of runtime data on all partitions of the physical tenant. > > The backupId must be omitted if continuous backups and/or a backup or checkpoint > schedule is enabled for the physical tenant, as the id is generated automatically. > Otherwise, backupId is required. **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------------- | ----------------------------------------- | | `data` | `TakeRuntimeBackupRequest` \| `Unset` | Request body for taking a runtime backup. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.ConflictError** – If the response status code is 409. A backup with the same or a higher id already exists. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.GatewayTimeoutError** – If the response status code is 504. The request from gateway to broker timed out. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TakeRuntimeBackupResponse - **Return type:** TakeRuntimeBackupResponse #### Examples **Take a runtime backup:** ```python def take_runtime_backup_example(backup_id: int) -> None: client = CamundaClient() # `backup_id` is optional: leave it unset when continuous backups or a # backup/checkpoint schedule is enabled and an id is generated for you. # Here it is supplied explicitly, which is what a one-off manual backup does. result = client.take_runtime_backup( data=TakeRuntimeBackupRequest( backup_id=backup_id, ) ) print(f"Scheduled backup {result.backup_id}") ``` ### take_runtime_backup_as_cluster_admin() ```python async def take_runtime_backup_as_cluster_admin(*, data=, physical_tenant_id=, **kwargs) ``` Take a runtime backup on one or every physical tenant > Triggers a runtime backup on every physical tenant of the cluster, or on the one named by > physicalTenantId. A cluster-wide backup is a set of independent per-tenant backups, not an atomic > snapshot of the cluster: they are neither coordinated nor rolled back together, and each tenant > stores its own, so the same backupId can be used for all of them. > > Every targeted physical tenant must be in the same backup-id mode. backupId must be omitted when > every targeted tenant generates its own ids (because continuous backups and/or a backup or > checkpoint schedule is enabled for it), and is required when none of them does. A cluster whose > targeted tenants mix the two modes is rejected with 400 and has to be driven one tenant at a time > through POST /v2/backups/runtime. In generated-id mode each tenant generates its own id, so the > response reports an id per physical tenant rather than one for the cluster. > > The trigger is all-or-error, and never silent about a partial trigger: if any targeted tenant cannot > be triggered the response carries an error status, but its body still lists every targeted tenant — > which ones were triggered, under which backupId to monitor or delete them, and why the others > failed. Nothing is rolled back, so the backups that were triggered keep running and have to be > deleted explicitly. A request rejected before any tenant was triggered answers with a problem detail > instead, and nothing is running. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Use POST > /v2/backups/runtime to act as a single physical tenant. **Parameters:** | Parameter | Type | Description | | -------------------- | ------------------------------------- | ----------------------------------------- | | `physical_tenant_id` | `str` \| `Unset` | Example: default. | | `data` | `TakeRuntimeBackupRequest` \| `Unset` | Request body for taking a runtime backup. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The request names a backupId while at least one targeted physical tenant generates its own ids, or omits it while at least one does not, or the id is not a positive number. No tenant was triggered. A targeted tenant that rejects the request as invalid during the fan-out answers with the same status but the cluster body, listing the tenants that were triggered. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster, so no tenant was triggered. - **errors.ConflictError** – If the response status code is 409. At least one targeted physical tenant already holds a backup with this id or a higher one. Backups are triggered without a preceding check, so the tenants that accepted the id are listed in the body and keep running; delete them before retrying. - **errors.InternalServerErrorError** – If the response status code is 500. At least one targeted physical tenant could not be triggered, and the failures do not agree on a single status. The body lists the tenants that were triggered and keep running. - **errors.BadGatewayError** – If the response status code is 502. The connection to the broker was cut mid-flight on at least one targeted physical tenant, which may or may not have accepted the request. Those tenants are reported as UNKNOWN with the id to check them under, and the tenants that were triggered keep running. - **errors.ServiceUnavailableError** – If the response status code is 503. At least one targeted physical tenant could not be reached. The body lists the tenants that were triggered and keep running. - **errors.GatewayTimeoutError** – If the response status code is 504. The request from gateway to broker timed out on at least one targeted physical tenant, which may or may not have accepted it. Those tenants are reported as UNKNOWN with the id to check them under, and the tenants that were triggered keep running. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterTakeRuntimeBackupResponse - **Return type:** ClusterTakeRuntimeBackupResponse #### Examples **Take a runtime backup across physical tenants:** ```python def take_runtime_backup_as_cluster_admin_example(backup_id: int) -> None: client = CamundaClient() # Requires the cluster-admin security chain. Triggers the backup on every # physical tenant; the backup id must be higher than any previously used id. # Use `physical_tenant_id` to target a single physical tenant instead. result = client.take_runtime_backup_as_cluster_admin( data=TakeRuntimeBackupRequest( backup_id=backup_id, ) ) for tenant in result.physical_tenants: print(f" physical tenant {tenant.physical_tenant_id}: backup {tenant.backup_id}") ``` ### throw_job_error() ```python async def throw_job_error(job_key, *, data, **kwargs) ``` Throw error for job > Reports a business error (i.e. non-technical) that occurs while processing a job. **Parameters:** | Parameter | Type | Description | | --------- | ----------------- | ---------------------------------------------------------- | | `job_key` | `str` | System-generated key for a job. Example: 2251799813653498. | | `data` | `JobErrorRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The job with the given key was not found or is not activated. - **errors.ConflictError** – If the response status code is 409. The job with the given key is in the wrong state currently. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Throw a job error:** ```python def throw_job_error_example(job_key: JobKey) -> None: client = CamundaClient() client.throw_job_error( job_key=job_key, data=JobErrorRequest( error_code="VALIDATION_ERROR", error_message="Input validation failed", ), ) ``` ### trigger_cluster_rebalance() ```python async def trigger_cluster_rebalance(*, data=, dry_run=, **kwargs) ``` Trigger a cluster-wide leadership rebalance > Transfers leadership of every partition that is not led by its highest-priority replica towards that > replica, one partition at a time. Returns as soon as the rebalance has been accepted (poll GET > /cluster/v2/rebalance to monitor progress). > > Each rebalance can specify overrides for the configured rebalance settings (e.g. maximum replication > lag to allow). An absent request body means “use the configured settings”. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `dry_run` | `bool` \| `Unset` | | | `data` | `ClusterRebalanceRequest` \| `Unset` | The settings to run a given rebalance with. Every setting is optional; an absent request body is equivalent to a body with every field absent, and means “use the configured settings”. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ConflictError** – If the response status code is 409. A rebalance or cluster configuration change is already in progress, so there is no settled configuration to plan a rebalance against. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.BadGatewayError** – If the response status code is 502. The coordinator was reached, but its response was absent or unusable. - **errors.ServiceUnavailableError** – If the response status code is 503. No coordinator is currently available or reachable. - **errors.GatewayTimeoutError** – If the response status code is 504. The coordinator did not answer before the request timeout. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterBalanceResponse - **Return type:** ClusterBalanceResponse #### Examples **Trigger a cluster-wide leadership rebalance:** ```python def trigger_cluster_rebalance_example() -> None: client = CamundaClient() # Start a dry run first to inspect the plan without transferring any leadership. # Omit dry_run (or set it to False) to execute the rebalance for real. result: ClusterBalanceResponse = client.trigger_cluster_rebalance( data=ClusterRebalanceRequest( replication_lag_threshold=8388608, max_transfer_attempts=3, ), dry_run=True, ) print(f"Cluster balance state: {result.state}") if result.running_rebalance is not None: print(f"Running rebalance: {result.running_rebalance}") for partition in result.partitions: print(f" Partition {partition.partition_id}: {partition.state}") ``` ### unassign_client_from_group() ```python async def unassign_client_from_group(group_id, client_id, **kwargs) ``` Unassign a client from a group > Unassigns a client from a group. > > The client is removed as a group member, with associated authorizations, roles, and tenant > assignments no longer applied. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `client_id` | str) – | | The unique identifier of an OAuth client. Minted outside the Camunda REST API: in SaaS by Console, in Self-Managed with OIDC by the external identity provider (e.g. EntraID, Keycloak, Okta). In Self-Managed with Basic authentication, machine-to-machine applications are modelled as users instead — see the user identifier. > Example: my-application. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group with the given ID was not found, or the client is not assigned to this group. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a client from a group:** ```python def unassign_client_from_group_example(group_id: GroupId, client_id: ClientId) -> None: client = CamundaClient() client.unassign_client_from_group( group_id=group_id, client_id=client_id, ) ``` ### unassign_client_from_tenant() ```python async def unassign_client_from_tenant(tenant_id, client_id, **kwargs) ``` Unassign a client from a tenant > Unassigns the client from the specified tenant. > > The client can no longer access tenant data. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `client_id` | str) – | | The unique identifier of an OAuth client. Minted outside the Camunda REST API: in SaaS by Console, in Self-Managed with OIDC by the external identity provider (e.g. EntraID, Keycloak, Okta). In Self-Managed with Basic authentication, machine-to-machine applications are modelled as users instead — see the user identifier. > Example: my-application. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The tenant does not exist or the client was not assigned to it. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a client from a tenant:** ```python def unassign_client_from_tenant_example(tenant_id: TenantId, client_id: ClientId) -> None: client = CamundaClient() client.unassign_client_from_tenant( tenant_id=tenant_id, client_id=client_id, ) ``` ### unassign_group_from_tenant() ```python async def unassign_group_from_tenant(tenant_id, group_id, **kwargs) ``` Unassign a group from a tenant > Unassigns a group from a specified tenant. > > Members of the group (users, clients) will no longer have access to the tenant’s data - except they > are assigned directly to the tenant. **Parameters:** | Parameter | Type | Description | | ----------- | ----- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The tenant or group was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a group from a tenant:** ```python def unassign_group_from_tenant_example(tenant_id: TenantId, group_id: GroupId) -> None: client = CamundaClient() client.unassign_group_from_tenant( tenant_id=tenant_id, group_id=group_id, ) ``` ### unassign_mapping_rule_from_group() ```python async def unassign_mapping_rule_from_group(group_id, mapping_rule_id, **kwargs) ``` Unassign a mapping rule from a group > Unassigns a mapping rule from a group. **Parameters:** | Parameter | Type | Description | | ----------------- | ----- | ------------------------------------------------------------------ | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `mapping_rule_id` | `str` | The unique identifier of a mapping rule. Example: my-mapping-rule. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group or mapping rule with the given ID was not found, or the mapping rule is not assigned to this group. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a mapping rule from a group:** ```python def unassign_mapping_rule_from_group_example(group_id: GroupId, mapping_rule_id: MappingRuleId) -> None: client = CamundaClient() client.unassign_mapping_rule_from_group( group_id=group_id, mapping_rule_id=mapping_rule_id, ) ``` ### unassign_mapping_rule_from_tenant() ```python async def unassign_mapping_rule_from_tenant(tenant_id, mapping_rule_id, **kwargs) ``` Unassign a mapping rule from a tenant > Unassigns a single mapping rule from a specified tenant without deleting the rule. **Parameters:** | Parameter | Type | Description | | ----------------- | ----- | ------------------------------------------------------------------ | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `mapping_rule_id` | `str` | The unique identifier of a mapping rule. Example: my-mapping-rule. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The tenant or mapping rule was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a mapping rule from a tenant:** ```python def unassign_mapping_rule_from_tenant_example(tenant_id: TenantId, mapping_rule_id: MappingRuleId) -> None: client = CamundaClient() client.unassign_mapping_rule_from_tenant( tenant_id=tenant_id, mapping_rule_id=mapping_rule_id, ) ``` ### unassign_role_from_client() ```python async def unassign_role_from_client(role_id, client_id, **kwargs) ``` Unassign a role from a client > Unassigns the specified role from the client. The client will no longer inherit the authorizations > associated with this role. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `client_id` | str) – | | The unique identifier of an OAuth client. Minted outside the Camunda REST API: in SaaS by Console, in Self-Managed with OIDC by the external identity provider (e.g. EntraID, Keycloak, Okta). In Self-Managed with Basic authentication, machine-to-machine applications are modelled as users instead — see the user identifier. > Example: my-application. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role or client with the given ID or username was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a role from a client:** ```python def unassign_role_from_client_example(role_id: RoleId, client_id: ClientId) -> None: client = CamundaClient() client.unassign_role_from_client( role_id=role_id, client_id=client_id, ) ``` ### unassign_role_from_group() ```python async def unassign_role_from_group(role_id, group_id, **kwargs) ``` Unassign a role from a group > Unassigns the specified role from the group. All group members (user or client) no longer inherit > the authorizations associated with this role. **Parameters:** | Parameter | Type | Description | | ---------- | ----- | ------------------------------------------------------- | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role or group with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a role from a group:** ```python def unassign_role_from_group_example(role_id: RoleId, group_id: GroupId) -> None: client = CamundaClient() client.unassign_role_from_group( role_id=role_id, group_id=group_id, ) ``` ### unassign_role_from_mapping_rule() ```python async def unassign_role_from_mapping_rule(role_id, mapping_rule_id, **kwargs) ``` Unassign a role from a mapping rule > Unassigns a role from a mapping rule. **Parameters:** | Parameter | Type | Description | | ----------------- | ----- | ------------------------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `mapping_rule_id` | `str` | The unique identifier of a mapping rule. Example: my-mapping-rule. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role or mapping rule with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a role from a mapping rule:** ```python def unassign_role_from_mapping_rule_example(role_id: RoleId, mapping_rule_id: MappingRuleId) -> None: client = CamundaClient() client.unassign_role_from_mapping_rule( role_id=role_id, mapping_rule_id=mapping_rule_id, ) ``` ### unassign_role_from_tenant() ```python async def unassign_role_from_tenant(tenant_id, role_id, **kwargs) ``` Unassign a role from a tenant > Unassigns a role from a specified tenant. > > Users, Clients or Groups, that have the role assigned, will no longer have access to the > tenant’s data - unless they are assigned directly to the tenant. **Parameters:** | Parameter | Type | Description | | ----------- | ----- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The tenant or role was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a role from a tenant:** ```python def unassign_role_from_tenant_example(tenant_id: TenantId, role_id: RoleId) -> None: client = CamundaClient() client.unassign_role_from_tenant( tenant_id=tenant_id, role_id=role_id, ) ``` ### unassign_role_from_user() ```python async def unassign_role_from_user(role_id, username, **kwargs) ``` Unassign a role from a user > Unassigns a role from a user. The user will no longer inherit the authorizations associated with > this role. **Parameters:** | Parameter | Type | Description | | ---------- | ----- | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `username` | `str` | The unique name of a user. Example: swillis. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role or user with the given ID or username was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a role from a user:** ```python def unassign_role_from_user_example(role_id: RoleId, username: Username) -> None: client = CamundaClient() client.unassign_role_from_user( role_id=role_id, username=username, ) ``` ### unassign_user_from_group() ```python async def unassign_user_from_group(group_id, username, **kwargs) ``` Unassign a user from a group > Unassigns a user from a group. > > The user is removed as a group member, with associated authorizations, roles, and tenant assignments > no longer applied. **Parameters:** | Parameter | Type | Description | | ---------- | ----- | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `username` | `str` | The unique name of a user. Example: swillis. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group or user with the given ID was not found, or the user is not assigned to this group. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a user from a group:** ```python def unassign_user_from_group_example(group_id: GroupId, username: Username) -> None: client = CamundaClient() client.unassign_user_from_group( group_id=group_id, username=username, ) ``` ### unassign_user_from_tenant() ```python async def unassign_user_from_tenant(tenant_id, username, **kwargs) ``` Unassign a user from a tenant > Unassigns the user from the specified tenant. > > The user can no longer access tenant data. **Parameters:** | Parameter | Type | Description | | ----------- | ----- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `username` | `str` | The unique name of a user. Example: swillis. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The tenant or user was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a user from a tenant:** ```python def unassign_user_from_tenant_example(tenant_id: TenantId, username: Username) -> None: client = CamundaClient() client.unassign_user_from_tenant( tenant_id=tenant_id, username=username, ) ``` ### unassign_user_task() ```python async def unassign_user_task(user_task_key, **kwargs) ``` Unassign user task > Removes the assignee of a task with the given key. Unassignment waits for blocking task listeners on > this lifecycle transition. If listener processing is delayed beyond the request timeout, this > endpoint can return 504. Other gateway timeout causes are also possible. Retry with backoff and > inspect listener worker availability and logs when this repeats. **Parameters:** | Parameter | Type | Description | | --------------- | ----- | ------------------------------------- | | `user_task_key` | `str` | System-generated key for a user task. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The user task with the given key was not found. - **errors.ConflictError** – If the response status code is 409. The user task with the given key is in the wrong state currently. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.GatewayTimeoutError** – If the response status code is 504. The request timed out between the gateway and the broker. For these endpoints, this often happens when user task listeners are configured and the corresponding listener job is not completed within the request timeout. Common causes include no available job workers for the listener type, busy or crashed job workers, or delayed job completion. As with any gateway timeout, general timeout causes (for example transient network issues) can also result in a 504 response. Troubleshooting: - verify that job workers for the listener type are running and healthy - check worker logs for crashes, retries, and completion failures - check network connectivity between workers, gateway, and broker - retry with backoff after transient failures - fail without retries if a problem persists - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a user task:** ```python def unassign_user_task_example(user_task_key: UserTaskKey) -> None: client = CamundaClient() client.unassign_user_task(user_task_key=user_task_key) ``` ### update_agent_instance() ```python async def update_agent_instance(agent_instance_key, *, data, **kwargs) ``` Update agent instance > Updates the status of an agent instance and appends a batch of history items > to its conversation history. Each history item created for this request is > echoed back in the response. **Parameters:** | Parameter | Type | Description | | -------------------- | ---------------------------- | ---------------------------------------------------------------------- | | `agent_instance_key` | `str` | System-generated key for an agent instance. Example: 4503599627370496. | | `data` | `AgentInstanceUpdateRequest` | Request to update the mutable state of an agent instance. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The agent instance with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AgentInstanceUpdateResult - **Return type:** AgentInstanceUpdateResult #### Examples **Update an agent instance:** ```python def update_agent_instance_example( agent_instance_key: AgentInstanceKey, element_instance_key: ElementInstanceKey, job_key: JobKey, # Engine-minted: take the token from the job activation response, never build one. job_lease_token: JobLeaseToken, ) -> None: client = CamundaClient() # Appending conversation history is part of an update; there is no separate # history-item endpoint. result = client.update_agent_instance( agent_instance_key=agent_instance_key, data=AgentInstanceUpdateRequest( element_instance_key=element_instance_key, job_key=job_key, job_lease_token=job_lease_token, status=AgentInstanceUpdateRequestStatus.THINKING, history=[ AgentInstanceHistoryItem( history_item_id=HistoryItemId("assistant-1"), loop_iteration=1, role=AgentInstanceHistoryItemRole.ASSISTANT, content=[ TextContent(content_type="TEXT", text="How can I help you today?"), ], produced_at=datetime.datetime.now(datetime.timezone.utc), ), ], ), ) for item in result.created_history: print(f"Appended history item {item.history_item_id}: {item.history_item_key}") ``` ### update_authorization() ```python async def update_authorization(authorization_key, *, data, **kwargs) ``` Update authorization > Update the authorization with the given key. **Parameters:** | Parameter | Type | Description | | ------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------- | | `authorization_key` | `str` | System-generated key for an authorization. Example: 2251799813684332. | | `data` | `AuthorizationIdBasedRequest` \| `AuthorizationPropertyBasedRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The authorization with the authorizationKey was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Update an authorization:** ```python def update_authorization_example(authorization_key: AuthorizationKey) -> None: client = CamundaClient() client.update_authorization( authorization_key=authorization_key, data=AuthorizationIdBasedRequest( resource_type=AuthorizationIdBasedRequestResourceType.PROCESS_DEFINITION, permission_types=[ AuthorizationIdBasedRequestPermissionTypesItem.READ, AuthorizationIdBasedRequestPermissionTypesItem.UPDATE, AuthorizationIdBasedRequestPermissionTypesItem.DELETE, ], resource_id="my-process", owner_type=OwnerTypeEnum.USER, owner_id="user@example.com", ), ) ``` ### update_global_cluster_variable() ```python async def update_global_cluster_variable(name, *, data, **kwargs) ``` Update a global-scoped cluster variable > Updates the value of an existing global cluster variable. > > The variable must exist, otherwise a 404 error is returned. **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------- | | `name` | `str` | The name of a cluster variable. Unique within its scope (global or tenant- specific). Example: feature-flag-checkout. | | `data` | `UpdateClusterVariableRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Cluster variable not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterVariableResult - **Return type:** ClusterVariableResult #### Examples **Update a global cluster variable:** ```python def update_global_cluster_variable_example(name: ClusterVariableName) -> None: client = CamundaClient() result = client.update_global_cluster_variable( name=name, data=UpdateClusterVariableRequest( value={"key": "updated-value"}, ), ) print(f"Updated variable: {result.name}") ``` ### update_global_task_listener() ```python async def update_global_task_listener(id, *, data, **kwargs) ``` Update global user task listener > Updates a global user task listener. **Parameters:** | Parameter | Type | Description | | --------- | --------------------------------- | ---------------------------------------------------------------------- | | `id` | `str` | The user-defined id for the global listener Example: GlobalListener_1. | | `data` | `UpdateGlobalTaskListenerRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The global user task listener was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GlobalTaskListenerResult - **Return type:** GlobalTaskListenerResult #### Examples **Update a global task listener:** ```python def update_global_task_listener_example(listener_id: GlobalListenerId) -> None: client = CamundaClient() result = client.update_global_task_listener( id=listener_id, data=UpdateGlobalTaskListenerRequest( event_types=[GlobalTaskListenerEventTypeEnum.COMPLETING], type_="updated-task-listener", ), ) print(f"Updated listener: {result.id}") ``` ### update_group() ```python async def update_group(group_id, *, data, **kwargs) ``` Update group > Update a group with the given ID. **Parameters:** | Parameter | Type | Description | | ---------- | -------------------- | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `data` | `GroupUpdateRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The group with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GroupUpdateResult - **Return type:** GroupUpdateResult #### Examples **Update a group:** ```python def update_group_example(group_id: GroupId) -> None: client = CamundaClient() client.update_group( group_id=group_id, data=GroupUpdateRequest(name="engineering-team"), ) ``` ### update_job() ```python async def update_job(job_key, *, data, **kwargs) ``` Update job > Update a job with the given key. **Parameters:** | Parameter | Type | Description | | --------- | ------------------ | ---------------------------------------------------------- | | `job_key` | `str` | System-generated key for a job. Example: 2251799813653498. | | `data` | `JobUpdateRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The job with the jobKey is not found. - **errors.ConflictError** – If the response status code is 409. The job with the given key is in the wrong state currently. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Update a job:** ```python def update_job_example(job_key: JobKey) -> None: client = CamundaClient() client.update_job( job_key=job_key, data=JobUpdateRequest( changeset=JobChangeset( retries=3, ), ), ) ``` ### update_jobs_batch_operation() ```python async def update_jobs_batch_operation(*, data, **kwargs) ``` Update jobs (batch) > Creates a batch operation to update jobs matching the given filter. At least one changeset field > must be non-null. This is done asynchronously; the progress can be tracked using the > batchOperationKey from the response and the batch operation status endpoint (/batch- > operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `data` | `JobBatchUpdateRequest` | The filter and changeset for a batch job update operation. The filter defines which jobs are updated; the changeset defines what to update. At least one changeset field must be non-null. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The job batch update operation failed. More details are provided in the response body. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationCreatedResult - **Return type:** BatchOperationCreatedResult #### Examples **Update jobs in batch:** ```python def update_jobs_batch_operation_example() -> None: client = CamundaClient() result = client.update_jobs_batch_operation( data=JobBatchUpdateRequest( filter_=JobBatchUpdateRequestFilter( type_="my-job-type", ), changeset=JobBatchUpdateRequestChangeset( retries=3, ), ), ) print(f"Batch operation key: {result.batch_operation_key}") ``` ### update_mapping_rule() ```python async def update_mapping_rule(mapping_rule_id, *, data=, **kwargs) ``` Update mapping rule > Update a mapping rule. **Parameters:** | Parameter | Type | Description | | ----------------- | ------------------------------------- | ------------------------------------------------------------------ | | `mapping_rule_id` | `str` | The unique identifier of a mapping rule. Example: my-mapping-rule. | | `data` | `MappingRuleUpdateRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. The request to update a mapping rule was denied. More details are provided in the response body. - **errors.NotFoundError** – If the response status code is 404. The request to update a mapping rule was denied. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** MappingRuleUpdateResult - **Return type:** MappingRuleUpdateResult #### Examples **Update a mapping rule:** ```python def update_mapping_rule_example(mapping_rule_id: MappingRuleId) -> None: client = CamundaClient() client.update_mapping_rule( mapping_rule_id=mapping_rule_id, data=MappingRuleUpdateRequest( claim_name="groups", claim_value="senior-engineering", name="Senior Engineering Mapping", ), ) ``` ### update_role() ```python async def update_role(role_id, *, data, **kwargs) ``` Update role > Update a role with the given ID. **Parameters:** | Parameter | Type | Description | | --------- | ------------------- | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `data` | `RoleUpdateRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The role with the ID is not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RoleUpdateResult - **Return type:** RoleUpdateResult #### Examples **Update a role:** ```python def update_role_example(role_id: RoleId) -> None: client = CamundaClient() client.update_role( role_id=role_id, data=RoleUpdateRequest(name="senior-developer"), ) ``` ### update_tenant() ```python async def update_tenant(tenant_id, *, data, **kwargs) ``` Update tenant > Updates an existing tenant. **Parameters:** | Parameter | Type | Description | | ----------- | --------------------- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `data` | `TenantUpdateRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The tenant was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TenantUpdateResult - **Return type:** TenantUpdateResult #### Examples **Update a tenant:** ```python def update_tenant_example(tenant_id: TenantId) -> None: client = CamundaClient() client.update_tenant( tenant_id=tenant_id, data=TenantUpdateRequest(name="Acme Corp International"), ) ``` ### update_tenant_cluster_variable() ```python async def update_tenant_cluster_variable(tenant_id, name, *, data, **kwargs) ``` Update a tenant-scoped cluster variable > Updates the value of an existing tenant-scoped cluster variable. > > The variable must exist, otherwise a 404 error is returned. **Parameters:** | Parameter | Type | Description | | ----------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `name` | `str` | The name of a cluster variable. Unique within its scope (global or tenant- specific). Example: feature-flag-checkout. | | `data` | `UpdateClusterVariableRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Cluster variable not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterVariableResult - **Return type:** ClusterVariableResult #### Examples **Update a tenant cluster variable:** ```python def update_tenant_cluster_variable_example(tenant_id: TenantId, name: ClusterVariableName) -> None: client = CamundaClient() result = client.update_tenant_cluster_variable( tenant_id=tenant_id, name=name, data=UpdateClusterVariableRequest( value={"key": "updated-tenant-value"}, ), ) print(f"Updated variable: {result.name}") ``` ### update_user() ```python async def update_user(username, *, data, **kwargs) ``` Update user > Updates a user. **Parameters:** | Parameter | Type | Description | | ---------- | ------------------- | -------------------------------------------- | | `username` | `str` | The unique name of a user. Example: swillis. | | `data` | `UserUpdateRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The user was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** UserUpdateResult - **Return type:** UserUpdateResult #### Examples **Update a user:** ```python def update_user_example(username: Username) -> None: client = CamundaClient() client.update_user( username=username, data=UserUpdateRequest( name="Jane Smith", email="jsmith@example.com", ), ) ``` ### update_user_task() ```python async def update_user_task(user_task_key, *, data=, **kwargs) ``` Update user task > Update a user task with the given key. Updates wait for blocking task listeners on this lifecycle > transition. If listener processing is delayed beyond the request timeout, this endpoint can return 504. Other gateway timeout causes are also possible. Retry with backoff and inspect listener worker availability and logs when this repeats. **Parameters:** | Parameter | Type | Description | | --------------- | ---------------------------------- | ------------------------------------- | | `user_task_key` | `str` | System-generated key for a user task. | | `data` | `UserTaskUpdateRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The user task with the given key was not found. - **errors.ConflictError** – If the response status code is 409. The user task with the given key is in the wrong state currently. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.GatewayTimeoutError** – If the response status code is 504. The request timed out between the gateway and the broker. For these endpoints, this often happens when user task listeners are configured and the corresponding listener job is not completed within the request timeout. Common causes include no available job workers for the listener type, busy or crashed job workers, or delayed job completion. As with any gateway timeout, general timeout causes (for example transient network issues) can also result in a 504 response. Troubleshooting: - verify that job workers for the listener type are running and healthy - check worker logs for crashes, retries, and completion failures - check network connectivity between workers, gateway, and broker - retry with backoff after transient failures - fail without retries if a problem persists - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Update a user task:** ```python def update_user_task_example(user_task_key: UserTaskKey) -> None: client = CamundaClient() client.update_user_task( user_task_key=user_task_key, data=UserTaskUpdateRequest( changeset=Changeset( due_date=datetime.datetime(2025, 12, 31, 23, 59, 59), ), ), ) ``` --- ## CamundaClient(3) ## CamundaClient ```python class CamundaClient(configuration=None, auth_provider=None, logger=None, clock=None, random=None, **kwargs) ``` Bases: `object` **Parameters:** | Parameter | Type | Description | | --------------- | ---------------------------------------------------------------------------------------------------------------------- | ----------- | | `configuration` | [CamundaSdkConfiguration](runtime.md#camunda_orchestration_sdk.runtime.configuration_resolver.CamundaSdkConfiguration) | | | `auth_provider` | [AuthProvider](runtime.md#camunda_orchestration_sdk.runtime.auth.AuthProvider) | | | `logger` | [CamundaLogger](runtime.md#camunda_orchestration_sdk.runtime.logging.CamundaLogger) \| `None` | | | `clock` | `Clock` \| `None` | | | `random` | `RandomSource` \| `None` | | | `kwargs` | `Any` | | ### activate_ad_hoc_sub_process_activities() ```python def activate_ad_hoc_sub_process_activities(ad_hoc_sub_process_instance_key, *, data, **kwargs) ``` Activate activities within an ad-hoc sub-process > Activates selected activities within an ad-hoc sub-process identified by element ID. > > The provided element IDs must exist within the ad-hoc sub-process instance identified by the > provided adHocSubProcessInstanceKey. **Parameters:** | Parameter | Type | Description | | --------------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------- | | `ad_hoc_sub_process_instance_key` | `str` | System-generated key for a element instance. Example: 2251799813686789. | | `data` | `AdHocSubProcessActivateActivitiesInstruction` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The ad-hoc sub-process instance is not found or the provided key does not identify an ad-hoc sub-process. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Activate ad-hoc sub-process activities:** ```python def activate_ad_hoc_sub_process_activities_example(element_id: ElementId) -> None: client = CamundaClient() client.activate_ad_hoc_sub_process_activities( ad_hoc_sub_process_instance_key="123456", data=AdHocSubProcessActivateActivitiesInstruction( elements=[ AdHocSubProcessActivateActivityReference(element_id=element_id), AdHocSubProcessActivateActivityReference(element_id=element_id), ], ), ) ``` ### activate_jobs() ```python def activate_jobs(*, data, **kwargs) ``` Activate jobs > Iterate through all known partitions and activate jobs up to the requested maximum. **Parameters:** | Parameter | Type | Description | | --------- | ---------------------- | ----------- | | `data` | `JobActivationRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** JobActivationResult - **Return type:** JobActivationResult #### Examples **Activate and process jobs:** ```python async def activate_jobs_example() -> None: async with CamundaAsyncClient() as client: result = await client.activate_jobs( data=JobActivationRequest( type_="payment-processing", timeout=30000, max_jobs_to_activate=5, ) ) for job in result.jobs: print(f"Job {job.job_key}: {job.type_}") ``` ### assign_client_to_group() ```python def assign_client_to_group(group_id, client_id, **kwargs) ``` Assign a client to a group > Assigns a client to a group, making it a member of the group. > > Members of the group inherit the group authorizations, roles, and tenant assignments. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `client_id` | str) – | | The unique identifier of an OAuth client. Minted outside the Camunda REST API: in SaaS by Console, in Self-Managed with OIDC by the external identity provider (e.g. EntraID, Keycloak, Okta). In Self-Managed with Basic authentication, machine-to-machine applications are modelled as users instead — see the user identifier. > Example: my-application. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group with the given ID was not found. - **errors.ConflictError** – If the response status code is 409. The client with the given ID is already assigned to the group. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a client to a group:** ```python def assign_client_to_group_example(group_id: GroupId, client_id: ClientId) -> None: client = CamundaClient() client.assign_client_to_group( group_id=group_id, client_id=client_id, ) ``` ### assign_client_to_tenant() ```python def assign_client_to_tenant(tenant_id, client_id, **kwargs) ``` Assign a client to a tenant > Assign the client to the specified tenant. > > The client can then access tenant data and perform authorized actions. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `client_id` | str) – | | The unique identifier of an OAuth client. Minted outside the Camunda REST API: in SaaS by Console, in Self-Managed with OIDC by the external identity provider (e.g. EntraID, Keycloak, Okta). In Self-Managed with Basic authentication, machine-to-machine applications are modelled as users instead — see the user identifier. > Example: my-application. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The tenant was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a client to a tenant:** ```python def assign_client_to_tenant_example(tenant_id: TenantId, client_id: ClientId) -> None: client = CamundaClient() client.assign_client_to_tenant( tenant_id=tenant_id, client_id=client_id, ) ``` ### assign_group_to_tenant() ```python def assign_group_to_tenant(tenant_id, group_id, **kwargs) ``` Assign a group to a tenant > Assigns a group to a specified tenant. > > Group members (users, clients) can then access tenant data and perform authorized actions. **Parameters:** | Parameter | Type | Description | | ----------- | ----- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The tenant or group was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a group to a tenant:** ```python def assign_group_to_tenant_example(tenant_id: TenantId, group_id: GroupId) -> None: client = CamundaClient() client.assign_group_to_tenant( tenant_id=tenant_id, group_id=group_id, ) ``` ### assign_mapping_rule_to_group() ```python def assign_mapping_rule_to_group(group_id, mapping_rule_id, **kwargs) ``` Assign a mapping rule to a group > Assigns a mapping rule to a group. **Parameters:** | Parameter | Type | Description | | ----------------- | ----- | ------------------------------------------------------------------ | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `mapping_rule_id` | `str` | The unique identifier of a mapping rule. Example: my-mapping-rule. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group or mapping rule with the given ID was not found. - **errors.ConflictError** – If the response status code is 409. The mapping rule with the given ID is already assigned to the group. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a mapping rule to a group:** ```python def assign_mapping_rule_to_group_example(group_id: GroupId, mapping_rule_id: MappingRuleId) -> None: client = CamundaClient() client.assign_mapping_rule_to_group( group_id=group_id, mapping_rule_id=mapping_rule_id, ) ``` ### assign_mapping_rule_to_tenant() ```python def assign_mapping_rule_to_tenant(tenant_id, mapping_rule_id, **kwargs) ``` Assign a mapping rule to a tenant > Assign a single mapping rule to a specified tenant. **Parameters:** | Parameter | Type | Description | | ----------------- | ----- | ------------------------------------------------------------------ | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `mapping_rule_id` | `str` | The unique identifier of a mapping rule. Example: my-mapping-rule. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The tenant or mapping rule was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a mapping rule to a tenant:** ```python def assign_mapping_rule_to_tenant_example(tenant_id: TenantId, mapping_rule_id: MappingRuleId) -> None: client = CamundaClient() client.assign_mapping_rule_to_tenant( tenant_id=tenant_id, mapping_rule_id=mapping_rule_id, ) ``` ### assign_process_instance_business_id() ```python def assign_process_instance_business_id(process_instance_key, *, data, **kwargs) ``` Assign business id to process instance > Assigns a business id to an already-running process instance that currently has none. > > The assignment is single and irreversible: only artifacts created after the assignment > (for example future jobs, user tasks, decision instances, and message subscriptions) carry > the business id, while existing artifacts are not retroactively enriched. Re-sending the > same business id succeeds as a no-op. This endpoint is only useful while business id > uniqueness enforcement is disabled; when it is enabled, the request is rejected with a 409 > response. **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `data` | `ProcessInstanceBusinessIdAssignmentInstruction` | The instruction describing the business id to assign to a running process instance. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The process instance is not found. - **errors.ConflictError** – If the response status code is 409. The business id assignment failed because the process instance is not eligible, for example it already has a different business id, it is a call-activity child, or business id uniqueness enforcement is enabled. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a business id to a process instance:** ```python def assign_process_instance_business_id_example(process_instance_key: ProcessInstanceKey) -> None: client = CamundaClient() client.assign_process_instance_business_id( process_instance_key=process_instance_key, data=ProcessInstanceBusinessIdAssignmentInstruction( business_id="order-12345", ), ) ``` ### assign_role_to_client() ```python def assign_role_to_client(role_id, client_id, **kwargs) ``` Assign a role to a client > Assigns the specified role to the client. The client will inherit the authorizations associated with > this role. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `client_id` | str) – | | The unique identifier of an OAuth client. Minted outside the Camunda REST API: in SaaS by Console, in Self-Managed with OIDC by the external identity provider (e.g. EntraID, Keycloak, Okta). In Self-Managed with Basic authentication, machine-to-machine applications are modelled as users instead — see the user identifier. > Example: my-application. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role with the given ID was not found. - **errors.ConflictError** – If the response status code is 409. The role was already assigned to the client with the given ID. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a role to a client:** ```python def assign_role_to_client_example(role_id: RoleId, client_id: ClientId) -> None: client = CamundaClient() client.assign_role_to_client( role_id=role_id, client_id=client_id, ) ``` ### assign_role_to_group() ```python def assign_role_to_group(role_id, group_id, **kwargs) ``` Assign a role to a group > Assigns the specified role to the group. Every member of the group (user or client) will inherit the > authorizations associated with this role. **Parameters:** | Parameter | Type | Description | | ---------- | ----- | ------------------------------------------------------- | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role or group with the given ID was not found. - **errors.ConflictError** – If the response status code is 409. The role is already assigned to the group with the given ID. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a role to a group:** ```python def assign_role_to_group_example(role_id: RoleId, group_id: GroupId) -> None: client = CamundaClient() client.assign_role_to_group( role_id=role_id, group_id=group_id, ) ``` ### assign_role_to_mapping_rule() ```python def assign_role_to_mapping_rule(role_id, mapping_rule_id, **kwargs) ``` Assign a role to a mapping rule > Assigns a role to a mapping rule. **Parameters:** | Parameter | Type | Description | | ----------------- | ----- | ------------------------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `mapping_rule_id` | `str` | The unique identifier of a mapping rule. Example: my-mapping-rule. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role or mapping rule with the given ID was not found. - **errors.ConflictError** – If the response status code is 409. The role is already assigned to the mapping rule with the given ID. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a role to a mapping rule:** ```python def assign_role_to_mapping_rule_example(role_id: RoleId, mapping_rule_id: MappingRuleId) -> None: client = CamundaClient() client.assign_role_to_mapping_rule( role_id=role_id, mapping_rule_id=mapping_rule_id, ) ``` ### assign_role_to_tenant() ```python def assign_role_to_tenant(tenant_id, role_id, **kwargs) ``` Assign a role to a tenant > Assigns a role to a specified tenant. > > Users, Clients or Groups, that have the role assigned, will get access to the tenant’s data and can > perform actions according to their authorizations. **Parameters:** | Parameter | Type | Description | | ----------- | ----- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The tenant or role was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a role to a tenant:** ```python def assign_role_to_tenant_example(tenant_id: TenantId, role_id: RoleId) -> None: client = CamundaClient() client.assign_role_to_tenant( tenant_id=tenant_id, role_id=role_id, ) ``` ### assign_role_to_user() ```python def assign_role_to_user(role_id, username, **kwargs) ``` Assign a role to a user > Assigns the specified role to the user. The user will inherit the authorizations associated with > this role. **Parameters:** | Parameter | Type | Description | | ---------- | ----- | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `username` | `str` | The unique name of a user. Example: swillis. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role or user with the given ID or username was not found. - **errors.ConflictError** – If the response status code is 409. The role is already assigned to the user with the given ID. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a role to a user:** ```python def assign_role_to_user_example(role_id: RoleId, username: Username) -> None: client = CamundaClient() client.assign_role_to_user( role_id=role_id, username=username, ) ``` ### assign_user_task() ```python def assign_user_task(user_task_key, *, data, **kwargs) ``` Assign user task > Assigns a user task with the given key to the given assignee. Assignment waits for blocking task > listeners on this lifecycle transition. If listener processing is delayed beyond the request > timeout, this endpoint can return 504. Other gateway timeout causes are also possible. Retry with > backoff and inspect listener worker availability and logs when this repeats. **Parameters:** | Parameter | Type | Description | | --------------- | --------------------------- | ------------------------------------- | | `user_task_key` | `str` | System-generated key for a user task. | | `data` | `UserTaskAssignmentRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The user task with the given key was not found. - **errors.ConflictError** – If the response status code is 409. The user task with the given key is in the wrong state currently. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.GatewayTimeoutError** – If the response status code is 504. The request timed out between the gateway and the broker. For these endpoints, this often happens when user task listeners are configured and the corresponding listener job is not completed within the request timeout. Common causes include no available job workers for the listener type, busy or crashed job workers, or delayed job completion. As with any gateway timeout, general timeout causes (for example transient network issues) can also result in a 504 response. Troubleshooting: - verify that job workers for the listener type are running and healthy - check worker logs for crashes, retries, and completion failures - check network connectivity between workers, gateway, and broker - retry with backoff after transient failures - fail without retries if a problem persists - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a user task:** ```python def assign_user_task_example(user_task_key: UserTaskKey) -> None: client = CamundaClient() client.assign_user_task( user_task_key=user_task_key, data=UserTaskAssignmentRequest( assignee="user@example.com", ), ) ``` ### assign_user_to_group() ```python def assign_user_to_group(group_id, username, **kwargs) ``` Assign a user to a group > Assigns a user to a group, making the user a member of the group. > > Group members inherit the group authorizations, roles, and tenant assignments. **Parameters:** | Parameter | Type | Description | | ---------- | ----- | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `username` | `str` | The unique name of a user. Example: swillis. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group or user with the given ID or username was not found. - **errors.ConflictError** – If the response status code is 409. The user with the given ID is already assigned to the group. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a user to a group:** ```python def assign_user_to_group_example(group_id: GroupId, username: Username) -> None: client = CamundaClient() client.assign_user_to_group( group_id=group_id, username=username, ) ``` ### assign_user_to_tenant() ```python def assign_user_to_tenant(tenant_id, username, **kwargs) ``` Assign a user to a tenant > Assign a single user to a specified tenant. The user can then access tenant data and perform > authorized actions. **Parameters:** | Parameter | Type | Description | | ----------- | ----- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `username` | `str` | The unique name of a user. Example: swillis. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The tenant or user was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Assign a user to a tenant:** ```python def assign_user_to_tenant_example(tenant_id: TenantId, username: Username) -> None: client = CamundaClient() client.assign_user_to_tenant( tenant_id=tenant_id, username=username, ) ``` ### auth_provider ```python auth_provider: [AuthProvider](runtime.md#camunda_orchestration_sdk.runtime.auth.AuthProvider) ``` ### broadcast_signal() ```python def broadcast_signal(*, data, **kwargs) ``` Broadcast signal > Broadcasts a signal. **Parameters:** | Parameter | Type | Description | | --------- | ------------------------ | ----------- | | `data` | `SignalBroadcastRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The signal is not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** SignalBroadcastResult - **Return type:** SignalBroadcastResult #### Examples **Broadcast a signal:** ```python def broadcast_signal_example() -> None: client = CamundaClient() result = client.broadcast_signal( data=SignalBroadcastRequest( signal_name="order-cancelled", ) ) print(f"Signal key: {result.signal_key}") ``` ### cancel_batch_operation() ```python def cancel_batch_operation(batch_operation_key, *, data=, **kwargs) ``` Cancel Batch operation > Cancels a running batch operation. > > This is done asynchronously, the progress can be tracked using the batch operation status endpoint > (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------------------- | ---------------- | ----------------------------------------------------------------------- | | `batch_operation_key` | `str` | System-generated key for an batch operation. Example: 2251799813684321. | | `data` | `Any` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The batch operation was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Cancel a batch operation:** ```python def cancel_batch_operation_example(batch_operation_key: BatchOperationKey) -> None: client = CamundaClient() client.cancel_batch_operation( batch_operation_key=batch_operation_key, ) ``` ### cancel_cluster_rebalance() ```python def cancel_cluster_rebalance(**kwargs) ``` Stop the running rebalance > Asks the running rebalance to stop once the transfer in flight has finished. Partitions already > transferred keep their new leaders, and those the rebalance had not yet reached keep their current > ones. > > Cancellation requests are idempotent and always accepted. The wasRunning response field can be > used to distinguish a cancellation that found a running rebalance from one that did not. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.BadGatewayError** – If the response status code is 502. The coordinator was reached, but its response was absent or unusable. - **errors.ServiceUnavailableError** – If the response status code is 503. No coordinator is currently available or reachable. - **errors.GatewayTimeoutError** – If the response status code is 504. The coordinator did not answer before the request timeout. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RebalanceCancellationResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** RebalanceCancellationResponse #### Examples **Cancel the running cluster rebalance:** ```python def cancel_cluster_rebalance_example() -> None: client = CamundaClient() # Asks the running rebalance to stop after the in-flight transfer finishes. # Partitions already rebalanced keep their new leaders. result = client.cancel_cluster_rebalance() if result.was_running: print("Rebalance was running and has been asked to stop.") else: print("No rebalance was running.") ``` ### cancel_process_instance() ```python def cancel_process_instance(process_instance_key, *, data=, **kwargs) ``` Cancel process instance > Cancels a running process instance. As a cancellation includes more than just the removal of the > process instance resource, the cancellation resource must be posted. Cancellation can wait on > listener-related processing; when that processing does not complete in time, this endpoint can > return 504. Other gateway timeout causes are also possible. Retry with backoff and inspect listener > worker availability and logs when this repeats. **Parameters:** | Parameter | Type | Description | | ---------------------- | --------------------------------------------------- | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `data` | `CancelProcessInstanceRequest` \| `None` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The process instance is not found. - **errors.ConflictError** – If the response status code is 409. The process instance cannot be canceled, for example because it is already being terminated or because it is a child process instance. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.GatewayTimeoutError** – If the response status code is 504. The request timed out between the gateway and the broker. For these endpoints, this often happens when user task listeners are configured and the corresponding listener job is not completed within the request timeout. Common causes include no available job workers for the listener type, busy or crashed job workers, or delayed job completion. As with any gateway timeout, general timeout causes (for example transient network issues) can also result in a 504 response. Troubleshooting: - verify that job workers for the listener type are running and healthy - check worker logs for crashes, retries, and completion failures - check network connectivity between workers, gateway, and broker - retry with backoff after transient failures - fail without retries if a problem persists - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Cancel a process instance:** ```python def cancel_process_instance_example(process_definition_id: ProcessDefinitionId) -> None: client = CamundaClient() # Create a process instance and get its key from the response created = client.create_process_instance( data=ProcessCreationById(process_definition_id=process_definition_id) ) # Cancel it using the key from the creation response client.cancel_process_instance( process_instance_key=created.process_instance_key, ) ``` ### cancel_process_instances_batch_operation() ```python def cancel_process_instances_batch_operation(*, data, **kwargs) ``` Cancel process instances (batch) > Cancels multiple active or suspended process instances. > > Only ACTIVE and SUSPENDED root instances can be cancelled. A state filter narrows the batch > to the given states. Requesting any state other than ACTIVE or SUSPENDED through the $eq or > $in operators is rejected. Other state operators ($neq, $exists, $like) are applied as > given, and the batch remains limited to ACTIVE and SUSPENDED instances. Without a state filter, > both ACTIVE and SUSPENDED instances are selected. Any given filter for parentProcessInstanceKey > is ignored and overridden during this batch operation. > This is done asynchronously, the progress can be tracked using the batchOperationKey from the > response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------- | -------------------------------------------------- | ------------------------------------------------------------------------------------ | | `data` | `ProcessInstanceCancellationBatchOperationRequest` | The process instance filter that defines which process instances should be canceled. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The process instance batch operation failed. More details are provided in the response body. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationCreatedResult - **Return type:** BatchOperationCreatedResult #### Examples **Cancel process instances in batch:** ```python def cancel_process_instances_batch_operation_example() -> None: client = CamundaClient() result = client.cancel_process_instances_batch_operation( data=ProcessInstanceCancellationBatchOperationRequest( filter_=ProcessInstanceCancellationBatchOperationRequestFilter(), ), ) print(f"Batch operation key: {result.batch_operation_key}") ``` ### change_cluster_mode() ```python def change_cluster_mode(*, mode, dry_run=, **kwargs) ``` Change cluster mode > Transitions the cluster between processing and recovery mode. This is a non-blocking operation: the > request is acknowledged once the change has been accepted, before the transition itself has > completed. Entering recovery mode deactivates all partitions so that only a restricted set of read- > only operations remains available; exiting recovery mode returns the cluster to normal processing. > Returns the planned cluster change so its progress can be monitored via the topology. **Parameters:** | Parameter | Type | Description | | --------- | ----------------- | --------------------------------------------- | | `mode` | `Mode` | The operating mode of a cluster’s partitions. | | `dry_run` | `bool` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterModeChangeResponse - **Return type:** ClusterModeChangeResponse #### Examples **Change cluster mode:** ```python def change_cluster_mode_example() -> None: client = CamundaClient() # Pass dry_run=True to validate the request and inspect the resulting plan # without applying it. Omit it (or set it to False) to trigger the transition. result = client.change_cluster_mode( mode=Mode.RECOVERING, dry_run=True, ) # Operations are grouped by physical tenant; a null tenant means the operation # is not scoped to one, such as a broker lifecycle operation. print(f"Cluster change {result.change_id}:") for group in result.planned_changes: print(f" {group.physical_tenant_id or 'cluster-wide'}:") for operation in group.operations: mode = getattr(operation, "mode", None) suffix = f" -> {mode}" if mode else "" print(f" {operation.operation}{suffix}") ``` ### change_cluster_mode_as_cluster_admin() ```python def change_cluster_mode_as_cluster_admin(*, mode, physical_tenant_id=, dry_run=, **kwargs) ``` Change the cluster mode of one or every physical tenant > Transitions physical tenants between processing and recovery mode. > > If the physicalTenantId parameter is not provided, all available physical tenants are transitioned > individually. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. **Parameters:** | Parameter | Type | Description | | -------------------- | ----------------- | --------------------------------------------- | | `mode` | `Mode` | The operating mode of a cluster’s partitions. | | `physical_tenant_id` | `str` \| `Unset` | Example: default. | | `dry_run` | `bool` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster. - **errors.ConflictError** – If the response status code is 409. The mode change conflicts with the cluster state, for example because another configuration change is in progress. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterModeChangeResponse - **Return type:** ClusterModeChangeResponse #### Examples **Change cluster mode as cluster admin:** ```python def change_cluster_mode_as_cluster_admin_example() -> None: client = CamundaClient() # The cluster-admin variant can target a single physical tenant. Omit # physical_tenant_id to apply the change to every physical tenant. result = client.change_cluster_mode_as_cluster_admin( mode=Mode.RECOVERING, physical_tenant_id="default", dry_run=True, ) print(f"Cluster change {result.change_id}:") for group in result.planned_changes: print(f" {group.physical_tenant_id or 'cluster-wide'}:") for operation in group.operations: mode = getattr(operation, "mode", None) suffix = f" -> {mode}" if mode else "" print(f" {operation.operation}{suffix}") ``` ### client ```python client: [Client](configuration.md#camunda_orchestration_sdk.Client) | [AuthenticatedClient](configuration.md#camunda_orchestration_sdk.AuthenticatedClient) ``` ### _property_ clock _: Clock_ the injected one when supplied, else the live clock. - **Type:** Clock backing SDK cadence ### close() ```python def close() ``` Close underlying HTTP clients. This closes both the API client’s httpx client and, when available, the auth provider’s token client. - **Return type:** None ### complete_job() ```python def complete_job(job_key, *, data=, **kwargs) ``` Complete job > Complete a job with the given payload, which allows completing the associated service task. **Parameters:** | Parameter | Type | Description | | --------- | --------------------------------- | ---------------------------------------------------------- | | `job_key` | `str` | System-generated key for a job. Example: 2251799813653498. | | `data` | `JobCompletionRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The job with the given key was not found. - **errors.ConflictError** – If the response status code is 409. The job with the given key is in the wrong state currently. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Complete a job:** ```python def complete_job_example(job_key: JobKey) -> None: client = CamundaClient() client.complete_job( job_key=job_key, data=JobCompletionRequest( variables=JobCompletionRequestVariables.from_dict( {"paymentId": "PAY-123", "status": "completed"} ) ), ) ``` ### complete_user_task() ```python def complete_user_task(user_task_key, *, data=, **kwargs) ``` Complete user task > Completes a user task with the given key. Completion waits for blocking task listeners on this > lifecycle transition. If listener processing is delayed beyond the request timeout, this endpoint > can return 504. Other gateway timeout causes are also possible. Retry with backoff and inspect > listener worker availability and logs when this repeats. **Parameters:** | Parameter | Type | Description | | --------------- | -------------------------------------- | ------------------------------------- | | `user_task_key` | `str` | System-generated key for a user task. | | `data` | `UserTaskCompletionRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The user task with the given key was not found. - **errors.ConflictError** – If the response status code is 409. The user task with the given key is in the wrong state currently. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.GatewayTimeoutError** – If the response status code is 504. The request timed out between the gateway and the broker. For these endpoints, this often happens when user task listeners are configured and the corresponding listener job is not completed within the request timeout. Common causes include no available job workers for the listener type, busy or crashed job workers, or delayed job completion. As with any gateway timeout, general timeout causes (for example transient network issues) can also result in a 504 response. Troubleshooting: - verify that job workers for the listener type are running and healthy - check worker logs for crashes, retries, and completion failures - check network connectivity between workers, gateway, and broker - retry with backoff after transient failures - fail without retries if a problem persists - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Complete a user task:** ```python def complete_user_task_example(user_task_key: UserTaskKey) -> None: client = CamundaClient() variables = UserTaskCompletionRequestVariables() variables["approved"] = True client.complete_user_task( user_task_key=user_task_key, data=UserTaskCompletionRequest( variables=variables, ), ) ``` ### configuration ```python configuration: [CamundaSdkConfiguration](runtime.md#camunda_orchestration_sdk.runtime.configuration_resolver.CamundaSdkConfiguration) ``` ### correlate_message() ```python def correlate_message(*, data, **kwargs) ``` Correlate message > Publishes a message and correlates it to a subscription. > > If correlation is successful it will return the first process instance key the message correlated > with. > The message is not buffered. > Use the publish message endpoint to send messages that can be buffered. **Parameters:** | Parameter | Type | Description | | --------- | --------------------------- | ----------- | | `data` | `MessageCorrelationRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** MessageCorrelationResult - **Return type:** MessageCorrelationResult #### Examples **Correlate a message:** ```python def correlate_message_example() -> None: client = CamundaClient() result = client.correlate_message( data=MessageCorrelationRequest( name="payment-received", correlation_key="order-12345", ) ) print(f"Message key: {result.message_key}") ``` ### create_admin_user() ```python def create_admin_user(*, data, **kwargs) ``` 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. **Parameters:** | Parameter | Type | Description | | --------- | ------------- | ----------- | | `data` | `UserRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.ConflictError** – If the response status code is 409. A user with this username already exists. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** UserCreateResult - **Return type:** UserCreateResult #### Examples **Create an admin user:** ```python def create_admin_user_example(username: Username) -> None: client = CamundaClient() result = client.create_admin_user( data=UserRequest( username=username, name="Admin User", email="admin@example.com", password="admin-password", ), ) print(f"Admin user: {result.username}") ``` ### create_agent_instance() ```python def create_agent_instance(*, data, **kwargs) ``` Create agent instance > Creates a new agent instance. The returned key identifies the instance and must > be used in subsequent update and query calls. **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------ | --------------------------------------- | | `data` | `AgentInstanceCreationRequest` | Request to create a new agent instance. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The elementInstanceKey does not correspond to an active element instance. More details are provided in the response body. - **errors.ConflictError** – If the response status code is 409. An agent instance already exists for the given element instance. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AgentInstanceCreationResult - **Return type:** AgentInstanceCreationResult #### Examples **Create an agent instance:** ```python def create_agent_instance_example( element_instance_key: ElementInstanceKey, job_key: JobKey, # Engine-minted: take the token from the job activation response, never build one. job_lease_token: JobLeaseToken, ) -> None: client = CamundaClient() result = client.create_agent_instance( data=AgentInstanceCreationRequest( element_instance_key=element_instance_key, job_key=job_key, job_lease_token=job_lease_token, history=[ # A CONFIGURATION item is mandatory on creation; it carries the model, # provider and system prompt in role-specific fields, not in content. AgentInstanceHistoryItem( history_item_id=HistoryItemId("configuration-1"), loop_iteration=1, role=AgentInstanceHistoryItemRole.CONFIGURATION, content=[], produced_at=datetime.datetime.now(datetime.timezone.utc), model="gpt-4o", provider="openai", system_prompt=[ TextContent(content_type="TEXT", text="You are a helpful assistant."), ], limits=AgentInstanceHistoryItemLimits( max_model_calls=10, max_tool_calls=20, max_tokens=100_000, ), ), ], ), ) print(f"Created agent instance: {result.agent_instance_key}") ``` ### create_authorization() ```python def create_authorization(*, data, **kwargs) ``` Create authorization > Create the authorization. **Parameters:** | Parameter | Type | Description | | --------- | -------------------------------------------------------------------- | ----------- | | `data` | `AuthorizationIdBasedRequest` \| `AuthorizationPropertyBasedRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The owner was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AuthorizationCreateResult - **Return type:** AuthorizationCreateResult #### Examples **Create an authorization:** ```python def create_authorization_example() -> None: client = CamundaClient() result = client.create_authorization( data=AuthorizationIdBasedRequest( resource_type=AuthorizationIdBasedRequestResourceType.PROCESS_DEFINITION, permission_types=[ AuthorizationIdBasedRequestPermissionTypesItem.READ, AuthorizationIdBasedRequestPermissionTypesItem.UPDATE, ], resource_id="my-process", owner_type=OwnerTypeEnum.USER, owner_id="user@example.com", ), ) print(f"Authorization key: {result.authorization_key}") ``` ### create_deployment() ```python def create_deployment(*, data, **kwargs) ``` Deploy resources > Deploys one or more resources, including BPMN processes, DMN decision models, forms, RPA resources, > and generic files. > A deployment can contain any file type. Files that are not interpreted as BPMN, DMN, form, or RPA > resources are stored as deployable generic resources in the engine. > This is an atomic call, i.e. either all resources are deployed or none of them are. **Parameters:** | Parameter | Type | Description | | --------- | ---------------------- | ----------- | | `data` | `CreateDeploymentData` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DeploymentResult - **Return type:** DeploymentResult #### Examples **From files:** ```python def deploy_resources_example() -> None: client = CamundaClient() result = client.deploy_resources_from_files( ["order-process.bpmn", "decision.dmn"] ) print(f"Deployment key: {result.deployment_key}") for process in result.processes: print( f" Process: {process.process_definition_id} v{process.process_definition_version}" ) for decision in result.decisions: print(f" Decision: {decision.decision_definition_id}") ``` **With tenant ID:** ```python def deploy_resources_with_tenant_example() -> None: client = CamundaClient() result = client.deploy_resources_from_files( ["order-process.bpmn"], tenant_id="my-tenant", ) print(f"Deployment key: {result.deployment_key}") print(f"Tenant: {result.tenant_id}") ``` ### create_document() ```python def create_document(*, data, store_id=, document_id=, **kwargs) ``` Upload document > Upload a document to the Camunda 8 cluster. > > Note that this is currently supported for document stores of type: AWS, Azure, GCP, in-memory (non- > production), local (non-production) **Parameters:** | Parameter | Type | Description | | ------------- | -------------------- | ------------------------------------------------ | | `store_id` | `str` \| `Unset` | | | `document_id` | `str` \| `Unset` | Document Id that uniquely identifies a document. | | `data` | `CreateDocumentData` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnsupportedMediaTypeError** – If the response status code is 415. The server cannot process the request because the media type (Content-Type) of the request payload is not supported by the server for the requested resource and method. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DocumentReference - **Return type:** DocumentReference #### Examples **Create a document:** ```python def create_document_example() -> None: client = CamundaClient() result = client.create_document( data=CreateDocumentData( file=File(payload=io.BytesIO(b"hello world"), file_name="example.txt"), ), ) print(f"Document ID: {result.document_id}") ``` ### create_document_link() ```python def create_document_link(document_id, *, data, store_id=, content_hash=, **kwargs) ``` Create document link > Create a link to a document in the Camunda 8 cluster. > > Note that this is currently supported for document stores of type: AWS, Azure, GCP **Parameters:** | Parameter | Type | Description | | -------------- | --------------------- | ------------------------------------------------ | | `document_id` | `str` | Document Id that uniquely identifies a document. | | `store_id` | `str` \| `Unset` | | | `content_hash` | `str` \| `Unset` | | | `data` | `DocumentLinkRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DocumentLink - **Return type:** DocumentLink #### Examples **Create a document link:** ```python def create_document_link_example(document_id: DocumentId) -> None: client = CamundaClient() result = client.create_document_link( document_id=document_id, data=DocumentLinkRequest(), ) print(f"Document link: {result.url}") ``` ### create_documents() ```python def create_documents(*, data, store_id=, **kwargs) ``` Upload multiple documents > Upload multiple documents to the Camunda 8 cluster. > > The caller must provide a file name for each document, which will be used in case of a multi-status > response > to identify which documents failed to upload. The file name can be provided in the Content- > Disposition header > of the file part or in the fileName field of the metadata. You can add a parallel array of > metadata objects. These > are matched with the files based on index, and must have the same length as the files array. > To pass homogenous metadata for all files, spread the metadata over the metadata array. > A filename value provided explicitly via the metadata array in the request overrides the Content- > Disposition header > of the file part. > > In case of a multi-status response, the response body will contain a list of > DocumentBatchProblemDetail objects, > each of which contains the file name of the document that failed to upload and the reason for the > failure. > The client can choose to retry the whole batch or individual documents based on the response. > > Note that this is currently supported for document stores of type: AWS, Azure, GCP, in-memory (non- > production), local (non-production) **Parameters:** | Parameter | Type | Description | | ---------- | --------------------- | ----------- | | `store_id` | `str` \| `Unset` | | | `data` | `CreateDocumentsData` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnsupportedMediaTypeError** – If the response status code is 415. The server cannot process the request because the media type (Content-Type) of the request payload is not supported by the server for the requested resource and method. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DocumentCreationBatchResponse - **Return type:** DocumentCreationBatchResponse #### Examples **Create documents:** ```python def create_documents_example() -> None: client = CamundaClient() result = client.create_documents( data=CreateDocumentsData( files=[ File(payload=io.BytesIO(b"file one"), file_name="one.txt"), File(payload=io.BytesIO(b"file two"), file_name="two.txt"), ], ), ) if not isinstance(result.created_documents, Unset): for doc in result.created_documents: print(f"Created document: {doc.document_id}") ``` ### create_element_instance_variables() ```python def create_element_instance_variables(element_instance_key, *, data, **kwargs) ``` Update element instance variables > Updates all the variables of a particular scope (for example, process instance, element instance) > with the given variable data. > Specify the element instance in the elementInstanceKey parameter. > Variable updates can be delayed by listener-related processing; if processing exceeds the > request timeout, this endpoint can return 504. Other gateway timeout causes are also > possible. Retry with backoff and inspect listener worker availability and logs when this > repeats. **Parameters:** | Parameter | Type | Description | | ---------------------- | -------------------- | ----------------------------------------------------------------------- | | `element_instance_key` | `str` | System-generated key for a element instance. Example: 2251799813686789. | | `data` | `SetVariableRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.GatewayTimeoutError** – If the response status code is 504. The request timed out between the gateway and the broker. For these endpoints, this often happens when user task listeners are configured and the corresponding listener job is not completed within the request timeout. Common causes include no available job workers for the listener type, busy or crashed job workers, or delayed job completion. As with any gateway timeout, general timeout causes (for example transient network issues) can also result in a 504 response. Troubleshooting: - verify that job workers for the listener type are running and healthy - check worker logs for crashes, retries, and completion failures - check network connectivity between workers, gateway, and broker - retry with backoff after transient failures - fail without retries if a problem persists - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Create element instance variables:** ```python def create_element_instance_variables_example( element_instance_key: ElementInstanceKey, ) -> None: client = CamundaClient() variables = SetVariableRequestVariables.from_dict({"myVar": "myValue"}) client.create_element_instance_variables( element_instance_key=element_instance_key, data=SetVariableRequest( variables=variables, ), ) ``` ### create_global_cluster_variable() ```python def create_global_cluster_variable(*, data, **kwargs) ``` Create a global-scoped cluster variable > Create a global-scoped cluster variable. **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------ | ----------- | | `data` | `CreateClusterVariableRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.ConflictError** – If the response status code is 409. A cluster variable with this name already exists. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterVariableResult - **Return type:** ClusterVariableResult #### Examples **Create a global cluster variable:** ```python def create_global_cluster_variable_example(name: ClusterVariableName) -> None: client = CamundaClient() result = client.create_global_cluster_variable( data=CreateClusterVariableRequest( name=name, value={"key": "my-value"}, ), ) print(f"Created variable: {result.name}") ``` ### create_global_task_listener() ```python def create_global_task_listener(*, data, **kwargs) ``` Create global user task listener > Create a new global user task listener. **Parameters:** | Parameter | Type | Description | | --------- | --------------------------------- | ----------- | | `data` | `CreateGlobalTaskListenerRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.ConflictError** – If the response status code is 409. A global listener with this id already exists. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GlobalTaskListenerResult - **Return type:** GlobalTaskListenerResult #### Examples **Create a global task listener:** ```python def create_global_task_listener_example() -> None: client = CamundaClient() result = client.create_global_task_listener( data=CreateGlobalTaskListenerRequest( id="audit-log-listener", event_types=[GlobalTaskListenerEventTypeEnum.COMPLETING], type_="my-task-listener", ), ) print(f"Task listener: {result.id}") ``` ### create_group() ```python def create_group(*, data=, **kwargs) ``` Create group > Create a new group. > > The supplied groupId is validated against \`\` ^[a-zA-Z0-9_~@.+-]+$ \`\` > (max 256 characters) by IdentifierValidator.validateId in the > runtime. This strict validation applies wherever the Groups API > is available: in OIDC deployments that set > camunda.security.authentication.oidc.groupsClaim the Groups > API (including this endpoint) is disabled entirely, so group > CRUD never sees externally-minted IdP IDs. The BYOG relaxation > only loosens validation when a group is referenced _as a member_ > of a role or tenant (assignRoleToGroup, > assignGroupToTenant); group CRUD itself always uses the strict > default-id regex. The constraint is not advertised on the > GroupId schema so that the same schema can be reused at > member-reference sites without falsely rejecting > externally-minted IdP group IDs there. **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------- | ----------- | | `data` | `GroupCreateRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.ConflictError** – If the response status code is 409. Group with this id already exists. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GroupCreateResult - **Return type:** GroupCreateResult #### Examples **Create a group:** ```python def create_group_example(group_id: GroupId) -> None: client = CamundaClient() result = client.create_group( data=GroupCreateRequest(group_id=group_id, name="Engineering"), ) print(f"Group: {result.group_id}") ``` ### create_mapping_rule() ```python def create_mapping_rule(*, data=, **kwargs) ``` Create mapping rule > Create a new mapping rule **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------------- | ----------- | | `data` | `MappingRuleCreateRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. The request to create a mapping rule was denied. More details are provided in the response body. - **errors.NotFoundError** – If the response status code is 404. The request to create a mapping rule was denied. - **errors.ConflictError** – If the response status code is 409. Mapping rule with this id already exists. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** MappingRuleCreateResult - **Return type:** MappingRuleCreateResult #### Examples **Create a mapping rule:** ```python def create_mapping_rule_example(mapping_rule_id: MappingRuleId) -> None: client = CamundaClient() result = client.create_mapping_rule( data=MappingRuleCreateRequest( mapping_rule_id=mapping_rule_id, claim_name="groups", claim_value="engineering", name="Engineering Group Mapping", ), ) print(f"Mapping rule: {result.mapping_rule_id}") ``` ### create_process_instance() ```python def create_process_instance(*, data, **kwargs) ``` Create process instance > Creates and starts an instance of the specified process. > > The process definition to use to create the instance can be specified either using its unique key > (as returned by Deploy resources), or using the BPMN process id and a version. > If only the process definition id is given, the latest ACTIVE version is used. > If no ACTIVE version exists, the request is rejected as not found. > > Waits for the completion of the process instance before returning a result > when awaitCompletion is enabled. **Parameters:** | Parameter | Type | Description | | --------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | `data` | `ProcessCreationById` \| `ProcessCreationByKey` | Instructions for creating a process instance. The process definition can be specified either by id or by key. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ConflictError** – If the response status code is 409. The process instance creation was rejected due to a business ID uniqueness conflict. This can happen only when Business ID Uniqueness Control is enabled and an active root process instance with the provided business ID already exists for the same process definition and tenant. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.GatewayTimeoutError** – If the response status code is 504. The process instance creation request timed out in the gateway. This can happen if the awaitCompletion request parameter is set to true and the created process instance did not complete within the defined request timeout. This often happens when the created instance is not fully automated or contains wait states. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** CreateProcessInstanceResult - **Return type:** CreateProcessInstanceResult #### Examples **By key:** ```python def create_process_instance_by_key_example() -> None: client = CamundaClient() # Deploy a process and obtain the typed key from the response deployment = client.deploy_resources_from_files(["order-process.bpmn"]) process_key = deployment.processes[0].process_definition_key # Use the typed key directly — no manual string lifting needed result = client.create_process_instance( data=ProcessCreationByKey( process_definition_key=process_key, ) ) print(f"Process instance key: {result.process_instance_key}") ``` **By stored key:** ```python def create_process_instance_by_key_from_storage_example() -> None: client = CamundaClient() # When restoring a key from a database or message queue, # wrap the raw string with the semantic type constructor: stored_key = "2251799813685249" # e.g. from a DB row result = client.create_process_instance( data=ProcessCreationByKey( process_definition_key=ProcessDefinitionKey(stored_key), ) ) print(f"Process instance key: {result.process_instance_key}") ``` **By ID:** ```python def create_process_instance_by_id_example(process_definition_id: ProcessDefinitionId) -> None: client = CamundaClient() result = client.create_process_instance( data=ProcessCreationById( process_definition_id=process_definition_id, ) ) print(f"Process instance key: {result.process_instance_key}") ``` ### create_role() ```python def create_role(*, data=, **kwargs) ``` Create role > Create a new role. **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------ | ----------- | | `data` | `RoleCreateRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.ConflictError** – If the response status code is 409. Role with this id already exists. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RoleCreateResult - **Return type:** RoleCreateResult #### Examples **Create a role:** ```python def create_role_example(role_id: RoleId) -> None: client = CamundaClient() result = client.create_role( data=RoleCreateRequest(role_id=role_id, name="Developer"), ) print(f"Role: {result.role_id}") ``` ### create_tenant() ```python def create_tenant(*, data, **kwargs) ``` Create tenant > Creates a new tenant. **Parameters:** | Parameter | Type | Description | | --------- | --------------------- | ----------- | | `data` | `TenantCreateRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The resource was not found. - **errors.ConflictError** – If the response status code is 409. Tenant with this id already exists. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TenantCreateResult - **Return type:** TenantCreateResult #### Examples **Create a tenant:** ```python def create_tenant_example(tenant_id: TenantId) -> None: client = CamundaClient() result = client.create_tenant( data=TenantCreateRequest( tenant_id=tenant_id, name="Acme Corporation", ), ) print(f"Tenant: {result.tenant_id}") ``` ### create_tenant_cluster_variable() ```python def create_tenant_cluster_variable(tenant_id, *, data, **kwargs) ``` Create a tenant-scoped cluster variable > Create a new cluster variable for the given tenant. **Parameters:** | Parameter | Type | Description | | ----------- | ------------------------------ | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `data` | `CreateClusterVariableRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The tenant with the given ID was not found. - **errors.ConflictError** – If the response status code is 409. A cluster variable with this name already exists for the given tenant. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterVariableResult - **Return type:** ClusterVariableResult #### Examples **Create a tenant cluster variable:** ```python def create_tenant_cluster_variable_example(tenant_id: TenantId, name: ClusterVariableName) -> None: client = CamundaClient() result = client.create_tenant_cluster_variable( tenant_id=tenant_id, data=CreateClusterVariableRequest( name=name, value={"key": "tenant-value"}, ), ) print(f"Created variable: {result.name}") ``` ### create_user() ```python def create_user(*, data, **kwargs) ``` Create user > Create a new user. **Parameters:** | Parameter | Type | Description | | --------- | ------------- | ----------- | | `data` | `UserRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.ConflictError** – If the response status code is 409. A user with this username already exists. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** UserCreateResult - **Return type:** UserCreateResult #### Examples **Create a user:** ```python def create_user_example(username: Username) -> None: client = CamundaClient() result = client.create_user( data=UserRequest( username=username, name="Jane Doe", email="jdoe@example.com", password="secure-password", ), ) print(f"Created user: {result.username}") ``` ### delete_authorization() ```python def delete_authorization(authorization_key, **kwargs) ``` Delete authorization > Deletes the authorization with the given key. **Parameters:** | Parameter | Type | Description | | ------------------- | ----- | --------------------------------------------------------------------- | | `authorization_key` | `str` | System-generated key for an authorization. Example: 2251799813684332. | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The authorization with the authorizationKey was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete an authorization:** ```python def delete_authorization_example(authorization_key: AuthorizationKey) -> None: client = CamundaClient() client.delete_authorization( authorization_key=authorization_key, ) ``` ### delete_decision_instance() ```python def delete_decision_instance(decision_evaluation_key, *, data=, **kwargs) ``` Delete decision instance > Delete all associated decision evaluations based on provided key. **Parameters:** | Parameter | Type | Description | | ------------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------- | | `decision_evaluation_key` | `str` | System-generated key for a decision evaluation. Example: 2251792362345323. | | `data` | `DeleteDecisionInstanceRequest` \| `None` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The decision instance is not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a decision instance:** ```python def delete_decision_instance_example(decision_evaluation_key: DecisionEvaluationKey) -> None: client = CamundaClient() client.delete_decision_instance( decision_evaluation_key=decision_evaluation_key, ) ``` ### delete_decision_instances_batch_operation() ```python def delete_decision_instances_batch_operation(*, data, **kwargs) ``` Delete decision instances (batch) > Delete multiple decision instances. This will delete the historic data from secondary storage. > > This is done asynchronously, the progress can be tracked using the batchOperationKey from the > response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------- | ----------------------------------------------- | ------------------------------------------------------------------------------------- | | `data` | `DecisionInstanceDeletionBatchOperationRequest` | The decision instance filter that defines which decision instances should be deleted. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The decision instance batch operation failed. More details are provided in the response body. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationCreatedResult - **Return type:** BatchOperationCreatedResult #### Examples **Delete decision instances in batch:** ```python def delete_decision_instances_batch_operation_example() -> None: client = CamundaClient() result = client.delete_decision_instances_batch_operation( data=DecisionInstanceDeletionBatchOperationRequest( filter_=DecisionInstanceDeletionBatchOperationRequestFilter(), ), ) print(f"Batch operation key: {result.batch_operation_key}") ``` ### delete_document() ```python def delete_document(document_id, *, store_id=, **kwargs) ``` Delete document > Delete a document from the Camunda 8 cluster. > > Note that this is currently supported for document stores of type: AWS, Azure, GCP, in-memory (non- > production), local (non-production) **Parameters:** | Parameter | Type | Description | | ------------- | ---------------- | ------------------------------------------------ | | `document_id` | `str` | Document Id that uniquely identifies a document. | | `store_id` | `str` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.NotFoundError** – If the response status code is 404. The document with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a document:** ```python def delete_document_example(document_id: DocumentId) -> None: client = CamundaClient() client.delete_document(document_id=document_id) ``` ### delete_global_cluster_variable() ```python def delete_global_cluster_variable(name, **kwargs) ``` Delete a global-scoped cluster variable > Delete a global-scoped cluster variable. **Parameters:** | Parameter | Type | Description | | --------- | ----- | --------------------------------------------------------------------------------------------------------------------- | | `name` | `str` | The name of a cluster variable. Unique within its scope (global or tenant- specific). Example: feature-flag-checkout. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Cluster variable not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a global cluster variable:** ```python def delete_global_cluster_variable_example(name: ClusterVariableName) -> None: client = CamundaClient() client.delete_global_cluster_variable(name=name) ``` ### delete_global_task_listener() ```python def delete_global_task_listener(id, **kwargs) ``` Delete global user task listener > Deletes a global user task listener. **Parameters:** | Parameter | Type | Description | | --------- | ----- | ---------------------------------------------------------------------- | | `id` | `str` | The user-defined id for the global listener Example: GlobalListener_1. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The global user task listener was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a global task listener:** ```python def delete_global_task_listener_example(listener_id: GlobalListenerId) -> None: client = CamundaClient() client.delete_global_task_listener(id=listener_id) ``` ### delete_group() ```python def delete_group(group_id, **kwargs) ``` Delete group > Deletes the group with the given ID. **Parameters:** | Parameter | Type | Description | | ---------- | ----- | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The group with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a group:** ```python def delete_group_example(group_id: GroupId) -> None: client = CamundaClient() client.delete_group(group_id=group_id) ``` ### delete_history_backup() ```python def delete_history_backup(backup_id, **kwargs) ``` Delete history backup > Deletes the history backup with the given id, by deleting every snapshot that makes it > up. > > Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ----------- | | `backup_id` | int) – | | The id of the backup. Must be a positive numerical value. As backups are logically ordered by their ids (ascending), each successive backup must use a higher id than the previous one. > Example: 1. - **kwargs** (_Any_) - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. The request is forbidden for one of three reasons: the authenticated caller lacks the required BACKUP permission; the cluster’s secondary storage is neither Elasticsearch nor OpenSearch and therefore cannot serve history backups; or the physical tenant’s snapshot repository is absent from the store — configured under a name the store does not have, or not configured at all. The problem detail says which applies. The latter two are deployment faults the caller cannot correct by changing its request. - **errors.NotFoundError** – If the response status code is 404. A backup with the given id does not exist. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a history backup:** ```python def delete_history_backup_example(backup_id: int) -> None: client = CamundaClient() client.delete_history_backup(backup_id=backup_id) ``` ### delete_history_backup_as_cluster_admin() ```python def delete_history_backup_as_cluster_admin(backup_id, *, physical_tenant_id=, **kwargs) ``` Delete a history backup across physical tenants > Deletes the history backup with the given id from every physical tenant of the cluster, or from the > one named by physicalTenantId. A tenant that does not hold the backup has already reached the > requested end state, so it counts as deleted rather than as a failure. > > The request is all-or-nothing: a physical tenant the backup cannot be deleted from fails the whole > request, and the deletions that already succeeded on other tenants are not undone. Narrow the > request with physicalTenantId to delete from the tenants that can still be reached. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Only available on clusters > whose secondary storage is Elasticsearch or OpenSearch. Use DELETE /v2/backups/history/{backupId} > to act as a single physical tenant. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ----------- | | `backup_id` | int) – | | The id of the backup. Must be a positive numerical value. As backups are logically ordered by their ids (ascending), each successive backup must use a higher id than the previous one. > Example: 1. - **physical_tenant_id** (_str_ _|_ _Unset_) – Example: default. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. The cluster’s secondary storage cannot serve history backups, or a targeted physical tenant’s snapshot repository is absent from the store. Unlike the per-physical-tenant backup endpoints, the cluster-admin surface performs no fine-grained authorization, so a missing BACKUP permission is never the reason. Deletion fans out with no preceding check, so an absent repository is found only once that tenant is reached, by which time the backup may already be deleted from the others; those deletions are not undone. Narrow the request with physicalTenantId to work with the tenants whose repository is usable. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster, or every targeted physical tenant was reached and none of them holds a backup with the given id. - **errors.InternalServerErrorError** – If the response status code is 500. The backup could not be deleted from every targeted physical tenant, because one of them hit an internal error, so it may still exist on some of them. The deletions that already succeeded are not undone, so a retry has only the remaining tenants left to reach. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a history backup across physical tenants:** ```python def delete_history_backup_as_cluster_admin_example(backup_id: int) -> None: client = CamundaClient() # Deletes the backup from every physical tenant. A tenant that does not hold # it already counts as deleted, so this is idempotent when all tenants are # reachable. Use `physical_tenant_id` to narrow to a single tenant. client.delete_history_backup_as_cluster_admin(backup_id=backup_id) ``` ### delete_mapping_rule() ```python def delete_mapping_rule(mapping_rule_id, **kwargs) ``` Delete a mapping rule > Deletes the mapping rule with the given ID. **Parameters:** | Parameter | Type | Description | | ----------------- | ----- | ------------------------------------------------------------------ | | `mapping_rule_id` | `str` | The unique identifier of a mapping rule. Example: my-mapping-rule. | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The mapping rule with the mappingRuleId was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a mapping rule:** ```python def delete_mapping_rule_example(mapping_rule_id: MappingRuleId) -> None: client = CamundaClient() client.delete_mapping_rule(mapping_rule_id=mapping_rule_id) ``` ### delete_process_instance() ```python def delete_process_instance(process_instance_key, *, data=, **kwargs) ``` Delete process instance > Deletes a process instance. Only instances that are completed or terminated can be deleted. **Parameters:** | Parameter | Type | Description | | ---------------------- | --------------------------------------------------- | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `data` | `DeleteProcessInstanceRequest` \| `None` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The process instance is not found. - **errors.ConflictError** – If the response status code is 409. The process instance is not in a completed or terminated state and cannot be deleted. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a process instance:** ```python def delete_process_instance_example(process_instance_key: ProcessInstanceKey) -> None: client = CamundaClient() client.delete_process_instance( process_instance_key=process_instance_key, ) ``` ### delete_process_instances_batch_operation() ```python def delete_process_instances_batch_operation(*, data, **kwargs) ``` Delete process instances (batch) > Delete multiple process instances. This will delete the historic data from secondary storage. > > Only process instances in a final state (COMPLETED or TERMINATED) can be deleted. > This is done asynchronously, the progress can be tracked using the batchOperationKey from the > response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------- | ---------------------------------------------- | ----------------------------------------------------------------------------------- | | `data` | `ProcessInstanceDeletionBatchOperationRequest` | The process instance filter that defines which process instances should be deleted. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The process instance batch operation failed. More details are provided in the response body. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationCreatedResult - **Return type:** BatchOperationCreatedResult #### Examples **Delete process instances in batch:** ```python def delete_process_instances_batch_operation_example() -> None: client = CamundaClient() result = client.delete_process_instances_batch_operation( data=ProcessInstanceDeletionBatchOperationRequest( filter_=ProcessInstanceCancellationBatchOperationRequestFilter(), ), ) print(f"Batch operation key: {result.batch_operation_key}") ``` ### delete_resource() ```python def delete_resource(resource_key, *, data=, **kwargs) ``` 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. History deletion is supported for process definitions and decision requirements > definitions; for other resource types (forms, generic resources) the flag is ignored and no > history is deleted. > > The two supported types differ in how the history is removed. For a decision requirements > definition the history is deleted asynchronously via a batch operation whose details are > returned in the batchOperation field of the response. For a process definition that still > exists in the runtime state, the definition first drains its running instances and its > history is deleted asynchronously once the definition is fully removed cluster-wide; no batch > operation is returned in the response. If the process definition has already been removed > from the runtime state and the deletion is later re-triggered with deleteHistory set to > true, a batch operation is created immediately and returned in the batchOperation field. **Parameters:** | Parameter | Type | Description | | -------------- | -------------------------------------------- | ------------------------------------------ | | `resource_key` | `str` | The system-assigned key for this resource. | | `data` | `DeleteResourceRequest` \| `None` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The resource is not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DeleteResourceResponse - **Return type:** DeleteResourceResponse #### Examples **Delete a resource:** ```python def delete_resource_example() -> None: client = CamundaClient() # Use a resource key from a previous deployment response client.delete_resource(resource_key="2251799813685249") ``` ### delete_role() ```python def delete_role(role_id, **kwargs) ``` Delete role > Deletes the role with the given ID. **Parameters:** | Parameter | Type | Description | | --------- | ----- | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The role with the ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a role:** ```python def delete_role_example(role_id: RoleId) -> None: client = CamundaClient() client.delete_role(role_id=role_id) ``` ### delete_runtime_backup() ```python def delete_runtime_backup(backup_id, **kwargs) ``` Delete runtime backup > Deletes the runtime backup with the given id. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ----------- | | `backup_id` | int) – | | The id of the backup. Must be a positive numerical value. As backups are logically ordered by their ids (ascending), each successive backup must use a higher id than the previous one. > Example: 1. - **kwargs** (_Any_) - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a runtime backup:** ```python def delete_runtime_backup_example(backup_id: int) -> None: client = CamundaClient() client.delete_runtime_backup(backup_id=backup_id) ``` ### delete_runtime_backup_as_cluster_admin() ```python def delete_runtime_backup_as_cluster_admin(backup_id, *, physical_tenant_id=, **kwargs) ``` Delete a runtime backup across physical tenants > Deletes the runtime backup with the given id from every physical tenant of the cluster, or from the > one named by physicalTenantId. A tenant that does not hold the backup has already reached the > requested end state, so it counts as deleted rather than as a failure — the same as deleting an > unknown backup id through the per-physical-tenant endpoint. > > The request is all-or-nothing: a physical tenant the backup cannot be deleted from fails the whole > request, and the deletions that already succeeded on other tenants are not undone. Narrow the > request with physicalTenantId to delete from the tenants that can still be reached. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Use DELETE > /v2/backups/runtime/{backupId} to act as a single physical tenant. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ----------- | | `backup_id` | int) – | | The id of the backup. Must be a positive numerical value. As backups are logically ordered by their ids (ascending), each successive backup must use a higher id than the previous one. > Example: 1. - **physical_tenant_id** (_str_ _|_ _Unset_) – Example: default. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster. - **errors.InternalServerErrorError** – If the response status code is 500. The backup could not be deleted from every targeted physical tenant, so it may still exist on some of them. The deletions that already succeeded are not undone, so a retry has only the remaining tenants left to reach. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a runtime backup across physical tenants:** ```python def delete_runtime_backup_as_cluster_admin_example(backup_id: int) -> None: client = CamundaClient() # Deletes the backup from every physical tenant. A tenant that does not hold # it already counts as deleted, so this is idempotent when all tenants are # reachable. Use `physical_tenant_id` to narrow to a single tenant. client.delete_runtime_backup_as_cluster_admin(backup_id=backup_id) ``` ### delete_runtime_backup_state() ```python def delete_runtime_backup_state(**kwargs) ``` Delete runtime backup state > Resets the runtime backup state of every partition of the physical tenant, clearing > all checkpoint info, backup info, checkpoint metadata, and backup ranges. Used when > switching backup stores. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Parameters:** **kwargs** (_Any_) - **Return type:** None #### Examples **Delete the runtime backup state:** ```python def delete_runtime_backup_state_example() -> None: client = CamundaClient() # Clears all checkpoint info, backup info, checkpoint metadata, and backup # ranges of every partition. Used when switching backup stores. client.delete_runtime_backup_state() ``` ### delete_runtime_backup_state_as_cluster_admin() ```python def delete_runtime_backup_state_as_cluster_admin(*, physical_tenant_id=, **kwargs) ``` Delete runtime backup state across physical tenants > Resets the runtime backup state of every partition of every physical tenant of the cluster, or of > the one named by physicalTenantId, clearing all checkpoint info, backup info, checkpoint metadata, > and backup ranges. Used when switching backup stores. > > The request is all-or-nothing: a physical tenant whose state cannot be reset fails the whole > request, and the resets that already succeeded on other tenants are not undone. Narrow the request > with physicalTenantId to reset the tenants that can still be reached. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Use DELETE > /v2/backups/runtime/state to act as a single physical tenant. **Parameters:** | Parameter | Type | Description | | -------------------- | ---------------- | ----------------- | | `physical_tenant_id` | `str` \| `Unset` | Example: default. | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster. - **errors.InternalServerErrorError** – If the response status code is 500. The state could not be reset on every targeted physical tenant, so it may still be set on some of them. The resets that already succeeded are not undone, so a retry has only the remaining tenants left to reach. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete runtime backup state across physical tenants:** ```python def delete_runtime_backup_state_as_cluster_admin_example() -> None: client = CamundaClient() # Clears all checkpoint info, backup info, checkpoint metadata, and backup # ranges of every partition of every physical tenant. Used when switching # backup stores. Use `physical_tenant_id` to narrow to a single tenant. client.delete_runtime_backup_state_as_cluster_admin() ``` ### delete_tenant() ```python def delete_tenant(tenant_id, **kwargs) ``` Delete tenant > Deletes an existing tenant. **Parameters:** | Parameter | Type | Description | | ----------- | ----- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The tenant was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a tenant:** ```python def delete_tenant_example(tenant_id: TenantId) -> None: client = CamundaClient() client.delete_tenant(tenant_id=tenant_id) ``` ### delete_tenant_cluster_variable() ```python def delete_tenant_cluster_variable(tenant_id, name, **kwargs) ``` Delete a tenant-scoped cluster variable > Delete a tenant-scoped cluster variable. **Parameters:** | Parameter | Type | Description | | ----------- | ----- | --------------------------------------------------------------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `name` | `str` | The name of a cluster variable. Unique within its scope (global or tenant- specific). Example: feature-flag-checkout. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Cluster variable not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a tenant cluster variable:** ```python def delete_tenant_cluster_variable_example(tenant_id: TenantId, name: ClusterVariableName) -> None: client = CamundaClient() client.delete_tenant_cluster_variable( tenant_id=tenant_id, name=name, ) ``` ### delete_user() ```python def delete_user(username, **kwargs) ``` Delete user > Deletes a user. **Parameters:** | Parameter | Type | Description | | ---------- | ----- | -------------------------------------------- | | `username` | `str` | The unique name of a user. Example: swillis. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The user is not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Delete a user:** ```python def delete_user_example(username: Username) -> None: client = CamundaClient() client.delete_user(username=username) ``` ### deploy_resources_from_files() ```python def deploy_resources_from_files(files, tenant_id=None) ``` Deploy BPMN/DMN/Form resources from local files. This is a convenience wrapper around [`create_deployment()`](#create_deployment) that: - Reads each path in `files` as bytes. - Wraps the bytes in `camunda_orchestration_sdk.types.File` using the file’s basename as `file_name`. - Builds `camunda_orchestration_sdk.models.CreateDeploymentData` and calls [`create_deployment()`](#create_deployment). - Returns an `ExtendedDeploymentResult`, which is the deployment response plus convenience lists (`processes`, `decisions`, `decision_requirements`, `forms`). **Parameters:** | Parameter | Type | Description | | ----------- | ------------------- | ------------------------------------------------------------------------ | | `files` | list [str \| Path ] | File paths (`str` or `Path`) to deploy. | | `tenant_id` | `str` \| `None` | Optional tenant identifier. If not provided, the default tenant is used. | - **Returns:** The deployment result with extracted resource lists. - **Return type:** ExtendedDeploymentResult - **Raises:** - **FileNotFoundError** – If any file path does not exist. - **PermissionError** – If any file path cannot be read. - **IsADirectoryError** – If any file path is a directory. - **OSError** – For other I/O failures while reading files. - **Exception** – Propagates any exception raised by [`create_deployment()`](#create_deployment) (including typed API errors in `camunda_orchestration_sdk.errors` and `httpx.TimeoutException`). ### evaluate_conditionals() ```python def evaluate_conditionals(*, data, **kwargs) ``` 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. **Parameters:** | Parameter | Type | Description | | --------- | ---------------------------------- | ----------- | | `data` | `ConditionalEvaluationInstruction` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. The client is not authorized to start process instances for the specified process definition. If a processDefinitionKey is not provided, this indicates that the client is not authorized to start process instances for at least one of the matched process definitions. - **errors.NotFoundError** – If the response status code is 404. The process definition was not found for the given processDefinitionKey. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** EvaluateConditionalResult - **Return type:** EvaluateConditionalResult #### Examples **Evaluate conditionals:** ```python def evaluate_conditionals_example() -> None: client = CamundaClient() result = client.evaluate_conditionals( data=ConditionalEvaluationInstruction( variables=ConditionalEvaluationInstructionVariables.from_dict({"orderReady": True}), ), ) print(f"Result: {result}") ``` ### evaluate_decision() ```python def evaluate_decision(*, data, **kwargs) ``` Evaluate decision > Evaluates a decision. > > You specify the decision to evaluate either by using its unique key (as returned by > DeployResource), or using the decision ID. When using the decision ID, the latest deployed > version of the decision is used. **Parameters:** | Parameter | Type | Description | | --------- | ----------------------------------------------------- | ----------- | | `data` | `DecisionEvaluationByID` \| `DecisionEvaluationByKey` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The decision is not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** EvaluateDecisionResult - **Return type:** EvaluateDecisionResult #### Examples **By key:** ```python def evaluate_decision_by_key_example(decision_definition_key: DecisionDefinitionKey) -> None: client = CamundaClient() result = client.evaluate_decision( data=DecisionEvaluationByKey( decision_definition_key=decision_definition_key, ) ) print(f"Decision key: {result.decision_definition_key}") ``` **By ID:** ```python def evaluate_decision_by_id_example(decision_definition_id: DecisionDefinitionId) -> None: client = CamundaClient() result = client.evaluate_decision( data=DecisionEvaluationByID( decision_definition_id=decision_definition_id, ) ) print(f"Decision key: {result.decision_definition_key}") ``` ### evaluate_expression() ```python def evaluate_expression(*, data, **kwargs) ``` Evaluate an expression > Evaluates a FEEL expression and returns the result. Supports references to tenant scoped > cluster variables when a tenant ID is provided. Optionally, provide a scopeKey to make the > variables of a specific process instance or element instance visible while evaluating the > expression. **Parameters:** | Parameter | Type | Description | | --------- | ----------------------------- | ----------- | | `data` | `ExpressionEvaluationRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ExpressionEvaluationResult - **Return type:** ExpressionEvaluationResult #### Examples **Evaluate an expression:** ```python def evaluate_expression_example() -> None: client = CamundaClient() result = client.evaluate_expression( data=ExpressionEvaluationRequest( expression="= 1 + 2", ), ) print(f"Result: {result.result}") ``` ### fail_job() ```python def fail_job(job_key, *, data=, **kwargs) ``` Fail job > Mark the job as failed. **Parameters:** | Parameter | Type | Description | | --------- | --------------------------- | ---------------------------------------------------------- | | `job_key` | `str` | System-generated key for a job. Example: 2251799813653498. | | `data` | `JobFailRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The job with the given jobKey is not found. It was completed by another worker, or the process instance itself was canceled. - **errors.ConflictError** – If the response status code is 409. The job with the given key is in the wrong state (i.e: not ACTIVATED or ACTIVATABLE). The job was failed by another worker with retries = 0, and the process is now in an incident state. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Fail a job with retry:** ```python def fail_job_example(job_key: JobKey) -> None: client = CamundaClient() client.fail_job( job_key=job_key, data=JobFailRequest( retries=2, error_message="Payment gateway timeout", retry_back_off=5000, ), ) ``` ### get_agent_definition() ```python def get_agent_definition(agent_definition_key, *, consistency=None, **kwargs) ``` Get agent definition > Returns an agent definition by key. **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------ | ------------------------------------------------------------------------ | | `agent_definition_key` | `str` | System-generated key for an agent definition. Example: 2251799813691958. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The agent definition with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AgentDefinitionResult - **Return type:** AgentDefinitionResult #### Examples **Get an agent definition:** ```python def get_agent_definition_example(agent_definition_key: AgentDefinitionKey) -> None: client = CamundaClient() agent_definition = client.get_agent_definition( agent_definition_key=agent_definition_key ) print(f"Agent definition name: {agent_definition.name}") ``` ### get_agent_instance() ```python def get_agent_instance(agent_instance_key, *, consistency=None, **kwargs) ``` Get agent instance > Returns agent instance as JSON. **Parameters:** | Parameter | Type | Description | | -------------------- | ------------------------------ | ---------------------------------------------------------------------- | | `agent_instance_key` | `str` | System-generated key for an agent instance. Example: 4503599627370496. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The agent instance with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AgentInstanceResult - **Return type:** AgentInstanceResult #### Examples **Get an agent instance:** ```python def get_agent_instance_example(agent_instance_key: AgentInstanceKey) -> None: client = CamundaClient() agent_instance = client.get_agent_instance(agent_instance_key=agent_instance_key) print(f"Agent instance status: {agent_instance.status}") ``` ### get_audit_log() ```python def get_audit_log(audit_log_key, *, consistency=None, **kwargs) ``` Get audit log > Get an audit log entry by auditLogKey. **Parameters:** | Parameter | Type | Description | | --------------- | ------------------------------ | ------------------------------------------------------------------------ | | `audit_log_key` | `str` | System-generated key for an audit log entry. Example: 22517998136843567. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The audit log with the given key was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AuditLogResult - **Return type:** AuditLogResult #### Examples **Get an audit log entry:** ```python def get_audit_log_example(audit_log_key: AuditLogKey) -> None: client = CamundaClient() result = client.get_audit_log(audit_log_key=audit_log_key) print(f"Audit log: {result.audit_log_key}") ``` ### get_authentication() ```python def get_authentication(**kwargs) ``` Get current user > Retrieves the current authenticated user. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** CamundaUserResult - **Parameters:** **kwargs** (_Any_) - **Return type:** CamundaUserResult #### Examples **Get authentication info:** ```python def get_authentication_example() -> None: client = CamundaClient() result = client.get_authentication() print(f"Authenticated user: {result.username}") ``` ### get_authorization() ```python def get_authorization(authorization_key, *, consistency=None, **kwargs) ``` Get authorization > Get authorization by the given key. **Parameters:** | Parameter | Type | Description | | ------------------- | ------------------------------ | --------------------------------------------------------------------- | | `authorization_key` | `str` | System-generated key for an authorization. Example: 2251799813684332. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The authorization with the given key was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AuthorizationResult - **Return type:** AuthorizationResult #### Examples **Get an authorization:** ```python def get_authorization_example(authorization_key: AuthorizationKey) -> None: client = CamundaClient() result = client.get_authorization( authorization_key=authorization_key, ) print(f"Resource type: {result.resource_type}") ``` ### get_batch_operation() ```python def get_batch_operation(batch_operation_key, *, consistency=None, **kwargs) ``` Get batch operation > Get batch operation by key. **Parameters:** | Parameter | Type | Description | | --------------------- | ------------------------------ | ----------------------------------------------------------------------- | | `batch_operation_key` | `str` | System-generated key for an batch operation. Example: 2251799813684321. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The batch operation is not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationResponse - **Return type:** BatchOperationResponse #### Examples **Get a batch operation:** ```python def get_batch_operation_example(batch_operation_key: BatchOperationKey) -> None: client = CamundaClient() result = client.get_batch_operation( batch_operation_key=batch_operation_key, ) print(f"Batch operation: {result.batch_operation_key}") ``` ### get_cluster_exporting_status() ```python def get_cluster_exporting_status(**kwargs) ``` Get exporting status of the whole cluster > Returns the exporting status of the whole cluster, folded over the exporting status of every > physical tenant. Only PAUSED and SOFT_PAUSED confirm that exporting is paused cluster-wide; > every other value means at least one physical tenant is not paused, so callers should keep polling. > A physical tenant that itself reports MIXED makes the whole cluster MIXED. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ExportingStatusResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** ExportingStatusResponse #### Examples **Get exporting status for the whole cluster:** ```python def get_cluster_exporting_status_example() -> None: client = CamundaClient() # Requires the cluster-admin security chain — not the Orchestration Cluster # user credentials. Only `PAUSED` and `SOFT_PAUSED` confirm a cluster-wide # pause; any other value means at least one physical tenant is still active. result = client.get_cluster_exporting_status() print(f"Cluster exporting status: {result.status}") ``` ### get_cluster_rebalance() ```python def get_cluster_rebalance(**kwargs) ``` Report the cluster’s current leadership balance > Reports whether the cluster is currently balanced, the current leadership state of every partition, > and what became of the last rebalance to finish. The last completed rebalance is held in memory by > the coordinating broker, so none will be reported if the coordinator has moved or restarted since > the last rebalance. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.BadGatewayError** – If the response status code is 502. The coordinator was reached, but its response was absent or unusable. - **errors.ServiceUnavailableError** – If the response status code is 503. No coordinator is currently available or reachable. - **errors.GatewayTimeoutError** – If the response status code is 504. The coordinator did not answer before the request timeout. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterBalanceResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** ClusterBalanceResponse #### Examples **Get the current cluster rebalance status:** ```python def get_cluster_rebalance_example() -> None: client = CamundaClient() # Poll this endpoint after triggering a rebalance to monitor progress. result: ClusterBalanceResponse = client.get_cluster_rebalance() print(f"Cluster balance state: {result.state}") if result.running_rebalance is not None: print(f"Running rebalance in progress: {result.running_rebalance}") if result.last_completed_rebalance is not None: print(f"Last completed rebalance: {result.last_completed_rebalance}") for partition in result.partitions: print(f" Partition {partition.partition_id}: {partition.state}") ``` ### get_cluster_status() ```python def get_cluster_status(**kwargs) ``` Get the status of the whole cluster > Checks the health status of the whole cluster, aggregated over all physical tenants. Returns > HEALTHY when every physical tenant is healthy, DOWN when no physical tenant can process work, > and DEGRADED in every other case. No per-tenant detail is reported; use GET /cluster/v2/topology > for that. > > This endpoint is public and requires no authentication, unlike PATCH /cluster/v2/mode below, which > needs cluster-admin credentials. - **Raises:** - **errors.ServiceUnavailableError** – If the response status code is 503. The cluster is DOWN because no physical tenant can process work. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterStatusResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** ClusterStatusResponse #### Examples **Get cluster status:** ```python def get_cluster_status_example() -> None: client = CamundaClient() result = client.get_cluster_status() print(f"Cluster status: {result.status}") ``` ### get_cluster_topology() ```python def get_cluster_topology(**kwargs) ``` Get the topology of the whole cluster > Obtains the topology of the whole cluster, aggregated over all physical tenants. Cluster-level > information is reported once; partition layout, replication and per-partition role, health and state > are reported per physical tenant. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Use GET /v2/topology for > the topology of a single physical tenant. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterTopologyResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** ClusterTopologyResponse #### Examples **Get cluster topology (cluster admin):** ```python def get_cluster_topology_example() -> None: client = CamundaClient() # Returns cluster-wide topology aggregated over all physical tenants. # Use GET /v2/topology for the topology of a single physical tenant. result = client.get_cluster_topology() print(f"Cluster {result.cluster_id or 'unknown'}: {result.cluster_size} brokers") print(f"Gateway version: {result.gateway_version}") for tenant in result.physical_tenants: print(f" Physical tenant: {tenant.physical_tenant_id}") ``` ### get_cluster_upgrade_status() ```python def get_cluster_upgrade_status(**kwargs) ``` Get the upgrade-readiness status of the whole cluster > Reports one overall upgrade-readiness status for the whole cluster, folded over every physical > tenant and condition. MIGRATED only once every known condition has migrated for every known > physical tenant; MIGRATION_IN_PROGRESS when at least one is confirmed not yet migrated; UNKNOWN > otherwise (including before anything has been reported yet). No per-tenant or per-condition detail > is reported here; see the upgradeReadiness actuator endpoint for that. - **Raises:** - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterUpgradeStatusResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** ClusterUpgradeStatusResponse #### Examples **Get cluster upgrade readiness status:** ```python def get_cluster_upgrade_status_example() -> None: client = CamundaClient() result = client.get_cluster_upgrade_status() print(f"Cluster upgrade readiness: {result.status.value}") ``` ### get_decision_definition() ```python def get_decision_definition(decision_definition_key, *, consistency=None, **kwargs) ``` Get decision definition > Returns a decision definition by key. **Parameters:** | Parameter | Type | Description | | ------------------------- | ------------------------------ | -------------------------------------------------------------------------- | | `decision_definition_key` | `str` | System-generated key for a decision definition. Example: 2251799813326547. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The decision definition with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DecisionDefinitionResult - **Return type:** DecisionDefinitionResult #### Examples **Get a decision definition:** ```python def get_decision_definition_example(decision_definition_key: DecisionDefinitionKey) -> None: client = CamundaClient() definition = client.get_decision_definition( decision_definition_key=decision_definition_key, ) print(f"Decision: {definition.decision_definition_id}") ``` ### get_decision_definition_xml() ```python def get_decision_definition_xml(decision_definition_key, *, consistency=None, **kwargs) ``` Get decision definition XML > Returns decision definition as XML. **Parameters:** | Parameter | Type | Description | | ------------------------- | ------------------------------ | -------------------------------------------------------------------------- | | `decision_definition_key` | `str` | System-generated key for a decision definition. Example: 2251799813326547. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The decision definition with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** str - **Return type:** str #### Examples **Get decision definition XML:** ```python def get_decision_definition_xml_example(decision_definition_key: DecisionDefinitionKey) -> None: client = CamundaClient() xml = client.get_decision_definition_xml( decision_definition_key=decision_definition_key, ) print(f"XML length: {len(xml)}") ``` ### get_decision_instance() ```python def get_decision_instance(decision_evaluation_instance_key, *, consistency=None, **kwargs) ``` Get decision instance > Returns a decision instance. **Parameters:** | Parameter | Type | Description | | ---------------------------------- | ------ | ----------- | | `decision_evaluation_instance_key` | str) – | | System-generated identifier for a decision evaluation instance. It is composed of the parent decision evaluation key and the 1-based index of the evaluated decision within that evaluation, joined by a hyphen (format: -). > Example: 2251799813684367-1. - **consistency** (_ConsistencyOptions_ _|_ _None_) - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The decision instance with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DecisionInstanceGetQueryResult - **Return type:** DecisionInstanceGetQueryResult #### Examples **Get a decision instance:** ```python def get_decision_instance_example(decision_evaluation_instance_key: DecisionEvaluationInstanceKey) -> None: client = CamundaClient() result = client.get_decision_instance( decision_evaluation_instance_key=decision_evaluation_instance_key, ) print(f"Decision instance: {result.decision_definition_id}") ``` ### get_decision_requirements() ```python def get_decision_requirements(decision_requirements_key, *, consistency=None, **kwargs) ``` Get decision requirements > Returns Decision Requirements as JSON. **Parameters:** | Parameter | Type | Description | | --------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------ | | `decision_requirements_key` | `str` | System-generated key for a deployed decision requirements definition. Example: 2251799813683346. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The decision requirements with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DecisionRequirementsResult - **Return type:** DecisionRequirementsResult #### Examples **Get decision requirements:** ```python def get_decision_requirements_example(decision_requirements_key: DecisionRequirementsKey) -> None: client = CamundaClient() result = client.get_decision_requirements( decision_requirements_key=decision_requirements_key, ) print(f"DRD: {result.decision_requirements_name}") ``` ### get_decision_requirements_xml() ```python def get_decision_requirements_xml(decision_requirements_key, *, consistency=None, **kwargs) ``` Get decision requirements XML > Returns decision requirements as XML. **Parameters:** | Parameter | Type | Description | | --------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------ | | `decision_requirements_key` | `str` | System-generated key for a deployed decision requirements definition. Example: 2251799813683346. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The decision requirements with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** str - **Return type:** str #### Examples **Get decision requirements XML:** ```python def get_decision_requirements_xml_example(decision_requirements_key: DecisionRequirementsKey) -> None: client = CamundaClient() xml = client.get_decision_requirements_xml( decision_requirements_key=decision_requirements_key, ) print(f"XML length: {len(xml)}") ``` ### get_document() ```python def get_document(document_id, *, store_id=, content_hash=, **kwargs) ``` Download document > Download a document from the Camunda 8 cluster. > > Note that this is currently supported for document stores of type: AWS, Azure, GCP, in-memory (non- > production), local (non-production) **Parameters:** | Parameter | Type | Description | | -------------- | ---------------- | ------------------------------------------------ | | `document_id` | `str` | Document Id that uniquely identifies a document. | | `store_id` | `str` \| `Unset` | | | `content_hash` | `str` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.NotFoundError** – If the response status code is 404. The document with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** File - **Return type:** File #### Examples **Get a document:** ```python def get_document_example(document_id: DocumentId) -> None: client = CamundaClient() result = client.get_document(document_id=document_id) print(f"File name: {result.file_name}") ``` ### get_element_instance() ```python def get_element_instance(element_instance_key, *, consistency=None, **kwargs) ``` Get element instance > Returns element instance as JSON. **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------ | ----------------------------------------------------------------------- | | `element_instance_key` | `str` | System-generated key for a element instance. Example: 2251799813686789. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The element instance with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ElementInstanceResult - **Return type:** ElementInstanceResult #### Examples **Get an element instance:** ```python def get_element_instance_example(element_instance_key: ElementInstanceKey) -> None: client = CamundaClient() result = client.get_element_instance( element_instance_key=element_instance_key, ) print(f"Element: {result.element_id}") ``` ### get_exporting_status() ```python def get_exporting_status(**kwargs) ``` Get exporting status > Returns the exporting status of the physical tenant, aggregated over every replica of > every one of its partitions. > > Because pause and resume are applied to all replicas, the status is only a single phase > if every replica reports that phase; otherwise it is MIXED, which means a pause or > resume is still in flight or was only partially applied. Backup tooling should treat > only PAUSED and SOFT_PAUSED as confirmation that exporting is paused. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ExportingStatusResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** ExportingStatusResponse #### Examples **Get exporting status:** ```python def get_exporting_status_example() -> None: client = CamundaClient() result = client.get_exporting_status() # The status is aggregated over every replica of every partition, so `MIXED` # means a pause or resume is still in flight or was only partially applied. # Only `PAUSED` and `SOFT_PAUSED` confirm that exporting has stopped. print(f"Status: {result.status}") ``` ### get_form_by_key() ```python def get_form_by_key(form_key, *, consistency=None, **kwargs) ``` Get form by key > Get a form by its unique form key. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | -------------------------------------------------------------------- | | `form_key` | `str` | System-generated key for a deployed form. Example: 2251799813684365. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The form with the given key was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** FormResult - **Return type:** FormResult #### Examples **Get a form by key:** ```python def get_form_by_key_example(form_key: FormKey) -> None: client = CamundaClient() result = client.get_form_by_key(form_key=form_key) print(f"Form: {result.form_id}") ``` ### get_global_cluster_variable() ```python def get_global_cluster_variable(name, *, consistency=None, **kwargs) ``` Get a global-scoped cluster variable > Get a global-scoped cluster variable. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------- | | `name` | `str` | The name of a cluster variable. Unique within its scope (global or tenant- specific). Example: feature-flag-checkout. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Cluster variable not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterVariableResult - **Return type:** ClusterVariableResult #### Examples **Get a global cluster variable:** ```python def get_global_cluster_variable_example(name: ClusterVariableName) -> None: client = CamundaClient() result = client.get_global_cluster_variable(name=name) print(f"Variable: {result.name} = {result.value}") ``` ### get_global_job_statistics() ```python def get_global_job_statistics(*, from_, to, job_type=, consistency=None, **kwargs) ``` Global job statistics > Returns global aggregated counts for jobs. Filter by the creation time window (required) and > optionally by jobType. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | ----------- | | `from` | `datetime.datetime` | | | `to` | `datetime.datetime` | | | `job_type` | `str` \| `Unset` | | | `from_` | `datetime.datetime` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GlobalJobStatisticsQueryResult - **Return type:** GlobalJobStatisticsQueryResult #### Examples **Get global job statistics:** ```python def get_global_job_statistics_example() -> None: client = CamundaClient() result = client.get_global_job_statistics( from_=datetime.datetime(2024, 1, 1), to=datetime.datetime(2024, 12, 31), ) print(f"Global job stats: {result}") ``` ### get_global_task_listener() ```python def get_global_task_listener(id, *, consistency=None, **kwargs) ``` Get global user task listener > Get a global user task listener by its id. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | ---------------------------------------------------------------------- | | `id` | `str` | The user-defined id for the global listener Example: GlobalListener_1. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The global user task listener with the given id was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GlobalTaskListenerResult - **Return type:** GlobalTaskListenerResult #### Examples **Get a global task listener:** ```python def get_global_task_listener_example(listener_id: GlobalListenerId) -> None: client = CamundaClient() result = client.get_global_task_listener(id=listener_id) print(f"Task listener: {result.event_types}") ``` ### get_group() ```python def get_group(group_id, *, consistency=None, **kwargs) ``` Get group > Get a group by its ID. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GroupResult - **Return type:** GroupResult #### Examples **Get a group:** ```python def get_group_example(group_id: GroupId) -> None: client = CamundaClient() result = client.get_group(group_id=group_id) print(f"Group: {result.name}") ``` ### get_history_backup() ```python def get_history_backup(backup_id, **kwargs) ``` Get history backup > Returns detailed status of the history backup with the given id. > > Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ----------- | | `backup_id` | int) – | | The id of the backup. Must be a positive numerical value. As backups are logically ordered by their ids (ascending), each successive backup must use a higher id than the previous one. > Example: 1. - **kwargs** (_Any_) - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. The request is forbidden for one of three reasons: the authenticated caller lacks the required BACKUP permission; the cluster’s secondary storage is neither Elasticsearch nor OpenSearch and therefore cannot serve history backups; or the physical tenant’s snapshot repository is absent from the store — configured under a name the store does not have, or not configured at all. The problem detail says which applies. The latter two are deployment faults the caller cannot correct by changing its request. - **errors.NotFoundError** – If the response status code is 404. A backup with the given id does not exist. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** HistoryBackupInfo - **Return type:** HistoryBackupInfo #### Examples **Get a history backup:** ```python def get_history_backup_example(backup_id: int) -> None: client = CamundaClient() result = client.get_history_backup(backup_id=backup_id) # The aggregated state is derived from the state of every expected snapshot. print(f"History backup {result.backup_id} is {result.state.value}") ``` ### get_history_backup_as_cluster_admin() ```python def get_history_backup_as_cluster_admin(backup_id, *, physical_tenant_id=, **kwargs) ``` Get a history backup across physical tenants > Reports what every physical tenant of the cluster, or the one named by physicalTenantId, holds for > the given backup id. There is no aggregated cluster-level state: a tenant that was reached and does > not hold this backup reports NOT_FOUND, which is a successful observation rather than a failure. > > The request is all-or-nothing: a physical tenant whose state cannot be read fails the whole request. > Narrow the request with physicalTenantId to read the tenants that can still be reached. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Only available on clusters > whose secondary storage is Elasticsearch or OpenSearch. Use GET /v2/backups/history/{backupId} to > act as a single physical tenant. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ----------- | | `backup_id` | int) – | | The id of the backup. Must be a positive numerical value. As backups are logically ordered by their ids (ascending), each successive backup must use a higher id than the previous one. > Example: 1. - **physical_tenant_id** (_str_ _|_ _Unset_) – Example: default. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. The cluster’s secondary storage is neither Elasticsearch nor OpenSearch and therefore cannot serve history backups, or a targeted physical tenant’s snapshot repository is absent from the store — configured under a name the store does not have, or not configured at all. Both are deployment faults the caller cannot correct by changing its request; narrow the request with physicalTenantId to work with the tenants whose repository is usable. Unlike the per-physical-tenant backup endpoints, the cluster-admin surface performs no fine-grained authorization, so a missing BACKUP permission is never the reason. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster, or every targeted physical tenant was read and none of them holds a backup with the given id. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterHistoryBackupInfo - **Return type:** ClusterHistoryBackupInfo #### Examples **Get a history backup across physical tenants:** ```python def get_history_backup_as_cluster_admin_example(backup_id: int) -> None: client = CamundaClient() # Returns what each physical tenant reports for the given backup id. # A tenant reporting `NOT_FOUND` is a successful observation, not an error. result = client.get_history_backup_as_cluster_admin(backup_id=backup_id) print(f"Cluster history backup {result.backup_id}") for tenant in result.physical_tenants: print(f" physical tenant {tenant.physical_tenant_id}: {tenant.state.value}") ``` ### get_incident() ```python def get_incident(incident_key, *, consistency=None, **kwargs) ``` Get incident > Returns incident as JSON. **Parameters:** | Parameter | Type | Description | | -------------- | ------------------------------ | --------------------------------------------------------------- | | `incident_key` | `str` | System-generated key for a incident. Example: 2251799813689432. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The incident with the given key was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** IncidentResult - **Return type:** IncidentResult #### Examples **Get an incident:** ```python def get_incident_example(incident_key: IncidentKey) -> None: client = CamundaClient() incident = client.get_incident(incident_key=incident_key) print(f"Incident error type: {incident.error_type}") ``` ### get_job_error_statistics() ```python def get_job_error_statistics(*, data, consistency=None, **kwargs) ``` Get error metrics for a job type > Returns aggregated metrics per error for the given jobType. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | --------------------------- | | `data` | `JobErrorStatisticsQuery` | Job error statistics query. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** JobErrorStatisticsQueryResult - **Return type:** JobErrorStatisticsQueryResult #### Examples **Get job error statistics:** ```python def get_job_error_statistics_example() -> None: client = CamundaClient() result = client.get_job_error_statistics( data=JobErrorStatisticsQuery( filter_=JobErrorStatisticsFilter( from_=datetime.datetime(2024, 1, 1), to=datetime.datetime(2024, 12, 31), job_type="payment-processing", ), ), ) if not isinstance(result.items, Unset): for stat in result.items: print(f"Error: {stat.error_code}") ``` ### get_job_time_series_statistics() ```python def get_job_time_series_statistics(*, data, consistency=None, **kwargs) ``` Get time-series metrics for a job type > Returns a list of time-bucketed metrics ordered ascending by time. > > The from and to fields select the time window of interest. > Each item in the response corresponds to one time bucket of the requested resolution. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | --------------------------------- | | `data` | `JobTimeSeriesStatisticsQuery` | Job time-series statistics query. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** JobTimeSeriesStatisticsQueryResult - **Return type:** JobTimeSeriesStatisticsQueryResult #### Examples **Get job time series statistics:** ```python def get_job_time_series_statistics_example() -> None: client = CamundaClient() result = client.get_job_time_series_statistics( data=JobTimeSeriesStatisticsQuery( filter_=JobTimeSeriesStatisticsFilter( from_=datetime.datetime(2024, 1, 1), to=datetime.datetime(2024, 12, 31), job_type="payment-processing", ), ), ) if not isinstance(result.items, Unset): for stat in result.items: print(f"Time series: {stat}") ``` ### get_job_type_statistics() ```python def get_job_type_statistics(*, data, consistency=None, **kwargs) ``` Get job statistics by type > Get statistics about jobs, grouped by job type. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | -------------------------- | | `data` | `JobTypeStatisticsQuery` | Job type statistics query. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** JobTypeStatisticsQueryResult - **Return type:** JobTypeStatisticsQueryResult #### Examples **Get job type statistics:** ```python def get_job_type_statistics_example() -> None: client = CamundaClient() result = client.get_job_type_statistics( data=JobTypeStatisticsQuery(), ) if not isinstance(result.items, Unset): for stat in result.items: print(f"Job type: {stat.job_type}") ``` ### get_job_worker_statistics() ```python def get_job_worker_statistics(*, data, consistency=None, **kwargs) ``` Get job statistics by worker > Get statistics about jobs, grouped by worker, for a given job type. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | ---------------------------- | | `data` | `JobWorkerStatisticsQuery` | Job worker statistics query. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** JobWorkerStatisticsQueryResult - **Return type:** JobWorkerStatisticsQueryResult #### Examples **Get job worker statistics:** ```python def get_job_worker_statistics_example() -> None: client = CamundaClient() result = client.get_job_worker_statistics( data=JobWorkerStatisticsQuery( filter_=JobWorkerStatisticsFilter( from_=datetime.datetime(2024, 1, 1), to=datetime.datetime(2024, 12, 31), job_type="payment-processing", ), ), ) if not isinstance(result.items, Unset): for stat in result.items: print(f"Worker: {stat.worker}") ``` ### get_license() ```python def get_license(**kwargs) ``` Get license status > Obtains the status of the current Camunda license. - **Raises:** - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** LicenseResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** LicenseResponse #### Examples **Get license information:** ```python def get_license_example() -> None: client = CamundaClient() result = client.get_license() print(f"License type: {result.license_type}") ``` ### get_mapping_rule() ```python def get_mapping_rule(mapping_rule_id, *, consistency=None, **kwargs) ``` Get a mapping rule > Gets the mapping rule with the given ID. **Parameters:** | Parameter | Type | Description | | ----------------- | ------------------------------ | ------------------------------------------------------------------ | | `mapping_rule_id` | `str` | The unique identifier of a mapping rule. Example: my-mapping-rule. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The mapping rule with the mappingRuleId was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** MappingRuleResult - **Return type:** MappingRuleResult #### Examples **Get a mapping rule:** ```python def get_mapping_rule_example(mapping_rule_id: MappingRuleId) -> None: client = CamundaClient() result = client.get_mapping_rule(mapping_rule_id=mapping_rule_id) print(f"Mapping rule: {result.name}") ``` ### get_process_definition() ```python def get_process_definition(process_definition_key, *, consistency=None, **kwargs) ``` Get process definition > Returns process definition as JSON. **Parameters:** | Parameter | Type | Description | | ------------------------ | ------------------------------ | ---------------------------------------------------------------------------------- | | `process_definition_key` | `str` | System-generated key for a deployed process definition. Example: 2251799813686749. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The process definition with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessDefinitionResult - **Return type:** ProcessDefinitionResult #### Examples **Get a process definition:** ```python def get_process_definition_example( process_definition_key: ProcessDefinitionKey, ) -> None: client = CamundaClient() result = client.get_process_definition( process_definition_key=process_definition_key, ) print(f"Process definition: {result.name}") ``` ### get_process_definition_instance_statistics() ```python def get_process_definition_instance_statistics(*, data=, consistency=None, **kwargs) ``` Get process instance statistics > Get statistics about process instances, grouped by process definition and tenant. **Parameters:** | Parameter | Type | Description | | ------------- | ----------------------------------------------------- | ----------- | | `data` | `ProcessDefinitionInstanceStatisticsQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessDefinitionInstanceStatisticsQueryResult - **Return type:** ProcessDefinitionInstanceStatisticsQueryResult #### Examples **Get process definition instance statistics:** ```python def get_process_definition_instance_statistics_example() -> None: client = CamundaClient() result = client.get_process_definition_instance_statistics( data=ProcessDefinitionInstanceStatisticsQuery(), ) if not isinstance(result.items, Unset): for stat in result.items: print(f"Definition: {stat.process_definition_id}") ``` ### get_process_definition_instance_version_statistics() ```python def get_process_definition_instance_version_statistics(*, data, consistency=None, **kwargs) ``` Get process instance statistics by version > Get statistics about process instances, grouped by version for a given process definition. > > The process definition ID must be provided as a required field in the request body filter. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------------- | ----------- | | `data` | `ProcessDefinitionInstanceVersionStatisticsQuery` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessDefinitionInstanceVersionStatisticsQueryResult - **Return type:** ProcessDefinitionInstanceVersionStatisticsQueryResult #### Examples **Get version statistics:** ```python def get_process_definition_instance_version_statistics_example( process_definition_id: ProcessDefinitionId, ) -> None: client = CamundaClient() result = client.get_process_definition_instance_version_statistics( data=ProcessDefinitionInstanceVersionStatisticsQuery( filter_=ProcessDefinitionInstanceVersionStatisticsQueryFilter( process_definition_id=process_definition_id, ), ), ) if not isinstance(result.items, Unset): for stat in result.items: print(f"Version: {stat.process_definition_version}") ``` ### get_process_definition_message_subscription_statistics() ```python def get_process_definition_message_subscription_statistics(*, data=, consistency=None, **kwargs) ``` Get message subscription statistics > Get message subscription statistics, grouped by process definition. **Parameters:** | Parameter | Type | Description | | ------------- | ---------------------------------------------------------------- | ----------- | | `data` | `ProcessDefinitionMessageSubscriptionStatisticsQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessDefinitionMessageSubscriptionStatisticsQueryResult - **Return type:** ProcessDefinitionMessageSubscriptionStatisticsQueryResult #### Examples **Get message subscription statistics:** ```python def get_process_definition_message_subscription_statistics_example() -> None: client = CamundaClient() result = client.get_process_definition_message_subscription_statistics( data=ProcessDefinitionMessageSubscriptionStatisticsQuery(), ) if not isinstance(result.items, Unset): for stat in result.items: print( f"Definition: {stat.process_definition_id}, subscriptions: {stat.active_subscriptions}" ) ``` ### get_process_definition_statistics() ```python def get_process_definition_statistics(process_definition_key, *, data=, consistency=None, **kwargs) ``` Get process definition statistics > Get statistics about elements in currently running process instances by process definition key and > search filter. **Parameters:** | Parameter | Type | Description | | ------------------------ | ---------------------------------------------------- | ---------------------------------------------------------------------------------- | | `process_definition_key` | `str` | System-generated key for a deployed process definition. Example: 2251799813686749. | | `data` | `ProcessDefinitionElementStatisticsQuery` \| `Unset` | Process definition element statistics request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessDefinitionElementStatisticsQueryResult - **Return type:** ProcessDefinitionElementStatisticsQueryResult #### Examples **Get process definition element statistics:** ```python def get_process_definition_statistics_example( process_definition_key: ProcessDefinitionKey, ) -> None: client = CamundaClient() result = client.get_process_definition_statistics( process_definition_key=process_definition_key, ) if not isinstance(result.items, Unset): for stat in result.items: print(f"Element: {stat.element_id}") ``` ### get_process_definition_xml() ```python def get_process_definition_xml(process_definition_key, *, consistency=None, **kwargs) ``` Get process definition XML > Returns process definition as XML. **Parameters:** | Parameter | Type | Description | | ------------------------ | ------------------------------ | ---------------------------------------------------------------------------------- | | `process_definition_key` | `str` | System-generated key for a deployed process definition. Example: 2251799813686749. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The process definition with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** str - **Return type:** str #### Examples **Get process definition XML:** ```python def get_process_definition_xml_example( process_definition_key: ProcessDefinitionKey, ) -> None: client = CamundaClient() xml = client.get_process_definition_xml( process_definition_key=process_definition_key, ) print(f"XML length: {len(xml)}") ``` ### get_process_instance() ```python def get_process_instance(process_instance_key, *, consistency=None, **kwargs) ``` Get process instance > Get the process instance by the process instance key. **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------ | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The process instance with the given key was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessInstanceResult - **Return type:** ProcessInstanceResult #### Examples **Get a process instance:** ```python def get_process_instance_example(process_instance_key: ProcessInstanceKey) -> None: client = CamundaClient() result = client.get_process_instance( process_instance_key=process_instance_key, ) print(f"Process instance: {result.process_definition_id}") ``` ### get_process_instance_call_hierarchy() ```python def get_process_instance_call_hierarchy(process_instance_key, *, consistency=None, **kwargs) ``` Get call hierarchy > Returns the call hierarchy for a given process instance, showing its ancestry up to the root > instance. **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------ | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The process instance is not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** list[Any] - **Return type:** list[Any] #### Examples **Get process instance call hierarchy:** ```python def get_process_instance_call_hierarchy_example( process_instance_key: ProcessInstanceKey, ) -> None: client = CamundaClient() result = client.get_process_instance_call_hierarchy( process_instance_key=process_instance_key, ) for entry in result: print(f"Call hierarchy entry: {entry}") ``` ### get_process_instance_sequence_flows() ```python def get_process_instance_sequence_flows(process_instance_key, *, consistency=None, **kwargs) ``` Get sequence flows > Get sequence flows taken by the process instance. **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------ | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessInstanceSequenceFlowsQueryResult - **Return type:** ProcessInstanceSequenceFlowsQueryResult #### Examples **Get process instance sequence flows:** ```python def get_process_instance_sequence_flows_example( process_instance_key: ProcessInstanceKey, ) -> None: client = CamundaClient() result = client.get_process_instance_sequence_flows( process_instance_key=process_instance_key, ) if not isinstance(result.items, Unset): for flow in result.items: print(f"Sequence flow: {flow}") ``` ### get_process_instance_statistics() ```python def get_process_instance_statistics(process_instance_key, *, consistency=None, **kwargs) ``` Get element instance statistics > Get statistics about elements by the process instance key. **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------ | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessInstanceElementStatisticsQueryResult - **Return type:** ProcessInstanceElementStatisticsQueryResult #### Examples **Get process instance statistics:** ```python def get_process_instance_statistics_example( process_instance_key: ProcessInstanceKey, ) -> None: client = CamundaClient() result = client.get_process_instance_statistics( process_instance_key=process_instance_key, ) if not isinstance(result.items, Unset): for stat in result.items: print(f"Element: {stat.element_id}, Active: {stat.active}") ``` ### get_process_instance_statistics_by_definition() ```python def get_process_instance_statistics_by_definition(*, data, consistency=None, **kwargs) ``` Get process instance statistics by definition > Returns statistics for active process instances with incidents, grouped by process > definition. The result set is scoped to a specific incident error hash code, which must be > provided as a filter in the request body. **Parameters:** | Parameter | Type | Description | | ------------- | ---------------------------------------------------- | ----------- | | `data` | `IncidentProcessInstanceStatisticsByDefinitionQuery` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** IncidentProcessInstanceStatisticsByDefinitionQueryResult - **Return type:** IncidentProcessInstanceStatisticsByDefinitionQueryResult #### Examples **Get instance statistics by definition:** ```python def get_process_instance_statistics_by_definition_example() -> None: client = CamundaClient() result = client.get_process_instance_statistics_by_definition( data=IncidentProcessInstanceStatisticsByDefinitionQuery( filter_=IncidentProcessInstanceStatisticsByDefinitionQueryFilter( error_hash_code=12345, ), ), ) if not isinstance(result.items, Unset): for stat in result.items: print(f"Definition: {stat.process_definition_key}") ``` ### get_process_instance_statistics_by_error() ```python def get_process_instance_statistics_by_error(*, data=, consistency=None, **kwargs) ``` Get process instance statistics by error > Returns statistics for active process instances that currently have active incidents, > grouped by incident error hash code. **Parameters:** | Parameter | Type | Description | | ------------- | ---------------------------------------------------------- | ----------- | | `data` | `IncidentProcessInstanceStatisticsByErrorQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** IncidentProcessInstanceStatisticsByErrorQueryResult - **Return type:** IncidentProcessInstanceStatisticsByErrorQueryResult #### Examples **Get instance statistics by error:** ```python def get_process_instance_statistics_by_error_example() -> None: client = CamundaClient() result = client.get_process_instance_statistics_by_error( data=IncidentProcessInstanceStatisticsByErrorQuery(), ) if not isinstance(result.items, Unset): for stat in result.items: print(f"Error: {stat.error_message}") ``` ### get_process_instance_wait_state_statistics() ```python def get_process_instance_wait_state_statistics(process_instance_key, *, consistency=None, **kwargs) ``` Get wait state statistics > Get statistics about waiting element instances by the process instance key, grouped by element id. **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------ | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessInstanceWaitStateStatisticsQueryResult - **Return type:** ProcessInstanceWaitStateStatisticsQueryResult #### Examples **Get process instance wait state statistics:** ```python def get_process_instance_wait_state_statistics_example( process_instance_key: ProcessInstanceKey, ) -> None: client = CamundaClient() result = client.get_process_instance_wait_state_statistics( process_instance_key=process_instance_key, ) for stat in result.items: print(f"Element: {stat.element_id}, Waiting: {stat.waiting_count}") ``` ### get_resource() ```python def get_resource(resource_key, *, consistency=None, **kwargs) ``` 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. ::: - **resource_key**: The system-assigned key for this resource. ```` * **Raises:** * **errors.NotFoundError** – If the response status code is 404. A resource with the given key was not found. * **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. * **errors.UnexpectedStatus** – If the response status code is not documented. * **httpx.TimeoutException** – If the request takes longer than Client.timeout. * **Returns:** ResourceResult **Parameters:** | Parameter | Type | Description | | --- | --- | --- | | `resource_key` | `str` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | * **Return type:** ResourceResult #### Examples **Get a resource:** ```python def get_resource_example() -> None: client = CamundaClient() result = client.get_resource(resource_key="123456") print(f"Resource: {result.resource_name}") ```` ### get_resource_content() ```python def get_resource_content(resource_key, *, consistency=None, **kwargs) ``` Get RPA resource content (deprecated) > **Deprecated** — use /resources/{resourceKey}/content/binary instead, which supports all > resource types and returns content as binary (octet-stream). > > Returns the content of a deployed RPA resource as JSON. :::info This endpoint only supports RPA resources. For generic resource content in binary format, use the /resources/{resourceKey}/content/binary endpoint. ::: - **resource_key**: The system-assigned key for this resource. ```` * **Raises:** * **errors.NotFoundError** – If the response status code is 404. A resource with the given key was not found. * **errors.NotAcceptableError** – If the response status code is 406. The resource exists but is not an RPA resource. * **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. * **errors.UnexpectedStatus** – If the response status code is not documented. * **httpx.TimeoutException** – If the request takes longer than Client.timeout. * **Returns:** GetResourceContentResponse200 **Parameters:** | Parameter | Type | Description | | --- | --- | --- | | `resource_key` | `str` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | * **Return type:** GetResourceContentResponse200 #### Examples **Get resource content:** ```python def get_resource_content_example() -> None: client = CamundaClient() content = client.get_resource_content(resource_key="123456") print(f"Content: {content}") ```` ### get_resource_content_binary() ```python def get_resource_content_binary(resource_key, *, consistency=None, **kwargs) ``` Get resource content as binary > Returns the content of a deployed resource in binary format (octet-stream). :::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. ::: - **resource_key**: The system-assigned key for this resource. ```` * **Raises:** * **errors.NotFoundError** – If the response status code is 404. A resource with the given key was not found. * **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. * **errors.UnexpectedStatus** – If the response status code is not documented. * **httpx.TimeoutException** – If the request takes longer than Client.timeout. * **Returns:** File **Parameters:** | Parameter | Type | Description | | --- | --- | --- | | `resource_key` | `str` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | * **Return type:** File #### Examples **Get resource content as binary:** ```python def get_resource_content_binary_example() -> None: client = CamundaClient() content = client.get_resource_content_binary(resource_key="123456") print(f"Binary content size: {len(content.payload.read())}") ```` ### get_restore_status() ```python def get_restore_status(**kwargs) ``` Get the status of the restore that is currently in progress > Returns the status of the restore that is currently in progress, reported per broker and per > partition. There is at most one restore in flight at any time. Once the restore has finished this > endpoint returns 404; the per-partition detail is not retained after completion. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. No restore is currently in progress. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RestoreStatusResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** RestoreStatusResponse #### Examples **Get restore status:** ```python def get_restore_status_example() -> None: client = CamundaClient() result = client.get_restore_status() print(f"Restore status: {result.status}") ``` ### get_role() ```python def get_role(role_id, *, consistency=None, **kwargs) ``` Get role > Get a role by its ID. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RoleResult - **Return type:** RoleResult #### Examples **Get a role:** ```python def get_role_example(role_id: RoleId) -> None: client = CamundaClient() result = client.get_role(role_id=role_id) print(f"Role: {result.name}") ``` ### get_runtime_backup() ```python def get_runtime_backup(backup_id, **kwargs) ``` Get runtime backup > Returns detailed status of the runtime backup with the given id. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ----------- | | `backup_id` | int) – | | The id of the backup. Must be a positive numerical value. As backups are logically ordered by their ids (ascending), each successive backup must use a higher id than the previous one. > Example: 1. - **kwargs** (_Any_) - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. A backup with the given id does not exist. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BackupInfo - **Return type:** BackupInfo #### Examples **Get a runtime backup:** ```python def get_runtime_backup_example(backup_id: int) -> None: client = CamundaClient() result = client.get_runtime_backup(backup_id=backup_id) print(f"Backup {result.backup_id} is {result.state.value}") for partition in result.details: print(f" partition {partition.partition_id}: {partition.state.value}") ``` ### get_runtime_backup_as_cluster_admin() ```python def get_runtime_backup_as_cluster_admin(backup_id, *, physical_tenant_id=, **kwargs) ``` Get a runtime backup across physical tenants > Reports what every physical tenant of the cluster, or the one named by physicalTenantId, holds for > the given backup id, plus the state aggregated over all of them. A tenant that was reached and does > not hold this backup reports DOES_NOT_EXIST, which is a successful observation rather than a > failure — so a backup only some tenants hold aggregates to INCOMPLETE, the same way a backup only > some partitions hold does within one tenant. > > The request is all-or-nothing: a physical tenant whose state cannot be read fails the whole request. > Narrow the request with physicalTenantId to read the tenants that can still be reached. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Use GET > /v2/backups/runtime/{backupId} to act as a single physical tenant. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ----------- | | `backup_id` | int) – | | The id of the backup. Must be a positive numerical value. As backups are logically ordered by their ids (ascending), each successive backup must use a higher id than the previous one. > Example: 1. - **physical_tenant_id** (_str_ _|_ _Unset_) – Example: default. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster, or every targeted physical tenant was read and none of them holds a backup with the given id. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterRuntimeBackupInfo - **Return type:** ClusterRuntimeBackupInfo #### Examples **Get a runtime backup across physical tenants:** ```python def get_runtime_backup_as_cluster_admin_example(backup_id: int) -> None: client = CamundaClient() # Returns what each physical tenant reports for the given backup id. # A tenant reporting `DOES_NOT_EXIST` is a successful observation, not an error. result = client.get_runtime_backup_as_cluster_admin(backup_id=backup_id) print(f"Cluster runtime backup {result.backup_id} is {result.state.value}") for tenant in result.physical_tenants: print(f" physical tenant {tenant.physical_tenant_id}: {tenant.state.value}") ``` ### get_runtime_backup_state() ```python def get_runtime_backup_state(**kwargs) ``` Get runtime backup state > Returns the current checkpoint and backup state of every partition of the physical > tenant. Unlike the backupRuntime actuator, this fails the whole request if the > checkpoint state or the backup ranges cannot be retrieved from any partition, instead > of silently returning an empty section. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RuntimeBackupState - **Parameters:** **kwargs** (_Any_) - **Return type:** RuntimeBackupState #### Examples **Get the runtime backup state:** ```python def get_runtime_backup_state_example() -> None: client = CamundaClient() result = client.get_runtime_backup_state() for checkpoint in result.checkpoint_states: print( f"Partition {checkpoint.partition_id} checkpoint {checkpoint.checkpoint_id}" f" at position {checkpoint.checkpoint_position}" ) for backup_range in result.ranges: print( f"Partition {backup_range.partition_id} range:" f" {backup_range.start} - {backup_range.end}" ) ``` ### get_runtime_backup_state_as_cluster_admin() ```python def get_runtime_backup_state_as_cluster_admin(*, physical_tenant_id=, **kwargs) ``` Get runtime backup state across physical tenants > Reports the checkpoint and backup state of every partition of every physical tenant of the cluster, > or of the one named by physicalTenantId, grouped by physical tenant. Checkpoint ids and log > positions only mean anything within one physical tenant’s partitions, so nothing is aggregated > across tenants. > > The request is all-or-nothing: a physical tenant whose state cannot be read fails the whole request > rather than contributing an empty section, which an operator making a delete or restore decision > could not tell apart from “nothing to report yet”. Narrow the request with physicalTenantId to > read the tenants that can still be reached. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Use GET > /v2/backups/runtime/state to act as a single physical tenant. **Parameters:** | Parameter | Type | Description | | -------------------- | ---------------- | ----------------- | | `physical_tenant_id` | `str` \| `Unset` | Example: default. | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterRuntimeBackupState - **Return type:** ClusterRuntimeBackupState #### Examples **Get runtime backup state across physical tenants:** ```python def get_runtime_backup_state_as_cluster_admin_example() -> None: client = CamundaClient() # Reports the checkpoint and backup state of every partition of every # physical tenant. Use `physical_tenant_id` to narrow to a single tenant. result = client.get_runtime_backup_state_as_cluster_admin() for tenant in result.physical_tenants: print(f"Physical tenant {tenant.physical_tenant_id}:") for checkpoint in tenant.state.checkpoint_states: print( f" partition {checkpoint.partition_id} checkpoint" f" {checkpoint.checkpoint_id}" ) ``` ### get_start_process_form() ```python def get_start_process_form(process_definition_key, *, consistency=None, **kwargs) ``` Get process start form > Get the start form of a process. > > Note that this endpoint will only return linked forms. This endpoint does not support embedded > forms. **Parameters:** | Parameter | Type | Description | | ------------------------ | ------------------------------ | ---------------------------------------------------------------------------------- | | `process_definition_key` | `str` | System-generated key for a deployed process definition. Example: 2251799813686749. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** FormResult - **Return type:** FormResult #### Examples **Get start process form:** ```python def get_start_process_form_example( process_definition_key: ProcessDefinitionKey, ) -> None: client = CamundaClient() result = client.get_start_process_form( process_definition_key=process_definition_key, ) print(f"Form: {result.form_key}") ``` ### get_status() ```python def get_status(**kwargs) ``` Get physical tenant status - **Raises:** - **errors.ServiceUnavailableError** – If the response status code is 503. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Parameters:** **kwargs** (_Any_) - **Return type:** None #### Examples **Check cluster status:** ```python def get_status_example() -> None: client = CamundaClient() client.get_status() print("Cluster is healthy") ``` ### get_system_configuration() ```python def get_system_configuration(**kwargs) ``` 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. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** SystemConfigurationResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** SystemConfigurationResponse #### Examples **Get system configuration:** ```python def get_system_configuration_example() -> None: client = CamundaClient() result = client.get_system_configuration() print(f"System config: {result}") ``` ### get_tenant() ```python def get_tenant(tenant_id, *, consistency=None, **kwargs) ``` Get tenant > Retrieves a single tenant by tenant ID. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Tenant not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TenantResult - **Return type:** TenantResult #### Examples **Get a tenant:** ```python def get_tenant_example(tenant_id: TenantId) -> None: client = CamundaClient() result = client.get_tenant(tenant_id=tenant_id) print(f"Tenant: {result.name}") ``` ### get_tenant_cluster_variable() ```python def get_tenant_cluster_variable(tenant_id, name, *, consistency=None, **kwargs) ``` Get a tenant-scoped cluster variable > Get a tenant-scoped cluster variable. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `name` | `str` | The name of a cluster variable. Unique within its scope (global or tenant- specific). Example: feature-flag-checkout. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Cluster variable not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterVariableResult - **Return type:** ClusterVariableResult #### Examples **Get a tenant cluster variable:** ```python def get_tenant_cluster_variable_example(tenant_id: TenantId, name: ClusterVariableName) -> None: client = CamundaClient() result = client.get_tenant_cluster_variable( tenant_id=tenant_id, name=name, ) print(f"Variable: {result.name} = {result.value}") ``` ### get_topology() ```python def get_topology(**kwargs) ``` Get cluster topology > Obtains the current topology of the cluster the gateway is part of. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TopologyResponse - **Parameters:** **kwargs** (_Any_) - **Return type:** TopologyResponse #### Examples **Get cluster topology:** ```python def get_topology_example() -> None: client = CamundaClient() result = client.get_topology() print(f"Topology: {result}") ``` ### get_usage_metrics() ```python def get_usage_metrics(*, start_time, end_time, tenant_id=, with_tenants=, consistency=None, **kwargs) ``` Get usage metrics > Retrieve the usage metrics based on given criteria. **Parameters:** | Parameter | Type | Description | | -------------- | ------------------------------ | --------------------------------------------------------------- | | `start_time` | `datetime.datetime` | Example: 2025-06-07T13:14:15Z. | | `end_time` | `datetime.datetime` | Example: 2025-06-07T13:14:15Z. | | `tenant_id` | `str` \| `Unset` | The unique identifier of the tenant. Example: customer-service. | | `with_tenants` | `bool` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** UsageMetricsResponse - **Return type:** UsageMetricsResponse #### Examples **Get usage metrics:** ```python def get_usage_metrics_example() -> None: client = CamundaClient() result = client.get_usage_metrics( start_time=datetime.datetime(2024, 1, 1), end_time=datetime.datetime(2024, 12, 31), ) print(f"Metrics: {result}") ``` ### get_user() ```python def get_user(username, *, consistency=None, **kwargs) ``` Get user > Get a user by its username. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | -------------------------------------------- | | `username` | `str` | The unique name of a user. Example: swillis. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The user with the given username was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** UserResult - **Return type:** UserResult #### Examples **Get a user:** ```python def get_user_example(username: Username) -> None: client = CamundaClient() result = client.get_user(username=username) print(f"User: {result.username}") ``` ### get_user_task() ```python def get_user_task(user_task_key, *, consistency=None, **kwargs) ``` Get user task > Get the user task by the user task key. **Parameters:** | Parameter | Type | Description | | --------------- | ------------------------------ | ------------------------------------- | | `user_task_key` | `str` | System-generated key for a user task. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The user task with the given key was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** UserTaskResult - **Return type:** UserTaskResult #### Examples **Get a user task:** ```python def get_user_task_example(user_task_key: UserTaskKey) -> None: client = CamundaClient() task = client.get_user_task(user_task_key=user_task_key) print(f"Task: {task.user_task_key}") ``` ### get_user_task_form() ```python def get_user_task_form(user_task_key, *, consistency=None, **kwargs) ``` Get user task form > Get the form of a user task. > > Note that this endpoint will only return linked forms. This endpoint does not support embedded > forms. **Parameters:** | Parameter | Type | Description | | --------------- | ------------------------------ | ------------------------------------- | | `user_task_key` | `str` | System-generated key for a user task. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** FormResult - **Return type:** FormResult #### Examples **Get a user task form:** ```python def get_user_task_form_example(user_task_key: UserTaskKey) -> None: client = CamundaClient() result = client.get_user_task_form( user_task_key=user_task_key, ) print(f"Form: {result.form_key}") ``` ### get_variable() ```python def get_variable(variable_key, *, consistency=None, **kwargs) ``` Get variable > Get a variable by its key. > > This endpoint returns both process-level and local (element-scoped) variables. > The variable’s scopeKey indicates whether it’s a process-level variable or scoped to a > specific element instance. **Parameters:** | Parameter | Type | Description | | -------------- | ------------------------------ | --------------------------------------------------------------- | | `variable_key` | `str` | System-generated key for a variable. Example: 2251799813683287. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** VariableResult - **Return type:** VariableResult #### Examples **Get a variable:** ```python def get_variable_example(variable_key: VariableKey) -> None: client = CamundaClient() result = client.get_variable( variable_key=variable_key, ) print(f"Variable: {result.name} = {result.value}") ``` ### list_history_backups() ```python def list_history_backups(*, prefix=, verbose=, **kwargs) ``` List history backups > Returns a list of all available history backups of the physical tenant, with their state > and additional info, most recent first by snapshot start time. > > Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. **Parameters:** | Parameter | Type | Description | | --------- | ----------------- | ----------- | | `prefix` | `str` \| Unset) – | | A prefix of a backup id, followed by a single ‘\*’ as a wildcard, matching any backup id starting with the given prefix. > Example: 17567\*. - **verbose** (_bool_ _|_ _Unset_) - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. The request is forbidden for one of three reasons: the authenticated caller lacks the required BACKUP permission; the cluster’s secondary storage is neither Elasticsearch nor OpenSearch and therefore cannot serve history backups; or the physical tenant’s snapshot repository is absent from the store — configured under a name the store does not have, or not configured at all. The problem detail says which applies. The latter two are deployment faults the caller cannot correct by changing its request. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** list[Any] - **Return type:** list[_Any_] #### Examples **List history backups:** ```python def list_history_backups_example() -> None: client = CamundaClient() # `prefix` is a backup id prefix followed by a single `*` wildcard. result = client.list_history_backups(prefix="17567*") for backup in result: print(f"History backup: {backup}") ``` ### list_history_backups_as_cluster_admin() ```python def list_history_backups_as_cluster_admin(*, physical_tenant_id=, prefix=, verbose=, **kwargs) ``` List history backups across physical tenants > Lists the history backups of every physical tenant of the cluster, or of the one named by > physicalTenantId, grouped by backup id. A backup id that only some physical tenants hold is a > supported outcome rather than a degraded one, so only the tenants that hold it are listed under it. > > The request is all-or-nothing: a physical tenant whose backups cannot be read fails the whole > request rather than silently dropping out of the listing. Narrow the request with physicalTenantId > to list the backups of the tenants that can still be read. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Only available on clusters > whose secondary storage is Elasticsearch or OpenSearch. Use GET /v2/backups/history to act as a > single physical tenant. **Parameters:** | Parameter | Type | Description | | -------------------- | ----------------- | ----------------- | | `physical_tenant_id` | `str` \| `Unset` | Example: default. | | `prefix` | `str` \| Unset) – | | A prefix of a backup id, followed by a single ‘\*’ as a wildcard, matching any backup id starting with the given prefix. > Example: 17567\*. - **verbose** (_bool_ _|_ _Unset_) - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. The cluster’s secondary storage is neither Elasticsearch nor OpenSearch and therefore cannot serve history backups, or a targeted physical tenant’s snapshot repository is absent from the store — configured under a name the store does not have, or not configured at all. Both are deployment faults the caller cannot correct by changing its request; narrow the request with physicalTenantId to work with the tenants whose repository is usable. Unlike the per-physical-tenant backup endpoints, the cluster-admin surface performs no fine-grained authorization, so a missing BACKUP permission is never the reason. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** list[Any] - **Return type:** list[_Any_] #### Examples **List history backups across physical tenants:** ```python def list_history_backups_as_cluster_admin_example() -> None: client = CamundaClient() # Lists backups across every physical tenant. Pass `physical_tenant_id` to # restrict to one tenant. `prefix` filters to backup ids starting with the # given value (end with a `*` wildcard, e.g. "17567*"). backups = client.list_history_backups_as_cluster_admin(prefix="17567*") for backup in backups: print(f"History backup: {backup}") ``` ### list_runtime_backups() ```python def list_runtime_backups(*, prefix=, **kwargs) ``` List runtime backups > Returns a list of all available runtime backups of the physical tenant, with their > state and additional info, sorted in descending order of backupId. **Parameters:** | Parameter | Type | Description | | --------- | ----------------- | ----------- | | `prefix` | `str` \| Unset) – | | A prefix of a backup id, followed by a single ‘\*’ as a wildcard, matching any backup id starting with the given prefix. > Example: 17567\*. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** list[Any] - **Return type:** list[_Any_] #### Examples **List runtime backups:** ```python def list_runtime_backups_example() -> None: client = CamundaClient() # `prefix` is a backup id prefix followed by a single `*` wildcard. result = client.list_runtime_backups(prefix="17567*") for backup in result: print(f"Runtime backup: {backup}") ``` ### list_runtime_backups_as_cluster_admin() ```python def list_runtime_backups_as_cluster_admin(*, physical_tenant_id=, prefix=, **kwargs) ``` List runtime backups across physical tenants > Lists the runtime backups of every physical tenant of the cluster, or of the one named by > physicalTenantId, grouped by backup id. Every group reports every targeted tenant, including the > ones holding nothing for that id, so a backup only some tenants hold aggregates to INCOMPLETE here > exactly as it does when looked up directly — the state of a listed group can be trusted to say > whether the cluster can be restored from it. A backup id that only some physical tenants hold is a > supported outcome rather than a degraded one; tenants that generate their own backup ids never share > one, so in that mode each backup forms its own group and the other tenants report DOES_NOT_EXIST > under it. > > The request is all-or-nothing: a physical tenant whose backups cannot be read fails the whole > request rather than silently dropping out of the listing. Narrow the request with physicalTenantId > to list the backups of the tenants that can still be read. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Use GET > /v2/backups/runtime to act as a single physical tenant. **Parameters:** | Parameter | Type | Description | | -------------------- | ----------------- | ----------------- | | `physical_tenant_id` | `str` \| `Unset` | Example: default. | | `prefix` | `str` \| Unset) – | | A prefix of a backup id, followed by a single ‘\*’ as a wildcard, matching any backup id starting with the given prefix. > Example: 17567\*. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** list[Any] - **Return type:** list[_Any_] #### Examples **List runtime backups across physical tenants:** ```python def list_runtime_backups_as_cluster_admin_example() -> None: client = CamundaClient() # Lists backups across every physical tenant. Pass `physical_tenant_id` to # restrict to one tenant. `prefix` filters to backup ids starting with the # given value (end with a `*` wildcard, e.g. "17567*"). backups = client.list_runtime_backups_as_cluster_admin(prefix="17567*") for backup in backups: print(f"Runtime backup: {backup}") ``` ### list_secrets() ```python def list_secrets(*, data=, **kwargs) ``` List secrets > List the camunda.secrets.\* references known for the caller’s physical tenant. > > Only references the caller holds SECRET:READ on are returned. This endpoint never > returns secret values, only the reference names. > > The references are read from the secret stores configured for the caller’s physical tenant. > A store may hold names outside the reference name charset (for example one containing a > dot); those are omitted, since /secrets/resolve would reject them and no permission can > be granted on them. > > A returned reference is usable verbatim with /secrets/resolve. In a FEEL expression, > however, a name that is not a bare identifier has to be backtick-escaped, since FEEL reads > a bare dash as the minus operator: a listed camunda.secrets.db-password is written > \`\` =camunda.secrets.\`db-password\` \`\` in a BPMN input mapping. **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | `SecretListRequest` \| `Unset` | Reserved for future filtering options. Currently takes no properties. The request body is optional: omitting it (or sending an empty object) applies no filters. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** SecretListResult - **Return type:** SecretListResult #### Examples **List secrets:** ```python def list_secrets_example() -> None: client = CamundaClient() # Lists the `camunda.secrets.*` references visible to the caller's physical # tenant. Only references the caller holds `SECRET:READ` on are returned, and # the response carries reference names only -- never the secret values. # The request body is optional; an empty one applies no filters. result = client.list_secrets(data=SecretListRequest()) for reference in result.references: print(f"Known secret reference: {reference}") ``` ### migrate_process_instance() ```python def migrate_process_instance(process_instance_key, *, data, **kwargs) ``` Migrate process instance > Migrates a process instance to a new process definition. > > This request can contain multiple mapping instructions to define mapping between the active > process instance’s elements and target process definition elements. > > Use this to upgrade a process instance to a new version of a process or to > a different process definition, e.g. to keep your running instances up-to-date with the > latest process improvements. **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `data` | `ProcessInstanceMigrationInstruction` | The migration instructions describe how to migrate a process instance from one process definition to another. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The process instance is not found. - **errors.ConflictError** – If the response status code is 409. The process instance migration failed. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Migrate a process instance:** ```python def migrate_process_instance_example( process_instance_key: ProcessInstanceKey, target_process_definition_key: ProcessDefinitionKey, source_element_id: ElementId, target_element_id: ElementId, ) -> None: client = CamundaClient() client.migrate_process_instance( process_instance_key=process_instance_key, data=ProcessInstanceMigrationInstruction( target_process_definition_key=target_process_definition_key, mapping_instructions=[ MigrateProcessInstanceMappingInstruction( source_element_id=source_element_id, target_element_id=target_element_id, ), ], ), ) ``` ### migrate_process_instances_batch_operation() ```python def migrate_process_instances_batch_operation(*, data, **kwargs) ``` Migrate process instances (batch) > Migrate multiple process instances. > > Since only process instances with ACTIVE state can be migrated, any given > filters for state are ignored and overridden during this batch operation. > This is done asynchronously, the progress can be tracked using the batchOperationKey from the > response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------- | ----------------------------------------------- | ----------- | | `data` | `ProcessInstanceMigrationBatchOperationRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The process instance batch operation failed. More details are provided in the response body. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationCreatedResult - **Return type:** BatchOperationCreatedResult #### Examples **Migrate process instances in batch:** ```python def migrate_process_instances_batch_operation_example(target_process_definition_key: ProcessDefinitionKey, source_element_id: ElementId, target_element_id: ElementId) -> None: client = CamundaClient() result = client.migrate_process_instances_batch_operation( data=ProcessInstanceMigrationBatchOperationRequest( filter_=ProcessInstanceCancellationBatchOperationRequestFilter(), migration_plan=ProcessInstanceMigrationBatchOperationRequestMigrationPlan( target_process_definition_key=target_process_definition_key, mapping_instructions=[ MigrateProcessInstanceMappingInstruction( source_element_id=source_element_id, target_element_id=target_element_id, ), ], ), ), ) print(f"Batch operation key: {result.batch_operation_key}") ``` ### modify_process_instance() ```python def modify_process_instance(process_instance_key, *, data, **kwargs) ``` Modify process instance > Modifies a running process instance. > > This request can contain multiple instructions to activate an element of the process or > to terminate an active instance of an element. > > Use this to repair a process instance that is stuck on an element or took an unintended path. > For example, because an external system is not available or doesn’t respond as expected. **Parameters:** | Parameter | Type | Description | | ---------------------- | ---------------------------------------- | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `data` | `ProcessInstanceModificationInstruction` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The process instance is not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Modify a process instance:** ```python def modify_process_instance_example(process_instance_key: ProcessInstanceKey) -> None: client = CamundaClient() client.modify_process_instance( process_instance_key=process_instance_key, data=ProcessInstanceModificationInstruction(), ) ``` ### modify_process_instances_batch_operation() ```python def modify_process_instances_batch_operation(*, data, **kwargs) ``` Modify process instances (batch) > Modify multiple process instances. > > Since only process instances with ACTIVE state can be modified, any given > filters for state are ignored and overridden during this batch operation. > In contrast to single modification operation, it is not possible to add variable instructions or > modify by element key. > It is only possible to use the element id of the source and target. > This is done asynchronously, the progress can be tracked using the batchOperationKey from the > response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | `ProcessInstanceModificationBatchOperationRequest` | The process instance filter to define on which process instances tokens should be moved, and new element instances should be activated or terminated. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The process instance batch operation failed. More details are provided in the response body. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationCreatedResult - **Return type:** BatchOperationCreatedResult #### Examples **Modify process instances in batch:** ```python def modify_process_instances_batch_operation_example(source_element_id: ElementId, target_element_id: ElementId) -> None: client = CamundaClient() result = client.modify_process_instances_batch_operation( data=ProcessInstanceModificationBatchOperationRequest( filter_=ProcessInstanceCancellationBatchOperationRequestFilter(), move_instructions=[ ProcessInstanceModificationMoveBatchOperationInstruction( source_element_id=source_element_id, target_element_id=target_element_id, ), ], ), ) print(f"Batch operation key: {result.batch_operation_key}") ``` ### pause_cluster_exporting() ```python def pause_cluster_exporting(*, soft=, **kwargs) ``` Pause exporting across the whole cluster > Pauses exporting on every physical tenant of the cluster in one call. With soft=true, every > physical tenant is soft-paused instead. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. **Parameters:** | Parameter | Type | Description | | --------- | ----------------- | ----------- | | `soft` | `bool` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Pause exporting across the whole cluster:** ```python def pause_cluster_exporting_example() -> None: client = CamundaClient() # Pauses exporting on every physical tenant in one call. # With `soft=True` the position is not committed, so the log is not compacted, # which is the right mode for taking a consistent backup without stopping # real processing work. client.pause_cluster_exporting(soft=True) ``` ### pause_exporting() ```python def pause_exporting(*, soft=, **kwargs) ``` Pause exporting > Pauses exporting on all partitions of the physical tenant. While paused, exported records > are not committed, so the log is not compacted for the affected partitions. > > With soft=true, exporting continues to run but its position is not committed, so the > state after resuming is identical to a hard pause; use this variant when exporting must > keep progressing (e.g. to avoid falling behind) while still preventing log compaction, > such as during a backup. **Parameters:** | Parameter | Type | Description | | --------- | ----------------- | ----------- | | `soft` | `bool` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Pause exporting:** ```python def pause_exporting_example() -> None: client = CamundaClient() # With `soft=True` exporting keeps running but its position is not committed, # so the log is still not compacted. Use it when exporting must keep # progressing -- for example while a backup is taken. client.pause_exporting(soft=True) ``` ### pin_clock() ```python def pin_clock(*, data, **kwargs) ``` 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. **Parameters:** | Parameter | Type | Description | | --------- | ----------------- | ----------- | | `data` | `ClockPinRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Pin the cluster clock:** ```python def pin_clock_example() -> None: client = CamundaClient() client.pin_clock( data=ClockPinRequest( timestamp=1700000000000, ), ) ``` ### publish_message() ```python def publish_message(*, data, **kwargs) ``` Publish message > Publishes a single message. > > Messages are published to specific partitions computed from their correlation keys. > Messages can be buffered. > The endpoint does not wait for a correlation result. > Use the message correlation endpoint for such use cases. **Parameters:** | Parameter | Type | Description | | --------- | --------------------------- | ----------- | | `data` | `MessagePublicationRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** MessagePublicationResult - **Return type:** MessagePublicationResult #### Examples **Publish a message:** ```python def publish_message_example() -> None: client = CamundaClient() result = client.publish_message( data=MessagePublicationRequest( name="order-created", correlation_key="order-12345", time_to_live=60000, ) ) print(f"Message key: {result.message_key}") ``` ### _property_ random _: RandomSource_ the injected source when supplied, else the live one. - **Type:** Randomness behind SDK jitter ### reset_clock() ```python def reset_clock(**kwargs) ``` 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. - **Raises:** - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Parameters:** **kwargs** (_Any_) - **Return type:** None #### Examples **Reset the cluster clock:** ```python def reset_clock_example() -> None: client = CamundaClient() client.reset_clock() ``` ### resolve_incident() ```python def resolve_incident(incident_key, *, data=, **kwargs) ``` Resolve incident > Marks the incident as resolved; most likely a call to Update job will be necessary > to reset the job’s retries, followed by this call. **Parameters:** | Parameter | Type | Description | | -------------- | -------------------------------------- | --------------------------------------------------------------- | | `incident_key` | `str` | System-generated key for a incident. Example: 2251799813689432. | | `data` | `IncidentResolutionRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The incident with the incidentKey is not found. - **errors.ConflictError** – If the response status code is 409. The incident cannot be resolved due to an invalid state. For example, the associated job may have no retries left. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Resolve an incident:** ```python def resolve_incident_example(incident_key: IncidentKey) -> None: client = CamundaClient() client.resolve_incident(incident_key=incident_key) ``` ### resolve_incidents_batch_operation() ```python def resolve_incidents_batch_operation(*, data, **kwargs) ``` Resolve related incidents (batch) > Resolves multiple instances of process instances. > > Since only process instances with ACTIVE state can have unresolved incidents, any given > filters for state are ignored and overridden during this batch operation. > This is done asynchronously, the progress can be tracked using the batchOperationKey from the > response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | `data` | `ProcessInstanceIncidentResolutionBatchOperationRequest` | The process instance filter that defines which process instances should have their incidents resolved. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The process instance batch operation failed. More details are provided in the response body. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationCreatedResult - **Return type:** BatchOperationCreatedResult #### Examples **Resolve incidents in batch:** ```python def resolve_incidents_batch_operation_example() -> None: client = CamundaClient() result = client.resolve_incidents_batch_operation( data=ProcessInstanceIncidentResolutionBatchOperationRequest( filter_=ProcessInstanceCancellationBatchOperationRequestFilter(), ), ) print(f"Batch operation key: {result.batch_operation_key}") ``` ### resolve_process_instance_incidents() ```python def resolve_process_instance_incidents(process_instance_key, **kwargs) ``` Resolve related incidents > Creates a batch operation to resolve multiple incidents of a process instance. **Parameters:** | Parameter | Type | Description | | ---------------------- | ----- | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The process instance is not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationCreatedResult - **Return type:** BatchOperationCreatedResult #### Examples **Resolve process instance incidents:** ```python def resolve_process_instance_incidents_example( process_instance_key: ProcessInstanceKey, ) -> None: client = CamundaClient() result = client.resolve_process_instance_incidents( process_instance_key=process_instance_key, ) print(f"Batch operation key: {result.batch_operation_key}") ``` ### resolve_secrets() ```python def resolve_secrets(*, data, **kwargs) ``` Resolve secrets > Resolve a deduplicated batch of camunda.secrets.\* references for the caller’s > physical tenant in a single round-trip. > > Each reference is authorized and resolved independently. For valid requests, the endpoint > always responds with HTTP 200: successfully resolved references are returned in resolved, > while references that could not be resolved (for example not found, malformed or over-long, > or the caller lacks SECRET:REVEAL on that reference) are returned in errors. A failure of > one reference never fails the others. Only structurally invalid requests are rejected with > HTTP 400: a missing or non-array references field, more than 20 references, or a null entry. > > References are resolved against the secret stores configured for the caller’s physical > tenant, served from the gateway’s secret cache when the value is already cached and read > from the store otherwise. **Parameters:** | Parameter | Type | Description | | --------- | ---------------------- | ----------- | | `data` | `SecretResolveRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** SecretResolveResult - **Return type:** SecretResolveResult #### Examples **Resolve secrets:** ```python def resolve_secrets_example() -> None: client = CamundaClient() # Hands the resolved secret to whatever needs it (an HTTP client, a DB # driver, ...) without logging it. def use_secret(value: str) -> None: ... result = client.resolve_secrets( data=SecretResolveRequest( references=[ "camunda.secrets.my_api_token", "camunda.secrets.db_password", ], ) ) # Successfully resolved references are returned in `resolved`; references that # could not be resolved are returned in `errors`, each with a typed error code. # Never log a resolved value -- it holds secret material. Pass it straight to # the consumer that needs it instead. for resolved in result.resolved: print(f"Resolved {resolved.reference} (value redacted)") use_secret(resolved.value) for error in result.errors: print(f"Failed to resolve {error.reference}: {error.code.value} - {error.message}") ``` ### restore() ```python def restore(*, data, dry_run=, **kwargs) ``` Restore from a backup > Restores the cluster from a backup. The restore is described either by a single backup ID or by a > time range (from/to) that selects the backups to restore. This endpoint is only accessible while > the cluster is in recovery mode; requests are rejected otherwise. The request is validated and > acknowledged, but the restore itself is performed asynchronously. **Parameters:** | Parameter | Type | Description | | --------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `dry_run` | `bool` \| `Unset` | | | `data` | `RestoreRequest` | Describes a restore request. Provide either a list of backup IDs or a time range (from/to) that selects the backups to restore; the two are mutually exclusive. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.ConflictError** – If the response status code is 409. The cluster is not in recovery mode, so the restore cannot be accepted. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterRestoreResponse - **Return type:** ClusterRestoreResponse #### Examples **Restore from a backup:** ```python def restore_example() -> None: client = CamundaClient() # The cluster must be in recovery mode before a restore is accepted. Provide # either a list of backup IDs (one per partition) or a time range (from/to) # that selects the backups to restore, but not both. result = client.restore( data=RestoreRequest(backup_ids=[100, 101]), ) print(f"Cluster change {result.change_id}:") for group in result.planned_changes: print(f" {group.physical_tenant_id or 'cluster-wide'}:") for operation in group.operations: mode = getattr(operation, "mode", None) suffix = f" -> {mode}" if mode else "" print(f" {operation.operation}{suffix}") ``` ### restore_as_cluster_admin() ```python def restore_as_cluster_admin(*, data, physical_tenant_id=, dry_run=, **kwargs) ``` Restore one or every physical tenant from a backup > Restores physical tenants from backups. The restore is described either by a list of backup IDs or > by a time range (from/to) that selects the backups to restore. Restores are only accepted while > the targeted physical tenants are in recovery mode; requests are rejected otherwise. The request is > validated and acknowledged, but the restore itself is performed asynchronously. > > If the physicalTenantId parameter is provided, only that physical tenant is restored and > overrides must be omitted. > > If it is not provided, every physical tenant of the cluster is restored: those named in overrides > with their own backup selection, all others with the selection at the top level of the request body. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. **Parameters:** | Parameter | Type | Description | | -------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `physical_tenant_id` | `str` \| `Unset` | Example: default. | | `dry_run` | `bool` \| `Unset` | | | `data` | `ClusterRestoreRequest` | Describes a restore request issued by a cluster admin. The backup selection at the top level applies to every targeted physical tenant, except for the ones listed in overrides. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId, or a physical tenant named in overrides, does not exist in this cluster. - **errors.ConflictError** – If the response status code is 409. A targeted physical tenant is not in recovery mode, so the restore cannot be accepted. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterRestoreResponse - **Return type:** ClusterRestoreResponse #### Examples **Restore physical tenants from backup as cluster admin:** ```python def restore_as_cluster_admin_example() -> None: client = CamundaClient() # The targeted physical tenants must be in recovery mode before a restore is # accepted. Provide either backup_ids (one per partition) or a time range # (from_/to), but not both. # # Omit physical_tenant_id to restore every physical tenant. Supply it to # scope the restore to a single tenant (overrides must then be omitted). result = client.restore_as_cluster_admin( data=ClusterRestoreRequest( backup_ids=[100, 101], ), dry_run=True, ) print(f"Cluster change {result.change_id}:") for group in result.planned_changes: print(f" {group.physical_tenant_id or 'cluster-wide'}:") for operation in group.operations: mode = getattr(operation, "mode", None) suffix = f" -> {mode}" if mode else "" print(f" {operation.operation}{suffix}") ``` ### resume_batch_operation() ```python def resume_batch_operation(batch_operation_key, *, data=, **kwargs) ``` Resume Batch operation > Resumes a suspended batch operation. > > This is done asynchronously, the progress can be tracked using the batch operation status endpoint > (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------------------- | ---------------- | ----------------------------------------------------------------------- | | `batch_operation_key` | `str` | System-generated key for an batch operation. Example: 2251799813684321. | | `data` | `Any` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The batch operation was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Resume a batch operation:** ```python def resume_batch_operation_example(batch_operation_key: BatchOperationKey) -> None: client = CamundaClient() client.resume_batch_operation( batch_operation_key=batch_operation_key, ) ``` ### resume_cluster_exporting() ```python def resume_cluster_exporting(**kwargs) ``` Resume exporting across the whole cluster > Resumes exporting on every physical tenant of the cluster in one call, after a pause or soft pause. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Parameters:** **kwargs** (_Any_) - **Return type:** None #### Examples **Resume exporting across the whole cluster:** ```python def resume_cluster_exporting_example() -> None: client = CamundaClient() # Resumes exporting on every physical tenant after a pause or soft pause. client.resume_cluster_exporting() ``` ### resume_exporting() ```python def resume_exporting(**kwargs) ``` Resume exporting > Resumes exporting on all partitions of the physical tenant after a pause or soft pause. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Parameters:** **kwargs** (_Any_) - **Return type:** None #### Examples **Resume exporting:** ```python def resume_exporting_example() -> None: client = CamundaClient() client.resume_exporting() ``` ### resume_process_instance() ```python def resume_process_instance(process_instance_key, *, data=, **kwargs) ``` Resume process instance > Resumes a suspended process instance, returning it to the ACTIVE state and continuing processing. > > Only process instances in the SUSPENDED state can be resumed. > A child process instance can be resumed independently of its parent or root process > instance; resumption does not cascade to or from related instances. **Parameters:** | Parameter | Type | Description | | ---------------------- | --------------------------------------------------- | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `data` | `None` \| `ResumeProcessInstanceRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The process instance is not found. - **errors.ConflictError** – If the response status code is 409. The process instance is not in the SUSPENDED state and cannot be resumed. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Resume a process instance:** ```python def resume_process_instance_example(process_instance_key: ProcessInstanceKey) -> None: client = CamundaClient() client.resume_process_instance( process_instance_key=process_instance_key, ) ``` ### resume_process_instances_batch_operation() ```python def resume_process_instances_batch_operation(*, data, **kwargs) ``` Resume process instances (batch) > Resumes multiple suspended process instances. > > Any given filter for state or parentProcessInstanceKey is ignored and overridden, as only > SUSPENDED process instances can be resumed and resumption does not cascade between parent > and child instances, so child instances are resumed independently of their parent or root > instance. > This is done asynchronously, the progress can be tracked using the batchOperationKey from the > response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------------------------ | ----------------------------------------------------------------------------------- | | `data` | `ProcessInstanceResumptionBatchOperationRequest` | The process instance filter that defines which process instances should be resumed. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The process instance batch operation failed. More details are provided in the response body. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationCreatedResult - **Return type:** BatchOperationCreatedResult #### Examples **Resume process instances in batch:** ```python def resume_process_instances_batch_operation_example() -> None: client = CamundaClient() result = client.resume_process_instances_batch_operation( data=ProcessInstanceResumptionBatchOperationRequest( filter_=ProcessInstanceCancellationBatchOperationRequestFilter(), ), ) print(f"Batch operation key: {result.batch_operation_key}") ``` ### search_agent_definitions() ```python def search_agent_definitions(*, data=, consistency=None, **kwargs) ``` Search agent definitions > Search for agent definitions based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | --------------------------------------- | -------------------------------- | | `data` | `AgentDefinitionSearchQuery` \| `Unset` | Agent definition search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AgentDefinitionSearchQueryResult - **Return type:** AgentDefinitionSearchQueryResult #### Examples **Search agent definitions:** ```python def search_agent_definitions_example() -> None: client = CamundaClient() result = client.search_agent_definitions(data=AgentDefinitionSearchQuery()) for agent_definition in result.items: print(f"Agent definition key: {agent_definition.agent_definition_key}") ``` ### search_agent_instance_history() ```python def search_agent_instance_history(agent_instance_key, *, data=, consistency=None, **kwargs) ``` Search agent instance history > Searches the conversation history of an agent instance. Committed items > are returned by default. **Parameters:** | Parameter | Type | Description | | -------------------- | -------------------------------------------- | ---------------------------------------------------------------------- | | `agent_instance_key` | `str` | System-generated key for an agent instance. Example: 4503599627370496. | | `data` | `AgentInstanceHistorySearchQuery` \| `Unset` | Agent instance history search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The agent instance with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AgentInstanceHistorySearchQueryResult - **Return type:** AgentInstanceHistorySearchQueryResult #### Examples **Search agent instance history:** ```python def search_agent_instance_history_example(agent_instance_key: AgentInstanceKey) -> None: client = CamundaClient() result = client.search_agent_instance_history( agent_instance_key=agent_instance_key, data=AgentInstanceHistorySearchQuery(), ) print(f"Found {len(result.items)} history items") ``` ### search_agent_instances() ```python def search_agent_instances(*, data=, consistency=None, **kwargs) ``` Search agent instances > Search for agent instances based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------- | ------------------------------ | | `data` | `AgentInstanceSearchQuery` \| `Unset` | Agent instance search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AgentInstanceSearchQueryResult - **Return type:** AgentInstanceSearchQueryResult #### Examples **Search agent instances:** ```python def search_agent_instances_example() -> None: client = CamundaClient() result = client.search_agent_instances(data=AgentInstanceSearchQuery()) if not isinstance(result.items, Unset): for agent_instance in result.items: print(f"Agent instance key: {agent_instance.agent_instance_key}") ``` ### search_audit_logs() ```python def search_audit_logs(*, data=, consistency=None, **kwargs) ``` Search audit logs > Search for audit logs based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | --------------------------------------- | ------------------------- | | `data` | `AuditLogSearchQueryRequest` \| `Unset` | Audit log search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AuditLogSearchQueryResult - **Return type:** AuditLogSearchQueryResult #### Examples **Search audit logs:** ```python def search_audit_logs_example() -> None: client = CamundaClient() result = client.search_audit_logs( data=AuditLogSearchQueryRequest(), ) if not isinstance(result.items, Unset): for log in result.items: print(f"Audit log: {log.audit_log_key}") ``` ### search_authorizations() ```python def search_authorizations(*, data=, consistency=None, **kwargs) ``` Search authorizations > Search for authorizations based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------- | ----------- | | `data` | `AuthorizationSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AuthorizationSearchResult - **Return type:** AuthorizationSearchResult #### Examples **Search authorizations:** ```python def search_authorizations_example() -> None: client = CamundaClient() result = client.search_authorizations( data=AuthorizationSearchQuery(), ) if not isinstance(result.items, Unset): for auth in result.items: print(f"Authorization: {auth.authorization_key}") ``` ### search_batch_operation_items() ```python def search_batch_operation_items(*, data=, consistency=None, **kwargs) ``` Search batch operation items > Search for batch operation items based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------ | ------------------------------------ | | `data` | `BatchOperationItemSearchQuery` \| `Unset` | Batch operation item search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationItemSearchQueryResult - **Return type:** BatchOperationItemSearchQueryResult #### Examples **Search batch operation items:** ```python def search_batch_operation_items_example(batch_operation_key: BatchOperationKey) -> None: client = CamundaClient() result = client.search_batch_operation_items( batch_operation_key=batch_operation_key, data=BatchOperationItemSearchQuery(), ) if not isinstance(result.items, Unset): for item in result.items: print(f"Item: {item.item_key}") ``` ### search_batch_operations() ```python def search_batch_operations(*, data=, consistency=None, **kwargs) ``` Search batch operations > Search for batch operations based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | -------------------------------------- | ------------------------------- | | `data` | `BatchOperationSearchQuery` \| `Unset` | Batch operation search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationSearchQueryResult - **Return type:** BatchOperationSearchQueryResult #### Examples **Search batch operations:** ```python def search_batch_operations_example() -> None: client = CamundaClient() result = client.search_batch_operations( data=BatchOperationSearchQuery(), ) if not isinstance(result.items, Unset): for op in result.items: print(f"Batch operation: {op.batch_operation_key}") ``` ### search_clients_for_group() ```python def search_clients_for_group(group_id, *, data=, consistency=None, **kwargs) ``` Search group clients > Search clients assigned to a group. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------ | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `data` | `GroupClientSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GroupClientSearchResult - **Return type:** GroupClientSearchResult #### Examples **Search clients in a group:** ```python def search_clients_for_group_example(group_id: GroupId) -> None: client = CamundaClient() result = client.search_clients_for_group( group_id=group_id, ) if not isinstance(result.items, Unset): for c in result.items: print(f"Client: {c.client_id}") ``` ### search_clients_for_role() ```python def search_clients_for_role(role_id, *, data=, consistency=None, **kwargs) ``` Search role clients > Search clients with assigned role. **Parameters:** | Parameter | Type | Description | | ------------- | ----------------------------------------- | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `data` | `RoleClientSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RoleClientSearchResult - **Return type:** RoleClientSearchResult #### Examples **Search clients for a role:** ```python def search_clients_for_role_example(role_id: RoleId) -> None: client = CamundaClient() result = client.search_clients_for_role( role_id=role_id, ) if not isinstance(result.items, Unset): for c in result.items: print(f"Client: {c.client_id}") ``` ### search_clients_for_tenant() ```python def search_clients_for_tenant(tenant_id, *, data=, consistency=None, **kwargs) ``` Search clients for tenant > Retrieves a filtered and sorted list of clients for a specified tenant. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `data` | `TenantClientSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TenantClientSearchResult - **Return type:** TenantClientSearchResult #### Examples **Search clients for a tenant:** ```python def search_clients_for_tenant_example(tenant_id: TenantId) -> None: client = CamundaClient() result = client.search_clients_for_tenant( tenant_id=tenant_id, ) if not isinstance(result.items, Unset): for c in result.items: print(f"Client: {c.client_id}") ``` ### search_cluster_variables() ```python def search_cluster_variables(*, data=, truncate_values=, consistency=None, **kwargs) ``` Search for cluster variables based on given criteria. By default, long variable values in the response are truncated. **Parameters:** | Parameter | Type | Description | | ----------------- | ---------------------------------------------- | -------------------------------------- | | `truncate_values` | `bool` \| `Unset` | | | `data` | `ClusterVariableSearchQueryRequest` \| `Unset` | Cluster variable search query request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterVariableSearchQueryResult - **Return type:** ClusterVariableSearchQueryResult #### Examples **Search cluster variables:** ```python def search_cluster_variables_example() -> None: client = CamundaClient() result = client.search_cluster_variables( data=ClusterVariableSearchQueryRequest(), ) if not isinstance(result.items, Unset): for var in result.items: print(f"Variable: {var.name}") ``` ### search_correlated_message_subscriptions() ```python def search_correlated_message_subscriptions(*, data=, consistency=None, **kwargs) ``` Search correlated message subscriptions > Search correlated message subscriptions based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ----------------------------------------------------- | ----------- | | `data` | `CorrelatedMessageSubscriptionSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** CorrelatedMessageSubscriptionSearchQueryResult - **Return type:** CorrelatedMessageSubscriptionSearchQueryResult #### Examples **Search correlated message subscriptions:** ```python def search_correlated_message_subscriptions_example() -> None: client = CamundaClient() result = client.search_correlated_message_subscriptions( data=CorrelatedMessageSubscriptionSearchQuery(), ) if not isinstance(result.items, Unset): for sub in result.items: print(f"Correlated subscription: {sub.message_name}") ``` ### search_decision_definitions() ```python def search_decision_definitions(*, data=, consistency=None, **kwargs) ``` Search decision definitions > Search for decision definitions based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------ | ----------- | | `data` | `DecisionDefinitionSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DecisionDefinitionSearchQueryResult - **Return type:** DecisionDefinitionSearchQueryResult #### Examples **Search decision definitions:** ```python def search_decision_definitions_example() -> None: client = CamundaClient() result = client.search_decision_definitions( data=DecisionDefinitionSearchQuery() ) if not isinstance(result.items, Unset): for definition in result.items: print(f"Decision: {definition.decision_definition_id}") ``` ### search_decision_instances() ```python def search_decision_instances(*, data=, consistency=None, **kwargs) ``` Search decision instances > Search for decision instances based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ---------------------------------------- | ----------- | | `data` | `DecisionInstanceSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DecisionInstanceSearchQueryResult - **Return type:** DecisionInstanceSearchQueryResult #### Examples **Search decision instances:** ```python def search_decision_instances_example() -> None: client = CamundaClient() result = client.search_decision_instances( data=DecisionInstanceSearchQuery(), ) if not isinstance(result.items, Unset): for di in result.items: print(f"Decision instance: {di.decision_definition_id}") ``` ### search_decision_requirements() ```python def search_decision_requirements(*, data=, consistency=None, **kwargs) ``` Search decision requirements > Search for decision requirements based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | -------------------------------------------- | ----------- | | `data` | `DecisionRequirementsSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** DecisionRequirementsSearchQueryResult - **Return type:** DecisionRequirementsSearchQueryResult #### Examples **Search decision requirements:** ```python def search_decision_requirements_example() -> None: client = CamundaClient() result = client.search_decision_requirements( data=DecisionRequirementsSearchQuery(), ) if not isinstance(result.items, Unset): for drd in result.items: print(f"DRD: {drd.decision_requirements_name}") ``` ### search_element_instance_incidents() ```python def search_element_instance_incidents(element_instance_key, *, data, consistency=None, **kwargs) ``` Search for incidents of a specific element instance > Search for incidents caused by the specified element instance, including incidents of any child > instances created from this element instance. > > Although the elementInstanceKey is provided as a path parameter to indicate the root element > instance, > you may also include an elementInstanceKey within the filter object to narrow results to specific > child element instances. This is useful, for example, if you want to isolate incidents associated > with > nested or subordinate elements within the given element instance while excluding incidents directly > tied > to the root element itself. **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------ | ----------------------------------------------------------------------- | | `element_instance_key` | `str` | System-generated key for a element instance. Example: 2251799813686789. | | `data` | `IncidentSearchQuery` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The element instance with the given key was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** IncidentSearchQueryResult - **Return type:** IncidentSearchQueryResult #### Examples **Search element instance incidents:** ```python def search_element_instance_incidents_example( element_instance_key: ElementInstanceKey, ) -> None: client = CamundaClient() result = client.search_element_instance_incidents( element_instance_key=element_instance_key, data=IncidentSearchQuery(), ) if not isinstance(result.items, Unset): for incident in result.items: print(f"Incident: {incident.incident_key}") ``` ### search_element_instance_wait_states() ```python def search_element_instance_wait_states(*, data=, consistency=None, **kwargs) ``` Search element instance wait states > Returns the wait states for element instances matching the given filter. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------ | ------------------------------------ | | `data` | `ElementInstanceWaitStateQuery` \| `Unset` | Element instance inspection request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ElementInstanceWaitStateQueryResult - **Return type:** ElementInstanceWaitStateQueryResult #### Examples **Search element instance wait states:** ```python def search_element_instance_wait_states_example() -> None: client = CamundaClient() result = client.search_element_instance_wait_states( data=ElementInstanceWaitStateQuery(), ) for wait_state in result.items: details = wait_state.details if isinstance(details, JobWaitStateDetails): info = f"waiting on job '{details.job_type}'" elif isinstance(details, MessageWaitStateDetails): info = f"waiting for message '{details.message_name}'" else: info = f"waiting ({details.wait_state_type})" print( f"Element {wait_state.element_id} " f"(instance {wait_state.element_instance_key}) {info}" ) ``` ### search_element_instances() ```python def search_element_instances(*, data=, consistency=None, **kwargs) ``` Search element instances > Search for element instances based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | --------------------------------------- | -------------------------------- | | `data` | `ElementInstanceSearchQuery` \| `Unset` | Element instance search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ElementInstanceSearchQueryResult - **Return type:** ElementInstanceSearchQueryResult #### Examples **Search element instances:** ```python def search_element_instances_example() -> None: client = CamundaClient() result = client.search_element_instances( data=ElementInstanceSearchQuery(), ) if not isinstance(result.items, Unset): for ei in result.items: print(f"Element instance: {ei.element_instance_key}") ``` ### search_global_task_listeners() ```python def search_global_task_listeners(*, data=, consistency=None, **kwargs) ``` Search global user task listeners > Search for global user task listeners based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------------- | ------------------------------------- | | `data` | `GlobalTaskListenerSearchQueryRequest` \| `Unset` | Global listener search query request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GlobalTaskListenerSearchQueryResult - **Return type:** GlobalTaskListenerSearchQueryResult #### Examples **Search global task listeners:** ```python def search_global_task_listeners_example() -> None: client = CamundaClient() result = client.search_global_task_listeners( data=GlobalTaskListenerSearchQueryRequest(), ) if not isinstance(result.items, Unset): for listener in result.items: print(f"Listener: {listener.id}") ``` ### search_group_ids_for_tenant() ```python def search_group_ids_for_tenant(tenant_id, *, data=, consistency=None, **kwargs) ``` Search groups for tenant > Retrieves a filtered and sorted list of groups for a specified tenant. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------ | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `data` | `TenantGroupSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TenantGroupSearchResult - **Return type:** TenantGroupSearchResult #### Examples **Search groups for a tenant:** ```python def search_group_ids_for_tenant_example(tenant_id: TenantId) -> None: client = CamundaClient() result = client.search_group_ids_for_tenant( tenant_id=tenant_id, data=TenantGroupSearchQueryRequest(), ) if not isinstance(result.items, Unset): for group in result.items: print(f"Group: {group.group_id}") ``` ### search_groups() ```python def search_groups(*, data=, consistency=None, **kwargs) ``` Search groups > Search for groups based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------ | --------------------- | | `data` | `GroupSearchQueryRequest` \| `Unset` | Group search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GroupSearchQueryResult - **Return type:** GroupSearchQueryResult #### Examples **Search groups:** ```python def search_groups_example() -> None: client = CamundaClient() result = client.search_groups( data=GroupSearchQueryRequest(), ) if not isinstance(result.items, Unset): for group in result.items: print(f"Group: {group.name}") ``` ### search_groups_for_role() ```python def search_groups_for_role(role_id, *, data=, consistency=None, **kwargs) ``` Search role groups > Search groups with assigned role. **Parameters:** | Parameter | Type | Description | | ------------- | ---------------------------------------- | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `data` | `RoleGroupSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RoleGroupSearchResult - **Return type:** RoleGroupSearchResult #### Examples **Search groups for a role:** ```python def search_groups_for_role_example(role_id: RoleId) -> None: client = CamundaClient() result = client.search_groups_for_role( role_id=role_id, data=RoleGroupSearchQueryRequest(), ) if not isinstance(result.items, Unset): for group in result.items: print(f"Group: {group.group_id}") ``` ### search_incidents() ```python def search_incidents(*, data=, consistency=None, **kwargs) ``` Search incidents > Search for incidents based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | -------------------------------- | ----------- | | `data` | `IncidentSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** IncidentSearchQueryResult - **Return type:** IncidentSearchQueryResult #### Examples **Search incidents:** ```python def search_incidents_example() -> None: client = CamundaClient() result = client.search_incidents( data=IncidentSearchQuery() ) if not isinstance(result.items, Unset): for incident in result.items: print(f"Incident key: {incident.incident_key}") ``` ### search_jobs() ```python def search_jobs(*, data=, consistency=None, **kwargs) ``` Search jobs > Search for jobs based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------ | ------------------- | | `data` | `JobSearchQuery` \| `Unset` | Job search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** JobSearchQueryResult - **Return type:** JobSearchQueryResult #### Examples **Search jobs:** ```python def search_jobs_example() -> None: client = CamundaClient() result = client.search_jobs( data=JobSearchQuery(), ) if not isinstance(result.items, Unset): for job in result.items: print(f"Job: {job.job_key}") ``` ### search_mapping_rule() ```python def search_mapping_rule(*, data=, consistency=None, **kwargs) ``` Search mapping rules > Search for mapping rules based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------ | ----------- | | `data` | `MappingRuleSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** MappingRuleSearchQueryResult - **Return type:** MappingRuleSearchQueryResult #### Examples **Search mapping rules:** ```python def search_mapping_rule_example() -> None: client = CamundaClient() result = client.search_mapping_rule( data=MappingRuleSearchQueryRequest(), ) if not isinstance(result.items, Unset): for rule in result.items: print(f"Mapping rule: {rule.name}") ``` ### search_mapping_rules_for_group() ```python def search_mapping_rules_for_group(group_id, *, data=, consistency=None, **kwargs) ``` Search group mapping rules > Search mapping rules assigned to a group. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------ | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `data` | `MappingRuleSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GroupMappingRuleSearchResult - **Return type:** GroupMappingRuleSearchResult #### Examples **Search mapping rules for a group:** ```python def search_mapping_rules_for_group_example(group_id: GroupId) -> None: client = CamundaClient() result = client.search_mapping_rules_for_group( group_id=group_id, data=MappingRuleSearchQueryRequest(), ) if not isinstance(result.items, Unset): for rule in result.items: print(f"Mapping rule: {rule.mapping_rule_id}") ``` ### search_mapping_rules_for_role() ```python def search_mapping_rules_for_role(role_id, *, data=, consistency=None, **kwargs) ``` Search role mapping rules > Search mapping rules with assigned role. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------ | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `data` | `MappingRuleSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RoleMappingRuleSearchResult - **Return type:** RoleMappingRuleSearchResult #### Examples **Search mapping rules for a role:** ```python def search_mapping_rules_for_role_example(role_id: RoleId) -> None: client = CamundaClient() result = client.search_mapping_rules_for_role( role_id=role_id, data=MappingRuleSearchQueryRequest(), ) if not isinstance(result.items, Unset): for rule in result.items: print(f"Mapping rule: {rule.mapping_rule_id}") ``` ### search_mapping_rules_for_tenant() ```python def search_mapping_rules_for_tenant(tenant_id, *, data=, consistency=None, **kwargs) ``` Search mapping rules for tenant > Retrieves a filtered and sorted list of MappingRules for a specified tenant. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------ | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `data` | `MappingRuleSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TenantMappingRuleSearchResult - **Return type:** TenantMappingRuleSearchResult #### Examples **Search mapping rules for a tenant:** ```python def search_mapping_rules_for_tenant_example(tenant_id: TenantId) -> None: client = CamundaClient() result = client.search_mapping_rules_for_tenant( tenant_id=tenant_id, data=MappingRuleSearchQueryRequest(), ) if not isinstance(result.items, Unset): for rule in result.items: print(f"Mapping rule: {rule.mapping_rule_id}") ``` ### search_message_subscriptions() ```python def search_message_subscriptions(*, data=, consistency=None, **kwargs) ``` Search message subscriptions > Search for message subscriptions based on given criteria. > > By default, both start and intermediate event subscriptions are returned. Use the > messageSubscriptionType filter to restrict results to a single type. > > **Version notes:** - Start event subscriptions are only captured for deployments made with 8.10 or later. - The messageSubscriptionType field is only populated for data created > with Camunda 8.10 or later. For pre-8.10 data, intermediate event entries have no > messageSubscriptionType value stored. For convenience, the API returns PROCESS_EVENT > as a default for such search results, though. - Searching for intermediate event subscriptions **including legacy data** can be achieved by filtering for messageSubscriptionType not matching START_EVENT. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------- | ----------- | | `data` | `MessageSubscriptionSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** MessageSubscriptionSearchQueryResult - **Return type:** MessageSubscriptionSearchQueryResult #### Examples **Search message subscriptions:** ```python def search_message_subscriptions_example() -> None: client = CamundaClient() result = client.search_message_subscriptions( data=MessageSubscriptionSearchQuery(), ) if not isinstance(result.items, Unset): for sub in result.items: print(f"Subscription: {sub.message_name}") ``` ### search_own_authorizations() ```python def search_own_authorizations(*, data=, consistency=None, **kwargs) ``` Search own authorizations > Search for the current authenticated principal’s own authorization records — including > authorizations granted directly to the user or client, as well as those granted via a group, role, > or mapping rule the principal belongs to. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------- | ----------- | | `data` | `AuthorizationSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** OwnAuthorizationSearchResult - **Return type:** OwnAuthorizationSearchResult #### Examples **Search own authorizations:** ```python def search_own_authorizations_example() -> None: client = CamundaClient() result = client.search_own_authorizations( data=AuthorizationSearchQuery( filter_=AuthorizationSearchQueryFilter( resource_type=AuthorizationSearchQueryFilterResourceType.PROCESS_DEFINITION, ), page=LimitBasedPagination(limit=20), ) ) if not isinstance(result.items, Unset): for auth in result.items: print(f"Resource: {auth.resource_id}, permissions: {auth.permission_types}") ``` ### search_process_definition_variable_names() ```python def search_process_definition_variable_names(process_definition_key, *, data=, consistency=None, **kwargs) ``` Search process definition variable names > Search for distinct variable names defined on a process definition, optionally narrowed by the name > filter. **Parameters:** | Parameter | Type | Description | | ------------------------ | ----------------------------------------------------- | ---------------------------------------------------------------------------------- | | `process_definition_key` | `str` | System-generated key for a deployed process definition. Example: 2251799813686749. | | `data` | `ProcessDefinitionVariableNameSearchQuery` \| `Unset` | Process definition variable name search query request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessDefinitionVariableNameSearchQueryResult - **Return type:** ProcessDefinitionVariableNameSearchQueryResult #### Examples **Search process definition variable names:** ```python def search_process_definition_variable_names_example( process_definition_key: ProcessDefinitionKey, ) -> None: client = CamundaClient() result = client.search_process_definition_variable_names( process_definition_key=process_definition_key, data=ProcessDefinitionVariableNameSearchQuery(), ) if not isinstance(result.items, Unset): for variable in result.items: print(f"Variable name: {variable.name}") ``` ### search_process_definitions() ```python def search_process_definitions(*, data=, consistency=None, **kwargs) ``` Search process definitions > Search for process definitions based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ----------------------------------------- | ----------- | | `data` | `ProcessDefinitionSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessDefinitionSearchQueryResult - **Return type:** ProcessDefinitionSearchQueryResult #### Examples **Search process definitions:** ```python def search_process_definitions_example() -> None: client = CamundaClient() result = client.search_process_definitions( data=ProcessDefinitionSearchQuery(), ) if not isinstance(result.items, Unset): for pd in result.items: print(f"Process definition: {pd.name}") ``` ### search_process_instance_incidents() ```python def search_process_instance_incidents(process_instance_key, *, data=, consistency=None, **kwargs) ``` Search related incidents > Search for incidents caused by the process instance or any of its called process or decision > instances. > > Although the processInstanceKey is provided as a path parameter to indicate the root process > instance, > you may also include a processInstanceKey within the filter object to narrow results to specific > child process instances. This is useful, for example, if you want to isolate incidents associated > with > subprocesses or called processes under the root instance while excluding incidents directly tied to > the root. **Parameters:** | Parameter | Type | Description | | ---------------------- | -------------------------------- | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `data` | `IncidentSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The process instance with the given key was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** IncidentSearchQueryResult - **Return type:** IncidentSearchQueryResult #### Examples **Search process instance incidents:** ```python def search_process_instance_incidents_example( process_instance_key: ProcessInstanceKey, ) -> None: client = CamundaClient() result = client.search_process_instance_incidents( process_instance_key=process_instance_key, data=IncidentSearchQuery(), ) if not isinstance(result.items, Unset): for incident in result.items: print(f"Incident: {incident.incident_key}") ``` ### search_process_instances() ```python def search_process_instances(*, data=, consistency=None, **kwargs) ``` Search process instances > Search for process instances based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | --------------------------------------- | -------------------------------- | | `data` | `ProcessInstanceSearchQuery` \| `Unset` | Process instance search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ProcessInstanceSearchQueryResult - **Return type:** ProcessInstanceSearchQueryResult #### Examples **Search process instances:** ```python def search_process_instances_example() -> None: client = CamundaClient() result = client.search_process_instances( data=ProcessInstanceSearchQuery( filter_=ProcessInstanceSearchQueryFilter( process_definition_id="order-process", ), sort=[ ProcessInstanceSearchQuerySortRequest( field=ProcessInstanceSearchQuerySortRequestField.STARTDATE, order=SortOrderEnum.DESC, ) ], page=LimitBasedPagination(limit=10), ) ) for instance in result.items: print(f"{instance.process_instance_key}: {instance.state}") print(f"Total: {result.page.total_items}") ``` ### search_resources() ```python def search_resources(*, data=, consistency=None, **kwargs) ``` Search resources > Search for deployed resources based on given criteria. :::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 search APIs. ::: - **data**: :type data: ResourceSearchQuery | Unset ```` * **Raises:** * **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. * **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. * **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. * **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. * **errors.UnexpectedStatus** – If the response status code is not documented. * **httpx.TimeoutException** – If the request takes longer than Client.timeout. * **Returns:** ResourceSearchQueryResult **Parameters:** | Parameter | Type | Description | | --- | --- | --- | | `data` | `ResourceSearchQuery` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | * **Return type:** ResourceSearchQueryResult #### Examples **Search resources:** ```python def search_resources_example() -> None: client = CamundaClient() result = client.search_resources( data=ResourceSearchQuery(), ) if not isinstance(result.items, Unset): for resource in result.items: print(f"Resource: {resource.resource_name}") ```` ### search_roles() ```python def search_roles(*, data=, consistency=None, **kwargs) ``` Search roles > Search for roles based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ----------------------------------- | -------------------- | | `data` | `RoleSearchQueryRequest` \| `Unset` | Role search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RoleSearchQueryResult - **Return type:** RoleSearchQueryResult #### Examples **Search roles:** ```python def search_roles_example() -> None: client = CamundaClient() result = client.search_roles( data=RoleSearchQueryRequest(), ) if not isinstance(result.items, Unset): for role in result.items: print(f"Role: {role.name}") ``` ### search_roles_for_group() ```python def search_roles_for_group(group_id, *, data=, consistency=None, **kwargs) ``` Search group roles > Search roles assigned to a group. **Parameters:** | Parameter | Type | Description | | ------------- | ----------------------------------- | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `data` | `RoleSearchQueryRequest` \| `Unset` | Role search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GroupRoleSearchResult - **Return type:** GroupRoleSearchResult #### Examples **Search roles for a group:** ```python def search_roles_for_group_example(group_id: GroupId) -> None: client = CamundaClient() result = client.search_roles_for_group( group_id=group_id, data=RoleSearchQueryRequest(), ) if not isinstance(result.items, Unset): for role in result.items: print(f"Role: {role.name}") ``` ### search_roles_for_tenant() ```python def search_roles_for_tenant(tenant_id, *, data=, consistency=None, **kwargs) ``` Search roles for tenant > Retrieves a filtered and sorted list of roles for a specified tenant. **Parameters:** | Parameter | Type | Description | | ------------- | ----------------------------------- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `data` | `RoleSearchQueryRequest` \| `Unset` | Role search request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TenantRoleSearchResult - **Return type:** TenantRoleSearchResult #### Examples **Search roles for a tenant:** ```python def search_roles_for_tenant_example(tenant_id: TenantId) -> None: client = CamundaClient() result = client.search_roles_for_tenant( tenant_id=tenant_id, data=RoleSearchQueryRequest(), ) if not isinstance(result.items, Unset): for role in result.items: print(f"Role: {role.name}") ``` ### search_tenants() ```python def search_tenants(*, data=, consistency=None, **kwargs) ``` Search tenants > Retrieves a filtered and sorted list of tenants. **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------- | --------------------- | | `data` | `TenantSearchQueryRequest` \| `Unset` | Tenant search request | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TenantSearchQueryResult - **Return type:** TenantSearchQueryResult #### Examples **Search tenants:** ```python def search_tenants_example() -> None: client = CamundaClient() result = client.search_tenants( data=TenantSearchQueryRequest(), ) if not isinstance(result.items, Unset): for tenant in result.items: print(f"Tenant: {tenant.name}") ``` ### search_user_task_audit_logs() ```python def search_user_task_audit_logs(user_task_key, *, data=, consistency=None, **kwargs) ``` Search user task audit logs > Search for user task audit logs based on given criteria. **Parameters:** | Parameter | Type | Description | | --------------- | ----------------------------------------------- | ------------------------------------- | | `user_task_key` | `str` | System-generated key for a user task. | | `data` | `UserTaskAuditLogSearchQueryRequest` \| `Unset` | User task search query request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AuditLogSearchQueryResult - **Return type:** AuditLogSearchQueryResult #### Examples **Search user task audit logs:** ```python def search_user_task_audit_logs_example(user_task_key: UserTaskKey) -> None: client = CamundaClient() result = client.search_user_task_audit_logs( user_task_key=user_task_key, data=UserTaskAuditLogSearchQueryRequest(), ) if not isinstance(result.items, Unset): for log in result.items: print(f"Audit log: {log.audit_log_key}") ``` ### search_user_task_effective_variables() ```python def search_user_task_effective_variables(user_task_key, *, data=, truncate_values=, consistency=None, **kwargs) ``` Search user task effective variables > Search for the effective variables of a user task. This endpoint returns deduplicated > variables where each variable name appears at most once. When the same variable name exists > at multiple scope levels in the scope hierarchy, the value from the innermost scope (closest > to the user task) takes precedence. This is useful for retrieving the actual runtime state > of variables as seen by the user task. By default, long variable values in the response are > truncated. **Parameters:** | Parameter | Type | Description | | ----------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------- | | `user_task_key` | `str` | System-generated key for a user task. | | `truncate_values` | `bool` \| `Unset` | | | `data` | `UserTaskEffectiveVariableSearchQueryRequest` \| `Unset` | User task effective variable search query request. Uses offset-based pagination only. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** VariableSearchQueryResult - **Return type:** VariableSearchQueryResult #### Examples **Search user task effective variables:** ```python def search_user_task_effective_variables_example(user_task_key: UserTaskKey) -> None: client = CamundaClient() result = client.search_user_task_effective_variables( user_task_key=user_task_key, ) if not isinstance(result.items, Unset): for var in result.items: print(f"Variable: {var.name}") ``` ### search_user_task_variables() ```python def search_user_task_variables(user_task_key, *, data=, truncate_values=, consistency=None, **kwargs) ``` Search user task variables > Search for user task variables based on given criteria. This endpoint returns all variable > documents visible from the user task’s scope, including variables from parent scopes in the > scope hierarchy. If the same variable name exists at multiple scope levels, each scope’s > variable is returned as a separate result. Use the > /user-tasks/{userTaskKey}/effective-variables/search endpoint to get deduplicated variables > where the innermost scope takes precedence. By default, long variable values in the response > are truncated. **Parameters:** | Parameter | Type | Description | | ----------------- | ----------------------------------------------- | ------------------------------------- | | `user_task_key` | `str` | System-generated key for a user task. | | `truncate_values` | `bool` \| `Unset` | | | `data` | `UserTaskVariableSearchQueryRequest` \| `Unset` | User task search query request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** VariableSearchQueryResult - **Return type:** VariableSearchQueryResult #### Examples **Search user task variables:** ```python def search_user_task_variables_example(user_task_key: UserTaskKey) -> None: client = CamundaClient() result = client.search_user_task_variables( user_task_key=user_task_key, ) if not isinstance(result.items, Unset): for var in result.items: print(f"Variable: {var.name}") ``` ### search_user_tasks() ```python def search_user_tasks(*, data=, consistency=None, **kwargs) ``` Search user tasks > Search for user tasks based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | -------------------------------- | ------------------------------- | | `data` | `UserTaskSearchQuery` \| `Unset` | User task search query request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** UserTaskSearchQueryResult - **Return type:** UserTaskSearchQueryResult #### Examples **Search user tasks:** ```python def search_user_tasks_example() -> None: client = CamundaClient() result = client.search_user_tasks( data=UserTaskSearchQuery() ) if not isinstance(result.items, Unset): for task in result.items: print(f"Task: {task.user_task_key}") ``` ### search_users() ```python def search_users(*, data=, consistency=None, **kwargs) ``` Search users > Search for users based on given criteria. **Parameters:** | Parameter | Type | Description | | ------------- | ----------------------------------- | ----------- | | `data` | `UserSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** UserSearchResult - **Return type:** UserSearchResult #### Examples **Search users:** ```python def search_users_example() -> None: client = CamundaClient() result = client.search_users( data=UserSearchQueryRequest(), ) if not isinstance(result.items, Unset): for user in result.items: print(f"User: {user.username}") ``` ### search_users_for_group() ```python def search_users_for_group(group_id, *, data=, consistency=None, **kwargs) ``` Search group users > Search users assigned to a group. **Parameters:** | Parameter | Type | Description | | ------------- | ---------------------------------------- | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `data` | `GroupUserSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GroupUserSearchResult - **Return type:** GroupUserSearchResult #### Examples **Search users in a group:** ```python def search_users_for_group_example(group_id: GroupId) -> None: client = CamundaClient() result = client.search_users_for_group( group_id=group_id, ) if not isinstance(result.items, Unset): for user in result.items: print(f"User: {user.username}") ``` ### search_users_for_role() ```python def search_users_for_role(role_id, *, data=, consistency=None, **kwargs) ``` Search role users > Search users with assigned role. **Parameters:** | Parameter | Type | Description | | ------------- | --------------------------------------- | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `data` | `RoleUserSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RoleUserSearchResult - **Return type:** RoleUserSearchResult #### Examples **Search users for a role:** ```python def search_users_for_role_example(role_id: RoleId) -> None: client = CamundaClient() result = client.search_users_for_role( role_id=role_id, ) if not isinstance(result.items, Unset): for user in result.items: print(f"User: {user.username}") ``` ### search_users_for_tenant() ```python def search_users_for_tenant(tenant_id, *, data=, consistency=None, **kwargs) ``` Search users for tenant > Retrieves a filtered and sorted list of users for a specified tenant. **Parameters:** | Parameter | Type | Description | | ------------- | ----------------------------------------- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `data` | `TenantUserSearchQueryRequest` \| `Unset` | | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TenantUserSearchResult - **Return type:** TenantUserSearchResult #### Examples **Search users for a tenant:** ```python def search_users_for_tenant_example(tenant_id: TenantId) -> None: client = CamundaClient() result = client.search_users_for_tenant( tenant_id=tenant_id, ) if not isinstance(result.items, Unset): for user in result.items: print(f"User: {user.username}") ``` ### search_variables() ```python def search_variables(*, data=, truncate_values=, consistency=None, **kwargs) ``` Search variables > Search for variables based on given criteria. > > This endpoint returns variables that exist directly at the specified scopes - it does not > include variables from parent scopes that would be visible through the scope hierarchy. > > Variables can be process-level (scoped to the process instance) or local (scoped to specific > BPMN elements like tasks, subprocesses, etc.). > > By default, long variable values in the response are truncated. **Parameters:** | Parameter | Type | Description | | ----------------- | -------------------------------- | ------------------------------ | | `truncate_values` | `bool` \| `Unset` | | | `data` | `VariableSearchQuery` \| `Unset` | Variable search query request. | | `consistency` | `ConsistencyOptions` \| `None` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** VariableSearchQueryResult - **Return type:** VariableSearchQueryResult #### Examples **Search variables:** ```python def search_variables_example() -> None: client = CamundaClient() result = client.search_variables() if not isinstance(result.items, Unset): for var in result.items: print(f"Variable: {var.name}") ``` ### search_variables_as_dto() ```python def search_variables_as_dto(dto, *, process_instance_key, scope_key=None, tenant_id=None, page_size=100, consistency=None) ``` Fetch the variables declared by a Pydantic model for a process instance. Derives a `name $in [...]` filter from the fields of `dto` (honouring Pydantic aliases), so only the declared variables are fetched — memory is bounded by the model shape, not by the total variable count. The result is a `camunda_orchestration_sdk.runtime.typed_variables.VariableMap`, which offers lenient access via `.get(name)` and strict, fully-typed access via `.validate()` (which constructs `dto` and raises `pydantic.ValidationError` on missing or invalid values). **Parameters:** | Parameter | Type | Description | | ---------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `dto` | type [ \_VarDtoT ] | A `pydantic.BaseModel` subclass describing the variables of interest. | | `process_instance_key` | `str` | The process instance whose variables to search. | | `scope_key` | `str` \| `None` | Optional scope key to disambiguate variables that exist at multiple scopes. Required when a variable name collides across scopes. | | `tenant_id` | `str` \| `None` | Optional tenant identifier to filter by. | | `page_size` | `int` | Page size used while paginating to exhaustion. Defaults to 100. | | `consistency` | `ConsistencyOptions` \| `None` | Optional eventual-consistency budget. When supplied, the whole collection is re-read until every declared variable is visible or `wait_up_to_ms` expires (the best snapshot is returned on expiry). Variable indexes update asynchronously, so a freshly written variable may not be visible immediately; without this the variables are read exactly once. | - **Returns:** The parsed variable map keyed by the declared field names. - **Return type:** VariableMap - **Raises:** - **TypeError** – If `dto` is not a pydantic `BaseModel` subclass. - **camunda_orchestration_sdk.runtime.typed_variables.VariableScopeCollisionError** – If a declared variable is found at more than one scope and no `scope_key` was supplied. - **camunda_orchestration_sdk.runtime.typed_variables.VariableDeserializationError** – If a returned variable value is present but not valid JSON. ### suspend_batch_operation() ```python def suspend_batch_operation(batch_operation_key, *, data=, **kwargs) ``` Suspend Batch operation > Suspends a running batch operation. > > This is done asynchronously, the progress can be tracked using the batch operation status endpoint > (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------------------- | ---------------- | ----------------------------------------------------------------------- | | `batch_operation_key` | `str` | System-generated key for an batch operation. Example: 2251799813684321. | | `data` | `Any` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The batch operation was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Suspend a batch operation:** ```python def suspend_batch_operation_example(batch_operation_key: BatchOperationKey) -> None: client = CamundaClient() client.suspend_batch_operation( batch_operation_key=batch_operation_key, ) ``` ### suspend_process_instance() ```python def suspend_process_instance(process_instance_key, *, data=, **kwargs) ``` Suspend process instance > Suspends a running process instance, pausing further processing until it is resumed. > > Only process instances in the ACTIVE state can be suspended. > A child process instance can be suspended independently of its parent or root process > instance; suspension does not cascade to or from related instances. **Parameters:** | Parameter | Type | Description | | ---------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------- | | `process_instance_key` | `str` | System-generated key for a process instance. Example: 2251799813690746. | | `data` | `None` \| `SuspendProcessInstanceRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The process instance is not found. - **errors.ConflictError** – If the response status code is 409. The process instance is not in the ACTIVE state and cannot be suspended. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Suspend a process instance:** ```python def suspend_process_instance_example(process_instance_key: ProcessInstanceKey) -> None: client = CamundaClient() client.suspend_process_instance( process_instance_key=process_instance_key, ) ``` ### suspend_process_instances_batch_operation() ```python def suspend_process_instances_batch_operation(*, data, **kwargs) ``` Suspend process instances (batch) > Suspends multiple running process instances. > > Any given filter for state or parentProcessInstanceKey is ignored and overridden, as only > ACTIVE process instances can be suspended and suspension does not cascade between parent > and child instances, so child instances are suspended independently of their parent or > root instance. > This is done asynchronously, the progress can be tracked using the batchOperationKey from the > response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------------------------ | ------------------------------------------------------------------------------------- | | `data` | `ProcessInstanceSuspensionBatchOperationRequest` | The process instance filter that defines which process instances should be suspended. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The process instance batch operation failed. More details are provided in the response body. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationCreatedResult - **Return type:** BatchOperationCreatedResult #### Examples **Suspend process instances in batch:** ```python def suspend_process_instances_batch_operation_example() -> None: client = CamundaClient() result = client.suspend_process_instances_batch_operation( data=ProcessInstanceSuspensionBatchOperationRequest( filter_=ProcessInstanceCancellationBatchOperationRequestFilter(), ), ) print(f"Batch operation key: {result.batch_operation_key}") ``` ### sync_runtime_backup_state() ```python def sync_runtime_backup_state(**kwargs) ``` Force-write runtime backup state > Force-writes the checkpoint and backup metadata of every partition of the physical > tenant to the backup store, independent of any backup being taken or confirmed, and > returns the updated state. - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.GatewayTimeoutError** – If the response status code is 504. The request from gateway to broker timed out. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RuntimeBackupState - **Parameters:** **kwargs** (_Any_) - **Return type:** RuntimeBackupState #### Examples **Force-write the runtime backup state:** ```python def sync_runtime_backup_state_example() -> None: client = CamundaClient() # Force-writes the checkpoint and backup metadata of every partition to the # backup store, independent of any backup being taken or confirmed. result = client.sync_runtime_backup_state() print(f"Synced {len(result.backup_states)} partition backup states") ``` ### sync_runtime_backup_state_as_cluster_admin() ```python def sync_runtime_backup_state_as_cluster_admin(*, physical_tenant_id=, **kwargs) ``` Force-write runtime backup state across physical tenants > Force-writes the checkpoint and backup metadata of every partition of every physical tenant of the > cluster, or of the one named by physicalTenantId, to that tenant’s backup store, independent of > any backup being taken or confirmed, and returns the updated state per physical tenant. > > The request is all-or-nothing: a physical tenant whose metadata cannot be written fails the whole > request, and the writes that already succeeded on other tenants are not undone. The operation is > idempotent, so retrying the same call is the correct remedy. Narrow the request with > physicalTenantId to write the tenants that can still be reached. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Use POST > /v2/backups/runtime/state/sync to act as a single physical tenant. **Parameters:** | Parameter | Type | Description | | -------------------- | ---------------- | ----------------- | | `physical_tenant_id` | `str` \| `Unset` | Example: default. | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.GatewayTimeoutError** – If the response status code is 504. The request from gateway to broker timed out on at least one targeted physical tenant. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterRuntimeBackupState - **Return type:** ClusterRuntimeBackupState #### Examples **Force-write runtime backup state across physical tenants:** ```python def sync_runtime_backup_state_as_cluster_admin_example() -> None: client = CamundaClient() # Force-writes the checkpoint and backup metadata of every partition of # every physical tenant to the backup store, independent of any backup being # taken or confirmed. Use `physical_tenant_id` to narrow to a single tenant. result = client.sync_runtime_backup_state_as_cluster_admin() print(f"Synced {len(result.physical_tenants)} physical tenant backup states") ``` ### take_history_backup() ```python def take_history_backup(*, data, **kwargs) ``` Take a history backup > Triggers a backup of the physical tenant’s history, by scheduling a snapshot of every > secondary storage index it owns. > > Unlike runtime backups, history backups have no generated-id mode: backupId is always > required. > > Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. **Parameters:** | Parameter | Type | Description | | --------- | -------------------------- | ----------------------------------------- | | `data` | `TakeHistoryBackupRequest` | Request body for taking a history backup. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. The request is forbidden for one of three reasons: the authenticated caller lacks the required BACKUP permission; the cluster’s secondary storage is neither Elasticsearch nor OpenSearch and therefore cannot serve history backups; or the physical tenant’s snapshot repository is absent from the store — configured under a name the store does not have, or not configured at all. The problem detail says which applies. The latter two are deployment faults the caller cannot correct by changing its request. - **errors.ConflictError** – If the response status code is 409. A backup with the given id already exists, or another backup is already running. The “already running” check is best-effort and node-local: it only observes backups started by the gateway that serves the request. Two concurrent requests reaching different gateways are narrowed by the duplicate-id check alone. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TakeHistoryBackupResponse - **Return type:** TakeHistoryBackupResponse #### Examples **Take a history backup:** ```python def take_history_backup_example(backup_id: int) -> None: client = CamundaClient() # Backups are logically ordered by id, so each successive backup must use a # higher id than the previous one. result = client.take_history_backup( data=TakeHistoryBackupRequest( backup_id=backup_id, ) ) print(f"Scheduled history backup {result.backup_id}") for snapshot in result.scheduled_snapshots: print(f" {snapshot}") ``` ### take_history_backup_as_cluster_admin() ```python def take_history_backup_as_cluster_admin(*, data, physical_tenant_id=, **kwargs) ``` Take a history backup on one or every physical tenant > Triggers a history backup on every physical tenant of the cluster, or on the one named by > physicalTenantId. Every targeted tenant uses the same caller-supplied backupId, but the backups > are independent: they are neither coordinated nor rolled back together. > > The request is all-or-nothing: the backupId is checked on every targeted tenant before any > snapshot is scheduled, so a tenant that already holds this id, or that cannot be reached, fails the > whole request and no backup is started anywhere. There is no aggregated cluster-level state in the > response. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Only available on clusters > whose secondary storage is Elasticsearch or OpenSearch. Use POST /v2/backups/history to act as a > single physical tenant. **Parameters:** | Parameter | Type | Description | | -------------------- | -------------------------- | ----------------------------------------- | | `physical_tenant_id` | `str` \| `Unset` | Example: default. | | `data` | `TakeHistoryBackupRequest` | Request body for taking a history backup. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. The cluster’s secondary storage is neither Elasticsearch nor OpenSearch and therefore cannot serve history backups, or a targeted physical tenant’s snapshot repository is absent from the store — configured under a name the store does not have, or not configured at all. Both are deployment faults the caller cannot correct by changing its request; narrow the request with physicalTenantId to work with the tenants whose repository is usable. Unlike the per-physical-tenant backup endpoints, the cluster-admin surface performs no fine-grained authorization, so a missing BACKUP permission is never the reason. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster. - **errors.ConflictError** – If the response status code is 409. At least one targeted physical tenant already holds a backup with this id, or already has another backup running. The check that precedes the fan-out normally rejects the request before anything is scheduled; a tenant that takes the id in between rejects it during the fan-out instead, which can leave snapshots behind on the tenants already reached, so delete this backup id before retrying. - **errors.InternalServerErrorError** – If the response status code is 500. The backup could not be scheduled on every targeted physical tenant, because one of them hit an internal error. The check that precedes the fan-out rejects the request before anything is scheduled, but a failure during the fan-out itself can leave snapshots behind on the tenants already reached, so delete this backup id before retrying. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterTakeHistoryBackupResponse - **Return type:** ClusterTakeHistoryBackupResponse #### Examples **Take a history backup on every physical tenant:** ```python def take_history_backup_as_cluster_admin_example(backup_id: int) -> None: client = CamundaClient() # Requires the cluster-admin security chain. Triggers the backup on every # physical tenant; the backup id must be higher than any previously used id. # Use `physical_tenant_id` to target a single physical tenant instead. result = client.take_history_backup_as_cluster_admin( data=TakeHistoryBackupRequest( backup_id=backup_id, ) ) print(f"Scheduled history backup {result.backup_id}") for tenant in result.physical_tenants: print(f" physical tenant {tenant.physical_tenant_id}: scheduled") ``` ### take_runtime_backup() ```python def take_runtime_backup(*, data=, **kwargs) ``` Take a runtime backup > Triggers a backup of runtime data on all partitions of the physical tenant. > > The backupId must be omitted if continuous backups and/or a backup or checkpoint > schedule is enabled for the physical tenant, as the id is generated automatically. > Otherwise, backupId is required. **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------------- | ----------------------------------------- | | `data` | `TakeRuntimeBackupRequest` \| `Unset` | Request body for taking a runtime backup. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.ConflictError** – If the response status code is 409. A backup with the same or a higher id already exists. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.GatewayTimeoutError** – If the response status code is 504. The request from gateway to broker timed out. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TakeRuntimeBackupResponse - **Return type:** TakeRuntimeBackupResponse #### Examples **Take a runtime backup:** ```python def take_runtime_backup_example(backup_id: int) -> None: client = CamundaClient() # `backup_id` is optional: leave it unset when continuous backups or a # backup/checkpoint schedule is enabled and an id is generated for you. # Here it is supplied explicitly, which is what a one-off manual backup does. result = client.take_runtime_backup( data=TakeRuntimeBackupRequest( backup_id=backup_id, ) ) print(f"Scheduled backup {result.backup_id}") ``` ### take_runtime_backup_as_cluster_admin() ```python def take_runtime_backup_as_cluster_admin(*, data=, physical_tenant_id=, **kwargs) ``` Take a runtime backup on one or every physical tenant > Triggers a runtime backup on every physical tenant of the cluster, or on the one named by > physicalTenantId. A cluster-wide backup is a set of independent per-tenant backups, not an atomic > snapshot of the cluster: they are neither coordinated nor rolled back together, and each tenant > stores its own, so the same backupId can be used for all of them. > > Every targeted physical tenant must be in the same backup-id mode. backupId must be omitted when > every targeted tenant generates its own ids (because continuous backups and/or a backup or > checkpoint schedule is enabled for it), and is required when none of them does. A cluster whose > targeted tenants mix the two modes is rejected with 400 and has to be driven one tenant at a time > through POST /v2/backups/runtime. In generated-id mode each tenant generates its own id, so the > response reports an id per physical tenant rather than one for the cluster. > > The trigger is all-or-error, and never silent about a partial trigger: if any targeted tenant cannot > be triggered the response carries an error status, but its body still lists every targeted tenant — > which ones were triggered, under which backupId to monitor or delete them, and why the others > failed. Nothing is rolled back, so the backups that were triggered keep running and have to be > deleted explicitly. A request rejected before any tenant was triggered answers with a problem detail > instead, and nothing is running. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. Use POST > /v2/backups/runtime to act as a single physical tenant. **Parameters:** | Parameter | Type | Description | | -------------------- | ------------------------------------- | ----------------------------------------- | | `physical_tenant_id` | `str` \| `Unset` | Example: default. | | `data` | `TakeRuntimeBackupRequest` \| `Unset` | Request body for taking a runtime backup. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The request names a backupId while at least one targeted physical tenant generates its own ids, or omits it while at least one does not, or the id is not a positive number. No tenant was triggered. A targeted tenant that rejects the request as invalid during the fan-out answers with the same status but the cluster body, listing the tenants that were triggered. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The requested physicalTenantId does not exist in this cluster, so no tenant was triggered. - **errors.ConflictError** – If the response status code is 409. At least one targeted physical tenant already holds a backup with this id or a higher one. Backups are triggered without a preceding check, so the tenants that accepted the id are listed in the body and keep running; delete them before retrying. - **errors.InternalServerErrorError** – If the response status code is 500. At least one targeted physical tenant could not be triggered, and the failures do not agree on a single status. The body lists the tenants that were triggered and keep running. - **errors.BadGatewayError** – If the response status code is 502. The connection to the broker was cut mid-flight on at least one targeted physical tenant, which may or may not have accepted the request. Those tenants are reported as UNKNOWN with the id to check them under, and the tenants that were triggered keep running. - **errors.ServiceUnavailableError** – If the response status code is 503. At least one targeted physical tenant could not be reached. The body lists the tenants that were triggered and keep running. - **errors.GatewayTimeoutError** – If the response status code is 504. The request from gateway to broker timed out on at least one targeted physical tenant, which may or may not have accepted it. Those tenants are reported as UNKNOWN with the id to check them under, and the tenants that were triggered keep running. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterTakeRuntimeBackupResponse - **Return type:** ClusterTakeRuntimeBackupResponse #### Examples **Take a runtime backup across physical tenants:** ```python def take_runtime_backup_as_cluster_admin_example(backup_id: int) -> None: client = CamundaClient() # Requires the cluster-admin security chain. Triggers the backup on every # physical tenant; the backup id must be higher than any previously used id. # Use `physical_tenant_id` to target a single physical tenant instead. result = client.take_runtime_backup_as_cluster_admin( data=TakeRuntimeBackupRequest( backup_id=backup_id, ) ) for tenant in result.physical_tenants: print(f" physical tenant {tenant.physical_tenant_id}: backup {tenant.backup_id}") ``` ### throw_job_error() ```python def throw_job_error(job_key, *, data, **kwargs) ``` Throw error for job > Reports a business error (i.e. non-technical) that occurs while processing a job. **Parameters:** | Parameter | Type | Description | | --------- | ----------------- | ---------------------------------------------------------- | | `job_key` | `str` | System-generated key for a job. Example: 2251799813653498. | | `data` | `JobErrorRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The job with the given key was not found or is not activated. - **errors.ConflictError** – If the response status code is 409. The job with the given key is in the wrong state currently. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Throw a job error:** ```python def throw_job_error_example(job_key: JobKey) -> None: client = CamundaClient() client.throw_job_error( job_key=job_key, data=JobErrorRequest( error_code="VALIDATION_ERROR", error_message="Input validation failed", ), ) ``` ### trigger_cluster_rebalance() ```python def trigger_cluster_rebalance(*, data=, dry_run=, **kwargs) ``` Trigger a cluster-wide leadership rebalance > Transfers leadership of every partition that is not led by its highest-priority replica towards that > replica, one partition at a time. Returns as soon as the rebalance has been accepted (poll GET > /cluster/v2/rebalance to monitor progress). > > Each rebalance can specify overrides for the configured rebalance settings (e.g. maximum replication > lag to allow). An absent request body means “use the configured settings”. > > Requires the cluster-admin security chain. Although this operation lists bearerAuth / basicAuth > like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user’s > credentials — only the separate cluster-admin credentials are valid here. **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `dry_run` | `bool` \| `Unset` | | | `data` | `ClusterRebalanceRequest` \| `Unset` | The settings to run a given rebalance with. Every setting is optional; an absent request body is equivalent to a body with every field absent, and means “use the configured settings”. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ConflictError** – If the response status code is 409. A rebalance or cluster configuration change is already in progress, so there is no settled configuration to plan a rebalance against. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.BadGatewayError** – If the response status code is 502. The coordinator was reached, but its response was absent or unusable. - **errors.ServiceUnavailableError** – If the response status code is 503. No coordinator is currently available or reachable. - **errors.GatewayTimeoutError** – If the response status code is 504. The coordinator did not answer before the request timeout. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterBalanceResponse - **Return type:** ClusterBalanceResponse #### Examples **Trigger a cluster-wide leadership rebalance:** ```python def trigger_cluster_rebalance_example() -> None: client = CamundaClient() # Start a dry run first to inspect the plan without transferring any leadership. # Omit dry_run (or set it to False) to execute the rebalance for real. result: ClusterBalanceResponse = client.trigger_cluster_rebalance( data=ClusterRebalanceRequest( replication_lag_threshold=8388608, max_transfer_attempts=3, ), dry_run=True, ) print(f"Cluster balance state: {result.state}") if result.running_rebalance is not None: print(f"Running rebalance: {result.running_rebalance}") for partition in result.partitions: print(f" Partition {partition.partition_id}: {partition.state}") ``` ### unassign_client_from_group() ```python def unassign_client_from_group(group_id, client_id, **kwargs) ``` Unassign a client from a group > Unassigns a client from a group. > > The client is removed as a group member, with associated authorizations, roles, and tenant > assignments no longer applied. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `client_id` | str) – | | The unique identifier of an OAuth client. Minted outside the Camunda REST API: in SaaS by Console, in Self-Managed with OIDC by the external identity provider (e.g. EntraID, Keycloak, Okta). In Self-Managed with Basic authentication, machine-to-machine applications are modelled as users instead — see the user identifier. > Example: my-application. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group with the given ID was not found, or the client is not assigned to this group. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a client from a group:** ```python def unassign_client_from_group_example(group_id: GroupId, client_id: ClientId) -> None: client = CamundaClient() client.unassign_client_from_group( group_id=group_id, client_id=client_id, ) ``` ### unassign_client_from_tenant() ```python def unassign_client_from_tenant(tenant_id, client_id, **kwargs) ``` Unassign a client from a tenant > Unassigns the client from the specified tenant. > > The client can no longer access tenant data. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `client_id` | str) – | | The unique identifier of an OAuth client. Minted outside the Camunda REST API: in SaaS by Console, in Self-Managed with OIDC by the external identity provider (e.g. EntraID, Keycloak, Okta). In Self-Managed with Basic authentication, machine-to-machine applications are modelled as users instead — see the user identifier. > Example: my-application. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The tenant does not exist or the client was not assigned to it. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a client from a tenant:** ```python def unassign_client_from_tenant_example(tenant_id: TenantId, client_id: ClientId) -> None: client = CamundaClient() client.unassign_client_from_tenant( tenant_id=tenant_id, client_id=client_id, ) ``` ### unassign_group_from_tenant() ```python def unassign_group_from_tenant(tenant_id, group_id, **kwargs) ``` Unassign a group from a tenant > Unassigns a group from a specified tenant. > > Members of the group (users, clients) will no longer have access to the tenant’s data - except they > are assigned directly to the tenant. **Parameters:** | Parameter | Type | Description | | ----------- | ----- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The tenant or group was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a group from a tenant:** ```python def unassign_group_from_tenant_example(tenant_id: TenantId, group_id: GroupId) -> None: client = CamundaClient() client.unassign_group_from_tenant( tenant_id=tenant_id, group_id=group_id, ) ``` ### unassign_mapping_rule_from_group() ```python def unassign_mapping_rule_from_group(group_id, mapping_rule_id, **kwargs) ``` Unassign a mapping rule from a group > Unassigns a mapping rule from a group. **Parameters:** | Parameter | Type | Description | | ----------------- | ----- | ------------------------------------------------------------------ | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `mapping_rule_id` | `str` | The unique identifier of a mapping rule. Example: my-mapping-rule. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group or mapping rule with the given ID was not found, or the mapping rule is not assigned to this group. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a mapping rule from a group:** ```python def unassign_mapping_rule_from_group_example(group_id: GroupId, mapping_rule_id: MappingRuleId) -> None: client = CamundaClient() client.unassign_mapping_rule_from_group( group_id=group_id, mapping_rule_id=mapping_rule_id, ) ``` ### unassign_mapping_rule_from_tenant() ```python def unassign_mapping_rule_from_tenant(tenant_id, mapping_rule_id, **kwargs) ``` Unassign a mapping rule from a tenant > Unassigns a single mapping rule from a specified tenant without deleting the rule. **Parameters:** | Parameter | Type | Description | | ----------------- | ----- | ------------------------------------------------------------------ | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `mapping_rule_id` | `str` | The unique identifier of a mapping rule. Example: my-mapping-rule. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The tenant or mapping rule was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a mapping rule from a tenant:** ```python def unassign_mapping_rule_from_tenant_example(tenant_id: TenantId, mapping_rule_id: MappingRuleId) -> None: client = CamundaClient() client.unassign_mapping_rule_from_tenant( tenant_id=tenant_id, mapping_rule_id=mapping_rule_id, ) ``` ### unassign_role_from_client() ```python def unassign_role_from_client(role_id, client_id, **kwargs) ``` Unassign a role from a client > Unassigns the specified role from the client. The client will no longer inherit the authorizations > associated with this role. **Parameters:** | Parameter | Type | Description | | ----------- | ------ | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `client_id` | str) – | | The unique identifier of an OAuth client. Minted outside the Camunda REST API: in SaaS by Console, in Self-Managed with OIDC by the external identity provider (e.g. EntraID, Keycloak, Okta). In Self-Managed with Basic authentication, machine-to-machine applications are modelled as users instead — see the user identifier. > Example: my-application. - **kwargs** (_Any_) - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role or client with the given ID or username was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a role from a client:** ```python def unassign_role_from_client_example(role_id: RoleId, client_id: ClientId) -> None: client = CamundaClient() client.unassign_role_from_client( role_id=role_id, client_id=client_id, ) ``` ### unassign_role_from_group() ```python def unassign_role_from_group(role_id, group_id, **kwargs) ``` Unassign a role from a group > Unassigns the specified role from the group. All group members (user or client) no longer inherit > the authorizations associated with this role. **Parameters:** | Parameter | Type | Description | | ---------- | ----- | ------------------------------------------------------- | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role or group with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a role from a group:** ```python def unassign_role_from_group_example(role_id: RoleId, group_id: GroupId) -> None: client = CamundaClient() client.unassign_role_from_group( role_id=role_id, group_id=group_id, ) ``` ### unassign_role_from_mapping_rule() ```python def unassign_role_from_mapping_rule(role_id, mapping_rule_id, **kwargs) ``` Unassign a role from a mapping rule > Unassigns a role from a mapping rule. **Parameters:** | Parameter | Type | Description | | ----------------- | ----- | ------------------------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `mapping_rule_id` | `str` | The unique identifier of a mapping rule. Example: my-mapping-rule. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role or mapping rule with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a role from a mapping rule:** ```python def unassign_role_from_mapping_rule_example(role_id: RoleId, mapping_rule_id: MappingRuleId) -> None: client = CamundaClient() client.unassign_role_from_mapping_rule( role_id=role_id, mapping_rule_id=mapping_rule_id, ) ``` ### unassign_role_from_tenant() ```python def unassign_role_from_tenant(tenant_id, role_id, **kwargs) ``` Unassign a role from a tenant > Unassigns a role from a specified tenant. > > Users, Clients or Groups, that have the role assigned, will no longer have access to the > tenant’s data - unless they are assigned directly to the tenant. **Parameters:** | Parameter | Type | Description | | ----------- | ----- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The tenant or role was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a role from a tenant:** ```python def unassign_role_from_tenant_example(tenant_id: TenantId, role_id: RoleId) -> None: client = CamundaClient() client.unassign_role_from_tenant( tenant_id=tenant_id, role_id=role_id, ) ``` ### unassign_role_from_user() ```python def unassign_role_from_user(role_id, username, **kwargs) ``` Unassign a role from a user > Unassigns a role from a user. The user will no longer inherit the authorizations associated with > this role. **Parameters:** | Parameter | Type | Description | | ---------- | ----- | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `username` | `str` | The unique name of a user. Example: swillis. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The role or user with the given ID or username was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a role from a user:** ```python def unassign_role_from_user_example(role_id: RoleId, username: Username) -> None: client = CamundaClient() client.unassign_role_from_user( role_id=role_id, username=username, ) ``` ### unassign_user_from_group() ```python def unassign_user_from_group(group_id, username, **kwargs) ``` Unassign a user from a group > Unassigns a user from a group. > > The user is removed as a group member, with associated authorizations, roles, and tenant assignments > no longer applied. **Parameters:** | Parameter | Type | Description | | ---------- | ----- | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `username` | `str` | The unique name of a user. Example: swillis. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The group or user with the given ID was not found, or the user is not assigned to this group. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a user from a group:** ```python def unassign_user_from_group_example(group_id: GroupId, username: Username) -> None: client = CamundaClient() client.unassign_user_from_group( group_id=group_id, username=username, ) ``` ### unassign_user_from_tenant() ```python def unassign_user_from_tenant(tenant_id, username, **kwargs) ``` Unassign a user from a tenant > Unassigns the user from the specified tenant. > > The user can no longer access tenant data. **Parameters:** | Parameter | Type | Description | | ----------- | ----- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `username` | `str` | The unique name of a user. Example: swillis. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The tenant or user was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a user from a tenant:** ```python def unassign_user_from_tenant_example(tenant_id: TenantId, username: Username) -> None: client = CamundaClient() client.unassign_user_from_tenant( tenant_id=tenant_id, username=username, ) ``` ### unassign_user_task() ```python def unassign_user_task(user_task_key, **kwargs) ``` Unassign user task > Removes the assignee of a task with the given key. Unassignment waits for blocking task listeners on > this lifecycle transition. If listener processing is delayed beyond the request timeout, this > endpoint can return 504. Other gateway timeout causes are also possible. Retry with backoff and > inspect listener worker availability and logs when this repeats. **Parameters:** | Parameter | Type | Description | | --------------- | ----- | ------------------------------------- | | `user_task_key` | `str` | System-generated key for a user task. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The user task with the given key was not found. - **errors.ConflictError** – If the response status code is 409. The user task with the given key is in the wrong state currently. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.GatewayTimeoutError** – If the response status code is 504. The request timed out between the gateway and the broker. For these endpoints, this often happens when user task listeners are configured and the corresponding listener job is not completed within the request timeout. Common causes include no available job workers for the listener type, busy or crashed job workers, or delayed job completion. As with any gateway timeout, general timeout causes (for example transient network issues) can also result in a 504 response. Troubleshooting: - verify that job workers for the listener type are running and healthy - check worker logs for crashes, retries, and completion failures - check network connectivity between workers, gateway, and broker - retry with backoff after transient failures - fail without retries if a problem persists - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Unassign a user task:** ```python def unassign_user_task_example(user_task_key: UserTaskKey) -> None: client = CamundaClient() client.unassign_user_task(user_task_key=user_task_key) ``` ### update_agent_instance() ```python def update_agent_instance(agent_instance_key, *, data, **kwargs) ``` Update agent instance > Updates the status of an agent instance and appends a batch of history items > to its conversation history. Each history item created for this request is > echoed back in the response. **Parameters:** | Parameter | Type | Description | | -------------------- | ---------------------------- | ---------------------------------------------------------------------- | | `agent_instance_key` | `str` | System-generated key for an agent instance. Example: 4503599627370496. | | `data` | `AgentInstanceUpdateRequest` | Request to update the mutable state of an agent instance. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The agent instance with the given key was not found. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** AgentInstanceUpdateResult - **Return type:** AgentInstanceUpdateResult #### Examples **Update an agent instance:** ```python def update_agent_instance_example( agent_instance_key: AgentInstanceKey, element_instance_key: ElementInstanceKey, job_key: JobKey, # Engine-minted: take the token from the job activation response, never build one. job_lease_token: JobLeaseToken, ) -> None: client = CamundaClient() # Appending conversation history is part of an update; there is no separate # history-item endpoint. result = client.update_agent_instance( agent_instance_key=agent_instance_key, data=AgentInstanceUpdateRequest( element_instance_key=element_instance_key, job_key=job_key, job_lease_token=job_lease_token, status=AgentInstanceUpdateRequestStatus.THINKING, history=[ AgentInstanceHistoryItem( history_item_id=HistoryItemId("assistant-1"), loop_iteration=1, role=AgentInstanceHistoryItemRole.ASSISTANT, content=[ TextContent(content_type="TEXT", text="How can I help you today?"), ], produced_at=datetime.datetime.now(datetime.timezone.utc), ), ], ), ) for item in result.created_history: print(f"Appended history item {item.history_item_id}: {item.history_item_key}") ``` ### update_authorization() ```python def update_authorization(authorization_key, *, data, **kwargs) ``` Update authorization > Update the authorization with the given key. **Parameters:** | Parameter | Type | Description | | ------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------- | | `authorization_key` | `str` | System-generated key for an authorization. Example: 2251799813684332. | | `data` | `AuthorizationIdBasedRequest` \| `AuthorizationPropertyBasedRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The authorization with the authorizationKey was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Update an authorization:** ```python def update_authorization_example(authorization_key: AuthorizationKey) -> None: client = CamundaClient() client.update_authorization( authorization_key=authorization_key, data=AuthorizationIdBasedRequest( resource_type=AuthorizationIdBasedRequestResourceType.PROCESS_DEFINITION, permission_types=[ AuthorizationIdBasedRequestPermissionTypesItem.READ, AuthorizationIdBasedRequestPermissionTypesItem.UPDATE, AuthorizationIdBasedRequestPermissionTypesItem.DELETE, ], resource_id="my-process", owner_type=OwnerTypeEnum.USER, owner_id="user@example.com", ), ) ``` ### update_global_cluster_variable() ```python def update_global_cluster_variable(name, *, data, **kwargs) ``` Update a global-scoped cluster variable > Updates the value of an existing global cluster variable. > > The variable must exist, otherwise a 404 error is returned. **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------- | | `name` | `str` | The name of a cluster variable. Unique within its scope (global or tenant- specific). Example: feature-flag-checkout. | | `data` | `UpdateClusterVariableRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Cluster variable not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterVariableResult - **Return type:** ClusterVariableResult #### Examples **Update a global cluster variable:** ```python def update_global_cluster_variable_example(name: ClusterVariableName) -> None: client = CamundaClient() result = client.update_global_cluster_variable( name=name, data=UpdateClusterVariableRequest( value={"key": "updated-value"}, ), ) print(f"Updated variable: {result.name}") ``` ### update_global_task_listener() ```python def update_global_task_listener(id, *, data, **kwargs) ``` Update global user task listener > Updates a global user task listener. **Parameters:** | Parameter | Type | Description | | --------- | --------------------------------- | ---------------------------------------------------------------------- | | `id` | `str` | The user-defined id for the global listener Example: GlobalListener_1. | | `data` | `UpdateGlobalTaskListenerRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The global user task listener was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GlobalTaskListenerResult - **Return type:** GlobalTaskListenerResult #### Examples **Update a global task listener:** ```python def update_global_task_listener_example(listener_id: GlobalListenerId) -> None: client = CamundaClient() result = client.update_global_task_listener( id=listener_id, data=UpdateGlobalTaskListenerRequest( event_types=[GlobalTaskListenerEventTypeEnum.COMPLETING], type_="updated-task-listener", ), ) print(f"Updated listener: {result.id}") ``` ### update_group() ```python def update_group(group_id, *, data, **kwargs) ``` Update group > Update a group with the given ID. **Parameters:** | Parameter | Type | Description | | ---------- | -------------------- | ------------------------------------------------------- | | `group_id` | `str` | The unique identifier of a group. Example: engineering. | | `data` | `GroupUpdateRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The group with the given ID was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** GroupUpdateResult - **Return type:** GroupUpdateResult #### Examples **Update a group:** ```python def update_group_example(group_id: GroupId) -> None: client = CamundaClient() client.update_group( group_id=group_id, data=GroupUpdateRequest(name="engineering-team"), ) ``` ### update_job() ```python def update_job(job_key, *, data, **kwargs) ``` Update job > Update a job with the given key. **Parameters:** | Parameter | Type | Description | | --------- | ------------------ | ---------------------------------------------------------- | | `job_key` | `str` | System-generated key for a job. Example: 2251799813653498. | | `data` | `JobUpdateRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The job with the jobKey is not found. - **errors.ConflictError** – If the response status code is 409. The job with the given key is in the wrong state currently. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Update a job:** ```python def update_job_example(job_key: JobKey) -> None: client = CamundaClient() client.update_job( job_key=job_key, data=JobUpdateRequest( changeset=JobChangeset( retries=3, ), ), ) ``` ### update_jobs_batch_operation() ```python def update_jobs_batch_operation(*, data, **kwargs) ``` Update jobs (batch) > Creates a batch operation to update jobs matching the given filter. At least one changeset field > must be non-null. This is done asynchronously; the progress can be tracked using the > batchOperationKey from the response and the batch operation status endpoint (/batch- > operations/{batchOperationKey}). **Parameters:** | Parameter | Type | Description | | --------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `data` | `JobBatchUpdateRequest` | The filter and changeset for a batch job update operation. The filter defines which jobs are updated; the changeset defines what to update. At least one changeset field must be non-null. | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The job batch update operation failed. More details are provided in the response body. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** BatchOperationCreatedResult - **Return type:** BatchOperationCreatedResult #### Examples **Update jobs in batch:** ```python def update_jobs_batch_operation_example() -> None: client = CamundaClient() result = client.update_jobs_batch_operation( data=JobBatchUpdateRequest( filter_=JobBatchUpdateRequestFilter( type_="my-job-type", ), changeset=JobBatchUpdateRequestChangeset( retries=3, ), ), ) print(f"Batch operation key: {result.batch_operation_key}") ``` ### update_mapping_rule() ```python def update_mapping_rule(mapping_rule_id, *, data=, **kwargs) ``` Update mapping rule > Update a mapping rule. **Parameters:** | Parameter | Type | Description | | ----------------- | ------------------------------------- | ------------------------------------------------------------------ | | `mapping_rule_id` | `str` | The unique identifier of a mapping rule. Example: my-mapping-rule. | | `data` | `MappingRuleUpdateRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. The request to update a mapping rule was denied. More details are provided in the response body. - **errors.NotFoundError** – If the response status code is 404. The request to update a mapping rule was denied. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** MappingRuleUpdateResult - **Return type:** MappingRuleUpdateResult #### Examples **Update a mapping rule:** ```python def update_mapping_rule_example(mapping_rule_id: MappingRuleId) -> None: client = CamundaClient() client.update_mapping_rule( mapping_rule_id=mapping_rule_id, data=MappingRuleUpdateRequest( claim_name="groups", claim_value="senior-engineering", name="Senior Engineering Mapping", ), ) ``` ### update_role() ```python def update_role(role_id, *, data, **kwargs) ``` Update role > Update a role with the given ID. **Parameters:** | Parameter | Type | Description | | --------- | ------------------- | ------------------------------------------------ | | `role_id` | `str` | The unique identifier of a role. Example: admin. | | `data` | `RoleUpdateRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.NotFoundError** – If the response status code is 404. The role with the ID is not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** RoleUpdateResult - **Return type:** RoleUpdateResult #### Examples **Update a role:** ```python def update_role_example(role_id: RoleId) -> None: client = CamundaClient() client.update_role( role_id=role_id, data=RoleUpdateRequest(name="senior-developer"), ) ``` ### update_tenant() ```python def update_tenant(tenant_id, *, data, **kwargs) ``` Update tenant > Updates an existing tenant. **Parameters:** | Parameter | Type | Description | | ----------- | --------------------- | --------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `data` | `TenantUpdateRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Not found. The tenant was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** TenantUpdateResult - **Return type:** TenantUpdateResult #### Examples **Update a tenant:** ```python def update_tenant_example(tenant_id: TenantId) -> None: client = CamundaClient() client.update_tenant( tenant_id=tenant_id, data=TenantUpdateRequest(name="Acme Corp International"), ) ``` ### update_tenant_cluster_variable() ```python def update_tenant_cluster_variable(tenant_id, name, *, data, **kwargs) ``` Update a tenant-scoped cluster variable > Updates the value of an existing tenant-scoped cluster variable. > > The variable must exist, otherwise a 404 error is returned. **Parameters:** | Parameter | Type | Description | | ----------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------- | | `tenant_id` | `str` | The unique identifier of the tenant. Example: customer-service. | | `name` | `str` | The name of a cluster variable. Unique within its scope (global or tenant- specific). Example: feature-flag-checkout. | | `data` | `UpdateClusterVariableRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.UnauthorizedError** – If the response status code is 401. The request lacks valid authentication credentials. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. Cluster variable not found - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** ClusterVariableResult - **Return type:** ClusterVariableResult #### Examples **Update a tenant cluster variable:** ```python def update_tenant_cluster_variable_example(tenant_id: TenantId, name: ClusterVariableName) -> None: client = CamundaClient() result = client.update_tenant_cluster_variable( tenant_id=tenant_id, name=name, data=UpdateClusterVariableRequest( value={"key": "updated-tenant-value"}, ), ) print(f"Updated variable: {result.name}") ``` ### update_user() ```python def update_user(username, *, data, **kwargs) ``` Update user > Updates a user. **Parameters:** | Parameter | Type | Description | | ---------- | ------------------- | -------------------------------------------- | | `username` | `str` | The unique name of a user. Example: swillis. | | `data` | `UserUpdateRequest` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.ForbiddenError** – If the response status code is 403. Forbidden. The request is not allowed. - **errors.NotFoundError** – If the response status code is 404. The user was not found. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** UserUpdateResult - **Return type:** UserUpdateResult #### Examples **Update a user:** ```python def update_user_example(username: Username) -> None: client = CamundaClient() client.update_user( username=username, data=UserUpdateRequest( name="Jane Smith", email="jsmith@example.com", ), ) ``` ### update_user_task() ```python def update_user_task(user_task_key, *, data=, **kwargs) ``` Update user task > Update a user task with the given key. Updates wait for blocking task listeners on this lifecycle > transition. If listener processing is delayed beyond the request timeout, this endpoint can return 504. Other gateway timeout causes are also possible. Retry with backoff and inspect listener worker availability and logs when this repeats. **Parameters:** | Parameter | Type | Description | | --------------- | ---------------------------------- | ------------------------------------- | | `user_task_key` | `str` | System-generated key for a user task. | | `data` | `UserTaskUpdateRequest` \| `Unset` | | | `kwargs` | `Any` | | - **Raises:** - **errors.BadRequestError** – If the response status code is 400. The provided data is not valid. - **errors.NotFoundError** – If the response status code is 404. The user task with the given key was not found. - **errors.ConflictError** – If the response status code is 409. The user task with the given key is in the wrong state currently. More details are provided in the response body. - **errors.InternalServerErrorError** – If the response status code is 500. An internal error occurred while processing the request. - **errors.ServiceUnavailableError** – If the response status code is 503. The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server’s compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains RESOURCE_EXHAUSTED. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: [internal processing](../../../components/zeebe/technical-concepts/internal-processing.md#handling-backpressure) . - **errors.GatewayTimeoutError** – If the response status code is 504. The request timed out between the gateway and the broker. For these endpoints, this often happens when user task listeners are configured and the corresponding listener job is not completed within the request timeout. Common causes include no available job workers for the listener type, busy or crashed job workers, or delayed job completion. As with any gateway timeout, general timeout causes (for example transient network issues) can also result in a 504 response. Troubleshooting: - verify that job workers for the listener type are running and healthy - check worker logs for crashes, retries, and completion failures - check network connectivity between workers, gateway, and broker - retry with backoff after transient failures - fail without retries if a problem persists - **errors.UnexpectedStatus** – If the response status code is not documented. - **httpx.TimeoutException** – If the request takes longer than Client.timeout. - **Returns:** None - **Return type:** None #### Examples **Update a user task:** ```python def update_user_task_example(user_task_key: UserTaskKey) -> None: client = CamundaClient() client.update_user_task( user_task_key=user_task_key, data=UserTaskUpdateRequest( changeset=Changeset( due_date=datetime.datetime(2025, 12, 31, 23, 59, 59), ), ), ) ``` --- ## Configuration(4) ## Client ```python class Client(base_url, , raise_on_unexpected_status=False, cookies=NOTHING, headers=NOTHING, timeout=None, verify_ssl=True, follow_redirects=False, httpx_args=NOTHING) ``` Bases: `object` A class for keeping track of data related to the API The following are accepted as keyword arguments and will be used to construct httpx Clients internally: > `base_url`: The base URL for the API, all requests are made to a relative path to this URL > `cookies`: A dictionary of cookies to be sent with every request > `headers`: A dictionary of headers to be sent with every request > `timeout`: The maximum amount of a time a request can take. API functions will raise > httpx.TimeoutException if this is exceeded. > `verify_ssl`: Whether or not to verify the SSL certificate of the API server. This should be True in production, > but can be set to False for testing purposes. > `follow_redirects`: Whether or not to follow redirects. Default value is False. > `httpx_args`: A dictionary of additional arguments to be passed to the `httpx.Client` and `httpx.AsyncClient` constructor. **Parameters:** | Parameter | Type | Description | | ---------------------------- | ----------------------------------- | ----------- | | `base_url` | `str` | | | `raise_on_unexpected_status` | `bool` | | | `cookies` | dict [str , str ] | | | `headers` | dict [str , str ] | | | `timeout` | `httpx.Timeout` \| `None` | | | `verify_ssl` | `str` \| `bool` \| `ssl.SSLContext` | | | `follow_redirects` | `bool` | | | `httpx_args` | dict [str , Any ] | | ### raise_on_unexpected_status Whether or not to raise an errors.UnexpectedStatus if the API returns a status code that was not documented in the source OpenAPI document. Can also be provided as a keyword argument to the constructor. - **Type:** bool ### get_async_httpx_client() ```python def get_async_httpx_client() ``` Get the underlying httpx.AsyncClient, constructing a new one if not previously set - **Return type:** _AsyncClient_ ### get_httpx_client() ```python def get_httpx_client() ``` Get the underlying httpx.Client, constructing a new one if not previously set - **Return type:** _Client_ ### raise_on_unexpected_status ```python raise_on_unexpected_status: bool ``` ### set_async_httpx_client() ```python def set_async_httpx_client(async_client) ``` Manually set the underlying httpx.AsyncClient **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout. - **Parameters:** **async_client** (_AsyncClient_) - **Return type:** [_Client_](#client) ### set_httpx_client() ```python def set_httpx_client(client) ``` Manually set the underlying httpx.Client **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout. - **Parameters:** **client** (_Client_) - **Return type:** [_Client_](#client) ### with_cookies() ```python def with_cookies(cookies) ``` Get a new client matching this one with additional cookies - **Parameters:** **cookies** (_dict_ _[__str_ _,_ _str_ _]_) - **Return type:** [_Client_](#client) ### with_headers() ```python def with_headers(headers) ``` Get a new client matching this one with additional headers - **Parameters:** **headers** (_dict_ _[__str_ _,_ _str_ _]_) - **Return type:** [_Client_](#client) ### with_timeout() ```python def with_timeout(timeout) ``` Get a new client matching this one with a new timeout configuration - **Parameters:** **timeout** (_Timeout_) - **Return type:** [_Client_](#client) ## AuthenticatedClient ```python class AuthenticatedClient(base_url, token, prefix='Bearer', auth_header_name='Authorization', , raise_on_unexpected_status=False, cookies=NOTHING, headers=NOTHING, timeout=None, verify_ssl=True, follow_redirects=False, httpx_args=NOTHING) ``` Bases: `object` A Client which has been authenticated for use on secured endpoints The following are accepted as keyword arguments and will be used to construct httpx Clients internally: > `base_url`: The base URL for the API, all requests are made to a relative path to this URL > `cookies`: A dictionary of cookies to be sent with every request > `headers`: A dictionary of headers to be sent with every request > `timeout`: The maximum amount of a time a request can take. API functions will raise > httpx.TimeoutException if this is exceeded. > `verify_ssl`: Whether or not to verify the SSL certificate of the API server. This should be True in production, > but can be set to False for testing purposes. > `follow_redirects`: Whether or not to follow redirects. Default value is False. > `httpx_args`: A dictionary of additional arguments to be passed to the `httpx.Client` and `httpx.AsyncClient` constructor. **Parameters:** | Parameter | Type | Description | | ---------------------------- | ----------------------------------- | ----------- | | `base_url` | `str` | | | `token` | `str` | | | `prefix` | `str` | | | `auth_header_name` | `str` | | | `raise_on_unexpected_status` | `bool` | | | `cookies` | dict [str , str ] | | | `headers` | dict [str , str ] | | | `timeout` | `httpx.Timeout` \| `None` | | | `verify_ssl` | `str` \| `bool` \| `ssl.SSLContext` | | | `follow_redirects` | `bool` | | | `httpx_args` | dict [str , Any ] | | ### raise_on_unexpected_status Whether or not to raise an errors.UnexpectedStatus if the API returns a status code that was not documented in the source OpenAPI document. Can also be provided as a keyword argument to the constructor. - **Type:** bool ### token The token to use for authentication - **Type:** str ### prefix The prefix to use for the Authorization header - **Type:** str ### auth_header_name The name of the Authorization header - **Type:** str ### auth_header_name ```python auth_header_name: str ``` ### get_async_httpx_client() ```python def get_async_httpx_client() ``` Get the underlying httpx.AsyncClient, constructing a new one if not previously set - **Return type:** _AsyncClient_ ### get_httpx_client() ```python def get_httpx_client() ``` Get the underlying httpx.Client, constructing a new one if not previously set - **Return type:** _Client_ ### prefix ```python prefix: str ``` ### raise_on_unexpected_status ```python raise_on_unexpected_status: bool ``` ### set_async_httpx_client() ```python def set_async_httpx_client(async_client) ``` Manually set the underlying httpx.AsyncClient **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout. - **Parameters:** **async_client** (_AsyncClient_) - **Return type:** [_AuthenticatedClient_](#authenticatedclient) ### set_httpx_client() ```python def set_httpx_client(client) ``` Manually set the underlying httpx.Client **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout. - **Parameters:** **client** (_Client_) - **Return type:** [_AuthenticatedClient_](#authenticatedclient) ### token ```python token: str ``` ### with_cookies() ```python def with_cookies(cookies) ``` Get a new client matching this one with additional cookies - **Parameters:** **cookies** (_dict_ _[__str_ _,_ _str_ _]_) - **Return type:** [_AuthenticatedClient_](#authenticatedclient) ### with_headers() ```python def with_headers(headers) ``` Get a new client matching this one with additional headers - **Parameters:** **headers** (_dict_ _[__str_ _,_ _str_ _]_) - **Return type:** [_AuthenticatedClient_](#authenticatedclient) ### with_timeout() ```python def with_timeout(timeout) ``` Get a new client matching this one with a new timeout configuration - **Parameters:** **timeout** (_Timeout_) - **Return type:** [_AuthenticatedClient_](#authenticatedclient) --- ## Python SDK API Reference # API Reference - [CamundaClient](client.md) - [`CamundaClient`](client.md#camunda_orchestration_sdk.CamundaClient) - [CamundaAsyncClient](async-client.md) - [`CamundaAsyncClient`](async-client.md#camunda_orchestration_sdk.CamundaAsyncClient) - [Configuration](configuration.md) - [`Client`](configuration.md#camunda_orchestration_sdk.Client) - [`AuthenticatedClient`](configuration.md#camunda_orchestration_sdk.AuthenticatedClient) - [Runtime](runtime.md) - [Authentication](runtime.md#module-camunda_orchestration_sdk.runtime.auth) - [Logging](runtime.md#module-camunda_orchestration_sdk.runtime.logging) - [Job Worker](runtime.md#module-camunda_orchestration_sdk.runtime.job_worker) - [Configuration Resolver](runtime.md#module-camunda_orchestration_sdk.runtime.configuration_resolver) - [Semantic Types](types.md) - [`AgentDefinitionKey`](types.md#camunda_orchestration_sdk.semantic_types.AgentDefinitionKey) - [`AgentHistoryItemKey`](types.md#camunda_orchestration_sdk.semantic_types.AgentHistoryItemKey) - [`AgentInstanceKey`](types.md#camunda_orchestration_sdk.semantic_types.AgentInstanceKey) - [`AuditLogEntityKey`](types.md#camunda_orchestration_sdk.semantic_types.AuditLogEntityKey) - [`AuditLogKey`](types.md#camunda_orchestration_sdk.semantic_types.AuditLogKey) - [`AuthorizationKey`](types.md#camunda_orchestration_sdk.semantic_types.AuthorizationKey) - [`BatchOperationKey`](types.md#camunda_orchestration_sdk.semantic_types.BatchOperationKey) - [`BusinessId`](types.md#camunda_orchestration_sdk.semantic_types.BusinessId) - [`ClientId`](types.md#camunda_orchestration_sdk.semantic_types.ClientId) - [`ClusterVariableName`](types.md#camunda_orchestration_sdk.semantic_types.ClusterVariableName) - [`ConditionalEvaluationKey`](types.md#camunda_orchestration_sdk.semantic_types.ConditionalEvaluationKey) - [`DecisionDefinitionId`](types.md#camunda_orchestration_sdk.semantic_types.DecisionDefinitionId) - [`DecisionDefinitionKey`](types.md#camunda_orchestration_sdk.semantic_types.DecisionDefinitionKey) - [`DecisionEvaluationInstanceKey`](types.md#camunda_orchestration_sdk.semantic_types.DecisionEvaluationInstanceKey) - [`DecisionEvaluationKey`](types.md#camunda_orchestration_sdk.semantic_types.DecisionEvaluationKey) - [`DecisionInstanceKey`](types.md#camunda_orchestration_sdk.semantic_types.DecisionInstanceKey) - [`DecisionRequirementsKey`](types.md#camunda_orchestration_sdk.semantic_types.DecisionRequirementsKey) - [`DeploymentKey`](types.md#camunda_orchestration_sdk.semantic_types.DeploymentKey) - [`DocumentId`](types.md#camunda_orchestration_sdk.semantic_types.DocumentId) - [`ElementId`](types.md#camunda_orchestration_sdk.semantic_types.ElementId) - [`ElementInstanceKey`](types.md#camunda_orchestration_sdk.semantic_types.ElementInstanceKey) - [`EndCursor`](types.md#camunda_orchestration_sdk.semantic_types.EndCursor) - [`FormId`](types.md#camunda_orchestration_sdk.semantic_types.FormId) - [`FormKey`](types.md#camunda_orchestration_sdk.semantic_types.FormKey) - [`GlobalListenerId`](types.md#camunda_orchestration_sdk.semantic_types.GlobalListenerId) - [`GroupId`](types.md#camunda_orchestration_sdk.semantic_types.GroupId) - [`HistoryItemId`](types.md#camunda_orchestration_sdk.semantic_types.HistoryItemId) - [`IncidentKey`](types.md#camunda_orchestration_sdk.semantic_types.IncidentKey) - [`JobKey`](types.md#camunda_orchestration_sdk.semantic_types.JobKey) - [`JobLeaseToken`](types.md#camunda_orchestration_sdk.semantic_types.JobLeaseToken) - [`MappingRuleId`](types.md#camunda_orchestration_sdk.semantic_types.MappingRuleId) - [`MessageKey`](types.md#camunda_orchestration_sdk.semantic_types.MessageKey) - [`MessageSubscriptionKey`](types.md#camunda_orchestration_sdk.semantic_types.MessageSubscriptionKey) - [`ProcessDefinitionId`](types.md#camunda_orchestration_sdk.semantic_types.ProcessDefinitionId) - [`ProcessDefinitionKey`](types.md#camunda_orchestration_sdk.semantic_types.ProcessDefinitionKey) - [`ProcessInstanceKey`](types.md#camunda_orchestration_sdk.semantic_types.ProcessInstanceKey) - [`RoleId`](types.md#camunda_orchestration_sdk.semantic_types.RoleId) - [`SignalKey`](types.md#camunda_orchestration_sdk.semantic_types.SignalKey) - [`StartCursor`](types.md#camunda_orchestration_sdk.semantic_types.StartCursor) - [`Tag`](types.md#camunda_orchestration_sdk.semantic_types.Tag) - [`TenantId`](types.md#camunda_orchestration_sdk.semantic_types.TenantId) - [`UserTaskKey`](types.md#camunda_orchestration_sdk.semantic_types.UserTaskKey) - [`Username`](types.md#camunda_orchestration_sdk.semantic_types.Username) - [`VariableKey`](types.md#camunda_orchestration_sdk.semantic_types.VariableKey) - [`lift_resource_key()`](types.md#camunda_orchestration_sdk.semantic_types.lift_resource_key) - [`lift_scope_key()`](types.md#camunda_orchestration_sdk.semantic_types.lift_scope_key) - [`try_lift_resource_key()`](types.md#camunda_orchestration_sdk.semantic_types.try_lift_resource_key) - [`try_lift_scope_key()`](types.md#camunda_orchestration_sdk.semantic_types.try_lift_scope_key) --- ## Runtime(3) ## Authentication ## AsyncAuthProvider ```python class AsyncAuthProvider(*args, **kwargs) ``` Bases: `Protocol` Async auth provider variant. If an auth provider implements this protocol, async clients will prefer it. ### aget_headers() ```python async def aget_headers() ``` - **Return type:** _Mapping_[str, str] ## AsyncOAuthClientCredentialsAuthProvider ```python class AsyncOAuthClientCredentialsAuthProvider(, oauth_url, client_id, client_secret, audience, cache_dir=None, disk_cache_disable=False, saas_401_cooldown_s=30.0, transport=None, timeout=None, logger=None, clock=None) ``` Bases: `object` OAuth 2.0 Client Credentials provider with in-memory caching. This is designed for async clients. **Parameters:** | Parameter | Type | Description | | --------------------- | ------------------------------------ | ----------- | | `oauth_url` | `str` | | | `client_id` | `str` | | | `client_secret` | `str` | | | `audience` | `str` | | | `cache_dir` | `str` \| `None` | | | `disk_cache_disable` | `bool` | | | `saas_401_cooldown_s` | `float` | | | `transport` | `httpx.AsyncBaseTransport` \| `None` | | | `timeout` | `float` \| `None` | | | `logger` | [SdkLogger](#sdklogger) \| `None` | | | `clock` | `Clock` \| `None` | | ### aclose() ```python async def aclose() ``` Close the underlying async HTTP client used for token requests. - **Return type:** None ### aget_headers() ```python async def aget_headers() ``` - **Return type:** _Mapping_[str, str] ### get_headers() ```python def get_headers() ``` Sync fallback satisfying the `AuthProvider` protocol. Returns cached token headers if a valid token is already held, otherwise returns empty headers (the next async request hook will call `aget_headers` to fetch a fresh token). - **Return type:** _Mapping_[str, str] ## AuthProvider ```python class AuthProvider(*args, **kwargs) ``` Bases: `Protocol` Provides per-request authentication headers. Implementations are expected to be lightweight and safe to call for every request. ### get_headers() ```python def get_headers() ``` - **Return type:** _Mapping_[str, str] ## BasicAuthProvider ```python class BasicAuthProvider(, username, password) ``` Bases: `object` HTTP Basic auth provider. **Parameters:** | Parameter | Type | Description | | ---------- | ----- | ----------- | | `username` | `str` | | | `password` | `str` | | ### get_headers() ```python def get_headers() ``` - **Return type:** _Mapping_[str, str] ## NullAuthProvider ```python class NullAuthProvider ``` Bases: `object` Default auth provider that adds no headers. ### get_headers() ```python def get_headers() ``` - **Return type:** dict[str, str] ## OAuthClientCredentialsAuthProvider ```python class OAuthClientCredentialsAuthProvider(, oauth_url, client_id, client_secret, audience, cache_dir=None, disk_cache_disable=False, saas_401_cooldown_s=30.0, transport=None, timeout=None, logger=None, clock=None) ``` Bases: `object` OAuth 2.0 Client Credentials provider with in-memory caching. This is designed for sync clients. **Parameters:** | Parameter | Type | Description | | --------------------- | --------------------------------- | ----------- | | `oauth_url` | `str` | | | `client_id` | `str` | | | `client_secret` | `str` | | | `audience` | `str` | | | `cache_dir` | `str` \| `None` | | | `disk_cache_disable` | `bool` | | | `saas_401_cooldown_s` | `float` | | | `transport` | `httpx.BaseTransport` \| `None` | | | `timeout` | `float` \| `None` | | | `logger` | [SdkLogger](#sdklogger) \| `None` | | | `clock` | `Clock` \| `None` | | ### close() ```python def close() ``` Close the underlying HTTP client used for token requests. Call this when the application is shutting down if you created a provider instance yourself (or if you want deterministic cleanup in tests). - **Return type:** None ### get_headers() ```python def get_headers() ``` - **Return type:** _Mapping_[str, str] ### inject_auth_event_hooks() ```python def inject_auth_event_hooks(httpx_args, auth_provider, *, async_client=False, log_level=None, logger=None) ``` Return a copy of httpx_args with a request hook that applies auth headers. This uses httpx event hooks so we don’t have to inject headers in every generated API call. **Parameters:** | Parameter | Type | Description | | --------------- | --------------------------------- | ----------- | | `httpx_args` | dict [str , Any ] \| `None` | | | `auth_provider` | `object` | | | `async_client` | `bool` | | | `log_level` | `str` \| `None` | | | `logger` | [SdkLogger](#sdklogger) \| `None` | | - **Return type:** dict[str, _Any_] ## Logging Pluggable logger abstraction for the Camunda SDK. Users can inject any logger that implements [`CamundaLogger`](#camundalogger) (stdlib `logging.Logger`, `loguru.logger`, or a custom object with `debug`/`info`/`warning`/`error` methods). When no logger is provided, loguru is used if installed, otherwise logging is silently disabled. ## CamundaLogger ```python class CamundaLogger(*args, **kwargs) ``` Bases: `Protocol` Protocol for a logger injectable into the SDK. Compatible with Python’s `logging.Logger`, `loguru.logger`, or any object that exposes these four methods. ### debug() ```python def debug(msg, *args, **kwargs) ``` **Parameters:** | Parameter | Type | Description | | --------- | ----- | ----------- | | `msg` | `str` | | | `args` | `Any` | | | `kwargs` | `Any` | | - **Return type:** None ### error() ```python def error(msg, *args, **kwargs) ``` **Parameters:** | Parameter | Type | Description | | --------- | ----- | ----------- | | `msg` | `str` | | | `args` | `Any` | | | `kwargs` | `Any` | | - **Return type:** None ### info() ```python def info(msg, *args, **kwargs) ``` **Parameters:** | Parameter | Type | Description | | --------- | ----- | ----------- | | `msg` | `str` | | | `args` | `Any` | | | `kwargs` | `Any` | | - **Return type:** None ### warning() ```python def warning(msg, *args, **kwargs) ``` **Parameters:** | Parameter | Type | Description | | --------- | ----- | ----------- | | `msg` | `str` | | | `args` | `Any` | | | `kwargs` | `Any` | | - **Return type:** None ## NullLogger ```python class NullLogger ``` Bases: `object` Logger that silently discards all messages. ### debug() ```python def debug(msg, *args, **kwargs) ``` **Parameters:** | Parameter | Type | Description | | --------- | ----- | ----------- | | `msg` | `str` | | | `args` | `Any` | | | `kwargs` | `Any` | | - **Return type:** None ### error() ```python def error(msg, *args, **kwargs) ``` **Parameters:** | Parameter | Type | Description | | --------- | ----- | ----------- | | `msg` | `str` | | | `args` | `Any` | | | `kwargs` | `Any` | | - **Return type:** None ### info() ```python def info(msg, *args, **kwargs) ``` **Parameters:** | Parameter | Type | Description | | --------- | ----- | ----------- | | `msg` | `str` | | | `args` | `Any` | | | `kwargs` | `Any` | | - **Return type:** None ### trace() ```python def trace(msg, *args, **kwargs) ``` **Parameters:** | Parameter | Type | Description | | --------- | ----- | ----------- | | `msg` | `str` | | | `args` | `Any` | | | `kwargs` | `Any` | | - **Return type:** None ### warning() ```python def warning(msg, *args, **kwargs) ``` **Parameters:** | Parameter | Type | Description | | --------- | ----- | ----------- | | `msg` | `str` | | | `args` | `Any` | | | `kwargs` | `Any` | | - **Return type:** None ## SdkLogger ```python class SdkLogger(logger, prefix='') ``` Bases: `object` Internal wrapper that normalises logger implementations. Adds `trace()` support (falls back to `debug()` on loggers that lack it) and `bind()` support (uses loguru’s native `bind` when available, otherwise prepends a `[key=value ...]` prefix to messages). **Parameters:** | Parameter | Type | Description | | --------- | ------------------------------- | ----------- | | `logger` | [CamundaLogger](#camundalogger) | | | `prefix` | `str` | | ### bind() ```python def bind(**kwargs) ``` Create a child logger with additional context. If the underlying logger supports `bind()` (e.g. loguru), the native method is used. Otherwise context is rendered as a `[k=v ...]` prefix on each message. - **Parameters:** **kwargs** (_str_) - **Return type:** [_SdkLogger_](#sdklogger) ### debug() ```python def debug(msg, *args, **kwargs) ``` **Parameters:** | Parameter | Type | Description | | --------- | ----- | ----------- | | `msg` | `str` | | | `args` | `Any` | | | `kwargs` | `Any` | | - **Return type:** None ### error() ```python def error(msg, *args, **kwargs) ``` **Parameters:** | Parameter | Type | Description | | --------- | ----- | ----------- | | `msg` | `str` | | | `args` | `Any` | | | `kwargs` | `Any` | | - **Return type:** None ### info() ```python def info(msg, *args, **kwargs) ``` **Parameters:** | Parameter | Type | Description | | --------- | ----- | ----------- | | `msg` | `str` | | | `args` | `Any` | | | `kwargs` | `Any` | | - **Return type:** None ### trace() ```python def trace(msg) ``` - **Parameters:** **msg** (_str_) - **Return type:** None ### warning() ```python def warning(msg, *args, **kwargs) ``` **Parameters:** | Parameter | Type | Description | | --------- | ----- | ----------- | | `msg` | `str` | | | `args` | `Any` | | | `kwargs` | `Any` | | - **Return type:** None ### create_logger() ```python def create_logger(logger=None) ``` Create an [`SdkLogger`](#sdklogger). - **Parameters:** **logger** ([_CamundaLogger_](#camundalogger) _|_ _None_) – A user-supplied logger. When `None`, loguru is used if installed, otherwise a [`NullLogger`](#nulllogger) is used. - **Return type:** [_SdkLogger_](#sdklogger) ## Job Worker ### AsyncJobContext alias of [`ConnectedJobContext`](#connectedjobcontext) ## ConnectedJobContext ```python class ConnectedJobContext(type_, process_definition_id, process_definition_version, element_id, custom_headers, worker, retries, deadline, variables, tenant_id, physical_tenant_id, job_key, process_instance_key, process_definition_key, element_instance_key, kind, listener_event_type, user_task, tags, root_process_instance_key, business_id, priority, job_lease_token, log=NOTHING, , client, clock) ``` Bases: [`JobContext`](#jobcontext) Context for **async** handlers — includes an async client reference. Extends [`JobContext`](#jobcontext) with a `client` attribute that provides access to the Camunda API from within an async job handler. Use `await job.client.method(...)` to call API methods. This context is provided when the execution strategy is `"async"`. For `"thread"` handlers, see [`SyncJobContext`](#syncjobcontext). For `"process"` handlers, see [`JobContext`](#jobcontext). **Parameters:** | Parameter | Type | Description | | ---------------------------- | ---------------------------------------------------------------------------------------------------- | ----------- | | `type_` | `str` | | | `process_definition_id` | [ProcessDefinitionId](types.md#camunda_orchestration_sdk.semantic_types.ProcessDefinitionId) | | | `process_definition_version` | `int` | | | `element_id` | [ElementId](types.md#camunda_orchestration_sdk.semantic_types.ElementId) | | | `custom_headers` | `ActivatedJobResultCustomHeaders` | | | `worker` | `str` | | | `retries` | `int` | | | `deadline` | `int` | | | `variables` | `ActivatedJobResultVariables` | | | `tenant_id` | [TenantId](types.md#camunda_orchestration_sdk.semantic_types.TenantId) | | | `physical_tenant_id` | `str` | | | `job_key` | [JobKey](types.md#camunda_orchestration_sdk.semantic_types.JobKey) | | | `process_instance_key` | [ProcessInstanceKey](types.md#camunda_orchestration_sdk.semantic_types.ProcessInstanceKey) | | | `process_definition_key` | [ProcessDefinitionKey](types.md#camunda_orchestration_sdk.semantic_types.ProcessDefinitionKey) | | | `element_instance_key` | [ElementInstanceKey](types.md#camunda_orchestration_sdk.semantic_types.ElementInstanceKey) | | | `kind` | `JobKindEnum` | | | `listener_event_type` | `JobListenerEventTypeEnum` | | | `user_task` | `ActivatedJobResultUserTask` \| `None` | | | `tags` | list [str ] | | | `root_process_instance_key` | `None` \| [ProcessInstanceKey](types.md#camunda_orchestration_sdk.semantic_types.ProcessInstanceKey) | | | `business_id` | `None` \| [BusinessId](types.md#camunda_orchestration_sdk.semantic_types.BusinessId) | | | `priority` | `int` | | | `job_lease_token` | `None` \| [JobLeaseToken](types.md#camunda_orchestration_sdk.semantic_types.JobLeaseToken) | | | `log` | [SdkLogger](#sdklogger) | | | `client` | [CamundaAsyncClient](async-client.md#camunda_orchestration_sdk.CamundaAsyncClient) | | | `clock` | `Clock` | | ### clock The worker’s clock. Await `job.clock.sleep(...)` rather than `asyncio.sleep(...)` so a handler that waits follows engine time when the engine’s clock is pinned, instead of stalling on the real one. For short in-handler coordination only – spacing a retry, waiting on a resource to settle. A long or business wait belongs in the process as a BPMN timer event: a handler holding a job for minutes occupies a worker slot, risks the job timeout expiring underneath it, and hides the wait from the process model where it cannot be seen or changed. - **Type:** Clock ### client ```python client: [CamundaAsyncClient](async-client.md#camunda_orchestration_sdk.CamundaAsyncClient) ``` ### clock ```python clock: Clock ``` ### _classmethod_ create(job, client, clock, logger=None) **Parameters:** | Parameter | Type | Description | | --------- | --------------------------------- | ----------- | | `job` | `ActivatedJobResult` | | | `client` | `Any` | | | `clock` | `Clock` | | | `logger` | [SdkLogger](#sdklogger) \| `None` | | - **Return type:** [_ConnectedJobContext_](#connectedjobcontext) ## JobContext ```python class JobContext(type_, process_definition_id, process_definition_version, element_id, custom_headers, worker, retries, deadline, variables, tenant_id, physical_tenant_id, job_key, process_instance_key, process_definition_key, element_instance_key, kind, listener_event_type, user_task, tags, root_process_instance_key, business_id, priority, job_lease_token, log=NOTHING) ``` Bases: `ActivatedJobResult` Read-only context for a job execution. Deliberately has no `clock`. This is the context handed to `process` handlers, so it is pickled across a process boundary, and a clock owns a lock (and, once pinned, a live connection to an engine) – neither of which survives the trip. Handlers that need the injected clock run under the `async` or `thread` strategies, whose contexts carry one; see [`ConnectedJobContext`](#connectedjobcontext) and [`SyncJobContext`](#syncjobcontext). **Parameters:** | Parameter | Type | Description | | ---------------------------- | ---------------------------------------------------------------------------------------------------- | ----------- | | `type_` | `str` | | | `process_definition_id` | [ProcessDefinitionId](types.md#camunda_orchestration_sdk.semantic_types.ProcessDefinitionId) | | | `process_definition_version` | `int` | | | `element_id` | [ElementId](types.md#camunda_orchestration_sdk.semantic_types.ElementId) | | | `custom_headers` | `ActivatedJobResultCustomHeaders` | | | `worker` | `str` | | | `retries` | `int` | | | `deadline` | `int` | | | `variables` | `ActivatedJobResultVariables` | | | `tenant_id` | [TenantId](types.md#camunda_orchestration_sdk.semantic_types.TenantId) | | | `physical_tenant_id` | `str` | | | `job_key` | [JobKey](types.md#camunda_orchestration_sdk.semantic_types.JobKey) | | | `process_instance_key` | [ProcessInstanceKey](types.md#camunda_orchestration_sdk.semantic_types.ProcessInstanceKey) | | | `process_definition_key` | [ProcessDefinitionKey](types.md#camunda_orchestration_sdk.semantic_types.ProcessDefinitionKey) | | | `element_instance_key` | [ElementInstanceKey](types.md#camunda_orchestration_sdk.semantic_types.ElementInstanceKey) | | | `kind` | `JobKindEnum` | | | `listener_event_type` | `JobListenerEventTypeEnum` | | | `user_task` | `ActivatedJobResultUserTask` \| `None` | | | `tags` | list [str ] | | | `root_process_instance_key` | `None` \| [ProcessInstanceKey](types.md#camunda_orchestration_sdk.semantic_types.ProcessInstanceKey) | | | `business_id` | `None` \| [BusinessId](types.md#camunda_orchestration_sdk.semantic_types.BusinessId) | | | `priority` | `int` | | | `job_lease_token` | `None` \| [JobLeaseToken](types.md#camunda_orchestration_sdk.semantic_types.JobLeaseToken) | | | `log` | [SdkLogger](#sdklogger) | | ### log A scoped logger bound to this job’s context (job type, job key). Use `job.log.info(...)` etc. inside your handler to emit structured log messages. - **Type:** [SdkLogger](#sdklogger) ### _classmethod_ from_job(job, logger=None) **Parameters:** | Parameter | Type | Description | | --------- | --------------------------------- | ----------- | | `job` | `ActivatedJobResult` | | | `logger` | [SdkLogger](#sdklogger) \| `None` | | - **Return type:** [_JobContext_](#jobcontext) ### log ```python log: [SdkLogger](#sdklogger) ``` ### _exception_ JobError(error_code, message='', variables=None) Bases: `Exception` Raise this exception to throw a BPMN error. **Parameters:** | Parameter | Type | Description | | ------------ | --------------------------- | ----------- | | `error_code` | `str` | | | `message` | `str` | | | `variables` | dict [str , Any ] \| `None` | | ### _exception_ JobFailure(message, retries=None, retry_back_off=0, variables=None) Bases: `Exception` Raise this exception to explicitly fail a job with custom retries/backoff. **Parameters:** | Parameter | Type | Description | | ---------------- | --------------------------- | ----------- | | `message` | `str` | | | `retries` | `int` \| `None` | | | `retry_back_off` | `int` | | | `variables` | dict [str , Any ] \| `None` | | ## JobWorker ```python class JobWorker(client, callback, config, logger=None, execution_strategy='auto', startup_jitter_max_seconds=0, clock=None, random=None) ``` Bases: `object` **Parameters:** | Parameter | Type | Description | | ---------------------------- | ---------------------------------------------------------------------------------- | ----------- | | `client` | [CamundaAsyncClient](async-client.md#camunda_orchestration_sdk.CamundaAsyncClient) | | | `callback` | `JobHandler` | | | `config` | [WorkerConfig](#workerconfig) | | | `logger` | [SdkLogger](#sdklogger) \| `None` | | | `execution_strategy` | `EXECUTION_STRATEGY` | | | `startup_jitter_max_seconds` | `float` | | | `clock` | `Clock` \| `None` | | | `random` | `RandomSource` \| `None` | | ### aclose() ```python async def aclose() ``` Async-aware teardown. Cancels any in-flight job tasks and awaits their cancellation (bounded by a timeout) before delegating to the synchronous `close()`. Prefer this over `stop()`/`close()` from inside a running event loop — it gives cancelled tasks a chance to propagate before the pools they depend on are shut down, which prevents ‘cannot schedule new futures after shutdown’ (and the post-#150 ‘JobWorker is closed’) errors from surfacing as task exceptions. - **Return type:** None ### close() ```python def close() ``` Release any resources this worker lazily allocated. Safe to call multiple times and from multiple threads concurrently. Use as a context manager (`with JobWorker(...) as worker:`) or in a pytest fixture teardown to avoid leaking file descriptors across many short-lived worker instances (see issue #148). Blocks until pools have finished shutdown so file descriptors and worker processes are reliably released before the references are cleared. If invoked from inside a pool worker thread, falls back to a non-waiting shutdown for that pool to avoid a self-join deadlock. If invoked from the worker loop thread, skips joining the worker thread (same self-join hazard). After `close()` returns, accessing `thread_pool`, `process_pool`, or `worker_loop` raises `RuntimeError`; a closed JobWorker cannot be reused. - **Return type:** None ### poll_loop() ```python async def poll_loop() ``` Background polling loop - always async ### _property_ process_pool _: ProcessPoolExecutor_ ### start() ```python def start() ``` ### stop() ```python def stop() ``` ### _property_ thread_pool _: ThreadPoolExecutor_ ### _property_ worker_loop _: AbstractEventLoop_ ## SyncJobContext ```python class SyncJobContext(type_, process_definition_id, process_definition_version, element_id, custom_headers, worker, retries, deadline, variables, tenant_id, physical_tenant_id, job_key, process_instance_key, process_definition_key, element_instance_key, kind, listener_event_type, user_task, tags, root_process_instance_key, business_id, priority, job_lease_token, log=NOTHING, , client, clock) ``` Bases: [`JobContext`](#jobcontext) Context for **thread** handlers — includes a sync client reference. Extends [`JobContext`](#jobcontext) with a `client` attribute that provides access to the Camunda API from within a synchronous (thread) handler. Call `job.client.method(...)` directly — no `await` needed. This context is provided when the execution strategy is `"thread"`. For `"async"` handlers, see [`ConnectedJobContext`](#connectedjobcontext). For `"process"` handlers, see [`JobContext`](#jobcontext). **Parameters:** | Parameter | Type | Description | | ---------------------------- | ---------------------------------------------------------------------------------------------------- | ----------- | | `type_` | `str` | | | `process_definition_id` | [ProcessDefinitionId](types.md#camunda_orchestration_sdk.semantic_types.ProcessDefinitionId) | | | `process_definition_version` | `int` | | | `element_id` | [ElementId](types.md#camunda_orchestration_sdk.semantic_types.ElementId) | | | `custom_headers` | `ActivatedJobResultCustomHeaders` | | | `worker` | `str` | | | `retries` | `int` | | | `deadline` | `int` | | | `variables` | `ActivatedJobResultVariables` | | | `tenant_id` | [TenantId](types.md#camunda_orchestration_sdk.semantic_types.TenantId) | | | `physical_tenant_id` | `str` | | | `job_key` | [JobKey](types.md#camunda_orchestration_sdk.semantic_types.JobKey) | | | `process_instance_key` | [ProcessInstanceKey](types.md#camunda_orchestration_sdk.semantic_types.ProcessInstanceKey) | | | `process_definition_key` | [ProcessDefinitionKey](types.md#camunda_orchestration_sdk.semantic_types.ProcessDefinitionKey) | | | `element_instance_key` | [ElementInstanceKey](types.md#camunda_orchestration_sdk.semantic_types.ElementInstanceKey) | | | `kind` | `JobKindEnum` | | | `listener_event_type` | `JobListenerEventTypeEnum` | | | `user_task` | `ActivatedJobResultUserTask` \| `None` | | | `tags` | list [str ] | | | `root_process_instance_key` | `None` \| [ProcessInstanceKey](types.md#camunda_orchestration_sdk.semantic_types.ProcessInstanceKey) | | | `business_id` | `None` \| [BusinessId](types.md#camunda_orchestration_sdk.semantic_types.BusinessId) | | | `priority` | `int` | | | `job_lease_token` | `None` \| [JobLeaseToken](types.md#camunda_orchestration_sdk.semantic_types.JobLeaseToken) | | | `log` | [SdkLogger](#sdklogger) | | | `client` | [CamundaClient](client.md#camunda_orchestration_sdk.CamundaClient) | | | `clock` | `Clock` | | ### clock The worker’s clock. Call `job.clock.sleep_sync(...)` rather than `time.sleep(...)` so a handler that waits follows engine time when the engine’s clock is pinned, instead of stalling on the real one. For short in-handler coordination only – spacing a retry, waiting on a resource to settle. A long or business wait belongs in the process as a BPMN timer event: a handler holding a job for minutes occupies a worker slot, risks the job timeout expiring underneath it, and hides the wait from the process model where it cannot be seen or changed. - **Type:** Clock ### client ```python client: [CamundaClient](client.md#camunda_orchestration_sdk.CamundaClient) ``` ### clock ```python clock: Clock ``` ### _classmethod_ create(job, client, clock, logger=None) **Parameters:** | Parameter | Type | Description | | --------- | --------------------------------- | ----------- | | `job` | `ActivatedJobResult` | | | `client` | `Any` | | | `clock` | `Clock` | | | `logger` | [SdkLogger](#sdklogger) \| `None` | | - **Return type:** [_SyncJobContext_](#syncjobcontext) ## WorkerConfig ```python class WorkerConfig(job_type, job_timeout_milliseconds=None, request_timeout_milliseconds=None, max_concurrent_jobs=None, fetch_variables=None, worker_name=None, with_lease=False) ``` Bases: `object` User-facing configuration. Fields left as `None` inherit the global default from `CAMUNDA_WORKER_*` environment variables (or the client constructor), falling back to the hardcoded SDK default when neither is set. **Parameters:** | Parameter | Type | Description | | ------------------------------ | --------------------- | ----------- | | `job_type` | `str` | | | `job_timeout_milliseconds` | `int` \| `None` | | | `request_timeout_milliseconds` | `int` \| `None` | | | `max_concurrent_jobs` | `int` \| `None` | | | `fetch_variables` | list [str ] \| `None` | | | `worker_name` | `str` \| `None` | | | `with_lease` | `bool` | | ### fetch_variables ```python fetch_variables: list[str] | None* *= None ``` ### job_timeout_milliseconds ```python job_timeout_milliseconds: int | None* *= None ``` How long the job is reserved for this worker only. Falls back to `CAMUNDA_WORKER_TIMEOUT` env var if not set. ### job_type ```python job_type: str ``` Job type to activate and process. ### max_concurrent_jobs ```python max_concurrent_jobs: int | None* *= None ``` Max jobs executing at once. Falls back to `CAMUNDA_WORKER_MAX_CONCURRENT_JOBS` env var, then `10`. ### request_timeout_milliseconds ```python request_timeout_milliseconds: int | None* *= None ``` Long-poll request timeout in milliseconds. Falls back to `CAMUNDA_WORKER_REQUEST_TIMEOUT` env var, then `0`. ### with_lease ```python with_lease: bool* *= False ``` Activate jobs with a lease. Each job then carries a lease token that the worker sends back on complete, fail, and throw-error, so the engine can fence the command against a superseded activation. Off by default, matching the engine. Requires a server that supports job leases: rather than degrade to unfenced commands, a worker that asked for a lease and is handed a job without a token raises `LeaseNotHonoredError`. ### worker_name ```python worker_name: str | None* *= None ``` Worker identifier. Falls back to `CAMUNDA_WORKER_NAME` env var, then `"camunda-python-sdk-worker"`. ### resolve_worker_config() ```python def resolve_worker_config(config, configuration) ``` Return a new WorkerConfig with `None` fields filled from _configuration_. Precedence: explicit field value > `CAMUNDA_WORKER_*` config > hardcoded default. Raises `ValueError` if `job_timeout_milliseconds` is still unset after merging. **Parameters:** | Parameter | Type | Description | | --------------- | ----------------------------- | ----------- | | `config` | [WorkerConfig](#workerconfig) | | | `configuration` | `Any` | | - **Return type:** [_WorkerConfig_](#workerconfig) ## Configuration Resolver ## CamundaSdkConfigPartial ```python class CamundaSdkConfigPartial ``` Bases: `TypedDict` ### CAMUNDA_AUTH_STRATEGY ```python CAMUNDA_AUTH_STRATEGY: Literal['NONE', 'OAUTH', 'BASIC'] ``` ### CAMUNDA_BASIC_AUTH_PASSWORD ```python CAMUNDA_BASIC_AUTH_PASSWORD: str ``` ### CAMUNDA_BASIC_AUTH_USERNAME ```python CAMUNDA_BASIC_AUTH_USERNAME: str ``` ### CAMUNDA_CLIENT_AUTH_CLIENTID ```python CAMUNDA_CLIENT_AUTH_CLIENTID: str ``` ### CAMUNDA_CLIENT_AUTH_CLIENTSECRET ```python CAMUNDA_CLIENT_AUTH_CLIENTSECRET: str ``` ### CAMUNDA_CLIENT_ID ```python CAMUNDA_CLIENT_ID: str ``` ### CAMUNDA_CLIENT_SECRET ```python CAMUNDA_CLIENT_SECRET: str ``` ### CAMUNDA_LOAD_ENVFILE ```python CAMUNDA_LOAD_ENVFILE: str ``` ### CAMUNDA_MTLS_CA ```python CAMUNDA_MTLS_CA: str ``` ### CAMUNDA_MTLS_CA_PATH ```python CAMUNDA_MTLS_CA_PATH: str ``` ### CAMUNDA_MTLS_CERT ```python CAMUNDA_MTLS_CERT: str ``` ### CAMUNDA_MTLS_CERT_PATH ```python CAMUNDA_MTLS_CERT_PATH: str ``` ### CAMUNDA_MTLS_KEY ```python CAMUNDA_MTLS_KEY: str ``` ### CAMUNDA_MTLS_KEY_PASSPHRASE ```python CAMUNDA_MTLS_KEY_PASSPHRASE: str ``` ### CAMUNDA_MTLS_KEY_PATH ```python CAMUNDA_MTLS_KEY_PATH: str ``` ### CAMUNDA_OAUTH_URL ```python CAMUNDA_OAUTH_URL: str ``` ### CAMUNDA_REST_ADDRESS ```python CAMUNDA_REST_ADDRESS: str ``` ### CAMUNDA_REST_ADDRESS_EXACT ```python CAMUNDA_REST_ADDRESS_EXACT: str | bool ``` ### CAMUNDA_SDK_BACKPRESSURE_PROFILE ```python CAMUNDA_SDK_BACKPRESSURE_PROFILE: str ``` ### CAMUNDA_SDK_LOG_LEVEL ```python CAMUNDA_SDK_LOG_LEVEL: Literal['silent', 'error', 'warn', 'info', 'debug', 'trace', 'silly'] ``` ### CAMUNDA_TENANT_ID ```python CAMUNDA_TENANT_ID: str ``` ### CAMUNDA_TENANT_IDS ```python CAMUNDA_TENANT_IDS: str | list[str] ``` ### CAMUNDA_TOKEN_AUDIENCE ```python CAMUNDA_TOKEN_AUDIENCE: str ``` ### CAMUNDA_TOKEN_CACHE_DIR ```python CAMUNDA_TOKEN_CACHE_DIR: str ``` ### CAMUNDA_TOKEN_DISK_CACHE_DISABLE ```python CAMUNDA_TOKEN_DISK_CACHE_DISABLE: str ``` ### CAMUNDA_WORKER_MAX_CONCURRENT_JOBS ```python CAMUNDA_WORKER_MAX_CONCURRENT_JOBS: str ``` ### CAMUNDA_WORKER_NAME ```python CAMUNDA_WORKER_NAME: str ``` ### CAMUNDA_WORKER_REQUEST_TIMEOUT ```python CAMUNDA_WORKER_REQUEST_TIMEOUT: str ``` ### CAMUNDA_WORKER_STARTUP_JITTER_MAX_SECONDS ```python CAMUNDA_WORKER_STARTUP_JITTER_MAX_SECONDS: str ``` ### CAMUNDA_WORKER_TIMEOUT ```python CAMUNDA_WORKER_TIMEOUT: str ``` ### ZEEBE_REST_ADDRESS ```python ZEEBE_REST_ADDRESS: str ``` ## CamundaSdkConfiguration ```python class CamundaSdkConfiguration(, ZEEBE_REST_ADDRESS='http://localhost:8080/v2', CAMUNDA_REST_ADDRESS='http://localhost:8080/v2', CAMUNDA_REST_ADDRESS_EXACT=False, CAMUNDA_TOKEN_AUDIENCE='zeebe.camunda.io', CAMUNDA_OAUTH_URL='https://login.cloud.camunda.io/oauth/token', CAMUNDA_CLIENT_ID=None, CAMUNDA_CLIENT_SECRET=None, CAMUNDA_CLIENT_AUTH_CLIENTID=None, CAMUNDA_CLIENT_AUTH_CLIENTSECRET=None, CAMUNDA_AUTH_STRATEGY='NONE', CAMUNDA_BASIC_AUTH_USERNAME=None, CAMUNDA_BASIC_AUTH_PASSWORD=None, CAMUNDA_SDK_LOG_LEVEL='error', CAMUNDA_TOKEN_CACHE_DIR=None, CAMUNDA_TOKEN_DISK_CACHE_DISABLE=False, CAMUNDA_SDK_BACKPRESSURE_PROFILE='BALANCED', CAMUNDA_TENANT_ID=None, CAMUNDA_TENANT_IDS=None, CAMUNDA_WORKER_TIMEOUT=None, CAMUNDA_WORKER_MAX_CONCURRENT_JOBS=None, CAMUNDA_WORKER_REQUEST_TIMEOUT=None, CAMUNDA_WORKER_NAME=None, CAMUNDA_WORKER_STARTUP_JITTER_MAX_SECONDS=None, CAMUNDA_MTLS_CERT_PATH=None, CAMUNDA_MTLS_KEY_PATH=None, CAMUNDA_MTLS_CA_PATH=None, CAMUNDA_MTLS_CERT=None, CAMUNDA_MTLS_KEY=None, CAMUNDA_MTLS_CA=None, CAMUNDA_MTLS_KEY_PASSPHRASE=None) ``` Bases: `BaseModel` **Parameters:** | Parameter | Type | Description | | ------------------------------------------- | ------------------------------------------------------------------------------ | ----------- | | `ZEEBE_REST_ADDRESS` | `str` | | | `CAMUNDA_REST_ADDRESS` | `str` | | | `CAMUNDA_REST_ADDRESS_EXACT` | `bool` | | | `CAMUNDA_TOKEN_AUDIENCE` | `str` | | | `CAMUNDA_OAUTH_URL` | `str` | | | `CAMUNDA_CLIENT_ID` | `str` \| `None` | | | `CAMUNDA_CLIENT_SECRET` | `str` \| `None` | | | `CAMUNDA_CLIENT_AUTH_CLIENTID` | `str` \| `None` | | | `CAMUNDA_CLIENT_AUTH_CLIENTSECRET` | `str` \| `None` | | | `CAMUNDA_AUTH_STRATEGY` | Literal [ 'NONE' , 'OAUTH' , 'BASIC' ] | | | `CAMUNDA_BASIC_AUTH_USERNAME` | `str` \| `None` | | | `CAMUNDA_BASIC_AUTH_PASSWORD` | `str` \| `None` | | | `CAMUNDA_SDK_LOG_LEVEL` | Literal [ 'silent' , 'error' , 'warn' , 'info' , 'debug' , 'trace' , 'silly' ] | | | `CAMUNDA_TOKEN_CACHE_DIR` | `str` \| `None` | | | `CAMUNDA_TOKEN_DISK_CACHE_DISABLE` | `bool` | | | `CAMUNDA_SDK_BACKPRESSURE_PROFILE` | Literal [ 'BALANCED' , 'LEGACY' ] | | | `CAMUNDA_TENANT_ID` | `str` \| `None` | | | `CAMUNDA_TENANT_IDS` | list [str ] \| `None` | | | `CAMUNDA_WORKER_TIMEOUT` | `int` \| `None` | | | `CAMUNDA_WORKER_MAX_CONCURRENT_JOBS` | `int` \| `None` | | | `CAMUNDA_WORKER_REQUEST_TIMEOUT` | `int` \| `None` | | | `CAMUNDA_WORKER_NAME` | `str` \| `None` | | | `CAMUNDA_WORKER_STARTUP_JITTER_MAX_SECONDS` | `float` \| `None` | | | `CAMUNDA_MTLS_CERT_PATH` | `str` \| `None` | | | `CAMUNDA_MTLS_KEY_PATH` | `str` \| `None` | | | `CAMUNDA_MTLS_CA_PATH` | `str` \| `None` | | | `CAMUNDA_MTLS_CERT` | `str` \| `None` | | | `CAMUNDA_MTLS_KEY` | `str` \| `None` | | | `CAMUNDA_MTLS_CA` | `str` \| `None` | | | `CAMUNDA_MTLS_KEY_PASSPHRASE` | `str` \| `None` | | ### CAMUNDA_AUTH_STRATEGY ```python CAMUNDA_AUTH_STRATEGY: CamundaAuthStrategy ``` ### CAMUNDA_BASIC_AUTH_PASSWORD ```python CAMUNDA_BASIC_AUTH_PASSWORD: str | None ``` ### CAMUNDA_BASIC_AUTH_USERNAME ```python CAMUNDA_BASIC_AUTH_USERNAME: str | None ``` ### CAMUNDA_CLIENT_AUTH_CLIENTID ```python CAMUNDA_CLIENT_AUTH_CLIENTID: str | None ``` ### CAMUNDA_CLIENT_AUTH_CLIENTSECRET ```python CAMUNDA_CLIENT_AUTH_CLIENTSECRET: str | None ``` ### CAMUNDA_CLIENT_ID ```python CAMUNDA_CLIENT_ID: str | None ``` ### CAMUNDA_CLIENT_SECRET ```python CAMUNDA_CLIENT_SECRET: str | None ``` ### CAMUNDA_MTLS_CA ```python CAMUNDA_MTLS_CA: str | None ``` ### CAMUNDA_MTLS_CA_PATH ```python CAMUNDA_MTLS_CA_PATH: str | None ``` ### CAMUNDA_MTLS_CERT ```python CAMUNDA_MTLS_CERT: str | None ``` ### CAMUNDA_MTLS_CERT_PATH ```python CAMUNDA_MTLS_CERT_PATH: str | None ``` ### CAMUNDA_MTLS_KEY ```python CAMUNDA_MTLS_KEY: str | None ``` ### CAMUNDA_MTLS_KEY_PASSPHRASE ```python CAMUNDA_MTLS_KEY_PASSPHRASE: str | None ``` ### CAMUNDA_MTLS_KEY_PATH ```python CAMUNDA_MTLS_KEY_PATH: str | None ``` ### CAMUNDA_OAUTH_URL ```python CAMUNDA_OAUTH_URL: str ``` ### CAMUNDA_REST_ADDRESS ```python CAMUNDA_REST_ADDRESS: str ``` ### CAMUNDA_REST_ADDRESS_EXACT ```python CAMUNDA_REST_ADDRESS_EXACT: bool ``` ### CAMUNDA_SDK_BACKPRESSURE_PROFILE ```python CAMUNDA_SDK_BACKPRESSURE_PROFILE: CamundaBackpressureProfile ``` ### CAMUNDA_SDK_LOG_LEVEL ```python CAMUNDA_SDK_LOG_LEVEL: CamundaSdkLogLevel ``` ### CAMUNDA_TENANT_ID ```python CAMUNDA_TENANT_ID: str | None ``` ### CAMUNDA_TENANT_IDS ```python CAMUNDA_TENANT_IDS: list[str] | None ``` ### CAMUNDA_TOKEN_AUDIENCE ```python CAMUNDA_TOKEN_AUDIENCE: str ``` ### CAMUNDA_TOKEN_CACHE_DIR ```python CAMUNDA_TOKEN_CACHE_DIR: str | None ``` ### CAMUNDA_TOKEN_DISK_CACHE_DISABLE ```python CAMUNDA_TOKEN_DISK_CACHE_DISABLE: bool ``` ### CAMUNDA_WORKER_MAX_CONCURRENT_JOBS ```python CAMUNDA_WORKER_MAX_CONCURRENT_JOBS: int | None ``` ### CAMUNDA_WORKER_NAME ```python CAMUNDA_WORKER_NAME: str | None ``` ### CAMUNDA_WORKER_REQUEST_TIMEOUT ```python CAMUNDA_WORKER_REQUEST_TIMEOUT: int | None ``` ### CAMUNDA_WORKER_STARTUP_JITTER_MAX_SECONDS ```python CAMUNDA_WORKER_STARTUP_JITTER_MAX_SECONDS: float | None ``` ### CAMUNDA_WORKER_TIMEOUT ```python CAMUNDA_WORKER_TIMEOUT: int | None ``` ### ZEEBE_REST_ADDRESS ```python ZEEBE_REST_ADDRESS: str ``` ### model_config _= {'extra': 'forbid'}_ Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict]. ## ConfigurationResolver ```python class ConfigurationResolver(environment, explicit_configuration=None) ``` Bases: `object` Resolves an effective configuration from environment + explicit overrides. **Parameters:** | Parameter | Type | Description | | ------------------------ | ------------------------------------------------------------------------------------- | ----------- | | `environment` | [CamundaSdkConfigPartial](#camundasdkconfigpartial) \| Mapping [str , Any ] | | | `explicit_configuration` | [CamundaSdkConfigPartial](#camundasdkconfigpartial) \| Mapping [str , Any ] \| `None` | | ### resolve() ```python def resolve() ``` - **Return type:** [_ResolvedCamundaSdkConfiguration_](#resolvedcamundasdkconfiguration) ## ResolvedCamundaSdkConfiguration ```python class ResolvedCamundaSdkConfiguration(effective: 'CamundaSdkConfiguration', environment: 'CamundaSdkConfigPartial', explicit: 'CamundaSdkConfigPartial | None') ``` Bases: `object` **Parameters:** | Parameter | Type | Description | | ------------- | ------------------------------------------------------------- | ----------- | | `effective` | [CamundaSdkConfiguration](#camundasdkconfiguration) | | | `environment` | [CamundaSdkConfigPartial](#camundasdkconfigpartial) | | | `explicit` | [CamundaSdkConfigPartial](#camundasdkconfigpartial) \| `None` | | ### effective ```python effective: [CamundaSdkConfiguration](#camundasdkconfiguration) ``` ### environment ```python environment: [CamundaSdkConfigPartial](#camundasdkconfigpartial) ``` ### explicit ```python explicit: [CamundaSdkConfigPartial](#camundasdkconfigpartial) | None ``` ### read_environment() ```python def read_environment(environ=None) ``` - **Parameters:** **environ** (_Mapping_ _[__str_ _,_ _str_ _]_ _|_ _None_) - **Return type:** [_CamundaSdkConfigPartial_](#camundasdkconfigpartial) --- ## Semantic Types(Api-reference) ## AgentDefinitionKey ```python class AgentDefinitionKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [AgentDefinitionKey](#agentdefinitionkey) ## AgentHistoryItemKey ```python class AgentHistoryItemKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [AgentHistoryItemKey](#agenthistoryitemkey) ## AgentInstanceKey ```python class AgentInstanceKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [AgentInstanceKey](#agentinstancekey) ## AuditLogEntityKey ```python class AuditLogEntityKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [AuditLogEntityKey](#auditlogentitykey) ## AuditLogKey ```python class AuditLogKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [AuditLogKey](#auditlogkey) ## AuthorizationKey ```python class AuthorizationKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [AuthorizationKey](#authorizationkey) ## BatchOperationKey ```python class BatchOperationKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [BatchOperationKey](#batchoperationkey) ## BusinessId ```python class BusinessId(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [BusinessId](#businessid) ## ClientId ```python class ClientId(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [ClientId](#clientid) ## ClusterVariableName ```python class ClusterVariableName(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [ClusterVariableName](#clustervariablename) ## ConditionalEvaluationKey ```python class ConditionalEvaluationKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [ConditionalEvaluationKey](#conditionalevaluationkey) ## DecisionDefinitionId ```python class DecisionDefinitionId(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [DecisionDefinitionId](#decisiondefinitionid) ## DecisionDefinitionKey ```python class DecisionDefinitionKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [DecisionDefinitionKey](#decisiondefinitionkey) ## DecisionEvaluationInstanceKey ```python class DecisionEvaluationInstanceKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [DecisionEvaluationInstanceKey](#decisionevaluationinstancekey) ## DecisionEvaluationKey ```python class DecisionEvaluationKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [DecisionEvaluationKey](#decisionevaluationkey) ## DecisionInstanceKey ```python class DecisionInstanceKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [DecisionInstanceKey](#decisioninstancekey) ## DecisionRequirementsKey ```python class DecisionRequirementsKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [DecisionRequirementsKey](#decisionrequirementskey) ## DeploymentKey ```python class DeploymentKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [DeploymentKey](#deploymentkey) ## DocumentId ```python class DocumentId(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [DocumentId](#documentid) ## ElementId ```python class ElementId(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [ElementId](#elementid) ## ElementInstanceKey ```python class ElementInstanceKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [ElementInstanceKey](#elementinstancekey) ## EndCursor ```python class EndCursor(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [EndCursor](#endcursor) ## FormId ```python class FormId(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [FormId](#formid) ## FormKey ```python class FormKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [FormKey](#formkey) ## GlobalListenerId ```python class GlobalListenerId(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [GlobalListenerId](#globallistenerid) ## GroupId ```python class GroupId(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [GroupId](#groupid) ## HistoryItemId ```python class HistoryItemId(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [HistoryItemId](#historyitemid) ## IncidentKey ```python class IncidentKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [IncidentKey](#incidentkey) ## JobKey ```python class JobKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [JobKey](#jobkey) ## JobLeaseToken ```python class JobLeaseToken(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [JobLeaseToken](#jobleasetoken) ## MappingRuleId ```python class MappingRuleId(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [MappingRuleId](#mappingruleid) ## MessageKey ```python class MessageKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [MessageKey](#messagekey) ## MessageSubscriptionKey ```python class MessageSubscriptionKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [MessageSubscriptionKey](#messagesubscriptionkey) ## ProcessDefinitionId ```python class ProcessDefinitionId(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [ProcessDefinitionId](#processdefinitionid) ## ProcessDefinitionKey ```python class ProcessDefinitionKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [ProcessDefinitionKey](#processdefinitionkey) ## ProcessInstanceKey ```python class ProcessInstanceKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [ProcessInstanceKey](#processinstancekey) ## RoleId ```python class RoleId(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [RoleId](#roleid) ## SignalKey ```python class SignalKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [SignalKey](#signalkey) ## StartCursor ```python class StartCursor(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [StartCursor](#startcursor) ## Tag ```python class Tag(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [Tag](#tag) ## TenantId ```python class TenantId(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [TenantId](#tenantid) ## UserTaskKey ```python class UserTaskKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [UserTaskKey](#usertaskkey) ## Username ```python class Username(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [Username](#username) ## VariableKey ```python class VariableKey(value) ``` Bases: `str` - **Parameters:** **value** (_str_) - **Return type:** [VariableKey](#variablekey) ### lift_resource_key() ```python def lift_resource_key(value) ``` - **Parameters:** **value** (_Any_) - **Return type:** [_ProcessDefinitionKey_](#processdefinitionkey) | [_DecisionRequirementsKey_](#decisionrequirementskey) | [_FormKey_](#formkey) | [_DecisionDefinitionKey_](#decisiondefinitionkey) ### lift_scope_key() ```python def lift_scope_key(value) ``` - **Parameters:** **value** (_Any_) - **Return type:** [_ProcessInstanceKey_](#processinstancekey) | [_ElementInstanceKey_](#elementinstancekey) ### try_lift_resource_key() ```python def try_lift_resource_key(value) ``` - **Parameters:** **value** (_Any_) - **Return type:** _Tuple_[bool, [_ProcessDefinitionKey_](#processdefinitionkey) | [_DecisionRequirementsKey_](#decisionrequirementskey) | [_FormKey_](#formkey) | [_DecisionDefinitionKey_](#decisiondefinitionkey) | Exception] ### try_lift_scope_key() ```python def try_lift_scope_key(value) ``` - **Parameters:** **value** (_Any_) - **Return type:** _Tuple_[bool, [_ProcessInstanceKey_](#processinstancekey) | [_ElementInstanceKey_](#elementinstancekey) | Exception] --- ## Authentication(Python-sdk) The SDK supports three authentication strategies, controlled by `CAMUNDA_AUTH_STRATEGY`: | Strategy | When to use | | -------- | --------------------------------------------------------- | | `NONE` | Local development with unauthenticated Camunda (default) | | `OAUTH` | Camunda SaaS or any OAuth 2.0 Client Credentials endpoint | | `BASIC` | Self-Managed Camunda with Basic auth (username/password) | ## Auto-detection If you omit `CAMUNDA_AUTH_STRATEGY`, the SDK infers it from the credentials you provide: - Only `CAMUNDA_CLIENT_ID` + `CAMUNDA_CLIENT_SECRET` → **OAUTH** - Only `CAMUNDA_BASIC_AUTH_USERNAME` + `CAMUNDA_BASIC_AUTH_PASSWORD` → **BASIC** - No credentials → **NONE** - Both OAuth and Basic credentials present → **error** (set `CAMUNDA_AUTH_STRATEGY` explicitly) ## OAuth 2.0 ```bash CAMUNDA_REST_ADDRESS=https://cluster.example/v2 CAMUNDA_AUTH_STRATEGY=OAUTH CAMUNDA_CLIENT_ID=your-client-id CAMUNDA_CLIENT_SECRET=your-client-secret # Optional: # CAMUNDA_OAUTH_URL=https://login.cloud.camunda.io/oauth/token # CAMUNDA_TOKEN_AUDIENCE=zeebe.camunda.io ``` ## Basic authentication ```bash CAMUNDA_REST_ADDRESS=http://localhost:8080/v2 CAMUNDA_AUTH_STRATEGY=BASIC CAMUNDA_BASIC_AUTH_USERNAME=your-username CAMUNDA_BASIC_AUTH_PASSWORD=your-password ``` Or programmatically: ```python from camunda_orchestration_sdk import CamundaClient client = CamundaClient( configuration={ "CAMUNDA_REST_ADDRESS": "http://localhost:8080/v2", "CAMUNDA_AUTH_STRATEGY": "BASIC", "CAMUNDA_BASIC_AUTH_USERNAME": "your-username", "CAMUNDA_BASIC_AUTH_PASSWORD": "your-password", } ) ``` --- ## Backpressure(Python-sdk) The SDK includes built-in adaptive backpressure management that protects the Camunda cluster from overload. When the cluster returns backpressure signals (HTTP 429, 503, or `RESOURCE_EXHAUSTED`), the SDK automatically reduces outbound concurrency. When conditions improve, it gradually recovers — returning to full throughput with no manual intervention. This is enabled by default with the `BALANCED` profile and requires no configuration. Operations that drain work from the cluster (completing jobs, failing jobs) are never throttled. | Profile | Behavior | | -------------------- | ----------------------------------------------------------------------------------------------- | | `BALANCED` (default) | Adaptive concurrency gating with AIMD-style permit management and exponential backoff at floor. | | `LEGACY` | Observe-only — records severity but never gates or queues requests. | Set the profile via the `CAMUNDA_SDK_BACKPRESSURE_PROFILE` environment variable. --- ## Configuration reference(Python-sdk) All `CAMUNDA_*` environment variables recognised by the SDK. These can also be passed as keys in the `configuration={...}` dict. | Variable | Default | Description | | ------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ZEEBE_REST_ADDRESS` | `http://localhost:8080/v2` | REST API base URL (alias for CAMUNDA_REST_ADDRESS). | | `CAMUNDA_REST_ADDRESS` | `http://localhost:8080/v2` | REST API base URL. `/v2` is appended automatically if missing, unless CAMUNDA_REST_ADDRESS_EXACT is true. | | `CAMUNDA_REST_ADDRESS_EXACT` | `false` | Use CAMUNDA_REST_ADDRESS / ZEEBE_REST_ADDRESS exactly as provided, without appending the `/v2` suffix. Useful for gateway- or proxy-fronted deployments whose base path does not follow the `.../v2` convention. | | `CAMUNDA_TOKEN_AUDIENCE` | `zeebe.camunda.io` | OAuth token audience. | | `CAMUNDA_OAUTH_URL` | `https://login.cloud.camunda.io/oauth/token` | OAuth token endpoint URL. | | `CAMUNDA_CLIENT_ID` | — | OAuth client ID. | | `CAMUNDA_CLIENT_SECRET` | — | OAuth client secret. | | `CAMUNDA_CLIENT_AUTH_CLIENTID` | — | Alias for CAMUNDA_CLIENT_ID. | | `CAMUNDA_CLIENT_AUTH_CLIENTSECRET` | — | Alias for CAMUNDA_CLIENT_SECRET. | | `CAMUNDA_AUTH_STRATEGY` | `NONE` | Authentication strategy: NONE, OAUTH, or BASIC. Auto-inferred from credentials if omitted. | | `CAMUNDA_BASIC_AUTH_USERNAME` | — | Basic auth username. Required when CAMUNDA_AUTH_STRATEGY=BASIC. | | `CAMUNDA_BASIC_AUTH_PASSWORD` | — | Basic auth password. Required when CAMUNDA_AUTH_STRATEGY=BASIC. | | `CAMUNDA_SDK_LOG_LEVEL` | `error` | SDK log level: silent, error, warn, info, debug, trace, or silly. | | `CAMUNDA_TOKEN_CACHE_DIR` | — | Directory for OAuth token disk cache. Disabled if unset. | | `CAMUNDA_TOKEN_DISK_CACHE_DISABLE` | `false` | Disable OAuth token disk caching. | | `CAMUNDA_SDK_BACKPRESSURE_PROFILE` | `BALANCED` | Backpressure profile: BALANCED (adaptive gating, default) or LEGACY (observe-only, no gating). | | `CAMUNDA_TENANT_ID` | — | Default tenant ID applied to all operations that accept a tenant_id parameter. | | `CAMUNDA_TENANT_IDS` | — | Default tenant IDs applied to operations whose request body accepts a plural `tenantIds` array (currently job activation). Accepts a comma-separated list when supplied via environment variable. Falls back to `[CAMUNDA_TENANT_ID]` when only the singular form is set. | | `CAMUNDA_WORKER_TIMEOUT` | — | Default job timeout in milliseconds for all workers. | | `CAMUNDA_WORKER_MAX_CONCURRENT_JOBS` | — | Default maximum concurrent jobs per worker. | | `CAMUNDA_WORKER_REQUEST_TIMEOUT` | — | Default long-poll request timeout in milliseconds for all workers. | | `CAMUNDA_WORKER_NAME` | — | Default worker name for all workers. | | `CAMUNDA_WORKER_STARTUP_JITTER_MAX_SECONDS` | — | Default maximum startup jitter in seconds for all workers. | | `CAMUNDA_MTLS_CERT_PATH` | — | Path to client certificate (PEM) for mTLS. | | `CAMUNDA_MTLS_KEY_PATH` | — | Path to client private key (PEM) for mTLS. | | `CAMUNDA_MTLS_CA_PATH` | — | Path to CA certificate bundle (PEM) for mTLS. Optional. | | `CAMUNDA_MTLS_CERT` | — | Inline PEM client certificate. Overrides CAMUNDA_MTLS_CERT_PATH. | | `CAMUNDA_MTLS_KEY` | — | Inline PEM client private key. Overrides CAMUNDA_MTLS_KEY_PATH. | | `CAMUNDA_MTLS_CA` | — | Inline PEM CA bundle. Overrides CAMUNDA_MTLS_CA_PATH. | | `CAMUNDA_MTLS_KEY_PASSPHRASE` | — | Passphrase for encrypted private key. | | `CAMUNDA_LOAD_ENVFILE` | — | Load configuration from a `.env` file. Set to `true` (or a file path). | --- ## Controlling time Every wait inside the SDK -- worker poll intervals, retry backoff, backpressure decay, eventual-consistency polling -- resolves through an injected clock rather than `asyncio.sleep` or `time.sleep`. Pass your own to make that cadence yours: | Clock | Use | | ------------- | ------------------------------------------------------------------------------------ | | `LiveClock` | the default; real time, with backward wall-clock jumps absorbed rather than reported | | `ManualClock` | tests; virtual time, so a poll loop settles without waiting | | `EngineClock` | drives the Camunda engine's clock and the client's together | ## Waiting inside a handler `ConnectedJobContext` and `SyncJobContext` carry the worker's clock, so a handler that needs to wait can do it on the same clock as everything else: ```python from camunda_orchestration_sdk import ConnectedJobContext async def handle_job(job: ConnectedJobContext) -> dict[str, object]: # Short coordination only -- a business wait belongs in the process as a BPMN timer. await job.clock.sleep(0.5) return {"result": "processed"} ``` Keep those waits short -- spacing a retry, letting a resource settle. **A long or business wait belongs in the process as a BPMN timer event, not in a handler.** A handler that sleeps for minutes holds a worker slot for the duration, risks the job timeout expiring underneath it, and hides the delay from the process model, where it would otherwise be visible and changeable without a redeploy. `process`-strategy handlers get a plain `JobContext` with no clock: that context is pickled across a process boundary, which a clock cannot cross. Handlers that need one should use the `async` or `thread` strategy. ## Driving the engine's clock A process that only completes after a BPMN timer fires has two clocks to satisfy: the engine's and your client's. Moving either alone does not help -- pin the engine and your worker keeps waiting on real time; drive the worker and the engine never reaches the timer. `EngineClock` moves both. Each wait pins the engine forward to that wait's wake instant, so a process spanning a minute of engine time finishes in real milliseconds. > [!WARNING] > Pinning stops time for **everything on that cluster**, not just your client. Use it only > against a cluster you own -- a local one, or a disposable test instance -- never a shared > or production environment. Always `reset()` in a `finally`, after the client and any > workers using the clock have stopped; leaving without it leaves the engine frozen for > whoever comes next. ```python from camunda_orchestration_sdk import ( CamundaAsyncClient, ConnectedJobContext, EngineClock, WorkerConfig, ) async def handle_job(job_context: ConnectedJobContext) -> dict[str, object]: # Waiting here moves the engine's clock too, so a BPMN timer downstream fires # without anyone waiting out the real duration. await job_context.clock.sleep(60) return {"result": "processed"} # Drive the engine from a separate client: pinning issues a request, and that request's # own backoff must not wait on the clock issuing it. async with CamundaAsyncClient() as driver: # Pins on entry and resets on exit -- including when the client below fails to # build, or fails to shut down. Written by hand as a single `finally`, either of # those leaves the cluster frozen. async with EngineClock(driver) as engine: async with CamundaAsyncClient(clock=engine) as client: client.create_job_worker( config=WorkerConfig(job_type="payment", job_timeout_milliseconds=30_000), callback=handle_job, ) await client.run_workers() ``` Overlapping waits settle at a shared wake instant rather than accumulating, so ten handlers each waiting a second advance the engine by a second -- as real sleeps would. `pin(at)` and `reset()` are control operations and may move time backwards; waits never do. Because `PUT /clock` is write-only, the clock mirrors what it last pinned. That mirror is accurate only while nothing else pins the same engine: use one `EngineClock` per engine. ## Reproducible jitter A pinned clock makes the SDK's timing virtual, but not repeatable: worker startup jitter is still drawn at random, so two runs can schedule differently. Pass a `SeededRandom` as `random=` alongside the clock to fix that jitter too. Omit it and the live, unseeded `live_random` is used, which is what production wants -- jitter exists so a fleet of workers does not start in lockstep. ```python from camunda_orchestration_sdk import CamundaAsyncClient, ManualClock, SeededRandom # Seeded from CAMUNDA_TEST_SEED when it is set, otherwise from a fresh random seed. random = SeededRandom.from_env() print(f"jitter source: {random!r}") # names the seed to replay on failure async with CamundaAsyncClient(clock=ManualClock(), random=random) as client: ... # worker startup jitter is now exact and repeatable ``` `SeededRandom.from_env()` reads `CAMUNDA_TEST_SEED`. Log the source's `repr()` -- it names the seed -- and when a run fails, set `CAMUNDA_TEST_SEED` to that seed to replay the same draws. Only an unset variable means "pick a fresh seed"; any other value that is not an unsigned 64-bit integer raises, so a typo cannot silently replay the wrong run. The same seed produces the same draws in every Camunda SDK. --- ## Creating a Process Instance(Python-sdk) The recommended pattern is to obtain keys from a prior API response (e.g. a deployment) and pass them directly — no manual lifting needed: ```python from camunda_orchestration_sdk import CamundaClient, ProcessCreationByKey with CamundaClient() as client: # Deploy and capture the typed key deployment = client.deploy_resources_from_files(["process.bpmn"]) process_key = deployment.processes[0].process_definition_key # Use it directly — the type flows through without conversion result = client.create_process_instance( data=ProcessCreationByKey(process_definition_key=process_key) ) print(f"Process instance key: {result.process_instance_key}") ``` If you need to restore a key from external storage (database, message queue, config file), use the semantic type constructor. Validation runs automatically: ```python from camunda_orchestration_sdk import CamundaClient, ProcessCreationByKey, ProcessDefinitionKey with CamundaClient() as client: stored_key = "2251799813685249" # from a DB row or config result = client.create_process_instance( data=ProcessCreationByKey(process_definition_key=ProcessDefinitionKey(stored_key)) ) print(f"Process instance key: {result.process_instance_key}") ``` **Migrating from pre-release versions:** Early pre-release builds exported `lift_*` helper functions (e.g., `lift_process_definition_key`). These have been removed — use the type constructor directly instead: `ProcessDefinitionKey(value)`. The constructor performs the same validation and is the single API surface for semantic types. --- ## Deploying Resources(Python-sdk) Deploy BPMN, DMN, or Form files from disk: ```python from camunda_orchestration_sdk import CamundaClient with CamundaClient() as client: result = client.deploy_resources_from_files(["process.bpmn", "decision.dmn"]) print(f"Deployment key: {result.deployment_key}") for process in result.processes: print(f" Process: {process.process_definition_id} (key: {process.process_definition_key})") ``` --- ## Error Handling(Python-sdk) The SDK raises typed exceptions for API errors. Each HTTP error status code has a corresponding exception class (e.g. `BadRequestError` for 400, `NotFoundError` for 404). Every exception carries the `operation_id` of the method that raised it: ```python from camunda_orchestration_sdk import CamundaClient, ProcessCreationByKey, ProcessDefinitionKey from camunda_orchestration_sdk.errors import BadRequestError process_definition_key = ProcessDefinitionKey("2251799813685249") with CamundaClient() as client: try: result = client.create_process_instance( data=ProcessCreationByKey(process_definition_key=process_definition_key) ) except BadRequestError as e: print(f"Bad request ({e.operation_id}): {e}") ``` --- ## Eventual Consistency(Python-sdk) Some Camunda endpoints — particularly search and "get by key" operations — are eventually consistent: data written via one API call may not be immediately visible to a follow-up read. Endpoints flagged as eventually consistent in the OpenAPI spec accept an optional `consistency` parameter that transparently polls until the data is visible (or a timeout is reached). The `consistency` parameter is fully optional and defaults to `None`, so existing call sites continue to work unchanged. ```python from camunda_orchestration_sdk import CamundaClient from camunda_orchestration_sdk.models import ( ProcessInstanceSearchQuery, ProcessInstanceSearchQueryFilter, ) from camunda_orchestration_sdk.runtime.eventual import ( ConsistencyOptions, EventualConsistencyTimeoutError, ) with CamundaClient() as client: try: result = client.search_process_instances( data=ProcessInstanceSearchQuery( filter_=ProcessInstanceSearchQueryFilter( process_definition_id="order-process", ), ), # Opt in to transparent polling. Default predicate accepts the # first response whose `items` list is non-empty. consistency=ConsistencyOptions( wait_up_to_ms=5000, poll_interval_ms=200, ), ) for instance in result.items: print(instance.process_instance_key) except EventualConsistencyTimeoutError as exc: print(f"Timed out after {exc.elapsed_ms}ms ({exc.attempts} attempts)") ``` For non-GET endpoints (search/list) the default predicate succeeds on the first response whose `items` list is non-empty. For GET endpoints it succeeds on any non-`None` result and transparently retries `404 Not Found` while waiting. Pass a custom `predicate` to wait for a more specific condition: ```python from camunda_orchestration_sdk import CamundaClient from camunda_orchestration_sdk.models import ( ProcessInstanceSearchQuery, ProcessInstanceSearchQueryFilter, ) from camunda_orchestration_sdk.runtime.eventual import ConsistencyOptions with CamundaClient() as client: # Wait until at least 3 instances are visible. result = client.search_process_instances( data=ProcessInstanceSearchQuery( filter_=ProcessInstanceSearchQueryFilter( process_definition_id="order-process", ), ), consistency=ConsistencyOptions( wait_up_to_ms=10_000, poll_interval_ms=250, predicate=lambda r: len(r.items) >= 3, ), ) print(f"Got {len(result.items)} instances") ``` `ConsistencyOptions` fields: | Field | Type | Default | Description | | ------------------ | ----------------------------- | ---------- | -------------------------------------------------- | | `wait_up_to_ms` | `int` | _required_ | Maximum time to wait. `0` skips polling entirely. | | `poll_interval_ms` | `int` | `500` | Delay between polling attempts (minimum 10ms). | | `predicate` | `Callable[[T], bool] \| None` | `None` | Custom success check. Defaults as described above. | Polling aborts immediately on `400`, `401`, `403`, `409`, `422`, and `5xx` responses. `429` responses are retried with backoff. On timeout the SDK raises `EventualConsistencyTimeoutError`, which exposes `attempts`, `elapsed_ms`, `last_status`, and `operation_id`. The same parameter is available on `CamundaAsyncClient` and behaves identically using `asyncio.sleep` for the polling delay. --- ## Installing the SDK to your project(Python-sdk) ## Requirements - Python 3.10 or later ## Stable release (recommended for production) The stable version tracks the latest supported Camunda server release. The first stable release will be **8.9.0**. ```bash pip install camunda-orchestration-sdk ``` ## Pre-release / dev channel Pre-release versions (e.g. `8.9.0.dev2`) are published from the `main` branch and contain the latest changes targeting the next server minor version. Use these to preview upcoming features or validate your integration ahead of a stable release. ```bash # pip pip install --pre camunda-orchestration-sdk # pin to a specific pre-release pip install camunda-orchestration-sdk==8.9.0.dev2 ``` In a `requirements.txt`: ```text camunda-orchestration-sdk>=8.9.0.dev1 ``` > **Note:** Pre-release versions may contain breaking changes between builds. Pin to a specific version if you need reproducible builds. ## Versioning This SDK has a different release cadence from the Camunda server. Features and fixes land in the SDK during a server release. The major version of the SDK signals a 1:1 type coherence with the server API for a Camunda minor release. SDK version `n.y.z` -> server version `8.n`, so the type surface of SDK version 9.y.z matches the API surface of Camunda 8.9. Using a later SDK version, for example: SDK version 10.y.z with Camunda 8.9, means that the SDK contains additive surfaces that are not guaranteed at runtime, and the compiler cannot warn of unsupported operations. Using an earlier SDK version, for example: SDK version 9.y.z with Camunda 8.10, results in slightly degraded compiler reasoning: exhaustiveness checks cannot be guaranteed by the compiler for any extended surfaces (principally, enums with added members). In the vast majority of use-cases, this will not be an issue; but you should be aware that using the matching SDK major version for the server minor version provides the strongest compiler guarantees about runtime reliability. **Recommended approach**: - Check the [CHANGELOG](https://github.com/camunda/orchestration-cluster-api-python/releases). - As a sanity check during server version upgrade, rebuild applications with the matching SDK major version to identify any affected runtime surfaces. --- ## Job Workers(Python-sdk) Job workers long-poll for available jobs, execute a callback, and automatically complete or fail the job based on the return value. Workers are available on `CamundaAsyncClient`. Handlers receive a context object that includes a `client` reference, so your handler can make API calls during job execution. The context type depends on the execution strategy: - **Async handlers** → `ConnectedJobContext` with `client: CamundaAsyncClient` (use `await`) - **Thread handlers** → `SyncJobContext` with `client: CamundaClient` (call directly) - **Process handlers** → plain `JobContext` (no client — cannot be pickled across process boundaries) ```python from camunda_orchestration_sdk import CamundaAsyncClient, ConnectedJobContext, WorkerConfig async def handle_job(job_context: ConnectedJobContext) -> dict[str, object]: variables = job_context.variables.to_dict() job_context.log.info(f"Processing job {job_context.job_key}: {variables}") return {"result": "processed"} async def main() -> None: async with CamundaAsyncClient() as client: config = WorkerConfig( job_type="my-service-task", job_timeout_milliseconds=30_000, ) client.create_job_worker(config=config, callback=handle_job) # Keep workers running until cancelled await client.run_workers() asyncio.run(main()) ``` ## Using the Client in a Job Handler Because `ConnectedJobContext` and `SyncJobContext` include a `client` reference, your handler can make API calls during job execution — for example, publishing a message to trigger another part of the process. **Async handlers** (`execution_strategy="async"`) — `await` the client method directly: ```python from camunda_orchestration_sdk import ConnectedJobContext, MessagePublicationRequest, MessagePublicationRequestVariables async def handle_order(job: ConnectedJobContext) -> dict[str, object]: variables = job.variables.to_dict() order_id = variables["orderId"] await job.client.publish_message( data=MessagePublicationRequest( name="order-processed", correlation_key=order_id, time_to_live=60000, variables=MessagePublicationRequestVariables.from_dict({"orderId": order_id, "status": "completed"}), ) ) job.log.info(f"Published order-processed message for order {order_id}") return {"status": "done"} ``` **Sync (thread) handlers** (`execution_strategy="thread"`) — `job.client` is a sync `CamundaClient`, so call methods directly: ```python from camunda_orchestration_sdk import MessagePublicationRequest, MessagePublicationRequestVariables, SyncJobContext def handle_order(job: SyncJobContext) -> dict[str, object]: variables = job.variables.to_dict() order_id = variables["orderId"] job.client.publish_message( data=MessagePublicationRequest( name="order-processed", correlation_key=order_id, time_to_live=60000, variables=MessagePublicationRequestVariables.from_dict({"orderId": order_id, "status": "completed"}), ) ) job.log.info(f"Published order-processed message for order {order_id}") return {"status": "done"} ``` > **Note:** The SDK automatically provides the right client type for each strategy — async handlers get `CamundaAsyncClient` (use `await`), thread handlers get `CamundaClient` (call directly). You don't need to create or manage these clients yourself. ## Job Logger Each `JobContext` exposes a `log` property — a scoped logger automatically bound with the job's context (job type, worker name, and job key). Use it inside your handler for structured, per-job log output: ```python async def handler(job: ConnectedJobContext) -> dict[str, object]: job.log.info(f"Starting work on {job.job_key}") # ... do work ... job.log.debug("Work completed successfully") return {"done": True} ``` The job logger inherits the SDK's logger configuration (loguru by default, or whatever you passed via `logger=`). If you injected a custom logger into the client, job handlers will use a child of that same logger. > **Note:** When using the `"process"` execution strategy, the job logger silently degrades to a no-op (`NullLogger`) because loggers cannot be pickled across process boundaries. The worker's main-process logger still records all job lifecycle events (activation, completion, failure, errors). If you need per-job logging from a process-isolated handler, configure a logger inside the handler itself. ## Execution Strategies Job workers support multiple execution strategies to match your workload type. Pass `execution_strategy` as a keyword argument to `create_job_worker`, or let the SDK auto-detect. | Strategy | How it runs your handler | Context type | Best for | | ------------------ | ------------------------------------------------------------------------------ | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `"auto"` (default) | Auto-detects: `"async"` for `async def` handlers, `"thread"` for sync handlers | `ConnectedJobContext` or `SyncJobContext` | Most use cases — sensible defaults without configuration | | `"async"` | Runs on the main `asyncio` event loop | `ConnectedJobContext` (async client) | I/O-bound async work (HTTP calls, database queries). Best throughput for handlers that call remote systems over HTTP | | `"thread"` | Runs in a `ThreadPoolExecutor` | `SyncJobContext` (sync client) | CPU-bound work, blocking I/O (file system, synchronous HTTP libraries) | | `"process"` | Runs in a `ProcessPoolExecutor` | `JobContext` (no client) | Heavy CPU-bound work that needs to escape the GIL (image processing, ML inference) | > **Choosing between `"async"` and `"thread"`:** If your job handler makes HTTP calls to remote systems (APIs, databases, microservices), `"async"` delivers the best performance — it can multiplex many concurrent jobs on a single thread without blocking. Use `"thread"` when your handler performs CPU-bound computation or calls synchronous libraries that would block the event loop. **Auto-detection logic:** If your handler is an `async def`, the strategy defaults to `"async"`. If it's a regular `def`, the strategy defaults to `"thread"`. You can override this explicitly: ```python from camunda_orchestration_sdk import SyncJobContext, JobContext # Force thread pool for a sync handler (receives SyncJobContext) def io_handler(job: SyncJobContext) -> dict[str, object]: return {"done": True} client.create_job_worker( config=WorkerConfig(job_type="io-bound-task", job_timeout_milliseconds=30_000), callback=io_handler, execution_strategy="thread", ) # Force process pool for CPU-heavy work (receives plain JobContext) def cpu_handler(job: JobContext) -> dict[str, object]: return {"computed": True} client.create_job_worker( config=WorkerConfig(job_type="image-processing", job_timeout_milliseconds=120_000), callback=cpu_handler, execution_strategy="process", ) ``` **Process strategy caveats:** The `"process"` strategy serialises (pickles) your handler and its context to send them to a worker process. Because the SDK client cannot be pickled, handlers running under this strategy receive a plain `JobContext` (without a `client` attribute) instead of `ConnectedJobContext`/`SyncJobContext`. This means: - Your handler function and its closure must be picklable (top-level functions work; lambdas and closures over unpicklable objects do not). - Your handler must accept `JobContext`, not `ConnectedJobContext` or `SyncJobContext` — the type checker enforces this via overloaded signatures on `create_job_worker`. - `job.log` degrades to a silent no-op logger in the child process (see [Job Logger](#job-logger)). - There is additional overhead per job from serialisation and inter-process communication. ## Worker Configuration `WorkerConfig` supports: | Parameter | Default | Description | | ------------------------------ | ----------------------------------- | ---------------------------------------------- | | `job_type` | _(required)_ | The BPMN service task type to poll for | | `job_timeout_milliseconds` | env / _(required)_ | How long the worker has to complete the job | | `request_timeout_milliseconds` | env / `0` | Long-poll request timeout (0 = server default) | | `max_concurrent_jobs` | env / `10` | Maximum jobs executing concurrently | | `fetch_variables` | `None` | List of variable names to fetch (None = all) | | `worker_name` | env / `"camunda-python-sdk-worker"` | Identifier for this worker in Camunda | The following are keyword-only arguments on `create_job_worker`, not part of `WorkerConfig`: | Parameter | Default | Description | | ---------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `execution_strategy` | `"auto"` | `"auto"`, `"async"`, `"thread"`, or `"process"`. Controls how the handler is invoked and which context type it receives. | | `startup_jitter_max_seconds` | env / `0` | Maximum random delay (in seconds) before the worker starts polling. When multiple application instances restart simultaneously, this spreads out initial activation requests to avoid saturating the server. A value of `0` (the default) means no delay. | ### Heritable Worker Defaults Worker configuration fields marked "env" in the table above can be set globally via environment variables or the client constructor. Individual `WorkerConfig` values take precedence. | Environment variable | Maps to | | ------------------------------------------- | ------------------------------ | | `CAMUNDA_WORKER_TIMEOUT` | `job_timeout_milliseconds` | | `CAMUNDA_WORKER_MAX_CONCURRENT_JOBS` | `max_concurrent_jobs` | | `CAMUNDA_WORKER_REQUEST_TIMEOUT` | `request_timeout_milliseconds` | | `CAMUNDA_WORKER_NAME` | `worker_name` | | `CAMUNDA_WORKER_STARTUP_JITTER_MAX_SECONDS` | `startup_jitter_max_seconds` | **Precedence:** explicit `WorkerConfig` value > environment variable / client constructor > hardcoded default. Example — set defaults via environment variables: ```bash export CAMUNDA_WORKER_TIMEOUT=30000 export CAMUNDA_WORKER_MAX_CONCURRENT_JOBS=32 ``` ```python # No need to set job_timeout_milliseconds on every worker — inherited from env client.create_job_worker( config=WorkerConfig(job_type="payment-service"), callback=handle_payment, ) client.create_job_worker( config=WorkerConfig(job_type="notification-service"), callback=handle_notification, ) ``` Example — set defaults via client constructor: ```python client = CamundaAsyncClient(configuration={ "CAMUNDA_WORKER_TIMEOUT": "30000", "CAMUNDA_WORKER_MAX_CONCURRENT_JOBS": "16", "CAMUNDA_WORKER_NAME": "my-app", }) # Both workers inherit timeout, concurrency, and name client.create_job_worker( config=WorkerConfig(job_type="payment-service"), callback=handle_payment, ) client.create_job_worker( config=WorkerConfig(job_type="shipping-service"), callback=handle_shipping, ) ``` ## Failing a Job To explicitly fail a job with a custom error message, retry count, and backoff, raise `JobFailure` in your handler: ```python from camunda_orchestration_sdk import ConnectedJobContext, JobFailure async def handle_job(job: ConnectedJobContext) -> dict[str, object]: if not job.variables.to_dict().get("required_field"): raise JobFailure( message="Missing required field", retries=2, retry_back_off=5000, # milliseconds ) return {"result": "ok"} ``` | Parameter | Default | Description | | ---------------- | ------------ | ----------------------------------------------------------------- | | `message` | _(required)_ | Error message attached to the failure | | `retries` | `None` | Remaining retries. `None` decrements the current retry count by 1 | | `retry_back_off` | `0` | Backoff before the next retry, in milliseconds | If an unhandled exception escapes your handler, the job is automatically failed with the exception message and the retry count decremented by 1. ## Throwing a BPMN Error To throw a [BPMN error](../../components/modeler/bpmn/error-events/error-events.md) from a job handler — for example, to trigger an error boundary event — raise `JobError`: ```python from camunda_orchestration_sdk import ConnectedJobContext, JobError async def handle_payment(job: ConnectedJobContext) -> dict[str, object]: variables = job.variables.to_dict() if variables.get("amount", 0) > 10_000: raise JobError(error_code="AMOUNT_TOO_HIGH", message="Payment exceeds limit") return {"status": "approved"} ``` | Parameter | Default | Description | | ------------ | ------------ | -------------------------------------------------------------- | | `error_code` | _(required)_ | The error code that is matched against BPMN error catch events | | `message` | `""` | An optional error message for logging/diagnostics | The `error_code` must match the error code defined on a BPMN error catch event in your process model. If no catch event matches, the job becomes an incident. ## Job Corrections (User Task Listeners) When a job worker handles a [user task listener](../../components/concepts/user-task-listeners.md), it can correct task properties (assignee, due date, candidate groups, etc.) as part of the completion. Return a `JobCompletionRequest` with a `result` containing `JobResultCorrections`: ```python from camunda_orchestration_sdk import ConnectedJobContext from camunda_orchestration_sdk.models import ( JobCompletionRequest, JobResultUserTask, JobResultCorrections, ) async def validate_task(job: ConnectedJobContext) -> JobCompletionRequest: return JobCompletionRequest( result=JobResultUserTask( type_="userTask", corrections=JobResultCorrections( assignee="corrected-user", priority=80, ), ), ) ``` To deny a task completion (reject the work), set `denied=True`: ```python async def review_task(job: ConnectedJobContext) -> JobCompletionRequest: return JobCompletionRequest( result=JobResultUserTask( type_="userTask", denied=True, denied_reason="Insufficient documentation", ), ) ``` | Correctable attribute | Type | Clear value | | --------------------- | ------------- | ----------------- | | `assignee` | `str` | Empty string `""` | | `due_date` | `datetime` | Empty string `""` | | `follow_up_date` | `datetime` | Empty string `""` | | `candidate_users` | `list[str]` | Empty list `[]` | | `candidate_groups` | `list[str]` | Empty list `[]` | | `priority` | `int` (0–100) | — | Omitting an attribute or passing `None` preserves the persisted value. This works with all handler types (async, thread, and process). --- ## Logging(Python-sdk) By default the SDK logs via [loguru](https://github.com/Delgan/loguru). You can inject any logger that exposes `debug`, `info`, `warning`, and `error` methods — including Python's built-in `logging.Logger`. ## Using the default logger (loguru) No configuration needed. Control verbosity with `CAMUNDA_SDK_LOG_LEVEL` or loguru's own `LOGURU_LEVEL` environment variable: ```bash CAMUNDA_SDK_LOG_LEVEL=debug python your_script.py ``` ## Injecting a custom logger Pass a `logger=` argument to `CamundaClient` or `CamundaAsyncClient`. The logger is forwarded to all internal components (auth providers, HTTP hooks, job workers). **stdlib `logging`:** ```python from camunda_orchestration_sdk import CamundaClient my_logger = logging.getLogger("my_app.camunda") my_logger.setLevel(logging.DEBUG) client = CamundaClient(logger=my_logger) ``` **Custom logger object:** ```python from camunda_orchestration_sdk import CamundaClient class MyLogger: def debug(self, msg: object, *args: object, **kwargs: object) -> None: print(f"[DEBUG] {msg}") def info(self, msg: object, *args: object, **kwargs: object) -> None: print(f"[INFO] {msg}") def warning(self, msg: object, *args: object, **kwargs: object) -> None: print(f"[WARN] {msg}") def error(self, msg: object, *args: object, **kwargs: object) -> None: print(f"[ERROR] {msg}") client = CamundaClient(logger=MyLogger()) ``` ## Disabling logging Pass an instance of `NullLogger` to silence all SDK output: ```python from camunda_orchestration_sdk import CamundaClient, NullLogger client = CamundaClient(logger=NullLogger()) ``` --- ## Migrating from v9 to v10 v10 tracks Camunda 8.10. Identifier arguments moved from plain `str` to **semantic types**, `get_resource_content` now returns an object instead of a `str`, and 26 model classes were renamed (the old names still work, with a deprecation warning). No client method or model class was removed. **→ See [MIGRATION.md](https://github.com/camunda/orchestration-cluster-api-python/blob/main/MIGRATION.md) for the full guide**, including the complete list of affected methods. The common case is wrapping an identifier at the boundary: ```python from camunda_orchestration_sdk import CamundaClient, GroupId, RoleId with CamundaClient() as client: # v9 — plain strings were accepted: # client.assign_role_to_group(role_id="developer", group_id="engineering") # v10 — wrap with the branded type constructor at the boundary client.assign_role_to_group( role_id=RoleId("developer"), group_id=GroupId("engineering"), ) ``` The brand constructors are subclasses of `str`, so the wrapped values remain valid where a `str` is expected (f-strings, logging, JSON serialisation). The wrap exists to enforce the upstream pattern and length constraints once, at the boundary, so a malformed identifier fails fast with `ValueError` instead of producing an HTTP 400 from the cluster. See [`semantic_types.py`](https://github.com/camunda/orchestration-cluster-api-python/blob/main/generated/camunda_orchestration_sdk/semantic_types.py) for the canonical list of brands and their constraints. --- ## Programmatic configuration (use sparingly) Only use `configuration={...}` when you must supply or mutate configuration dynamically (e.g. tests, multi-tenant routing, or ephemeral preview environments). Keys mirror their `CAMUNDA_*` environment names. ```python from camunda_orchestration_sdk import CamundaClient client = CamundaClient( configuration={ "CAMUNDA_REST_ADDRESS": "http://localhost:8080/v2", "CAMUNDA_AUTH_STRATEGY": "NONE", } ) ``` --- ## Quick start (Zero-config – recommended) Keep configuration out of application code. Let the client read `CAMUNDA_*` variables from the environment (12-factor style). This makes secret rotation, environment promotion (dev → staging → prod), and operational tooling (vaults / secret managers) safer and simpler. If no configuration is present, the SDK defaults to a local Camunda 8 Run-style endpoint at `http://localhost:8080/v2`. ```python from camunda_orchestration_sdk import CamundaAsyncClient, CamundaClient # Zero-config construction: reads CAMUNDA_* from the environment client = CamundaClient() async_client = CamundaAsyncClient() ``` Typical `.env` (example): ```bash CAMUNDA_REST_ADDRESS=https://cluster.example/v2 CAMUNDA_AUTH_STRATEGY=OAUTH CAMUNDA_CLIENT_ID=*** CAMUNDA_CLIENT_SECRET=*** ``` ### Loading configuration from a `.env` file (`CAMUNDA_LOAD_ENVFILE`) The SDK can optionally load configuration values from a dotenv file. - Set `CAMUNDA_LOAD_ENVFILE=true` (or `1` / `yes`) to load `.env` from the current working directory. - Set `CAMUNDA_LOAD_ENVFILE=/path/to/file.env` to load from an explicit path. - If the file does not exist, it is silently ignored. - Precedence is: `.env` < environment variables < explicit `configuration={...}` passed to the client. - The resolver reads dotenv values without mutating `os.environ`. Example `.env`: ```bash CAMUNDA_REST_ADDRESS=http://localhost:8080/v2 CAMUNDA_CLIENT_ID=your-client-id CAMUNDA_CLIENT_SECRET=your-client-secret ``` Enable loading from the current directory: ```bash export CAMUNDA_LOAD_ENVFILE=true python your_script.py ``` Or enable loading from a specific file: ```bash export CAMUNDA_LOAD_ENVFILE=~/camunda/dev.env python your_script.py ``` You can also enable it via the explicit configuration dict: ```python from camunda_orchestration_sdk import CamundaClient client = CamundaClient(configuration={"CAMUNDA_LOAD_ENVFILE": "true"}) ``` --- ## Self-signed TLS / mTLS(Python-sdk) The SDK supports custom TLS certificates via environment variables. This is useful for: - **Self-signed server certificates** — trust a CA that signed your server's certificate, without presenting a client identity. - **Mutual TLS (mTLS)** — present a client certificate and key to prove the client's identity. - **Both** — trust a custom CA _and_ present client credentials. ## Trusting a self-signed server certificate Set only the CA certificate to trust the server's self-signed certificate: ```bash # Path to PEM file: CAMUNDA_MTLS_CA_PATH=/path/to/ca.pem # Or inline PEM: CAMUNDA_MTLS_CA="-----BEGIN CERTIFICATE-----\n..." ``` ## Mutual TLS (client certificate) To present a client certificate for mutual TLS, provide both the certificate and private key: ```bash CAMUNDA_MTLS_CERT_PATH=/path/to/client.crt CAMUNDA_MTLS_KEY_PATH=/path/to/client.key # Optional — passphrase if the key is encrypted: # CAMUNDA_MTLS_KEY_PASSPHRASE=secret ``` ## Full mTLS with custom CA Combine a custom CA with client credentials: ```bash CAMUNDA_MTLS_CA_PATH=/path/to/ca.pem CAMUNDA_MTLS_CERT_PATH=/path/to/client.crt CAMUNDA_MTLS_KEY_PATH=/path/to/client.key ``` Inline PEM values (`CAMUNDA_MTLS_CERT`, `CAMUNDA_MTLS_KEY`, `CAMUNDA_MTLS_CA`) take precedence over their `_PATH` counterparts. No code changes are needed — the SDK picks up TLS configuration from environment variables automatically: ```python from camunda_orchestration_sdk import CamundaClient client = CamundaClient() # TLS configured from env vars ``` --- ## Semantic Types(Python-sdk) The SDK uses distinct types for identifiers like `ProcessDefinitionKey`, `ProcessInstanceKey`, `JobKey`, `TenantId`, etc., defined in `camunda_orchestration_sdk.semantic_types` and re-exported from the top-level package. These types inherit from `str`, so they serialize transparently to/from JSON and are compatible with any code expecting a string. ## Why they exist Camunda's API has many operations that accept string keys — process definition keys, process instance keys, incident keys, job keys, and so on. Without semantic types, it is easy to accidentally pass a process instance key where a process definition key is expected, or mix up a job key with an incident key. The type checker cannot help you if everything is `str`. Semantic types make these identifiers **distinct at the type level**. Pyright (and other type checkers) will flag an error if you pass a `ProcessInstanceKey` where a `ProcessDefinitionKey` is expected, catching bugs before runtime. ## How to use them Treat semantic types as **opaque identifiers** — receive them from API responses and pass them to subsequent API calls without inspecting or transforming the underlying value: ```python from camunda_orchestration_sdk import CamundaClient, ProcessCreationByKey client = CamundaClient() # Deploy → the response already carries typed keys deployment = client.deploy_resources_from_files(["process.bpmn"]) process_key = deployment.processes[0].process_definition_key # ProcessDefinitionKey # Pass it directly to another call — no conversion needed result = client.create_process_instance( data=ProcessCreationByKey(process_definition_key=process_key) ) # The result also carries typed keys instance_key = result.process_instance_key # ProcessInstanceKey client.cancel_process_instance(process_instance_key=instance_key) ``` ## Serialising in and out of the type system Semantic types inherit from `str` and validate on construction, so they work transparently: ```python from camunda_orchestration_sdk import ProcessDefinitionKey, ProcessInstanceKey # --- Serialising out (to storage / JSON / message queue) --- # A semantic type IS a str, so it works directly with any str API: process_key: ProcessDefinitionKey = deployment.processes[0].process_definition_key db.save("process_key", process_key) # stores the raw string json.dumps({"key": process_key}) # "2251799813685249" # --- Deserialising in (from storage / external input) --- # Wrap the raw string with the type constructor (validates automatically): raw = db.load("process_key") # returns a plain str typed_key = ProcessDefinitionKey(raw) # validates and wraps the value result = client.create_process_instance( data=ProcessCreationByKey(process_definition_key=typed_key) ) ``` The available semantic types include: `ProcessDefinitionKey`, `ProcessDefinitionId`, `ProcessInstanceKey`, `JobKey`, `IncidentKey`, `DecisionDefinitionKey`, `DecisionDefinitionId`, `DeploymentKey`, `UserTaskKey`, `MessageKey`, `SignalKey`, `TenantId`, `ElementId`, `FormKey`, and others. All are importable from `camunda_orchestration_sdk` or `camunda_orchestration_sdk.semantic_types`. --- ## Using the SDK(Python-sdk) The SDK provides two clients with identical API surfaces: - **`CamundaClient`** — synchronous. Every method blocks until the response arrives. Use this in scripts, CLI tools, Django views, Flask handlers, or anywhere you don't have an async event loop. - **`CamundaAsyncClient`** — asynchronous (`async`/`await`). Every method is a coroutine. Use this in FastAPI, aiohttp, or any `asyncio`-based application. **Job workers require `CamundaAsyncClient`** because they use `asyncio` for long-polling and concurrent job execution. Both clients share the same method names and parameters — the only difference is calling convention: ```python # Sync from camunda_orchestration_sdk import CamundaClient with CamundaClient() as client: topology = client.get_topology() ``` ```python # Async from camunda_orchestration_sdk import CamundaAsyncClient async def main() -> None: async with CamundaAsyncClient() as client: topology = await client.get_topology() asyncio.run(main()) ``` > **Which one should I use?** If your application already uses `asyncio` (FastAPI, aiohttp, etc.) or you need job workers, use `CamundaAsyncClient`. Otherwise, `CamundaClient` is simpler and works everywhere. --- ## Python SDK # Camunda Orchestration Cluster API – Python SDK A fully typed Python client for the [Camunda 8 Orchestration Cluster REST API](../apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). Fully compliant with the Camunda OpenAPI spec with hand-written runtime infrastructure for authentication, configuration, and job workers. - **Sync and async** — `CamundaClient` (synchronous) and `CamundaAsyncClient` (async/await) - **Strict typing** — ty + pyright compatible with PEP 561 `py.typed` marker - **Zero-config** — reads `CAMUNDA_*` environment variables (12-factor style) - **Job workers** — long-poll workers with thread, process, or async execution strategies - **OAuth & Basic auth** — pluggable authentication with automatic token management - **Pluggable logging** — inject your own logger (stdlib `logging`, loguru, or custom) --- ## CamundaClient(4) The primary entry point of the SDK. A `CamundaClient` is cheap to clone — clones share the same configuration, HTTP client, OAuth token cache, and worker registry. `CamundaClient` exposes **262** methods covering the full Orchestration Cluster REST API surface, with authentication, retries, and backpressure applied automatically. ## Methods | Method | Description | | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`activate_ad_hoc_sub_process_activities`](#activate_ad_hoc_sub_process_activities) | Activate activities within an ad-hoc sub-process (`POST /element-instances/ad-hoc-activities/{adHocSubProcessInstanceKey}/activation`). | | [`activate_jobs`](#activate_jobs) | Activate jobs of a given type. Prefer `CamundaClient::create_job_worker` for continuous polling. | | [`assign_client_to_group`](#assign_client_to_group) | Assign a client to a group (`PUT /groups/{groupId}/clients/{clientId}`). | | [`assign_client_to_tenant`](#assign_client_to_tenant) | Assign a client to a tenant (`PUT /tenants/{tenantId}/clients/{clientId}`). | | [`assign_group_to_tenant`](#assign_group_to_tenant) | Assign a group to a tenant (`PUT /tenants/{tenantId}/groups/{groupId}`). | | [`assign_mapping_rule_to_group`](#assign_mapping_rule_to_group) | Assign a mapping rule to a group (`PUT /groups/{groupId}/mapping-rules/{mappingRuleId}`). | | [`assign_mapping_rule_to_tenant`](#assign_mapping_rule_to_tenant) | Assign a mapping rule to a tenant (`PUT /tenants/{tenantId}/mapping-rules/{mappingRuleId}`). | | [`assign_process_instance_business_id`](#assign_process_instance_business_id) | Assign business id to process instance (`POST /process-instances/{processInstanceKey}/business-id-assignment`). | | [`assign_role_to_client`](#assign_role_to_client) | Assign a role to a client (`PUT /roles/{roleId}/clients/{clientId}`). | | [`assign_role_to_group`](#assign_role_to_group) | Assign a role to a group (`PUT /roles/{roleId}/groups/{groupId}`). | | [`assign_role_to_mapping_rule`](#assign_role_to_mapping_rule) | Assign a role to a mapping rule (`PUT /roles/{roleId}/mapping-rules/{mappingRuleId}`). | | [`assign_role_to_tenant`](#assign_role_to_tenant) | Assign a role to a tenant (`PUT /tenants/{tenantId}/roles/{roleId}`). | | [`assign_role_to_user`](#assign_role_to_user) | Assign a role to a user (`PUT /roles/{roleId}/users/{username}`). | | [`assign_user_task`](#assign_user_task) | Assign user task (`POST /user-tasks/{userTaskKey}/assignment`). | | [`assign_user_to_group`](#assign_user_to_group) | Assign a user to a group (`PUT /groups/{groupId}/users/{username}`). | | [`assign_user_to_tenant`](#assign_user_to_tenant) | Assign a user to a tenant (`PUT /tenants/{tenantId}/users/{username}`). | | [`auth`](#auth) | The authentication handler. | | [`backpressure_state`](#backpressure_state) | A snapshot of the adaptive backpressure controller's state, for observability. | | [`broadcast_signal`](#broadcast_signal) | Broadcast a signal. The configured default tenant id is applied when unset. | | [`cancel_batch_operation`](#cancel_batch_operation) | Cancel Batch operation (`POST /batch-operations/{batchOperationKey}/cancellation`). | | [`cancel_cluster_rebalance`](#cancel_cluster_rebalance) | Stop the running rebalance (`DELETE /cluster/v2/rebalance`). | | [`cancel_process_instance`](#cancel_process_instance) | Cancel a running process instance by key. | | [`cancel_process_instances_batch_operation`](#cancel_process_instances_batch_operation) | Cancel process instances (batch) (`POST /process-instances/cancellation`). | | [`change_cluster_mode`](#change_cluster_mode) | Change cluster mode (`PATCH /mode`). | | [`change_cluster_mode_as_cluster_admin`](#change_cluster_mode_as_cluster_admin) | Change the cluster mode of one or every physical tenant (`PATCH /cluster/v2/mode`). | | [`clock`](#clock) | The clock this client's cadence resolves through. | | [`complete_job`](#complete_job) | Complete a job, optionally with output variables. | | [`complete_user_task`](#complete_user_task) | Complete user task (`POST /user-tasks/{userTaskKey}/completion`). | | [`config`](#config) | The resolved configuration. | | [`configuration`](#configuration) | Build a generated-client `Configuration` with the base URL set and the current authentication applied (refreshing the OAuth token if needed). | | [`correlate_message`](#correlate_message) | Correlate a message to a waiting process instance. The configured default tenant id is applied when unset. | | [`create_admin_user`](#create_admin_user) | Create admin user (`POST /setup/user`). | | [`create_agent_instance`](#create_agent_instance) | Create agent instance (`POST /agent-instances`). | | [`create_authorization`](#create_authorization) | Create authorization (`POST /authorizations`). | | [`create_deployment`](#create_deployment) | Deploy resources (`POST /deployments`). | | [`create_document`](#create_document) | Upload document (`POST /documents`). | | [`create_document_link`](#create_document_link) | Create document link (`POST /documents/{documentId}/links`). | | [`create_documents`](#create_documents) | Upload multiple documents (`POST /documents/batch`). | | [`create_element_instance_variables`](#create_element_instance_variables) | Update element instance variables (`PUT /element-instances/{elementInstanceKey}/variables`). | | [`create_global_cluster_variable`](#create_global_cluster_variable) | Create a global-scoped cluster variable (`POST /cluster-variables/global`). | | [`create_global_task_listener`](#create_global_task_listener) | Create global user task listener (`POST /global-task-listeners`). | | [`create_group`](#create_group) | Create group (`POST /groups`). | | [`create_job_worker`](#create_job_worker) | Create a `JobWorker` that continuously polls for and processes jobs of the configured type. Call `JobWorker::run` (or `JobWorker::start` / `JobWorker::spawn`) to begin. | | [`create_mapping_rule`](#create_mapping_rule) | Create mapping rule (`POST /mapping-rules`). | | [`create_process_instance`](#create_process_instance) | Create (start) a process instance. The configured default tenant id is applied when the instruction does not already specify one. | | [`create_role`](#create_role) | Create role (`POST /roles`). | | [`create_tenant`](#create_tenant) | Create tenant (`POST /tenants`). | | [`create_tenant_cluster_variable`](#create_tenant_cluster_variable) | Create a tenant-scoped cluster variable (`POST /cluster-variables/tenants/{tenantId}`). | | [`create_user`](#create_user) | Create user (`POST /users`). | | [`delete_authorization`](#delete_authorization) | Delete authorization (`DELETE /authorizations/{authorizationKey}`). | | [`delete_decision_instance`](#delete_decision_instance) | Delete decision instance (`POST /decision-instances/{decisionEvaluationKey}/deletion`). | | [`delete_decision_instances_batch_operation`](#delete_decision_instances_batch_operation) | Delete decision instances (batch) (`POST /decision-instances/deletion`). | | [`delete_document`](#delete_document) | Delete document (`DELETE /documents/{documentId}`). | | [`delete_global_cluster_variable`](#delete_global_cluster_variable) | Delete a global-scoped cluster variable (`DELETE /cluster-variables/global/{name}`). | | [`delete_global_task_listener`](#delete_global_task_listener) | Delete global user task listener (`DELETE /global-task-listeners/{id}`). | | [`delete_group`](#delete_group) | Delete group (`DELETE /groups/{groupId}`). | | [`delete_history_backup`](#delete_history_backup) | Delete history backup (`DELETE /backups/history/{backupId}`). | | [`delete_history_backup_as_cluster_admin`](#delete_history_backup_as_cluster_admin) | Delete a history backup across physical tenants (`DELETE /cluster/v2/backups/history/{backupId}`). | | [`delete_mapping_rule`](#delete_mapping_rule) | Delete a mapping rule (`DELETE /mapping-rules/{mappingRuleId}`). | | [`delete_process_instance`](#delete_process_instance) | Delete process instance (`POST /process-instances/{processInstanceKey}/deletion`). | | [`delete_process_instances_batch_operation`](#delete_process_instances_batch_operation) | Delete process instances (batch) (`POST /process-instances/deletion`). | | [`delete_resource`](#delete_resource) | Delete resource (`POST /resources/{resourceKey}/deletion`). | | [`delete_role`](#delete_role) | Delete role (`DELETE /roles/{roleId}`). | | [`delete_runtime_backup`](#delete_runtime_backup) | Delete runtime backup (`DELETE /backups/runtime/{backupId}`). | | [`delete_runtime_backup_as_cluster_admin`](#delete_runtime_backup_as_cluster_admin) | Delete a runtime backup across physical tenants (`DELETE /cluster/v2/backups/runtime/{backupId}`). | | [`delete_runtime_backup_state`](#delete_runtime_backup_state) | Delete runtime backup state (`DELETE /backups/runtime/state`). | | [`delete_runtime_backup_state_as_cluster_admin`](#delete_runtime_backup_state_as_cluster_admin) | Delete runtime backup state across physical tenants (`DELETE /cluster/v2/backups/runtime/state`). | | [`delete_tenant`](#delete_tenant) | Delete tenant (`DELETE /tenants/{tenantId}`). | | [`delete_tenant_cluster_variable`](#delete_tenant_cluster_variable) | Delete a tenant-scoped cluster variable (`DELETE /cluster-variables/tenants/{tenantId}/{name}`). | | [`delete_user`](#delete_user) | Delete user (`DELETE /users/{username}`). | | [`deploy_resources`](#deploy_resources) | Deploy one or more resources (BPMN, DMN, forms) from local file paths. The configured default tenant id is applied when `tenant_id` is `None`. | | [`evaluate_conditionals`](#evaluate_conditionals) | Evaluate root level conditional start events (`POST /conditionals/evaluation`). | | [`evaluate_decision`](#evaluate_decision) | Evaluate a decision (DMN). The configured default tenant id is applied when unset. | | [`evaluate_expression`](#evaluate_expression) | Evaluate an expression (`POST /expression/evaluation`). | | [`eventual`](#eventual) | Poll a read operation until it returns `Ok`, retrying `404 Not Found` (the typical symptom of a not-yet-replicated read) until the eventual-consistency window elapses. | | [`eventual_until`](#eventual_until) | Poll a read operation until `predicate` is satisfied by its result, retrying `404` and ignoring consistent-but-not-yet-matching reads, until the eventual-consistency window elapses. | | [`fail_job`](#fail_job) | Fail a job, decrementing retries. | | [`from_env`](#from_env) | Construct a client from environment variables only. | | [`get_agent_definition`](#get_agent_definition) | Get agent definition (`GET /agent-definitions/{agentDefinitionKey}`). | | [`get_agent_instance`](#get_agent_instance) | Get agent instance (`GET /agent-instances/{agentInstanceKey}`). | | [`get_audit_log`](#get_audit_log) | Get audit log (`GET /audit-logs/{auditLogKey}`). | | [`get_authentication`](#get_authentication) | Get current user (`GET /authentication/me`). | | [`get_authorization`](#get_authorization) | Get authorization (`GET /authorizations/{authorizationKey}`). | | [`get_batch_operation`](#get_batch_operation) | Get batch operation (`GET /batch-operations/{batchOperationKey}`). | | [`get_cluster_exporting_status`](#get_cluster_exporting_status) | Get exporting status of the whole cluster (`GET /cluster/v2/exporting`). | | [`get_cluster_rebalance`](#get_cluster_rebalance) | Report the cluster's current leadership balance (`GET /cluster/v2/rebalance`). | | [`get_cluster_status`](#get_cluster_status) | Get the status of the whole cluster (`GET /cluster/v2/status`). | | [`get_cluster_topology`](#get_cluster_topology) | Get the topology of the whole cluster (`GET /cluster/v2/topology`). | | [`get_cluster_upgrade_status`](#get_cluster_upgrade_status) | Get the upgrade-readiness status of the whole cluster (`GET /cluster/v2/status/upgrade`). | | [`get_decision_definition`](#get_decision_definition) | Get decision definition (`GET /decision-definitions/{decisionDefinitionKey}`). | | [`get_decision_definition_xml`](#get_decision_definition_xml) | Get decision definition XML (`GET /decision-definitions/{decisionDefinitionKey}/xml`). | | [`get_decision_instance`](#get_decision_instance) | Get decision instance (`GET /decision-instances/{decisionEvaluationInstanceKey}`). | | [`get_decision_requirements`](#get_decision_requirements) | Get decision requirements (`GET /decision-requirements/{decisionRequirementsKey}`). | | [`get_decision_requirements_xml`](#get_decision_requirements_xml) | Get decision requirements XML (`GET /decision-requirements/{decisionRequirementsKey}/xml`). | | [`get_document`](#get_document) | Download document (`GET /documents/{documentId}`). | | [`get_element_instance`](#get_element_instance) | Get element instance (`GET /element-instances/{elementInstanceKey}`). | | [`get_exporting_status`](#get_exporting_status) | Get exporting status (`GET /exporting`). | | [`get_form_by_key`](#get_form_by_key) | Get form by key (`GET /forms/{formKey}`). | | [`get_global_cluster_variable`](#get_global_cluster_variable) | Get a global-scoped cluster variable (`GET /cluster-variables/global/{name}`). | | [`get_global_job_statistics`](#get_global_job_statistics) | Global job statistics (`GET /jobs/statistics/global`). | | [`get_global_task_listener`](#get_global_task_listener) | Get global user task listener (`GET /global-task-listeners/{id}`). | | [`get_group`](#get_group) | Get group (`GET /groups/{groupId}`). | | [`get_history_backup`](#get_history_backup) | Get history backup (`GET /backups/history/{backupId}`). | | [`get_history_backup_as_cluster_admin`](#get_history_backup_as_cluster_admin) | Get a history backup across physical tenants (`GET /cluster/v2/backups/history/{backupId}`). | | [`get_incident`](#get_incident) | Get incident (`GET /incidents/{incidentKey}`). | | [`get_job_error_statistics`](#get_job_error_statistics) | Get error metrics for a job type (`POST /jobs/statistics/errors`). | | [`get_job_time_series_statistics`](#get_job_time_series_statistics) | Get time-series metrics for a job type (`POST /jobs/statistics/time-series`). | | [`get_job_type_statistics`](#get_job_type_statistics) | Get job statistics by type (`POST /jobs/statistics/by-types`). | | [`get_job_worker_statistics`](#get_job_worker_statistics) | Get job statistics by worker (`POST /jobs/statistics/by-workers`). | | [`get_license`](#get_license) | Get license status (`GET /license`). | | [`get_mapping_rule`](#get_mapping_rule) | Get a mapping rule (`GET /mapping-rules/{mappingRuleId}`). | | [`get_process_definition`](#get_process_definition) | Get process definition (`GET /process-definitions/{processDefinitionKey}`). | | [`get_process_definition_instance_statistics`](#get_process_definition_instance_statistics) | Get process instance statistics (`POST /process-definitions/statistics/process-instances`). | | [`get_process_definition_instance_version_statistics`](#get_process_definition_instance_version_statistics) | Get process instance statistics by version (`POST /process-definitions/statistics/process-instances-by-version`). | | [`get_process_definition_message_subscription_statistics`](#get_process_definition_message_subscription_statistics) | Get message subscription statistics (`POST /process-definitions/statistics/message-subscriptions`). | | [`get_process_definition_statistics`](#get_process_definition_statistics) | Get process definition statistics (`POST /process-definitions/{processDefinitionKey}/statistics/element-instances`). | | [`get_process_definition_xml`](#get_process_definition_xml) | Get process definition XML (`GET /process-definitions/{processDefinitionKey}/xml`). | | [`get_process_instance`](#get_process_instance) | Fetch a process instance by key (a read; not subject to backpressure). Returns a `404` `CamundaError::Api` if not yet visible — compose with `CamundaClient::eventual` to wait through replication lag. | | [`get_process_instance_call_hierarchy`](#get_process_instance_call_hierarchy) | Get call hierarchy (`GET /process-instances/{processInstanceKey}/call-hierarchy`). | | [`get_process_instance_sequence_flows`](#get_process_instance_sequence_flows) | Get sequence flows (`GET /process-instances/{processInstanceKey}/sequence-flows`). | | [`get_process_instance_statistics`](#get_process_instance_statistics) | Get element instance statistics (`GET /process-instances/{processInstanceKey}/statistics/element-instances`). | | [`get_process_instance_statistics_by_definition`](#get_process_instance_statistics_by_definition) | Get process instance statistics by definition (`POST /incidents/statistics/process-instances-by-definition`). | | [`get_process_instance_statistics_by_error`](#get_process_instance_statistics_by_error) | Get process instance statistics by error (`POST /incidents/statistics/process-instances-by-error`). | | [`get_process_instance_wait_state_statistics`](#get_process_instance_wait_state_statistics) | Get wait state statistics (`GET /process-instances/{processInstanceKey}/statistics/wait-states`). | | [`get_resource`](#get_resource) | Get resource (`GET /resources/{resourceKey}`). | | [`get_resource_content`](#get_resource_content) | Get RPA resource content (deprecated) (`GET /resources/{resourceKey}/content`). | | [`get_resource_content_binary`](#get_resource_content_binary) | Get resource content as binary (`GET /resources/{resourceKey}/content/binary`). | | [`get_restore_status`](#get_restore_status) | Get the status of the restore that is currently in progress (`GET /restore`). | | [`get_role`](#get_role) | Get role (`GET /roles/{roleId}`). | | [`get_runtime_backup`](#get_runtime_backup) | Get runtime backup (`GET /backups/runtime/{backupId}`). | | [`get_runtime_backup_as_cluster_admin`](#get_runtime_backup_as_cluster_admin) | Get a runtime backup across physical tenants (`GET /cluster/v2/backups/runtime/{backupId}`). | | [`get_runtime_backup_state`](#get_runtime_backup_state) | Get runtime backup state (`GET /backups/runtime/state`). | | [`get_runtime_backup_state_as_cluster_admin`](#get_runtime_backup_state_as_cluster_admin) | Get runtime backup state across physical tenants (`GET /cluster/v2/backups/runtime/state`). | | [`get_start_process_form`](#get_start_process_form) | Get process start form (`GET /process-definitions/{processDefinitionKey}/form`). | | [`get_status`](#get_status) | Get physical tenant status (`GET /status`). | | [`get_system_configuration`](#get_system_configuration) | System configuration (alpha) (`GET /system/configuration`). | | [`get_tenant`](#get_tenant) | Get tenant (`GET /tenants/{tenantId}`). | | [`get_tenant_cluster_variable`](#get_tenant_cluster_variable) | Get a tenant-scoped cluster variable (`GET /cluster-variables/tenants/{tenantId}/{name}`). | | [`get_usage_metrics`](#get_usage_metrics) | Get usage metrics (`GET /system/usage-metrics`). | | [`get_user`](#get_user) | Get user (`GET /users/{username}`). | | [`get_user_task`](#get_user_task) | Get user task (`GET /user-tasks/{userTaskKey}`). | | [`get_user_task_form`](#get_user_task_form) | Get user task form (`GET /user-tasks/{userTaskKey}/form`). | | [`get_variable`](#get_variable) | Get variable (`GET /variables/{variableKey}`). | | [`init_logging`](#init_logging) | Install a formatting `tracing` subscriber filtered to the configured `CAMUNDA_SDK_LOG_LEVEL`. No-op if a global subscriber is already set or logging is off. Returns `true` if this call installed the subscriber. | | [`list_history_backups`](#list_history_backups) | List history backups (`GET /backups/history`). | | [`list_history_backups_as_cluster_admin`](#list_history_backups_as_cluster_admin) | List history backups across physical tenants (`GET /cluster/v2/backups/history`). | | [`list_runtime_backups`](#list_runtime_backups) | List runtime backups (`GET /backups/runtime`). | | [`list_runtime_backups_as_cluster_admin`](#list_runtime_backups_as_cluster_admin) | List runtime backups across physical tenants (`GET /cluster/v2/backups/runtime`). | | [`list_secrets`](#list_secrets) | List secrets (`POST /secrets/list`). | | [`migrate_process_instance`](#migrate_process_instance) | Migrate process instance (`POST /process-instances/{processInstanceKey}/migration`). | | [`migrate_process_instances_batch_operation`](#migrate_process_instances_batch_operation) | Migrate process instances (batch) (`POST /process-instances/migration`). | | [`modify_process_instance`](#modify_process_instance) | Modify process instance (`POST /process-instances/{processInstanceKey}/modification`). | | [`modify_process_instances_batch_operation`](#modify_process_instances_batch_operation) | Modify process instances (batch) (`POST /process-instances/modification`). | | [`new`](#new) | Construct a client from `CamundaOptions` (environment + overrides). | | [`pause_cluster_exporting`](#pause_cluster_exporting) | Pause exporting across the whole cluster (`POST /cluster/v2/exporting/pause`). | | [`pause_exporting`](#pause_exporting) | Pause exporting (`POST /exporting/pause`). | | [`pin_clock`](#pin_clock) | Pin internal clock (alpha) (`PUT /clock`). | | [`publish_message`](#publish_message) | Publish a message (no correlation key matching against active subscriptions only — buffered). The configured default tenant id is applied when unset. | | [`random`](#random) | The source this client draws jitter from. | | [`reset_clock`](#reset_clock) | Reset internal clock (alpha) (`POST /clock/reset`). | | [`resolve_incident`](#resolve_incident) | Resolve incident (`POST /incidents/{incidentKey}/resolution`). | | [`resolve_incidents_batch_operation`](#resolve_incidents_batch_operation) | Resolve related incidents (batch) (`POST /process-instances/incident-resolution`). | | [`resolve_process_instance_incidents`](#resolve_process_instance_incidents) | Resolve related incidents (`POST /process-instances/{processInstanceKey}/incident-resolution`). | | [`resolve_secrets`](#resolve_secrets) | Resolve secrets (`POST /secrets/resolve`). | | [`restore`](#restore) | Restore from a backup (`POST /restore`). | | [`restore_as_cluster_admin`](#restore_as_cluster_admin) | Restore one or every physical tenant from a backup (`POST /cluster/v2/restore`). | | [`resume_batch_operation`](#resume_batch_operation) | Resume Batch operation (`POST /batch-operations/{batchOperationKey}/resumption`). | | [`resume_cluster_exporting`](#resume_cluster_exporting) | Resume exporting across the whole cluster (`POST /cluster/v2/exporting/resume`). | | [`resume_exporting`](#resume_exporting) | Resume exporting (`POST /exporting/resume`). | | [`resume_process_instance`](#resume_process_instance) | Resume process instance (`POST /process-instances/{processInstanceKey}/resumption`). | | [`resume_process_instances_batch_operation`](#resume_process_instances_batch_operation) | Resume process instances (batch) (`POST /process-instances/resumption`). | | [`running_workers`](#running_workers) | The job types of all currently-registered workers that are still running. | | [`search_agent_definitions`](#search_agent_definitions) | Search agent definitions (`POST /agent-definitions/search`). | | [`search_agent_instance_history`](#search_agent_instance_history) | Search agent instance history (`POST /agent-instances/{agentInstanceKey}/history/search`). | | [`search_agent_instances`](#search_agent_instances) | Search agent instances (`POST /agent-instances/search`). | | [`search_audit_logs`](#search_audit_logs) | Search audit logs (`POST /audit-logs/search`). | | [`search_authorizations`](#search_authorizations) | Search authorizations (`POST /authorizations/search`). | | [`search_batch_operation_items`](#search_batch_operation_items) | Search batch operation items (`POST /batch-operation-items/search`). | | [`search_batch_operations`](#search_batch_operations) | Search batch operations (`POST /batch-operations/search`). | | [`search_clients_for_group`](#search_clients_for_group) | Search group clients (`POST /groups/{groupId}/clients/search`). | | [`search_clients_for_role`](#search_clients_for_role) | Search role clients (`POST /roles/{roleId}/clients/search`). | | [`search_clients_for_tenant`](#search_clients_for_tenant) | Search clients for tenant (`POST /tenants/{tenantId}/clients/search`). | | [`search_cluster_variables`](#search_cluster_variables) | Search for cluster variables based on given criteria (`POST /cluster-variables/search`). | | [`search_correlated_message_subscriptions`](#search_correlated_message_subscriptions) | Search correlated message subscriptions (`POST /correlated-message-subscriptions/search`). | | [`search_decision_definitions`](#search_decision_definitions) | Search decision definitions (`POST /decision-definitions/search`). | | [`search_decision_instances`](#search_decision_instances) | Search decision instances (`POST /decision-instances/search`). | | [`search_decision_requirements`](#search_decision_requirements) | Search decision requirements (`POST /decision-requirements/search`). | | [`search_element_instance_incidents`](#search_element_instance_incidents) | Search for incidents of a specific element instance (`POST /element-instances/{elementInstanceKey}/incidents/search`). | | [`search_element_instance_wait_states`](#search_element_instance_wait_states) | Search element instance wait states (`POST /element-instances/wait-states/search`). | | [`search_element_instances`](#search_element_instances) | Search element instances (`POST /element-instances/search`). | | [`search_global_task_listeners`](#search_global_task_listeners) | Search global user task listeners (`POST /global-task-listeners/search`). | | [`search_group_ids_for_tenant`](#search_group_ids_for_tenant) | Search groups for tenant (`POST /tenants/{tenantId}/groups/search`). | | [`search_groups`](#search_groups) | Search groups (`POST /groups/search`). | | [`search_groups_for_role`](#search_groups_for_role) | Search role groups (`POST /roles/{roleId}/groups/search`). | | [`search_incidents`](#search_incidents) | Search incidents (`POST /incidents/search`). | | [`search_jobs`](#search_jobs) | Search jobs (`POST /jobs/search`). | | [`search_mapping_rule`](#search_mapping_rule) | Search mapping rules (`POST /mapping-rules/search`). | | [`search_mapping_rules_for_group`](#search_mapping_rules_for_group) | Search group mapping rules (`POST /groups/{groupId}/mapping-rules/search`). | | [`search_mapping_rules_for_role`](#search_mapping_rules_for_role) | Search role mapping rules (`POST /roles/{roleId}/mapping-rules/search`). | | [`search_mapping_rules_for_tenant`](#search_mapping_rules_for_tenant) | Search mapping rules for tenant (`POST /tenants/{tenantId}/mapping-rules/search`). | | [`search_message_subscriptions`](#search_message_subscriptions) | Search message subscriptions (`POST /message-subscriptions/search`). | | [`search_own_authorizations`](#search_own_authorizations) | Search own authorizations (`POST /authentication/me/authorizations/search`). | | [`search_process_definition_variable_names`](#search_process_definition_variable_names) | Search process definition variable names (`POST /process-definitions/{processDefinitionKey}/variable-names/search`). | | [`search_process_definitions`](#search_process_definitions) | Search process definitions (`POST /process-definitions/search`). | | [`search_process_instance_incidents`](#search_process_instance_incidents) | Search related incidents (`POST /process-instances/{processInstanceKey}/incidents/search`). | | [`search_process_instances`](#search_process_instances) | Search process instances (`POST /process-instances/search`). | | [`search_resources`](#search_resources) | Search resources (`POST /resources/search`). | | [`search_roles`](#search_roles) | Search roles (`POST /roles/search`). | | [`search_roles_for_group`](#search_roles_for_group) | Search group roles (`POST /groups/{groupId}/roles/search`). | | [`search_roles_for_tenant`](#search_roles_for_tenant) | Search roles for tenant (`POST /tenants/{tenantId}/roles/search`). | | [`search_tenants`](#search_tenants) | Search tenants (`POST /tenants/search`). | | [`search_user_task_audit_logs`](#search_user_task_audit_logs) | Search user task audit logs (`POST /user-tasks/{userTaskKey}/audit-logs/search`). | | [`search_user_task_effective_variables`](#search_user_task_effective_variables) | Search user task effective variables (`POST /user-tasks/{userTaskKey}/effective-variables/search`). | | [`search_user_task_variables`](#search_user_task_variables) | Search user task variables (`POST /user-tasks/{userTaskKey}/variables/search`). | | [`search_user_tasks`](#search_user_tasks) | Search user tasks (`POST /user-tasks/search`). | | [`search_users`](#search_users) | Search users (`POST /users/search`). | | [`search_users_for_group`](#search_users_for_group) | Search group users (`POST /groups/{groupId}/users/search`). | | [`search_users_for_role`](#search_users_for_role) | Search role users (`POST /roles/{roleId}/users/search`). | | [`search_users_for_tenant`](#search_users_for_tenant) | Search users for tenant (`POST /tenants/{tenantId}/users/search`). | | [`search_variables`](#search_variables) | Search variables, returning the raw generated result. | | [`search_variables_as`](#search_variables_as) | Search variables and deserialize each variable's JSON `value` into a typed `T`. | | [`spawn_worker`](#spawn_worker) | Spawn a managed job worker and register it for lifecycle control. The returned handle can stop the individual worker; `CamundaClient::stop_all_workers` stops every registered worker. The worker is also retained by the client so it keeps running even if the returned handle is dropped. | | [`stop_all_workers`](#stop_all_workers) | Gracefully stop every registered worker, letting in-flight jobs drain, and await their completion. Clears the registry. | | [`suspend_batch_operation`](#suspend_batch_operation) | Suspend Batch operation (`POST /batch-operations/{batchOperationKey}/suspension`). | | [`suspend_process_instance`](#suspend_process_instance) | Suspend process instance (`POST /process-instances/{processInstanceKey}/suspension`). | | [`suspend_process_instances_batch_operation`](#suspend_process_instances_batch_operation) | Suspend process instances (batch) (`POST /process-instances/suspension`). | | [`sync_runtime_backup_state`](#sync_runtime_backup_state) | Force-write runtime backup state (`POST /backups/runtime/state/sync`). | | [`sync_runtime_backup_state_as_cluster_admin`](#sync_runtime_backup_state_as_cluster_admin) | Force-write runtime backup state across physical tenants (`POST /cluster/v2/backups/runtime/state/sync`). | | [`take_history_backup`](#take_history_backup) | Take a history backup (`POST /backups/history`). | | [`take_history_backup_as_cluster_admin`](#take_history_backup_as_cluster_admin) | Take a history backup on one or every physical tenant (`POST /cluster/v2/backups/history`). | | [`take_runtime_backup`](#take_runtime_backup) | Take a runtime backup (`POST /backups/runtime`). | | [`take_runtime_backup_as_cluster_admin`](#take_runtime_backup_as_cluster_admin) | Take a runtime backup on one or every physical tenant (`POST /cluster/v2/backups/runtime`). | | [`throw_job_error`](#throw_job_error) | Throw a BPMN error from a job. | | [`topology`](#topology) | Fetch the cluster topology. | | [`trigger_cluster_rebalance`](#trigger_cluster_rebalance) | Trigger a cluster-wide leadership rebalance (`POST /cluster/v2/rebalance`). | | [`unassign_client_from_group`](#unassign_client_from_group) | Unassign a client from a group (`DELETE /groups/{groupId}/clients/{clientId}`). | | [`unassign_client_from_tenant`](#unassign_client_from_tenant) | Unassign a client from a tenant (`DELETE /tenants/{tenantId}/clients/{clientId}`). | | [`unassign_group_from_tenant`](#unassign_group_from_tenant) | Unassign a group from a tenant (`DELETE /tenants/{tenantId}/groups/{groupId}`). | | [`unassign_mapping_rule_from_group`](#unassign_mapping_rule_from_group) | Unassign a mapping rule from a group (`DELETE /groups/{groupId}/mapping-rules/{mappingRuleId}`). | | [`unassign_mapping_rule_from_tenant`](#unassign_mapping_rule_from_tenant) | Unassign a mapping rule from a tenant (`DELETE /tenants/{tenantId}/mapping-rules/{mappingRuleId}`). | | [`unassign_role_from_client`](#unassign_role_from_client) | Unassign a role from a client (`DELETE /roles/{roleId}/clients/{clientId}`). | | [`unassign_role_from_group`](#unassign_role_from_group) | Unassign a role from a group (`DELETE /roles/{roleId}/groups/{groupId}`). | | [`unassign_role_from_mapping_rule`](#unassign_role_from_mapping_rule) | Unassign a role from a mapping rule (`DELETE /roles/{roleId}/mapping-rules/{mappingRuleId}`). | | [`unassign_role_from_tenant`](#unassign_role_from_tenant) | Unassign a role from a tenant (`DELETE /tenants/{tenantId}/roles/{roleId}`). | | [`unassign_role_from_user`](#unassign_role_from_user) | Unassign a role from a user (`DELETE /roles/{roleId}/users/{username}`). | | [`unassign_user_from_group`](#unassign_user_from_group) | Unassign a user from a group (`DELETE /groups/{groupId}/users/{username}`). | | [`unassign_user_from_tenant`](#unassign_user_from_tenant) | Unassign a user from a tenant (`DELETE /tenants/{tenantId}/users/{username}`). | | [`unassign_user_task`](#unassign_user_task) | Unassign user task (`DELETE /user-tasks/{userTaskKey}/assignee`). | | [`update_agent_instance`](#update_agent_instance) | Update agent instance (`PATCH /agent-instances/{agentInstanceKey}`). | | [`update_authorization`](#update_authorization) | Update authorization (`PUT /authorizations/{authorizationKey}`). | | [`update_global_cluster_variable`](#update_global_cluster_variable) | Update a global-scoped cluster variable (`PUT /cluster-variables/global/{name}`). | | [`update_global_task_listener`](#update_global_task_listener) | Update global user task listener (`PUT /global-task-listeners/{id}`). | | [`update_group`](#update_group) | Update group (`PUT /groups/{groupId}`). | | [`update_job`](#update_job) | Update job (`PATCH /jobs/{jobKey}`). | | [`update_jobs_batch_operation`](#update_jobs_batch_operation) | Update jobs (batch) (`POST /jobs/batch-update`). | | [`update_mapping_rule`](#update_mapping_rule) | Update mapping rule (`PUT /mapping-rules/{mappingRuleId}`). | | [`update_role`](#update_role) | Update role (`PUT /roles/{roleId}`). | | [`update_tenant`](#update_tenant) | Update tenant (`PUT /tenants/{tenantId}`). | | [`update_tenant_cluster_variable`](#update_tenant_cluster_variable) | Update a tenant-scoped cluster variable (`PUT /cluster-variables/tenants/{tenantId}/{name}`). | | [`update_user`](#update_user) | Update user (`PUT /users/{username}`). | | [`update_user_task`](#update_user_task) | Update user task (`PATCH /user-tasks/{userTaskKey}`). | | [`worker_config`](#worker_config) | Build a `JobWorkerConfig` for `job_type` pre-seeded from the SDK's resolved worker defaults (env-driven: `CAMUNDA_WORKER_*`). Builder methods override fields. | ## Method details ### activate_ad_hoc_sub_process_activities ```rust pub async fn activate_ad_hoc_sub_process_activities(&self, params: ActivateAdHocSubProcessActivitiesParams) -> Result<()> ``` Activate activities within an ad-hoc sub-process (`POST /element-instances/ad-hoc-activities/{adHocSubProcessInstanceKey}/activation`). **Example** ```rust async fn activate_ad_hoc_sub_process_activities( ad_hoc_sub_process_instance_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .activate_ad_hoc_sub_process_activities(ActivateAdHocSubProcessActivitiesParams { ad_hoc_sub_process_instance_key, ad_hoc_sub_process_activate_activities_instruction: AdHocSubProcessActivateActivitiesInstruction { elements: vec![AdHocSubProcessActivateActivityReference { element_id: ElementId::assume_exists("my-element"), ..Default::default() }], ..Default::default() }, }) .await?; println!("Activate activities within an ad-hoc sub-process: done"); Ok(()) } ``` ### activate_jobs ```rust pub async fn activate_jobs(&self, request: models::JobActivationRequest) -> Result ``` Activate jobs of a given type. Prefer `CamundaClient::create_job_worker` for continuous polling. **Example** ```rust async fn activate_jobs() -> Result<(), Box> { let client = CamundaClient::from_env()?; // For long-running workers prefer `client.create_job_worker(..)`, which handles // polling, back-off, and concurrency for you. let activated = client .activate_jobs(JobActivationRequest { r#type: "payment-service".to_string(), timeout: 60_000, max_jobs_to_activate: 10, worker: Some("payment-worker".to_string()), ..Default::default() }) .await?; for job in activated.jobs { println!("Activated job {} for {}", job.job_key, job.element_id); } Ok(()) } ``` ### assign_client_to_group ```rust pub async fn assign_client_to_group(&self, params: AssignClientToGroupParams) -> Result<()> ``` Assign a client to a group (`PUT /groups/{groupId}/clients/{clientId}`). **Example** ```rust async fn assign_client_to_group( group_id: String, client_id: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .assign_client_to_group(AssignClientToGroupParams { group_id, client_id, }) .await?; println!("Assign a client to a group: done"); Ok(()) } ``` ### assign_client_to_tenant ```rust pub async fn assign_client_to_tenant(&self, params: AssignClientToTenantParams) -> Result<()> ``` Assign a client to a tenant (`PUT /tenants/{tenantId}/clients/{clientId}`). **Example** ```rust async fn assign_client_to_tenant( tenant_id: String, client_id: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .assign_client_to_tenant(AssignClientToTenantParams { tenant_id, client_id, }) .await?; println!("Assign a client to a tenant: done"); Ok(()) } ``` ### assign_group_to_tenant ```rust pub async fn assign_group_to_tenant(&self, params: AssignGroupToTenantParams) -> Result<()> ``` Assign a group to a tenant (`PUT /tenants/{tenantId}/groups/{groupId}`). **Example** ```rust async fn assign_group_to_tenant( tenant_id: String, group_id: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .assign_group_to_tenant(AssignGroupToTenantParams { tenant_id, group_id, }) .await?; println!("Assign a group to a tenant: done"); Ok(()) } ``` ### assign_mapping_rule_to_group ```rust pub async fn assign_mapping_rule_to_group(&self, params: AssignMappingRuleToGroupParams) -> Result<()> ``` Assign a mapping rule to a group (`PUT /groups/{groupId}/mapping-rules/{mappingRuleId}`). **Example** ```rust async fn assign_mapping_rule_to_group( group_id: String, mapping_rule_id: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .assign_mapping_rule_to_group(AssignMappingRuleToGroupParams { group_id, mapping_rule_id, }) .await?; println!("Assign a mapping rule to a group: done"); Ok(()) } ``` ### assign_mapping_rule_to_tenant ```rust pub async fn assign_mapping_rule_to_tenant(&self, params: AssignMappingRuleToTenantParams) -> Result<()> ``` Assign a mapping rule to a tenant (`PUT /tenants/{tenantId}/mapping-rules/{mappingRuleId}`). **Example** ```rust async fn assign_mapping_rule_to_tenant( tenant_id: String, mapping_rule_id: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .assign_mapping_rule_to_tenant(AssignMappingRuleToTenantParams { tenant_id, mapping_rule_id, }) .await?; println!("Assign a mapping rule to a tenant: done"); Ok(()) } ``` ### assign_process_instance_business_id ```rust pub async fn assign_process_instance_business_id(&self, params: AssignProcessInstanceBusinessIdParams) -> Result<()> ``` Assign business id to process instance (`POST /process-instances/{processInstanceKey}/business-id-assignment`). **Example** ```rust async fn assign_process_instance_business_id( process_instance_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .assign_process_instance_business_id(AssignProcessInstanceBusinessIdParams { process_instance_key, process_instance_business_id_assignment_instruction: ProcessInstanceBusinessIdAssignmentInstruction { business_id: BusinessId::assume_exists("my-business-id"), }, }) .await?; println!("Assign business id to process instance: done"); Ok(()) } ``` ### assign_role_to_client ```rust pub async fn assign_role_to_client(&self, params: AssignRoleToClientParams) -> Result<()> ``` Assign a role to a client (`PUT /roles/{roleId}/clients/{clientId}`). **Example** ```rust async fn assign_role_to_client( role_id: String, client_id: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .assign_role_to_client(AssignRoleToClientParams { role_id, client_id }) .await?; println!("Assign a role to a client: done"); Ok(()) } ``` ### assign_role_to_group ```rust pub async fn assign_role_to_group(&self, params: AssignRoleToGroupParams) -> Result<()> ``` Assign a role to a group (`PUT /roles/{roleId}/groups/{groupId}`). **Example** ```rust async fn assign_role_to_group( role_id: String, group_id: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .assign_role_to_group(AssignRoleToGroupParams { role_id, group_id }) .await?; println!("Assign a role to a group: done"); Ok(()) } ``` ### assign_role_to_mapping_rule ```rust pub async fn assign_role_to_mapping_rule(&self, params: AssignRoleToMappingRuleParams) -> Result<()> ``` Assign a role to a mapping rule (`PUT /roles/{roleId}/mapping-rules/{mappingRuleId}`). **Example** ```rust async fn assign_role_to_mapping_rule( role_id: String, mapping_rule_id: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .assign_role_to_mapping_rule(AssignRoleToMappingRuleParams { role_id, mapping_rule_id, }) .await?; println!("Assign a role to a mapping rule: done"); Ok(()) } ``` ### assign_role_to_tenant ```rust pub async fn assign_role_to_tenant(&self, params: AssignRoleToTenantParams) -> Result<()> ``` Assign a role to a tenant (`PUT /tenants/{tenantId}/roles/{roleId}`). **Example** ```rust async fn assign_role_to_tenant( tenant_id: String, role_id: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .assign_role_to_tenant(AssignRoleToTenantParams { tenant_id, role_id }) .await?; println!("Assign a role to a tenant: done"); Ok(()) } ``` ### assign_role_to_user ```rust pub async fn assign_role_to_user(&self, params: AssignRoleToUserParams) -> Result<()> ``` Assign a role to a user (`PUT /roles/{roleId}/users/{username}`). **Example** ```rust async fn assign_role_to_user( role_id: String, username: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .assign_role_to_user(AssignRoleToUserParams { role_id, username }) .await?; println!("Assign a role to a user: done"); Ok(()) } ``` ### assign_user_task ```rust pub async fn assign_user_task(&self, params: AssignUserTaskParams) -> Result<()> ``` Assign user task (`POST /user-tasks/{userTaskKey}/assignment`). **Example** ```rust async fn assign_user_task(user_task_key: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .assign_user_task(AssignUserTaskParams { user_task_key, user_task_assignment_request: UserTaskAssignmentRequest::default(), }) .await?; println!("Assign user task: done"); Ok(()) } ``` ### assign_user_to_group ```rust pub async fn assign_user_to_group(&self, params: AssignUserToGroupParams) -> Result<()> ``` Assign a user to a group (`PUT /groups/{groupId}/users/{username}`). **Example** ```rust async fn assign_user_to_group( group_id: String, username: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .assign_user_to_group(AssignUserToGroupParams { group_id, username }) .await?; println!("Assign a user to a group: done"); Ok(()) } ``` ### assign_user_to_tenant ```rust pub async fn assign_user_to_tenant(&self, params: AssignUserToTenantParams) -> Result<()> ``` Assign a user to a tenant (`PUT /tenants/{tenantId}/users/{username}`). **Example** ```rust async fn assign_user_to_tenant( tenant_id: String, username: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .assign_user_to_tenant(AssignUserToTenantParams { tenant_id, username, }) .await?; println!("Assign a user to a tenant: done"); Ok(()) } ``` ### auth ```rust pub fn auth(&self) -> &Authentication ``` The authentication handler. ### backpressure_state ```rust pub fn backpressure_state(&self) -> BackpressureState ``` A snapshot of the adaptive backpressure controller's state, for observability. ### broadcast_signal ```rust pub async fn broadcast_signal(&self, request: models::SignalBroadcastRequest) -> Result ``` Broadcast a signal. The configured default tenant id is applied when unset. **Example** ```rust async fn broadcast_signal() -> Result<(), Box> { let client = CamundaClient::from_env()?; let broadcast = client .broadcast_signal(SignalBroadcastRequest { signal_name: "cancel-all-orders".to_string(), variables: Some( [("reason".to_string(), serde_json::json!("stock outage"))] .into_iter() .collect(), ), ..Default::default() }) .await?; println!("Broadcast signal {}", broadcast.signal_key); Ok(()) } ``` ### cancel_batch_operation ```rust pub async fn cancel_batch_operation(&self, params: CancelBatchOperationParams) -> Result<()> ``` Cancel Batch operation (`POST /batch-operations/{batchOperationKey}/cancellation`). **Example** ```rust async fn cancel_batch_operation( batch_operation_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .cancel_batch_operation(CancelBatchOperationParams { batch_operation_key, }) .await?; println!("Cancel Batch operation: done"); Ok(()) } ``` ### cancel_cluster_rebalance ```rust pub async fn cancel_cluster_rebalance(&self) -> Result ``` Stop the running rebalance (`DELETE /cluster/v2/rebalance`). **Example** ```rust async fn cancel_cluster_rebalance() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client.cancel_cluster_rebalance().await?; println!("{result:#?}"); Ok(()) } ``` ### cancel_process_instance ```rust pub async fn cancel_process_instance(&self, process_instance_key: &str, request: Option) -> Result<()> ``` Cancel a running process instance by key. **Example** ```rust async fn cancel_process_instance( process_instance_key: &str, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; // Pass `Some(CancelProcessInstanceRequest { .. })` to supply an operation reference. client .cancel_process_instance(process_instance_key, None) .await?; println!("Cancelled process instance {process_instance_key}"); Ok(()) } ``` ### cancel_process_instances_batch_operation ```rust pub async fn cancel_process_instances_batch_operation(&self, params: CancelProcessInstancesBatchOperationParams) -> Result ``` Cancel process instances (batch) (`POST /process-instances/cancellation`). **Example** ```rust async fn cancel_process_instances_batch_operation() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .cancel_process_instances_batch_operation(CancelProcessInstancesBatchOperationParams { process_instance_cancellation_batch_operation_request: ProcessInstanceCancellationBatchOperationRequest { filter: Box::new(ProcessInstanceFilter::default()), ..Default::default() }, }) .await?; println!("{}", result.batch_operation_key); Ok(()) } ``` ### change_cluster_mode ```rust pub async fn change_cluster_mode(&self, params: ChangeClusterModeParams) -> Result ``` Change cluster mode (`PATCH /mode`). **Example** ```rust async fn change_cluster_mode() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .change_cluster_mode(ChangeClusterModeParams { mode: Mode::Recovering, dry_run: None, }) .await?; println!("{}", result.change_id); Ok(()) } ``` ### change_cluster_mode_as_cluster_admin ```rust pub async fn change_cluster_mode_as_cluster_admin(&self, params: ChangeClusterModeAsClusterAdminParams) -> Result ``` Change the cluster mode of one or every physical tenant (`PATCH /cluster/v2/mode`). **Example** ```rust async fn change_cluster_mode_as_cluster_admin() -> Result<(), Box> { let client = CamundaClient::from_env()?; // Omit `physical_tenant_id` to apply the change to every physical tenant. let result = client .change_cluster_mode_as_cluster_admin(ChangeClusterModeAsClusterAdminParams { mode: Mode::Recovering, physical_tenant_id: Some("default".to_string()), dry_run: Some(true), }) .await?; println!("{}", result.change_id); Ok(()) } ``` ### clock ```rust pub fn clock(&self) -> &Arc ``` The clock this client's cadence resolves through. ### complete_job ```rust pub async fn complete_job(&self, job_key: &str, request: Option) -> Result<()> ``` Complete a job, optionally with output variables. Job completion is a _drain_ operation and intentionally bypasses the backpressure gate so in-flight work can always be drained, even while new load is being shed. **Example** ```rust async fn complete_job(job_key: &str) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .complete_job( job_key, Some(JobCompletionRequest { variables: Some(Some( [("paymentStatus".to_string(), serde_json::json!("SETTLED"))] .into_iter() .collect(), )), ..Default::default() }), ) .await?; println!("Completed job {job_key}"); Ok(()) } ``` ### complete_user_task ```rust pub async fn complete_user_task(&self, params: CompleteUserTaskParams) -> Result<()> ``` Complete user task (`POST /user-tasks/{userTaskKey}/completion`). **Example** ```rust async fn complete_user_task(user_task_key: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .complete_user_task(CompleteUserTaskParams { user_task_key, user_task_completion_request: Some(UserTaskCompletionRequest::default()), }) .await?; println!("Complete user task: done"); Ok(()) } ``` ### config ```rust pub fn config(&self) -> &CamundaConfig ``` The resolved configuration. ### configuration ```rust pub async fn configuration(&self) -> Result ``` Build a generated-client `Configuration` with the base URL set and the current authentication applied (refreshing the OAuth token if needed). Use this to call generated operations that the facade does not yet wrap: ```rust use camunda_orchestration_sdk::client::apis::authentication_api; let client = CamundaClient::from_env()?; let cfg = client.configuration().await?; let me = authentication_api::get_authentication(&cfg).await?; ``` ### correlate_message ```rust pub async fn correlate_message(&self, request: models::MessageCorrelationRequest) -> Result ``` Correlate a message to a waiting process instance. The configured default tenant id is applied when unset. **Example** ```rust async fn correlate_message() -> Result<(), Box> { let client = CamundaClient::from_env()?; // Unlike `publish_message`, this blocks until the message is correlated and // returns the process instance it was correlated with. let correlated = client .correlate_message(MessageCorrelationRequest { name: "order-received".to_string(), correlation_key: Some("ORD-4711".to_string()), variables: Some( [("total".to_string(), serde_json::json!(99.5))] .into_iter() .collect(), ), ..Default::default() }) .await?; println!( "Correlated message {} with process instance {}", correlated.message_key, correlated.process_instance_key ); Ok(()) } ``` ### create_admin_user ```rust pub async fn create_admin_user(&self, params: CreateAdminUserParams) -> Result ``` Create admin user (`POST /setup/user`). **Example** ```rust async fn create_admin_user() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .create_admin_user(CreateAdminUserParams { user_request: UserRequest { password: "my-password".to_string(), username: Username::assume_exists("my-user"), ..Default::default() }, }) .await?; println!("{}", result.username); Ok(()) } ``` ### create_agent_instance ```rust pub async fn create_agent_instance(&self, params: CreateAgentInstanceParams) -> Result ``` Create agent instance (`POST /agent-instances`). **Example** ```rust async fn create_agent_instance() -> Result<(), Box> { let client = CamundaClient::from_env()?; // The batch must open with a CONFIGURATION item; it establishes the model, // provider and system prompt for the instance. let result = client .create_agent_instance(CreateAgentInstanceParams { agent_instance_creation_request: AgentInstanceCreationRequest { element_instance_key: Box::new(ElementInstanceKey::assume_exists( "my-element-instance", )), job_key: Box::new(JobKey::assume_exists("my-job")), job_lease_token: JobLeaseToken::assume_exists("my-job-lease"), history: vec![AgentInstanceHistoryItem { history_item_id: HistoryItemId::assume_exists("configuration-1"), loop_iteration: 0, role: AgentInstanceHistoryRoleEnum::Configuration, content: Vec::new(), produced_at: Default::default(), model: Some("my-model".to_string()), provider: Some("my-provider".to_string()), system_prompt: Some(Some(vec![AgentInstanceMessageContent::Text(Box::new( AgentInstanceTextContent { text: "my-system-prompt".to_string(), }, ))])), ..Default::default() }], }, }) .await?; println!("{result:#?}"); Ok(()) } ``` ### create_authorization ```rust pub async fn create_authorization(&self, params: CreateAuthorizationParams) -> Result ``` Create authorization (`POST /authorizations`). **Example** ```rust async fn create_authorization() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .create_authorization(CreateAuthorizationParams { authorization_request: AuthorizationRequest::AuthorizationIdBasedRequest(Box::new( AuthorizationIdBasedRequest { owner_id: "my-owner".to_string(), owner_type: OwnerTypeEnum::User, resource_id: "my-resource".to_string(), resource_type: ResourceTypeEnum::AuditLog, permission_types: vec![PermissionTypeEnum::Access], }, )), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### create_deployment ```rust pub async fn create_deployment(&self, params: CreateDeploymentParams) -> Result ``` Deploy resources (`POST /deployments`). **Example** ```rust async fn create_deployment() -> Result<(), Box> { let client = CamundaClient::from_env()?; // `deploy_resources` reads the files and builds the multipart request for you. // BPMN, DMN, and form resources can be deployed together in one atomic call. let deployment = client .deploy_resources( vec![ PathBuf::from("resources/order-process.bpmn"), PathBuf::from("resources/approval.dmn"), ], None, // tenant id; `None` uses the default tenant ) .await?; println!("Deployment {}", deployment.deployment_key); for item in deployment.deployments { if let Some(process) = item.process_definition { println!(" deployed process {}", process.process_definition_id); } } Ok(()) } ``` ### create_document ```rust pub async fn create_document(&self, params: CreateDocumentParams) -> Result ``` Upload document (`POST /documents`). **Example** ```rust async fn create_document() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .create_document(CreateDocumentParams { file: Default::default(), store_id: None, document_id: None, metadata: Some(DocumentMetadata::default()), }) .await?; println!("{}", result.document_id); Ok(()) } ``` ### create_document_link ```rust pub async fn create_document_link(&self, params: CreateDocumentLinkParams) -> Result ``` Create document link (`POST /documents/{documentId}/links`). **Example** ```rust async fn create_document_link(document_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .create_document_link(CreateDocumentLinkParams { document_id, store_id: None, content_hash: None, document_link_request: DocumentLinkRequest::default(), }) .await?; println!("{}", result.url); Ok(()) } ``` ### create_documents ```rust pub async fn create_documents(&self, params: CreateDocumentsParams) -> Result ``` Upload multiple documents (`POST /documents/batch`). **Example** ```rust async fn create_documents() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .create_documents(CreateDocumentsParams { files: vec![Default::default()], store_id: None, metadata_list: None, }) .await?; println!("{result:#?}"); Ok(()) } ``` ### create_element_instance_variables ```rust pub async fn create_element_instance_variables(&self, params: CreateElementInstanceVariablesParams) -> Result<()> ``` Update element instance variables (`PUT /element-instances/{elementInstanceKey}/variables`). **Example** ```rust async fn create_element_instance_variables( element_instance_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .create_element_instance_variables(CreateElementInstanceVariablesParams { element_instance_key, set_variable_request: SetVariableRequest { variables: [("key".to_string(), serde_json::json!({"key": "value"}))] .into_iter() .collect(), ..Default::default() }, }) .await?; println!("Update element instance variables: done"); Ok(()) } ``` ### create_global_cluster_variable ```rust pub async fn create_global_cluster_variable(&self, params: CreateGlobalClusterVariableParams) -> Result ``` Create a global-scoped cluster variable (`POST /cluster-variables/global`). **Example** ```rust async fn create_global_cluster_variable() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .create_global_cluster_variable(CreateGlobalClusterVariableParams { create_cluster_variable_request: CreateClusterVariableRequest { name: ClusterVariableName::assume_exists("my-variable"), value: Some(serde_json::json!({"key": "value"})), ..Default::default() }, }) .await?; println!("{}", result.name); Ok(()) } ``` ### create_global_task_listener ```rust pub async fn create_global_task_listener(&self, params: CreateGlobalTaskListenerParams) -> Result ``` Create global user task listener (`POST /global-task-listeners`). **Example** ```rust async fn create_global_task_listener() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .create_global_task_listener(CreateGlobalTaskListenerParams { create_global_task_listener_request: CreateGlobalTaskListenerRequest { id: GlobalListenerId::assume_exists("my-id"), ..Default::default() }, }) .await?; println!("{}", result.id); Ok(()) } ``` ### create_group ```rust pub async fn create_group(&self, params: CreateGroupParams) -> Result ``` Create group (`POST /groups`). **Example** ```rust async fn create_group() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .create_group(CreateGroupParams { group_create_request: Some(GroupCreateRequest { group_id: GroupId::assume_exists("my-group"), name: "my-variable".to_string(), ..Default::default() }), }) .await?; println!("{}", result.group_id); Ok(()) } ``` ### create_job_worker ```rust pub fn create_job_worker(&self, config: JobWorkerConfig) -> JobWorker ``` Create a `JobWorker` that continuously polls for and processes jobs of the configured type. Call `JobWorker::run` (or `JobWorker::start` / `JobWorker::spawn`) to begin. ### create_mapping_rule ```rust pub async fn create_mapping_rule(&self, params: CreateMappingRuleParams) -> Result ``` Create mapping rule (`POST /mapping-rules`). **Example** ```rust async fn create_mapping_rule() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .create_mapping_rule(CreateMappingRuleParams { mapping_rule_create_request: Some(MappingRuleCreateRequest { claim_name: "my-claim-name".to_string(), claim_value: "my-claim-value".to_string(), name: "my-variable".to_string(), mapping_rule_id: MappingRuleId::assume_exists("my-mapping-rule"), }), }) .await?; println!("{}", result.mapping_rule_id); Ok(()) } ``` ### create_process_instance ```rust pub async fn create_process_instance(&self, instruction: models::ProcessInstanceCreationInstruction) -> Result ``` Create (start) a process instance. The configured default tenant id is applied when the instruction does not already specify one. **Example** ```rust async fn create_process_instance() -> Result<(), Box> { let client = CamundaClient::from_env()?; // Start by BPMN process id. Use `ProcessInstanceCreationInstructionByKey` to // pin a specific deployed version instead. let instruction = ProcessInstanceCreationInstruction::ProcessInstanceCreationInstructionById( Box::new(ProcessInstanceCreationInstructionById { process_definition_id: ProcessDefinitionId::assume_exists("order-process"), variables: Some( [ ("orderId".to_string(), serde_json::json!("ORD-4711")), ("amount".to_string(), serde_json::json!(42)), ] .into_iter() .collect(), ), ..Default::default() }), ); let created = client.create_process_instance(instruction).await?; println!("Started process instance {}", created.process_instance_key); Ok(()) } ``` ### create_role ```rust pub async fn create_role(&self, params: CreateRoleParams) -> Result ``` Create role (`POST /roles`). **Example** ```rust async fn create_role() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .create_role(CreateRoleParams { role_create_request: Some(RoleCreateRequest { role_id: RoleId::assume_exists("my-role"), name: "my-variable".to_string(), ..Default::default() }), }) .await?; println!("{}", result.role_id); Ok(()) } ``` ### create_tenant ```rust pub async fn create_tenant(&self, params: CreateTenantParams) -> Result ``` Create tenant (`POST /tenants`). **Example** ```rust async fn create_tenant() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .create_tenant(CreateTenantParams { tenant_create_request: TenantCreateRequest { tenant_id: TenantId::assume_exists("my-tenant"), name: "my-variable".to_string(), ..Default::default() }, }) .await?; println!("{}", result.name); Ok(()) } ``` ### create_tenant_cluster_variable ```rust pub async fn create_tenant_cluster_variable(&self, params: CreateTenantClusterVariableParams) -> Result ``` Create a tenant-scoped cluster variable (`POST /cluster-variables/tenants/{tenantId}`). **Example** ```rust async fn create_tenant_cluster_variable( tenant_id: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .create_tenant_cluster_variable(CreateTenantClusterVariableParams { tenant_id, create_cluster_variable_request: CreateClusterVariableRequest { name: ClusterVariableName::assume_exists("my-variable"), value: Some(serde_json::json!({"key": "value"})), ..Default::default() }, }) .await?; println!("{}", result.name); Ok(()) } ``` ### create_user ```rust pub async fn create_user(&self, params: CreateUserParams) -> Result ``` Create user (`POST /users`). **Example** ```rust async fn create_user() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .create_user(CreateUserParams { user_request: UserRequest { password: "my-password".to_string(), username: Username::assume_exists("my-user"), ..Default::default() }, }) .await?; println!("{}", result.username); Ok(()) } ``` ### delete_authorization ```rust pub async fn delete_authorization(&self, params: DeleteAuthorizationParams) -> Result<()> ``` Delete authorization (`DELETE /authorizations/{authorizationKey}`). **Example** ```rust async fn delete_authorization(authorization_key: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .delete_authorization(DeleteAuthorizationParams { authorization_key }) .await?; println!("Delete authorization: done"); Ok(()) } ``` ### delete_decision_instance ```rust pub async fn delete_decision_instance(&self, params: DeleteDecisionInstanceParams) -> Result<()> ``` Delete decision instance (`POST /decision-instances/{decisionEvaluationKey}/deletion`). **Example** ```rust async fn delete_decision_instance( decision_evaluation_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .delete_decision_instance(DeleteDecisionInstanceParams { decision_evaluation_key, delete_decision_instance_request: Some(DeleteDecisionInstanceRequest::default()), }) .await?; println!("Delete decision instance: done"); Ok(()) } ``` ### delete_decision_instances_batch_operation ```rust pub async fn delete_decision_instances_batch_operation(&self, params: DeleteDecisionInstancesBatchOperationParams) -> Result ``` Delete decision instances (batch) (`POST /decision-instances/deletion`). **Example** ```rust async fn delete_decision_instances_batch_operation() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .delete_decision_instances_batch_operation(DeleteDecisionInstancesBatchOperationParams { decision_instance_deletion_batch_operation_request: DecisionInstanceDeletionBatchOperationRequest { filter: Box::new(DecisionInstanceFilter::default()), ..Default::default() }, }) .await?; println!("{}", result.batch_operation_key); Ok(()) } ``` ### delete_document ```rust pub async fn delete_document(&self, params: DeleteDocumentParams) -> Result<()> ``` Delete document (`DELETE /documents/{documentId}`). **Example** ```rust async fn delete_document(document_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .delete_document(DeleteDocumentParams { document_id, store_id: None, }) .await?; println!("Delete document: done"); Ok(()) } ``` ### delete_global_cluster_variable ```rust pub async fn delete_global_cluster_variable(&self, params: DeleteGlobalClusterVariableParams) -> Result<()> ``` Delete a global-scoped cluster variable (`DELETE /cluster-variables/global/{name}`). **Example** ```rust async fn delete_global_cluster_variable(name: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .delete_global_cluster_variable(DeleteGlobalClusterVariableParams { name }) .await?; println!("Delete a global-scoped cluster variable: done"); Ok(()) } ``` ### delete_global_task_listener ```rust pub async fn delete_global_task_listener(&self, params: DeleteGlobalTaskListenerParams) -> Result<()> ``` Delete global user task listener (`DELETE /global-task-listeners/{id}`). **Example** ```rust async fn delete_global_task_listener(id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .delete_global_task_listener(DeleteGlobalTaskListenerParams { id }) .await?; println!("Delete global user task listener: done"); Ok(()) } ``` ### delete_group ```rust pub async fn delete_group(&self, params: DeleteGroupParams) -> Result<()> ``` Delete group (`DELETE /groups/{groupId}`). **Example** ```rust async fn delete_group(group_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; client.delete_group(DeleteGroupParams { group_id }).await?; println!("Delete group: done"); Ok(()) } ``` ### delete_history_backup ```rust pub async fn delete_history_backup(&self, params: DeleteHistoryBackupParams) -> Result<()> ``` Delete history backup (`DELETE /backups/history/{backupId}`). **Example** ```rust async fn delete_history_backup() -> Result<(), Box> { let client = CamundaClient::from_env()?; client .delete_history_backup(DeleteHistoryBackupParams { backup_id: 1 }) .await?; println!("Delete history backup: done"); Ok(()) } ``` ### delete_history_backup_as_cluster_admin ```rust pub async fn delete_history_backup_as_cluster_admin(&self, params: DeleteHistoryBackupAsClusterAdminParams) -> Result<()> ``` Delete a history backup across physical tenants (`DELETE /cluster/v2/backups/history/{backupId}`). **Example** ```rust async fn delete_history_backup_as_cluster_admin() -> Result<(), Box> { let client = CamundaClient::from_env()?; client .delete_history_backup_as_cluster_admin(DeleteHistoryBackupAsClusterAdminParams { backup_id: 1, physical_tenant_id: None, }) .await?; println!("Delete history backup as cluster admin: done"); Ok(()) } ``` ### delete_mapping_rule ```rust pub async fn delete_mapping_rule(&self, params: DeleteMappingRuleParams) -> Result<()> ``` Delete a mapping rule (`DELETE /mapping-rules/{mappingRuleId}`). **Example** ```rust async fn delete_mapping_rule(mapping_rule_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .delete_mapping_rule(DeleteMappingRuleParams { mapping_rule_id }) .await?; println!("Delete a mapping rule: done"); Ok(()) } ``` ### delete_process_instance ```rust pub async fn delete_process_instance(&self, params: DeleteProcessInstanceParams) -> Result<()> ``` Delete process instance (`POST /process-instances/{processInstanceKey}/deletion`). **Example** ```rust async fn delete_process_instance( process_instance_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .delete_process_instance(DeleteProcessInstanceParams { process_instance_key, delete_process_instance_request: Some(DeleteProcessInstanceRequest::default()), }) .await?; println!("Delete process instance: done"); Ok(()) } ``` ### delete_process_instances_batch_operation ```rust pub async fn delete_process_instances_batch_operation(&self, params: DeleteProcessInstancesBatchOperationParams) -> Result ``` Delete process instances (batch) (`POST /process-instances/deletion`). **Example** ```rust async fn delete_process_instances_batch_operation() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .delete_process_instances_batch_operation(DeleteProcessInstancesBatchOperationParams { process_instance_deletion_batch_operation_request: ProcessInstanceDeletionBatchOperationRequest { filter: Box::new(ProcessInstanceFilter::default()), ..Default::default() }, }) .await?; println!("{}", result.batch_operation_key); Ok(()) } ``` ### delete_resource ```rust pub async fn delete_resource(&self, params: DeleteResourceParams) -> Result ``` Delete resource (`POST /resources/{resourceKey}/deletion`). **Example** ```rust async fn delete_resource(resource_key: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .delete_resource(DeleteResourceParams { resource_key, delete_resource_request: Some(DeleteResourceRequest::default()), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### delete_role ```rust pub async fn delete_role(&self, params: DeleteRoleParams) -> Result<()> ``` Delete role (`DELETE /roles/{roleId}`). **Example** ```rust async fn delete_role(role_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; client.delete_role(DeleteRoleParams { role_id }).await?; println!("Delete role: done"); Ok(()) } ``` ### delete_runtime_backup ```rust pub async fn delete_runtime_backup(&self, params: DeleteRuntimeBackupParams) -> Result<()> ``` Delete runtime backup (`DELETE /backups/runtime/{backupId}`). **Example** ```rust async fn delete_runtime_backup() -> Result<(), Box> { let client = CamundaClient::from_env()?; client .delete_runtime_backup(DeleteRuntimeBackupParams { backup_id: 1 }) .await?; println!("Delete runtime backup: done"); Ok(()) } ``` ### delete_runtime_backup_as_cluster_admin ```rust pub async fn delete_runtime_backup_as_cluster_admin(&self, params: DeleteRuntimeBackupAsClusterAdminParams) -> Result<()> ``` Delete a runtime backup across physical tenants (`DELETE /cluster/v2/backups/runtime/{backupId}`). **Example** ```rust async fn delete_runtime_backup_as_cluster_admin() -> Result<(), Box> { let client = CamundaClient::from_env()?; client .delete_runtime_backup_as_cluster_admin(DeleteRuntimeBackupAsClusterAdminParams { backup_id: 1, physical_tenant_id: None, }) .await?; println!("Delete runtime backup as cluster admin: done"); Ok(()) } ``` ### delete_runtime_backup_state ```rust pub async fn delete_runtime_backup_state(&self) -> Result<()> ``` Delete runtime backup state (`DELETE /backups/runtime/state`). **Example** ```rust async fn delete_runtime_backup_state() -> Result<(), Box> { let client = CamundaClient::from_env()?; client.delete_runtime_backup_state().await?; println!("Delete runtime backup state: done"); Ok(()) } ``` ### delete_runtime_backup_state_as_cluster_admin ```rust pub async fn delete_runtime_backup_state_as_cluster_admin(&self, params: DeleteRuntimeBackupStateAsClusterAdminParams) -> Result<()> ``` Delete runtime backup state across physical tenants (`DELETE /cluster/v2/backups/runtime/state`). **Example** ```rust async fn delete_runtime_backup_state_as_cluster_admin() -> Result<(), Box> { let client = CamundaClient::from_env()?; client .delete_runtime_backup_state_as_cluster_admin( DeleteRuntimeBackupStateAsClusterAdminParams { physical_tenant_id: None, }, ) .await?; println!("Delete runtime backup state as cluster admin: done"); Ok(()) } ``` ### delete_tenant ```rust pub async fn delete_tenant(&self, params: DeleteTenantParams) -> Result<()> ``` Delete tenant (`DELETE /tenants/{tenantId}`). **Example** ```rust async fn delete_tenant(tenant_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .delete_tenant(DeleteTenantParams { tenant_id }) .await?; println!("Delete tenant: done"); Ok(()) } ``` ### delete_tenant_cluster_variable ```rust pub async fn delete_tenant_cluster_variable(&self, params: DeleteTenantClusterVariableParams) -> Result<()> ``` Delete a tenant-scoped cluster variable (`DELETE /cluster-variables/tenants/{tenantId}/{name}`). **Example** ```rust async fn delete_tenant_cluster_variable( tenant_id: String, name: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .delete_tenant_cluster_variable(DeleteTenantClusterVariableParams { tenant_id, name }) .await?; println!("Delete a tenant-scoped cluster variable: done"); Ok(()) } ``` ### delete_user ```rust pub async fn delete_user(&self, params: DeleteUserParams) -> Result<()> ``` Delete user (`DELETE /users/{username}`). **Example** ```rust async fn delete_user(username: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; client.delete_user(DeleteUserParams { username }).await?; println!("Delete user: done"); Ok(()) } ``` ### deploy_resources ```rust pub async fn deploy_resources(&self, resources: Vec, tenant_id: Option) -> Result ``` Deploy one or more resources (BPMN, DMN, forms) from local file paths. The configured default tenant id is applied when `tenant_id` is `None`. ### evaluate_conditionals ```rust pub async fn evaluate_conditionals(&self, params: EvaluateConditionalsParams) -> Result ``` Evaluate root level conditional start events (`POST /conditionals/evaluation`). **Example** ```rust async fn evaluate_conditionals() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .evaluate_conditionals(EvaluateConditionalsParams { conditional_evaluation_instruction: ConditionalEvaluationInstruction { variables: [("key".to_string(), serde_json::json!({"key": "value"}))] .into_iter() .collect(), ..Default::default() }, }) .await?; println!("{}", result.tenant_id); Ok(()) } ``` ### evaluate_decision ```rust pub async fn evaluate_decision(&self, instruction: models::DecisionEvaluationInstruction) -> Result ``` Evaluate a decision (DMN). The configured default tenant id is applied when unset. **Example** ```rust async fn evaluate_decision() -> Result<(), Box> { let client = CamundaClient::from_env()?; let instruction = DecisionEvaluationInstruction::DecisionEvaluationById(Box::new(DecisionEvaluationById { decision_definition_id: DecisionDefinitionId::assume_exists("approval-decision"), variables: Some( [("amount".to_string(), serde_json::json!(500))] .into_iter() .collect(), ), ..Default::default() })); let evaluated = client.evaluate_decision(instruction).await?; println!("Decision output: {}", evaluated.output); Ok(()) } ``` ### evaluate_expression ```rust pub async fn evaluate_expression(&self, params: EvaluateExpressionParams) -> Result ``` Evaluate an expression (`POST /expression/evaluation`). **Example** ```rust async fn evaluate_expression() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .evaluate_expression(EvaluateExpressionParams { expression_evaluation_request: ExpressionEvaluationRequest { expression: "my-expression".to_string(), ..Default::default() }, }) .await?; println!("{}", result.expression); Ok(()) } ``` ### eventual ```rust pub async fn eventual(&self, options: ConsistencyOptions, op: F) -> Result ``` Poll a read operation until it returns `Ok`, retrying `404 Not Found` (the typical symptom of a not-yet-replicated read) until the eventual-consistency window elapses. The default timeout is `CAMUNDA_SDK_EVENTUAL_POLL_DEFAULT_MS`; override per-call with `ConsistencyOptions`. ### eventual_until ```rust pub async fn eventual_until(&self, options: ConsistencyOptions, op: F, predicate: P) -> Result ``` Poll a read operation until `predicate` is satisfied by its result, retrying `404` and ignoring consistent-but-not-yet-matching reads, until the eventual-consistency window elapses. ### fail_job ```rust pub async fn fail_job(&self, job_key: &str, request: Option) -> Result<()> ``` Fail a job, decrementing retries. **Example** ```rust async fn fail_job(job_key: &str) -> Result<(), Box> { let client = CamundaClient::from_env()?; // Setting `retries` to 0 raises an incident instead of retrying. client .fail_job( job_key, Some(JobFailRequest { retries: Some(2), error_message: Some("payment gateway timed out".to_string()), retry_back_off: Some(10_000), ..Default::default() }), ) .await?; println!("Failed job {job_key}"); Ok(()) } ``` ### from_env ```rust pub fn from_env() -> Result ``` Construct a client from environment variables only. ### get_agent_definition ```rust pub async fn get_agent_definition(&self, params: GetAgentDefinitionParams) -> Result ``` Get agent definition (`GET /agent-definitions/{agentDefinitionKey}`). **Example** ```rust async fn get_agent_definition() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_agent_definition(GetAgentDefinitionParams { agent_definition_key: "2251799813691958".to_string(), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_agent_instance ```rust pub async fn get_agent_instance(&self, params: GetAgentInstanceParams) -> Result ``` Get agent instance (`GET /agent-instances/{agentInstanceKey}`). **Example** ```rust async fn get_agent_instance(agent_instance_key: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_agent_instance(GetAgentInstanceParams { agent_instance_key }) .await?; println!("{}", result.element_id); Ok(()) } ``` ### get_audit_log ```rust pub async fn get_audit_log(&self, params: GetAuditLogParams) -> Result ``` Get audit log (`GET /audit-logs/{auditLogKey}`). **Example** ```rust async fn get_audit_log(audit_log_key: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_audit_log(GetAuditLogParams { audit_log_key }) .await?; println!("{}", result.entity_key); Ok(()) } ``` ### get_authentication ```rust pub async fn get_authentication(&self) -> Result ``` Get current user (`GET /authentication/me`). **Example** ```rust async fn get_authentication() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client.get_authentication().await?; println!("{}", result.username); Ok(()) } ``` ### get_authorization ```rust pub async fn get_authorization(&self, params: GetAuthorizationParams) -> Result ``` Get authorization (`GET /authorizations/{authorizationKey}`). **Example** ```rust async fn get_authorization(authorization_key: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_authorization(GetAuthorizationParams { authorization_key }) .await?; println!("{}", result.owner_id); Ok(()) } ``` ### get_batch_operation ```rust pub async fn get_batch_operation(&self, params: GetBatchOperationParams) -> Result ``` Get batch operation (`GET /batch-operations/{batchOperationKey}`). **Example** ```rust async fn get_batch_operation( batch_operation_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_batch_operation(GetBatchOperationParams { batch_operation_key, }) .await?; println!("{}", result.batch_operation_key); Ok(()) } ``` ### get_cluster_exporting_status ```rust pub async fn get_cluster_exporting_status(&self) -> Result ``` Get exporting status of the whole cluster (`GET /cluster/v2/exporting`). **Example** ```rust async fn get_cluster_exporting_status() -> Result<(), Box> { let client = CamundaClient::from_env()?; let status = client.get_cluster_exporting_status().await?; println!("{status:#?}"); Ok(()) } ``` ### get_cluster_rebalance ```rust pub async fn get_cluster_rebalance(&self) -> Result ``` Report the cluster's current leadership balance (`GET /cluster/v2/rebalance`). **Example** ```rust async fn get_cluster_rebalance() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client.get_cluster_rebalance().await?; println!("{result:#?}"); Ok(()) } ``` ### get_cluster_status ```rust pub async fn get_cluster_status(&self) -> Result ``` Get the status of the whole cluster (`GET /cluster/v2/status`). **Example** ```rust async fn get_cluster_status() -> Result<(), Box> { let client = CamundaClient::from_env()?; let status = client.get_cluster_status().await?; println!("{status:#?}"); Ok(()) } ``` ### get_cluster_topology ```rust pub async fn get_cluster_topology(&self) -> Result ``` Get the topology of the whole cluster (`GET /cluster/v2/topology`). **Example** ```rust async fn get_cluster_topology() -> Result<(), Box> { let client = CamundaClient::from_env()?; // Unlike `topology`, this reports the brokers of every physical tenant. let topology = client.get_cluster_topology().await?; println!("Cluster id: {:?}", topology.cluster_id); println!("Cluster size: {}", topology.cluster_size); for tenant in topology.physical_tenants { println!("{tenant:#?}"); } Ok(()) } ``` ### get_cluster_upgrade_status ```rust pub async fn get_cluster_upgrade_status(&self) -> Result ``` Get the upgrade-readiness status of the whole cluster (`GET /cluster/v2/status/upgrade`). **Example** ```rust async fn get_cluster_upgrade_status() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client.get_cluster_upgrade_status().await?; println!("Upgrade-readiness status: {:?}", result.status); Ok(()) } ``` ### get_decision_definition ```rust pub async fn get_decision_definition(&self, params: GetDecisionDefinitionParams) -> Result ``` Get decision definition (`GET /decision-definitions/{decisionDefinitionKey}`). **Example** ```rust async fn get_decision_definition( decision_definition_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_decision_definition(GetDecisionDefinitionParams { decision_definition_key, }) .await?; println!("{}", result.decision_definition_id); Ok(()) } ``` ### get_decision_definition_xml ```rust pub async fn get_decision_definition_xml(&self, params: GetDecisionDefinitionXmlParams) -> Result ``` Get decision definition XML (`GET /decision-definitions/{decisionDefinitionKey}/xml`). **Example** ```rust async fn get_decision_definition_xml( decision_definition_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let xml = client .get_decision_definition_xml(GetDecisionDefinitionXmlParams { decision_definition_key, }) .await?; println!("{xml}"); Ok(()) } ``` ### get_decision_instance ```rust pub async fn get_decision_instance(&self, params: GetDecisionInstanceParams) -> Result ``` Get decision instance (`GET /decision-instances/{decisionEvaluationInstanceKey}`). **Example** ```rust async fn get_decision_instance( decision_evaluation_instance_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_decision_instance(GetDecisionInstanceParams { decision_evaluation_instance_key, }) .await?; println!("{}", result.decision_evaluation_instance_key); Ok(()) } ``` ### get_decision_requirements ```rust pub async fn get_decision_requirements(&self, params: GetDecisionRequirementsParams) -> Result ``` Get decision requirements (`GET /decision-requirements/{decisionRequirementsKey}`). **Example** ```rust async fn get_decision_requirements( decision_requirements_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_decision_requirements(GetDecisionRequirementsParams { decision_requirements_key, }) .await?; println!("{}", result.decision_requirements_id); Ok(()) } ``` ### get_decision_requirements_xml ```rust pub async fn get_decision_requirements_xml(&self, params: GetDecisionRequirementsXmlParams) -> Result ``` Get decision requirements XML (`GET /decision-requirements/{decisionRequirementsKey}/xml`). **Example** ```rust async fn get_decision_requirements_xml( decision_requirements_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let xml = client .get_decision_requirements_xml(GetDecisionRequirementsXmlParams { decision_requirements_key, }) .await?; println!("{xml}"); Ok(()) } ``` ### get_document ```rust pub async fn get_document(&self, params: GetDocumentParams) -> Result ``` Download document (`GET /documents/{documentId}`). **Example** ```rust async fn get_document(document_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_document(GetDocumentParams { document_id, store_id: None, content_hash: None, }) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_element_instance ```rust pub async fn get_element_instance(&self, params: GetElementInstanceParams) -> Result ``` Get element instance (`GET /element-instances/{elementInstanceKey}`). **Example** ```rust async fn get_element_instance( element_instance_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_element_instance(GetElementInstanceParams { element_instance_key, }) .await?; println!("{}", result.element_id); Ok(()) } ``` ### get_exporting_status ```rust pub async fn get_exporting_status(&self) -> Result ``` Get exporting status (`GET /exporting`). **Example** ```rust async fn get_exporting_status() -> Result<(), Box> { let client = CamundaClient::from_env()?; let status = client.get_exporting_status().await?; println!("{status:#?}"); Ok(()) } ``` ### get_form_by_key ```rust pub async fn get_form_by_key(&self, params: GetFormByKeyParams) -> Result ``` Get form by key (`GET /forms/{formKey}`). **Example** ```rust async fn get_form_by_key(form_key: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_form_by_key(GetFormByKeyParams { form_key }) .await?; println!("{}", result.form_id); Ok(()) } ``` ### get_global_cluster_variable ```rust pub async fn get_global_cluster_variable(&self, params: GetGlobalClusterVariableParams) -> Result ``` Get a global-scoped cluster variable (`GET /cluster-variables/global/{name}`). **Example** ```rust async fn get_global_cluster_variable(name: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_global_cluster_variable(GetGlobalClusterVariableParams { name }) .await?; println!("{}", result.name); Ok(()) } ``` ### get_global_job_statistics ```rust pub async fn get_global_job_statistics(&self, params: GetGlobalJobStatisticsParams) -> Result ``` Global job statistics (`GET /jobs/statistics/global`). **Example** ```rust async fn get_global_job_statistics() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_global_job_statistics(GetGlobalJobStatisticsParams { from: Default::default(), to: Default::default(), job_type: None, }) .await?; println!("{}", result.is_incomplete); Ok(()) } ``` ### get_global_task_listener ```rust pub async fn get_global_task_listener(&self, params: GetGlobalTaskListenerParams) -> Result ``` Get global user task listener (`GET /global-task-listeners/{id}`). **Example** ```rust async fn get_global_task_listener(id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_global_task_listener(GetGlobalTaskListenerParams { id }) .await?; println!("{}", result.id); Ok(()) } ``` ### get_group ```rust pub async fn get_group(&self, params: GetGroupParams) -> Result ``` Get group (`GET /groups/{groupId}`). **Example** ```rust async fn get_group(group_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client.get_group(GetGroupParams { group_id }).await?; println!("{}", result.group_id); Ok(()) } ``` ### get_history_backup ```rust pub async fn get_history_backup(&self, params: GetHistoryBackupParams) -> Result ``` Get history backup (`GET /backups/history/{backupId}`). **Example** ```rust async fn get_history_backup() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_history_backup(GetHistoryBackupParams { backup_id: 1 }) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_history_backup_as_cluster_admin ```rust pub async fn get_history_backup_as_cluster_admin(&self, params: GetHistoryBackupAsClusterAdminParams) -> Result ``` Get a history backup across physical tenants (`GET /cluster/v2/backups/history/{backupId}`). **Example** ```rust async fn get_history_backup_as_cluster_admin() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_history_backup_as_cluster_admin(GetHistoryBackupAsClusterAdminParams { backup_id: 1, physical_tenant_id: None, }) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_incident ```rust pub async fn get_incident(&self, params: GetIncidentParams) -> Result ``` Get incident (`GET /incidents/{incidentKey}`). **Example** ```rust async fn get_incident(incident_key: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_incident(GetIncidentParams { incident_key }) .await?; println!("{}", result.element_id); Ok(()) } ``` ### get_job_error_statistics ```rust pub async fn get_job_error_statistics(&self, params: GetJobErrorStatisticsParams) -> Result ``` Get error metrics for a job type (`POST /jobs/statistics/errors`). **Example** ```rust async fn get_job_error_statistics() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_job_error_statistics(GetJobErrorStatisticsParams { job_error_statistics_query: JobErrorStatisticsQuery { filter: Box::new(JobErrorStatisticsFilter { from: Default::default(), to: Default::default(), job_type: "payment-service".to_string(), ..Default::default() }), ..Default::default() }, }) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_job_time_series_statistics ```rust pub async fn get_job_time_series_statistics(&self, params: GetJobTimeSeriesStatisticsParams) -> Result ``` Get time-series metrics for a job type (`POST /jobs/statistics/time-series`). **Example** ```rust async fn get_job_time_series_statistics() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_job_time_series_statistics(GetJobTimeSeriesStatisticsParams { job_time_series_statistics_query: JobTimeSeriesStatisticsQuery { filter: Box::new(JobTimeSeriesStatisticsFilter { from: Default::default(), to: Default::default(), job_type: "payment-service".to_string(), ..Default::default() }), ..Default::default() }, }) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_job_type_statistics ```rust pub async fn get_job_type_statistics(&self, params: GetJobTypeStatisticsParams) -> Result ``` Get job statistics by type (`POST /jobs/statistics/by-types`). **Example** ```rust async fn get_job_type_statistics() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_job_type_statistics(GetJobTypeStatisticsParams { job_type_statistics_query: JobTypeStatisticsQuery::default(), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_job_worker_statistics ```rust pub async fn get_job_worker_statistics(&self, params: GetJobWorkerStatisticsParams) -> Result ``` Get job statistics by worker (`POST /jobs/statistics/by-workers`). **Example** ```rust async fn get_job_worker_statistics() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_job_worker_statistics(GetJobWorkerStatisticsParams { job_worker_statistics_query: JobWorkerStatisticsQuery { filter: Box::new(JobWorkerStatisticsFilter { from: Default::default(), to: Default::default(), job_type: "payment-service".to_string(), }), ..Default::default() }, }) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_license ```rust pub async fn get_license(&self) -> Result ``` Get license status (`GET /license`). **Example** ```rust async fn get_license() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client.get_license().await?; println!("{}", result.license_type); Ok(()) } ``` ### get_mapping_rule ```rust pub async fn get_mapping_rule(&self, params: GetMappingRuleParams) -> Result ``` Get a mapping rule (`GET /mapping-rules/{mappingRuleId}`). **Example** ```rust async fn get_mapping_rule(mapping_rule_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_mapping_rule(GetMappingRuleParams { mapping_rule_id }) .await?; println!("{}", result.mapping_rule_id); Ok(()) } ``` ### get_process_definition ```rust pub async fn get_process_definition(&self, params: GetProcessDefinitionParams) -> Result ``` Get process definition (`GET /process-definitions/{processDefinitionKey}`). **Example** ```rust async fn get_process_definition( process_definition_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_process_definition(GetProcessDefinitionParams { process_definition_key, }) .await?; println!("{}", result.process_definition_id); Ok(()) } ``` ### get_process_definition_instance_statistics ```rust pub async fn get_process_definition_instance_statistics(&self, params: GetProcessDefinitionInstanceStatisticsParams) -> Result ``` Get process instance statistics (`POST /process-definitions/statistics/process-instances`). **Example** ```rust async fn get_process_definition_instance_statistics() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_process_definition_instance_statistics(GetProcessDefinitionInstanceStatisticsParams { process_definition_instance_statistics_query: Some( ProcessDefinitionInstanceStatisticsQuery::default(), ), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_process_definition_instance_version_statistics ```rust pub async fn get_process_definition_instance_version_statistics(&self, params: GetProcessDefinitionInstanceVersionStatisticsParams) -> Result ``` Get process instance statistics by version (`POST /process-definitions/statistics/process-instances-by-version`). **Example** ```rust async fn get_process_definition_instance_version_statistics( ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_process_definition_instance_version_statistics( GetProcessDefinitionInstanceVersionStatisticsParams { process_definition_instance_version_statistics_query: ProcessDefinitionInstanceVersionStatisticsQuery { filter: Box::new(ProcessDefinitionInstanceVersionStatisticsFilter { process_definition_id: ProcessDefinitionId::assume_exists("my-process"), ..Default::default() }), ..Default::default() }, }, ) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_process_definition_message_subscription_statistics ```rust pub async fn get_process_definition_message_subscription_statistics(&self, params: GetProcessDefinitionMessageSubscriptionStatisticsParams) -> Result ``` Get message subscription statistics (`POST /process-definitions/statistics/message-subscriptions`). **Example** ```rust async fn get_process_definition_message_subscription_statistics( ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_process_definition_message_subscription_statistics( GetProcessDefinitionMessageSubscriptionStatisticsParams { process_definition_message_subscription_statistics_query: Some( ProcessDefinitionMessageSubscriptionStatisticsQuery::default(), ), }, ) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_process_definition_statistics ```rust pub async fn get_process_definition_statistics(&self, params: GetProcessDefinitionStatisticsParams) -> Result ``` Get process definition statistics (`POST /process-definitions/{processDefinitionKey}/statistics/element-instances`). **Example** ```rust async fn get_process_definition_statistics( process_definition_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_process_definition_statistics(GetProcessDefinitionStatisticsParams { process_definition_key, process_definition_element_statistics_query: Some( ProcessDefinitionElementStatisticsQuery::default(), ), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_process_definition_xml ```rust pub async fn get_process_definition_xml(&self, params: GetProcessDefinitionXmlParams) -> Result ``` Get process definition XML (`GET /process-definitions/{processDefinitionKey}/xml`). **Example** ```rust async fn get_process_definition_xml( process_definition_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let xml = client .get_process_definition_xml(GetProcessDefinitionXmlParams { process_definition_key, }) .await?; println!("{xml}"); Ok(()) } ``` ### get_process_instance ```rust pub async fn get_process_instance(&self, process_instance_key: &str) -> Result ``` Fetch a process instance by key (a read; not subject to backpressure). Returns a `404` `CamundaError::Api` if not yet visible — compose with `CamundaClient::eventual` to wait through replication lag. **Example** ```rust async fn get_process_instance( process_instance_key: &str, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let instance = client.get_process_instance(process_instance_key).await?; println!("Process: {}", instance.process_definition_id); println!("State: {:?}", instance.state); println!("Incident: {}", instance.has_incident); Ok(()) } ``` ### get_process_instance_call_hierarchy ```rust pub async fn get_process_instance_call_hierarchy(&self, params: GetProcessInstanceCallHierarchyParams) -> Result> ``` Get call hierarchy (`GET /process-instances/{processInstanceKey}/call-hierarchy`). **Example** ```rust async fn get_process_instance_call_hierarchy( process_instance_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_process_instance_call_hierarchy(GetProcessInstanceCallHierarchyParams { process_instance_key, }) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_process_instance_sequence_flows ```rust pub async fn get_process_instance_sequence_flows(&self, params: GetProcessInstanceSequenceFlowsParams) -> Result ``` Get sequence flows (`GET /process-instances/{processInstanceKey}/sequence-flows`). **Example** ```rust async fn get_process_instance_sequence_flows( process_instance_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_process_instance_sequence_flows(GetProcessInstanceSequenceFlowsParams { process_instance_key, }) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_process_instance_statistics ```rust pub async fn get_process_instance_statistics(&self, params: GetProcessInstanceStatisticsParams) -> Result ``` Get element instance statistics (`GET /process-instances/{processInstanceKey}/statistics/element-instances`). **Example** ```rust async fn get_process_instance_statistics( process_instance_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_process_instance_statistics(GetProcessInstanceStatisticsParams { process_instance_key, }) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_process_instance_statistics_by_definition ```rust pub async fn get_process_instance_statistics_by_definition(&self, params: GetProcessInstanceStatisticsByDefinitionParams) -> Result ``` Get process instance statistics by definition (`POST /incidents/statistics/process-instances-by-definition`). **Example** ```rust async fn get_process_instance_statistics_by_definition() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_process_instance_statistics_by_definition( GetProcessInstanceStatisticsByDefinitionParams { incident_process_instance_statistics_by_definition_query: IncidentProcessInstanceStatisticsByDefinitionQuery { filter: Box::new(IncidentProcessInstanceStatisticsByDefinitionFilter { error_hash_code: 1, }), ..Default::default() }, }, ) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_process_instance_statistics_by_error ```rust pub async fn get_process_instance_statistics_by_error(&self, params: GetProcessInstanceStatisticsByErrorParams) -> Result ``` Get process instance statistics by error (`POST /incidents/statistics/process-instances-by-error`). **Example** ```rust async fn get_process_instance_statistics_by_error() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_process_instance_statistics_by_error(GetProcessInstanceStatisticsByErrorParams { incident_process_instance_statistics_by_error_query: Some( IncidentProcessInstanceStatisticsByErrorQuery::default(), ), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_process_instance_wait_state_statistics ```rust pub async fn get_process_instance_wait_state_statistics(&self, params: GetProcessInstanceWaitStateStatisticsParams) -> Result ``` Get wait state statistics (`GET /process-instances/{processInstanceKey}/statistics/wait-states`). **Example** ```rust async fn get_process_instance_wait_state_statistics( process_instance_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_process_instance_wait_state_statistics(GetProcessInstanceWaitStateStatisticsParams { process_instance_key, }) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_resource ```rust pub async fn get_resource(&self, params: GetResourceParams) -> Result ``` Get resource (`GET /resources/{resourceKey}`). **Example** ```rust async fn get_resource(resource_key: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_resource(GetResourceParams { resource_key }) .await?; println!("{}", result.resource_id); Ok(()) } ``` ### get_resource_content ```rust pub async fn get_resource_content(&self, params: GetResourceContentParams) -> Result> ``` Get RPA resource content (deprecated) (`GET /resources/{resourceKey}/content`). **Example** ```rust async fn get_resource_content(resource_key: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_resource_content(GetResourceContentParams { resource_key }) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_resource_content_binary ```rust pub async fn get_resource_content_binary(&self, params: GetResourceContentBinaryParams) -> Result ``` Get resource content as binary (`GET /resources/{resourceKey}/content/binary`). **Example** ```rust async fn get_resource_content_binary( resource_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_resource_content_binary(GetResourceContentBinaryParams { resource_key }) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_restore_status ```rust pub async fn get_restore_status(&self) -> Result ``` Get the status of the restore that is currently in progress (`GET /restore`). **Example** ```rust async fn get_restore_status() -> Result<(), Box> { let client = CamundaClient::from_env()?; let status = client.get_restore_status().await?; println!("{status:#?}"); Ok(()) } ``` ### get_role ```rust pub async fn get_role(&self, params: GetRoleParams) -> Result ``` Get role (`GET /roles/{roleId}`). **Example** ```rust async fn get_role(role_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client.get_role(GetRoleParams { role_id }).await?; println!("{}", result.role_id); Ok(()) } ``` ### get_runtime_backup ```rust pub async fn get_runtime_backup(&self, params: GetRuntimeBackupParams) -> Result ``` Get runtime backup (`GET /backups/runtime/{backupId}`). **Example** ```rust async fn get_runtime_backup() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_runtime_backup(GetRuntimeBackupParams { backup_id: 1 }) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_runtime_backup_as_cluster_admin ```rust pub async fn get_runtime_backup_as_cluster_admin(&self, params: GetRuntimeBackupAsClusterAdminParams) -> Result ``` Get a runtime backup across physical tenants (`GET /cluster/v2/backups/runtime/{backupId}`). **Example** ```rust async fn get_runtime_backup_as_cluster_admin() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_runtime_backup_as_cluster_admin(GetRuntimeBackupAsClusterAdminParams { backup_id: 1, physical_tenant_id: None, }) .await?; println!("{result:#?}"); Ok(()) } ``` ### get_runtime_backup_state ```rust pub async fn get_runtime_backup_state(&self) -> Result ``` Get runtime backup state (`GET /backups/runtime/state`). **Example** ```rust async fn get_runtime_backup_state() -> Result<(), Box> { let client = CamundaClient::from_env()?; let state = client.get_runtime_backup_state().await?; println!("{state:#?}"); Ok(()) } ``` ### get_runtime_backup_state_as_cluster_admin ```rust pub async fn get_runtime_backup_state_as_cluster_admin(&self, params: GetRuntimeBackupStateAsClusterAdminParams) -> Result ``` Get runtime backup state across physical tenants (`GET /cluster/v2/backups/runtime/state`). **Example** ```rust async fn get_runtime_backup_state_as_cluster_admin() -> Result<(), Box> { let client = CamundaClient::from_env()?; let state = client .get_runtime_backup_state_as_cluster_admin(GetRuntimeBackupStateAsClusterAdminParams { physical_tenant_id: None, }) .await?; println!("{state:#?}"); Ok(()) } ``` ### get_start_process_form ```rust pub async fn get_start_process_form(&self, params: GetStartProcessFormParams) -> Result ``` Get process start form (`GET /process-definitions/{processDefinitionKey}/form`). **Example** ```rust async fn get_start_process_form( process_definition_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_start_process_form(GetStartProcessFormParams { process_definition_key, }) .await?; println!("{}", result.form_id); Ok(()) } ``` ### get_status ```rust pub async fn get_status(&self) -> Result<()> ``` Get physical tenant status (`GET /status`). **Example** ```rust async fn get_status() -> Result<(), Box> { let client = CamundaClient::from_env()?; client.get_status().await?; println!("Get cluster status: done"); Ok(()) } ``` ### get_system_configuration ```rust pub async fn get_system_configuration(&self) -> Result ``` System configuration (alpha) (`GET /system/configuration`). **Example** ```rust async fn get_system_configuration() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client.get_system_configuration().await?; println!("{result:#?}"); Ok(()) } ``` ### get_tenant ```rust pub async fn get_tenant(&self, params: GetTenantParams) -> Result ``` Get tenant (`GET /tenants/{tenantId}`). **Example** ```rust async fn get_tenant(tenant_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client.get_tenant(GetTenantParams { tenant_id }).await?; println!("{}", result.name); Ok(()) } ``` ### get_tenant_cluster_variable ```rust pub async fn get_tenant_cluster_variable(&self, params: GetTenantClusterVariableParams) -> Result ``` Get a tenant-scoped cluster variable (`GET /cluster-variables/tenants/{tenantId}/{name}`). **Example** ```rust async fn get_tenant_cluster_variable( tenant_id: String, name: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_tenant_cluster_variable(GetTenantClusterVariableParams { tenant_id, name }) .await?; println!("{}", result.name); Ok(()) } ``` ### get_usage_metrics ```rust pub async fn get_usage_metrics(&self, params: GetUsageMetricsParams) -> Result ``` Get usage metrics (`GET /system/usage-metrics`). **Example** ```rust async fn get_usage_metrics() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_usage_metrics(GetUsageMetricsParams { start_time: Default::default(), end_time: Default::default(), tenant_id: None, with_tenants: None, }) .await?; println!("{}", result.active_tenants); Ok(()) } ``` ### get_user ```rust pub async fn get_user(&self, params: GetUserParams) -> Result ``` Get user (`GET /users/{username}`). **Example** ```rust async fn get_user(username: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client.get_user(GetUserParams { username }).await?; println!("{}", result.username); Ok(()) } ``` ### get_user_task ```rust pub async fn get_user_task(&self, params: GetUserTaskParams) -> Result ``` Get user task (`GET /user-tasks/{userTaskKey}`). **Example** ```rust async fn get_user_task(user_task_key: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_user_task(GetUserTaskParams { user_task_key }) .await?; println!("{}", result.element_id); Ok(()) } ``` ### get_user_task_form ```rust pub async fn get_user_task_form(&self, params: GetUserTaskFormParams) -> Result ``` Get user task form (`GET /user-tasks/{userTaskKey}/form`). **Example** ```rust async fn get_user_task_form(user_task_key: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_user_task_form(GetUserTaskFormParams { user_task_key }) .await?; println!("{}", result.form_id); Ok(()) } ``` ### get_variable ```rust pub async fn get_variable(&self, params: GetVariableParams) -> Result ``` Get variable (`GET /variables/{variableKey}`). **Example** ```rust async fn get_variable(variable_key: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .get_variable(GetVariableParams { variable_key }) .await?; println!("{}", result.name); Ok(()) } ``` ### init_logging ```rust pub fn init_logging(&self) -> bool ``` Install a formatting `tracing` subscriber filtered to the configured `CAMUNDA_SDK_LOG_LEVEL`. No-op if a global subscriber is already set or logging is off. Returns `true` if this call installed the subscriber. ### list_history_backups ```rust pub async fn list_history_backups(&self, params: ListHistoryBackupsParams) -> Result> ``` List history backups (`GET /backups/history`). **Example** ```rust async fn list_history_backups() -> Result<(), Box> { let client = CamundaClient::from_env()?; // `prefix` must end in a single '*'. Setting `verbose` to false makes the // query cheaper, at the cost of snapshot-level detail. let backups = client .list_history_backups(ListHistoryBackupsParams { prefix: None, verbose: None, }) .await?; for backup in backups { println!("{backup:#?}"); } Ok(()) } ``` ### list_history_backups_as_cluster_admin ```rust pub async fn list_history_backups_as_cluster_admin(&self, params: ListHistoryBackupsAsClusterAdminParams) -> Result> ``` List history backups across physical tenants (`GET /cluster/v2/backups/history`). **Example** ```rust async fn list_history_backups_as_cluster_admin() -> Result<(), Box> { let client = CamundaClient::from_env()?; // `prefix` must end in a single '*'. Setting `verbose` to false makes the // query cheaper, at the cost of snapshot-level detail. let backups = client .list_history_backups_as_cluster_admin(ListHistoryBackupsAsClusterAdminParams { physical_tenant_id: None, prefix: None, verbose: None, }) .await?; for backup in backups { println!("{backup:#?}"); } Ok(()) } ``` ### list_runtime_backups ```rust pub async fn list_runtime_backups(&self, params: ListRuntimeBackupsParams) -> Result> ``` List runtime backups (`GET /backups/runtime`). **Example** ```rust async fn list_runtime_backups() -> Result<(), Box> { let client = CamundaClient::from_env()?; let backups = client .list_runtime_backups(ListRuntimeBackupsParams { prefix: None }) .await?; for backup in backups { println!("{backup:#?}"); } Ok(()) } ``` ### list_runtime_backups_as_cluster_admin ```rust pub async fn list_runtime_backups_as_cluster_admin(&self, params: ListRuntimeBackupsAsClusterAdminParams) -> Result> ``` List runtime backups across physical tenants (`GET /cluster/v2/backups/runtime`). **Example** ```rust async fn list_runtime_backups_as_cluster_admin() -> Result<(), Box> { let client = CamundaClient::from_env()?; let backups = client .list_runtime_backups_as_cluster_admin(ListRuntimeBackupsAsClusterAdminParams { physical_tenant_id: None, prefix: None, }) .await?; for backup in backups { println!("{backup:#?}"); } Ok(()) } ``` ### list_secrets ```rust pub async fn list_secrets(&self, params: ListSecretsParams) -> Result ``` List secrets (`POST /secrets/list`). **Example** ```rust async fn list_secrets() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .list_secrets(ListSecretsParams { body: None }) .await?; println!("{result:#?}"); Ok(()) } ``` ### migrate_process_instance ```rust pub async fn migrate_process_instance(&self, params: MigrateProcessInstanceParams) -> Result<()> ``` Migrate process instance (`POST /process-instances/{processInstanceKey}/migration`). **Example** ```rust async fn migrate_process_instance( process_instance_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .migrate_process_instance(MigrateProcessInstanceParams { process_instance_key, process_instance_migration_instruction: ProcessInstanceMigrationInstruction { target_process_definition_key: Box::new(ProcessDefinitionKey::assume_exists( "my-target-process-definition", )), mapping_instructions: vec![MigrateProcessInstanceMappingInstruction { source_element_id: ElementId::assume_exists("my-source-element"), target_element_id: ElementId::assume_exists("my-target-element"), }], ..Default::default() }, }) .await?; println!("Migrate process instance: done"); Ok(()) } ``` ### migrate_process_instances_batch_operation ```rust pub async fn migrate_process_instances_batch_operation(&self, params: MigrateProcessInstancesBatchOperationParams) -> Result ``` Migrate process instances (batch) (`POST /process-instances/migration`). **Example** ```rust async fn migrate_process_instances_batch_operation() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .migrate_process_instances_batch_operation(MigrateProcessInstancesBatchOperationParams { process_instance_migration_batch_operation_request: ProcessInstanceMigrationBatchOperationRequest { filter: Box::new(ProcessInstanceFilter::default()), migration_plan: Box::new(ProcessInstanceMigrationBatchOperationPlan { target_process_definition_key: Box::new( ProcessDefinitionKey::assume_exists("my-target-process-definition"), ), mapping_instructions: vec![Default::default()], }), ..Default::default() }, }) .await?; println!("{}", result.batch_operation_key); Ok(()) } ``` ### modify_process_instance ```rust pub async fn modify_process_instance(&self, params: ModifyProcessInstanceParams) -> Result<()> ``` Modify process instance (`POST /process-instances/{processInstanceKey}/modification`). **Example** ```rust async fn modify_process_instance( process_instance_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .modify_process_instance(ModifyProcessInstanceParams { process_instance_key, process_instance_modification_instruction: ProcessInstanceModificationInstruction::default(), }) .await?; println!("Modify process instance: done"); Ok(()) } ``` ### modify_process_instances_batch_operation ```rust pub async fn modify_process_instances_batch_operation(&self, params: ModifyProcessInstancesBatchOperationParams) -> Result ``` Modify process instances (batch) (`POST /process-instances/modification`). **Example** ```rust async fn modify_process_instances_batch_operation() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .modify_process_instances_batch_operation(ModifyProcessInstancesBatchOperationParams { process_instance_modification_batch_operation_request: ProcessInstanceModificationBatchOperationRequest { filter: Box::new(ProcessInstanceFilter::default()), move_instructions: vec![ ProcessInstanceModificationMoveBatchOperationInstruction { source_element_id: ElementId::assume_exists("my-source-element"), target_element_id: ElementId::assume_exists("my-target-element"), }, ], ..Default::default() }, }) .await?; println!("{}", result.batch_operation_key); Ok(()) } ``` ### new ```rust pub fn new(options: CamundaOptions) -> Result ``` Construct a client from `CamundaOptions` (environment + overrides). ### pause_cluster_exporting ```rust pub async fn pause_cluster_exporting(&self, params: PauseClusterExportingParams) -> Result<()> ``` Pause exporting across the whole cluster (`POST /cluster/v2/exporting/pause`). **Example** ```rust async fn pause_cluster_exporting() -> Result<(), Box> { let client = CamundaClient::from_env()?; client .pause_cluster_exporting(PauseClusterExportingParams { soft: Some(true) }) .await?; println!("Pause cluster exporting: done"); Ok(()) } ``` ### pause_exporting ```rust pub async fn pause_exporting(&self, params: PauseExportingParams) -> Result<()> ``` Pause exporting (`POST /exporting/pause`). **Example** ```rust async fn pause_exporting() -> Result<(), Box> { let client = CamundaClient::from_env()?; client .pause_exporting(PauseExportingParams { soft: Some(true) }) .await?; println!("Pause exporting: done"); Ok(()) } ``` ### pin_clock ```rust pub async fn pin_clock(&self, params: PinClockParams) -> Result<()> ``` Pin internal clock (alpha) (`PUT /clock`). **Example** ```rust async fn pin_clock() -> Result<(), Box> { let client = CamundaClient::from_env()?; client .pin_clock(PinClockParams { clock_pin_request: ClockPinRequest { timestamp: 1 }, }) .await?; println!("Pin internal clock (alpha): done"); Ok(()) } ``` ### publish_message ```rust pub async fn publish_message(&self, request: models::MessagePublicationRequest) -> Result ``` Publish a message (no correlation key matching against active subscriptions only — buffered). The configured default tenant id is applied when unset. **Example** ```rust async fn publish_message() -> Result<(), Box> { let client = CamundaClient::from_env()?; let published = client .publish_message(MessagePublicationRequest { name: "order-received".to_string(), correlation_key: Some("ORD-4711".to_string()), time_to_live: Some(60_000), variables: Some( [("total".to_string(), serde_json::json!(99.5))] .into_iter() .collect(), ), ..Default::default() }) .await?; println!("Published message {}", published.message_key); Ok(()) } ``` ### random ```rust pub fn random(&self) -> &Arc ``` The source this client draws jitter from. ### reset_clock ```rust pub async fn reset_clock(&self) -> Result<()> ``` Reset internal clock (alpha) (`POST /clock/reset`). **Example** ```rust async fn reset_clock() -> Result<(), Box> { let client = CamundaClient::from_env()?; client.reset_clock().await?; println!("Reset internal clock (alpha): done"); Ok(()) } ``` ### resolve_incident ```rust pub async fn resolve_incident(&self, params: ResolveIncidentParams) -> Result<()> ``` Resolve incident (`POST /incidents/{incidentKey}/resolution`). **Example** ```rust async fn resolve_incident(incident_key: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .resolve_incident(ResolveIncidentParams { incident_key, incident_resolution_request: Some(IncidentResolutionRequest::default()), }) .await?; println!("Resolve incident: done"); Ok(()) } ``` ### resolve_incidents_batch_operation ```rust pub async fn resolve_incidents_batch_operation(&self, params: ResolveIncidentsBatchOperationParams) -> Result ``` Resolve related incidents (batch) (`POST /process-instances/incident-resolution`). **Example** ```rust async fn resolve_incidents_batch_operation() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .resolve_incidents_batch_operation(ResolveIncidentsBatchOperationParams { process_instance_incident_resolution_batch_operation_request: ProcessInstanceIncidentResolutionBatchOperationRequest { filter: Box::new(ProcessInstanceFilter::default()), ..Default::default() }, }) .await?; println!("{}", result.batch_operation_key); Ok(()) } ``` ### resolve_process_instance_incidents ```rust pub async fn resolve_process_instance_incidents(&self, params: ResolveProcessInstanceIncidentsParams) -> Result ``` Resolve related incidents (`POST /process-instances/{processInstanceKey}/incident-resolution`). **Example** ```rust async fn resolve_process_instance_incidents( process_instance_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .resolve_process_instance_incidents(ResolveProcessInstanceIncidentsParams { process_instance_key, }) .await?; println!("{}", result.batch_operation_key); Ok(()) } ``` ### resolve_secrets ```rust pub async fn resolve_secrets(&self, params: ResolveSecretsParams) -> Result ``` Resolve secrets (`POST /secrets/resolve`). **Example** ```rust async fn resolve_secrets() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .resolve_secrets(ResolveSecretsParams { secret_resolve_request: SecretResolveRequest { references: vec!["camunda.secrets.my-secret".to_string()], }, }) .await?; println!("{result:#?}"); Ok(()) } ``` ### restore ```rust pub async fn restore(&self, params: RestoreParams) -> Result ``` Restore from a backup (`POST /restore`). **Example** ```rust async fn restore() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .restore(RestoreParams { restore_request: RestoreRequest::default(), dry_run: None, }) .await?; println!("{}", result.change_id); Ok(()) } ``` ### restore_as_cluster_admin ```rust pub async fn restore_as_cluster_admin(&self, params: RestoreAsClusterAdminParams) -> Result ``` Restore one or every physical tenant from a backup (`POST /cluster/v2/restore`). **Example** ```rust async fn restore_as_cluster_admin() -> Result<(), Box> { let client = CamundaClient::from_env()?; // Omitting `physical_tenant_id` restores the whole cluster. A cluster-wide // restore may also set `overrides` to pick a different backup per physical // tenant. let result = client .restore_as_cluster_admin(RestoreAsClusterAdminParams { cluster_restore_request: ClusterRestoreRequest { backup_ids: Some(Some(vec![1])), ..Default::default() }, physical_tenant_id: None, dry_run: Some(true), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### resume_batch_operation ```rust pub async fn resume_batch_operation(&self, params: ResumeBatchOperationParams) -> Result<()> ``` Resume Batch operation (`POST /batch-operations/{batchOperationKey}/resumption`). **Example** ```rust async fn resume_batch_operation( batch_operation_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .resume_batch_operation(ResumeBatchOperationParams { batch_operation_key, }) .await?; println!("Resume Batch operation: done"); Ok(()) } ``` ### resume_cluster_exporting ```rust pub async fn resume_cluster_exporting(&self) -> Result<()> ``` Resume exporting across the whole cluster (`POST /cluster/v2/exporting/resume`). **Example** ```rust async fn resume_cluster_exporting() -> Result<(), Box> { let client = CamundaClient::from_env()?; client.resume_cluster_exporting().await?; println!("Resume cluster exporting: done"); Ok(()) } ``` ### resume_exporting ```rust pub async fn resume_exporting(&self) -> Result<()> ``` Resume exporting (`POST /exporting/resume`). **Example** ```rust async fn resume_exporting() -> Result<(), Box> { let client = CamundaClient::from_env()?; client.resume_exporting().await?; println!("Resume exporting: done"); Ok(()) } ``` ### resume_process_instance ```rust pub async fn resume_process_instance(&self, params: ResumeProcessInstanceParams) -> Result<()> ``` Resume process instance (`POST /process-instances/{processInstanceKey}/resumption`). **Example** ```rust async fn resume_process_instance( process_instance_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .resume_process_instance(ResumeProcessInstanceParams { process_instance_key, resume_process_instance_request: Some(ResumeProcessInstanceRequest::default()), }) .await?; println!("Resume process instance: done"); Ok(()) } ``` ### resume_process_instances_batch_operation ```rust pub async fn resume_process_instances_batch_operation(&self, params: ResumeProcessInstancesBatchOperationParams) -> Result ``` Resume process instances (batch) (`POST /process-instances/resumption`). **Example** ```rust async fn resume_process_instances_batch_operation() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .resume_process_instances_batch_operation(ResumeProcessInstancesBatchOperationParams { process_instance_resumption_batch_operation_request: ProcessInstanceResumptionBatchOperationRequest { filter: Box::new(ProcessInstanceFilter::default()), ..Default::default() }, }) .await?; println!("{}", result.batch_operation_key); Ok(()) } ``` ### running_workers ```rust pub fn running_workers(&self) -> Vec ``` The job types of all currently-registered workers that are still running. ### search_agent_definitions ```rust pub async fn search_agent_definitions(&self, params: SearchAgentDefinitionsParams) -> Result ``` Search agent definitions (`POST /agent-definitions/search`). **Example** ```rust async fn search_agent_definitions() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_agent_definitions(SearchAgentDefinitionsParams { agent_definition_search_query: Some(AgentDefinitionSearchQuery::default()), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### search_agent_instance_history ```rust pub async fn search_agent_instance_history(&self, params: SearchAgentInstanceHistoryParams) -> Result ``` Search agent instance history (`POST /agent-instances/{agentInstanceKey}/history/search`). **Example** ```rust async fn search_agent_instance_history( agent_instance_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_agent_instance_history(SearchAgentInstanceHistoryParams { agent_instance_key, agent_instance_history_search_query: Some(AgentInstanceHistorySearchQuery::default()), }) .await?; for item in result.items { println!("{}", item.job_lease_token); } Ok(()) } ``` ### search_agent_instances ```rust pub async fn search_agent_instances(&self, params: SearchAgentInstancesParams) -> Result ``` Search agent instances (`POST /agent-instances/search`). **Example** ```rust async fn search_agent_instances() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_agent_instances(SearchAgentInstancesParams { agent_instance_search_query: Some(AgentInstanceSearchQuery::default()), }) .await?; for item in result.items { println!("{}", item.element_id); } Ok(()) } ``` ### search_audit_logs ```rust pub async fn search_audit_logs(&self, params: SearchAuditLogsParams) -> Result ``` Search audit logs (`POST /audit-logs/search`). **Example** ```rust async fn search_audit_logs() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_audit_logs(SearchAuditLogsParams { audit_log_search_query_request: Some(AuditLogSearchQueryRequest::default()), }) .await?; for item in result.items { println!("{}", item.entity_key); } Ok(()) } ``` ### search_authorizations ```rust pub async fn search_authorizations(&self, params: SearchAuthorizationsParams) -> Result ``` Search authorizations (`POST /authorizations/search`). **Example** ```rust async fn search_authorizations() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_authorizations(SearchAuthorizationsParams { authorization_search_query: Some(AuthorizationSearchQuery::default()), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### search_batch_operation_items ```rust pub async fn search_batch_operation_items(&self, params: SearchBatchOperationItemsParams) -> Result ``` Search batch operation items (`POST /batch-operation-items/search`). **Example** ```rust async fn search_batch_operation_items() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_batch_operation_items(SearchBatchOperationItemsParams { batch_operation_item_search_query: Some(BatchOperationItemSearchQuery::default()), }) .await?; for item in result.items { println!("{}", item.batch_operation_key); } Ok(()) } ``` ### search_batch_operations ```rust pub async fn search_batch_operations(&self, params: SearchBatchOperationsParams) -> Result ``` Search batch operations (`POST /batch-operations/search`). **Example** ```rust async fn search_batch_operations() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_batch_operations(SearchBatchOperationsParams { batch_operation_search_query: Some(BatchOperationSearchQuery::default()), }) .await?; for item in result.items { println!("{}", item.batch_operation_key); } Ok(()) } ``` ### search_clients_for_group ```rust pub async fn search_clients_for_group(&self, params: SearchClientsForGroupParams) -> Result ``` Search group clients (`POST /groups/{groupId}/clients/search`). **Example** ```rust async fn search_clients_for_group(group_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_clients_for_group(SearchClientsForGroupParams { group_id, group_client_search_query_request: Some(GroupClientSearchQueryRequest::default()), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### search_clients_for_role ```rust pub async fn search_clients_for_role(&self, params: SearchClientsForRoleParams) -> Result ``` Search role clients (`POST /roles/{roleId}/clients/search`). **Example** ```rust async fn search_clients_for_role(role_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_clients_for_role(SearchClientsForRoleParams { role_id, role_client_search_query_request: Some(RoleClientSearchQueryRequest::default()), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### search_clients_for_tenant ```rust pub async fn search_clients_for_tenant(&self, params: SearchClientsForTenantParams) -> Result ``` Search clients for tenant (`POST /tenants/{tenantId}/clients/search`). **Example** ```rust async fn search_clients_for_tenant(tenant_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_clients_for_tenant(SearchClientsForTenantParams { tenant_id, tenant_client_search_query_request: Some(TenantClientSearchQueryRequest::default()), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### search_cluster_variables ```rust pub async fn search_cluster_variables(&self, params: SearchClusterVariablesParams) -> Result ``` Search for cluster variables based on given criteria (`POST /cluster-variables/search`). **Example** ```rust async fn search_cluster_variables() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_cluster_variables(SearchClusterVariablesParams { truncate_values: None, cluster_variable_search_query_request: Some( ClusterVariableSearchQueryRequest::default(), ), }) .await?; for item in result.items { println!("{}", item.name); } Ok(()) } ``` ### search_correlated_message_subscriptions ```rust pub async fn search_correlated_message_subscriptions(&self, params: SearchCorrelatedMessageSubscriptionsParams) -> Result ``` Search correlated message subscriptions (`POST /correlated-message-subscriptions/search`). **Example** ```rust async fn search_correlated_message_subscriptions() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_correlated_message_subscriptions(SearchCorrelatedMessageSubscriptionsParams { correlated_message_subscription_search_query: Some( CorrelatedMessageSubscriptionSearchQuery::default(), ), }) .await?; for item in result.items { println!("{}", item.process_definition_id); } Ok(()) } ``` ### search_decision_definitions ```rust pub async fn search_decision_definitions(&self, params: SearchDecisionDefinitionsParams) -> Result ``` Search decision definitions (`POST /decision-definitions/search`). **Example** ```rust async fn search_decision_definitions() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_decision_definitions(SearchDecisionDefinitionsParams { decision_definition_search_query: Some(DecisionDefinitionSearchQuery::default()), }) .await?; for item in result.items { println!("{}", item.decision_definition_id); } Ok(()) } ``` ### search_decision_instances ```rust pub async fn search_decision_instances(&self, params: SearchDecisionInstancesParams) -> Result ``` Search decision instances (`POST /decision-instances/search`). **Example** ```rust async fn search_decision_instances() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_decision_instances(SearchDecisionInstancesParams { decision_instance_search_query: Some(DecisionInstanceSearchQuery::default()), }) .await?; for item in result.items { println!("{}", item.decision_evaluation_instance_key); } Ok(()) } ``` ### search_decision_requirements ```rust pub async fn search_decision_requirements(&self, params: SearchDecisionRequirementsParams) -> Result ``` Search decision requirements (`POST /decision-requirements/search`). **Example** ```rust async fn search_decision_requirements() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_decision_requirements(SearchDecisionRequirementsParams { decision_requirements_search_query: Some(DecisionRequirementsSearchQuery::default()), }) .await?; for item in result.items { println!("{}", item.decision_requirements_id); } Ok(()) } ``` ### search_element_instance_incidents ```rust pub async fn search_element_instance_incidents(&self, params: SearchElementInstanceIncidentsParams) -> Result ``` Search for incidents of a specific element instance (`POST /element-instances/{elementInstanceKey}/incidents/search`). **Example** ```rust async fn search_element_instance_incidents( element_instance_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_element_instance_incidents(SearchElementInstanceIncidentsParams { element_instance_key, incident_search_query: IncidentSearchQuery::default(), }) .await?; for item in result.items { println!("{}", item.element_id); } Ok(()) } ``` ### search_element_instance_wait_states ```rust pub async fn search_element_instance_wait_states(&self, params: SearchElementInstanceWaitStatesParams) -> Result ``` Search element instance wait states (`POST /element-instances/wait-states/search`). **Example** ```rust async fn search_element_instance_wait_states() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_element_instance_wait_states(SearchElementInstanceWaitStatesParams { element_instance_wait_state_query: Some(ElementInstanceWaitStateQuery::default()), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### search_element_instances ```rust pub async fn search_element_instances(&self, params: SearchElementInstancesParams) -> Result ``` Search element instances (`POST /element-instances/search`). **Example** ```rust async fn search_element_instances() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_element_instances(SearchElementInstancesParams { element_instance_search_query: Some(ElementInstanceSearchQuery::default()), }) .await?; for item in result.items { println!("{}", item.element_id); } Ok(()) } ``` ### search_global_task_listeners ```rust pub async fn search_global_task_listeners(&self, params: SearchGlobalTaskListenersParams) -> Result ``` Search global user task listeners (`POST /global-task-listeners/search`). **Example** ```rust async fn search_global_task_listeners() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_global_task_listeners(SearchGlobalTaskListenersParams { global_task_listener_search_query_request: Some( GlobalTaskListenerSearchQueryRequest::default(), ), }) .await?; for item in result.items { println!("{}", item.id); } Ok(()) } ``` ### search_group_ids_for_tenant ```rust pub async fn search_group_ids_for_tenant(&self, params: SearchGroupIdsForTenantParams) -> Result ``` Search groups for tenant (`POST /tenants/{tenantId}/groups/search`). **Example** ```rust async fn search_group_ids_for_tenant(tenant_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_group_ids_for_tenant(SearchGroupIdsForTenantParams { tenant_id, tenant_group_search_query_request: Some(TenantGroupSearchQueryRequest::default()), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### search_groups ```rust pub async fn search_groups(&self, params: SearchGroupsParams) -> Result ``` Search groups (`POST /groups/search`). **Example** ```rust async fn search_groups() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_groups(SearchGroupsParams { group_search_query_request: Some(GroupSearchQueryRequest::default()), }) .await?; for item in result.items { println!("{}", item.group_id); } Ok(()) } ``` ### search_groups_for_role ```rust pub async fn search_groups_for_role(&self, params: SearchGroupsForRoleParams) -> Result ``` Search role groups (`POST /roles/{roleId}/groups/search`). **Example** ```rust async fn search_groups_for_role(role_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_groups_for_role(SearchGroupsForRoleParams { role_id, role_group_search_query_request: Some(RoleGroupSearchQueryRequest::default()), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### search_incidents ```rust pub async fn search_incidents(&self, params: SearchIncidentsParams) -> Result ``` Search incidents (`POST /incidents/search`). **Example** ```rust async fn search_incidents() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_incidents(SearchIncidentsParams { incident_search_query: Some(IncidentSearchQuery::default()), }) .await?; for item in result.items { println!("{}", item.element_id); } Ok(()) } ``` ### search_jobs ```rust pub async fn search_jobs(&self, params: SearchJobsParams) -> Result ``` Search jobs (`POST /jobs/search`). **Example** ```rust async fn search_jobs() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_jobs(SearchJobsParams { job_search_query: Some(JobSearchQuery::default()), }) .await?; for item in result.items { println!("{}", item.process_definition_id); } Ok(()) } ``` ### search_mapping_rule ```rust pub async fn search_mapping_rule(&self, params: SearchMappingRuleParams) -> Result ``` Search mapping rules (`POST /mapping-rules/search`). **Example** ```rust async fn search_mapping_rule() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_mapping_rule(SearchMappingRuleParams { mapping_rule_search_query_request: Some(MappingRuleSearchQueryRequest::default()), }) .await?; for item in result.items { println!("{}", item.mapping_rule_id); } Ok(()) } ``` ### search_mapping_rules_for_group ```rust pub async fn search_mapping_rules_for_group(&self, params: SearchMappingRulesForGroupParams) -> Result ``` Search group mapping rules (`POST /groups/{groupId}/mapping-rules/search`). **Example** ```rust async fn search_mapping_rules_for_group( group_id: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_mapping_rules_for_group(SearchMappingRulesForGroupParams { group_id, mapping_rule_search_query_request: Some(MappingRuleSearchQueryRequest::default()), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### search_mapping_rules_for_role ```rust pub async fn search_mapping_rules_for_role(&self, params: SearchMappingRulesForRoleParams) -> Result ``` Search role mapping rules (`POST /roles/{roleId}/mapping-rules/search`). **Example** ```rust async fn search_mapping_rules_for_role(role_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_mapping_rules_for_role(SearchMappingRulesForRoleParams { role_id, mapping_rule_search_query_request: Some(MappingRuleSearchQueryRequest::default()), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### search_mapping_rules_for_tenant ```rust pub async fn search_mapping_rules_for_tenant(&self, params: SearchMappingRulesForTenantParams) -> Result ``` Search mapping rules for tenant (`POST /tenants/{tenantId}/mapping-rules/search`). **Example** ```rust async fn search_mapping_rules_for_tenant( tenant_id: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_mapping_rules_for_tenant(SearchMappingRulesForTenantParams { tenant_id, mapping_rule_search_query_request: Some(MappingRuleSearchQueryRequest::default()), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### search_message_subscriptions ```rust pub async fn search_message_subscriptions(&self, params: SearchMessageSubscriptionsParams) -> Result ``` Search message subscriptions (`POST /message-subscriptions/search`). **Example** ```rust async fn search_message_subscriptions() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_message_subscriptions(SearchMessageSubscriptionsParams { message_subscription_search_query: Some(MessageSubscriptionSearchQuery::default()), }) .await?; for item in result.items { println!("{}", item.element_id); } Ok(()) } ``` ### search_own_authorizations ```rust pub async fn search_own_authorizations(&self, params: SearchOwnAuthorizationsParams) -> Result ``` Search own authorizations (`POST /authentication/me/authorizations/search`). **Example** ```rust async fn search_own_authorizations() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_own_authorizations(SearchOwnAuthorizationsParams { authorization_search_query: Some(AuthorizationSearchQuery::default()), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### search_process_definition_variable_names ```rust pub async fn search_process_definition_variable_names(&self, params: SearchProcessDefinitionVariableNamesParams) -> Result ``` Search process definition variable names (`POST /process-definitions/{processDefinitionKey}/variable-names/search`). **Example** ```rust async fn search_process_definition_variable_names( process_definition_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_process_definition_variable_names(SearchProcessDefinitionVariableNamesParams { process_definition_key, process_definition_variable_name_search_query: Some( ProcessDefinitionVariableNameSearchQuery::default(), ), }) .await?; for item in result.items { println!("{}", item.name); } Ok(()) } ``` ### search_process_definitions ```rust pub async fn search_process_definitions(&self, params: SearchProcessDefinitionsParams) -> Result ``` Search process definitions (`POST /process-definitions/search`). **Example** ```rust async fn search_process_definitions() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_process_definitions(SearchProcessDefinitionsParams { process_definition_search_query: Some(ProcessDefinitionSearchQuery::default()), }) .await?; for item in result.items { println!("{}", item.process_definition_id); } Ok(()) } ``` ### search_process_instance_incidents ```rust pub async fn search_process_instance_incidents(&self, params: SearchProcessInstanceIncidentsParams) -> Result ``` Search related incidents (`POST /process-instances/{processInstanceKey}/incidents/search`). **Example** ```rust async fn search_process_instance_incidents( process_instance_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_process_instance_incidents(SearchProcessInstanceIncidentsParams { process_instance_key, incident_search_query: Some(IncidentSearchQuery::default()), }) .await?; for item in result.items { println!("{}", item.element_id); } Ok(()) } ``` ### search_process_instances ```rust pub async fn search_process_instances(&self, params: SearchProcessInstancesParams) -> Result ``` Search process instances (`POST /process-instances/search`). **Example** ```rust async fn search_process_instances() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_process_instances(SearchProcessInstancesParams { process_instance_search_query: Some(ProcessInstanceSearchQuery::default()), }) .await?; for item in result.items { println!("{}", item.process_definition_id); } Ok(()) } ``` ### search_resources ```rust pub async fn search_resources(&self, params: SearchResourcesParams) -> Result ``` Search resources (`POST /resources/search`). **Example** ```rust async fn search_resources() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_resources(SearchResourcesParams { resource_search_query: Some(ResourceSearchQuery::default()), }) .await?; for item in result.items { println!("{}", item.resource_id); } Ok(()) } ``` ### search_roles ```rust pub async fn search_roles(&self, params: SearchRolesParams) -> Result ``` Search roles (`POST /roles/search`). **Example** ```rust async fn search_roles() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_roles(SearchRolesParams { role_search_query_request: Some(RoleSearchQueryRequest::default()), }) .await?; for item in result.items { println!("{}", item.role_id); } Ok(()) } ``` ### search_roles_for_group ```rust pub async fn search_roles_for_group(&self, params: SearchRolesForGroupParams) -> Result ``` Search group roles (`POST /groups/{groupId}/roles/search`). **Example** ```rust async fn search_roles_for_group(group_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_roles_for_group(SearchRolesForGroupParams { group_id, role_search_query_request: Some(RoleSearchQueryRequest::default()), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### search_roles_for_tenant ```rust pub async fn search_roles_for_tenant(&self, params: SearchRolesForTenantParams) -> Result ``` Search roles for tenant (`POST /tenants/{tenantId}/roles/search`). **Example** ```rust async fn search_roles_for_tenant(tenant_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_roles_for_tenant(SearchRolesForTenantParams { tenant_id, role_search_query_request: Some(RoleSearchQueryRequest::default()), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### search_tenants ```rust pub async fn search_tenants(&self, params: SearchTenantsParams) -> Result ``` Search tenants (`POST /tenants/search`). **Example** ```rust async fn search_tenants() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_tenants(SearchTenantsParams { tenant_search_query_request: Some(TenantSearchQueryRequest::default()), }) .await?; for item in result.items { println!("{}", item.name); } Ok(()) } ``` ### search_user_task_audit_logs ```rust pub async fn search_user_task_audit_logs(&self, params: SearchUserTaskAuditLogsParams) -> Result ``` Search user task audit logs (`POST /user-tasks/{userTaskKey}/audit-logs/search`). **Example** ```rust async fn search_user_task_audit_logs( user_task_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_user_task_audit_logs(SearchUserTaskAuditLogsParams { user_task_key, user_task_audit_log_search_query_request: Some( UserTaskAuditLogSearchQueryRequest::default(), ), }) .await?; for item in result.items { println!("{}", item.entity_key); } Ok(()) } ``` ### search_user_task_effective_variables ```rust pub async fn search_user_task_effective_variables(&self, params: SearchUserTaskEffectiveVariablesParams) -> Result ``` Search user task effective variables (`POST /user-tasks/{userTaskKey}/effective-variables/search`). **Example** ```rust async fn search_user_task_effective_variables( user_task_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_user_task_effective_variables(SearchUserTaskEffectiveVariablesParams { user_task_key, truncate_values: None, user_task_effective_variable_search_query_request: Some( UserTaskEffectiveVariableSearchQueryRequest::default(), ), }) .await?; for item in result.items { println!("{}", item.name); } Ok(()) } ``` ### search_user_task_variables ```rust pub async fn search_user_task_variables(&self, params: SearchUserTaskVariablesParams) -> Result ``` Search user task variables (`POST /user-tasks/{userTaskKey}/variables/search`). **Example** ```rust async fn search_user_task_variables( user_task_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_user_task_variables(SearchUserTaskVariablesParams { user_task_key, truncate_values: None, user_task_variable_search_query_request: Some( UserTaskVariableSearchQueryRequest::default(), ), }) .await?; for item in result.items { println!("{}", item.name); } Ok(()) } ``` ### search_user_tasks ```rust pub async fn search_user_tasks(&self, params: SearchUserTasksParams) -> Result ``` Search user tasks (`POST /user-tasks/search`). **Example** ```rust async fn search_user_tasks() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_user_tasks(SearchUserTasksParams { user_task_search_query: Some(UserTaskSearchQuery::default()), }) .await?; for item in result.items { println!("{}", item.element_id); } Ok(()) } ``` ### search_users ```rust pub async fn search_users(&self, params: SearchUsersParams) -> Result ``` Search users (`POST /users/search`). **Example** ```rust async fn search_users() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_users(SearchUsersParams { user_search_query_request: Some(UserSearchQueryRequest::default()), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### search_users_for_group ```rust pub async fn search_users_for_group(&self, params: SearchUsersForGroupParams) -> Result ``` Search group users (`POST /groups/{groupId}/users/search`). **Example** ```rust async fn search_users_for_group(group_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_users_for_group(SearchUsersForGroupParams { group_id, group_user_search_query_request: Some(GroupUserSearchQueryRequest::default()), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### search_users_for_role ```rust pub async fn search_users_for_role(&self, params: SearchUsersForRoleParams) -> Result ``` Search role users (`POST /roles/{roleId}/users/search`). **Example** ```rust async fn search_users_for_role(role_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_users_for_role(SearchUsersForRoleParams { role_id, role_user_search_query_request: Some(RoleUserSearchQueryRequest::default()), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### search_users_for_tenant ```rust pub async fn search_users_for_tenant(&self, params: SearchUsersForTenantParams) -> Result ``` Search users for tenant (`POST /tenants/{tenantId}/users/search`). **Example** ```rust async fn search_users_for_tenant(tenant_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .search_users_for_tenant(SearchUsersForTenantParams { tenant_id, tenant_user_search_query_request: Some(TenantUserSearchQueryRequest::default()), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### search_variables ```rust pub async fn search_variables(&self, request: models::VariableSearchQuery) -> Result ``` Search variables, returning the raw generated result. **Example** ```rust async fn search_variables() -> Result<(), Box> { let client = CamundaClient::from_env()?; // An empty query returns the first page of all variables. Set `filter` to narrow // by process instance, scope, name, or value. let results = client .search_variables(VariableSearchQuery::default()) .await?; for variable in results.items { println!("{} = {}", variable.name, variable.value); } Ok(()) } ``` ### search_variables_as ```rust pub async fn search_variables_as(&self, request: models::VariableSearchQuery) -> Result> ``` Search variables and deserialize each variable's JSON `value` into a typed `T`. Returns the deserialized values in result order; variables whose `value` is absent are skipped. ### spawn_worker ```rust pub fn spawn_worker(&self, config: JobWorkerConfig, handler: F) ``` Spawn a managed job worker and register it for lifecycle control. The returned handle can stop the individual worker; `CamundaClient::stop_all_workers` stops every registered worker. The worker is also retained by the client so it keeps running even if the returned handle is dropped. ### stop_all_workers ```rust pub async fn stop_all_workers(&self) -> Result<()> ``` Gracefully stop every registered worker, letting in-flight jobs drain, and await their completion. Clears the registry. ### suspend_batch_operation ```rust pub async fn suspend_batch_operation(&self, params: SuspendBatchOperationParams) -> Result<()> ``` Suspend Batch operation (`POST /batch-operations/{batchOperationKey}/suspension`). **Example** ```rust async fn suspend_batch_operation( batch_operation_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .suspend_batch_operation(SuspendBatchOperationParams { batch_operation_key, }) .await?; println!("Suspend Batch operation: done"); Ok(()) } ``` ### suspend_process_instance ```rust pub async fn suspend_process_instance(&self, params: SuspendProcessInstanceParams) -> Result<()> ``` Suspend process instance (`POST /process-instances/{processInstanceKey}/suspension`). **Example** ```rust async fn suspend_process_instance( process_instance_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .suspend_process_instance(SuspendProcessInstanceParams { process_instance_key, suspend_process_instance_request: Some(SuspendProcessInstanceRequest::default()), }) .await?; println!("Suspend process instance: done"); Ok(()) } ``` ### suspend_process_instances_batch_operation ```rust pub async fn suspend_process_instances_batch_operation(&self, params: SuspendProcessInstancesBatchOperationParams) -> Result ``` Suspend process instances (batch) (`POST /process-instances/suspension`). **Example** ```rust async fn suspend_process_instances_batch_operation() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .suspend_process_instances_batch_operation(SuspendProcessInstancesBatchOperationParams { process_instance_suspension_batch_operation_request: ProcessInstanceSuspensionBatchOperationRequest { filter: Box::new(ProcessInstanceFilter::default()), ..Default::default() }, }) .await?; println!("{}", result.batch_operation_key); Ok(()) } ``` ### sync_runtime_backup_state ```rust pub async fn sync_runtime_backup_state(&self) -> Result ``` Force-write runtime backup state (`POST /backups/runtime/state/sync`). **Example** ```rust async fn sync_runtime_backup_state() -> Result<(), Box> { let client = CamundaClient::from_env()?; let state = client.sync_runtime_backup_state().await?; println!("{state:#?}"); Ok(()) } ``` ### sync_runtime_backup_state_as_cluster_admin ```rust pub async fn sync_runtime_backup_state_as_cluster_admin(&self, params: SyncRuntimeBackupStateAsClusterAdminParams) -> Result ``` Force-write runtime backup state across physical tenants (`POST /cluster/v2/backups/runtime/state/sync`). **Example** ```rust async fn sync_runtime_backup_state_as_cluster_admin() -> Result<(), Box> { let client = CamundaClient::from_env()?; let state = client .sync_runtime_backup_state_as_cluster_admin(SyncRuntimeBackupStateAsClusterAdminParams { physical_tenant_id: None, }) .await?; println!("{state:#?}"); Ok(()) } ``` ### take_history_backup ```rust pub async fn take_history_backup(&self, params: TakeHistoryBackupParams) -> Result ``` Take a history backup (`POST /backups/history`). **Example** ```rust async fn take_history_backup() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .take_history_backup(TakeHistoryBackupParams { take_history_backup_request: TakeHistoryBackupRequest { backup_id: 1 }, }) .await?; println!("{result:#?}"); Ok(()) } ``` ### take_history_backup_as_cluster_admin ```rust pub async fn take_history_backup_as_cluster_admin(&self, params: TakeHistoryBackupAsClusterAdminParams) -> Result ``` Take a history backup on one or every physical tenant (`POST /cluster/v2/backups/history`). **Example** ```rust async fn take_history_backup_as_cluster_admin() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .take_history_backup_as_cluster_admin(TakeHistoryBackupAsClusterAdminParams { take_history_backup_request: TakeHistoryBackupRequest { backup_id: 1 }, physical_tenant_id: None, }) .await?; println!("{result:#?}"); Ok(()) } ``` ### take_runtime_backup ```rust pub async fn take_runtime_backup(&self, params: TakeRuntimeBackupParams) -> Result ``` Take a runtime backup (`POST /backups/runtime`). **Example** ```rust async fn take_runtime_backup() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .take_runtime_backup(TakeRuntimeBackupParams { take_runtime_backup_request: Some(TakeRuntimeBackupRequest { backup_id: Some(Some(1)), }), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### take_runtime_backup_as_cluster_admin ```rust pub async fn take_runtime_backup_as_cluster_admin(&self, params: TakeRuntimeBackupAsClusterAdminParams) -> Result ``` Take a runtime backup on one or every physical tenant (`POST /cluster/v2/backups/runtime`). **Example** ```rust async fn take_runtime_backup_as_cluster_admin() -> Result<(), Box> { let client = CamundaClient::from_env()?; // Setting `physical_tenant_id` scopes the backup to one physical tenant; omit // it to back up every physical tenant of the cluster. let result = client .take_runtime_backup_as_cluster_admin(TakeRuntimeBackupAsClusterAdminParams { physical_tenant_id: Some("default".to_string()), take_runtime_backup_request: Some(TakeRuntimeBackupRequest { backup_id: Some(Some(1)), }), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### throw_job_error ```rust pub async fn throw_job_error(&self, job_key: &str, request: models::JobErrorRequest) -> Result<()> ``` Throw a BPMN error from a job. **Example** ```rust async fn throw_job_error(job_key: &str) -> Result<(), Box> { let client = CamundaClient::from_env()?; // Routes the token to a matching BPMN error boundary event, rather than // failing the job technically. client .throw_job_error( job_key, JobErrorRequest { error_code: "PAYMENT_DECLINED".to_string(), error_message: Some(Some("card declined by issuer".to_string())), ..Default::default() }, ) .await?; println!("Threw BPMN error for job {job_key}"); Ok(()) } ``` ### topology ```rust pub async fn topology(&self) -> Result ``` Fetch the cluster topology. **Example** ```rust async fn get_topology() -> Result<(), Box> { let client = CamundaClient::from_env()?; let topology = client.topology().await?; println!("Gateway version: {}", topology.gateway_version); println!("Cluster size: {}", topology.cluster_size); println!("Partitions: {}", topology.partitions_count); println!("Replication factor: {}", topology.replication_factor); for broker in topology.brokers { println!( " broker {} at {}:{}", broker.node_id, broker.host, broker.port ); } Ok(()) } ``` ### trigger_cluster_rebalance ```rust pub async fn trigger_cluster_rebalance(&self, params: TriggerClusterRebalanceParams) -> Result ``` Trigger a cluster-wide leadership rebalance (`POST /cluster/v2/rebalance`). **Example** ```rust async fn trigger_cluster_rebalance() -> Result<(), Box> { let client = CamundaClient::from_env()?; // Every setting is optional; an absent request body means "use the configured // settings". Set `dry_run` to report the plan without moving any leadership. let result = client .trigger_cluster_rebalance(TriggerClusterRebalanceParams { dry_run: Some(true), cluster_rebalance_request: Some(ClusterRebalanceRequest { replication_timeout: Some("PT30S".to_string()), ..Default::default() }), }) .await?; println!("{result:#?}"); Ok(()) } ``` ### unassign_client_from_group ```rust pub async fn unassign_client_from_group(&self, params: UnassignClientFromGroupParams) -> Result<()> ``` Unassign a client from a group (`DELETE /groups/{groupId}/clients/{clientId}`). **Example** ```rust async fn unassign_client_from_group( group_id: String, client_id: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .unassign_client_from_group(UnassignClientFromGroupParams { group_id, client_id, }) .await?; println!("Unassign a client from a group: done"); Ok(()) } ``` ### unassign_client_from_tenant ```rust pub async fn unassign_client_from_tenant(&self, params: UnassignClientFromTenantParams) -> Result<()> ``` Unassign a client from a tenant (`DELETE /tenants/{tenantId}/clients/{clientId}`). **Example** ```rust async fn unassign_client_from_tenant( tenant_id: String, client_id: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .unassign_client_from_tenant(UnassignClientFromTenantParams { tenant_id, client_id, }) .await?; println!("Unassign a client from a tenant: done"); Ok(()) } ``` ### unassign_group_from_tenant ```rust pub async fn unassign_group_from_tenant(&self, params: UnassignGroupFromTenantParams) -> Result<()> ``` Unassign a group from a tenant (`DELETE /tenants/{tenantId}/groups/{groupId}`). **Example** ```rust async fn unassign_group_from_tenant( tenant_id: String, group_id: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .unassign_group_from_tenant(UnassignGroupFromTenantParams { tenant_id, group_id, }) .await?; println!("Unassign a group from a tenant: done"); Ok(()) } ``` ### unassign_mapping_rule_from_group ```rust pub async fn unassign_mapping_rule_from_group(&self, params: UnassignMappingRuleFromGroupParams) -> Result<()> ``` Unassign a mapping rule from a group (`DELETE /groups/{groupId}/mapping-rules/{mappingRuleId}`). **Example** ```rust async fn unassign_mapping_rule_from_group( group_id: String, mapping_rule_id: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .unassign_mapping_rule_from_group(UnassignMappingRuleFromGroupParams { group_id, mapping_rule_id, }) .await?; println!("Unassign a mapping rule from a group: done"); Ok(()) } ``` ### unassign_mapping_rule_from_tenant ```rust pub async fn unassign_mapping_rule_from_tenant(&self, params: UnassignMappingRuleFromTenantParams) -> Result<()> ``` Unassign a mapping rule from a tenant (`DELETE /tenants/{tenantId}/mapping-rules/{mappingRuleId}`). **Example** ```rust async fn unassign_mapping_rule_from_tenant( tenant_id: String, mapping_rule_id: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .unassign_mapping_rule_from_tenant(UnassignMappingRuleFromTenantParams { tenant_id, mapping_rule_id, }) .await?; println!("Unassign a mapping rule from a tenant: done"); Ok(()) } ``` ### unassign_role_from_client ```rust pub async fn unassign_role_from_client(&self, params: UnassignRoleFromClientParams) -> Result<()> ``` Unassign a role from a client (`DELETE /roles/{roleId}/clients/{clientId}`). **Example** ```rust async fn unassign_role_from_client( role_id: String, client_id: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .unassign_role_from_client(UnassignRoleFromClientParams { role_id, client_id }) .await?; println!("Unassign a role from a client: done"); Ok(()) } ``` ### unassign_role_from_group ```rust pub async fn unassign_role_from_group(&self, params: UnassignRoleFromGroupParams) -> Result<()> ``` Unassign a role from a group (`DELETE /roles/{roleId}/groups/{groupId}`). **Example** ```rust async fn unassign_role_from_group( role_id: String, group_id: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .unassign_role_from_group(UnassignRoleFromGroupParams { role_id, group_id }) .await?; println!("Unassign a role from a group: done"); Ok(()) } ``` ### unassign_role_from_mapping_rule ```rust pub async fn unassign_role_from_mapping_rule(&self, params: UnassignRoleFromMappingRuleParams) -> Result<()> ``` Unassign a role from a mapping rule (`DELETE /roles/{roleId}/mapping-rules/{mappingRuleId}`). **Example** ```rust async fn unassign_role_from_mapping_rule( role_id: String, mapping_rule_id: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .unassign_role_from_mapping_rule(UnassignRoleFromMappingRuleParams { role_id, mapping_rule_id, }) .await?; println!("Unassign a role from a mapping rule: done"); Ok(()) } ``` ### unassign_role_from_tenant ```rust pub async fn unassign_role_from_tenant(&self, params: UnassignRoleFromTenantParams) -> Result<()> ``` Unassign a role from a tenant (`DELETE /tenants/{tenantId}/roles/{roleId}`). **Example** ```rust async fn unassign_role_from_tenant( tenant_id: String, role_id: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .unassign_role_from_tenant(UnassignRoleFromTenantParams { tenant_id, role_id }) .await?; println!("Unassign a role from a tenant: done"); Ok(()) } ``` ### unassign_role_from_user ```rust pub async fn unassign_role_from_user(&self, params: UnassignRoleFromUserParams) -> Result<()> ``` Unassign a role from a user (`DELETE /roles/{roleId}/users/{username}`). **Example** ```rust async fn unassign_role_from_user( role_id: String, username: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .unassign_role_from_user(UnassignRoleFromUserParams { role_id, username }) .await?; println!("Unassign a role from a user: done"); Ok(()) } ``` ### unassign_user_from_group ```rust pub async fn unassign_user_from_group(&self, params: UnassignUserFromGroupParams) -> Result<()> ``` Unassign a user from a group (`DELETE /groups/{groupId}/users/{username}`). **Example** ```rust async fn unassign_user_from_group( group_id: String, username: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .unassign_user_from_group(UnassignUserFromGroupParams { group_id, username }) .await?; println!("Unassign a user from a group: done"); Ok(()) } ``` ### unassign_user_from_tenant ```rust pub async fn unassign_user_from_tenant(&self, params: UnassignUserFromTenantParams) -> Result<()> ``` Unassign a user from a tenant (`DELETE /tenants/{tenantId}/users/{username}`). **Example** ```rust async fn unassign_user_from_tenant( tenant_id: String, username: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .unassign_user_from_tenant(UnassignUserFromTenantParams { tenant_id, username, }) .await?; println!("Unassign a user from a tenant: done"); Ok(()) } ``` ### unassign_user_task ```rust pub async fn unassign_user_task(&self, params: UnassignUserTaskParams) -> Result<()> ``` Unassign user task (`DELETE /user-tasks/{userTaskKey}/assignee`). **Example** ```rust async fn unassign_user_task(user_task_key: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .unassign_user_task(UnassignUserTaskParams { user_task_key }) .await?; println!("Unassign user task: done"); Ok(()) } ``` ### update_agent_instance ```rust pub async fn update_agent_instance(&self, params: UpdateAgentInstanceParams) -> Result ``` Update agent instance (`PATCH /agent-instances/{agentInstanceKey}`). **Example** ```rust async fn update_agent_instance( agent_instance_key: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .update_agent_instance(UpdateAgentInstanceParams { agent_instance_key, agent_instance_update_request: AgentInstanceUpdateRequest { element_instance_key: Box::new(ElementInstanceKey::assume_exists( "my-element-instance", )), job_key: Box::new(JobKey::assume_exists("my-job")), job_lease_token: JobLeaseToken::assume_exists("my-job-lease"), ..Default::default() }, }) .await?; println!("Update agent instance: done"); Ok(()) } ``` ### update_authorization ```rust pub async fn update_authorization(&self, params: UpdateAuthorizationParams) -> Result<()> ``` Update authorization (`PUT /authorizations/{authorizationKey}`). **Example** ```rust async fn update_authorization(authorization_key: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .update_authorization(UpdateAuthorizationParams { authorization_key, authorization_request: AuthorizationRequest::AuthorizationIdBasedRequest(Box::new( AuthorizationIdBasedRequest { owner_id: "my-owner".to_string(), owner_type: OwnerTypeEnum::User, resource_id: "my-resource".to_string(), resource_type: ResourceTypeEnum::AuditLog, permission_types: vec![PermissionTypeEnum::Access], }, )), }) .await?; println!("Update authorization: done"); Ok(()) } ``` ### update_global_cluster_variable ```rust pub async fn update_global_cluster_variable(&self, params: UpdateGlobalClusterVariableParams) -> Result ``` Update a global-scoped cluster variable (`PUT /cluster-variables/global/{name}`). **Example** ```rust async fn update_global_cluster_variable(name: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .update_global_cluster_variable(UpdateGlobalClusterVariableParams { name, update_cluster_variable_request: UpdateClusterVariableRequest { value: Some(serde_json::json!({"key": "value"})), ..Default::default() }, }) .await?; println!("{}", result.name); Ok(()) } ``` ### update_global_task_listener ```rust pub async fn update_global_task_listener(&self, params: UpdateGlobalTaskListenerParams) -> Result ``` Update global user task listener (`PUT /global-task-listeners/{id}`). **Example** ```rust async fn update_global_task_listener(id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .update_global_task_listener(UpdateGlobalTaskListenerParams { id, update_global_task_listener_request: UpdateGlobalTaskListenerRequest { r#type: "my-type".to_string(), event_types: vec![GlobalTaskListenerEventTypeEnum::All], ..Default::default() }, }) .await?; println!("{}", result.id); Ok(()) } ``` ### update_group ```rust pub async fn update_group(&self, params: UpdateGroupParams) -> Result ``` Update group (`PUT /groups/{groupId}`). **Example** ```rust async fn update_group(group_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .update_group(UpdateGroupParams { group_id, group_update_request: GroupUpdateRequest { name: "my-variable".to_string(), ..Default::default() }, }) .await?; println!("{}", result.group_id); Ok(()) } ``` ### update_job ```rust pub async fn update_job(&self, params: UpdateJobParams) -> Result<()> ``` Update job (`PATCH /jobs/{jobKey}`). **Example** ```rust async fn update_job(job_key: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .update_job(UpdateJobParams { job_key, job_update_request: JobUpdateRequest { changeset: Box::new(JobChangeset::default()), ..Default::default() }, }) .await?; println!("Update job: done"); Ok(()) } ``` ### update_jobs_batch_operation ```rust pub async fn update_jobs_batch_operation(&self, params: UpdateJobsBatchOperationParams) -> Result ``` Update jobs (batch) (`POST /jobs/batch-update`). **Example** ```rust async fn update_jobs_batch_operation() -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .update_jobs_batch_operation(UpdateJobsBatchOperationParams { job_batch_update_request: JobBatchUpdateRequest { filter: Box::new(JobFilter::default()), changeset: Box::new(JobChangeset::default()), ..Default::default() }, }) .await?; println!("{}", result.batch_operation_key); Ok(()) } ``` ### update_mapping_rule ```rust pub async fn update_mapping_rule(&self, params: UpdateMappingRuleParams) -> Result ``` Update mapping rule (`PUT /mapping-rules/{mappingRuleId}`). **Example** ```rust async fn update_mapping_rule(mapping_rule_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .update_mapping_rule(UpdateMappingRuleParams { mapping_rule_id, mapping_rule_update_request: Some(MappingRuleUpdateRequest { claim_name: "my-claim-name".to_string(), claim_value: "my-claim-value".to_string(), name: "my-variable".to_string(), }), }) .await?; println!("{}", result.mapping_rule_id); Ok(()) } ``` ### update_role ```rust pub async fn update_role(&self, params: UpdateRoleParams) -> Result ``` Update role (`PUT /roles/{roleId}`). **Example** ```rust async fn update_role(role_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .update_role(UpdateRoleParams { role_id, role_update_request: RoleUpdateRequest { name: "my-variable".to_string(), ..Default::default() }, }) .await?; println!("{}", result.role_id); Ok(()) } ``` ### update_tenant ```rust pub async fn update_tenant(&self, params: UpdateTenantParams) -> Result ``` Update tenant (`PUT /tenants/{tenantId}`). **Example** ```rust async fn update_tenant(tenant_id: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .update_tenant(UpdateTenantParams { tenant_id, tenant_update_request: TenantUpdateRequest { name: "my-variable".to_string(), ..Default::default() }, }) .await?; println!("{}", result.name); Ok(()) } ``` ### update_tenant_cluster_variable ```rust pub async fn update_tenant_cluster_variable(&self, params: UpdateTenantClusterVariableParams) -> Result ``` Update a tenant-scoped cluster variable (`PUT /cluster-variables/tenants/{tenantId}/{name}`). **Example** ```rust async fn update_tenant_cluster_variable( tenant_id: String, name: String, ) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .update_tenant_cluster_variable(UpdateTenantClusterVariableParams { tenant_id, name, update_cluster_variable_request: UpdateClusterVariableRequest { value: Some(serde_json::json!({"key": "value"})), ..Default::default() }, }) .await?; println!("{}", result.name); Ok(()) } ``` ### update_user ```rust pub async fn update_user(&self, params: UpdateUserParams) -> Result ``` Update user (`PUT /users/{username}`). **Example** ```rust async fn update_user(username: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; let result = client .update_user(UpdateUserParams { username, user_update_request: UserUpdateRequest::default(), }) .await?; println!("{}", result.username); Ok(()) } ``` ### update_user_task ```rust pub async fn update_user_task(&self, params: UpdateUserTaskParams) -> Result<()> ``` Update user task (`PATCH /user-tasks/{userTaskKey}`). **Example** ```rust async fn update_user_task(user_task_key: String) -> Result<(), Box> { let client = CamundaClient::from_env()?; client .update_user_task(UpdateUserTaskParams { user_task_key, user_task_update_request: Some(UserTaskUpdateRequest::default()), }) .await?; println!("Update user task: done"); Ok(()) } ``` ### worker_config ```rust pub fn worker_config(&self, job_type: impl Into) -> JobWorkerConfig ``` Build a `JobWorkerConfig` for `job_type` pre-seeded from the SDK's resolved worker defaults (env-driven: `CAMUNDA_WORKER_*`). Builder methods override fields. ## CamundaOptions Options for constructing a `CamundaClient`. Non-exhaustive: construct with `CamundaOptions::new` and the `with_*` builders, so that later additions here are not breaking changes for callers. ### Fields | Field | Type | Description | | ------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `config` | `HashMap` | Programmatic overrides for `CAMUNDA_*` configuration keys. These take precedence over environment variables. | | `http_client` | `Option` | A pre-built `reqwest::Client` to use for all requests (including OAuth token fetches). When `None`, a default client is created. | | `clock` | `Option>` | The clock the client's cadence resolves through. When `None`, real time is used. | | `random` | `Option>` | The source jitter is drawn from. When `None`, `LiveRandom` is used. | ### Methods | Method | Description | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | | `new` | Create empty options. | | `with` | Add a single `CAMUNDA_*` configuration override. | | `with_clock` | Resolve the client's cadence through `clock` instead of real time. | | `with_http_client` | Use a custom `reqwest::Client`. | | `with_random` | Draw jitter (retry backoff, worker startup delay, FALCON endpoint selection) from `random`. Pass a `SeededRandom` to make it reproducible. | --- ## Configuration(5) Configuration is resolved from explicit options first, then environment variables, then built-in defaults. ## AuthStrategy Authentication strategy. ### Variants | Variant | Payload | Description | | ------- | ------- | ----------------------------------- | | `OAuth` | — | OAuth 2.0 client-credentials grant. | | `Basic` | — | HTTP Basic authentication. | | `None` | — | No authentication. | ## Authentication Resolves and applies authentication to outgoing requests. Cloning an `Authentication` shares the same underlying OAuth token cache. ### Methods | Method | Description | | ------------- | ---------------------------------------------------------------------------------------------------- | | `apply` | Apply authentication to a generated-client `Configuration`, refreshing the OAuth token if necessary. | | `from_config` | Build an `Authentication` from resolved configuration. | | `strategy` | The configured strategy. | ## CamundaConfig Resolved SDK configuration. ### Fields | Field | Type | Description | | -------------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `rest_address` | `String` | Base REST address of the Orchestration Cluster, including the `/v2` suffix (unless `CAMUNDA_REST_ADDRESS_EXACT` is truthy, in which case the configured address is used as-is, with only trailing slashes trimmed — no `/v2` appended). | | `rest_address_exact` | `bool` | Whether `CAMUNDA_REST_ADDRESS_EXACT` opted the REST address out of the `/v2` suffix. Threaded into Falcon command-stream URL construction so an exact base path is reduced to the bare origin — even when that path itself ends in `/v2`. | | `auth_strategy` | `AuthStrategy` | Authentication strategy. | | `client_id` | `Option` | OAuth 2.0 client id (client-credentials grant). | | `client_secret` | `Option` | OAuth 2.0 client secret. | | `oauth_url` | `Option` | OAuth 2.0 token endpoint URL. | | `token_audience` | `Option` | OAuth token audience (sent as the `audience` form parameter). | | `oauth_scope` | `Option` | OAuth scope (optional). | | `oauth_cache_dir` | `Option` | Directory for the on-disk OAuth token cache (`CAMUNDA_OAUTH_CACHE_DIR`). When set, fetched tokens are persisted so they survive process restarts. | | `basic_auth_username` | `Option` | Basic-auth username. | | `basic_auth_password` | `Option` | Basic-auth password. | | `default_tenant_id` | `Option` | Default tenant id applied to operations that accept one. | | `backpressure_profile` | `BackpressureProfile` | Adaptive backpressure profile (`CAMUNDA_SDK_BACKPRESSURE_PROFILE`). | | `log_level` | `LogLevel` | SDK log level (`CAMUNDA_SDK_LOG_LEVEL`). | | `eventual_poll_default_ms` | `u64` | Default per-operation timeout, in milliseconds, for eventual-consistency polling helpers (`CAMUNDA_SDK_EVENTUAL_POLL_DEFAULT_MS`). | | `retry` | `RetryConfig` | Transient-error HTTP retry policy. | | `tls` | `TlsConfig` | TLS / mutual-TLS configuration. | | `worker_defaults` | `WorkerDefaults` | Default job-worker settings sourced from `CAMUNDA_WORKER_*`. | | `falcon` | `bool` | Whether to upgrade to the nanobpmn command-stream transport when the gateway advertises it (`CAMUNDA_FALCON`, default on). When off, the SDK stays on pure REST even against a nanobpmn gateway. | ### Methods | Method | Description | | ------------------------- | ------------------------------------------------------------------------------------- | | `from_env` | Resolve configuration from process environment variables. | | `from_env_with_overrides` | Resolve configuration from environment variables, with `overrides` taking precedence. | ## ConsistencyOptions Options controlling an eventual-consistency poll. ### Fields | Field | Type | Description | | ----------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | | `timeout` | `Option` | Maximum time to keep polling before giving up. `None` uses the SDK default (`CAMUNDA_SDK_EVENTUAL_POLL_DEFAULT_MS`). | | `interval` | `Duration` | Delay between polling attempts. | | `retry_not_found` | `bool` | Treat a `404` (not-found) result as "not yet consistent" and keep polling, rather than failing. Useful right after creating an entity. | ### Methods | Method | Description | | ----------------- | ---------------------------------------------------------------- | | `interval` | Set the delay between polling attempts. | | `retry_not_found` | Whether to treat `404` as "not yet consistent" (default `true`). | | `timeout` | Set an explicit overall timeout. | ## LogLevel SDK log level, controlling the verbosity of the SDK's structured logging. ### Variants | Variant | Payload | Description | | ------- | ------- | ------------------------- | | `Off` | — | Suppress all SDK logging. | | `Error` | — | | | `Warn` | — | | | `Info` | — | | | `Debug` | — | | | `Trace` | — | | ### Methods | Method | Description | | ------------ | ------------------------------------------------------------- | | `to_tracing` | The matching `tracing::Level`, or `None` when logging is off. | ## RetryConfig Transient-error HTTP retry policy (`CAMUNDA_SDK_HTTP_RETRY_*`). Initiating operations that fail with a retryable signal (HTTP 429/503, or a network error) are retried with exponential backoff and full jitter. ### Fields | Field | Type | Description | | --------------- | ----- | ------------------------------------------------ | | `max_attempts` | `u32` | Maximum number of attempts (1 disables retries). | | `base_delay_ms` | `u64` | Base backoff delay, in milliseconds. | | `max_delay_ms` | `u64` | Maximum backoff delay, in milliseconds. | ## TlsConfig TLS / mutual-TLS configuration (`CAMUNDA_MTLS_*`). A client certificate (`cert` + `key`) enables mutual TLS; a `ca` enables trusting a private certificate authority. Inline PEM values take precedence over the `*_path` file locations. ### Fields | Field | Type | Description | | ---------------- | ---------------- | ----------------------------------------------------------------------- | | `cert` | `Option` | Inline client-certificate PEM (`CAMUNDA_MTLS_CERT`). | | `key` | `Option` | Inline client-key PEM (`CAMUNDA_MTLS_KEY`). | | `ca` | `Option` | Inline CA-certificate PEM (`CAMUNDA_MTLS_CA`). | | `cert_path` | `Option` | Path to a client-certificate PEM (`CAMUNDA_MTLS_CERT_PATH`). | | `key_path` | `Option` | Path to a client-key PEM (`CAMUNDA_MTLS_KEY_PATH`). | | `ca_path` | `Option` | Path to a CA-certificate PEM (`CAMUNDA_MTLS_CA_PATH`). | | `key_passphrase` | `Option` | Passphrase for an encrypted client key (`CAMUNDA_MTLS_KEY_PASSPHRASE`). | ### Methods | Method | Description | | --------------- | --------------------------------------------- | | `is_configured` | Whether any TLS material has been configured. | ## WorkerDefaults Default job-worker settings sourced from `CAMUNDA_WORKER_*`. Used to seed `JobWorkerConfig` defaults. ### Fields | Field | Type | Description | | ---------------------------- | -------- | --------------------------------------------------------------------------------------- | | `timeout_ms` | `i64` | Job activation timeout, in milliseconds (`CAMUNDA_WORKER_TIMEOUT`). | | `max_concurrent_jobs` | `i32` | Maximum concurrent jobs per worker (`CAMUNDA_WORKER_MAX_CONCURRENT_JOBS`). | | `request_timeout_ms` | `i64` | Long-poll request timeout, in milliseconds (`CAMUNDA_WORKER_REQUEST_TIMEOUT`). | | `name` | `String` | Worker name reported to the engine (`CAMUNDA_WORKER_NAME`). | | `startup_jitter_max_seconds` | `u64` | Maximum random startup delay, in seconds (`CAMUNDA_WORKER_STARTUP_JITTER_MAX_SECONDS`). | --- ## Domain keys(Api-reference) The Camunda Domain Type System replaces the bare `String` identifiers emitted by the OpenAPI generator with validated newtypes. Passing a `ProcessInstanceKey` where a `JobKey` is expected is a compile error, so whole classes of identifier mix-ups are caught before the request is sent. Every key type exposes the same methods: | Method | Description | | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | | `assume_exists(value: impl Into) -> Self` | Construct without validation. Use when side-loading values not received from an API call. | | `into_value(self) -> String` | Consume the newtype and return the underlying string value. | | `is_valid(value: &str) -> bool` | Returns true if the value satisfies this type's constraints. | | `try_new(value: impl Into) -> Result` | Construct with constraint validation. | | `value(&self) -> &str` | The underlying string value. | ## Key types | Key type | Description | | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `AgentDefinitionKey` | Identifier for an agent definition. | | `AgentHistoryItemKey` | Identifier for an agent history item. | | `AgentInstanceKey` | Identifier for an agent instance. | | `AuditLogEntityKey` | System-generated entity key for an audit log entry. | | `AuditLogKey` | Identifier for an audit log. | | `AuthorizationKey` | Identifier for an authorization. | | `BatchOperationKey` | System-generated key for an batch operation. | | `BusinessId` | An optional, user-defined string identifier that identifies the process instance within the scope of a process definition (scoped by tenant). If provided and uniqueness enforcement is enabled, the engine will reject creation if another root process instance with the same business id is already active for the same process definition. Note that any active child process instances with the same business id are not taken into account. | | `CamundaKey` | Shared behaviour for every Camunda Domain Type (semantic key newtype). | | `CamundaKeyError` | Error returned when a value does not satisfy a Camunda Domain Type's constraints. | | `ClientId` | Validated identifier newtype. | | `ClusterVariableName` | Validated identifier newtype. | | `ConditionalEvaluationKey` | Identifier for a conditional evaluation. | | `DecisionDefinitionId` | Validated identifier newtype. | | `DecisionDefinitionKey` | Identifier for a decision definition. | | `DecisionEvaluationInstanceKey` | Identifier for a decision evaluation instance. | | `DecisionEvaluationKey` | Identifier for a decision evaluation. | | `DecisionInstanceKey` | Identifier for a decision instance. | | `DecisionRequirementsKey` | Identifier for a decision requirements. | | `DeploymentKey` | Identifier for a deployment. | | `DocumentId` | Document Id that uniquely identifies a document. | | `ElementId` | The model-defined id of an element. | | `ElementInstanceKey` | Identifier for an element instance. | | `EndCursor` | Validated identifier newtype. | | `FormId` | The user-defined id for the form | | `FormKey` | Identifier for a form. | | `GlobalListenerId` | Validated identifier newtype. | | `GroupId` | The unique identifier of a group. | | `HistoryItemId` | The client-supplied identifier this item was created with. | | `IncidentKey` | Identifier for an incident. | | `JobKey` | Identifier for a job. | | `JobLeaseToken` | An opaque, engine-minted fencing token identifying a single activation of a job. Returned by Activate Jobs as `ActivatedJobResult.jobLeaseToken` when the job is activated with a lease, and passed back under the same name on fenced job commands and on agent-instance creation/updates, to prove the caller holds the current lease. The token is opaque: clients may rely on its presence and equality only, and must never construct, parse, or otherwise interpret it beyond equality checks. It cannot be minted client-side; only the engine produces it, exactly once per leased activation, and clients must not depend on any particular internal format. | | `MappingRuleId` | Validated identifier newtype. | | `MessageKey` | Identifier for a message. | | `MessageSubscriptionKey` | Identifier for a message subscription. | | `ProcessDefinitionId` | Validated identifier newtype. | | `ProcessDefinitionKey` | Identifier for a process definition. | | `ProcessInstanceKey` | Identifier for a process instance. | | `RoleId` | Validated identifier newtype. | | `ScopeKey` | System-generated key for a scope. A scope can hold variables and represents either an element instance in a BPMN process or the process instance itself. | | `SignalKey` | Identifier for a signal. | | `StartCursor` | Validated identifier newtype. | | `Tag` | Validated identifier newtype. | | `TenantId` | Validated identifier newtype. | | `UserTaskKey` | Identifier for an user task. | | `Username` | Validated identifier newtype. | | `VariableKey` | Identifier for a variable. | Each key also has a matching `ExactMatch` wrapper used by the search filter models to express an exact-value match. --- ## API reference(Api-reference) This reference covers the hand-written ergonomic surface of the Rust SDK: the client, its configuration, the job worker, and the error and backpressure types. | Page | Contents | | ---------------------------------- | ------------------------------------------------------------ | | [CamundaClient](camunda-client.md) | The client and its 262 API methods. | | [Configuration](configuration.md) | Client configuration, authentication, TLS, and retry policy. | | [Job workers](job-workers.md) | Job worker configuration, handlers, and lifecycle. | | [Runtime](runtime.md) | Error types and adaptive backpressure. | | [Domain keys](domain-keys.md) | 47 validated identifier newtypes. | The generated request and response models are not reproduced here — there are several hundred of them. Browse them on [docs.rs](https://docs.rs/camunda-orchestration-api-client/latest/camunda_orchestration_api_client/models/index.html), or use your editor's go-to-definition on any method signature. --- ## Job workers(3) Job workers poll for jobs of a given type, run a handler, and report the outcome back to the cluster. ## Job A job delivered to a worker handler. Wraps the activated job with convenient accessors for variables and custom headers. ### Methods | Method | Description | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `clock` | The clock this job's worker resolves cadence through. Handlers that need to wait must use this rather than `tokio::time::sleep`, so an injected clock controls them. | | `custom_headers` | The job custom headers. | | `element_id` | The BPMN element id that created this job. | | `job_type` | The job type. | | `key` | The job key, as a string. | | `lease_token` | The lease token for this job, or `None` if it was not activated with a lease. | | `process_instance_key` | The process instance key, as a string. | | `raw` | The underlying generated activated-job model. | | `retries` | Remaining retries for this job. | | `variables` | The job variables as a JSON map. | | `variables_as` | Deserialize the job variables into a typed value. | ## JobAction The action a handler asks the worker to take after processing a job. ### Variants | Variant | Payload | Description | | ---------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | | `Complete` | `{ variables: Option }` | Complete the job, optionally with output variables. | | `Fail` | `{ error_message: String, retries: Option, retry_backoff_ms: Option, variables: Option }` | Fail the job, decrementing retries (unless `retries` is set explicitly). | | `Error` | `{ error_code: String, error_message: Option, variables: Option }` | Throw a BPMN error to be caught by an error boundary event. | | `Leave` | — | Take no action; the job remains activated until its timeout elapses. | ### Methods | Method | Description | | --------------- | --------------------------------------------------------------------------- | | `complete` | Complete the job with no output variables. | | `complete_with` | Complete the job with output variables. | | `error` | Throw a BPMN error with the given error code. | | `fail` | Fail the job with an error message (retries are decremented by the engine). | | `leave` | Leave the job activated (take no action). | ## JobHandler Boxed, shareable job handler. You normally pass a closure to `JobWorker::run` rather than constructing this directly. ```rust pub type JobHandler = Arc Pin + Send>> + Send + Sync>; ``` ## JobWorker A continuously-polling job worker. Build one via `CamundaClient::create_job_worker`. ### Methods | Method | Description | | ------- | --------------------------------------------------------------------------------------------------- | | `run` | Run the worker loop, processing jobs with `handler` until stopped or an unrecoverable error occurs. | | `spawn` | Spawn the worker loop and return a `JobWorkerHandle` for graceful shutdown. | | `start` | Spawn the worker loop on the Tokio runtime, returning a `tokio::task::JoinHandle`. | ## JobWorkerConfig Configuration for a `JobWorker`. Build with `JobWorkerConfig::new` and the builder methods rather than a struct literal: the type is `#[non_exhaustive]`, so new fields are added without breaking callers. ### Fields | Field | Type | Description | | ---------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `job_type` | `String` | The job type to poll for (required). | | `max_jobs_to_activate` | `i32` | Maximum number of jobs to activate per poll. Also bounds in-flight concurrency. | | `job_timeout_ms` | `i64` | How long the engine reserves an activated job for this worker, in milliseconds. | | `request_timeout_ms` | `i64` | Long-poll timeout for the activate-jobs request, in milliseconds. | | `poll_interval_ms` | `u64` | Delay between polls when the last poll returned no jobs, in milliseconds. | | `worker_name` | `String` | Worker name reported to the engine. | | `fetch_variables` | `Option>` | Variable names to fetch with each job. `None` fetches all variables. | | `tenant_ids` | `Option>` | Tenant ids to activate jobs for. | | `startup_jitter_max_seconds` | `u64` | Maximum random startup delay before the first poll, in seconds. Spreads the initial activate-jobs stampede when many workers start at once. | | `with_lease` | `bool` | Activate jobs with a lease. Each job then carries a lease token that the worker sends back on complete, fail, and throw-error, so the engine can fence the command against a superseded activation (for example after the job timed out and another worker picked it up). Off by default, matching the engine. | | `on_ready` | `Option` | Optional callback fired once when the worker becomes ready to receive jobs (Falcon subscription established, or REST poll loop entered). Set via `JobWorkerConfig::on_ready`. Excluded from `Debug` output. | ### Methods | Method | Description | | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `fetch_variables` | Restrict fetched variables to the given names. | | `from_defaults` | Create a config seeded from the SDK's resolved `WorkerDefaults` (env-driven), for the given job type. Builder methods can still override individual fields. | | `job_timeout_ms` | Set the job activation timeout, in milliseconds. | | `max_jobs_to_activate` | Set the maximum number of jobs activated per poll. | | `new` | Create a config for the given job type with sensible defaults. | | `on_ready` | Register a callback fired once when this worker becomes ready to receive jobs (its Falcon subscription is established, or it has entered the REST poll loop). | | `startup_jitter_max_seconds` | Set the maximum random startup delay (seconds) applied before the first poll. | | `tenant_ids` | Set the tenant ids to activate jobs for. | | `with_lease` | Activate jobs with a lease (off by default). Each job then carries a lease token the worker sends back on complete, fail, and throw-error, so the engine can fence the command against a superseded activation. Requires a server that supports job leases; see `JobWorkerConfig::with_lease` for the fail-loud behaviour against one that does not. | | `worker_name` | Set the worker name. | ## JobWorkerHandle A handle to a spawned `JobWorker`, used to stop it and await its completion. Dropping the handle does **not** stop the worker; call `JobWorkerHandle::stop` (or `CamundaClient::stop_all_workers`) for a graceful shutdown that lets in-flight jobs drain. ### Methods | Method | Description | | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `is_finished` | Whether the worker task has finished. | | `job_type` | The job type this worker polls for. | | `shutdown` | Signal the worker to stop and await its graceful shutdown. | | `stop` | Signal the worker to stop. It finishes draining any in-flight jobs from the current batch, then exits before the next poll. Non-blocking. | | `worker_name` | The worker name reported to the engine. | ## ReadyCallback Callback invoked once, when a worker becomes ready to receive jobs — i.e. its Falcon command-stream subscription has been established, or (when Falcon is unavailable) its REST poll loop has been entered. Useful for readiness gates and probes; the SDK guarantees it fires at most once per worker run. ```rust pub type ReadyCallback = Arc; ``` --- ## Runtime(4) Error types returned by every SDK call, and the adaptive backpressure manager that paces requests when the cluster pushes back. ## BackpressureManager Adaptive backpressure manager shared (via `Arc`) by an SDK client and all its clones. Initiating operations call `acquire` before issuing a request and `release` afterwards, recording the outcome with `record_healthy_hint` on success or `record_backpressure` on a backpressure signal. Drain operations (job completion / failure) bypass the gate entirely. ### Methods | Method | Description | | --------------------- | ------------------------------------------------------------------------------- | | `acquire` | Acquire a permit, awaiting until one is available. | | `new` | Create a manager for the given profile, resolving its cadence through `clock`. | | `record_backpressure` | Record a backpressure signal from the server. | | `record_healthy_hint` | Record a successful (non-backpressure) completion, triggering passive recovery. | | `release` | Release a permit and wake one waiter. In the `Legacy` profile this is a no-op. | | `severity` | The current severity level. | | `state` | A snapshot of the manager's internal state, for observability. | ## BackpressureProfile Backpressure tuning profile, configured via `CAMUNDA_SDK_BACKPRESSURE_PROFILE`. ### Variants | Variant | Payload | Description | | ---------- | ------- | ----------------------------------------------------- | | `Balanced` | — | Adaptive global concurrency control (default). | | `Legacy` | — | Observe-only: record signals but never gate requests. | ## BackpressureSeverity Backpressure severity level reported by the manager. ### Variants | Variant | Payload | Description | | --------- | ------- | ---------------------------------------------- | | `Healthy` | — | No recent backpressure. | | `Soft` | — | Backpressure observed; mild throttling. | | `Severe` | — | Sustained backpressure; aggressive throttling. | ## BackpressureState A point-in-time snapshot of the manager's internal state, for observability. ### Fields | Field | Type | Description | | ----------------- | ---------------------- | -------------------------------------------------------------- | | `severity` | `BackpressureSeverity` | Current severity level. | | `consecutive` | `u32` | Consecutive backpressure signals since the last healthy decay. | | `permits_max` | `Option` | Current permit cap, or `None` when unlimited. | | `permits_current` | `u32` | Permits currently held. | | `waiters` | `u32` | Number of callers queued waiting for a permit. | | `backoff_ms` | `u64` | Current backoff-at-floor delay in milliseconds. | ## CamundaError Errors returned by the Camunda SDK. Non-exhaustive: match with a wildcard arm. New variants are added as the SDK grows and are not a breaking change. ### Variants | Variant | Payload | Description | | ---------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `Config` | `(String)` | Configuration was invalid or incomplete (e.g. missing OAuth credentials). | | `Auth` | `(String)` | Failed to obtain or refresh an authentication token. | | `Network` | `(Error)` | A network-level failure occurred (connection, TLS, timeout). | | `Io` | `(Error)` | An I/O failure occurred (e.g. while streaming a multipart upload). | | `Api` | `{ status: u16, body: Option }` | The server returned a non-success HTTP status. | | `Serialization` | `(Error)` | A response payload could not be (de)serialized. | | `Validation` | `(String)` | A domain-type or input constraint was violated before sending the request. | | `Backpressure` | `(String)` | The client-side backpressure controller rejected the request to avoid unbounded memory growth (waiter queue at capacity). | | `EventualConsistencyTimeout` | `{ elapsed_ms: u64 }` | An eventual-consistency polling helper timed out before its predicate was met. | | `LeaseNotHonored` | `{ job_key: String, request_flag: &'static str }` | A worker activated jobs with a lease but the server returned a job carrying no lease token. | ### Methods | Method | Description | | -------- | ------------------------------------------------------------- | | `status` | The HTTP status code, if this is a `CamundaError::Api` error. | ## Clock Time and waiting, as the SDK runtime sees them. Implementations must be cheap to clone behind an `Arc` and safe to share across tasks and threads. ### Methods | Method | Description | | ---------- | ---------------------------------------------------------------- | | `now` | A monotonic reading, for deadlines and elapsed-time measurement. | | `now_wall` | A wall-clock reading, for state that outlives the process. | | `sleep` | Wait for `duration`. | ## ClockController The engine-side clock an `EngineClock` drives. Implemented for `CamundaClient` in terms of the `PUT /clock` and `POST /clock/reset` endpoints. It exists as a trait so the pin semantics can be tested without a running engine. ### Methods | Method | Description | | ------- | ------------------------------------------------------------------- | | `pin` | Pin the engine clock to an absolute instant, in epoch milliseconds. | | `reset` | Return the engine clock to real time. | ## EngineClock A clock bound to the engine's own clock. A wait does not pass time locally -- it moves the _engine_ forward and reports the new instant. Process instances, timers and the SDK therefore agree on what time it is, which a purely local test clock cannot achieve. ```rust let control = CamundaClient::from_env()?; let clock: Arc = Arc::new(EngineClock::new(Arc::new(control))); // Anything the SDK waits on now advances the engine instead of real time. let client = CamundaClient::new(CamundaOptions::new().with_clock(clock))?; ``` A wait resolves against an instant read before the engine is contacted, so waits that overlap -- those that read the clock before any of them lands -- settle at a single instant instead of summing. A wait that begins after an earlier one has landed reads the new time and composes from it, which is the intended behaviour: it really did start later. ### Methods | Method | Description | | ----------- | ---------------------------------------------------------------------------------------------------------------------------- | | `is_pinned` | Whether the engine clock is currently pinned by this clock. | | `new` | Bind to an engine. The clock starts unpinned, following real time until the first wait or `pin_to`. | | `pin_to` | Move the engine clock to an absolute instant, in epoch milliseconds. | | `reset` | Return the engine to real time. Readings follow live time again afterwards, rather than freezing at the last pinned instant. | ## LiveClock Real time. `now` and `sleep` are both tokio's, so they share one timeline: under `#[tokio::test(start_paused = true)]` this clock is already virtual, and a poll loop settles without waiting. That covers cadence in tests; it does not bind the engine's clock, which is what an engine-bound implementation is for. ## LiveRandom The default source: a process-wide generator seeded once from OS entropy. Rust's standard library has no random-number API, so this seeds the same generator as `SeededRandom` from `std::hash::RandomState`, the only entropy std exposes. ## Random A source of uniformly distributed values in `[0, 1)`. Implementations must be safe to share across tasks and threads. ### Methods | Method | Description | | ---------- | ------------------------------------------ | | `next_f64` | A uniformly distributed value in `[0, 1)`. | ## Result Convenience alias used throughout the SDK. ```rust pub type Result = std::result::Result; ``` ## SeededRandom A deterministic `Random`. Two sources with the same seed produce the same sequence, so a test can assert the exact delays the SDK schedules and replay a failure. The generator is SplitMix64, taking the top 53 bits of each output. It is specified across the Camunda SDKs, so one seed yields the same sequence in every language. It is lock-free and safe to share; concurrent callers then share one sequence in nondeterministic order. ### Methods | Method | Description | | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `from_env` | Seeds from `CAMUNDA_TEST_SEED` when it is set, otherwise from a fresh seed. Either way the source reports its seed through `seed` and its `Display` form, so a failing test can be replayed. | | `new` | A source that replays the sequence for `seed`. | | `seed` | The seed this source was created with. | --- ## Authentication(Rust-sdk) :::caution Technical Preview The Rust SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: The strategy is chosen by `CAMUNDA_AUTH_STRATEGY` (or inferred from the presence of OAuth credentials): | Strategy | Required configuration | | -------- | --------------------------------------------------------------------------------------------------------------------------- | | `OAUTH` | `CAMUNDA_CLIENT_ID`, `CAMUNDA_CLIENT_SECRET`, `CAMUNDA_OAUTH_URL`, optional `CAMUNDA_TOKEN_AUDIENCE`, `CAMUNDA_OAUTH_SCOPE` | | `BASIC` | `CAMUNDA_BASIC_AUTH_USERNAME`, `CAMUNDA_BASIC_AUTH_PASSWORD` | | `NONE` | — (local development) | OAuth uses the client-credentials grant. Tokens are cached in memory and refreshed shortly before expiry. --- ## Backpressure(Rust-sdk) :::caution Technical Preview The Rust SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: The client includes an adaptive global backpressure controller, mirroring the JS/Python SDKs. Initiating operations (`create_process_instance`, `activate_jobs`, `deploy_resources`, `topology`) pass through an AIMD-style concurrency gate that reacts to cluster backpressure signals (HTTP `429` / `503` / `RESOURCE_EXHAUSTED`): - Starts **unlimited**; on the first backpressure signal it boots to an initial permit cap and shrinks multiplicatively (soft ×0.70, severe ×0.50 after 3 consecutive signals) down to a floor of 1, with an escalating 25 ms → 2 s backoff while stuck at the floor. - Recovers when quiet: additive growth while unhealthy, then multiplicative growth while healthy, returning to unlimited after a sustained-healthy period. - **Drain operations** (`complete_job`, `fail_job`, `throw_job_error`) bypass the gate so in-flight work always drains, even while new load is being shed. Set `CAMUNDA_SDK_BACKPRESSURE_PROFILE=LEGACY` to observe signals without gating. Inspect the live state via `CamundaClient::backpressure_state`. --- ## Clocks(Rust-sdk) :::caution Technical Preview The Rust SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: Every wait and every elapsed-time measurement in the runtime -- retry backoff, the backpressure gate, token refresh, job-worker polling, eventual-consistency polling -- resolves through an injected clock rather than ambient time. | Clock | Use | | ------------------------------------- | -------------------------------------------------------------------------- | | `LiveClock` | real time; the default when nothing is injected | | `#[tokio::test(start_paused = true)]` | tests; tokio virtualises its own timer, so cadence settles without waiting | | `EngineClock` | drives the Camunda engine's clock and the SDK's together | `start_paused` is the lighter-weight option, but it only virtualises _tokio's_ timer -- the engine carries on in real time. A test whose process only completes once a BPMN timer fires needs `EngineClock`. ## Waiting inside a handler A `Job` carries its worker's clock, so a handler that needs to wait can do it on the same clock as everything else: ```rust use camunda_orchestration_sdk::{CamundaClient, JobAction, JobWorkerConfig}; use std::time::Duration; let client = CamundaClient::from_env()?; client .create_job_worker(JobWorkerConfig::new("payment")) .run(|job| async move { // Short coordination only -- a business wait belongs in the process // as a BPMN timer event. job.clock().sleep(Duration::from_millis(500)).await; JobAction::complete() }) .await?; ``` Keep those waits short -- spacing a retry, letting a resource settle. **A long or business wait belongs in the process as a BPMN timer event, not in a handler.** A handler that sleeps for minutes holds a worker slot for the duration, risks the job timeout expiring underneath it, and hides the delay from the process model, where it would otherwise be visible and changeable without a redeploy. ## Driving the engine's clock `EngineClock` pins the engine's clock instead of passing time locally: ```rust use camunda_orchestration_sdk::{CamundaClient, CamundaOptions, Clock, EngineClock}; use std::sync::Arc; // The control client issues the pin requests, and keeps real time itself. let control = CamundaClient::from_env()?; let clock: Arc = Arc::new(EngineClock::new(Arc::new(control))); // Anything this client waits on now advances the engine instead of real time. let client = CamundaClient::new(CamundaOptions::new().with_clock(clock))?; ``` A wait now moves the engine forward and reports the new instant, so the SDK and the engine agree on what time it is. Overlapping waits settle at a single instant rather than summing: ten concurrent one-second waits advance the engine by one second, not ten. `pin_to` and `reset` are available directly for tests that need to move the engine without waiting. Clock pinning is an alpha engine endpoint, intended for tests rather than production clusters. Pass the control client the pin requests should travel on -- it keeps real time, so the requests themselves are unaffected by the pinning. ## Writing your own `Clock` is a public trait (`now`, `now_wall`, `sleep`); implement it and pass it to `CamundaOptions::with_clock`. `ClockController` is the engine-side half, if you want `EngineClock` to drive something other than a `CamundaClient`. Ambient time is banned in the runtime by `clippy.toml` -- `Instant::now`, `SystemTime::now`, `tokio::time::sleep` and friends -- so cadence cannot quietly drift back onto real time. --- ## Configuration reference(Rust-sdk) :::caution Technical Preview The Rust SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: | Variable | Purpose | | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `CAMUNDA_REST_ADDRESS` | Base cluster address. `/v2` is appended automatically. | | `CAMUNDA_REST_ADDRESS_EXACT` | When truthy (`true`/`1`/`yes`/`on`), use `CAMUNDA_REST_ADDRESS` as-is — no `/v2` suffix appended (only trailing slashes are trimmed). For gateway/reverse-proxy setups. | | `ZEEBE_REST_ADDRESS` | Alias for `CAMUNDA_REST_ADDRESS`. | | `CAMUNDA_AUTH_STRATEGY` | `OAUTH` \| `BASIC` \| `NONE`. | | `CAMUNDA_CLIENT_ID` / `CAMUNDA_CLIENT_SECRET` | OAuth client credentials. | | `CAMUNDA_OAUTH_URL` | OAuth token endpoint. | | `CAMUNDA_TOKEN_AUDIENCE` | OAuth `audience` parameter. | | `CAMUNDA_OAUTH_SCOPE` | OAuth `scope` parameter. | | `CAMUNDA_BASIC_AUTH_USERNAME` / `CAMUNDA_BASIC_AUTH_PASSWORD` | Basic-auth credentials. | | `CAMUNDA_DEFAULT_TENANT_ID` | Default tenant id (alias `CAMUNDA_TENANT_ID`). Injected into deploys, instance creation, messages, signals, decisions, and worker activation when none is set. | | `CAMUNDA_SDK_BACKPRESSURE_PROFILE` | Adaptive backpressure profile: `BALANCED` (default, adaptive gating) or `LEGACY` (observe-only, no gating). | | `CAMUNDA_OAUTH_CACHE_DIR` | Directory for the cross-process OAuth token cache. Unset disables disk caching (in-memory only). | | `CAMUNDA_SDK_LOG_LEVEL` | SDK log level for `CamundaClient::init_logging`: `OFF` \| `ERROR` \| `WARN` \| `INFO` (default) \| `DEBUG` \| `TRACE`. | | `CAMUNDA_SDK_EVENTUAL_POLL_DEFAULT_MS` | Default timeout for `CamundaClient::eventual` consistency polling (default `10000`). | | `CAMUNDA_SDK_HTTP_RETRY_MAX_ATTEMPTS` | Max attempts for transient-error retry of initiating operations (default `4`; `1` disables retry). | | `CAMUNDA_SDK_HTTP_RETRY_BASE_DELAY_MS` / `CAMUNDA_SDK_HTTP_RETRY_MAX_DELAY_MS` | Full-jitter backoff bounds for HTTP retry (defaults `250` / `5000`). | | `CAMUNDA_MTLS_CERT` / `CAMUNDA_MTLS_CERT_PATH` | Client certificate (inline PEM or file path) for mutual TLS. | | `CAMUNDA_MTLS_KEY` / `CAMUNDA_MTLS_KEY_PATH` | Client private key (inline PEM or file path) for mutual TLS. | | `CAMUNDA_MTLS_CA` / `CAMUNDA_MTLS_CA_PATH` | Additional CA root (inline PEM or file path) to trust. | | `CAMUNDA_MTLS_KEY_PASSPHRASE` | Passphrase for an encrypted client key (not supported by the default `native-tls` backend; errors clearly if set). | | `CAMUNDA_WORKER_NAME` | Default worker name for `CamundaClient::worker_config`. | | `CAMUNDA_WORKER_MAX_CONCURRENT_JOBS` | Default max concurrent jobs per worker. | | `CAMUNDA_WORKER_TIMEOUT` / `CAMUNDA_WORKER_REQUEST_TIMEOUT` | Default job-activation and long-poll timeouts (ms). | | `CAMUNDA_WORKER_STARTUP_JITTER_MAX_SECONDS` | Max random startup delay before a worker's first poll, to spread activation stampedes. | --- ## Eventual consistency(Rust-sdk) :::caution Technical Preview The Rust SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: Camunda's read APIs are eventually consistent. Wrap a read in `eventual` to poll through replication lag, transparently retrying `404`s: ```rust use camunda_orchestration_sdk::ConsistencyOptions; // Reads are eventually consistent: poll until the instance is visible, retrying 404s. let instance = client .eventual(ConsistencyOptions::default(), || { let client = client.clone(); let key = process_instance_key.clone(); async move { client.get_process_instance(&key).await } }) .await?; ``` --- ## Installation(Rust-sdk) :::caution Technical Preview The Rust SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: Add the SDK from crates.io: ```bash cargo add camunda-orchestration-sdk cargo add tokio --features full ``` Or add it to `Cargo.toml` directly: ```toml [dependencies] camunda-orchestration-sdk = "11" tokio = { version = "1", features = ["full"] } ``` The async API requires a Tokio runtime. The low-level generated client is also published as [`camunda-orchestration-api-client`](https://crates.io/crates/camunda-orchestration-api-client), but most users only need the `camunda-orchestration-sdk` facade. --- ## Job workers(Rust-sdk) :::caution Technical Preview The Rust SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: ```rust use camunda_orchestration_sdk::{JobAction, JobWorkerConfig}; let worker = client.create_job_worker( JobWorkerConfig::new("payment-service") .max_jobs_to_activate(20) .worker_name("payment-worker"), ); worker .run(|job| async move { println!("handling job {}", job.key()); JobAction::complete_with(serde_json::json!({ "paid": true })) }) .await?; ``` A handler returns a `JobAction`: - `JobAction::complete()` / `JobAction::complete_with(vars)` — complete the job. - `JobAction::fail("message")` — fail the job (retries decremented by the engine). - `JobAction::error("ERROR_CODE")` — throw a catchable BPMN error. - `JobAction::leave()` — take no action; the job remains activated until timeout. The `Job` exposes `key()`, `job_type()`, `process_instance_key()`, `variables()`, and `variables_as::()` for typed deserialization. Enable **job leasing** with `JobWorkerConfig::new("...").with_lease(true)`. Each activated job then carries a lease token that the worker sends back on complete, fail, and throw-error, so the engine fences the command against a superseded activation (for example after the job timed out and another worker picked it up). Leasing is off by default and needs a server that supports it: rather than silently sending unfenced commands, a worker that asked for a lease and is handed a job without a token stops with `CamundaError::LeaseNotHonored`. For managed lifecycle, register workers on the client and stop them all gracefully: ```rust // Spawn managed workers; the client retains them in its registry. client.spawn_worker(client.worker_config("payment-service"), |job| async move { JobAction::complete_with(serde_json::json!({ "paid": true })) }); // ... later, on shutdown: drain in-flight jobs and stop every worker gracefully. client.stop_all_workers().await?; ``` > For complete, runnable programs see > [`examples/worker.rs`](https://github.com/camunda/orchestration-cluster-api-rust/blob/main/examples/worker.rs) > and > [`examples/deploy_start_and_work.rs`](https://github.com/camunda/orchestration-cluster-api-rust/blob/main/examples/deploy_start_and_work.rs). --- ## License(Rust-sdk) :::caution Technical Preview The Rust SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: This SDK is licensed under the [Apache License, Version 2.0](https://github.com/camunda/orchestration-cluster-api-rust/blob/main/LICENSE). The Camunda OpenAPI specification from which the low-level client is generated is subject to the [Camunda License Version 1.0](https://github.com/camunda/camunda/blob/main/licenses/CAMUNDA-LICENSE-1.0.txt). --- ## Quick start(Rust-sdk) :::caution Technical Preview The Rust SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: ```rust use camunda_orchestration_sdk::CamundaClient; // Reads CAMUNDA_REST_ADDRESS, CAMUNDA_AUTH_STRATEGY, CAMUNDA_CLIENT_ID, ... from the env. let client = CamundaClient::from_env()?; let topology = client.topology().await?; println!("Gateway version: {}", topology.gateway_version); ``` Programmatic configuration overrides take precedence over the environment: ```rust use camunda_orchestration_sdk::{CamundaClient, CamundaOptions}; let client = CamundaClient::new( CamundaOptions::new() .with("CAMUNDA_REST_ADDRESS", "https://my-cluster.camunda.io") .with("CAMUNDA_AUTH_STRATEGY", "OAUTH") .with("CAMUNDA_CLIENT_ID", "my-client-id") .with("CAMUNDA_CLIENT_SECRET", "my-secret") .with( "CAMUNDA_OAUTH_URL", "https://login.cloud.camunda.io/oauth/token", ) .with("CAMUNDA_TOKEN_AUDIENCE", "zeebe.camunda.io"), )?; ``` > For a complete, runnable program see > [`examples/topology.rs`](https://github.com/camunda/orchestration-cluster-api-rust/blob/main/examples/topology.rs). --- ## Reliability & convenience features :::caution Technical Preview The Rust SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: The runtime mirrors the JS/Python/C# SDKs: - **Transient HTTP retry** — initiating operations retry `429`/`502`/`503`/`504` and network errors with full-jitter backoff (`CAMUNDA_SDK_HTTP_RETRY_*`). Drain operations are never retried blindly. - **Eventual-consistency polling** — `client.eventual(opts, op)` / `eventual_until(opts, op, predicate)` retry `404` reads (the symptom of replication lag) until consistent or the window elapses. - **Mutual TLS** — client cert/key/CA from `CAMUNDA_MTLS_*` (inline PEM or file path). - **OAuth disk token cache** — set `CAMUNDA_OAUTH_CACHE_DIR` to share tokens across processes (atomic write, namespaced per client/audience). - **Default-tenant injection** — `CAMUNDA_DEFAULT_TENANT_ID` is applied automatically wherever a tenant is accepted. - **Configurable logging** — `client.init_logging()` installs a `tracing` subscriber filtered to `CAMUNDA_SDK_LOG_LEVEL`. - **Facade convenience methods** — `cancel_process_instance`, `get_process_instance`, `publish_message`, `correlate_message`, `broadcast_signal`, `evaluate_decision`, `search_variables` / `search_variables_as::()`. - **Worker lifecycle** — `client.spawn_worker(..)` registers managed workers; `running_workers()` lists them and `stop_all_workers().await` drains and stops them all gracefully. Per-worker control via the `JobWorkerHandle` returned from `JobWorker::spawn`. --- ## Reproducible jitter(Rust-sdk) :::caution Technical Preview The Rust SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: Retry backoff, worker startup delay (`CAMUNDA_WORKER_STARTUP_JITTER_MAX_SECONDS`) and FALCON endpoint selection draw from an injected `Random` rather than an ambient generator. The default, `LiveRandom`, is seeded once per process from OS entropy. A `SeededRandom` replays the same sequence for the same seed, so a test can assert the exact delays the SDK schedules instead of a range: ```rust use camunda_orchestration_sdk::{CamundaClient, CamundaOptions, SeededRandom}; use std::sync::Arc; // Seeds from CAMUNDA_TEST_SEED when it is set, otherwise from a fresh seed. let random = Arc::new(SeededRandom::from_env()?); // Log the source: it names the seed and how to replay this run. println!("{random}"); // SeededRandom(seed=...; replay with CAMUNDA_TEST_SEED=...) let client = CamundaClient::new(CamundaOptions::new().with_random(random))?; ``` The seeded generator is specified across the Camunda SDKs, so one seed produces the same draws in every language. A worker draws its startup delay when it is started (`run`, `start` or `spawn`), not when its task first runs, so workers started in sequence draw in that order however the runtime schedules them. `std::hash::RandomState`, the only entropy std exposes, is banned in the runtime by `clippy.toml` outside the `LiveRandom` adapter. --- ## Support status(Rust-sdk) :::caution Technical Preview The Rust SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: This is a **Technical Preview** of the Rust client for the Camunda 8 Orchestration Cluster API, provided for evaluation and feedback. It gives you a stable foundation to build on now, with a clear path to full support: we do not intend to make breaking changes to the application integration surface, but we do not guarantee that we will not. The SDK will become fully supported with an SLA in a future release. We don't anticipate major changes — and [your feedback](https://github.com/camunda/orchestration-cluster-api-rust/issues) is what helps close that gap. > As a Technical Preview, the API surface may still evolve before it is declared fully > supported. Pin a specific version if you need stability. --- ## The Camunda Domain Type System :::caution Technical Preview The Rust SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: Camunda's spec marks identifier schemas with `x-semantic-type` (e.g. `JobKey`, `ProcessInstanceKey`, and `*ExactMatch` filter variants). The SDK models each as a nominal newtype over its string value, serialized transparently: ```rust use camunda_orchestration_sdk::models::{CamundaKey, JobKey}; let key = JobKey::try_new("2251799813653498")?; // validates pattern & length assert_eq!(key.value(), "2251799813653498"); // Side-load without validation: let loose = JobKey::assume_exists("123"); ``` These types are **generated by post-processing**, because `openapi-generator` does not handle the semantic-key schemas correctly (it emits broken empty structs or drops them). --- ## The full API surface(Rust-sdk) :::caution Technical Preview The Rust SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: The ergonomic facade is **flat and complete**: every REST operation has a method directly on `CamundaClient` (mirroring the JavaScript, Python, and C# SDKs). Each method builds an authenticated request, runs under the adaptive backpressure gate and transient-retry policy, and maps failures to a typed `CamundaError`. Parameter types are re-exported under `camunda_orchestration_sdk::apis::_api`, so everything imports from one crate: ```rust // Every REST operation has a flat, ergonomic method on the client. Parameter types // are imported from `camunda_orchestration_sdk::apis::_api`. use camunda_orchestration_sdk::apis::process_instance_api::SearchProcessInstancesParams; let page = client .search_process_instances(SearchProcessInstancesParams { process_instance_search_query: None, }) .await?; println!("found {} process instance(s)", page.items.len()); ``` A curated set of hot-path operations (`topology`, `create_process_instance`, `deploy_resources`, job operations, messages, decisions, variables) have hand-written wrappers with extra ergonomics — semantic key arguments, typed variables, and default tenant injection. The remaining ~180 methods are **generated** from the operation list by a post-processing hook, so the facade tracks the upstream spec automatically. ## Raw client access If you ever need to drop below the facade, build a generated `Configuration` (base URL + auth applied) and call the generated API directly: ```rust use camunda_orchestration_sdk::client::apis::authentication_api; use camunda_orchestration_sdk::CamundaClient; let client = CamundaClient::from_env()?; let cfg = client.configuration().await?; // base URL + auth applied let me = authentication_api::get_authentication(&cfg).await?; ``` --- ## Versioning(Rust-sdk) :::caution Technical Preview The Rust SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: The SDK major version tracks the Camunda server minor version: use the SDK line that matches your server. | Camunda server | SDK version | Branch | | --------------------- | ----------- | ----------- | | 8.10 | `10.x` | `stable/10` | | 8.11 (in development) | `11.x` | `main` | Releases before 10.0.0 (`0.x`) predate this scheme. --- ## Rust SDK (Technical Preview) :::caution Technical Preview The Rust SDK is a **technical preview**. Its API surface may still evolve and changes may not follow semantic versioning. Pin an exact version if you need stability. ::: Ergonomic Rust SDK for the [Camunda 8 Orchestration Cluster REST API](../apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). This SDK follows the same architecture as the official [JavaScript](https://github.com/camunda/orchestration-cluster-api-js), [Python](https://github.com/camunda/orchestration-cluster-api-python), and [C#](https://github.com/camunda/orchestration-cluster-api-csharp) SDKs: - A **generated low-level client** (`camunda-orchestration-api-client`) produced from the upstream OpenAPI spec (sourced and bundled with [`camunda-schema-bundler`](https://github.com/camunda/camunda-schema-bundler)). - A **hand-written runtime** (`camunda-orchestration-sdk`) on top: environment-driven configuration, authentication, typed errors, and **job workers**. - The **Camunda Domain Type System**: semantic keys such as `JobKey` and `ProcessInstanceKey` are nominal newtypes with validation, not bare strings. Target API version: **8.11** (`main`). --- ## Migrate from the removed Tasklist API :::warning The Tasklist API was removed in Camunda 8.10 and is no longer part of the current documentation set. ::: For the release-level summary of this removal, see the [8.10 release announcement](/reference/announcements-release-notes/8100/8100-announcements.md#removal-of-legacy-apis-tasklist-v1-dependent-features-and-zeebe-process-test). Use the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) to build current task applications, and review [migrating to the Orchestration Cluster REST API](/apis-tools/migration-manuals/migrate-to-camunda-api.md) if you still have clients that call the removed Tasklist API. For current Tasklist behavior, see [Tasklist API changes](/components/tasklist/api-versions.md) and [user task authorization](/components/tasklist/user-task-authorization.md). --- ## Assertions The class `CamundaAssert` is the entry point for all assertions. It is based on [AssertJ](https://github.com/assertj/assertj) and [Awaitility](http://www.awaitility.org/). The assertions follow the style: `assertThat(object_to_test)` + expected property. Use the assertions by adding the following static import in your test class: ```java ``` :::info Assertions are blocking Camunda executes BPMN processes asynchronously. For testing, this means that there might be a delay between creating a process instance and reaching the expected state. The assertions handle the asynchronous behavior and wait until the expected property is fulfilled. Only if the property is not fulfilled within the given time, the assertion fails. ::: :::tip CPT provides the most common assertions. However, if you miss an assertion you can implement a [custom assertion](#custom-assertions) yourself. ::: ## Configuration You can configure the behavior of the assertions in the following ways. ### Assertion timeout By default, assertions wait 10 seconds for the expected property to be fulfilled and wait 100 milliseconds between two attempts. You can change these defaults globally in the configuration or per assertion. Configure the assertions globally in your `application.yml` (or `application.properties`): ```yaml camunda: process-test: assertion: # Set the assertion timeout to 1 minute timeout: PT1M # Set the assertion interval to 100 milliseconds interval: PT0.1S ``` Configure the assertions globally in your `/camunda-container-runtime.properties` file: ```properties # Set the assertion timeout to 1 minute assertion.timeout=PT1M # Set the assertion interval to 100 milliseconds assertion.interval=PT0.1S ``` Alternatively, you can configure the assertions within your test class using `CamundaAssert`. ```java @BeforeAll static void configureAssertions() { // Set the assertion timeout to 1 minute CamundaAssert.setAssertionTimeout(Duration.ofMinutes(1)); // Set the assertion interval to 100 milliseconds CamundaAssert.setAssertionInterval(Duration.ofMillis(100)); } ``` You can override the global timeout for an assertion using `withAssertionTimeout()`. The given timeout applies only to subsequent assertions in the calling chain. ```java assertThat(processInstance) .withAssertionTimeout(Duration.ofMinutes(1)) .isCompleted(); ``` ### Element selector By default, the element instance assertions identify the BPMN elements by their ID. You can change the [ElementSelector](utilities.md#element-selector) globally in your test class using `CamundaAssert`. ```java @BeforeAll static void configureAssertions() { // Identify the BPMN elements by their name CamundaAssert.setElementSelector(ElementSelectors::byName); } ``` ### Judge configuration Override the global [judge configuration](configuration.md#judge-configuration) for a single assertion chain using `withJudgeConfig`. ```java assertThat(processInstance) .withJudgeConfig(config -> config.withThreshold(0.9)) .hasVariableSatisfiesJudge("result", "Contains a valid JSON response with status OK."); ``` ### Semantic similarity configuration Override the global [semantic similarity configuration](configuration.md#semantic-similarity-configuration) for a single assertion chain using `withSemanticSimilarityConfig`. ```java assertThat(processInstance) .withSemanticSimilarityConfig(config -> config.withThreshold(0.9)) .hasVariableSimilarTo("greeting", "Hello, how can I help you today?"); ``` ## Process instance assertions You can verify the process instance state and other properties using `CamundaAssert.assertThat()` or `CamundaAssert.assertThatProcessInstance()`. Use the process instance creation event or a [ProcessInstanceSelector](utilities.md#process-instance-selector) to identify the process instance. ### With process instance event Use the creation event of the create instance command to identify the process instance: ```java // given/when ProcessInstanceEvent processInstance = client .newCreateInstanceCommand() .bpmnProcessId("my-process") .latestVersion() .send() .join(); // then assertThat(processInstance).isActive(); ``` ### With process instance result Use the result event of the create instance command to identify the process instance: ```java // given/when ProcessInstanceResult processInstance = client .newCreateInstanceCommand() .bpmnProcessId("my-process") .latestVersion() .withResult() .send() .join(); // then assertThat(processInstance).isActive(); ``` ### With process instance selector Use a [ProcessInstanceSelector](utilities.md#process-instance-selector) to identify the process instance. ```java // by process instance key assertThatProcessInstance(ProcessInstanceSelectors.byKey(processInstanceKey)).isActive(); // by process ID assertThatProcessInstance(ProcessInstanceSelectors.byProcessId("my-process")).isActive(); ``` ### isActive Assert that the process instance is active. The assertion fails if the process instance is completed, terminated, or not created. ```java assertThat(processInstance).isActive(); ``` ### isCompleted Assert that the process instance is completed. The assertion fails if the process instance is active, terminated, or not created. ```java assertThat(processInstance).isCompleted(); ``` ### isTerminated Assert that the process instance is terminated. The assertion fails if the process instance is active, completed, or not created. ```java assertThat(processInstance).isTerminated(); ``` ### isCreated Assert that the process instance is created and either active, completed, or terminated. The assertion fails if the process instance is not created. ```java assertThat(processInstance).isCreated(); ``` ### hasActiveIncidents Assert that the process instance has at least one active incident. The assertion fails if there is no active incident. ```java assertThat(processInstance).hasActiveIncidents(); ``` ### hasNoActiveIncidents Assert that the process instance has no active incidents. The assertion fails if there is any active incident. ```java assertThat(processInstance).hasNoActiveIncidents(); ``` ## Element instance assertions You can verify the element instance states and other properties using `CamundaAssert.assertThat(processInstance)`. Use the BPMN element ID or a [ElementSelector](utilities.md#element-selector) to identify the elements. ### With BPMN element ID Use the BPMN element ID to identify the elements: ```java assertThat(processInstance).hasActiveElements("task_A"); ``` You can customize how the elements are identified in the [configuration](#element-selector). ### With element selector Use a [ElementSelector](utilities.md#element-selector) to identify the elements: ```java // by BPMN element ID assertThat(processInstance).hasActiveElements(ElementSelectors.byId("task_A")); // by BPMN element name assertThat(processInstance).hasActiveElements(ElementSelectors.byName("A")); ``` ### hasActiveElements Assert that the given BPMN elements of the process instance are active. The assertion fails if at least one element is completed, terminated, or not entered. ```java assertThat(processInstance).hasActiveElements("task_A", "task_B"); ``` ### hasActiveElement Assert that the BPMN element of the process instance is active the given amount of times. The assertion fails if the element is not active or not exactly the given amount of times. ```java assertThat(processInstance).hasActiveElement("task_A", 2); ``` ### hasActiveElementsExactly Assert that only the given BPMN elements are active. The assertion fails if at least one element is not active, or other elements are active. ```java assertThat(processInstance).hasActiveElementsExactly("task_A", "task_B"); ``` ### hasNoActiveElements Assert that the given BPMN elements are not active. The assertion fails if at least one element is active. ```java assertThat(processInstance).hasNoActiveElements("task_A", "task_B"); ``` ### hasNotActivatedElements Assert that the given BPMN elements are not activated (i.e. not entered). The assertion fails if at least one element is active, completed, or terminated. This assertion does not wait for the given activities. ```java assertThat(processInstance).hasNotActivatedElements("task_A", "task_B"); ``` ### hasCompletedElements Assert that the given BPMN elements of the process instance are completed. The assertion fails if at least one element is active, terminated, or not entered. ```java assertThat(processInstance).hasCompletedElements("task_A", "task_B"); ``` ### hasCompletedElement Assert that the BPMN element of the process instance is completed the given amount of times. The assertion fails if the element is not completed or not exactly the given amount of times. ```java assertThat(processInstance).hasCompletedElement("task_A", 2); ``` ### hasCompletedElementsInOrder Assert that the given BPMN elements are completed in order. Elements that do not match any of the given element IDs are ignored. The assertion fails if at least one of the elements is not completed, or the order is not correct. ```java assertThat(processInstance).hasCompletedElementsInOrder("task_A", "task_B"); ``` ### hasTerminatedElements Assert that the given BPMN elements of the process instance are terminated. The assertion fails if at least one element is active, completed, or not entered. ```java assertThat(processInstance).hasTerminatedElements("task_A", "task_B"); ``` ### hasTerminatedElement Assert that the BPMN element of the process instance is terminated the given amount of times. The assertion fails if the element is not terminated or not exactly the given amount of times. ```java assertThat(processInstance).hasTerminatedElement("task_A", 2); ``` ## Variable assertions You can verify the process variables using `CamundaAssert.assertThat(processInstance)`. Use the variable name or a [VariableSelector](utilities.md#variable-selector) to identify the variable. ### With variable name Use the variable name to identify the variable: ```java assertThat(processInstance).hasVariable("approved", true); ``` ### With variable selector Use a [VariableSelector](utilities.md#variable-selector) to identify the variable: ```java // by variable name assertThat(processInstance).hasVariable(VariableSelectors.byName("approved"), true); // by partial variable value assertThat(processInstance).hasVariableSatisfies( VariableSelectors.byValueContains("order-123"), Order.class, order -> { }); ``` ### hasVariableNames Assert that the process instance has the given variables. The assertion fails if at least one variable doesn't exist. ```java assertThat(processInstance).hasVariableNames("var1", "var2"); ``` ### hasVariable Assert that the process instance has the variable with the given value. The assertion fails if the variable doesn't exist or has a different value. ```java assertThat(processInstance).hasVariable("var1", 100); ``` ### hasVariables Assert that the process instance has the given variables. The assertion fails if at least one variable doesn't exist or has a different value. ```java Map expectedVariables = // assertThat(processInstance).hasVariables(expectedVariables); ``` ### hasVariableSatisfies Assert that the process instance has a variable with a value that satisfies the given requirements. The assertion transforms the value into the given type. In the consumer, you can use [AssertJ](https://github.com/assertj/assertj) to verify the value. The assertion fails if the variable doesn't exist, the value is of a different type, or the value doesn't satisfy the requirements. ```java assertThat(processInstance).hasVariableSatisfies("order", Order.class, order -> { Assertions.assertThat(order.status()).isEqualTo("approved"); Assertions.assertThat(order.items()) .hasSize(3) .extracting("name", "quantity") .containsExactlyInAnyOrder( tuple("Helmet", 1), tuple("Flag", 1), tuple("Oxygen tank", 3) ); }); ``` ### hasVariableSatisfiesExpression Assert that the process instance has a variable with a value that satisfies the given FEEL expression. The expression is evaluated with a context containing the variable under its name. The expression should access the variable in a Boolean expression, for example, with comparisons. Learn more in the [FEEL expressions introduction](/components/modeler/feel/language-guide/feel-expressions-introduction.md). The assertion fails if the variable doesn't exist or the expression doesn't evaluate to `true`. ```java assertThat(processInstance) .hasVariableSatisfiesExpression( "order", "order.status = \"approved\" and list contains(order.items.name, \"Oxygen tank\")"); ``` ### hasVariableSatisfiesJudge Assert that a process variable satisfies a natural language expectation using a configured LLM judge. The expectation is evaluated only once. The assertion fails if the LLM score is below the configured threshold (default: 0.5). It requires [judge configuration](configuration.md#judge-configuration). When [document attachment](configuration.md#document-attachment) is enabled, Camunda document references found in the variable value are resolved and their content is passed to the judge. ```java assertThat(processInstance) .hasVariableSatisfiesJudge("result", "Contains a valid JSON response with status OK."); ``` ### hasVariableSimilarTo Assert that a process variable is semantically similar to an expected string using a configured embedding model. The variable value and the expected value are converted to embeddings, and the cosine similarity is compared against the configured threshold. The assertion fails if the variable doesn't exist or the similarity score is below the configured threshold (default: 0.5). It requires [semantic similarity configuration](configuration.md#semantic-similarity-configuration). ```java assertThat(processInstance) .hasVariableSimilarTo("greeting", "Hello, how can I help you today?"); ``` ### hasLocalVariableNames Assert that the process instance has the local variables in the scope of the given element. Use the BPMN element ID or a [ElementSelector](utilities.md#element-selector) to identify the element. The assertion fails if at least one variable doesn't exist. ```java assertThat(processInstance).hasLocalVariableNames(ElementSelectors.byId("task_A"), "var1", "var2"); ``` ### hasLocalVariable Assert that the process instance has the local variable with the value in the scope of the given element. Use the BPMN element ID or a [ElementSelector](utilities.md#element-selector) to identify the element. The assertion fails if the variable doesn't exist or has a different value. ```java assertThat(processInstance).hasLocalVariable(ElementSelectors.byId("task_A"), "var1", 100); ``` ### hasLocalVariables Assert that the process instance has the local variables in the scope of the given element. Use the BPMN element ID or a [ElementSelector](utilities.md#element-selector) to identify the element. The assertion fails if at least one variable doesn't exist or has a different value. ```java Map expectedVariables = // assertThat(processInstance).hasLocalVariables(ElementSelectors.byId("task_A"), expectedVariables); ``` ### hasLocalVariableSatisfies Assert that the process instance has a local variable in the scope of the given element with a value that satisfies the given requirements. Use the BPMN element ID or a [ElementSelector](utilities.md#element-selector) to identify the element. The assertion transforms the value into the given type. In the consumer, you can use [AssertJ](https://github.com/assertj/assertj) to verify the value. The assertion fails if the variable doesn't exist, the value is of a different type, or the value doesn't satisfy the requirements. ```java assertThat(processInstance).hasLocalVariableSatisfies( ElementSelectors.byId("send-email"), "to", EmailTo.class, emailTo -> { Assertions.assertThat(emailTo.name()).isEqualTo("Zee"); Assertions.assertThat(emailTo.email()).isEqualTo("zee@camunda.com"); }); ``` ### hasLocalVariableSatisfiesExpression Assert that the process instance has a local variable in the scope of the given element with a value that satisfies the given FEEL expression. Use the BPMN element ID or a [ElementSelector](utilities.md#element-selector) to identify the element. The expression is evaluated with a context containing the variable under its name. The expression should access the variable in a Boolean expression, for example, with comparisons. Learn more in the [FEEL expressions introduction](/components/modeler/feel/language-guide/feel-expressions-introduction.md). The assertion fails if the variable doesn't exist or the expression doesn't evaluate to `true`. ```java assertThat(processInstance) .hasLocalVariableSatisfiesExpression( ElementSelectors.byId("review-order"), "order", "order.status = \"approved\" and list contains(order.items.name, \"Oxygen tank\")"); ``` ### hasLocalVariableSatisfiesJudge Assert that a local variable in the scope of a given element satisfies a natural language expectation using a configured LLM judge. Use the BPMN element ID or an [element selector](utilities.md#element-selector) to identify the element. The expectation is evaluated only once. The assertion fails if the LLM score is below the configured threshold (default: 0.5). It requires [judge configuration](configuration.md#judge-configuration). When [document attachment](configuration.md#document-attachment) is enabled, Camunda document references found in the variable value are resolved and their content is passed to the judge. ```java assertThat(processInstance) .hasLocalVariableSatisfiesJudge( ElementSelectors.byName("Greet Customer"), "output", "Contains a polite greeting addressed to the customer."); ``` ### hasLocalVariableSimilarTo Assert that a local variable in the scope of a given element is semantically similar to an expected string using a configured embedding model. Use the BPMN element ID or an [element selector](utilities.md#element-selector) to identify the element. The assertion fails if the variable doesn't exist or the similarity score is below the configured threshold (default: 0.5). It requires [semantic similarity configuration](configuration.md#semantic-similarity-configuration). ```java assertThat(processInstance) .hasLocalVariableSimilarTo( ElementSelectors.byName("Greet Customer"), "output", "Hello, how can I help you today?"); ``` ## Process instance message assertions You can verify the message subscriptions of a process instance using `CamundaAssert.assertThat(processInstance)`. ### isWaitingForMessage Assert that the process instance is waiting for the given message. The assertion fails if the process instance has no active message subscription for the given message name and optional correlation key. ```java // 1) By message name assertThat(processInstance).isWaitingForMessage("message-name"); // 2) By message name and correlation key assertThat(processInstance).isWaitingForMessage("message-name", "correlation-key"); ``` ### isNotWaitingForMessage Assert that the process instance is not waiting for the given message. The assertion fails if the process instance has an active message subscription for the given message name and optional correlation key. ```java // 1) By message name assertThat(processInstance).isNotWaitingForMessage("message-name"); // 2) By message name and correlation key assertThat(processInstance).isNotWaitingForMessage("message-name", "correlation-key"); ``` ### hasCorrelatedMessage Assert that the given message was correlated to the process instance. The assertion fails if the process instance has no correlated message subscription for the given message name and optional correlation key. ```java // 1) By message name assertThat(processInstance).hasCorrelatedMessage("message-name"); // 2) By message name and correlation key assertThat(processInstance).hasCorrelatedMessage("message-name", "correlation-key"); ``` ## User task assertions You can verify the user task states and other properties using `CamundaAssert.assertThat()` or `CamundaAssert.assertThatUserTask()`. Use a [UserTaskSelector](utilities.md#user-task-selector) to identify the user task. ### With user task selector Use a [UserTaskSelector](utilities.md#user-task-selector) to identify the user task: ```java // by BPMN element ID assertThatUserTask(UserTaskSelectors.byElementId("user-task-id")).isCompleted(); // by user task name assertThatUserTask(UserTaskSelectors.byTaskName("User Task")).isCompleted(); // by process instance key assertThatUserTask(UserTaskSelectors.byProcessInstanceKey(processInstanceKey)).isCompleted(); ``` ### isCreated Asserts that the user task is created. The assertion fails if the task is in any other state. ```java assertThatUserTask(UserTaskSelectors.byTaskName("User Task")).isCreated(); ``` ### isCompleted Asserts that the user task is completed. The assertion fails if the task is in any other state. ```java assertThatUserTask(UserTaskSelectors.byTaskName("User Task")).isCompleted(); ``` ### isCanceled Asserts that the user task is canceled. The assertion fails if the task is in any other state. ```java assertThatUserTask(UserTaskSelectors.byTaskName("User Task")).isCanceled(); ``` ### isFailed Asserts that the user task is failed. The assertion fails if the task is in any other state. ```java assertThatUserTask(UserTaskSelectors.byTaskName("User Task")).isFailed(); ``` ### hasAssignee Asserts that the user task has the expected assignee. ```java assertThatUserTask(UserTaskSelectors.byTaskName("User Task")).hasAssignee("John Doe"); ``` ### hasPriority Asserts that the user task has the expected priority. ```java assertThatUserTask(UserTaskSelectors.byTaskName("User Task")).hasPriority(100); ``` ### hasElementId Asserts that the user task has the expected BPMN element ID. ```java assertThatUserTask(UserTaskSelectors.byTaskName("User Task")).hasElementId("user-task-id"); ``` ### hasName Asserts that the user task has the expected name. ```java assertThatUserTask(UserTaskSelectors.byElementId("user-task-id")).hasName("User Task"); ``` ### hasProcessInstanceKey Asserts that the user task has the expected process instance key. ```java assertThatUserTask(UserTaskSelectors.byTaskName("User Task")).hasProcessInstanceKey(processInstanceKey); ``` ### hasDueDate Asserts that the user task has the expected due date. ```java assertThatUserTask(UserTaskSelectors.byTaskName("User Task")).hasDueDate("2023-10-01T00:00:00Z"); ``` ### hasCompletionDate Asserts that the user task has the expected completion date. ```java assertThatUserTask(UserTaskSelectors.byTaskName("User Task")).hasCompletionDate("2023-10-01T00:00:00Z"); ``` ### hasFollowUpDate Asserts that the user task has the expected follow-up date. ```java assertThatUserTask(UserTaskSelectors.byTaskName("User Task")).hasFollowUpDate("2023-10-01T00:00:00Z"); ``` ### hasCreationDate Asserts that the user task has the expected creation date. ```java assertThatUserTask(UserTaskSelectors.byTaskName("User Task")).hasCreationDate("2023-10-01T00:00:00Z"); ``` ### hasCandidateGroup Asserts that the user task has the expected candidate group. ```java assertThatUserTask(UserTaskSelectors.byTaskName("User Task")).hasCandidateGroup("groupA"); ``` ### hasCandidateGroups Asserts that the user task has the expected candidate groups. ```java assertThatUserTask(UserTaskSelectors.byTaskName("User Task")).hasCandidateGroups("groupA", "groupB", "groupC"); ``` ## Decision assertions You can verify the decision evaluation state and other properties using `CamundaAssert.assertThat()` or `CamundaAssert.assertThatDecision()`. Use the evaluate decision response or a [DecisionSelector](utilities.md#decision-selector) to identify the decision instance. ### With evaluate decision response Use the response of the evaluate decision command to identify the decision instance: ```java // given/when EvaluateDecisionResponse response = client .newEvaluateDecisionCommand() .decisionId(decisionId) .variables(variables) .send() .join(); // then assertThat(response).isEvaluated(); ``` ### With decision selector Use a [DecisionSelector](utilities.md#decision-selector) to identify the decision instance: ```java // by decision ID assertThatDecision(DecisionSelectors.byId("decision-id")).isEvaluated(); // by decision name assertThatDecision(DecisionSelectors.byName("Decision Name")).isEvaluated(); // by process instance key assertThatDecision(DecisionSelectors.byProcessInstanceKey(processInstanceKey)).isEvaluated(); ``` ### isEvaluated Asserts that the decision is evaluated. The assertion fails if the evaluation failed and outputs the evaluation failure message. ```java assertThatDecision(DecisionSelectors.byId("decision-id")).isEvaluated(); ``` ### hasOutput Asserts that the decision is evaluated with the expected output. The verification fails if the decision evaluation failed or the output does not match. ```java // With primitive value assertThatDecision(DecisionSelectors.byId("decision-id")).hasOutput("output"); // With a map of values Map expectedOutput = // assertThatDecision(DecisionSelectors.byId("decision-id")).hasOutput(expectedOutput); // With a list of values List expectedOutput = // assertThatDecision(DecisionSelectors.byId("decision-id")).hasOutput(expectedOutput); ``` ### hasMatchedRules Asserts that the decision table has matched the given rule indices. The evaluation fails if the decision evaluation failed or at least one of the expected matched rules didn't match. The assertion will pass if the expected indexes are a subset of the total matches, e.g. `hasMatchedRules(1, 2)` will pass if rules [1, 2, 3] matched. ```java // Single rule assertThatDecision(DecisionSelectors.byId("decision-id")).hasMatchedRules(1); // Multiple rules assertThatDecision(DecisionSelectors.byId("decision-id")).hasMatchedRules(1, 3); ``` ### hasNotMatchedRules Asserts that the decision table has not matched the given rule indices. The assertion will fail if the decision evaluation has failed or at least one of the rules indexes has matched. ```java // Single rule assertThatDecision(DecisionSelectors.byId("decision-id")).hasNotMatchedRules(2); // Multiple rules assertThatDecision(DecisionSelectors.byId("decision-id")).hasNotMatchedRules(2, 4); ``` ### hasNoMatchedRules Asserts that the decision table matched no rules. The assertion will fail if the decision evaluation has failed or at least one rule matched. ```java assertThatDecision(DecisionSelectors.byId("decision-id")).hasNoMatchedRules(); ``` ## Value assertions You can verify arbitrary string values, independent of a process instance, using `CamundaAssert.assertThatValue()`. This is useful for evaluating values produced outside of a running process. For example, a single property of a variable object, with the same LLM judge and embedding-based similarity checks used for process variables. ### satisfiesJudge Assert that the given value satisfies a natural language expectation using a configured LLM judge. The expectation is evaluated only once. The assertion fails if the LLM score is below the configured threshold (default: 0.5). It requires [judge configuration](configuration.md#judge-configuration). [Document attachment](configuration.md#document-attachment) is not supported for value assertions. To evaluate document content, use [hasVariableSatisfiesJudge](#hasvariablesatisfiesjudge) or [hasLocalVariableSatisfiesJudge](#haslocalvariablesatisfiesjudge) instead. ```java assertThatValue("The order has been shipped and will arrive tomorrow.") .satisfiesJudge("Confirms that the order is on its way to the customer."); ``` Override the global judge configuration for a single assertion chain using `withJudgeConfig`. ```java assertThatValue(response) .withJudgeConfig(config -> config.withThreshold(0.9)) .satisfiesJudge("Contains a valid JSON response with status OK."); ``` ### isSimilarTo Assert that the given value is semantically similar to an expected string using a configured embedding model. Both values are converted to embeddings, and the cosine similarity is compared against the configured threshold. The assertion fails if the similarity score is below the configured threshold (default: 0.5). It requires [semantic similarity configuration](configuration.md#semantic-similarity-configuration). ```java assertThatValue("Hi there, what can I do for you?") .isSimilarTo("Hello, how can I help you today?"); ``` Override the global semantic similarity configuration for a single assertion chain using `withSemanticSimilarityConfig`. ```java assertThatValue(response) .withSemanticSimilarityConfig(config -> config.withThreshold(0.9)) .isSimilarTo("Hello, how can I help you today?"); ``` ## Custom assertions You can build your own assertions similar to the assertions from CPT. - Use the preconfigured Camunda client to retrieve the process data. - Use [AssertJ](https://github.com/assertj/assertj)'s assertions to verify the expected properties. - Use [Awaitility](http://www.awaitility.org/) around verifications to compensate delays until the data is available. ```java @Test void shouldCreateUserTask() { // given: the process is deployed // when: create a process instance // then Awaitility.await() .ignoreException(ClientException.class) .untilAsserted( () -> { final List userTasks = getUserTasks(processInstanceKey); assertThat(userTasks).hasSize(1); final UserTask userTask = userTasks.getFirst(); assertThat(userTask) .returns("task", UserTask::getName) .returns("me", UserTask::getAssignee); }); } // helper method private List getUserTasks(final long processInstanceKey) { return client .newUserTaskSearchRequest() .filter(filter -> filter.processInstanceKey(processInstanceKey).state(UserTaskState.CREATED)) .send() .join() .items(); } ``` --- ## Configuration(Testing) By default, CPT uses a runtime based on [Testcontainers](#testcontainers-runtime). You can customize the runtime to your needs, or replace it with a [Remote runtime](#remote-runtime), for example, if you can't install a Docker runtime. ## Configuration files CPT properties can be set directly in a configuration file or resolved from environment variables. The file location and resolution mechanism depend on your setup: Configure CPT in your `application.yml` (or `application.properties`). Properties also support [Spring's external configuration](https://docs.spring.io/spring-boot/reference/features/external-config.html), so you can set them through environment variables, system properties, or additional profiles. Configure CPT in a `camunda-container-runtime.properties` file. Properties support automatic environment variable resolution. If a property is not explicitly set, it is resolved from an environment variable by prepending `CAMUNDA_PROCESSTEST_`, replacing dots with underscores, removing hyphens, and converting to uppercase. For example, `judge.chatModel.apiKey` resolves to `CAMUNDA_PROCESSTEST_JUDGE_CHATMODEL_APIKEY`. ## Testcontainers runtime The default runtime of CPT is based on [Testcontainers](https://java.testcontainers.org/). It uses the Camunda Docker image and includes the following components: - Camunda - Connectors :::note Why Testcontainers? CPT follows a common practice by using Testcontainers to provide an isolated, reproducible, and easily configurable environment using Docker containers. This ensures consistent test results, simplifies setup across different platforms, and allows integration with Camunda and other components without manual installation or complex dependencies. ::: :::tip Shared runtime If you use the same runtime configuration for all test classes, then you can use a [shared runtime](#shared-runtime) to speed up the test execution. ::: ### Prerequisites - A Docker-API compatible container runtime, such as Docker on Linux or Docker Desktop on Mac and Windows. If you're experiencing issues with your Docker runtime, have a look at the [Testcontainers documentation](https://java.testcontainers.org/supported_docker_environment/). ### Usage By default, the runtime uses the same version of the Camunda Docker image as the Maven module. The Connectors Docker image doesn't follow the Camunda version. The runtime uses a fixed, published Connectors version for each CPT release. To use another version, set `connectors-docker-image-version`. You can change the Docker images and other runtime properties in the following way. If you change the version of the Camunda Docker image, use the same version as CPT or later. CPT doesn't support older Camunda runtimes. In your `application.yml` (or `application.properties`): ```yaml camunda: process-test: # Change the version of the Camunda Docker image camunda-docker-image-version: 8.11.0 # Change the Camunda Docker image camunda-docker-image-name: camunda/camunda # Set additional Camunda environment variables camunda-env-vars: env_1: value_1 # Expose additional Camunda ports camunda-exposed-ports: - 9000 # Change the Camunda logger name camunda-logger-name: tc.camunda # Enable Connectors connectors-enabled: true # Change the Connectors Docker image connectors-docker-image-name: camunda/connectors # Change version of the Connectors Docker image connectors-docker-image-version: 8.8.0 # Set additional Connectors environment variables connectors-env-vars: env_1: value_1 # Set cluster secrets cluster-secrets: secret_1: value_1 # Set legacy Connectors secrets connectors-secrets: secret_1: value_1 # Expose additional Connectors ports connectors-exposed-ports: - 9010 # Change the Connectors logger name connectors-logger-name: tc.connectors ``` In your `/camunda-container-runtime.properties` file: ```properties # Change the version of the Camunda Docker image camundaDockerImageVersion=8.11.0 # Change the Camunda Docker image camundaDockerImageName=camunda/camunda # Set additional Camunda environment variables camundaEnvVars.env_1=value_1 camundaEnvVars.env_2=value_2 # Expose additional Camunda ports camundaExposedPorts[0]=9000 camundaExposedPorts[1]=9001 # Change the Camunda logger name camundaLoggerName=tc.camunda # Enable Connectors connectorsEnabled=true # Change version of the Connectors Docker image connectorsDockerImageVersion=8.8.0 # Change the Connectors Docker image connectorsDockerImageName=camunda/connectors # Set additional Connectors environment variables connectorsEnvVars.env_1=value_1 connectorsEnvVars.env_2=value_2 # Set legacy Connectors secrets connectorsSecrets.secret_1=value_1 connectorsSecrets.secret_2=value_2 # Expose additional Connectors ports connectorsExposedPorts[0]=9010 connectorsExposedPorts[1]=9011 # Change the Connectors logger name connectorsLoggerName=tc.connectors ``` Alternatively, you can register the JUnit extension manually and use the fluent builder: ```java package com.example; // No annotation: @CamundaProcessTest public class MyProcessTest { @RegisterExtension private static final CamundaProcessTestExtension EXTENSION = new CamundaProcessTestExtension() // Change the version of the Camunda Docker image .withCamundaDockerImageVersion("8.11.0") // Change the Camunda Docker image .withCamundaDockerImageName("camunda/camunda") // Set additional Camunda environment variables .withCamundaEnv("env_1", "value_1") // Expose additional Camunda ports .withCamundaExposedPort(4567) // Enable Connectors .withConnectorsEnabled(true) // Change the Connectors Docker image .withConnectorsDockerImageName("camunda/connectors") // Change version of the Connectors Docker image .withConnectorsDockerImageVersion("8.8.0") // Set additional Connectors environment variables .withConnectorsEnv("env_1", "value_1") // Set cluster secrets .withClusterSecret("secret_1", "value_1") // Set legacy Connectors secrets .withConnectorsSecret("secret_1", "value_1"); } ``` ### Set cluster secrets Cluster secrets are the default way to provide secrets in tests with Camunda 8.10+. They let processes that reference `=camunda.secrets.` resolve in your tests. This feature requires Camunda Process Test 8.10.6+. Set cluster secrets with the `cluster-secrets` property (Spring) or `withClusterSecret(name, value)` and `withClusterSecrets(map)` (Java builder). You can't set cluster secrets in `camunda-container-runtime.properties`. - In a managed or shared runtime, CPT writes each secret to a temporary file, copies the file into the Camunda container, and sets `CAMUNDA_SECRETS_STORES_FILE_DEFAULT_PATH`. - CPT fixes the secrets when the runtime starts. You can't change them per test. - Use `connectors-secrets` only for the legacy `{{secrets.X}}` syntax. It doesn't populate the cluster secret store. See [Connectors](./connectors.md#set-cluster-secrets) for examples. - To learn how secrets resolve, see [secrets](/components/concepts/secrets.md) and [secret resolution](/components/concepts/secret-resolution.md). :::warning CPT drops trailing newlines from cluster secret values. Don't rely on a trailing newline in a value. This is a known issue ([camunda/camunda#66616](https://github.com/camunda/camunda/issues/66616)). ::: ### Shared runtime By default, CPT creates a new runtime for each test class. You can change this behavior and use a shared Testcontainers runtime for all test classes to speed up the test execution. You can enable the shared runtime in the following way. In your `application.yml` (or `application.properties`): ```yaml camunda: process-test: # Switch from a managed to a shared runtime runtime-mode: shared ``` All test classes using the shared runtime will use the same runtime configuration. You can't change the runtime configuration for individual test classes, such as enabling connectors or setting connector secrets. However, you can switch to a managed runtime for individual test classes and override the runtime configuration. ```java @SpringBootTest( properties = { // Use a managed runtime for a different configuration "camunda.process-test.runtime-mode=managed", "camunda.process-test.connectors-enabled=true", } ) @CamundaSpringProcessTest public class MyProcessTest { // } ``` In your `/camunda-container-runtime.properties` file: ```properties # Switch from a managed to a shared runtime runtimeMode=shared ``` All test classes using the shared runtime will use the same runtime configuration. You can't change the runtime configuration for individual test classes, such as enabling connectors or setting connector secrets. However, you can switch to a managed runtime for individual test classes and override the runtime configuration. ```java package com.example; // No annotation: @CamundaProcessTest public class MyProcessTest { @RegisterExtension private static final CamundaProcessTestExtension EXTENSION = new CamundaProcessTestExtension() // Use a managed runtime for a different configuration .withRuntimeMode(CamundaProcessTestRuntimeMode.MANAGED) .withConnectorsEnabled(true); } ``` ### Multi-tenancy Multi-tenancy is disabled by default. You can enable multi-tenancy in the following way: In your `application.yml` (or `application.properties`): ```yaml camunda: process-test: # Enable multi-tenancy multi-tenancy-enabled: true ``` By enabling multi-tenancy, the runtime enables Basic Auth security and creates a default user with username/password `demo` with admin rights to interact with the runtime. A process test using multi-tenancy could look like the following example: ```java @SpringBootTest @CamundaSpringProcessTest public class MyProcessTest { private static final String DEFAULT_USERNAME = "demo"; private static final String TENANT_ID_1 = "tenant-1"; private static final String TENANT_ID_2 = "tenant-2"; @Autowired private CamundaClient client; @Autowired private CamundaProcessTestContext processTestContext; private CamundaClient clientForTenant1; @BeforeEach void setupTenants() { // create tenants client.newCreateTenantCommand().tenantId(TENANT_ID_1).name(TENANT_ID_1).send().join(); client.newCreateTenantCommand().tenantId(TENANT_ID_2).name(TENANT_ID_2).send().join(); // assign the default user to the tenants client .newAssignUserToTenantCommand() .username(DEFAULT_USERNAME) .tenantId(TENANT_ID_1) .send() .join(); client .newAssignUserToTenantCommand() .username(DEFAULT_USERNAME) .tenantId(TENANT_ID_2) .send() .join(); // create a client for tenant 1 clientForTenant1 = processTestContext.createClient( clientBuilder -> clientBuilder.defaultTenantId(TENANT_ID_1)); } @Test void createProcessInstance() { // given clientForTenant1 .newDeployResourceCommand() .addResourceFromClasspath("bpmn/order-process.bpmn") .send() .join(); // when final var processInstance = clientForTenant1 .newCreateInstanceCommand() .bpmnProcessId("order-process") .latestVersion() .variable("order_id", "order-1") .send() .join(); // then assertThatProcessInstance(processInstance).isCreated(); Assertions.assertThat(processInstance.getTenantId()).isEqualTo(TENANT_ID_1); } } ``` In your `/camunda-container-runtime.properties` file: ```properties # Enable multi-tenancy multiTenancyEnabled=true ``` Alternatively, you can register the JUnit extension manually and use the fluent builder: ```java package com.example; // No annotation: @CamundaProcessTest public class MyProcessTest { @RegisterExtension private static final CamundaProcessTestExtension EXTENSION = new CamundaProcessTestExtension() // Enable multi-tenancy .withMultiTenancyEnabled(true); } ``` By enabling multi-tenancy, the runtime enables Basic Auth security and creates a default user with username/password `demo` with admin rights to interact with the runtime. A process test using multi-tenancy could look like the following example: ```java @CamundaProcessTest public class MyProcessTest { private static final String DEFAULT_USERNAME = "demo"; private static final String TENANT_ID_1 = "tenant-1"; private static final String TENANT_ID_2 = "tenant-2"; private CamundaClient client; private CamundaProcessTestContext processTestContext; private CamundaClient clientForTenant1; @BeforeEach void setupTenants() { // create tenants client.newCreateTenantCommand().tenantId(TENANT_ID_1).name(TENANT_ID_1).send().join(); client.newCreateTenantCommand().tenantId(TENANT_ID_2).name(TENANT_ID_2).send().join(); // assign the default user to the tenants client .newAssignUserToTenantCommand() .username(DEFAULT_USERNAME) .tenantId(TENANT_ID_1) .send() .join(); client .newAssignUserToTenantCommand() .username(DEFAULT_USERNAME) .tenantId(TENANT_ID_2) .send() .join(); // create a client for tenant 1 clientForTenant1 = processTestContext.createClient( clientBuilder -> clientBuilder.defaultTenantId(TENANT_ID_1)); } @Test void createProcessInstance() { // given clientForTenant1 .newDeployResourceCommand() .addResourceFromClasspath("bpmn/order-process.bpmn") .send() .join(); // when final var processInstance = clientForTenant1 .newCreateInstanceCommand() .bpmnProcessId("order-process") .latestVersion() .variable("order_id", "order-1") .send() .join(); // then assertThatProcessInstance(processInstance).isCreated(); Assertions.assertThat(processInstance.getTenantId()).isEqualTo(TENANT_ID_1); } } ``` :::info You should assign the default user (`demo`) to all tenants to ensure that the assertions can access all data. ::: ### Custom containers You can add custom containers to the managed or shared Testcontainers runtime, for example, to add a database, an MCP server, or a mock service. The CPT runtime manages the lifecycle of the custom containers and ensures that they are started before the tests and stopped after the tests. The custom containers are added to the same network as the Camunda and Connectors containers to allow communication between the containers. You can add a custom container in the following way. Implement a `CamundaProcessTestContainerProvider` bean that creates the custom container. In this example, we create a WireMock container to mock external HTTP calls in the tests. ```java @Configuration public class TestConfig { @Bean public CamundaProcessTestContainerProvider wireMockProvider() { return containerContext -> new WireMockContainer(); } // A WireMock container to mock external HTTP calls in the tests private static final class WireMockContainer extends GenericContainer { public WireMockContainer() { // Configure the Docker image super("wiremock/wiremock:3.13.0"); // Configure the network alias for communication between the containers withNetworkAliases("wiremock"); // Configure the ports to expose withExposedPorts(8080); // Configure the logger withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("tc.wiremock"), true)); // Configure the wait strategy to ensure that the container is ready before running the tests waitingFor( Wait.forHttp("/__admin/mappings").forPort(8080).withMethod("GET").forStatusCode(200)); // Custom container-specific configuration withCopyFileToContainer( // Copy the WireMock mapping file for the HTTP stubs to the container MountableFile.forClasspathResource("/wiremock/mapping.json"), "/home/wiremock/mappings/mapping.json"); } } } ``` In the `application.yml` configuration, we use a connector secret to bind the connector task to the WireMock container using its network alias `wiremock` and the exposed port `8080`. ```yaml camunda: process-test: connectors-enabled: true connectors-secrets: BASE_URL: http://wiremock:8080 ``` Implement the `CamundaProcessTestContainerProvider` interface that creates the custom container. In this example, we create a WireMock container to mock external HTTP calls in the tests. ```java public class WireMockContainerProvider implements CamundaProcessTestContainerProvider { @Override public GenericContainer createContainer(final CamundaProcessTestContainerContext containerContext) { return new WireMockContainer(); } // A WireMock container to mock external HTTP calls in the tests private static final class WireMockContainer extends GenericContainer { public WireMockContainer() { // Configure the Docker image super("wiremock/wiremock:3.13.0"); // Configure the network alias for communication between the containers withNetworkAliases("wiremock"); // Configure the ports to expose withExposedPorts(8080); // Configure the logger withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("tc.wiremock"), true)); // Configure the wait strategy to ensure that the container is ready before running the tests waitingFor( Wait.forHttp("/__admin/mappings").forPort(8080).withMethod("GET").forStatusCode(200)); // Custom container-specific configuration withCopyFileToContainer( // Copy the WireMock mapping file for the HTTP stubs to the container MountableFile.forClasspathResource("/wiremock/mapping.json"), "/home/wiremock/mappings/mapping.json"); } } } ``` Register the container provider using the Java ServiceLoader mechanism by creating a file `io.camunda.process.test.api.runtime.CamundaProcessTestContainerProvider` in the `src/test/resources/META-INF/services` directory of your project and adding the fully qualified name of the container provider implementation: ``` com.example.WireMockContainerProvider ``` In the `/camunda-container-runtime.properties` configuration file, we use a connector secret to bind the connector task to the WireMock container using its network alias `wiremock` and the exposed port `8080`. ```properties connectorsEnabled=true connectorsSecrets.BASE_URL=http://wiremock:8080 ``` Alternatively, you can register the container provider on the JUnit extension using the fluent builder: ```java // No annotation: @CamundaProcessTest public class MyProcessTest { @RegisterExtension private static final CamundaProcessTestExtension EXTENSION = new CamundaProcessTestExtension() .withContainerProvider(new WireMockContainerProvider()) .withConnectorsEnabled(true) .withConnectorsSecret("BASE_URL", "http://wiremock:8080"); } ``` ## Remote runtime Instead of using the managed [Testcontainers runtime](#testcontainers-runtime), you can configure CPT to connect to a remote runtime, for example, to a local [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) running on your machine. When to use it: - You can't install a Docker-API compatible container runtime - Debugging of test case on your local machine :::info You are responsible for configuring and managing the remote runtime. Ensure the runtime is running before executing tests. Keep in mind that CPT automatically deletes all data between test runs to maintain a clean state. ::: ### Prerequisites - Install a Camunda 8 runtime of the same version as CPT or later, for example, [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md). CPT doesn't support older Camunda runtimes. - Expose the management API port (`9600`) to delete the data between test runs (by default for a local Camunda 8 Run) - If [authorizations](/components/concepts/access-control/authorizations.md) are enabled, grant the `READ` and `UPDATE` permissions on the `SYSTEM` resource to the user or client that CPT uses to connect to the runtime. CPT needs these permissions to change the clock. You set up this user or client in the [client configuration](#client-configuration). ### Usage You need to set the following property to switch to a remote runtime. In your `application.yml` (or `application.properties`): ```yaml camunda: process-test: # Switch from a managed to a remote runtime runtime-mode: remote ``` In your `/camunda-container-runtime.properties` file: ```properties # Switch from a managed to a remote runtime runtimeMode=remote ``` Alternatively, you can register the JUnit extension manually and use the fluent builder: ```java package com.example; // No annotation: @CamundaProcessTest public class MyProcessTest { @RegisterExtension private static final CamundaProcessTestExtension EXTENSION = new CamundaProcessTestExtension() // Switch from a managed to a remote runtime .withRuntimeMode(CamundaProcessTestRuntimeMode.REMOTE); } ``` In a remote runtime, the cluster owns its secret store. CPT ignores `cluster-secrets` and logs a warning. Configure secrets on the cluster instead. ### Change the connection By default, CPT connects to a remote Camunda 8 Run running on your local machine. CPT checks if the remote runtime is available and ready, before running the tests. It waits up to 1 minute for the remote runtime to become ready. You can change the connection to the remote runtime and the connection time in the following way. In your `application.yml` (or `application.properties`): ```yaml camunda: process-test: runtime-mode: remote # Change the connection (default: Camunda 8 Run) remote: camunda-monitoring-api-address: http://0.0.0.0:9600 connectors-rest-api-address: http://0.0.0.0:8086 # The connection timeout in ISO-8601 duration format (default: PT1M) runtime-connection-timeout: PT1M client: rest-address: http://0.0.0.0:8080 grpc-address: http://0.0.0.0:26500 ``` :::note The properties `camunda.process-test.remote.client.rest-address` and `camunda.process-test.remote.client.grpc-address` are deprecated. Use `camunda.client.rest-address` and `camunda.client.grpc-address` instead. ::: In your `/camunda-container-runtime.properties` file: ```properties runtimeMode=remote # Change the connection (default: Camunda 8 Run) remote.camundaMonitoringApiAddress=http://0.0.0.0:9600 remote.connectorsRestApiAddress=http://0.0.0.0:8086 camunda.client.gateway.grpc.address=http://0.0.0.0:26500 camunda.client.gateway.rest.address=http://0.0.0.0:8080 # The connection timeout in ISO-8601 duration format (default: PT1M) remote.runtimeConnectionTimeout=PT1M ``` :::note The properties `remote.client.grpcAddress` and `remote.client.restAddress` are deprecated. Use `camunda.client.gateway.grpc.address` and `camunda.client.gateway.rest.address` instead. ::: Alternatively, register the JUnit extension manually and use the fluent builder: ```java package com.example; // No annotation: @CamundaProcessTest public class MyProcessTest { @RegisterExtension private static final CamundaProcessTestExtension EXTENSION = new CamundaProcessTestExtension() .withRuntimeMode(CamundaProcessTestRuntimeMode.REMOTE) // Change the connection (default: Camunda 8 Run) .withCamundaClientBuilderFactory(() -> CamundaClient.newClientBuilder() .restAddress(URI.create("http://0.0.0.0:8080")) .grpcAddress(URI.create("http://0.0.0.0:26500")) ) .withRemoteCamundaMonitoringApiAddress(URI.create("http://0.0.0.0:9600")) .withRemoteConnectorsRestApiAddress(URI.create("http://0.0.0.0:8086")) // Change the connection timeout (default: PT1M) .withRemoteRuntimeConnectionTimeout(Duration.ofMinutes(1)); } ``` ### Debugging of test cases You can use a remote runtime to debug your test cases on your local machine. Set breakpoints in your test case and run the test in debug mode from your IDE. When the test execution stops at a breakpoint, you can inspect the process instance state using Operate and the user task state using Tasklist. You can also use the Camunda client to interact with the runtime from the debugger console. ## Client configuration CPT configures the Camunda client automatically based on the runtime mode. You can customize the client configuration beyond the connection addresses, for example, to set up authentication. CPT applies all [Camunda client configurations](/apis-tools/camunda-spring-boot-starter/configuration.md) from your `application.yml`. For example, to configure Basic authentication for a remote runtime: ```yaml camunda: client: grpc-address: http://localhost:26500 rest-address: http://localhost:8080 auth: method: basic username: demo password: demo ``` For full flexibility, provide a `CamundaClientBuilderFactory` bean: ```java @Bean public CamundaClientBuilderFactory customClientBuilderFactory() { return () -> CamundaClient.newClientBuilder() .restAddress(URI.create("http://0.0.0.0:8080")) .grpcAddress(URI.create("http://0.0.0.0:26500")) .credentialsProvider( CredentialsProvider.newBasicAuthCredentialsProviderBuilder() .username("demo") .password("demo") .build()); } ``` In the `camunda-container-runtime.properties` file, you can set any [`ClientProperties`](https://javadoc.io/doc/io.camunda/camunda-client-java/latest/io/camunda/client/ClientProperties.html). For example, to configure the connection to a remote runtime: ```properties camunda.client.gateway.rest.address=http://0.0.0.0:8080 camunda.client.gateway.grpc.address=http://0.0.0.0:26500 ``` For more flexibility, use the fluent builder to set a client builder factory: ```java @RegisterExtension private static final CamundaProcessTestExtension EXTENSION = new CamundaProcessTestExtension() .withRuntimeMode(CamundaProcessTestRuntimeMode.REMOTE) .withCamundaClientBuilderFactory( () -> CamundaClient.newClientBuilder() .restAddress(URI.create("http://0.0.0.0:8080")) .grpcAddress(URI.create("http://0.0.0.0:26500"))); ``` To override specific client properties, for example to configure a credential provider, use `withCamundaClientBuilderOverrides`. This works together with the client builder factory and the configuration file: ```java @RegisterExtension private static final CamundaProcessTestExtension EXTENSION = new CamundaProcessTestExtension() .withCamundaClientBuilderOverrides( camundaClientBuilder -> camundaClientBuilder .credentialsProvider( CredentialsProvider.newBasicAuthCredentialsProviderBuilder() .username("demo") .password("demo") .build())); ``` ## Query page limit CPT paginates the individual search requests used by assertions, utilities, and the coverage report, with a default page limit of 100 results. If your process tests create a lot of data, such as many process instances or a process with a multi-instance activity, assertions can fail or the coverage report can be incomplete. In that case, increase the query page limit. In your `application.yml` (or `application.properties`): ```yaml camunda: process-test: query-page-limit: 1000 ``` In your `/camunda-container-runtime.properties` file: ```properties queryPageLimit=1000 ``` ### Property reference | Property | Type | Default | Description | | ------------------ | --------- | ------- | -------------------------------------------------------- | | `query-page-limit` | `integer` | `100` | The maximum number of results to return per paged query. | ## Test cleanup settings {#test-cleanup-settings} After each test, CPT resets the Camunda runtime clock and deletes all runtime data by default. You can disable either behavior to inspect the process state after a test run. :::info Disabling clock reset or data deletion means state from one test can affect subsequent tests. ::: In your `application.yml` (or `application.properties`): ```yaml camunda: process-test: # Keep the Camunda runtime clock as-is after each test clock-reset-enabled: false # Skip runtime data deletion after each test data-deletion-mode: none ``` In your `/camunda-container-runtime.properties` file: ```properties # Keep the Camunda runtime clock as-is after each test clockResetEnabled=false # Skip runtime data deletion after each test dataDeletionMode=NONE ``` ### Property reference | Property | Type | Default | Description | | --------------------- | ----------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `clock-reset-enabled` | `boolean` | `true` | When `true`, resets the Camunda runtime clock after each test. Set to `false` to keep the clock at its current value for post-run inspection. | | `data-deletion-mode` | `string` (`enum`) | `CLUSTER_PURGE` | Controls how CPT deletes runtime data after each test. `CLUSTER_PURGE` (default) purges the full cluster state. `NONE` skips data deletion entirely. | ## Process Test Coverage CPT generates an HTML and JSON coverage report of your BPMN processes and DMN decision tables. You can configure the report generation in the following way. In your `application.yml` (or `application.properties`): ```yaml camunda: process-test: coverage: # Change the directory where the report is generated reportDirectory: target/coverage-report # Exclude processes from the report by their process definition ID excludedProcesses: - process_1 - process_2 # Exclude decisions from the report by their decision definition ID excludedDecisions: - decision_1 - decision_2 ``` In your `/camunda-container-runtime.properties` file: ```properties # Change the directory where the report is generated coverage.reportDirectory=target/coverage-report # Exclude processes from the report by their process definition ID excludedProcesses[0]=process_1 excludedProcesses[1]=process_2 # Exclude decisions from the report by their decision definition ID excludedDecisions[0]=decision_1 excludedDecisions[1]=decision_2 ``` ## Logging The test runtime uses [SLF4J](https://www.slf4j.org/) as the logging framework. If needed, you can enable the logging for the following packages: - `io.camunda.process.test` - The test runtime (recommended level `info`) - `tc.camunda` - The Camunda Docker container (recommended level `error`) - `tc.connectors` - The connectors Docker container (recommended level `error`) - `org.testcontainers` - The Testcontainers framework (recommended level `warn`) ## Judge configuration [Judge assertions](assertions.md#hasvariablesatisfiesjudge) use a configured LLM to score process variables (or plain values) against natural language expectations. This section covers how to set up the LLM provider and tune the judge behavior. ### Prerequisites CPT provides an optional [LangChain4j](https://docs.langchain4j.dev/) integration module that ships with preconfigured support for major LLM providers: OpenAI, Anthropic, Amazon Bedrock, Azure OpenAI, and OpenAI-compatible APIs. LangChain4j requires Java 17+. You can provide your own LLM integration through a custom `ChatModelAdapter` instead (see [custom ChatModelAdapter](#custom-chatmodeladapter)). :::tip For a guided walkthrough of setting up and testing AI agents, see [test your AI agents](/components/agentic-orchestration/evaluate-agents/test-ai-agents.md). ::: Camunda Process Test Spring includes the LangChain4j providers as a transitive dependency. No additional dependency is needed. Add the `camunda-process-test-langchain4j` dependency to your project: ```xml io.camunda camunda-process-test-langchain4j test ``` If you provide a custom `ChatModelAdapter` (see [custom ChatModelAdapter](#custom-chatmodeladapter)), this dependency is not required. ### Property reference All judge properties are nested under `camunda.process-test.judge` in Spring configuration. In Java properties files, use the `judge.` prefix with camelCase keys (for example, `judge.chat-model.api-key` becomes `judge.chatModel.apiKey`). For configuration examples, see [Step 2: configure the LLM provider and connectors](/components/agentic-orchestration/evaluate-agents/test-ai-agents.md#step-2-configure-the-llm-provider-and-connectors). Unless noted otherwise, properties in the provider tables are required. #### Judge settings | Property | Type | Default | Description | | ------------------------ | --------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `judge.threshold` | `double` | `0.5` | Confidence threshold (0.0 to 1.0) for the judge to pass. | | `judge.custom-prompt` | `string` | | Custom evaluation prompt replacing the default criteria. | | `judge.attach-documents` | `boolean` | `false` | When `true`, resolves Camunda document references in the evaluated variable and attaches their content to the judge. Disabled by default to avoid unnecessary token cost. To evaluate attached content, use a multimodal-capable model; otherwise, CPT evaluates only the raw variable JSON. See [document attachment](#document-attachment). | The default threshold of `0.5` treats a response as acceptable when it is at least partially satisfied according to the judge rubric. This is a practical default for AI-generated output, where wording and level of detail may vary between runs even when the response is still useful. Increase the threshold when your assertion needs stricter semantic agreement. #### Chat model settings | Property | Required | Type | Description | | ------------------------------ | -------- | ---------- | --------------------------------------------------------- | | `judge.chat-model.provider` | Yes | `string` | Set to `openai`. | | `judge.chat-model.model` | Yes | `string` | Model name (for example `gpt-4o`). | | `judge.chat-model.api-key` | Yes | `string` | API key. | | `judge.chat-model.timeout` | No | `duration` | Request timeout (ISO-8601 duration, for example `PT30S`). | | `judge.chat-model.temperature` | No | `double` | Temperature for response randomness (0.0 to 2.0). | **Example:** ```yaml camunda: process-test: judge: chat-model: provider: "openai" model: "gpt-4o" api-key: ${OPENAI_API_KEY} ``` | Property | Required | Type | Description | | ------------------------------ | -------- | ---------- | --------------------------------------------------------- | | `judge.chat-model.provider` | Yes | `string` | Set to `anthropic`. | | `judge.chat-model.model` | Yes | `string` | Model name (for example `claude-sonnet-4-20250514`). | | `judge.chat-model.api-key` | Yes | `string` | API key. | | `judge.chat-model.timeout` | No | `duration` | Request timeout (ISO-8601 duration, for example `PT30S`). | | `judge.chat-model.temperature` | No | `double` | Temperature for response randomness (0.0 to 2.0). | **Example:** ```yaml camunda: process-test: judge: chat-model: provider: "anthropic" model: "claude-sonnet-4-20250514" api-key: ${ANTHROPIC_API_KEY} ``` Supports Bedrock long-term API keys or AWS IAM credentials. Falls back to the [AWS default credentials provider chain](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/credentials-chain.html). :::note The AWS principal must be authorized to perform [`bedrock:InvokeModel`](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_InvokeModel.html) on the configured model ARN. The model must also be [enabled for access](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html) in the chosen region. If you use Bedrock for both the judge chat model and the embedding model, each model requires a separate `bedrock:InvokeModel` grant; access to one does not imply access to the other. ::: | Property | Required | Type | Description | | ----------------------------------------- | ------------------------------ | ---------- | ---------------------------------------------------------------------------------------------- | | `judge.chat-model.provider` | Yes | `string` | Set to `amazon-bedrock`. | | `judge.chat-model.model` | Yes | `string` | Model name (for example `eu.anthropic.claude-haiku-4-5-20251001-v1:0`). | | `judge.chat-model.region` | No | `string` | AWS region (for example `eu-central-1`). | | `judge.chat-model.api-key` | No | `string` | Bedrock long-term API key. Optional if using IAM credentials or the default credentials chain. | | `judge.chat-model.credentials.access-key` | Conditionally, with secret key | `string` | AWS IAM access key. Optional if using an API key or the default credentials chain. | | `judge.chat-model.credentials.secret-key` | Conditionally, with access key | `string` | AWS IAM secret key. Optional if using an API key or the default credentials chain. | | `judge.chat-model.timeout` | No | `duration` | Request timeout (ISO-8601 duration, for example `PT30S`). | | `judge.chat-model.temperature` | No | `double` | Temperature for response randomness (0.0 to 2.0). | **Example:** ```yaml camunda: process-test: judge: chat-model: provider: "amazon-bedrock" model: "eu.anthropic.claude-haiku-4-5-20251001-v1:0" region: "eu-central-1" credentials: access-key: ${AWS_BEDROCK_ACCESS_KEY} secret-key: ${AWS_BEDROCK_SECRET_KEY} ``` Supports API key authentication. Falls back to [`DefaultAzureCredential`](https://learn.microsoft.com/en-us/java/api/com.azure.identity.defaultazurecredential). | Property | Required | Type | Description | | ------------------------------ | -------- | ---------- | -------------------------------------------------------------------------------------------------------------------------- | | `judge.chat-model.provider` | Yes | `string` | Set to `azure-openai`. | | `judge.chat-model.model` | Yes | `string` | Azure [deployment name](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/create-resource#deploy-a-model). | | `judge.chat-model.endpoint` | Yes | `string` | Azure OpenAI resource URL (for example `https://my-resource.openai.azure.com/`). | | `judge.chat-model.api-key` | No | `string` | API key. Optional; if omitted, falls back to `DefaultAzureCredential`. | | `judge.chat-model.timeout` | No | `duration` | Request timeout (ISO-8601 duration, for example `PT30S`). | | `judge.chat-model.temperature` | No | `double` | Temperature for response randomness (0.0 to 2.0). | **Example:** ```yaml camunda: process-test: judge: chat-model: provider: "azure-openai" model: "my-gpt4o-deployment" endpoint: "https://my-resource.openai.azure.com/" api-key: ${AZURE_OPENAI_API_KEY} ``` For local models (such as [Ollama](https://ollama.com/)) or any third-party API that implements the [OpenAI chat completions format](https://platform.openai.com/docs/api-reference/chat). | Property | Required | Type | Description | | ------------------------------ | -------- | ---------- | ------------------------------------------------------------------------ | | `judge.chat-model.provider` | Yes | `string` | Set to `openai-compatible`. | | `judge.chat-model.model` | Yes | `string` | Model name (for example `llama3`). | | `judge.chat-model.base-url` | Yes | `string` | Base URL for the API endpoint (for example `http://localhost:11434/v1`). | | `judge.chat-model.api-key` | No | `string` | API key. Optional for local providers. | | `judge.chat-model.headers.*` | No | `map` | Custom HTTP headers. | | `judge.chat-model.timeout` | No | `duration` | Request timeout (ISO-8601 duration, for example `PT30S`). | | `judge.chat-model.temperature` | No | `double` | Temperature for response randomness (0.0 to 2.0). | **Example (Ollama):** ```yaml camunda: process-test: judge: chat-model: provider: "openai-compatible" model: "llama3" base-url: "http://localhost:11434/v1" ``` For providers not listed above, use a custom provider name and pass arbitrary properties. See [Custom ChatModelAdapter](#custom-chatmodeladapter) for implementation details. | Property | Required | Type | Description | | -------------------------------------- | -------- | ---------- | --------------------------------------------------------------------------------------------- | | `judge.chat-model.provider` | Yes | `string` | Custom provider name matching your SPI implementation. | | `judge.chat-model.model` | Yes | `string` | Model name. | | `judge.chat-model.custom-properties.*` | No | `map` | Arbitrary key-value pairs passed to SPI providers via `ProviderConfig.getCustomProperties()`. | | `judge.chat-model.timeout` | No | `duration` | Request timeout (ISO-8601 duration, for example `PT30S`). | | `judge.chat-model.temperature` | No | `double` | Temperature for response randomness (0.0 to 2.0). | **Example:** ```yaml camunda: process-test: judge: chat-model: provider: "my-custom-provider" model: "my-model" custom-properties: endpoint: "https://my-llm.example.com/v1" ``` ### Document attachment When `judge.attach-documents` is enabled, CPT scans the serialized variable JSON for [Camunda document](/components/document-handling/getting-started.md) references, downloads their content, and passes it to the judge alongside the text prompt as structured content blocks. This lets the judge evaluate document content, such as generated PDFs, images, or text files. Document attachment is disabled by default. Enable it globally: ```yaml camunda: process-test: judge: attach-documents: true ``` ```properties judge.attachDocuments=true ``` You can also enable it per assertion using `withJudgeConfig`: ```java assertThat(processInstance) .withJudgeConfig(config -> config.withAttachDocuments(true)) .hasVariableSatisfiesJudge("report", "Contains an executive summary with at least three key findings."); ``` #### Content type handling How the document is passed to the judge depends on its MIME content type: | Content type | Passed to judge as | | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | `image/*` | Inline image block | | `application/pdf` | PDF file block | | `text/*`, `application/json`, `application/xml`, `application/yaml`, `application/x-yaml`, or types with a structured suffix (`+json`, `+xml`, `+yaml`) | Inline text block (UTF-8) | | All other types | Placeholder text only; content is not inspectable. A warning is logged. | #### Behavior - Built-in LangChain4j providers implement `MultimodalChatModelAdapter`. Custom `ChatModelAdapter` implementations must implement `MultimodalChatModelAdapter` to receive documents. Otherwise, document attachment does not take effect and the judge evaluates only the raw variable JSON. - Documents with the same document ID and store ID are deduplicated and downloaded only once. - If a document fails to download, the assertion fails with an `IllegalStateException`. ### Custom prompt You can replace the default evaluation criteria with a custom prompt. The custom prompt replaces only the evaluation criteria (the "You are an impartial judge..." preamble). The system still controls the expectation and value injection, the scoring rubric, and the JSON output format. By default, CPT uses an internal prompt that instructs the model to act as an impartial judge, compare the provided value against the natural language expectation, apply the documented scoring rubric, and return the result in the expected JSON structure. ```yaml camunda: process-test: judge: custom-prompt: "You are a domain expert evaluating financial data accuracy." ``` ```properties judge.customPrompt=You are a domain expert evaluating financial data accuracy. ``` Or programmatically: ```java JudgeConfig.of(prompt -> myChatModelAdapter.generate(prompt)) .withCustomPrompt("You are a domain expert evaluating financial data accuracy."); ``` You can also override the custom prompt for a single assertion chain: ```java assertThat(processInstance) .withJudgeConfig(config -> config .withCustomPrompt("You are a domain expert evaluating financial data accuracy.")) .hasVariableSatisfiesJudge("result", "Contains valid totals."); ``` ### Custom ChatModelAdapter You can provide your own `ChatModelAdapter` implementation without depending on the `camunda-process-test-langchain4j` module. A `ChatModelAdapter` is a functional interface that takes a prompt string and returns a response string. If you have a single `ChatModelAdapter` bean and no `provider` property is set, CPT auto-detects and uses it: ```java @TestConfiguration class JudgeTestConfig { @Bean ChatModelAdapter chatModelAdapter() { return prompt -> myChatModelAdapter.generate(prompt); } } ``` When you have multiple beans, set `provider` to the bean name you want to use. In Spring, the bean name defaults to the method name: ```java @TestConfiguration class JudgeTestConfig { @Bean ChatModelAdapter openAiAdapter() { /* ... */ } @Bean ChatModelAdapter ollamaAdapter() { /* ... */ } } ``` ```yaml camunda: process-test: judge: chat-model: provider: "ollamaAdapter" # matches the bean method name ``` :::note Resolution order When using `@CamundaSpringProcessTest`, CPT resolves the judge adapter in the following order: 1. If a single `ChatModelAdapter` bean exists and no `provider` property is configured, that bean is used automatically. 2. If the `provider` property is configured and a bean with a matching name exists, that bean is selected. 3. If no matching bean is found, CPT falls back to the built-in LangChain4j implementations, provided that `camunda-process-test-langchain4j` is on the classpath. 4. If a `provider` is configured but no matching implementation can be resolved at all, CPT throws an exception. ::: Alternatively, you can configure the judge programmatically. Set the configuration globally using `CamundaAssert.setJudgeConfig()`: ```java CamundaAssert.setJudgeConfig( JudgeConfig.of(prompt -> myChatModelAdapter.generate(prompt)) .withThreshold(0.8)); ``` Implement `ChatModelAdapterProvider` and register it through `META-INF/services`: ```java public class MyCustomProvider implements ChatModelAdapterProvider { @Override public String getProviderName() { return "my-provider"; } @Override public ChatModelAdapter create(ProviderConfig config) { String endpoint = config.getCustomProperties().get("endpoint"); return prompt -> callEndpoint(endpoint, prompt); } } ``` Register the provider in `META-INF/services/io.camunda.process.test.api.judge.ChatModelAdapterProvider`: ``` com.example.MyCustomProvider ``` Alternatively, you can configure the judge programmatically. Set the configuration globally using `CamundaAssert.setJudgeConfig()`: ```java CamundaAssert.setJudgeConfig( JudgeConfig.of(prompt -> myChatModelAdapter.generate(prompt)) .withThreshold(0.8)); ``` Or register the JUnit extension manually with a judge configuration: ```java @RegisterExtension CamundaProcessTestExtension extension = new CamundaProcessTestExtension() .withJudgeConfig(JudgeConfig.of(prompt -> myChatModelAdapter.generate(prompt)) .withThreshold(0.8)); ``` #### Multimodal support To use [document attachment](#document-attachment) with a custom adapter, implement `MultimodalChatModelAdapter` instead of `ChatModelAdapter`. `MultimodalChatModelAdapter` extends `ChatModelAdapter` and adds a second `generate` overload that receives the resolved documents. Each `ResolvedDocument` provides the binary content via `getContent()` and metadata via `getDocumentId()`, `getFileName()`, and `getContentType()`. Pass each document to the provider as a native structured content block (image block, file block, or text block depending on the content type). Prefix each block with a text content header containing the document metadata so the judge can correlate the block back to the document reference in ``: ```java public class MyMultimodalAdapter implements MultimodalChatModelAdapter { @Override public String generate(String prompt) { return myClient.chat(prompt); } @Override public String generate(String prompt, List documents) { List parts = new ArrayList<>(); parts.add(new TextPart(prompt)); for (ResolvedDocument doc : documents) { // text header identifying this document block parts.add(new TextPart( "--- documentId=\"" + doc.getDocumentId() + "\" fileName=\"" + doc.getFileName() + "\" contentType=\"" + doc.getContentType() + "\" ---")); // binary content as a native structured block parts.add(new BinaryPart(doc.getContent(), doc.getContentType())); } return myClient.chat(parts); } } ``` Replace `TextPart` and `BinaryPart` with the content-block types your provider's SDK defines. If document attachment is enabled but the adapter only implements `ChatModelAdapter`, document attachment does not take effect and the judge evaluates only the raw variable JSON. ## Semantic similarity configuration [Semantic similarity assertions](assertions.md#hasvariablesimilarto) use a configured embedding model to compare process variables (or plain values) to an expected string using vector embeddings. The assertion converts both values to embeddings and compares them using cosine similarity. This section covers how to set up the embedding model provider and tune the similarity behavior. ### Prerequisites CPT provides an optional [LangChain4j](https://docs.langchain4j.dev/) integration module that ships with preconfigured support for major embedding model providers, such as OpenAI, Azure OpenAI, Amazon Bedrock, and OpenAI-compatible APIs. :::note LangChain4j requires Java 17+. ::: Camunda Process Test Spring includes the LangChain4j providers as a transitive dependency. No additional dependency or configuration is needed. Add the `camunda-process-test-langchain4j` dependency to your project: ```xml io.camunda camunda-process-test-langchain4j test ``` :::important You can provide your own embedding integration through a custom `EmbeddingModelAdapter`. In that case, this dependency is not required. See [custom EmbeddingModelAdapter](#custom-embeddingmodeladapter) for more details. ::: ### Property reference All semantic similarity properties are nested under `camunda.process-test.similarity` in Spring configuration. In Java properties files, use the `similarity.` prefix with camelCase keys. For example, `similarity.embedding-model.api-key` becomes `similarity.embeddingModel.apiKey`. :::note Unless noted otherwise, properties in the provider tables are required. ::: #### Similarity settings | Property | Type | Default | Description | | ------------------------------------------ | --------- | ------- | ---------------------------------------------------------------------------------------------------------------------------- | | `similarity.threshold` | `double` | `0.5` | Cosine similarity threshold (0.0 to 1.0) for the assertion to pass. | | `similarity.default-preprocessors-enabled` | `boolean` | `true` | When `true`, applies the default text preprocessors (lowercase, Unicode NFC, and whitespace normalization) before embedding. | The default threshold of 0.5 treats two strings as similar when their cosine similarity is at least 0.5. This is a practical default for AI-generated text, where wording and phrasing may vary between runs even when the meaning is the same. Increase the threshold when your assertion needs stricter semantic agreement. #### Embedding model settings | Property | Required | Type | Description | | --------------------------------------- | -------- | ---------- | ---------------------------------------------------------------------- | | `similarity.embedding-model.provider` | Yes | `string` | Set to `openai`. | | `similarity.embedding-model.model` | Yes | `string` | Model name (for example `text-embedding-3-small`). | | `similarity.embedding-model.api-key` | Yes | `string` | API key. | | `similarity.embedding-model.dimensions` | No | `integer` | Number of output dimensions for models that support custom dimensions. | | `similarity.embedding-model.timeout` | No | `duration` | Request timeout (ISO-8601 duration, for example `PT30S`). | **Example:** ```yaml camunda: process-test: similarity: embedding-model: provider: "openai" model: "text-embedding-3-small" api-key: ${OPENAI_API_KEY} ``` It supports Bedrock long-term API keys or AWS IAM credentials. It falls back to the [AWS default credentials provider chain](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/credentials-chain.html). :::note The AWS principal must be authorized to perform [`bedrock:InvokeModel`](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_InvokeModel.html) on the configured embedding model ARN. The model must also be [enabled for access](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html) in the chosen region. If you use Bedrock for both the judge chat model and the embedding model, each model requires a separate bedrock:InvokeModel grant; access to one does not imply access to the other. ::: | Property | Required | Type | Description | | --------------------------------------------------- | ------------------------------ | ---------- | ---------------------------------------------------------------------------------------------- | | `similarity.embedding-model.provider` | Yes | `string` | Set to `amazon-bedrock`. | | `similarity.embedding-model.model` | Yes | `string` | Model name (for example `amazon.titan-embed-text-v2:0`). | | `similarity.embedding-model.region` | No | `string` | AWS region (for example `eu-central-1`). | | `similarity.embedding-model.api-key` | No | `string` | Bedrock long-term API key. Optional if using IAM credentials or the default credentials chain. | | `similarity.embedding-model.credentials.access-key` | Conditionally, with secret key | `string` | AWS IAM access key. Optional if using an API key or the default credentials chain. | | `similarity.embedding-model.credentials.secret-key` | Conditionally, with access key | `string` | AWS IAM secret key. Optional if using an API key or the default credentials chain. | | `similarity.embedding-model.dimensions` | No | `integer` | Number of output dimensions for models that support custom dimensions. | | `similarity.embedding-model.normalize` | No | `boolean` | Whether to normalize the output embeddings. | | `similarity.embedding-model.timeout` | No | `duration` | Request timeout (ISO-8601 duration, for example `PT30S`). | **Example:** ```yaml camunda: process-test: similarity: embedding-model: provider: "amazon-bedrock" model: "amazon.titan-embed-text-v2:0" region: "eu-central-1" credentials: access-key: ${AWS_BEDROCK_ACCESS_KEY} secret-key: ${AWS_BEDROCK_SECRET_KEY} ``` It supports API key authentication. It falls back to [`DefaultAzureCredential`](https://learn.microsoft.com/en-us/java/api/com.azure.identity.defaultazurecredential). | Property | Required | Type | Description | | --------------------------------------- | -------- | ---------- | -------------------------------------------------------------------------------------------------------------------------- | | `similarity.embedding-model.provider` | Yes | `string` | Set to `azure-openai`. | | `similarity.embedding-model.model` | Yes | `string` | Azure [deployment name](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/create-resource#deploy-a-model). | | `similarity.embedding-model.endpoint` | Yes | `string` | Azure OpenAI resource URL (for example `https://my-resource.openai.azure.com/`). | | `similarity.embedding-model.api-key` | No | `string` | API key. Optional; if omitted, falls back to `DefaultAzureCredential`. | | `similarity.embedding-model.dimensions` | No | `integer` | Number of output dimensions for models that support custom dimensions. | | `similarity.embedding-model.timeout` | No | `duration` | Request timeout (ISO-8601 duration, for example `PT30S`). | **Example:** ```yaml camunda: process-test: similarity: embedding-model: provider: "azure-openai" model: "my-embedding-deployment" endpoint: "https://my-resource.openai.azure.com/" api-key: ${AZURE_OPENAI_API_KEY} ``` For local models, such as [Ollama](https://ollama.com/), or any third-party API that implements the [OpenAI embeddings format](https://platform.openai.com/docs/api-reference/embeddings). | Property | Required | Type | Description | | --------------------------------------- | -------- | ---------- | ------------------------------------------------------------------------ | | `similarity.embedding-model.provider` | Yes | `string` | Set to `openai-compatible`. | | `similarity.embedding-model.model` | Yes | `string` | Model name (for example `nomic-embed-text`). | | `similarity.embedding-model.base-url` | Yes | `string` | Base URL for the API endpoint (for example `http://localhost:11434/v1`). | | `similarity.embedding-model.api-key` | No | `string` | API key. Optional for local providers. | | `similarity.embedding-model.headers.*` | No | `map` | Custom HTTP headers. | | `similarity.embedding-model.dimensions` | No | `integer` | Number of output dimensions for models that support custom dimensions. | | `similarity.embedding-model.timeout` | No | `duration` | Request timeout (ISO-8601 duration, for example `PT30S`). | **Example (Ollama):** ```yaml camunda: process-test: similarity: embedding-model: provider: "openai-compatible" model: "nomic-embed-text" base-url: "http://localhost:11434/v1" ``` For providers not listed above, use a custom provider name and pass arbitrary properties. See [custom EmbeddingModelAdapter](#custom-embeddingmodeladapter) for implementation details. | Property | Required | Type | Description | | ------------------------------------------------ | -------- | ---------- | --------------------------------------------------------------------------------------------- | | `similarity.embedding-model.provider` | Yes | `string` | Custom provider name matching your SPI implementation. | | `similarity.embedding-model.model` | Yes | `string` | Model name. | | `similarity.embedding-model.custom-properties.*` | No | `map` | Arbitrary key-value pairs passed to SPI providers via `ProviderConfig.getCustomProperties()`. | | `similarity.embedding-model.timeout` | No | `duration` | Request timeout (ISO-8601 duration, for example `PT30S`). | **Example:** ```yaml camunda: process-test: similarity: embedding-model: provider: "my-custom-provider" model: "my-model" custom-properties: endpoint: "https://my-embeddings.example.com/v1" ``` ### Text preprocessors By default, CPT applies a set of text preprocessors to both the actual and expected values before computing embeddings. This improves stability of similarity scores by reducing noise from formatting differences. The default preprocessors are: - **Lowercase normalization**: converts text to lowercase. - **Unicode normalization**: applies Unicode NFC normalization. - **Whitespace normalization**: collapses repeated whitespace and trims leading/trailing whitespace. To disable the default preprocessors, set `similarity.default-preprocessors-enabled` to `false`: ```yaml camunda: process-test: similarity: default-preprocessors-enabled: false ``` ```properties similarity.defaultPreprocessorsEnabled=false ``` You can also configure preprocessors programmatically using `SemanticSimilarityConfig`: ```java SemanticSimilarityConfig.of(myEmbeddingAdapter, 0.7) .withoutPreprocessors(); ``` ### Custom EmbeddingModelAdapter You can provide your own `EmbeddingModelAdapter` implementation without depending on the `camunda-process-test-langchain4j` module. An `EmbeddingModelAdapter` is a functional interface that takes a string and returns a vector of floating-point numbers representing the text's semantic embedding. If you have a single `EmbeddingModelAdapter` bean and no `provider` property is set, CPT auto-detects and uses it: ```java @TestConfiguration class SimilarityTestConfig { @Bean EmbeddingModelAdapter embeddingModelAdapter() { return text -> myEmbeddingClient.embed(text); } } ``` When you have multiple beans, set `provider` to the bean name you want to use. In Spring, the bean name defaults to the method name: ```java @TestConfiguration class SimilarityTestConfig { @Bean EmbeddingModelAdapter openAiEmbeddingAdapter() { /* ... */ } @Bean EmbeddingModelAdapter ollamaEmbeddingAdapter() { /* ... */ } } ``` ```yaml camunda: process-test: similarity: embedding-model: provider: "ollamaEmbeddingAdapter" # matches the bean method name ``` :::note Resolution order When using `@CamundaSpringProcessTest`, CPT resolves the embedding adapter in the following order: 1. If a single `EmbeddingModelAdapter` bean exists and no `provider` property is configured, that bean is used automatically. 2. If the `provider` property is configured and a bean with a matching name exists, that bean is selected. 3. If no matching bean is found, CPT falls back to the built-in LangChain4j implementations, provided that `camunda-process-test-langchain4j` is on the classpath. 4. If a `provider` is configured but no matching implementation can be resolved at all, CPT throws an exception. ::: Alternatively, you can configure semantic similarity programmatically. Set the configuration globally using `CamundaAssert.setSemanticSimilarityConfig()`: ```java CamundaAssert.setSemanticSimilarityConfig( SemanticSimilarityConfig.of(text -> myEmbeddingClient.embed(text), 0.8)); ``` Implement `EmbeddingModelAdapterProvider` and register it through `META-INF/services`: ```java public class MyCustomEmbeddingProvider implements EmbeddingModelAdapterProvider { @Override public String getProviderName() { return "my-provider"; } @Override public EmbeddingModelAdapter create(ProviderConfig config) { String endpoint = config.getCustomProperties().get("endpoint"); return text -> callEndpoint(endpoint, text); } } ``` Register the provider in `META-INF/services/io.camunda.process.test.api.similarity.EmbeddingModelAdapterProvider`: ``` com.example.MyCustomEmbeddingProvider ``` Alternatively, you can configure semantic similarity programmatically. Set the configuration globally using `CamundaAssert.setSemanticSimilarityConfig()`: ```java CamundaAssert.setSemanticSimilarityConfig( SemanticSimilarityConfig.of(text -> myEmbeddingClient.embed(text), 0.8)); ``` Or register the JUnit extension manually with a semantic similarity configuration: ```java @RegisterExtension CamundaProcessTestExtension extension = new CamundaProcessTestExtension() .withSemanticSimilarityConfig( SemanticSimilarityConfig.of(text -> myEmbeddingClient.embed(text), 0.8)); ``` --- ## Connectors You can run your process test with [Connectors](/components/connectors/introduction.md) to verify the integration with external systems or the configuration of the connector tasks in your processes. For more unit-focused tests, mock the interaction; for example, by [completing connector jobs](utilities.md#mock-job-workers) with an expected result. :::note The instructions on this page are based on the default Testcontainer runtime. If you are using a remote runtime, consult the relevant distribution documentation, such as [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run/configuration.md#use-built-in-and-custom-connectors). ::: ## Enable connectors By default, the connectors are disabled. You need to change the configuration in the following way. In your `application.yml` (or `application.properties`): ```yaml camunda: process-test: # Enable connectors connectors-enabled: true ``` Or, directly on your test class: ```java @SpringBootTest(properties = {"camunda.process-test.connectors-enabled=true"}) @CamundaSpringProcessTest public class MyProcessTest { // } ``` In your `/camunda-container-runtime.properties` file: ```properties # Enable Connectors connectorsEnabled=true ``` Or, register the JUnit extension manually and use the fluent builder: ```java // No annotation: @CamundaProcessTest public class MyProcessTest { @RegisterExtension private static final CamundaProcessTestExtension EXTENSION = new CamundaProcessTestExtension() // Enable Connectors .withConnectorsEnabled(true); } ``` ## Set cluster secrets Cluster secrets are the default way to provide secrets in tests with Camunda 8.10+. They let processes that reference `=camunda.secrets.` resolve in your tests. This feature requires Camunda Process Test 8.10.6+. To learn more, see [secrets](/components/concepts/secrets.md) and [secret resolution](/components/concepts/secret-resolution.md). In your `application.yml` (or `application.properties`): ```yaml camunda: process-test: connectors-enabled: true cluster-secrets: GITHUB_TOKEN: ghp_secret SLACK_TOKEN: xoxb-secret ``` Or, on your test class: ```java @SpringBootTest( properties = { "camunda.process-test.connectors-enabled=true", "camunda.process-test.cluster-secrets.GITHUB_TOKEN=ghp_secret", "camunda.process-test.cluster-secrets.SLACK_TOKEN=xoxb-secret" } ) @CamundaSpringProcessTest public class MyProcessTest { // } ``` Register the JUnit extension and use the fluent builder: ```java // No annotation: @CamundaProcessTest public class MyProcessTest { @RegisterExtension private static final CamundaProcessTestExtension EXTENSION = new CamundaProcessTestExtension() .withConnectorsEnabled(true) .withClusterSecret("GITHUB_TOKEN", "ghp_secret") .withClusterSecret("SLACK_TOKEN", "xoxb-secret"); } ``` CPT drops trailing newlines from cluster secret values. For details, see the [cluster secrets configuration](./configuration.md#set-cluster-secrets). In a remote runtime, for example a real cluster or Camunda 8 Run, the cluster owns its secret store. CPT ignores the test properties and logs a warning. Configure the secrets on the cluster instead. If your process still uses the legacy `{{secrets.X}}` syntax, see [Connector secrets](#connector-secrets). To move to cluster secrets, see [migrate secrets](/components/connectors/use-connectors/migrate-secrets.md). ## Connector secrets If your processes use [connector secrets](/components/connectors/use-connectors/index.md#using-secrets) with the legacy `{{secrets.X}}` syntax, you can add the secrets to the test runtime in the following way. These properties don't populate the cluster secret store. For `=camunda.secrets.X` references, use [cluster secrets](#set-cluster-secrets). In your `application.yml` (or `application.properties`): ```yaml camunda: process-test: connectors-enabled: true connectors-secrets: GITHUB_TOKEN: ghp_secret SLACK_TOKEN: xoxb-secret ``` Or, on your test class: ```java @SpringBootTest( properties = { "camunda.process-test.connectors-enabled=true", "camunda.process-test.connectors-secrets.GITHUB_TOKEN=ghp_secret", "camunda.process-test.connectors-secrets.SLACK_TOKEN=xoxb-secret" } ) @CamundaSpringProcessTest public class MyProcessTest { // } ``` In your `/camunda-container-runtime.properties` file: ```properties connectorsEnabled=true connectorsSecrets.GITHUB_TOKEN=ghp_secret connectorsSecrets.SLACK_TOKEN=xoxb-secret ``` Or, via JUnit extension: ```java // No annotation: @CamundaProcessTest public class MyProcessTest { @RegisterExtension private static final CamundaProcessTestExtension EXTENSION = new CamundaProcessTestExtension() .withConnectorsEnabled(true) .withConnectorsSecret("GITHUB_TOKEN", "ghp_secret") .withConnectorsSecret("SLACK_TOKEN", "xoxb-secret"); } ``` ## Invoke an inbound connector You can retrieve the URL address to invoke an inbound connector in your test from the `CamundaProcessTestContext`. ```java @SpringBootTest @CamundaSpringProcessTest public class MyProcessTest { @Autowired private CamundaClient client; @Autowired private CamundaProcessTestContext processTestContext; @Test void shouldInvokeConnector() { // given: a process instance waiting at a connector event // when final String inboundConnectorAddress = processTestContext.getConnectorsAddress() + "/inbound/" + CONNECTOR_ID; // invoke the connector address, for example, via HTTP request // then: verify that the connector event is completed } } ``` ```java @CamundaProcessTest public class MyProcessTest { // to be injected private CamundaClient client; private CamundaProcessTestContext processTestContext; @Test void shouldInvokeConnector() { // given: a process instance waiting at a connector event // when final String inboundConnectorAddress = processTestContext.getConnectorsAddress() + "/inbound/" + CONNECTOR_ID; // invoke the connector address, for example, via HTTP request // then: verify that the connector event is completed } } ``` :::tip You might need to wrap the invocation of the connector in a retry loop, for example, by using [Awaitility](http://www.awaitility.org/). There can be a delay between verifying that the connectors event is active and opening the connectors inbound subscription. ::: ## Access host ports By default, the connectors run inside the Testcontainers environment in isolation and can't access your local machine. However, you can expose [host ports](https://java.testcontainers.org/features/networking/#exposing-host-ports-to-the-container) to the containers, for example, to invoke a mock HTTP server running on your local machine from an outbound REST connector. Expose the host ports using `TestContainers.exposeHostPorts(port)`. Inside the container, the local machine is available under the hostname `host.testcontainers.internal`. ```java @WireMockTest(httpPort = 9999) @SpringBootTest( properties = { "camunda.process-test.connectors-enabled=true", "camunda.process-test.connectors-secrets.BASE_URL=http://host.testcontainers.internal:9999" }) @CamundaSpringProcessTest public class MyProcessTest { @Autowired private CamundaClient client; @BeforeAll static void setup() { Testcontainers.exposeHostPorts(9999); } @Test void shouldInvokeUrlFromConnector() { // given: stub the HTTP server stubFor( get(urlPathMatching("/test")) .willReturn( aResponse() .withHeader("Content-Type", "application/json") .withStatus(200) .withBody("{\"status\":\"okay\"}"))); // when: a process instance invoked the outbound connector // then: verify the HTTP request CamundaAssert.assertThat(processInstance) .isCompleted() .hasVariable("status", "okay"); verify(getRequestedFor(urlEqualTo("/test"))); } } ``` ```java @WireMockTest(httpPort = 9999) public class MyProcessTest { @RegisterExtension private static final CamundaProcessTestExtension EXTENSION = new CamundaProcessTestExtension() .withConnectorsEnabled(true) .withConnectorsSecret("BASE_URL", "http://host.testcontainers.internal:9999"); private CamundaClient client; @BeforeAll static void setup() { Testcontainers.exposeHostPorts(9999); } @Test void shouldInvokeUrlFromConnector() { // given: stub the HTTP server stubFor( get(urlPathMatching("/test")) .willReturn( aResponse() .withHeader("Content-Type", "application/json") .withStatus(200) .withBody("{\"status\":\"okay\"}"))); // when: a process instance invoked the outbound connector // then: verify the HTTP request CamundaAssert.assertThat(processInstance) .isCompleted() .hasVariable("status", "okay"); verify(getRequestedFor(urlEqualTo("/test"))); } } ``` :::tip You can configure the URL of an outbound connector in your BPMN process using [Connectors secrets](/components/connectors/use-connectors/index.md#using-secrets) to replace it in the tests, for example, setting the URL expression to `"{{secrets.BASE_URL}}" + "/test"`. ::: If your process references the URL as a [cluster secret](#set-cluster-secrets), set the URL expression to `=camunda.secrets.BASE_URL + "/test"` and provide the value with `cluster-secrets` instead of `connectors-secrets`. ```java @SpringBootTest( properties = { "camunda.process-test.connectors-enabled=true", "camunda.process-test.cluster-secrets.BASE_URL=http://host.testcontainers.internal:9999" }) ``` ```java new CamundaProcessTestExtension() .withConnectorsEnabled(true) .withClusterSecret("BASE_URL", "http://host.testcontainers.internal:9999"); ``` ## Custom connectors By default, the runtime uses the built-in connectors bundle in a fixed, published version. This version isn't tied to the Camunda version or the Maven module version. To use another version, set `connectors-docker-image-version`. You can also use a custom connectors bundle in the following way. In your `application.yml` (or `application.properties`): ```yaml camunda: process-test: connectors-enabled: true connectors-docker-image-name: my-org/my-connectors connectors-docker-image-version: 1.0.0 ``` In your `/camunda-container-runtime.properties` file: ```properties connectorsEnabled=true connectorsDockerImageName=my-org/my-connectors connectorsDockerImageVersion=1.0.0 ``` Or, via JUnit extension: ```java // No annotation: @CamundaProcessTest public class MyProcessTest { @RegisterExtension private static final CamundaProcessTestExtension EXTENSION = new CamundaProcessTestExtension() .withConnectorsEnabled(true) .withConnectorsDockerImageName("my-org/my-connectors") .withConnectorsDockerImageVersion("1.0.0"); } ``` --- ## Camunda Process Test ## About [Camunda Process Test](https://github.com/camunda/camunda/tree/main/testing/camunda-process-test-java) (CPT) is a Java library to test your BPMN processes and your process application. CPT provides different runtimes to execute your process tests: - [Testcontainers runtime](configuration.md#testcontainers-runtime) (default) - A managed runtime based on [Testcontainers](https://java.testcontainers.org/) and Docker. - [Remote runtime](configuration.md#remote-runtime) - Your own runtime, such as, [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) :::info Public API CPT is part of the Camunda 8 [public API](/reference/public-api.md) and is covered by our SemVer stability guarantees (except for alpha features). Breaking changes will not be introduced in minor or patch releases. ::: :::note CPT is the successor to Zeebe Process Test (ZPT). Our previous testing library was removed in Camunda 8.10. See the [migration guide](/apis-tools/migration-manuals/migrate-to-camunda-process-test.md) on how to migrate your process tests. ::: ## Prerequisites - Java: - For the Camunda Java client: 8+ - For the Camunda Spring Boot Starter: 17+ - [JUnit 5](https://junit.org/junit5/) For the default [Testcontainers runtime](configuration.md#testcontainers-runtime): - A Docker-API compatible container runtime, such as Docker on Linux or Docker Desktop on Mac and Windows. ## Install CPT has two variants: - For the [Camunda Spring Boot Starter](/apis-tools/camunda-spring-boot-starter/getting-started.md) - For the [Camunda Java client](/apis-tools/java-client/getting-started.md) Choose the one depending on which library you use in your process application. Add the following dependency to your Maven project: ```xml io.camunda camunda-process-test-spring ${camunda.version} test ``` ### Spring Boot 3 support If you use the [dedicated Spring Boot 3 starter](/apis-tools/camunda-spring-boot-starter/getting-started.md#dedicated-spring-boot-3-and-4-modules) (`camunda-spring-boot-3-starter`), you must also use the dedicated Spring Boot 3 test artifact: ```xml io.camunda camunda-process-test-spring-boot-3 ${camunda.version} test ``` Add the following dependency to your Maven project: ```xml io.camunda camunda-process-test-java ${camunda.version} test ``` ## Write a test Create a new Java class with the following structure: ```java package com.example; @SpringBootTest @CamundaSpringProcessTest public class MyProcessTest { @Autowired private CamundaClient client; @Autowired private CamundaProcessTestContext processTestContext; @Test void shouldCreateProcessInstance() { // given process definition is deployed // when final ProcessInstanceEvent processInstance = client .newCreateInstanceCommand() .bpmnProcessId("my-process") .latestVersion() .send() .join(); // then CamundaAssert.assertThat(processInstance).isActive(); } } ``` - `@SpringBootTest` is the standard Spring annotation for tests. - `@CamundaSpringProcessTest` registers the Camunda test execution listener that starts and stops the test runtime. - `@Test` is the standard JUnit 5 annotation for a test case. - (_optional_) Inject a preconfigured `CamundaClient` to interact with the Camunda runtime. - (_optional_) Inject a `CamundaProcessTestContext` to interact with the test runtime. - (_optional_) Use `CamundaAssert` to verify the process instance state. The Spring test requires a Spring Boot process application in the same package. Usually, the process application [deploys the process resources](/apis-tools/camunda-spring-boot-starter/getting-started.md#deploy-process-models) using the annotation `@Deployment`. If you have no process application yet, you can add a minimal one inside the test class as follows: ```java @SpringBootApplication @Deployment(resources = "classpath*:/bpmn/**/*.bpmn") static class TestProcessApplication {} ``` ```java package com.example; @CamundaProcessTest public class MyProcessTest { // to be injected private CamundaClient client; private CamundaProcessTestContext processTestContext; @Test void shouldCreateProcessInstance() { // given client .newDeployResourceCommand() .addResourceFromClasspath("my-process.bpmn") .send() .join(); // when final ProcessInstanceEvent processInstance = client .newCreateInstanceCommand() .bpmnProcessId("my-process") .latestVersion() .send() .join(); // then CamundaAssert.assertThat(processInstance).isActive(); } } ``` - `@CamundaProcessTest` registers the Camunda JUnit extension that starts and stops the test runtime. - `@Test` is the standard JUnit 5 annotation for a test case. - (_optional_) Get a preconfigured `CamundaClient` injected to interact with the Camunda runtime. - (_optional_) Get a `CamundaProcessTestContext` injected to interact with the test runtime. - (_optional_) Use `CamundaAssert` to verify the process instance state. :::tip Shared runtime If you use the same runtime configuration for all test classes, you can use a [shared runtime](configuration.md#shared-runtime) to speed up the test execution. ::: ### Deploy resources You can deploy additional BPMN processes and other resources by adding the annotation `@TestDeployment` on the test class or the method. An annotation on the test method takes precedence over an annotation on the test class. The resources are loaded from the root classpath of the test. ```java @Test @TestDeployment(resources = "my-process.bpmn") void shouldCreateProcessInstance() { // the given resources are deployed before running the test } ``` ## Test lifecycle CPT performs the following actions during the JUnit 5 lifecycle when running a test class: - `beforeAll` (test methods) - Start the test runtime - `beforeEach` (test method) - Inject the `CamundaClient`, the `CamundaProcessTestContext`, and the `TestScenarioRunner` - Publish the client created event for the Spring Boot process application to trigger the deployment and start job workers - Deploy resources defined via `@TestDeployment` - `afterEach` (test method) - Collect the data for the coverage report - Print the created process instances if the test failed - Close the client connections - Publish the client closed event for the Spring Boot process application to stop job workers - Reset the Camunda runtime clock (can be disabled in the [configuration](configuration.md#test-cleanup-settings)) - Delete all data in the Camunda runtime (can be disabled in the [configuration](configuration.md#test-cleanup-settings)) - `afterAll` (test methods) - Generate the coverage report - Stop the test runtime ### Limitations CPT doesn't support Spring Boot process applications with `@PostConstruct` methods or a `CommandLineRunner` implementation. These methods are executed when the test class is initialized, but not before each test method. We recommend to use a minimal configuration for the test instead of the Spring Boot process application and invoke the `@PostConstruct` or `run()` methods manually before each test method. ```java @SpringBootTest(classes = {TestProcessApplication.class}) @CamundaSpringProcessTest public class ProcessTest { @Autowired private CamundaClient client; @BeforeEach void invokeProcessApplication() throws Exception { final Application springBootApplication = new Application(); springBootApplication.setCamundaClient(client); // call the @PostConstruct methods springBootApplication.afterStarted(); // call the CommandLineRunner method springBootApplication.run(); } } ``` Minimal test configuration: ```java // must be in a different package than the Spring Boot application package org.example.test; @SpringBootApplication( // list all required packages for the process test, such as job workers scanBasePackages = {"org.example.services", "org.example.workers"} ) @Deployment(resources = "classpath*:/bpmn/**/*.bpmn") public class TestProcessApplication {} ``` - `beforeAll` (test methods) - Start the test runtime - `beforeEach` (test method) - Inject the `CamundaClient`, the `CamundaProcessTestContext`, and the `TestScenarioRunner` - Deploy resources defined via `@TestDeployment` - `afterEach` (test method) - Collect the data for the coverage report - Print the created process instances if the test failed - Close the client connections - Reset the Camunda runtime clock (can be disabled in the [configuration](configuration.md#test-cleanup-settings)) - Delete all data in the Camunda runtime (can be disabled in the [configuration](configuration.md#test-cleanup-settings)) - `afterAll` (test methods) - Generate the coverage report - Stop the test runtime ## Next steps Learn more about the following topics: - `CamundaAssert` and [assertions](assertions.md) - `CamundaProcessTestContext` and [utilities](utilities.md) - How to [configure the runtime](configuration.md) - How to [test AI agent processes](/components/agentic-orchestration/evaluate-agents/test-ai-agents.md) - Best practices for [writing process tests](/components/best-practices/development/testing-process-definitions.md) Refer to the [API documentation](https://javadoc.io/doc/io.camunda/camunda-process-test-java/latest/io/camunda/process/test/api/package-summary.html) for details. ## Examples Take a look at the example project on [GitHub](https://github.com/camunda/camunda/tree/main/testing/camunda-process-test-example). This demonstrates the usage of the library for a demo Spring Boot process application. ## Process Test Coverage After a test run, CPT prints the coverage of your BPMN processes and DMN decision tables to the log and generates a detailed HTML and JSON report. You can use the report to identify untested paths in your processes and decision tables, and increase your test coverage. A link to the HTML report is printed in the log: ``` Coverage: io.camunda.InvoiceApprovalTest ======================== Process coverage: - Process_InvoiceApproval: 96% Decision coverage: - auto-approve-invoice: 20% Coverage report: file:///my/home/projects/my-process-application/target/coverage-report/report.html ``` ![An example process test coverage HTML report](assets/process-coverage-report.png) --- ## JSON test cases You can write your process tests in JSON format instead of coding the test logic in Java. The JSON file describes test cases with instructions that align with CPT's assertions and utilities. CPT's JSON test cases use the same schema as [test files in Test mode](/components/hub/workspace/modeler/validation/test-files.md), so you can edit the same files in Test mode and execute them with CPT. ## Write a JSON test case The JSON format is defined in the [JSON schema](https://camunda.com/json-schema/cpt-test-cases/8.10/schema.json). It defines the following structure: - `testCases`: An array of test cases to be executed. - `name`: The name of the test case. - `description`: A description of the test case. - `instructions`: An array of [instructions](#reference-instructions) to execute the test case. - Each instruction has a `type` that defines the action to be performed (for example, `CREATE_PROCESS_INSTANCE`). - Additional properties depend on the instruction type (for example, process definition ID and variables). How to start: 1. Create a new JSON file in your test resources folder (for example, `src/test/resources/test-cases/invoice-approval.json`) 2. Refer to the JSON schema `https://camunda.com/json-schema/cpt-test-cases/8.10/schema.json` in the `$schema` property. Use the same schema version as the CPT version you are using to ensure compatibility. 3. Add your test cases and use the [available instructions](#reference-instructions) to define the behavior of your process test. The basic structure of the JSON file looks like this: ```JSON { "$schema": "https://camunda.com/json-schema/cpt-test-cases/8.10/schema.json", "testCases": [ { "name": "My first test case", "description": "A human-readable description of the test case.", "instructions": [ ] } ] } ``` You can find a full example of a JSON test case file in the [Examples](#examples) section below. :::tip Use AI to support the generation of your JSON files. Refer to the documentation, provide a description of your test case, and your BPMN processes to get a first draft of your test cases. Or, use an IDE with JSON schema support to get auto-completion and validation while writing your test cases, for example [IntelliJ IDEA](https://www.jetbrains.com/help/idea/json.html#ws_json_schema_add_custom). ::: ## Run a JSON test case You can run your JSON test case files as parameterized JUnit tests. Add the `@TestCaseSource` annotation to your test method to read the files and provide test cases as arguments. Then, execute the test cases using the `TestCaseRunner` provided by CPT. The runner executes the test case instructions by leveraging CPT's assertions and utilities. If an assertion instruction fails, the runner throws an assertion error, causing the test to fail. If all instructions pass, the test case is considered successful. ```java @SpringBootTest @CamundaSpringProcessTest public class MyProcessTest { @Autowired private TestCaseRunner testCaseRunner; @ParameterizedTest @TestCaseSource void shouldPass(final TestCase testCase, final String fileName) { // given: the process definitions are deployed // when/then: run and verify the test case testCaseRunner.run(testCase); } } ``` ```java @CamundaProcessTest public class MyProcessTest { private TestCaseRunner testCaseRunner; @ParameterizedTest @TestCaseSource void shouldPass(final TestCase testCase, final String fileName) { // given: the process definitions are deployed // when/then: run and verify the test case testCaseRunner.run(testCase); } } ``` You can set the following fields in the `@TestCaseSource` annotation to configure which files to load: - `directory`: The classpath directory to scan for JSON test case files. Defaults to `/test-cases`. - `fileNames`: An array of specific file names to load from the directory. If not set, all files in the directory are loaded. - `fileExtension`: The file extension to filter files in the directory. Defaults to `json`. The filter is ignored if `fileNames` is set. ### Connect your process application The `TestCaseRunner` integrates seamlessly with CPT's [test lifecycle](getting-started.md#test-lifecycle). It connects to your process application and starts job workers, if enabled. You can add additional steps before and after running the test case, for example, to deploy additional resources or to mock external services of your process application. ```java @SpringBootTest @CamundaSpringProcessTest public class MyProcessTest { @Autowired private CamundaClient client; @Autowired private CamundaProcessTestContext processTestContext; @Autowired private TestCaseRunner testCaseRunner; @MockitoBean private AccountingService accountingService; @ParameterizedTest @TestCaseSource void shouldPass(final TestCase testCase, final String fileName) { // given: the process definitions are deployed via @Deployment on the process application // optionally: set up mocks, job workers, etc. // when/then: run and verify the test case testCaseRunner.run(testCase); // optionally: verify mock invocations, external resources, etc. Mockito.verify(accountingService).addInvoiceToAccount("0815", "INV-1001"); } } ``` ```java @CamundaProcessTest @ExtendWith(MockitoExtension.class) public class MyProcessTest { private CamundaClient client; private CamundaProcessTestContext processTestContext; private TestCaseRunner testCaseRunner; // Inject the mock in the process application @Mock private AccountingService accountingService; @ParameterizedTest @TestCaseSource @TestDeployment(resources = "invoice-approval.bpmn") void shouldPass(final TestCase testCase, final String fileName) { // given: the process definitions are deployed via @TestDeployment // optionally: set up mocks, job workers, etc. // when/then: run and verify the test case testCaseRunner.run(testCase); // optionally: verify mock invocations, external resources, etc. Mockito.verify(accountingService).addInvoiceToAccount("0815", "INV-1001"); } } ``` ## Examples You can find some example process tests using JSON test cases on [GitHub](https://github.com/camunda/camunda/tree/main/testing/camunda-process-test-example), like the following one: ```json reference referenceLinkText="Source" title="Invoice Approval JSON test case" https://github.com/camunda/camunda/blob/stable/8.10/testing/camunda-process-test-example/src/test/resources/test-cases/invoice-approval.json ``` ## Reference: Instructions Instructions define the actions and assertions to be performed in a test case. Each instruction has a `type` property that identifies the instruction, along with additional properties specific to that instruction type. ### ASSERT_DECISION An instruction to assert the evaluation of a decision. See the [assertions documentation](assertions.md#decision-assertions) for more details. Property Description Type Required Default type Instruction type, must be "ASSERT_DECISION" string Yes decisionSelector The selector to identify the decision. DecisionSelector Yes output Expected output of the decision. Can be any JSON type. any No matchedRules Expected matched rule indexes array of integer No notMatchedRules Expected not matched rule indexes array of integer No noMatchedRules Assert that no rules were matched boolean No false Example: ```json { "type": "ASSERT_DECISION", "decisionSelector": { "decisionDefinitionId": "ChooseRocket" }, "output": { "rocket": "Ariane 6" }, "matchedRules": [3] } ``` ### ASSERT_ELEMENT_INSTANCE An instruction to assert the state of an element instance. See the [assertions documentation](assertions.md#element-instance-assertions) for more details. Property Description Type Required Default type Instruction type, must be "ASSERT_ELEMENT_INSTANCE" string Yes processInstanceSelector The selector to identify the process instance. ProcessInstanceSelector Yes elementSelector The selector to identify the element. ElementSelector Yes state The expected state of the element instance. enum: IS_ACTIVE, IS_COMPLETED, IS_TERMINATED Yes amount The expected amount of element instances in the given state. integer (minimum: 1) No 1 Example: ```json { "type": "ASSERT_ELEMENT_INSTANCE", "processInstanceSelector": { "processDefinitionId": "MoonExplorationProcess" }, "elementSelector": { "elementId": "LaunchRocket" }, "state": "IS_COMPLETED" } ``` ### ASSERT_ELEMENT_INSTANCES An instruction to assert the state of multiple element instances. See the [assertions documentation](assertions.md#element-instance-assertions) for more details. Property Description Type Required Default type Instruction type, must be "ASSERT_ELEMENT_INSTANCES" string Yes processInstanceSelector The selector to identify the process instance. ProcessInstanceSelector Yes elementSelectors The selectors to identify the elements. array of ElementSelector Yes state The expected state of the element instances. enum: IS_ACTIVE, IS_COMPLETED, IS_TERMINATED, IS_NOT_ACTIVE, IS_NOT_ACTIVATED, IS_ACTIVE_EXACTLY, IS_COMPLETED_IN_ORDER Yes Example: ```json { "type": "ASSERT_ELEMENT_INSTANCES", "processInstanceSelector": { "processDefinitionId": "MoonExplorationProcess" }, "elementSelectors": [ { "elementId": "PrepareMission" }, { "elementId": "LaunchRocket" }, { "elementId": "LandOnMoon" } ], "state": "IS_COMPLETED_IN_ORDER" } ``` ### ASSERT_PROCESS_INSTANCE An instruction to assert the state of a process instance. See the [assertions documentation](assertions.md#process-instance-assertions) for more details. Property Description Type Required Default type Instruction type, must be "ASSERT_PROCESS_INSTANCE" string Yes processInstanceSelector The selector to identify the process instance. ProcessInstanceSelector Yes state The expected state of the process instance. enum: IS_ACTIVE, IS_COMPLETED, IS_CREATED, IS_TERMINATED No hasActiveIncidents Whether the process instance has active incidents. boolean No Example: ```json { "type": "ASSERT_PROCESS_INSTANCE", "processInstanceSelector": { "processDefinitionId": "MoonExplorationProcess" }, "state": "IS_COMPLETED" } ``` ### ASSERT_PROCESS_INSTANCE_MESSAGE_SUBSCRIPTION An instruction to assert the state of a process instance message subscription. See the [assertions documentation](assertions.md#process-instance-message-assertions) for more details. Property Description Type Required Default type Instruction type, must be "ASSERT_PROCESS_INSTANCE_MESSAGE_SUBSCRIPTION" string Yes processInstanceSelector The selector to identify the process instance. ProcessInstanceSelector Yes messageSelector The selector to identify the message. MessageSelector Yes state The expected state of the message subscription. enum: IS_WAITING, IS_NOT_WAITING, IS_CORRELATED Yes Example: ```json { "type": "ASSERT_PROCESS_INSTANCE_MESSAGE_SUBSCRIPTION", "processInstanceSelector": { "processDefinitionId": "MoonExplorationProcess" }, "messageSelector": { "messageName": "AstronautReady" }, "state": "IS_CORRELATED" } ``` ### ASSERT_USER_TASK An instruction to assert the state of a user task. See the [assertions documentation](assertions.md#user-task-assertions) for more details. Property Description Type Required Default type Instruction type, must be "ASSERT_USER_TASK" string Yes userTaskSelector The selector to identify the user task. UserTaskSelector Yes state The expected state of the user task. enum: IS_CREATED, IS_COMPLETED, IS_CANCELED, IS_FAILED No assignee The expected assignee of the user task. string No candidateGroups The expected candidate groups of the user task. array of string No priority The expected priority of the user task. integer No elementId The expected element ID of the user task. string No name The expected name of the user task. string No dueDate The expected due date of the user task in ISO-8601 format. string No followUpDate The expected follow-up date of the user task in ISO-8601 format. string No Example: ```json { "type": "ASSERT_USER_TASK", "userTaskSelector": { "elementId": "ReviewMissionPlan" }, "state": "IS_CREATED", "assignee": "zee-astronaut", "priority": 100 } ``` ### ASSERT_VARIABLE An instruction to assert a single variable of a process instance. See the [assertions documentation](assertions.md#variable-assertions) for more details. Property Description Type Required type Instruction type, must be "ASSERT_VARIABLE" string Yes processInstanceSelector The selector to identify the process instance. ProcessInstanceSelector Yes elementSelector The selector to identify the element for local variables. ElementSelector No variableName The name of the variable to evaluate. string Yes satisfiesExpression A FEEL expression assertion that must evaluate to true for the given variable. string No satisfiesJudge An LLM judge assertion that evaluates the variable against a semantic expectation. JudgeAssertion No similarTo A semantic similarity assertion that checks the variable value against an expected value using text embeddings. SemanticSimilarityAssertion No Example: ```json { "type": "ASSERT_VARIABLE", "processInstanceSelector": { "processDefinitionId": "MoonExplorationProcess" }, "variableName": "mission", "satisfiesExpression": "mission.status = \"completed\" and list contains(mission.astronauts, \"Zee\")" } ``` #### Judge Assertion An LLM-as-judge assertion that evaluates a variable against a semantic expectation. Property Description Type Required expectation The semantic expectation for the variable value. string Yes threshold The score threshold (0.0–1.0) at or above which the assertion passes. Defaults to the threshold configured in the CPT runtime (0.5 if not configured). number (0.0–1.0) No customPrompt A custom prompt for the judge evaluation. Overrides the configured custom prompt. string No attachDocuments When true, resolves Camunda document references in the variable value and attaches their content to the judge. Overrides the configured judge.attach-documents setting. To evaluate attached content, use a multimodal-capable model; otherwise, CPT evaluates only the raw variable JSON. boolean No Example: ```json { "type": "ASSERT_VARIABLE", "processInstanceSelector": { "processDefinitionId": "MoonExplorationProcess" }, "variableName": "missionSummary", "satisfiesJudge": { "expectation": "The summary confirms a successful moon landing.", "threshold": 0.8 } } ``` #### Semantic Similarity Assertion A semantic similarity assertion that checks a variable value against an expected value using text embeddings. Property Description Type Required expectedValue The expected value the variable should be semantically similar to. string Yes threshold The minimum similarity score (0.0–1.0) for the assertion to pass. Defaults to the threshold configured in the CPT runtime (0.5 if not configured). number (0.0–1.0) No Example: ```json { "type": "ASSERT_VARIABLE", "processInstanceSelector": { "processDefinitionId": "MoonExplorationProcess" }, "elementSelector": { "elementId": "ReviewMissionPlan" }, "variableName": "reviewComment", "similarTo": { "expectedValue": "The mission plan meets the required standards.", "threshold": 0.85 } } ``` ### ASSERT_VARIABLES An instruction to assert the variables of a process instance. See the [assertions documentation](assertions.md#variable-assertions) for more details. Property Description Type Required Default type Instruction type, must be "ASSERT_VARIABLES" string Yes processInstanceSelector The selector to identify the process instance. ProcessInstanceSelector Yes elementSelector The selector to identify the element for local variables. ElementSelector No variableNames The expected variable names. array of string No variables The expected variables with their values. object No Example: ```json { "type": "ASSERT_VARIABLES", "processInstanceSelector": { "processDefinitionId": "MoonExplorationProcess" }, "variables": { "missionStatus": "completed", "astronautName": "Zee" } } ``` ### BROADCAST_SIGNAL An instruction to broadcast a signal. Property Description Type Required Default type Instruction type, must be "BROADCAST_SIGNAL" string Yes signalName The name of the signal to broadcast. string Yes variables The variables to broadcast with the signal. object No Example: ```json { "type": "BROADCAST_SIGNAL", "signalName": "EmergencyEvacuation", "variables": { "reason": "meteor-shower", "destination": "space-station" } } ``` ### COMPLETE_JOB An instruction to complete a job. See the [utilities documentation](utilities.md#complete-jobs) for more details. Property Description Type Required Default type Instruction type, must be "COMPLETE_JOB" string Yes jobSelector The selector to identify the job to complete. JobSelector Yes variables The variables to complete the job with. object No useExampleData Whether to complete the job with example data from the BPMN element. This property has precedence over variables. boolean No false Example: ```json { "type": "COMPLETE_JOB", "jobSelector": { "jobType": "analyze-moon-samples" }, "variables": { "analysisResult": "high-mineral-content" } } ``` ### COMPLETE_JOB_AD_HOC_SUB_PROCESS An instruction to complete a job of an ad-hoc sub-process. See the [utilities documentation](utilities.md#ad-hoc-sub-process-jobs) for more details. Property Description Type Required Default type Instruction type, must be "COMPLETE_JOB_AD_HOC_SUB_PROCESS" string Yes jobSelector The selector to identify the job to complete. JobSelector Yes variables The variables to complete the job with. object No activateElements The elements to activate in the ad-hoc sub-process. array of ActivateElementInstruction No cancelRemainingInstances Whether to cancel remaining instances of the ad-hoc sub-process. boolean No false completionConditionFulfilled Whether the completion condition of the ad-hoc sub-process is fulfilled. boolean No false #### Activate Element Instruction An instruction to activate an element in an ad-hoc sub-process. Property Description Type Required elementId The ID of the element to activate. string Yes variables The variables to set when activating the element. object No Example: ```json { "type": "COMPLETE_JOB_AD_HOC_SUB_PROCESS", "jobSelector": { "elementId": "conduct-experiment" }, "variables": { "experimentResult": "success" }, "activateElements": [ { "elementId": "CollectMoonSamples", "variables": { "sampleType": "regolith" } } ] } ``` ### COMPLETE_JOB_USER_TASK_LISTENER An instruction to complete a job of a user task listener. See the [utilities documentation](utilities.md#user-task-listener-jobs) for more details. Property Description Type Required Default type Instruction type, must be "COMPLETE_JOB_USER_TASK_LISTENER" string Yes jobSelector The selector to identify the job to complete. JobSelector Yes denied Whether the worker denies the work. boolean No false deniedReason The reason for denying the job. string No corrections The corrections to apply to the user task. Only applicable if denied is false. UserTaskCorrections No #### User Task Corrections The corrections to apply to a user task. Property Description Type Required assignee The assignee of the task. string No dueDate The due date of the task. string No followUpDate The follow up date of the task. string No candidateUsers The candidate users of the task. array of string No candidateGroups The candidate groups of the task. array of string No priority The priority of the task. integer No Example: ```json { "type": "COMPLETE_JOB_USER_TASK_LISTENER", "jobSelector": { "jobType": "validate-astronaut-assignment" }, "corrections": { "assignee": "zee-senior-astronaut", "priority": 50 } } ``` ### COMPLETE_USER_TASK An instruction to complete a user task. See the [utilities documentation](utilities.md#complete-user-tasks) for more details. Property Description Type Required Default type Instruction type, must be "COMPLETE_USER_TASK" string Yes userTaskSelector The selector to identify the user task to complete. UserTaskSelector Yes variables The variables to set when completing the user task. Ignored if useExampleData is true. object No useExampleData Whether to complete the user task with example data from the BPMN element. If true, the variables property is ignored. boolean No false Example: ```json { "type": "COMPLETE_USER_TASK", "userTaskSelector": { "elementId": "ReviewMissionPlan" }, "variables": { "approved": true, "comments": "Mission plan looks good for moon exploration" } } ``` ### CONDITIONAL_BEHAVIOR An instruction to register a conditional behavior that reacts to process state changes. See the [utilities documentation](utilities.md#conditional-behavior) for more details. The conditions form a conjunction; the behavior fires only when every assertion succeeds. Actions are consumed in order: the first match fires the first action, the second match fires the second, and the last action repeats indefinitely. Property Description Type Required Default type Instruction type, must be "CONDITIONAL_BEHAVIOR" string Yes name A descriptive name for diagnostics and log messages. string No conditions The ASSERT_* instructions to watch. The behavior fires only when every condition succeeds (conjunction). array of instructions Yes actions The action instructions to execute when all conditions are met. Consumed in order; the last action repeats indefinitely. array of instructions Yes Example: ```json { "type": "CONDITIONAL_BEHAVIOR", "name": "auto-complete-review-task", "conditions": [ { "type": "ASSERT_USER_TASK", "userTaskSelector": { "taskName": "Review Task" }, "state": "IS_CREATED" } ], "actions": [ { "type": "COMPLETE_USER_TASK", "userTaskSelector": { "taskName": "Review Task" }, "variables": { "approved": true } } ] } ``` ### CORRELATE_MESSAGE An instruction to correlate a message. Property Description Type Required Default type Instruction type, must be "CORRELATE_MESSAGE" string Yes name The name of the message. string Yes correlationKey The correlation key of the message. string No variables The variables to correlate with the message. object No Example: ```json { "type": "CORRELATE_MESSAGE", "name": "AstronautReady", "correlationKey": "mission-001", "variables": { "astronautName": "Zee", "status": "ready-for-launch" } } ``` ### CREATE_PROCESS_INSTANCE An instruction to create a new process instance. Property Description Type Required Default type Instruction type, must be "CREATE_PROCESS_INSTANCE" string Yes processDefinitionSelector The selector to identify the process definition to create the process instance for. ProcessDefinitionSelector Yes variables The variables to create the process instance with. object No startInstructions The instructions to execute when starting the process instance. array of StartInstruction No runtimeInstructions The instructions to affect the runtime behavior of the process instance. array of RuntimeInstruction No #### Start Instruction An instruction to execute when starting a process instance. Property Description Type Required elementId The ID of the element to start the process instance at. string Yes #### Runtime Instruction An instruction to affect the runtime behavior of a process instance. Property Description Type Required type The type of the runtime instruction. Currently supports "TERMINATE_PROCESS_INSTANCE". string Yes afterElementId The ID of the element after which to terminate the process instance. Required when type is "TERMINATE_PROCESS_INSTANCE". string Yes Example: ```json { "type": "CREATE_PROCESS_INSTANCE", "processDefinitionSelector": { "processDefinitionId": "MoonExplorationProcess" }, "variables": { "missionName": "Artemis-Zee", "destination": "Moon", "astronautCount": 4 } } ``` ### EVALUATE_CONDITIONAL_START_EVENT An instruction to evaluate conditional start events. Property Description Type Required Default type Instruction type, must be "EVALUATE_CONDITIONAL_START_EVENT" string Yes variables The variables to evaluate the conditional start events with. object Yes Example: ```json { "type": "EVALUATE_CONDITIONAL_START_EVENT", "variables": { "weatherCondition": "clear", "fuelLevel": 100 } } ``` ### EVALUATE_DECISION An instruction to evaluate a DMN decision. Property Description Type Required Default type Instruction type, must be "EVALUATE_DECISION" string Yes decisionDefinitionSelector The selector to identify the decision definition to evaluate. DecisionDefinitionSelector Yes variables The variables to evaluate the decision with. object No Example: ```json { "type": "EVALUATE_DECISION", "decisionDefinitionSelector": { "decisionDefinitionId": "ChooseRocket" }, "variables": { "payload": 5000, "destination": "Moon" } } ``` ### INCREASE_TIME An instruction to increase the time. See the [utilities documentation](utilities.md#increase-time) for more details. Property Description Type Required Default type Instruction type, must be "INCREASE_TIME" string Yes duration The duration to increase the time by, in ISO 8601 duration format (for example, "PT1H", "P2D"). string Yes Example: ```json { "type": "INCREASE_TIME", "duration": "P3D" } ``` ### MOCK_CHILD_PROCESS An instruction to mock a child process. See the [utilities documentation](utilities.md#mock-child-processes) for more details. Property Description Type Required Default type Instruction type, must be "MOCK_CHILD_PROCESS" string Yes processDefinitionId The ID of the child process to mock. string Yes variables The variables to set for the mocked child process. object No versionTag The version tag for the deployed stub process. Required when the call activity uses bindingType="versionTag". string No Example: ```json { "type": "MOCK_CHILD_PROCESS", "processDefinitionId": "AstronautTrainingProcess", "variables": { "trainingCompleted": true, "grade": "excellent" } } ``` ### MOCK_DMN_DECISION An instruction to mock a DMN decision. See the [utilities documentation](utilities.md#mock-dmn-decisions) for more details. Property Description Type Required Default type Instruction type, must be "MOCK_DMN_DECISION" string Yes decisionDefinitionId The decision definition ID to mock. string Yes variables The variables to set as the decision output. Deprecated, use decisionOutput. object No decisionOutput The decision output to mock. Can be any JSON type. any No Example: ```json { "type": "MOCK_DMN_DECISION", "decisionDefinitionId": "ChooseRocket", "decisionOutput": "Falcon Heavy" } ``` ### MOCK_JOB_WORKER_COMPLETE_JOB An instruction to mock a job worker who completes jobs. See the [utilities documentation](utilities.md#complete-job) for more details. Property Description Type Required Default type Instruction type, must be "MOCK_JOB_WORKER_COMPLETE_JOB" string Yes jobType The job type to mock. This should match the zeebeJobType in the BPMN model. string Yes variables The variables to complete the job with. object No useExampleData Whether to use example data from the BPMN element. If true, the variables property is ignored. boolean No false Example: ```json { "type": "MOCK_JOB_WORKER_COMPLETE_JOB", "jobType": "calculate-trajectory", "variables": { "trajectory": "optimal", "fuelConsumption": 450 } } ``` ### MOCK_JOB_WORKER_THROW_BPMN_ERROR An instruction to mock a job worker who throws BPMN errors. See the [utilities documentation](utilities.md#throw-bpmn-error) for more details. Property Description Type Required Default type Instruction type, must be "MOCK_JOB_WORKER_THROW_BPMN_ERROR" string Yes jobType The job type to mock. This should match the zeebeJobType in the BPMN model. string Yes errorCode The error code to throw. This should match the error code in an error catch event. string Yes errorMessage The error message to include when throwing the error. string No variables The variables to include when throwing the error. object No Example: ```json { "type": "MOCK_JOB_WORKER_THROW_BPMN_ERROR", "jobType": "launch-rocket", "errorCode": "WEATHER_UNSUITABLE", "errorMessage": "High winds detected" } ``` ### PUBLISH_MESSAGE An instruction to publish a message. Property Description Type Required Default type Instruction type, must be "PUBLISH_MESSAGE" string Yes name The name of the message. string Yes correlationKey The correlation key of the message. string No variables The variables to publish with the message. object No timeToLive The time-to-live of the message in milliseconds. integer No messageId The message ID for uniqueness. string No Example: ```json { "type": "PUBLISH_MESSAGE", "name": "LaunchApproved", "correlationKey": "mission-001", "variables": { "approvedBy": "mission-control", "launchWindow": "2026-03-15T10:00:00Z" } } ``` ### RESOLVE_INCIDENT An instruction to resolve an incident. See the [utilities documentation](utilities.md#resolve-incidents) for more details. Property Description Type Required Default type Instruction type, must be "RESOLVE_INCIDENT" string Yes incidentSelector The selector to identify the incident to resolve. IncidentSelector Yes Example: ```json { "type": "RESOLVE_INCIDENT", "incidentSelector": { "elementId": "LaunchRocket" } } ``` ### SET_TIME An instruction to set the time. See the [utilities documentation](utilities.md#set-time) for more details. Property Description Type Required Default type Instruction type, must be "SET_TIME" string Yes time The time to set, in ISO 8601 instant format (for example, "2026-01-19T13:00:00Z"). string Yes Example: ```json { "type": "SET_TIME", "time": "2026-03-15T10:00:00Z" } ``` ### THROW_BPMN_ERROR_FROM_JOB An instruction to throw a BPMN error from a job. See the [utilities documentation](utilities.md#throw-bpmn-errors-from-jobs) for more details. Property Description Type Required Default type Instruction type, must be "THROW_BPMN_ERROR_FROM_JOB" string Yes jobSelector The selector to identify the job to throw the error from. JobSelector Yes errorCode The error code to throw. string Yes errorMessage The error message to throw. string No variables The variables to set when throwing the error. object No Example: ```json { "type": "THROW_BPMN_ERROR_FROM_JOB", "jobSelector": { "jobType": "deploy-satellite" }, "errorCode": "DEPLOYMENT_FAILED", "errorMessage": "Insufficient orbital velocity" } ``` ### UPDATE_VARIABLES An instruction to create or update process instance variables. See the [utilities documentation](utilities.md#update-variables) for more details. Property Description Type Required Default type Instruction type, must be "UPDATE_VARIABLES" string Yes processInstanceSelector The selector to identify the process instance. ProcessInstanceSelector Yes variables The variables to create or update. object Yes elementSelector The selector to identify the element for local variables. ElementSelector No createLocalVariables Whether to create variables locally in the scope of the element (requires elementSelector). When true, variables are created in the element's local scope and are not propagated to parent scopes. boolean No false Example: ```json { "type": "UPDATE_VARIABLES", "processInstanceSelector": { "processDefinitionId": "MoonExplorationProcess" }, "variables": { "currentPhase": "landing", "fuelRemaining": 75 } } ``` ## Reference: Selectors Selectors are used to identify specific resources in your process tests. Each selector must contain at least one of the specified properties. ### Decision Definition Selector A selector to identify a decision definition. Property Description Type Required decisionDefinitionId ID of the decision definition string Yes Example: ```json { "decisionDefinitionId": "ChooseRocket" } ``` ### Decision Selector A selector to identify a decision. The selector must contain at least one of the following properties: Property Description Type Required decisionDefinitionId ID of the decision definition string No decisionDefinitionName Name of the decision definition string No Example: ```json { "decisionDefinitionId": "ChooseRocket" } ``` ### Element Selector A selector to identify a BPMN element. The selector must contain at least one of the following properties: Property Description Type Required elementId ID of the BPMN element string No elementName Name of the BPMN element string No Example: ```json { "elementId": "LaunchRocket" } ``` ### Incident Selector A selector to identify an incident. The selector must contain at least one of the following properties: Property Description Type Required elementId ID of the BPMN element where the incident occurred string No processDefinitionId Process definition ID of the incident string No Example: ```json { "elementId": "LaunchRocket" } ``` ### Job Selector A selector to identify a job. The selector must contain at least one of the following properties: Property Description Type Required jobType Type of the job string No elementId ID of the BPMN element string No processDefinitionId Process definition ID of the job string No Example: ```json { "jobType": "analyze-moon-samples" } ``` ### Message Selector A selector to identify a message. Property Description Type Required messageName Name of the message string Yes correlationKey Correlation key of the message string No Example: ```json { "messageName": "AstronautReady" } ``` ### Process Definition Selector A selector to identify a process definition. Property Description Type Required processDefinitionId ID of the process definition string Yes Example: ```json { "processDefinitionId": "MoonExplorationProcess" } ``` ### Process Instance Selector A selector to identify a process instance. Property Description Type Required processDefinitionId Process definition ID of the process instance string Yes ```json { "processDefinitionId": "MoonExplorationProcess" } ``` ### User Task Selector A selector to identify a user task. The selector must contain at least one of the following properties: Property Description Type Required elementId ID of the BPMN element string No taskName Name of the user task string No processDefinitionId Process definition ID of the user task string No Example: ```json { "elementId": "ReviewMissionPlan" } ``` --- ## Utilities There are different utilities that can help you to write your process test. ## Manipulate the clock The Camunda runtime uses an internal clock to execute process instances and to calculate when a BPMN timer event is due. In a test, you can use `CamundaProcessTestContext` to manipulate the clock. When to use it: - Trigger an active BPMN timer event - Test scenarios that require a specific date or time, for example, a leap year :::tip If you trigger a BPMN timer event, you should assert that the BPMN timer event is active before manipulating the clock. Otherwise, you may manipulate the clock too early and the BPMN timer event is not triggered. ::: ### Increase time You can increase the time by a given duration. As a result, the clock is moved forward (i.e., in the future). ```java @Test void shouldTriggerTimerEvent() { // given: a process instance waiting at a BPMN timer event // when assertThat(processInstance).hasActiveElements("wait_2_days"); processTestContext.increaseTime(Duration.ofDays(2)); // then assertThat(processInstance).hasCompletedElements("wait_2_days"); } ``` ### Set time You can set the clock to a given date and time. ```java @Test void shouldCreateProcessInstanceInTheMorning() { // given processTestContext.setTime(Instant.parse("2025-10-01T08:00:00Z")); // when: create a process instance // then: verify the behavior at the given time } ``` ## Mock job workers You can mock a job worker to simulate its behavior without invoking the actual worker. The mock handles all jobs of the given job type. When to use it: - Test the process in isolation from the actual job workers - Simulate different outcomes of a job worker (success, BPMN error) - Mock disabled job workers or Connectors :::tip If you start the process application in your test case, you should [disable the job workers](../camunda-spring-boot-starter/configuration.md#disable-a-job-worker) to avoid interferences with the mocks, for example, by setting the following configuration: ```java @SpringBootTest(properties = {"camunda.client.worker.defaults.enabled=false"}) @CamundaSpringProcessTest class MyProcessTest { .. } ``` ::: ### Complete job The mock completes jobs with/without variables. ```java @Test void shouldCompleteJob() { // given: mock job worker for the job type "send-email" // 1) Complete jobs without variables processTestContext.mockJobWorker("send-email").thenComplete(); // 2) Complete jobs with variables final Map variables = Map.of( "emailSent", true, "timestamp", "2024-01-01T10:00:00Z" ); processTestContext.mockJobWorker("send-email").thenComplete(variables); // when: create a process instance // then: verify that the process instance completed all tasks } ``` ### Complete with example data The mock completes jobs with [example data](/components/modeler/data-handling.md#defining-example-data) that is defined at the related BPMN element. If the BPMN element has no example data, the mock completes the job without variables. ```java @Test void shouldCompleteJobWithExampleData() { // given: mock job worker for the job type "fetch-weather-data" processTestContext.mockJobWorker("fetch-weather-data").thenCompleteWithExampleData(); // when: create a process instance // then: verify that the process instance completed all tasks } ``` :::tip Add example data during modeling to provide context and make writing FEEL expressions easier. By using the same example data for mocks, you keep the data in the BPMN process itself and avoid repeating them in the process tests. This can simplify your tests and reducing the maintenance effort. ::: ### Throw BPMN error The mock throws BPMN errors for jobs with the given error code and optional error message and variables. ```java @Test void shouldThrowBpmnError() { // given: mock job worker for the job type "validate-order" // 1) Throw BPMN errors with error code "INVALID_ORDER" processTestContext.mockJobWorker("validate-order").thenThrowBpmnError("INVALID_ORDER"); // 2) Throw BPMN errors with error code "INVALID_ORDER" and variables final Map variables = Map.of( "reason", "The order exceeds the item limit." ); processTestContext .mockJobWorker("validate-order") .thenThrowBpmnError("INVALID_ORDER", variables); // 3) Throw BPMN errors with error code, error message, and variables processTestContext .mockJobWorker("validate-order") .thenThrowBpmnError("INVALID_ORDER", "Order validation failed", variables); // when: create a process instance // then: verify that the process instance handled the BPMN error } ``` ### Custom handler You can implement a custom handler to mock more complex behaviors. ```java @Test void shouldUseCustomHandler() { // given: mock job worker for the job type "calculate-discount" processTestContext .mockJobWorker("calculate-discount") .withHandler( (jobClient, job) -> { final Map variables = job.getVariablesAsMap(); final double orderAmount = (double) variables.get("orderAmount"); final double discount = orderAmount > 100 ? 0.1 : 0.0; jobClient.newCompleteCommand(job).variable("discount", discount).send().join(); }); // when: create a process instance // then: verify that the process instance has the expected variables } ``` ### Inspect mock invocations You can inspect the invocations of a mock job worker to verify how many jobs were handled and to get the details of each job. ```java @Test void shouldInspectMockInvocations() { // given: mock job worker for the job type "send-email" final JobWorkerMock mockJobWorker = processTestContext.mockJobWorker("send-email").thenComplete(); // when: create a process instance that triggers the job worker // then: verify the number of invocations assertThat(mockJobWorker.getInvocations()).isEqualTo(1); // and: inspect the details of each invocation assertThat(mockJobWorker.getActivatedJobs()) .hasSize(1) .flatExtracting(job -> job.getVariablesAsMap().entrySet()) .contains(entry("receiver", "Zee"), entry("subject", "Greetings")); } ``` ## Mock child processes You can mock a child process for a call activity to simulate its output without executing the actual child process. The mock deploys a dummy process with the given process ID that returns the given variables. You can optionally add a version tag. This is required when the call activity uses `bindingType="versionTag"` to resolve the child process by a specific version. When to use it: - Test the parent process in isolation from the actual child process - Simulate different outcomes of a child process - Mock a non-existing child process ```java @Test void shouldMockChildProcess() { // given: mock child process with the process ID "lunar-lander" // 1) Complete the child process without variables processTestContext.mockChildProcess().withProcessId("lunar-lander").thenComplete(); // 2) Complete the child process with variables final Map variables = Map.of("landingStatus", "nominal"); processTestContext.mockChildProcess().withProcessId("lunar-lander").thenComplete(variables); // 3) Complete the child process with a version tag // Use when the call activity has bindingType="versionTag" processTestContext.mockChildProcess() .withProcessId("lunar-lander") .withVersionTag("1.7.1") .thenComplete(variables); // when: create a process instance // then: verify that the process instance completed the call activity } ``` ### Child process with dynamic variables You can mock a child process with dynamic behavior whose output variables are derived from the parent process instance. The handler receives the parent variables and returns the child process variables. ```java @Test void shouldMockChildProcess() { // given: mock dynamic child process with the process ID "AstronautTrainingProcess" processTestContext .mockChildProcess() .withProcessId("AstronautTrainingProcess") .thenComplete(parentVariables -> { final String astronautName = (String) parentVariables.get("astronautName"); final String grade = "Zee".equals(astronautName) ? "excellent" : "good"; return Map.of( "trainingCompleted", true, "grade", grade); }); // when: create a process instance // then: verify that the process instance completed the call activity } ``` ## Mock DMN decisions You can mock a DMN decision for a business rule task to simulate its output without evaluating the actual DMN decision. The mock deploys a dummy DMN decision with the given decision ID that returns the given variables. When to use it: - Test the process with the business rule task in isolation from the actual DMN decision - Simulate different outcomes of a DMN decision - Mock a non-existing DMN decision ```java @Test void shouldMockDmnDecision() { // given: mock DMN decision with the decision ID "credit-check-decision" final Map variables = Map.of( "approved", true, "riskLevel", "low", "creditLimit", 5000 ); processTestContext.mockDmnDecision("credit-check-decision", variables); // when: create a process instance // then: verify that the process instance completed the business rule task } ``` ## Conditional behavior The `when(condition).then(action)` API on `CamundaProcessTestContext` registers background behaviors that react to process state changes without blocking the test thread. This is useful for non-deterministic flows where the execution order is unknown. You can register multiple behaviors before starting the process, and they will react independently as the process progresses. Behaviors are cleared automatically after each test. ```java @Test void shouldCompleteTaskAutomatically() { // given: define a conditional behavior processTestContext .when(() -> assertThat(processInstance).hasActiveElements("approve_order")) .as("approve order") .then(() -> processTestContext.completeUserTask("approve_order", Map.of("approved", true))); // when: create a process instance // then: verify that the process instance is completed } ``` :::important The action should resolve the process state that the condition checks for. After an action executes, the engine waits for the condition to become false again before re-evaluating. For example, if the condition asserts that a user task is active, the action should complete that user task. This advances the process flow so that the condition no longer holds. Otherwise, the same condition may not be detected again reliably. ::: If the same conditional behavior applies to multiple tests, you can define it in a `@BeforeEach` method: ```java @BeforeEach void setupBehaviors() { processTestContext .when(() -> assertThat(processInstance).hasActiveElements("send_notification")) .as("complete send notification") .then(() -> processTestContext.completeJob("send-notification")); } @Test void shouldCompleteOrder() { // given: the conditional behavior is defined in @BeforeEach // when: create a process instance // then: verify that the process instance completed the task } ``` ### Chain multiple actions Actions are consumed in order on each condition match. The last action repeats indefinitely once all preceding actions are exhausted. ```java @Test void shouldHandleRepeatedTask() { // given: define a conditional behavior with chained actions processTestContext .when(() -> assertThat(processInstance).hasActiveElements("review_document")) .as("review document") .then(() -> processTestContext.completeUserTask("review_document", Map.of("approved", false, "comment", "Needs revision"))) .then(() -> processTestContext.completeUserTask("review_document", Map.of("approved", true, "comment", "Looks good"))); // when: create a process instance // then: verify that the process instance is completed } ``` ### Name a behavior You can assign a descriptive name to a conditional behavior using `.as()`. The name is used in log messages and diagnostics. ```java @Test void shouldNameBehavior() { // given: define a named conditional behavior processTestContext .when(() -> assertThat(processInstance).hasActiveElements("send_notification")) .as("send-notification is active") .then(() -> processTestContext.completeJob("send-notification")); // when: create a process instance // then: verify that the process instance completed the task } ``` ## Complete jobs You can complete an active job to simulate the behavior of a job worker without invoking the actual worker. The command waits for the first job with the given job type and completes it. If no job exists, the command fails. Identify the job by its job type or using a [JobSelector](#job-selector). You can pass variables or complete the job with the [example data](/components/modeler/data-handling.md#defining-example-data) from the related BPMN element. When to use it: - Test the process with full control over the job completion - Complete a repeated task with different outcomes ```java @Test void shouldCompleteJob() { // given: a process instance is waiting at a task // when: complete the job with type "send-notification" // 1) Without variables processTestContext.completeJob("send-notification"); // 2) With variables final Map variables = Map.of( "notification-sent", true, "recipients", List.of("user1@example.com", "user2@example.com") ); processTestContext.completeJob("send-notification", variables); // 3) With example data from the BPMN element processTestContext.completeJobWithExampleData("send-notification"); // 4) With job selector by element ID "send_notification_task" processTestContext.completeJob(JobSelectors.byElementId("send_notification_task")); // 5) With a mapper from input variables to output variables processTestContext.completeJob( "send-notification", inputVariables -> { final String recipient = (String) inputVariables.get("recipient"); return Map.of("notificationSent", true, "sentTo", recipient); }); // then: verify that the process instance completed the task } ``` ### Ad-hoc sub-process jobs You can simulate the behavior of an [ad-hoc sub-process job worker](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md#job-worker-implementation), for example, an AI agent, and control the execution of the ad-hoc sub-process. The job completion allows you to activate an element in the ad-hoc sub-process or to fulfill the completion condition. ```java @Test void shouldCompleteJobOfAdHocSubProcess() { // given: the ad-hoc sub-process is active // when: complete the job of the ad-hoc sub-process // 1) With activating an element with variables processTestContext.completeJobOfAdHocSubProcess( JobSelectors.byElementId("ad-hoc-sub-process"), result -> result.activateElement("search-knowledge-base").variable("query", "launch rockets")); // 2) With job variables (for the ad-hoc sub-process) processTestContext.completeJobOfAdHocSubProcess( JobSelectors.byElementId("ad-hoc-sub-process"), Map.of("agent", agentContext), result -> result.activateElement("search-knowledge-base").variable("query", "launch rockets")); // 3) With fulfilling the completion condition processTestContext.completeJobOfAdHocSubProcess( JobSelectors.byElementId("ad-hoc-sub-process"), result -> result.completionConditionFulfilled(true)); // then: verify that the ad-hoc sub-process completed the task } ``` ### User task listener jobs You can simulate the behavior of a [user task listener job worker](/components/concepts/user-task-listeners.md#implement-a-user-task-listener). The job completion allows to correct the user task data or to deny the user task lifecycle transition. ```java @Test void shouldCompleteJobOfUserTaskListener() { // given: the process instance is waiting at a user task // when: complete the job of the user task listener // 1) With correcting user task data processTestContext.completeJobOfUserTaskListener( JobSelectors.byElementId("approve_request_task"), result -> result.correctAssignee("me").correctPriority(100)); // 2) With denying the user task lifecycle transition processTestContext.completeJobOfUserTaskListener( JobSelectors.byElementId("approve_request_task"), result -> result.deny(true).deniedReason("Policy violation")); // then: verify that the user task is completed } ``` ## Throw BPMN errors from jobs You can throw a BPMN error from an active job to simulate the behavior of a job worker without invoking the actual worker. The command waits for the first job with the given job type and throws the BPMN error. If no job exists, the command fails. Identify the job by its job type or using a [JobSelector](#job-selector). Optionally, you can pass variables and an error message with the BPMN error. When to use it: - Test the error paths in the process - Simulate different behaviors of a repeated task (success, BPMN error) ```java @Test void shouldThrowBpmnErrorFromJob() { // given: a process instance is waiting at a task // when: throw a BPMN error for the job with type "validate-data" // 1) With error code "VALIDATION_FAILED" and no variables processTestContext.throwBpmnErrorFromJob("validate-data", "VALIDATION_FAILED"); // 2) With error code "VALIDATION_FAILED" and variables final Map variables = Map.of( "error-message", "Invalid customer data", "error-code", "ERR_VALIDATION_001" ); processTestContext.throwBpmnErrorFromJob("validate-data", "VALIDATION_FAILED", variables); // 3) With error code, error message, and variables processTestContext.throwBpmnErrorFromJob( "validate-data", "VALIDATION_FAILED", "Data validation failed due to missing required fields", variables); // 4) With job selector by element ID "validate_data_task" processTestContext.throwBpmnErrorFromJob( JobSelectors.byElementId("validate_data_task"), "VALIDATION_FAILED"); // then: verify that the process instance handled the error } ``` ## Complete user tasks You can complete a user task to simulate the user behavior in Tasklist. The command waits for the first user task and completes it. If no user task exists, the command fails. Identify the user task by its BPMN element ID or using a [UserTaskSelector](#user-task-selector). You can pass variables or complete the user task with the [example data](/components/modeler/data-handling.md#defining-example-data) from the related BPMN element. When to use it: - Test a process with user tasks ```java @Test void shouldCompleteUserTask() { // given: a process instance is waiting at a user task // when: complete the user task // 1) With element ID "task_approveRequest" final Map variables = Map.of( "approved", true, "comment", "Request approved by manager", "approvedAmount", 5000.00 ); processTestContext.completeUserTask("task_approveRequest", variables); // 2) With selector by task name "Approve Request" processTestContext.completeUserTask( UserTaskSelectors.byTaskName("Approve Request"), variables); // 3) With example data from the BPMN element processTestContext.completeUserTaskWithExampleData( UserTaskSelectors.byElementId("task_approveRequest")); // 4) With a mapper from input variables to output variables processTestContext.completeUserTask( "task_approveRequest", inputVariables -> Map.of("approved", inputVariables.containsKey("preApproved"))); // then: verify that the process instance is completed } ``` ## Update variables You can update or create variables of a process instance or in the local scope of a BPMN element, for example, to trigger a BPMN conditional event. To target local variables of a specific BPMN element, use an [ElementSelector](#element-selector). ### Update process instance variables Use `updateVariables()` to update or create variables on a process instance. ```java @Test void shouldTriggerConditionalEvent() { // given: a process instance is waiting at the conditional event // when: update the variables to trigger the conditional event final Map variables = Map.of( "priority", 80, "riskLevel", "high" ); processTestContext.updateVariables( ProcessInstanceSelectors.byKey(processInstanceKey), variables); // then: verify that the conditional event is completed } ``` ### Update local variables Use `updateLocalVariables()` to propagate variables starting from a given element's scope. The variables are updated on the element or the nearest parent scope where they already exist. If a variable doesn't exist in any scope, it's created on the process instance scope. ```java processTestContext.updateLocalVariables( ProcessInstanceSelectors.byKey(processInstanceKey), ElementSelectors.byId("sub-process"), variables); ``` ### Create local variables Use `createLocalVariables()` to create variables in the local scope of a given element. The variables are created only in the element's scope and are not propagated to parent scopes. ```java processTestContext.createLocalVariables( ProcessInstanceSelectors.byKey(processInstanceKey), ElementSelectors.byId("sub-process"), variables); ``` ## Resolve incidents You can resolve an active incident. Use the [IncidentSelector](#incident-selector) to identify the incident based on different criteria, for example, by the BPMN element ID where the incident occurred. If the incident is caused by a job, the command increases the job retries by one before resolving the incident. If the incident is caused by a missing variable, you should [update the variables](#update-variables) before resolving the incident. ```java @Test void shouldResolveIncident() { // given: a process instance has an active incident // when: resolve the incident at the element with ID "validate-data" processTestContext.resolveIncident(IncidentSelectors.byElementId("validate-data")); // then: verify that the element is completed } ``` ## Selectors CPT provides selectors to identify entities such as jobs, user tasks, process instances, and more based on different criteria. You can use selectors in both utilities and assertions to target the entities you want to interact with or verify. The selector targets the first matching entity. CPT provides predefined selectors for common use cases. You can combine multiple selectors using `.and()` to create more specific selection criteria. ```java // Combine job selectors by job type and process instance key processTestContext.completeJob( JobSelectors.byJobType("send-notification") .and(JobSelectors.byProcessInstanceKey(processInstanceKey)) ); ``` Alternatively, you can implement your own custom selector when you need specialized selection logic. ```java // Implement a custom selector to select a user task by its assignee private static UserTaskSelector byAssignee(String assignee) { return new UserTaskSelectorByAssignee(assignee); } private static final class UserTaskSelectorByAssignee implements UserTaskSelector { private final String assignee; public UserTaskSelectorByAssignee(String assignee) { this.assignee = assignee; } @Override public boolean test(final UserTask userTask) { return assignee.equals(userTask.getAssignee()); } @Override public String describe() { return "assignee: " + assignee; } @Override public void applyFilter(final UserTaskFilter filter) { filter.assignee(assignee); } } ``` ### Decision selector You can use a decision selector to identify a DMN decision evaluation based on different criteria, such as decision ID or decision name. Predefined decision selectors are available in the `io.camunda.process.test.api.assertions.DecisionSelectors` class. ```java // Assert the evaluation of a DMN decision with the ID "credit-score" assertThatDecision(DecisionSelectors.byId("credit-score")).hasOutput(750); ``` ### Element selector You can use an element selector to identify a BPMN element based on different criteria, such as element ID or element name. Predefined element selectors are available in the `io.camunda.process.test.api.assertions.ElementSelectors` class. ```java // Assert the BPMN element with the ID "approve_request_task" assertThat(processInstance).hasActiveElements(ElementSelectors.byId("approve_request_task")); // Assert the BPMN element with the name "Approve Request" assertThat(processInstance).hasActiveElements(ElementSelectors.byName("Approve Request")); ``` ### Incident selector You can use an incident selector to identify an incident based on different criteria, such as BPMN element ID or process instance key. Predefined incident selectors are available in the `io.camunda.process.test.api.assertions.IncidentSelectors` class. ```java // Resolve an incident by the BPMN element ID "approve_request_task" processTestContext.resolveIncident(IncidentSelectors.byElementId("approve_request_task")); ``` ### Job selector You can use a job selector to identify a job based on different criteria, such as job type, BPMN element ID, or process instance key. Predefined job selectors are available in the `io.camunda.process.test.api.assertions.JobSelectors` class. ```java // Complete a job by its BPMN element ID processTestContext.completeJob(JobSelectors.byElementId("send_notification_task")); // Throw a BPMN error from a job by its job type processTestContext.throwBpmnErrorFromJob(JobSelectors.byJobType("validate-data"), "VALIDATION_FAILED"); ``` ### Process instance selector You can use a process instance selector to identify a process instance based on different criteria, such as process instance key or BPMN process ID. Predefined process instance selectors are available in the `io.camunda.process.test.api.assertions.ProcessInstanceSelectors` class. ```java // Assert a process instance by its process instance key assertThatProcessInstance(ProcessInstanceSelectors.byKey(processInstanceKey)).isCreated(); ``` ### User task selector You can use a user task selector to identify a user task based on different criteria, such as element ID, task name, or process instance key. Predefined user task selectors are available in the `io.camunda.process.test.api.assertions.UserTaskSelectors` class. ```java // Complete a user task by its task name processTestContext.completeUserTask(UserTaskSelectors.byTaskName("Approve Request"), variables); ``` ### Variable selector You can use a variable selector to identify a variable based on different criteria, such as variable name or variable value. Predefined variable selectors are available in the `io.camunda.process.test.api.assertions.VariableSelectors` class. ```java // Assert a variable by its name assertThatProcessInstance(processInstance).hasVariable(VariableSelectors.byName("approved"), true); ``` --- ## Migrate from Zeebe Process Test :::warning Zeebe Process Test was removed in Camunda 8.10. Use [Camunda Process Test](/apis-tools/testing/getting-started.md) instead. ::: For the release-level summary of this removal, see the [8.10 release announcement](/reference/announcements-release-notes/8100/8100-announcements.md#removal-of-legacy-apis-tasklist-v1-dependent-features-and-zeebe-process-test). Use [Camunda Process Test](/apis-tools/testing/getting-started.md) for current process testing, and review the [migration guide](/apis-tools/migration-manuals/migrate-to-camunda-process-test.md) to replace old ZPT dependencies, annotations, and assertions. ## Examples For example tests, refer to [GitHub](https://github.com/camunda-cloud/zeebe-process-test). --- ## Class: CamundaEffect :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: `Context` service key for the Effect Camunda client. Compose worker/orchestration code against this tag and provide [layer](../functions/layer.md) (or a test double) via `Layer`. ## Extends - `Shape`\<`"CamundaEffect"`, [`CamundaEffectClient`](../type-aliases/CamundaEffectClient.md), `this`\> ## Constructors ### Constructor ```ts new CamundaEffect(_): CamundaEffect; ``` #### Parameters ##### \_ `never` #### Returns `CamundaEffect` #### Inherited from ```ts Context.Service()("CamundaEffect") .constructor; ``` --- ## Class: CamundaGenericError :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: Any other thrown value that does not map to a more specific tag. ## Extends - `YieldableError`\<`this`\> & `object` & `Readonly`\<\{ `cause?`: `unknown`; `message`: `string`; \}\> ## Constructors ### Constructor ```ts new CamundaGenericError(args): CamundaGenericError; ``` #### Parameters ##### args ###### cause? `unknown` ###### message `string` #### Returns `CamundaGenericError` #### Inherited from ```ts Data.TaggedError('CamundaGenericError')<{ readonly message: string; readonly cause?: unknown; }>.constructor ``` --- ## Class: CamundaValidationError :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: A request/response validation failure surfaced by the SDK. ## Extends - `YieldableError`\<`this`\> & `object` & `Readonly`\<\{ `cause?`: `unknown`; `issues`: `string`[]; `message`: `string`; `operationId?`: `string`; `side`: `"request"` \| `"response"`; `summary`: `string`; \}\> ## Constructors ### Constructor ```ts new CamundaValidationError(args): CamundaValidationError; ``` #### Parameters ##### args ###### cause? `unknown` ###### issues `string`[] ###### message `string` ###### operationId? `string` ###### side `"request"` \| `"response"` ###### summary `string` #### Returns `CamundaValidationError` #### Inherited from ```ts Data.TaggedError('CamundaValidationError')<{ readonly side: 'request' | 'response'; readonly operationId?: string; readonly summary: string; readonly issues: string[]; readonly message: string; readonly cause?: unknown; }>.constructor ``` ## Properties ### issues ```ts readonly issues: string[]; ``` #### Inherited from ```ts Data.TaggedError("CamundaValidationError").issues; ``` --- ### operationId? ```ts readonly optional operationId?: string; ``` #### Inherited from ```ts Data.TaggedError("CamundaValidationError").operationId; ``` --- ### side ```ts readonly side: "request" | "response"; ``` #### Inherited from ```ts Data.TaggedError("CamundaValidationError").side; ``` --- ### summary ```ts readonly summary: string; ``` #### Inherited from ```ts Data.TaggedError("CamundaValidationError").summary; ``` --- ## Class: EventualConsistencyTimeout :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: An eventual-consistency poll that did not converge within its budget. ## Extends - `YieldableError`\<`this`\> & `object` & `Readonly`\<\{ `attempts?`: `number`; `cause?`: `unknown`; `elapsedMs?`: `number`; `message`: `string`; \}\> ## Constructors ### Constructor ```ts new EventualConsistencyTimeout(args): EventualConsistencyTimeout; ``` #### Parameters ##### args ###### attempts? `number` ###### cause? `unknown` ###### elapsedMs? `number` ###### message `string` #### Returns `EventualConsistencyTimeout` #### Inherited from ```ts Data.TaggedError('EventualConsistencyTimeout')<{ readonly attempts?: number; readonly elapsedMs?: number; readonly message: string; readonly cause?: unknown; }>.constructor ``` ## Properties ### attempts? ```ts readonly optional attempts?: number; ``` #### Inherited from ```ts Data.TaggedError("EventualConsistencyTimeout").attempts; ``` --- ### elapsedMs? ```ts readonly optional elapsedMs?: number; ``` #### Inherited from ```ts Data.TaggedError("EventualConsistencyTimeout").elapsedMs; ``` --- ## Class: HttpError :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: An HTTP-level failure (non-2xx / transport carrying a status). ## Extends - `YieldableError`\<`this`\> & `object` & `Readonly`\<\{ `body?`: `unknown`; `cause?`: `unknown`; `message`: `string`; `status?`: `number`; \}\> ## Constructors ### Constructor ```ts new HttpError(args): HttpError; ``` #### Parameters ##### args ###### body? `unknown` ###### cause? `unknown` ###### message `string` ###### status? `number` #### Returns `HttpError` #### Inherited from ```ts Data.TaggedError('HttpError')<{ readonly status?: number; readonly body?: unknown; readonly message: string; readonly cause?: unknown; }>.constructor ``` ## Properties ### body? ```ts readonly optional body?: unknown; ``` #### Inherited from ```ts Data.TaggedError("HttpError").body; ``` --- ### status? ```ts readonly optional status?: number; ``` #### Inherited from ```ts Data.TaggedError("HttpError").status; ``` --- ## Class: RetryableJobError :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: A retryable job failure: the handler could not complete the job now, but a later activation might succeed. Mapped to `failJob` with `retries - 1` and an optional server-side re-activation backoff. ## Extends - `YieldableError`\<`this`\> & `object` & `Readonly`\<\{ `cause?`: `unknown`; `message`: `string`; `retryBackoff?`: `Input`; `variables?`: \{ \[`key`: `string`\]: `unknown`; \}; \}\> ## Constructors ### Constructor ```ts new RetryableJobError(args): RetryableJobError; ``` #### Parameters ##### args ###### cause? `unknown` ###### message `string` ###### retryBackoff? `Input` Server-side delay before the job becomes re-activatable (`failJob` `retryBackOff`). ###### variables? \{ \[`key`: `string`\]: `unknown`; \} Optional variables to attach to the job on failure. #### Returns `RetryableJobError` #### Inherited from ```ts Data.TaggedError('RetryableJobError')<{ readonly message: string; / Server-side delay before the job becomes re-activatable (failJob retryBackOff). / readonly retryBackoff?: Duration.Input; / Optional variables to attach to the job on failure. / readonly variables?: { readonly [key: string]: unknown }; readonly cause?: unknown; }>.constructor ``` ## Properties ### retryBackoff? ```ts readonly optional retryBackoff?: Input; ``` Server-side delay before the job becomes re-activatable (`failJob` `retryBackOff`). #### Inherited from ```ts Data.TaggedError("RetryableJobError").retryBackoff; ``` --- ### variables? ```ts readonly optional variables?: object; ``` Optional variables to attach to the job on failure. #### Index Signature ```ts [key: string]: unknown ``` #### Inherited from ```ts Data.TaggedError("RetryableJobError").variables; ``` --- ## Class: TerminalJobError :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: A terminal job failure: retrying will not help. Mapped to `throwJobError`, which is caught by a matching BPMN error boundary event or — if uncaught — raises an incident. ## Extends - `YieldableError`\<`this`\> & `object` & `Readonly`\<\{ `cause?`: `unknown`; `code`: `string`; `message`: `string`; `variables?`: \{ \[`key`: `string`\]: `unknown`; \}; \}\> ## Constructors ### Constructor ```ts new TerminalJobError(args): TerminalJobError; ``` #### Parameters ##### args ###### cause? `unknown` ###### code `string` The BPMN error code matched against an error catch event. ###### message `string` ###### variables? \{ \[`key`: `string`\]: `unknown`; \} Optional variables to instantiate at the error catch event's scope. #### Returns `TerminalJobError` #### Inherited from ```ts Data.TaggedError('TerminalJobError')<{ / The BPMN error code matched against an error catch event. / readonly code: string; readonly message: string; / Optional variables to instantiate at the error catch event's scope. / readonly variables?: { readonly [key: string]: unknown }; readonly cause?: unknown; }>.constructor ``` ## Properties ### code ```ts readonly code: string; ``` The BPMN error code matched against an error catch event. #### Inherited from ```ts Data.TaggedError("TerminalJobError").code; ``` --- ### variables? ```ts readonly optional variables?: object; ``` Optional variables to instantiate at the error catch event's scope. #### Index Signature ```ts [key: string]: unknown ``` #### Inherited from ```ts Data.TaggedError("TerminalJobError").variables; ``` --- ## Function: activateJobsStream() :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: ```ts function activateJobsStream( type, options? ): Stream; ``` A `Stream` of activated jobs of `type`. Repeatedly calls `activateJobs` (through the `/effect` client `Layer`, i.e. the same backpressure-aware runtime the Promise worker uses) and emits each activated job. The between-empty-polls delay and the optional activation-retry `Schedule` both run on the Effect `Clock`, so `TestClock.adjust` advances them deterministically in tests. ## Type Parameters ### R `R` = `never` ## Parameters ### type `string` ### options? [`ActivateJobsStreamOptions`](../interfaces/ActivateJobsStreamOptions.md)\<`R`\> = `{}` ## Returns `Stream`\<[`ActivatedJobResult`](../../index/type-aliases/ActivatedJobResult.md), [`DomainError`](../type-aliases/DomainError.md), [`CamundaEffect`](../classes/CamundaEffect.md) \| `R`\> --- ## Function: createCamundaEffectClient() :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: ```ts function createCamundaEffectClient(options?): CamundaEffectClient; ``` Create an Effect-flavoured Camunda client. Every `CamundaClient` method becomes `(...args) => Effect.Effect, DomainError, never>`. Failures are narrowed into the tagged [DomainError](../type-aliases/DomainError.md) union so callers use `Effect.catchTag`/`catchTags`. The underlying throwing client is reachable via the `.inner` escape hatch. ## Parameters ### options? [`CamundaOptions`](../../index/interfaces/CamundaOptions.md) ## Returns [`CamundaEffectClient`](../type-aliases/CamundaEffectClient.md) ## Description Camunda Effect Client. See the README and [this test](https://github.com/camunda/orchestration-cluster-api-js/blob/main/tests-integration/effect.test.ts) for example usage. --- ## Function: createCamundaEffectWorker() :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: ```ts function createCamundaEffectWorker( config ): Effect; ``` Create and start an Effect job worker, forked into the current `Scope`. The worker runs until its scope closes (or [CamundaEffectWorkerHandle.interrupt](../interfaces/CamundaEffectWorkerHandle.md#interrupt) is run), at which point it is interrupted and any in-flight job's lease is released. Depends on the [CamundaEffect](../classes/CamundaEffect.md) service — provide the `/effect` client `layer`. ## Type Parameters ### A `A` _extends_ [`CompleteVars`](../type-aliases/CompleteVars.md) ### R `R` = `never` ## Parameters ### config [`EffectWorkerConfig`](../interfaces/EffectWorkerConfig.md)\<`A`, `R`\> ## Returns `Effect`\<[`CamundaEffectWorkerHandle`](../interfaces/CamundaEffectWorkerHandle.md), `never`, [`CamundaEffect`](../classes/CamundaEffect.md) \| `R` \| `Scope`\> ## Description Camunda Effect Worker. See the README and [this test](https://github.com/camunda/orchestration-cluster-api-js/blob/main/tests-integration/effect-worker.test.ts) for example usage. --- ## Function: eventually() :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: ```ts function eventually( effect, predicate, opts ): Effect; ``` Poll `effect` on the Effect `Clock` until `predicate` holds, timing out to [EventualConsistencyTimeout](../classes/EventualConsistencyTimeout.md) once `waitUpTo` elapses. Because it uses the Effect `Clock` (not `Date.now`/`setTimeout`), `TestClock.adjust` advances it deterministically in tests — no real-clock burn. ## Type Parameters ### A `A` ### E `E` ### R `R` ## Parameters ### effect `Effect`\<`A`, `E`, `R`\> ### predicate (`a`) => `boolean` ### opts #### interval? `Input` #### waitUpTo `Input` ## Returns `Effect`\<`A`, \| [`EventualConsistencyTimeout`](../classes/EventualConsistencyTimeout.md) \| `E`, `R`\> --- ## Function: layer() :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: ```ts function layer(options?): Layer; ``` A `Layer` that constructs a [CamundaEffectClient](../type-aliases/CamundaEffectClient.md) and provides it as the [CamundaEffect](../classes/CamundaEffect.md) service. Swap in a test double by providing a different `Layer` for the same tag. ## Parameters ### options? [`CamundaOptions`](../../index/interfaces/CamundaOptions.md) ## Returns `Layer`\<[`CamundaEffect`](../classes/CamundaEffect.md)\> --- ## Function: retryWithBackoff() :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: ```ts function retryWithBackoff(effect, opts): Effect; ``` Retry an effect with exponential backoff (+ jitter), capped attempts, and an optional predicate over the error. ## Type Parameters ### A `A` ### E `E` ### R `R` ## Parameters ### effect `Effect`\<`A`, `E`, `R`\> ### opts #### baseDelay? `Input` #### max `number` #### while? (`e`) => `boolean` ## Returns `Effect`\<`A`, `E`, `R`\> --- ## Function: runWorkerLoop() :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: ```ts function runWorkerLoop( config ): Effect; ``` The worker loop: drains [activateJobsStream](activateJobsStream.md), running the handler for each job with bounded `concurrency` (the backpressure knob), and acknowledging via `completeJob`/`failJob`/`throwJobError` per the handler's typed outcome. ## Type Parameters ### A `A` _extends_ [`CompleteVars`](../type-aliases/CompleteVars.md) ### R `R` = `never` ## Parameters ### config [`EffectWorkerConfig`](../interfaces/EffectWorkerConfig.md)\<`A`, `R`\> ## Returns `Effect`\<`void`, [`DomainError`](../type-aliases/DomainError.md), [`CamundaEffect`](../classes/CamundaEffect.md) \| `R`\> --- ## Function: withTimeout() :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: ```ts function withTimeout( effect, duration, onTimeout? ): Effect; ``` Fail an effect with a real interruption if it does not settle within `duration` (true interruption, not a best-effort `Promise.race`). ## Type Parameters ### A `A` ### E `E` ### R `R` ## Parameters ### effect `Effect`\<`A`, `E`, `R`\> ### duration `Input` ### onTimeout? () => \| [`EventualConsistencyTimeout`](../classes/EventualConsistencyTimeout.md) \| `E` ## Returns `Effect`\<`A`, \| [`EventualConsistencyTimeout`](../classes/EventualConsistencyTimeout.md) \| `E`, `R`\> --- ## Function: workerLayer() :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: ```ts function workerLayer(config): Layer; ``` A `Layer` that runs a Camunda Effect worker for the layer's lifetime. Compose it with the `/effect` client `layer` (which provides its [CamundaEffect](../classes/CamundaEffect.md) dependency) plus a `Layer` for the handler's own requirements `R`. ## Type Parameters ### A `A` _extends_ [`CompleteVars`](../type-aliases/CompleteVars.md) ### R `R` = `never` ## Parameters ### config [`EffectWorkerConfig`](../interfaces/EffectWorkerConfig.md)\<`A`, `R`\> ## Returns `Layer`\<`never`, `never`, [`CamundaEffect`](../classes/CamundaEffect.md) \| `R`\> --- ## effect :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: ## Classes - [CamundaEffect](classes/CamundaEffect.md) - [CamundaGenericError](classes/CamundaGenericError.md) - [CamundaValidationError](classes/CamundaValidationError.md) - [EventualConsistencyTimeout](classes/EventualConsistencyTimeout.md) - [HttpError](classes/HttpError.md) - [RetryableJobError](classes/RetryableJobError.md) - [TerminalJobError](classes/TerminalJobError.md) ## Interfaces - [ActivateJobsStreamOptions](interfaces/ActivateJobsStreamOptions.md) - [CamundaEffectWorkerHandle](interfaces/CamundaEffectWorkerHandle.md) - [EffectPaginateOptions](interfaces/EffectPaginateOptions.md) - [EffectPaginator](interfaces/EffectPaginator.md) - [EffectWorkerConfig](interfaces/EffectWorkerConfig.md) ## Type Aliases - [CamundaEffectClient](type-aliases/CamundaEffectClient.md) - [CompleteVars](type-aliases/CompleteVars.md) - [DomainError](type-aliases/DomainError.md) - [DomainErrorTag](type-aliases/DomainErrorTag.md) - [Effectify](type-aliases/Effectify.md) - [FnKeys](type-aliases/FnKeys.md) - [Job](type-aliases/Job.md) - [JobError](type-aliases/JobError.md) - [JobHandler](type-aliases/JobHandler.md) ## Functions - [activateJobsStream](functions/activateJobsStream.md) - [createCamundaEffectClient](functions/createCamundaEffectClient.md) - [createCamundaEffectWorker](functions/createCamundaEffectWorker.md) - [eventually](functions/eventually.md) - [layer](functions/layer.md) - [retryWithBackoff](functions/retryWithBackoff.md) - [runWorkerLoop](functions/runWorkerLoop.md) - [withTimeout](functions/withTimeout.md) - [workerLayer](functions/workerLayer.md) --- ## Interface: ActivateJobsStreamOptions # Interface: ActivateJobsStreamOptions\ :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: ## Extended by - [`EffectWorkerConfig`](EffectWorkerConfig.md) ## Type Parameters ### R `R` = `never` ## Properties ### activationRetrySchedule? ```ts readonly optional activationRetrySchedule?: Schedule; ``` `Schedule` used to back off and retry a **failed activation request** (transport outage, broker restart, transient server error). Runs on the Effect `Clock`. When omitted, an activation failure fails the stream. --- ### fetchVariables? ```ts readonly optional fetchVariables?: readonly string[]; ``` Restrict activation to these variable names. --- ### jobTimeout? ```ts readonly optional jobTimeout?: Input; ``` Per-job activation lock timeout (server-side). Default `60 seconds`. --- ### maxJobsToActivate? ```ts readonly optional maxJobsToActivate?: number; ``` Max jobs to activate per poll (the activation batch size). Default `10`. --- ### pollInterval? ```ts readonly optional pollInterval?: Input; ``` Delay between polls that returned **no** jobs, on the Effect `Clock` (so it is virtual under `TestClock`). A poll that returns jobs schedules the next poll immediately. Default `1 second`. --- ### requestTimeout? ```ts readonly optional requestTimeout?: Input; ``` Long-poll request timeout. `0` (the default) lets the broker hold the request for its configured default; a negative value returns immediately when idle. --- ### withLease? ```ts readonly optional withLease?: boolean; ``` Activate jobs with a lease — default `false`. When `true`, each activated job is assigned a distinct, opaque lease token (`ActivatedJobResult.jobLeaseToken`) that is threaded back into the fenced `completeJob` / `failJob` / `throwJobError` commands, fencing them against a superseded activation of the same job. Note: the Effect `handler` intentionally keeps the base job shape (`jobLeaseToken` remains optional/nullable); the token is threaded back into fenced commands automatically, so handlers do not read it directly. --- ### workerName? ```ts readonly optional workerName?: string; ``` Worker name recorded on the activation request. Defaults to `effect-worker--`, where `` is an incrementing per-process counter. --- ## Interface: CamundaEffectWorkerHandle :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: A handle to a running Effect worker. ## Properties ### interrupt ```ts readonly interrupt: Effect; ``` Interrupt the worker (also happens automatically when the owning scope closes). --- ### join ```ts readonly join: Effect; ``` Completes when the worker loop ends (only on a fatal, non-retryable activation error). --- ### type ```ts readonly type: string; ``` The job type this worker activates. --- ## Interface: EffectPaginateOptions # Interface: EffectPaginateOptions\ :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: Options for the Effect client's `.paginate`. ## Type Parameters ### TData `TData` = `unknown` ## Properties ### consistency? ```ts readonly optional consistency?: ConsistencyOptions; ``` Eventual-consistency controls forwarded to the underlying search call. Defaults to `{ waitUpToMs: 0 }`. Only the **first** page honours this window: once paging is under way an empty page is end-of-results, not a not-yet-consistent read. --- ### maxPages? ```ts readonly optional maxPages?: number; ``` Safety cap on pages fetched (default: unbounded). A non-positive value fetches no pages at all — the cap is enforced _before_ the first request. --- ### mode? ```ts readonly optional mode?: PaginationMode; ``` How to advance. `auto` prefers a cursor, falls back to offset. --- ## Interface: EffectPaginator # Interface: EffectPaginator\ :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: The Effect-flavoured counterpart of a [Paginator](../../index/interfaces/Paginator.md): the same three views (`pages` / `items` / `toArray`) over a multi-page search, as `Stream`s and an `Effect` rather than async iterables and a `Promise`. Every view is lazy — a page is fetched only when pulled — and interruptible: interrupting the fiber cancels the in-flight page request rather than leaving it to settle unobserved. ## Type Parameters ### TItem `TItem` ## Methods ### items() ```ts items(): Stream; ``` A `Stream` of individual items, flattened across all pages. #### Returns `Stream`\<`TItem`, [`DomainError`](../type-aliases/DomainError.md)\> --- ### pages() ```ts pages(): Stream, DomainError>; ``` A `Stream` of whole pages, each fetched lazily as it is pulled. #### Returns `Stream`\<[`SearchResponse`](../../index/interfaces/SearchResponse.md)\<`TItem`\>, [`DomainError`](../type-aliases/DomainError.md)\> --- ### toArray() ```ts toArray(): Effect; ``` Eagerly drains every item into an array. Bounded result sets only. #### Returns `Effect`\<`TItem`[], [`DomainError`](../type-aliases/DomainError.md)\> --- ## Interface: EffectWorkerConfig # Interface: EffectWorkerConfig\ :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: ## Extends - [`ActivateJobsStreamOptions`](ActivateJobsStreamOptions.md)\<`R`\> ## Type Parameters ### A `A` _extends_ [`CompleteVars`](../type-aliases/CompleteVars.md) ### R `R` = `never` ## Properties ### activationRetrySchedule? ```ts readonly optional activationRetrySchedule?: Schedule; ``` `Schedule` used to back off and retry a **failed activation request** (transport outage, broker restart, transient server error). Runs on the Effect `Clock`. When omitted, an activation failure fails the stream. #### Inherited from [`ActivateJobsStreamOptions`](ActivateJobsStreamOptions.md).[`activationRetrySchedule`](ActivateJobsStreamOptions.md#activationretryschedule) --- ### concurrency? ```ts readonly optional concurrency?: number | "unbounded"; ``` Max jobs processed concurrently (handler parallelism / backpressure). The activation loop will not pull faster than handlers drain, and the activation batch is capped to this value (when it is a finite number) so the worker never leases more jobs than it can process at once. Default: the value of [ActivateJobsStreamOptions.maxJobsToActivate](ActivateJobsStreamOptions.md#maxjobstoactivate) (or `10`). --- ### fetchVariables? ```ts readonly optional fetchVariables?: readonly string[]; ``` Restrict activation to these variable names. #### Inherited from [`ActivateJobsStreamOptions`](ActivateJobsStreamOptions.md).[`fetchVariables`](ActivateJobsStreamOptions.md#fetchvariables) --- ### handler ```ts readonly handler: JobHandler; ``` The Effect job handler. --- ### handlerRetrySchedule? ```ts readonly optional handlerRetrySchedule?: Schedule; ``` `Schedule` used to retry the **handler** in-process on a [RetryableJobError](../classes/RetryableJobError.md) before the job is failed back to the broker. Runs on the Effect `Clock` (virtual under `TestClock`). A [TerminalJobError](../classes/TerminalJobError.md) is never retried. --- ### jobTimeout? ```ts readonly optional jobTimeout?: Input; ``` Per-job activation lock timeout (server-side). Default `60 seconds`. #### Inherited from [`ActivateJobsStreamOptions`](ActivateJobsStreamOptions.md).[`jobTimeout`](ActivateJobsStreamOptions.md#jobtimeout) --- ### maxJobsToActivate? ```ts readonly optional maxJobsToActivate?: number; ``` Max jobs to activate per poll (the activation batch size). Default `10`. #### Inherited from [`ActivateJobsStreamOptions`](ActivateJobsStreamOptions.md).[`maxJobsToActivate`](ActivateJobsStreamOptions.md#maxjobstoactivate) --- ### pollInterval? ```ts readonly optional pollInterval?: Input; ``` Delay between polls that returned **no** jobs, on the Effect `Clock` (so it is virtual under `TestClock`). A poll that returns jobs schedules the next poll immediately. Default `1 second`. #### Inherited from [`ActivateJobsStreamOptions`](ActivateJobsStreamOptions.md).[`pollInterval`](ActivateJobsStreamOptions.md#pollinterval) --- ### requestTimeout? ```ts readonly optional requestTimeout?: Input; ``` Long-poll request timeout. `0` (the default) lets the broker hold the request for its configured default; a negative value returns immediately when idle. #### Inherited from [`ActivateJobsStreamOptions`](ActivateJobsStreamOptions.md).[`requestTimeout`](ActivateJobsStreamOptions.md#requesttimeout) --- ### type ```ts readonly type: string; ``` The job type to activate. --- ### withLease? ```ts readonly optional withLease?: boolean; ``` Activate jobs with a lease — default `false`. When `true`, each activated job is assigned a distinct, opaque lease token (`ActivatedJobResult.jobLeaseToken`) that is threaded back into the fenced `completeJob` / `failJob` / `throwJobError` commands, fencing them against a superseded activation of the same job. Note: the Effect `handler` intentionally keeps the base job shape (`jobLeaseToken` remains optional/nullable); the token is threaded back into fenced commands automatically, so handlers do not read it directly. #### Inherited from [`ActivateJobsStreamOptions`](ActivateJobsStreamOptions.md).[`withLease`](ActivateJobsStreamOptions.md#withlease) --- ### workerName? ```ts readonly optional workerName?: string; ``` Worker name recorded on the activation request. Defaults to `effect-worker--`, where `` is an incrementing per-process counter. #### Inherited from [`ActivateJobsStreamOptions`](ActivateJobsStreamOptions.md).[`workerName`](ActivateJobsStreamOptions.md#workername) --- ## Type Alias: CamundaEffectClient :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: ```ts type CamundaEffectClient = Effectify; ``` The Effect-flavoured Camunda client. Every operation returns an `Effect`. --- ## Type Alias: CompleteVars :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: ```ts type CompleteVars = | { [key: string]: unknown; } | void | undefined; ``` Variables to complete a job with. `void`/`undefined` completes with no variables. --- ## Type Alias: DomainError :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: ```ts type DomainError = | CamundaValidationError | EventualConsistencyTimeout | HttpError | CamundaGenericError; ``` The typed error channel for every Effect the client produces. --- ## Type Alias: DomainErrorTag :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: ```ts type DomainErrorTag = DomainError["_tag"]; ``` The tag literals of the [DomainError](DomainError.md) union. --- ## Type Alias: Effectify # Type Alias: Effectify\ :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: ```ts type Effectify = { [K in FnKeys]: EffectifyMethod } & object & { [K in Exclude>]: C[K]; }; ``` Maps every method of `C` to an Effect-returning method, preserving non-fn members. ## Type Parameters ### C `C` ## Type Declaration ### inner ```ts inner: C; ``` --- ## Type Alias: FnKeys # Type Alias: FnKeys\ :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: ```ts type FnKeys = { [K in keyof C]: C[K] extends (a: any) => any ? K : never; }[keyof C]; ``` Keys of `C` whose values are callable. ## Type Parameters ### C `C` --- ## Type Alias: Job :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: ```ts type Job = ActivatedJobResult; ``` A single activated job handed to an Effect handler. This is the raw activation payload (variables + custom headers + lifecycle keys); acknowledgement is driven by the value/error the handler's `Effect` produces, not by imperative methods. --- ## Type Alias: JobError :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: ```ts type JobError = RetryableJobError | TerminalJobError; ``` The typed error channel a job handler may fail with. --- ## Type Alias: JobHandler # Type Alias: JobHandler\ :::info Opt-In Subpath The Effect API is an **opt-in subpath** (`@camunda8/orchestration-cluster-api/effect`) and requires the optional `effect` peer dependency. The main `.` entry stays Promise-based and pulls in zero Effect at runtime. ::: ```ts type JobHandler = (job) => Effect.Effect; ``` A job handler: consumes a [Job](Job.md), produces completion variables or a [JobError](JobError.md). ## Type Parameters ### A `A` _extends_ [`CompleteVars`](CompleteVars.md) ### R `R` ## Parameters ### job [`Job`](Job.md) ## Returns `Effect.Effect`\<`A`, [`JobError`](JobError.md), `R`\> --- ## Class: CamundaClientBase The Camunda client's operation methods. Create clients with [createCamundaClient](../functions/createCamundaClient.md) or [CamundaClient](../variables/CamundaClient.md), which add `.paginate(...)` to every search operation. ## Extends - `CamundaCore` ## Constructors ### Constructor ```ts new CamundaClientBase(opts?): CamundaClientBase; ``` #### Parameters ##### opts? [`CamundaOptions`](../interfaces/CamundaOptions.md) = `{}` #### Returns `CamundaClientBase` #### Overrides ```ts CamundaCore.constructor; ``` ## Accessors ### clock #### Get Signature ```ts get clock(): Clock; ``` Clock backing SDK-internal cadence. The injected one when supplied, else the live clock. ##### Returns [`Clock`](../interfaces/Clock.md) #### Inherited from ```ts CamundaCore.clock; ``` --- ### config #### Get Signature ```ts get config(): Readonly; ``` ##### Returns `Readonly`\<[`CamundaConfig`](../interfaces/CamundaConfig.md)\> #### Inherited from ```ts CamundaCore.config; ``` --- ### random #### Get Signature ```ts get random(): RandomSource; ``` Randomness behind SDK-internal jitter. The injected source when supplied, else the live one. ##### Returns [`RandomSource`](../interfaces/RandomSource.md) #### Inherited from ```ts CamundaCore.random; ``` ## Methods ### \_getSupportLogger() ```ts _getSupportLogger(): SupportLogger; ``` Internal accessor for support logger (no public API commitment yet). #### Returns [`SupportLogger`](../interfaces/SupportLogger.md) #### Inherited from ```ts CamundaCore._getSupportLogger; ``` --- ### \_invokeWithRetry() ```ts _invokeWithRetry(op, opts): Promise; ``` Internal invocation helper to apply global backpressure gating + retry + normalization #### Type Parameters ##### T `T` #### Parameters ##### op () => `Promise`\<`T`\> ##### opts ###### classify? (`e`) => `object` ###### exempt? `boolean` ###### opId `string` ###### retryOverride? \| `false` \| `Partial`\<[`HttpRetryPolicy`](../interfaces/HttpRetryPolicy.md)\> #### Returns `Promise`\<`T`\> #### Inherited from ```ts CamundaCore._invokeWithRetry; ``` --- ### activateAdHocSubProcessActivities() ```ts activateAdHocSubProcessActivities(input, options?): CancelablePromise; ``` Activate activities within an ad-hoc sub-process Activates selected activities within an ad-hoc sub-process identified by element ID. The provided element IDs must exist within the ad-hoc sub-process instance identified by the provided adHocSubProcessInstanceKey. - #### Parameters ##### input [`activateAdHocSubProcessActivitiesInput`](../type-aliases/activateAdHocSubProcessActivitiesInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Activate ad-hoc sub-process activities** ```ts async function activateAdHocSubProcessActivitiesExample( adHocSubProcessInstanceKey: ElementInstanceKey, elementId: ElementId ) { const camunda = createCamundaClient(); await camunda.activateAdHocSubProcessActivities({ adHocSubProcessInstanceKey, elements: [{ elementId }], }); } ``` #### Operation Id activateAdHocSubProcessActivities #### Tags Ad-hoc sub-process --- ### activateJobs() #### Call Signature ```ts activateJobs(input, options?): CancelablePromise<{ jobs: EnrichedActivatedJobOf[]; }>; ``` Activate jobs Iterate through all known partitions and activate jobs up to the requested maximum. - ##### Parameters ###### input [`JobActivationRequest`](../type-aliases/JobActivationRequest.md) & `object` ###### options? [`OperationOptions`](../interfaces/OperationOptions.md) ##### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `jobs`: [`EnrichedActivatedJobOf`](../type-aliases/EnrichedActivatedJobOf.md)\<[`ActivatedJobResultWithJobLeaseToken`](../type-aliases/ActivatedJobResultWithJobLeaseToken.md)\>[]; \}\> ##### Example **Activate and process jobs** ```ts async function activateJobsExample() { const camunda = createCamundaClient(); const result = await camunda.activateJobs({ type: "payment-processing", timeout: 30000, maxJobsToActivate: 5, }); for (const job of result.jobs) { console.log(`Job ${job.jobKey}: ${job.type}`); // Each enriched job has helper methods await job.complete({ paymentId: "PAY-123" }); } } ``` ##### Operation Id activateJobs ##### Tags Job #### Call Signature ```ts activateJobs(input, options?): CancelablePromise<{ jobs: EnrichedActivatedJobOf[]; }>; ``` Activate jobs Iterate through all known partitions and activate jobs up to the requested maximum. - ##### Parameters ###### input [`JobActivationRequest`](../type-aliases/JobActivationRequest.md) & `object` ###### options? [`OperationOptions`](../interfaces/OperationOptions.md) ##### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `jobs`: [`EnrichedActivatedJobOf`](../type-aliases/EnrichedActivatedJobOf.md)\<[`ActivatedJobResultWithoutJobLeaseToken`](../type-aliases/ActivatedJobResultWithoutJobLeaseToken.md)\>[]; \}\> ##### Example **Activate and process jobs** ```ts async function activateJobsExample() { const camunda = createCamundaClient(); const result = await camunda.activateJobs({ type: "payment-processing", timeout: 30000, maxJobsToActivate: 5, }); for (const job of result.jobs) { console.log(`Job ${job.jobKey}: ${job.type}`); // Each enriched job has helper methods await job.complete({ paymentId: "PAY-123" }); } } ``` ##### Operation Id activateJobs ##### Tags Job #### Call Signature ```ts activateJobs(input, options?): CancelablePromise<{ jobs: EnrichedActivatedJob[]; }>; ``` Activate jobs Iterate through all known partitions and activate jobs up to the requested maximum. - ##### Parameters ###### input [`JobActivationRequest`](../type-aliases/JobActivationRequest.md) ###### options? [`OperationOptions`](../interfaces/OperationOptions.md) ##### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `jobs`: [`EnrichedActivatedJob`](../interfaces/EnrichedActivatedJob.md)[]; \}\> ##### Example **Activate and process jobs** ```ts async function activateJobsExample() { const camunda = createCamundaClient(); const result = await camunda.activateJobs({ type: "payment-processing", timeout: 30000, maxJobsToActivate: 5, }); for (const job of result.jobs) { console.log(`Job ${job.jobKey}: ${job.type}`); // Each enriched job has helper methods await job.complete({ paymentId: "PAY-123" }); } } ``` ##### Operation Id activateJobs ##### Tags Job --- ### assignClientToGroup() ```ts assignClientToGroup(input, options?): CancelablePromise; ``` Assign a client to a group Assigns a client to a group, making it a member of the group. Members of the group inherit the group authorizations, roles, and tenant assignments. - #### Parameters ##### input [`assignClientToGroupInput`](../type-aliases/assignClientToGroupInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Assign a client to a group** ```ts async function assignClientToGroupExample( groupId: GroupId, clientId: ClientId ) { const camunda = createCamundaClient(); await camunda.assignClientToGroup({ groupId, clientId, }); } ``` #### Operation Id assignClientToGroup #### Tags Group --- ### assignClientToTenant() ```ts assignClientToTenant(input, options?): CancelablePromise; ``` Assign a client to a tenant Assign the client to the specified tenant. The client can then access tenant data and perform authorized actions. - #### Parameters ##### input [`assignClientToTenantInput`](../type-aliases/assignClientToTenantInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Assign a client to a tenant** ```ts async function assignClientToTenantExample( tenantId: TenantId, clientId: ClientId ) { const camunda = createCamundaClient(); await camunda.assignClientToTenant({ tenantId, clientId, }); } ``` #### Operation Id assignClientToTenant #### Tags Tenant --- ### assignGroupToTenant() ```ts assignGroupToTenant(input, options?): CancelablePromise; ``` Assign a group to a tenant Assigns a group to a specified tenant. Group members (users, clients) can then access tenant data and perform authorized actions. - #### Parameters ##### input [`assignGroupToTenantInput`](../type-aliases/assignGroupToTenantInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Assign a group to a tenant** ```ts async function assignGroupToTenantExample( tenantId: TenantId, groupId: GroupId ) { const camunda = createCamundaClient(); await camunda.assignGroupToTenant({ tenantId, groupId, }); } ``` #### Operation Id assignGroupToTenant #### Tags Tenant --- ### assignMappingRuleToGroup() ```ts assignMappingRuleToGroup(input, options?): CancelablePromise; ``` Assign a mapping rule to a group Assigns a mapping rule to a group. * #### Parameters ##### input [`assignMappingRuleToGroupInput`](../type-aliases/assignMappingRuleToGroupInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Assign a mapping rule to a group** ```ts async function assignMappingRuleToGroupExample( groupId: GroupId, mappingRuleId: MappingRuleId ) { const camunda = createCamundaClient(); await camunda.assignMappingRuleToGroup({ groupId, mappingRuleId, }); } ``` #### Operation Id assignMappingRuleToGroup #### Tags Group --- ### assignMappingRuleToTenant() ```ts assignMappingRuleToTenant(input, options?): CancelablePromise; ``` Assign a mapping rule to a tenant Assign a single mapping rule to a specified tenant. * #### Parameters ##### input [`assignMappingRuleToTenantInput`](../type-aliases/assignMappingRuleToTenantInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Assign a mapping rule to a tenant** ```ts async function assignMappingRuleToTenantExample( tenantId: TenantId, mappingRuleId: MappingRuleId ) { const camunda = createCamundaClient(); await camunda.assignMappingRuleToTenant({ tenantId, mappingRuleId, }); } ``` #### Operation Id assignMappingRuleToTenant #### Tags Tenant --- ### assignProcessInstanceBusinessId() ```ts assignProcessInstanceBusinessId(input, options?): CancelablePromise; ``` Assign business id to process instance Assigns a business id to an already-running process instance that currently has none. The assignment is single and irreversible: only artifacts created after the assignment (for example future jobs, user tasks, decision instances, and message subscriptions) carry the business id, while existing artifacts are not retroactively enriched. Re-sending the same business id succeeds as a no-op. This endpoint is only useful while business id uniqueness enforcement is disabled; when it is enabled, the request is rejected with a 409 response. - #### Parameters ##### input [`assignProcessInstanceBusinessIdInput`](../type-aliases/assignProcessInstanceBusinessIdInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Assign a business ID to a process instance** ```ts async function assignProcessInstanceBusinessIdExample( processInstanceKey: ProcessInstanceKey, businessId: BusinessId ) { const camunda = createCamundaClient(); await camunda.assignProcessInstanceBusinessId({ processInstanceKey, businessId, }); } ``` #### Operation Id assignProcessInstanceBusinessId #### Tags Process instance --- ### assignRoleToClient() ```ts assignRoleToClient(input, options?): CancelablePromise; ``` Assign a role to a client Assigns the specified role to the client. The client will inherit the authorizations associated with this role. * #### Parameters ##### input [`assignRoleToClientInput`](../type-aliases/assignRoleToClientInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Assign a role to a client** ```ts async function assignRoleToClientExample(roleId: RoleId, clientId: ClientId) { const camunda = createCamundaClient(); await camunda.assignRoleToClient({ roleId, clientId, }); } ``` #### Operation Id assignRoleToClient #### Tags Role --- ### assignRoleToGroup() ```ts assignRoleToGroup(input, options?): CancelablePromise; ``` Assign a role to a group Assigns the specified role to the group. Every member of the group (user or client) will inherit the authorizations associated with this role. * #### Parameters ##### input [`assignRoleToGroupInput`](../type-aliases/assignRoleToGroupInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Assign a role to a group** ```ts async function assignRoleToGroupExample(roleId: RoleId, groupId: GroupId) { const camunda = createCamundaClient(); await camunda.assignRoleToGroup({ roleId, groupId, }); } ``` #### Operation Id assignRoleToGroup #### Tags Role --- ### assignRoleToMappingRule() ```ts assignRoleToMappingRule(input, options?): CancelablePromise; ``` Assign a role to a mapping rule Assigns a role to a mapping rule. * #### Parameters ##### input [`assignRoleToMappingRuleInput`](../type-aliases/assignRoleToMappingRuleInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Assign a role to a mapping rule** ```ts async function assignRoleToMappingRuleExample( roleId: RoleId, mappingRuleId: MappingRuleId ) { const camunda = createCamundaClient(); await camunda.assignRoleToMappingRule({ roleId, mappingRuleId, }); } ``` #### Operation Id assignRoleToMappingRule #### Tags Role --- ### assignRoleToTenant() ```ts assignRoleToTenant(input, options?): CancelablePromise; ``` Assign a role to a tenant Assigns a role to a specified tenant. Users, Clients or Groups, that have the role assigned, will get access to the tenant's data and can perform actions according to their authorizations. - #### Parameters ##### input [`assignRoleToTenantInput`](../type-aliases/assignRoleToTenantInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Assign a role to a tenant** ```ts async function assignRoleToTenantExample(tenantId: TenantId, roleId: RoleId) { const camunda = createCamundaClient(); await camunda.assignRoleToTenant({ tenantId, roleId, }); } ``` #### Operation Id assignRoleToTenant #### Tags Tenant --- ### assignRoleToUser() ```ts assignRoleToUser(input, options?): CancelablePromise; ``` Assign a role to a user Assigns the specified role to the user. The user will inherit the authorizations associated with this role. * #### Parameters ##### input [`assignRoleToUserInput`](../type-aliases/assignRoleToUserInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Assign a role to a user** ```ts async function assignRoleToUserExample(roleId: RoleId, username: Username) { const camunda = createCamundaClient(); await camunda.assignRoleToUser({ roleId, username, }); } ``` #### Operation Id assignRoleToUser #### Tags Role --- ### assignUserTask() ```ts assignUserTask(input, options?): CancelablePromise; ``` Assign user task Assigns a user task with the given key to the given assignee. Assignment waits for blocking task listeners on this lifecycle transition. If listener processing is delayed beyond the request timeout, this endpoint can return 504. Other gateway timeout causes are also possible. Retry with backoff and inspect listener worker availability and logs when this repeats. - #### Parameters ##### input [`assignUserTaskInput`](../type-aliases/assignUserTaskInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Assign a user task** ```ts async function assignUserTaskExample(userTaskKey: UserTaskKey) { const camunda = createCamundaClient(); await camunda.assignUserTask({ userTaskKey, assignee: "alice", allowOverride: true, }); } ``` #### Operation Id assignUserTask #### Tags User task --- ### assignUserToGroup() ```ts assignUserToGroup(input, options?): CancelablePromise; ``` Assign a user to a group Assigns a user to a group, making the user a member of the group. Group members inherit the group authorizations, roles, and tenant assignments. - #### Parameters ##### input [`assignUserToGroupInput`](../type-aliases/assignUserToGroupInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Assign a user to a group** ```ts async function assignUserToGroupExample(groupId: GroupId, username: Username) { const camunda = createCamundaClient(); await camunda.assignUserToGroup({ groupId, username, }); } ``` #### Operation Id assignUserToGroup #### Tags Group --- ### assignUserToTenant() ```ts assignUserToTenant(input, options?): CancelablePromise; ``` Assign a user to a tenant Assign a single user to a specified tenant. The user can then access tenant data and perform authorized actions. * #### Parameters ##### input [`assignUserToTenantInput`](../type-aliases/assignUserToTenantInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Assign a user to a tenant** ```ts async function assignUserToTenantExample( tenantId: TenantId, username: Username ) { const camunda = createCamundaClient(); await camunda.assignUserToTenant({ tenantId, username, }); } ``` #### Operation Id assignUserToTenant #### Tags Tenant --- ### broadcastSignal() ```ts broadcastSignal(input, options?): CancelablePromise; ``` Broadcast signal Broadcasts a signal. * #### Parameters ##### input [`SignalBroadcastRequest`](../type-aliases/SignalBroadcastRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`SignalBroadcastResult`](../type-aliases/SignalBroadcastResult.md)\> #### Example **Broadcast a signal** ```ts async function broadcastSignalExample() { const camunda = createCamundaClient(); const result = await camunda.broadcastSignal({ signalName: "system-shutdown", variables: { reason: "Scheduled maintenance", }, }); console.log(`Signal broadcast key: ${result.signalKey}`); } ``` #### Operation Id broadcastSignal #### Tags Signal --- ### cancelBatchOperation() ```ts cancelBatchOperation(input, options?): CancelablePromise; ``` Cancel Batch operation Cancels a running batch operation. This is done asynchronously, the progress can be tracked using the batch operation status endpoint (/batch-operations/{batchOperationKey}). - #### Parameters ##### input ###### batchOperationKey [`BatchOperationKey`](../type-aliases/BatchOperationKey.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Cancel a batch operation** ```ts async function cancelBatchOperationExample( batchOperationKey: BatchOperationKey ) { const camunda = createCamundaClient(); await camunda.cancelBatchOperation({ batchOperationKey }); } ``` #### Operation Id cancelBatchOperation #### Tags Batch operation --- ### cancelClusterRebalance() ```ts cancelClusterRebalance(options?): CancelablePromise; ``` Stop the running rebalance Asks the running rebalance to stop once the transfer in flight has finished. Partitions already transferred keep their new leaders, and those the rebalance had not yet reached keep their current ones. Cancellation requests are idempotent and always accepted. The `wasRunning` response field can be used to distinguish a cancellation that found a running rebalance from one that did not. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. * #### Parameters ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`RebalanceCancellationResponse`](../type-aliases/RebalanceCancellationResponse.md)\> #### Example **Cancel the running cluster rebalance** ```ts async function cancelClusterRebalanceExample() { const camunda = createCamundaClient(); const result = await camunda.cancelClusterRebalance(); console.log( `Cancel requested; was a rebalance running? ${result.wasRunning}` ); } ``` #### Operation Id cancelClusterRebalance #### Tags Cluster --- ### cancelProcessInstance() ```ts cancelProcessInstance(input, options?): CancelablePromise; ``` Cancel process instance Cancels a running process instance. As a cancellation includes more than just the removal of the process instance resource, the cancellation resource must be posted. Cancellation can wait on listener-related processing; when that processing does not complete in time, this endpoint can return 504. Other gateway timeout causes are also possible. Retry with backoff and inspect listener worker availability and logs when this repeats. - #### Parameters ##### input `object` & `object` ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Cancel a process instance** ```ts async function cancelProcessInstanceExample( processDefinitionId: ProcessDefinitionId ) { const camunda = createCamundaClient(); // Create a process instance and get its key from the response const created = await camunda.createProcessInstance({ processDefinitionId, }); // Cancel the process instance using the key from the creation response await camunda.cancelProcessInstance({ processInstanceKey: created.processInstanceKey, }); } ``` #### Operation Id cancelProcessInstance #### Tags Process instance --- ### cancelProcessInstancesBatchOperation() ```ts cancelProcessInstancesBatchOperation(input, options?): CancelablePromise; ``` Cancel process instances (batch) Cancels multiple active or suspended process instances. Only ACTIVE and SUSPENDED root instances can be cancelled. A state filter narrows the batch to the given states. Requesting any state other than ACTIVE or SUSPENDED through the `$eq` or `$in` operators is rejected. Other state operators (`$neq`, `$exists`, `$like`) are applied as given, and the batch remains limited to ACTIVE and SUSPENDED instances. Without a state filter, both ACTIVE and SUSPENDED instances are selected. Any given filter for parentProcessInstanceKey is ignored and overridden during this batch operation. This is done asynchronously, the progress can be tracked using the batchOperationKey from the response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). - #### Parameters ##### input [`ProcessInstanceCancellationBatchOperationRequest`](../type-aliases/ProcessInstanceCancellationBatchOperationRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`BatchOperationCreatedResult`](../type-aliases/BatchOperationCreatedResult.md)\> #### Example **Cancel process instances in batch** ```ts async function cancelProcessInstancesBatchOperationExample( processDefinitionKey: ProcessDefinitionKey ) { const camunda = createCamundaClient(); const result = await camunda.cancelProcessInstancesBatchOperation({ filter: { processDefinitionKey, }, }); console.log(`Batch operation key: ${result.batchOperationKey}`); } ``` #### Operation Id cancelProcessInstancesBatchOperation #### Tags Process instance --- ### changeClusterMode() ```ts changeClusterMode(input, options?): CancelablePromise; ``` Change cluster mode Transitions the cluster between processing and recovery mode. This is a non-blocking operation: the request is acknowledged once the change has been accepted, before the transition itself has completed. Entering recovery mode deactivates all partitions so that only a restricted set of read-only operations remains available; exiting recovery mode returns the cluster to normal processing. Returns the planned cluster change so its progress can be monitored via the topology. * #### Parameters ##### input [`changeClusterModeInput`](../type-aliases/changeClusterModeInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterModeChangeResponse`](../type-aliases/ClusterModeChangeResponse.md)\> #### Example **Change cluster mode** ```ts async function changeClusterModeExample() { const camunda = createCamundaClient(); // Transition the cluster into recovery mode. Pass `dryRun: true` to validate // the request and inspect the resulting plan without applying it. Omit it (or // set it to false) to actually trigger the transition. const change = await camunda.changeClusterMode({ mode: "RECOVERING", dryRun: true, }); // Operations are grouped by physical tenant; a null tenant means the operation // is not scoped to one, such as a broker lifecycle operation. console.log(`Cluster change ${change.changeId}:`); for (const group of change.plannedChanges) { console.log(` ${group.physicalTenantId ?? "cluster-wide"}:`); for (const op of group.operations) { console.log(` ${op.operation}${op.mode ? ` -> ${op.mode}` : ""}`); } } } ``` #### Operation Id changeClusterMode #### Tags Recovery --- ### changeClusterModeAsClusterAdmin() ```ts changeClusterModeAsClusterAdmin(input, options?): CancelablePromise; ``` Change the cluster mode of one or every physical tenant Transitions physical tenants between processing and recovery mode. If the `physicalTenantId` parameter is not provided, all available physical tenants are transitioned individually. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. * #### Parameters ##### input [`changeClusterModeAsClusterAdminInput`](../type-aliases/changeClusterModeAsClusterAdminInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterModeChangeResponse`](../type-aliases/ClusterModeChangeResponse.md)\> #### Example **Change cluster mode as cluster admin** ```ts async function changeClusterModeAsClusterAdminExample() { const camunda = createCamundaClient(); // The cluster-admin variant can target a single physical tenant. Omit // `physicalTenantId` to apply the change to every physical tenant. const change = await camunda.changeClusterModeAsClusterAdmin({ mode: "RECOVERING", physicalTenantId: "default", dryRun: true, }); console.log(`Cluster change ${change.changeId}:`); for (const group of change.plannedChanges) { console.log(` ${group.physicalTenantId ?? "cluster-wide"}:`); for (const op of group.operations) { console.log(` ${op.operation}${op.mode ? ` -> ${op.mode}` : ""}`); } } } ``` #### Operation Id changeClusterModeAsClusterAdmin #### Tags Recovery --- ### clearAuthCache() ```ts clearAuthCache(opts?): void; ``` #### Parameters ##### opts? ###### disk? `boolean` ###### memory? `boolean` #### Returns `void` #### Inherited from ```ts CamundaCore.clearAuthCache; ``` --- ### completeJob() ```ts completeJob(input, options?): CancelablePromise; ``` Complete job Complete a job with the given payload, which allows completing the associated service task. - #### Parameters ##### input [`completeJobInput`](../type-aliases/completeJobInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Complete a job** ```ts async function completeJobExample(jobKey: JobKey) { const camunda = createCamundaClient(); await camunda.completeJob({ jobKey, variables: { paymentId: "PAY-123", status: "completed", }, }); } ``` #### Operation Id completeJob #### Tags Job --- ### completeUserTask() ```ts completeUserTask(input, options?): CancelablePromise; ``` Complete user task Completes a user task with the given key. Completion waits for blocking task listeners on this lifecycle transition. If listener processing is delayed beyond the request timeout, this endpoint can return 504. Other gateway timeout causes are also possible. Retry with backoff and inspect listener worker availability and logs when this repeats. - #### Parameters ##### input [`completeUserTaskInput`](../type-aliases/completeUserTaskInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Complete a user task** ```ts async function completeUserTaskExample(userTaskKey: UserTaskKey) { const camunda = createCamundaClient(); await camunda.completeUserTask({ userTaskKey, variables: { approved: true, comment: "Looks good", }, }); } ``` #### Operation Id completeUserTask #### Tags User task --- ### configure() ```ts configure(next): void; ``` #### Parameters ##### next [`CamundaOptions`](../interfaces/CamundaOptions.md) #### Returns `void` #### Inherited from ```ts CamundaCore.configure; ``` --- ### correlateMessage() ```ts correlateMessage(input, options?): CancelablePromise; ``` Correlate message Publishes a message and correlates it to a subscription. If correlation is successful it will return the first process instance key the message correlated with. The message is not buffered. Use the publish message endpoint to send messages that can be buffered. - #### Parameters ##### input [`MessageCorrelationRequest`](../type-aliases/MessageCorrelationRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`MessageCorrelationResult`](../type-aliases/MessageCorrelationResult.md)\> #### Example **Correlate a message** ```ts async function correlateMessageExample() { const camunda = createCamundaClient(); const result = await camunda.correlateMessage({ name: "order-payment-received", correlationKey: "ORD-12345", variables: { paymentId: "PAY-123", amount: 99.95, }, }); console.log(`Message correlated to: ${result.processInstanceKey}`); } ``` #### Operation Id correlateMessage #### Tags Message --- ### createAdminUser() ```ts createAdminUser(input, options?): CancelablePromise; ``` 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. * #### Parameters ##### input [`UserRequest`](../type-aliases/UserRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`UserCreateResult`](../type-aliases/UserCreateResult.md)\> #### Example **Create an admin user** ```ts async function createAdminUserExample(username: Username) { const camunda = createCamundaClient(); const result = await camunda.createAdminUser({ username, name: "Admin User", email: "admin@example.com", password: "admin-password-123", }); console.log(`Created admin user: ${result.username}`); } ``` #### Operation Id createAdminUser #### Tags Setup --- ### createAgentInstance() ```ts createAgentInstance(input, options?): CancelablePromise; ``` Create agent instance Creates a new agent instance. The returned key identifies the instance and must be used in subsequent update and query calls. - #### Parameters ##### input [`AgentInstanceCreationRequest`](../type-aliases/AgentInstanceCreationRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`AgentInstanceCreationResult`](../type-aliases/AgentInstanceCreationResult.md)\> #### Example **Create an agent instance** ```ts async function createAgentInstanceExample( elementInstanceKey: ElementInstanceKey, jobKey: JobKey, jobLeaseToken: JobLeaseToken ) { const camunda = createCamundaClient(); // The batch must open with a CONFIGURATION item; it establishes the model, // provider and system prompt for the instance. const result = await camunda.createAgentInstance({ elementInstanceKey, jobKey, jobLeaseToken, history: [ { historyItemId: HistoryItemId.assumeExists("configuration-1"), loopIteration: 1, role: "CONFIGURATION", content: [], producedAt: new Date().toISOString(), model: "gpt-4o", provider: "openai", systemPrompt: [ { contentType: "TEXT", text: "You are a helpful assistant." }, ], }, ], }); console.log(`Created agent instance: ${result.agentInstanceKey}`); } ``` #### Operation Id createAgentInstance #### Tags Agent instance --- ### createAuthorization() ```ts createAuthorization(input, options?): CancelablePromise; ``` Create authorization Create the authorization. * #### Parameters ##### input \| [`AuthorizationIdBasedRequest`](../type-aliases/AuthorizationIdBasedRequest.md) \| [`AuthorizationPropertyBasedRequest`](../type-aliases/AuthorizationPropertyBasedRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`AuthorizationCreateResult`](../type-aliases/AuthorizationCreateResult.md)\> #### Example **Create an authorization** ```ts async function createAuthorizationExample() { const camunda = createCamundaClient(); const result = await camunda.createAuthorization({ ownerId: "user-123", ownerType: "USER", resourceId: "order-process", resourceType: "PROCESS_DEFINITION", permissionTypes: ["CREATE_PROCESS_INSTANCE", "READ_PROCESS_INSTANCE"], }); console.log(`Authorization key: ${result.authorizationKey}`); } ``` #### Operation Id createAuthorization #### Tags Authorization --- ### createDeployment() ```ts createDeployment(input, options?): CancelablePromise; ``` Deploy resources Deploys one or more resources, including BPMN processes, DMN decision models, forms, RPA resources, and generic files. A deployment can contain any file type. Files that are not interpreted as BPMN, DMN, form, or RPA resources are stored as deployable generic resources in the engine. This is an atomic call, i.e. either all resources are deployed or none of them are. - #### Parameters ##### input [`createDeploymentInput`](../type-aliases/createDeploymentInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ExtendedDeploymentResult`](../interfaces/ExtendedDeploymentResult.md)\> Enriched deployment result with typed arrays (processes, decisions, decisionRequirements, forms, resources). #### Example **Deploy resources from files** ```ts async function deployResourcesFromFilesExample() { const camunda = createCamundaClient(); // Node.js only: deploy directly from file paths const result = await camunda.deployResourcesFromFiles([ "./process.bpmn", "./decision.dmn", ]); console.log(`Deployment key: ${result.deploymentKey}`); } ``` #### Operation Id createDeployment #### Tags Resource --- ### createDocument() ```ts createDocument(input, options?): CancelablePromise; ``` Upload document Upload a document to the Camunda 8 cluster. Note that this is currently supported for document stores of type: AWS, Azure, GCP, in-memory (non-production), local (non-production) - #### Parameters ##### input [`createDocumentInput`](../type-aliases/createDocumentInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`DocumentReference`](../type-aliases/DocumentReference.md)\> #### Example **Upload a document** ```ts async function createDocumentExample() { const camunda = createCamundaClient(); const file = new Blob(["Hello, world!"], { type: "text/plain" }); const result = await camunda.createDocument({ file, metadata: { fileName: "hello.txt" }, }); console.log(`Document ID: ${result.documentId}`); } ``` #### Operation Id createDocument #### Tags Document --- ### createDocumentLink() ```ts createDocumentLink(input, options?): CancelablePromise; ``` Create document link Create a link to a document in the Camunda 8 cluster. Note that this is currently supported for document stores of type: AWS, Azure, GCP - #### Parameters ##### input [`createDocumentLinkInput`](../type-aliases/createDocumentLinkInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`DocumentLink`](../type-aliases/DocumentLink.md)\> #### Example **Create a document link** ```ts async function createDocumentLinkExample(documentId: DocumentId) { const camunda = createCamundaClient(); const link = await camunda.createDocumentLink({ documentId, timeToLive: 3600000, }); console.log(`Document link: ${link.url}`); } ``` #### Operation Id createDocumentLink #### Tags Document --- ### createDocuments() ```ts createDocuments(input, options?): CancelablePromise; ``` Upload multiple documents Upload multiple documents to the Camunda 8 cluster. The caller must provide a file name for each document, which will be used in case of a multi-status response to identify which documents failed to upload. The file name can be provided in the `Content-Disposition` header of the file part or in the `fileName` field of the metadata. You can add a parallel array of metadata objects. These are matched with the files based on index, and must have the same length as the files array. To pass homogenous metadata for all files, spread the metadata over the metadata array. A filename value provided explicitly via the metadata array in the request overrides the `Content-Disposition` header of the file part. In case of a multi-status response, the response body will contain a list of `DocumentBatchProblemDetail` objects, each of which contains the file name of the document that failed to upload and the reason for the failure. The client can choose to retry the whole batch or individual documents based on the response. Note that this is currently supported for document stores of type: AWS, Azure, GCP, in-memory (non-production), local (non-production) - #### Parameters ##### input [`createDocumentsInput`](../type-aliases/createDocumentsInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`DocumentCreationBatchResponse`](../type-aliases/DocumentCreationBatchResponse.md)\> #### Example **Upload multiple documents** ```ts async function createDocumentsExample() { const camunda = createCamundaClient(); const file1 = new Blob(["File one"], { type: "text/plain" }); const file2 = new Blob(["File two"], { type: "text/plain" }); const result = await camunda.createDocuments({ files: [file1, file2], metadataList: [{ fileName: "one.txt" }, { fileName: "two.txt" }], }); for (const doc of result.createdDocuments ?? []) { console.log(`Created: ${doc.documentId}`); } } ``` #### Operation Id createDocuments #### Tags Document --- ### createElementInstanceVariables() ```ts createElementInstanceVariables(input, options?): CancelablePromise; ``` Update element instance variables Updates all the variables of a particular scope (for example, process instance, element instance) with the given variable data. Specify the element instance in the `elementInstanceKey` parameter. Variable updates can be delayed by listener-related processing; if processing exceeds the request timeout, this endpoint can return 504. Other gateway timeout causes are also possible. Retry with backoff and inspect listener worker availability and logs when this repeats. - #### Parameters ##### input [`createElementInstanceVariablesInput`](../type-aliases/createElementInstanceVariablesInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Create element instance variables** ```ts async function createElementInstanceVariablesExample( elementInstanceKey: ElementInstanceKey ) { const camunda = createCamundaClient(); await camunda.createElementInstanceVariables({ elementInstanceKey, variables: { orderId: "ORD-12345", status: "processing" }, }); } ``` #### Operation Id createElementInstanceVariables #### Tags Element instance --- ### createGlobalClusterVariable() ```ts createGlobalClusterVariable(input, options?): CancelablePromise; ``` Create a global-scoped cluster variable Create a global-scoped cluster variable. * #### Parameters ##### input [`CreateClusterVariableRequest`](../type-aliases/CreateClusterVariableRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterVariableResult`](../type-aliases/ClusterVariableResult.md)\> #### Example **Create a global cluster variable** ```ts async function createGlobalClusterVariableExample(name: ClusterVariableName) { const camunda = createCamundaClient(); const result = await camunda.createGlobalClusterVariable({ name, value: { darkMode: true }, }); console.log(`Created: ${result.name}`); } ``` #### Operation Id createGlobalClusterVariable #### Tags Cluster Variable --- ### createGlobalTaskListener() ```ts createGlobalTaskListener(input, options?): CancelablePromise; ``` Create global user task listener Create a new global user task listener. * #### Parameters ##### input [`CreateGlobalTaskListenerRequest`](../type-aliases/CreateGlobalTaskListenerRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`GlobalTaskListenerResult`](../type-aliases/GlobalTaskListenerResult.md)\> #### Example **Create a global task listener** ```ts async function createGlobalTaskListenerExample(id: GlobalListenerId) { const camunda = createCamundaClient(); const result = await camunda.createGlobalTaskListener({ id, eventTypes: ["completing"], type: "audit-log-listener", }); console.log(`Created listener: ${result.id}`); } ``` #### Operation Id createGlobalTaskListener #### Tags Global listener --- ### createGroup() ```ts createGroup(input, options?): CancelablePromise; ``` Create group Create a new group. The supplied `groupId` is validated against `^[a-zA-Z0-9_~@.+-]+$` (max 256 characters) by `IdentifierValidator.validateId` in the runtime. This strict validation applies wherever the Groups API is available: in OIDC deployments that set `camunda.security.authentication.oidc.groupsClaim` the Groups API (including this endpoint) is disabled entirely, so group CRUD never sees externally-minted IdP IDs. The BYOG relaxation only loosens validation when a group is referenced _as a member_ of a role or tenant (`assignRoleToGroup`, `assignGroupToTenant`); group CRUD itself always uses the strict default-id regex. The constraint is not advertised on the `GroupId` schema so that the same schema can be reused at member-reference sites without falsely rejecting externally-minted IdP group IDs there. - #### Parameters ##### input [`GroupCreateRequest`](../type-aliases/GroupCreateRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`GroupCreateResult`](../type-aliases/GroupCreateResult.md)\> #### Example **Create a group** ```ts async function createGroupExample(groupId: GroupId) { const camunda = createCamundaClient(); const result = await camunda.createGroup({ groupId, name: "Engineering Team", }); console.log(`Created group: ${result.groupId}`); } ``` #### Operation Id createGroup #### Tags Group --- ### createJobWorker() ```ts createJobWorker(cfg): JobWorker; ``` Create a job worker that activates and processes jobs of the given type. Worker configuration fields inherit global defaults resolved via the unified configuration (environment variables or equivalent `CAMUNDA_WORKER_*` keys provided via `CamundaOptions.config`) when not explicitly set on the config object. #### Type Parameters ##### In `In` _extends_ `ZodTypeAny`\<`unknown`, `unknown`, `$ZodTypeInternals`\<`unknown`, `unknown`\>\> = `any` ##### Out `Out` _extends_ `ZodTypeAny`\<`unknown`, `unknown`, `$ZodTypeInternals`\<`unknown`, `unknown`\>\> = `any` ##### Headers `Headers` _extends_ `ZodTypeAny`\<`unknown`, `unknown`, `$ZodTypeInternals`\<`unknown`, `unknown`\>\> = `any` #### Parameters ##### cfg [`JobWorkerConfig`](../interfaces/JobWorkerConfig.md)\<`In`, `Out`, `Headers`\> Worker configuration #### Returns [`JobWorker`](../interfaces/JobWorker.md) #### Examples **Create a job worker** ```ts async function createJobWorkerExample() { const camunda = createCamundaClient(); const _worker = camunda.createJobWorker({ jobType: "payment-processing", jobTimeoutMs: 30000, maxParallelJobs: 5, jobHandler: async (job): Promise => { console.log(`Processing job ${job.jobKey}`); return job.complete({ processed: true }); }, }); // Workers run continuously until closed // worker.close(); } ``` **Job worker with error handling** ```ts async function jobWorkerWithErrorHandlingExample() { const camunda = createCamundaClient(); const worker = camunda.createJobWorker({ jobType: "email-sending", jobTimeoutMs: 60000, maxParallelJobs: 10, pollIntervalMs: 300, jobHandler: async (job): Promise => { try { console.log(`Sending email for job ${job.jobKey}`); return job.complete({ sent: true }); } catch (err) { return job.fail({ errorMessage: String(err), retries: (job.retries ?? 1) - 1, }); } }, }); void worker; } ``` --- ### createMappingRule() ```ts createMappingRule(input, options?): CancelablePromise; ``` Create mapping rule Create a new mapping rule - #### Parameters ##### input [`MappingRuleCreateRequest`](../type-aliases/MappingRuleCreateRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`MappingRuleCreateUpdateResult`](../type-aliases/MappingRuleCreateUpdateResult.md)\> #### Example **Create a mapping rule** ```ts async function createMappingRuleExample(mappingRuleId: MappingRuleId) { const camunda = createCamundaClient(); const result = await camunda.createMappingRule({ mappingRuleId, name: "LDAP Group Mapping", claimName: "groups", claimValue: "engineering", }); console.log(`Created mapping rule: ${result.mappingRuleId}`); } ``` #### Operation Id createMappingRule #### Tags Mapping rule --- ### createProcessInstance() ```ts createProcessInstance(input, options?): CancelablePromise; ``` Create process instance Creates and starts an instance of the specified process. The process definition to use to create the instance can be specified either using its unique key (as returned by Deploy resources), or using the BPMN process id and a version. If only the process definition id is given, the latest ACTIVE version is used. If no ACTIVE version exists, the request is rejected as not found. Waits for the completion of the process instance before returning a result when awaitCompletion is enabled. - #### Parameters ##### input \| [`ProcessInstanceCreationInstructionByKey`](../type-aliases/ProcessInstanceCreationInstructionByKey.md) \| [`ProcessInstanceCreationInstructionById`](../type-aliases/ProcessInstanceCreationInstructionById.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`CreateProcessInstanceResult`](../type-aliases/CreateProcessInstanceResult.md)\> #### Examples **By ID** ```ts async function createProcessInstanceByIdExample( processDefinitionId: ProcessDefinitionId ) { const camunda = createCamundaClient(); const result = await camunda.createProcessInstance({ processDefinitionId, variables: { orderId: "ORD-12345", amount: 99.95, }, }); console.log(`Started process instance: ${result.processInstanceKey}`); } ``` **By key** ```ts async function createProcessInstanceByKeyExample( processDefinitionKey: ProcessDefinitionKey ) { const camunda = createCamundaClient(); // Key from a previous API response (e.g. deployment) const result = await camunda.createProcessInstance({ processDefinitionKey, variables: { orderId: "ORD-12345", amount: 99.95, }, }); console.log(`Started process instance: ${result.processInstanceKey}`); } ``` #### Operation Id createProcessInstance #### Tags Process instance --- ### createRole() ```ts createRole(input, options?): CancelablePromise; ``` Create role Create a new role. * #### Parameters ##### input [`RoleCreateRequest`](../type-aliases/RoleCreateRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`RoleCreateResult`](../type-aliases/RoleCreateResult.md)\> #### Example **Create a role** ```ts async function createRoleExample(roleId: RoleId) { const camunda = createCamundaClient(); const result = await camunda.createRole({ roleId, name: "Process Admin", }); console.log(`Created role: ${result.roleId}`); } ``` #### Operation Id createRole #### Tags Role --- ### createTenant() ```ts createTenant(input, options?): CancelablePromise; ``` Create tenant Creates a new tenant. * #### Parameters ##### input [`TenantCreateRequest`](../type-aliases/TenantCreateRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`TenantCreateResult`](../type-aliases/TenantCreateResult.md)\> #### Example **Create a tenant** ```ts async function createTenantExample(tenantId: TenantId) { const camunda = createCamundaClient(); const result = await camunda.createTenant({ tenantId, name: "Customer Service", }); console.log(`Created tenant: ${result.tenantId}`); } ``` #### Operation Id createTenant #### Tags Tenant --- ### createTenantClusterVariable() ```ts createTenantClusterVariable(input, options?): CancelablePromise; ``` Create a tenant-scoped cluster variable Create a new cluster variable for the given tenant. * #### Parameters ##### input [`createTenantClusterVariableInput`](../type-aliases/createTenantClusterVariableInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterVariableResult`](../type-aliases/ClusterVariableResult.md)\> #### Example **Create a tenant cluster variable** ```ts async function createTenantClusterVariableExample( tenantId: TenantId, name: ClusterVariableName ) { const camunda = createCamundaClient(); const result = await camunda.createTenantClusterVariable({ tenantId, name, value: { region: "us-east-1" }, }); console.log(`Created: ${result.name}`); } ``` #### Operation Id createTenantClusterVariable #### Tags Cluster Variable --- ### createThreadedJobWorker() ```ts createThreadedJobWorker(cfg): ThreadedJobWorker; ``` Create a threaded job worker that runs handler logic in a pool of worker threads. The handler must be a separate module file that exports a default function with signature `(job, client) => Promise`. This keeps the main event loop free for polling and I/O, dramatically improving throughput for CPU-bound job handlers. Worker configuration fields inherit global defaults resolved via the unified configuration (environment variables or equivalent `CAMUNDA_WORKER_*` keys provided via `CamundaOptions.config`) when not explicitly set on the config object. #### Type Parameters ##### In `In` _extends_ `ZodTypeAny`\<`unknown`, `unknown`, `$ZodTypeInternals`\<`unknown`, `unknown`\>\> = `any` ##### Out `Out` _extends_ `ZodTypeAny`\<`unknown`, `unknown`, `$ZodTypeInternals`\<`unknown`, `unknown`\>\> = `any` ##### Headers `Headers` _extends_ `ZodTypeAny`\<`unknown`, `unknown`, `$ZodTypeInternals`\<`unknown`, `unknown`\>\> = `any` #### Parameters ##### cfg [`ThreadedJobWorkerConfig`](../interfaces/ThreadedJobWorkerConfig.md)\<`In`, `Out`, `Headers`\> Threaded worker configuration #### Returns [`ThreadedJobWorker`](../interfaces/ThreadedJobWorker.md) #### Example **Create a threaded job worker** ```ts const worker = client.createThreadedJobWorker({ jobType: "cpu-heavy-task", handlerModule: "./my-handler.js", maxParallelJobs: 32, jobTimeoutMs: 30000, }); ``` --- ### createUser() ```ts createUser(input, options?): CancelablePromise; ``` Create user Create a new user. * #### Parameters ##### input [`UserRequest`](../type-aliases/UserRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`UserCreateResult`](../type-aliases/UserCreateResult.md)\> #### Example **Create a user** ```ts async function createUserExample(username: Username) { const camunda = createCamundaClient(); const result = await camunda.createUser({ username, name: "Alice Smith", email: "alice@example.com", password: "secure-password-123", }); console.log(`Created user: ${result.username}`); } ``` #### Operation Id createUser #### Tags User --- ### deleteAuthorization() ```ts deleteAuthorization(input, options?): CancelablePromise; ``` Delete authorization Deletes the authorization with the given key. * #### Parameters ##### input [`deleteAuthorizationInput`](../type-aliases/deleteAuthorizationInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Delete an authorization** ```ts async function deleteAuthorizationExample(authorizationKey: AuthorizationKey) { const camunda = createCamundaClient(); await camunda.deleteAuthorization({ authorizationKey }); } ``` #### Operation Id deleteAuthorization #### Tags Authorization --- ### deleteDecisionInstance() ```ts deleteDecisionInstance(input, options?): CancelablePromise; ``` Delete decision instance Delete all associated decision evaluations based on provided key. * #### Parameters ##### input `object` & `object` ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Delete a decision instance** ```ts async function deleteDecisionInstanceExample( decisionEvaluationKey: DecisionEvaluationKey ) { const camunda = createCamundaClient(); await camunda.deleteDecisionInstance({ decisionEvaluationKey }); } ``` #### Operation Id deleteDecisionInstance #### Tags Decision instance --- ### deleteDecisionInstancesBatchOperation() ```ts deleteDecisionInstancesBatchOperation(input, options?): CancelablePromise; ``` Delete decision instances (batch) Delete multiple decision instances. This will delete the historic data from secondary storage. This is done asynchronously, the progress can be tracked using the batchOperationKey from the response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). - #### Parameters ##### input [`DecisionInstanceDeletionBatchOperationRequest`](../type-aliases/DecisionInstanceDeletionBatchOperationRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`BatchOperationCreatedResult`](../type-aliases/BatchOperationCreatedResult.md)\> #### Example **Delete decision instances in batch** ```ts async function deleteDecisionInstancesBatchOperationExample() { const camunda = createCamundaClient(); const result = await camunda.deleteDecisionInstancesBatchOperation({ filter: {}, }); console.log(`Batch operation key: ${result.batchOperationKey}`); } ``` #### Operation Id deleteDecisionInstancesBatchOperation #### Tags Decision instance --- ### deleteDocument() ```ts deleteDocument(input, options?): CancelablePromise; ``` Delete document Delete a document from the Camunda 8 cluster. Note that this is currently supported for document stores of type: AWS, Azure, GCP, in-memory (non-production), local (non-production) - #### Parameters ##### input [`deleteDocumentInput`](../type-aliases/deleteDocumentInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Delete a document** ```ts async function deleteDocumentExample(documentId: DocumentId) { const camunda = createCamundaClient(); await camunda.deleteDocument({ documentId }); } ``` #### Operation Id deleteDocument #### Tags Document --- ### deleteGlobalClusterVariable() ```ts deleteGlobalClusterVariable(input, options?): CancelablePromise; ``` Delete a global-scoped cluster variable Delete a global-scoped cluster variable. * #### Parameters ##### input [`deleteGlobalClusterVariableInput`](../type-aliases/deleteGlobalClusterVariableInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Delete a global cluster variable** ```ts async function deleteGlobalClusterVariableExample(name: ClusterVariableName) { const camunda = createCamundaClient(); await camunda.deleteGlobalClusterVariable({ name }); } ``` #### Operation Id deleteGlobalClusterVariable #### Tags Cluster Variable --- ### deleteGlobalTaskListener() ```ts deleteGlobalTaskListener(input, options?): CancelablePromise; ``` Delete global user task listener Deletes a global user task listener. * #### Parameters ##### input [`deleteGlobalTaskListenerInput`](../type-aliases/deleteGlobalTaskListenerInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Delete a global task listener** ```ts async function deleteGlobalTaskListenerExample(id: GlobalListenerId) { const camunda = createCamundaClient(); await camunda.deleteGlobalTaskListener({ id, }); } ``` #### Operation Id deleteGlobalTaskListener #### Tags Global listener --- ### deleteGroup() ```ts deleteGroup(input, options?): CancelablePromise; ``` Delete group Deletes the group with the given ID. * #### Parameters ##### input [`deleteGroupInput`](../type-aliases/deleteGroupInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Delete a group** ```ts async function deleteGroupExample(groupId: GroupId) { const camunda = createCamundaClient(); await camunda.deleteGroup({ groupId }); } ``` #### Operation Id deleteGroup #### Tags Group --- ### deleteHistoryBackup() ```ts deleteHistoryBackup(input, options?): CancelablePromise; ``` Delete history backup Deletes the history backup with the given id, by deleting every snapshot that makes it up. Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. - #### Parameters ##### input [`deleteHistoryBackupInput`](../type-aliases/deleteHistoryBackupInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Delete a history backup** ```ts async function deleteHistoryBackupExample() { const camunda = createCamundaClient(); await camunda.deleteHistoryBackup({ backupId: 100 }); } ``` #### Operation Id deleteHistoryBackup #### Tags Backup --- ### deleteHistoryBackupAsClusterAdmin() ```ts deleteHistoryBackupAsClusterAdmin(input, options?): CancelablePromise; ``` Delete a history backup across physical tenants Deletes the history backup with the given id from every physical tenant of the cluster, or from the one named by `physicalTenantId`. A tenant that does not hold the backup has already reached the requested end state, so it counts as deleted rather than as a failure. The request is all-or-nothing: a physical tenant the backup cannot be deleted from fails the whole request, and the deletions that already succeeded on other tenants are not undone. Narrow the request with `physicalTenantId` to delete from the tenants that can still be reached. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. Use `DELETE /v2/backups/history/{backupId}` to act as a single physical tenant. * #### Parameters ##### input [`deleteHistoryBackupAsClusterAdminInput`](../type-aliases/deleteHistoryBackupAsClusterAdminInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Delete a history backup (cluster admin)** ```ts async function deleteHistoryBackupAsClusterAdminExample() { const camunda = createCamundaClient(); // Deletion fans out to every physical tenant (or a single one when // `physicalTenantId` is given) and is not undone if a later tenant fails. await camunda.deleteHistoryBackupAsClusterAdmin({ backupId: 100 }); } ``` #### Operation Id deleteHistoryBackupAsClusterAdmin #### Tags Backup --- ### deleteMappingRule() ```ts deleteMappingRule(input, options?): CancelablePromise; ``` Delete a mapping rule Deletes the mapping rule with the given ID. - #### Parameters ##### input [`deleteMappingRuleInput`](../type-aliases/deleteMappingRuleInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Delete a mapping rule** ```ts async function deleteMappingRuleExample(mappingRuleId: MappingRuleId) { const camunda = createCamundaClient(); await camunda.deleteMappingRule({ mappingRuleId }); } ``` #### Operation Id deleteMappingRule #### Tags Mapping rule --- ### deleteProcessInstance() ```ts deleteProcessInstance(input, options?): CancelablePromise; ``` Delete process instance Deletes a process instance. Only instances that are completed or terminated can be deleted. * #### Parameters ##### input `object` & `object` ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Delete a process instance** ```ts async function deleteProcessInstanceExample( processInstanceKey: ProcessInstanceKey ) { const camunda = createCamundaClient(); await camunda.deleteProcessInstance({ processInstanceKey }); } ``` #### Operation Id deleteProcessInstance #### Tags Process instance --- ### deleteProcessInstancesBatchOperation() ```ts deleteProcessInstancesBatchOperation(input, options?): CancelablePromise; ``` Delete process instances (batch) Delete multiple process instances. This will delete the historic data from secondary storage. Only process instances in a final state (COMPLETED or TERMINATED) can be deleted. This is done asynchronously, the progress can be tracked using the batchOperationKey from the response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). - #### Parameters ##### input [`ProcessInstanceDeletionBatchOperationRequest`](../type-aliases/ProcessInstanceDeletionBatchOperationRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`BatchOperationCreatedResult`](../type-aliases/BatchOperationCreatedResult.md)\> #### Example **Delete process instances in batch** ```ts async function deleteProcessInstancesBatchOperationExample( processDefinitionKey: ProcessDefinitionKey ) { const camunda = createCamundaClient(); const result = await camunda.deleteProcessInstancesBatchOperation({ filter: { processDefinitionKey, }, }); console.log(`Batch operation key: ${result.batchOperationKey}`); } ``` #### Operation Id deleteProcessInstancesBatchOperation #### Tags Process instance --- ### deleteResource() ```ts deleteResource(input, options?): CancelablePromise; ``` 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`. History deletion is supported for process definitions and decision requirements definitions; for other resource types (forms, generic resources) the flag is ignored and no history is deleted. The two supported types differ in how the history is removed. For a decision requirements definition the history is deleted asynchronously via a batch operation whose details are returned in the `batchOperation` field of the response. For a process definition that still exists in the runtime state, the definition first drains its running instances and its history is deleted asynchronously once the definition is fully removed cluster-wide; no batch operation is returned in the response. If the process definition has already been removed from the runtime state and the deletion is later re-triggered with `deleteHistory` set to `true`, a batch operation is created immediately and returned in the `batchOperation` field. * #### Parameters ##### input `object` & `object` ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`DeleteResourceResponse`](../type-aliases/DeleteResourceResponse.md)\> #### Example **Delete a resource** ```ts async function deleteResourceExample(resourceKey: ProcessDefinitionKey) { const camunda = createCamundaClient(); // Use a process definition key as a resource key for deletion await camunda.deleteResource({ resourceKey, }); } ``` #### Operation Id deleteResource #### Tags Resource --- ### deleteRole() ```ts deleteRole(input, options?): CancelablePromise; ``` Delete role Deletes the role with the given ID. * #### Parameters ##### input [`deleteRoleInput`](../type-aliases/deleteRoleInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Delete a role** ```ts async function deleteRoleExample(roleId: RoleId) { const camunda = createCamundaClient(); await camunda.deleteRole({ roleId }); } ``` #### Operation Id deleteRole #### Tags Role --- ### deleteRuntimeBackup() ```ts deleteRuntimeBackup(input, options?): CancelablePromise; ``` Delete runtime backup Deletes the runtime backup with the given id. * #### Parameters ##### input [`deleteRuntimeBackupInput`](../type-aliases/deleteRuntimeBackupInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Delete a runtime backup** ```ts async function deleteRuntimeBackupExample() { const camunda = createCamundaClient(); await camunda.deleteRuntimeBackup({ backupId: 100 }); } ``` #### Operation Id deleteRuntimeBackup #### Tags Backup --- ### deleteRuntimeBackupAsClusterAdmin() ```ts deleteRuntimeBackupAsClusterAdmin(input, options?): CancelablePromise; ``` Delete a runtime backup across physical tenants Deletes the runtime backup with the given id from every physical tenant of the cluster, or from the one named by `physicalTenantId`. A tenant that does not hold the backup has already reached the requested end state, so it counts as deleted rather than as a failure — the same as deleting an unknown backup id through the per-physical-tenant endpoint. The request is all-or-nothing: a physical tenant the backup cannot be deleted from fails the whole request, and the deletions that already succeeded on other tenants are not undone. Narrow the request with `physicalTenantId` to delete from the tenants that can still be reached. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Use `DELETE /v2/backups/runtime/{backupId}` to act as a single physical tenant. * #### Parameters ##### input [`deleteRuntimeBackupAsClusterAdminInput`](../type-aliases/deleteRuntimeBackupAsClusterAdminInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Delete a runtime backup (cluster admin)** ```ts async function deleteRuntimeBackupAsClusterAdminExample() { const camunda = createCamundaClient(); // Deletion fans out to every physical tenant (or a single one when // `physicalTenantId` is given) and is not undone if a later tenant fails. await camunda.deleteRuntimeBackupAsClusterAdmin({ backupId: 100 }); } ``` #### Operation Id deleteRuntimeBackupAsClusterAdmin #### Tags Backup --- ### deleteRuntimeBackupState() ```ts deleteRuntimeBackupState(options?): CancelablePromise; ``` Delete runtime backup state Resets the runtime backup state of every partition of the physical tenant, clearing all checkpoint info, backup info, checkpoint metadata, and backup ranges. Used when switching backup stores. - #### Parameters ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Delete the runtime backup state** ```ts async function deleteRuntimeBackupStateExample() { const camunda = createCamundaClient(); // Clears all checkpoint info, backup info, checkpoint metadata, and backup // ranges on every partition. Used when switching backup stores. await camunda.deleteRuntimeBackupState(); } ``` #### Operation Id deleteRuntimeBackupState #### Tags Backup --- ### deleteRuntimeBackupStateAsClusterAdmin() ```ts deleteRuntimeBackupStateAsClusterAdmin(input, options?): CancelablePromise; ``` Delete runtime backup state across physical tenants Resets the runtime backup state of every partition of every physical tenant of the cluster, or of the one named by `physicalTenantId`, clearing all checkpoint info, backup info, checkpoint metadata, and backup ranges. Used when switching backup stores. The request is all-or-nothing: a physical tenant whose state cannot be reset fails the whole request, and the resets that already succeeded on other tenants are not undone. Narrow the request with `physicalTenantId` to reset the tenants that can still be reached. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Use `DELETE /v2/backups/runtime/state` to act as a single physical tenant. * #### Parameters ##### input [`deleteRuntimeBackupStateAsClusterAdminInput`](../type-aliases/deleteRuntimeBackupStateAsClusterAdminInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Delete the runtime backup state (cluster admin)** ```ts async function deleteRuntimeBackupStateAsClusterAdminExample() { const camunda = createCamundaClient(); // Clears all checkpoint info, backup info, checkpoint metadata, and backup // ranges on every partition of every targeted physical tenant (or a single one // when `physicalTenantId` is given). Used when switching backup stores. await camunda.deleteRuntimeBackupStateAsClusterAdmin({}); } ``` #### Operation Id deleteRuntimeBackupStateAsClusterAdmin #### Tags Backup --- ### deleteTenant() ```ts deleteTenant(input, options?): CancelablePromise; ``` Delete tenant Deletes an existing tenant. * #### Parameters ##### input [`deleteTenantInput`](../type-aliases/deleteTenantInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Delete a tenant** ```ts async function deleteTenantExample(tenantId: TenantId) { const camunda = createCamundaClient(); await camunda.deleteTenant({ tenantId }); } ``` #### Operation Id deleteTenant #### Tags Tenant --- ### deleteTenantClusterVariable() ```ts deleteTenantClusterVariable(input, options?): CancelablePromise; ``` Delete a tenant-scoped cluster variable Delete a tenant-scoped cluster variable. * #### Parameters ##### input [`deleteTenantClusterVariableInput`](../type-aliases/deleteTenantClusterVariableInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Delete a tenant cluster variable** ```ts async function deleteTenantClusterVariableExample( tenantId: TenantId, name: ClusterVariableName ) { const camunda = createCamundaClient(); await camunda.deleteTenantClusterVariable({ tenantId, name, }); } ``` #### Operation Id deleteTenantClusterVariable #### Tags Cluster Variable --- ### deleteUser() ```ts deleteUser(input, options?): CancelablePromise; ``` Delete user Deletes a user. * #### Parameters ##### input [`deleteUserInput`](../type-aliases/deleteUserInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Delete a user** ```ts async function deleteUserExample(username: Username) { const camunda = createCamundaClient(); await camunda.deleteUser({ username }); } ``` #### Operation Id deleteUser #### Tags User --- ### deployResourcesFromFiles() ```ts deployResourcesFromFiles(resourceFilenames, options?): CancelablePromise; ``` Node-only convenience: deploy resources from local filesystem paths. #### Parameters ##### resourceFilenames `string`[] Absolute or relative file paths to BPMN/DMN/form/resource files. ##### options? Optional: tenantId. ###### tenantId? `string` #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ExtendedDeploymentResult`](../interfaces/ExtendedDeploymentResult.md)\> ExtendedDeploymentResult --- ### emitSupportLogPreamble() ```ts emitSupportLogPreamble(): void; ``` Emit the standard support log preamble & redacted configuration to the current support logger. Safe to call multiple times; subsequent calls are ignored (idempotent). Useful when a custom supportLogger was injected and you still want the canonical header & config dump. #### Returns `void` #### Inherited from ```ts CamundaCore.emitSupportLogPreamble; ``` --- ### evaluateConditionals() ```ts evaluateConditionals(input, options?): CancelablePromise; ``` 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. - #### Parameters ##### input [`ConditionalEvaluationInstruction`](../type-aliases/ConditionalEvaluationInstruction.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`EvaluateConditionalResult`](../type-aliases/EvaluateConditionalResult.md)\> #### Example **Evaluate conditionals** ```ts async function evaluateConditionalsExample(tenantId: TenantId) { const camunda = createCamundaClient(); const result = await camunda.evaluateConditionals({ variables: { orderReady: true }, tenantId, }); console.log(`Evaluated conditionals: ${JSON.stringify(result)}`); } ``` #### Operation Id evaluateConditionals #### Tags Conditional --- ### evaluateDecision() ```ts evaluateDecision(input, options?): CancelablePromise; ``` Evaluate decision Evaluates a decision. You specify the decision to evaluate either by using its unique key (as returned by DeployResource), or using the decision ID. When using the decision ID, the latest deployed version of the decision is used. - #### Parameters ##### input \| [`DecisionEvaluationById`](../type-aliases/DecisionEvaluationById.md) \| [`DecisionEvaluationByKey`](../type-aliases/DecisionEvaluationByKey.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`EvaluateDecisionResult`](../type-aliases/EvaluateDecisionResult.md)\> #### Examples **By ID** ```ts async function evaluateDecisionByIdExample( decisionDefinitionId: DecisionDefinitionId ) { const camunda = createCamundaClient(); const result = await camunda.evaluateDecision({ decisionDefinitionId, variables: { amount: 1000, invoiceCategory: "Misc", }, }); console.log(`Decision: ${result.decisionDefinitionId}`); console.log(`Output: ${result.output}`); } ``` **By key** ```ts async function evaluateDecisionByKeyExample( decisionDefinitionKey: DecisionDefinitionKey ) { const camunda = createCamundaClient(); const result = await camunda.evaluateDecision({ decisionDefinitionKey, variables: { amount: 1000, invoiceCategory: "Misc", }, }); console.log(`Decision output: ${result.output}`); } ``` #### Operation Id evaluateDecision #### Tags Decision definition --- ### evaluateExpression() ```ts evaluateExpression(input, options?): CancelablePromise; ``` Evaluate an expression Evaluates a FEEL expression and returns the result. Supports references to tenant scoped cluster variables when a tenant ID is provided. Optionally, provide a `scopeKey` to make the variables of a specific process instance or element instance visible while evaluating the expression. - #### Parameters ##### input [`ExpressionEvaluationRequest`](../type-aliases/ExpressionEvaluationRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ExpressionEvaluationResult`](../type-aliases/ExpressionEvaluationResult.md)\> #### Example **Evaluate an expression** ```ts async function evaluateExpressionExample() { const camunda = createCamundaClient(); const result = await camunda.evaluateExpression({ expression: "= x + y", variables: { x: 10, y: 20 }, }); console.log(`Result: ${result.result}`); } ``` #### Operation Id evaluateExpression #### Tags Expression --- ### failJob() ```ts failJob(input, options?): CancelablePromise; ``` Fail job Mark the job as failed. - #### Parameters ##### input [`failJobInput`](../type-aliases/failJobInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Fail a job with retry** ```ts async function failJobExample(jobKey: JobKey) { const camunda = createCamundaClient(); await camunda.failJob({ jobKey, retries: 2, errorMessage: "Payment gateway timeout", retryBackOff: 5000, }); } ``` #### Operation Id failJob #### Tags Job --- ### forceAuthRefresh() ```ts forceAuthRefresh(): Promise; ``` #### Returns `Promise`\<`string` \| `undefined`\> #### Inherited from ```ts CamundaCore.forceAuthRefresh; ``` --- ### getAgentDefinition() ```ts getAgentDefinition( input, consistencyManagement, options? ): CancelablePromise; ``` Get agent definition Returns an agent definition by key. * #### Parameters ##### input [`getAgentDefinitionInput`](../type-aliases/getAgentDefinitionInput.md) ##### consistencyManagement [`getAgentDefinitionConsistency`](../type-aliases/getAgentDefinitionConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`AgentDefinitionResult`](../type-aliases/AgentDefinitionResult.md)\> #### Example **Get an agent definition** ```ts async function getAgentDefinitionExample( agentDefinitionKey: AgentDefinitionKey ) { const camunda = createCamundaClient(); const definition = await camunda.getAgentDefinition( { agentDefinitionKey }, { consistency: { waitUpToMs: 5000 } } ); console.log(`Name: ${definition.name}`); console.log(`Type: ${definition.agentType}`); console.log(`Element: ${definition.elementId}`); } ``` #### Operation Id getAgentDefinition #### Tags Agent definition #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getAgentInstance() ```ts getAgentInstance( input, consistencyManagement, options? ): CancelablePromise; ``` Get agent instance Returns agent instance as JSON. * #### Parameters ##### input [`getAgentInstanceInput`](../type-aliases/getAgentInstanceInput.md) ##### consistencyManagement [`getAgentInstanceConsistency`](../type-aliases/getAgentInstanceConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`AgentInstanceResult`](../type-aliases/AgentInstanceResult.md)\> #### Example **Get an agent instance** ```ts async function getAgentInstanceExample(agentInstanceKey: AgentInstanceKey) { const camunda = createCamundaClient(); const instance = await camunda.getAgentInstance( { agentInstanceKey }, { consistency: { waitUpToMs: 5000 } } ); console.log(`Status: ${instance.status}`); console.log(`Element: ${instance.elementId}`); } ``` #### Operation Id getAgentInstance #### Tags Agent instance #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getAuditLog() ```ts getAuditLog( input, consistencyManagement, options? ): CancelablePromise; ``` Get audit log Get an audit log entry by auditLogKey. * #### Parameters ##### input [`getAuditLogInput`](../type-aliases/getAuditLogInput.md) ##### consistencyManagement [`getAuditLogConsistency`](../type-aliases/getAuditLogConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`AuditLogResult`](../type-aliases/AuditLogResult.md)\> #### Example **Get an audit log entry** ```ts async function getAuditLogExample(auditLogKey: AuditLogKey) { const camunda = createCamundaClient(); const log = await camunda.getAuditLog( { auditLogKey }, { consistency: { waitUpToMs: 5000 } } ); console.log(`Audit log: ${log.operationType}`); } ``` #### Operation Id getAuditLog #### Tags Audit Log #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getAuthentication() ```ts getAuthentication(options?): CancelablePromise; ``` Get current user Retrieves the current authenticated user. * #### Parameters ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`CamundaUserResult`](../type-aliases/CamundaUserResult.md)\> #### Example **Get authentication info** ```ts async function getAuthenticationExample() { const camunda = createCamundaClient(); const user = await camunda.getAuthentication(); console.log(`Authenticated as: ${user.username}`); } ``` #### Operation Id getAuthentication #### Tags Authentication --- ### getAuthHeaders() ```ts getAuthHeaders(): Promise>; ``` #### Returns `Promise`\<`Record`\<`string`, `string`\>\> #### Inherited from ```ts CamundaCore.getAuthHeaders; ``` --- ### getAuthorization() ```ts getAuthorization( input, consistencyManagement, options? ): CancelablePromise; ``` Get authorization Get authorization by the given key. * #### Parameters ##### input [`getAuthorizationInput`](../type-aliases/getAuthorizationInput.md) ##### consistencyManagement [`getAuthorizationConsistency`](../type-aliases/getAuthorizationConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`AuthorizationResult`](../type-aliases/AuthorizationResult.md)\> #### Example **Get an authorization** ```ts async function getAuthorizationExample(authorizationKey: AuthorizationKey) { const camunda = createCamundaClient(); const authorization = await camunda.getAuthorization( { authorizationKey }, { consistency: { waitUpToMs: 5000 } } ); console.log(`Owner: ${authorization.ownerId} (${authorization.ownerType})`); } ``` #### Operation Id getAuthorization #### Tags Authorization #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getBackpressureState() ```ts getBackpressureState(): | { backoffMs: number; consecutive: number; permitsCurrent: number; permitsMax: number | null; severity: BackpressureSeverity; waiters: number; } | { consecutive: number; permitsCurrent: number; permitsMax: null; severity: string; waiters: number; }; ``` Public accessor for current backpressure adaptive limiter state (stable) #### Returns \| \{ `backoffMs`: `number`; `consecutive`: `number`; `permitsCurrent`: `number`; `permitsMax`: `number` \| `null`; `severity`: [`BackpressureSeverity`](../type-aliases/BackpressureSeverity.md); `waiters`: `number`; \} \| \{ `consecutive`: `number`; `permitsCurrent`: `number`; `permitsMax`: `null`; `severity`: `string`; `waiters`: `number`; \} #### Inherited from ```ts CamundaCore.getBackpressureState; ``` --- ### getBatchOperation() ```ts getBatchOperation( input, consistencyManagement, options? ): CancelablePromise; ``` Get batch operation Get batch operation by key. * #### Parameters ##### input [`getBatchOperationInput`](../type-aliases/getBatchOperationInput.md) ##### consistencyManagement [`getBatchOperationConsistency`](../type-aliases/getBatchOperationConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`BatchOperationResponse`](../type-aliases/BatchOperationResponse.md)\> #### Example **Get a batch operation** ```ts async function getBatchOperationExample(batchOperationKey: BatchOperationKey) { const camunda = createCamundaClient(); const batch = await camunda.getBatchOperation( { batchOperationKey }, { consistency: { waitUpToMs: 5000 } } ); console.log(`Batch: ${batch.batchOperationType} (${batch.state})`); } ``` #### Operation Id getBatchOperation #### Tags Batch operation #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getClusterExportingStatus() ```ts getClusterExportingStatus(options?): CancelablePromise; ``` Get exporting status of the whole cluster Returns the exporting status of the whole cluster, folded over the exporting status of every physical tenant. Only `PAUSED` and `SOFT_PAUSED` confirm that exporting is paused cluster-wide; every other value means at least one physical tenant is not paused, so callers should keep polling. A physical tenant that itself reports `MIXED` makes the whole cluster `MIXED`. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. * #### Parameters ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ExportingStatusResponse`](../type-aliases/ExportingStatusResponse.md)\> #### Example **Get cluster exporting status** ```ts async function getClusterExportingStatusExample() { const camunda = createCamundaClient(); // Reports the aggregated exporting status of the whole cluster — useful to // confirm exporting has paused everywhere before taking a cluster-wide backup. const { status } = await camunda.getClusterExportingStatus(); console.log(`Cluster exporting status: ${status}`); } ``` #### Operation Id getClusterExportingStatus #### Tags Exporting --- ### getClusterRebalance() ```ts getClusterRebalance(options?): CancelablePromise; ``` Report the cluster's current leadership balance Reports whether the cluster is currently balanced, the current leadership state of every partition, and what became of the last rebalance to finish. The last completed rebalance is held in memory by the coordinating broker, so none will be reported if the coordinator has moved or restarted since the last rebalance. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. * #### Parameters ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterBalanceResponse`](../type-aliases/ClusterBalanceResponse.md)\> #### Example **Get cluster rebalance status** ```ts async function getClusterRebalanceExample() { const camunda = createCamundaClient(); const balance = await camunda.getClusterRebalance(); console.log(`Cluster balance state: ${balance.state}`); for (const partition of balance.partitions) { console.log( ` Partition ${partition.partitionId}: state=${partition.state}, currentLeader=${partition.currentLeader}, desiredLeader=${partition.desiredLeader}` ); } if (balance.runningRebalance) { console.log(`Running rebalance id=${balance.runningRebalance.rebalanceId}`); } } ``` #### Operation Id getClusterRebalance #### Tags Cluster --- ### getClusterStatus() ```ts getClusterStatus(options?): CancelablePromise; ``` Get the status of the whole cluster Checks the health status of the whole cluster, aggregated over all physical tenants. Returns `HEALTHY` when every physical tenant is healthy, `DOWN` when no physical tenant can process work, and `DEGRADED` in every other case. No per-tenant detail is reported; use `GET /cluster/v2/topology` for that. This endpoint is public and requires no authentication, unlike `PATCH /cluster/v2/mode` below, which needs cluster-admin credentials. * #### Parameters ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterStatusResponse`](../type-aliases/ClusterStatusResponse.md)\> #### Example **Get cluster status** ```ts async function getClusterStatusExample() { const camunda = createCamundaClient(); const status = await camunda.getClusterStatus(); console.log(`Cluster status: ${status.status}`); } ``` #### Operation Id getClusterStatus #### Tags Cluster --- ### getClusterTopology() ```ts getClusterTopology(options?): CancelablePromise; ``` Get the topology of the whole cluster Obtains the topology of the whole cluster, aggregated over all physical tenants. Cluster-level information is reported once; partition layout, replication and per-partition role, health and state are reported per physical tenant. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Use `GET /v2/topology` for the topology of a single physical tenant. * #### Parameters ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterTopologyResponse`](../type-aliases/ClusterTopologyResponse.md)\> #### Example **Get cluster topology (v2)** ```ts async function getClusterTopologyExample() { const camunda = createCamundaClient(); // Returns the full cluster topology: brokers, physical tenants (in a // multi-tenant cluster), cluster size, and gateway version. const topology = await camunda.getClusterTopology(); console.log( `Cluster ${topology.clusterId} — ${topology.clusterSize} broker(s), gateway ${topology.gatewayVersion}` ); for (const broker of topology.brokers) { console.log( ` Broker ${broker.brokerId}: ${broker.host}:${broker.port} (${broker.version})` ); } for (const tenant of topology.physicalTenants) { console.log( ` Physical tenant ${tenant.physicalTenantId}: ${tenant.partitionsCount} partition(s), replication ${tenant.replicationFactor}` ); } } ``` #### Operation Id getClusterTopology #### Tags Cluster --- ### getClusterUpgradeStatus() ```ts getClusterUpgradeStatus(options?): CancelablePromise; ``` Get the upgrade-readiness status of the whole cluster Reports one overall upgrade-readiness status for the whole cluster, folded over every physical tenant and condition. `MIGRATED` only once every known condition has migrated for every known physical tenant; `MIGRATION_IN_PROGRESS` when at least one is confirmed not yet migrated; `UNKNOWN` otherwise (including before anything has been reported yet). No per-tenant or per-condition detail is reported here; see the `upgradeReadiness` actuator endpoint for that. * #### Parameters ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterUpgradeStatusResponse`](../type-aliases/ClusterUpgradeStatusResponse.md)\> #### Example **Get cluster upgrade-readiness status** ```ts async function getClusterUpgradeStatusExample() { const camunda = createCamundaClient(); const upgradeStatus = await camunda.getClusterUpgradeStatus(); console.log(`Cluster upgrade-readiness status: ${upgradeStatus.status}`); } ``` #### Operation Id getClusterUpgradeStatus #### Tags Cluster --- ### getConfig() ```ts getConfig(): Readonly; ``` Read-only snapshot of current hydrated configuration (do not mutate directly). Use configure(...) to apply changes. #### Returns `Readonly`\<[`CamundaConfig`](../interfaces/CamundaConfig.md)\> #### Inherited from ```ts CamundaCore.getConfig; ``` --- ### getDecisionDefinition() ```ts getDecisionDefinition( input, consistencyManagement, options? ): CancelablePromise; ``` Get decision definition Returns a decision definition by key. * #### Parameters ##### input [`getDecisionDefinitionInput`](../type-aliases/getDecisionDefinitionInput.md) ##### consistencyManagement [`getDecisionDefinitionConsistency`](../type-aliases/getDecisionDefinitionConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`DecisionDefinitionResult`](../type-aliases/DecisionDefinitionResult.md)\> #### Example **Get a decision definition** ```ts async function getDecisionDefinitionExample( decisionDefinitionKey: DecisionDefinitionKey ) { const camunda = createCamundaClient(); const definition = await camunda.getDecisionDefinition( { decisionDefinitionKey }, { consistency: { waitUpToMs: 5000 } } ); console.log(`Decision: ${definition.decisionDefinitionId}`); console.log(`Version: ${definition.version}`); } ``` #### Operation Id getDecisionDefinition #### Tags Decision definition #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getDecisionDefinitionXml() ```ts getDecisionDefinitionXml( input, consistencyManagement, options? ): CancelablePromise; ``` Get decision definition XML Returns decision definition as XML. * #### Parameters ##### input [`getDecisionDefinitionXmlInput`](../type-aliases/getDecisionDefinitionXmlInput.md) ##### consistencyManagement [`getDecisionDefinitionXmlConsistency`](../type-aliases/getDecisionDefinitionXmlConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`string`\> #### Example **Get decision definition XML** ```ts async function getDecisionDefinitionXmlExample( decisionDefinitionKey: DecisionDefinitionKey ) { const camunda = createCamundaClient(); const xml = await camunda.getDecisionDefinitionXml( { decisionDefinitionKey }, { consistency: { waitUpToMs: 5000 } } ); console.log(`XML length: ${JSON.stringify(xml).length}`); } ``` #### Operation Id getDecisionDefinitionXML #### Tags Decision definition #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getDecisionInstance() ```ts getDecisionInstance( input, consistencyManagement, options? ): CancelablePromise; ``` Get decision instance Returns a decision instance. * #### Parameters ##### input [`getDecisionInstanceInput`](../type-aliases/getDecisionInstanceInput.md) ##### consistencyManagement [`getDecisionInstanceConsistency`](../type-aliases/getDecisionInstanceConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`DecisionInstanceGetQueryResult`](../type-aliases/DecisionInstanceGetQueryResult.md)\> #### Example **Get a decision instance** ```ts async function getDecisionInstanceExample( decisionEvaluationInstanceKey: DecisionEvaluationInstanceKey ) { const camunda = createCamundaClient(); const instance = await camunda.getDecisionInstance( { decisionEvaluationInstanceKey }, { consistency: { waitUpToMs: 5000 } } ); console.log(`Decision: ${instance.decisionDefinitionId}`); } ``` #### Operation Id getDecisionInstance #### Tags Decision instance #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getDecisionRequirements() ```ts getDecisionRequirements( input, consistencyManagement, options? ): CancelablePromise; ``` Get decision requirements Returns Decision Requirements as JSON. * #### Parameters ##### input [`getDecisionRequirementsInput`](../type-aliases/getDecisionRequirementsInput.md) ##### consistencyManagement [`getDecisionRequirementsConsistency`](../type-aliases/getDecisionRequirementsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`DecisionRequirementsResult`](../type-aliases/DecisionRequirementsResult.md)\> #### Example **Get decision requirements** ```ts async function getDecisionRequirementsExample( decisionRequirementsKey: DecisionRequirementsKey ) { const camunda = createCamundaClient(); const requirements = await camunda.getDecisionRequirements( { decisionRequirementsKey }, { consistency: { waitUpToMs: 5000 } } ); console.log(`Requirements: ${requirements.decisionRequirementsId}`); } ``` #### Operation Id getDecisionRequirements #### Tags Decision requirements #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getDecisionRequirementsXml() ```ts getDecisionRequirementsXml( input, consistencyManagement, options? ): CancelablePromise; ``` Get decision requirements XML Returns decision requirements as XML. * #### Parameters ##### input [`getDecisionRequirementsXmlInput`](../type-aliases/getDecisionRequirementsXmlInput.md) ##### consistencyManagement [`getDecisionRequirementsXmlConsistency`](../type-aliases/getDecisionRequirementsXmlConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`string`\> #### Example **Get decision requirements XML** ```ts async function getDecisionRequirementsXmlExample( decisionRequirementsKey: DecisionRequirementsKey ) { const camunda = createCamundaClient(); const xml = await camunda.getDecisionRequirementsXml( { decisionRequirementsKey }, { consistency: { waitUpToMs: 5000 } } ); console.log(`XML length: ${JSON.stringify(xml).length}`); } ``` #### Operation Id getDecisionRequirementsXML #### Tags Decision requirements #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getDocument() ```ts getDocument(input, options?): CancelablePromise; ``` Download document Download a document from the Camunda 8 cluster. Note that this is currently supported for document stores of type: AWS, Azure, GCP, in-memory (non-production), local (non-production) - #### Parameters ##### input [`getDocumentInput`](../type-aliases/getDocumentInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`Blob`\> #### Example **Download a document** ```ts async function getDocumentExample(documentId: DocumentId) { const camunda = createCamundaClient(); await camunda.getDocument({ documentId }); console.log(`Downloaded document: ${documentId}`); } ``` #### Operation Id getDocument #### Tags Document --- ### getElementInstance() ```ts getElementInstance( input, consistencyManagement, options? ): CancelablePromise; ``` Get element instance Returns element instance as JSON. * #### Parameters ##### input [`getElementInstanceInput`](../type-aliases/getElementInstanceInput.md) ##### consistencyManagement [`getElementInstanceConsistency`](../type-aliases/getElementInstanceConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ElementInstanceResult`](../type-aliases/ElementInstanceResult.md)\> #### Example **Get an element instance** ```ts async function getElementInstanceExample( elementInstanceKey: ElementInstanceKey ) { const camunda = createCamundaClient(); const element = await camunda.getElementInstance( { elementInstanceKey }, { consistency: { waitUpToMs: 5000 } } ); console.log(`Element: ${element.elementId} (${element.type})`); } ``` #### Operation Id getElementInstance #### Tags Element instance #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getErrorMode() ```ts getErrorMode(): "throw" | "result"; ``` Internal accessor (read-only) for eventual consistency error mode. #### Returns `"throw"` \| `"result"` #### Inherited from ```ts CamundaCore.getErrorMode; ``` --- ### getExportingStatus() ```ts getExportingStatus(options?): CancelablePromise; ``` Get exporting status Returns the exporting status of the physical tenant, aggregated over every replica of every one of its partitions. Because pause and resume are applied to all replicas, the status is only a single phase if every replica reports that phase; otherwise it is `MIXED`, which means a pause or resume is still in flight or was only partially applied. Backup tooling should treat only `PAUSED` and `SOFT_PAUSED` as confirmation that exporting is paused. - #### Parameters ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ExportingStatusResponse`](../type-aliases/ExportingStatusResponse.md)\> #### Example **Get exporting status** ```ts async function getExportingStatusExample() { const camunda = createCamundaClient(); // Reports the aggregated exporting status of the physical tenant — useful to // confirm exporting has actually paused before taking a backup, and that it // has resumed afterwards. const { status } = await camunda.getExportingStatus(); console.log(`Exporting status: ${status}`); } ``` #### Operation Id getExportingStatus #### Tags Exporting --- ### getFormByKey() ```ts getFormByKey( input, consistencyManagement, options? ): CancelablePromise; ``` Get form by key Get a form by its unique form key. - #### Parameters ##### input [`getFormByKeyInput`](../type-aliases/getFormByKeyInput.md) ##### consistencyManagement [`getFormByKeyConsistency`](../type-aliases/getFormByKeyConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`FormResult`](../type-aliases/FormResult.md)\> #### Example **Get a form by key** ```ts async function getFormByKeyExample(formKey: FormKey) { const camunda = createCamundaClient(); const form = await camunda.getFormByKey( { formKey, }, { consistency: { waitUpToMs: 5000 } } ); console.log(`Form: ${form.formId}, version: ${form.version}`); } ``` #### Operation Id getFormByKey #### Tags Form #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getGlobalClusterVariable() ```ts getGlobalClusterVariable( input, consistencyManagement, options? ): CancelablePromise; ``` Get a global-scoped cluster variable Get a global-scoped cluster variable. * #### Parameters ##### input [`getGlobalClusterVariableInput`](../type-aliases/getGlobalClusterVariableInput.md) ##### consistencyManagement [`getGlobalClusterVariableConsistency`](../type-aliases/getGlobalClusterVariableConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterVariableResult`](../type-aliases/ClusterVariableResult.md)\> #### Example **Get a global cluster variable** ```ts async function getGlobalClusterVariableExample(name: ClusterVariableName) { const camunda = createCamundaClient(); const variable = await camunda.getGlobalClusterVariable( { name }, { consistency: { waitUpToMs: 5000 } } ); console.log(`${variable.name} = ${variable.value}`); } ``` #### Operation Id getGlobalClusterVariable #### Tags Cluster Variable #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getGlobalJobStatistics() ```ts getGlobalJobStatistics( input, consistencyManagement, options? ): CancelablePromise; ``` Global job statistics Returns global aggregated counts for jobs. Filter by the creation time window (required) and optionally by jobType. - #### Parameters ##### input [`getGlobalJobStatisticsInput`](../type-aliases/getGlobalJobStatisticsInput.md) ##### consistencyManagement [`getGlobalJobStatisticsConsistency`](../type-aliases/getGlobalJobStatisticsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`GlobalJobStatisticsQueryResult`](../type-aliases/GlobalJobStatisticsQueryResult.md)\> #### Example **Get global job statistics** ```ts async function getGlobalJobStatisticsExample() { const camunda = createCamundaClient(); const result = await camunda.getGlobalJobStatistics( { from: "2025-01-01T00:00:00Z", to: "2025-12-31T23:59:59Z", }, { consistency: { waitUpToMs: 5000 } } ); console.log(`Statistics retrieved: ${JSON.stringify(result)}`); } ``` #### Operation Id getGlobalJobStatistics #### Tags Job #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getGlobalTaskListener() ```ts getGlobalTaskListener( input, consistencyManagement, options? ): CancelablePromise; ``` Get global user task listener Get a global user task listener by its id. * #### Parameters ##### input [`getGlobalTaskListenerInput`](../type-aliases/getGlobalTaskListenerInput.md) ##### consistencyManagement [`getGlobalTaskListenerConsistency`](../type-aliases/getGlobalTaskListenerConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`GlobalTaskListenerResult`](../type-aliases/GlobalTaskListenerResult.md)\> #### Example **Get a global task listener** ```ts async function getGlobalTaskListenerExample(id: GlobalListenerId) { const camunda = createCamundaClient(); const listener = await camunda.getGlobalTaskListener( { id }, { consistency: { waitUpToMs: 5000 } } ); console.log(`Listener: ${listener.type} (${listener.eventTypes})`); } ``` #### Operation Id getGlobalTaskListener #### Tags Global listener #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getGroup() ```ts getGroup( input, consistencyManagement, options? ): CancelablePromise; ``` Get group Get a group by its ID. * #### Parameters ##### input [`getGroupInput`](../type-aliases/getGroupInput.md) ##### consistencyManagement [`getGroupConsistency`](../type-aliases/getGroupConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`GroupResult`](../type-aliases/GroupResult.md)\> #### Example **Get a group** ```ts async function getGroupExample(groupId: GroupId) { const camunda = createCamundaClient(); const group = await camunda.getGroup( { groupId }, { consistency: { waitUpToMs: 5000 } } ); console.log(`Group: ${group.name}`); } ``` #### Operation Id getGroup #### Tags Group #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getHistoryBackup() ```ts getHistoryBackup(input, options?): CancelablePromise; ``` Get history backup Returns detailed status of the history backup with the given id. Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. - #### Parameters ##### input [`getHistoryBackupInput`](../type-aliases/getHistoryBackupInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`HistoryBackupInfo`](../type-aliases/HistoryBackupInfo.md)\> #### Example **Get a history backup** ```ts async function getHistoryBackupExample() { const camunda = createCamundaClient(); const backup = await camunda.getHistoryBackup({ backupId: 100 }); // The aggregated state is derived from the state of every expected snapshot. console.log(`History backup ${backup.backupId}: ${backup.state}`); } ``` #### Operation Id getHistoryBackup #### Tags Backup --- ### getHistoryBackupAsClusterAdmin() ```ts getHistoryBackupAsClusterAdmin(input, options?): CancelablePromise; ``` Get a history backup across physical tenants Reports what every physical tenant of the cluster, or the one named by `physicalTenantId`, holds for the given backup id. There is no aggregated cluster-level state: a tenant that was reached and does not hold this backup reports `NOT_FOUND`, which is a successful observation rather than a failure. The request is all-or-nothing: a physical tenant whose state cannot be read fails the whole request. Narrow the request with `physicalTenantId` to read the tenants that can still be reached. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. Use `GET /v2/backups/history/{backupId}` to act as a single physical tenant. * #### Parameters ##### input [`getHistoryBackupAsClusterAdminInput`](../type-aliases/getHistoryBackupAsClusterAdminInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterHistoryBackupInfo`](../type-aliases/ClusterHistoryBackupInfo.md)\> #### Example **Get a history backup (cluster admin)** ```ts async function getHistoryBackupAsClusterAdminExample() { const camunda = createCamundaClient(); // Looking a backup id up directly lists every targeted physical tenant, // including the ones reporting `NOT_FOUND` — a backup that only some tenants // hold is a supported outcome. const backup = await camunda.getHistoryBackupAsClusterAdmin({ backupId: 100, }); console.log(`Cluster history backup ${backup.backupId}:`); for (const tenant of backup.physicalTenants) { console.log(` [${tenant.physicalTenantId}] ${tenant.state}`); } } ``` #### Operation Id getHistoryBackupAsClusterAdmin #### Tags Backup --- ### getIncident() ```ts getIncident( input, consistencyManagement, options? ): CancelablePromise; ``` Get incident Returns incident as JSON. - #### Parameters ##### input [`getIncidentInput`](../type-aliases/getIncidentInput.md) ##### consistencyManagement [`getIncidentConsistency`](../type-aliases/getIncidentConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`IncidentResult`](../type-aliases/IncidentResult.md)\> #### Example **Get an incident** ```ts async function getIncidentExample(incidentKey: IncidentKey) { const camunda = createCamundaClient(); const incident = await camunda.getIncident( { incidentKey }, { consistency: { waitUpToMs: 5000 } } ); console.log(`Type: ${incident.errorType}`); console.log(`State: ${incident.state}`); console.log(`Message: ${incident.errorMessage}`); } ``` #### Operation Id getIncident #### Tags Incident #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getJobErrorStatistics() ```ts getJobErrorStatistics( input, consistencyManagement, options? ): CancelablePromise; ``` Get error metrics for a job type Returns aggregated metrics per error for the given jobType. - #### Parameters ##### input [`JobErrorStatisticsQuery`](../type-aliases/JobErrorStatisticsQuery.md) ##### consistencyManagement [`getJobErrorStatisticsConsistency`](../type-aliases/getJobErrorStatisticsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`JobErrorStatisticsQueryResult`](../type-aliases/JobErrorStatisticsQueryResult.md)\> #### Example **Get job error statistics** ```ts async function getJobErrorStatisticsExample() { const camunda = createCamundaClient(); const result = await camunda.getJobErrorStatistics( { filter: { from: "2025-01-01T00:00:00Z", to: "2025-12-31T23:59:59Z", jobType: "payment-processing", }, }, { consistency: { waitUpToMs: 5000 } } ); for (const stat of result.items ?? []) { console.log(`Error: ${stat.errorMessage}, workers: ${stat.workers}`); } } ``` #### Operation Id getJobErrorStatistics #### Tags Job #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getJobTimeSeriesStatistics() ```ts getJobTimeSeriesStatistics( input, consistencyManagement, options? ): CancelablePromise; ``` Get time-series metrics for a job type Returns a list of time-bucketed metrics ordered ascending by time. The `from` and `to` fields select the time window of interest. Each item in the response corresponds to one time bucket of the requested resolution. - #### Parameters ##### input [`JobTimeSeriesStatisticsQuery`](../type-aliases/JobTimeSeriesStatisticsQuery.md) ##### consistencyManagement [`getJobTimeSeriesStatisticsConsistency`](../type-aliases/getJobTimeSeriesStatisticsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`JobTimeSeriesStatisticsQueryResult`](../type-aliases/JobTimeSeriesStatisticsQueryResult.md)\> #### Example **Get job time series statistics** ```ts async function getJobTimeSeriesStatisticsExample() { const camunda = createCamundaClient(); const result = await camunda.getJobTimeSeriesStatistics( { filter: { from: "2025-01-01T00:00:00Z", to: "2025-12-31T23:59:59Z", jobType: "payment-processing", }, }, { consistency: { waitUpToMs: 5000 } } ); for (const point of result.items ?? []) { console.log(`Time: ${point.time}, created: ${point.created.count}`); } } ``` #### Operation Id getJobTimeSeriesStatistics #### Tags Job #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getJobTypeStatistics() ```ts getJobTypeStatistics( input, consistencyManagement, options? ): CancelablePromise; ``` Get job statistics by type Get statistics about jobs, grouped by job type. - #### Parameters ##### input [`JobTypeStatisticsQuery`](../type-aliases/JobTypeStatisticsQuery.md) ##### consistencyManagement [`getJobTypeStatisticsConsistency`](../type-aliases/getJobTypeStatisticsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`JobTypeStatisticsQueryResult`](../type-aliases/JobTypeStatisticsQueryResult.md)\> #### Example **Get job type statistics** ```ts async function getJobTypeStatisticsExample() { const camunda = createCamundaClient(); const result = await camunda.getJobTypeStatistics( {}, { consistency: { waitUpToMs: 5000 } } ); for (const stat of result.items ?? []) { console.log(`Type: ${stat.jobType}, workers: ${stat.workers}`); } } ``` #### Operation Id getJobTypeStatistics #### Tags Job #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getJobWorkerStatistics() ```ts getJobWorkerStatistics( input, consistencyManagement, options? ): CancelablePromise; ``` Get job statistics by worker Get statistics about jobs, grouped by worker, for a given job type. - #### Parameters ##### input [`JobWorkerStatisticsQuery`](../type-aliases/JobWorkerStatisticsQuery.md) ##### consistencyManagement [`getJobWorkerStatisticsConsistency`](../type-aliases/getJobWorkerStatisticsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`JobWorkerStatisticsQueryResult`](../type-aliases/JobWorkerStatisticsQueryResult.md)\> #### Example **Get job worker statistics** ```ts async function getJobWorkerStatisticsExample() { const camunda = createCamundaClient(); const result = await camunda.getJobWorkerStatistics( { filter: { from: "2025-01-01T00:00:00Z", to: "2025-12-31T23:59:59Z", jobType: "payment-processing", }, }, { consistency: { waitUpToMs: 5000 } } ); for (const stat of result.items ?? []) { console.log(`Worker: ${stat.worker}, completed: ${stat.completed.count}`); } } ``` #### Operation Id getJobWorkerStatistics #### Tags Job #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getLicense() ```ts getLicense(options?): CancelablePromise; ``` Get license status Obtains the status of the current Camunda license. * #### Parameters ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`LicenseResponse`](../type-aliases/LicenseResponse.md)\> #### Example **Get license information** ```ts async function getLicenseExample() { const camunda = createCamundaClient(); const license = await camunda.getLicense(); console.log(`License type: ${license.validLicense}`); } ``` #### Operation Id getLicense #### Tags License --- ### getMappingRule() ```ts getMappingRule( input, consistencyManagement, options? ): CancelablePromise; ``` Get a mapping rule Gets the mapping rule with the given ID. - #### Parameters ##### input [`getMappingRuleInput`](../type-aliases/getMappingRuleInput.md) ##### consistencyManagement [`getMappingRuleConsistency`](../type-aliases/getMappingRuleConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`MappingRuleResult`](../type-aliases/MappingRuleResult.md)\> #### Example **Get a mapping rule** ```ts async function getMappingRuleExample(mappingRuleId: MappingRuleId) { const camunda = createCamundaClient(); const rule = await camunda.getMappingRule( { mappingRuleId }, { consistency: { waitUpToMs: 5000 } } ); console.log(`Rule: ${rule.name} (${rule.claimName}=${rule.claimValue})`); } ``` #### Operation Id getMappingRule #### Tags Mapping rule #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getProcessDefinition() ```ts getProcessDefinition( input, consistencyManagement, options? ): CancelablePromise; ``` Get process definition Returns process definition as JSON. * #### Parameters ##### input [`getProcessDefinitionInput`](../type-aliases/getProcessDefinitionInput.md) ##### consistencyManagement [`getProcessDefinitionConsistency`](../type-aliases/getProcessDefinitionConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ProcessDefinitionResult`](../type-aliases/ProcessDefinitionResult.md)\> #### Example **Get a process definition** ```ts async function getProcessDefinitionExample( processDefinitionKey: ProcessDefinitionKey ) { const camunda = createCamundaClient(); const definition = await camunda.getProcessDefinition( { processDefinitionKey }, { consistency: { waitUpToMs: 5000 } } ); console.log( `Process: ${definition.processDefinitionId} v${definition.version}` ); } ``` #### Operation Id getProcessDefinition #### Tags Process definition #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getProcessDefinitionInstanceStatistics() ```ts getProcessDefinitionInstanceStatistics( input, consistencyManagement, options? ): CancelablePromise; ``` Get process instance statistics Get statistics about process instances, grouped by process definition and tenant. - #### Parameters ##### input [`ProcessDefinitionInstanceStatisticsQuery`](../type-aliases/ProcessDefinitionInstanceStatisticsQuery.md) ##### consistencyManagement [`getProcessDefinitionInstanceStatisticsConsistency`](../type-aliases/getProcessDefinitionInstanceStatisticsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ProcessDefinitionInstanceStatisticsQueryResult`](../type-aliases/ProcessDefinitionInstanceStatisticsQueryResult.md)\> #### Example **Get process definition instance statistics** ```ts async function getProcessDefinitionInstanceStatisticsExample() { const camunda = createCamundaClient(); const result = await camunda.getProcessDefinitionInstanceStatistics( {}, { consistency: { waitUpToMs: 5000 } } ); for (const stat of result.items ?? []) { console.log( `Definition ${stat.processDefinitionId}: ${stat.activeInstancesWithoutIncidentCount} active` ); } } ``` #### Operation Id getProcessDefinitionInstanceStatistics #### Tags Process definition #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getProcessDefinitionInstanceVersionStatistics() ```ts getProcessDefinitionInstanceVersionStatistics( input, consistencyManagement, options? ): CancelablePromise; ``` Get process instance statistics by version Get statistics about process instances, grouped by version for a given process definition. The process definition ID must be provided as a required field in the request body filter. - #### Parameters ##### input [`ProcessDefinitionInstanceVersionStatisticsQuery`](../type-aliases/ProcessDefinitionInstanceVersionStatisticsQuery.md) ##### consistencyManagement [`getProcessDefinitionInstanceVersionStatisticsConsistency`](../type-aliases/getProcessDefinitionInstanceVersionStatisticsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ProcessDefinitionInstanceVersionStatisticsQueryResult`](../type-aliases/ProcessDefinitionInstanceVersionStatisticsQueryResult.md)\> #### Example **Get version statistics** ```ts async function getProcessDefinitionInstanceVersionStatisticsExample( processDefinitionId: ProcessDefinitionId ) { const camunda = createCamundaClient(); const result = await camunda.getProcessDefinitionInstanceVersionStatistics( { filter: { processDefinitionId, }, }, { consistency: { waitUpToMs: 5000 } } ); for (const stat of result.items ?? []) { console.log( `Version ${stat.processDefinitionVersion}: ${stat.activeInstancesWithoutIncidentCount} active` ); } } ``` #### Operation Id getProcessDefinitionInstanceVersionStatistics #### Tags Process definition #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getProcessDefinitionMessageSubscriptionStatistics() ```ts getProcessDefinitionMessageSubscriptionStatistics( input, consistencyManagement, options? ): CancelablePromise; ``` Get message subscription statistics Get message subscription statistics, grouped by process definition. - #### Parameters ##### input [`ProcessDefinitionMessageSubscriptionStatisticsQuery`](../type-aliases/ProcessDefinitionMessageSubscriptionStatisticsQuery.md) ##### consistencyManagement [`getProcessDefinitionMessageSubscriptionStatisticsConsistency`](../type-aliases/getProcessDefinitionMessageSubscriptionStatisticsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ProcessDefinitionMessageSubscriptionStatisticsQueryResult`](../type-aliases/ProcessDefinitionMessageSubscriptionStatisticsQueryResult.md)\> #### Example **Get message subscription statistics** ```ts async function getProcessDefinitionMessageSubscriptionStatisticsExample() { const camunda = createCamundaClient(); const result = await camunda.getProcessDefinitionMessageSubscriptionStatistics( {}, { consistency: { waitUpToMs: 5000 } } ); for (const stat of result.items ?? []) { console.log( `Definition ${stat.processDefinitionId}: ${stat.activeSubscriptions} subscriptions` ); } } ``` #### Operation Id getProcessDefinitionMessageSubscriptionStatistics #### Tags Process definition #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getProcessDefinitionStatistics() ```ts getProcessDefinitionStatistics( input, consistencyManagement, options? ): CancelablePromise; ``` Get process definition statistics Get statistics about elements in currently running process instances by process definition key and search filter. * #### Parameters ##### input [`getProcessDefinitionStatisticsInput`](../type-aliases/getProcessDefinitionStatisticsInput.md) ##### consistencyManagement [`getProcessDefinitionStatisticsConsistency`](../type-aliases/getProcessDefinitionStatisticsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ProcessDefinitionElementStatisticsQueryResult`](../type-aliases/ProcessDefinitionElementStatisticsQueryResult.md)\> #### Example **Get process definition element statistics** ```ts async function getProcessDefinitionStatisticsExample( processDefinitionKey: ProcessDefinitionKey ) { const camunda = createCamundaClient(); const result = await camunda.getProcessDefinitionStatistics( { processDefinitionKey }, { consistency: { waitUpToMs: 5000 } } ); for (const stat of result.items ?? []) { console.log(`Element ${stat.elementId}: active=${stat.active}`); } } ``` #### Operation Id getProcessDefinitionStatistics #### Tags Process definition #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getProcessDefinitionXml() ```ts getProcessDefinitionXml( input, consistencyManagement, options? ): CancelablePromise; ``` Get process definition XML Returns process definition as XML. * #### Parameters ##### input [`getProcessDefinitionXmlInput`](../type-aliases/getProcessDefinitionXmlInput.md) ##### consistencyManagement [`getProcessDefinitionXmlConsistency`](../type-aliases/getProcessDefinitionXmlConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`string`\> #### Example **Get process definition XML** ```ts async function getProcessDefinitionXmlExample( processDefinitionKey: ProcessDefinitionKey ) { const camunda = createCamundaClient(); const xml = await camunda.getProcessDefinitionXml( { processDefinitionKey }, { consistency: { waitUpToMs: 5000 } } ); console.log(`XML length: ${JSON.stringify(xml).length}`); } ``` #### Operation Id getProcessDefinitionXML #### Tags Process definition #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getProcessInstance() ```ts getProcessInstance( input, consistencyManagement, options? ): CancelablePromise; ``` Get process instance Get the process instance by the process instance key. * #### Parameters ##### input [`getProcessInstanceInput`](../type-aliases/getProcessInstanceInput.md) ##### consistencyManagement [`getProcessInstanceConsistency`](../type-aliases/getProcessInstanceConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ProcessInstanceResult`](../type-aliases/ProcessInstanceResult.md)\> #### Example **Get a process instance** ```ts async function getProcessInstanceExample( processInstanceKey: ProcessInstanceKey ) { const camunda = createCamundaClient(); const instance = await camunda.getProcessInstance( { processInstanceKey }, { consistency: { waitUpToMs: 5000 } } ); console.log(`State: ${instance.state}`); console.log(`Process: ${instance.processDefinitionId}`); } ``` #### Operation Id getProcessInstance #### Tags Process instance #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getProcessInstanceCallHierarchy() ```ts getProcessInstanceCallHierarchy( input, consistencyManagement, options? ): CancelablePromise; ``` Get call hierarchy Returns the call hierarchy for a given process instance, showing its ancestry up to the root instance. * #### Parameters ##### input [`getProcessInstanceCallHierarchyInput`](../type-aliases/getProcessInstanceCallHierarchyInput.md) ##### consistencyManagement [`getProcessInstanceCallHierarchyConsistency`](../type-aliases/getProcessInstanceCallHierarchyConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ProcessInstanceCallHierarchyEntry`](../type-aliases/ProcessInstanceCallHierarchyEntry.md)[]\> #### Example **Get process instance call hierarchy** ```ts async function getProcessInstanceCallHierarchyExample( processInstanceKey: ProcessInstanceKey ) { const camunda = createCamundaClient(); const result = await camunda.getProcessInstanceCallHierarchy( { processInstanceKey }, { consistency: { waitUpToMs: 5000 } } ); console.log(`Call hierarchy entries: ${result.length}`); } ``` #### Operation Id getProcessInstanceCallHierarchy #### Tags Process instance #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getProcessInstanceSequenceFlows() ```ts getProcessInstanceSequenceFlows( input, consistencyManagement, options? ): CancelablePromise; ``` Get sequence flows Get sequence flows taken by the process instance. * #### Parameters ##### input [`getProcessInstanceSequenceFlowsInput`](../type-aliases/getProcessInstanceSequenceFlowsInput.md) ##### consistencyManagement [`getProcessInstanceSequenceFlowsConsistency`](../type-aliases/getProcessInstanceSequenceFlowsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ProcessInstanceSequenceFlowsQueryResult`](../type-aliases/ProcessInstanceSequenceFlowsQueryResult.md)\> #### Example **Get process instance sequence flows** ```ts async function getProcessInstanceSequenceFlowsExample( processInstanceKey: ProcessInstanceKey ) { const camunda = createCamundaClient(); const result = await camunda.getProcessInstanceSequenceFlows( { processInstanceKey }, { consistency: { waitUpToMs: 5000 } } ); for (const flow of result.items ?? []) { console.log(`Sequence flow: ${flow.sequenceFlowId}`); } } ``` #### Operation Id getProcessInstanceSequenceFlows #### Tags Process instance #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getProcessInstanceStatistics() ```ts getProcessInstanceStatistics( input, consistencyManagement, options? ): CancelablePromise; ``` Get element instance statistics Get statistics about elements by the process instance key. * #### Parameters ##### input [`getProcessInstanceStatisticsInput`](../type-aliases/getProcessInstanceStatisticsInput.md) ##### consistencyManagement [`getProcessInstanceStatisticsConsistency`](../type-aliases/getProcessInstanceStatisticsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ProcessInstanceElementStatisticsQueryResult`](../type-aliases/ProcessInstanceElementStatisticsQueryResult.md)\> #### Example **Get process instance statistics** ```ts async function getProcessInstanceStatisticsExample( processInstanceKey: ProcessInstanceKey ) { const camunda = createCamundaClient(); const result = await camunda.getProcessInstanceStatistics( { processInstanceKey }, { consistency: { waitUpToMs: 5000 } } ); for (const stat of result.items ?? []) { console.log(`Element ${stat.elementId}: active=${stat.active}`); } } ``` #### Operation Id getProcessInstanceStatistics #### Tags Process instance #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getProcessInstanceStatisticsByDefinition() ```ts getProcessInstanceStatisticsByDefinition( input, consistencyManagement, options? ): CancelablePromise; ``` Get process instance statistics by definition Returns statistics for active process instances with incidents, grouped by process definition. The result set is scoped to a specific incident error hash code, which must be provided as a filter in the request body. - #### Parameters ##### input [`IncidentProcessInstanceStatisticsByDefinitionQuery`](../type-aliases/IncidentProcessInstanceStatisticsByDefinitionQuery.md) ##### consistencyManagement [`getProcessInstanceStatisticsByDefinitionConsistency`](../type-aliases/getProcessInstanceStatisticsByDefinitionConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`IncidentProcessInstanceStatisticsByDefinitionQueryResult`](../type-aliases/IncidentProcessInstanceStatisticsByDefinitionQueryResult.md)\> #### Example **Get instance statistics by definition** ```ts async function getProcessInstanceStatisticsByDefinitionExample() { const camunda = createCamundaClient(); const result = await camunda.getProcessInstanceStatisticsByDefinition( { filter: { errorHashCode: 12345, }, }, { consistency: { waitUpToMs: 5000 } } ); for (const stat of result.items ?? []) { console.log( `Definition ${stat.processDefinitionId}: ${stat.activeInstancesWithErrorCount} incidents` ); } } ``` #### Operation Id getProcessInstanceStatisticsByDefinition #### Tags Incident #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getProcessInstanceStatisticsByError() ```ts getProcessInstanceStatisticsByError( input, consistencyManagement, options? ): CancelablePromise; ``` Get process instance statistics by error Returns statistics for active process instances that currently have active incidents, grouped by incident error hash code. - #### Parameters ##### input [`IncidentProcessInstanceStatisticsByErrorQuery`](../type-aliases/IncidentProcessInstanceStatisticsByErrorQuery.md) ##### consistencyManagement [`getProcessInstanceStatisticsByErrorConsistency`](../type-aliases/getProcessInstanceStatisticsByErrorConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`IncidentProcessInstanceStatisticsByErrorQueryResult`](../type-aliases/IncidentProcessInstanceStatisticsByErrorQueryResult.md)\> #### Example **Get instance statistics by error** ```ts async function getProcessInstanceStatisticsByErrorExample() { const camunda = createCamundaClient(); const result = await camunda.getProcessInstanceStatisticsByError( {}, { consistency: { waitUpToMs: 5000 } } ); for (const stat of result.items ?? []) { console.log( `Error: ${stat.errorMessage}, count: ${stat.activeInstancesWithErrorCount}` ); } } ``` #### Operation Id getProcessInstanceStatisticsByError #### Tags Incident #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getProcessInstanceWaitStateStatistics() ```ts getProcessInstanceWaitStateStatistics( input, consistencyManagement, options? ): CancelablePromise; ``` Get wait state statistics Get statistics about waiting element instances by the process instance key, grouped by element id. * #### Parameters ##### input [`getProcessInstanceWaitStateStatisticsInput`](../type-aliases/getProcessInstanceWaitStateStatisticsInput.md) ##### consistencyManagement [`getProcessInstanceWaitStateStatisticsConsistency`](../type-aliases/getProcessInstanceWaitStateStatisticsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ProcessInstanceWaitStateStatisticsQueryResult`](../type-aliases/ProcessInstanceWaitStateStatisticsQueryResult.md)\> #### Example **Get process instance wait state statistics** ```ts async function getProcessInstanceWaitStateStatisticsExample( processInstanceKey: ProcessInstanceKey ) { const camunda = createCamundaClient(); const result = await camunda.getProcessInstanceWaitStateStatistics( { processInstanceKey }, { consistency: { waitUpToMs: 5000 } } ); for (const stat of result.items ?? []) { console.log(`Element ${stat.elementId}: waiting=${stat.waitingCount}`); } } ``` #### Operation Id getProcessInstanceWaitStateStatistics #### Tags Process instance #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getResource() ```ts getResource( input, consistencyManagement, options? ): CancelablePromise; ``` 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. ::: - #### Parameters ##### input [`getResourceInput`](../type-aliases/getResourceInput.md) ##### consistencyManagement [`getResourceConsistency`](../type-aliases/getResourceConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ResourceResult`](../type-aliases/ResourceResult.md)\> #### Example **Get a resource** ```ts async function getResourceExample(resourceKey: ProcessDefinitionKey) { const camunda = createCamundaClient(); const resource = await camunda.getResource( { resourceKey, }, { consistency: { waitUpToMs: 0 } } ); console.log(`Resource: ${resource.resourceName} (${resource.resourceId})`); } ``` #### Operation Id getResource #### Tags Resource #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### ~~getResourceContent()~~ ```ts getResourceContent( input, consistencyManagement, options? ): CancelablePromise<{ [key: string]: unknown; }>; ``` Get RPA resource content (deprecated) **Deprecated** — use `/resources/{resourceKey}/content/binary` instead, which supports all resource types and returns content as binary (octet-stream). Returns the content of a deployed RPA resource as JSON. :::info This endpoint only supports RPA resources. For generic resource content in binary format, use the `/resources/{resourceKey}/content/binary` endpoint. ::: #### Parameters ##### input [`getResourceContentInput`](../type-aliases/getResourceContentInput.md) ##### consistencyManagement [`getResourceContentConsistency`](../type-aliases/getResourceContentConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ \[`key`: `string`\]: `unknown`; \}\> #### Deprecated - #### Example **Get resource content** ```ts async function getResourceContentExample(resourceKey: ProcessDefinitionKey) { const camunda = createCamundaClient(); const content = await camunda.getResourceContent( { resourceKey, }, { consistency: { waitUpToMs: 0 } } ); console.log(`Content retrieved (type: ${typeof content})`); } ``` #### Operation Id getResourceContent #### Tags Resource #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getResourceContentBinary() ```ts getResourceContentBinary( input, consistencyManagement, options? ): CancelablePromise; ``` Get resource content as binary Returns the content of a deployed resource in binary format (octet-stream). :::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. ::: - #### Parameters ##### input [`getResourceContentBinaryInput`](../type-aliases/getResourceContentBinaryInput.md) ##### consistencyManagement [`getResourceContentBinaryConsistency`](../type-aliases/getResourceContentBinaryConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`Blob`\> #### Example **Get resource content as binary** ```ts async function getResourceContentBinaryExample( resourceKey: ProcessDefinitionKey ) { const camunda = createCamundaClient(); const content = await camunda.getResourceContentBinary( { resourceKey, }, { consistency: { waitUpToMs: 0 } } ); console.log(`Binary content retrieved (type: ${typeof content})`); } ``` #### Operation Id getResourceContentBinary #### Tags Resource #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getRestoreStatus() ```ts getRestoreStatus(options?): CancelablePromise; ``` Get the status of the restore that is currently in progress Returns the status of the restore that is currently in progress, reported per broker and per partition. There is at most one restore in flight at any time. Once the restore has finished this endpoint returns 404; the per-partition detail is not retained after completion. * #### Parameters ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`RestoreStatusResponse`](../type-aliases/RestoreStatusResponse.md)\> #### Example **Get restore status** ```ts async function getRestoreStatusExample() { const camunda = createCamundaClient(); const status = await camunda.getRestoreStatus(); console.log(`Restore status: ${status.status} (change ${status.changeId})`); for (const broker of status.brokers) { console.log( ` Broker ${broker.brokerId}: ${broker.partitionsRestored}/${broker.partitionsToRestore} partitions restored` ); } } ``` #### Operation Id getRestoreStatus #### Tags Recovery --- ### getRole() ```ts getRole( input, consistencyManagement, options? ): CancelablePromise; ``` Get role Get a role by its ID. * #### Parameters ##### input [`getRoleInput`](../type-aliases/getRoleInput.md) ##### consistencyManagement [`getRoleConsistency`](../type-aliases/getRoleConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`RoleResult`](../type-aliases/RoleResult.md)\> #### Example **Get a role** ```ts async function getRoleExample(roleId: RoleId) { const camunda = createCamundaClient(); const role = await camunda.getRole( { roleId }, { consistency: { waitUpToMs: 5000 } } ); console.log(`Role: ${role.name}`); } ``` #### Operation Id getRole #### Tags Role #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getRuntimeBackup() ```ts getRuntimeBackup(input, options?): CancelablePromise; ``` Get runtime backup Returns detailed status of the runtime backup with the given id. * #### Parameters ##### input [`getRuntimeBackupInput`](../type-aliases/getRuntimeBackupInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`BackupInfo`](../type-aliases/BackupInfo.md)\> #### Example **Get a runtime backup** ```ts async function getRuntimeBackupExample() { const camunda = createCamundaClient(); const backup = await camunda.getRuntimeBackup({ backupId: 100 }); console.log(`Backup ${backup.backupId}: ${backup.state}`); for (const partition of backup.details) { console.log(` Partition ${partition.partitionId}: ${partition.state}`); } } ``` #### Operation Id getRuntimeBackup #### Tags Backup --- ### getRuntimeBackupAsClusterAdmin() ```ts getRuntimeBackupAsClusterAdmin(input, options?): CancelablePromise; ``` Get a runtime backup across physical tenants Reports what every physical tenant of the cluster, or the one named by `physicalTenantId`, holds for the given backup id, plus the state aggregated over all of them. A tenant that was reached and does not hold this backup reports `DOES_NOT_EXIST`, which is a successful observation rather than a failure — so a backup only some tenants hold aggregates to `INCOMPLETE`, the same way a backup only some partitions hold does within one tenant. The request is all-or-nothing: a physical tenant whose state cannot be read fails the whole request. Narrow the request with `physicalTenantId` to read the tenants that can still be reached. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Use `GET /v2/backups/runtime/{backupId}` to act as a single physical tenant. * #### Parameters ##### input [`getRuntimeBackupAsClusterAdminInput`](../type-aliases/getRuntimeBackupAsClusterAdminInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterRuntimeBackupInfo`](../type-aliases/ClusterRuntimeBackupInfo.md)\> #### Example **Get a runtime backup (cluster admin)** ```ts async function getRuntimeBackupAsClusterAdminExample() { const camunda = createCamundaClient(); // Looking a backup id up directly lists every targeted physical tenant, // including the ones reporting `DOES_NOT_EXIST` — a backup that only some // tenants hold is a supported outcome. const backup = await camunda.getRuntimeBackupAsClusterAdmin({ backupId: 100, }); console.log(`Cluster runtime backup ${backup.backupId}: ${backup.state}`); for (const tenant of backup.physicalTenants) { console.log(` [${tenant.physicalTenantId}] ${tenant.state}`); } } ``` #### Operation Id getRuntimeBackupAsClusterAdmin #### Tags Backup --- ### getRuntimeBackupState() ```ts getRuntimeBackupState(options?): CancelablePromise; ``` Get runtime backup state Returns the current checkpoint and backup state of every partition of the physical tenant. Unlike the `backupRuntime` actuator, this fails the whole request if the checkpoint state or the backup ranges cannot be retrieved from any partition, instead of silently returning an empty section. - #### Parameters ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`RuntimeBackupState`](../type-aliases/RuntimeBackupState.md)\> #### Example **Get the runtime backup state** ```ts async function getRuntimeBackupStateExample() { const camunda = createCamundaClient(); const state = await camunda.getRuntimeBackupState(); for (const checkpoint of state.checkpointStates) { console.log( `Partition ${checkpoint.partitionId} checkpoint ${checkpoint.checkpointId} (${checkpoint.checkpointType})` ); } for (const range of state.ranges) { console.log( `Partition ${range.partitionId} range: ${range.start?.checkpointId} -> ${range.end?.checkpointId}` ); } } ``` #### Operation Id getRuntimeBackupState #### Tags Backup --- ### getRuntimeBackupStateAsClusterAdmin() ```ts getRuntimeBackupStateAsClusterAdmin(input, options?): CancelablePromise; ``` Get runtime backup state across physical tenants Reports the checkpoint and backup state of every partition of every physical tenant of the cluster, or of the one named by `physicalTenantId`, grouped by physical tenant. Checkpoint ids and log positions only mean anything within one physical tenant's partitions, so nothing is aggregated across tenants. The request is all-or-nothing: a physical tenant whose state cannot be read fails the whole request rather than contributing an empty section, which an operator making a delete or restore decision could not tell apart from "nothing to report yet". Narrow the request with `physicalTenantId` to read the tenants that can still be reached. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Use `GET /v2/backups/runtime/state` to act as a single physical tenant. * #### Parameters ##### input [`getRuntimeBackupStateAsClusterAdminInput`](../type-aliases/getRuntimeBackupStateAsClusterAdminInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterRuntimeBackupState`](../type-aliases/ClusterRuntimeBackupState.md)\> #### Example **Get the runtime backup state (cluster admin)** ```ts async function getRuntimeBackupStateAsClusterAdminExample() { const camunda = createCamundaClient(); // Returns the checkpoint and backup state of every targeted physical tenant. // Nothing is aggregated across tenants — checkpoint ids and log positions only // mean anything within one tenant's partitions. const clusterState = await camunda.getRuntimeBackupStateAsClusterAdmin({}); for (const tenant of clusterState.physicalTenants) { console.log( `[${tenant.physicalTenantId}] ${tenant.state.checkpointStates.length} checkpoints` ); } } ``` #### Operation Id getRuntimeBackupStateAsClusterAdmin #### Tags Backup --- ### getStartProcessForm() ```ts getStartProcessForm( input, consistencyManagement, options? ): CancelablePromise; ``` Get process start form Get the start form of a process. Note that this endpoint will only return linked forms. This endpoint does not support embedded forms. - #### Parameters ##### input [`getStartProcessFormInput`](../type-aliases/getStartProcessFormInput.md) ##### consistencyManagement [`getStartProcessFormConsistency`](../type-aliases/getStartProcessFormConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void` \| [`FormResult`](../type-aliases/FormResult.md)\> #### Example **Get start process form** ```ts async function getStartProcessFormExample( processDefinitionKey: ProcessDefinitionKey ) { const camunda = createCamundaClient(); const form = await camunda.getStartProcessForm( { processDefinitionKey }, { consistency: { waitUpToMs: 5000 } } ); if (form) { console.log(`Form key: ${form.formKey}`); } } ``` #### Operation Id getStartProcessForm #### Tags Process definition #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getStatus() ```ts getStatus(options?): CancelablePromise; ``` Get physical tenant status Checks the health status of the default physical tenant by verifying if there's at least one partition of its group with a healthy leader. This endpoint is scoped to the default physical tenant only: it is available unprefixed and at `/physical-tenants/default/v2/status`, but not for any other physical tenant id (`/physical-tenants/{id}/v2/status` returns 404 for every other id, whether or not a physical tenant with that id exists). On a cluster with only the default physical tenant this endpoint answers the same question as `/cluster/v2/status`, though not with the same response: `/cluster/v2/status` reports its status in a body and so also distinguishes a degraded tenant from a healthy one. Use `/cluster/v2/status` for the aggregated status of the whole cluster, or `/physical-tenants/{id}/v2/topology` for the health of a specific physical tenant's partitions. * #### Parameters ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Check cluster status** ```ts async function getStatusExample() { const camunda = createCamundaClient(); await camunda.getStatus(); console.log("Cluster is healthy"); } ``` #### Operation Id getStatus #### Tags Cluster --- ### getSystemConfiguration() ```ts getSystemConfiguration(options?): CancelablePromise; ``` 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. - #### Parameters ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`SystemConfigurationResponse`](../type-aliases/SystemConfigurationResponse.md)\> #### Example **Get system configuration** ```ts async function getSystemConfigurationExample() { const camunda = createCamundaClient(); const config = await camunda.getSystemConfiguration(); console.log(`Configuration loaded: ${JSON.stringify(config)}`); } ``` #### Operation Id getSystemConfiguration #### Tags System --- ### getTenant() ```ts getTenant( input, consistencyManagement, options? ): CancelablePromise; ``` Get tenant Retrieves a single tenant by tenant ID. * #### Parameters ##### input [`getTenantInput`](../type-aliases/getTenantInput.md) ##### consistencyManagement [`getTenantConsistency`](../type-aliases/getTenantConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`TenantResult`](../type-aliases/TenantResult.md)\> #### Example **Get a tenant** ```ts async function getTenantExample(tenantId: TenantId) { const camunda = createCamundaClient(); const tenant = await camunda.getTenant( { tenantId }, { consistency: { waitUpToMs: 5000 } } ); console.log(`Tenant: ${tenant.name}`); } ``` #### Operation Id getTenant #### Tags Tenant #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getTenantClusterVariable() ```ts getTenantClusterVariable( input, consistencyManagement, options? ): CancelablePromise; ``` Get a tenant-scoped cluster variable Get a tenant-scoped cluster variable. * #### Parameters ##### input [`getTenantClusterVariableInput`](../type-aliases/getTenantClusterVariableInput.md) ##### consistencyManagement [`getTenantClusterVariableConsistency`](../type-aliases/getTenantClusterVariableConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterVariableResult`](../type-aliases/ClusterVariableResult.md)\> #### Example **Get a tenant cluster variable** ```ts async function getTenantClusterVariableExample( tenantId: TenantId, name: ClusterVariableName ) { const camunda = createCamundaClient(); const variable = await camunda.getTenantClusterVariable( { tenantId, name, }, { consistency: { waitUpToMs: 5000 } } ); console.log(`${variable.name} = ${variable.value}`); } ``` #### Operation Id getTenantClusterVariable #### Tags Cluster Variable #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getTopology() ```ts getTopology(options?): CancelablePromise; ``` Get cluster topology Obtains the current topology of the cluster the gateway is part of. * #### Parameters ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`TopologyResponse`](../type-aliases/TopologyResponse.md)\> #### Example **Get cluster topology** ```ts async function getTopologyExample() { const camunda = createCamundaClient(); const topology = await camunda.getTopology(); console.log(`Cluster size: ${topology.clusterSize}`); console.log(`Partitions: ${topology.partitionsCount}`); for (const broker of topology.brokers ?? []) { console.log(` Broker ${broker.nodeId}: ${broker.host}:${broker.port}`); } } ``` #### Operation Id getTopology #### Tags Cluster --- ### getUsageMetrics() ```ts getUsageMetrics( input, consistencyManagement, options? ): CancelablePromise; ``` Get usage metrics Retrieve the usage metrics based on given criteria. * #### Parameters ##### input [`getUsageMetricsInput`](../type-aliases/getUsageMetricsInput.md) ##### consistencyManagement [`getUsageMetricsConsistency`](../type-aliases/getUsageMetricsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`UsageMetricsResponse`](../type-aliases/UsageMetricsResponse.md)\> #### Example **Get usage metrics** ```ts async function getUsageMetricsExample() { const camunda = createCamundaClient(); const metrics = await camunda.getUsageMetrics( { startTime: "2025-01-01T00:00:00Z", endTime: "2025-12-31T23:59:59Z", }, { consistency: { waitUpToMs: 5000 } } ); console.log(`Usage metrics retrieved: ${JSON.stringify(metrics)}`); } ``` #### Operation Id getUsageMetrics #### Tags System #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getUser() ```ts getUser( input, consistencyManagement, options? ): CancelablePromise; ``` Get user Get a user by its username. * #### Parameters ##### input [`getUserInput`](../type-aliases/getUserInput.md) ##### consistencyManagement [`getUserConsistency`](../type-aliases/getUserConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`UserResult`](../type-aliases/UserResult.md)\> #### Example **Get a user** ```ts async function getUserExample(username: Username) { const camunda = createCamundaClient(); const user = await camunda.getUser( { username }, { consistency: { waitUpToMs: 5000 } } ); console.log(`User: ${user.name} (${user.email})`); } ``` #### Operation Id getUser #### Tags User #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getUserTask() ```ts getUserTask( input, consistencyManagement, options? ): CancelablePromise; ``` Get user task Get the user task by the user task key. * #### Parameters ##### input [`getUserTaskInput`](../type-aliases/getUserTaskInput.md) ##### consistencyManagement [`getUserTaskConsistency`](../type-aliases/getUserTaskConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`UserTaskResult`](../type-aliases/UserTaskResult.md)\> #### Example **Get a user task** ```ts async function getUserTaskExample(userTaskKey: UserTaskKey) { const camunda = createCamundaClient(); const task = await camunda.getUserTask( { userTaskKey }, { consistency: { waitUpToMs: 5000 } } ); console.log(`Task: ${task.name} (${task.state})`); } ``` #### Operation Id getUserTask #### Tags User task #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getUserTaskForm() ```ts getUserTaskForm( input, consistencyManagement, options? ): CancelablePromise; ``` Get user task form Get the form of a user task. Note that this endpoint will only return linked forms. This endpoint does not support embedded forms. - #### Parameters ##### input [`getUserTaskFormInput`](../type-aliases/getUserTaskFormInput.md) ##### consistencyManagement [`getUserTaskFormConsistency`](../type-aliases/getUserTaskFormConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void` \| [`FormResult`](../type-aliases/FormResult.md)\> #### Example **Get a user task form** ```ts async function getUserTaskFormExample(userTaskKey: UserTaskKey) { const camunda = createCamundaClient(); const form = await camunda.getUserTaskForm( { userTaskKey }, { consistency: { waitUpToMs: 5000 } } ); if (form) { console.log(`Form key: ${form.formKey}`); } } ``` #### Operation Id getUserTaskForm #### Tags User task #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getVariable() ```ts getVariable( input, consistencyManagement, options? ): CancelablePromise; ``` Get variable Get a variable by its key. This endpoint returns both process-level and local (element-scoped) variables. The variable's scopeKey indicates whether it's a process-level variable or scoped to a specific element instance. * #### Parameters ##### input [`getVariableInput`](../type-aliases/getVariableInput.md) ##### consistencyManagement [`getVariableConsistency`](../type-aliases/getVariableConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`VariableResult`](../type-aliases/VariableResult.md)\> #### Example **Get a variable** ```ts async function getVariableExample(variableKey: VariableKey) { const camunda = createCamundaClient(); const variable = await camunda.getVariable( { variableKey }, { consistency: { waitUpToMs: 5000 } } ); console.log(`${variable.name} = ${variable.value}`); } ``` #### Operation Id getVariable #### Tags Variable #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### getWorkers() ```ts getWorkers(): any[]; ``` Return a read-only snapshot of currently registered job workers. #### Returns `any`[] --- ### listHistoryBackups() ```ts listHistoryBackups(input, options?): CancelablePromise; ``` List history backups Returns a list of all available history backups of the physical tenant, with their state and additional info, most recent first by snapshot start time. Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. - #### Parameters ##### input [`listHistoryBackupsInput`](../type-aliases/listHistoryBackupsInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`HistoryBackupInfo`](../type-aliases/HistoryBackupInfo.md)[]\> #### Example **List history backups** ```ts async function listHistoryBackupsExample() { const camunda = createCamundaClient(); // `prefix` must end in a single '*'. Omit it to list every history backup. const backups = await camunda.listHistoryBackups({ prefix: "10*" }); for (const backup of backups) { console.log(`History backup ${backup.backupId}: ${backup.state}`); } } ``` #### Operation Id listHistoryBackups #### Tags Backup --- ### listHistoryBackupsAsClusterAdmin() ```ts listHistoryBackupsAsClusterAdmin(input, options?): CancelablePromise; ``` List history backups across physical tenants Lists the history backups of every physical tenant of the cluster, or of the one named by `physicalTenantId`, grouped by backup id. A backup id that only some physical tenants hold is a supported outcome rather than a degraded one, so only the tenants that hold it are listed under it. The request is all-or-nothing: a physical tenant whose backups cannot be read fails the whole request rather than silently dropping out of the listing. Narrow the request with `physicalTenantId` to list the backups of the tenants that can still be read. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. Use `GET /v2/backups/history` to act as a single physical tenant. * #### Parameters ##### input [`listHistoryBackupsAsClusterAdminInput`](../type-aliases/listHistoryBackupsAsClusterAdminInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterHistoryBackupInfo`](../type-aliases/ClusterHistoryBackupInfo.md)[]\> #### Example **List history backups (cluster admin)** ```ts async function listHistoryBackupsAsClusterAdminExample() { const camunda = createCamundaClient(); // `prefix` must end in a single '*'. Omit `physicalTenantId` to span every // physical tenant of the cluster — results are grouped by backup id, and each // group lists only the tenants that hold that id. const backups = await camunda.listHistoryBackupsAsClusterAdmin({ prefix: "10*", }); for (const backup of backups) { console.log(`Cluster history backup ${backup.backupId}:`); for (const tenant of backup.physicalTenants) { console.log(` [${tenant.physicalTenantId}] ${tenant.state}`); } } } ``` #### Operation Id listHistoryBackupsAsClusterAdmin #### Tags Backup --- ### listRuntimeBackups() ```ts listRuntimeBackups(input, options?): CancelablePromise; ``` List runtime backups Returns a list of all available runtime backups of the physical tenant, with their state and additional info, sorted in descending order of backupId. - #### Parameters ##### input [`listRuntimeBackupsInput`](../type-aliases/listRuntimeBackupsInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`BackupInfo`](../type-aliases/BackupInfo.md)[]\> #### Example **List runtime backups** ```ts async function listRuntimeBackupsExample() { const camunda = createCamundaClient(); // `prefix` must end in a single '*'. Omit it to list every backup. const backups = await camunda.listRuntimeBackups({ prefix: "10*" }); for (const backup of backups) { console.log(`Backup ${backup.backupId}: ${backup.state}`); } } ``` #### Operation Id listRuntimeBackups #### Tags Backup --- ### listRuntimeBackupsAsClusterAdmin() ```ts listRuntimeBackupsAsClusterAdmin(input, options?): CancelablePromise; ``` List runtime backups across physical tenants Lists the runtime backups of every physical tenant of the cluster, or of the one named by `physicalTenantId`, grouped by backup id. Every group reports every targeted tenant, including the ones holding nothing for that id, so a backup only some tenants hold aggregates to `INCOMPLETE` here exactly as it does when looked up directly — the state of a listed group can be trusted to say whether the cluster can be restored from it. A backup id that only some physical tenants hold is a supported outcome rather than a degraded one; tenants that generate their own backup ids never share one, so in that mode each backup forms its own group and the other tenants report `DOES_NOT_EXIST` under it. The request is all-or-nothing: a physical tenant whose backups cannot be read fails the whole request rather than silently dropping out of the listing. Narrow the request with `physicalTenantId` to list the backups of the tenants that can still be read. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Use `GET /v2/backups/runtime` to act as a single physical tenant. * #### Parameters ##### input [`listRuntimeBackupsAsClusterAdminInput`](../type-aliases/listRuntimeBackupsAsClusterAdminInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterRuntimeBackupInfo`](../type-aliases/ClusterRuntimeBackupInfo.md)[]\> #### Example **List runtime backups (cluster admin)** ```ts async function listRuntimeBackupsAsClusterAdminExample() { const camunda = createCamundaClient(); // `prefix` must end in a single '*'. Omit `physicalTenantId` to span every // physical tenant — results are grouped by backup id, and each group reports // every targeted tenant, including ones holding nothing for that id (reported // as `DOES_NOT_EXIST`). const backups = await camunda.listRuntimeBackupsAsClusterAdmin({ prefix: "10*", }); for (const backup of backups) { console.log(`Cluster runtime backup ${backup.backupId}: ${backup.state}`); for (const tenant of backup.physicalTenants) { console.log(` [${tenant.physicalTenantId}] ${tenant.state}`); } } } ``` #### Operation Id listRuntimeBackupsAsClusterAdmin #### Tags Backup --- ### listSecrets() ```ts listSecrets(input, options?): CancelablePromise; ``` List secrets List the `camunda.secrets.*` references known for the caller's physical tenant. Only references the caller holds `SECRET:READ` on are returned. This endpoint never returns secret values, only the reference names. The references are read from the secret stores configured for the caller's physical tenant. A store may hold names outside the reference name charset (for example one containing a dot); those are omitted, since `/secrets/resolve` would reject them and no permission can be granted on them. A returned reference is usable verbatim with `/secrets/resolve`. In a FEEL expression, however, a name that is not a bare identifier has to be backtick-escaped, since FEEL reads a bare dash as the minus operator: a listed `camunda.secrets.db-password` is written `` =camunda.secrets.`db-password` `` in a BPMN input mapping. - #### Parameters ##### input [`SecretListRequest`](../type-aliases/SecretListRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`SecretListResult`](../type-aliases/SecretListResult.md)\> #### Example **List secret references** ```ts async function listSecretsExample() { const camunda = createCamundaClient(); // The request body is reserved for future filtering options and currently // takes no properties. const result = await camunda.listSecrets({}); // Only the references are returned — never the secret values. Use // `resolveSecrets` to fetch a value when one is actually needed. for (const reference of result.references) { console.log(`Secret available: ${reference}`); } } ``` #### Operation Id listSecrets #### Tags Secret --- ### logger() ```ts logger(scope?): Logger; ``` Access a scoped logger (internal & future user emission). #### Parameters ##### scope? `string` #### Returns [`Logger`](../../logger/interfaces/Logger.md) #### Inherited from ```ts CamundaCore.logger; ``` --- ### migrateProcessInstance() ```ts migrateProcessInstance(input, options?): CancelablePromise; ``` Migrate process instance Migrates a process instance to a new process definition. This request can contain multiple mapping instructions to define mapping between the active process instance's elements and target process definition elements. Use this to upgrade a process instance to a new version of a process or to a different process definition, e.g. to keep your running instances up-to-date with the latest process improvements. - #### Parameters ##### input [`migrateProcessInstanceInput`](../type-aliases/migrateProcessInstanceInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Migrate a process instance** ```ts async function migrateProcessInstanceExample( processInstanceKey: ProcessInstanceKey, targetProcessDefinitionKey: ProcessDefinitionKey, sourceElementId: ElementId, targetElementId: ElementId ) { const camunda = createCamundaClient(); await camunda.migrateProcessInstance({ processInstanceKey, targetProcessDefinitionKey, mappingInstructions: [ { sourceElementId, targetElementId, }, ], }); } ``` #### Operation Id migrateProcessInstance #### Tags Process instance --- ### migrateProcessInstancesBatchOperation() ```ts migrateProcessInstancesBatchOperation(input, options?): CancelablePromise; ``` Migrate process instances (batch) Migrate multiple process instances. Since only process instances with ACTIVE state can be migrated, any given filters for state are ignored and overridden during this batch operation. This is done asynchronously, the progress can be tracked using the batchOperationKey from the response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). - #### Parameters ##### input [`ProcessInstanceMigrationBatchOperationRequest`](../type-aliases/ProcessInstanceMigrationBatchOperationRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`BatchOperationCreatedResult`](../type-aliases/BatchOperationCreatedResult.md)\> #### Example **Migrate process instances in batch** ```ts async function migrateProcessInstancesBatchOperationExample( processDefinitionKey: ProcessDefinitionKey, targetProcessDefinitionKey: ProcessDefinitionKey, sourceElementId: ElementId, targetElementId: ElementId ) { const camunda = createCamundaClient(); const result = await camunda.migrateProcessInstancesBatchOperation({ filter: { processDefinitionKey, }, migrationPlan: { targetProcessDefinitionKey, mappingInstructions: [ { sourceElementId, targetElementId, }, ], }, }); console.log(`Batch operation key: ${result.batchOperationKey}`); } ``` #### Operation Id migrateProcessInstancesBatchOperation #### Tags Process instance --- ### modifyProcessInstance() ```ts modifyProcessInstance(input, options?): CancelablePromise; ``` Modify process instance Modifies a running process instance. This request can contain multiple instructions to activate an element of the process or to terminate an active instance of an element. Use this to repair a process instance that is stuck on an element or took an unintended path. For example, because an external system is not available or doesn't respond as expected. - #### Parameters ##### input [`modifyProcessInstanceInput`](../type-aliases/modifyProcessInstanceInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Modify a process instance** ```ts async function modifyProcessInstanceExample( processInstanceKey: ProcessInstanceKey, elementId: ElementId, elementInstanceKey: ElementInstanceKey ) { const camunda = createCamundaClient(); await camunda.modifyProcessInstance({ processInstanceKey, activateInstructions: [{ elementId }], terminateInstructions: [{ elementInstanceKey }], }); } ``` #### Operation Id modifyProcessInstance #### Tags Process instance --- ### modifyProcessInstancesBatchOperation() ```ts modifyProcessInstancesBatchOperation(input, options?): CancelablePromise; ``` Modify process instances (batch) Modify multiple process instances. Since only process instances with ACTIVE state can be modified, any given filters for state are ignored and overridden during this batch operation. In contrast to single modification operation, it is not possible to add variable instructions or modify by element key. It is only possible to use the element id of the source and target. This is done asynchronously, the progress can be tracked using the batchOperationKey from the response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). - #### Parameters ##### input [`ProcessInstanceModificationBatchOperationRequest`](../type-aliases/ProcessInstanceModificationBatchOperationRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`BatchOperationCreatedResult`](../type-aliases/BatchOperationCreatedResult.md)\> #### Example **Modify process instances in batch** ```ts async function modifyProcessInstancesBatchOperationExample( processDefinitionKey: ProcessDefinitionKey, sourceElementId: ElementId, targetElementId: ElementId ) { const camunda = createCamundaClient(); const result = await camunda.modifyProcessInstancesBatchOperation({ filter: { processDefinitionKey, }, moveInstructions: [ { sourceElementId, targetElementId, }, ], }); console.log(`Batch operation key: ${result.batchOperationKey}`); } ``` #### Operation Id modifyProcessInstancesBatchOperation #### Tags Process instance --- ### onAuthHeaders() ```ts onAuthHeaders(h): void; ``` #### Parameters ##### h (`headers`) => \| `Record`\<`string`, `string`\> \| `Promise`\<`Record`\<`string`, `string`\>\> #### Returns `void` #### Inherited from ```ts CamundaCore.onAuthHeaders; ``` --- ### pauseClusterExporting() ```ts pauseClusterExporting(input, options?): CancelablePromise; ``` Pause exporting across the whole cluster Pauses exporting on every physical tenant of the cluster in one call. With `soft=true`, every physical tenant is soft-paused instead. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. * #### Parameters ##### input [`pauseClusterExportingInput`](../type-aliases/pauseClusterExportingInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Pause cluster exporting** ```ts async function pauseClusterExportingExample() { const camunda = createCamundaClient(); // Cluster-admin variant: pauses exporting on every physical tenant of the // cluster. With `soft: true` exporting keeps running but its position is not // committed, so the log is still not compacted. await camunda.pauseClusterExporting({ soft: true }); } ``` #### Operation Id pauseClusterExporting #### Tags Exporting --- ### pauseExporting() ```ts pauseExporting(input, options?): CancelablePromise; ``` Pause exporting Pauses exporting on all partitions of the physical tenant. While paused, exported records are not committed, so the log is not compacted for the affected partitions. With `soft=true`, exporting continues to run but its position is not committed, so the state after resuming is identical to a hard pause; use this variant when exporting must keep progressing (e.g. to avoid falling behind) while still preventing log compaction, such as during a backup. - #### Parameters ##### input [`pauseExportingInput`](../type-aliases/pauseExportingInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Pause exporting** ```ts async function pauseExportingExample() { const camunda = createCamundaClient(); // With `soft: true` exporting keeps running but its position is not committed, // so the log is still not compacted — use it when exporting must keep // progressing, for example while a backup is taken. await camunda.pauseExporting({ soft: true }); } ``` #### Operation Id pauseExporting #### Tags Exporting --- ### pinClock() ```ts pinClock(input, options?): CancelablePromise; ``` 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. - #### Parameters ##### input [`ClockPinRequest`](../type-aliases/ClockPinRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Pin the cluster clock** ```ts async function pinClockExample() { const camunda = createCamundaClient(); await camunda.pinClock({ timestamp: 1735689599000, }); console.log("Clock pinned"); } ``` #### Operation Id pinClock #### Tags Clock --- ### publishMessage() ```ts publishMessage(input, options?): CancelablePromise; ``` Publish message Publishes a single message. Messages are published to specific partitions computed from their correlation keys. Messages can be buffered. The endpoint does not wait for a correlation result. Use the message correlation endpoint for such use cases. - #### Parameters ##### input [`MessagePublicationRequest`](../type-aliases/MessagePublicationRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`MessagePublicationResult`](../type-aliases/MessagePublicationResult.md)\> #### Example **Publish a message** ```ts async function publishMessageExample() { const camunda = createCamundaClient(); await camunda.publishMessage({ name: "order-payment-received", correlationKey: "ORD-12345", timeToLive: 60000, variables: { paymentId: "PAY-123", }, }); } ``` #### Operation Id publishMessage #### Tags Message --- ### resetClock() ```ts resetClock(options?): CancelablePromise; ``` 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. - #### Parameters ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Reset the cluster clock** ```ts async function resetClockExample() { const camunda = createCamundaClient(); await camunda.resetClock(); console.log("Clock reset"); } ``` #### Operation Id resetClock #### Tags Clock --- ### resolveIncident() ```ts resolveIncident(input, options?): CancelablePromise; ``` Resolve incident Marks the incident as resolved; most likely a call to Update job will be necessary to reset the job's retries, followed by this call. - #### Parameters ##### input [`resolveIncidentInput`](../type-aliases/resolveIncidentInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Resolve an incident** ```ts async function resolveIncidentExample(incidentKey: IncidentKey) { const camunda = createCamundaClient(); await camunda.resolveIncident({ incidentKey }); } ``` #### Operation Id resolveIncident #### Tags Incident --- ### resolveIncidentsBatchOperation() ```ts resolveIncidentsBatchOperation(input, options?): CancelablePromise; ``` Resolve related incidents (batch) Resolves multiple instances of process instances. Since only process instances with ACTIVE state can have unresolved incidents, any given filters for state are ignored and overridden during this batch operation. This is done asynchronously, the progress can be tracked using the batchOperationKey from the response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). - #### Parameters ##### input [`ProcessInstanceIncidentResolutionBatchOperationRequest`](../type-aliases/ProcessInstanceIncidentResolutionBatchOperationRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`BatchOperationCreatedResult`](../type-aliases/BatchOperationCreatedResult.md)\> #### Example **Resolve incidents in batch** ```ts async function resolveIncidentsBatchOperationExample( processDefinitionKey: ProcessDefinitionKey ) { const camunda = createCamundaClient(); const result = await camunda.resolveIncidentsBatchOperation({ filter: { processDefinitionKey, }, }); console.log(`Batch operation key: ${result.batchOperationKey}`); } ``` #### Operation Id resolveIncidentsBatchOperation #### Tags Process instance --- ### resolveProcessInstanceIncidents() ```ts resolveProcessInstanceIncidents(input, options?): CancelablePromise; ``` Resolve related incidents Creates a batch operation to resolve multiple incidents of a process instance. * #### Parameters ##### input [`resolveProcessInstanceIncidentsInput`](../type-aliases/resolveProcessInstanceIncidentsInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`BatchOperationCreatedResult`](../type-aliases/BatchOperationCreatedResult.md)\> #### Example **Resolve process instance incidents** ```ts async function resolveProcessInstanceIncidentsExample( processInstanceKey: ProcessInstanceKey ) { const camunda = createCamundaClient(); const result = await camunda.resolveProcessInstanceIncidents({ processInstanceKey, }); console.log(`Batch operation key: ${result.batchOperationKey}`); } ``` #### Operation Id resolveProcessInstanceIncidents #### Tags Process instance --- ### resolveSecrets() ```ts resolveSecrets(input, options?): CancelablePromise; ``` Resolve secrets Resolve a deduplicated batch of `camunda.secrets.*` references for the caller's physical tenant in a single round-trip. Each reference is authorized and resolved independently. For valid requests, the endpoint always responds with HTTP 200: successfully resolved references are returned in `resolved`, while references that could not be resolved (for example not found, malformed or over-long, or the caller lacks `SECRET:REVEAL` on that reference) are returned in `errors`. A failure of one reference never fails the others. Only structurally invalid requests are rejected with HTTP 400: a missing or non-array `references` field, more than 20 references, or a null entry. References are resolved against the secret stores configured for the caller's physical tenant, served from the gateway's secret cache when the value is already cached and read from the store otherwise. - #### Parameters ##### input [`SecretResolveRequest`](../type-aliases/SecretResolveRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`SecretResolveResult`](../type-aliases/SecretResolveResult.md)\> #### Example **Resolve secrets** ```ts async function resolveSecretsExample() { const camunda = createCamundaClient(); const result = await camunda.resolveSecrets({ references: ["camunda.secrets.myApiToken", "camunda.secrets.dbPassword"], }); // Successfully resolved references are returned in `resolved`; references that // could not be resolved are returned in `errors`, each with a typed error code. // Never log a resolved value — it holds secret material. Pass it straight to the // consumer that needs it (HTTP client, DB driver, ...) instead. for (const resolved of result.resolved) { console.log(`Resolved ${resolved.reference} (value redacted)`); useSecret(resolved.value); } for (const error of result.errors) { console.log( `Failed to resolve ${error.reference}: ${error.code} - ${error.message}` ); } } // Hands the resolved secret to whatever needs it, without logging it. function useSecret(_value: string) {} ``` #### Operation Id resolveSecrets #### Tags Secret --- ### restore() ```ts restore(input, options?): CancelablePromise; ``` Restore from a backup Restores the cluster from a backup. The restore is described either by a single backup ID or by a time range (`from`/`to`) that selects the backups to restore. This endpoint is only accessible while the cluster is in recovery mode; requests are rejected otherwise. The request is validated and acknowledged, but the restore itself is performed asynchronously. * #### Parameters ##### input [`restoreInput`](../type-aliases/restoreInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterRestoreResponse`](../type-aliases/ClusterRestoreResponse.md)\> #### Example **Restore from a backup** ```ts async function restoreExample() { const camunda = createCamundaClient(); // The cluster must be in recovery mode before a restore is accepted. Provide // either a list of backup IDs (one per partition) or a time range (`from`/`to`) // that selects the backups to restore, but not both. const change = await camunda.restore({ backupIds: [100, 101], }); console.log(`Cluster change ${change.changeId}:`); for (const group of change.plannedChanges) { console.log(` ${group.physicalTenantId ?? "cluster-wide"}:`); for (const op of group.operations) { const mode = "mode" in op ? op.mode : undefined; console.log(` ${op.operation}${mode ? ` -> ${mode}` : ""}`); } } } ``` #### Operation Id restore #### Tags Recovery --- ### restoreAsClusterAdmin() ```ts restoreAsClusterAdmin(input, options?): CancelablePromise; ``` Restore one or every physical tenant from a backup Restores physical tenants from backups. The restore is described either by a list of backup IDs or by a time range (`from`/`to`) that selects the backups to restore. Restores are only accepted while the targeted physical tenants are in recovery mode; requests are rejected otherwise. The request is validated and acknowledged, but the restore itself is performed asynchronously. If the `physicalTenantId` parameter is provided, only that physical tenant is restored and `overrides` must be omitted. If it is not provided, every physical tenant of the cluster is restored: those named in `overrides` with their own backup selection, all others with the selection at the top level of the request body. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. * #### Parameters ##### input [`restoreAsClusterAdminInput`](../type-aliases/restoreAsClusterAdminInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterRestoreResponse`](../type-aliases/ClusterRestoreResponse.md)\> #### Example **Restore from a backup as cluster admin** ```ts async function restoreAsClusterAdminExample() { const camunda = createCamundaClient(); // The cluster-admin variant can target a specific physical tenant and supports // per-tenant overrides. Omit `physicalTenantId` to restore every physical // tenant. Provide either backup IDs (one per partition) or a time range // (`from`/`to`), but not both. const change = await camunda.restoreAsClusterAdmin({ backupIds: [200, 201], physicalTenantId: "default", dryRun: true, }); console.log(`Cluster change ${change.changeId}:`); for (const group of change.plannedChanges) { console.log(` ${group.physicalTenantId ?? "cluster-wide"}:`); for (const op of group.operations) { const mode = "mode" in op ? op.mode : undefined; console.log(` ${op.operation}${mode ? ` -> ${mode}` : ""}`); } } } ``` #### Operation Id restoreAsClusterAdmin #### Tags Recovery --- ### resumeBatchOperation() ```ts resumeBatchOperation(input, options?): CancelablePromise; ``` Resume Batch operation Resumes a suspended batch operation. This is done asynchronously, the progress can be tracked using the batch operation status endpoint (/batch-operations/{batchOperationKey}). - #### Parameters ##### input ###### batchOperationKey [`BatchOperationKey`](../type-aliases/BatchOperationKey.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Resume a batch operation** ```ts async function resumeBatchOperationExample( batchOperationKey: BatchOperationKey ) { const camunda = createCamundaClient(); await camunda.resumeBatchOperation({ batchOperationKey }); } ``` #### Operation Id resumeBatchOperation #### Tags Batch operation --- ### resumeClusterExporting() ```ts resumeClusterExporting(options?): CancelablePromise; ``` Resume exporting across the whole cluster Resumes exporting on every physical tenant of the cluster in one call, after a pause or soft pause. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. * #### Parameters ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Resume cluster exporting** ```ts async function resumeClusterExportingExample() { const camunda = createCamundaClient(); await camunda.resumeClusterExporting(); } ``` #### Operation Id resumeClusterExporting #### Tags Exporting --- ### resumeExporting() ```ts resumeExporting(options?): CancelablePromise; ``` Resume exporting Resumes exporting on all partitions of the physical tenant after a pause or soft pause. - #### Parameters ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Resume exporting** ```ts async function resumeExportingExample() { const camunda = createCamundaClient(); await camunda.resumeExporting(); } ``` #### Operation Id resumeExporting #### Tags Exporting --- ### resumeProcessInstance() ```ts resumeProcessInstance(input, options?): CancelablePromise; ``` Resume process instance Resumes a suspended process instance, returning it to the ACTIVE state and continuing processing. Only process instances in the SUSPENDED state can be resumed. A child process instance can be resumed independently of its parent or root process instance; resumption does not cascade to or from related instances. - #### Parameters ##### input `object` & `object` ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Resume a process instance** ```ts async function resumeProcessInstanceExample( processInstanceKey: ProcessInstanceKey ) { const camunda = createCamundaClient(); await camunda.resumeProcessInstance({ processInstanceKey }); } ``` #### Operation Id resumeProcessInstance #### Tags Process instance --- ### resumeProcessInstancesBatchOperation() ```ts resumeProcessInstancesBatchOperation(input, options?): CancelablePromise; ``` Resume process instances (batch) Resumes multiple suspended process instances. Any given filter for state or parentProcessInstanceKey is ignored and overridden, as only SUSPENDED process instances can be resumed and resumption does not cascade between parent and child instances, so child instances are resumed independently of their parent or root instance. This is done asynchronously, the progress can be tracked using the batchOperationKey from the response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). - #### Parameters ##### input [`ProcessInstanceResumptionBatchOperationRequest`](../type-aliases/ProcessInstanceResumptionBatchOperationRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`BatchOperationCreatedResult`](../type-aliases/BatchOperationCreatedResult.md)\> #### Example **Resume process instances in batch** ```ts async function resumeProcessInstancesBatchOperationExample( processDefinitionKey: ProcessDefinitionKey ) { const camunda = createCamundaClient(); const result = await camunda.resumeProcessInstancesBatchOperation({ filter: { processDefinitionKey, }, }); console.log(`Batch operation key: ${result.batchOperationKey}`); } ``` #### Operation Id resumeProcessInstancesBatchOperation #### Tags Process instance --- ### searchAgentDefinitions() ```ts searchAgentDefinitions( input, consistencyManagement, options? ): CancelablePromise; ``` Search agent definitions Search for agent definitions based on given criteria. * #### Parameters ##### input [`AgentDefinitionSearchQuery`](../type-aliases/AgentDefinitionSearchQuery.md) ##### consistencyManagement [`searchAgentDefinitionsConsistency`](../type-aliases/searchAgentDefinitionsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`AgentDefinitionSearchQueryResult`](../type-aliases/AgentDefinitionSearchQueryResult.md)\> #### Example **Search agent definitions** ```ts async function searchAgentDefinitionsExample() { const camunda = createCamundaClient(); const result = await camunda.searchAgentDefinitions( { filter: { agentType: { $eq: "AI_AGENT_TASK" } }, sort: [{ field: "name", order: "ASC" }], page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const definition of result.items ?? []) { console.log( `${definition.agentDefinitionKey}: ${definition.name} (${definition.agentType})` ); } console.log(`Total: ${result.page.totalItems}`); } ``` #### Operation Id searchAgentDefinitions #### Tags Agent definition #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchAgentInstanceHistory() ```ts searchAgentInstanceHistory( input, consistencyManagement, options? ): CancelablePromise; ``` Search agent instance history Searches the conversation history of an agent instance. Committed items are returned by default. - #### Parameters ##### input [`searchAgentInstanceHistoryInput`](../type-aliases/searchAgentInstanceHistoryInput.md) ##### consistencyManagement [`searchAgentInstanceHistoryConsistency`](../type-aliases/searchAgentInstanceHistoryConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`AgentInstanceHistorySearchQueryResult`](../type-aliases/AgentInstanceHistorySearchQueryResult.md)\> #### Example **Search agent instance history** ```ts async function searchAgentInstanceHistoryExample( agentInstanceKey: AgentInstanceKey ) { const camunda = createCamundaClient(); const result = await camunda.searchAgentInstanceHistory( { agentInstanceKey, filter: { role: { $eq: "ASSISTANT" } }, sort: [{ field: "producedAt", order: "ASC" }], page: { limit: 20 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const item of result.items ?? []) { console.log(`${item.historyItemKey} (${item.role})`); } console.log(`Total: ${result.page.totalItems}`); } ``` #### Operation Id searchAgentInstanceHistory #### Tags Agent instance #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchAgentInstances() ```ts searchAgentInstances( input, consistencyManagement, options? ): CancelablePromise; ``` Search agent instances Search for agent instances based on given criteria. * #### Parameters ##### input [`AgentInstanceSearchQuery`](../type-aliases/AgentInstanceSearchQuery.md) ##### consistencyManagement [`searchAgentInstancesConsistency`](../type-aliases/searchAgentInstancesConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`AgentInstanceSearchQueryResult`](../type-aliases/AgentInstanceSearchQueryResult.md)\> #### Example **Search agent instances** ```ts async function searchAgentInstancesExample() { const camunda = createCamundaClient(); const result = await camunda.searchAgentInstances( { filter: { status: { $eq: "IDLE" } }, sort: [{ field: "creationDate", order: "DESC" }], page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const instance of result.items ?? []) { console.log(`${instance.agentInstanceKey}: ${instance.status}`); } console.log(`Total: ${result.page.totalItems}`); } ``` #### Operation Id searchAgentInstances #### Tags Agent instance #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchAuditLogs() ```ts searchAuditLogs( input, consistencyManagement, options? ): CancelablePromise; ``` Search audit logs Search for audit logs based on given criteria. * #### Parameters ##### input [`AuditLogSearchQueryRequest`](../type-aliases/AuditLogSearchQueryRequest.md) ##### consistencyManagement [`searchAuditLogsConsistency`](../type-aliases/searchAuditLogsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`AuditLogSearchQueryResult`](../type-aliases/AuditLogSearchQueryResult.md)\> #### Example **Search audit logs** ```ts async function searchAuditLogsExample() { const camunda = createCamundaClient(); const result = await camunda.searchAuditLogs( { page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const log of result.items ?? []) { console.log(`${log.auditLogKey}: ${log.operationType}`); } } ``` #### Operation Id searchAuditLogs #### Tags Audit Log #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchAuthorizations() ```ts searchAuthorizations( input, consistencyManagement, options? ): CancelablePromise; ``` Search authorizations Search for authorizations based on given criteria. * #### Parameters ##### input [`AuthorizationSearchQuery`](../type-aliases/AuthorizationSearchQuery.md) ##### consistencyManagement [`searchAuthorizationsConsistency`](../type-aliases/searchAuthorizationsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`AuthorizationSearchResult`](../type-aliases/AuthorizationSearchResult.md)\> #### Example **Search authorizations** ```ts async function searchAuthorizationsExample() { const camunda = createCamundaClient(); const result = await camunda.searchAuthorizations( { filter: { ownerType: "USER" }, page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const auth of result.items ?? []) { console.log( `${auth.authorizationKey}: ${auth.ownerId} - ${auth.resourceType}` ); } } ``` #### Operation Id searchAuthorizations #### Tags Authorization #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchBatchOperationItems() ```ts searchBatchOperationItems( input, consistencyManagement, options? ): CancelablePromise; ``` Search batch operation items Search for batch operation items based on given criteria. * #### Parameters ##### input [`BatchOperationItemSearchQuery`](../type-aliases/BatchOperationItemSearchQuery.md) ##### consistencyManagement [`searchBatchOperationItemsConsistency`](../type-aliases/searchBatchOperationItemsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`BatchOperationItemSearchQueryResult`](../type-aliases/BatchOperationItemSearchQueryResult.md)\> #### Example **Search batch operation items** ```ts async function searchBatchOperationItemsExample() { const camunda = createCamundaClient(); const result = await camunda.searchBatchOperationItems( { page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const item of result.items ?? []) { console.log(`Item: ${item.itemKey} (${item.state})`); } } ``` #### Operation Id searchBatchOperationItems #### Tags Batch operation #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchBatchOperations() ```ts searchBatchOperations( input, consistencyManagement, options? ): CancelablePromise; ``` Search batch operations Search for batch operations based on given criteria. * #### Parameters ##### input [`BatchOperationSearchQuery`](../type-aliases/BatchOperationSearchQuery.md) ##### consistencyManagement [`searchBatchOperationsConsistency`](../type-aliases/searchBatchOperationsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`BatchOperationSearchQueryResult`](../type-aliases/BatchOperationSearchQueryResult.md)\> #### Example **Search batch operations** ```ts async function searchBatchOperationsExample() { const camunda = createCamundaClient(); const result = await camunda.searchBatchOperations( { page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const batch of result.items ?? []) { console.log( `${batch.batchOperationKey}: ${batch.batchOperationType} (${batch.state})` ); } } ``` #### Operation Id searchBatchOperations #### Tags Batch operation #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchClientsForGroup() ```ts searchClientsForGroup( input, consistencyManagement, options? ): CancelablePromise; ``` Search group clients Search clients assigned to a group. * #### Parameters ##### input [`searchClientsForGroupInput`](../type-aliases/searchClientsForGroupInput.md) ##### consistencyManagement [`searchClientsForGroupConsistency`](../type-aliases/searchClientsForGroupConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`GroupClientSearchResult`](../type-aliases/GroupClientSearchResult.md)\> #### Example **Search clients in a group** ```ts async function searchClientsForGroupExample(groupId: GroupId) { const camunda = createCamundaClient(); const result = await camunda.searchClientsForGroup( { groupId }, { consistency: { waitUpToMs: 5000 } } ); for (const client of result.items ?? []) { console.log(`Client: ${client.clientId}`); } } ``` #### Operation Id searchClientsForGroup #### Tags Group #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchClientsForRole() ```ts searchClientsForRole( input, consistencyManagement, options? ): CancelablePromise; ``` Search role clients Search clients with assigned role. * #### Parameters ##### input [`searchClientsForRoleInput`](../type-aliases/searchClientsForRoleInput.md) ##### consistencyManagement [`searchClientsForRoleConsistency`](../type-aliases/searchClientsForRoleConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`RoleClientSearchResult`](../type-aliases/RoleClientSearchResult.md)\> #### Example **Search clients for a role** ```ts async function searchClientsForRoleExample(roleId: RoleId) { const camunda = createCamundaClient(); const result = await camunda.searchClientsForRole( { roleId }, { consistency: { waitUpToMs: 5000 } } ); for (const client of result.items ?? []) { console.log(`Client: ${client.clientId}`); } } ``` #### Operation Id searchClientsForRole #### Tags Role #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchClientsForTenant() ```ts searchClientsForTenant( input, consistencyManagement, options? ): CancelablePromise; ``` Search clients for tenant Retrieves a filtered and sorted list of clients for a specified tenant. * #### Parameters ##### input [`searchClientsForTenantInput`](../type-aliases/searchClientsForTenantInput.md) ##### consistencyManagement [`searchClientsForTenantConsistency`](../type-aliases/searchClientsForTenantConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`TenantClientSearchResult`](../type-aliases/TenantClientSearchResult.md)\> #### Example **Search clients for a tenant** ```ts async function searchClientsForTenantExample(tenantId: TenantId) { const camunda = createCamundaClient(); const result = await camunda.searchClientsForTenant( { tenantId }, { consistency: { waitUpToMs: 5000 } } ); for (const client of result.items ?? []) { console.log(`Client: ${client.clientId}`); } } ``` #### Operation Id searchClientsForTenant #### Tags Tenant #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchClusterVariables() ```ts searchClusterVariables( input, consistencyManagement, options? ): CancelablePromise; ``` Search for cluster variables based on given criteria. By default, long variable values in the response are truncated. * #### Parameters ##### input [`searchClusterVariablesInput`](../type-aliases/searchClusterVariablesInput.md) ##### consistencyManagement [`searchClusterVariablesConsistency`](../type-aliases/searchClusterVariablesConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterVariableSearchQueryResult`](../type-aliases/ClusterVariableSearchQueryResult.md)\> #### Example **Search cluster variables** ```ts async function searchClusterVariablesExample() { const camunda = createCamundaClient(); const result = await camunda.searchClusterVariables( { page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const variable of result.items ?? []) { console.log(`${variable.name} = ${variable.value}`); } } ``` #### Operation Id searchClusterVariables #### Tags Cluster Variable #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchCorrelatedMessageSubscriptions() ```ts searchCorrelatedMessageSubscriptions( input, consistencyManagement, options? ): CancelablePromise; ``` Search correlated message subscriptions Search correlated message subscriptions based on given criteria. * #### Parameters ##### input [`CorrelatedMessageSubscriptionSearchQuery`](../type-aliases/CorrelatedMessageSubscriptionSearchQuery.md) ##### consistencyManagement [`searchCorrelatedMessageSubscriptionsConsistency`](../type-aliases/searchCorrelatedMessageSubscriptionsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`CorrelatedMessageSubscriptionSearchQueryResult`](../type-aliases/CorrelatedMessageSubscriptionSearchQueryResult.md)\> #### Example **Search correlated message subscriptions** ```ts async function searchCorrelatedMessageSubscriptionsExample() { const camunda = createCamundaClient(); const result = await camunda.searchCorrelatedMessageSubscriptions( { page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const sub of result.items ?? []) { console.log(`Correlated subscription: ${sub.messageName}`); } } ``` #### Operation Id searchCorrelatedMessageSubscriptions #### Tags Message subscription #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchDecisionDefinitions() ```ts searchDecisionDefinitions( input, consistencyManagement, options? ): CancelablePromise; ``` Search decision definitions Search for decision definitions based on given criteria. * #### Parameters ##### input [`DecisionDefinitionSearchQuery`](../type-aliases/DecisionDefinitionSearchQuery.md) ##### consistencyManagement [`searchDecisionDefinitionsConsistency`](../type-aliases/searchDecisionDefinitionsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`DecisionDefinitionSearchQueryResult`](../type-aliases/DecisionDefinitionSearchQueryResult.md)\> #### Example **Search decision definitions** ```ts async function searchDecisionDefinitionsExample( decisionDefinitionId: DecisionDefinitionId ) { const camunda = createCamundaClient(); const result = await camunda.searchDecisionDefinitions( { filter: { decisionDefinitionId }, }, { consistency: { waitUpToMs: 5000 } } ); for (const definition of result.items ?? []) { console.log(`${definition.decisionDefinitionId} v${definition.version}`); } } ``` #### Operation Id searchDecisionDefinitions #### Tags Decision definition #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchDecisionInstances() ```ts searchDecisionInstances( input, consistencyManagement, options? ): CancelablePromise; ``` Search decision instances Search for decision instances based on given criteria. * #### Parameters ##### input [`DecisionInstanceSearchQuery`](../type-aliases/DecisionInstanceSearchQuery.md) ##### consistencyManagement [`searchDecisionInstancesConsistency`](../type-aliases/searchDecisionInstancesConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`DecisionInstanceSearchQueryResult`](../type-aliases/DecisionInstanceSearchQueryResult.md)\> #### Example **Search decision instances** ```ts async function searchDecisionInstancesExample() { const camunda = createCamundaClient(); const result = await camunda.searchDecisionInstances( { page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const instance of result.items ?? []) { console.log( `${instance.decisionEvaluationKey}: ${instance.decisionDefinitionId}` ); } } ``` #### Operation Id searchDecisionInstances #### Tags Decision instance #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchDecisionRequirements() ```ts searchDecisionRequirements( input, consistencyManagement, options? ): CancelablePromise; ``` Search decision requirements Search for decision requirements based on given criteria. * #### Parameters ##### input [`DecisionRequirementsSearchQuery`](../type-aliases/DecisionRequirementsSearchQuery.md) ##### consistencyManagement [`searchDecisionRequirementsConsistency`](../type-aliases/searchDecisionRequirementsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`DecisionRequirementsSearchQueryResult`](../type-aliases/DecisionRequirementsSearchQueryResult.md)\> #### Example **Search decision requirements** ```ts async function searchDecisionRequirementsExample() { const camunda = createCamundaClient(); const result = await camunda.searchDecisionRequirements( { page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const req of result.items ?? []) { console.log( `${req.decisionRequirementsKey}: ${req.decisionRequirementsId}` ); } } ``` #### Operation Id searchDecisionRequirements #### Tags Decision requirements #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchElementInstanceIncidents() ```ts searchElementInstanceIncidents( input, consistencyManagement, options? ): CancelablePromise; ``` Search for incidents of a specific element instance Search for incidents caused by the specified element instance, including incidents of any child instances created from this element instance. Although the `elementInstanceKey` is provided as a path parameter to indicate the root element instance, you may also include an `elementInstanceKey` within the filter object to narrow results to specific child element instances. This is useful, for example, if you want to isolate incidents associated with nested or subordinate elements within the given element instance while excluding incidents directly tied to the root element itself. - #### Parameters ##### input [`searchElementInstanceIncidentsInput`](../type-aliases/searchElementInstanceIncidentsInput.md) ##### consistencyManagement [`searchElementInstanceIncidentsConsistency`](../type-aliases/searchElementInstanceIncidentsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`IncidentSearchQueryResult`](../type-aliases/IncidentSearchQueryResult.md)\> #### Example **Search element instance incidents** ```ts async function searchElementInstanceIncidentsExample( elementInstanceKey: ElementInstanceKey ) { const camunda = createCamundaClient(); const result = await camunda.searchElementInstanceIncidents( { elementInstanceKey }, { consistency: { waitUpToMs: 5000 } } ); for (const incident of result.items ?? []) { console.log(`Incident: ${incident.errorType}`); } } ``` #### Operation Id searchElementInstanceIncidents #### Tags Element instance #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchElementInstances() ```ts searchElementInstances( input, consistencyManagement, options? ): CancelablePromise; ``` Search element instances Search for element instances based on given criteria. * #### Parameters ##### input [`ElementInstanceSearchQuery`](../type-aliases/ElementInstanceSearchQuery.md) ##### consistencyManagement [`searchElementInstancesConsistency`](../type-aliases/searchElementInstancesConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ElementInstanceSearchQueryResult`](../type-aliases/ElementInstanceSearchQueryResult.md)\> #### Example **Search element instances** ```ts async function searchElementInstancesExample( processInstanceKey: ProcessInstanceKey ) { const camunda = createCamundaClient(); const result = await camunda.searchElementInstances( { filter: { processInstanceKey, }, page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const element of result.items ?? []) { console.log(`${element.elementId}: ${element.type} (${element.state})`); } } ``` #### Operation Id searchElementInstances #### Tags Element instance #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchElementInstanceWaitStates() ```ts searchElementInstanceWaitStates( input, consistencyManagement, options? ): CancelablePromise; ``` Search element instance wait states Returns the wait states for element instances matching the given filter. - #### Parameters ##### input [`ElementInstanceWaitStateQuery`](../type-aliases/ElementInstanceWaitStateQuery.md) ##### consistencyManagement [`searchElementInstanceWaitStatesConsistency`](../type-aliases/searchElementInstanceWaitStatesConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ElementInstanceWaitStateQueryResult`](../type-aliases/ElementInstanceWaitStateQueryResult.md)\> #### Example **Search element instance wait states** ```ts async function searchElementInstanceWaitStatesExample( processInstanceKey: ProcessInstanceKey ) { const camunda = createCamundaClient(); const result = await camunda.searchElementInstanceWaitStates( { filter: { processInstanceKey, }, page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const waitState of result.items ?? []) { const { details } = waitState; let description: string; if (details.waitStateType === "JOB") { description = `waiting on job '${details.jobType}'`; } else if (details.waitStateType === "MESSAGE") { description = `waiting for message '${details.messageName}'`; } else { description = `waiting (${details.waitStateType})`; } console.log(`${waitState.elementId}: ${description}`); } } ``` #### Operation Id searchElementInstanceWaitStates #### Tags Element instance #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchGlobalTaskListeners() ```ts searchGlobalTaskListeners( input, consistencyManagement, options? ): CancelablePromise; ``` Search global user task listeners Search for global user task listeners based on given criteria. * #### Parameters ##### input [`GlobalTaskListenerSearchQueryRequest`](../type-aliases/GlobalTaskListenerSearchQueryRequest.md) ##### consistencyManagement [`searchGlobalTaskListenersConsistency`](../type-aliases/searchGlobalTaskListenersConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`GlobalTaskListenerSearchQueryResult`](../type-aliases/GlobalTaskListenerSearchQueryResult.md)\> #### Example **Search global task listeners** ```ts async function searchGlobalTaskListenersExample() { const camunda = createCamundaClient(); const result = await camunda.searchGlobalTaskListeners( { page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const listener of result.items ?? []) { console.log(`${listener.id}: ${listener.type} (${listener.eventTypes})`); } } ``` #### Operation Id searchGlobalTaskListeners #### Tags Global listener #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchGroupIdsForTenant() ```ts searchGroupIdsForTenant( input, consistencyManagement, options? ): CancelablePromise; ``` Search groups for tenant Retrieves a filtered and sorted list of groups for a specified tenant. * #### Parameters ##### input [`searchGroupIdsForTenantInput`](../type-aliases/searchGroupIdsForTenantInput.md) ##### consistencyManagement [`searchGroupIdsForTenantConsistency`](../type-aliases/searchGroupIdsForTenantConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`TenantGroupSearchResult`](../type-aliases/TenantGroupSearchResult.md)\> #### Example **Search groups for a tenant** ```ts async function searchGroupIdsForTenantExample(tenantId: TenantId) { const camunda = createCamundaClient(); const result = await camunda.searchGroupIdsForTenant( { tenantId }, { consistency: { waitUpToMs: 5000 } } ); for (const group of result.items ?? []) { console.log(`Group: ${group.groupId}`); } } ``` #### Operation Id searchGroupIdsForTenant #### Tags Tenant #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchGroups() ```ts searchGroups( input, consistencyManagement, options? ): CancelablePromise; ``` Search groups Search for groups based on given criteria. * #### Parameters ##### input [`GroupSearchQueryRequest`](../type-aliases/GroupSearchQueryRequest.md) ##### consistencyManagement [`searchGroupsConsistency`](../type-aliases/searchGroupsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`GroupSearchQueryResult`](../type-aliases/GroupSearchQueryResult.md)\> #### Example **Search groups** ```ts async function searchGroupsExample() { const camunda = createCamundaClient(); const result = await camunda.searchGroups( { page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const group of result.items ?? []) { console.log(`${group.groupId}: ${group.name}`); } } ``` #### Operation Id searchGroups #### Tags Group #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchGroupsForRole() ```ts searchGroupsForRole( input, consistencyManagement, options? ): CancelablePromise; ``` Search role groups Search groups with assigned role. * #### Parameters ##### input [`searchGroupsForRoleInput`](../type-aliases/searchGroupsForRoleInput.md) ##### consistencyManagement [`searchGroupsForRoleConsistency`](../type-aliases/searchGroupsForRoleConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`RoleGroupSearchResult`](../type-aliases/RoleGroupSearchResult.md)\> #### Example **Search groups for a role** ```ts async function searchGroupsForRoleExample(roleId: RoleId) { const camunda = createCamundaClient(); const result = await camunda.searchGroupsForRole( { roleId }, { consistency: { waitUpToMs: 5000 } } ); for (const group of result.items ?? []) { console.log(`Group: ${group.groupId}`); } } ``` #### Operation Id searchGroupsForRole #### Tags Role #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchIncidents() ```ts searchIncidents( input, consistencyManagement, options? ): CancelablePromise; ``` Search incidents Search for incidents based on given criteria. - #### Parameters ##### input [`IncidentSearchQuery`](../type-aliases/IncidentSearchQuery.md) ##### consistencyManagement [`searchIncidentsConsistency`](../type-aliases/searchIncidentsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`IncidentSearchQueryResult`](../type-aliases/IncidentSearchQueryResult.md)\> #### Example **Search incidents** ```ts async function searchIncidentsExample() { const camunda = createCamundaClient(); const result = await camunda.searchIncidents( { filter: { state: "ACTIVE" }, sort: [{ field: "creationTime", order: "DESC" }], page: { limit: 20 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const incident of result.items ?? []) { console.log( `${incident.incidentKey}: ${incident.errorType} — ${incident.errorMessage}` ); } console.log(`Total active incidents: ${result.page.totalItems}`); } ``` #### Operation Id searchIncidents #### Tags Incident #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchJobs() ```ts searchJobs( input, consistencyManagement, options? ): CancelablePromise; ``` Search jobs Search for jobs based on given criteria. * #### Parameters ##### input [`JobSearchQuery`](../type-aliases/JobSearchQuery.md) ##### consistencyManagement [`searchJobsConsistency`](../type-aliases/searchJobsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`JobSearchQueryResult`](../type-aliases/JobSearchQueryResult.md)\> #### Example **Search jobs** ```ts async function searchJobsExample() { const camunda = createCamundaClient(); const result = await camunda.searchJobs( { filter: { type: "payment-processing", state: "CREATED" }, page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const job of result.items ?? []) { console.log(`Job ${job.jobKey}: ${job.type} (${job.state})`); } } ``` #### Operation Id searchJobs #### Tags Job #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchMappingRule() ```ts searchMappingRule( input, consistencyManagement, options? ): CancelablePromise; ``` Search mapping rules Search for mapping rules based on given criteria. - #### Parameters ##### input [`MappingRuleSearchQueryRequest`](../type-aliases/MappingRuleSearchQueryRequest.md) ##### consistencyManagement [`searchMappingRuleConsistency`](../type-aliases/searchMappingRuleConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`MappingRuleSearchQueryResult`](../type-aliases/MappingRuleSearchQueryResult.md)\> #### Example **Search mapping rules** ```ts async function searchMappingRulesExample() { const camunda = createCamundaClient(); const result = await camunda.searchMappingRule( { page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const rule of result.items ?? []) { console.log(`${rule.mappingRuleId}: ${rule.name}`); } } ``` #### Operation Id searchMappingRule #### Tags Mapping rule #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchMappingRulesForGroup() ```ts searchMappingRulesForGroup( input, consistencyManagement, options? ): CancelablePromise; ``` Search group mapping rules Search mapping rules assigned to a group. * #### Parameters ##### input [`searchMappingRulesForGroupInput`](../type-aliases/searchMappingRulesForGroupInput.md) ##### consistencyManagement [`searchMappingRulesForGroupConsistency`](../type-aliases/searchMappingRulesForGroupConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`GroupMappingRuleSearchResult`](../type-aliases/GroupMappingRuleSearchResult.md)\> #### Example **Search mapping rules for a group** ```ts async function searchMappingRulesForGroupExample(groupId: GroupId) { const camunda = createCamundaClient(); const result = await camunda.searchMappingRulesForGroup( { groupId }, { consistency: { waitUpToMs: 5000 } } ); for (const rule of result.items ?? []) { console.log(`Mapping rule: ${rule.name}`); } } ``` #### Operation Id searchMappingRulesForGroup #### Tags Group #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchMappingRulesForRole() ```ts searchMappingRulesForRole( input, consistencyManagement, options? ): CancelablePromise; ``` Search role mapping rules Search mapping rules with assigned role. * #### Parameters ##### input [`searchMappingRulesForRoleInput`](../type-aliases/searchMappingRulesForRoleInput.md) ##### consistencyManagement [`searchMappingRulesForRoleConsistency`](../type-aliases/searchMappingRulesForRoleConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`RoleMappingRuleSearchResult`](../type-aliases/RoleMappingRuleSearchResult.md)\> #### Example **Search mapping rules for a role** ```ts async function searchMappingRulesForRoleExample(roleId: RoleId) { const camunda = createCamundaClient(); const result = await camunda.searchMappingRulesForRole( { roleId }, { consistency: { waitUpToMs: 5000 } } ); for (const rule of result.items ?? []) { console.log(`Mapping rule: ${rule.name}`); } } ``` #### Operation Id searchMappingRulesForRole #### Tags Role #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchMappingRulesForTenant() ```ts searchMappingRulesForTenant( input, consistencyManagement, options? ): CancelablePromise; ``` Search mapping rules for tenant Retrieves a filtered and sorted list of MappingRules for a specified tenant. * #### Parameters ##### input [`searchMappingRulesForTenantInput`](../type-aliases/searchMappingRulesForTenantInput.md) ##### consistencyManagement [`searchMappingRulesForTenantConsistency`](../type-aliases/searchMappingRulesForTenantConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`TenantMappingRuleSearchResult`](../type-aliases/TenantMappingRuleSearchResult.md)\> #### Example **Search mapping rules for a tenant** ```ts async function searchMappingRulesForTenantExample(tenantId: TenantId) { const camunda = createCamundaClient(); const result = await camunda.searchMappingRulesForTenant( { tenantId }, { consistency: { waitUpToMs: 5000 } } ); for (const rule of result.items ?? []) { console.log(`Mapping rule: ${rule.name}`); } } ``` #### Operation Id searchMappingRulesForTenant #### Tags Tenant #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchMessageSubscriptions() ```ts searchMessageSubscriptions( input, consistencyManagement, options? ): CancelablePromise; ``` Search message subscriptions Search for message subscriptions based on given criteria. By default, both start and intermediate event subscriptions are returned. Use the `messageSubscriptionType` filter to restrict results to a single type. **Version notes:** - Start event subscriptions are only captured for deployments made with 8.10 or later. - The `messageSubscriptionType` field is only populated for data created with Camunda 8.10 or later. For pre-8.10 data, intermediate event entries have no `messageSubscriptionType` value stored. For convenience, the API returns `PROCESS_EVENT` as a default for such search results, though. - Searching for intermediate event subscriptions **including legacy data** can be achieved by filtering for `messageSubscriptionType` not matching `START_EVENT`. * #### Parameters ##### input [`MessageSubscriptionSearchQuery`](../type-aliases/MessageSubscriptionSearchQuery.md) ##### consistencyManagement [`searchMessageSubscriptionsConsistency`](../type-aliases/searchMessageSubscriptionsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`MessageSubscriptionSearchQueryResult`](../type-aliases/MessageSubscriptionSearchQueryResult.md)\> #### Example **Search message subscriptions** ```ts async function searchMessageSubscriptionsExample() { const camunda = createCamundaClient(); const result = await camunda.searchMessageSubscriptions( { page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const sub of result.items ?? []) { console.log(`Subscription: ${sub.messageName}`); } } ``` #### Operation Id searchMessageSubscriptions #### Tags Message subscription #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchOwnAuthorizations() ```ts searchOwnAuthorizations( input, consistencyManagement, options? ): CancelablePromise; ``` Search own authorizations Search for the current authenticated principal's own authorization records — including authorizations granted directly to the user or client, as well as those granted via a group, role, or mapping rule the principal belongs to. * #### Parameters ##### input [`AuthorizationSearchQuery`](../type-aliases/AuthorizationSearchQuery.md) ##### consistencyManagement [`searchOwnAuthorizationsConsistency`](../type-aliases/searchOwnAuthorizationsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`OwnAuthorizationSearchResult`](../type-aliases/OwnAuthorizationSearchResult.md)\> #### Example **Search own authorizations** ```ts async function searchOwnAuthorizationsExample() { const camunda = createCamundaClient(); const result = await camunda.searchOwnAuthorizations( { filter: { resourceType: "PROCESS_DEFINITION" }, page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const auth of result.items ?? []) { console.log(`${auth.resourceId}: ${auth.permissionTypes?.join(", ")}`); } } ``` #### Operation Id searchOwnAuthorizations #### Tags Authentication #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchProcessDefinitions() ```ts searchProcessDefinitions( input, consistencyManagement, options? ): CancelablePromise; ``` Search process definitions Search for process definitions based on given criteria. * #### Parameters ##### input [`ProcessDefinitionSearchQuery`](../type-aliases/ProcessDefinitionSearchQuery.md) ##### consistencyManagement [`searchProcessDefinitionsConsistency`](../type-aliases/searchProcessDefinitionsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ProcessDefinitionSearchQueryResult`](../type-aliases/ProcessDefinitionSearchQueryResult.md)\> #### Example **Search process definitions** ```ts async function searchProcessDefinitionsExample() { const camunda = createCamundaClient(); const result = await camunda.searchProcessDefinitions( { page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const def of result.items ?? []) { console.log( `${def.processDefinitionKey}: ${def.processDefinitionId} v${def.version}` ); } } ``` #### Operation Id searchProcessDefinitions #### Tags Process definition #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchProcessDefinitionVariableNames() ```ts searchProcessDefinitionVariableNames( input, consistencyManagement, options? ): CancelablePromise; ``` Search process definition variable names Search for distinct variable names defined on a process definition, optionally narrowed by the name filter. * #### Parameters ##### input [`searchProcessDefinitionVariableNamesInput`](../type-aliases/searchProcessDefinitionVariableNamesInput.md) ##### consistencyManagement [`searchProcessDefinitionVariableNamesConsistency`](../type-aliases/searchProcessDefinitionVariableNamesConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ProcessDefinitionVariableNameSearchQueryResult`](../type-aliases/ProcessDefinitionVariableNameSearchQueryResult.md)\> #### Example **Search process definition variable names** ```ts async function searchProcessDefinitionVariableNamesExample( processDefinitionKey: ProcessDefinitionKey ) { const camunda = createCamundaClient(); const result = await camunda.searchProcessDefinitionVariableNames( { processDefinitionKey }, { consistency: { waitUpToMs: 5000 } } ); for (const variable of result.items ?? []) { console.log(`Variable name: ${variable.name}`); } } ``` #### Operation Id searchProcessDefinitionVariableNames #### Tags Process definition #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchProcessInstanceIncidents() ```ts searchProcessInstanceIncidents( input, consistencyManagement, options? ): CancelablePromise; ``` Search related incidents Search for incidents caused by the process instance or any of its called process or decision instances. Although the `processInstanceKey` is provided as a path parameter to indicate the root process instance, you may also include a `processInstanceKey` within the filter object to narrow results to specific child process instances. This is useful, for example, if you want to isolate incidents associated with subprocesses or called processes under the root instance while excluding incidents directly tied to the root. - #### Parameters ##### input [`searchProcessInstanceIncidentsInput`](../type-aliases/searchProcessInstanceIncidentsInput.md) ##### consistencyManagement [`searchProcessInstanceIncidentsConsistency`](../type-aliases/searchProcessInstanceIncidentsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`IncidentSearchQueryResult`](../type-aliases/IncidentSearchQueryResult.md)\> #### Example **Search process instance incidents** ```ts async function searchProcessInstanceIncidentsExample( processInstanceKey: ProcessInstanceKey ) { const camunda = createCamundaClient(); const result = await camunda.searchProcessInstanceIncidents( { processInstanceKey, }, { consistency: { waitUpToMs: 5000 } } ); for (const incident of result.items ?? []) { console.log(`Incident: ${incident.errorType} - ${incident.errorMessage}`); } } ``` #### Operation Id searchProcessInstanceIncidents #### Tags Process instance #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchProcessInstances() ```ts searchProcessInstances( input, consistencyManagement, options? ): CancelablePromise; ``` Search process instances Search for process instances based on given criteria. * #### Parameters ##### input [`ProcessInstanceSearchQuery`](../type-aliases/ProcessInstanceSearchQuery.md) ##### consistencyManagement [`searchProcessInstancesConsistency`](../type-aliases/searchProcessInstancesConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ProcessInstanceSearchQueryResult`](../type-aliases/ProcessInstanceSearchQueryResult.md)\> #### Example **Search process instances** ```ts async function searchProcessInstancesExample( processDefinitionId: ProcessDefinitionId ) { const camunda = createCamundaClient(); const result = await camunda.searchProcessInstances( { filter: { processDefinitionId }, sort: [{ field: "startDate", order: "DESC" }], page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const instance of result.items ?? []) { console.log(`${instance.processInstanceKey}: ${instance.state}`); } console.log(`Total: ${result.page.totalItems}`); } ``` #### Operation Id searchProcessInstances #### Tags Process instance #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchResources() ```ts searchResources( input, consistencyManagement, options? ): CancelablePromise; ``` Search resources Search for deployed resources based on given criteria. :::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 search APIs. ::: - #### Parameters ##### input [`ResourceSearchQuery`](../type-aliases/ResourceSearchQuery.md) ##### consistencyManagement [`searchResourcesConsistency`](../type-aliases/searchResourcesConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ResourceSearchQueryResult`](../type-aliases/ResourceSearchQueryResult.md)\> #### Example **Search resources** ```ts async function searchResourcesExample() { const camunda = createCamundaClient(); const result = await camunda.searchResources( { page: { limit: 10 } }, { consistency: { waitUpToMs: 5000 } } ); for (const resource of result.items ?? []) { console.log(`Resource: ${resource.resourceName}`); } } ``` #### Operation Id searchResources #### Tags Resource #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchRoles() ```ts searchRoles( input, consistencyManagement, options? ): CancelablePromise; ``` Search roles Search for roles based on given criteria. * #### Parameters ##### input [`RoleSearchQueryRequest`](../type-aliases/RoleSearchQueryRequest.md) ##### consistencyManagement [`searchRolesConsistency`](../type-aliases/searchRolesConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`RoleSearchQueryResult`](../type-aliases/RoleSearchQueryResult.md)\> #### Example **Search roles** ```ts async function searchRolesExample() { const camunda = createCamundaClient(); const result = await camunda.searchRoles( { page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const role of result.items ?? []) { console.log(`${role.roleId}: ${role.name}`); } } ``` #### Operation Id searchRoles #### Tags Role #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchRolesForGroup() ```ts searchRolesForGroup( input, consistencyManagement, options? ): CancelablePromise; ``` Search group roles Search roles assigned to a group. * #### Parameters ##### input [`searchRolesForGroupInput`](../type-aliases/searchRolesForGroupInput.md) ##### consistencyManagement [`searchRolesForGroupConsistency`](../type-aliases/searchRolesForGroupConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`GroupRoleSearchResult`](../type-aliases/GroupRoleSearchResult.md)\> #### Example **Search roles for a group** ```ts async function searchRolesForGroupExample(groupId: GroupId) { const camunda = createCamundaClient(); const result = await camunda.searchRolesForGroup( { groupId }, { consistency: { waitUpToMs: 5000 } } ); for (const role of result.items ?? []) { console.log(`Role: ${role.name}`); } } ``` #### Operation Id searchRolesForGroup #### Tags Group #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchRolesForTenant() ```ts searchRolesForTenant( input, consistencyManagement, options? ): CancelablePromise; ``` Search roles for tenant Retrieves a filtered and sorted list of roles for a specified tenant. * #### Parameters ##### input [`searchRolesForTenantInput`](../type-aliases/searchRolesForTenantInput.md) ##### consistencyManagement [`searchRolesForTenantConsistency`](../type-aliases/searchRolesForTenantConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`TenantRoleSearchResult`](../type-aliases/TenantRoleSearchResult.md)\> #### Example **Search roles for a tenant** ```ts async function searchRolesForTenantExample(tenantId: TenantId) { const camunda = createCamundaClient(); const result = await camunda.searchRolesForTenant( { tenantId }, { consistency: { waitUpToMs: 5000 } } ); for (const role of result.items ?? []) { console.log(`Role: ${role.name}`); } } ``` #### Operation Id searchRolesForTenant #### Tags Tenant #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchTenants() ```ts searchTenants( input, consistencyManagement, options? ): CancelablePromise; ``` Search tenants Retrieves a filtered and sorted list of tenants. * #### Parameters ##### input [`TenantSearchQueryRequest`](../type-aliases/TenantSearchQueryRequest.md) ##### consistencyManagement [`searchTenantsConsistency`](../type-aliases/searchTenantsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`TenantSearchQueryResult`](../type-aliases/TenantSearchQueryResult.md)\> #### Example **Search tenants** ```ts async function searchTenantsExample() { const camunda = createCamundaClient(); const result = await camunda.searchTenants( { page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const tenant of result.items ?? []) { console.log(`${tenant.tenantId}: ${tenant.name}`); } } ``` #### Operation Id searchTenants #### Tags Tenant #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchUsers() ```ts searchUsers( input, consistencyManagement, options? ): CancelablePromise; ``` Search users Search for users based on given criteria. * #### Parameters ##### input [`UserSearchQueryRequest`](../type-aliases/UserSearchQueryRequest.md) ##### consistencyManagement [`searchUsersConsistency`](../type-aliases/searchUsersConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`UserSearchResult`](../type-aliases/UserSearchResult.md)\> #### Example **Search users** ```ts async function searchUsersExample() { const camunda = createCamundaClient(); const result = await camunda.searchUsers( { filter: {}, page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const user of result.items ?? []) { console.log(`${user.username}: ${user.name}`); } } ``` #### Operation Id searchUsers #### Tags User #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchUsersForGroup() ```ts searchUsersForGroup( input, consistencyManagement, options? ): CancelablePromise; ``` Search group users Search users assigned to a group. * #### Parameters ##### input [`searchUsersForGroupInput`](../type-aliases/searchUsersForGroupInput.md) ##### consistencyManagement [`searchUsersForGroupConsistency`](../type-aliases/searchUsersForGroupConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`GroupUserSearchResult`](../type-aliases/GroupUserSearchResult.md)\> #### Example **Search users in a group** ```ts async function searchUsersForGroupExample(groupId: GroupId) { const camunda = createCamundaClient(); const result = await camunda.searchUsersForGroup( { groupId }, { consistency: { waitUpToMs: 5000 } } ); for (const user of result.items ?? []) { console.log(`Member: ${user.username}`); } } ``` #### Operation Id searchUsersForGroup #### Tags Group #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchUsersForRole() ```ts searchUsersForRole( input, consistencyManagement, options? ): CancelablePromise; ``` Search role users Search users with assigned role. * #### Parameters ##### input [`searchUsersForRoleInput`](../type-aliases/searchUsersForRoleInput.md) ##### consistencyManagement [`searchUsersForRoleConsistency`](../type-aliases/searchUsersForRoleConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`RoleUserSearchResult`](../type-aliases/RoleUserSearchResult.md)\> #### Example **Search users for a role** ```ts async function searchUsersForRoleExample(roleId: RoleId) { const camunda = createCamundaClient(); const result = await camunda.searchUsersForRole( { roleId }, { consistency: { waitUpToMs: 5000 } } ); for (const user of result.items ?? []) { console.log(`User: ${user.username}`); } } ``` #### Operation Id searchUsersForRole #### Tags Role #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchUsersForTenant() ```ts searchUsersForTenant( input, consistencyManagement, options? ): CancelablePromise; ``` Search users for tenant Retrieves a filtered and sorted list of users for a specified tenant. * #### Parameters ##### input [`searchUsersForTenantInput`](../type-aliases/searchUsersForTenantInput.md) ##### consistencyManagement [`searchUsersForTenantConsistency`](../type-aliases/searchUsersForTenantConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`TenantUserSearchResult`](../type-aliases/TenantUserSearchResult.md)\> #### Example **Search users for a tenant** ```ts async function searchUsersForTenantExample(tenantId: TenantId) { const camunda = createCamundaClient(); const result = await camunda.searchUsersForTenant( { tenantId }, { consistency: { waitUpToMs: 5000 } } ); for (const user of result.items ?? []) { console.log(`Tenant member: ${user.username}`); } } ``` #### Operation Id searchUsersForTenant #### Tags Tenant #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchUserTaskAuditLogs() ```ts searchUserTaskAuditLogs( input, consistencyManagement, options? ): CancelablePromise; ``` Search user task audit logs Search for user task audit logs based on given criteria. * #### Parameters ##### input [`searchUserTaskAuditLogsInput`](../type-aliases/searchUserTaskAuditLogsInput.md) ##### consistencyManagement [`searchUserTaskAuditLogsConsistency`](../type-aliases/searchUserTaskAuditLogsConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`AuditLogSearchQueryResult`](../type-aliases/AuditLogSearchQueryResult.md)\> #### Example **Search user task audit logs** ```ts async function searchUserTaskAuditLogsExample(userTaskKey: UserTaskKey) { const camunda = createCamundaClient(); const result = await camunda.searchUserTaskAuditLogs( { userTaskKey }, { consistency: { waitUpToMs: 5000 } } ); for (const log of result.items ?? []) { console.log(`Audit: ${log.operationType} at ${log.timestamp}`); } } ``` #### Operation Id searchUserTaskAuditLogs #### Tags User task #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchUserTaskEffectiveVariables() ```ts searchUserTaskEffectiveVariables( input, consistencyManagement, options? ): CancelablePromise; ``` Search user task effective variables Search for the effective variables of a user task. This endpoint returns deduplicated variables where each variable name appears at most once. When the same variable name exists at multiple scope levels in the scope hierarchy, the value from the innermost scope (closest to the user task) takes precedence. This is useful for retrieving the actual runtime state of variables as seen by the user task. By default, long variable values in the response are truncated. - #### Parameters ##### input [`searchUserTaskEffectiveVariablesInput`](../type-aliases/searchUserTaskEffectiveVariablesInput.md) ##### consistencyManagement [`searchUserTaskEffectiveVariablesConsistency`](../type-aliases/searchUserTaskEffectiveVariablesConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`VariableSearchQueryResult`](../type-aliases/VariableSearchQueryResult.md)\> #### Example **Search user task effective variables** ```ts async function searchUserTaskEffectiveVariablesExample( userTaskKey: UserTaskKey ) { const camunda = createCamundaClient(); const result = await camunda.searchUserTaskEffectiveVariables( { userTaskKey }, { consistency: { waitUpToMs: 5000 } } ); for (const variable of result.items ?? []) { console.log(`${variable.name} = ${variable.value}`); } } ``` #### Operation Id searchUserTaskEffectiveVariables #### Tags User task #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchUserTasks() ```ts searchUserTasks( input, consistencyManagement, options? ): CancelablePromise; ``` Search user tasks Search for user tasks based on given criteria. * #### Parameters ##### input [`UserTaskSearchQuery`](../type-aliases/UserTaskSearchQuery.md) ##### consistencyManagement [`searchUserTasksConsistency`](../type-aliases/searchUserTasksConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`UserTaskSearchQueryResult`](../type-aliases/UserTaskSearchQueryResult.md)\> #### Example **Search user tasks** ```ts async function searchUserTasksExample() { const camunda = createCamundaClient(); const result = await camunda.searchUserTasks( { filter: { assignee: "alice", state: "CREATED" }, sort: [{ field: "creationDate", order: "DESC" }], page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const task of result.items ?? []) { console.log(`${task.userTaskKey}: ${task.name} (${task.state})`); } } ``` #### Operation Id searchUserTasks #### Tags User task #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchUserTaskVariables() ```ts searchUserTaskVariables( input, consistencyManagement, options? ): CancelablePromise; ``` Search user task variables Search for user task variables based on given criteria. This endpoint returns all variable documents visible from the user task's scope, including variables from parent scopes in the scope hierarchy. If the same variable name exists at multiple scope levels, each scope's variable is returned as a separate result. Use the `/user-tasks/{userTaskKey}/effective-variables/search` endpoint to get deduplicated variables where the innermost scope takes precedence. By default, long variable values in the response are truncated. - #### Parameters ##### input [`searchUserTaskVariablesInput`](../type-aliases/searchUserTaskVariablesInput.md) ##### consistencyManagement [`searchUserTaskVariablesConsistency`](../type-aliases/searchUserTaskVariablesConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`VariableSearchQueryResult`](../type-aliases/VariableSearchQueryResult.md)\> #### Example **Search user task variables** ```ts async function searchUserTaskVariablesExample(userTaskKey: UserTaskKey) { const camunda = createCamundaClient(); const result = await camunda.searchUserTaskVariables( { userTaskKey }, { consistency: { waitUpToMs: 5000 } } ); for (const variable of result.items ?? []) { console.log(`${variable.name} = ${variable.value}`); } } ``` #### Operation Id searchUserTaskVariables #### Tags User task #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchVariables() ```ts searchVariables( input, consistencyManagement, options? ): CancelablePromise; ``` Search variables Search for variables based on given criteria. This endpoint returns variables that exist directly at the specified scopes - it does not include variables from parent scopes that would be visible through the scope hierarchy. Variables can be process-level (scoped to the process instance) or local (scoped to specific BPMN elements like tasks, subprocesses, etc.). By default, long variable values in the response are truncated. * #### Parameters ##### input [`searchVariablesInput`](../type-aliases/searchVariablesInput.md) ##### consistencyManagement [`searchVariablesConsistency`](../type-aliases/searchVariablesConsistency.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`VariableSearchQueryResult`](../type-aliases/VariableSearchQueryResult.md)\> #### Example **Search variables** ```ts async function searchVariablesExample(processInstanceKey: ProcessInstanceKey) { const camunda = createCamundaClient(); const result = await camunda.searchVariables( { filter: { processInstanceKey, }, page: { limit: 10 }, }, { consistency: { waitUpToMs: 5000 } } ); for (const variable of result.items ?? []) { console.log(`${variable.name} = ${variable.value}`); } } ``` #### Operation Id searchVariables #### Tags Variable #### Consistency eventual - this endpoint is backed by data that is eventually consistent with the system state. --- ### searchVariablesAsDto() ```ts searchVariablesAsDto(schema, options): CancelablePromise>; ``` Search for process variables and bind them to a Zod schema (the DTO). The schema's keys are the exact variable names to fetch; its shape drives validation. Only those declared variables are queried (via a `name $in [...]` filter), so memory stays bound by the DTO shape rather than the total number of variables on the instance. Results are paged internally until every declared variable is found or the result set is exhausted. Returns a [VariableMap](VariableMap.md) offering lenient access (`has` / `get`) and a strict `validate()` that parses the collected values against the schema — returning a fully-typed object or throwing a `ZodError` when a required variable is missing or malformed. #### Type Parameters ##### TSchema `TSchema` _extends_ [`AnyVariableSchema`](../type-aliases/AnyVariableSchema.md) #### Parameters ##### schema `TSchema` A Zod object schema declaring the variables to fetch. ##### options Query scope. `processInstanceKey` is required; `scopeKey` narrows to a single element-instance scope, `tenantId` filters by tenant, and `pageSize` tunes the page limit. `consistency` controls eventual-consistency tolerance for the underlying `searchVariables` calls: it defaults to `{ waitUpToMs: 0 }` (no waiting), but a non-zero `waitUpToMs` makes the paging calls poll until the data is consistent, avoiding intermittent missing variables / `ZodError` on a freshly-updated instance. ###### consistency? \{ `pollIntervalMs?`: `number`; `waitUpToMs`: `number`; \} ###### consistency.pollIntervalMs? `number` ###### consistency.waitUpToMs `number` ###### pageSize? `number` ###### processInstanceKey [`ProcessInstanceKey`](../type-aliases/ProcessInstanceKey.md) ###### scopeKey? [`ScopeKey`](../type-aliases/ScopeKey.md) ###### tenantId? [`TenantId`](../type-aliases/TenantId.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`VariableMap`](VariableMap.md)\<`TSchema`\>\> #### Throws when a declared variable is found at more than one scope and no `scopeKey` was provided to disambiguate. #### Throws when a variable's value is not valid JSON. #### Example ```ts const OrderVariables = z.object({ orderId: z.string(), amount: z.number().optional(), }); const map = await client.searchVariablesAsDto(OrderVariables, { processInstanceKey, }); if (map.has("amount")) console.log(map.get("amount")); const order = map.validate(); // { orderId: string; amount?: number } ``` --- ### stopAllWorkers() ```ts stopAllWorkers(): void; ``` Stop all registered job workers (best-effort) and terminate the shared thread pool. #### Returns `void` --- ### suspendBatchOperation() ```ts suspendBatchOperation(input, options?): CancelablePromise; ``` Suspend Batch operation Suspends a running batch operation. This is done asynchronously, the progress can be tracked using the batch operation status endpoint (/batch-operations/{batchOperationKey}). - #### Parameters ##### input ###### batchOperationKey [`BatchOperationKey`](../type-aliases/BatchOperationKey.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Suspend a batch operation** ```ts async function suspendBatchOperationExample( batchOperationKey: BatchOperationKey ) { const camunda = createCamundaClient(); await camunda.suspendBatchOperation({ batchOperationKey }); } ``` #### Operation Id suspendBatchOperation #### Tags Batch operation --- ### suspendProcessInstance() ```ts suspendProcessInstance(input, options?): CancelablePromise; ``` Suspend process instance Suspends a running process instance, pausing further processing until it is resumed. Only process instances in the ACTIVE state can be suspended. A child process instance can be suspended independently of its parent or root process instance; suspension does not cascade to or from related instances. - #### Parameters ##### input `object` & `object` ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Suspend a process instance** ```ts async function suspendProcessInstanceExample( processInstanceKey: ProcessInstanceKey ) { const camunda = createCamundaClient(); await camunda.suspendProcessInstance({ processInstanceKey }); } ``` #### Operation Id suspendProcessInstance #### Tags Process instance --- ### suspendProcessInstancesBatchOperation() ```ts suspendProcessInstancesBatchOperation(input, options?): CancelablePromise; ``` Suspend process instances (batch) Suspends multiple running process instances. Any given filter for state or parentProcessInstanceKey is ignored and overridden, as only ACTIVE process instances can be suspended and suspension does not cascade between parent and child instances, so child instances are suspended independently of their parent or root instance. This is done asynchronously, the progress can be tracked using the batchOperationKey from the response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). - #### Parameters ##### input [`ProcessInstanceSuspensionBatchOperationRequest`](../type-aliases/ProcessInstanceSuspensionBatchOperationRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`BatchOperationCreatedResult`](../type-aliases/BatchOperationCreatedResult.md)\> #### Example **Suspend process instances in batch** ```ts async function suspendProcessInstancesBatchOperationExample( processDefinitionKey: ProcessDefinitionKey ) { const camunda = createCamundaClient(); const result = await camunda.suspendProcessInstancesBatchOperation({ filter: { processDefinitionKey, }, }); console.log(`Batch operation key: ${result.batchOperationKey}`); } ``` #### Operation Id suspendProcessInstancesBatchOperation #### Tags Process instance --- ### syncRuntimeBackupState() ```ts syncRuntimeBackupState(options?): CancelablePromise; ``` Force-write runtime backup state Force-writes the checkpoint and backup metadata of every partition of the physical tenant to the backup store, independent of any backup being taken or confirmed, and returns the updated state. - #### Parameters ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`RuntimeBackupState`](../type-aliases/RuntimeBackupState.md)\> #### Example **Force-write the runtime backup state** ```ts async function syncRuntimeBackupStateExample() { const camunda = createCamundaClient(); // Force-writes checkpoint and backup metadata of every partition to the backup // store, independent of any backup being taken, and returns the updated state. const state = await camunda.syncRuntimeBackupState(); console.log(`Synced ${state.backupStates.length} partition backup states`); } ``` #### Operation Id syncRuntimeBackupState #### Tags Backup --- ### syncRuntimeBackupStateAsClusterAdmin() ```ts syncRuntimeBackupStateAsClusterAdmin(input, options?): CancelablePromise; ``` Force-write runtime backup state across physical tenants Force-writes the checkpoint and backup metadata of every partition of every physical tenant of the cluster, or of the one named by `physicalTenantId`, to that tenant's backup store, independent of any backup being taken or confirmed, and returns the updated state per physical tenant. The request is all-or-nothing: a physical tenant whose metadata cannot be written fails the whole request, and the writes that already succeeded on other tenants are not undone. The operation is idempotent, so retrying the same call is the correct remedy. Narrow the request with `physicalTenantId` to write the tenants that can still be reached. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Use `POST /v2/backups/runtime/state/sync` to act as a single physical tenant. * #### Parameters ##### input [`syncRuntimeBackupStateAsClusterAdminInput`](../type-aliases/syncRuntimeBackupStateAsClusterAdminInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterRuntimeBackupState`](../type-aliases/ClusterRuntimeBackupState.md)\> #### Example **Force-write the runtime backup state (cluster admin)** ```ts async function syncRuntimeBackupStateAsClusterAdminExample() { const camunda = createCamundaClient(); // Force-writes checkpoint and backup metadata of every partition to the backup // store on every targeted physical tenant, independent of any backup being // taken, and returns the updated per-tenant state. const clusterState = await camunda.syncRuntimeBackupStateAsClusterAdmin({}); console.log(`Synced ${clusterState.physicalTenants.length} physical tenants`); } ``` #### Operation Id syncRuntimeBackupStateAsClusterAdmin #### Tags Backup --- ### takeHistoryBackup() ```ts takeHistoryBackup(input, options?): CancelablePromise; ``` Take a history backup Triggers a backup of the physical tenant's history, by scheduling a snapshot of every secondary storage index it owns. Unlike runtime backups, history backups have no generated-id mode: `backupId` is always required. Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. - #### Parameters ##### input [`TakeHistoryBackupRequest`](../type-aliases/TakeHistoryBackupRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`TakeHistoryBackupResponse`](../type-aliases/TakeHistoryBackupResponse.md)\> #### Example **Take a history backup** ```ts async function takeHistoryBackupExample() { const camunda = createCamundaClient(); // Backups are logically ordered by id, so each successive backup must use a // higher id than the previous one. const backup = await camunda.takeHistoryBackup({ backupId: 100 }); console.log(`Scheduled history backup ${backup.backupId}`); for (const snapshot of backup.scheduledSnapshots) { console.log(` ${snapshot}`); } } ``` #### Operation Id takeHistoryBackup #### Tags Backup --- ### takeHistoryBackupAsClusterAdmin() ```ts takeHistoryBackupAsClusterAdmin(input, options?): CancelablePromise; ``` Take a history backup on one or every physical tenant Triggers a history backup on every physical tenant of the cluster, or on the one named by `physicalTenantId`. Every targeted tenant uses the same caller-supplied `backupId`, but the backups are independent: they are neither coordinated nor rolled back together. The request is all-or-nothing: the `backupId` is checked on every targeted tenant before any snapshot is scheduled, so a tenant that already holds this id, or that cannot be reached, fails the whole request and no backup is started anywhere. There is no aggregated cluster-level state in the response. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Only available on clusters whose secondary storage is Elasticsearch or OpenSearch. Use `POST /v2/backups/history` to act as a single physical tenant. * #### Parameters ##### input [`takeHistoryBackupAsClusterAdminInput`](../type-aliases/takeHistoryBackupAsClusterAdminInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterTakeHistoryBackupResponse`](../type-aliases/ClusterTakeHistoryBackupResponse.md)\> #### Example **Take a history backup (cluster admin)** ```ts async function takeHistoryBackupAsClusterAdminExample() { const camunda = createCamundaClient(); // Cluster-admin variant: fans the backup out to every physical tenant of the // cluster (or a single one when `physicalTenantId` is given). Requires a // separate cluster-admin security chain — Orchestration Cluster user // credentials are NOT accepted. Each backup must use a higher id than the last. const backup = await camunda.takeHistoryBackupAsClusterAdmin({ backupId: 100, }); console.log(`Scheduled cluster history backup ${backup.backupId}`); for (const tenant of backup.physicalTenants) { console.log( ` [${tenant.physicalTenantId}] scheduled ${tenant.scheduledSnapshots.length} snapshots` ); } } ``` #### Operation Id takeHistoryBackupAsClusterAdmin #### Tags Backup --- ### takeRuntimeBackup() ```ts takeRuntimeBackup(input, options?): CancelablePromise; ``` Take a runtime backup Triggers a backup of runtime data on all partitions of the physical tenant. The `backupId` must be omitted if continuous backups and/or a backup or checkpoint schedule is enabled for the physical tenant, as the id is generated automatically. Otherwise, `backupId` is required. - #### Parameters ##### input [`TakeRuntimeBackupRequest`](../type-aliases/TakeRuntimeBackupRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`TakeRuntimeBackupResponse`](../type-aliases/TakeRuntimeBackupResponse.md)\> #### Example **Take a runtime backup** ```ts async function takeRuntimeBackupExample() { const camunda = createCamundaClient(); // Omit `backupId` when continuous backups or a backup/checkpoint schedule is // enabled for the physical tenant — the id is then generated by the cluster. // Otherwise `backupId` is required and must be higher than any existing one. const backup = await camunda.takeRuntimeBackup({ backupId: 100 }); console.log(`Scheduled backup ${backup.backupId}`); } ``` #### Operation Id takeRuntimeBackup #### Tags Backup --- ### takeRuntimeBackupAsClusterAdmin() ```ts takeRuntimeBackupAsClusterAdmin(input, options?): CancelablePromise; ``` Take a runtime backup on one or every physical tenant Triggers a runtime backup on every physical tenant of the cluster, or on the one named by `physicalTenantId`. A cluster-wide backup is a set of independent per-tenant backups, not an atomic snapshot of the cluster: they are neither coordinated nor rolled back together, and each tenant stores its own, so the same `backupId` can be used for all of them. Every targeted physical tenant must be in the same backup-id mode. `backupId` must be omitted when every targeted tenant generates its own ids (because continuous backups and/or a backup or checkpoint schedule is enabled for it), and is required when none of them does. A cluster whose targeted tenants mix the two modes is rejected with 400 and has to be driven one tenant at a time through `POST /v2/backups/runtime`. In generated-id mode each tenant generates its own id, so the response reports an id per physical tenant rather than one for the cluster. The trigger is all-or-error, and never silent about a partial trigger: if any targeted tenant cannot be triggered the response carries an error status, but its body still lists every targeted tenant — which ones were triggered, under which `backupId` to monitor or delete them, and why the others failed. Nothing is rolled back, so the backups that were triggered keep running and have to be deleted explicitly. A request rejected before any tenant was triggered answers with a problem detail instead, and nothing is running. Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. Use `POST /v2/backups/runtime` to act as a single physical tenant. * #### Parameters ##### input [`takeRuntimeBackupAsClusterAdminInput`](../type-aliases/takeRuntimeBackupAsClusterAdminInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterTakeRuntimeBackupResponse`](../type-aliases/ClusterTakeRuntimeBackupResponse.md)\> #### Example **Take a runtime backup (cluster admin)** ```ts async function takeRuntimeBackupAsClusterAdminExample() { const camunda = createCamundaClient(); // Cluster-admin variant: triggers a runtime backup on every physical tenant of // the cluster (or a single one when `physicalTenantId` is given). Requires the // separate cluster-admin security chain — Orchestration Cluster user // credentials are NOT accepted. Passing an explicit `backupId` is manual-id // mode: every targeted tenant must share that id (omit it for generated-id // mode, where each tenant generates its own). Either way the response lists the // outcome per physical tenant rather than cluster-wide. const backup = await camunda.takeRuntimeBackupAsClusterAdmin({ backupId: 100, }); for (const tenant of backup.physicalTenants) { console.log( `[${tenant.physicalTenantId}] ${tenant.outcome} (backupId ${tenant.backupId})` ); } } ``` #### Operation Id takeRuntimeBackupAsClusterAdmin #### Tags Backup --- ### throwJobError() ```ts throwJobError(input, options?): CancelablePromise; ``` Throw error for job Reports a business error (i.e. non-technical) that occurs while processing a job. - #### Parameters ##### input [`throwJobErrorInput`](../type-aliases/throwJobErrorInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Throw a job error** ```ts async function throwJobErrorExample(jobKey: JobKey) { const camunda = createCamundaClient(); await camunda.throwJobError({ jobKey, errorCode: "PAYMENT_FAILED", errorMessage: "Payment provider returned error", }); } ``` #### Operation Id throwJobError #### Tags Job --- ### triggerClusterRebalance() ```ts triggerClusterRebalance(input, options?): CancelablePromise; ``` Trigger a cluster-wide leadership rebalance Transfers leadership of every partition that is not led by its highest-priority replica towards that replica, one partition at a time. Returns as soon as the rebalance has been accepted (poll `GET /cluster/v2/rebalance` to monitor progress). Each rebalance can specify overrides for the configured rebalance settings (e.g. maximum replication lag to allow). An absent request body means "use the configured settings". Requires the cluster-admin security chain. Although this operation lists `bearerAuth` / `basicAuth` like the rest of the Orchestration Cluster API, it does not accept an Orchestration Cluster user's credentials — only the separate cluster-admin credentials are valid here. * #### Parameters ##### input [`triggerClusterRebalanceInput`](../type-aliases/triggerClusterRebalanceInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterBalanceResponse`](../type-aliases/ClusterBalanceResponse.md)\> #### Example **Trigger a cluster-wide leadership rebalance** ```ts async function triggerClusterRebalanceExample() { const camunda = createCamundaClient(); const balance = await camunda.triggerClusterRebalance({ replicationLagThreshold: 10_000_000, maxTransferAttempts: 3, }); console.log(`Cluster balance state: ${balance.state}`); if (balance.runningRebalance) { console.log( `Rebalance started: id=${balance.runningRebalance.rebalanceId}` ); } } ``` #### Operation Id triggerClusterRebalance #### Tags Cluster --- ### unassignClientFromGroup() ```ts unassignClientFromGroup(input, options?): CancelablePromise; ``` Unassign a client from a group Unassigns a client from a group. The client is removed as a group member, with associated authorizations, roles, and tenant assignments no longer applied. - #### Parameters ##### input [`unassignClientFromGroupInput`](../type-aliases/unassignClientFromGroupInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Unassign a client from a group** ```ts async function unassignClientFromGroupExample( groupId: GroupId, clientId: ClientId ) { const camunda = createCamundaClient(); await camunda.unassignClientFromGroup({ groupId, clientId, }); } ``` #### Operation Id unassignClientFromGroup #### Tags Group --- ### unassignClientFromTenant() ```ts unassignClientFromTenant(input, options?): CancelablePromise; ``` Unassign a client from a tenant Unassigns the client from the specified tenant. The client can no longer access tenant data. - #### Parameters ##### input [`unassignClientFromTenantInput`](../type-aliases/unassignClientFromTenantInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Unassign a client from a tenant** ```ts async function unassignClientFromTenantExample( tenantId: TenantId, clientId: ClientId ) { const camunda = createCamundaClient(); await camunda.unassignClientFromTenant({ tenantId, clientId, }); } ``` #### Operation Id unassignClientFromTenant #### Tags Tenant --- ### unassignGroupFromTenant() ```ts unassignGroupFromTenant(input, options?): CancelablePromise; ``` Unassign a group from a tenant Unassigns a group from a specified tenant. Members of the group (users, clients) will no longer have access to the tenant's data - except they are assigned directly to the tenant. - #### Parameters ##### input [`unassignGroupFromTenantInput`](../type-aliases/unassignGroupFromTenantInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Unassign a group from a tenant** ```ts async function unassignGroupFromTenantExample( tenantId: TenantId, groupId: GroupId ) { const camunda = createCamundaClient(); await camunda.unassignGroupFromTenant({ tenantId, groupId, }); } ``` #### Operation Id unassignGroupFromTenant #### Tags Tenant --- ### unassignMappingRuleFromGroup() ```ts unassignMappingRuleFromGroup(input, options?): CancelablePromise; ``` Unassign a mapping rule from a group Unassigns a mapping rule from a group. * #### Parameters ##### input [`unassignMappingRuleFromGroupInput`](../type-aliases/unassignMappingRuleFromGroupInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Unassign a mapping rule from a group** ```ts async function unassignMappingRuleFromGroupExample( groupId: GroupId, mappingRuleId: MappingRuleId ) { const camunda = createCamundaClient(); await camunda.unassignMappingRuleFromGroup({ groupId, mappingRuleId, }); } ``` #### Operation Id unassignMappingRuleFromGroup #### Tags Group --- ### unassignMappingRuleFromTenant() ```ts unassignMappingRuleFromTenant(input, options?): CancelablePromise; ``` Unassign a mapping rule from a tenant Unassigns a single mapping rule from a specified tenant without deleting the rule. * #### Parameters ##### input [`unassignMappingRuleFromTenantInput`](../type-aliases/unassignMappingRuleFromTenantInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Unassign a mapping rule from a tenant** ```ts async function unassignMappingRuleFromTenantExample( tenantId: TenantId, mappingRuleId: MappingRuleId ) { const camunda = createCamundaClient(); await camunda.unassignMappingRuleFromTenant({ tenantId, mappingRuleId, }); } ``` #### Operation Id unassignMappingRuleFromTenant #### Tags Tenant --- ### unassignRoleFromClient() ```ts unassignRoleFromClient(input, options?): CancelablePromise; ``` Unassign a role from a client Unassigns the specified role from the client. The client will no longer inherit the authorizations associated with this role. * #### Parameters ##### input [`unassignRoleFromClientInput`](../type-aliases/unassignRoleFromClientInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Unassign a role from a client** ```ts async function unassignRoleFromClientExample( roleId: RoleId, clientId: ClientId ) { const camunda = createCamundaClient(); await camunda.unassignRoleFromClient({ roleId, clientId, }); } ``` #### Operation Id unassignRoleFromClient #### Tags Role --- ### unassignRoleFromGroup() ```ts unassignRoleFromGroup(input, options?): CancelablePromise; ``` Unassign a role from a group Unassigns the specified role from the group. All group members (user or client) no longer inherit the authorizations associated with this role. * #### Parameters ##### input [`unassignRoleFromGroupInput`](../type-aliases/unassignRoleFromGroupInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Unassign a role from a group** ```ts async function unassignRoleFromGroupExample(roleId: RoleId, groupId: GroupId) { const camunda = createCamundaClient(); await camunda.unassignRoleFromGroup({ roleId, groupId, }); } ``` #### Operation Id unassignRoleFromGroup #### Tags Role --- ### unassignRoleFromMappingRule() ```ts unassignRoleFromMappingRule(input, options?): CancelablePromise; ``` Unassign a role from a mapping rule Unassigns a role from a mapping rule. * #### Parameters ##### input [`unassignRoleFromMappingRuleInput`](../type-aliases/unassignRoleFromMappingRuleInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Unassign a role from a mapping rule** ```ts async function unassignRoleFromMappingRuleExample( roleId: RoleId, mappingRuleId: MappingRuleId ) { const camunda = createCamundaClient(); await camunda.unassignRoleFromMappingRule({ roleId, mappingRuleId, }); } ``` #### Operation Id unassignRoleFromMappingRule #### Tags Role --- ### unassignRoleFromTenant() ```ts unassignRoleFromTenant(input, options?): CancelablePromise; ``` Unassign a role from a tenant Unassigns a role from a specified tenant. Users, Clients or Groups, that have the role assigned, will no longer have access to the tenant's data - unless they are assigned directly to the tenant. - #### Parameters ##### input [`unassignRoleFromTenantInput`](../type-aliases/unassignRoleFromTenantInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Unassign a role from a tenant** ```ts async function unassignRoleFromTenantExample( tenantId: TenantId, roleId: RoleId ) { const camunda = createCamundaClient(); await camunda.unassignRoleFromTenant({ tenantId, roleId, }); } ``` #### Operation Id unassignRoleFromTenant #### Tags Tenant --- ### unassignRoleFromUser() ```ts unassignRoleFromUser(input, options?): CancelablePromise; ``` Unassign a role from a user Unassigns a role from a user. The user will no longer inherit the authorizations associated with this role. * #### Parameters ##### input [`unassignRoleFromUserInput`](../type-aliases/unassignRoleFromUserInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Unassign a role from a user** ```ts async function unassignRoleFromUserExample(roleId: RoleId, username: Username) { const camunda = createCamundaClient(); await camunda.unassignRoleFromUser({ roleId, username, }); } ``` #### Operation Id unassignRoleFromUser #### Tags Role --- ### unassignUserFromGroup() ```ts unassignUserFromGroup(input, options?): CancelablePromise; ``` Unassign a user from a group Unassigns a user from a group. The user is removed as a group member, with associated authorizations, roles, and tenant assignments no longer applied. - #### Parameters ##### input [`unassignUserFromGroupInput`](../type-aliases/unassignUserFromGroupInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Unassign a user from a group** ```ts async function unassignUserFromGroupExample( groupId: GroupId, username: Username ) { const camunda = createCamundaClient(); await camunda.unassignUserFromGroup({ groupId, username, }); } ``` #### Operation Id unassignUserFromGroup #### Tags Group --- ### unassignUserFromTenant() ```ts unassignUserFromTenant(input, options?): CancelablePromise; ``` Unassign a user from a tenant Unassigns the user from the specified tenant. The user can no longer access tenant data. - #### Parameters ##### input [`unassignUserFromTenantInput`](../type-aliases/unassignUserFromTenantInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Unassign a user from a tenant** ```ts async function unassignUserFromTenantExample( tenantId: TenantId, username: Username ) { const camunda = createCamundaClient(); await camunda.unassignUserFromTenant({ tenantId, username, }); } ``` #### Operation Id unassignUserFromTenant #### Tags Tenant --- ### unassignUserTask() ```ts unassignUserTask(input, options?): CancelablePromise; ``` Unassign user task Removes the assignee of a task with the given key. Unassignment waits for blocking task listeners on this lifecycle transition. If listener processing is delayed beyond the request timeout, this endpoint can return 504. Other gateway timeout causes are also possible. Retry with backoff and inspect listener worker availability and logs when this repeats. - #### Parameters ##### input [`unassignUserTaskInput`](../type-aliases/unassignUserTaskInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Unassign a user task** ```ts async function unassignUserTaskExample(userTaskKey: UserTaskKey) { const camunda = createCamundaClient(); await camunda.unassignUserTask({ userTaskKey }); } ``` #### Operation Id unassignUserTask #### Tags User task --- ### updateAgentInstance() ```ts updateAgentInstance(input, options?): CancelablePromise; ``` Update agent instance Updates the status of an agent instance and appends a batch of history items to its conversation history. Each history item created for this request is echoed back in the response. - #### Parameters ##### input [`updateAgentInstanceInput`](../type-aliases/updateAgentInstanceInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`AgentInstanceUpdateResult`](../type-aliases/AgentInstanceUpdateResult.md)\> #### Example **Update an agent instance** ```ts async function updateAgentInstanceExample( agentInstanceKey: AgentInstanceKey, elementInstanceKey: ElementInstanceKey, jobKey: JobKey, jobLeaseToken: JobLeaseToken ) { const camunda = createCamundaClient(); await camunda.updateAgentInstance({ agentInstanceKey, elementInstanceKey, jobKey, jobLeaseToken, status: "THINKING", history: [ { historyItemId: HistoryItemId.assumeExists("assistant-1"), loopIteration: 1, role: "ASSISTANT", content: [{ contentType: "TEXT", text: "How can I help you?" }], producedAt: new Date().toISOString(), metrics: { inputTokens: 150, outputTokens: 50, durationMs: 820 }, }, ], }); console.log(`Updated agent instance: ${agentInstanceKey}`); } ``` #### Operation Id updateAgentInstance #### Tags Agent instance --- ### updateAuthorization() ```ts updateAuthorization(input, options?): CancelablePromise; ``` Update authorization Update the authorization with the given key. * #### Parameters ##### input [`updateAuthorizationInput`](../type-aliases/updateAuthorizationInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Update an authorization** ```ts async function updateAuthorizationExample(authorizationKey: AuthorizationKey) { const camunda = createCamundaClient(); await camunda.updateAuthorization({ authorizationKey, ownerId: "user-123", ownerType: "USER", resourceId: "order-process", resourceType: "PROCESS_DEFINITION", permissionTypes: [ "CREATE_PROCESS_INSTANCE", "READ_PROCESS_INSTANCE", "DELETE_PROCESS_INSTANCE", ], }); } ``` #### Operation Id updateAuthorization #### Tags Authorization --- ### updateGlobalClusterVariable() ```ts updateGlobalClusterVariable(input, options?): CancelablePromise; ``` Update a global-scoped cluster variable Updates the value of an existing global cluster variable. The variable must exist, otherwise a 404 error is returned. - #### Parameters ##### input [`updateGlobalClusterVariableInput`](../type-aliases/updateGlobalClusterVariableInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterVariableResult`](../type-aliases/ClusterVariableResult.md)\> #### Example **Update a global cluster variable** ```ts async function updateGlobalClusterVariableExample(name: ClusterVariableName) { const camunda = createCamundaClient(); await camunda.updateGlobalClusterVariable({ name, value: { darkMode: false }, }); } ``` #### Operation Id updateGlobalClusterVariable #### Tags Cluster Variable --- ### updateGlobalTaskListener() ```ts updateGlobalTaskListener(input, options?): CancelablePromise; ``` Update global user task listener Updates a global user task listener. * #### Parameters ##### input [`updateGlobalTaskListenerInput`](../type-aliases/updateGlobalTaskListenerInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`GlobalTaskListenerResult`](../type-aliases/GlobalTaskListenerResult.md)\> #### Example **Update a global task listener** ```ts async function updateGlobalTaskListenerExample(id: GlobalListenerId) { const camunda = createCamundaClient(); await camunda.updateGlobalTaskListener({ id, eventTypes: ["completing"], type: "updated-audit-listener", }); } ``` #### Operation Id updateGlobalTaskListener #### Tags Global listener --- ### updateGroup() ```ts updateGroup(input, options?): CancelablePromise; ``` Update group Update a group with the given ID. * #### Parameters ##### input [`updateGroupInput`](../type-aliases/updateGroupInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`GroupUpdateResult`](../type-aliases/GroupUpdateResult.md)\> #### Example **Update a group** ```ts async function updateGroupExample(groupId: GroupId) { const camunda = createCamundaClient(); await camunda.updateGroup({ groupId, name: "Engineering Team", }); } ``` #### Operation Id updateGroup #### Tags Group --- ### updateJob() ```ts updateJob(input, options?): CancelablePromise; ``` Update job Update a job with the given key. * #### Parameters ##### input [`updateJobInput`](../type-aliases/updateJobInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Update a job** ```ts async function updateJobExample(jobKey: JobKey) { const camunda = createCamundaClient(); await camunda.updateJob({ jobKey, changeset: { retries: 5, timeout: 60000 }, }); } ``` #### Operation Id updateJob #### Tags Job --- ### updateJobsBatchOperation() ```ts updateJobsBatchOperation(input, options?): CancelablePromise; ``` Update jobs (batch) Creates a batch operation to update jobs matching the given filter. At least one changeset field must be non-null. This is done asynchronously; the progress can be tracked using the batchOperationKey from the response and the batch operation status endpoint (/batch-operations/{batchOperationKey}). - #### Parameters ##### input [`JobBatchUpdateRequest`](../type-aliases/JobBatchUpdateRequest.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`BatchOperationCreatedResult`](../type-aliases/BatchOperationCreatedResult.md)\> #### Example **Update jobs in batch** ```ts async function updateJobsBatchOperationExample() { const camunda = createCamundaClient(); const result = await camunda.updateJobsBatchOperation({ filter: { type: "payment-processing", hasFailedWithRetriesLeft: false, }, changeset: { retries: 3, }, }); console.log(`Batch operation key: ${result.batchOperationKey}`); } ``` #### Operation Id updateJobsBatchOperation #### Tags Job --- ### updateMappingRule() ```ts updateMappingRule(input, options?): CancelablePromise; ``` Update mapping rule Update a mapping rule. - #### Parameters ##### input [`updateMappingRuleInput`](../type-aliases/updateMappingRuleInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`MappingRuleCreateUpdateResult`](../type-aliases/MappingRuleCreateUpdateResult.md)\> #### Example **Update a mapping rule** ```ts async function updateMappingRuleExample(mappingRuleId: MappingRuleId) { const camunda = createCamundaClient(); await camunda.updateMappingRule({ mappingRuleId, name: "LDAP Group Mapping", claimName: "groups", claimValue: "engineering-team", }); } ``` #### Operation Id updateMappingRule #### Tags Mapping rule --- ### updateRole() ```ts updateRole(input, options?): CancelablePromise; ``` Update role Update a role with the given ID. * #### Parameters ##### input [`updateRoleInput`](../type-aliases/updateRoleInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`RoleUpdateResult`](../type-aliases/RoleUpdateResult.md)\> #### Example **Update a role** ```ts async function updateRoleExample(roleId: RoleId) { const camunda = createCamundaClient(); await camunda.updateRole({ roleId, name: "Process Administrator", }); } ``` #### Operation Id updateRole #### Tags Role --- ### updateTenant() ```ts updateTenant(input, options?): CancelablePromise; ``` Update tenant Updates an existing tenant. * #### Parameters ##### input [`updateTenantInput`](../type-aliases/updateTenantInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`TenantUpdateResult`](../type-aliases/TenantUpdateResult.md)\> #### Example **Update a tenant** ```ts async function updateTenantExample(tenantId: TenantId) { const camunda = createCamundaClient(); await camunda.updateTenant({ tenantId, name: "Customer Service Team", }); } ``` #### Operation Id updateTenant #### Tags Tenant --- ### updateTenantClusterVariable() ```ts updateTenantClusterVariable(input, options?): CancelablePromise; ``` Update a tenant-scoped cluster variable Updates the value of an existing tenant-scoped cluster variable. The variable must exist, otherwise a 404 error is returned. - #### Parameters ##### input [`updateTenantClusterVariableInput`](../type-aliases/updateTenantClusterVariableInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`ClusterVariableResult`](../type-aliases/ClusterVariableResult.md)\> #### Example **Update a tenant cluster variable** ```ts async function updateTenantClusterVariableExample( tenantId: TenantId, name: ClusterVariableName ) { const camunda = createCamundaClient(); await camunda.updateTenantClusterVariable({ tenantId, name, value: { region: "eu-west-1" }, }); } ``` #### Operation Id updateTenantClusterVariable #### Tags Cluster Variable --- ### updateUser() ```ts updateUser(input, options?): CancelablePromise; ``` Update user Updates a user. * #### Parameters ##### input [`updateUserInput`](../type-aliases/updateUserInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<[`UserUpdateResult`](../type-aliases/UserUpdateResult.md)\> #### Example **Update a user** ```ts async function updateUserExample(username: Username) { const camunda = createCamundaClient(); await camunda.updateUser({ username, name: "Alice Jones", email: "alice.jones@example.com", }); } ``` #### Operation Id updateUser #### Tags User --- ### updateUserTask() ```ts updateUserTask(input, options?): CancelablePromise; ``` Update user task Update a user task with the given key. Updates wait for blocking task listeners on this lifecycle transition. If listener processing is delayed beyond the request timeout, this endpoint can return 504. Other gateway timeout causes are also possible. Retry with backoff and inspect listener worker availability and logs when this repeats. - #### Parameters ##### input [`updateUserTaskInput`](../type-aliases/updateUserTaskInput.md) ##### options? [`OperationOptions`](../interfaces/OperationOptions.md) #### Returns [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\> #### Example **Update a user task** ```ts async function updateUserTaskExample(userTaskKey: UserTaskKey) { const camunda = createCamundaClient(); await camunda.updateUserTask({ userTaskKey, changeset: { candidateUsers: ["alice", "bob"], dueDate: "2025-12-31T23:59:59Z", priority: 80, }, }); } ``` #### Operation Id updateUserTask #### Tags User task --- ### withCorrelation() ```ts withCorrelation(id, fn): Promise; ``` #### Type Parameters ##### T `T` #### Parameters ##### id `string` ##### fn () => `T` \| `Promise`\<`T`\> #### Returns `Promise`\<`T`\> #### Inherited from ```ts CamundaCore.withCorrelation; ``` --- ## Class: CamundaValidationError(Classes) ## Extends - `Error` ## Constructors ### Constructor ```ts new CamundaValidationError(params): CamundaValidationError; ``` #### Parameters ##### params ###### issues `string`[] ###### message `string` ###### operationId? `string` ###### side `"request"` \| `"response"` ###### summary `string` #### Returns `CamundaValidationError` #### Overrides ```ts Error.constructor; ``` ## Properties ### issues ```ts issues: string[]; ``` --- ### operationId? ```ts optional operationId?: string; ``` --- ### side ```ts side: "request" | "response"; ``` --- ### summary ```ts summary: string; ``` --- ## Class: CancelError ## Extends - `Error` ## Constructors ### Constructor ```ts new CancelError(): CancelError; ``` #### Returns `CancelError` #### Overrides ```ts Error.constructor; ``` --- ## Class: EventualConsistencyTimeoutError ## Extends - `Error` ## Constructors ### Constructor ```ts new EventualConsistencyTimeoutError(params): EventualConsistencyTimeoutError; ``` #### Parameters ##### params ###### attempts `number` ###### elapsedMs `number` ###### lastResponse? `any` ###### lastStatus? `number` ###### message? `string` ###### operationId? `string` #### Returns `EventualConsistencyTimeoutError` #### Overrides ```ts Error.constructor; ``` ## Properties ### attempts ```ts attempts: number; ``` --- ### code ```ts code: string = "CAMUNDA_SDK_EVENTUAL_TIMEOUT"; ``` --- ### elapsedMs ```ts elapsedMs: number; ``` --- ### lastResponseSnippet? ```ts optional lastResponseSnippet?: string; ``` --- ### lastStatus? ```ts optional lastStatus?: number; ``` --- ### operationId? ```ts optional operationId?: string; ``` --- ## Class: TypedVariablesError Base class for all typed-variable errors, so callers can catch the whole family. ## Extends - `Error` ## Extended by - [`VariableDeserializationError`](VariableDeserializationError.md) - [`VariableScopeCollisionError`](VariableScopeCollisionError.md) ## Constructors ### Constructor ```ts new TypedVariablesError(message, options?): TypedVariablesError; ``` #### Parameters ##### message `string` ##### options? ###### cause? `unknown` #### Returns `TypedVariablesError` #### Overrides ```ts Error.constructor; ``` --- ## Class: VariableCollector Incrementally collapses paged variable items into a parsed name-to-value map. Memory stays bounded by the DTO shape rather than the total number of paged items: only the first value seen per requested name is retained, alongside the set of scope keys observed for that name (used for collision detection). Items for undeclared variables are dropped, so large values for variables outside the DTO are never accumulated. ## Constructors ### Constructor ```ts new VariableCollector(queryNames): VariableCollector; ``` #### Parameters ##### queryNames `Iterable`\<`string`\> #### Returns `VariableCollector` ## Methods ### build() ```ts build(): Record; ``` Parse retained values, raising on scope collisions or malformed JSON. #### Returns `Record`\<`string`, `unknown`\> #### Throws when a name was observed at more than one scope. #### Throws when a retained value is not valid JSON. --- ### ingest() ```ts ingest(items): void; ``` Fold one page of results into the retained per-name state. #### Parameters ##### items `Iterable`\<[`TypedVariableItem`](../interfaces/TypedVariableItem.md)\> #### Returns `void` --- ## Class: VariableDeserializationError Raised when a variable's serialized value is not valid JSON. ## Extends - [`TypedVariablesError`](TypedVariablesError.md) ## Constructors ### Constructor ```ts new VariableDeserializationError(variableName, options?): VariableDeserializationError; ``` #### Parameters ##### variableName `string` ##### options? ###### cause? `unknown` #### Returns `VariableDeserializationError` #### Overrides [`TypedVariablesError`](TypedVariablesError.md).[`constructor`](TypedVariablesError.md#constructor) ## Properties ### variableName ```ts readonly variableName: string; ``` --- ## Class: VariableMap # Class: VariableMap\ Result of a DTO-driven variable search. Holds the parsed variable values keyed by their declared name. Provides lenient, defensive access via [has](#has) / [get](#get), and a strict [validate](#validate) that parses the values against the schema — returning a fully-typed object or throwing a `ZodError` when a required variable is missing or malformed. ## Type Parameters ### TSchema `TSchema` _extends_ [`AnyVariableSchema`](../type-aliases/AnyVariableSchema.md) ## Constructors ### Constructor ```ts new VariableMap(_raw, _schema): VariableMap; ``` #### Parameters ##### \_raw `Readonly`\<`Record`\<`string`, `unknown`\>\> ##### \_schema `TSchema` #### Returns `VariableMap`\<`TSchema`\> ## Accessors ### raw #### Get Signature ```ts get raw(): Readonly>; ``` The parsed variable values, keyed by variable name. ##### Returns `Readonly`\<`Record`\<`string`, `unknown`\>\> ## Methods ### get() ```ts get(variableName): K extends keyof input ? input[K] | undefined : undefined; ``` Lenient access. Returns the JSON-parsed wire value, or `undefined` when the variable is absent. The value is _not_ run through the schema, so a declared key is narrowed to that field's schema _input_ type (`z.input`) unioned with `undefined` — not the post-validation output type. This keeps the type honest for schemas with transforms or effects, where the parsed wire value can differ from `validate()`'s output. Any other key resolves to `undefined`: the result only ever holds the declared variable names, so an undeclared key can never carry a value. #### Type Parameters ##### K `K` _extends_ `string` #### Parameters ##### variableName `K` #### Returns `K` _extends_ keyof `input`\<`TSchema`\> ? `input`\<`TSchema`\>\[`K`\] \| `undefined` : `undefined` --- ### has() #### Call Signature ```ts has(variableName): boolean; ``` Whether a variable with the given name is present in the result. ##### Parameters ###### variableName keyof `input`\<`TSchema`\> & `string` ##### Returns `boolean` #### Call Signature ```ts has(variableName): boolean; ``` Whether a variable with the given name is present in the result. ##### Parameters ###### variableName `string` ##### Returns `boolean` --- ### validate() ```ts validate(): output; ``` Strict access. Parses the collected values against the schema and returns the typed object. Required variables must be present and well-formed, otherwise a `ZodError` is thrown. #### Returns `output`\<`TSchema`\> --- ## Class: VariableScopeCollisionError Raised when a declared variable name is observed at more than one scope (for example a local variable shadowing a process-level variable). The result would be ambiguous, so the search fails loudly rather than silently picking one. Pass an explicit `scopeKey` to disambiguate. ## Extends - [`TypedVariablesError`](TypedVariablesError.md) ## Constructors ### Constructor ```ts new VariableScopeCollisionError(variableName, scopeKeys): VariableScopeCollisionError; ``` #### Parameters ##### variableName `string` ##### scopeKeys readonly `string`[] #### Returns `VariableScopeCollisionError` #### Overrides [`TypedVariablesError`](TypedVariablesError.md).[`constructor`](TypedVariablesError.md#constructor) ## Properties ### scopeKeys ```ts readonly scopeKeys: readonly string[]; ``` --- ### variableName ```ts readonly variableName: string; ``` --- ## Function: assertConstraint() ```ts function assertConstraint(value, label, c): void; ``` ## Parameters ### value `string` ### label `string` ### c #### maxLength? `number` #### minLength? `number` #### pattern? `string` ## Returns `void` --- ## Function: collectTypedVariables() ```ts function collectTypedVariables(params): Promise>; ``` Page through variable search results until every declared variable is found or the result set is exhausted, then collapse them into a [VariableMap](../classes/VariableMap.md). Eventual-consistency waiting (when `consistency.waitUpToMs > 0`) is applied here, at the collection level — not on the underlying search calls. A freshly-written instance indexes its declared variables independently, so an early read can return only a subset (e.g. `orderId` before `amount`). Waiting on the first _search_ alone is too weak: that search's success condition is "at least one matching variable", so it settles on a partial result. Instead we re-run the whole collection until every declared name is visible or the budget expires. On expiry we return the best snapshot gathered so far rather than throwing: a genuinely-absent variable is indistinguishable from a late one, and [VariableMap.validate](../classes/VariableMap.md#validate) is the right place to surface a missing required variable. This also keeps pagination correct — a paging read that legitimately returns zero items never blocks, because the inner search never waits. The `fetchPage` callback isolates the HTTP call so the paging and collapse logic is unit-testable in isolation; `clock` isolates time so the consistency loop is too. ## Type Parameters ### TSchema `TSchema` _extends_ [`AnyVariableSchema`](../type-aliases/AnyVariableSchema.md) ## Parameters ### params #### clock? `CollectClock` Injectable clock for deterministic tests; defaults to real time. #### consistency? `VariableConsistencyOptions` Eventual-consistency tolerance. Omitted or `waitUpToMs: 0` reads exactly once. #### fetchPage (`after`) => `Promise`\<[`TypedVariablePage`](../interfaces/TypedVariablePage.md)\> #### schema `TSchema` #### singleScope `boolean` Whether the query is scoped to a single scope (collisions impossible, early-stop safe). ## Returns `Promise`\<[`VariableMap`](../classes/VariableMap.md)\<`TSchema`\>\> --- ## Function: createCamundaClient() ```ts function createCamundaClient(options?): CamundaClient; ``` ## Parameters ### options? [`CamundaOptions`](../interfaces/CamundaOptions.md) ## Returns [`CamundaClient`](../type-aliases/CamundaClient.md) --- ## Function: createCamundaClientLoose() ```ts function createCamundaClientLoose(...args): WithSearchPagination<{ clock: { deadline(...a): { dispose: (...a) => void; signal: { }; }; now(...a): number; sleep(...a): Promise; }; config: { __raw: { [key: string]: string | undefined; }; auth: { basic?: { password?: string; username?: string; }; strategy: AuthStrategy; }; backpressure: { decayQuietMs: number; enabled: boolean; floor: number; healthyRecoveryMultiplier: number; initialMax: number; maxWaiters: number; observeOnly: boolean; profile: string; recoveryIntervalMs: number; recoveryStep: number; severeFactor: number; severeThreshold: number; softFactor: number; unlimitedAfterHealthyMs: number; }; defaultTenantId: string; eventual?: { pollDefaultMs: number; }; httpRetry: { baseDelayMs: number; maxAttempts: number; maxDelayMs: number; }; logLevel: "trace" | "error" | "silent" | "warn" | "info" | "debug"; mtls?: { ca?: string; caPath?: string; cert?: string; certPath?: string; key?: string; keyPassphrase?: string; keyPath?: string; }; oauth: { cacheDir?: string; clientId?: string; clientSecret?: string; grantType: string; oauthUrl: string; retry: { baseDelayMs: number; max: number; }; scope?: string; timeoutMs: number; }; restAddress: string; supportLog?: { enabled: boolean; filePath: string; }; telemetry?: { correlation: boolean; log: boolean; }; tokenAudience: string; validation: { raw: string; req: ValidationMode; res: ValidationMode; }; workerDefaults?: { jobTimeoutMs?: number; maxParallelJobs?: number; pollTimeoutMs?: number; startupJitterMaxSeconds?: number; workerName?: string; }; }; random: { next(...a): number; }; _getSupportLogger(...a): { log(...a): void; }; _invokeWithRetry(...a): Promise; activateAdHocSubProcessActivities(...a): CancelablePromise; activateJobs(...a): CancelablePromise<{ jobs: object[]; }>; assignClientToGroup(...a): CancelablePromise; assignClientToTenant(...a): CancelablePromise; assignGroupToTenant(...a): CancelablePromise; assignMappingRuleToGroup(...a): CancelablePromise; assignMappingRuleToTenant(...a): CancelablePromise; assignProcessInstanceBusinessId(...a): CancelablePromise; assignRoleToClient(...a): CancelablePromise; assignRoleToGroup(...a): CancelablePromise; assignRoleToMappingRule(...a): CancelablePromise; assignRoleToTenant(...a): CancelablePromise; assignRoleToUser(...a): CancelablePromise; assignUserTask(...a): CancelablePromise; assignUserToGroup(...a): CancelablePromise; assignUserToTenant(...a): CancelablePromise; broadcastSignal(...a): CancelablePromise<{ signalKey: string; tenantId: string; }>; cancelBatchOperation(...a): CancelablePromise; cancelClusterRebalance(...a): CancelablePromise<{ wasRunning: boolean; }>; cancelProcessInstance(...a): CancelablePromise; cancelProcessInstancesBatchOperation(...a): CancelablePromise<{ batchOperationKey: string; batchOperationType: BatchOperationTypeEnum; }>; changeClusterMode(...a): CancelablePromise<{ changeId: string; plannedChanges: object[]; }>; changeClusterModeAsClusterAdmin(...a): CancelablePromise<{ changeId: string; plannedChanges: object[]; }>; clearAuthCache(...a): void; completeJob(...a): CancelablePromise; completeUserTask(...a): CancelablePromise; configure(...a): void; correlateMessage(...a): CancelablePromise<{ messageKey: string; processInstanceKey: string; tenantId: string; }>; createAdminUser(...a): CancelablePromise<{ email: string | null; name: string | null; username: string; }>; createAgentInstance(...a): CancelablePromise<{ agentInstanceKey: string; createdHistory: object[]; }>; createAuthorization(...a): CancelablePromise<{ authorizationKey: string; }>; createDeployment(...a): CancelablePromise<{ decisionRequirements: object[]; decisions: object[]; deploymentKey: string; deployments: object[]; forms: object[]; processes: object[]; resources: object[]; tenantId: string; }>; createDocument(...a): CancelablePromise<{ camunda.document.type: "camunda"; contentHash: string | null; documentId: string; metadata: { contentType: string; customProperties: { [key: string]: unknown; }; expiresAt: string | null; fileName: string; processDefinitionId: | { [key: number]: string; __brand: "ProcessDefinitionId"; } | null; processInstanceKey: | { [key: number]: string; __brand: "ProcessInstanceKey"; } | null; size: number; }; storeId: string; }>; createDocumentLink(...a): CancelablePromise<{ expiresAt: string; url: string; }>; createDocuments(...a): CancelablePromise<{ createdDocuments: object[]; failedDocuments: object[]; }>; createElementInstanceVariables(...a): CancelablePromise; createGlobalClusterVariable(...a): CancelablePromise<{ kind: ClusterVariableKindEnum; metadata: { [key: string]: string | number; }; name: string; scope: ClusterVariableScopeEnum; tenantId: string | null; value: string; }>; createGlobalTaskListener(...a): CancelablePromise<{ afterNonGlobal?: boolean; eventTypes: GlobalTaskListenerEventTypeEnum[]; id: string; priority?: number; retries?: number; source: GlobalListenerSourceEnum; type?: string; }>; createGroup(...a): CancelablePromise<{ description: string | null; groupId: string; name: string; }>; createJobWorker(...a): { activeJobs: number; name: string; stopped: boolean; start(...a): void; stop(...a): void; stopGracefully(...a): Promise<{ remainingJobs: number; timedOut: boolean; }>; }; createMappingRule(...a): CancelablePromise<{ claimName: string; claimValue: string; mappingRuleId: string; name: string; }>; createProcessInstance(...a): CancelablePromise<{ businessId: | { [key: number]: string; __brand: "BusinessId"; } | null; processDefinitionId: string; processDefinitionKey: string; processDefinitionVersion: number; processInstanceKey: string; tags: string[]; tenantId: string; variables: { [key: string]: unknown; }; }>; createRole(...a): CancelablePromise<{ description: string | null; name: string; roleId: string; }>; createTenant(...a): CancelablePromise<{ description: string | null; name: string; tenantId: string; }>; createTenantClusterVariable(...a): CancelablePromise<{ kind: ClusterVariableKindEnum; metadata: { [key: string]: string | number; }; name: string; scope: ClusterVariableScopeEnum; tenantId: string | null; value: string; }>; createThreadedJobWorker(...a): { activeJobs: number; busyThreads: number; name: string; poolSize: number; ready: Promise; stopped: boolean; start(...a): void; stop(...a): void; stopGracefully(...a): Promise<{ remainingJobs: number; timedOut: boolean; }>; }; createUser(...a): CancelablePromise<{ email: string | null; name: string | null; username: string; }>; deleteAuthorization(...a): CancelablePromise; deleteDecisionInstance(...a): CancelablePromise; deleteDecisionInstancesBatchOperation(...a): CancelablePromise<{ batchOperationKey: string; batchOperationType: BatchOperationTypeEnum; }>; deleteDocument(...a): CancelablePromise; deleteGlobalClusterVariable(...a): CancelablePromise; deleteGlobalTaskListener(...a): CancelablePromise; deleteGroup(...a): CancelablePromise; deleteHistoryBackup(...a): CancelablePromise; deleteHistoryBackupAsClusterAdmin(...a): CancelablePromise; deleteMappingRule(...a): CancelablePromise; deleteProcessInstance(...a): CancelablePromise; deleteProcessInstancesBatchOperation(...a): CancelablePromise<{ batchOperationKey: string; batchOperationType: BatchOperationTypeEnum; }>; deleteResource(...a): CancelablePromise<{ batchOperation: | { batchOperationKey: string; batchOperationType: BatchOperationTypeEnum; } | null; resourceKey: string; }>; deleteRole(...a): CancelablePromise; deleteRuntimeBackup(...a): CancelablePromise; deleteRuntimeBackupAsClusterAdmin(...a): CancelablePromise; deleteRuntimeBackupState(...a): CancelablePromise; deleteRuntimeBackupStateAsClusterAdmin(...a): CancelablePromise; deleteTenant(...a): CancelablePromise; deleteTenantClusterVariable(...a): CancelablePromise; deleteUser(...a): CancelablePromise; deployResourcesFromFiles(...a): CancelablePromise<{ decisionRequirements: object[]; decisions: object[]; deploymentKey: string; deployments: object[]; forms: object[]; processes: object[]; resources: object[]; tenantId: string; }>; emitSupportLogPreamble(...a): void; evaluateConditionals(...a): CancelablePromise<{ conditionalEvaluationKey: string; processInstances: object[]; tenantId: string; }>; evaluateDecision(...a): CancelablePromise<{ decisionDefinitionId: string; decisionDefinitionKey: string; decisionDefinitionName: string; decisionDefinitionVersion: number; decisionEvaluationKey: string; decisionInstanceKey: string; decisionRequirementsId: string; decisionRequirementsKey: string; evaluatedDecisions: object[]; failedDecisionDefinitionId: | { [key: number]: string; __brand: "DecisionDefinitionId"; } | null; failureMessage: string | null; output: string; tenantId: string; }>; evaluateExpression(...a): CancelablePromise<{ expression: string; referencedSecrets: object[]; result: unknown; warnings: object[]; }>; failJob(...a): CancelablePromise; forceAuthRefresh(...a): Promise; getAgentDefinition(...a): CancelablePromise<{ agentDefinitionKey: string; agentType: AgentDefinitionTypeEnum; elementId: string; name: string; processDefinitionId: string; processDefinitionKey: string; processDefinitionVersion: number; processDefinitionVersionTag: string | null; tenantId: string; }>; getAgentInstance(...a): CancelablePromise<{ agentDefinitionKey: string; agentInstanceKey: string; completionDate: string | null; creationDate: string; definition: { model: string; provider: string; systemPrompt: ( | { contentType: "TEXT"; text: string; } | { contentType: "DOCUMENT"; documentReference: { camunda.document.type: "camunda"; contentHash: string | null; documentId: string; metadata: { contentType: string; customProperties: { [key: ...]: ...; }; expiresAt: ... | ...; fileName: string; processDefinitionId: ... | ...; processInstanceKey: ... | ...; size: number; }; storeId: string; }; } | { contentType: "OBJECT"; object: unknown; })[]; }; elementId: string; elementInstanceKeys: string[]; lastUpdatedDate: string; limits: { maxModelCalls: number; maxTokens: number; maxToolCalls: number; }; metrics: { cacheCreationTokenCount: number; cacheReadTokenCount: number; inputTokens: number; modelCalls: number; outputTokens: number; reasoningTokenCount: number; toolCalls: number; }; processDefinitionId: string; processDefinitionKey: string; processDefinitionVersion: number; processDefinitionVersionTag: string | null; processInstanceKey: string; rootProcessInstanceKey: string; status: AgentInstanceStatusEnum; tenantId: string; tools: object[]; }>; getAuditLog(...a): CancelablePromise<{ actorId: string | null; actorType: | AuditLogActorTypeEnum | null; agentElementId: string | null; auditLogKey: string; batchOperationKey: | { [key: number]: string; __brand: "BatchOperationKey"; } | null; batchOperationType: | BatchOperationTypeEnum | null; category: AuditLogCategoryEnum; decisionDefinitionId: | { [key: number]: string; __brand: "DecisionDefinitionId"; } | null; decisionDefinitionKey: | { [key: number]: string; __brand: "DecisionDefinitionKey"; } | null; decisionEvaluationKey: | { [key: number]: string; __brand: "DecisionEvaluationKey"; } | null; decisionRequirementsId: string | null; decisionRequirementsKey: | { [key: number]: string; __brand: "DecisionRequirementsKey"; } | null; deploymentKey: | { [key: number]: string; __brand: "DeploymentKey"; } | null; elementInstanceKey: | { [key: number]: string; __brand: "ElementInstanceKey"; } | null; entityDescription: string | null; entityKey: string; entityType: AuditLogEntityTypeEnum; formKey: | { [key: number]: string; __brand: "FormKey"; } | null; inboundChannelToolName: string | null; inboundChannelType: string | null; jobKey: | { [key: number]: string; __brand: "JobKey"; } | null; operationType: AuditLogOperationTypeEnum; processDefinitionId: | { [key: number]: string; __brand: "ProcessDefinitionId"; } | null; processDefinitionKey: | { [key: number]: string; __brand: "ProcessDefinitionKey"; } | null; processInstanceKey: | { [key: number]: string; __brand: "ProcessInstanceKey"; } | null; relatedEntityKey: | { [key: number]: string; __brand: "AuditLogEntityKey"; } | null; relatedEntityType: | AuditLogEntityTypeEnum | null; resourceKey: | { [key: number]: string; __brand: "ProcessDefinitionKey"; } | { [key: number]: string; __brand: "DecisionRequirementsKey"; } | { [key: number]: string; __brand: "DecisionDefinitionKey"; } | { [key: number]: string; __brand: "FormKey"; } | null; result: AuditLogResultEnum; rootProcessInstanceKey: | { [key: number]: string; __brand: "ProcessInstanceKey"; } | null; tenantId: | { [key: number]: string; __brand: "TenantId"; } | null; timestamp: string; userTaskKey: | { [key: number]: string; __brand: "UserTaskKey"; } | null; }>; getAuthentication(...a): CancelablePromise<{ authorizedComponents: string[]; c8Links: { [key: string]: string; }; canLogout: boolean; displayName: string | null; email: string | null; groups: string[]; roles: string[]; salesPlanType: string | null; tenants: object[]; username: string; }>; getAuthHeaders(...a): Promise<{ [key: string]: string; }>; getAuthorization(...a): CancelablePromise<{ authorizationKey: string; ownerId: string; ownerType: OwnerTypeEnum; permissionTypes: PermissionTypeEnum[]; resourceId: string | null; resourcePropertyName: string | null; resourceType: ResourceTypeEnum; }>; getBackpressureState(...a): | { backoffMs: number; consecutive: number; permitsCurrent: number; permitsMax: number | null; severity: BackpressureSeverity; waiters: number; } | { consecutive: number; permitsCurrent: number; permitsMax: null; severity: string; waiters: number; }; getBatchOperation(...a): CancelablePromise<{ actorId: string | null; actorType: | AuditLogActorTypeEnum | null; batchOperationKey: string; batchOperationType: | BatchOperationTypeEnum | null; endDate: string | null; errors: object[]; operationsCompletedCount: number; operationsFailedCount: number; operationsTotalCount: number; startDate: string | null; state: BatchOperationStateEnum; }>; getClusterExportingStatus(...a): CancelablePromise<{ status: ExportingStatusCode; }>; getClusterRebalance(...a): CancelablePromise<{ lastCompletedRebalance: | { finishedAt: string; partitions: object[]; rebalanceId: number; result: "COMPLETED" | "FAILED" | "CANCELLED"; startedAt: string; } | null; partitions: object[]; runningRebalance: | { cancelRequested: boolean; dryRun: boolean; partitions: object[]; rebalanceId: number; startedAt: string; } | null; state: "BALANCED" | "BALANCING" | "UNBALANCED"; }>; getClusterStatus(...a): CancelablePromise<{ status: "HEALTHY" | "DEGRADED" | "DOWN"; }>; getClusterTopology(...a): CancelablePromise<{ brokers: object[]; clusterId: string | null; clusterSize: number; gatewayVersion: string | null; physicalTenants: object[]; }>; getClusterUpgradeStatus(...a): CancelablePromise<{ status: "UNKNOWN" | "MIGRATED" | "MIGRATION_IN_PROGRESS"; }>; getConfig(...a): { __raw: { [key: string]: string | undefined; }; auth: { basic?: { password?: string; username?: string; }; strategy: AuthStrategy; }; backpressure: { decayQuietMs: number; enabled: boolean; floor: number; healthyRecoveryMultiplier: number; initialMax: number; maxWaiters: number; observeOnly: boolean; profile: string; recoveryIntervalMs: number; recoveryStep: number; severeFactor: number; severeThreshold: number; softFactor: number; unlimitedAfterHealthyMs: number; }; defaultTenantId: string; eventual?: { pollDefaultMs: number; }; httpRetry: { baseDelayMs: number; maxAttempts: number; maxDelayMs: number; }; logLevel: "trace" | "error" | "silent" | "warn" | "info" | "debug"; mtls?: { ca?: string; caPath?: string; cert?: string; certPath?: string; key?: string; keyPassphrase?: string; keyPath?: string; }; oauth: { cacheDir?: string; clientId?: string; clientSecret?: string; grantType: string; oauthUrl: string; retry: { baseDelayMs: number; max: number; }; scope?: string; timeoutMs: number; }; restAddress: string; supportLog?: { enabled: boolean; filePath: string; }; telemetry?: { correlation: boolean; log: boolean; }; tokenAudience: string; validation: { raw: string; req: ValidationMode; res: ValidationMode; }; workerDefaults?: { jobTimeoutMs?: number; maxParallelJobs?: number; pollTimeoutMs?: number; startupJitterMaxSeconds?: number; workerName?: string; }; }; getDecisionDefinition(...a): CancelablePromise<{ decisionDefinitionId: string; decisionDefinitionKey: string; decisionRequirementsId: string; decisionRequirementsKey: string; decisionRequirementsName: string; decisionRequirementsVersion: number; name: string; tenantId: string; version: number; }>; getDecisionDefinitionXml(...a): CancelablePromise; getDecisionInstance(...a): CancelablePromise<{ businessId: | { [key: number]: string; __brand: "BusinessId"; } | null; decisionDefinitionId: string; decisionDefinitionKey: string; decisionDefinitionName: string; decisionDefinitionType: DecisionDefinitionTypeEnum; decisionDefinitionVersion: number; decisionEvaluationInstanceKey: string; decisionEvaluationKey: string; elementInstanceKey: | { [key: number]: string; __brand: "ElementInstanceKey"; } | null; evaluatedInputs: object[]; evaluationDate: string; evaluationFailure: string | null; matchedRules: object[]; processDefinitionKey: | { [key: number]: string; __brand: "ProcessDefinitionKey"; } | null; processInstanceKey: | { [key: number]: string; __brand: "ProcessInstanceKey"; } | null; result: string; rootDecisionDefinitionKey: string; rootProcessInstanceKey: | { [key: number]: string; __brand: "ProcessInstanceKey"; } | null; state: DecisionInstanceStateEnum; tenantId: string; }>; getDecisionRequirements(...a): CancelablePromise<{ decisionRequirementsId: string; decisionRequirementsKey: string; decisionRequirementsName: string; resourceName: string; tenantId: string; version: number; }>; getDecisionRequirementsXml(...a): CancelablePromise; getDocument(...a): CancelablePromise<{ }>; getElementInstance(...a): CancelablePromise<{ elementId: string; elementInstanceKey: string; elementName: string; endDate: string | null; hasIncident: boolean; incidentKey: | { [key: number]: string; __brand: "IncidentKey"; } | null; processDefinitionId: string; processDefinitionKey: string; processInstanceKey: string; rootProcessInstanceKey: | { [key: number]: string; __brand: "ProcessInstanceKey"; } | null; startDate: string; state: ElementInstanceStateEnum; tenantId: string; type: | "UNKNOWN" | "USER_TASK" | "UNSPECIFIED" | "PROCESS" | "SUB_PROCESS" | "EVENT_SUB_PROCESS" | "AD_HOC_SUB_PROCESS" | "AD_HOC_SUB_PROCESS_INNER_INSTANCE" | "START_EVENT" | "INTERMEDIATE_CATCH_EVENT" | "INTERMEDIATE_THROW_EVENT" | "BOUNDARY_EVENT" | "END_EVENT" | "SERVICE_TASK" | "RECEIVE_TASK" | "MANUAL_TASK" | "TASK" | "EXCLUSIVE_GATEWAY" | "INCLUSIVE_GATEWAY" | "PARALLEL_GATEWAY" | "EVENT_BASED_GATEWAY" | "SEQUENCE_FLOW" | "MULTI_INSTANCE_BODY" | "CALL_ACTIVITY" | "BUSINESS_RULE_TASK" | "SCRIPT_TASK" | "SEND_TASK"; }>; getErrorMode(...a): "throw" | "result"; getExportingStatus(...a): CancelablePromise<{ status: ExportingStatusCode; }>; getFormByKey(...a): CancelablePromise<{ formId: string; formKey: string; schema: string; tenantId: string; version: number; }>; getGlobalClusterVariable(...a): CancelablePromise<{ kind: ClusterVariableKindEnum; metadata: { [key: string]: string | number; }; name: string; scope: ClusterVariableScopeEnum; tenantId: string | null; value: string; }>; getGlobalJobStatistics(...a): CancelablePromise<{ completed: { count: number; lastUpdatedAt: string | null; }; created: { count: number; lastUpdatedAt: string | null; }; failed: { count: number; lastUpdatedAt: string | null; }; isIncomplete: boolean; }>; getGlobalTaskListener(...a): CancelablePromise<{ afterNonGlobal?: boolean; eventTypes: GlobalTaskListenerEventTypeEnum[]; id: string; priority?: number; retries?: number; source: GlobalListenerSourceEnum; type?: string; }>; getGroup(...a): CancelablePromise<{ description: string | null; groupId: string; name: string; }>; getHistoryBackup(...a): CancelablePromise<{ backupId: number; details: object[]; failureReason: string | null; state: HistoryBackupStateCode; }>; getHistoryBackupAsClusterAdmin(...a): CancelablePromise<{ backupId: number; physicalTenants: object[]; }>; getIncident(...a): CancelablePromise<{ creationTime: string; elementId: string; elementInstanceKey: string; errorMessage: string; errorType: IncidentErrorTypeEnum; incidentKey: string; jobKey: | { [key: number]: string; __brand: "JobKey"; } | null; processDefinitionId: string; processDefinitionKey: string; processInstanceKey: string; rootProcessInstanceKey: | { [key: number]: string; __brand: "ProcessInstanceKey"; } | null; state: IncidentStateEnum; tenantId: string; }>; getJobErrorStatistics(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; getJobTimeSeriesStatistics(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; getJobTypeStatistics(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; getJobWorkerStatistics(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; getLicense(...a): CancelablePromise<{ expiresAt: string | null; isCommercial: boolean; licenseType: string; validLicense: boolean; }>; getMappingRule(...a): CancelablePromise<{ claimName: string; claimValue: string; mappingRuleId: string; name: string; }>; getProcessDefinition(...a): CancelablePromise<{ hasStartForm: boolean; name: string | null; processDefinitionId: string; processDefinitionKey: string; resourceName: string; state: "ACTIVE" | "DRAINING" | "DELETED"; tenantId: string; version: number; versionTag: string | null; }>; getProcessDefinitionInstanceStatistics(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; getProcessDefinitionInstanceVersionStatistics(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; getProcessDefinitionMessageSubscriptionStatistics(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; getProcessDefinitionStatistics(...a): CancelablePromise<{ items: object[]; }>; getProcessDefinitionXml(...a): CancelablePromise; getProcessInstance(...a): CancelablePromise<{ businessId: | { [key: number]: string; __brand: "BusinessId"; } | null; endDate: string | null; hasIncident: boolean; parentElementInstanceKey: | { [key: number]: string; __brand: "ElementInstanceKey"; } | null; parentProcessInstanceKey: | { [key: number]: string; __brand: "ProcessInstanceKey"; } | null; processDefinitionId: string; processDefinitionKey: string; processDefinitionName: string | null; processDefinitionVersion: number; processDefinitionVersionTag: string | null; processInstanceKey: string; rootProcessInstanceKey: | { [key: number]: string; __brand: "ProcessInstanceKey"; } | null; startDate: string; state: ProcessInstanceStateEnum; suspendedDate: string | null; tags: string[]; tenantId: string; }>; getProcessInstanceCallHierarchy(...a): CancelablePromise; getProcessInstanceSequenceFlows(...a): CancelablePromise<{ items: object[]; }>; getProcessInstanceStatistics(...a): CancelablePromise<{ items: object[]; }>; getProcessInstanceStatisticsByDefinition(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; getProcessInstanceStatisticsByError(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; getProcessInstanceWaitStateStatistics(...a): CancelablePromise<{ items: object[]; }>; getResource(...a): CancelablePromise<{ resourceId: string; resourceKey: string; resourceName: string; tenantId: string; version: number; versionTag: string | null; }>; getResourceContent(...a): CancelablePromise<{ [key: string]: unknown; }>; getResourceContentBinary(...a): CancelablePromise<{ }>; getRestoreStatus(...a): CancelablePromise<{ brokers: object[]; changeId: string; startedAt: string | null; status: "COMPLETED" | "FAILED" | "IN_PROGRESS" | "CANCELLED"; }>; getRole(...a): CancelablePromise<{ description: string | null; name: string; roleId: string; }>; getRuntimeBackup(...a): CancelablePromise<{ backupId: number; details: object[]; failureReason: string | null; state: StateCode; }>; getRuntimeBackupAsClusterAdmin(...a): CancelablePromise<{ backupId: number; failureReason: string | null; physicalTenants: object[]; state: StateCode; }>; getRuntimeBackupState(...a): CancelablePromise<{ backupStates: object[]; checkpointStates: object[]; ranges: object[]; }>; getRuntimeBackupStateAsClusterAdmin(...a): CancelablePromise<{ physicalTenants: object[]; }>; getStartProcessForm(...a): CancelablePromise< | void | { formId: string; formKey: string; schema: string; tenantId: string; version: number; }>; getStatus(...a): CancelablePromise; getSystemConfiguration(...a): CancelablePromise<{ authentication: { canLogout: boolean; isCamundaGroupsEnabled: boolean; isLoginDelegated: boolean; }; cloud: { stage: CloudStage | null; }; components: { active: WebappComponent[]; }; deployment: { isMultiTenancyEnabled: boolean; isTenantsApiEnabled: boolean; maxRequestSize: number; }; jobMetrics: { enabled: boolean; exportInterval: string; maxJobTypeLength: number; maxTenantIdLength: number; maxUniqueKeys: number; maxWorkerNameLength: number; }; }>; getTenant(...a): CancelablePromise<{ description: string | null; name: string; tenantId: string; }>; getTenantClusterVariable(...a): CancelablePromise<{ kind: ClusterVariableKindEnum; metadata: { [key: string]: string | number; }; name: string; scope: ClusterVariableScopeEnum; tenantId: string | null; value: string; }>; getTopology(...a): CancelablePromise<{ brokers: object[]; clusterId: string | null; clusterSize: number; gatewayVersion: string; lastCompletedChangeId: string; partitionsCount: number; replicationFactor: number; }>; getUsageMetrics(...a): CancelablePromise<{ activeTenants: number; assignees: number; decisionInstances: number; processInstances: number; tenants: { [key: string]: object; }; }>; getUser(...a): CancelablePromise<{ email: string | null; name: string | null; username: string; }>; getUserTask(...a): CancelablePromise<{ assignee: string | null; businessId: | { [key: number]: string; __brand: "BusinessId"; } | null; candidateGroups: string[]; candidateUsers: string[]; completionDate: string | null; creationDate: string; customHeaders: { [key: string]: string; }; dueDate: string | null; elementId: string; elementInstanceKey: string; externalFormReference: string | null; followUpDate: string | null; formKey: | { [key: number]: string; __brand: "FormKey"; } | null; isSuspended: boolean; name: string | null; priority: number; processDefinitionId: string; processDefinitionKey: string; processDefinitionVersion: number; processInstanceKey: string; processName: string | null; rootProcessInstanceKey: | { [key: number]: string; __brand: "ProcessInstanceKey"; } | null; state: UserTaskStateEnum; tags: string[]; tenantId: string; userTaskKey: string; }>; getUserTaskForm(...a): CancelablePromise< | void | { formId: string; formKey: string; schema: string; tenantId: string; version: number; }>; getVariable(...a): CancelablePromise<{ name: string; processInstanceKey: string; rootProcessInstanceKey: | { [key: number]: string; __brand: "ProcessInstanceKey"; } | null; scopeKey: string; tenantId: string; value: string; variableKey: string; }>; getWorkers(...a): any[]; listHistoryBackups(...a): CancelablePromise; listHistoryBackupsAsClusterAdmin(...a): CancelablePromise; listRuntimeBackups(...a): CancelablePromise; listRuntimeBackupsAsClusterAdmin(...a): CancelablePromise; listSecrets(...a): CancelablePromise<{ references: string[]; }>; logger(...a): { code(...a): void; debug(...a): void; error(...a): void; info(...a): void; level(...a): LogLevel; scope(...a): { level: () => LogLevel; setLevel: (level: LogLevel) => void; setTransport: (t?: ((e: { level: LogLevel; scope: string; ts: number; args: any[]; code?: string | undefined; data?: any; }) => void) | undefined) => void; ... 7 more ...; code: (level: LogLevel, code: string, msg: string, data?: any) => void; }; setLevel(...a): void; setTransport(...a): void; silly(...a): void; trace(...a): void; warn(...a): void; }; migrateProcessInstance(...a): CancelablePromise; migrateProcessInstancesBatchOperation(...a): CancelablePromise<{ batchOperationKey: string; batchOperationType: BatchOperationTypeEnum; }>; modifyProcessInstance(...a): CancelablePromise; modifyProcessInstancesBatchOperation(...a): CancelablePromise<{ batchOperationKey: string; batchOperationType: BatchOperationTypeEnum; }>; onAuthHeaders(...a): void; pauseClusterExporting(...a): CancelablePromise; pauseExporting(...a): CancelablePromise; pinClock(...a): CancelablePromise; publishMessage(...a): CancelablePromise<{ messageKey: string; tenantId: string; }>; resetClock(...a): CancelablePromise; resolveIncident(...a): CancelablePromise; resolveIncidentsBatchOperation(...a): CancelablePromise<{ batchOperationKey: string; batchOperationType: BatchOperationTypeEnum; }>; resolveProcessInstanceIncidents(...a): CancelablePromise<{ batchOperationKey: string; batchOperationType: BatchOperationTypeEnum; }>; resolveSecrets(...a): CancelablePromise<{ errors: object[]; resolved: object[]; }>; restore(...a): CancelablePromise<{ changeId: string; plannedChanges: object[]; }>; restoreAsClusterAdmin(...a): CancelablePromise<{ changeId: string; plannedChanges: object[]; }>; resumeBatchOperation(...a): CancelablePromise; resumeClusterExporting(...a): CancelablePromise; resumeExporting(...a): CancelablePromise; resumeProcessInstance(...a): CancelablePromise; resumeProcessInstancesBatchOperation(...a): CancelablePromise<{ batchOperationKey: string; batchOperationType: BatchOperationTypeEnum; }>; searchAgentDefinitions(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchAgentInstanceHistory(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchAgentInstances(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchAuditLogs(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchAuthorizations(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchBatchOperationItems(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchBatchOperations(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchClientsForGroup(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchClientsForRole(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchClientsForTenant(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchClusterVariables(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchCorrelatedMessageSubscriptions(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchDecisionDefinitions(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchDecisionInstances(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchDecisionRequirements(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchElementInstanceIncidents(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchElementInstances(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchElementInstanceWaitStates(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchGlobalTaskListeners(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchGroupIdsForTenant(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchGroups(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchGroupsForRole(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchIncidents(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchJobs(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchMappingRule(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchMappingRulesForGroup(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchMappingRulesForRole(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchMappingRulesForTenant(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchMessageSubscriptions(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchOwnAuthorizations(...a): CancelablePromise<{ authorizationsEnabled: boolean; items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchProcessDefinitions(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchProcessDefinitionVariableNames(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchProcessInstanceIncidents(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchProcessInstances(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchResources(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchRoles(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchRolesForGroup(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchRolesForTenant(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchTenants(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchUsers(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchUsersForGroup(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchUsersForRole(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchUsersForTenant(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchUserTaskAuditLogs(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchUserTaskEffectiveVariables(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchUserTasks(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchUserTaskVariables(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchVariables(...a): CancelablePromise<{ items: object[]; page: { endCursor: | { [key: number]: string; __brand: "EndCursor"; } | null; hasMoreTotalItems: boolean; startCursor: | { [key: number]: string; __brand: "StartCursor"; } | null; totalItems: number; }; }>; searchVariablesAsDto(...a): CancelablePromise<{ raw: { [key: string]: unknown; }; get(...a): unknown; has(...a): boolean; validate(...a): { [key: string]: unknown; }; }>; stopAllWorkers(...a): void; suspendBatchOperation(...a): CancelablePromise; suspendProcessInstance(...a): CancelablePromise; suspendProcessInstancesBatchOperation(...a): CancelablePromise<{ batchOperationKey: string; batchOperationType: BatchOperationTypeEnum; }>; syncRuntimeBackupState(...a): CancelablePromise<{ backupStates: object[]; checkpointStates: object[]; ranges: object[]; }>; syncRuntimeBackupStateAsClusterAdmin(...a): CancelablePromise<{ physicalTenants: object[]; }>; takeHistoryBackup(...a): CancelablePromise<{ backupId: number; scheduledSnapshots: string[]; }>; takeHistoryBackupAsClusterAdmin(...a): CancelablePromise<{ backupId: number; physicalTenants: object[]; }>; takeRuntimeBackup(...a): CancelablePromise<{ backupId: number; }>; takeRuntimeBackupAsClusterAdmin(...a): CancelablePromise<{ physicalTenants: object[]; }>; throwJobError(...a): CancelablePromise; triggerClusterRebalance(...a): CancelablePromise<{ lastCompletedRebalance: | { finishedAt: string; partitions: object[]; rebalanceId: number; result: "COMPLETED" | "FAILED" | "CANCELLED"; startedAt: string; } | null; partitions: object[]; runningRebalance: | { cancelRequested: boolean; dryRun: boolean; partitions: object[]; rebalanceId: number; startedAt: string; } | null; state: "BALANCED" | "BALANCING" | "UNBALANCED"; }>; unassignClientFromGroup(...a): CancelablePromise; unassignClientFromTenant(...a): CancelablePromise; unassignGroupFromTenant(...a): CancelablePromise; unassignMappingRuleFromGroup(...a): CancelablePromise; unassignMappingRuleFromTenant(...a): CancelablePromise; unassignRoleFromClient(...a): CancelablePromise; unassignRoleFromGroup(...a): CancelablePromise; unassignRoleFromMappingRule(...a): CancelablePromise; unassignRoleFromTenant(...a): CancelablePromise; unassignRoleFromUser(...a): CancelablePromise; unassignUserFromGroup(...a): CancelablePromise; unassignUserFromTenant(...a): CancelablePromise; unassignUserTask(...a): CancelablePromise; updateAgentInstance(...a): CancelablePromise<{ createdHistory: object[]; }>; updateAuthorization(...a): CancelablePromise; updateGlobalClusterVariable(...a): CancelablePromise<{ kind: ClusterVariableKindEnum; metadata: { [key: string]: string | number; }; name: string; scope: ClusterVariableScopeEnum; tenantId: string | null; value: string; }>; updateGlobalTaskListener(...a): CancelablePromise<{ afterNonGlobal?: boolean; eventTypes: GlobalTaskListenerEventTypeEnum[]; id: string; priority?: number; retries?: number; source: GlobalListenerSourceEnum; type?: string; }>; updateGroup(...a): CancelablePromise<{ description: string | null; groupId: string; name: string; }>; updateJob(...a): CancelablePromise; updateJobsBatchOperation(...a): CancelablePromise<{ batchOperationKey: string; batchOperationType: BatchOperationTypeEnum; }>; updateMappingRule(...a): CancelablePromise<{ claimName: string; claimValue: string; mappingRuleId: string; name: string; }>; updateRole(...a): CancelablePromise<{ description: string | null; name: string; roleId: string; }>; updateTenant(...a): CancelablePromise<{ description: string | null; name: string; tenantId: string; }>; updateTenantClusterVariable(...a): CancelablePromise<{ kind: ClusterVariableKindEnum; metadata: { [key: string]: string | number; }; name: string; scope: ClusterVariableScopeEnum; tenantId: string | null; value: string; }>; updateUser(...a): CancelablePromise<{ email: string | null; name: string | null; username: string; }>; updateUserTask(...a): CancelablePromise; withCorrelation(...a): Promise; }>; ``` Create a client where all branded key types are widened to string. Use when integrating with external systems or when dynamic string keys are common and brand friction is unwanted. For maximum type safety prefer the strict createCamundaClient. `Loose` rebuilds callable types and drops properties attached to them, which would strip the `.paginate` methods installed on every `search*` operation. We re-apply `WithSearchPagination` on top of the loosened client so loose clients keep `.paginate` in their static type (recomputed from the loosened search signatures), matching the runtime wrappers the constructor installs. ## Parameters ### args ...\[`options?`: [`CamundaOptions`](../interfaces/CamundaOptions.md)\] ## Returns [`WithSearchPagination`](../type-aliases/WithSearchPagination.md)\<\{ `clock`: \{ `deadline`(...`a`): \{ `dispose`: (...`a`) => `void`; `signal`: \{ \}; \}; `now`(...`a`): `number`; `sleep`(...`a`): `Promise`\<`void`\>; \}; `config`: \{ `__raw`: \{ \[`key`: `string`\]: `string` \| `undefined`; \}; `auth`: \{ `basic?`: \{ `password?`: `string`; `username?`: `string`; \}; `strategy`: [`AuthStrategy`](../type-aliases/AuthStrategy.md); \}; `backpressure`: \{ `decayQuietMs`: `number`; `enabled`: `boolean`; `floor`: `number`; `healthyRecoveryMultiplier`: `number`; `initialMax`: `number`; `maxWaiters`: `number`; `observeOnly`: `boolean`; `profile`: `string`; `recoveryIntervalMs`: `number`; `recoveryStep`: `number`; `severeFactor`: `number`; `severeThreshold`: `number`; `softFactor`: `number`; `unlimitedAfterHealthyMs`: `number`; \}; `defaultTenantId`: `string`; `eventual?`: \{ `pollDefaultMs`: `number`; \}; `httpRetry`: \{ `baseDelayMs`: `number`; `maxAttempts`: `number`; `maxDelayMs`: `number`; \}; `logLevel`: `"trace"` \| `"error"` \| `"silent"` \| `"warn"` \| `"info"` \| `"debug"`; `mtls?`: \{ `ca?`: `string`; `caPath?`: `string`; `cert?`: `string`; `certPath?`: `string`; `key?`: `string`; `keyPassphrase?`: `string`; `keyPath?`: `string`; \}; `oauth`: \{ `cacheDir?`: `string`; `clientId?`: `string`; `clientSecret?`: `string`; `grantType`: `string`; `oauthUrl`: `string`; `retry`: \{ `baseDelayMs`: `number`; `max`: `number`; \}; `scope?`: `string`; `timeoutMs`: `number`; \}; `restAddress`: `string`; `supportLog?`: \{ `enabled`: `boolean`; `filePath`: `string`; \}; `telemetry?`: \{ `correlation`: `boolean`; `log`: `boolean`; \}; `tokenAudience`: `string`; `validation`: \{ `raw`: `string`; `req`: [`ValidationMode`](../type-aliases/ValidationMode.md); `res`: [`ValidationMode`](../type-aliases/ValidationMode.md); \}; `workerDefaults?`: \{ `jobTimeoutMs?`: `number`; `maxParallelJobs?`: `number`; `pollTimeoutMs?`: `number`; `startupJitterMaxSeconds?`: `number`; `workerName?`: `string`; \}; \}; `random`: \{ `next`(...`a`): `number`; \}; `_getSupportLogger`(...`a`): \{ `log`(...`a`): `void`; \}; `_invokeWithRetry`(...`a`): `Promise`\<`unknown`\>; `activateAdHocSubProcessActivities`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `activateJobs`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `jobs`: `object`[]; \}\>; `assignClientToGroup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `assignClientToTenant`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `assignGroupToTenant`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `assignMappingRuleToGroup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `assignMappingRuleToTenant`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `assignProcessInstanceBusinessId`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `assignRoleToClient`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `assignRoleToGroup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `assignRoleToMappingRule`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `assignRoleToTenant`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `assignRoleToUser`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `assignUserTask`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `assignUserToGroup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `assignUserToTenant`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `broadcastSignal`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `signalKey`: `string`; `tenantId`: `string`; \}\>; `cancelBatchOperation`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `cancelClusterRebalance`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `wasRunning`: `boolean`; \}\>; `cancelProcessInstance`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `cancelProcessInstancesBatchOperation`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `batchOperationKey`: `string`; `batchOperationType`: [`BatchOperationTypeEnum`](../type-aliases/BatchOperationTypeEnum.md); \}\>; `changeClusterMode`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `changeId`: `string`; `plannedChanges`: `object`[]; \}\>; `changeClusterModeAsClusterAdmin`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `changeId`: `string`; `plannedChanges`: `object`[]; \}\>; `clearAuthCache`(...`a`): `void`; `completeJob`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `completeUserTask`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `configure`(...`a`): `void`; `correlateMessage`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `messageKey`: `string`; `processInstanceKey`: `string`; `tenantId`: `string`; \}\>; `createAdminUser`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `email`: `string` \| `null`; `name`: `string` \| `null`; `username`: `string`; \}\>; `createAgentInstance`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `agentInstanceKey`: `string`; `createdHistory`: `object`[]; \}\>; `createAuthorization`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `authorizationKey`: `string`; \}\>; `createDeployment`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `decisionRequirements`: `object`[]; `decisions`: `object`[]; `deploymentKey`: `string`; `deployments`: `object`[]; `forms`: `object`[]; `processes`: `object`[]; `resources`: `object`[]; `tenantId`: `string`; \}\>; `createDocument`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `camunda.document.type`: `"camunda"`; `contentHash`: `string` \| `null`; `documentId`: `string`; `metadata`: \{ `contentType`: `string`; `customProperties`: \{ \[`key`: `string`\]: `unknown`; \}; `expiresAt`: `string` \| `null`; `fileName`: `string`; `processDefinitionId`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"ProcessDefinitionId"`; \} \| `null`; `processInstanceKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"ProcessInstanceKey"`; \} \| `null`; `size`: `number`; \}; `storeId`: `string`; \}\>; `createDocumentLink`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `expiresAt`: `string`; `url`: `string`; \}\>; `createDocuments`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `createdDocuments`: `object`[]; `failedDocuments`: `object`[]; \}\>; `createElementInstanceVariables`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `createGlobalClusterVariable`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `kind`: [`ClusterVariableKindEnum`](../type-aliases/ClusterVariableKindEnum.md); `metadata`: \{ \[`key`: `string`\]: `string` \| `number`; \}; `name`: `string`; `scope`: [`ClusterVariableScopeEnum`](../type-aliases/ClusterVariableScopeEnum.md); `tenantId`: `string` \| `null`; `value`: `string`; \}\>; `createGlobalTaskListener`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `afterNonGlobal?`: `boolean`; `eventTypes`: [`GlobalTaskListenerEventTypeEnum`](../type-aliases/GlobalTaskListenerEventTypeEnum.md)[]; `id`: `string`; `priority?`: `number`; `retries?`: `number`; `source`: [`GlobalListenerSourceEnum`](../type-aliases/GlobalListenerSourceEnum.md); `type?`: `string`; \}\>; `createGroup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `description`: `string` \| `null`; `groupId`: `string`; `name`: `string`; \}\>; `createJobWorker`(...`a`): \{ `activeJobs`: `number`; `name`: `string`; `stopped`: `boolean`; `start`(...`a`): `void`; `stop`(...`a`): `void`; `stopGracefully`(...`a`): `Promise`\<\{ `remainingJobs`: `number`; `timedOut`: `boolean`; \}\>; \}; `createMappingRule`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `claimName`: `string`; `claimValue`: `string`; `mappingRuleId`: `string`; `name`: `string`; \}\>; `createProcessInstance`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `businessId`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"BusinessId"`; \} \| `null`; `processDefinitionId`: `string`; `processDefinitionKey`: `string`; `processDefinitionVersion`: `number`; `processInstanceKey`: `string`; `tags`: `string`[]; `tenantId`: `string`; `variables`: \{ \[`key`: `string`\]: `unknown`; \}; \}\>; `createRole`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `description`: `string` \| `null`; `name`: `string`; `roleId`: `string`; \}\>; `createTenant`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `description`: `string` \| `null`; `name`: `string`; `tenantId`: `string`; \}\>; `createTenantClusterVariable`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `kind`: [`ClusterVariableKindEnum`](../type-aliases/ClusterVariableKindEnum.md); `metadata`: \{ \[`key`: `string`\]: `string` \| `number`; \}; `name`: `string`; `scope`: [`ClusterVariableScopeEnum`](../type-aliases/ClusterVariableScopeEnum.md); `tenantId`: `string` \| `null`; `value`: `string`; \}\>; `createThreadedJobWorker`(...`a`): \{ `activeJobs`: `number`; `busyThreads`: `number`; `name`: `string`; `poolSize`: `number`; `ready`: `Promise`\<`void`\>; `stopped`: `boolean`; `start`(...`a`): `void`; `stop`(...`a`): `void`; `stopGracefully`(...`a`): `Promise`\<\{ `remainingJobs`: `number`; `timedOut`: `boolean`; \}\>; \}; `createUser`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `email`: `string` \| `null`; `name`: `string` \| `null`; `username`: `string`; \}\>; `deleteAuthorization`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `deleteDecisionInstance`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `deleteDecisionInstancesBatchOperation`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `batchOperationKey`: `string`; `batchOperationType`: [`BatchOperationTypeEnum`](../type-aliases/BatchOperationTypeEnum.md); \}\>; `deleteDocument`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `deleteGlobalClusterVariable`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `deleteGlobalTaskListener`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `deleteGroup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `deleteHistoryBackup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `deleteHistoryBackupAsClusterAdmin`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `deleteMappingRule`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `deleteProcessInstance`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `deleteProcessInstancesBatchOperation`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `batchOperationKey`: `string`; `batchOperationType`: [`BatchOperationTypeEnum`](../type-aliases/BatchOperationTypeEnum.md); \}\>; `deleteResource`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `batchOperation`: \| \{ `batchOperationKey`: `string`; `batchOperationType`: [`BatchOperationTypeEnum`](../type-aliases/BatchOperationTypeEnum.md); \} \| `null`; `resourceKey`: `string`; \}\>; `deleteRole`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `deleteRuntimeBackup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `deleteRuntimeBackupAsClusterAdmin`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `deleteRuntimeBackupState`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `deleteRuntimeBackupStateAsClusterAdmin`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `deleteTenant`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `deleteTenantClusterVariable`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `deleteUser`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `deployResourcesFromFiles`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `decisionRequirements`: `object`[]; `decisions`: `object`[]; `deploymentKey`: `string`; `deployments`: `object`[]; `forms`: `object`[]; `processes`: `object`[]; `resources`: `object`[]; `tenantId`: `string`; \}\>; `emitSupportLogPreamble`(...`a`): `void`; `evaluateConditionals`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `conditionalEvaluationKey`: `string`; `processInstances`: `object`[]; `tenantId`: `string`; \}\>; `evaluateDecision`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `decisionDefinitionId`: `string`; `decisionDefinitionKey`: `string`; `decisionDefinitionName`: `string`; `decisionDefinitionVersion`: `number`; `decisionEvaluationKey`: `string`; `decisionInstanceKey`: `string`; `decisionRequirementsId`: `string`; `decisionRequirementsKey`: `string`; `evaluatedDecisions`: `object`[]; `failedDecisionDefinitionId`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"DecisionDefinitionId"`; \} \| `null`; `failureMessage`: `string` \| `null`; `output`: `string`; `tenantId`: `string`; \}\>; `evaluateExpression`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `expression`: `string`; `referencedSecrets`: `object`[]; `result`: `unknown`; `warnings`: `object`[]; \}\>; `failJob`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `forceAuthRefresh`(...`a`): `Promise`\<`string` \| `undefined`\>; `getAgentDefinition`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `agentDefinitionKey`: `string`; `agentType`: [`AgentDefinitionTypeEnum`](../type-aliases/AgentDefinitionTypeEnum.md); `elementId`: `string`; `name`: `string`; `processDefinitionId`: `string`; `processDefinitionKey`: `string`; `processDefinitionVersion`: `number`; `processDefinitionVersionTag`: `string` \| `null`; `tenantId`: `string`; \}\>; `getAgentInstance`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `agentDefinitionKey`: `string`; `agentInstanceKey`: `string`; `completionDate`: `string` \| `null`; `creationDate`: `string`; `definition`: \{ `model`: `string`; `provider`: `string`; `systemPrompt`: ( \| \{ `contentType`: `"TEXT"`; `text`: `string`; \} \| \{ `contentType`: `"DOCUMENT"`; `documentReference`: \{ `camunda.document.type`: `"camunda"`; `contentHash`: `string` \| `null`; `documentId`: `string`; `metadata`: \{ `contentType`: `string`; `customProperties`: \{ \[`key`: ...\]: ...; \}; `expiresAt`: ... \| ...; `fileName`: `string`; `processDefinitionId`: ... \| ...; `processInstanceKey`: ... \| ...; `size`: `number`; \}; `storeId`: `string`; \}; \} \| \{ `contentType`: `"OBJECT"`; `object`: `unknown`; \})[]; \}; `elementId`: `string`; `elementInstanceKeys`: `string`[]; `lastUpdatedDate`: `string`; `limits`: \{ `maxModelCalls`: `number`; `maxTokens`: `number`; `maxToolCalls`: `number`; \}; `metrics`: \{ `cacheCreationTokenCount`: `number`; `cacheReadTokenCount`: `number`; `inputTokens`: `number`; `modelCalls`: `number`; `outputTokens`: `number`; `reasoningTokenCount`: `number`; `toolCalls`: `number`; \}; `processDefinitionId`: `string`; `processDefinitionKey`: `string`; `processDefinitionVersion`: `number`; `processDefinitionVersionTag`: `string` \| `null`; `processInstanceKey`: `string`; `rootProcessInstanceKey`: `string`; `status`: [`AgentInstanceStatusEnum`](../type-aliases/AgentInstanceStatusEnum.md); `tenantId`: `string`; `tools`: `object`[]; \}\>; `getAuditLog`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `actorId`: `string` \| `null`; `actorType`: \| [`AuditLogActorTypeEnum`](../type-aliases/AuditLogActorTypeEnum.md) \| `null`; `agentElementId`: `string` \| `null`; `auditLogKey`: `string`; `batchOperationKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"BatchOperationKey"`; \} \| `null`; `batchOperationType`: \| [`BatchOperationTypeEnum`](../type-aliases/BatchOperationTypeEnum.md) \| `null`; `category`: [`AuditLogCategoryEnum`](../type-aliases/AuditLogCategoryEnum.md); `decisionDefinitionId`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"DecisionDefinitionId"`; \} \| `null`; `decisionDefinitionKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"DecisionDefinitionKey"`; \} \| `null`; `decisionEvaluationKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"DecisionEvaluationKey"`; \} \| `null`; `decisionRequirementsId`: `string` \| `null`; `decisionRequirementsKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"DecisionRequirementsKey"`; \} \| `null`; `deploymentKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"DeploymentKey"`; \} \| `null`; `elementInstanceKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"ElementInstanceKey"`; \} \| `null`; `entityDescription`: `string` \| `null`; `entityKey`: `string`; `entityType`: [`AuditLogEntityTypeEnum`](../type-aliases/AuditLogEntityTypeEnum.md); `formKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"FormKey"`; \} \| `null`; `inboundChannelToolName`: `string` \| `null`; `inboundChannelType`: `string` \| `null`; `jobKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"JobKey"`; \} \| `null`; `operationType`: [`AuditLogOperationTypeEnum`](../type-aliases/AuditLogOperationTypeEnum.md); `processDefinitionId`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"ProcessDefinitionId"`; \} \| `null`; `processDefinitionKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"ProcessDefinitionKey"`; \} \| `null`; `processInstanceKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"ProcessInstanceKey"`; \} \| `null`; `relatedEntityKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"AuditLogEntityKey"`; \} \| `null`; `relatedEntityType`: \| [`AuditLogEntityTypeEnum`](../type-aliases/AuditLogEntityTypeEnum.md) \| `null`; `resourceKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"ProcessDefinitionKey"`; \} \| \{ \[`key`: `number`\]: `string`; `__brand`: `"DecisionRequirementsKey"`; \} \| \{ \[`key`: `number`\]: `string`; `__brand`: `"DecisionDefinitionKey"`; \} \| \{ \[`key`: `number`\]: `string`; `__brand`: `"FormKey"`; \} \| `null`; `result`: [`AuditLogResultEnum`](../type-aliases/AuditLogResultEnum.md); `rootProcessInstanceKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"ProcessInstanceKey"`; \} \| `null`; `tenantId`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"TenantId"`; \} \| `null`; `timestamp`: `string`; `userTaskKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"UserTaskKey"`; \} \| `null`; \}\>; `getAuthentication`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `authorizedComponents`: `string`[]; `c8Links`: \{ \[`key`: `string`\]: `string`; \}; `canLogout`: `boolean`; `displayName`: `string` \| `null`; `email`: `string` \| `null`; `groups`: `string`[]; `roles`: `string`[]; `salesPlanType`: `string` \| `null`; `tenants`: `object`[]; `username`: `string`; \}\>; `getAuthHeaders`(...`a`): `Promise`\<\{ \[`key`: `string`\]: `string`; \}\>; `getAuthorization`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `authorizationKey`: `string`; `ownerId`: `string`; `ownerType`: [`OwnerTypeEnum`](../type-aliases/OwnerTypeEnum.md); `permissionTypes`: [`PermissionTypeEnum`](../type-aliases/PermissionTypeEnum.md)[]; `resourceId`: `string` \| `null`; `resourcePropertyName`: `string` \| `null`; `resourceType`: [`ResourceTypeEnum`](../type-aliases/ResourceTypeEnum.md); \}\>; `getBackpressureState`(...`a`): \| \{ `backoffMs`: `number`; `consecutive`: `number`; `permitsCurrent`: `number`; `permitsMax`: `number` \| `null`; `severity`: [`BackpressureSeverity`](../type-aliases/BackpressureSeverity.md); `waiters`: `number`; \} \| \{ `consecutive`: `number`; `permitsCurrent`: `number`; `permitsMax`: `null`; `severity`: `string`; `waiters`: `number`; \}; `getBatchOperation`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `actorId`: `string` \| `null`; `actorType`: \| [`AuditLogActorTypeEnum`](../type-aliases/AuditLogActorTypeEnum.md) \| `null`; `batchOperationKey`: `string`; `batchOperationType`: \| [`BatchOperationTypeEnum`](../type-aliases/BatchOperationTypeEnum.md) \| `null`; `endDate`: `string` \| `null`; `errors`: `object`[]; `operationsCompletedCount`: `number`; `operationsFailedCount`: `number`; `operationsTotalCount`: `number`; `startDate`: `string` \| `null`; `state`: [`BatchOperationStateEnum`](../type-aliases/BatchOperationStateEnum.md); \}\>; `getClusterExportingStatus`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `status`: [`ExportingStatusCode`](../type-aliases/ExportingStatusCode.md); \}\>; `getClusterRebalance`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `lastCompletedRebalance`: \| \{ `finishedAt`: `string`; `partitions`: `object`[]; `rebalanceId`: `number`; `result`: `"COMPLETED"` \| `"FAILED"` \| `"CANCELLED"`; `startedAt`: `string`; \} \| `null`; `partitions`: `object`[]; `runningRebalance`: \| \{ `cancelRequested`: `boolean`; `dryRun`: `boolean`; `partitions`: `object`[]; `rebalanceId`: `number`; `startedAt`: `string`; \} \| `null`; `state`: `"BALANCED"` \| `"BALANCING"` \| `"UNBALANCED"`; \}\>; `getClusterStatus`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `status`: `"HEALTHY"` \| `"DEGRADED"` \| `"DOWN"`; \}\>; `getClusterTopology`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `brokers`: `object`[]; `clusterId`: `string` \| `null`; `clusterSize`: `number`; `gatewayVersion`: `string` \| `null`; `physicalTenants`: `object`[]; \}\>; `getClusterUpgradeStatus`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `status`: `"UNKNOWN"` \| `"MIGRATED"` \| `"MIGRATION_IN_PROGRESS"`; \}\>; `getConfig`(...`a`): \{ `__raw`: \{ \[`key`: `string`\]: `string` \| `undefined`; \}; `auth`: \{ `basic?`: \{ `password?`: `string`; `username?`: `string`; \}; `strategy`: [`AuthStrategy`](../type-aliases/AuthStrategy.md); \}; `backpressure`: \{ `decayQuietMs`: `number`; `enabled`: `boolean`; `floor`: `number`; `healthyRecoveryMultiplier`: `number`; `initialMax`: `number`; `maxWaiters`: `number`; `observeOnly`: `boolean`; `profile`: `string`; `recoveryIntervalMs`: `number`; `recoveryStep`: `number`; `severeFactor`: `number`; `severeThreshold`: `number`; `softFactor`: `number`; `unlimitedAfterHealthyMs`: `number`; \}; `defaultTenantId`: `string`; `eventual?`: \{ `pollDefaultMs`: `number`; \}; `httpRetry`: \{ `baseDelayMs`: `number`; `maxAttempts`: `number`; `maxDelayMs`: `number`; \}; `logLevel`: `"trace"` \| `"error"` \| `"silent"` \| `"warn"` \| `"info"` \| `"debug"`; `mtls?`: \{ `ca?`: `string`; `caPath?`: `string`; `cert?`: `string`; `certPath?`: `string`; `key?`: `string`; `keyPassphrase?`: `string`; `keyPath?`: `string`; \}; `oauth`: \{ `cacheDir?`: `string`; `clientId?`: `string`; `clientSecret?`: `string`; `grantType`: `string`; `oauthUrl`: `string`; `retry`: \{ `baseDelayMs`: `number`; `max`: `number`; \}; `scope?`: `string`; `timeoutMs`: `number`; \}; `restAddress`: `string`; `supportLog?`: \{ `enabled`: `boolean`; `filePath`: `string`; \}; `telemetry?`: \{ `correlation`: `boolean`; `log`: `boolean`; \}; `tokenAudience`: `string`; `validation`: \{ `raw`: `string`; `req`: [`ValidationMode`](../type-aliases/ValidationMode.md); `res`: [`ValidationMode`](../type-aliases/ValidationMode.md); \}; `workerDefaults?`: \{ `jobTimeoutMs?`: `number`; `maxParallelJobs?`: `number`; `pollTimeoutMs?`: `number`; `startupJitterMaxSeconds?`: `number`; `workerName?`: `string`; \}; \}; `getDecisionDefinition`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `decisionDefinitionId`: `string`; `decisionDefinitionKey`: `string`; `decisionRequirementsId`: `string`; `decisionRequirementsKey`: `string`; `decisionRequirementsName`: `string`; `decisionRequirementsVersion`: `number`; `name`: `string`; `tenantId`: `string`; `version`: `number`; \}\>; `getDecisionDefinitionXml`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`string`\>; `getDecisionInstance`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `businessId`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"BusinessId"`; \} \| `null`; `decisionDefinitionId`: `string`; `decisionDefinitionKey`: `string`; `decisionDefinitionName`: `string`; `decisionDefinitionType`: [`DecisionDefinitionTypeEnum`](../type-aliases/DecisionDefinitionTypeEnum.md); `decisionDefinitionVersion`: `number`; `decisionEvaluationInstanceKey`: `string`; `decisionEvaluationKey`: `string`; `elementInstanceKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"ElementInstanceKey"`; \} \| `null`; `evaluatedInputs`: `object`[]; `evaluationDate`: `string`; `evaluationFailure`: `string` \| `null`; `matchedRules`: `object`[]; `processDefinitionKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"ProcessDefinitionKey"`; \} \| `null`; `processInstanceKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"ProcessInstanceKey"`; \} \| `null`; `result`: `string`; `rootDecisionDefinitionKey`: `string`; `rootProcessInstanceKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"ProcessInstanceKey"`; \} \| `null`; `state`: [`DecisionInstanceStateEnum`](../type-aliases/DecisionInstanceStateEnum.md); `tenantId`: `string`; \}\>; `getDecisionRequirements`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `decisionRequirementsId`: `string`; `decisionRequirementsKey`: `string`; `decisionRequirementsName`: `string`; `resourceName`: `string`; `tenantId`: `string`; `version`: `number`; \}\>; `getDecisionRequirementsXml`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`string`\>; `getDocument`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ \}\>; `getElementInstance`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `elementId`: `string`; `elementInstanceKey`: `string`; `elementName`: `string`; `endDate`: `string` \| `null`; `hasIncident`: `boolean`; `incidentKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"IncidentKey"`; \} \| `null`; `processDefinitionId`: `string`; `processDefinitionKey`: `string`; `processInstanceKey`: `string`; `rootProcessInstanceKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"ProcessInstanceKey"`; \} \| `null`; `startDate`: `string`; `state`: [`ElementInstanceStateEnum`](../type-aliases/ElementInstanceStateEnum.md); `tenantId`: `string`; `type`: \| `"UNKNOWN"` \| `"USER_TASK"` \| `"UNSPECIFIED"` \| `"PROCESS"` \| `"SUB_PROCESS"` \| `"EVENT_SUB_PROCESS"` \| `"AD_HOC_SUB_PROCESS"` \| `"AD_HOC_SUB_PROCESS_INNER_INSTANCE"` \| `"START_EVENT"` \| `"INTERMEDIATE_CATCH_EVENT"` \| `"INTERMEDIATE_THROW_EVENT"` \| `"BOUNDARY_EVENT"` \| `"END_EVENT"` \| `"SERVICE_TASK"` \| `"RECEIVE_TASK"` \| `"MANUAL_TASK"` \| `"TASK"` \| `"EXCLUSIVE_GATEWAY"` \| `"INCLUSIVE_GATEWAY"` \| `"PARALLEL_GATEWAY"` \| `"EVENT_BASED_GATEWAY"` \| `"SEQUENCE_FLOW"` \| `"MULTI_INSTANCE_BODY"` \| `"CALL_ACTIVITY"` \| `"BUSINESS_RULE_TASK"` \| `"SCRIPT_TASK"` \| `"SEND_TASK"`; \}\>; `getErrorMode`(...`a`): `"throw"` \| `"result"`; `getExportingStatus`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `status`: [`ExportingStatusCode`](../type-aliases/ExportingStatusCode.md); \}\>; `getFormByKey`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `formId`: `string`; `formKey`: `string`; `schema`: `string`; `tenantId`: `string`; `version`: `number`; \}\>; `getGlobalClusterVariable`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `kind`: [`ClusterVariableKindEnum`](../type-aliases/ClusterVariableKindEnum.md); `metadata`: \{ \[`key`: `string`\]: `string` \| `number`; \}; `name`: `string`; `scope`: [`ClusterVariableScopeEnum`](../type-aliases/ClusterVariableScopeEnum.md); `tenantId`: `string` \| `null`; `value`: `string`; \}\>; `getGlobalJobStatistics`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `completed`: \{ `count`: `number`; `lastUpdatedAt`: `string` \| `null`; \}; `created`: \{ `count`: `number`; `lastUpdatedAt`: `string` \| `null`; \}; `failed`: \{ `count`: `number`; `lastUpdatedAt`: `string` \| `null`; \}; `isIncomplete`: `boolean`; \}\>; `getGlobalTaskListener`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `afterNonGlobal?`: `boolean`; `eventTypes`: [`GlobalTaskListenerEventTypeEnum`](../type-aliases/GlobalTaskListenerEventTypeEnum.md)[]; `id`: `string`; `priority?`: `number`; `retries?`: `number`; `source`: [`GlobalListenerSourceEnum`](../type-aliases/GlobalListenerSourceEnum.md); `type?`: `string`; \}\>; `getGroup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `description`: `string` \| `null`; `groupId`: `string`; `name`: `string`; \}\>; `getHistoryBackup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `backupId`: `number`; `details`: `object`[]; `failureReason`: `string` \| `null`; `state`: [`HistoryBackupStateCode`](../type-aliases/HistoryBackupStateCode.md); \}\>; `getHistoryBackupAsClusterAdmin`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `backupId`: `number`; `physicalTenants`: `object`[]; \}\>; `getIncident`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `creationTime`: `string`; `elementId`: `string`; `elementInstanceKey`: `string`; `errorMessage`: `string`; `errorType`: [`IncidentErrorTypeEnum`](../type-aliases/IncidentErrorTypeEnum.md); `incidentKey`: `string`; `jobKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"JobKey"`; \} \| `null`; `processDefinitionId`: `string`; `processDefinitionKey`: `string`; `processInstanceKey`: `string`; `rootProcessInstanceKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"ProcessInstanceKey"`; \} \| `null`; `state`: [`IncidentStateEnum`](../type-aliases/IncidentStateEnum.md); `tenantId`: `string`; \}\>; `getJobErrorStatistics`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `getJobTimeSeriesStatistics`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `getJobTypeStatistics`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `getJobWorkerStatistics`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `getLicense`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `expiresAt`: `string` \| `null`; `isCommercial`: `boolean`; `licenseType`: `string`; `validLicense`: `boolean`; \}\>; `getMappingRule`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `claimName`: `string`; `claimValue`: `string`; `mappingRuleId`: `string`; `name`: `string`; \}\>; `getProcessDefinition`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `hasStartForm`: `boolean`; `name`: `string` \| `null`; `processDefinitionId`: `string`; `processDefinitionKey`: `string`; `resourceName`: `string`; `state`: `"ACTIVE"` \| `"DRAINING"` \| `"DELETED"`; `tenantId`: `string`; `version`: `number`; `versionTag`: `string` \| `null`; \}\>; `getProcessDefinitionInstanceStatistics`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `getProcessDefinitionInstanceVersionStatistics`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `getProcessDefinitionMessageSubscriptionStatistics`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `getProcessDefinitionStatistics`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; \}\>; `getProcessDefinitionXml`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`string`\>; `getProcessInstance`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `businessId`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"BusinessId"`; \} \| `null`; `endDate`: `string` \| `null`; `hasIncident`: `boolean`; `parentElementInstanceKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"ElementInstanceKey"`; \} \| `null`; `parentProcessInstanceKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"ProcessInstanceKey"`; \} \| `null`; `processDefinitionId`: `string`; `processDefinitionKey`: `string`; `processDefinitionName`: `string` \| `null`; `processDefinitionVersion`: `number`; `processDefinitionVersionTag`: `string` \| `null`; `processInstanceKey`: `string`; `rootProcessInstanceKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"ProcessInstanceKey"`; \} \| `null`; `startDate`: `string`; `state`: [`ProcessInstanceStateEnum`](../type-aliases/ProcessInstanceStateEnum.md); `suspendedDate`: `string` \| `null`; `tags`: `string`[]; `tenantId`: `string`; \}\>; `getProcessInstanceCallHierarchy`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`object`[]\>; `getProcessInstanceSequenceFlows`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; \}\>; `getProcessInstanceStatistics`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; \}\>; `getProcessInstanceStatisticsByDefinition`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `getProcessInstanceStatisticsByError`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `getProcessInstanceWaitStateStatistics`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; \}\>; `getResource`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `resourceId`: `string`; `resourceKey`: `string`; `resourceName`: `string`; `tenantId`: `string`; `version`: `number`; `versionTag`: `string` \| `null`; \}\>; `getResourceContent`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ \[`key`: `string`\]: `unknown`; \}\>; `getResourceContentBinary`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ \}\>; `getRestoreStatus`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `brokers`: `object`[]; `changeId`: `string`; `startedAt`: `string` \| `null`; `status`: `"COMPLETED"` \| `"FAILED"` \| `"IN_PROGRESS"` \| `"CANCELLED"`; \}\>; `getRole`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `description`: `string` \| `null`; `name`: `string`; `roleId`: `string`; \}\>; `getRuntimeBackup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `backupId`: `number`; `details`: `object`[]; `failureReason`: `string` \| `null`; `state`: [`StateCode`](../type-aliases/StateCode.md); \}\>; `getRuntimeBackupAsClusterAdmin`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `backupId`: `number`; `failureReason`: `string` \| `null`; `physicalTenants`: `object`[]; `state`: [`StateCode`](../type-aliases/StateCode.md); \}\>; `getRuntimeBackupState`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `backupStates`: `object`[]; `checkpointStates`: `object`[]; `ranges`: `object`[]; \}\>; `getRuntimeBackupStateAsClusterAdmin`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `physicalTenants`: `object`[]; \}\>; `getStartProcessForm`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\< \| `void` \| \{ `formId`: `string`; `formKey`: `string`; `schema`: `string`; `tenantId`: `string`; `version`: `number`; \}\>; `getStatus`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `getSystemConfiguration`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `authentication`: \{ `canLogout`: `boolean`; `isCamundaGroupsEnabled`: `boolean`; `isLoginDelegated`: `boolean`; \}; `cloud`: \{ `stage`: [`CloudStage`](../type-aliases/CloudStage.md) \| `null`; \}; `components`: \{ `active`: [`WebappComponent`](../type-aliases/WebappComponent.md)[]; \}; `deployment`: \{ `isMultiTenancyEnabled`: `boolean`; `isTenantsApiEnabled`: `boolean`; `maxRequestSize`: `number`; \}; `jobMetrics`: \{ `enabled`: `boolean`; `exportInterval`: `string`; `maxJobTypeLength`: `number`; `maxTenantIdLength`: `number`; `maxUniqueKeys`: `number`; `maxWorkerNameLength`: `number`; \}; \}\>; `getTenant`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `description`: `string` \| `null`; `name`: `string`; `tenantId`: `string`; \}\>; `getTenantClusterVariable`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `kind`: [`ClusterVariableKindEnum`](../type-aliases/ClusterVariableKindEnum.md); `metadata`: \{ \[`key`: `string`\]: `string` \| `number`; \}; `name`: `string`; `scope`: [`ClusterVariableScopeEnum`](../type-aliases/ClusterVariableScopeEnum.md); `tenantId`: `string` \| `null`; `value`: `string`; \}\>; `getTopology`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `brokers`: `object`[]; `clusterId`: `string` \| `null`; `clusterSize`: `number`; `gatewayVersion`: `string`; `lastCompletedChangeId`: `string`; `partitionsCount`: `number`; `replicationFactor`: `number`; \}\>; `getUsageMetrics`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `activeTenants`: `number`; `assignees`: `number`; `decisionInstances`: `number`; `processInstances`: `number`; `tenants`: \{ \[`key`: `string`\]: `object`; \}; \}\>; `getUser`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `email`: `string` \| `null`; `name`: `string` \| `null`; `username`: `string`; \}\>; `getUserTask`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `assignee`: `string` \| `null`; `businessId`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"BusinessId"`; \} \| `null`; `candidateGroups`: `string`[]; `candidateUsers`: `string`[]; `completionDate`: `string` \| `null`; `creationDate`: `string`; `customHeaders`: \{ \[`key`: `string`\]: `string`; \}; `dueDate`: `string` \| `null`; `elementId`: `string`; `elementInstanceKey`: `string`; `externalFormReference`: `string` \| `null`; `followUpDate`: `string` \| `null`; `formKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"FormKey"`; \} \| `null`; `isSuspended`: `boolean`; `name`: `string` \| `null`; `priority`: `number`; `processDefinitionId`: `string`; `processDefinitionKey`: `string`; `processDefinitionVersion`: `number`; `processInstanceKey`: `string`; `processName`: `string` \| `null`; `rootProcessInstanceKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"ProcessInstanceKey"`; \} \| `null`; `state`: [`UserTaskStateEnum`](../type-aliases/UserTaskStateEnum.md); `tags`: `string`[]; `tenantId`: `string`; `userTaskKey`: `string`; \}\>; `getUserTaskForm`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\< \| `void` \| \{ `formId`: `string`; `formKey`: `string`; `schema`: `string`; `tenantId`: `string`; `version`: `number`; \}\>; `getVariable`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `name`: `string`; `processInstanceKey`: `string`; `rootProcessInstanceKey`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"ProcessInstanceKey"`; \} \| `null`; `scopeKey`: `string`; `tenantId`: `string`; `value`: `string`; `variableKey`: `string`; \}\>; `getWorkers`(...`a`): `any`[]; `listHistoryBackups`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`object`[]\>; `listHistoryBackupsAsClusterAdmin`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`object`[]\>; `listRuntimeBackups`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`object`[]\>; `listRuntimeBackupsAsClusterAdmin`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`object`[]\>; `listSecrets`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `references`: `string`[]; \}\>; `logger`(...`a`): \{ `code`(...`a`): `void`; `debug`(...`a`): `void`; `error`(...`a`): `void`; `info`(...`a`): `void`; `level`(...`a`): [`LogLevel`](../../logger/type-aliases/LogLevel.md); `scope`(...`a`): \{ level: () =\> LogLevel; setLevel: (level: LogLevel) =\> void; setTransport: (t?: ((e: \{ level: LogLevel; scope: string; ts: number; args: any\[\]; code?: string \| undefined; data?: any; \}) =\> void) \| undefined) =\> void; ... 7 more ...; code: (level: LogLevel, code: string, msg: string, data?: any) =\> void; \}; `setLevel`(...`a`): `void`; `setTransport`(...`a`): `void`; `silly`(...`a`): `void`; `trace`(...`a`): `void`; `warn`(...`a`): `void`; \}; `migrateProcessInstance`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `migrateProcessInstancesBatchOperation`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `batchOperationKey`: `string`; `batchOperationType`: [`BatchOperationTypeEnum`](../type-aliases/BatchOperationTypeEnum.md); \}\>; `modifyProcessInstance`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `modifyProcessInstancesBatchOperation`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `batchOperationKey`: `string`; `batchOperationType`: [`BatchOperationTypeEnum`](../type-aliases/BatchOperationTypeEnum.md); \}\>; `onAuthHeaders`(...`a`): `void`; `pauseClusterExporting`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `pauseExporting`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `pinClock`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `publishMessage`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `messageKey`: `string`; `tenantId`: `string`; \}\>; `resetClock`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `resolveIncident`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `resolveIncidentsBatchOperation`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `batchOperationKey`: `string`; `batchOperationType`: [`BatchOperationTypeEnum`](../type-aliases/BatchOperationTypeEnum.md); \}\>; `resolveProcessInstanceIncidents`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `batchOperationKey`: `string`; `batchOperationType`: [`BatchOperationTypeEnum`](../type-aliases/BatchOperationTypeEnum.md); \}\>; `resolveSecrets`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `errors`: `object`[]; `resolved`: `object`[]; \}\>; `restore`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `changeId`: `string`; `plannedChanges`: `object`[]; \}\>; `restoreAsClusterAdmin`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `changeId`: `string`; `plannedChanges`: `object`[]; \}\>; `resumeBatchOperation`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `resumeClusterExporting`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `resumeExporting`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `resumeProcessInstance`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `resumeProcessInstancesBatchOperation`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `batchOperationKey`: `string`; `batchOperationType`: [`BatchOperationTypeEnum`](../type-aliases/BatchOperationTypeEnum.md); \}\>; `searchAgentDefinitions`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchAgentInstanceHistory`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchAgentInstances`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchAuditLogs`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchAuthorizations`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchBatchOperationItems`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchBatchOperations`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchClientsForGroup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchClientsForRole`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchClientsForTenant`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchClusterVariables`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchCorrelatedMessageSubscriptions`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchDecisionDefinitions`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchDecisionInstances`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchDecisionRequirements`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchElementInstanceIncidents`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchElementInstances`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchElementInstanceWaitStates`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchGlobalTaskListeners`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchGroupIdsForTenant`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchGroups`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchGroupsForRole`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchIncidents`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchJobs`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchMappingRule`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchMappingRulesForGroup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchMappingRulesForRole`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchMappingRulesForTenant`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchMessageSubscriptions`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchOwnAuthorizations`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `authorizationsEnabled`: `boolean`; `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchProcessDefinitions`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchProcessDefinitionVariableNames`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchProcessInstanceIncidents`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchProcessInstances`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchResources`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchRoles`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchRolesForGroup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchRolesForTenant`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchTenants`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchUsers`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchUsersForGroup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchUsersForRole`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchUsersForTenant`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchUserTaskAuditLogs`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchUserTaskEffectiveVariables`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchUserTasks`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchUserTaskVariables`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchVariables`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `items`: `object`[]; `page`: \{ `endCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"EndCursor"`; \} \| `null`; `hasMoreTotalItems`: `boolean`; `startCursor`: \| \{ \[`key`: `number`\]: `string`; `__brand`: `"StartCursor"`; \} \| `null`; `totalItems`: `number`; \}; \}\>; `searchVariablesAsDto`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `raw`: \{ \[`key`: `string`\]: `unknown`; \}; `get`(...`a`): `unknown`; `has`(...`a`): `boolean`; `validate`(...`a`): \{ \[`key`: `string`\]: `unknown`; \}; \}\>; `stopAllWorkers`(...`a`): `void`; `suspendBatchOperation`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `suspendProcessInstance`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `suspendProcessInstancesBatchOperation`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `batchOperationKey`: `string`; `batchOperationType`: [`BatchOperationTypeEnum`](../type-aliases/BatchOperationTypeEnum.md); \}\>; `syncRuntimeBackupState`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `backupStates`: `object`[]; `checkpointStates`: `object`[]; `ranges`: `object`[]; \}\>; `syncRuntimeBackupStateAsClusterAdmin`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `physicalTenants`: `object`[]; \}\>; `takeHistoryBackup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `backupId`: `number`; `scheduledSnapshots`: `string`[]; \}\>; `takeHistoryBackupAsClusterAdmin`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `backupId`: `number`; `physicalTenants`: `object`[]; \}\>; `takeRuntimeBackup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `backupId`: `number`; \}\>; `takeRuntimeBackupAsClusterAdmin`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `physicalTenants`: `object`[]; \}\>; `throwJobError`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `triggerClusterRebalance`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `lastCompletedRebalance`: \| \{ `finishedAt`: `string`; `partitions`: `object`[]; `rebalanceId`: `number`; `result`: `"COMPLETED"` \| `"FAILED"` \| `"CANCELLED"`; `startedAt`: `string`; \} \| `null`; `partitions`: `object`[]; `runningRebalance`: \| \{ `cancelRequested`: `boolean`; `dryRun`: `boolean`; `partitions`: `object`[]; `rebalanceId`: `number`; `startedAt`: `string`; \} \| `null`; `state`: `"BALANCED"` \| `"BALANCING"` \| `"UNBALANCED"`; \}\>; `unassignClientFromGroup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `unassignClientFromTenant`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `unassignGroupFromTenant`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `unassignMappingRuleFromGroup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `unassignMappingRuleFromTenant`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `unassignRoleFromClient`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `unassignRoleFromGroup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `unassignRoleFromMappingRule`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `unassignRoleFromTenant`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `unassignRoleFromUser`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `unassignUserFromGroup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `unassignUserFromTenant`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `unassignUserTask`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `updateAgentInstance`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `createdHistory`: `object`[]; \}\>; `updateAuthorization`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `updateGlobalClusterVariable`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `kind`: [`ClusterVariableKindEnum`](../type-aliases/ClusterVariableKindEnum.md); `metadata`: \{ \[`key`: `string`\]: `string` \| `number`; \}; `name`: `string`; `scope`: [`ClusterVariableScopeEnum`](../type-aliases/ClusterVariableScopeEnum.md); `tenantId`: `string` \| `null`; `value`: `string`; \}\>; `updateGlobalTaskListener`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `afterNonGlobal?`: `boolean`; `eventTypes`: [`GlobalTaskListenerEventTypeEnum`](../type-aliases/GlobalTaskListenerEventTypeEnum.md)[]; `id`: `string`; `priority?`: `number`; `retries?`: `number`; `source`: [`GlobalListenerSourceEnum`](../type-aliases/GlobalListenerSourceEnum.md); `type?`: `string`; \}\>; `updateGroup`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `description`: `string` \| `null`; `groupId`: `string`; `name`: `string`; \}\>; `updateJob`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `updateJobsBatchOperation`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `batchOperationKey`: `string`; `batchOperationType`: [`BatchOperationTypeEnum`](../type-aliases/BatchOperationTypeEnum.md); \}\>; `updateMappingRule`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `claimName`: `string`; `claimValue`: `string`; `mappingRuleId`: `string`; `name`: `string`; \}\>; `updateRole`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `description`: `string` \| `null`; `name`: `string`; `roleId`: `string`; \}\>; `updateTenant`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `description`: `string` \| `null`; `name`: `string`; `tenantId`: `string`; \}\>; `updateTenantClusterVariable`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `kind`: [`ClusterVariableKindEnum`](../type-aliases/ClusterVariableKindEnum.md); `metadata`: \{ \[`key`: `string`\]: `string` \| `number`; \}; `name`: `string`; `scope`: [`ClusterVariableScopeEnum`](../type-aliases/ClusterVariableScopeEnum.md); `tenantId`: `string` \| `null`; `value`: `string`; \}\>; `updateUser`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<\{ `email`: `string` \| `null`; `name`: `string` \| `null`; `username`: `string`; \}\>; `updateUserTask`(...`a`): [`CancelablePromise`](../interfaces/CancelablePromise.md)\<`void`\>; `withCorrelation`(...`a`): `Promise`\<`unknown`\>; \}\> --- ## Function: createCamundaResultClient() ```ts function createCamundaResultClient(options?): CamundaResultClient; ``` **`Experimental`** This feature is under development and is not guaranteed to be fully tested or stable. ## Parameters ### options? [`CamundaOptions`](../interfaces/CamundaOptions.md) ## Returns [`CamundaResultClient`](../type-aliases/CamundaResultClient.md) ## Description Factory returning a proxy that mirrors the CamundaClient surface but never throws. All async returning methods (Promise or CancelablePromise) are wrapped into Promise>. Synchronous utility methods (e.g. logger(), getConfig()) are passed through unchanged. --- ## Function: createEngineClock() ```ts function createEngineClock(target, options?): EngineClock; ``` A clock bound to the engine's own clock, so client cadence and engine time advance together. This is the point of the whole exercise. The engine has been pinnable for a long time (`PUT /clock`), but the SDK's poll loops ran on the platform timer, so the two were decoupled: you could pin the engine and the worker would still poll on real time. A worker waiting on something that never becomes ready burned real seconds inside a test that was otherwise deterministic. Here `sleep` does not wait — it moves the engine forward by the requested duration and returns. A poll loop therefore _drives_ engine time rather than racing it, and a test that would have taken a real minute finishes as fast as the requests complete. Intended for tests and embedded scenarios that own the engine. Pinning is global to the cluster, so never point one of these at an environment shared with anything else. `target` must be a client that is _not_ itself configured with this clock. The client's HTTP retry sleeps on its injected clock, so a self-referential setup would have a failed `pinClock` back off through `sleep`, which issues another `pinClock`, and so on. ## Parameters ### target [`EngineClockTarget`](../interfaces/EngineClockTarget.md) ### options? #### start? `number` ## Returns [`EngineClock`](../interfaces/EngineClock.md) --- ## Function: createLiveClock() ```ts function createLiveClock(source?): Clock; ``` The live clock: the platform clock, made non-decreasing and self-correcting. This is the single place the SDK runtime is allowed to read ambient time or use a platform timer. Everything else takes a `Clock`. Ruling 2a requires three properties together, and the C# pilot shipped three implementations that each satisfied only two: - **Never decreases.** Wall clocks step backwards (NTP correction, VM resume, manual change), and a deadline measured across a backward step waits longer than asked. - **Keeps advancing immediately after a step.** Clamping to a high-water mark satisfies the first property but freezes logical time for the _whole_ duration of the correction, so an hour-long step adds an hour to every deadline in flight — the very damage the rule exists to prevent. - **Converges back.** Absorbing the step into a permanent offset satisfies the first two but leaves reported time ahead of true time forever, so any comparison against a server-supplied absolute time is wrong for the life of the process. A backward step is therefore absorbed and then repaid gradually out of forward progress, the way NTP slews rather than steps. ## Parameters ### source? () => `number` injectable purely so the slew behaviour itself is testable; production callers use the default. Called through rather than captured, so a test that swaps the global `Date` (fake timers) still drives the shared `liveClock`. ## Returns [`Clock`](../interfaces/Clock.md) --- ## Function: createSeededRandom() ```ts function createSeededRandom(seed): SeededRandom; ``` A deterministic source for tests: the same seed yields the same draws on every run, and in every Camunda SDK. SplitMix64, taking the top 53 bits of each output. Specified by the cross-SDK contract rather than borrowed from the platform, so a seed reproduces the same jitter in any SDK. ## Parameters ### seed `number` \| `bigint` ## Returns [`SeededRandom`](../interfaces/SeededRandom.md) --- ## Function: createTestClock() ```ts function createTestClock(options?): TestClock; ``` A deterministic clock for tests: virtual time, so poll loops and backoff settle without burning real time. Exists so nobody hand-rolls one. Every ad-hoc clock this replaced got some clause of the contract wrong — most often settling `sleep` in a microtask, which spins any caller that reschedules itself on resolution. See #478. With `autoAdvance` (the default) a sleep settles itself on the next macrotask, having moved time to its wake point; the SDK's loops make progress without the test driving them. Set it to `false` to hold every sleep until `advance()` releases it, which is what you want when asserting on the state _between_ two waits. ## Parameters ### options? #### autoAdvance? `boolean` #### start? `number` ## Returns [`TestClock`](../interfaces/TestClock.md) --- ## Function: isErr() ```ts function isErr(r): r is { error: E; ok: false }; ``` ## Type Parameters ### T `T` ### E `E` ## Parameters ### r [`Result`](../type-aliases/Result.md)\<`T`, `E`\> ## Returns `r is { error: E; ok: false }` --- ## Function: isOk() ```ts function isOk(r): r is { ok: true; value: T }; ``` ## Type Parameters ### T `T` ### E `E` ## Parameters ### r [`Result`](../type-aliases/Result.md)\<`T`, `E`\> ## Returns `r is { ok: true; value: T }` --- ## Function: isPresentWhenUnsupportedError() ```ts function isPresentWhenUnsupportedError(e): e is PresentWhenUnsupportedError; ``` Discriminates the terminal dependent-presence guard error. A worker poll loop uses this to stop instead of rescheduling: the fault is a server/contract mismatch, not a transient transport error, so exponential backoff would loop forever. ## Parameters ### e `unknown` ## Returns `e is PresentWhenUnsupportedError` --- ## Function: isSdkError() ```ts function isSdkError(e): e is SdkError; ``` ## Parameters ### e `unknown` ## Returns `e is SdkError` --- ## Function: nextPageRequest() ```ts function nextPageRequest(body, response, mode?): TBody | null; ``` Compute the next request body from the previous body + response, or `null` when no further page exists. Pure — the async generator is built on top of it. ## Type Parameters ### TItem `TItem` ### TBody `TBody` _extends_ [`SearchBody`](../type-aliases/SearchBody.md) ## Parameters ### body `TBody` ### response [`SearchResponse`](../interfaces/SearchResponse.md)\<`TItem`\> ### mode? [`PaginationMode`](../type-aliases/PaginationMode.md) = `'auto'` ## Returns `TBody` \| `null` --- ## Function: paginate() ```ts function paginate(fetchPage, body, opts?): Paginator; ``` Wrap a search operation as a lazy, cancelable async stream. ## Type Parameters ### TItem `TItem` ### TBody `TBody` _extends_ [`SearchBody`](../type-aliases/SearchBody.md) ## Parameters ### fetchPage [`FetchPage`](../type-aliases/FetchPage.md)\<`TItem`, `TBody`\> ### body `TBody` ### opts? [`PaginateOptions`](../interfaces/PaginateOptions.md) = `{}` ## Returns [`Paginator`](../interfaces/Paginator.md)\<`TItem`\> ## Example const p = paginate((b, s) => searchProcessInstances(b, { ... }), body); for await (const pi of p.items()) { ... } // item-by-item for await (const page of p.pages()) { ... } // page-by-page --- ## Function: seededRandomFromEnv() ```ts function seededRandomFromEnv(env?): SeededRandom; ``` A [SeededRandom](../interfaces/SeededRandom.md) seeded from `CAMUNDA_TEST_SEED` when it is set, otherwise from a fresh random seed. Report its seed on failure, and set the variable to it to replay the run. Only an unset variable means "absent": any other value that is not an unsigned 64-bit decimal integer throws rather than silently replacing the seed being replayed. ## Parameters ### env? `Record`\<`string`, `string` \| `undefined`\> = `...` ## Returns [`SeededRandom`](../interfaces/SeededRandom.md) --- ## Function: variableNamesFromSchema() ```ts function variableNamesFromSchema(schema): string[]; ``` The declared variable names, in declaration order. These key the `name $in [...]` filter. Guards against non-schema inputs (e.g. a JS caller, or an `any` cast) so the failure is a clear, actionable error rather than an opaque `Cannot read properties of undefined` deep in paging. ## Parameters ### schema [`AnyVariableSchema`](../type-aliases/AnyVariableSchema.md) ## Returns `string`[] --- ## index ## Classes - [CamundaClientBase](classes/CamundaClientBase.md) - [CamundaValidationError](classes/CamundaValidationError.md) - [CancelError](classes/CancelError.md) - [EventualConsistencyTimeoutError](classes/EventualConsistencyTimeoutError.md) - [TypedVariablesError](classes/TypedVariablesError.md) - [VariableCollector](classes/VariableCollector.md) - [VariableDeserializationError](classes/VariableDeserializationError.md) - [VariableMap](classes/VariableMap.md) - [VariableScopeCollisionError](classes/VariableScopeCollisionError.md) ## Interfaces - [CamundaConfig](interfaces/CamundaConfig.md) - [CamundaOptions](interfaces/CamundaOptions.md) - [CancelablePromise](interfaces/CancelablePromise.md) - [Clock](interfaces/Clock.md) - [CreateLoggerOptions](interfaces/CreateLoggerOptions.md) - [EngineClock](interfaces/EngineClock.md) - [EngineClockTarget](interfaces/EngineClockTarget.md) - [EnrichedActivatedJob](interfaces/EnrichedActivatedJob.md) - [ExtendedDeploymentResult](interfaces/ExtendedDeploymentResult.md) - [HttpRetryPolicy](interfaces/HttpRetryPolicy.md) - [JobWorker](interfaces/JobWorker.md) - [JobWorkerConfig](interfaces/JobWorkerConfig.md) - [OperationOptions](interfaces/OperationOptions.md) - [PaginateOptions](interfaces/PaginateOptions.md) - [Paginator](interfaces/Paginator.md) - [PresentWhenUnsupportedError](interfaces/PresentWhenUnsupportedError.md) - [RandomSource](interfaces/RandomSource.md) - [SearchPageRequest](interfaces/SearchPageRequest.md) - [SearchPageResponse](interfaces/SearchPageResponse.md) - [SearchPaginateOptions](interfaces/SearchPaginateOptions.md) - [SearchResponse](interfaces/SearchResponse.md) - [SeededRandom](interfaces/SeededRandom.md) - [SupportLogger](interfaces/SupportLogger.md) - [TelemetryHooks](interfaces/TelemetryHooks.md) - [TestClock](interfaces/TestClock.md) - [ThreadedJobWorker](interfaces/ThreadedJobWorker.md) - [ThreadedJobWorkerConfig](interfaces/ThreadedJobWorkerConfig.md) - [ThreadPool](interfaces/ThreadPool.md) - [TypedVariableItem](interfaces/TypedVariableItem.md) - [TypedVariablePage](interfaces/TypedVariablePage.md) ## Type Aliases - [ActivateAdHocSubProcessActivitiesData](type-aliases/ActivateAdHocSubProcessActivitiesData.md) - [ActivateAdHocSubProcessActivitiesError](type-aliases/ActivateAdHocSubProcessActivitiesError.md) - [ActivateAdHocSubProcessActivitiesErrors](type-aliases/ActivateAdHocSubProcessActivitiesErrors.md) - [activateAdHocSubProcessActivitiesInput](type-aliases/activateAdHocSubProcessActivitiesInput.md) - [ActivateAdHocSubProcessActivitiesResponse](type-aliases/ActivateAdHocSubProcessActivitiesResponse.md) - [ActivateAdHocSubProcessActivitiesResponses](type-aliases/ActivateAdHocSubProcessActivitiesResponses.md) - [ActivatedJobResult](type-aliases/ActivatedJobResult.md) - [ActivatedJobResultWithJobLeaseToken](type-aliases/ActivatedJobResultWithJobLeaseToken.md) - [ActivatedJobResultWithoutJobLeaseToken](type-aliases/ActivatedJobResultWithoutJobLeaseToken.md) - [ActivateJobsData](type-aliases/ActivateJobsData.md) - [ActivateJobsError](type-aliases/ActivateJobsError.md) - [ActivateJobsErrors](type-aliases/ActivateJobsErrors.md) - [activateJobsInput](type-aliases/activateJobsInput.md) - [ActivateJobsResponse](type-aliases/ActivateJobsResponse.md) - [ActivateJobsResponses](type-aliases/ActivateJobsResponses.md) - [AdHocSubProcessActivateActivitiesInstruction](type-aliases/AdHocSubProcessActivateActivitiesInstruction.md) - [AdHocSubProcessActivateActivityReference](type-aliases/AdHocSubProcessActivateActivityReference.md) - [AdvancedActorTypeFilter](type-aliases/AdvancedActorTypeFilter.md) - [AdvancedAgentDefinitionKeyFilter](type-aliases/AdvancedAgentDefinitionKeyFilter.md) - [AdvancedAgentDefinitionTypeFilter](type-aliases/AdvancedAgentDefinitionTypeFilter.md) - [AdvancedAgentHistoryItemKeyFilter](type-aliases/AdvancedAgentHistoryItemKeyFilter.md) - [AdvancedAgentInstanceHistoryCommitStatusFilter](type-aliases/AdvancedAgentInstanceHistoryCommitStatusFilter.md) - [AdvancedAgentInstanceHistoryRoleFilter](type-aliases/AdvancedAgentInstanceHistoryRoleFilter.md) - [AdvancedAgentInstanceKeyFilter](type-aliases/AdvancedAgentInstanceKeyFilter.md) - [AdvancedAgentInstanceStatusFilter](type-aliases/AdvancedAgentInstanceStatusFilter.md) - [AdvancedAuditLogEntityKeyFilter](type-aliases/AdvancedAuditLogEntityKeyFilter.md) - [AdvancedAuditLogKeyFilter](type-aliases/AdvancedAuditLogKeyFilter.md) - [AdvancedBatchOperationItemStateFilter](type-aliases/AdvancedBatchOperationItemStateFilter.md) - [AdvancedBatchOperationStateFilter](type-aliases/AdvancedBatchOperationStateFilter.md) - [AdvancedBatchOperationTypeFilter](type-aliases/AdvancedBatchOperationTypeFilter.md) - [AdvancedCategoryFilter](type-aliases/AdvancedCategoryFilter.md) - [AdvancedClusterVariableKindFilter](type-aliases/AdvancedClusterVariableKindFilter.md) - [AdvancedClusterVariableScopeFilter](type-aliases/AdvancedClusterVariableScopeFilter.md) - [AdvancedDateTimeFilter](type-aliases/AdvancedDateTimeFilter.md) - [AdvancedDecisionDefinitionKeyFilter](type-aliases/AdvancedDecisionDefinitionKeyFilter.md) - [AdvancedDecisionEvaluationInstanceKeyFilter](type-aliases/AdvancedDecisionEvaluationInstanceKeyFilter.md) - [AdvancedDecisionEvaluationKeyFilter](type-aliases/AdvancedDecisionEvaluationKeyFilter.md) - [AdvancedDecisionInstanceStateFilter](type-aliases/AdvancedDecisionInstanceStateFilter.md) - [AdvancedDecisionRequirementsKeyFilter](type-aliases/AdvancedDecisionRequirementsKeyFilter.md) - [AdvancedDeploymentKeyFilter](type-aliases/AdvancedDeploymentKeyFilter.md) - [AdvancedElementIdFilter](type-aliases/AdvancedElementIdFilter.md) - [AdvancedElementInstanceKeyFilter](type-aliases/AdvancedElementInstanceKeyFilter.md) - [AdvancedElementInstanceStateFilter](type-aliases/AdvancedElementInstanceStateFilter.md) - [AdvancedEntityTypeFilter](type-aliases/AdvancedEntityTypeFilter.md) - [AdvancedFormKeyFilter](type-aliases/AdvancedFormKeyFilter.md) - [AdvancedGlobalListenerSourceFilter](type-aliases/AdvancedGlobalListenerSourceFilter.md) - [AdvancedGlobalTaskListenerEventTypeFilter](type-aliases/AdvancedGlobalTaskListenerEventTypeFilter.md) - [AdvancedIncidentErrorTypeFilter](type-aliases/AdvancedIncidentErrorTypeFilter.md) - [AdvancedIncidentStateFilter](type-aliases/AdvancedIncidentStateFilter.md) - [AdvancedIntegerFilter](type-aliases/AdvancedIntegerFilter.md) - [AdvancedJobKeyFilter](type-aliases/AdvancedJobKeyFilter.md) - [AdvancedJobKindFilter](type-aliases/AdvancedJobKindFilter.md) - [AdvancedJobListenerEventTypeFilter](type-aliases/AdvancedJobListenerEventTypeFilter.md) - [AdvancedJobStateFilter](type-aliases/AdvancedJobStateFilter.md) - [AdvancedMessageSubscriptionKeyFilter](type-aliases/AdvancedMessageSubscriptionKeyFilter.md) - [AdvancedMessageSubscriptionStateFilter](type-aliases/AdvancedMessageSubscriptionStateFilter.md) - [AdvancedMessageSubscriptionTypeFilter](type-aliases/AdvancedMessageSubscriptionTypeFilter.md) - [AdvancedMetadataValueFilter](type-aliases/AdvancedMetadataValueFilter.md) - [AdvancedOperationTypeFilter](type-aliases/AdvancedOperationTypeFilter.md) - [AdvancedProcessDefinitionIdFilter](type-aliases/AdvancedProcessDefinitionIdFilter.md) - [AdvancedProcessDefinitionKeyFilter](type-aliases/AdvancedProcessDefinitionKeyFilter.md) - [AdvancedProcessInstanceKeyFilter](type-aliases/AdvancedProcessInstanceKeyFilter.md) - [AdvancedProcessInstanceStateFilter](type-aliases/AdvancedProcessInstanceStateFilter.md) - [AdvancedResourceKeyFilter](type-aliases/AdvancedResourceKeyFilter.md) - [AdvancedResultFilter](type-aliases/AdvancedResultFilter.md) - [AdvancedScopeKeyFilter](type-aliases/AdvancedScopeKeyFilter.md) - [AdvancedStringFilter](type-aliases/AdvancedStringFilter.md) - [AdvancedUserTaskStateFilter](type-aliases/AdvancedUserTaskStateFilter.md) - [AdvancedVariableKeyFilter](type-aliases/AdvancedVariableKeyFilter.md) - [AdvancedWaitStateElementTypeFilter](type-aliases/AdvancedWaitStateElementTypeFilter.md) - [AdvancedWaitStateTypeFilter](type-aliases/AdvancedWaitStateTypeFilter.md) - [AgentDefinitionFilter](type-aliases/AgentDefinitionFilter.md) - [AgentDefinitionKey](type-aliases/AgentDefinitionKey.md) - [AgentDefinitionKeyExactMatch](type-aliases/AgentDefinitionKeyExactMatch.md) - [AgentDefinitionKeyExactMatchWritable](type-aliases/AgentDefinitionKeyExactMatchWritable.md) - [AgentDefinitionKeyFilterProperty](type-aliases/AgentDefinitionKeyFilterProperty.md) - [AgentDefinitionKeyWritable](type-aliases/AgentDefinitionKeyWritable.md) - [AgentDefinitionResult](type-aliases/AgentDefinitionResult.md) - [AgentDefinitionSearchQuery](type-aliases/AgentDefinitionSearchQuery.md) - [AgentDefinitionSearchQueryResult](type-aliases/AgentDefinitionSearchQueryResult.md) - [AgentDefinitionSearchQuerySortRequest](type-aliases/AgentDefinitionSearchQuerySortRequest.md) - [AgentDefinitionTypeEnum](type-aliases/AgentDefinitionTypeEnum.md) - [AgentDefinitionTypeExactMatch](type-aliases/AgentDefinitionTypeExactMatch.md) - [AgentDefinitionTypeExactMatchWritable](type-aliases/AgentDefinitionTypeExactMatchWritable.md) - [AgentDefinitionTypeFilterProperty](type-aliases/AgentDefinitionTypeFilterProperty.md) - [AgentHistoryItemKey](type-aliases/AgentHistoryItemKey.md) - [AgentHistoryItemKeyExactMatch](type-aliases/AgentHistoryItemKeyExactMatch.md) - [AgentHistoryItemKeyExactMatchWritable](type-aliases/AgentHistoryItemKeyExactMatchWritable.md) - [AgentHistoryItemKeyFilterProperty](type-aliases/AgentHistoryItemKeyFilterProperty.md) - [AgentHistoryItemKeyWritable](type-aliases/AgentHistoryItemKeyWritable.md) - [AgentInstanceCreatedHistoryItem](type-aliases/AgentInstanceCreatedHistoryItem.md) - [AgentInstanceCreationRequest](type-aliases/AgentInstanceCreationRequest.md) - [AgentInstanceCreationResult](type-aliases/AgentInstanceCreationResult.md) - [AgentInstanceDefinitionResult](type-aliases/AgentInstanceDefinitionResult.md) - [AgentInstanceDocumentContent](type-aliases/AgentInstanceDocumentContent.md) - [AgentInstanceFilter](type-aliases/AgentInstanceFilter.md) - [AgentInstanceHistoryCommitStatusEnum](type-aliases/AgentInstanceHistoryCommitStatusEnum.md) - [AgentInstanceHistoryCommitStatusExactMatch](type-aliases/AgentInstanceHistoryCommitStatusExactMatch.md) - [AgentInstanceHistoryCommitStatusExactMatchWritable](type-aliases/AgentInstanceHistoryCommitStatusExactMatchWritable.md) - [AgentInstanceHistoryCommitStatusFilterProperty](type-aliases/AgentInstanceHistoryCommitStatusFilterProperty.md) - [AgentInstanceHistoryFilter](type-aliases/AgentInstanceHistoryFilter.md) - [AgentInstanceHistoryItem](type-aliases/AgentInstanceHistoryItem.md) - [AgentInstanceHistoryItemMetrics](type-aliases/AgentInstanceHistoryItemMetrics.md) - [AgentInstanceHistoryItemMetricsRequest](type-aliases/AgentInstanceHistoryItemMetricsRequest.md) - [AgentInstanceHistoryItemResult](type-aliases/AgentInstanceHistoryItemResult.md) - [AgentInstanceHistoryRoleEnum](type-aliases/AgentInstanceHistoryRoleEnum.md) - [AgentInstanceHistoryRoleExactMatch](type-aliases/AgentInstanceHistoryRoleExactMatch.md) - [AgentInstanceHistoryRoleExactMatchWritable](type-aliases/AgentInstanceHistoryRoleExactMatchWritable.md) - [AgentInstanceHistoryRoleFilterProperty](type-aliases/AgentInstanceHistoryRoleFilterProperty.md) - [AgentInstanceHistorySearchQuery](type-aliases/AgentInstanceHistorySearchQuery.md) - [AgentInstanceHistorySearchQueryResult](type-aliases/AgentInstanceHistorySearchQueryResult.md) - [AgentInstanceHistorySearchQuerySortRequest](type-aliases/AgentInstanceHistorySearchQuerySortRequest.md) - [AgentInstanceKey](type-aliases/AgentInstanceKey.md) - [AgentInstanceKeyExactMatch](type-aliases/AgentInstanceKeyExactMatch.md) - [AgentInstanceKeyExactMatchWritable](type-aliases/AgentInstanceKeyExactMatchWritable.md) - [AgentInstanceKeyFilterProperty](type-aliases/AgentInstanceKeyFilterProperty.md) - [AgentInstanceKeyWritable](type-aliases/AgentInstanceKeyWritable.md) - [AgentInstanceLimits](type-aliases/AgentInstanceLimits.md) - [AgentInstanceMessageContent](type-aliases/AgentInstanceMessageContent.md) - [AgentInstanceMessageContentTypeEnum](type-aliases/AgentInstanceMessageContentTypeEnum.md) - [AgentInstanceMetrics](type-aliases/AgentInstanceMetrics.md) - [AgentInstanceObjectContent](type-aliases/AgentInstanceObjectContent.md) - [AgentInstanceResult](type-aliases/AgentInstanceResult.md) - [AgentInstanceSearchQuery](type-aliases/AgentInstanceSearchQuery.md) - [AgentInstanceSearchQueryResult](type-aliases/AgentInstanceSearchQueryResult.md) - [AgentInstanceSearchQuerySortRequest](type-aliases/AgentInstanceSearchQuerySortRequest.md) - [AgentInstanceStatusEnum](type-aliases/AgentInstanceStatusEnum.md) - [AgentInstanceStatusExactMatch](type-aliases/AgentInstanceStatusExactMatch.md) - [AgentInstanceStatusExactMatchWritable](type-aliases/AgentInstanceStatusExactMatchWritable.md) - [AgentInstanceStatusFilterProperty](type-aliases/AgentInstanceStatusFilterProperty.md) - [AgentInstanceTextContent](type-aliases/AgentInstanceTextContent.md) - [AgentInstanceToolCall](type-aliases/AgentInstanceToolCall.md) - [AgentInstanceUpdateRequest](type-aliases/AgentInstanceUpdateRequest.md) - [AgentInstanceUpdateResult](type-aliases/AgentInstanceUpdateResult.md) - [AgentInstanceUpdateStatusEnum](type-aliases/AgentInstanceUpdateStatusEnum.md) - [AgentTool](type-aliases/AgentTool.md) - [AncestorScopeInstruction](type-aliases/AncestorScopeInstruction.md) - [AnyVariableSchema](type-aliases/AnyVariableSchema.md) - [AssignClientToGroupData](type-aliases/AssignClientToGroupData.md) - [AssignClientToGroupError](type-aliases/AssignClientToGroupError.md) - [AssignClientToGroupErrors](type-aliases/AssignClientToGroupErrors.md) - [assignClientToGroupInput](type-aliases/assignClientToGroupInput.md) - [AssignClientToGroupResponse](type-aliases/AssignClientToGroupResponse.md) - [AssignClientToGroupResponses](type-aliases/AssignClientToGroupResponses.md) - [AssignClientToTenantData](type-aliases/AssignClientToTenantData.md) - [AssignClientToTenantError](type-aliases/AssignClientToTenantError.md) - [AssignClientToTenantErrors](type-aliases/AssignClientToTenantErrors.md) - [assignClientToTenantInput](type-aliases/assignClientToTenantInput.md) - [AssignClientToTenantResponse](type-aliases/AssignClientToTenantResponse.md) - [AssignClientToTenantResponses](type-aliases/AssignClientToTenantResponses.md) - [AssignGroupToTenantData](type-aliases/AssignGroupToTenantData.md) - [AssignGroupToTenantError](type-aliases/AssignGroupToTenantError.md) - [AssignGroupToTenantErrors](type-aliases/AssignGroupToTenantErrors.md) - [assignGroupToTenantInput](type-aliases/assignGroupToTenantInput.md) - [AssignGroupToTenantResponse](type-aliases/AssignGroupToTenantResponse.md) - [AssignGroupToTenantResponses](type-aliases/AssignGroupToTenantResponses.md) - [AssignMappingRuleToGroupData](type-aliases/AssignMappingRuleToGroupData.md) - [AssignMappingRuleToGroupError](type-aliases/AssignMappingRuleToGroupError.md) - [AssignMappingRuleToGroupErrors](type-aliases/AssignMappingRuleToGroupErrors.md) - [assignMappingRuleToGroupInput](type-aliases/assignMappingRuleToGroupInput.md) - [AssignMappingRuleToGroupResponse](type-aliases/AssignMappingRuleToGroupResponse.md) - [AssignMappingRuleToGroupResponses](type-aliases/AssignMappingRuleToGroupResponses.md) - [AssignMappingRuleToTenantData](type-aliases/AssignMappingRuleToTenantData.md) - [AssignMappingRuleToTenantError](type-aliases/AssignMappingRuleToTenantError.md) - [AssignMappingRuleToTenantErrors](type-aliases/AssignMappingRuleToTenantErrors.md) - [assignMappingRuleToTenantInput](type-aliases/assignMappingRuleToTenantInput.md) - [AssignMappingRuleToTenantResponse](type-aliases/AssignMappingRuleToTenantResponse.md) - [AssignMappingRuleToTenantResponses](type-aliases/AssignMappingRuleToTenantResponses.md) - [AssignProcessInstanceBusinessIdData](type-aliases/AssignProcessInstanceBusinessIdData.md) - [AssignProcessInstanceBusinessIdError](type-aliases/AssignProcessInstanceBusinessIdError.md) - [AssignProcessInstanceBusinessIdErrors](type-aliases/AssignProcessInstanceBusinessIdErrors.md) - [assignProcessInstanceBusinessIdInput](type-aliases/assignProcessInstanceBusinessIdInput.md) - [AssignProcessInstanceBusinessIdResponse](type-aliases/AssignProcessInstanceBusinessIdResponse.md) - [AssignProcessInstanceBusinessIdResponses](type-aliases/AssignProcessInstanceBusinessIdResponses.md) - [AssignRoleToClientData](type-aliases/AssignRoleToClientData.md) - [AssignRoleToClientError](type-aliases/AssignRoleToClientError.md) - [AssignRoleToClientErrors](type-aliases/AssignRoleToClientErrors.md) - [assignRoleToClientInput](type-aliases/assignRoleToClientInput.md) - [AssignRoleToClientResponse](type-aliases/AssignRoleToClientResponse.md) - [AssignRoleToClientResponses](type-aliases/AssignRoleToClientResponses.md) - [AssignRoleToGroupData](type-aliases/AssignRoleToGroupData.md) - [AssignRoleToGroupError](type-aliases/AssignRoleToGroupError.md) - [AssignRoleToGroupErrors](type-aliases/AssignRoleToGroupErrors.md) - [assignRoleToGroupInput](type-aliases/assignRoleToGroupInput.md) - [AssignRoleToGroupResponse](type-aliases/AssignRoleToGroupResponse.md) - [AssignRoleToGroupResponses](type-aliases/AssignRoleToGroupResponses.md) - [AssignRoleToMappingRuleData](type-aliases/AssignRoleToMappingRuleData.md) - [AssignRoleToMappingRuleError](type-aliases/AssignRoleToMappingRuleError.md) - [AssignRoleToMappingRuleErrors](type-aliases/AssignRoleToMappingRuleErrors.md) - [assignRoleToMappingRuleInput](type-aliases/assignRoleToMappingRuleInput.md) - [AssignRoleToMappingRuleResponse](type-aliases/AssignRoleToMappingRuleResponse.md) - [AssignRoleToMappingRuleResponses](type-aliases/AssignRoleToMappingRuleResponses.md) - [AssignRoleToTenantData](type-aliases/AssignRoleToTenantData.md) - [AssignRoleToTenantError](type-aliases/AssignRoleToTenantError.md) - [AssignRoleToTenantErrors](type-aliases/AssignRoleToTenantErrors.md) - [assignRoleToTenantInput](type-aliases/assignRoleToTenantInput.md) - [AssignRoleToTenantResponse](type-aliases/AssignRoleToTenantResponse.md) - [AssignRoleToTenantResponses](type-aliases/AssignRoleToTenantResponses.md) - [AssignRoleToUserData](type-aliases/AssignRoleToUserData.md) - [AssignRoleToUserError](type-aliases/AssignRoleToUserError.md) - [AssignRoleToUserErrors](type-aliases/AssignRoleToUserErrors.md) - [assignRoleToUserInput](type-aliases/assignRoleToUserInput.md) - [AssignRoleToUserResponse](type-aliases/AssignRoleToUserResponse.md) - [AssignRoleToUserResponses](type-aliases/AssignRoleToUserResponses.md) - [AssignUserTaskData](type-aliases/AssignUserTaskData.md) - [AssignUserTaskError](type-aliases/AssignUserTaskError.md) - [AssignUserTaskErrors](type-aliases/AssignUserTaskErrors.md) - [assignUserTaskInput](type-aliases/assignUserTaskInput.md) - [AssignUserTaskResponse](type-aliases/AssignUserTaskResponse.md) - [AssignUserTaskResponses](type-aliases/AssignUserTaskResponses.md) - [AssignUserToGroupData](type-aliases/AssignUserToGroupData.md) - [AssignUserToGroupError](type-aliases/AssignUserToGroupError.md) - [AssignUserToGroupErrors](type-aliases/AssignUserToGroupErrors.md) - [assignUserToGroupInput](type-aliases/assignUserToGroupInput.md) - [AssignUserToGroupResponse](type-aliases/AssignUserToGroupResponse.md) - [AssignUserToGroupResponses](type-aliases/AssignUserToGroupResponses.md) - [AssignUserToTenantData](type-aliases/AssignUserToTenantData.md) - [AssignUserToTenantError](type-aliases/AssignUserToTenantError.md) - [AssignUserToTenantErrors](type-aliases/AssignUserToTenantErrors.md) - [assignUserToTenantInput](type-aliases/assignUserToTenantInput.md) - [AssignUserToTenantResponse](type-aliases/AssignUserToTenantResponse.md) - [AssignUserToTenantResponses](type-aliases/AssignUserToTenantResponses.md) - [AuditLogActorTypeEnum](type-aliases/AuditLogActorTypeEnum.md) - [AuditLogActorTypeExactMatch](type-aliases/AuditLogActorTypeExactMatch.md) - [AuditLogActorTypeExactMatchWritable](type-aliases/AuditLogActorTypeExactMatchWritable.md) - [AuditLogActorTypeFilterProperty](type-aliases/AuditLogActorTypeFilterProperty.md) - [AuditLogCategoryEnum](type-aliases/AuditLogCategoryEnum.md) - [AuditLogEntityKey](type-aliases/AuditLogEntityKey.md) - [AuditLogEntityKeyExactMatch](type-aliases/AuditLogEntityKeyExactMatch.md) - [AuditLogEntityKeyExactMatchWritable](type-aliases/AuditLogEntityKeyExactMatchWritable.md) - [AuditLogEntityKeyFilterProperty](type-aliases/AuditLogEntityKeyFilterProperty.md) - [AuditLogEntityTypeEnum](type-aliases/AuditLogEntityTypeEnum.md) - [AuditLogFilter](type-aliases/AuditLogFilter.md) - [AuditLogKey](type-aliases/AuditLogKey.md) - [AuditLogKeyExactMatch](type-aliases/AuditLogKeyExactMatch.md) - [AuditLogKeyExactMatchWritable](type-aliases/AuditLogKeyExactMatchWritable.md) - [AuditLogKeyFilterProperty](type-aliases/AuditLogKeyFilterProperty.md) - [AuditLogKeyWritable](type-aliases/AuditLogKeyWritable.md) - [AuditLogOperationTypeEnum](type-aliases/AuditLogOperationTypeEnum.md) - [AuditLogResult](type-aliases/AuditLogResult.md) - [AuditLogResultEnum](type-aliases/AuditLogResultEnum.md) - [AuditLogResultExactMatch](type-aliases/AuditLogResultExactMatch.md) - [AuditLogResultExactMatchWritable](type-aliases/AuditLogResultExactMatchWritable.md) - [AuditLogResultFilterProperty](type-aliases/AuditLogResultFilterProperty.md) - [AuditLogSearchQueryRequest](type-aliases/AuditLogSearchQueryRequest.md) - [AuditLogSearchQueryResult](type-aliases/AuditLogSearchQueryResult.md) - [AuditLogSearchQuerySortRequest](type-aliases/AuditLogSearchQuerySortRequest.md) - [AuthenticationConfigurationResponse](type-aliases/AuthenticationConfigurationResponse.md) - [AuthorizationCreateResult](type-aliases/AuthorizationCreateResult.md) - [AuthorizationFilter](type-aliases/AuthorizationFilter.md) - [AuthorizationIdBasedRequest](type-aliases/AuthorizationIdBasedRequest.md) - [AuthorizationKey](type-aliases/AuthorizationKey.md) - [AuthorizationKeyWritable](type-aliases/AuthorizationKeyWritable.md) - [AuthorizationPropertyBasedRequest](type-aliases/AuthorizationPropertyBasedRequest.md) - [AuthorizationRequest](type-aliases/AuthorizationRequest.md) - [AuthorizationResult](type-aliases/AuthorizationResult.md) - [AuthorizationSearchQuery](type-aliases/AuthorizationSearchQuery.md) - [AuthorizationSearchQuerySortRequest](type-aliases/AuthorizationSearchQuerySortRequest.md) - [AuthorizationSearchResult](type-aliases/AuthorizationSearchResult.md) - [AuthStrategy](type-aliases/AuthStrategy.md) - [BackpressureSeverity](type-aliases/BackpressureSeverity.md) - [BackupId](type-aliases/BackupId.md) - [BackupIdPrefix](type-aliases/BackupIdPrefix.md) - [BackupInfo](type-aliases/BackupInfo.md) - [BackupInfoWritable](type-aliases/BackupInfoWritable.md) - [BackupType](type-aliases/BackupType.md) - [BaseProcessInstanceFilterFields](type-aliases/BaseProcessInstanceFilterFields.md) - [BaseWaitStateDetails](type-aliases/BaseWaitStateDetails.md) - [BasicStringFilter](type-aliases/BasicStringFilter.md) - [BasicStringFilterProperty](type-aliases/BasicStringFilterProperty.md) - [BatchOperationCreatedResult](type-aliases/BatchOperationCreatedResult.md) - [BatchOperationError](type-aliases/BatchOperationError.md) - [BatchOperationFilter](type-aliases/BatchOperationFilter.md) - [BatchOperationItemFilter](type-aliases/BatchOperationItemFilter.md) - [BatchOperationItemResponse](type-aliases/BatchOperationItemResponse.md) - [BatchOperationItemSearchQuery](type-aliases/BatchOperationItemSearchQuery.md) - [BatchOperationItemSearchQueryResult](type-aliases/BatchOperationItemSearchQueryResult.md) - [BatchOperationItemSearchQuerySortRequest](type-aliases/BatchOperationItemSearchQuerySortRequest.md) - [BatchOperationItemStateEnum](type-aliases/BatchOperationItemStateEnum.md) - [BatchOperationItemStateExactMatch](type-aliases/BatchOperationItemStateExactMatch.md) - [BatchOperationItemStateExactMatchWritable](type-aliases/BatchOperationItemStateExactMatchWritable.md) - [BatchOperationItemStateFilterProperty](type-aliases/BatchOperationItemStateFilterProperty.md) - [BatchOperationKey](type-aliases/BatchOperationKey.md) - [BatchOperationResponse](type-aliases/BatchOperationResponse.md) - [BatchOperationSearchQuery](type-aliases/BatchOperationSearchQuery.md) - [BatchOperationSearchQueryResult](type-aliases/BatchOperationSearchQueryResult.md) - [BatchOperationSearchQuerySortRequest](type-aliases/BatchOperationSearchQuerySortRequest.md) - [BatchOperationStateEnum](type-aliases/BatchOperationStateEnum.md) - [BatchOperationStateExactMatch](type-aliases/BatchOperationStateExactMatch.md) - [BatchOperationStateExactMatchWritable](type-aliases/BatchOperationStateExactMatchWritable.md) - [BatchOperationStateFilterProperty](type-aliases/BatchOperationStateFilterProperty.md) - [BatchOperationTypeEnum](type-aliases/BatchOperationTypeEnum.md) - [BatchOperationTypeExactMatch](type-aliases/BatchOperationTypeExactMatch.md) - [BatchOperationTypeExactMatchWritable](type-aliases/BatchOperationTypeExactMatchWritable.md) - [BatchOperationTypeFilterProperty](type-aliases/BatchOperationTypeFilterProperty.md) - [BroadcastSignalData](type-aliases/BroadcastSignalData.md) - [BroadcastSignalError](type-aliases/BroadcastSignalError.md) - [BroadcastSignalErrors](type-aliases/BroadcastSignalErrors.md) - [broadcastSignalInput](type-aliases/broadcastSignalInput.md) - [BroadcastSignalResponse](type-aliases/BroadcastSignalResponse.md) - [BroadcastSignalResponses](type-aliases/BroadcastSignalResponses.md) - [BrokerInfo](type-aliases/BrokerInfo.md) - [BusinessId](type-aliases/BusinessId.md) - [CamundaClient](type-aliases/CamundaClient.md) - [CamundaClientLoose](type-aliases/CamundaClientLoose.md) - [CamundaKey](type-aliases/CamundaKey.md) - [CamundaResultClient](type-aliases/CamundaResultClient.md) - [CamundaUserResult](type-aliases/CamundaUserResult.md) - [CancelBatchOperationData](type-aliases/CancelBatchOperationData.md) - [CancelBatchOperationError](type-aliases/CancelBatchOperationError.md) - [CancelBatchOperationErrors](type-aliases/CancelBatchOperationErrors.md) - [cancelBatchOperationInput](type-aliases/cancelBatchOperationInput.md) - [CancelBatchOperationResponse](type-aliases/CancelBatchOperationResponse.md) - [CancelBatchOperationResponses](type-aliases/CancelBatchOperationResponses.md) - [CancelClusterRebalanceData](type-aliases/CancelClusterRebalanceData.md) - [CancelClusterRebalanceError](type-aliases/CancelClusterRebalanceError.md) - [CancelClusterRebalanceErrors](type-aliases/CancelClusterRebalanceErrors.md) - [cancelClusterRebalanceInput](type-aliases/cancelClusterRebalanceInput.md) - [CancelClusterRebalanceResponse](type-aliases/CancelClusterRebalanceResponse.md) - [CancelClusterRebalanceResponses](type-aliases/CancelClusterRebalanceResponses.md) - [CancelProcessInstanceData](type-aliases/CancelProcessInstanceData.md) - [CancelProcessInstanceError](type-aliases/CancelProcessInstanceError.md) - [CancelProcessInstanceErrors](type-aliases/CancelProcessInstanceErrors.md) - [cancelProcessInstanceInput](type-aliases/cancelProcessInstanceInput.md) - [CancelProcessInstanceRequest](type-aliases/CancelProcessInstanceRequest.md) - [CancelProcessInstanceResponse](type-aliases/CancelProcessInstanceResponse.md) - [CancelProcessInstanceResponses](type-aliases/CancelProcessInstanceResponses.md) - [CancelProcessInstancesBatchOperationData](type-aliases/CancelProcessInstancesBatchOperationData.md) - [CancelProcessInstancesBatchOperationError](type-aliases/CancelProcessInstancesBatchOperationError.md) - [CancelProcessInstancesBatchOperationErrors](type-aliases/CancelProcessInstancesBatchOperationErrors.md) - [cancelProcessInstancesBatchOperationInput](type-aliases/cancelProcessInstancesBatchOperationInput.md) - [CancelProcessInstancesBatchOperationResponse](type-aliases/CancelProcessInstancesBatchOperationResponse.md) - [CancelProcessInstancesBatchOperationResponses](type-aliases/CancelProcessInstancesBatchOperationResponses.md) - [CategoryExactMatch](type-aliases/CategoryExactMatch.md) - [CategoryExactMatchWritable](type-aliases/CategoryExactMatchWritable.md) - [CategoryFilterProperty](type-aliases/CategoryFilterProperty.md) - [ChangeClusterModeAsClusterAdminData](type-aliases/ChangeClusterModeAsClusterAdminData.md) - [ChangeClusterModeAsClusterAdminError](type-aliases/ChangeClusterModeAsClusterAdminError.md) - [ChangeClusterModeAsClusterAdminErrors](type-aliases/ChangeClusterModeAsClusterAdminErrors.md) - [changeClusterModeAsClusterAdminInput](type-aliases/changeClusterModeAsClusterAdminInput.md) - [ChangeClusterModeAsClusterAdminResponse](type-aliases/ChangeClusterModeAsClusterAdminResponse.md) - [ChangeClusterModeAsClusterAdminResponses](type-aliases/ChangeClusterModeAsClusterAdminResponses.md) - [ChangeClusterModeData](type-aliases/ChangeClusterModeData.md) - [ChangeClusterModeError](type-aliases/ChangeClusterModeError.md) - [ChangeClusterModeErrors](type-aliases/ChangeClusterModeErrors.md) - [changeClusterModeInput](type-aliases/changeClusterModeInput.md) - [ChangeClusterModeResponse](type-aliases/ChangeClusterModeResponse.md) - [ChangeClusterModeResponses](type-aliases/ChangeClusterModeResponses.md) - [Changeset](type-aliases/Changeset.md) - [CheckpointId](type-aliases/CheckpointId.md) - [CheckpointType](type-aliases/CheckpointType.md) - [ClientId](type-aliases/ClientId.md) - [ClientOptions](type-aliases/ClientOptions.md) - [ClockPinRequest](type-aliases/ClockPinRequest.md) - [CloudConfigurationResponse](type-aliases/CloudConfigurationResponse.md) - [CloudStage](type-aliases/CloudStage.md) - [ClusterBalanceResponse](type-aliases/ClusterBalanceResponse.md) - [ClusterBrokerInfo](type-aliases/ClusterBrokerInfo.md) - [ClusterCompletedRebalance](type-aliases/ClusterCompletedRebalance.md) - [ClusterHistoryBackupInfo](type-aliases/ClusterHistoryBackupInfo.md) - [ClusterHistoryBackupInfoWritable](type-aliases/ClusterHistoryBackupInfoWritable.md) - [ClusterHistoryBackupTakeResult](type-aliases/ClusterHistoryBackupTakeResult.md) - [ClusterHistoryBackupTenantInfo](type-aliases/ClusterHistoryBackupTenantInfo.md) - [ClusterHistoryBackupTenantInfoWritable](type-aliases/ClusterHistoryBackupTenantInfoWritable.md) - [ClusterHistoryBackupTenantState](type-aliases/ClusterHistoryBackupTenantState.md) - [ClusterModeChangeOperation](type-aliases/ClusterModeChangeOperation.md) - [ClusterModeChangePlannedChange](type-aliases/ClusterModeChangePlannedChange.md) - [ClusterModeChangeResponse](type-aliases/ClusterModeChangeResponse.md) - [ClusterRebalance](type-aliases/ClusterRebalance.md) - [ClusterRebalanceOperationPartition](type-aliases/ClusterRebalanceOperationPartition.md) - [ClusterRebalancePartition](type-aliases/ClusterRebalancePartition.md) - [ClusterRebalanceRequest](type-aliases/ClusterRebalanceRequest.md) - [ClusterRestoreAwaitModeChangeOperation](type-aliases/ClusterRestoreAwaitModeChangeOperation.md) - [ClusterRestoreBrokerOperation](type-aliases/ClusterRestoreBrokerOperation.md) - [ClusterRestoreModeChangeOperation](type-aliases/ClusterRestoreModeChangeOperation.md) - [ClusterRestoreOperation](type-aliases/ClusterRestoreOperation.md) - [ClusterRestorePartitionOperation](type-aliases/ClusterRestorePartitionOperation.md) - [ClusterRestorePartitionRestoreOperation](type-aliases/ClusterRestorePartitionRestoreOperation.md) - [ClusterRestorePlannedChange](type-aliases/ClusterRestorePlannedChange.md) - [ClusterRestoreRequest](type-aliases/ClusterRestoreRequest.md) - [ClusterRestoreResponse](type-aliases/ClusterRestoreResponse.md) - [ClusterRunningRebalance](type-aliases/ClusterRunningRebalance.md) - [ClusterRuntimeBackupInfo](type-aliases/ClusterRuntimeBackupInfo.md) - [ClusterRuntimeBackupInfoWritable](type-aliases/ClusterRuntimeBackupInfoWritable.md) - [ClusterRuntimeBackupState](type-aliases/ClusterRuntimeBackupState.md) - [ClusterRuntimeBackupTakeOutcome](type-aliases/ClusterRuntimeBackupTakeOutcome.md) - [ClusterRuntimeBackupTakeResult](type-aliases/ClusterRuntimeBackupTakeResult.md) - [ClusterRuntimeBackupTenantInfo](type-aliases/ClusterRuntimeBackupTenantInfo.md) - [ClusterRuntimeBackupTenantInfoWritable](type-aliases/ClusterRuntimeBackupTenantInfoWritable.md) - [ClusterRuntimeBackupTenantState](type-aliases/ClusterRuntimeBackupTenantState.md) - [ClusterStatusResponse](type-aliases/ClusterStatusResponse.md) - [ClusterTakeHistoryBackupResponse](type-aliases/ClusterTakeHistoryBackupResponse.md) - [ClusterTakeRuntimeBackupResponse](type-aliases/ClusterTakeRuntimeBackupResponse.md) - [ClusterTopologyResponse](type-aliases/ClusterTopologyResponse.md) - [ClusterUpgradeStatusResponse](type-aliases/ClusterUpgradeStatusResponse.md) - [ClusterVariableKindEnum](type-aliases/ClusterVariableKindEnum.md) - [ClusterVariableKindExactMatch](type-aliases/ClusterVariableKindExactMatch.md) - [ClusterVariableKindExactMatchWritable](type-aliases/ClusterVariableKindExactMatchWritable.md) - [ClusterVariableKindFilterProperty](type-aliases/ClusterVariableKindFilterProperty.md) - [ClusterVariableName](type-aliases/ClusterVariableName.md) - [ClusterVariableResult](type-aliases/ClusterVariableResult.md) - [ClusterVariableResultBase](type-aliases/ClusterVariableResultBase.md) - [ClusterVariableScopeEnum](type-aliases/ClusterVariableScopeEnum.md) - [ClusterVariableScopeExactMatch](type-aliases/ClusterVariableScopeExactMatch.md) - [ClusterVariableScopeExactMatchWritable](type-aliases/ClusterVariableScopeExactMatchWritable.md) - [ClusterVariableScopeFilterProperty](type-aliases/ClusterVariableScopeFilterProperty.md) - [ClusterVariableSearchQueryFilterRequest](type-aliases/ClusterVariableSearchQueryFilterRequest.md) - [ClusterVariableSearchQueryRequest](type-aliases/ClusterVariableSearchQueryRequest.md) - [ClusterVariableSearchQueryResult](type-aliases/ClusterVariableSearchQueryResult.md) - [ClusterVariableSearchQuerySortRequest](type-aliases/ClusterVariableSearchQuerySortRequest.md) - [ClusterVariableSearchResult](type-aliases/ClusterVariableSearchResult.md) - [CompleteJobData](type-aliases/CompleteJobData.md) - [CompleteJobError](type-aliases/CompleteJobError.md) - [CompleteJobErrors](type-aliases/CompleteJobErrors.md) - [completeJobInput](type-aliases/completeJobInput.md) - [CompleteJobResponse](type-aliases/CompleteJobResponse.md) - [CompleteJobResponses](type-aliases/CompleteJobResponses.md) - [CompleteUserTaskData](type-aliases/CompleteUserTaskData.md) - [CompleteUserTaskError](type-aliases/CompleteUserTaskError.md) - [CompleteUserTaskErrors](type-aliases/CompleteUserTaskErrors.md) - [completeUserTaskInput](type-aliases/completeUserTaskInput.md) - [CompleteUserTaskResponse](type-aliases/CompleteUserTaskResponse.md) - [CompleteUserTaskResponses](type-aliases/CompleteUserTaskResponses.md) - [ComponentsConfigurationResponse](type-aliases/ComponentsConfigurationResponse.md) - [ConditionalEvaluationInstruction](type-aliases/ConditionalEvaluationInstruction.md) - [ConditionalEvaluationKey](type-aliases/ConditionalEvaluationKey.md) - [ConditionalEvaluationKeyWritable](type-aliases/ConditionalEvaluationKeyWritable.md) - [ConditionWaitStateDetails](type-aliases/ConditionWaitStateDetails.md) - [CorrelatedMessageSubscriptionFilter](type-aliases/CorrelatedMessageSubscriptionFilter.md) - [CorrelatedMessageSubscriptionResult](type-aliases/CorrelatedMessageSubscriptionResult.md) - [CorrelatedMessageSubscriptionSearchQuery](type-aliases/CorrelatedMessageSubscriptionSearchQuery.md) - [CorrelatedMessageSubscriptionSearchQueryResult](type-aliases/CorrelatedMessageSubscriptionSearchQueryResult.md) - [CorrelatedMessageSubscriptionSearchQuerySortRequest](type-aliases/CorrelatedMessageSubscriptionSearchQuerySortRequest.md) - [CorrelateMessageData](type-aliases/CorrelateMessageData.md) - [CorrelateMessageError](type-aliases/CorrelateMessageError.md) - [CorrelateMessageErrors](type-aliases/CorrelateMessageErrors.md) - [correlateMessageInput](type-aliases/correlateMessageInput.md) - [CorrelateMessageResponse](type-aliases/CorrelateMessageResponse.md) - [CorrelateMessageResponses](type-aliases/CorrelateMessageResponses.md) - [CreateAdminUserData](type-aliases/CreateAdminUserData.md) - [CreateAdminUserError](type-aliases/CreateAdminUserError.md) - [CreateAdminUserErrors](type-aliases/CreateAdminUserErrors.md) - [createAdminUserInput](type-aliases/createAdminUserInput.md) - [CreateAdminUserResponse](type-aliases/CreateAdminUserResponse.md) - [CreateAdminUserResponses](type-aliases/CreateAdminUserResponses.md) - [CreateAgentInstanceData](type-aliases/CreateAgentInstanceData.md) - [CreateAgentInstanceError](type-aliases/CreateAgentInstanceError.md) - [CreateAgentInstanceErrors](type-aliases/CreateAgentInstanceErrors.md) - [createAgentInstanceInput](type-aliases/createAgentInstanceInput.md) - [CreateAgentInstanceResponse](type-aliases/CreateAgentInstanceResponse.md) - [CreateAgentInstanceResponses](type-aliases/CreateAgentInstanceResponses.md) - [CreateAuthorizationData](type-aliases/CreateAuthorizationData.md) - [CreateAuthorizationError](type-aliases/CreateAuthorizationError.md) - [CreateAuthorizationErrors](type-aliases/CreateAuthorizationErrors.md) - [createAuthorizationInput](type-aliases/createAuthorizationInput.md) - [CreateAuthorizationResponse](type-aliases/CreateAuthorizationResponse.md) - [CreateAuthorizationResponses](type-aliases/CreateAuthorizationResponses.md) - [CreateClusterVariableRequest](type-aliases/CreateClusterVariableRequest.md) - [CreateDeploymentData](type-aliases/CreateDeploymentData.md) - [CreateDeploymentError](type-aliases/CreateDeploymentError.md) - [CreateDeploymentErrors](type-aliases/CreateDeploymentErrors.md) - [createDeploymentInput](type-aliases/createDeploymentInput.md) - [CreateDeploymentResponse](type-aliases/CreateDeploymentResponse.md) - [CreateDeploymentResponses](type-aliases/CreateDeploymentResponses.md) - [CreateDocumentData](type-aliases/CreateDocumentData.md) - [CreateDocumentError](type-aliases/CreateDocumentError.md) - [CreateDocumentErrors](type-aliases/CreateDocumentErrors.md) - [createDocumentInput](type-aliases/createDocumentInput.md) - [CreateDocumentLinkData](type-aliases/CreateDocumentLinkData.md) - [CreateDocumentLinkError](type-aliases/CreateDocumentLinkError.md) - [CreateDocumentLinkErrors](type-aliases/CreateDocumentLinkErrors.md) - [createDocumentLinkInput](type-aliases/createDocumentLinkInput.md) - [CreateDocumentLinkResponse](type-aliases/CreateDocumentLinkResponse.md) - [CreateDocumentLinkResponses](type-aliases/CreateDocumentLinkResponses.md) - [CreateDocumentResponse](type-aliases/CreateDocumentResponse.md) - [CreateDocumentResponses](type-aliases/CreateDocumentResponses.md) - [CreateDocumentsData](type-aliases/CreateDocumentsData.md) - [CreateDocumentsError](type-aliases/CreateDocumentsError.md) - [CreateDocumentsErrors](type-aliases/CreateDocumentsErrors.md) - [createDocumentsInput](type-aliases/createDocumentsInput.md) - [CreateDocumentsResponse](type-aliases/CreateDocumentsResponse.md) - [CreateDocumentsResponses](type-aliases/CreateDocumentsResponses.md) - [CreateElementInstanceVariablesData](type-aliases/CreateElementInstanceVariablesData.md) - [CreateElementInstanceVariablesError](type-aliases/CreateElementInstanceVariablesError.md) - [CreateElementInstanceVariablesErrors](type-aliases/CreateElementInstanceVariablesErrors.md) - [createElementInstanceVariablesInput](type-aliases/createElementInstanceVariablesInput.md) - [CreateElementInstanceVariablesResponse](type-aliases/CreateElementInstanceVariablesResponse.md) - [CreateElementInstanceVariablesResponses](type-aliases/CreateElementInstanceVariablesResponses.md) - [CreateGlobalClusterVariableData](type-aliases/CreateGlobalClusterVariableData.md) - [CreateGlobalClusterVariableError](type-aliases/CreateGlobalClusterVariableError.md) - [CreateGlobalClusterVariableErrors](type-aliases/CreateGlobalClusterVariableErrors.md) - [createGlobalClusterVariableInput](type-aliases/createGlobalClusterVariableInput.md) - [CreateGlobalClusterVariableResponse](type-aliases/CreateGlobalClusterVariableResponse.md) - [CreateGlobalClusterVariableResponses](type-aliases/CreateGlobalClusterVariableResponses.md) - [CreateGlobalTaskListenerData](type-aliases/CreateGlobalTaskListenerData.md) - [CreateGlobalTaskListenerError](type-aliases/CreateGlobalTaskListenerError.md) - [CreateGlobalTaskListenerErrors](type-aliases/CreateGlobalTaskListenerErrors.md) - [createGlobalTaskListenerInput](type-aliases/createGlobalTaskListenerInput.md) - [CreateGlobalTaskListenerRequest](type-aliases/CreateGlobalTaskListenerRequest.md) - [CreateGlobalTaskListenerResponse](type-aliases/CreateGlobalTaskListenerResponse.md) - [CreateGlobalTaskListenerResponses](type-aliases/CreateGlobalTaskListenerResponses.md) - [CreateGroupData](type-aliases/CreateGroupData.md) - [CreateGroupError](type-aliases/CreateGroupError.md) - [CreateGroupErrors](type-aliases/CreateGroupErrors.md) - [createGroupInput](type-aliases/createGroupInput.md) - [CreateGroupResponse](type-aliases/CreateGroupResponse.md) - [CreateGroupResponses](type-aliases/CreateGroupResponses.md) - [CreateMappingRuleData](type-aliases/CreateMappingRuleData.md) - [CreateMappingRuleError](type-aliases/CreateMappingRuleError.md) - [CreateMappingRuleErrors](type-aliases/CreateMappingRuleErrors.md) - [createMappingRuleInput](type-aliases/createMappingRuleInput.md) - [CreateMappingRuleResponse](type-aliases/CreateMappingRuleResponse.md) - [CreateMappingRuleResponses](type-aliases/CreateMappingRuleResponses.md) - [CreateProcessInstanceData](type-aliases/CreateProcessInstanceData.md) - [CreateProcessInstanceError](type-aliases/CreateProcessInstanceError.md) - [CreateProcessInstanceErrors](type-aliases/CreateProcessInstanceErrors.md) - [createProcessInstanceInput](type-aliases/createProcessInstanceInput.md) - [CreateProcessInstanceResponse](type-aliases/CreateProcessInstanceResponse.md) - [CreateProcessInstanceResponses](type-aliases/CreateProcessInstanceResponses.md) - [CreateProcessInstanceResult](type-aliases/CreateProcessInstanceResult.md) - [CreateRoleData](type-aliases/CreateRoleData.md) - [CreateRoleError](type-aliases/CreateRoleError.md) - [CreateRoleErrors](type-aliases/CreateRoleErrors.md) - [createRoleInput](type-aliases/createRoleInput.md) - [CreateRoleResponse](type-aliases/CreateRoleResponse.md) - [CreateRoleResponses](type-aliases/CreateRoleResponses.md) - [CreateTenantClusterVariableData](type-aliases/CreateTenantClusterVariableData.md) - [CreateTenantClusterVariableError](type-aliases/CreateTenantClusterVariableError.md) - [CreateTenantClusterVariableErrors](type-aliases/CreateTenantClusterVariableErrors.md) - [createTenantClusterVariableInput](type-aliases/createTenantClusterVariableInput.md) - [CreateTenantClusterVariableResponse](type-aliases/CreateTenantClusterVariableResponse.md) - [CreateTenantClusterVariableResponses](type-aliases/CreateTenantClusterVariableResponses.md) - [CreateTenantData](type-aliases/CreateTenantData.md) - [CreateTenantError](type-aliases/CreateTenantError.md) - [CreateTenantErrors](type-aliases/CreateTenantErrors.md) - [createTenantInput](type-aliases/createTenantInput.md) - [CreateTenantResponse](type-aliases/CreateTenantResponse.md) - [CreateTenantResponses](type-aliases/CreateTenantResponses.md) - [CreateUserData](type-aliases/CreateUserData.md) - [CreateUserError](type-aliases/CreateUserError.md) - [CreateUserErrors](type-aliases/CreateUserErrors.md) - [createUserInput](type-aliases/createUserInput.md) - [CreateUserResponse](type-aliases/CreateUserResponse.md) - [CreateUserResponses](type-aliases/CreateUserResponses.md) - [CursorBackwardPagination](type-aliases/CursorBackwardPagination.md) - [CursorForwardPagination](type-aliases/CursorForwardPagination.md) - [DateTimeFilterProperty](type-aliases/DateTimeFilterProperty.md) - [DecisionDefinitionFilter](type-aliases/DecisionDefinitionFilter.md) - [DecisionDefinitionId](type-aliases/DecisionDefinitionId.md) - [DecisionDefinitionKey](type-aliases/DecisionDefinitionKey.md) - [DecisionDefinitionKeyExactMatch](type-aliases/DecisionDefinitionKeyExactMatch.md) - [DecisionDefinitionKeyExactMatchWritable](type-aliases/DecisionDefinitionKeyExactMatchWritable.md) - [DecisionDefinitionKeyFilterProperty](type-aliases/DecisionDefinitionKeyFilterProperty.md) - [DecisionDefinitionKeyWritable](type-aliases/DecisionDefinitionKeyWritable.md) - [DecisionDefinitionResult](type-aliases/DecisionDefinitionResult.md) - [DecisionDefinitionSearchQuery](type-aliases/DecisionDefinitionSearchQuery.md) - [DecisionDefinitionSearchQueryResult](type-aliases/DecisionDefinitionSearchQueryResult.md) - [DecisionDefinitionSearchQuerySortRequest](type-aliases/DecisionDefinitionSearchQuerySortRequest.md) - [DecisionDefinitionTypeEnum](type-aliases/DecisionDefinitionTypeEnum.md) - [DecisionEvaluationById](type-aliases/DecisionEvaluationById.md) - [DecisionEvaluationByKey](type-aliases/DecisionEvaluationByKey.md) - [DecisionEvaluationInstanceKey](type-aliases/DecisionEvaluationInstanceKey.md) - [DecisionEvaluationInstanceKeyExactMatch](type-aliases/DecisionEvaluationInstanceKeyExactMatch.md) - [DecisionEvaluationInstanceKeyExactMatchWritable](type-aliases/DecisionEvaluationInstanceKeyExactMatchWritable.md) - [DecisionEvaluationInstanceKeyFilterProperty](type-aliases/DecisionEvaluationInstanceKeyFilterProperty.md) - [DecisionEvaluationInstruction](type-aliases/DecisionEvaluationInstruction.md) - [DecisionEvaluationKey](type-aliases/DecisionEvaluationKey.md) - [DecisionEvaluationKeyExactMatch](type-aliases/DecisionEvaluationKeyExactMatch.md) - [DecisionEvaluationKeyExactMatchWritable](type-aliases/DecisionEvaluationKeyExactMatchWritable.md) - [DecisionEvaluationKeyFilterProperty](type-aliases/DecisionEvaluationKeyFilterProperty.md) - [DecisionEvaluationKeyWritable](type-aliases/DecisionEvaluationKeyWritable.md) - [DecisionInstanceDeletionBatchOperationRequest](type-aliases/DecisionInstanceDeletionBatchOperationRequest.md) - [DecisionInstanceFilter](type-aliases/DecisionInstanceFilter.md) - [DecisionInstanceGetQueryResult](type-aliases/DecisionInstanceGetQueryResult.md) - [DecisionInstanceKey](type-aliases/DecisionInstanceKey.md) - [DecisionInstanceKeyWritable](type-aliases/DecisionInstanceKeyWritable.md) - [DecisionInstanceResult](type-aliases/DecisionInstanceResult.md) - [DecisionInstanceSearchQuery](type-aliases/DecisionInstanceSearchQuery.md) - [DecisionInstanceSearchQueryResult](type-aliases/DecisionInstanceSearchQueryResult.md) - [DecisionInstanceSearchQuerySortRequest](type-aliases/DecisionInstanceSearchQuerySortRequest.md) - [DecisionInstanceStateEnum](type-aliases/DecisionInstanceStateEnum.md) - [DecisionInstanceStateExactMatch](type-aliases/DecisionInstanceStateExactMatch.md) - [DecisionInstanceStateExactMatchWritable](type-aliases/DecisionInstanceStateExactMatchWritable.md) - [DecisionInstanceStateFilterProperty](type-aliases/DecisionInstanceStateFilterProperty.md) - [DecisionRequirementsFilter](type-aliases/DecisionRequirementsFilter.md) - [DecisionRequirementsKey](type-aliases/DecisionRequirementsKey.md) - [DecisionRequirementsKeyExactMatch](type-aliases/DecisionRequirementsKeyExactMatch.md) - [DecisionRequirementsKeyExactMatchWritable](type-aliases/DecisionRequirementsKeyExactMatchWritable.md) - [DecisionRequirementsKeyFilterProperty](type-aliases/DecisionRequirementsKeyFilterProperty.md) - [DecisionRequirementsKeyWritable](type-aliases/DecisionRequirementsKeyWritable.md) - [DecisionRequirementsResult](type-aliases/DecisionRequirementsResult.md) - [DecisionRequirementsSearchQuery](type-aliases/DecisionRequirementsSearchQuery.md) - [DecisionRequirementsSearchQueryResult](type-aliases/DecisionRequirementsSearchQueryResult.md) - [DecisionRequirementsSearchQuerySortRequest](type-aliases/DecisionRequirementsSearchQuerySortRequest.md) - [DeleteAuthorizationData](type-aliases/DeleteAuthorizationData.md) - [DeleteAuthorizationError](type-aliases/DeleteAuthorizationError.md) - [DeleteAuthorizationErrors](type-aliases/DeleteAuthorizationErrors.md) - [deleteAuthorizationInput](type-aliases/deleteAuthorizationInput.md) - [DeleteAuthorizationResponse](type-aliases/DeleteAuthorizationResponse.md) - [DeleteAuthorizationResponses](type-aliases/DeleteAuthorizationResponses.md) - [DeleteDecisionInstanceData](type-aliases/DeleteDecisionInstanceData.md) - [DeleteDecisionInstanceError](type-aliases/DeleteDecisionInstanceError.md) - [DeleteDecisionInstanceErrors](type-aliases/DeleteDecisionInstanceErrors.md) - [deleteDecisionInstanceInput](type-aliases/deleteDecisionInstanceInput.md) - [DeleteDecisionInstanceRequest](type-aliases/DeleteDecisionInstanceRequest.md) - [DeleteDecisionInstanceResponse](type-aliases/DeleteDecisionInstanceResponse.md) - [DeleteDecisionInstanceResponses](type-aliases/DeleteDecisionInstanceResponses.md) - [DeleteDecisionInstancesBatchOperationData](type-aliases/DeleteDecisionInstancesBatchOperationData.md) - [DeleteDecisionInstancesBatchOperationError](type-aliases/DeleteDecisionInstancesBatchOperationError.md) - [DeleteDecisionInstancesBatchOperationErrors](type-aliases/DeleteDecisionInstancesBatchOperationErrors.md) - [deleteDecisionInstancesBatchOperationInput](type-aliases/deleteDecisionInstancesBatchOperationInput.md) - [DeleteDecisionInstancesBatchOperationResponse](type-aliases/DeleteDecisionInstancesBatchOperationResponse.md) - [DeleteDecisionInstancesBatchOperationResponses](type-aliases/DeleteDecisionInstancesBatchOperationResponses.md) - [DeleteDocumentData](type-aliases/DeleteDocumentData.md) - [DeleteDocumentError](type-aliases/DeleteDocumentError.md) - [DeleteDocumentErrors](type-aliases/DeleteDocumentErrors.md) - [deleteDocumentInput](type-aliases/deleteDocumentInput.md) - [DeleteDocumentResponse](type-aliases/DeleteDocumentResponse.md) - [DeleteDocumentResponses](type-aliases/DeleteDocumentResponses.md) - [DeleteGlobalClusterVariableData](type-aliases/DeleteGlobalClusterVariableData.md) - [DeleteGlobalClusterVariableError](type-aliases/DeleteGlobalClusterVariableError.md) - [DeleteGlobalClusterVariableErrors](type-aliases/DeleteGlobalClusterVariableErrors.md) - [deleteGlobalClusterVariableInput](type-aliases/deleteGlobalClusterVariableInput.md) - [DeleteGlobalClusterVariableResponse](type-aliases/DeleteGlobalClusterVariableResponse.md) - [DeleteGlobalClusterVariableResponses](type-aliases/DeleteGlobalClusterVariableResponses.md) - [DeleteGlobalTaskListenerData](type-aliases/DeleteGlobalTaskListenerData.md) - [DeleteGlobalTaskListenerError](type-aliases/DeleteGlobalTaskListenerError.md) - [DeleteGlobalTaskListenerErrors](type-aliases/DeleteGlobalTaskListenerErrors.md) - [deleteGlobalTaskListenerInput](type-aliases/deleteGlobalTaskListenerInput.md) - [DeleteGlobalTaskListenerResponse](type-aliases/DeleteGlobalTaskListenerResponse.md) - [DeleteGlobalTaskListenerResponses](type-aliases/DeleteGlobalTaskListenerResponses.md) - [DeleteGroupData](type-aliases/DeleteGroupData.md) - [DeleteGroupError](type-aliases/DeleteGroupError.md) - [DeleteGroupErrors](type-aliases/DeleteGroupErrors.md) - [deleteGroupInput](type-aliases/deleteGroupInput.md) - [DeleteGroupResponse](type-aliases/DeleteGroupResponse.md) - [DeleteGroupResponses](type-aliases/DeleteGroupResponses.md) - [DeleteHistoryBackupAsClusterAdminData](type-aliases/DeleteHistoryBackupAsClusterAdminData.md) - [DeleteHistoryBackupAsClusterAdminError](type-aliases/DeleteHistoryBackupAsClusterAdminError.md) - [DeleteHistoryBackupAsClusterAdminErrors](type-aliases/DeleteHistoryBackupAsClusterAdminErrors.md) - [deleteHistoryBackupAsClusterAdminInput](type-aliases/deleteHistoryBackupAsClusterAdminInput.md) - [DeleteHistoryBackupAsClusterAdminResponse](type-aliases/DeleteHistoryBackupAsClusterAdminResponse.md) - [DeleteHistoryBackupAsClusterAdminResponses](type-aliases/DeleteHistoryBackupAsClusterAdminResponses.md) - [DeleteHistoryBackupData](type-aliases/DeleteHistoryBackupData.md) - [DeleteHistoryBackupError](type-aliases/DeleteHistoryBackupError.md) - [DeleteHistoryBackupErrors](type-aliases/DeleteHistoryBackupErrors.md) - [deleteHistoryBackupInput](type-aliases/deleteHistoryBackupInput.md) - [DeleteHistoryBackupResponse](type-aliases/DeleteHistoryBackupResponse.md) - [DeleteHistoryBackupResponses](type-aliases/DeleteHistoryBackupResponses.md) - [DeleteMappingRuleData](type-aliases/DeleteMappingRuleData.md) - [DeleteMappingRuleError](type-aliases/DeleteMappingRuleError.md) - [DeleteMappingRuleErrors](type-aliases/DeleteMappingRuleErrors.md) - [deleteMappingRuleInput](type-aliases/deleteMappingRuleInput.md) - [DeleteMappingRuleResponse](type-aliases/DeleteMappingRuleResponse.md) - [DeleteMappingRuleResponses](type-aliases/DeleteMappingRuleResponses.md) - [DeleteProcessInstanceData](type-aliases/DeleteProcessInstanceData.md) - [DeleteProcessInstanceError](type-aliases/DeleteProcessInstanceError.md) - [DeleteProcessInstanceErrors](type-aliases/DeleteProcessInstanceErrors.md) - [deleteProcessInstanceInput](type-aliases/deleteProcessInstanceInput.md) - [DeleteProcessInstanceRequest](type-aliases/DeleteProcessInstanceRequest.md) - [DeleteProcessInstanceResponse](type-aliases/DeleteProcessInstanceResponse.md) - [DeleteProcessInstanceResponses](type-aliases/DeleteProcessInstanceResponses.md) - [DeleteProcessInstancesBatchOperationData](type-aliases/DeleteProcessInstancesBatchOperationData.md) - [DeleteProcessInstancesBatchOperationError](type-aliases/DeleteProcessInstancesBatchOperationError.md) - [DeleteProcessInstancesBatchOperationErrors](type-aliases/DeleteProcessInstancesBatchOperationErrors.md) - [deleteProcessInstancesBatchOperationInput](type-aliases/deleteProcessInstancesBatchOperationInput.md) - [DeleteProcessInstancesBatchOperationResponse](type-aliases/DeleteProcessInstancesBatchOperationResponse.md) - [DeleteProcessInstancesBatchOperationResponses](type-aliases/DeleteProcessInstancesBatchOperationResponses.md) - [DeleteResourceData](type-aliases/DeleteResourceData.md) - [DeleteResourceError](type-aliases/DeleteResourceError.md) - [DeleteResourceErrors](type-aliases/DeleteResourceErrors.md) - [deleteResourceInput](type-aliases/deleteResourceInput.md) - [DeleteResourceRequest](type-aliases/DeleteResourceRequest.md) - [DeleteResourceResponse](type-aliases/DeleteResourceResponse.md) - [DeleteResourceResponse2](type-aliases/DeleteResourceResponse2.md) - [DeleteResourceResponses](type-aliases/DeleteResourceResponses.md) - [DeleteRoleData](type-aliases/DeleteRoleData.md) - [DeleteRoleError](type-aliases/DeleteRoleError.md) - [DeleteRoleErrors](type-aliases/DeleteRoleErrors.md) - [deleteRoleInput](type-aliases/deleteRoleInput.md) - [DeleteRoleResponse](type-aliases/DeleteRoleResponse.md) - [DeleteRoleResponses](type-aliases/DeleteRoleResponses.md) - [DeleteRuntimeBackupAsClusterAdminData](type-aliases/DeleteRuntimeBackupAsClusterAdminData.md) - [DeleteRuntimeBackupAsClusterAdminError](type-aliases/DeleteRuntimeBackupAsClusterAdminError.md) - [DeleteRuntimeBackupAsClusterAdminErrors](type-aliases/DeleteRuntimeBackupAsClusterAdminErrors.md) - [deleteRuntimeBackupAsClusterAdminInput](type-aliases/deleteRuntimeBackupAsClusterAdminInput.md) - [DeleteRuntimeBackupAsClusterAdminResponse](type-aliases/DeleteRuntimeBackupAsClusterAdminResponse.md) - [DeleteRuntimeBackupAsClusterAdminResponses](type-aliases/DeleteRuntimeBackupAsClusterAdminResponses.md) - [DeleteRuntimeBackupData](type-aliases/DeleteRuntimeBackupData.md) - [DeleteRuntimeBackupError](type-aliases/DeleteRuntimeBackupError.md) - [DeleteRuntimeBackupErrors](type-aliases/DeleteRuntimeBackupErrors.md) - [deleteRuntimeBackupInput](type-aliases/deleteRuntimeBackupInput.md) - [DeleteRuntimeBackupResponse](type-aliases/DeleteRuntimeBackupResponse.md) - [DeleteRuntimeBackupResponses](type-aliases/DeleteRuntimeBackupResponses.md) - [DeleteRuntimeBackupStateAsClusterAdminData](type-aliases/DeleteRuntimeBackupStateAsClusterAdminData.md) - [DeleteRuntimeBackupStateAsClusterAdminError](type-aliases/DeleteRuntimeBackupStateAsClusterAdminError.md) - [DeleteRuntimeBackupStateAsClusterAdminErrors](type-aliases/DeleteRuntimeBackupStateAsClusterAdminErrors.md) - [deleteRuntimeBackupStateAsClusterAdminInput](type-aliases/deleteRuntimeBackupStateAsClusterAdminInput.md) - [DeleteRuntimeBackupStateAsClusterAdminResponse](type-aliases/DeleteRuntimeBackupStateAsClusterAdminResponse.md) - [DeleteRuntimeBackupStateAsClusterAdminResponses](type-aliases/DeleteRuntimeBackupStateAsClusterAdminResponses.md) - [DeleteRuntimeBackupStateData](type-aliases/DeleteRuntimeBackupStateData.md) - [DeleteRuntimeBackupStateError](type-aliases/DeleteRuntimeBackupStateError.md) - [DeleteRuntimeBackupStateErrors](type-aliases/DeleteRuntimeBackupStateErrors.md) - [deleteRuntimeBackupStateInput](type-aliases/deleteRuntimeBackupStateInput.md) - [DeleteRuntimeBackupStateResponse](type-aliases/DeleteRuntimeBackupStateResponse.md) - [DeleteRuntimeBackupStateResponses](type-aliases/DeleteRuntimeBackupStateResponses.md) - [DeleteTenantClusterVariableData](type-aliases/DeleteTenantClusterVariableData.md) - [DeleteTenantClusterVariableError](type-aliases/DeleteTenantClusterVariableError.md) - [DeleteTenantClusterVariableErrors](type-aliases/DeleteTenantClusterVariableErrors.md) - [deleteTenantClusterVariableInput](type-aliases/deleteTenantClusterVariableInput.md) - [DeleteTenantClusterVariableResponse](type-aliases/DeleteTenantClusterVariableResponse.md) - [DeleteTenantClusterVariableResponses](type-aliases/DeleteTenantClusterVariableResponses.md) - [DeleteTenantData](type-aliases/DeleteTenantData.md) - [DeleteTenantError](type-aliases/DeleteTenantError.md) - [DeleteTenantErrors](type-aliases/DeleteTenantErrors.md) - [deleteTenantInput](type-aliases/deleteTenantInput.md) - [DeleteTenantResponse](type-aliases/DeleteTenantResponse.md) - [DeleteTenantResponses](type-aliases/DeleteTenantResponses.md) - [DeleteUserData](type-aliases/DeleteUserData.md) - [DeleteUserError](type-aliases/DeleteUserError.md) - [DeleteUserErrors](type-aliases/DeleteUserErrors.md) - [deleteUserInput](type-aliases/deleteUserInput.md) - [DeleteUserResponse](type-aliases/DeleteUserResponse.md) - [DeleteUserResponses](type-aliases/DeleteUserResponses.md) - [DeploymentConfigurationResponse](type-aliases/DeploymentConfigurationResponse.md) - [DeploymentDecisionRequirementsResult](type-aliases/DeploymentDecisionRequirementsResult.md) - [DeploymentDecisionResult](type-aliases/DeploymentDecisionResult.md) - [DeploymentFormResult](type-aliases/DeploymentFormResult.md) - [DeploymentKey](type-aliases/DeploymentKey.md) - [DeploymentKeyExactMatch](type-aliases/DeploymentKeyExactMatch.md) - [DeploymentKeyExactMatchWritable](type-aliases/DeploymentKeyExactMatchWritable.md) - [DeploymentKeyFilterProperty](type-aliases/DeploymentKeyFilterProperty.md) - [DeploymentKeyWritable](type-aliases/DeploymentKeyWritable.md) - [DeploymentMetadataResult](type-aliases/DeploymentMetadataResult.md) - [DeploymentProcessResult](type-aliases/DeploymentProcessResult.md) - [DeploymentResourceResult](type-aliases/DeploymentResourceResult.md) - [DeploymentResult](type-aliases/DeploymentResult.md) - [DirectAncestorKeyInstruction](type-aliases/DirectAncestorKeyInstruction.md) - [DocumentCreationBatchResponse](type-aliases/DocumentCreationBatchResponse.md) - [DocumentCreationFailureDetail](type-aliases/DocumentCreationFailureDetail.md) - [DocumentId](type-aliases/DocumentId.md) - [DocumentLink](type-aliases/DocumentLink.md) - [DocumentLinkRequest](type-aliases/DocumentLinkRequest.md) - [DocumentMetadata](type-aliases/DocumentMetadata.md) - [DocumentMetadataResponse](type-aliases/DocumentMetadataResponse.md) - [DocumentReference](type-aliases/DocumentReference.md) - [ElementId](type-aliases/ElementId.md) - [ElementIdExactMatch](type-aliases/ElementIdExactMatch.md) - [ElementIdExactMatchWritable](type-aliases/ElementIdExactMatchWritable.md) - [ElementIdFilterProperty](type-aliases/ElementIdFilterProperty.md) - [ElementInstanceFilter](type-aliases/ElementInstanceFilter.md) - [ElementInstanceFilterFields](type-aliases/ElementInstanceFilterFields.md) - [ElementInstanceKey](type-aliases/ElementInstanceKey.md) - [ElementInstanceKeyExactMatch](type-aliases/ElementInstanceKeyExactMatch.md) - [ElementInstanceKeyExactMatchWritable](type-aliases/ElementInstanceKeyExactMatchWritable.md) - [ElementInstanceKeyFilterProperty](type-aliases/ElementInstanceKeyFilterProperty.md) - [ElementInstanceKeyWritable](type-aliases/ElementInstanceKeyWritable.md) - [ElementInstanceResult](type-aliases/ElementInstanceResult.md) - [ElementInstanceSearchQuery](type-aliases/ElementInstanceSearchQuery.md) - [ElementInstanceSearchQueryResult](type-aliases/ElementInstanceSearchQueryResult.md) - [ElementInstanceSearchQuerySortRequest](type-aliases/ElementInstanceSearchQuerySortRequest.md) - [ElementInstanceStateEnum](type-aliases/ElementInstanceStateEnum.md) - [ElementInstanceStateExactMatch](type-aliases/ElementInstanceStateExactMatch.md) - [ElementInstanceStateExactMatchWritable](type-aliases/ElementInstanceStateExactMatchWritable.md) - [ElementInstanceStateFilterProperty](type-aliases/ElementInstanceStateFilterProperty.md) - [ElementInstanceWaitStateFilter](type-aliases/ElementInstanceWaitStateFilter.md) - [ElementInstanceWaitStateQuery](type-aliases/ElementInstanceWaitStateQuery.md) - [ElementInstanceWaitStateQueryResult](type-aliases/ElementInstanceWaitStateQueryResult.md) - [ElementInstanceWaitStateQuerySortRequest](type-aliases/ElementInstanceWaitStateQuerySortRequest.md) - [ElementInstanceWaitStateResult](type-aliases/ElementInstanceWaitStateResult.md) - [EndCursor](type-aliases/EndCursor.md) - [EnrichedActivatedJobOf](type-aliases/EnrichedActivatedJobOf.md) - [EntityTypeExactMatch](type-aliases/EntityTypeExactMatch.md) - [EntityTypeExactMatchWritable](type-aliases/EntityTypeExactMatchWritable.md) - [EntityTypeFilterProperty](type-aliases/EntityTypeFilterProperty.md) - [EvaluateConditionalResult](type-aliases/EvaluateConditionalResult.md) - [EvaluateConditionalsData](type-aliases/EvaluateConditionalsData.md) - [EvaluateConditionalsError](type-aliases/EvaluateConditionalsError.md) - [EvaluateConditionalsErrors](type-aliases/EvaluateConditionalsErrors.md) - [evaluateConditionalsInput](type-aliases/evaluateConditionalsInput.md) - [EvaluateConditionalsResponse](type-aliases/EvaluateConditionalsResponse.md) - [EvaluateConditionalsResponses](type-aliases/EvaluateConditionalsResponses.md) - [EvaluatedDecisionInputItem](type-aliases/EvaluatedDecisionInputItem.md) - [EvaluatedDecisionOutputItem](type-aliases/EvaluatedDecisionOutputItem.md) - [EvaluatedDecisionResult](type-aliases/EvaluatedDecisionResult.md) - [EvaluateDecisionData](type-aliases/EvaluateDecisionData.md) - [EvaluateDecisionError](type-aliases/EvaluateDecisionError.md) - [EvaluateDecisionErrors](type-aliases/EvaluateDecisionErrors.md) - [evaluateDecisionInput](type-aliases/evaluateDecisionInput.md) - [EvaluateDecisionResponse](type-aliases/EvaluateDecisionResponse.md) - [EvaluateDecisionResponses](type-aliases/EvaluateDecisionResponses.md) - [EvaluateDecisionResult](type-aliases/EvaluateDecisionResult.md) - [EvaluateExpressionData](type-aliases/EvaluateExpressionData.md) - [EvaluateExpressionError](type-aliases/EvaluateExpressionError.md) - [EvaluateExpressionErrors](type-aliases/EvaluateExpressionErrors.md) - [evaluateExpressionInput](type-aliases/evaluateExpressionInput.md) - [EvaluateExpressionResponse](type-aliases/EvaluateExpressionResponse.md) - [EvaluateExpressionResponses](type-aliases/EvaluateExpressionResponses.md) - [ExportingStatusCode](type-aliases/ExportingStatusCode.md) - [ExportingStatusResponse](type-aliases/ExportingStatusResponse.md) - [ExpressionEvaluationRequest](type-aliases/ExpressionEvaluationRequest.md) - [ExpressionEvaluationResult](type-aliases/ExpressionEvaluationResult.md) - [ExpressionEvaluationWarningItem](type-aliases/ExpressionEvaluationWarningItem.md) - [ExpressionSecretReferenceItem](type-aliases/ExpressionSecretReferenceItem.md) - [FailJobData](type-aliases/FailJobData.md) - [FailJobError](type-aliases/FailJobError.md) - [FailJobErrors](type-aliases/FailJobErrors.md) - [failJobInput](type-aliases/failJobInput.md) - [FailJobResponse](type-aliases/FailJobResponse.md) - [FailJobResponses](type-aliases/FailJobResponses.md) - [FetchPage](type-aliases/FetchPage.md) - [FormId](type-aliases/FormId.md) - [FormKey](type-aliases/FormKey.md) - [FormKeyExactMatch](type-aliases/FormKeyExactMatch.md) - [FormKeyExactMatchWritable](type-aliases/FormKeyExactMatchWritable.md) - [FormKeyFilterProperty](type-aliases/FormKeyFilterProperty.md) - [FormKeyWritable](type-aliases/FormKeyWritable.md) - [FormResult](type-aliases/FormResult.md) - [getAgentDefinitionConsistency](type-aliases/getAgentDefinitionConsistency.md) - [GetAgentDefinitionData](type-aliases/GetAgentDefinitionData.md) - [GetAgentDefinitionError](type-aliases/GetAgentDefinitionError.md) - [GetAgentDefinitionErrors](type-aliases/GetAgentDefinitionErrors.md) - [getAgentDefinitionInput](type-aliases/getAgentDefinitionInput.md) - [GetAgentDefinitionResponse](type-aliases/GetAgentDefinitionResponse.md) - [GetAgentDefinitionResponses](type-aliases/GetAgentDefinitionResponses.md) - [getAgentInstanceConsistency](type-aliases/getAgentInstanceConsistency.md) - [GetAgentInstanceData](type-aliases/GetAgentInstanceData.md) - [GetAgentInstanceError](type-aliases/GetAgentInstanceError.md) - [GetAgentInstanceErrors](type-aliases/GetAgentInstanceErrors.md) - [getAgentInstanceInput](type-aliases/getAgentInstanceInput.md) - [GetAgentInstanceResponse](type-aliases/GetAgentInstanceResponse.md) - [GetAgentInstanceResponses](type-aliases/GetAgentInstanceResponses.md) - [getAuditLogConsistency](type-aliases/getAuditLogConsistency.md) - [GetAuditLogData](type-aliases/GetAuditLogData.md) - [GetAuditLogError](type-aliases/GetAuditLogError.md) - [GetAuditLogErrors](type-aliases/GetAuditLogErrors.md) - [getAuditLogInput](type-aliases/getAuditLogInput.md) - [GetAuditLogResponse](type-aliases/GetAuditLogResponse.md) - [GetAuditLogResponses](type-aliases/GetAuditLogResponses.md) - [GetAuthenticationData](type-aliases/GetAuthenticationData.md) - [GetAuthenticationError](type-aliases/GetAuthenticationError.md) - [GetAuthenticationErrors](type-aliases/GetAuthenticationErrors.md) - [getAuthenticationInput](type-aliases/getAuthenticationInput.md) - [GetAuthenticationResponse](type-aliases/GetAuthenticationResponse.md) - [GetAuthenticationResponses](type-aliases/GetAuthenticationResponses.md) - [getAuthorizationConsistency](type-aliases/getAuthorizationConsistency.md) - [GetAuthorizationData](type-aliases/GetAuthorizationData.md) - [GetAuthorizationError](type-aliases/GetAuthorizationError.md) - [GetAuthorizationErrors](type-aliases/GetAuthorizationErrors.md) - [getAuthorizationInput](type-aliases/getAuthorizationInput.md) - [GetAuthorizationResponse](type-aliases/GetAuthorizationResponse.md) - [GetAuthorizationResponses](type-aliases/GetAuthorizationResponses.md) - [getBatchOperationConsistency](type-aliases/getBatchOperationConsistency.md) - [GetBatchOperationData](type-aliases/GetBatchOperationData.md) - [GetBatchOperationError](type-aliases/GetBatchOperationError.md) - [GetBatchOperationErrors](type-aliases/GetBatchOperationErrors.md) - [getBatchOperationInput](type-aliases/getBatchOperationInput.md) - [GetBatchOperationResponse](type-aliases/GetBatchOperationResponse.md) - [GetBatchOperationResponses](type-aliases/GetBatchOperationResponses.md) - [GetClusterExportingStatusData](type-aliases/GetClusterExportingStatusData.md) - [GetClusterExportingStatusError](type-aliases/GetClusterExportingStatusError.md) - [GetClusterExportingStatusErrors](type-aliases/GetClusterExportingStatusErrors.md) - [getClusterExportingStatusInput](type-aliases/getClusterExportingStatusInput.md) - [GetClusterExportingStatusResponse](type-aliases/GetClusterExportingStatusResponse.md) - [GetClusterExportingStatusResponses](type-aliases/GetClusterExportingStatusResponses.md) - [GetClusterRebalanceData](type-aliases/GetClusterRebalanceData.md) - [GetClusterRebalanceError](type-aliases/GetClusterRebalanceError.md) - [GetClusterRebalanceErrors](type-aliases/GetClusterRebalanceErrors.md) - [getClusterRebalanceInput](type-aliases/getClusterRebalanceInput.md) - [GetClusterRebalanceResponse](type-aliases/GetClusterRebalanceResponse.md) - [GetClusterRebalanceResponses](type-aliases/GetClusterRebalanceResponses.md) - [GetClusterStatusData](type-aliases/GetClusterStatusData.md) - [GetClusterStatusError](type-aliases/GetClusterStatusError.md) - [GetClusterStatusErrors](type-aliases/GetClusterStatusErrors.md) - [getClusterStatusInput](type-aliases/getClusterStatusInput.md) - [GetClusterStatusResponse](type-aliases/GetClusterStatusResponse.md) - [GetClusterStatusResponses](type-aliases/GetClusterStatusResponses.md) - [GetClusterTopologyData](type-aliases/GetClusterTopologyData.md) - [GetClusterTopologyError](type-aliases/GetClusterTopologyError.md) - [GetClusterTopologyErrors](type-aliases/GetClusterTopologyErrors.md) - [getClusterTopologyInput](type-aliases/getClusterTopologyInput.md) - [GetClusterTopologyResponse](type-aliases/GetClusterTopologyResponse.md) - [GetClusterTopologyResponses](type-aliases/GetClusterTopologyResponses.md) - [GetClusterUpgradeStatusData](type-aliases/GetClusterUpgradeStatusData.md) - [getClusterUpgradeStatusInput](type-aliases/getClusterUpgradeStatusInput.md) - [GetClusterUpgradeStatusResponse](type-aliases/GetClusterUpgradeStatusResponse.md) - [GetClusterUpgradeStatusResponses](type-aliases/GetClusterUpgradeStatusResponses.md) - [getDecisionDefinitionConsistency](type-aliases/getDecisionDefinitionConsistency.md) - [GetDecisionDefinitionData](type-aliases/GetDecisionDefinitionData.md) - [GetDecisionDefinitionError](type-aliases/GetDecisionDefinitionError.md) - [GetDecisionDefinitionErrors](type-aliases/GetDecisionDefinitionErrors.md) - [getDecisionDefinitionInput](type-aliases/getDecisionDefinitionInput.md) - [GetDecisionDefinitionResponse](type-aliases/GetDecisionDefinitionResponse.md) - [GetDecisionDefinitionResponses](type-aliases/GetDecisionDefinitionResponses.md) - [getDecisionDefinitionXmlConsistency](type-aliases/getDecisionDefinitionXmlConsistency.md) - [GetDecisionDefinitionXmlData](type-aliases/GetDecisionDefinitionXmlData.md) - [GetDecisionDefinitionXmlError](type-aliases/GetDecisionDefinitionXmlError.md) - [GetDecisionDefinitionXmlErrors](type-aliases/GetDecisionDefinitionXmlErrors.md) - [getDecisionDefinitionXmlInput](type-aliases/getDecisionDefinitionXmlInput.md) - [GetDecisionDefinitionXmlResponse](type-aliases/GetDecisionDefinitionXmlResponse.md) - [GetDecisionDefinitionXmlResponses](type-aliases/GetDecisionDefinitionXmlResponses.md) - [getDecisionInstanceConsistency](type-aliases/getDecisionInstanceConsistency.md) - [GetDecisionInstanceData](type-aliases/GetDecisionInstanceData.md) - [GetDecisionInstanceError](type-aliases/GetDecisionInstanceError.md) - [GetDecisionInstanceErrors](type-aliases/GetDecisionInstanceErrors.md) - [getDecisionInstanceInput](type-aliases/getDecisionInstanceInput.md) - [GetDecisionInstanceResponse](type-aliases/GetDecisionInstanceResponse.md) - [GetDecisionInstanceResponses](type-aliases/GetDecisionInstanceResponses.md) - [getDecisionRequirementsConsistency](type-aliases/getDecisionRequirementsConsistency.md) - [GetDecisionRequirementsData](type-aliases/GetDecisionRequirementsData.md) - [GetDecisionRequirementsError](type-aliases/GetDecisionRequirementsError.md) - [GetDecisionRequirementsErrors](type-aliases/GetDecisionRequirementsErrors.md) - [getDecisionRequirementsInput](type-aliases/getDecisionRequirementsInput.md) - [GetDecisionRequirementsResponse](type-aliases/GetDecisionRequirementsResponse.md) - [GetDecisionRequirementsResponses](type-aliases/GetDecisionRequirementsResponses.md) - [getDecisionRequirementsXmlConsistency](type-aliases/getDecisionRequirementsXmlConsistency.md) - [GetDecisionRequirementsXmlData](type-aliases/GetDecisionRequirementsXmlData.md) - [GetDecisionRequirementsXmlError](type-aliases/GetDecisionRequirementsXmlError.md) - [GetDecisionRequirementsXmlErrors](type-aliases/GetDecisionRequirementsXmlErrors.md) - [getDecisionRequirementsXmlInput](type-aliases/getDecisionRequirementsXmlInput.md) - [GetDecisionRequirementsXmlResponse](type-aliases/GetDecisionRequirementsXmlResponse.md) - [GetDecisionRequirementsXmlResponses](type-aliases/GetDecisionRequirementsXmlResponses.md) - [GetDocumentData](type-aliases/GetDocumentData.md) - [GetDocumentError](type-aliases/GetDocumentError.md) - [GetDocumentErrors](type-aliases/GetDocumentErrors.md) - [getDocumentInput](type-aliases/getDocumentInput.md) - [GetDocumentResponse](type-aliases/GetDocumentResponse.md) - [GetDocumentResponses](type-aliases/GetDocumentResponses.md) - [getElementInstanceConsistency](type-aliases/getElementInstanceConsistency.md) - [GetElementInstanceData](type-aliases/GetElementInstanceData.md) - [GetElementInstanceError](type-aliases/GetElementInstanceError.md) - [GetElementInstanceErrors](type-aliases/GetElementInstanceErrors.md) - [getElementInstanceInput](type-aliases/getElementInstanceInput.md) - [GetElementInstanceResponse](type-aliases/GetElementInstanceResponse.md) - [GetElementInstanceResponses](type-aliases/GetElementInstanceResponses.md) - [GetExportingStatusData](type-aliases/GetExportingStatusData.md) - [GetExportingStatusError](type-aliases/GetExportingStatusError.md) - [GetExportingStatusErrors](type-aliases/GetExportingStatusErrors.md) - [getExportingStatusInput](type-aliases/getExportingStatusInput.md) - [GetExportingStatusResponse](type-aliases/GetExportingStatusResponse.md) - [GetExportingStatusResponses](type-aliases/GetExportingStatusResponses.md) - [getFormByKeyConsistency](type-aliases/getFormByKeyConsistency.md) - [GetFormByKeyData](type-aliases/GetFormByKeyData.md) - [GetFormByKeyError](type-aliases/GetFormByKeyError.md) - [GetFormByKeyErrors](type-aliases/GetFormByKeyErrors.md) - [getFormByKeyInput](type-aliases/getFormByKeyInput.md) - [GetFormByKeyResponse](type-aliases/GetFormByKeyResponse.md) - [GetFormByKeyResponses](type-aliases/GetFormByKeyResponses.md) - [getGlobalClusterVariableConsistency](type-aliases/getGlobalClusterVariableConsistency.md) - [GetGlobalClusterVariableData](type-aliases/GetGlobalClusterVariableData.md) - [GetGlobalClusterVariableError](type-aliases/GetGlobalClusterVariableError.md) - [GetGlobalClusterVariableErrors](type-aliases/GetGlobalClusterVariableErrors.md) - [getGlobalClusterVariableInput](type-aliases/getGlobalClusterVariableInput.md) - [GetGlobalClusterVariableResponse](type-aliases/GetGlobalClusterVariableResponse.md) - [GetGlobalClusterVariableResponses](type-aliases/GetGlobalClusterVariableResponses.md) - [getGlobalJobStatisticsConsistency](type-aliases/getGlobalJobStatisticsConsistency.md) - [GetGlobalJobStatisticsData](type-aliases/GetGlobalJobStatisticsData.md) - [GetGlobalJobStatisticsError](type-aliases/GetGlobalJobStatisticsError.md) - [GetGlobalJobStatisticsErrors](type-aliases/GetGlobalJobStatisticsErrors.md) - [getGlobalJobStatisticsInput](type-aliases/getGlobalJobStatisticsInput.md) - [GetGlobalJobStatisticsResponse](type-aliases/GetGlobalJobStatisticsResponse.md) - [GetGlobalJobStatisticsResponses](type-aliases/GetGlobalJobStatisticsResponses.md) - [getGlobalTaskListenerConsistency](type-aliases/getGlobalTaskListenerConsistency.md) - [GetGlobalTaskListenerData](type-aliases/GetGlobalTaskListenerData.md) - [GetGlobalTaskListenerError](type-aliases/GetGlobalTaskListenerError.md) - [GetGlobalTaskListenerErrors](type-aliases/GetGlobalTaskListenerErrors.md) - [getGlobalTaskListenerInput](type-aliases/getGlobalTaskListenerInput.md) - [GetGlobalTaskListenerResponse](type-aliases/GetGlobalTaskListenerResponse.md) - [GetGlobalTaskListenerResponses](type-aliases/GetGlobalTaskListenerResponses.md) - [getGroupConsistency](type-aliases/getGroupConsistency.md) - [GetGroupData](type-aliases/GetGroupData.md) - [GetGroupError](type-aliases/GetGroupError.md) - [GetGroupErrors](type-aliases/GetGroupErrors.md) - [getGroupInput](type-aliases/getGroupInput.md) - [GetGroupResponse](type-aliases/GetGroupResponse.md) - [GetGroupResponses](type-aliases/GetGroupResponses.md) - [GetHistoryBackupAsClusterAdminData](type-aliases/GetHistoryBackupAsClusterAdminData.md) - [GetHistoryBackupAsClusterAdminError](type-aliases/GetHistoryBackupAsClusterAdminError.md) - [GetHistoryBackupAsClusterAdminErrors](type-aliases/GetHistoryBackupAsClusterAdminErrors.md) - [getHistoryBackupAsClusterAdminInput](type-aliases/getHistoryBackupAsClusterAdminInput.md) - [GetHistoryBackupAsClusterAdminResponse](type-aliases/GetHistoryBackupAsClusterAdminResponse.md) - [GetHistoryBackupAsClusterAdminResponses](type-aliases/GetHistoryBackupAsClusterAdminResponses.md) - [GetHistoryBackupData](type-aliases/GetHistoryBackupData.md) - [GetHistoryBackupError](type-aliases/GetHistoryBackupError.md) - [GetHistoryBackupErrors](type-aliases/GetHistoryBackupErrors.md) - [getHistoryBackupInput](type-aliases/getHistoryBackupInput.md) - [GetHistoryBackupResponse](type-aliases/GetHistoryBackupResponse.md) - [GetHistoryBackupResponses](type-aliases/GetHistoryBackupResponses.md) - [getIncidentConsistency](type-aliases/getIncidentConsistency.md) - [GetIncidentData](type-aliases/GetIncidentData.md) - [GetIncidentError](type-aliases/GetIncidentError.md) - [GetIncidentErrors](type-aliases/GetIncidentErrors.md) - [getIncidentInput](type-aliases/getIncidentInput.md) - [GetIncidentResponse](type-aliases/GetIncidentResponse.md) - [GetIncidentResponses](type-aliases/GetIncidentResponses.md) - [getJobErrorStatisticsConsistency](type-aliases/getJobErrorStatisticsConsistency.md) - [GetJobErrorStatisticsData](type-aliases/GetJobErrorStatisticsData.md) - [GetJobErrorStatisticsError](type-aliases/GetJobErrorStatisticsError.md) - [GetJobErrorStatisticsErrors](type-aliases/GetJobErrorStatisticsErrors.md) - [getJobErrorStatisticsInput](type-aliases/getJobErrorStatisticsInput.md) - [GetJobErrorStatisticsResponse](type-aliases/GetJobErrorStatisticsResponse.md) - [GetJobErrorStatisticsResponses](type-aliases/GetJobErrorStatisticsResponses.md) - [getJobTimeSeriesStatisticsConsistency](type-aliases/getJobTimeSeriesStatisticsConsistency.md) - [GetJobTimeSeriesStatisticsData](type-aliases/GetJobTimeSeriesStatisticsData.md) - [GetJobTimeSeriesStatisticsError](type-aliases/GetJobTimeSeriesStatisticsError.md) - [GetJobTimeSeriesStatisticsErrors](type-aliases/GetJobTimeSeriesStatisticsErrors.md) - [getJobTimeSeriesStatisticsInput](type-aliases/getJobTimeSeriesStatisticsInput.md) - [GetJobTimeSeriesStatisticsResponse](type-aliases/GetJobTimeSeriesStatisticsResponse.md) - [GetJobTimeSeriesStatisticsResponses](type-aliases/GetJobTimeSeriesStatisticsResponses.md) - [getJobTypeStatisticsConsistency](type-aliases/getJobTypeStatisticsConsistency.md) - [GetJobTypeStatisticsData](type-aliases/GetJobTypeStatisticsData.md) - [GetJobTypeStatisticsError](type-aliases/GetJobTypeStatisticsError.md) - [GetJobTypeStatisticsErrors](type-aliases/GetJobTypeStatisticsErrors.md) - [getJobTypeStatisticsInput](type-aliases/getJobTypeStatisticsInput.md) - [GetJobTypeStatisticsResponse](type-aliases/GetJobTypeStatisticsResponse.md) - [GetJobTypeStatisticsResponses](type-aliases/GetJobTypeStatisticsResponses.md) - [getJobWorkerStatisticsConsistency](type-aliases/getJobWorkerStatisticsConsistency.md) - [GetJobWorkerStatisticsData](type-aliases/GetJobWorkerStatisticsData.md) - [GetJobWorkerStatisticsError](type-aliases/GetJobWorkerStatisticsError.md) - [GetJobWorkerStatisticsErrors](type-aliases/GetJobWorkerStatisticsErrors.md) - [getJobWorkerStatisticsInput](type-aliases/getJobWorkerStatisticsInput.md) - [GetJobWorkerStatisticsResponse](type-aliases/GetJobWorkerStatisticsResponse.md) - [GetJobWorkerStatisticsResponses](type-aliases/GetJobWorkerStatisticsResponses.md) - [GetLicenseData](type-aliases/GetLicenseData.md) - [GetLicenseError](type-aliases/GetLicenseError.md) - [GetLicenseErrors](type-aliases/GetLicenseErrors.md) - [getLicenseInput](type-aliases/getLicenseInput.md) - [GetLicenseResponse](type-aliases/GetLicenseResponse.md) - [GetLicenseResponses](type-aliases/GetLicenseResponses.md) - [getMappingRuleConsistency](type-aliases/getMappingRuleConsistency.md) - [GetMappingRuleData](type-aliases/GetMappingRuleData.md) - [GetMappingRuleError](type-aliases/GetMappingRuleError.md) - [GetMappingRuleErrors](type-aliases/GetMappingRuleErrors.md) - [getMappingRuleInput](type-aliases/getMappingRuleInput.md) - [GetMappingRuleResponse](type-aliases/GetMappingRuleResponse.md) - [GetMappingRuleResponses](type-aliases/GetMappingRuleResponses.md) - [getProcessDefinitionConsistency](type-aliases/getProcessDefinitionConsistency.md) - [GetProcessDefinitionData](type-aliases/GetProcessDefinitionData.md) - [GetProcessDefinitionError](type-aliases/GetProcessDefinitionError.md) - [GetProcessDefinitionErrors](type-aliases/GetProcessDefinitionErrors.md) - [getProcessDefinitionInput](type-aliases/getProcessDefinitionInput.md) - [getProcessDefinitionInstanceStatisticsConsistency](type-aliases/getProcessDefinitionInstanceStatisticsConsistency.md) - [GetProcessDefinitionInstanceStatisticsData](type-aliases/GetProcessDefinitionInstanceStatisticsData.md) - [GetProcessDefinitionInstanceStatisticsError](type-aliases/GetProcessDefinitionInstanceStatisticsError.md) - [GetProcessDefinitionInstanceStatisticsErrors](type-aliases/GetProcessDefinitionInstanceStatisticsErrors.md) - [getProcessDefinitionInstanceStatisticsInput](type-aliases/getProcessDefinitionInstanceStatisticsInput.md) - [GetProcessDefinitionInstanceStatisticsResponse](type-aliases/GetProcessDefinitionInstanceStatisticsResponse.md) - [GetProcessDefinitionInstanceStatisticsResponses](type-aliases/GetProcessDefinitionInstanceStatisticsResponses.md) - [getProcessDefinitionInstanceVersionStatisticsConsistency](type-aliases/getProcessDefinitionInstanceVersionStatisticsConsistency.md) - [GetProcessDefinitionInstanceVersionStatisticsData](type-aliases/GetProcessDefinitionInstanceVersionStatisticsData.md) - [GetProcessDefinitionInstanceVersionStatisticsError](type-aliases/GetProcessDefinitionInstanceVersionStatisticsError.md) - [GetProcessDefinitionInstanceVersionStatisticsErrors](type-aliases/GetProcessDefinitionInstanceVersionStatisticsErrors.md) - [getProcessDefinitionInstanceVersionStatisticsInput](type-aliases/getProcessDefinitionInstanceVersionStatisticsInput.md) - [GetProcessDefinitionInstanceVersionStatisticsResponse](type-aliases/GetProcessDefinitionInstanceVersionStatisticsResponse.md) - [GetProcessDefinitionInstanceVersionStatisticsResponses](type-aliases/GetProcessDefinitionInstanceVersionStatisticsResponses.md) - [getProcessDefinitionMessageSubscriptionStatisticsConsistency](type-aliases/getProcessDefinitionMessageSubscriptionStatisticsConsistency.md) - [GetProcessDefinitionMessageSubscriptionStatisticsData](type-aliases/GetProcessDefinitionMessageSubscriptionStatisticsData.md) - [GetProcessDefinitionMessageSubscriptionStatisticsError](type-aliases/GetProcessDefinitionMessageSubscriptionStatisticsError.md) - [GetProcessDefinitionMessageSubscriptionStatisticsErrors](type-aliases/GetProcessDefinitionMessageSubscriptionStatisticsErrors.md) - [getProcessDefinitionMessageSubscriptionStatisticsInput](type-aliases/getProcessDefinitionMessageSubscriptionStatisticsInput.md) - [GetProcessDefinitionMessageSubscriptionStatisticsResponse](type-aliases/GetProcessDefinitionMessageSubscriptionStatisticsResponse.md) - [GetProcessDefinitionMessageSubscriptionStatisticsResponses](type-aliases/GetProcessDefinitionMessageSubscriptionStatisticsResponses.md) - [GetProcessDefinitionResponse](type-aliases/GetProcessDefinitionResponse.md) - [GetProcessDefinitionResponses](type-aliases/GetProcessDefinitionResponses.md) - [getProcessDefinitionStatisticsConsistency](type-aliases/getProcessDefinitionStatisticsConsistency.md) - [GetProcessDefinitionStatisticsData](type-aliases/GetProcessDefinitionStatisticsData.md) - [GetProcessDefinitionStatisticsError](type-aliases/GetProcessDefinitionStatisticsError.md) - [GetProcessDefinitionStatisticsErrors](type-aliases/GetProcessDefinitionStatisticsErrors.md) - [getProcessDefinitionStatisticsInput](type-aliases/getProcessDefinitionStatisticsInput.md) - [GetProcessDefinitionStatisticsResponse](type-aliases/GetProcessDefinitionStatisticsResponse.md) - [GetProcessDefinitionStatisticsResponses](type-aliases/GetProcessDefinitionStatisticsResponses.md) - [getProcessDefinitionXmlConsistency](type-aliases/getProcessDefinitionXmlConsistency.md) - [GetProcessDefinitionXmlData](type-aliases/GetProcessDefinitionXmlData.md) - [GetProcessDefinitionXmlError](type-aliases/GetProcessDefinitionXmlError.md) - [GetProcessDefinitionXmlErrors](type-aliases/GetProcessDefinitionXmlErrors.md) - [getProcessDefinitionXmlInput](type-aliases/getProcessDefinitionXmlInput.md) - [GetProcessDefinitionXmlResponse](type-aliases/GetProcessDefinitionXmlResponse.md) - [GetProcessDefinitionXmlResponses](type-aliases/GetProcessDefinitionXmlResponses.md) - [getProcessInstanceCallHierarchyConsistency](type-aliases/getProcessInstanceCallHierarchyConsistency.md) - [GetProcessInstanceCallHierarchyData](type-aliases/GetProcessInstanceCallHierarchyData.md) - [GetProcessInstanceCallHierarchyError](type-aliases/GetProcessInstanceCallHierarchyError.md) - [GetProcessInstanceCallHierarchyErrors](type-aliases/GetProcessInstanceCallHierarchyErrors.md) - [getProcessInstanceCallHierarchyInput](type-aliases/getProcessInstanceCallHierarchyInput.md) - [GetProcessInstanceCallHierarchyResponse](type-aliases/GetProcessInstanceCallHierarchyResponse.md) - [GetProcessInstanceCallHierarchyResponses](type-aliases/GetProcessInstanceCallHierarchyResponses.md) - [getProcessInstanceConsistency](type-aliases/getProcessInstanceConsistency.md) - [GetProcessInstanceData](type-aliases/GetProcessInstanceData.md) - [GetProcessInstanceError](type-aliases/GetProcessInstanceError.md) - [GetProcessInstanceErrors](type-aliases/GetProcessInstanceErrors.md) - [getProcessInstanceInput](type-aliases/getProcessInstanceInput.md) - [GetProcessInstanceResponse](type-aliases/GetProcessInstanceResponse.md) - [GetProcessInstanceResponses](type-aliases/GetProcessInstanceResponses.md) - [getProcessInstanceSequenceFlowsConsistency](type-aliases/getProcessInstanceSequenceFlowsConsistency.md) - [GetProcessInstanceSequenceFlowsData](type-aliases/GetProcessInstanceSequenceFlowsData.md) - [GetProcessInstanceSequenceFlowsError](type-aliases/GetProcessInstanceSequenceFlowsError.md) - [GetProcessInstanceSequenceFlowsErrors](type-aliases/GetProcessInstanceSequenceFlowsErrors.md) - [getProcessInstanceSequenceFlowsInput](type-aliases/getProcessInstanceSequenceFlowsInput.md) - [GetProcessInstanceSequenceFlowsResponse](type-aliases/GetProcessInstanceSequenceFlowsResponse.md) - [GetProcessInstanceSequenceFlowsResponses](type-aliases/GetProcessInstanceSequenceFlowsResponses.md) - [getProcessInstanceStatisticsByDefinitionConsistency](type-aliases/getProcessInstanceStatisticsByDefinitionConsistency.md) - [GetProcessInstanceStatisticsByDefinitionData](type-aliases/GetProcessInstanceStatisticsByDefinitionData.md) - [GetProcessInstanceStatisticsByDefinitionError](type-aliases/GetProcessInstanceStatisticsByDefinitionError.md) - [GetProcessInstanceStatisticsByDefinitionErrors](type-aliases/GetProcessInstanceStatisticsByDefinitionErrors.md) - [getProcessInstanceStatisticsByDefinitionInput](type-aliases/getProcessInstanceStatisticsByDefinitionInput.md) - [GetProcessInstanceStatisticsByDefinitionResponse](type-aliases/GetProcessInstanceStatisticsByDefinitionResponse.md) - [GetProcessInstanceStatisticsByDefinitionResponses](type-aliases/GetProcessInstanceStatisticsByDefinitionResponses.md) - [getProcessInstanceStatisticsByErrorConsistency](type-aliases/getProcessInstanceStatisticsByErrorConsistency.md) - [GetProcessInstanceStatisticsByErrorData](type-aliases/GetProcessInstanceStatisticsByErrorData.md) - [GetProcessInstanceStatisticsByErrorError](type-aliases/GetProcessInstanceStatisticsByErrorError.md) - [GetProcessInstanceStatisticsByErrorErrors](type-aliases/GetProcessInstanceStatisticsByErrorErrors.md) - [getProcessInstanceStatisticsByErrorInput](type-aliases/getProcessInstanceStatisticsByErrorInput.md) - [GetProcessInstanceStatisticsByErrorResponse](type-aliases/GetProcessInstanceStatisticsByErrorResponse.md) - [GetProcessInstanceStatisticsByErrorResponses](type-aliases/GetProcessInstanceStatisticsByErrorResponses.md) - [getProcessInstanceStatisticsConsistency](type-aliases/getProcessInstanceStatisticsConsistency.md) - [GetProcessInstanceStatisticsData](type-aliases/GetProcessInstanceStatisticsData.md) - [GetProcessInstanceStatisticsError](type-aliases/GetProcessInstanceStatisticsError.md) - [GetProcessInstanceStatisticsErrors](type-aliases/GetProcessInstanceStatisticsErrors.md) - [getProcessInstanceStatisticsInput](type-aliases/getProcessInstanceStatisticsInput.md) - [GetProcessInstanceStatisticsResponse](type-aliases/GetProcessInstanceStatisticsResponse.md) - [GetProcessInstanceStatisticsResponses](type-aliases/GetProcessInstanceStatisticsResponses.md) - [getProcessInstanceWaitStateStatisticsConsistency](type-aliases/getProcessInstanceWaitStateStatisticsConsistency.md) - [GetProcessInstanceWaitStateStatisticsData](type-aliases/GetProcessInstanceWaitStateStatisticsData.md) - [GetProcessInstanceWaitStateStatisticsError](type-aliases/GetProcessInstanceWaitStateStatisticsError.md) - [GetProcessInstanceWaitStateStatisticsErrors](type-aliases/GetProcessInstanceWaitStateStatisticsErrors.md) - [getProcessInstanceWaitStateStatisticsInput](type-aliases/getProcessInstanceWaitStateStatisticsInput.md) - [GetProcessInstanceWaitStateStatisticsResponse](type-aliases/GetProcessInstanceWaitStateStatisticsResponse.md) - [GetProcessInstanceWaitStateStatisticsResponses](type-aliases/GetProcessInstanceWaitStateStatisticsResponses.md) - [getResourceConsistency](type-aliases/getResourceConsistency.md) - [getResourceContentBinaryConsistency](type-aliases/getResourceContentBinaryConsistency.md) - [GetResourceContentBinaryData](type-aliases/GetResourceContentBinaryData.md) - [GetResourceContentBinaryError](type-aliases/GetResourceContentBinaryError.md) - [GetResourceContentBinaryErrors](type-aliases/GetResourceContentBinaryErrors.md) - [getResourceContentBinaryInput](type-aliases/getResourceContentBinaryInput.md) - [GetResourceContentBinaryResponse](type-aliases/GetResourceContentBinaryResponse.md) - [GetResourceContentBinaryResponses](type-aliases/GetResourceContentBinaryResponses.md) - [getResourceContentConsistency](type-aliases/getResourceContentConsistency.md) - [GetResourceContentData](type-aliases/GetResourceContentData.md) - [GetResourceContentError](type-aliases/GetResourceContentError.md) - [GetResourceContentErrors](type-aliases/GetResourceContentErrors.md) - [getResourceContentInput](type-aliases/getResourceContentInput.md) - [GetResourceContentResponse](type-aliases/GetResourceContentResponse.md) - [GetResourceContentResponses](type-aliases/GetResourceContentResponses.md) - [GetResourceData](type-aliases/GetResourceData.md) - [GetResourceError](type-aliases/GetResourceError.md) - [GetResourceErrors](type-aliases/GetResourceErrors.md) - [getResourceInput](type-aliases/getResourceInput.md) - [GetResourceResponse](type-aliases/GetResourceResponse.md) - [GetResourceResponses](type-aliases/GetResourceResponses.md) - [GetRestoreStatusData](type-aliases/GetRestoreStatusData.md) - [GetRestoreStatusError](type-aliases/GetRestoreStatusError.md) - [GetRestoreStatusErrors](type-aliases/GetRestoreStatusErrors.md) - [getRestoreStatusInput](type-aliases/getRestoreStatusInput.md) - [GetRestoreStatusResponse](type-aliases/GetRestoreStatusResponse.md) - [GetRestoreStatusResponses](type-aliases/GetRestoreStatusResponses.md) - [getRoleConsistency](type-aliases/getRoleConsistency.md) - [GetRoleData](type-aliases/GetRoleData.md) - [GetRoleError](type-aliases/GetRoleError.md) - [GetRoleErrors](type-aliases/GetRoleErrors.md) - [getRoleInput](type-aliases/getRoleInput.md) - [GetRoleResponse](type-aliases/GetRoleResponse.md) - [GetRoleResponses](type-aliases/GetRoleResponses.md) - [GetRuntimeBackupAsClusterAdminData](type-aliases/GetRuntimeBackupAsClusterAdminData.md) - [GetRuntimeBackupAsClusterAdminError](type-aliases/GetRuntimeBackupAsClusterAdminError.md) - [GetRuntimeBackupAsClusterAdminErrors](type-aliases/GetRuntimeBackupAsClusterAdminErrors.md) - [getRuntimeBackupAsClusterAdminInput](type-aliases/getRuntimeBackupAsClusterAdminInput.md) - [GetRuntimeBackupAsClusterAdminResponse](type-aliases/GetRuntimeBackupAsClusterAdminResponse.md) - [GetRuntimeBackupAsClusterAdminResponses](type-aliases/GetRuntimeBackupAsClusterAdminResponses.md) - [GetRuntimeBackupData](type-aliases/GetRuntimeBackupData.md) - [GetRuntimeBackupError](type-aliases/GetRuntimeBackupError.md) - [GetRuntimeBackupErrors](type-aliases/GetRuntimeBackupErrors.md) - [getRuntimeBackupInput](type-aliases/getRuntimeBackupInput.md) - [GetRuntimeBackupResponse](type-aliases/GetRuntimeBackupResponse.md) - [GetRuntimeBackupResponses](type-aliases/GetRuntimeBackupResponses.md) - [GetRuntimeBackupStateAsClusterAdminData](type-aliases/GetRuntimeBackupStateAsClusterAdminData.md) - [GetRuntimeBackupStateAsClusterAdminError](type-aliases/GetRuntimeBackupStateAsClusterAdminError.md) - [GetRuntimeBackupStateAsClusterAdminErrors](type-aliases/GetRuntimeBackupStateAsClusterAdminErrors.md) - [getRuntimeBackupStateAsClusterAdminInput](type-aliases/getRuntimeBackupStateAsClusterAdminInput.md) - [GetRuntimeBackupStateAsClusterAdminResponse](type-aliases/GetRuntimeBackupStateAsClusterAdminResponse.md) - [GetRuntimeBackupStateAsClusterAdminResponses](type-aliases/GetRuntimeBackupStateAsClusterAdminResponses.md) - [GetRuntimeBackupStateData](type-aliases/GetRuntimeBackupStateData.md) - [GetRuntimeBackupStateError](type-aliases/GetRuntimeBackupStateError.md) - [GetRuntimeBackupStateErrors](type-aliases/GetRuntimeBackupStateErrors.md) - [getRuntimeBackupStateInput](type-aliases/getRuntimeBackupStateInput.md) - [GetRuntimeBackupStateResponse](type-aliases/GetRuntimeBackupStateResponse.md) - [GetRuntimeBackupStateResponses](type-aliases/GetRuntimeBackupStateResponses.md) - [getStartProcessFormConsistency](type-aliases/getStartProcessFormConsistency.md) - [GetStartProcessFormData](type-aliases/GetStartProcessFormData.md) - [GetStartProcessFormError](type-aliases/GetStartProcessFormError.md) - [GetStartProcessFormErrors](type-aliases/GetStartProcessFormErrors.md) - [getStartProcessFormInput](type-aliases/getStartProcessFormInput.md) - [GetStartProcessFormResponse](type-aliases/GetStartProcessFormResponse.md) - [GetStartProcessFormResponses](type-aliases/GetStartProcessFormResponses.md) - [GetStatusData](type-aliases/GetStatusData.md) - [GetStatusErrors](type-aliases/GetStatusErrors.md) - [getStatusInput](type-aliases/getStatusInput.md) - [GetStatusResponse](type-aliases/GetStatusResponse.md) - [GetStatusResponses](type-aliases/GetStatusResponses.md) - [GetSystemConfigurationData](type-aliases/GetSystemConfigurationData.md) - [GetSystemConfigurationError](type-aliases/GetSystemConfigurationError.md) - [GetSystemConfigurationErrors](type-aliases/GetSystemConfigurationErrors.md) - [getSystemConfigurationInput](type-aliases/getSystemConfigurationInput.md) - [GetSystemConfigurationResponse](type-aliases/GetSystemConfigurationResponse.md) - [GetSystemConfigurationResponses](type-aliases/GetSystemConfigurationResponses.md) - [getTenantClusterVariableConsistency](type-aliases/getTenantClusterVariableConsistency.md) - [GetTenantClusterVariableData](type-aliases/GetTenantClusterVariableData.md) - [GetTenantClusterVariableError](type-aliases/GetTenantClusterVariableError.md) - [GetTenantClusterVariableErrors](type-aliases/GetTenantClusterVariableErrors.md) - [getTenantClusterVariableInput](type-aliases/getTenantClusterVariableInput.md) - [GetTenantClusterVariableResponse](type-aliases/GetTenantClusterVariableResponse.md) - [GetTenantClusterVariableResponses](type-aliases/GetTenantClusterVariableResponses.md) - [getTenantConsistency](type-aliases/getTenantConsistency.md) - [GetTenantData](type-aliases/GetTenantData.md) - [GetTenantError](type-aliases/GetTenantError.md) - [GetTenantErrors](type-aliases/GetTenantErrors.md) - [getTenantInput](type-aliases/getTenantInput.md) - [GetTenantResponse](type-aliases/GetTenantResponse.md) - [GetTenantResponses](type-aliases/GetTenantResponses.md) - [GetTopologyData](type-aliases/GetTopologyData.md) - [GetTopologyError](type-aliases/GetTopologyError.md) - [GetTopologyErrors](type-aliases/GetTopologyErrors.md) - [getTopologyInput](type-aliases/getTopologyInput.md) - [GetTopologyResponse](type-aliases/GetTopologyResponse.md) - [GetTopologyResponses](type-aliases/GetTopologyResponses.md) - [getUsageMetricsConsistency](type-aliases/getUsageMetricsConsistency.md) - [GetUsageMetricsData](type-aliases/GetUsageMetricsData.md) - [GetUsageMetricsError](type-aliases/GetUsageMetricsError.md) - [GetUsageMetricsErrors](type-aliases/GetUsageMetricsErrors.md) - [getUsageMetricsInput](type-aliases/getUsageMetricsInput.md) - [GetUsageMetricsResponse](type-aliases/GetUsageMetricsResponse.md) - [GetUsageMetricsResponses](type-aliases/GetUsageMetricsResponses.md) - [getUserConsistency](type-aliases/getUserConsistency.md) - [GetUserData](type-aliases/GetUserData.md) - [GetUserError](type-aliases/GetUserError.md) - [GetUserErrors](type-aliases/GetUserErrors.md) - [getUserInput](type-aliases/getUserInput.md) - [GetUserResponse](type-aliases/GetUserResponse.md) - [GetUserResponses](type-aliases/GetUserResponses.md) - [getUserTaskConsistency](type-aliases/getUserTaskConsistency.md) - [GetUserTaskData](type-aliases/GetUserTaskData.md) - [GetUserTaskError](type-aliases/GetUserTaskError.md) - [GetUserTaskErrors](type-aliases/GetUserTaskErrors.md) - [getUserTaskFormConsistency](type-aliases/getUserTaskFormConsistency.md) - [GetUserTaskFormData](type-aliases/GetUserTaskFormData.md) - [GetUserTaskFormError](type-aliases/GetUserTaskFormError.md) - [GetUserTaskFormErrors](type-aliases/GetUserTaskFormErrors.md) - [getUserTaskFormInput](type-aliases/getUserTaskFormInput.md) - [GetUserTaskFormResponse](type-aliases/GetUserTaskFormResponse.md) - [GetUserTaskFormResponses](type-aliases/GetUserTaskFormResponses.md) - [getUserTaskInput](type-aliases/getUserTaskInput.md) - [GetUserTaskResponse](type-aliases/GetUserTaskResponse.md) - [GetUserTaskResponses](type-aliases/GetUserTaskResponses.md) - [getVariableConsistency](type-aliases/getVariableConsistency.md) - [GetVariableData](type-aliases/GetVariableData.md) - [GetVariableError](type-aliases/GetVariableError.md) - [GetVariableErrors](type-aliases/GetVariableErrors.md) - [getVariableInput](type-aliases/getVariableInput.md) - [GetVariableResponse](type-aliases/GetVariableResponse.md) - [GetVariableResponses](type-aliases/GetVariableResponses.md) - [GlobalJobStatisticsQueryResult](type-aliases/GlobalJobStatisticsQueryResult.md) - [GlobalListenerBase](type-aliases/GlobalListenerBase.md) - [GlobalListenerId](type-aliases/GlobalListenerId.md) - [GlobalListenerSourceEnum](type-aliases/GlobalListenerSourceEnum.md) - [GlobalListenerSourceExactMatch](type-aliases/GlobalListenerSourceExactMatch.md) - [GlobalListenerSourceExactMatchWritable](type-aliases/GlobalListenerSourceExactMatchWritable.md) - [GlobalListenerSourceFilterProperty](type-aliases/GlobalListenerSourceFilterProperty.md) - [GlobalTaskListenerBase](type-aliases/GlobalTaskListenerBase.md) - [GlobalTaskListenerEventTypeEnum](type-aliases/GlobalTaskListenerEventTypeEnum.md) - [GlobalTaskListenerEventTypeExactMatch](type-aliases/GlobalTaskListenerEventTypeExactMatch.md) - [GlobalTaskListenerEventTypeExactMatchWritable](type-aliases/GlobalTaskListenerEventTypeExactMatchWritable.md) - [GlobalTaskListenerEventTypeFilterProperty](type-aliases/GlobalTaskListenerEventTypeFilterProperty.md) - [GlobalTaskListenerEventTypes](type-aliases/GlobalTaskListenerEventTypes.md) - [GlobalTaskListenerEventTypesWritable](type-aliases/GlobalTaskListenerEventTypesWritable.md) - [GlobalTaskListenerResult](type-aliases/GlobalTaskListenerResult.md) - [GlobalTaskListenerSearchQueryFilterRequest](type-aliases/GlobalTaskListenerSearchQueryFilterRequest.md) - [GlobalTaskListenerSearchQueryRequest](type-aliases/GlobalTaskListenerSearchQueryRequest.md) - [GlobalTaskListenerSearchQueryResult](type-aliases/GlobalTaskListenerSearchQueryResult.md) - [GlobalTaskListenerSearchQuerySortRequest](type-aliases/GlobalTaskListenerSearchQuerySortRequest.md) - [GroupClientResult](type-aliases/GroupClientResult.md) - [GroupClientSearchQueryRequest](type-aliases/GroupClientSearchQueryRequest.md) - [GroupClientSearchQuerySortRequest](type-aliases/GroupClientSearchQuerySortRequest.md) - [GroupClientSearchResult](type-aliases/GroupClientSearchResult.md) - [GroupCreateRequest](type-aliases/GroupCreateRequest.md) - [GroupCreateResult](type-aliases/GroupCreateResult.md) - [GroupFilter](type-aliases/GroupFilter.md) - [GroupFilterFields](type-aliases/GroupFilterFields.md) - [GroupId](type-aliases/GroupId.md) - [GroupMappingRuleSearchResult](type-aliases/GroupMappingRuleSearchResult.md) - [GroupResult](type-aliases/GroupResult.md) - [GroupRoleSearchResult](type-aliases/GroupRoleSearchResult.md) - [GroupSearchQueryRequest](type-aliases/GroupSearchQueryRequest.md) - [GroupSearchQueryResult](type-aliases/GroupSearchQueryResult.md) - [GroupSearchQuerySortRequest](type-aliases/GroupSearchQuerySortRequest.md) - [GroupUpdateRequest](type-aliases/GroupUpdateRequest.md) - [GroupUpdateResult](type-aliases/GroupUpdateResult.md) - [GroupUserResult](type-aliases/GroupUserResult.md) - [GroupUserSearchQueryRequest](type-aliases/GroupUserSearchQueryRequest.md) - [GroupUserSearchQuerySortRequest](type-aliases/GroupUserSearchQuerySortRequest.md) - [GroupUserSearchResult](type-aliases/GroupUserSearchResult.md) - [HandlerClock](type-aliases/HandlerClock.md) - [HistoryBackupInfo](type-aliases/HistoryBackupInfo.md) - [HistoryBackupInfoWritable](type-aliases/HistoryBackupInfoWritable.md) - [HistoryBackupSnapshotInfo](type-aliases/HistoryBackupSnapshotInfo.md) - [HistoryBackupStateCode](type-aliases/HistoryBackupStateCode.md) - [HistoryItemId](type-aliases/HistoryItemId.md) - [IncidentErrorTypeEnum](type-aliases/IncidentErrorTypeEnum.md) - [IncidentErrorTypeExactMatch](type-aliases/IncidentErrorTypeExactMatch.md) - [IncidentErrorTypeExactMatchWritable](type-aliases/IncidentErrorTypeExactMatchWritable.md) - [IncidentErrorTypeFilterProperty](type-aliases/IncidentErrorTypeFilterProperty.md) - [IncidentFilter](type-aliases/IncidentFilter.md) - [IncidentKey](type-aliases/IncidentKey.md) - [IncidentKeyWritable](type-aliases/IncidentKeyWritable.md) - [IncidentProcessInstanceStatisticsByDefinitionFilter](type-aliases/IncidentProcessInstanceStatisticsByDefinitionFilter.md) - [IncidentProcessInstanceStatisticsByDefinitionQuery](type-aliases/IncidentProcessInstanceStatisticsByDefinitionQuery.md) - [IncidentProcessInstanceStatisticsByDefinitionQueryResult](type-aliases/IncidentProcessInstanceStatisticsByDefinitionQueryResult.md) - [IncidentProcessInstanceStatisticsByDefinitionQuerySortRequest](type-aliases/IncidentProcessInstanceStatisticsByDefinitionQuerySortRequest.md) - [IncidentProcessInstanceStatisticsByDefinitionResult](type-aliases/IncidentProcessInstanceStatisticsByDefinitionResult.md) - [IncidentProcessInstanceStatisticsByErrorQuery](type-aliases/IncidentProcessInstanceStatisticsByErrorQuery.md) - [IncidentProcessInstanceStatisticsByErrorQueryResult](type-aliases/IncidentProcessInstanceStatisticsByErrorQueryResult.md) - [IncidentProcessInstanceStatisticsByErrorQuerySortRequest](type-aliases/IncidentProcessInstanceStatisticsByErrorQuerySortRequest.md) - [IncidentProcessInstanceStatisticsByErrorResult](type-aliases/IncidentProcessInstanceStatisticsByErrorResult.md) - [IncidentResolutionRequest](type-aliases/IncidentResolutionRequest.md) - [IncidentResult](type-aliases/IncidentResult.md) - [IncidentSearchQuery](type-aliases/IncidentSearchQuery.md) - [IncidentSearchQueryResult](type-aliases/IncidentSearchQueryResult.md) - [IncidentSearchQuerySortRequest](type-aliases/IncidentSearchQuerySortRequest.md) - [IncidentStateEnum](type-aliases/IncidentStateEnum.md) - [IncidentStateExactMatch](type-aliases/IncidentStateExactMatch.md) - [IncidentStateExactMatchWritable](type-aliases/IncidentStateExactMatchWritable.md) - [IncidentStateFilterProperty](type-aliases/IncidentStateFilterProperty.md) - [InferredAncestorKeyInstruction](type-aliases/InferredAncestorKeyInstruction.md) - [IntegerFilterProperty](type-aliases/IntegerFilterProperty.md) - [Job](type-aliases/Job.md) - [JobActionReceipt](type-aliases/JobActionReceipt.md) - [JobActionReceipt](type-aliases/JobActionReceipt-1.md) - [JobActivationRequest](type-aliases/JobActivationRequest.md) - [JobActivationResult](type-aliases/JobActivationResult.md) - [JobBatchUpdateRequest](type-aliases/JobBatchUpdateRequest.md) - [JobChangeset](type-aliases/JobChangeset.md) - [JobCompletionRequest](type-aliases/JobCompletionRequest.md) - [JobErrorRequest](type-aliases/JobErrorRequest.md) - [JobErrorStatisticsFilter](type-aliases/JobErrorStatisticsFilter.md) - [JobErrorStatisticsItem](type-aliases/JobErrorStatisticsItem.md) - [JobErrorStatisticsQuery](type-aliases/JobErrorStatisticsQuery.md) - [JobErrorStatisticsQueryResult](type-aliases/JobErrorStatisticsQueryResult.md) - [JobFailRequest](type-aliases/JobFailRequest.md) - [JobFilter](type-aliases/JobFilter.md) - [JobKey](type-aliases/JobKey.md) - [JobKeyExactMatch](type-aliases/JobKeyExactMatch.md) - [JobKeyExactMatchWritable](type-aliases/JobKeyExactMatchWritable.md) - [JobKeyFilterProperty](type-aliases/JobKeyFilterProperty.md) - [JobKeyWritable](type-aliases/JobKeyWritable.md) - [JobKindEnum](type-aliases/JobKindEnum.md) - [JobKindExactMatch](type-aliases/JobKindExactMatch.md) - [JobKindExactMatchWritable](type-aliases/JobKindExactMatchWritable.md) - [JobKindFilterProperty](type-aliases/JobKindFilterProperty.md) - [JobLeaseToken](type-aliases/JobLeaseToken.md) - [JobListenerEventTypeEnum](type-aliases/JobListenerEventTypeEnum.md) - [JobListenerEventTypeExactMatch](type-aliases/JobListenerEventTypeExactMatch.md) - [JobListenerEventTypeExactMatchWritable](type-aliases/JobListenerEventTypeExactMatchWritable.md) - [JobListenerEventTypeFilterProperty](type-aliases/JobListenerEventTypeFilterProperty.md) - [JobMetricsConfigurationResponse](type-aliases/JobMetricsConfigurationResponse.md) - [JobResult](type-aliases/JobResult.md) - [JobResultActivateElement](type-aliases/JobResultActivateElement.md) - [JobResultAdHocSubProcess](type-aliases/JobResultAdHocSubProcess.md) - [JobResultCorrections](type-aliases/JobResultCorrections.md) - [JobResultUserTask](type-aliases/JobResultUserTask.md) - [JobSearchQuery](type-aliases/JobSearchQuery.md) - [JobSearchQueryResult](type-aliases/JobSearchQueryResult.md) - [JobSearchQuerySortRequest](type-aliases/JobSearchQuerySortRequest.md) - [JobSearchResult](type-aliases/JobSearchResult.md) - [JobStateEnum](type-aliases/JobStateEnum.md) - [JobStateExactMatch](type-aliases/JobStateExactMatch.md) - [JobStateExactMatchWritable](type-aliases/JobStateExactMatchWritable.md) - [JobStateFilterProperty](type-aliases/JobStateFilterProperty.md) - [JobTimeSeriesStatisticsFilter](type-aliases/JobTimeSeriesStatisticsFilter.md) - [JobTimeSeriesStatisticsItem](type-aliases/JobTimeSeriesStatisticsItem.md) - [JobTimeSeriesStatisticsQuery](type-aliases/JobTimeSeriesStatisticsQuery.md) - [JobTimeSeriesStatisticsQueryResult](type-aliases/JobTimeSeriesStatisticsQueryResult.md) - [JobTypeStatisticsFilter](type-aliases/JobTypeStatisticsFilter.md) - [JobTypeStatisticsItem](type-aliases/JobTypeStatisticsItem.md) - [JobTypeStatisticsQuery](type-aliases/JobTypeStatisticsQuery.md) - [JobTypeStatisticsQueryResult](type-aliases/JobTypeStatisticsQueryResult.md) - [JobUpdateRequest](type-aliases/JobUpdateRequest.md) - [JobWaitStateDetails](type-aliases/JobWaitStateDetails.md) - [JobWorkerStatisticsFilter](type-aliases/JobWorkerStatisticsFilter.md) - [JobWorkerStatisticsItem](type-aliases/JobWorkerStatisticsItem.md) - [JobWorkerStatisticsQuery](type-aliases/JobWorkerStatisticsQuery.md) - [JobWorkerStatisticsQueryResult](type-aliases/JobWorkerStatisticsQueryResult.md) - [LicenseResponse](type-aliases/LicenseResponse.md) - [LikeFilter](type-aliases/LikeFilter.md) - [LimitPagination](type-aliases/LimitPagination.md) - [ListHistoryBackupsAsClusterAdminData](type-aliases/ListHistoryBackupsAsClusterAdminData.md) - [ListHistoryBackupsAsClusterAdminError](type-aliases/ListHistoryBackupsAsClusterAdminError.md) - [ListHistoryBackupsAsClusterAdminErrors](type-aliases/ListHistoryBackupsAsClusterAdminErrors.md) - [listHistoryBackupsAsClusterAdminInput](type-aliases/listHistoryBackupsAsClusterAdminInput.md) - [ListHistoryBackupsAsClusterAdminResponse](type-aliases/ListHistoryBackupsAsClusterAdminResponse.md) - [ListHistoryBackupsAsClusterAdminResponses](type-aliases/ListHistoryBackupsAsClusterAdminResponses.md) - [ListHistoryBackupsData](type-aliases/ListHistoryBackupsData.md) - [ListHistoryBackupsError](type-aliases/ListHistoryBackupsError.md) - [ListHistoryBackupsErrors](type-aliases/ListHistoryBackupsErrors.md) - [listHistoryBackupsInput](type-aliases/listHistoryBackupsInput.md) - [ListHistoryBackupsResponse](type-aliases/ListHistoryBackupsResponse.md) - [ListHistoryBackupsResponses](type-aliases/ListHistoryBackupsResponses.md) - [ListRuntimeBackupsAsClusterAdminData](type-aliases/ListRuntimeBackupsAsClusterAdminData.md) - [ListRuntimeBackupsAsClusterAdminError](type-aliases/ListRuntimeBackupsAsClusterAdminError.md) - [ListRuntimeBackupsAsClusterAdminErrors](type-aliases/ListRuntimeBackupsAsClusterAdminErrors.md) - [listRuntimeBackupsAsClusterAdminInput](type-aliases/listRuntimeBackupsAsClusterAdminInput.md) - [ListRuntimeBackupsAsClusterAdminResponse](type-aliases/ListRuntimeBackupsAsClusterAdminResponse.md) - [ListRuntimeBackupsAsClusterAdminResponses](type-aliases/ListRuntimeBackupsAsClusterAdminResponses.md) - [ListRuntimeBackupsData](type-aliases/ListRuntimeBackupsData.md) - [ListRuntimeBackupsError](type-aliases/ListRuntimeBackupsError.md) - [ListRuntimeBackupsErrors](type-aliases/ListRuntimeBackupsErrors.md) - [listRuntimeBackupsInput](type-aliases/listRuntimeBackupsInput.md) - [ListRuntimeBackupsResponse](type-aliases/ListRuntimeBackupsResponse.md) - [ListRuntimeBackupsResponses](type-aliases/ListRuntimeBackupsResponses.md) - [ListSecretsData](type-aliases/ListSecretsData.md) - [ListSecretsError](type-aliases/ListSecretsError.md) - [ListSecretsErrors](type-aliases/ListSecretsErrors.md) - [listSecretsInput](type-aliases/listSecretsInput.md) - [ListSecretsResponse](type-aliases/ListSecretsResponse.md) - [ListSecretsResponses](type-aliases/ListSecretsResponses.md) - [LongKey](type-aliases/LongKey.md) - [LoopIterationId](type-aliases/LoopIterationId.md) - [Loose](type-aliases/Loose.md) - [MappingRuleCreateRequest](type-aliases/MappingRuleCreateRequest.md) - [MappingRuleCreateResult](type-aliases/MappingRuleCreateResult.md) - [MappingRuleCreateUpdateRequest](type-aliases/MappingRuleCreateUpdateRequest.md) - [MappingRuleCreateUpdateResult](type-aliases/MappingRuleCreateUpdateResult.md) - [MappingRuleFilter](type-aliases/MappingRuleFilter.md) - [MappingRuleFilterFields](type-aliases/MappingRuleFilterFields.md) - [MappingRuleId](type-aliases/MappingRuleId.md) - [MappingRuleResult](type-aliases/MappingRuleResult.md) - [MappingRuleSearchQueryRequest](type-aliases/MappingRuleSearchQueryRequest.md) - [MappingRuleSearchQueryResult](type-aliases/MappingRuleSearchQueryResult.md) - [MappingRuleSearchQuerySortRequest](type-aliases/MappingRuleSearchQuerySortRequest.md) - [MappingRuleUpdateRequest](type-aliases/MappingRuleUpdateRequest.md) - [MappingRuleUpdateResult](type-aliases/MappingRuleUpdateResult.md) - [MatchedDecisionRuleItem](type-aliases/MatchedDecisionRuleItem.md) - [MessageCorrelationRequest](type-aliases/MessageCorrelationRequest.md) - [MessageCorrelationResult](type-aliases/MessageCorrelationResult.md) - [MessageKey](type-aliases/MessageKey.md) - [MessageKeyWritable](type-aliases/MessageKeyWritable.md) - [MessagePublicationRequest](type-aliases/MessagePublicationRequest.md) - [MessagePublicationResult](type-aliases/MessagePublicationResult.md) - [MessageSubscriptionFilter](type-aliases/MessageSubscriptionFilter.md) - [MessageSubscriptionKey](type-aliases/MessageSubscriptionKey.md) - [MessageSubscriptionKeyExactMatch](type-aliases/MessageSubscriptionKeyExactMatch.md) - [MessageSubscriptionKeyExactMatchWritable](type-aliases/MessageSubscriptionKeyExactMatchWritable.md) - [MessageSubscriptionKeyFilterProperty](type-aliases/MessageSubscriptionKeyFilterProperty.md) - [MessageSubscriptionKeyWritable](type-aliases/MessageSubscriptionKeyWritable.md) - [MessageSubscriptionResult](type-aliases/MessageSubscriptionResult.md) - [MessageSubscriptionSearchQuery](type-aliases/MessageSubscriptionSearchQuery.md) - [MessageSubscriptionSearchQueryResult](type-aliases/MessageSubscriptionSearchQueryResult.md) - [MessageSubscriptionSearchQuerySortRequest](type-aliases/MessageSubscriptionSearchQuerySortRequest.md) - [MessageSubscriptionStateEnum](type-aliases/MessageSubscriptionStateEnum.md) - [MessageSubscriptionStateExactMatch](type-aliases/MessageSubscriptionStateExactMatch.md) - [MessageSubscriptionStateExactMatchWritable](type-aliases/MessageSubscriptionStateExactMatchWritable.md) - [MessageSubscriptionStateFilterProperty](type-aliases/MessageSubscriptionStateFilterProperty.md) - [MessageSubscriptionTypeEnum](type-aliases/MessageSubscriptionTypeEnum.md) - [MessageSubscriptionTypeExactMatch](type-aliases/MessageSubscriptionTypeExactMatch.md) - [MessageSubscriptionTypeExactMatchWritable](type-aliases/MessageSubscriptionTypeExactMatchWritable.md) - [MessageSubscriptionTypeFilterProperty](type-aliases/MessageSubscriptionTypeFilterProperty.md) - [MessageWaitStateDetails](type-aliases/MessageWaitStateDetails.md) - [MigrateProcessInstanceData](type-aliases/MigrateProcessInstanceData.md) - [MigrateProcessInstanceError](type-aliases/MigrateProcessInstanceError.md) - [MigrateProcessInstanceErrors](type-aliases/MigrateProcessInstanceErrors.md) - [migrateProcessInstanceInput](type-aliases/migrateProcessInstanceInput.md) - [MigrateProcessInstanceMappingInstruction](type-aliases/MigrateProcessInstanceMappingInstruction.md) - [MigrateProcessInstanceResponse](type-aliases/MigrateProcessInstanceResponse.md) - [MigrateProcessInstanceResponses](type-aliases/MigrateProcessInstanceResponses.md) - [MigrateProcessInstancesBatchOperationData](type-aliases/MigrateProcessInstancesBatchOperationData.md) - [MigrateProcessInstancesBatchOperationError](type-aliases/MigrateProcessInstancesBatchOperationError.md) - [MigrateProcessInstancesBatchOperationErrors](type-aliases/MigrateProcessInstancesBatchOperationErrors.md) - [migrateProcessInstancesBatchOperationInput](type-aliases/migrateProcessInstancesBatchOperationInput.md) - [MigrateProcessInstancesBatchOperationResponse](type-aliases/MigrateProcessInstancesBatchOperationResponse.md) - [MigrateProcessInstancesBatchOperationResponses](type-aliases/MigrateProcessInstancesBatchOperationResponses.md) - [Mode](type-aliases/Mode.md) - [ModifyProcessInstanceData](type-aliases/ModifyProcessInstanceData.md) - [ModifyProcessInstanceError](type-aliases/ModifyProcessInstanceError.md) - [ModifyProcessInstanceErrors](type-aliases/ModifyProcessInstanceErrors.md) - [modifyProcessInstanceInput](type-aliases/modifyProcessInstanceInput.md) - [ModifyProcessInstanceResponse](type-aliases/ModifyProcessInstanceResponse.md) - [ModifyProcessInstanceResponses](type-aliases/ModifyProcessInstanceResponses.md) - [ModifyProcessInstancesBatchOperationData](type-aliases/ModifyProcessInstancesBatchOperationData.md) - [ModifyProcessInstancesBatchOperationError](type-aliases/ModifyProcessInstancesBatchOperationError.md) - [ModifyProcessInstancesBatchOperationErrors](type-aliases/ModifyProcessInstancesBatchOperationErrors.md) - [modifyProcessInstancesBatchOperationInput](type-aliases/modifyProcessInstancesBatchOperationInput.md) - [ModifyProcessInstancesBatchOperationResponse](type-aliases/ModifyProcessInstancesBatchOperationResponse.md) - [ModifyProcessInstancesBatchOperationResponses](type-aliases/ModifyProcessInstancesBatchOperationResponses.md) - [ModifyProcessInstanceVariableInstruction](type-aliases/ModifyProcessInstanceVariableInstruction.md) - [OffsetPagination](type-aliases/OffsetPagination.md) - [OperationReference](type-aliases/OperationReference.md) - [OperationTypeExactMatch](type-aliases/OperationTypeExactMatch.md) - [OperationTypeExactMatchWritable](type-aliases/OperationTypeExactMatchWritable.md) - [OperationTypeFilterProperty](type-aliases/OperationTypeFilterProperty.md) - [OwnAuthorizationSearchResult](type-aliases/OwnAuthorizationSearchResult.md) - [OwnerTypeEnum](type-aliases/OwnerTypeEnum.md) - [PaginationMode](type-aliases/PaginationMode.md) - [Partition](type-aliases/Partition.md) - [PartitionBackupInfo](type-aliases/PartitionBackupInfo.md) - [PartitionBackupInfoWritable](type-aliases/PartitionBackupInfoWritable.md) - [PartitionBackupRange](type-aliases/PartitionBackupRange.md) - [PartitionBackupState](type-aliases/PartitionBackupState.md) - [PartitionCheckpointState](type-aliases/PartitionCheckpointState.md) - [PartitionId](type-aliases/PartitionId.md) - [PauseClusterExportingData](type-aliases/PauseClusterExportingData.md) - [PauseClusterExportingError](type-aliases/PauseClusterExportingError.md) - [PauseClusterExportingErrors](type-aliases/PauseClusterExportingErrors.md) - [pauseClusterExportingInput](type-aliases/pauseClusterExportingInput.md) - [PauseClusterExportingResponse](type-aliases/PauseClusterExportingResponse.md) - [PauseClusterExportingResponses](type-aliases/PauseClusterExportingResponses.md) - [PauseExportingData](type-aliases/PauseExportingData.md) - [PauseExportingError](type-aliases/PauseExportingError.md) - [PauseExportingErrors](type-aliases/PauseExportingErrors.md) - [pauseExportingInput](type-aliases/pauseExportingInput.md) - [PauseExportingResponse](type-aliases/PauseExportingResponse.md) - [PauseExportingResponses](type-aliases/PauseExportingResponses.md) - [PermissionTypeEnum](type-aliases/PermissionTypeEnum.md) - [PhysicalTenantBrokerTopology](type-aliases/PhysicalTenantBrokerTopology.md) - [PhysicalTenantTopology](type-aliases/PhysicalTenantTopology.md) - [PinClockData](type-aliases/PinClockData.md) - [PinClockError](type-aliases/PinClockError.md) - [PinClockErrors](type-aliases/PinClockErrors.md) - [pinClockInput](type-aliases/pinClockInput.md) - [PinClockResponse](type-aliases/PinClockResponse.md) - [PinClockResponses](type-aliases/PinClockResponses.md) - [ProblemDetail](type-aliases/ProblemDetail.md) - [ProcessDefinitionElementStatisticsQuery](type-aliases/ProcessDefinitionElementStatisticsQuery.md) - [ProcessDefinitionElementStatisticsQueryResult](type-aliases/ProcessDefinitionElementStatisticsQueryResult.md) - [ProcessDefinitionFilter](type-aliases/ProcessDefinitionFilter.md) - [ProcessDefinitionId](type-aliases/ProcessDefinitionId.md) - [ProcessDefinitionIdExactMatch](type-aliases/ProcessDefinitionIdExactMatch.md) - [ProcessDefinitionIdExactMatchWritable](type-aliases/ProcessDefinitionIdExactMatchWritable.md) - [ProcessDefinitionIdFilterProperty](type-aliases/ProcessDefinitionIdFilterProperty.md) - [ProcessDefinitionInstanceStatisticsQuery](type-aliases/ProcessDefinitionInstanceStatisticsQuery.md) - [ProcessDefinitionInstanceStatisticsQueryResult](type-aliases/ProcessDefinitionInstanceStatisticsQueryResult.md) - [ProcessDefinitionInstanceStatisticsQuerySortRequest](type-aliases/ProcessDefinitionInstanceStatisticsQuerySortRequest.md) - [ProcessDefinitionInstanceStatisticsResult](type-aliases/ProcessDefinitionInstanceStatisticsResult.md) - [ProcessDefinitionInstanceVersionStatisticsFilter](type-aliases/ProcessDefinitionInstanceVersionStatisticsFilter.md) - [ProcessDefinitionInstanceVersionStatisticsQuery](type-aliases/ProcessDefinitionInstanceVersionStatisticsQuery.md) - [ProcessDefinitionInstanceVersionStatisticsQueryResult](type-aliases/ProcessDefinitionInstanceVersionStatisticsQueryResult.md) - [ProcessDefinitionInstanceVersionStatisticsQuerySortRequest](type-aliases/ProcessDefinitionInstanceVersionStatisticsQuerySortRequest.md) - [ProcessDefinitionInstanceVersionStatisticsResult](type-aliases/ProcessDefinitionInstanceVersionStatisticsResult.md) - [ProcessDefinitionKey](type-aliases/ProcessDefinitionKey.md) - [ProcessDefinitionKeyExactMatch](type-aliases/ProcessDefinitionKeyExactMatch.md) - [ProcessDefinitionKeyExactMatchWritable](type-aliases/ProcessDefinitionKeyExactMatchWritable.md) - [ProcessDefinitionKeyFilterProperty](type-aliases/ProcessDefinitionKeyFilterProperty.md) - [ProcessDefinitionKeyWritable](type-aliases/ProcessDefinitionKeyWritable.md) - [ProcessDefinitionMessageSubscriptionStatisticsQuery](type-aliases/ProcessDefinitionMessageSubscriptionStatisticsQuery.md) - [ProcessDefinitionMessageSubscriptionStatisticsQueryResult](type-aliases/ProcessDefinitionMessageSubscriptionStatisticsQueryResult.md) - [ProcessDefinitionMessageSubscriptionStatisticsResult](type-aliases/ProcessDefinitionMessageSubscriptionStatisticsResult.md) - [ProcessDefinitionResult](type-aliases/ProcessDefinitionResult.md) - [ProcessDefinitionSearchQuery](type-aliases/ProcessDefinitionSearchQuery.md) - [ProcessDefinitionSearchQueryResult](type-aliases/ProcessDefinitionSearchQueryResult.md) - [ProcessDefinitionSearchQuerySortRequest](type-aliases/ProcessDefinitionSearchQuerySortRequest.md) - [ProcessDefinitionStatisticsFilter](type-aliases/ProcessDefinitionStatisticsFilter.md) - [ProcessDefinitionVariableNameFilter](type-aliases/ProcessDefinitionVariableNameFilter.md) - [ProcessDefinitionVariableNameSearchQuery](type-aliases/ProcessDefinitionVariableNameSearchQuery.md) - [ProcessDefinitionVariableNameSearchQueryResult](type-aliases/ProcessDefinitionVariableNameSearchQueryResult.md) - [ProcessDefinitionVariableNameSearchResult](type-aliases/ProcessDefinitionVariableNameSearchResult.md) - [ProcessElementStatisticsResult](type-aliases/ProcessElementStatisticsResult.md) - [ProcessInstanceBusinessIdAssignmentInstruction](type-aliases/ProcessInstanceBusinessIdAssignmentInstruction.md) - [ProcessInstanceCallHierarchyEntry](type-aliases/ProcessInstanceCallHierarchyEntry.md) - [ProcessInstanceCancellationBatchOperationRequest](type-aliases/ProcessInstanceCancellationBatchOperationRequest.md) - [ProcessInstanceCreationInstruction](type-aliases/ProcessInstanceCreationInstruction.md) - [ProcessInstanceCreationInstructionById](type-aliases/ProcessInstanceCreationInstructionById.md) - [ProcessInstanceCreationInstructionByKey](type-aliases/ProcessInstanceCreationInstructionByKey.md) - [ProcessInstanceCreationRuntimeInstruction](type-aliases/ProcessInstanceCreationRuntimeInstruction.md) - [ProcessInstanceCreationStartInstruction](type-aliases/ProcessInstanceCreationStartInstruction.md) - [ProcessInstanceCreationTerminateInstruction](type-aliases/ProcessInstanceCreationTerminateInstruction.md) - [ProcessInstanceDeletionBatchOperationRequest](type-aliases/ProcessInstanceDeletionBatchOperationRequest.md) - [ProcessInstanceElementStatisticsQueryResult](type-aliases/ProcessInstanceElementStatisticsQueryResult.md) - [ProcessInstanceFilter](type-aliases/ProcessInstanceFilter.md) - [ProcessInstanceFilterFields](type-aliases/ProcessInstanceFilterFields.md) - [ProcessInstanceIncidentResolutionBatchOperationRequest](type-aliases/ProcessInstanceIncidentResolutionBatchOperationRequest.md) - [ProcessInstanceKey](type-aliases/ProcessInstanceKey.md) - [ProcessInstanceKeyExactMatch](type-aliases/ProcessInstanceKeyExactMatch.md) - [ProcessInstanceKeyExactMatchWritable](type-aliases/ProcessInstanceKeyExactMatchWritable.md) - [ProcessInstanceKeyFilterProperty](type-aliases/ProcessInstanceKeyFilterProperty.md) - [ProcessInstanceKeyWritable](type-aliases/ProcessInstanceKeyWritable.md) - [ProcessInstanceMigrationBatchOperationPlan](type-aliases/ProcessInstanceMigrationBatchOperationPlan.md) - [ProcessInstanceMigrationBatchOperationRequest](type-aliases/ProcessInstanceMigrationBatchOperationRequest.md) - [ProcessInstanceMigrationInstruction](type-aliases/ProcessInstanceMigrationInstruction.md) - [ProcessInstanceModificationActivateInstruction](type-aliases/ProcessInstanceModificationActivateInstruction.md) - [ProcessInstanceModificationBatchOperationRequest](type-aliases/ProcessInstanceModificationBatchOperationRequest.md) - [ProcessInstanceModificationInstruction](type-aliases/ProcessInstanceModificationInstruction.md) - [ProcessInstanceModificationMoveBatchOperationInstruction](type-aliases/ProcessInstanceModificationMoveBatchOperationInstruction.md) - [ProcessInstanceModificationMoveInstruction](type-aliases/ProcessInstanceModificationMoveInstruction.md) - [ProcessInstanceModificationTerminateByIdInstruction](type-aliases/ProcessInstanceModificationTerminateByIdInstruction.md) - [ProcessInstanceModificationTerminateByKeyInstruction](type-aliases/ProcessInstanceModificationTerminateByKeyInstruction.md) - [ProcessInstanceModificationTerminateInstruction](type-aliases/ProcessInstanceModificationTerminateInstruction.md) - [ProcessInstanceReference](type-aliases/ProcessInstanceReference.md) - [ProcessInstanceResult](type-aliases/ProcessInstanceResult.md) - [ProcessInstanceResumptionBatchOperationRequest](type-aliases/ProcessInstanceResumptionBatchOperationRequest.md) - [ProcessInstanceSearchQuery](type-aliases/ProcessInstanceSearchQuery.md) - [ProcessInstanceSearchQueryResult](type-aliases/ProcessInstanceSearchQueryResult.md) - [ProcessInstanceSearchQuerySortRequest](type-aliases/ProcessInstanceSearchQuerySortRequest.md) - [ProcessInstanceSequenceFlowResult](type-aliases/ProcessInstanceSequenceFlowResult.md) - [ProcessInstanceSequenceFlowsQueryResult](type-aliases/ProcessInstanceSequenceFlowsQueryResult.md) - [ProcessInstanceStateEnum](type-aliases/ProcessInstanceStateEnum.md) - [ProcessInstanceStateExactMatch](type-aliases/ProcessInstanceStateExactMatch.md) - [ProcessInstanceStateExactMatchWritable](type-aliases/ProcessInstanceStateExactMatchWritable.md) - [ProcessInstanceStateFilterProperty](type-aliases/ProcessInstanceStateFilterProperty.md) - [ProcessInstanceSuspensionBatchOperationRequest](type-aliases/ProcessInstanceSuspensionBatchOperationRequest.md) - [ProcessInstanceWaitStateStatisticsQueryResult](type-aliases/ProcessInstanceWaitStateStatisticsQueryResult.md) - [ProcessInstanceWaitStateStatisticsResult](type-aliases/ProcessInstanceWaitStateStatisticsResult.md) - [PublishMessageData](type-aliases/PublishMessageData.md) - [PublishMessageError](type-aliases/PublishMessageError.md) - [PublishMessageErrors](type-aliases/PublishMessageErrors.md) - [publishMessageInput](type-aliases/publishMessageInput.md) - [PublishMessageResponse](type-aliases/PublishMessageResponse.md) - [PublishMessageResponses](type-aliases/PublishMessageResponses.md) - [RebalanceCancellationResponse](type-aliases/RebalanceCancellationResponse.md) - [ResetClockData](type-aliases/ResetClockData.md) - [ResetClockError](type-aliases/ResetClockError.md) - [ResetClockErrors](type-aliases/ResetClockErrors.md) - [resetClockInput](type-aliases/resetClockInput.md) - [ResetClockResponse](type-aliases/ResetClockResponse.md) - [ResetClockResponses](type-aliases/ResetClockResponses.md) - [ResolvedSecret](type-aliases/ResolvedSecret.md) - [ResolveIncidentData](type-aliases/ResolveIncidentData.md) - [ResolveIncidentError](type-aliases/ResolveIncidentError.md) - [ResolveIncidentErrors](type-aliases/ResolveIncidentErrors.md) - [resolveIncidentInput](type-aliases/resolveIncidentInput.md) - [ResolveIncidentResponse](type-aliases/ResolveIncidentResponse.md) - [ResolveIncidentResponses](type-aliases/ResolveIncidentResponses.md) - [ResolveIncidentsBatchOperationData](type-aliases/ResolveIncidentsBatchOperationData.md) - [ResolveIncidentsBatchOperationError](type-aliases/ResolveIncidentsBatchOperationError.md) - [ResolveIncidentsBatchOperationErrors](type-aliases/ResolveIncidentsBatchOperationErrors.md) - [resolveIncidentsBatchOperationInput](type-aliases/resolveIncidentsBatchOperationInput.md) - [ResolveIncidentsBatchOperationResponse](type-aliases/ResolveIncidentsBatchOperationResponse.md) - [ResolveIncidentsBatchOperationResponses](type-aliases/ResolveIncidentsBatchOperationResponses.md) - [ResolveProcessInstanceIncidentsData](type-aliases/ResolveProcessInstanceIncidentsData.md) - [ResolveProcessInstanceIncidentsError](type-aliases/ResolveProcessInstanceIncidentsError.md) - [ResolveProcessInstanceIncidentsErrors](type-aliases/ResolveProcessInstanceIncidentsErrors.md) - [resolveProcessInstanceIncidentsInput](type-aliases/resolveProcessInstanceIncidentsInput.md) - [ResolveProcessInstanceIncidentsResponse](type-aliases/ResolveProcessInstanceIncidentsResponse.md) - [ResolveProcessInstanceIncidentsResponses](type-aliases/ResolveProcessInstanceIncidentsResponses.md) - [ResolveSecretsData](type-aliases/ResolveSecretsData.md) - [ResolveSecretsError](type-aliases/ResolveSecretsError.md) - [ResolveSecretsErrors](type-aliases/ResolveSecretsErrors.md) - [resolveSecretsInput](type-aliases/resolveSecretsInput.md) - [ResolveSecretsResponse](type-aliases/ResolveSecretsResponse.md) - [ResolveSecretsResponses](type-aliases/ResolveSecretsResponses.md) - [ResourceFilter](type-aliases/ResourceFilter.md) - [ResourceKey](type-aliases/ResourceKey.md) - [ResourceKeyExactMatch](type-aliases/ResourceKeyExactMatch.md) - [ResourceKeyExactMatchWritable](type-aliases/ResourceKeyExactMatchWritable.md) - [ResourceKeyFilterProperty](type-aliases/ResourceKeyFilterProperty.md) - [ResourceKeyWritable](type-aliases/ResourceKeyWritable.md) - [ResourceResult](type-aliases/ResourceResult.md) - [ResourceSearchQuery](type-aliases/ResourceSearchQuery.md) - [ResourceSearchQueryResult](type-aliases/ResourceSearchQueryResult.md) - [ResourceSearchQuerySortRequest](type-aliases/ResourceSearchQuerySortRequest.md) - [ResourceTypeEnum](type-aliases/ResourceTypeEnum.md) - [RestoreAsClusterAdminData](type-aliases/RestoreAsClusterAdminData.md) - [RestoreAsClusterAdminError](type-aliases/RestoreAsClusterAdminError.md) - [RestoreAsClusterAdminErrors](type-aliases/RestoreAsClusterAdminErrors.md) - [restoreAsClusterAdminInput](type-aliases/restoreAsClusterAdminInput.md) - [RestoreAsClusterAdminResponse](type-aliases/RestoreAsClusterAdminResponse.md) - [RestoreAsClusterAdminResponses](type-aliases/RestoreAsClusterAdminResponses.md) - [RestoreBrokerStatus](type-aliases/RestoreBrokerStatus.md) - [RestoreData](type-aliases/RestoreData.md) - [RestoreError](type-aliases/RestoreError.md) - [RestoreErrors](type-aliases/RestoreErrors.md) - [restoreInput](type-aliases/restoreInput.md) - [RestorePartitionStatus](type-aliases/RestorePartitionStatus.md) - [RestoreRequest](type-aliases/RestoreRequest.md) - [RestoreResponse](type-aliases/RestoreResponse.md) - [RestoreResponses](type-aliases/RestoreResponses.md) - [RestoreStatusResponse](type-aliases/RestoreStatusResponse.md) - [Result](type-aliases/Result.md) - [ResumeBatchOperationData](type-aliases/ResumeBatchOperationData.md) - [ResumeBatchOperationError](type-aliases/ResumeBatchOperationError.md) - [ResumeBatchOperationErrors](type-aliases/ResumeBatchOperationErrors.md) - [resumeBatchOperationInput](type-aliases/resumeBatchOperationInput.md) - [ResumeBatchOperationResponse](type-aliases/ResumeBatchOperationResponse.md) - [ResumeBatchOperationResponses](type-aliases/ResumeBatchOperationResponses.md) - [ResumeClusterExportingData](type-aliases/ResumeClusterExportingData.md) - [ResumeClusterExportingError](type-aliases/ResumeClusterExportingError.md) - [ResumeClusterExportingErrors](type-aliases/ResumeClusterExportingErrors.md) - [resumeClusterExportingInput](type-aliases/resumeClusterExportingInput.md) - [ResumeClusterExportingResponse](type-aliases/ResumeClusterExportingResponse.md) - [ResumeClusterExportingResponses](type-aliases/ResumeClusterExportingResponses.md) - [ResumeExportingData](type-aliases/ResumeExportingData.md) - [ResumeExportingError](type-aliases/ResumeExportingError.md) - [ResumeExportingErrors](type-aliases/ResumeExportingErrors.md) - [resumeExportingInput](type-aliases/resumeExportingInput.md) - [ResumeExportingResponse](type-aliases/ResumeExportingResponse.md) - [ResumeExportingResponses](type-aliases/ResumeExportingResponses.md) - [ResumeProcessInstanceData](type-aliases/ResumeProcessInstanceData.md) - [ResumeProcessInstanceError](type-aliases/ResumeProcessInstanceError.md) - [ResumeProcessInstanceErrors](type-aliases/ResumeProcessInstanceErrors.md) - [resumeProcessInstanceInput](type-aliases/resumeProcessInstanceInput.md) - [ResumeProcessInstanceRequest](type-aliases/ResumeProcessInstanceRequest.md) - [ResumeProcessInstanceResponse](type-aliases/ResumeProcessInstanceResponse.md) - [ResumeProcessInstanceResponses](type-aliases/ResumeProcessInstanceResponses.md) - [ResumeProcessInstancesBatchOperationData](type-aliases/ResumeProcessInstancesBatchOperationData.md) - [ResumeProcessInstancesBatchOperationError](type-aliases/ResumeProcessInstancesBatchOperationError.md) - [ResumeProcessInstancesBatchOperationErrors](type-aliases/ResumeProcessInstancesBatchOperationErrors.md) - [resumeProcessInstancesBatchOperationInput](type-aliases/resumeProcessInstancesBatchOperationInput.md) - [ResumeProcessInstancesBatchOperationResponse](type-aliases/ResumeProcessInstancesBatchOperationResponse.md) - [ResumeProcessInstancesBatchOperationResponses](type-aliases/ResumeProcessInstancesBatchOperationResponses.md) - [RoleClientResult](type-aliases/RoleClientResult.md) - [RoleClientSearchQueryRequest](type-aliases/RoleClientSearchQueryRequest.md) - [RoleClientSearchQuerySortRequest](type-aliases/RoleClientSearchQuerySortRequest.md) - [RoleClientSearchResult](type-aliases/RoleClientSearchResult.md) - [RoleCreateRequest](type-aliases/RoleCreateRequest.md) - [RoleCreateResult](type-aliases/RoleCreateResult.md) - [RoleFilter](type-aliases/RoleFilter.md) - [RoleFilterFields](type-aliases/RoleFilterFields.md) - [RoleGroupResult](type-aliases/RoleGroupResult.md) - [RoleGroupSearchQueryRequest](type-aliases/RoleGroupSearchQueryRequest.md) - [RoleGroupSearchQuerySortRequest](type-aliases/RoleGroupSearchQuerySortRequest.md) - [RoleGroupSearchResult](type-aliases/RoleGroupSearchResult.md) - [RoleId](type-aliases/RoleId.md) - [RoleMappingRuleSearchResult](type-aliases/RoleMappingRuleSearchResult.md) - [RoleResult](type-aliases/RoleResult.md) - [RoleSearchQueryRequest](type-aliases/RoleSearchQueryRequest.md) - [RoleSearchQueryResult](type-aliases/RoleSearchQueryResult.md) - [RoleSearchQuerySortRequest](type-aliases/RoleSearchQuerySortRequest.md) - [RoleUpdateRequest](type-aliases/RoleUpdateRequest.md) - [RoleUpdateResult](type-aliases/RoleUpdateResult.md) - [RoleUserFilterRequest](type-aliases/RoleUserFilterRequest.md) - [RoleUserResult](type-aliases/RoleUserResult.md) - [RoleUserSearchQueryRequest](type-aliases/RoleUserSearchQueryRequest.md) - [RoleUserSearchQuerySortRequest](type-aliases/RoleUserSearchQuerySortRequest.md) - [RoleUserSearchResult](type-aliases/RoleUserSearchResult.md) - [RuntimeBackupState](type-aliases/RuntimeBackupState.md) - [ScopeKey](type-aliases/ScopeKey.md) - [ScopeKeyExactMatch](type-aliases/ScopeKeyExactMatch.md) - [ScopeKeyExactMatchWritable](type-aliases/ScopeKeyExactMatchWritable.md) - [ScopeKeyFilterProperty](type-aliases/ScopeKeyFilterProperty.md) - [ScopeKeyWritable](type-aliases/ScopeKeyWritable.md) - [SdkError](type-aliases/SdkError.md) - [searchAgentDefinitionsConsistency](type-aliases/searchAgentDefinitionsConsistency.md) - [SearchAgentDefinitionsData](type-aliases/SearchAgentDefinitionsData.md) - [SearchAgentDefinitionsError](type-aliases/SearchAgentDefinitionsError.md) - [SearchAgentDefinitionsErrors](type-aliases/SearchAgentDefinitionsErrors.md) - [searchAgentDefinitionsInput](type-aliases/searchAgentDefinitionsInput.md) - [SearchAgentDefinitionsResponse](type-aliases/SearchAgentDefinitionsResponse.md) - [SearchAgentDefinitionsResponses](type-aliases/SearchAgentDefinitionsResponses.md) - [searchAgentInstanceHistoryConsistency](type-aliases/searchAgentInstanceHistoryConsistency.md) - [SearchAgentInstanceHistoryData](type-aliases/SearchAgentInstanceHistoryData.md) - [SearchAgentInstanceHistoryError](type-aliases/SearchAgentInstanceHistoryError.md) - [SearchAgentInstanceHistoryErrors](type-aliases/SearchAgentInstanceHistoryErrors.md) - [searchAgentInstanceHistoryInput](type-aliases/searchAgentInstanceHistoryInput.md) - [SearchAgentInstanceHistoryResponse](type-aliases/SearchAgentInstanceHistoryResponse.md) - [SearchAgentInstanceHistoryResponses](type-aliases/SearchAgentInstanceHistoryResponses.md) - [searchAgentInstancesConsistency](type-aliases/searchAgentInstancesConsistency.md) - [SearchAgentInstancesData](type-aliases/SearchAgentInstancesData.md) - [SearchAgentInstancesError](type-aliases/SearchAgentInstancesError.md) - [SearchAgentInstancesErrors](type-aliases/SearchAgentInstancesErrors.md) - [searchAgentInstancesInput](type-aliases/searchAgentInstancesInput.md) - [SearchAgentInstancesResponse](type-aliases/SearchAgentInstancesResponse.md) - [SearchAgentInstancesResponses](type-aliases/SearchAgentInstancesResponses.md) - [searchAuditLogsConsistency](type-aliases/searchAuditLogsConsistency.md) - [SearchAuditLogsData](type-aliases/SearchAuditLogsData.md) - [SearchAuditLogsError](type-aliases/SearchAuditLogsError.md) - [SearchAuditLogsErrors](type-aliases/SearchAuditLogsErrors.md) - [searchAuditLogsInput](type-aliases/searchAuditLogsInput.md) - [SearchAuditLogsResponse](type-aliases/SearchAuditLogsResponse.md) - [SearchAuditLogsResponses](type-aliases/SearchAuditLogsResponses.md) - [searchAuthorizationsConsistency](type-aliases/searchAuthorizationsConsistency.md) - [SearchAuthorizationsData](type-aliases/SearchAuthorizationsData.md) - [SearchAuthorizationsError](type-aliases/SearchAuthorizationsError.md) - [SearchAuthorizationsErrors](type-aliases/SearchAuthorizationsErrors.md) - [searchAuthorizationsInput](type-aliases/searchAuthorizationsInput.md) - [SearchAuthorizationsResponse](type-aliases/SearchAuthorizationsResponse.md) - [SearchAuthorizationsResponses](type-aliases/SearchAuthorizationsResponses.md) - [searchBatchOperationItemsConsistency](type-aliases/searchBatchOperationItemsConsistency.md) - [SearchBatchOperationItemsData](type-aliases/SearchBatchOperationItemsData.md) - [SearchBatchOperationItemsError](type-aliases/SearchBatchOperationItemsError.md) - [SearchBatchOperationItemsErrors](type-aliases/SearchBatchOperationItemsErrors.md) - [searchBatchOperationItemsInput](type-aliases/searchBatchOperationItemsInput.md) - [SearchBatchOperationItemsResponse](type-aliases/SearchBatchOperationItemsResponse.md) - [SearchBatchOperationItemsResponses](type-aliases/SearchBatchOperationItemsResponses.md) - [searchBatchOperationsConsistency](type-aliases/searchBatchOperationsConsistency.md) - [SearchBatchOperationsData](type-aliases/SearchBatchOperationsData.md) - [SearchBatchOperationsError](type-aliases/SearchBatchOperationsError.md) - [SearchBatchOperationsErrors](type-aliases/SearchBatchOperationsErrors.md) - [searchBatchOperationsInput](type-aliases/searchBatchOperationsInput.md) - [SearchBatchOperationsResponse](type-aliases/SearchBatchOperationsResponse.md) - [SearchBatchOperationsResponses](type-aliases/SearchBatchOperationsResponses.md) - [SearchBody](type-aliases/SearchBody.md) - [searchClientsForGroupConsistency](type-aliases/searchClientsForGroupConsistency.md) - [SearchClientsForGroupData](type-aliases/SearchClientsForGroupData.md) - [SearchClientsForGroupError](type-aliases/SearchClientsForGroupError.md) - [SearchClientsForGroupErrors](type-aliases/SearchClientsForGroupErrors.md) - [searchClientsForGroupInput](type-aliases/searchClientsForGroupInput.md) - [SearchClientsForGroupResponse](type-aliases/SearchClientsForGroupResponse.md) - [SearchClientsForGroupResponses](type-aliases/SearchClientsForGroupResponses.md) - [searchClientsForRoleConsistency](type-aliases/searchClientsForRoleConsistency.md) - [SearchClientsForRoleData](type-aliases/SearchClientsForRoleData.md) - [SearchClientsForRoleError](type-aliases/SearchClientsForRoleError.md) - [SearchClientsForRoleErrors](type-aliases/SearchClientsForRoleErrors.md) - [searchClientsForRoleInput](type-aliases/searchClientsForRoleInput.md) - [SearchClientsForRoleResponse](type-aliases/SearchClientsForRoleResponse.md) - [SearchClientsForRoleResponses](type-aliases/SearchClientsForRoleResponses.md) - [searchClientsForTenantConsistency](type-aliases/searchClientsForTenantConsistency.md) - [SearchClientsForTenantData](type-aliases/SearchClientsForTenantData.md) - [searchClientsForTenantInput](type-aliases/searchClientsForTenantInput.md) - [SearchClientsForTenantResponse](type-aliases/SearchClientsForTenantResponse.md) - [SearchClientsForTenantResponses](type-aliases/SearchClientsForTenantResponses.md) - [searchClusterVariablesConsistency](type-aliases/searchClusterVariablesConsistency.md) - [SearchClusterVariablesData](type-aliases/SearchClusterVariablesData.md) - [SearchClusterVariablesError](type-aliases/SearchClusterVariablesError.md) - [SearchClusterVariablesErrors](type-aliases/SearchClusterVariablesErrors.md) - [searchClusterVariablesInput](type-aliases/searchClusterVariablesInput.md) - [SearchClusterVariablesResponse](type-aliases/SearchClusterVariablesResponse.md) - [SearchClusterVariablesResponses](type-aliases/SearchClusterVariablesResponses.md) - [searchCorrelatedMessageSubscriptionsConsistency](type-aliases/searchCorrelatedMessageSubscriptionsConsistency.md) - [SearchCorrelatedMessageSubscriptionsData](type-aliases/SearchCorrelatedMessageSubscriptionsData.md) - [SearchCorrelatedMessageSubscriptionsError](type-aliases/SearchCorrelatedMessageSubscriptionsError.md) - [SearchCorrelatedMessageSubscriptionsErrors](type-aliases/SearchCorrelatedMessageSubscriptionsErrors.md) - [searchCorrelatedMessageSubscriptionsInput](type-aliases/searchCorrelatedMessageSubscriptionsInput.md) - [SearchCorrelatedMessageSubscriptionsResponse](type-aliases/SearchCorrelatedMessageSubscriptionsResponse.md) - [SearchCorrelatedMessageSubscriptionsResponses](type-aliases/SearchCorrelatedMessageSubscriptionsResponses.md) - [searchDecisionDefinitionsConsistency](type-aliases/searchDecisionDefinitionsConsistency.md) - [SearchDecisionDefinitionsData](type-aliases/SearchDecisionDefinitionsData.md) - [SearchDecisionDefinitionsError](type-aliases/SearchDecisionDefinitionsError.md) - [SearchDecisionDefinitionsErrors](type-aliases/SearchDecisionDefinitionsErrors.md) - [searchDecisionDefinitionsInput](type-aliases/searchDecisionDefinitionsInput.md) - [SearchDecisionDefinitionsResponse](type-aliases/SearchDecisionDefinitionsResponse.md) - [SearchDecisionDefinitionsResponses](type-aliases/SearchDecisionDefinitionsResponses.md) - [searchDecisionInstancesConsistency](type-aliases/searchDecisionInstancesConsistency.md) - [SearchDecisionInstancesData](type-aliases/SearchDecisionInstancesData.md) - [SearchDecisionInstancesError](type-aliases/SearchDecisionInstancesError.md) - [SearchDecisionInstancesErrors](type-aliases/SearchDecisionInstancesErrors.md) - [searchDecisionInstancesInput](type-aliases/searchDecisionInstancesInput.md) - [SearchDecisionInstancesResponse](type-aliases/SearchDecisionInstancesResponse.md) - [SearchDecisionInstancesResponses](type-aliases/SearchDecisionInstancesResponses.md) - [searchDecisionRequirementsConsistency](type-aliases/searchDecisionRequirementsConsistency.md) - [SearchDecisionRequirementsData](type-aliases/SearchDecisionRequirementsData.md) - [SearchDecisionRequirementsError](type-aliases/SearchDecisionRequirementsError.md) - [SearchDecisionRequirementsErrors](type-aliases/SearchDecisionRequirementsErrors.md) - [searchDecisionRequirementsInput](type-aliases/searchDecisionRequirementsInput.md) - [SearchDecisionRequirementsResponse](type-aliases/SearchDecisionRequirementsResponse.md) - [SearchDecisionRequirementsResponses](type-aliases/SearchDecisionRequirementsResponses.md) - [searchElementInstanceIncidentsConsistency](type-aliases/searchElementInstanceIncidentsConsistency.md) - [SearchElementInstanceIncidentsData](type-aliases/SearchElementInstanceIncidentsData.md) - [SearchElementInstanceIncidentsError](type-aliases/SearchElementInstanceIncidentsError.md) - [SearchElementInstanceIncidentsErrors](type-aliases/SearchElementInstanceIncidentsErrors.md) - [searchElementInstanceIncidentsInput](type-aliases/searchElementInstanceIncidentsInput.md) - [SearchElementInstanceIncidentsResponse](type-aliases/SearchElementInstanceIncidentsResponse.md) - [SearchElementInstanceIncidentsResponses](type-aliases/SearchElementInstanceIncidentsResponses.md) - [searchElementInstancesConsistency](type-aliases/searchElementInstancesConsistency.md) - [SearchElementInstancesData](type-aliases/SearchElementInstancesData.md) - [SearchElementInstancesError](type-aliases/SearchElementInstancesError.md) - [SearchElementInstancesErrors](type-aliases/SearchElementInstancesErrors.md) - [searchElementInstancesInput](type-aliases/searchElementInstancesInput.md) - [SearchElementInstancesResponse](type-aliases/SearchElementInstancesResponse.md) - [SearchElementInstancesResponses](type-aliases/SearchElementInstancesResponses.md) - [searchElementInstanceWaitStatesConsistency](type-aliases/searchElementInstanceWaitStatesConsistency.md) - [SearchElementInstanceWaitStatesData](type-aliases/SearchElementInstanceWaitStatesData.md) - [SearchElementInstanceWaitStatesError](type-aliases/SearchElementInstanceWaitStatesError.md) - [SearchElementInstanceWaitStatesErrors](type-aliases/SearchElementInstanceWaitStatesErrors.md) - [searchElementInstanceWaitStatesInput](type-aliases/searchElementInstanceWaitStatesInput.md) - [SearchElementInstanceWaitStatesResponse](type-aliases/SearchElementInstanceWaitStatesResponse.md) - [SearchElementInstanceWaitStatesResponses](type-aliases/SearchElementInstanceWaitStatesResponses.md) - [searchGlobalTaskListenersConsistency](type-aliases/searchGlobalTaskListenersConsistency.md) - [SearchGlobalTaskListenersData](type-aliases/SearchGlobalTaskListenersData.md) - [SearchGlobalTaskListenersError](type-aliases/SearchGlobalTaskListenersError.md) - [SearchGlobalTaskListenersErrors](type-aliases/SearchGlobalTaskListenersErrors.md) - [searchGlobalTaskListenersInput](type-aliases/searchGlobalTaskListenersInput.md) - [SearchGlobalTaskListenersResponse](type-aliases/SearchGlobalTaskListenersResponse.md) - [SearchGlobalTaskListenersResponses](type-aliases/SearchGlobalTaskListenersResponses.md) - [searchGroupIdsForTenantConsistency](type-aliases/searchGroupIdsForTenantConsistency.md) - [SearchGroupIdsForTenantData](type-aliases/SearchGroupIdsForTenantData.md) - [searchGroupIdsForTenantInput](type-aliases/searchGroupIdsForTenantInput.md) - [SearchGroupIdsForTenantResponse](type-aliases/SearchGroupIdsForTenantResponse.md) - [SearchGroupIdsForTenantResponses](type-aliases/SearchGroupIdsForTenantResponses.md) - [searchGroupsConsistency](type-aliases/searchGroupsConsistency.md) - [SearchGroupsData](type-aliases/SearchGroupsData.md) - [SearchGroupsError](type-aliases/SearchGroupsError.md) - [SearchGroupsErrors](type-aliases/SearchGroupsErrors.md) - [searchGroupsForRoleConsistency](type-aliases/searchGroupsForRoleConsistency.md) - [SearchGroupsForRoleData](type-aliases/SearchGroupsForRoleData.md) - [SearchGroupsForRoleError](type-aliases/SearchGroupsForRoleError.md) - [SearchGroupsForRoleErrors](type-aliases/SearchGroupsForRoleErrors.md) - [searchGroupsForRoleInput](type-aliases/searchGroupsForRoleInput.md) - [SearchGroupsForRoleResponse](type-aliases/SearchGroupsForRoleResponse.md) - [SearchGroupsForRoleResponses](type-aliases/SearchGroupsForRoleResponses.md) - [searchGroupsInput](type-aliases/searchGroupsInput.md) - [SearchGroupsResponse](type-aliases/SearchGroupsResponse.md) - [SearchGroupsResponses](type-aliases/SearchGroupsResponses.md) - [searchIncidentsConsistency](type-aliases/searchIncidentsConsistency.md) - [SearchIncidentsData](type-aliases/SearchIncidentsData.md) - [SearchIncidentsError](type-aliases/SearchIncidentsError.md) - [SearchIncidentsErrors](type-aliases/SearchIncidentsErrors.md) - [searchIncidentsInput](type-aliases/searchIncidentsInput.md) - [SearchIncidentsResponse](type-aliases/SearchIncidentsResponse.md) - [SearchIncidentsResponses](type-aliases/SearchIncidentsResponses.md) - [searchJobsConsistency](type-aliases/searchJobsConsistency.md) - [SearchJobsData](type-aliases/SearchJobsData.md) - [SearchJobsError](type-aliases/SearchJobsError.md) - [SearchJobsErrors](type-aliases/SearchJobsErrors.md) - [searchJobsInput](type-aliases/searchJobsInput.md) - [SearchJobsResponse](type-aliases/SearchJobsResponse.md) - [SearchJobsResponses](type-aliases/SearchJobsResponses.md) - [searchMappingRuleConsistency](type-aliases/searchMappingRuleConsistency.md) - [SearchMappingRuleData](type-aliases/SearchMappingRuleData.md) - [SearchMappingRuleError](type-aliases/SearchMappingRuleError.md) - [SearchMappingRuleErrors](type-aliases/SearchMappingRuleErrors.md) - [searchMappingRuleInput](type-aliases/searchMappingRuleInput.md) - [SearchMappingRuleResponse](type-aliases/SearchMappingRuleResponse.md) - [SearchMappingRuleResponses](type-aliases/SearchMappingRuleResponses.md) - [searchMappingRulesForGroupConsistency](type-aliases/searchMappingRulesForGroupConsistency.md) - [SearchMappingRulesForGroupData](type-aliases/SearchMappingRulesForGroupData.md) - [SearchMappingRulesForGroupError](type-aliases/SearchMappingRulesForGroupError.md) - [SearchMappingRulesForGroupErrors](type-aliases/SearchMappingRulesForGroupErrors.md) - [searchMappingRulesForGroupInput](type-aliases/searchMappingRulesForGroupInput.md) - [SearchMappingRulesForGroupResponse](type-aliases/SearchMappingRulesForGroupResponse.md) - [SearchMappingRulesForGroupResponses](type-aliases/SearchMappingRulesForGroupResponses.md) - [searchMappingRulesForRoleConsistency](type-aliases/searchMappingRulesForRoleConsistency.md) - [SearchMappingRulesForRoleData](type-aliases/SearchMappingRulesForRoleData.md) - [SearchMappingRulesForRoleError](type-aliases/SearchMappingRulesForRoleError.md) - [SearchMappingRulesForRoleErrors](type-aliases/SearchMappingRulesForRoleErrors.md) - [searchMappingRulesForRoleInput](type-aliases/searchMappingRulesForRoleInput.md) - [SearchMappingRulesForRoleResponse](type-aliases/SearchMappingRulesForRoleResponse.md) - [SearchMappingRulesForRoleResponses](type-aliases/SearchMappingRulesForRoleResponses.md) - [searchMappingRulesForTenantConsistency](type-aliases/searchMappingRulesForTenantConsistency.md) - [SearchMappingRulesForTenantData](type-aliases/SearchMappingRulesForTenantData.md) - [searchMappingRulesForTenantInput](type-aliases/searchMappingRulesForTenantInput.md) - [SearchMappingRulesForTenantResponse](type-aliases/SearchMappingRulesForTenantResponse.md) - [SearchMappingRulesForTenantResponses](type-aliases/SearchMappingRulesForTenantResponses.md) - [searchMessageSubscriptionsConsistency](type-aliases/searchMessageSubscriptionsConsistency.md) - [SearchMessageSubscriptionsData](type-aliases/SearchMessageSubscriptionsData.md) - [SearchMessageSubscriptionsError](type-aliases/SearchMessageSubscriptionsError.md) - [SearchMessageSubscriptionsErrors](type-aliases/SearchMessageSubscriptionsErrors.md) - [searchMessageSubscriptionsInput](type-aliases/searchMessageSubscriptionsInput.md) - [SearchMessageSubscriptionsResponse](type-aliases/SearchMessageSubscriptionsResponse.md) - [SearchMessageSubscriptionsResponses](type-aliases/SearchMessageSubscriptionsResponses.md) - [searchOwnAuthorizationsConsistency](type-aliases/searchOwnAuthorizationsConsistency.md) - [SearchOwnAuthorizationsData](type-aliases/SearchOwnAuthorizationsData.md) - [SearchOwnAuthorizationsError](type-aliases/SearchOwnAuthorizationsError.md) - [SearchOwnAuthorizationsErrors](type-aliases/SearchOwnAuthorizationsErrors.md) - [searchOwnAuthorizationsInput](type-aliases/searchOwnAuthorizationsInput.md) - [SearchOwnAuthorizationsResponse](type-aliases/SearchOwnAuthorizationsResponse.md) - [SearchOwnAuthorizationsResponses](type-aliases/SearchOwnAuthorizationsResponses.md) - [SearchPaginationApi](type-aliases/SearchPaginationApi.md) - [searchProcessDefinitionsConsistency](type-aliases/searchProcessDefinitionsConsistency.md) - [SearchProcessDefinitionsData](type-aliases/SearchProcessDefinitionsData.md) - [SearchProcessDefinitionsError](type-aliases/SearchProcessDefinitionsError.md) - [SearchProcessDefinitionsErrors](type-aliases/SearchProcessDefinitionsErrors.md) - [searchProcessDefinitionsInput](type-aliases/searchProcessDefinitionsInput.md) - [SearchProcessDefinitionsResponse](type-aliases/SearchProcessDefinitionsResponse.md) - [SearchProcessDefinitionsResponses](type-aliases/SearchProcessDefinitionsResponses.md) - [searchProcessDefinitionVariableNamesConsistency](type-aliases/searchProcessDefinitionVariableNamesConsistency.md) - [SearchProcessDefinitionVariableNamesData](type-aliases/SearchProcessDefinitionVariableNamesData.md) - [SearchProcessDefinitionVariableNamesError](type-aliases/SearchProcessDefinitionVariableNamesError.md) - [SearchProcessDefinitionVariableNamesErrors](type-aliases/SearchProcessDefinitionVariableNamesErrors.md) - [searchProcessDefinitionVariableNamesInput](type-aliases/searchProcessDefinitionVariableNamesInput.md) - [SearchProcessDefinitionVariableNamesResponse](type-aliases/SearchProcessDefinitionVariableNamesResponse.md) - [SearchProcessDefinitionVariableNamesResponses](type-aliases/SearchProcessDefinitionVariableNamesResponses.md) - [searchProcessInstanceIncidentsConsistency](type-aliases/searchProcessInstanceIncidentsConsistency.md) - [SearchProcessInstanceIncidentsData](type-aliases/SearchProcessInstanceIncidentsData.md) - [SearchProcessInstanceIncidentsError](type-aliases/SearchProcessInstanceIncidentsError.md) - [SearchProcessInstanceIncidentsErrors](type-aliases/SearchProcessInstanceIncidentsErrors.md) - [searchProcessInstanceIncidentsInput](type-aliases/searchProcessInstanceIncidentsInput.md) - [SearchProcessInstanceIncidentsResponse](type-aliases/SearchProcessInstanceIncidentsResponse.md) - [SearchProcessInstanceIncidentsResponses](type-aliases/SearchProcessInstanceIncidentsResponses.md) - [searchProcessInstancesConsistency](type-aliases/searchProcessInstancesConsistency.md) - [SearchProcessInstancesData](type-aliases/SearchProcessInstancesData.md) - [SearchProcessInstancesError](type-aliases/SearchProcessInstancesError.md) - [SearchProcessInstancesErrors](type-aliases/SearchProcessInstancesErrors.md) - [searchProcessInstancesInput](type-aliases/searchProcessInstancesInput.md) - [SearchProcessInstancesResponse](type-aliases/SearchProcessInstancesResponse.md) - [SearchProcessInstancesResponses](type-aliases/SearchProcessInstancesResponses.md) - [SearchQueryPageRequest](type-aliases/SearchQueryPageRequest.md) - [SearchQueryPageResponse](type-aliases/SearchQueryPageResponse.md) - [SearchQueryRequest](type-aliases/SearchQueryRequest.md) - [SearchQueryResponse](type-aliases/SearchQueryResponse.md) - [searchResourcesConsistency](type-aliases/searchResourcesConsistency.md) - [SearchResourcesData](type-aliases/SearchResourcesData.md) - [SearchResourcesError](type-aliases/SearchResourcesError.md) - [SearchResourcesErrors](type-aliases/SearchResourcesErrors.md) - [searchResourcesInput](type-aliases/searchResourcesInput.md) - [SearchResourcesResponse](type-aliases/SearchResourcesResponse.md) - [SearchResourcesResponses](type-aliases/SearchResourcesResponses.md) - [searchRolesConsistency](type-aliases/searchRolesConsistency.md) - [SearchRolesData](type-aliases/SearchRolesData.md) - [SearchRolesError](type-aliases/SearchRolesError.md) - [SearchRolesErrors](type-aliases/SearchRolesErrors.md) - [searchRolesForGroupConsistency](type-aliases/searchRolesForGroupConsistency.md) - [SearchRolesForGroupData](type-aliases/SearchRolesForGroupData.md) - [SearchRolesForGroupError](type-aliases/SearchRolesForGroupError.md) - [SearchRolesForGroupErrors](type-aliases/SearchRolesForGroupErrors.md) - [searchRolesForGroupInput](type-aliases/searchRolesForGroupInput.md) - [SearchRolesForGroupResponse](type-aliases/SearchRolesForGroupResponse.md) - [SearchRolesForGroupResponses](type-aliases/SearchRolesForGroupResponses.md) - [searchRolesForTenantConsistency](type-aliases/searchRolesForTenantConsistency.md) - [SearchRolesForTenantData](type-aliases/SearchRolesForTenantData.md) - [searchRolesForTenantInput](type-aliases/searchRolesForTenantInput.md) - [SearchRolesForTenantResponse](type-aliases/SearchRolesForTenantResponse.md) - [SearchRolesForTenantResponses](type-aliases/SearchRolesForTenantResponses.md) - [searchRolesInput](type-aliases/searchRolesInput.md) - [SearchRolesResponse](type-aliases/SearchRolesResponse.md) - [SearchRolesResponses](type-aliases/SearchRolesResponses.md) - [searchTenantsConsistency](type-aliases/searchTenantsConsistency.md) - [SearchTenantsData](type-aliases/SearchTenantsData.md) - [SearchTenantsError](type-aliases/SearchTenantsError.md) - [SearchTenantsErrors](type-aliases/SearchTenantsErrors.md) - [searchTenantsInput](type-aliases/searchTenantsInput.md) - [SearchTenantsResponse](type-aliases/SearchTenantsResponse.md) - [SearchTenantsResponses](type-aliases/SearchTenantsResponses.md) - [searchUsersConsistency](type-aliases/searchUsersConsistency.md) - [SearchUsersData](type-aliases/SearchUsersData.md) - [SearchUsersError](type-aliases/SearchUsersError.md) - [SearchUsersErrors](type-aliases/SearchUsersErrors.md) - [searchUsersForGroupConsistency](type-aliases/searchUsersForGroupConsistency.md) - [SearchUsersForGroupData](type-aliases/SearchUsersForGroupData.md) - [SearchUsersForGroupError](type-aliases/SearchUsersForGroupError.md) - [SearchUsersForGroupErrors](type-aliases/SearchUsersForGroupErrors.md) - [searchUsersForGroupInput](type-aliases/searchUsersForGroupInput.md) - [SearchUsersForGroupResponse](type-aliases/SearchUsersForGroupResponse.md) - [SearchUsersForGroupResponses](type-aliases/SearchUsersForGroupResponses.md) - [searchUsersForRoleConsistency](type-aliases/searchUsersForRoleConsistency.md) - [SearchUsersForRoleData](type-aliases/SearchUsersForRoleData.md) - [SearchUsersForRoleError](type-aliases/SearchUsersForRoleError.md) - [SearchUsersForRoleErrors](type-aliases/SearchUsersForRoleErrors.md) - [searchUsersForRoleInput](type-aliases/searchUsersForRoleInput.md) - [SearchUsersForRoleResponse](type-aliases/SearchUsersForRoleResponse.md) - [SearchUsersForRoleResponses](type-aliases/SearchUsersForRoleResponses.md) - [searchUsersForTenantConsistency](type-aliases/searchUsersForTenantConsistency.md) - [SearchUsersForTenantData](type-aliases/SearchUsersForTenantData.md) - [searchUsersForTenantInput](type-aliases/searchUsersForTenantInput.md) - [SearchUsersForTenantResponse](type-aliases/SearchUsersForTenantResponse.md) - [SearchUsersForTenantResponses](type-aliases/SearchUsersForTenantResponses.md) - [searchUsersInput](type-aliases/searchUsersInput.md) - [SearchUsersResponse](type-aliases/SearchUsersResponse.md) - [SearchUsersResponses](type-aliases/SearchUsersResponses.md) - [searchUserTaskAuditLogsConsistency](type-aliases/searchUserTaskAuditLogsConsistency.md) - [SearchUserTaskAuditLogsData](type-aliases/SearchUserTaskAuditLogsData.md) - [SearchUserTaskAuditLogsError](type-aliases/SearchUserTaskAuditLogsError.md) - [SearchUserTaskAuditLogsErrors](type-aliases/SearchUserTaskAuditLogsErrors.md) - [searchUserTaskAuditLogsInput](type-aliases/searchUserTaskAuditLogsInput.md) - [SearchUserTaskAuditLogsResponse](type-aliases/SearchUserTaskAuditLogsResponse.md) - [SearchUserTaskAuditLogsResponses](type-aliases/SearchUserTaskAuditLogsResponses.md) - [searchUserTaskEffectiveVariablesConsistency](type-aliases/searchUserTaskEffectiveVariablesConsistency.md) - [SearchUserTaskEffectiveVariablesData](type-aliases/SearchUserTaskEffectiveVariablesData.md) - [SearchUserTaskEffectiveVariablesError](type-aliases/SearchUserTaskEffectiveVariablesError.md) - [SearchUserTaskEffectiveVariablesErrors](type-aliases/SearchUserTaskEffectiveVariablesErrors.md) - [searchUserTaskEffectiveVariablesInput](type-aliases/searchUserTaskEffectiveVariablesInput.md) - [SearchUserTaskEffectiveVariablesResponse](type-aliases/SearchUserTaskEffectiveVariablesResponse.md) - [SearchUserTaskEffectiveVariablesResponses](type-aliases/SearchUserTaskEffectiveVariablesResponses.md) - [searchUserTasksConsistency](type-aliases/searchUserTasksConsistency.md) - [SearchUserTasksData](type-aliases/SearchUserTasksData.md) - [SearchUserTasksError](type-aliases/SearchUserTasksError.md) - [SearchUserTasksErrors](type-aliases/SearchUserTasksErrors.md) - [searchUserTasksInput](type-aliases/searchUserTasksInput.md) - [SearchUserTasksResponse](type-aliases/SearchUserTasksResponse.md) - [SearchUserTasksResponses](type-aliases/SearchUserTasksResponses.md) - [searchUserTaskVariablesConsistency](type-aliases/searchUserTaskVariablesConsistency.md) - [SearchUserTaskVariablesData](type-aliases/SearchUserTaskVariablesData.md) - [SearchUserTaskVariablesError](type-aliases/SearchUserTaskVariablesError.md) - [SearchUserTaskVariablesErrors](type-aliases/SearchUserTaskVariablesErrors.md) - [searchUserTaskVariablesInput](type-aliases/searchUserTaskVariablesInput.md) - [SearchUserTaskVariablesResponse](type-aliases/SearchUserTaskVariablesResponse.md) - [SearchUserTaskVariablesResponses](type-aliases/SearchUserTaskVariablesResponses.md) - [searchVariablesConsistency](type-aliases/searchVariablesConsistency.md) - [SearchVariablesData](type-aliases/SearchVariablesData.md) - [SearchVariablesError](type-aliases/SearchVariablesError.md) - [SearchVariablesErrors](type-aliases/SearchVariablesErrors.md) - [searchVariablesInput](type-aliases/searchVariablesInput.md) - [SearchVariablesResponse](type-aliases/SearchVariablesResponse.md) - [SearchVariablesResponses](type-aliases/SearchVariablesResponses.md) - [SecretErrorCode](type-aliases/SecretErrorCode.md) - [SecretListRequest](type-aliases/SecretListRequest.md) - [SecretListResult](type-aliases/SecretListResult.md) - [SecretResolutionError](type-aliases/SecretResolutionError.md) - [SecretResolveRequest](type-aliases/SecretResolveRequest.md) - [SecretResolveResult](type-aliases/SecretResolveResult.md) - [SetVariableRequest](type-aliases/SetVariableRequest.md) - [SignalBroadcastRequest](type-aliases/SignalBroadcastRequest.md) - [SignalBroadcastResult](type-aliases/SignalBroadcastResult.md) - [SignalKey](type-aliases/SignalKey.md) - [SignalKeyWritable](type-aliases/SignalKeyWritable.md) - [SignalWaitStateDetails](type-aliases/SignalWaitStateDetails.md) - [SortOrderEnum](type-aliases/SortOrderEnum.md) - [SourceElementIdInstruction](type-aliases/SourceElementIdInstruction.md) - [SourceElementInstanceKeyInstruction](type-aliases/SourceElementInstanceKeyInstruction.md) - [SourceElementInstruction](type-aliases/SourceElementInstruction.md) - [StartCursor](type-aliases/StartCursor.md) - [StateCode](type-aliases/StateCode.md) - [StatusMetric](type-aliases/StatusMetric.md) - [StringFilterProperty](type-aliases/StringFilterProperty.md) - [SuspendBatchOperationData](type-aliases/SuspendBatchOperationData.md) - [SuspendBatchOperationError](type-aliases/SuspendBatchOperationError.md) - [SuspendBatchOperationErrors](type-aliases/SuspendBatchOperationErrors.md) - [suspendBatchOperationInput](type-aliases/suspendBatchOperationInput.md) - [SuspendBatchOperationResponse](type-aliases/SuspendBatchOperationResponse.md) - [SuspendBatchOperationResponses](type-aliases/SuspendBatchOperationResponses.md) - [SuspendProcessInstanceData](type-aliases/SuspendProcessInstanceData.md) - [SuspendProcessInstanceError](type-aliases/SuspendProcessInstanceError.md) - [SuspendProcessInstanceErrors](type-aliases/SuspendProcessInstanceErrors.md) - [suspendProcessInstanceInput](type-aliases/suspendProcessInstanceInput.md) - [SuspendProcessInstanceRequest](type-aliases/SuspendProcessInstanceRequest.md) - [SuspendProcessInstanceResponse](type-aliases/SuspendProcessInstanceResponse.md) - [SuspendProcessInstanceResponses](type-aliases/SuspendProcessInstanceResponses.md) - [SuspendProcessInstancesBatchOperationData](type-aliases/SuspendProcessInstancesBatchOperationData.md) - [SuspendProcessInstancesBatchOperationError](type-aliases/SuspendProcessInstancesBatchOperationError.md) - [SuspendProcessInstancesBatchOperationErrors](type-aliases/SuspendProcessInstancesBatchOperationErrors.md) - [suspendProcessInstancesBatchOperationInput](type-aliases/suspendProcessInstancesBatchOperationInput.md) - [SuspendProcessInstancesBatchOperationResponse](type-aliases/SuspendProcessInstancesBatchOperationResponse.md) - [SuspendProcessInstancesBatchOperationResponses](type-aliases/SuspendProcessInstancesBatchOperationResponses.md) - [SyncRuntimeBackupStateAsClusterAdminData](type-aliases/SyncRuntimeBackupStateAsClusterAdminData.md) - [SyncRuntimeBackupStateAsClusterAdminError](type-aliases/SyncRuntimeBackupStateAsClusterAdminError.md) - [SyncRuntimeBackupStateAsClusterAdminErrors](type-aliases/SyncRuntimeBackupStateAsClusterAdminErrors.md) - [syncRuntimeBackupStateAsClusterAdminInput](type-aliases/syncRuntimeBackupStateAsClusterAdminInput.md) - [SyncRuntimeBackupStateAsClusterAdminResponse](type-aliases/SyncRuntimeBackupStateAsClusterAdminResponse.md) - [SyncRuntimeBackupStateAsClusterAdminResponses](type-aliases/SyncRuntimeBackupStateAsClusterAdminResponses.md) - [SyncRuntimeBackupStateData](type-aliases/SyncRuntimeBackupStateData.md) - [SyncRuntimeBackupStateError](type-aliases/SyncRuntimeBackupStateError.md) - [SyncRuntimeBackupStateErrors](type-aliases/SyncRuntimeBackupStateErrors.md) - [syncRuntimeBackupStateInput](type-aliases/syncRuntimeBackupStateInput.md) - [SyncRuntimeBackupStateResponse](type-aliases/SyncRuntimeBackupStateResponse.md) - [SyncRuntimeBackupStateResponses](type-aliases/SyncRuntimeBackupStateResponses.md) - [SystemConfigurationResponse](type-aliases/SystemConfigurationResponse.md) - [Tag](type-aliases/Tag.md) - [TagSet](type-aliases/TagSet.md) - [TagSetWritable](type-aliases/TagSetWritable.md) - [TakeHistoryBackupAsClusterAdminData](type-aliases/TakeHistoryBackupAsClusterAdminData.md) - [TakeHistoryBackupAsClusterAdminError](type-aliases/TakeHistoryBackupAsClusterAdminError.md) - [TakeHistoryBackupAsClusterAdminErrors](type-aliases/TakeHistoryBackupAsClusterAdminErrors.md) - [takeHistoryBackupAsClusterAdminInput](type-aliases/takeHistoryBackupAsClusterAdminInput.md) - [TakeHistoryBackupAsClusterAdminResponse](type-aliases/TakeHistoryBackupAsClusterAdminResponse.md) - [TakeHistoryBackupAsClusterAdminResponses](type-aliases/TakeHistoryBackupAsClusterAdminResponses.md) - [TakeHistoryBackupData](type-aliases/TakeHistoryBackupData.md) - [TakeHistoryBackupError](type-aliases/TakeHistoryBackupError.md) - [TakeHistoryBackupErrors](type-aliases/TakeHistoryBackupErrors.md) - [takeHistoryBackupInput](type-aliases/takeHistoryBackupInput.md) - [TakeHistoryBackupRequest](type-aliases/TakeHistoryBackupRequest.md) - [TakeHistoryBackupResponse](type-aliases/TakeHistoryBackupResponse.md) - [TakeHistoryBackupResponse2](type-aliases/TakeHistoryBackupResponse2.md) - [TakeHistoryBackupResponses](type-aliases/TakeHistoryBackupResponses.md) - [TakeRuntimeBackupAsClusterAdminData](type-aliases/TakeRuntimeBackupAsClusterAdminData.md) - [TakeRuntimeBackupAsClusterAdminError](type-aliases/TakeRuntimeBackupAsClusterAdminError.md) - [TakeRuntimeBackupAsClusterAdminErrors](type-aliases/TakeRuntimeBackupAsClusterAdminErrors.md) - [takeRuntimeBackupAsClusterAdminInput](type-aliases/takeRuntimeBackupAsClusterAdminInput.md) - [TakeRuntimeBackupAsClusterAdminResponse](type-aliases/TakeRuntimeBackupAsClusterAdminResponse.md) - [TakeRuntimeBackupAsClusterAdminResponses](type-aliases/TakeRuntimeBackupAsClusterAdminResponses.md) - [TakeRuntimeBackupData](type-aliases/TakeRuntimeBackupData.md) - [TakeRuntimeBackupError](type-aliases/TakeRuntimeBackupError.md) - [TakeRuntimeBackupErrors](type-aliases/TakeRuntimeBackupErrors.md) - [takeRuntimeBackupInput](type-aliases/takeRuntimeBackupInput.md) - [TakeRuntimeBackupRequest](type-aliases/TakeRuntimeBackupRequest.md) - [TakeRuntimeBackupResponse](type-aliases/TakeRuntimeBackupResponse.md) - [TakeRuntimeBackupResponse2](type-aliases/TakeRuntimeBackupResponse2.md) - [TakeRuntimeBackupResponses](type-aliases/TakeRuntimeBackupResponses.md) - [TenantClientResult](type-aliases/TenantClientResult.md) - [TenantClientSearchQueryRequest](type-aliases/TenantClientSearchQueryRequest.md) - [TenantClientSearchQuerySortRequest](type-aliases/TenantClientSearchQuerySortRequest.md) - [TenantClientSearchResult](type-aliases/TenantClientSearchResult.md) - [TenantCreateRequest](type-aliases/TenantCreateRequest.md) - [TenantCreateResult](type-aliases/TenantCreateResult.md) - [TenantFilter](type-aliases/TenantFilter.md) - [TenantFilterEnum](type-aliases/TenantFilterEnum.md) - [TenantGroupResult](type-aliases/TenantGroupResult.md) - [TenantGroupSearchQueryRequest](type-aliases/TenantGroupSearchQueryRequest.md) - [TenantGroupSearchQuerySortRequest](type-aliases/TenantGroupSearchQuerySortRequest.md) - [TenantGroupSearchResult](type-aliases/TenantGroupSearchResult.md) - [TenantId](type-aliases/TenantId.md) - [TenantMappingRuleSearchResult](type-aliases/TenantMappingRuleSearchResult.md) - [TenantResult](type-aliases/TenantResult.md) - [TenantRoleSearchResult](type-aliases/TenantRoleSearchResult.md) - [TenantSearchQueryRequest](type-aliases/TenantSearchQueryRequest.md) - [TenantSearchQueryResult](type-aliases/TenantSearchQueryResult.md) - [TenantSearchQuerySortRequest](type-aliases/TenantSearchQuerySortRequest.md) - [TenantUpdateRequest](type-aliases/TenantUpdateRequest.md) - [TenantUpdateResult](type-aliases/TenantUpdateResult.md) - [TenantUserResult](type-aliases/TenantUserResult.md) - [TenantUserSearchQueryRequest](type-aliases/TenantUserSearchQueryRequest.md) - [TenantUserSearchQuerySortRequest](type-aliases/TenantUserSearchQuerySortRequest.md) - [TenantUserSearchResult](type-aliases/TenantUserSearchResult.md) - [ThreadedJob](type-aliases/ThreadedJob.md) - [ThreadedJobHandler](type-aliases/ThreadedJobHandler.md) - [ThrowJobErrorData](type-aliases/ThrowJobErrorData.md) - [ThrowJobErrorError](type-aliases/ThrowJobErrorError.md) - [ThrowJobErrorErrors](type-aliases/ThrowJobErrorErrors.md) - [throwJobErrorInput](type-aliases/throwJobErrorInput.md) - [ThrowJobErrorResponse](type-aliases/ThrowJobErrorResponse.md) - [ThrowJobErrorResponses](type-aliases/ThrowJobErrorResponses.md) - [TimerWaitStateDetails](type-aliases/TimerWaitStateDetails.md) - [TopologyResponse](type-aliases/TopologyResponse.md) - [TriggerClusterRebalanceData](type-aliases/TriggerClusterRebalanceData.md) - [TriggerClusterRebalanceError](type-aliases/TriggerClusterRebalanceError.md) - [TriggerClusterRebalanceErrors](type-aliases/TriggerClusterRebalanceErrors.md) - [triggerClusterRebalanceInput](type-aliases/triggerClusterRebalanceInput.md) - [TriggerClusterRebalanceResponse](type-aliases/TriggerClusterRebalanceResponse.md) - [TriggerClusterRebalanceResponses](type-aliases/TriggerClusterRebalanceResponses.md) - [UnassignClientFromGroupData](type-aliases/UnassignClientFromGroupData.md) - [UnassignClientFromGroupError](type-aliases/UnassignClientFromGroupError.md) - [UnassignClientFromGroupErrors](type-aliases/UnassignClientFromGroupErrors.md) - [unassignClientFromGroupInput](type-aliases/unassignClientFromGroupInput.md) - [UnassignClientFromGroupResponse](type-aliases/UnassignClientFromGroupResponse.md) - [UnassignClientFromGroupResponses](type-aliases/UnassignClientFromGroupResponses.md) - [UnassignClientFromTenantData](type-aliases/UnassignClientFromTenantData.md) - [UnassignClientFromTenantError](type-aliases/UnassignClientFromTenantError.md) - [UnassignClientFromTenantErrors](type-aliases/UnassignClientFromTenantErrors.md) - [unassignClientFromTenantInput](type-aliases/unassignClientFromTenantInput.md) - [UnassignClientFromTenantResponse](type-aliases/UnassignClientFromTenantResponse.md) - [UnassignClientFromTenantResponses](type-aliases/UnassignClientFromTenantResponses.md) - [UnassignGroupFromTenantData](type-aliases/UnassignGroupFromTenantData.md) - [UnassignGroupFromTenantError](type-aliases/UnassignGroupFromTenantError.md) - [UnassignGroupFromTenantErrors](type-aliases/UnassignGroupFromTenantErrors.md) - [unassignGroupFromTenantInput](type-aliases/unassignGroupFromTenantInput.md) - [UnassignGroupFromTenantResponse](type-aliases/UnassignGroupFromTenantResponse.md) - [UnassignGroupFromTenantResponses](type-aliases/UnassignGroupFromTenantResponses.md) - [UnassignMappingRuleFromGroupData](type-aliases/UnassignMappingRuleFromGroupData.md) - [UnassignMappingRuleFromGroupError](type-aliases/UnassignMappingRuleFromGroupError.md) - [UnassignMappingRuleFromGroupErrors](type-aliases/UnassignMappingRuleFromGroupErrors.md) - [unassignMappingRuleFromGroupInput](type-aliases/unassignMappingRuleFromGroupInput.md) - [UnassignMappingRuleFromGroupResponse](type-aliases/UnassignMappingRuleFromGroupResponse.md) - [UnassignMappingRuleFromGroupResponses](type-aliases/UnassignMappingRuleFromGroupResponses.md) - [UnassignMappingRuleFromTenantData](type-aliases/UnassignMappingRuleFromTenantData.md) - [UnassignMappingRuleFromTenantError](type-aliases/UnassignMappingRuleFromTenantError.md) - [UnassignMappingRuleFromTenantErrors](type-aliases/UnassignMappingRuleFromTenantErrors.md) - [unassignMappingRuleFromTenantInput](type-aliases/unassignMappingRuleFromTenantInput.md) - [UnassignMappingRuleFromTenantResponse](type-aliases/UnassignMappingRuleFromTenantResponse.md) - [UnassignMappingRuleFromTenantResponses](type-aliases/UnassignMappingRuleFromTenantResponses.md) - [UnassignRoleFromClientData](type-aliases/UnassignRoleFromClientData.md) - [UnassignRoleFromClientError](type-aliases/UnassignRoleFromClientError.md) - [UnassignRoleFromClientErrors](type-aliases/UnassignRoleFromClientErrors.md) - [unassignRoleFromClientInput](type-aliases/unassignRoleFromClientInput.md) - [UnassignRoleFromClientResponse](type-aliases/UnassignRoleFromClientResponse.md) - [UnassignRoleFromClientResponses](type-aliases/UnassignRoleFromClientResponses.md) - [UnassignRoleFromGroupData](type-aliases/UnassignRoleFromGroupData.md) - [UnassignRoleFromGroupError](type-aliases/UnassignRoleFromGroupError.md) - [UnassignRoleFromGroupErrors](type-aliases/UnassignRoleFromGroupErrors.md) - [unassignRoleFromGroupInput](type-aliases/unassignRoleFromGroupInput.md) - [UnassignRoleFromGroupResponse](type-aliases/UnassignRoleFromGroupResponse.md) - [UnassignRoleFromGroupResponses](type-aliases/UnassignRoleFromGroupResponses.md) - [UnassignRoleFromMappingRuleData](type-aliases/UnassignRoleFromMappingRuleData.md) - [UnassignRoleFromMappingRuleError](type-aliases/UnassignRoleFromMappingRuleError.md) - [UnassignRoleFromMappingRuleErrors](type-aliases/UnassignRoleFromMappingRuleErrors.md) - [unassignRoleFromMappingRuleInput](type-aliases/unassignRoleFromMappingRuleInput.md) - [UnassignRoleFromMappingRuleResponse](type-aliases/UnassignRoleFromMappingRuleResponse.md) - [UnassignRoleFromMappingRuleResponses](type-aliases/UnassignRoleFromMappingRuleResponses.md) - [UnassignRoleFromTenantData](type-aliases/UnassignRoleFromTenantData.md) - [UnassignRoleFromTenantError](type-aliases/UnassignRoleFromTenantError.md) - [UnassignRoleFromTenantErrors](type-aliases/UnassignRoleFromTenantErrors.md) - [unassignRoleFromTenantInput](type-aliases/unassignRoleFromTenantInput.md) - [UnassignRoleFromTenantResponse](type-aliases/UnassignRoleFromTenantResponse.md) - [UnassignRoleFromTenantResponses](type-aliases/UnassignRoleFromTenantResponses.md) - [UnassignRoleFromUserData](type-aliases/UnassignRoleFromUserData.md) - [UnassignRoleFromUserError](type-aliases/UnassignRoleFromUserError.md) - [UnassignRoleFromUserErrors](type-aliases/UnassignRoleFromUserErrors.md) - [unassignRoleFromUserInput](type-aliases/unassignRoleFromUserInput.md) - [UnassignRoleFromUserResponse](type-aliases/UnassignRoleFromUserResponse.md) - [UnassignRoleFromUserResponses](type-aliases/UnassignRoleFromUserResponses.md) - [UnassignUserFromGroupData](type-aliases/UnassignUserFromGroupData.md) - [UnassignUserFromGroupError](type-aliases/UnassignUserFromGroupError.md) - [UnassignUserFromGroupErrors](type-aliases/UnassignUserFromGroupErrors.md) - [unassignUserFromGroupInput](type-aliases/unassignUserFromGroupInput.md) - [UnassignUserFromGroupResponse](type-aliases/UnassignUserFromGroupResponse.md) - [UnassignUserFromGroupResponses](type-aliases/UnassignUserFromGroupResponses.md) - [UnassignUserFromTenantData](type-aliases/UnassignUserFromTenantData.md) - [UnassignUserFromTenantError](type-aliases/UnassignUserFromTenantError.md) - [UnassignUserFromTenantErrors](type-aliases/UnassignUserFromTenantErrors.md) - [unassignUserFromTenantInput](type-aliases/unassignUserFromTenantInput.md) - [UnassignUserFromTenantResponse](type-aliases/UnassignUserFromTenantResponse.md) - [UnassignUserFromTenantResponses](type-aliases/UnassignUserFromTenantResponses.md) - [UnassignUserTaskData](type-aliases/UnassignUserTaskData.md) - [UnassignUserTaskError](type-aliases/UnassignUserTaskError.md) - [UnassignUserTaskErrors](type-aliases/UnassignUserTaskErrors.md) - [unassignUserTaskInput](type-aliases/unassignUserTaskInput.md) - [UnassignUserTaskResponse](type-aliases/UnassignUserTaskResponse.md) - [UnassignUserTaskResponses](type-aliases/UnassignUserTaskResponses.md) - [UpdateAgentInstanceData](type-aliases/UpdateAgentInstanceData.md) - [UpdateAgentInstanceError](type-aliases/UpdateAgentInstanceError.md) - [UpdateAgentInstanceErrors](type-aliases/UpdateAgentInstanceErrors.md) - [updateAgentInstanceInput](type-aliases/updateAgentInstanceInput.md) - [UpdateAgentInstanceResponse](type-aliases/UpdateAgentInstanceResponse.md) - [UpdateAgentInstanceResponses](type-aliases/UpdateAgentInstanceResponses.md) - [UpdateAuthorizationData](type-aliases/UpdateAuthorizationData.md) - [UpdateAuthorizationError](type-aliases/UpdateAuthorizationError.md) - [UpdateAuthorizationErrors](type-aliases/UpdateAuthorizationErrors.md) - [updateAuthorizationInput](type-aliases/updateAuthorizationInput.md) - [UpdateAuthorizationResponse](type-aliases/UpdateAuthorizationResponse.md) - [UpdateAuthorizationResponses](type-aliases/UpdateAuthorizationResponses.md) - [UpdateClusterVariableRequest](type-aliases/UpdateClusterVariableRequest.md) - [UpdateGlobalClusterVariableData](type-aliases/UpdateGlobalClusterVariableData.md) - [UpdateGlobalClusterVariableError](type-aliases/UpdateGlobalClusterVariableError.md) - [UpdateGlobalClusterVariableErrors](type-aliases/UpdateGlobalClusterVariableErrors.md) - [updateGlobalClusterVariableInput](type-aliases/updateGlobalClusterVariableInput.md) - [UpdateGlobalClusterVariableResponse](type-aliases/UpdateGlobalClusterVariableResponse.md) - [UpdateGlobalClusterVariableResponses](type-aliases/UpdateGlobalClusterVariableResponses.md) - [UpdateGlobalTaskListenerData](type-aliases/UpdateGlobalTaskListenerData.md) - [UpdateGlobalTaskListenerError](type-aliases/UpdateGlobalTaskListenerError.md) - [UpdateGlobalTaskListenerErrors](type-aliases/UpdateGlobalTaskListenerErrors.md) - [updateGlobalTaskListenerInput](type-aliases/updateGlobalTaskListenerInput.md) - [UpdateGlobalTaskListenerRequest](type-aliases/UpdateGlobalTaskListenerRequest.md) - [UpdateGlobalTaskListenerResponse](type-aliases/UpdateGlobalTaskListenerResponse.md) - [UpdateGlobalTaskListenerResponses](type-aliases/UpdateGlobalTaskListenerResponses.md) - [UpdateGroupData](type-aliases/UpdateGroupData.md) - [UpdateGroupError](type-aliases/UpdateGroupError.md) - [UpdateGroupErrors](type-aliases/UpdateGroupErrors.md) - [updateGroupInput](type-aliases/updateGroupInput.md) - [UpdateGroupResponse](type-aliases/UpdateGroupResponse.md) - [UpdateGroupResponses](type-aliases/UpdateGroupResponses.md) - [UpdateJobData](type-aliases/UpdateJobData.md) - [UpdateJobError](type-aliases/UpdateJobError.md) - [UpdateJobErrors](type-aliases/UpdateJobErrors.md) - [updateJobInput](type-aliases/updateJobInput.md) - [UpdateJobResponse](type-aliases/UpdateJobResponse.md) - [UpdateJobResponses](type-aliases/UpdateJobResponses.md) - [UpdateJobsBatchOperationData](type-aliases/UpdateJobsBatchOperationData.md) - [UpdateJobsBatchOperationError](type-aliases/UpdateJobsBatchOperationError.md) - [UpdateJobsBatchOperationErrors](type-aliases/UpdateJobsBatchOperationErrors.md) - [updateJobsBatchOperationInput](type-aliases/updateJobsBatchOperationInput.md) - [UpdateJobsBatchOperationResponse](type-aliases/UpdateJobsBatchOperationResponse.md) - [UpdateJobsBatchOperationResponses](type-aliases/UpdateJobsBatchOperationResponses.md) - [UpdateMappingRuleData](type-aliases/UpdateMappingRuleData.md) - [UpdateMappingRuleError](type-aliases/UpdateMappingRuleError.md) - [UpdateMappingRuleErrors](type-aliases/UpdateMappingRuleErrors.md) - [updateMappingRuleInput](type-aliases/updateMappingRuleInput.md) - [UpdateMappingRuleResponse](type-aliases/UpdateMappingRuleResponse.md) - [UpdateMappingRuleResponses](type-aliases/UpdateMappingRuleResponses.md) - [UpdateRoleData](type-aliases/UpdateRoleData.md) - [UpdateRoleError](type-aliases/UpdateRoleError.md) - [UpdateRoleErrors](type-aliases/UpdateRoleErrors.md) - [updateRoleInput](type-aliases/updateRoleInput.md) - [UpdateRoleResponse](type-aliases/UpdateRoleResponse.md) - [UpdateRoleResponses](type-aliases/UpdateRoleResponses.md) - [UpdateTenantClusterVariableData](type-aliases/UpdateTenantClusterVariableData.md) - [UpdateTenantClusterVariableError](type-aliases/UpdateTenantClusterVariableError.md) - [UpdateTenantClusterVariableErrors](type-aliases/UpdateTenantClusterVariableErrors.md) - [updateTenantClusterVariableInput](type-aliases/updateTenantClusterVariableInput.md) - [UpdateTenantClusterVariableResponse](type-aliases/UpdateTenantClusterVariableResponse.md) - [UpdateTenantClusterVariableResponses](type-aliases/UpdateTenantClusterVariableResponses.md) - [UpdateTenantData](type-aliases/UpdateTenantData.md) - [UpdateTenantError](type-aliases/UpdateTenantError.md) - [UpdateTenantErrors](type-aliases/UpdateTenantErrors.md) - [updateTenantInput](type-aliases/updateTenantInput.md) - [UpdateTenantResponse](type-aliases/UpdateTenantResponse.md) - [UpdateTenantResponses](type-aliases/UpdateTenantResponses.md) - [UpdateUserData](type-aliases/UpdateUserData.md) - [UpdateUserError](type-aliases/UpdateUserError.md) - [UpdateUserErrors](type-aliases/UpdateUserErrors.md) - [updateUserInput](type-aliases/updateUserInput.md) - [UpdateUserResponse](type-aliases/UpdateUserResponse.md) - [UpdateUserResponses](type-aliases/UpdateUserResponses.md) - [UpdateUserTaskData](type-aliases/UpdateUserTaskData.md) - [UpdateUserTaskError](type-aliases/UpdateUserTaskError.md) - [UpdateUserTaskErrors](type-aliases/UpdateUserTaskErrors.md) - [updateUserTaskInput](type-aliases/updateUserTaskInput.md) - [UpdateUserTaskResponse](type-aliases/UpdateUserTaskResponse.md) - [UpdateUserTaskResponses](type-aliases/UpdateUserTaskResponses.md) - [UsageMetricsResponse](type-aliases/UsageMetricsResponse.md) - [UsageMetricsResponseItem](type-aliases/UsageMetricsResponseItem.md) - [UserCreateResult](type-aliases/UserCreateResult.md) - [UserFilter](type-aliases/UserFilter.md) - [UserFilterFields](type-aliases/UserFilterFields.md) - [Username](type-aliases/Username.md) - [UserRequest](type-aliases/UserRequest.md) - [UserResult](type-aliases/UserResult.md) - [UserSearchQueryRequest](type-aliases/UserSearchQueryRequest.md) - [UserSearchQuerySortRequest](type-aliases/UserSearchQuerySortRequest.md) - [UserSearchResult](type-aliases/UserSearchResult.md) - [UserTaskAssignmentRequest](type-aliases/UserTaskAssignmentRequest.md) - [UserTaskAuditLogFilter](type-aliases/UserTaskAuditLogFilter.md) - [UserTaskAuditLogSearchQueryRequest](type-aliases/UserTaskAuditLogSearchQueryRequest.md) - [UserTaskCompletionRequest](type-aliases/UserTaskCompletionRequest.md) - [UserTaskEffectiveVariableSearchQueryRequest](type-aliases/UserTaskEffectiveVariableSearchQueryRequest.md) - [UserTaskFilter](type-aliases/UserTaskFilter.md) - [UserTaskFilterFields](type-aliases/UserTaskFilterFields.md) - [UserTaskKey](type-aliases/UserTaskKey.md) - [UserTaskKeyWritable](type-aliases/UserTaskKeyWritable.md) - [UserTaskProperties](type-aliases/UserTaskProperties.md) - [UserTaskResult](type-aliases/UserTaskResult.md) - [UserTaskSearchQuery](type-aliases/UserTaskSearchQuery.md) - [UserTaskSearchQueryResult](type-aliases/UserTaskSearchQueryResult.md) - [UserTaskSearchQuerySortRequest](type-aliases/UserTaskSearchQuerySortRequest.md) - [UserTaskStateEnum](type-aliases/UserTaskStateEnum.md) - [UserTaskStateExactMatch](type-aliases/UserTaskStateExactMatch.md) - [UserTaskStateExactMatchWritable](type-aliases/UserTaskStateExactMatchWritable.md) - [UserTaskStateFilterProperty](type-aliases/UserTaskStateFilterProperty.md) - [UserTaskUpdateRequest](type-aliases/UserTaskUpdateRequest.md) - [UserTaskVariableFilter](type-aliases/UserTaskVariableFilter.md) - [UserTaskVariableSearchQueryRequest](type-aliases/UserTaskVariableSearchQueryRequest.md) - [UserTaskVariableSearchQuerySortRequest](type-aliases/UserTaskVariableSearchQuerySortRequest.md) - [UserTaskWaitStateDetails](type-aliases/UserTaskWaitStateDetails.md) - [UserUpdateRequest](type-aliases/UserUpdateRequest.md) - [UserUpdateResult](type-aliases/UserUpdateResult.md) - [UseSourceParentKeyInstruction](type-aliases/UseSourceParentKeyInstruction.md) - [ValidationMode](type-aliases/ValidationMode.md) - [VariableFilter](type-aliases/VariableFilter.md) - [VariableKey](type-aliases/VariableKey.md) - [VariableKeyExactMatch](type-aliases/VariableKeyExactMatch.md) - [VariableKeyExactMatchWritable](type-aliases/VariableKeyExactMatchWritable.md) - [VariableKeyFilterProperty](type-aliases/VariableKeyFilterProperty.md) - [VariableKeyWritable](type-aliases/VariableKeyWritable.md) - [VariableResult](type-aliases/VariableResult.md) - [VariableResultBase](type-aliases/VariableResultBase.md) - [VariableSearchQuery](type-aliases/VariableSearchQuery.md) - [VariableSearchQueryResult](type-aliases/VariableSearchQueryResult.md) - [VariableSearchQuerySortRequest](type-aliases/VariableSearchQuerySortRequest.md) - [VariableSearchResult](type-aliases/VariableSearchResult.md) - [VariableValueFilterProperty](type-aliases/VariableValueFilterProperty.md) - [WaitStateDetails](type-aliases/WaitStateDetails.md) - [WaitStateElementTypeEnum](type-aliases/WaitStateElementTypeEnum.md) - [WaitStateElementTypeExactMatch](type-aliases/WaitStateElementTypeExactMatch.md) - [WaitStateElementTypeExactMatchWritable](type-aliases/WaitStateElementTypeExactMatchWritable.md) - [WaitStateElementTypeFilterProperty](type-aliases/WaitStateElementTypeFilterProperty.md) - [WaitStateTypeEnum](type-aliases/WaitStateTypeEnum.md) - [WaitStateTypeExactMatch](type-aliases/WaitStateTypeExactMatch.md) - [WaitStateTypeExactMatchWritable](type-aliases/WaitStateTypeExactMatchWritable.md) - [WaitStateTypeFilterProperty](type-aliases/WaitStateTypeFilterProperty.md) - [WebappComponent](type-aliases/WebappComponent.md) - [WithSearchPagination](type-aliases/WithSearchPagination.md) ## Variables - [AgentDefinitionKey](variables/AgentDefinitionKey.md) - [AgentDefinitionTypeEnum](variables/AgentDefinitionTypeEnum.md) - [AgentHistoryItemKey](variables/AgentHistoryItemKey.md) - [AgentInstanceHistoryCommitStatusEnum](variables/AgentInstanceHistoryCommitStatusEnum.md) - [AgentInstanceHistoryRoleEnum](variables/AgentInstanceHistoryRoleEnum.md) - [AgentInstanceKey](variables/AgentInstanceKey.md) - [AgentInstanceMessageContentTypeEnum](variables/AgentInstanceMessageContentTypeEnum.md) - [AgentInstanceStatusEnum](variables/AgentInstanceStatusEnum.md) - [AgentInstanceUpdateStatusEnum](variables/AgentInstanceUpdateStatusEnum.md) - [AuditLogActorTypeEnum](variables/AuditLogActorTypeEnum.md) - [AuditLogCategoryEnum](variables/AuditLogCategoryEnum.md) - [AuditLogEntityKey](variables/AuditLogEntityKey.md) - [AuditLogEntityTypeEnum](variables/AuditLogEntityTypeEnum.md) - [AuditLogKey](variables/AuditLogKey.md) - [AuditLogOperationTypeEnum](variables/AuditLogOperationTypeEnum.md) - [AuditLogResultEnum](variables/AuditLogResultEnum.md) - [AuthorizationKey](variables/AuthorizationKey.md) - [BatchOperationItemStateEnum](variables/BatchOperationItemStateEnum.md) - [BatchOperationKey](variables/BatchOperationKey.md) - [BatchOperationStateEnum](variables/BatchOperationStateEnum.md) - [BatchOperationTypeEnum](variables/BatchOperationTypeEnum.md) - [BusinessId](variables/BusinessId.md) - [CamundaClient](variables/CamundaClient.md) - [ClientId](variables/ClientId.md) - [ClusterVariableKindEnum](variables/ClusterVariableKindEnum.md) - [ClusterVariableName](variables/ClusterVariableName.md) - [ClusterVariableScopeEnum](variables/ClusterVariableScopeEnum.md) - [ConditionalEvaluationKey](variables/ConditionalEvaluationKey.md) - [DecisionDefinitionId](variables/DecisionDefinitionId.md) - [DecisionDefinitionKey](variables/DecisionDefinitionKey.md) - [DecisionDefinitionTypeEnum](variables/DecisionDefinitionTypeEnum.md) - [DecisionEvaluationInstanceKey](variables/DecisionEvaluationInstanceKey.md) - [DecisionEvaluationKey](variables/DecisionEvaluationKey.md) - [DecisionInstanceKey](variables/DecisionInstanceKey.md) - [DecisionInstanceStateEnum](variables/DecisionInstanceStateEnum.md) - [DecisionRequirementsKey](variables/DecisionRequirementsKey.md) - [DeploymentKey](variables/DeploymentKey.md) - [DocumentId](variables/DocumentId.md) - [ElementId](variables/ElementId.md) - [ElementInstanceKey](variables/ElementInstanceKey.md) - [ElementInstanceStateEnum](variables/ElementInstanceStateEnum.md) - [EndCursor](variables/EndCursor.md) - [FormId](variables/FormId.md) - [FormKey](variables/FormKey.md) - [GlobalListenerId](variables/GlobalListenerId.md) - [GlobalListenerSourceEnum](variables/GlobalListenerSourceEnum.md) - [GlobalTaskListenerEventTypeEnum](variables/GlobalTaskListenerEventTypeEnum.md) - [GroupId](variables/GroupId.md) - [HistoryItemId](variables/HistoryItemId.md) - [IncidentErrorTypeEnum](variables/IncidentErrorTypeEnum.md) - [IncidentKey](variables/IncidentKey.md) - [IncidentStateEnum](variables/IncidentStateEnum.md) - [JobKey](variables/JobKey.md) - [JobKindEnum](variables/JobKindEnum.md) - [JobLeaseToken](variables/JobLeaseToken.md) - [JobListenerEventTypeEnum](variables/JobListenerEventTypeEnum.md) - [JobStateEnum](variables/JobStateEnum.md) - [liveClock](variables/liveClock.md) - [liveRandom](variables/liveRandom.md) - [MappingRuleId](variables/MappingRuleId.md) - [MessageKey](variables/MessageKey.md) - [MessageSubscriptionKey](variables/MessageSubscriptionKey.md) - [MessageSubscriptionStateEnum](variables/MessageSubscriptionStateEnum.md) - [MessageSubscriptionTypeEnum](variables/MessageSubscriptionTypeEnum.md) - [OwnerTypeEnum](variables/OwnerTypeEnum.md) - [PermissionTypeEnum](variables/PermissionTypeEnum.md) - [ProcessDefinitionId](variables/ProcessDefinitionId.md) - [ProcessDefinitionKey](variables/ProcessDefinitionKey.md) - [ProcessInstanceKey](variables/ProcessInstanceKey.md) - [ProcessInstanceStateEnum](variables/ProcessInstanceStateEnum.md) - [ResourceTypeEnum](variables/ResourceTypeEnum.md) - [RoleId](variables/RoleId.md) - [SEED\_ENV\_VAR](variables/SEED_ENV_VAR.md) - [SignalKey](variables/SignalKey.md) - [SortOrderEnum](variables/SortOrderEnum.md) - [SPEC\_HASH](variables/SPEC_HASH.md) - [StartCursor](variables/StartCursor.md) - [Tag](variables/Tag.md) - [TenantFilterEnum](variables/TenantFilterEnum.md) - [TenantId](variables/TenantId.md) - [Username](variables/Username.md) - [UserTaskKey](variables/UserTaskKey.md) - [UserTaskStateEnum](variables/UserTaskStateEnum.md) - [VariableKey](variables/VariableKey.md) - [WaitStateElementTypeEnum](variables/WaitStateElementTypeEnum.md) - [WaitStateTypeEnum](variables/WaitStateTypeEnum.md) ## Functions - [assertConstraint](functions/assertConstraint.md) - [collectTypedVariables](functions/collectTypedVariables.md) - [createCamundaClient](functions/createCamundaClient.md) - [createCamundaClientLoose](functions/createCamundaClientLoose.md) - [createCamundaResultClient](functions/createCamundaResultClient.md) - [createEngineClock](functions/createEngineClock.md) - [createLiveClock](functions/createLiveClock.md) - [createSeededRandom](functions/createSeededRandom.md) - [createTestClock](functions/createTestClock.md) - [isErr](functions/isErr.md) - [isOk](functions/isOk.md) - [isPresentWhenUnsupportedError](functions/isPresentWhenUnsupportedError.md) - [isSdkError](functions/isSdkError.md) - [nextPageRequest](functions/nextPageRequest.md) - [paginate](functions/paginate.md) - [seededRandomFromEnv](functions/seededRandomFromEnv.md) - [variableNamesFromSchema](functions/variableNamesFromSchema.md) ## References ### default Renames and re-exports [createCamundaClient](functions/createCamundaClient.md) --- ### JobActionReceiptSymbol Renames and re-exports [JobActionReceipt](type-aliases/JobActionReceipt.md) --- ## Interface: CamundaConfig ## Properties ### \_\_raw ```ts __raw: Record; ``` --- ### auth ```ts auth: object; ``` #### basic? ```ts optional basic?: object; ``` ##### basic.password? ```ts optional password?: string; ``` ##### basic.username? ```ts optional username?: string; ``` #### strategy ```ts strategy: AuthStrategy; ``` --- ### backpressure ```ts backpressure: object; ``` #### decayQuietMs ```ts decayQuietMs: number; ``` #### enabled ```ts enabled: boolean; ``` #### floor ```ts floor: number; ``` #### healthyRecoveryMultiplier ```ts healthyRecoveryMultiplier: number; ``` #### initialMax ```ts initialMax: number; ``` #### maxWaiters ```ts maxWaiters: number; ``` #### observeOnly ```ts observeOnly: boolean; ``` #### profile ```ts profile: string; ``` #### recoveryIntervalMs ```ts recoveryIntervalMs: number; ``` #### recoveryStep ```ts recoveryStep: number; ``` #### severeFactor ```ts severeFactor: number; ``` #### severeThreshold ```ts severeThreshold: number; ``` #### softFactor ```ts softFactor: number; ``` #### unlimitedAfterHealthyMs ```ts unlimitedAfterHealthyMs: number; ``` --- ### defaultTenantId ```ts defaultTenantId: string; ``` --- ### eventual? ```ts optional eventual?: object; ``` #### pollDefaultMs ```ts pollDefaultMs: number; ``` --- ### httpRetry ```ts httpRetry: object; ``` #### baseDelayMs ```ts baseDelayMs: number; ``` #### maxAttempts ```ts maxAttempts: number; ``` #### maxDelayMs ```ts maxDelayMs: number; ``` --- ### logLevel ```ts logLevel: "trace" | "error" | "silent" | "warn" | "info" | "debug"; ``` --- ### mtls? ```ts optional mtls?: object; ``` #### ca? ```ts optional ca?: string; ``` #### caPath? ```ts optional caPath?: string; ``` #### cert? ```ts optional cert?: string; ``` #### certPath? ```ts optional certPath?: string; ``` #### key? ```ts optional key?: string; ``` #### keyPassphrase? ```ts optional keyPassphrase?: string; ``` #### keyPath? ```ts optional keyPath?: string; ``` --- ### oauth ```ts oauth: object; ``` #### cacheDir? ```ts optional cacheDir?: string; ``` #### clientId? ```ts optional clientId?: string; ``` #### clientSecret? ```ts optional clientSecret?: string; ``` #### grantType ```ts grantType: string; ``` #### oauthUrl ```ts oauthUrl: string; ``` #### retry ```ts retry: object; ``` ##### retry.baseDelayMs ```ts baseDelayMs: number; ``` ##### retry.max ```ts max: number; ``` #### scope? ```ts optional scope?: string; ``` #### timeoutMs ```ts timeoutMs: number; ``` --- ### restAddress ```ts restAddress: string; ``` --- ### supportLog? ```ts optional supportLog?: object; ``` #### enabled ```ts enabled: boolean; ``` #### filePath ```ts filePath: string; ``` --- ### telemetry? ```ts optional telemetry?: object; ``` #### correlation ```ts correlation: boolean; ``` #### log ```ts log: boolean; ``` --- ### tokenAudience ```ts tokenAudience: string; ``` --- ### validation ```ts validation: object; ``` #### raw ```ts raw: string; ``` #### req ```ts req: ValidationMode; ``` #### res ```ts res: ValidationMode; ``` --- ### workerDefaults? ```ts optional workerDefaults?: object; ``` #### jobTimeoutMs? ```ts optional jobTimeoutMs?: number; ``` #### maxParallelJobs? ```ts optional maxParallelJobs?: number; ``` #### pollTimeoutMs? ```ts optional pollTimeoutMs?: number; ``` #### startupJitterMaxSeconds? ```ts optional startupJitterMaxSeconds?: number; ``` #### workerName? ```ts optional workerName?: string; ``` --- ## Interface: CamundaOptions ## Properties ### \_\_camundaComponent? ```ts optional __camundaComponent?: "CamundaCore" | "CamundaClient"; ``` **`Internal`** Explicit component discriminator for support diagnostics. Set only by SDK-internal subclasses: `CamundaClientBase` passes `'CamundaClient'` so the construction log names the real component even when a consumer subclasses the public `CamundaCore` (where `new.target !== CamundaCore` alone could not distinguish core-subclass from client). --- ### clock? ```ts optional clock?: Clock; ``` --- ### config? ```ts optional config?: Partial<{ CAMUNDA_AUTH_STRATEGY: "OAUTH" | "NONE" | "BASIC"; CAMUNDA_BASIC_AUTH_PASSWORD: string; CAMUNDA_BASIC_AUTH_USERNAME: string; CAMUNDA_CLIENT_ID: string; CAMUNDA_CLIENT_SECRET: string; CAMUNDA_DEFAULT_TENANT_ID: string; CAMUNDA_MTLS_CA: string; CAMUNDA_MTLS_CA_PATH: string; CAMUNDA_MTLS_CERT: string; CAMUNDA_MTLS_CERT_PATH: string; CAMUNDA_MTLS_KEY: string; CAMUNDA_MTLS_KEY_PASSPHRASE: string; CAMUNDA_MTLS_KEY_PATH: string; CAMUNDA_OAUTH_CACHE_DIR: string; CAMUNDA_OAUTH_GRANT_TYPE: string; CAMUNDA_OAUTH_RETRY_BASE_DELAY_MS: number; CAMUNDA_OAUTH_RETRY_MAX: number; CAMUNDA_OAUTH_SCOPE: string; CAMUNDA_OAUTH_TIMEOUT_MS: number; CAMUNDA_OAUTH_URL: string; CAMUNDA_REST_ADDRESS: string; CAMUNDA_REST_ADDRESS_EXACT: boolean; CAMUNDA_SDK_BACKPRESSURE_DECAY_QUIET_MS: number; CAMUNDA_SDK_BACKPRESSURE_FLOOR: number; CAMUNDA_SDK_BACKPRESSURE_HEALTHY_RECOVERY_MULTIPLIER: number; CAMUNDA_SDK_BACKPRESSURE_INITIAL_MAX: number; CAMUNDA_SDK_BACKPRESSURE_MAX_WAITERS: number; CAMUNDA_SDK_BACKPRESSURE_PROFILE: "BALANCED" | "CONSERVATIVE" | "AGGRESSIVE" | "LEGACY"; CAMUNDA_SDK_BACKPRESSURE_RECOVERY_INTERVAL_MS: number; CAMUNDA_SDK_BACKPRESSURE_RECOVERY_STEP: number; CAMUNDA_SDK_BACKPRESSURE_SEVERE_FACTOR: number; CAMUNDA_SDK_BACKPRESSURE_SEVERE_THRESHOLD: number; CAMUNDA_SDK_BACKPRESSURE_SOFT_FACTOR: number; CAMUNDA_SDK_BACKPRESSURE_UNLIMITED_AFTER_HEALTHY_MS: number; CAMUNDA_SDK_EVENTUAL_POLL_DEFAULT_MS: number; CAMUNDA_SDK_HTTP_RETRY_BASE_DELAY_MS: number; CAMUNDA_SDK_HTTP_RETRY_MAX_ATTEMPTS: number; CAMUNDA_SDK_HTTP_RETRY_MAX_DELAY_MS: number; CAMUNDA_SDK_LOG_LEVEL: "trace" | "error" | "silent" | "warn" | "info" | "debug" | "silly"; CAMUNDA_SDK_TELEMETRY_CORRELATION: boolean; CAMUNDA_SDK_TELEMETRY_LOG: boolean; CAMUNDA_SDK_VALIDATION: string; CAMUNDA_SUPPORT_LOG_ENABLED: boolean; CAMUNDA_SUPPORT_LOG_FILE_PATH: string; CAMUNDA_SUPPORT_LOGGER: boolean; CAMUNDA_TOKEN_AUDIENCE: string; CAMUNDA_WORKER_MAX_CONCURRENT_JOBS: number; CAMUNDA_WORKER_NAME: string; CAMUNDA_WORKER_REQUEST_TIMEOUT: number; CAMUNDA_WORKER_STARTUP_JITTER_MAX_SECONDS: number; CAMUNDA_WORKER_TIMEOUT: number; }>; ``` --- ### env? ```ts optional env?: Record; ``` --- ### fetch? ```ts optional fetch?: (input, init?) => Promise; ``` #### Parameters ##### input `RequestInfo` \| `URL` ##### init? `RequestInit` #### Returns `Promise`\<`Response`\> --- ### log? ```ts optional log?: object; ``` #### level? ```ts optional level?: LogLevel; ``` #### transport? ```ts optional transport?: LogTransport; ``` --- ### random? ```ts optional random?: RandomSource; ``` --- ### supportLogger? ```ts optional supportLogger?: SupportLogger; ``` --- ### telemetry? ```ts optional telemetry?: object; ``` #### correlation? ```ts optional correlation?: boolean; ``` #### hooks? ```ts optional hooks?: TelemetryHooks; ``` #### mirrorToLog? ```ts optional mirrorToLog?: boolean; ``` --- ### throwOnError? ```ts optional throwOnError?: boolean; ``` --- ## Interface: CancelablePromise # Interface: CancelablePromise\ ## Extends - `Promise`\<`T`\> ## Type Parameters ### T `T` ## Methods ### cancel() ```ts cancel(): void; ``` #### Returns `void` --- ## Interface: Clock The clock all SDK runtime cadence resolves through — worker poll loops, eventual consistency polling, retry backoff, backpressure decay and auth refresh. Pinning this pins the client's own timing, which is what makes those loops testable without waiting for real time. See the cross-SDK contract in camunda/orchestration-cluster-api-js#450. Generalises the two seams that already existed: `CollectClock` in `typedVariables.ts` and the injected `now`/`sleep` on `BackpressureManager`. ## Extended by - [`EngineClock`](EngineClock.md) - [`TestClock`](TestClock.md) ## Methods ### deadline() ```ts deadline(ms): object; ``` A signal that aborts once `ms` have elapsed on this clock. `dispose()` releases the underlying timer; call it when the guarded work finishes early, or a long deadline keeps a handle alive for its full duration. #### Parameters ##### ms `number` #### Returns `object` ##### dispose ```ts dispose: () => void; ``` ###### Returns `void` ##### signal ```ts signal: AbortSignal; ``` --- ### now() ```ts now(): number; ``` Current wall-clock time in epoch milliseconds. #### Returns `number` --- ### sleep() ```ts sleep(ms, signal?): Promise; ``` Resolve after `ms` have elapsed on this clock. Rejects with the signal's reason if `signal` aborts first, so a caller can cancel a wait without leaving the timer behind. An injected implementation must not resolve synchronously. The worker schedules its next poll by awaiting this, so a sleep that settles in a microtask turns the poll loop into an unbounded spin that starves the event loop and exhausts the heap. A test clock should resolve only when the test advances it. #### Parameters ##### ms `number` ##### signal? `AbortSignal` #### Returns `Promise`\<`void`\> --- ## Interface: CreateLoggerOptions ## Properties ### level? ```ts optional level?: LogLevel; ``` --- ### scope? ```ts optional scope?: string; ``` --- ### transport? ```ts optional transport?: LogTransport; ``` --- ## Interface: EngineClock The clock all SDK runtime cadence resolves through — worker poll loops, eventual consistency polling, retry backoff, backpressure decay and auth refresh. Pinning this pins the client's own timing, which is what makes those loops testable without waiting for real time. See the cross-SDK contract in camunda/orchestration-cluster-api-js#450. Generalises the two seams that already existed: `CollectClock` in `typedVariables.ts` and the injected `now`/`sleep` on `BackpressureManager`. ## Extends - [`Clock`](Clock.md) ## Properties ### sleeps ```ts readonly sleeps: readonly number[]; ``` Durations passed to `sleep`, in call order. ## Methods ### deadline() ```ts deadline(ms): object; ``` A signal that aborts once `ms` have elapsed on this clock. `dispose()` releases the underlying timer; call it when the guarded work finishes early, or a long deadline keeps a handle alive for its full duration. #### Parameters ##### ms `number` #### Returns `object` ##### dispose ```ts dispose: () => void; ``` ###### Returns `void` ##### signal ```ts signal: AbortSignal; ``` #### Inherited from [`Clock`](Clock.md).[`deadline`](Clock.md#deadline) --- ### now() ```ts now(): number; ``` Current wall-clock time in epoch milliseconds. #### Returns `number` #### Inherited from [`Clock`](Clock.md).[`now`](Clock.md#now) --- ### pin() ```ts pin(epochMs): Promise; ``` Pin the engine to an absolute instant and adopt it as this clock's reading. #### Parameters ##### epochMs `number` #### Returns `Promise`\<`void`\> --- ### reset() ```ts reset(): Promise; ``` Hand the engine back to real time. The local reading stays where it was. #### Returns `Promise`\<`void`\> --- ### sleep() ```ts sleep(ms, signal?): Promise; ``` Resolve after `ms` have elapsed on this clock. Rejects with the signal's reason if `signal` aborts first, so a caller can cancel a wait without leaving the timer behind. An injected implementation must not resolve synchronously. The worker schedules its next poll by awaiting this, so a sleep that settles in a microtask turns the poll loop into an unbounded spin that starves the event loop and exhausts the heap. A test clock should resolve only when the test advances it. #### Parameters ##### ms `number` ##### signal? `AbortSignal` #### Returns `Promise`\<`void`\> #### Inherited from [`Clock`](Clock.md).[`sleep`](Clock.md#sleep) --- ## Interface: EngineClockTarget The two engine clock operations this clock drives. Declared structurally so this module stays independent of the generated client; `CamundaClient` satisfies it as-is. ## Methods ### pinClock() ```ts pinClock(input): PromiseLike; ``` #### Parameters ##### input ###### timestamp `number` #### Returns `PromiseLike`\<`unknown`\> --- ### resetClock() ```ts resetClock(): PromiseLike; ``` #### Returns `PromiseLike`\<`unknown`\> --- ## Interface: EnrichedActivatedJob Enriched job type with convenience methods. A back-compatible **interface** so downstream consumers can declaration-merge or `extends` it exactly as they could before the dependent-presence typing work landed (restoring the pre-#513 public surface — turning it into a generic type alias was a breaking change for those consumers). It resolves to the base `ActivatedJobResult` shape plus the action methods. The marker-driven dependent-presence projections (see `hooks/post/710-derive-present-when.ts`) narrow individual response properties via the generic [EnrichedActivatedJobOf](../type-aliases/EnrichedActivatedJobOf.md) companion instead of re-parameterising this interface — an interface cannot intersect an arbitrary type parameter, and keeping this name non-generic preserves back-compat. ## Extends - `EnrichedActivatedJobActions` - `ActivatedJobResult` ## Properties ### acknowledged? ```ts optional acknowledged?: boolean; ``` Set true once any acknowledgement method is invoked. #### Inherited from ```ts EnrichedActivatedJobActions.acknowledged; ``` --- ### businessId ```ts businessId: BusinessId | null; ``` The business ID of the owning process instance, inherited when the job was created. This is `null` for jobs created before version 8.10 and for jobs whose owning process instance has no business ID. --- ### clock ```ts clock: HandlerClock; ``` The clock this worker's client resolves time through. Reading and waiting through it means a test that pins the client's clock also drives the handler. #### Inherited from ```ts EnrichedActivatedJobActions.clock; ``` --- ### customHeaders ```ts customHeaders: object; ``` A set of custom headers defined during modelling; returned as a serialized JSON document. #### Index Signature ```ts [key: string]: unknown ``` --- ### deadline ```ts deadline: number; ``` When the job can be activated again, sent as a UNIX epoch timestamp. --- ### elementId ```ts elementId: ElementId; ``` The associated task element ID. --- ### elementInstanceKey ```ts elementInstanceKey: ElementInstanceKey; ``` The element instance key of the task. --- ### jobKey ```ts jobKey: JobKey; ``` The key, a unique identifier for the job. --- ### jobLeaseToken ```ts jobLeaseToken: JobLeaseToken | null; ``` The lease token identifying this activation. This is `null` when the job was activated without a lease. --- ### kind ```ts kind: JobKindEnum; ``` --- ### listenerEventType ```ts listenerEventType: JobListenerEventTypeEnum; ``` --- ### log ```ts log: Logger; ``` #### Inherited from ```ts EnrichedActivatedJobActions.log; ``` --- ### modifyJobTimeout ```ts modifyJobTimeout: (__namedParameters) => Promise; ``` Extend the timeout for the job by setting a new timeout #### Parameters ##### \_\_namedParameters ###### newTimeoutMs `number` #### Returns `Promise`\<`void`\> #### Inherited from ```ts EnrichedActivatedJobActions.modifyJobTimeout; ``` --- ### modifyRetries ```ts modifyRetries: (__namedParameters) => Promise; ``` #### Parameters ##### \_\_namedParameters ###### retries `number` #### Returns `Promise`\<`void`\> #### Inherited from ```ts EnrichedActivatedJobActions.modifyRetries; ``` --- ### physicalTenantId ```ts physicalTenantId: string; ``` The ID of the physical tenant that the job-activation request was routed to; the default physical tenant when the request did not specify one. --- ### priority ```ts priority: number; ``` The priority of the job. Higher values indicate higher priority. Jobs created before 8.10 have no stored priority; the API returns 0 for such jobs. --- ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId; ``` The bpmn process ID of the job's process definition. --- ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey; ``` The key of the job's process definition. --- ### processDefinitionVersion ```ts processDefinitionVersion: number; ``` The version of the job's process definition. --- ### processInstanceKey ```ts processInstanceKey: ProcessInstanceKey; ``` The job's process instance key. --- ### retries ```ts retries: number; ``` The amount of retries left to this job (should always be positive). --- ### rootProcessInstanceKey ```ts rootProcessInstanceKey: ProcessInstanceKey | null; ``` The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. --- ### tags ```ts tags: TagSet; ``` --- ### tenantId ```ts tenantId: TenantId; ``` The ID of the tenant that owns the job. --- ### type ```ts type: string; ``` The type of the job (should match what was requested). --- ### userTask ```ts userTask: UserTaskProperties | null; ``` User task properties, if the job is a user task. This is `null` if the job is not a user task. --- ### variables ```ts variables: object; ``` All variables visible to the task scope, computed at activation time. #### Index Signature ```ts [key: string]: unknown ``` --- ### worker ```ts worker: string; ``` The name of the worker which activated this job. ## Methods ### cancelWorkflow() ```ts cancelWorkflow(): Promise<"JOB_ACTION_RECEIPT">; ``` #### Returns `Promise`\<`"JOB_ACTION_RECEIPT"`\> #### Inherited from ```ts EnrichedActivatedJobActions.cancelWorkflow; ``` --- ### complete() ```ts complete(variables?, result?): Promise<"JOB_ACTION_RECEIPT">; ``` #### Parameters ##### variables? \{ \[`k`: `string`\]: `any`; \} ##### result? [`JobResult`](../type-aliases/JobResult.md) #### Returns `Promise`\<`"JOB_ACTION_RECEIPT"`\> #### Inherited from ```ts EnrichedActivatedJobActions.complete; ``` --- ### error() ```ts error(error): Promise<"JOB_ACTION_RECEIPT">; ``` #### Parameters ##### error [`JobErrorRequest`](../type-aliases/JobErrorRequest.md) #### Returns `Promise`\<`"JOB_ACTION_RECEIPT"`\> #### Inherited from ```ts EnrichedActivatedJobActions.error; ``` --- ### fail() ```ts fail(body): Promise<"JOB_ACTION_RECEIPT">; ``` #### Parameters ##### body `any` #### Returns `Promise`\<`"JOB_ACTION_RECEIPT"`\> #### Inherited from ```ts EnrichedActivatedJobActions.fail; ``` --- ### ignore() ```ts ignore(): Promise<"JOB_ACTION_RECEIPT">; ``` #### Returns `Promise`\<`"JOB_ACTION_RECEIPT"`\> #### Inherited from ```ts EnrichedActivatedJobActions.ignore; ``` --- ## Interface: ExtendedDeploymentResult Extended deployment result with typed buckets for direct access to deployed artifacts. ## Extends - `_DataOf`\<_typeof_ `Sdk.createDeployment`\> ## Properties ### decisionRequirements ```ts decisionRequirements: DeploymentDecisionRequirementsResult[]; ``` --- ### decisions ```ts decisions: DeploymentDecisionResult[]; ``` --- ### deploymentKey ```ts deploymentKey: DeploymentKey; ``` The unique key identifying the deployment. #### Inherited from ```ts _DataOf.deploymentKey; ``` --- ### deployments ```ts deployments: DeploymentMetadataResult[]; ``` Items deployed by the request. #### Inherited from ```ts _DataOf.deployments; ``` --- ### forms ```ts forms: DeploymentFormResult[]; ``` --- ### processes ```ts processes: DeploymentProcessResult[]; ``` --- ### resources ```ts resources: DeploymentResourceResult[]; ``` --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID associated with the deployment. #### Inherited from ```ts _DataOf.tenantId; ``` --- ## Interface: HttpRetryPolicy ## Properties ### baseDelayMs ```ts baseDelayMs: number; ``` --- ### maxAttempts ```ts maxAttempts: number; ``` --- ### maxDelayMs ```ts maxDelayMs: number; ``` --- ## Interface: JobWorker ## Accessors ### activeJobs #### Get Signature ```ts get activeJobs(): number; ``` ##### Returns `number` --- ### name #### Get Signature ```ts get name(): string; ``` ##### Returns `string` --- ### stopped #### Get Signature ```ts get stopped(): boolean; ``` ##### Returns `boolean` ## Methods ### start() ```ts start(): void; ``` Begin polling for jobs. Safe to call at any point: the request is buffered until the transport is ready, and redundant calls (including an explicit call on an `autoStart` worker) are dropped rather than starting a second poll loop. Once the worker is stopped, `start()` is a no-op. #### Returns `void` --- ### stop() ```ts stop(): void; ``` #### Returns `void` --- ### stopGracefully() ```ts stopGracefully(opts?): Promise; ``` Gracefully stop the worker: prevent new polls, allow any in-flight activation to finish without cancellation, and wait for currently active jobs to drain (be acknowledged) up to waitUpToMs. If timeout is reached, falls back to hard stop logic (cancels activation if still pending). #### Parameters ##### opts? ###### checkIntervalMs? `number` ###### waitUpToMs? `number` #### Returns `Promise`\<`GracefulStopResult`\> --- ## Interface: JobWorkerConfig # Interface: JobWorkerConfig\ ## Type Parameters ### In `In` _extends_ `z.ZodTypeAny` = `any` ### Out `Out` _extends_ `z.ZodTypeAny` = `any` ### Headers `Headers` _extends_ `z.ZodTypeAny` = `any` ## Properties ### autoStart? ```ts optional autoStart?: boolean; ``` Immediately start polling for work - default `true` --- ### customHeadersSchema? ```ts optional customHeadersSchema?: Headers; ``` Zod schema for custom headers in the activated job --- ### fetchVariables? ```ts optional fetchVariables?: In extends ZodTypeAny> ? Extract, string>[] : string[]; ``` Optional list of variable names to fetch during activation --- ### inputSchema? ```ts optional inputSchema?: In; ``` Zod schema for variables in the activated job --- ### jobHandler ```ts jobHandler: (job) => "JOB_ACTION_RECEIPT" | Promise<"JOB_ACTION_RECEIPT">; ``` #### Parameters ##### job [`Job`](../type-aliases/Job.md)\<`In`, `Headers`\> #### Returns `"JOB_ACTION_RECEIPT"` \| `Promise`\<`"JOB_ACTION_RECEIPT"`\> --- ### jobTimeoutMs? ```ts optional jobTimeoutMs?: number; ``` Job activation timeout in ms — default `60000`. Overridden by CAMUNDA_WORKER_TIMEOUT env var. --- ### jobType ```ts jobType: string; ``` Zeebe job type --- ### ~~maxBackoffTimeMs?~~ ```ts optional maxBackoffTimeMs?: number; ``` #### Deprecated Not used; pacing handled by long polling + client backpressure. Present only for migration compatibility. --- ### maxParallelJobs? ```ts optional maxParallelJobs?: number; ``` Concurrency limit — default `10`. Overridden by CAMUNDA_WORKER_MAX_CONCURRENT_JOBS env var. --- ### outputSchema? ```ts optional outputSchema?: Out; ``` Zod schema for variables in the complete command --- ### pollBackoffMaxMs? ```ts optional pollBackoffMaxMs?: number; ``` Ceiling (ms) of the between-failed-poll exponential backoff. Default `30000`. --- ### pollBackoffMinMs? ```ts optional pollBackoffMinMs?: number; ``` Floor (ms) of the exponential backoff applied between _failed_ activation requests (connection refused, connect timeout, broker restart, LAN blip …). The first retry waits ≈ this/2–this ms; subsequent consecutive failures double the window up to [pollBackoffMaxMs](#pollbackoffmaxms), with jitter, and reset to zero on the first successful poll. Default `1000`. Set to `0` to disable backoff (failed polls then fall back to [pollIntervalMs](#pollintervalms) rather than retrying immediately). --- ### pollIntervalMs? ```ts optional pollIntervalMs?: number; ``` Backoff between polls - default 1ms --- ### pollTimeoutMs? ```ts optional pollTimeoutMs?: number; ``` The request will be completed when at least one job is activated or after the requestTimeout. If the requestTimeout = 0, the request will be completed after a default configured timeout in the broker. To immediately complete the request when no job is activated set the requestTimeout to a negative value --- ### startupJitterMaxSeconds? ```ts optional startupJitterMaxSeconds?: number; ``` Maximum random delay (in seconds) before the worker starts polling. When multiple application instances restart simultaneously, this spreads out initial activation requests to avoid saturating the server. `0` (the default) means no delay. --- ### validateSchemas? ```ts optional validateSchemas?: boolean; ``` Validate any provided input, output, customheader schema default: false --- ### withLease? ```ts optional withLease?: boolean; ``` Activate jobs with a lease — default `false`. When `true`, each activated job is assigned a distinct, opaque lease token (`ActivatedJobResult.jobLeaseToken`) that is automatically threaded back into the fenced `complete` / `fail` / `error` commands. The lease fences those commands against a superseded activation of the same job (e.g. after a timeout and re-activation by another worker): a command carrying a stale token is rejected rather than racing the newer activation. Once a job type is leased, it is served only to leasing workers of that type, so a homogeneous fleet per job type is recommended. Note: the marker-derived non-null `jobLeaseToken` projection applies to the direct `activateJobs` client call; a worker `jobHandler` intentionally keeps the base `Job<...>` shape (`jobLeaseToken` remains optional/nullable) because the token is threaded back into fenced commands automatically — handlers do not need to read it. --- ### workerName? ```ts optional workerName?: string; ``` Optional explicit name --- ## Interface: OperationOptions Per-call options for individual SDK method invocations. ## Properties ### retry? ```ts optional retry?: false | Partial; ``` Override retry behaviour for this call. - Pass `false` to disable retry entirely (single attempt). - Pass a partial policy to override specific fields (merged with global config). --- ## Interface: PaginateOptions ## Extended by - [`SearchPaginateOptions`](SearchPaginateOptions.md) ## Properties ### maxPages? ```ts optional maxPages?: number; ``` Safety cap on pages fetched (default: unbounded). A non-positive value (`0` or negative) fetches no pages at all — the cap is enforced _before_ the first request, so it is always honoured exactly. --- ### mode? ```ts optional mode?: PaginationMode; ``` How to advance. `auto` prefers a cursor, falls back to offset. --- ### signal? ```ts optional signal?: AbortSignal; ``` Abort between pages; a fired signal stops further fetches. --- ## Interface: Paginator # Interface: Paginator\ ## Extends - `AsyncIterable`\<[`SearchResponse`](SearchResponse.md)\<`TItem`\>\> ## Type Parameters ### TItem `TItem` ## Methods ### items() ```ts items(): AsyncGenerator; ``` Yields individual items across all pages (`yield*`-flattened). #### Returns `AsyncGenerator`\<`TItem`, `void`, `void`\> --- ### pages() ```ts pages(): AsyncGenerator, void, void>; ``` Yields whole pages, fetching each lazily as it is consumed. #### Returns `AsyncGenerator`\<[`SearchResponse`](SearchResponse.md)\<`TItem`\>, `void`, `void`\> --- ### toArray() ```ts toArray(): Promise; ``` Eagerly drains every item into an array. Bounded result sets only. #### Returns `Promise`\<`TItem`[]\> --- ## Interface: PresentWhenUnsupportedError Raised by a generated dependent-presence (`x-present-when`) guard when the server returns a response shape that predates the feature — e.g. a lease token was requested but the payload cannot support the derived presence contract. It is **terminal, not transient**: retrying the same request against the same server can never satisfy the contract, so it is marked `nonRetryable` and a job worker stops rather than looping on backoff. ## Extends - `Error` ## Properties ### cause? ```ts optional cause?: unknown; ``` #### Overrides ```ts Error.cause; ``` --- ### name ```ts name: "PresentWhenUnsupportedError"; ``` #### Overrides ```ts Error.name; ``` --- ### nonRetryable ```ts nonRetryable: true; ``` Always `true` — retrying cannot change an unsupported server response shape. --- ### operationId? ```ts optional operationId?: string; ``` --- ## Interface: RandomSource The randomness all SDK runtime jitter resolves through — HTTP retry backoff, OAuth retry backoff, eventual-consistency 429 backoff, activation-retry backoff and worker startup staggering. The randomness counterpart of `Clock`: pinning the clock makes cadence virtual, injecting a [SeededRandom](SeededRandom.md) as well makes it reproducible. See the cross-SDK contract in camunda/sdk-infra#50. ## Extended by - [`SeededRandom`](SeededRandom.md) ## Methods ### next() ```ts next(): number; ``` A uniformly distributed value in `[0, 1)`. #### Returns `number` --- ## Interface: SearchPageRequest The `page` field of a search request body (cursor-forward or offset). ## Indexable ```ts [k: string]: unknown ``` ## Properties ### after? ```ts optional after?: string; ``` Cursor-forward: `endCursor` of the previous page. --- ### before? ```ts optional before?: string; ``` Cursor-backward: `startCursor` of the previous page (mutually exclusive with `after`/`from`). --- ### from? ```ts optional from?: number; ``` Offset: index to start from. --- ### limit? ```ts optional limit?: number; ``` --- ## Interface: SearchPageResponse The `page` object every search response carries (structural subset). ## Properties ### endCursor ```ts endCursor: string | null; ``` --- ### hasMoreTotalItems ```ts hasMoreTotalItems: boolean; ``` --- ### startCursor ```ts startCursor: string | null; ``` --- ### totalItems ```ts totalItems: number; ``` --- ## Interface: SearchPaginateOptions # Interface: SearchPaginateOptions\ Options for `search*.paginate(...)`: page controls + per-call forwarding. ## Extends - [`PaginateOptions`](PaginateOptions.md) ## Type Parameters ### TData `TData` ## Properties ### consistency? ```ts optional consistency?: ConsistencyOptions; ``` Eventual-consistency controls forwarded to the underlying search call. Defaults to `{ waitUpToMs: 0 }` (ignore eventual consistency) when omitted. Only the **first** page honours this window: once paging is under way an empty page is a legitimate end-of-results, not a not-yet-consistent read, so subsequent (and exhaustion-probe) fetches always use `{ waitUpToMs: 0 }`. Waiting on those would make the terminal fetch of an exactly-full or empty result set block for the whole window and then throw. --- ### maxPages? ```ts optional maxPages?: number; ``` Safety cap on pages fetched (default: unbounded). A non-positive value (`0` or negative) fetches no pages at all — the cap is enforced _before_ the first request, so it is always honoured exactly. #### Inherited from [`PaginateOptions`](PaginateOptions.md).[`maxPages`](PaginateOptions.md#maxpages) --- ### mode? ```ts optional mode?: PaginationMode; ``` How to advance. `auto` prefers a cursor, falls back to offset. #### Inherited from [`PaginateOptions`](PaginateOptions.md).[`mode`](PaginateOptions.md#mode) --- ### signal? ```ts optional signal?: AbortSignal; ``` Abort between pages; a fired signal stops further fetches. #### Inherited from [`PaginateOptions`](PaginateOptions.md).[`signal`](PaginateOptions.md#signal) --- ## Interface: SearchResponse # Interface: SearchResponse\ Any search response: an `items` array plus the `page` cursor block. ## Type Parameters ### TItem `TItem` ## Properties ### items ```ts items: TItem[]; ``` --- ### page ```ts page: SearchPageResponse; ``` --- ## Interface: SeededRandom A deterministic [RandomSource](RandomSource.md); report [SeededRandom.seed](#seed) on failure. ## Extends - [`RandomSource`](RandomSource.md) ## Properties ### seed ```ts readonly seed: bigint; ``` ## Methods ### next() ```ts next(): number; ``` A uniformly distributed value in `[0, 1)`. #### Returns `number` #### Inherited from [`RandomSource`](RandomSource.md).[`next`](RandomSource.md#next) --- ### toString() ```ts toString(): string; ``` Names the seed and how to replay it, for failure messages. #### Returns `string` --- ## Interface: SupportLogger ## Methods ### log() ```ts log(message, addTimestamp?): void; ``` #### Parameters ##### message `string` \| `number` \| `boolean` \| `object` ##### addTimestamp? `boolean` #### Returns `void` --- ## Interface: TelemetryHooks ## Methods ### afterResponse()? ```ts optional afterResponse(e): void; ``` #### Parameters ##### e `TelemetryHttpEndEvent` #### Returns `void` --- ### authError()? ```ts optional authError(e): void; ``` #### Parameters ##### e `TelemetryAuthErrorEvent` #### Returns `void` --- ### authStart()? ```ts optional authStart(e): void; ``` #### Parameters ##### e `TelemetryAuthStartEvent` #### Returns `void` --- ### authSuccess()? ```ts optional authSuccess(e): void; ``` #### Parameters ##### e `TelemetryAuthSuccessEvent` #### Returns `void` --- ### beforeRequest()? ```ts optional beforeRequest(e): void; ``` #### Parameters ##### e `TelemetryHttpStartEvent` #### Returns `void` --- ### requestError()? ```ts optional requestError(e): void; ``` #### Parameters ##### e `TelemetryHttpErrorEvent` #### Returns `void` --- ### retry()? ```ts optional retry(e): void; ``` #### Parameters ##### e `TelemetryRetryEvent` #### Returns `void` --- ## Interface: TestClock A `Clock` whose time only moves when the test says so, plus the counters a test needs to assert on. Everything beyond `Clock` is inspection or control, never behaviour the SDK depends on. ## Extends - [`Clock`](Clock.md) ## Properties ### nowCalls ```ts readonly nowCalls: number; ``` How many times `now` has been read. --- ### pending ```ts readonly pending: number; ``` Sleeps still waiting for time to advance. --- ### sleeps ```ts readonly sleeps: readonly number[]; ``` Durations passed to `sleep`, in call order. ## Methods ### advance() ```ts advance(ms): Promise; ``` Move time forward by `ms`, settling every sleep that comes due, then drain the queue. #### Parameters ##### ms `number` #### Returns `Promise`\<`void`\> --- ### deadline() ```ts deadline(ms): object; ``` A signal that aborts once `ms` have elapsed on this clock. `dispose()` releases the underlying timer; call it when the guarded work finishes early, or a long deadline keeps a handle alive for its full duration. #### Parameters ##### ms `number` #### Returns `object` ##### dispose ```ts dispose: () => void; ``` ###### Returns `void` ##### signal ```ts signal: AbortSignal; ``` #### Inherited from [`Clock`](Clock.md).[`deadline`](Clock.md#deadline) --- ### now() ```ts now(): number; ``` Current wall-clock time in epoch milliseconds. #### Returns `number` #### Inherited from [`Clock`](Clock.md).[`now`](Clock.md#now) --- ### sleep() ```ts sleep(ms, signal?): Promise; ``` Resolve after `ms` have elapsed on this clock. Rejects with the signal's reason if `signal` aborts first, so a caller can cancel a wait without leaving the timer behind. An injected implementation must not resolve synchronously. The worker schedules its next poll by awaiting this, so a sleep that settles in a microtask turns the poll loop into an unbounded spin that starves the event loop and exhausts the heap. A test clock should resolve only when the test advances it. #### Parameters ##### ms `number` ##### signal? `AbortSignal` #### Returns `Promise`\<`void`\> #### Inherited from [`Clock`](Clock.md).[`sleep`](Clock.md#sleep) --- ## Interface: ThreadPool ## Accessors ### busyCount #### Get Signature ```ts get busyCount(): number; ``` Number of threads currently processing a job. ##### Returns `number` --- ### idleCount #### Get Signature ```ts get idleCount(): number; ``` Number of threads that are ready and idle. ##### Returns `number` --- ### onThreadReady #### Set Signature ```ts set onThreadReady(cb): void; ``` Register a callback invoked whenever a thread becomes ready or idle. ##### Parameters ###### cb (() => `void`) \| `undefined` ##### Returns `void` --- ### ready #### Get Signature ```ts get ready(): Promise; ``` Resolves when all threads have been spawned and signalled ready. ##### Returns `Promise`\<`void`\> --- ### size #### Get Signature ```ts get size(): number; ``` Total number of threads in the pool. ##### Returns `number` ## Methods ### dispatch() ```ts dispatch( pw, jobData, handlerModule, callbacks ): Promise; ``` Dispatch a serialized job to a specific idle worker. The caller is responsible for checking idleness first. #### Parameters ##### pw `PoolWorker` ##### jobData `Record`\<`string`, `unknown`\> ##### handlerModule `string` ##### callbacks ###### onComplete (`completionAction?`) => `void` ###### onError (`err`) => `void` #### Returns `Promise`\<`void`\> --- ### getIdleWorker() ```ts getIdleWorker(): PoolWorker | undefined; ``` Find the first ready & idle thread. #### Returns `PoolWorker` \| `undefined` --- ### terminate() ```ts terminate(): void; ``` Terminate all threads and reject any in-flight tasks. #### Returns `void` --- ## Interface: ThreadedJobWorker A job worker that runs handler logic in a shared pool of worker_threads, keeping the main Node.js event loop free for polling and I/O. The thread pool is owned by CamundaClient and shared across all threaded workers. Each thread is generic — the handler module path is sent with each job, and threads cache loaded handlers by module path. ## Accessors ### activeJobs #### Get Signature ```ts get activeJobs(): number; ``` ##### Returns `number` --- ### busyThreads #### Get Signature ```ts get busyThreads(): number; ``` Number of threads currently processing a job (across all workers). ##### Returns `number` --- ### name #### Get Signature ```ts get name(): string; ``` ##### Returns `string` --- ### poolSize #### Get Signature ```ts get poolSize(): number; ``` Number of threads in the shared pool. ##### Returns `number` --- ### ready #### Get Signature ```ts get ready(): Promise; ``` Resolves when the shared thread pool has finished initialising. ##### Returns `Promise`\<`void`\> --- ### stopped #### Get Signature ```ts get stopped(): boolean; ``` ##### Returns `boolean` ## Methods ### start() ```ts start(): void; ``` Begin polling for jobs. Safe to call at any point: the request is buffered until the shared thread pool is ready, and redundant calls (including an explicit call on an `autoStart` worker) are dropped rather than starting a second poll loop. Once the worker is stopped, `start()` is a no-op. #### Returns `void` --- ### stop() ```ts stop(): void; ``` #### Returns `void` --- ### stopGracefully() ```ts stopGracefully(opts?): Promise; ``` #### Parameters ##### opts? ###### checkIntervalMs? `number` ###### waitUpToMs? `number` #### Returns `Promise`\<`GracefulStopResult`\> --- ## Interface: ThreadedJobWorkerConfig # Interface: ThreadedJobWorkerConfig\ Configuration for a threaded job worker. Same as JobWorkerConfig but replaces `jobHandler` with `handlerModule`. ## Type Parameters ### In `In` _extends_ `z.ZodTypeAny` = `any` ### Out `Out` _extends_ `z.ZodTypeAny` = `any` ### Headers `Headers` _extends_ `z.ZodTypeAny` = `any` ## Properties ### autoStart? ```ts optional autoStart?: boolean; ``` Immediately start polling for work - default `true` --- ### customHeadersSchema? ```ts optional customHeadersSchema?: Headers; ``` Zod schema for custom headers in the activated job --- ### fetchVariables? ```ts optional fetchVariables?: In extends ZodTypeAny> ? Extract, string>[] : string[]; ``` Optional list of variable names to fetch during activation --- ### handlerModule ```ts handlerModule: string; ``` Absolute or relative path to a JS/TS module that exports a default handler function. The function signature must be: `(job, client) => Promise` --- ### inputSchema? ```ts optional inputSchema?: In; ``` Zod schema for variables in the activated job --- ### jobTimeoutMs? ```ts optional jobTimeoutMs?: number; ``` Job activation timeout in ms — default `60000`. Overridden by CAMUNDA_WORKER_TIMEOUT env var. --- ### jobType ```ts jobType: string; ``` Zeebe job type --- ### maxParallelJobs? ```ts optional maxParallelJobs?: number; ``` Concurrency limit — default `10`. Overridden by CAMUNDA_WORKER_MAX_CONCURRENT_JOBS env var. --- ### outputSchema? ```ts optional outputSchema?: Out; ``` Zod schema for variables in the complete command --- ### pollBackoffMaxMs? ```ts optional pollBackoffMaxMs?: number; ``` Ceiling (ms) of the between-failed-poll exponential backoff. Default `30000`. --- ### pollBackoffMinMs? ```ts optional pollBackoffMinMs?: number; ``` Floor (ms) of the exponential backoff applied between _failed_ activation requests (connection refused, connect timeout, broker restart, LAN blip …). The first retry waits ≈ this/2–this ms; subsequent consecutive failures double the window up to [pollBackoffMaxMs](#pollbackoffmaxms), with jitter, and reset to zero on the first successful poll. Default `1000`. Set to `0` to disable backoff (failed polls then fall back to [pollIntervalMs](#pollintervalms) rather than retrying immediately). --- ### pollIntervalMs? ```ts optional pollIntervalMs?: number; ``` Backoff between polls - default 1ms --- ### pollTimeoutMs? ```ts optional pollTimeoutMs?: number; ``` The request will be completed when at least one job is activated or after the requestTimeout. If the requestTimeout = 0, the request will be completed after a default configured timeout in the broker. To immediately complete the request when no job is activated set the requestTimeout to a negative value --- ### startupJitterMaxSeconds? ```ts optional startupJitterMaxSeconds?: number; ``` Maximum random delay (in seconds) before the worker starts polling. When multiple application instances restart simultaneously, this spreads out initial activation requests to avoid saturating the server. `0` (the default) means no delay. --- ### threadPoolSize? ```ts optional threadPoolSize?: number; ``` Number of threads in the shared pool (used only when the pool is first created; subsequent workers share the existing pool). Default: number of CPU cores available to the process. --- ### validateSchemas? ```ts optional validateSchemas?: boolean; ``` Validate any provided input, output, customheader schema default: false --- ### withLease? ```ts optional withLease?: boolean; ``` Activate jobs with a lease — default `false`. When `true`, each activated job is assigned a distinct, opaque lease token (`ActivatedJobResult.jobLeaseToken`) that is automatically threaded back into the fenced `complete` / `fail` / `error` commands. See [JobWorkerConfig.withLease](JobWorkerConfig.md#withlease). Note: the threaded `jobHandler` intentionally keeps the base job shape (`jobLeaseToken` remains optional/nullable); the token is threaded back into fenced commands automatically, so handlers do not read it directly. --- ### workerName? ```ts optional workerName?: string; ``` Optional explicit name --- ## Interface: TypedVariableItem A single variable item from a search page (the subset the collector needs). ## Properties ### name ```ts name: string; ``` --- ### scopeKey ```ts scopeKey: string; ``` The scope key the variable is directly defined in. --- ### value ```ts value: string; ``` The variable value, serialized as JSON (the wire representation). --- ## Interface: TypedVariablePage One page of variable search results. ## Properties ### endCursor ```ts endCursor: string | null; ``` Cursor for the next page, or `null` when there are no more pages. --- ### items ```ts items: readonly TypedVariableItem[]; ``` --- ## Type Alias: ActivateAdHocSubProcessActivitiesData ```ts type ActivateAdHocSubProcessActivitiesData = object; ``` ## Properties ### body ```ts body: AdHocSubProcessActivateActivitiesInstruction; ``` --- ### path ```ts path: object; ``` #### adHocSubProcessInstanceKey ```ts adHocSubProcessInstanceKey: ElementInstanceKeyWritable; ``` The key of the ad-hoc sub-process instance that contains the activities. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/element-instances/ad-hoc-activities/{adHocSubProcessInstanceKey}/activation"; ``` --- ## Type Alias: ActivateAdHocSubProcessActivitiesError ```ts type ActivateAdHocSubProcessActivitiesError = ActivateAdHocSubProcessActivitiesErrors[keyof ActivateAdHocSubProcessActivitiesErrors]; ``` --- ## Type Alias: ActivateAdHocSubProcessActivitiesErrors ```ts type ActivateAdHocSubProcessActivitiesErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The ad-hoc sub-process instance is not found or the provided key does not identify an ad-hoc sub-process. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: ActivateAdHocSubProcessActivitiesResponse ```ts type ActivateAdHocSubProcessActivitiesResponse = ActivateAdHocSubProcessActivitiesResponses[keyof ActivateAdHocSubProcessActivitiesResponses]; ``` --- ## Type Alias: ActivateAdHocSubProcessActivitiesResponses ```ts type ActivateAdHocSubProcessActivitiesResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The ad-hoc sub-process instance is modified. --- ## Type Alias: ActivateJobsData ```ts type ActivateJobsData = object; ``` ## Properties ### body ```ts body: JobActivationRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/jobs/activation"; ``` --- ## Type Alias: ActivateJobsError ```ts type ActivateJobsError = ActivateJobsErrors[keyof ActivateJobsErrors]; ``` --- ## Type Alias: ActivateJobsErrors ```ts type ActivateJobsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: ActivateJobsResponse ```ts type ActivateJobsResponse = ActivateJobsResponses[keyof ActivateJobsResponses]; ``` --- ## Type Alias: ActivateJobsResponses ```ts type ActivateJobsResponses = object; ``` ## Properties ### 200 ```ts 200: JobActivationResult; ``` The list of activated jobs. --- ## Type Alias: ActivatedJobResult ```ts type ActivatedJobResult = object; ``` ## Properties ### businessId ```ts businessId: BusinessId | null; ``` The business ID of the owning process instance, inherited when the job was created. This is `null` for jobs created before version 8.10 and for jobs whose owning process instance has no business ID. --- ### customHeaders ```ts customHeaders: object; ``` A set of custom headers defined during modelling; returned as a serialized JSON document. #### Index Signature ```ts [key: string]: unknown ``` --- ### deadline ```ts deadline: number; ``` When the job can be activated again, sent as a UNIX epoch timestamp. --- ### elementId ```ts elementId: ElementId; ``` The associated task element ID. --- ### elementInstanceKey ```ts elementInstanceKey: ElementInstanceKey; ``` The element instance key of the task. --- ### jobKey ```ts jobKey: JobKey; ``` The key, a unique identifier for the job. --- ### jobLeaseToken ```ts jobLeaseToken: JobLeaseToken | null; ``` The lease token identifying this activation. This is `null` when the job was activated without a lease. --- ### kind ```ts kind: JobKindEnum; ``` --- ### listenerEventType ```ts listenerEventType: JobListenerEventTypeEnum; ``` --- ### physicalTenantId ```ts physicalTenantId: string; ``` The ID of the physical tenant that the job-activation request was routed to; the default physical tenant when the request did not specify one. --- ### priority ```ts priority: number; ``` The priority of the job. Higher values indicate higher priority. Jobs created before 8.10 have no stored priority; the API returns 0 for such jobs. --- ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId; ``` The bpmn process ID of the job's process definition. --- ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey; ``` The key of the job's process definition. --- ### processDefinitionVersion ```ts processDefinitionVersion: number; ``` The version of the job's process definition. --- ### processInstanceKey ```ts processInstanceKey: ProcessInstanceKey; ``` The job's process instance key. --- ### retries ```ts retries: number; ``` The amount of retries left to this job (should always be positive). --- ### rootProcessInstanceKey ```ts rootProcessInstanceKey: ProcessInstanceKey | null; ``` The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. --- ### tags ```ts tags: TagSet; ``` --- ### tenantId ```ts tenantId: TenantId; ``` The ID of the tenant that owns the job. --- ### type ```ts type: string; ``` The type of the job (should match what was requested). --- ### userTask ```ts userTask: UserTaskProperties | null; ``` User task properties, if the job is a user task. This is `null` if the job is not a user task. --- ### variables ```ts variables: object; ``` All variables visible to the task scope, computed at activation time. #### Index Signature ```ts [key: string]: unknown ``` --- ### worker ```ts worker: string; ``` The name of the worker which activated this job. --- ## Type Alias: ActivatedJobResultWithJobLeaseToken ```ts type ActivatedJobResultWithJobLeaseToken = Omit< ActivatedJobResult, "jobLeaseToken" > & object; ``` `ActivatedJobResult` when `withLease === true`: `jobLeaseToken` is present (required, non-null). ## Type Declaration ### jobLeaseToken ```ts jobLeaseToken: NonNullable; ``` --- ## Type Alias: ActivatedJobResultWithoutJobLeaseToken ```ts type ActivatedJobResultWithoutJobLeaseToken = Omit< ActivatedJobResult, "jobLeaseToken" > & object; ``` `ActivatedJobResult` when `withLease` is absent / a non-matching literal: `jobLeaseToken` is null (the nullable wire shape). ## Type Declaration ### jobLeaseToken? ```ts optional jobLeaseToken?: null; ``` --- ## Type Alias: AdHocSubProcessActivateActivitiesInstruction ```ts type AdHocSubProcessActivateActivitiesInstruction = object; ``` ## Properties ### cancelRemainingInstances? ```ts optional cancelRemainingInstances?: boolean; ``` Whether to cancel remaining instances of the ad-hoc sub-process. --- ### elements ```ts elements: AdHocSubProcessActivateActivityReference[]; ``` Activities to activate. --- ## Type Alias: AdHocSubProcessActivateActivityReference ```ts type AdHocSubProcessActivateActivityReference = object; ``` ## Properties ### elementId ```ts elementId: ElementId; ``` The ID of the element that should be activated. --- ### variables? ```ts optional variables?: object; ``` Variables to be set when activating the element. #### Index Signature ```ts [key: string]: unknown ``` --- ## Type Alias: AdvancedActorTypeFilter ```ts type AdvancedActorTypeFilter = object; ``` Advanced filter Advanced AuditLogActorTypeEnum filter. ## Properties ### $eq? ```ts optional $eq?: AuditLogActorTypeEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: AuditLogActorTypeEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: AuditLogActorTypeEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedAgentDefinitionKeyFilter ```ts type AdvancedAgentDefinitionKeyFilter = object; ``` Advanced filter Advanced AgentDefinitionKey filter. ## Properties ### $eq? ```ts optional $eq?: AgentDefinitionKey; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: AgentDefinitionKey[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: AgentDefinitionKey; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: AgentDefinitionKey[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedAgentDefinitionTypeFilter ```ts type AdvancedAgentDefinitionTypeFilter = object; ``` Advanced filter Advanced AgentDefinitionTypeEnum filter. ## Properties ### $eq? ```ts optional $eq?: AgentDefinitionTypeEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: AgentDefinitionTypeEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: AgentDefinitionTypeEnum; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: AgentDefinitionTypeEnum[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedAgentHistoryItemKeyFilter ```ts type AdvancedAgentHistoryItemKeyFilter = object; ``` Advanced filter Advanced AgentHistoryItemKey filter. ## Properties ### $eq? ```ts optional $eq?: AgentHistoryItemKey; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: AgentHistoryItemKey[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: AgentHistoryItemKey; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: AgentHistoryItemKey[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedAgentInstanceHistoryCommitStatusFilter ```ts type AdvancedAgentInstanceHistoryCommitStatusFilter = object; ``` Advanced filter Advanced AgentInstanceHistoryCommitStatusEnum filter. ## Properties ### $eq? ```ts optional $eq?: AgentInstanceHistoryCommitStatusEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: AgentInstanceHistoryCommitStatusEnum[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: AgentInstanceHistoryCommitStatusEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedAgentInstanceHistoryRoleFilter ```ts type AdvancedAgentInstanceHistoryRoleFilter = object; ``` Advanced filter Advanced AgentInstanceHistoryRoleEnum filter. ## Properties ### $eq? ```ts optional $eq?: AgentInstanceHistoryRoleEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: AgentInstanceHistoryRoleEnum[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: AgentInstanceHistoryRoleEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedAgentInstanceKeyFilter ```ts type AdvancedAgentInstanceKeyFilter = object; ``` Advanced filter Advanced AgentInstanceKey filter. ## Properties ### $eq? ```ts optional $eq?: AgentInstanceKey; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: AgentInstanceKey[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: AgentInstanceKey; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: AgentInstanceKey[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedAgentInstanceStatusFilter ```ts type AdvancedAgentInstanceStatusFilter = object; ``` Advanced filter Advanced AgentInstanceStatusEnum filter. ## Properties ### $eq? ```ts optional $eq?: AgentInstanceStatusEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: AgentInstanceStatusEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: AgentInstanceStatusEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedAuditLogEntityKeyFilter ```ts type AdvancedAuditLogEntityKeyFilter = object; ``` Advanced filter Advanced entityKey filter. ## Properties ### $eq? ```ts optional $eq?: AuditLogEntityKey; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: AuditLogEntityKey[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: AuditLogEntityKey; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: AuditLogEntityKey[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedAuditLogKeyFilter ```ts type AdvancedAuditLogKeyFilter = object; ``` Advanced filter Advanced AuditLogKey filter. ## Properties ### $eq? ```ts optional $eq?: AuditLogKey; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: AuditLogKey[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: AuditLogKey; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: AuditLogKey[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedBatchOperationItemStateFilter ```ts type AdvancedBatchOperationItemStateFilter = object; ``` Advanced filter Advanced BatchOperationItemStateEnum filter. ## Properties ### $eq? ```ts optional $eq?: BatchOperationItemStateEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: BatchOperationItemStateEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: BatchOperationItemStateEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedBatchOperationStateFilter ```ts type AdvancedBatchOperationStateFilter = object; ``` Advanced filter Advanced BatchOperationStateEnum filter. ## Properties ### $eq? ```ts optional $eq?: BatchOperationStateEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: BatchOperationStateEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: BatchOperationStateEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedBatchOperationTypeFilter ```ts type AdvancedBatchOperationTypeFilter = object; ``` Advanced filter Advanced BatchOperationTypeEnum filter. ## Properties ### $eq? ```ts optional $eq?: BatchOperationTypeEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: BatchOperationTypeEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: BatchOperationTypeEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedCategoryFilter ```ts type AdvancedCategoryFilter = object; ``` Advanced filter Advanced AuditLogCategoryEnum filter. ## Properties ### $eq? ```ts optional $eq?: AuditLogCategoryEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: AuditLogCategoryEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: AuditLogCategoryEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedClusterVariableKindFilter ```ts type AdvancedClusterVariableKindFilter = object; ``` Advanced filter Advanced ClusterVariableKindEnum filter. ## Properties ### $eq? ```ts optional $eq?: ClusterVariableKindEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: ClusterVariableKindEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: ClusterVariableKindEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedClusterVariableScopeFilter ```ts type AdvancedClusterVariableScopeFilter = object; ``` Advanced filter Advanced ClusterVariableScopeEnum filter. ## Properties ### $eq? ```ts optional $eq?: ClusterVariableScopeEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: ClusterVariableScopeEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: ClusterVariableScopeEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedDateTimeFilter ```ts type AdvancedDateTimeFilter = object; ``` Advanced filter Advanced date-time filter. ## Properties ### $eq? ```ts optional $eq?: string; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $gt? ```ts optional $gt?: string; ``` Greater than comparison with the provided value. --- ### $gte? ```ts optional $gte?: string; ``` Greater than or equal comparison with the provided value. --- ### $in? ```ts optional $in?: string[]; ``` Checks if the property matches any of the provided values. --- ### $lt? ```ts optional $lt?: string; ``` Lower than comparison with the provided value. --- ### $lte? ```ts optional $lte?: string; ``` Lower than or equal comparison with the provided value. --- ### $neq? ```ts optional $neq?: string; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedDecisionDefinitionKeyFilter ```ts type AdvancedDecisionDefinitionKeyFilter = object; ``` Advanced filter Advanced DecisionDefinitionKey filter. ## Properties ### $eq? ```ts optional $eq?: DecisionDefinitionKey; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: DecisionDefinitionKey[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: DecisionDefinitionKey; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: DecisionDefinitionKey[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedDecisionEvaluationInstanceKeyFilter ```ts type AdvancedDecisionEvaluationInstanceKeyFilter = object; ``` Advanced filter Advanced DecisionEvaluationInstanceKey filter. ## Properties ### $eq? ```ts optional $eq?: DecisionEvaluationInstanceKey; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: DecisionEvaluationInstanceKey[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: DecisionEvaluationInstanceKey; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: DecisionEvaluationInstanceKey[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedDecisionEvaluationKeyFilter ```ts type AdvancedDecisionEvaluationKeyFilter = object; ``` Advanced filter Advanced DecisionEvaluationKey filter. ## Properties ### $eq? ```ts optional $eq?: DecisionEvaluationKey; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: DecisionEvaluationKey[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: DecisionEvaluationKey; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: DecisionEvaluationKey[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedDecisionInstanceStateFilter ```ts type AdvancedDecisionInstanceStateFilter = object; ``` Advanced filter Advanced DecisionInstanceStateEnum filter. ## Properties ### $eq? ```ts optional $eq?: DecisionInstanceStateEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: DecisionInstanceStateEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: DecisionInstanceStateEnum; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: DecisionInstanceStateEnum[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedDecisionRequirementsKeyFilter ```ts type AdvancedDecisionRequirementsKeyFilter = object; ``` Advanced filter Advanced DecisionRequirementsKey filter. ## Properties ### $eq? ```ts optional $eq?: DecisionRequirementsKey; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: DecisionRequirementsKey[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: DecisionRequirementsKey; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: DecisionRequirementsKey[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedDeploymentKeyFilter ```ts type AdvancedDeploymentKeyFilter = object; ``` Advanced filter Advanced DeploymentKey filter. ## Properties ### $eq? ```ts optional $eq?: DeploymentKey; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: DeploymentKey[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: DeploymentKey; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: DeploymentKey[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedElementIdFilter ```ts type AdvancedElementIdFilter = object; ``` Advanced filter Advanced ElementId filter. ## Properties ### $eq? ```ts optional $eq?: ElementId; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: ElementId[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: ElementId; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: ElementId[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedElementInstanceKeyFilter ```ts type AdvancedElementInstanceKeyFilter = object; ``` Advanced filter Advanced ElementInstanceKey filter. ## Properties ### $eq? ```ts optional $eq?: ElementInstanceKey; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: ElementInstanceKey[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: ElementInstanceKey; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: ElementInstanceKey[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedElementInstanceStateFilter ```ts type AdvancedElementInstanceStateFilter = object; ``` Advanced filter Advanced ElementInstanceStateEnum filter. ## Properties ### $eq? ```ts optional $eq?: ElementInstanceStateEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: ElementInstanceStateEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: ElementInstanceStateEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedEntityTypeFilter ```ts type AdvancedEntityTypeFilter = object; ``` Advanced filter Advanced AuditLogEntityTypeEnum filter. ## Properties ### $eq? ```ts optional $eq?: AuditLogEntityTypeEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: AuditLogEntityTypeEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: AuditLogEntityTypeEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedFormKeyFilter ```ts type AdvancedFormKeyFilter = object; ``` Advanced filter Advanced FormKey filter. ## Properties ### $eq? ```ts optional $eq?: FormKey; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: FormKey[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: FormKey; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: FormKey[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedGlobalListenerSourceFilter ```ts type AdvancedGlobalListenerSourceFilter = object; ``` Advanced filter Advanced global listener source filter. ## Properties ### $eq? ```ts optional $eq?: GlobalListenerSourceEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: GlobalListenerSourceEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: GlobalListenerSourceEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedGlobalTaskListenerEventTypeFilter ```ts type AdvancedGlobalTaskListenerEventTypeFilter = object; ``` Advanced filter Advanced global listener event type filter. ## Properties ### $eq? ```ts optional $eq?: GlobalTaskListenerEventTypeEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: GlobalTaskListenerEventTypeEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: GlobalTaskListenerEventTypeEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedIncidentErrorTypeFilter ```ts type AdvancedIncidentErrorTypeFilter = object; ``` Advanced filter Advanced IncidentErrorTypeEnum filter ## Properties ### $eq? ```ts optional $eq?: IncidentErrorTypeEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: IncidentErrorTypeEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: IncidentErrorTypeEnum; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: IncidentErrorTypeEnum[]; ``` Checks if the property does not match any of the provided values. --- ## Type Alias: AdvancedIncidentStateFilter ```ts type AdvancedIncidentStateFilter = object; ``` Advanced filter Advanced IncidentStateEnum filter ## Properties ### $eq? ```ts optional $eq?: IncidentStateEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: IncidentStateEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: IncidentStateEnum; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: IncidentStateEnum[]; ``` Checks if the property does not match any of the provided values. --- ## Type Alias: AdvancedIntegerFilter ```ts type AdvancedIntegerFilter = object; ``` Advanced filter Advanced integer (int32) filter. ## Properties ### $eq? ```ts optional $eq?: number; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $gt? ```ts optional $gt?: number; ``` Greater than comparison with the provided value. --- ### $gte? ```ts optional $gte?: number; ``` Greater than or equal comparison with the provided value. --- ### $in? ```ts optional $in?: number[]; ``` Checks if the property matches any of the provided values. --- ### $lt? ```ts optional $lt?: number; ``` Lower than comparison with the provided value. --- ### $lte? ```ts optional $lte?: number; ``` Lower than or equal comparison with the provided value. --- ### $neq? ```ts optional $neq?: number; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedJobKeyFilter ```ts type AdvancedJobKeyFilter = object; ``` Advanced filter Advanced JobKey filter. ## Properties ### $eq? ```ts optional $eq?: JobKey; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: JobKey[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: JobKey; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: JobKey[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedJobKindFilter ```ts type AdvancedJobKindFilter = object; ``` Advanced filter Advanced JobKindEnum filter. ## Properties ### $eq? ```ts optional $eq?: JobKindEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: JobKindEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: JobKindEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedJobListenerEventTypeFilter ```ts type AdvancedJobListenerEventTypeFilter = object; ``` Advanced filter Advanced JobListenerEventTypeEnum filter. ## Properties ### $eq? ```ts optional $eq?: JobListenerEventTypeEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: JobListenerEventTypeEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: JobListenerEventTypeEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedJobStateFilter ```ts type AdvancedJobStateFilter = object; ``` Advanced filter Advanced JobStateEnum filter. ## Properties ### $eq? ```ts optional $eq?: JobStateEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: JobStateEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: JobStateEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedMessageSubscriptionKeyFilter ```ts type AdvancedMessageSubscriptionKeyFilter = object; ``` Advanced filter Advanced MessageSubscriptionKey filter. ## Properties ### $eq? ```ts optional $eq?: MessageSubscriptionKey; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: MessageSubscriptionKey[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: MessageSubscriptionKey; ``` Checks for equality with the provided value. --- ### $notIn? ```ts optional $notIn?: MessageSubscriptionKey[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedMessageSubscriptionStateFilter ```ts type AdvancedMessageSubscriptionStateFilter = object; ``` Advanced filter Advanced MessageSubscriptionStateEnum filter ## Properties ### $eq? ```ts optional $eq?: MessageSubscriptionStateEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: MessageSubscriptionStateEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: MessageSubscriptionStateEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedMessageSubscriptionTypeFilter ```ts type AdvancedMessageSubscriptionTypeFilter = object; ``` Advanced filter Advanced MessageSubscriptionTypeEnum filter ## Properties ### $eq? ```ts optional $eq?: MessageSubscriptionTypeEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: MessageSubscriptionTypeEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: MessageSubscriptionTypeEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedMetadataValueFilter ```ts type AdvancedMetadataValueFilter = object; ``` Advanced filter Advanced filter on a metadata value (string or number). ## Properties ### $eq? ```ts optional $eq?: string | number; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the metadata key exists. --- ### $gt? ```ts optional $gt?: number; ``` Greater than comparison with the provided value. --- ### $gte? ```ts optional $gte?: number; ``` Greater than or equal comparison with the provided value. --- ### $in? ```ts optional $in?: (string | number)[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $lt? ```ts optional $lt?: number; ``` Lower than comparison with the provided value. --- ### $lte? ```ts optional $lte?: number; ``` Lower than or equal comparison with the provided value. --- ### $neq? ```ts optional $neq?: string | number; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedOperationTypeFilter ```ts type AdvancedOperationTypeFilter = object; ``` Advanced filter Advanced AuditLogOperationTypeEnum filter. ## Properties ### $eq? ```ts optional $eq?: AuditLogOperationTypeEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: AuditLogOperationTypeEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: AuditLogOperationTypeEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedProcessDefinitionIdFilter ```ts type AdvancedProcessDefinitionIdFilter = object; ``` Advanced filter Advanced ProcessDefinitionId filter. ## Properties ### $eq? ```ts optional $eq?: ProcessDefinitionId; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: ProcessDefinitionId[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: ProcessDefinitionId; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: ProcessDefinitionId[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedProcessDefinitionKeyFilter ```ts type AdvancedProcessDefinitionKeyFilter = object; ``` Advanced filter Advanced ProcessDefinitionKey filter. ## Properties ### $eq? ```ts optional $eq?: ProcessDefinitionKey; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: ProcessDefinitionKey[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: ProcessDefinitionKey; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: ProcessDefinitionKey[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedProcessInstanceKeyFilter ```ts type AdvancedProcessInstanceKeyFilter = object; ``` Advanced filter Advanced ProcessInstanceKey filter. ## Properties ### $eq? ```ts optional $eq?: ProcessInstanceKey; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: ProcessInstanceKey[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: ProcessInstanceKey; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: ProcessInstanceKey[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedProcessInstanceStateFilter ```ts type AdvancedProcessInstanceStateFilter = object; ``` Advanced filter Advanced ProcessInstanceStateEnum filter. ## Properties ### $eq? ```ts optional $eq?: ProcessInstanceStateEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: ProcessInstanceStateEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: ProcessInstanceStateEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedResourceKeyFilter ```ts type AdvancedResourceKeyFilter = object; ``` Advanced filter Advanced ResourceKey filter. ## Properties ### $eq? ```ts optional $eq?: ResourceKey; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: ResourceKey[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: ResourceKey; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: ResourceKey[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedResultFilter ```ts type AdvancedResultFilter = object; ``` Advanced filter Advanced AuditLogResultEnum filter. ## Properties ### $eq? ```ts optional $eq?: AuditLogResultEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: AuditLogResultEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: AuditLogResultEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedScopeKeyFilter ```ts type AdvancedScopeKeyFilter = object; ``` Advanced filter Advanced ScopeKey filter. ## Properties ### $eq? ```ts optional $eq?: ScopeKey; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: ScopeKey[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: ScopeKey; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: ScopeKey[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedStringFilter ```ts type AdvancedStringFilter = BasicStringFilter & object; ``` Advanced filter Advanced string filter. ## Type Declaration ### $like? ```ts optional $like?: LikeFilter; ``` --- ## Type Alias: AdvancedUserTaskStateFilter ```ts type AdvancedUserTaskStateFilter = object; ``` Advanced filter Advanced UserTaskStateEnum filter. ## Properties ### $eq? ```ts optional $eq?: UserTaskStateEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: UserTaskStateEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: UserTaskStateEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedVariableKeyFilter ```ts type AdvancedVariableKeyFilter = object; ``` Advanced filter Advanced VariableKey filter. ## Properties ### $eq? ```ts optional $eq?: VariableKey; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: VariableKey[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: VariableKey; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: VariableKey[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: AdvancedWaitStateElementTypeFilter ```ts type AdvancedWaitStateElementTypeFilter = object; ``` Advanced filter Advanced element type filter. ## Properties ### $eq? ```ts optional $eq?: WaitStateElementTypeEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: WaitStateElementTypeEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: WaitStateElementTypeEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AdvancedWaitStateTypeFilter ```ts type AdvancedWaitStateTypeFilter = object; ``` Advanced filter Advanced wait state type filter. ## Properties ### $eq? ```ts optional $eq?: WaitStateTypeEnum; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: WaitStateTypeEnum[]; ``` Checks if the property matches any of the provided values. --- ### $like? ```ts optional $like?: LikeFilter; ``` --- ### $neq? ```ts optional $neq?: WaitStateTypeEnum; ``` Checks for inequality with the provided value. --- ## Type Alias: AgentDefinitionFilter ```ts type AgentDefinitionFilter = object; ``` Agent definition search filter. ## Properties ### agentDefinitionKey? ```ts optional agentDefinitionKey?: AgentDefinitionKeyFilterProperty; ``` The unique key of the agent definition. --- ### agentType? ```ts optional agentType?: AgentDefinitionTypeFilterProperty; ``` The kind of agent this agent definition describes. --- ### elementId? ```ts optional elementId?: ElementIdFilterProperty; ``` The BPMN element ID of the process element that owns the agent definition. --- ### name? ```ts optional name?: StringFilterProperty; ``` The human-readable name of the process element that owns the agent definition. --- ### processDefinitionId? ```ts optional processDefinitionId?: ProcessDefinitionIdFilterProperty; ``` The BPMN process ID of the process definition that owns the agent definition. --- ### processDefinitionKey? ```ts optional processDefinitionKey?: ProcessDefinitionKeyFilterProperty; ``` The key of the process definition that owns the agent definition. --- ### processDefinitionVersion? ```ts optional processDefinitionVersion?: IntegerFilterProperty; ``` The version of the process definition that owns the agent definition. --- ### processDefinitionVersionTag? ```ts optional processDefinitionVersionTag?: StringFilterProperty; ``` The version tag of the process definition that owns the agent definition. --- ### tenantId? ```ts optional tenantId?: StringFilterProperty; ``` The tenant ID of the agent definition. --- ## Type Alias: AgentDefinitionKey ```ts type AgentDefinitionKey = CamundaKey<"AgentDefinitionKey">; ``` System-generated key for an agent definition. --- ## Type Alias: AgentDefinitionKeyExactMatch ```ts type AgentDefinitionKeyExactMatch = AgentDefinitionKey; ``` Exact match Matches the value exactly. --- ## Type Alias: AgentDefinitionKeyExactMatchWritable ```ts type AgentDefinitionKeyExactMatchWritable = AgentDefinitionKeyWritable; ``` Exact match Matches the value exactly. --- ## Type Alias: AgentDefinitionKeyFilterProperty ```ts type AgentDefinitionKeyFilterProperty = AgentDefinitionKeyExactMatch | AdvancedAgentDefinitionKeyFilter; ``` AgentDefinitionKey property with full advanced search capabilities. --- ## Type Alias: AgentDefinitionKeyWritable ```ts type AgentDefinitionKeyWritable = LongKey; ``` System-generated key for an agent definition. --- ## Type Alias: AgentDefinitionResult ```ts type AgentDefinitionResult = object; ``` An agent definition, created at deploy time for the process element it belongs to. ## Properties ### agentDefinitionKey ```ts agentDefinitionKey: AgentDefinitionKey; ``` The unique key for this agent definition. Unique across process definition versions. --- ### agentType ```ts agentType: AgentDefinitionTypeEnum; ``` --- ### elementId ```ts elementId: ElementId; ``` The BPMN element ID of the process element that owns the agent definition. --- ### name ```ts name: string; ``` The human-readable name of the process element that owns the agent definition. Falls back to elementId when the element has no BPMN name configured. --- ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId; ``` The BPMN process ID of the process definition that owns the agent definition. --- ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey; ``` The key of the process definition that owns the agent definition. --- ### processDefinitionVersion ```ts processDefinitionVersion: number; ``` The version of the process definition that owns the agent definition. --- ### processDefinitionVersionTag ```ts processDefinitionVersionTag: string | null; ``` The version tag of the process definition that owns the agent definition. --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID of this agent definition. --- ## Type Alias: AgentDefinitionSearchQuery ```ts type AgentDefinitionSearchQuery = SearchQueryRequest & object; ``` Agent definition search request. ## Type Declaration ### filter? ```ts optional filter?: AgentDefinitionFilter; ``` The agent definition search filters. ### sort? ```ts optional sort?: AgentDefinitionSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: AgentDefinitionSearchQueryResult ```ts type AgentDefinitionSearchQueryResult = SearchQueryResponse & object; ``` Agent definition search response. ## Type Declaration ### items ```ts items: AgentDefinitionResult[]; ``` The matching agent definitions. --- ## Type Alias: AgentDefinitionSearchQuerySortRequest ```ts type AgentDefinitionSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "agentDefinitionKey" | "agentType" | "name" | "elementId" | "processDefinitionId" | "processDefinitionKey" | "processDefinitionVersion" | "processDefinitionVersionTag" | "tenantId"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: AgentDefinitionTypeEnum ```ts type AgentDefinitionTypeEnum = (typeof AgentDefinitionTypeEnum)[keyof typeof AgentDefinitionTypeEnum]; ``` The kind of agent an agent definition describes. --- ## Type Alias: AgentDefinitionTypeExactMatch ```ts type AgentDefinitionTypeExactMatch = AgentDefinitionTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: AgentDefinitionTypeExactMatchWritable ```ts type AgentDefinitionTypeExactMatchWritable = AgentDefinitionTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: AgentDefinitionTypeFilterProperty ```ts type AgentDefinitionTypeFilterProperty = AgentDefinitionTypeExactMatch | AdvancedAgentDefinitionTypeFilter; ``` AgentDefinitionTypeEnum property with full advanced search capabilities. --- ## Type Alias: AgentHistoryItemKey ```ts type AgentHistoryItemKey = CamundaKey<"AgentHistoryItemKey">; ``` System-generated key for an agent history item. --- ## Type Alias: AgentHistoryItemKeyExactMatch ```ts type AgentHistoryItemKeyExactMatch = AgentHistoryItemKey; ``` Exact match Matches the value exactly. --- ## Type Alias: AgentHistoryItemKeyExactMatchWritable ```ts type AgentHistoryItemKeyExactMatchWritable = AgentHistoryItemKeyWritable; ``` Exact match Matches the value exactly. --- ## Type Alias: AgentHistoryItemKeyFilterProperty ```ts type AgentHistoryItemKeyFilterProperty = AgentHistoryItemKeyExactMatch | AdvancedAgentHistoryItemKeyFilter; ``` AgentHistoryItemKey property with full advanced search capabilities. --- ## Type Alias: AgentHistoryItemKeyWritable ```ts type AgentHistoryItemKeyWritable = LongKey; ``` System-generated key for an agent history item. --- ## Type Alias: AgentInstanceCreatedHistoryItem ```ts type AgentInstanceCreatedHistoryItem = object; ``` The outcome of appending a single history item from an update request's history batch. ## Properties ### historyItemId ```ts historyItemId: HistoryItemId; ``` The historyItemId of the corresponding item in the request, echoed back so callers can correlate response entries with request items by id. --- ### historyItemKey ```ts historyItemKey: AgentHistoryItemKey; ``` The system-generated key for the history item. When isDuplicate is true, this is the key of the original entry, not a new one. --- ### isDuplicate ```ts isDuplicate: boolean; ``` True if this item had already been recorded and no new AGENT_HISTORY event was created for it; false if a new event was created. --- ## Type Alias: AgentInstanceCreationRequest ```ts type AgentInstanceCreationRequest = object; ``` Request to create a new agent instance. ## Properties ### elementInstanceKey ```ts elementInstanceKey: ElementInstanceKey; ``` The key of the AI Agent Sub-process or AI Agent Task element instance. The engine uses this key to infer processInstanceKey, elementId, processDefinitionKey, and tenantId. --- ### history ```ts history: AgentInstanceHistoryItem[]; ``` A batch of history items to append to the agent instance's conversation history, in request order. Each created item is echoed back in the response's createdHistory, positionally correlated. Must include a CONFIGURATION item establishing model, provider, and systemPrompt (and, if needed, limits). Every item's role must be CONFIGURATION or USER, and no item may carry non-zero usage-token metrics (inputTokens, outputTokens, reasoningTokenCount, cacheCreationTokenCount, cacheReadTokenCount); durationMs is exempt and may be non-zero. --- ### jobKey ```ts jobKey: JobKey; ``` The key of the job activation during which this creation is being made. A creation must always be attributed to the active job that produced it. --- ### jobLeaseToken ```ts jobLeaseToken: JobLeaseToken; ``` Opaque lease token received from the job activation response. Disambiguates this activation from any other activation of the same job: if the job is later retried, history items submitted under a superseded lease are discarded rather than committed. --- ## Type Alias: AgentInstanceCreationResult ```ts type AgentInstanceCreationResult = object; ``` Response returned after successfully creating an agent instance. ## Properties ### agentInstanceKey ```ts agentInstanceKey: AgentInstanceKey; ``` The system-generated key for the created agent instance. --- ### createdHistory ```ts createdHistory: AgentInstanceCreatedHistoryItem[]; ``` One entry per history item submitted in the request, in request order. --- ## Type Alias: AgentInstanceDefinitionResult ```ts type AgentInstanceDefinitionResult = object; ``` The definition of an agent instance. Set at creation, but can change later via a CONFIGURATION history item. ## Properties ### model ```ts model: string; ``` The LLM model identifier (for example, gpt-4o). --- ### provider ```ts provider: string; ``` The LLM provider (for example, openai or anthropic). --- ### systemPrompt ```ts systemPrompt: AgentInstanceMessageContent[]; ``` The system prompt configured for this agent instance, as content blocks. --- ## Type Alias: AgentInstanceDocumentContent ```ts type AgentInstanceDocumentContent = object; ``` Document content A Camunda Document Store reference content block. ## Properties ### contentType ```ts contentType: string; ``` The content type discriminator. --- ### documentReference ```ts documentReference: DocumentReference; ``` A reference to a document stored in the Camunda Document Store. --- ## Type Alias: AgentInstanceFilter ```ts type AgentInstanceFilter = object; ``` Agent instance search filter. ## Properties ### agentDefinitionKey? ```ts optional agentDefinitionKey?: AgentDefinitionKeyFilterProperty; ``` The key of the agent definition this agent instance is an instance of. --- ### agentInstanceKey? ```ts optional agentInstanceKey?: AgentInstanceKeyFilterProperty; ``` The unique key of the agent instance. --- ### completionDate? ```ts optional completionDate?: DateTimeFilterProperty; ``` The completion date of the agent instance. --- ### creationDate? ```ts optional creationDate?: DateTimeFilterProperty; ``` The creation date of the agent instance. --- ### elementId? ```ts optional elementId?: ElementIdFilterProperty; ``` The BPMN element ID of the agent task. --- ### elementInstanceKeys? ```ts optional elementInstanceKeys?: ElementInstanceKeyFilterProperty[]; ``` The keys of element instances associated with this agent instance. If multiple keys are provided, the filter matches agent instances associated with all of the provided keys at the same time. --- ### lastUpdatedDate? ```ts optional lastUpdatedDate?: DateTimeFilterProperty; ``` The date the agent instance was last updated. --- ### processDefinitionId? ```ts optional processDefinitionId?: StringFilterProperty; ``` The BPMN process ID of the process definition associated with this agent instance. --- ### processDefinitionKey? ```ts optional processDefinitionKey?: ProcessDefinitionKeyFilterProperty; ``` The key of the process definition associated with this agent instance. --- ### processDefinitionVersion? ```ts optional processDefinitionVersion?: IntegerFilterProperty; ``` The version of the process definition associated with this agent instance. --- ### processDefinitionVersionTag? ```ts optional processDefinitionVersionTag?: StringFilterProperty; ``` The version tag of the process definition associated with this agent instance. --- ### processInstanceKey? ```ts optional processInstanceKey?: ProcessInstanceKeyFilterProperty; ``` The key of the process instance that owns this agent instance. --- ### rootProcessInstanceKey? ```ts optional rootProcessInstanceKey?: ProcessInstanceKeyFilterProperty; ``` The key of the root process instance. Filters agent instances belonging to a specific call hierarchy. The root process instance is the top-level ancestor in the process instance hierarchy. --- ### status? ```ts optional status?: AgentInstanceStatusFilterProperty; ``` The current status of the agent instance. --- ### tenantId? ```ts optional tenantId?: StringFilterProperty; ``` The tenant ID of the agent instance. --- ## Type Alias: AgentInstanceHistoryCommitStatusEnum ```ts type AgentInstanceHistoryCommitStatusEnum = (typeof AgentInstanceHistoryCommitStatusEnum)[keyof typeof AgentInstanceHistoryCommitStatusEnum]; ``` The commit status of a history item. COMMITTED: the producing job completed successfully. PENDING: the producing job is still active (in-flight). DISCARDED: the producing job failed; this item was superseded by a later activation. --- ## Type Alias: AgentInstanceHistoryCommitStatusExactMatch ```ts type AgentInstanceHistoryCommitStatusExactMatch = AgentInstanceHistoryCommitStatusEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: AgentInstanceHistoryCommitStatusExactMatchWritable ```ts type AgentInstanceHistoryCommitStatusExactMatchWritable = AgentInstanceHistoryCommitStatusEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: AgentInstanceHistoryCommitStatusFilterProperty ```ts type AgentInstanceHistoryCommitStatusFilterProperty = | AgentInstanceHistoryCommitStatusExactMatch | AdvancedAgentInstanceHistoryCommitStatusFilter; ``` AgentInstanceHistoryCommitStatusEnum property with full advanced search capabilities. --- ## Type Alias: AgentInstanceHistoryFilter ```ts type AgentInstanceHistoryFilter = object; ``` Agent instance history item search filter. ## Properties ### commitStatus? ```ts optional commitStatus?: AgentInstanceHistoryCommitStatusFilterProperty; ``` The commit status of the history item. Defaults to COMMITTED only. Include PENDING or DISCARDED explicitly to debug in-flight or failed activations. --- ### elementInstanceKey? ```ts optional elementInstanceKey?: ElementInstanceKeyFilterProperty; ``` The key of the element instance under which the history item was produced. --- ### historyItemKey? ```ts optional historyItemKey?: AgentHistoryItemKeyFilterProperty; ``` The unique key of the history item. --- ### jobKey? ```ts optional jobKey?: JobKeyFilterProperty; ``` The key of the job activation that produced the history item. --- ### loopIteration? ```ts optional loopIteration?: IntegerFilterProperty; ``` Filter by loop iteration number. --- ### producedAt? ```ts optional producedAt?: DateTimeFilterProperty; ``` The timestamp when the history item was produced. --- ### role? ```ts optional role?: AgentInstanceHistoryRoleFilterProperty; ``` The role of the history item. --- ## Type Alias: AgentInstanceHistoryItem ```ts type AgentInstanceHistoryItem = object; ``` A single history item to append to the agent instance's conversation history, submitted as part of the batch on an agent instance update request. ## Properties ### content ```ts content: AgentInstanceMessageContent[]; ``` The content blocks of this history item. --- ### historyItemId ```ts historyItemId: HistoryItemId; ``` Caller-assigned identifier used to detect and dedupe retries of the same item. For example, when a retried job activation resubmits history items it already sent in an earlier attempt, those items are not rejected; they are flagged via isDuplicate in the response instead. Must be non-blank. --- ### limits? ```ts optional limits?: AgentInstanceLimits; ``` The operational limits as of this entry. CONFIGURATION items only; omit for other roles. --- ### loopIteration ```ts loopIteration: LoopIterationId; ``` The loop iteration this item belongs to. --- ### metrics? ```ts optional metrics?: | AgentInstanceHistoryItemMetricsRequest | null; ``` Per-call token and latency metrics. Present on ASSISTANT items only. --- ### model? ```ts optional model?: string; ``` The LLM model identifier as of this entry. CONFIGURATION items only; omit for other roles. --- ### producedAt ```ts producedAt: string; ``` The agent-side timestamp of when this message was produced. --- ### provider? ```ts optional provider?: string; ``` The LLM provider as of this entry. CONFIGURATION items only; omit for other roles. --- ### role ```ts role: AgentInstanceHistoryRoleEnum; ``` The role of this history item in the conversation. --- ### systemPrompt? ```ts optional systemPrompt?: | AgentInstanceMessageContent[] | null; ``` The system prompt, as content blocks, as of this entry. CONFIGURATION items only; omit for other roles. Omit to leave the system prompt unchanged; when present, must be non-empty. --- ### toolCalls? ```ts optional toolCalls?: AgentInstanceToolCall[] | null; ``` Tool calls associated with this history item. For ASSISTANT items: tool calls dispatched by this LLM response. For TOOL_RESULT items: single-entry array referencing the originating tool call. Omit for USER items. --- ### tools? ```ts optional tools?: AgentTool[] | null; ``` The complete list of tools available to the agent as of this entry. CONFIGURATION items only; omit for other roles. Omit to leave the tool list unchanged; send an empty array to clear it. --- ## Type Alias: AgentInstanceHistoryItemMetrics ```ts type AgentInstanceHistoryItemMetrics = object; ``` Per-call token and latency metrics for an ASSISTANT history item. ## Properties ### cacheCreationTokenCount ```ts cacheCreationTokenCount: number | null; ``` Cache-creation tokens consumed by this LLM call. Null when not provided. --- ### cacheReadTokenCount ```ts cacheReadTokenCount: number | null; ``` Cache-read tokens consumed by this LLM call. Null when not provided. --- ### durationMs ```ts durationMs: number | null; ``` Wall-clock duration of the LLM call in milliseconds. Null when not provided. --- ### inputTokens ```ts inputTokens: number | null; ``` Input tokens consumed by this LLM call. Null when not provided. --- ### outputTokens ```ts outputTokens: number | null; ``` Output tokens produced by this LLM call. Null when not provided. --- ### reasoningTokenCount ```ts reasoningTokenCount: number | null; ``` Reasoning tokens consumed by this LLM call. Null when not provided. --- ## Type Alias: AgentInstanceHistoryItemMetricsRequest ```ts type AgentInstanceHistoryItemMetricsRequest = object; ``` Per-call token and latency metrics for an ASSISTANT history item, as submitted on a create/update request. All fields are optional: omit a field the caller has no value for rather than sending it as an explicit null. ## Properties ### cacheCreationTokenCount? ```ts optional cacheCreationTokenCount?: number | null; ``` Cache-creation tokens consumed by this LLM call. Null when not provided. --- ### cacheReadTokenCount? ```ts optional cacheReadTokenCount?: number | null; ``` Cache-read tokens consumed by this LLM call. Null when not provided. --- ### durationMs? ```ts optional durationMs?: number | null; ``` Wall-clock duration of the LLM call in milliseconds. Null when not provided. --- ### inputTokens? ```ts optional inputTokens?: number | null; ``` Input tokens consumed by this LLM call. Null when not provided. --- ### outputTokens? ```ts optional outputTokens?: number | null; ``` Output tokens produced by this LLM call. Null when not provided. --- ### reasoningTokenCount? ```ts optional reasoningTokenCount?: number | null; ``` Reasoning tokens consumed by this LLM call. Null when not provided. --- ## Type Alias: AgentInstanceHistoryItemResult ```ts type AgentInstanceHistoryItemResult = object; ``` A single conversation history item belonging to an agent instance. ## Properties ### agentInstanceKey ```ts agentInstanceKey: AgentInstanceKey; ``` The key of the agent instance this item belongs to. --- ### commitStatus ```ts commitStatus: AgentInstanceHistoryCommitStatusEnum; ``` The commit status of this history item. --- ### content ```ts content: AgentInstanceMessageContent[]; ``` The content blocks of this history item. --- ### elementInstanceKey ```ts elementInstanceKey: ElementInstanceKey; ``` The key of the AI Agent Task or ad-hoc sub-process element instance under which this item was produced. --- ### historyItemId ```ts historyItemId: HistoryItemId; ``` The client-supplied identifier this item was created with. Empty for items that don't carry one. Not unique: a job can be re-activated under a superseded lease any number of times before it completes, so one historyItemId can have zero or more DISCARDED records and at most one COMMITTED record, since only historyItemKey is guaranteed unique. Filter by commitStatus rather than assuming one record per historyItemId. --- ### historyItemKey ```ts historyItemKey: AgentHistoryItemKey; ``` The unique key for this history item. Stable and sortable by creation order. --- ### jobKey ```ts jobKey: JobKey; ``` The key of the job activation during which this item was produced. --- ### jobLeaseToken ```ts jobLeaseToken: JobLeaseToken; ``` The lease token of the activation that produced this item. --- ### limits ```ts limits: AgentInstanceLimits; ``` The operational limits as of this entry. CONFIGURATION items only; -1 on any field means "no limit configured" for other roles. --- ### loopIteration ```ts loopIteration: LoopIterationId; ``` The loop iteration this item belongs to. --- ### metrics ```ts metrics: | AgentInstanceHistoryItemMetrics | null; ``` Per-call token and latency metrics. Null when metrics were not provided at creation time. --- ### model ```ts model: string | null; ``` The LLM model identifier as of this entry. CONFIGURATION items only; null for other roles. --- ### producedAt ```ts producedAt: string; ``` The agent-side timestamp of when this message was produced. --- ### provider ```ts provider: string | null; ``` The LLM provider as of this entry. CONFIGURATION items only; null for other roles. --- ### role ```ts role: AgentInstanceHistoryRoleEnum; ``` The role of this history item in the conversation. --- ### systemPrompt ```ts systemPrompt: AgentInstanceMessageContent[]; ``` The system prompt, as content blocks, as of this entry. CONFIGURATION items only; empty for other roles. --- ### toolCalls ```ts toolCalls: AgentInstanceToolCall[]; ``` Tool calls for this item. Empty for USER items and ASSISTANT items with no tool dispatches. ASSISTANT items: dispatched tool calls. TOOL_RESULT items: single-entry array referencing the originating tool call. --- ### tools ```ts tools: AgentTool[]; ``` The complete list of tools available to the agent as of this entry. CONFIGURATION items only; empty for other roles. --- ## Type Alias: AgentInstanceHistoryRoleEnum ```ts type AgentInstanceHistoryRoleEnum = (typeof AgentInstanceHistoryRoleEnum)[keyof typeof AgentInstanceHistoryRoleEnum]; ``` The role of a history item in the agent conversation. --- ## Type Alias: AgentInstanceHistoryRoleExactMatch ```ts type AgentInstanceHistoryRoleExactMatch = AgentInstanceHistoryRoleEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: AgentInstanceHistoryRoleExactMatchWritable ```ts type AgentInstanceHistoryRoleExactMatchWritable = AgentInstanceHistoryRoleEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: AgentInstanceHistoryRoleFilterProperty ```ts type AgentInstanceHistoryRoleFilterProperty = AgentInstanceHistoryRoleExactMatch | AdvancedAgentInstanceHistoryRoleFilter; ``` AgentInstanceHistoryRoleEnum property with full advanced search capabilities. --- ## Type Alias: AgentInstanceHistorySearchQuery ```ts type AgentInstanceHistorySearchQuery = SearchQueryRequest & object; ``` Agent instance history search request. ## Type Declaration ### filter? ```ts optional filter?: AgentInstanceHistoryFilter; ``` The history item search filters. ### sort? ```ts optional sort?: AgentInstanceHistorySearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: AgentInstanceHistorySearchQueryResult ```ts type AgentInstanceHistorySearchQueryResult = SearchQueryResponse & object; ``` Agent instance history search response. ## Type Declaration ### items ```ts items: AgentInstanceHistoryItemResult[]; ``` The matching history items. --- ## Type Alias: AgentInstanceHistorySearchQuerySortRequest ```ts type AgentInstanceHistorySearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: "producedAt" | "historyItemKey" | "loopIteration"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: AgentInstanceKey ```ts type AgentInstanceKey = CamundaKey<"AgentInstanceKey">; ``` System-generated key for an agent instance. --- ## Type Alias: AgentInstanceKeyExactMatch ```ts type AgentInstanceKeyExactMatch = AgentInstanceKey; ``` Exact match Matches the value exactly. --- ## Type Alias: AgentInstanceKeyExactMatchWritable ```ts type AgentInstanceKeyExactMatchWritable = AgentInstanceKeyWritable; ``` Exact match Matches the value exactly. --- ## Type Alias: AgentInstanceKeyFilterProperty ```ts type AgentInstanceKeyFilterProperty = AgentInstanceKeyExactMatch | AdvancedAgentInstanceKeyFilter; ``` AgentInstanceKey property with full advanced search capabilities. --- ## Type Alias: AgentInstanceKeyWritable ```ts type AgentInstanceKeyWritable = LongKey; ``` System-generated key for an agent instance. --- ## Type Alias: AgentInstanceLimits ```ts type AgentInstanceLimits = object; ``` The configured limits for an agent instance, set once at creation. ## Properties ### maxModelCalls ```ts maxModelCalls: number; ``` Maximum LLM calls allowed. -1 if no limit is configured. --- ### maxTokens ```ts maxTokens: number; ``` Maximum total tokens allowed. -1 if no limit is configured. --- ### maxToolCalls ```ts maxToolCalls: number; ``` Maximum tool calls allowed. -1 if no limit is configured. --- ## Type Alias: AgentInstanceMessageContent ```ts type AgentInstanceMessageContent = | (object & AgentInstanceTextContent) | (object & AgentInstanceDocumentContent) | (object & AgentInstanceObjectContent); ``` A single content block within a history item. Discriminated by `contentType`. --- ## Type Alias: AgentInstanceMessageContentTypeEnum ```ts type AgentInstanceMessageContentTypeEnum = (typeof AgentInstanceMessageContentTypeEnum)[keyof typeof AgentInstanceMessageContentTypeEnum]; ``` The content type discriminator for a history item content block. --- ## Type Alias: AgentInstanceMetrics ```ts type AgentInstanceMetrics = object; ``` Aggregated metrics for an agent instance across all model calls. ## Properties ### cacheCreationTokenCount ```ts cacheCreationTokenCount: number; ``` Total tokens used to create prompt cache entries across all model calls. --- ### cacheReadTokenCount ```ts cacheReadTokenCount: number; ``` Total tokens read from prompt cache across all model calls. --- ### inputTokens ```ts inputTokens: number; ``` Total input tokens consumed across all model calls. --- ### modelCalls ```ts modelCalls: number; ``` Total number of LLM calls made. --- ### outputTokens ```ts outputTokens: number; ``` Total output tokens produced across all model calls. --- ### reasoningTokenCount ```ts reasoningTokenCount: number; ``` Total reasoning tokens consumed across all model calls. --- ### toolCalls ```ts toolCalls: number; ``` Total number of tool calls made. --- ## Type Alias: AgentInstanceObjectContent ```ts type AgentInstanceObjectContent = object; ``` Object content An arbitrary structured content block. Accepts any valid JSON value: objects, arrays, numbers, booleans, or strings. Use TEXT content for human-readable natural language; use OBJECT content for machine-readable structured data. ## Properties ### contentType ```ts contentType: string; ``` The content type discriminator. --- ### object ```ts object: unknown; ``` Arbitrary structured content — any valid JSON value (object, array, number, boolean, or string). --- ## Type Alias: AgentInstanceResult ```ts type AgentInstanceResult = object; ``` ## Properties ### agentDefinitionKey ```ts agentDefinitionKey: AgentDefinitionKey; ``` The key of the agent definition this agent instance is an instance of. --- ### agentInstanceKey ```ts agentInstanceKey: AgentInstanceKey; ``` The unique key for this agent instance. --- ### completionDate ```ts completionDate: string | null; ``` The date when this agent instance completed. Null while the agent is still running. --- ### creationDate ```ts creationDate: string; ``` The date when this agent instance was created. --- ### definition ```ts definition: AgentInstanceDefinitionResult; ``` The definition of the agent, including model, provider, and system prompt. Set at creation, but can change later via a CONFIGURATION history item. --- ### elementId ```ts elementId: ElementId; ``` The BPMN element ID of the ad-hoc sub-process or AI agent task that owns this agent instance. --- ### elementInstanceKeys ```ts elementInstanceKeys: ElementInstanceKey[]; ``` The keys of all element instances associated with this agent instance. --- ### lastUpdatedDate ```ts lastUpdatedDate: string; ``` The date when this agent instance was last updated. --- ### limits ```ts limits: AgentInstanceLimits; ``` The configured limits for this agent instance, set once at creation. --- ### metrics ```ts metrics: AgentInstanceMetrics; ``` Aggregated metrics across all loopIterations of this agent instance. Includes history items later discarded: metrics are counted when an item is accepted, not when it's committed. --- ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId; ``` The BPMN process ID of the process definition associated with this agent instance. --- ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey; ``` The key of the process definition associated with this agent instance. --- ### processDefinitionVersion ```ts processDefinitionVersion: number; ``` The version of the process definition associated with this agent instance. --- ### processDefinitionVersionTag ```ts processDefinitionVersionTag: string | null; ``` The version tag of the process definition associated with this agent instance. --- ### processInstanceKey ```ts processInstanceKey: ProcessInstanceKey; ``` The key of the process instance that owns this agent instance. --- ### rootProcessInstanceKey ```ts rootProcessInstanceKey: ProcessInstanceKey; ``` The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. --- ### status ```ts status: AgentInstanceStatusEnum; ``` --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID of this agent instance. --- ### tools ```ts tools: AgentTool[]; ``` The tools available to the agent. --- ## Type Alias: AgentInstanceSearchQuery ```ts type AgentInstanceSearchQuery = SearchQueryRequest & object; ``` Agent instance search request. ## Type Declaration ### filter? ```ts optional filter?: AgentInstanceFilter; ``` The agent instance search filters. ### sort? ```ts optional sort?: AgentInstanceSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: AgentInstanceSearchQueryResult ```ts type AgentInstanceSearchQueryResult = SearchQueryResponse & object; ``` Agent instance search response. ## Type Declaration ### items ```ts items: AgentInstanceResult[]; ``` The matching agent instances. --- ## Type Alias: AgentInstanceSearchQuerySortRequest ```ts type AgentInstanceSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "agentInstanceKey" | "agentDefinitionKey" | "status" | "elementId" | "processInstanceKey" | "rootProcessInstanceKey" | "processDefinitionKey" | "tenantId" | "creationDate" | "lastUpdatedDate" | "completionDate"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: AgentInstanceStatusEnum ```ts type AgentInstanceStatusEnum = (typeof AgentInstanceStatusEnum)[keyof typeof AgentInstanceStatusEnum]; ``` The current status of an agent instance. --- ## Type Alias: AgentInstanceStatusExactMatch ```ts type AgentInstanceStatusExactMatch = AgentInstanceStatusEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: AgentInstanceStatusExactMatchWritable ```ts type AgentInstanceStatusExactMatchWritable = AgentInstanceStatusEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: AgentInstanceStatusFilterProperty ```ts type AgentInstanceStatusFilterProperty = AgentInstanceStatusExactMatch | AdvancedAgentInstanceStatusFilter; ``` AgentInstanceStatusEnum property with full advanced search capabilities. --- ## Type Alias: AgentInstanceTextContent ```ts type AgentInstanceTextContent = object; ``` Text content A plain-text content block. ## Properties ### contentType ```ts contentType: string; ``` The content type discriminator. --- ### text ```ts text: string; ``` The text content. --- ## Type Alias: AgentInstanceToolCall ```ts type AgentInstanceToolCall = object; ``` A tool call associated with a history item. Used in both ASSISTANT and TOOL_RESULT items. ## Properties ### arguments ```ts arguments: | { [key: string]: unknown; } | null; ``` The tool call arguments as provided by the LLM. May be null or populated on any item, including TOOL_RESULT. --- ### elementId ```ts elementId: ElementId | null; ``` The BPMN element ID handling this tool. --- ### toolCallId ```ts toolCallId: string; ``` The LLM-assigned tool call ID. Correlates ASSISTANT items to their matching TOOL_RESULT items. --- ### toolName ```ts toolName: string; ``` The LLM-visible tool name. --- ## Type Alias: AgentInstanceUpdateRequest ```ts type AgentInstanceUpdateRequest = object; ``` Request to update the mutable state of an agent instance. ## Properties ### elementInstanceKey ```ts elementInstanceKey: ElementInstanceKey; ``` The key of the currently-active element instance for this agent instance. Used for ownership/equality validation against the stored agent instance and, when the supplied key differs from the previous association (re-entry of an ad-hoc sub-process or AI Agent task), appended to elementInstanceKeys with the reverse link updated on the supplied element instance. Only one element instance may hold this write claim at a time: any update from a different element instance is rejected while the current writer's job is still active. --- ### history? ```ts optional history?: AgentInstanceHistoryItem[] | null; ``` A batch of history items to append to the agent instance's conversation history, in request order. Each created item is echoed back in the response's createdHistory, positionally correlated. --- ### jobKey ```ts jobKey: JobKey; ``` The key of the job activation during which this update is being made. An update must always be attributed to the active job that produced it. --- ### jobLeaseToken ```ts jobLeaseToken: JobLeaseToken; ``` Opaque lease token received from the job activation response. Disambiguates this activation from any other activation of the same job: if the job is later retried, history items submitted under a superseded lease are discarded rather than committed. --- ### status? ```ts optional status?: AgentInstanceUpdateStatusEnum; ``` The new status of the agent instance. --- ## Type Alias: AgentInstanceUpdateResult ```ts type AgentInstanceUpdateResult = object; ``` Response returned after successfully updating an agent instance. ## Properties ### createdHistory ```ts createdHistory: AgentInstanceCreatedHistoryItem[]; ``` One entry per history item submitted in the request, in request order. Empty when no history items were submitted. --- ## Type Alias: AgentInstanceUpdateStatusEnum ```ts type AgentInstanceUpdateStatusEnum = (typeof AgentInstanceUpdateStatusEnum)[keyof typeof AgentInstanceUpdateStatusEnum]; ``` The status values that can be set on an agent instance via an update request. --- ## Type Alias: AgentTool ```ts type AgentTool = object; ``` A tool available to the agent. ## Properties ### description ```ts description: string | null; ``` A human-readable description of the tool. --- ### elementId ```ts elementId: ElementId | null; ``` The BPMN element ID of the tool element within the ad-hoc sub-process. --- ### name ```ts name: string; ``` The tool name as visible to the LLM. --- ## Type Alias: AncestorScopeInstruction ```ts type AncestorScopeInstruction = | (object & DirectAncestorKeyInstruction) | (object & InferredAncestorKeyInstruction) | (object & UseSourceParentKeyInstruction); ``` Defines the ancestor scope for the created element instances. The default behavior resembles a "direct" scope instruction with an `ancestorElementInstanceKey` of `"-1"`. --- ## Type Alias: AnyVariableSchema ```ts type AnyVariableSchema = z.ZodObject; ``` Any Zod object schema; used as the DTO that declares the variables to fetch. --- ## Type Alias: AssignClientToGroupData ```ts type AssignClientToGroupData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### clientId ```ts clientId: ClientId; ``` The client ID. #### groupId ```ts groupId: GroupId; ``` The group ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/groups/{groupId}/clients/{clientId}"; ``` --- ## Type Alias: AssignClientToGroupError ```ts type AssignClientToGroupError = AssignClientToGroupErrors[keyof AssignClientToGroupErrors]; ``` --- ## Type Alias: AssignClientToGroupErrors ```ts type AssignClientToGroupErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The group with the given ID was not found. --- ### 409 ```ts 409: ProblemDetail; ``` The client with the given ID is already assigned to the group. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: AssignClientToGroupResponse ```ts type AssignClientToGroupResponse = AssignClientToGroupResponses[keyof AssignClientToGroupResponses]; ``` --- ## Type Alias: AssignClientToGroupResponses ```ts type AssignClientToGroupResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The client was assigned successfully to the group. --- ## Type Alias: AssignClientToTenantData ```ts type AssignClientToTenantData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### clientId ```ts clientId: ClientId; ``` The unique identifier of the application. #### tenantId ```ts tenantId: TenantId; ``` The unique identifier of the tenant. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/tenants/{tenantId}/clients/{clientId}"; ``` --- ## Type Alias: AssignClientToTenantError ```ts type AssignClientToTenantError = AssignClientToTenantErrors[keyof AssignClientToTenantErrors]; ``` --- ## Type Alias: AssignClientToTenantErrors ```ts type AssignClientToTenantErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The tenant was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: AssignClientToTenantResponse ```ts type AssignClientToTenantResponse = AssignClientToTenantResponses[keyof AssignClientToTenantResponses]; ``` --- ## Type Alias: AssignClientToTenantResponses ```ts type AssignClientToTenantResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The client was successfully assigned to the tenant. --- ## Type Alias: AssignGroupToTenantData ```ts type AssignGroupToTenantData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### groupId ```ts groupId: GroupId; ``` The unique identifier of the group. #### tenantId ```ts tenantId: TenantId; ``` The unique identifier of the tenant. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/tenants/{tenantId}/groups/{groupId}"; ``` --- ## Type Alias: AssignGroupToTenantError ```ts type AssignGroupToTenantError = AssignGroupToTenantErrors[keyof AssignGroupToTenantErrors]; ``` --- ## Type Alias: AssignGroupToTenantErrors ```ts type AssignGroupToTenantErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Not found. The tenant or group was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: AssignGroupToTenantResponse ```ts type AssignGroupToTenantResponse = AssignGroupToTenantResponses[keyof AssignGroupToTenantResponses]; ``` --- ## Type Alias: AssignGroupToTenantResponses ```ts type AssignGroupToTenantResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The group was successfully assigned to the tenant. --- ## Type Alias: AssignMappingRuleToGroupData ```ts type AssignMappingRuleToGroupData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### groupId ```ts groupId: GroupId; ``` The group ID. #### mappingRuleId ```ts mappingRuleId: MappingRuleId; ``` The mapping rule ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/groups/{groupId}/mapping-rules/{mappingRuleId}"; ``` --- ## Type Alias: AssignMappingRuleToGroupError ```ts type AssignMappingRuleToGroupError = AssignMappingRuleToGroupErrors[keyof AssignMappingRuleToGroupErrors]; ``` --- ## Type Alias: AssignMappingRuleToGroupErrors ```ts type AssignMappingRuleToGroupErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The group or mapping rule with the given ID was not found. --- ### 409 ```ts 409: ProblemDetail; ``` The mapping rule with the given ID is already assigned to the group. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: AssignMappingRuleToGroupResponse ```ts type AssignMappingRuleToGroupResponse = AssignMappingRuleToGroupResponses[keyof AssignMappingRuleToGroupResponses]; ``` --- ## Type Alias: AssignMappingRuleToGroupResponses ```ts type AssignMappingRuleToGroupResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The mapping rule was assigned successfully to the group. --- ## Type Alias: AssignMappingRuleToTenantData ```ts type AssignMappingRuleToTenantData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### mappingRuleId ```ts mappingRuleId: MappingRuleId; ``` The unique identifier of the mapping rule. #### tenantId ```ts tenantId: TenantId; ``` The unique identifier of the tenant. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/tenants/{tenantId}/mapping-rules/{mappingRuleId}"; ``` --- ## Type Alias: AssignMappingRuleToTenantError ```ts type AssignMappingRuleToTenantError = AssignMappingRuleToTenantErrors[keyof AssignMappingRuleToTenantErrors]; ``` --- ## Type Alias: AssignMappingRuleToTenantErrors ```ts type AssignMappingRuleToTenantErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Not found. The tenant or mapping rule was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: AssignMappingRuleToTenantResponse ```ts type AssignMappingRuleToTenantResponse = AssignMappingRuleToTenantResponses[keyof AssignMappingRuleToTenantResponses]; ``` --- ## Type Alias: AssignMappingRuleToTenantResponses ```ts type AssignMappingRuleToTenantResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The mapping rule was successfully assigned to the tenant. --- ## Type Alias: AssignProcessInstanceBusinessIdData ```ts type AssignProcessInstanceBusinessIdData = object; ``` ## Properties ### body ```ts body: ProcessInstanceBusinessIdAssignmentInstruction; ``` --- ### path ```ts path: object; ``` #### processInstanceKey ```ts processInstanceKey: ProcessInstanceKeyWritable; ``` The key of the process instance to assign the business id to. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/{processInstanceKey}/business-id-assignment"; ``` --- ## Type Alias: AssignProcessInstanceBusinessIdError ```ts type AssignProcessInstanceBusinessIdError = AssignProcessInstanceBusinessIdErrors[keyof AssignProcessInstanceBusinessIdErrors]; ``` --- ## Type Alias: AssignProcessInstanceBusinessIdErrors ```ts type AssignProcessInstanceBusinessIdErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 404 ```ts 404: ProblemDetail; ``` The process instance is not found. --- ### 409 ```ts 409: ProblemDetail; ``` The business id assignment failed because the process instance is not eligible, for example it already has a different business id, it is a call-activity child, or business id uniqueness enforcement is enabled. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: AssignProcessInstanceBusinessIdResponse ```ts type AssignProcessInstanceBusinessIdResponse = AssignProcessInstanceBusinessIdResponses[keyof AssignProcessInstanceBusinessIdResponses]; ``` --- ## Type Alias: AssignProcessInstanceBusinessIdResponses ```ts type AssignProcessInstanceBusinessIdResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The business id is assigned to the process instance. --- ## Type Alias: AssignRoleToClientData ```ts type AssignRoleToClientData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### clientId ```ts clientId: ClientId; ``` The client ID. #### roleId ```ts roleId: RoleId; ``` The role ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/roles/{roleId}/clients/{clientId}"; ``` --- ## Type Alias: AssignRoleToClientError ```ts type AssignRoleToClientError = AssignRoleToClientErrors[keyof AssignRoleToClientErrors]; ``` --- ## Type Alias: AssignRoleToClientErrors ```ts type AssignRoleToClientErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The role with the given ID was not found. --- ### 409 ```ts 409: ProblemDetail; ``` The role was already assigned to the client with the given ID. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: AssignRoleToClientResponse ```ts type AssignRoleToClientResponse = AssignRoleToClientResponses[keyof AssignRoleToClientResponses]; ``` --- ## Type Alias: AssignRoleToClientResponses ```ts type AssignRoleToClientResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The role was assigned successfully to the client. --- ## Type Alias: AssignRoleToGroupData ```ts type AssignRoleToGroupData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### groupId ```ts groupId: GroupId; ``` The group ID. #### roleId ```ts roleId: RoleId; ``` The role ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/roles/{roleId}/groups/{groupId}"; ``` --- ## Type Alias: AssignRoleToGroupError ```ts type AssignRoleToGroupError = AssignRoleToGroupErrors[keyof AssignRoleToGroupErrors]; ``` --- ## Type Alias: AssignRoleToGroupErrors ```ts type AssignRoleToGroupErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The role or group with the given ID was not found. --- ### 409 ```ts 409: ProblemDetail; ``` The role is already assigned to the group with the given ID. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: AssignRoleToGroupResponse ```ts type AssignRoleToGroupResponse = AssignRoleToGroupResponses[keyof AssignRoleToGroupResponses]; ``` --- ## Type Alias: AssignRoleToGroupResponses ```ts type AssignRoleToGroupResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The role was assigned successfully to the group. --- ## Type Alias: AssignRoleToMappingRuleData ```ts type AssignRoleToMappingRuleData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### mappingRuleId ```ts mappingRuleId: MappingRuleId; ``` The mapping rule ID. #### roleId ```ts roleId: RoleId; ``` The role ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/roles/{roleId}/mapping-rules/{mappingRuleId}"; ``` --- ## Type Alias: AssignRoleToMappingRuleError ```ts type AssignRoleToMappingRuleError = AssignRoleToMappingRuleErrors[keyof AssignRoleToMappingRuleErrors]; ``` --- ## Type Alias: AssignRoleToMappingRuleErrors ```ts type AssignRoleToMappingRuleErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The role or mapping rule with the given ID was not found. --- ### 409 ```ts 409: ProblemDetail; ``` The role is already assigned to the mapping rule with the given ID. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: AssignRoleToMappingRuleResponse ```ts type AssignRoleToMappingRuleResponse = AssignRoleToMappingRuleResponses[keyof AssignRoleToMappingRuleResponses]; ``` --- ## Type Alias: AssignRoleToMappingRuleResponses ```ts type AssignRoleToMappingRuleResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The role was assigned successfully to the mapping rule. --- ## Type Alias: AssignRoleToTenantData ```ts type AssignRoleToTenantData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### roleId ```ts roleId: RoleId; ``` The unique identifier of the role. #### tenantId ```ts tenantId: TenantId; ``` The unique identifier of the tenant. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/tenants/{tenantId}/roles/{roleId}"; ``` --- ## Type Alias: AssignRoleToTenantError ```ts type AssignRoleToTenantError = AssignRoleToTenantErrors[keyof AssignRoleToTenantErrors]; ``` --- ## Type Alias: AssignRoleToTenantErrors ```ts type AssignRoleToTenantErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Not found. The tenant or role was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: AssignRoleToTenantResponse ```ts type AssignRoleToTenantResponse = AssignRoleToTenantResponses[keyof AssignRoleToTenantResponses]; ``` --- ## Type Alias: AssignRoleToTenantResponses ```ts type AssignRoleToTenantResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The role was successfully assigned to the tenant. --- ## Type Alias: AssignRoleToUserData ```ts type AssignRoleToUserData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### roleId ```ts roleId: RoleId; ``` The role ID. #### username ```ts username: Username; ``` The user username. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/roles/{roleId}/users/{username}"; ``` --- ## Type Alias: AssignRoleToUserError ```ts type AssignRoleToUserError = AssignRoleToUserErrors[keyof AssignRoleToUserErrors]; ``` --- ## Type Alias: AssignRoleToUserErrors ```ts type AssignRoleToUserErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The role or user with the given ID or username was not found. --- ### 409 ```ts 409: ProblemDetail; ``` The role is already assigned to the user with the given ID. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: AssignRoleToUserResponse ```ts type AssignRoleToUserResponse = AssignRoleToUserResponses[keyof AssignRoleToUserResponses]; ``` --- ## Type Alias: AssignRoleToUserResponses ```ts type AssignRoleToUserResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The role was assigned successfully to the user. --- ## Type Alias: AssignUserTaskData ```ts type AssignUserTaskData = object; ``` ## Properties ### body ```ts body: UserTaskAssignmentRequest; ``` --- ### path ```ts path: object; ``` #### userTaskKey ```ts userTaskKey: UserTaskKeyWritable; ``` The key of the user task to assign. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/user-tasks/{userTaskKey}/assignment"; ``` --- ## Type Alias: AssignUserTaskError ```ts type AssignUserTaskError = AssignUserTaskErrors[keyof AssignUserTaskErrors]; ``` --- ## Type Alias: AssignUserTaskErrors ```ts type AssignUserTaskErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 404 ```ts 404: ProblemDetail; ``` The user task with the given key was not found. --- ### 409 ```ts 409: ProblemDetail; ``` The user task with the given key is in the wrong state currently. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ### 504 ```ts 504: ProblemDetail; ``` The request timed out between the gateway and the broker. For these endpoints, this often happens when user task listeners are configured and the corresponding listener job is not completed within the request timeout. Common causes include no available job workers for the listener type, busy or crashed job workers, or delayed job completion. As with any gateway timeout, general timeout causes (for example transient network issues) can also result in a 504 response. Troubleshooting: - verify that job workers for the listener type are running and healthy - check worker logs for crashes, retries, and completion failures - check network connectivity between workers, gateway, and broker - retry with backoff after transient failures - fail without retries if a problem persists --- ## Type Alias: AssignUserTaskResponse ```ts type AssignUserTaskResponse = AssignUserTaskResponses[keyof AssignUserTaskResponses]; ``` --- ## Type Alias: AssignUserTaskResponses ```ts type AssignUserTaskResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The user task's assignment was adjusted. --- ## Type Alias: AssignUserToGroupData ```ts type AssignUserToGroupData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### groupId ```ts groupId: GroupId; ``` The group ID. #### username ```ts username: Username; ``` The user username. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/groups/{groupId}/users/{username}"; ``` --- ## Type Alias: AssignUserToGroupError ```ts type AssignUserToGroupError = AssignUserToGroupErrors[keyof AssignUserToGroupErrors]; ``` --- ## Type Alias: AssignUserToGroupErrors ```ts type AssignUserToGroupErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The group or user with the given ID or username was not found. --- ### 409 ```ts 409: ProblemDetail; ``` The user with the given ID is already assigned to the group. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: AssignUserToGroupResponse ```ts type AssignUserToGroupResponse = AssignUserToGroupResponses[keyof AssignUserToGroupResponses]; ``` --- ## Type Alias: AssignUserToGroupResponses ```ts type AssignUserToGroupResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The user was assigned successfully to the group. --- ## Type Alias: AssignUserToTenantData ```ts type AssignUserToTenantData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### tenantId ```ts tenantId: TenantId; ``` The unique identifier of the tenant. #### username ```ts username: Username; ``` The unique identifier of the user. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/tenants/{tenantId}/users/{username}"; ``` --- ## Type Alias: AssignUserToTenantError ```ts type AssignUserToTenantError = AssignUserToTenantErrors[keyof AssignUserToTenantErrors]; ``` --- ## Type Alias: AssignUserToTenantErrors ```ts type AssignUserToTenantErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Not found. The tenant or user was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: AssignUserToTenantResponse ```ts type AssignUserToTenantResponse = AssignUserToTenantResponses[keyof AssignUserToTenantResponses]; ``` --- ## Type Alias: AssignUserToTenantResponses ```ts type AssignUserToTenantResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The user was successfully assigned to the tenant. --- ## Type Alias: AuditLogActorTypeEnum ```ts type AuditLogActorTypeEnum = (typeof AuditLogActorTypeEnum)[keyof typeof AuditLogActorTypeEnum]; ``` The type of actor who performed the operation. --- ## Type Alias: AuditLogActorTypeExactMatch ```ts type AuditLogActorTypeExactMatch = AuditLogActorTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: AuditLogActorTypeExactMatchWritable ```ts type AuditLogActorTypeExactMatchWritable = AuditLogActorTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: AuditLogActorTypeFilterProperty ```ts type AuditLogActorTypeFilterProperty = AuditLogActorTypeExactMatch | AdvancedActorTypeFilter; ``` AuditLogActorTypeEnum property with full advanced search capabilities. --- ## Type Alias: AuditLogCategoryEnum ```ts type AuditLogCategoryEnum = (typeof AuditLogCategoryEnum)[keyof typeof AuditLogCategoryEnum]; ``` The category of the audit log operation. --- ## Type Alias: AuditLogEntityKey ```ts type AuditLogEntityKey = CamundaKey<"AuditLogEntityKey">; ``` System-generated entity key for an audit log entry. --- ## Type Alias: AuditLogEntityKeyExactMatch ```ts type AuditLogEntityKeyExactMatch = AuditLogEntityKey; ``` Exact match Matches the value exactly. --- ## Type Alias: AuditLogEntityKeyExactMatchWritable ```ts type AuditLogEntityKeyExactMatchWritable = AuditLogEntityKey; ``` Exact match Matches the value exactly. --- ## Type Alias: AuditLogEntityKeyFilterProperty ```ts type AuditLogEntityKeyFilterProperty = AuditLogEntityKeyExactMatch | AdvancedAuditLogEntityKeyFilter; ``` EntityKey property with full advanced search capabilities. --- ## Type Alias: AuditLogEntityTypeEnum ```ts type AuditLogEntityTypeEnum = (typeof AuditLogEntityTypeEnum)[keyof typeof AuditLogEntityTypeEnum]; ``` The type of entity affected by the operation. --- ## Type Alias: AuditLogFilter ```ts type AuditLogFilter = object; ``` Audit log filter request ## Properties ### actorId? ```ts optional actorId?: StringFilterProperty; ``` The actor ID search filter. --- ### actorType? ```ts optional actorType?: AuditLogActorTypeFilterProperty; ``` The actor type search filter. --- ### agentElementId? ```ts optional agentElementId?: StringFilterProperty; ``` The agent element ID search filter. --- ### auditLogKey? ```ts optional auditLogKey?: AuditLogKeyFilterProperty; ``` The audit log key search filter. --- ### batchOperationType? ```ts optional batchOperationType?: BatchOperationTypeFilterProperty; ``` The batch operation type search filter. --- ### category? ```ts optional category?: CategoryFilterProperty; ``` The category search filter. --- ### decisionDefinitionId? ```ts optional decisionDefinitionId?: StringFilterProperty; ``` The decision definition ID search filter. --- ### decisionDefinitionKey? ```ts optional decisionDefinitionKey?: DecisionDefinitionKeyFilterProperty; ``` The decision definition key search filter. --- ### decisionEvaluationKey? ```ts optional decisionEvaluationKey?: DecisionEvaluationKeyFilterProperty; ``` The decision evaluation key search filter. --- ### decisionRequirementsId? ```ts optional decisionRequirementsId?: StringFilterProperty; ``` The decision requirements ID search filter. --- ### decisionRequirementsKey? ```ts optional decisionRequirementsKey?: DecisionRequirementsKeyFilterProperty; ``` The decision requirements key search filter. --- ### deploymentKey? ```ts optional deploymentKey?: DeploymentKeyFilterProperty; ``` The deployment key search filter. --- ### elementInstanceKey? ```ts optional elementInstanceKey?: ElementInstanceKeyFilterProperty; ``` The element instance key search filter. --- ### entityDescription? ```ts optional entityDescription?: StringFilterProperty; ``` The entity description filter. --- ### entityKey? ```ts optional entityKey?: AuditLogEntityKeyFilterProperty; ``` The entity key search filter. --- ### entityType? ```ts optional entityType?: EntityTypeFilterProperty; ``` The entity type search filter. --- ### formKey? ```ts optional formKey?: FormKeyFilterProperty; ``` The form key search filter. --- ### inboundChannelToolName? ```ts optional inboundChannelToolName?: StringFilterProperty; ``` The inbound channel tool name search filter. --- ### inboundChannelType? ```ts optional inboundChannelType?: StringFilterProperty; ``` The inbound channel type search filter (e.g. MCP). --- ### jobKey? ```ts optional jobKey?: JobKeyFilterProperty; ``` The job key search filter. --- ### operationType? ```ts optional operationType?: OperationTypeFilterProperty; ``` The operation type search filter. --- ### processDefinitionId? ```ts optional processDefinitionId?: StringFilterProperty; ``` The process definition ID search filter. --- ### processDefinitionKey? ```ts optional processDefinitionKey?: ProcessDefinitionKeyFilterProperty; ``` The process definition key search filter. --- ### processInstanceKey? ```ts optional processInstanceKey?: ProcessInstanceKeyFilterProperty; ``` The process instance key search filter. --- ### relatedEntityKey? ```ts optional relatedEntityKey?: AuditLogEntityKeyFilterProperty; ``` The related entity key search filter. --- ### relatedEntityType? ```ts optional relatedEntityType?: EntityTypeFilterProperty; ``` The related entity type search filter. --- ### resourceKey? ```ts optional resourceKey?: ResourceKeyFilterProperty; ``` The resource key search filter. --- ### result? ```ts optional result?: AuditLogResultFilterProperty; ``` The result search filter. --- ### tenantId? ```ts optional tenantId?: StringFilterProperty; ``` The tenant ID search filter. --- ### timestamp? ```ts optional timestamp?: DateTimeFilterProperty; ``` The timestamp search filter. --- ### userTaskKey? ```ts optional userTaskKey?: BasicStringFilterProperty; ``` The user task key search filter. --- ## Type Alias: AuditLogKey ```ts type AuditLogKey = CamundaKey<"AuditLogKey">; ``` System-generated key for an audit log entry. --- ## Type Alias: AuditLogKeyExactMatch ```ts type AuditLogKeyExactMatch = AuditLogKey; ``` Exact match Matches the value exactly. --- ## Type Alias: AuditLogKeyExactMatchWritable ```ts type AuditLogKeyExactMatchWritable = AuditLogKeyWritable; ``` Exact match Matches the value exactly. --- ## Type Alias: AuditLogKeyFilterProperty ```ts type AuditLogKeyFilterProperty = AuditLogKeyExactMatch | AdvancedAuditLogKeyFilter; ``` AuditLogKey property with full advanced search capabilities. --- ## Type Alias: AuditLogKeyWritable ```ts type AuditLogKeyWritable = LongKey; ``` System-generated key for an audit log entry. --- ## Type Alias: AuditLogOperationTypeEnum ```ts type AuditLogOperationTypeEnum = (typeof AuditLogOperationTypeEnum)[keyof typeof AuditLogOperationTypeEnum]; ``` The type of operation performed. --- ## Type Alias: AuditLogResult ```ts type AuditLogResult = object; ``` Audit log item. ## Properties ### actorId ```ts actorId: string | null; ``` The ID of the actor who performed the operation. --- ### actorType ```ts actorType: AuditLogActorTypeEnum | null; ``` The type of the actor who performed the operation. --- ### agentElementId ```ts agentElementId: string | null; ``` The element ID of the agent that performed the operation (e.g. ad-hoc subprocess element ID). --- ### auditLogKey ```ts auditLogKey: AuditLogKey; ``` The unique key of the audit log entry. --- ### batchOperationKey ```ts batchOperationKey: BatchOperationKey | null; ``` Key of the batch operation. --- ### batchOperationType ```ts batchOperationType: BatchOperationTypeEnum | null; ``` The type of batch operation performed, if this is part of a batch. --- ### category ```ts category: AuditLogCategoryEnum; ``` --- ### decisionDefinitionId ```ts decisionDefinitionId: DecisionDefinitionId | null; ``` The decision definition ID. --- ### decisionDefinitionKey ```ts decisionDefinitionKey: DecisionDefinitionKey | null; ``` The key of the decision definition. --- ### decisionEvaluationKey ```ts decisionEvaluationKey: DecisionEvaluationKey | null; ``` The key of the decision evaluation. --- ### decisionRequirementsId ```ts decisionRequirementsId: string | null; ``` The decision requirements ID. --- ### decisionRequirementsKey ```ts decisionRequirementsKey: DecisionRequirementsKey | null; ``` The assigned key of the decision requirements. --- ### deploymentKey ```ts deploymentKey: DeploymentKey | null; ``` The key of the deployment. --- ### elementInstanceKey ```ts elementInstanceKey: ElementInstanceKey | null; ``` The key of the element instance. --- ### entityDescription ```ts entityDescription: string | null; ``` Additional description of the entity affected by the operation. For example, for variable operations, this will contain the variable name. --- ### entityKey ```ts entityKey: AuditLogEntityKey; ``` --- ### entityType ```ts entityType: AuditLogEntityTypeEnum; ``` --- ### formKey ```ts formKey: FormKey | null; ``` The key of the form. --- ### inboundChannelToolName ```ts inboundChannelToolName: string | null; ``` The tool name of the inbound channel (e.g. the MCP tool that triggered the operation). --- ### inboundChannelType ```ts inboundChannelType: string | null; ``` The type of the inbound channel that triggered the operation (e.g. MCP). --- ### jobKey ```ts jobKey: JobKey | null; ``` The key of the job. --- ### operationType ```ts operationType: AuditLogOperationTypeEnum; ``` --- ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId | null; ``` The process definition ID. --- ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey | null; ``` The key of the process definition. --- ### processInstanceKey ```ts processInstanceKey: ProcessInstanceKey | null; ``` The key of the process instance. --- ### relatedEntityKey ```ts relatedEntityKey: AuditLogEntityKey | null; ``` The key of the related entity. The content depends on the operation type and entity type. For example, for authorization operations, this will contain the ID of the owner (e.g., user or group) the authorization belongs to. --- ### relatedEntityType ```ts relatedEntityType: AuditLogEntityTypeEnum | null; ``` The type of the related entity. The content depends on the operation type and entity type. For example, for authorization operations, this will contain the type of the owner (e.g., USER or GROUP) the authorization belongs to. --- ### resourceKey ```ts resourceKey: ResourceKey | null; ``` The system-assigned key for this resource. --- ### result ```ts result: AuditLogResultEnum; ``` --- ### rootProcessInstanceKey ```ts rootProcessInstanceKey: ProcessInstanceKey | null; ``` The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. --- ### tenantId ```ts tenantId: TenantId | null; ``` The tenant ID of the audit log. --- ### timestamp ```ts timestamp: string; ``` The timestamp when the operation occurred. --- ### userTaskKey ```ts userTaskKey: UserTaskKey | null; ``` The key of the user task. --- ## Type Alias: AuditLogResultEnum ```ts type AuditLogResultEnum = (typeof AuditLogResultEnum)[keyof typeof AuditLogResultEnum]; ``` The result status of the operation. --- ## Type Alias: AuditLogResultExactMatch ```ts type AuditLogResultExactMatch = AuditLogResultEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: AuditLogResultExactMatchWritable ```ts type AuditLogResultExactMatchWritable = AuditLogResultEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: AuditLogResultFilterProperty ```ts type AuditLogResultFilterProperty = AuditLogResultExactMatch | AdvancedResultFilter; ``` AuditLogResultEnum property with full advanced search capabilities. --- ## Type Alias: AuditLogSearchQueryRequest ```ts type AuditLogSearchQueryRequest = SearchQueryRequest & object; ``` Audit log search request. ## Type Declaration ### filter? ```ts optional filter?: AuditLogFilter; ``` The audit log search filters. ### sort? ```ts optional sort?: AuditLogSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: AuditLogSearchQueryResult ```ts type AuditLogSearchQueryResult = SearchQueryResponse & object; ``` Audit log search response. ## Type Declaration ### items ```ts items: AuditLogResult[]; ``` The matching audit logs. --- ## Type Alias: AuditLogSearchQuerySortRequest ```ts type AuditLogSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "actorId" | "actorType" | "auditLogKey" | "batchOperationKey" | "batchOperationType" | "category" | "decisionDefinitionId" | "decisionDefinitionKey" | "decisionEvaluationKey" | "decisionRequirementsId" | "decisionRequirementsKey" | "elementInstanceKey" | "entityKey" | "entityType" | "jobKey" | "operationType" | "processDefinitionId" | "processDefinitionKey" | "processInstanceKey" | "inboundChannelType" | "inboundChannelToolName" | "result" | "tenantId" | "timestamp" | "userTaskKey"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: AuthStrategy ```ts type AuthStrategy = "NONE" | "OAUTH" | "BASIC"; ``` --- ## Type Alias: AuthenticationConfigurationResponse ```ts type AuthenticationConfigurationResponse = object; ``` Configuration for authentication and session management. ## Properties ### canLogout ```ts canLogout: boolean; ``` Whether users can log out (false for SaaS deployments). --- ### isCamundaGroupsEnabled ```ts isCamundaGroupsEnabled: boolean; ``` Whether Camunda manages group membership locally, as opposed to delegating it to the identity provider via a groups claim. --- ### isLoginDelegated ```ts isLoginDelegated: boolean; ``` Whether login is delegated to an external identity provider. --- ## Type Alias: AuthorizationCreateResult ```ts type AuthorizationCreateResult = object; ``` ## Properties ### authorizationKey ```ts authorizationKey: AuthorizationKey; ``` The key of the created authorization. --- ## Type Alias: AuthorizationFilter ```ts type AuthorizationFilter = object; ``` Authorization search filter. ## Properties ### ownerId? ```ts optional ownerId?: string; ``` The ID of the owner of permissions. --- ### ownerType? ```ts optional ownerType?: OwnerTypeEnum; ``` --- ### resourceIds? ```ts optional resourceIds?: string[]; ``` The IDs of the resource to search permissions for. --- ### resourcePropertyNames? ```ts optional resourcePropertyNames?: string[]; ``` The names of the resource properties to search permissions for. --- ### resourceType? ```ts optional resourceType?: ResourceTypeEnum; ``` The type of resource to search permissions for. --- ## Type Alias: AuthorizationIdBasedRequest ```ts type AuthorizationIdBasedRequest = object; ``` ## Properties ### ownerId ```ts ownerId: string; ``` The ID of the owner of the permissions. --- ### ownerType ```ts ownerType: OwnerTypeEnum; ``` --- ### permissionTypes ```ts permissionTypes: PermissionTypeEnum[]; ``` The permission types to add. --- ### resourceId ```ts resourceId: string; ``` The ID of the resource to add permissions to. --- ### resourceType ```ts resourceType: ResourceTypeEnum; ``` The type of resource to add permissions to. --- ## Type Alias: AuthorizationKey ```ts type AuthorizationKey = CamundaKey<"AuthorizationKey">; ``` System-generated key for an authorization. --- ## Type Alias: AuthorizationKeyWritable ```ts type AuthorizationKeyWritable = LongKey; ``` System-generated key for an authorization. --- ## Type Alias: AuthorizationPropertyBasedRequest ```ts type AuthorizationPropertyBasedRequest = object; ``` ## Properties ### ownerId ```ts ownerId: string; ``` The ID of the owner of the permissions. --- ### ownerType ```ts ownerType: OwnerTypeEnum; ``` --- ### permissionTypes ```ts permissionTypes: PermissionTypeEnum[]; ``` The permission types to add. --- ### resourcePropertyName ```ts resourcePropertyName: string; ``` The name of the resource property on which this authorization is based. --- ### resourceType ```ts resourceType: ResourceTypeEnum; ``` The type of resource to add permissions to. --- ## Type Alias: AuthorizationRequest ```ts type AuthorizationRequest = AuthorizationIdBasedRequest | AuthorizationPropertyBasedRequest; ``` Defines an authorization request. Either an id-based or a property-based authorization can be provided. --- ## Type Alias: AuthorizationResult ```ts type AuthorizationResult = object; ``` ## Properties ### authorizationKey ```ts authorizationKey: AuthorizationKey; ``` The key of the authorization. --- ### ownerId ```ts ownerId: string; ``` The ID of the owner of permissions. --- ### ownerType ```ts ownerType: OwnerTypeEnum; ``` --- ### permissionTypes ```ts permissionTypes: PermissionTypeEnum[]; ``` Specifies the types of the permissions. --- ### resourceId ```ts resourceId: string | null; ``` ID of the resource the permission relates to (mutually exclusive with `resourcePropertyName`). --- ### resourcePropertyName ```ts resourcePropertyName: string | null; ``` The name of the resource property the permission relates to (mutually exclusive with `resourceId`). --- ### resourceType ```ts resourceType: ResourceTypeEnum; ``` The type of resource that the permissions relate to. --- ## Type Alias: AuthorizationSearchQuery ```ts type AuthorizationSearchQuery = SearchQueryRequest & object; ``` ## Type Declaration ### filter? ```ts optional filter?: AuthorizationFilter; ``` The authorization search filters. ### sort? ```ts optional sort?: AuthorizationSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: AuthorizationSearchQuerySortRequest ```ts type AuthorizationSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "ownerId" | "ownerType" | "resourceId" | "resourcePropertyName" | "resourceType"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: AuthorizationSearchResult ```ts type AuthorizationSearchResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: AuthorizationResult[]; ``` The matching authorizations. --- ## Type Alias: BackpressureSeverity ```ts type BackpressureSeverity = "healthy" | "soft" | "severe"; ``` --- ## Type Alias: BackupId ```ts type BackupId = number; ``` Backup ID The id of the backup. Must be a positive numerical value. As backups are logically ordered by their ids (ascending), each successive backup must use a higher id than the previous one. --- ## Type Alias: BackupIdPrefix ```ts type BackupIdPrefix = string; ``` Backup ID Prefix A prefix of a backup id, followed by a single '*' as a wildcard, matching any backup id starting with the given prefix. --- ## Type Alias: BackupInfo ```ts type BackupInfo = object; ``` Backup Info Detailed status of a runtime backup. The aggregated state is computed from the backup state of each partition as: - If the backup of all partitions is 'COMPLETED', the overall state is 'COMPLETED'. - If one partition is 'FAILED', the overall state is 'FAILED'. - Otherwise, if one partition is 'DOES_NOT_EXIST', the overall state is 'INCOMPLETE'. - Otherwise, if one partition is 'IN_PROGRESS', the overall state is 'IN_PROGRESS'. ## Properties ### backupId ```ts backupId: BackupId; ``` The id of the backup. --- ### details ```ts readonly details: PartitionBackupInfo[]; ``` Detailed status of the backup per partition. Always contains every partition of the physical tenant. --- ### failureReason ```ts failureReason: string | null; ``` Reason for failure if the state is 'FAILED'. --- ### state ```ts state: StateCode; ``` The aggregated state of the backup. --- ## Type Alias: BackupInfoWritable ```ts type BackupInfoWritable = object; ``` Backup Info Detailed status of a runtime backup. The aggregated state is computed from the backup state of each partition as: - If the backup of all partitions is 'COMPLETED', the overall state is 'COMPLETED'. - If one partition is 'FAILED', the overall state is 'FAILED'. - Otherwise, if one partition is 'DOES_NOT_EXIST', the overall state is 'INCOMPLETE'. - Otherwise, if one partition is 'IN_PROGRESS', the overall state is 'IN_PROGRESS'. ## Properties ### failureReason ```ts failureReason: string | null; ``` Reason for failure if the state is 'FAILED'. --- ## Type Alias: BackupType ```ts type BackupType = "MANUAL_BACKUP" | "SCHEDULED_BACKUP"; ``` Backup Type The type of the backup. --- ## Type Alias: BaseProcessInstanceFilterFields ```ts type BaseProcessInstanceFilterFields = object; ``` Base process instance search filter. ## Properties ### ~~batchOperationId?~~ ```ts optional batchOperationId?: StringFilterProperty; ``` The batch operation id. **Deprecated**: Use `batchOperationKey` instead. This field will be removed in a future release. If both `batchOperationId` and `batchOperationKey` are provided, the request will be rejected with a 400 error. #### Deprecated --- ### batchOperationKey? ```ts optional batchOperationKey?: StringFilterProperty; ``` The batch operation key. --- ### businessId? ```ts optional businessId?: StringFilterProperty; ``` The business id associated with the process instance. --- ### elementId? ```ts optional elementId?: StringFilterProperty; ``` The element id associated with the process instance. --- ### elementInstanceState? ```ts optional elementInstanceState?: ElementInstanceStateFilterProperty; ``` The state of the element instances associated with the process instance. --- ### endDate? ```ts optional endDate?: DateTimeFilterProperty; ``` The end date. --- ### errorMessage? ```ts optional errorMessage?: StringFilterProperty; ``` The error message related to the process. --- ### hasElementInstanceIncident? ```ts optional hasElementInstanceIncident?: boolean; ``` Whether the element instance has an incident or not. --- ### hasIncident? ```ts optional hasIncident?: boolean; ``` Whether this process instance has a related incident or not. --- ### hasRetriesLeft? ```ts optional hasRetriesLeft?: boolean; ``` Whether the process has failed jobs with retries left. --- ### incidentErrorHashCode? ```ts optional incidentErrorHashCode?: IntegerFilterProperty; ``` The incident error hash code, associated with this process. --- ### parentElementInstanceKey? ```ts optional parentElementInstanceKey?: ElementInstanceKeyFilterProperty; ``` The parent element instance key. --- ### parentProcessInstanceKey? ```ts optional parentProcessInstanceKey?: ProcessInstanceKeyFilterProperty; ``` The parent process instance key. --- ### processInstanceKey? ```ts optional processInstanceKey?: ProcessInstanceKeyFilterProperty; ``` The key of this process instance. --- ### startDate? ```ts optional startDate?: DateTimeFilterProperty; ``` The start date. --- ### state? ```ts optional state?: ProcessInstanceStateFilterProperty; ``` The process instance state. --- ### suspendedDate? ```ts optional suspendedDate?: DateTimeFilterProperty; ``` The time this process instance most recently entered the SUSPENDED state. This is cleared (null) again once the process instance is resumed. --- ### tags? ```ts optional tags?: TagSet; ``` --- ### tenantId? ```ts optional tenantId?: StringFilterProperty; ``` The tenant id. --- ### variables? ```ts optional variables?: VariableValueFilterProperty[]; ``` The process instance variables. --- ## Type Alias: BaseWaitStateDetails ```ts type BaseWaitStateDetails = object; ``` Common fields shared by all wait-state details variants. ## Properties ### waitStateType ```ts waitStateType: string; ``` The wait state type discriminator. --- ## Type Alias: BasicStringFilter ```ts type BasicStringFilter = object; ``` Advanced filter Basic advanced string filter. ## Properties ### $eq? ```ts optional $eq?: string; ``` Checks for equality with the provided value. --- ### $exists? ```ts optional $exists?: boolean; ``` Checks if the current property exists. --- ### $in? ```ts optional $in?: string[]; ``` Checks if the property matches any of the provided values. --- ### $neq? ```ts optional $neq?: string; ``` Checks for inequality with the provided value. --- ### $notIn? ```ts optional $notIn?: string[]; ``` Checks if the property matches none of the provided values. --- ## Type Alias: BasicStringFilterProperty ```ts type BasicStringFilterProperty = string | BasicStringFilter; ``` String property with basic advanced search capabilities. --- ## Type Alias: BatchOperationCreatedResult ```ts type BatchOperationCreatedResult = object; ``` The created batch operation. ## Properties ### batchOperationKey ```ts batchOperationKey: BatchOperationKey; ``` Key of the batch operation. --- ### batchOperationType ```ts batchOperationType: BatchOperationTypeEnum; ``` --- ## Type Alias: BatchOperationError ```ts type BatchOperationError = object; ``` ## Properties ### message ```ts message: string; ``` The error message that occurred during the batch operation. --- ### partitionId ```ts partitionId: number; ``` The partition ID where the error occurred. --- ### type ```ts type: "QUERY_FAILED" | "RESULT_BUFFER_SIZE_EXCEEDED"; ``` The type of the error that occurred during the batch operation. --- ## Type Alias: BatchOperationFilter ```ts type BatchOperationFilter = object; ``` Batch operation filter request. ## Properties ### actorId? ```ts optional actorId?: StringFilterProperty; ``` The ID of the actor who performed the operation. --- ### actorType? ```ts optional actorType?: AuditLogActorTypeEnum; ``` The type of the actor who performed the operation. --- ### batchOperationKey? ```ts optional batchOperationKey?: BasicStringFilterProperty; ``` The key (or operate legacy ID) of the batch operation. --- ### operationType? ```ts optional operationType?: BatchOperationTypeFilterProperty; ``` The type of the batch operation. --- ### state? ```ts optional state?: BatchOperationStateFilterProperty; ``` The state of the batch operation. --- ## Type Alias: BatchOperationItemFilter ```ts type BatchOperationItemFilter = object; ``` Batch operation item filter request. ## Properties ### batchOperationKey? ```ts optional batchOperationKey?: BasicStringFilterProperty; ``` The key (or operate legacy ID) of the batch operation. --- ### itemKey? ```ts optional itemKey?: BasicStringFilterProperty; ``` The key of the item, e.g. a process instance key. --- ### operationType? ```ts optional operationType?: BatchOperationTypeFilterProperty; ``` The type of the batch operation. --- ### processInstanceKey? ```ts optional processInstanceKey?: ProcessInstanceKeyFilterProperty; ``` The process instance key of the processed item. --- ### state? ```ts optional state?: BatchOperationItemStateFilterProperty; ``` The state of the batch operation. --- ## Type Alias: BatchOperationItemResponse ```ts type BatchOperationItemResponse = object; ``` ## Properties ### batchOperationKey ```ts batchOperationKey: BatchOperationKey; ``` The key (or operate legacy ID) of the batch operation. --- ### errorMessage ```ts errorMessage: string | null; ``` The error message from the engine in case of a failed operation. --- ### itemKey ```ts itemKey: string; ``` Key of the item, e.g. a process instance key. --- ### operationType ```ts operationType: BatchOperationTypeEnum; ``` --- ### processedDate ```ts processedDate: string | null; ``` The date this item was processed. This is `null` if the item has not yet been processed. --- ### processInstanceKey ```ts processInstanceKey: ProcessInstanceKey | null; ``` The process instance key of the processed item. Null for batch-op types whose targets are not process instances (e.g. DELETE_DECISION_INSTANCE, DELETE_DECISION_DEFINITION, DELETE_PROCESS_DEFINITION). --- ### rootProcessInstanceKey ```ts rootProcessInstanceKey: ProcessInstanceKey | null; ``` The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. --- ### state ```ts state: "ACTIVE" | "COMPLETED" | "SKIPPED" | "CANCELED" | "FAILED"; ``` State of the item. --- ## Type Alias: BatchOperationItemSearchQuery ```ts type BatchOperationItemSearchQuery = SearchQueryRequest & object; ``` Batch operation item search request. ## Type Declaration ### filter? ```ts optional filter?: BatchOperationItemFilter; ``` The batch operation item search filters. ### sort? ```ts optional sort?: BatchOperationItemSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: BatchOperationItemSearchQueryResult ```ts type BatchOperationItemSearchQueryResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: BatchOperationItemResponse[]; ``` The matching batch operation items. --- ## Type Alias: BatchOperationItemSearchQuerySortRequest ```ts type BatchOperationItemSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "batchOperationKey" | "itemKey" | "processInstanceKey" | "processedDate" | "state"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: BatchOperationItemStateEnum ```ts type BatchOperationItemStateEnum = (typeof BatchOperationItemStateEnum)[keyof typeof BatchOperationItemStateEnum]; ``` The batch operation item state. --- ## Type Alias: BatchOperationItemStateExactMatch ```ts type BatchOperationItemStateExactMatch = BatchOperationItemStateEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: BatchOperationItemStateExactMatchWritable ```ts type BatchOperationItemStateExactMatchWritable = BatchOperationItemStateEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: BatchOperationItemStateFilterProperty ```ts type BatchOperationItemStateFilterProperty = BatchOperationItemStateExactMatch | AdvancedBatchOperationItemStateFilter; ``` BatchOperationItemStateEnum property with full advanced search capabilities. --- ## Type Alias: BatchOperationKey ```ts type BatchOperationKey = CamundaKey<"BatchOperationKey">; ``` System-generated key for an batch operation. --- ## Type Alias: BatchOperationResponse ```ts type BatchOperationResponse = object; ``` ## Properties ### actorId ```ts actorId: string | null; ``` The ID of the actor who performed the operation. Available for batch operations created since 8.9. --- ### actorType ```ts actorType: AuditLogActorTypeEnum | null; ``` The type of the actor who performed the operation. This is `null` if the batch operation was created before 8.9, or if the actor information is not available. --- ### batchOperationKey ```ts batchOperationKey: BatchOperationKey; ``` Key or (Operate Legacy ID = UUID) of the batch operation. --- ### batchOperationType ```ts batchOperationType: BatchOperationTypeEnum | null; ``` The type of the batch operation. This is `null` for batch operations whose type was never recorded in secondary storage, such as legacy Operate batch operations. --- ### endDate ```ts endDate: string | null; ``` The end date of the batch operation. This is `null` if the batch operation is still running. --- ### errors ```ts errors: BatchOperationError[]; ``` The errors that occurred per partition during the batch operation. --- ### operationsCompletedCount ```ts operationsCompletedCount: number; ``` The number of successfully completed tasks. --- ### operationsFailedCount ```ts operationsFailedCount: number; ``` The number of items which failed during execution of the batch operation. (e.g. because they are rejected by the Zeebe engine). --- ### operationsTotalCount ```ts operationsTotalCount: number; ``` The total number of items contained in this batch operation. --- ### startDate ```ts startDate: string | null; ``` The start date of the batch operation. This is `null` if the batch operation has not yet started. --- ### state ```ts state: BatchOperationStateEnum; ``` --- ## Type Alias: BatchOperationSearchQuery ```ts type BatchOperationSearchQuery = SearchQueryRequest & object; ``` Batch operation search request. ## Type Declaration ### filter? ```ts optional filter?: BatchOperationFilter; ``` The batch operation search filters. ### sort? ```ts optional sort?: BatchOperationSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: BatchOperationSearchQueryResult ```ts type BatchOperationSearchQueryResult = SearchQueryResponse & object; ``` The batch operation search query result. ## Type Declaration ### items ```ts items: BatchOperationResponse[]; ``` The matching batch operations. --- ## Type Alias: BatchOperationSearchQuerySortRequest ```ts type BatchOperationSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "batchOperationKey" | "operationType" | "state" | "startDate" | "endDate" | "actorType" | "actorId"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: BatchOperationStateEnum ```ts type BatchOperationStateEnum = (typeof BatchOperationStateEnum)[keyof typeof BatchOperationStateEnum]; ``` The batch operation state. --- ## Type Alias: BatchOperationStateExactMatch ```ts type BatchOperationStateExactMatch = BatchOperationStateEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: BatchOperationStateExactMatchWritable ```ts type BatchOperationStateExactMatchWritable = BatchOperationStateEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: BatchOperationStateFilterProperty ```ts type BatchOperationStateFilterProperty = BatchOperationStateExactMatch | AdvancedBatchOperationStateFilter; ``` BatchOperationStateEnum property with full advanced search capabilities. --- ## Type Alias: BatchOperationTypeEnum ```ts type BatchOperationTypeEnum = (typeof BatchOperationTypeEnum)[keyof typeof BatchOperationTypeEnum]; ``` The type of the batch operation. --- ## Type Alias: BatchOperationTypeExactMatch ```ts type BatchOperationTypeExactMatch = BatchOperationTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: BatchOperationTypeExactMatchWritable ```ts type BatchOperationTypeExactMatchWritable = BatchOperationTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: BatchOperationTypeFilterProperty ```ts type BatchOperationTypeFilterProperty = BatchOperationTypeExactMatch | AdvancedBatchOperationTypeFilter; ``` BatchOperationTypeEnum property with full advanced search capabilities. --- ## Type Alias: BroadcastSignalData ```ts type BroadcastSignalData = object; ``` ## Properties ### body ```ts body: SignalBroadcastRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/signals/broadcast"; ``` --- ## Type Alias: BroadcastSignalError ```ts type BroadcastSignalError = BroadcastSignalErrors[keyof BroadcastSignalErrors]; ``` --- ## Type Alias: BroadcastSignalErrors ```ts type BroadcastSignalErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 404 ```ts 404: ProblemDetail; ``` The signal is not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: BroadcastSignalResponse ```ts type BroadcastSignalResponse = BroadcastSignalResponses[keyof BroadcastSignalResponses]; ``` --- ## Type Alias: BroadcastSignalResponses ```ts type BroadcastSignalResponses = object; ``` ## Properties ### 200 ```ts 200: SignalBroadcastResult; ``` The signal was broadcast. --- ## Type Alias: BrokerInfo ```ts type BrokerInfo = object; ``` Provides information on a broker node. ## Properties ### brokerId ```ts brokerId: string; ``` The unique (within a cluster) broker identifier. When the cluster is not zoned, then it's a string that represents the nodeId (an integer). When the cluster is zoned, instead, it's of the form "$zoneName_$nodeId", providing uniqueness even across zones. --- ### host ```ts host: string; ``` The hostname for reaching the broker. --- ### ~~nodeId~~ ```ts nodeId: number; ``` The node ID for the broker. The uniqueness of this identifier depends if the cluster is zone-aware or not. - non zone-aware: (default) nodeId is unique across the cluster - zone-aware: (opt-in) nodeId is unique only within its zone. If you are migrating to a zone aware cluster, you must use `brokerId` instead. This property is deprecated, as it's been replaced by `brokerId`. #### Deprecated --- ### partitions ```ts partitions: Partition[]; ``` A list of partitions managed or replicated on this broker. --- ### port ```ts port: number; ``` The port for reaching the broker. --- ### version ```ts version: string; ``` The broker version. --- ## Type Alias: BusinessId ```ts type BusinessId = CamundaKey<"BusinessId">; ``` An optional, user-defined string identifier that identifies the process instance within the scope of a process definition (scoped by tenant). If provided and uniqueness enforcement is enabled, the engine will reject creation if another root process instance with the same business id is already active for the same process definition. Note that any active child process instances with the same business id are not taken into account. --- ## Type Alias: CamundaClient ```ts type CamundaClient = WithSearchPagination; ``` Public Camunda client type: the base class augmented with `.paginate(...)` on every `search*` operation. The `.paginate` methods are installed at runtime by the constructor (via `installSearchPagination`), so both construction paths — the `createCamundaClient` factory _and_ direct `new CamundaClient()` — yield a value whose static type matches the runtime shape. This is expressed as a separate type + value pair rather than declaration-merging an interface onto the class because `SearchPaginationApi` is self-referential (it maps over `keyof CamundaClient`), which TypeScript rejects as an interface `extends` clause ("recursively references itself as a base type"). --- ## Type Alias: CamundaClientLoose ```ts type CamundaClientLoose = ReturnType; ``` --- ## Type Alias: CamundaKey # Type Alias: CamundaKey\ ```ts type CamundaKey = string & object; ``` ## Type Parameters ### T `T` _extends_ `string` = `string` ## Type Declaration ### \_\_brand ```ts readonly __brand: T; ``` --- ## Type Alias: CamundaResultClient ```ts type CamundaResultClient = object & { [K in keyof CamundaClient]: CamundaClient[K] extends ( a: infer A ) => Promise ? (a: A) => Promise> : CamundaClient[K] extends (a: infer A) => any ? ( a: A ) => | Promise>> | ReturnType : CamundaClient[K]; }; ``` ## Type Declaration ### inner ```ts inner: CamundaClient; ``` --- ## Type Alias: CamundaUserResult ```ts type CamundaUserResult = object; ``` ## Properties ### authorizedComponents ```ts authorizedComponents: string[]; ``` The web components the user is authorized to use. When authorizations are disabled for the cluster, this always returns `["*"]`, regardless of the user's actual permissions, since access is not restricted in that case. --- ### c8Links ```ts c8Links: object; ``` The links to the components in the C8 stack. #### Index Signature ```ts [key: string]: string ``` --- ### canLogout ```ts canLogout: boolean; ``` Flag for understanding if the user is able to perform logout. --- ### displayName ```ts displayName: string | null; ``` The display name of the user. --- ### email ```ts email: string | null; ``` The email of the user. --- ### groups ```ts groups: string[]; ``` The groups assigned to the user. --- ### roles ```ts roles: string[]; ``` The roles assigned to the user. --- ### salesPlanType ```ts salesPlanType: string | null; ``` The plan of the user. --- ### tenants ```ts tenants: TenantResult[]; ``` The tenants the user is a member of. --- ### username ```ts username: Username; ``` The username of the user. --- ## Type Alias: CancelBatchOperationData ```ts type CancelBatchOperationData = object; ``` ## Properties ### body? ```ts optional body?: unknown; ``` --- ### path ```ts path: object; ``` #### batchOperationKey ```ts batchOperationKey: BatchOperationKey; ``` The key (or operate legacy ID) of the batch operation. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/batch-operations/{batchOperationKey}/cancellation"; ``` --- ## Type Alias: CancelBatchOperationError ```ts type CancelBatchOperationError = CancelBatchOperationErrors[keyof CancelBatchOperationErrors]; ``` --- ## Type Alias: CancelBatchOperationErrors ```ts type CancelBatchOperationErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Not found. The batch operation was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: CancelBatchOperationResponse ```ts type CancelBatchOperationResponse = CancelBatchOperationResponses[keyof CancelBatchOperationResponses]; ``` --- ## Type Alias: CancelBatchOperationResponses ```ts type CancelBatchOperationResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The batch operation cancel request was created. --- ## Type Alias: CancelClusterRebalanceData ```ts type CancelClusterRebalanceData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/cluster/v2/rebalance"; ``` --- ## Type Alias: CancelClusterRebalanceError ```ts type CancelClusterRebalanceError = CancelClusterRebalanceErrors[keyof CancelClusterRebalanceErrors]; ``` --- ## Type Alias: CancelClusterRebalanceErrors ```ts type CancelClusterRebalanceErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 502 ```ts 502: ProblemDetail; ``` The coordinator was reached, but its response was absent or unusable. --- ### 503 ```ts 503: ProblemDetail; ``` No coordinator is currently available or reachable. --- ### 504 ```ts 504: ProblemDetail; ``` The coordinator did not answer before the request timeout. --- ## Type Alias: CancelClusterRebalanceResponse ```ts type CancelClusterRebalanceResponse = CancelClusterRebalanceResponses[keyof CancelClusterRebalanceResponses]; ``` --- ## Type Alias: CancelClusterRebalanceResponses ```ts type CancelClusterRebalanceResponses = object; ``` ## Properties ### 200 ```ts 200: RebalanceCancellationResponse; ``` The cancellation was accepted. --- ## Type Alias: CancelProcessInstanceData ```ts type CancelProcessInstanceData = object; ``` ## Properties ### body? ```ts optional body?: CancelProcessInstanceRequest; ``` --- ### path ```ts path: object; ``` #### processInstanceKey ```ts processInstanceKey: ProcessInstanceKeyWritable; ``` The key of the process instance to cancel. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/{processInstanceKey}/cancellation"; ``` --- ## Type Alias: CancelProcessInstanceError ```ts type CancelProcessInstanceError = CancelProcessInstanceErrors[keyof CancelProcessInstanceErrors]; ``` --- ## Type Alias: CancelProcessInstanceErrors ```ts type CancelProcessInstanceErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 404 ```ts 404: ProblemDetail; ``` The process instance is not found. --- ### 409 ```ts 409: ProblemDetail; ``` The process instance cannot be canceled, for example because it is already being terminated or because it is a child process instance. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ### 504 ```ts 504: ProblemDetail; ``` The request timed out between the gateway and the broker. For these endpoints, this often happens when user task listeners are configured and the corresponding listener job is not completed within the request timeout. Common causes include no available job workers for the listener type, busy or crashed job workers, or delayed job completion. As with any gateway timeout, general timeout causes (for example transient network issues) can also result in a 504 response. Troubleshooting: - verify that job workers for the listener type are running and healthy - check worker logs for crashes, retries, and completion failures - check network connectivity between workers, gateway, and broker - retry with backoff after transient failures - fail without retries if a problem persists --- ## Type Alias: CancelProcessInstanceRequest ```ts type CancelProcessInstanceRequest = { operationReference?: OperationReference; } | null; ``` --- ## Type Alias: CancelProcessInstanceResponse ```ts type CancelProcessInstanceResponse = CancelProcessInstanceResponses[keyof CancelProcessInstanceResponses]; ``` --- ## Type Alias: CancelProcessInstanceResponses ```ts type CancelProcessInstanceResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The process instance is canceled. --- ## Type Alias: CancelProcessInstancesBatchOperationData ```ts type CancelProcessInstancesBatchOperationData = object; ``` ## Properties ### body ```ts body: ProcessInstanceCancellationBatchOperationRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/cancellation"; ``` --- ## Type Alias: CancelProcessInstancesBatchOperationError ```ts type CancelProcessInstancesBatchOperationError = CancelProcessInstancesBatchOperationErrors[keyof CancelProcessInstancesBatchOperationErrors]; ``` --- ## Type Alias: CancelProcessInstancesBatchOperationErrors ```ts type CancelProcessInstancesBatchOperationErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The process instance batch operation failed. More details are provided in the response body. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: CancelProcessInstancesBatchOperationResponse ```ts type CancelProcessInstancesBatchOperationResponse = CancelProcessInstancesBatchOperationResponses[keyof CancelProcessInstancesBatchOperationResponses]; ``` --- ## Type Alias: CancelProcessInstancesBatchOperationResponses ```ts type CancelProcessInstancesBatchOperationResponses = object; ``` ## Properties ### 200 ```ts 200: BatchOperationCreatedResult; ``` The batch operation request was created. --- ## Type Alias: CategoryExactMatch ```ts type CategoryExactMatch = AuditLogCategoryEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: CategoryExactMatchWritable ```ts type CategoryExactMatchWritable = AuditLogCategoryEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: CategoryFilterProperty ```ts type CategoryFilterProperty = CategoryExactMatch | AdvancedCategoryFilter; ``` AuditLogCategoryEnum property with full advanced search capabilities. --- ## Type Alias: ChangeClusterModeAsClusterAdminData ```ts type ChangeClusterModeAsClusterAdminData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query ```ts query: object; ``` #### dryRun? ```ts optional dryRun?: boolean; ``` If true, the requested change is only validated and the resulting plan is returned, without applying it to the cluster. #### mode ```ts mode: Mode; ``` The target cluster mode. #### physicalTenantId? ```ts optional physicalTenantId?: string; ``` The physical tenant to apply the change to. When omitted, or when passed with an empty value, the change is applied to every physical tenant of the cluster. --- ### url ```ts url: "/cluster/v2/mode"; ``` --- ## Type Alias: ChangeClusterModeAsClusterAdminError ```ts type ChangeClusterModeAsClusterAdminError = ChangeClusterModeAsClusterAdminErrors[keyof ChangeClusterModeAsClusterAdminErrors]; ``` --- ## Type Alias: ChangeClusterModeAsClusterAdminErrors ```ts type ChangeClusterModeAsClusterAdminErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 404 ```ts 404: ProblemDetail; ``` The requested `physicalTenantId` does not exist in this cluster. --- ### 409 ```ts 409: unknown; ``` The mode change conflicts with the cluster state, for example because another configuration change is in progress. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: ChangeClusterModeAsClusterAdminResponse ```ts type ChangeClusterModeAsClusterAdminResponse = ChangeClusterModeAsClusterAdminResponses[keyof ChangeClusterModeAsClusterAdminResponses]; ``` --- ## Type Alias: ChangeClusterModeAsClusterAdminResponses ```ts type ChangeClusterModeAsClusterAdminResponses = object; ``` ## Properties ### 200 ```ts 200: ClusterModeChangeResponse; ``` The mode change request was accepted; returns the planned cluster change covering every requested physical tenant. --- ## Type Alias: ChangeClusterModeData ```ts type ChangeClusterModeData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query ```ts query: object; ``` #### dryRun? ```ts optional dryRun?: boolean; ``` If true, the requested change is only validated and the resulting plan is returned, without applying it to the cluster. #### mode ```ts mode: Mode; ``` The target cluster mode. --- ### url ```ts url: "/mode"; ``` --- ## Type Alias: ChangeClusterModeError ```ts type ChangeClusterModeError = ChangeClusterModeErrors[keyof ChangeClusterModeErrors]; ``` --- ## Type Alias: ChangeClusterModeErrors ```ts type ChangeClusterModeErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: ChangeClusterModeResponse ```ts type ChangeClusterModeResponse = ChangeClusterModeResponses[keyof ChangeClusterModeResponses]; ``` --- ## Type Alias: ChangeClusterModeResponses ```ts type ChangeClusterModeResponses = object; ``` ## Properties ### 200 ```ts 200: ClusterModeChangeResponse; ``` The mode change request was accepted; returns the planned cluster changes. --- ## Type Alias: Changeset ```ts type Changeset = { [key: string]: unknown; candidateGroups?: string[] | null; candidateUsers?: string[] | null; dueDate?: string | null; followUpDate?: string | null; priority?: number | null; } | null; ``` JSON object with changed task attribute values. The following attributes can be adjusted with this endpoint, additional attributes will be ignored: - `candidateGroups` - reset by providing an empty list - `candidateUsers` - reset by providing an empty list - `dueDate` - reset by providing an empty String - `followUpDate` - reset by providing an empty String - `priority` - minimum 0, maximum 100, default 50 Providing any of those attributes with a `null` value or omitting it preserves the persisted attribute's value. The assignee cannot be adjusted with this endpoint, use the Assign task endpoint. This ensures correct event emission for assignee changes. ## Union Members ### Type Literal ```ts { [key: string]: unknown; candidateGroups?: string[] | null; candidateUsers?: string[] | null; dueDate?: string | null; followUpDate?: string | null; priority?: number | null; } ``` ### Index Signature ```ts [key: string]: unknown ``` #### candidateGroups? ```ts optional candidateGroups?: string[] | null; ``` The list of candidate groups of the task. Reset by providing an empty list. #### candidateUsers? ```ts optional candidateUsers?: string[] | null; ``` The list of candidate users of the task. Reset by providing an empty list. #### dueDate? ```ts optional dueDate?: string | null; ``` The due date of the task. Reset by providing an empty String. #### followUpDate? ```ts optional followUpDate?: string | null; ``` The follow-up date of the task. Reset by providing an empty String. #### priority? ```ts optional priority?: number | null; ``` The priority of the task. --- `null` --- ## Type Alias: CheckpointId ```ts type CheckpointId = number; ``` Checkpoint ID The id of the checkpoint. Must be a non-negative numerical value. As checkpoints are logically ordered by their ids (ascending), each successive checkpoint must use a higher id than the previous one. --- ## Type Alias: CheckpointType ```ts type CheckpointType = "MARKER" | "SCHEDULED_BACKUP" | "MANUAL_BACKUP"; ``` Checkpoint Type The type of the checkpoint. --- ## Type Alias: ClientId ```ts type ClientId = CamundaKey<"ClientId">; ``` The unique identifier of an OAuth client. Minted outside the Camunda REST API: in SaaS by Console, in Self-Managed with OIDC by the external identity provider (e.g. EntraID, Keycloak, Okta). In Self-Managed with Basic authentication, machine-to-machine applications are modelled as users instead — see the user identifier. --- ## Type Alias: ClientOptions ```ts type ClientOptions = object; ``` ## Properties ### baseUrl ```ts baseUrl: | "{schema}://{host}:{port}/v2" | "{schema}://{host}:{port}" | string & object; ``` --- ## Type Alias: ClockPinRequest ```ts type ClockPinRequest = object; ``` ## Properties ### timestamp ```ts timestamp: number; ``` The exact time in epoch milliseconds to which the clock should be pinned. --- ## Type Alias: CloudConfigurationResponse ```ts type CloudConfigurationResponse = object; ``` Configuration for SaaS/cloud-specific settings. ## Properties ### stage ```ts stage: CloudStage | null; ``` The cloud deployment stage. --- ## Type Alias: CloudStage ```ts type CloudStage = "dev" | "int" | "prod"; ``` The cloud deployment stage. --- ## Type Alias: ClusterBalanceResponse ```ts type ClusterBalanceResponse = object; ``` The cluster's current per-partition balance state, the running rebalance, and the last completed rebalance. ## Properties ### lastCompletedRebalance ```ts lastCompletedRebalance: ClusterCompletedRebalance | null; ``` The last completed non-dry-run rebalance this coordinator finished. --- ### partitions ```ts partitions: ClusterRebalancePartition[]; ``` The balance state of each partition as of the time of the request. --- ### runningRebalance ```ts runningRebalance: ClusterRunningRebalance | null; ``` Normally the rebalance currently running, or absent if no rebalance is running. For a dry-run response, this is instead the unexecuted plan of that dry run. --- ### state ```ts state: "BALANCED" | "BALANCING" | "UNBALANCED"; ``` The cluster's aggregate balance state as of the time of the request. --- ## Type Alias: ClusterBrokerInfo ```ts type ClusterBrokerInfo = object; ``` Provides information on a broker node, independent of any physical tenant. ## Properties ### brokerId ```ts brokerId: string; ``` The unique (within a cluster) broker identifier. When the cluster is not zoned, then it's a string that represents the nodeId (an integer). When the cluster is zoned, instead, it's of the form "$zoneName_$nodeId", providing uniqueness even across zones. --- ### host ```ts host: string; ``` The hostname for reaching the broker. --- ### port ```ts port: number; ``` The port for reaching the broker. --- ### version ```ts version: string; ``` The broker version. --- ## Type Alias: ClusterCompletedRebalance ```ts type ClusterCompletedRebalance = ClusterRebalance & object; ``` The last completed rebalance. ## Type Declaration ### finishedAt ```ts finishedAt: string; ``` When this rebalance finished. ### result ```ts result: "COMPLETED" | "CANCELLED" | "FAILED"; ``` How the rebalance ended. --- ## Type Alias: ClusterHistoryBackupInfo ```ts type ClusterHistoryBackupInfo = object; ``` A history backup id and what each physical tenant reports for it. No cluster-level state is aggregated from the per-tenant states. ## Properties ### backupId ```ts backupId: BackupId; ``` The id of the backup. --- ### physicalTenants ```ts physicalTenants: ClusterHistoryBackupTenantInfo[]; ``` What each physical tenant reports for this backup id, ordered by physical tenant id. When looking a backup id up directly, every targeted tenant is listed, including the ones reporting `NOT_FOUND`. Within a listing, only the tenants that hold the id are listed. --- ## Type Alias: ClusterHistoryBackupInfoWritable ```ts type ClusterHistoryBackupInfoWritable = object; ``` A history backup id and what each physical tenant reports for it. No cluster-level state is aggregated from the per-tenant states. ## Properties ### backupId ```ts backupId: BackupId; ``` The id of the backup. --- ### physicalTenants ```ts physicalTenants: ClusterHistoryBackupTenantInfoWritable[]; ``` What each physical tenant reports for this backup id, ordered by physical tenant id. When looking a backup id up directly, every targeted tenant is listed, including the ones reporting `NOT_FOUND`. Within a listing, only the tenants that hold the id are listed. --- ## Type Alias: ClusterHistoryBackupTakeResult ```ts type ClusterHistoryBackupTakeResult = object; ``` The snapshots scheduled on a single physical tenant. Only successfully scheduled tenants are reported: the request fails as a whole if any targeted tenant could not schedule the backup. ## Properties ### physicalTenantId ```ts physicalTenantId: string; ``` The id of the physical tenant. --- ### scheduledSnapshots ```ts scheduledSnapshots: string[]; ``` The names of the snapshots scheduled on this physical tenant. --- ## Type Alias: ClusterHistoryBackupTenantInfo ```ts type ClusterHistoryBackupTenantInfo = object; ``` What a single physical tenant reports for a history backup id. ## Properties ### details ```ts details: HistoryBackupSnapshotInfo[]; ``` Detailed status of the backup per snapshot on this physical tenant. Empty when the tenant does not hold the backup. --- ### failureReason ```ts failureReason: string | null; ``` Reason for failure if the state is 'FAILED'. --- ### physicalTenantId ```ts physicalTenantId: string; ``` The id of the physical tenant. --- ### state ```ts state: ClusterHistoryBackupTenantState; ``` The state of the backup on this physical tenant. --- ## Type Alias: ClusterHistoryBackupTenantInfoWritable ```ts type ClusterHistoryBackupTenantInfoWritable = object; ``` What a single physical tenant reports for a history backup id. ## Properties ### details ```ts details: unknown[]; ``` Detailed status of the backup per snapshot on this physical tenant. Empty when the tenant does not hold the backup. --- ### failureReason ```ts failureReason: string | null; ``` Reason for failure if the state is 'FAILED'. --- ### physicalTenantId ```ts physicalTenantId: string; ``` The id of the physical tenant. --- ### state ```ts state: ClusterHistoryBackupTenantState; ``` The state of the backup on this physical tenant. --- ## Type Alias: ClusterHistoryBackupTenantState ```ts type ClusterHistoryBackupTenantState = | "IN_PROGRESS" | "COMPLETED" | "FAILED" | "INCOMPLETE" | "INCOMPATIBLE" | "NOT_FOUND"; ``` Cluster History Backup Tenant State What a physical tenant reports for a history backup id: the per-tenant `HistoryBackupStateCode` extended with `NOT_FOUND` for a tenant that was read and does not hold the backup. `NOT_FOUND` is a successful observation, not a failure — a backup that only some physical tenants hold is a supported outcome. There is no state for a tenant that could not be read at all, because such a tenant fails the whole request. --- ## Type Alias: ClusterModeChangeOperation ```ts type ClusterModeChangeOperation = object; ``` A single operation that is part of a cluster mode change. ## Properties ### mode ```ts mode: string | null; ``` The target mode of the operation, if applicable. --- ### operation ```ts operation: string; ``` The type of the operation. --- ## Type Alias: ClusterModeChangePlannedChange ```ts type ClusterModeChangePlannedChange = object; ``` The operations of a cluster mode change that apply to one physical tenant. ## Properties ### operations ```ts operations: ClusterModeChangeOperation[]; ``` The ordered list of operations that will be applied to the physical tenant. --- ### physicalTenantId ```ts physicalTenantId: string | null; ``` The physical tenant the operations apply to; null for operations that are not scoped to a single physical tenant, such as broker lifecycle operations. --- ## Type Alias: ClusterModeChangeResponse ```ts type ClusterModeChangeResponse = object; ``` The planned changes resulting from a cluster mode transition request. ## Properties ### changeId ```ts changeId: string; ``` The ID of the cluster change that was triggered by the request. --- ### plannedChanges ```ts plannedChanges: ClusterModeChangePlannedChange[]; ``` The operations that will be applied to complete the change, grouped by the physical tenant they belong to. Groups are transitioned in parallel; the operations within a group are applied in the given order. --- ## Type Alias: ClusterRebalance ```ts type ClusterRebalance = object; ``` The fields common to a running and a completed rebalance. ## Properties ### partitions ```ts partitions: ClusterRebalanceOperationPartition[]; ``` Every partition in the rebalance plan and its progress within this rebalance. --- ### rebalanceId ```ts rebalanceId: number; ``` The ID of this rebalance. --- ### startedAt ```ts startedAt: string; ``` When this rebalance was created. --- ## Type Alias: ClusterRebalanceOperationPartition ```ts type ClusterRebalanceOperationPartition = object; ``` One partition's plan, progress, and outcome within a rebalance. ## Properties ### currentLeader ```ts currentLeader: string | null; ``` The leader last observed by this rebalance, or absent if there was no leader. --- ### desiredLeader ```ts desiredLeader: string; ``` The leader selected when this rebalance was planned. --- ### partitionId ```ts partitionId: number; ``` The unique ID of this partition, within its physical tenant. --- ### physicalTenantId ```ts physicalTenantId: string; ``` The partition group this partition belongs to. --- ### progress ```ts progress: "PENDING" | "TRANSFERRING" | "COMPLETED"; ``` Where this rebalance has reached for the partition. --- ### result ```ts result: | "TRANSFERRED" | "ALREADY_LEADER" | "NOT_MEMBER" | "NOT_REPLICATING" | "UNREACHABLE" | "NOT_COORDINATOR" | "STALE_CONFIGURATION" | "TRANSFER_IN_PROGRESS" | "LAG_TOO_HIGH" | "LEADER_INITIALIZING" | "CONFIGURATION_CHANGE_IN_PROGRESS" | "PAUSE_FAILED" | "REPLICATION_TIMED_OUT" | "TIMEOUT_NOW_EXHAUSTED" | "LEADER_CHANGED" | "NO_LEADER" | "NO_RESPONSE" | "CANCELLED" | "PHYSICAL_TENANT_DISABLED" | "PHYSICAL_TENANT_RECOVERING"; ``` The terminal outcome, present only when progress is COMPLETED. --- ## Type Alias: ClusterRebalancePartition ```ts type ClusterRebalancePartition = object; ``` One partition's leadership/balance status - its current leader, its desired leader, and whether a rebalance is currently moving it. ## Properties ### currentLeader ```ts currentLeader: string | null; ``` The broker ID currently leading this partition, or absent if it has no leader. --- ### desiredLeader ```ts desiredLeader: string; ``` The broker ID the current configuration wants to lead this partition. --- ### partitionId ```ts partitionId: number; ``` The unique ID of this partition, within its physical tenant. --- ### physicalTenantId ```ts physicalTenantId: string; ``` The partition group this partition belongs to. Partition IDs are unique only within a group, so this is needed to identify the partition. --- ### state ```ts state: "TRANSFERRING" | "UNBALANCED" | "BALANCED"; ``` Whether this partition is being actively transferred, unbalanced, or balanced. --- ## Type Alias: ClusterRebalanceRequest ```ts type ClusterRebalanceRequest = object; ``` The settings to run a given rebalance with. Every setting is optional; an absent request body is equivalent to a body with every field absent, and means "use the configured settings". ## Properties ### leaderWaitTimeout? ```ts optional leaderWaitTimeout?: string; ``` How long the coordinator waits for a partition without a leader to acquire one before reporting `NO_LEADER` and moving on (as a positive ISO-8601 duration). --- ### maxTransferAttempts? ```ts optional maxTransferAttempts?: number; ``` How many times a current leader may prompt the desired leader to take over leadership before giving up. --- ### replicationLagThreshold? ```ts optional replicationLagThreshold?: number; ``` The highest replication lag (in bytes) that a desired leader may have for its transfer to be accepted. --- ### replicationTimeout? ```ts optional replicationTimeout?: string; ``` How long a partition may stay frozen waiting for its desired leader to catch up (as a positive ISO-8601 duration). --- ## Type Alias: ClusterRestoreAwaitModeChangeOperation ```ts type ClusterRestoreAwaitModeChangeOperation = object; ``` The operation that awaits the transition of a broker to a mode. ## Properties ### brokerId ```ts brokerId: string; ``` The ID of the broker that applies the operation, including its zone if it belongs to one. --- ### mode ```ts mode: string; ``` The mode the broker is awaited to have transitioned to. --- ### operation ```ts operation: string; ``` The type of the operation. --- ## Type Alias: ClusterRestoreBrokerOperation ```ts type ClusterRestoreBrokerOperation = object; ``` A restore operation that applies to a broker as a whole, such as the one that updates its incarnation number. ## Properties ### brokerId ```ts brokerId: string; ``` The ID of the broker that applies the operation, including its zone if it belongs to one. --- ### operation ```ts operation: string; ``` The type of the operation. --- ## Type Alias: ClusterRestoreModeChangeOperation ```ts type ClusterRestoreModeChangeOperation = object; ``` The operation that transitions a broker to a mode once its partitions are restored. ## Properties ### brokerId ```ts brokerId: string; ``` The ID of the broker that applies the operation, including its zone if it belongs to one. --- ### mode ```ts mode: string; ``` The mode the broker is transitioned to. --- ### operation ```ts operation: string; ``` The type of the operation. --- ## Type Alias: ClusterRestoreOperation ```ts type ClusterRestoreOperation = | (object & ClusterRestoreBrokerOperation) | (object & ClusterRestorePartitionOperation) | (object & ClusterRestorePartitionRestoreOperation) | (object & ClusterRestoreModeChangeOperation) | (object & ClusterRestoreAwaitModeChangeOperation); ``` A single operation that is part of a restore. Every operation names the broker that applies it; the rest of its properties depend on what the operation does, so it is reported as one of the variants below, distinguished by `operation`. A property a variant does not declare is absent from the response rather than reported as null. --- ## Type Alias: ClusterRestorePartitionOperation ```ts type ClusterRestorePartitionOperation = object; ``` A restore operation that targets a single partition without restoring it, such as the one that prepares the partition for its restore. ## Properties ### brokerId ```ts brokerId: string; ``` The ID of the broker that applies the operation, including its zone if it belongs to one. --- ### operation ```ts operation: string; ``` The type of the operation. --- ### partitionId ```ts partitionId: number; ``` The partition the operation applies to. --- ## Type Alias: ClusterRestorePartitionRestoreOperation ```ts type ClusterRestorePartitionRestoreOperation = object; ``` The operation that restores a single partition from the backups resolved for it. ## Properties ### backupIds ```ts backupIds: number[]; ``` The IDs of the backups the partition is restored from. --- ### brokerId ```ts brokerId: string; ``` The ID of the broker that applies the operation, including its zone if it belongs to one. --- ### operation ```ts operation: string; ``` The type of the operation. --- ### partitionId ```ts partitionId: number; ``` The partition the operation restores. --- ## Type Alias: ClusterRestorePlannedChange ```ts type ClusterRestorePlannedChange = object; ``` The operations of a restore that apply to one physical tenant. ## Properties ### operations ```ts operations: ClusterRestoreOperation[]; ``` The ordered list of operations that will be applied to the physical tenant. --- ### physicalTenantId ```ts physicalTenantId: string | null; ``` The physical tenant the operations apply to; null for operations that are not scoped to a single physical tenant, such as broker lifecycle operations. --- ## Type Alias: ClusterRestoreRequest ```ts type ClusterRestoreRequest = RestoreRequest & object; ``` Describes a restore request issued by a cluster admin. The backup selection at the top level applies to every targeted physical tenant, except for the ones listed in `overrides`. ## Type Declaration ### overrides? ```ts optional overrides?: | { [key: string]: RestoreRequest; } | null; ``` The backup selection to apply to individual physical tenants, keyed by physical tenant id. Only allowed for a cluster-wide restore, that is when no `physicalTenantId` parameter is given. --- ## Type Alias: ClusterRestoreResponse ```ts type ClusterRestoreResponse = object; ``` The planned changes resulting from a restore request. ## Properties ### changeId ```ts changeId: string; ``` The ID of the cluster change that was triggered by the request. --- ### plannedChanges ```ts plannedChanges: ClusterRestorePlannedChange[]; ``` The operations that will be applied to complete the restore, grouped by the physical tenant they belong to. Groups are restored in parallel; the operations within a group are applied in the given order. --- ## Type Alias: ClusterRunningRebalance ```ts type ClusterRunningRebalance = ClusterRebalance & object; ``` The rebalance currently running. ## Type Declaration ### cancelRequested ```ts cancelRequested: boolean; ``` Whether cancellation has been requested. ### dryRun ```ts dryRun: boolean; ``` Whether this rebalance is a dry run. --- ## Type Alias: ClusterRuntimeBackupInfo ```ts type ClusterRuntimeBackupInfo = object; ``` A runtime backup id, what each physical tenant reports for it, and the state aggregated over every targeted tenant — folded from the per-tenant states by the same rules a per-tenant state is folded from its partitions. ## Properties ### backupId ```ts backupId: BackupId; ``` The id of the backup. --- ### failureReason ```ts failureReason: string | null; ``` Reason for failure if the aggregated state is 'FAILED'. --- ### physicalTenants ```ts physicalTenants: ClusterRuntimeBackupTenantInfo[]; ``` What each physical tenant reports for this backup id, ordered by physical tenant id. Every targeted tenant is listed, including the ones reporting `DOES_NOT_EXIST`. --- ### state ```ts state: StateCode; ``` The state aggregated over every targeted physical tenant, whether the backup id was looked up directly or listed. A tenant holding nothing for this id counts as `DOES_NOT_EXIST`, so the aggregate is `INCOMPLETE` unless every targeted tenant holds the backup. --- ## Type Alias: ClusterRuntimeBackupInfoWritable ```ts type ClusterRuntimeBackupInfoWritable = object; ``` A runtime backup id, what each physical tenant reports for it, and the state aggregated over every targeted tenant — folded from the per-tenant states by the same rules a per-tenant state is folded from its partitions. ## Properties ### backupId ```ts backupId: BackupId; ``` The id of the backup. --- ### failureReason ```ts failureReason: string | null; ``` Reason for failure if the aggregated state is 'FAILED'. --- ### physicalTenants ```ts physicalTenants: ClusterRuntimeBackupTenantInfoWritable[]; ``` What each physical tenant reports for this backup id, ordered by physical tenant id. Every targeted tenant is listed, including the ones reporting `DOES_NOT_EXIST`. --- ### state ```ts state: StateCode; ``` The state aggregated over every targeted physical tenant, whether the backup id was looked up directly or listed. A tenant holding nothing for this id counts as `DOES_NOT_EXIST`, so the aggregate is `INCOMPLETE` unless every targeted tenant holds the backup. --- ## Type Alias: ClusterRuntimeBackupState ```ts type ClusterRuntimeBackupState = object; ``` The checkpoint and backup state of each physical tenant. Nothing is aggregated across tenants: checkpoint ids and log positions only mean anything within one tenant's partitions. ## Properties ### physicalTenants ```ts physicalTenants: ClusterRuntimeBackupTenantState[]; ``` The runtime backup state of each targeted physical tenant, ordered by physical tenant id. --- ## Type Alias: ClusterRuntimeBackupTakeOutcome ```ts type ClusterRuntimeBackupTakeOutcome = "TRIGGERED" | "FAILED" | "UNKNOWN"; ``` Cluster Runtime Backup Take Outcome What a physical tenant did with the trigger. `TRIGGERED` says the backup is running, not that it completed — poll `GET /cluster/v2/backups/runtime/{backupId}` for that. A `FAILED` tenant is running no backup for this request and needs no cleanup. `UNKNOWN` means the broker may or may not have accepted the request — the connection was cut mid-flight, or the gateway timed out waiting — so that tenant's backups have to be checked before retrying; it is reported separately from `FAILED` precisely because calling it failed would claim nothing is running there. Tenants that were triggered are never rolled back. --- ## Type Alias: ClusterRuntimeBackupTakeResult ```ts type ClusterRuntimeBackupTakeResult = object; ``` Whether one physical tenant's runtime backup was triggered, and under which id it can be monitored and deleted. ## Properties ### backupId ```ts backupId: BackupId | null; ``` The id to monitor or delete this physical tenant's backup by: the id it is running under when `TRIGGERED` — the requested one, or the one the tenant generated when ids are generated — and the requested id to check when `UNKNOWN`. Null when the tenant is known to be running no backup, and also when an `UNKNOWN` tenant generates its own ids, because the id it may be running under was never reported; list that tenant's backups to find it. --- ### outcome ```ts outcome: ClusterRuntimeBackupTakeOutcome; ``` What this physical tenant did with the trigger. --- ### physicalTenantId ```ts physicalTenantId: string; ``` The id of the physical tenant. --- ### reason ```ts reason: string | null; ``` Why this physical tenant reported no triggered backup. Null when it was triggered. --- ## Type Alias: ClusterRuntimeBackupTenantInfo ```ts type ClusterRuntimeBackupTenantInfo = object; ``` What a single physical tenant reports for a runtime backup id. ## Properties ### details ```ts details: PartitionBackupInfo[]; ``` Detailed status of the backup per partition of this physical tenant. Contains every partition of the tenant when the backup id was looked up directly, including for a tenant that holds no such backup. Empty for a tenant that holds nothing for a listed id: a listing asks each tenant for the backups it has, so there is nothing to report per partition for one it does not. --- ### failureReason ```ts failureReason: string | null; ``` Reason for failure if the state is 'FAILED'. --- ### physicalTenantId ```ts physicalTenantId: string; ``` The id of the physical tenant. --- ### state ```ts state: StateCode; ``` The state of the backup on this physical tenant, aggregated over its partitions. --- ## Type Alias: ClusterRuntimeBackupTenantInfoWritable ```ts type ClusterRuntimeBackupTenantInfoWritable = object; ``` What a single physical tenant reports for a runtime backup id. ## Properties ### details ```ts details: PartitionBackupInfoWritable[]; ``` Detailed status of the backup per partition of this physical tenant. Contains every partition of the tenant when the backup id was looked up directly, including for a tenant that holds no such backup. Empty for a tenant that holds nothing for a listed id: a listing asks each tenant for the backups it has, so there is nothing to report per partition for one it does not. --- ### failureReason ```ts failureReason: string | null; ``` Reason for failure if the state is 'FAILED'. --- ### physicalTenantId ```ts physicalTenantId: string; ``` The id of the physical tenant. --- ### state ```ts state: StateCode; ``` The state of the backup on this physical tenant, aggregated over its partitions. --- ## Type Alias: ClusterRuntimeBackupTenantState ```ts type ClusterRuntimeBackupTenantState = object; ``` The checkpoint and backup state of one physical tenant. ## Properties ### physicalTenantId ```ts physicalTenantId: string; ``` The id of the physical tenant. --- ### state ```ts state: RuntimeBackupState; ``` The checkpoint and backup state of this physical tenant's partitions. --- ## Type Alias: ClusterStatusResponse ```ts type ClusterStatusResponse = object; ``` The aggregated status of the whole cluster. ## Properties ### status ```ts status: "HEALTHY" | "DEGRADED" | "DOWN"; ``` `HEALTHY` when every physical tenant is healthy, `DOWN` when no physical tenant can process work, `DEGRADED` in every other case. --- ## Type Alias: ClusterTakeHistoryBackupResponse ```ts type ClusterTakeHistoryBackupResponse = object; ``` The snapshots scheduled on every targeted physical tenant. No cluster-level state is aggregated from the per-tenant outcomes. ## Properties ### backupId ```ts backupId: BackupId; ``` The id requested for the backup on every targeted physical tenant. --- ### physicalTenants ```ts physicalTenants: ClusterHistoryBackupTakeResult[]; ``` The outcome for each targeted physical tenant, ordered by physical tenant id. --- ## Type Alias: ClusterTakeRuntimeBackupResponse ```ts type ClusterTakeRuntimeBackupResponse = object; ``` The outcome of triggering a runtime backup on every targeted physical tenant. Returned both when every tenant was triggered and when only some were, so a partial trigger is never silent: the status code says whether the request succeeded, the body says what is running. ## Properties ### physicalTenants ```ts physicalTenants: ClusterRuntimeBackupTakeResult[]; ``` The outcome for each targeted physical tenant, ordered by physical tenant id. Carries no cluster-level backup id: in generated-id mode each tenant generates its own. --- ## Type Alias: ClusterTopologyResponse ```ts type ClusterTopologyResponse = object; ``` The topology of the whole cluster, aggregated over all physical tenants. ## Properties ### brokers ```ts brokers: ClusterBrokerInfo[]; ``` The brokers that are part of this cluster, across all physical tenants. --- ### clusterId ```ts clusterId: string | null; ``` The cluster Id. --- ### clusterSize ```ts clusterSize: number; ``` The number of brokers in the cluster. --- ### gatewayVersion ```ts gatewayVersion: string | null; ``` The version of the Orchestration Cluster Gateway. --- ### physicalTenants ```ts physicalTenants: PhysicalTenantTopology[]; ``` The topology of each physical tenant of this cluster. --- ## Type Alias: ClusterUpgradeStatusResponse ```ts type ClusterUpgradeStatusResponse = object; ``` The upgrade-readiness status of the whole cluster. ## Properties ### status ```ts status: "MIGRATED" | "MIGRATION_IN_PROGRESS" | "UNKNOWN"; ``` `MIGRATED` once every known upgrade-readiness condition is met for every known physical tenant; `MIGRATION_IN_PROGRESS` when at least one is confirmed not yet migrated; `UNKNOWN` otherwise. --- ## Type Alias: ClusterVariableKindEnum ```ts type ClusterVariableKindEnum = (typeof ClusterVariableKindEnum)[keyof typeof ClusterVariableKindEnum]; ``` The kind of a cluster variable. JSON is the default. SECRET_REFERENCE allows the value to contain camunda.secrets.X references that are resolved at job activation time. --- ## Type Alias: ClusterVariableKindExactMatch ```ts type ClusterVariableKindExactMatch = ClusterVariableKindEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: ClusterVariableKindExactMatchWritable ```ts type ClusterVariableKindExactMatchWritable = ClusterVariableKindEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: ClusterVariableKindFilterProperty ```ts type ClusterVariableKindFilterProperty = ClusterVariableKindExactMatch | AdvancedClusterVariableKindFilter; ``` ClusterVariableKindEnum property with full advanced search capabilities. --- ## Type Alias: ClusterVariableName ```ts type ClusterVariableName = CamundaKey<"ClusterVariableName">; ``` The name of a cluster variable. Unique within its scope (global or tenant-specific). --- ## Type Alias: ClusterVariableResult ```ts type ClusterVariableResult = ClusterVariableResultBase & object; ``` ## Type Declaration ### value ```ts value: string; ``` Full value of this cluster variable. --- ## Type Alias: ClusterVariableResultBase ```ts type ClusterVariableResultBase = object; ``` Cluster variable response item. ## Properties ### kind ```ts kind: ClusterVariableKindEnum; ``` --- ### metadata ```ts metadata: object; ``` A generic key-value metadata bag attached to the cluster variable. Values are strings or numbers. #### Index Signature ```ts [key: string]: string | number ``` --- ### name ```ts name: ClusterVariableName; ``` The name of the cluster variable. Unique within its scope (global or tenant-specific). --- ### scope ```ts scope: ClusterVariableScopeEnum; ``` --- ### tenantId ```ts tenantId: string | null; ``` Only provided if the cluster variable scope is TENANT. Null for global scope variables. --- ## Type Alias: ClusterVariableScopeEnum ```ts type ClusterVariableScopeEnum = (typeof ClusterVariableScopeEnum)[keyof typeof ClusterVariableScopeEnum]; ``` The scope of a cluster variable. --- ## Type Alias: ClusterVariableScopeExactMatch ```ts type ClusterVariableScopeExactMatch = ClusterVariableScopeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: ClusterVariableScopeExactMatchWritable ```ts type ClusterVariableScopeExactMatchWritable = ClusterVariableScopeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: ClusterVariableScopeFilterProperty ```ts type ClusterVariableScopeFilterProperty = ClusterVariableScopeExactMatch | AdvancedClusterVariableScopeFilter; ``` ClusterVariableScopeEnum property with full advanced search capabilities. --- ## Type Alias: ClusterVariableSearchQueryFilterRequest ```ts type ClusterVariableSearchQueryFilterRequest = object; ``` Cluster variable filter request. ## Properties ### isTruncated? ```ts optional isTruncated?: boolean; ``` Filter cluster variables by truncation status of their stored values. When true, returns only variables whose stored values are truncated (i.e., the value exceeds the storage size limit and is truncated in storage). When false, returns only variables with non-truncated stored values. This filter is based on the underlying storage characteristic, not the response format. --- ### kind? ```ts optional kind?: ClusterVariableKindFilterProperty; ``` The kind filter for cluster variables. --- ### metadata? ```ts optional metadata?: object; ``` Filter by metadata entries. A map of metadata key to an advanced filter on that key's value. Metadata values are strings or numbers. #### Index Signature ```ts [key: string]: AdvancedMetadataValueFilter ``` --- ### name? ```ts optional name?: StringFilterProperty; ``` Name of the cluster variable. --- ### scope? ```ts optional scope?: ClusterVariableScopeFilterProperty; ``` The scope filter for cluster variables. --- ### tenantId? ```ts optional tenantId?: StringFilterProperty; ``` Tenant ID of this variable. --- ### value? ```ts optional value?: StringFilterProperty; ``` The value of the cluster variable. --- ## Type Alias: ClusterVariableSearchQueryRequest ```ts type ClusterVariableSearchQueryRequest = SearchQueryRequest & object; ``` Cluster variable search query request. ## Type Declaration ### filter? ```ts optional filter?: ClusterVariableSearchQueryFilterRequest; ``` The cluster variable search filters. ### sort? ```ts optional sort?: ClusterVariableSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: ClusterVariableSearchQueryResult ```ts type ClusterVariableSearchQueryResult = SearchQueryResponse & object; ``` Cluster variable search query response. ## Type Declaration ### items ```ts items: ClusterVariableSearchResult[]; ``` The matching cluster variables. --- ## Type Alias: ClusterVariableSearchQuerySortRequest ```ts type ClusterVariableSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: "name" | "value" | "tenantId" | "scope"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: ClusterVariableSearchResult ```ts type ClusterVariableSearchResult = ClusterVariableResultBase & object; ``` Cluster variable search response item. ## Type Declaration ### isTruncated ```ts isTruncated: boolean; ``` Whether the value is truncated or not. ### value ```ts value: string; ``` Value of this cluster variable. Can be truncated. --- ## Type Alias: CompleteJobData ```ts type CompleteJobData = object; ``` ## Properties ### body? ```ts optional body?: JobCompletionRequest; ``` --- ### path ```ts path: object; ``` #### jobKey ```ts jobKey: JobKeyWritable; ``` The key of the job to complete. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/jobs/{jobKey}/completion"; ``` --- ## Type Alias: CompleteJobError ```ts type CompleteJobError = CompleteJobErrors[keyof CompleteJobErrors]; ``` --- ## Type Alias: CompleteJobErrors ```ts type CompleteJobErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 404 ```ts 404: ProblemDetail; ``` The job with the given key was not found. --- ### 409 ```ts 409: ProblemDetail; ``` The job with the given key is in the wrong state currently. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: CompleteJobResponse ```ts type CompleteJobResponse = CompleteJobResponses[keyof CompleteJobResponses]; ``` --- ## Type Alias: CompleteJobResponses ```ts type CompleteJobResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The job was completed successfully. --- ## Type Alias: CompleteUserTaskData ```ts type CompleteUserTaskData = object; ``` ## Properties ### body? ```ts optional body?: UserTaskCompletionRequest; ``` --- ### path ```ts path: object; ``` #### userTaskKey ```ts userTaskKey: UserTaskKeyWritable; ``` The key of the user task to complete. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/user-tasks/{userTaskKey}/completion"; ``` --- ## Type Alias: CompleteUserTaskError ```ts type CompleteUserTaskError = CompleteUserTaskErrors[keyof CompleteUserTaskErrors]; ``` --- ## Type Alias: CompleteUserTaskErrors ```ts type CompleteUserTaskErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 404 ```ts 404: ProblemDetail; ``` The user task with the given key was not found. --- ### 409 ```ts 409: ProblemDetail; ``` The user task with the given key is in the wrong state currently. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ### 504 ```ts 504: ProblemDetail; ``` The request timed out between the gateway and the broker. For these endpoints, this often happens when user task listeners are configured and the corresponding listener job is not completed within the request timeout. Common causes include no available job workers for the listener type, busy or crashed job workers, or delayed job completion. As with any gateway timeout, general timeout causes (for example transient network issues) can also result in a 504 response. Troubleshooting: - verify that job workers for the listener type are running and healthy - check worker logs for crashes, retries, and completion failures - check network connectivity between workers, gateway, and broker - retry with backoff after transient failures - fail without retries if a problem persists --- ## Type Alias: CompleteUserTaskResponse ```ts type CompleteUserTaskResponse = CompleteUserTaskResponses[keyof CompleteUserTaskResponses]; ``` --- ## Type Alias: CompleteUserTaskResponses ```ts type CompleteUserTaskResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The user task was completed successfully. --- ## Type Alias: ComponentsConfigurationResponse ```ts type ComponentsConfigurationResponse = object; ``` Configuration for active Camunda components in the deployment. ## Properties ### active ```ts active: WebappComponent[]; ``` List of webapp components whose UI is enabled in this deployment. --- ## Type Alias: ConditionWaitStateDetails ```ts type ConditionWaitStateDetails = BaseWaitStateDetails & object; ``` ## Type Declaration ### events ```ts events: ("create" | "update")[]; ``` The variable events that trigger condition re-evaluation. Empty means all events. ### expression ```ts expression: string; ``` The condition expression that must evaluate to true to proceed. --- ## Type Alias: ConditionalEvaluationInstruction ```ts type ConditionalEvaluationInstruction = object; ``` ## Properties ### processDefinitionKey? ```ts optional processDefinitionKey?: ProcessDefinitionKey; ``` Used to evaluate root-level conditional start events of the process definition with the given key. --- ### tenantId? ```ts optional tenantId?: TenantId; ``` Used to evaluate root-level conditional start events for a tenant with the given ID. This will only evaluate root-level conditional start events of process definitions which belong to the tenant. --- ### variables ```ts variables: object; ``` JSON object representing the variables to use for evaluation of the conditions and to pass to the process instances that have been triggered. #### Index Signature ```ts [key: string]: unknown ``` --- ## Type Alias: ConditionalEvaluationKey ```ts type ConditionalEvaluationKey = CamundaKey<"ConditionalEvaluationKey">; ``` System-generated key for a conditional evaluation. --- ## Type Alias: ConditionalEvaluationKeyWritable ```ts type ConditionalEvaluationKeyWritable = LongKey; ``` System-generated key for a conditional evaluation. --- ## Type Alias: CorrelateMessageData ```ts type CorrelateMessageData = object; ``` ## Properties ### body ```ts body: MessageCorrelationRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/messages/correlation"; ``` --- ## Type Alias: CorrelateMessageError ```ts type CorrelateMessageError = CorrelateMessageErrors[keyof CorrelateMessageErrors]; ``` --- ## Type Alias: CorrelateMessageErrors ```ts type CorrelateMessageErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Not found --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: CorrelateMessageResponse ```ts type CorrelateMessageResponse = CorrelateMessageResponses[keyof CorrelateMessageResponses]; ``` --- ## Type Alias: CorrelateMessageResponses ```ts type CorrelateMessageResponses = object; ``` ## Properties ### 200 ```ts 200: MessageCorrelationResult; ``` The message is correlated to one or more process instances --- ## Type Alias: CorrelatedMessageSubscriptionFilter ```ts type CorrelatedMessageSubscriptionFilter = object; ``` Correlated message subscriptions search filter. ## Properties ### businessId? ```ts optional businessId?: StringFilterProperty; ``` Filter by the business id stored on the correlated message subscription — for message start event correlations the correlating message's business id, and for catch, boundary, or intermediate event correlations the subscribing process instance's business id. Supports advanced string filtering, including `$like` with `*`/`?` wildcards. --- ### correlationKey? ```ts optional correlationKey?: StringFilterProperty; ``` The correlation key of the message. --- ### correlationTime? ```ts optional correlationTime?: DateTimeFilterProperty; ``` The time when the message was correlated. --- ### elementId? ```ts optional elementId?: StringFilterProperty; ``` The element ID that received the message. --- ### elementInstanceKey? ```ts optional elementInstanceKey?: ElementInstanceKeyFilterProperty; ``` The element instance key that received the message. --- ### messageKey? ```ts optional messageKey?: BasicStringFilterProperty; ``` The message key. --- ### messageName? ```ts optional messageName?: StringFilterProperty; ``` The name of the message. --- ### partitionId? ```ts optional partitionId?: IntegerFilterProperty; ``` The partition ID that correlated the message. --- ### processDefinitionId? ```ts optional processDefinitionId?: StringFilterProperty; ``` The process definition ID associated with this correlated message subscription. --- ### processDefinitionKey? ```ts optional processDefinitionKey?: ProcessDefinitionKeyFilterProperty; ``` The process definition key associated with this correlated message subscription. For intermediate message events, this only works for data created with 8.9 and later. --- ### processInstanceKey? ```ts optional processInstanceKey?: ProcessInstanceKeyFilterProperty; ``` The process instance key associated with this correlated message subscription. --- ### subscriptionKey? ```ts optional subscriptionKey?: MessageSubscriptionKeyFilterProperty; ``` The subscription key that received the message. --- ### tenantId? ```ts optional tenantId?: StringFilterProperty; ``` The tenant ID associated with this correlated message subscription. --- ## Type Alias: CorrelatedMessageSubscriptionResult ```ts type CorrelatedMessageSubscriptionResult = object; ``` ## Properties ### businessId ```ts businessId: BusinessId | null; ``` The business id associated with this correlated message subscription. For a message start event correlation, it is the business id carried by the correlating message that was stamped on the started process instance to enforce its uniqueness. For a catch, boundary, or intermediate event correlation, it is the business id of the subscribing process instance, captured when the subscription was opened. It is `null` when the relevant process instance has no business id. --- ### correlationKey ```ts correlationKey: string | null; ``` The correlation key of the message. --- ### correlationTime ```ts correlationTime: string; ``` The time when the message was correlated. --- ### elementId ```ts elementId: ElementId; ``` The element ID that received the message. --- ### elementInstanceKey ```ts elementInstanceKey: ElementInstanceKey | null; ``` The element instance key that received the message. It is `null` for start event subscriptions. --- ### messageKey ```ts messageKey: MessageKey; ``` The message key. --- ### messageName ```ts messageName: string; ``` The name of the message. --- ### partitionId ```ts partitionId: number; ``` The partition ID that correlated the message. --- ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId; ``` The process definition ID associated with this correlated message subscription. --- ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey; ``` The process definition key associated with this correlated message subscription. --- ### processInstanceKey ```ts processInstanceKey: ProcessInstanceKey; ``` The process instance key associated with this correlated message subscription. --- ### rootProcessInstanceKey ```ts rootProcessInstanceKey: ProcessInstanceKey | null; ``` The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. --- ### subscriptionKey ```ts subscriptionKey: MessageSubscriptionKey; ``` The subscription key that received the message. --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID associated with this correlated message subscription. --- ## Type Alias: CorrelatedMessageSubscriptionSearchQuery ```ts type CorrelatedMessageSubscriptionSearchQuery = SearchQueryRequest & object; ``` ## Type Declaration ### filter? ```ts optional filter?: CorrelatedMessageSubscriptionFilter; ``` The correlated message subscriptions search filters. ### sort? ```ts optional sort?: CorrelatedMessageSubscriptionSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: CorrelatedMessageSubscriptionSearchQueryResult ```ts type CorrelatedMessageSubscriptionSearchQueryResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: CorrelatedMessageSubscriptionResult[]; ``` The matching correlated message subscriptions. --- ## Type Alias: CorrelatedMessageSubscriptionSearchQuerySortRequest ```ts type CorrelatedMessageSubscriptionSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "businessId" | "correlationKey" | "correlationTime" | "elementId" | "elementInstanceKey" | "messageKey" | "messageName" | "partitionId" | "processDefinitionId" | "processDefinitionKey" | "processInstanceKey" | "subscriptionKey" | "tenantId"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: CreateAdminUserData ```ts type CreateAdminUserData = object; ``` ## Properties ### body ```ts body: UserRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/setup/user"; ``` --- ## Type Alias: CreateAdminUserError ```ts type CreateAdminUserError = CreateAdminUserErrors[keyof CreateAdminUserErrors]; ``` --- ## Type Alias: CreateAdminUserErrors ```ts type CreateAdminUserErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 409 ```ts 409: ProblemDetail; ``` A user with this username already exists. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: CreateAdminUserResponse ```ts type CreateAdminUserResponse = CreateAdminUserResponses[keyof CreateAdminUserResponses]; ``` --- ## Type Alias: CreateAdminUserResponses ```ts type CreateAdminUserResponses = object; ``` ## Properties ### 201 ```ts 201: UserCreateResult; ``` The admin user was created successfully. --- ## Type Alias: CreateAgentInstanceData ```ts type CreateAgentInstanceData = object; ``` ## Properties ### body ```ts body: AgentInstanceCreationRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/agent-instances"; ``` --- ## Type Alias: CreateAgentInstanceError ```ts type CreateAgentInstanceError = CreateAgentInstanceErrors[keyof CreateAgentInstanceErrors]; ``` --- ## Type Alias: CreateAgentInstanceErrors ```ts type CreateAgentInstanceErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The elementInstanceKey does not correspond to an active element instance. More details are provided in the response body. --- ### 409 ```ts 409: ProblemDetail; ``` An agent instance already exists for the given element instance. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: CreateAgentInstanceResponse ```ts type CreateAgentInstanceResponse = CreateAgentInstanceResponses[keyof CreateAgentInstanceResponses]; ``` --- ## Type Alias: CreateAgentInstanceResponses ```ts type CreateAgentInstanceResponses = object; ``` ## Properties ### 200 ```ts 200: AgentInstanceCreationResult; ``` The agent instance was created. --- ## Type Alias: CreateAuthorizationData ```ts type CreateAuthorizationData = object; ``` ## Properties ### body ```ts body: AuthorizationRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/authorizations"; ``` --- ## Type Alias: CreateAuthorizationError ```ts type CreateAuthorizationError = CreateAuthorizationErrors[keyof CreateAuthorizationErrors]; ``` --- ## Type Alias: CreateAuthorizationErrors ```ts type CreateAuthorizationErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The owner was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: CreateAuthorizationResponse ```ts type CreateAuthorizationResponse = CreateAuthorizationResponses[keyof CreateAuthorizationResponses]; ``` --- ## Type Alias: CreateAuthorizationResponses ```ts type CreateAuthorizationResponses = object; ``` ## Properties ### 201 ```ts 201: AuthorizationCreateResult; ``` The authorization was created successfully. --- ## Type Alias: CreateClusterVariableRequest ```ts type CreateClusterVariableRequest = object; ``` ## Properties ### kind? ```ts optional kind?: ClusterVariableKindEnum; ``` The kind of the cluster variable. Defaults to JSON if not specified. --- ### metadata? ```ts optional metadata?: object; ``` A generic key-value metadata bag attached to the cluster variable. Values must be strings or numbers. Limited to 100 entries and a configurable maximum serialized size (default: 100 entries at max key length of a cluster variable name (256 chars) plus the maximum value length, 8192 characters). #### Index Signature ```ts [key: string]: string | number ``` --- ### name ```ts name: ClusterVariableName; ``` The name of the cluster variable. Must be unique within its scope (global or tenant-specific). --- ### value ```ts value: unknown; ``` The value of the cluster variable. Can be any JSON object or primitive value. Will be serialized as a JSON string in responses. --- ## Type Alias: CreateDeploymentData ```ts type CreateDeploymentData = object; ``` ## Properties ### body ```ts body: object; ``` #### resources ```ts resources: (Blob | File)[]; ``` The binary data to create the deployment resources. It is possible to have more than one form part with different form part names for the binary data to create a deployment. #### tenantId? ```ts optional tenantId?: TenantId; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/deployments"; ``` --- ## Type Alias: CreateDeploymentError ```ts type CreateDeploymentError = CreateDeploymentErrors[keyof CreateDeploymentErrors]; ``` --- ## Type Alias: CreateDeploymentErrors ```ts type CreateDeploymentErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: CreateDeploymentResponse ```ts type CreateDeploymentResponse = CreateDeploymentResponses[keyof CreateDeploymentResponses]; ``` --- ## Type Alias: CreateDeploymentResponses ```ts type CreateDeploymentResponses = object; ``` ## Properties ### 200 ```ts 200: DeploymentResult; ``` The resources are deployed. --- ## Type Alias: CreateDocumentData ```ts type CreateDocumentData = object; ``` ## Properties ### body ```ts body: object; ``` #### file ```ts file: Blob | File; ``` #### metadata? ```ts optional metadata?: DocumentMetadata; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: object; ``` #### documentId? ```ts optional documentId?: DocumentId; ``` The ID of the document to upload. If not provided, a new ID will be generated. Specifying an existing ID will result in an error if the document already exists. #### storeId? ```ts optional storeId?: string; ``` The ID of the document store to upload the documents to. Currently, only a single document store is supported per cluster. However, this attribute is included to allow for potential future support of multiple document stores. --- ### url ```ts url: "/documents"; ``` --- ## Type Alias: CreateDocumentError ```ts type CreateDocumentError = CreateDocumentErrors[keyof CreateDocumentErrors]; ``` --- ## Type Alias: CreateDocumentErrors ```ts type CreateDocumentErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 415 ```ts 415: ProblemDetail; ``` The server cannot process the request because the media type (Content-Type) of the request payload is not supported by the server for the requested resource and method. --- ## Type Alias: CreateDocumentLinkData ```ts type CreateDocumentLinkData = object; ``` ## Properties ### body ```ts body: DocumentLinkRequest; ``` --- ### path ```ts path: object; ``` #### documentId ```ts documentId: DocumentId; ``` The ID of the document to link. --- ### query? ```ts optional query?: object; ``` #### contentHash? ```ts optional contentHash?: string; ``` The hash of the document content that was computed by the document store during upload. The hash is part of the document reference that is returned when uploading a document. If the client fails to provide the correct hash, the request will be rejected. #### storeId? ```ts optional storeId?: string; ``` The ID of the document store where the document is located. --- ### url ```ts url: "/documents/{documentId}/links"; ``` --- ## Type Alias: CreateDocumentLinkError ```ts type CreateDocumentLinkError = CreateDocumentLinkErrors[keyof CreateDocumentLinkErrors]; ``` --- ## Type Alias: CreateDocumentLinkErrors ```ts type CreateDocumentLinkErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ## Type Alias: CreateDocumentLinkResponse ```ts type CreateDocumentLinkResponse = CreateDocumentLinkResponses[keyof CreateDocumentLinkResponses]; ``` --- ## Type Alias: CreateDocumentLinkResponses ```ts type CreateDocumentLinkResponses = object; ``` ## Properties ### 201 ```ts 201: DocumentLink; ``` The document link was created successfully. --- ## Type Alias: CreateDocumentResponse ```ts type CreateDocumentResponse = CreateDocumentResponses[keyof CreateDocumentResponses]; ``` --- ## Type Alias: CreateDocumentResponses ```ts type CreateDocumentResponses = object; ``` ## Properties ### 201 ```ts 201: DocumentReference; ``` The document was uploaded successfully. --- ## Type Alias: CreateDocumentsData ```ts type CreateDocumentsData = object; ``` ## Properties ### body ```ts body: object; ``` #### files ```ts files: (Blob | File)[]; ``` The documents to upload. #### metadataList? ```ts optional metadataList?: DocumentMetadata[]; ``` Optional JSON array of metadata object whose index aligns with each file entry. The metadata array must have the same length as the files array. --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: object; ``` #### storeId? ```ts optional storeId?: string; ``` The ID of the document store to upload the documents to. Currently, only a single document store is supported per cluster. However, this attribute is included to allow for potential future support of multiple document stores. --- ### url ```ts url: "/documents/batch"; ``` --- ## Type Alias: CreateDocumentsError ```ts type CreateDocumentsError = CreateDocumentsErrors[keyof CreateDocumentsErrors]; ``` --- ## Type Alias: CreateDocumentsErrors ```ts type CreateDocumentsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 415 ```ts 415: ProblemDetail; ``` The server cannot process the request because the media type (Content-Type) of the request payload is not supported by the server for the requested resource and method. --- ## Type Alias: CreateDocumentsResponse ```ts type CreateDocumentsResponse = CreateDocumentsResponses[keyof CreateDocumentsResponses]; ``` --- ## Type Alias: CreateDocumentsResponses ```ts type CreateDocumentsResponses = object; ``` ## Properties ### 201 ```ts 201: DocumentCreationBatchResponse; ``` All documents were uploaded successfully. --- ### 207 ```ts 207: DocumentCreationBatchResponse; ``` Some documents were uploaded successfully, others failed. --- ## Type Alias: CreateElementInstanceVariablesData ```ts type CreateElementInstanceVariablesData = object; ``` ## Properties ### body ```ts body: SetVariableRequest; ``` --- ### path ```ts path: object; ``` #### elementInstanceKey ```ts elementInstanceKey: ElementInstanceKeyWritable; ``` The key of the element instance to update the variables for. This can be the process instance key (as obtained during instance creation), or a given element, such as a service task (see the `elementInstanceKey` on the job message). --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/element-instances/{elementInstanceKey}/variables"; ``` --- ## Type Alias: CreateElementInstanceVariablesError ```ts type CreateElementInstanceVariablesError = CreateElementInstanceVariablesErrors[keyof CreateElementInstanceVariablesErrors]; ``` --- ## Type Alias: CreateElementInstanceVariablesErrors ```ts type CreateElementInstanceVariablesErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ### 504 ```ts 504: ProblemDetail; ``` The request timed out between the gateway and the broker. For these endpoints, this often happens when user task listeners are configured and the corresponding listener job is not completed within the request timeout. Common causes include no available job workers for the listener type, busy or crashed job workers, or delayed job completion. As with any gateway timeout, general timeout causes (for example transient network issues) can also result in a 504 response. Troubleshooting: - verify that job workers for the listener type are running and healthy - check worker logs for crashes, retries, and completion failures - check network connectivity between workers, gateway, and broker - retry with backoff after transient failures - fail without retries if a problem persists --- ## Type Alias: CreateElementInstanceVariablesResponse ```ts type CreateElementInstanceVariablesResponse = CreateElementInstanceVariablesResponses[keyof CreateElementInstanceVariablesResponses]; ``` --- ## Type Alias: CreateElementInstanceVariablesResponses ```ts type CreateElementInstanceVariablesResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The variables were updated. --- ## Type Alias: CreateGlobalClusterVariableData ```ts type CreateGlobalClusterVariableData = object; ``` ## Properties ### body ```ts body: CreateClusterVariableRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/cluster-variables/global"; ``` --- ## Type Alias: CreateGlobalClusterVariableError ```ts type CreateGlobalClusterVariableError = CreateGlobalClusterVariableErrors[keyof CreateGlobalClusterVariableErrors]; ``` --- ## Type Alias: CreateGlobalClusterVariableErrors ```ts type CreateGlobalClusterVariableErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 409 ```ts 409: ProblemDetail; ``` A cluster variable with this name already exists. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: CreateGlobalClusterVariableResponse ```ts type CreateGlobalClusterVariableResponse = CreateGlobalClusterVariableResponses[keyof CreateGlobalClusterVariableResponses]; ``` --- ## Type Alias: CreateGlobalClusterVariableResponses ```ts type CreateGlobalClusterVariableResponses = object; ``` ## Properties ### 200 ```ts 200: ClusterVariableResult; ``` Cluster variable created --- ## Type Alias: CreateGlobalTaskListenerData ```ts type CreateGlobalTaskListenerData = object; ``` ## Properties ### body ```ts body: CreateGlobalTaskListenerRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/global-task-listeners"; ``` --- ## Type Alias: CreateGlobalTaskListenerError ```ts type CreateGlobalTaskListenerError = CreateGlobalTaskListenerErrors[keyof CreateGlobalTaskListenerErrors]; ``` --- ## Type Alias: CreateGlobalTaskListenerErrors ```ts type CreateGlobalTaskListenerErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 409 ```ts 409: ProblemDetail; ``` A global listener with this id already exists. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: CreateGlobalTaskListenerRequest ```ts type CreateGlobalTaskListenerRequest = GlobalTaskListenerBase & object; ``` ## Type Declaration ### eventTypes ```ts eventTypes: GlobalTaskListenerEventTypes; ``` ### id ```ts id: GlobalListenerId; ``` --- ## Type Alias: CreateGlobalTaskListenerResponse ```ts type CreateGlobalTaskListenerResponse = CreateGlobalTaskListenerResponses[keyof CreateGlobalTaskListenerResponses]; ``` --- ## Type Alias: CreateGlobalTaskListenerResponses ```ts type CreateGlobalTaskListenerResponses = object; ``` ## Properties ### 201 ```ts 201: GlobalTaskListenerResult; ``` The global user task listener was created successfully. --- ## Type Alias: CreateGroupData ```ts type CreateGroupData = object; ``` ## Properties ### body? ```ts optional body?: GroupCreateRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/groups"; ``` --- ## Type Alias: CreateGroupError ```ts type CreateGroupError = CreateGroupErrors[keyof CreateGroupErrors]; ``` --- ## Type Alias: CreateGroupErrors ```ts type CreateGroupErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 409 ```ts 409: ProblemDetail; ``` Group with this id already exists. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: CreateGroupResponse ```ts type CreateGroupResponse = CreateGroupResponses[keyof CreateGroupResponses]; ``` --- ## Type Alias: CreateGroupResponses ```ts type CreateGroupResponses = object; ``` ## Properties ### 201 ```ts 201: GroupCreateResult; ``` The group was created successfully. --- ## Type Alias: CreateMappingRuleData ```ts type CreateMappingRuleData = object; ``` ## Properties ### body? ```ts optional body?: MappingRuleCreateRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/mapping-rules"; ``` --- ## Type Alias: CreateMappingRuleError ```ts type CreateMappingRuleError = CreateMappingRuleErrors[keyof CreateMappingRuleErrors]; ``` --- ## Type Alias: CreateMappingRuleErrors ```ts type CreateMappingRuleErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` The request to create a mapping rule was denied. More details are provided in the response body. --- ### 404 ```ts 404: ProblemDetail; ``` The request to create a mapping rule was denied. --- ### 409 ```ts 409: ProblemDetail; ``` Mapping rule with this id already exists. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: CreateMappingRuleResponse ```ts type CreateMappingRuleResponse = CreateMappingRuleResponses[keyof CreateMappingRuleResponses]; ``` --- ## Type Alias: CreateMappingRuleResponses ```ts type CreateMappingRuleResponses = object; ``` ## Properties ### 201 ```ts 201: MappingRuleCreateResult; ``` The mapping rule was created successfully. --- ## Type Alias: CreateProcessInstanceData ```ts type CreateProcessInstanceData = object; ``` ## Properties ### body ```ts body: ProcessInstanceCreationInstruction; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances"; ``` --- ## Type Alias: CreateProcessInstanceError ```ts type CreateProcessInstanceError = CreateProcessInstanceErrors[keyof CreateProcessInstanceErrors]; ``` --- ## Type Alias: CreateProcessInstanceErrors ```ts type CreateProcessInstanceErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 409 ```ts 409: ProblemDetail; ``` The process instance creation was rejected due to a business ID uniqueness conflict. This can happen only when Business ID Uniqueness Control is enabled and an active root process instance with the provided business ID already exists for the same process definition and tenant. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ### 504 ```ts 504: ProblemDetail; ``` The process instance creation request timed out in the gateway. This can happen if the `awaitCompletion` request parameter is set to `true` and the created process instance did not complete within the defined request timeout. This often happens when the created instance is not fully automated or contains wait states. --- ## Type Alias: CreateProcessInstanceResponse ```ts type CreateProcessInstanceResponse = CreateProcessInstanceResponses[keyof CreateProcessInstanceResponses]; ``` --- ## Type Alias: CreateProcessInstanceResponses ```ts type CreateProcessInstanceResponses = object; ``` ## Properties ### 200 ```ts 200: CreateProcessInstanceResult; ``` The process instance was created. --- ## Type Alias: CreateProcessInstanceResult ```ts type CreateProcessInstanceResult = object; ``` ## Properties ### businessId ```ts businessId: BusinessId | null; ``` Business id as provided on creation. --- ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId; ``` The BPMN process id of the process definition which was used to create the process. instance --- ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey; ``` The key of the process definition which was used to create the process instance. --- ### processDefinitionVersion ```ts processDefinitionVersion: number; ``` The version of the process definition which was used to create the process instance. --- ### processInstanceKey ```ts processInstanceKey: ProcessInstanceKey; ``` The unique identifier of the created process instance; to be used wherever a request needs a process instance key (e.g. CancelProcessInstanceRequest). --- ### tags ```ts tags: TagSet; ``` --- ### tenantId ```ts tenantId: TenantId; ``` The tenant id of the created process instance. --- ### variables ```ts variables: object; ``` All the variables visible in the root scope. #### Index Signature ```ts [key: string]: unknown ``` --- ## Type Alias: CreateRoleData ```ts type CreateRoleData = object; ``` ## Properties ### body? ```ts optional body?: RoleCreateRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/roles"; ``` --- ## Type Alias: CreateRoleError ```ts type CreateRoleError = CreateRoleErrors[keyof CreateRoleErrors]; ``` --- ## Type Alias: CreateRoleErrors ```ts type CreateRoleErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 409 ```ts 409: ProblemDetail; ``` Role with this id already exists. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: CreateRoleResponse ```ts type CreateRoleResponse = CreateRoleResponses[keyof CreateRoleResponses]; ``` --- ## Type Alias: CreateRoleResponses ```ts type CreateRoleResponses = object; ``` ## Properties ### 201 ```ts 201: RoleCreateResult; ``` The role was created successfully. --- ## Type Alias: CreateTenantClusterVariableData ```ts type CreateTenantClusterVariableData = object; ``` ## Properties ### body ```ts body: CreateClusterVariableRequest; ``` --- ### path ```ts path: object; ``` #### tenantId ```ts tenantId: TenantId; ``` The tenant ID --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/cluster-variables/tenants/{tenantId}"; ``` --- ## Type Alias: CreateTenantClusterVariableError ```ts type CreateTenantClusterVariableError = CreateTenantClusterVariableErrors[keyof CreateTenantClusterVariableErrors]; ``` --- ## Type Alias: CreateTenantClusterVariableErrors ```ts type CreateTenantClusterVariableErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The tenant with the given ID was not found. --- ### 409 ```ts 409: ProblemDetail; ``` A cluster variable with this name already exists for the given tenant. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: CreateTenantClusterVariableResponse ```ts type CreateTenantClusterVariableResponse = CreateTenantClusterVariableResponses[keyof CreateTenantClusterVariableResponses]; ``` --- ## Type Alias: CreateTenantClusterVariableResponses ```ts type CreateTenantClusterVariableResponses = object; ``` ## Properties ### 200 ```ts 200: ClusterVariableResult; ``` Cluster variable created --- ## Type Alias: CreateTenantData ```ts type CreateTenantData = object; ``` ## Properties ### body ```ts body: TenantCreateRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/tenants"; ``` --- ## Type Alias: CreateTenantError ```ts type CreateTenantError = CreateTenantErrors[keyof CreateTenantErrors]; ``` --- ## Type Alias: CreateTenantErrors ```ts type CreateTenantErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Not found. The resource was not found. --- ### 409 ```ts 409: ProblemDetail; ``` Tenant with this id already exists. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: CreateTenantResponse ```ts type CreateTenantResponse = CreateTenantResponses[keyof CreateTenantResponses]; ``` --- ## Type Alias: CreateTenantResponses ```ts type CreateTenantResponses = object; ``` ## Properties ### 201 ```ts 201: TenantCreateResult; ``` The tenant was created successfully. --- ## Type Alias: CreateUserData ```ts type CreateUserData = object; ``` ## Properties ### body ```ts body: UserRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/users"; ``` --- ## Type Alias: CreateUserError ```ts type CreateUserError = CreateUserErrors[keyof CreateUserErrors]; ``` --- ## Type Alias: CreateUserErrors ```ts type CreateUserErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 409 ```ts 409: ProblemDetail; ``` A user with this username already exists. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: CreateUserResponse ```ts type CreateUserResponse = CreateUserResponses[keyof CreateUserResponses]; ``` --- ## Type Alias: CreateUserResponses ```ts type CreateUserResponses = object; ``` ## Properties ### 201 ```ts 201: UserCreateResult; ``` The user was created successfully. --- ## Type Alias: CursorBackwardPagination ```ts type CursorBackwardPagination = object; ``` Cursor-based backward pagination ## Properties ### before? ```ts optional before?: StartCursor; ``` Use the `startCursor` value from the previous response to fetch the previous page of results. --- ### limit? ```ts optional limit?: number; ``` The maximum number of items to return in one request. --- ## Type Alias: CursorForwardPagination ```ts type CursorForwardPagination = object; ``` Cursor-based forward pagination ## Properties ### after? ```ts optional after?: EndCursor; ``` Use the `endCursor` value from the previous response to fetch the next page of results. --- ### limit? ```ts optional limit?: number; ``` The maximum number of items to return in one request. --- ## Type Alias: DateTimeFilterProperty ```ts type DateTimeFilterProperty = string | AdvancedDateTimeFilter; ``` Date-time property with full advanced search capabilities. --- ## Type Alias: DecisionDefinitionFilter ```ts type DecisionDefinitionFilter = object; ``` Decision definition search filter. ## Properties ### decisionDefinitionId? ```ts optional decisionDefinitionId?: DecisionDefinitionId; ``` The DMN ID of the decision definition. --- ### decisionDefinitionKey? ```ts optional decisionDefinitionKey?: DecisionDefinitionKey; ``` The assigned key, which acts as a unique identifier for this decision definition. --- ### decisionRequirementsId? ```ts optional decisionRequirementsId?: string; ``` the DMN ID of the decision requirements graph that the decision definition is part of. --- ### decisionRequirementsKey? ```ts optional decisionRequirementsKey?: DecisionRequirementsKey; ``` The assigned key of the decision requirements graph that the decision definition is part of. --- ### decisionRequirementsName? ```ts optional decisionRequirementsName?: string; ``` The DMN name of the decision requirements that the decision definition is part of. --- ### decisionRequirementsVersion? ```ts optional decisionRequirementsVersion?: number; ``` The assigned version of the decision requirements that the decision definition is part of. --- ### isLatestVersion? ```ts optional isLatestVersion?: boolean; ``` Whether to only return the latest version of each decision definition. When using this filter, pagination functionality is limited, you can only paginate forward using `after` and `limit`. The response contains no `startCursor` in the `page`, and requests ignore the `from` and `before` in the `page`. --- ### name? ```ts optional name?: string; ``` The DMN name of the decision definition. --- ### tenantId? ```ts optional tenantId?: TenantId; ``` The tenant ID of the decision definition. --- ### version? ```ts optional version?: number; ``` The assigned version of the decision definition. --- ## Type Alias: DecisionDefinitionId ```ts type DecisionDefinitionId = CamundaKey<"DecisionDefinitionId">; ``` Id of a decision definition, from the model. Only ids of decision definitions that are deployed are useful. --- ## Type Alias: DecisionDefinitionKey ```ts type DecisionDefinitionKey = CamundaKey<"DecisionDefinitionKey">; ``` System-generated key for a decision definition. --- ## Type Alias: DecisionDefinitionKeyExactMatch ```ts type DecisionDefinitionKeyExactMatch = DecisionDefinitionKey; ``` Exact match Matches the value exactly. --- ## Type Alias: DecisionDefinitionKeyExactMatchWritable ```ts type DecisionDefinitionKeyExactMatchWritable = DecisionDefinitionKeyWritable; ``` Exact match Matches the value exactly. --- ## Type Alias: DecisionDefinitionKeyFilterProperty ```ts type DecisionDefinitionKeyFilterProperty = DecisionDefinitionKeyExactMatch | AdvancedDecisionDefinitionKeyFilter; ``` DecisionDefinitionKey property with full advanced search capabilities. --- ## Type Alias: DecisionDefinitionKeyWritable ```ts type DecisionDefinitionKeyWritable = LongKey; ``` System-generated key for a decision definition. --- ## Type Alias: DecisionDefinitionResult ```ts type DecisionDefinitionResult = object; ``` ## Properties ### decisionDefinitionId ```ts decisionDefinitionId: DecisionDefinitionId; ``` The DMN ID of the decision definition. --- ### decisionDefinitionKey ```ts decisionDefinitionKey: DecisionDefinitionKey; ``` The assigned key, which acts as a unique identifier for this decision definition. --- ### decisionRequirementsId ```ts decisionRequirementsId: string; ``` the DMN ID of the decision requirements graph that the decision definition is part of. --- ### decisionRequirementsKey ```ts decisionRequirementsKey: DecisionRequirementsKey; ``` The assigned key of the decision requirements graph that the decision definition is part of. --- ### decisionRequirementsName ```ts decisionRequirementsName: string; ``` The DMN name of the decision requirements that the decision definition is part of. --- ### decisionRequirementsVersion ```ts decisionRequirementsVersion: number; ``` The assigned version of the decision requirements that the decision definition is part of. --- ### name ```ts name: string; ``` The DMN name of the decision definition. --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID of the decision definition. --- ### version ```ts version: number; ``` The assigned version of the decision definition. --- ## Type Alias: DecisionDefinitionSearchQuery ```ts type DecisionDefinitionSearchQuery = SearchQueryRequest & object; ``` ## Type Declaration ### filter? ```ts optional filter?: DecisionDefinitionFilter; ``` The decision definition search filters. ### sort? ```ts optional sort?: DecisionDefinitionSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: DecisionDefinitionSearchQueryResult ```ts type DecisionDefinitionSearchQueryResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: DecisionDefinitionResult[]; ``` The matching decision definitions. --- ## Type Alias: DecisionDefinitionSearchQuerySortRequest ```ts type DecisionDefinitionSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "decisionDefinitionKey" | "decisionDefinitionId" | "name" | "version" | "decisionRequirementsId" | "decisionRequirementsKey" | "decisionRequirementsName" | "decisionRequirementsVersion" | "tenantId"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: DecisionDefinitionTypeEnum ```ts type DecisionDefinitionTypeEnum = (typeof DecisionDefinitionTypeEnum)[keyof typeof DecisionDefinitionTypeEnum]; ``` The type of the decision. UNSPECIFIED is deprecated and should not be used anymore, for removal in 8.10 --- ## Type Alias: DecisionEvaluationById ```ts type DecisionEvaluationById = object; ``` Decision evaluation by ID ## Properties ### decisionDefinitionId ```ts decisionDefinitionId: DecisionDefinitionId; ``` The ID of the decision to be evaluated. When using the decision ID, the latest deployed version of the decision is used. --- ### tenantId? ```ts optional tenantId?: TenantId; ``` The tenant ID of the decision. --- ### variables? ```ts optional variables?: object; ``` The decision evaluation variables as JSON document. #### Index Signature ```ts [key: string]: unknown ``` --- ## Type Alias: DecisionEvaluationByKey ```ts type DecisionEvaluationByKey = object; ``` Decision evaluation by key ## Properties ### decisionDefinitionKey ```ts decisionDefinitionKey: DecisionDefinitionKey; ``` --- ### tenantId? ```ts optional tenantId?: TenantId; ``` The tenant ID of the decision. --- ### variables? ```ts optional variables?: object; ``` The decision evaluation variables as JSON document. #### Index Signature ```ts [key: string]: unknown ``` --- ## Type Alias: DecisionEvaluationInstanceKey ```ts type DecisionEvaluationInstanceKey = CamundaKey<"DecisionEvaluationInstanceKey">; ``` System-generated identifier for a decision evaluation instance. It is composed of the parent decision evaluation key and the 1-based index of the evaluated decision within that evaluation, joined by a hyphen (format: `-`). --- ## Type Alias: DecisionEvaluationInstanceKeyExactMatch ```ts type DecisionEvaluationInstanceKeyExactMatch = DecisionEvaluationInstanceKey; ``` Exact match Matches the value exactly. --- ## Type Alias: DecisionEvaluationInstanceKeyExactMatchWritable ```ts type DecisionEvaluationInstanceKeyExactMatchWritable = DecisionEvaluationInstanceKey; ``` Exact match Matches the value exactly. --- ## Type Alias: DecisionEvaluationInstanceKeyFilterProperty ```ts type DecisionEvaluationInstanceKeyFilterProperty = | DecisionEvaluationInstanceKeyExactMatch | AdvancedDecisionEvaluationInstanceKeyFilter; ``` DecisionEvaluationInstanceKey property with full advanced search capabilities. --- ## Type Alias: DecisionEvaluationInstruction ```ts type DecisionEvaluationInstruction = DecisionEvaluationById | DecisionEvaluationByKey; ``` --- ## Type Alias: DecisionEvaluationKey ```ts type DecisionEvaluationKey = CamundaKey<"DecisionEvaluationKey">; ``` System-generated key for a decision evaluation. --- ## Type Alias: DecisionEvaluationKeyExactMatch ```ts type DecisionEvaluationKeyExactMatch = DecisionEvaluationKey; ``` Exact match Matches the value exactly. --- ## Type Alias: DecisionEvaluationKeyExactMatchWritable ```ts type DecisionEvaluationKeyExactMatchWritable = DecisionEvaluationKeyWritable; ``` Exact match Matches the value exactly. --- ## Type Alias: DecisionEvaluationKeyFilterProperty ```ts type DecisionEvaluationKeyFilterProperty = DecisionEvaluationKeyExactMatch | AdvancedDecisionEvaluationKeyFilter; ``` DecisionEvaluationKey property with full advanced search capabilities. --- ## Type Alias: DecisionEvaluationKeyWritable ```ts type DecisionEvaluationKeyWritable = LongKey; ``` System-generated key for a decision evaluation. --- ## Type Alias: DecisionInstanceDeletionBatchOperationRequest ```ts type DecisionInstanceDeletionBatchOperationRequest = object; ``` The decision instance filter that defines which decision instances should be deleted. ## Properties ### filter ```ts filter: DecisionInstanceFilter; ``` The decision instance filter. --- ### operationReference? ```ts optional operationReference?: OperationReference; ``` --- ## Type Alias: DecisionInstanceFilter ```ts type DecisionInstanceFilter = object; ``` Decision instance search filter. ## Properties ### businessId? ```ts optional businessId?: StringFilterProperty; ``` The business ID of the owning process instance the decision instance belongs to. This only works for decision instances created with 8.10 and onwards. Decision instances from prior versions and standalone evaluations don't contain this data and cannot be found. --- ### decisionDefinitionId? ```ts optional decisionDefinitionId?: DecisionDefinitionId; ``` The ID of the DMN decision. --- ### decisionDefinitionKey? ```ts optional decisionDefinitionKey?: DecisionDefinitionKeyFilterProperty; ``` The key of the decision. --- ### decisionDefinitionName? ```ts optional decisionDefinitionName?: string; ``` The name of the DMN decision. --- ### decisionDefinitionType? ```ts optional decisionDefinitionType?: DecisionDefinitionTypeEnum; ``` --- ### decisionDefinitionVersion? ```ts optional decisionDefinitionVersion?: number; ``` The version of the decision. --- ### decisionEvaluationInstanceKey? ```ts optional decisionEvaluationInstanceKey?: DecisionEvaluationInstanceKeyFilterProperty; ``` The key of the decision evaluation instance. --- ### decisionEvaluationKey? ```ts optional decisionEvaluationKey?: DecisionEvaluationKey; ``` The key of the parent decision evaluation. Note that this is not the identifier of an individual decision instance; the `decisionEvaluationInstanceKey` is the identifier for a decision instance. --- ### decisionRequirementsKey? ```ts optional decisionRequirementsKey?: DecisionRequirementsKeyFilterProperty; ``` The key of the decision requirements definition. --- ### elementInstanceKey? ```ts optional elementInstanceKey?: ElementInstanceKeyFilterProperty; ``` The key of the element instance this decision instance is linked to. --- ### evaluationDate? ```ts optional evaluationDate?: DateTimeFilterProperty; ``` The evaluation date of the decision instance. --- ### evaluationFailure? ```ts optional evaluationFailure?: string; ``` The evaluation failure of the decision instance. --- ### processDefinitionKey? ```ts optional processDefinitionKey?: ProcessDefinitionKey; ``` The key of the process definition. --- ### processInstanceKey? ```ts optional processInstanceKey?: ProcessInstanceKey; ``` The key of the process instance. --- ### rootDecisionDefinitionKey? ```ts optional rootDecisionDefinitionKey?: DecisionDefinitionKeyFilterProperty; ``` The key of the root decision definition. --- ### state? ```ts optional state?: DecisionInstanceStateFilterProperty; ``` The state of the decision instance. --- ### tenantId? ```ts optional tenantId?: TenantId; ``` The tenant ID of the decision instance. --- ## Type Alias: DecisionInstanceGetQueryResult ```ts type DecisionInstanceGetQueryResult = DecisionInstanceResult & object; ``` ## Type Declaration ### evaluatedInputs ```ts evaluatedInputs: EvaluatedDecisionInputItem[]; ``` The evaluated inputs of the decision instance. ### matchedRules ```ts matchedRules: MatchedDecisionRuleItem[]; ``` The matched rules of the decision instance. --- ## Type Alias: DecisionInstanceKey ```ts type DecisionInstanceKey = CamundaKey<"DecisionInstanceKey">; ``` System-generated key for a deployed decision instance. --- ## Type Alias: DecisionInstanceKeyWritable ```ts type DecisionInstanceKeyWritable = LongKey; ``` System-generated key for a deployed decision instance. --- ## Type Alias: DecisionInstanceResult ```ts type DecisionInstanceResult = object; ``` ## Properties ### businessId ```ts businessId: BusinessId | null; ``` The business ID of the owning process instance, inherited when the decision instance was evaluated. This is `null` for decision instances created before version 8.10, for standalone decision evaluations, and for decision instances whose owning process instance has no business ID. --- ### decisionDefinitionId ```ts decisionDefinitionId: DecisionDefinitionId; ``` The ID of the DMN decision. --- ### decisionDefinitionKey ```ts decisionDefinitionKey: DecisionDefinitionKey; ``` The key of the decision. --- ### decisionDefinitionName ```ts decisionDefinitionName: string; ``` The name of the DMN decision. --- ### decisionDefinitionType ```ts decisionDefinitionType: DecisionDefinitionTypeEnum; ``` --- ### decisionDefinitionVersion ```ts decisionDefinitionVersion: number; ``` The version of the decision. --- ### decisionEvaluationInstanceKey ```ts decisionEvaluationInstanceKey: DecisionEvaluationInstanceKey; ``` --- ### decisionEvaluationKey ```ts decisionEvaluationKey: DecisionEvaluationKey; ``` The key of the decision evaluation where this instance was created. --- ### elementInstanceKey ```ts elementInstanceKey: ElementInstanceKey | null; ``` The key of the element instance this decision instance is linked to. --- ### evaluationDate ```ts evaluationDate: string; ``` The evaluation date of the decision instance. --- ### evaluationFailure ```ts evaluationFailure: string | null; ``` The evaluation failure of the decision instance. --- ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey | null; ``` The key of the process definition. --- ### processInstanceKey ```ts processInstanceKey: ProcessInstanceKey | null; ``` The key of the process instance. --- ### result ```ts result: string; ``` The result of the decision instance. --- ### rootDecisionDefinitionKey ```ts rootDecisionDefinitionKey: DecisionDefinitionKey; ``` The key of the root decision definition. --- ### rootProcessInstanceKey ```ts rootProcessInstanceKey: ProcessInstanceKey | null; ``` The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. --- ### state ```ts state: DecisionInstanceStateEnum; ``` --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID of the decision instance. --- ## Type Alias: DecisionInstanceSearchQuery ```ts type DecisionInstanceSearchQuery = SearchQueryRequest & object; ``` ## Type Declaration ### filter? ```ts optional filter?: DecisionInstanceFilter; ``` The decision instance search filters. ### sort? ```ts optional sort?: DecisionInstanceSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: DecisionInstanceSearchQueryResult ```ts type DecisionInstanceSearchQueryResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: DecisionInstanceResult[]; ``` The matching decision instances. --- ## Type Alias: DecisionInstanceSearchQuerySortRequest ```ts type DecisionInstanceSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "businessId" | "decisionDefinitionId" | "decisionDefinitionKey" | "decisionDefinitionName" | "decisionDefinitionType" | "decisionDefinitionVersion" | "decisionEvaluationInstanceKey" | "decisionEvaluationKey" | "elementInstanceKey" | "evaluationDate" | "evaluationFailure" | "processDefinitionKey" | "processInstanceKey" | "rootDecisionDefinitionKey" | "state" | "tenantId"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: DecisionInstanceStateEnum ```ts type DecisionInstanceStateEnum = (typeof DecisionInstanceStateEnum)[keyof typeof DecisionInstanceStateEnum]; ``` The state of the decision instance. UNSPECIFIED and UNKNOWN are deprecated and should not be used anymore, for removal in 8.10 --- ## Type Alias: DecisionInstanceStateExactMatch ```ts type DecisionInstanceStateExactMatch = DecisionInstanceStateEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: DecisionInstanceStateExactMatchWritable ```ts type DecisionInstanceStateExactMatchWritable = DecisionInstanceStateEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: DecisionInstanceStateFilterProperty ```ts type DecisionInstanceStateFilterProperty = DecisionInstanceStateExactMatch | AdvancedDecisionInstanceStateFilter; ``` DecisionInstanceStateEnum property with full advanced search capabilities. --- ## Type Alias: DecisionRequirementsFilter ```ts type DecisionRequirementsFilter = object; ``` Decision requirements search filter. ## Properties ### decisionRequirementsId? ```ts optional decisionRequirementsId?: string; ``` the DMN ID of the decision requirements. --- ### decisionRequirementsKey? ```ts optional decisionRequirementsKey?: DecisionRequirementsKey; ``` --- ### decisionRequirementsName? ```ts optional decisionRequirementsName?: string; ``` The DMN name of the decision requirements. --- ### resourceName? ```ts optional resourceName?: string; ``` The name of the resource from which the decision requirements were parsed --- ### tenantId? ```ts optional tenantId?: TenantId; ``` The tenant ID of the decision requirements. --- ### version? ```ts optional version?: number; ``` The assigned version of the decision requirements. --- ## Type Alias: DecisionRequirementsKey ```ts type DecisionRequirementsKey = CamundaKey<"DecisionRequirementsKey">; ``` System-generated key for a deployed decision requirements definition. --- ## Type Alias: DecisionRequirementsKeyExactMatch ```ts type DecisionRequirementsKeyExactMatch = DecisionRequirementsKey; ``` Exact match Matches the value exactly. --- ## Type Alias: DecisionRequirementsKeyExactMatchWritable ```ts type DecisionRequirementsKeyExactMatchWritable = DecisionRequirementsKeyWritable; ``` Exact match Matches the value exactly. --- ## Type Alias: DecisionRequirementsKeyFilterProperty ```ts type DecisionRequirementsKeyFilterProperty = DecisionRequirementsKeyExactMatch | AdvancedDecisionRequirementsKeyFilter; ``` DecisionRequirementsKey property with full advanced search capabilities. --- ## Type Alias: DecisionRequirementsKeyWritable ```ts type DecisionRequirementsKeyWritable = LongKey; ``` System-generated key for a deployed decision requirements definition. --- ## Type Alias: DecisionRequirementsResult ```ts type DecisionRequirementsResult = object; ``` ## Properties ### decisionRequirementsId ```ts decisionRequirementsId: string; ``` The DMN ID of the decision requirements. --- ### decisionRequirementsKey ```ts decisionRequirementsKey: DecisionRequirementsKey; ``` The assigned key, which acts as a unique identifier for this decision requirements. --- ### decisionRequirementsName ```ts decisionRequirementsName: string; ``` The DMN name of the decision requirements. --- ### resourceName ```ts resourceName: string; ``` The name of the resource from which this decision requirements was parsed. --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID of the decision requirements. --- ### version ```ts version: number; ``` The assigned version of the decision requirements. --- ## Type Alias: DecisionRequirementsSearchQuery ```ts type DecisionRequirementsSearchQuery = SearchQueryRequest & object; ``` ## Type Declaration ### filter? ```ts optional filter?: DecisionRequirementsFilter; ``` The decision definition search filters. ### sort? ```ts optional sort?: DecisionRequirementsSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: DecisionRequirementsSearchQueryResult ```ts type DecisionRequirementsSearchQueryResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: DecisionRequirementsResult[]; ``` The matching decision requirements. --- ## Type Alias: DecisionRequirementsSearchQuerySortRequest ```ts type DecisionRequirementsSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "decisionRequirementsKey" | "decisionRequirementsName" | "version" | "decisionRequirementsId" | "tenantId"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: DeleteAuthorizationData ```ts type DeleteAuthorizationData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### authorizationKey ```ts authorizationKey: AuthorizationKeyWritable; ``` The key of the authorization to delete. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/authorizations/{authorizationKey}"; ``` --- ## Type Alias: DeleteAuthorizationError ```ts type DeleteAuthorizationError = DeleteAuthorizationErrors[keyof DeleteAuthorizationErrors]; ``` --- ## Type Alias: DeleteAuthorizationErrors ```ts type DeleteAuthorizationErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 404 ```ts 404: ProblemDetail; ``` The authorization with the authorizationKey was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: DeleteAuthorizationResponse ```ts type DeleteAuthorizationResponse = DeleteAuthorizationResponses[keyof DeleteAuthorizationResponses]; ``` --- ## Type Alias: DeleteAuthorizationResponses ```ts type DeleteAuthorizationResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The authorization was deleted successfully. --- ## Type Alias: DeleteDecisionInstanceData ```ts type DeleteDecisionInstanceData = object; ``` ## Properties ### body? ```ts optional body?: DeleteDecisionInstanceRequest; ``` --- ### path ```ts path: object; ``` #### decisionEvaluationKey ```ts decisionEvaluationKey: DecisionEvaluationKeyWritable; ``` The key of the decision evaluation to delete. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/decision-instances/{decisionEvaluationKey}/deletion"; ``` --- ## Type Alias: DeleteDecisionInstanceError ```ts type DeleteDecisionInstanceError = DeleteDecisionInstanceErrors[keyof DeleteDecisionInstanceErrors]; ``` --- ## Type Alias: DeleteDecisionInstanceErrors ```ts type DeleteDecisionInstanceErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The decision instance is not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: DeleteDecisionInstanceRequest ```ts type DeleteDecisionInstanceRequest = { operationReference?: OperationReference; } | null; ``` --- ## Type Alias: DeleteDecisionInstanceResponse ```ts type DeleteDecisionInstanceResponse = DeleteDecisionInstanceResponses[keyof DeleteDecisionInstanceResponses]; ``` --- ## Type Alias: DeleteDecisionInstanceResponses ```ts type DeleteDecisionInstanceResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The decision instance is marked for deletion. --- ## Type Alias: DeleteDecisionInstancesBatchOperationData ```ts type DeleteDecisionInstancesBatchOperationData = object; ``` ## Properties ### body ```ts body: DecisionInstanceDeletionBatchOperationRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/decision-instances/deletion"; ``` --- ## Type Alias: DeleteDecisionInstancesBatchOperationError ```ts type DeleteDecisionInstancesBatchOperationError = DeleteDecisionInstancesBatchOperationErrors[keyof DeleteDecisionInstancesBatchOperationErrors]; ``` --- ## Type Alias: DeleteDecisionInstancesBatchOperationErrors ```ts type DeleteDecisionInstancesBatchOperationErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The decision instance batch operation failed. More details are provided in the response body. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: DeleteDecisionInstancesBatchOperationResponse ```ts type DeleteDecisionInstancesBatchOperationResponse = DeleteDecisionInstancesBatchOperationResponses[keyof DeleteDecisionInstancesBatchOperationResponses]; ``` --- ## Type Alias: DeleteDecisionInstancesBatchOperationResponses ```ts type DeleteDecisionInstancesBatchOperationResponses = object; ``` ## Properties ### 200 ```ts 200: BatchOperationCreatedResult; ``` The batch operation request was created. --- ## Type Alias: DeleteDocumentData ```ts type DeleteDocumentData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### documentId ```ts documentId: DocumentId; ``` The ID of the document to delete. --- ### query? ```ts optional query?: object; ``` #### storeId? ```ts optional storeId?: string; ``` The ID of the document store to delete the document from. --- ### url ```ts url: "/documents/{documentId}"; ``` --- ## Type Alias: DeleteDocumentError ```ts type DeleteDocumentError = DeleteDocumentErrors[keyof DeleteDocumentErrors]; ``` --- ## Type Alias: DeleteDocumentErrors ```ts type DeleteDocumentErrors = object; ``` ## Properties ### 404 ```ts 404: ProblemDetail; ``` The document with the given ID was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: DeleteDocumentResponse ```ts type DeleteDocumentResponse = DeleteDocumentResponses[keyof DeleteDocumentResponses]; ``` --- ## Type Alias: DeleteDocumentResponses ```ts type DeleteDocumentResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The document was deleted successfully. --- ## Type Alias: DeleteGlobalClusterVariableData ```ts type DeleteGlobalClusterVariableData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### name ```ts name: ClusterVariableName; ``` The name of the cluster variable --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/cluster-variables/global/{name}"; ``` --- ## Type Alias: DeleteGlobalClusterVariableError ```ts type DeleteGlobalClusterVariableError = DeleteGlobalClusterVariableErrors[keyof DeleteGlobalClusterVariableErrors]; ``` --- ## Type Alias: DeleteGlobalClusterVariableErrors ```ts type DeleteGlobalClusterVariableErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Cluster variable not found --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: DeleteGlobalClusterVariableResponse ```ts type DeleteGlobalClusterVariableResponse = DeleteGlobalClusterVariableResponses[keyof DeleteGlobalClusterVariableResponses]; ``` --- ## Type Alias: DeleteGlobalClusterVariableResponses ```ts type DeleteGlobalClusterVariableResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` Cluster variable deleted successfully --- ## Type Alias: DeleteGlobalTaskListenerData ```ts type DeleteGlobalTaskListenerData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### id ```ts id: GlobalListenerId; ``` The id of the global user task listener to delete. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/global-task-listeners/{id}"; ``` --- ## Type Alias: DeleteGlobalTaskListenerError ```ts type DeleteGlobalTaskListenerError = DeleteGlobalTaskListenerErrors[keyof DeleteGlobalTaskListenerErrors]; ``` --- ## Type Alias: DeleteGlobalTaskListenerErrors ```ts type DeleteGlobalTaskListenerErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The global user task listener was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: DeleteGlobalTaskListenerResponse ```ts type DeleteGlobalTaskListenerResponse = DeleteGlobalTaskListenerResponses[keyof DeleteGlobalTaskListenerResponses]; ``` --- ## Type Alias: DeleteGlobalTaskListenerResponses ```ts type DeleteGlobalTaskListenerResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The global listener was deleted successfully. --- ## Type Alias: DeleteGroupData ```ts type DeleteGroupData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### groupId ```ts groupId: GroupId; ``` The group ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/groups/{groupId}"; ``` --- ## Type Alias: DeleteGroupError ```ts type DeleteGroupError = DeleteGroupErrors[keyof DeleteGroupErrors]; ``` --- ## Type Alias: DeleteGroupErrors ```ts type DeleteGroupErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 404 ```ts 404: ProblemDetail; ``` The group with the given ID was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: DeleteGroupResponse ```ts type DeleteGroupResponse = DeleteGroupResponses[keyof DeleteGroupResponses]; ``` --- ## Type Alias: DeleteGroupResponses ```ts type DeleteGroupResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The group was deleted successfully. --- ## Type Alias: DeleteHistoryBackupAsClusterAdminData ```ts type DeleteHistoryBackupAsClusterAdminData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### backupId ```ts backupId: BackupId; ``` The id of the backup. --- ### query? ```ts optional query?: object; ``` #### physicalTenantId? ```ts optional physicalTenantId?: string; ``` The physical tenant to apply the change to. When omitted, or when passed with an empty value, the change is applied to every physical tenant of the cluster. --- ### url ```ts url: "/cluster/v2/backups/history/{backupId}"; ``` --- ## Type Alias: DeleteHistoryBackupAsClusterAdminError ```ts type DeleteHistoryBackupAsClusterAdminError = DeleteHistoryBackupAsClusterAdminErrors[keyof DeleteHistoryBackupAsClusterAdminErrors]; ``` --- ## Type Alias: DeleteHistoryBackupAsClusterAdminErrors ```ts type DeleteHistoryBackupAsClusterAdminErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` The cluster's secondary storage cannot serve history backups, or a targeted physical tenant's snapshot repository is absent from the store. Unlike the per-physical-tenant backup endpoints, the cluster-admin surface performs no fine-grained authorization, so a missing `BACKUP` permission is never the reason. Deletion fans out with no preceding check, so an absent repository is found only once that tenant is reached, by which time the backup may already be deleted from the others; those deletions are not undone. Narrow the request with `physicalTenantId` to work with the tenants whose repository is usable. --- ### 404 ```ts 404: ProblemDetail; ``` The requested `physicalTenantId` does not exist in this cluster, or every targeted physical tenant was reached and none of them holds a backup with the given id. --- ### 500 ```ts 500: ProblemDetail; ``` The backup could not be deleted from every targeted physical tenant, because one of them hit an internal error, so it may still exist on some of them. The deletions that already succeeded are not undone, so a retry has only the remaining tenants left to reach. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: DeleteHistoryBackupAsClusterAdminResponse ```ts type DeleteHistoryBackupAsClusterAdminResponse = DeleteHistoryBackupAsClusterAdminResponses[keyof DeleteHistoryBackupAsClusterAdminResponses]; ``` --- ## Type Alias: DeleteHistoryBackupAsClusterAdminResponses ```ts type DeleteHistoryBackupAsClusterAdminResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` No targeted physical tenant holds the backup any more, because it was deleted from every tenant that held it. At least one tenant held it. --- ## Type Alias: DeleteHistoryBackupData ```ts type DeleteHistoryBackupData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### backupId ```ts backupId: BackupId; ``` The id of the backup. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/backups/history/{backupId}"; ``` --- ## Type Alias: DeleteHistoryBackupError ```ts type DeleteHistoryBackupError = DeleteHistoryBackupErrors[keyof DeleteHistoryBackupErrors]; ``` --- ## Type Alias: DeleteHistoryBackupErrors ```ts type DeleteHistoryBackupErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` The request is forbidden for one of three reasons: the authenticated caller lacks the required `BACKUP` permission; the cluster's secondary storage is neither Elasticsearch nor OpenSearch and therefore cannot serve history backups; or the physical tenant's snapshot repository is absent from the store — configured under a name the store does not have, or not configured at all. The problem detail says which applies. The latter two are deployment faults the caller cannot correct by changing its request. --- ### 404 ```ts 404: ProblemDetail; ``` A backup with the given id does not exist. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: DeleteHistoryBackupResponse ```ts type DeleteHistoryBackupResponse = DeleteHistoryBackupResponses[keyof DeleteHistoryBackupResponses]; ``` --- ## Type Alias: DeleteHistoryBackupResponses ```ts type DeleteHistoryBackupResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The backup has been successfully deleted. --- ## Type Alias: DeleteMappingRuleData ```ts type DeleteMappingRuleData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### mappingRuleId ```ts mappingRuleId: MappingRuleId; ``` The ID of the mapping rule to delete. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/mapping-rules/{mappingRuleId}"; ``` --- ## Type Alias: DeleteMappingRuleError ```ts type DeleteMappingRuleError = DeleteMappingRuleErrors[keyof DeleteMappingRuleErrors]; ``` --- ## Type Alias: DeleteMappingRuleErrors ```ts type DeleteMappingRuleErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 404 ```ts 404: ProblemDetail; ``` The mapping rule with the mappingRuleId was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: DeleteMappingRuleResponse ```ts type DeleteMappingRuleResponse = DeleteMappingRuleResponses[keyof DeleteMappingRuleResponses]; ``` --- ## Type Alias: DeleteMappingRuleResponses ```ts type DeleteMappingRuleResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The mapping rule was deleted successfully. --- ## Type Alias: DeleteProcessInstanceData ```ts type DeleteProcessInstanceData = object; ``` ## Properties ### body? ```ts optional body?: DeleteProcessInstanceRequest; ``` --- ### path ```ts path: object; ``` #### processInstanceKey ```ts processInstanceKey: ProcessInstanceKeyWritable; ``` The key of the process instance to delete. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/{processInstanceKey}/deletion"; ``` --- ## Type Alias: DeleteProcessInstanceError ```ts type DeleteProcessInstanceError = DeleteProcessInstanceErrors[keyof DeleteProcessInstanceErrors]; ``` --- ## Type Alias: DeleteProcessInstanceErrors ```ts type DeleteProcessInstanceErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The process instance is not found. --- ### 409 ```ts 409: ProblemDetail; ``` The process instance is not in a completed or terminated state and cannot be deleted. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: DeleteProcessInstanceRequest ```ts type DeleteProcessInstanceRequest = { operationReference?: OperationReference; } | null; ``` --- ## Type Alias: DeleteProcessInstanceResponse ```ts type DeleteProcessInstanceResponse = DeleteProcessInstanceResponses[keyof DeleteProcessInstanceResponses]; ``` --- ## Type Alias: DeleteProcessInstanceResponses ```ts type DeleteProcessInstanceResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The process instance is marked for deletion. --- ## Type Alias: DeleteProcessInstancesBatchOperationData ```ts type DeleteProcessInstancesBatchOperationData = object; ``` ## Properties ### body ```ts body: ProcessInstanceDeletionBatchOperationRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/deletion"; ``` --- ## Type Alias: DeleteProcessInstancesBatchOperationError ```ts type DeleteProcessInstancesBatchOperationError = DeleteProcessInstancesBatchOperationErrors[keyof DeleteProcessInstancesBatchOperationErrors]; ``` --- ## Type Alias: DeleteProcessInstancesBatchOperationErrors ```ts type DeleteProcessInstancesBatchOperationErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The process instance batch operation failed. More details are provided in the response body. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: DeleteProcessInstancesBatchOperationResponse ```ts type DeleteProcessInstancesBatchOperationResponse = DeleteProcessInstancesBatchOperationResponses[keyof DeleteProcessInstancesBatchOperationResponses]; ``` --- ## Type Alias: DeleteProcessInstancesBatchOperationResponses ```ts type DeleteProcessInstancesBatchOperationResponses = object; ``` ## Properties ### 200 ```ts 200: BatchOperationCreatedResult; ``` The batch operation request was created. --- ## Type Alias: DeleteResourceData ```ts type DeleteResourceData = object; ``` ## Properties ### body? ```ts optional body?: DeleteResourceRequest; ``` --- ### path ```ts path: object; ``` #### resourceKey ```ts resourceKey: ResourceKeyWritable; ``` The key of the resource to delete. This can be the key of a process definition, the key of a decision requirements definition or the key of a form definition --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/resources/{resourceKey}/deletion"; ``` --- ## Type Alias: DeleteResourceError ```ts type DeleteResourceError = DeleteResourceErrors[keyof DeleteResourceErrors]; ``` --- ## Type Alias: DeleteResourceErrors ```ts type DeleteResourceErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 404 ```ts 404: ProblemDetail; ``` The resource is not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: DeleteResourceRequest ```ts type DeleteResourceRequest = { deleteHistory?: boolean; operationReference?: OperationReference; } | null; ``` ## Union Members ### Type Literal ```ts { deleteHistory?: boolean; operationReference?: OperationReference; } ``` #### deleteHistory? ```ts optional deleteHistory?: boolean; ``` Indicates if the historic data associated with the resource should also be deleted asynchronously. This flag is effective for process definitions and decision requirements definitions. For other resource types (forms, generic resources) it is ignored and no history is deleted. For a decision requirements definition the `batchOperation` field in the response carries the created batch operation. For a process definition the history is deleted as part of the definition's draining/deletion lifecycle and no batch operation is returned. #### operationReference? ```ts optional operationReference?: OperationReference; ``` --- `null` --- ## Type Alias: DeleteResourceResponse ```ts type DeleteResourceResponse = object; ``` ## Properties ### batchOperation ```ts batchOperation: BatchOperationCreatedResult | null; ``` The batch operation created for asynchronously deleting the historic data. Populated when `deleteHistory` is `true` and either the resource is a decision requirements definition, or the resource is a process definition that is already fully deleted from the runtime state (its history is purged directly by a batch operation). For a process definition that still exists in the runtime state, deletion first drains the definition and its history is removed asynchronously as part of that lifecycle, so no batch operation is returned and this field is `null`. It is also `null` for forms and generic resources. --- ### resourceKey ```ts resourceKey: ResourceKey; ``` The system-assigned key for this resource, requested to be deleted. --- ## Type Alias: DeleteResourceResponse2 ```ts type DeleteResourceResponse2 = DeleteResourceResponses[keyof DeleteResourceResponses]; ``` --- ## Type Alias: DeleteResourceResponses ```ts type DeleteResourceResponses = object; ``` ## Properties ### 200 ```ts 200: DeleteResourceResponse; ``` The resource is deleted. --- ## Type Alias: DeleteRoleData ```ts type DeleteRoleData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### roleId ```ts roleId: RoleId; ``` The role ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/roles/{roleId}"; ``` --- ## Type Alias: DeleteRoleError ```ts type DeleteRoleError = DeleteRoleErrors[keyof DeleteRoleErrors]; ``` --- ## Type Alias: DeleteRoleErrors ```ts type DeleteRoleErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 404 ```ts 404: ProblemDetail; ``` The role with the ID was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: DeleteRoleResponse ```ts type DeleteRoleResponse = DeleteRoleResponses[keyof DeleteRoleResponses]; ``` --- ## Type Alias: DeleteRoleResponses ```ts type DeleteRoleResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The role was deleted successfully. --- ## Type Alias: DeleteRuntimeBackupAsClusterAdminData ```ts type DeleteRuntimeBackupAsClusterAdminData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### backupId ```ts backupId: BackupId; ``` The id of the backup. --- ### query? ```ts optional query?: object; ``` #### physicalTenantId? ```ts optional physicalTenantId?: string; ``` The physical tenant to apply the change to. When omitted, or when passed with an empty value, the change is applied to every physical tenant of the cluster. --- ### url ```ts url: "/cluster/v2/backups/runtime/{backupId}"; ``` --- ## Type Alias: DeleteRuntimeBackupAsClusterAdminError ```ts type DeleteRuntimeBackupAsClusterAdminError = DeleteRuntimeBackupAsClusterAdminErrors[keyof DeleteRuntimeBackupAsClusterAdminErrors]; ``` --- ## Type Alias: DeleteRuntimeBackupAsClusterAdminErrors ```ts type DeleteRuntimeBackupAsClusterAdminErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 404 ```ts 404: ProblemDetail; ``` The requested `physicalTenantId` does not exist in this cluster. --- ### 500 ```ts 500: ProblemDetail; ``` The backup could not be deleted from every targeted physical tenant, so it may still exist on some of them. The deletions that already succeeded are not undone, so a retry has only the remaining tenants left to reach. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: DeleteRuntimeBackupAsClusterAdminResponse ```ts type DeleteRuntimeBackupAsClusterAdminResponse = DeleteRuntimeBackupAsClusterAdminResponses[keyof DeleteRuntimeBackupAsClusterAdminResponses]; ``` --- ## Type Alias: DeleteRuntimeBackupAsClusterAdminResponses ```ts type DeleteRuntimeBackupAsClusterAdminResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` No targeted physical tenant holds the backup any more. --- ## Type Alias: DeleteRuntimeBackupData ```ts type DeleteRuntimeBackupData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### backupId ```ts backupId: BackupId; ``` The id of the backup. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/backups/runtime/{backupId}"; ``` --- ## Type Alias: DeleteRuntimeBackupError ```ts type DeleteRuntimeBackupError = DeleteRuntimeBackupErrors[keyof DeleteRuntimeBackupErrors]; ``` --- ## Type Alias: DeleteRuntimeBackupErrors ```ts type DeleteRuntimeBackupErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: DeleteRuntimeBackupResponse ```ts type DeleteRuntimeBackupResponse = DeleteRuntimeBackupResponses[keyof DeleteRuntimeBackupResponses]; ``` --- ## Type Alias: DeleteRuntimeBackupResponses ```ts type DeleteRuntimeBackupResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The backup has been successfully deleted. --- ## Type Alias: DeleteRuntimeBackupStateAsClusterAdminData ```ts type DeleteRuntimeBackupStateAsClusterAdminData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: object; ``` #### physicalTenantId? ```ts optional physicalTenantId?: string; ``` The physical tenant to apply the change to. When omitted, or when passed with an empty value, the change is applied to every physical tenant of the cluster. --- ### url ```ts url: "/cluster/v2/backups/runtime/state"; ``` --- ## Type Alias: DeleteRuntimeBackupStateAsClusterAdminError ```ts type DeleteRuntimeBackupStateAsClusterAdminError = DeleteRuntimeBackupStateAsClusterAdminErrors[keyof DeleteRuntimeBackupStateAsClusterAdminErrors]; ``` --- ## Type Alias: DeleteRuntimeBackupStateAsClusterAdminErrors ```ts type DeleteRuntimeBackupStateAsClusterAdminErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 404 ```ts 404: ProblemDetail; ``` The requested `physicalTenantId` does not exist in this cluster. --- ### 500 ```ts 500: ProblemDetail; ``` The state could not be reset on every targeted physical tenant, so it may still be set on some of them. The resets that already succeeded are not undone, so a retry has only the remaining tenants left to reach. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: DeleteRuntimeBackupStateAsClusterAdminResponse ```ts type DeleteRuntimeBackupStateAsClusterAdminResponse = DeleteRuntimeBackupStateAsClusterAdminResponses[keyof DeleteRuntimeBackupStateAsClusterAdminResponses]; ``` --- ## Type Alias: DeleteRuntimeBackupStateAsClusterAdminResponses ```ts type DeleteRuntimeBackupStateAsClusterAdminResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The runtime backup state was reset on every targeted physical tenant. --- ## Type Alias: DeleteRuntimeBackupStateData ```ts type DeleteRuntimeBackupStateData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/backups/runtime/state"; ``` --- ## Type Alias: DeleteRuntimeBackupStateError ```ts type DeleteRuntimeBackupStateError = DeleteRuntimeBackupStateErrors[keyof DeleteRuntimeBackupStateErrors]; ``` --- ## Type Alias: DeleteRuntimeBackupStateErrors ```ts type DeleteRuntimeBackupStateErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: DeleteRuntimeBackupStateResponse ```ts type DeleteRuntimeBackupStateResponse = DeleteRuntimeBackupStateResponses[keyof DeleteRuntimeBackupStateResponses]; ``` --- ## Type Alias: DeleteRuntimeBackupStateResponses ```ts type DeleteRuntimeBackupStateResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The runtime backup state has been successfully reset. --- ## Type Alias: DeleteTenantClusterVariableData ```ts type DeleteTenantClusterVariableData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### name ```ts name: ClusterVariableName; ``` The name of the cluster variable #### tenantId ```ts tenantId: TenantId; ``` The tenant ID --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/cluster-variables/tenants/{tenantId}/{name}"; ``` --- ## Type Alias: DeleteTenantClusterVariableError ```ts type DeleteTenantClusterVariableError = DeleteTenantClusterVariableErrors[keyof DeleteTenantClusterVariableErrors]; ``` --- ## Type Alias: DeleteTenantClusterVariableErrors ```ts type DeleteTenantClusterVariableErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Cluster variable not found --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: DeleteTenantClusterVariableResponse ```ts type DeleteTenantClusterVariableResponse = DeleteTenantClusterVariableResponses[keyof DeleteTenantClusterVariableResponses]; ``` --- ## Type Alias: DeleteTenantClusterVariableResponses ```ts type DeleteTenantClusterVariableResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` Cluster variable deleted successfully --- ## Type Alias: DeleteTenantData ```ts type DeleteTenantData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### tenantId ```ts tenantId: TenantId; ``` The unique identifier of the tenant. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/tenants/{tenantId}"; ``` --- ## Type Alias: DeleteTenantError ```ts type DeleteTenantError = DeleteTenantErrors[keyof DeleteTenantErrors]; ``` --- ## Type Alias: DeleteTenantErrors ```ts type DeleteTenantErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Not found. The tenant was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: DeleteTenantResponse ```ts type DeleteTenantResponse = DeleteTenantResponses[keyof DeleteTenantResponses]; ``` --- ## Type Alias: DeleteTenantResponses ```ts type DeleteTenantResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The tenant was deleted successfully. --- ## Type Alias: DeleteUserData ```ts type DeleteUserData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### username ```ts username: Username; ``` The username of the user to delete. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/users/{username}"; ``` --- ## Type Alias: DeleteUserError ```ts type DeleteUserError = DeleteUserErrors[keyof DeleteUserErrors]; ``` --- ## Type Alias: DeleteUserErrors ```ts type DeleteUserErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 404 ```ts 404: ProblemDetail; ``` The user is not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: DeleteUserResponse ```ts type DeleteUserResponse = DeleteUserResponses[keyof DeleteUserResponses]; ``` --- ## Type Alias: DeleteUserResponses ```ts type DeleteUserResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The user was deleted successfully. --- ## Type Alias: DeploymentConfigurationResponse ```ts type DeploymentConfigurationResponse = object; ``` Configuration for deployment characteristics. ## Properties ### isMultiTenancyEnabled ```ts isMultiTenancyEnabled: boolean; ``` Whether multi-tenancy is enabled. --- ### isTenantsApiEnabled ```ts isTenantsApiEnabled: boolean; ``` Whether the tenants management API and UI are enabled. --- ### maxRequestSize ```ts maxRequestSize: number; ``` The maximum HTTP request size in bytes. --- ## Type Alias: DeploymentDecisionRequirementsResult ```ts type DeploymentDecisionRequirementsResult = object; ``` Deployed decision requirements. ## Properties ### decisionRequirementsId ```ts decisionRequirementsId: string; ``` The id of the deployed decision requirements. --- ### decisionRequirementsKey ```ts decisionRequirementsKey: DecisionRequirementsKey; ``` The assigned decision requirements key, which acts as a unique identifier for this decision requirements. --- ### decisionRequirementsName ```ts decisionRequirementsName: string; ``` The name of the deployed decision requirements. --- ### resourceName ```ts resourceName: string; ``` The name of the resource. --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID of the deployed decision requirements. --- ### version ```ts version: number; ``` The version of the deployed decision requirements. --- ## Type Alias: DeploymentDecisionResult ```ts type DeploymentDecisionResult = object; ``` A deployed decision. ## Properties ### decisionDefinitionId ```ts decisionDefinitionId: DecisionDefinitionId; ``` The dmn decision ID, as parsed during deployment, together with the version forms a unique identifier for a specific decision. --- ### decisionDefinitionKey ```ts decisionDefinitionKey: DecisionDefinitionKey; ``` The assigned decision key, which acts as a unique identifier for this decision. --- ### decisionRequirementsId ```ts decisionRequirementsId: string; ``` The dmn ID of the decision requirements graph that this decision is part of, as parsed during deployment. --- ### decisionRequirementsKey ```ts decisionRequirementsKey: DecisionRequirementsKey; ``` The assigned key of the decision requirements graph that this decision is part of. --- ### name ```ts name: string; ``` The DMN name of the decision, as parsed during deployment. --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID of the deployed decision. --- ### version ```ts version: number; ``` The assigned decision version. --- ## Type Alias: DeploymentFormResult ```ts type DeploymentFormResult = object; ``` A deployed form. ## Properties ### formId ```ts formId: FormId; ``` The form ID, as parsed during deployment, together with the version forms a unique identifier for a specific form. --- ### formKey ```ts formKey: FormKey; ``` The assigned key, which acts as a unique identifier for this form. --- ### resourceName ```ts resourceName: string; ``` The name of the resource. --- ### tenantId ```ts tenantId: TenantId; ``` --- ### version ```ts version: number; ``` The version of the deployed form. --- ## Type Alias: DeploymentKey ```ts type DeploymentKey = CamundaKey<"DeploymentKey">; ``` Key for a deployment. --- ## Type Alias: DeploymentKeyExactMatch ```ts type DeploymentKeyExactMatch = DeploymentKey; ``` Exact match Matches the value exactly. --- ## Type Alias: DeploymentKeyExactMatchWritable ```ts type DeploymentKeyExactMatchWritable = DeploymentKeyWritable; ``` Exact match Matches the value exactly. --- ## Type Alias: DeploymentKeyFilterProperty ```ts type DeploymentKeyFilterProperty = DeploymentKeyExactMatch | AdvancedDeploymentKeyFilter; ``` DeploymentKey property with full advanced search capabilities. --- ## Type Alias: DeploymentKeyWritable ```ts type DeploymentKeyWritable = LongKey; ``` Key for a deployment. --- ## Type Alias: DeploymentMetadataResult ```ts type DeploymentMetadataResult = object; ``` ## Properties ### decisionDefinition ```ts decisionDefinition: DeploymentDecisionResult | null; ``` Deployed decision. --- ### decisionRequirements ```ts decisionRequirements: | DeploymentDecisionRequirementsResult | null; ``` Deployed decision requirement definition. --- ### form ```ts form: DeploymentFormResult | null; ``` Deployed form. --- ### processDefinition ```ts processDefinition: DeploymentProcessResult | null; ``` Deployed process. --- ### resource ```ts resource: DeploymentResourceResult | null; ``` Deployed resource. --- ## Type Alias: DeploymentProcessResult ```ts type DeploymentProcessResult = object; ``` A deployed process. ## Properties ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId; ``` The bpmn process ID, as parsed during deployment, together with the version forms a unique identifier for a specific process definition. --- ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey; ``` The assigned key, which acts as a unique identifier for this process. --- ### processDefinitionVersion ```ts processDefinitionVersion: number; ``` The assigned process version. --- ### resourceName ```ts resourceName: string; ``` The resource name from which this process was parsed. --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID of the deployed process. --- ## Type Alias: DeploymentResourceResult ```ts type DeploymentResourceResult = object; ``` A deployed Resource. ## Properties ### resourceId ```ts resourceId: string; ``` The resource id of the deployed resource. --- ### resourceKey ```ts resourceKey: ResourceKey; ``` The assigned key, which acts as a unique identifier for this Resource. --- ### resourceName ```ts resourceName: string; ``` The name of the deployed resource. --- ### tenantId ```ts tenantId: TenantId; ``` --- ### version ```ts version: number; ``` The description of the deployed resource. --- ## Type Alias: DeploymentResult ```ts type DeploymentResult = object; ``` ## Properties ### deploymentKey ```ts deploymentKey: DeploymentKey; ``` The unique key identifying the deployment. --- ### deployments ```ts deployments: DeploymentMetadataResult[]; ``` Items deployed by the request. --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID associated with the deployment. --- ## Type Alias: DirectAncestorKeyInstruction ```ts type DirectAncestorKeyInstruction = object; ``` Provides a concrete key to use as ancestor scope for the created element instance. ## Properties ### ancestorElementInstanceKey ```ts ancestorElementInstanceKey: ElementInstanceKey; ``` The key of the ancestor scope the element instance should be created in. Set to -1 to create the new element instance within an existing element instance of the flow scope. If multiple instances of the target element's flow scope exist, choose one specifically with this property by providing its key. --- ### ancestorScopeType ```ts ancestorScopeType: string; ``` The type of ancestor scope instruction. --- ## Type Alias: DocumentCreationBatchResponse ```ts type DocumentCreationBatchResponse = object; ``` ## Properties ### createdDocuments ```ts createdDocuments: DocumentReference[]; ``` Documents that failed creation. --- ### failedDocuments ```ts failedDocuments: DocumentCreationFailureDetail[]; ``` Documents that were successfully created. --- ## Type Alias: DocumentCreationFailureDetail ```ts type DocumentCreationFailureDetail = object; ``` ## Properties ### detail ```ts detail: string; ``` A human-readable explanation specific to this occurrence of the problem. --- ### fileName ```ts fileName: string; ``` The name of the file that failed to upload. --- ### status ```ts status: number; ``` The HTTP status code of the failure. --- ### title ```ts title: string; ``` A short, human-readable summary of the problem type. --- ## Type Alias: DocumentId ```ts type DocumentId = CamundaKey<"DocumentId">; ``` Document Id that uniquely identifies a document. --- ## Type Alias: DocumentLink ```ts type DocumentLink = object; ``` ## Properties ### expiresAt ```ts expiresAt: string; ``` The date and time when the link expires. --- ### url ```ts url: string; ``` The link to the document. --- ## Type Alias: DocumentLinkRequest ```ts type DocumentLinkRequest = object; ``` ## Properties ### timeToLive? ```ts optional timeToLive?: number; ``` The time-to-live of the document link in ms. --- ## Type Alias: DocumentMetadata ```ts type DocumentMetadata = object; ``` Information about the document. ## Properties ### contentType? ```ts optional contentType?: string; ``` The content type of the document. --- ### customProperties? ```ts optional customProperties?: object; ``` Custom properties of the document. #### Index Signature ```ts [key: string]: unknown ``` --- ### expiresAt? ```ts optional expiresAt?: string; ``` The date and time when the document expires. --- ### fileName? ```ts optional fileName?: string; ``` The name of the file. --- ### processDefinitionId? ```ts optional processDefinitionId?: ProcessDefinitionId; ``` The ID of the process definition that created the document. --- ### processInstanceKey? ```ts optional processInstanceKey?: ProcessInstanceKey; ``` The key of the process instance that created the document. --- ### size? ```ts optional size?: number; ``` The size of the document in bytes. --- ## Type Alias: DocumentMetadataResponse ```ts type DocumentMetadataResponse = object; ``` Information about the document that is returned in responses. ## Properties ### contentType ```ts contentType: string; ``` The content type of the document. --- ### customProperties ```ts customProperties: object; ``` Custom properties of the document. #### Index Signature ```ts [key: string]: unknown ``` --- ### expiresAt ```ts expiresAt: string | null; ``` The date and time when the document expires. --- ### fileName ```ts fileName: string; ``` The name of the file. --- ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId | null; ``` The ID of the process definition that created the document. --- ### processInstanceKey ```ts processInstanceKey: ProcessInstanceKey | null; ``` The key of the process instance that created the document. --- ### size ```ts size: number; ``` The size of the document in bytes. --- ## Type Alias: DocumentReference ```ts type DocumentReference = object; ``` ## Properties ### camunda.document.type ```ts camunda.document.type: "camunda"; ``` Document discriminator. Always set to "camunda". --- ### contentHash ```ts contentHash: string | null; ``` The hash of the document. --- ### documentId ```ts documentId: DocumentId; ``` The ID of the document. --- ### metadata ```ts metadata: DocumentMetadataResponse; ``` --- ### storeId ```ts storeId: string; ``` The ID of the document store. --- ## Type Alias: ElementId ```ts type ElementId = CamundaKey<"ElementId">; ``` The model-defined id of an element. --- ## Type Alias: ElementIdExactMatch ```ts type ElementIdExactMatch = ElementId; ``` Exact match Matches the value exactly. --- ## Type Alias: ElementIdExactMatchWritable ```ts type ElementIdExactMatchWritable = ElementId; ``` Exact match Matches the value exactly. --- ## Type Alias: ElementIdFilterProperty ```ts type ElementIdFilterProperty = ElementIdExactMatch | AdvancedElementIdFilter; ``` ElementId property with full advanced search capabilities. --- ## Type Alias: ElementInstanceFilter ```ts type ElementInstanceFilter = ElementInstanceFilterFields & object; ``` Element instance search filter. ## Type Declaration ### $or? ```ts optional $or?: ElementInstanceFilterFields[]; ``` Defines a list of alternative filter groups combined using OR logic. Each object in the array is evaluated independently, and the filter matches if any one of them is satisfied. Top-level fields and the `$or` clause are combined using AND logic — meaning: (top-level filters) AND (any of the `$or` filters) must match. _Example:_ ```json { "processInstanceKey": "2251799813685323", "$or": [ { "elementName": { "$like": "*Order*" } }, { "elementId": { "$like": "*Order*" } } ] } ``` This matches element instances scoped to the given process instance whose: - `elementName` contains _Order_, or - `elementId` contains _Order_ Note: Using complex `$or` conditions may impact performance, use with caution in high-volume environments. --- ## Type Alias: ElementInstanceFilterFields ```ts type ElementInstanceFilterFields = object; ``` Element instance filter fields. ## Properties ### elementId? ```ts optional elementId?: ElementIdFilterProperty; ``` The element ID for this element instance. --- ### elementInstanceKey? ```ts optional elementInstanceKey?: ElementInstanceKey; ``` The assigned key, which acts as a unique identifier for this element instance. --- ### elementInstanceScopeKey? ```ts optional elementInstanceScopeKey?: | ElementInstanceKey | ProcessInstanceKey; ``` The scope key of this element instance. If provided with a process instance key it will return element instances that are immediate children of the process instance. If provided with an element instance key it will return element instances that are immediate children of the element instance. --- ### elementName? ```ts optional elementName?: StringFilterProperty; ``` The element name. This only works for data created with 8.8 and onwards. Instances from prior versions don't contain this data and cannot be found. --- ### endDate? ```ts optional endDate?: DateTimeFilterProperty; ``` The end date of this element instance. --- ### hasIncident? ```ts optional hasIncident?: boolean; ``` Shows whether this element instance has an incident related to. --- ### incidentKey? ```ts optional incidentKey?: IncidentKey; ``` The key of incident if field incident is true. --- ### processDefinitionId? ```ts optional processDefinitionId?: ProcessDefinitionId; ``` The process definition ID associated to this element instance. --- ### processDefinitionKey? ```ts optional processDefinitionKey?: ProcessDefinitionKey; ``` The process definition key associated to this element instance. --- ### processInstanceKey? ```ts optional processInstanceKey?: ProcessInstanceKey; ``` The process instance key associated to this element instance. --- ### startDate? ```ts optional startDate?: DateTimeFilterProperty; ``` The start date of this element instance. --- ### state? ```ts optional state?: ElementInstanceStateFilterProperty; ``` State of element instance as defined set of values. --- ### tenantId? ```ts optional tenantId?: TenantId; ``` --- ### type? ```ts optional type?: | "UNSPECIFIED" | "PROCESS" | "SUB_PROCESS" | "EVENT_SUB_PROCESS" | "AD_HOC_SUB_PROCESS" | "AD_HOC_SUB_PROCESS_INNER_INSTANCE" | "START_EVENT" | "INTERMEDIATE_CATCH_EVENT" | "INTERMEDIATE_THROW_EVENT" | "BOUNDARY_EVENT" | "END_EVENT" | "SERVICE_TASK" | "RECEIVE_TASK" | "USER_TASK" | "MANUAL_TASK" | "TASK" | "EXCLUSIVE_GATEWAY" | "INCLUSIVE_GATEWAY" | "PARALLEL_GATEWAY" | "EVENT_BASED_GATEWAY" | "SEQUENCE_FLOW" | "MULTI_INSTANCE_BODY" | "CALL_ACTIVITY" | "BUSINESS_RULE_TASK" | "SCRIPT_TASK" | "SEND_TASK" | "UNKNOWN"; ``` Type of element as defined set of values. --- ## Type Alias: ElementInstanceKey ```ts type ElementInstanceKey = CamundaKey<"ElementInstanceKey">; ``` System-generated key for a element instance. --- ## Type Alias: ElementInstanceKeyExactMatch ```ts type ElementInstanceKeyExactMatch = ElementInstanceKey; ``` Exact match Matches the value exactly. --- ## Type Alias: ElementInstanceKeyExactMatchWritable ```ts type ElementInstanceKeyExactMatchWritable = ElementInstanceKeyWritable; ``` Exact match Matches the value exactly. --- ## Type Alias: ElementInstanceKeyFilterProperty ```ts type ElementInstanceKeyFilterProperty = ElementInstanceKeyExactMatch | AdvancedElementInstanceKeyFilter; ``` ElementInstanceKey property with full advanced search capabilities. --- ## Type Alias: ElementInstanceKeyWritable ```ts type ElementInstanceKeyWritable = LongKey; ``` System-generated key for a element instance. --- ## Type Alias: ElementInstanceResult ```ts type ElementInstanceResult = object; ``` ## Properties ### elementId ```ts elementId: ElementId; ``` The element ID for this element instance. --- ### elementInstanceKey ```ts elementInstanceKey: ElementInstanceKey; ``` The assigned key, which acts as a unique identifier for this element instance. --- ### elementName ```ts elementName: string; ``` The element name for this element instance. --- ### endDate ```ts endDate: string | null; ``` Date when element instance finished. --- ### hasIncident ```ts hasIncident: boolean; ``` Shows whether this element instance has an incident. If true also an incidentKey is provided. --- ### incidentKey ```ts incidentKey: IncidentKey | null; ``` Incident key associated with this element instance. --- ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId; ``` The process definition ID associated to this element instance. --- ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey; ``` The process definition key associated to this element instance. --- ### processInstanceKey ```ts processInstanceKey: ProcessInstanceKey; ``` The process instance key associated to this element instance. --- ### rootProcessInstanceKey ```ts rootProcessInstanceKey: ProcessInstanceKey | null; ``` The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. --- ### startDate ```ts startDate: string; ``` Date when element instance started. --- ### state ```ts state: ElementInstanceStateEnum; ``` State of element instance as defined set of values. --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID of the incident. --- ### type ```ts type: | "UNSPECIFIED" | "PROCESS" | "SUB_PROCESS" | "EVENT_SUB_PROCESS" | "AD_HOC_SUB_PROCESS" | "AD_HOC_SUB_PROCESS_INNER_INSTANCE" | "START_EVENT" | "INTERMEDIATE_CATCH_EVENT" | "INTERMEDIATE_THROW_EVENT" | "BOUNDARY_EVENT" | "END_EVENT" | "SERVICE_TASK" | "RECEIVE_TASK" | "USER_TASK" | "MANUAL_TASK" | "TASK" | "EXCLUSIVE_GATEWAY" | "INCLUSIVE_GATEWAY" | "PARALLEL_GATEWAY" | "EVENT_BASED_GATEWAY" | "SEQUENCE_FLOW" | "MULTI_INSTANCE_BODY" | "CALL_ACTIVITY" | "BUSINESS_RULE_TASK" | "SCRIPT_TASK" | "SEND_TASK" | "UNKNOWN"; ``` Type of element as defined set of values. --- ## Type Alias: ElementInstanceSearchQuery ```ts type ElementInstanceSearchQuery = SearchQueryRequest & object; ``` Element instance search request. ## Type Declaration ### filter? ```ts optional filter?: ElementInstanceFilter; ``` The element instance search filters. ### sort? ```ts optional sort?: ElementInstanceSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: ElementInstanceSearchQueryResult ```ts type ElementInstanceSearchQueryResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: ElementInstanceResult[]; ``` The matching element instances. --- ## Type Alias: ElementInstanceSearchQuerySortRequest ```ts type ElementInstanceSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "elementInstanceKey" | "processInstanceKey" | "processDefinitionKey" | "processDefinitionId" | "startDate" | "endDate" | "elementId" | "elementName" | "type" | "state" | "incidentKey" | "tenantId"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: ElementInstanceStateEnum ```ts type ElementInstanceStateEnum = (typeof ElementInstanceStateEnum)[keyof typeof ElementInstanceStateEnum]; ``` Element states --- ## Type Alias: ElementInstanceStateExactMatch ```ts type ElementInstanceStateExactMatch = ElementInstanceStateEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: ElementInstanceStateExactMatchWritable ```ts type ElementInstanceStateExactMatchWritable = ElementInstanceStateEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: ElementInstanceStateFilterProperty ```ts type ElementInstanceStateFilterProperty = ElementInstanceStateExactMatch | AdvancedElementInstanceStateFilter; ``` ElementInstanceStateEnum property with full advanced search capabilities. --- ## Type Alias: ElementInstanceWaitStateFilter ```ts type ElementInstanceWaitStateFilter = object; ``` Filters for the element instance inspection. ## Properties ### elementId? ```ts optional elementId?: ElementIdFilterProperty; ``` Filter by element ID. --- ### elementInstanceKey? ```ts optional elementInstanceKey?: ElementInstanceKeyFilterProperty; ``` Filter by element instance key. --- ### elementType? ```ts optional elementType?: WaitStateElementTypeFilterProperty; ``` Filter by element type. --- ### processInstanceKey? ```ts optional processInstanceKey?: ProcessInstanceKeyFilterProperty; ``` Filter by process instance key. --- ### rootProcessInstanceKey? ```ts optional rootProcessInstanceKey?: ProcessInstanceKeyFilterProperty; ``` Filter by root process instance key. --- ### waitStateType? ```ts optional waitStateType?: WaitStateTypeFilterProperty; ``` Filter by wait state type. --- ## Type Alias: ElementInstanceWaitStateQuery ```ts type ElementInstanceWaitStateQuery = SearchQueryRequest & object; ``` Element instance inspection request. ## Type Declaration ### filter? ```ts optional filter?: ElementInstanceWaitStateFilter; ``` Filter criteria for the inspection. ### sort? ```ts optional sort?: ElementInstanceWaitStateQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: ElementInstanceWaitStateQueryResult ```ts type ElementInstanceWaitStateQueryResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: ElementInstanceWaitStateResult[]; ``` The matching waiting states. --- ## Type Alias: ElementInstanceWaitStateQuerySortRequest ```ts type ElementInstanceWaitStateQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "elementInstanceKey" | "processInstanceKey" | "rootProcessInstanceKey" | "elementId"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: ElementInstanceWaitStateResult ```ts type ElementInstanceWaitStateResult = object; ``` An element instance waiting state. ## Properties ### bpmnProcessId ```ts bpmnProcessId: string; ``` The BPMN process ID of the process definition associated to this element instance. --- ### details ```ts details: WaitStateDetails; ``` Wait-state-specific details, resolved by waitStateType. --- ### elementId ```ts elementId: ElementId; ``` The element ID for this element instance. --- ### elementInstanceKey ```ts elementInstanceKey: ElementInstanceKey; ``` The element instance key associated to this element instance. --- ### elementType ```ts elementType: WaitStateElementTypeEnum; ``` The BPMN element type of this element instance. --- ### processInstanceKey ```ts processInstanceKey: ProcessInstanceKey; ``` The process instance key associated to this element instance. --- ### rootProcessInstanceKey ```ts rootProcessInstanceKey: ProcessInstanceKey | null; ``` Key of the root process instance. --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID of the element instance. --- ## Type Alias: EndCursor ```ts type EndCursor = CamundaKey<"EndCursor">; ``` The end cursor in a search query result set. --- ## Type Alias: EnrichedActivatedJobOf # Type Alias: EnrichedActivatedJobOf\ ```ts type EnrichedActivatedJobOf = J & Omit; ``` Generic projection of an enriched job over its underlying activated-job shape `J`, used by the marker-driven dependent-presence overloads to narrow individual response properties — e.g. `jobLeaseToken` becomes non-null when a job was activated with `withLease: true`, or an optional nullable field (`{ jobLeaseToken?: null }`) when it was not. The absent projection re-types the property as optional-and-null rather than removing it, so it is not assignable to the base non-nullable narrowing; `J` is therefore left unconstrained rather than bounded by `ActivatedJobResult`. The overlay is `Omit` — the non-base portion of [EnrichedActivatedJob](../interfaces/EnrichedActivatedJob.md) — rather than `EnrichedActivatedJobActions` directly. This preserves the declaration-merge compatibility promise for _narrowed_ jobs too: any field a consumer merges onto the public `EnrichedActivatedJob` interface (e.g. `interface EnrichedActivatedJob { myFlag: boolean }`) is carried onto `activateJobs({ withLease: true })` results as well, instead of silently disappearing from the narrowed overload while remaining on the base one. Defaults to the base `ActivatedJobResult`, so `EnrichedActivatedJobOf` with no argument equals [EnrichedActivatedJob](../interfaces/EnrichedActivatedJob.md). ## Type Parameters ### J `J` = `ActivatedJobResult` --- ## Type Alias: EntityTypeExactMatch ```ts type EntityTypeExactMatch = AuditLogEntityTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: EntityTypeExactMatchWritable ```ts type EntityTypeExactMatchWritable = AuditLogEntityTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: EntityTypeFilterProperty ```ts type EntityTypeFilterProperty = EntityTypeExactMatch | AdvancedEntityTypeFilter; ``` AuditLogEntityTypeEnum property with full advanced search capabilities. --- ## Type Alias: EvaluateConditionalResult ```ts type EvaluateConditionalResult = object; ``` ## Properties ### conditionalEvaluationKey ```ts conditionalEvaluationKey: ConditionalEvaluationKey; ``` The unique key of the conditional evaluation operation. --- ### processInstances ```ts processInstances: ProcessInstanceReference[]; ``` List of process instances created. If no root-level conditional start events evaluated to true, the list will be empty. --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID of the conditional evaluation operation. --- ## Type Alias: EvaluateConditionalsData ```ts type EvaluateConditionalsData = object; ``` ## Properties ### body ```ts body: ConditionalEvaluationInstruction; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/conditionals/evaluation"; ``` --- ## Type Alias: EvaluateConditionalsError ```ts type EvaluateConditionalsError = EvaluateConditionalsErrors[keyof EvaluateConditionalsErrors]; ``` --- ## Type Alias: EvaluateConditionalsErrors ```ts type EvaluateConditionalsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` The client is not authorized to start process instances for the specified process definition. If a processDefinitionKey is not provided, this indicates that the client is not authorized to start process instances for at least one of the matched process definitions. --- ### 404 ```ts 404: ProblemDetail; ``` The process definition was not found for the given processDefinitionKey. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: EvaluateConditionalsResponse ```ts type EvaluateConditionalsResponse = EvaluateConditionalsResponses[keyof EvaluateConditionalsResponses]; ``` --- ## Type Alias: EvaluateConditionalsResponses ```ts type EvaluateConditionalsResponses = object; ``` ## Properties ### 200 ```ts 200: EvaluateConditionalResult; ``` Successfully evaluated root-level conditional start events. --- ## Type Alias: EvaluateDecisionData ```ts type EvaluateDecisionData = object; ``` ## Properties ### body ```ts body: DecisionEvaluationInstruction; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/decision-definitions/evaluation"; ``` --- ## Type Alias: EvaluateDecisionError ```ts type EvaluateDecisionError = EvaluateDecisionErrors[keyof EvaluateDecisionErrors]; ``` --- ## Type Alias: EvaluateDecisionErrors ```ts type EvaluateDecisionErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 404 ```ts 404: ProblemDetail; ``` The decision is not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: EvaluateDecisionResponse ```ts type EvaluateDecisionResponse = EvaluateDecisionResponses[keyof EvaluateDecisionResponses]; ``` --- ## Type Alias: EvaluateDecisionResponses ```ts type EvaluateDecisionResponses = object; ``` ## Properties ### 200 ```ts 200: EvaluateDecisionResult; ``` The decision was evaluated. --- ## Type Alias: EvaluateDecisionResult ```ts type EvaluateDecisionResult = object; ``` ## Properties ### decisionDefinitionId ```ts decisionDefinitionId: DecisionDefinitionId; ``` The ID of the decision which was evaluated. --- ### decisionDefinitionKey ```ts decisionDefinitionKey: DecisionDefinitionKey; ``` The unique key identifying the decision which was evaluated. --- ### decisionDefinitionName ```ts decisionDefinitionName: string; ``` The name of the decision which was evaluated. --- ### decisionDefinitionVersion ```ts decisionDefinitionVersion: number; ``` The version of the decision which was evaluated. --- ### decisionEvaluationKey ```ts decisionEvaluationKey: DecisionEvaluationKey; ``` The unique key identifying this decision evaluation. --- ### ~~decisionInstanceKey~~ ```ts decisionInstanceKey: DecisionInstanceKey; ``` Deprecated, please refer to `decisionEvaluationKey`. #### Deprecated --- ### decisionRequirementsId ```ts decisionRequirementsId: string; ``` The ID of the decision requirements graph that the decision which was evaluated is part of. --- ### decisionRequirementsKey ```ts decisionRequirementsKey: DecisionRequirementsKey; ``` The unique key identifying the decision requirements graph that the decision which was evaluated is part of. --- ### evaluatedDecisions ```ts evaluatedDecisions: EvaluatedDecisionResult[]; ``` Decisions that were evaluated within the requested decision evaluation. --- ### failedDecisionDefinitionId ```ts failedDecisionDefinitionId: DecisionDefinitionId | null; ``` The ID of the decision which failed during evaluation. --- ### failureMessage ```ts failureMessage: string | null; ``` Message describing why the decision which was evaluated failed. --- ### output ```ts output: string; ``` JSON document that will instantiate the result of the decision which was evaluated. --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID of the evaluated decision. --- ## Type Alias: EvaluateExpressionData ```ts type EvaluateExpressionData = object; ``` ## Properties ### body ```ts body: ExpressionEvaluationRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/expression/evaluation"; ``` --- ## Type Alias: EvaluateExpressionError ```ts type EvaluateExpressionError = EvaluateExpressionErrors[keyof EvaluateExpressionErrors]; ``` --- ## Type Alias: EvaluateExpressionErrors ```ts type EvaluateExpressionErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: EvaluateExpressionResponse ```ts type EvaluateExpressionResponse = EvaluateExpressionResponses[keyof EvaluateExpressionResponses]; ``` --- ## Type Alias: EvaluateExpressionResponses ```ts type EvaluateExpressionResponses = object; ``` ## Properties ### 200 ```ts 200: ExpressionEvaluationResult; ``` Expression evaluated successfully --- ## Type Alias: EvaluatedDecisionInputItem ```ts type EvaluatedDecisionInputItem = object; ``` A decision input that was evaluated within this decision evaluation. ## Properties ### inputId ```ts inputId: string; ``` The identifier of the decision input. --- ### inputName ```ts inputName: string; ``` The name of the decision input. --- ### inputValue ```ts inputValue: string; ``` The value of the decision input. --- ## Type Alias: EvaluatedDecisionOutputItem ```ts type EvaluatedDecisionOutputItem = object; ``` The evaluated decision outputs. ## Properties ### outputId ```ts outputId: string; ``` The ID of the evaluated decison output item. --- ### outputName ```ts outputName: string; ``` The name of the of the evaluated decison output item. --- ### outputValue ```ts outputValue: string; ``` The value of the evaluated decison output item. --- ### ruleId ```ts ruleId: string | null; ``` The ID of the matched rule. --- ### ruleIndex ```ts ruleIndex: number | null; ``` The index of the matched rule. --- ## Type Alias: EvaluatedDecisionResult ```ts type EvaluatedDecisionResult = object; ``` A decision that was evaluated. ## Properties ### decisionDefinitionId ```ts decisionDefinitionId: DecisionDefinitionId; ``` The ID of the decision which was evaluated. --- ### decisionDefinitionKey ```ts decisionDefinitionKey: DecisionDefinitionKey; ``` The unique key identifying the decision which was evaluate. --- ### decisionDefinitionName ```ts decisionDefinitionName: string; ``` The name of the decision which was evaluated. --- ### decisionDefinitionType ```ts decisionDefinitionType: string; ``` The type of the decision which was evaluated. --- ### decisionDefinitionVersion ```ts decisionDefinitionVersion: number; ``` The version of the decision which was evaluated. --- ### decisionEvaluationInstanceKey ```ts decisionEvaluationInstanceKey: DecisionEvaluationInstanceKey; ``` The unique key identifying this decision evaluation instance. --- ### evaluatedInputs ```ts evaluatedInputs: EvaluatedDecisionInputItem[]; ``` The decision inputs that were evaluated within this decision evaluation. --- ### matchedRules ```ts matchedRules: MatchedDecisionRuleItem[]; ``` The decision rules that matched within this decision evaluation. --- ### output ```ts output: string; ``` JSON document that will instantiate the result of the decision which was evaluated. --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID of the evaluated decision. --- ## Type Alias: ExportingStatusCode ```ts type ExportingStatusCode = "EXPORTING" | "PAUSED" | "SOFT_PAUSED" | "MIXED"; ``` Exporting Status Code The exporting status of a physical tenant, aggregated over every replica of every one of its partitions: - `EXPORTING`: all replicas are exporting and committing their position. - `PAUSED`: all replicas are paused, nothing is being exported. - `SOFT_PAUSED`: all replicas keep exporting but do not commit their position. - `MIXED`: replicas report different phases, so the tenant is in no single phase. --- ## Type Alias: ExportingStatusResponse ```ts type ExportingStatusResponse = object; ``` ExportingStatusResponse Response body for the exporting status of a physical tenant. ## Properties ### status ```ts status: ExportingStatusCode; ``` The aggregated exporting status of the physical tenant. --- ## Type Alias: ExpressionEvaluationRequest ```ts type ExpressionEvaluationRequest = object; ``` ## Properties ### expression ```ts expression: string; ``` The expression to evaluate (e.g., "=x + y") --- ### scopeKey? ```ts optional scopeKey?: ScopeKey; ``` Key of the process instance or element instance whose variables should be made visible to the expression. Use a process instance key to evaluate against the process instance scope, or an element instance key to evaluate against that element instance scope. If omitted, the expression is evaluated unscoped, using only cluster variables and request-body variables. --- ### tenantId? ```ts optional tenantId?: string; ``` Required when the expression references tenant-scoped cluster variables --- ### variables? ```ts optional variables?: | { [key: string]: unknown; } | null; ``` Optional variables for expression evaluation. These variables are only used for the current evaluation and do not persist beyond it. --- ## Type Alias: ExpressionEvaluationResult ```ts type ExpressionEvaluationResult = object; ``` ## Properties ### expression ```ts expression: string; ``` The evaluated expression --- ### referencedSecrets ```ts referencedSecrets: ExpressionSecretReferenceItem[]; ``` The secret references resolved from trusted sources while evaluating the expression: a `camunda.secrets.` reference used directly in the expression, or a reference carried by a `SECRET_REFERENCE`-kind cluster variable the expression read. References appearing only in request-body variables or plain cluster variables are excluded. Callers use this to know which `camunda.secrets.` occurrences in the result they may safely resolve. --- ### result ```ts result: unknown; ``` The result value. Its type can vary. --- ### warnings ```ts warnings: ExpressionEvaluationWarningItem[]; ``` List of warnings generated during expression evaluation --- ## Type Alias: ExpressionEvaluationWarningItem ```ts type ExpressionEvaluationWarningItem = object; ``` ## Properties ### message ```ts message: string; ``` The warning message --- ## Type Alias: ExpressionSecretReferenceItem ```ts type ExpressionSecretReferenceItem = object; ``` ## Properties ### secretName ```ts secretName: string; ``` The secret name, e.g. "token" for "camunda.secrets.token" --- ### storeId ```ts storeId: string; ``` The identifier of the secret store that holds the referenced secret --- ## Type Alias: FailJobData ```ts type FailJobData = object; ``` ## Properties ### body? ```ts optional body?: JobFailRequest; ``` --- ### path ```ts path: object; ``` #### jobKey ```ts jobKey: JobKeyWritable; ``` The key of the job to fail. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/jobs/{jobKey}/failure"; ``` --- ## Type Alias: FailJobError ```ts type FailJobError = FailJobErrors[keyof FailJobErrors]; ``` --- ## Type Alias: FailJobErrors ```ts type FailJobErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 404 ```ts 404: ProblemDetail; ``` The job with the given jobKey is not found. It was completed by another worker, or the process instance itself was canceled. --- ### 409 ```ts 409: ProblemDetail; ``` The job with the given key is in the wrong state (i.e: not ACTIVATED or ACTIVATABLE). The job was failed by another worker with retries = 0, and the process is now in an incident state. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: FailJobResponse ```ts type FailJobResponse = FailJobResponses[keyof FailJobResponses]; ``` --- ## Type Alias: FailJobResponses ```ts type FailJobResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The job is failed. --- ## Type Alias: FetchPage # Type Alias: FetchPage\ ```ts type FetchPage = ( body, signal?, isFirstPage? ) => Promise>; ``` Fetches one page for a given body. Decoupled from the facade's `ec` arg. `isFirstPage` is `true` only for the initial fetch of _this_ iteration run (each `pages()`/`items()`/`toArray()` call starts a fresh run). Adapters that carry per-first-page state (e.g. an eventual-consistency window) must key off this flag rather than closure state so that reusing a paginator, or consuming two views concurrently, does not leak first-page behaviour across iterators. ## Type Parameters ### TItem `TItem` ### TBody `TBody` _extends_ [`SearchBody`](SearchBody.md) ## Parameters ### body `TBody` ### signal? `AbortSignal` ### isFirstPage? `boolean` ## Returns `Promise`\<[`SearchResponse`](../interfaces/SearchResponse.md)\<`TItem`\>\> --- ## Type Alias: FormId ```ts type FormId = CamundaKey<"FormId">; ``` The user-defined id for the form --- ## Type Alias: FormKey ```ts type FormKey = CamundaKey<"FormKey">; ``` System-generated key for a deployed form. --- ## Type Alias: FormKeyExactMatch ```ts type FormKeyExactMatch = FormKey; ``` Exact match Matches the value exactly. --- ## Type Alias: FormKeyExactMatchWritable ```ts type FormKeyExactMatchWritable = FormKeyWritable; ``` Exact match Matches the value exactly. --- ## Type Alias: FormKeyFilterProperty ```ts type FormKeyFilterProperty = FormKeyExactMatch | AdvancedFormKeyFilter; ``` FormKey property with full advanced search capabilities. --- ## Type Alias: FormKeyWritable ```ts type FormKeyWritable = LongKey; ``` System-generated key for a deployed form. --- ## Type Alias: FormResult ```ts type FormResult = object; ``` ## Properties ### formId ```ts formId: FormId; ``` The user-provided identifier of the form. --- ### formKey ```ts formKey: FormKey; ``` The assigned key, which acts as a unique identifier for this form. --- ### schema ```ts schema: string; ``` The form schema as a JSON document serialized as a string. --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID of the form. --- ### version ```ts version: number; ``` The version of the the deployed form. --- ## Type Alias: GetAgentDefinitionData ```ts type GetAgentDefinitionData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### agentDefinitionKey ```ts agentDefinitionKey: AgentDefinitionKeyWritable; ``` The assigned key of the agent definition, which acts as a unique identifier for this agent definition. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/agent-definitions/{agentDefinitionKey}"; ``` --- ## Type Alias: GetAgentDefinitionError ```ts type GetAgentDefinitionError = GetAgentDefinitionErrors[keyof GetAgentDefinitionErrors]; ``` --- ## Type Alias: GetAgentDefinitionErrors ```ts type GetAgentDefinitionErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The agent definition with the given key was not found. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetAgentDefinitionResponse ```ts type GetAgentDefinitionResponse = GetAgentDefinitionResponses[keyof GetAgentDefinitionResponses]; ``` --- ## Type Alias: GetAgentDefinitionResponses ```ts type GetAgentDefinitionResponses = object; ``` ## Properties ### 200 ```ts 200: AgentDefinitionResult; ``` The agent definition is successfully returned. --- ## Type Alias: GetAgentInstanceData ```ts type GetAgentInstanceData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### agentInstanceKey ```ts agentInstanceKey: AgentInstanceKeyWritable; ``` The key of the agent instance to retrieve. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/agent-instances/{agentInstanceKey}"; ``` --- ## Type Alias: GetAgentInstanceError ```ts type GetAgentInstanceError = GetAgentInstanceErrors[keyof GetAgentInstanceErrors]; ``` --- ## Type Alias: GetAgentInstanceErrors ```ts type GetAgentInstanceErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The agent instance with the given key was not found. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: GetAgentInstanceResponse ```ts type GetAgentInstanceResponse = GetAgentInstanceResponses[keyof GetAgentInstanceResponses]; ``` --- ## Type Alias: GetAgentInstanceResponses ```ts type GetAgentInstanceResponses = object; ``` ## Properties ### 200 ```ts 200: AgentInstanceResult; ``` The agent instance is successfully returned. --- ## Type Alias: GetAuditLogData ```ts type GetAuditLogData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### auditLogKey ```ts auditLogKey: AuditLogKeyWritable; ``` The audit log key. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/audit-logs/{auditLogKey}"; ``` --- ## Type Alias: GetAuditLogError ```ts type GetAuditLogError = GetAuditLogErrors[keyof GetAuditLogErrors]; ``` --- ## Type Alias: GetAuditLogErrors ```ts type GetAuditLogErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The audit log with the given key was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetAuditLogResponse ```ts type GetAuditLogResponse = GetAuditLogResponses[keyof GetAuditLogResponses]; ``` --- ## Type Alias: GetAuditLogResponses ```ts type GetAuditLogResponses = object; ``` ## Properties ### 200 ```ts 200: AuditLogResult; ``` The audit log entry is successfully returned. --- ## Type Alias: GetAuthenticationData ```ts type GetAuthenticationData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/authentication/me"; ``` --- ## Type Alias: GetAuthenticationError ```ts type GetAuthenticationError = GetAuthenticationErrors[keyof GetAuthenticationErrors]; ``` --- ## Type Alias: GetAuthenticationErrors ```ts type GetAuthenticationErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetAuthenticationResponse ```ts type GetAuthenticationResponse = GetAuthenticationResponses[keyof GetAuthenticationResponses]; ``` --- ## Type Alias: GetAuthenticationResponses ```ts type GetAuthenticationResponses = object; ``` ## Properties ### 200 ```ts 200: CamundaUserResult; ``` The current user is successfully returned. --- ## Type Alias: GetAuthorizationData ```ts type GetAuthorizationData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### authorizationKey ```ts authorizationKey: AuthorizationKeyWritable; ``` The key of the authorization to get. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/authorizations/{authorizationKey}"; ``` --- ## Type Alias: GetAuthorizationError ```ts type GetAuthorizationError = GetAuthorizationErrors[keyof GetAuthorizationErrors]; ``` --- ## Type Alias: GetAuthorizationErrors ```ts type GetAuthorizationErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The authorization with the given key was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetAuthorizationResponse ```ts type GetAuthorizationResponse = GetAuthorizationResponses[keyof GetAuthorizationResponses]; ``` --- ## Type Alias: GetAuthorizationResponses ```ts type GetAuthorizationResponses = object; ``` ## Properties ### 200 ```ts 200: AuthorizationResult; ``` The authorization was successfully returned. --- ## Type Alias: GetBatchOperationData ```ts type GetBatchOperationData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### batchOperationKey ```ts batchOperationKey: BatchOperationKey; ``` The key (or operate legacy ID) of the batch operation. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/batch-operations/{batchOperationKey}"; ``` --- ## Type Alias: GetBatchOperationError ```ts type GetBatchOperationError = GetBatchOperationErrors[keyof GetBatchOperationErrors]; ``` --- ## Type Alias: GetBatchOperationErrors ```ts type GetBatchOperationErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 404 ```ts 404: ProblemDetail; ``` The batch operation is not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetBatchOperationResponse ```ts type GetBatchOperationResponse = GetBatchOperationResponses[keyof GetBatchOperationResponses]; ``` --- ## Type Alias: GetBatchOperationResponses ```ts type GetBatchOperationResponses = object; ``` ## Properties ### 200 ```ts 200: BatchOperationResponse; ``` The batch operation was found. --- ## Type Alias: GetClusterExportingStatusData ```ts type GetClusterExportingStatusData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/cluster/v2/exporting"; ``` --- ## Type Alias: GetClusterExportingStatusError ```ts type GetClusterExportingStatusError = GetClusterExportingStatusErrors[keyof GetClusterExportingStatusErrors]; ``` --- ## Type Alias: GetClusterExportingStatusErrors ```ts type GetClusterExportingStatusErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: GetClusterExportingStatusResponse ```ts type GetClusterExportingStatusResponse = GetClusterExportingStatusResponses[keyof GetClusterExportingStatusResponses]; ``` --- ## Type Alias: GetClusterExportingStatusResponses ```ts type GetClusterExportingStatusResponses = object; ``` ## Properties ### 200 ```ts 200: ExportingStatusResponse; ``` The aggregated exporting status of the whole cluster. --- ## Type Alias: GetClusterRebalanceData ```ts type GetClusterRebalanceData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/cluster/v2/rebalance"; ``` --- ## Type Alias: GetClusterRebalanceError ```ts type GetClusterRebalanceError = GetClusterRebalanceErrors[keyof GetClusterRebalanceErrors]; ``` --- ## Type Alias: GetClusterRebalanceErrors ```ts type GetClusterRebalanceErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 502 ```ts 502: ProblemDetail; ``` The coordinator was reached, but its response was absent or unusable. --- ### 503 ```ts 503: ProblemDetail; ``` No coordinator is currently available or reachable. --- ### 504 ```ts 504: ProblemDetail; ``` The coordinator did not answer before the request timeout. --- ## Type Alias: GetClusterRebalanceResponse ```ts type GetClusterRebalanceResponse = GetClusterRebalanceResponses[keyof GetClusterRebalanceResponses]; ``` --- ## Type Alias: GetClusterRebalanceResponses ```ts type GetClusterRebalanceResponses = object; ``` ## Properties ### 200 ```ts 200: ClusterBalanceResponse; ``` The cluster's current leadership balance. --- ## Type Alias: GetClusterStatusData ```ts type GetClusterStatusData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/cluster/v2/status"; ``` --- ## Type Alias: GetClusterStatusError ```ts type GetClusterStatusError = GetClusterStatusErrors[keyof GetClusterStatusErrors]; ``` --- ## Type Alias: GetClusterStatusErrors ```ts type GetClusterStatusErrors = object; ``` ## Properties ### 503 ```ts 503: ClusterStatusResponse; ``` The cluster is DOWN because no physical tenant can process work. --- ## Type Alias: GetClusterStatusResponse ```ts type GetClusterStatusResponse = GetClusterStatusResponses[keyof GetClusterStatusResponses]; ``` --- ## Type Alias: GetClusterStatusResponses ```ts type GetClusterStatusResponses = object; ``` ## Properties ### 200 ```ts 200: ClusterStatusResponse; ``` The cluster can process work; the body reports whether it is fully healthy or degraded. --- ## Type Alias: GetClusterTopologyData ```ts type GetClusterTopologyData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/cluster/v2/topology"; ``` --- ## Type Alias: GetClusterTopologyError ```ts type GetClusterTopologyError = GetClusterTopologyErrors[keyof GetClusterTopologyErrors]; ``` --- ## Type Alias: GetClusterTopologyErrors ```ts type GetClusterTopologyErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetClusterTopologyResponse ```ts type GetClusterTopologyResponse = GetClusterTopologyResponses[keyof GetClusterTopologyResponses]; ``` --- ## Type Alias: GetClusterTopologyResponses ```ts type GetClusterTopologyResponses = object; ``` ## Properties ### 200 ```ts 200: ClusterTopologyResponse; ``` The topology of the whole cluster, aggregated over all physical tenants. --- ## Type Alias: GetClusterUpgradeStatusData ```ts type GetClusterUpgradeStatusData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/cluster/v2/status/upgrade"; ``` --- ## Type Alias: GetClusterUpgradeStatusResponse ```ts type GetClusterUpgradeStatusResponse = GetClusterUpgradeStatusResponses[keyof GetClusterUpgradeStatusResponses]; ``` --- ## Type Alias: GetClusterUpgradeStatusResponses ```ts type GetClusterUpgradeStatusResponses = object; ``` ## Properties ### 200 ```ts 200: ClusterUpgradeStatusResponse; ``` The cluster's upgrade-readiness status. --- ## Type Alias: GetDecisionDefinitionData ```ts type GetDecisionDefinitionData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### decisionDefinitionKey ```ts decisionDefinitionKey: DecisionDefinitionKeyWritable; ``` The assigned key of the decision definition, which acts as a unique identifier for this decision. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/decision-definitions/{decisionDefinitionKey}"; ``` --- ## Type Alias: GetDecisionDefinitionError ```ts type GetDecisionDefinitionError = GetDecisionDefinitionErrors[keyof GetDecisionDefinitionErrors]; ``` --- ## Type Alias: GetDecisionDefinitionErrors ```ts type GetDecisionDefinitionErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The decision definition with the given key was not found. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetDecisionDefinitionResponse ```ts type GetDecisionDefinitionResponse = GetDecisionDefinitionResponses[keyof GetDecisionDefinitionResponses]; ``` --- ## Type Alias: GetDecisionDefinitionResponses ```ts type GetDecisionDefinitionResponses = object; ``` ## Properties ### 200 ```ts 200: DecisionDefinitionResult; ``` The decision definition is successfully returned. --- ## Type Alias: GetDecisionDefinitionXmlData ```ts type GetDecisionDefinitionXmlData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### decisionDefinitionKey ```ts decisionDefinitionKey: DecisionDefinitionKeyWritable; ``` The assigned key of the decision definition, which acts as a unique identifier for this decision. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/decision-definitions/{decisionDefinitionKey}/xml"; ``` --- ## Type Alias: GetDecisionDefinitionXmlError ```ts type GetDecisionDefinitionXmlError = GetDecisionDefinitionXmlErrors[keyof GetDecisionDefinitionXmlErrors]; ``` --- ## Type Alias: GetDecisionDefinitionXmlErrors ```ts type GetDecisionDefinitionXmlErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The decision definition with the given key was not found. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetDecisionDefinitionXmlResponse ```ts type GetDecisionDefinitionXmlResponse = GetDecisionDefinitionXmlResponses[keyof GetDecisionDefinitionXmlResponses]; ``` --- ## Type Alias: GetDecisionDefinitionXmlResponses ```ts type GetDecisionDefinitionXmlResponses = object; ``` ## Properties ### 200 ```ts 200: string; ``` The XML of the decision definition is successfully returned. --- ## Type Alias: GetDecisionInstanceData ```ts type GetDecisionInstanceData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### decisionEvaluationInstanceKey ```ts decisionEvaluationInstanceKey: DecisionEvaluationInstanceKey; ``` The assigned key of the decision instance, which acts as a unique identifier for this decision instance. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/decision-instances/{decisionEvaluationInstanceKey}"; ``` --- ## Type Alias: GetDecisionInstanceError ```ts type GetDecisionInstanceError = GetDecisionInstanceErrors[keyof GetDecisionInstanceErrors]; ``` --- ## Type Alias: GetDecisionInstanceErrors ```ts type GetDecisionInstanceErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The decision instance with the given key was not found. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetDecisionInstanceResponse ```ts type GetDecisionInstanceResponse = GetDecisionInstanceResponses[keyof GetDecisionInstanceResponses]; ``` --- ## Type Alias: GetDecisionInstanceResponses ```ts type GetDecisionInstanceResponses = object; ``` ## Properties ### 200 ```ts 200: DecisionInstanceGetQueryResult; ``` The decision instance is successfully returned. --- ## Type Alias: GetDecisionRequirementsData ```ts type GetDecisionRequirementsData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### decisionRequirementsKey ```ts decisionRequirementsKey: DecisionRequirementsKeyWritable; ``` The assigned key of the decision requirements, which acts as a unique identifier for this decision requirements. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/decision-requirements/{decisionRequirementsKey}"; ``` --- ## Type Alias: GetDecisionRequirementsError ```ts type GetDecisionRequirementsError = GetDecisionRequirementsErrors[keyof GetDecisionRequirementsErrors]; ``` --- ## Type Alias: GetDecisionRequirementsErrors ```ts type GetDecisionRequirementsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The decision requirements with the given key was not found. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetDecisionRequirementsResponse ```ts type GetDecisionRequirementsResponse = GetDecisionRequirementsResponses[keyof GetDecisionRequirementsResponses]; ``` --- ## Type Alias: GetDecisionRequirementsResponses ```ts type GetDecisionRequirementsResponses = object; ``` ## Properties ### 200 ```ts 200: DecisionRequirementsResult; ``` The decision requirements is successfully returned. --- ## Type Alias: GetDecisionRequirementsXmlData ```ts type GetDecisionRequirementsXmlData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### decisionRequirementsKey ```ts decisionRequirementsKey: DecisionRequirementsKeyWritable; ``` The assigned key of the decision requirements, which acts as a unique identifier for this decision. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/decision-requirements/{decisionRequirementsKey}/xml"; ``` --- ## Type Alias: GetDecisionRequirementsXmlError ```ts type GetDecisionRequirementsXmlError = GetDecisionRequirementsXmlErrors[keyof GetDecisionRequirementsXmlErrors]; ``` --- ## Type Alias: GetDecisionRequirementsXmlErrors ```ts type GetDecisionRequirementsXmlErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The decision requirements with the given key was not found. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetDecisionRequirementsXmlResponse ```ts type GetDecisionRequirementsXmlResponse = GetDecisionRequirementsXmlResponses[keyof GetDecisionRequirementsXmlResponses]; ``` --- ## Type Alias: GetDecisionRequirementsXmlResponses ```ts type GetDecisionRequirementsXmlResponses = object; ``` ## Properties ### 200 ```ts 200: string; ``` The XML of the decision requirements is successfully returned. --- ## Type Alias: GetDocumentData ```ts type GetDocumentData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### documentId ```ts documentId: DocumentId; ``` The ID of the document to download. --- ### query? ```ts optional query?: object; ``` #### contentHash? ```ts optional contentHash?: string; ``` The hash of the document content that was computed by the document store during upload. The hash is part of the document reference that is returned when uploading a document. If the client fails to provide the correct hash, the request will be rejected. #### storeId? ```ts optional storeId?: string; ``` The ID of the document store to download the document from. --- ### url ```ts url: "/documents/{documentId}"; ``` --- ## Type Alias: GetDocumentError ```ts type GetDocumentError = GetDocumentErrors[keyof GetDocumentErrors]; ``` --- ## Type Alias: GetDocumentErrors ```ts type GetDocumentErrors = object; ``` ## Properties ### 404 ```ts 404: ProblemDetail; ``` The document with the given ID was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetDocumentResponse ```ts type GetDocumentResponse = GetDocumentResponses[keyof GetDocumentResponses]; ``` --- ## Type Alias: GetDocumentResponses ```ts type GetDocumentResponses = object; ``` ## Properties ### 200 ```ts 200: Blob | File; ``` The document was downloaded successfully. --- ## Type Alias: GetElementInstanceData ```ts type GetElementInstanceData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### elementInstanceKey ```ts elementInstanceKey: ElementInstanceKeyWritable; ``` The assigned key of the element instance, which acts as a unique identifier for this element instance. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/element-instances/{elementInstanceKey}"; ``` --- ## Type Alias: GetElementInstanceError ```ts type GetElementInstanceError = GetElementInstanceErrors[keyof GetElementInstanceErrors]; ``` --- ## Type Alias: GetElementInstanceErrors ```ts type GetElementInstanceErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The element instance with the given key was not found. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetElementInstanceResponse ```ts type GetElementInstanceResponse = GetElementInstanceResponses[keyof GetElementInstanceResponses]; ``` --- ## Type Alias: GetElementInstanceResponses ```ts type GetElementInstanceResponses = object; ``` ## Properties ### 200 ```ts 200: ElementInstanceResult; ``` The element instance is successfully returned. --- ## Type Alias: GetExportingStatusData ```ts type GetExportingStatusData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/exporting"; ``` --- ## Type Alias: GetExportingStatusError ```ts type GetExportingStatusError = GetExportingStatusErrors[keyof GetExportingStatusErrors]; ``` --- ## Type Alias: GetExportingStatusErrors ```ts type GetExportingStatusErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: GetExportingStatusResponse ```ts type GetExportingStatusResponse = GetExportingStatusResponses[keyof GetExportingStatusResponses]; ``` --- ## Type Alias: GetExportingStatusResponses ```ts type GetExportingStatusResponses = object; ``` ## Properties ### 200 ```ts 200: ExportingStatusResponse; ``` The current exporting status of the physical tenant. --- ## Type Alias: GetFormByKeyData ```ts type GetFormByKeyData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### formKey ```ts formKey: FormKeyWritable; ``` The form key. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/forms/{formKey}"; ``` --- ## Type Alias: GetFormByKeyError ```ts type GetFormByKeyError = GetFormByKeyErrors[keyof GetFormByKeyErrors]; ``` --- ## Type Alias: GetFormByKeyErrors ```ts type GetFormByKeyErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The form with the given key was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetFormByKeyResponse ```ts type GetFormByKeyResponse = GetFormByKeyResponses[keyof GetFormByKeyResponses]; ``` --- ## Type Alias: GetFormByKeyResponses ```ts type GetFormByKeyResponses = object; ``` ## Properties ### 200 ```ts 200: FormResult; ``` The form is successfully returned. --- ## Type Alias: GetGlobalClusterVariableData ```ts type GetGlobalClusterVariableData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### name ```ts name: ClusterVariableName; ``` The name of the cluster variable --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/cluster-variables/global/{name}"; ``` --- ## Type Alias: GetGlobalClusterVariableError ```ts type GetGlobalClusterVariableError = GetGlobalClusterVariableErrors[keyof GetGlobalClusterVariableErrors]; ``` --- ## Type Alias: GetGlobalClusterVariableErrors ```ts type GetGlobalClusterVariableErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Cluster variable not found --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetGlobalClusterVariableResponse ```ts type GetGlobalClusterVariableResponse = GetGlobalClusterVariableResponses[keyof GetGlobalClusterVariableResponses]; ``` --- ## Type Alias: GetGlobalClusterVariableResponses ```ts type GetGlobalClusterVariableResponses = object; ``` ## Properties ### 200 ```ts 200: ClusterVariableResult; ``` Cluster variable found --- ## Type Alias: GetGlobalJobStatisticsData ```ts type GetGlobalJobStatisticsData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query ```ts query: object; ``` #### from ```ts from: string; ``` Start of the time window to filter metrics. ISO 8601 date-time format. #### jobType? ```ts optional jobType?: string; ``` Optional job type to limit the aggregation to a single job type. #### to ```ts to: string; ``` End of the time window to filter metrics. ISO 8601 date-time format. --- ### url ```ts url: "/jobs/statistics/global"; ``` --- ## Type Alias: GetGlobalJobStatisticsError ```ts type GetGlobalJobStatisticsError = GetGlobalJobStatisticsErrors[keyof GetGlobalJobStatisticsErrors]; ``` --- ## Type Alias: GetGlobalJobStatisticsErrors ```ts type GetGlobalJobStatisticsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetGlobalJobStatisticsResponse ```ts type GetGlobalJobStatisticsResponse = GetGlobalJobStatisticsResponses[keyof GetGlobalJobStatisticsResponses]; ``` --- ## Type Alias: GetGlobalJobStatisticsResponses ```ts type GetGlobalJobStatisticsResponses = object; ``` ## Properties ### 200 ```ts 200: GlobalJobStatisticsQueryResult; ``` Global job metrics --- ## Type Alias: GetGlobalTaskListenerData ```ts type GetGlobalTaskListenerData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### id ```ts id: GlobalListenerId; ``` The id of the global user task listener. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/global-task-listeners/{id}"; ``` --- ## Type Alias: GetGlobalTaskListenerError ```ts type GetGlobalTaskListenerError = GetGlobalTaskListenerErrors[keyof GetGlobalTaskListenerErrors]; ``` --- ## Type Alias: GetGlobalTaskListenerErrors ```ts type GetGlobalTaskListenerErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The global user task listener with the given id was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetGlobalTaskListenerResponse ```ts type GetGlobalTaskListenerResponse = GetGlobalTaskListenerResponses[keyof GetGlobalTaskListenerResponses]; ``` --- ## Type Alias: GetGlobalTaskListenerResponses ```ts type GetGlobalTaskListenerResponses = object; ``` ## Properties ### 200 ```ts 200: GlobalTaskListenerResult; ``` The global user task listener is successfully returned. --- ## Type Alias: GetGroupData ```ts type GetGroupData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### groupId ```ts groupId: GroupId; ``` The group ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/groups/{groupId}"; ``` --- ## Type Alias: GetGroupError ```ts type GetGroupError = GetGroupErrors[keyof GetGroupErrors]; ``` --- ## Type Alias: GetGroupErrors ```ts type GetGroupErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The group with the given ID was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetGroupResponse ```ts type GetGroupResponse = GetGroupResponses[keyof GetGroupResponses]; ``` --- ## Type Alias: GetGroupResponses ```ts type GetGroupResponses = object; ``` ## Properties ### 200 ```ts 200: GroupResult; ``` The group is successfully returned. --- ## Type Alias: GetHistoryBackupAsClusterAdminData ```ts type GetHistoryBackupAsClusterAdminData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### backupId ```ts backupId: BackupId; ``` The id of the backup. --- ### query? ```ts optional query?: object; ``` #### physicalTenantId? ```ts optional physicalTenantId?: string; ``` The physical tenant to apply the change to. When omitted, or when passed with an empty value, the change is applied to every physical tenant of the cluster. --- ### url ```ts url: "/cluster/v2/backups/history/{backupId}"; ``` --- ## Type Alias: GetHistoryBackupAsClusterAdminError ```ts type GetHistoryBackupAsClusterAdminError = GetHistoryBackupAsClusterAdminErrors[keyof GetHistoryBackupAsClusterAdminErrors]; ``` --- ## Type Alias: GetHistoryBackupAsClusterAdminErrors ```ts type GetHistoryBackupAsClusterAdminErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` The cluster's secondary storage is neither Elasticsearch nor OpenSearch and therefore cannot serve history backups, or a targeted physical tenant's snapshot repository is absent from the store — configured under a name the store does not have, or not configured at all. Both are deployment faults the caller cannot correct by changing its request; narrow the request with `physicalTenantId` to work with the tenants whose repository is usable. Unlike the per-physical-tenant backup endpoints, the cluster-admin surface performs no fine-grained authorization, so a missing `BACKUP` permission is never the reason. --- ### 404 ```ts 404: ProblemDetail; ``` The requested `physicalTenantId` does not exist in this cluster, or every targeted physical tenant was read and none of them holds a backup with the given id. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: GetHistoryBackupAsClusterAdminResponse ```ts type GetHistoryBackupAsClusterAdminResponse = GetHistoryBackupAsClusterAdminResponses[keyof GetHistoryBackupAsClusterAdminResponses]; ``` --- ## Type Alias: GetHistoryBackupAsClusterAdminResponses ```ts type GetHistoryBackupAsClusterAdminResponses = object; ``` ## Properties ### 200 ```ts 200: ClusterHistoryBackupInfo; ``` Every targeted physical tenant was read. Each one reports either the backup or `NOT_FOUND`; at least one holds the backup. --- ## Type Alias: GetHistoryBackupData ```ts type GetHistoryBackupData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### backupId ```ts backupId: BackupId; ``` The id of the backup. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/backups/history/{backupId}"; ``` --- ## Type Alias: GetHistoryBackupError ```ts type GetHistoryBackupError = GetHistoryBackupErrors[keyof GetHistoryBackupErrors]; ``` --- ## Type Alias: GetHistoryBackupErrors ```ts type GetHistoryBackupErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` The request is forbidden for one of three reasons: the authenticated caller lacks the required `BACKUP` permission; the cluster's secondary storage is neither Elasticsearch nor OpenSearch and therefore cannot serve history backups; or the physical tenant's snapshot repository is absent from the store — configured under a name the store does not have, or not configured at all. The problem detail says which applies. The latter two are deployment faults the caller cannot correct by changing its request. --- ### 404 ```ts 404: ProblemDetail; ``` A backup with the given id does not exist. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: GetHistoryBackupResponse ```ts type GetHistoryBackupResponse = GetHistoryBackupResponses[keyof GetHistoryBackupResponses]; ``` --- ## Type Alias: GetHistoryBackupResponses ```ts type GetHistoryBackupResponses = object; ``` ## Properties ### 200 ```ts 200: HistoryBackupInfo; ``` The history backup. --- ## Type Alias: GetIncidentData ```ts type GetIncidentData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### incidentKey ```ts incidentKey: IncidentKeyWritable; ``` The assigned key of the incident, which acts as a unique identifier for this incident. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/incidents/{incidentKey}"; ``` --- ## Type Alias: GetIncidentError ```ts type GetIncidentError = GetIncidentErrors[keyof GetIncidentErrors]; ``` --- ## Type Alias: GetIncidentErrors ```ts type GetIncidentErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The incident with the given key was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetIncidentResponse ```ts type GetIncidentResponse = GetIncidentResponses[keyof GetIncidentResponses]; ``` --- ## Type Alias: GetIncidentResponses ```ts type GetIncidentResponses = object; ``` ## Properties ### 200 ```ts 200: IncidentResult; ``` The incident is successfully returned. --- ## Type Alias: GetJobErrorStatisticsData ```ts type GetJobErrorStatisticsData = object; ``` ## Properties ### body ```ts body: JobErrorStatisticsQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/jobs/statistics/errors"; ``` --- ## Type Alias: GetJobErrorStatisticsError ```ts type GetJobErrorStatisticsError = GetJobErrorStatisticsErrors[keyof GetJobErrorStatisticsErrors]; ``` --- ## Type Alias: GetJobErrorStatisticsErrors ```ts type GetJobErrorStatisticsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetJobErrorStatisticsResponse ```ts type GetJobErrorStatisticsResponse = GetJobErrorStatisticsResponses[keyof GetJobErrorStatisticsResponses]; ``` --- ## Type Alias: GetJobErrorStatisticsResponses ```ts type GetJobErrorStatisticsResponses = object; ``` ## Properties ### 200 ```ts 200: JobErrorStatisticsQueryResult; ``` The job error statistics result. --- ## Type Alias: GetJobTimeSeriesStatisticsData ```ts type GetJobTimeSeriesStatisticsData = object; ``` ## Properties ### body ```ts body: JobTimeSeriesStatisticsQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/jobs/statistics/time-series"; ``` --- ## Type Alias: GetJobTimeSeriesStatisticsError ```ts type GetJobTimeSeriesStatisticsError = GetJobTimeSeriesStatisticsErrors[keyof GetJobTimeSeriesStatisticsErrors]; ``` --- ## Type Alias: GetJobTimeSeriesStatisticsErrors ```ts type GetJobTimeSeriesStatisticsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetJobTimeSeriesStatisticsResponse ```ts type GetJobTimeSeriesStatisticsResponse = GetJobTimeSeriesStatisticsResponses[keyof GetJobTimeSeriesStatisticsResponses]; ``` --- ## Type Alias: GetJobTimeSeriesStatisticsResponses ```ts type GetJobTimeSeriesStatisticsResponses = object; ``` ## Properties ### 200 ```ts 200: JobTimeSeriesStatisticsQueryResult; ``` The job time-series statistics result. --- ## Type Alias: GetJobTypeStatisticsData ```ts type GetJobTypeStatisticsData = object; ``` ## Properties ### body ```ts body: JobTypeStatisticsQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/jobs/statistics/by-types"; ``` --- ## Type Alias: GetJobTypeStatisticsError ```ts type GetJobTypeStatisticsError = GetJobTypeStatisticsErrors[keyof GetJobTypeStatisticsErrors]; ``` --- ## Type Alias: GetJobTypeStatisticsErrors ```ts type GetJobTypeStatisticsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetJobTypeStatisticsResponse ```ts type GetJobTypeStatisticsResponse = GetJobTypeStatisticsResponses[keyof GetJobTypeStatisticsResponses]; ``` --- ## Type Alias: GetJobTypeStatisticsResponses ```ts type GetJobTypeStatisticsResponses = object; ``` ## Properties ### 200 ```ts 200: JobTypeStatisticsQueryResult; ``` The job type statistics result. --- ## Type Alias: GetJobWorkerStatisticsData ```ts type GetJobWorkerStatisticsData = object; ``` ## Properties ### body ```ts body: JobWorkerStatisticsQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/jobs/statistics/by-workers"; ``` --- ## Type Alias: GetJobWorkerStatisticsError ```ts type GetJobWorkerStatisticsError = GetJobWorkerStatisticsErrors[keyof GetJobWorkerStatisticsErrors]; ``` --- ## Type Alias: GetJobWorkerStatisticsErrors ```ts type GetJobWorkerStatisticsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetJobWorkerStatisticsResponse ```ts type GetJobWorkerStatisticsResponse = GetJobWorkerStatisticsResponses[keyof GetJobWorkerStatisticsResponses]; ``` --- ## Type Alias: GetJobWorkerStatisticsResponses ```ts type GetJobWorkerStatisticsResponses = object; ``` ## Properties ### 200 ```ts 200: JobWorkerStatisticsQueryResult; ``` The job worker statistics result. --- ## Type Alias: GetLicenseData ```ts type GetLicenseData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/license"; ``` --- ## Type Alias: GetLicenseError ```ts type GetLicenseError = GetLicenseErrors[keyof GetLicenseErrors]; ``` --- ## Type Alias: GetLicenseErrors ```ts type GetLicenseErrors = object; ``` ## Properties ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetLicenseResponse ```ts type GetLicenseResponse = GetLicenseResponses[keyof GetLicenseResponses]; ``` --- ## Type Alias: GetLicenseResponses ```ts type GetLicenseResponses = object; ``` ## Properties ### 200 ```ts 200: LicenseResponse; ``` Obtains the current status of the Camunda license. --- ## Type Alias: GetMappingRuleData ```ts type GetMappingRuleData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### mappingRuleId ```ts mappingRuleId: MappingRuleId; ``` The ID of the mapping rule to get. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/mapping-rules/{mappingRuleId}"; ``` --- ## Type Alias: GetMappingRuleError ```ts type GetMappingRuleError = GetMappingRuleErrors[keyof GetMappingRuleErrors]; ``` --- ## Type Alias: GetMappingRuleErrors ```ts type GetMappingRuleErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 404 ```ts 404: ProblemDetail; ``` The mapping rule with the mappingRuleId was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetMappingRuleResponse ```ts type GetMappingRuleResponse = GetMappingRuleResponses[keyof GetMappingRuleResponses]; ``` --- ## Type Alias: GetMappingRuleResponses ```ts type GetMappingRuleResponses = object; ``` ## Properties ### 200 ```ts 200: MappingRuleResult; ``` The mapping rule was returned successfully. --- ## Type Alias: GetProcessDefinitionData ```ts type GetProcessDefinitionData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKeyWritable; ``` The assigned key of the process definition, which acts as a unique identifier for this process definition. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-definitions/{processDefinitionKey}"; ``` --- ## Type Alias: GetProcessDefinitionError ```ts type GetProcessDefinitionError = GetProcessDefinitionErrors[keyof GetProcessDefinitionErrors]; ``` --- ## Type Alias: GetProcessDefinitionErrors ```ts type GetProcessDefinitionErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The process definition with the given key was not found. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetProcessDefinitionInstanceStatisticsData ```ts type GetProcessDefinitionInstanceStatisticsData = object; ``` ## Properties ### body? ```ts optional body?: ProcessDefinitionInstanceStatisticsQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-definitions/statistics/process-instances"; ``` --- ## Type Alias: GetProcessDefinitionInstanceStatisticsError ```ts type GetProcessDefinitionInstanceStatisticsError = GetProcessDefinitionInstanceStatisticsErrors[keyof GetProcessDefinitionInstanceStatisticsErrors]; ``` --- ## Type Alias: GetProcessDefinitionInstanceStatisticsErrors ```ts type GetProcessDefinitionInstanceStatisticsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetProcessDefinitionInstanceStatisticsResponse ```ts type GetProcessDefinitionInstanceStatisticsResponse = GetProcessDefinitionInstanceStatisticsResponses[keyof GetProcessDefinitionInstanceStatisticsResponses]; ``` --- ## Type Alias: GetProcessDefinitionInstanceStatisticsResponses ```ts type GetProcessDefinitionInstanceStatisticsResponses = object; ``` ## Properties ### 200 ```ts 200: ProcessDefinitionInstanceStatisticsQueryResult; ``` The process definition instance statistic result. --- ## Type Alias: GetProcessDefinitionInstanceVersionStatisticsData ```ts type GetProcessDefinitionInstanceVersionStatisticsData = object; ``` ## Properties ### body ```ts body: ProcessDefinitionInstanceVersionStatisticsQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-definitions/statistics/process-instances-by-version"; ``` --- ## Type Alias: GetProcessDefinitionInstanceVersionStatisticsError ```ts type GetProcessDefinitionInstanceVersionStatisticsError = GetProcessDefinitionInstanceVersionStatisticsErrors[keyof GetProcessDefinitionInstanceVersionStatisticsErrors]; ``` --- ## Type Alias: GetProcessDefinitionInstanceVersionStatisticsErrors ```ts type GetProcessDefinitionInstanceVersionStatisticsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetProcessDefinitionInstanceVersionStatisticsResponse ```ts type GetProcessDefinitionInstanceVersionStatisticsResponse = GetProcessDefinitionInstanceVersionStatisticsResponses[keyof GetProcessDefinitionInstanceVersionStatisticsResponses]; ``` --- ## Type Alias: GetProcessDefinitionInstanceVersionStatisticsResponses ```ts type GetProcessDefinitionInstanceVersionStatisticsResponses = object; ``` ## Properties ### 200 ```ts 200: ProcessDefinitionInstanceVersionStatisticsQueryResult; ``` The process definition instance version statistic result. --- ## Type Alias: GetProcessDefinitionMessageSubscriptionStatisticsData ```ts type GetProcessDefinitionMessageSubscriptionStatisticsData = object; ``` ## Properties ### body? ```ts optional body?: ProcessDefinitionMessageSubscriptionStatisticsQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-definitions/statistics/message-subscriptions"; ``` --- ## Type Alias: GetProcessDefinitionMessageSubscriptionStatisticsError ```ts type GetProcessDefinitionMessageSubscriptionStatisticsError = GetProcessDefinitionMessageSubscriptionStatisticsErrors[keyof GetProcessDefinitionMessageSubscriptionStatisticsErrors]; ``` --- ## Type Alias: GetProcessDefinitionMessageSubscriptionStatisticsErrors ```ts type GetProcessDefinitionMessageSubscriptionStatisticsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetProcessDefinitionMessageSubscriptionStatisticsResponse ```ts type GetProcessDefinitionMessageSubscriptionStatisticsResponse = GetProcessDefinitionMessageSubscriptionStatisticsResponses[keyof GetProcessDefinitionMessageSubscriptionStatisticsResponses]; ``` --- ## Type Alias: GetProcessDefinitionMessageSubscriptionStatisticsResponses ```ts type GetProcessDefinitionMessageSubscriptionStatisticsResponses = object; ``` ## Properties ### 200 ```ts 200: ProcessDefinitionMessageSubscriptionStatisticsQueryResult; ``` The process definition message subscription statistics result. --- ## Type Alias: GetProcessDefinitionResponse ```ts type GetProcessDefinitionResponse = GetProcessDefinitionResponses[keyof GetProcessDefinitionResponses]; ``` --- ## Type Alias: GetProcessDefinitionResponses ```ts type GetProcessDefinitionResponses = object; ``` ## Properties ### 200 ```ts 200: ProcessDefinitionResult; ``` The process definition is successfully returned. --- ## Type Alias: GetProcessDefinitionStatisticsData ```ts type GetProcessDefinitionStatisticsData = object; ``` ## Properties ### body? ```ts optional body?: ProcessDefinitionElementStatisticsQuery; ``` --- ### path ```ts path: object; ``` #### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKeyWritable; ``` The assigned key of the process definition, which acts as a unique identifier for this process definition. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-definitions/{processDefinitionKey}/statistics/element-instances"; ``` --- ## Type Alias: GetProcessDefinitionStatisticsError ```ts type GetProcessDefinitionStatisticsError = GetProcessDefinitionStatisticsErrors[keyof GetProcessDefinitionStatisticsErrors]; ``` --- ## Type Alias: GetProcessDefinitionStatisticsErrors ```ts type GetProcessDefinitionStatisticsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetProcessDefinitionStatisticsResponse ```ts type GetProcessDefinitionStatisticsResponse = GetProcessDefinitionStatisticsResponses[keyof GetProcessDefinitionStatisticsResponses]; ``` --- ## Type Alias: GetProcessDefinitionStatisticsResponses ```ts type GetProcessDefinitionStatisticsResponses = object; ``` ## Properties ### 200 ```ts 200: ProcessDefinitionElementStatisticsQueryResult; ``` The process definition statistics result. --- ## Type Alias: GetProcessDefinitionXmlData ```ts type GetProcessDefinitionXmlData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKeyWritable; ``` The assigned key of the process definition, which acts as a unique identifier for this process definition. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-definitions/{processDefinitionKey}/xml"; ``` --- ## Type Alias: GetProcessDefinitionXmlError ```ts type GetProcessDefinitionXmlError = GetProcessDefinitionXmlErrors[keyof GetProcessDefinitionXmlErrors]; ``` --- ## Type Alias: GetProcessDefinitionXmlErrors ```ts type GetProcessDefinitionXmlErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The process definition with the given key was not found. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetProcessDefinitionXmlResponse ```ts type GetProcessDefinitionXmlResponse = GetProcessDefinitionXmlResponses[keyof GetProcessDefinitionXmlResponses]; ``` --- ## Type Alias: GetProcessDefinitionXmlResponses ```ts type GetProcessDefinitionXmlResponses = object; ``` ## Properties ### 200 ```ts 200: string; ``` The XML of the process definition is successfully returned. --- ### 204 ```ts 204: string; ``` The process definition was found but does not have XML. --- ## Type Alias: GetProcessInstanceCallHierarchyData ```ts type GetProcessInstanceCallHierarchyData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### processInstanceKey ```ts processInstanceKey: ProcessInstanceKeyWritable; ``` The key of the process instance to fetch the hierarchy for. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/{processInstanceKey}/call-hierarchy"; ``` --- ## Type Alias: GetProcessInstanceCallHierarchyError ```ts type GetProcessInstanceCallHierarchyError = GetProcessInstanceCallHierarchyErrors[keyof GetProcessInstanceCallHierarchyErrors]; ``` --- ## Type Alias: GetProcessInstanceCallHierarchyErrors ```ts type GetProcessInstanceCallHierarchyErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The process instance is not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetProcessInstanceCallHierarchyResponse ```ts type GetProcessInstanceCallHierarchyResponse = GetProcessInstanceCallHierarchyResponses[keyof GetProcessInstanceCallHierarchyResponses]; ``` --- ## Type Alias: GetProcessInstanceCallHierarchyResponses ```ts type GetProcessInstanceCallHierarchyResponses = object; ``` ## Properties ### 200 ```ts 200: ProcessInstanceCallHierarchyEntry[]; ``` The call hierarchy is successfully returned. --- ## Type Alias: GetProcessInstanceData ```ts type GetProcessInstanceData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### processInstanceKey ```ts processInstanceKey: ProcessInstanceKeyWritable; ``` The process instance key. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/{processInstanceKey}"; ``` --- ## Type Alias: GetProcessInstanceError ```ts type GetProcessInstanceError = GetProcessInstanceErrors[keyof GetProcessInstanceErrors]; ``` --- ## Type Alias: GetProcessInstanceErrors ```ts type GetProcessInstanceErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The process instance with the given key was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetProcessInstanceResponse ```ts type GetProcessInstanceResponse = GetProcessInstanceResponses[keyof GetProcessInstanceResponses]; ``` --- ## Type Alias: GetProcessInstanceResponses ```ts type GetProcessInstanceResponses = object; ``` ## Properties ### 200 ```ts 200: ProcessInstanceResult; ``` The process instance is successfully returned. --- ## Type Alias: GetProcessInstanceSequenceFlowsData ```ts type GetProcessInstanceSequenceFlowsData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### processInstanceKey ```ts processInstanceKey: ProcessInstanceKeyWritable; ``` The assigned key of the process instance, which acts as a unique identifier for this process instance. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/{processInstanceKey}/sequence-flows"; ``` --- ## Type Alias: GetProcessInstanceSequenceFlowsError ```ts type GetProcessInstanceSequenceFlowsError = GetProcessInstanceSequenceFlowsErrors[keyof GetProcessInstanceSequenceFlowsErrors]; ``` --- ## Type Alias: GetProcessInstanceSequenceFlowsErrors ```ts type GetProcessInstanceSequenceFlowsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetProcessInstanceSequenceFlowsResponse ```ts type GetProcessInstanceSequenceFlowsResponse = GetProcessInstanceSequenceFlowsResponses[keyof GetProcessInstanceSequenceFlowsResponses]; ``` --- ## Type Alias: GetProcessInstanceSequenceFlowsResponses ```ts type GetProcessInstanceSequenceFlowsResponses = object; ``` ## Properties ### 200 ```ts 200: ProcessInstanceSequenceFlowsQueryResult; ``` The process instance sequence flows result. --- ## Type Alias: GetProcessInstanceStatisticsByDefinitionData ```ts type GetProcessInstanceStatisticsByDefinitionData = object; ``` ## Properties ### body ```ts body: IncidentProcessInstanceStatisticsByDefinitionQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/incidents/statistics/process-instances-by-definition"; ``` --- ## Type Alias: GetProcessInstanceStatisticsByDefinitionError ```ts type GetProcessInstanceStatisticsByDefinitionError = GetProcessInstanceStatisticsByDefinitionErrors[keyof GetProcessInstanceStatisticsByDefinitionErrors]; ``` --- ## Type Alias: GetProcessInstanceStatisticsByDefinitionErrors ```ts type GetProcessInstanceStatisticsByDefinitionErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetProcessInstanceStatisticsByDefinitionResponse ```ts type GetProcessInstanceStatisticsByDefinitionResponse = GetProcessInstanceStatisticsByDefinitionResponses[keyof GetProcessInstanceStatisticsByDefinitionResponses]; ``` --- ## Type Alias: GetProcessInstanceStatisticsByDefinitionResponses ```ts type GetProcessInstanceStatisticsByDefinitionResponses = object; ``` ## Properties ### 200 ```ts 200: IncidentProcessInstanceStatisticsByDefinitionQueryResult; ``` The process instance incident statistics grouped by process definition are successfully returned. --- ## Type Alias: GetProcessInstanceStatisticsByErrorData ```ts type GetProcessInstanceStatisticsByErrorData = object; ``` ## Properties ### body? ```ts optional body?: IncidentProcessInstanceStatisticsByErrorQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/incidents/statistics/process-instances-by-error"; ``` --- ## Type Alias: GetProcessInstanceStatisticsByErrorError ```ts type GetProcessInstanceStatisticsByErrorError = GetProcessInstanceStatisticsByErrorErrors[keyof GetProcessInstanceStatisticsByErrorErrors]; ``` --- ## Type Alias: GetProcessInstanceStatisticsByErrorErrors ```ts type GetProcessInstanceStatisticsByErrorErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetProcessInstanceStatisticsByErrorResponse ```ts type GetProcessInstanceStatisticsByErrorResponse = GetProcessInstanceStatisticsByErrorResponses[keyof GetProcessInstanceStatisticsByErrorResponses]; ``` --- ## Type Alias: GetProcessInstanceStatisticsByErrorResponses ```ts type GetProcessInstanceStatisticsByErrorResponses = object; ``` ## Properties ### 200 ```ts 200: IncidentProcessInstanceStatisticsByErrorQueryResult; ``` The statistics about process instances with incident, grouped by error hash code are successfully returned. --- ## Type Alias: GetProcessInstanceStatisticsData ```ts type GetProcessInstanceStatisticsData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### processInstanceKey ```ts processInstanceKey: ProcessInstanceKeyWritable; ``` The assigned key of the process instance, which acts as a unique identifier for this process instance. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/{processInstanceKey}/statistics/element-instances"; ``` --- ## Type Alias: GetProcessInstanceStatisticsError ```ts type GetProcessInstanceStatisticsError = GetProcessInstanceStatisticsErrors[keyof GetProcessInstanceStatisticsErrors]; ``` --- ## Type Alias: GetProcessInstanceStatisticsErrors ```ts type GetProcessInstanceStatisticsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetProcessInstanceStatisticsResponse ```ts type GetProcessInstanceStatisticsResponse = GetProcessInstanceStatisticsResponses[keyof GetProcessInstanceStatisticsResponses]; ``` --- ## Type Alias: GetProcessInstanceStatisticsResponses ```ts type GetProcessInstanceStatisticsResponses = object; ``` ## Properties ### 200 ```ts 200: ProcessInstanceElementStatisticsQueryResult; ``` The process instance statistics result. --- ## Type Alias: GetProcessInstanceWaitStateStatisticsData ```ts type GetProcessInstanceWaitStateStatisticsData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### processInstanceKey ```ts processInstanceKey: ProcessInstanceKeyWritable; ``` The assigned key of the process instance, which acts as a unique identifier for this process instance. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/{processInstanceKey}/statistics/wait-states"; ``` --- ## Type Alias: GetProcessInstanceWaitStateStatisticsError ```ts type GetProcessInstanceWaitStateStatisticsError = GetProcessInstanceWaitStateStatisticsErrors[keyof GetProcessInstanceWaitStateStatisticsErrors]; ``` --- ## Type Alias: GetProcessInstanceWaitStateStatisticsErrors ```ts type GetProcessInstanceWaitStateStatisticsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetProcessInstanceWaitStateStatisticsResponse ```ts type GetProcessInstanceWaitStateStatisticsResponse = GetProcessInstanceWaitStateStatisticsResponses[keyof GetProcessInstanceWaitStateStatisticsResponses]; ``` --- ## Type Alias: GetProcessInstanceWaitStateStatisticsResponses ```ts type GetProcessInstanceWaitStateStatisticsResponses = object; ``` ## Properties ### 200 ```ts 200: ProcessInstanceWaitStateStatisticsQueryResult; ``` The process instance wait state statistics result. --- ## Type Alias: GetResourceContentBinaryData ```ts type GetResourceContentBinaryData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### resourceKey ```ts resourceKey: ResourceKeyWritable; ``` The unique key identifying the resource. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/resources/{resourceKey}/content/binary"; ``` --- ## Type Alias: GetResourceContentBinaryError ```ts type GetResourceContentBinaryError = GetResourceContentBinaryErrors[keyof GetResourceContentBinaryErrors]; ``` --- ## Type Alias: GetResourceContentBinaryErrors ```ts type GetResourceContentBinaryErrors = object; ``` ## Properties ### 404 ```ts 404: ProblemDetail; ``` A resource with the given key was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetResourceContentBinaryResponse ```ts type GetResourceContentBinaryResponse = GetResourceContentBinaryResponses[keyof GetResourceContentBinaryResponses]; ``` --- ## Type Alias: GetResourceContentBinaryResponses ```ts type GetResourceContentBinaryResponses = object; ``` ## Properties ### 200 ```ts 200: Blob | File; ``` The resource content is successfully returned. --- ## Type Alias: GetResourceContentData ```ts type GetResourceContentData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### resourceKey ```ts resourceKey: ResourceKeyWritable; ``` The unique key identifying the RPA resource. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/resources/{resourceKey}/content"; ``` --- ## Type Alias: GetResourceContentError ```ts type GetResourceContentError = GetResourceContentErrors[keyof GetResourceContentErrors]; ``` --- ## Type Alias: GetResourceContentErrors ```ts type GetResourceContentErrors = object; ``` ## Properties ### 404 ```ts 404: ProblemDetail; ``` A resource with the given key was not found. --- ### 406 ```ts 406: ProblemDetail; ``` The resource exists but is not an RPA resource. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetResourceContentResponse ```ts type GetResourceContentResponse = GetResourceContentResponses[keyof GetResourceContentResponses]; ``` --- ## Type Alias: GetResourceContentResponses ```ts type GetResourceContentResponses = object; ``` ## Properties ### 200 ```ts 200: object; ``` The resource content is successfully returned. #### Index Signature ```ts [key: string]: unknown ``` --- ## Type Alias: GetResourceData ```ts type GetResourceData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### resourceKey ```ts resourceKey: ResourceKeyWritable; ``` The unique key identifying the resource. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/resources/{resourceKey}"; ``` --- ## Type Alias: GetResourceError ```ts type GetResourceError = GetResourceErrors[keyof GetResourceErrors]; ``` --- ## Type Alias: GetResourceErrors ```ts type GetResourceErrors = object; ``` ## Properties ### 404 ```ts 404: ProblemDetail; ``` A resource with the given key was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetResourceResponse ```ts type GetResourceResponse = GetResourceResponses[keyof GetResourceResponses]; ``` --- ## Type Alias: GetResourceResponses ```ts type GetResourceResponses = object; ``` ## Properties ### 200 ```ts 200: ResourceResult; ``` The resource is successfully returned. --- ## Type Alias: GetRestoreStatusData ```ts type GetRestoreStatusData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/restore"; ``` --- ## Type Alias: GetRestoreStatusError ```ts type GetRestoreStatusError = GetRestoreStatusErrors[keyof GetRestoreStatusErrors]; ``` --- ## Type Alias: GetRestoreStatusErrors ```ts type GetRestoreStatusErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: unknown; ``` No restore is currently in progress. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetRestoreStatusResponse ```ts type GetRestoreStatusResponse = GetRestoreStatusResponses[keyof GetRestoreStatusResponses]; ``` --- ## Type Alias: GetRestoreStatusResponses ```ts type GetRestoreStatusResponses = object; ``` ## Properties ### 200 ```ts 200: RestoreStatusResponse; ``` The status of the restore that is currently in progress. --- ## Type Alias: GetRoleData ```ts type GetRoleData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### roleId ```ts roleId: RoleId; ``` The role ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/roles/{roleId}"; ``` --- ## Type Alias: GetRoleError ```ts type GetRoleError = GetRoleErrors[keyof GetRoleErrors]; ``` --- ## Type Alias: GetRoleErrors ```ts type GetRoleErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The role with the given ID was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetRoleResponse ```ts type GetRoleResponse = GetRoleResponses[keyof GetRoleResponses]; ``` --- ## Type Alias: GetRoleResponses ```ts type GetRoleResponses = object; ``` ## Properties ### 200 ```ts 200: RoleResult; ``` The role is successfully returned. --- ## Type Alias: GetRuntimeBackupAsClusterAdminData ```ts type GetRuntimeBackupAsClusterAdminData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### backupId ```ts backupId: BackupId; ``` The id of the backup. --- ### query? ```ts optional query?: object; ``` #### physicalTenantId? ```ts optional physicalTenantId?: string; ``` The physical tenant to apply the change to. When omitted, or when passed with an empty value, the change is applied to every physical tenant of the cluster. --- ### url ```ts url: "/cluster/v2/backups/runtime/{backupId}"; ``` --- ## Type Alias: GetRuntimeBackupAsClusterAdminError ```ts type GetRuntimeBackupAsClusterAdminError = GetRuntimeBackupAsClusterAdminErrors[keyof GetRuntimeBackupAsClusterAdminErrors]; ``` --- ## Type Alias: GetRuntimeBackupAsClusterAdminErrors ```ts type GetRuntimeBackupAsClusterAdminErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 404 ```ts 404: ProblemDetail; ``` The requested `physicalTenantId` does not exist in this cluster, or every targeted physical tenant was read and none of them holds a backup with the given id. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: GetRuntimeBackupAsClusterAdminResponse ```ts type GetRuntimeBackupAsClusterAdminResponse = GetRuntimeBackupAsClusterAdminResponses[keyof GetRuntimeBackupAsClusterAdminResponses]; ``` --- ## Type Alias: GetRuntimeBackupAsClusterAdminResponses ```ts type GetRuntimeBackupAsClusterAdminResponses = object; ``` ## Properties ### 200 ```ts 200: ClusterRuntimeBackupInfo; ``` Every targeted physical tenant was read, and at least one holds the backup. Each tenant reports either the backup or `DOES_NOT_EXIST`. --- ## Type Alias: GetRuntimeBackupData ```ts type GetRuntimeBackupData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### backupId ```ts backupId: BackupId; ``` The id of the backup. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/backups/runtime/{backupId}"; ``` --- ## Type Alias: GetRuntimeBackupError ```ts type GetRuntimeBackupError = GetRuntimeBackupErrors[keyof GetRuntimeBackupErrors]; ``` --- ## Type Alias: GetRuntimeBackupErrors ```ts type GetRuntimeBackupErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` A backup with the given id does not exist. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: GetRuntimeBackupResponse ```ts type GetRuntimeBackupResponse = GetRuntimeBackupResponses[keyof GetRuntimeBackupResponses]; ``` --- ## Type Alias: GetRuntimeBackupResponses ```ts type GetRuntimeBackupResponses = object; ``` ## Properties ### 200 ```ts 200: BackupInfo; ``` The runtime backup. --- ## Type Alias: GetRuntimeBackupStateAsClusterAdminData ```ts type GetRuntimeBackupStateAsClusterAdminData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: object; ``` #### physicalTenantId? ```ts optional physicalTenantId?: string; ``` The physical tenant to apply the change to. When omitted, or when passed with an empty value, the change is applied to every physical tenant of the cluster. --- ### url ```ts url: "/cluster/v2/backups/runtime/state"; ``` --- ## Type Alias: GetRuntimeBackupStateAsClusterAdminError ```ts type GetRuntimeBackupStateAsClusterAdminError = GetRuntimeBackupStateAsClusterAdminErrors[keyof GetRuntimeBackupStateAsClusterAdminErrors]; ``` --- ## Type Alias: GetRuntimeBackupStateAsClusterAdminErrors ```ts type GetRuntimeBackupStateAsClusterAdminErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 404 ```ts 404: ProblemDetail; ``` The requested `physicalTenantId` does not exist in this cluster. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: GetRuntimeBackupStateAsClusterAdminResponse ```ts type GetRuntimeBackupStateAsClusterAdminResponse = GetRuntimeBackupStateAsClusterAdminResponses[keyof GetRuntimeBackupStateAsClusterAdminResponses]; ``` --- ## Type Alias: GetRuntimeBackupStateAsClusterAdminResponses ```ts type GetRuntimeBackupStateAsClusterAdminResponses = object; ``` ## Properties ### 200 ```ts 200: ClusterRuntimeBackupState; ``` The runtime backup state of every targeted physical tenant, ordered by physical tenant id. --- ## Type Alias: GetRuntimeBackupStateData ```ts type GetRuntimeBackupStateData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/backups/runtime/state"; ``` --- ## Type Alias: GetRuntimeBackupStateError ```ts type GetRuntimeBackupStateError = GetRuntimeBackupStateErrors[keyof GetRuntimeBackupStateErrors]; ``` --- ## Type Alias: GetRuntimeBackupStateErrors ```ts type GetRuntimeBackupStateErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: GetRuntimeBackupStateResponse ```ts type GetRuntimeBackupStateResponse = GetRuntimeBackupStateResponses[keyof GetRuntimeBackupStateResponses]; ``` --- ## Type Alias: GetRuntimeBackupStateResponses ```ts type GetRuntimeBackupStateResponses = object; ``` ## Properties ### 200 ```ts 200: RuntimeBackupState; ``` The runtime backup state. --- ## Type Alias: GetStartProcessFormData ```ts type GetStartProcessFormData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKeyWritable; ``` The process key. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-definitions/{processDefinitionKey}/form"; ``` --- ## Type Alias: GetStartProcessFormError ```ts type GetStartProcessFormError = GetStartProcessFormErrors[keyof GetStartProcessFormErrors]; ``` --- ## Type Alias: GetStartProcessFormErrors ```ts type GetStartProcessFormErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Not found --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetStartProcessFormResponse ```ts type GetStartProcessFormResponse = GetStartProcessFormResponses[keyof GetStartProcessFormResponses]; ``` --- ## Type Alias: GetStartProcessFormResponses ```ts type GetStartProcessFormResponses = object; ``` ## Properties ### 200 ```ts 200: FormResult; ``` The form is successfully returned. --- ### 204 ```ts 204: void; ``` The process was found, but no form is associated with it. --- ## Type Alias: GetStatusData ```ts type GetStatusData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/status"; ``` --- ## Type Alias: GetStatusErrors ```ts type GetStatusErrors = object; ``` ## Properties ### 503 ```ts 503: unknown; ``` The default physical tenant is DOWN and does not have any partition with a healthy leader. --- ## Type Alias: GetStatusResponse ```ts type GetStatusResponse = GetStatusResponses[keyof GetStatusResponses]; ``` --- ## Type Alias: GetStatusResponses ```ts type GetStatusResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The default physical tenant is UP and has at least one partition with a healthy leader. --- ## Type Alias: GetSystemConfigurationData ```ts type GetSystemConfigurationData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/system/configuration"; ``` --- ## Type Alias: GetSystemConfigurationError ```ts type GetSystemConfigurationError = GetSystemConfigurationErrors[keyof GetSystemConfigurationErrors]; ``` --- ## Type Alias: GetSystemConfigurationErrors ```ts type GetSystemConfigurationErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetSystemConfigurationResponse ```ts type GetSystemConfigurationResponse = GetSystemConfigurationResponses[keyof GetSystemConfigurationResponses]; ``` --- ## Type Alias: GetSystemConfigurationResponses ```ts type GetSystemConfigurationResponses = object; ``` ## Properties ### 200 ```ts 200: SystemConfigurationResponse; ``` Current system configuration grouped by feature area. --- ## Type Alias: GetTenantClusterVariableData ```ts type GetTenantClusterVariableData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### name ```ts name: ClusterVariableName; ``` The name of the cluster variable #### tenantId ```ts tenantId: TenantId; ``` The tenant ID --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/cluster-variables/tenants/{tenantId}/{name}"; ``` --- ## Type Alias: GetTenantClusterVariableError ```ts type GetTenantClusterVariableError = GetTenantClusterVariableErrors[keyof GetTenantClusterVariableErrors]; ``` --- ## Type Alias: GetTenantClusterVariableErrors ```ts type GetTenantClusterVariableErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Cluster variable not found --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetTenantClusterVariableResponse ```ts type GetTenantClusterVariableResponse = GetTenantClusterVariableResponses[keyof GetTenantClusterVariableResponses]; ``` --- ## Type Alias: GetTenantClusterVariableResponses ```ts type GetTenantClusterVariableResponses = object; ``` ## Properties ### 200 ```ts 200: ClusterVariableResult; ``` Cluster variable found --- ## Type Alias: GetTenantData ```ts type GetTenantData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### tenantId ```ts tenantId: TenantId; ``` The unique identifier of the tenant. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/tenants/{tenantId}"; ``` --- ## Type Alias: GetTenantError ```ts type GetTenantError = GetTenantErrors[keyof GetTenantErrors]; ``` --- ## Type Alias: GetTenantErrors ```ts type GetTenantErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Tenant not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetTenantResponse ```ts type GetTenantResponse = GetTenantResponses[keyof GetTenantResponses]; ``` --- ## Type Alias: GetTenantResponses ```ts type GetTenantResponses = object; ``` ## Properties ### 200 ```ts 200: TenantResult; ``` The tenant was retrieved successfully. --- ## Type Alias: GetTopologyData ```ts type GetTopologyData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/topology"; ``` --- ## Type Alias: GetTopologyError ```ts type GetTopologyError = GetTopologyErrors[keyof GetTopologyErrors]; ``` --- ## Type Alias: GetTopologyErrors ```ts type GetTopologyErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetTopologyResponse ```ts type GetTopologyResponse = GetTopologyResponses[keyof GetTopologyResponses]; ``` --- ## Type Alias: GetTopologyResponses ```ts type GetTopologyResponses = object; ``` ## Properties ### 200 ```ts 200: TopologyResponse; ``` Obtains the current topology of the cluster the gateway is part of. --- ## Type Alias: GetUsageMetricsData ```ts type GetUsageMetricsData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query ```ts query: object; ``` #### endTime ```ts endTime: string; ``` The end date for usage metrics, including this date. Value in ISO 8601 format. #### startTime ```ts startTime: string; ``` The start date for usage metrics, including this date. Value in ISO 8601 format. #### tenantId? ```ts optional tenantId?: TenantId; ``` Restrict results to a specific tenant ID. If not provided, results for all tenants are returned. #### withTenants? ```ts optional withTenants?: boolean; ``` Whether to return tenant metrics in addition to the total metrics or not. Default false. --- ### url ```ts url: "/system/usage-metrics"; ``` --- ## Type Alias: GetUsageMetricsError ```ts type GetUsageMetricsError = GetUsageMetricsErrors[keyof GetUsageMetricsErrors]; ``` --- ## Type Alias: GetUsageMetricsErrors ```ts type GetUsageMetricsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetUsageMetricsResponse ```ts type GetUsageMetricsResponse = GetUsageMetricsResponses[keyof GetUsageMetricsResponses]; ``` --- ## Type Alias: GetUsageMetricsResponses ```ts type GetUsageMetricsResponses = object; ``` ## Properties ### 200 ```ts 200: UsageMetricsResponse; ``` The usage metrics search result. --- ## Type Alias: GetUserData ```ts type GetUserData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### username ```ts username: Username; ``` The username of the user. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/users/{username}"; ``` --- ## Type Alias: GetUserError ```ts type GetUserError = GetUserErrors[keyof GetUserErrors]; ``` --- ## Type Alias: GetUserErrors ```ts type GetUserErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The user with the given username was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetUserResponse ```ts type GetUserResponse = GetUserResponses[keyof GetUserResponses]; ``` --- ## Type Alias: GetUserResponses ```ts type GetUserResponses = object; ``` ## Properties ### 200 ```ts 200: UserResult; ``` The user is successfully returned. --- ## Type Alias: GetUserTaskData ```ts type GetUserTaskData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### userTaskKey ```ts userTaskKey: UserTaskKeyWritable; ``` The user task key. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/user-tasks/{userTaskKey}"; ``` --- ## Type Alias: GetUserTaskError ```ts type GetUserTaskError = GetUserTaskErrors[keyof GetUserTaskErrors]; ``` --- ## Type Alias: GetUserTaskErrors ```ts type GetUserTaskErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The user task with the given key was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetUserTaskFormData ```ts type GetUserTaskFormData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### userTaskKey ```ts userTaskKey: UserTaskKeyWritable; ``` The user task key. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/user-tasks/{userTaskKey}/form"; ``` --- ## Type Alias: GetUserTaskFormError ```ts type GetUserTaskFormError = GetUserTaskFormErrors[keyof GetUserTaskFormErrors]; ``` --- ## Type Alias: GetUserTaskFormErrors ```ts type GetUserTaskFormErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Not found --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetUserTaskFormResponse ```ts type GetUserTaskFormResponse = GetUserTaskFormResponses[keyof GetUserTaskFormResponses]; ``` --- ## Type Alias: GetUserTaskFormResponses ```ts type GetUserTaskFormResponses = object; ``` ## Properties ### 200 ```ts 200: FormResult; ``` The form is successfully returned. --- ### 204 ```ts 204: void; ``` The user task was found, but no form is associated with it. --- ## Type Alias: GetUserTaskResponse ```ts type GetUserTaskResponse = GetUserTaskResponses[keyof GetUserTaskResponses]; ``` --- ## Type Alias: GetUserTaskResponses ```ts type GetUserTaskResponses = object; ``` ## Properties ### 200 ```ts 200: UserTaskResult; ``` The user task is successfully returned. --- ## Type Alias: GetVariableData ```ts type GetVariableData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### variableKey ```ts variableKey: VariableKeyWritable; ``` The variable key. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/variables/{variableKey}"; ``` --- ## Type Alias: GetVariableError ```ts type GetVariableError = GetVariableErrors[keyof GetVariableErrors]; ``` --- ## Type Alias: GetVariableErrors ```ts type GetVariableErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Not found --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: GetVariableResponse ```ts type GetVariableResponse = GetVariableResponses[keyof GetVariableResponses]; ``` --- ## Type Alias: GetVariableResponses ```ts type GetVariableResponses = object; ``` ## Properties ### 200 ```ts 200: VariableResult; ``` The variable is successfully returned. --- ## Type Alias: GlobalJobStatisticsQueryResult ```ts type GlobalJobStatisticsQueryResult = object; ``` Global job statistics query result. ## Properties ### completed ```ts completed: StatusMetric; ``` --- ### created ```ts created: StatusMetric; ``` --- ### failed ```ts failed: StatusMetric; ``` --- ### isIncomplete ```ts isIncomplete: boolean; ``` True if some data is missing because internal limits were reached and some metrics were not recorded. --- ## Type Alias: GlobalListenerBase ```ts type GlobalListenerBase = object; ``` ## Properties ### afterNonGlobal? ```ts optional afterNonGlobal?: boolean; ``` Whether the listener should run after model-level listeners. --- ### priority? ```ts optional priority?: number; ``` The priority of the listener. Higher priority listeners are executed before lower priority ones. --- ### retries? ```ts optional retries?: number; ``` Number of retries for the listener job. --- ### type? ```ts optional type?: string; ``` The name of the job type, used as a reference to specify which job workers request the respective listener job. --- ## Type Alias: GlobalListenerId ```ts type GlobalListenerId = CamundaKey<"GlobalListenerId">; ``` The user-defined id for the global listener --- ## Type Alias: GlobalListenerSourceEnum ```ts type GlobalListenerSourceEnum = (typeof GlobalListenerSourceEnum)[keyof typeof GlobalListenerSourceEnum]; ``` How the global listener was defined. --- ## Type Alias: GlobalListenerSourceExactMatch ```ts type GlobalListenerSourceExactMatch = GlobalListenerSourceEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: GlobalListenerSourceExactMatchWritable ```ts type GlobalListenerSourceExactMatchWritable = GlobalListenerSourceEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: GlobalListenerSourceFilterProperty ```ts type GlobalListenerSourceFilterProperty = GlobalListenerSourceExactMatch | AdvancedGlobalListenerSourceFilter; ``` Global listener source property with full advanced search capabilities. --- ## Type Alias: GlobalTaskListenerBase ```ts type GlobalTaskListenerBase = GlobalListenerBase & object; ``` ## Type Declaration ### eventTypes? ```ts optional eventTypes?: GlobalTaskListenerEventTypes; ``` --- ## Type Alias: GlobalTaskListenerEventTypeEnum ```ts type GlobalTaskListenerEventTypeEnum = (typeof GlobalTaskListenerEventTypeEnum)[keyof typeof GlobalTaskListenerEventTypeEnum]; ``` The event type that triggers the user task listener. --- ## Type Alias: GlobalTaskListenerEventTypeExactMatch ```ts type GlobalTaskListenerEventTypeExactMatch = GlobalTaskListenerEventTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: GlobalTaskListenerEventTypeExactMatchWritable ```ts type GlobalTaskListenerEventTypeExactMatchWritable = GlobalTaskListenerEventTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: GlobalTaskListenerEventTypeFilterProperty ```ts type GlobalTaskListenerEventTypeFilterProperty = | GlobalTaskListenerEventTypeExactMatch | AdvancedGlobalTaskListenerEventTypeFilter; ``` Global listener event type property with full advanced search capabilities. --- ## Type Alias: GlobalTaskListenerEventTypes ```ts type GlobalTaskListenerEventTypes = GlobalTaskListenerEventTypeEnum[]; ``` List of user task event types that trigger the listener. --- ## Type Alias: GlobalTaskListenerEventTypesWritable ```ts type GlobalTaskListenerEventTypesWritable = GlobalTaskListenerEventTypeEnum[]; ``` List of user task event types that trigger the listener. --- ## Type Alias: GlobalTaskListenerResult ```ts type GlobalTaskListenerResult = GlobalTaskListenerBase & object; ``` ## Type Declaration ### eventTypes ```ts eventTypes: GlobalTaskListenerEventTypes; ``` ### id ```ts id: GlobalListenerId; ``` ### source ```ts source: GlobalListenerSourceEnum; ``` --- ## Type Alias: GlobalTaskListenerSearchQueryFilterRequest ```ts type GlobalTaskListenerSearchQueryFilterRequest = object; ``` Global listener filter request. ## Properties ### afterNonGlobal? ```ts optional afterNonGlobal?: boolean; ``` Whether the listener runs after model-level listeners. --- ### eventTypes? ```ts optional eventTypes?: GlobalTaskListenerEventTypeFilterProperty[]; ``` Event types of the global listener. --- ### id? ```ts optional id?: StringFilterProperty; ``` Id of the global listener. --- ### priority? ```ts optional priority?: IntegerFilterProperty; ``` Priority of the global listener. --- ### retries? ```ts optional retries?: IntegerFilterProperty; ``` Number of retries of the global listener. --- ### source? ```ts optional source?: GlobalListenerSourceFilterProperty; ``` How the global listener was defined. --- ### type? ```ts optional type?: StringFilterProperty; ``` Job type of the global listener. --- ## Type Alias: GlobalTaskListenerSearchQueryRequest ```ts type GlobalTaskListenerSearchQueryRequest = SearchQueryRequest & object; ``` Global listener search query request. ## Type Declaration ### filter? ```ts optional filter?: GlobalTaskListenerSearchQueryFilterRequest; ``` The global listener search filters. ### sort? ```ts optional sort?: GlobalTaskListenerSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: GlobalTaskListenerSearchQueryResult ```ts type GlobalTaskListenerSearchQueryResult = SearchQueryResponse & object; ``` Global listener search query response. ## Type Declaration ### items ```ts items: GlobalTaskListenerResult[]; ``` The matching global listeners. --- ## Type Alias: GlobalTaskListenerSearchQuerySortRequest ```ts type GlobalTaskListenerSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: "id" | "type" | "afterNonGlobal" | "priority" | "source"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: GroupClientResult ```ts type GroupClientResult = object; ``` ## Properties ### clientId ```ts clientId: ClientId; ``` The ID of the client. --- ## Type Alias: GroupClientSearchQueryRequest ```ts type GroupClientSearchQueryRequest = SearchQueryRequest & object; ``` ## Type Declaration ### sort? ```ts optional sort?: GroupClientSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: GroupClientSearchQuerySortRequest ```ts type GroupClientSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: "clientId"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: GroupClientSearchResult ```ts type GroupClientSearchResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: GroupClientResult[]; ``` The matching client IDs. --- ## Type Alias: GroupCreateRequest ```ts type GroupCreateRequest = object; ``` ## Properties ### description? ```ts optional description?: string; ``` The description of the new group. --- ### groupId ```ts groupId: GroupId; ``` The ID of the new group. --- ### name ```ts name: string; ``` The display name of the new group. --- ## Type Alias: GroupCreateResult ```ts type GroupCreateResult = object; ``` ## Properties ### description ```ts description: string | null; ``` The description of the created group. --- ### groupId ```ts groupId: GroupId; ``` The ID of the created group. --- ### name ```ts name: string; ``` The display name of the created group. --- ## Type Alias: GroupFilter ```ts type GroupFilter = GroupFilterFields & object; ``` Group filter request ## Type Declaration ### $or? ```ts optional $or?: GroupFilterFields[] | null; ``` Defines a list of alternative filter groups combined using OR logic. Each object in the array is evaluated independently, and the filter matches if any one of them is satisfied. Top-level fields and the `$or` clause are combined using AND logic — meaning: (top-level filters) AND (any of the `$or` filters) must match. _Example:_ ```json { "$or": [{ "groupId": "group-1" }, { "groupId": "group-2" }] } ``` This matches groups whose `groupId` is _group-1_ or _group-2_. Note: Using complex `$or` conditions may impact performance, use with caution in high-volume environments. --- ## Type Alias: GroupFilterFields ```ts type GroupFilterFields = object; ``` Group filter request ## Properties ### groupId? ```ts optional groupId?: StringFilterProperty; ``` The group ID search filters. --- ### name? ```ts optional name?: StringFilterProperty; ``` The group name search filters. --- ## Type Alias: GroupId ```ts type GroupId = CamundaKey<"GroupId">; ``` The unique identifier of a group. --- ## Type Alias: GroupMappingRuleSearchResult ```ts type GroupMappingRuleSearchResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: MappingRuleResult[]; ``` The matching mapping rules. --- ## Type Alias: GroupResult ```ts type GroupResult = object; ``` Group search response item. ## Properties ### description ```ts description: string | null; ``` The group description. --- ### groupId ```ts groupId: GroupId; ``` The group ID. --- ### name ```ts name: string; ``` The group name. --- ## Type Alias: GroupRoleSearchResult ```ts type GroupRoleSearchResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: RoleResult[]; ``` The matching roles. --- ## Type Alias: GroupSearchQueryRequest ```ts type GroupSearchQueryRequest = SearchQueryRequest & object; ``` Group search request. ## Type Declaration ### filter? ```ts optional filter?: GroupFilter; ``` The group search filters. ### sort? ```ts optional sort?: GroupSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: GroupSearchQueryResult ```ts type GroupSearchQueryResult = SearchQueryResponse & object; ``` Group search response. ## Type Declaration ### items ```ts items: GroupResult[]; ``` The matching groups. --- ## Type Alias: GroupSearchQuerySortRequest ```ts type GroupSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: "name" | "groupId"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: GroupUpdateRequest ```ts type GroupUpdateRequest = object; ``` ## Properties ### description? ```ts optional description?: string; ``` The new description of the group. --- ### name ```ts name: string; ``` The new name of the group. --- ## Type Alias: GroupUpdateResult ```ts type GroupUpdateResult = object; ``` ## Properties ### description ```ts description: string | null; ``` The description of the group. --- ### groupId ```ts groupId: GroupId; ``` The unique group ID. --- ### name ```ts name: string; ``` The name of the group. --- ## Type Alias: GroupUserResult ```ts type GroupUserResult = object; ``` ## Properties ### username ```ts username: Username; ``` --- ## Type Alias: GroupUserSearchQueryRequest ```ts type GroupUserSearchQueryRequest = SearchQueryRequest & object; ``` ## Type Declaration ### sort? ```ts optional sort?: GroupUserSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: GroupUserSearchQuerySortRequest ```ts type GroupUserSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: "username"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: GroupUserSearchResult ```ts type GroupUserSearchResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: GroupUserResult[]; ``` The matching members. --- ## Type Alias: HandlerClock ```ts type HandlerClock = Pick; ``` The slice of the clock handed to job handlers. Deliberately narrower than `Clock`: `deadline` is a liveness primitive, and a handler that built one against a pinned clock would hang rather than time out. Handlers get to read time and to wait, nothing else. `sleep` is for short in-handler coordination — backing off around a flaky dependency, spacing retries within one job. Long or business-meaningful waits belong in the process as BPMN timers, where they survive a crash and are visible to operations. --- ## Type Alias: HistoryBackupInfo ```ts type HistoryBackupInfo = object; ``` History Backup Info Detailed status of a history backup. The aggregated state is computed from the state of each of its snapshots as: - If every expected snapshot exists and all are complete, the overall state is 'COMPLETED'. - If one snapshot failed or is partial, the overall state is 'FAILED'. - Otherwise, if one snapshot is incompatible, the overall state is 'INCOMPATIBLE'. - Otherwise, if one snapshot is still running, the overall state is 'IN_PROGRESS'. - Otherwise, if snapshots are missing and the backup has not progressed within the configured timeout, the overall state is 'INCOMPLETE'. ## Properties ### backupId ```ts backupId: BackupId; ``` The id of the backup. --- ### details ```ts readonly details: HistoryBackupSnapshotInfo[]; ``` Detailed status of the backup per snapshot. Always lists every snapshot found for the backup; when the backup was read without snapshot detail, each entry carries only its name. --- ### failureReason ```ts failureReason: string | null; ``` Reason for failure if the state is 'FAILED'. --- ### state ```ts state: HistoryBackupStateCode; ``` The aggregated state of the backup. --- ## Type Alias: HistoryBackupInfoWritable ```ts type HistoryBackupInfoWritable = object; ``` History Backup Info Detailed status of a history backup. The aggregated state is computed from the state of each of its snapshots as: - If every expected snapshot exists and all are complete, the overall state is 'COMPLETED'. - If one snapshot failed or is partial, the overall state is 'FAILED'. - Otherwise, if one snapshot is incompatible, the overall state is 'INCOMPATIBLE'. - Otherwise, if one snapshot is still running, the overall state is 'IN_PROGRESS'. - Otherwise, if snapshots are missing and the backup has not progressed within the configured timeout, the overall state is 'INCOMPLETE'. ## Properties ### failureReason ```ts failureReason: string | null; ``` Reason for failure if the state is 'FAILED'. --- ## Type Alias: HistoryBackupSnapshotInfo ```ts type HistoryBackupSnapshotInfo = object; ``` History Backup Snapshot Info Detailed info of a single snapshot making up a history backup. ## Properties ### failures ```ts readonly failures: string[]; ``` The failures reported for this snapshot. Empty if there were none. --- ### snapshotName ```ts readonly snapshotName: string; ``` The name of the snapshot. --- ### startTime ```ts readonly startTime: string | null; ``` The timestamp at which the snapshot was started. Not reported when the backup was listed without snapshot detail. --- ### state ```ts readonly state: string | null; ``` The state of the snapshot, reported verbatim by the secondary storage (for example 'SUCCESS', 'IN_PROGRESS' or 'PARTIAL'). Deliberately not a closed set: Elasticsearch and OpenSearch report different vocabularies. Not reported when the backup was listed without snapshot detail. --- ## Type Alias: HistoryBackupStateCode ```ts type HistoryBackupStateCode = "IN_PROGRESS" | "COMPLETED" | "FAILED" | "INCOMPLETE" | "INCOMPATIBLE"; ``` History Backup State The aggregated state of a history backup, computed from the state of each of its snapshots. --- ## Type Alias: HistoryItemId ```ts type HistoryItemId = CamundaKey<"HistoryItemId">; ``` The client-supplied identifier this item was created with. --- ## Type Alias: IncidentErrorTypeEnum ```ts type IncidentErrorTypeEnum = (typeof IncidentErrorTypeEnum)[keyof typeof IncidentErrorTypeEnum]; ``` Incident error type with a defined set of values. --- ## Type Alias: IncidentErrorTypeExactMatch ```ts type IncidentErrorTypeExactMatch = IncidentErrorTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: IncidentErrorTypeExactMatchWritable ```ts type IncidentErrorTypeExactMatchWritable = IncidentErrorTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: IncidentErrorTypeFilterProperty ```ts type IncidentErrorTypeFilterProperty = IncidentErrorTypeExactMatch | AdvancedIncidentErrorTypeFilter; ``` IncidentErrorTypeEnum with full advanced search capabilities. --- ## Type Alias: IncidentFilter ```ts type IncidentFilter = object; ``` Incident search filter. ## Properties ### creationTime? ```ts optional creationTime?: DateTimeFilterProperty; ``` Date of incident creation. --- ### elementId? ```ts optional elementId?: StringFilterProperty; ``` The element ID associated to this incident. --- ### elementInstanceKey? ```ts optional elementInstanceKey?: ElementInstanceKeyFilterProperty; ``` The element instance key associated to this incident. --- ### errorMessage? ```ts optional errorMessage?: StringFilterProperty; ``` The error message of this incident. For `$eq`, `$neq`, `$in`, and `$notIn`, matching is case-insensitive and matches if the incident's error message contains the given value as a phrase, not necessarily the entire error message. `$like` matches on individual words of the error message and does not support multi-word patterns. --- ### errorType? ```ts optional errorType?: IncidentErrorTypeFilterProperty; ``` Incident error type with a defined set of values. --- ### incidentKey? ```ts optional incidentKey?: BasicStringFilterProperty; ``` The assigned key, which acts as a unique identifier for this incident. --- ### jobKey? ```ts optional jobKey?: JobKeyFilterProperty; ``` The job key, if exists, associated with this incident. --- ### processDefinitionId? ```ts optional processDefinitionId?: StringFilterProperty; ``` The process definition ID associated to this incident. --- ### processDefinitionKey? ```ts optional processDefinitionKey?: ProcessDefinitionKeyFilterProperty; ``` The process definition key associated to this incident. --- ### processInstanceKey? ```ts optional processInstanceKey?: ProcessInstanceKeyFilterProperty; ``` The process instance key associated to this incident. --- ### state? ```ts optional state?: IncidentStateFilterProperty; ``` State of this incident with a defined set of values. --- ### tenantId? ```ts optional tenantId?: StringFilterProperty; ``` The tenant ID of the incident. --- ## Type Alias: IncidentKey ```ts type IncidentKey = CamundaKey<"IncidentKey">; ``` System-generated key for a incident. --- ## Type Alias: IncidentKeyWritable ```ts type IncidentKeyWritable = LongKey; ``` System-generated key for a incident. --- ## Type Alias: IncidentProcessInstanceStatisticsByDefinitionFilter ```ts type IncidentProcessInstanceStatisticsByDefinitionFilter = object; ``` Filter for the incident process instance statistics by definition query. ## Properties ### errorHashCode ```ts errorHashCode: number; ``` The error hash code of the incidents to filter the process instance statistics by. --- ## Type Alias: IncidentProcessInstanceStatisticsByDefinitionQuery ```ts type IncidentProcessInstanceStatisticsByDefinitionQuery = object; ``` ## Properties ### filter ```ts filter: IncidentProcessInstanceStatisticsByDefinitionFilter; ``` Filter criteria for the aggregated process instance statistics. --- ### page? ```ts optional page?: OffsetPagination; ``` Pagination parameters for the aggregated process instance statistics. --- ### sort? ```ts optional sort?: IncidentProcessInstanceStatisticsByDefinitionQuerySortRequest[]; ``` Sorting criteria for process instance statistics grouped by process definition. --- ## Type Alias: IncidentProcessInstanceStatisticsByDefinitionQueryResult ```ts type IncidentProcessInstanceStatisticsByDefinitionQueryResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: IncidentProcessInstanceStatisticsByDefinitionResult[]; ``` Statistics of active process instances with incidents, grouped by process definition for the specified error hash code. --- ## Type Alias: IncidentProcessInstanceStatisticsByDefinitionQuerySortRequest ```ts type IncidentProcessInstanceStatisticsByDefinitionQuerySortRequest = object; ``` ## Properties ### field ```ts field: "activeInstancesWithErrorCount" | "processDefinitionKey" | "tenantId"; ``` The aggregated field by which the process instance statistics are sorted. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: IncidentProcessInstanceStatisticsByDefinitionResult ```ts type IncidentProcessInstanceStatisticsByDefinitionResult = object; ``` ## Properties ### activeInstancesWithErrorCount ```ts activeInstancesWithErrorCount: number; ``` The number of active process instances that currently have an incident with the specified error hash code. --- ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId; ``` --- ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey; ``` --- ### processDefinitionName ```ts processDefinitionName: string; ``` The name of the process definition. --- ### processDefinitionVersion ```ts processDefinitionVersion: number; ``` The version of the process definition. --- ### tenantId ```ts tenantId: TenantId; ``` --- ## Type Alias: IncidentProcessInstanceStatisticsByErrorQuery ```ts type IncidentProcessInstanceStatisticsByErrorQuery = object; ``` ## Properties ### page? ```ts optional page?: OffsetPagination; ``` Pagination parameters for process instance statistics grouped by incident error. --- ### sort? ```ts optional sort?: IncidentProcessInstanceStatisticsByErrorQuerySortRequest[]; ``` Sorting criteria for process instance statistics grouped by incident error. --- ## Type Alias: IncidentProcessInstanceStatisticsByErrorQueryResult ```ts type IncidentProcessInstanceStatisticsByErrorQueryResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: IncidentProcessInstanceStatisticsByErrorResult[]; ``` Statistics of active process instances grouped by incident error. --- ## Type Alias: IncidentProcessInstanceStatisticsByErrorQuerySortRequest ```ts type IncidentProcessInstanceStatisticsByErrorQuerySortRequest = object; ``` ## Properties ### field ```ts field: "errorMessage" | "activeInstancesWithErrorCount"; ``` The field to sort the incident error statistics by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: IncidentProcessInstanceStatisticsByErrorResult ```ts type IncidentProcessInstanceStatisticsByErrorResult = object; ``` ## Properties ### activeInstancesWithErrorCount ```ts activeInstancesWithErrorCount: number; ``` The number of active process instances that currently have an active incident with this error. --- ### errorHashCode ```ts errorHashCode: number; ``` The hash code identifying a specific incident error.. --- ### errorMessage ```ts errorMessage: string; ``` The error message associated with the incident error hash code. --- ## Type Alias: IncidentResolutionRequest ```ts type IncidentResolutionRequest = object; ``` ## Properties ### operationReference? ```ts optional operationReference?: OperationReference; ``` --- ## Type Alias: IncidentResult ```ts type IncidentResult = object; ``` ## Properties ### creationTime ```ts creationTime: string; ``` The creation time of the incident. --- ### elementId ```ts elementId: ElementId; ``` The element ID associated to this incident. --- ### elementInstanceKey ```ts elementInstanceKey: ElementInstanceKey; ``` The element instance key associated to this incident. --- ### errorMessage ```ts errorMessage: string; ``` Error message which describes the error in more detail. --- ### errorType ```ts errorType: IncidentErrorTypeEnum; ``` The type of the incident error. --- ### incidentKey ```ts incidentKey: IncidentKey; ``` The assigned key, which acts as a unique identifier for this incident. --- ### jobKey ```ts jobKey: JobKey | null; ``` The job key, if exists, associated with this incident. --- ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId; ``` The process definition ID associated to this incident. --- ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey; ``` The process definition key associated to this incident. --- ### processInstanceKey ```ts processInstanceKey: ProcessInstanceKey; ``` The process instance key associated to this incident. --- ### rootProcessInstanceKey ```ts rootProcessInstanceKey: ProcessInstanceKey | null; ``` The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. --- ### state ```ts state: IncidentStateEnum; ``` The incident state. --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID of the incident. --- ## Type Alias: IncidentSearchQuery ```ts type IncidentSearchQuery = SearchQueryRequest & object; ``` ## Type Declaration ### filter? ```ts optional filter?: IncidentFilter; ``` The incident search filters. ### sort? ```ts optional sort?: IncidentSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: IncidentSearchQueryResult ```ts type IncidentSearchQueryResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: IncidentResult[]; ``` The matching incidents. --- ## Type Alias: IncidentSearchQuerySortRequest ```ts type IncidentSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "incidentKey" | "processDefinitionKey" | "processDefinitionId" | "processInstanceKey" | "errorType" | "elementId" | "elementInstanceKey" | "creationTime" | "state" | "jobKey" | "tenantId"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: IncidentStateEnum ```ts type IncidentStateEnum = (typeof IncidentStateEnum)[keyof typeof IncidentStateEnum]; ``` Incident states with a defined set of values. --- ## Type Alias: IncidentStateExactMatch ```ts type IncidentStateExactMatch = IncidentStateEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: IncidentStateExactMatchWritable ```ts type IncidentStateExactMatchWritable = IncidentStateEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: IncidentStateFilterProperty ```ts type IncidentStateFilterProperty = IncidentStateExactMatch | AdvancedIncidentStateFilter; ``` IncidentStateEnum with full advanced search capabilities. --- ## Type Alias: InferredAncestorKeyInstruction ```ts type InferredAncestorKeyInstruction = object; ``` Instructs the engine to derive the ancestor scope key from the source element's hierarchy. The engine traverses the source element's ancestry to find an instance that matches one of the target element's flow scopes, ensuring the target is activated in the correct scope. ## Properties ### ancestorScopeType ```ts ancestorScopeType: string; ``` The type of ancestor scope instruction. --- ## Type Alias: IntegerFilterProperty ```ts type IntegerFilterProperty = number | AdvancedIntegerFilter; ``` Integer property with advanced search capabilities. --- ## Type Alias: Job # Type Alias: Job\ ```ts type Job = EnrichedActivatedJob & object; ``` ## Type Parameters ### In `In` _extends_ `z.ZodTypeAny` \| `undefined` ### Headers `Headers` _extends_ `z.ZodTypeAny` \| `undefined` ## Type Declaration ### customHeaders ```ts customHeaders: InferOrUnknown; ``` ### variables ```ts variables: InferOrUnknown; ``` --- ## Type Alias: JobActionReceipt ```ts type JobActionReceipt = typeof JobActionReceipt; ``` Unique receipt symbol returned by job action methods. --- ## Type Alias: JobActionReceipt(Type-aliases) ```ts type JobActionReceipt = "JOB_ACTION_RECEIPT"; ``` Unique receipt symbol returned by job action methods. --- ## Type Alias: JobActivationRequest ```ts type JobActivationRequest = object; ``` ## Properties ### fetchVariable? ```ts optional fetchVariable?: string[]; ``` A list of variables to fetch as the job variables; if empty, all visible variables at the time of activation for the scope of the job will be returned. --- ### maxJobsToActivate ```ts maxJobsToActivate: number; ``` The maximum jobs to activate by this request. --- ### requestTimeout? ```ts optional requestTimeout?: number; ``` The request will be completed when at least one job is activated or after the requestTimeout (in ms). If the requestTimeout = 0, a default timeout is used. If the requestTimeout < 0, long polling is disabled and the request is completed immediately, even when no job is activated. --- ### tenantFilter? ```ts optional tenantFilter?: TenantFilterEnum; ``` The tenant filtering strategy - determines whether to use provided tenant IDs or assigned tenant IDs from the authenticated principal's authorized tenants. --- ### tenantIds? ```ts optional tenantIds?: TenantId[]; ``` A list of IDs of tenants for which to activate jobs. --- ### timeout ```ts timeout: number; ``` A job returned after this call will not be activated by another call until the timeout (in ms) has been reached. --- ### type ```ts type: string; ``` The job type, as defined in the BPMN process (e.g. ) --- ### withLease? ```ts optional withLease?: boolean | null; ``` Whether to activate the jobs with a lease. When true, each activated job is assigned a distinct, opaque lease token, returned as ActivatedJobResult.jobLeaseToken. The lease fences the complete, fail, and throw-error commands against a superseded activation of the same job (for example, after the job timed out or failed and was re-activated by another worker): a command carrying a stale lease token is rejected rather than racing with the newer activation. Once a job has been activated with a lease, it is served only to leasing workers of that job type; a homogeneous fleet per job type is recommended. Omit or set to false to activate jobs without a lease. --- ### worker? ```ts optional worker?: string; ``` The name of the worker activating the jobs, mostly used for logging purposes. --- ## Type Alias: JobActivationResult ```ts type JobActivationResult = object; ``` The list of activated jobs ## Properties ### jobs ```ts jobs: ActivatedJobResult[]; ``` The activated jobs. --- ## Type Alias: JobBatchUpdateRequest ```ts type JobBatchUpdateRequest = object; ``` The filter and changeset for a batch job update operation. The filter defines which jobs are updated; the changeset defines what to update. At least one changeset field must be non-null. ## Properties ### changeset ```ts changeset: JobChangeset; ``` The fields to update. At least one field must be non-null. --- ### filter ```ts filter: JobFilter; ``` The job filter. At least one dimension must be set. --- ### operationReference? ```ts optional operationReference?: OperationReference; ``` --- ## Type Alias: JobChangeset ```ts type JobChangeset = object; ``` JSON object with changed job attribute values. The job cannot be completed or failed with this endpoint, use the complete job or fail job endpoints instead. ## Properties ### priority? ```ts optional priority?: number | null; ``` The new priority for the job. Higher values indicate higher priority. --- ### retries? ```ts optional retries?: number | null; ``` The new number of retries for the job. --- ### timeout? ```ts optional timeout?: number | null; ``` The new timeout for the job in milliseconds. --- ## Type Alias: JobCompletionRequest ```ts type JobCompletionRequest = object; ``` ## Properties ### businessId? ```ts optional businessId?: BusinessId | null; ``` An optional business id to assign to the process instance the job belongs to, as part of completing the job, letting a worker set the identifier from work it just performed. The business id can only be assigned to a root process instance: if the job belongs to a child process instance (one started by a call activity), the completion is rejected. An empty business id is likewise rejected. The assignment is single and irreversible and is only accepted while business id uniqueness is disabled. Only artifacts created after the assignment carry the business id; already-existing ones are not enriched. Completing with a business id that differs from one already assigned rejects the whole completion, leaving the job open; re-sending the identical business id is an idempotent no-op. --- ### jobLeaseToken? ```ts optional jobLeaseToken?: JobLeaseToken | null; ``` The token identifying a leased job's activation, obtained from `ActivatedJobResult.jobLeaseToken`. For a leased job, the matching token must be supplied to prove the command comes from the worker that holds the current lease; a command with no token is rejected. A command carrying a stale token is likewise rejected, fencing the job against a superseded activation (for example, after the job timed out or failed and was re-activated by another worker). A job that was activated without a lease requires no token. --- ### result? ```ts optional result?: JobResult; ``` --- ### variables? ```ts optional variables?: | { [key: string]: unknown; } | null; ``` The variables to complete the job with. --- ## Type Alias: JobErrorRequest ```ts type JobErrorRequest = object; ``` ## Properties ### errorCode ```ts errorCode: string; ``` The error code that will be matched with an error catch event. --- ### errorMessage? ```ts optional errorMessage?: string | null; ``` An error message that provides additional context. --- ### jobLeaseToken? ```ts optional jobLeaseToken?: JobLeaseToken | null; ``` The token identifying a leased job's activation, obtained from `ActivatedJobResult.jobLeaseToken`. For a leased job, the matching token must be supplied to prove the command comes from the worker that holds the current lease; a command with no token is rejected. A command carrying a stale token is likewise rejected, fencing the job against a superseded activation (for example, after the job timed out or failed and was re-activated by another worker). A job that was activated without a lease requires no token. --- ### variables? ```ts optional variables?: | { [key: string]: unknown; } | null; ``` JSON object that will instantiate the variables at the local scope of the error catch event that catches the thrown error. --- ## Type Alias: JobErrorStatisticsFilter ```ts type JobErrorStatisticsFilter = object; ``` Job error statistics search filter. ## Properties ### errorCode? ```ts optional errorCode?: StringFilterProperty; ``` Optional error code filter with advanced search capabilities. --- ### errorMessage? ```ts optional errorMessage?: StringFilterProperty; ``` Optional error message filter with advanced search capabilities. --- ### from ```ts from: string; ``` Start of the time window to filter metrics. ISO 8601 date-time format. --- ### jobType ```ts jobType: string; ``` Job type to return error metrics for. --- ### to ```ts to: string; ``` End of the time window to filter metrics. ISO 8601 date-time format. --- ## Type Alias: JobErrorStatisticsItem ```ts type JobErrorStatisticsItem = object; ``` Aggregated error metrics for a single error type and message combination. ## Properties ### errorCode ```ts errorCode: string; ``` The error code identifier. --- ### errorMessage ```ts errorMessage: string; ``` The error message. --- ### workers ```ts workers: number; ``` Number of distinct workers that encountered this error. --- ## Type Alias: JobErrorStatisticsQuery ```ts type JobErrorStatisticsQuery = object; ``` Job error statistics query. ## Properties ### filter ```ts filter: JobErrorStatisticsFilter; ``` --- ### page? ```ts optional page?: CursorForwardPagination; ``` Search cursor pagination. --- ## Type Alias: JobErrorStatisticsQueryResult ```ts type JobErrorStatisticsQueryResult = SearchQueryResponse & object; ``` Job error statistics query result. ## Type Declaration ### items ```ts items: JobErrorStatisticsItem[]; ``` The list of per-error statistics items. --- ## Type Alias: JobFailRequest ```ts type JobFailRequest = object; ``` ## Properties ### errorMessage? ```ts optional errorMessage?: string; ``` An optional error message describing why the job failed; if not provided, an empty string is used. --- ### jobLeaseToken? ```ts optional jobLeaseToken?: JobLeaseToken | null; ``` The token identifying a leased job's activation, obtained from `ActivatedJobResult.jobLeaseToken`. For a leased job, the matching token must be supplied to prove the command comes from the worker that holds the current lease; a command with no token is rejected. A command carrying a stale token is likewise rejected, fencing the job against a superseded activation (for example, after the job timed out or failed and was re-activated by another worker). A job that was activated without a lease requires no token. --- ### retries? ```ts optional retries?: number; ``` The amount of retries the job should have left --- ### retryBackOff? ```ts optional retryBackOff?: number; ``` An optional retry back off for the failed job. The job will not be retryable before the current time plus the back off time. The default is 0 which means the job is retryable immediately. --- ### variables? ```ts optional variables?: object; ``` JSON object that will instantiate the variables at the local scope of the job's associated task. #### Index Signature ```ts [key: string]: unknown ``` --- ## Type Alias: JobFilter ```ts type JobFilter = object; ``` Job search filter. ## Properties ### creationTime? ```ts optional creationTime?: DateTimeFilterProperty; ``` When the job was created. Field is present for jobs created after 8.9. --- ### deadline? ```ts optional deadline?: DateTimeFilterProperty | null; ``` When the job can next be activated. --- ### deniedReason? ```ts optional deniedReason?: StringFilterProperty; ``` The reason provided by the user task listener for denying the work. --- ### elementId? ```ts optional elementId?: StringFilterProperty; ``` The element ID associated with the job. --- ### elementInstanceKey? ```ts optional elementInstanceKey?: ElementInstanceKeyFilterProperty; ``` The element instance key associated with the job. --- ### endTime? ```ts optional endTime?: DateTimeFilterProperty; ``` When the job ended. --- ### errorCode? ```ts optional errorCode?: StringFilterProperty; ``` The error code provided for the failed job. --- ### errorMessage? ```ts optional errorMessage?: StringFilterProperty; ``` The error message that provides additional context for a failed job. --- ### hasFailedWithRetriesLeft? ```ts optional hasFailedWithRetriesLeft?: boolean; ``` Indicates whether the job has failed with retries left. --- ### isDenied? ```ts optional isDenied?: boolean | null; ``` Indicates whether the user task listener denies the work. --- ### jobKey? ```ts optional jobKey?: JobKeyFilterProperty; ``` The key, a unique identifier for the job. --- ### kind? ```ts optional kind?: JobKindFilterProperty; ``` The kind of the job. --- ### lastUpdateTime? ```ts optional lastUpdateTime?: DateTimeFilterProperty; ``` When the job was last updated. Field is present for jobs created after 8.9. --- ### listenerEventType? ```ts optional listenerEventType?: JobListenerEventTypeFilterProperty; ``` The listener event type of the job. --- ### priority? ```ts optional priority?: IntegerFilterProperty; ``` The priority of the job. Jobs created before 8.10 have no stored priority and are excluded from results when this filter is applied. --- ### processDefinitionId? ```ts optional processDefinitionId?: StringFilterProperty; ``` The process definition ID associated with the job. --- ### processDefinitionKey? ```ts optional processDefinitionKey?: ProcessDefinitionKeyFilterProperty; ``` The process definition key associated with the job. --- ### processInstanceKey? ```ts optional processInstanceKey?: ProcessInstanceKeyFilterProperty; ``` The process instance key associated with the job. --- ### retries? ```ts optional retries?: IntegerFilterProperty; ``` The number of retries left. --- ### state? ```ts optional state?: JobStateFilterProperty; ``` The state of the job. --- ### tenantId? ```ts optional tenantId?: StringFilterProperty; ``` The tenant ID. --- ### type? ```ts optional type?: StringFilterProperty; ``` The type of the job. --- ### worker? ```ts optional worker?: StringFilterProperty; ``` The name of the worker for this job. --- ## Type Alias: JobKey ```ts type JobKey = CamundaKey<"JobKey">; ``` System-generated key for a job. --- ## Type Alias: JobKeyExactMatch ```ts type JobKeyExactMatch = JobKey; ``` Exact match Matches the value exactly. --- ## Type Alias: JobKeyExactMatchWritable ```ts type JobKeyExactMatchWritable = JobKeyWritable; ``` Exact match Matches the value exactly. --- ## Type Alias: JobKeyFilterProperty ```ts type JobKeyFilterProperty = JobKeyExactMatch | AdvancedJobKeyFilter; ``` JobKey property with full advanced search capabilities. --- ## Type Alias: JobKeyWritable ```ts type JobKeyWritable = LongKey; ``` System-generated key for a job. --- ## Type Alias: JobKindEnum ```ts type JobKindEnum = (typeof JobKindEnum)[keyof typeof JobKindEnum]; ``` The job kind. --- ## Type Alias: JobKindExactMatch ```ts type JobKindExactMatch = JobKindEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: JobKindExactMatchWritable ```ts type JobKindExactMatchWritable = JobKindEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: JobKindFilterProperty ```ts type JobKindFilterProperty = JobKindExactMatch | AdvancedJobKindFilter; ``` JobKindEnum property with full advanced search capabilities. --- ## Type Alias: JobLeaseToken ```ts type JobLeaseToken = CamundaKey<"JobLeaseToken">; ``` An opaque, engine-minted fencing token identifying a single activation of a job. Returned by Activate Jobs as `ActivatedJobResult.jobLeaseToken` when the job is activated with a lease, and passed back under the same name on fenced job commands and on agent-instance creation/updates, to prove the caller holds the current lease. The token is opaque: clients may rely on its presence and equality only, and must never construct, parse, or otherwise interpret it beyond equality checks. It cannot be minted client-side; only the engine produces it, exactly once per leased activation, and clients must not depend on any particular internal format. --- ## Type Alias: JobListenerEventTypeEnum ```ts type JobListenerEventTypeEnum = (typeof JobListenerEventTypeEnum)[keyof typeof JobListenerEventTypeEnum]; ``` The listener event type of the job. --- ## Type Alias: JobListenerEventTypeExactMatch ```ts type JobListenerEventTypeExactMatch = JobListenerEventTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: JobListenerEventTypeExactMatchWritable ```ts type JobListenerEventTypeExactMatchWritable = JobListenerEventTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: JobListenerEventTypeFilterProperty ```ts type JobListenerEventTypeFilterProperty = JobListenerEventTypeExactMatch | AdvancedJobListenerEventTypeFilter; ``` JobListenerEventTypeEnum property with full advanced search capabilities. --- ## Type Alias: JobMetricsConfigurationResponse ```ts type JobMetricsConfigurationResponse = object; ``` Configuration for job metrics collection and export. ## Properties ### enabled ```ts enabled: boolean; ``` Whether job metrics export is enabled. --- ### exportInterval ```ts exportInterval: string; ``` The interval at which job metrics are exported, as an ISO 8601 duration. --- ### maxJobTypeLength ```ts maxJobTypeLength: number; ``` The maximum length of the job type used in job metrics labels. --- ### maxTenantIdLength ```ts maxTenantIdLength: number; ``` The maximum length of the tenant ID used in job metrics labels. --- ### maxUniqueKeys ```ts maxUniqueKeys: number; ``` The maximum number of unique metric keys tracked for job metrics. --- ### maxWorkerNameLength ```ts maxWorkerNameLength: number; ``` The maximum length of the worker name used in job metrics labels. --- ## Type Alias: JobResult ```ts type JobResult = (object & JobResultUserTask) | (object & JobResultAdHocSubProcess); ``` The result of the completed job as determined by the worker. --- ## Type Alias: JobResultActivateElement ```ts type JobResultActivateElement = object; ``` Instruction to activate a single BPMN element within an ad‑hoc sub‑process, optionally providing variables scoped to that element. ## Properties ### elementId? ```ts optional elementId?: ElementId; ``` The element ID to activate. --- ### variables? ```ts optional variables?: | { [key: string]: unknown; } | null; ``` Variables for the element. --- ## Type Alias: JobResultAdHocSubProcess ```ts type JobResultAdHocSubProcess = { activateElements?: JobResultActivateElement[]; isCancelRemainingInstances?: boolean; isCompletionConditionFulfilled?: boolean; type?: string; } | null; ``` Job result details for an ad‑hoc sub‑process, including elements to activate and flags indicating completion or cancellation behavior. ## Union Members ### Type Literal ```ts { activateElements?: JobResultActivateElement[]; isCancelRemainingInstances?: boolean; isCompletionConditionFulfilled?: boolean; type?: string; } ``` #### activateElements? ```ts optional activateElements?: JobResultActivateElement[]; ``` Indicates which elements need to be activated in the ad-hoc subprocess. #### isCancelRemainingInstances? ```ts optional isCancelRemainingInstances?: boolean; ``` Indicates whether the remaining instances of the ad-hoc subprocess should be canceled. #### isCompletionConditionFulfilled? ```ts optional isCompletionConditionFulfilled?: boolean; ``` Indicates whether the completion condition of the ad-hoc subprocess is fulfilled. #### type? ```ts optional type?: string; ``` Used to distinguish between different types of job results. --- `null` --- ## Type Alias: JobResultCorrections ```ts type JobResultCorrections = { assignee?: string | null; candidateGroups?: string[] | null; candidateUsers?: string[] | null; dueDate?: string | null; followUpDate?: string | null; priority?: number | null; } | null; ``` JSON object with attributes that were corrected by the worker. The following attributes can be corrected, additional attributes will be ignored: - `assignee` - clear by providing an empty String - `dueDate` - clear by providing an empty String - `followUpDate` - clear by providing an empty String - `candidateGroups` - clear by providing an empty list - `candidateUsers` - clear by providing an empty list - `priority` - minimum 0, maximum 100, default 50 Providing any of those attributes with a `null` value or omitting it preserves the persisted attribute's value. ## Union Members ### Type Literal ```ts { assignee?: string | null; candidateGroups?: string[] | null; candidateUsers?: string[] | null; dueDate?: string | null; followUpDate?: string | null; priority?: number | null; } ``` #### assignee? ```ts optional assignee?: string | null; ``` Assignee of the task. #### candidateGroups? ```ts optional candidateGroups?: string[] | null; ``` The list of candidate groups of the task. #### candidateUsers? ```ts optional candidateUsers?: string[] | null; ``` The list of candidate users of the task. #### dueDate? ```ts optional dueDate?: string | null; ``` The due date of the task. #### followUpDate? ```ts optional followUpDate?: string | null; ``` The follow-up date of the task. #### priority? ```ts optional priority?: number | null; ``` The priority of the task. --- `null` --- ## Type Alias: JobResultUserTask ```ts type JobResultUserTask = { corrections?: JobResultCorrections; denied?: boolean | null; deniedReason?: string | null; type?: string; } | null; ``` Job result details for a user task completion, optionally including a denial reason and corrected task properties. ## Union Members ### Type Literal ```ts { corrections?: JobResultCorrections; denied?: boolean | null; deniedReason?: string | null; type?: string; } ``` #### corrections? ```ts optional corrections?: JobResultCorrections; ``` #### denied? ```ts optional denied?: boolean | null; ``` Indicates whether the worker denies the work, i.e. explicitly doesn't approve it. For example, a user task listener can deny the completion of a task by setting this flag to true. In this example, the completion of a task is represented by a job that the worker can complete as denied. As a result, the completion request is rejected and the task remains active. Defaults to false. #### deniedReason? ```ts optional deniedReason?: string | null; ``` The reason provided by the user task listener for denying the work. #### type? ```ts optional type?: string; ``` Used to distinguish between different types of job results. --- `null` --- ## Type Alias: JobSearchQuery ```ts type JobSearchQuery = SearchQueryRequest & object; ``` Job search request. ## Type Declaration ### filter? ```ts optional filter?: JobFilter; ``` The job search filters. ### sort? ```ts optional sort?: JobSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: JobSearchQueryResult ```ts type JobSearchQueryResult = SearchQueryResponse & object; ``` Job search response. ## Type Declaration ### items ```ts items: JobSearchResult[]; ``` The matching jobs. --- ## Type Alias: JobSearchQuerySortRequest ```ts type JobSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "creationTime" | "deadline" | "deniedReason" | "elementId" | "elementInstanceKey" | "endTime" | "errorCode" | "errorMessage" | "hasFailedWithRetriesLeft" | "isDenied" | "jobKey" | "kind" | "listenerEventType" | "priority" | "processDefinitionId" | "processDefinitionKey" | "processInstanceKey" | "retries" | "state" | "tenantId" | "type" | "worker"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: JobSearchResult ```ts type JobSearchResult = object; ``` ## Properties ### businessId ```ts businessId: BusinessId | null; ``` The business ID of the owning process instance, inherited when the job was created. This is `null` for jobs created before version 8.10 and for jobs whose owning process instance has no business ID. --- ### creationTime ```ts creationTime: string | null; ``` When the job was created. Field is present for jobs created after 8.9. --- ### customHeaders ```ts customHeaders: object; ``` A set of custom headers defined during modelling. #### Index Signature ```ts [key: string]: string ``` --- ### deadline ```ts deadline: string | null; ``` If the job has been activated, when it will next be available to be activated. --- ### deniedReason ```ts deniedReason: string | null; ``` The reason provided by the user task listener for denying the work. --- ### elementId ```ts elementId: ElementId; ``` The element ID associated with the job. --- ### elementInstanceKey ```ts elementInstanceKey: ElementInstanceKey; ``` The element instance key associated with the job. --- ### endTime ```ts endTime: string | null; ``` End date of the job. This is `null` if the job is not in an end state yet. --- ### errorCode ```ts errorCode: string | null; ``` The error code provided for a failed job. --- ### errorMessage ```ts errorMessage: string | null; ``` The error message that provides additional context for a failed job. --- ### hasFailedWithRetriesLeft ```ts hasFailedWithRetriesLeft: boolean; ``` Indicates whether the job has failed with retries left. --- ### isDenied ```ts isDenied: boolean | null; ``` Indicates whether the user task listener denies the work. --- ### jobKey ```ts jobKey: JobKey; ``` The key, a unique identifier for the job. --- ### kind ```ts kind: JobKindEnum; ``` --- ### lastUpdateTime ```ts lastUpdateTime: string | null; ``` When the job was last updated. Field is present for jobs created after 8.9. --- ### listenerEventType ```ts listenerEventType: JobListenerEventTypeEnum; ``` --- ### priority ```ts priority: number; ``` The priority of the job. Higher values indicate higher priority. Jobs created before 8.10 have no stored priority; they appear last when sorting by this field and are excluded when filtering by this field. The API returns 0 for such jobs. --- ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId; ``` The process definition ID associated with the job. --- ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey; ``` The process definition key associated with the job. --- ### processInstanceKey ```ts processInstanceKey: ProcessInstanceKey; ``` The process instance key associated with the job. --- ### retries ```ts retries: number; ``` The amount of retries left to this job. --- ### rootProcessInstanceKey ```ts rootProcessInstanceKey: ProcessInstanceKey | null; ``` The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. --- ### state ```ts state: JobStateEnum; ``` --- ### tenantId ```ts tenantId: TenantId; ``` --- ### type ```ts type: string; ``` The type of the job. --- ### worker ```ts worker: string; ``` The name of the worker of this job. --- ## Type Alias: JobStateEnum ```ts type JobStateEnum = (typeof JobStateEnum)[keyof typeof JobStateEnum]; ``` The state of the job. --- ## Type Alias: JobStateExactMatch ```ts type JobStateExactMatch = JobStateEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: JobStateExactMatchWritable ```ts type JobStateExactMatchWritable = JobStateEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: JobStateFilterProperty ```ts type JobStateFilterProperty = JobStateExactMatch | AdvancedJobStateFilter; ``` JobStateEnum property with full advanced search capabilities. --- ## Type Alias: JobTimeSeriesStatisticsFilter ```ts type JobTimeSeriesStatisticsFilter = object; ``` Job time-series statistics search filter. ## Properties ### from ```ts from: string; ``` Start of the time window to filter metrics. ISO 8601 date-time format. --- ### jobType ```ts jobType: string; ``` Job type to return time-series metrics for. --- ### resolution? ```ts optional resolution?: string; ``` Time bucket resolution as an ISO 8601 duration (for example `PT1M` for 1 minute, `PT1H` for 1 hour). If omitted, the server chooses a sensible default. --- ### to ```ts to: string; ``` End of the time window to filter metrics. ISO 8601 date-time format. --- ## Type Alias: JobTimeSeriesStatisticsItem ```ts type JobTimeSeriesStatisticsItem = object; ``` Aggregated job metrics for a single time bucket. ## Properties ### completed ```ts completed: StatusMetric; ``` --- ### created ```ts created: StatusMetric; ``` --- ### failed ```ts failed: StatusMetric; ``` --- ### time ```ts time: string; ``` ISO 8601 timestamp representing the start of this time bucket. --- ## Type Alias: JobTimeSeriesStatisticsQuery ```ts type JobTimeSeriesStatisticsQuery = object; ``` Job time-series statistics query. ## Properties ### filter ```ts filter: JobTimeSeriesStatisticsFilter; ``` --- ### page? ```ts optional page?: CursorForwardPagination; ``` Search cursor pagination. --- ## Type Alias: JobTimeSeriesStatisticsQueryResult ```ts type JobTimeSeriesStatisticsQueryResult = SearchQueryResponse & object; ``` Job time-series statistics query result. ## Type Declaration ### items ```ts items: JobTimeSeriesStatisticsItem[]; ``` The list of time-bucketed statistics items, ordered ascending by time. --- ## Type Alias: JobTypeStatisticsFilter ```ts type JobTypeStatisticsFilter = object; ``` Job type statistics search filter. ## Properties ### from ```ts from: string; ``` Start of the time window to filter metrics. ISO 8601 date-time format. --- ### jobType? ```ts optional jobType?: StringFilterProperty; ``` Optional job type filter with advanced search capabilities. Supports exact match, pattern matching, and other operators. --- ### to ```ts to: string; ``` End of the time window to filter metrics. ISO 8601 date-time format. --- ## Type Alias: JobTypeStatisticsItem ```ts type JobTypeStatisticsItem = object; ``` Statistics for a single job type. ## Properties ### completed ```ts completed: StatusMetric; ``` --- ### created ```ts created: StatusMetric; ``` --- ### failed ```ts failed: StatusMetric; ``` --- ### jobType ```ts jobType: string; ``` The job type identifier. --- ### workers ```ts workers: number; ``` Number of distinct workers observed for this job type. --- ## Type Alias: JobTypeStatisticsQuery ```ts type JobTypeStatisticsQuery = object; ``` Job type statistics query. ## Properties ### filter? ```ts optional filter?: JobTypeStatisticsFilter; ``` --- ### page? ```ts optional page?: CursorForwardPagination; ``` Search cursor pagination. --- ## Type Alias: JobTypeStatisticsQueryResult ```ts type JobTypeStatisticsQueryResult = SearchQueryResponse & object; ``` Job type statistics query result. ## Type Declaration ### items ```ts items: JobTypeStatisticsItem[]; ``` The list of job type statistics items. --- ## Type Alias: JobUpdateRequest ```ts type JobUpdateRequest = object; ``` ## Properties ### changeset ```ts changeset: JobChangeset; ``` --- ### jobLeaseToken? ```ts optional jobLeaseToken?: JobLeaseToken | null; ``` The token identifying a leased job's activation, obtained from `ActivatedJobResult.jobLeaseToken`. For a leased job, a supplied token is validated to prove the command comes from the worker that holds the current lease; a command carrying a stale token is rejected, fencing the job against a superseded activation (for example, after the job timed out or failed and was re-activated by another worker). An update without a token always applies to support operator and bulk updates of leased jobs. Note that this is different from lifecycle requests like complete, fail, and throw-error that always require a token for leased jobs. A job that was activated without a lease requires no token. --- ### operationReference? ```ts optional operationReference?: OperationReference; ``` --- ## Type Alias: JobWaitStateDetails ```ts type JobWaitStateDetails = BaseWaitStateDetails & object; ``` ## Type Declaration ### jobKey ```ts jobKey: JobKey; ``` The key of the job. ### jobKind ```ts jobKind: JobKindEnum; ``` The kind of job. ### jobType ```ts jobType: string; ``` The job type (worker subscription identifier). ### listenerEventType ```ts listenerEventType: JobListenerEventTypeEnum | null; ``` The listener event type of the job (only set for execution listener and task listener jobs). ### retries ```ts retries: number | null; ``` The number of retries remaining for the job. --- ## Type Alias: JobWorkerStatisticsFilter ```ts type JobWorkerStatisticsFilter = object; ``` Job worker statistics search filter. ## Properties ### from ```ts from: string; ``` Start of the time window to filter metrics. ISO 8601 date-time format. --- ### jobType ```ts jobType: string; ``` Job type to return worker metrics for. --- ### to ```ts to: string; ``` End of the time window to filter metrics. ISO 8601 date-time format. --- ## Type Alias: JobWorkerStatisticsItem ```ts type JobWorkerStatisticsItem = object; ``` Statistics for a single worker within a job type. ## Properties ### completed ```ts completed: StatusMetric; ``` --- ### created ```ts created: StatusMetric; ``` --- ### failed ```ts failed: StatusMetric; ``` --- ### worker ```ts worker: string; ``` The name of the worker activating the jobs, mostly used for logging purposes. --- ## Type Alias: JobWorkerStatisticsQuery ```ts type JobWorkerStatisticsQuery = object; ``` Job worker statistics query. ## Properties ### filter ```ts filter: JobWorkerStatisticsFilter; ``` --- ### page? ```ts optional page?: CursorForwardPagination; ``` Search cursor pagination. --- ## Type Alias: JobWorkerStatisticsQueryResult ```ts type JobWorkerStatisticsQueryResult = SearchQueryResponse & object; ``` Job worker statistics query result. ## Type Declaration ### items ```ts items: JobWorkerStatisticsItem[]; ``` The list of per-worker statistics items. --- ## Type Alias: LicenseResponse ```ts type LicenseResponse = object; ``` The response of a license request. ## Properties ### expiresAt ```ts expiresAt: string | null; ``` The date when the Camunda license expires --- ### isCommercial ```ts isCommercial: boolean; ``` Will be false when a license contains a non-commerical=true property --- ### licenseType ```ts licenseType: string; ``` Will return the license type property of the Camunda license --- ### validLicense ```ts validLicense: boolean; ``` True if the Camunda license is valid, false if otherwise --- ## Type Alias: LikeFilter ```ts type LikeFilter = string; ``` Checks if the property matches the provided like value. Supported wildcard characters are: - `*`: matches zero, one, or multiple characters. - `?`: matches one, single character. Wildcard characters can be escaped with backslash, for instance: `\*`. --- ## Type Alias: LimitPagination ```ts type LimitPagination = object; ``` Limit-based pagination ## Properties ### limit? ```ts optional limit?: number; ``` The maximum number of items to return in one request. --- ## Type Alias: ListHistoryBackupsAsClusterAdminData ```ts type ListHistoryBackupsAsClusterAdminData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: object; ``` #### physicalTenantId? ```ts optional physicalTenantId?: string; ``` The physical tenant to apply the change to. When omitted, or when passed with an empty value, the change is applied to every physical tenant of the cluster. #### prefix? ```ts optional prefix?: BackupIdPrefix; ``` A prefix that backup ids must match, ending in a single '*'. If omitted, all backups are returned. #### verbose? ```ts optional verbose?: boolean; ``` Whether to ask the secondary storage for snapshot-level detail. Setting this to `false` makes the query cheaper, but the store then reports neither snapshot state nor start time, so both the per-snapshot `details` and the per-tenant `state` are incomplete and the listing order is unspecified. --- ### url ```ts url: "/cluster/v2/backups/history"; ``` --- ## Type Alias: ListHistoryBackupsAsClusterAdminError ```ts type ListHistoryBackupsAsClusterAdminError = ListHistoryBackupsAsClusterAdminErrors[keyof ListHistoryBackupsAsClusterAdminErrors]; ``` --- ## Type Alias: ListHistoryBackupsAsClusterAdminErrors ```ts type ListHistoryBackupsAsClusterAdminErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` The cluster's secondary storage is neither Elasticsearch nor OpenSearch and therefore cannot serve history backups, or a targeted physical tenant's snapshot repository is absent from the store — configured under a name the store does not have, or not configured at all. Both are deployment faults the caller cannot correct by changing its request; narrow the request with `physicalTenantId` to work with the tenants whose repository is usable. Unlike the per-physical-tenant backup endpoints, the cluster-admin surface performs no fine-grained authorization, so a missing `BACKUP` permission is never the reason. --- ### 404 ```ts 404: ProblemDetail; ``` The requested `physicalTenantId` does not exist in this cluster. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: ListHistoryBackupsAsClusterAdminResponse ```ts type ListHistoryBackupsAsClusterAdminResponse = ListHistoryBackupsAsClusterAdminResponses[keyof ListHistoryBackupsAsClusterAdminResponses]; ``` --- ## Type Alias: ListHistoryBackupsAsClusterAdminResponses ```ts type ListHistoryBackupsAsClusterAdminResponses = object; ``` ## Properties ### 200 ```ts 200: ClusterHistoryBackupInfo[]; ``` The history backups of every targeted physical tenant, grouped by backup id and ordered by backup id, descending. Deliberately not the per-physical-tenant endpoint's order, which is by snapshot start time: start times are per tenant, so a group spanning several tenants has no single one to sort on. Descending id is only recency for ids that ascend with time. Empty when every targeted tenant was read and none of them holds a matching backup. --- ## Type Alias: ListHistoryBackupsData ```ts type ListHistoryBackupsData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: object; ``` #### prefix? ```ts optional prefix?: BackupIdPrefix; ``` A prefix that backup ids must match, ending in a single '*'. If omitted, all backups are returned. #### verbose? ```ts optional verbose?: boolean; ``` Whether to ask the secondary storage for snapshot-level detail. Setting this to `false` makes the query cheaper, but the store then reports neither snapshot state nor start time, so both the per-snapshot `details` and the aggregated `state` are incomplete and the listing order is unspecified. --- ### url ```ts url: "/backups/history"; ``` --- ## Type Alias: ListHistoryBackupsError ```ts type ListHistoryBackupsError = ListHistoryBackupsErrors[keyof ListHistoryBackupsErrors]; ``` --- ## Type Alias: ListHistoryBackupsErrors ```ts type ListHistoryBackupsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` The request is forbidden for one of three reasons: the authenticated caller lacks the required `BACKUP` permission; the cluster's secondary storage is neither Elasticsearch nor OpenSearch and therefore cannot serve history backups; or the physical tenant's snapshot repository is absent from the store — configured under a name the store does not have, or not configured at all. The problem detail says which applies. The latter two are deployment faults the caller cannot correct by changing its request. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: ListHistoryBackupsResponse ```ts type ListHistoryBackupsResponse = ListHistoryBackupsResponses[keyof ListHistoryBackupsResponses]; ``` --- ## Type Alias: ListHistoryBackupsResponses ```ts type ListHistoryBackupsResponses = object; ``` ## Properties ### 200 ```ts 200: HistoryBackupInfo[]; ``` The list of history backups. --- ## Type Alias: ListRuntimeBackupsAsClusterAdminData ```ts type ListRuntimeBackupsAsClusterAdminData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: object; ``` #### physicalTenantId? ```ts optional physicalTenantId?: string; ``` The physical tenant to apply the change to. When omitted, or when passed with an empty value, the change is applied to every physical tenant of the cluster. #### prefix? ```ts optional prefix?: BackupIdPrefix; ``` A prefix that backup ids must match, ending in a single '*'. If omitted, all backups are returned. --- ### url ```ts url: "/cluster/v2/backups/runtime"; ``` --- ## Type Alias: ListRuntimeBackupsAsClusterAdminError ```ts type ListRuntimeBackupsAsClusterAdminError = ListRuntimeBackupsAsClusterAdminErrors[keyof ListRuntimeBackupsAsClusterAdminErrors]; ``` --- ## Type Alias: ListRuntimeBackupsAsClusterAdminErrors ```ts type ListRuntimeBackupsAsClusterAdminErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 404 ```ts 404: ProblemDetail; ``` The requested `physicalTenantId` does not exist in this cluster. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: ListRuntimeBackupsAsClusterAdminResponse ```ts type ListRuntimeBackupsAsClusterAdminResponse = ListRuntimeBackupsAsClusterAdminResponses[keyof ListRuntimeBackupsAsClusterAdminResponses]; ``` --- ## Type Alias: ListRuntimeBackupsAsClusterAdminResponses ```ts type ListRuntimeBackupsAsClusterAdminResponses = object; ``` ## Properties ### 200 ```ts 200: ClusterRuntimeBackupInfo[]; ``` The runtime backups of every targeted physical tenant, grouped by backup id and sorted in descending order of backup id, as the per-physical-tenant listing is. Empty when every targeted tenant was read and none of them holds a matching backup. --- ## Type Alias: ListRuntimeBackupsData ```ts type ListRuntimeBackupsData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: object; ``` #### prefix? ```ts optional prefix?: BackupIdPrefix; ``` A prefix that backup ids must match, ending in a single '*'. If omitted, all backups are returned. --- ### url ```ts url: "/backups/runtime"; ``` --- ## Type Alias: ListRuntimeBackupsError ```ts type ListRuntimeBackupsError = ListRuntimeBackupsErrors[keyof ListRuntimeBackupsErrors]; ``` --- ## Type Alias: ListRuntimeBackupsErrors ```ts type ListRuntimeBackupsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: ListRuntimeBackupsResponse ```ts type ListRuntimeBackupsResponse = ListRuntimeBackupsResponses[keyof ListRuntimeBackupsResponses]; ``` --- ## Type Alias: ListRuntimeBackupsResponses ```ts type ListRuntimeBackupsResponses = object; ``` ## Properties ### 200 ```ts 200: BackupInfo[]; ``` The list of runtime backups. --- ## Type Alias: ListSecretsData ```ts type ListSecretsData = object; ``` ## Properties ### body? ```ts optional body?: SecretListRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/secrets/list"; ``` --- ## Type Alias: ListSecretsError ```ts type ListSecretsError = ListSecretsErrors[keyof ListSecretsErrors]; ``` --- ## Type Alias: ListSecretsErrors ```ts type ListSecretsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: ListSecretsResponse ```ts type ListSecretsResponse = ListSecretsResponses[keyof ListSecretsResponses]; ``` --- ## Type Alias: ListSecretsResponses ```ts type ListSecretsResponses = object; ``` ## Properties ### 200 ```ts 200: SecretListResult; ``` The references the caller is authorized to see. --- ## Type Alias: LongKey ```ts type LongKey = string; ``` Zeebe Engine resource key (Java long serialized as string) --- ## Type Alias: LoopIterationId ```ts type LoopIterationId = number; ``` A client-provided sequential integer identifying a loop iteration: one pass through an AI agent's loop, during which the model reasons, selects tools, evaluates the result, and decides whether to continue. One iteration covers the input for the LLM call, the call itself, and the tools it dispatches; the results of those tool calls are input to the next iteration. Must be a positive integer, increasing with each loopIteration. Established by the connector when appending the first history item of a loopIteration. --- ## Type Alias: Loose # Type Alias: Loose\ ```ts type Loose = IsBrandedKey extends true ? string : T extends CancelablePromise ? CancelablePromise : T extends Promise ? Promise : T extends infer U[] ? Loose[] : T extends ReadonlyArray ? ReadonlyArray : T extends (...a) => infer R ? (...a) => Loose : T extends object ? { [K in keyof T]: Loose } : T; ``` ## Type Parameters ### T `T` --- ## Type Alias: MappingRuleCreateRequest ```ts type MappingRuleCreateRequest = MappingRuleCreateUpdateRequest & object; ``` ## Type Declaration ### mappingRuleId ```ts mappingRuleId: MappingRuleId; ``` The unique ID of the mapping rule. --- ## Type Alias: MappingRuleCreateResult ```ts type MappingRuleCreateResult = MappingRuleCreateUpdateResult; ``` --- ## Type Alias: MappingRuleCreateUpdateRequest ```ts type MappingRuleCreateUpdateRequest = object; ``` ## Properties ### claimName ```ts claimName: string; ``` The name of the claim to map. --- ### claimValue ```ts claimValue: string; ``` The value of the claim to map. --- ### name ```ts name: string; ``` The name of the mapping rule. --- ## Type Alias: MappingRuleCreateUpdateResult ```ts type MappingRuleCreateUpdateResult = object; ``` ## Properties ### claimName ```ts claimName: string; ``` The name of the claim to map. --- ### claimValue ```ts claimValue: string; ``` The value of the claim to map. --- ### mappingRuleId ```ts mappingRuleId: MappingRuleId; ``` The unique ID of the mapping rule. --- ### name ```ts name: string; ``` The name of the mapping rule. --- ## Type Alias: MappingRuleFilter ```ts type MappingRuleFilter = MappingRuleFilterFields & object; ``` Mapping rule search filter. ## Type Declaration ### $or? ```ts optional $or?: MappingRuleFilterFields[] | null; ``` Defines a list of alternative filter groups combined using OR logic. Each object in the array is evaluated independently, and the filter matches if any one of them is satisfied. Top-level fields and the `$or` clause are combined using AND logic — meaning: (top-level filters) AND (any of the `$or` filters) must match. _Example:_ ```json { "$or": [{ "mappingRuleId": "rule-1" }, { "mappingRuleId": "rule-2" }] } ``` This matches mapping rules whose `mappingRuleId` is _rule-1_ or _rule-2_. Note: Using complex `$or` conditions may impact performance, use with caution in high-volume environments. --- ## Type Alias: MappingRuleFilterFields ```ts type MappingRuleFilterFields = object; ``` Mapping rule search filter fields. ## Properties ### claimName? ```ts optional claimName?: string; ``` The claim name to match against a token. --- ### claimValue? ```ts optional claimValue?: string; ``` The value of the claim to match. --- ### mappingRuleId? ```ts optional mappingRuleId?: StringFilterProperty; ``` The ID of the mapping rule. --- ### name? ```ts optional name?: StringFilterProperty; ``` The name of the mapping rule. --- ## Type Alias: MappingRuleId ```ts type MappingRuleId = CamundaKey<"MappingRuleId">; ``` The unique identifier of a mapping rule. --- ## Type Alias: MappingRuleResult ```ts type MappingRuleResult = object; ``` ## Properties ### claimName ```ts claimName: string; ``` The name of the claim to map. --- ### claimValue ```ts claimValue: string; ``` The value of the claim to map. --- ### mappingRuleId ```ts mappingRuleId: MappingRuleId; ``` The ID of the mapping rule. --- ### name ```ts name: string; ``` The name of the mapping rule. --- ## Type Alias: MappingRuleSearchQueryRequest ```ts type MappingRuleSearchQueryRequest = SearchQueryRequest & object; ``` ## Type Declaration ### filter? ```ts optional filter?: MappingRuleFilter; ``` The mapping rule search filters. ### sort? ```ts optional sort?: MappingRuleSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: MappingRuleSearchQueryResult ```ts type MappingRuleSearchQueryResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: MappingRuleResult[]; ``` The matching mapping rules. --- ## Type Alias: MappingRuleSearchQuerySortRequest ```ts type MappingRuleSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: "mappingRuleId" | "claimName" | "claimValue" | "name"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: MappingRuleUpdateRequest ```ts type MappingRuleUpdateRequest = MappingRuleCreateUpdateRequest; ``` --- ## Type Alias: MappingRuleUpdateResult ```ts type MappingRuleUpdateResult = MappingRuleCreateUpdateResult; ``` --- ## Type Alias: MatchedDecisionRuleItem ```ts type MatchedDecisionRuleItem = object; ``` A decision rule that matched within this decision evaluation. ## Properties ### evaluatedOutputs ```ts evaluatedOutputs: EvaluatedDecisionOutputItem[]; ``` The evaluated decision outputs. --- ### ruleId ```ts ruleId: string; ``` The ID of the matched rule. --- ### ruleIndex ```ts ruleIndex: number; ``` The index of the matched rule. --- ## Type Alias: MessageCorrelationRequest ```ts type MessageCorrelationRequest = object; ``` ## Properties ### businessId? ```ts optional businessId?: BusinessId; ``` An optional business id used to enforce uniqueness of the process instance that a message start event would create. If provided and uniqueness enforcement is enabled, the engine rejects starting a new process instance when another root process instance with the same business id is already active for the same process definition. It has no effect when the message correlates to a catch, boundary, or intermediate event. --- ### correlationKey? ```ts optional correlationKey?: string; ``` The correlation key of the message. --- ### name ```ts name: string; ``` The message name as defined in the BPMN process --- ### tenantId? ```ts optional tenantId?: TenantId; ``` the tenant for which the message is published --- ### variables? ```ts optional variables?: object; ``` The message variables as JSON document #### Index Signature ```ts [key: string]: unknown ``` --- ## Type Alias: MessageCorrelationResult ```ts type MessageCorrelationResult = object; ``` The message key of the correlated message, as well as the first process instance key it correlated with. ## Properties ### messageKey ```ts messageKey: MessageKey; ``` The key of the correlated message. --- ### processInstanceKey ```ts processInstanceKey: ProcessInstanceKey; ``` The key of the first process instance the message correlated with --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID of the correlated message --- ## Type Alias: MessageKey ```ts type MessageKey = CamundaKey<"MessageKey">; ``` System-generated key for an message. --- ## Type Alias: MessageKeyWritable ```ts type MessageKeyWritable = LongKey; ``` System-generated key for an message. --- ## Type Alias: MessagePublicationRequest ```ts type MessagePublicationRequest = object; ``` ## Properties ### businessId? ```ts optional businessId?: BusinessId; ``` An optional business id used to enforce uniqueness of the process instance that a message start event would create. If provided and uniqueness enforcement is enabled, the engine rejects starting a new process instance when another root process instance with the same business id is already active for the same process definition. It has no effect when the message correlates to a catch, boundary, or intermediate event. --- ### correlationKey? ```ts optional correlationKey?: string; ``` The correlation key of the message. --- ### messageId? ```ts optional messageId?: string; ``` The unique ID of the message. This is used to ensure only one message with the given ID will be published during the lifetime of the message (if `timeToLive` is set). --- ### name ```ts name: string; ``` The name of the message. --- ### tenantId? ```ts optional tenantId?: TenantId; ``` The tenant of the message sender. --- ### timeToLive? ```ts optional timeToLive?: number; ``` Timespan (in ms) to buffer the message on the broker. --- ### variables? ```ts optional variables?: object; ``` The message variables as JSON document. #### Index Signature ```ts [key: string]: unknown ``` --- ## Type Alias: MessagePublicationResult ```ts type MessagePublicationResult = object; ``` The message key of the published message. ## Properties ### messageKey ```ts messageKey: MessageKey; ``` The key of the published message. --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID of the message. --- ## Type Alias: MessageSubscriptionFilter ```ts type MessageSubscriptionFilter = object; ``` Message subscription search filter. ## Properties ### businessId? ```ts optional businessId?: StringFilterProperty; ``` Filter by the business id inherited from the subscribing process instance when the subscription was opened. Supports advanced string filtering, including `$like` with `*`/`?` wildcards. --- ### correlationKey? ```ts optional correlationKey?: StringFilterProperty; ``` The correlation key of the message subscription. --- ### elementId? ```ts optional elementId?: StringFilterProperty; ``` The element ID associated with this message subscription. --- ### elementInstanceKey? ```ts optional elementInstanceKey?: ElementInstanceKeyFilterProperty; ``` The element instance key associated with this message subscription. --- ### inboundConnectorType? ```ts optional inboundConnectorType?: StringFilterProperty; ``` Filter by inbound connector type extracted from the `inbound.type` zeebe:property. --- ### lastUpdatedDate? ```ts optional lastUpdatedDate?: DateTimeFilterProperty; ``` The last updated date of the message subscription. --- ### messageName? ```ts optional messageName?: StringFilterProperty; ``` The name of the message associated with the message subscription. --- ### messageSubscriptionKey? ```ts optional messageSubscriptionKey?: MessageSubscriptionKeyFilterProperty; ``` The message subscription key associated with this message subscription. --- ### messageSubscriptionState? ```ts optional messageSubscriptionState?: MessageSubscriptionStateFilterProperty; ``` The message subscription state. --- ### messageSubscriptionType? ```ts optional messageSubscriptionType?: MessageSubscriptionTypeFilterProperty; ``` The type of message subscription to filter by. When omitted, both `START_EVENT` and `PROCESS_EVENT` are returned. Only available for data created with Camunda 8.10 or later. --- ### processDefinitionId? ```ts optional processDefinitionId?: StringFilterProperty; ``` The process definition ID associated with this message subscription. --- ### processDefinitionKey? ```ts optional processDefinitionKey?: ProcessDefinitionKeyFilterProperty; ``` The process definition key associated with this correlated message subscription. This only works for data created with 8.9 and later. --- ### processDefinitionName? ```ts optional processDefinitionName?: StringFilterProperty; ``` The name of the process definition associated with this message subscription. --- ### processDefinitionVersion? ```ts optional processDefinitionVersion?: IntegerFilterProperty; ``` The version of the process definition associated with this message subscription. --- ### processInstanceKey? ```ts optional processInstanceKey?: ProcessInstanceKeyFilterProperty; ``` The process instance key associated with this message subscription. --- ### tenantId? ```ts optional tenantId?: StringFilterProperty; ``` The unique external tenant ID. --- ### toolName? ```ts optional toolName?: StringFilterProperty; ``` Filter by tool name extracted from the `io.camunda.tool:name` zeebe:property. --- ## Type Alias: MessageSubscriptionKey ```ts type MessageSubscriptionKey = CamundaKey<"MessageSubscriptionKey">; ``` System-generated key for a message subscription. --- ## Type Alias: MessageSubscriptionKeyExactMatch ```ts type MessageSubscriptionKeyExactMatch = MessageSubscriptionKey; ``` Exact match Matches the value exactly. --- ## Type Alias: MessageSubscriptionKeyExactMatchWritable ```ts type MessageSubscriptionKeyExactMatchWritable = MessageSubscriptionKeyWritable; ``` Exact match Matches the value exactly. --- ## Type Alias: MessageSubscriptionKeyFilterProperty ```ts type MessageSubscriptionKeyFilterProperty = MessageSubscriptionKeyExactMatch | AdvancedMessageSubscriptionKeyFilter; ``` MessageSubscriptionKey property with full advanced search capabilities. --- ## Type Alias: MessageSubscriptionKeyWritable ```ts type MessageSubscriptionKeyWritable = LongKey; ``` System-generated key for a message subscription. --- ## Type Alias: MessageSubscriptionResult ```ts type MessageSubscriptionResult = object; ``` ## Properties ### businessId ```ts businessId: BusinessId | null; ``` The business id inherited from the subscribing process instance when this message subscription was opened. It is `null` when the process instance has no business id, and for message start event subscriptions, which are not tied to a process instance. --- ### correlationKey ```ts correlationKey: string | null; ``` The correlation key of the message subscription. --- ### elementId ```ts elementId: ElementId; ``` The element ID associated with this message subscription. --- ### elementInstanceKey ```ts elementInstanceKey: ElementInstanceKey | null; ``` The element instance key associated with this message subscription. Only populated for intermediate event entities. --- ### inboundConnectorType ```ts inboundConnectorType: string | null; ``` Inbound connector type extracted from the `inbound.type` zeebe:property. Null when the property is absent. --- ### lastUpdatedDate ```ts lastUpdatedDate: string; ``` The last updated date of the message subscription. --- ### messageName ```ts messageName: string; ``` The name of the message associated with the message subscription. --- ### messageSubscriptionKey ```ts messageSubscriptionKey: MessageSubscriptionKey; ``` The message subscription key associated with this message subscription. --- ### messageSubscriptionState ```ts messageSubscriptionState: MessageSubscriptionStateEnum; ``` --- ### messageSubscriptionType ```ts messageSubscriptionType: MessageSubscriptionTypeEnum; ``` --- ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId; ``` The process definition ID associated with this message subscription. --- ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey | null; ``` The process definition key associated with this message subscription. --- ### processDefinitionName ```ts processDefinitionName: string | null; ``` The name of the process definition associated with this message subscription. --- ### processDefinitionVersion ```ts processDefinitionVersion: number | null; ``` The version of the process definition associated with this message subscription. --- ### processInstanceKey ```ts processInstanceKey: ProcessInstanceKey | null; ``` The process instance key associated with this message subscription. Only populated for intermediate event entities. --- ### rootProcessInstanceKey ```ts rootProcessInstanceKey: ProcessInstanceKey | null; ``` The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. --- ### tenantId ```ts tenantId: TenantId; ``` --- ### toolName ```ts toolName: string | null; ``` Tool name extracted from the `io.camunda.tool:name` zeebe:property. Null when the property is absent. --- ### toolProperties ```ts toolProperties: object; ``` The subset of `zeebe:properties` extension properties whose keys start with the `io.camunda.tool:` prefix, extracted from the BPMN element associated with this subscription. Empty object when no matching properties are defined. #### Index Signature ```ts [key: string]: string ``` --- ## Type Alias: MessageSubscriptionSearchQuery ```ts type MessageSubscriptionSearchQuery = SearchQueryRequest & object; ``` ## Type Declaration ### filter? ```ts optional filter?: MessageSubscriptionFilter; ``` The incident search filters. ### sort? ```ts optional sort?: MessageSubscriptionSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: MessageSubscriptionSearchQueryResult ```ts type MessageSubscriptionSearchQueryResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: MessageSubscriptionResult[]; ``` The matching message subscriptions. --- ## Type Alias: MessageSubscriptionSearchQuerySortRequest ```ts type MessageSubscriptionSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "businessId" | "messageSubscriptionKey" | "processDefinitionId" | "processDefinitionName" | "processDefinitionVersion" | "processInstanceKey" | "elementId" | "elementInstanceKey" | "messageSubscriptionState" | "messageSubscriptionType" | "lastUpdatedDate" | "messageName" | "correlationKey" | "tenantId" | "toolName" | "inboundConnectorType"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: MessageSubscriptionStateEnum ```ts type MessageSubscriptionStateEnum = (typeof MessageSubscriptionStateEnum)[keyof typeof MessageSubscriptionStateEnum]; ``` The state of message subscription. **Note for `START_EVENT` subscriptions:** The `CORRELATED` and `MIGRATED` states are not tracked for these subscriptions. To query correlation history for process start events, use the `/correlated-message-subscriptions/search` endpoint. --- ## Type Alias: MessageSubscriptionStateExactMatch ```ts type MessageSubscriptionStateExactMatch = MessageSubscriptionStateEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: MessageSubscriptionStateExactMatchWritable ```ts type MessageSubscriptionStateExactMatchWritable = MessageSubscriptionStateEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: MessageSubscriptionStateFilterProperty ```ts type MessageSubscriptionStateFilterProperty = MessageSubscriptionStateExactMatch | AdvancedMessageSubscriptionStateFilter; ``` MessageSubscriptionStateEnum with full advanced search capabilities. --- ## Type Alias: MessageSubscriptionTypeEnum ```ts type MessageSubscriptionTypeEnum = (typeof MessageSubscriptionTypeEnum)[keyof typeof MessageSubscriptionTypeEnum]; ``` The type of message subscription. `START_EVENT` is definition-scoped (process start events). Always has a value; only captured from Camunda 8.10 onwards. `PROCESS_EVENT` is instance-scoped (intermediate catch events). Pre-8.10 entries have no value stored; the API returns `PROCESS_EVENT` as a default for those entries. --- ## Type Alias: MessageSubscriptionTypeExactMatch ```ts type MessageSubscriptionTypeExactMatch = MessageSubscriptionTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: MessageSubscriptionTypeExactMatchWritable ```ts type MessageSubscriptionTypeExactMatchWritable = MessageSubscriptionTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: MessageSubscriptionTypeFilterProperty ```ts type MessageSubscriptionTypeFilterProperty = MessageSubscriptionTypeExactMatch | AdvancedMessageSubscriptionTypeFilter; ``` MessageSubscriptionTypeEnum with full advanced search capabilities. --- ## Type Alias: MessageWaitStateDetails ```ts type MessageWaitStateDetails = BaseWaitStateDetails & object; ``` ## Type Declaration ### correlationKey ```ts correlationKey: string | null; ``` The correlation key for the message subscription (null for start events). ### messageName ```ts messageName: string; ``` The name of the message being awaited. --- ## Type Alias: MigrateProcessInstanceData ```ts type MigrateProcessInstanceData = object; ``` ## Properties ### body ```ts body: ProcessInstanceMigrationInstruction; ``` --- ### path ```ts path: object; ``` #### processInstanceKey ```ts processInstanceKey: ProcessInstanceKeyWritable; ``` The key of the process instance that should be migrated. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/{processInstanceKey}/migration"; ``` --- ## Type Alias: MigrateProcessInstanceError ```ts type MigrateProcessInstanceError = MigrateProcessInstanceErrors[keyof MigrateProcessInstanceErrors]; ``` --- ## Type Alias: MigrateProcessInstanceErrors ```ts type MigrateProcessInstanceErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 404 ```ts 404: ProblemDetail; ``` The process instance is not found. --- ### 409 ```ts 409: ProblemDetail; ``` The process instance migration failed. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: MigrateProcessInstanceMappingInstruction ```ts type MigrateProcessInstanceMappingInstruction = object; ``` The mapping instructions describe how to map elements from the source process definition to the target process definition. ## Properties ### sourceElementId ```ts sourceElementId: ElementId; ``` The element id to migrate from. --- ### targetElementId ```ts targetElementId: ElementId; ``` The element id to migrate into. --- ## Type Alias: MigrateProcessInstanceResponse ```ts type MigrateProcessInstanceResponse = MigrateProcessInstanceResponses[keyof MigrateProcessInstanceResponses]; ``` --- ## Type Alias: MigrateProcessInstanceResponses ```ts type MigrateProcessInstanceResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The process instance is migrated. --- ## Type Alias: MigrateProcessInstancesBatchOperationData ```ts type MigrateProcessInstancesBatchOperationData = object; ``` ## Properties ### body ```ts body: ProcessInstanceMigrationBatchOperationRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/migration"; ``` --- ## Type Alias: MigrateProcessInstancesBatchOperationError ```ts type MigrateProcessInstancesBatchOperationError = MigrateProcessInstancesBatchOperationErrors[keyof MigrateProcessInstancesBatchOperationErrors]; ``` --- ## Type Alias: MigrateProcessInstancesBatchOperationErrors ```ts type MigrateProcessInstancesBatchOperationErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The process instance batch operation failed. More details are provided in the response body. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: MigrateProcessInstancesBatchOperationResponse ```ts type MigrateProcessInstancesBatchOperationResponse = MigrateProcessInstancesBatchOperationResponses[keyof MigrateProcessInstancesBatchOperationResponses]; ``` --- ## Type Alias: MigrateProcessInstancesBatchOperationResponses ```ts type MigrateProcessInstancesBatchOperationResponses = object; ``` ## Properties ### 200 ```ts 200: BatchOperationCreatedResult; ``` The batch operation request was created. --- ## Type Alias: Mode ```ts type Mode = "PROCESSING" | "RECOVERING"; ``` The operating mode of a cluster's partitions. --- ## Type Alias: ModifyProcessInstanceData ```ts type ModifyProcessInstanceData = object; ``` ## Properties ### body ```ts body: ProcessInstanceModificationInstruction; ``` --- ### path ```ts path: object; ``` #### processInstanceKey ```ts processInstanceKey: ProcessInstanceKeyWritable; ``` The key of the process instance that should be modified. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/{processInstanceKey}/modification"; ``` --- ## Type Alias: ModifyProcessInstanceError ```ts type ModifyProcessInstanceError = ModifyProcessInstanceErrors[keyof ModifyProcessInstanceErrors]; ``` --- ## Type Alias: ModifyProcessInstanceErrors ```ts type ModifyProcessInstanceErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 404 ```ts 404: ProblemDetail; ``` The process instance is not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: ModifyProcessInstanceResponse ```ts type ModifyProcessInstanceResponse = ModifyProcessInstanceResponses[keyof ModifyProcessInstanceResponses]; ``` --- ## Type Alias: ModifyProcessInstanceResponses ```ts type ModifyProcessInstanceResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The process instance is modified. --- ## Type Alias: ModifyProcessInstanceVariableInstruction ```ts type ModifyProcessInstanceVariableInstruction = object; ``` Instruction describing which variables to create or update. ## Properties ### scopeId? ```ts optional scopeId?: string; ``` The id of the element in which scope the variables should be created. Leave empty to create the variables in the global scope of the process instance. --- ### variables ```ts variables: object; ``` JSON document that will instantiate the variables at the scope defined by the scopeId. It must be a JSON object, as variables will be mapped in a key-value fashion. #### Index Signature ```ts [key: string]: unknown ``` --- ## Type Alias: ModifyProcessInstancesBatchOperationData ```ts type ModifyProcessInstancesBatchOperationData = object; ``` ## Properties ### body ```ts body: ProcessInstanceModificationBatchOperationRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/modification"; ``` --- ## Type Alias: ModifyProcessInstancesBatchOperationError ```ts type ModifyProcessInstancesBatchOperationError = ModifyProcessInstancesBatchOperationErrors[keyof ModifyProcessInstancesBatchOperationErrors]; ``` --- ## Type Alias: ModifyProcessInstancesBatchOperationErrors ```ts type ModifyProcessInstancesBatchOperationErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The process instance batch operation failed. More details are provided in the response body. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: ModifyProcessInstancesBatchOperationResponse ```ts type ModifyProcessInstancesBatchOperationResponse = ModifyProcessInstancesBatchOperationResponses[keyof ModifyProcessInstancesBatchOperationResponses]; ``` --- ## Type Alias: ModifyProcessInstancesBatchOperationResponses ```ts type ModifyProcessInstancesBatchOperationResponses = object; ``` ## Properties ### 200 ```ts 200: BatchOperationCreatedResult; ``` The batch operation request was created. --- ## Type Alias: OffsetPagination ```ts type OffsetPagination = object; ``` Offset-based pagination ## Properties ### from? ```ts optional from?: number; ``` The index of items to start searching from. --- ### limit? ```ts optional limit?: number; ``` The maximum number of items to return in one request. --- ## Type Alias: OperationReference ```ts type OperationReference = number; ``` A reference key chosen by the user that will be part of all records resulting from this operation. Must be > 0 if provided. --- ## Type Alias: OperationTypeExactMatch ```ts type OperationTypeExactMatch = AuditLogOperationTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: OperationTypeExactMatchWritable ```ts type OperationTypeExactMatchWritable = AuditLogOperationTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: OperationTypeFilterProperty ```ts type OperationTypeFilterProperty = OperationTypeExactMatch | AdvancedOperationTypeFilter; ``` AuditLogOperationTypeEnum property with full advanced search capabilities. --- ## Type Alias: OwnAuthorizationSearchResult ```ts type OwnAuthorizationSearchResult = AuthorizationSearchResult & object; ``` ## Type Declaration ### authorizationsEnabled ```ts authorizationsEnabled: boolean; ``` Indicates whether authorization checks are enabled for the cluster. --- ## Type Alias: OwnerTypeEnum ```ts type OwnerTypeEnum = (typeof OwnerTypeEnum)[keyof typeof OwnerTypeEnum]; ``` The type of the owner of permissions. --- ## Type Alias: PaginationMode ```ts type PaginationMode = "auto" | "cursor" | "offset"; ``` --- ## Type Alias: Partition ```ts type Partition = object; ``` Provides information on a partition within a broker node. ## Properties ### health ```ts health: "healthy" | "unhealthy" | "dead"; ``` Describes the current health of the partition. --- ### partitionId ```ts partitionId: number; ``` The unique ID of this partition. --- ### role ```ts role: "leader" | "follower" | "inactive"; ``` Describes the Raft role of the broker for a given partition. --- ### state ```ts state: "unknown" | "joining" | "active" | "leaving" | "recovering" | "learner"; ``` Describes the current operational state of the partition within the cluster configuration. --- ## Type Alias: PartitionBackupInfo ```ts type PartitionBackupInfo = object; ``` Partition Backup Info Detailed info of the backup for a given partition. ## Properties ### brokerId ```ts readonly brokerId: number | null; ``` The id of the broker from which the backup was taken for this partition. --- ### brokerVersion ```ts readonly brokerVersion: string | null; ``` The version of the broker from which the backup was taken for this partition. --- ### checkpointPosition ```ts readonly checkpointPosition: number | null; ``` The position of the checkpoint for this backup. --- ### createdAt ```ts readonly createdAt: string | null; ``` The timestamp at which the backup was started on this partition. --- ### failureReason ```ts failureReason: string | null; ``` Failure reason if the state is 'FAILED'. --- ### firstLogPosition ```ts readonly firstLogPosition: number | null; ``` The first log position included in this backup. --- ### lastUpdatedAt ```ts readonly lastUpdatedAt: string | null; ``` The timestamp at which the backup was last updated on this partition, e.g. changed state from 'IN_PROGRESS' to 'COMPLETED'. --- ### partitionId ```ts partitionId: PartitionId; ``` The id of the partition. --- ### snapshotId ```ts readonly snapshotId: string | null; ``` The id of the snapshot which is included in this backup. --- ### state ```ts state: StateCode; ``` The state of the backup on this partition. --- ## Type Alias: PartitionBackupInfoWritable ```ts type PartitionBackupInfoWritable = object; ``` Partition Backup Info Detailed info of the backup for a given partition. ## Properties ### failureReason ```ts failureReason: string | null; ``` Failure reason if the state is 'FAILED'. --- ## Type Alias: PartitionBackupRange ```ts type PartitionBackupRange = object; ``` Partition Backup Range Information about one backup range for a partition. ## Properties ### end ```ts end: PartitionBackupState | null; ``` The newest backup in the range. --- ### partitionId ```ts partitionId: PartitionId; ``` The id of the partition. --- ### start ```ts start: PartitionBackupState | null; ``` The oldest backup in the range. --- ## Type Alias: PartitionBackupState ```ts type PartitionBackupState = object; ``` Partition Backup State Detailed information about the backup state for a given partition. ## Properties ### checkpointId ```ts checkpointId: CheckpointId; ``` The id of the checkpoint this backup is based on. --- ### checkpointPosition ```ts checkpointPosition: number; ``` The log position of the checkpoint this backup is based on. --- ### checkpointTimestamp ```ts checkpointTimestamp: string; ``` The timestamp at which the checkpoint was created. --- ### checkpointType ```ts checkpointType: BackupType; ``` The type of the backup. --- ### firstLogPosition ```ts firstLogPosition: number; ``` The first log position included in this backup. --- ### partitionId ```ts partitionId: PartitionId | null; ``` The id of the partition. Omitted when nested inside a backup range's `start`/`end`, where the partition is already identified by the enclosing range. --- ## Type Alias: PartitionCheckpointState ```ts type PartitionCheckpointState = object; ``` Partition Checkpoint State Detailed information about the checkpoint state for a given partition. ## Properties ### checkpointId ```ts checkpointId: CheckpointId; ``` The id of the checkpoint. --- ### checkpointPosition ```ts checkpointPosition: number; ``` The log position of the checkpoint. --- ### checkpointTimestamp ```ts checkpointTimestamp: string; ``` The timestamp at which the checkpoint was created. --- ### checkpointType ```ts checkpointType: CheckpointType; ``` The type of the checkpoint. --- ### partitionId ```ts partitionId: PartitionId; ``` The id of the partition. --- ## Type Alias: PartitionId ```ts type PartitionId = number; ``` Partition ID The id of a partition. Always a positive number greater than or equal to 1. --- ## Type Alias: PauseClusterExportingData ```ts type PauseClusterExportingData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: object; ``` #### soft? ```ts optional soft?: boolean; ``` If true, soft-pauses exporting instead of a hard pause. --- ### url ```ts url: "/cluster/v2/exporting/pause"; ``` --- ## Type Alias: PauseClusterExportingError ```ts type PauseClusterExportingError = PauseClusterExportingErrors[keyof PauseClusterExportingErrors]; ``` --- ## Type Alias: PauseClusterExportingErrors ```ts type PauseClusterExportingErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: PauseClusterExportingResponse ```ts type PauseClusterExportingResponse = PauseClusterExportingResponses[keyof PauseClusterExportingResponses]; ``` --- ## Type Alias: PauseClusterExportingResponses ```ts type PauseClusterExportingResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` Exporting was successfully paused on every physical tenant. --- ## Type Alias: PauseExportingData ```ts type PauseExportingData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: object; ``` #### soft? ```ts optional soft?: boolean; ``` If true, soft-pauses exporting instead of a hard pause. --- ### url ```ts url: "/exporting/pause"; ``` --- ## Type Alias: PauseExportingError ```ts type PauseExportingError = PauseExportingErrors[keyof PauseExportingErrors]; ``` --- ## Type Alias: PauseExportingErrors ```ts type PauseExportingErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: PauseExportingResponse ```ts type PauseExportingResponse = PauseExportingResponses[keyof PauseExportingResponses]; ``` --- ## Type Alias: PauseExportingResponses ```ts type PauseExportingResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` Exporting was successfully paused. --- ## Type Alias: PermissionTypeEnum ```ts type PermissionTypeEnum = (typeof PermissionTypeEnum)[keyof typeof PermissionTypeEnum]; ``` Specifies the type of permissions. --- ## Type Alias: PhysicalTenantBrokerTopology ```ts type PhysicalTenantBrokerTopology = object; ``` The partitions of one physical tenant that one broker manages or replicates. ## Properties ### brokerId ```ts brokerId: string; ``` The unique (within a cluster) identifier of the broker, as reported in the cluster-level broker list. --- ### partitions ```ts partitions: Partition[]; ``` The partitions of this physical tenant managed or replicated on this broker. --- ## Type Alias: PhysicalTenantTopology ```ts type PhysicalTenantTopology = object; ``` The topology of a single physical tenant. ## Properties ### brokers ```ts brokers: PhysicalTenantBrokerTopology[]; ``` The brokers holding partitions of this physical tenant. --- ### lastCompletedChangeId ```ts lastCompletedChangeId: string; ``` ID of the last completed change of this physical tenant. --- ### partitionsCount ```ts partitionsCount: number; ``` The number of partitions spread across this physical tenant. --- ### physicalTenantId ```ts physicalTenantId: string; ``` The id of the physical tenant. --- ### replicationFactor ```ts replicationFactor: number; ``` The configured replication factor for this physical tenant. --- ## Type Alias: PinClockData ```ts type PinClockData = object; ``` ## Properties ### body ```ts body: ClockPinRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/clock"; ``` --- ## Type Alias: PinClockError ```ts type PinClockError = PinClockErrors[keyof PinClockErrors]; ``` --- ## Type Alias: PinClockErrors ```ts type PinClockErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: PinClockResponse ```ts type PinClockResponse = PinClockResponses[keyof PinClockResponses]; ``` --- ## Type Alias: PinClockResponses ```ts type PinClockResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The clock was successfully pinned. --- ## Type Alias: ProblemDetail ```ts type ProblemDetail = object; ``` A Problem detail object as described in [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457). There may be additional properties specific to the problem type. ## Properties ### detail ```ts detail: string; ``` An explanation of the problem in more detail. --- ### instance ```ts instance: string; ``` A URI path identifying the origin of the problem. --- ### status ```ts status: number; ``` The HTTP status code for this problem. --- ### title ```ts title: string; ``` A summary of the problem type. --- ### type ```ts type: string; ``` A URI identifying the problem type. --- ## Type Alias: ProcessDefinitionElementStatisticsQuery ```ts type ProcessDefinitionElementStatisticsQuery = object; ``` Process definition element statistics request. ## Properties ### filter? ```ts optional filter?: ProcessDefinitionStatisticsFilter; ``` The process definition statistics search filters. --- ## Type Alias: ProcessDefinitionElementStatisticsQueryResult ```ts type ProcessDefinitionElementStatisticsQueryResult = object; ``` Process definition element statistics query response. ## Properties ### items ```ts items: ProcessElementStatisticsResult[]; ``` The element statistics. --- ## Type Alias: ProcessDefinitionFilter ```ts type ProcessDefinitionFilter = object; ``` Process definition search filter. ## Properties ### hasStartForm? ```ts optional hasStartForm?: boolean; ``` Indicates whether the start event of the process has an associated Form Key. --- ### isLatestVersion? ```ts optional isLatestVersion?: boolean; ``` Whether to only return the latest version of each process definition. When using this filter, pagination functionality is limited, you can only paginate forward using `after` and `limit`. The response contains no `startCursor` in the `page`, and requests ignore the `from` and `before` in the `page`. When using this filter, sorting is limited to `processDefinitionId` and `tenantId` fields only. --- ### name? ```ts optional name?: StringFilterProperty; ``` Name of this process definition. --- ### processDefinitionId? ```ts optional processDefinitionId?: StringFilterProperty; ``` Process definition ID of this process definition. --- ### processDefinitionKey? ```ts optional processDefinitionKey?: ProcessDefinitionKey; ``` The key for this process definition. --- ### resourceName? ```ts optional resourceName?: string; ``` Resource name of this process definition. --- ### state? ```ts optional state?: "ACTIVE" | "DRAINING" | "DELETED"; ``` Filter by the process definition's state. When not set, process definitions in any state are returned. Set to `ACTIVE` to exclude draining and deleted definitions (recommended for most use cases). Set to `DRAINING` to return only definitions that are being deleted but still have active process instances draining. Set to `DELETED` to return only definitions that have been deleted but are still retained in secondary storage. --- ### tenantId? ```ts optional tenantId?: TenantId; ``` Tenant ID of this process definition. --- ### version? ```ts optional version?: number; ``` Version of this process definition. --- ### versionTag? ```ts optional versionTag?: string; ``` Version tag of this process definition. --- ## Type Alias: ProcessDefinitionId ```ts type ProcessDefinitionId = CamundaKey<"ProcessDefinitionId">; ``` Id of a process definition, from the model. Only ids of process definitions that are deployed are useful. --- ## Type Alias: ProcessDefinitionIdExactMatch ```ts type ProcessDefinitionIdExactMatch = ProcessDefinitionId; ``` Exact match Matches the value exactly. --- ## Type Alias: ProcessDefinitionIdExactMatchWritable ```ts type ProcessDefinitionIdExactMatchWritable = ProcessDefinitionId; ``` Exact match Matches the value exactly. --- ## Type Alias: ProcessDefinitionIdFilterProperty ```ts type ProcessDefinitionIdFilterProperty = ProcessDefinitionIdExactMatch | AdvancedProcessDefinitionIdFilter; ``` ProcessDefinitionId property with full advanced search capabilities. --- ## Type Alias: ProcessDefinitionInstanceStatisticsQuery ```ts type ProcessDefinitionInstanceStatisticsQuery = object; ``` ## Properties ### page? ```ts optional page?: OffsetPagination; ``` Search cursor pagination. --- ### sort? ```ts optional sort?: ProcessDefinitionInstanceStatisticsQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: ProcessDefinitionInstanceStatisticsQueryResult ```ts type ProcessDefinitionInstanceStatisticsQueryResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: ProcessDefinitionInstanceStatisticsResult[]; ``` The process definition instance statistics result. --- ## Type Alias: ProcessDefinitionInstanceStatisticsQuerySortRequest ```ts type ProcessDefinitionInstanceStatisticsQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "processDefinitionId" | "activeInstancesWithIncidentCount" | "activeInstancesWithoutIncidentCount"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: ProcessDefinitionInstanceStatisticsResult ```ts type ProcessDefinitionInstanceStatisticsResult = object; ``` Process definition instance statistics response. ## Properties ### activeInstancesWithIncidentCount ```ts activeInstancesWithIncidentCount: number; ``` Total number of currently active process instances of this definition that have at least one incident. --- ### activeInstancesWithoutIncidentCount ```ts activeInstancesWithoutIncidentCount: number; ``` Total number of currently active process instances of this definition that do not have incidents. --- ### hasMultipleVersions ```ts hasMultipleVersions: boolean; ``` Indicates whether multiple versions of this process definition instance are deployed. --- ### latestProcessDefinitionName ```ts latestProcessDefinitionName: string | null; ``` Name of the latest deployed process definition instance version. --- ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId; ``` --- ### tenantId ```ts tenantId: TenantId; ``` --- ## Type Alias: ProcessDefinitionInstanceVersionStatisticsFilter ```ts type ProcessDefinitionInstanceVersionStatisticsFilter = object; ``` Process definition instance version statistics search filter. ## Properties ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId; ``` The ID of the process definition to retrieve version statistics for. --- ### tenantId? ```ts optional tenantId?: TenantId; ``` Tenant ID of this process definition. --- ## Type Alias: ProcessDefinitionInstanceVersionStatisticsQuery ```ts type ProcessDefinitionInstanceVersionStatisticsQuery = object; ``` ## Properties ### filter ```ts filter: ProcessDefinitionInstanceVersionStatisticsFilter; ``` The process definition instance version statistics search filters. --- ### page? ```ts optional page?: OffsetPagination; ``` Pagination criteria. --- ### sort? ```ts optional sort?: ProcessDefinitionInstanceVersionStatisticsQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: ProcessDefinitionInstanceVersionStatisticsQueryResult ```ts type ProcessDefinitionInstanceVersionStatisticsQueryResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: ProcessDefinitionInstanceVersionStatisticsResult[]; ``` The process definition instance version statistics result. --- ## Type Alias: ProcessDefinitionInstanceVersionStatisticsQuerySortRequest ```ts type ProcessDefinitionInstanceVersionStatisticsQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "processDefinitionId" | "processDefinitionKey" | "processDefinitionName" | "processDefinitionVersion" | "activeInstancesWithIncidentCount" | "activeInstancesWithoutIncidentCount"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: ProcessDefinitionInstanceVersionStatisticsResult ```ts type ProcessDefinitionInstanceVersionStatisticsResult = object; ``` Process definition instance version statistics response. ## Properties ### activeInstancesWithIncidentCount ```ts activeInstancesWithIncidentCount: number; ``` The number of active process instances for this version that currently have incidents. --- ### activeInstancesWithoutIncidentCount ```ts activeInstancesWithoutIncidentCount: number; ``` The number of active process instances for this version that do not have any incidents. --- ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId; ``` The ID associated with the process definition. --- ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey; ``` The unique key of the process definition. --- ### processDefinitionName ```ts processDefinitionName: string | null; ``` The name of the process definition. --- ### processDefinitionVersion ```ts processDefinitionVersion: number; ``` The version number of the process definition. --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID associated with the process definition. --- ## Type Alias: ProcessDefinitionKey ```ts type ProcessDefinitionKey = CamundaKey<"ProcessDefinitionKey">; ``` System-generated key for a deployed process definition. --- ## Type Alias: ProcessDefinitionKeyExactMatch ```ts type ProcessDefinitionKeyExactMatch = ProcessDefinitionKey; ``` Exact match Matches the value exactly. --- ## Type Alias: ProcessDefinitionKeyExactMatchWritable ```ts type ProcessDefinitionKeyExactMatchWritable = ProcessDefinitionKeyWritable; ``` Exact match Matches the value exactly. --- ## Type Alias: ProcessDefinitionKeyFilterProperty ```ts type ProcessDefinitionKeyFilterProperty = ProcessDefinitionKeyExactMatch | AdvancedProcessDefinitionKeyFilter; ``` ProcessDefinitionKey property with full advanced search capabilities. --- ## Type Alias: ProcessDefinitionKeyWritable ```ts type ProcessDefinitionKeyWritable = LongKey; ``` System-generated key for a deployed process definition. --- ## Type Alias: ProcessDefinitionMessageSubscriptionStatisticsQuery ```ts type ProcessDefinitionMessageSubscriptionStatisticsQuery = object; ``` ## Properties ### filter? ```ts optional filter?: MessageSubscriptionFilter; ``` The message subscription filters. --- ### page? ```ts optional page?: CursorForwardPagination; ``` Search cursor pagination. --- ## Type Alias: ProcessDefinitionMessageSubscriptionStatisticsQueryResult ```ts type ProcessDefinitionMessageSubscriptionStatisticsQueryResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: ProcessDefinitionMessageSubscriptionStatisticsResult[]; ``` The matching process definition message subscription statistics. --- ## Type Alias: ProcessDefinitionMessageSubscriptionStatisticsResult ```ts type ProcessDefinitionMessageSubscriptionStatisticsResult = object; ``` ## Properties ### activeSubscriptions ```ts activeSubscriptions: number; ``` The total number of active message subscriptions for this process definition key. --- ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId; ``` The process definition ID associated with this message subscription. --- ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey; ``` The process definition key associated with this message subscription. --- ### processInstancesWithActiveSubscriptions ```ts processInstancesWithActiveSubscriptions: number; ``` The number of process instances with active message subscriptions. --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID associated with this message subscription. --- ## Type Alias: ProcessDefinitionResult ```ts type ProcessDefinitionResult = object; ``` ## Properties ### hasStartForm ```ts hasStartForm: boolean; ``` Indicates whether the start event of the process has an associated Form Key. --- ### name ```ts name: string | null; ``` Name of this process definition. --- ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId; ``` Process definition ID of this process definition. --- ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey; ``` The key for this process definition. --- ### resourceName ```ts resourceName: string; ``` Resource name for this process definition. --- ### state ```ts state: "ACTIVE" | "DRAINING" | "DELETED"; ``` The state of this process definition. `DRAINING` indicates the definition is being deleted but still has active process instances draining before it is removed. --- ### tenantId ```ts tenantId: TenantId; ``` Tenant ID of this process definition. --- ### version ```ts version: number; ``` Version of this process definition. --- ### versionTag ```ts versionTag: string | null; ``` Version tag of this process definition. --- ## Type Alias: ProcessDefinitionSearchQuery ```ts type ProcessDefinitionSearchQuery = SearchQueryRequest & object; ``` ## Type Declaration ### filter? ```ts optional filter?: ProcessDefinitionFilter; ``` The process definition search filters. ### sort? ```ts optional sort?: ProcessDefinitionSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: ProcessDefinitionSearchQueryResult ```ts type ProcessDefinitionSearchQueryResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: ProcessDefinitionResult[]; ``` The matching process definitions. --- ## Type Alias: ProcessDefinitionSearchQuerySortRequest ```ts type ProcessDefinitionSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "processDefinitionKey" | "name" | "resourceName" | "version" | "versionTag" | "processDefinitionId" | "tenantId"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: ProcessDefinitionStatisticsFilter ```ts type ProcessDefinitionStatisticsFilter = BaseProcessInstanceFilterFields & object; ``` Process definition statistics search filter. ## Type Declaration ### $or? ```ts optional $or?: BaseProcessInstanceFilterFields[]; ``` Defines a list of alternative filter groups combined using OR logic. Each object in the array is evaluated independently, and the filter matches if any one of them is satisfied. Top-level fields and the `$or` clause are combined using AND logic — meaning: (top-level filters) AND (any of the `$or` filters) must match. _Example:_ ```json { "state": "ACTIVE", "tenantId": 123, "$or": [ { "processDefinitionId": "process_v1" }, { "processDefinitionId": "process_v2", "hasIncident": true } ] } ``` This matches process instances that: - are in _ACTIVE_ state - have tenant id equal to _123_ - and match either: - `processDefinitionId` is _process_v1_, or - `processDefinitionId` is _process_v2_ and `hasIncident` is _true_ Note: Using complex `$or` conditions may impact performance, use with caution in high-volume environments. --- ## Type Alias: ProcessDefinitionVariableNameFilter ```ts type ProcessDefinitionVariableNameFilter = object; ``` Process definition variable name filter request. ## Properties ### name? ```ts optional name?: StringFilterProperty; ``` The variable name search filter. --- ## Type Alias: ProcessDefinitionVariableNameSearchQuery ```ts type ProcessDefinitionVariableNameSearchQuery = SearchQueryRequest & object; ``` Process definition variable name search query request. ## Type Declaration ### filter? ```ts optional filter?: ProcessDefinitionVariableNameFilter; ``` The process definition variable name search filters. --- ## Type Alias: ProcessDefinitionVariableNameSearchQueryResult ```ts type ProcessDefinitionVariableNameSearchQueryResult = SearchQueryResponse & object; ``` Process definition variable name search query response. ## Type Declaration ### items ```ts items: ProcessDefinitionVariableNameSearchResult[]; ``` The matching variable names. --- ## Type Alias: ProcessDefinitionVariableNameSearchResult ```ts type ProcessDefinitionVariableNameSearchResult = object; ``` Process definition variable name search response item. ## Properties ### name ```ts name: string; ``` The variable name. --- ## Type Alias: ProcessElementStatisticsResult ```ts type ProcessElementStatisticsResult = object; ``` Process element statistics response. ## Properties ### active ```ts active: number; ``` The total number of active instances of the element. --- ### canceled ```ts canceled: number; ``` The total number of canceled instances of the element. --- ### completed ```ts completed: number; ``` The total number of completed instances of the element. --- ### elementId ```ts elementId: ElementId; ``` The element ID for which the results are aggregated. --- ### incidents ```ts incidents: number; ``` The total number of incidents for the element. --- ## Type Alias: ProcessInstanceBusinessIdAssignmentInstruction ```ts type ProcessInstanceBusinessIdAssignmentInstruction = object; ``` The instruction describing the business id to assign to a running process instance. ## Properties ### businessId ```ts businessId: BusinessId; ``` --- ## Type Alias: ProcessInstanceCallHierarchyEntry ```ts type ProcessInstanceCallHierarchyEntry = object; ``` ## Properties ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey; ``` The key of the process definition. --- ### processDefinitionName ```ts processDefinitionName: string; ``` The name of the process definition (fall backs to the process definition id if not available). --- ### processInstanceKey ```ts processInstanceKey: ProcessInstanceKey; ``` The key of the process instance. --- ## Type Alias: ProcessInstanceCancellationBatchOperationRequest ```ts type ProcessInstanceCancellationBatchOperationRequest = object; ``` The process instance filter that defines which process instances should be canceled. ## Properties ### filter ```ts filter: ProcessInstanceFilter; ``` The process instance filter. --- ### operationReference? ```ts optional operationReference?: OperationReference; ``` --- ## Type Alias: ProcessInstanceCreationInstruction ```ts type ProcessInstanceCreationInstruction = | ProcessInstanceCreationInstructionByKey | ProcessInstanceCreationInstructionById; ``` Instructions for creating a process instance. The process definition can be specified either by id or by key. --- ## Type Alias: ProcessInstanceCreationInstructionById ```ts type ProcessInstanceCreationInstructionById = object; ``` Process creation by id ## Properties ### awaitCompletion? ```ts optional awaitCompletion?: boolean; ``` Wait for the process instance to complete. If the process instance does not complete within the request timeout limit, a 504 response status will be returned. The process instance will continue to run in the background regardless of the timeout. Disabled by default. --- ### businessId? ```ts optional businessId?: BusinessId; ``` --- ### fetchVariables? ```ts optional fetchVariables?: string[]; ``` List of variables by name to be included in the response when awaitCompletion is set to true. If empty, all visible variables in the root scope will be returned. --- ### operationReference? ```ts optional operationReference?: OperationReference; ``` --- ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId; ``` The BPMN process id of the process definition to start an instance of. --- ### processDefinitionVersion? ```ts optional processDefinitionVersion?: number; ``` The version of the process. If omitted, the latest active version is used. --- ### requestTimeout? ```ts optional requestTimeout?: number; ``` Timeout (in ms) the request waits for the process to complete. By default or when set to 0, the generic request timeout configured in the cluster is applied. --- ### runtimeInstructions? ```ts optional runtimeInstructions?: ProcessInstanceCreationRuntimeInstruction[]; ``` Runtime instructions (alpha). List of instructions that affect the runtime behavior of the process instance. Refer to specific instruction types for more details. This parameter is an alpha feature and may be subject to change in future releases. --- ### startInstructions? ```ts optional startInstructions?: ProcessInstanceCreationStartInstruction[]; ``` List of start instructions. By default, the process instance will start at the start event. If provided, the process instance will apply start instructions after it has been created. --- ### tags? ```ts optional tags?: TagSet; ``` --- ### tenantId? ```ts optional tenantId?: TenantId; ``` The tenant id of the process definition. If multi-tenancy is enabled, provide the tenant id of the process definition to start a process instance of. If multi-tenancy is disabled, don't provide this parameter. --- ### variables? ```ts optional variables?: object; ``` JSON object that will instantiate the variables for the root variable scope of the process instance. #### Index Signature ```ts [key: string]: unknown ``` --- ## Type Alias: ProcessInstanceCreationInstructionByKey ```ts type ProcessInstanceCreationInstructionByKey = object; ``` Process creation by key ## Properties ### awaitCompletion? ```ts optional awaitCompletion?: boolean; ``` Wait for the process instance to complete. If the process instance does not complete within the request timeout limit, a 504 response status will be returned. The process instance will continue to run in the background regardless of the timeout. Disabled by default. --- ### businessId? ```ts optional businessId?: BusinessId; ``` --- ### fetchVariables? ```ts optional fetchVariables?: string[]; ``` List of variables by name to be included in the response when awaitCompletion is set to true. If empty, all visible variables in the root scope will be returned. --- ### operationReference? ```ts optional operationReference?: OperationReference; ``` --- ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey; ``` The unique key identifying the process definition, for example, returned for a process in the deploy resources endpoint. --- ### processDefinitionVersion? ```ts optional processDefinitionVersion?: number; ``` As the version is already identified by the `processDefinitionKey`, the value of this field is ignored. It's here for backwards-compatibility only as previous releases accepted it in request bodies. --- ### requestTimeout? ```ts optional requestTimeout?: number; ``` Timeout (in ms) the request waits for the process to complete. By default or when set to 0, the generic request timeout configured in the cluster is applied. --- ### runtimeInstructions? ```ts optional runtimeInstructions?: ProcessInstanceCreationRuntimeInstruction[]; ``` Runtime instructions (alpha). List of instructions that affect the runtime behavior of the process instance. Refer to specific instruction types for more details. This parameter is an alpha feature and may be subject to change in future releases. --- ### startInstructions? ```ts optional startInstructions?: ProcessInstanceCreationStartInstruction[]; ``` List of start instructions. By default, the process instance will start at the start event. If provided, the process instance will apply start instructions after it has been created. --- ### tags? ```ts optional tags?: TagSet; ``` --- ### tenantId? ```ts optional tenantId?: TenantId; ``` The tenant id of the process definition. If multi-tenancy is enabled, provide the tenant id of the process definition to start a process instance of. If multi-tenancy is disabled, don't provide this parameter. --- ### variables? ```ts optional variables?: object; ``` Set of variables as JSON object to instantiate in the root variable scope of the process instance. Can include nested complex objects. #### Index Signature ```ts [key: string]: unknown ``` --- ## Type Alias: ProcessInstanceCreationRuntimeInstruction ```ts type ProcessInstanceCreationRuntimeInstruction = object & ProcessInstanceCreationTerminateInstruction; ``` ## Type Declaration ### type ```ts type: "TERMINATE_PROCESS_INSTANCE"; ``` --- ## Type Alias: ProcessInstanceCreationStartInstruction ```ts type ProcessInstanceCreationStartInstruction = object; ``` ## Properties ### elementId ```ts elementId: ElementId; ``` Future extensions might include: - different types of start instructions - ability to set local variables for different flow scopes For now, however, the start instruction is implicitly a "startBeforeElement" instruction --- ## Type Alias: ProcessInstanceCreationTerminateInstruction ```ts type ProcessInstanceCreationTerminateInstruction = object; ``` Terminates the process instance after a specific BPMN element is completed or terminated. ## Properties ### afterElementId ```ts afterElementId: ElementId; ``` The id of the element that, once completed or terminated, will cause the process to be terminated. --- ### type? ```ts optional type?: string; ``` The type of the runtime instruction --- ## Type Alias: ProcessInstanceDeletionBatchOperationRequest ```ts type ProcessInstanceDeletionBatchOperationRequest = object; ``` The process instance filter that defines which process instances should be deleted. ## Properties ### filter ```ts filter: ProcessInstanceFilter; ``` The process instance filter. --- ### operationReference? ```ts optional operationReference?: OperationReference; ``` --- ## Type Alias: ProcessInstanceElementStatisticsQueryResult ```ts type ProcessInstanceElementStatisticsQueryResult = object; ``` Process instance element statistics query response. ## Properties ### items ```ts items: ProcessElementStatisticsResult[]; ``` The element statistics. --- ## Type Alias: ProcessInstanceFilter ```ts type ProcessInstanceFilter = ProcessInstanceFilterFields & object; ``` Process instance search filter. ## Type Declaration ### $or? ```ts optional $or?: ProcessInstanceFilterFields[]; ``` Defines a list of alternative filter groups combined using OR logic. Each object in the array is evaluated independently, and the filter matches if any one of them is satisfied. Top-level fields and the `$or` clause are combined using AND logic — meaning: (top-level filters) AND (any of the `$or` filters) must match. _Example:_ ```json { "state": "ACTIVE", "tenantId": 123, "$or": [ { "processDefinitionId": "process_v1" }, { "processDefinitionId": "process_v2", "hasIncident": true } ] } ``` This matches process instances that: - are in _ACTIVE_ state - have tenant id equal to _123_ - and match either: - `processDefinitionId` is _process_v1_, or - `processDefinitionId` is _process_v2_ and `hasIncident` is _true_ Note: Using complex `$or` conditions may impact performance, use with caution in high-volume environments. --- ## Type Alias: ProcessInstanceFilterFields ```ts type ProcessInstanceFilterFields = BaseProcessInstanceFilterFields & object; ``` Process instance search filter. ## Type Declaration ### processDefinitionId? ```ts optional processDefinitionId?: StringFilterProperty; ``` The process definition id. ### processDefinitionKey? ```ts optional processDefinitionKey?: ProcessDefinitionKeyFilterProperty; ``` The process definition key. ### processDefinitionName? ```ts optional processDefinitionName?: StringFilterProperty; ``` The process definition name. ### processDefinitionVersion? ```ts optional processDefinitionVersion?: IntegerFilterProperty; ``` The process definition version. ### processDefinitionVersionTag? ```ts optional processDefinitionVersionTag?: StringFilterProperty; ``` The process definition version tag. --- ## Type Alias: ProcessInstanceIncidentResolutionBatchOperationRequest ```ts type ProcessInstanceIncidentResolutionBatchOperationRequest = object; ``` The process instance filter that defines which process instances should have their incidents resolved. ## Properties ### filter ```ts filter: ProcessInstanceFilter; ``` The process instance filter. --- ### operationReference? ```ts optional operationReference?: OperationReference; ``` --- ## Type Alias: ProcessInstanceKey ```ts type ProcessInstanceKey = CamundaKey<"ProcessInstanceKey">; ``` System-generated key for a process instance. --- ## Type Alias: ProcessInstanceKeyExactMatch ```ts type ProcessInstanceKeyExactMatch = ProcessInstanceKey; ``` Exact match Matches the value exactly. --- ## Type Alias: ProcessInstanceKeyExactMatchWritable ```ts type ProcessInstanceKeyExactMatchWritable = ProcessInstanceKeyWritable; ``` Exact match Matches the value exactly. --- ## Type Alias: ProcessInstanceKeyFilterProperty ```ts type ProcessInstanceKeyFilterProperty = ProcessInstanceKeyExactMatch | AdvancedProcessInstanceKeyFilter; ``` ProcessInstanceKey property with full advanced search capabilities. --- ## Type Alias: ProcessInstanceKeyWritable ```ts type ProcessInstanceKeyWritable = LongKey; ``` System-generated key for a process instance. --- ## Type Alias: ProcessInstanceMigrationBatchOperationPlan ```ts type ProcessInstanceMigrationBatchOperationPlan = object; ``` The migration instructions describe how to migrate a process instance from one process definition to another. ## Properties ### mappingInstructions ```ts mappingInstructions: MigrateProcessInstanceMappingInstruction[]; ``` The mapping instructions. --- ### targetProcessDefinitionKey ```ts targetProcessDefinitionKey: ProcessDefinitionKey; ``` The target process definition key. --- ## Type Alias: ProcessInstanceMigrationBatchOperationRequest ```ts type ProcessInstanceMigrationBatchOperationRequest = object; ``` ## Properties ### filter ```ts filter: ProcessInstanceFilter; ``` The process instance filter. --- ### migrationPlan ```ts migrationPlan: ProcessInstanceMigrationBatchOperationPlan; ``` The migration plan. --- ### operationReference? ```ts optional operationReference?: OperationReference; ``` --- ## Type Alias: ProcessInstanceMigrationInstruction ```ts type ProcessInstanceMigrationInstruction = object; ``` The migration instructions describe how to migrate a process instance from one process definition to another. ## Properties ### mappingInstructions ```ts mappingInstructions: MigrateProcessInstanceMappingInstruction[]; ``` Element mappings from the source process instance to the target process instance. --- ### operationReference? ```ts optional operationReference?: OperationReference; ``` --- ### targetProcessDefinitionKey ```ts targetProcessDefinitionKey: ProcessDefinitionKey; ``` The key of process definition to migrate the process instance to. --- ## Type Alias: ProcessInstanceModificationActivateInstruction ```ts type ProcessInstanceModificationActivateInstruction = object; ``` Instruction describing an element to activate. ## Properties ### ancestorElementInstanceKey? ```ts optional ancestorElementInstanceKey?: ElementInstanceKey; ``` The key of the ancestor scope the element instance should be created in. Set to -1 to create the new element instance within an existing element instance of the flow scope. If multiple instances of the target element's flow scope exist, choose one specifically with this property by providing its key. --- ### elementId ```ts elementId: ElementId; ``` The id of the element to activate. --- ### variableInstructions? ```ts optional variableInstructions?: ModifyProcessInstanceVariableInstruction[]; ``` Instructions describing which variables to create or update. --- ## Type Alias: ProcessInstanceModificationBatchOperationRequest ```ts type ProcessInstanceModificationBatchOperationRequest = object; ``` The process instance filter to define on which process instances tokens should be moved, and new element instances should be activated or terminated. ## Properties ### filter ```ts filter: ProcessInstanceFilter; ``` The process instance filter. --- ### moveInstructions ```ts moveInstructions: ProcessInstanceModificationMoveBatchOperationInstruction[]; ``` Instructions for moving tokens between elements. --- ### operationReference? ```ts optional operationReference?: OperationReference; ``` --- ## Type Alias: ProcessInstanceModificationInstruction ```ts type ProcessInstanceModificationInstruction = object; ``` ## Properties ### activateInstructions? ```ts optional activateInstructions?: ProcessInstanceModificationActivateInstruction[]; ``` Instructions describing which elements to activate in which scopes and which variables to create or update. --- ### moveInstructions? ```ts optional moveInstructions?: ProcessInstanceModificationMoveInstruction[]; ``` Instructions describing which elements to move from one scope to another. --- ### operationReference? ```ts optional operationReference?: OperationReference; ``` --- ### terminateInstructions? ```ts optional terminateInstructions?: ProcessInstanceModificationTerminateInstruction[]; ``` Instructions describing which elements to terminate. --- ## Type Alias: ProcessInstanceModificationMoveBatchOperationInstruction ```ts type ProcessInstanceModificationMoveBatchOperationInstruction = object; ``` Instructions describing a move operation. This instruction will terminate all active element instances at `sourceElementId` and activate a new element instance for each terminated one at `targetElementId`. The new element instances are created in the parent scope of the source element instances. ## Properties ### sourceElementId ```ts sourceElementId: ElementId; ``` The source element ID. --- ### targetElementId ```ts targetElementId: ElementId; ``` The target element ID. --- ## Type Alias: ProcessInstanceModificationMoveInstruction ```ts type ProcessInstanceModificationMoveInstruction = object; ``` Instruction describing a move operation. This instruction will terminate active element instances based on the sourceElementInstruction and activate a new element instance for each terminated one at targetElementId. Note that, for multi-instance activities, only the multi-instance body instances will activate new element instances at the target id. ## Properties ### ancestorScopeInstruction? ```ts optional ancestorScopeInstruction?: AncestorScopeInstruction; ``` --- ### sourceElementInstruction ```ts sourceElementInstruction: SourceElementInstruction; ``` --- ### targetElementId ```ts targetElementId: ElementId; ``` The target element id. --- ### variableInstructions? ```ts optional variableInstructions?: ModifyProcessInstanceVariableInstruction[]; ``` Instructions describing which variables to create or update. --- ## Type Alias: ProcessInstanceModificationTerminateByIdInstruction ```ts type ProcessInstanceModificationTerminateByIdInstruction = object; ``` Instruction describing which elements to terminate. The element instances are determined at runtime by the given id. ## Properties ### elementId ```ts elementId: ElementId; ``` The id of the elements to terminate. The element instances are determined at runtime. --- ## Type Alias: ProcessInstanceModificationTerminateByKeyInstruction ```ts type ProcessInstanceModificationTerminateByKeyInstruction = object; ``` Instruction providing the key of the element instance to terminate. ## Properties ### elementInstanceKey ```ts elementInstanceKey: ElementInstanceKey; ``` The key of the element instance to terminate. --- ## Type Alias: ProcessInstanceModificationTerminateInstruction ```ts type ProcessInstanceModificationTerminateInstruction = | ProcessInstanceModificationTerminateByIdInstruction | ProcessInstanceModificationTerminateByKeyInstruction; ``` Instruction describing which elements to terminate. --- ## Type Alias: ProcessInstanceReference ```ts type ProcessInstanceReference = object; ``` ## Properties ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey; ``` The key of the process definition. --- ### processInstanceKey ```ts processInstanceKey: ProcessInstanceKey; ``` The key of the created process instance. --- ## Type Alias: ProcessInstanceResult ```ts type ProcessInstanceResult = object; ``` Process instance search response item. ## Properties ### businessId ```ts businessId: BusinessId | null; ``` The business id associated with this process instance. --- ### endDate ```ts endDate: string | null; ``` The completion or termination time of the process instance. --- ### hasIncident ```ts hasIncident: boolean; ``` Whether this process instance has a related incident or not. --- ### parentElementInstanceKey ```ts parentElementInstanceKey: ElementInstanceKey | null; ``` The parent element instance key. --- ### parentProcessInstanceKey ```ts parentProcessInstanceKey: ProcessInstanceKey | null; ``` The parent process instance key. --- ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId; ``` --- ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey; ``` The process definition key. --- ### processDefinitionName ```ts processDefinitionName: string | null; ``` The process definition name. --- ### processDefinitionVersion ```ts processDefinitionVersion: number; ``` The process definition version. --- ### processDefinitionVersionTag ```ts processDefinitionVersionTag: string | null; ``` The process definition version tag. --- ### processInstanceKey ```ts processInstanceKey: ProcessInstanceKey; ``` The key of this process instance. --- ### rootProcessInstanceKey ```ts rootProcessInstanceKey: ProcessInstanceKey | null; ``` The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. --- ### startDate ```ts startDate: string; ``` The start time of the process instance. --- ### state ```ts state: ProcessInstanceStateEnum; ``` --- ### suspendedDate ```ts suspendedDate: string | null; ``` The time this process instance most recently entered the `SUSPENDED` state. This is `null` if the process instance is not currently suspended. --- ### tags ```ts tags: TagSet; ``` --- ### tenantId ```ts tenantId: TenantId; ``` --- ## Type Alias: ProcessInstanceResumptionBatchOperationRequest ```ts type ProcessInstanceResumptionBatchOperationRequest = object; ``` The process instance filter that defines which process instances should be resumed. ## Properties ### filter ```ts filter: ProcessInstanceFilter; ``` The process instance filter. --- ### operationReference? ```ts optional operationReference?: OperationReference; ``` --- ## Type Alias: ProcessInstanceSearchQuery ```ts type ProcessInstanceSearchQuery = SearchQueryRequest & object; ``` Process instance search request. ## Type Declaration ### filter? ```ts optional filter?: ProcessInstanceFilter; ``` The process instance search filters. ### sort? ```ts optional sort?: ProcessInstanceSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: ProcessInstanceSearchQueryResult ```ts type ProcessInstanceSearchQueryResult = SearchQueryResponse & object; ``` Process instance search response. ## Type Declaration ### items ```ts items: ProcessInstanceResult[]; ``` The matching process instances. --- ## Type Alias: ProcessInstanceSearchQuerySortRequest ```ts type ProcessInstanceSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "processInstanceKey" | "processDefinitionId" | "processDefinitionName" | "processDefinitionVersion" | "processDefinitionVersionTag" | "processDefinitionKey" | "parentProcessInstanceKey" | "parentElementInstanceKey" | "startDate" | "endDate" | "suspendedDate" | "state" | "hasIncident" | "tenantId" | "businessId"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: ProcessInstanceSequenceFlowResult ```ts type ProcessInstanceSequenceFlowResult = object; ``` Process instance sequence flow result. ## Properties ### elementId ```ts elementId: ElementId; ``` The element id for this sequence flow, as provided in the BPMN process. --- ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId; ``` The process definition id. --- ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey; ``` The process definition key. --- ### processInstanceKey ```ts processInstanceKey: ProcessInstanceKey; ``` The key of this process instance. --- ### rootProcessInstanceKey ```ts rootProcessInstanceKey: ProcessInstanceKey | null; ``` The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. --- ### sequenceFlowId ```ts sequenceFlowId: string; ``` The sequence flow id. --- ### tenantId ```ts tenantId: TenantId; ``` --- ## Type Alias: ProcessInstanceSequenceFlowsQueryResult ```ts type ProcessInstanceSequenceFlowsQueryResult = object; ``` Process instance sequence flows query response. ## Properties ### items ```ts items: ProcessInstanceSequenceFlowResult[]; ``` The sequence flows. --- ## Type Alias: ProcessInstanceStateEnum ```ts type ProcessInstanceStateEnum = (typeof ProcessInstanceStateEnum)[keyof typeof ProcessInstanceStateEnum]; ``` Process instance states --- ## Type Alias: ProcessInstanceStateExactMatch ```ts type ProcessInstanceStateExactMatch = ProcessInstanceStateEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: ProcessInstanceStateExactMatchWritable ```ts type ProcessInstanceStateExactMatchWritable = ProcessInstanceStateEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: ProcessInstanceStateFilterProperty ```ts type ProcessInstanceStateFilterProperty = ProcessInstanceStateExactMatch | AdvancedProcessInstanceStateFilter; ``` ProcessInstanceStateEnum property with full advanced search capabilities. --- ## Type Alias: ProcessInstanceSuspensionBatchOperationRequest ```ts type ProcessInstanceSuspensionBatchOperationRequest = object; ``` The process instance filter that defines which process instances should be suspended. ## Properties ### filter ```ts filter: ProcessInstanceFilter; ``` The process instance filter. --- ### operationReference? ```ts optional operationReference?: OperationReference; ``` --- ## Type Alias: ProcessInstanceWaitStateStatisticsQueryResult ```ts type ProcessInstanceWaitStateStatisticsQueryResult = object; ``` Process instance wait state statistics query response. ## Properties ### items ```ts items: ProcessInstanceWaitStateStatisticsResult[]; ``` The wait state statistics. --- ## Type Alias: ProcessInstanceWaitStateStatisticsResult ```ts type ProcessInstanceWaitStateStatisticsResult = object; ``` Process instance wait state statistics response item. ## Properties ### elementId ```ts elementId: ElementId; ``` The element id for which the wait states are aggregated. --- ### waitingCount ```ts waitingCount: number; ``` The total number of waiting instances of the element. --- ## Type Alias: PublishMessageData ```ts type PublishMessageData = object; ``` ## Properties ### body ```ts body: MessagePublicationRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/messages/publication"; ``` --- ## Type Alias: PublishMessageError ```ts type PublishMessageError = PublishMessageErrors[keyof PublishMessageErrors]; ``` --- ## Type Alias: PublishMessageErrors ```ts type PublishMessageErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: PublishMessageResponse ```ts type PublishMessageResponse = PublishMessageResponses[keyof PublishMessageResponses]; ``` --- ## Type Alias: PublishMessageResponses ```ts type PublishMessageResponses = object; ``` ## Properties ### 200 ```ts 200: MessagePublicationResult; ``` The message was published. --- ## Type Alias: RebalanceCancellationResponse ```ts type RebalanceCancellationResponse = object; ``` Response to a rebalance cancellation request. ## Properties ### wasRunning ```ts wasRunning: boolean; ``` Whether there was a rebalance to stop. --- ## Type Alias: ResetClockData ```ts type ResetClockData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/clock/reset"; ``` --- ## Type Alias: ResetClockError ```ts type ResetClockError = ResetClockErrors[keyof ResetClockErrors]; ``` --- ## Type Alias: ResetClockErrors ```ts type ResetClockErrors = object; ``` ## Properties ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: ResetClockResponse ```ts type ResetClockResponse = ResetClockResponses[keyof ResetClockResponses]; ``` --- ## Type Alias: ResetClockResponses ```ts type ResetClockResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The clock was successfully reset to the system time. --- ## Type Alias: ResolveIncidentData ```ts type ResolveIncidentData = object; ``` ## Properties ### body? ```ts optional body?: IncidentResolutionRequest; ``` --- ### path ```ts path: object; ``` #### incidentKey ```ts incidentKey: IncidentKeyWritable; ``` Key of the incident to resolve. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/incidents/{incidentKey}/resolution"; ``` --- ## Type Alias: ResolveIncidentError ```ts type ResolveIncidentError = ResolveIncidentErrors[keyof ResolveIncidentErrors]; ``` --- ## Type Alias: ResolveIncidentErrors ```ts type ResolveIncidentErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 404 ```ts 404: ProblemDetail; ``` The incident with the incidentKey is not found. --- ### 409 ```ts 409: ProblemDetail; ``` The incident cannot be resolved due to an invalid state. For example, the associated job may have no retries left. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: ResolveIncidentResponse ```ts type ResolveIncidentResponse = ResolveIncidentResponses[keyof ResolveIncidentResponses]; ``` --- ## Type Alias: ResolveIncidentResponses ```ts type ResolveIncidentResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The incident is marked as resolved. --- ## Type Alias: ResolveIncidentsBatchOperationData ```ts type ResolveIncidentsBatchOperationData = object; ``` ## Properties ### body ```ts body: ProcessInstanceIncidentResolutionBatchOperationRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/incident-resolution"; ``` --- ## Type Alias: ResolveIncidentsBatchOperationError ```ts type ResolveIncidentsBatchOperationError = ResolveIncidentsBatchOperationErrors[keyof ResolveIncidentsBatchOperationErrors]; ``` --- ## Type Alias: ResolveIncidentsBatchOperationErrors ```ts type ResolveIncidentsBatchOperationErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The process instance batch operation failed. More details are provided in the response body. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: ResolveIncidentsBatchOperationResponse ```ts type ResolveIncidentsBatchOperationResponse = ResolveIncidentsBatchOperationResponses[keyof ResolveIncidentsBatchOperationResponses]; ``` --- ## Type Alias: ResolveIncidentsBatchOperationResponses ```ts type ResolveIncidentsBatchOperationResponses = object; ``` ## Properties ### 200 ```ts 200: BatchOperationCreatedResult; ``` The batch operation request was created. --- ## Type Alias: ResolveProcessInstanceIncidentsData ```ts type ResolveProcessInstanceIncidentsData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### processInstanceKey ```ts processInstanceKey: ProcessInstanceKeyWritable; ``` The key of the process instance to resolve incidents for. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/{processInstanceKey}/incident-resolution"; ``` --- ## Type Alias: ResolveProcessInstanceIncidentsError ```ts type ResolveProcessInstanceIncidentsError = ResolveProcessInstanceIncidentsErrors[keyof ResolveProcessInstanceIncidentsErrors]; ``` --- ## Type Alias: ResolveProcessInstanceIncidentsErrors ```ts type ResolveProcessInstanceIncidentsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 404 ```ts 404: ProblemDetail; ``` The process instance is not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: ResolveProcessInstanceIncidentsResponse ```ts type ResolveProcessInstanceIncidentsResponse = ResolveProcessInstanceIncidentsResponses[keyof ResolveProcessInstanceIncidentsResponses]; ``` --- ## Type Alias: ResolveProcessInstanceIncidentsResponses ```ts type ResolveProcessInstanceIncidentsResponses = object; ``` ## Properties ### 200 ```ts 200: BatchOperationCreatedResult; ``` The batch operation request for incident resolution was created. --- ## Type Alias: ResolveSecretsData ```ts type ResolveSecretsData = object; ``` ## Properties ### body ```ts body: SecretResolveRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/secrets/resolve"; ``` --- ## Type Alias: ResolveSecretsError ```ts type ResolveSecretsError = ResolveSecretsErrors[keyof ResolveSecretsErrors]; ``` --- ## Type Alias: ResolveSecretsErrors ```ts type ResolveSecretsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: ResolveSecretsResponse ```ts type ResolveSecretsResponse = ResolveSecretsResponses[keyof ResolveSecretsResponses]; ``` --- ## Type Alias: ResolveSecretsResponses ```ts type ResolveSecretsResponses = object; ``` ## Properties ### 200 ```ts 200: SecretResolveResult; ``` The batch was processed. Per-reference outcomes are split between `resolved` and `errors`; this status is returned even when some or all references failed. --- ## Type Alias: ResolvedSecret ```ts type ResolvedSecret = object; ``` ## Properties ### reference ```ts reference: string; ``` The resolved secret reference of the form `camunda.secrets.`. --- ### value ```ts value: string; ``` The resolved secret value. --- ## Type Alias: ResourceFilter ```ts type ResourceFilter = object; ``` Resource search filter. ## Properties ### deploymentKey? ```ts optional deploymentKey?: DeploymentKeyFilterProperty; ``` Deployment key of this resource. --- ### resourceId? ```ts optional resourceId?: StringFilterProperty; ``` Resource ID of this resource. --- ### resourceKey? ```ts optional resourceKey?: ResourceKeyFilterProperty; ``` The key for this resource. --- ### resourceName? ```ts optional resourceName?: StringFilterProperty; ``` Resource name of this resource. --- ### tenantId? ```ts optional tenantId?: TenantId; ``` Tenant ID of this resource. --- ### version? ```ts optional version?: IntegerFilterProperty; ``` Version of this resource. --- ### versionTag? ```ts optional versionTag?: StringFilterProperty; ``` Version tag of this resource. --- ## Type Alias: ResourceKey ```ts type ResourceKey = | ProcessDefinitionKey | DecisionRequirementsKey | FormKey | DecisionDefinitionKey; ``` The system-assigned key for this resource. --- ## Type Alias: ResourceKeyExactMatch ```ts type ResourceKeyExactMatch = ResourceKey; ``` Exact match Matches the value exactly. --- ## Type Alias: ResourceKeyExactMatchWritable ```ts type ResourceKeyExactMatchWritable = ResourceKeyWritable; ``` Exact match Matches the value exactly. --- ## Type Alias: ResourceKeyFilterProperty ```ts type ResourceKeyFilterProperty = ResourceKeyExactMatch | AdvancedResourceKeyFilter; ``` ResourceKey property with full advanced search capabilities. --- ## Type Alias: ResourceKeyWritable ```ts type ResourceKeyWritable = | ProcessDefinitionKeyWritable | DecisionRequirementsKeyWritable | FormKeyWritable | DecisionDefinitionKeyWritable; ``` The system-assigned key for this resource. --- ## Type Alias: ResourceResult ```ts type ResourceResult = object; ``` ## Properties ### resourceId ```ts resourceId: string; ``` The resource ID of this resource. --- ### resourceKey ```ts resourceKey: ResourceKey; ``` The unique key of this resource. --- ### resourceName ```ts resourceName: string; ``` The resource name from which this resource was parsed. --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID of this resource. --- ### version ```ts version: number; ``` The assigned resource version. --- ### versionTag ```ts versionTag: string | null; ``` The version tag of this resource. --- ## Type Alias: ResourceSearchQuery ```ts type ResourceSearchQuery = SearchQueryRequest & object; ``` ## Type Declaration ### filter? ```ts optional filter?: ResourceFilter; ``` The resource search filters. ### sort? ```ts optional sort?: ResourceSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: ResourceSearchQueryResult ```ts type ResourceSearchQueryResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: ResourceResult[]; ``` The matching resources. --- ## Type Alias: ResourceSearchQuerySortRequest ```ts type ResourceSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "resourceKey" | "resourceName" | "resourceId" | "version" | "versionTag" | "deploymentKey" | "tenantId"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: ResourceTypeEnum ```ts type ResourceTypeEnum = (typeof ResourceTypeEnum)[keyof typeof ResourceTypeEnum]; ``` The type of resource to add/remove permissions to/from. --- ## Type Alias: RestoreAsClusterAdminData ```ts type RestoreAsClusterAdminData = object; ``` ## Properties ### body ```ts body: ClusterRestoreRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: object; ``` #### dryRun? ```ts optional dryRun?: boolean; ``` If true, the requested change is only validated and the resulting plan is returned, without applying it to the cluster. #### physicalTenantId? ```ts optional physicalTenantId?: string; ``` The physical tenant to apply the change to. When omitted, or when passed with an empty value, the change is applied to every physical tenant of the cluster. --- ### url ```ts url: "/cluster/v2/restore"; ``` --- ## Type Alias: RestoreAsClusterAdminError ```ts type RestoreAsClusterAdminError = RestoreAsClusterAdminErrors[keyof RestoreAsClusterAdminErrors]; ``` --- ## Type Alias: RestoreAsClusterAdminErrors ```ts type RestoreAsClusterAdminErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 404 ```ts 404: ProblemDetail; ``` The requested `physicalTenantId`, or a physical tenant named in `overrides`, does not exist in this cluster. --- ### 409 ```ts 409: unknown; ``` A targeted physical tenant is not in recovery mode, so the restore cannot be accepted. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: RestoreAsClusterAdminResponse ```ts type RestoreAsClusterAdminResponse = RestoreAsClusterAdminResponses[keyof RestoreAsClusterAdminResponses]; ``` --- ## Type Alias: RestoreAsClusterAdminResponses ```ts type RestoreAsClusterAdminResponses = object; ``` ## Properties ### 202 ```ts 202: ClusterRestoreResponse; ``` The restore request was accepted; returns the planned cluster change covering every requested physical tenant. --- ## Type Alias: RestoreBrokerStatus ```ts type RestoreBrokerStatus = object; ``` The restore status of a single broker. ## Properties ### brokerId ```ts brokerId: string; ``` The ID of the broker, including its zone if it belongs to one. --- ### partitions ```ts partitions: RestorePartitionStatus[]; ``` The per-partition restore status for this broker. --- ### partitionsRestored ```ts partitionsRestored: number; ``` The number of the broker's partitions that have been restored so far. --- ### partitionsToRestore ```ts partitionsToRestore: number; ``` The total number of the broker's partitions to restore. --- ## Type Alias: RestoreData ```ts type RestoreData = object; ``` ## Properties ### body ```ts body: RestoreRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: object; ``` #### dryRun? ```ts optional dryRun?: boolean; ``` If true, the requested change is only validated and the resulting plan is returned, without applying it to the cluster. --- ### url ```ts url: "/restore"; ``` --- ## Type Alias: RestoreError ```ts type RestoreError = RestoreErrors[keyof RestoreErrors]; ``` --- ## Type Alias: RestoreErrors ```ts type RestoreErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 409 ```ts 409: unknown; ``` The cluster is not in recovery mode, so the restore cannot be accepted. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: RestorePartitionStatus ```ts type RestorePartitionStatus = object; ``` The restore status of a single partition on a broker. ## Properties ### backupIds ```ts backupIds: number[]; ``` The IDs of the backups this partition is restored from. --- ### completedAt ```ts completedAt: string | null; ``` The time the partition was restored, as an ISO 8601 timestamp; null unless the partition state is `RESTORED`. --- ### partitionId ```ts partitionId: number; ``` The ID of the partition. --- ### state ```ts state: "PENDING" | "RESTORING" | "RESTORED"; ``` The restore state of the partition. --- ## Type Alias: RestoreRequest ```ts type RestoreRequest = object; ``` Describes a restore request. Provide either a list of backup IDs or a time range (`from`/`to`) that selects the backups to restore; the two are mutually exclusive. ## Properties ### backupIds? ```ts optional backupIds?: number[] | null; ``` The IDs of the backups to restore from, one per partition. --- ### from? ```ts optional from?: string | null; ``` The start of the time range to restore from, as an ISO 8601 timestamp. --- ### to? ```ts optional to?: string | null; ``` The end of the time range to restore from, as an ISO 8601 timestamp. --- ## Type Alias: RestoreResponse ```ts type RestoreResponse = RestoreResponses[keyof RestoreResponses]; ``` --- ## Type Alias: RestoreResponses ```ts type RestoreResponses = object; ``` ## Properties ### 202 ```ts 202: ClusterRestoreResponse; ``` The restore request was accepted; returns the planned cluster changes. --- ## Type Alias: RestoreStatusResponse ```ts type RestoreStatusResponse = object; ``` The status of the restore that is currently in progress. ## Properties ### brokers ```ts brokers: RestoreBrokerStatus[]; ``` The per-broker restore status. --- ### changeId ```ts changeId: string; ``` The ID of the cluster change that performs the restore. --- ### startedAt ```ts startedAt: string | null; ``` The time the restore started, as an ISO 8601 timestamp. --- ### status ```ts status: "IN_PROGRESS" | "COMPLETED" | "FAILED" | "CANCELLED"; ``` The overall status of the restore. --- ## Type Alias: Result # Type Alias: Result\ ```ts type Result = | { ok: true; value: T; } | { error: E; ok: false; }; ``` ## Type Parameters ### T `T` ### E `E` = `unknown` --- ## Type Alias: ResumeBatchOperationData ```ts type ResumeBatchOperationData = object; ``` ## Properties ### body? ```ts optional body?: unknown; ``` --- ### path ```ts path: object; ``` #### batchOperationKey ```ts batchOperationKey: BatchOperationKey; ``` The key (or operate legacy ID) of the batch operation. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/batch-operations/{batchOperationKey}/resumption"; ``` --- ## Type Alias: ResumeBatchOperationError ```ts type ResumeBatchOperationError = ResumeBatchOperationErrors[keyof ResumeBatchOperationErrors]; ``` --- ## Type Alias: ResumeBatchOperationErrors ```ts type ResumeBatchOperationErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Not found. The batch operation was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: ResumeBatchOperationResponse ```ts type ResumeBatchOperationResponse = ResumeBatchOperationResponses[keyof ResumeBatchOperationResponses]; ``` --- ## Type Alias: ResumeBatchOperationResponses ```ts type ResumeBatchOperationResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The batch operation resume request was created. --- ## Type Alias: ResumeClusterExportingData ```ts type ResumeClusterExportingData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/cluster/v2/exporting/resume"; ``` --- ## Type Alias: ResumeClusterExportingError ```ts type ResumeClusterExportingError = ResumeClusterExportingErrors[keyof ResumeClusterExportingErrors]; ``` --- ## Type Alias: ResumeClusterExportingErrors ```ts type ResumeClusterExportingErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: ResumeClusterExportingResponse ```ts type ResumeClusterExportingResponse = ResumeClusterExportingResponses[keyof ResumeClusterExportingResponses]; ``` --- ## Type Alias: ResumeClusterExportingResponses ```ts type ResumeClusterExportingResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` Exporting was successfully resumed on every physical tenant. --- ## Type Alias: ResumeExportingData ```ts type ResumeExportingData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/exporting/resume"; ``` --- ## Type Alias: ResumeExportingError ```ts type ResumeExportingError = ResumeExportingErrors[keyof ResumeExportingErrors]; ``` --- ## Type Alias: ResumeExportingErrors ```ts type ResumeExportingErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: ResumeExportingResponse ```ts type ResumeExportingResponse = ResumeExportingResponses[keyof ResumeExportingResponses]; ``` --- ## Type Alias: ResumeExportingResponses ```ts type ResumeExportingResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` Exporting was successfully resumed. --- ## Type Alias: ResumeProcessInstanceData ```ts type ResumeProcessInstanceData = object; ``` ## Properties ### body? ```ts optional body?: ResumeProcessInstanceRequest; ``` --- ### path ```ts path: object; ``` #### processInstanceKey ```ts processInstanceKey: ProcessInstanceKeyWritable; ``` The key of the process instance to resume. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/{processInstanceKey}/resumption"; ``` --- ## Type Alias: ResumeProcessInstanceError ```ts type ResumeProcessInstanceError = ResumeProcessInstanceErrors[keyof ResumeProcessInstanceErrors]; ``` --- ## Type Alias: ResumeProcessInstanceErrors ```ts type ResumeProcessInstanceErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 404 ```ts 404: ProblemDetail; ``` The process instance is not found. --- ### 409 ```ts 409: ProblemDetail; ``` The process instance is not in the SUSPENDED state and cannot be resumed. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: ResumeProcessInstanceRequest ```ts type ResumeProcessInstanceRequest = { operationReference?: OperationReference; } | null; ``` --- ## Type Alias: ResumeProcessInstanceResponse ```ts type ResumeProcessInstanceResponse = ResumeProcessInstanceResponses[keyof ResumeProcessInstanceResponses]; ``` --- ## Type Alias: ResumeProcessInstanceResponses ```ts type ResumeProcessInstanceResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The process instance is resumed. --- ## Type Alias: ResumeProcessInstancesBatchOperationData ```ts type ResumeProcessInstancesBatchOperationData = object; ``` ## Properties ### body ```ts body: ProcessInstanceResumptionBatchOperationRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/resumption"; ``` --- ## Type Alias: ResumeProcessInstancesBatchOperationError ```ts type ResumeProcessInstancesBatchOperationError = ResumeProcessInstancesBatchOperationErrors[keyof ResumeProcessInstancesBatchOperationErrors]; ``` --- ## Type Alias: ResumeProcessInstancesBatchOperationErrors ```ts type ResumeProcessInstancesBatchOperationErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The process instance batch operation failed. More details are provided in the response body. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: ResumeProcessInstancesBatchOperationResponse ```ts type ResumeProcessInstancesBatchOperationResponse = ResumeProcessInstancesBatchOperationResponses[keyof ResumeProcessInstancesBatchOperationResponses]; ``` --- ## Type Alias: ResumeProcessInstancesBatchOperationResponses ```ts type ResumeProcessInstancesBatchOperationResponses = object; ``` ## Properties ### 200 ```ts 200: BatchOperationCreatedResult; ``` The batch operation request was created. --- ## Type Alias: RoleClientResult ```ts type RoleClientResult = object; ``` ## Properties ### clientId ```ts clientId: ClientId; ``` The ID of the client. --- ## Type Alias: RoleClientSearchQueryRequest ```ts type RoleClientSearchQueryRequest = SearchQueryRequest & object; ``` ## Type Declaration ### sort? ```ts optional sort?: RoleClientSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: RoleClientSearchQuerySortRequest ```ts type RoleClientSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: "clientId"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: RoleClientSearchResult ```ts type RoleClientSearchResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: RoleClientResult[]; ``` The matching clients. --- ## Type Alias: RoleCreateRequest ```ts type RoleCreateRequest = object; ``` ## Properties ### description? ```ts optional description?: string; ``` The description of the new role. --- ### name ```ts name: string; ``` The display name of the new role. --- ### roleId ```ts roleId: RoleId; ``` The ID of the new role. --- ## Type Alias: RoleCreateResult ```ts type RoleCreateResult = object; ``` ## Properties ### description ```ts description: string | null; ``` The description of the created role. --- ### name ```ts name: string; ``` The display name of the created role. --- ### roleId ```ts roleId: RoleId; ``` The ID of the created role. --- ## Type Alias: RoleFilter ```ts type RoleFilter = RoleFilterFields & object; ``` Role filter request ## Type Declaration ### $or? ```ts optional $or?: RoleFilterFields[] | null; ``` Defines a list of alternative filter groups combined using OR logic. Each object in the array is evaluated independently, and the filter matches if any one of them is satisfied. Top-level fields and the `$or` clause are combined using AND logic — meaning: (top-level filters) AND (any of the `$or` filters) must match. _Example:_ ```json { "name": "Admin", "$or": [{ "roleId": "role-1" }, { "roleId": "role-2" }] } ``` This matches roles that: - have name equal to _Admin_ - and match either: - `roleId` is _role-1_, or - `roleId` is _role-2_ Note: Using complex `$or` conditions may impact performance, use with caution in high-volume environments. --- ## Type Alias: RoleFilterFields ```ts type RoleFilterFields = object; ``` Role filter request ## Properties ### name? ```ts optional name?: StringFilterProperty; ``` The role name search filters. --- ### roleId? ```ts optional roleId?: StringFilterProperty; ``` The role ID search filters. --- ## Type Alias: RoleGroupResult ```ts type RoleGroupResult = object; ``` ## Properties ### groupId ```ts groupId: GroupId; ``` The id of the group. --- ## Type Alias: RoleGroupSearchQueryRequest ```ts type RoleGroupSearchQueryRequest = SearchQueryRequest & object; ``` ## Type Declaration ### sort? ```ts optional sort?: RoleGroupSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: RoleGroupSearchQuerySortRequest ```ts type RoleGroupSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: "groupId"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: RoleGroupSearchResult ```ts type RoleGroupSearchResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: RoleGroupResult[]; ``` The matching groups. --- ## Type Alias: RoleId ```ts type RoleId = CamundaKey<"RoleId">; ``` The unique identifier of a role. --- ## Type Alias: RoleMappingRuleSearchResult ```ts type RoleMappingRuleSearchResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: MappingRuleResult[]; ``` The matching mapping rules. --- ## Type Alias: RoleResult ```ts type RoleResult = object; ``` Role search response item. ## Properties ### description ```ts description: string | null; ``` The description of the role. --- ### name ```ts name: string; ``` The role name. --- ### roleId ```ts roleId: RoleId; ``` The role id. --- ## Type Alias: RoleSearchQueryRequest ```ts type RoleSearchQueryRequest = SearchQueryRequest & object; ``` Role search request. ## Type Declaration ### filter? ```ts optional filter?: RoleFilter; ``` The role search filters. ### sort? ```ts optional sort?: RoleSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: RoleSearchQueryResult ```ts type RoleSearchQueryResult = SearchQueryResponse & object; ``` Role search response. ## Type Declaration ### items ```ts items: RoleResult[]; ``` The matching roles. --- ## Type Alias: RoleSearchQuerySortRequest ```ts type RoleSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: "name" | "roleId"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: RoleUpdateRequest ```ts type RoleUpdateRequest = object; ``` ## Properties ### description? ```ts optional description?: string; ``` The description of the new role. --- ### name ```ts name: string; ``` The display name of the new role. --- ## Type Alias: RoleUpdateResult ```ts type RoleUpdateResult = object; ``` ## Properties ### description ```ts description: string | null; ``` The description of the updated role. --- ### name ```ts name: string; ``` The display name of the updated role. --- ### roleId ```ts roleId: RoleId; ``` The ID of the updated role. --- ## Type Alias: RoleUserFilterRequest ```ts type RoleUserFilterRequest = object; ``` Role user filter request ## Properties ### username? ```ts optional username?: StringFilterProperty; ``` The username search filters. --- ## Type Alias: RoleUserResult ```ts type RoleUserResult = object; ``` ## Properties ### username ```ts username: Username; ``` --- ## Type Alias: RoleUserSearchQueryRequest ```ts type RoleUserSearchQueryRequest = SearchQueryRequest & object; ``` ## Type Declaration ### filter? ```ts optional filter?: RoleUserFilterRequest; ``` The user filter criteria. ### sort? ```ts optional sort?: RoleUserSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: RoleUserSearchQuerySortRequest ```ts type RoleUserSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: "username"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: RoleUserSearchResult ```ts type RoleUserSearchResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: RoleUserResult[]; ``` The matching users. --- ## Type Alias: RuntimeBackupState ```ts type RuntimeBackupState = object; ``` Runtime Backup State Information about the checkpoint and backup state of the physical tenant. ## Properties ### backupStates ```ts backupStates: PartitionBackupState[]; ``` List of partition backup states. --- ### checkpointStates ```ts checkpointStates: PartitionCheckpointState[]; ``` List of partition checkpoint states. --- ### ranges ```ts ranges: PartitionBackupRange[]; ``` List of partition backup ranges. --- ## Type Alias: ScopeKey ```ts type ScopeKey = ProcessInstanceKey | ElementInstanceKey; ``` System-generated key for a scope. A scope can hold variables and represents either an element instance in a BPMN process or the process instance itself. --- ## Type Alias: ScopeKeyExactMatch ```ts type ScopeKeyExactMatch = ScopeKey; ``` Exact match Matches the value exactly. --- ## Type Alias: ScopeKeyExactMatchWritable ```ts type ScopeKeyExactMatchWritable = ScopeKeyWritable; ``` Exact match Matches the value exactly. --- ## Type Alias: ScopeKeyFilterProperty ```ts type ScopeKeyFilterProperty = ScopeKeyExactMatch | AdvancedScopeKeyFilter; ``` ScopeKey property with full advanced search capabilities. Filter by the key of the element instance or process instance that defines the scope of a variable. --- ## Type Alias: ScopeKeyWritable ```ts type ScopeKeyWritable = ProcessInstanceKeyWritable | ElementInstanceKeyWritable; ``` System-generated key for a scope. A scope can hold variables and represents either an element instance in a BPMN process or the process instance itself. --- ## Type Alias: SdkError ```ts type SdkError = | HttpSdkError | ValidationSdkError | AuthSdkError | NetworkSdkError | CancelSdkError | PresentWhenUnsupportedError; ``` --- ## Type Alias: SearchAgentDefinitionsData ```ts type SearchAgentDefinitionsData = object; ``` ## Properties ### body? ```ts optional body?: AgentDefinitionSearchQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/agent-definitions/search"; ``` --- ## Type Alias: SearchAgentDefinitionsError ```ts type SearchAgentDefinitionsError = SearchAgentDefinitionsErrors[keyof SearchAgentDefinitionsErrors]; ``` --- ## Type Alias: SearchAgentDefinitionsErrors ```ts type SearchAgentDefinitionsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchAgentDefinitionsResponse ```ts type SearchAgentDefinitionsResponse = SearchAgentDefinitionsResponses[keyof SearchAgentDefinitionsResponses]; ``` --- ## Type Alias: SearchAgentDefinitionsResponses ```ts type SearchAgentDefinitionsResponses = object; ``` ## Properties ### 200 ```ts 200: AgentDefinitionSearchQueryResult; ``` The agent definition search result. --- ## Type Alias: SearchAgentInstanceHistoryData ```ts type SearchAgentInstanceHistoryData = object; ``` ## Properties ### body? ```ts optional body?: AgentInstanceHistorySearchQuery; ``` --- ### path ```ts path: object; ``` #### agentInstanceKey ```ts agentInstanceKey: AgentInstanceKeyWritable; ``` The key of the agent instance whose history to search. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/agent-instances/{agentInstanceKey}/history/search"; ``` --- ## Type Alias: SearchAgentInstanceHistoryError ```ts type SearchAgentInstanceHistoryError = SearchAgentInstanceHistoryErrors[keyof SearchAgentInstanceHistoryErrors]; ``` --- ## Type Alias: SearchAgentInstanceHistoryErrors ```ts type SearchAgentInstanceHistoryErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The agent instance with the given key was not found. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchAgentInstanceHistoryResponse ```ts type SearchAgentInstanceHistoryResponse = SearchAgentInstanceHistoryResponses[keyof SearchAgentInstanceHistoryResponses]; ``` --- ## Type Alias: SearchAgentInstanceHistoryResponses ```ts type SearchAgentInstanceHistoryResponses = object; ``` ## Properties ### 200 ```ts 200: AgentInstanceHistorySearchQueryResult; ``` The agent instance history search result. --- ## Type Alias: SearchAgentInstancesData ```ts type SearchAgentInstancesData = object; ``` ## Properties ### body? ```ts optional body?: AgentInstanceSearchQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/agent-instances/search"; ``` --- ## Type Alias: SearchAgentInstancesError ```ts type SearchAgentInstancesError = SearchAgentInstancesErrors[keyof SearchAgentInstancesErrors]; ``` --- ## Type Alias: SearchAgentInstancesErrors ```ts type SearchAgentInstancesErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchAgentInstancesResponse ```ts type SearchAgentInstancesResponse = SearchAgentInstancesResponses[keyof SearchAgentInstancesResponses]; ``` --- ## Type Alias: SearchAgentInstancesResponses ```ts type SearchAgentInstancesResponses = object; ``` ## Properties ### 200 ```ts 200: AgentInstanceSearchQueryResult; ``` The agent instance search result. --- ## Type Alias: SearchAuditLogsData ```ts type SearchAuditLogsData = object; ``` ## Properties ### body? ```ts optional body?: AuditLogSearchQueryRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/audit-logs/search"; ``` --- ## Type Alias: SearchAuditLogsError ```ts type SearchAuditLogsError = SearchAuditLogsErrors[keyof SearchAuditLogsErrors]; ``` --- ## Type Alias: SearchAuditLogsErrors ```ts type SearchAuditLogsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: unknown; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchAuditLogsResponse ```ts type SearchAuditLogsResponse = SearchAuditLogsResponses[keyof SearchAuditLogsResponses]; ``` --- ## Type Alias: SearchAuditLogsResponses ```ts type SearchAuditLogsResponses = object; ``` ## Properties ### 200 ```ts 200: AuditLogSearchQueryResult; ``` The audit logs search result. --- ## Type Alias: SearchAuthorizationsData ```ts type SearchAuthorizationsData = object; ``` ## Properties ### body? ```ts optional body?: AuthorizationSearchQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/authorizations/search"; ``` --- ## Type Alias: SearchAuthorizationsError ```ts type SearchAuthorizationsError = SearchAuthorizationsErrors[keyof SearchAuthorizationsErrors]; ``` --- ## Type Alias: SearchAuthorizationsErrors ```ts type SearchAuthorizationsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchAuthorizationsResponse ```ts type SearchAuthorizationsResponse = SearchAuthorizationsResponses[keyof SearchAuthorizationsResponses]; ``` --- ## Type Alias: SearchAuthorizationsResponses ```ts type SearchAuthorizationsResponses = object; ``` ## Properties ### 200 ```ts 200: AuthorizationSearchResult; ``` The authorization search result. --- ## Type Alias: SearchBatchOperationItemsData ```ts type SearchBatchOperationItemsData = object; ``` ## Properties ### body? ```ts optional body?: BatchOperationItemSearchQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/batch-operation-items/search"; ``` --- ## Type Alias: SearchBatchOperationItemsError ```ts type SearchBatchOperationItemsError = SearchBatchOperationItemsErrors[keyof SearchBatchOperationItemsErrors]; ``` --- ## Type Alias: SearchBatchOperationItemsErrors ```ts type SearchBatchOperationItemsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchBatchOperationItemsResponse ```ts type SearchBatchOperationItemsResponse = SearchBatchOperationItemsResponses[keyof SearchBatchOperationItemsResponses]; ``` --- ## Type Alias: SearchBatchOperationItemsResponses ```ts type SearchBatchOperationItemsResponses = object; ``` ## Properties ### 200 ```ts 200: BatchOperationItemSearchQueryResult; ``` The batch operation search result. --- ## Type Alias: SearchBatchOperationsData ```ts type SearchBatchOperationsData = object; ``` ## Properties ### body? ```ts optional body?: BatchOperationSearchQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/batch-operations/search"; ``` --- ## Type Alias: SearchBatchOperationsError ```ts type SearchBatchOperationsError = SearchBatchOperationsErrors[keyof SearchBatchOperationsErrors]; ``` --- ## Type Alias: SearchBatchOperationsErrors ```ts type SearchBatchOperationsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchBatchOperationsResponse ```ts type SearchBatchOperationsResponse = SearchBatchOperationsResponses[keyof SearchBatchOperationsResponses]; ``` --- ## Type Alias: SearchBatchOperationsResponses ```ts type SearchBatchOperationsResponses = object; ``` ## Properties ### 200 ```ts 200: BatchOperationSearchQueryResult; ``` The batch operation search result. --- ## Type Alias: SearchBody ```ts type SearchBody = object & Record; ``` A request body that optionally carries a `page` block. ## Type Declaration ### page? ```ts optional page?: SearchPageRequest; ``` --- ## Type Alias: SearchClientsForGroupData ```ts type SearchClientsForGroupData = object; ``` ## Properties ### body? ```ts optional body?: GroupClientSearchQueryRequest; ``` --- ### path ```ts path: object; ``` #### groupId ```ts groupId: GroupId; ``` The group ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/groups/{groupId}/clients/search"; ``` --- ## Type Alias: SearchClientsForGroupError ```ts type SearchClientsForGroupError = SearchClientsForGroupErrors[keyof SearchClientsForGroupErrors]; ``` --- ## Type Alias: SearchClientsForGroupErrors ```ts type SearchClientsForGroupErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The group with the given ID was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchClientsForGroupResponse ```ts type SearchClientsForGroupResponse = SearchClientsForGroupResponses[keyof SearchClientsForGroupResponses]; ``` --- ## Type Alias: SearchClientsForGroupResponses ```ts type SearchClientsForGroupResponses = object; ``` ## Properties ### 200 ```ts 200: GroupClientSearchResult; ``` The clients assigned to the group. --- ## Type Alias: SearchClientsForRoleData ```ts type SearchClientsForRoleData = object; ``` ## Properties ### body? ```ts optional body?: RoleClientSearchQueryRequest; ``` --- ### path ```ts path: object; ``` #### roleId ```ts roleId: RoleId; ``` The role ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/roles/{roleId}/clients/search"; ``` --- ## Type Alias: SearchClientsForRoleError ```ts type SearchClientsForRoleError = SearchClientsForRoleErrors[keyof SearchClientsForRoleErrors]; ``` --- ## Type Alias: SearchClientsForRoleErrors ```ts type SearchClientsForRoleErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The role with the given ID was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchClientsForRoleResponse ```ts type SearchClientsForRoleResponse = SearchClientsForRoleResponses[keyof SearchClientsForRoleResponses]; ``` --- ## Type Alias: SearchClientsForRoleResponses ```ts type SearchClientsForRoleResponses = object; ``` ## Properties ### 200 ```ts 200: RoleClientSearchResult; ``` The clients with the assigned role. --- ## Type Alias: SearchClientsForTenantData ```ts type SearchClientsForTenantData = object; ``` ## Properties ### body? ```ts optional body?: TenantClientSearchQueryRequest; ``` --- ### path ```ts path: object; ``` #### tenantId ```ts tenantId: TenantId; ``` The unique identifier of the tenant. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/tenants/{tenantId}/clients/search"; ``` --- ## Type Alias: SearchClientsForTenantResponse ```ts type SearchClientsForTenantResponse = SearchClientsForTenantResponses[keyof SearchClientsForTenantResponses]; ``` --- ## Type Alias: SearchClientsForTenantResponses ```ts type SearchClientsForTenantResponses = object; ``` ## Properties ### 200 ```ts 200: TenantClientSearchResult; ``` The search result of users for the tenant. --- ## Type Alias: SearchClusterVariablesData ```ts type SearchClusterVariablesData = object; ``` ## Properties ### body? ```ts optional body?: ClusterVariableSearchQueryRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: object; ``` #### truncateValues? ```ts optional truncateValues?: boolean; ``` When true (default), long variable values in the response are truncated. When false, full variable values are returned. --- ### url ```ts url: "/cluster-variables/search"; ``` --- ## Type Alias: SearchClusterVariablesError ```ts type SearchClusterVariablesError = SearchClusterVariablesErrors[keyof SearchClusterVariablesErrors]; ``` --- ## Type Alias: SearchClusterVariablesErrors ```ts type SearchClusterVariablesErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchClusterVariablesResponse ```ts type SearchClusterVariablesResponse = SearchClusterVariablesResponses[keyof SearchClusterVariablesResponses]; ``` --- ## Type Alias: SearchClusterVariablesResponses ```ts type SearchClusterVariablesResponses = object; ``` ## Properties ### 200 ```ts 200: ClusterVariableSearchQueryResult; ``` The cluster variable search result. --- ## Type Alias: SearchCorrelatedMessageSubscriptionsData ```ts type SearchCorrelatedMessageSubscriptionsData = object; ``` ## Properties ### body? ```ts optional body?: CorrelatedMessageSubscriptionSearchQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/correlated-message-subscriptions/search"; ``` --- ## Type Alias: SearchCorrelatedMessageSubscriptionsError ```ts type SearchCorrelatedMessageSubscriptionsError = SearchCorrelatedMessageSubscriptionsErrors[keyof SearchCorrelatedMessageSubscriptionsErrors]; ``` --- ## Type Alias: SearchCorrelatedMessageSubscriptionsErrors ```ts type SearchCorrelatedMessageSubscriptionsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchCorrelatedMessageSubscriptionsResponse ```ts type SearchCorrelatedMessageSubscriptionsResponse = SearchCorrelatedMessageSubscriptionsResponses[keyof SearchCorrelatedMessageSubscriptionsResponses]; ``` --- ## Type Alias: SearchCorrelatedMessageSubscriptionsResponses ```ts type SearchCorrelatedMessageSubscriptionsResponses = object; ``` ## Properties ### 200 ```ts 200: CorrelatedMessageSubscriptionSearchQueryResult; ``` The correlated message subscriptions search result. --- ## Type Alias: SearchDecisionDefinitionsData ```ts type SearchDecisionDefinitionsData = object; ``` ## Properties ### body? ```ts optional body?: DecisionDefinitionSearchQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/decision-definitions/search"; ``` --- ## Type Alias: SearchDecisionDefinitionsError ```ts type SearchDecisionDefinitionsError = SearchDecisionDefinitionsErrors[keyof SearchDecisionDefinitionsErrors]; ``` --- ## Type Alias: SearchDecisionDefinitionsErrors ```ts type SearchDecisionDefinitionsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchDecisionDefinitionsResponse ```ts type SearchDecisionDefinitionsResponse = SearchDecisionDefinitionsResponses[keyof SearchDecisionDefinitionsResponses]; ``` --- ## Type Alias: SearchDecisionDefinitionsResponses ```ts type SearchDecisionDefinitionsResponses = object; ``` ## Properties ### 200 ```ts 200: DecisionDefinitionSearchQueryResult; ``` The decision definition search result. --- ## Type Alias: SearchDecisionInstancesData ```ts type SearchDecisionInstancesData = object; ``` ## Properties ### body? ```ts optional body?: DecisionInstanceSearchQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/decision-instances/search"; ``` --- ## Type Alias: SearchDecisionInstancesError ```ts type SearchDecisionInstancesError = SearchDecisionInstancesErrors[keyof SearchDecisionInstancesErrors]; ``` --- ## Type Alias: SearchDecisionInstancesErrors ```ts type SearchDecisionInstancesErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchDecisionInstancesResponse ```ts type SearchDecisionInstancesResponse = SearchDecisionInstancesResponses[keyof SearchDecisionInstancesResponses]; ``` --- ## Type Alias: SearchDecisionInstancesResponses ```ts type SearchDecisionInstancesResponses = object; ``` ## Properties ### 200 ```ts 200: DecisionInstanceSearchQueryResult; ``` The decision instance search result. --- ## Type Alias: SearchDecisionRequirementsData ```ts type SearchDecisionRequirementsData = object; ``` ## Properties ### body? ```ts optional body?: DecisionRequirementsSearchQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/decision-requirements/search"; ``` --- ## Type Alias: SearchDecisionRequirementsError ```ts type SearchDecisionRequirementsError = SearchDecisionRequirementsErrors[keyof SearchDecisionRequirementsErrors]; ``` --- ## Type Alias: SearchDecisionRequirementsErrors ```ts type SearchDecisionRequirementsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchDecisionRequirementsResponse ```ts type SearchDecisionRequirementsResponse = SearchDecisionRequirementsResponses[keyof SearchDecisionRequirementsResponses]; ``` --- ## Type Alias: SearchDecisionRequirementsResponses ```ts type SearchDecisionRequirementsResponses = object; ``` ## Properties ### 200 ```ts 200: DecisionRequirementsSearchQueryResult; ``` The decision requirements search result. --- ## Type Alias: SearchElementInstanceIncidentsData ```ts type SearchElementInstanceIncidentsData = object; ``` ## Properties ### body ```ts body: IncidentSearchQuery; ``` --- ### path ```ts path: object; ``` #### elementInstanceKey ```ts elementInstanceKey: ElementInstanceKeyWritable; ``` The unique key of the element instance to search incidents for. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/element-instances/{elementInstanceKey}/incidents/search"; ``` --- ## Type Alias: SearchElementInstanceIncidentsError ```ts type SearchElementInstanceIncidentsError = SearchElementInstanceIncidentsErrors[keyof SearchElementInstanceIncidentsErrors]; ``` --- ## Type Alias: SearchElementInstanceIncidentsErrors ```ts type SearchElementInstanceIncidentsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The element instance with the given key was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchElementInstanceIncidentsResponse ```ts type SearchElementInstanceIncidentsResponse = SearchElementInstanceIncidentsResponses[keyof SearchElementInstanceIncidentsResponses]; ``` --- ## Type Alias: SearchElementInstanceIncidentsResponses ```ts type SearchElementInstanceIncidentsResponses = object; ``` ## Properties ### 200 ```ts 200: IncidentSearchQueryResult; ``` The element instance incident search result. --- ## Type Alias: SearchElementInstanceWaitStatesData ```ts type SearchElementInstanceWaitStatesData = object; ``` ## Properties ### body? ```ts optional body?: ElementInstanceWaitStateQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/element-instances/wait-states/search"; ``` --- ## Type Alias: SearchElementInstanceWaitStatesError ```ts type SearchElementInstanceWaitStatesError = SearchElementInstanceWaitStatesErrors[keyof SearchElementInstanceWaitStatesErrors]; ``` --- ## Type Alias: SearchElementInstanceWaitStatesErrors ```ts type SearchElementInstanceWaitStatesErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchElementInstanceWaitStatesResponse ```ts type SearchElementInstanceWaitStatesResponse = SearchElementInstanceWaitStatesResponses[keyof SearchElementInstanceWaitStatesResponses]; ``` --- ## Type Alias: SearchElementInstanceWaitStatesResponses ```ts type SearchElementInstanceWaitStatesResponses = object; ``` ## Properties ### 200 ```ts 200: ElementInstanceWaitStateQueryResult; ``` The element instance wait state search result. --- ## Type Alias: SearchElementInstancesData ```ts type SearchElementInstancesData = object; ``` ## Properties ### body? ```ts optional body?: ElementInstanceSearchQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/element-instances/search"; ``` --- ## Type Alias: SearchElementInstancesError ```ts type SearchElementInstancesError = SearchElementInstancesErrors[keyof SearchElementInstancesErrors]; ``` --- ## Type Alias: SearchElementInstancesErrors ```ts type SearchElementInstancesErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchElementInstancesResponse ```ts type SearchElementInstancesResponse = SearchElementInstancesResponses[keyof SearchElementInstancesResponses]; ``` --- ## Type Alias: SearchElementInstancesResponses ```ts type SearchElementInstancesResponses = object; ``` ## Properties ### 200 ```ts 200: ElementInstanceSearchQueryResult; ``` The element instance search result. --- ## Type Alias: SearchGlobalTaskListenersData ```ts type SearchGlobalTaskListenersData = object; ``` ## Properties ### body? ```ts optional body?: GlobalTaskListenerSearchQueryRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/global-task-listeners/search"; ``` --- ## Type Alias: SearchGlobalTaskListenersError ```ts type SearchGlobalTaskListenersError = SearchGlobalTaskListenersErrors[keyof SearchGlobalTaskListenersErrors]; ``` --- ## Type Alias: SearchGlobalTaskListenersErrors ```ts type SearchGlobalTaskListenersErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchGlobalTaskListenersResponse ```ts type SearchGlobalTaskListenersResponse = SearchGlobalTaskListenersResponses[keyof SearchGlobalTaskListenersResponses]; ``` --- ## Type Alias: SearchGlobalTaskListenersResponses ```ts type SearchGlobalTaskListenersResponses = object; ``` ## Properties ### 200 ```ts 200: GlobalTaskListenerSearchQueryResult; ``` The global user task listener search result. --- ## Type Alias: SearchGroupIdsForTenantData ```ts type SearchGroupIdsForTenantData = object; ``` ## Properties ### body? ```ts optional body?: TenantGroupSearchQueryRequest; ``` --- ### path ```ts path: object; ``` #### tenantId ```ts tenantId: TenantId; ``` The unique identifier of the tenant. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/tenants/{tenantId}/groups/search"; ``` --- ## Type Alias: SearchGroupIdsForTenantResponse ```ts type SearchGroupIdsForTenantResponse = SearchGroupIdsForTenantResponses[keyof SearchGroupIdsForTenantResponses]; ``` --- ## Type Alias: SearchGroupIdsForTenantResponses ```ts type SearchGroupIdsForTenantResponses = object; ``` ## Properties ### 200 ```ts 200: TenantGroupSearchResult; ``` The search result of groups for the tenant. --- ## Type Alias: SearchGroupsData ```ts type SearchGroupsData = object; ``` ## Properties ### body? ```ts optional body?: GroupSearchQueryRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/groups/search"; ``` --- ## Type Alias: SearchGroupsError ```ts type SearchGroupsError = SearchGroupsErrors[keyof SearchGroupsErrors]; ``` --- ## Type Alias: SearchGroupsErrors ```ts type SearchGroupsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchGroupsForRoleData ```ts type SearchGroupsForRoleData = object; ``` ## Properties ### body? ```ts optional body?: RoleGroupSearchQueryRequest; ``` --- ### path ```ts path: object; ``` #### roleId ```ts roleId: RoleId; ``` The role ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/roles/{roleId}/groups/search"; ``` --- ## Type Alias: SearchGroupsForRoleError ```ts type SearchGroupsForRoleError = SearchGroupsForRoleErrors[keyof SearchGroupsForRoleErrors]; ``` --- ## Type Alias: SearchGroupsForRoleErrors ```ts type SearchGroupsForRoleErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The role with the given ID was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchGroupsForRoleResponse ```ts type SearchGroupsForRoleResponse = SearchGroupsForRoleResponses[keyof SearchGroupsForRoleResponses]; ``` --- ## Type Alias: SearchGroupsForRoleResponses ```ts type SearchGroupsForRoleResponses = object; ``` ## Properties ### 200 ```ts 200: RoleGroupSearchResult; ``` The groups with assigned role. --- ## Type Alias: SearchGroupsResponse ```ts type SearchGroupsResponse = SearchGroupsResponses[keyof SearchGroupsResponses]; ``` --- ## Type Alias: SearchGroupsResponses ```ts type SearchGroupsResponses = object; ``` ## Properties ### 200 ```ts 200: GroupSearchQueryResult; ``` The groups search result. --- ## Type Alias: SearchIncidentsData ```ts type SearchIncidentsData = object; ``` ## Properties ### body? ```ts optional body?: IncidentSearchQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/incidents/search"; ``` --- ## Type Alias: SearchIncidentsError ```ts type SearchIncidentsError = SearchIncidentsErrors[keyof SearchIncidentsErrors]; ``` --- ## Type Alias: SearchIncidentsErrors ```ts type SearchIncidentsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchIncidentsResponse ```ts type SearchIncidentsResponse = SearchIncidentsResponses[keyof SearchIncidentsResponses]; ``` --- ## Type Alias: SearchIncidentsResponses ```ts type SearchIncidentsResponses = object; ``` ## Properties ### 200 ```ts 200: IncidentSearchQueryResult; ``` The incident search result. --- ## Type Alias: SearchJobsData ```ts type SearchJobsData = object; ``` ## Properties ### body? ```ts optional body?: JobSearchQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/jobs/search"; ``` --- ## Type Alias: SearchJobsError ```ts type SearchJobsError = SearchJobsErrors[keyof SearchJobsErrors]; ``` --- ## Type Alias: SearchJobsErrors ```ts type SearchJobsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchJobsResponse ```ts type SearchJobsResponse = SearchJobsResponses[keyof SearchJobsResponses]; ``` --- ## Type Alias: SearchJobsResponses ```ts type SearchJobsResponses = object; ``` ## Properties ### 200 ```ts 200: JobSearchQueryResult; ``` The job search result. --- ## Type Alias: SearchMappingRuleData ```ts type SearchMappingRuleData = object; ``` ## Properties ### body? ```ts optional body?: MappingRuleSearchQueryRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/mapping-rules/search"; ``` --- ## Type Alias: SearchMappingRuleError ```ts type SearchMappingRuleError = SearchMappingRuleErrors[keyof SearchMappingRuleErrors]; ``` --- ## Type Alias: SearchMappingRuleErrors ```ts type SearchMappingRuleErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchMappingRuleResponse ```ts type SearchMappingRuleResponse = SearchMappingRuleResponses[keyof SearchMappingRuleResponses]; ``` --- ## Type Alias: SearchMappingRuleResponses ```ts type SearchMappingRuleResponses = object; ``` ## Properties ### 200 ```ts 200: MappingRuleSearchQueryResult; ``` The mapping rule search result. --- ## Type Alias: SearchMappingRulesForGroupData ```ts type SearchMappingRulesForGroupData = object; ``` ## Properties ### body? ```ts optional body?: MappingRuleSearchQueryRequest; ``` --- ### path ```ts path: object; ``` #### groupId ```ts groupId: GroupId; ``` The group ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/groups/{groupId}/mapping-rules/search"; ``` --- ## Type Alias: SearchMappingRulesForGroupError ```ts type SearchMappingRulesForGroupError = SearchMappingRulesForGroupErrors[keyof SearchMappingRulesForGroupErrors]; ``` --- ## Type Alias: SearchMappingRulesForGroupErrors ```ts type SearchMappingRulesForGroupErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The group with the given ID was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchMappingRulesForGroupResponse ```ts type SearchMappingRulesForGroupResponse = SearchMappingRulesForGroupResponses[keyof SearchMappingRulesForGroupResponses]; ``` --- ## Type Alias: SearchMappingRulesForGroupResponses ```ts type SearchMappingRulesForGroupResponses = object; ``` ## Properties ### 200 ```ts 200: GroupMappingRuleSearchResult; ``` The mapping rules assigned to the group. --- ## Type Alias: SearchMappingRulesForRoleData ```ts type SearchMappingRulesForRoleData = object; ``` ## Properties ### body? ```ts optional body?: MappingRuleSearchQueryRequest; ``` --- ### path ```ts path: object; ``` #### roleId ```ts roleId: RoleId; ``` The role ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/roles/{roleId}/mapping-rules/search"; ``` --- ## Type Alias: SearchMappingRulesForRoleError ```ts type SearchMappingRulesForRoleError = SearchMappingRulesForRoleErrors[keyof SearchMappingRulesForRoleErrors]; ``` --- ## Type Alias: SearchMappingRulesForRoleErrors ```ts type SearchMappingRulesForRoleErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The role with the given ID was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchMappingRulesForRoleResponse ```ts type SearchMappingRulesForRoleResponse = SearchMappingRulesForRoleResponses[keyof SearchMappingRulesForRoleResponses]; ``` --- ## Type Alias: SearchMappingRulesForRoleResponses ```ts type SearchMappingRulesForRoleResponses = object; ``` ## Properties ### 200 ```ts 200: RoleMappingRuleSearchResult; ``` The mapping rules with assigned role. --- ## Type Alias: SearchMappingRulesForTenantData ```ts type SearchMappingRulesForTenantData = object; ``` ## Properties ### body? ```ts optional body?: MappingRuleSearchQueryRequest; ``` --- ### path ```ts path: object; ``` #### tenantId ```ts tenantId: TenantId; ``` The unique identifier of the tenant. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/tenants/{tenantId}/mapping-rules/search"; ``` --- ## Type Alias: SearchMappingRulesForTenantResponse ```ts type SearchMappingRulesForTenantResponse = SearchMappingRulesForTenantResponses[keyof SearchMappingRulesForTenantResponses]; ``` --- ## Type Alias: SearchMappingRulesForTenantResponses ```ts type SearchMappingRulesForTenantResponses = object; ``` ## Properties ### 200 ```ts 200: TenantMappingRuleSearchResult; ``` The search result of MappingRules for the tenant. --- ## Type Alias: SearchMessageSubscriptionsData ```ts type SearchMessageSubscriptionsData = object; ``` ## Properties ### body? ```ts optional body?: MessageSubscriptionSearchQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/message-subscriptions/search"; ``` --- ## Type Alias: SearchMessageSubscriptionsError ```ts type SearchMessageSubscriptionsError = SearchMessageSubscriptionsErrors[keyof SearchMessageSubscriptionsErrors]; ``` --- ## Type Alias: SearchMessageSubscriptionsErrors ```ts type SearchMessageSubscriptionsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchMessageSubscriptionsResponse ```ts type SearchMessageSubscriptionsResponse = SearchMessageSubscriptionsResponses[keyof SearchMessageSubscriptionsResponses]; ``` --- ## Type Alias: SearchMessageSubscriptionsResponses ```ts type SearchMessageSubscriptionsResponses = object; ``` ## Properties ### 200 ```ts 200: MessageSubscriptionSearchQueryResult; ``` The message subscription search result. --- ## Type Alias: SearchOwnAuthorizationsData ```ts type SearchOwnAuthorizationsData = object; ``` ## Properties ### body? ```ts optional body?: AuthorizationSearchQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/authentication/me/authorizations/search"; ``` --- ## Type Alias: SearchOwnAuthorizationsError ```ts type SearchOwnAuthorizationsError = SearchOwnAuthorizationsErrors[keyof SearchOwnAuthorizationsErrors]; ``` --- ## Type Alias: SearchOwnAuthorizationsErrors ```ts type SearchOwnAuthorizationsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchOwnAuthorizationsResponse ```ts type SearchOwnAuthorizationsResponse = SearchOwnAuthorizationsResponses[keyof SearchOwnAuthorizationsResponses]; ``` --- ## Type Alias: SearchOwnAuthorizationsResponses ```ts type SearchOwnAuthorizationsResponses = object; ``` ## Properties ### 200 ```ts 200: OwnAuthorizationSearchResult; ``` The authorization search result. --- ## Type Alias: SearchPaginationApi # Type Alias: SearchPaginationApi\ ```ts type SearchPaginationApi = { [ K in keyof C as K extends `search${string}` ? SearchSignature extends { result: infer D } ? [ItemsOf] extends [never] ? never : K : never : never ]: SearchSignature extends { body: infer B; result: infer D } ? { paginate: any } : never; }; ``` For a client type `C`, the set of `.paginate` methods to intersect onto it. Only `search*` keys whose response actually carries `items` are included. ## Type Parameters ### C `C` --- ## Type Alias: SearchProcessDefinitionVariableNamesData ```ts type SearchProcessDefinitionVariableNamesData = object; ``` ## Properties ### body? ```ts optional body?: ProcessDefinitionVariableNameSearchQuery; ``` --- ### path ```ts path: object; ``` #### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKeyWritable; ``` The assigned key of the process definition, which acts as a unique identifier for this process definition. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-definitions/{processDefinitionKey}/variable-names/search"; ``` --- ## Type Alias: SearchProcessDefinitionVariableNamesError ```ts type SearchProcessDefinitionVariableNamesError = SearchProcessDefinitionVariableNamesErrors[keyof SearchProcessDefinitionVariableNamesErrors]; ``` --- ## Type Alias: SearchProcessDefinitionVariableNamesErrors ```ts type SearchProcessDefinitionVariableNamesErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchProcessDefinitionVariableNamesResponse ```ts type SearchProcessDefinitionVariableNamesResponse = SearchProcessDefinitionVariableNamesResponses[keyof SearchProcessDefinitionVariableNamesResponses]; ``` --- ## Type Alias: SearchProcessDefinitionVariableNamesResponses ```ts type SearchProcessDefinitionVariableNamesResponses = object; ``` ## Properties ### 200 ```ts 200: ProcessDefinitionVariableNameSearchQueryResult; ``` The process definition variable name search result. --- ## Type Alias: SearchProcessDefinitionsData ```ts type SearchProcessDefinitionsData = object; ``` ## Properties ### body? ```ts optional body?: ProcessDefinitionSearchQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-definitions/search"; ``` --- ## Type Alias: SearchProcessDefinitionsError ```ts type SearchProcessDefinitionsError = SearchProcessDefinitionsErrors[keyof SearchProcessDefinitionsErrors]; ``` --- ## Type Alias: SearchProcessDefinitionsErrors ```ts type SearchProcessDefinitionsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchProcessDefinitionsResponse ```ts type SearchProcessDefinitionsResponse = SearchProcessDefinitionsResponses[keyof SearchProcessDefinitionsResponses]; ``` --- ## Type Alias: SearchProcessDefinitionsResponses ```ts type SearchProcessDefinitionsResponses = object; ``` ## Properties ### 200 ```ts 200: ProcessDefinitionSearchQueryResult; ``` The process definition search result. --- ## Type Alias: SearchProcessInstanceIncidentsData ```ts type SearchProcessInstanceIncidentsData = object; ``` ## Properties ### body? ```ts optional body?: IncidentSearchQuery; ``` --- ### path ```ts path: object; ``` #### processInstanceKey ```ts processInstanceKey: ProcessInstanceKeyWritable; ``` The assigned key of the process instance, which acts as a unique identifier for this process instance. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/{processInstanceKey}/incidents/search"; ``` --- ## Type Alias: SearchProcessInstanceIncidentsError ```ts type SearchProcessInstanceIncidentsError = SearchProcessInstanceIncidentsErrors[keyof SearchProcessInstanceIncidentsErrors]; ``` --- ## Type Alias: SearchProcessInstanceIncidentsErrors ```ts type SearchProcessInstanceIncidentsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The process instance with the given key was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchProcessInstanceIncidentsResponse ```ts type SearchProcessInstanceIncidentsResponse = SearchProcessInstanceIncidentsResponses[keyof SearchProcessInstanceIncidentsResponses]; ``` --- ## Type Alias: SearchProcessInstanceIncidentsResponses ```ts type SearchProcessInstanceIncidentsResponses = object; ``` ## Properties ### 200 ```ts 200: IncidentSearchQueryResult; ``` The process instance search result. --- ## Type Alias: SearchProcessInstancesData ```ts type SearchProcessInstancesData = object; ``` ## Properties ### body? ```ts optional body?: ProcessInstanceSearchQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/search"; ``` --- ## Type Alias: SearchProcessInstancesError ```ts type SearchProcessInstancesError = SearchProcessInstancesErrors[keyof SearchProcessInstancesErrors]; ``` --- ## Type Alias: SearchProcessInstancesErrors ```ts type SearchProcessInstancesErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchProcessInstancesResponse ```ts type SearchProcessInstancesResponse = SearchProcessInstancesResponses[keyof SearchProcessInstancesResponses]; ``` --- ## Type Alias: SearchProcessInstancesResponses ```ts type SearchProcessInstancesResponses = object; ``` ## Properties ### 200 ```ts 200: ProcessInstanceSearchQueryResult; ``` The process instance search result. --- ## Type Alias: SearchQueryPageRequest ```ts type SearchQueryPageRequest = | LimitPagination | OffsetPagination | CursorForwardPagination | CursorBackwardPagination; ``` Pagination criteria. Can use offset-based pagination (from/limit) OR cursor-based pagination (after/before + limit), but not both. --- ## Type Alias: SearchQueryPageResponse ```ts type SearchQueryPageResponse = object; ``` Pagination information about the search results. ## Properties ### endCursor ```ts endCursor: EndCursor | null; ``` The cursor value for getting the next page of results. Use this in the `after` field of an ensuing request. --- ### hasMoreTotalItems ```ts hasMoreTotalItems: boolean; ``` Indicates whether the `totalItems` value has been capped due to system limits. When true, `totalItems` is a lower bound and the actual number of matching items is greater than the reported value. --- ### startCursor ```ts startCursor: StartCursor | null; ``` The cursor value for getting the previous page of results. Use this in the `before` field of an ensuing request. --- ### totalItems ```ts totalItems: number; ``` Total items matching the criteria. --- ## Type Alias: SearchQueryRequest ```ts type SearchQueryRequest = object; ``` ## Properties ### page? ```ts optional page?: SearchQueryPageRequest; ``` Pagination criteria. --- ## Type Alias: SearchQueryResponse ```ts type SearchQueryResponse = object; ``` ## Properties ### page ```ts page: SearchQueryPageResponse; ``` --- ## Type Alias: SearchResourcesData ```ts type SearchResourcesData = object; ``` ## Properties ### body? ```ts optional body?: ResourceSearchQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/resources/search"; ``` --- ## Type Alias: SearchResourcesError ```ts type SearchResourcesError = SearchResourcesErrors[keyof SearchResourcesErrors]; ``` --- ## Type Alias: SearchResourcesErrors ```ts type SearchResourcesErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchResourcesResponse ```ts type SearchResourcesResponse = SearchResourcesResponses[keyof SearchResourcesResponses]; ``` --- ## Type Alias: SearchResourcesResponses ```ts type SearchResourcesResponses = object; ``` ## Properties ### 200 ```ts 200: ResourceSearchQueryResult; ``` The resource search result. --- ## Type Alias: SearchRolesData ```ts type SearchRolesData = object; ``` ## Properties ### body? ```ts optional body?: RoleSearchQueryRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/roles/search"; ``` --- ## Type Alias: SearchRolesError ```ts type SearchRolesError = SearchRolesErrors[keyof SearchRolesErrors]; ``` --- ## Type Alias: SearchRolesErrors ```ts type SearchRolesErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchRolesForGroupData ```ts type SearchRolesForGroupData = object; ``` ## Properties ### body? ```ts optional body?: RoleSearchQueryRequest; ``` --- ### path ```ts path: object; ``` #### groupId ```ts groupId: GroupId; ``` The group ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/groups/{groupId}/roles/search"; ``` --- ## Type Alias: SearchRolesForGroupError ```ts type SearchRolesForGroupError = SearchRolesForGroupErrors[keyof SearchRolesForGroupErrors]; ``` --- ## Type Alias: SearchRolesForGroupErrors ```ts type SearchRolesForGroupErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The group with the given ID was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchRolesForGroupResponse ```ts type SearchRolesForGroupResponse = SearchRolesForGroupResponses[keyof SearchRolesForGroupResponses]; ``` --- ## Type Alias: SearchRolesForGroupResponses ```ts type SearchRolesForGroupResponses = object; ``` ## Properties ### 200 ```ts 200: GroupRoleSearchResult; ``` The roles assigned to the group. --- ## Type Alias: SearchRolesForTenantData ```ts type SearchRolesForTenantData = object; ``` ## Properties ### body? ```ts optional body?: RoleSearchQueryRequest; ``` --- ### path ```ts path: object; ``` #### tenantId ```ts tenantId: TenantId; ``` The unique identifier of the tenant. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/tenants/{tenantId}/roles/search"; ``` --- ## Type Alias: SearchRolesForTenantResponse ```ts type SearchRolesForTenantResponse = SearchRolesForTenantResponses[keyof SearchRolesForTenantResponses]; ``` --- ## Type Alias: SearchRolesForTenantResponses ```ts type SearchRolesForTenantResponses = object; ``` ## Properties ### 200 ```ts 200: TenantRoleSearchResult; ``` The search result of roles for the tenant. --- ## Type Alias: SearchRolesResponse ```ts type SearchRolesResponse = SearchRolesResponses[keyof SearchRolesResponses]; ``` --- ## Type Alias: SearchRolesResponses ```ts type SearchRolesResponses = object; ``` ## Properties ### 200 ```ts 200: RoleSearchQueryResult; ``` The roles search result. --- ## Type Alias: SearchTenantsData ```ts type SearchTenantsData = object; ``` ## Properties ### body? ```ts optional body?: TenantSearchQueryRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/tenants/search"; ``` --- ## Type Alias: SearchTenantsError ```ts type SearchTenantsError = SearchTenantsErrors[keyof SearchTenantsErrors]; ``` --- ## Type Alias: SearchTenantsErrors ```ts type SearchTenantsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Not found --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchTenantsResponse ```ts type SearchTenantsResponse = SearchTenantsResponses[keyof SearchTenantsResponses]; ``` --- ## Type Alias: SearchTenantsResponses ```ts type SearchTenantsResponses = object; ``` ## Properties ### 200 ```ts 200: TenantSearchQueryResult; ``` The tenants search result --- ## Type Alias: SearchUserTaskAuditLogsData ```ts type SearchUserTaskAuditLogsData = object; ``` ## Properties ### body? ```ts optional body?: UserTaskAuditLogSearchQueryRequest; ``` --- ### path ```ts path: object; ``` #### userTaskKey ```ts userTaskKey: UserTaskKeyWritable; ``` The key of the user task. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/user-tasks/{userTaskKey}/audit-logs/search"; ``` --- ## Type Alias: SearchUserTaskAuditLogsError ```ts type SearchUserTaskAuditLogsError = SearchUserTaskAuditLogsErrors[keyof SearchUserTaskAuditLogsErrors]; ``` --- ## Type Alias: SearchUserTaskAuditLogsErrors ```ts type SearchUserTaskAuditLogsErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchUserTaskAuditLogsResponse ```ts type SearchUserTaskAuditLogsResponse = SearchUserTaskAuditLogsResponses[keyof SearchUserTaskAuditLogsResponses]; ``` --- ## Type Alias: SearchUserTaskAuditLogsResponses ```ts type SearchUserTaskAuditLogsResponses = object; ``` ## Properties ### 200 ```ts 200: AuditLogSearchQueryResult; ``` The user task audit log search result. --- ## Type Alias: SearchUserTaskEffectiveVariablesData ```ts type SearchUserTaskEffectiveVariablesData = object; ``` ## Properties ### body? ```ts optional body?: UserTaskEffectiveVariableSearchQueryRequest; ``` --- ### path ```ts path: object; ``` #### userTaskKey ```ts userTaskKey: UserTaskKeyWritable; ``` The key of the user task. --- ### query? ```ts optional query?: object; ``` #### truncateValues? ```ts optional truncateValues?: boolean; ``` When true (default), long variable values in the response are truncated. When false, full variable values are returned. --- ### url ```ts url: "/user-tasks/{userTaskKey}/effective-variables/search"; ``` --- ## Type Alias: SearchUserTaskEffectiveVariablesError ```ts type SearchUserTaskEffectiveVariablesError = SearchUserTaskEffectiveVariablesErrors[keyof SearchUserTaskEffectiveVariablesErrors]; ``` --- ## Type Alias: SearchUserTaskEffectiveVariablesErrors ```ts type SearchUserTaskEffectiveVariablesErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchUserTaskEffectiveVariablesResponse ```ts type SearchUserTaskEffectiveVariablesResponse = SearchUserTaskEffectiveVariablesResponses[keyof SearchUserTaskEffectiveVariablesResponses]; ``` --- ## Type Alias: SearchUserTaskEffectiveVariablesResponses ```ts type SearchUserTaskEffectiveVariablesResponses = object; ``` ## Properties ### 200 ```ts 200: VariableSearchQueryResult; ``` The user task effective variable search result. --- ## Type Alias: SearchUserTaskVariablesData ```ts type SearchUserTaskVariablesData = object; ``` ## Properties ### body? ```ts optional body?: UserTaskVariableSearchQueryRequest; ``` --- ### path ```ts path: object; ``` #### userTaskKey ```ts userTaskKey: UserTaskKeyWritable; ``` The key of the user task. --- ### query? ```ts optional query?: object; ``` #### truncateValues? ```ts optional truncateValues?: boolean; ``` When true (default), long variable values in the response are truncated. When false, full variable values are returned. --- ### url ```ts url: "/user-tasks/{userTaskKey}/variables/search"; ``` --- ## Type Alias: SearchUserTaskVariablesError ```ts type SearchUserTaskVariablesError = SearchUserTaskVariablesErrors[keyof SearchUserTaskVariablesErrors]; ``` --- ## Type Alias: SearchUserTaskVariablesErrors ```ts type SearchUserTaskVariablesErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchUserTaskVariablesResponse ```ts type SearchUserTaskVariablesResponse = SearchUserTaskVariablesResponses[keyof SearchUserTaskVariablesResponses]; ``` --- ## Type Alias: SearchUserTaskVariablesResponses ```ts type SearchUserTaskVariablesResponses = object; ``` ## Properties ### 200 ```ts 200: VariableSearchQueryResult; ``` The user task variable search result. --- ## Type Alias: SearchUserTasksData ```ts type SearchUserTasksData = object; ``` ## Properties ### body? ```ts optional body?: UserTaskSearchQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/user-tasks/search"; ``` --- ## Type Alias: SearchUserTasksError ```ts type SearchUserTasksError = SearchUserTasksErrors[keyof SearchUserTasksErrors]; ``` --- ## Type Alias: SearchUserTasksErrors ```ts type SearchUserTasksErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchUserTasksResponse ```ts type SearchUserTasksResponse = SearchUserTasksResponses[keyof SearchUserTasksResponses]; ``` --- ## Type Alias: SearchUserTasksResponses ```ts type SearchUserTasksResponses = object; ``` ## Properties ### 200 ```ts 200: UserTaskSearchQueryResult; ``` The user task search result. --- ## Type Alias: SearchUsersData ```ts type SearchUsersData = object; ``` ## Properties ### body? ```ts optional body?: UserSearchQueryRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/users/search"; ``` --- ## Type Alias: SearchUsersError ```ts type SearchUsersError = SearchUsersErrors[keyof SearchUsersErrors]; ``` --- ## Type Alias: SearchUsersErrors ```ts type SearchUsersErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchUsersForGroupData ```ts type SearchUsersForGroupData = object; ``` ## Properties ### body? ```ts optional body?: GroupUserSearchQueryRequest; ``` --- ### path ```ts path: object; ``` #### groupId ```ts groupId: GroupId; ``` The group ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/groups/{groupId}/users/search"; ``` --- ## Type Alias: SearchUsersForGroupError ```ts type SearchUsersForGroupError = SearchUsersForGroupErrors[keyof SearchUsersForGroupErrors]; ``` --- ## Type Alias: SearchUsersForGroupErrors ```ts type SearchUsersForGroupErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The group with the given ID was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchUsersForGroupResponse ```ts type SearchUsersForGroupResponse = SearchUsersForGroupResponses[keyof SearchUsersForGroupResponses]; ``` --- ## Type Alias: SearchUsersForGroupResponses ```ts type SearchUsersForGroupResponses = object; ``` ## Properties ### 200 ```ts 200: GroupUserSearchResult; ``` The users assigned to the group. --- ## Type Alias: SearchUsersForRoleData ```ts type SearchUsersForRoleData = object; ``` ## Properties ### body? ```ts optional body?: RoleUserSearchQueryRequest; ``` --- ### path ```ts path: object; ``` #### roleId ```ts roleId: RoleId; ``` The role ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/roles/{roleId}/users/search"; ``` --- ## Type Alias: SearchUsersForRoleError ```ts type SearchUsersForRoleError = SearchUsersForRoleErrors[keyof SearchUsersForRoleErrors]; ``` --- ## Type Alias: SearchUsersForRoleErrors ```ts type SearchUsersForRoleErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The role with the given ID was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchUsersForRoleResponse ```ts type SearchUsersForRoleResponse = SearchUsersForRoleResponses[keyof SearchUsersForRoleResponses]; ``` --- ## Type Alias: SearchUsersForRoleResponses ```ts type SearchUsersForRoleResponses = object; ``` ## Properties ### 200 ```ts 200: RoleUserSearchResult; ``` The users with the assigned role. --- ## Type Alias: SearchUsersForTenantData ```ts type SearchUsersForTenantData = object; ``` ## Properties ### body? ```ts optional body?: TenantUserSearchQueryRequest; ``` --- ### path ```ts path: object; ``` #### tenantId ```ts tenantId: TenantId; ``` The unique identifier of the tenant. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/tenants/{tenantId}/users/search"; ``` --- ## Type Alias: SearchUsersForTenantResponse ```ts type SearchUsersForTenantResponse = SearchUsersForTenantResponses[keyof SearchUsersForTenantResponses]; ``` --- ## Type Alias: SearchUsersForTenantResponses ```ts type SearchUsersForTenantResponses = object; ``` ## Properties ### 200 ```ts 200: TenantUserSearchResult; ``` The search result of users for the tenant. --- ## Type Alias: SearchUsersResponse ```ts type SearchUsersResponse = SearchUsersResponses[keyof SearchUsersResponses]; ``` --- ## Type Alias: SearchUsersResponses ```ts type SearchUsersResponses = object; ``` ## Properties ### 200 ```ts 200: UserSearchResult; ``` The user search result. --- ## Type Alias: SearchVariablesData ```ts type SearchVariablesData = object; ``` ## Properties ### body? ```ts optional body?: VariableSearchQuery; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: object; ``` #### truncateValues? ```ts optional truncateValues?: boolean; ``` When true (default), long variable values in the response are truncated. When false, full variable values are returned. --- ### url ```ts url: "/variables/search"; ``` --- ## Type Alias: SearchVariablesError ```ts type SearchVariablesError = SearchVariablesErrors[keyof SearchVariablesErrors]; ``` --- ## Type Alias: SearchVariablesErrors ```ts type SearchVariablesErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SearchVariablesResponse ```ts type SearchVariablesResponse = SearchVariablesResponses[keyof SearchVariablesResponses]; ``` --- ## Type Alias: SearchVariablesResponses ```ts type SearchVariablesResponses = object; ``` ## Properties ### 200 ```ts 200: VariableSearchQueryResult; ``` The variable search result. --- ## Type Alias: SecretErrorCode ```ts type SecretErrorCode = "NOT_FOUND" | "ACCESS_DENIED" | "INVALID_REFERENCE" | "UNREADABLE"; ``` The typed reason a reference could not be resolved. - `NOT_FOUND`: no secret exists for the reference. - `ACCESS_DENIED`: the caller lacks `SECRET:REVEAL` on the reference. - `INVALID_REFERENCE`: the reference is malformed, or the configured store rejected it as an invalid secret identifier. - `UNREADABLE`: the configured store could not return a value for the reference, for example because it rejected the cluster's own store credentials or the stored value could not be read. Whether the secret exists is not implied. --- ## Type Alias: SecretListRequest ```ts type SecretListRequest = object; ``` Reserved for future filtering options. Currently takes no properties. The request body is optional: omitting it (or sending an empty object) applies no filters. ## Index Signature ```ts [key: string]: never ``` --- ## Type Alias: SecretListResult ```ts type SecretListResult = object; ``` The secret references the caller is authorized to see. Unbounded: the response carries the configured stores' full enumeration for the physical tenant. ## Properties ### references ```ts references: string[]; ``` The secret references, each of the form `camunda.secrets.`. --- ## Type Alias: SecretResolutionError ```ts type SecretResolutionError = object; ``` ## Properties ### code ```ts code: SecretErrorCode; ``` --- ### message ```ts message: string; ``` A human-readable description of the failure. Never contains the secret value; only error metadata (codes, names) is included. --- ### reference ```ts reference: string; ``` The secret reference that could not be resolved. --- ## Type Alias: SecretResolveRequest ```ts type SecretResolveRequest = object; ``` ## Properties ### references ```ts references: string[]; ``` The secret references to resolve, each of the form `camunda.secrets.`. Duplicate references are deduplicated by the server and resolved once. At most 20 references may be requested in a single batch. --- ## Type Alias: SecretResolveResult ```ts type SecretResolveResult = object; ``` The per-reference outcome of a resolve request. ## Properties ### errors ```ts errors: SecretResolutionError[]; ``` The references that could not be resolved, each with a typed error code. --- ### resolved ```ts resolved: ResolvedSecret[]; ``` The references that were successfully resolved. --- ## Type Alias: SetVariableRequest ```ts type SetVariableRequest = object; ``` ## Properties ### local? ```ts optional local?: boolean; ``` If set to `true`, the variables are merged strictly into the local scope (as specified by the `elementInstanceKey`). Otherwise, the variables are propagated to upper scopes and set at the outermost one. Let's consider the following example: There are two scopes '1' and '2'. Scope '1' is the parent scope of '2'. The effective variables of the scopes are: 1 => { "foo" : 2 } 2 => { "bar" : 1 } An update request with elementInstanceKey as '2', variables { "foo": 5 }, and local set to `true` leaves scope '1' unchanged and adjusts scope '2' to { "bar": 1, "foo": 5 }. By default, with local set to `false`, scope '1' will be { "foo": 5 } and scope '2' will be { "bar": 1 }. --- ### operationReference? ```ts optional operationReference?: OperationReference; ``` --- ### variables ```ts variables: object; ``` JSON object representing the variables to set in the element’s scope. #### Index Signature ```ts [key: string]: unknown ``` --- ## Type Alias: SignalBroadcastRequest ```ts type SignalBroadcastRequest = object; ``` ## Properties ### signalName ```ts signalName: string; ``` The name of the signal to broadcast. --- ### tenantId? ```ts optional tenantId?: TenantId; ``` The ID of the tenant that owns the signal. --- ### variables? ```ts optional variables?: object; ``` The signal variables as a JSON object. #### Index Signature ```ts [key: string]: unknown ``` --- ## Type Alias: SignalBroadcastResult ```ts type SignalBroadcastResult = object; ``` ## Properties ### signalKey ```ts signalKey: SignalKey; ``` The key of the broadcasted signal. --- ### tenantId ```ts tenantId: TenantId; ``` The tenant ID of the signal that was broadcast. --- ## Type Alias: SignalKey ```ts type SignalKey = CamundaKey<"SignalKey">; ``` System-generated key for an signal. --- ## Type Alias: SignalKeyWritable ```ts type SignalKeyWritable = LongKey; ``` System-generated key for an signal. --- ## Type Alias: SignalWaitStateDetails ```ts type SignalWaitStateDetails = BaseWaitStateDetails & object; ``` ## Type Declaration ### signalName ```ts signalName: string; ``` The name of the signal being awaited. --- ## Type Alias: SortOrderEnum ```ts type SortOrderEnum = (typeof SortOrderEnum)[keyof typeof SortOrderEnum]; ``` The order in which to sort the related field. --- ## Type Alias: SourceElementIdInstruction ```ts type SourceElementIdInstruction = object; ``` Defines an instruction with a sourceElementId. The move instruction with this sourceType will terminate all active element instances with the sourceElementId and activate a new element instance for each terminated one at targetElementId. ## Properties ### sourceElementId ```ts sourceElementId: ElementId; ``` The id of the source element for the move instruction. --- ### sourceType ```ts sourceType: string; ``` The type of source element instruction. --- ## Type Alias: SourceElementInstanceKeyInstruction ```ts type SourceElementInstanceKeyInstruction = object; ``` Defines an instruction with a sourceElementInstanceKey. The move instruction with this sourceType will terminate one active element instance with the sourceElementInstanceKey and activate a new element instance at targetElementId. ## Properties ### sourceElementInstanceKey ```ts sourceElementInstanceKey: ElementInstanceKey; ``` The source element instance key for the move instruction. --- ### sourceType ```ts sourceType: string; ``` The type of source element instruction. --- ## Type Alias: SourceElementInstruction ```ts type SourceElementInstruction = | (object & SourceElementIdInstruction) | (object & SourceElementInstanceKeyInstruction); ``` Defines the source element identifier for the move instruction. It can either be a sourceElementId, or sourceElementInstanceKey. --- ## Type Alias: StartCursor ```ts type StartCursor = CamundaKey<"StartCursor">; ``` The start cursor in a search query result set. --- ## Type Alias: StateCode ```ts type StateCode = | "DOES_NOT_EXIST" | "IN_PROGRESS" | "COMPLETED" | "FAILED" | "INCOMPLETE" | "DELETED"; ``` Runtime Backup State The aggregated state of the backup, computed from the state of each partition. --- ## Type Alias: StatusMetric ```ts type StatusMetric = object; ``` Metric for a single job status. ## Properties ### count ```ts count: number; ``` Number of jobs in this status. --- ### lastUpdatedAt ```ts lastUpdatedAt: string | null; ``` ISO 8601 timestamp of the last update for this status. --- ## Type Alias: StringFilterProperty ```ts type StringFilterProperty = string | AdvancedStringFilter; ``` String property with full advanced search capabilities. --- ## Type Alias: SuspendBatchOperationData ```ts type SuspendBatchOperationData = object; ``` ## Properties ### body? ```ts optional body?: unknown; ``` --- ### path ```ts path: object; ``` #### batchOperationKey ```ts batchOperationKey: BatchOperationKey; ``` The key (or operate legacy ID) of the batch operation. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/batch-operations/{batchOperationKey}/suspension"; ``` --- ## Type Alias: SuspendBatchOperationError ```ts type SuspendBatchOperationError = SuspendBatchOperationErrors[keyof SuspendBatchOperationErrors]; ``` --- ## Type Alias: SuspendBatchOperationErrors ```ts type SuspendBatchOperationErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Not found. The batch operation was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: SuspendBatchOperationResponse ```ts type SuspendBatchOperationResponse = SuspendBatchOperationResponses[keyof SuspendBatchOperationResponses]; ``` --- ## Type Alias: SuspendBatchOperationResponses ```ts type SuspendBatchOperationResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The batch operation pause request was created. --- ## Type Alias: SuspendProcessInstanceData ```ts type SuspendProcessInstanceData = object; ``` ## Properties ### body? ```ts optional body?: SuspendProcessInstanceRequest; ``` --- ### path ```ts path: object; ``` #### processInstanceKey ```ts processInstanceKey: ProcessInstanceKeyWritable; ``` The key of the process instance to suspend. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/{processInstanceKey}/suspension"; ``` --- ## Type Alias: SuspendProcessInstanceError ```ts type SuspendProcessInstanceError = SuspendProcessInstanceErrors[keyof SuspendProcessInstanceErrors]; ``` --- ## Type Alias: SuspendProcessInstanceErrors ```ts type SuspendProcessInstanceErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 404 ```ts 404: ProblemDetail; ``` The process instance is not found. --- ### 409 ```ts 409: ProblemDetail; ``` The process instance is not in the ACTIVE state and cannot be suspended. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: SuspendProcessInstanceRequest ```ts type SuspendProcessInstanceRequest = { operationReference?: OperationReference; } | null; ``` --- ## Type Alias: SuspendProcessInstanceResponse ```ts type SuspendProcessInstanceResponse = SuspendProcessInstanceResponses[keyof SuspendProcessInstanceResponses]; ``` --- ## Type Alias: SuspendProcessInstanceResponses ```ts type SuspendProcessInstanceResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The process instance is suspended. --- ## Type Alias: SuspendProcessInstancesBatchOperationData ```ts type SuspendProcessInstancesBatchOperationData = object; ``` ## Properties ### body ```ts body: ProcessInstanceSuspensionBatchOperationRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/process-instances/suspension"; ``` --- ## Type Alias: SuspendProcessInstancesBatchOperationError ```ts type SuspendProcessInstancesBatchOperationError = SuspendProcessInstancesBatchOperationErrors[keyof SuspendProcessInstancesBatchOperationErrors]; ``` --- ## Type Alias: SuspendProcessInstancesBatchOperationErrors ```ts type SuspendProcessInstancesBatchOperationErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The process instance batch operation failed. More details are provided in the response body. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: SuspendProcessInstancesBatchOperationResponse ```ts type SuspendProcessInstancesBatchOperationResponse = SuspendProcessInstancesBatchOperationResponses[keyof SuspendProcessInstancesBatchOperationResponses]; ``` --- ## Type Alias: SuspendProcessInstancesBatchOperationResponses ```ts type SuspendProcessInstancesBatchOperationResponses = object; ``` ## Properties ### 200 ```ts 200: BatchOperationCreatedResult; ``` The batch operation request was created. --- ## Type Alias: SyncRuntimeBackupStateAsClusterAdminData ```ts type SyncRuntimeBackupStateAsClusterAdminData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: object; ``` #### physicalTenantId? ```ts optional physicalTenantId?: string; ``` The physical tenant to apply the change to. When omitted, or when passed with an empty value, the change is applied to every physical tenant of the cluster. --- ### url ```ts url: "/cluster/v2/backups/runtime/state/sync"; ``` --- ## Type Alias: SyncRuntimeBackupStateAsClusterAdminError ```ts type SyncRuntimeBackupStateAsClusterAdminError = SyncRuntimeBackupStateAsClusterAdminErrors[keyof SyncRuntimeBackupStateAsClusterAdminErrors]; ``` --- ## Type Alias: SyncRuntimeBackupStateAsClusterAdminErrors ```ts type SyncRuntimeBackupStateAsClusterAdminErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 404 ```ts 404: ProblemDetail; ``` The requested `physicalTenantId` does not exist in this cluster. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ### 504 ```ts 504: ProblemDetail; ``` The request from gateway to broker timed out on at least one targeted physical tenant. --- ## Type Alias: SyncRuntimeBackupStateAsClusterAdminResponse ```ts type SyncRuntimeBackupStateAsClusterAdminResponse = SyncRuntimeBackupStateAsClusterAdminResponses[keyof SyncRuntimeBackupStateAsClusterAdminResponses]; ``` --- ## Type Alias: SyncRuntimeBackupStateAsClusterAdminResponses ```ts type SyncRuntimeBackupStateAsClusterAdminResponses = object; ``` ## Properties ### 200 ```ts 200: ClusterRuntimeBackupState; ``` The updated runtime backup state of every targeted physical tenant, ordered by physical tenant id. --- ## Type Alias: SyncRuntimeBackupStateData ```ts type SyncRuntimeBackupStateData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/backups/runtime/state/sync"; ``` --- ## Type Alias: SyncRuntimeBackupStateError ```ts type SyncRuntimeBackupStateError = SyncRuntimeBackupStateErrors[keyof SyncRuntimeBackupStateErrors]; ``` --- ## Type Alias: SyncRuntimeBackupStateErrors ```ts type SyncRuntimeBackupStateErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ### 504 ```ts 504: ProblemDetail; ``` The request from gateway to broker timed out. --- ## Type Alias: SyncRuntimeBackupStateResponse ```ts type SyncRuntimeBackupStateResponse = SyncRuntimeBackupStateResponses[keyof SyncRuntimeBackupStateResponses]; ``` --- ## Type Alias: SyncRuntimeBackupStateResponses ```ts type SyncRuntimeBackupStateResponses = object; ``` ## Properties ### 200 ```ts 200: RuntimeBackupState; ``` The updated runtime backup state. --- ## Type Alias: SystemConfigurationResponse ```ts type SystemConfigurationResponse = object; ``` Envelope for all system configuration sections. Each property represents a feature area. ## Properties ### authentication ```ts authentication: AuthenticationConfigurationResponse; ``` --- ### cloud ```ts cloud: CloudConfigurationResponse; ``` --- ### components ```ts components: ComponentsConfigurationResponse; ``` --- ### deployment ```ts deployment: DeploymentConfigurationResponse; ``` --- ### jobMetrics ```ts jobMetrics: JobMetricsConfigurationResponse; ``` --- ## Type Alias: Tag ```ts type Tag = CamundaKey<"Tag">; ``` A tag. Needs to start with a letter; then alphanumerics, `_`, `-`, `:`, or `.`; length ≤ 100. --- ## Type Alias: TagSet ```ts type TagSet = Tag[] & object; ``` List of tags. Tags need to start with a letter; then alphanumerics, `_`, `-`, `:`, or `.`; length ≤ 100. ## Type Declaration ### length ```ts readonly length: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10; ``` --- ## Type Alias: TagSetWritable ```ts type TagSetWritable = Tag[]; ``` List of tags. Tags need to start with a letter; then alphanumerics, `_`, `-`, `:`, or `.`; length ≤ 100. --- ## Type Alias: TakeHistoryBackupAsClusterAdminData ```ts type TakeHistoryBackupAsClusterAdminData = object; ``` ## Properties ### body ```ts body: TakeHistoryBackupRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: object; ``` #### physicalTenantId? ```ts optional physicalTenantId?: string; ``` The physical tenant to apply the change to. When omitted, or when passed with an empty value, the change is applied to every physical tenant of the cluster. --- ### url ```ts url: "/cluster/v2/backups/history"; ``` --- ## Type Alias: TakeHistoryBackupAsClusterAdminError ```ts type TakeHistoryBackupAsClusterAdminError = TakeHistoryBackupAsClusterAdminErrors[keyof TakeHistoryBackupAsClusterAdminErrors]; ``` --- ## Type Alias: TakeHistoryBackupAsClusterAdminErrors ```ts type TakeHistoryBackupAsClusterAdminErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` The cluster's secondary storage is neither Elasticsearch nor OpenSearch and therefore cannot serve history backups, or a targeted physical tenant's snapshot repository is absent from the store — configured under a name the store does not have, or not configured at all. Both are deployment faults the caller cannot correct by changing its request; narrow the request with `physicalTenantId` to work with the tenants whose repository is usable. Unlike the per-physical-tenant backup endpoints, the cluster-admin surface performs no fine-grained authorization, so a missing `BACKUP` permission is never the reason. --- ### 404 ```ts 404: ProblemDetail; ``` The requested `physicalTenantId` does not exist in this cluster. --- ### 409 ```ts 409: ProblemDetail; ``` At least one targeted physical tenant already holds a backup with this id, or already has another backup running. The check that precedes the fan-out normally rejects the request before anything is scheduled; a tenant that takes the id in between rejects it during the fan-out instead, which can leave snapshots behind on the tenants already reached, so delete this backup id before retrying. --- ### 500 ```ts 500: ProblemDetail; ``` The backup could not be scheduled on every targeted physical tenant, because one of them hit an internal error. The check that precedes the fan-out rejects the request before anything is scheduled, but a failure during the fan-out itself can leave snapshots behind on the tenants already reached, so delete this backup id before retrying. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: TakeHistoryBackupAsClusterAdminResponse ```ts type TakeHistoryBackupAsClusterAdminResponse = TakeHistoryBackupAsClusterAdminResponses[keyof TakeHistoryBackupAsClusterAdminResponses]; ``` --- ## Type Alias: TakeHistoryBackupAsClusterAdminResponses ```ts type TakeHistoryBackupAsClusterAdminResponses = object; ``` ## Properties ### 202 ```ts 202: ClusterTakeHistoryBackupResponse; ``` The backup has been scheduled on every targeted physical tenant. --- ## Type Alias: TakeHistoryBackupData ```ts type TakeHistoryBackupData = object; ``` ## Properties ### body ```ts body: TakeHistoryBackupRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/backups/history"; ``` --- ## Type Alias: TakeHistoryBackupError ```ts type TakeHistoryBackupError = TakeHistoryBackupErrors[keyof TakeHistoryBackupErrors]; ``` --- ## Type Alias: TakeHistoryBackupErrors ```ts type TakeHistoryBackupErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` The request is forbidden for one of three reasons: the authenticated caller lacks the required `BACKUP` permission; the cluster's secondary storage is neither Elasticsearch nor OpenSearch and therefore cannot serve history backups; or the physical tenant's snapshot repository is absent from the store — configured under a name the store does not have, or not configured at all. The problem detail says which applies. The latter two are deployment faults the caller cannot correct by changing its request. --- ### 409 ```ts 409: ProblemDetail; ``` A backup with the given id already exists, or another backup is already running. The "already running" check is best-effort and node-local: it only observes backups started by the gateway that serves the request. Two concurrent requests reaching different gateways are narrowed by the duplicate-id check alone. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: TakeHistoryBackupRequest ```ts type TakeHistoryBackupRequest = object; ``` TakeHistoryBackupRequest Request body for taking a history backup. ## Properties ### backupId ```ts backupId: BackupId; ``` The id of the backup to take. --- ## Type Alias: TakeHistoryBackupResponse ```ts type TakeHistoryBackupResponse = object; ``` TakeHistoryBackupResponse Response body for taking a history backup. ## Properties ### backupId ```ts backupId: BackupId; ``` The id of the backup that has been scheduled. --- ### scheduledSnapshots ```ts scheduledSnapshots: string[]; ``` The names of the snapshots that have been scheduled for this backup. --- ## Type Alias: TakeHistoryBackupResponse2 ```ts type TakeHistoryBackupResponse2 = TakeHistoryBackupResponses[keyof TakeHistoryBackupResponses]; ``` --- ## Type Alias: TakeHistoryBackupResponses ```ts type TakeHistoryBackupResponses = object; ``` ## Properties ### 202 ```ts 202: TakeHistoryBackupResponse; ``` The backup has been successfully scheduled. --- ## Type Alias: TakeRuntimeBackupAsClusterAdminData ```ts type TakeRuntimeBackupAsClusterAdminData = object; ``` ## Properties ### body? ```ts optional body?: TakeRuntimeBackupRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: object; ``` #### physicalTenantId? ```ts optional physicalTenantId?: string; ``` The physical tenant to apply the change to. When omitted, or when passed with an empty value, the change is applied to every physical tenant of the cluster. --- ### url ```ts url: "/cluster/v2/backups/runtime"; ``` --- ## Type Alias: TakeRuntimeBackupAsClusterAdminError ```ts type TakeRuntimeBackupAsClusterAdminError = TakeRuntimeBackupAsClusterAdminErrors[keyof TakeRuntimeBackupAsClusterAdminErrors]; ``` --- ## Type Alias: TakeRuntimeBackupAsClusterAdminErrors ```ts type TakeRuntimeBackupAsClusterAdminErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The request names a `backupId` while at least one targeted physical tenant generates its own ids, or omits it while at least one does not, or the id is not a positive number. No tenant was triggered. A targeted tenant that rejects the request as invalid during the fan-out answers with the same status but the cluster body, listing the tenants that were triggered. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 404 ```ts 404: ProblemDetail; ``` The requested `physicalTenantId` does not exist in this cluster, so no tenant was triggered. --- ### 409 ```ts 409: ClusterTakeRuntimeBackupResponse; ``` At least one targeted physical tenant already holds a backup with this id or a higher one. Backups are triggered without a preceding check, so the tenants that accepted the id are listed in the body and keep running; delete them before retrying. --- ### 500 ```ts 500: ClusterTakeRuntimeBackupResponse; ``` At least one targeted physical tenant could not be triggered, and the failures do not agree on a single status. The body lists the tenants that were triggered and keep running. --- ### 502 ```ts 502: ClusterTakeRuntimeBackupResponse; ``` The connection to the broker was cut mid-flight on at least one targeted physical tenant, which may or may not have accepted the request. Those tenants are reported as `UNKNOWN` with the id to check them under, and the tenants that were triggered keep running. --- ### 503 ```ts 503: ClusterTakeRuntimeBackupResponse; ``` At least one targeted physical tenant could not be reached. The body lists the tenants that were triggered and keep running. --- ### 504 ```ts 504: ClusterTakeRuntimeBackupResponse; ``` The request from gateway to broker timed out on at least one targeted physical tenant, which may or may not have accepted it. Those tenants are reported as `UNKNOWN` with the id to check them under, and the tenants that were triggered keep running. --- ## Type Alias: TakeRuntimeBackupAsClusterAdminResponse ```ts type TakeRuntimeBackupAsClusterAdminResponse = TakeRuntimeBackupAsClusterAdminResponses[keyof TakeRuntimeBackupAsClusterAdminResponses]; ``` --- ## Type Alias: TakeRuntimeBackupAsClusterAdminResponses ```ts type TakeRuntimeBackupAsClusterAdminResponses = object; ``` ## Properties ### 202 ```ts 202: ClusterTakeRuntimeBackupResponse; ``` The backup was triggered on every targeted physical tenant. --- ## Type Alias: TakeRuntimeBackupData ```ts type TakeRuntimeBackupData = object; ``` ## Properties ### body? ```ts optional body?: TakeRuntimeBackupRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/backups/runtime"; ``` --- ## Type Alias: TakeRuntimeBackupError ```ts type TakeRuntimeBackupError = TakeRuntimeBackupErrors[keyof TakeRuntimeBackupErrors]; ``` --- ## Type Alias: TakeRuntimeBackupErrors ```ts type TakeRuntimeBackupErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 409 ```ts 409: ProblemDetail; ``` A backup with the same or a higher id already exists. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ### 504 ```ts 504: ProblemDetail; ``` The request from gateway to broker timed out. --- ## Type Alias: TakeRuntimeBackupRequest ```ts type TakeRuntimeBackupRequest = object; ``` TakeRuntimeBackupRequest Request body for taking a runtime backup. ## Properties ### backupId? ```ts optional backupId?: BackupId | null; ``` The id of the backup to take. Must be omitted if continuous backups and/or a backup or checkpoint schedule is enabled for the physical tenant. --- ## Type Alias: TakeRuntimeBackupResponse ```ts type TakeRuntimeBackupResponse = object; ``` TakeRuntimeBackupResponse Response body for taking a runtime backup. ## Properties ### backupId ```ts backupId: BackupId; ``` The id of the backup that has been scheduled. --- ## Type Alias: TakeRuntimeBackupResponse2 ```ts type TakeRuntimeBackupResponse2 = TakeRuntimeBackupResponses[keyof TakeRuntimeBackupResponses]; ``` --- ## Type Alias: TakeRuntimeBackupResponses ```ts type TakeRuntimeBackupResponses = object; ``` ## Properties ### 202 ```ts 202: TakeRuntimeBackupResponse; ``` The backup has been successfully scheduled. --- ## Type Alias: TenantClientResult ```ts type TenantClientResult = object; ``` ## Properties ### clientId ```ts clientId: ClientId; ``` The ID of the client. --- ## Type Alias: TenantClientSearchQueryRequest ```ts type TenantClientSearchQueryRequest = SearchQueryRequest & object; ``` ## Type Declaration ### sort? ```ts optional sort?: TenantClientSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: TenantClientSearchQuerySortRequest ```ts type TenantClientSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: "clientId"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: TenantClientSearchResult ```ts type TenantClientSearchResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: TenantClientResult[]; ``` The matching clients. --- ## Type Alias: TenantCreateRequest ```ts type TenantCreateRequest = object; ``` ## Properties ### description? ```ts optional description?: string; ``` The description of the tenant. --- ### name ```ts name: string; ``` The name of the tenant. --- ### tenantId ```ts tenantId: TenantId; ``` The unique ID for the tenant. Must be 31 characters or less and match `^[\w.-]{1,31}$` (word characters, `.`, `-`). The literal `` is also accepted as the default-tenant alias. --- ## Type Alias: TenantCreateResult ```ts type TenantCreateResult = object; ``` ## Properties ### description ```ts description: string | null; ``` The description of the tenant. --- ### name ```ts name: string; ``` The name of the tenant. --- ### tenantId ```ts tenantId: TenantId; ``` The unique identifier of the created tenant. --- ## Type Alias: TenantFilter ```ts type TenantFilter = object; ``` Tenant filter request ## Properties ### name? ```ts optional name?: string; ``` The name of the tenant. --- ### tenantId? ```ts optional tenantId?: TenantId; ``` The unique identifier of the tenant. --- ## Type Alias: TenantFilterEnum ```ts type TenantFilterEnum = (typeof TenantFilterEnum)[keyof typeof TenantFilterEnum]; ``` The tenant filtering strategy for job activation. Determines whether to use tenant IDs provided in the request or tenant IDs assigned to the authenticated principal. --- ## Type Alias: TenantGroupResult ```ts type TenantGroupResult = object; ``` ## Properties ### groupId ```ts groupId: GroupId; ``` The group ID. --- ## Type Alias: TenantGroupSearchQueryRequest ```ts type TenantGroupSearchQueryRequest = SearchQueryRequest & object; ``` ## Type Declaration ### sort? ```ts optional sort?: TenantGroupSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: TenantGroupSearchQuerySortRequest ```ts type TenantGroupSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: "groupId"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: TenantGroupSearchResult ```ts type TenantGroupSearchResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: TenantGroupResult[]; ``` The matching groups. --- ## Type Alias: TenantId ```ts type TenantId = CamundaKey<"TenantId">; ``` The unique identifier of the tenant. --- ## Type Alias: TenantMappingRuleSearchResult ```ts type TenantMappingRuleSearchResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: MappingRuleResult[]; ``` The matching mapping rules. --- ## Type Alias: TenantResult ```ts type TenantResult = object; ``` Tenant search response item. ## Properties ### description ```ts description: string | null; ``` The tenant description. --- ### name ```ts name: string; ``` The tenant name. --- ### tenantId ```ts tenantId: TenantId; ``` The unique identifier of the tenant. --- ## Type Alias: TenantRoleSearchResult ```ts type TenantRoleSearchResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: RoleResult[]; ``` The matching roles. --- ## Type Alias: TenantSearchQueryRequest ```ts type TenantSearchQueryRequest = SearchQueryRequest & object; ``` Tenant search request ## Type Declaration ### filter? ```ts optional filter?: TenantFilter; ``` The tenant search filters. ### sort? ```ts optional sort?: TenantSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: TenantSearchQueryResult ```ts type TenantSearchQueryResult = SearchQueryResponse & object; ``` Tenant search response. ## Type Declaration ### items ```ts items: TenantResult[]; ``` The matching tenants. --- ## Type Alias: TenantSearchQuerySortRequest ```ts type TenantSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: "key" | "name" | "tenantId"; ``` The field to sort by. `key` is deprecated and should not be used anymore. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: TenantUpdateRequest ```ts type TenantUpdateRequest = object; ``` ## Properties ### description? ```ts optional description?: string; ``` The new description of the tenant. --- ### name ```ts name: string; ``` The new name of the tenant. --- ## Type Alias: TenantUpdateResult ```ts type TenantUpdateResult = object; ``` ## Properties ### description ```ts description: string | null; ``` The description of the tenant. --- ### name ```ts name: string; ``` The name of the tenant. --- ### tenantId ```ts tenantId: TenantId; ``` The unique identifier of the updated tenant. --- ## Type Alias: TenantUserResult ```ts type TenantUserResult = object; ``` ## Properties ### username ```ts username: Username; ``` --- ## Type Alias: TenantUserSearchQueryRequest ```ts type TenantUserSearchQueryRequest = SearchQueryRequest & object; ``` ## Type Declaration ### sort? ```ts optional sort?: TenantUserSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: TenantUserSearchQuerySortRequest ```ts type TenantUserSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: "username"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: TenantUserSearchResult ```ts type TenantUserSearchResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: TenantUserResult[]; ``` The matching users. --- ## Type Alias: ThreadedJob ```ts type ThreadedJob = Omit; ``` `clock` is omitted alongside `log` for the same reason: both are live in-process objects, and a pinned clock cannot cross the worker-thread boundary. Threaded handlers use ambient time — see camunda/orchestration-cluster-api-js#450. --- ## Type Alias: ThreadedJobHandler ```ts type ThreadedJobHandler = ( job, client ) => Promise | JobActionReceipt; ``` Handler function signature for threaded job workers. Import this type in your handler module for full intellisense on `job` and `client`: ```ts const handler: ThreadedJobHandler = async (job, client) => { // full intellisense for job.variables, job.complete(), client.publishMessage(), etc. return job.complete({ result: "done" }); }; export default handler; ``` ## Parameters ### job [`ThreadedJob`](ThreadedJob.md) ### client [`CamundaClient`](CamundaClient.md) ## Returns \| `Promise`\<[`JobActionReceipt`](JobActionReceipt.md)\> \| [`JobActionReceipt`](JobActionReceipt.md) --- ## Type Alias: ThrowJobErrorData ```ts type ThrowJobErrorData = object; ``` ## Properties ### body ```ts body: JobErrorRequest; ``` --- ### path ```ts path: object; ``` #### jobKey ```ts jobKey: JobKeyWritable; ``` The key of the job. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/jobs/{jobKey}/error"; ``` --- ## Type Alias: ThrowJobErrorError ```ts type ThrowJobErrorError = ThrowJobErrorErrors[keyof ThrowJobErrorErrors]; ``` --- ## Type Alias: ThrowJobErrorErrors ```ts type ThrowJobErrorErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 404 ```ts 404: ProblemDetail; ``` The job with the given key was not found or is not activated. --- ### 409 ```ts 409: ProblemDetail; ``` The job with the given key is in the wrong state currently. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: ThrowJobErrorResponse ```ts type ThrowJobErrorResponse = ThrowJobErrorResponses[keyof ThrowJobErrorResponses]; ``` --- ## Type Alias: ThrowJobErrorResponses ```ts type ThrowJobErrorResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` An error is thrown for the job. --- ## Type Alias: TimerWaitStateDetails ```ts type TimerWaitStateDetails = BaseWaitStateDetails & object; ``` ## Type Declaration ### dueDate ```ts dueDate: number | null; ``` When the timer is due, as a UNIX epoch timestamp in milliseconds. ### repetitions ```ts repetitions: number | null; ``` The number of remaining timer repetitions (-1 for infinite, 0 for non-repeating). --- ## Type Alias: TopologyResponse ```ts type TopologyResponse = object; ``` The response of a topology request. ## Properties ### brokers ```ts brokers: BrokerInfo[]; ``` A list of brokers that are part of this cluster. --- ### clusterId ```ts clusterId: string | null; ``` The cluster Id. --- ### clusterSize ```ts clusterSize: number; ``` The number of brokers in the cluster. --- ### gatewayVersion ```ts gatewayVersion: string; ``` The version of the Zeebe Gateway. --- ### lastCompletedChangeId ```ts lastCompletedChangeId: string; ``` ID of the last completed change --- ### partitionsCount ```ts partitionsCount: number; ``` The number of partitions are spread across the cluster. --- ### replicationFactor ```ts replicationFactor: number; ``` The configured replication factor for this cluster. --- ## Type Alias: TriggerClusterRebalanceData ```ts type TriggerClusterRebalanceData = object; ``` ## Properties ### body? ```ts optional body?: ClusterRebalanceRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: object; ``` #### dryRun? ```ts optional dryRun?: boolean; ``` If true, report the plan the rebalance would carry out without pausing any partition or transferring any leadership. --- ### url ```ts url: "/cluster/v2/rebalance"; ``` --- ## Type Alias: TriggerClusterRebalanceError ```ts type TriggerClusterRebalanceError = TriggerClusterRebalanceErrors[keyof TriggerClusterRebalanceErrors]; ``` --- ## Type Alias: TriggerClusterRebalanceErrors ```ts type TriggerClusterRebalanceErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 409 ```ts 409: ProblemDetail; ``` A rebalance or cluster configuration change is already in progress, so there is no settled configuration to plan a rebalance against. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 502 ```ts 502: ProblemDetail; ``` The coordinator was reached, but its response was absent or unusable. --- ### 503 ```ts 503: ProblemDetail; ``` No coordinator is currently available or reachable. --- ### 504 ```ts 504: ProblemDetail; ``` The coordinator did not answer before the request timeout. --- ## Type Alias: TriggerClusterRebalanceResponse ```ts type TriggerClusterRebalanceResponse = TriggerClusterRebalanceResponses[keyof TriggerClusterRebalanceResponses]; ``` --- ## Type Alias: TriggerClusterRebalanceResponses ```ts type TriggerClusterRebalanceResponses = object; ``` ## Properties ### 202 ```ts 202: ClusterBalanceResponse; ``` The rebalance was accepted, and its status is reported as it starts. --- ## Type Alias: UnassignClientFromGroupData ```ts type UnassignClientFromGroupData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### clientId ```ts clientId: ClientId; ``` The client ID. #### groupId ```ts groupId: GroupId; ``` The group ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/groups/{groupId}/clients/{clientId}"; ``` --- ## Type Alias: UnassignClientFromGroupError ```ts type UnassignClientFromGroupError = UnassignClientFromGroupErrors[keyof UnassignClientFromGroupErrors]; ``` --- ## Type Alias: UnassignClientFromGroupErrors ```ts type UnassignClientFromGroupErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The group with the given ID was not found, or the client is not assigned to this group. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: UnassignClientFromGroupResponse ```ts type UnassignClientFromGroupResponse = UnassignClientFromGroupResponses[keyof UnassignClientFromGroupResponses]; ``` --- ## Type Alias: UnassignClientFromGroupResponses ```ts type UnassignClientFromGroupResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The client was unassigned successfully from the group. --- ## Type Alias: UnassignClientFromTenantData ```ts type UnassignClientFromTenantData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### clientId ```ts clientId: ClientId; ``` The unique identifier of the application. #### tenantId ```ts tenantId: TenantId; ``` The unique identifier of the tenant. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/tenants/{tenantId}/clients/{clientId}"; ``` --- ## Type Alias: UnassignClientFromTenantError ```ts type UnassignClientFromTenantError = UnassignClientFromTenantErrors[keyof UnassignClientFromTenantErrors]; ``` --- ## Type Alias: UnassignClientFromTenantErrors ```ts type UnassignClientFromTenantErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The tenant does not exist or the client was not assigned to it. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: UnassignClientFromTenantResponse ```ts type UnassignClientFromTenantResponse = UnassignClientFromTenantResponses[keyof UnassignClientFromTenantResponses]; ``` --- ## Type Alias: UnassignClientFromTenantResponses ```ts type UnassignClientFromTenantResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The client was successfully unassigned from the tenant. --- ## Type Alias: UnassignGroupFromTenantData ```ts type UnassignGroupFromTenantData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### groupId ```ts groupId: GroupId; ``` The unique identifier of the group. #### tenantId ```ts tenantId: TenantId; ``` The unique identifier of the tenant. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/tenants/{tenantId}/groups/{groupId}"; ``` --- ## Type Alias: UnassignGroupFromTenantError ```ts type UnassignGroupFromTenantError = UnassignGroupFromTenantErrors[keyof UnassignGroupFromTenantErrors]; ``` --- ## Type Alias: UnassignGroupFromTenantErrors ```ts type UnassignGroupFromTenantErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Not found. The tenant or group was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: UnassignGroupFromTenantResponse ```ts type UnassignGroupFromTenantResponse = UnassignGroupFromTenantResponses[keyof UnassignGroupFromTenantResponses]; ``` --- ## Type Alias: UnassignGroupFromTenantResponses ```ts type UnassignGroupFromTenantResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The group was successfully unassigned from the tenant. --- ## Type Alias: UnassignMappingRuleFromGroupData ```ts type UnassignMappingRuleFromGroupData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### groupId ```ts groupId: GroupId; ``` The group ID. #### mappingRuleId ```ts mappingRuleId: MappingRuleId; ``` The mapping rule ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/groups/{groupId}/mapping-rules/{mappingRuleId}"; ``` --- ## Type Alias: UnassignMappingRuleFromGroupError ```ts type UnassignMappingRuleFromGroupError = UnassignMappingRuleFromGroupErrors[keyof UnassignMappingRuleFromGroupErrors]; ``` --- ## Type Alias: UnassignMappingRuleFromGroupErrors ```ts type UnassignMappingRuleFromGroupErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The group or mapping rule with the given ID was not found, or the mapping rule is not assigned to this group. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: UnassignMappingRuleFromGroupResponse ```ts type UnassignMappingRuleFromGroupResponse = UnassignMappingRuleFromGroupResponses[keyof UnassignMappingRuleFromGroupResponses]; ``` --- ## Type Alias: UnassignMappingRuleFromGroupResponses ```ts type UnassignMappingRuleFromGroupResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The mapping rule was unassigned successfully from the group. --- ## Type Alias: UnassignMappingRuleFromTenantData ```ts type UnassignMappingRuleFromTenantData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### mappingRuleId ```ts mappingRuleId: MappingRuleId; ``` The unique identifier of the mapping rule. #### tenantId ```ts tenantId: TenantId; ``` The unique identifier of the tenant. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/tenants/{tenantId}/mapping-rules/{mappingRuleId}"; ``` --- ## Type Alias: UnassignMappingRuleFromTenantError ```ts type UnassignMappingRuleFromTenantError = UnassignMappingRuleFromTenantErrors[keyof UnassignMappingRuleFromTenantErrors]; ``` --- ## Type Alias: UnassignMappingRuleFromTenantErrors ```ts type UnassignMappingRuleFromTenantErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Not found. The tenant or mapping rule was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: UnassignMappingRuleFromTenantResponse ```ts type UnassignMappingRuleFromTenantResponse = UnassignMappingRuleFromTenantResponses[keyof UnassignMappingRuleFromTenantResponses]; ``` --- ## Type Alias: UnassignMappingRuleFromTenantResponses ```ts type UnassignMappingRuleFromTenantResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The mapping rule was successfully unassigned from the tenant. --- ## Type Alias: UnassignRoleFromClientData ```ts type UnassignRoleFromClientData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### clientId ```ts clientId: ClientId; ``` The client ID. #### roleId ```ts roleId: RoleId; ``` The role ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/roles/{roleId}/clients/{clientId}"; ``` --- ## Type Alias: UnassignRoleFromClientError ```ts type UnassignRoleFromClientError = UnassignRoleFromClientErrors[keyof UnassignRoleFromClientErrors]; ``` --- ## Type Alias: UnassignRoleFromClientErrors ```ts type UnassignRoleFromClientErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The role or client with the given ID or username was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: UnassignRoleFromClientResponse ```ts type UnassignRoleFromClientResponse = UnassignRoleFromClientResponses[keyof UnassignRoleFromClientResponses]; ``` --- ## Type Alias: UnassignRoleFromClientResponses ```ts type UnassignRoleFromClientResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The role was unassigned successfully from the client. --- ## Type Alias: UnassignRoleFromGroupData ```ts type UnassignRoleFromGroupData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### groupId ```ts groupId: GroupId; ``` The group ID. #### roleId ```ts roleId: RoleId; ``` The role ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/roles/{roleId}/groups/{groupId}"; ``` --- ## Type Alias: UnassignRoleFromGroupError ```ts type UnassignRoleFromGroupError = UnassignRoleFromGroupErrors[keyof UnassignRoleFromGroupErrors]; ``` --- ## Type Alias: UnassignRoleFromGroupErrors ```ts type UnassignRoleFromGroupErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The role or group with the given ID was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: UnassignRoleFromGroupResponse ```ts type UnassignRoleFromGroupResponse = UnassignRoleFromGroupResponses[keyof UnassignRoleFromGroupResponses]; ``` --- ## Type Alias: UnassignRoleFromGroupResponses ```ts type UnassignRoleFromGroupResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The role was unassigned successfully from the group. --- ## Type Alias: UnassignRoleFromMappingRuleData ```ts type UnassignRoleFromMappingRuleData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### mappingRuleId ```ts mappingRuleId: MappingRuleId; ``` The mapping rule ID. #### roleId ```ts roleId: RoleId; ``` The role ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/roles/{roleId}/mapping-rules/{mappingRuleId}"; ``` --- ## Type Alias: UnassignRoleFromMappingRuleError ```ts type UnassignRoleFromMappingRuleError = UnassignRoleFromMappingRuleErrors[keyof UnassignRoleFromMappingRuleErrors]; ``` --- ## Type Alias: UnassignRoleFromMappingRuleErrors ```ts type UnassignRoleFromMappingRuleErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The role or mapping rule with the given ID was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: UnassignRoleFromMappingRuleResponse ```ts type UnassignRoleFromMappingRuleResponse = UnassignRoleFromMappingRuleResponses[keyof UnassignRoleFromMappingRuleResponses]; ``` --- ## Type Alias: UnassignRoleFromMappingRuleResponses ```ts type UnassignRoleFromMappingRuleResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The role was unassigned successfully from the mapping rule. --- ## Type Alias: UnassignRoleFromTenantData ```ts type UnassignRoleFromTenantData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### roleId ```ts roleId: RoleId; ``` The unique identifier of the role. #### tenantId ```ts tenantId: TenantId; ``` The unique identifier of the tenant. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/tenants/{tenantId}/roles/{roleId}"; ``` --- ## Type Alias: UnassignRoleFromTenantError ```ts type UnassignRoleFromTenantError = UnassignRoleFromTenantErrors[keyof UnassignRoleFromTenantErrors]; ``` --- ## Type Alias: UnassignRoleFromTenantErrors ```ts type UnassignRoleFromTenantErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Not found. The tenant or role was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: UnassignRoleFromTenantResponse ```ts type UnassignRoleFromTenantResponse = UnassignRoleFromTenantResponses[keyof UnassignRoleFromTenantResponses]; ``` --- ## Type Alias: UnassignRoleFromTenantResponses ```ts type UnassignRoleFromTenantResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The role was successfully unassigned from the tenant. --- ## Type Alias: UnassignRoleFromUserData ```ts type UnassignRoleFromUserData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### roleId ```ts roleId: RoleId; ``` The role ID. #### username ```ts username: Username; ``` The user username. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/roles/{roleId}/users/{username}"; ``` --- ## Type Alias: UnassignRoleFromUserError ```ts type UnassignRoleFromUserError = UnassignRoleFromUserErrors[keyof UnassignRoleFromUserErrors]; ``` --- ## Type Alias: UnassignRoleFromUserErrors ```ts type UnassignRoleFromUserErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The role or user with the given ID or username was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: UnassignRoleFromUserResponse ```ts type UnassignRoleFromUserResponse = UnassignRoleFromUserResponses[keyof UnassignRoleFromUserResponses]; ``` --- ## Type Alias: UnassignRoleFromUserResponses ```ts type UnassignRoleFromUserResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The role was unassigned successfully from the user. --- ## Type Alias: UnassignUserFromGroupData ```ts type UnassignUserFromGroupData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### groupId ```ts groupId: GroupId; ``` The group ID. #### username ```ts username: Username; ``` The user username. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/groups/{groupId}/users/{username}"; ``` --- ## Type Alias: UnassignUserFromGroupError ```ts type UnassignUserFromGroupError = UnassignUserFromGroupErrors[keyof UnassignUserFromGroupErrors]; ``` --- ## Type Alias: UnassignUserFromGroupErrors ```ts type UnassignUserFromGroupErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The group or user with the given ID was not found, or the user is not assigned to this group. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: UnassignUserFromGroupResponse ```ts type UnassignUserFromGroupResponse = UnassignUserFromGroupResponses[keyof UnassignUserFromGroupResponses]; ``` --- ## Type Alias: UnassignUserFromGroupResponses ```ts type UnassignUserFromGroupResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The user was unassigned successfully from the group. --- ## Type Alias: UnassignUserFromTenantData ```ts type UnassignUserFromTenantData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### tenantId ```ts tenantId: TenantId; ``` The unique identifier of the tenant. #### username ```ts username: Username; ``` The unique identifier of the user. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/tenants/{tenantId}/users/{username}"; ``` --- ## Type Alias: UnassignUserFromTenantError ```ts type UnassignUserFromTenantError = UnassignUserFromTenantErrors[keyof UnassignUserFromTenantErrors]; ``` --- ## Type Alias: UnassignUserFromTenantErrors ```ts type UnassignUserFromTenantErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Not found. The tenant or user was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: UnassignUserFromTenantResponse ```ts type UnassignUserFromTenantResponse = UnassignUserFromTenantResponses[keyof UnassignUserFromTenantResponses]; ``` --- ## Type Alias: UnassignUserFromTenantResponses ```ts type UnassignUserFromTenantResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The user was successfully unassigned from the tenant. --- ## Type Alias: UnassignUserTaskData ```ts type UnassignUserTaskData = object; ``` ## Properties ### body? ```ts optional body?: never; ``` --- ### path ```ts path: object; ``` #### userTaskKey ```ts userTaskKey: UserTaskKeyWritable; ``` The key of the user task. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/user-tasks/{userTaskKey}/assignee"; ``` --- ## Type Alias: UnassignUserTaskError ```ts type UnassignUserTaskError = UnassignUserTaskErrors[keyof UnassignUserTaskErrors]; ``` --- ## Type Alias: UnassignUserTaskErrors ```ts type UnassignUserTaskErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 404 ```ts 404: ProblemDetail; ``` The user task with the given key was not found. --- ### 409 ```ts 409: ProblemDetail; ``` The user task with the given key is in the wrong state currently. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ### 504 ```ts 504: ProblemDetail; ``` The request timed out between the gateway and the broker. For these endpoints, this often happens when user task listeners are configured and the corresponding listener job is not completed within the request timeout. Common causes include no available job workers for the listener type, busy or crashed job workers, or delayed job completion. As with any gateway timeout, general timeout causes (for example transient network issues) can also result in a 504 response. Troubleshooting: - verify that job workers for the listener type are running and healthy - check worker logs for crashes, retries, and completion failures - check network connectivity between workers, gateway, and broker - retry with backoff after transient failures - fail without retries if a problem persists --- ## Type Alias: UnassignUserTaskResponse ```ts type UnassignUserTaskResponse = UnassignUserTaskResponses[keyof UnassignUserTaskResponses]; ``` --- ## Type Alias: UnassignUserTaskResponses ```ts type UnassignUserTaskResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The user task was unassigned successfully. --- ## Type Alias: UpdateAgentInstanceData ```ts type UpdateAgentInstanceData = object; ``` ## Properties ### body ```ts body: AgentInstanceUpdateRequest; ``` --- ### path ```ts path: object; ``` #### agentInstanceKey ```ts agentInstanceKey: AgentInstanceKeyWritable; ``` The key of the agent instance to update. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/agent-instances/{agentInstanceKey}"; ``` --- ## Type Alias: UpdateAgentInstanceError ```ts type UpdateAgentInstanceError = UpdateAgentInstanceErrors[keyof UpdateAgentInstanceErrors]; ``` --- ## Type Alias: UpdateAgentInstanceErrors ```ts type UpdateAgentInstanceErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The agent instance with the given key was not found. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: UpdateAgentInstanceResponse ```ts type UpdateAgentInstanceResponse = UpdateAgentInstanceResponses[keyof UpdateAgentInstanceResponses]; ``` --- ## Type Alias: UpdateAgentInstanceResponses ```ts type UpdateAgentInstanceResponses = object; ``` ## Properties ### 200 ```ts 200: AgentInstanceUpdateResult; ``` The agent instance was updated successfully. --- ## Type Alias: UpdateAuthorizationData ```ts type UpdateAuthorizationData = object; ``` ## Properties ### body ```ts body: AuthorizationRequest; ``` --- ### path ```ts path: object; ``` #### authorizationKey ```ts authorizationKey: AuthorizationKeyWritable; ``` The key of the authorization to delete. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/authorizations/{authorizationKey}"; ``` --- ## Type Alias: UpdateAuthorizationError ```ts type UpdateAuthorizationError = UpdateAuthorizationErrors[keyof UpdateAuthorizationErrors]; ``` --- ## Type Alias: UpdateAuthorizationErrors ```ts type UpdateAuthorizationErrors = object; ``` ## Properties ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 404 ```ts 404: ProblemDetail; ``` The authorization with the authorizationKey was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: UpdateAuthorizationResponse ```ts type UpdateAuthorizationResponse = UpdateAuthorizationResponses[keyof UpdateAuthorizationResponses]; ``` --- ## Type Alias: UpdateAuthorizationResponses ```ts type UpdateAuthorizationResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The authorization was updated successfully. --- ## Type Alias: UpdateClusterVariableRequest ```ts type UpdateClusterVariableRequest = object; ``` ## Properties ### metadata? ```ts optional metadata?: object; ``` A generic key-value metadata bag attached to the cluster variable. Values must be strings or numbers. Limited to 100 entries and a configurable maximum serialized size (default: 100 entries at max key length of a cluster variable name (256 chars) plus the maximum value length, 8192 characters). #### Index Signature ```ts [key: string]: string | number ``` --- ### value ```ts value: unknown; ``` The new value of the cluster variable. Can be any JSON object or primitive value. Will be serialized as a JSON string in responses. --- ## Type Alias: UpdateGlobalClusterVariableData ```ts type UpdateGlobalClusterVariableData = object; ``` ## Properties ### body ```ts body: UpdateClusterVariableRequest; ``` --- ### path ```ts path: object; ``` #### name ```ts name: ClusterVariableName; ``` The name of the cluster variable --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/cluster-variables/global/{name}"; ``` --- ## Type Alias: UpdateGlobalClusterVariableError ```ts type UpdateGlobalClusterVariableError = UpdateGlobalClusterVariableErrors[keyof UpdateGlobalClusterVariableErrors]; ``` --- ## Type Alias: UpdateGlobalClusterVariableErrors ```ts type UpdateGlobalClusterVariableErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Cluster variable not found --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: UpdateGlobalClusterVariableResponse ```ts type UpdateGlobalClusterVariableResponse = UpdateGlobalClusterVariableResponses[keyof UpdateGlobalClusterVariableResponses]; ``` --- ## Type Alias: UpdateGlobalClusterVariableResponses ```ts type UpdateGlobalClusterVariableResponses = object; ``` ## Properties ### 200 ```ts 200: ClusterVariableResult; ``` Cluster variable updated successfully --- ## Type Alias: UpdateGlobalTaskListenerData ```ts type UpdateGlobalTaskListenerData = object; ``` ## Properties ### body ```ts body: UpdateGlobalTaskListenerRequest; ``` --- ### path ```ts path: object; ``` #### id ```ts id: GlobalListenerId; ``` The id of the global user task listener to update. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/global-task-listeners/{id}"; ``` --- ## Type Alias: UpdateGlobalTaskListenerError ```ts type UpdateGlobalTaskListenerError = UpdateGlobalTaskListenerErrors[keyof UpdateGlobalTaskListenerErrors]; ``` --- ## Type Alias: UpdateGlobalTaskListenerErrors ```ts type UpdateGlobalTaskListenerErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The global user task listener was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: UpdateGlobalTaskListenerRequest ```ts type UpdateGlobalTaskListenerRequest = GlobalTaskListenerBase; ``` --- ## Type Alias: UpdateGlobalTaskListenerResponse ```ts type UpdateGlobalTaskListenerResponse = UpdateGlobalTaskListenerResponses[keyof UpdateGlobalTaskListenerResponses]; ``` --- ## Type Alias: UpdateGlobalTaskListenerResponses ```ts type UpdateGlobalTaskListenerResponses = object; ``` ## Properties ### 200 ```ts 200: GlobalTaskListenerResult; ``` The global listener was updated successfully. --- ## Type Alias: UpdateGroupData ```ts type UpdateGroupData = object; ``` ## Properties ### body ```ts body: GroupUpdateRequest; ``` --- ### path ```ts path: object; ``` #### groupId ```ts groupId: GroupId; ``` The group ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/groups/{groupId}"; ``` --- ## Type Alias: UpdateGroupError ```ts type UpdateGroupError = UpdateGroupErrors[keyof UpdateGroupErrors]; ``` --- ## Type Alias: UpdateGroupErrors ```ts type UpdateGroupErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 404 ```ts 404: ProblemDetail; ``` The group with the given ID was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: UpdateGroupResponse ```ts type UpdateGroupResponse = UpdateGroupResponses[keyof UpdateGroupResponses]; ``` --- ## Type Alias: UpdateGroupResponses ```ts type UpdateGroupResponses = object; ``` ## Properties ### 200 ```ts 200: GroupUpdateResult; ``` The group was updated successfully. --- ## Type Alias: UpdateJobData ```ts type UpdateJobData = object; ``` ## Properties ### body ```ts body: JobUpdateRequest; ``` --- ### path ```ts path: object; ``` #### jobKey ```ts jobKey: JobKeyWritable; ``` The key of the job to update. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/jobs/{jobKey}"; ``` --- ## Type Alias: UpdateJobError ```ts type UpdateJobError = UpdateJobErrors[keyof UpdateJobErrors]; ``` --- ## Type Alias: UpdateJobErrors ```ts type UpdateJobErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 404 ```ts 404: ProblemDetail; ``` The job with the jobKey is not found. --- ### 409 ```ts 409: ProblemDetail; ``` The job with the given key is in the wrong state currently. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: UpdateJobResponse ```ts type UpdateJobResponse = UpdateJobResponses[keyof UpdateJobResponses]; ``` --- ## Type Alias: UpdateJobResponses ```ts type UpdateJobResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The job was updated successfully. --- ## Type Alias: UpdateJobsBatchOperationData ```ts type UpdateJobsBatchOperationData = object; ``` ## Properties ### body ```ts body: JobBatchUpdateRequest; ``` --- ### path? ```ts optional path?: never; ``` --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/jobs/batch-update"; ``` --- ## Type Alias: UpdateJobsBatchOperationError ```ts type UpdateJobsBatchOperationError = UpdateJobsBatchOperationErrors[keyof UpdateJobsBatchOperationErrors]; ``` --- ## Type Alias: UpdateJobsBatchOperationErrors ```ts type UpdateJobsBatchOperationErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The job batch update operation failed. More details are provided in the response body. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: UpdateJobsBatchOperationResponse ```ts type UpdateJobsBatchOperationResponse = UpdateJobsBatchOperationResponses[keyof UpdateJobsBatchOperationResponses]; ``` --- ## Type Alias: UpdateJobsBatchOperationResponses ```ts type UpdateJobsBatchOperationResponses = object; ``` ## Properties ### 200 ```ts 200: BatchOperationCreatedResult; ``` The batch operation was created. --- ## Type Alias: UpdateMappingRuleData ```ts type UpdateMappingRuleData = object; ``` ## Properties ### body? ```ts optional body?: MappingRuleUpdateRequest; ``` --- ### path ```ts path: object; ``` #### mappingRuleId ```ts mappingRuleId: MappingRuleId; ``` The ID of the mapping rule to update. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/mapping-rules/{mappingRuleId}"; ``` --- ## Type Alias: UpdateMappingRuleError ```ts type UpdateMappingRuleError = UpdateMappingRuleErrors[keyof UpdateMappingRuleErrors]; ``` --- ## Type Alias: UpdateMappingRuleErrors ```ts type UpdateMappingRuleErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` The request to update a mapping rule was denied. More details are provided in the response body. --- ### 404 ```ts 404: ProblemDetail; ``` The request to update a mapping rule was denied. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: UpdateMappingRuleResponse ```ts type UpdateMappingRuleResponse = UpdateMappingRuleResponses[keyof UpdateMappingRuleResponses]; ``` --- ## Type Alias: UpdateMappingRuleResponses ```ts type UpdateMappingRuleResponses = object; ``` ## Properties ### 200 ```ts 200: MappingRuleUpdateResult; ``` The mapping rule was updated successfully. --- ## Type Alias: UpdateRoleData ```ts type UpdateRoleData = object; ``` ## Properties ### body ```ts body: RoleUpdateRequest; ``` --- ### path ```ts path: object; ``` #### roleId ```ts roleId: RoleId; ``` The role ID. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/roles/{roleId}"; ``` --- ## Type Alias: UpdateRoleError ```ts type UpdateRoleError = UpdateRoleErrors[keyof UpdateRoleErrors]; ``` --- ## Type Alias: UpdateRoleErrors ```ts type UpdateRoleErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 404 ```ts 404: ProblemDetail; ``` The role with the ID is not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: UpdateRoleResponse ```ts type UpdateRoleResponse = UpdateRoleResponses[keyof UpdateRoleResponses]; ``` --- ## Type Alias: UpdateRoleResponses ```ts type UpdateRoleResponses = object; ``` ## Properties ### 200 ```ts 200: RoleUpdateResult; ``` The role was updated successfully. --- ## Type Alias: UpdateTenantClusterVariableData ```ts type UpdateTenantClusterVariableData = object; ``` ## Properties ### body ```ts body: UpdateClusterVariableRequest; ``` --- ### path ```ts path: object; ``` #### name ```ts name: ClusterVariableName; ``` The name of the cluster variable #### tenantId ```ts tenantId: TenantId; ``` The tenant ID --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/cluster-variables/tenants/{tenantId}/{name}"; ``` --- ## Type Alias: UpdateTenantClusterVariableError ```ts type UpdateTenantClusterVariableError = UpdateTenantClusterVariableErrors[keyof UpdateTenantClusterVariableErrors]; ``` --- ## Type Alias: UpdateTenantClusterVariableErrors ```ts type UpdateTenantClusterVariableErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 401 ```ts 401: ProblemDetail; ``` The request lacks valid authentication credentials. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Cluster variable not found --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ## Type Alias: UpdateTenantClusterVariableResponse ```ts type UpdateTenantClusterVariableResponse = UpdateTenantClusterVariableResponses[keyof UpdateTenantClusterVariableResponses]; ``` --- ## Type Alias: UpdateTenantClusterVariableResponses ```ts type UpdateTenantClusterVariableResponses = object; ``` ## Properties ### 200 ```ts 200: ClusterVariableResult; ``` Cluster variable updated successfully --- ## Type Alias: UpdateTenantData ```ts type UpdateTenantData = object; ``` ## Properties ### body ```ts body: TenantUpdateRequest; ``` --- ### path ```ts path: object; ``` #### tenantId ```ts tenantId: TenantId; ``` The unique identifier of the tenant. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/tenants/{tenantId}"; ``` --- ## Type Alias: UpdateTenantError ```ts type UpdateTenantError = UpdateTenantErrors[keyof UpdateTenantErrors]; ``` --- ## Type Alias: UpdateTenantErrors ```ts type UpdateTenantErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` Not found. The tenant was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: UpdateTenantResponse ```ts type UpdateTenantResponse = UpdateTenantResponses[keyof UpdateTenantResponses]; ``` --- ## Type Alias: UpdateTenantResponses ```ts type UpdateTenantResponses = object; ``` ## Properties ### 200 ```ts 200: TenantUpdateResult; ``` The tenant was updated successfully. --- ## Type Alias: UpdateUserData ```ts type UpdateUserData = object; ``` ## Properties ### body ```ts body: UserUpdateRequest; ``` --- ### path ```ts path: object; ``` #### username ```ts username: Username; ``` The username of the user to update. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/users/{username}"; ``` --- ## Type Alias: UpdateUserError ```ts type UpdateUserError = UpdateUserErrors[keyof UpdateUserErrors]; ``` --- ## Type Alias: UpdateUserErrors ```ts type UpdateUserErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 403 ```ts 403: ProblemDetail; ``` Forbidden. The request is not allowed. --- ### 404 ```ts 404: ProblemDetail; ``` The user was not found. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ## Type Alias: UpdateUserResponse ```ts type UpdateUserResponse = UpdateUserResponses[keyof UpdateUserResponses]; ``` --- ## Type Alias: UpdateUserResponses ```ts type UpdateUserResponses = object; ``` ## Properties ### 200 ```ts 200: UserUpdateResult; ``` The user was updated successfully. --- ## Type Alias: UpdateUserTaskData ```ts type UpdateUserTaskData = object; ``` ## Properties ### body? ```ts optional body?: UserTaskUpdateRequest; ``` --- ### path ```ts path: object; ``` #### userTaskKey ```ts userTaskKey: UserTaskKeyWritable; ``` The key of the user task to update. --- ### query? ```ts optional query?: never; ``` --- ### url ```ts url: "/user-tasks/{userTaskKey}"; ``` --- ## Type Alias: UpdateUserTaskError ```ts type UpdateUserTaskError = UpdateUserTaskErrors[keyof UpdateUserTaskErrors]; ``` --- ## Type Alias: UpdateUserTaskErrors ```ts type UpdateUserTaskErrors = object; ``` ## Properties ### 400 ```ts 400: ProblemDetail; ``` The provided data is not valid. --- ### 404 ```ts 404: ProblemDetail; ``` The user task with the given key was not found. --- ### 409 ```ts 409: ProblemDetail; ``` The user task with the given key is in the wrong state currently. More details are provided in the response body. --- ### 500 ```ts 500: ProblemDetail; ``` An internal error occurred while processing the request. --- ### 503 ```ts 503: ProblemDetail; ``` The service is currently unavailable. This may happen only on some requests where the system creates backpressure to prevent the server's compute resources from being exhausted, avoiding more severe failures. In this case, the title of the error object contains `RESOURCE_EXHAUSTED`. Clients are recommended to eventually retry those requests after a backoff period. You can learn more about the backpressure mechanism here: https://docs.camunda.io/docs/components/zeebe/technical-concepts/internal-processing/#handling-backpressure . --- ### 504 ```ts 504: ProblemDetail; ``` The request timed out between the gateway and the broker. For these endpoints, this often happens when user task listeners are configured and the corresponding listener job is not completed within the request timeout. Common causes include no available job workers for the listener type, busy or crashed job workers, or delayed job completion. As with any gateway timeout, general timeout causes (for example transient network issues) can also result in a 504 response. Troubleshooting: - verify that job workers for the listener type are running and healthy - check worker logs for crashes, retries, and completion failures - check network connectivity between workers, gateway, and broker - retry with backoff after transient failures - fail without retries if a problem persists --- ## Type Alias: UpdateUserTaskResponse ```ts type UpdateUserTaskResponse = UpdateUserTaskResponses[keyof UpdateUserTaskResponses]; ``` --- ## Type Alias: UpdateUserTaskResponses ```ts type UpdateUserTaskResponses = object; ``` ## Properties ### 204 ```ts 204: void; ``` The user task was updated successfully. --- ## Type Alias: UsageMetricsResponse ```ts type UsageMetricsResponse = UsageMetricsResponseItem & object; ``` ## Type Declaration ### activeTenants ```ts activeTenants: number; ``` The amount of active tenants. ### tenants ```ts tenants: object; ``` The usage metrics by tenants. Only available if request `withTenants` query parameter was `true`. #### Index Signature ```ts [key: string]: UsageMetricsResponseItem ``` --- ## Type Alias: UsageMetricsResponseItem ```ts type UsageMetricsResponseItem = object; ``` ## Properties ### assignees ```ts assignees: number; ``` The amount of unique active task users. --- ### decisionInstances ```ts decisionInstances: number; ``` The amount of executed decision instances. --- ### processInstances ```ts processInstances: number; ``` The amount of created root process instances. --- ## Type Alias: UseSourceParentKeyInstruction ```ts type UseSourceParentKeyInstruction = object; ``` Instructs the engine to use the source's direct parent key as the ancestor scope key for the target element. This is a simpler alternative to `inferred` that skips hierarchy traversal and directly uses the source's parent key. This is useful when the source and target elements are siblings within the same flow scope. ## Properties ### ancestorScopeType ```ts ancestorScopeType: string; ``` The type of ancestor scope instruction. --- ## Type Alias: UserCreateResult ```ts type UserCreateResult = object; ``` ## Properties ### email ```ts email: string | null; ``` The email of the user. --- ### name ```ts name: string | null; ``` The name of the user. --- ### username ```ts username: Username; ``` The username of the created user. --- ## Type Alias: UserFilter ```ts type UserFilter = UserFilterFields & object; ``` User search filter. ## Type Declaration ### $or? ```ts optional $or?: UserFilterFields[] | null; ``` Defines a list of alternative filter groups combined using OR logic. Each object in the array is evaluated independently, and the filter matches if any one of them is satisfied. Top-level fields and the `$or` clause are combined using AND logic — meaning: (top-level filters) AND (any of the `$or` filters) must match. _Example:_ ```json { "$or": [{ "username": "user-1" }, { "username": "user-2" }] } ``` This matches users whose `username` is _user-1_ or _user-2_. Note: Using complex `$or` conditions may impact performance, use with caution in high-volume environments. --- ## Type Alias: UserFilterFields ```ts type UserFilterFields = object; ``` User search filter fields. ## Properties ### email? ```ts optional email?: StringFilterProperty; ``` The email of the user. --- ### name? ```ts optional name?: StringFilterProperty; ``` The name of the user. --- ### username? ```ts optional username?: StringFilterProperty; ``` The username of the user. --- ## Type Alias: UserRequest ```ts type UserRequest = object; ``` ## Properties ### email? ```ts optional email?: string; ``` The email of the user. --- ### name? ```ts optional name?: string; ``` The name of the user. --- ### password ```ts password: string; ``` The password of the user. --- ### username ```ts username: Username; ``` The username of the new user. --- ## Type Alias: UserResult ```ts type UserResult = object; ``` ## Properties ### email ```ts email: string | null; ``` The email of the user. --- ### name ```ts name: string | null; ``` The name of the user. --- ### username ```ts username: Username; ``` The username of the user. --- ## Type Alias: UserSearchQueryRequest ```ts type UserSearchQueryRequest = SearchQueryRequest & object; ``` ## Type Declaration ### filter? ```ts optional filter?: UserFilter; ``` The user search filters. ### sort? ```ts optional sort?: UserSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: UserSearchQuerySortRequest ```ts type UserSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: "username" | "name" | "email"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: UserSearchResult ```ts type UserSearchResult = SearchQueryResponse & object; ``` ## Type Declaration ### items ```ts items: UserResult[]; ``` The matching users. --- ## Type Alias: UserTaskAssignmentRequest ```ts type UserTaskAssignmentRequest = object; ``` ## Properties ### action? ```ts optional action?: string | null; ``` A custom action value that will be accessible from user task events resulting from this endpoint invocation. If not provided, it will default to "assign". --- ### allowOverride? ```ts optional allowOverride?: boolean | null; ``` By default, the task is reassigned if it was already assigned. Set this to `false` to return an error in such cases. The task must then first be unassigned to be assigned again. Use this when you have users picking from group task queues to prevent race conditions. --- ### assignee ```ts assignee: string; ``` The assignee for the user task. The assignee must not be empty or `null`. --- ## Type Alias: UserTaskAuditLogFilter ```ts type UserTaskAuditLogFilter = object; ``` The user task audit log search filters. ## Properties ### actorId? ```ts optional actorId?: StringFilterProperty; ``` The actor ID search filter. --- ### actorType? ```ts optional actorType?: AuditLogActorTypeFilterProperty; ``` The actor type search filter. --- ### operationType? ```ts optional operationType?: OperationTypeFilterProperty; ``` The audit log operation type search filter. --- ### result? ```ts optional result?: AuditLogResultFilterProperty; ``` The audit log result search filter. --- ### timestamp? ```ts optional timestamp?: DateTimeFilterProperty; ``` The audit log timestamp filter. --- ## Type Alias: UserTaskAuditLogSearchQueryRequest ```ts type UserTaskAuditLogSearchQueryRequest = SearchQueryRequest & object; ``` User task search query request. ## Type Declaration ### filter? ```ts optional filter?: UserTaskAuditLogFilter; ``` ### sort? ```ts optional sort?: AuditLogSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: UserTaskCompletionRequest ```ts type UserTaskCompletionRequest = object; ``` ## Properties ### action? ```ts optional action?: string | null; ``` A custom action value that will be accessible from user task events resulting from this endpoint invocation. If not provided, it will default to "complete". --- ### variables? ```ts optional variables?: | { [key: string]: unknown; } | null; ``` The variables to complete the user task with. --- ## Type Alias: UserTaskEffectiveVariableSearchQueryRequest ```ts type UserTaskEffectiveVariableSearchQueryRequest = object; ``` User task effective variable search query request. Uses offset-based pagination only. ## Properties ### filter? ```ts optional filter?: UserTaskVariableFilter; ``` The user task variable search filters. --- ### page? ```ts optional page?: OffsetPagination; ``` Pagination parameters. --- ### sort? ```ts optional sort?: UserTaskVariableSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: UserTaskFilter ```ts type UserTaskFilter = UserTaskFilterFields & object; ``` User task filter request. ## Type Declaration ### $or? ```ts optional $or?: UserTaskFilterFields[]; ``` Defines a list of alternative filter groups combined using OR logic. Each object in the array is evaluated independently, and the filter matches if any one of them is satisfied. Top-level fields and the `$or` clause are combined using AND logic — meaning: (top-level filters) AND (any of the `$or` filters) must match. _Example:_ ```json { "assignee": "user1", "$or": [{ "candidateGroup": "groupA" }, { "candidateUser": "user2" }] } ``` This matches user tasks that: - are assigned to _user1_ - and match either: - `candidateGroup` is _groupA_, or - `candidateUser` is _user2_ Note: Using complex `$or` conditions may impact performance, use with caution in high-volume environments. --- ## Type Alias: UserTaskFilterFields ```ts type UserTaskFilterFields = object; ``` User task filter fields. ## Properties ### assignee? ```ts optional assignee?: StringFilterProperty; ``` The assignee of the user task. --- ### businessId? ```ts optional businessId?: StringFilterProperty; ``` The business ID of the owning process instance the user task belongs to. This only works for user tasks created with 8.10 and onwards. Tasks from prior versions don't contain this data and cannot be found. --- ### candidateGroup? ```ts optional candidateGroup?: StringFilterProperty; ``` The candidate group for this user task. --- ### candidateUser? ```ts optional candidateUser?: StringFilterProperty; ``` The candidate user for this user task. --- ### completionDate? ```ts optional completionDate?: DateTimeFilterProperty; ``` The user task completion date. --- ### creationDate? ```ts optional creationDate?: DateTimeFilterProperty; ``` The user task creation date. --- ### dueDate? ```ts optional dueDate?: DateTimeFilterProperty; ``` The user task due date. --- ### elementId? ```ts optional elementId?: ElementId; ``` The element ID of the user task. --- ### elementInstanceKey? ```ts optional elementInstanceKey?: ElementInstanceKey; ``` The key of the element instance. --- ### followUpDate? ```ts optional followUpDate?: DateTimeFilterProperty; ``` The user task follow-up date. --- ### isSuspended? ```ts optional isSuspended?: boolean | null; ``` Whether the user task is suspended because its owning process instance is suspended. --- ### localVariables? ```ts optional localVariables?: VariableValueFilterProperty[]; ``` The local variables of the user task. --- ### name? ```ts optional name?: StringFilterProperty; ``` The task name. This only works for data created with 8.8 and onwards. Instances from prior versions don't contain this data and cannot be found. --- ### priority? ```ts optional priority?: IntegerFilterProperty; ``` The priority of the user task. --- ### processDefinitionId? ```ts optional processDefinitionId?: ProcessDefinitionIdFilterProperty; ``` The ID of the process definition. --- ### processDefinitionKey? ```ts optional processDefinitionKey?: ProcessDefinitionKeyFilterProperty; ``` The key of the process definition. --- ### processInstanceKey? ```ts optional processInstanceKey?: ProcessInstanceKeyFilterProperty; ``` The key of the process instance. --- ### processInstanceVariables? ```ts optional processInstanceVariables?: VariableValueFilterProperty[]; ``` The variables of the process instance. --- ### state? ```ts optional state?: UserTaskStateFilterProperty; ``` The user task state. --- ### tags? ```ts optional tags?: TagSet; ``` --- ### tenantId? ```ts optional tenantId?: StringFilterProperty; ``` Tenant ID of this user task. --- ### userTaskKey? ```ts optional userTaskKey?: UserTaskKey; ``` The key for this user task. --- ## Type Alias: UserTaskKey ```ts type UserTaskKey = CamundaKey<"UserTaskKey">; ``` System-generated key for a user task. --- ## Type Alias: UserTaskKeyWritable ```ts type UserTaskKeyWritable = LongKey; ``` System-generated key for a user task. --- ## Type Alias: UserTaskProperties ```ts type UserTaskProperties = object; ``` Contains properties of a user task. ## Properties ### action ```ts action: string; ``` The action performed on the user task. --- ### assignee ```ts assignee: string | null; ``` The user assigned to the task. --- ### candidateGroups ```ts candidateGroups: string[]; ``` The groups eligible to claim the task. --- ### candidateUsers ```ts candidateUsers: string[]; ``` The users eligible to claim the task. --- ### changedAttributes ```ts changedAttributes: string[]; ``` The attributes that were changed in the task. --- ### dueDate ```ts dueDate: string | null; ``` The due date of the user task in ISO 8601 format. --- ### followUpDate ```ts followUpDate: string | null; ``` The follow-up date of the user task in ISO 8601 format. --- ### formKey ```ts formKey: FormKey | null; ``` The key of the form associated with the user task. --- ### priority ```ts priority: number | null; ``` The priority of the user task. --- ### userTaskKey ```ts userTaskKey: UserTaskKey | null; ``` The unique key identifying the user task. --- ## Type Alias: UserTaskResult ```ts type UserTaskResult = object; ``` ## Properties ### assignee ```ts assignee: string | null; ``` The assignee of the user task. --- ### businessId ```ts businessId: BusinessId | null; ``` The business ID of the owning process instance, inherited when the user task was created. This is `null` for user tasks created before version 8.10, and for user tasks whose owning process instance has no business ID. --- ### candidateGroups ```ts candidateGroups: string[]; ``` The candidate groups for this user task. --- ### candidateUsers ```ts candidateUsers: string[]; ``` The candidate users for this user task. --- ### completionDate ```ts completionDate: string | null; ``` The completion date of a user task. --- ### creationDate ```ts creationDate: string; ``` The creation date of a user task. --- ### customHeaders ```ts customHeaders: object; ``` Custom headers for the user task. #### Index Signature ```ts [key: string]: string ``` --- ### dueDate ```ts dueDate: string | null; ``` The due date of a user task. --- ### elementId ```ts elementId: ElementId; ``` The element ID of the user task. --- ### elementInstanceKey ```ts elementInstanceKey: ElementInstanceKey; ``` The key of the element instance. --- ### externalFormReference ```ts externalFormReference: string | null; ``` The external form reference. --- ### followUpDate ```ts followUpDate: string | null; ``` The follow date of a user task. --- ### formKey ```ts formKey: FormKey | null; ``` The key of the form. --- ### isSuspended ```ts isSuspended: boolean; ``` Whether the user task is suspended because its owning process instance is suspended. --- ### name ```ts name: string | null; ``` The name for this user task. --- ### priority ```ts priority: number; ``` The priority of a user task. The higher the value the higher the priority. --- ### processDefinitionId ```ts processDefinitionId: ProcessDefinitionId; ``` The ID of the process definition. --- ### processDefinitionKey ```ts processDefinitionKey: ProcessDefinitionKey; ``` The key of the process definition. --- ### processDefinitionVersion ```ts processDefinitionVersion: number; ``` The version of the process definition. --- ### processInstanceKey ```ts processInstanceKey: ProcessInstanceKey; ``` The key of the process instance. --- ### processName ```ts processName: string | null; ``` The name of the process definition. This is `null` if the process has no name defined. --- ### rootProcessInstanceKey ```ts rootProcessInstanceKey: ProcessInstanceKey | null; ``` The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. --- ### state ```ts state: UserTaskStateEnum; ``` --- ### tags ```ts tags: TagSet; ``` --- ### tenantId ```ts tenantId: TenantId; ``` --- ### userTaskKey ```ts userTaskKey: UserTaskKey; ``` The key of the user task. --- ## Type Alias: UserTaskSearchQuery ```ts type UserTaskSearchQuery = SearchQueryRequest & object; ``` User task search query request. ## Type Declaration ### filter? ```ts optional filter?: UserTaskFilter; ``` The user task search filters. ### sort? ```ts optional sort?: UserTaskSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: UserTaskSearchQueryResult ```ts type UserTaskSearchQueryResult = SearchQueryResponse & object; ``` User task search query response. ## Type Declaration ### items ```ts items: UserTaskResult[]; ``` The matching user tasks. --- ## Type Alias: UserTaskSearchQuerySortRequest ```ts type UserTaskSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "creationDate" | "completionDate" | "followUpDate" | "dueDate" | "priority" | "name" | "businessId"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: UserTaskStateEnum ```ts type UserTaskStateEnum = (typeof UserTaskStateEnum)[keyof typeof UserTaskStateEnum]; ``` The state of the user task. Note: FAILED state is only for legacy job-worker-based tasks. --- ## Type Alias: UserTaskStateExactMatch ```ts type UserTaskStateExactMatch = UserTaskStateEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: UserTaskStateExactMatchWritable ```ts type UserTaskStateExactMatchWritable = UserTaskStateEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: UserTaskStateFilterProperty ```ts type UserTaskStateFilterProperty = UserTaskStateExactMatch | AdvancedUserTaskStateFilter; ``` UserTaskStateEnum property with full advanced search capabilities. --- ## Type Alias: UserTaskUpdateRequest ```ts type UserTaskUpdateRequest = object; ``` ## Properties ### action? ```ts optional action?: string | null; ``` A custom action value that will be accessible from user task events resulting from this endpoint invocation. If not provided, it will default to "update". --- ### changeset? ```ts optional changeset?: Changeset; ``` --- ## Type Alias: UserTaskVariableFilter ```ts type UserTaskVariableFilter = object; ``` The user task variable search filters. ## Properties ### name? ```ts optional name?: StringFilterProperty; ``` Name of the variable. --- ## Type Alias: UserTaskVariableSearchQueryRequest ```ts type UserTaskVariableSearchQueryRequest = SearchQueryRequest & object; ``` User task search query request. ## Type Declaration ### filter? ```ts optional filter?: UserTaskVariableFilter; ``` The user task variable search filters. ### sort? ```ts optional sort?: UserTaskVariableSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: UserTaskVariableSearchQuerySortRequest ```ts type UserTaskVariableSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "value" | "name" | "tenantId" | "variableKey" | "scopeKey" | "processInstanceKey"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: UserTaskWaitStateDetails ```ts type UserTaskWaitStateDetails = BaseWaitStateDetails & object; ``` ## Type Declaration ### dueDate ```ts dueDate: string | null; ``` The due date of the user task, if set. ### taskKey ```ts taskKey: UserTaskKey; ``` The key of the user task. --- ## Type Alias: UserUpdateRequest ```ts type UserUpdateRequest = object; ``` ## Properties ### email? ```ts optional email?: string; ``` The email of the user. --- ### name? ```ts optional name?: string; ``` The name of the user. --- ### password? ```ts optional password?: string; ``` The password of the user. If blank, the password is unchanged. --- ## Type Alias: UserUpdateResult ```ts type UserUpdateResult = object; ``` ## Properties ### email ```ts email: string | null; ``` The email of the user. --- ### name ```ts name: string | null; ``` The name of the user. --- ### username ```ts username: Username; ``` The username of the updated user. --- ## Type Alias: Username ```ts type Username = CamundaKey<"Username">; ``` The unique name of a user. --- ## Type Alias: ValidationMode ```ts type ValidationMode = "none" | "warn" | "strict" | "fanatical"; ``` --- ## Type Alias: VariableFilter ```ts type VariableFilter = object; ``` Variable filter request. ## Properties ### isTruncated? ```ts optional isTruncated?: boolean; ``` Whether the value is truncated or not. --- ### name? ```ts optional name?: StringFilterProperty; ``` Name of the variable. --- ### processInstanceKey? ```ts optional processInstanceKey?: ProcessInstanceKeyFilterProperty; ``` The key of the process instance of this variable. --- ### scopeKey? ```ts optional scopeKey?: ScopeKeyFilterProperty; ``` The key of the scope that defines where this variable is directly defined. This can be a process instance key (for process-level variables) or an element instance key (for local variables scoped to tasks, subprocesses, gateways, events, etc.). Use this filter to find variables directly defined in specific scopes. Note that this does not include variables from parent scopes that would be visible through the scope hierarchy. --- ### tenantId? ```ts optional tenantId?: TenantId; ``` Tenant ID of this variable. --- ### value? ```ts optional value?: StringFilterProperty; ``` The value of the variable. Variable values in filters need to be in serialized JSON format. For example, a variable with string value `myValue` can be found with the filter value `"myValue"`. Consider appropriate escaping for special characters in JSON strings when constructing filter values. --- ### variableKey? ```ts optional variableKey?: VariableKeyFilterProperty; ``` The key for this variable. --- ## Type Alias: VariableKey ```ts type VariableKey = CamundaKey<"VariableKey">; ``` System-generated key for a variable. --- ## Type Alias: VariableKeyExactMatch ```ts type VariableKeyExactMatch = VariableKey; ``` Exact match Matches the value exactly. --- ## Type Alias: VariableKeyExactMatchWritable ```ts type VariableKeyExactMatchWritable = VariableKeyWritable; ``` Exact match Matches the value exactly. --- ## Type Alias: VariableKeyFilterProperty ```ts type VariableKeyFilterProperty = VariableKeyExactMatch | AdvancedVariableKeyFilter; ``` VariableKey property with full advanced search capabilities. --- ## Type Alias: VariableKeyWritable ```ts type VariableKeyWritable = LongKey; ``` System-generated key for a variable. --- ## Type Alias: VariableResult ```ts type VariableResult = VariableResultBase & object; ``` Variable search response item. ## Type Declaration ### value ```ts value: string; ``` Full value of this variable. --- ## Type Alias: VariableResultBase ```ts type VariableResultBase = object; ``` Variable response item. ## Properties ### name ```ts name: string; ``` Name of this variable. --- ### processInstanceKey ```ts processInstanceKey: ProcessInstanceKey; ``` The key of the process instance of this variable. --- ### rootProcessInstanceKey ```ts rootProcessInstanceKey: ProcessInstanceKey | null; ``` The key of the root process instance. The root process instance is the top-level ancestor in the process instance hierarchy. This field is only present for data belonging to process instance hierarchies created in version 8.9 or later. --- ### scopeKey ```ts scopeKey: ScopeKey; ``` The key of the scope where this variable is directly defined. For process-level variables, this is the process instance key. For local variables, this is the key of the specific element instance (task, subprocess, gateway, event, etc.) where the variable is directly defined. --- ### tenantId ```ts tenantId: TenantId; ``` Tenant ID of this variable. --- ### variableKey ```ts variableKey: VariableKey; ``` The key for this variable. --- ## Type Alias: VariableSearchQuery ```ts type VariableSearchQuery = SearchQueryRequest & object; ``` Variable search query request. ## Type Declaration ### filter? ```ts optional filter?: VariableFilter; ``` The variable search filters. ### sort? ```ts optional sort?: VariableSearchQuerySortRequest[]; ``` Sort field criteria. --- ## Type Alias: VariableSearchQueryResult ```ts type VariableSearchQueryResult = SearchQueryResponse & object; ``` Variable search query response. ## Type Declaration ### items ```ts items: VariableSearchResult[]; ``` The matching variables. --- ## Type Alias: VariableSearchQuerySortRequest ```ts type VariableSearchQuerySortRequest = object; ``` ## Properties ### field ```ts field: | "value" | "name" | "tenantId" | "variableKey" | "scopeKey" | "processInstanceKey"; ``` The field to sort by. --- ### order? ```ts optional order?: SortOrderEnum; ``` --- ## Type Alias: VariableSearchResult ```ts type VariableSearchResult = VariableResultBase & object; ``` Variable search response item. ## Type Declaration ### isTruncated ```ts isTruncated: boolean; ``` Whether the value is truncated or not. ### value ```ts value: string; ``` Value of this variable. Can be truncated. --- ## Type Alias: VariableValueFilterProperty ```ts type VariableValueFilterProperty = object; ``` ## Properties ### name ```ts name: string; ``` Name of the variable. --- ### value ```ts value: StringFilterProperty; ``` The value of the variable. Variable values in filters need to be in serialized JSON format. For example, a variable with string value `myValue` can be found with the filter value `"myValue"`. Consider appropriate escaping for special characters in JSON strings when constructing filter values. --- ## Type Alias: WaitStateDetails ```ts type WaitStateDetails = | (object & JobWaitStateDetails) | (object & MessageWaitStateDetails) | (object & UserTaskWaitStateDetails) | (object & TimerWaitStateDetails) | (object & SignalWaitStateDetails) | (object & ConditionWaitStateDetails); ``` Wait-state-specific details of an element instance. --- ## Type Alias: WaitStateElementTypeEnum ```ts type WaitStateElementTypeEnum = (typeof WaitStateElementTypeEnum)[keyof typeof WaitStateElementTypeEnum]; ``` The BPMN element type of a waiting element instance. --- ## Type Alias: WaitStateElementTypeExactMatch ```ts type WaitStateElementTypeExactMatch = WaitStateElementTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: WaitStateElementTypeExactMatchWritable ```ts type WaitStateElementTypeExactMatchWritable = WaitStateElementTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: WaitStateElementTypeFilterProperty ```ts type WaitStateElementTypeFilterProperty = WaitStateElementTypeExactMatch | AdvancedWaitStateElementTypeFilter; ``` Element type property with full advanced search capabilities. --- ## Type Alias: WaitStateTypeEnum ```ts type WaitStateTypeEnum = (typeof WaitStateTypeEnum)[keyof typeof WaitStateTypeEnum]; ``` The type of waiting state an element instance is in. --- ## Type Alias: WaitStateTypeExactMatch ```ts type WaitStateTypeExactMatch = WaitStateTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: WaitStateTypeExactMatchWritable ```ts type WaitStateTypeExactMatchWritable = WaitStateTypeEnum; ``` Exact match Matches the value exactly. --- ## Type Alias: WaitStateTypeFilterProperty ```ts type WaitStateTypeFilterProperty = WaitStateTypeExactMatch | AdvancedWaitStateTypeFilter; ``` Wait state type property with full advanced search capabilities. --- ## Type Alias: WebappComponent ```ts type WebappComponent = "operate" | "tasklist" | "admin"; ``` A Camunda webapp component name. --- ## Type Alias: WithSearchPagination # Type Alias: WithSearchPagination\ ```ts type WithSearchPagination = C & SearchPaginationApi; ``` A Camunda client augmented with `.paginate` on every search operation. ## Type Parameters ### C `C` --- ## Type Alias: activateAdHocSubProcessActivitiesInput ```ts type activateAdHocSubProcessActivitiesInput = activateAdHocSubProcessActivitiesBody & object; ``` ## Type Declaration ### adHocSubProcessInstanceKey ```ts adHocSubProcessInstanceKey: activateAdHocSubProcessActivitiesPathParam_adHocSubProcessInstanceKey; ``` --- ## Type Alias: activateJobsInput ```ts type activateJobsInput = activateJobsBody; ``` --- ## Type Alias: assignClientToGroupInput ```ts type assignClientToGroupInput = object; ``` ## Properties ### clientId ```ts clientId: assignClientToGroupPathParam_clientId; ``` --- ### groupId ```ts groupId: assignClientToGroupPathParam_groupId; ``` --- ## Type Alias: assignClientToTenantInput ```ts type assignClientToTenantInput = object; ``` ## Properties ### clientId ```ts clientId: assignClientToTenantPathParam_clientId; ``` --- ### tenantId ```ts tenantId: assignClientToTenantPathParam_tenantId; ``` --- ## Type Alias: assignGroupToTenantInput ```ts type assignGroupToTenantInput = object; ``` ## Properties ### groupId ```ts groupId: assignGroupToTenantPathParam_groupId; ``` --- ### tenantId ```ts tenantId: assignGroupToTenantPathParam_tenantId; ``` --- ## Type Alias: assignMappingRuleToGroupInput ```ts type assignMappingRuleToGroupInput = object; ``` ## Properties ### groupId ```ts groupId: assignMappingRuleToGroupPathParam_groupId; ``` --- ### mappingRuleId ```ts mappingRuleId: assignMappingRuleToGroupPathParam_mappingRuleId; ``` --- ## Type Alias: assignMappingRuleToTenantInput ```ts type assignMappingRuleToTenantInput = object; ``` ## Properties ### mappingRuleId ```ts mappingRuleId: assignMappingRuleToTenantPathParam_mappingRuleId; ``` --- ### tenantId ```ts tenantId: assignMappingRuleToTenantPathParam_tenantId; ``` --- ## Type Alias: assignProcessInstanceBusinessIdInput ```ts type assignProcessInstanceBusinessIdInput = assignProcessInstanceBusinessIdBody & object; ``` ## Type Declaration ### processInstanceKey ```ts processInstanceKey: assignProcessInstanceBusinessIdPathParam_processInstanceKey; ``` --- ## Type Alias: assignRoleToClientInput ```ts type assignRoleToClientInput = object; ``` ## Properties ### clientId ```ts clientId: assignRoleToClientPathParam_clientId; ``` --- ### roleId ```ts roleId: assignRoleToClientPathParam_roleId; ``` --- ## Type Alias: assignRoleToGroupInput ```ts type assignRoleToGroupInput = object; ``` ## Properties ### groupId ```ts groupId: assignRoleToGroupPathParam_groupId; ``` --- ### roleId ```ts roleId: assignRoleToGroupPathParam_roleId; ``` --- ## Type Alias: assignRoleToMappingRuleInput ```ts type assignRoleToMappingRuleInput = object; ``` ## Properties ### mappingRuleId ```ts mappingRuleId: assignRoleToMappingRulePathParam_mappingRuleId; ``` --- ### roleId ```ts roleId: assignRoleToMappingRulePathParam_roleId; ``` --- ## Type Alias: assignRoleToTenantInput ```ts type assignRoleToTenantInput = object; ``` ## Properties ### roleId ```ts roleId: assignRoleToTenantPathParam_roleId; ``` --- ### tenantId ```ts tenantId: assignRoleToTenantPathParam_tenantId; ``` --- ## Type Alias: assignRoleToUserInput ```ts type assignRoleToUserInput = object; ``` ## Properties ### roleId ```ts roleId: assignRoleToUserPathParam_roleId; ``` --- ### username ```ts username: assignRoleToUserPathParam_username; ``` --- ## Type Alias: assignUserTaskInput ```ts type assignUserTaskInput = assignUserTaskBody & object; ``` ## Type Declaration ### userTaskKey ```ts userTaskKey: assignUserTaskPathParam_userTaskKey; ``` --- ## Type Alias: assignUserToGroupInput ```ts type assignUserToGroupInput = object; ``` ## Properties ### groupId ```ts groupId: assignUserToGroupPathParam_groupId; ``` --- ### username ```ts username: assignUserToGroupPathParam_username; ``` --- ## Type Alias: assignUserToTenantInput ```ts type assignUserToTenantInput = object; ``` ## Properties ### tenantId ```ts tenantId: assignUserToTenantPathParam_tenantId; ``` --- ### username ```ts username: assignUserToTenantPathParam_username; ``` --- ## Type Alias: broadcastSignalInput ```ts type broadcastSignalInput = broadcastSignalBody; ``` --- ## Type Alias: cancelBatchOperationInput ```ts type cancelBatchOperationInput = cancelBatchOperationBody & object; ``` ## Type Declaration ### batchOperationKey ```ts batchOperationKey: cancelBatchOperationPathParam_batchOperationKey; ``` --- ## Type Alias: cancelClusterRebalanceInput ```ts type cancelClusterRebalanceInput = void; ``` --- ## Type Alias: cancelProcessInstanceInput ```ts type cancelProcessInstanceInput = cancelProcessInstanceBody & object; ``` ## Type Declaration ### processInstanceKey ```ts processInstanceKey: cancelProcessInstancePathParam_processInstanceKey; ``` --- ## Type Alias: cancelProcessInstancesBatchOperationInput ```ts type cancelProcessInstancesBatchOperationInput = cancelProcessInstancesBatchOperationBody; ``` --- ## Type Alias: changeClusterModeAsClusterAdminInput ```ts type changeClusterModeAsClusterAdminInput = object; ``` ## Properties ### dryRun? ```ts optional dryRun?: changeClusterModeAsClusterAdminQueryParam_dryRun; ``` --- ### mode ```ts mode: changeClusterModeAsClusterAdminQueryParam_mode; ``` --- ### physicalTenantId? ```ts optional physicalTenantId?: changeClusterModeAsClusterAdminQueryParam_physicalTenantId; ``` --- ## Type Alias: changeClusterModeInput ```ts type changeClusterModeInput = object; ``` ## Properties ### dryRun? ```ts optional dryRun?: changeClusterModeQueryParam_dryRun; ``` --- ### mode ```ts mode: changeClusterModeQueryParam_mode; ``` --- ## Type Alias: completeJobInput ```ts type completeJobInput = completeJobBody & object; ``` ## Type Declaration ### jobKey ```ts jobKey: completeJobPathParam_jobKey; ``` --- ## Type Alias: completeUserTaskInput ```ts type completeUserTaskInput = completeUserTaskBody & object; ``` ## Type Declaration ### userTaskKey ```ts userTaskKey: completeUserTaskPathParam_userTaskKey; ``` --- ## Type Alias: correlateMessageInput ```ts type correlateMessageInput = correlateMessageBody; ``` --- ## Type Alias: createAdminUserInput ```ts type createAdminUserInput = createAdminUserBody; ``` --- ## Type Alias: createAgentInstanceInput ```ts type createAgentInstanceInput = createAgentInstanceBody; ``` --- ## Type Alias: createAuthorizationInput ```ts type createAuthorizationInput = createAuthorizationBody; ``` --- ## Type Alias: createDeploymentInput ```ts type createDeploymentInput = Omit & object; ``` ## Type Declaration ### resources ```ts resources: File[]; ``` --- ## Type Alias: createDocumentInput ```ts type createDocumentInput = createDocumentBody & object; ``` ## Type Declaration ### documentId? ```ts optional documentId?: createDocumentQueryParam_documentId; ``` ### storeId? ```ts optional storeId?: createDocumentQueryParam_storeId; ``` --- ## Type Alias: createDocumentLinkInput ```ts type createDocumentLinkInput = createDocumentLinkBody & object; ``` ## Type Declaration ### contentHash? ```ts optional contentHash?: createDocumentLinkQueryParam_contentHash; ``` ### documentId ```ts documentId: createDocumentLinkPathParam_documentId; ``` ### storeId? ```ts optional storeId?: createDocumentLinkQueryParam_storeId; ``` --- ## Type Alias: createDocumentsInput ```ts type createDocumentsInput = createDocumentsBody & object; ``` ## Type Declaration ### storeId? ```ts optional storeId?: createDocumentsQueryParam_storeId; ``` --- ## Type Alias: createElementInstanceVariablesInput ```ts type createElementInstanceVariablesInput = createElementInstanceVariablesBody & object; ``` ## Type Declaration ### elementInstanceKey ```ts elementInstanceKey: createElementInstanceVariablesPathParam_elementInstanceKey; ``` --- ## Type Alias: createGlobalClusterVariableInput ```ts type createGlobalClusterVariableInput = createGlobalClusterVariableBody; ``` --- ## Type Alias: createGlobalTaskListenerInput ```ts type createGlobalTaskListenerInput = createGlobalTaskListenerBody; ``` --- ## Type Alias: createGroupInput ```ts type createGroupInput = createGroupBody; ``` --- ## Type Alias: createMappingRuleInput ```ts type createMappingRuleInput = createMappingRuleBody; ``` --- ## Type Alias: createProcessInstanceInput ```ts type createProcessInstanceInput = createProcessInstanceBody; ``` --- ## Type Alias: createRoleInput ```ts type createRoleInput = createRoleBody; ``` --- ## Type Alias: createTenantClusterVariableInput ```ts type createTenantClusterVariableInput = createTenantClusterVariableBody & object; ``` ## Type Declaration ### tenantId ```ts tenantId: createTenantClusterVariablePathParam_tenantId; ``` --- ## Type Alias: createTenantInput ```ts type createTenantInput = createTenantBody; ``` --- ## Type Alias: createUserInput ```ts type createUserInput = createUserBody; ``` --- ## Type Alias: deleteAuthorizationInput ```ts type deleteAuthorizationInput = object; ``` ## Properties ### authorizationKey ```ts authorizationKey: deleteAuthorizationPathParam_authorizationKey; ``` --- ## Type Alias: deleteDecisionInstanceInput ```ts type deleteDecisionInstanceInput = deleteDecisionInstanceBody & object; ``` ## Type Declaration ### decisionEvaluationKey ```ts decisionEvaluationKey: deleteDecisionInstancePathParam_decisionEvaluationKey; ``` --- ## Type Alias: deleteDecisionInstancesBatchOperationInput ```ts type deleteDecisionInstancesBatchOperationInput = deleteDecisionInstancesBatchOperationBody; ``` --- ## Type Alias: deleteDocumentInput ```ts type deleteDocumentInput = object; ``` ## Properties ### documentId ```ts documentId: deleteDocumentPathParam_documentId; ``` --- ### storeId? ```ts optional storeId?: deleteDocumentQueryParam_storeId; ``` --- ## Type Alias: deleteGlobalClusterVariableInput ```ts type deleteGlobalClusterVariableInput = object; ``` ## Properties ### name ```ts name: deleteGlobalClusterVariablePathParam_name; ``` --- ## Type Alias: deleteGlobalTaskListenerInput ```ts type deleteGlobalTaskListenerInput = object; ``` ## Properties ### id ```ts id: deleteGlobalTaskListenerPathParam_id; ``` --- ## Type Alias: deleteGroupInput ```ts type deleteGroupInput = object; ``` ## Properties ### groupId ```ts groupId: deleteGroupPathParam_groupId; ``` --- ## Type Alias: deleteHistoryBackupAsClusterAdminInput ```ts type deleteHistoryBackupAsClusterAdminInput = object; ``` ## Properties ### backupId ```ts backupId: deleteHistoryBackupAsClusterAdminPathParam_backupId; ``` --- ### physicalTenantId? ```ts optional physicalTenantId?: deleteHistoryBackupAsClusterAdminQueryParam_physicalTenantId; ``` --- ## Type Alias: deleteHistoryBackupInput ```ts type deleteHistoryBackupInput = object; ``` ## Properties ### backupId ```ts backupId: deleteHistoryBackupPathParam_backupId; ``` --- ## Type Alias: deleteMappingRuleInput ```ts type deleteMappingRuleInput = object; ``` ## Properties ### mappingRuleId ```ts mappingRuleId: deleteMappingRulePathParam_mappingRuleId; ``` --- ## Type Alias: deleteProcessInstanceInput ```ts type deleteProcessInstanceInput = deleteProcessInstanceBody & object; ``` ## Type Declaration ### processInstanceKey ```ts processInstanceKey: deleteProcessInstancePathParam_processInstanceKey; ``` --- ## Type Alias: deleteProcessInstancesBatchOperationInput ```ts type deleteProcessInstancesBatchOperationInput = deleteProcessInstancesBatchOperationBody; ``` --- ## Type Alias: deleteResourceInput ```ts type deleteResourceInput = deleteResourceBody & object; ``` ## Type Declaration ### resourceKey ```ts resourceKey: deleteResourcePathParam_resourceKey; ``` --- ## Type Alias: deleteRoleInput ```ts type deleteRoleInput = object; ``` ## Properties ### roleId ```ts roleId: deleteRolePathParam_roleId; ``` --- ## Type Alias: deleteRuntimeBackupAsClusterAdminInput ```ts type deleteRuntimeBackupAsClusterAdminInput = object; ``` ## Properties ### backupId ```ts backupId: deleteRuntimeBackupAsClusterAdminPathParam_backupId; ``` --- ### physicalTenantId? ```ts optional physicalTenantId?: deleteRuntimeBackupAsClusterAdminQueryParam_physicalTenantId; ``` --- ## Type Alias: deleteRuntimeBackupInput ```ts type deleteRuntimeBackupInput = object; ``` ## Properties ### backupId ```ts backupId: deleteRuntimeBackupPathParam_backupId; ``` --- ## Type Alias: deleteRuntimeBackupStateAsClusterAdminInput ```ts type deleteRuntimeBackupStateAsClusterAdminInput = object; ``` ## Properties ### physicalTenantId? ```ts optional physicalTenantId?: deleteRuntimeBackupStateAsClusterAdminQueryParam_physicalTenantId; ``` --- ## Type Alias: deleteRuntimeBackupStateInput ```ts type deleteRuntimeBackupStateInput = void; ``` --- ## Type Alias: deleteTenantClusterVariableInput ```ts type deleteTenantClusterVariableInput = object; ``` ## Properties ### name ```ts name: deleteTenantClusterVariablePathParam_name; ``` --- ### tenantId ```ts tenantId: deleteTenantClusterVariablePathParam_tenantId; ``` --- ## Type Alias: deleteTenantInput ```ts type deleteTenantInput = object; ``` ## Properties ### tenantId ```ts tenantId: deleteTenantPathParam_tenantId; ``` --- ## Type Alias: deleteUserInput ```ts type deleteUserInput = object; ``` ## Properties ### username ```ts username: deleteUserPathParam_username; ``` --- ## Type Alias: evaluateConditionalsInput ```ts type evaluateConditionalsInput = evaluateConditionalsBody; ``` --- ## Type Alias: evaluateDecisionInput ```ts type evaluateDecisionInput = evaluateDecisionBody; ``` --- ## Type Alias: evaluateExpressionInput ```ts type evaluateExpressionInput = evaluateExpressionBody; ``` --- ## Type Alias: failJobInput ```ts type failJobInput = failJobBody & object; ``` ## Type Declaration ### jobKey ```ts jobKey: failJobPathParam_jobKey; ``` --- ## Type Alias: getAgentDefinitionConsistency ```ts type getAgentDefinitionConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getAgentDefinitionInput ```ts type getAgentDefinitionInput = object; ``` ## Properties ### agentDefinitionKey ```ts agentDefinitionKey: getAgentDefinitionPathParam_agentDefinitionKey; ``` --- ## Type Alias: getAgentInstanceConsistency ```ts type getAgentInstanceConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getAgentInstanceInput ```ts type getAgentInstanceInput = object; ``` ## Properties ### agentInstanceKey ```ts agentInstanceKey: getAgentInstancePathParam_agentInstanceKey; ``` --- ## Type Alias: getAuditLogConsistency ```ts type getAuditLogConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getAuditLogInput ```ts type getAuditLogInput = object; ``` ## Properties ### auditLogKey ```ts auditLogKey: getAuditLogPathParam_auditLogKey; ``` --- ## Type Alias: getAuthenticationInput ```ts type getAuthenticationInput = void; ``` --- ## Type Alias: getAuthorizationConsistency ```ts type getAuthorizationConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getAuthorizationInput ```ts type getAuthorizationInput = object; ``` ## Properties ### authorizationKey ```ts authorizationKey: getAuthorizationPathParam_authorizationKey; ``` --- ## Type Alias: getBatchOperationConsistency ```ts type getBatchOperationConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getBatchOperationInput ```ts type getBatchOperationInput = object; ``` ## Properties ### batchOperationKey ```ts batchOperationKey: getBatchOperationPathParam_batchOperationKey; ``` --- ## Type Alias: getClusterExportingStatusInput ```ts type getClusterExportingStatusInput = void; ``` --- ## Type Alias: getClusterRebalanceInput ```ts type getClusterRebalanceInput = void; ``` --- ## Type Alias: getClusterStatusInput ```ts type getClusterStatusInput = void; ``` --- ## Type Alias: getClusterTopologyInput ```ts type getClusterTopologyInput = void; ``` --- ## Type Alias: getClusterUpgradeStatusInput ```ts type getClusterUpgradeStatusInput = void; ``` --- ## Type Alias: getDecisionDefinitionConsistency ```ts type getDecisionDefinitionConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getDecisionDefinitionInput ```ts type getDecisionDefinitionInput = object; ``` ## Properties ### decisionDefinitionKey ```ts decisionDefinitionKey: getDecisionDefinitionPathParam_decisionDefinitionKey; ``` --- ## Type Alias: getDecisionDefinitionXmlConsistency ```ts type getDecisionDefinitionXmlConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getDecisionDefinitionXmlInput ```ts type getDecisionDefinitionXmlInput = object; ``` ## Properties ### decisionDefinitionKey ```ts decisionDefinitionKey: getDecisionDefinitionXmlPathParam_decisionDefinitionKey; ``` --- ## Type Alias: getDecisionInstanceConsistency ```ts type getDecisionInstanceConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getDecisionInstanceInput ```ts type getDecisionInstanceInput = object; ``` ## Properties ### decisionEvaluationInstanceKey ```ts decisionEvaluationInstanceKey: getDecisionInstancePathParam_decisionEvaluationInstanceKey; ``` --- ## Type Alias: getDecisionRequirementsConsistency ```ts type getDecisionRequirementsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getDecisionRequirementsInput ```ts type getDecisionRequirementsInput = object; ``` ## Properties ### decisionRequirementsKey ```ts decisionRequirementsKey: getDecisionRequirementsPathParam_decisionRequirementsKey; ``` --- ## Type Alias: getDecisionRequirementsXmlConsistency ```ts type getDecisionRequirementsXmlConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getDecisionRequirementsXmlInput ```ts type getDecisionRequirementsXmlInput = object; ``` ## Properties ### decisionRequirementsKey ```ts decisionRequirementsKey: getDecisionRequirementsXmlPathParam_decisionRequirementsKey; ``` --- ## Type Alias: getDocumentInput ```ts type getDocumentInput = object; ``` ## Properties ### contentHash? ```ts optional contentHash?: getDocumentQueryParam_contentHash; ``` --- ### documentId ```ts documentId: getDocumentPathParam_documentId; ``` --- ### storeId? ```ts optional storeId?: getDocumentQueryParam_storeId; ``` --- ## Type Alias: getElementInstanceConsistency ```ts type getElementInstanceConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getElementInstanceInput ```ts type getElementInstanceInput = object; ``` ## Properties ### elementInstanceKey ```ts elementInstanceKey: getElementInstancePathParam_elementInstanceKey; ``` --- ## Type Alias: getExportingStatusInput ```ts type getExportingStatusInput = void; ``` --- ## Type Alias: getFormByKeyConsistency ```ts type getFormByKeyConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getFormByKeyInput ```ts type getFormByKeyInput = object; ``` ## Properties ### formKey ```ts formKey: getFormByKeyPathParam_formKey; ``` --- ## Type Alias: getGlobalClusterVariableConsistency ```ts type getGlobalClusterVariableConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getGlobalClusterVariableInput ```ts type getGlobalClusterVariableInput = object; ``` ## Properties ### name ```ts name: getGlobalClusterVariablePathParam_name; ``` --- ## Type Alias: getGlobalJobStatisticsConsistency ```ts type getGlobalJobStatisticsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getGlobalJobStatisticsInput ```ts type getGlobalJobStatisticsInput = object; ``` ## Properties ### from ```ts from: getGlobalJobStatisticsQueryParam_from; ``` --- ### jobType? ```ts optional jobType?: getGlobalJobStatisticsQueryParam_jobType; ``` --- ### to ```ts to: getGlobalJobStatisticsQueryParam_to; ``` --- ## Type Alias: getGlobalTaskListenerConsistency ```ts type getGlobalTaskListenerConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getGlobalTaskListenerInput ```ts type getGlobalTaskListenerInput = object; ``` ## Properties ### id ```ts id: getGlobalTaskListenerPathParam_id; ``` --- ## Type Alias: getGroupConsistency ```ts type getGroupConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getGroupInput ```ts type getGroupInput = object; ``` ## Properties ### groupId ```ts groupId: getGroupPathParam_groupId; ``` --- ## Type Alias: getHistoryBackupAsClusterAdminInput ```ts type getHistoryBackupAsClusterAdminInput = object; ``` ## Properties ### backupId ```ts backupId: getHistoryBackupAsClusterAdminPathParam_backupId; ``` --- ### physicalTenantId? ```ts optional physicalTenantId?: getHistoryBackupAsClusterAdminQueryParam_physicalTenantId; ``` --- ## Type Alias: getHistoryBackupInput ```ts type getHistoryBackupInput = object; ``` ## Properties ### backupId ```ts backupId: getHistoryBackupPathParam_backupId; ``` --- ## Type Alias: getIncidentConsistency ```ts type getIncidentConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getIncidentInput ```ts type getIncidentInput = object; ``` ## Properties ### incidentKey ```ts incidentKey: getIncidentPathParam_incidentKey; ``` --- ## Type Alias: getJobErrorStatisticsConsistency ```ts type getJobErrorStatisticsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getJobErrorStatisticsInput ```ts type getJobErrorStatisticsInput = getJobErrorStatisticsBody; ``` --- ## Type Alias: getJobTimeSeriesStatisticsConsistency ```ts type getJobTimeSeriesStatisticsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getJobTimeSeriesStatisticsInput ```ts type getJobTimeSeriesStatisticsInput = getJobTimeSeriesStatisticsBody; ``` --- ## Type Alias: getJobTypeStatisticsConsistency ```ts type getJobTypeStatisticsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getJobTypeStatisticsInput ```ts type getJobTypeStatisticsInput = getJobTypeStatisticsBody; ``` --- ## Type Alias: getJobWorkerStatisticsConsistency ```ts type getJobWorkerStatisticsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getJobWorkerStatisticsInput ```ts type getJobWorkerStatisticsInput = getJobWorkerStatisticsBody; ``` --- ## Type Alias: getLicenseInput ```ts type getLicenseInput = void; ``` --- ## Type Alias: getMappingRuleConsistency ```ts type getMappingRuleConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getMappingRuleInput ```ts type getMappingRuleInput = object; ``` ## Properties ### mappingRuleId ```ts mappingRuleId: getMappingRulePathParam_mappingRuleId; ``` --- ## Type Alias: getProcessDefinitionConsistency ```ts type getProcessDefinitionConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getProcessDefinitionInput ```ts type getProcessDefinitionInput = object; ``` ## Properties ### processDefinitionKey ```ts processDefinitionKey: getProcessDefinitionPathParam_processDefinitionKey; ``` --- ## Type Alias: getProcessDefinitionInstanceStatisticsConsistency ```ts type getProcessDefinitionInstanceStatisticsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions< _DataOf >; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getProcessDefinitionInstanceStatisticsInput ```ts type getProcessDefinitionInstanceStatisticsInput = getProcessDefinitionInstanceStatisticsBody; ``` --- ## Type Alias: getProcessDefinitionInstanceVersionStatisticsConsistency ```ts type getProcessDefinitionInstanceVersionStatisticsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions< _DataOf >; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getProcessDefinitionInstanceVersionStatisticsInput ```ts type getProcessDefinitionInstanceVersionStatisticsInput = getProcessDefinitionInstanceVersionStatisticsBody; ``` --- ## Type Alias: getProcessDefinitionMessageSubscriptionStatisticsConsistency ```ts type getProcessDefinitionMessageSubscriptionStatisticsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions< _DataOf >; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getProcessDefinitionMessageSubscriptionStatisticsInput ```ts type getProcessDefinitionMessageSubscriptionStatisticsInput = getProcessDefinitionMessageSubscriptionStatisticsBody; ``` --- ## Type Alias: getProcessDefinitionStatisticsConsistency ```ts type getProcessDefinitionStatisticsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions< _DataOf >; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getProcessDefinitionStatisticsInput ```ts type getProcessDefinitionStatisticsInput = getProcessDefinitionStatisticsBody & object; ``` ## Type Declaration ### processDefinitionKey ```ts processDefinitionKey: getProcessDefinitionStatisticsPathParam_processDefinitionKey; ``` --- ## Type Alias: getProcessDefinitionXmlConsistency ```ts type getProcessDefinitionXmlConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getProcessDefinitionXmlInput ```ts type getProcessDefinitionXmlInput = object; ``` ## Properties ### processDefinitionKey ```ts processDefinitionKey: getProcessDefinitionXmlPathParam_processDefinitionKey; ``` --- ## Type Alias: getProcessInstanceCallHierarchyConsistency ```ts type getProcessInstanceCallHierarchyConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions< _DataOf >; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getProcessInstanceCallHierarchyInput ```ts type getProcessInstanceCallHierarchyInput = object; ``` ## Properties ### processInstanceKey ```ts processInstanceKey: getProcessInstanceCallHierarchyPathParam_processInstanceKey; ``` --- ## Type Alias: getProcessInstanceConsistency ```ts type getProcessInstanceConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getProcessInstanceInput ```ts type getProcessInstanceInput = object; ``` ## Properties ### processInstanceKey ```ts processInstanceKey: getProcessInstancePathParam_processInstanceKey; ``` --- ## Type Alias: getProcessInstanceSequenceFlowsConsistency ```ts type getProcessInstanceSequenceFlowsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions< _DataOf >; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getProcessInstanceSequenceFlowsInput ```ts type getProcessInstanceSequenceFlowsInput = object; ``` ## Properties ### processInstanceKey ```ts processInstanceKey: getProcessInstanceSequenceFlowsPathParam_processInstanceKey; ``` --- ## Type Alias: getProcessInstanceStatisticsByDefinitionConsistency ```ts type getProcessInstanceStatisticsByDefinitionConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions< _DataOf >; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getProcessInstanceStatisticsByDefinitionInput ```ts type getProcessInstanceStatisticsByDefinitionInput = getProcessInstanceStatisticsByDefinitionBody; ``` --- ## Type Alias: getProcessInstanceStatisticsByErrorConsistency ```ts type getProcessInstanceStatisticsByErrorConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions< _DataOf >; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getProcessInstanceStatisticsByErrorInput ```ts type getProcessInstanceStatisticsByErrorInput = getProcessInstanceStatisticsByErrorBody; ``` --- ## Type Alias: getProcessInstanceStatisticsConsistency ```ts type getProcessInstanceStatisticsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions< _DataOf >; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getProcessInstanceStatisticsInput ```ts type getProcessInstanceStatisticsInput = object; ``` ## Properties ### processInstanceKey ```ts processInstanceKey: getProcessInstanceStatisticsPathParam_processInstanceKey; ``` --- ## Type Alias: getProcessInstanceWaitStateStatisticsConsistency ```ts type getProcessInstanceWaitStateStatisticsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions< _DataOf >; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getProcessInstanceWaitStateStatisticsInput ```ts type getProcessInstanceWaitStateStatisticsInput = object; ``` ## Properties ### processInstanceKey ```ts processInstanceKey: getProcessInstanceWaitStateStatisticsPathParam_processInstanceKey; ``` --- ## Type Alias: getResourceConsistency ```ts type getResourceConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getResourceContentBinaryConsistency ```ts type getResourceContentBinaryConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getResourceContentBinaryInput ```ts type getResourceContentBinaryInput = object; ``` ## Properties ### resourceKey ```ts resourceKey: getResourceContentBinaryPathParam_resourceKey; ``` --- ## Type Alias: getResourceContentConsistency ```ts type getResourceContentConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getResourceContentInput ```ts type getResourceContentInput = object; ``` ## Properties ### resourceKey ```ts resourceKey: getResourceContentPathParam_resourceKey; ``` --- ## Type Alias: getResourceInput ```ts type getResourceInput = object; ``` ## Properties ### resourceKey ```ts resourceKey: getResourcePathParam_resourceKey; ``` --- ## Type Alias: getRestoreStatusInput ```ts type getRestoreStatusInput = void; ``` --- ## Type Alias: getRoleConsistency ```ts type getRoleConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getRoleInput ```ts type getRoleInput = object; ``` ## Properties ### roleId ```ts roleId: getRolePathParam_roleId; ``` --- ## Type Alias: getRuntimeBackupAsClusterAdminInput ```ts type getRuntimeBackupAsClusterAdminInput = object; ``` ## Properties ### backupId ```ts backupId: getRuntimeBackupAsClusterAdminPathParam_backupId; ``` --- ### physicalTenantId? ```ts optional physicalTenantId?: getRuntimeBackupAsClusterAdminQueryParam_physicalTenantId; ``` --- ## Type Alias: getRuntimeBackupInput ```ts type getRuntimeBackupInput = object; ``` ## Properties ### backupId ```ts backupId: getRuntimeBackupPathParam_backupId; ``` --- ## Type Alias: getRuntimeBackupStateAsClusterAdminInput ```ts type getRuntimeBackupStateAsClusterAdminInput = object; ``` ## Properties ### physicalTenantId? ```ts optional physicalTenantId?: getRuntimeBackupStateAsClusterAdminQueryParam_physicalTenantId; ``` --- ## Type Alias: getRuntimeBackupStateInput ```ts type getRuntimeBackupStateInput = void; ``` --- ## Type Alias: getStartProcessFormConsistency ```ts type getStartProcessFormConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getStartProcessFormInput ```ts type getStartProcessFormInput = object; ``` ## Properties ### processDefinitionKey ```ts processDefinitionKey: getStartProcessFormPathParam_processDefinitionKey; ``` --- ## Type Alias: getStatusInput ```ts type getStatusInput = void; ``` --- ## Type Alias: getSystemConfigurationInput ```ts type getSystemConfigurationInput = void; ``` --- ## Type Alias: getTenantClusterVariableConsistency ```ts type getTenantClusterVariableConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getTenantClusterVariableInput ```ts type getTenantClusterVariableInput = object; ``` ## Properties ### name ```ts name: getTenantClusterVariablePathParam_name; ``` --- ### tenantId ```ts tenantId: getTenantClusterVariablePathParam_tenantId; ``` --- ## Type Alias: getTenantConsistency ```ts type getTenantConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getTenantInput ```ts type getTenantInput = object; ``` ## Properties ### tenantId ```ts tenantId: getTenantPathParam_tenantId; ``` --- ## Type Alias: getTopologyInput ```ts type getTopologyInput = void; ``` --- ## Type Alias: getUsageMetricsConsistency ```ts type getUsageMetricsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getUsageMetricsInput ```ts type getUsageMetricsInput = object; ``` ## Properties ### endTime ```ts endTime: getUsageMetricsQueryParam_endTime; ``` --- ### startTime ```ts startTime: getUsageMetricsQueryParam_startTime; ``` --- ### tenantId? ```ts optional tenantId?: getUsageMetricsQueryParam_tenantId; ``` --- ### withTenants? ```ts optional withTenants?: getUsageMetricsQueryParam_withTenants; ``` --- ## Type Alias: getUserConsistency ```ts type getUserConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getUserInput ```ts type getUserInput = object; ``` ## Properties ### username ```ts username: getUserPathParam_username; ``` --- ## Type Alias: getUserTaskConsistency ```ts type getUserTaskConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getUserTaskFormConsistency ```ts type getUserTaskFormConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getUserTaskFormInput ```ts type getUserTaskFormInput = object; ``` ## Properties ### userTaskKey ```ts userTaskKey: getUserTaskFormPathParam_userTaskKey; ``` --- ## Type Alias: getUserTaskInput ```ts type getUserTaskInput = object; ``` ## Properties ### userTaskKey ```ts userTaskKey: getUserTaskPathParam_userTaskKey; ``` --- ## Type Alias: getVariableConsistency ```ts type getVariableConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: getVariableInput ```ts type getVariableInput = object; ``` ## Properties ### variableKey ```ts variableKey: getVariablePathParam_variableKey; ``` --- ## Type Alias: listHistoryBackupsAsClusterAdminInput ```ts type listHistoryBackupsAsClusterAdminInput = object; ``` ## Properties ### physicalTenantId? ```ts optional physicalTenantId?: listHistoryBackupsAsClusterAdminQueryParam_physicalTenantId; ``` --- ### prefix? ```ts optional prefix?: listHistoryBackupsAsClusterAdminQueryParam_prefix; ``` --- ### verbose? ```ts optional verbose?: listHistoryBackupsAsClusterAdminQueryParam_verbose; ``` --- ## Type Alias: listHistoryBackupsInput ```ts type listHistoryBackupsInput = object; ``` ## Properties ### prefix? ```ts optional prefix?: listHistoryBackupsQueryParam_prefix; ``` --- ### verbose? ```ts optional verbose?: listHistoryBackupsQueryParam_verbose; ``` --- ## Type Alias: listRuntimeBackupsAsClusterAdminInput ```ts type listRuntimeBackupsAsClusterAdminInput = object; ``` ## Properties ### physicalTenantId? ```ts optional physicalTenantId?: listRuntimeBackupsAsClusterAdminQueryParam_physicalTenantId; ``` --- ### prefix? ```ts optional prefix?: listRuntimeBackupsAsClusterAdminQueryParam_prefix; ``` --- ## Type Alias: listRuntimeBackupsInput ```ts type listRuntimeBackupsInput = object; ``` ## Properties ### prefix? ```ts optional prefix?: listRuntimeBackupsQueryParam_prefix; ``` --- ## Type Alias: listSecretsInput ```ts type listSecretsInput = listSecretsBody; ``` --- ## Type Alias: migrateProcessInstanceInput ```ts type migrateProcessInstanceInput = migrateProcessInstanceBody & object; ``` ## Type Declaration ### processInstanceKey ```ts processInstanceKey: migrateProcessInstancePathParam_processInstanceKey; ``` --- ## Type Alias: migrateProcessInstancesBatchOperationInput ```ts type migrateProcessInstancesBatchOperationInput = migrateProcessInstancesBatchOperationBody; ``` --- ## Type Alias: modifyProcessInstanceInput ```ts type modifyProcessInstanceInput = modifyProcessInstanceBody & object; ``` ## Type Declaration ### processInstanceKey ```ts processInstanceKey: modifyProcessInstancePathParam_processInstanceKey; ``` --- ## Type Alias: modifyProcessInstancesBatchOperationInput ```ts type modifyProcessInstancesBatchOperationInput = modifyProcessInstancesBatchOperationBody; ``` --- ## Type Alias: pauseClusterExportingInput ```ts type pauseClusterExportingInput = object; ``` ## Properties ### soft? ```ts optional soft?: pauseClusterExportingQueryParam_soft; ``` --- ## Type Alias: pauseExportingInput ```ts type pauseExportingInput = object; ``` ## Properties ### soft? ```ts optional soft?: pauseExportingQueryParam_soft; ``` --- ## Type Alias: pinClockInput ```ts type pinClockInput = pinClockBody; ``` --- ## Type Alias: publishMessageInput ```ts type publishMessageInput = publishMessageBody; ``` --- ## Type Alias: resetClockInput ```ts type resetClockInput = void; ``` --- ## Type Alias: resolveIncidentInput ```ts type resolveIncidentInput = resolveIncidentBody & object; ``` ## Type Declaration ### incidentKey ```ts incidentKey: resolveIncidentPathParam_incidentKey; ``` --- ## Type Alias: resolveIncidentsBatchOperationInput ```ts type resolveIncidentsBatchOperationInput = resolveIncidentsBatchOperationBody; ``` --- ## Type Alias: resolveProcessInstanceIncidentsInput ```ts type resolveProcessInstanceIncidentsInput = object; ``` ## Properties ### processInstanceKey ```ts processInstanceKey: resolveProcessInstanceIncidentsPathParam_processInstanceKey; ``` --- ## Type Alias: resolveSecretsInput ```ts type resolveSecretsInput = resolveSecretsBody; ``` --- ## Type Alias: restoreAsClusterAdminInput ```ts type restoreAsClusterAdminInput = restoreAsClusterAdminBody & object; ``` ## Type Declaration ### dryRun? ```ts optional dryRun?: restoreAsClusterAdminQueryParam_dryRun; ``` ### physicalTenantId? ```ts optional physicalTenantId?: restoreAsClusterAdminQueryParam_physicalTenantId; ``` --- ## Type Alias: restoreInput ```ts type restoreInput = restoreBody & object; ``` ## Type Declaration ### dryRun? ```ts optional dryRun?: restoreQueryParam_dryRun; ``` --- ## Type Alias: resumeBatchOperationInput ```ts type resumeBatchOperationInput = resumeBatchOperationBody & object; ``` ## Type Declaration ### batchOperationKey ```ts batchOperationKey: resumeBatchOperationPathParam_batchOperationKey; ``` --- ## Type Alias: resumeClusterExportingInput ```ts type resumeClusterExportingInput = void; ``` --- ## Type Alias: resumeExportingInput ```ts type resumeExportingInput = void; ``` --- ## Type Alias: resumeProcessInstanceInput ```ts type resumeProcessInstanceInput = resumeProcessInstanceBody & object; ``` ## Type Declaration ### processInstanceKey ```ts processInstanceKey: resumeProcessInstancePathParam_processInstanceKey; ``` --- ## Type Alias: resumeProcessInstancesBatchOperationInput ```ts type resumeProcessInstancesBatchOperationInput = resumeProcessInstancesBatchOperationBody; ``` --- ## Type Alias: searchAgentDefinitionsConsistency ```ts type searchAgentDefinitionsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchAgentDefinitionsInput ```ts type searchAgentDefinitionsInput = searchAgentDefinitionsBody; ``` --- ## Type Alias: searchAgentInstanceHistoryConsistency ```ts type searchAgentInstanceHistoryConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchAgentInstanceHistoryInput ```ts type searchAgentInstanceHistoryInput = searchAgentInstanceHistoryBody & object; ``` ## Type Declaration ### agentInstanceKey ```ts agentInstanceKey: searchAgentInstanceHistoryPathParam_agentInstanceKey; ``` --- ## Type Alias: searchAgentInstancesConsistency ```ts type searchAgentInstancesConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchAgentInstancesInput ```ts type searchAgentInstancesInput = searchAgentInstancesBody; ``` --- ## Type Alias: searchAuditLogsConsistency ```ts type searchAuditLogsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchAuditLogsInput ```ts type searchAuditLogsInput = searchAuditLogsBody; ``` --- ## Type Alias: searchAuthorizationsConsistency ```ts type searchAuthorizationsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchAuthorizationsInput ```ts type searchAuthorizationsInput = searchAuthorizationsBody; ``` --- ## Type Alias: searchBatchOperationItemsConsistency ```ts type searchBatchOperationItemsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchBatchOperationItemsInput ```ts type searchBatchOperationItemsInput = searchBatchOperationItemsBody; ``` --- ## Type Alias: searchBatchOperationsConsistency ```ts type searchBatchOperationsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchBatchOperationsInput ```ts type searchBatchOperationsInput = searchBatchOperationsBody; ``` --- ## Type Alias: searchClientsForGroupConsistency ```ts type searchClientsForGroupConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchClientsForGroupInput ```ts type searchClientsForGroupInput = searchClientsForGroupBody & object; ``` ## Type Declaration ### groupId ```ts groupId: searchClientsForGroupPathParam_groupId; ``` --- ## Type Alias: searchClientsForRoleConsistency ```ts type searchClientsForRoleConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchClientsForRoleInput ```ts type searchClientsForRoleInput = searchClientsForRoleBody & object; ``` ## Type Declaration ### roleId ```ts roleId: searchClientsForRolePathParam_roleId; ``` --- ## Type Alias: searchClientsForTenantConsistency ```ts type searchClientsForTenantConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchClientsForTenantInput ```ts type searchClientsForTenantInput = searchClientsForTenantBody & object; ``` ## Type Declaration ### tenantId ```ts tenantId: searchClientsForTenantPathParam_tenantId; ``` --- ## Type Alias: searchClusterVariablesConsistency ```ts type searchClusterVariablesConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchClusterVariablesInput ```ts type searchClusterVariablesInput = searchClusterVariablesBody & object; ``` ## Type Declaration ### truncateValues? ```ts optional truncateValues?: searchClusterVariablesQueryParam_truncateValues; ``` --- ## Type Alias: searchCorrelatedMessageSubscriptionsConsistency ```ts type searchCorrelatedMessageSubscriptionsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions< _DataOf >; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchCorrelatedMessageSubscriptionsInput ```ts type searchCorrelatedMessageSubscriptionsInput = searchCorrelatedMessageSubscriptionsBody; ``` --- ## Type Alias: searchDecisionDefinitionsConsistency ```ts type searchDecisionDefinitionsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchDecisionDefinitionsInput ```ts type searchDecisionDefinitionsInput = searchDecisionDefinitionsBody; ``` --- ## Type Alias: searchDecisionInstancesConsistency ```ts type searchDecisionInstancesConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchDecisionInstancesInput ```ts type searchDecisionInstancesInput = searchDecisionInstancesBody; ``` --- ## Type Alias: searchDecisionRequirementsConsistency ```ts type searchDecisionRequirementsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchDecisionRequirementsInput ```ts type searchDecisionRequirementsInput = searchDecisionRequirementsBody; ``` --- ## Type Alias: searchElementInstanceIncidentsConsistency ```ts type searchElementInstanceIncidentsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions< _DataOf >; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchElementInstanceIncidentsInput ```ts type searchElementInstanceIncidentsInput = searchElementInstanceIncidentsBody & object; ``` ## Type Declaration ### elementInstanceKey ```ts elementInstanceKey: searchElementInstanceIncidentsPathParam_elementInstanceKey; ``` --- ## Type Alias: searchElementInstanceWaitStatesConsistency ```ts type searchElementInstanceWaitStatesConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions< _DataOf >; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchElementInstanceWaitStatesInput ```ts type searchElementInstanceWaitStatesInput = searchElementInstanceWaitStatesBody; ``` --- ## Type Alias: searchElementInstancesConsistency ```ts type searchElementInstancesConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchElementInstancesInput ```ts type searchElementInstancesInput = searchElementInstancesBody; ``` --- ## Type Alias: searchGlobalTaskListenersConsistency ```ts type searchGlobalTaskListenersConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchGlobalTaskListenersInput ```ts type searchGlobalTaskListenersInput = searchGlobalTaskListenersBody; ``` --- ## Type Alias: searchGroupIdsForTenantConsistency ```ts type searchGroupIdsForTenantConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchGroupIdsForTenantInput ```ts type searchGroupIdsForTenantInput = searchGroupIdsForTenantBody & object; ``` ## Type Declaration ### tenantId ```ts tenantId: searchGroupIdsForTenantPathParam_tenantId; ``` --- ## Type Alias: searchGroupsConsistency ```ts type searchGroupsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchGroupsForRoleConsistency ```ts type searchGroupsForRoleConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchGroupsForRoleInput ```ts type searchGroupsForRoleInput = searchGroupsForRoleBody & object; ``` ## Type Declaration ### roleId ```ts roleId: searchGroupsForRolePathParam_roleId; ``` --- ## Type Alias: searchGroupsInput ```ts type searchGroupsInput = searchGroupsBody; ``` --- ## Type Alias: searchIncidentsConsistency ```ts type searchIncidentsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchIncidentsInput ```ts type searchIncidentsInput = searchIncidentsBody; ``` --- ## Type Alias: searchJobsConsistency ```ts type searchJobsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchJobsInput ```ts type searchJobsInput = searchJobsBody; ``` --- ## Type Alias: searchMappingRuleConsistency ```ts type searchMappingRuleConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchMappingRuleInput ```ts type searchMappingRuleInput = searchMappingRuleBody; ``` --- ## Type Alias: searchMappingRulesForGroupConsistency ```ts type searchMappingRulesForGroupConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchMappingRulesForGroupInput ```ts type searchMappingRulesForGroupInput = searchMappingRulesForGroupBody & object; ``` ## Type Declaration ### groupId ```ts groupId: searchMappingRulesForGroupPathParam_groupId; ``` --- ## Type Alias: searchMappingRulesForRoleConsistency ```ts type searchMappingRulesForRoleConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchMappingRulesForRoleInput ```ts type searchMappingRulesForRoleInput = searchMappingRulesForRoleBody & object; ``` ## Type Declaration ### roleId ```ts roleId: searchMappingRulesForRolePathParam_roleId; ``` --- ## Type Alias: searchMappingRulesForTenantConsistency ```ts type searchMappingRulesForTenantConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions< _DataOf >; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchMappingRulesForTenantInput ```ts type searchMappingRulesForTenantInput = searchMappingRulesForTenantBody & object; ``` ## Type Declaration ### tenantId ```ts tenantId: searchMappingRulesForTenantPathParam_tenantId; ``` --- ## Type Alias: searchMessageSubscriptionsConsistency ```ts type searchMessageSubscriptionsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchMessageSubscriptionsInput ```ts type searchMessageSubscriptionsInput = searchMessageSubscriptionsBody; ``` --- ## Type Alias: searchOwnAuthorizationsConsistency ```ts type searchOwnAuthorizationsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchOwnAuthorizationsInput ```ts type searchOwnAuthorizationsInput = searchOwnAuthorizationsBody; ``` --- ## Type Alias: searchProcessDefinitionVariableNamesConsistency ```ts type searchProcessDefinitionVariableNamesConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions< _DataOf >; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchProcessDefinitionVariableNamesInput ```ts type searchProcessDefinitionVariableNamesInput = searchProcessDefinitionVariableNamesBody & object; ``` ## Type Declaration ### processDefinitionKey ```ts processDefinitionKey: searchProcessDefinitionVariableNamesPathParam_processDefinitionKey; ``` --- ## Type Alias: searchProcessDefinitionsConsistency ```ts type searchProcessDefinitionsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchProcessDefinitionsInput ```ts type searchProcessDefinitionsInput = searchProcessDefinitionsBody; ``` --- ## Type Alias: searchProcessInstanceIncidentsConsistency ```ts type searchProcessInstanceIncidentsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions< _DataOf >; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchProcessInstanceIncidentsInput ```ts type searchProcessInstanceIncidentsInput = searchProcessInstanceIncidentsBody & object; ``` ## Type Declaration ### processInstanceKey ```ts processInstanceKey: searchProcessInstanceIncidentsPathParam_processInstanceKey; ``` --- ## Type Alias: searchProcessInstancesConsistency ```ts type searchProcessInstancesConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchProcessInstancesInput ```ts type searchProcessInstancesInput = searchProcessInstancesBody; ``` --- ## Type Alias: searchResourcesConsistency ```ts type searchResourcesConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchResourcesInput ```ts type searchResourcesInput = searchResourcesBody; ``` --- ## Type Alias: searchRolesConsistency ```ts type searchRolesConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchRolesForGroupConsistency ```ts type searchRolesForGroupConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchRolesForGroupInput ```ts type searchRolesForGroupInput = searchRolesForGroupBody & object; ``` ## Type Declaration ### groupId ```ts groupId: searchRolesForGroupPathParam_groupId; ``` --- ## Type Alias: searchRolesForTenantConsistency ```ts type searchRolesForTenantConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchRolesForTenantInput ```ts type searchRolesForTenantInput = searchRolesForTenantBody & object; ``` ## Type Declaration ### tenantId ```ts tenantId: searchRolesForTenantPathParam_tenantId; ``` --- ## Type Alias: searchRolesInput ```ts type searchRolesInput = searchRolesBody; ``` --- ## Type Alias: searchTenantsConsistency ```ts type searchTenantsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchTenantsInput ```ts type searchTenantsInput = searchTenantsBody; ``` --- ## Type Alias: searchUserTaskAuditLogsConsistency ```ts type searchUserTaskAuditLogsConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchUserTaskAuditLogsInput ```ts type searchUserTaskAuditLogsInput = searchUserTaskAuditLogsBody & object; ``` ## Type Declaration ### userTaskKey ```ts userTaskKey: searchUserTaskAuditLogsPathParam_userTaskKey; ``` --- ## Type Alias: searchUserTaskEffectiveVariablesConsistency ```ts type searchUserTaskEffectiveVariablesConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions< _DataOf >; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchUserTaskEffectiveVariablesInput ```ts type searchUserTaskEffectiveVariablesInput = searchUserTaskEffectiveVariablesBody & object; ``` ## Type Declaration ### truncateValues? ```ts optional truncateValues?: searchUserTaskEffectiveVariablesQueryParam_truncateValues; ``` ### userTaskKey ```ts userTaskKey: searchUserTaskEffectiveVariablesPathParam_userTaskKey; ``` --- ## Type Alias: searchUserTaskVariablesConsistency ```ts type searchUserTaskVariablesConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchUserTaskVariablesInput ```ts type searchUserTaskVariablesInput = searchUserTaskVariablesBody & object; ``` ## Type Declaration ### truncateValues? ```ts optional truncateValues?: searchUserTaskVariablesQueryParam_truncateValues; ``` ### userTaskKey ```ts userTaskKey: searchUserTaskVariablesPathParam_userTaskKey; ``` --- ## Type Alias: searchUserTasksConsistency ```ts type searchUserTasksConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchUserTasksInput ```ts type searchUserTasksInput = searchUserTasksBody; ``` --- ## Type Alias: searchUsersConsistency ```ts type searchUsersConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchUsersForGroupConsistency ```ts type searchUsersForGroupConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchUsersForGroupInput ```ts type searchUsersForGroupInput = searchUsersForGroupBody & object; ``` ## Type Declaration ### groupId ```ts groupId: searchUsersForGroupPathParam_groupId; ``` --- ## Type Alias: searchUsersForRoleConsistency ```ts type searchUsersForRoleConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchUsersForRoleInput ```ts type searchUsersForRoleInput = searchUsersForRoleBody & object; ``` ## Type Declaration ### roleId ```ts roleId: searchUsersForRolePathParam_roleId; ``` --- ## Type Alias: searchUsersForTenantConsistency ```ts type searchUsersForTenantConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchUsersForTenantInput ```ts type searchUsersForTenantInput = searchUsersForTenantBody & object; ``` ## Type Declaration ### tenantId ```ts tenantId: searchUsersForTenantPathParam_tenantId; ``` --- ## Type Alias: searchUsersInput ```ts type searchUsersInput = searchUsersBody; ``` --- ## Type Alias: searchVariablesConsistency ```ts type searchVariablesConsistency = object; ``` Management of eventual consistency * ## Properties ### consistency ```ts consistency: ConsistencyOptions<_DataOf>; ``` Management of eventual consistency tolerance. Set waitUpToMs to 0 to ignore eventual consistency. pollInterval is 500ms by default. --- ## Type Alias: searchVariablesInput ```ts type searchVariablesInput = searchVariablesBody & object; ``` ## Type Declaration ### truncateValues? ```ts optional truncateValues?: searchVariablesQueryParam_truncateValues; ``` --- ## Type Alias: suspendBatchOperationInput ```ts type suspendBatchOperationInput = suspendBatchOperationBody & object; ``` ## Type Declaration ### batchOperationKey ```ts batchOperationKey: suspendBatchOperationPathParam_batchOperationKey; ``` --- ## Type Alias: suspendProcessInstanceInput ```ts type suspendProcessInstanceInput = suspendProcessInstanceBody & object; ``` ## Type Declaration ### processInstanceKey ```ts processInstanceKey: suspendProcessInstancePathParam_processInstanceKey; ``` --- ## Type Alias: suspendProcessInstancesBatchOperationInput ```ts type suspendProcessInstancesBatchOperationInput = suspendProcessInstancesBatchOperationBody; ``` --- ## Type Alias: syncRuntimeBackupStateAsClusterAdminInput ```ts type syncRuntimeBackupStateAsClusterAdminInput = object; ``` ## Properties ### physicalTenantId? ```ts optional physicalTenantId?: syncRuntimeBackupStateAsClusterAdminQueryParam_physicalTenantId; ``` --- ## Type Alias: syncRuntimeBackupStateInput ```ts type syncRuntimeBackupStateInput = void; ``` --- ## Type Alias: takeHistoryBackupAsClusterAdminInput ```ts type takeHistoryBackupAsClusterAdminInput = takeHistoryBackupAsClusterAdminBody & object; ``` ## Type Declaration ### physicalTenantId? ```ts optional physicalTenantId?: takeHistoryBackupAsClusterAdminQueryParam_physicalTenantId; ``` --- ## Type Alias: takeHistoryBackupInput ```ts type takeHistoryBackupInput = takeHistoryBackupBody; ``` --- ## Type Alias: takeRuntimeBackupAsClusterAdminInput ```ts type takeRuntimeBackupAsClusterAdminInput = takeRuntimeBackupAsClusterAdminBody & object; ``` ## Type Declaration ### physicalTenantId? ```ts optional physicalTenantId?: takeRuntimeBackupAsClusterAdminQueryParam_physicalTenantId; ``` --- ## Type Alias: takeRuntimeBackupInput ```ts type takeRuntimeBackupInput = takeRuntimeBackupBody; ``` --- ## Type Alias: throwJobErrorInput ```ts type throwJobErrorInput = throwJobErrorBody & object; ``` ## Type Declaration ### jobKey ```ts jobKey: throwJobErrorPathParam_jobKey; ``` --- ## Type Alias: triggerClusterRebalanceInput ```ts type triggerClusterRebalanceInput = triggerClusterRebalanceBody & object; ``` ## Type Declaration ### dryRun? ```ts optional dryRun?: triggerClusterRebalanceQueryParam_dryRun; ``` --- ## Type Alias: unassignClientFromGroupInput ```ts type unassignClientFromGroupInput = object; ``` ## Properties ### clientId ```ts clientId: unassignClientFromGroupPathParam_clientId; ``` --- ### groupId ```ts groupId: unassignClientFromGroupPathParam_groupId; ``` --- ## Type Alias: unassignClientFromTenantInput ```ts type unassignClientFromTenantInput = object; ``` ## Properties ### clientId ```ts clientId: unassignClientFromTenantPathParam_clientId; ``` --- ### tenantId ```ts tenantId: unassignClientFromTenantPathParam_tenantId; ``` --- ## Type Alias: unassignGroupFromTenantInput ```ts type unassignGroupFromTenantInput = object; ``` ## Properties ### groupId ```ts groupId: unassignGroupFromTenantPathParam_groupId; ``` --- ### tenantId ```ts tenantId: unassignGroupFromTenantPathParam_tenantId; ``` --- ## Type Alias: unassignMappingRuleFromGroupInput ```ts type unassignMappingRuleFromGroupInput = object; ``` ## Properties ### groupId ```ts groupId: unassignMappingRuleFromGroupPathParam_groupId; ``` --- ### mappingRuleId ```ts mappingRuleId: unassignMappingRuleFromGroupPathParam_mappingRuleId; ``` --- ## Type Alias: unassignMappingRuleFromTenantInput ```ts type unassignMappingRuleFromTenantInput = object; ``` ## Properties ### mappingRuleId ```ts mappingRuleId: unassignMappingRuleFromTenantPathParam_mappingRuleId; ``` --- ### tenantId ```ts tenantId: unassignMappingRuleFromTenantPathParam_tenantId; ``` --- ## Type Alias: unassignRoleFromClientInput ```ts type unassignRoleFromClientInput = object; ``` ## Properties ### clientId ```ts clientId: unassignRoleFromClientPathParam_clientId; ``` --- ### roleId ```ts roleId: unassignRoleFromClientPathParam_roleId; ``` --- ## Type Alias: unassignRoleFromGroupInput ```ts type unassignRoleFromGroupInput = object; ``` ## Properties ### groupId ```ts groupId: unassignRoleFromGroupPathParam_groupId; ``` --- ### roleId ```ts roleId: unassignRoleFromGroupPathParam_roleId; ``` --- ## Type Alias: unassignRoleFromMappingRuleInput ```ts type unassignRoleFromMappingRuleInput = object; ``` ## Properties ### mappingRuleId ```ts mappingRuleId: unassignRoleFromMappingRulePathParam_mappingRuleId; ``` --- ### roleId ```ts roleId: unassignRoleFromMappingRulePathParam_roleId; ``` --- ## Type Alias: unassignRoleFromTenantInput ```ts type unassignRoleFromTenantInput = object; ``` ## Properties ### roleId ```ts roleId: unassignRoleFromTenantPathParam_roleId; ``` --- ### tenantId ```ts tenantId: unassignRoleFromTenantPathParam_tenantId; ``` --- ## Type Alias: unassignRoleFromUserInput ```ts type unassignRoleFromUserInput = object; ``` ## Properties ### roleId ```ts roleId: unassignRoleFromUserPathParam_roleId; ``` --- ### username ```ts username: unassignRoleFromUserPathParam_username; ``` --- ## Type Alias: unassignUserFromGroupInput ```ts type unassignUserFromGroupInput = object; ``` ## Properties ### groupId ```ts groupId: unassignUserFromGroupPathParam_groupId; ``` --- ### username ```ts username: unassignUserFromGroupPathParam_username; ``` --- ## Type Alias: unassignUserFromTenantInput ```ts type unassignUserFromTenantInput = object; ``` ## Properties ### tenantId ```ts tenantId: unassignUserFromTenantPathParam_tenantId; ``` --- ### username ```ts username: unassignUserFromTenantPathParam_username; ``` --- ## Type Alias: unassignUserTaskInput ```ts type unassignUserTaskInput = object; ``` ## Properties ### userTaskKey ```ts userTaskKey: unassignUserTaskPathParam_userTaskKey; ``` --- ## Type Alias: updateAgentInstanceInput ```ts type updateAgentInstanceInput = updateAgentInstanceBody & object; ``` ## Type Declaration ### agentInstanceKey ```ts agentInstanceKey: updateAgentInstancePathParam_agentInstanceKey; ``` --- ## Type Alias: updateAuthorizationInput ```ts type updateAuthorizationInput = updateAuthorizationBody & object; ``` ## Type Declaration ### authorizationKey ```ts authorizationKey: updateAuthorizationPathParam_authorizationKey; ``` --- ## Type Alias: updateGlobalClusterVariableInput ```ts type updateGlobalClusterVariableInput = updateGlobalClusterVariableBody & object; ``` ## Type Declaration ### name ```ts name: updateGlobalClusterVariablePathParam_name; ``` --- ## Type Alias: updateGlobalTaskListenerInput ```ts type updateGlobalTaskListenerInput = updateGlobalTaskListenerBody & object; ``` ## Type Declaration ### id ```ts id: updateGlobalTaskListenerPathParam_id; ``` --- ## Type Alias: updateGroupInput ```ts type updateGroupInput = updateGroupBody & object; ``` ## Type Declaration ### groupId ```ts groupId: updateGroupPathParam_groupId; ``` --- ## Type Alias: updateJobInput ```ts type updateJobInput = updateJobBody & object; ``` ## Type Declaration ### jobKey ```ts jobKey: updateJobPathParam_jobKey; ``` --- ## Type Alias: updateJobsBatchOperationInput ```ts type updateJobsBatchOperationInput = updateJobsBatchOperationBody; ``` --- ## Type Alias: updateMappingRuleInput ```ts type updateMappingRuleInput = updateMappingRuleBody & object; ``` ## Type Declaration ### mappingRuleId ```ts mappingRuleId: updateMappingRulePathParam_mappingRuleId; ``` --- ## Type Alias: updateRoleInput ```ts type updateRoleInput = updateRoleBody & object; ``` ## Type Declaration ### roleId ```ts roleId: updateRolePathParam_roleId; ``` --- ## Type Alias: updateTenantClusterVariableInput ```ts type updateTenantClusterVariableInput = updateTenantClusterVariableBody & object; ``` ## Type Declaration ### name ```ts name: updateTenantClusterVariablePathParam_name; ``` ### tenantId ```ts tenantId: updateTenantClusterVariablePathParam_tenantId; ``` --- ## Type Alias: updateTenantInput ```ts type updateTenantInput = updateTenantBody & object; ``` ## Type Declaration ### tenantId ```ts tenantId: updateTenantPathParam_tenantId; ``` --- ## Type Alias: updateUserInput ```ts type updateUserInput = updateUserBody & object; ``` ## Type Declaration ### username ```ts username: updateUserPathParam_username; ``` --- ## Type Alias: updateUserTaskInput ```ts type updateUserTaskInput = updateUserTaskBody & object; ``` ## Type Declaration ### userTaskKey ```ts userTaskKey: updateUserTaskPathParam_userTaskKey; ``` --- ## Variable: AgentDefinitionKey ```ts AgentDefinitionKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): AgentDefinitionKey; ``` #### Parameters ##### value `string` #### Returns [`AgentDefinitionKey`](../type-aliases/AgentDefinitionKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`AgentDefinitionKey`](../type-aliases/AgentDefinitionKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: AgentDefinitionTypeEnum ```ts const AgentDefinitionTypeEnum: object; ``` The kind of agent an agent definition describes. ## Type Declaration ### AI\_AGENT\_SUB\_PROCESS ```ts readonly AI_AGENT_SUB_PROCESS: "AI_AGENT_SUB_PROCESS" = 'AI_AGENT_SUB_PROCESS'; ``` ### AI\_AGENT\_TASK ```ts readonly AI_AGENT_TASK: "AI_AGENT_TASK" = 'AI_AGENT_TASK'; ``` ### EXTERNAL\_AGENT ```ts readonly EXTERNAL_AGENT: "EXTERNAL_AGENT" = 'EXTERNAL_AGENT'; ``` --- ## Variable: AgentHistoryItemKey ```ts AgentHistoryItemKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): AgentHistoryItemKey; ``` #### Parameters ##### value `string` #### Returns [`AgentHistoryItemKey`](../type-aliases/AgentHistoryItemKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`AgentHistoryItemKey`](../type-aliases/AgentHistoryItemKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: AgentInstanceHistoryCommitStatusEnum ```ts const AgentInstanceHistoryCommitStatusEnum: object; ``` The commit status of a history item. COMMITTED: the producing job completed successfully. PENDING: the producing job is still active (in-flight). DISCARDED: the producing job failed; this item was superseded by a later activation. ## Type Declaration ### COMMITTED ```ts readonly COMMITTED: "COMMITTED" = 'COMMITTED'; ``` ### DISCARDED ```ts readonly DISCARDED: "DISCARDED" = 'DISCARDED'; ``` ### PENDING ```ts readonly PENDING: "PENDING" = 'PENDING'; ``` --- ## Variable: AgentInstanceHistoryRoleEnum ```ts const AgentInstanceHistoryRoleEnum: object; ``` The role of a history item in the agent conversation. ## Type Declaration ### ASSISTANT ```ts readonly ASSISTANT: "ASSISTANT" = 'ASSISTANT'; ``` ### CONFIGURATION ```ts readonly CONFIGURATION: "CONFIGURATION" = 'CONFIGURATION'; ``` ### TOOL\_RESULT ```ts readonly TOOL_RESULT: "TOOL_RESULT" = 'TOOL_RESULT'; ``` ### USER ```ts readonly USER: "USER" = 'USER'; ``` --- ## Variable: AgentInstanceKey ```ts AgentInstanceKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): AgentInstanceKey; ``` #### Parameters ##### value `string` #### Returns [`AgentInstanceKey`](../type-aliases/AgentInstanceKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`AgentInstanceKey`](../type-aliases/AgentInstanceKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: AgentInstanceMessageContentTypeEnum ```ts const AgentInstanceMessageContentTypeEnum: object; ``` The content type discriminator for a history item content block. ## Type Declaration ### DOCUMENT ```ts readonly DOCUMENT: "DOCUMENT" = 'DOCUMENT'; ``` ### OBJECT ```ts readonly OBJECT: "OBJECT" = 'OBJECT'; ``` ### TEXT ```ts readonly TEXT: "TEXT" = 'TEXT'; ``` --- ## Variable: AgentInstanceStatusEnum ```ts const AgentInstanceStatusEnum: object; ``` The current status of an agent instance. ## Type Declaration ### COMPLETED ```ts readonly COMPLETED: "COMPLETED" = 'COMPLETED'; ``` ### IDLE ```ts readonly IDLE: "IDLE" = 'IDLE'; ``` ### INITIALIZING ```ts readonly INITIALIZING: "INITIALIZING" = 'INITIALIZING'; ``` ### THINKING ```ts readonly THINKING: "THINKING" = 'THINKING'; ``` ### TOOL\_CALLING ```ts readonly TOOL_CALLING: "TOOL_CALLING" = 'TOOL_CALLING'; ``` ### TOOL\_DISCOVERY ```ts readonly TOOL_DISCOVERY: "TOOL_DISCOVERY" = 'TOOL_DISCOVERY'; ``` ### UNKNOWN ```ts readonly UNKNOWN: "UNKNOWN" = 'UNKNOWN'; ``` --- ## Variable: AgentInstanceUpdateStatusEnum ```ts const AgentInstanceUpdateStatusEnum: object; ``` The status values that can be set on an agent instance via an update request. ## Type Declaration ### IDLE ```ts readonly IDLE: "IDLE" = 'IDLE'; ``` ### THINKING ```ts readonly THINKING: "THINKING" = 'THINKING'; ``` ### TOOL\_CALLING ```ts readonly TOOL_CALLING: "TOOL_CALLING" = 'TOOL_CALLING'; ``` ### TOOL\_DISCOVERY ```ts readonly TOOL_DISCOVERY: "TOOL_DISCOVERY" = 'TOOL_DISCOVERY'; ``` --- ## Variable: AuditLogActorTypeEnum ```ts const AuditLogActorTypeEnum: object; ``` The type of actor who performed the operation. ## Type Declaration ### ANONYMOUS ```ts readonly ANONYMOUS: "ANONYMOUS" = 'ANONYMOUS'; ``` ### CLIENT ```ts readonly CLIENT: "CLIENT" = 'CLIENT'; ``` ### UNKNOWN ```ts readonly UNKNOWN: "UNKNOWN" = 'UNKNOWN'; ``` ### USER ```ts readonly USER: "USER" = 'USER'; ``` --- ## Variable: AuditLogCategoryEnum ```ts const AuditLogCategoryEnum: object; ``` The category of the audit log operation. ## Type Declaration ### ADMIN ```ts readonly ADMIN: "ADMIN" = 'ADMIN'; ``` ### DEPLOYED\_RESOURCES ```ts readonly DEPLOYED_RESOURCES: "DEPLOYED_RESOURCES" = 'DEPLOYED_RESOURCES'; ``` ### USER\_TASKS ```ts readonly USER_TASKS: "USER_TASKS" = 'USER_TASKS'; ``` --- ## Variable: AuditLogEntityKey ```ts AuditLogEntityKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): AuditLogEntityKey; ``` #### Parameters ##### value `string` #### Returns [`AuditLogEntityKey`](../type-aliases/AuditLogEntityKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`AuditLogEntityKey`](../type-aliases/AuditLogEntityKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: AuditLogEntityTypeEnum ```ts const AuditLogEntityTypeEnum: object; ``` The type of entity affected by the operation. ## Type Declaration ### AUTHORIZATION ```ts readonly AUTHORIZATION: "AUTHORIZATION" = 'AUTHORIZATION'; ``` ### BATCH ```ts readonly BATCH: "BATCH" = 'BATCH'; ``` ### CLIENT ```ts readonly CLIENT: "CLIENT" = 'CLIENT'; ``` ### DECISION ```ts readonly DECISION: "DECISION" = 'DECISION'; ``` ### GROUP ```ts readonly GROUP: "GROUP" = 'GROUP'; ``` ### INCIDENT ```ts readonly INCIDENT: "INCIDENT" = 'INCIDENT'; ``` ### JOB ```ts readonly JOB: "JOB" = 'JOB'; ``` ### MAPPING\_RULE ```ts readonly MAPPING_RULE: "MAPPING_RULE" = 'MAPPING_RULE'; ``` ### PROCESS\_INSTANCE ```ts readonly PROCESS_INSTANCE: "PROCESS_INSTANCE" = 'PROCESS_INSTANCE'; ``` ### RESOURCE ```ts readonly RESOURCE: "RESOURCE" = 'RESOURCE'; ``` ### ROLE ```ts readonly ROLE: "ROLE" = 'ROLE'; ``` ### TENANT ```ts readonly TENANT: "TENANT" = 'TENANT'; ``` ### USER ```ts readonly USER: "USER" = 'USER'; ``` ### USER\_TASK ```ts readonly USER_TASK: "USER_TASK" = 'USER_TASK'; ``` ### VARIABLE ```ts readonly VARIABLE: "VARIABLE" = 'VARIABLE'; ``` --- ## Variable: AuditLogKey ```ts AuditLogKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): AuditLogKey; ``` #### Parameters ##### value `string` #### Returns [`AuditLogKey`](../type-aliases/AuditLogKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`AuditLogKey`](../type-aliases/AuditLogKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: AuditLogOperationTypeEnum ```ts const AuditLogOperationTypeEnum: object; ``` The type of operation performed. ## Type Declaration ### ASSIGN ```ts readonly ASSIGN: "ASSIGN" = 'ASSIGN'; ``` ### CANCEL ```ts readonly CANCEL: "CANCEL" = 'CANCEL'; ``` ### COMPLETE ```ts readonly COMPLETE: "COMPLETE" = 'COMPLETE'; ``` ### CREATE ```ts readonly CREATE: "CREATE" = 'CREATE'; ``` ### DELETE ```ts readonly DELETE: "DELETE" = 'DELETE'; ``` ### EVALUATE ```ts readonly EVALUATE: "EVALUATE" = 'EVALUATE'; ``` ### MIGRATE ```ts readonly MIGRATE: "MIGRATE" = 'MIGRATE'; ``` ### MODIFY ```ts readonly MODIFY: "MODIFY" = 'MODIFY'; ``` ### RESOLVE ```ts readonly RESOLVE: "RESOLVE" = 'RESOLVE'; ``` ### RESUME ```ts readonly RESUME: "RESUME" = 'RESUME'; ``` ### SUSPEND ```ts readonly SUSPEND: "SUSPEND" = 'SUSPEND'; ``` ### UNASSIGN ```ts readonly UNASSIGN: "UNASSIGN" = 'UNASSIGN'; ``` ### UNKNOWN ```ts readonly UNKNOWN: "UNKNOWN" = 'UNKNOWN'; ``` ### UPDATE ```ts readonly UPDATE: "UPDATE" = 'UPDATE'; ``` --- ## Variable: AuditLogResultEnum ```ts const AuditLogResultEnum: object; ``` The result status of the operation. ## Type Declaration ### FAIL ```ts readonly FAIL: "FAIL" = 'FAIL'; ``` ### SUCCESS ```ts readonly SUCCESS: "SUCCESS" = 'SUCCESS'; ``` --- ## Variable: AuthorizationKey ```ts AuthorizationKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): AuthorizationKey; ``` #### Parameters ##### value `string` #### Returns [`AuthorizationKey`](../type-aliases/AuthorizationKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`AuthorizationKey`](../type-aliases/AuthorizationKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: BatchOperationItemStateEnum ```ts const BatchOperationItemStateEnum: object; ``` The batch operation item state. ## Type Declaration ### ACTIVE ```ts readonly ACTIVE: "ACTIVE" = 'ACTIVE'; ``` ### CANCELED ```ts readonly CANCELED: "CANCELED" = 'CANCELED'; ``` ### COMPLETED ```ts readonly COMPLETED: "COMPLETED" = 'COMPLETED'; ``` ### FAILED ```ts readonly FAILED: "FAILED" = 'FAILED'; ``` --- ## Variable: BatchOperationKey ```ts BatchOperationKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): BatchOperationKey; ``` #### Parameters ##### value `string` #### Returns [`BatchOperationKey`](../type-aliases/BatchOperationKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`BatchOperationKey`](../type-aliases/BatchOperationKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: BatchOperationStateEnum ```ts const BatchOperationStateEnum: object; ``` The batch operation state. ## Type Declaration ### ACTIVE ```ts readonly ACTIVE: "ACTIVE" = 'ACTIVE'; ``` ### CANCELED ```ts readonly CANCELED: "CANCELED" = 'CANCELED'; ``` ### COMPLETED ```ts readonly COMPLETED: "COMPLETED" = 'COMPLETED'; ``` ### CREATED ```ts readonly CREATED: "CREATED" = 'CREATED'; ``` ### FAILED ```ts readonly FAILED: "FAILED" = 'FAILED'; ``` ### PARTIALLY\_COMPLETED ```ts readonly PARTIALLY_COMPLETED: "PARTIALLY_COMPLETED" = 'PARTIALLY_COMPLETED'; ``` ### SUSPENDED ```ts readonly SUSPENDED: "SUSPENDED" = 'SUSPENDED'; ``` --- ## Variable: BatchOperationTypeEnum ```ts const BatchOperationTypeEnum: object; ``` The type of the batch operation. ## Type Declaration ### ADD\_VARIABLE ```ts readonly ADD_VARIABLE: "ADD_VARIABLE" = 'ADD_VARIABLE'; ``` ### CANCEL\_PROCESS\_INSTANCE ```ts readonly CANCEL_PROCESS_INSTANCE: "CANCEL_PROCESS_INSTANCE" = 'CANCEL_PROCESS_INSTANCE'; ``` ### DELETE\_DECISION\_DEFINITION ```ts readonly DELETE_DECISION_DEFINITION: "DELETE_DECISION_DEFINITION" = 'DELETE_DECISION_DEFINITION'; ``` ### DELETE\_DECISION\_INSTANCE ```ts readonly DELETE_DECISION_INSTANCE: "DELETE_DECISION_INSTANCE" = 'DELETE_DECISION_INSTANCE'; ``` ### DELETE\_PROCESS\_DEFINITION ```ts readonly DELETE_PROCESS_DEFINITION: "DELETE_PROCESS_DEFINITION" = 'DELETE_PROCESS_DEFINITION'; ``` ### DELETE\_PROCESS\_INSTANCE ```ts readonly DELETE_PROCESS_INSTANCE: "DELETE_PROCESS_INSTANCE" = 'DELETE_PROCESS_INSTANCE'; ``` ### MIGRATE\_PROCESS\_INSTANCE ```ts readonly MIGRATE_PROCESS_INSTANCE: "MIGRATE_PROCESS_INSTANCE" = 'MIGRATE_PROCESS_INSTANCE'; ``` ### MODIFY\_PROCESS\_INSTANCE ```ts readonly MODIFY_PROCESS_INSTANCE: "MODIFY_PROCESS_INSTANCE" = 'MODIFY_PROCESS_INSTANCE'; ``` ### RESOLVE\_INCIDENT ```ts readonly RESOLVE_INCIDENT: "RESOLVE_INCIDENT" = 'RESOLVE_INCIDENT'; ``` ### RESUME\_PROCESS\_INSTANCE ```ts readonly RESUME_PROCESS_INSTANCE: "RESUME_PROCESS_INSTANCE" = 'RESUME_PROCESS_INSTANCE'; ``` ### SUSPEND\_PROCESS\_INSTANCE ```ts readonly SUSPEND_PROCESS_INSTANCE: "SUSPEND_PROCESS_INSTANCE" = 'SUSPEND_PROCESS_INSTANCE'; ``` ### UPDATE\_JOB ```ts readonly UPDATE_JOB: "UPDATE_JOB" = 'UPDATE_JOB'; ``` ### UPDATE\_VARIABLE ```ts readonly UPDATE_VARIABLE: "UPDATE_VARIABLE" = 'UPDATE_VARIABLE'; ``` --- ## Variable: BusinessId ```ts BusinessId: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): BusinessId; ``` #### Parameters ##### value `string` #### Returns [`BusinessId`](../type-aliases/BusinessId.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`BusinessId`](../type-aliases/BusinessId.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: CamundaClient ```ts CamundaClient: (options?) => CamundaClient & typeof CamundaClientBase; ``` --- ## Variable: ClientId ```ts ClientId: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): ClientId; ``` #### Parameters ##### value `string` #### Returns [`ClientId`](../type-aliases/ClientId.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`ClientId`](../type-aliases/ClientId.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: ClusterVariableKindEnum ```ts const ClusterVariableKindEnum: object; ``` The kind of a cluster variable. JSON is the default. SECRET_REFERENCE allows the value to contain camunda.secrets.X references that are resolved at job activation time. ## Type Declaration ### JSON ```ts readonly JSON: "JSON" = 'JSON'; ``` ### SECRET\_REFERENCE ```ts readonly SECRET_REFERENCE: "SECRET_REFERENCE" = 'SECRET_REFERENCE'; ``` --- ## Variable: ClusterVariableName ```ts ClusterVariableName: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): ClusterVariableName; ``` #### Parameters ##### value `string` #### Returns [`ClusterVariableName`](../type-aliases/ClusterVariableName.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`ClusterVariableName`](../type-aliases/ClusterVariableName.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: ClusterVariableScopeEnum ```ts const ClusterVariableScopeEnum: object; ``` The scope of a cluster variable. ## Type Declaration ### GLOBAL ```ts readonly GLOBAL: "GLOBAL" = 'GLOBAL'; ``` ### TENANT ```ts readonly TENANT: "TENANT" = 'TENANT'; ``` --- ## Variable: ConditionalEvaluationKey ```ts ConditionalEvaluationKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): ConditionalEvaluationKey; ``` #### Parameters ##### value `string` #### Returns [`ConditionalEvaluationKey`](../type-aliases/ConditionalEvaluationKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`ConditionalEvaluationKey`](../type-aliases/ConditionalEvaluationKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: DecisionDefinitionId ```ts DecisionDefinitionId: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): DecisionDefinitionId; ``` #### Parameters ##### value `string` #### Returns [`DecisionDefinitionId`](../type-aliases/DecisionDefinitionId.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`DecisionDefinitionId`](../type-aliases/DecisionDefinitionId.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: DecisionDefinitionKey ```ts DecisionDefinitionKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): DecisionDefinitionKey; ``` #### Parameters ##### value `string` #### Returns [`DecisionDefinitionKey`](../type-aliases/DecisionDefinitionKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`DecisionDefinitionKey`](../type-aliases/DecisionDefinitionKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: DecisionDefinitionTypeEnum ```ts const DecisionDefinitionTypeEnum: object; ``` The type of the decision. UNSPECIFIED is deprecated and should not be used anymore, for removal in 8.10 ## Type Declaration ### DECISION\_TABLE ```ts readonly DECISION_TABLE: "DECISION_TABLE" = 'DECISION_TABLE'; ``` ### LITERAL\_EXPRESSION ```ts readonly LITERAL_EXPRESSION: "LITERAL_EXPRESSION" = 'LITERAL_EXPRESSION'; ``` ### UNKNOWN ```ts readonly UNKNOWN: "UNKNOWN" = 'UNKNOWN'; ``` ### ~~UNSPECIFIED~~ ```ts readonly UNSPECIFIED: "UNSPECIFIED" = 'UNSPECIFIED'; ``` #### Deprecated since 8.9.0 --- ## Variable: DecisionEvaluationInstanceKey ```ts DecisionEvaluationInstanceKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): DecisionEvaluationInstanceKey; ``` #### Parameters ##### value `string` #### Returns [`DecisionEvaluationInstanceKey`](../type-aliases/DecisionEvaluationInstanceKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`DecisionEvaluationInstanceKey`](../type-aliases/DecisionEvaluationInstanceKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: DecisionEvaluationKey ```ts DecisionEvaluationKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): DecisionEvaluationKey; ``` #### Parameters ##### value `string` #### Returns [`DecisionEvaluationKey`](../type-aliases/DecisionEvaluationKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`DecisionEvaluationKey`](../type-aliases/DecisionEvaluationKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: DecisionInstanceKey ```ts DecisionInstanceKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): DecisionInstanceKey; ``` #### Parameters ##### value `string` #### Returns [`DecisionInstanceKey`](../type-aliases/DecisionInstanceKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`DecisionInstanceKey`](../type-aliases/DecisionInstanceKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: DecisionInstanceStateEnum ```ts const DecisionInstanceStateEnum: object; ``` The state of the decision instance. UNSPECIFIED and UNKNOWN are deprecated and should not be used anymore, for removal in 8.10 ## Type Declaration ### EVALUATED ```ts readonly EVALUATED: "EVALUATED" = 'EVALUATED'; ``` ### FAILED ```ts readonly FAILED: "FAILED" = 'FAILED'; ``` ### ~~UNKNOWN~~ ```ts readonly UNKNOWN: "UNKNOWN" = 'UNKNOWN'; ``` #### Deprecated since 8.9.0 ### ~~UNSPECIFIED~~ ```ts readonly UNSPECIFIED: "UNSPECIFIED" = 'UNSPECIFIED'; ``` #### Deprecated since 8.9.0 --- ## Variable: DecisionRequirementsKey ```ts DecisionRequirementsKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): DecisionRequirementsKey; ``` #### Parameters ##### value `string` #### Returns [`DecisionRequirementsKey`](../type-aliases/DecisionRequirementsKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`DecisionRequirementsKey`](../type-aliases/DecisionRequirementsKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: DeploymentKey ```ts DeploymentKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): DeploymentKey; ``` #### Parameters ##### value `string` #### Returns [`DeploymentKey`](../type-aliases/DeploymentKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`DeploymentKey`](../type-aliases/DeploymentKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: DocumentId ```ts DocumentId: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): DocumentId; ``` #### Parameters ##### value `string` #### Returns [`DocumentId`](../type-aliases/DocumentId.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`DocumentId`](../type-aliases/DocumentId.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: ElementId ```ts ElementId: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): ElementId; ``` #### Parameters ##### value `string` #### Returns [`ElementId`](../type-aliases/ElementId.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`ElementId`](../type-aliases/ElementId.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: ElementInstanceKey ```ts ElementInstanceKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): ElementInstanceKey; ``` #### Parameters ##### value `string` #### Returns [`ElementInstanceKey`](../type-aliases/ElementInstanceKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`ElementInstanceKey`](../type-aliases/ElementInstanceKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: ElementInstanceStateEnum ```ts const ElementInstanceStateEnum: object; ``` Element states ## Type Declaration ### ACTIVE ```ts readonly ACTIVE: "ACTIVE" = 'ACTIVE'; ``` ### COMPLETED ```ts readonly COMPLETED: "COMPLETED" = 'COMPLETED'; ``` ### TERMINATED ```ts readonly TERMINATED: "TERMINATED" = 'TERMINATED'; ``` --- ## Variable: EndCursor ```ts EndCursor: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): EndCursor; ``` #### Parameters ##### value `string` #### Returns [`EndCursor`](../type-aliases/EndCursor.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`EndCursor`](../type-aliases/EndCursor.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: FormId ```ts FormId: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): FormId; ``` #### Parameters ##### value `string` #### Returns [`FormId`](../type-aliases/FormId.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`FormId`](../type-aliases/FormId.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: FormKey ```ts FormKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): FormKey; ``` #### Parameters ##### value `string` #### Returns [`FormKey`](../type-aliases/FormKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`FormKey`](../type-aliases/FormKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: GlobalListenerId ```ts GlobalListenerId: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): GlobalListenerId; ``` #### Parameters ##### value `string` #### Returns [`GlobalListenerId`](../type-aliases/GlobalListenerId.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`GlobalListenerId`](../type-aliases/GlobalListenerId.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: GlobalListenerSourceEnum ```ts const GlobalListenerSourceEnum: object; ``` How the global listener was defined. ## Type Declaration ### API ```ts readonly API: "API" = 'API'; ``` ### CONFIGURATION ```ts readonly CONFIGURATION: "CONFIGURATION" = 'CONFIGURATION'; ``` --- ## Variable: GlobalTaskListenerEventTypeEnum ```ts const GlobalTaskListenerEventTypeEnum: object; ``` The event type that triggers the user task listener. ## Type Declaration ### all ```ts readonly all: "all" = 'all'; ``` ### assigning ```ts readonly assigning: "assigning" = 'assigning'; ``` ### canceling ```ts readonly canceling: "canceling" = 'canceling'; ``` ### completing ```ts readonly completing: "completing" = 'completing'; ``` ### creating ```ts readonly creating: "creating" = 'creating'; ``` ### updating ```ts readonly updating: "updating" = 'updating'; ``` --- ## Variable: GroupId ```ts GroupId: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): GroupId; ``` #### Parameters ##### value `string` #### Returns [`GroupId`](../type-aliases/GroupId.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`GroupId`](../type-aliases/GroupId.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: HistoryItemId ```ts HistoryItemId: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): HistoryItemId; ``` #### Parameters ##### value `string` #### Returns [`HistoryItemId`](../type-aliases/HistoryItemId.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`HistoryItemId`](../type-aliases/HistoryItemId.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: IncidentErrorTypeEnum ```ts const IncidentErrorTypeEnum: object; ``` Incident error type with a defined set of values. ## Type Declaration ### AD\_HOC\_SUB\_PROCESS\_NO\_RETRIES ```ts readonly AD_HOC_SUB_PROCESS_NO_RETRIES: "AD_HOC_SUB_PROCESS_NO_RETRIES" = 'AD_HOC_SUB_PROCESS_NO_RETRIES'; ``` ### CALLED\_DECISION\_ERROR ```ts readonly CALLED_DECISION_ERROR: "CALLED_DECISION_ERROR" = 'CALLED_DECISION_ERROR'; ``` ### CALLED\_ELEMENT\_ERROR ```ts readonly CALLED_ELEMENT_ERROR: "CALLED_ELEMENT_ERROR" = 'CALLED_ELEMENT_ERROR'; ``` ### CONDITION\_ERROR ```ts readonly CONDITION_ERROR: "CONDITION_ERROR" = 'CONDITION_ERROR'; ``` ### DECISION\_EVALUATION\_ERROR ```ts readonly DECISION_EVALUATION_ERROR: "DECISION_EVALUATION_ERROR" = 'DECISION_EVALUATION_ERROR'; ``` ### EXECUTION\_LISTENER\_NO\_RETRIES ```ts readonly EXECUTION_LISTENER_NO_RETRIES: "EXECUTION_LISTENER_NO_RETRIES" = 'EXECUTION_LISTENER_NO_RETRIES'; ``` ### EXTRACT\_VALUE\_ERROR ```ts readonly EXTRACT_VALUE_ERROR: "EXTRACT_VALUE_ERROR" = 'EXTRACT_VALUE_ERROR'; ``` ### FORM\_NOT\_FOUND ```ts readonly FORM_NOT_FOUND: "FORM_NOT_FOUND" = 'FORM_NOT_FOUND'; ``` ### IO\_MAPPING\_ERROR ```ts readonly IO_MAPPING_ERROR: "IO_MAPPING_ERROR" = 'IO_MAPPING_ERROR'; ``` ### JOB\_NO\_RETRIES ```ts readonly JOB_NO_RETRIES: "JOB_NO_RETRIES" = 'JOB_NO_RETRIES'; ``` ### MESSAGE\_SIZE\_EXCEEDED ```ts readonly MESSAGE_SIZE_EXCEEDED: "MESSAGE_SIZE_EXCEEDED" = 'MESSAGE_SIZE_EXCEEDED'; ``` ### RESOURCE\_NOT\_FOUND ```ts readonly RESOURCE_NOT_FOUND: "RESOURCE_NOT_FOUND" = 'RESOURCE_NOT_FOUND'; ``` ### SECRET\_RESOLUTION\_ERROR ```ts readonly SECRET_RESOLUTION_ERROR: "SECRET_RESOLUTION_ERROR" = 'SECRET_RESOLUTION_ERROR'; ``` ### TASK\_LISTENER\_NO\_RETRIES ```ts readonly TASK_LISTENER_NO_RETRIES: "TASK_LISTENER_NO_RETRIES" = 'TASK_LISTENER_NO_RETRIES'; ``` ### UNHANDLED\_ERROR\_EVENT ```ts readonly UNHANDLED_ERROR_EVENT: "UNHANDLED_ERROR_EVENT" = 'UNHANDLED_ERROR_EVENT'; ``` ### UNKNOWN ```ts readonly UNKNOWN: "UNKNOWN" = 'UNKNOWN'; ``` ### UNSPECIFIED ```ts readonly UNSPECIFIED: "UNSPECIFIED" = 'UNSPECIFIED'; ``` --- ## Variable: IncidentKey ```ts IncidentKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): IncidentKey; ``` #### Parameters ##### value `string` #### Returns [`IncidentKey`](../type-aliases/IncidentKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`IncidentKey`](../type-aliases/IncidentKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: IncidentStateEnum ```ts const IncidentStateEnum: object; ``` Incident states with a defined set of values. ## Type Declaration ### ACTIVE ```ts readonly ACTIVE: "ACTIVE" = 'ACTIVE'; ``` ### MIGRATED ```ts readonly MIGRATED: "MIGRATED" = 'MIGRATED'; ``` ### PENDING ```ts readonly PENDING: "PENDING" = 'PENDING'; ``` ### RESOLVED ```ts readonly RESOLVED: "RESOLVED" = 'RESOLVED'; ``` ### UNKNOWN ```ts readonly UNKNOWN: "UNKNOWN" = 'UNKNOWN'; ``` --- ## Variable: JobKey ```ts JobKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): JobKey; ``` #### Parameters ##### value `string` #### Returns [`JobKey`](../type-aliases/JobKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`JobKey`](../type-aliases/JobKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: JobKindEnum ```ts const JobKindEnum: object; ``` The job kind. ## Type Declaration ### AD\_HOC\_SUB\_PROCESS ```ts readonly AD_HOC_SUB_PROCESS: "AD_HOC_SUB_PROCESS" = 'AD_HOC_SUB_PROCESS'; ``` ### BPMN\_ELEMENT ```ts readonly BPMN_ELEMENT: "BPMN_ELEMENT" = 'BPMN_ELEMENT'; ``` ### EXECUTION\_LISTENER ```ts readonly EXECUTION_LISTENER: "EXECUTION_LISTENER" = 'EXECUTION_LISTENER'; ``` ### TASK\_LISTENER ```ts readonly TASK_LISTENER: "TASK_LISTENER" = 'TASK_LISTENER'; ``` --- ## Variable: JobLeaseToken ```ts JobLeaseToken: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): JobLeaseToken; ``` #### Parameters ##### value `string` #### Returns [`JobLeaseToken`](../type-aliases/JobLeaseToken.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`JobLeaseToken`](../type-aliases/JobLeaseToken.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: JobListenerEventTypeEnum ```ts const JobListenerEventTypeEnum: object; ``` The listener event type of the job. ## Type Declaration ### ASSIGNING ```ts readonly ASSIGNING: "ASSIGNING" = 'ASSIGNING'; ``` ### BEFORE\_ALL ```ts readonly BEFORE_ALL: "BEFORE_ALL" = 'BEFORE_ALL'; ``` ### CANCEL ```ts readonly CANCEL: "CANCEL" = 'CANCEL'; ``` ### CANCELING ```ts readonly CANCELING: "CANCELING" = 'CANCELING'; ``` ### COMPLETING ```ts readonly COMPLETING: "COMPLETING" = 'COMPLETING'; ``` ### CREATING ```ts readonly CREATING: "CREATING" = 'CREATING'; ``` ### END ```ts readonly END: "END" = 'END'; ``` ### START ```ts readonly START: "START" = 'START'; ``` ### UNSPECIFIED ```ts readonly UNSPECIFIED: "UNSPECIFIED" = 'UNSPECIFIED'; ``` ### UPDATING ```ts readonly UPDATING: "UPDATING" = 'UPDATING'; ``` --- ## Variable: JobStateEnum ```ts const JobStateEnum: object; ``` The state of the job. ## Type Declaration ### CANCELED ```ts readonly CANCELED: "CANCELED" = 'CANCELED'; ``` ### COMPLETED ```ts readonly COMPLETED: "COMPLETED" = 'COMPLETED'; ``` ### CREATED ```ts readonly CREATED: "CREATED" = 'CREATED'; ``` ### ERROR\_THROWN ```ts readonly ERROR_THROWN: "ERROR_THROWN" = 'ERROR_THROWN'; ``` ### FAILED ```ts readonly FAILED: "FAILED" = 'FAILED'; ``` ### MIGRATED ```ts readonly MIGRATED: "MIGRATED" = 'MIGRATED'; ``` ### PRIORITY\_UPDATED ```ts readonly PRIORITY_UPDATED: "PRIORITY_UPDATED" = 'PRIORITY_UPDATED'; ``` ### RETRIES\_UPDATED ```ts readonly RETRIES_UPDATED: "RETRIES_UPDATED" = 'RETRIES_UPDATED'; ``` ### TIMED\_OUT ```ts readonly TIMED_OUT: "TIMED_OUT" = 'TIMED_OUT'; ``` ### TIMEOUT\_UPDATED ```ts readonly TIMEOUT_UPDATED: "TIMEOUT_UPDATED" = 'TIMEOUT_UPDATED'; ``` --- ## Variable: MappingRuleId ```ts MappingRuleId: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): MappingRuleId; ``` #### Parameters ##### value `string` #### Returns [`MappingRuleId`](../type-aliases/MappingRuleId.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`MappingRuleId`](../type-aliases/MappingRuleId.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: MessageKey ```ts MessageKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): MessageKey; ``` #### Parameters ##### value `string` #### Returns [`MessageKey`](../type-aliases/MessageKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`MessageKey`](../type-aliases/MessageKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: MessageSubscriptionKey ```ts MessageSubscriptionKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): MessageSubscriptionKey; ``` #### Parameters ##### value `string` #### Returns [`MessageSubscriptionKey`](../type-aliases/MessageSubscriptionKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`MessageSubscriptionKey`](../type-aliases/MessageSubscriptionKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: MessageSubscriptionStateEnum ```ts const MessageSubscriptionStateEnum: object; ``` The state of message subscription. **Note for `START_EVENT` subscriptions:** The `CORRELATED` and `MIGRATED` states are not tracked for these subscriptions. To query correlation history for process start events, use the `/correlated-message-subscriptions/search` endpoint. ## Type Declaration ### CORRELATED ```ts readonly CORRELATED: "CORRELATED" = 'CORRELATED'; ``` ### CREATED ```ts readonly CREATED: "CREATED" = 'CREATED'; ``` ### DELETED ```ts readonly DELETED: "DELETED" = 'DELETED'; ``` ### MIGRATED ```ts readonly MIGRATED: "MIGRATED" = 'MIGRATED'; ``` --- ## Variable: MessageSubscriptionTypeEnum ```ts const MessageSubscriptionTypeEnum: object; ``` The type of message subscription. `START_EVENT` is definition-scoped (process start events). Always has a value; only captured from Camunda 8.10 onwards. `PROCESS_EVENT` is instance-scoped (intermediate catch events). Pre-8.10 entries have no value stored; the API returns `PROCESS_EVENT` as a default for those entries. ## Type Declaration ### PROCESS\_EVENT ```ts readonly PROCESS_EVENT: "PROCESS_EVENT" = 'PROCESS_EVENT'; ``` ### START\_EVENT ```ts readonly START_EVENT: "START_EVENT" = 'START_EVENT'; ``` --- ## Variable: OwnerTypeEnum ```ts const OwnerTypeEnum: object; ``` The type of the owner of permissions. ## Type Declaration ### CLIENT ```ts readonly CLIENT: "CLIENT" = 'CLIENT'; ``` ### GROUP ```ts readonly GROUP: "GROUP" = 'GROUP'; ``` ### MAPPING\_RULE ```ts readonly MAPPING_RULE: "MAPPING_RULE" = 'MAPPING_RULE'; ``` ### ROLE ```ts readonly ROLE: "ROLE" = 'ROLE'; ``` ### UNSPECIFIED ```ts readonly UNSPECIFIED: "UNSPECIFIED" = 'UNSPECIFIED'; ``` ### USER ```ts readonly USER: "USER" = 'USER'; ``` --- ## Variable: PermissionTypeEnum ```ts const PermissionTypeEnum: object; ``` Specifies the type of permissions. ## Type Declaration ### ACCESS ```ts readonly ACCESS: "ACCESS" = 'ACCESS'; ``` ### CANCEL\_PROCESS\_INSTANCE ```ts readonly CANCEL_PROCESS_INSTANCE: "CANCEL_PROCESS_INSTANCE" = 'CANCEL_PROCESS_INSTANCE'; ``` ### CLAIM ```ts readonly CLAIM: "CLAIM" = 'CLAIM'; ``` ### CLAIM\_USER\_TASK ```ts readonly CLAIM_USER_TASK: "CLAIM_USER_TASK" = 'CLAIM_USER_TASK'; ``` ### COMPLETE ```ts readonly COMPLETE: "COMPLETE" = 'COMPLETE'; ``` ### COMPLETE\_USER\_TASK ```ts readonly COMPLETE_USER_TASK: "COMPLETE_USER_TASK" = 'COMPLETE_USER_TASK'; ``` ### CREATE ```ts readonly CREATE: "CREATE" = 'CREATE'; ``` ### CREATE\_BATCH\_OPERATION\_CANCEL\_PROCESS\_INSTANCE ```ts readonly CREATE_BATCH_OPERATION_CANCEL_PROCESS_INSTANCE: "CREATE_BATCH_OPERATION_CANCEL_PROCESS_INSTANCE" = 'CREATE_BATCH_OPERATION_CANCEL_PROCESS_INSTANCE'; ``` ### CREATE\_BATCH\_OPERATION\_DELETE\_DECISION\_DEFINITION ```ts readonly CREATE_BATCH_OPERATION_DELETE_DECISION_DEFINITION: "CREATE_BATCH_OPERATION_DELETE_DECISION_DEFINITION" = 'CREATE_BATCH_OPERATION_DELETE_DECISION_DEFINITION'; ``` ### CREATE\_BATCH\_OPERATION\_DELETE\_DECISION\_INSTANCE ```ts readonly CREATE_BATCH_OPERATION_DELETE_DECISION_INSTANCE: "CREATE_BATCH_OPERATION_DELETE_DECISION_INSTANCE" = 'CREATE_BATCH_OPERATION_DELETE_DECISION_INSTANCE'; ``` ### CREATE\_BATCH\_OPERATION\_DELETE\_PROCESS\_DEFINITION ```ts readonly CREATE_BATCH_OPERATION_DELETE_PROCESS_DEFINITION: "CREATE_BATCH_OPERATION_DELETE_PROCESS_DEFINITION" = 'CREATE_BATCH_OPERATION_DELETE_PROCESS_DEFINITION'; ``` ### CREATE\_BATCH\_OPERATION\_DELETE\_PROCESS\_INSTANCE ```ts readonly CREATE_BATCH_OPERATION_DELETE_PROCESS_INSTANCE: "CREATE_BATCH_OPERATION_DELETE_PROCESS_INSTANCE" = 'CREATE_BATCH_OPERATION_DELETE_PROCESS_INSTANCE'; ``` ### CREATE\_BATCH\_OPERATION\_MIGRATE\_PROCESS\_INSTANCE ```ts readonly CREATE_BATCH_OPERATION_MIGRATE_PROCESS_INSTANCE: "CREATE_BATCH_OPERATION_MIGRATE_PROCESS_INSTANCE" = 'CREATE_BATCH_OPERATION_MIGRATE_PROCESS_INSTANCE'; ``` ### CREATE\_BATCH\_OPERATION\_MODIFY\_PROCESS\_INSTANCE ```ts readonly CREATE_BATCH_OPERATION_MODIFY_PROCESS_INSTANCE: "CREATE_BATCH_OPERATION_MODIFY_PROCESS_INSTANCE" = 'CREATE_BATCH_OPERATION_MODIFY_PROCESS_INSTANCE'; ``` ### CREATE\_BATCH\_OPERATION\_RESOLVE\_INCIDENT ```ts readonly CREATE_BATCH_OPERATION_RESOLVE_INCIDENT: "CREATE_BATCH_OPERATION_RESOLVE_INCIDENT" = 'CREATE_BATCH_OPERATION_RESOLVE_INCIDENT'; ``` ### CREATE\_BATCH\_OPERATION\_SUSPEND\_PROCESS\_INSTANCE ```ts readonly CREATE_BATCH_OPERATION_SUSPEND_PROCESS_INSTANCE: "CREATE_BATCH_OPERATION_SUSPEND_PROCESS_INSTANCE" = 'CREATE_BATCH_OPERATION_SUSPEND_PROCESS_INSTANCE'; ``` ### CREATE\_BATCH\_OPERATION\_UPDATE\_JOB ```ts readonly CREATE_BATCH_OPERATION_UPDATE_JOB: "CREATE_BATCH_OPERATION_UPDATE_JOB" = 'CREATE_BATCH_OPERATION_UPDATE_JOB'; ``` ### CREATE\_DECISION\_INSTANCE ```ts readonly CREATE_DECISION_INSTANCE: "CREATE_DECISION_INSTANCE" = 'CREATE_DECISION_INSTANCE'; ``` ### CREATE\_PROCESS\_INSTANCE ```ts readonly CREATE_PROCESS_INSTANCE: "CREATE_PROCESS_INSTANCE" = 'CREATE_PROCESS_INSTANCE'; ``` ### CREATE\_TASK\_LISTENER ```ts readonly CREATE_TASK_LISTENER: "CREATE_TASK_LISTENER" = 'CREATE_TASK_LISTENER'; ``` ### DELETE ```ts readonly DELETE: "DELETE" = 'DELETE'; ``` ### DELETE\_DECISION\_INSTANCE ```ts readonly DELETE_DECISION_INSTANCE: "DELETE_DECISION_INSTANCE" = 'DELETE_DECISION_INSTANCE'; ``` ### DELETE\_DRD ```ts readonly DELETE_DRD: "DELETE_DRD" = 'DELETE_DRD'; ``` ### DELETE\_FORM ```ts readonly DELETE_FORM: "DELETE_FORM" = 'DELETE_FORM'; ``` ### DELETE\_PROCESS ```ts readonly DELETE_PROCESS: "DELETE_PROCESS" = 'DELETE_PROCESS'; ``` ### DELETE\_PROCESS\_INSTANCE ```ts readonly DELETE_PROCESS_INSTANCE: "DELETE_PROCESS_INSTANCE" = 'DELETE_PROCESS_INSTANCE'; ``` ### DELETE\_RESOURCE ```ts readonly DELETE_RESOURCE: "DELETE_RESOURCE" = 'DELETE_RESOURCE'; ``` ### DELETE\_TASK\_LISTENER ```ts readonly DELETE_TASK_LISTENER: "DELETE_TASK_LISTENER" = 'DELETE_TASK_LISTENER'; ``` ### EVALUATE ```ts readonly EVALUATE: "EVALUATE" = 'EVALUATE'; ``` ### MODIFY\_PROCESS\_INSTANCE ```ts readonly MODIFY_PROCESS_INSTANCE: "MODIFY_PROCESS_INSTANCE" = 'MODIFY_PROCESS_INSTANCE'; ``` ### PAUSE ```ts readonly PAUSE: "PAUSE" = 'PAUSE'; ``` ### READ ```ts readonly READ: "READ" = 'READ'; ``` ### READ\_DECISION\_DEFINITION ```ts readonly READ_DECISION_DEFINITION: "READ_DECISION_DEFINITION" = 'READ_DECISION_DEFINITION'; ``` ### READ\_DECISION\_INSTANCE ```ts readonly READ_DECISION_INSTANCE: "READ_DECISION_INSTANCE" = 'READ_DECISION_INSTANCE'; ``` ### READ\_JOB\_METRIC ```ts readonly READ_JOB_METRIC: "READ_JOB_METRIC" = 'READ_JOB_METRIC'; ``` ### READ\_PROCESS\_DEFINITION ```ts readonly READ_PROCESS_DEFINITION: "READ_PROCESS_DEFINITION" = 'READ_PROCESS_DEFINITION'; ``` ### READ\_PROCESS\_INSTANCE ```ts readonly READ_PROCESS_INSTANCE: "READ_PROCESS_INSTANCE" = 'READ_PROCESS_INSTANCE'; ``` ### READ\_TASK\_LISTENER ```ts readonly READ_TASK_LISTENER: "READ_TASK_LISTENER" = 'READ_TASK_LISTENER'; ``` ### READ\_USAGE\_METRIC ```ts readonly READ_USAGE_METRIC: "READ_USAGE_METRIC" = 'READ_USAGE_METRIC'; ``` ### READ\_USER\_TASK ```ts readonly READ_USER_TASK: "READ_USER_TASK" = 'READ_USER_TASK'; ``` ### RESTORE ```ts readonly RESTORE: "RESTORE" = 'RESTORE'; ``` ### REVEAL ```ts readonly REVEAL: "REVEAL" = 'REVEAL'; ``` ### SUSPEND\_PROCESS\_INSTANCE ```ts readonly SUSPEND_PROCESS_INSTANCE: "SUSPEND_PROCESS_INSTANCE" = 'SUSPEND_PROCESS_INSTANCE'; ``` ### UPDATE ```ts readonly UPDATE: "UPDATE" = 'UPDATE'; ``` ### UPDATE\_PROCESS\_INSTANCE ```ts readonly UPDATE_PROCESS_INSTANCE: "UPDATE_PROCESS_INSTANCE" = 'UPDATE_PROCESS_INSTANCE'; ``` ### UPDATE\_TASK\_LISTENER ```ts readonly UPDATE_TASK_LISTENER: "UPDATE_TASK_LISTENER" = 'UPDATE_TASK_LISTENER'; ``` ### UPDATE\_USER\_TASK ```ts readonly UPDATE_USER_TASK: "UPDATE_USER_TASK" = 'UPDATE_USER_TASK'; ``` --- ## Variable: ProcessDefinitionId ```ts ProcessDefinitionId: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): ProcessDefinitionId; ``` #### Parameters ##### value `string` #### Returns [`ProcessDefinitionId`](../type-aliases/ProcessDefinitionId.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`ProcessDefinitionId`](../type-aliases/ProcessDefinitionId.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: ProcessDefinitionKey ```ts ProcessDefinitionKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): ProcessDefinitionKey; ``` #### Parameters ##### value `string` #### Returns [`ProcessDefinitionKey`](../type-aliases/ProcessDefinitionKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`ProcessDefinitionKey`](../type-aliases/ProcessDefinitionKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: ProcessInstanceKey ```ts ProcessInstanceKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): ProcessInstanceKey; ``` #### Parameters ##### value `string` #### Returns [`ProcessInstanceKey`](../type-aliases/ProcessInstanceKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`ProcessInstanceKey`](../type-aliases/ProcessInstanceKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: ProcessInstanceStateEnum ```ts const ProcessInstanceStateEnum: object; ``` Process instance states ## Type Declaration ### ACTIVE ```ts readonly ACTIVE: "ACTIVE" = 'ACTIVE'; ``` ### COMPLETED ```ts readonly COMPLETED: "COMPLETED" = 'COMPLETED'; ``` ### SUSPENDED ```ts readonly SUSPENDED: "SUSPENDED" = 'SUSPENDED'; ``` ### TERMINATED ```ts readonly TERMINATED: "TERMINATED" = 'TERMINATED'; ``` --- ## Variable: ResourceTypeEnum ```ts const ResourceTypeEnum: object; ``` The type of resource to add/remove permissions to/from. ## Type Declaration ### AUDIT\_LOG ```ts readonly AUDIT_LOG: "AUDIT_LOG" = 'AUDIT_LOG'; ``` ### AUTHORIZATION ```ts readonly AUTHORIZATION: "AUTHORIZATION" = 'AUTHORIZATION'; ``` ### BACKUP ```ts readonly BACKUP: "BACKUP" = 'BACKUP'; ``` ### BATCH ```ts readonly BATCH: "BATCH" = 'BATCH'; ``` ### CLUSTER\_VARIABLE ```ts readonly CLUSTER_VARIABLE: "CLUSTER_VARIABLE" = 'CLUSTER_VARIABLE'; ``` ### COMPONENT ```ts readonly COMPONENT: "COMPONENT" = 'COMPONENT'; ``` ### DECISION\_DEFINITION ```ts readonly DECISION_DEFINITION: "DECISION_DEFINITION" = 'DECISION_DEFINITION'; ``` ### DECISION\_REQUIREMENTS\_DEFINITION ```ts readonly DECISION_REQUIREMENTS_DEFINITION: "DECISION_REQUIREMENTS_DEFINITION" = 'DECISION_REQUIREMENTS_DEFINITION'; ``` ### DOCUMENT ```ts readonly DOCUMENT: "DOCUMENT" = 'DOCUMENT'; ``` ### EXPORTER ```ts readonly EXPORTER: "EXPORTER" = 'EXPORTER'; ``` ### EXPRESSION ```ts readonly EXPRESSION: "EXPRESSION" = 'EXPRESSION'; ``` ### GLOBAL\_LISTENER ```ts readonly GLOBAL_LISTENER: "GLOBAL_LISTENER" = 'GLOBAL_LISTENER'; ``` ### GROUP ```ts readonly GROUP: "GROUP" = 'GROUP'; ``` ### MAPPING\_RULE ```ts readonly MAPPING_RULE: "MAPPING_RULE" = 'MAPPING_RULE'; ``` ### MESSAGE ```ts readonly MESSAGE: "MESSAGE" = 'MESSAGE'; ``` ### PROCESS\_DEFINITION ```ts readonly PROCESS_DEFINITION: "PROCESS_DEFINITION" = 'PROCESS_DEFINITION'; ``` ### RESOURCE ```ts readonly RESOURCE: "RESOURCE" = 'RESOURCE'; ``` ### ROLE ```ts readonly ROLE: "ROLE" = 'ROLE'; ``` ### SECRET ```ts readonly SECRET: "SECRET" = 'SECRET'; ``` ### SYSTEM ```ts readonly SYSTEM: "SYSTEM" = 'SYSTEM'; ``` ### TENANT ```ts readonly TENANT: "TENANT" = 'TENANT'; ``` ### USER ```ts readonly USER: "USER" = 'USER'; ``` ### USER\_TASK ```ts readonly USER_TASK: "USER_TASK" = 'USER_TASK'; ``` --- ## Variable: RoleId ```ts RoleId: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): RoleId; ``` #### Parameters ##### value `string` #### Returns [`RoleId`](../type-aliases/RoleId.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`RoleId`](../type-aliases/RoleId.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: SEED ENV VAR # Variable: SEED\_ENV\_VAR ```ts const SEED_ENV_VAR: "CAMUNDA_TEST_SEED" = "CAMUNDA_TEST_SEED"; ``` Environment variable [seededRandomFromEnv](../functions/seededRandomFromEnv.md) reads to replay a seed. --- ## Variable: SPEC HASH # Variable: SPEC\_HASH ```ts const SPEC_HASH: "sha256:06287d5f9ab82bc96d9533d975aeae78dfc5c5ecda0871ba6939bc7553da2b7c"; ``` --- ## Variable: SignalKey ```ts SignalKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): SignalKey; ``` #### Parameters ##### value `string` #### Returns [`SignalKey`](../type-aliases/SignalKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`SignalKey`](../type-aliases/SignalKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: SortOrderEnum ```ts const SortOrderEnum: object; ``` The order in which to sort the related field. ## Type Declaration ### ASC ```ts readonly ASC: "ASC" = 'ASC'; ``` ### DESC ```ts readonly DESC: "DESC" = 'DESC'; ``` --- ## Variable: StartCursor ```ts StartCursor: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): StartCursor; ``` #### Parameters ##### value `string` #### Returns [`StartCursor`](../type-aliases/StartCursor.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`StartCursor`](../type-aliases/StartCursor.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: Tag ```ts Tag: object; ``` ## Type Declaration ### fromString() ```ts fromString(value): Tag; ``` #### Parameters ##### value `string` #### Returns [`Tag`](../type-aliases/Tag.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`Tag`](../type-aliases/Tag.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: TenantFilterEnum ```ts const TenantFilterEnum: object; ``` The tenant filtering strategy for job activation. Determines whether to use tenant IDs provided in the request or tenant IDs assigned to the authenticated principal. ## Type Declaration ### ASSIGNED ```ts readonly ASSIGNED: "ASSIGNED" = 'ASSIGNED'; ``` ### PROVIDED ```ts readonly PROVIDED: "PROVIDED" = 'PROVIDED'; ``` --- ## Variable: TenantId ```ts TenantId: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): TenantId; ``` #### Parameters ##### value `string` #### Returns [`TenantId`](../type-aliases/TenantId.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`TenantId`](../type-aliases/TenantId.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: UserTaskKey ```ts UserTaskKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): UserTaskKey; ``` #### Parameters ##### value `string` #### Returns [`UserTaskKey`](../type-aliases/UserTaskKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`UserTaskKey`](../type-aliases/UserTaskKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: UserTaskStateEnum ```ts const UserTaskStateEnum: object; ``` The state of the user task. Note: FAILED state is only for legacy job-worker-based tasks. ## Type Declaration ### ASSIGNING ```ts readonly ASSIGNING: "ASSIGNING" = 'ASSIGNING'; ``` ### CANCELED ```ts readonly CANCELED: "CANCELED" = 'CANCELED'; ``` ### CANCELING ```ts readonly CANCELING: "CANCELING" = 'CANCELING'; ``` ### COMPLETED ```ts readonly COMPLETED: "COMPLETED" = 'COMPLETED'; ``` ### COMPLETING ```ts readonly COMPLETING: "COMPLETING" = 'COMPLETING'; ``` ### CREATED ```ts readonly CREATED: "CREATED" = 'CREATED'; ``` ### CREATING ```ts readonly CREATING: "CREATING" = 'CREATING'; ``` ### FAILED ```ts readonly FAILED: "FAILED" = 'FAILED'; ``` ### UPDATING ```ts readonly UPDATING: "UPDATING" = 'UPDATING'; ``` --- ## Variable: Username ```ts Username: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): Username; ``` #### Parameters ##### value `string` #### Returns [`Username`](../type-aliases/Username.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`Username`](../type-aliases/Username.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: VariableKey ```ts VariableKey: object; ``` ## Type Declaration ### assumeExists() ```ts assumeExists(value): VariableKey; ``` #### Parameters ##### value `string` #### Returns [`VariableKey`](../type-aliases/VariableKey.md) ### getValue() ```ts getValue(key): string; ``` #### Parameters ##### key [`VariableKey`](../type-aliases/VariableKey.md) #### Returns `string` ### isValid() ```ts isValid(value): boolean; ``` #### Parameters ##### value `string` #### Returns `boolean` --- ## Variable: WaitStateElementTypeEnum ```ts const WaitStateElementTypeEnum: object; ``` The BPMN element type of a waiting element instance. ## Type Declaration ### AD\_HOC\_SUB\_PROCESS ```ts readonly AD_HOC_SUB_PROCESS: "AD_HOC_SUB_PROCESS" = 'AD_HOC_SUB_PROCESS'; ``` ### AD\_HOC\_SUB\_PROCESS\_INNER\_INSTANCE ```ts readonly AD_HOC_SUB_PROCESS_INNER_INSTANCE: "AD_HOC_SUB_PROCESS_INNER_INSTANCE" = 'AD_HOC_SUB_PROCESS_INNER_INSTANCE'; ``` ### BOUNDARY\_EVENT ```ts readonly BOUNDARY_EVENT: "BOUNDARY_EVENT" = 'BOUNDARY_EVENT'; ``` ### BUSINESS\_RULE\_TASK ```ts readonly BUSINESS_RULE_TASK: "BUSINESS_RULE_TASK" = 'BUSINESS_RULE_TASK'; ``` ### CALL\_ACTIVITY ```ts readonly CALL_ACTIVITY: "CALL_ACTIVITY" = 'CALL_ACTIVITY'; ``` ### END\_EVENT ```ts readonly END_EVENT: "END_EVENT" = 'END_EVENT'; ``` ### EVENT\_BASED\_GATEWAY ```ts readonly EVENT_BASED_GATEWAY: "EVENT_BASED_GATEWAY" = 'EVENT_BASED_GATEWAY'; ``` ### EVENT\_SUB\_PROCESS ```ts readonly EVENT_SUB_PROCESS: "EVENT_SUB_PROCESS" = 'EVENT_SUB_PROCESS'; ``` ### EXCLUSIVE\_GATEWAY ```ts readonly EXCLUSIVE_GATEWAY: "EXCLUSIVE_GATEWAY" = 'EXCLUSIVE_GATEWAY'; ``` ### INCLUSIVE\_GATEWAY ```ts readonly INCLUSIVE_GATEWAY: "INCLUSIVE_GATEWAY" = 'INCLUSIVE_GATEWAY'; ``` ### INTERMEDIATE\_CATCH\_EVENT ```ts readonly INTERMEDIATE_CATCH_EVENT: "INTERMEDIATE_CATCH_EVENT" = 'INTERMEDIATE_CATCH_EVENT'; ``` ### INTERMEDIATE\_THROW\_EVENT ```ts readonly INTERMEDIATE_THROW_EVENT: "INTERMEDIATE_THROW_EVENT" = 'INTERMEDIATE_THROW_EVENT'; ``` ### MANUAL\_TASK ```ts readonly MANUAL_TASK: "MANUAL_TASK" = 'MANUAL_TASK'; ``` ### MULTI\_INSTANCE\_BODY ```ts readonly MULTI_INSTANCE_BODY: "MULTI_INSTANCE_BODY" = 'MULTI_INSTANCE_BODY'; ``` ### PARALLEL\_GATEWAY ```ts readonly PARALLEL_GATEWAY: "PARALLEL_GATEWAY" = 'PARALLEL_GATEWAY'; ``` ### PROCESS ```ts readonly PROCESS: "PROCESS" = 'PROCESS'; ``` ### RECEIVE\_TASK ```ts readonly RECEIVE_TASK: "RECEIVE_TASK" = 'RECEIVE_TASK'; ``` ### SCRIPT\_TASK ```ts readonly SCRIPT_TASK: "SCRIPT_TASK" = 'SCRIPT_TASK'; ``` ### SEND\_TASK ```ts readonly SEND_TASK: "SEND_TASK" = 'SEND_TASK'; ``` ### SEQUENCE\_FLOW ```ts readonly SEQUENCE_FLOW: "SEQUENCE_FLOW" = 'SEQUENCE_FLOW'; ``` ### SERVICE\_TASK ```ts readonly SERVICE_TASK: "SERVICE_TASK" = 'SERVICE_TASK'; ``` ### START\_EVENT ```ts readonly START_EVENT: "START_EVENT" = 'START_EVENT'; ``` ### SUB\_PROCESS ```ts readonly SUB_PROCESS: "SUB_PROCESS" = 'SUB_PROCESS'; ``` ### TASK ```ts readonly TASK: "TASK" = 'TASK'; ``` ### UNKNOWN ```ts readonly UNKNOWN: "UNKNOWN" = 'UNKNOWN'; ``` ### UNSPECIFIED ```ts readonly UNSPECIFIED: "UNSPECIFIED" = 'UNSPECIFIED'; ``` ### USER\_TASK ```ts readonly USER_TASK: "USER_TASK" = 'USER_TASK'; ``` --- ## Variable: WaitStateTypeEnum ```ts const WaitStateTypeEnum: object; ``` The type of waiting state an element instance is in. ## Type Declaration ### CONDITION ```ts readonly CONDITION: "CONDITION" = 'CONDITION'; ``` ### JOB ```ts readonly JOB: "JOB" = 'JOB'; ``` ### MESSAGE ```ts readonly MESSAGE: "MESSAGE" = 'MESSAGE'; ``` ### SIGNAL ```ts readonly SIGNAL: "SIGNAL" = 'SIGNAL'; ``` ### TIMER ```ts readonly TIMER: "TIMER" = 'TIMER'; ``` ### USER\_TASK ```ts readonly USER_TASK: "USER_TASK" = 'USER_TASK'; ``` --- ## Variable: liveClock ```ts const liveClock: Clock; ``` The clock used when none is injected. --- ## Variable: liveRandom ```ts const liveRandom: RandomSource; ``` The live source: the platform generator. Production jitter stays random so a fleet of workers does not retry or start in lockstep. This is the single place the SDK runtime is allowed to read ambient randomness. --- ## TypeScript SDK API Reference # Camunda 8 Orchestration Cluster TypeScript SDK [![npm](https://img.shields.io/npm/v/@camunda8/orchestration-cluster-api)](https://www.npmjs.com/package/@camunda8/orchestration-cluster-api) [![npm downloads](https://img.shields.io/npm/dw/@camunda8/orchestration-cluster-api)](https://www.npmjs.com/package/@camunda8/orchestration-cluster-api) [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/camunda/orchestration-cluster-api-js/blob/main/LICENSE) [![GitHub release](https://img.shields.io/github/v/release/camunda/orchestration-cluster-api-js)](https://github.com/camunda/orchestration-cluster-api-js/releases) Type‑safe, promise‑based client for the Camunda 8 Orchestration Cluster REST API. ## Highlights - Strong TypeScript models (requests, responses, discriminated unions) - Branded key types to prevent mixing IDs at compile time - Optional request/response schema validation (Zod) via a single env variable - OAuth2 client‑credentials & Basic auth (token cache, early refresh, jittered retry, singleflight) - Optional mTLS (Node) with inline or \*\_PATH environment variables - Cancelable promises for all operations - Eventual consistency helper for polling endpoints - Immutable, deep‑frozen configuration accessible through a factory‑created client instance - Automatic body-level tenantId defaulting: if a request body supports an optional tenantId and you omit it, the SDK fills it from CAMUNDA_DEFAULT_TENANT_ID (path params are never auto-filled) - Automatic transient HTTP retry (429, 503, network) with exponential backoff + full jitter (configurable via CAMUNDA_SDK_HTTP_RETRY\*). Non-retryable 500s fail fast. - Per-method retry override: disable or customize retry policy on any individual API call without changing global settings ## Install ```bash npm install @camunda8/orchestration-cluster-api ``` Runtime support: - Node 20+ (native fetch & File; Node 18 needs global File polyfill) - Modern browsers (Chromium, Firefox, Safari) – global `fetch` & `File` available For older Node versions supply a fetch ponyfill AND a `File` shim (or upgrade). For legacy browsers, add a fetch polyfill (e.g. `whatwg-fetch`). ### Versioning This SDK has a different release cadence from the Camunda server. Features and fixes land in the SDK during a server release. The major version of the SDK signals a 1:1 type coherence with the server API for a Camunda minor release. SDK version `n.y.z` -> server version `8.n`, so the type surface of SDK version 9.y.z matches the API surface of Camunda 8.9. Using a later SDK version, for example: SDK version 10.y.z with Camunda 8.9, means that the SDK contains additive surfaces that are not guaranteed at runtime, and the compiler cannot warn of unsupported operations. Using an earlier SDK version, for example: SDK version 9.y.z with Camunda 8.10, results in slightly degraded compiler reasoning: exhaustiveness checks cannot be guaranteed by the compiler for any extended surfaces (principally, enums with added members). In the vast majority of use-cases, this will not be an issue; but you should be aware that using the matching SDK major version for the server minor version provides the strongest compiler guarantees about runtime reliability. **Recommended approach**: - Check the [CHANGELOG](https://github.com/camunda/orchestration-cluster-api-js/releases). - As a sanity check during server version upgrade, rebuild applications with the matching SDK major version to identify any affected runtime surfaces. ## Migrating from 8.8 SDK 9.x (for Camunda 8.9) introduces two categories of breaking type changes relative to SDK 8.x (for Camunda 8.8). Neither change affects runtime behavior — existing code that compiled against 8.x will run identically — but the compiler will flag type mismatches until you update. ### Search results: optional → required fields The search result types changed several fields from **optional** to **required**: **`SearchQueryPageResponse` (page metadata)** | Field | SDK 8.x (Camunda 8.8) | SDK 9.x (Camunda 8.9) | | ------------------- | ----------------------------- | ---------------------------------- | | `totalItems` | `totalItems?: number` | `totalItems: number` | | `hasMoreTotalItems` | `hasMoreTotalItems?: boolean` | `hasMoreTotalItems: boolean` | | `endCursor` | `endCursor?: EndCursor` | `endCursor: EndCursor \| null` | | `startCursor` | `startCursor?: StartCursor` | `startCursor: StartCursor \| null` | **`*SearchQueryResult` types (result containers)** | Field | SDK 8.x (Camunda 8.8) | SDK 9.x (Camunda 8.9) | | ------- | -------------------------------- | ------------------------------- | | `items` | `items?: T[]` | `items: T[]` | | `page` | `page?: SearchQueryPageResponse` | `page: SearchQueryPageResponse` | This reflects upstream OpenAPI spec changes where these fields are now always present in the response, with `null` indicating "no value" for cursors rather than being absent. **What to change**: Update code that checks for these fields using optional chaining or `undefined` comparisons. The `items` array and `page` object are now always present, so optional chaining on them is unnecessary: ```ts // Before (8.x) — checking for undefined if (result.page?.endCursor !== undefined) { nextPage(result.page.endCursor); } const count = result.items?.length ?? 0; // After (9.x) — page and items are always present; check cursors for null if (result.page.endCursor !== null) { nextPage(result.page.endCursor); } const count = result.items.length; ``` If you have a custom `PagedResponse` type, update its `page` shape to match: ```ts // Before (8.x) type PagedResponse = { items?: T[]; page?: { totalItems?: number; endCursor?: string; startCursor?: string; hasMoreTotalItems?: boolean; }; }; // After (9.x) type PagedResponse = { items: T[]; page: { totalItems: number; endCursor: EndCursor | null; startCursor: StartCursor | null; hasMoreTotalItems: boolean; }; }; ``` ### Branded key types for `tenantId` The `tenantId` field on request types (e.g. `CreateDeploymentData`) changed from `string` to the branded `TenantId` type. A plain `string` is no longer assignable: ```ts // Before (8.x) — plain string worked await camunda.createDeployment({ tenantId: "my-tenant", resources: [file], }); // After (9.x) — use the branded type helper await camunda.createDeployment({ tenantId: TenantId.assumeExists("my-tenant"), resources: [file], }); ``` `TenantId.assumeExists()` validates the string against the tenant ID pattern and returns a branded value. The branded value is just a string at runtime, but `assumeExists()` performs validation and can throw if the input is malformed. See [Branded Keys](#branded-keys) for more on this pattern. > **Tip**: If your tenant ID comes from a validated source (environment variable, config file), call `TenantId.assumeExists()` once at startup and pass the branded value throughout your application. ## Migrating from 8.9 SDK 10.x (for Camunda 8.10) promotes several identifier fields from plain `string` to **branded types**, changes the `getResourceContent` response from a string to an object, and makes `BatchOperationItemResponse.processInstanceKey` nullable. Nothing was removed or renamed, and the wire format is unchanged. **→ See [MIGRATION.md](https://github.com/camunda/orchestration-cluster-api-js/blob/main/MIGRATION.md) for the full guide**, including the complete list of affected fields. The common case is branding an identifier at the boundary: ```ts // v9 — plain strings were accepted // await camunda.assignRoleToGroup({ // roleId: 'developer', // groupId: 'engineering', // }); // v10 — use the branded type helpers at the boundary await camunda.assignRoleToGroup({ roleId: RoleId.assumeExists("developer"), groupId: GroupId.assumeExists("engineering"), }); ``` Each branded type has an `.assumeExists()` method that validates the string and returns the branded value. Validation runs at call time and can throw if the input is malformed, so call it once at the boundary (startup, config parsing, API response) and pass the branded value through your application. See [Branded Keys](#branded-keys) for more on this pattern. ## Quick Start (Zero‑Config – Recommended) Keep configuration out of application code. Let the factory read `CAMUNDA_*` variables from the environment (12‑factor style). This makes rotation, secret management, and environment promotion safer & simpler. ```ts // Zero‑config construction: reads CAMUNDA_* from process.env. If no configuration is present, defaults to Camunda 8 Run on localhost. const camunda = createCamundaClient(); const topology = await camunda.getTopology(); console.log("Brokers:", topology.brokers?.length ?? 0); ``` Typical `.env` (example): ```bash CAMUNDA_REST_ADDRESS=https://cluster.example # SDK will use https://cluster.example/v2/... unless /v2 already present # CAMUNDA_REST_ADDRESS_EXACT=true # optional (specialized): suppress the /v2 suffix (trailing slashes/whitespace are still normalized) — only for gateway/reverse-proxy base paths CAMUNDA_AUTH_STRATEGY=OAUTH CAMUNDA_CLIENT_ID=*** CAMUNDA_CLIENT_SECRET=*** CAMUNDA_DEFAULT_TENANT_ID= # optional: override default tenant resolution CAMUNDA_SDK_HTTP_RETRY_MAX_ATTEMPTS=4 # optional: total attempts (initial + 3 retries) CAMUNDA_SDK_HTTP_RETRY_BASE_DELAY_MS=100 # optional: base backoff (ms) CAMUNDA_SDK_HTTP_RETRY_MAX_DELAY_MS=2000 # optional: cap (ms) ``` > Prefer environment / secret manager injection over hard‑coding values in source. Treat the SDK like a leaf dependency: construct once near process start, pass the instance where needed. > **Why zero‑config?** > > - Separation of concerns: business code depends on an interface, not on secret/constants wiring. > - 12‑Factor alignment: config lives in the environment → simpler promotion (dev → staging → prod). > - Secret rotation & incident response: rotate credentials without a code change or redeploy of application containers built with baked‑in values. > - Immutable start: single hydration pass prevents drift / mid‑request mutations. > - Test ergonomics: swap an `.env.test` (or injected vars) without touching source; create multiple clients for multi‑tenant tests. > - Security review: fewer code paths handling secrets; scanners & vault tooling work at the boundary. > - Deploy portability: same artifact runs everywhere; only the environment differs. > - Observability clarity: configuration diffing is an ops concern, not an application code diff. ### Advanced: Programmatic Overrides Use only when you must supply or mutate configuration dynamically (e.g. multi‑tenant routing, tests, ephemeral preview environments) or in the browser. Keys mirror their `CAMUNDA_*` env names. ```ts const camunda = createCamundaClient({ config: { CAMUNDA_REST_ADDRESS: "https://cluster.example", CAMUNDA_AUTH_STRATEGY: "BASIC", CAMUNDA_BASIC_AUTH_USERNAME: "alice", CAMUNDA_BASIC_AUTH_PASSWORD: "secret", }, }); ``` ### Advanced: Custom Fetch Implementation Inject a custom `fetch` to add tracing, mock responses, instrumentation, circuit breakers, etc. ```ts const camunda = createCamundaClient({ fetch: (input, init) => { // inspect / modify request here return fetch(input, init); }, }); ``` ### Reconfiguration At Runtime (Rare) You can call `client.configure({ config: { ... } })` to re‑hydrate. The exposed `client.getConfig()` stays `Readonly` and deep‑frozen. Prefer creating a new client instead of mutating a shared one in long‑lived services. ## Validation This allows you to validate that requests to the API from your application and responses from the API have the expected types and shape declared in the type system. This protects your application from runtime bugs or errors in the type system leading to undefined states hitting your business logic. Recommended to use `fanatical` or `strict` in development and then switch to `strict` or `warn` in production. Or you can just YOLO it and leave it on `none` all the time. Controlled by `CAMUNDA_SDK_VALIDATION` (or `config` override). Grammar: ``` none | warn | strict | req:[,res:] | res:[,req:] = none|warn|strict|fanatical ``` Examples: ```bash CAMUNDA_SDK_VALIDATION=warn # warn on both CAMUNDA_SDK_VALIDATION=req:strict,res:warn # strict on requests, warn on responses CAMUNDA_SDK_VALIDATION=none ``` Behavior: - `none` - no validation performed - `warn` - emit warning on invalid shape - `strict` - fail on type mismatch or missing required fields - `fanatical` - fail on type mismatch, missing required fields, or unknown additional fields > **Note on `int64` fields**: The upstream OpenAPI spec declares some fields (e.g. `totalItems`, `timeout`, `timestamp`) as `integer` with `format: int64`. The TypeScript types map these to `number`. JSON responses also deserialize as `number` (with precision loss beyond `Number.MAX_SAFE_INTEGER`). The Zod schemas use `z.coerce.number().int()` for these fields, preserving the integer constraint while keeping the runtime type aligned with TypeScript. All validation modes (`none`, `warn`, `strict`, `fanatical`) return `number`. ## Per-Method Retry Override Every API method accepts an optional trailing `options` parameter that lets you override or disable the global retry policy for that single call. ### Disable Retry for a Single Call ```ts // This call will not retry on transient errors await camunda.completeJob({ jobKey }, { retry: false }); ``` ### Override Specific Retry Settings Pass a partial `HttpRetryPolicy` to override individual fields. Unspecified fields inherit from the global configuration. ```ts // More aggressive retry for this operation only await camunda.createProcessInstance( { processDefinitionId }, { retry: { maxAttempts: 8, maxDelayMs: 5000 } } ); // Minimal retry: single retry with short backoff await camunda.getTopology({ retry: { maxAttempts: 2, baseDelayMs: 50 } }); ``` ### How It Works | `options.retry` value | Behavior | | --------------------- | -------------------------------------------------------------------- | | omitted / `undefined` | Uses global policy (`CAMUNDA_SDK_HTTP_RETRY_*` env vars) | | `false` | Disables retry entirely (single attempt, no backoff) | | `{ maxAttempts: 5 }` | Merges with global policy — only the specified fields are overridden | The `HttpRetryPolicy` fields available for override: | Field | Type | Description | | ------------- | -------- | --------------------------------------- | | `maxAttempts` | `number` | Total attempts (initial + retries) | | `baseDelayMs` | `number` | Base delay for exponential backoff (ms) | | `maxDelayMs` | `number` | Maximum delay cap (ms) | ## Advanced HTTP Retry: Cockatiel Adapter (Optional) For advanced resilience patterns beyond per-method overrides — circuit breakers, timeouts, custom classification, combining policies — you can integrate [cockatiel](https://github.com/connor4312/cockatiel). > **Tip:** For most use cases, per-method retry override (above) is sufficient. Reach for Cockatiel when you need circuit breaking, hedging, or bulkhead controls. ### When To Use Cockatiel - You want circuit breaking, hedging, timeout, or bulkhead controls - You want to add custom classification (e.g. retry certain 5xx only on safe verbs) - You need to compose multiple resilience policies together ### Disable Built‑In HTTP Retries Set `CAMUNDA_SDK_HTTP_RETRY_MAX_ATTEMPTS=1` so the SDK does only the initial attempt; then wrap operations with cockatiel. ### Minimal Example (Single Operation) ```ts const client = createCamundaClient({ config: { CAMUNDA_REST_ADDRESS: "https://cluster.example", CAMUNDA_AUTH_STRATEGY: "NONE", CAMUNDA_SDK_HTTP_RETRY_MAX_ATTEMPTS: 1, // disable SDK automatic retries } as any, }); // Policy: up to 5 attempts total (1 + 4 retries) with exponential backoff & jitter const policy = retry(handleAll, { maxAttempts: 5, backoff: new ExponentialBackoff({ initialDelay: 100, maxDelay: 2000, jitter: true, }), }); // Wrap getTopology const origGetTopology = client.getTopology.bind(client); client.getTopology = (() => policy.execute(() => origGetTopology())) as any; const topo = await client.getTopology(); console.log(topo.brokers?.length); ``` ### Bulk Wrapping All Operations ```ts const client = createCamundaClient({ config: { CAMUNDA_REST_ADDRESS: "https://cluster.example", CAMUNDA_AUTH_STRATEGY: "OAUTH", CAMUNDA_CLIENT_ID: process.env.CAMUNDA_CLIENT_ID, CAMUNDA_CLIENT_SECRET: process.env.CAMUNDA_CLIENT_SECRET, CAMUNDA_OAUTH_URL: process.env.CAMUNDA_OAUTH_URL, CAMUNDA_TOKEN_AUDIENCE: "zeebe.camunda.io", CAMUNDA_SDK_HTTP_RETRY_MAX_ATTEMPTS: 1, } as any, }); const retryPolicy = retry(handleAll, { maxAttempts: 4, backoff: new ExponentialBackoff({ initialDelay: 150, maxDelay: 2500, jitter: true, }), }); const skip = new Set([ "logger", "configure", "getConfig", "withCorrelation", "deployResourcesFromFiles", ]); for (const key of Object.keys(client)) { const val: any = (client as any)[key]; if (typeof val === "function" && !key.startsWith("_") && !skip.has(key)) { const original = val.bind(client); (client as any)[key] = (...a: any[]) => retryPolicy.execute(() => original(...a)); } } // Now every public operation is wrapped. ``` ## Support Logger (Node Only) For diagnostics during support interactions you can enable an auxiliary file logger that captures a sanitized snapshot of environment & configuration plus selected runtime events. Enable by setting one of: ```bash CAMUNDA_SUPPORT_LOG_ENABLED=true # canonical ``` Optional override for output path (default is `./camunda-support.log` in the current working directory): ```bash CAMUNDA_SUPPORT_LOG_FILE_PATH=/var/log/camunda-support.log ``` Behavior: - File is created eagerly on first client construction (one per process; if the path exists a numeric suffix is appended to avoid clobbering). - Initial preamble includes SDK package version, timestamp, and redacted environment snapshot. - Secrets (client secret, passwords, mTLS private key, etc.) are automatically masked or truncated. - Designed to be low‑impact: append‑only, newline‑delimited JSON records may be added in future releases for deeper inspection (current version writes the preamble only unless additional events are wired). Recommended usage: ```bash CAMUNDA_SUPPORT_LOG_ENABLED=1 CAMUNDA_SDK_LOG_LEVEL=debug node app.js ``` Keep the file only as long as needed for troubleshooting; it may contain sensitive non‑secret operational metadata. Do not commit it to version control. To disable, unset the env variable or set `CAMUNDA_SUPPORT_LOG_ENABLED=false`. Refer to `./docs/CONFIG_REFERENCE.md` for the full list of related environment variables. ### Custom Classification Example Retry only network errors + 429/503, plus optionally 500 on safe GET endpoints you mark: ```ts const classify = handleWhen((err) => { const status = (err as any)?.status; if (status === 429 || status === 503) return true; if (status === 500 && (err as any).__opVerb === "GET") return true; // custom tagging optional return err?.name === "TypeError"; // network errors from fetch }); const policy = retry(classify, { maxAttempts: 5, backoff: new ExponentialBackoff({ initialDelay: 100, maxDelay: 2000, jitter: true, }), }); ``` ### Notes - Keep SDK retries disabled to prevent duplicate layers. - SDK synthesizes `Error` objects with a `status` for retry-significant HTTP responses (429, 503, 500), enabling classification. - You can tag errors (e.g. assign `err.__opVerb`) in a wrapper if verb-level logic is needed. - For per-operation retry customization without external dependencies, use the built-in [per-method retry override](#per-method-retry-override) instead. > Combine cockatiel retry with a circuit breaker, timeout, or bulkhead policy for more robust behavior in partial outages. ## Global Backpressure (Adaptive Concurrency) The client now includes an internal global backpressure manager that adaptively throttles the number of _initiating_ in‑flight operations when the cluster signals resource exhaustion. It complements (not replaces) per‑request HTTP retry. ### Signals Considered An HTTP response is treated as a backpressure signal when it is classified retryable **and** matches one of: - `429` (Too Many Requests) – always - `503` with `title === "RESOURCE_EXHAUSTED"` - `500` whose RFC 9457 / 7807 `detail` text contains `RESOURCE_EXHAUSTED` All other 5xx / 503 variants are treated as non‑retryable (fail fast) and do **not** influence the adaptive gate. ### How It Works 1. Normal state starts with effectively unlimited concurrency (no global semaphore enforced) until the first backpressure event. 2. On the first signal the manager boots with a provisional concurrency cap (e.g. 16) and immediately reduces it (soft state). 3. Repeated consecutive signals escalate severity to `severe`, applying a stronger reduction factor. 4. Successful (non‑backpressure) completions trigger passive recovery checks that gradually restore permits over time if the system stays quiet. 5. Quiet periods (no signals for a configurable decay interval) downgrade severity (`severe → soft → healthy`) and reset the consecutive counter when fully healthy. The policy is intentionally conservative: it only engages after genuine pressure signals and recovers gradually to avoid oscillation. ### Exempt Operations Certain operations that help drain work or complete execution are _exempt_ from gating so they are never queued behind initiating calls: - `completeJob` - `failJob` - `throwJobError` - `completeUserTask` These continue immediately even during severe backpressure to promote system recovery. ### Interaction With HTTP Retry Per‑request retry still performs exponential backoff + jitter for classified transient errors. The adaptive concurrency layer sits _outside_ retry: 1. A call acquires a permit (unless exempt) before its first attempt. 2. Internal retry re‑attempts happen _within_ the held permit. 3. On final success the permit is released and a healthy hint is recorded (possible gradual recovery). 4. On final failure (non‑retryable or attempts exhausted) the permit is released; a 429 on the terminal attempt still records backpressure. This design prevents noisy churn (permits would not shrink/expand per retry attempt) and focuses on admission control of distinct logical operations. ### Observability Enable debug logging (`CAMUNDA_SDK_LOG_LEVEL=debug` or `trace`) to see events emitted under the scoped logger `bp` (e.g. `backpressure.permits.scale`, `backpressure.permits.recover`, `backpressure.severity`). These are trace‑level; use `trace` for the most granular insight. ### Configuration Current release ships with defaults tuned for conservative behavior. Adaptive gating is controlled by a profile (no separate boolean toggle). Use the `LEGACY` profile for observe‑only mode (no global gating, still records severity). Otherwise choose a tuning profile and optionally override individual knobs. Tuning environment variables (all optional; defaults shown): | Variable | Default | Description | | ----------------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------- | | `CAMUNDA_SDK_BACKPRESSURE_INITIAL_MAX` | `16` | Bootstrap concurrency cap once the first signal is observed (null/unlimited before any signal). | | `CAMUNDA_SDK_BACKPRESSURE_SOFT_FACTOR` | `70` | Percentage multiplier applied on each soft backpressure event (70 => 0.70x permits). | | `CAMUNDA_SDK_BACKPRESSURE_SEVERE_FACTOR` | `50` | Percentage multiplier when entering or re-triggering in severe state. | | `CAMUNDA_SDK_BACKPRESSURE_RECOVERY_INTERVAL_MS` | `1000` | Interval between passive recovery checks. | | `CAMUNDA_SDK_BACKPRESSURE_RECOVERY_STEP` | `1` | Permits regained per recovery interval until reaching the bootstrap cap. | | `CAMUNDA_SDK_BACKPRESSURE_DECAY_QUIET_MS` | `2000` | Quiet period to downgrade severity (`severe→soft→healthy`). | | `CAMUNDA_SDK_BACKPRESSURE_FLOOR` | `1` | Minimum concurrency floor while degraded. | | `CAMUNDA_SDK_BACKPRESSURE_SEVERE_THRESHOLD` | `3` | Consecutive signals required to enter severe state. | | `CAMUNDA_SDK_BACKPRESSURE_PROFILE` | `BALANCED` | Preset profile: BALANCED, CONSERVATIVE, AGGRESSIVE, LEGACY (LEGACY = observe-only, no gating). | #### Profiles Profiles supply coordinated defaults when you don't want to reason about individual knobs. Any explicitly set knob env var overrides the profile value. | Profile | initialMax | softFactor% | severeFactor% | recoveryIntervalMs | recoveryStep | quietDecayMs | floor | severeThreshold | Intended Use | | ------------ | ---------- | ----------- | ------------- | ------------------ | ------------ | ------------ | ----- | --------------- | --------------------------------------------------------------- | | BALANCED | 16 | 70 | 50 | 1000 | 1 | 2000 | 1 | 3 | General workloads with moderate spikes | | CONSERVATIVE | 12 | 60 | 40 | 1200 | 1 | 2500 | 1 | 2 | Protect cluster under tighter capacity / cost constraints | | AGGRESSIVE | 24 | 80 | 60 | 800 | 2 | 1500 | 2 | 4 | High throughput scenarios aiming to utilize headroom quickly | | LEGACY | n/a | 70 | 50 | 1000 | 1 | 2000 | 1 | 3 | Observe signals only (severity metrics) without adaptive gating | Select via: ```bash CAMUNDA_SDK_BACKPRESSURE_PROFILE=AGGRESSIVE ``` Then optionally override a single parameter, e.g.: ```bash CAMUNDA_SDK_BACKPRESSURE_PROFILE=AGGRESSIVE CAMUNDA_SDK_BACKPRESSURE_INITIAL_MAX=32 ``` If the profile name is unrecognized the SDK falls back to BALANCED silently (future versions may emit a warning). Factors use integer percentages to avoid floating point drift in env parsing; the SDK converts them to multipliers internally (e.g. `70` -> `0.7`). If you have concrete tuning needs, open an issue describing workload patterns (operation mix, baseline concurrency, observed broker limits) to help prioritize which knobs to surface. ### What Should I Set? If you're unsure about your workload shape, **don't set anything**. The default BALANCED profile activates automatically and outperforms no-gating (LEGACY) in most scenarios — on raw throughput alone, not just error reduction. Benchmark results against a single-node local cluster with multiple independent clients (no shared state between them): | Scenario | BALANCED | LEGACY (no gating) | | ----------------------------- | --------------- | ------------------ | | Single-client (1K processes) | **80.1 ops/s** | 67.8 ops/s | | Single-client sustained (10K) | **119.6 ops/s** | 87.8 ops/s | | Multi-client 3+2 spike | **86.3 ops/s** | 48.0 ops/s | | Stress 8 clients ×1000 | 76.3 ops/s | **106.4 ops/s** | BALANCED wins 3 of 4 on pure throughput. The only scenario where LEGACY is faster is extreme overload (800 concurrent requests against a single broker) — and in that case LEGACY accumulates 44,505 errors vs BALANCED's 15,527. The default just works. ## Typed Variable Map (DTO-driven search) `searchVariablesAsDto` fetches process variables and binds them to a [Zod](https://zod.dev) schema that acts as the DTO. The schema's keys are the exact variable names to fetch, and its shape drives validation. Only the declared variables are queried (via a `name $in [...]` filter), so memory stays bound by the DTO shape rather than the total number of variables on the instance. Results are paged internally until every declared variable is found or the result set is exhausted. The returned `VariableMap` offers two access modes: - **Lenient** — `has(name)` / `get(name)` for defensive reads that never throw on missing variables. - **Strict** — `validate()` returns a fully-typed object, or throws a `ZodError` when a required variable is missing or malformed. If a declared variable is found at more than one scope (for example a local variable shadowing a process-level one), the search throws a `VariableScopeCollisionError` rather than silently picking one. Pass an explicit `scopeKey` to disambiguate. ```ts // The Zod schema is the DTO: its keys are the variable names to fetch, and its // shape drives validation. Only these declared variables are queried, so memory // stays bound by the DTO — not by the total number of variables on the instance. const OrderVariables = z.object({ orderId: z.string(), // required amount: z.number().optional(), // optional }); const map = await camunda.searchVariablesAsDto(OrderVariables, { processInstanceKey, }); // Lenient access: defensive reads that never throw on missing variables. if (map.has("amount")) { console.log("Amount:", map.get("amount")); } // Strict access: returns a fully-typed object, or throws a ZodError when a // required variable is missing or malformed. const order = map.validate(); // { orderId: string; amount?: number } console.log("Order:", order.orderId); ``` ## Job Workers (Polling API) The SDK provides a lightweight polling job worker for service task job types using `createJobWorker`. It activates jobs in batches (respecting a concurrency limit), validates variables (optional), and offers action helpers on each job. ### Minimal Example ```ts const client = createCamundaClient(); // Define schemas (optional) const Input = z.object({ orderId: z.string() }); const Output = z.object({ processed: z.boolean() }); const worker = client.createJobWorker({ jobType: "process-order", maxParallelJobs: 10, jobTimeoutMs: 15_000, // long‑poll timeout (server side requestTimeout) pollIntervalMs: 100, // delay between polls when no jobs / at capacity // Optional: only fetch specific variables during activation fetchVariables: ["orderId"], inputSchema: Input, // validates incoming variables if validateSchemas true outputSchema: Output, // validates variables passed to complete(...) validateSchemas: true, // set false for max throughput (skip Zod) autoStart: true, // default true; start polling immediately startupJitterMaxSeconds: 5, // random delay up to 5s before first poll (default 0) jobHandler: (job) => { // Access typed variables const vars = job.variables; // inferred from Input schema console.log(`Processing order: ${vars.orderId}`); // Do work... return job.complete({ processed: true }); }, }); // Later, on shutdown: process.on("SIGINT", () => { worker.stop(); }); ``` Note on variable fetching: - `fetchVariables: string[]` limits variables returned on activated jobs to the specified keys. If omitted, all visible variables at activation scope are returned. This maps to the REST API field `fetchVariable`. TypeScript inference: - When you provide `inputSchema`, the type of `fetchVariables` is constrained to the keys of the inferred `variables` type from that schema. Example: ```ts const Input = z.object({ orderId: z.string(), amount: z.number() }); client.createJobWorker({ jobType: "process-order", maxParallelJobs: 5, jobTimeoutMs: 30_000, inputSchema: Input, // Only allows 'orderId' | 'amount' here at compile-time fetchVariables: ["orderId", "amount"], jobHandler: async (job) => job.complete(), }); ``` - Without `inputSchema`, `fetchVariables` defaults to `string[]`. ### Job Handler Semantics Your `jobHandler` must ultimately invoke exactly one of: - `job.complete(variables?, result?)` OR `job.complete()` - `job.fail({ errorMessage, retries?, retryBackoff? })` - `job.cancelWorkflow({})` (cancels the process instance) - `job.error({ errorCode, errorMessage? })` (throws a business error) - `job.ignore()` (marks as done locally without reporting to broker – can be used for decoupled flows) Each action returns an opaque unique symbol receipt (`JobActionReceipt`). The handler's declared return type (`Promise`) is intentional: Why this design: - Enforces a single terminal code path: every successful handler path should end by returning the sentinal obtained by invoking an action. - Enables static reasoning: TypeScript can identify if your handler has a code path that does not acknowledge the job (catch unintended behavior early). - Makes test assertions simple: e.g. `expect(await job.complete()).toBe(JobActionReceipt)`. Acknowledgement lifecycle: - Calling any action (`complete`, `fail`, `cancelWorkflow`, `ignore`) sets `job.acknowledged = true` internally. This surfaces multiple job resolution code paths at runtime. - If the handler resolves (returns the symbol manually or via an action) without any acknowledgement having occurred, the worker logs `job.handler.noAction` and locally marks the job finished WITHOUT informing the broker (avoids a leak of the in-memory slot, but the broker will eventually time out and re-dispatch the job). Recommended usage: - Always invoke an action; if you truly mean to skip broker acknowledgement (for example: forwarding a job to another system which will complete it) use `job.ignore()`. Example patterns: ```ts // GOOD: explicit completion return job.complete({ variables: { processed: true } }); // GOOD: No-arg completion example, sentinel stored for ultimate return // biome-ignore lint/correctness/noUnreachable: intentional — showing multiple completion patterns const ack = await job.complete(); // ... return ack; // GOOD: explicit ignore const ack2 = await job.ignore(); ``` ### Deterministic Time (`job.clock`) The SDK resolves its own cadence — worker poll intervals, retry backoff, eventual-consistency polling, backpressure decay — through an injectable clock. Pinning that clock runs all of it on virtual time, so tests that would otherwise wait out a 30-second poll finish immediately. The clock is configured on the client and available as `client.clock`. Handlers reach it as `job.clock`, a narrowed view exposing only `now()` and `sleep(ms, signal?)` — `deadline` is withheld because a handler that built one against a pinned clock would hang rather than time out: ```ts const startedAt = job.clock.now(); // A short back-off around a flaky dependency. Waiting here rather than on // setTimeout means a test that pins the client's clock also drives the handler. await job.clock.sleep(250); return job.complete({ variables: { waitedMs: job.clock.now() - startedAt } }); ``` Read and wait through `job.clock` rather than `Date.now()` / `setTimeout`, and a test that pins the client's clock drives your handler too. `job.clock.sleep` is for **short in-handler coordination** — spacing retries within one job, backing off around a flaky dependency. Long or business-meaningful waits belong in the process as BPMN timers, where they survive a crash and are visible to operations. Pass `createTestClock()` to pin the clock in your own tests: ```ts // Pin the client's clock and the SDK's own cadence runs on virtual time: poll intervals, // retry backoff and backpressure decay all settle without waiting in real time. const clock = createTestClock({ start: 0, autoAdvance: false }); const client = createCamundaClient({ clock }); // Nothing settles until the test moves time, so start the wait and advance into it. const waiting = client.clock.sleep(30_000); await clock.advance(30_000); await waiting; console.log(client.clock.now()); // 30000 console.log(clock.sleeps); // [30000] — every duration the SDK asked to wait ``` `autoAdvance` defaults to `true`, where each sleep settles itself on the next macrotask having moved time to its wake point — the SDK's loops make progress without the test driving them. Set it to `false`, as above, when you need to assert on state _between_ two waits. #### Binding the SDK to the engine clock `createTestClock` pins the SDK in isolation: the engine carries on at real time. When you are testing against a live engine, `createEngineClock` binds the two together so they advance as one — `sleep` moves engine time forward via `PUT /clock` instead of waiting: ```ts // Bind the SDK's cadence to the engine's own clock. `sleep` no longer waits — it moves // engine time forward — so a worker polling for something that never arrives advances the // engine instead of burning real seconds. // // Two clients, deliberately. `client` issues the pins and must stay on the live clock: // HTTP retry sleeps on whatever clock its client was given, so pointing the engine clock // at its own driver would have a failed pin back off through `sleep`, which issues another // pin, and so on. const client = createCamundaClient(); const clock = createEngineClock(client, { start: Date.now() }); const pinned = createCamundaClient({ clock }); await clock.pin(Date.now()); try { // A minute of engine time. BPMN timers due inside it fire; the test does not wait. await pinned.clock.sleep(60_000); } finally { await clock.reset(); // hand the engine back to real time } ``` This is what makes a worker loop deterministic end to end: the poll interval _drives_ engine time rather than racing it, so a test that would spend a real minute waiting on something that never becomes ready finishes as fast as the requests complete. > [!WARNING] > Pinning is global to the cluster. Only point an engine clock at an engine you own — > never a shared environment. Always `reset()` in a `finally`. > [!IMPORTANT] > The client you hand to `createEngineClock` must not itself be configured with that clock. > HTTP retry backs off on whatever clock its client was given, so a self-referential setup > would have a failed `pinClock` retry through `sleep`, which issues another `pinClock`. > Keep the driving client on the live clock, as in the example above. Prefer `createTestClock` over hand-writing a `Clock`. The contract has clauses that are easy to get subtly wrong — most notably that `sleep` must not settle in a microtask, because the worker schedules its next poll on resolution and would otherwise spin. Two things deliberately stay on real time even when the clock is pinned, so that pinning it cannot hang a process: **liveness bounds** (shutdown drain, request and config-fetch timeouts) and **observational timestamps** (log, telemetry and support-bundle records). ### Job Corrections (User Task Listeners) When a job worker handles a [user task listener](https://docs.camunda.io/docs/components/concepts/user-task-listeners/), it can correct task properties (assignee, due date, candidate groups, etc.) by passing a `result` to `job.complete()`: ```ts const worker = client.createJobWorker({ jobType: "io.camunda:userTaskListener", jobTimeoutMs: 30_000, maxParallelJobs: 5, jobHandler: async (job) => { const result: JobResult = { type: "userTask", corrections: { assignee: "corrected-user", priority: 80, }, }; return job.complete({}, result); }, }); ``` To deny a task completion (reject the work): ```ts return job.complete( {}, { type: "userTask", denied: true, deniedReason: "Insufficient documentation", } ); ``` | Correctable attribute | Type | Clear value | | --------------------- | ------------------- | ----------------- | | `assignee` | `string` | Empty string `""` | | `dueDate` | `string` (ISO 8601) | Empty string `""` | | `followUpDate` | `string` (ISO 8601) | Empty string `""` | | `candidateUsers` | `string[]` | Empty array `[]` | | `candidateGroups` | `string[]` | Empty array `[]` | | `priority` | `number` (0–100) | — | Omitting an attribute or passing `null` preserves the persisted value. ### Concurrency & Backpressure Set `maxParallelJobs` to the maximum number of jobs you want actively processing concurrently. The worker will long‑poll for up to the remaining capacity each cycle. Global backpressure (adaptive concurrency) still applies to the underlying REST calls; activation itself is a normal operation. ### Validation If `validateSchemas` is true: - Incoming `variables` are parsed with `inputSchema` (fail => job is failed with a validation error message). - Incoming `customHeaders` parsed with `customHeadersSchema` if provided. - Completion payload `variables` parsed with `outputSchema` (warns & proceeds on failure). ### Graceful Shutdown Use `await worker.stopGracefully({ waitUpToMs?, checkIntervalMs? })` to drain without force‑cancelling the current activation request. ```ts // Attempt graceful drain for up to 8 seconds const { remainingJobs, timedOut } = await worker.stopGracefully({ waitUpToMs: 8000, }); if (timedOut) { console.warn("Graceful stop timed out; remaining jobs:", remainingJobs); } ``` Behavior: - Stops scheduling new polls immediately. - Lets any in‑flight activation finish (not cancelled proactively). - Waits for active jobs to acknowledge (complete/fail/cancelWorkflow/ignore). - On timeout: falls back to hard stop semantics (cancels activation) and logs `worker.gracefulStop.timeout` at debug. For immediate termination call `worker.stop()` (or `client.stopAllWorkers()`) which cancels the in‑flight activation if present. Activation cancellations during stop are logged at debug (`activation.cancelled`) instead of error noise. ### Multiple Workers You can register multiple workers on a single client instance—one per job type is typical. The client exposes `client.getWorkers()` for inspection and `client.stopAllWorkers()` for coordinated shutdown. ### Startup Jitter When deploying multiple application instances simultaneously (e.g. a rolling restart or scale-up), all workers start polling at the same time and can saturate the server with activation requests. Set `startupJitterMaxSeconds` to spread out the initial poll across a random window: ```ts client.createJobWorker({ jobType: "process-order", maxParallelJobs: 10, jobTimeoutMs: 30_000, startupJitterMaxSeconds: 5, // each instance delays 0–5s before first poll jobHandler: async (job) => job.complete(), }); ``` A value of `0` (the default) means no delay. ### Heritable Worker Defaults When running many workers with the same base configuration, you can set global defaults via environment variables (or equivalent keys in `CamundaOptions.config`). These apply to every worker created by the client (both `createJobWorker` and `createThreadedJobWorker`) unless the individual worker config explicitly overrides them. | Environment Variable | Worker Config Field | Type | | ------------------------------------------- | ------------------------- | ------ | | `CAMUNDA_WORKER_TIMEOUT` | `jobTimeoutMs` | number | | `CAMUNDA_WORKER_MAX_CONCURRENT_JOBS` | `maxParallelJobs` | number | | `CAMUNDA_WORKER_REQUEST_TIMEOUT` | `pollTimeoutMs` | number | | `CAMUNDA_WORKER_NAME` | `workerName` | string | | `CAMUNDA_WORKER_STARTUP_JITTER_MAX_SECONDS` | `startupJitterMaxSeconds` | number | **Precedence:** explicit worker config value > `CAMUNDA_WORKER_*` (from environment variables or `CamundaOptions.config` overrides) > hardcoded default (where applicable). Example — set defaults via environment: ```bash export CAMUNDA_WORKER_TIMEOUT=30000 export CAMUNDA_WORKER_MAX_CONCURRENT_JOBS=8 export CAMUNDA_WORKER_NAME=order-service ``` ```ts // Workers inherit timeout, concurrency, and name from environment const w1 = client.createJobWorker({ jobType: "validate-order", jobHandler: async (job) => job.complete(), }); const w2 = client.createJobWorker({ jobType: "ship-order", jobHandler: async (job) => job.complete(), }); // Per-worker override: this worker uses 32 concurrent jobs instead of the global 8 const w3 = client.createJobWorker({ jobType: "bulk-import", maxParallelJobs: 32, jobHandler: async (job) => job.complete(), }); ``` You can also pass defaults programmatically via the client constructor: ```ts const client = createCamundaClient({ config: { CAMUNDA_WORKER_TIMEOUT: 30000, CAMUNDA_WORKER_MAX_CONCURRENT_JOBS: 8, }, }); ``` ### Receipt Type (Unique Symbol) Action methods return a unique symbol (not a string) to avoid accidental misuse and allow internal metrics. If you store the receipt, annotate its type as `JobActionReceipt` to preserve uniqueness: ```ts const receipt: JobActionReceipt = await job.complete({ processed: true }); ``` If you ignore the return value you don’t need to import the symbol. ### When Not To Use The Worker - Extremely latency‑sensitive tasks where a push mechanism or streaming protocol is required. - Massive fan‑out requiring custom partitioning strategies (implement a custom activator loop instead). - Browser environments (long‑lived polling + secret handling often unsuitable). For custom strategies you can still call `client.activateJobs(...)`, manage concurrency yourself, and use `completeJob` / `failJob` directly. ### Guarantees & Caveats - Never increases latency for healthy clusters (no cap until first signal). - Cannot create fairness across multiple _processes_; it is per client instance in a single process. Scale your worker pool with that in mind. - Not a replacement for server‑side quotas or external rate limiters—it's a cooperative adaptive limiter. ### Opt‑Out To bypass adaptive concurrency while still collecting severity metrics use: ```bash CAMUNDA_SDK_BACKPRESSURE_PROFILE=LEGACY ``` This reverts to only per‑request retry for transient errors (no global gating) while keeping observability. ### Inspecting State Programmatically Call `client.getBackpressureState()` to obtain: ```ts const state = client.getBackpressureState(); // state.severity: 'healthy' | 'soft' | 'severe' // state.consecutive: number — consecutive backpressure signals observed // state.permitsMax: number | null — current concurrency cap (null => unlimited/not engaged) // state.permitsCurrent: number — currently acquired permits // state.waiters: number — queued operations waiting for a permit ``` ### Threaded Job Workers (Node.js Only) For CPU-intensive job handlers, `createThreadedJobWorker` offloads handler execution to a pool of Node.js `worker_threads`. Polling and I/O remain on the main event loop, while handler logic runs in parallel threads — dramatically improving throughput when the handler does CPU-bound work (JSON processing, validation, transformation, cryptography). #### When to use - Your handler spends significant time on CPU work (not just waiting for HTTP responses) - You observe that a single-threaded worker saturates one CPU core while throughput plateaus - You need to process more jobs per second without deploying additional instances If your handler is mostly I/O-bound (HTTP calls, database queries), the standard `createJobWorker` is sufficient. #### Handler module The handler must be a **separate file** (not an inline function) that exports a default async function: ```ts // my-handler.ts (or my-handler.js) const handler: ThreadedJobHandler = async (job, client) => { const { orderId } = job.variables; // CPU-intensive work here... const result = heavyComputation(orderId); return job.complete({ result }); }; export default handler; ``` Typing your handler as `ThreadedJobHandler` gives full intellisense for `job` (variables, action methods like `complete()`, `fail()`, `error()`) and `client` (every `CamundaClient` API method). The handler receives two arguments: 1. **`job`** — a proxy with the same shape as a regular job worker job (`variables`, `customHeaders`, `jobKey`, plus action methods: `complete()`, `fail()`, `error()`, `cancelWorkflow()`, `ignore()`) 2. **`client`** — a proxy to the `CamundaClient` on the main thread. You can call any SDK method (e.g. `client.publishMessage(...)`, `client.createProcessInstance(...)`) and it will be forwarded to the main thread and executed there. #### Minimal example ```ts const client = createCamundaClient(); const worker = client.createThreadedJobWorker({ jobType: "cpu-heavy-task", handlerModule: path.join( path.dirname(fileURLToPath(import.meta.url)), "my-handler.js" ), maxParallelJobs: 32, jobTimeoutMs: 30_000, }); ``` #### Configuration `createThreadedJobWorker` accepts all the same options as `createJobWorker` (except `jobHandler`), plus: | Option | Type | Default | Description | | ---------------- | -------- | --------------------------- | ---------------------------------------------------------------- | | `handlerModule` | `string` | (required) | Path to handler module (absolute or relative to `process.cwd()`) | | `threadPoolSize` | `number` | `os.availableParallelism()` | Number of worker threads in the pool | Other familiar options: `jobType`, `maxParallelJobs`, `jobTimeoutMs`, `pollIntervalMs`, `pollTimeoutMs`, `fetchVariables`, `inputSchema`, `outputSchema`, `customHeadersSchema`, `validateSchemas`, `autoStart`, `startupJitterMaxSeconds`, `workerName`. #### Lifecycle Threaded workers integrate with the same lifecycle as regular workers: ```ts // Returned by getWorkers() const allWorkers = client.getWorkers(); // Stopped by stopAllWorkers() client.stopAllWorkers(); ``` ```ts // Graceful shutdown (waits for in-flight jobs to finish) const { timedOut, remainingJobs } = await worker.stopGracefully({ waitUpToMs: 10_000, }); ``` #### Pool stats ```ts worker.poolSize; // number of threads worker.busyThreads; // threads currently processing a job worker.activeJobs; // total jobs dispatched but not yet completed ``` #### How it works 1. The main thread polls `activateJobs` using the same mechanism as `createJobWorker` 2. Activated jobs are serialized and dispatched to an idle thread via `MessageChannel` 3. The thread loads the handler module (lazy, on first job), creates a proxy for `job` action methods and `client` API calls 4. Action methods (`job.complete()`, `job.fail()`, etc.) and client calls are forwarded back to the main thread over the `MessagePort` and executed there 5. The result is relayed back, and the thread is marked idle for the next job #### Constraints - **Node.js only**: `worker_threads` is not available in browsers or Deno - **Handler must be a file module**: Inline functions cannot be transferred to threads - **Job variables must be JSON-serializable**: Functions and class instances on the job are stripped during transfer - **Client calls are async round-trips**: Each `client.xyz()` call crosses a thread boundary, adding a small amount of latency per call --- ## Authentication Set `CAMUNDA_AUTH_STRATEGY` to `NONE` (default), `BASIC`, or `OAUTH`. Basic: ``` CAMUNDA_AUTH_STRATEGY=BASIC CAMUNDA_BASIC_AUTH_USERNAME=alice CAMUNDA_BASIC_AUTH_PASSWORD=supersecret ``` OAuth (client credentials): ``` CAMUNDA_AUTH_STRATEGY=OAUTH CAMUNDA_CLIENT_ID=yourClientId CAMUNDA_CLIENT_SECRET=yourSecret CAMUNDA_OAUTH_URL=https://idp.example/oauth/token # if required by your deployment ``` Optional audience / retry / timeout vars are also read if present (see generated config reference). Auth helper features (automatic inside the client): - Disk + memory token cache - Early refresh with skew handling - Exponential backoff & jitter - Singleflight suppression of concurrent refreshes - Hook: `client.onAuthHeaders(h => ({ ...h, 'X-Trace': 'abc' }))` - Force refresh: `await client.forceAuthRefresh()` - Clear caches: `client.clearAuthCache({ disk: true, memory: true })` ### Token Caching & Persistence The SDK always keeps the active OAuth access token in memory. Optional disk persistence (Node only) is enabled by setting: ```bash CAMUNDA_OAUTH_CACHE_DIR=/path/to/cache ``` When present and running under Node, each distinct credential context (combination of `oauthUrl | clientId | audience | scope`) is hashed to a filename: ``` /camunda_oauth_token_cache_.json ``` Writes are atomic (`.tmp` + rename) and use file mode `0600` (owner read/write). On process start the SDK attempts to load the persisted file to avoid an unnecessary token fetch; if the token is near expiry it will still perform an early refresh (5s skew window plus additional safety buffer based on 5% or 30s minimum). Clearing / refreshing: - Programmatic clear: `client.clearAuthCache({ disk: true, memory: true })` - Memory only: `client.clearAuthCache({ memory: true, disk: false })` - Force new token (ignores freshness): `await client.forceAuthRefresh()` Disable disk persistence by simply omitting `CAMUNDA_OAUTH_CACHE_DIR` (memory cache still applies). For short‑lived or serverless functions you may prefer no disk cache to minimize I/O; for long‑running workers disk caching reduces cold‑start latency and load on the identity provider across restarts / rolling deploys. Security considerations: - Ensure the directory has restrictive ownership/permissions; the SDK creates files with `0600` but will not alter parent directory permissions. - Tokens are bearer credentials; treat the directory like a secrets store and avoid including it in container image layers or backups. - If you rotate credentials (client secret) the filename hash changes; old cache files become unused and can be pruned safely. Browser usage: There is no disk concept—if executed in a browser the SDK (when strategy OAUTH) attempts to store the token in `sessionStorage` (tab‑scoped). Closing the tab clears the cache; a new tab will fetch a fresh token. If you need a custom persistence strategy (e.g. Redis / encrypted keychain), wrap the client and periodically call `client.forceAuthRefresh()` while storing and re‑injecting the token via a headers hook; first measure whether the built‑in disk cache already meets your needs. ## Self-signed TLS / mTLS (Node only) The SDK supports custom TLS certificates via environment variables. This is useful for: - **Self-signed server certificates** — trust a CA that signed your server's certificate, without presenting a client identity. - **Mutual TLS (mTLS)** — present a client certificate and key to prove the client's identity. - **Both** — trust a custom CA _and_ present client credentials. ### Trusting a self-signed server certificate Set only the CA certificate to trust the server's self-signed certificate: ```bash # Path to PEM file: CAMUNDA_MTLS_CA_PATH=/path/to/ca.pem # Or inline PEM (must contain real newlines, not literal '\n'): CAMUNDA_MTLS_CA="$(cat /path/to/ca.pem)" ``` ### Mutual TLS (client certificate) To present a client certificate for mutual TLS, provide both the certificate and private key: ```bash CAMUNDA_MTLS_CERT_PATH=/path/to/client.crt CAMUNDA_MTLS_KEY_PATH=/path/to/client.key # Optional — passphrase if the key is encrypted: # CAMUNDA_MTLS_KEY_PASSPHRASE=secret ``` ### Full mTLS with custom CA Combine a custom CA with client credentials: ```bash CAMUNDA_MTLS_CA_PATH=/path/to/ca.pem CAMUNDA_MTLS_CERT_PATH=/path/to/client.crt CAMUNDA_MTLS_KEY_PATH=/path/to/client.key ``` Inline PEM values (`CAMUNDA_MTLS_CERT`, `CAMUNDA_MTLS_KEY`, `CAMUNDA_MTLS_CA`) take precedence over their `_PATH` counterparts. An `https.Agent` is attached to all outbound calls (including token fetches). ## Branded Keys Import branded key helpers directly: ```ts ProcessDefinitionKey, ProcessInstanceKey, } from "@camunda8/orchestration-cluster-api"; const defKey = ProcessDefinitionKey.assumeExists("2251799813686749"); // @ts-expect-error – cannot assign def key to instance key const bad: ProcessInstanceKey = defKey; ``` They are zero‑cost runtime strings with compile‑time separation. ## Cancelable Operations All methods return a `CancelablePromise`: ```ts const p = camunda.searchProcessInstances( { filter: { processDefinitionKey: defKey } }, { consistency: { waitUpToMs: 0 } } ); setTimeout(() => p.cancel(), 100); // best‑effort cancel try { await p; // resolves if not cancelled } catch (e) { if (isSdkError(e) && e.name === "CancelSdkError") { console.log("Operation cancelled"); } else throw e; } ``` Notes: - Rejects with `CancelSdkError`. - Cancellation classification runs first so aborted fetches are never downgraded to generic network errors. - Abort is immediate and idempotent; underlying fetch is signalled. ## Per-Operation Functions (Tree-Shakeable Subpath) For bundle-size-sensitive apps (browsers, edge functions), the `@camunda8/orchestration-cluster-api/fn` subpath exports every operation as a standalone function that takes a shared core as its first argument. Bundlers keep only the operations you import, and each operation lazily loads only its own validation schemas. > **esbuild code-splitting caveat:** this holds for bundlers that tree-shake dead `import()` > targets (e.g. Rollup, and esbuild without code splitting). esbuild's `splitting: true` mode > retains the dynamic-import targets of unimported operation functions, so a code-split esbuild > build may still pull in schema chunks for operations you did not import. The size figures below > are measured unsplit; prefer an unsplit build (or Rollup) when minimal `./fn` bundles matter. ```ts createCamundaCore, createProcessInstance, getTopology, } from "@camunda8/orchestration-cluster-api/fn"; // Same options as createCamundaClient(); a CamundaClient also works as the core. const core = createCamundaCore(); const topology = await getTopology(core); console.log(topology.brokers?.length); const instance = await createProcessInstance(core, { processDefinitionKey: defKey, variables: { orderId: "A-1" }, }); console.log(instance.processInstanceKey); ``` Notes: - Each function takes the same arguments as the `CamundaClient` method of the same name and behaves identically (retry, backpressure, validation, eventual consistency, cancellation) — the client methods delegate to these functions. One intentional exception: for operations that take no input (e.g. `getTopology`), the standalone functions honor their `options` argument (`getTopology(core, { retry: false })` disables retry), while the class methods ignore an options argument passed to them (`client.getTopology({ retry: false })` retries as usual) — a legacy quirk preserved for backward compatibility. - `createCamundaCore()` accepts the same options as `createCamundaClient()`. It does not include job workers, the thread pool, or the deployment/search convenience helpers; use the client for those. Jobs returned by `activateJobs(core, …)` still have `complete()`, `fail()` and the other job actions. - Measured with esbuild (minified, browser, everything that can load including validation schemas): the core is ~135 KB and each operation adds ~4–60 KB, versus ~930 KB for the full client. ## Effect Surface (Opt-In Subpath) The main entry stays Promise-based and pulls in **zero** Effect at runtime. Opt in to a first-class [Effect](https://effect.website) surface — a client whose every method returns an `Effect`, tagged domain errors, and Effect-native combinators — by importing the dedicated `./effect` subpath. > **Peer dependency:** `effect` is an **optional peer dependency** (Effect **v4**). The `./effect` > subpath requires it; install it alongside the SDK: > > ```sh > npm install effect > ``` > > The main `.` entry never imports `effect`, so Promise-first users are never forced to adopt it. > > **Module resolution:** Effect v4 ships as an `exports`-map-only package (no legacy > `main`/`types`), so consuming the `./effect` types requires a modern TypeScript module > resolution — set `"moduleResolution": "bundler"` (or `"node16"`/`"nodenext"`) in your > `tsconfig.json`. The Promise-first `.` entry is unaffected. ```ts createCamundaEffectClient, eventually, EventualConsistencyTimeout, } from "@camunda8/orchestration-cluster-api/effect"; const camunda = createCamundaEffectClient(); const program = Effect.gen(function* () { const deployment = yield* camunda.deployResourcesFromFiles([ "./bpmn/process.bpmn", ]); const { processInstanceKey } = yield* camunda.createProcessInstance({ processDefinitionKey: deployment.processes[0].processDefinitionKey, }); // Poll on the Effect Clock until the instance is searchable, timing out deterministically. // waitUpToMs: 0 asks the SDK for the latest available state without its own wall-clock // wait, so the Effect `eventually` combinator owns the predicate + timeout horizon — // making the eventual-consistency wait deterministic under TestClock. const search = yield* eventually( camunda.searchProcessInstances( { filter: { processInstanceKey } }, { consistency: { waitUpToMs: 0 } } ), (s) => s.items.some((i) => i.processInstanceKey === processInstanceKey), { waitUpTo: "30 seconds", interval: "750 millis" } ); return { processInstanceKey, search }; }).pipe( // Tagged errors → discriminate with catchTag / catchTags instead of a manual switch. Effect.catchTag( "EventualConsistencyTimeout", (e: EventualConsistencyTimeout) => Effect.logError(`Timed out: ${e.message}`).pipe( Effect.andThen(Effect.fail(e)) ) ) ); const result = await Effect.runPromise(program); ``` Why a subpath? - Keeps the base bundle lean for the Promise-first 80% use case. - No dependency on `effect` at runtime unless you opt in; it is an **optional** peer. - Unlocks the Effect ecosystem (typed errors, `Schedule`, `Layer`/`Context`, `TestClock`). Exports available from `.../effect`: - `createCamundaEffectClient(options?)` – a `Proxy` client where every method returns `Effect.Effect, DomainError, never>`; the throwing client is reachable via `.inner`. - Tagged errors (`Data.TaggedError`): `CamundaValidationError`, `EventualConsistencyTimeout`, `HttpError`, `CamundaGenericError` — together the `DomainError` union. Discriminate with `Effect.catchTag` / `Effect.catchTags`. - Combinators: `retryWithBackoff` (`Effect.retry` + `Schedule.exponential` + jitter), `withTimeout` (`Effect.timeoutOrElse` with real interruption), `eventually` (a recursive `Effect.sleep` poll on the Effect `Clock`, timing out to `EventualConsistencyTimeout`). - Dependency injection: `CamundaEffect` (`Context.Service`) + `layer(options?)` (`Layer`) so worker / orchestration code composes via `Layer` and swaps a test double trivially. - Pagination: `.paginate(body, opts?)` on every `search*` method, returning an `EffectPaginator` (`pages()` / `items()` → `Stream`, `toArray()` → `Effect`). See below. **Clock-class win:** `eventually` / `withTimeout` run on the Effect `Clock`, so `TestClock.adjust` advances eventual/timeout deterministically in tests — no real-clock burn. The Promise surface has the same property via [`createTestClock`](#deterministic-time-jobclock); the difference is that Effect gives you `TestClock` and the rest of the ecosystem for free. ### Paginated Search as a `Stream` Every `search*` operation on the Effect client carries the same `.paginate` helper the Promise client installs, re-expressed in Effect terms: `pages()` and `items()` are `Stream`s and `toArray()` is an `Effect`. Pages are fetched lazily as they are pulled, and interrupting the fiber cancels the in-flight page request. ```ts const camunda = createCamundaEffectClient(); // Walk every ACTIVE process instance, 100 per request, without ever holding more // than one page in memory. `Stream.take` stops pulling — and so stops fetching. const activeKeys = await Effect.runPromise( camunda.searchProcessInstances .paginate({ filter: { state: "ACTIVE" }, page: { limit: 100 } }) .items() .pipe( Stream.map((instance) => instance.processInstanceKey), Stream.take(500), Stream.runCollect ) ); ``` Options: `maxPages` (safety cap), `mode` (`auto` | `cursor` | `offset`), and `consistency` (forwarded to the first page only — once paging is under way an empty page is end-of-results, not a stale read). ### Effect Job Workers The same subpath also exposes an **Effect-native job worker** — the long-running `activateJobs` → handle → `completeJob`/`failJob` loop, modelled as Effect. A handler is `(job) => Effect.Effect` with a **typed failure channel**: a `RetryableJobError` becomes `failJob` with `retries - 1` (plus an optional server-side backoff), and a `TerminalJobError` becomes `throwJobError` (caught by a BPMN error boundary, or an incident if uncaught). Success completes the job with the returned variables. It composes over the same activation/backpressure runtime the Promise worker uses — it does not reimplement activation. ```ts createCamundaEffectWorker, layer, RetryableJobError, TerminalJobError, } from "@camunda8/orchestration-cluster-api/effect"; const program = Effect.gen(function* () { // Forked into the current Scope: interrupted (with a best-effort lease release) when // the scope closes. Let both type parameters infer — supplying only the completion- // variable type (`createCamundaEffectWorker<{ ok: boolean }>(…)`) makes TypeScript // fall back to the *default* for the handler's requirements (`R = never`) rather // than inferring it, so a handler with dependencies would stop compiling. See // "Injecting Services into a Handler". yield* createCamundaEffectWorker({ type: "payment-processing", maxJobsToActivate: 10, // activation batch size concurrency: 10, // max jobs handled in parallel (backpressure) pollInterval: "1 second", // between empty polls, on the Effect Clock // Optional: retry the handler in-process on a RetryableJobError before failing the job. handlerRetrySchedule: Schedule.spaced("2 seconds"), handler: (job) => Effect.gen(function* () { if (!job.variables.amount) { // Terminal → raise a BPMN error / incident. return yield* Effect.fail( new TerminalJobError({ code: "INVALID_INPUT", message: "amount is required", }) ); } if (yield* isServiceDown()) { // Retryable → failJob(retries - 1) with a re-activation backoff. return yield* Effect.fail( new RetryableJobError({ message: "downstream unavailable", retryBackoff: "5 seconds", }) ); } return { ok: true }; // success → completeJob(variables) }), }); // ... the worker runs for the lifetime of this scope. yield* Effect.never; }).pipe( Effect.scoped, Effect.provide(layer()) // provides the `/effect` client the worker depends on ); void program; ``` Worker exports from `.../effect`: - `createCamundaEffectWorker(config)` – forks the worker into the current `Scope` and returns a handle (`{ type, join, interrupt }`); provide the client `layer()` as its dependency. - `activateJobsStream(type, options)` – the lower-level `Stream.Stream` of activated jobs, polling on the Effect `Clock`. - `workerLayer(config)` – a `Layer` that runs a worker for the layer's lifetime. - Tagged job failures: `RetryableJobError` (→ `failJob`), `TerminalJobError` (→ `throwJobError`), together the `JobError` channel. **Clock-class win:** the activation poll interval and the handler-retry `Schedule` run on the Effect `Clock`, so `TestClock.adjust` bounds activation/retry timing in virtual time — the whole loop is deterministic in tests, with no real-clock burn. The Promise worker is equally drivable by pinning the client clock (see [Deterministic Time](#deterministic-time-jobclock)); what Effect adds here is `Schedule` composition over the retry policy. ### Injecting Services into a Handler A handler is `(job) => Effect.Effect`, and `R` — whatever services the handler depends on — is threaded out through `createCamundaEffectWorker` / `workerLayer` into the worker's own requirements. So a handler's dependencies are provided, and swapped for mocks, exactly like any other `Layer`. ```ts CamundaEffect, type CamundaEffectClient, layer, workerLayer, } from "@camunda8/orchestration-cluster-api/effect"; // A service the handler depends on. Nothing about it is Camunda-specific — it is an // ordinary Effect service. class PaymentGateway extends Context.Service< PaymentGateway, { readonly charge: (amount: number) => Effect.Effect } >()("PaymentGateway") {} // The handler's requirements flow out through the worker's own requirements, so the // worker layer asks for `PaymentGateway` just like it asks for the Camunda client. const paymentWorker = workerLayer({ type: "payment-processing", handler: (job) => Effect.gen(function* () { const gateway = yield* PaymentGateway; return { receipt: yield* gateway.charge(Number(job.variables.amount)) }; }), }); // paymentWorker: Layer // Production: the real gateway and a real client. const liveWorker = paymentWorker.pipe( Layer.provide( Layer.succeed(PaymentGateway, { charge: (amount) => Effect.succeed(`live-receipt-${amount}`), }) ), Layer.provide(layer()) ); // Tests: the same worker with *both* dependencies swapped. `CamundaEffect` is a service // too, so the broker is mocked exactly like the gateway — the worker runs end-to-end // with neither a payment provider nor a broker. const fakeClient = { activateJobs: () => Effect.succeed({ jobs: [] }), completeJob: () => Effect.void, failJob: () => Effect.void, throwJobError: () => Effect.void, } as unknown as CamundaEffectClient; const mockedWorker = paymentWorker.pipe( Layer.provide( Layer.succeed(PaymentGateway, { charge: () => Effect.succeed("mock-receipt"), }) ), Layer.provide(Layer.succeed(CamundaEffect, fakeClient)) ); ``` `Layer.succeed(CamundaEffect, fakeClient)` is what the SDK's own worker tests use; see [tests/effect-worker-di.test.ts](https://github.com/camunda/orchestration-cluster-api-js/blob/main/tests/effect-worker-di.test.ts) for worked examples that mock both dependencies and drive the loop to a `completeJob` / `failJob` under `TestClock`. > **Gotcha — let both type parameters infer.** `createCamundaEffectWorker` has `R = never` > as its default, and TypeScript does not infer a type parameter when only _some_ are supplied. > So `createCamundaEffectWorker<{ ok: boolean }>({ ... })` pins `R` to `never`, and a handler with > dependencies fails to compile with an error pointing at the handler rather than at the missing > type argument: > > ``` > Type 'PaymentGateway' is not assignable to type 'never'. > ``` > > Omit both — `A` is inferred from the handler's success value — or supply both > (`createCamundaEffectWorker<{ receipt: string }, PaymentGateway>({ ... })`). ## Eventual Consistency Polling Some endpoints accept consistency management options. Pass a `consistency` block (where supported) with `waitUpToMs` and optional `pollIntervalMs` (default 500). If the condition is not met within timeout an `EventualConsistencyTimeoutError` is thrown. To consume eventual polling in a non‑throwing fashion set the client error mode before invoking an eventually consistent method: At present the canonical client operates in throwing mode. Non‑throwing adaptation (Result / Effect) is achieved via the functional wrappers rather than mutating the base client. ### Options `consistency` object fields (all optional except `waitUpToMs`): | Field | Type | Description | | ---------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `waitUpToMs` | `number` | Maximum total time to wait before failing. `0` disables polling and returns the first response immediately. | | `pollIntervalMs` | `number` | Base delay between attempts (minimum enforced at 10ms). Defaults to `500` or the value of `CAMUNDA_SDK_EVENTUAL_POLL_DEFAULT_MS` if provided. | | `predicate` | `(result) => boolean \| Promise` | Custom success condition. If omitted, non-GET endpoints default to: first 2xx body whose `items` array (if present) is non-empty. | | `trace` | `boolean` | When true, logs each 200 response body (truncated ~1KB) before predicate evaluation and emits a success line with elapsed time when the predicate passes. Requires log level `debug` (or `trace`) to see output. | | `onAttempt` | `(info) => void` | Callback after each attempt: `{ attempt, elapsedMs, remainingMs, status, predicateResult, nextDelayMs }`. | | `onComplete` | `(info) => void` | Callback when predicate succeeds: `{ attempts, elapsedMs }`. Not called on timeout. | ### Trace Logging Enable by setting `trace: true` inside `consistency`. Output appears under the `eventual` log scope at level `debug` so you must raise the SDK log level (e.g. `CAMUNDA_SDK_LOG_LEVEL=debug`). Emitted lines (examples): ``` [camunda-sdk][debug][eventual] op=searchJobs attempt=3 trace body={"items":[]} [camunda-sdk][debug][eventual] op=searchJobs attempt=5 status=200 predicate=true elapsed=742ms totalAttempts=5 ``` Use this to understand convergence speed and data shape evolution during tests or to diagnose slow propagation. ### Example ```ts const jobs = await camunda.searchJobs( { filter: { type: "payment" }, }, { consistency: { waitUpToMs: 5000, pollIntervalMs: 200, trace: true, predicate: (r) => Array.isArray(r.items) && r.items.some((j) => j.state === "CREATED"), }, } ); ``` On timeout an `EventualConsistencyTimeoutError` includes diagnostic fields: `{ attempts, elapsedMs, lastStatus, lastResponse, operationId }`. ## Logging Per‑client logger; no global singleton. The level defaults from `CAMUNDA_SDK_LOG_LEVEL` (default `error`). ```ts const client = createCamundaClient({ log: { level: "info", transport: (evt) => { // evt: { level, scope, ts, args, code?, data? } console.log(JSON.stringify(evt)); }, }, }); const log = client.logger("worker"); log.debug(() => ["expensive detail only if enabled", { meta: 1 }]); log.code("info", "WORK_START", "Starting work loop", { pid: process.pid }); ``` Lazy args (functions with zero arity) are only invoked if the level is enabled. Update log level / transport at runtime via `client.configure({ log: { level: 'debug' } })`. ### Default Behaviour Without any explicit `log` option: - Level = `error` (unless `CAMUNDA_SDK_LOG_LEVEL` is set) - Transport = console (`console.error` / `console.warn` / `console.log`) - Only `error` level internal events are emitted (e.g. strict validation failure summaries, fatal auth issues) - No info/debug/trace noise by default To silence everything set level to `silent`: ```bash CAMUNDA_SDK_LOG_LEVEL=silent ``` To enable debug logs via env: ```bash CAMUNDA_SDK_LOG_LEVEL=debug ``` ### Unsafe Deep Diagnostics (`silly`) Setting `CAMUNDA_SDK_LOG_LEVEL=silly` enables the deepest diagnostics. In addition to everything at `trace`, the SDK will emit HTTP request and response body previews for all HTTP methods under the `telemetry` scope (log line contains `http.body`). This can leak sensitive information (secrets, PII). A warning (`log.level.silly.enabled`) is emitted on client construction. Use only for short‑lived local debugging; never enable in production or share captured logs externally. Body output is truncated (max ~4KB) and form-data parts identify uploaded files as `[File]`. ### Bring Your Own Logger Provide a `transport` function to forward structured `LogEvent` objects into any logging library. #### Pino ```ts const p = pino(); const client = createCamundaClient({ log: { level: 'info', transport: e => { const lvl = e.level === 'trace' ? 'debug' : e.level; // map trace p.child({ scope: e.scope, code: e.code }).[lvl]({ ts: e.ts, data: e.data, args: e.args }, e.args.filter(a=>typeof a==='string').join(' ')); } } }); ``` #### Winston ```ts const w = winston.createLogger({ transports: [new winston.transports.Console()], }); const client = createCamundaClient({ log: { level: "debug", transport: (e) => { const lvl = e.level === "trace" ? "silly" : e.level; // winston has 'silly' w.log({ level: lvl, message: e.args.filter((a) => typeof a === "string").join(" "), scope: e.scope, code: e.code, data: e.data, ts: e.ts, }); }, }, }); ``` #### loglevel ```ts log.setLevel("info"); // host app level const client = createCamundaClient({ log: { level: "info", transport: (e) => { if (e.level === "silent") return; const method = ( ["error", "warn", "info", "debug"].includes(e.level) ? e.level : "debug" ) as "error" | "warn" | "info" | "debug"; (log as any)[method]( `[${e.scope}]`, e.code ? `${e.code}:` : "", ...e.args ); }, }, }); ``` #### Notes - Map `trace` to the nearest available level if your logger lacks it. - Use `log.code(level, code, msg, data)` for machine-parsable events. - Redact secrets before logging if you add token contents to custom messages. - Reconfigure later: `client.configure({ log: { level: 'warn' } })` updates only that client. - When the effective level is `debug` (or `trace`), the client emits a lazy `config.hydrated` event on construction and `config.reconfigured` on `configure()`, each containing the redacted effective configuration `{ config: { CAMUNDA_... } }`. Secrets are already masked using the SDK's redaction rules. ## Errors May throw: - Network / fetch failures - Non‑2xx HTTP responses - Validation errors (strict mode) - `EventualConsistencyTimeoutError` - `CancelSdkError` on cancellation ### Typed Error Handling All SDK-thrown operational errors normalize to a discriminated union (`SdkError`) when they originate from HTTP, network, auth, or validation layers. Use the guard `isSdkError` to narrow inside a catch: ```ts createCamundaClient, isSdkError, } from "@camunda8/orchestration-cluster-api"; const client = createCamundaClient(); try { await client.getTopology(); } catch (e) { if (isSdkError(e)) { switch (e.name) { case "HttpSdkError": console.error("HTTP failure", e.status, e.operationId); break; case "ValidationSdkError": console.error("Validation issue on", e.operationId, e.side, e.issues); break; case "AuthSdkError": console.error("Auth problem", e.message, e.status); break; case "CancelSdkError": console.error("Operation cancelled", e.operationId); break; case "NetworkSdkError": console.error("Network layer error", e.message); break; } return; } // Non-SDK (programmer) error; rethrow or wrap throw e; } ``` Guarantees: - HTTP errors expose `status` and optional `operationId`. - If the server returns RFC 9457 / RFC 7807 Problem Details JSON (`type`, `title`, `status`, `detail`, `instance`) these fields are passed through on the `HttpSdkError` when present. - Validation errors expose `side` and `operationId`. - Classification is best-effort; unknown shapes fall back to `NetworkSdkError`. > Advanced: You can still layer your own domain errors on top (e.g. translate certain status codes) by mapping `SdkError` into custom discriminants. ### Functional / Non‑Throwing Variant - EXPERIMENTAL _Note that this feature is experimental and subject to change._ If you prefer FP‑style explicit error handling instead of exceptions, use the result client wrapper: ```ts createCamundaResultClient, isOk, } from "@camunda8/orchestration-cluster-api"; const camundaR = createCamundaResultClient(); const res = await camundaR.createDeployment({ resources: [file] }); if (isOk(res)) { console.log("Deployment key", res.value.deploymentKey); } else { console.error("Deployment failed", res.error); } ``` API surface differences: - All async operation methods return `Promise>` where `Result = { ok: true; value: T } | { ok: false; error: unknown }`. - No exceptions are thrown for HTTP / validation errors (cancellation and programmer errors like invalid argument sync throws are still converted to `{ ok:false }`). - The original throwing client is available via `client.inner` if you need to mix styles. Helpers: ```ts ``` When to use: - Integrating with algebraic effects / functional pipelines. - Avoiding try/catch nesting in larger orchestration flows. - Converting to libraries expecting an Either/Result pattern. ### Effect Adapter For Effect-based projects, wrap the throwing client in an Effect-flavoured facade whose every method returns an `Effect` with a typed `DomainError` channel: ```ts const camunda = createCamundaEffectClient(); const deployment = await Effect.runPromise( camunda.createDeployment({ resources: [file] }) ); console.log(deployment.deployments.length); ``` See [Effect Surface (Opt-In Subpath)](#effect-surface-opt-in-subpath) above for the full surface — tagged errors, `retryWithBackoff` / `withTimeout` / `eventually`, and `Layer`/`Context` DI. Notes: - `effect` is an **optional peer dependency**; only the `./effect` subpath imports it. - Each method returns `Effect.Effect, DomainError, never>`; the throwing client is reachable via `.inner`. - Failures are narrowed into tagged errors so you discriminate with `Effect.catchTag` / `catchTags`. ## Pagination Every `search*` operation exposes a `.paginate(body, options?)` method that returns a lazy, cancelable async stream over **all** matching results. Cursors (or offsets) are advanced internally, so you never hand-write next-page bookkeeping. ```ts // Stream every matching process instance across all pages. Cursors are advanced // internally; the loop stops when the server runs out of pages. async function everyActiveInstanceExample() { const camunda = createCamundaClient(); const stream = camunda.searchProcessInstances.paginate({ filter: { state: "ACTIVE" }, page: { limit: 100 }, }); for await (const instance of stream.items()) { console.log(instance.processInstanceKey); } } ``` Iterate a page at a time with `.pages()`, or drain a bounded result set into an array with `.toArray()`. Bound long streams with a `maxPages` cap and/or an `AbortSignal`: ```ts // Bound the stream with an AbortSignal and a hard page cap. A non-zero // `consistency` window is applied to the first page only, so freshly-written // data can be waited for without the terminal empty page timing out. async function boundedPaginationExample( processDefinitionId: ProcessDefinitionId ) { const camunda = createCamundaClient(); const ac = new AbortController(); setTimeout(() => ac.abort(), 30_000); const stream = camunda.searchProcessInstances.paginate( { filter: { processDefinitionId }, page: { limit: 100 } }, { signal: ac.signal, maxPages: 10, consistency: { waitUpToMs: 5000 } } ); for await (const instance of stream.items()) { console.log(instance.processInstanceKey); } } ``` For advanced use, the low-level `nextPageRequest()` / `paginate()` primitives are also exported from the package entry point. ## Configuration Reference Generated doc enumerating all supported environment variables (types, defaults, conditional requirements, redaction rules) is produced at build time: ``` ./docs/CONFIG_REFERENCE.md ``` ## Deploying Resources (File-only) The deployment endpoint requires each resource to have a filename (extension used to infer type: `.bpmn`, `.dmn`, `.form` / `.json`). Extensions influence server classification; incorrect or missing extensions may yield unexpected results. Pass an array of `File` objects (NOT plain `Blob`). ### Browser ```ts const bpmnXml = `...`; const file = new File([bpmnXml], "order-process.bpmn", { type: "application/xml", }); const result = await camunda.createDeployment({ resources: [file] }); console.log(result.deployments.length); ``` From an existing Blob: ```ts const blob: Blob = getBlob(); const file = new File([blob], "model.bpmn"); await camunda.createDeployment({ resources: [file] }); ``` ### Node (Recommended Convenience) Use the built-in helper `deployResourcesFromFiles(...)` to read local files and create `File` objects automatically. It returns the enriched `ExtendedDeploymentResult` (adds typed arrays: `processes`, `decisions`, `decisionRequirements`, `forms`, `resources`). ```ts const result = await camunda.deployResourcesFromFiles([ "./bpmn/order-process.bpmn", "./dmn/discount.dmn", "./forms/order.form", ]); console.log(result.processes.map((p) => p.processDefinitionId)); console.log(result.decisions.length); ``` With explicit tenant (overriding tenant from configuration): ```ts await camunda.deployResourcesFromFiles(["./bpmn/order-process.bpmn"], { tenantId: "tenant-a", }); ``` Error handling: ```ts try { await camunda.deployResourcesFromFiles([]); // throws (empty array) } catch (e) { console.error("Deployment failed:", e); } ``` Manual construction alternative (if you need custom logic): ```ts const bpmnXml = ''; const file = new File([Buffer.from(bpmnXml)], "order-process.bpmn", { type: "application/xml", }); await camunda.createDeployment({ resources: [file] }); ``` Helper behavior: - Dynamically imports `node:fs/promises` & `node:path` (tree-shaken from browser bundles) - Validates Node environment (throws in browsers) - Lightweight MIME inference: `.bpmn|.dmn|.xml -> application/xml`, `.json|.form -> application/json`, fallback `application/octet-stream` - Rejects empty path list Empty arrays are rejected. Always use correct extensions so the server can classify each resource. ## Testing Patterns Create isolated clients per test file: ```ts const client = createCamundaClient({ config: { CAMUNDA_REST_ADDRESS: "http://localhost:8080", CAMUNDA_AUTH_STRATEGY: "NONE", }, }); ``` Inject a mock fetch: ```ts const client = createCamundaClient({ fetch: async (_input, _init) => new Response(JSON.stringify({ ok: true }), { status: 200 }), }); ``` ## API Documentation Generate an HTML API reference site with TypeDoc (public entry points only): ```bash npm run docs:api ``` Output: static site in `docs/api` (open `docs/api/index.html` in a browser or serve the folder, e.g. `npx http-server docs/api`). Entry points: `src/index.ts`, `src/logger.ts`, `src/effect/index.ts`. Internal generated code, scripts, tests are excluded and private / protected members are filtered. Regenerate after changing public exports. ## Contributing We welcome issues and pull requests. Please read the [CONTRIBUTING.md](https://github.com/camunda/orchestration-cluster-api-js/blob/main/CONTRIBUTING.md) guide before opening a PR to understand: - Deterministic builds policy (no committed timestamps) – see CONTRIBUTING - Commit message conventions (Conventional Commits with enforced subject length) - Release workflow & how to dry‑run semantic‑release locally - Testing strategy (unit vs integration) - Performance and security considerations ## License Apache 2.0 --- ## Function: createLogger() ```ts function createLogger(opts?): Logger; ``` ## Parameters ### opts? [`CreateLoggerOptions`](../../index/interfaces/CreateLoggerOptions.md) = `{}` ## Returns [`Logger`](../interfaces/Logger.md) --- ## logger ## Interfaces - [LogEvent](interfaces/LogEvent.md) - [Logger](interfaces/Logger.md) ## Type Aliases - [LogLevel](type-aliases/LogLevel.md) - [LogTransport](type-aliases/LogTransport.md) ## Functions - [createLogger](functions/createLogger.md) --- ## Interface: LogEvent ## Properties ### args ```ts args: any[]; ``` --- ### code? ```ts optional code?: string; ``` --- ### data? ```ts optional data?: any; ``` --- ### level ```ts level: LogLevel; ``` --- ### scope ```ts scope: string; ``` --- ### ts ```ts ts: number; ``` --- ## Interface: Logger ## Methods ### code() ```ts code( level, code, msg, data? ): void; ``` #### Parameters ##### level [`LogLevel`](../type-aliases/LogLevel.md) ##### code `string` ##### msg `string` ##### data? `any` #### Returns `void` --- ### debug() ```ts debug(...a): void; ``` #### Parameters ##### a ...`any`[] #### Returns `void` --- ### error() ```ts error(...a): void; ``` #### Parameters ##### a ...`any`[] #### Returns `void` --- ### info() ```ts info(...a): void; ``` #### Parameters ##### a ...`any`[] #### Returns `void` --- ### level() ```ts level(): LogLevel; ``` #### Returns [`LogLevel`](../type-aliases/LogLevel.md) --- ### scope() ```ts scope(child): Logger; ``` #### Parameters ##### child `string` #### Returns `Logger` --- ### setLevel() ```ts setLevel(level): void; ``` #### Parameters ##### level [`LogLevel`](../type-aliases/LogLevel.md) #### Returns `void` --- ### setTransport() ```ts setTransport(t?): void; ``` #### Parameters ##### t? [`LogTransport`](../type-aliases/LogTransport.md) #### Returns `void` --- ### silly() ```ts silly(...a): void; ``` #### Parameters ##### a ...`any`[] #### Returns `void` --- ### trace() ```ts trace(...a): void; ``` #### Parameters ##### a ...`any`[] #### Returns `void` --- ### warn() ```ts warn(...a): void; ``` #### Parameters ##### a ...`any`[] #### Returns `void` --- ## Type Alias: LogLevel ```ts type LogLevel = "silent" | "error" | "warn" | "info" | "debug" | "trace" | "silly"; ``` --- ## Type Alias: LogTransport ```ts type LogTransport = (e) => void; ``` ## Parameters ### e [`LogEvent`](../interfaces/LogEvent.md) ## Returns `void` --- ## @camunda8/orchestration-cluster-api ## Modules - [effect](effect/index.md) - [index](index/index.md) - [logger](logger/index.md) --- ## Manage backpressure with the TypeScript SDK Learn how to manage backpressure with the TypeScript SDK. ## Manage backpressure The Orchestration Cluster REST API client implements adaptive backpressure management. - It automatically handles retries and backoff in response to backpressure signals from the server. - The SDK applies global backpressure management to throttle operations when the server returns backpressure. :::info To learn more about backpressure in Camunda 8, see [backpressure](/self-managed/components/orchestration-cluster/zeebe/operations/backpressure.md). ::: ### Enable backpressure management This behavior is controlled by the `CAMUNDA_SDK_BACKPRESSURE_PROFILE` environment variable and is enabled by default. - When enabled, SDK calls do not fail when the server responds with a backpressure signal. Instead, the SDK retries these calls after an increasing backoff period. Additional SDK calls made during backpressure are delayed proactively rather than each needing to receive a backpressure signal. - When a retried operation succeeds, the SDK reduces its internal backpressure status and removes throttling. This allows the SDK to match the server’s capacity and optimize throughput. - This adaptive mechanism can lead to 2x–5x higher performance under load. See [Patterns to use today to make Camunda 8 apps even more reliable](https://www.camundacon.com/event-session/camundacon-new-york-2025/patterns-to-use-today-to-make-camunda-8-apps-even-more-reliable?on_demand=true) for a demonstration. ### Disable backpressure management To disable SDK backpressure management and throw immediately when the server responds with a backpressure signal, set `CAMUNDA_SDK_BACKPRESSURE_PROFILE=LEGACY`. --- ## TypeScript SDK Use the TypeScript SDK to connect to Camunda 8, deploy process models, and work with the Orchestration Cluster API. ## About this SDK The TypeScript SDK provides typed access to Camunda 8 APIs. - It includes IntelliSense support and works in both JavaScript and TypeScript projects. - The [Camunda 8 TypeScript SDK for Node.js](https://github.com/camunda/camunda-8-js-sdk) is available via [npm](https://www.npmjs.com/package/@camunda8/sdk). ### When to use this package Use the [`@camunda8/sdk`](https://www.npmjs.com/package/@camunda8/sdk) package if: - You need to use the gRPC API for job streaming. - Your server target is Camunda 8.7 or earlier. - You want to migrate an existing application to the Orchestration Cluster API. :::info If you do not need to use gRPC, use the [Orchestration Cluster API TypeScript client](oca-client.md). ::: ## Prerequisites The following prerequisites are required to use the TypeScript SDK: | Prerequisite | Description | | :----------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Node.js | The SDK runs in Node.js and cannot run in a web browser due to [technical limitations](https://github.com/camunda/camunda-8-js-sdk/issues/79).If you want to write an application in the web browser, use `@camunda8/orchestration-cluster-api`.See [Get started with the Orchestration Cluster API TypeScript client](./oca-client.md). | ## Get started Get started with the Orchestration Cluster API. 1. Create a new Node.js project that uses TypeScript: ```bash npm init -y npm install -D typescript npx tsc --init ``` 2. Install the SDK as a dependency: ```bash npm i @camunda8/sdk ``` :::info - A complete working version of the quickstart code is [available on GitHub](https://github.com/camunda-community-hub/c8-sdk-demo). - For earlier versions (Camunda 8.7 and below), refer to the [SDK README file](https://github.com/camunda/camunda-8-js-sdk). ::: ## Configure the connection Choose one of the following configuration options: - Explicit configuration in code - Zero-configuration constructor with environment variables The recommended configuration is via the zero-configuration constructor, with all values for configuration supplied via environment variables. This makes rotation, secret management, and environment promotion safer and simpler. **The environment variables you must set are outlined below. Replace these with your secrets and URLs.** :::info To configure a client and capture these values when creating the client, see [setting up client connection credentials](/components/saas/clusters/manage-api-clients.md#create-a-client). ::: ### Self-managed configuration Minimal configuration: ```bash # Self-Managed with Orchestration Cluster API export ZEEBE_REST_ADDRESS='http://localhost:8080/v2' ``` With OAuth: ```bash export ZEEBE_REST_ADDRESS='http://localhost:8080/v2' export ZEEBE_GRPC_ADDRESS='grpc://localhost:26500' export ZEEBE_CLIENT_ID='zeebe' export ZEEBE_CLIENT_SECRET='zecret' export CAMUNDA_OAUTH_URL='http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token' ``` If you are running with multi-tenancy enabled: ```bash export CAMUNDA_TENANT_ID='my-tenant' # tenant used by default if none set ``` ### Camunda SaaS configuration ```bash export ZEEBE_REST_ADDRESS='5c34c0a7-...-125615f7a9b9.syd-1.zeebe.camunda.io' export ZEEBE_GRPC_ADDRESS='grpcs://5c34c0a7-...-125615f7a9b9.syd-1.zeebe.camunda.io:443' export ZEEBE_CLIENT_ID='yvvURO...' export ZEEBE_CLIENT_SECRET='iJJu-SHg...' export CAMUNDA_OAUTH_URL='https://login.cloud.camunda.io/oauth/token' ``` :::caution To set these values explicitly in code (not recommended), pass them with the same key names to the `Camunda8` constructor. ::: ## Use the SDK 1. Create a file `index.ts` in your IDE. 2. Import the SDK: ```typescript const clientFactory = new Camunda8(); ``` 3. Get an Orchestration API client. This is used to deploy process models and start process instances: ```typescript const camunda = clientFactory.getOrchestrationClusterApiClient(); ``` ### Deploy a process model Next, deploy a process model. Network operations are asynchronous and methods that operate over the network return promises. Wrap the main function in an `async` function: ```typescript async function main() { const deployResponse = await camunda.deployResourcesFromFiles([ path.join(process.cwd(), "process.bpmn"), ]); console.log( `[Camunda] Deployed process ${deployResponse.processes[0].processDefinitionId}` ); } main(); // remember to invoke the function ``` Paste the process model XML below into a file named `process.bpmn`: ```xml Flow_0yqo0wz Flow_03qgl0x Flow_0yqo0wz Flow_0qugen1 Flow_0qugen1 Flow_03qgl0x ``` For reference, this is the model you use in this example: ![Example BPMN process model](../img/process-model.png) Run the program to deploy the process model to Camunda: ```bash npx tsx index.ts ``` If your configuration is correct, you should see output similar to the following: ``` [Camunda] Deployed process c8-sdk-demo ``` ### Create a service worker Outside the main function, add the following code: ```typescript console.log("Starting worker..."); const worker = camunda.createJobWorker({ jobType: "service-task", workerName: "test-worker", maxParallelJobs: 20, pollIntervalMs: 1000, pollTimeoutMs: 50_000, jobTimeoutMs: 5000, jobHandler: (job) => { console.log( `[worker]: Completing job ${job.jobKey} from process ${job.processInstanceKey}\n` ); return job.complete({ serviceTaskOutcome: "We did it!", }); }, }); ``` This code starts a service task worker that runs in an asynchronous loop and invokes `jobHandler` when a job of type `service-task` becomes available. The handler must return a job completion function such as `fail`, `complete`, `error`, or `ignore`. The type system enforces this to ensure every code path responds to Zeebe after taking a job. The `job.complete` function can take an object with variables to update. ### Create a programmatic user task worker The process has a [user task](/guides/getting-started-orchestrate-human-tasks.md) after the service task. The service task worker completes the service task job. You complete the user task using the Tasklist API client. Add the following code below the service worker: ```typescript // User task poller const last = new Set(); const userTaskPoller = camunda.searchUserTasks( { filter: { state: "CREATED", }, }, { // To set up a subscription, set waitUpToMs to Infinity consistency: { waitUpToMs: Infinity, pollIntervalMs: 1_000, // predicate now becomes a polling subscription function predicate: async (results) => { // polling memoization - handles idempotency with eventually consistent mutation const current = results.items.filter( (item) => !last.has(item.userTaskKey) ); last.clear(); results.items.forEach((task) => last.add(task.userTaskKey)); for (const userTask of current) { console.log( `[usertask poller]: Claiming task ${userTask.userTaskKey} from process ${userTask.processInstanceKey}\n` ); await camunda.assignUserTask({ userTaskKey: userTask.userTaskKey, assignee: "jwulf", }); console.log( `[usertask poller]: Completing user task ${userTask.userTaskKey} from process ${userTask.processInstanceKey}\n` ); await camunda.completeUserTask({ userTaskKey: userTask.userTaskKey, variables: { userTaskStatus: "Got done", }, }); } return false; // return false to keep polling }, }, } ); ``` You now have an asynchronously polling service and user task worker. The final step is to create a process instance. ### Create a process instance There are two options for creating a process instance: - For long-running processes, use `createProcessInstance`. It returns as soon as the process instance is created with the process instance ID. - For the shorter-running process we are using, set `awaitCompletion: true`. It awaits the completion of the process and returns with the final variable values. 1. Locate the following line in the `main` function: ```typescript console.log( `[Zeebe] Deployed process ${res.deployments[0].process.bpmnProcessId}` ); ``` 2. Inside the `main` function, add the following: ```typescript const result = await camunda.createProcessInstanceWithResult({ processDefinitionId, variables: { userTaskStatus: "Needs doing", }, awaitCompletion: true, }); console.log( `[Camunda] Finished Process Instance ${result.processInstanceKey}` ); console.log( `[Camunda] userTaskStatus is "${result.variables.userTaskStatus}"` ); console.log( `[Camunda] serviceTaskOutcome is "${result.variables.serviceTaskOutcome}"` ); worker.stop(); userTaskPoller.catch((e) => e); // Swallow cancel exception userTaskPoller.cancel(); // Cancel poller to exit app ``` 3. Run the program with the following command: ```bash npx tsx index.ts ``` You see output similar to the following: ``` [Camunda] Deployed process c8-sdk-demo [worker]: Completing job 4503599632829668 from process 4503599632829662 [usertask poller]: Claiming task 4503599632829678 from process 4503599632829662 [usertask poller]: Completing user task 4503599632829678 from process 4503599632829662 [Camunda] Finished Process Instance 4503599632829662 [Camunda] userTaskStatus is "Got done" [Camunda] serviceTaskOutcome is "We did it!" ``` The program continues running until you press `Ctrl+C` because both the service worker and the user task poller run in continuous loops. To explore more SDK functionality, use the examples below. ### Retrieve a process instance When you create a long-running process instance, you typically use `createProcessInstance` and get back the process instance key of the running process immediately instead of waiting for it to complete. To examine the process instance status, use the process instance key to query the Operate API. You can also check completed process instances in the same way. In the following example, you query the process instance created earlier. 1. Locate the following line in the `main` function: ```typescript console.log( `[Camunda] serviceTaskOutcome is "${result.variables.serviceTaskOutcome}"` ); ``` 2. Add the following after this line and inside the `main` function: ```typescript const historicalProcessInstance = await camunda.getProcessInstance( { processInstanceKey: result.processInstanceKey, }, { consistency: { waitUpToMs: 5000 } } ); console.log("[Camunda]", JSON.stringify(historicalProcessInstance, null, 2)); ``` When you run the program now, you should see additional output similar to the following: ``` { processInstanceKey: 4503599632829662, processVersion: 1, processDefinitionId: 'c8-sdk-demo', startDate: '2025-11-08T09:11:06.157+0000', endDate: '2025-11-08T09:11:12.403+0000', state: 'COMPLETED', processDefinitionKey: 2251799814900879, } ``` The state may appear as `ACTIVE` rather than `COMPLETED`. This happens because the data read over the API is historical data from the Zeebe exporter, and lags behind the actual state of the system. It is _eventually consistent_. ## Further resources See the [complete API documentation for the SDK](https://camunda.github.io/camunda-8-js-sdk/) and the [Orchestration Cluster API client](https://camunda.github.io/orchestration-cluster-api-js/classes/index.CamundaClient.html). --- ## Manage Orchestration Cluster API data consistency Learn how to manage eventually consistent data when using the Orchestration Cluster API. ## About eventual consistency Data in Camunda 8 is [eventually consistent](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-data-fetching.md#data-consistency). - To ensure that your applications behave explicitly and deterministically at runtime under different load scenarios, Orchestration Cluster API methods that access eventually consistent data take a required second parameter `consistency`. - This parameter lets you either ignore eventual consistency or manage how your application interacts with it. For example, if you search for a process instance immediately after you create it, the response may contain the new instance. If the data is not yet available, the search may return an empty result or a 404 error. ```typescript const camunda = createCamundaClient(); async function main() { const deploymentResponse = await camunda.deployResourcesFromFile([ "./process.bpmn", ]); const { processDefinitionKey } = deploymentResponse.processes[0]; const { processInstanceKey } = await camunda.createProcessInstance({ processDefinitionKey, }); // May return the process instance, but more likely will return an empty set const runningProcessInstance = await camunda.searchProcessInstances( { filter: { processInstanceKey, }, }, { consistencyManagement: { waitUpToMs: 0 } } ); console.log(JSON.stringify(runningProcessInstance, null, 2)); } main(); ``` To ignore eventual consistency, set `waitUpToMs` to 0. The operation returns immediately with the current API response. In this scenario, you are more likely to receive an empty set for a search operation or a 404 error for a get operation than to find the process instance you just created. Even though the system already created the process instance and returned its key, you might still receive a result such as: ```json { "items": [] } ``` In some situations, you may want to wait for eventual consistency to settle. The SDK provides you with an ergonomic surface for this. ## Manage eventual consistency If `waitUpToMs` is set to a value greater than `0` (for example, `10_000`), the SDK polls every 500 ms for up to that duration and returns a value as soon as one is available. For search operations, this is when the result set has a length > 0. For get operations, it is when the API returns 200 rather than 404. If no results appear within the specified time, the operation will throw an `EventualConsistencyTimeoutError`. This means eventually consistent operations return either a value or an error: ```typescript // get, 0: Value or 'NOT_FOUND' exception await camunda.getProcessInstance( { processInstanceKey, }, { consistency: { waitForMs: 0 } } ); // get, > 0: Value or EventualConsistencyTimeoutError await camunda.getProcessInstance( { processInstanceKey, }, { consistency: { waitForMs: 1_000 } } ); // search, 0: Value, including empty set await camunda.searchProcessInstances( { processInstanceKey, }, { consistency: { waitForMs: 0 } } ); // search, >0: Expected value or EventualConsistencyTimeoutError await camunda.searchProcessInstances( { processInstanceKey, }, { consistency: { waitForMs: 1_000 } } ); ``` Change the polling interval using the `pollIntervalMs` parameter. For query operations, optionally provide a custom predicate via the `predicate` parameter. The predicate function receives the current result set, and returns a boolean: `false` to continue polling or `true` to accept and propagate the current results. You can use this for advanced client-side filtering or to build a subscription mechanism. Eventually consistent operations return a cancelable promise. Calling `cancel` stops polling and cancels any in-flight network operation, then throws a `CancelSdkError`. --- ## Migrate to the Orchestration Cluster API(Typescript) Migrate an existing Camunda 8 TypeScript application from `@camunda8/sdk` to use the `@camunda8/orchestration-cluster-api`. ## Choose a client option For existing applications using `@camunda8/sdk`, the SDK includes the Orchestration Cluster API client by depending on the `@camunda8/orchestration-cluster-api` package and normalizing configuration to ensure forward compatibility without requiring configuration changes. Use the following guidance to choose between the SDK-bundled client and the focused `@camunda8/orchestration-cluster-api` package. Use the bundled client if: - You depend on APIs not supported by the focused client, such as gRPC. - You do not care about application size and do not want to modify your environment configuration. Import the focused client directly alongside the SDK if: - You do not use the gRPC API. - Your application targets Camunda 8.9 or later. - You intend to migrate completely to the Orchestration Cluster API and remove all other API usage in your application. - You do not mind changing or extending your application configuration. ### Key differences Using the bundled client in `@camunda8/sdk`: - Will always pull in as dependencies all the other API clients and their dependencies. - Uses the same configuration variables across the various clients (the SDK normalizes configuration). ```typescript const clientFactory = new Camunda8(); // Get a strongly typed CamundaClient const camunda = clientFactory.getOrchestrationClusterApiClient(); // Get a loosely typed CamundaClient const camundaLoose = clientFactory.getOrchestrationClusterApiClient(); ``` Using the focused client in `@camunda8/orchestration-cluster-api`: - Allows you to reduce the application dependency to the Orchestration Cluster API client only. - Requires you to change the environment configuration (the focused client uses distinct configuration). ```typescript createCamundaClient, createCamundaClientLoose, } from "@camunda8/orchestration-cluster-api"; // Get a strongly-typed CamundaClient const camunda = createCamundaClient(); // Get a loosely-typed CamundaClient const camundaLoose = createCamundaClientLoose(); ``` As a middle ground, you can use the bundled client initially and then switch to the focused client when you are ready to update the configuration. ## Strong vs. loose typing A feature of the Orchestration Cluster API is strong domain types. Request and response fields such as `ProcessDefinitionKey` and `ProcessDefinitionId` are distinct types, leading to better static analysis, enhanced IDE completion, more powerful refactoring, and early detection of runtime bugs. The client implements this using [nominal typing](https://en.wikipedia.org/wiki/Nominal_type_system). You can pass a `ProcessDefinitionId` wherever a string is expected, but cannot pass a free string or a `ProcessDefinitionKey` where a `ProcessDefinitionId` is expected — even though structurally they are all type `string`. ```typescript const camunda = new Camunda8().getOrchestrationClusterApiClient(); async function main() { const deploymentResponse = await camunda.deployResourcesFromFile([ "./process.bpmn", ]); const { processDefinitionKey } = deploymentResponse.processes[0]; // nominal type is ProcessDefinitionKey console.log(typeof processDefinitionKey); // works — structural type is 'string' await camunda.createProcessInstance({ processDefinitionId: processDefinitionKey, }); // error — incompatible types } main(); ``` This approach makes output from the Orchestration Cluster API client compatible with input fields of earlier clients, but the reverse is not true. The earlier clients accept the structural types of the new client, and do not examine the nominal types at all. ```typescript const factory = new Camunda8(); const camunda = new factory.getOrchestrationClusterApiClient(); const camundaLegacy = new factory.getCamundaRestClient(); async function main() { const deploymentResponse = await camunda.deployResourcesFromFile([ "./process.bpmn", ]); const { processDefinitionKey } = deploymentResponse.processes[0]; // nominal type is ProcessDefinitionKey console.log(typeof processDefinitionKey); // structural type is 'string' const { processInstanceKey } = await camundaLegacy.createProcessInstance({ processDefinitionKey, }); // works — structural type is string const runningProcessInstance = await camunda.searchProcessInstances( { filter: { processInstanceKey, // fails — legacy type is `string`, client requires `ProcessInstanceKey` }, }, { consistencyManagement: { waitUpToMs: 10_000 } } ); } main(); ``` ## Integrate the strongly typed client To handle this, you can either manage the type system boundary or erase nominal typing. The new client provides lifters to deal with interoperability. For example: ```typescript const factory = new Camunda8() const camunda = new factory.getOrchestrationClusterApiClient() const legacyCamunda = new factory.getCamundaRestClient() async function main() { const deploymentResponse = await legacyCamunda.deployResourcesFromFile(['./process.bpmn']) const { processDefinitionKey } = deploymentResponse.processes[0] console.log(typeof processDefinitionKey) // structural type is 'string' const {processInstanceKey} = await legacyCamunda.createProcessInstance({ processDefinitionKey }) // works — structural type is string const const runningProcessInstance = await camunda.searchProcessInstances({ filter: { processInstanceKey: processInstanceKey as OrchestrationLifters.ProcessInstanceKey // cast to `ProcessInstanceKey` } }, { consistencyManagement: { waitUpToMs: 10_000 } }) } main() ``` Rather than casting in multiple places, you can do it once via assignment with the `assumeExists` lifter. The lifter will cast the type and also apply runtime constraint validation. ```typescript const camunda = new Camunda8().getOrchestrationClusterApi(); async function cancelRunningProcessInstances(processDefinitionKey: string) { // lift to nominal type `ProcessInstanceKey`. Will throw if passed invalid format. const _processDefinitionKey = OrchestrationLifters.ProcessDefinitionKey.assumeExists( processDefinitionKey ); const processes = await camunda.searchProcessInstances( { filter: { processDefinitionKey: _processDefinitionKey, }, }, { consistencyManagement: { waitUpToMs: 10_000 } } ); for (const process in processes.items) { await camunda.cancelProcess(process.processInstanceKey); } } ``` :::info This approach is the preferred method. This allows you to move the type and constraint validation boundary from the server API to the boundaries of your application. See [the presentation on patterns](https://www.camundacon.com/event-session/camundacon-new-york-2025/patterns-to-use-today-to-make-camunda-8-apps-even-more-reliable?on_demand=true) for more information (refer to the third demo). ::: ## Integrate the loosely-typed client The SDK provides a legacy-compatible loosely-typed client. This erases the domain typing (all fields remain type `string`). This enables you to use the new client without managing type interaction with your existing code. ```typescript const factory = new Camunda8() const camunda = new factory.getOrchestrationClusterApiClientLoose() const legacyCamunda = new factory.getCamundaRestClient() async function main() { const deploymentResponse = await legacyCamunda.deployResourcesFromFile(['./process.bpmn']) const { processDefinitionKey } = deploymentResponse.processes[0] const {processInstanceKey} = await legacyCamunda.createProcessInstance({ processDefinitionKey }) // string const const runningProcessInstance = await camunda.searchProcessInstances({ filter: { processInstanceKey // accepts string type } }, { consistencyManagement: { waitUpToMs: 10_000 } }) } main() ``` You can use loosely-typed in the first instance, then progressively migrate to the strongly typed variant. ## Configuration The Orchestration Cluster API client uses the `ZEEBE_REST_ADDRESS` configuration value to connect to the server. ## Refactor API calls The Orchestration Cluster API has command and query operations. All operations searching data using the v1 component APIs can be refactored to use the equivalent Orchestration Cluster API method. Some method signatures have changed, mostly in the names of fields. Your IDE intellisense will guide you in refactoring to the new signatures. ## Data access and consistency Search and get operations require a second parameter to manage eventual consistency. :::info See the examples in the [TypeScript SDK guide](./camunda8-sdk.md) and the [manage Orchestration Cluster API data consistency](./eventual-consistency.md) overview. ::: ## Manage backpressure The new client has enhanced backpressure management. :::info See [manage backpressure using the TypeScript SDK](./backpressure.md). ::: --- ## Orchestration Cluster API TypeScript client Use the Orchestration Cluster API TypeScript client to connect to Camunda 8, deploy process models, and interact with the Orchestration Cluster REST API. ## About this client The `@camunda8/orchestration-cluster-api` package provides focused support for the Orchestration Cluster REST API. ### When to use this package Use the [`@camunda8/orchestration-cluster-api`](https://www.npmjs.com/package/@camunda8/orchestration-cluster-api) package if: - You are starting a new project. - You do not need the gRPC API. - You are using Camunda 8.9 or later. - You are developing an application for the web browser. ### Differences between the package and the SDK | Difference | Description | | :----------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Configuration keys | Environment variable configuration is different when using the package directly or using it via the SDK.The SDK wraps the Orchestration Cluster API client package and allows you to use the previous configuration keys.When using the package directly, you must use the environment variable configuration keys it requires. Keys that were prefixed with `ZEEBE_` in the SDK are now prefixed with `CAMUNDA_` in the Orchestration Cluster API client package configuration.See the [Configuration Reference](https://github.com/camunda/orchestration-cluster-api-js/blob/main/documentation/CONFIG_REFERENCE.md?plain=1) for a list of configuration parameters. | | ESM | The Orchestration Cluster API client is a dual ESM/CJS package, allowing you to use ESM and tree shake the package as a dependency. | ## Use the Orchestration Cluster API package The following example retrieves the cluster topology: 1. Install the package in your project: ```bash npm i @camunda8/orchestration-cluster-api ``` 2. Import it into your application: ```typescript const camunda = createCamundaClient(); async function main() { const response = await camunda.getTopology(); console.log(JSON.stringify(response, null, 2)); } main(); ``` The `createCamundaClient` function returns a strongly typed client. ## Example project See a [complete example project](https://github.com/camunda-community-hub/c8-sdk-demo) that demonstrates how to use the package. ## API documentation See the [package README](https://camunda.github.io/orchestration-cluster-api-js/) and the [full API documentation](https://camunda.github.io/orchestration-cluster-api-js/classes/index.CamundaClient.html) for more details. --- ## TypeScript SDK(Typescript) ## Get started Choose how you want to get started with Camunda 8 TypeScript development: ## Features and concepts Learn more about Camunda TypeScript SDK features and important concepts: :::info Learn more about the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). ::: --- ## Authentication(Web-modeler-api) All Web Modeler API requests require authentication. To authenticate, generate a [JSON Web Token (JWT)](https://jwt.io/introduction/) depending on your environment and include it in each request. :::note Clients using a valid generated token have access to all resources within an organization, similar to [organization admin and owner access](/components/hub/organization/users-and-roles.md#elevated-workspace-access). While there's no project-level access control enforced in the API, access is still dependent on the [CRUD operations assigned](#generate-a-token). ::: ## Generate a token 1. Create client credentials by clicking **Organization > Manage organization > Administration API > Create new credentials**. 2. Add permissions to this client for **Web Modeler API** with the needed CRUD permissions. 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. 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 **Web Modeler API** with the needed [CRUD permissions](/self-managed/components/management-identity/access-management/access-management-overview.md#preset-permissions). 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 Web Modeler 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 Web Modeler API's `/info` endpoint: ```shell curl --header "Authorization: Bearer ${TOKEN}" \ https://modeler.cloud.camunda.io/api/v1/info ``` :::tip The `${WEB_MODELER_REST_URL}` variable below represents the URL of the Web Modeler API. You can configure this value in your Self-Managed installation. The default value is `http://localhost:8070`. ::: ```shell curl --header "Authorization: Bearer ${TOKEN}" \ ${WEB_MODELER_REST_URL}/api/v1/info ``` A successful response includes [information about the environment](https://modeler.camunda.io/swagger-ui/index.html#/Info/getInfo). For example: ```json { "version": "v1", "authorizedOrganization": "12345678-ABCD-DCBA-ABCD-123456789ABC", "createPermission": true, "readPermission": true, "updatePermission": true, "deletePermission": false } ``` ## Organization-level access API tokens are granted to organization-level _applications_ (Self-Managed) or _clients_ (SaaS) rather than individual _users_. With an API token, you can read, edit, and delete all workspaces ([called "projects" before Camunda 8.10](../migration-manuals/migrate-from-web-modeler-to-hub-api.md#structure-and-terminology)) and workspace resources in the organization, as long as the application or client has the required Web Modeler API permissions. This is true even if you aren't a member of the workspace and you can't see it in the Camunda Hub user interface. ## 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. --- ## Web Modeler API :::note DEPRECATED Web Modeler API v1 is deprecated in Camunda 8.10 and will be removed in 8.12. [Migrate to Camunda Hub REST API v2](../migration-manuals/migrate-from-web-modeler-to-hub-api.md). ::: Use the Web Modeler API to access your Web Modeler data. ## About You can use the Web Modeler REST API to programmatically access your Web Modeler data. - Web Modeler provides a REST API at `/api/*`. - Requests and responses are in JSON notation. ## Authentication Clients can only access the Web Modeler REST API by passing a JWT access token in an authorization header `Authorization: Bearer `. Details are covered in the [Authentication](authentication.md) section. ## API reference A detailed API description is available as [OpenAPI](https://www.openapis.org/) specification at [https://modeler.camunda.io/swagger-ui/index.html](https://modeler.camunda.io/swagger-ui/index.html) for SaaS and at [http://localhost:8070/swagger-ui.html](http://localhost:8070/swagger-ui.html) for Self-Managed installations. ## API in Postman Work with this API in our [Postman collection](https://www.postman.com/camundateam/workspace/camunda-8-postman/collection/26079299-0bb668f4-af6a-4ab0-88a3-c78b900125ed?action=share&creator=11465105). ## Usage notes When using Web Modeler API: - You will not receive a warning when deleting a file, a folder, or a project. This is important, because deletion cannot be undone. - You will not receive a warning about breaking call activity links or business rule task links when moving files or folders to another project. Breaking these links is considered harmless. The broken links can be manually removed or restored in Web Modeler. This operation is also reversible - simply move the files or folders back to their original location. ## Rate limiting In SaaS, the Web Modeler API uses rate limiting to control traffic. The limit is 240 requests per minute. Surpassing this limit will result into a `HTTP 429 Too Many Requests` response. On Self-Managed instances no limits are enforced. ## FAQ ### What is the difference between _simplePath_ and _canonicalPath_? In Web Modeler you can have multiple files with the same name, multiple folders with the same name, and even multiple projects with the same name. Internally, duplicate names are disambiguated by unique IDs. The API gives you access to the names, as well as the IDs. For example, when requesting a file you will get the following information: - **simplePath** contains the human-readable path. This path may be ambiguous or may have ambiguous elements (e.g. folders) in it. - **canonicalPath** contains the unique path. It is a list of **PathElementDto** objects which contain the ID and the name of the element. Internally, the IDs are what matters. You can rename files or move files between folders and projects and the ID will stay the same. --- ## Tutorial(Web-modeler-api) In this tutorial, we'll step through examples to highlight the capabilities of the Web Modeler API, such as creating a new project, adding a collaborator to a project, viewing the details of a project, and deleting a project. This tutorial focuses on using the Web Modeler API in a Camunda 8 SaaS environment, but the same principles apply to a Self-Managed environment. :::note DEPRECATED Web Modeler API v1 is deprecated in Camunda 8.10 and will be removed in 8.12. [Migrate to Camunda Hub REST API v2](../migration-manuals/migrate-from-web-modeler-to-hub-api.md). ::: ## Prerequisites - Create your first client by navigating to **Organization > Manage organization > Administration API > Create new credentials**. Ensure you determine the scoped access for client credentials. For example, in this tutorial we will create a project, add a collaborator, and delete a project. Ensure you check the box for the Web Modeler scope. :::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://modeler.cloud.camunda.io/swagger-ui/index.html) 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/web-modeler-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 `MODELER_CLIENT_ID`, `MODELER_CLIENT_SECRET`, `MODELER_AUDIENCE`, which is `modeler.cloud.camunda.io` in a Camunda 8 SaaS environment, and `MODELER_BASE_URL`, which is `https://modeler.camunda.io/api/v1`. 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`, and `CAMUNDA_CONSOLE_OAUTH_AUDIENCE`. ::: 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 create a project, add a collaborator, and delete a project. You can examine the framework for processing these arguments in the `cli.js` file before getting started. ::: ## Create a new project (POST) and add a collaborator (PUT) First, let's script an API call to create a new project. To do this, take the following steps: 1. In the file named `modeler.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.MODELER_CLIENT_ID, clientSecret: process.env.MODELER_CLIENT_SECRET, audience: process.env.MODELER_AUDIENCE, }; ``` 2. Examine the function `async function createProject([projectName, adminEmail])` below this configuration. This is where you will script out your API call, defining a project name and the project administrator's email. 3. Within the function, you must first generate an access token for this request, so your function should now look like the following: ```javascript async function createProject([projectName, adminEmail]) { const accessToken = await getAccessToken(authorizationConfiguration); } ``` 4. Using your generated client credentials from [prerequisites](#prerequisites), capture your Web Modeler base URL beneath your call for an access token by defining `modelerApiUrl`: ```javascript const modelerApiUrl = process.env.MODELER_BASE_URL; ``` 5. On the next line, script the API endpoint to create your project: ```javascript const projectUrl = `${modelerApiUrl}/projects`; ``` 6. Configure your POST request to the appropriate endpoint, including an authorization header based on the previously acquired `accessToken`. You will also add a body to outline information about the new project: ```javascript const projectOptions = { method: "POST", url: projectUrl, headers: { Accept: "application/json", Authorization: `Bearer ${accessToken}`, }, data: { name: projectName, }, }; ``` 7. Call the add project endpoint and capture the data for the new project: ```javascript try { const response = await axios(projectOptions); const newProject = response.data; console.log( `Project added! Name: ${newProject.name}. ID: ${newProject.id}.` ); ``` 8. Next, we'll add a collaborator to the project you just created. After calling the add project endpoint, add an endpoint to add a collaborator to the project: ```javascript const collaboratorUrl = `${modelerApiUrl}/collaborators`; ``` 9. Configure the API call, including a body with information about the project and the new collaborator: ```javascript const collaboratorOptions = { method: "PUT", url: collaboratorUrl, headers: { Accept: "application/json", Authorization: `Bearer ${accessToken}` }, data: { email: adminEmail, projectId: newProject.id, role: "project_admin" } ``` 10. Call the add collaborator endpoint and process the results: ```javascript const collaboratorResponse = await axios(collaboratorOptions); if (collaboratorResponse.status === 204) { console.log(`Collaborator added! Email: ${adminEmail}.`); } else { console.error("Unable to add collaborator!"); } } catch (error) { // Emit an error from the server. console.error(error.message); } ``` 11. In your terminal, run `npm run cli modeler create` to create your project. :::note This `create` command is connected to the `createProject` function at the bottom of the `modeler.js` file, and executed by the `cli.js` file. While we create a project in this tutorial, you may add additional arguments depending on the API calls you would like to make. ::: ## View project details (GET) To view project details, take the following steps: 1. Outline your function, similar to the steps above: ```javascript async function viewProject([projectId]) { const accessToken = await getAccessToken(authorizationConfiguration); const modelerApiUrl = process.env.MODELER_BASE_URL; const url = `${modelerApiUrl}/projects/${projectId}`; } ``` 2. Configure the API call using the GET method: ```javascript const options = { method: "GET", 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); const project = response.data; console.log("Project:", project); } catch (error) { console.error(error.message); } ``` 4. In your terminal, run `npm run cli modeler view `, where `` is the ID output by the command to create a project. ## Delete a project To delete a project, take the following steps: 1. Outline your function, similar to the steps above: ```javascript async function deleteProject([projectId]) { const accessToken = await getAccessToken(authorizationConfiguration); const modelerApiUrl = process.env.MODELER_BASE_URL; const url = `${modelerApiUrl}/projects/${projectId}`; ``` 2. Configure the API call using the DELETE method: ```javascript const 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(`Project ${projectId} was deleted!`); } else { console.error("Unable to delete project!"); } } catch (error) { console.error(error.message); } ``` 4. In your terminal, run `npm run cli modeler delete `, where `` is the ID output by the command to create a project. ## 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 `modeler.js` file. ## Next steps You can script several additional API calls as outlined in the [Web Modeler API reference material](/apis-tools/web-modeler-api/index.md). --- ## APIs & tools Camunda 8 APIs and official clients and SDKs. Use Camunda 8 APIs and clients to build, automate, and monitor your applications. Use the official Camunda clients and SDKs (Java, Spring, and Node.js) to simplify API usage and speed up development. [Get started with the Camunda Java Client](./java-client/getting-started.md) :::info Upgrade to Camunda 8.10 - Existing customer? Upgrade your APIs & tools to 8.10 using the [APIs & tools migration guide](/apis-tools/migration-manuals/migrate-to-810.md). - See [what's new in Camunda 8.11](/reference/announcements-release-notes/8110/whats-new-in-811.md), [release announcements](/reference/announcements-release-notes/8110/8110-announcements.md), and [release notes](/reference/announcements-release-notes/8110/8110-release-notes.md). ::: ## APIs Use the following APIs for Camunda 8 integration and automation: ## API clients Camunda provides the following official clients to simplify API usage and speed up development: :::note community clients In addition to the core Camunda-maintained clients, there are a number of [community-maintained component clients](/apis-tools/community-clients/index.md). ::: ## Client and API compatibility Camunda clients and SDKs are **forward-compatible** with the Orchestration Cluster, meaning you can upgrade the cluster first and clients after. The Orchestration Cluster REST API is backward-compatible, ensuring no breaking changes to existing endpoints across versions. [Client and API compatibility guarantees](/reference/public-api.md#client-and-api-compatibility) ## Testing Use Camunda Process Test to test your process definitions and automations with a dedicated testing framework. [Camunda Process Test](/apis-tools/testing/getting-started.md) ## Upgrade to Camunda 8.10 If you are migrating from Camunda 7 or from v1 component REST APIs, see the migration guide for guidance. [Camunda 8.10 APIs & tools migration guide](/apis-tools/migration-manuals/migrate-to-810.md) --- ## Deprecated RPCs The following RPCs are exposed by the gateway service, but have been deprecated. ## `DeployProcess` RPC :::note Deprecated since 8, replaced by [DeployResource RPC](#deployresource-rpc). ::: :::note When multi-tenancy is enabled, processes are always deployed to the `` tenant. ::: Deploys one or more processes to Zeebe. Note that this is an atomic call, i.e. either all processes are deployed, or none of them are. ### Input: `DeployProcessRequest` ```protobuf message DeployProcessRequest { // List of process resources to deploy repeated ProcessRequestObject processes = 1; } message ProcessRequestObject { enum ResourceType { // FILE type means the gateway will try to detect the resource type // using the file extension of the name field FILE = 0; BPMN = 1; // extension 'bpmn' YAML = 2 [deprecated = true]; // extension 'yaml'; removed as of release 1.0 } // the resource basename, e.g. myProcess.bpmn string name = 1; // the resource type; if set to BPMN or YAML then the file extension // is ignored // As of release 1.0, YAML support was removed and BPMN is the only supported resource type. // The field was kept to not break clients. ResourceType type = 2 [deprecated = true]; // the process definition as a UTF8-encoded string bytes definition = 3; } ``` ### Output: `DeployProcessResponse` ```protobuf message DeployProcessResponse { // the unique key identifying the deployment int64 key = 1; // a list of deployed processes repeated ProcessMetadata processes = 2; } message ProcessMetadata { // the bpmn process ID, as parsed during deployment; together with the version forms a // unique identifier for a specific process definition string bpmnProcessId = 1; // the assigned process version int32 version = 2; // the assigned key, which acts as a unique identifier for this process int64 processKey = 3; // the resource name (see: ProcessRequestObject.name) from which this process was // parsed string resourceName = 4; } ``` ### Errors #### GRPC_STATUS_INVALID_ARGUMENT Returned if: - No resources given. - At least one resource is invalid. A resource is considered invalid if: - It is not a BPMN or YAML file (currently detected through the file extension). - The resource data is not deserializable (e.g. detected as BPMN, but it's broken XML). - The process is invalid (e.g. an event-based gateway has an outgoing sequence flow to a task.) --- ## Zeebe API RPCs The Zeebe client gRPC API is exposed through a single gateway service. The current version of the protocol buffer file can be found in the [Zeebe repository](https://github.com/camunda/camunda/blob/main/zeebe/gateway-protocol/src/main/proto/gateway.proto). ## Default service config Along with the gateway protocol definition, the gateway service also bundles a [default service configuration file](https://github.com/camunda/camunda/blob/main/zeebe/gateway-protocol-impl/src/main/resources/gateway-service-config.json). This file can be used as is, or as a template to create your own, and defines default retry strategies on a per-RPC basis: when to retry (based on error code), how often, how soon, etc. This file is also loaded by the [Camunda Java client](../java-client/getting-started.md) if `useDefaultRetryPolicy` is set to true. :::note Read more about [service configuration files](https://github.com/grpc/grpc/blob/master/doc/service_config.md). These files are especially useful when using the Camunda protocol in languages without, or with less feature-rich clients and SDKs. ::: Usage of this file largely depends on the gRPC bindings for your language of choice. For example, when using Java, you would programmatically configure your client using: ```java final ObjectMapper objectMapper = new ObjectMapper(); final File configFile = new File("gateway-service-config.json"); final Map serviceConfig = objectMapper.readValue( configFile, new TypeReference>() {}); final ManagedChannelBuilder channelBuilder = ManagedChannelBuilder.forAddress("localhost", 26500); channelBuilder.defaultServiceConfig(serviceConfig); channelBuilder.enableRetry(); ``` ## `ActivateJobs` RPC Iterates through all known partitions round-robin, activates up to the requested maximum, and streams them back to the client as they are activated. ### Input: `ActivateJobsRequest` ```protobuf message ActivateJobsRequest { // the job type, as defined in the BPMN process (e.g. ) string type = 1; // the name of the worker activating the jobs, mostly used for logging purposes string worker = 2; // a job returned after this call will not be activated by another call until the // timeout (in ms) has been reached int64 timeout = 3; // the maximum jobs to activate by this request int32 maxJobsToActivate = 4; // a list of variables to fetch as the job variables; if empty, all visible variables at // the time of activation for the scope of the job will be returned repeated string fetchVariable = 5; // The request will be completed when at least one job is activated or after the requestTimeout (in ms). // if the requestTimeout = 0, a default timeout is used. // if the requestTimeout < 0, long polling is disabled and the request is completed immediately, even when no job is activated. int64 requestTimeout = 6; // a list of IDs of tenants for which to activate jobs repeated string tenantIds = 7; // whether to activate the jobs with a lease; when true, each activated job is assigned a // distinct, opaque lease token, returned as ActivatedJob.jobLeaseToken. The lease fences the // complete, fail, and throw-error commands against a superseded activation of the same job // (e.g. after the job timed out or failed and was re-activated by another worker): a command // carrying a stale lease token is rejected rather than racing with the newer activation. Once // a job has been activated with a lease, it is served only to leasing workers of that job // type; a homogeneous fleet per job type is recommended. Defaults to false, which activates // jobs without a lease. bool withLease = 9; } ``` If `requestTimeout` is set to `0`, the effective timeout depends on whether long polling is enabled: - If long polling is enabled, the gateway uses its configured long-polling timeout (`camunda.api.long-polling.timeout` / `zeebe.gateway.longPolling.timeout`, default 10,000 ms). - If long polling is disabled, the request falls back to a client-side timeout. For gRPC clients, this currently defaults to 10,000 ms. If `requestTimeout` is set to a value less than `0`, long polling is disabled and the request completes immediately, even when no job is activated. ### Output: `ActivateJobsResponse` ```protobuf message ActivateJobsResponse { // list of activated jobs repeated ActivatedJob jobs = 1; } message ActivatedJob { // Describes the kind of job. enum JobKind { BPMN_ELEMENT = 0; EXECUTION_LISTENER = 1; TASK_LISTENER = 2; } // Describes the listener event type of the job. enum ListenerEventType { ASSIGNING = 0; CANCELING = 1; COMPLETING = 2; CREATING = 3; END = 4; START = 5; UNSPECIFIED = 6; UPDATING = 7; } // the key, a unique identifier for the job int64 key = 1; // the type of the job (should match what was requested) string type = 2; // the job's process instance key int64 processInstanceKey = 3; // the bpmn process ID of the job process definition string bpmnProcessId = 4; // the version of the job process definition int32 processDefinitionVersion = 5; // the key of the job process definition int64 processDefinitionKey = 6; // the associated task element ID string elementId = 7; // the unique key identifying the associated task, unique within the scope of the // process instance int64 elementInstanceKey = 8; // a set of custom headers defined during modelling; returned as a serialized // JSON document string customHeaders = 9; // the name of the worker which activated this job string worker = 10; // the amount of retries left to this job (should always be positive) int32 retries = 11; // when the job can be activated again, sent as a UNIX epoch timestamp int64 deadline = 12; // JSON document, computed at activation time, consisting of all visible variables to // the task scope string variables = 13; // the ID of the tenant that owns the job string tenantId = 14; // the kind of the job. JobKind kind = 15; // the listener event type of the job. ListenerEventType listenerEventType = 16; // the lease token identifying this activation; unset when the job was activated without a // lease optional string jobLeaseToken = 21; } ``` ### Errors #### GRPC_STATUS_INVALID_ARGUMENT Returned if: - Type is blank (empty string, null) - Worker is blank (empty string, null) - Timeout less than 1 (ms) - maxJobsToActivate is less than 1 - If multi-tenancy is enabled, and `tenantIds` is empty (empty list) - If multi-tenancy is enabled, and an invalid tenant ID is provided. A tenant ID is considered invalid if: - The tenant ID is blank (empty string, null) - The tenant ID is longer than 31 characters - The tenant ID contains anything other than alphanumeric characters, dot (.), dash (-), or underscore (\_) - If multi-tenancy is disabled, and `tenantIds` is not empty (empty list), or has an ID other than `` #### GRPC_STATUS_PERMISSION_DENIED - If multi-tenancy is enabled, and an unauthorized tenant ID is provided ## `BroadcastSignal` RPC Broadcasts a [signal](/components/concepts/signals.md). ### Input: `BroadcastSignalRequest` ```protobuf message BroadcastSignalRequest { // The name of the signal string signalName = 1; // the signal variables as a JSON document; to be valid, the root of the document must be an // object, e.g. { "a": "foo" }. [ "foo" ] would not be valid. string variables = 2; // the ID of the tenant that owns the signal. string tenantId = 3; } ``` ### Output: `BroadcastSignalResponse` ```protobuf message BroadcastSignalResponse { // the unique ID of the signal that was broadcasted. int64 key = 1; // the tenant ID of the signal that was broadcasted. string tenantId = 2; } ``` ### Errors #### GRPC_STATUS_NOT_FOUND - If multi-tenancy is enabled, and `tenantId` is blank (empty string, null) - If multi-tenancy is enabled, and an invalid tenant ID is provided. A tenant ID is considered invalid if: - The tenant ID is blank (empty string, null) - The tenant ID is longer than 31 characters - The tenant ID contains anything other than alphanumeric characters, dot (.), dash (-), or underscore (\_) - If multi-tenancy is disabled, and `tenantId` is not blank (empty string, null), or has an ID other than `` #### GRPC_STATUS_PERMISSION_DENIED - If multi-tenancy is enabled, and an unauthorized tenant ID is provided ## `CancelProcessInstance` RPC Cancels a running process instance. ### Input: `CancelProcessInstanceRequest` ```protobuf message CancelProcessInstanceRequest { // the process instance key (as, for example, obtained from // CreateProcessInstanceResponse) int64 processInstanceKey = 1; } ``` ### Output: `CancelProcessInstanceResponse` ```protobuf message CancelProcessInstanceResponse { } ``` ### Errors #### GRPC_STATUS_NOT_FOUND Returned if: - No process instance exists with the given key. Note that since process instances are removed once they are finished, it could mean the instance did exist at some point. - No process instance exists with the given key for the tenants the user is authorized to work with. ## `CompleteJob` RPC Completes a job with the given payload, which allows completing the associated service task. ### Input: `CompleteJobRequest` ```protobuf message CompleteJobRequest { // the unique job identifier, as obtained from ActivateJobsResponse int64 jobKey = 1; // a JSON document representing the variables in the current task scope string variables = 2; // The result of the completed job as determined by the worker. // This functionality is currently supported only by user task listeners optional JobResult result = 3; // the token identifying a leased job's activation, obtained from ActivatedJob.jobLeaseToken. // For a leased job, the matching token must be supplied to prove the command comes from the // worker that holds the current lease; a command with no token is rejected. A command carrying // a stale token is likewise rejected, fencing the job against a superseded activation (e.g. // after the job timed out or failed and was re-activated by another worker). A job that was // activated without a lease requires no token. optional string jobLeaseToken = 4; } message JobResult{ // Indicates whether the worker denies the work, or explicitly doesn't approve it. // For example, a user task listener can deny the completion of a user task by setting this flag to true. // In this example, the completion of a task is represented by a job that the worker can complete as denied. // As a result, the completion request is rejected and the task remains active. // Defaults to false. // Only applicable for user task listener jobs. optional bool denied = 1; // Attributes that were corrected by the worker. // The following attributes can be corrected, additional attributes will be ignored: // * `assignee` - clear by providing an empty string // * `dueDate` - clear by providing an empty string // * `followUpDate` - clear by providing an empty string // * `candidateGroups` - clear by providing an empty list // * `candidateUsers` - clear by providing an empty list // * `priority` - minimum 0, maximum 100, default 50 // Omitting any of the attributes will preserve the persisted attribute's value. // Only applicable for user task listener jobs. optional JobResultCorrections corrections = 2; // The reason provided by the user task listener for denying the work. optional string deniedReason = 3; // Identifies the type of job result. Must be either "userTask" or "adHocSubprocess". // Defaults to "userTask" if not explicitly set. optional string type = 4; // The list of elements that should be activated after the job is completed. // Only applicable for ad-hoc subprocesses. repeated JobResultActivateElement activateElements = 5; } message JobResultCorrections { // The assignee of the task. optional string assignee = 1; // The due date of the task. optional string dueDate = 2; // The follow-up date of the task. optional string followUpDate = 3; // The list of candidate users of the task. optional StringList candidateUsers = 4; // The list of candidate groups of the task. optional StringList candidateGroups = 5; // The priority of the task. optional int32 priority = 6; } message JobResultActivateElement { // The id of the element to activate string elementId = 1; // JSON document of variables that will be created on the scope of the activated element. // It must be a JSON object, as variables will be mapped in a key-value fashion. // e.g. { "a": 1, "b": 2 } will create two variables, named "a" and // "b" respectively, with their associated values. [{ "a": 1, "b": 2 }] would not be a // valid argument, as the root of the JSON document is an array and not an object. string variables = 2; } message StringList { // Wrapper around a list of string values. repeated string values = 1; } ``` ### Output: `CompleteJobResponse` ```protobuf message CompleteJobResponse { } ``` ### Errors #### GRPC_STATUS_NOT_FOUND Returned if: - No job exists with the given job key. Note that since jobs are removed once completed, it could be that this job did exist at some point. - No job exists with the given job key for the tenants the user is authorized to work with. #### GRPC_STATUS_FAILED_PRECONDITION Returned if: - The job was marked as failed. In that case, the related [incident](/components/concepts/incidents.md) must be resolved before the job can be activated again and completed. ## `CreateProcessInstance` RPC Creates and starts an instance of the specified process. The process definition to use to create the instance can be specified either using its unique key (as returned by DeployProcess), or using the BPMN process ID and a version. Pass -1 as the version to use the latest deployed version. :::note Only processes with none start events can be started through this command. ::: :::note Start and runtime instructions have the same [limitations as process instance modification](/components/concepts/process-instance-modification.md#limitations), e.g., it is not possible to start at a sequence flow or terminate a process instance when a sequence flow completes. ::: ### Input: `CreateProcessInstanceRequest` ```protobuf message CreateProcessInstanceRequest { // the unique key identifying the process definition (e.g. returned from a process // in the DeployProcessResponse message) int64 processDefinitionKey = 1; // the BPMN process ID of the process definition string bpmnProcessId = 2; // the version of the process; set to -1 to use the latest version int32 version = 3; // JSON document that will instantiate the variables for the root variable scope of the // process instance; it must be a JSON object, as variables will be mapped in a // key-value fashion. e.g. { "a": 1, "b": 2 } will create two variables, named "a" and // "b" respectively, with their associated values. [{ "a": 1, "b": 2 }] would not be a // valid argument, as the root of the JSON document is an array and not an object. string variables = 4; // List of start instructions. If empty (default) the process instance // will start at the start event. If non-empty the process instance will apply start // instructions after it has been created repeated ProcessInstanceCreationStartInstruction startInstructions = 5; // the tenant id of the process definition string tenantId = 6; // a reference key chosen by the user and will be part of all records resulted from this operation optional uint64 operationReference = 7; // a list of runtime instruction that can modify the behavior of the process // instance during its execution // if empty (default), the process instance will be executed normally repeated ProcessInstanceCreationRuntimeInstruction runtimeInstructions = 8; // a list of tags that can be attached as meta-data to process instances repeated string tags = 9; // an optional, user-defined string identifier that identifies the process instance // within the scope of the process definition (scoped by tenant). If provided and uniqueness // enforcement is enabled, the engine will reject creation if another root process instance // with the same business id is already active for the same process definition. // Note that any active child process instances with the same business id are not taken into account. optional string businessId = 10; } message ProcessInstanceCreationStartInstruction { // future extensions might include // - different types of start instructions // - ability to set local variables for different flow scopes // for now, however, the start instruction is implicitly a // "startBeforeElement" instruction // element ID string elementId = 1; } message ProcessInstanceCreationRuntimeInstruction { oneof instruction { TerminateProcessInstanceInstruction terminate = 1; } } message TerminateProcessInstanceInstruction { // the ID of the process element after which the process instance should be // terminated string afterElementId = 1; } ``` ### Output: `CreateProcessInstanceResponse` ```protobuf message CreateProcessInstanceResponse { // the key of the process definition which was used to create the process instance int64 processDefinitionKey = 1; // the BPMN process ID of the process definition which was used to create the process // instance string bpmnProcessId = 2; // the version of the process definition which was used to create the process instance int32 version = 3; // the unique identifier of the created process instance; to be used wherever a request // needs a process instance key (e.g. CancelProcessInstanceRequest) int64 processInstanceKey = 4; // the tenant identifier of the created process instance string tenantId = 5; // tags attached to a process instance repeated string tags = 6; // the business id of the created process instance optional string businessId = 7; } ``` ## `CreateProcessInstanceWithResult` RPC Similar to `CreateProcessInstance` RPC, creates and starts an instance of the specified process. Unlike `CreateProcessInstance` RPC, the response is returned when the process is completed. :::note Only processes with none start events can be started through this command. ::: :::note Start instructions have the same [limitations as process instance modification](/components/concepts/process-instance-modification.md#limitations), e.g., it is not possible to start at a sequence flow. ::: ### Input: `CreateProcessInstanceWithResultRequest` ```protobuf message CreateProcessInstanceWithResultRequest { CreateProcessInstanceRequest request = 1; // timeout (in ms). the request will be closed if the process is not completed // before the requestTimeout. // if requestTimeout = 0, uses the generic requestTimeout configured in the gateway. int64 requestTimeout = 2; // list of names of variables to be included in `CreateProcessInstanceWithResultResponse.variables` // if empty, all visible variables in the root scope will be returned. repeated string fetchVariables = 3; } ``` ### Output: `CreateProcessInstanceWithResultResponse` ```protobuf message CreateProcessInstanceWithResultResponse { // the key of the process definition which was used to create the process instance int64 processDefinitionKey = 1; // the BPMN process ID of the process definition which was used to create the process // instance string bpmnProcessId = 2; // the version of the process definition which was used to create the process instance int32 version = 3; // the unique identifier of the created process instance; to be used wherever a request // needs a process instance key (e.g. CancelProcessInstanceRequest) int64 processInstanceKey = 4; // JSON document // consists of visible variables in the root scope string variables = 5; // the tenant identifier of the process definition string tenantId = 6; // tags attached to a process instance repeated string tags = 7; // the business id of the created process instance optional string businessId = 8; } ``` ### Errors #### GRPC_STATUS_NOT_FOUND Returned if: - No process with the given key exists (if processKey was given). - No process with the given process ID exists (if bpmnProcessId was given but version was -1). - No process with the given process ID and version exists (if both bpmnProcessId and version were given). #### GRPC_STATUS_FAILED_PRECONDITION Returned if: - The process definition does not contain a none start event; only processes with none start event can be started manually. #### GRPC_STATUS_INVALID_ARGUMENT Returned if: - The given variables argument is not a valid JSON document; it is expected to be a valid JSON document where the root node is an object. - The given `businessId` exceeds the maximum length of 256 characters. - If multi-tenancy is enabled, and `tenantId` is blank (empty string, null) - If multi-tenancy is enabled, and an invalid tenant ID is provided. A tenant ID is considered invalid if: - The tenant ID is blank (empty string, null) - The tenant ID is longer than 31 characters - The tenant ID contains anything other than alphanumeric characters, dot (.), dash (-), or underscore (\_) - If multi-tenancy is disabled, and `tenantId` is not blank (empty string, null), or has an ID other than `` #### GRPC_STATUS_PERMISSION_DENIED - If multi-tenancy is enabled, and an unauthorized tenant ID is provided ## `DeleteResource` RPC Deletes a resource (a process definition, decision requirements definition, or form) identified by its key. Deleting a process definition that still has running instances no longer fails. The definition starts draining: new instances are blocked immediately, running instances continue to completion, and the definition is removed automatically once its last instance finishes. See [resource deletion](/components/concepts/resource-deletion.md#draining). ### Input `DeleteResourceRequest` ```protobuf message DeleteResourceRequest { // The key of the resource that should be deleted. This can either be the key // of a process definition, the key of a decision requirements definition or the key of a form. int64 resourceKey = 1; } ``` ### Output: `DeleteResourceResponse` ```protobuf message DeleteResourceResponse { } ``` ### Errors #### GRPC_STATUS_NOT_FOUND Returned if: - No resource exists with the given key. - No resource was found with the given key for the tenants the user is authorized to work with. ## `DeployResource` RPC Deploys one or more resources (e.g. processes, decision models or forms) to Zeebe. Note that this is an atomic call, i.e. either all resources are deployed, or none of them are. ### Input: `DeployResourceRequest` ```protobuf message DeployResourceRequest { // list of resources to deploy repeated Resource resources = 1; // the tenant id of the resources to deploy string tenantId = 2; } message Resource { // the resource name, e.g. myProcess.bpmn or myDecision.dmn string name = 1; // the file content as a UTF8-encoded string bytes content = 2; } ``` ### Output: `DeployResourceResponse` ```protobuf message DeployResourceResponse { // the unique key identifying the deployment int64 key = 1; // a list of deployed resources, e.g. processes repeated Deployment deployments = 2; // the tenant id of the deployed resources string tenantId = 3; } message Deployment { // each deployment has only one metadata oneof Metadata { // metadata of a deployed process ProcessMetadata process = 1; // metadata of a deployed decision DecisionMetadata decision = 2; // metadata of a deployed decision requirements DecisionRequirementsMetadata decisionRequirements = 3; // metadata of a deployed form FormMetadata form = 4; } } message ProcessMetadata { // the bpmn process ID, as parsed during deployment; together with the version forms a // unique identifier for a specific process definition string bpmnProcessId = 1; // the assigned process version int32 version = 2; // the assigned key, which acts as a unique identifier for this process int64 processDefinitionKey = 3; // the resource name (see: ProcessRequestObject.name) from which this process was // parsed string resourceName = 4; // the tenant id of the deployed process string tenantId = 5; } message DecisionMetadata { // the dmn decision ID, as parsed during deployment; together with the // versions forms a unique identifier for a specific decision string dmnDecisionId = 1; // the dmn name of the decision, as parsed during deployment string dmnDecisionName = 2; // the assigned decision version int32 version = 3; // the assigned decision key, which acts as a unique identifier for this // decision int64 decisionKey = 4; // the dmn ID of the decision requirements graph that this decision is part // of, as parsed during deployment string dmnDecisionRequirementsId = 5; // the assigned key of the decision requirements graph that this decision is // part of int64 decisionRequirementsKey = 6; // the tenant id of the deployed decision string tenantId = 7; } message DecisionRequirementsMetadata { // the dmn decision requirements ID, as parsed during deployment; together // with the versions forms a unique identifier for a specific decision string dmnDecisionRequirementsId = 1; // the dmn name of the decision requirements, as parsed during deployment string dmnDecisionRequirementsName = 2; // the assigned decision requirements version int32 version = 3; // the assigned decision requirements key, which acts as a unique identifier // for this decision requirements int64 decisionRequirementsKey = 4; // the resource name (see: Resource.name) from which this decision // requirements was parsed string resourceName = 5; // the tenant id of the deployed decision requirements string tenantId = 6; } message FormMetadata { // the form ID, as parsed during deployment; together with the // versions forms a unique identifier for a specific form string formId = 1; // the assigned form version int32 version = 2; // the assigned key, which acts as a unique identifier for this form int64 formKey = 3; // the resource name string resourceName = 4; // the tenant id of the deployed form string tenantId = 5; } ``` ### Errors #### GRPC_STATUS_INVALID_ARGUMENT Returned if: - No resources given. - At least one resource is invalid. A resource is considered invalid if: - The resource type is not supported (e.g. supported resources include BPMN and DMN files) - The content is not deserializable (e.g. detected as BPMN, but it's broken XML) - The content is invalid (e.g. an event-based gateway has an outgoing sequence flow to a task) - If multi-tenancy is enabled, and `tenantId` is blank (empty string, null) - If multi-tenancy is enabled, and an invalid tenant ID is provided. A tenant ID is considered invalid if: - The tenant ID is blank (empty string, null) - The tenant ID is longer than 31 characters - The tenant ID contains anything other than alphanumeric characters, dot (.), dash (-), or underscore (\_) - If multi-tenancy is disabled, and `tenantId` is not blank (empty string, null), or has an ID other than `` #### GRPC_STATUS_PERMISSION_DENIED - If multi-tenancy is enabled, and an unauthorized tenant ID is provided ## `EvaluateConditional` RPC 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. ### Input: `EvaluateConditionalRequest` ```protobuf message EvaluateConditionalRequest { // Used to evaluate root-level conditional start events for a tenant with the given ID. // This will only evaluate root-level conditional start events of process definitions which belong to the tenant. string tenantId = 1; // Used to evaluate root-level conditional start events of the process definition with the given key. optional int64 processDefinitionKey = 2; // Serialized JSON object representing the variables to use for evaluation of the conditions and to pass to the process instances that have been triggered. string variables = 3; } ``` ### Output: `EvaluateConditionalResponse` ```protobuf message EvaluateConditionalResponse { // List of process instances created. If no root-level conditional start events evaluated to true, the list will be empty. repeated ProcessInstanceReference processInstances = 1; // The unique key of the conditional evaluation operation. int64 conditionalEvaluationKey = 2; // The tenant ID of the conditional evaluation operation. string tenantId = 3; } message ProcessInstanceReference { // The key of the process definition. int64 processDefinitionKey = 1; // The key of the created process instance. int64 processInstanceKey = 2; } ``` ### Errors #### GRPC_STATUS_INVALID_ARGUMENT Returned if: - The provided data is not valid #### GRPC_STATUS_NOT_FOUND Returned if: - The process definition was not found for the given processDefinitionKey #### GRPC_STATUS_PERMISSION_DENIED - The client is not authorized to start process instances for the specified process definition - If a processDefinitionKey is not provided, this indicates that the client is not authorized to start process instances for at least one of the matched process definitions ## `EvaluateDecision` RPC Evaluates a decision. You specify the decision to evaluate either by using its unique KEY (as returned by DeployResource), or using the decision ID. When using the decision ID, the latest deployed version of the decision is used. :::note When you specify both the decision ID and KEY, the ID is used to find the decision to be evaluated. ::: ### Input: `EvaluateDecisionRequest` ```protobuf message EvaluateDecisionRequest { // the unique key identifying the decision to be evaluated (e.g. returned // from a decision in the DeployResourceResponse message) int64 decisionKey = 1; // the ID of the decision to be evaluated string decisionId = 2; // JSON document that will instantiate the variables for the decision to be // evaluated; it must be a JSON object, as variables will be mapped in a // key-value fashion, e.g. { "a": 1, "b": 2 } will create two variables, // named "a" and "b" respectively, with their associated values. // [{ "a": 1, "b": 2 }] would not be a valid argument, as the root of the // JSON document is an array and not an object. string variables = 3; // the tenant identifier of the decision string tenantId = 4; } ``` ### Output: `EvaluateDecisionResponse` ```protobuf message EvaluateDecisionResponse { // the unique key identifying the decision which was evaluated (e.g. returned // from a decision in the DeployResourceResponse message) int64 decisionKey = 1; // the ID of the decision which was evaluated string decisionId = 2; // the name of the decision which was evaluated string decisionName = 3; // the version of the decision which was evaluated int32 decisionVersion = 4; // the ID of the decision requirements graph that the decision which was // evaluated is part of. string decisionRequirementsId = 5; // the unique key identifying the decision requirements graph that the // decision which was evaluated is part of. int64 decisionRequirementsKey = 6; // JSON document that will instantiate the result of the decision which was // evaluated; it will be a JSON object, as the result output will be mapped // in a key-value fashion, e.g. { "a": 1 }. string decisionOutput = 7; // a list of decisions that were evaluated within the requested decision evaluation repeated EvaluatedDecision evaluatedDecisions = 8; // an optional string indicating the ID of the decision which // failed during evaluation string failedDecisionId = 9; // an optional message describing why the decision which was evaluated failed string failureMessage = 10; // the tenant identifier of the evaluated decision string tenantId = 11; // the unique key identifying this decision evaluation int64 decisionInstanceKey = 12; } message EvaluatedDecision { // the unique key identifying the decision which was evaluated (e.g. returned // from a decision in the DeployResourceResponse message) int64 decisionKey = 1; // the ID of the decision which was evaluated string decisionId = 2; // the name of the decision which was evaluated string decisionName = 3; // the version of the decision which was evaluated int32 decisionVersion = 4; // the type of the decision which was evaluated string decisionType = 5; // JSON document that will instantiate the result of the decision which was // evaluated; it will be a JSON object, as the result output will be mapped // in a key-value fashion, e.g. { "a": 1 }. string decisionOutput = 6; // the decision rules that matched within this decision evaluation repeated MatchedDecisionRule matchedRules = 7; // the decision inputs that were evaluated within this decision evaluation repeated EvaluatedDecisionInput evaluatedInputs = 8; // the tenant identifier of the evaluated decision string tenantId = 9; } message EvaluatedDecisionInput { // the id of the evaluated decision input string inputId = 1; // the name of the evaluated decision input string inputName = 2; // the value of the evaluated decision input string inputValue = 3; } message EvaluatedDecisionOutput { // the ID of the evaluated decision output string outputId = 1; // the name of the evaluated decision output string outputName = 2; // the value of the evaluated decision output string outputValue = 3; } message MatchedDecisionRule { // the ID of the matched rule string ruleId = 1; // the index of the matched rule int32 ruleIndex = 2; // the evaluated decision outputs repeated EvaluatedDecisionOutput evaluatedOutputs = 3; } ``` ### Errors #### GRPC_STATUS_INVALID_ARGUMENT Returned if: - No decision with the given key exists (if decisionKey was given). - No decision with the given decision ID exists (if decisionId was given). - Both decision ID and decision KEY were provided, or are missing. - If multi-tenancy is enabled, and `tenantId` is blank (empty string, null) - If multi-tenancy is enabled, and an invalid tenant ID is provided. A tenant ID is considered invalid if: - The tenant ID is blank (empty string, null) - The tenant ID is longer than 31 characters - The tenant ID contains anything other than alphanumeric characters, dot (.), dash (-), or underscore (\_) - If multi-tenancy is disabled, and `tenantId` is not blank (empty string, null), or has an ID other than `` #### GRPC_STATUS_PERMISSION_DENIED - If multi-tenancy is enabled, and an unauthorized tenant ID is provided ## `FailJob` RPC Marks the job as failed. If the retries argument is positive and no retry back off is set, the job is immediately activatable again. If the retry back off is positive the job becomes activatable once the back off timeout has passed. If the retries argument is zero or negative, an incident is raised, tagged with the given errorMessage, and the job is not activatable until the incident is resolved. If the variables argument is set, the variables are merged into the process at the local scope of the job's associated task. ### Input: `FailJobRequest` ```protobuf message FailJobRequest { // the unique job identifier, as obtained when activating the job int64 jobKey = 1; // the amount of retries the job should have left int32 retries = 2; // an optional message describing why the job failed // this is particularly useful if a job runs out of retries and an incident is raised, // as it this message can help explain why an incident was raised string errorMessage = 3; // the backoff timeout (in ms) for the next retry int64 retryBackOff = 4; // JSON document that will instantiate the variables at the local scope of the // job's associated task; it must be a JSON object, as variables will be mapped in a // key-value fashion. e.g. { "a": 1, "b": 2 } will create two variables, named "a" and // "b" respectively, with their associated values. [{ "a": 1, "b": 2 }] would not be a // valid argument, as the root of the JSON document is an array and not an object. string variables = 5; // the token identifying a leased job's activation, obtained from ActivatedJob.jobLeaseToken. // For a leased job, the matching token must be supplied to prove the command comes from the // worker that holds the current lease; a command with no token is rejected. A command carrying // a stale token is likewise rejected, fencing the job against a superseded activation (e.g. // after the job timed out or failed and was re-activated by another worker). A job that was // activated without a lease requires no token. optional string jobLeaseToken = 6; } ``` ### Output: `FailJobResponse` ```protobuf message FailJobResponse { } ``` ### Errors #### GRPC_STATUS_NOT_FOUND Returned if: - No job was found with the given key. - No job was found with the given key for the tenants the user is authorized to work with. #### GRPC_STATUS_FAILED_PRECONDITION Returned if: - The job was not activated. - The job is already in a failed state, i.e. ran out of retries. ## `MigrateProcessInstance` RPC Migrates a process instance to a new process definition. The command can contain multiple mapping instructions to define mapping between the active process instance's elements and target process definition elements. Use the command to upgrade a process instance to a new version of a process or to a different process definition. E.g. keep your running instances up-to-date with the latest process improvements. ### Input: `MigrateProcessInstanceRequest` ```protobuf message MigrateProcessInstanceRequest { // key of the process instance to migrate int64 processInstanceKey = 1; // the migration plan that defines target process and element mappings MigrationPlan migrationPlan = 2; message MigrationPlan { // the key of process definition to migrate the process instance to int64 targetProcessDefinitionKey = 1; // the mapping instructions describe how to map elements from the source process definition to the target process definition repeated MappingInstruction mappingInstructions = 2; } message MappingInstruction { // the element ID to migrate from string sourceElementId = 1; // the element ID to migrate into string targetElementId = 2; } } ``` ### Output: `MigrateProcessInstanceResponse` ```protobuf message MigrateProcessInstanceResponse { } ``` ### Errors #### GRPC_STATUS_NOT_FOUND Returned if: - No process instance exists with the given key, or it is not active - No process definition exists with the given target definition key - No process instance exists with the given key for the tenants the user is authorized to work with. #### GRPC_STATUS_INVALID_ARGUMENT Returned if: - A `sourceElementId` does not refer to an element in the process instance's process definition - A `targetElementId` does not refer to an element in the target process definition - A `sourceElementId` is mapped by multiple mapping instructions. For example, the engine cannot determine how to migrate a process instance when the instructions are: [A->B, A->C]. #### GRPC_STATUS_FAILED_PRECONDITION Returned if: - Not all active elements in the given process instance are mapped to the elements in the target process definition - A mapping instruction changes the type of an element or event - A mapping instruction changes the implementation of a task - A mapping instruction refers to an unsupported element (i.e. some elements will be supported later on) - A mapping instruction refers to element in unsupported scenarios. (i.e. migrating active elements with event subscriptions will be supported later on) - A mapping instruction detaches a boundary event from an active element - Multiple mapping instructions refer to the same catch event - A mapping instruction changes a parallel multi-instance body to a sequential multi-instance body or vice versa ## `ModifyProcessInstance` RPC Modifies a running process instance. The command can contain multiple instructions to activate an element of the process, or to terminate an active instance of an element. Use the command to repair a process instance that is stuck on an element or took an unintended path. For example, because an external system is not available or doesn't respond as expected. ### Input: `ModifyProcessInstanceRequest` ```protobuf message ModifyProcessInstanceRequest { // the key of the process instance that should be modified int64 processInstanceKey = 1; // instructions describing which elements should be activated in which scopes, // and which variables should be created repeated ActivateInstruction activateInstructions = 2; // instructions describing which elements should be terminated repeated TerminateInstruction terminateInstructions = 3; message ActivateInstruction { // the ID of the element that should be activated string elementId = 1; // the key of the ancestor scope the element instance should be created in; // set to -1 to create the new element instance within an existing element // instance of the flow scope int64 ancestorElementInstanceKey = 2; // instructions describing which variables should be created repeated VariableInstruction variableInstructions = 3; } message VariableInstruction { // JSON document that will instantiate the variables for the root variable scope of the // process instance; it must be a JSON object, as variables will be mapped in a // key-value fashion. e.g. { "a": 1, "b": 2 } will create two variables, named "a" and // "b" respectively, with their associated values. [{ "a": 1, "b": 2 }] would not be a // valid argument, as the root of the JSON document is an array and not an object. string variables = 1; // the ID of the element in which scope the variables should be created; // leave empty to create the variables in the global scope of the process instance string scopeId = 2; } message TerminateInstruction { // the ID of the element that should be terminated int64 elementInstanceKey = 1; } } ``` ### Output: `ModifyProcessInstanceResponse` ```protobuf message ModifyProcessInstanceResponse { } ``` ### Errors #### GRPC_STATUS_NOT_FOUND Returned if: - No process instance exists with the given key, or it is not active. - No process instance was found with the given key for the tenants the user is authorized to work with. #### GRPC_STATUS_INVALID_ARGUMENT Returned if: - At least one activate instruction is invalid. An activate instruction is considered invalid if: - The process doesn't contain an element with the given ID. - A flow scope of the given element can't be created. - The given element has more than one active instance of its flow scope. - At least one variable instruction is invalid. A variable instruction is considered invalid if: - The process doesn't contain an element with the given scope ID. - The given element doesn't belong to the activating element's flow scope. - The given variables are not a valid JSON document. - At least one terminate instruction is invalid. A terminate instruction is considered invalid if: - No element instance exists with the given key, or it is not active. - The instructions would terminate all element instances of a process instance that was created by a call activity in the parent process. ## `PublishMessage` RPC Publishes a single message. Messages are published to specific partitions computed from their correlation keys. ### Input: `PublishMessageRequest` ```protobuf message PublishMessageRequest { // the name of the message string name = 1; // the correlation key of the message string correlationKey = 2; // how long the message should be buffered on the broker, in milliseconds int64 timeToLive = 3; // the unique ID of the message; can be omitted. only useful to ensure only one message // with the given ID will ever be published (during its lifetime) string messageId = 4; // the message variables as a JSON document; to be valid, the root of the document must be an // object, e.g. { "a": "foo" }. [ "foo" ] would not be valid. string variables = 5; // the tenant id of the message string tenantId = 6; } ``` ### Output: `PublishMessageResponse` ```protobuf message PublishMessageResponse { // the unique ID of the message that was published int64 key = 1; // the tenant id of the message string tenantId = 2; } ``` ### Errors #### GRPC_STATUS_ALREADY_EXISTS Returned if: - A message with the same ID was previously published (and is still alive). #### GRPC_STATUS_NOT_FOUND - If multi-tenancy is enabled, and `tenantId` is blank (empty string, null) - If multi-tenancy is enabled, and an invalid tenant ID is provided. A tenant ID is considered invalid if: - The tenant ID is blank (empty string, null) - The tenant ID is longer than 31 characters - The tenant ID contains anything other than alphanumeric characters, dot (.), dash (-), or underscore (\_) - If multi-tenancy is disabled, and `tenantId` is not blank (empty string, null), or has an ID other than `` #### GRPC_STATUS_PERMISSION_DENIED - If multi-tenancy is enabled, and an unauthorized tenant ID is provided ## `ResolveIncident` RPC Resolves a given incident. This simply marks the incident as resolved; most likely a call to UpdateJobRetries or SetVariables will be necessary to actually resolve the problem, followed by this call. ### Input: `ResolveIncidentRequest` ```protobuf message ResolveIncidentRequest { // the unique ID of the incident to resolve int64 incidentKey = 1; } ``` ### Output: `ResolveIncidentResponse` ```protobuf message ResolveIncidentResponse { } ``` ### Errors #### GRPC_STATUS_NOT_FOUND Returned if: - No incident with the given key exists. - No incident with the given key was found for the tenants the user is authorized to work with. ## `SetVariables` RPC Updates all the variables of a particular scope (e.g. process instance, flow element instance) from the given JSON document. ### Input: `SetVariablesRequest` ```protobuf message SetVariablesRequest { // the unique identifier of a particular element; can be the process instance key (as // obtained during instance creation), or a given element, such as a service task (see // elementInstanceKey on the job message) int64 elementInstanceKey = 1; // a JSON serialized document describing variables as key value pairs; the root of the document // must be an object string variables = 2; // if true, the variables will be merged strictly into the local scope (as indicated by // elementInstanceKey); this means the variables is not propagated to upper scopes. // for example, let's say we have two scopes, '1' and '2', with each having effective variables as: // 1 => `{ "foo" : 2 }`, and 2 => `{ "bar" : 1 }`. if we send an update request with // elementInstanceKey = 2, variables `{ "foo" : 5 }`, and local is true, then scope 1 will // be unchanged, and scope 2 will now be `{ "bar" : 1, "foo" 5 }`. if local was false, however, // then scope 1 would be `{ "foo": 5 }`, and scope 2 would be `{ "bar" : 1 }`. bool local = 3; } ``` ### Output: `SetVariablesResponse` ```protobuf message SetVariablesResponse { // the unique key of the set variables command int64 key = 1; } ``` ### Errors #### GRPC_STATUS_NOT_FOUND Returned if: - No element with the given `elementInstanceKey` exists. - No element with the given `elementInstanceKey` was found for the tenants the user is authorized to work with. #### GRPC_STATUS_INVALID_ARGUMENT Returned if: - The given payload is not a valid JSON document; all payloads are expected to be valid JSON documents where the root node is an object. ## `StreamActivatedJobs` RPC Opens a long living stream for the given job type, worker name, job timeout, and fetch variables. This will cause available jobs in the engine to be activated and pushed down this stream. See the [job worker's technical reference](/components/concepts/job-workers.md) for more on this. ### Input `StreamActivatedJobsRequest` ```protobuf message StreamActivatedJobsRequest { // the job type, as defined in the BPMN process (e.g. ) string type = 1; // the name of the worker activating the jobs, mostly used for logging purposes string worker = 2; // a job returned after this call will not be activated by another call until the // timeout (in ms) has been reached int64 timeout = 3; // a list of variables to fetch as the job variables; if empty, all visible variables at // the time of activation for the scope of the job will be returned repeated string fetchVariable = 5; // a list of identifiers of tenants for which to stream jobs repeated string tenantIds = 6; // whether to stream jobs with a lease; when true, each job pushed on this stream is // assigned a distinct, opaque lease token, returned as ActivatedJob.jobLeaseToken. The lease // fences the complete, fail, and throw-error commands against a superseded activation of // the same job (e.g. after the job timed out or failed and was re-activated by another // worker): a command carrying a stale lease token is rejected rather than racing with the // newer activation. Defaults to false, which pushes jobs without a lease. bool withLease = 8; } ``` ### Output: a stream of `ActivatedJob` ```protobuf message ActivatedJob { // Describes the kind of job. enum JobKind { BPMN_ELEMENT = 0; EXECUTION_LISTENER = 1; TASK_LISTENER = 2; } // Describes the listener event type of the job. enum ListenerEventType { ASSIGNING = 0; CANCELING = 1; COMPLETING = 2; CREATING = 3; END = 4; START = 5; UNSPECIFIED = 6; UPDATING = 7; } // the key, a unique identifier for the job int64 key = 1; // the type of the job (should match what was requested) string type = 2; // the job's process instance key int64 processInstanceKey = 3; // the bpmn process ID of the job process definition string bpmnProcessId = 4; // the version of the job process definition int32 processDefinitionVersion = 5; // the key of the job process definition int64 processDefinitionKey = 6; // the associated task element ID string elementId = 7; // the unique key identifying the associated task, unique within the scope of the // process instance int64 elementInstanceKey = 8; // a set of custom headers defined during modelling; returned as a serialized // JSON document string customHeaders = 9; // the name of the worker which activated this job string worker = 10; // the amount of retries left to this job (should always be positive) int32 retries = 11; // when the job can be activated again, sent as a UNIX epoch timestamp int64 deadline = 12; // JSON document, computed at activation time, consisting of all visible variables to // the task scope string variables = 13; // the ID of the tenant that owns the job string tenantId = 14; // the kind of the job. JobKind kind = 15; // the listener event type of the job. ListenerEventType listenerEventType = 16; // the lease token identifying this activation; unset when the job was activated without a // lease optional string jobLeaseToken = 21; } ``` ### Errors #### GRPC_STATUS_INVALID_ARGUMENT Returned if: - Type is blank (empty string, null) - Timeout less than 1 (ms) - If multi-tenancy is enabled, and `tenantIds` is empty (empty list) - If multi-tenancy is enabled, and an invalid tenant ID is provided. A tenant ID is considered invalid if: - The tenant ID is blank (empty string, null) - The tenant ID is longer than 31 characters - The tenant ID contains anything other than alphanumeric characters, dot (.), dash (-), or underscore (\_) - If multi-tenancy is disabled, and `tenantIds` is not empty (empty list), or has an ID other than `` ## `ThrowError` RPC `ThrowError` reports a business error (i.e. non-technical) that occurs while processing a job. The error is handled in the process by an error catch event. If there is no error catch event with the specified `errorCode`, an incident is raised instead. Variables can be passed along with the thrown error to provide additional details that can be used in the process. ### Input: `ThrowErrorRequest` ```protobuf message ThrowErrorRequest { // the unique job identifier, as obtained when activating the job int64 jobKey = 1; // the error code that will be matched with an error catch event string errorCode = 2; // an optional error message that provides additional context string errorMessage = 3; // JSON document that will instantiate the variables at the local scope of the // error catch event that catches the thrown error; it must be a JSON object, as variables will be mapped in a // key-value fashion. e.g. { "a": 1, "b": 2 } will create two variables, named "a" and // "b" respectively, with their associated values. [{ "a": 1, "b": 2 }] would not be a // valid argument, as the root of the JSON document is an array and not an object. string variables = 4; // the token identifying a leased job's activation, obtained from ActivatedJob.jobLeaseToken. // For a leased job, the matching token must be supplied to prove the command comes from the // worker that holds the current lease; a command with no token is rejected. A command carrying // a stale token is likewise rejected, fencing the job against a superseded activation (e.g. // after the job timed out or failed and was re-activated by another worker). A job that was // activated without a lease requires no token. optional string jobLeaseToken = 5; } ``` ### Output: `ThrowErrorResponse` ```protobuf message ThrowErrorResponse { } ``` ### Errors #### GRPC_STATUS_NOT_FOUND Returned if: - No job was found with the given key. - No job was found with the given key for the tenants the user is authorized to work with. #### GRPC_STATUS_FAILED_PRECONDITION Returned if: - The job is already in a failed state, i.e. ran out of retries. ## `Topology` RPC Obtains the current topology of the cluster the gateway is part of. :::note The partition role can be one of `LEADER`, `FOLLOWER`, or `INACTIVE`, which [is defined here](../../components/zeebe/technical-concepts/partitions.md#roles). ::: :::note The partition health can be one of `HEALTHY`, `UNHEALTHY`, or `DEAD`, which [is defined here](../../components/zeebe/technical-concepts/health.md). ::: ### Input: `TopologyRequest` ```protobuf message TopologyRequest { } ``` ### Output: `TopologyResponse` ```protobuf message TopologyResponse { // list of brokers part of this cluster repeated BrokerInfo brokers = 1; // how many nodes are in the cluster int32 clusterSize = 2; // how many partitions are spread across the cluster int32 partitionsCount = 3; // configured replication factor for this cluster int32 replicationFactor = 4; // gateway version string gatewayVersion = 5; // the cluster's unique ID string clusterId = 6; } message BrokerInfo { // unique (within a cluster) node ID for the broker int32 nodeId = 1; // hostname of the broker string host = 2; // port for the broker int32 port = 3; // list of partitions managed or replicated on this broker repeated Partition partitions = 4; // broker version string version = 5; } message Partition { // Describes the Raft role of the broker for a given partition enum PartitionBrokerRole { LEADER = 0; FOLLOWER = 1; INACTIVE = 2; } // Describes the current health of the partition enum PartitionBrokerHealth { HEALTHY = 0; UNHEALTHY = 1; DEAD = 2; } // the unique ID of this partition int32 partitionId = 1; // the role of the broker for this partition PartitionBrokerRole role = 2; // the health of this partition PartitionBrokerHealth health = 3; } ``` ### Errors No specific errors. ## `UpdateJobRetries` RPC Updates the number of retries a job has left. This is mostly useful for jobs that have run out of retries, should the underlying problem be solved. ### Input: `UpdateJobRetriesRequest` ```protobuf message UpdateJobRetriesRequest { // the unique job identifier, as obtained through ActivateJobs int64 jobKey = 1; // the new amount of retries for the job; must be positive int32 retries = 2; // the token identifying a leased job's activation, obtained from ActivatedJob.jobLeaseToken. // For a leased job, a supplied token is validated to prove the command comes from the worker // that holds the current lease; a command carrying a stale token is rejected, fencing the job // against a superseded activation (e.g. after the job timed out or failed and was re-activated // by another worker). An update without a token always applies, to support operator and bulk // updates of leased jobs; this differs from lifecycle commands like complete, fail, and // throw-error, which always require a token for leased jobs. A job that was activated without a // lease requires no token. optional string jobLeaseToken = 4; } ``` ### Output: `UpdateJobRetriesResponse` ```protobuf message UpdateJobRetriesResponse { } ``` ### Errors #### GRPC_STATUS_NOT_FOUND Returned if: - No job exists with the given key. - No job was found with the given key for the tenants the user is authorized to work with. #### GRPC_STATUS_INVALID_ARGUMENT Returned if: - Retries is not greater than 0. ## `UpdateJobTimeout` RPC Updates the deadline of a job using the timeout (in milliseconds) provided. This can be used for extending or shortening the job deadline. The new deadline will be calculated from the current time, adding the timeout provided. ### Input: `UpdateJobTimeoutRequest` ```protobuf message UpdateJobTimeoutRequest { // the unique job identifier, as obtained from ActivateJobsResponse int64 jobKey = 1; // the duration of the new timeout in ms, starting from the current moment int64 timeout = 2; // the token identifying a leased job's activation, obtained from ActivatedJob.jobLeaseToken. // For a leased job, a supplied token is validated to prove the command comes from the worker // that holds the current lease; a command carrying a stale token is rejected, fencing the job // against a superseded activation (e.g. after the job timed out or failed and was re-activated // by another worker). An update without a token always applies, to support operator and bulk // updates of leased jobs; this differs from lifecycle commands like complete, fail, and // throw-error, which always require a token for leased jobs. A job that was activated without a // lease requires no token. optional string jobLeaseToken = 4; } ``` ### Output: `UpdateJobTimeoutResponse` ```protobuf message UpdateJobTimeoutResponse { } ``` ### Errors #### GRPC_STATUS_NOT_FOUND Returned if: - No job exists with the given key. - No job was found with the given key for the tenants the user is authorized to work with. #### GRPC_STATUS_INVALID_STATE Returned if: - The job is not active. ## `UpdateJobPriority` RPC Updates the priority of a job. ### Input: `UpdateJobPriorityRequest` ```protobuf message UpdateJobPriorityRequest { // the unique job identifier, as obtained from ActivateJobsResponse int64 jobKey = 1; // the new priority value for the job optional int32 priority = 2; // a reference key chosen by the user and will be part of all records resulted from this operation optional uint64 operationReference = 3; // the token identifying a leased job's activation, obtained from ActivatedJob.jobLeaseToken. // For a leased job, a supplied token is validated to prove the command comes from the worker // that holds the current lease; a command carrying a stale token is rejected, fencing the job // against a superseded activation (e.g. after the job timed out or failed and was re-activated // by another worker). An update without a token always applies, to support operator and bulk // updates of leased jobs; this differs from lifecycle commands like complete, fail, and // throw-error, which always require a token for leased jobs. A job that was activated without a // lease requires no token. optional string jobLeaseToken = 4; } ``` ### Output: `UpdateJobPriorityResponse` ```protobuf message UpdateJobPriorityResponse { } ``` ### Errors #### GRPC_STATUS_NOT_FOUND Returned if: - No job exists with the given key. - No job was found with the given key for the tenants the user is authorized to work with. #### GRPC_STATUS_INVALID_ARGUMENT Returned if: - Priority is not provided. #### GRPC_STATUS_INVALID_STATE Returned if: - The job is in a terminal state. --- ## Zeebe API (gRPC) ## About You can use the [gRPC](https://grpc.io/) high-performance, cross-platform remote procedure call (RPC) framework to communicate with the Orchestration Cluster. Zeebe clients use gRPC to communicate with the cluster. For example, you can use this API to activate jobs, cancel and create process instances, and more. ## Why use gRPC? Using this API may be beneficial if your use-case requires low-latency or high-throughput communication. Benefits of gRPC include: - Low-latency, high-throughput communication - Bidirectional streaming for efficient microservices integration - Ideal for scalable, event-driven process automation ## Key capabilities - Activate jobs - Create and cancel process instances - Manage workflows and more See [Zeebe API RPCs](gateway-service.md) for all available operations. Additionally, review [technical error handling](/apis-tools/zeebe-api/technical-error-handling.md) for a closer look at business logic errors, or [Postman](https://www.postman.com/camundateam/camunda-8-postman/collection/jzgs776/zeebe-api-grpc?action=share&creator=11465105) to experiment with the API. ## Authentication Authentication for the Zeebe API (gRPC) depends on your environment and how you deploy Camunda 8. You can find more details in the [Authentication guide](./zeebe-api-authentication.md). --- ## Technical error handling In the documentation above, the documented errors are business logic errors. These errors are a result of request processing logic, and not serialization, network, or other more general errors. These errors are described in this section. The gRPC API for Zeebe is exposed through an API gateway, which acts as a proxy for the cluster. Generally, this means the clients execute a remote call on the gateway, which is then translated to special binary protocol the gateway uses to communicate with nodes in the cluster. The nodes in the cluster are called brokers. Technical errors which occur between gateway and brokers (e.g. the gateway cannot deserialize the broker response, the broker is unavailable, etc.) are reported to the client using the following error codes: - `GRPC_STATUS_RESOURCE_EXHAUSTED`: When a broker receives more requests than it can handle, it signals backpressure and rejects requests with this error code. - In this case, it is possible to retry the requests with an appropriate retry strategy. - If you receive many such errors within a short time period, it indicates the broker is constantly under high load. - `GRPC_STATUS_UNAVAILABLE`: If the gateway itself is in an invalid state (e.g. out of memory). - `GRPC_STATUS_INTERNAL`: For any other internal errors that occurred between the gateway and the broker. This behavior applies to every request. In these cases, the client should retry with an appropriate retry policy (e.g. a combination of exponential backoff or jitter wrapped in a circuit breaker). As the gRPC server/client is based on generated code, keep in mind that any call made to the server can also return errors as described by the spec [here](https://grpc.io/docs/guides/error.html#error-status-codes). --- ## Authentication(Zeebe-api) This page describes the available authentication methods for accessing the Zeebe API (gRPC) in the Orchestration Cluster. Similar to the Orchestration Cluster REST API, the Zeebe API supports three authentication methods: - **No Authentication** - **Basic Authentication** - **OIDC-based Authentication** Please refer to the [Orchestration Cluster API authentication guide](./../orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md) for more details on each method and general guidance on usage. --- ## Tutorial(Zeebe-api-rest) In this tutorial, we'll step through examples to highlight the capabilities of the Zeebe API, such as assigning and unassigning a user to and from a Zeebe user task. ## Prerequisites - If you haven't done so already, [create a cluster](/components/saas/clusters/create-cluster.md). - Upon cluster creation, [create your first client](/components/saas/clusters/manage-api-clients.md#create-a-client). Ensure you check the `Zeebe` client scope box. :::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 - You need authentication to access the API endpoints. See [Zeebe REST API authentication](./zeebe-api-rest-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 `ZEEBE_CLIENT_ID`, `ZEEBE_CLIENT_SECRET`, `ZEEBE_BASE_URL`, and `ZEEBE_AUDIENCE`, which is `zeebe.camunda.io` in a Camunda 8 SaaS environment. For example, your audience may be defined as `ZEEBE_AUDIENCE=zeebe.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). 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 assign and unassign a user to and from a Zeebe user task. You can examine the framework for processing these arguments in the `cli.js` file before getting started. ::: ## Assign a Zeebe user task (POST) :::note In this tutorial, you will capture a **Zeebe user task** ID to assign and unassign users in this API. Camunda 8.5 introduced this new [user task](/components/modeler/bpmn/user-tasks/user-tasks.md) implementation type, and these Zeebe user tasks are different from job worker-based user tasks (which while still supported, are now deprecated with 8.6). See more details on task type differences in the [migrating to Zeebe user tasks documentation](/apis-tools/migration-manuals/migrate-to-camunda-user-tasks.md#task-type-differences). ::: First, let's script an API call to assign a Zeebe user task. To do this, take the following steps: 1. In the file named `zeebe.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.ZEEBE_CLIENT_ID, clientSecret: process.env.ZEEBE_CLIENT_SECRET, audience: process.env.ZEEBE_AUDIENCE, }; ``` 2. Examine the function `async function assignUser([userTaskKey, assignee])` below this configuration. This is where you will script out your API call. 3. Within the function, you must first generate an access token for this request, so your function should now look like the following: ```javascript async function assignUser([userTaskKey, assignee]) { const accessToken = await getAccessToken(authorizationConfiguration); } ``` 4. Using your generated client credentials from [prerequisites](#prerequisites), capture your Zeebe API URL beneath your call for an access token by defining `zeebeApiUrl`: `const zeebeApiUrl = process.env.ZEEBE_BASE_URL` 5. On the next line, script the API endpoint to assign a Zeebe user task.: ```javascript const url = `${ZeebeApiUrl}/user-tasks/${userTaskKey}/assignment`; ``` 6. Configure your POST request to the appropriate endpoint, including an authorization header based on the previously acquired `accessToken`: ```javascript const options = { method: "POST", url, headers: { Accept: "application/json", Authorization: `Bearer ${accessToken}`, }, data: { // The body contains information about the new assignment. assignee: assignee, }, }; ``` 7. Call the assign endpoint, process the results from the API call, and emit an error message from the server if necessary: ```javascript try { // Call the assign endpoint. const response = await axios(options); // Process the results from the API call. if (response.status === 204) { console.log(`User task assigned to ${assignee}.`); } else { // Emit an unexpected error message. console.error("Unable to assign this user!"); } } catch (error) { // Emit an error from the server. console.error(error.message); } ``` 8. In your terminal, run `node cli.js zeebe assign `, where `` is the Zeebe user task ID you've captured from Tasklist, and `` is the assignee's email address. Include your own email address if you would like to see these results in your user interface. :::note This `assign` command is connected to the `assignUser` function at the bottom of the `zeebe.js` file, and executed by the `cli.js` file. While we will assign and unassign users in this tutorial, you may add additional arguments depending on the API calls you would like to make. ::: If you have a valid user and task ID, the assignment 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. If no action is provided, it will default to "assign" everywhere, except when unassigning a user. ## Unassign a Zeebe user task (DELETE) To unassign a user from a Zeebe user task, you can use the same Zeebe user task ID from the previous exercise and take the following steps: 1. Outline your function, similar to the steps above: ```javascript async function unassignUser([userTaskKey]) { const accessToken = await getAccessToken(authorizationConfiguration); const ZeebeApiUrl = process.env.ZEEBE_BASE_URL; const url = `${ZeebeApiUrl}/user-tasks/${userTaskKey}/assignee`; } ``` 2. Configure the API call using the DELETE method: ```javascript const options = { method: "DELETE", url, headers: { Accept: "application/json", Authorization: `Bearer ${accessToken}`, }, }; ``` 3. Process the results from the API call. For example: ```javascript try { // Call the delete endpoint. const response = await axios(options); // Process the results from the API call. if (response.status === 204) { console.log("User task has been unassigned!"); } else { // Emit an unexpected error message. console.error("Unable to unassign this user task!"); } } catch (error) { // Emit an error from the server. console.error(error.message); } ``` 4. In your terminal, run `node cli.js zeebe unassign `, where `` is the Zeebe user task ID. ## 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 `zeebe.js` file. ## Next steps You can script several additional API calls as outlined in the [Zeebe API reference material](./zeebe-api-rest-overview.md). --- ## Authentication(Zeebe-api-rest) :::warning The Zeebe REST API is **deprecated**. While it continues to function, new development should use the Orchestration Cluster REST API by referencing the [Orchestration Cluster REST API migration documentation](/apis-tools/migration-manuals/migrate-to-camunda-api.md). ::: The Zeebe REST API uses the same authentication mechanism as the [Orchestration Cluster REST API](../orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md). If your environment uses **OIDC-based authentication**, obtain an access token following [Using a token (OIDC/JWT)](../orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md#using-a-token-oidcjwt). If **no authentication is configured** (for example, for local development), see [No authentication (local development)](../orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md#no-authentication-local-development). When making requests to Zeebe, replace the base URL used in examples with your Zeebe API URL. Example: ```bash curl "$ZEEBE_BASE_URL/v2/topology" \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` #### 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. --- ## Overview(Zeebe-api-rest) :::warning The Zeebe REST API is **deprecated**. While it continues to function, new development should use the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). See the [migration guide](/apis-tools/migration-manuals/migrate-to-camunda-api.md) for details. ::: ## Introduction The Zeebe REST API is a REST API designed to interact with the Zeebe workflow engine. :::note Ensure you [authenticate](./zeebe-api-rest-authentication.md) before accessing the Zeebe REST API. ::: ## Context paths ### SaaS Find your **region Id** and **cluster Id** under **Connection information** in your client credentials (revealed when you click on your client under the **API** tab within your cluster). Example path: `https://${REGION}.api.camunda.io:443/${CLUSTER_ID}/v1/` ### Self-Managed Use the host and path defined for your [Zeebe Gateway](/reference/glossary.md#zeebe-gateway). For Ingress and routing details, see the [configuration guide](/self-managed/deployment/helm/configure/ingress/ingress-setup.md). The path used here is the default. Example path: `http://localhost:8080/v1/` ## API Explorer See [the interactive Zeebe REST API Explorer][zeebe-api-explorer] for specifications, example requests and responses, and code samples of interacting with the Tasklist REST API. [zeebe-api-explorer]: ./specifications/zeebe-rest-api.info.mdx --- ## Access control If authorization control is enabled for your Orchestration Cluster, users require the following authorizations to work with Admin. :::note If you already have another administration user, they can assign these [in the Admin UI](components/admin/authorization.md#create-an-authorization-in-admin). See [the introduction to authorizations](components/concepts/access-control/authorizations.md#available-resources) for a list of all available authorizations. ::: ## Mandatory authorizations The following mandatory authorizations are required to work with Admin: | Authorization type | Resource type | Resource ID | Permission | | :--------------------- | :------------ | :--------------------------------------------------------------------------- | :--------- | | Admin component access | `Component` | `admin` or `identity` (deprecated) or `*` (for access to all web components) | `ACCESS` | ## Authorizations per resource The following authorizations are required to manage each User, Group, Role, Authorization, Mapping Rule, and Tenant resource: | Authorization type | Resource type | Resource ID | Permission | | :--------------------------------- | :---------------------------------------------------------------- | :------------------------------------------------------------------------------ | :------------------------------------------ | | Create/Read/Update/Delete resource | One of `User`, `Group`, `Authorization`, `Mapping Rule`, `Tenant` | ID of the resource or `*` (for access to all resources and to create resources) | Any of `CREATE`, `READ`, `UPDATE`, `DELETE` | ## Optional authorizations The following optional authorizations can also be defined: | Authorization type | Resource type | Resource ID | Permission | | :--------------------------------- | :---------------- | :--------------------------------- | :------------------------------------------------------------------------------------------- | | View audit log entries. | `AUDIT_LOG` | `ADMIN` or `*` for all categories. | `READ` | | Manage global user task listeners. | `GLOBAL_LISTENER` | `*` | `CREATE_TASK_LISTENER`, `READ_TASK_LISTENER`, `UPDATE_TASK_LISTENER`, `DELETE_TASK_LISTENER` | --- ## Introduction to Admin Use the integrated [Orchestration Cluster](../orchestration-cluster.md) Admin (formerly Orchestration Cluster Identity) to manage Camunda 8 authentication, authorization, and cluster administration. :::note This was renamed in 8.9 to reflect its expanded scope and to avoid confusion with [Management Identity](/self-managed/components/management-identity/overview.md). ::: ## About Admin The Orchestration Cluster Admin interface centralizes all key administrative jobs for a single cluster. This interface manages identity and access control for cluster components, including Zeebe, Operate, Tasklist, and Orchestration Cluster APIs, while also handling other core features such as cluster variables and the global user task listener. This provides administrators with one central place to configure and operate their clusters end-to-end. Admin includes the following features: | Feature | Description | | :---------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Unified access management | Authentication and authorization are handled consistently across all Orchestration Cluster components and APIs. | | Flexible authentication | Admin supports multiple authentication modes, including no authentication, Basic authentication, and OpenID Connect (OIDC), depending on the deployment type. | | Tenant management | Multi-tenancy is managed directly within the Orchestration Cluster, allowing for clear separation of resources. | | [Cluster variables](cluster-variables.md) | Manage configuration values centrally across your cluster, making them available in FEEL expressions. | | [Global user task listeners](global-user-task-listeners.md) | Configure cluster-wide listeners that react to user task lifecycle events across all processes. | For details about authorization concepts, resources, and configuration, see [Orchestration Cluster authorizations](../concepts/access-control/authorizations.md). ## Open Admin From Camunda Hub, you can open Admin in any [environment](/components/concepts/environments.md) you have access to: 1. Log in to Camunda Hub. 1. In the left navigation, click **Environments**, and then select an environment. Each environment has its own instance of Admin. 1. Under **Applications**, on the **Admin** card, click **Open**. This opens Admin for the environment in a new tab. You can also expand an environment in the left navigation, and select Admin from its applications. :::tip If the environment is paused, you must [resume it](/components/hub/organization/manage-environments/index.md#resume-a-paused-environment) to access its applications. ::: ## Manage access Depending on your setup, Admin allows you to manage Orchestration Cluster access as follows: | Entity | Description | Availability | | :--------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------- | :---------------- | | [Users](user.md) | Individuals who can access applications and perform actions based on their permissions. | All deployments | | [Groups](group.md) | Simplify access management by granting permissions collectively to groups of users. | All deployments | | [Roles](role.md) | Sets of permissions to define what actions can be performed on specific resources. Roles can be assigned to users and groups. | All deployments | | [Authorizations](authorization.md) | The specific permissions that connect users, groups, or roles with resources and actions (for example, `READ`, `UPDATE`, `DELETE`). | All deployments | | [Tenants](tenant.md) | Logically isolate data within a single cluster. This is useful for multi-tenancy applications. | All deployments | | [Cluster admin](cluster-admin.md) | A separate role for cluster-wide operations that span all Physical Tenants, such as status, topology, and restore. | Self-Managed only | :::info Admin in Self-Managed For documentation on deploying Admin as part of Camunda 8 Self-Managed, see [Admin in Self-Managed](/self-managed/components/orchestration-cluster/admin/overview.md). ::: --- ## Audit operations Audit [operations](../audit-log/overview.md) in Camunda 8 Admin. ## Prerequisites To follow the steps in this guide, you must be [authorized to view operations in the audit log](../audit-log/overview/access-control.md). ## Audit operations In Admin, you can audit all [`ADMIN` operations](../audit-log/overview/recorded-operations.md#admin-operations): 1. In the top navigation, click **Operations log**. 2. To sort the log, click a column header. ## Next steps - [Learn about the operation data structure in the operations log.](../audit-log/overview/operation-structure.md) - [Use the Audit Log REST API to programmatically access the audit log](../../apis-tools/orchestration-cluster-api-rest/specifications/search-audit-logs.api.mdx). --- ## Authorizations Use authorizations to control access to resources in your Orchestration Cluster. ## About authorizations An authorization grants an owner access to a resource and defines the specific permissions they have. - Owner: The entity that receives permissions, such as a [user](user.md), [group](group.md), [role](role.md), [client](client.md), or [mapping rule](mapping-rules.md). - In SaaS deployments, the username is the user's email address. - In Self-Managed deployments, the username must match [the value of the claim configured as `username-claim`](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md#step-4-configure-the-oidc-connection-details). - Resource: The object that the permissions apply to, such as a process definition, decision definition, or system. See the full list of [available resources](/components/concepts/access-control/authorizations.md#available-resources). Each authorization specifies which permissions the owner has for the resource (for example, `READ`, `UPDATE`, `DELETE`). For an authorization to apply, [enable it in your cluster configuration](/components/concepts/access-control/authorizations.md#configuration). To learn more, see [Orchestration Cluster authorizations](/components/concepts/access-control/authorizations.md). ## Create an authorization in Admin To create a new authorization: 1. Log in to Admin, and select the **Authorizations** tab. 2. Select a resource type from the list on the left, and select **Create authorization**. 3. Enter the following information: - **Owner type**: The entity to which you want to assign permissions, such as a user, group, role, client, or mapping rule. - **Owner ID**: The ID of the owner. - **Resource type**: The selected resource type. - **Resource scope**: Choose how this authorization is scoped: - By **Resource ID**, or - For `USER_TASK`, by **Resource property name** with the `PROPERTY` matcher. - **Resource ID**: The ID of the resource within the selected resource type. Use `*` to grant permissions for all resources of that type. - **Resource property name** _(USER_TASK only)_: The task property used when scoping access with the `PROPERTY` matcher. Supported values are: - `assignee` - `candidateUsers` - `candidateGroups` Only one of **Resource ID** or **Resource property name** can be specified. If you use a resource property, set the matcher to `PROPERTY`. 4. Select the permissions you want to grant. 5. Click **Create authorization**. The authorization is created, and the owner is granted the specified permissions. ## User task authorizations To support fine-grained access to user tasks in Tasklist and the Orchestration Cluster REST API, Admin provides a **USER_TASK** resource type with the following permissions: - `READ`: View the task and its properties. - `UPDATE`: Perform updates on the task (for example, change assignment, due dates, or candidate users or groups). - `CLAIM`: Claim a task from a pool of candidate users or groups. - `COMPLETE`: Complete the task, with or without variables. ### Configure property-based user task authorizations With property-based user task authorizations, you can grant permissions based on task assignment rather than a specific task ID. A user is authorized when their username or group membership matches a corresponding task property. To create a property-based user task authorization: 1. Log in to Admin, and select the **Authorizations** tab. 2. Create a new authorization for the `USER_TASK` resource type. 3. Specify the **Owner type** and **Owner ID** (for example, a role that represents task workers). 4. Set the matcher to `PROPERTY`. 5. Select the task property used to scope access: - `assignee` - `candidateUsers` - `candidateGroups` 6. Select the permissions to grant (for example `READ`, `CLAIM`, and `COMPLETE`). 7. Create the authorization. You can't combine multiple task properties in a single authorization. To cover all three properties (`assignee`, `candidateUsers`, `candidateGroups`), create one authorization per property. ### Authorization for user tasks You can control access to user tasks using a combination of process-level and task-level permissions: - Process-level permissions on the `Process Definition` resource, such as `READ_USER_TASK`, `CLAIM_USER_TASK`, `COMPLETE_USER_TASK`, and `UPDATE_USER_TASK`. - Task-level permissions on the `USER_TASK` resource, such as `READ`, `UPDATE`, `CLAIM`, and `COMPLETE`, which are typically scoped using property-based access control on task properties such as `assignee`, `candidateUsers`, and `candidateGroups`. When both process-level and task-level permissions exist, process-level permissions take precedence. If a user already has the required `Process Definition` permission for an operation (for example, `READ_USER_TASK`, `CLAIM_USER_TASK`, `COMPLETE_USER_TASK`, or `UPDATE_USER_TASK`), the system does not evaluate `USER_TASK` permissions for that operation. Task-level `USER_TASK` permissions are evaluated only when no effective process‑level permission exists for that user and process definition. For Tasklist-specific behavior and practical authorization patterns, see [User task authorization in Tasklist](../tasklist/user-task-authorization.md). ### Authorization examples #### Supervisor: broad process-level access To allow a supervisor to see and manage all user tasks for one or more processes: - Resource type: `PROCESS_DEFINITION` - Resource scope: by **Resource ID** - Resource ID: `*` (or a specific BPMN process ID) - Permissions: `READ_USER_TASK`, `UPDATE_USER_TASK`, `CLAIM_USER_TASK`, `COMPLETE_USER_TASK` This grants broad visibility and control over all user tasks for the selected processes, without needing task-level authorizations. #### Task worker: property-based access The default task worker role is created with property-based user task authorizations: - Role ID: `task-worker` - Resource type: `USER_TASK` - Resource scope: by **Resource property name** (`PROPERTY` matcher) - Property name: `assignee`, `candidateUsers`, or `candidateGroups` - Permissions: `READ`, `CLAIM`, `COMPLETE` This ensures that task workers can only see, claim, and complete tasks where they are the assignee, a candidate user, or in a candidate group. :::note Default roles, including task worker, are recreated each time the cluster starts and are not customizable. To adjust permissions, create and manage custom roles instead. ::: ## Secret authorizations Use authorizations on the `SECRET` resource to control which owners can see that a secret reference exists, and which owners can resolve it to its actual value. For background on the `camunda.secrets.` reference syntax, see [secret resolution](/components/concepts/secret-resolution.md). A `SECRET` authorization grants one of two permissions: - `READ`: Lets the owner see that a secret reference exists, for example in a `POST /v2/secrets/list` response. - `REVEAL`: Lets the owner resolve a reference to its actual value, for example with `POST /v2/secrets/resolve`. `REVEAL` is never implied by `READ`, and it is never granted automatically alongside it. Grant `REVEAL` only to trusted owners, since it exposes secret values. See [the REVEAL permission for the Secret](/components/concepts/access-control/authorizations.md#reveal-permission-for-the-secret) for why the two permissions are kept separate. The **Resource ID** you choose determines the scope of the grant: - `*` grants the permission for every secret reference in the cluster. - `camunda.secrets.` scopes the permission to one secret reference, for example `camunda.secrets.demoApiToken`. ### Grant secret access to an owner To grant an owner access to secrets: 1. Log in to Admin, and select the **Authorizations** tab. 2. Click **Create authorization**. 3. Set the **Owner type** to the entity you want to grant access to, such as `Client`, `User`, `Group`, `Role`, or `Mapping rule`. 4. In the **Owner ID** field, enter the owner's ID. For a client, use its **Client ID**. 5. Set the **Resource type** to `SECRET`. 6. In the **Resource ID** field, enter `*` for every secret, or `camunda.secrets.` for one secret. 7. Select **READ** to let the owner see the reference exists, **REVEAL** to let the owner resolve it to its value, or both. 8. Click **Create authorization**. An owner that resolves a reference without `REVEAL` receives an `ACCESS_DENIED` error instead of the secret value. See [Secrets](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-secrets.md#review-per-reference-errors) for the full list of resolve and list errors. ### Secret authorization examples #### Full secret access for a client To let a client list and resolve every secret in the cluster: - Owner type: `Client` - Resource type: `SECRET` - Resource ID: `*` - Permissions: `READ`, `REVEAL` Grant this only to trusted clients, since it exposes every secret value in the cluster. #### Access to one secret To scope a client to a single secret reference: - Owner type: `Client` - Resource type: `SECRET` - Resource ID: `camunda.secrets.demoApiToken` - Permissions: `READ`, `REVEAL` Create one authorization per secret reference the client needs. Resource IDs don't support partial wildcard matching, so `camunda.secrets.demo*` isn't valid. ## Change an existing authorization :::tip Partial wildcard matching, for example `my-resource*`, is not supported. ::: ## Update an authorization Authorizations cannot be updated after they are created. To edit an authorization, [delete](#delete-an-authorization) the existing one, and create a new authorization with the updated permissions. ## Delete an authorization Delete an authorization by completing the following steps: 1. Log in to Admin, and select the **Authorizations** tab. 2. Select the resource type of the authorization you want to delete. 3. In the list, find the authorization you want to remove and click **Delete**. 4. Confirm the deletion by clicking **Delete** in the confirmation dialog. The authorization is deleted, and the owner no longer has the permissions granted by it. :::caution Deleting an authorization is permanent and can't be undone. ::: --- ## Clients Configure and manage client access to a cluster so the client application has the permissions it requires. ## About client application access A client is an application that interacts with an Orchestration Cluster via its APIs. For example, an [external agent](/reference/glossary.md#external-agent)'s runtime can be configured as a client so it can call the [Agent Instance API](/apis-tools/orchestration-cluster-api-rest/specifications/create-agent-instance.api.mdx) to report its execution. This guide describes how to manage client access in SaaS and in Self-Managed environments that use an [external OpenID Connect (OIDC) identity provider](../concepts/access-control/connect-to-identity-provider.md) for authentication. If you are using the Orchestration Cluster with [Basic authentication](/self-managed/concepts/authentication/authentication-to-orchestration-cluster.md#basic-authentication), both end users and machine-to-machine (m2m) applications are treated as users and must be [managed accordingly](user.md). The Admin UI does not display dedicated client options in Basic authentication setups for this reason. ## Manage clients in SaaS In Camunda 8 SaaS, client credentials are created and managed in [Camunda Hub](../hub/index.md). ### Step 1: Create client credentials in Camunda Hub Follow the [guide for creating client credentials in Camunda Hub](../saas/clusters/manage-api-clients.md#create-a-client). Copy the **client id** shown in the variables after you have created your client as this is required in the next step. ### Step 2: Configure authorizations in Admin If you have enabled [authorizations](/components/concepts/access-control/authorizations.md) on your cluster, the new client has no permissions by default, even after assigning scopes in Camunda Hub. You must grant fine-grained permissions in Admin: 1. Open the **Admin** application for your cluster. 2. Open the **Authorizations** tab. 3. Click **Create authorization**. 4. Set the **Owner type** to `Client`. 5. In the **Owner ID** field, enter the **Client ID** of the client you just created and copied. 6. Select the **Resource type**, **Resource ID**, and permissions the client needs. 7. Click **Create authorization**. If authorizations are disabled, your client will have full access based on the scopes you selected during creation. ## Manage clients in Self-Managed with OIDC authentication To configure a client application in a [Self-Managed environment with OIDC](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md), complete the following two steps: 1. Register your client application with your identity provider to obtain client credentials. 2. Configure authorizations for the client in the Orchestration Cluster Admin to grant the necessary permissions. After completing these steps, your client application can then authenticate with your IdP, obtain an access token, and use that token to make authorized API calls to the Camunda 8 orchestration cluster. ### Prerequisites Your Orchestration Cluster must be [configured to use a token claim as the client id](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md#step-1-configure-the-oidc-client-id-claim). ### Step 1: Create client credentials in your IdP Before configuring access in the Orchestration Cluster, you must register your client application in your OIDC-compatible identity provider (for example, EntraID, Keycloak, Okta). During the registration process, your identity provider will provide you with a **Client ID** and a **Client Secret**. Your application will use these credentials to authenticate and obtain an access token. ### Step 2: Configure authorizations in Admin Once you have your client credentials, you can configure the required permissions in the Admin component of your cluster. Log in to Admin and choose one of the following methods to grant authorizations. #### Authorization based on client ID This method is suitable when your client application requires a fixed set of permissions. Follow [the steps on how to create authorizations](/components/admin/authorization.md#create-an-authorization-in-admin) with the following specifics: - As the **Owner type**, select `Client`. - In the **Owner ID** field, enter the **Client ID** that matches your client's value for the configured client id claim. You can also assign the client to existing [groups](./group.md) or [roles](./role.md) to inherit their permissions. #### Flexible authorization based on JWT claims with mapping rules This method is ideal when you need to dynamically assign permissions based on claims in the OIDC access token, such as scopes or custom claims. 1. [Create a mapping rule](/components/admin/mapping-rules.md#add-a-mapping-rule) that matches a claim from your client's access token. 2. [Create authorizations](/components/admin/authorization.md#create-an-authorization-in-admin) for the mapping rule with the following specifics: - As the **Owner type**, select `Mapping Rule`. - In the **Owner ID** field, enter the **Mapping Rule ID** that you chose in the previous step. Alternatively, you can assign the mapping rule to [groups](./group.md) or [roles](./role.md) to inherit their permissions. Any client that authenticates with a token matching the criteria of the mapping rule will be granted the associated permissions. --- ## Cluster admin Self-Managed only Cluster admin is a role for operations that apply to an entire [Orchestration Cluster](../orchestration-cluster.md), rather than a single Physical Tenant. It is separate from the [tenant-scoped roles and authorizations](authorization.md) managed elsewhere in Admin, and uses its own credentials. :::note Cluster admin was added in 8.10 alongside [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md). It is most relevant in clusters running multiple Physical Tenants, where cluster-wide visibility and recovery operations span Physical Tenant boundaries. ::: ## How cluster admin differs from tenant-scoped access - Cluster admin is authenticated and authorized separately from Orchestration Cluster user sessions. It does not share credentials, roles, or mapping rules with tenant-scoped users. - Cluster admin credentials are verified against an isolated user store bound to `/cluster/v2/**`. A cluster admin has no route to `/physical-tenants/{physicalTenantId}/v2/...` endpoints, and no existing tenant role grants cluster admin implicitly. Configure it explicitly, as described in [Configure cluster admin access](#configure-cluster-admin-access). - Authorization is coarse-grained by design. Cluster admin grants access to all cluster-level operations. There are no sub-roles or partial cluster admin grants. ## Cluster-wide operations Cluster admin protects the operations served under the `/cluster/v2/...` path prefix. These operations can act at cluster scope, with endpoint-specific parameters documented in the generated API reference. | Area | Endpoints | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Status and topology | [`GET /cluster/v2/status`](/apis-tools/orchestration-cluster-api-rest/specifications/get-cluster-status.api.mdx), [`GET /cluster/v2/topology`](/apis-tools/orchestration-cluster-api-rest/specifications/get-cluster-topology.api.mdx) | | Backup | [`/cluster/v2/backups/runtime`](/apis-tools/orchestration-cluster-api-rest/specifications/list-runtime-backups-as-cluster-admin.api.mdx), [`/cluster/v2/backups/runtime/state`](/apis-tools/orchestration-cluster-api-rest/specifications/get-runtime-backup-state-as-cluster-admin.api.mdx), [`/cluster/v2/backups/history`](/apis-tools/orchestration-cluster-api-rest/specifications/list-history-backups-as-cluster-admin.api.mdx) | | Exporting | [`GET /cluster/v2/exporting`](/apis-tools/orchestration-cluster-api-rest/specifications/get-cluster-exporting-status.api.mdx), [`POST /cluster/v2/exporting/pause`](/apis-tools/orchestration-cluster-api-rest/specifications/pause-cluster-exporting.api.mdx), [`POST /cluster/v2/exporting/resume`](/apis-tools/orchestration-cluster-api-rest/specifications/resume-cluster-exporting.api.mdx) | | Recovery | [`POST /cluster/v2/restore`](/apis-tools/orchestration-cluster-api-rest/specifications/restore-as-cluster-admin.api.mdx), [`PATCH /cluster/v2/mode`](/apis-tools/orchestration-cluster-api-rest/specifications/change-cluster-mode-as-cluster-admin.api.mdx) | | Partition placement | [`POST /cluster/v2/rebalance`](/apis-tools/orchestration-cluster-api-rest/specifications/trigger-cluster-rebalance.api.mdx), [`GET /cluster/v2/rebalance`](/apis-tools/orchestration-cluster-api-rest/specifications/get-cluster-rebalance.api.mdx), [`DELETE /cluster/v2/rebalance`](/apis-tools/orchestration-cluster-api-rest/specifications/cancel-cluster-rebalance.api.mdx) | Some operations can target a single Physical Tenant, while others always apply to the whole cluster. See the API reference for each operation's scope and parameters. For the operator procedures that use these endpoints, see [back up and restore](/self-managed/operational-guides/backup-restore/backup-and-restore.md#multiple-physical-tenants) and [cluster scaling](/self-managed/components/orchestration-cluster/zeebe/operations/cluster-scaling.md#scale-a-cluster-with-multiple-physical-tenants). Scaling and multi-region failover use the actuator surface rather than this API. For endpoint details, see the [Orchestration Cluster REST API reference](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). Cluster admin protects every operation under this prefix except `GET /cluster/v2/status`, which is deliberately unauthenticated so load balancers and operators can use it as a health check. Cluster topology is its authenticated counterpart, because topology exposes Physical Tenant identifiers. ## Configure cluster admin access Assign cluster admin access using one of two methods. Which one applies is determined by `camunda.security.authentication.method`, not by choice: only the chain matching your cluster's authentication method is instantiated. The cluster admin chain is stateless in both modes. An existing web application session cookie can never authenticate a `/cluster/v2/**` request. Under OIDC, tokens are issued by the cluster's default provider. For the `client_credentials` flow and request examples, see [Orchestration Cluster API authentication](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md). Define one or more cluster admin users directly in configuration: ```yaml camunda: security: cluster-admin: basic: users: - name: cluster-operator password: ``` Match cluster admin against a claim in the access token, such as a specific client, group, or custom claim: ```yaml camunda: security: cluster-admin: oidc: clients: - cluster-admin-client groups: - cluster-operators # claims: # - name: # value: ``` :::warning Under OIDC, configure at least one client, group, or claim. If none are configured, every bearer token is denied on `/cluster/v2/**` and the API becomes unreachable. Matching on `clients` or `groups` also requires the provider's `client-id-claim` and `groups-claim` to be set under `camunda.security.authentication.oidc`, otherwise startup fails. Under Basic authentication, an empty or absent user list is accepted silently and leaves no cluster admin provisioned. Only a malformed entry, such as a duplicate or blank name or password, fails startup. ::: ## Related - [Physical Tenants authorization model](/self-managed/concepts/physical-tenants/authorization-model.md) - [Physical Tenants API routing](/self-managed/concepts/physical-tenants/api-routing.md) - [Back up and restore](/self-managed/operational-guides/backup-restore/backup-and-restore.md) - [Cluster scaling](/self-managed/components/orchestration-cluster/zeebe/operations/cluster-scaling.md) - [Rebalancing partitions](/self-managed/components/orchestration-cluster/zeebe/operations/rebalancing.md) - [Orchestration Cluster authorizations](../concepts/access-control/authorizations.md) --- ## Cluster variables Use Admin to manage cluster variables, which store configuration values centrally across your cluster and make them available in [FEEL expressions](/components/modeler/feel/cluster-variable/overview.md). ## About cluster variables Cluster variables allow you to maintain environment-specific configurations, API endpoints, feature flags, and other shared values without hardcoding them into individual process definitions. Variables can be defined at the global (cluster-wide) or tenant level. Each variable also has a kind, either `JSON` or `SECRET_REFERENCE`, which determines how Camunda reads its value. `JSON` is the default. See [variable kinds](/components/modeler/feel/cluster-variable/data-types.md#variable-kinds). :::tip To learn more about cluster variables, including scope resolution, data types, and FEEL expression usage, see the [cluster variables overview](/components/modeler/feel/cluster-variable/overview.md). ::: You can manage cluster variables through the Admin UI or the [Orchestration Cluster API](/apis-tools/orchestration-cluster-api-rest/specifications/create-global-cluster-variable.api.mdx). ## Manage global cluster variables Global cluster variables are available to all processes across the entire cluster. ### Create a global cluster variable 1. Log in to Admin in your cluster, and select the **Cluster Variables** tab. 2. Click **Create variable**. 3. Provide the following details: - **Name**: A unique identifier for the variable. - **Value**: The value of the variable, which can be a string, number, boolean, or JSON object. 4. Click **Create variable**. The variable is created and immediately available for use in FEEL expressions across all processes in the cluster using `camunda.vars.cluster.` or `camunda.vars.env.`. ### Update a global cluster variable 1. Log in to Admin in your cluster, and select the **Cluster Variables** tab. 2. Click the **pencil icon** next to the variable you want to update. 3. Update the variable value. 4. Click **Save**. The updated value takes effect for new evaluations of FEEL expressions that reference this variable. ### Delete a global cluster variable 1. Log in to Admin in your cluster, and select the **Cluster Variables** tab. 2. Click **Delete** next to the variable you want to delete. 3. Confirm the deletion by clicking **Delete** in the confirmation dialog. The variable is deleted and is no longer available in FEEL expressions. :::note Deleting a cluster variable does not affect process instances that have already resolved the variable value. ::: ## Manage tenant cluster variables When [multi-tenancy](/components/concepts/multi-tenancy.md) is enabled, you can define tenant-specific cluster variables that override global variables with the same name for processes running in that tenant. ### Create a tenant cluster variable 1. Log in to Admin in your cluster, and select the **Cluster Variables** tab. 2. Select the tenant for which you want to create a variable. 3. Click **Create variable**. 4. Provide the following details: - **Name**: A unique identifier for the variable. - **Value**: The value of the variable. 5. Click **Create variable**. The variable is created and available in FEEL expressions for processes running in the selected tenant using `camunda.vars.tenant.` or `camunda.vars.env.`. :::note If a global variable with the same name exists, the tenant-level variable takes precedence for processes running in this tenant. See [scope resolution](/components/modeler/feel/cluster-variable/scope-and-priority.md) for details. ::: ### Update a tenant cluster variable 1. Log in to Admin in your cluster, and select the **Cluster Variables** tab. 2. Select the tenant for which you want to update the variable. 3. Click the **pencil icon** next to the variable you want to update. 4. Update the variable value. 5. Click **Save**. ### Delete a tenant cluster variable 1. Log in to Admin in your cluster, and select the **Cluster Variables** tab. 2. Select the tenant for which you want to delete the variable. 3. Click **Delete** next to the variable you want to delete. 4. Confirm the deletion by clicking **Delete** in the confirmation dialog. ## Manage cluster variables of kind `SECRET_REFERENCE` A cluster variable of kind `SECRET_REFERENCE` can contain an [Orchestration Cluster secret reference](/reference/glossary.md#secret-reference-orchestration-cluster), `camunda.secrets.`, in its value. When a process reads the variable in an input mapping, Camunda resolves these references in the background ahead of activation, and injects the resolved values into the job only once it's handed to a worker. A `JSON`-kind variable whose value contains the same text is treated as ordinary text. Resolving these references requires a secret store to be configured for the physical tenant. Without one, every reference fails permanently as not found, and the job gets a [`SECRET_RESOLUTION_ERROR` incident](/components/concepts/secret-resolution-incidents.md#resolve-secret-lookup-failures) naming the reference. The job does not stay unactivated with no error pointing at the cause. See [`camunda.secrets.stores.file`](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#camundasecretsstoresfile) to configure a store. The Admin UI create form has no kind field, so every variable you create there is a `JSON`-kind variable. To create a `SECRET_REFERENCE`-kind variable, use the Orchestration Cluster API with `"kind": "SECRET_REFERENCE"` in the request body, either [globally](/apis-tools/orchestration-cluster-api-rest/specifications/create-global-cluster-variable.api.mdx) or [for a tenant](/apis-tools/orchestration-cluster-api-rest/specifications/create-tenant-cluster-variable.api.mdx). You can manage an existing `SECRET_REFERENCE`-kind variable in the Admin UI: - Updating its value keeps its kind, and Camunda scans the new value for references. A variable's kind is fixed at creation and cannot be changed. - The value shown in the variable list and in the variable details is the stored value, so you see the reference text rather than a resolved value. - Deleting it works the same as deleting any other cluster variable. For where resolved values appear and where they do not, see [secret resolution and job activation](/components/concepts/secret-resolution-and-job-activation.md). ## Required permissions Managing cluster variables requires permissions on the `CLUSTER_VARIABLE` resource type. These permissions are the same for every kind, and there is no additional permission for a `SECRET_REFERENCE`-kind variable. | Action | Required permission | | ------------------------- | ------------------- | | Create a cluster variable | `CREATE` | | View a cluster variable | `READ` | | Update a cluster variable | `UPDATE` | | Delete a cluster variable | `DELETE` | The resource identifier is the variable name, or `*` for all cluster variables. See [authorizations](/components/concepts/access-control/authorizations.md#available-resources) for how to grant these permissions. ## See also - [Cluster variables overview](/components/modeler/feel/cluster-variable/overview.md) — learn about scopes, data types, and FEEL expression usage. - [Get started with cluster variables](/components/modeler/feel/cluster-variable/get-started.md) — tutorial for creating and using your first cluster variable. - [Secret resolution and job activation](/components/concepts/secret-resolution-and-job-activation.md) — understand when a job that references secrets reaches a worker. - [Orchestration Cluster API: Create global cluster variable](/apis-tools/orchestration-cluster-api-rest/specifications/create-global-cluster-variable.api.mdx) — manage cluster variables via API. --- ## Global user task listeners Use Admin to manage [global user task listeners](/components/concepts/global-user-task-listeners.md), which are cluster-wide listeners that react to user task lifecycle events across all processes. ## About global user task listeners Global user task listeners allow you to define listeners once for all processes in a cluster, instead of individually per user task. They are useful for centralizing audit logging, notifications, governance rules, and other cross-cutting concerns. :::tip To learn more about global user task listeners, including execution order, supported features, and configuration options, see the [global user task listeners concept page](/components/concepts/global-user-task-listeners.md). ::: You can manage global user task listeners through the Admin UI, [Unified Configuration](/components/concepts/global-user-task-listeners/configuration.md#configure-through-unified-configuration), or the [Orchestration Cluster API](/apis-tools/orchestration-cluster-api-rest/specifications/create-global-task-listener.api.mdx). ## Manage global user task listeners in Admin The Admin UI provides a user-friendly interface to manage global user task listeners in the **Global User Task Listeners** tab. Changes made through the Admin UI take effect immediately, without requiring a cluster restart. :::note The Admin UI uses the Orchestration Cluster API to manage listeners. Listeners created through the Admin UI have their `source` property set to `API`. ::: ### Create a global user task listener 1. Log in to Admin in your cluster, and select the **Global User Task Listeners** tab. 2. Click **Create listener**. 3. Provide the following details: - **Listener ID**: A unique identifier for the listener. - **Listener type**: The name of the listener type. Job workers use this to identify and process listener jobs. - **Event type**: The user task lifecycle events that trigger the listener, selected from the dropdown menu with the following supported values: "All events", "Assigning", "Canceling", "Completing", "Creating", and "Updating". - **Retries** (optional): Number of retries for the listener job. Defaults to `3`. - **Execution order**: When the global listener should be executed with respect to model-level ones. Supported values: "Before model-level listeners" or "After model-level listeners". - **Priority** (optional): The priority of the listener. Higher priority listeners are executed first. Defaults to `50`. 4. Click **Create**. The listener is created and immediately applies to new lifecycle events for both running and new process instances. ### Update a global user task listener 1. Log in to Admin in your cluster, and select the **Global User Task Listeners** tab. 2. Click the **pencil icon** next to the listener you want to update. 3. Update the listener details. 4. Click **Update**. The updated listener configuration applies immediately to new lifecycle events. ### Delete a global user task listener 1. Log in to Admin in your cluster, and select the **Global User Task Listeners** tab. 2. Click **Delete** next to the listener you want to delete. 3. Confirm the deletion by clicking **Delete** in the confirmation dialog. The listener is deleted and no longer triggers for new lifecycle events. In-progress listener jobs are not affected. ### Known limitations The Admin UI retrieves the listener list from secondary storage. Because secondary storage is eventually consistent, changes might not appear immediately. ## See also - [Global user task listeners](/components/concepts/global-user-task-listeners.md) — learn about execution order, supported features, and configuration options. - [User task listeners](/components/concepts/user-task-listeners.md) — learn about model-level user task listeners. - [Orchestration Cluster API: Create global task listener](/apis-tools/orchestration-cluster-api-rest/specifications/create-global-task-listener.api.mdx) — manage listeners via API. --- ## Groups A user group is a way to organize multiple [users](user.md) in one unit. Groups simplify access management by allowing you to assign permissions to a collection of users at once, rather than individually. You can grant permissions to a group by assigning [roles](role.md) to it or creating direct [authorizations](authorization.md). ## Create a group To create a group: 1. Log in to Admin in your cluster, and click on the **Groups** tab. 2. Click on the **Create group** button, and provide the following group details: - **Group ID**: The unique identifier for the group. - **Name**: The name of the group. - **Description**: A description of the group. 3. Click on the **Create group** button. The group is created and can now be assigned to roles or users. ![identity-create-group-tab](./img/create-group-tab.png) ## Update a group To update a group: 1. Log in to Admin in your cluster, and click on the **Groups** tab. 2. Click on the **pencil icon** next to the group you want to update. 3. Update the group details: - **Name**: The name of the group. - **Description**: A description of the group. 4. Click on the **Save** button. The group details are updated. ## Delete a group 1. Log in to Admin in your cluster, and click on the **Groups** tab. 2. Click on the **Delete** button next to the group you want to delete. 3. Confirm the deletion by clicking on the **Delete** button in the confirmation dialog. The group is deleted. Users and roles that were assigned to the group will not be affected, but they will no longer be part of the group. The authorizations that were granted to the group will also be removed. ## Assign authorizations to a group See the [authorization](./authorization.md) section to learn how to create authorizations for groups. ## Manage users ### Assign users to a group To assign users to a group: 1. Log in to Admin in your cluster, and click on the **Groups** tab. 2. Click on the group you want to assign users to. 3. Click on the **Users** tab. 4. Click on the **Assign user** button. 5. Type the username of the user you want to assign to the group, and click on the **Assign user** button. For SaaS deployments, the username field refers to the email address of the user. For Self-Managed deployments, the username field has to match [the value of the claim configured as `username-claim`](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md#step-4-configure-the-oidc-connection-details). :::note For Self-Managed deployments with Basic authentication, you must search for existing users. ::: The user is assigned to the group and inherits its permissions. ### Remove users from a group To remove users from a group: 1. Log in to Admin in your cluster, and click on the **Groups** tab. 2. Click on the group you want to remove users from. 3. Click on the **Users** tab. 4. Click on the **Remove** button next to the user you want to remove from the group. 5. Confirm the removal by clicking on the **Remove** button in the confirmation dialog. The user is removed from the group and loses any permissions that were granted through the group. ## Manage roles ### Assign roles to a group To assign roles to a group: 1. Log in to Admin in your cluster, and click on the **Groups** tab. 2. Click on the group you want to assign roles to. 3. Click on the **Roles** tab. 4. Click on the **Assign role** button. 5. Search for the ID of the role you want to assign to the group, and click on the **Assign role** button. The role is assigned to the group. Users in the group now have the permissions granted by that role. ### Remove roles from a group To remove roles from a group: 1. Log in to Admin in your cluster, and click on the **Groups** tab. 2. Click on the group you want to remove roles from. 3. Click on the **Roles** tab. 4. Click on the **Remove** button next to the role you want to remove from the group. 5. Confirm the removal by clicking on the **Remove** button in the confirmation dialog. The role is removed from the group. Users in the group will lose the permissions that were granted through that role. ## Manage clients :::note In Self-Managed deployment, [client management](client.md) is only available for OIDC authentication. ::: ### Assign client to a group To assign a client to a group: 1. Log in to Admin in your cluster, and click on the **Groups** tab. 2. Click on the group you want to assign a client to. 3. Click on the **Clients** tab. 4. Click on the **Assign client** button. 5. Type the ID of the client you want to assign to the group, and click on the **Assign client** button. The client is assigned to the group. ### Remove client from a group To remove a client from a group: 1. Log in to Admin in your cluster, and click on the **Groups** tab. 2. Click on the group you want to remove a client from. 3. Click on the **Clients** tab. 4. Click on the **Remove** button next to the client you want to remove from the group. 5. Confirm the removal by clicking on the **Remove** button in the confirmation dialog. The client is removed from the group. ## Manage mapping rules :::note [Mapping rules](../concepts/access-control/mapping-rules.md) are only available for OIDC authentication. On SaaS, they become available after you [connect an external identity provider](/components/saas/clusters/connect-external-identity-provider.md) to the cluster. ::: ### Assign mapping rules to a group To assign a mapping rule to a group: 1. Log in to Admin in your cluster, and click on the **Groups** tab. 2. Click on the group you want to assign mapping rules to. 3. Click on the **Mapping rules** tab. 4. Click on the **Assign mapping rule** button. 5. Search for the ID of the mapping rule you want to assign to the group, and click on the **Assign mapping rule** button. The mapping rule is assigned to the group. ### Remove mapping rules from a group To remove a mapping rule from a group: 1. Log in to Admin in your cluster, and click on the **Groups** tab. 2. Click on the group you want to remove mapping rules from. 3. Click on the **Mapping rules** tab. 4. Click on the **Remove** button next to the mapping rule you want to remove from the group. 5. Confirm the removal by clicking on the **Remove** button in the confirmation dialog. The mapping rule is removed from the group. --- ## Mapping rules Mapping rules provide flexible access to Orchestration Cluster resources based on claims in a user's or client's OIDC access token. In Self-Managed, the Mapping Rules UI is always available. In SaaS, it becomes available once you [connect an external identity provider](/components/saas/clusters/connect-external-identity-provider.md) to the cluster. The mapping rules API works regardless. However, without an external identity provider, there are no custom claims to map, and attempts to assign mapping rules to roles or groups (or unassign them) are rejected with a `403 Forbidden` response. :::info To learn more, see [mapping rules](../concepts/access-control/mapping-rules.md). ::: ## Create a mapping rule To create a mapping rule: 1. Log in to Admin in your cluster, and select the **Mapping Rules** tab. 2. Click **Create a mapping rule**, and enter the following details: - **Mapping Rule ID**: A unique identifier for the mapping rule. - **Mapping Rule name**: A user-friendly name. - **Claim name**: The name of a claim in the OIDC access token or a [JSONPath expression](https://www.rfc-editor.org/rfc/rfc9535) that points to a claim in the access token. - **Claim value**: The expected value of the claim so that the mapping rule matches an access token. 3. Click **Create mapping rule** to create the role. You can now assign the role to groups, roles, or tenants, or create and apply authorizations for it. ## Update a mapping rule To update a mapping rule: 1. Log in to Admin in your cluster, and select the **Mapping rules** tab. 2. Click the **pencil icon** next to the mapping rule you want to update. 3. Update the mapping rule details as required. 4. Click **Save** to update the mapping rule. ## Delete a mapping rule To delete a mapping rule: 1. Log in to Admin in your cluster, and select the **Mapping Rules** tab. 2. Click **Delete** next to the mapping rule you want to delete. 3. Confirm the deletion by clicking on the **Delete** button in the confirmation dialog. The mapping rule is deleted. ## Assign authorizations to a role See [authorizations](./authorization.md) to learn how to create authorizations for mapping rules. --- ## Roles A role is a collection of [authorizations](authorization.md) that defines a set of permissions. ## About roles Roles are used to grant users the system and data access required to fulfill a certain responsibility. A role can be assigned to [users](user.md) directly or as part of a [group](group.md) they belong to. :::info The Orchestration Cluster creates [a set of default roles](../concepts/access-control/authorizations.md#default-roles) on startup. If deleted, they're automatically recreated on cluster startup. ::: ## Create a role To create a role: 1. Log in to Admin in your cluster, and select the **Roles** tab. 2. Click **Create role**, and enter the following role details: - **Role ID**: The unique identifier for the role. - **Role name**: The name of the role. - **Description**: An optional description of the role. 3. Click **Create role**. The role is created and can now be assigned to users or groups. ## Update a role To update a role: 1. Log in to Admin in your cluster, and select the **Roles** tab. 2. Click the **pencil icon** next to the role you want to update. 3. Update the role details: - **Name**: The name of the role. - **Description**: An optional description of the role. 4. Click the **Save** button. The role details are updated. :::note Default roles that are automatically created are system entities and cannot be updated. ::: ## Delete a role To delete a role: 1. Log in to Admin in your cluster, and select the **Roles** tab. 2. Click **Delete** next to the role you want to delete. 3. Confirm the deletion by clicking **Delete** in the confirmation dialog. The role is deleted. The authorizations that were granted to the role are also removed. :::note Default roles that are automatically created are system entities and cannot be deleted. ::: ## Assign authorizations to a role See [authorizations](./authorization.md) to learn how to create authorizations for roles. ## Manage users ### Assign users to a role To assign users to a role: 1. Log in to Admin in your cluster, and select the **Roles** tab. 2. Click on the role you want to assign users to. 3. Select the **Users** tab. 4. Click **Assign user**. 5. Type the username of the user you want to assign to the role, and click **Assign user**. For SaaS deployments, the username field refers to the email address of the user. For Self-Managed deployments, the username field has to match [the value of the claim configured as `username-claim`](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md#step-4-configure-the-oidc-connection-details). :::note For Self-Managed deployments with Basic authentication, you must search for existing users. ::: The user is assigned to the role and inherits its permissions. ### Remove users from a role To remove users from a role: 1. Log in to Admin in your cluster, and select the **Roles** tab. 2. Click on the role you want to remove users from. 3. Select the **Users** tab. 4. Click **Remove** next to the user you want to remove from the role. 5. Confirm the removal by clicking **Remove** in the confirmation dialog. The user is removed from the role and loses any permissions that were granted through it. ## Manage groups ### Assign groups to a role To assign groups to a role: 1. Log in to Admin in your cluster, and select the **Roles** tab. 2. Click on the role you want to assign users to. 3. Select the **Groups** tab. 4. Click **Assign group**. 5. Type the ID of the group you want to assign to the role, and click **Assign group**. The group is assigned to the role and inherits its permissions. ### Remove groups from a role To remove groups from a role: 1. Log in to Admin in your cluster, and select the **Roles** tab. 2. Click on the role you want to remove groups from. 3. Select the **Groups** tab. 4. Click **Remove** next to the group you want to remove from the role. 5. Confirm the removal by clicking **Remove** in the confirmation dialog. The group is removed from the role and loses any permissions that were granted through it. ## Manage clients :::note In a Self-Managed deployment, [client management](client.md) is only available for OIDC authentication. ::: ### Assign client to a role To assign a client to a role: 1. Log in to Admin in your cluster, and select the **Roles** tab. 2. Click on the role you want to assign a client to. 3. Select the **Clients** tab. 4. Click **Assign client**. 5. Type the ID of the client you want to assign to the role, and click **Assign client**. The client is assigned to the role. ### Remove client from a role To remove a client from a role: 1. Log in to Admin in your cluster, and select the **Roles** tab. 2. Click on the role you want to remove a client from. 3. Select the **Clients** tab. 4. Click **Remove** next to the client you want to remove from the role. 5. Confirm the removal by clicking **Remove** in the confirmation dialog. The client is removed from the role. ## Manage mapping rules :::note [Mapping rules](../concepts/access-control/mapping-rules.md) are only available for OIDC authentication. On SaaS, they become available after you [connect an external identity provider](/components/saas/clusters/connect-external-identity-provider.md) to the cluster. ::: ### Assign mapping rules to a role To assign mapping rules to a role: 1. Log in to Admin in your cluster, and select the **Roles** tab. 2. Click on the role you want to assign mapping rules to. 3. Select the **Mapping rules** tab. 4. Click **Assign mapping rule**. 5. Search for the ID of the mapping rule you want to assign to the role, and click **Assign mapping rule**. The mapping rule is assigned to the role. ### Remove mapping rules from a role To remove a mapping rule from a role: 1. Log in to Admin in your cluster, and select the **Roles** tab. 2. Click on the role you want to remove mapping rules from. 3. Select the **Mapping rules** tab. 4. Click **Remove** next to the mapping rule you want to remove from the role. 5. Confirm the removal by clicking **Remove** in the confirmation dialog. The mapping rule is removed from the role. --- ## Tenants Use Admin to manage Orchestration Cluster tenants and isolate data within a single cluster. Tenant management is available on both Camunda 8 SaaS and Self-Managed. :::note On SaaS, the **Tenants** tab is visible to organization admins on clusters running generation 8.8 and later, even before multi-tenancy checks are enabled. This allows admins to set up tenants and assignments before enforcing checks. Before enabling checks, confirm your tenant assignments so users retain the access they need. ::: ## About tenants A tenant is a logical boundary for data within a Camunda 8 installation. This enables multiple teams, departments, or clients to share a single environment while keeping data isolated. :::tip To learn more about tenants, see [multi-tenancy](../concepts/multi-tenancy.md). ::: You can manage your Orchestration Cluster tenants directly in [Admin](admin-introduction.md). - **Multi-tenancy** is enabled by default. - **Multi-tenancy checks** are disabled by default. All data maps to the `` tenant. This allows administrators to set up tenants and assignments before enforcing multi-tenancy checks. How you enable multi-tenancy checks depends on your deployment model: - **SaaS**: Enable the **Multi-tenancy** toggle per cluster in [Camunda Hub cluster settings](/components/saas/clusters/settings.md#multi-tenancy). - **Self-Managed**: Configure multi-tenancy through [Orchestration Cluster configuration properties](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#multi-tenancy). :::warning Before you enable multi-tenancy checks, assign all users, groups, and roles that need access to their tenants and to the `` tenant. Once checks are enforced, any principal not assigned to a tenant loses access to the resources scoped to that tenant. ::: ## Create a tenant :::note The `` tenant is automatically created when Admin starts. ::: 1. Log in to Admin and open the **Tenants** tab. ![tenant-management-tab](./img/tenant-management-tab.png) 2. Click **Create tenant**. In the modal, provide the tenant **ID**, **name**, and optional **description**. Then click **Create tenant**. ![tenant-management-create-tenant-modal](./img/tenant-management-create-tenant-modal.png) 3. The tenant appears in the list. If not, refresh the page. ![tenant-management-new-tenant-in-table](./img/tenant-management-new-tenant-in-table.png) 4. Click the tenant to open details and manage assignments. ![tenant-management-tenant-details-users-tab](./img/tenant-management-tenant-details-users-tab.png) ## Update a tenant You can update the name and description of a tenant, but cannot change its ID after creation. To change a tenant's ID, you must delete the tenant and create a new one. To update a tenant: 1. Log in to Admin in your cluster, and select the **Tenants** tab. 2. Click the **pencil icon** next to the tenant you want to update. 3. Update the tenant details: - **Name**: The name of the tenant. - **Description**: An optional description of the tenant. 4. Click the **Save** button. The tenant details are updated. :::note The `` tenant is a system entity and cannot be updated. ::: ## Delete a tenant To delete a tenant, click on the **Delete** option in the list of tenants, and confirm the deletion. :::note The `` tenant is a system entity and cannot be deleted. ::: ## Tenant assignments You can assign the following entities to a tenant: - [Users](user.md) - [Groups](group.md) - [Roles](role.md) - [Mapping rules](mapping-rules.md) - [Clients](client.md) You can manage these assignments by selecting the relevant tab on the tenant details page. ### Assign users to a tenant 1. Select the **Users** tab. 2. Click **Assign user**. In the modal, enter the username and confirm. The username field has to match [the value of the claim configured as `username-claim`](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md#step-4-configure-the-oidc-connection-details). ![tenant-management-assign-users-modal](./img/tenant-management-assign-users-modal.png) 3. The user appears in the list after assignment. Refresh the page if needed. ![tenant-management-assigned-users](./img/tenant-management-assigned-users.png) ### Assign groups to a tenant 1. Select the **Groups** tab. 2. Click **Assign group**. Search for a group ID and confirm. ![tenant-management-assign-groups-modal](./img/tenant-management-assign-groups-modal.png) 3. The group appears in the list after assignment. Refresh the page if needed. ![tenant-management-assigned-groups](./img/tenant-management-assigned-groups.png) ### Assign roles to a tenant 1. Select the **Roles** tab. 2. Click **Assign role**. Search for a role ID and confirm. ![tenant-management-assign-roles-modal](./img/tenant-management-assign-roles-modal.png) 3. The role appears in the list after assignment. Refresh the page if needed. ![tenant-management-assigned-roles](./img/tenant-management-assigned-roles.png) ### Assign mapping rules to a tenant :::note Assignment of [mapping rules](../concepts/access-control/mapping-rules.md) is only available for OIDC authentication. On Self-Managed, see [OIDC authentication](../concepts/access-control/connect-to-identity-provider.md#self-managed). On SaaS, mapping rules become available after you [connect an external identity provider](/components/saas/clusters/connect-external-identity-provider.md) to the cluster. ::: 1. Select the **Mapping rules** tab. 2. Click **Assign mapping rule**. Search for a mapping rule ID and confirm. ![tenant-management-assign-mapping-rules-modal](./img/tenant-management-assign-mapping-rules-modal.png) 3. The mapping rule appears in the list after assignment. Refresh the page if needed. ![tenant-management-assigned-mapping-rules](./img/tenant-management-assigned-mapping-rules.png) ### Assign clients to a tenant 1. Select the **Clients** tab. 2. Click **Assign client**. Enter the client ID and confirm. ![tenant-management-assign-client-modal](./img/tenant-management-assign-client-modal.png) 3. The client appears in the list after assignment. Refresh the page if needed. ![tenant-management-assigned-clients](./img/tenant-management-assigned-clients.png) --- ## Users Users are individuals who are granted with access to an orchestration cluster and it's components like Operate, Tasklist and REST API. User management differs depending on whether you are using Camunda 8 SaaS or a Self-Managed installation. ## SaaS In a SaaS environment, user management is handled through [Camunda Hub](/components/hub/organization/users-and-roles.md). From Camunda Hub, you can [invite new users to your organization](/components/saas/organization/create-manage-users.md#add-a-user) and [manage their roles](/components/saas/organization/create-manage-users.md#edit-or-remove-a-user). For more advanced user management, you can configure [single sign-on (SSO)](/components/saas/organization/external-sso.md) to integrate with your own identity provider. ## Self-Managed For Self-Managed deployments, user management depends on your authentication setup: - When using **Basic authentication**, users are managed through Admin. This involves creating, updating, and deleting them directly in your cluster. - If you have configured an external [OpenID Connect (OIDC) provider](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md), user management is handled by that provider. The following sections describe how to manage users in a Self-Managed environment with **Basic authentication** enabled. ### Create a user To create a user: 1. Log in to Admin in your cluster, and click on the **Users** tab. 2. Click on the **Create user** button, and provide the following user details: - **Username**: The username for the user. - **Name**: The name of the user. - **Email**: The email address of the user. - **Password**: The password for the user. 3. Click on the **Create user** button. The user is created, and can now log in to the Camunda 8 web applications. ![identity-create-user-tab](./img/create-user-tab.png) ### Update a user 1. Log in to Admin in your cluster, and click on the **Users** tab. 2. Click on the **pencil icon** next to the user you want to update. :::note You can also select the user, and click the three vertical dots > **Update**. ::: 3. Update the user details: - **Name**: The name of the user. - **Email**: The email address of the user. - **Password**: The password for the user. 4. Click on the **Save** button. The user details are updated, and the user can now use these credentials to log in. ![identity-update-user-tab](./img/update-user-tab.png) ### Delete a user 1. Log in to Admin in your cluster, and click on the **Users** tab. 2. Click on the **Delete** button next to the user you want to delete. :::note You can also select the user, and click the three vertical dots > **Delete**. ::: 3. Confirm the deletion by clicking on the **Delete** button in the confirmation dialog. The user is deleted, and can no longer log in to the Camunda 8 web applications. ### Assign authorizations to a user See the [authorization](./authorization.md) section to learn how to create authorizations for users. --- ## Benchmark Data Regarding maintenance of the by_models.csv file (in static\data\by_models.csv) that feeds into the component from the LiveBenchModelFilter file (in components\react-components\livebench-model-filter.js). # Benchmark Data This repo contains benchmark CSVs for LLMs. They are exported manually (copy-pasted) from: - [LiveBench](https://livebench.ai/#/) You can use the script file (in static\data\script\Clean_and_Extend_getlivebench_data.ipynb) to fetch the artificial analysis data faster. ## How to update 1. Open livebench site. 2. Copy-paste or download the CSV as provided. 3. fetch the input and output prices from the official source. 4. compute the 3 to 1 blended, 1 being input price and 3 the output. 5. fetch speed numbers from official website or open source and compare it with the number in the csv to place it next to a comparable 0 to 10 (integer) 6. Replace the old file in this repo. 7. Commit the change. ## Format - Keep exactly the same schema as exported. - Do not rename, reorder, or edit columns. - Leave missing values blank. - Commit the **raw CSV only** (no edits, no formatting). --- ## Add tools to an AI agent ## About A tool is a single BPMN element, or a flow of BPMN elements, inside an [ad-hoc sub-process](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md) that an [LLM](/reference/glossary.md#large-language-model-llm) can choose to invoke to complete a goal. When a tool is a flow of several elements, only the root node is exposed to the LLM as a tool. You can use any BPMN element or connector as a tool. See [AI agent tool definitions](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-tool-definitions.md) for more details. :::note See a full example For this in the context of a running AI agent, see [add your first tool](/guides/getting-started-agentic-orchestration.md#step-4-add-your-first-tool). ::: ## Add an element inside the ad-hoc sub-process 1. Open your process in [Camunda Hub](/components/hub/workspace/modeler/index.md) or [Desktop Modeler](/components/modeler/desktop-modeler/index.md). 1. Click inside the ad-hoc sub-process to enter it. 1. Add a new task element. You can use any BPMN element as a tool, including service tasks, script tasks, user tasks, and sub-processes. 1. Apply the appropriate connector or task type. For example: - Use the [REST Outbound connector](/components/connectors/protocol/rest.md) to call an external API. - Use a [user task](/components/modeler/bpmn/user-tasks/user-tasks.md) to route to a human reviewer. - Use a [script task](/components/modeler/bpmn/script-tasks/script-tasks.md) to execute inline logic. 1. Make sure the element has **no incoming sequence flow**, as the AI Agent connector only resolves root-level elements as tools. :::tip You can model a sub-flow inside the ad-hoc sub-process. Only the first element in the sub-flow (the root node) is exposed to the LLM as a tool; the rest of the flow executes automatically once the LLM selects it. ::: ## Write a tool name and description The LLM selects tools based on the tool element's **ID** and its **Documentation** fields: - The element's **ID** field is always used as the tool name. - The element's **Name** field is a human-readable label shown on the diagram. - The element's **Documentation** field is used as the tool description. :::note The **ID** is used as the tool name instead of the **Name** field because element IDs are unique within a process, which gives the LLM an unambiguous identifier to reference when it calls the tool. The **Name** field is free-form text for readers of the diagram and can repeat across elements, so it only acts as a fallback description when **Documentation** is empty. ::: See [tool definitions](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-tool-definitions.md#tool-definitions) for more details. Clear, specific descriptions significantly improve the reliability of tool selection. 1. Give the element a descriptive **ID**, since this is what the LLM receives as the tool name. 1. Give the element a descriptive **Name**. Since this is used as a fallback description when **Documentation** is empty, keep it meaningful even though it's primarily a diagram label. 1. Open the **Documentation** field in the properties panel and write a description that explains: - What the tool does. - When the LLM should use it. - When it should not, especially if two tools have overlapping purposes. - Any constraints or expected inputs. :::note Modeler provides [modeling guidance](/components/modeler/reference/modeling-guidance/rules/agent-tool-documentation.md) that flags tools with missing or empty documentation as you model. ::: ### Example: weak vs. strong description A precise description makes the expected behavior explicit and reduces the risk of incorrect tool selection, repeated calls, or hallucinated behavior. Vague descriptions are the most common cause of unreliable agent behavior. See the following comparison: |   | Tool name | Documentation | | :----- | :--------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Weak | `Lookup` | `Find customer data` | | Strong | `Resolve customer by company name` | `Use this tool when a document mentions a company and you need its internal customer ID. If multiple matches are returned, request human validation before continuing.` | ## Declare AI-generated parameters with `fromAi()` If the tool requires values that the LLM should supply at runtime, such as a search query, a location, or an identifier, wrap those values in the [`fromAi()`](/components/modeler/feel/builtin-functions/feel-built-in-functions-ai-agent.md#fromaivalue) FEEL function. The function returns the value unchanged at runtime, but it registers the parameter in the tool's input schema so the LLM knows it must generate a value. Where you write the `fromAi()` call depends on whether the tool element has an element template applied: - An element with an element template applied, such as a connector task, exposes the template's own input fields. Write `fromAi()` directly in those fields. - An element without an element template, such as a plain service, script, or user task, exposes an [**Input mapping**](/components/concepts/variables.md#input-mappings) section instead. Write `fromAi()` in an input mapping entry. Both approaches produce the same tool input schema, because the AI Agent connector treats element template fields as input mappings. Use this approach for an element with an element template applied, such as a [connector](/components/connectors/use-connectors/index.md) task. 1. Select the tool element and find the template field whose value the LLM should supply. For example, the [REST outbound connector](/components/connectors/protocol/rest.md) exposes a **URL** field in its **HTTP Endpoint** section, plus **Query parameters** and **Request body** fields. 1. Set the field to a FEEL expression and wrap the value in `fromAi()`, referencing the parameter as a field of the `toolCall` context. For example, in the **URL** field: ```feel fromAi(toolCall.url, "The URL to fetch. Must be a valid HTTP(s) URL.") ``` 1. Repeat for each field the LLM should supply. A single field can also declare several parameters. For example, in the **Query parameters** field: ```feel { latitude: fromAi(toolCall.latitude, "The latitude of the location.", "number"), longitude: fromAi(toolCall.longitude, "The longitude of the location.", "number") } ``` You don't need an additional input mapping entry for these fields. The AI Agent connector handles element template fields as input mappings, so it picks up the `fromAi()` calls written directly in them. Use this approach for an element without an element template, such as a plain service, script, or user task. 1. Select the tool element and open the **Input mapping** section in the properties panel. 1. Add a new entry and set its **Local variable name**. This is the name you use to reference the value elsewhere in the element, for example in a script task's FEEL expression. 1. Set the **Variable assignment value** to a `fromAi()` call, referencing the parameter as a field of the `toolCall` context. 1. Repeat for each value the LLM should supply. For example, to let the LLM supply the URL a task should call: | Local variable name | Variable assignment value | | :------------------ | :------------------------------------------------------------------------ | | `url` | `=fromAi(toolCall.url, "The URL to fetch. Must be a valid HTTP(s) URL.")` | Whichever approach you use, the following applies: - The first argument must be a reference to a field of the `toolCall` context, such as `toolCall.url`. The AI Agent connector populates this context with the LLM-generated values. - The parameter name the LLM sees is the last segment of that reference, `url` in the previous examples, not the **Local variable name** or the template field name. - The AI Agent connector collects every `fromAi()` call in the element and combines them into one input schema for the tool. See [AI-generated parameters via `fromAi`](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-tool-definitions.md#ai-generated-parameters-via-fromai) for more details, including parameter types, optional parameters, and JSON Schema constraints. :::note While modeling, you'll receive [guidance](/components/modeler/reference/modeling-guidance/rules/agent-fromai-contract.md) that flags malformed `fromAi()` calls. When modeling in Camunda Hub, you can also [autofill a starter `fromAi()` call](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-tool-definitions.md#autofill-a-fromai-input) into a blank input. ::: ## Return the result as `toolCallResult` After the tool executes, its output must be returned in a [process variable](/reference/glossary.md#process-variable) named `toolCallResult` so the AI Agent connector can pass it back to the LLM. At runtime, each tool call produces one `toolCallResult`. The ad-hoc sub-process's multi-instance output collection aggregates these into `toolCallResults`, which the AI Agent connector reads to build the LLM's response. :::note While modeling, you'll receive [guidance](/components/modeler/reference/modeling-guidance/rules/agent-tool-output-key.md) that flags tools that do not set a result or set it under the wrong variable name. When modeling in Camunda Hub, you can also [autofill the `toolCallResult` output](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-tool-definitions.md#autofill-a-toolcallresult-output). ::: How you set `toolCallResult` depends on the BPMN element type that implements your tool. For example, a connector task exposes a dedicated [result expression](/components/connectors/use-connectors/index.md#result-expression) field, a regular task uses [output mappings](/components/concepts/variables.md#output-mappings), and a script task uses a dedicated result variable. Use the approach that matches your tool's element type: In the **Output mapping** section of a connector, set **Result Expression** to map relevant response fields into `toolCallResult`: ```feel { toolCallResult: { temperature_celsius: response.body.current.temperature_2m, wind_speed_kmh: response.body.current.wind_speed_10m, weather_code: response.body.current.weather_code } } ``` In the **Output mapping** section, add an output mapping with `toolCallResult` as the process variable name: | Variable assignment value | Process variable name | | :------------------------ | :-------------------- | | `= response.body` | `toolCallResult` | Set the script task's **Result variable** to `toolCallResult`. The FEEL expression provides the value that is assigned to it: ```feel { status: "completed", id: customerId } ``` ### Combine multiple outputs into a single `toolCallResult` When your tool produces several values that each contribute a field to `toolCallResult`, do not add one output mapping per field targeting `toolCallResult.`: - Output mappings and result variables containing a period are discouraged. See [output mappings](/components/concepts/variables.md#output-mappings). - Mapping to `toolCallResult` directly replaces the entire variable, so several mappings targeting it overwrite each other. Whether you can add fields to `toolCallResult` one at a time depends on the element type. Use the approach that matches your tool's element type: A connector task cannot add fields one at a time. A connector **Result Expression** has no access to process variables, so it cannot read the current value of `toolCallResult`. Build the complete result in a single **Result expression** instead: ```feel { toolCallResult: { status: response.status, body: response.body } } ``` A regular task, such as a user task, can add fields one at a time. Use the [`context put()`](/components/modeler/feel/builtin-functions/feel-built-in-functions-context.md#context-putcontext-key-value) FEEL function to add a single key to the existing `toolCallResult` context without replacing it. Write the call as the **Variable assignment value** of a mapping whose **Process variable name** is `toolCallResult`: | Variable assignment value | Process variable name | | :------------------------------------------------------------- | :-------------------- | | `=context put(toolCallResult, "status", response.body.status)` | `toolCallResult` | A script task can add fields one at a time. Use the [`context put()`](/components/modeler/feel/builtin-functions/feel-built-in-functions-context.md#context-putcontext-key-value) FEEL function to add a single key to the existing `toolCallResult` context without replacing it. Write the call directly in the script task's FEEL expression in the **Script** section: ```feel context put(toolCallResult, "status", response.body.status) ``` :::note The `toolCallResult` value can be a primitive string, a number, or a complex FEEL context object. Complex objects are serialized to JSON before being passed to the LLM. Prefer returning a structured FEEL context over a raw string when the result has multiple fields, as this gives the LLM more to work with when summarizing the outcome. If `toolCallResult` is not set or is empty after the tool executes, the AI Agent connector returns a constant success string to the LLM. ::: #### Example The following ad-hoc sub-process asks a human to approve sending an email, then sends the email or records the decline depending on the response: All BPMN elements belong to the same tool flow. Only **Ask human to send email** is exposed to the LLM as the tool, as described in [add an element inside the ad-hoc sub-process](#add-an-element-inside-the-ad-hoc-sub-process). Each element in the flow updates `toolCallResult` as the process instance evolves: 1. **Ask human to send email** is a user task and the first element in the flow, so it assigns `toolCallResult` directly through an output mapping: | Variable assignment value | Process variable name | | :------------------------ | :-------------------- | | `= { approved: true }` | `toolCallResult` | The gateway then routes the process instance based on the `approved` field. 1. **Send email** is a regular task. It adds a `sent` field to the existing `toolCallResult` with an output mapping, instead of overwriting it: | Variable assignment value | Process variable name | | :------------------------------------------- | :-------------------- | | `=context put(toolCallResult, "sent", true)` | `toolCallResult` | 1. **Record decline** is a script task. It adds `sent: false` directly in its FEEL expression: ```feel context put(toolCallResult, "sent", false) ``` The following table shows how `toolCallResult` accumulates fields as the process instance progresses through each branch: | Step | If approved | If declined | | :---------------------------------------- | :------------------------------- | :--------------------------------- | | After **Ask human to send email** | `{ approved: true }` | `{ approved: false }` | | After **Send email** / **Record decline** | `{ approved: true, sent: true }` | `{ approved: false, sent: false }` | By the time the ad-hoc sub-process completes, `toolCallResult` is a single structured object with the full history of the tool call, which the AI Agent connector passes back to the LLM. --- ## Agent definitions and instances ## About Camunda models AI agents using the same definition-and-instance relationship as [processes](/components/concepts/processes.md). An **agent definition** describes a deployed agent, while an **agent instance** represents a specific running execution of that agent. ### Why definitions and instances are separate An AI agent is not the same as the BPMN element that defines it, and it does not have the same lifecycle as an element instance. - A single agent element defines one agent, whether it is an [AI Agent Sub-process](/reference/glossary.md#ad-hoc-sub-process), an [AI Agent Task](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md), or an [external agent](/reference/glossary.md#external-agent). - Each time the process activates that element, Camunda creates an element instance. - The agent instance can be **reused across several element instances** within the same process instance. For example, in a process where the execution returns to the agent element after a user reply, the agent element is activated more than once. Each activation is a separate element instance, but they share the same agent instance so the agent keeps its memory and continues the same conversation. This reuse is what allows an agent to hold a multi-turn conversation across a loop in the process. ## Agent definitions An AI agent definition is a first-class, queryable resource that Camunda creates when you deploy a process containing one or more agents. Use the [Agent Definition API](/apis-tools/orchestration-cluster-api-rest/specifications/get-agent-definition.api.mdx) to get an agent definition by key, or the [search endpoint](/apis-tools/orchestration-cluster-api-rest/specifications/search-agent-definitions.api.mdx) to list agent definitions filtered by any of their properties. Camunda creates one agent definition per agent element in a deployed process, analogous to how a [DRD](/reference/glossary.md#drd-decision-requirements-diagram) deployment creates one decision definition per decision. An agent definition is a **structural descriptor** of the agent, not a store of its runtime configuration. An agent definition is bound to a specific process definition version. Deploying a new version of a process creates a new agent definition for each of its agent elements, in the same way that each process version has its own process definition. With agent definitions, you can inventory the agents deployed to your cluster, aggregate per-agent metrics in Optimize, and confirm that an agent exists before starting one of its instances. ### What an agent definition contains An agent definition contains the following data: | Property | Description | | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `agentDefinitionKey` | The unique key for this agent definition. A new key is assigned for the same process element on every process definition version. | | `agentType` | The [agent type](/components/agentic-orchestration/ai-agents.md#agent-types): either a [Camunda AI agent](/reference/glossary.md#camunda-ai-agent) or an [external agent](/reference/glossary.md#external-agent). | | `name` | The human-readable name of the process element that owns the agent definition. Falls back to `elementId` when the element has no BPMN name configured. | | `elementId` | The BPMN element ID of the process element that owns the agent definition. | | `processDefinitionId` | The BPMN process ID of the process definition that owns the agent definition. | | `processDefinitionKey` | The key of the process definition that owns the agent definition. | | `processDefinitionVersion` | The version of the process definition that owns the agent definition. | | `processDefinitionVersionTag` | The version tag of the process definition that owns the agent definition. | | `tenantId` | The tenant ID of this agent definition. | ### Mark an element as an agent For Camunda to recognize an element as an agent, the element **must be marked** in the BPMN model with the `zeebe:agentDefinition` extension element. For a [Camunda AI agent](/reference/glossary.md#camunda-ai-agent), the AI Agent Sub-process and AI Agent Task element templates add the marker for you when you model in Camunda Modeler. For an [external agent](/reference/glossary.md#external-agent), Camunda doesn't ship an element template, so add the marker to the BPMN XML yourself. See [connect an external agent](/components/agentic-orchestration/connect-external-agent.md) for the full setup, including how to package your agent as a custom element template. :::info Update element templates created before Camunda 8.10 If you modeled the agent element before Camunda 8.10, update to the latest AI Agent Sub-process or AI Agent Task element template. Open the process in Camunda Hub or Desktop Modeler, select the agent element, click **Update element template** in the properties panel to apply the latest template version, and redeploy the process. ::: #### Mark an element as an agent in XML The marker is an extension element on the [ad-hoc sub-process](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md) or [service task](/components/modeler/bpmn/service-tasks/service-tasks.md) that hosts the agent. Its `agentType` attribute declares the [agent type](#what-an-agent-definition-contains), and accepts `aiAgentSubProcess`, `aiAgentTask`, or `external`. An AI Agent Sub-process marked as an agent: ```xml ``` An external agent marked as an agent: ```xml ``` ### Reuse an agent across processes To reuse the same agent across multiple process definitions, use a [call activity](/components/modeler/bpmn/call-activities/call-activities.md). Place the agent in one process definition and call it from the [parent process instances](/reference/glossary.md#parent-process-instance). This produces a single agent definition for the reused agent, so its metrics aggregate into one registry entry. Duplicating the same BPMN element directly across several process definitions creates a separate agent definition for each copy, with no cross-definition aggregation. ## Agent instances An agent instance is a specific runtime execution of an agent definition that can be created for an active agent element. It is identified by an agent instance key, which the [Agent Instance API](/apis-tools/orchestration-cluster-api-rest/specifications/get-agent-instance.api.mdx) uses to represent the agent's state, including conversation, tool calls, and reasoning, for visibility and explainability in tools like Operate. This representation is not the source of truth for the agent's runtime execution; how an agent's actual state is stored depends on its type, as described in [Agent context and memory](#agent-context-and-memory). For [Camunda AI agents](/reference/glossary.md#camunda-ai-agent), both the AI agent Sub-process and AI Agent Task types, the AI Agent connector automatically creates the agent instance through the [Agent Instance API](/apis-tools/orchestration-cluster-api-rest/specifications/create-agent-instance.api.mdx) as the first step in handling the job for an active agent element. For [external agents](/reference/glossary.md#external-agent), the external runtime creates the instance itself by calling the same API, which can happen at any point while the element is active. You can reuse an agent instance across multiple element instances within the same process instance, allowing the agent to maintain a multi-turn conversation when the process loops back to it. Use the [search endpoint](/apis-tools/orchestration-cluster-api-rest/specifications/search-agent-instances.api.mdx) to list agent instances filtered by any of their properties, including their agent definition key. For example, you can find every runtime instance created from a specific process definition version. ### Agent context and memory For [Camunda AI agents](/reference/glossary.md#camunda-ai-agent), the AI Agent connector keeps the agent's runtime state in an agent context object. The context holds the conversation, tool calls and their results, reasoning traces, and metadata such as token usage. It also records the agent instance key, which links the context back to its agent instance. [External agents](/reference/glossary.md#external-agent) don't use this agent context. Their runtime manages the agent's actual state independently of Camunda, and reports only what it chooses through the [Agent Instance API](/apis-tools/orchestration-cluster-api-rest/specifications/create-agent-instance.api.mdx) for visibility. By default, the agent context is stored as a process variable, typically named `agent`, and is available both on the agent element and on the process instance. When the process returns to the agent element, the agent evaluates a FEEL expression (for example, `agent.context`) to load the existing context and continue the conversation with the same agent instance. You control this behavior through the agent's memory configuration: - **Reuse the context** to continue an existing conversation. The process passes the stored context back to the agent element, and the same agent instance handles each activation. - **Start with a fresh context** on each activation. The agent element receives an empty context, so Camunda creates a new agent instance every time the element is entered, and no memory carries over. Where the context is stored depends on the memory storage type. See [memory](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess.md#memory) for more details. ### Data available in Operate Operate surfaces agent instance data so you can monitor an agent as part of its process instance. See [monitor your AI agents with Operate](/components/agentic-orchestration/evaluate-agents/monitor-ai-agents.md) for a hands-on guide to inspecting this data. The following data is available for an agent instance in Operate: | Data | Description | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Agent instance key | The unique identifier of the agent instance. Use it to look up or interact with the agent through the [Agent Instance API](/apis-tools/orchestration-cluster-api-rest/specifications/create-agent-instance.api.mdx). | | Agent state | The current execution state of the agent, such as initializing, tool discovery, thinking, tool calling, or idle. The state is also highlighted on the BPMN diagram. See [states](/components/agentic-orchestration/agent-states-and-metrics.md#agent-states) for what each state means and what triggers a transition. | | Usage metrics | Token consumption, tool call count, and model call count. Model calls are shown against the configured limit, so you can see how close the agent is to its limit. See [usage metrics](/components/agentic-orchestration/agent-states-and-metrics.md#usage-metrics) for details. | | Model | The LLM the agent is running against. | | System prompt | The system prompt the agent was configured with. | | Tool definitions | The [tools](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-tool-definitions.md) available to the agent, resolved from the agent's ad-hoc sub-process. | | Conversation history | The decision trail of the agent execution: initial configuration, user prompts, assistant messages, the tools the agent selected with its reasoning, and tool calls with their inputs and results. | #### Conversation history and loop iterations The conversation history captures the full reasoning chain of an agent execution, grouped by loop iteration. A [loop iteration](/reference/glossary.md#loop-iteration) is one pass through the [agent loop](/reference/glossary.md#agent-loop): the model reasons over the current messages, optionally calls tools, and receives the tool results that become the input for the next loop iteration. Grouping the history by loop iteration makes it easier to reference a specific point in an agent's execution. Rather than describing a moment in time, you can refer to a specific loop iteration, for example "on loop iteration five the agent called this tool." Operate labels each entry in the conversation history simply as `iteration` (for example, `5. iteration`) as shorthand for loop iteration. #### Model reasoning in conversation history Operate displays readable model reasoning as a static **Thinking** entry before the assistant’s response. It recognizes non-empty text in the assistant history marked with `camunda.agenticai.content.type: reasoning`. To display readable model reasoning where supported, [migrate to the AI Agent element templates introduced in Camunda 8.10](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-upgrade.md). If you do not see **Thinking**, confirm that your [model provider](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-model-providers.md) returns readable reasoning. Opaque or redacted reasoning is not displayed. #### Visibility for external agents Agents built with external frameworks get the same visibility in Operate as Camunda AI agents. An external agent reports its system prompt, available tools, tool calls, and conversation history through the [Agent Instance API](/apis-tools/orchestration-cluster-api-rest/specifications/create-agent-instance.api.mdx), and Operate displays that data alongside the process instance. See [connect an external agent](/components/agentic-orchestration/connect-external-agent.md) for a step-by-step walkthrough of reporting this data from your own runtime. --- ## States and usage metrics ## About Every [agent instance](/components/agentic-orchestration/ai-agents.md) exposes a defined execution state and a set of usage metrics as it runs. Together, they show whether the agent is progressing normally. In this guide, you will understand the state model, what triggers each transition, and the usage metrics available for every agent instance. For guidance on using these signals to catch an agent going off-rail, see [detect off-rail agents](/components/agentic-orchestration/evaluate-agents/detect-off-rail-agents.md). ## Agent states Camunda updates an agent instance's state as it progresses through its agent loop, fed by status updates from the connector handling the agent. | State | Meaning | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Initializing` | The agent instance is being set up. | | `Tool discovery` | The agent is resolving which tools are available to it. | | `Thinking` | The agent is reasoning with the model to decide its next step. | | `Tool calling` | The agent is calling one or more tools. | | `Idle` | The process instance has moved away from the agent element, so the agent isn't currently working. It resumes when the process instance activates the element again. | | `Completed` | The agent instance is completed, because the process instance completed or terminated. | ### State transitions An agent instance follows a predictable path through these states. The following table lists what triggers entry into each state and what happens next. | State | Entered when | Moves to | | ---------------- | -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Initializing` | An agent element is activated and an agent instance is created. | `Tool discovery`, once the agent instance is set up. | | `Tool discovery` | The agent instance resolves the tools available to it. | `Thinking`, once tool definitions are resolved. | | `Thinking` | The agent instance is reasoning over the current conversation. | `Tool calling`, if the model selects one or more tools. `Idle` or `Completed`, if the model returns a final response. | | `Tool calling` | The model selected one or more tools in the previous `Thinking` state. | `Thinking`, once tool results are available. This starts the next loop iteration. | | `Idle` | The process instance moves away from the agent element, for example, to wait for a user task or message. | `Thinking`, when the process instance re-activates the same agent element and reuses this agent instance. `Completed`, if the process instance ends first. | | `Completed` | The process instance for the agent completes or terminates. | Terminal state; the agent instance stops updating. | ## Usage metrics Track usage metrics for each agent instance to monitor cost and activity. :::note Usage metrics can be lost Camunda can only record usage metrics when it's informed about them by the worker or connector that made the call to the LLM. If that worker or connector fails before it can report the usage, for example, if it crashes or loses connectivity, those metrics are lost, even though the LLM provider already processed and billed for the call. This can happen regardless of which agent implementation you use, including the [AI Agent connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md) and [external agents](/components/agentic-orchestration/connect-external-agent.md). For the authoritative token counts and costs, always refer to your LLM provider's own usage reporting rather than relying solely on Camunda's metrics. ::: ### Token consumption Camunda tracks the number of tokens consumed by the agent's model calls. Token consumption accumulates as the conversation grows: every loop iteration adds the previous tool results and the model's reasoning to the context that's sent with the next model call, so token usage tends to climb with each additional loop iteration. Rising token consumption without a final response is a signal that the conversation is growing without converging on an outcome. Token usage is also a direct driver of the LLM provider cost for the agent's execution. When using the [AI Agent connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md), see [context window size](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess.md#memory) for how conversation length is capped independently of token count. ### Tool call count Camunda counts the number of tool calls the agent instance has made across all loop iterations. A climbing tool call count generally reflects active work, but a count that climbs while the agent calls the same tool repeatedly with similar or identical inputs can indicate the agent is stuck rather than progressing. ### Model call duration Camunda records the duration of each model call in the agent's conversation history. A model call that takes far longer than comparable ones is a sign the agent isn't progressing normally. This duration is surfaced per model call, not for tool results. Tool execution duration is not part of the conversation history metrics. When a tool is a BPMN element, you can see how long its execution took from the usual element instance details in the details tab for that tool activation. --- ## Get started with agentic orchestration ## About agentic orchestration Agentic orchestration is a hybrid orchestration model that combines the power of deterministic logic with LLM-driven flexibility. An [AI agent](/reference/glossary.md#ai-agent) evaluates the prompt, decides which tools to execute, and decides when the goal is achieved, while Camunda orchestrates the process around it with governance and auditability. Use agentic orchestration to run the non-deterministic parts of a process, where the path to the outcome can't be fully defined in advance, on the proven foundation of BPMN. ## How it works In an agentic process, decision-making and execution are intentionally split: - **AI agents decide**: Which tool to call next, in what order, and when the goal is met. - **Camunda orchestrates**: Executes the selected BPMN activities, stores variables, applies retries and incident handling, and routes human tasks and events. Agents work alongside the other participants in your end-to-end process: - **Deterministic logic**, such as gateways, rules, and service tasks, handles the steps that must always run the same way. - **People** review, approve, or take over through human tasks, so there is a human in the loop where you need one. - **Systems** are reached through connectors, APIs, and [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers, which the agent can use as tools. ## Why use agentic orchestration - **Reliability**: Camunda enforces process boundaries, and retries and incident handling apply to every tool an agent calls. - **Governance**: You control which tools an agent can use and how its results are validated, with guardrails between an agent's decision and what it actually does. - **Auditability**: Agent activity is recorded as part of the process instance, so you can inspect and monitor what an agent did and why. - **Flexibility**: Build anything from a simple task agent to a complex multi-agent system, and combine them with deterministic steps in one model. ## Next steps - Learn [what AI agents are](/components/agentic-orchestration/ai-agents.md) and how the agent loop works. - [Build your first AI agent](/guides/getting-started-agentic-orchestration.md). - Review the [AI usage guidelines](/components/agentic-orchestration/ai-usage-guidelines.md) for requirements on security, legality, data handling, and human oversight. --- ## AI agents ## About AI agents An AI agent is an addressable execution of an LLM-driven loop with shared memory context across iterations. An agent runs a loop where the model decides what to do next, which tools to invoke, and when to stop. The loop is what makes it an agent. A standalone LLM call with no loop and no autonomous tool selection, such as a single connector call that returns output along a fixed execution path, is not an agent. AI agents can perform a variety of functions, including making decisions, solving problems, interacting with external environments, and taking actions. ### Agent types Camunda supports two types of agent: - **[Camunda AI agents](/reference/glossary.md#camunda-ai-agent)** are native. They run their [agent loop](/reference/glossary.md#agent-loop) in Camunda's engine, which activates each tool call as a governed BPMN activity, maintains memory across iterations, and emits lifecycle events. They are implemented using the [AI Agent connector](#the-ai-agent-connector), either as an [AI Agent Sub-process](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess.md) or an [AI Agent Task](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md#ai-agent-task). To give a Camunda AI agent tools, see [add tools to an AI agent](/components/agentic-orchestration/add-tool-to-ai-agent.md). - **[External agents](/reference/glossary.md#external-agent)** run their [agent loop](/reference/glossary.md#agent-loop) in an external runtime, such as LangGraph, Amazon Bedrock, or custom code, instead of Camunda's engine. Camunda orchestrates when the agent runs as part of the process, but it can only surface what the runtime reports back. To make an external agent visible in Camunda, see [connect an external agent](/components/agentic-orchestration/connect-external-agent.md). :::note Camunda represents every agent with an [agent definition and agent instances](/components/agentic-orchestration/agent-definitions-and-instances.md). ::: ## The AI Agent connector The AI Agent connector is the primary Camunda connector for building Camunda AI agents. It integrates an LLM with your BPMN process, enabling the agent to reason over context, select tools, and respond to users or process events. Key capabilities include: - **LLM provider support**: Connects to a range of providers, such as Anthropic, Amazon Bedrock, Google Gemini, and OpenAI. - **Tool calling**: Exposes BPMN activities inside an [ad-hoc sub-process](/reference/glossary.md#ad-hoc-sub-process) as tools the LLM can select. - **Memory**: Short-term conversational memory enables multi-turn interactions and follow-up questions within a process instance. See the [AI Agent connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md) documentation for full configuration details, implementation examples, and reference. ### Integrate an AI agent into your process The recommended approach for most use cases is to use the [AI Agent Sub-process](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess.md) implementation due to its simplified configuration and support for event sub-processes. In this approach, you integrate the agent using an [ad-hoc sub-process](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md) and the AI Agent connector in an [agent loop](/reference/glossary.md#agent-loop), where the agent understands the process goal and uses the available tools to complete it. #### How the agent loop works The AI Agent connector operates in an agent loop between the LLM and Camunda: 1. A user prompt is sent to the connector. The LLM evaluates the prompt, the system prompt, and the available tool definitions. 1. If the LLM determines that a tool call is needed, Camunda activates the corresponding BPMN activity in the ad-hoc sub-process. 1. The tool result is passed back to the LLM, which decides whether more tool calls are needed. 1. The loop continues until the LLM returns a final response, which can then be routed to the next step in the process. Decision-making and execution are intentionally split: - **LLM decides**: Which tool to call next, in what order, and with which parameters. - **Camunda orchestrates**: Executes the selected BPMN activity, stores variables, applies retries and incident handling, and routes human tasks and events. Think of the ad-hoc sub-process as a governed toolbox: - Each element can be selected by the LLM as a tool. - Elements can be executed multiple times, in different orders, in parallel, or skipped. - The LLM chooses a path from the allowed options, while Camunda enforces process boundaries and execution reliability. :::tip Learn more in the [example AI Agent Sub-process connector integration](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess-example.md) and [Add tools to an AI agent](/components/agentic-orchestration/add-tool-to-ai-agent.md). ::: #### Example: an export compliance agent Consider an export shipment process where the compliance details arrive as free text instead of pre-parsed fields, and a shipment must be checked before it can proceed. An AI agent extracts what it needs from the text and calls tools built from real connector calls: - **Verify genetic marker**: a SQL query against a genetic marker database. - **Check destination country**: a GraphQL query that resolves the destination to an ISO country code. - **Compute compliance score**: a REST connector call that scores the shipment from the marker and country. The agent extracts the marker and destination country from the shipment notes, decides which tools to call, and returns a decision. Camunda then applies deterministic routing: an exclusive [gateway](/reference/glossary.md#gateway) sends cleared shipments to an automatic notification, and flagged shipments to a [human task](/reference/glossary.md#human-task) for review. ![Export compliance agent BPMN process, showing an AI agent ad-hoc sub-process followed by a gateway that routes to either an automatic notification or a human review task](img/seed-export-compliance-agent.png) See the [export compliance agent example](https://github.com/camunda/camunda-8-tutorials/tree/main/examples/task-agent) for the full BPMN models and setup instructions. ## AI agent integration features Use the following Camunda 8 features to integrate AI agents into your processes: **Feature** **Description** [Ad-hoc sub-process](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md) A special kind of embedded BPMN subprocess with an ad-hoc marker that allows a small part of your process decision-making to be handed over to a human or agent. [AI Agent connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md) Enables AI agents to integrate with an LLM to provide interaction/reasoning capabilities. This connector is designed for use with an ad-hoc sub-process in an agent loop, providing automated tool selection. [MCP Client connector](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-client.md) Connect an AI agent connector to tools exposed by [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers. [Ad-hoc tools schema resolver connector](/components/connectors/out-of-the-box-connectors/agentic-ai-ahsp-tools-schema-resolver.md) Can be used independently with other AI connectors for direct LLM interaction. Use this connector if you don't want to use the AI agent connector but still want to resolve tools for an ad-hoc sub-process or debug tool definitions. [Vector database connector](/components/connectors/out-of-the-box-connectors/embeddings-vector-db.md) Allows embedding, storing, and retrieving LLM embeddings. Use this connector to build AI-based solutions such as context document search, long-term memory for LLMs, and agentic AI interaction. --- ## AI usage guidelines ## About Camunda AI features Camunda AI features and capabilities currently include: **Feature** **Description** [Process OS Harness](../process-os-harness/overview.md) Discovers your existing processes, re-engineers them to use AI, and generates executable Camunda solutions. [Agentic orchestration](./agentic-orchestration-overview.md) Embed AI agents into BPMN processes so that an AI model can, within boundaries you define, decide which tools to call to reach a goal. [Camunda Copilot](../early-access/alpha/bpmn-copilot/bpmn-copilot.md) AI-assisted process, form, and decision modeling. Describe what you need in natural language and Camunda generates BPMN diagrams, Camunda Forms, and FEEL expressions. [Intelligent Document Processing (IDP)](../hub/workspace/modeler/intelligent-document-processing.md) Automated document analysis and structured data extraction, connecting to your chosen document AI provider. Camunda provides an **application and orchestration layer** that integrates AI models via interfaces. In most cases, the underlying AI models are not developed or operated by Camunda itself. Depending on your setup, AI models may be: - Provided by a third-party provider that Camunda has selected and licensed (for example, in SaaS deployments). - Provided by a third-party provider that you select and contract directly. - Brought and operated by you under a bring-your-own-model approach. The specific models and providers active in your environment are described in the applicable service description and documentation. ## How to use AI services responsibly The following principles apply across all AI service use cases. They reflect both good engineering practice and regulatory expectations. | Principle | What it means in practice | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Security | Use as little data, as few permissions, and as few interfaces as necessary for your use case. Before using any data or system, confirm you are authorized to use it in the way you intend. | | Legality | Comply with all applicable laws, industry regulations, and contractual obligations, including the EU AI Act where it applies to your use case. | | Fairness | Review AI-generated outputs to assess whether they could have a discriminatory effect on individuals or groups. | | Transparency | Document data sources, AI models, versions, prompts, and key decisions. Where applicable law requires it, label AI-generated content as such. | | Monitoring | Monitor AI service usage at intervals appropriate to the risk level of your use case, and keep records of your monitoring activities. | ## What these features are designed for Camunda's AI services are designed and intended for use in non-high-risk scenarios. If your use case qualifies as high-risk, you are responsible for compliance with the applicable legal requirements. [Reach out to Camunda](https://www.camunda.com/services/support-guide/) before proceeding so we can discuss what that means for your specific scenario. ### Use cases that may qualify as high-risk If your use case involves any of the following, take a closer look before deploying: - Recruitment or selection of individuals. For example, filtering job applications or evaluating candidates. - Decisions affecting employment relationships. For example, promotion, termination, or performance monitoring. - Creditworthiness assessment or credit scoring (except systems used solely for fraud detection). - Risk assessment or pricing for life and health insurance. If you're unsure, [contact Camunda](https://www.camunda.com/services/support-guide/). ## Evaluating AI outputs AI-generated outputs may be incorrect, incomplete, biased, or outdated. Always review outputs critically before acting on them, particularly in business-critical, legally relevant, or financially significant contexts. In those contexts, consider implementing a [Human-in-the-Loop (HITL)](/reference/glossary.md#human-in-the-loop-hitl) mechanism: a human review step before the output is acted upon. ## The AI models behind these features Camunda's AI services integrate models from various sources. Here is what you need to know depending on your setup. ### Camunda-provided models If Camunda offers its own AI model as part of an AI service and that model qualifies as a general-purpose AI (GPAI) model under the AI Act, Camunda provides it as a GPAI model without systemic risk unless Camunda notifies you otherwise. ### Fine-tuning and training Where Camunda enables you to refine AI models through fine-tuning or other training methods, be aware that Camunda does not assume responsibility for the resulting model being classified as a GPAI model with systemic risk. Any obligations that arise from such a classification are yours to manage. ### Third-party models If you train or modify a third-party model, you may become a "provider" of that model under the AI Act, with the compliance obligations that role entails. ## Knowing your AI tools In practice, AI literacy means the people in your organization who configure, operate, or make decisions based on Camunda's AI services should have a working understanding of: - What the AI services can and cannot do. - The risks associated with AI-generated outputs, including the possibility of inaccurate, biased, or incomplete results. - The requirements set out in these usage guidelines. - How to correctly interpret and apply AI outputs in your specific context. ### Practical steps How you meet this standard is largely up to you. The AI Act does not prescribe a specific training format. As a starting point, we suggest: - Providing some form of orientation before people in your organization first use Camunda's AI services. - Revisiting that orientation when the AI services change significantly or when use cases evolve. - Keeping a record of what training you have provided, in whatever format suits your organization's existing compliance processes. ## Handling data with care When using Camunda's AI services, you are submitting data, potentially including personal data, to AI models. The following requirements apply. ### Data minimization Use as little data as necessary for your intended purpose. Where possible, use anonymized or pseudonymized data. If you need to use special categories of personal data (such as health data, biometric data, or data revealing racial or ethnic origin) or data relating to minors, ensure a lawful basis exists and, where required, complete a data protection impact assessment (DPIA) first. ### Retention and compliance Follow applicable retention periods and deletion obligations. Ensure compliance with data subject rights and document data provenance, legal bases, and recipients as required by applicable data protection law. ### Confidential information Do not include secrets, credentials, or confidential information in prompts, configurations, or any other input to AI services. This includes: - API keys and passwords. - Internal pricing or financial data. - Product roadmaps. - Confidential contracts or legal documents. Only use data from approved, lawful sources, and comply with all applicable license, usage, and purpose limitation requirements. ### Security Implement appropriate measures against prompt injection, data exfiltration, and service misuse. These risks are specific to AI systems and are worth addressing separately from your general application security practices. ## Using content in AI features ### Input content Before submitting content as input to Camunda's AI services, make sure that: - You have the right to use it in the way you intend, including for AI processing. - It does not infringe third-party intellectual property rights, including copyright, trademark, database rights, and open-source license obligations. - Where the content belongs to a third party, your license covers AI training and the specific use you have in mind. ### Output content Outputs generated by AI services may be subject to copyright or related rights, the legal position varies by jurisdiction and continues to evolve. Before relying on AI-generated outputs commercially or legally, verify that your intended use is permissible. ## What you must not do The following uses of Camunda's AI services are prohibited. If you are unsure if a planned use case falls into any of these categories, [contact Camunda](https://www.camunda.com/services/support-guide/) before proceeding. These uses are prohibited regardless of technical feasibility: - **Manipulation**: Using AI to materially distort people's behavior through covert or manipulative techniques in a way likely to cause significant harm. - **Exploitation of vulnerable groups**: Deliberately exploiting the vulnerabilities of specific groups, such as children or persons with disabilities, to materially distort their behavior. - **Social scoring**: Evaluating or ranking individuals using AI in a way that leads to detrimental or unfair treatment. - **Predictive policing**: Predicting criminal offenses based on profiling or personal characteristics. - **Biometric database scraping**: Collecting facial or biometric data from the internet or camera systems without targeting specific individuals, to build or expand facial recognition databases. - **Biometric categorization**: Inferring sensitive personal characteristics from biometric data, such as racial or ethnic origin, political opinion, religious belief, health data, or sexual orientation. - **Emotion recognition at work or school**: Monitoring the emotional state of employees or learners in workplace or educational contexts. - **Real-time biometric identification in public spaces**: Using AI to identify individuals in real time in publicly accessible spaces for law enforcement purposes. ## Working with AI agents AI agents are AI-powered components that can, with a degree of autonomy, plan and execute multi-step actions to reach a goal, including calling tools, triggering actions in connected systems, and orchestrating sub-processes. Because of this autonomy, they carry higher operational and legal risk than assistive AI features like the Copilot. The requirements below are not about limiting what you can build: Camunda's AI agent framework is designed to be flexible and powerful. They are about helping you deploy agents safely, so that the autonomy you enable stays within the boundaries you intend. ### Before you deploy: planning A small amount of upfront planning makes AI agent deployments significantly safer and easier to manage. Before deploying an agent into production, document: - What the agent is supposed to do, and what it is not supposed to do. - How much autonomy it has. For example, whether it can act immediately or only propose actions for human approval. - What "success" looks like, and under what conditions the agent should be stopped. - Which systems, processes, and interfaces are in scope. ### Governance For any agent running in production, define clear answers to: who owns this use case, who is responsible for the technical implementation, who handles incidents, and how changes are approved. The AI agent has no legal capacity, and everything it does is legally attributed to your organization, so knowing who is accountable matters. ### Pilot before you scale Running a limited pilot before full deployment gives you the opportunity to catch unexpected behaviors in a controlled environment. A good pilot has defined success criteria and a clear process for incorporating what you learn before going broader. ### Human oversight Maintain effective human oversight over any AI agent you deploy. This is also a sound operational principle; autonomous agents acting in production systems can have real-world consequences that are difficult to reverse. At a minimum, human oversight means: - Someone with the authority and technical ability to stop the agent at any time using a [kill switch](/reference/glossary.md#kill-switch) or equivalent mechanism. - Defined limits on what the agent can do. For example, caps on transaction amounts, action frequency, or which systems it can write to. - A human approval step for any action with significant legal, financial, or safety implications. - A regular review process so you can catch drift or unexpected behavior before it becomes a problem. How you implement these controls is up to you. The right approach depends on your use case, your risk tolerance, and your existing operational processes. These guidelines do not prescribe a specific technical implementation. ### Technical readiness Before going to production with an AI agent, ask: - Have we tested for edge cases and unexpected inputs, including adversarial ones like prompt injection? - Do we have monitoring in place to detect errors or anomalous behavior? - Can we roll back if something goes wrong? These are standard questions for any production system and they matter more for AI agents because the failure modes can be less predictable. ### Data and tool access Agents with broad access to data and tools are harder to control and audit. As a general principle, give agents access to only what they need to do their job, the least-privilege principle. In practice this means: - Knowing what data the agent can reach and how sensitive it is. - Restricting the tools and actions available to the agent to those actually needed for the use case. - Keeping production data access as narrow and time-limited as possible. ### Transparency and communication Users interacting with AI-driven processes should know they are doing so. Make sure that: - AI-driven interactions are clearly identified. - Users know how to escalate or get help. - There is a plan for communicating disruptions or incidents, both internally and, where required, externally. The same applies to interfaces you build on top of Camunda's AI features. When you present AI interactions or AI-generated content to your users, make it clear that AI is involved. [Article 50 of the EU AI Act](https://eur-lex.europa.eu/eli/reg/2024/1689/oj) provides transparency requirements for certain AI systems that interact with people or generate content. Camunda identifies the AI-generated content it produces as part of its responsibilities as a provider. When you expose an agent or its output through your own interface, add appropriate transparency notices for your users. For example, identify a chat assistant as AI-powered or label AI-generated text and documents before presenting them to end users. ### Bias and fairness Where an AI agent makes or influences decisions in sensitive areas, such as HR, financial access, or similar, build in a periodic fairness review. This does not need to be a formal audit process; it can be as simple as periodically reviewing a sample of agent decisions to check that outcomes are consistent and not systematically skewed. ### Documentation Keep a record of the key decisions you made when setting up and operating your AI agent, including what it does, how it is configured, what controls are in place, and how those have evolved over time. Good documentation makes incidents easier to investigate and changes easier to manage. It also supports your compliance obligations under the AI Act if your use case is ever scrutinized. --- ## Camunda-provided LLM ## About Camunda-provided LLM is a Camunda-managed LLM provider option that comes with automatically configured credentials. With it, you can run AI agents in your processes right away without additional setup. Camunda-provided LLM is only available in Camunda 8 SaaS. It is not available in Camunda 8 Self-Managed. :::info Camunda-provided LLM is free to use within the provided budget, and is intended for testing and experimentation. When you're ready for production or need more control, switch to a customer-managed provider. ::: Key benefits: - **No LLM account setup required.** You don't need to sign up with a model provider or configure credentials to start exploring AI agents. - **Compare different models.** Run your agent with different models and easily switch between them to find the best fit for your use case. - **No surprise bills.** Your organization gets a free, preconfigured budget for testing and experimentation. - **Instant blueprints.** AI agent blueprints that use Camunda-provided LLM work out of the box with no configuration needed. - **Seamless transition.** When you're ready for production, switch to a customer-managed provider like AWS Bedrock without changing your process architecture. Camunda-provided LLM is available in Camunda SaaS for: - **SaaS trial organizations**: Includes Camunda-managed credentials and a free budget. Camunda-provided LLM is enabled by default. - **SaaS enterprise organizations**: Includes a larger budget to support multiple proofs of concept. You must explicitly [enable the **Camunda Provided LLM** toggle](/components/saas/organization/enable-alpha-features.md#enable-camunda-provided-llm) in Camunda Hub. This is a separate toggle from **AI-powered features**, which controls other AI capabilities such as Camunda Copilot. The first time you enable either toggle, you must accept the **Terms for AI Usage**. :::note Availability, budgets, and UI may vary by environment and rollout stage. ::: See [Trial vs. enterprise budgets](#trial-vs-enterprise-budgets) for more details. ## Set up Camunda-provided LLM Once Camunda-provided LLM is available in your organization, its credentials are populated automatically as [SaaS-managed secrets](/reference/glossary.md#saas-managed-secret). - If you are using an AI agent blueprint, no additional configuration is needed in most cases. Explore selected AI agent blueprints in the [Camunda Marketplace](https://marketplace.camunda.com/en-US/home). - If you are building your own agent from scratch, enable Camunda-provided LLM by configuring your [AI Agent connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md) with the following parameters: - **Provider**: `OpenAI Compatible`. - **API endpoint**: `{{secrets.CAMUNDA_PROVIDED_LLM_API_ENDPOINT}}`. - **API key**: `{{secrets.CAMUNDA_PROVIDED_LLM_API_KEY}}`. - **Model**: Select a model from the [list of supported models](#supported-models). For example, `anthropic/claude-sonnet-4.6`. ## Supported models Camunda-provided LLM uses a managed LLM gateway that supports the models listed below. You can switch between these models to compare how your agent performs with each one. When using the AI Agent connector, set the **Model** field to one of the following values: | Model | Value to set in **Model** | What it's good for | | :------------------------------------------------------------------------------- | :---------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Anthropic Claude Sonnet 5](https://openrouter.ai/anthropic/claude-sonnet-5) | `anthropic/claude-sonnet-5` | Best as the default for complex agent tasks, balancing strong reasoning, reliable tool use, speed, and budget consumption, with improvements over Sonnet 4.6. | | [Anthropic Claude Sonnet 4.6](https://openrouter.ai/anthropic/claude-sonnet-4.6) | `anthropic/claude-sonnet-4.6` | Best as the default for complex agent tasks, balancing strong reasoning, reliable tool use, speed, and budget consumption. | | [Anthropic Claude Opus 4.5](https://openrouter.ai/anthropic/claude-opus-4.5) | `anthropic/claude-opus-4.5` | Best for the most demanding agents: multi-step reasoning, many tools, and ambiguous or high-stakes decisions, where quality matters more than speed and budget consumption. Consumes budget fastest. | | [Anthropic Claude Haiku 5.5](https://openrouter.ai/anthropic/claude-haiku-5.5) | `anthropic/claude-haiku-5.5` | Best for lightweight assistants, short interactions, and lower-cost tasks that still need good instruction following, with improvements over Haiku 4.5. | | [Anthropic Claude Haiku 4.5](https://openrouter.ai/anthropic/claude-haiku-4.5) | `anthropic/claude-haiku-4.5` | Best for lightweight assistants, short interactions, and lower-cost tasks that still need good instruction following. | | [Amazon Nova Pro](https://openrouter.ai/amazon/nova-pro-v1) | `amazon/nova-pro-v1` | Best for general-purpose, cost-efficient agents with straightforward tool use and document or text processing, as a non-Anthropic option at a moderate cost. | | [OpenAI gpt-oss-120b](https://openrouter.ai/openai/gpt-oss-120b) | `openai/gpt-oss-120b` | Best for agents that need strong reasoning and tool use from an open-weight model at a lower cost than frontier models. | | [OpenAI gpt-oss-20b](https://openrouter.ai/openai/gpt-oss-20b) | `openai/gpt-oss-20b` | Best for simple, high-volume, or latency-sensitive tasks such as classification, extraction, and short answers, at the lowest cost. | :::note When selecting a model, consider your process requirements, expected usage volume, and token budget. For model selection guidelines, see how to [choose the right LLM](./choose-right-model-agentic.md). ::: ## Trial vs. enterprise budgets The budgets, measured in **dollars (USD) spent**, differ depending on your SaaS plan: - **Trial**: A smaller budget intended for quick evaluation and early experiments by individuals and small teams. - **Enterprise**: A larger budget intended for broader team experimentation and proofs of concept. :::important Budgets are topped up automatically and enforced at the organization level (not per user). This means multiple users in the same organization draw from the same budget. ::: ### What the budget covers The Camunda-provided LLM budget covers LLM provider calls during AI agent execution: - **Trial budget**: Allows for a hundred to a few thousand agent runs, depending on the model used and the agent complexity. - **Enterprise budget**: Is significantly larger to support more extensive experimentation. Other Camunda AI features, such as Camunda Copilot, do not consume your Camunda-provided LLM budget and can be used independently. :::note The total cost of an agent run depends on how many LLM calls it makes, which can vary based on the agent’s design and task complexity. Cost also depends on the model used, since different models have different per-token pricing. ::: ### When budget is exhausted When your organization reaches its Camunda-provided LLM budget cap: - Additional LLM calls are **blocked**. - Your process execution may fail with an “out of budget” error, such as `COST_LIMIT_EXCEEDED`, depending on how your process handles errors. :::tip If your process model doesn’t handle LLM failures, an exhausted budget may result in incidents or failed instances. Consider adding BPMN error handling to provide a user-friendly fallback path. ::: ## Monitor usage The Camunda-provided LLM budget is shared across your organization, so you should monitor consumption. Camunda Hub shows usage statistics for the Camunda-provided LLM, including: - How much of your budget has been used. - How much budget remains. Use this data to plan your transition to a customer-managed provider when you're ready for production. ## Switch away from Camunda-provided LLM As you move from evaluation to production, you may want to switch to your own LLM provider. This gives you: - Direct control over provider choice. - Your own billing and quota management. - The ability to scale beyond the Camunda-provided LLM budget caps. :::important Before you begin - Ensure your organization has access to the LLM provider you plan to use. - Gather credentials and any required configuration. - Identify where your current AI agent models rely on Camunda-provided LLM defaults. ::: To switch away, follow these steps: 1. Add your LLM provider credentials in the appropriate Camunda location for managing secrets and credentials. 2. Update your AI Agent connector configuration to use the new LLM provider. 3. Re-deploy your process. 4. Test a process instance end-to-end and verify results. Your orchestration model doesn’t change during this transition. The BPMN process, event choreography, and human touchpoints you designed with Camunda-provided LLM carry forward unchanged, while only the LLM backend configuration shifts. ## Data processing and AI terms Review how Camunda-provided LLM processes your data, how OpenRouter is involved, and which terms apply when you use this feature. ### How Camunda-provided LLM works The Camunda-provided LLM is an optional feature. When enabled, you configure which process variables, prompts, and tool outputs the agent may use. Only the data you choose to make available to the agent is sent for processing. You control this through your process configuration, such as input mappings. ### How OpenRouter is used OpenRouter, Inc. is a routing service that gives Camunda access to a range of third-party AI models through a single integration. OpenRouter does not host or train models itself. Instead, it forwards each request to the selected AI model provider for processing. Camunda restricts this feature to providers that enforce zero data retention (ZDR). This means that neither OpenRouter nor the underlying model provider retains your data after processing the request, and your data is not used to train their models. ### OpenRouter as a sub-processor OpenRouter, Inc. is a sub-processor engaged by Camunda in connection with this feature. When you enable this feature, your prompts, agent memory, and tool call inputs and outputs, which may contain personal data, are transmitted to OpenRouter and the selected model provider for the purpose of generating a response. This feature is optional and can be disabled at any time by turning off the **Camunda Provided LLM** toggle in [Camunda Hub](/components/saas/organization/enable-alpha-features.md#enable-camunda-provided-llm). When disabled, no data is sent to OpenRouter. A comprehensive list of Camunda's sub-processors is available in [Camunda's Trust Center](https://trust.camunda.com/). ### AI terms This feature is an AI Feature under Camunda's Terms for AI Usage, which apply to your use of it unless your existing agreement with Camunda provides for AI terms, in which case the latter would prevail. In addition, please refer to our [AI Usage Guidelines](/components/agentic-orchestration/ai-usage-guidelines.md) to learn more about how to use Camunda’s AI features responsibly. ### Data sharing You control what data is made available to this feature through your process configuration. Do not include sensitive data, or other content you are not authorized to share with a third-party AI service provider. --- ## Choose the right LLM ## About this guide This guide helps you evaluate and select the best LLMs based on your deployment requirements and business needs. It explains how to measure agent performance and shows how to leverage LiveBench’s standardized benchmarks to compare models effectively. ## Define your LLM needs Consider the following aspects regarding your model requirements and setup constraints: | Consideration | Description | | :------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Hosting** | Cloud-only vs. on-premises deployment. For compliance-heavy or air-gapped environments, self-hostable open-source models are preferred. | | **Data sensitivity** | Workflows handling Personally Identifiable Information (PII) or confidential data may require private deployments or self-hosting to meet data control requirements. | | **Cost vs. speed** | Larger models offer higher accuracy but often with higher latency and cost. Balance performance against Service Level Agreements (SLAs) and budgets. | | **Accuracy vs. openness** | Proprietary models often lead in benchmark accuracy. Open-source models provide flexibility, fine-tuning, and offline use cases. | ## Measure agent performance The ideal model should handle tools effectively, follow instructions consistently, and complete actions successfully. When evaluating models for your agentic process, focus on these three core capabilities: - **Tool usage:** How accurately does the model choose and use the right tools? Poor tool usage leads to failed tasks or wasted steps. - **Action completion:** Does the agent fully achieve the goals of each task without manual intervention? - **Instruction adherence:** Does the model follow your prompts, policies, and constraints as expected? :::tip To validate these capabilities against your own agent process, [test your AI agents with Camunda Process Test (CPT)](./evaluate-agents/test-ai-agents.md). ::: ## Benchmark your candidate models Once you have defined your model needs, setup requirements, and peformance metrics, standardized benchmarks help you measure these aspects objectively. They use the same tasks and conditions for every model, enabling fair comparisons. ### Learn about LiveBench metrics One such benchmark is ​​[LiveBench](https://arxiv.org/abs/2406.19314), that evaluates LLMs across multiple skill areas. It avoids common pitfalls such as test data contamination or subjective scoring by using fresh tasks and objective ground-truth answers. Each LiveBench metric represents a core capability: | Metric | What it measures | When it matters | | :--------------------------------- | :------------------------------------------------------- | :------------------------------------------------ | | **Reasoning** | Logical thinking and stepwise problem-solving. | Strategic planning, multi-step workflows. | | **Math** | Numerical accuracy and quantitative reasoning. | Finance, analytics, reporting. | | **Coding** | Code generation and debugging. | Dev tools, automation scripts. | | **Data analysis** | Extracting insights from datasets, tables, or documents. | Research, reporting, content analysis. | | **Instruction following** | Compliance with formats, rules, and prompts. | Policy-driven workflows, SOP tasks. | | **Software engineering (agentic)** | Tool-assisted coding and autonomous dev work. | CI/CD, issue triage, automated PRs. | | **Language** | Context understanding, fluency, general knowledge. | Chatbots, documentation, natural language output. | Different models excel in different areas. For instance, a model might rank highly in reasoning but score average on coding tasks. Matching model strengths to your workflow requirements ensures better outcomes. ### Compare models with LiveBench Use the interactive tool below to generate a LiveBench benchmark comparison. It outputs a pre-filtered table based on your selected criteria. :::note - LiveBench benchmarks are used under a Creative Commons license. - [LiveBench rankings](https://livebench.ai/#/) update continuously. The table above shows the most recent evaluation results. - This tool is provided for illustration purposes only to help you choose the right LLM for your agentic processes. ::: ## Key takeaways Your model choice should align with: 1. **Practical constraints** like hosting, privacy, cost, and accuracy needs. 1. **Performance metrics** such as tool usage, action completion, and instruction adherence. 1. **LiveBench scores** for skills relevant to your workflow. :::important Model selection should always reflect your **use case**. For example, a math-heavy workflow may weight numerical accuracy higher, while a customer support bot might prioritize language skills and instruction-following. ::: A clear framework and benchmarked data helps you choose an LLM or foundation model to power your Camunda agent. ### Explore models with Camunda-provided LLM Camunda-provided LLM gives you access to [multiple models](./camunda-provided-llm.md#supported-models) for experimentation and evaluation, so you can test different options without setting up your own provider. :::important Camunda-provided LLM is only available in Camunda 8 SaaS. It is not available in Camunda 8 Self-Managed. ::: See [Camunda-provided LLM](./camunda-provided-llm.md) for more details. ## Next steps Selecting and benchmarking a model is only the first step. Once your agent is running, validate and continuously evaluate its behavior in practice: - [Evaluate your AI agents](./agentic-orchestration-overview.md#evaluate): Monitor agents in real time with Operate and improve their performance over time with Optimize. - [Test with CPT](./evaluate-agents/test-ai-agents.md): Write integration tests that assert on tool usage, action completion, and agent output. --- ## Connect an external agent ## About An [external agent](/reference/glossary.md#external-agent) runs its [agent loop](/reference/glossary.md#agent-loop) in its own runtime instead of Camunda's engine. Camunda orchestrates when the agent runs as part of the process, but it can only surface what the runtime reports back. To make an external agent visible: - **Mark the agent in the model**: add the `zeebe:agentDefinition` extension element to the BPMN element that hosts the agent, so Camunda creates an [agent definition](/components/agentic-orchestration/agent-definitions-and-instances.md#agent-definitions) when you deploy the process. - **Report the execution**: create an [agent instance](/components/agentic-orchestration/agent-definitions-and-instances.md#agent-instances) and report the agent's state, usage metrics, tools, and conversation history through the [Agent Instance API](/apis-tools/orchestration-cluster-api-rest/specifications/create-agent-instance.api.mdx) while the agent runs. Camunda tracks an agent instance only for an element that carries an agent definition. Without the marker, the agent still runs, but it stays invisible in Operate and Optimize. ## Prerequisites - An [Orchestration Cluster](/components/orchestration-cluster.md) running Camunda 8.10 or later. - An agent runtime that can act as a [job worker](/components/concepts/job-workers.md) and call the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). - An [authenticated API client](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md) with the `READ_PROCESS_INSTANCE` and `UPDATE_PROCESS_INSTANCE` authorizations on the process definition that contains the agent. ## Step 1: Mark the element as an external agent Host the external agent on a [service task](/components/modeler/bpmn/service-tasks/service-tasks.md) whose job type your runtime subscribes to, and add the `zeebe:agentDefinition` extension element with `agentType="external"`. ```xml ``` When you deploy the process, Camunda creates one agent definition for this element. The agent definition is bound to the process definition version, so redeploy the process after you add or change the marker. See [mark an element as an agent](/components/agentic-orchestration/agent-definitions-and-instances.md#mark-an-element-as-an-agent) for the other `agentType` values and the properties an agent definition holds. ### Package the setup as a custom element template Camunda doesn't ship an external agent [element template](/components/modeler/element-templates/about-templates.md), but you can create your own. :::note Create an element template This step is optional. The marker and job type added to the BPMN XML in the previous step are enough for Camunda to track the agent. However, an element template avoids manual XML editing and keeps the configuration consistent when the same external agent is used across multiple processes. ::: The following template applies to a service task, fixes the job type your runtime subscribes to, and exposes the agent's prompt and result variable as configurable fields: ```json { "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", "name": "Research agent", "id": "com.example.agents.research", "description": "An external research agent running on LangGraph.", "version": 1, "engines": { "camunda": "^8.10" }, "appliesTo": ["bpmn:Task"], "elementType": { "value": "bpmn:ServiceTask" }, "properties": [ { "type": "Hidden", "value": "research-agent", "binding": { "type": "zeebe:taskDefinition", "property": "type" } }, { "label": "Prompt", "type": "Text", "feel": "optional", "binding": { "type": "zeebe:input", "name": "prompt" } }, { "label": "Result variable", "type": "String", "binding": { "type": "zeebe:output", "source": "= response" } } ] } ``` See [defining templates](/components/modeler/element-templates/defining-templates.md) for the full set of keys, and [template properties](/components/modeler/element-templates/template-properties.md) for the available bindings. :::note Element templates configure properties through bindings. If a `zeebe:agentDefinition` binding isn't defined for your template, add the marker to the BPMN XML manually as shown in [step 1](#step-1-mark-the-element-as-an-external-agent), in addition to applying the template. ::: ## Step 2: Activate the job with a lease Activate the job for the agent element with `withLease` set to `true`. The activation response returns a `jobLeaseToken` alongside the `jobKey` and `elementInstanceKey` you need for every later call. ```bash curl -L 'http://localhost:8080/v2/jobs/activation' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d '{ "type": "research-agent", "worker": "research-agent-worker", "timeout": 300000, "maxJobsToActivate": 1, "withLease": true }' ``` A lease is required because the conversation history you report is fenced to a single job activation. Items reported under a superseded lease are discarded instead of committed, so a retried activation can't interleave its history with the previous attempt. See [activate jobs](/apis-tools/orchestration-cluster-api-rest/specifications/activate-jobs.api.mdx) for the full activation response, and [job leasing](/components/concepts/job-workers.md#job-leasing) for how the lease is enforced, the permanent lease-only caveat, and fleet guidance. ## Step 3: Create the agent instance Create the agent instance as the first step of handling the job, before your agent makes its first model call. Establish the agent's initial configuration through a `CONFIGURATION` history item included in the same request, and pass the `jobKey` and `jobLeaseToken` from the job activation so Camunda can associate the item with this run. The response returns the `agentInstanceKey` that identifies the agent for every later call. ```bash curl -L 'http://localhost:8080/v2/agent-instances' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d '{ "elementInstanceKey": "2251799813685254", "jobKey": "2251799813685260", "jobLeaseToken": "eyJhY3RpdmF0aW9uIjoxfQ", "history": [ { "historyItemId": "run-7f3a-config", "loopIteration": 1, "role": "CONFIGURATION", "content": [], "model": "gpt-4o", "provider": "openai", "systemPrompt": [ { "contentType": "TEXT", "text": "You are a research assistant. Use the available tools to gather sources before answering." } ], "limits": { "maxModelCalls": 20, "maxToolCalls": 50, "maxTokens": 200000 }, "producedAt": "2026-08-18T09:13:58.000Z" } ] }' ``` | Field | Required | Description | | :------------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `elementInstanceKey` | Yes | The key of the agent element instance, taken from the job activation response. Camunda derives the process instance, element ID, process definition, and tenant from it. | | `jobKey` | Yes | The key of the job activation from [step 2](#step-2-activate-the-job-with-a-lease). Required whenever `history` is provided. | | `jobLeaseToken` | Yes | The lease token from the job activation, the same one used in [step 5](#step-5-report-the-conversation-history). | | `history` | Yes | A batch containing at least one `CONFIGURATION` item that reports `model`, `provider`, and `systemPrompt`; `limits` and `tools` on that item are optional. See [step 5](#step-5-report-the-conversation-history) for the full shape of a history item. | Report the limits your runtime enforces even though Camunda doesn't enforce them for an external agent. Operate shows model calls against the configured limit, which is what makes limit proximity visible when you [detect off-rail agents](/components/agentic-orchestration/evaluate-agents/detect-off-rail-agents.md). Only one agent instance can exist per element instance. If the job is retried, the create call returns `409`. Handle a retry by finding the existing agent instance with [search agent instances](/apis-tools/orchestration-cluster-api-rest/specifications/search-agent-instances.api.mdx), filtering on `elementInstanceKeys`, and continuing to report against the key it returns. ## Step 4: Report state transitions Update the agent instance whenever the agent moves between phases of its loop. Send the update to `PATCH /agent-instances/{agentInstanceKey}`. ```bash curl -L -X PATCH 'http://localhost:8080/v2/agent-instances/4503599627370496' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d '{ "elementInstanceKey": "2251799813685254", "status": "TOOL_CALLING" }' ``` | Field | Required | Description | | :------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `elementInstanceKey` | Yes | The key of the currently active element instance. Camunda validates it against the stored agent instance. | | `status` | No | The agent's current state: `TOOL_DISCOVERY`, `THINKING`, `TOOL_CALLING`, or `IDLE`. See [agent states](/components/agentic-orchestration/agent-states-and-metrics.md#agent-states) for what each state means. | | `history` | No | A batch of conversation history items to append. Usage metrics, tool updates, and the conversation itself all flow through this field. See [step 5](#step-5-report-the-conversation-history). | Camunda sets the `Initializing` and `Completed` states itself, so your runtime can't set them. Report the tools once the agent has resolved them, typically while the agent is in `TOOL_DISCOVERY`, through a `CONFIGURATION` history item rather than a dedicated field: ```bash curl -L -X PATCH 'http://localhost:8080/v2/agent-instances/4503599627370496' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d '{ "elementInstanceKey": "2251799813685254", "jobKey": "2251799813685260", "jobLeaseToken": "eyJhY3RpdmF0aW9uIjoxfQ", "status": "THINKING", "history": [ { "historyItemId": "run-7f3a-tools", "loopIteration": 1, "role": "CONFIGURATION", "content": [], "tools": [ { "name": "search_papers", "description": "Search an academic paper index by topic.", "elementId": null }, { "name": "summarize_source", "description": "Summarize a single source into three bullet points.", "elementId": null } ], "producedAt": "2026-08-18T09:14:01.000Z" } ] }' ``` Set `elementId` only for a tool that a BPMN element in your process handles. For a tool that lives entirely in your external runtime, leave it `null` so Operate doesn't try to link it to the diagram. Omit `tools` from a later `CONFIGURATION` item to leave the stored list unchanged, or send an empty array to clear it. ## Step 5: Report the conversation history The conversation history is the decision trail Operate displays for the agent: the prompts it received, the messages the model returned, the tools it selected, and the results those tools produced. Group the items by `loopIteration` so each pass through the agent loop is legible on its own. Report history items as a batch in the `history` field of the [create agent instance](/apis-tools/orchestration-cluster-api-rest/specifications/create-agent-instance.api.mdx) or [update agent instance](/apis-tools/orchestration-cluster-api-rest/specifications/update-agent-instance.api.mdx) call. Send every item for a loop iteration in a single request. ```bash curl -L -X PATCH 'http://localhost:8080/v2/agent-instances/4503599627370496' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d '{ "elementInstanceKey": "2251799813685254", "jobKey": "2251799813685260", "jobLeaseToken": "eyJhY3RpdmF0aW9uIjoxfQ", "status": "TOOL_CALLING", "history": [ { "historyItemId": "run-7f3a-iter-1-user", "loopIteration": 1, "role": "USER", "content": [ { "contentType": "TEXT", "text": "Summarize the current research on retrieval-augmented generation." } ], "producedAt": "2026-08-18T09:14:02.120Z" }, { "historyItemId": "run-7f3a-iter-1-assistant", "loopIteration": 1, "role": "ASSISTANT", "content": [ { "contentType": "TEXT", "text": "I need recent sources before answering. Searching the paper index." } ], "toolCalls": [ { "toolCallId": "call_01", "toolName": "search_papers", "elementId": null, "arguments": { "topic": "retrieval-augmented generation", "since": 2024 } } ], "metrics": { "inputTokens": 1840, "outputTokens": 260, "durationMs": 2310 }, "producedAt": "2026-08-18T09:14:05.480Z" } ] }' ``` | Field | Required | Description | | :-------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `historyItemId` | Yes | An identifier you assign to the item. Camunda uses it to recognize a resubmitted item as a duplicate rather than rejecting it, so reuse the same ID when a retried activation resends an item. | | `loopIteration` | Yes | The loop iteration the item belongs to, starting at `1`. | | `role` | Yes | `USER`, `ASSISTANT`, `TOOL_RESULT`, or `CONFIGURATION`. | | `content` | Yes | The content blocks of the item, each typed as `TEXT`, `DOCUMENT`, or `OBJECT`. Use `TEXT` for natural language and `OBJECT` for structured data. An empty array is valid for a `CONFIGURATION` item, whose data lives in the fields below instead. | | `toolCalls` | No | For an `ASSISTANT` item, the tool calls the model dispatched. For a `TOOL_RESULT` item, a single entry referencing the originating tool call through its `toolCallId`. Omit for a `USER` item. | | `metrics` | No | The `inputTokens`, `outputTokens`, and `durationMs` of a single model call. Report these on `ASSISTANT` items only. Camunda aggregates them into the agent instance's running totals, so there's no separate counter to increment. | | `model` | No | The LLM model identifier. `CONFIGURATION` items only. | | `provider` | No | The LLM provider. `CONFIGURATION` items only. | | `systemPrompt` | No | The system prompt, as content blocks. `CONFIGURATION` items only. Together with `model` and `provider`, at least one of the `CONFIGURATION` items you send must establish all three. See [step 3](#step-3-create-the-agent-instance). Omit any of the three on a later item to leave it unchanged. | | `limits` | No | The agent's operational limits. `CONFIGURATION` items only; omit to leave the previously reported limits unchanged. | | `tools` | No | The complete list of tools available to the agent, replacing any previously reported list. `CONFIGURATION` items only; omit to leave the list unchanged, or send an empty array to clear it. | | `producedAt` | Yes | The timestamp from your runtime for when the message was produced. | Whenever you send `history`, also send the `jobKey` and `jobLeaseToken` from the job activation. Camunda records each item with a `PENDING` commit status and promotes it to `COMMITTED` when the job completes successfully. If the job fails and a later activation supersedes the lease, the items are marked `DISCARDED` instead. :::note Usage metrics can be lost Camunda can only record usage metrics when your runtime reports them. If your runtime fails after interacting with the LLM but before it reports the usage, for example, if it crashes or loses connectivity, those metrics are lost, even though the LLM provider already processed and billed for the call. For the authoritative token counts and costs, always refer to your LLM provider's own usage reporting rather than relying solely on Camunda's metrics. You can still report those metrics under the new activation’s lease if you reconnect before the job is completed. ::: The response echoes one entry per submitted item, in request order, with the `historyItemKey` Camunda assigned and an `isDuplicate` flag showing whether the item had already been recorded. ## Step 6: Complete the job Complete the job with the same lease token you activated it with. Completing the job commits the conversation history and moves the process instance on to the next element. ```bash curl -L 'http://localhost:8080/v2/jobs/2251799813685260/completion' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d '{ "jobLeaseToken": "eyJhY3RpdmF0aW9uIjoxfQ", "variables": { "response": "Retrieval-augmented generation research since 2024 focuses on..." } }' ``` If your agent can't finish, [fail the job](/apis-tools/orchestration-cluster-api-rest/specifications/fail-job.api.mdx) or [throw a BPMN error](/apis-tools/orchestration-cluster-api-rest/specifications/throw-job-error.api.mdx) so the process can react to the failure. Both are governed the same way as any other job, so a failing external agent raises an [incident](/components/concepts/incidents.md) you can act on in Operate. ## Step 7: Verify the agent in Operate Start a process instance and [open it in Operate](/components/operate/userguide/basic-operate-navigation.md). Select the agent element on the diagram to see the agent instance data you reported: its state, usage metrics, model, system prompt, tools, and conversation history grouped by loop iteration. See [monitor your AI agents](/components/agentic-orchestration/evaluate-agents/monitor-ai-agents.md) to learn how to inspect and debug AI agents in Operate. --- ## Design and architecture ## Plan agentic orchestration solutions Follow these principles when planning your agentic orchestration solution: - **Problem first**: First, identify any problem you might have in a process, and only then determine whether an AI agent could help solve the problem. Do not use an AI agent where it is not really necessary, or just for the sake of it. - **Architect for composability**. Avoid becoming too dependant on a specific LLM model, for example by doing too much fine tuning. This allows you to more easily integrate newer LLM providers and models in the future that better suit your needs. - **Observability and governance**: Use [Operate](/components/operate/operate-introduction.md) and [Optimize](/components/optimize/what-is-optimize.md) for visibility into your agentic orchestration processes. ### Blend deterministic and dynamic orchestration Blending both deterministic and dynamic (AI-driven) process orchestration into your end-to-end processes allows you to take advantage of non-deterministic process orchestration without sacrificing predictability, customer experience, and compliance. For example, you could use an AI agent to enhance a Know Your Customer (KYC) process, where the AI agent: - Provides dynamic guidance and problem-solving to the person throughout the process. - Monitors for policy changes and dynamically changes the process execution in response. - Automatically adjusts risk level and takes action such as restricting account activity or dynamically adjusting spend limits. ### When to use deterministic or non-deterministic orchestration Agentic orchestration involves blending both deterministic and dynamic (AI-driven) process orchestration into your end-to-end processes. It is important to understand when to use each approach: |   | Deterministic | Dynamic | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Suitable for** | Clear, predictable paths.Repeatable, rules-based decisions.Fast, efficient execution.Regulation dictates execution sequence. | Flexible, context-aware choices.AI-driven task planning.Unstructured cases: classification, triage, or investigation.Adaptable to data and conditions. | | **Enabled by** | Advanced workflow patterns.Business/IT alignment on process and decision logic. | Dynamic task scheduling and tracking.Collaboration between AI and Human. | :::info To learn more about determining when and where to use AI agents within your automation strategy, download the [why agentic process orchestration belongs in your automation](https://page.camunda.com/wp-why-agentic-process-orchestration-belongs-in-your-automation-strategy) strategy guide. ::: ## Design agent orchestration workflows Follow these principles when designing your agentic orchestration solution: - **Guardrail sandwich**: Apply guardrails in your process when using agents. For example, you could have one agent performing the task execution, with another agent following up to check the chain of thought and make sure every execution is compliant. If the execution is not compliant, route to a human for additional validation. - **Human-in-the-Loop escalation**: Provide an agent with an escalation path to a human - confidence levels are useful, but it is good to always provide deterministic outbreaks for agents. - **Prompt versioning**: Version every prompt, so you can revert to using a previous prompt when required. For how the agent loop works and how responsibilities are split between the LLM and Camunda, see [AI agents](/components/agentic-orchestration/ai-agents.md#how-the-agent-loop-works). ### Define your agent tools In the AI agent model, each BPMN element inside an ad-hoc sub-process is a tool exposed to the LLM. The element's **ID** is used as the tool name, and its **Documentation** field is used as the tool description (falling back to the element's **Name** if **Documentation** is empty). The LLM uses this tool definition to decide which tool to call, in what order, and with which parameters. Clear, behavior-oriented tool names and descriptions directly improve agent reliability. Vague or missing documentation increases the risk of incorrect tool selection, repeated calls, and hallucinated behavior. For a how-to guide on adding tools, see [add tools to an AI agent](./add-tool-to-ai-agent.md). ### Mix agents with workflow patterns 1 **Process flow within tools**: Full BPMN control inside ad-hoc sub-processes for ultimate flexibility. 2 **Agents pivot instantly with external messages and timers**: During execution, agents can be influenced by events like external messages or timers, enabling on-the-fly adjustments. 3 **Event-driven agent reconfiguration**: Sub-workflows handle new data, guiding the next AI steps. 4 **Agents orchestrate sub-workflow**: A tool doesn't need to be a single tool - it can be a whole subprocess. 5 **Multi-agent orchestration**: Agents orchestrate other agents for streamlined, scalable solutions. This agent-to-agent pattern runs inside Camunda's [agentic orchestration](/components/agentic-orchestration/agentic-orchestration-overview.md), as one of the tools available to an agent. It is not the same as agentic orchestration itself, which is Camunda's overall model for orchestrating agents, people, and systems. The [A2A Client connector](/components/early-access/alpha/a2a-client/a2a-client.md) implements this pattern: an agent can call a remote agent using the Agent-to-Agent (A2A) protocol. #### Example: an orchestrator agent that delegates to specialist agents The following example applies this workflow pattern to a bank support process, where an orchestrator agent receives a customer request and delegates parts of it to specialist agents: - A **loan support agent** that calculates loan payments. - An **account support agent** that validates IBANs. - A **card support agent** that looks up card issuers. Each specialist is modeled as its own separate BPMN process: a single AI Agent Sub-process wrapping one real tool call. The orchestrator's [ad-hoc sub-process](/reference/glossary.md#ad-hoc-sub-process) exposes each specialist as a [call activity](/components/modeler/bpmn/call-activities/call-activities.md) tool, so the LLM can delegate to one or more specialists based on the customer's request. Because delegation is modeled with call activities, every specialist that runs shows up as its own, separately inspectable [child process instance](/reference/glossary.md#child-process-instance) in Operate. After the specialists return their results, a script task combines their summaries into one readable block, and a [gateway](/reference/glossary.md#gateway) routes automatically resolved cases to a customer notification, or escalates unresolved cases to a [human task](/reference/glossary.md#human-task) for review. ![Bank support orchestrator BPMN process, showing an orchestrator agent ad-hoc sub-process delegating to loan, account, and card specialist agents through call activities](img/orchestrator-agent.png) Splitting agents this way keeps each agent's tool set small and scoped to one domain, and lets specialist agents be reused across multiple processes. You can also design specialist agents that run outside Camunda and integrate them using the [A2A Client connector](/components/early-access/alpha/a2a-client/a2a-client.md). See the [bank support orchestrator agent example](https://github.com/camunda/camunda-8-tutorials/tree/main/examples/orchestrator-agent) for the full BPMN models and setup instructions. ### Call processes as agent tools When an AI agent needs to invoke another BPMN process as a tool, you have two options: - Use a call activity inside the ad-hoc sub-process. - Add an MCP client gateway tool connected to the [Processes MCP Server](/apis-tools/processes-mcp/processes-mcp-overview.md). The right choice depends on whether your target process runs on the same or a different Orchestration Cluster: | Scenario | Recommended approach | | :--------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Target process is on the **same** Orchestration Cluster | Use a [call activity](/components/modeler/bpmn/call-activities/call-activities.md) inside the ad-hoc sub-process. | | Target process is on a **different** Orchestration Cluster | Use the [MCP Remote Client connector](../connectors/out-of-the-box-connectors/agentic-ai-mcp-remote-client-connector.md) connected to the other cluster's Processes MCP Server. | These two approaches differ in runtime behavior, the result the agent receives, instance visibility, and the audit trail: |   | Call activity | MCP client | | :--------------------- | :----------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | **Execution** | The tool call waits for the called process to complete and returns its output to the agent. | The called process starts immediately and the tool returns the process instance key. The agent does not receive the process output. | | **Instance hierarchy** | The called process instance is a child of the ad-hoc sub-process element, visible in the instance tree in Operate. | The called process runs independently with no structural link to the calling agent process. | | **Audit logs** | The audit trail reflects the full call hierarchy. | The audit trail shows a process triggered by an external message, with no structural link to the calling agent. | :::note Although it is technically possible to use an MCP client to connect to the Processes MCP Server on the same cluster as the calling agent, this produces a detached hierarchy and a less coherent audit trail. Use call activities for same-cluster process invocations. ::: --- ## Analyze your AI agents with Optimize ## About In this guide, you will: - Understand what data Optimize can analyze for AI agents. - Create reports and heatmaps, including token and tool usage. - Build a dashboard to track AI agent behavior over time. After completing this guide, you will be able to analyze AI agent executions in Optimize and build dashboards that track usage and performance trends. :::important Agentic control plane in Optimize The [agentic control plane](/components/optimize/userguide/agentic-control-plane.md) already provides standard metrics for AI-agent-powered processes without additional setup. Use this guide to learn how to create custom metrics, thresholds, breakdowns, or reports grouped by process variables. ::: ## Prerequisites - You have access to [Optimize](/components/optimize/what-is-optimize.md). - You have deployed and run the [AI Agent Chat Quick Start](https://marketplace.camunda.com/en-US/apps/587865) model blueprint. This is needed for Optimize to fetch execution data to analyze. Consider running it using different prompts to trigger various AI agent tools. :::important This guide is a follow-up to [build your first AI agent](/guides/getting-started-agentic-orchestration.md), in which you use the same example AI agent process. We recommend completing that guide first. However, you can also apply this guide to other AI agent process implementations. ::: ## Step 1: Make your data available Optimize's report builder can only aggregate over process variables, so any custom report you build yourself needs its data available as one. To make data available to Optimize, make sure it's scoped at the process level. If it's scoped to a lower level, for example, within a connector or tool-execution scope, extract it into process variables. :::important Optimize can only use variable data at the **process level**. ::: ### Example: Collect token usage In the AI Agent Chat Quick Start example, token usage data is not available at the process level, but in a nested scope. How you extract it depends on your AI agent implementation. The important aspect is that the target variables exist at the **process level** when the instance finishes. :::note Getting the token usage from the agent context only works with the AI Agent Sub-process when the **Include agent context** option in the **Response** section is enabled. ::: To surface this data, you can add a script task after the AI agent execution that copies the values into process variables as follows: 1. Add a [script task](/components/modeler/bpmn/script-tasks/script-tasks.md). 1. Configure its **Properties**: - Set **Name** to `Gather metrics` under the **General** section. - Select **FEEL expression** as **Implementation**. - Under **Script**: - Set **Result variable** to `tokenUsage`. - Set **FEEL expression** to `agent.context.metrics.tokenUsage`. - Under **Output mapping**, add two process variables: - `inputTokenUsage` with **Variable assignment value**: `agent.context.metrics.tokenUsage.inputTokenCount`. - `outputTokenUsage` with **Variable assignment value**: `agent.context.metrics.tokenUsage.outputTokenCount`. You should see something similar to the following: ## Step 2: Examine data in Optimize You can use Optimize reports and dashboards to examine data collected during process execution and identify areas for improvement in your AI agent processes. 1. [Open Optimize](/components/optimize/improve-processes-with-optimize.md#open-optimize). 1. Go to the **Dashboards** tab. 1. Select your AI agent process, **AI Agent Chat With Tools**, in the **Process dashboards and KPIs** section. 1. Verify that Optimize shows data for your executed process instances in the **Business Operations** section, including your AI agent process model diagram and other statistics below. 1. Explore the other metrics shown below in the **Business Reporting** and **Process Improvement** sections. See [getting started](/components/optimize/improve-processes-with-optimize.md) with Optimize for more details on using Optimize for business intelligence. ## Step 3: Create reports for token usage You can create reports for token usage across process instances and over time. 1. Go to the **Collections** tab. 1. Select **Report** from the **Create new** dropdown. 1. Select **AI Agent Chat With Tools** from the **Select one or more processes** dropdown. You can fetch data for all process model versions or customize it. 1. Choose a blank template. 1. Enable **Update preview automatically** to make it easier to see the report results as you configure it. 1. In the **Report setup** section, select **Variable** in the **View** option, and then select **tokenUsage**. 1. Click the pencil icon and select an aggregation that matches your goal. For example: - **Sum** to track total tokens across instances. - **Average** to track typical usage per process instance. 1. Save the report with a descriptive name. For example, **Token usage**. :::note You can create similar reports, targeting other goals and process variables, such as `inputTokenUsage` or `outputTokenUsage`. ::: ### Example: Set a target threshold If you have a token budget, you can set a target in the report. 1. Complete steps 1–6 in [Step 3: Create reports for token usage](#step-3-create-reports-for-token-usage). 1. In the **Visualization** settings, click the gear icon and enable **Set target** to configure a target value. For example, a maximum token usage threshold. 1. Set the target threshold to match your budget. For example, select the **below** option and set it to 10,000 tokens. 1. Save the report with a descriptive name. For example, **Token usage with threshold**. ## Step 4: Create reports for tool usage You can create reports for tool usage across process instances and over time. ### Example: Create a heatmap Use a heatmap to understand how long your AI agent spends in each task. 1. Go to the **Collections** tab. 1. Select **Report** from the **Create new** dropdown. 1. Select **Flow node** as the **View**. 1. Select **Duration** as the **Measure**. 1. (Optional) Filter the report by selecting **Flow node selection** in the **Filter flow nodes** dropdown. For example, select only tool tasks within the AI Agent connector. 1. In the **Visualization** settings, select **Heatmap**. Click the gear icon and enable the tooltip to show absolute values. 1. Save the report with a descriptive name. For example, **Tool usage heatmap**. You can see from the heatmap that the **Search recipe**, **Jokes API**, and **Get list of Tech Stuff** tools are the only ones that were called across all process executions, and that your AI agent spent the most time in **Get list of Tech Stuff**. ### Example: Create a report for tool call counts Create a bar chart to see how many times each tool is called. 1. Go to the **Collections** tab. 1. Select **Report** from the **Create new** dropdown. 1. Select **Flow node** as **View**. 1. Select **Count** as the **Measure**. 1. (Optional) Filter the report by selecting **Flow node selection** in the **Filter flow nodes** dropdown. For example, select only tool tasks within the AI Agent connector. 1. In the **Visualization** settings, select **Bar chart** or **Pie chart**. Then click the gear icon and enable both tooltips to show absolute and relative values. 1. Save the report with a descriptive name. For example, **Tool usage**. You can see from the pie chart that the AI agent called the Jokes API tool most often across all process executions. ### Example: Track trends over time Use a timeline report to analyze trends over time. For example, you can see how many times a tool is called per day over a one-week period. 1. Go to the **Collections** tab. 1. Select **Report** from the **Create new** dropdown. 1. Select **Flow node** as **View**. 1. Select **Count** as the **Measure**. 1. In **Group by**, select **Start date**. Then choose your preferred interval, for example, **Week**. 1. (Optional) Filter the report by selecting **Flow node selection** in the **Filter flow nodes** dropdown. For example, select only tool tasks within the AI Agent connector. 1. In the **Visualization** settings, select **Bar chart** or **Line chart**. Then click the gear icon and enable both tooltips to show absolute and relative values. 1. Save the report with a descriptive name. For example, **Tool usage over time**. ## Step 5: Build a dashboard You can create a dashboard, which is a collection of reports that you can view together, for your AI agent process. 1. Go to the **Collections** tab. 1. Select **Dashboard** from the **Create new** dropdown. 1. Click the plus icon to add tiles for the reports you created. 1. In the **Optimize report** section, add reports as needed. 1. Arrange the tiles to customize your dashboard layout. 1. Save the dashboard with a descriptive name. For example, **AI Agent Chat Quick Start dashboard**. ## Next steps Now that you know how to analyze your AI agents, you can: - [Monitor your AI agents](./monitor-ai-agents.md) with Operate. - Learn more about [Camunda agentic orchestration](/components/agentic-orchestration/agentic-orchestration-overview.md) and the [AI Agent connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md). --- ## Detect off-rail agents ## About An agent can go off-rail without failing outright, for example, by getting stuck repeating a tool call or reasoning in circles. Left unnoticed, it keeps consuming tokens and model calls until it hits its configured limit. In this guide, you will learn how to read an agent instance's [state and usage metrics](/components/agentic-orchestration/agent-states-and-metrics.md) to catch this early, and how to respond to limit proximity warnings. ## Identify a stuck or looping agent No single health indicator proves that an agent is stuck. Look at the combination of state, usage metrics, and duration for the agent instance: - **State doesn't change over an extended period.** An agent instance stuck in Thinking far longer than a typical model call, or in Tool calling far longer than the tool normally takes to respond, is not progressing through its loop. - **Tool call count climbs without resolution.** The agent keeps calling the same tool, or a small set of tools, with similar or identical inputs across multiple loops, without the conversation moving toward a final response. - **Token consumption grows steadily over loop iterations.** Since each loop iteration appends the previous reasoning and tool results to the conversation, a steadily rising token count with no final response is a sign the agent is accumulating context without converging. - **Execution duration is disproportionate to the task.** The cumulative time the agent instance has spent in `Thinking` and `Tool calling`, not counting time spent `Idle` waiting on external input, is much longer than comparable runs of the same agent, which suggests the agent is caught in a longer-than-expected cycle of loop iterations rather than a single stuck call. :::note An agent that calls the same tool many times can still be legitimately working through a multi-step task. Treat these indicators together, not individually: an agent instance with a stalled state, a climbing tool call count, and a duration that keeps growing without settling is the strongest signal that it's going off-rail. ::: ## Respond to limit proximity warnings Camunda tracks model calls against an agent’s configured limit, so you can see how close an agent instance is to reaching it. A proximity warning means the agent instance is nearing its limit and could reach it during a subsequent loop iteration. Apply the following best practices when you see a proximity warning: - **Investigate before the hard limit hits.** Use a proximity warning as a prompt to check the agent instance's other health indicators. An agent nearing its limit while making steady progress is different from one nearing its limit while stuck in a loop. - **Handle the limit explicitly instead of letting it fail the process instance.** Catch the limit-reached error with an error boundary event and an [error expression](/components/connectors/use-connectors/index.md#error-expression), and route it to a human reviewer instead of failing the process instance. See the [guardrail sandwich and human-in-the-loop escalation](/components/agentic-orchestration/design-architecture.md#design-agent-orchestration-workflows) design principles. - **Size the limit to the task, not the default.** A **Maximum model calls** value that's frequently near-exhausted by agents completing their task normally is probably too low for that task's typical loop count. Raise it deliberately rather than repeatedly dismissing the warning. - **Don't treat a higher limit as the fix for a stuck agent.** If an agent instance reaches its limit while stuck, for example, repeating the same tool call, raising the limit only lets it consume more tokens and tool calls before failing. Address the underlying cause, such as the tool's input, output, or description, instead. With the AI Agent connector, you can configure this limit using the **Maximum model calls** field. For more details, see the **Limits** section in the [AI Agent Sub-process](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess.md#limits) or [AI Agent Task](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-task.md#limits) connector documentation, depending on your Camunda AI agent implementation. Reaching this limit throws a `MAXIMUM_NUMBER_OF_MODEL_CALLS_REACHED` [error](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess.md#error-handling), which creates an incident unless it's caught with an error boundary event. --- ## Monitor your AI agents with Operate ## About In this guide, you will: - Inspect an AI agent's real-time state and usage metrics from its process instance in Operate. - Review the agent's decision trail: the conversation history grouped by loop iteration, including the tools it selected and the results it received. - Understand how the agent's conversation memory is stored. :::note Operate surfaces the agent's state, metrics, and conversation history directly, so you rarely need to inspect raw process variables. Some runtime artifacts, such as document storage contents, may still require additional configuration to view. See [agent context and memory](/components/agentic-orchestration/agent-definitions-and-instances.md#agent-context-and-memory) for how the underlying data is stored. ::: After completing this guide, you will be able to monitor, debug, and troubleshoot AI agent executions in Operate, including agents built with external frameworks such as LangGraph or CrewAI. See [connect an external agent](/components/agentic-orchestration/connect-external-agent.md) for how those agents report the same data. ## Prerequisites - You have access to [Operate](/components/operate/operate-introduction.md) on Camunda 8.10 or later. - You have the [AI Agent Chat Quick Start](https://marketplace.camunda.com/en-US/apps/587865) model blueprint installed in a [Camunda Hub project](/components/hub/workspace/modeler/modeling/camunda-marketplace.md#browse-marketplace-blueprints) or open in Desktop Modeler. :::important This guide is a follow-up to [build your first AI agent](/guides/getting-started-agentic-orchestration.md), where you use the same example AI agent process. We recommend completing that guide first. However, you can also apply this guide to other AI agent process implementations. ::: ## Step 1: Run your AI agent process Run your process instance using a prompt to trigger the AI Agent connector. For example: 1. Enter "Tell me a joke" in the **How can I help you today?** field. 1. Click **Start instance**. ## Step 2: Open the process instance in Operate 1. [Open Operate](/components/operate/userguide/basic-operate-navigation.md#open-operate). 2. Locate the process instance created by your prompt. See [view a deployed process](/components/operate/userguide/basic-operate-navigation.md#view-a-deployed-process) for more details. 3. Open your process instance view by clicking on its process instance key. At this point, you should see the process progressing through your model: Operate highlights the agent element's current [state](/components/agentic-orchestration/agent-states-and-metrics.md#agent-states). For example, `Thinking` while the agent reasons, or `Tool calling` while it calls the **Jokes API** tool. A simple prompt like this one moves through its loop quickly, so the agent instance may already show `Idle` or `Completed` by the time you look. ## Step 3: Inspect the agent's state and usage metrics Select the agent element on the diagram. Operate shows the [data available](/components/agentic-orchestration/agent-definitions-and-instances.md#data-available-in-operate) for its agent instance, including: - Its agent instance key, displayed above the status. - Its current state, model, and system prompt. - The tools resolved for it. - Its usage metrics: token consumption, tool call count, and model call count against the configured limit. :::note If multiple agent instances are active at the same element, use the dropdown next to the agent instance key to switch between them. Alternatively, select the relevant element instance in **Instance History**, as each element instance has only one agent instance. ::: For guidance on reading these signals to catch a stuck or looping agent, see [detect off-rail agents](./detect-off-rail-agents.md). ## Step 4: Review the conversation history The conversation history is the agent's decision trail, grouped by [loop iteration](/components/agentic-orchestration/agent-definitions-and-instances.md#conversation-history-and-loop-iterations). Operate labels each group simply as `iteration`, for example `1. iteration`. By default, entries are sorted by **Latest first**. You can select **Oldest first** to read the history chronologically: - **Latest first**: Quickly understand the current situation, typically when resolving a problem. - **Oldest first**: Trace how the agent reached its current state, typically when building an agent for the first time. For this example, the first iteration shows: - The user prompt, "Tell me a joke." - The assistant message where the agent selects the **Jokes API** tool, along with its reasoning. :::tip Hover over a message's token count or duration badge to see a breakdown of its usage metrics. ::: Operate shows **Thinking** and the formatted text only when the model returns reasoning content. To display readable model reasoning where supported, [migrate to the 8.10 AI Agent element template](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-upgrade.md). ### Dive deeper into messages and tool calls Every entry in the conversation history can be expanded for a closer look. Select the expand icon on a user or assistant message, or on a tool call, to open a larger view: A user or assistant message may contain formatted text, so expanding it opens a **Preview** of its rendered Markdown by default. Switch to **Source** to view the raw Markdown instead. Expanding a tool call shows the tool's description, along with the full input and output exchanged with it: :::note Use the copy icon in any of these expanded views to copy its content. ::: ## Step 5: Understand how agent memory is stored In [Camunda Hub](/components/hub/workspace/modeler/index.md) or Desktop Modeler, within the AI Agent sub-process, you can define how the conversation memory is stored using the **Memory storage type** field. By default, agent memory uses the **In Process** type, which stores it as part of the agent context, the same underlying data the conversation history in [step 4](#step-4-review-the-conversation-history) is built from. Other available options include **Camunda Document Storage**, **AWS AgentCore Memory**, and a custom implementation. See [memory](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess.md#memory) for more details. :::note Advanced: inspect the raw agent context For a Camunda AI agent, this data is stored in the `agentContext` process variable. Open the element's **Variables** tab to inspect it directly, for example to check a runtime artifact not surfaced in the conversation history. See [agent context and memory](/components/agentic-orchestration/agent-definitions-and-instances.md#agent-context-and-memory) for how it's structured. ::: ## Step 6: Review the results Go back to Operate. In the **User Feedback** element, you will see the execution count in green. This means the process instance execution is stopped there and waiting for action. In this case, the required action is to provide feedback on the agent results. To do so: 1. [Open Tasklist](/components/tasklist/userguide/using-tasklist.md#open-tasklist). 2. Locate the user feedback task and assign it to yourself by clicking **Assign to me**. 3. Analyze the result. You will see a joke, as requested in the prompt. 4. You can follow up with more prompts to continue testing your AI agent. 5. Select the **Are you satisfied with the result?** checkbox when you want to finish the process, then click **Complete task**. 6. Go back to Operate. You will see the process instance is now completed, and the end event has been triggered. ## Next steps Now that you know how to monitor your AI agents, you can: - [Analyze your AI agents](./analyze-ai-agents.md) with Optimize. - [Test your AI agents](./test-ai-agents.md) with Camunda Process Test, including handling non-deterministic flows and verifying AI-generated output. - Learn more about [Camunda agentic orchestration](/components/agentic-orchestration/agentic-orchestration-overview.md) and the [AI Agent connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md). --- ## Test your AI agents with CPT ## About AI agent processes are non-deterministic: the [AI Agent connector](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md) inside an [ad-hoc sub-process](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md) decides at runtime which tools to invoke and in what order, and its free-text output varies across runs. In this guide, you will build integration tests that keep the AI agent and LLM interaction real while mocking external tool executions, using the following CPT features: - [Conditional behavior](/apis-tools/testing/utilities.md#conditional-behavior): Reacts to whichever tasks the agent activates, instead of blocking on a single hard-coded execution order. This addresses non-deterministic control flow. - [Judge](/apis-tools/testing/assertions.md#hasvariablesatisfiesjudge) and [semantic similarity assertions](/apis-tools/testing/assertions.md#hasvariablesimilarto): Verify AI-generated output. After completing this guide, you will be able to test your AI agents using CPT. ## Prerequisites - You use Camunda 8.9+. - You use the Camunda Process Test Spring Boot Starter. - You have [Camunda Process Test set up](/apis-tools/testing/getting-started.md). - You have downloaded the [AI Agent Chat With Tools](https://marketplace.camunda.com/en-US/apps/587865) process to your local machine. :::important This guide is a follow-up to [build your first AI agent](/guides/getting-started-agentic-orchestration.md), in which you use the same example AI agent process. We recommend completing that guide first. However, you can also apply this guide to other AI agent process implementations. ::: ## Step 1: Prepare the example AI agent blueprint Place the BPMN file and any associated forms for your AI agent process in the `src/main/resources` directory of your Spring Boot project. Create it if it does not already exist. You can organize files into subdirectories such as `bpmn/` and `forms/`. ## Step 2: Configure the LLM provider and connectors Judge assertions send a process variable and a natural language expectation to a configured LLM, which scores how well they match. The assertion passes if the score meets a configurable threshold. This avoids brittle string-matching on free-text AI output. For this testing style, first configure both the connector runtime and the judge LLM. The goal is to keep the AI agent and LLM interaction real while disabling outbound connector execution for the tool calls you want to control in the test. ### Configure the connector runtime Add the following connector runtime configuration to your test configuration, for example in `src/test/resources/application.yaml` or as inline properties on `@SpringBootTest`. For the full property reference, see the [CPT configuration docs](/apis-tools/testing/configuration.md). ```yaml camunda: process-test: assertion: timeout: PT1M connectors-enabled: true connectors-env-vars: CAMUNDA_CONNECTOR_POLLING_ENABLED: "false" CONNECTOR_OUTBOUND_DISCOVERY_DISABLED: "true" CONNECTOR_OUTBOUND_DISABLED: "io.camunda:http-json:1" ``` With this setup: - The assertion timeout is increased to one minute. AI agent processes involve LLM interactions and typically take longer than standard BPMN processes. - CPT starts the connector runtime needed by the AI agent process. - Outbound connector executions, such as the HTTP JSON connector, are disabled so tool behavior can be controlled by the test with conditional behavior. If your AI agent tools use different outbound connectors, adjust `CONNECTOR_OUTBOUND_DISABLED` accordingly. ### Configure the LLM provider Configure the LLM provider for the judge. The judge does not need the same provider or model as your AI agent. A lighter model often works well since the judge context is much smaller. ```yaml camunda: process-test: connectors-secrets: AWS_BEDROCK_ACCESS_KEY: ${AWS_LLM_BEDROCK_ACCESS_KEY} AWS_BEDROCK_SECRET_KEY: ${AWS_LLM_BEDROCK_SECRET_KEY} judge: chat-model: provider: "amazon-bedrock" model: "eu.anthropic.claude-haiku-4-5-20251001-v1:0" region: "eu-central-1" credentials: access-key: ${AWS_LLM_BEDROCK_ACCESS_KEY} secret-key: ${AWS_LLM_BEDROCK_SECRET_KEY} ``` :::note Bedrock IAM requirements The AWS principal must have `bedrock:InvokeModel` permission on each model ARN you configure, and each model must be [enabled for access](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access-modify.html) in the configured region. If you also configure an embedding model through Bedrock for [semantic similarity assertions](#verify-with-semantic-similarity), it requires a separate IAM grant. ::: Use this provider for [Ollama](https://ollama.com/). ```yaml camunda: process-test: judge: chat-model: provider: "openai-compatible" model: "gpt-oss:20b" base-url: "http://localhost:11434/v1" ``` :::tip Manage secrets safely Avoid committing credentials to your test configuration files. CPT properties support [Spring's external configuration](https://docs.spring.io/spring-boot/reference/features/external-config.html), so you can inject secrets through environment variables, CI/CD secret stores, or other techniques. See the [CPT configuration reference](/apis-tools/testing/configuration.md) for details. ::: The AI agent can still interact with the configured LLM provider, while the test controls the tool executions. For the full property reference, see [judge configuration](/apis-tools/testing/configuration.md#judge-configuration). ## Step 3: Set up the test class Add the `@Deployment` annotation to your Spring Boot application class to declare which resources CPT should deploy: ```java @SpringBootApplication @Deployment(resources = {"classpath*:/bpmn/**/*.bpmn", "classpath*:/forms/**/*.form"}) public class MyApplication {} ``` Then create a test class annotated with `@SpringBootTest` and `@CamundaSpringProcessTest`, and inject the `CamundaClient` and `CamundaProcessTestContext`: ```java @SpringBootTest(classes = MyApplication.class) @CamundaSpringProcessTest class AiAgentProcessTest { @Autowired private CamundaClient client; @Autowired private CamundaProcessTestContext processTestContext; } ``` For the full setup including dependencies and project structure, see [Getting started with Camunda Process Test](/apis-tools/testing/getting-started.md). ## Step 4: Handle non-deterministic flow paths In this guide, the test uses the prompt `"Give me a joke! Greet Ervin as an introduction"`. In response, the agent: - Calls `List Users` and `Jokes API` in any order. - Collects feedback through the `User Feedback` user task. With [conditional behavior](/apis-tools/testing/utilities.md#conditional-behavior), you can register background reactions that monitor the process state and execute actions as conditions are met, without blocking the test thread. Register behaviors before starting the process; they then react independently as the process progresses. Each behavior watches for a specific element to become active and then completes it with test data. If the agent never activates that element, the behavior simply never triggers and the test does not stall. ### Complete tool tasks Register a behavior for each tool task the agent might invoke. In this integration test, these behaviors stand in for external tool executions such as REST connector calls. First, define records for the tool call results: ```java record User(int id, String name, String username) {} ``` Register a behavior that completes the `List Users` tool with a mock user list when the agent invokes it: ```java processTestContext .when( () -> assertThatProcessInstance(ProcessInstanceSelectors.byProcessId("ai-agent-chat-with-tools")) .hasActiveElements("ListUsers")) .as("complete ListUsers") .then( () -> processTestContext.completeJob( JobSelectors.byElementId("ListUsers"), Map.of("toolCallResult", List.of( new User(1, "Leanne Graham", "Bret"), new User(2, "Ervin Howell", "Antonette"))))); ``` Register a behavior that completes the `Jokes API` tool. This behavior uses chained `.then()` calls to return different jokes on repeated invocations: ```java String firstJoke = "Why did the workflow cross the road? To get to the happy path."; String secondJoke = "Why did the BPMN diagram apply for a job? It had excellent flow experience."; processTestContext .when( () -> assertThatProcessInstance(ProcessInstanceSelectors.byProcessId("ai-agent-chat-with-tools")) .hasActiveElements("Jokes_API")) .as("complete jokes tool") .then( () -> processTestContext.completeJob( byElementId("Jokes_API"), Map.of("toolCallResult", firstJoke))) .then( () -> processTestContext.completeJob( byElementId("Jokes_API"), Map.of("toolCallResult", secondJoke))); ``` ### Complete user tasks The `User Feedback` user task is outside the agent and prompts the user to approve the agent output or request a different one. Use chained `.then()` calls when a behavior should produce different results on repeated invocations: the first action is consumed on the first invocation, and the last action repeats for all subsequent invocations. For example, register a behavior that first rejects the joke with a follow-up request, then approves the result on the next invocation: ```java processTestContext .when( () -> assertThatProcessInstance(ProcessInstanceSelectors.byProcessId("ai-agent-chat-with-tools")) .hasActiveElements("User_Feedback")) .as("feedback loop") .then( () -> processTestContext.completeUserTask( "User_Feedback", Map.of( "userSatisfied", false, "followUpInput", "This joke is bad, send Ervin a better joke"))) .then( () -> processTestContext.completeUserTask( "User_Feedback", Map.of("userSatisfied", true))); ``` :::important Each behavior's action should resolve the process state that the condition checks for. For example, if the condition checks for an active user task, the action should complete that task. Otherwise the behavior may execute repeatedly. ::: For the full conditional behavior API, see [Utilities](/apis-tools/testing/utilities.md#conditional-behavior). ## Step 5: Verify the agent output You can use two types of assertions to verify the agent output: - **[Judge assertions](/apis-tools/testing/assertions.md#hasvariablesatisfiesjudge)** verify AI-generated output or tool execution results with a judge LLM that scores whether a value satisfies a natural-language expectation. - **[Semantic similarity assertions](/apis-tools/testing/assertions.md#hasvariablesimilarto)** verify AI-generated output against a reference text using embeddings and cosine similarity. They are a deterministic, lower-cost alternative to judge assertions. ### When to use judge vs. similarity | Assertion | Best for | Cost | | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | [Judge](#verify-with-judge-assertions) | Open-ended natural-language criteria, multi-part expectations, structured data, Camunda document content (with [document attachment](/apis-tools/testing/configuration.md#document-attachment) enabled), anything that needs reasoning. | One extra LLM call per assertion. Score and explanation depend on the configured judge model. | | [Semantic similarity](#verify-with-semantic-similarity) | Checks where a concrete reference text is close to a variable's actual content. Deterministic and fast. | One embedding call per value. No reasoning step, so it cannot evaluate criteria that aren't expressed in the wording. | :::tip - Use judge assertions when it feels natural to express the expectation in natural language. - Use similarity assertions when the expected answer is itself a sample string. ::: #### Limitations - Judge assertions support Camunda document evaluation when [document attachment](/apis-tools/testing/configuration.md#document-attachment) is enabled. When enabled, document references in the variable are resolved, and their content is passed to the judge as structured content blocks. - Semantic similarity assertions operate on the **serialized JSON string** of a process variable and cannot evaluate non-text content, such as [Camunda documents](/components/document-handling/getting-started.md) or other embedded binaries. In those cases, only metadata or encoded strings reach the assertion. - Semantic similarity assertions compare the serialized variable against the expected string using a vector space. Highly structured variables, such as JSON objects with many fields, may score lower than expected even when the semantic meaning matches. ### Verify with judge assertions Use a judge assertion to verify the agent output satisfies a natural language expectation. The following example registers the conditional behaviors from [Step 4](#step-4-handle-non-deterministic-flow-paths), starts the process with the prompt `"Give me a joke! Greet Ervin as an introduction"`, and then asserts that the agent completed the scenario correctly: ```java @Test void shouldSendErvinAJoke() { ProcessInstanceEvent processInstance = client.newCreateInstanceCommand() .bpmnProcessId("ai-agent-chat-with-tools") .latestVersion() .variables(Map.of("inputText", "Give me a joke! Greet Ervin as an introduction")) .send() .join(); assertThat(processInstance).isCompleted(); assertThat(processInstance) .hasVariableSatisfiesJudge( "agent", """ The agent correctly identified Ervin by calling ListUsers. The agent fetched a joke using Jokes_API. After the user rejected the first joke and asked for another one, the agent offered a second, different joke. """); } ``` The expectation is a plain-text description of what the agent should have done. The judge does not compare strings literally. It evaluates whether the actual variable content satisfies the expectation semantically, so different phrasing or formatting in the agent's output does not cause false failures. The judge evaluates matches using the following scoring scale: | Score | Meaning | | ----- | ---------------------------------------------------------------------------------------------------------------------- | | 1.0 | Fully satisfied semantically. Different wording or formatting that conveys the same meaning counts as fully satisfied. | | 0.75 | Satisfied in substance with only minor differences that do not affect correctness. | | 0.5 | Partially satisfied. Some required elements are present but others are missing or incorrect. | | 0.25 | Mostly not satisfied. Only marginal relevance. | | 0.0 | Not satisfied at all, or the actual value is empty. | The LLM may return any value between these anchor points (for example, 0.6 or 0.85). The default threshold is 0.5. This means the assertion passes when the response is at least partially satisfied according to the rubric, which is a practical default for AI-generated output that may vary in wording or completeness across runs. Use a higher threshold when the response must satisfy stricter semantic requirements. You can change the threshold globally in the [judge configuration](/apis-tools/testing/configuration.md#judge-configuration) or per assertion using `withJudgeConfig`. If the assertion fails, for example, because the agent made up its own joke instead of calling the `Jokes API` tool, or the feedback was not handled correctly, the judge returns a low score with an explanation of which parts of the expectation were not met. This gives you a clear, human-readable failure message instead of a generic assertion error. #### Tune the judge evaluation Use `withJudgeConfig` to set a stricter threshold for individual assertions: ```java assertThat(processInstance) .withJudgeConfig(config -> config.withThreshold(0.8)) .hasVariableSatisfiesJudge( "agent", "The email body contains a joke addressed to Ervin."); ``` You can also replace the default evaluation criteria with a custom prompt. The custom prompt replaces only the evaluation criteria. The system still controls the expectation and value injection, the scoring rubric, and the JSON output format. Set a custom prompt globally in configuration: ```yaml camunda: process-test: judge: custom-prompt: "You are evaluating whether an AI agent correctly identified the intended recipient, used the right tools, and produced an appropriate email response." ``` Or override the prompt for a single assertion: ```java assertThat(processInstance) .withJudgeConfig(config -> config .withCustomPrompt("You are evaluating whether an AI agent correctly identified the intended recipient, used the right tools, and produced an appropriate email response.")) .hasVariableSatisfiesJudge("agent", "The email body contains a joke addressed to Ervin."); ``` For the full assertion API, see [Assertions](/apis-tools/testing/assertions.md#hasvariablesatisfiesjudge). ### Verify with semantic similarity assertions Use a semantic similarity assertion to verify the agent output. Semantic similarity assertions are a deterministic, lower-cost alternative to [judge assertions](#step-5-verify-with-judge-assertions). Instead of calling a judge LLM at assertion time, they convert both the actual variable value and the expected text to vector embeddings and compare them using cosine similarity. They work best when you can express the expected result as a concrete sample string. ### Configure the embedding model The embedding model does not need to match the AI agent's LLM or the judge model. Depending on your requirements, a lightweight model is often good enough for a good test result. Add the embedding model configuration to your test configuration alongside the CPT settings from [Step 2](#step-2-configure-the-llm-provider-and-connectors): ```yaml camunda: process-test: similarity: embedding-model: provider: "amazon-bedrock" model: "amazon.titan-embed-text-v2:0" region: "eu-central-1" dimensions: 256 credentials: access-key: ${AWS_LLM_BEDROCK_ACCESS_KEY} secret-key: ${AWS_LLM_BEDROCK_SECRET_KEY} ``` Use this provider for [Ollama](https://ollama.com/). ```yaml camunda: process-test: similarity: embedding-model: provider: "openai-compatible" model: "" base-url: "http://localhost:11434/v1" ``` For the full property reference, see [semantic similarity configuration](/apis-tools/testing/configuration.md#semantic-similarity-configuration). ### Add a similarity assertion With the embedding model configured, use `hasVariableSimilarTo` as a complementary check on the `responseText` variable of the `User_Feedback` task instance: ```java assertThat(processInstance) .hasVariableSimilarTo( "User_Feedback", "responseText", """ Hey Ervin! Here is a joke for you: Why did the workflow cross the road? To get to the happy path. """); ``` The assertion converts both strings to embeddings, applies the default text preprocessors (lowercase, Unicode NFC, and whitespace normalization), and compares cosine similarity against the default threshold of 0.5. Override the minimal success threshold for a single assertion if you require a higher precision for some assertions: ```java assertThat(processInstance) .withSemanticSimilarityConfig(config -> config.withThreshold(0.8)) .hasVariableSimilarTo( "User_Feedback", "responseText", """ Hey Ervin! Here is a joke for you: Why did the workflow cross the road? To get to the happy path. """); ``` ## Next steps Now that you know how to test your AI agents, you can: - Learn more about [Camunda agentic orchestration](/components/agentic-orchestration/agentic-orchestration-overview.md) and the [AI Agent connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md). - Dive into [Camunda Process Test assertions](/apis-tools/testing/assertions.md). - Review [judge](/apis-tools/testing/configuration.md#judge-configuration) and [semantic similarity](/apis-tools/testing/configuration.md#semantic-similarity-configuration) configurations for the full property references. - Explore [conditional behavior](/apis-tools/testing/utilities.md#conditional-behavior), including chained actions and lifecycle details. --- ## Expose a process as an MCP tool ## About You can configure a BPMN process as a callable [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) tool through the [Processes MCP Server](/apis-tools/processes-mcp/processes-mcp-overview.md). It is built into the Orchestration Cluster and automatically registers processes as MCP tools when they are deployed with the [MCP start event element template](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-start-event.md). :::tip If your AI agent and the target process run on the **same** Orchestration Cluster, consider using a [call activity](/components/modeler/bpmn/call-activities/call-activities.md) inside the ad-hoc sub-process instead. Call activities are synchronous and maintain a connected instance hierarchy with a consistent audit trail. See [call processes as agent tools](/components/agentic-orchestration/design-architecture.md#call-processes-as-agent-tools) for a full comparison. ::: ## Prerequisites - A [Camunda Hub project](/components/hub/workspace/manage-projects/create-a-project.md) or access to [Desktop Modeler](/components/modeler/desktop-modeler/install-the-modeler.md). - An [Orchestration Cluster](/components/orchestration-cluster.md) running Camunda 8.10 or later. ## Step 1: Add an MCP start event to your process The [MCP start event element template](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-start-event.md) is an element template that you apply to a BPMN message start event. When deployed, it registers the process as an MCP tool. 1. Open your BPMN process in [Camunda Hub](/components/hub/workspace/modeler/modeling/model-your-first-diagram.md) or Desktop Modeler. 2. Select the start event (or add a new one). 3. In the properties panel, click the element template picker and select **MCP start event** from the **AI Tools** category. ![A BPMN message start event in the Camunda Hub modeler with the MCP start event element template applied, showing the properties panel](img/mcp-start-event-modeler.png) ## Step 2: Configure the MCP tool metadata The properties you fill in become the MCP tool's metadata, which AI agents and LLMs use to decide when and how to call your process. Define them in clear and concise language. Vague or incomplete metadata leads to incorrect tool selection or missing arguments. | Property | Required | Description | | :------------------------ | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Name** | Yes | The MCP tool identifier used by clients to call this process. Alphanumeric characters, hyphens (`-`), underscores (`_`), and dots (`.`) **only**. Maximum 100 characters. | | **What it does** | Yes | A plain-language description of the process function, shown to LLMs as tool metadata. | | **Which inputs it needs** | Yes | A plain-language description of required and optional input parameters, their types, and any constraints. | | **When to use** | No | Specific situations or user intents that should trigger this tool. | | **When not to use** | No | Conditions or situations where this tool should not be invoked. | | **What the tool returns** | No | The outcomes, results, and variable names the process produces on completion. | ## Step 3: Design the process execution When an MCP client calls the tool: 1. The Processes MCP Server starts a new process instance with the tool call arguments mapped as process variables. 2. The server immediately returns the started process instance key to the MCP client. You can map the incoming tool call arguments from the LLM to the process variables your process expects using the **Output mapping** property. If you don't define any explicit output mapping, all incoming tool call arguments become process variables with the same names. ## Step 4: Deploy the process Deploy the process to your Orchestration Cluster. After deployment, the Processes MCP Server automatically registers the process as an MCP tool using the metadata you configured. :::important Version binding Only the latest deployed version of a process is exposed as an MCP tool. If you redeploy the process with a changed interface, existing MCP clients holding a cached reference to the old tool will receive a stale-tool error and must re-fetch the tool list. See [version binding](/apis-tools/processes-mcp/processes-mcp-version-binding.md) for more details. ::: ## Step 5: Connect an MCP client Connect any MCP-compliant client to the Processes MCP Server. See [Enable and connect](/apis-tools/processes-mcp/processes-mcp-setup.md) for endpoint URLs, authentication options, and other configuration details. ## Step 6: Verify After deployment, you can verify that your process is registered as an MCP tool in the [Orchestration Cluster admin UI](/components/admin/admin-introduction.md#open-admin). See [MCP processes](/self-managed/components/orchestration-cluster/admin/mcp-processes.md) to learn how. --- ## LLM recommendations for agentic processes ## General model requirements To implement an agentic process, you must choose a model that meets certain baseline requirements. These include: ### Tool calling support The model should be able to invoke external tools and work with work with tool-calling mechanisms, as part of its output. If a model cannot call tools, it won’t be suitable for an agentic workflow. ### Vendor compatibility The model must be available through at least one supported vendor or API. In practice, this means using a model from AWS Bedrock, Google Vertex AI, Azure OpenAI, OpenAI (or any platform compatible with the OpenAI API). Choosing a model from these ecosystems ensures it will integrate with Camunda’s connectors and the agentic orchestration framework. ### Plain text I/O The model should accept and return plain text. Agentic processes rely on text prompts and text-based replies (which may include JSON or other structured text). Avoid models that only produce non-text outputs or require special input formats. _Text in, text out_ is essential for simplicity and reliable tool integration. ### Choose the right model for each use case Not every decision in a process needs the same model: - Frontier models deliver stronger reasoning and handle ambiguous inputs well, but carry higher per-call costs and depend on external infrastructure. - Open-weight models, hosted via Ollama or a compatible inference platform, offer lower costs and full infrastructure control. They're a good fit for high-volume or simpler decisions. With the right model choice, you can keep accuracy where it counts and control costs everywhere else. ## General recommendations for agentic processes As well as choosing the right model, you should follow best practices in designing your agentic process. These recommendations help your AI agent work effectively and safely within a workflow. ### Use detailed tool descriptions When defining tools for the agent to use be very specific about what each tool does and what input it expects: - The more context you give the AI about the tool’s purpose, the more accurately it will use that tool. - Write clear, instructive descriptions for each tool call. You can do so via [`fromAi` expressions](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-tool-definitions.md#ai-generated-parameters-via-fromai) in Camunda. For example, `fromAi(toolCall.emailBody, "Body of the email to be sent")`. ### Mind the context window Consider the model’s context size when designing your agent’s interactions: - For example, if your model has a 4k token limit, plan your prompt and tool usage so you don’t exceed that. Only include relevant information in the prompt and trim any unnecessary details. - When defining tools’ input and output, anticipate how large those could be. A large chunk of text returned from a tool can quickly consume the context window, leaving little room for the model’s reasoning or response. ### Avoid overfilling the prompt with tool output Be cautious that tool responses don’t unintentionally fill the entire context. - If a tool returns very large data, consider post-processing it before feeding it back into the model. - For example, you might take only a summary of a document rather than the full text. This prevents the model’s next prompt from being dominated by irrelevant or excessive content, which can degrade performance and increase cost. ### Sanitize tool outputs Sanitizing ensures the agent doesn’t accidentally get confused or manipulated by malformed tool data, and also reduces the risk of prompt injections coming from external tool results: - Always clean and validate the output from tools before the AI agent uses it in a prompt, by removing any irrelevant, sensitive, or potentially prompt-breaking content. This is important for both security and prompt clarity. - For example, if a web search tool returns HTML or script tags, strip those out or convert them to plain text. ### Account for limited memory and long processes Agentic workflows can be long-running. The AI model won’t “remember” everything forever, as its memory is essentially the prompt history within the context window. - If your process spans multiple steps or lengthy pauses, store important information outside the model’s short-term memory. Use Camunda’s document storage or your own persistent storage layer, such as a database, to save key data between steps. - For example, if the agent gathers info in an early step that’s needed much later, persist that info so it can be reloaded into the prompt when required. This way, the agent can retrieve past knowledge without relying on an ever-growing prompt history. :::note An agentic process might pause or wait for events, causing the context to reset between runs. By saving state to a database or Camunda document storage, you ensure nothing vital is lost when the process continues. Think of it as the agent’s long-term memory—use it for any details the agent might need beyond the current prompt. ::: ### Incorporate human feedback when appropriate Consider adding a “human in the loop” as one of the agent’s tools. In practice, this could be a special tool, such as `ask_human` or a review task, that the agent can invoke to get confirmation or guidance from a user. - This is especially useful for high-stakes decisions or if the AI is unsure how to proceed. Designing your process with a human feedback option means the agent can defer to a person instead of guessing. - For example, the workflow might include a step where an employee reviews the AI’s draft output or where the AI explicitly asks the user to clarify an ambiguous request. This supervision loop can greatly improve the quality and safety of the agent’s actions. ## Prompting recommendations Constructing effective prompts is critical for guiding the model in an agentic process. Keep the following guidelines in mind. ### Leverage vendor-specific best practices Each model has recommended prompting techniques. Refer to the official documentation for each model: - [Anthropic Claude](https://docs.anthropic.com/claude/docs) - [OpenAI GPT / reasoning models](https://platform.openai.com/docs/guides/prompt-engineering) - [Google Gemini](https://cloud.google.com/vertex-ai/generative-ai/docs/models) - [Cohere Command-R](https://docs.cohere.com/docs/the-cohere-platform) - [Meta Llama](https://llama.meta.com/docs/) - [Mistral / Mixtral / Codestral](https://docs.mistral.ai/) - [Alibaba Qwen](https://qwen.readthedocs.io/) ### Use chain-of-thought and examples for complex tasks Don’t hesitate to let the model “think out loud” or guide it through tricky scenarios. Chain-of-thought prompting asks the model to solve problems step by step, for example, by including a phrase like “Let’s reason this out step by step...” or using a hidden `` tag if supported. This approach helps improve reasoning accuracy. Also, provide a few in-context examples (few-shot prompting) to show how to handle edge cases, compliance rules, or specific output formats. Illustrate any structured output formats in the prompt, and if possible, configure the connector's [response format](../connectors/out-of-the-box-connectors/agentic-ai-aiagent.md) options to enforce JSON or parsed text responses. Clear examples and format guidance set expectations for the AI, ensuring consistency and reducing errors. ### Define when and how the agent should seek user input In your system or prompt instructions, make it clear when the AI should involve a human. For example, you could say, “If the user’s request is unclear or more information is needed, the assistant should ask a follow-up question via the customer communication tool.” Decide which points in the process need user feedback, such as after showing an intermediate result or when the AI is unsure about a critical decision. Specify this in the prompt so the model knows it is acceptable or expected to ask for clarification. The instructions should also guide the AI on using the human feedback tool. For example, “Before finalizing an answer, if confidence is low, call the `ask_human` tool to confirm the details.” Being explicit helps the agent make better decisions. ### Set a clear persona and objective in the system prompt Always start your prompt by defining the AI’s role and goal. For example, in an agentic process, the model could act as a specialized assistant: “You are OrderAgent, an AI assistant helping users track and modify their orders.” Include the persona’s traits and main objective. For example: “Your goal is to resolve customer inquiries using the tools provided while following all company guidelines.” Defining the persona and objective helps the model maintain a consistent tone and produce focused, coherent outputs. :::tip Prompt engineering is iterative. After writing an initial prompt, test it with your model and sample scenarios. If the agent’s behavior isn’t quite right, refine the wording or add another example. Small phrasing changes can have a big impact. Continue to experiment and refine to achieve reliable, compliant results for your specific agentic use case. ::: ### Example of a generic prompt ```text You are **OrderAgent**, a helpful AI assistant supporting order management. Your objective is to resolve requests by: 1. Using the available tools when external action is required. 2. Asking for clarification when input is incomplete or ambiguous. 3. Returning outputs in JSON format if requested by the connector. Let’s reason step by step. Comportments for tool usage: - **Direct actions:** - Use `cancel_order` when a clear and valid order ID is provided. - Use `send_email` only when communication with the customer is explicitly required. - **Chained actions:** - If cancelling an order also requires notifying the customer, first call `cancel_order`, then call `send_email`. - If a tool returns a status update that triggers a follow-up action (for example, an order is “on hold”), use the corresponding resolution tool in sequence. - **Ambiguity handling:** - If the order reference is missing, request clarification before proceeding with a tool. - If multiple orders match the request, return options and request the user (or `ask_human`) to disambiguate. - **Escalation to human (`ask_human`):** - If the requested action could have irreversible impact (e.g., “delete all orders”), always escalate. - If tool outputs are malformed, incomplete, or contradictory, escalate for review. - If confidence in the decision path is low (for example, conflicting data across tools), escalate rather than guessing. - **Unexpected tool outputs:** - If a tool returns irrelevant or excessive data (e.g., HTML instead of plain text), sanitize and summarize before continuing. - If output cannot be parsed or mapped correctly, escalate with `ask_human`. The goal is to use tools precisely, combine them logically when workflows require multiple steps, and defer to a human when safety, ambiguity, or unexpected results make autonomous resolution unreliable. ``` --- ## Add long-term memory to your AI agents ## About Use Retrieval-Augmented Generation (RAG) with the [Vector Database connector](/components/connectors/out-of-the-box-connectors/embeddings-vector-db.md) to give your AI agents access to persistent, domain-specific knowledge that grows over time. ## When to use long-term memory A standard AI agent operates within a fixed context window. This works well for many tasks, but becomes limiting when the agent needs access to large or frequently updated knowledge. Long-term memory solves this by storing knowledge outside the agent in a vector database and retrieving only the most relevant fragments at runtime. Common use cases include: - **Policy and procedure lookup**: The agent answers questions about internal rules or processes by retrieving the relevant document sections on demand. - **Product and catalog search**: The agent finds product details, specifications, or pricing from a large catalog without loading it all into context. - **Support knowledge base**: Answers to previously resolved questions are stored and surfaced automatically when similar questions arise in the future. - **Compliance and audit**: The agent retrieves the exact policy text needed to justify or explain a decision, making its reasoning traceable. ## How it works The [Vector Database connector](/components/connectors/out-of-the-box-connectors/embeddings-vector-db.md) supports two operations that together implement long-term memory: - **Retrieve document**: Performs a semantic similarity search and returns the most relevant results from a vector index. Use this to let the agent query its knowledge base. - **Embed document**: Converts text into a vector embedding and stores it in the vector index. Use this to add new knowledge to the agent's memory. The LLM is responsible for generating natural language queries when retrieving, and for deciding what content is worth storing. The actual vector operations (encoding, indexing, and searching) are handled by the connector and the underlying vector store. To configure either operation, you need: - A supported [vector store](/components/connectors/out-of-the-box-connectors/embeddings-vector-db.md#vector-stores) and connection credentials. - A supported [embedding model](/components/connectors/out-of-the-box-connectors/embeddings-vector-db.md#embedding-models) and its provider credentials. - An index name that identifies the collection to read from or write to. ## Retrieve from the vector database To perform a semantic search from a vector database, you can use one of the following two approaches. ### Add a vector database query tool To let an agent query a vector database, add a **Vector Database connector task** with no incoming sequence flows inside the AI Agent's [ad-hoc sub-process](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md). :::note Tasks with no incoming flows are treated as available [tools](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-tool-definitions.md) by the AI Agent connector. ::: Configure the task as follows: 1. Give the task a clear **Name** and write a descriptive **Element documentation** to help the LLM understand when to use this tool. The element documentation is passed to the LLM as the [tool description](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-tool-definitions.md#tool-definitions). 2. Set **Operation** to **Retrieve document**. 3. Set **Search query** using the [`fromAi()`](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-tool-definitions.md#ai-generated-parameters-via-fromai) function so the LLM generates the query dynamically at runtime: ```feel fromAi(toolCall.query, "The query you're making to the vector database.") ``` 4. Set **Max results** to control the maximum number of documents returned. For example, set it to five. 5. Configure the [**Embedding model**](/components/connectors/out-of-the-box-connectors/embeddings-vector-db.md#embedding-models) with your provider credentials. 6. Configure the [**Vector store**](/components/connectors/out-of-the-box-connectors/embeddings-vector-db.md#vector-stores) with your database connection details and **index name**. The index name identifies the collection of documents the agent searches. You can use different indexes for different knowledge domains. 7. In the **Output mapping** section, set the output **Result variable** to `toolCallResult`. ### Isolate content with dynamic index names When multiple agents, tenants, or conversations share the same vector database, you can isolate their content by using **dynamic index names**. Instead of hardcoding a single index, construct the index name at runtime using process variables. For example, by appending a tenant ID or conversation ID: ```feel "knowledge-base-" + tenantId ``` This ensures each scope reads and writes only its own documents, without requiring metadata-based filtering at query time. Because indexes are created on demand when documents are first embedded, a retrieval task may run before any documents have been stored for a given scope. This may result in an `index_not_found` error. See how to [handle missing or empty results](#handle-missing-or-empty-results). #### Handle missing or empty results To prevent process failures when no results are retrieved, you can set an error handler to inform the agent as follows. 1. In the **Error handling** section, set the **Error expression** to handle these scenarios. For example: ``` if contains(error.message, "index_not_found") then bpmnError("index_not_found", "The index does not exist") else null ``` 2. Add an [**error boundary event**](/components/modeler/bpmn/call-activities/call-activities.md#boundary-events) to the Vector Database connector: 3. In the boundary event's **Output mapping** section, add an output variable as follows: - Set **Process variable name** to `toolCallResult`. - Set **Variable assignment value** to: ``` { "searchResult": "Nothing was found" } ``` ### Prefetch context with a vector database retrieval Instead of letting the agent decide when to query the vector database via a tool, you can retrieve relevant context **before** the agent runs. This ensures the agent always has access to relevant knowledge from the first interaction, without requiring a tool call. This pattern is useful when: - The user's query is predictable enough to retrieve meaningful context upfront. - You want to reduce the number of tool calls and agent reasoning steps. - The agent should ground its first response in domain-specific knowledge without deciding whether to search. #### Configure the retrieval task 1. Add a **Vector Database connector task** in your process, sequenced before the AI Agent connector task. 2. Set **Operation** to **Retrieve document**. 3. Set **Search query** to the user's input. For example, if the user query is stored in a process variable: ```feel userQuery ``` 4. Set **Max results** to control the maximum number of documents returned. For example, set it to five. 5. Configure the [**Embedding model**](/components/connectors/out-of-the-box-connectors/embeddings-vector-db.md#embedding-models) with your provider credentials. 6. Configure the [**Vector store**](/components/connectors/out-of-the-box-connectors/embeddings-vector-db.md#vector-stores) with your database connection details and **index name**. The index name identifies the collection of documents the agent searches. You can use different indexes for different knowledge domains. 7. In the **Output mapping** section, set the output **Result variable**. For example, `retrievalResult`. ### Pass retrieved context to the agent Once the retrieval task completes, include its results in the AI Agent's user message. There are two approaches: #### Concatenate as text Build the user message by appending the retrieved text to the original query: ```feel userQuery + " Use the following context to inform your answer: " + " ".join(retrievalResult.searchResult) ``` This works well when the retrieved content is short and you want the agent to treat it as inline context. #### Attach as documents If the Vector Database connector returns structured document objects, you can add them to the user message's document list. This keeps the user query and the supporting documents separate, which can help the LLM distinguish between the question and the reference material. Refer to the [AI Agent connector documentation](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md) for details on how to structure the message input with documents. ### Prefetch vs. tool-based approach | Consideration | Prefetch | Tool-based retrieval | | ------------------ | ---------------------------------------------------- | ---------------------------------------------------------- | | **Agent autonomy** | Agent does not choose when to search | Agent decides if and when to search | | **Latency** | Retrieval runs once before the agent starts | Retrieval adds a tool-call round trip | | **Query control** | Uses the raw user query directly | LLM reformulates the query dynamically | | **Relevance** | Best when the user query maps well to stored content | Best when the agent needs to refine or decompose the query | :::tip You can combine both patterns: prefetch broad context to prime the agent, and still expose a retrieval tool for follow-up searches the agent initiates on its own. ::: ## Store in the vector database You can store knowledge in the vector database in two ways: - **Batch import**: Documents are embedded and stored before the agent starts processing, typically as part of a data preparation process. Use a Vector Database connector task in a separate BPMN process or script. - **Runtime ingestion**: New knowledge is added to the vector database as the agent encounters it. For example, when a human provides an answer that did not previously exist in the database. :::note Re-embedding the same document is **not idempotent**: if you store it again without deleting the existing chunks first, you’ll create **duplicate chunks** in the vector database. ::: For both approaches, add a **Vector Database connector task** and configure it as follows: 1. Set **Operation** to **Embed document**. 1. Set **Document source** to **Plain text**. 1. Provide the text to embed. This can be a process variable, a form output, or any string value. 1. Configure the same [**Embedding model**](/components/connectors/out-of-the-box-connectors/embeddings-vector-db.md#embedding-models) and [**Vector store**](/components/connectors/out-of-the-box-connectors/embeddings-vector-db.md#vector-stores) settings used by the retrieval method so both operations target the same index. :::important Make sure the embedding model configuration, including vector dimensions, matches your retrieval setup. ::: ## Gate memory writes with human approval Allowing an agent to write to its own knowledge database without human oversight can lead to incorrect or irrelevant data being stored. Besides, an effective pattern for building long-term memory is to combine a human escalation tool with runtime knowledge ingestion. When the agent can’t find an answer in the vector database, it escalates to a human. The human responds to the agent and decides whether it’s worth storing the answer in the vector database for future queries. Over time, this creates a self-improving knowledge base: as humans answer previously unknown questions, the agent's ability to resolve those questions autonomously increases and the rate of human escalations decreases. To implement this pattern: 1. Add a [user task](/components/modeler/bpmn/user-tasks/user-tasks.md) inside the AI Agent's ad-hoc sub-process. The agent will invoke it as a tool when it cannot resolve a query from its existing knowledge. 2. Configure the user task's **Input mapping** to pass the agent's question to the form using `fromAi()`. For example: ```feel fromAi(toolCall.question, "The question the agent needs a human to answer.") ``` 3. Add a [form](/components/modeler/forms/camunda-forms-reference.md) to the user task. It should capture the human's answer and include a decision checkbox for whether to store it in long-term memory. 4. Configure the user task's **Output mapping** to set the human's answer as `toolCallResult` so it is returned directly to the agent. 5. Add an [exclusive gateway](/components/modeler/bpmn/exclusive-gateways/exclusive-gateways.md) after the user task with two outgoing paths: - **Approved**: [Store in the vector database](#store-in-the-vector-database). - **Rejected**: Do not store. 6. Use the human's output variable as the gateway condition. --- ## Access control(Overview) Reference the permissions required to access audit log entries. ## About To access entries in the audit log, you must have the relevant authorizations to match your needs: | Authorization type | Resource type | Resource ID | Permission | | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------- | :------------------------------------------------------------------------------------------------ | :---------------------- | | View audit log entries. | `AUDIT_LOG` | An operation category (`ADMIN`, `DEPLOYED_RESOURCES`, or `USER_TASKS`) or `*` for all categories. | `READ` | | View `DEPLOYED_RESOURCES` and `USER_TASKS` operation logs for instances of a specific process definition. This provides access to both the general and process instance-level operation logs in Operate. | `PROCESS_DEFINITION` | A process definition ID or `*` for all process definitions. | `READ_PROCESS_INSTANCE` | | View `USER_TASKS` operation logs for instances of a specific process definition. This provides access to the operation log in Operate and the task history in Tasklist. | `PROCESS_DEFINITION` | A process definition ID or `*` for all process definitions. | `READ_USER_TASK` | | View operations related to specific tasks the user has access to based on task properties. This provides access to task history records in Tasklist. | `USER_TASK` | A user task property (assignee, candidateUsers, candidateGroups). | `READ` | Learn more about the operation categories in [Recorded operations](./recorded-operations.md). --- ## Operation data structure Learn more about how operation data from the audit log is presented in different contexts. ## Applications Depending on the view you're using to access the audit log in [Operate](../../operate/userguide/audit-operations.md), [Admin](../../admin/audit-operations.md), or [Tasklist](../../tasklist/userguide/audit-task-history.md), you'll see a subset of the following operation details: | Property | Description | | :------------- | :-------------------------------------------------------------------------- | | Status | The status of the operation. | | Operation type | The type of operation applied. | | Entity type | The type of entity the operation was applied to. | | Entity key | The key and name of the entity the operation was applied to, if applicable. | | Parent entity | The key and name of the parent entity, if applicable. | | Related entity | The ID or name of the related entity, if applicable. | | Details | Details about the operation. | | Actor | The user, client, agent, or MCP tool that applied the operation. | | Date | The date and time at which the operation was applied. | ### Entity key Some audit log entries contain extra details about the entity in the **entity key** field: | Operation type | Entity type | Entity key | | :------------- | :--------------- | :------------ | | Create | Process instance | Process name | | Delete | Process instance | Process name | | Create | Variable | Variable name | | Update | Variable | Variable name | | Create | Resource | Resource name | | Delete | Resource | Resource name | | Create | Decision | Decision name | | Delete | Decision | Decision name | ### Details Some audit log entries contain extra details about the operation in the **details** field: | Operation type | Entity type | Details | | :------------- | :------------ | :--------------------------------- | | Create | Batch | Batch operation type | | Assign | User task | Assignee | | Unassign | User task | Assignee | | Create | Authorization | Owner(entity type, entity name) | | Assign | Tenant | Assignee(entity type, entity name) | | Unassign | Tenant | Assignee(entity type, entity name) | | Assign | Role | Assignee(entity type, entity name) | | Unassign | Role | Assignee(entity type, entity name) | | Assign | Group | Assignee(entity type, entity name) | | Unassign | Group | Assignee(entity type, entity name) | ### Actor The following actor types can trigger operations: | Actor type | Identifier | | :--------- | :--------- | | User | Username | | Client | Client ID | Agents can perform operations on behalf of a user or client. In this case, you will see the agent's information in the record. ### Inbound channel Alongside the actor, operations record the inbound channel through which they were triggered, when available, identifying how the operation entered the system: | Channel | Description | | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | REST | The operation was triggered through the Orchestration Cluster REST API. | | GRPC | The operation was triggered through the gRPC API. | | MCP | The operation was triggered through the [Model Context Protocol (MCP)](../../agentic-orchestration/expose-process-as-mcp-tool.md), for example, by an AI agent invoking an MCP tool. | | INTERNAL | The operation was triggered internally by Camunda. | When the operation was triggered through MCP, the record also captures the name of the MCP tool that triggered it, so you can distinguish operations initiated by AI agents through MCP from those performed directly by users or clients. In the REST API, these values are exposed as `inboundChannelType` and `inboundChannelToolName`. In Operate, when the inbound channel is `MCP`, it is shown separately in the **Actor** column. The channel and the channel tool name are also shown in the operation details popup. ## REST API With the API, you can access more operation data than you can in the applications. See the [API response schema](../../../apis-tools/orchestration-cluster-api-rest/specifications/search-audit-logs.api.mdx#responses) for more information. --- ## Recorded operations Learn more about which operations are recorded in the audit log. ## Limitations and constraints The audit log contains operations performed using: - [Operate](../../operate/userguide/audit-operations.md), [Admin](../../admin/audit-operations.md), and [Tasklist](../../tasklist/userguide/audit-task-history.md) - [Orchestration Cluster REST API](../../../apis-tools/orchestration-cluster-api-rest/specifications/search-audit-logs.api.mdx) However, only operations that are authenticated, authorized, and reach execution with a success or execution‑time failure are recorded. Operations rejected before execution are not recorded in the audit log. Additionally, only user operations are tracked by default, not [client](../../zeebe/technical-concepts/architecture.md#clients) operations. Unlike the other constraints, you can configure this behavior. ## Recorded operations There are three categories of recorded operations: - `USER_TASKS` - `ADMIN` - `DEPLOYED_RESOURCES` ### `USER_TASKS` operations You can review the full history of user task actions, including assignment changes, completions, and updates. With this, you can resolve disputes, investigate SLA breaches, and validate that required steps were followed during case handling. These operations belong to the category `USER_TASKS`. The following operations are recorded in the audit log: | Operation type | Entity | Tracked rejections | | :------------- | :-------- | :----------------- | | Update | User task | INVALID_STATE | | Assign | User task | INVALID_STATE | | Unassign | User task | INVALID_STATE | | Complete | User task | INVALID_STATE | ### `ADMIN` operations You can track all changes to identity resources, like authorizations, users, and tenants. With this, you can detect misconfigurations and investigate potential unauthorized access to sensitive process data. These operations belong to the category `ADMIN`. The following operations are recorded in the audit log: | Operation type | Entity | Tracked rejections | | :------------- | :------------ | :----------------- | | Create | Authorization | – | | Update | Authorization | – | | Delete | Authorization | – | | Create | User | – | | Update | User | – | | Delete | User | – | | Create | Tenant | – | | Update | Tenant | – | | Delete | Tenant | – | | Assign | Tenant | – | | Unassign | Tenant | – | | Create | Role | – | | Update | Role | – | | Delete | Role | – | | Assign | Role | – | | Unassign | Role | – | | Create | Group | – | | Update | Group | – | | Delete | Group | – | | Assign | Group | – | | Unassign | Group | – | | Create | Mapping rule | – | | Update | Mapping rule | – | | Delete | Mapping rule | – | ### `DEPLOYED_RESOURCES` operations You can audit user and client actions that modified or influenced deployed resources and dependent entities, like process instances, batch operations, and variables. With this, you can identify manual corrections and confirm the sequence of actions that led to a process failure or escalation. These operations belong to the category `DEPLOYED_RESOURCES`. The following operations are recorded in the audit log: | Operation type | Entity | Tracked rejections | | :------------- | :--------------- | :------------------------------ | | Create | Process instance | – | | Cancel | Process instance | – | | Modify | Process instance | – | | Migrate | Process instance | INVALID_STATE, PROCESSING_ERROR | | Create | Variable | – | | Update | Variable | – | | Resolve | Incident | INVALID_STATE | | Create | Resource | – | | Delete | Resource | – | | Create | Batch | – | | Suspend | Batch | INVALID_STATE | | Resume | Batch | INVALID_STATE | | Cancel | Batch | INVALID_STATE | | Create | Decision | – | | Delete | Decision | – | | Evaluate | Decision | – | For tasks with output mappings, the audit log shows changes made by those mappings. Unchanged variables aren't included. Variable audit entries don't include variable values. #### Batch operations While the operations for creating and managing batch operations are recorded in the audit log, the batch operation state changes aren't. For more information, learn how to [monitor batch operations](../../operate/userguide/monitor-batch-operations.md). ## Log scope `ADMIN` and `BATCH` operations are not scoped to a particular tenant. Instead, they're applied at a global scope because Identity-related operations don't belong to an individual tenant and batch operations may include items from multiple tenants. Keep this in mind when you filter by tenant ID with the [search audit logs API](/apis-tools/orchestration-cluster-api-rest/specifications/search-audit-logs.api.mdx) or the [Operate user interface](/components/operate/userguide/audit-operations.md). As these operations aren't scoped to a tenant, selecting a particular tenant ID will filter out these operations. --- ## Audit log View and audit a comprehensive record of operations across process, identity, and user task domains. ## About The audit log provides a record of operations, including who performed an operation (a user, client, or [AI agent](/reference/glossary.md#ai-agent)), when it was performed, and on which entities the operation was performed. Use the audit log to: - **Prove compliance:** Produce defensible evidence of operation ownership and history during internal and external audits. - **Meet governance and regulatory requirements:** Validate if required steps were followed during case handling, and investigate unauthorized access to sensitive process data. - **Maintain operational integrity and transparency:** See a complete record of actions taken to resolve disputes and investigate SLA breaches. - **Troubleshoot issues:** Review user and client actions that modified or influenced process instances to confirm the sequence of actions that led to a process failure. ## Impact on secondary storage When the audit log is active, a record is written to [secondary storage](../../self-managed/concepts/secondary-storage/index.md) for every applicable operation instance. By default, only user operations are tracked, not [client](../zeebe/technical-concepts/architecture.md#clients) operations. With this default behavior, you can expect a 3.5% increase in disk usage. :::warning The audit log is enabled by default. Because of the increase in resource usage on secondary storage, you may see increased costs associated with this feature. ::: You can configure the audit log to fine tune log thoroughness and resource usage according to your needs: - [SaaS](../saas/clusters/configure-audit-log.md) - [Self-Managed](../../self-managed/concepts/audit-log/configure.md) ## Get started Start auditing operations in Operate, Tasklist, and Admin (formerly Orchestration Cluster Identity). ## Learn the fundamentals Learn fundamental concepts about how the audit log works and how to access its data. ## Explore further resources Once you have a foundational understanding of the audit log, explore these additional resources: - [Use the Camunda REST API to access the audit log](../../apis-tools/orchestration-cluster-api-rest/specifications/search-audit-logs.api.mdx) --- ## Data flow Understand how data moves through Camunda 8.10+ and why it matters when sizing your environment. ## About Camunda 8.8 introduced a consolidated [Orchestration Cluster](/components/orchestration-cluster.md). Camunda 8.10 introduced a consolidated design-time management and modeling component: [Camunda Hub](/components/hub/index.md). This is an overview of Camunda 8.10+ architecture: ![Camunda 8.10+ architecture overview](assets/architecture-8.10plus.jpg) See the [reference architecture](/self-managed/reference-architecture/reference-architecture.md) for a component-topology overview. ### How Camunda stores data Every record in Camunda passes through two distinct storage layers. Understanding the difference between them is the key to understanding sizing. - **[Primary storage](/reference/glossary.md#primary-storage)** is the multi-Raft cluster in Camunda, with partitions as the scaling unit. Each partition has a Raft append-only log, RocksDB to store internal state, and snapshots for compaction. All writes land here first. It is durable and strongly consistent, but it is not directly queryable from outside the cluster. Each partition has exactly one leader responsible for both processing commands and exporting records. - **[Secondary storage](/reference/glossary.md#secondary-storage)** is an external data storage where events are written, such as Elasticsearch, OpenSearch, or an RDBMS (available from 8.9). It is eventually consistent and populated asynchronously by the export pipeline. Everything Operate, Tasklist, Admin, and the REST Query API reads comes exclusively from secondary storage. ## Command processing path A command travels from the client to primary storage and then to the engine. A response only comes back after processing. Its processing path (command lifecycle) follows this pattern: **Client (REST or gRPC) → Camunda API (Gateway) → Broker (Command API) → Raft partition (log) → Raft replication → Processing Engine → event on log → RocksDB state update → Client response** This path is highlighted green in the following diagram: ![Camunda 8.10+ architecture overview - Data Flow Command processing path](assets/architecture-8.10plus-data-flow-command.jpg) Client responses are not sent until the command is fully processed by the engine. The engine can only process a command once it has been committed to the log (as part of the Raft consensus protocol). Commands are read sequentially per partition, only one command per partition is processed at a time, and only the Raft partition leader runs the engine. This means command response latency is bounded below by Raft commit time, engine processing time, and processing queue length. In a healthy and stable cluster, this typically results in sub-second response latency for simple commands. Raft commit time depends on disk performance: the leader and enough followers to reach quorum must flush each appended batch to disk before the engine can process the command. Primary storage therefore needs low-latency SSDs with enough sustained IOPS per broker, otherwise throughput drops and latency rises. See [Disk IOPS](sizing-self-managed.md#disk-iops) for the recommended value. If the engine cannot process commands fast enough, for example, because disk I/O is saturated, network latency is high, or the backlog is large, the Command API applies backpressure to the client. See [internal processing](../../zeebe/technical-concepts/internal-processing.md) for more details. ## Export pipeline After the engine processes a command, it confirms its state change with an event on the log. Exporters asynchronously read such events from the log (only committed events) and write them to secondary storage in _batches_. This pipeline is highlighted blue in the following diagram: ![Camunda 8.10+ architecture overview - Data Flow Export pipeline](assets/architecture-8.10plus-data-flow-export-path.jpg) **The exporters run on the same leader as the engine.** They are partition-bounded and cannot scale independently of partition count. There are three built-in exporters involved: | Exporter | Storage and purpose | | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------- | | [Camunda Exporter](../../../self-managed/components/orchestration-cluster/zeebe/exporters/camunda-exporter.md) | Aggregates and writes enriched data to secondary storage (ES/OS) for Operate, Tasklist, and the REST Query API. | | [RDBMS Exporter](../../../self-managed/components/orchestration-cluster/zeebe/exporters/rdbms-exporter.md) | Aggregates and writes enriched data to secondary storage (RDBMS) for Operate, Tasklist, and the REST Query API. | | [Elasticsearch Exporter](../../../self-managed/components/orchestration-cluster/zeebe/exporters/elasticsearch-exporter.md) / [OpenSearch Exporter](../../../self-managed/components/orchestration-cluster/zeebe/exporters/opensearch-exporter.md) | Writes raw engine events into specific Elasticsearch/OpenSearch indices, consumed by Optimize. | - The Camunda Exporter and RDBMS Exporter are mutually exclusive, only one can be enabled at a time. - The Elasticsearch/OpenSearch exporter is independent and can be enabled alongside either of the other two. :::note Read events are applied to the registered exporters one by one, in the same order as they appear on the log. Each event is applied to ALL exporters before the next event is processed. ::: The exporters track their position in the Exporter state (backed by RocksDB). If the exporting backlog grows over a certain threshold, Camunda reduces the record write rate via a corresponding [flow control](/self-managed/operational-guides/configure-flow-control/configure-flow-control.md) mechanics to keep the exporting backlog manageable. In extreme cases, client commands are rejected via the standard backpressure mechanism. Exporter behavior and performance is important for the system, because: - If an exporter falls behind, it holds up all exporters for that partition. - Slow secondary storage directly reduces process execution throughput. - A custom exporter runs in the same export step as the built-in exporters. A custom exporter with a slow `export()` method delays exporters that write to secondary storage, such as the Camunda Exporter. As a result, data becomes available later in Operate, Tasklist, and the REST Query API. - When flow-control throttling is enabled, a slow custom exporter also reduces the partition’s write rate. See [performance impact](/self-managed/concepts/exporters.md#performance-impact). ## Query path Operate, Tasklist, and the REST Query API (`GET /v2/...`) read exclusively from the configured secondary storage. They never read directly from the engine. This path is highlighted red in the following diagram: ![Camunda 8.10+ architecture overview - Data Flow Query path](assets/architecture-8.10plus-data-flow-query.jpg) Query results depend on the performance of both the primary (processing path) and secondary storage (exporting pipeline). They are **eventually consistent**: there is always some lag between a command completing in the engine and the result being visible in search results or the UI. This is measured as the **data availability latency**. Data availability latency is bounded below by export pipeline lag; if the exporter is behind, data availability is behind. This can be caused by a slow or overloaded secondary storage. ## Optimize data flow Optimize sits on top of the export pipeline as a second-tier consumer. This is highlighted purple in the following diagram: ![Camunda 8.10+ architecture overview - Data Flow Optimize](assets/architecture-8.10plus-data-flow-optimize.jpg) 1. The Elasticsearch/OpenSearch exporter writes raw engine events into per-partition Elasticsearch/OpenSearch indices. 2. Optimize's **importer** reads from those indices and transforms the data into its own analytics indices. 3. Optimize writes the analytics indices **back into the same or another Elasticsearch/OpenSearch cluster**. This means Optimize has an additional hop in the data flow compared to Operate and Tasklist, and it writes to secondary storage twice: once for the raw events and once for the analytics indices. As a result, data availability latency for Optimize is higher than for Operate and Tasklist, and the overall write load on Elasticsearch/OpenSearch is significantly higher when Optimize is enabled. Optimize's indices store variables differently from the raw export. Each variable is stored in its owning process instance document, and its value is indexed in several forms simultaneously. This allows Optimize's variable filters and reports to support the following query types without requiring separate reindexing: - An exact-match form. - A case-insensitive form. - A substring-searchable form. - A best-effort date form. - Best-effort numeric forms for long and double values. As a result, Optimize's storage cost per variable is significantly higher than the cost of the raw exported record. The storage cost increases further for high-cardinality string variables because the substring-searchable form scales with the number of distinct values: - Variables with a small number of repeated values, such as a status field, compress efficiently. - Variables with a different value for almost every process instance, such as a customer or order ID, compress poorly. See [Impact of Optimize](./sizing-your-environment.md#impact-of-optimize) for measured examples, including how object variable flattening compounds this by multiplying variable count rather than variable value size. :::note This is exactly why the architecture was changed in 8.8: the Camunda Exporter now aggregates the data for Operate and Tasklist, which previously both used an Exporter-Importer architecture similar to Optimize. See this [blog post](https://camunda.com/blog/2025/02/one-exporter-to-rule-them-all-exploring-camunda-exporter/) for more details. ::: See the [sizing guide](./sizing-your-environment.md#impact-of-optimize) for details on the impact of running Optimize and how to reduce it. :::note Optimize is not supported with RDBMS backends. If Optimize is required, a separate Elasticsearch/OpenSearch instance must be present even if the core platform uses RDBMS. ::: ## Performance and sizing factors The paths above map directly to the factors to consider when [sizing your environment](sizing-your-environment.md): - **Partition count** bounds both command path throughput and export pipeline parallelism. More partitions means more parallel processing and exporting, up to the available hardware. - **Elasticsearch/OpenSearch resources** is the most common cause of operational delay and degradation. Monitor and scale storage before hitting performance bottlenecks. - **Optimize** significantly increases secondary storage write load. Size Elasticsearch/OpenSearch accordingly, or use a dedicated Elasticsearch/OpenSearch instance, if Optimize is enabled. For hardware recommendations based on these factors, see [size your environment](sizing-your-environment.md). --- ## Deciding about your stack Our greenfield stack recommendation is a result of extensive discussions and evaluations. While not the only option, it is a solid choice if there are no specific reasons to choose an alternative. Your choice of programming language should align with your team's expertise; we suggest Java or JavaScript for their broad applicability and support, and have outlined the Java greenfield stack below with Camunda 8 SaaS. ## The Java greenfield stack ![greenfield stack architecture diagram](deciding-about-your-stack-assets/greenfield-architecture.png) This architecture diagram illustrates the flow of requests from a user's browser through Camunda SaaS, where workflows and decisions are orchestrated. The process then moves to the Spring Boot application, which is responsible for executing business logic, handling database interactions with PostgreSQL, and managing various components such as custom REST endpoints, BPMN/DMN definitions, and external task workers. ### Why this stack? - SaaS simplifies workflow engine integration. - Spring Boot is widely adopted for Java application development. - Flexible for both on-premises and cloud environments. Discover more in our [getting started guide using Spring](/guides/getting-started-example.md) or the [Camunda Spring Boot Starter instructions](../../../apis-tools/camunda-spring-boot-starter/getting-started.md). ### Set up the stack For a Java-based setup using Camunda 8 SaaS and Spring Boot, use the following stack: #### Camunda 8 SaaS account and cluster If you're new to Camunda SaaS, check out our [getting started guide](/guides/introduction-to-camunda-8.md#getting-started) to set up your environment. After signing up, create a cluster by following [creating a cluster in Camunda 8](/components/saas/clusters/create-cluster.md), which provides step-by-step instructions on setting up a new cluster in the Camunda 8 environment. #### Spring Boot Develop your own process solutions as [Spring Boot](https://spring.io/projects/spring-boot) applications. This involves setting up a new Spring Boot project, either manually or using tools like [Spring Initializr](https://start.spring.io/). Integrate the [Camunda Spring Boot Starter](../../../apis-tools/camunda-spring-boot-starter/getting-started.md) into the Spring Boot project by adding necessary dependencies to the project’s `pom.xml` file, and configure the application to use Camunda services. #### Maven Use [Maven](https://maven.apache.org/) to manage the build lifecycle of the application. #### IDE selection Select an Integrated Development Environment (IDE) that supports Java development, Maven, and Spring Boot. Frequently used options include Visual Studio Code, IntelliJ IDEA, or Eclipse. #### Java runtime Install and use OpenJDK 17 as your Java runtime environment. Download it from the [official JDK 17 download page](https://jdk.java.net/17/). #### Modeling Download and use Desktop Modeler for designing and modeling business processes from [Camunda downloads](https://docs.camunda.io/downloads/). #### Code integration Incorporate all Java code and BPMN process models into the Spring Boot project, ensuring that they are structured correctly and referenced properly within the application. ### Run the process application: To run the process application, transfer the `jar` to the desired server. Start the application using the command `java -jar YourProcessApplication.jar`. Frequently, this deployment process is managed through Docker for ease of use. For a practical implementation, refer to our [example application on GitHub](https://github.com/camunda-community-hub/camunda-cloud-examples/tree/main/twitter-review-java-springboot), which demonstrates a typical setup for a Spring Boot-based process application with Camunda. ## Customize your stack ### Polyglot stacks You can develop process solutions as described with Java above also in any other programming language, including JavaScript. Use the [existing language clients and SDKs](/apis-tools/working-with-apis-tools.md) for doing this. ### Run Camunda 8 Self-Managed Run Camunda 8 on your Kubernetes cluster. For local development, a [Docker Compose configuration is available](/self-managed/deployment/docker/docker.md), though not for production use. Learn more in the [deployment docs](/self-managed/deployment/helm/install/quick-install.md). ### Choose an LLM provider If your processes hand steps to [AI agents](/reference/glossary.md#ai-agent), the LLM provider becomes part of your stack decision. Weigh hosting, data sensitivity, and cost as described in [choosing the right LLM](/components/agentic-orchestration/choose-right-model-agentic.md). On Camunda 8 SaaS, you can also start with [Camunda-provided LLM](/components/agentic-orchestration/camunda-provided-llm.md) and run agents without setting up a provider account first. --- ## Run benchmarks Run your own benchmarks to validate [Camunda 8 sizing](./sizing-your-environment.md) for your specific workload. ## Reference benchmark scenario The sizing recommendations for [SaaS](sizing-saas.md) and [Self-Managed](sizing-self-managed.md) are based on a reference benchmark scenario. Your actual workload may differ significantly, so running your own benchmarks is the most reliable way to validate that your chosen configuration meets your needs. Camunda uses the following realistic benchmark scenario: - **Process model:** [bankCustomerComplaintDisputeHandling.bpmn](https://github.com/camunda/camunda/blob/main/load-tests/load-tester/src/main/resources/bpmn/realistic/bankCustomerComplaintDisputeHandling.bpmn) (a credit card fraud dispute handling process from the [Camunda Marketplace blueprint](https://marketplace.camunda.com/en-US/apps/449510/credit-card-fraud-dispute-handling)). - **Payload:** [realisticPayload.json](https://github.com/camunda/camunda/blob/main/load-tests/load-tester/src/main/resources/bpmn/realistic/realisticPayload.json) (~11 KB). - This setup produces approximately **101 tasks per second at 1 PI/s** due to internal sub-process instantiation (50 sub-process instances per root instance). :::note The official sizing numbers on this page are produced using the [load-tester](https://github.com/camunda/camunda/tree/main/load-tests/load-tester) tool from the Camunda monorepo. ::: ## Run your own benchmarks Use the [Camunda 8 Benchmark project (c8b)](https://github.com/camunda-community-hub/camunda-8-benchmark), a Spring Boot application, to run load tests against your cluster. ### Key features - Starts process instances at a configurable rate and **automatically adjusts based on backpressure**. - Completes tasks that appear in the process instances. - **Bring your own BPMN process model and payload**, which can be provided as URLs, such as GitHub Gists. - **Automatic job type discovery** from BPMN files. - Configurable **task completion delay** to simulate real worker behavior. - Built-in **Prometheus metrics and Grafana dashboards** for observability. ### Quick start Run the following command against your cluster: ```bash mvn spring-boot:run ``` With Docker: ```bash docker run camundacommunityhub/camunda-8-benchmark:main ``` Customize it with your own process and payload: ```bash benchmark.bpmnResource=url:https://your-gist-url/your-process.bpmn benchmark.payloadPath=url:https://your-gist-url/your-payload.json benchmark.processInstanceStartRate=25 benchmark.taskCompletionDelay=200 ``` :::important To run meaningful benchmarks, use a **properly sized environment**. SaaS trial clusters and local developer machines have limited resources and will hit bottlenecks too early. Use either a correctly sized Camunda SaaS cluster (with help from your Camunda representative) or a properly provisioned Self-Managed Kubernetes environment. ::: ## When to benchmark Running your own benchmarks when: - Your process models or payload sizes **differ significantly** from the reference scenario. - **Latency or cycle time requirements** are critical to your use case. - You are running Optimize with **payloads larger than the reference ~11 KB** or retention periods **exceeding 6 months**. Larger payloads and longer retention amplify Elasticsearch disk consumption and Optimize import times. - You are **upgrading from a pre-8.8 version** and want to validate resource requirements. - You are using **RDBMS (PostgreSQL) as secondary storage** and want to validate throughput differences. ## What to measure When running benchmarks, focus on these key metrics: - **Sustained throughput (tasks/second):** The rate your cluster can handle continuously without increasing backpressure. - **Backpressure rate:** Should remain below 10% for sustainable operation. - **Process instance latency (p99):** End-to-end time from instance creation to completion. Target depends on your SLO. - **Elasticsearch disk growth rate:** Helps you forecast disk capacity needs. - **Data availability latency:** The time between an event in the engine and its appearance in Operate/Tasklist. - Note: to measure this, you have to compare the time from starting an instance and its availability in query APIs using the Orchestration Cluster REST API - **CPU usage and throttling:** High CPU usage or frequent throttling indicates a need for more CPU resources or additional brokers. - **Memory usage:** Sustained high memory usage suggests the need for larger memory limits or additional nodes. --- ## Size clusters with Physical Tenants Self-Managed only Learn how to size broker memory, secondary storage, and noisy neighbor limits when you run multiple [Physical Tenants](/self-managed/concepts/physical-tenants/index.md) on one Orchestration Cluster. :::note This guide covers only what changes when you add tenants. Start from [Self-Managed resource planning](sizing-self-managed.md) for the baseline configuration, partitions, disk, RocksDB, and Elasticsearch/OpenSearch. Figures here are directional observations from internal Camunda 8.10 tests, not capacity guarantees. Your limit is whichever resource runs out first in your environment. ::: ## Understand the sizing model Tenants share brokers, gateways, and actor threads, but each tenant brings its own partitions, exporters, and secondary storage connections. The default tenant counts as a tenant in every formula on this page. ```mermaid graph TD subgraph broker["Each broker"] rocks["RocksDB pool\ngrows with partition replicas"] heap["Java heap\ngrows with tenant count"] subgraph tenants["Per tenant, on every broker"] pool["Connection pool"] meta["Database mapping metadata"] end end pool --> db["Secondary storage\ngrows with combined tenant load"] classDef shared fill:#e4eef8,stroke:#2272c9,color:#14082c classDef tenant fill:#fde8da,stroke:#fc5d0d,color:#14082c classDef storage fill:#e8fdf1,stroke:#10c95d,color:#14082c class rocks,heap shared class tenants,pool,meta tenant class db storage ``` | Resource | Scales with | | :------------------------------------------ | :--------------------------------------------------------------- | | Engine CPU, replication, and broker disk | Total partitions and throughput across all tenants | | RocksDB memory (native) | Partition replicas per broker, across all tenants | | Java heap (RDBMS) | Tenant count, on every broker, regardless of partition placement | | RDBMS connections | Brokers × tenants × pool size | | Secondary storage capacity | Combined load of all tenants that share an instance or cluster | | Elasticsearch/OpenSearch indices and shards | Tenants that share a cluster, each with its own index prefix | | CPU throttling and thread count | Tenant count, because each adds stream processors and exporters | To see which infrastructure tenants share, read [what is not isolated](/self-managed/concepts/physical-tenants/index.md#what-is-not-isolated). ## Size broker memory Size native RocksDB memory and Java heap separately. RocksDB memory grows with partition replicas per broker, and Java heap grows with the tenant count. ### Size RocksDB memory Each partition replica on a broker, leader or follower, needs at least 32 MiB of RocksDB memory, and by default all tenants' replicas share one pool. The broker checks this at startup for every allocation strategy. With the default `FRACTION` strategy: ```text broker memory × memory-fraction ≥ partition replicas on the broker × 32 MiB partition replicas on the broker = Σ over tenants (partitions × replication factor) / brokers ``` :::warning If the check fails, the broker doesn't start and logs `Expected the allocated memory for RocksDB per partition to be at least 33554432 bytes, but was bytes.` ::: For example, [baseline](sizing-self-managed.md#baseline-resource-configuration) brokers with 2 GiB and the default fraction of `0.1` have about 205 MiB, enough for six replicas. With three brokers and three partitions at replication factor three per tenant, each tenant adds three replicas per broker, so a third tenant stops the brokers from starting. To meet the minimum, raise broker memory, raise `camunda.data.primary-storage.rocksdb.memory-fraction`, change the RocksDB allocation strategy, or add brokers. A higher fraction leaves less memory for the heap and page cache, so raising broker memory is usually safer. See [memory](sizing-self-managed.md#memory) for how these budgets fit together. Disk is a separate budget, covered in [RocksDB](sizing-self-managed.md#rocksdb). ### Size Java heap With RDBMS secondary storage, every broker creates a connection pool and database mapping metadata for every tenant at startup. Heap usage grows with the tenant count, independent of load and broker count. If the heap is too small, brokers run out of memory during startup, so validate heap at your target tenant count. In an internal test, each tenant held about 4.4 MB of heap, about 700 MB across 160 tenants. The figure covers only the retained database mapping metadata, so treat it as a lower bound. The heap cost per tenant for Elasticsearch or OpenSearch hasn't been measured.
Heap test configuration Camunda 8.10 with 160 tenants plus the default tenant, one partition each, replication factor three. 20 brokers with 6 vCPU, 8 GiB memory, and a 2 GiB heap. PostgreSQL behind PgBouncer. Brokers ran out of memory at startup, before any load.
## Size secondary storage Secondary storage is usually the first bottleneck as you add tenants. Size a shared database instance or cluster for the combined load of all its tenants. Size a dedicated instance for its own tenant, using the general [secondary storage](sizing-your-environment.md#secondary-storage) guidance. ### Size RDBMS connections Each broker opens one HikariCP connection pool per tenant at startup, so connections scale with brokers and tenants, not partitions: ```text idle connections = brokers × tenants × minimum-idle maximum connections = brokers × tenants × maximum-pool-size ``` With the defaults (`maximum-pool-size` `10`, `minimum-idle` `2`), three brokers and four tenants can open 120 connections, above the PostgreSQL default `max_connections` of 100. Calculate this for each database instance, counting only its tenants, and either raise the database limit or add a connection pooler. You can override [pool properties](/self-managed/concepts/databases/relational-db/configuration.md#connection-pool-configuration) for each tenant. HikariCP metrics carry the `physicalTenant` tag. If data availability latency rises as you add tenants, check pool saturation before adding database capacity. If brokers fail to start or restart repeatedly with a database connection error, see [Database rejects new connections](/self-managed/concepts/physical-tenants/troubleshooting.md#database-rejects-new-connections-on-rdbms). #### Use a connection pooler Camunda doesn't ship or require a connection pooler. If you use one, such as PgBouncer, validate it with your own load and size its limits separately: | Limit | What it caps | Compare it against | | :---------------------------- | :---------------------------------------------- | :--------------------------------------- | | PgBouncer `max_client_conn` | Client connections from all brokers and tenants | Brokers × tenants × `maximum-pool-size` | | PgBouncer `default_pool_size` | Server connections per database and user pair | Concurrent queries the tenants need | | PostgreSQL `max_connections` | Backend connections on the database server | Sum of server connections from all pools | Camunda's tests used PgBouncer in transaction pooling mode with `prepareThreshold=0` on the JDBC URL. The PgBouncer defaults were too low: a `default_pool_size` of 20 limited throughput with 24 partitions, and a `max_client_conn` of 100 was exhausted at 120 tenants. #### Reduce table row count metric queries The RDBMS table row count metric queries the database for each tenant on every broker, every `camunda.data.secondary-storage.rdbms.metrics.table-row-count-cache-duration` (default `5m`). Camunda issues one query per tenant per broker at this interval. If CPU spikes still match this interval, increase the duration. #### Spread tenants across database instances Tenants that share one database compete for its capacity. If tenants run at high load together, give them separate [database instances](/self-managed/concepts/physical-tenants/storage-isolation.md#rdbms-storage). In an internal test with 10 tenants at maximum load, one shared instance reached 69% of the throughput of 10 single-tenant clusters, unevenly spread across tenants. Two instances with five tenants each reached 88%, evenly spread.
Database split test configuration 10 tenants, three partitions each, replication factor three, 30 brokers. PostgreSQL behind PgBouncer. Optimize disabled.
### Size Elasticsearch and OpenSearch Each tenant that shares an Elasticsearch or OpenSearch cluster has its own index prefix and indices. Size the cluster for: - Aggregate write load: The combined export load of all tenants, at their combined peak. - Shard budget: Every tenant adds indices. Count all tenants in the [shard budget](sizing-your-environment.md#elasticsearchopensearch-shard-budget). - Disk: The sum of each tenant's data volume under its own retention settings. For scale, an internal test split the [realistic baseline workload](sizing-self-managed.md#baseline-performance) across five tenants. The workload met the baseline targets, but broker CPU rose 24%, CPU throttling rose about eight times, and Elasticsearch held about 290 indices.
Elasticsearch test configuration Five tenants, three partitions each, replication factor three. Three brokers with 3 vCPU and 2 GiB, `memory-fraction` `0.3`. Three Elasticsearch nodes with 7 vCPU and 8 GiB. 40-minute window.
## Plan for noisy neighbors A busy tenant mainly costs tenants that share its brokers and secondary storage latency, not throughput. ### Understand the observed effects In an internal test on a prerelease 8.10 build, eight tenants shared three brokers (6 vCPU, 8 GiB) and PostgreSQL behind PgBouncer. Quiet tenants ran a typical workload at 6 process instances per second (PI/s) and kept that throughput while one tenant's load increased: | Noisy tenant offered load | Quiet tenants' request-response p99 | | :------------------------ | :---------------------------------- | | 6 PI/s (baseline) | 0.05–0.06 s | | 24 PI/s | 0.15–0.20 s | | 48 PI/s | 1.13–1.72 s | | 96 PI/s | 2.48–3.20 s | The noisy tenant's throughput leveled off at about 33–35 PI/s. At the highest load, quiet tenants' data availability p99 rose from about 1 s to 3.4–3.9 s. Latency returned to the baseline when the load dropped. Raising the noisy tenant from three to 12 partitions at 6 PI/s barely changed quiet tenants' p99 (about 50 ms to 54–66 ms). It used about 0.73 more broker CPU cores across the cluster and 2.2–2.5 GiB more disk per broker. ### Limit tenant load with flow control Use [flow control](/self-managed/operational-guides/configure-flow-control/configure-flow-control.md) to limit what a tenant can write: - Static configuration: Override `camunda.processing.flow-control.write.*` under `camunda.physical-tenants.`. - Runtime change: Call `POST actuator/flowControl?physicalTenant=`. Without the parameter, the change applies to every tenant. Runtime changes revert when the broker restarts. Flow control isn't a direct cap for each tenant: - The write limit applies to each partition, so a tenant's ceiling is roughly the limit times its partition count. - Throttling reacts to each partition's export backlog. In the 10-tenant PostgreSQL test described earlier, one slow shared database throttled tenants unevenly. - Limits count records, not bytes or CPU time. Large payloads or expensive processing can use more shared resources at the same record rate. Runaway loops and large multi-instance elements are bounded by write limits. Expressions are canceled after [`camunda.expression.timeout`](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#expression) (default `5s`) and raise an incident. ## Know the current limitations These limitations apply to Camunda 8.10.0: - Per-tenant RDBMS infrastructure on every broker: The Physical Tenant count is bounded by single-broker heap and database connections. Adding brokers doesn't add tenant capacity and increases total connections. See [camunda/camunda#61935](https://github.com/camunda/camunda/issues/61935). - Tested range: Sizing tests cover tens of Physical Tenants per cluster. Load test your own target if you plan for more. - No performance guarantee for each tenant: If one Physical Tenant needs high throughput or strict latency on its own, validate it with dedicated load tests or use a separate cluster. - Single secondary storage type: All tenants must use the same type. See [storage isolation](/self-managed/concepts/physical-tenants/storage-isolation.md#known-limitations). ## Validate your configuration Before production, check: - Representative load: Each tenant's real process models, payload sizes, and job workers. - Concurrent load: All tenants at peak together, and one tenant above its peak. - Database connections: HikariCP pending connections and timeouts by `physicalTenant`, pooler waiting clients, and database connections against `max_connections`. - Heap: Heap usage and GC time after startup with all tenants, before load. - CPU throttling: Container throttling, not only average CPU. - Latency: Request p99 and data availability p99 for each tenant. - Startup: A rolling restart and adding a tenant. The RocksDB check and heap exhaustion fail at startup, not under load. --- ## Size your SaaS cluster Select the right Camunda 8 SaaS cluster size based on your needs. For an overview of the factors that influence sizing, see [Size your environment](./sizing-your-environment.md). ## Determine your cluster size Camunda 8 defines four [cluster sizes](/components/saas/clusters.md#cluster-size) (1x, 2x, 3x, and 4x) you can select after choosing your [cluster type](/components/saas/clusters.md#cluster-type). To do so, follow these steps: 1. Calculate your throughput and storage requirements using the guidance in [Size your environment](./sizing-your-environment.md). 2. Use the [sizing tables](#sizing-tables) to find the cluster size that meets your needs. :::note To increase the cluster size beyond 4x, [reach out to Camunda](https://camunda.com/contact-us/). This requires custom sizing and pricing. ::: ### Sizing tables | Cluster size | 1x | 2x | 3x | 4x | | :--------------------------------------------------- | ------------------------------: | ------------------------------: | ------------------------------: | ------------------------------: | | Max Throughput **Tasks/day** **\*** | 9 M | 18 M | 27 M | 36 M | | Max Throughput **Tasks/second** **\*** | 100 | 200 | 300 | 400 | | Max Throughput **Process Instances/second** **\*\*** | 5 | 10 | 15 | 20 | | Max Total Number of PI stored (in ES) **\*\*\*** | 200 k | 400 k | 600 k | 800 k | | Approximate resources provisioned **\*\*\*\*** | 11 vCPU, 22 GB mem, 192 GB disk | 22 vCPU, 44 GB mem, 384 GB disk | 33 vCPU, 66 GB mem, 576 GB disk | 44 vCPU, 88 GB mem, 768 GB disk | :::note The numbers in the tables were measured using Camunda 8 (version 8.8), [the benchmark project](https://github.com/camunda-community-hub/camunda-8-benchmark) running on its own Kubernetes cluster, and using a [realistic process](https://github.com/camunda/camunda/blob/main/load-tests/load-tester/src/main/resources/bpmn/realistic/bankCustomerComplaintDisputeHandling.bpmn) with this [payload](https://github.com/camunda/camunda/blob/main/load-tests/load-tester/src/main/resources/bpmn/realistic/reducedPayload.json) (~1.4 KB). To calculate day-based metrics, an equal distribution over 24 hours is assumed. ::: **\*** Tasks (including service, send, and user tasks, among others) completed per day are the primary metric, as this is easy to measure and strongly influences resource consumption. This number assumes a constant load throughout the day. Tasks/day and Tasks/second are scaled linearly. **\*\*** Because tasks are the primary resource driver, the number of process instances supported by a cluster is calculated assuming an average of 10 tasks per process. As a customers, you can calculate a more accurate process instance estimate using your anticipated number of tasks per process. **\*\*\*** Maximum total number of historical process instances within the retention period. For active process instances, this is limited mostly by Zeebe resources; for historical instances, it is limited mostly by Elasticsearch resources. Calculated assuming a typical set of process variables per process instance. Note that it makes a difference whether you add one or two strings (requiring ~1 KB of space) to your process instances or attach a full JSON document containing 1 MB, as this data must be stored in various places, influencing memory and disk requirements. If this number increases, you can still retain the runtime throughput, but Tasklist, Operate, and/or Optimize may lag behind. The provisioned disk size is calculated as the sum of the disk size used by Zeebe and Elasticsearch. **\*\*\*\*** These are the resource limits configured in the Kubernetes cluster and are subject to change. ## Data retention The maximum throughput numbers should be considered peak loads, and the data retention configuration considered when defining the amount of data kept for completed instances in your cluster. See [Camunda 8 SaaS data retention](/components/saas/data-retention.md) for the default retention times for Zeebe, Tasklist, Operate, and Optimize. - If process instances are completed and older than the configured retention time for an application, the data is removed. - If a process instance is older than the configured retention time but still active and incomplete, it continues to function at runtime and is _not_ removed. Camunda can adjust data retention on request (up to certain limits). Consider retention time adjustments and/or storage capacity increases if you plan to run more than \[max PI stored in ES\] / \[configured retention time\]. :::note Why is the total number of process instances stored that low? This is related to the limited resources provided to Elasticsearch, which can cause performance problems when too much data is stored there. By increasing the available memory for Elasticsearch, you can also increase that number. At the same time, even with this rather low number, you can always guarantee the throughput of the core workflow engine during peak loads, as this performance is not affected. You can also increase memory for Elasticsearch later if needed. ::: ## Next steps Validate your chosen configuration by [running your own benchmarks](sizing-benchmarks.md). --- ## Self-Managed resource planning Provisioning Camunda 8 on your Self-Managed cluster depends on several factors. Use [Kubernetes with Helm](/self-managed/deployment/helm/index.md) to deploy and manage your Self-Managed cluster. Use the configurations and guidance below as a baseline, then adjust based on your workload. For background on the factors that drive provisioning requirements, see [Size your environment](sizing-your-environment.md). ## Camunda 8.8+ resource consumption Camunda 8.8 introduced a streamlined architecture that consolidates the broker, gateway, Operate, Tasklist, and Identity into a single application, the [Orchestration Cluster](/components/orchestration-cluster.md). This changes how you think about resource consumption compared to older versions. If you are upgrading from a pre-8.8 version, expect different resource profiles: - The Orchestration Cluster requires **more CPU per broker** compared to 8.7 (approximately 75% more CPU, for example, 2 to 3.5 cores, to maintain equivalent throughput). - Throughput at the default 2 CPU cores drops ~35% compared to 8.7.x. - With properly aligned resources (3.5 CPU cores), 8.8.x achieves similar throughput to 8.7.x with **significantly lower latency** (approximately a 2x improvement). - The streamlined architecture reduces operational complexity (fewer pods to manage) but consolidates resource consumption into fewer, larger pods. All components are clustered to provide high-availability, fault-tolerance, and resilience. The Orchestration Cluster scales horizontally by adding more nodes (pods). This is limited by the [number of partitions](/components/zeebe/technical-concepts/partitions.md) configured for a cluster, as the work within one partition cannot be parallelized by design. Hence, you need to define enough partitions to utilize your hardware. The [number of partitions can be scaled up](/self-managed/components/orchestration-cluster/zeebe/operations/cluster-scaling.md) after the cluster is initially provisioned, but not yet scaled down. Camunda 8 runs on Kubernetes. Every component runs as a pod with assigned resources. These resources can be scaled vertically (assigned more or fewer resources dynamically) within certain limits. Vertical scaling does not always increase throughput, since the components depend on each other. :::note Camunda licensing does not depend on the provisioned hardware resources, making it easy to size according to your needs. ::: ## Baseline performance With this [baseline resource configuration](#baseline-resource-configuration), you can expect the following **sustained** performance. This is a reliability target that the configuration has consistently demonstrated it can maintain, not the hardware’s absolute limit. For the maximum throughput this configuration can achieve under stress using a much simpler process, see the `max`/stress variant in [How we test](#how-we-test). | Metric | Value | | ----------------------------------------------- | ---------------------------------------------- | | Completed process instances per second | 51 (includes root and child process instances) | | Completed flow node instances (FNIs) per second | 560 | | Completed tasks per second | 100 | | Data availability (query API latency) | < 5 seconds | :::important These numbers reflect Camunda 8.10, measured using Camunda's [load test application](https://github.com/camunda/camunda/tree/main/load-tests/load-tester) with a [realistic reference process](https://github.com/camunda/camunda/blob/main/load-tests/load-tester/src/main/resources/bpmn/realistic/bankCustomerComplaintDisputeHandling.bpmn) and [realistic payload](https://github.com/camunda/camunda/blob/main/load-tests/load-tester/src/main/resources/bpmn/realistic/realisticPayload.json) (~11 KB). For details on the testing methodology, see the [reliability testing documentation](https://github.com/camunda/camunda/blob/main/docs/testing/reliability-testing.md). ::: The realistic reference process starts one root process instance, which spawns 50 sub-process instances via call activities. It covers a wide variety of BPMN elements, including call activities, multi-instance, sub-processes, and DMN. The process is based on the [Credit Card Fraud Dispute Handling](https://marketplace.camunda.com/en-US/apps/449510/credit-card-fraud-dispute-handling) blueprint from the Camunda Marketplace. ## How we test Camunda runs these load tests as part of its **reliability testing** practices. The goal is to detect performance regressions, memory leaks, and configuration issues before they affect customers, and to confirm that the system performs within its expected bounds over extended periods of continuous operation—not to produce a one-off benchmark for this page. The numbers presented here reflect a configuration that Camunda has repeatedly demonstrated can sustain this load reliably. These tests run on a dedicated Kubernetes cluster, using the same [load-tester](https://github.com/camunda/camunda/tree/main/load-tests/load-tester) application and [Helm-based setup](https://github.com/camunda/camunda/blob/main/load-tests/README.md) used to validate every release before it ships. Reliability testing focuses on two test types, run against `main` and every supported `stable/*` branch: | Test type | Process model | PI/s target | FNI/s target | Purpose | | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | -----------------------------------: | -----------: | ------------------------------------------------------------------------------------- | | **[Weekly (endurance)](https://github.com/camunda/camunda/blob/main/load-tests/README.md#weekly-load-tests-endurance-test)** | Credit card fraud dispute process (call activities, multi-instance, DMN) | 1 root PIs (50 sub PIs)\* | 560 | Validates sustained reliability over four weeks (used for the numbers on this page) | | [Daily (stress)](https://github.com/camunda/camunda/blob/main/load-tests/README.md#daily-load-tests-stress-test) | Single service task | 300 | 900 | Puts the system under stress and finds the throughput ceiling in a bounded 3-hour run | \* One root process instance per second fans out into 50 sub-process instances via call activities, so completed PI/s includes both. The endurance run supports the numbers on this page: - A new instance is created every Monday for each variant and runs for four weeks. - A configuration is considered validated only after sustaining a continuous, production-like load—not merely a short burst. - The stress run answers a different question: how far can the system be pushed? Its ceiling of 300 PI/s is not a target for capacity planning. See [reliability testing](https://github.com/camunda/camunda/blob/main/docs/testing/reliability-testing.md) for the full test-type taxonomy and [load test metrics](https://github.com/camunda/camunda/blob/main/load-tests/docs/metrics.md) for how a run is judged healthy. ## Baseline resource configuration The following table lists the Helm values for Elasticsearch or OpenSearch with Optimize. Optimize is the main driver of Elasticsearch and OpenSearch sizing in this configuration because its importer reads from and writes to the secondary storage indices in addition to the primary export traffic. See [impact of Optimize](sizing-your-environment.md#impact-of-optimize) for a breakdown of the resource requirements. These are the exact Helm values that Camunda uses in its continuous realistic-load tests with Optimize enabled (see [how we test](#how-we-test)). The values are sourced from [`camunda-platform-values-defaults.yaml`](https://github.com/camunda/camunda/blob/main/load-tests/setup/main/values/camunda-platform-values-defaults.yaml) for the Orchestration Cluster and [`load-test-setup/values.yaml`](https://github.com/camunda/camunda/blob/main/load-tests/setup/charts/load-test-setup/values.yaml) for Elasticsearch and OpenSearch, which use identical resources. | Component | | Request | Limit | | ------------------------------ | ------------------- | ------: | ----: | | **Orchestration Cluster** | | | | | Brokers | 3 | | | | Partitions | 3 | | | | Replication factor | 3 | | | | | vCPU \[cores\] | 3 | 3 | | | Memory \[GB\] | 2 | 2 | | | Disk \[GB\] | | 64 | | | Disk IOPS | | 4,000 | | **Connectors** | | | | | # | 1 | | | | | vCPU \[cores\] | 0.2 | 0.2 | | | Memory limit \[GB\] | 0.512 | 1 | | **Identity** | | | | | # | 1 | | | | | vCPU \[cores\] | 0.6 | 2 | | | Memory limit \[GB\] | 0.4 | 2 | | **Keycloak** | | | | | # | 1 | | | | | vCPU \[cores\] | 1 | 2 | | | Memory limit \[GB\] | 1 | 2 | | **Optimize** | | | | | # | 1 | | | | | vCPU \[cores\] | 0.6 | 2 | | | Memory limit \[GB\] | 1 | 2 | | **Elasticsearch / OpenSearch** | | | | | #statefulset | 3 | | | | | vCPU \[cores\] | 7 | 7 | | | Memory limit \[GB\] | 8 | 8 | | | Disk request \[GB\] | | 256 | :::note If you don't use Optimize, omit the Optimize row above and size the secondary storage accordingly. Enabling Optimize roughly triples or quadruples Elasticsearch CPU and disk usage under a realistic workload. OpenSearch is expected to see a similar impact, but it hasn't been measured separately. See [impact of Optimize](sizing-your-environment.md#impact-of-optimize) for the full breakdown. Use this ratio as a starting point for sizing a smaller Elasticsearch deployment, review the available [Elasticsearch scaling](#elasticsearch-scaling) options, and validate any reduced configuration with [your own benchmarks](sizing-benchmarks.md). ::: RDBMS (PostgreSQL) is available from Camunda 8.9. See [secondary storage considerations](#secondary-storage-considerations) for how its scaling and write throughput compare to Elasticsearch/OpenSearch. Optimize does not support RDBMS as a data source, so running Optimize alongside RDBMS requires a separate Elasticsearch or OpenSearch cluster. The following configuration contains the exact Helm values that Camunda uses in its continuous realistic-load tests against PostgreSQL, including Optimize (see [how we test](#how-we-test)). The values are sourced from [`camunda-platform-values-defaults.yaml`](https://github.com/camunda/camunda/blob/main/load-tests/setup/main/values/camunda-platform-values-defaults.yaml) for the Orchestration Cluster and [`load-test-setup/values.yaml`](https://github.com/camunda/camunda/blob/main/load-tests/setup/charts/load-test-setup/values.yaml) for PostgreSQL and Elasticsearch. If you don't use Optimize, omit the Optimize and Elasticsearch rows entirely. | Component | | Request | Limit | | ---------------------------- | ------------------- | ------: | ----: | | **Orchestration Cluster** | | | | | Brokers | 3 | | | | Partitions | 3 | | | | Replication factor | 3 | | | | | vCPU \[cores\] | 3 | 3 | | | Memory \[GB\] | 2 | 2 | | | Disk \[GB\] | | 64 | | | Disk IOPS | | 4,000 | | **Connectors** | | | | | # | 1 | | | | | vCPU \[cores\] | 0.2 | 0.2 | | | Memory limit \[GB\] | 0.512 | 1 | | **Identity** | | | | | # | 1 | | | | | vCPU \[cores\] | 0.6 | 2 | | | Memory limit \[GB\] | 0.4 | 2 | | **Keycloak** | | | | | # | 1 | | | | | vCPU \[cores\] | 1 | 2 | | | Memory limit \[GB\] | 1 | 2 | | **PostgreSQL** | | | | | | vCPU \[cores\] | 3 | 3 | | | Memory limit \[GB\] | 8 | 12 | | | Disk request \[GB\] | | 256 | | **Optimize** | | | | | # | 1 | | | | | vCPU \[cores\] | 0.6 | 2 | | | Memory limit \[GB\] | 1 | 2 | | **Elasticsearch (Optimize)** | | | | | #statefulset | 3 | | | | | vCPU \[cores\] | 7 | 7 | | | Memory limit \[GB\] | 8 | 8 | | | Disk request \[GB\] | | 256 | :::note In this configuration, Elasticsearch only serves Optimize because PostgreSQL handles all primary Camunda data, but its resources are not reduced for that. The Elasticsearch row above is the same sizing as the Elasticsearch-only baseline, which covers primary export traffic and Optimize. A smaller Elasticsearch instance may be sufficient, but we haven't tested this. Treat the row above as a safe but untested starting point if you need Optimize with an RDBMS, and validate any reduction with [your own benchmarks](sizing-benchmarks.md). ::: Identity and Keycloak, including Keycloak's bundled PostgreSQL database, which is not itemized here, are included in each table above because our test harness always authenticates through OIDC. Most Self-Managed deployments connect Identity to an external identity provider instead, in which case you can omit these rows entirely. If you deploy Identity and Keycloak together, the rows above show the load-test configuration for them. In our sizing, retention is set to one day for the Camunda Exporter and three days for the legacy exporter, where applicable. This gives the Optimize importer enough time to catch up before the data is removed. See [Elasticsearch scaling](#elasticsearch-scaling) for information about how retention affects disk sizing. The daily metrics above assume that the load is distributed evenly over 24 hours. ## Primary storage Primary storage must use low-latency **SSDs**, as HDD-backed volumes are not supported. Disk **latency**, rather than throughput, is the critical metric. Cloud providers often report similar throughput figures for HDD and SSD volumes, but the difference in latency is what matters for Camunda. In testing, HDD-backed primary storage reduced throughput by approximately 50% compared with SSDs, increased commit latency, and triggered additional Raft snapshot replication between brokers. See [Command processing path](data-flow.md#command-processing-path) for the architectural context on why disk latency sits on the critical path, the [reference architecture minimum cluster requirements](/self-managed/reference-architecture/kubernetes.md#minimum-cluster-requirements) for concrete per-platform disk recommendations, and the [slow disk chaos day experiment](https://camunda.github.io/zeebe-chaos/2026/06/19/Using-slow-disk-with-Camunda) for the detailed findings. ### Disk IOPS Camunda is IOPS-heavy: every broker flushes the log to disk as part of Raft commits, so each broker's volume needs enough sustained IOPS, in addition to the latency requirements in [supported environments](/reference/supported-environments.md#performance). The IOPS a broker needs depends on your workload and scales with the number of partitions it hosts. A cluster with a low load and few partitions can run with far fewer IOPS. - **Recommended:** provision **4,000 sustained IOPS** per broker for production workloads comparable to the configuration in [Baseline resource configuration](#baseline-resource-configuration), where each broker hosts three partitions. This leaves headroom over what we measured in our load tests, so short bursts do not throttle the volume. Measure your own workload. - **Disk bandwidth:** bandwidth (MiB/s) was not a limiting factor. Brokers write many small blocks, so the volume's IOPS limit is reached long before its bandwidth limit, and IOPS is the figure to size for. - **Smaller clusters:** clusters with a lower load or fewer partitions per broker need fewer IOPS and can use smaller disks, provided the volume still sustains the IOPS your workload requires. :::warning Many cloud providers scale volume IOPS with disk capacity, or offer burstable volume types that rely on burst credits. A small or burstable volume can run fine until its credits are exhausted, then throttle and cause higher commit latency, backpressure, and lower throughput. Before choosing or shrinking a disk, check the **sustained** (not burst) IOPS the provider guarantees for that size, and size the volume to meet your target IOPS without relying on burst credits. ::: Elasticsearch IOPS was not a limiting factor in our tests. See [Elasticsearch scaling](#elasticsearch-scaling) for secondary storage sizing. ### Disk space All brokers in a partition use disk space to store: - The event log for each partition in which they participate. By default, the event log has a minimum size of 128 MB per partition and grows in 128 MB segments. It is truncated once its data has been processed and successfully exported by all loaded exporters. - A periodic snapshot of the running state (in-flight data) of each partition. Its size is unbounded and depends on the amount of in-flight work. Every partition instance hosted by a broker, whether a leader or follower, also uses disk space to store a projection of the partition’s running state in RocksDB. Its size is unbounded and depends on the amount of in-flight work. See [RocksDB](#rocksdb) below to learn how leaders and followers build this state differently. Use the following formula as a starting point for estimating the required disk space: ``` neededDiskSpace = replicatedState + localState replicatedState = totalEventLogSize + totalSnapshotSize totalEventLogSize = followerPartitionsPerNode * eventLogSize * reserveForPartialSystemFailure totalSnapshotSize = partitionsPerNode * singleSnapshotSize * 2 // singleSnapshotSize * 2: // the last snapshot (already replicated) + // the next snapshot (in transit, while it is being replicated) partitionsPerNode = leaderPartitionsPerNode + followerPartitionsPerNode leaderPartitionsPerNode = partitionsCount / numberOfNodes followerPartitionsPerNode = partitionsCount * replicationFactor / numberOfNodes clusterSize = [number of broker nodes] partitionsCount = [number of partitions] replicationFactor = [number of replicas per partition] reserveForPartialSystemFailure = [factor to account for partial system failure] singleSnapshotSize = [size of a single RocksDB snapshot] eventLogSize = [event log size for duration of snapshotPeriod] ``` - `eventLogSize` scales with the throughput of your system. - `totalSnapshotSize` scales with the number of in-flight process instances. - `reserveForPartialSystemFailure` is a reserve to account for partial system failure, such as loss of quorum inside the cluster or loss of connection to an external system. See [effects on disk growth](#effects-on-disk-growth) below. The relevant configuration settings are: ```yaml camunda: cluster: partition-count: 1 replication-factor: 1 size: 1 data: snapshot-period: 5m primary-storage: log-stream: log-segment-size: 128MB ``` | Environment variable | Default | | ------------------------------------------------------ | ------- | | `CAMUNDA_DATA_PRIMARYSTORAGE_LOGSTREAM_LOGSEGMENTSIZE` | `128MB` | | `CAMUNDA_DATA_SNAPSHOTPERIOD` | `5m` | | `CAMUNDA_CLUSTER_PARTITIONCOUNT` | `1` | | `CAMUNDA_CLUSTER_REPLICATIONFACTOR` | `1` | | `CAMUNDA_CLUSTER_SIZE` | `1` | Other factors are best observed in a production-like system under representative throughput. By default, this data is stored in the following directories: - `segments`: The append-only log, split into segments. Data can be deleted once it becomes part of a new snapshot. - `state`: The active state (deployed processes, active process instances, and so on). Completed process instances or jobs are removed. - `snapshot`: A state at a certain point in time. :::caution Avoid unbounded log growth Do not configure an exporter that does not advance its record position, such as the Debug Exporter. If you configure an exporter, monitor its availability and the health of its dependencies. An exporter that stops advancing prevents log truncation, causing data to accumulate on disk until the issue is resolved. See [effects on disk growth](#effects-on-disk-growth). ::: ### Event log The event log for each partition is segmented. By default, the segment size is 128 MB. The event log grows over time unless and until individual segments are deleted. An event log segment can be deleted once: - All the events it contains have been processed by exporters. - All the events it contains have been replicated to other brokers. - All the events it contains have been processed. The following conditions inhibit automatic deletion: - The cluster loses quorum. Events are queued but not processed until quorum is reestablished. - An exporter does not advance its read position. The event log grows without bound. Exporting occurs only on the partition leader. Followers do not delete their replicas of a segment until the leader marks the segment as no longer needed by exporters. A segment is not deleted until a snapshot that includes it has been taken, and only log entries up to that snapshot can be deleted. ### Snapshots The running state of a partition is captured periodically on each broker. By default, a snapshot is taken every five minutes, as configured by `snapshot-period`. A snapshot is a projection of all events that represent the current running state, including deployed processes, active process instances, and jobs that have not yet been completed. Writing a new snapshot deletes all log data written before the snapshot. :::note The snapshot interval was tested in a Zeebe Chaos experiment. Learn more in the [Zeebe Chaos blog](https://camunda.github.io/zeebe-chaos/2022/02/01/High-Snapshot-Frequency/#snapshot-interval). ::: ### RocksDB The leader of a partition processes commands and applies committed events to its RocksDB state. Followers continuously replay the same committed events into their local RocksDB state without processing commands, keeping them warm and ready for fast failover if the leader changes. In practice, the RocksDB state of a partition grows to around 2 GB under heavy load with long-running processes. Snapshot replication brings new or lagging followers fully up to date; it is not how followers normally maintain their state. ### Effects on disk growth **Exporter or external system failure.** If a system an exporter depends on fails (for example, a lost connection to Elasticsearch), the exporter stops advancing its position and brokers can't truncate their logs. The log grows until the connection is restored. Size broker disks with enough headroom to keep operating through an outage. During a [hot backup (soft-pause window)](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#soft-pause-exports), log compaction is intentionally blocked for the backup's duration. This adds a predictable, temporary disk requirement: roughly `throughput × backup_window_duration` of extra log data per partition, replicated across followers. Size disks with headroom for at least one full backup window on top of steady-state estimates. **Node failure.** Only the leader exports events, and only committed (replicated) events are passed to exporters. An exporter's read position is only captured in snapshots, never in the event log itself; it can't be reconstructed from the log alone. When a partition fails over to a new leader, the new leader reconstructs state by projecting the log from the last snapshot, but the exporter position resets to that snapshot too. This means an exporter can see the same events twice after a failover. Assign idempotent IDs in your exporter (the combination of record position and partition ID is a reliable unique key) if this matters for your system. **Quorum loss.** If a partition drops below quorum (for example, two nodes down in a three-node cluster), the leader keeps accepting requests, but they aren't replicated or committed, so they can't be truncated, and the event log grows. The disk space needed to ride this out is a function of broker throughput and how long it takes to restore quorum; size nodes with enough headroom to absorb this failure mode. ## Memory Memory usage is determined by the Java heap size (by default, [25% of the maximum RAM](https://docs.oracle.com/en/java/javase/21/gctuning/ergonomics.html#GUID-DA88B6A6-AF89-4423-95A6-BBCBD9FAE781)) and native memory usage (also 25% by default); the JVM can use up to 50% of available RAM. Zeebe supports three RocksDB memory allocation strategies, configured using `CAMUNDA_DATA_PRIMARYSTORAGE_ROCKSDB_MEMORYALLOCATIONSTRATEGY`: - **`FRACTION`** (the default starting with 8.10): Total RocksDB memory is calculated as `..._MEMORYFRACTION` (default: `0.1`, or 10%) of the broker’s total system memory and is shared across all partitions on the broker. - **`PARTITION`** (the default before 8.10): Total RocksDB memory is calculated by multiplying the number of partitions on the broker by `CAMUNDA_DATA_PRIMARYSTORAGE_ROCKSDB_MEMORYLIMIT` (default: 512 MB). - **`BROKER`**: Total RocksDB memory is equal to `..._MEMORYLIMIT` and is shared across all partitions on the broker, regardless of the number of partitions. When hardcoding memory values using `PARTITION` or `BROKER`, consider the following: - Zeebe relies heavily on memory-mapped files, so sufficient OS page cache is required. Insufficient page cache degrades I/O performance. - Reserve 20-30% of total memory for the OS page cache as a starting point, adjusting based on observed performance. The right amount depends on partition count and system throughput. The minimum memory usage (using the `PARTITION` strategy) is: | Component | Amount | | ------------------ | ------------------------: | | Java heap | 25% | | Java native memory | 25% | | RocksDB | 512 MB × partition count | | OS page cache | 20-30% | | **Sum** | **x MB + 50% of max RAM** | When using `FRACTION`, replace the RocksDB row with `memory-fraction × total memory` (10% by default) instead. ### Keep the default `FRACTION` strategy for primary storage As of 8.10, `FRACTION` is the shipped default for primary storage: no action is required to benefit from it on a new deployment. `FRACTION` scales with the broker's actual memory automatically, unlike `PARTITION` and `BROKER`, which are absolute limits: if you resize a broker's memory or change its partition count, you have to remember to retune the limit too, or RocksDB's share of memory silently stays where it was. This also mirrors the direction Camunda SaaS already takes for primary storage. If you're upgrading from a version before 8.10 and previously relied on the `PARTITION` default, your effective RocksDB memory allocation changes on upgrade unless you set the strategy explicitly. See the [8.10 release notes](/reference/announcements-release-notes/8100/8100-release-notes.md#default-rocksdb-memory-allocation-strategy-changed-to-fraction) for details. ```yaml camunda: data: primary-storage: rocks-db: memory-allocation-strategy: fraction memory-fraction: 0.1 ``` :::caution `FRACTION` splits its budget across **all** partitions on a broker, the same way `BROKER` does. Unlike `PARTITION`, it does not scale up with partition count. On a broker with many partitions but modest total memory, a flat 10% fraction can allocate less RocksDB memory than a previously tuned fixed limit would have. An optional minimum-floor setting for `FRACTION` is proposed in [camunda/camunda#57768](https://github.com/camunda/camunda/issues/57768) (open) to address exactly this; until it ships, verify the resulting absolute memory is enough for your partition count, and fall back to an explicit `..._MEMORYLIMIT` if it isn't. ::: If you run multiple Physical Tenants, every tenant's partitions count toward the partitions on a broker, and each partition needs a minimum share of RocksDB memory. See [size clusters with Physical Tenants](sizing-physical-tenants.md#size-rocksdb-memory) for the minimum and the other budgets that grow with tenant count. ## Scale your cluster Once you have a baseline configuration running, you can scale in several ways: ### Horizontal scaling Add more brokers and partitions to increase throughput capacity. Partitions can be [scaled up](/self-managed/components/orchestration-cluster/zeebe/operations/cluster-scaling.md) but not down, so avoid over-provisioning. When scaling horizontally, **secondary storage often becomes the limiting factor**. Adding brokers increases export volume to Elasticsearch/OpenSearch. If secondary storage isn't scaled accordingly, it will bottleneck overall throughput. See [Elasticsearch scaling](#elasticsearch-scaling) for tuning options, and [secondary storage considerations](#secondary-storage-considerations) for a sharding limitation that adding nodes alone doesn’t resolve. ### Vertical scaling Increase CPU and memory per broker. Note that there are **diminishing returns** due to component interdependencies. For example, Elasticsearch indexing speed can bottleneck broker throughput. ### Elasticsearch scaling - **Memory:** Increase Elasticsearch memory to store more historical data without performance degradation. - **Nodes:** Add Elasticsearch statefulset replicas for more IOPS and query throughput. - **Disk size:** Increase disk size based on your data retention requirements. Our tests use a short retention period, one day for the Camunda Exporter, to prevent test clusters from filling up. With Optimize enabled and a realistic payload of approximately 11 KB, even a 128 GiB disk can fill up in under 12 hours at 1 PI/s if you configure a longer retention period, such as 30 days. - **Disk type:** Use SSDs for Elasticsearch storage. Disk latency, not throughput, is the critical factor. HDD-backed Elasticsearch has been observed to cause 8-10s flush durations, a growing export backlog, increased broker memory from in-flight records, and up to ~70% throughput degradation versus an equivalent SSD setup. See the [slow disk chaos day experiment](https://camunda.github.io/zeebe-chaos/2026/06/19/Using-slow-disk-with-Camunda) for details, and [Export pipeline](data-flow.md#export-pipeline) for background on how slow secondary storage affects overall throughput. - **Index replicas:** The disk estimates in the baseline tables above do not account for index-level replicas. In multi-node clusters, configure at least one replica per index for fault tolerance: each replica stores a full copy of the primary shard data, approximately doubling total disk usage. See [managing replicas](/self-managed/concepts/secondary-storage/managing-secondary-storage.md#replicas). ## Secondary storage considerations The [baseline resource configuration](#baseline-resource-configuration) above provides a sizing table for each secondary storage backend: Elasticsearch, OpenSearch, and RDBMS (PostgreSQL). [Dynamically scaling partitions](/self-managed/components/orchestration-cluster/zeebe/operations/cluster-scaling.md) scales the primary storage, but not the secondary storage, so manual steps are necessary: - When using Elasticsearch or OpenSearch: - All Zeebe partitions export data to the same indices. - Write throughput for an index scales with the number of primary shards, not the number of nodes. Reassess the shard count for your indices when scaling the number of partitions. See the [Elasticsearch shards documentation](/self-managed/concepts/secondary-storage/managing-secondary-storage.md#shards). - When using RDBMS (PostgreSQL): - The RDBMS schema has no table partitioning, and each broker opens a connection pool per physical tenant, unlike Elasticsearch/OpenSearch, which scale write throughput by adding primary shards across nodes. - RDBMS scales primarily **vertically** (a larger instance) rather than horizontally. Plan your initial sizing with more headroom, as adding capacity later is more disruptive. - RDBMS write throughput was measured at roughly 70% of Elasticsearch/OpenSearch write performance across tested scenarios; see [RDBMS benchmarking results](/self-managed/concepts/secondary-storage/rdbms-benchmark-results.md) for the full methodology and caveats. See [Secondary storage](sizing-your-environment.md#secondary-storage) for more details. ## Custom exporter considerations Custom exporters share each partition’s export capacity with the built-in exporters. Test custom exporters under a realistic, steady load before you use them in production. See [How we test](#how-we-test) for how Camunda tests, and [Run benchmarks](sizing-benchmarks.md) to run your own. - Compare the average export time of your exporter with the built-in exporters. See [performance impact of exporters](/self-managed/concepts/exporters.md#performance-impact) for the metrics. - Check that the acknowledged position of your exporter keeps advancing while the load runs. If it falls further and further behind, the exporter does not keep up and the broker delays log compaction. - Measure how long data takes to become available in the target system of your exporter, and watch that system for rejections and timeouts. Larger batches and longer flush delays increase the time to availability, and batches that are too large can overwhelm the target. Increase the batch size step by step to find the best value for your system. Keep the `export()` method fast and batch writes to the target system. ## Next steps Validate your chosen configuration by [running your own benchmarks](sizing-benchmarks.md). --- ## Size your environment Understand the aspects relevant to Camunda 8 sizing. Once you do, use the sizing recommendations for [SaaS](sizing-saas.md) or [Self-Managed](sizing-self-managed.md) to select your appropriate configuration. :::tip Before you size See [Data flow](data-flow.md) first to understand the factors that drive the recommendations on this page. ::: ## Sizing requirements and influencing factors Consider the following aspects when planning and sizing Camunda SaaS or Self-Managed. ### Data availability latency Data availability latency is the time between an event occurring in the engine and it being queryable in Operate, Tasklist, or Optimize. Under heavy load or with Optimize enabled, this can lag from seconds to minutes. Data availability latency is influenced by: - **Exporter throughput:** The rate at which the Camunda Exporter can write events to Elasticsearch (ES). - **Elasticsearch indexing speed:** How quickly ES can index incoming documents. - **Elasticsearch disk usage:** High disk utilization (above ~70%) significantly increases indexing latency. Monitor ES disk usage and scale storage before hitting this threshold. ### Disk space The workflow engine stores data for each process instance, especially to persist the current state. In addition, it sends data to secondary storage (Elasticsearch, OpenSearch, or an RDBMS) for indexing, search, analytics, and long-term retention. You can configure retention times for data stored in secondary storage. For Self-Managed, see [Disk space](sizing-self-managed.md#disk-space) for the formula and mechanics behind Zeebe's primary storage disk usage. ### Impact of Optimize Optimize is an optional component that provides process analytics and reporting. When enabled, it has significant implications for sizing. :::note The data below comes from Camunda 8.9 load tests. Because 8.8 and 8.9 share the same exporter architecture, it applies to 8.8+ as well. ::: #### In short - Enabling Optimize roughly **triples to quadruples Elasticsearch CPU and disk usage** at a [realistic workload](./sizing-benchmarks.md#reference-benchmark-scenario) (around 3.4x CPU and 3.6x disk), largely independent of throughput. - It lowers achievable **processing throughput by 25-50% at maximum load** on the same hardware. - The single most effective mitigation is to **keep variables out of Optimize**. This recovers around 60% of the storage and 65% of the CPU, plus most of the lost throughput, at the cost of variable-based analytics. - Size Elasticsearch/OpenSearch accordingly (CPU, disk, **and shard budget**), or run Optimize on a **dedicated Elasticsearch/OpenSearch instance**. For how Optimize fits into the export pipeline, see [Optimize data flow](./data-flow.md#optimize-data-flow). The full studies behind these numbers are [Impact of Optimize on Camunda](https://camunda.github.io/zeebe-chaos/2026/06/10/Impact-of-Optimize-on-Camunda) and [Reducing Optimize's Elasticsearch overhead](https://camunda.github.io/zeebe-chaos/2026/06/25/Impact-of-Optimize-Variable-Filtering). #### Why Optimize matters for sizing - Optimize is a second-tier consumer of the export pipeline: the Elasticsearch/OpenSearch exporter writes raw engine events, Optimize's importer reads them and writes its own analytics indices back to Elasticsearch/OpenSearch, so data is written to secondary storage twice. See [Optimize data flow](./data-flow.md#optimize-data-flow). - In Camunda 8.8+, the Camunda Exporter and the Elasticsearch exporter run in the same thread within the broker, so Optimize data-pipeline competes directly with core platform exporting for throughput. - The overhead is **not proportional to throughput.** It scales with process model complexity (multi-instance and call activities) and variable volume. At a realistic workload where Optimize-enabled and Optimize-disabled clusters reached identical throughput with zero backpressure, the Optimize-enabled cluster still consumed **around 3.4x more Elasticsearch CPU.** Budget for this even at comfortable throughput. #### What Optimize affects At a [realistic workload](./sizing-benchmarks.md#reference-benchmark-scenario), with Optimize enabled vs. disabled: - **Elasticsearch CPU:** around 3.4x higher. - **Elasticsearch disk:** around 3.6x more total data. - **Throughput:** unaffected at a realistic workload, but 25-50% lower at maximum load on the same hardware. - **Write-to-exporting latency:** around 2.6x higher. - **Backpressure at maximum load:** around 45% with Optimize vs. 35% without. - **Individual import latency:** increases approximately linearly with payload size. - **Report loading times:** increase approximately linearly with the data complexity (such as process instances and variables) and as historical data accumulates. Secondary storage memory is not a meaningful differentiator for improving performance. :::tip **Watch Optimize import lag.** When Optimize's importer falls behind the export rate, two problems can appear: - **Optimize's analytics indices grow.** Optimize keeps one document per process instance and can only apply retention-based cleanup once its importer has processed the instance's completion. While the importer lags, completions are recorded late, cleanup is deferred, and Optimize's own indices grow beyond their steady-state size. - **Data can be missed.** The raw exporter indices are cleaned up on the Elasticsearch/OpenSearch retention schedule. If the importer falls far enough behind, those records are deleted before Optimize imports them, and that data never reaches Optimize. This Exporter-Importer hazard is exactly what the 8.8 Camunda Exporter architecture removed for Operate and Tasklist. Track import progress with the [Optimize metrics and bundled Grafana dashboards](/self-managed/operational-guides/monitoring/metrics.md). If you see persistent import lag, raise the import throughput (see [mitigations](#mitigations) for details). ::: #### Mitigations ##### Keep variables out of Optimize (highest impact, lowest risk) Variables account for most of Optimize's storage and CPU usage in the secondary storage layer. In benchmarks, disabling Optimize's variable storage reduced its disk usage by a factor of **approximately 14** relative to the raw export. Isolating a group of customer-related variables showed an even larger difference, a factor of **approximately 29**, driven primarily by object variable flattening, described below, rather than by the variable values themselves. See [Optimize data flow](./data-flow.md#optimize-data-flow) for an explanation of the underlying storage mechanism. Almost all of this cost comes from Optimize's indices. The following three levers are listed from most to least aggressive: - **Stop exporting variables entirely.** Set `camunda.data.exporters.elasticsearch.args.index.variable: false` (OpenSearch: `camunda.data.exporters.opensearch.args.index.variable: false`) at the exporter to drop all variable records. This is the only lever that also recovers throughput because the exporter write path is the bottleneck at maximum load. - **Export only the variables you need (name and prefix filters).** Keep a subset with name or prefix filters, for example only `customer`-prefixed variables. Use this when some variables drive Optimize reports, but most are noise. On SaaS, configure variable name filters in [cluster settings](/components/saas/clusters/settings.md#data-filters). On Self-Managed, see [Optimize export filtering](/self-managed/components/optimize/configuration/optimize-export-filtering.md). - **[Disable variable import](/self-managed/components/optimize/configuration/variable-import.md) in Optimize.** Available on all supported versions; achieves the storage savings but does not recover throughput, because the records are still written by the exporter. **Trade-off:** Filtered variables are unavailable in Optimize reports, including variable filters, variable-based grouping, and raw-data variable columns. These levers affect **Optimize only**; Operate and Tasklist read through the Camunda Exporter, so their variables stay intact. ##### Disable object variable flattening (high impact for object-heavy processes) By default, Optimize [flattens each object variable](/self-managed/components/optimize/configuration/object-variables.md) into a separate variable for each property and stores the full raw object as another variable. Each generated variable incurs its own storage cost, so an object variable with several properties can require several times more storage than a single scalar variable. If you don't rely on flattened object-variable filtering, grouping, or raw-data columns in Optimize reports, disable it by setting: - Environment variable: `CAMUNDA_OPTIMIZE_ZEEBE_INCLUDE_OBJECT_VARIABLE=false` - Configuration property: `zeebe.includeObjectVariableValue: false` :::note This behavior is enabled by default in Self-Managed and disabled in Camunda 8 SaaS. ::: In an isolated benchmark that changed only this setting for the same workload: - Optimize's share of total Elasticsearch disk usage dropped from 62.8% to 7.6%, a reduction by a factor of 8.3. - Total secondary storage per created process instance dropped from 6.34 MB to 2.97 MB, a reduction by a factor of 2.13. This reduction was smaller because the setting does not affect Zeebe or Camunda Exporter storage. See [Confirming Optimize's object variable flattening cost with a controlled A/B test](https://camunda.github.io/zeebe-chaos/2026/07/09/Optimize-Object-Variable-Flattening/) for the complete methodology and additional measurements. :::warning These ratios are specific to the benchmark's payload and process models; they are not universal constants. Object variable flattening processes nested JSON recursively without a depth limit, so payloads with deeper nesting or more object fields can require considerably more storage than measured here. Measure your workload before using these numbers for capacity planning. ::: ##### Other mitigations - **Run Optimize on a separate Elasticsearch/OpenSearch instance.** Contention is bidirectional: Optimize's write spikes degrade Operate, Tasklist, and the Camunda Exporter, while heavy exporter activity degrades Optimize import. Isolation removes this mutual interference. - **Tune retention periods.** Shorter retention means less data in Elasticsearch/OpenSearch and better performance. - **Increase import throughput if Optimize lags.** If you notice a significant lag between the rate of exported Zeebe records and imported Optimize data, raise `CAMUNDA_OPTIMIZE_ZEEBE_MAX_IMPORT_PAGE_SIZE` so each import cycle fetches more exported records. This helps Optimize keep pace under high load, but increases memory use per fetch and can negatively impact individual record latency, as Optimize must wait to fill larger batches before processing. #### Elasticsearch/OpenSearch shard budget :::warning Optimize creates a dedicated index per deployed process definition, each using at least one shard. Elasticsearch and OpenSearch cap the number of shards per node (1,000 by default), so a cluster's total shard budget is `nodes × per-node limit` (for example, 3,000 on a three-node cluster). A large or growing number of deployed process definitions consumes this budget and can approach the ceiling; small development or test clusters with few nodes reach it quickly. Once the ceiling is hit, new index creation is rejected, which cascades into exporter backpressure and stalled processing. Account for shard budget when sizing the Elasticsearch/OpenSearch cluster, not just CPU, memory, and disk. See [impact of high process deployments on Elasticsearch](https://camunda.github.io/zeebe-chaos/2026/05/28/Impact-of-High-Process-Deployments-on-Elasticsearch). ::: #### Zeebe record ILM retention When the [Elasticsearch exporter retention policy](/self-managed/components/orchestration-cluster/zeebe/exporters/elasticsearch-exporter.md#retention) is enabled, Zeebe record indices are deleted after the configured `minimum-age`. Optimize reads from these same indices, so the retention window must be long enough to cover Optimize's worst-case import lag. If the exporter deletes records before Optimize imports them, process instance completion events are permanently lost: Optimize records the instance as `ACTIVE` with no `endDate`, and history cleanup can never remove it. **Minimum recommended retention:** Set `minimum-age` to at least **3 days** when running Optimize; **7 or more days** is recommended. This provides headroom for: - Import lag that grows as the Optimize process instance index grows larger. - Recovery time after Elasticsearch cluster events such as node restarts, rolling upgrades, and shard rebalancing. The default `minimum-age` of `30d` provides sufficient headroom. If you reduced it to limit disk usage, verify that the new value still exceeds your observed Optimize import lag before applying it to production. **Disk sizing:** A longer ILM retention window means Zeebe record indices are kept on disk longer before deletion. Factor the additional raw exporter index volume into your Elasticsearch disk budget when increasing `minimum-age` beyond the default. **Self-reinforcing failure mode:** As Optimize's process instance index grows, Elasticsearch write latency increases, which raises per-batch import lag. Higher import lag increases the probability of an ILM race on the next cluster event. This cycle compounds on long-lived clusters running at sustained load. To break it, increase ILM retention and reduce the Optimize index size through history cleanup or variable filtering. **Symptom:** If Optimize history cleanup runs on schedule but consistently completes in zero seconds against a large dataset, orphaned `ACTIVE` documents are likely accumulating. See [diagnosing stalled cleanup](/self-managed/components/optimize/configuration/history-cleanup.md#diagnosing-stalled-cleanup). The sizing guidance for [Self-Managed](./sizing-self-managed.md#baseline-resource-configuration) provides a baseline configuration, including Optimize's resource impact, for each secondary storage backend to help you plan accordingly. ### Latency and cycle time In some use cases, process cycle time (or even individual task cycle time) matters. For example, you might expose a REST endpoint that starts a process instance to calculate a customer score. The process runs four service tasks, and the REST request must return synchronously within 250 ms. While service-task duration depends on the work performed, you can measure the workflow engine’s own overhead. :::note The latency measurements below are approximate and were last validated against an earlier version of Camunda 8. Updated measurements for 8.8/8.9 are pending. With the 8.8 streamlined architecture and properly aligned resources (3.5 CPU cores per broker), latency is expected to improve by approximately 2x compared to the previous distributed deployment. Actual latency is highly environment-dependent — factors like network latency between workers and the cluster, disk I/O speed (commit latency), and cloud region placement significantly affect these numbers. ::: As a rough estimate, you can expect: - Single-digit millisecond processing time per process node. - Approximately 50 ms latency to process service tasks in remote workers when running worker code in the same cloud region as the Camunda cluster. Hence, executing four service tasks results in roughly 200-250 ms workflow engine overhead. As you push throughput toward the cluster’s limits, latency increases because requests compete for resources, especially disk writes. If cycle time and latency matter, leave enough headroom and avoid running the cluster near full utilization to prevent resource contention. :::tip A good rule of thumb is to size for about **20x your average load**. This gives you capacity for peaks and keeps latency low during normal operation. ::: | Indicator | Number | Calculation method | Notes | | :------------------------------------------------------------- | --------: | :----------------: | :----------------------------------------------------------------------------------------- | | Onboarding instances per year | 5,000,000 | | Business input. | | Expected process instances on peak day | 150,000 | | Business input. | | Process instances per second within business hours on peak day | 5.20 | / (8\*60\*60) | Only looking at the seconds within the eight business hours of a day. | | Process instances per second including buffer | 104.16 | \* 20 | Adding some buffer is recommended for critical, high-performance or low-latency use cases. | ### Payload size Each process instance can hold a payload, known as [process variables](/components/concepts/variables.md). The workflow engine must manage the variables for all running instances, and data from both running and completed process instances is forwarded to Operate and Tasklist. Process variable size affects resource requirements. For example, there’s a big difference between storing a few strings (around 1 KB) and storing a full 1 MB JSON document. That’s why payload size is a key sizing factor. Camunda's official benchmarks use two reference payloads: - [**Typical payload**](https://github.com/camunda/camunda/blob/main/load-tests/load-tester/src/main/resources/bpmn/typical_payload.json): Used for baseline measurements (~0.5 KB, 15 simple variables). - [**Realistic payload**](https://github.com/camunda/camunda/blob/main/load-tests/load-tester/src/main/resources/bpmn/realistic/realisticPayload.json): Used for the reference sizing benchmarks. This better represents real-world payloads (~11 KB). :::note Payload size has a multiplicative effect, affecting Zeebe storage, Elasticsearch export volume, Optimize import time, and query/report performance. An 11 KB payload vs. a 0.5 KB payload can change disk consumption by **10-20x**. ::: Consider these general rules for payload size: - The maximum [variable size per process instance is limited](/components/concepts/variables.md#variable-size-limitation), currently to roughly three MB. - Camunda does not recommend storing large amounts of data in your process context. Refer to our [best practices on handling data in processes](/components/best-practices/development/handling-data-in-processes.md) for more details. - An AI agent's [agent context](/components/agentic-orchestration/agent-definitions-and-instances.md#agent-context-and-memory) is a process variable that grows with each loop iteration, so it counts toward this limit. Switch the agent's memory to [Camunda document storage](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess.md#choose-a-memory-storage-backend) when a long conversation would outgrow it. - Each [partition](/components/zeebe/technical-concepts/partitions.md) of the Zeebe installation can typically handle up to one GB of payload in total. Larger payloads can lead to slower processing. For example, one million process instances with four KB each is about 3.9 GB, so you need at least four partitions. In practice, you’d typically use six partitions, since the number of partitions is usually a multiple of the replication factor (three by default). ### Peak loads In most scenarios, your load will be volatile rather than constant. For example, your company might start 90% of its monthly process instances on a single day of the month. The **ability to handle those peaks is the more crucial requirement and should drive your decision**, rather than the average load. In this example, that single peak day defines your overall throughput requirements. In addition, sizing for peaks may mean you shouldn’t assume a full 24-hour day. Instead, you might size for just the eight business hours, or even the busiest two hours—depending on your workload. ### Job worker capacity Even when your cluster has spare throughput capacity, an undersized job worker can still allow jobs to accumulate in the backlog. Worker capacity requires its own sizing exercise, separate from cluster sizing. The workflow engine delivers jobs to workers through two paths that share a worker's capacity but behave differently: [Job streaming pushes a job as soon as it becomes available for activation](/components/concepts/job-workers.md#how-job-streaming-and-polling-deliver-jobs), while polling is the only path that drains jobs already queued in the backlog. Therefore, healthy throughput does not indicate whether the backlog is draining; they are independent signals. The backlog can continue to grow after workers recover from an outage, even when throughput appears to have fully recovered. See [impact of worker downtime on a realistic load test](https://camunda.github.io/zeebe-chaos/2026/08/06/worker-downtime-throughput-recovery) for more details. :::note There is currently no built-in metric that directly reports the size of this backlog. ::: Size the worker’s capacity according to its concurrency model and the job timeout. For the Java client’s fixed-thread-pool model, see [sizing `maxJobsActive` against execution threads](/components/best-practices/development/writing-good-workers.md#size-maxjobsactive-against-execution-threads). Other client SDKs implement worker capacity differently and are not covered by this formula. ### Secondary storage Starting with Camunda 8.9, the platform supports three secondary storage backends, each with different sizing implications. #### Elasticsearch (default) - The **most mature and most benchmarked** option. - Required if you use Optimize. - Provides full-text search capabilities used by Operate and Tasklist. :::important Sizing data provided throughout this guide assumes Elasticsearch unless stated otherwise. ::: #### OpenSearch - A drop-in alternative to Elasticsearch with a similar resource profile. - Supported for all components including Optimize. See [supported environments](/reference/supported-environments.md) for more details. - Sizing recommendations for Elasticsearch generally apply to OpenSearch as well. #### RDBMS - A different storage paradigm: a relational database instead of a document store. See the full list of [supported databases](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md#supported-rdbms). - A different resource profile: CPU/memory-oriented rather than disk/IOPS-oriented. - Write throughput is approximately **70% of Elasticsearch** on equivalent hardware. See [RDBMS benchmarking results](/self-managed/concepts/secondary-storage/rdbms-benchmark-results.md) for the full methodology and caveats. - **No Optimize support**: If you need Optimize, you must run Elasticsearch alongside RDBMS. - **Scales primarily vertically** rather than horizontally like Elasticsearch, since its schema has no table partitioning and each broker opens a connection pool per physical tenant. Plan initial sizing with more headroom, as adding capacity is more disruptive. - Ideal for organizations that already operate a supported RDBMS at scale and want to avoid adding Elasticsearch to their infrastructure. ### Throughput Throughput defines how many process instances can be executed within a certain timeframe. It is typically easy to estimate the number of process instances per day you need to execute. However, hardware sizing depends more on the **number of BPMN tasks** in a process model. If you already know your future process model, you can use it to count the number of tasks in the process. For example, the following onboarding process contains five service tasks in a typical execution: :::tip If you don't yet know the number of service tasks, Camunda recommends assuming **10 service tasks** as a rule of thumb. ::: The number of tasks per process allows you to calculate the number of tasks per day. You can also convert this to tasks per second. For example: | Indicator | Number | Calculation method | Notes | | :--------------------------------- | --------: | :----------------: | :------------------------------------------- | | Onboarding instances per year | 5,000,000 | | Business input. | | Process instances per business day | 20,000 | / 250 | Average number of working days in a year. | | Tasks per day | 100,000 | \* 5 | Tasks in the process model as counted above. | | Tasks per second | 1.16 | / (24\*60\*60) | Seconds per day. | In most cases, Camunda defines throughput per day, as this time frame is easier to understand. However, in high-performance use cases, you might need to define the throughput per second. ## Plan non-production environments All clusters can be used for development, testing, integration, Q&A, and production. For typical integration or functional test environments, you can usually deploy a small cluster even if your production environment is sized larger. This is typically sufficient, as functional tests run much smaller workloads. Load or performance tests should ideally run on the same sizing configuration as your production cluster to yield reliable results. A typical customer setup consists of: - A production cluster. - An integration or pre-production cluster (equal in size to your anticipated production cluster if you want to run load tests or benchmarks). - A test cluster. - Development clusters. ## Next steps Now that you understand the factors that influence sizing: - **SaaS customers:** [Size your SaaS cluster](sizing-saas.md) to select the right cluster size. - **Self-Managed admins:** Provision your Kubernetes cluster using these [baseline resource settings](sizing-self-managed.md). - **Validate sizing:** [Run your own benchmarks](sizing-benchmarks.md) to test your specific workload. For current secondary storage benchmarks, see [RDBMS benchmark results](/self-managed/concepts/secondary-storage/rdbms-benchmark-results.md). --- ## Understanding human task management ## Using task assignment features The lifecycle of human task orchestration (like assigning, delegating, and completing tasks) is mostly a generic issue. There is no need to model common aspects into all your processes, if often makes models unreadable. Use Camunda task management features or implement your requirements in a generic way. ![Task assignment](understanding-human-tasks-management-assets/human-tasks.png) So every task can be assigned to either a group of people, or a specific individual. An individual can 'claim' a task, indicating that they are picking the task from the pool (to avoid multiple people working on the same task). As a general rule, you should assign human tasks, like [user tasks](/components/modeler/bpmn/user-tasks/user-tasks.md) or [manual tasks](/components/modeler/bpmn/manual-tasks/manual-tasks.md), in your business process to _groups of people_ instead of specific individuals. ```xml ``` Then, require individual members of that group to explicitly _claim tasks_ before working on them. This way, you avoid different people working on the same task at the same time. Refer to [`assign`](/apis-tools/orchestration-cluster-api-rest/specifications/assign-user-task.api.mdx). You can also directly claim tasks in Camunda Tasklist with the click of a button. ![Claim](understanding-human-tasks-management-assets/claim.png) While assigning users to groups is advised, it's not the only option. You could always assign a task to a _single person_ who is supposed to complete the task (e.g. the individual 'customer' of your process or a coworker having specific knowledge for the case). You will need to have access to the specific person relevant for your process instance, e.g. via a process variable: ```xml ``` ## Deciding about your task list frontend If you are orchestrating human tasks in your process, you must make up your mind on how exactly you want to let your users work on their tasks and interact with the workflow engine. You have basically three options: - [Camunda Tasklist](/components/tasklist/introduction-to-tasklist.md): The Tasklist application shipped with Camunda. This works out-of-the-box and has a low development effort. However, it is limited in terms of customizability and how much you can influence the user experience. - Custom task list application: You can develop a custom task list and adapt this to your needs without compromises. User tasks are shown inside your custom application, following your style guide and usability concept. You will use the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) in the background. This is very flexible, but requires additional development work. - Third party tasklist: If our organization already has a task list application rolled out to the field, you might want to use this for tasks created by Camunda. You will need to develop some synchronization mechanism. The upside of this approach is that your end users might not even notice that you introduce a new workflow engine. ### Considerations for developing custom task lists When building a custom tasklist/application, you must plan for the following aspects. You will need to - _Query_ for user tasks and _generate lists_ of those tasks. - _Filter the list_ along specific attributes like current assignee, candidate groups, etc. - _Select_ and _display_ the right forms for starting processes and completing tasks. - Use _custom/business value_ data in order to _filter_ with those values and _display_ them correlated with the task list and within forms. - _Authorize_ users to access those lists, filters, and forms. ### Considerations for using third party task lists When integrating a third party tasklist, you must plan for the following aspects. You will need to take care of: - _Creating_ tasks in the third party tasklist based on the user tasks created by Camunda. - _Completing_ tasks in Camunda and move on process execution based on user action in the third party tasklist. - _Cancelling_ tasks, triggered by Camunda or triggered by the user in the third-party tasklist. - Transferring _business data_ to be edited in the third-party tasklist back and forth. Your third party tasklist application also needs to allow for some programmatic control of the lifecycle of its tasks. The third-party application _must have_ the ability: - To programmatically _create_ a new task. - To _hook in code_ which programmatically informs other systems that the user is about to change a task's state. - To _manage custom attributes_ connected to a task and programmatically access them. Additionally, it _should have_ the ability - To programmatically _delete_ a task which was cancelled in Camunda. Without this possibility such tasks remain in the users tasklist and would need to be removed manually. Depending on the way you integrate the task completion mechanism, when the user tries to complete such tasks, they would immediately observe an error or the action would just not matter anymore and serve as a removal from the list. Transfer just the minimal amount of business data in between Camunda and your third-party tasklist application. For creating tasks, transfer just the taskId and important business data references/ids to your domain objects. As much as possible should be retrieved later, and just when needed (e.g. when displaying task forms to the user) by requesting data from the process engine or by requesting data directly from other systems. For completing tasks, transfer just the business data which originated from Camunda and was changed by the user. This means, in case you just maintain references, nothing needs to be transferred back. All other business data changed by the user will be directly transferred to the affected systems. ### Task lists may not look like task lists There are situations where you might want to show a user interface that does not look like a task list, even if it is fed by tasks. The following _example_ shows such a situation in the document _input management_ process of a company. Every document is handled by a separate process instance, but users typically look at complete mailings consisting of several such documents. In a customer scenario, there were people in charge of assessing the scanned mailing and distributing the individual documents to the responsible departments. It was important to do that in one step, as sometimes documents referred to each other. So you have several user tasks which are heavily _interdependent_ from a business point of view and should therefore be completed _in one step_ by the same person. The solution to this was a custom user interface that basically queries for human tasks, but show them grouped by mailings: ![custom tasklist mockup](understanding-human-tasks-management-assets/tasklist-mockup.png) 1 The custom tasklist shows each mailing as one "distribution task", even though they consist of several human tasks fetched from the workflow instance. 2 The custom user interface allows you to work on all four human tasks at once. By dragging and dropping a document within the tree, the user can choose to which department the document is delivered to. 3 In case the user detects a scanning problem, they can request a new scan of the mailing. But as soon as all documents are quality assured, the button **Distribute Mailing** gets enabled. By clicking on it, the system completes all four human tasks - one for each document - which moves forward the four process instances associated with the documents. --- ## Best Practices The Camunda Best Practices distill Camunda's experience with BPMN, DMN, and agentic orchestration on the Camunda toolstack, drawing on consulting engagements, community feedback, and customer projects. They offer a blend of conceptual and practical guidance, generalized from current project experience. They are not definitive: learning is ongoing, and how well a practice applies depends on your situation. Note that Camunda extends the same guarantee to Best Practices as to the core product. However, Camunda cannot ensure the absolute accuracy or timeliness of the information provided, and disclaims any liability for damages resulting from applying these recommendations. ## Project management ## Architecture ## Development ## Modeling ## Operations ## CI/CD guidelines --- ## Element templates at scale To effectively manage large libraries of reusable building blocks ([element templates](/components/concepts/element-templates.md)), you can create a pipeline that: - Provisions the [dependencies of element templates](/components/modeler/element-templates/element-template-with-dependencies.md) to required clusters. - Makes templates available at design time to multiple [workspaces](/components/hub/organization/manage-workspaces/index.md) within an organization. ![Pipeline goal](./img/pipeline-goal.png) This guide covers conceptually what your pipeline needs to do, from obtaining credentials to runtime provisioning and template syncing. ## Prerequisites Before building your pipeline, ensure you have the following: | Prerequisite | Purpose | | -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Git Repository](https://en.wikipedia.org/wiki/Git) | Store all element templates | | Template state management | Maintain an authoritative inventory (for example, via Git or an IaC tool like Terraform) that defines which templates are applied to each cluster and which workspaces depend on them. This source acts as the single source of truth for template deployments. | | Camunda Hub API token ([SaaS](/apis-tools/hub-api-saas/authentication.md) or [Self-Managed](/apis-tools/hub-api-sm/authentication.md)) | Access Camunda Hub programmatically | | [Orchestration Cluster API client](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md) | Provision dependencies to clusters | For simplicity, this guide assumes: - One organization - One cluster - One [workspace](/components/hub/organization/manage-workspaces/index.md) - A pipeline handling runtime provisioning and template syncing ## Runtime provisioning ### Secrets You can use sensitive information in your element templates without exposing it in your BPMN processes by referencing secrets. These guides show you how to configure them depending on the environment you are using: - **SaaS**: Use the [Administration API](/apis-tools/administration-api/administration-api-reference.md) or [Camunda Hub UI](/components/saas/clusters/manage-secrets.md) to configure secrets. - **Self-Managed/local development**: Configure secrets outside the pipeline. See [connector secrets](/self-managed/components/connectors/connectors-configuration.md#secrets). ### Job Workers As part of the pipeline, you may spin up a service that will connect to a Camunda cluster to perform specific tasks. For example, you can use the [Spring Boot Camunda Starter](/apis-tools/camunda-spring-boot-starter/getting-started.md) to start a job worker. Recommended resources: - [Outbound connectors vs. job workers](/components/concepts/outbound-connectors-job-workers.md) - [Host custom connectors](/components/connectors/custom-built-connectors/host-custom-connector.md) ### Other dependencies The following dependency types are provisioned at runtime using the [Orchestration Cluster API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md): | Dependency | Purpose | | --------------------------------------------------------------------- | --------------------------- | | [Camunda forms](/components/modeler/forms/camunda-forms-reference.md) | Used in user tasks | | [RPA scripts](/components/rpa/overview.md) | Used in service tasks | | [BPMN processes](/components/modeler/bpmn/bpmn.md) | Used in call activities | | [DMN decisions](/components/modeler/dmn/dmn.md) | Used in business rule tasks | To deploy dependencies, send a [POST request](/apis-tools/orchestration-cluster-api-rest/specifications/create-deployment.api.mdx) with the files. This works for SaaS, Self-Managed, and local development. For example: ```bash curl -L 'http://localhost:8080/v2/deployments' \ -H 'Accept: application/json' \ -F resources=@/path/to/your/form/user-signup.form ``` You will get a response containing the details of the deployed elements: ```json { "deployments": [ { "form": { "formKey": "KEY_OF_THE_FORM", "formId": "user-signup", "version": 1, "resourceName": "user-signup.form", "tenantId": "" } } ], "deploymentKey": "KEY_OF_THE_DEPLOYMENT", "tenantId": "" } ``` When referencing a dependency such as a form, Camunda recommends using a `versionTag` as your [binding type](/components/best-practices/modeling/choosing-the-resource-binding-type.md#supported-binding-types). This option ensures the right version of the target resource is always used. ## Make templates available in Camunda Hub Make templates available in Camunda Hub with the Camunda Hub API ([SaaS](/apis-tools/hub-api-saas/overview.md) or [Self-Managed](/apis-tools/hub-api-sm/overview.md)). ### Get the workspace key Search for your workspace to get the `workspaceKey`: ```bash POST /api/v2/workspaces/search { "filter": { "name": "(WORKSPACE NAME)" } } ``` You'll use the `workspaceKey` to filter projects to the target workspace. ### Get projects With the `workspaceKey`, retrieve the projects that belong to the workspace: ```bash GET /api/v2/workspaces/(WORKSPACE KEY) ``` Under `content`, get the `projectKey` for the project you want to update. ### Get file metadata With the `projectKey`, retrieve a list of files and metadata: ```bash GET /api/v2/projects/(PROJECT KEY) ``` Using `content`, compare the files in Camunda Hub to the files in your repository. ### Create or update files For each file in your repository that doesn't match the content in Camunda Hub, [create](/apis-tools/hub-api-saas/specifications/create-file.api.mdx) or [update](/apis-tools/hub-api-saas/specifications/update-file.api.mdx) the appropriate file resource. ### Create new file versions If desired, [create a new file version](https://modeler.camunda.io/swagger-ui/index.html#/Versions) for each of the affected files: ```bash POST /api/v2/versions { "fileKey": "(FILE KEY)", "name": "(VERSION NAME)" } ``` ## Making templates available in Desktop Modeler To set up your local environment: - Access the VCS repository containing the templates. - Choose how to [configure them](/components/modeler/desktop-modeler/element-templates/configuring-templates.md) depending on your needs. If your templates are reused across multiple projects, configuring them globally will make it easier to maintain. For project-specific templates, consider making them available only for that project to avoid exposing templates to projects that should not be using them. :::note If you are the template creator/maintainer, include a `README` file in your repository that lists the requirements for using your templates -- for example, which dependencies need to be provisioned in advance. ::: ## Next steps Refer to [integrate Camunda Hub in CI/CD](/components/hub/workspace/modeler/integrate-modeler-in-ci-cd.md) for additional CI/CD-related guidance. --- ## Connecting the workflow engine with your world One of your first tasks to build a process solution is to sketch the basic architecture of your solution. To do so, you need to answer the question of how to connect the workflow engine (Zeebe) with your application or with remote systems. The workflow engine is a remote system for your applications, just like a database. Your application connects with Zeebe via remote protocols (like [gRPC](https://grpc.io/) or REST), which is typically hidden from you, like when using a database driver based on ODBC or JDBC. With Camunda 8 and the Zeebe workflow engine, there are two basic options: 1. Write some **programming code** that typically leverages the client library for the programming language of your choice. 2. Use some **existing connector** which just needs a configuration. The trade-offs will be discussed later; let’s look at the two options first. ## Programming glue code To write code that connects to Zeebe, you typically embed [the Zeebe client library](../../../apis-tools/working-with-apis-tools.md) into your application. An application can of course also be a service or microservice. If you have multiple applications that connect to Zeebe, all of them will require the client library. If you want to use a programming language where no such client library exists, you can [generate a gRPC client yourself](https://camunda.com/blog/2018/11/grpc-generating-a-zeebe-python-client/). ![Clients to Zeebe](connecting-the-workflow-engine-with-your-world-assets/clients.png) Your application can basically do two things with the client: 1. **Actively call Zeebe**, for example, to start process instances, correlate messages, or deploy process definitions. 2. **Subscribe to tasks** created in the workflow engine in the context of BPMN service tasks. ### Calling Zeebe Using the Zeebe client’s API, you can communicate with the workflow engine. The two most important API calls are to start new process instances and to correlate messages to a process instance. **Start process instances using the** [**Java Client**](../../../apis-tools/java-client/getting-started.md)**:** ```java processInstance = zeebeClient.newCreateInstanceCommand() .bpmnProcessId("someProcess").latestVersion() .variables( someProcessVariablesAsMap ) .send() .exceptionally( throwable -> { throw new RuntimeException("Could not create new instance", throwable); }); ``` **Correlate messages to process instances using the Java Client**: ```java zeebeClient.newPublishMessageCommand() // .messageName("messageA") .messageId(uniqueMessageIdForDeduplication) .correlationKey(message.getCorrelationid()) .variables(singletonMap("paymentInfo", "YeahWeCouldAddSomething")) .send() .exceptionally( throwable -> { throw new RuntimeException("Could not publish message " + message, throwable); }); ``` **Correlate messages to process instances using the Node.js client**: ```js zbc.publishMessage({ name: "messageA", messageId: messageId, correlationKey: correlationId, variables: { valueToAddToWorkflowVariables: "here", status: "PROCESSED", }, timeToLive: Duration.seconds.of(10), }); ``` This allows you to connect Zeebe with any external system by writing some custom glue code. We will look at common technology examples to illustrate this in a minute. ### Subscribing to tasks using a job worker To implement service tasks of a process model, you can write code that subscribes to the workflow engine. In essence, you will write some glue code that is called whenever a service task is reached (which internally creates a job, hence the name). **Glue code in Java:** ```java class ExampleJobHandler implements JobHandler { public void handle(final JobClient client, final ActivatedJob job) { // here: business logic that is executed with every job client.newCompleteCommand(job.getKey()).send() .exceptionally( throwable -> { throw new RuntimeException("Could not complete job " + job, throwable); });; } } ``` **Glue code in Node.js:** ```js function handler(job, complete, worker) { // here: business logic that is executed with every job complete.success(); } ``` Now, this handler needs to be connected to Zeebe, which is generally done by subscriptions, which internally use long polling to retrieve jobs. **Open subscription via the Zeebe Java client:** ```java zeebeClient .newWorker() .jobType("serviceA") .handler(new ExampleJobHandler()) .timeout(Duration.ofSeconds(10)) .open()) {waitUntilSystemInput("exit");} ``` **Open subscription via the Zeebe Node.js client:** ```js zbc.createWorker({ taskType: "serviceA", taskHandler: handler, }); ``` You can also use integrations in certain programming frameworks, like the [Camunda Spring Boot Starter](../../../apis-tools/camunda-spring-boot-starter/getting-started.md) in the Java world, which starts the job worker and implements the subscription automatically in the background for your glue code. **A subscription for your glue code is opened automatically by the Spring integration:** ```java @JobWorker(type = "serviceA") public void handleJobFoo(final JobClient client, final ActivatedJob job) { // here: business logic that is executed with every job // you do not need to call "complete" on the job, as autoComplete is turned on above } ``` There is also documentation on [how to write a good job worker](../writing-good-workers/). ## Technology examples Most projects want to connect to specific technologies. Currently, most people ask for REST, messaging, or Kafka. REST and messaging are common core integration patterns. Kafka is different: it is optional infrastructure that you would typically introduce for event streaming or broader event-driven architectures, not because Zeebe requires it to execute workflows. ### REST You could build a piece of code that provides a REST endpoint in the language of choice and then starts a process instance. The [Ticket Booking Example](https://github.com/berndruecker/ticket-booking-camunda-cloud) contains an example using Java and Spring Boot for the [REST endpoint](https://github.com/berndruecker/ticket-booking-camunda-cloud/blob/master/booking-service-java/src/main/java/io/berndruecker/ticketbooking/rest/TicketBookingRestController.java#L35). Similarly, you can leverage the [Spring Boot extension](https://github.com/zeebe-io/spring-zeebe/) to startup job workers that will [execute outgoing REST calls](https://github.com/berndruecker/ticket-booking-camunda-cloud/blob/master/booking-service-java/src/main/java/io/berndruecker/ticketbooking/adapter/GenerateTicketAdapter.java#L29). ![REST example](connecting-the-workflow-engine-with-your-world-assets/rest-example.png) You can find [Spring Boot sample code for the REST endpoint](https://github.com/berndruecker/flowing-retail/blob/master/zeebe/java/checkout/src/main/java/io/flowing/retail/checkout/rest/ShopRestController.java) in the [Flowing Retail example](https://github.com/berndruecker/flowing-retail). ### Messaging You can do the same for messages, which is often [AMQP](https://en.wikipedia.org/wiki/Advanced_Message_Queuing_Protocol) nowadays. The [Ticket Booking Example](https://github.com/berndruecker/ticket-booking-camunda-cloud) contains an example for RabbitMQ, Java, and Spring Boot. It provides a message listener to correlate incoming messages with waiting process instances, and [glue code to send outgoing messages onto the message broker](https://github.com/berndruecker/ticket-booking-camunda-cloud/blob/master/booking-service-java/src/main/java/io/berndruecker/ticketbooking/adapter/RetrievePaymentAdapter.java). ![Messaging example](connecting-the-workflow-engine-with-your-world-assets/messaging-example.png) [Service integration patterns](../service-integration-patterns/) goes into details of if you want to use a send and receive task here, or prefer simply one service task (spoiler alert: send and receive tasks are used here because the payment service might be long-running; think about expired credit cards that need to be updated or wire transfers that need to happen). ### Apache Kafka Kafka is not required for Camunda 8 or Zeebe to work. Zeebe executes workflows itself, while Kafka can optionally be used as an event backbone around the workflow engine. Typical Kafka-based patterns are: - **Kafka to Zeebe**: Consume records from a Kafka topic and translate them into Zeebe API calls, such as starting a process instance or correlating a message. - **Zeebe to Kafka**: When a workflow reaches a service task or other integration point, write a record to Kafka so downstream systems can react asynchronously. You can implement these patterns with custom glue code. The [Flowing Retail example](https://github.com/berndruecker/flowing-retail) shows this using Java, Spring Boot, and Spring Cloud Streams. There is [code to subscribe to a Kafka topic and start new process instances for new records](https://github.com/berndruecker/flowing-retail/blob/master/kafka/java/order-zeebe/src/main/java/io/flowing/retail/kafka/order/messages/MessageListener.java#L39), and there is some glue code to create new records when a process instance executes a service task. Of course, you could also use other frameworks to achieve the same result. This means Kafka is a good fit if you already use Kafka, need loose coupling, or want to broadcast workflow-related events to multiple consumers. If you simply need to call a remote system from a workflow, a job worker or connector is often the more direct option. ![Kafka Example](connecting-the-workflow-engine-with-your-world-assets/kafka-example.png) ## Designing process solutions containing all glue code Typical applications will include multiple pieces of glue code in one codebase. ![Architecture with glue code](connecting-the-workflow-engine-with-your-world-assets/architecture.png) For example, the onboarding microservice shown in the figure above includes: - A REST endpoint that starts a process instance (1) - The process definition itself (2), probably auto-deployed to the workflow engine during the startup of the application. - Glue code subscribing to the two service tasks that shall call a remote REST API (3) and (4). A job worker will be started automatically as part of the application to handle the subscriptions. In this example, the application is written in Java, but again, it could be [any supported programming language](/apis-tools/working-with-apis-tools.md). As discussed in [writing good workers](../writing-good-workers/), you typically will bundle all workers within one process solution, but there are exceptions where it makes sense to have single workers as separate application. ## Connectors The glue code is relatively simple, but you need to write code. You might prefer using an out-of-the-box component, connecting Zeebe with the technology you need just by configuration. This component is called a **Connector**. A connector can be uni or bidirectional and is typically one dedicated application that implements the connection that translates in one or both directions of communication. Such a connector might also be helpful in case integrations are not that simple anymore. ![Connectors](connecting-the-workflow-engine-with-your-world-assets/connector.png) For example, the [HTTP connector](https://github.com/camunda-community-hub/zeebe-http-worker) is a one-way connector that contains a job worker that can process service tasks doing HTTP calls as visualized in the example in the following figure: ![REST connectors](connecting-the-workflow-engine-with-your-world-assets/rest-connector.png) Another example is the [Kafka connector](https://github.com/camunda-community-hub/kafka-connect-zeebe), as illustrated below. ![Kafka connector](connecting-the-workflow-engine-with-your-world-assets/kafka-connector.png) This is a bidirectional connector which contains a Kafka listener for forwarding Kafka records to Zeebe and also a job worker which creates Kafka records every time a service task is executed. In other words, the connector helps Kafka exchange events with Zeebe; it does not replace Zeebe's own workflow execution or state handling. This is illustrated by the following example: ![Kafka connector Details](connecting-the-workflow-engine-with-your-world-assets/kafka-connector-details.png) ### Out-of-the-box connectors As well as Camunda-maintained connectors, additional connectors are maintained by the community (made up of consultants, partners, customers, and enthusiastic individuals). You can find a list of connectors in the [Camunda Marketplace](https://marketplace.camunda.com/). ### Reusing your own integration logic by extracting connectors If you need to integrate with certain infrastructure regularly, for example your CRM system, you might also want to create your own CRM connector, run it centralized, and reuse it in various applications. In general, we recommend not to start such connectors too early. Don’t forget that such a connector gets hard to adjust once in production and reused across multiple applications. Also, it is often much harder to extract all configuration parameters correctly and fill them from within the process, than it would be to have bespoke glue code in the programming language of your choice. Therefore, only extract a full-blown connector if you understand exactly what you need. Don’t forget about the possibility to extract common glue code in a simple library that is then used at different places. :::note Updating a library that is used in various other applications can be harder than updating one central connector. In this case, the best approach depends on your scenario. ::: Whenever you have such glue code running and really understand the implications of making it a connector, as well as the value it will bring, it can make a lot of sense. ## Recommendation As a general rule of thumb, prefer custom glue code whenever you don’t have a good reason to go with an existing connector. A good reason to use connectors is if you need to solve complex integrations where little customization is needed, such as the [Camunda RPA bridge](https://docs.camunda.org/manual/latest/user-guide/camunda-bpm-rpa-bridge/) to connect RPA bots (soon to be available for Camunda 8). Good use of connectors are also scenarios where you don’t need custom glue code. For example, when orchestrating serverless functions on AWS with the [AWS Lambda connector](https://github.com/camunda-community-hub/zeebe-lambda-worker). This connector can be operated once and used in different processes. Some use cases also allow you to create a **reusable generic adapter**; for example, to send status events to your business intelligence system. But there are also common downsides with connectors. First, the possibilities are limited to what the creator of the connector has foreseen. In reality, you might have slightly different requirements and hit a limitation of a connector. Second, the connector requires you to operate this connector in addition to your own application. The complexity associated with this depends on your environment. Third, testing your glue code gets harder, as you can’t easily hook in mocks into such a connector as you could in your own glue code. --- ## Deal with problems and exceptions ## Understanding workers :::caution Camunda 8 only The description of workers targets Camunda 8, even if [external tasks in Camunda 7](https://docs.camunda.org/manual/latest/user-guide/process-engine/external-tasks/) are conceptually similar. ::: First, let's briefly examine how a worker operates. Whenever a process instance arrives at a service task, a new job is created and pushed to an internal persistent queue within Camunda 8. A client application can subscribe to these jobs with the workflow engine by the task type name (which is comparable to a queue name). If there is no worker subscribed when a job is created, the job is simply put in a queue. If multiple workers are subscribed, they are competing consumers, and jobs are distributed among them. ![Worker concept](dealing-with-problems-and-exceptions-assets/worker-concept.png) Whenever the worker has finished whatever it needs to do (like invoking the REST endpoint), it sends another call to the workflow engine, which [can be one of these three](/components/concepts/job-workers.md#completing-or-failing-jobs): - [`CompleteJob`](/apis-tools/zeebe-api/gateway-service.md#completejob-rpc): The service task went well, the process instance can move on. - [`FailJob `](/apis-tools/zeebe-api/gateway-service.md#failjob-rpc): The service task failed, and the workflow engine should handle this failure. There are two possibilities: - `remaining retries > 0`: The job is retried. - `remaining retries <= 0`: An [incident](/components/concepts/incidents.md) is raised and the job is not retried until the incident is resolved. - [`ThrowError`](/apis-tools/zeebe-api/gateway-service.md#throwerror-rpc): A BPMN error is reported, which typically is handled on the BPMN level. As the glue code in the worker is external to the workflow engine, there is **no technical transaction spanning both components**. Technical transactions refer to ACID (atomic, consistent, isolated, durable) properties, mostly known from relational databases. If, for example, your application leverages those capabilities, your business logic is either successfully committed as a whole, or rolled back completely in case of any error. However, those ACID transactions cannot be applied to distributed systems (the talk [lost in transaction](https://www.youtube.com/watch?v=WRR26jJNh68) elaborates on this). In other words, things can get out of sync if either the job handler or the workflow engine fails. A typical example scenario is the following, where a worker calls a REST endpoint to invoke business logic: ![Typical call chain](dealing-with-problems-and-exceptions-assets/typical-call-chain.png) Technical ACID transaction will only be applied in the business application. The job worker mostly needs to handle exceptions on a technical level, e.g. to control retry behavior, or pass it on to the process level, where you might need to implement business transactions. ## Handling exceptions on a technical level ### Leveraging retries Using the [`FailJob `](/apis-tools/zeebe-api/gateway-service.md#failjob-rpc) API is pretty handy to leverage the built-in retry mechanism of Zeebe. The initial number of retries is set in the BPMN process model: ```xml ``` This number is typically decremented with every attempt to execute the service task. Note that you need to do that in your worker code. Example in Java: ```java @JobWorker(type = "retrieveMoney", autoComplete = false) public void retrieveMoney(final JobClient client, final ActivatedJob job) { try { // your code } catch (Exception ex) { jobClient.newFailCommand(job) .retries(job.getRetries()-1) // <1>: Decrement retries .errorMessage("Could not retrieve money due to: " + ex.getMessage()) // <2> .send() .exceptionally(t -> {throw new RuntimeException("Could not fail job: " + t.getMessage(), t);}); } } ``` 1 Decrement the retries by one. 2 Provide a meaningful error message, as this will be displayed to a human operator once an incident is created in Operate. Example in Node.js: ```js zbc.createWorker("retrieveMoney", (job) => { try { // ... } catch (e) { job.fail("Could not retrieve money due to: " + e.message, job.retries - 1); } }); ``` ### Using incidents Whenever a job fails with a retry count of `0`, an incident is raised. An incident requires human intervention, typically using Operate. Refer to [incidents in the Operate docs](/components/operate/userguide/resolve-incidents-update-variables.md). For example, this behavior also applies to tools called by an AI agent. Each tool the agent selects runs as an ordinary BPMN activity, so retries and incidents work as described above. See [how the agent loop works](/components/agentic-orchestration/ai-agents.md#how-the-agent-loop-works). ### Writing idempotent workers Zeebe uses the **at-least-once strategy** for job handlers, which is a typical choice in distributed systems. This means that the process instance only advances in the happy case (the job was completed, the workflow engine received the complete job request and committed it). A typical failure case occurs when the worker who polled the job crashes and cannot complete the job anymore. [In this case, the workflow engine gives the job to another worker after a configured timeout](/components/concepts/job-workers.md#timeouts). This ensures that the job handler is executed at least once. But this can mean that the handler is executed more than once! You need to consider this in your handler code, as the handler might be called more than one time. The [technical term describing this is idempotency](https://en.wikipedia.org/wiki/Idempotence). For example, typical strategies are described in [3 common pitfalls in microservice integration — and how to avoid them](https://blog.bernd-ruecker.com/3-common-pitfalls-in-microservice-integration-and-how-to-avoid-them-3f27a442cd07). One possibility is to ask the service provider if it has already seen the same request. A more common approach is to implement the service provider in a way that allows for duplicate calls. There are two ways of mastering this: - **Natural idempotency**. Some methods can be executed as often as you want because they just flip some state. Example: `confirmCustomer()`. - **Business idempotency**. Sometimes you have business identifiers that allow you to detect duplicate calls (e.g. by keeping a database of records that you can check). Example: `createCustomer(email)`. If these approaches do not work, you will need to add a **custom idempotency handling** by using unique IDs or hashes. For example, you can generate a unique identifier and add it to the call. This way, a duplicate call can be easily spotted if you store that ID on the service provider side. If you leverage a workflow engine you probably can let it do the heavy lifting. Example: `charge(transactionId, amount)`. See this snippet of a process about how to support custom idempotency handling in a process model: Whatever strategy you use, make sure that you’ve considered idempotency consciously. ## Handling errors on the process level You often encounter deviations from the "happy path" (the default scenario with a positive outcome) which shall be modeled in the process model. ### Using BPMN error events A common way to resolve these deviations is using a BPMN error event, which allows a process model to react to errors within a task. For example: 1 We decide that we want to deal with an exception in the process: in case the invoice cannot be sent automatically... 2 ...we assign a task to a human user, who is now in charge of taking care of delivering the invoice. Learn more about the usage of [error events](/components/modeler/bpmn/error-events/error-events.md) in the user guide. ### Throwing and handling BPMN errors In BPMN process definitions, we can explicitly model an end event as an error. 1 In case the item is not available, we finish the process with an **error end event**. :::note You can mimic a BPMN error in your glue code by using the [`ThrowError`](/apis-tools/zeebe-api/gateway-service.md#throwerror-rpc) API. The consequences for the process are the same as if it were an explicit error end event. So, in case your 'purchase' activity is not a subprocess, but a service task, it could throw a BPMN Error informing the process that the good is unavailable. ::: Example in Java: ```java jobClient.newThrowErrorCommand(job) .errorCode("GOOD_UNAVAILABLE") .errorMessage() .send() .exceptionally(t -> {throw new RuntimeException("Could not throw BPMN error: " + t.getMessage(), t);}); ``` ### Thinking about unhandled BPMN exceptions It is crucial to understand that if a BPMN error is not handled anywhere in the process, Camunda 8 raises an [incident](/components/concepts/incidents.md) (for example, `Unhandled error event`) instead of silently terminating the process instance. Therefore, you can and normally should always handle the BPMN error. You can, of course, also handle it in a parent process scope like in the example below: 1 The boundary error event deals with the case that the item is unavailable. ### Distinguishing between exceptions and results As an alternative to throwing a Java exception, you can also write a problematic result into a process variable and model an XOR-Gateway later in the process flow to take a different path if that problem occurs. From a business perspective, the underlying problem then looks less like an error and more like a result of an activity, so as a rule of thumb we deal with _expected results_ of activities by means of gateways, but model exceptional errors, which _hinder us in reaching the expected result_ as boundary error events. 1 The task is to "check the customer's credit-worthiness", so we can reason that we _expect as a result_ to know whether the customer is credit-worthy or not. 2 We can therefore model an _exclusive gateway_ working on that result and decide via the subsequent process flow what to do with a customer who is not credit-worthy. Here, we just consider the order to be declined. 3 However, it could be that we _cannot reach a result_, because while we are trying to obtain knowledge about the customer's creditworthiness, we discover that the ID we have is not associated with any known real person. We can't obtain the expected result and therefore model a _boundary error event_. In the example, the consequence is just the same and we consider the order to be declined. ### Business vs. technical errors Note that you have two different ways of dealing with problems at your disposal now: - **Retrying**. You don't want to model the retrying, as you would have to add it to each and every service task. This will bloat the visual model and confuse business personnel. Instead, either retry or fall back to incidents as described above. This is hidden in the visual. - Branch out **separate paths**, as described with the error event. In this context, we found the terms **business error** and **technical error** can be confusing, as they emphasize the source of the error too much. This can lead to long discussions about whether a certain problem is technical or not, and if you are allowed to observe technical errors in a business process model. It's much more important to look at how you react to certain errors. Even a technical problem can qualify for a business reaction. In the above example, upon technical problems with the invoice service you can decide to manually send the invoice (business reaction) or to retry until the invoice service becomes available again (technical reaction). Or, for example, you could decide to continue a process in the event that a scoring service is not available, and simply give every customer a good rating instead of blocking progress. The error is clearly technical, but the reaction is a business decision. In general, we recommend talking about business reactions, which are modeled in your process, and technical reactions, which are handled generically using retries or incidents. ## Embracing business transactions and eventual consistency ### Technical vs business transactions Applications using databases can often leverage ACID (atomic, consistent, isolated, durable) capabilities of that database. This means that some business logic is either successfully committed as a whole, or rolled back completely in case of any error. It is normally referred to as "transactions". Those ACID transactions cannot be applied to distributed systems (the talk [lost in transaction](https://www.youtube.com/watch?v=WRR26jJNh68) elaborates on this), so if you call out to multiple services from a process, you end up with separate ACID transactions at play. The following illustrations are taken from the O'Reilly book [Practical Process Automation](https://processautomationbook.com/): ![Multiple ACID transactions](dealing-with-problems-and-exceptions-assets/multiple-acid-transactions.png) In the above example, the CRM system and the billing system have their local ACID transactions. The workflow engine itself also runs transactional. However, there cannot be a joined technical transaction. This requires a new way of dealing with consistency on the business level, which is referred to as **business transaction**: ![Businss vs technical transaction](dealing-with-problems-and-exceptions-assets/business-vs-technical-transaction.png) A **business transaction** marks a section in a process for which 'all or nothing' semantics (similar to a technical transaction) should apply, but from a business perspective. You might encounter inconsistent states in between (for example a new customer being present in the CRM system, but not yet in the billing system). ### Eventual consistency It is important to be aware that these temporary inconsistencies are possible. You also have to understand the failure scenarios they can cause. In the above example, you could have created a marketing campaign at a moment when a customer was already in the CRM system, but not yet in billing, so they got included in that list. Then, even if their order gets rejected and they never end up as an active customer, they might still receive an upgrade advertisement. You need to understand the effects of this happening. Furthermore, you have to think about a strategy to resolve inconsistencies. The term **eventual consistency** suggests that you need to take measures to get back to a consistent state eventually. In the onboarding example, this could mean you need to deactivate the customer in the CRM system if adding them to the billing system fails. This leads to the consistent state that the customer is not visible in any system anymore. ### Business strategies to handle inconsistency There are three basic strategies if a consistency problem occurs: - Ignore it. While it sounds strange to consider ignoring a consistency issue, it actually can be a valid strategy. It’s a question of how much business impact the inconsistency may have. - Apologize. This is an extension of the strategy to ignore. You don’t try to prevent inconsistencies, but you do make sure that you apologize when their effects come to light. - Resolve it. Tackle the problem head-on and actively resolve the inconsistency. This could be done by different means, such as the reconciliation jobs mentioned earlier, but this practice focuses on how BPMN can help by looking into the Saga pattern. Selecting the right strategy is a clear business decision, as none of them are right or wrong, but simply more or less well suited to the situation at hand. You should always think about the cost/value ratio. ### The Saga pattern and BPMN compensation The Saga pattern describes long-running transactions in distributed systems. The main idea is simple: when you can’t roll back tasks, you undo them. (The name Saga refers back to a paper written in the 1980s about long-lived transactions in databases.) Camunda supports this through BPMN compensation events, which can link tasks with their undo tasks. 1 Assume the customer was already added to the CRM system... 2 ...when an error occurred... 3 ...the process triggers the compensation to happen. This will roll back the business transaction. 4 All compensating activities of successfully completed tasks will be executed, in this case also this one. 5 As a result, the customer will be deactivated, as the API of the CRM system might not allow to simply delete it. --- ## Handling data in processes When using Camunda, you have access to a dynamic map of process variables, which lets you associate data to every single process instance (and local scopes in case of user tasks or parallel flows). Ensure you use these mechanisms in a lightweight and meaningful manner, storing just the relevant data in the process instance. Depending on your programming language, consider accessing your process variables in a type safe way, centralizing (simple and complex) type conversion and using constants for process variable names. ## Understanding data handling in Camunda When reading and interpreting a business process diagram, you quickly realize there is always data necessary for tasks, but also to drive the process through gateways to the correct next steps. Examine the following tweet approval process example: 1 The process instance starts with a freshly written `tweet` we need to remember. 2 We need to present this `tweet` so that the user can decide whether to `approve` it. 3 The gateway needs to have access to this information: was the tweet `approved`? 4 To publish the tweet, the service task again needs the `tweet` itself! Therefore, the tweet approval process needs two variables: | Variable name | Variable type | Sample value | | ------------- | ------------- | ---------------- | | `tweet` | String | "@Camunda rocks" | | `approved` | Boolean | true | In Camunda 8, [values are stored as JSON](/components/concepts/variables.md#variable-values). :::caution Camunda 7 handles variables slightly differently This best practice describes variable handling within Camunda 8. Process variables are handled slightly differently with Camunda 7. Consult the [Camunda 7 documentation](https://docs.camunda.org/manual/latest/user-guide/process-engine/variables/) for details. In essence, variable values are not handled as JSON and thus there are [different values](https://docs.camunda.org/manual/latest/user-guide/process-engine/variables/#supported-variable-values) supported. ::: You can dynamically create such variables by assigning an object of choice to a (string typed) variable name; for example, by passing a `Map` when [completing](/apis-tools/orchestration-cluster-api-rest/specifications/complete-user-task.api.mdx) the "Review tweet" task via the API: ``` // TODO: Double check! completeTask( taskId: "547811" variables: [ { name: "approved" value: true } ] ) ``` In Camunda, you do _not_ declare process variables in the process model. This allows for a lot of flexibility. Refer to recommendations below on how to overcome possible disadvantages of this approach. Consult the [docs about variables](/components/concepts/variables.md#variable-values) to learn more. Camunda does not treat BPMN **data objects** () as process variables. We recommend using them occasionally _for documentation_, but you need to [avoid excessive usage of data objects](../../modeling/creating-readable-process-models#avoiding-excessive-usage-of-data-objects). ## Storing just the relevant data Do not excessively use process variables. Process variables are subject to [size limitations](/components/concepts/variables.md#variable-size-limitation), so as a rule of thumb, store _as few variables as possible_ within Camunda. Keep this limitation in mind when designing your BPMN processes. For example, an AI agent’s context is stored in a process variable by default and counts toward this size limit. Alternatively, you can store the conversation in document storage. See [choose a memory storage backend](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess.md#choose-a-memory-storage-backend). ### Storing references only If you have leading systems already storing the business relevant data... ![Hold references only](handling-data-in-processes-assets/hold-references-only.svg) ...then we suggest you store references only (e.g. ID's) to the objects stored there. So instead of holding the `tweet` and the `approved` variable, the process variables would now, for example, look more like the following: | Variable name | Variable type | Value | | ------------- | ------------- | ----- | | `tweetId` | Long | 8213 | ### Use cases for storing payload Store _payload_ (actual business data) as process variables, if you.... - ...have data only of interest within the process itself (e.g. for gateway decisions). In case of the tweet approval process, even if you are using a tweet domain object, it might still be meaningful to hold the approved value explicitly as a process variable, because it serves the purpose to guide the gateway decision in the process. It might not be true if you want to keep track in the tweet domain objects regarding the approval. | Variable name | Variable type | Value | | ------------- | ------------- | ----- | | `tweetId` | Long | 8213 | | `approved` | Boolean | true | - ...communicate in a _message oriented_ style. For example, retrieving data from one system and handing it over to another system via a process. When receiving external messages, consider storing just those parts of the payload relevant for you, and not the whole response. This not only serves the goal of having a lean process variables map, it also makes you more independent of changes in the service's message interface. - ...want to use the process engine as kind of _cache_. For example, you cannot query relevant customer data in every step for performance reasons. - ...need to _postpone data changes_ in the leading system to a later step in the process. For example, you only want to insert the Tweet in the Tweet Management Application if it is approved. - ...want to track the _historical development_ of the data going through your process. - ...don't have a leading system for this data. ## Using constants and data accessors Avoid the copy/paste of string representations of your process variable names across your code base. Collect the variable names for a process definition in _constants_. For example, in Java: ```java public interface TwitterDemoProcessConstants { String VAR_NAME_TWEET = "tweet"; String VAR_NAME_APPROVED = "approved"; } ``` This way, you have much more security against typos and can easily make use of refactoring mechanisms offered by your IDE. However, if you also want to solve necessary type conversions (casting) or probably even complex serialization logic, we recommend that you use a **Data Accessor** class. It comes in two flavors: - A **Process Data Accessor**: Knows the names and types of all process variables of a certain process definition. It serves as the central point to declare variables for that process. - A **Process Variable Accessor**: Encapsulates the access to exactly one variable. This is useful if you reuse certain variables in different processes. Consider, for example, the BPMN "Publish on Twitter" task in the Tweet Approval Process: 1 We use a **TweetPublicationDelegate** to implement the "Publish on Twitter" task: ```java public class PublishTweetJobHandler implements JobHandler { public void handle(JobClient client, ActivatedJob job) throws Exception { String tweet = job.getVariablesAsType(TwitterDemoProcessVariables.class).getTweet(); // ... ``` The `tweet` variable is accessed in a type safe way. This reusable **Process Data Accessor** class could, for example, be a simple object. The Java client API can automatically deserialize the process variables as JSON into this object, while all process variables that are not found in that class are ignored. ```java public class TwitterDemoProcessVariables { private String tweet; private boolean approved; public String getTweet() { return tweet; } public void setTweet(String tweet) { this.tweet = tweet; } } ``` The getters and setters could further take care of additional serialization and deserialization logic for complex objects. Your specific implementation approach might differ depending on the programming language and framework you are using. ## Complex data as entities There are some use cases when it is clever to _introduce entities alongside the process_ to store complex data in a relational database. You can observe this logically as _typed process context_ where you create custom tables for your custom process deployment. Then, you can even use **Data** **Accessor** classes to access these entities in a convenient way. You will only store a reference to the entity's primary key (typically an artificial UUID) as real process variable within Camunda. Some people refer to this as **externalized process context**. There are a couple of advantages of this approach: - You can do very _rich queries_ on structured process variables via typical SQL. - You can apply custom _data migration strategies_ when deploying new versions of your process or services, which require data changes. - Data can be designed and modeled properly, even graphically by, for example, leveraging UML. It requires additional complexity by adding the need for a relational database and code to handle this. --- ## Local development with element templates and Camunda 8 Run When working with [element templates](/components/concepts/element-templates.md) in your local development environment using [Camunda 8 Run with Docker Compose](/self-managed/quickstart/developer-quickstart/c8run.md), ensure all dependencies are provisioned before you start. This guide explains how to set up element templates in your local environment. ## Prerequisites - [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) installed on your local machine. - Basic knowledge of [element templates with dependencies](/components/modeler/element-templates/element-template-with-dependencies.md). - Familiarity with [custom connectors](/components/connectors/manage-connector-templates.md). ## Provisioning secrets If your element templates use secrets, you must provide these values to the connector runtime. Add secrets to the `connector-secrets.txt` file in the root directory of your Camunda 8 Run setup. Use the following format, with one secret per line: ``` NAME=VALUE ``` These secrets will then be available in the connector runtime using the format `secrets.NAME`. For example: ``` MY_TOKEN=value AWS_KEY=keyValue ... ``` In this case, the `MY_TOKEN` secret can be referenced as `secrets.MY_TOKEN`. This applies when custom connectors are deployed as part of the Camunda 8 Run Docker Compose setup. If you choose to run connectors differently, as described in the [custom connector hosting guide](/components/connectors/custom-built-connectors/host-custom-connector.md#wiring-your-connector-with-a-camunda-cluster), configure secrets as environment variables instead. ## Provisioning a custom connector runtime You can add a custom connector runtime to Camunda 8 Run by copying the `.jar` file containing all connector dependencies into the `custom_connectors` directory in the root folder of your Camunda 8 Run setup. This guide uses a generic [connector template](https://github.com/camunda/connector-template-outbound) as a reference. 1. Clone the repository and run the following command to generate a deployable file: ```bash mvn clean verify package ``` This command creates a file named `target/connector-template-0.1.0-SNAPSHOT-with-dependencies.jar`. 2. Copy the `.jar` file into the `custom_connectors` directory. 3. Start Camunda 8 Run with Docker Compose. For example, from the Docker Compose directory in your Camunda 8 Run setup, run: ```bash docker compose up -d ``` 4. Your connector is ready to execute jobs when a process references it. If you use a different [connector runtime environment](/components/connectors/custom-built-connectors/connector-sdk.md#runtime-environments), ensure that secrets are also exposed to that runtime. ## Provisioning other dependencies ### Using Desktop Modeler Deploy element template dependencies using [Desktop Modeler](/components/modeler/desktop-modeler/index.md) by following the [self-managed deployment guide](/self-managed/components/modeler/desktop-modeler/deploy-to-self-managed.md). This process applies to BPMN diagrams, forms, DMN diagrams, and RPA scripts. ### Using the Cluster API For an automated approach, write scripts that use the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) to deploy dependencies. To deploy additional dependencies—such as forms, DMN diagrams, or subprocesses—send a [POST request](/apis-tools/orchestration-cluster-api-rest/specifications/create-deployment.api.mdx) with the relevant files. For example: ``` curl -L 'http://localhost:8080/v2/deployments' \ -H 'Accept: application/json' \ -F resources=@/pathToYourForm/user-signup.form ``` You will get a response containing the details of the deployed elements: ```json { "deployments": [ { "form": { "formKey": "KEY_OF_THE_FORM", "formId": "user-signup", "version": 1, "resourceName": "user-signup.form", "tenantId": "" } } ], "deploymentKey": "KEY_OF_THE_DEPLOYMENT", "tenantId": "" } ``` You can use element templates that reference the `user-signup.form`. ## Configure element templates in Desktop Modeler To make your element templates available in Desktop Modeler, see the [configuration guide](/components/modeler/desktop-modeler/element-templates/configuring-templates.md). ## Additional resources and next steps - [Using element templates in Desktop Modeler](/components/modeler/desktop-modeler/element-templates/using-templates.md) - [Run your first local Camunda 8 project](/guides/getting-started-example.md) - [Available connectors](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md) --- ## Routing events to processes To start a new process instance or to route a message to an already running instance, you have to choose the appropriate technology option to do so, like using the existing API or using customized possibilities including SOAP, AMQP, or Kafka. Leverage the possibilities of the universe of your runtime (like Java or Node.js) and the frameworks of your choice to support the technologies or protocols you need. ## Choosing the right BPMN event ### Start events Several BPMN start events can be used to start a new process instance. | | None Event | Message Event | Timer Event | Signal Event | Conditional Event | | ----------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------- | | | ![none start](/img/bpmn-elements/none-start.svg) | ![message start](/img/bpmn-elements/message-start.svg) | ![timer start](/img/bpmn-elements/timer-start.svg) | ![signal start](/img/bpmn-elements/signal-start.svg) | ![conditional start](/img/bpmn-elements/conditional-start.svg) | | Use when | You have only **one start event** or a start event which is clearly standard. | You have to differentiate **several start events**. | You want to automatically start process instances **time controlled**. | You need to start **several process instances** at once. Rarely used. | When a specific **condition** is met, a process instance is created. | | Supported for Execution | ✔ | ✔ | ✔ | ✔ | Determine occurrence of condition externally yourself and use the message event. | | | [Learn more](/components/modeler/bpmn/none-events/none-events.md) | [Learn more](/components/modeler/bpmn/message-events/message-events.md) | [Learn more](/components/modeler/bpmn/timer-events/timer-events.md) | [Learn more](/components/modeler/bpmn/signal-events/signal-events.md) | | 1 This none start event indicates the typical starting point. Note that only _one_ such start event can exist in one process definition. 2 This message start event is defined to react to a specific message type... 3 ...hence you can have _multiple_ message start events in a process definition. In this example, both message start events seems to be exceptional cases - for equivalent cases we recommend to just use message instead of none start events. ### Intermediate events Several BPMN intermediate events (and the receive task) can be used to make a process instance _wait_ for and _react_ to certain triggers. | | Message Event | Receive Task | Timer Event | Signal Event | Conditional Event | | ----------------------- | ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | | ![message intermediate](/img/bpmn-elements/message-intermediate.svg) | ![task receive](/img/bpmn-elements/task-receive.svg) | ![timer intermediate](/img/bpmn-elements/timer-intermediate.svg) | ![signal intermediate](/img/bpmn-elements/signal-intermediate.svg) | ![conditional intermediate](/img/bpmn-elements/conditional-intermediate.svg) | | Use when | You route an incoming **message** to a specific and unique process instance. | As alternative to message events (to leverage BPMN boundary events, for example, for timeouts). | You want to make your process instance wait for a certain (point in) **time**. | You route an incoming **signal** to all process instances waiting for it. | When a specific **condition** is met, the waiting process instance moves on. | | Supported for Execution | ✔ | ✔ | ✔ | ✔ | Not yet supported in Camunda 8 | | | [Learn more](/components/modeler/bpmn/message-events/message-events.md) | [Learn more](/components/modeler/bpmn/receive-tasks/receive-tasks.md) | [Learn more](/components/modeler/bpmn/timer-events/timer-events.md) | [Learn more](/components/modeler/bpmn/signal-events/signal-events.md) | | Consider this example: 1 This intermediate message event causes the process instance to wait unconditionally for a _specific_ event... 2 ...whereas the intermediate message event attached to the boundary of an activity waits for an _optional_ event, potentially arriving while we are occupied with the activity. ## Reacting to process-internal events Events relevant for the process execution can occur from within the workflow engine itself. Consider the following loan application process - or at least the initial part with which the applicant's income is confirmed either via the employer or via the last income tax statement. 1 In case the employer does not confirm the income within three business days, a **timer event** triggers and a human clerk now tries to contact the employer and investigate the situation. 2 This could end with a successful income confirmation. However, it could also end with new findings regarding the applicant's employment status. We learn that the applicant is actually unemployed. 3 In this case, a **conditional event** watching this data (for example, a process variable changed by the user task) triggers and causes the process to reconsider the consequences of the new findings. A conditional event's condition expression is evaluated at it's "scope" creation time, too, and not just when variable data changes. For our example of a boundary conditional event, that means that the activity it is attached to could principally be left immediately via the boundary event. However, our process example evaluates the data via the exclusive gateway - therefore such a scenario is semantically impossible. ## Routing events from the outside to the workflow engine Most events actually occur somewhere external to the workflow engine and need to be routed to it. The core workflow engine is by design not concerned with the technical part of receiving external messages, but you can receive messages and route them to the workflow engine by the following ways: - Using API: Receive the message by means of your platform-specific activities such as connecting to a AMQP queue or processing a REST request and then route it to the process. - Using connectors: Configure a connector to receive messages such as Kafka records and rote it to the process. Note that this possibility works for Camunda 8 only. - Using the Processes MCP Server: Apply the [MCP start event element template](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-start-event.md) to a message start event, and the [Processes MCP Server](/apis-tools/processes-mcp/processes-mcp-overview.md) registers the process as a tool that MCP clients, such as AI agents, can call to start an instance. See [expose a process as an MCP tool](/components/agentic-orchestration/expose-process-as-mcp-tool.md). ### Starting process instance by BPMN process ID If you have only one starting point (none start event) in your process definition, you reference the process definition by the ID in the BPMN XML file. :::note This is the most common case and requires using the [`CreateProcessInstance`](/apis-tools/zeebe-api/gateway-service.md#createprocessinstance-rpc) API. ::: Example in Java: ```java processInstance = zeebeClient.newCreateInstanceCommand() .bpmnProcessId("invoice").latestVersion() .send() .exceptionally( throwable -> { throw new RuntimeException("Could not create new process instance", throwable); }); ``` Example in Node.js: ```js zbc.createWorkflowInstance({ bpmnProcessId: "invoice", }); ``` This starts a new process instance in the latest version of the process definition. You can also start a specific version of a process definition: ```java processInstance = zeebeClient.newCreateInstanceCommand() .bpmnProcessId("invoice").version(5) //... ``` or ```js zbc.createWorkflowInstance({ bpmnProcessId: "invoice", version: 6, }); ``` You can also use [`CreateProcessInstanceWithResult`](/apis-tools/zeebe-api/gateway-service.md#createprocessinstancewithresult-rpc) instead, if you want to block the execution until the process instance has completed. ### Starting process instance by message As soon as you have multiple possible starting points, you have to use named messages to start process instances. The API method is [`PublishMessage`](/apis-tools/zeebe-api/gateway-service.md#publishmessage-rpc): ```java client.newPublishMessageCommand() .messageName("message_invoiceReceived") // <1> .corrlationKey(invoiceId) // <2> .variables( // <3> //... ).send() .exceptionally( throwable -> { throw new RuntimeException("Could not publish message", throwable); }); ``` 1 Message name as defined in the BPMN. 2 Correlation key has to be provided, even if a start event does not require correlation. 3 _Payload_ delivered with the message. On one hand, now you do not have to know the key of the BPMN process. On the other hand, you cannot influence the version of the process definition used when starting a process instance by message. The message name for start events should be unique for the whole workflow engine - otherwise you might experience side effects you did not intend (like starting other processes too). ## Technology examples for messages sent by external systems In this section, we give examples for _technical messages_, which are received from other systems, typically by leveraging technologies like SOAP, REST, JMS, and others. 1 You will need a mechanism receiving that message and routing it to the workflow engine. That could be a direct API call to Camunda. It could also be a AMQP or Kafka consumer or a SOAP endpoint using the Camunda API internally. It could even be a hotfolder polled by some framework like Apache Camel. API examples for REST, AMQP, and Kafka are shown in [connecting the workflow engine with your world](../connecting-the-workflow-engine-with-your-world/). ## Using the Camunda BPMN framework If you use the **Camunda BPMN Framework** as described in the book ["Real Life BPMN"](https://page.camunda.com/wp-real-life-bpmn-book-excerpt) you will typically have message start events (even if you only have a single start event) to connect the surrounding human flows to the technical flow via messages: 1 This is a message start event, which allows you to show the collaboration between the human and the technical flows. However, it is the only the starting point of the technical pool and could be a none start event in terms of execution. If there is _exactly one message start event_ for the whole process definition, it can also be treated as if it were a none start event when starting a process instance. ## Sending messages to other processes If messages are exchanged between different processes deployed in the workflow engine you have to implement the communication yourself by writing some code that starts a new process instance. 1 Use some simple code on the sending side to route the message to a new process instance, for example by starting a new process instance by the BPMN ID in Java: ```java @JobWorker(type="routeInput") public void routeInput(@Variable String invoiceId) { Map variables = new HashMap(); variables.put("invoiceId", invoiceId); zeebeClient.newCreateInstanceCommand() .bpmnProcessId("invoice").latestVersion() .variables(variables) .send() .exceptionally( throwable -> { throw new RuntimeException("Could not create new process instance", throwable); }); } ``` 2 Use some simple code on the sending side to correlate the message to a running process instance, for example in Java: ```java @JobWorker(type="notifyOrder") public void notifyOrder(@Variable String orderId, @Variable String paymentInformation) { Map variables = new HashMap(); variables.put("paymentInformation", paymentInformation); zeebeClient.newPublishMessageCommand() .messageName("MsgPaymentReceived") .corrlationKey(orderId) .variables(variables) .send() .exceptionally( throwable -> { throw new RuntimeException("Could not publish message", throwable); }); } ``` ## Handling messages sent by a user Sometimes explicit "user tasks" are not an appropriate choice to involve a human user to participate in a process: the user does not want to observe a task in Tasklist, but rather have the possibility to actively trigger some action right at the time when it becomes necessary from a business perspective. The difference is which event gives the _active trigger_. 1 We did not model a user task in this process, as the user will not immediately be triggered. The user cannot do anything at the moment when the process enters this event. Instead, we made it wait for a "message" which is later triggered by a human user. 2 The accountant actually receives the "external trigger" by actively looking at new payments in the bank account. 3 Every new payment now has to be correlated to the right waiting process instance manually. In this situation it is often the better choice not to model a user task, but let the process wait for a "message" generated from a user. These scenarios are not directly supported by Camunda Tasklist. A custom search screen built for the accountant might allow you to observe and find orders waiting for a payment. By interacting with such a screen, the accountant communicates with those process instances all at once. When hitting a 'Paid' button, a piece of custom code using the API must now correlate the user's message to the affected process instance(s). --- ## Service integration patterns with BPMN When integrating systems and services, you can choose between various modeling possibilities in BPMN. This practice will give you an overview and advice on how to decide between alternatives. You will note that service tasks in general are a good choice, but there are also situations where you might want to switch to send and receive tasks or events. ## Understanding communication patterns Let's briefly examine the three typical communication patterns to integrate systems: - **Request/response using synchronous communication styles**: You use a synchronous protocol, like HTTP, and block for the result. - **Request/response using asynchronous communication styles**: You use asynchronous communication, for example, by sending messages via a message broker, but wait for a response message right after. Technically, these are two independent asynchronous messages, but the sender blocks until the response is received, hence logically making it a request/response. - **Asynchronous messages or events:** If a peer service needs a long time to process a request, the response is much later than the request, say hours instead of milliseconds. In this case, the response is typically handled as a separate message. Additionally, some of your services might also wait for messages or events that are not connected to a concrete request, especially in event-driven architectures. The following table gives a summary of the three options: | | Synchronous request/response | Asynchronous request/response | Asynchronous messages or events | | --------------------------------- | :--------------------------- | :---------------------------- | :------------------------------ | | **Business level** | Synchronous | Synchronous | Asynchronous | | **Technical communication style** | Synchronous | Asynchronous | Asynchronous | | **Example** | HTTP | AMQP, JMS | AMQP, Apache Kafka | You can dive more into communication styles in the webinar [Communication Between Loosely Coupled Microservices](https://page.camunda.com/wb-communication-between-microservices) ([slides](https://www.slideshare.net/BerndRuecker/webinar-communication-between-loosely-coupled-microservices), [recording](https://page.camunda.com/wb-communication-between-microservices) and [FAQ](https://blog.bernd-ruecker.com/communication-between-loosely-coupled-microservices-webinar-faq-a02708b3c8b5)). ## Integrating services with BPMN tasks Let’s look at using BPMN tasks to handle these communication patterns before diving into BPMN events later. ### Service task The [service task](/components/modeler/bpmn/service-tasks/service-tasks.md) is the typical element to implement synchronous request/response calls, such as REST, gRPC or SOAP. You should **always use service tasks for synchronous request/response**. ![Service task](service-integration-patterns-assets/service-task.png) ### Send task Technically, **send tasks behave exactly like service tasks**. However, the alternative symbol makes the meaning of sending a message easier to understand for some stakeholders. You **should use send tasks for sending asynchronous messages**, like AMQP messages or Kafka records. ![Send task](service-integration-patterns-assets/send-task.png) There is some gray area whenever you call a synchronous service that then sends an asynchronous message. A good example is email. Assume your process does a synchronous request/response call to a service that then sends an email to inform the customer. The call itself is synchronous because it gives you a confirmation (acknowledgement, or ACK for short) that the email has been sent. Now is the "inform customer" task in your process a service, or a send task? ![Asynchronous ACK](service-integration-patterns-assets/synchronous-ack.png) This question is not easy to answer and **depends on what your stakeholders understand more intuitively**. The more technical people are, the more you might tend towards a service task, as this is technically correct. The more you move towards the business side, the more you might tend to use a send task, as business people will consider sending an email an asynchronous message. In general, we tend to **let the business win** as it is vital that business stakeholders understand business processes. However, if you follow a microservice (or service-oriented architecture) mindset, you might argue that you don’t need to know exactly how customers are informed within the process. Hiding the information if the notification is synchronous or asynchronous is good to keep your process model independent of such choices, making it more robust whenever the implementation of the notification service changes. This is a very valid concern too, and might motivate for a service task. :::note In case you can’t easily reach a conclusion, save discussion time and just use a service task. ::: You could also argue to use send tasks to invoke synchronous request/response calls when you are not interested in the response. However, this is typically confusing, and we do not recommend this. ### Receive task A [receive task](/components/modeler/bpmn/receive-tasks/receive-tasks.md) waits for an asynchronous message. Receive tasks **should be used for incoming asynchronous messages or events**, like AMQP messages or Kafka records. ![Receive task](service-integration-patterns-assets/receive-task.png) Receive tasks can be used to receive the response in asynchronous request/response scenarios, which is discussed next. ### Service task vs. send/receive task combo For asynchronous request/response calls, you can use a send task for the request, and a following receive task to wait for the response: ![Send and receive task](service-integration-patterns-assets/send-and-receive-task.png) You can also use a service task, which is sometimes unknown even to advanced users. A service task can technically wait for a response that happens at any time, a process instance will wait in the service task, as it would in the receive task. ![Service task](service-integration-patterns-assets/service-task.png) Deciding between these options is not completely straightforward. You can find a table listing the decision criteria below. As a general rule-of-thumb, we recommend using **the service task as the default option for synchronous _and_ asynchronous request/response** calls. The beauty of service tasks is that you remove visual clutter from the diagram, which makes it easier to read for most stakeholders. This is ideal if the business problem requires a logically synchronous service invocation. It allows you to ignore the technical details about the protocol on the process model level. The typical counter-argument is that asynchronous technical protocols might lead to different failure scenarios that you have to care about. For example, when using a separate receive task, readers of the diagram almost immediately start to think about what happens if the response will not be received. But this also has the drawback that now business people might start discussing technical concerns, which is not necessarily good. Furthermore, this is a questionable argument, as synchronous REST service calls could also timeout. This is exactly the same situation, just hidden deeper in network abstraction layers, as every form of remote communication uses asynchronous messaging somewhere down in the network stack. On a technical level, you should always think about these failure scenarios. The talk [3 common pitfalls in microservice integration and how to avoid them](https://blog.bernd-ruecker.com/3-common-pitfalls-in-microservice-integration-and-how-to-avoid-them-3f27a442cd07) goes into more detail on this. On a business level, you should be aware of the business implications of technical failures, but not discuss or model all the nuts and bolts around it. However, there are also technical implications of this design choice that need to be considered. **Technical implications of using service tasks** You can keep a service task open and just complete it later when the response arrives, but in **to complete the service task, you need the _job instance key_** from Zeebe. This is an internal ID from the workflow engine. You can either: - Pass it around to the third party service which sends it back as part of the response message. - Build some kind of lookup table, where you map your own correlation information to the right job key. :::note Later versions of Zeebe might provide query possibilities for this job key based on user controlled data, which might open up more possibilities. ::: Using workflow engine internal IDs can lead to problems. For example, you might cancel and restart a process instance because of operational failures, which can lead to a new ID. Outstanding responses cannot be correlated anymore in such instances. Or, you might run multiple workflow engines which can lead to internal IDs only being unique within one workflow engine. All of this might not happen, but the nature of an internal ID is that it is internal and you have no control over it — which bears some risk. In practice, however, using the internal job instance key is not a big problem if you get responses in very short time frames (milliseconds). Whenever you have more long-running interactions, you should consider using send and receive tasks, or build your own lookup table that can also address the problems mentioned above. This is also balanced by the fact that service tasks are simply very handy. The concept is by far the easiest way to implement asynchronous request/response communication. The job instance key is generated for you and unique for every message interchange. You don’t have to think about race conditions or idempotency constraints yourself. [Timeout handling and retry logic](/components/concepts/job-workers.md#timeouts) is built into the service task implementation of Zeebe. There is also [a clear API to let the workflow engine know of technical or business errors](/components/concepts/job-workers.md#completing-or-failing-jobs). **Technical implications of using send and receive tasks** Using send and receive tasks means to use [the message concept built into Zeebe](/components/concepts/messages.md). This is a powerful concept to solve a lot of problems around cardinalities of subscriptions, correlation of the message to the right process instances, and verification of uniqueness of the message (idempotency). When using messages, you need to provide the correlation ID yourself. This means that the correlation ID is fully under your control, but it also means that you need to generate it yourself and make sure it is unique. You will most likely end up with generated UUIDs. You can leverage [message buffering](/components/concepts/messages.md#message-buffering) capabilities, which means that the process does not yet need to be ready to receive the message. You could, for example, do other things in between, but this also means that you will not get an exception right away if a message cannot be correlated, as it is simply buffered. This leaves you in charge of dealing with messages that can never be delivered. Retries are not built-in, so if you need to model a loop to retry the initial service call if no response is received. And (at least in the current Zeebe version), there is no possibility to trigger error events for a receive task, which means you need to model error messages as response payload or separate message types — both are discussed later in this post. A final note for high-performance environments: These powerful messaging capabilities do not come for free and require some overhead within the engine. For pure request/response calls that return within milliseconds, none of the features are truly required. If you are looking to build a high-performance scenario, using service tasks instead of message correlation for request/response calls, you can tune your overall performance or throughput. However, as with everything performance related, the devil is in the detail, so [reach out to us](/reference/contact.md) to discuss such a scenario in more depth. **Summary And recommendations** The following table summarizes the possibilities and recommendations. | Case | Synchronous request/response | Synchronous request/response | Asynchronous request/response | Asynchronous request/response | | :--------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | BPMN element | Service task | Send task | Service task | Send + receive task | | | ![Service task](/img/bpmn-elements/task-service.svg) | ![Send task](/img/bpmn-elements/task-send.svg) | ![Service task](/img/bpmn-elements/task-service.svg) | ![Send and receive task](/img/bpmn-elements/send-and-receive-task.png) | | Technical implications | | Behaves like a service task | A unique correlation ID is generated for you. You don’t have to think about race conditions or idempotency. Timeout handling and retry logic are built-in. API to flag business or technical errors. | Correlation ID needs to be generated yourself, but is fully under control. Message buffering is possible but also necessary. Timeouts and retries need to be modeled. BPMN errors cannot be used. | | Assessment | Very intuitive. | Might be more intuitive for fire and forget semantics, but can also lead to discussions. | Removes visual noise which helps stakeholders to concentrate on core business logic, but requires use of internal job instance keys. | More visual clutter, but also more powerful options around correlation and modeling patterns. | | Recommendation | Default option, use unless it is confusing for business stakeholders (e.g. because of fire and forget semantics of a task). | Use for fire and forget semantics, unless it leads to unnecessary discussions, in this case use service task instead. | Use when response is within milliseconds and you can pass the Zeebe-internal job instance key around. | Use when the response will take time (> some seconds), or you need a correlation ID you can control. | ## Integrating services with BPMN events Instead of using send or receive **tasks**, you can also use send or receive **events** in BPMN. ![Events vs tasks](service-integration-patterns-assets/events-vs-tasks.png) Let's first explore when you want to do that, and afterwards look into some more advanced patterns that become possible with events. ### Tasks vs. events The **execution semantics of send and receive events is identical with send and receive tasks**, so you can express the very same thing with tasks or events. However, there is one small difference that might be relevant: **only tasks can have boundary events**, which allows to easily model when you want to cancel waiting for a message: ![Boundary events](service-integration-patterns-assets/boundary-event.png) Despite this, the whole visual representation is of course different. In general, tasks are easier understood by most stakeholders, as they are used very often in BPMN models. However, in certain contexts, such as event-driven architectures, events might be better suited as the concept of events is very common. Especially, if you apply domain-driven design (DDD) and discuss domain events all day long, it might be intuitive that events are clearly visible in your BPMN models. Another situation better suited for events is if you send events to your internal reporting system besides doing “the real” business logic. Our experience shows that the smaller event symbols are often unconsciously treated as less important by readers of the model, leading to models that are easier to understand. | | Send task | Receive task | Send event | Receive event | | :------------- | :----------------------- | :----------------------- | :---------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------- | | Recommendation | Prefer tasks over events | Prefer tasks over events | Use only if you consistently use events over tasks and have a good reason for doing so (e.g. event-driven architecture) | Use only if you consistently use events over tasks and have a good reason for doing so (e.g. event-driven architecture) | :::note The choice about events vs. commands also [needs to be reflected in the naming of the element](../../modeling/naming-bpmn-elements), as a task emphasizes the action (e.g. "wait for response") and the event reflects what happened (e.g. "response received"). ::: ### Handling different response messages Very often the response payload of the message will be examined to determine how to move on in the process. ![Gateway handling response](service-integration-patterns-assets/response-gateway.png) In this case, you receive exactly one type of message for the response. As an alternative, you could also use different message types, to which the process can react differently. For example, you might wait for the validation message, but also accept a cancellation or rejection message instead: ![Boundary message event to capture different response messages](service-integration-patterns-assets/response-boundary-message-events.png) This modeling has the advantage that it is much easier to note the expected flow of the process (also called the happy path), with exceptions deviating from it. On the other hand, this pattern mixes receive tasks and events in one model, which can confuse readers. Keep in mind that it only works for a limited number of non-happy messages. To avoid the task/event mixture you could use a so-called event-based gateway instead, this gateway waits for one of a list of possible message types to be received: ![Event based gateway to capture different response messages](service-integration-patterns-assets/response-event-based-gateway.png) We typically try to avoid the event-based gateway, as it is hard to understand for non-BPMN professionals. At the same time, it shares the downside of the first pattern with the decision gateway after the receive task: the happy path cannot be easily spotted. As a fourth possibility, you can add event subprocesses, which get activated whenever some event is received while the process is still active in some other area. In the above example, you could model the happy path and model all deviations as event subprocesses. ![Event subprocess to capture different response messages](service-integration-patterns-assets/response-event-subprocess.png) This pattern is pretty handy, but also needs some explanation to people new to BPMN. It has one downside you need to know: once your process instance moves to the subprocess, you can’t easily go back to the typical flow. To some extent this problem can be solved by advanced modeling patterns like shown in the [allow for order cancellation anytime](../../modeling/building-flexibility-into-bpmn-models/#allow-for-order-cancellation-any-time) example. At the same time, the event subprocess has a superpower worth mentioning: you can now wait for cancellation messages in whole chunks of your process — it could arrive anytime. | | Receive task with boundary events | Payload and XOR-gateway | Event-based gateway | Event subprocess | | ----------------- | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | | ![Boundary Events](service-integration-patterns-assets/response-boundary-message-events.png) | ![XOR Gateway](service-integration-patterns-assets/response-gateway.png) | ![Event-based Gateway](service-integration-patterns-assets/response-event-based-gateway.png) | ![Event Subprocess](service-integration-patterns-assets/response-event-subprocess.png) | | Understandability | Easy | Very easy | Hard | Medium | | Assessment | Limitation on how many message types are possible | Happy path not easily visible | | Might need some explanation for readers of the model | | Recommendation | Use when it is important to observe message types in the visual, limit to two boundary message events | Use when there are more response types or if the response type can be treated as a result | Try to avoid | Use if you need bigger scopes where you can react to events | ### Message type on the wire != BPMN message type There is one important detail worth mentioning in the context of message response patterns: The message type used in BPMN models does not have to be exactly the message type you get on the wire. When you correlate technical messages, e.g. from AMQP, you typically write a piece of glue code that receives the message and calls the workflow engine API. This is described in [connecting the workflow engine with your world](../connecting-the-workflow-engine-with-your-world/), including a code example. In this glue code you can do various transformations, for example: - Messages on different message queues could lead to the same BPMN message type, probably having some additional parameter in the payload indicating the origin. - Some message header or payload attributes could be used to select between different BPMN message types being used. It is probably not best practice to be as inconsistent as possible between technical message types and BPMN message types. Still, the flexibility of a custom mapping might be beneficial in some cases. ## Hiding technical complexity behind call activities Whenever technical details of one service integration become complicated, you can think of creating a separate process model for the technicalities of the call and use a [call activity](/components/modeler/bpmn/call-activities/call-activities.md) in the main process. An example is given in chapter 7 of [Practical Process Automation](https://processautomationbook.com/): ![Hiding technical details behind call activity](service-integration-patterns-assets/hiding-technical-details-behind-call-activity.png) In the customer scenario, a document storage service was long-running, but could not do a real callback or response message for technical reasons (in short, firewall limitations). As a result, the document storage service needed to be regularly polled for the response. In the customer scenario, this was done by a "document storage adapter" process that leveraged workflow engine features to implement the polling every minute, and especially the persistent waiting in between. In the main business process, this technical adapter process was simply invoked via a call activity, meaning no technicalities bloated that diagram. --- ## Testing process definitions Test your executable BPMN processes as you would any software. When possible, write fast automated unit tests using a localized and isolated workflow engine. Before releasing, verify your implementation with integration tests in an environment that closely mirrors your production setup, which may include human-driven, exploratory integration tests. This best practice uses the following process example for incoming invoices that need to be approved: 1 Invoices need to be approved. 2 The invoice sender is notified about a rejection. 3 Approved invoices get processed. 4 If the approval task takes too long, the process takes an alternative path—in this case, the invoice is automatically approved. 5 If an error occurs while communicating with the archive system (assume you have an unreliable legacy system), the process takes a detour to handle this situation manually. ## Testing scopes There are three typical test scopes used when building process solutions: 1. **Unit tests**: Testing glue code or programming code you developed for your process solution. How to unit test your software itself is not discussed here, as this is a common practice for software development. 2. **Process tests**: Testing the expected behavior of the process model, including glue code and specifically the data flowing through the process model. These tests should run frequently, so they should behave like unit tests (quick turnaround, no need for external resources). 3. **Integration tests**: Testing the system in a close-to-production environment to ensure it works correctly. This is typically done before releasing a new version of your system. These tests include _human-driven_, _exploratory_ tests. ![Scopes](testing-process-definitions-assets/scopes.png) ## Writing process tests in Java This section describes how to write process tests as unit tests in Java. We are working on additional information for writing tests in other languages, such as Node.js or C#. When using Java, most customers use Spring Boot, so we describe this approach in this best practice. While this is a common setup for customers, it is not the only one. Find more examples of plain Java process tests in [Getting Started with Camunda Process Test](../../../apis-tools/testing/getting-started.md). If your process hands a step to an AI agent, the same Camunda Process Test setup applies, but the tool calls the agent makes are not fixed in advance. See [test your AI agents](/components/agentic-orchestration/evaluate-agents/test-ai-agents.md#step-4-handle-non-deterministic-flow-paths) for more details. ### Technical setup using Spring :::caution - Camunda Process Test was introduced with **Camunda 8.8**. - You must use **JUnit 5** in every test class. The `@Test` annotation you import must be `org.junit.jupiter.api.Test`. ::: 1. Use [_JUnit 5_](http://junit.org) as your unit test framework. 2. Use the [Camunda Spring Boot Starter](../../../apis-tools/camunda-spring-boot-starter/getting-started.md). 3. Use `@CamundaSpringProcessTest` to start a process engine. 4. Ensure you have Docker installed locally to use [TestContainers](../../../apis-tools/testing/getting-started.md#prerequisites), which is the easiest way to run tests. 5. Use assertions from [Camunda Process Test](../../../apis-tools/testing/assertions.md) to verify that your expectations about the process state are met. 6. Use a mocking framework of your choice (such as [Mockito](http://mockito.org)) to mock service methods and verify that services are called as expected. 7. Use utilities from [Camunda Process Test](../../../apis-tools/testing/utilities.md) to mock job workers you don't want to run (for example, connectors). The following code shows an example test: ```java @SpringBootTest( properties = { "camunda.client.worker.defaults.enabled=false", // disable job workers and enable them selectively "camunda.client.worker.override.archive-invoice.enabled=true", }) @CamundaSpringProcessTest public class InvoiceApprovalTest { @Autowired private CamundaClient client; @Autowired private CamundaProcessTestContext processTestContext; @Autowired private ObjectMapper objectMapper; // Mock services that are called from the job workers @MockitoBean private ArchiveService archiveService; @MockitoBean private AccountingService accountingService; // Sample data used private final String invoiceJson = """ { "id": "INV-1001", "amount": 12000, "currency": "EUR", "supplier": { "id": "0815", "name": "Acme GmbH" }, "contactEmail": "accounting@acme.com" }"""; @Test public void happyPath() throws Exception { final HashMap variables = new HashMap(); variables.put("approver", "Zee"); variables.put("invoice", objectMapper.readTree(invoiceJson)); // After all preparations, start the process instance final var processInstance = client .newCreateInstanceCommand() .bpmnProcessId("Process_InvoiceApproval") .latestVersion() .variables(variables) .send() .join(); // assert the User Task was created assertThat(byElementId("UserTask_ApproveInvoice")).isCreated().hasAssignee("Zee"); // and simulate the user completing it processTestContext.completeUserTask(byElementId("UserTask_ApproveInvoice"), Map.of("approved", true)); // This should make the process instance execute to completion assertThat(processInstance) .hasCompletedElementsInOrder( byId("StartEvent_InvoiceReceived"), byId("UserTask_ApproveInvoice"), byId("ServiceTask_ArchiveInvoice"), byId("ServiceTask_AddInvoiceAccounting"), byId("EndEvent_InvoiceApproved")) .isCompleted(); // verify that side effects have happened Mockito.verify(archiveService).archiveInvoice("INV-1001", objectMapper.readTree(invoiceJson)); Mockito.verify(accountingService).addInvoiceToAccount("0815", "INV-1001"); } ``` :::note The complete source code for this example test is available on [GitHub](https://github.com/camunda/camunda/tree/main/testing/camunda-process-test-example/src/test/java/io/camunda/InvoiceApprovalTest.java). ::: ### Test scope and mocking In a test case like this, you want to test the executable BPMN process definition, plus all the glue code that logically belongs to the process definition in a broader sense. Typical examples of glue code you want to include in a process test are: - Worker code, typically connected to a service task - Expressions (FEEL) used in your process model for gateway decisions or input/output mappings - Other glue code, for example, your own Client API (probably exposed via REST) that performs data mapping before calling the Camunda Client. The following illustration shows this for the invoice approval example: ![Process test scope example](testing-process-definitions-assets/process-test-scope-example.png) Workflow engine-independent business code should _not_ be included in the tests. In the invoice approval example, the `ArchiveService` will be mocked, and the `ArchiveInvoiceWorker` will read and transform process variables and call this mock. This way, you can test the process model, the glue code, and the data flow in your process test without calling out to the real archive system. The following code examples highlight the important aspects around mocking. The `ArchiveInvoiceWorker` is executed as part of the test. It does input data mapping **(1)** and also translates a specific business exception into a BPMN error **(2)**: ```java @Component public class ArchiveInvoiceWorker { private final ArchiveService service; public ArchiveInvoiceWorker(final ArchiveService service) { this.service = service; } @JobWorker(type = "archive-invoice") public void handleJob( @Variable("invoiceId") final String invoiceId, // <1> @Variable("invoice") final JsonNode invoiceJson) { try { service.archiveInvoice(invoiceId, invoiceJson); } catch (WiredLegacyException e) { // <2> throw new BpmnError( "LEGACY_ERROR_ARCHIVE", "The archive system had a problem: " + e.getMessage()); } } } ``` The `ArchiveService` is considered a business service (it could, for example, wrap the archive system client SDK to make the appropriate remote calls) and should _not_ be executed during the test. This is why this interface is mocked in the test case: ```java @MockitoBean private ArchiveService archiveService; @Test public void happyPath() throws Exception { // ... // Using Mockito you can verify a business method was called with the expected parameters Mockito.verify(archiveService).archiveInvoice("INV-1001", objectMapper.readTree(invoiceJson)); } @Test void testArchiveSystemError() throws Exception { // Using Mockito you can define what should happen when a method is called, in this case an exception is thrown to simulate a business error doThrow(new WiredLegacyException()).when(archiveService).archiveInvoice(anyString(), any()); //... } ``` Some workers might not delegate to a proper service class, which you can easily mock. The prime example is connectors. The invoice process uses the REST connector to trigger the invoice rejection via some REST API. To avoid calling the REST endpoint, you can mock the job worker that would be provided by the connector runtime: ```java @Test public void testRejectionPath() throws Exception { processTestContext.mockJobWorker("io.camunda:http-json:1").thenComplete(); // ... } ``` You could also mock the REST endpoint, which we touch on later discussing integration tests. Some projects consider REST mocking part of the unit test scope, and this is generally also fine, even if we see it as integration test scope by default. You can use the same [utilities from Camunda Process Test](../../../apis-tools/testing/utilities.md) to mock other workers, where you simply do not want to run the job worker itself. Maybe the implementation is not clean, but beyond your control. However, we advise to use a proper service interface whenever possible instead of job worker mocking. ```java // Define the mock final JobWorkerMock addInvoiceJobWorkerMock = processTestContext .mockJobWorker("add-invoice-to-accounting") .withHandler( (jobClient, job) -> { jobClient .newCompleteCommand(job) // .variables(null) // We could now also simulate setting some response values .send() .join(); }); // ... drive the process ... // and assert: assertThat(addInvoiceJobWorkerMock.getInvocations()) .as("add-invoice-to-accounting job worker called") .isEqualTo(1); assertThat(addInvoiceJobWorkerMock.getActivatedJobs().get(0).getVariablesAsMap()) .containsEntry("invoiceId", "INV-1001"); ``` ### Drive the process and assert the state For tests, you drive the process from wait state to wait state and assert that you observe the expected process and variable states. For example, you might implement a test for the scenario when an invoice gets approved and processed without errors: ```java @Test public void happyPath() throws Exception { final HashMap variables = new HashMap(); variables.put("approver", "Zee"); variables.put("invoice", objectMapper.readTree(invoiceJson)); // Kick off the process instance // <1> final var processInstance = client .newCreateInstanceCommand() .bpmnProcessId("Process_InvoiceApproval") .latestVersion() .variables(variables) .send() .join(); // assert the User Task and simulate a human decision // <2> assertThat(byElementId("UserTask_ApproveInvoice")).isCreated().hasAssignee("Zee"); processTestContext.completeUserTask( byElementId("UserTask_ApproveInvoice"), Map.of("approved", true)); // This should make the process instance execute till the end // <3> assertThat(processInstance) .hasCompletedElementsInOrder( byId("StartEvent_InvoiceReceived"), byId("UserTask_ApproveInvoice"), byId("ServiceTask_ArchiveInvoice"), byId("ServiceTask_AddInvoiceAccounting"), byId("EndEvent_InvoiceApproved")) .isCompleted(); // verify that side effects have happened // <4> verify(archiveService).archiveInvoice("INV-1001", objectMapper.readTree(invoiceJson)); verify(accountingService).addInvoiceToAccount("0815", "INV-1001"); } ``` 1. Create a new process instance. You may want to use some glue code to start your process (e.g. the REST API facade), or also create helper methods within your test class. 2. Drive the process through its wait states, e.g. by completing a waiting user task. 3. Assert that your process is in the expected state. 4. Verify with your mocking library that your business service methods were called as expected. Be careful not to "overspecify" your test method by asserting too much. Your process definition will likely evolve in the future and such changes should break as little test code as possible, but just as much as necessary! As a rule of thumb _always_ assert that the expected _external effects_ of your process really took place (e.g. that business services were called as expected). Additionally, carefully choose which aspects of _internal process state_ are important enough so that you want your test method to warn about any related change later on. ### Testing your process in chunks Divide and conquer by _testing your process in chunks_. Consider the important chunks and paths the invoice approval process consists of: 1 The _happy path_: The invoice gets approved. 2 The invoice gets rejected. 3 A timeout on waiting for approval leads to an automatic approval. 4 An approved invoice can't get archived. #### Testing the happy path The happy path is kind of the default scenario with a positive outcome, so no exceptions or errors or deviations are experienced. Fully test the happy path in one (big) test method. This makes sure you have one consistent data flow in your process. Additionally, it is easy to read and to understand, making it a great starting point for new developers to understand your process and process test case. You were already exposed to the happy path in our example, which is the scenario that the invoice gets approved: ```java @Test public void happyPath() throws Exception { final HashMap variables = new HashMap(); variables.put("approver", "Zee"); variables.put("invoice", objectMapper.readTree(invoiceJson)); // Kick off the process instance // <1> final var processInstance = client .newCreateInstanceCommand() .bpmnProcessId("Process_InvoiceApproval") .latestVersion() .variables(variables) .send() .join(); // assert the User Task and simulate a human decision // <2> assertThat(byElementId("UserTask_ApproveInvoice")).isCreated().hasAssignee("Zee"); processTestContext.completeUserTask( byElementId("UserTask_ApproveInvoice"), Map.of("approved", true)); // This should make the process instance execute till the end // <3> assertThat(processInstance) .hasCompletedElementsInOrder( byId("StartEvent_InvoiceReceived"), byId("UserTask_ApproveInvoice"), byId("ServiceTask_ArchiveInvoice"), byId("ServiceTask_AddInvoiceAccounting"), byId("EndEvent_InvoiceApproved")) .isCompleted(); // verify that side effects have happened // <4> verify(archiveService).archiveInvoice("INV-1001", objectMapper.readTree(invoiceJson)); verify(accountingService).addInvoiceToAccount("0815", "INV-1001"); } ``` #### Testing detours Test _forks/detours_ from the happy path as well as _errors/exceptional_ paths as chunks in separate test methods. This allows to unit test in meaningful units. The tests for the exceptional paths are basically very similar to the happy path in our example. 2 The invoice gets rejected: ```java @Test public void testRejectionPath() throws Exception { final HashMap variables = new HashMap(); variables.put("approver", "Zee"); variables.put("invoice", objectMapper.readTree(invoiceJson)); // We skip HTTP for the simple unit test - mock the http connector processTestContext.mockJobWorker("io.camunda:http-json:1").thenComplete(); // Kick of the process instance final var processInstance = client .newCreateInstanceCommand() .bpmnProcessId("Process_InvoiceApproval") .latestVersion() .variables(variables) .send() .join(); // assert the User Task and simulate a human decision assertThat(byElementId("UserTask_ApproveInvoice")).isCreated().hasAssignee("Zee"); processTestContext.completeUserTask( byElementId("UserTask_ApproveInvoice"), Map.of( // "approved", false, // "rejectionReason", "it is a test case :-)")); // This should make the process instance execute till the end assertThat(processInstance) .hasCompletedElementsInOrder( byId("StartEvent_InvoiceReceived"), byId("UserTask_ApproveInvoice"), byId("Gateway_Approved"), byId("ServiceTask_SendRejection"), byId("EndEvent_InvoiceRejected")) .isCompleted(); } ``` 3 A timeout on waiting for approval leads to an automatic approval: ```java @Test public void testApprovalTimeout() throws Exception { final HashMap variables = new HashMap(); variables.put("approver", "Zee"); variables.put("invoice", objectMapper.readTree(invoiceJson)); final var processInstance = client .newCreateInstanceCommand() .bpmnProcessId("Process_InvoiceApproval") .latestVersion() .variables(variables) .send() .join(); // assert the User Task and simulate the timeout assertThat(processInstance).hasActiveElements("UserTask_ApproveInvoice"); processTestContext.increaseTime(Duration.ofDays(5)); // This should make the process instance auto approve and run till the end assertThat(processInstance) .isCompleted() .hasCompletedElementsInOrder( byId("StartEvent_InvoiceReceived"), byId("ServiceTask_ArchiveInvoice"), byId("ServiceTask_AddInvoiceAccounting"), byId("EndEvent_InvoiceApproved")) .hasTerminatedElements(byId("UserTask_ApproveInvoice")); } ``` 4 An approved invoice can't get archived: ```java @Test public void testArchiveSystemError() throws Exception { final HashMap variables = new HashMap(); variables.put("approver", "Zee"); variables.put("invoice", objectMapper.readTree(invoiceJson)); doThrow(new WiredLegacyException()).when(archiveService).archiveInvoice(anyString(), any()); final var processInstance = client .newCreateInstanceCommand() .bpmnProcessId("Process_InvoiceApproval") .latestVersion() .variables(variables) .send() .join(); // approve the request assertThat(byElementId("UserTask_ApproveInvoice")).isCreated(); processTestContext.completeUserTask(byElementId("UserTask_ApproveInvoice"), Map.of("approved", true)); // This should lead to the exception being thrown, causing the process to end up in the user task designed to handle the problem. assertThat(byElementId("UserTask_ManuallyArchiveInvoice")) .isCreated(); // The test for .hasCandidateGroup("archive-team") is probably not worth implementing // as it limits flexibility in model changes. processTestContext.completeUserTask(byElementId("UserTask_ManuallyArchiveInvoice")); assertThat(processInstance) .isCompleted() .hasCompletedElementsInOrder( byId("StartEvent_InvoiceReceived"), byId("UserTask_ApproveInvoice"), byId("UserTask_ManuallyArchiveInvoice"), byId("ServiceTask_AddInvoiceAccounting"), byId("EndEvent_InvoiceApproved")) .hasTerminatedElements(byId("ServiceTask_ArchiveInvoice")); verify(accountingService).addInvoiceToAccount("0815", "INV-1001"); } ``` ## Integration tests Test the process in a close-to-real-life environment. This verifies that it really works before releasing a new version of your process definition, which includes _human-driven_, _exploratory_ tests. Clearly _define your goals_ for integration tests! Goals could be: - End user & acceptance tests - Complete end-to-end tests - Performance & load tests, etc. Carefully consider _automating_ tests on scope 3. You need to look at the overall effort spent on writing test automation code and maintaining it when compared with executing human-driven tests for your software project's lifespan. The best choice depends very much on the frequency of regression test runs. Most effort is typically invested in setting up proper test data in surrounding systems. Configure your tests to be dedicated integration tests, and separate them from unit or process tests. You can use typical industry standard tools for integration testing together with Camunda. ### Mocking REST calls Especially when using the Connector framework, there might be relevant logic to test in configuration of a connector, especially the input and output data mapping. To test those, you typically want to mock the endpoint, rather than the job worker. In the invoice approval example, the `Send invoice rejection` task leverages an outbound REST connector. The service task might look like this in the BPMN XML: ```xml ``` You can mock the REST endpoint using the Spring Boot integration of [WireMock](http://wiremock.org/), allowing you to stub the endpoint in your JUnit test and make it accessible to the TestContainers runtime. 1. Add the required [WireMock Spring Boot](https://wiremock.org/docs/spring-boot/) dependency to your project (`org.wiremock.integrations:wiremock-spring-boot`). 2. Add the annotation `@EnableWireMock` to your test class to start the WireMock server. 3. Use the secrets in Camunda to configure the endpoint of the REST call, which is best practice anyway to configure the URL in the environment. In the test you need to set it to the URL containing of the hostname `host.testcontainers.internal` and the WireMock server port. 4. Make sure the connector runtime is enabled in the test case, so that the out-of-the-box REST connector is executed. 5. Expose the WireMock server port to the TestContainers runtime before running the test case. Here is the relevant source code: ```java @EnableWireMock @SpringBootTest( properties = { "camunda.client.worker.defaults.enabled=false", "camunda.process-test.connectors-enabled=true", "camunda.process-test.connectors-secrets.INVOICE_REJECTION_URL=" + "http://host.testcontainers.internal:${wiremock.server.port}" }) @CamundaSpringProcessTest public class InvoiceApprovalIntegrationTest { @Value("${wiremock.server.port}") private int wireMockPort; @BeforeEach void setup() { Testcontainers.exposeHostPorts(wireMockPort); } @Test public void testRejectionPath() throws Exception { // configure mock behavior stubFor(post("/reject").willReturn(aResponse().withStatus(200).withBody("ok"))); // Now drive the test case as in a unit test shown above ... // Verify the mock was called verify( postRequestedFor(urlEqualTo("/reject")) .withRequestBody( equalToJson( """ { "invoiceId": "INV-1001", "rejectionReason": "it is a test case :-)" }"""))); } ``` --- ## Writing good workers [Service tasks](/components/modeler/bpmn/service-tasks/service-tasks.md) within Camunda 8 require you to set a task type and implement [job workers](/components/concepts/job-workers.md) who perform whatever needs to be performed. This describes that you might want to: 1. Write all glue code in one application, separating different classes or functions for the different task types. 2. Write idempotent workers, because Zeebe uses an at-least-once strategy and can deliver the same job more than once. Also read and write as little data as possible from/to the process. 3. If you use Java 21 or later, prefer virtual threads for parallel workers that perform blocking I/O. Use reactive or async code when your runtime already uses it, or when you need extremely high throughput or low latency. ## Organize glue code and workers in process solutions Assume the following order fulfillment process, that needs to invoke three synchronous REST calls to the responsible systems (payment, inventory, and shipping) via custom glue code: ![order fulfillment example](writing-good-workers-assets/order-fulfillment-process.png) Should you create three different applications with a worker for one task type each, or would it be better to process all task types within one application? As a rule of thumb, we recommend implementing **all glue code in one application**, which then is the so-called **process solution** (as described in [Practical Process Automation](https://processautomationbook.com/)). This process solution might also include the BPMN process model itself, deployed during startup. Thus, you create a self-contained application that is easy to version, test, integrate, and deploy. ![Process solution](writing-good-workers-assets/process-solution.png) Figure taken from [Practical Process Automation](https://processautomationbook.com/) Thinking of Java, the three REST invocations might live in three classes within the same package (showing only two for brevity): ```java public class RetrieveMoneyWorker { @JobWorker(type = "retrieveMoney", autoComplete = false) public void retrieveMoney(final JobClient client, final ActivatedJob job) { // ... code } } ``` ```java public class FetchGoodsWorker { @JobWorker(type = "fetchGoods", autoComplete = false) public void fetchGoods(final JobClient client, final ActivatedJob job) { // ... code } } ``` You can also pull the glue code for all task types into one class. Technically, it does not make any difference and some people find that structure in their code easier. If in doubt, the default is to create one class per task type. There are exceptions when you might not want to have all glue code within one application: 1. You need to specifically control the load for one task type, like _scaling it out_ or _throttling it_. For example, if one service task is doing PDF generation, which is compute-intensive, you might need to scale it much more than all other glue code. On the other hand, it could also mean limiting the number of parallel generation jobs due to licensing limitations of your third-party PDF generation library. 2. You want to write glue code in different programming languages, for example, because writing specific logic in a specific language is much easier (like using Python for certain AI calculations or Java for certain mainframe integrations). In this case, you would spread your workers into different applications. Most often, you might still have a main process solution that will also still deploy the process model. Only specific workers are carved out. ## Write idempotent workers Zeebe uses an **at-least-once** execution strategy for jobs. A job is only completed when the engine receives and commits the complete job request. If a worker crashes, loses its connection, or exceeds the [job timeout](/components/concepts/job-workers.md#timeouts) before it can complete the job, the engine gives the job to another worker. This guarantees that the job handler runs at least once, but it also means the handler can run more than once for the same job, possibly with side effects already applied. :::warning Your workers **must** be idempotent. Running the handler more than once for the same job must leave the application in the same state as running it once. Non-idempotent workers can cause duplicate payments, duplicate orders, or other inconsistent data. ::: Make idempotency a conscious design decision for every worker, not an afterthought. Strategies include: - **Natural idempotency**: some operations can safely run any number of times because they only set state, for example `confirmCustomer()`. - **Business idempotency**: use a business identifier to detect duplicate calls, for example `createCustomer(email)`. - **Custom idempotency handling**: generate a unique ID or hash, pass it with the call, and let the target system reject duplicates, for example `charge(transactionId, amount)`. To learn how workers handle transactions, exceptions, and custom idempotency, see [Deal with problems and exceptions](../dealing-with-problems-and-exceptions/#writing-idempotent-workers). ## Data minimization in workers If performance or efficiency matters in your scenario, there are two rules about data in your workers you should be aware of: 1. Minimize what data you read for your job. In your job client, you can define which process variables you will need in your worker, and only these will be read and transferred, saving resources on the broker as well as network bandwidth. 2. Minimize what data you write on job completion. You should explicitly not transmit the input variables of a job upon completion, which might happen easily if you simply reuse the map of variables you received as input for submitting the result. Not transmitting all variables saves resources and bandwidth, but serves another purpose as well: upon job completion, these variables are written to the process and might overwrite existing variables. If you have parallel paths in your process (e.g. [parallel gateway](/components/modeler/bpmn/parallel-gateways/parallel-gateways.md), [multiple instance](/components/modeler/bpmn/multi-instance/multi-instance.md)) this can lead to race conditions that you need to think about. The less data you write, the smaller the problem. While the easiest way is to avoid large variables, one option to keep things light during job activation is to use the `FetchVariables` parameter. Remember, by default, when this parameter is omitted, the job payload will contain _all_ variables visible within the scope ([see the variables documentation for more on that](../../concepts/variables.md). This could mean tens or more variables, of arbitrary size, and it can be difficult to estimate how much this will represent in general. We recommend you use the `FetchVariables` parameter, and only fetch the variables which your job handler needs. This will keep the amount of data transferred to a minimum, and will greatly help performance. ## Scale workers If you need to process a lot of jobs, you need to think about optimizing your workers. Workers can control the number of jobs retrieved at once. In a busy system it makes sense to not only request one job, but probably 20 or even up to 50 jobs in one remote request to the workflow engine, and then start working on them locally. In a lesser utilized system, long polling is used to avoid delays when a job comes in. Long polling means the client’s request to fetch jobs is blocked until a job is received (or some timeout hits). Therefore, the client does not constantly need to ask. You will have jobs in your local application that need to be processed. The worst case in terms of scalability is that you process the jobs sequentially one after the other. While this sounds bad, it is still a valid approach for many use cases, as most projects do not need any parallel processing in the worker code as they simply do not care whether a job is executed a second earlier or later. Think of a business process that is executed only some hundred times per day and includes mostly human tasks — a sequential worker is totally sufficient. In this case, you can skip this paragraph section. However, you might need to do better and process jobs in parallel. In such a case, you should read on and understand the difference between writing blocking code on platform threads, blocking code on virtual threads, and non-blocking code. ### Blocking / synchronous code and thread pools With blocking code a thread needs to wait (is blocked) until something finishes before it can move on. In the above example, making a REST call requires the client to wait for IO — the response. The CPU cannot compute anything during this time period, however, the thread cannot do anything else. Assume that your worker shall invoke 20 REST requests, each taking around 100ms, this will take 2s in total to process. Your throughput can’t go beyond 10 jobs per second with one thread. A common approach to scaling throughput beyond this limit is to leverage a thread pool. This works as blocked threads are not actively consuming CPU cores, so you can run more threads than CPU cores — since they are only waiting for I/O most of the time. In the above example with 100ms latency of REST calls, having a thread pool of 10 threads increases throughput to 100 jobs/second. The downside of using thread pools is that you need to have a good understanding of your code, thread pools in general, and the concrete libraries being used. Typically, we do not recommend configuring platform thread pools yourself. In Java 21 and later, prefer virtual threads for workers that perform blocking I/O. ### Blocking code with virtual threads Virtual threads let you keep a straightforward blocking programming model while avoiding the cost of assigning one platform thread to every blocked operation. This makes them a good default for Java workers that spend most of their time waiting for I/O, such as REST calls or database requests. Use virtual threads when you run on Java 21 or later and want to process many I/O-bound jobs in parallel without rewriting your worker code into a reactive style. Reactive programming can still be useful for extremely high-throughput or low-latency scenarios where the lower overhead matters enough to justify the added complexity. ### Non-blocking / reactive code Reactive programming uses a different approach to achieve parallel work: extract the waiting part from your code. With a reactive HTTP client you will write code to issue the REST request, but then not block for the response. Instead, you define a callback as to what happens if the request returns. Most of you know this from JavaScript programming. Thus, the runtime can optimize the utilization of threads itself, without you the developer even knowing. ### Recommendation In Java 21 and later, prefer virtual threads for I/O-bound job workers that need parallel processing. They keep worker code easier to read while avoiding most of the scalability limits of platform threads. Use reactive programming when your client stack already uses it, or when you need extremely high throughput or low latency and have measured that the lower overhead is worth the complexity. ## Performance best-practices Most of the business logic in your process models will likely end up being worked on as a job. As such, optimizing how jobs are handled in Zeebe can have a big impact on the performance of your system as a whole. Here are some best practices to keep things running smoothly. ### Reduce latency with job streaming We recommend enabling [job streaming](../../concepts/job-workers.md#job-streaming) in order to reduce latency to a maximum. Essentially, when using long polling, your job workers have to periodically poll every partition in your Zeebe cluster to check if there are new jobs available. Additionally, they have to balance polling aggressively with minimizing their impact on the cluster, which still has to handle all requests, even when no jobs are available. In large clusters, this can add a noticeable delay in the order of seconds, which can be unacceptable for certain workloads. :::note You can read more about the difference between long polling and job streaming [in this blog post](https://camunda.com/blog/2024/03/reducing-job-activation-delay-zeebe/). ::: As such, we recommend using job streaming if possible. ## Client library examples Let’s go through a few code examples using Java, Node.js, and C#, using the corresponding client libraries. All [code is available on GitHub](https://github.com/berndruecker/camunda-cloud-clients-parallel-job-execution) and a [walk through recording is available on YouTube](https://youtu.be/ZHKz9l5yG3Q). ### Java Using the [Java Client](https://github.com/camunda/camunda-platform-get-started/tree/master/java) you can write worker code like this: ```java client.newWorker().jobType("retrieveMoney") .handler((jobClient, job) -> { //... }).open(); ``` The [Camunda Spring Boot Starter](/apis-tools/camunda-spring-boot-starter/getting-started.md) provides a more elegant way of writing this, but also uses a normal worker from the Java client underneath. In this case, your code might look like this: ```java @JobWorker(type = "retrieveMoney", autoComplete = false) public void retrieveMoney(final JobClient client, final ActivatedJob job) { //... } ``` In the background, a worker starts a polling component and [a thread pool](https://github.com/camunda-cloud/zeebe/blob/d24b31493b8e22ad3405ee183adfd5a546b7742e/clients/java/src/main/java/io/camunda/zeebe/client/impl/ZeebeClientImpl.java#L179-L183) to [handle the polled jobs](https://github.com/camunda/camunda/blob/d24b31493b8e22ad3405ee183adfd5a546b7742e/clients/java/src/main/java/io/camunda/zeebe/client/impl/worker/JobPoller.java#L109-L111). The [**default thread pool size is one**](https://github.com/camunda-cloud/zeebe/blob/760074f59bc1bcfb483fab4645501430f362a475/clients/java/src/main/java/io/camunda/zeebe/client/impl/ZeebeClientBuilderImpl.java#L49). If you need more, you can enable a thread pool: ```java ZeebeClient client = ZeebeClient.newClientBuilder() .numJobWorkerExecutionThreads(5) .build(); ``` In the Camunda Spring Boot Starter, you can do this using a [configuration](/apis-tools/camunda-spring-boot-starter/configuration.md#execution-threads). Now, you can **leverage blocking code** for your REST call, for example, the `RestTemplate` inside Spring: ```java @JobWorker(type = "rest", autoComplete = false) public void blockingRestCall(final JobClient client, final ActivatedJob job) { LOGGER.info("Invoke REST call..."); String response = restTemplate.getForObject( // <-- blocking call PAYMENT_URL, String.class); LOGGER.info("...finished. Complete Job..."); client.newCompleteCommand(job.getKey()).send() .join(); // <-- this blocks to wait for the response LOGGER.info(counter.inc()); } ``` Doing so **limits** the degree of parallelism to the number of threads you have configured. You can [observe in the logs](https://github.com/berndruecker/camunda-cloud-clients-parallel-job-execution/blob/main/results/java-blocking-thread-1.log) that jobs are executed sequentially when running with one thread ([the code is available on GitHub)](https://github.com/berndruecker/camunda-cloud-clients-parallel-job-execution/blob/main/java-worker/src/main/java/io/berndruecker/experiments/cloudclient/java/RestInvocationWorker.java): ``` 10:57:00.258 [pool-4-thread-1] Invoke REST call… 10:57:00.258 [ault-executor-0] Activated 32 jobs for worker default and job type rest 10:57:00.398 [pool-4-thread-1] …finished. Complete Job… 10:57:00.446 [pool-4-thread-1] …completed (1). Current throughput (jobs/s ): 1 10:57:00.446 [pool-4-thread-1] Invoke REST call… 10:57:00.562 [pool-4-thread-1] …finished. Complete Job… 10:57:00.648 [pool-4-thread-1] …completed (2). Current throughput (jobs/s ): 2 10:57:00.648 [pool-4-thread-1] Invoke REST call… 10:57:00.764 [pool-4-thread-1] …finished. Complete Job…10:57:00.805 [pool-4-thread-1] …completed (3). Current throughput (jobs/s ): 3 ``` If you experience a large number of jobs, and these jobs are waiting for IO the whole time — as REST calls do — Java 21 virtual threads are usually a good default. You can also use **reactive programming**, especially for extremely high-throughput or low-latency scenarios where you have measured that its lower overhead matters. For the REST call, this means for example the Spring WebClient: ```java @JobWorker(type = "rest", autoComplete = false) public void nonBlockingRestCall(final JobClient client, final ActivatedJob job) { LOGGER.info("Invoke REST call..."); Flux paymentResponseFlux = WebClient.create() .get().uri(PAYMENT_URL).retrieve() .bodyToFlux(String.class); // non-blocking, so we register the callbacks (for happy and exceptional case) paymentResponseFlux.subscribe( response -> { LOGGER.info("...finished. Complete Job..."); client.newCompleteCommand(job.getKey()).send() // non-blocking, so we register the callbacks (for happy and exceptional case) .thenApply(jobResponse -> { LOGGER.info(counter.inc()); return jobResponse;}) .exceptionally(t -> {throw new RuntimeException("Could not complete job: " + t.getMessage(), t);}); }, exception -> { LOGGER.info("...REST invocation problem: " + exception.getMessage()); client.newFailCommand(job.getKey()) .retries(1) .errorMessage("Could not invoke REST API: " + exception.getMessage()).send() .exceptionally(t -> {throw new RuntimeException("Could not fail job: " + t.getMessage(), t);}); } ); } ``` This code uses the reactive approach to use the Zeebe API: ``` client.newCompleteCommand(job.getKey()).send() .thenApply(jobResponse -> { counter.inc(); return jobResponse; }) .exceptionally(t -> { throw new RuntimeException("Could not complete job: " + t.getMessage(), t); }); ``` With this reactive glue code, you don’t need to worry about thread pools in the workers anymore, as this is handled under the hood from the frameworks or the Java runtime. [You can observe in the logs](https://github.com/berndruecker/camunda-cloud-clients-parallel-job-execution/blob/main/results/java-nonblocking.log) that many jobs are now executed in parallel — and even by the same thread in a loop within milliseconds. ``` 10:54:07.105 [pool-4-thread-1] Invoke REST call… […] 30–40 times! 10:54:07.421 [pool-4-thread-1] Invoke REST call… 10:54:07.451 [ctor-http-nio-3] …finished. Complete Job… 10:54:07.451 [ctor-http-nio-7] …finished. Complete Job… 10:54:07.451 [ctor-http-nio-2] …finished. Complete Job… 10:54:07.451 [ctor-http-nio-5] …finished. Complete Job… 10:54:07.451 [ctor-http-nio-1] …finished. Complete Job… 10:54:07.451 [ctor-http-nio-6] …finished. Complete Job… 10:54:07.451 [ctor-http-nio-4] …finished. Complete Job… […] 10:54:08.090 [pool-4-thread-1] Invoke REST call… 10:54:08.091 [pool-4-thread-1] Invoke REST call… […] 10:54:08.167 [ault-executor-2] …completed (56). Current throughput (jobs/s ): 56, Max: 56 10:54:08.167 [ault-executor-1] …completed (54). Current throughput (jobs/s ): 54, Max: 54 10:54:08.167 [ault-executor-0] …completed (55). Current throughput (jobs/s ): 55, Max: 55 ``` These observations yield the following recommendations for Java: | | Blocking code on platform threads | Blocking code on virtual threads | Reactive code | | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | Parallelism | Some parallelism is possible with a thread pool, which is used by the client library. The default thread pool size is one, which needs to be adjusted in the config in order to scale. | Many blocked operations can run concurrently without tying each blocked operation to a platform thread. | A processing loop combined with an internal thread pool, both are details of the framework and runtime platform. | | **Use when** | You don't have requirements to process jobs in parallel. | You use Java 21 or later, need to process I/O-bound jobs in parallel, and want to keep straightforward blocking code. | Your client stack already uses reactive programming, or you need extremely high throughput or low latency and have measured the tradeoffs. | | | You intentionally want to limit parallelism with a small worker thread pool. | This should be the default for Java workers that need parallel I/O and don't otherwise require reactive programming. | Your developers are familiar with reactive programming and the added complexity is acceptable. | #### Size `maxJobsActive` against execution threads For workers backed by a fixed execution thread pool (blocking or virtual threads), `maxJobsActive` defines the queue length and bounds the number of jobs a worker holds at once, but it isn't a throughput knob: throughput is `numJobWorkerExecutionThreads / handlerDuration`. Increasing `maxJobsActive` beyond what your execution threads and job timeouts can support only lengthens the queue within the worker; it does not make jobs complete faster. Size `maxJobsActive` so your worker's queue stays within its job deadlines, with some margin: ```text maxJobsActive < numJobWorkerExecutionThreads × (jobTimeout / averageHandlerDuration) ``` For example, with 30 execution threads, a job timeout of 1,800 ms, and an average handler duration of 300 ms, `maxJobsActive` should remain below 180 (`30 × (1800 / 300)`). A higher value allows more jobs to queue behind busy threads than can be completed before their deadlines. As a result, jobs may time out and be redelivered to other workers instead of completing. Size the job timeout against your worst-case handler duration, not the average. The timeout serves two purposes at once: it's the broker's deadline for redelivering a job to another worker, and, if [job streaming](/components/concepts/job-workers.md#job-streaming) is enabled, it also determines how long a pushed job can wait for an available capacity slot before it is dropped and retried. A handler that occasionally takes longer than average will exceed a timeout sized only for the average case. :::note This formula and the `maxJobsActive` capacity model it describes are specific to the Java client’s worker implementation, which uses a shared semaphore to limit both pushed and polled jobs. Other client SDKs implement worker capacity differently. Check your client’s documentation for the equivalent tuning parameters. ::: ### Node.js client Using the [Node.js client](https://github.com/camunda/camunda-8-js-sdk), your worker code will look like this, assuming that you use Axios to do rest calls (but of course any other library is fine as well): ```js zbc.createWorker({ taskType: "rest", taskHandler: (job) => { console.log("Invoke REST call..."); axios .get(PAYMENT_URL) .then((response) => { console.log("...finished. Complete Job..."); job.complete().then((result) => { incCounter(); }); }) .catch((error) => { job.fail("Could not invoke REST API: " + error.message); }); }, }); ``` This is **reactive code**. And a really interesting observation is that reactive programming is so deep in the JavaScript language that it is impossible to write blocking code, even code that looks blocking is still [executed in a non-blocking fashion](https://github.com/berndruecker/camunda-cloud-clients-parallel-job-execution/blob/main/results/nodejs-blocking.log). Node.js code scales pretty well and there is no specific thread pool defined or necessary. The Camunda 8 Node.js client library also [uses reactive programming internally](https://github.com/camunda/camunda-8-js-sdk/blob/main/src/zeebe/zb/ZBWorker.ts#L27). This makes the recommendation very straight-forward: | | Reactive code | | ------------ | ------------------------------ | | Parallelism | Event loop provided by Node.js | | **Use when** | Always | ### C# Using the [C# client](https://github.com/camunda/camunda-platform-get-started/tree/master/csharp), you can write worker code like this: ```csharp zeebeClient.NewWorker() .JobType("payment") .Handler(JobHandler) .HandlerThreads(3) .Name("MyPaymentWorker") .Open() ``` You can observe that you can set a number of handler threads. Interestingly, this is a naming legacy. The C# client uses the [Dataflow Task Parallel Library (TPL)](https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/dataflow-task-parallel-library) to implement parallelism, so the thread count configures the degree of parallelism allowed to TPL in reality. Internally, this is implemented as a mixture of event loop and threading, which is an implementation detail of TPL. This is a great foundation to scale the worker. You need to provide a handler. For this handler, you have to make sure to write non-blocking code; the following example shows this for a REST call using the [HttpClient](https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-5.0) library: ```csharp private static async void NonBlockingJobHandler(IJobClient jobClient, IJob activatedJob) { Log.LogInformation("Invoke REST call..."); var response = await httpClient.GetAsync("/"); Log.LogInformation("...finished. Complete Job..."); var result = await jobClient.NewCompleteJobCommand(activatedJob).Send(); counter.inc(); } ``` The code is executed in parallel, [as you can observe in the logs](https://github.com/berndruecker/camunda-cloud-clients-parallel-job-execution/blob/main/results/dotnet-nonblocking.log). Interestingly, the following code runs even faster for me, but [that’s a topic for another discussion](https://stackoverflow.com/questions/21403023/performance-of-task-continuewith-in-non-async-method-vs-using-async-await): ```csharp private static void NonBlockingJobHandler(IJobClient jobClient, IJob activatedJob) { Log.LogInformation("Invoke REST call..."); var response = httpClient.GetAsync("/").ContinueWith( response => { Log.LogInformation("...finished. Complete Job..."); jobClient.NewCompleteJobCommand(activatedJob).Send().ContinueWith( result => { if (result.Exception==null) { counter.inc(); } else { Log.LogInformation("...could not do REST call because of: " + result.Exception); } }); }); } ``` In contrast to Node.js, you can also write **blocking code** in C# if you want to (or more probable: it happens by accident): ```csharp private static async void BlockingJobHandler(IJobClient jobClient, IJob activatedJob) { Log.LogInformation("Invoke REST call..."); var response = httpClient.GetAsync("/").Result; Log.LogInformation("...finished. Complete Job..."); await jobClient.NewCompleteJobCommand(activatedJob).Send(); counter.inc(); } ``` The degree of parallelism is down to one again, [according to the logs](https://github.com/berndruecker/camunda-cloud-clients-parallel-job-execution/blob/main/results/dotnet-blocking-thread-1.log). So C# is comparable to Java, just that the typically used C# libraries are reactive by default, whereas Java still knows just too many blocking libraries. The recommendations for C#: | | Blocking code | Reactive code | | ------------ | -------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | Parallelism | Some parallelism is possibly by a thread pool, which is used by the client library. | A processing loop combined with an internal thread pool, both are details of the framework and runtime platform. | | **Use when** | **Rarely**, and only if you don't have requirements to process jobs in parallel or might even want to reduce the level or parallelism. | This should be the **default** | | | Your developers are not familiar with reactive programming | You need to scale and have IO-intensive glue code (e.g. remote service calls like REST) | --- ## Doing a proper POC When evaluating your process automation approach, a **proof of concept (POC)** is often a good step to check if the process automation methodology, the standards of BPMN and DMN, as well as the Camunda technology suit your needs. It is vital for a POC to make up your mind about your goals, to select a suitable process, and to prepare it and carry it out properly. ## Understanding POC With a POC, you create a prototype application within no more than _three to five days_. The result of a POC is intended to be thrown away after having served its purpose: to try and show that your project will "fly" - including all aspects relevant for your specific situation. Such aspects might be: - Does Camunda _fit into your own architecture_? - Does the _development approach_ fit into your own organization's approaches? - How can you _model_ a specific business domain problem? - Which kind of _know how_ is needed for the business and development teams? - Which _effort_ will typically be needed for these kinds of projects? - What are the impacts of projects for _operations_? Often, it does make sense to implement such a POC together with Camunda, our partners, or specialized consultants to get quick results and focused feedback with respect to your specific challenges. However, you should always at least _co-develop_ the POC yourself to really understand what is going on. A team size of two to four people has proven to be quite optimal. ## Defining and focusing on specific goals Before planning and carrying out a POC, you should consciously clarify the specific goals you want the POC to achieve. Typical goals might be: - To _verify_ the approach or the tool works under specific circumstances. - To _show_ a case that _convinces_ internal stakeholders that the approach makes sense. - To work through a complete _example_ and get specific _questions_ sorted out. - To _learn_ about Camunda and _understand_ how it works. :::note When selecting your goal, keep in mind the needs of all relevant stakeholders. ::: Do not just "collect" goals here, but try to make up your mind as to what really matters. Often, it is better to make a clear choice. For example, whether to show off a nice user interface at the end of the week or to have time to clarify all questions and to understand Camunda in depth, maybe even only using unit tests. ## Defining a scope relevant to your business Select a _useful_ and _suitable_ process, case, or decision given your goals. Typically, it should... - Be _relevant_ to your _core business_ stakeholders. - Make your organization's _return on investment_ on BPM more transparent. - Be _feasible_ within the POC time box. Avoid political mine fields when selecting the process for your POC. ## Planning the POC ### Involving the right people It does make sense to implement a POC together with the software vendor and/or specialized consultants to get _quick results_ and _focused feedback_ with respect to your specific challenges. However, you should always at least _co-develop_ the POC to really understand what is going on. When planning for your team, consider that successful process modeling requires not just knowledge about the business and the targeted technical solution, but experience with BPMN modeling and methodology as well as analytical and moderation skills. We therefore typically bring together _business people_ with _IT staff_ and internal _business analysts_, _train them properly_ and let them continue to _learn on the job_ by carrying out the POC together with an _experienced consultant_. A team size of up to a maximum of _four people_ has proven to be quite optimal. In case you want to access _system interfaces_ during your POC, also determine who will be a technically knowledgeable and available _contact person_ for that system. To integrate into existing _user interfaces_, you might need help from colleagues within your organization. Define a _moderator_ to avoid too many detours and keep your POC on track. ### Planning the technical environment Make the necessary technological choices. Typically, POCs _run on Camunda 8 SaaS_ unless your goal is to validate that Camunda 8 runs in your Kubernetes environment in a self-managed fashion. A simple test account is often sufficient, unless your goal is to do load or performance tests, for which you need bigger clusters. Reach out to us in such cases. If your POC includes [AI agents](/reference/glossary.md#ai-agent), SaaS also gives you [Camunda-provided LLM](/components/agentic-orchestration/camunda-provided-llm.md), so you can run them within a provided budget instead of setting up an LLM provider account first. To access _third party systems_ during your POC, set up proper test systems for those and verify that they are usable. Prepare a location in a _version control system_ where you can develop your POC. Having a shared repository with history does make sense also (or especially) in a 2-day POC! Collaboration is simplified if the Camunda consultant can also access that repository. It may be worth just creating a repository with weaker access limitations for the POC. If your organization cannot easily set up a repository for the POC, or access for externals is impossible, you can create a cloud repository. We typically recommend [GitHub](https://github.com/); a free account is sufficient. It gives you a Git repository and you can invite all necessary people for the POC. Afterwards, you can delete that repository. ### Selecting the time frame As already mentioned above, we typically plan no more than _a focused week_ for the POC workshop itself. Sometimes it also works well to split up the POC into two weeks of 2-3 days each, which allows everybody to reflect on the POC over the weekend. - Plan _1-3 days_ for _modeling_ the process. - Plan _2-3 days_ for _implementing_ the process solution. When selecting the exact time frame, consider all the people involved, as well as any technical preparation you need to do up front. You also might want to plan for further steps, like a few more things you implement yourself internally in a second follow up week. ## Presenting the results Before presenting the results of your POC to a wider audience of stakeholders, select a _speaker_ who is comfortable with presenting, prepare a set of focused _slides_ illustrating your progress and the lessons learned, and _test_ your solution and presentation at least once up front. The speaker might also be your Camunda Consultant - they are used to presenting to a wide audience! ## Checklists ### Technical - _Cloud Access_: Make sure you have an account for Camunda 8 with an active subscription or trial account. - _Installations_: Make sure your _developer systems_, as well as any _target systems_ for the POC test and production you wish to use are set up. In particular install: - Camunda [Desktop Modeler](https://docs.camunda.io/downloads/) - Java, Maven, and your favorite IDE (e.g. Eclipse) - Make sure _Maven_ runs and builds and it can access all necessary dependencies. [Download and build this project](https://github.com/camunda/camunda-platform-tutorials/tree/main/quick-start/microservice-orchestration/worker-java) to verify that your build runs. - _Developer Computers_: For maximum productivity, all participating developers should use the computer with which they work every day. Avoid using computers from a training room or shared laptops unless they allow a remote connection to the developer's personal computer. If the developer's computers are neither portable nor remotely accessible consider conducting the POC in the regular office space of the developers. If your company network is restricting access to Maven and Git repositories on the internet, consider using laptops that are not connected to the company network. Similarly, you should not force the external consultants to work on one of your computers. They will be twice as productive on their laptops and not lose time with software setup, configuration, and access restrictions. Obviously, you do not have to connect the consultant's laptop to your company network. Internet access and a shared code repository are enough to collaborate. - _Files_ or _Version Control System_: Make sure we can easily exchange files and code during the POC, preferably via your own version control system (e.g. Git or SVN) or at least via shared folders, USB sticks, or email attachments. - _Interfaces_: Clarify which technical systems' interfaces you want to access during your POC, make any _documentation_ for those available to the whole POC team, and make sure there is a technically knowledgeable _contact person_ for the interface available to the team during the POC. Set up a _test system_ and verify that it is usable. Verify with Camunda that everything is clear to the team, in particular from a technological perspective. ### Organizational Inform all POC team members and other relevant stakeholders about the following: - _Goals_ and the selected _scope_ for the POC - _Start_ and _end times_, as well as any additional preparation/meet-up times - _Names and roles_ of all involved _people_ - For onsite POCs: - Exact _location/address_ at which the POC is taking place as well as instructions about how to find together when arriving (for onsite POCs) - _Projector_, white-board, and flip-chart availability - _Internet_ availability for team members and external consultants - For remote POCs: - Exact meeting setup. For example, links to the meeting room, passwords, etc. In case you can't easily host meetings for external participants, your Camunda consultant can setup a Zoom or Microsoft Teams call. - Ideally, some chat capability (e.g. a temporary Slack account) Ideally, prepare a few _organizational_ and/or _project_ info slides to get everybody up to speed on day one of the workshop. --- ## Following the customer success path Following certain steps when evaluating and introducing process automation helps to make it a success. Ensure you review the appropriate best practices at the right time. ## Understanding the customer success path When introducing Camunda as a new process automation platform inside your company, the following process has shown to work best: 1 _Evaluation_: Take the philosophy of the evaluated products into greater consideration than working solely with feature matrices. Practical experience can be invaluable. You might be interested in our [Whitepaper: "Camunda compared to alternatives"](https://page.camunda.com/wp-camunda-compared-to-alternatives). 2 _Process selection_: It is very important to select a suitable pilot process. Use a relevant process where you can show benefits of BPM including a Return on Invest (ROI) calculation. However, avoid too big or too "political" processes to minimize the risk of failure due to avoidable reasons. Note that you can use this process in the proof of concept (PoC) or select a different process for the first PoC, depending on the goals you have. 3 _Proof of Concept_ (PoC): Model the process to a high standard. It should be clear, understandable, and precise, as it will have a high visibility. Include necessary technical proofs, like calling real services in your environment. Include human tasks if your process where appropriate. We suggest using Camunda Tasklist as a first step to save effort in developing your own tasklist, unless a tasklist is important for your overall proof. Include "eye candies" like reporting to make non-technical stakeholders happy. Concentrate on the important aspects to do the proof and prepare to throw away the code afterwards to start fresh for the pilot, as it is very valid for early POCs to be "hacky" in order to keep focus on the end goals. 4 _Development_: Model the process with the same standard described for a PoC. It should be clear, understandable, and precise. Again, the reason for this is that it will be the most visible part of the project. Develop the project in an iterative manner to learn fast. Do proper testing to achieve a high quality. 5 _Operations_: Prepare for real operations, which includes setting up the real hardware as well as securing and monitoring the platform. 6 _Pilot review_ and _Pilot improvements_: Review the project after it has finished and gone live. Take some time to clean up, as the project normally serves as a "lighthouse" and "copy and paste" template for sequential projects, so it is worth the effort. It's better to plan time for this phase than try to make things perfect during early development, as you will have learned a lot once the pilot runs on the live system for a while. 7 _Next processes_: Try to avoid doing too many projects in parallel in the beginning to allow new learning to influence your future work. If you have parallel pilots, organize knowledge sharing between the teams. Ideally, let the team of the first pilot directly implement a sequential process. 8 _Custom BPM Platform_: In bigger organizations, you typically try to set up your custom BPM platform, meaning a common infrastructure for all upcoming Camunda projects. Try to do as little of this as possible during the first pilot and start building the platform afterwards, taking all learnings into account. At the same time, do what is necessary for the pilot project itself or for other stakeholders to feel comfortable (e.g. Enterprise Architecture). 9 _Process architecture_: BPM initiatives often start by drafting a process landscape and capture all relevant processes of the company. Try to avoid this, and do as little as possible during your first pilot project. Maybe do a quick process survey to capture relevant processes (by name) to identify a good candidate for the pilot. Especially do not model all processes in your company in depth before you experienced an "end-to-end" project, including automation of Camunda yourself. Then, you will have gained a deeper understanding of methodology and value around BPMN and DMN. ## Estimating effort When starting your BPM project, it is often necessary to roughly estimate the expected effort. A process model can serve as a central artifact for estimation purposes. Avoid too fine-grained estimations as they typically are not worth the effort. However, on a management level one often must have some estimations to secure budgets, get projects started, allocate needed resources, and communicate expected time frames. The success factor is to do estimations _on a very rough level_ and avoid spending too much time with details. More often than not, the details develop differently than expected anyway. We often note customers successfully estimate _T-Shirt size categories (S, M, L, XL and XXL)_. Such an approach is sufficient for us to make roughly informed decisions about priority and return on investment. ![T-Shirts](following-the-customer-success-path-assets/t-shirts.png) Having said that, your organization may demand that you _map_ such rough sizes to some measuring system already used; for example, _story points_ or _person days_. To preserve the rough character, consider mapping the sizes by using a series of sharply increasing numbers: | S | M | L | XL | XXL | | --- | --- | --- | --- | --- | | 2 | 5 | 13 | 50 | 200 | Much more important than concrete numbers is an educated gut feeling. Therefore, try to understand the influencing factors determining most of the effort by implementing your lighthouse process. ### Using the process model for estimation A process model can be seen as a central artifact for estimation purpose, as it indicates and visually maintains a lot of the influencing factors mentioned above. Here are the figures you could estimate: 1. Setting up development environment: **S** 2. Modeling and understanding requirements: **L** 3. Implementing the process solution: - Implement UI for PDF upload: **S** 1 - Implement forms: **S** 2 3 4 5 - Implement integrating the PDF archive: **S** 6 4. Going live: **M** Using the process model, you can also foresee potential effort drivers, for example: - The legacy archive is really hard to integrate. - The tasks need to be integrated into an existing legacy task list, which might not be straight forward to do. - The metadata from the PDF shall be extracted, and a specialized form be shown to the user. --- ## Building flexibility into BPMN models BPMN modeling primarily targets structured processes, often with the goal to automate as many steps as possible, increase efficiency, and decrease process execution costs. But sometimes we need ways to build flexibility into such process models to deal with expected or unexpected operational problems or to allow for humans to intervene. ## Understanding the required symbols To build flexibility into BPMN process models, one must understand BPMN symbols and modeling techniques. After introducing the main symbols, we can demonstrate more concrete examples. ### Use events as triggers BPMN events allow us to react to all kinds of information. We can use them to trigger flexible activities. In particular, BPMN events **catching** **messages**, **conditions**, and **timeouts** are useful in that context. ### Boundary events to add activities on triggers BPMN allows us to attach events to the boundary of activities to trigger some follow-up action. By modeling such an event as either **interrupting** or **non-interrupting**, we can decide to do the activities either _instead of_ the activity we attach the event to, or _in addition to_ it. ### Subprocesses with boundary events By attaching boundary events not just to individual activities, but also to subprocesses, we can flexibly define the area or scope for which we want to trigger some flexible activities. 1 While we are occupied with carrying out some area of activities, in a scope of our process... 2 ...an event might occur, which causes us... 3 ...to carry out this activity in addition to continuing with ordinary work. ### Event subprocesses Sometimes we need to build in flexible activities which are carried out at any point in time. In such cases, we can leverage BPMN's event-based subprocesses. ### Escalation events Sometimes we need highly flexible means to cancel scopes or trigger additional activities from within a scope. The BPMN escalation events can be particularly useful to implement such requirements. 1 As soon as we are finished with the first activity inside the scope... 2 ...we inform the surrounding scope about that and trigger an additional, essential activity... 3 ...but also continue with our second activity to complete the subprocess. 4 We can then already continue with the follow-up work regardless of whether that additional activity is already finished. ### Terminate end events To build flexibility into process models, it is also useful to remember that the termination end event just terminates the scope within which it is defined and therefore _not_ always the whole process instance. With that technique, it becomes possible to cancel some activities inside a subprocess while completing it successfully and leaving it via the typical outgoing path. 1 As soon as one of our two activities achieves the result, we can cancel the other one... 2 ...and successfully complete the subprocess and normally continue with our follow-up work. ### Ad-hoc sub-processes The techniques above keep the sequence of activities fixed and use events to deviate from it. An [ad-hoc sub-process](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md) instead leaves the sequence open: its activities carry no sequence flow between them, and which of them run, in which order, and how often is decided at runtime instead of at design time. The decision can be made either by the engine, based on an expression in the model, or by a job worker. For example, if the subprocess hosts an AI agent, the AI Agent connector can make the decision. The symbols above still apply around and within an ad-hoc subprocess. You can use boundary events and [event subprocesses](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md#event-sub-processes) to interrupt or redirect it while it is running. Therefore, leaving the sequence open-ended does not mean giving up control over it. ## Examples ### Allow proactive order status communication Assume that for an order to be validated, the customer must determine the delivery date before we can confirm the order. If the order is not acceptable—due to consistency issues or customer related issues—it is declined. Some of our orders might be so important that we want to ensure we keep customers happy, even if not everything runs smoothly on our side. 1 Order managers can request proactive customer communication on demand. Assume they can communicate the reasons via a form, whereas the communication as such is carried out by the call center. 2 On a regular basis, we check based on some rules, whether the order is so important that we proactively communicate why the order is not yet confirmed. Again, the communication is carried out by the call center. ### Allow for order cancellation any time The customer might be allowed to request a cancellation until the order is confirmed. This request would have to be reviewed to determine whether we must accept the cancellation. 1 Whenever the customer requests a cancellation until the order is confirmed, we review that request and decide whether we have to accept the cancellation or not. 2 If we accept the cancellation, we must terminate the entire process. To do so, we need to use one trick: throw an error event that will end the current event subprocess, but not yet the order process. 3 This leads to another subprocess to be triggered, and this one is interrupting. Now, the process instance is really cancelled. ### Allow for order details to change, but repeat order validation If the customer changes the order details, the order must be validated again. --- ## Choosing the DMN hit policy **Hit policies** describe different ways (standardized by DMN) to evaluate the rules contained in a decision table. Different hit policies do not only lead to different results, but typically also require different modes of thinking and reason about the meaning of the entire table. Therefore, it's crucial to not just know the different DMN hit policies, but also to understand the motivations for their existence and the most typical cases for using them. ## Knowing the DMN hit policy basics A decision table consists of several **rules**, typically represented as rows. When reading such a row, we look at certain **input values** and deduct a certain result represented by **output values**. When using the simplest hit policy **"unique"** (**U**), such rules do **not overlap**: only a single rule must match. 1 We define an "input" value **season** here. For every single season ... 2 ... there is a **jacket** defined we want to use, the "output" of the rules here. 3 The hit policy "**Unique**" (indicated by the character **U**) enforces that rules do **not overlap**: only a single rule must match. Now consider that we build a decision table with **overlapping rules**. In other words, that means more than one rule may match a given set of input values. We then need one of the **alternative hit policy** indicators to unambiguously understand the decision logic according to which such rules are interpreted. The hit policy **indicator** is a single character shown in the decision table's top left cell, right beneath the decision's name. The character is the initial letter of one of the defined seven hit policies `U`**nique**, `A`**ny**, `P`**riority**, `F`**irst**, `C`**ollect**, `O`**utput order** and `R`**ule order**. Furthermore, the hit policy 'Collect' may also be used with one of four aggregation operators, actually giving us four more hit policies `C+` (**Sum**), `C<` (**Minimum**), `C<` (**Maximum**) and `C#` (**Number**). Eight of those eleven hit policies evaluate a decision table to a **single result**. Three hit policies evaluate a decision table to **multiple results**. ### Single result decision tables Such tables either return the output of only one rule or aggregate the output of many rules into one result. The hit policies to be considered are - `U`**nique**: Rules do not overlap. Only a single rule can match. - `F`**irst**: Rules are evaluated from top to bottom. Rules may overlap, but only the first match counts. - `P`**riority**: Rule outputs are prioritized. Rules may overlap, but only the match with the highest output priority counts. :::note Camunda does not yet support the hit policy **priority**. In essence, priorities are specified as an ordered list of output values in decreasing order of priority. Such priorities are therefore independent from rule sequence! Though not yet supported, you can mimic that behavior using hit policy "(**C**)ollect" and determining a priority yourself; for example, by means of an execution listener attached to the end of your business rule task. ::: - `A`**ny**: Multiple matching rules must not make a difference: all matching rules must lead to the same output. **Collect** and **aggregate**: The output of all matching rules is aggregated by means of an operator: - `C+`**Sum**: Add up all the matching rule's distinct outputs. - `C<`**Minimum**: Take the smallest value of all the matching rule's outputs. - `C>`**Maximum**: Take the largest value of all the matching rule's outputs. - `C#`**Number**: Return the number of all the matching rule's distinct outputs. ### Multiple result decision tables **Multiple result** tables may return the output of multiple rules. The hit policies for such tables are: - `C`**ollect**: All matching rules result in an arbitrarily ordered list of all the output entries. - `R`**ule order**: All matching rules result in a list of outputs ordered by the sequence of those rules in the decision table. - `O`**utput order**: All matching rules result in a list of outputs ordered by their (decreasing) output priority. :::note Camunda does not yet support the hit policy **output order**. In essence, output orders are specified as an ordered list of output values in decreasing order of priority. Such priorities are therefore independent from rule sequence! Though not yet supported, you can mimic that behavior using hit policy "(**C**)ollect" and determining an output order yourself; for example, by means of an execution listener attached to the end of your business rule task. ::: ## Understanding DMN hit policy use cases Most situations can be addressed using different hit policies. In that case, the hit policy will have an effect on the readability and maintainability of the table. Often it is worth trying different varieties until you have a feel for what will work best. ### Unique: granting categories of customers a specified discount Hit policy "**Unique**" will typically make it easy to build a decision table, which ensures your rules are "complete" - in the sense that the rules do not just not overlap but cover all possible input values - so that you do not "forget" anything. 1 The _input_ area of each row specifies a certain **segment** of possible input values. 2 This row, for example, expresses that _long time silver customers receive a 9% discount_. Such a use case fits to the hit policy "**Unique**". For such use cases, it is an advantage that this hit policy make your decision logic invalid in case you violate its requirement that your table rules never "overlap": after all, you must not produce ambiguous results. ### First: accepting a customer based on hard criteria Having said that, the hit policy "**First**" can sometimes make it easier for an organization to reason about decision logic dealing with some criteria that are "harder" (more "clearcut") than others. Furthermore, it can help to make a decision table layout more compact and therefore easier to interpret. 1 Assume that everybody in the organization knows that first rule: "Once on the blocklist, never again accepted." The layout and the hit policy of the decision table therefore supports the organization's way of doing business: once we know that single fact about a customer, we don't need to think further. 2 The following rules from row 2-4 are expressed in an "Accept" manner and might change more often over time. The organization's way of thinking is literally "from top to bottom". Once we find an acceptance rule, we can deal with the customer. 3 For execution in a decision engine, don't forget to add a rule not accepting any other customers as a last row. In scenarions dealing with **hard** **exclusion** and **inclusion** criteria, we often don't care that much if the rules overlap, but prefer to argue about very clearcut cases first and about more sophisticated ones later on. Furthermore, the organization's way of thinking and doing business might be better supported by a decision table using the hit policy **First**. Our experience so far tends to show that it can be more tricky and error prone to argue about a **First** hit policy decision table than it might occur to you at first sight. Therefore, be especially careful and always test your logic in case you are dealing with sensitive business! ### Collect: deciding which groups of people may review an order With hit policy **collect**, you do not care about the order or any interdependencies between your rules at all. Instead, you just "collect" independent rules and care about the question which rules are applicable to your specific case. Consider, for example, the question of "who is allowed" to carry out some action, as, for example, reviewing and deciding about incoming orders: As a result of this decision table, we will either get `["Sales"]` or `["Management"]` or a list of both groups `["Sales", "Management"]`. We could use this information to route the order into the applicable group's task lists or control access rights of a configurable software solution, etc. Of course, you could at any time introduce more rules and eventually also differentiate between more groups without changing your software solution. ### Sum: accepting a customer based on soft criteria Hit policy "collect" may be combined with operators such as **Sum (C+)**, leading to very different use cases. A very typical one is the requirement to evaluate a case based on manyfold factors influencing the overall result. Assume, for example, that we want to deal with customers we know nothing about. They receive a score of 0. But in case we know something about them, we also weigh in our knowledge: 1 The overall creditworthiness is deducted by throwing in many factors. 2 Here, for example, we give credit in case we made good experiences with the customer in the past. 3 A very low current income does not matter as long as the customer is not a stranger to us! 4 On the other hand, as soon as a customer has proof for a good income, they receive five points for "reasonable" income as well as 10 points extra for good income. Even if we had bad experience with a customer (which means they start from -15), we end up with an overall score of 0 in case the customer has a good income now, and start to accept the customer again. In scenarions dealing with **soft exclusion** and **inclusion** criteria, we need a mechanism to associate a weight to different scenarios. This is ideally supported by hit policy **Sum (C+)**. --- ## Choosing the resource binding type Camunda 8 offers version binding for linked processes, decisions, forms, and deployment-bound resources. Use deployment binding when a resource is deployed together with the process and should be consumed as the version that shipped with that deployment. This also applies to generic resources that are packaged and deployed with the process application. You can choose the binding type for the linked target resource for the following BPMN process elements: - [Call activities](/components/modeler/bpmn/call-activities/call-activities.md#defining-the-called-process) - [Business rule tasks](/components/modeler/bpmn/business-rule-tasks/business-rule-tasks.md#defining-a-called-decision) (if the DMN decision implementation is used) - [User tasks](/components/modeler/bpmn/user-tasks/user-tasks.md#user-task-forms) (if a Camunda Form is linked) The binding type determines the version of the target resource used at runtime. For example, for a call activity this would be the version of the called process to be instantiated. ## Supported binding types Camunda 8 supports the following binding types: Type Description latest Resolves to the latest deployed version of the target resource at the moment the process element is activated. You can use this option to easily change the target resource at runtime by deploying a new version of the resource. This allows for fast, iterative development, and easy hot fixes in production: Process instances that have not yet reached the process element use the newly deployed version of the target resource once the element is activated. If the process element has already been reached, you can use process instance modification to reactivate and relink it to the newly deployed target resource version. Be aware that using latest can lead to unexpected behavior if you deploy a new version of the target resource without ensuring backwards compatibility with every deployed process that depends on it.This might not be suited for production environments that require stability and a predictable behavior. deployment Resolves to the specific version of the target resource that was deployed together with the currently running version of the process in the same deployment. This option ensures predictable behavior by tying the two versions together, and allows you to deploy future versions of the target resource without disrupting ongoing process instances. It is ideal for self-contained projects without external or shared dependencies. To use the deployment binding option, create and deploy a project in Camunda Hub, or deploy multiple resources together via the Zeebe API. versionTag Resolves to the specific version of the target resource that is annotated with the given version tag. The version tag is a user-provided string (for example 1.2.0.Final) that makes it easy to identify a certain version of a resource and track it across multiple deployment stages (e.g. dev, test, prod). You can set the version tag for a BPMN process, DMN decision, or Form in the properties panel in the modeling interface. A version tag is different from the numeric process definition version assigned by the Orchestration Cluster. Using the versionTag binding option ensures that the right version of the target resource is always used, regardless of future deployments, by pinning the dependency to a specific version. The option is ideal for managing external or shared dependencies. Caution: If the target resource ID and version tag pair are not deployed, the process instance will have an incident. To avoid this situation, ensure the version tag defined in the call activity, business rule task, or user task matches the version tag in the dependent resource. Be aware that you can deploy a new version of a resource with an already existing version tag. In this case, the version tag reference will be updated and point to the latest deployed version. :::note If the binding type is not explicitly specified in your BPMN diagram, `latest` is used as the default. ::: --- ## Creating readable process models We create visual process models to better understand, discuss, and remember processes. Hence, it is crucial that models are easy to read and understand. The single most important thing is to use well-chosen labels. ## Essential practices ### Labeling BPMN elements Use [conventions for naming BPMN elements](naming-bpmn-elements.md); this will consistently inform the reader of the business semantics. The clarity and meaning of a process is often only as good as its labels. 1 _Start event_ labels informs the reader of how the process is _triggered_. 2 An _activity_ - labeled as "activity" - informs the reader of the piece of _work_ to be _carried out_. 3 _Gateway_ labels clarifies based on which condition(s) and along _which sequence flow_ the process proceeds. 4 Labeled _boundary events_ clearly express in which cases a process execustion might follow an _exceptional path_. 5 Labeled _end events_ characterize end _results_ of the process from a business perspective. ## Recommended practices ### Modeling symmetrically Try to model symmetrically. Identify related splitting and joining gateways and form easily recognizable _visual_, eventually _nested_, _blocks_ with those gateways. 1 The inclusive gateway splits the process flow into two paths which are ... 2 ... joined again with an inclusive gateway. Inside that block ... 3 another exclusive gateway splits the process flow into two more paths which are ... 4 ... joined again with an exclusive gateway. By explicitly showing _pairs of gateways_ "opening" and "closing" parts of the process diagram, and by positioning such gateway pairs _as symmetrically as possible_, the readability of process model is improved. The reader can easily recognize logical parts of the diagram and quickly jump to those parts the reader is momentarily interested in. ### Modeling from left to right Model process diagrams _from left to right_. By carefully positioning symbols from left to right, according to the typical point in time at which they occur, one can improve the readability of process models significantly: Modeling from left to right supports the reading direction (for western audience) and supports the human field of vision - which prefers wide screens. ### Creating readable sequence flows Consciously decide whether _overlapping sequence flows_ make your model more or less readable. On one hand, avoid overlapping sequence flows where the reader will not be able to follow the flow directions anymore. Use overlapping sequence flows where it is less confusing for the reader to observe just one line representing several sequence flows leading to the same target. Avoid sequence flows _violating the reading direction_, meaning no outgoing flows on the left or incoming flows on the right of a symbol. 1 The author could have made the five (!) sequence flows leading into the end event visible by separating them. However, by consciously choosing to partly overlap those flows, this model becomes less cluttered, therefore less confusing and easier to read. 2 The author could have attached the sequence flow, leaving this task on its left. However, this would have decreased readability, because the flow connection violates the reading direction. The same applies to incoming flows on the right of a symbol. _Avoid flows crossing each other_ and _flows crossing many pools or lanes_, wherever possible. Rearrange the order of lanes and paths to make your sequence flows more readable. Oftentimes, removing lanes can improve readability! Rearrange the order of pools in a collaboration diagram to avoid message flows crossing pools as much as possible. Often, you will find a "natural" order of pools reflecting the order of first involvement of parties in the end-to-end process. This order will often also lead to a minimum of crossing lines. _Avoid very long (multi page) sequence flows_, especially when flowing against the reading direction. The reader will lose any sense of what such lines actually mean. Instead, use link events to connect points which are not on the same page or screen anymore. 1 You observe a throwing link event here, which... 2 ...directly links to a catching link event just as if the sequence flow would have been connected. Avoid excessive use of link events. The example above serves to show the possible usage, but at the same time, it is too small to satisfy the usage of link events in real-world scenario! ### Modeling explicitly Make your models easier to understand by modeling _explicitly_, which most often means to either completely avoid certain more "implicit" BPMN constructs, or at least to use them cautiously. Always consider the central _goal of increased readability_ and understandability of the model when deciding whether to model explicitly or implicitly. When in doubt, it's best to favor an explicit style. #### Using gateways instead of conditional flows Model splitting the process flow by always using _gateway symbols_ such as instead of conditional flows . 1 For example, you could've left out this inclusive gateway by drawing two outgoing sequence flows directly out of the preceding task **Choose menu** and attaching conditions to those sequence flows (becoming conditional sequence flows ). However, experience shows that readers understand the flow semantics of gateways better, which is why we do not make use of this possibility. #### Modeling start and end events Model the trigger and the end status of processes by always explicitly showing the _start_ and _end event symbols_. :::caution Process models without start and end event cannot be executed on the Camunda workflow engine ::: 1 According to the BPMN standard, you could have left out the start event... 2 ...as long as you also leave out the end events of a process. However, you would have lost important information in your model, which is why we do not make use of this syntactical possibility. Be specific about the _state_ you reached with your event from a _business perspective_. Quite typically, you will reach "success" and "failure" like events from a business perspective: 1 'Invoice paid' better qualifies the "successful" business state than e.g. 'Invoice processed' would... 2 ...because in principle, you can call the failed state 'Invoice processed', too, but the reader of the diagram is much better informed by calling it 'Invoice rejected'. #### Separating splitting and joining gateways In general, avoid mixing up the split and join semantics of gateways by explicitly showing _two separate symbols_: 1 You could have modeled this join implicitly by leaving out the explicitly joining XOR gateway and directly connecting two incoming sequence flows to... 2 ...the subsequent splitting XOR gateway. Of course, BPMN would allow this for other gateway types, too. However, experience shows that readers will often overlook the join semantics of such gateways serving two purposes at the same time. The fact that readers will often overlook the join semantics of gateways serving to join as well as split the process flow at the same time, combined with the preference for [modeling symmetrically](#modeling-symmetrically), leads us to prefer _splitting and joining gateways modeled with separate symbols_. However, there are cases in which the readability of models can be improved with _implicit modeling_. Consider the following example: 1 The two incoming sequence flows to the task "Review tweet" could be merged with an XOR gateway, following explicit modeling. We argue that a merging XOR gateway directly behind the start event decreases the readability. A merging XOR gateway is a passive element and the reader expects the process to continue with an active element after the start event. #### Using XOR gateway markers Model the XOR gateway by explicitly showing the **X** symbol, even if some tools allow to draw a blank gateway. 1 You could have shown the splitting gateway... 2 ...as well as the joining gateway without the **X** symbol indicating that it is an exclusive gateway. The **X** marker makes a clearer difference to the other gateway types (inclusive, parallel, event-based, complex) which leads us to prefer _explicit XOR gateway markers_ in general. #### Splitting sequence flows with parallel gateways Always model splitting the process flow by explicitly showing the _gateway symbol_: 1 You could have modeled this parallel split implicitly by leaving out the gateway and drawing two outgoing sequence flows out of the preceding task **Choose menu**. However, the reader needs deeper BPMN knowledge in order to understand this model. Additionally, for joining the parallel flows... 2 ...you will always need the explicit symbol. The fact that readers of models using parallelization will likely need to understand the semantics of a parallel join combined with the preference for modeling symmetrically leads us to prefer _explicit parallel gateways_, too. #### Joining sequence flows with XOR gateways Model joining the process flow by explicitly showing the _XOR gateway symbol_ so the reader does not have to know BPMN details to understand how two incoming or outgoing sequence flows in a task behave. Additionally, this often supports the [symmetry of the model](#modeling-symmetrically) by explicitly showing a "relationship" of the splitting and joining _gateways forming a visual "block"_. 1 You could have modeled this join implicitly by leaving out the gateway and directly connecting the two incoming sequence flows to the subsequent task **Have lunch**. However, explicitly modeling the join better visualizes a block, the joining gateway semantically "belongs" to... 2 ...the earlier split. In case the reader is not interested in the details of dinner preparation but just in having dinner, it's easy to "jump" to the gateway, "closing" that logical part of the model. This is particularly helpful for models bigger than that example with many such (eventually nested) blocks. Consider the following model, showing two _nested blocks_ of gateways: 1 Now, you couldn't have modeled this join implicitly, because it's directly followed by an inclusive gateway with very different join semantics. _Consistency_ of joining techniques is another reason why we prefer explicitly joining sequence flows in general. There are always exceptions to the rule! There are cases in which the readability of models can be _improved_ with _implicit modeling_. So don't be dogmatic about explicit modeling; always aim for the most readable model. The following example shows a case of a model in which splitting and joining points do not form natural "blocks" anyway. In such cases, it can be preferable to make use of _implicit joining_ to improve the overall readability! ### Avoiding lanes Consider _avoiding lanes_ for most of your models all together. They tend to conflict with several of the best practices presented here, like [Modeling _Symmetrically_](#modeling-symmetrically), [Emphasizing the _Happy Path_](#emphasizing-the-happy-path) and [Creating Readable _Sequence Flows_](#creating-readable-sequence-flows). Apart from readability concerns, our experience also shows that lanes make it more difficult to change the resulting process models and therefore cause considerably _more effort in maintenance_. When modeling on an _operational level_, where showing the responsibility of roles matters most, we recommend to [use _collaboration diagrams_](#using-collaboration-diagrams) with several _separate pools_ for the process participants instead of lanes. However, the usage of lanes might be meaningful for: - _Strategic_ level models (refer to [BPMN Tutorial](https://camunda.com/bpmn/) and [Real-Life BPMN](https://www.amazon.com/Real-Life-BPMN-4th-introduction-DMN/dp/1086302095/) on details for modeling levels) - especially when they have a focus on _responsibilities and their borders_. - _Technical/executable_ models with a focus on _human work-flow_ and its ongoing "ping pong" between several participants. For these cases, also consider alternative methods to maintain and show roles: - As a _visible part_ of the _task name_, e.g. in between squared brackets []: _"Review tweet [Boss]"_. :::caution Camunda 7 Only During execution you can remove this part of the task name if you like by using simple mechanisms like shown in the [Task Name Beautifier](https://github.com/camunda/camunda-consulting/tree/master/snippets/task-name-beautifier) so it does not clutter your tasklist. ::: - As a _text annotation_ or a _custom artifact_ :::note Roles are part of your executable BPMN process model as _technical attributes_ anyway - even if hidden in the BPMN diagram. For example, they can be used during execution for assignment at runtime. ::: ## Helpful practices ### Emphasizing the happy path You may want to emphasize the _"happy path"_ leading to the delivery of a successful process result by placing the tasks, events, and gateways belonging to the happy path on a straight sequence flow in the center of your diagram - at least as often as possible. The _five_ BPMN symbols belonging to the happy path are put on a straight sequence flow in the center of the diagram. ### Avoid modeling retry behavior A common idea is to model retry behavior into your process models. This _should be avoided_ in general. The following process model shows a typical example of this anti pattern: All operations use cases put into the model can be handled via Camunda tooling, e.g. by [retrying](/components/concepts/job-workers.md#completing-or-failing-jobs) or [Camunda Operate](/components/operate/operate-introduction.md). ### Using collaboration diagrams If you model on an operational level (refer to [BPMN Tutorial](https://camunda.com/bpmn/) and [Real-Life BPMN](https://www.amazon.com/Real-Life-BPMN-4th-introduction-DMN/dp/1086302095/) on details for modeling levels) use _collaboration diagrams_ with several _separate pools_ for the process participants [instead of lanes](#avoiding-lanes) as operational models using lanes make it very hard for the individual process participant to identify the details of their process involvement. Furthermore, model just _one coherent process per pool_ (apart from event subprocesses, of course), even though BPMN in principle allows several processes per pool. This improves readability by constituting a clear visual border around every process and by providing a natural space for labeling that part of the end-to-end process in the pool's header. 1 The Team Assistance is responsible for initial "Invoice Collection" as well as "Invoice Clarification" - if applicable. Those two processes are modeled by using two separate pools for the team assistance, just as... 2 ...the approver can observe the "Invoice Approval" process in a separate pool and... 3 ...the managing director can observe the "Invoice Payment" process in a separate pool while the collaboration diagram as a whole shows the business analyst that the overall end-to-end process works. Using _collaboration diagrams_ with _separate pools_ for the process participants allows to explicitly show interaction and communication between them by means of message flow and further improves readability by transparently showing the participants their own involvement in the end-to-end-process. As a consequence, they do not need to fully read and understand the end-to-end process in order to read, understand, and agree to their own involvement by looking at their own pools. ### Showing interaction with systems Consciously decide how you want to model systems the process participants are interacting with. Use _data stores_ to show systems which primarily serve as a means to store and retrieve data. Use - depending on your needs _collapsed_ or _expanded_ - _pools_ for systems which are carrying out crucial activities in the process going way beyond storing and retrieving data. 1 A _collapsed pool_ is used to represent a system which supports the process and/or carries out process tasks on its own. The pool could be expanded later to model the internal system details, maybe even with the goal to execute a technical process flow directly with a BPMN capable process engine. 2 A _data store_ is used to represent a technical container meant to archive PDFs and store them for later retrieval. 3 Another _data store_ is used to represent a container which could be a physical storage place for paper invoices to be paid at the moment but could become a representation for business objects in a database with the object state "to be paid" in the future. When _choosing_ between those _two options_ for modeling systems (data stores, collapsed pools) keep in mind that only pools represent processes and therefore have the capability to be expanded and modeled in all their internal details later on. ### Avoiding excessive usage of data objects Avoid excessive use of _data objects_, but use them cautiously to show the _most important data related aspects_ of your process. Experience shows that many data objects and especially many data associations quickly clutter your process model and that visual noise reduces readability - especially for less experienced readers. You might find three practices helpful to find your own "right" amount of data visualization: 1 Cautiously use data objects and associations to show the _most important data related aspects_ of your process. We could have modeled that all the tasks in the "Payments Creation" process either read, update, or delete the "new payment", however we decided that we just want to point out that the process works on a new payment object. 2 Use data stores for _coupling processes via data_. We could have modeled a lot of other tasks in the process that either read or update the "payments", however, we decided to just point out the most important aspect for the process diagram, which is that the "Payments Creation" process of delivery service is loosely coupled with the "Payments Processing" via commonly shared data. 3 Here we decided that it's helpful to know that this message does not only inform an adjustment possibility was checked, but that it also delivers all the necessary details of the adjustment. ### Avoiding changes to symbol size and color Leave the _size of symbols as it is_ by default. For example, different sizes of tasks or events suggest that the bigger symbol is more important than the smaller one - an often unwarranted assumption. Instead of writing long labels, use short and consistent labels in line with your [naming conventions](naming-bpmn-elements.md) and move all additional information into BPMN annotations associated to your specific BPMN element. Furthermore, avoid _excessive use of colors_. Experience shows that colors are visually very strong instruments and psychologically very suggestive, but will typically suggest different things to different readers. Additionally, a colorful model often looks less professional. However, there are valid exceptions. For example, you could mark the _happy path_ through a process with a visually weak coloring: Another case for useful coloring might be to make a visual difference between _human_ and _technical flows_ within a bigger collaboration diagram by coloring the header bar on the left side of the pools. --- ## Modeling beyond the happy path First, model the happy path to the desired end result before collecting problems and exceptions, prioritizing them, and introducing them incrementally. Secondly, focus on one selected issue at a time, and choose the right techniques for modeling beyond the happy path. ## The happy path and beyond The happy path is kind of the default scenario with a positive outcome, so no exceptions, errors, or deviations are experienced. Typically, you want to model the happy path first, and therefore you should define the desired _end result_, find a suitable _start event_, and collect the _activities_ and external _dependencies_ which _always_ need to be considered to reach the result. When we have that, the diagram shows the _happy path_ of a business process (or of the selectively chosen part of the end-to-end business process): 1 _End Event_: It's often the easiest first step to agree upon the desired ("happy") end _result_ of a process. 2 _Start Event_: As a second step, one might agree upon a _trigger_ for the work leading to the end result. 3 _Activities_: After that, you can brainstorm and collect activities which _always_ need to be carried out to reach the result. 4 _Intermediate Events_: Optionally, you can brainstorm and collect _milestones_ (modeled as blank events) and important external _dependencies_ (e.g. modeled as message events). ### Modeling beyond the happy path by error scenarios As soon as you have this happy path, start modeling beyond the happy path. Focus on _one_ particular, selected problem at a time. 1. Try to _understand_ the worries for _the business_ in the light of the desired end result. 1. Identify the _undesired end result_ the process will reach in case the problem cannot be mitigated. This informs you about the _end event_ you will eventually reach because of the problem. 1. Identify the affected areas in the happy path. Can the problem occur at a _particular point_, _during_ (one or several) _activities_, or basically _all the time_? This will inform you about the most promising modeling technique for the problem: whether either _gateways_, _boundary events_, or _event-based subprocesses_ can serve you to fork off your "problem path". This best practice will guide you through practices that help you model beyond the happy path. ## Forking off at a particular point With BPMN gateways, we can deal with problems arising at a _particular point_ in our process. ### Dealing with results By using data-based gateways, we _actively decide_ "now and here" on the basis of our own _process data_ which path our process must move along. For example, we can therefore use an XOR gateway to fork off a "problem path," dealing with a problematic result of _our own activities_: 1 The _exclusive gateway_ deals with the potentially problematic result of incomplete order data. Note that we deal here with the procedural consequences of work which already took place in the preceding task, where we actually checked the order for completeness. 2 Again, the preceding task already dealt with the actual work of checking the customer's creditworthiness. The _result_ of the task is a "yes" or "no" (true or false). We can deal with data by means of a data-based gateway, which immediately redirects to the path our process must move along. 3 The _end event_ characterizes the undesired end result "order declined," which we now reach because of having modeled two problems. In the example, both of them lead to one and the same business outcome. ### Dealing with events By using event-based gateways, we _passively wait_ for _future events_ deciding about which path our process will have to move along. For example, we can therefore use use it to fork off a "problem path" dealing with an undesired event _outside of our own control_: 1 After having requested a delivery date (e.g. from wholesale), we use an _event-based gateway_ to passively wait for what happens next. We can not know "now and here", because it's outside of our own control. 2 The _intermediate message event_ allows us to deal with the undesired event that the ordered good is not deliverable. ### Dealing with missing results via timeouts By using event-based gateways, we can also deal with the situation that _nothing relevant_ for our process _happens_. We do this by defining a time period, after which we decide that we do not want to wait any longer: 1 The _intermediate timer event_ allows us to deal with the situation that nothing relevant for our process happened for a defined time period. In case we do not get an answer from wholesale, we inform the customer that the order is not deliverable at the moment. ## Forking off during (one or several) activities With BPMN boundary events, we can deal with problems arising _while we are actively occupied_ to carry out work in our process. ### Dealing with errors A typical case is that it turns out to be _impossible to achieve the result_ of an activity while working on it. We can then choose to interrupt our work and fork off a "problem path" to deal with the issue: 1 The _interrupting boundary error event_ allows us to deal with the fact that the order is not readable. As this prevents us from properly judging the completeness of the order, we cannot reach one of the expected results of our activity ("complete" or "not complete"), but instead deal with the problem by interrupting the activity and assuming the order to be declined. When modeling for business process automation, "dealing with errors" might be a highly technical concern. As a rule of thumb, we just want to show the _"business related" problems_ in a process model: those problems and errors which cause that our business process must move along a different path, because different work must be carried out as a reaction. An example for a typical technical concern would be that we currently cannot reach a system, which is why, for example, we want to re-attempt it another time later on. We do not show such purely technical problems in a business process diagram, not even in an executable one: (1) It would clutter the diagram, and (2) There are more suitable ways to deal with technical issues potentially occuring almost anywhere. Read our Best Practice about [dealing with problems and exceptions](../../development/dealing-with-problems-and-exceptions) from a more technical point of view to learn more about the border between business related shown in a process diagram and purely technical concerns not shown in a process diagram. ### Dealing with work on top of usual work Another typical use case for reacting to situations while we are actively occupied is that it sometimes turns out we need to do stuff _in addition to what we already do_: 1 We encapsulate part of our process into a subprocess to enable us to express that while we are occupied with that part of the process, additional work might pop up. 2 The _non-interrupting boundary timer event_ allows us to speed up order preparation in case it takes longer than two days; for example, by informing a responsible manager. ## Being able to react all the time A bit similar to boundary events, with BPMN event subprocesses we can deal with problems arising while we are actively occupied to carry out work. The main advantage when being compared with boundary events is that some issues can _occur almost anywhere_ on our way through the happy path. ### Dealing with issues occurring almost anywhere Some issues can occur almost anywhere on the way through our process. The event subprocess allows us to fork off a _problem path_ modeled separately from our main process to deal with such issues: 1 The _non-interrupting start message event_ of the event subprocess allows us to express that wherever we currently are on our way through order confirmation, it can happen that the customer requests information about the status of that process. 2 We should then provide the requested information without interferring with the order confirmation process itself. ### Dealing with canceling the process Another typical use case for event-based subprocesses is a cancellation requested by the customer: 1 The _interrupting start message event_ of the event subprocess allows us to express that wherever we currently are on our way through order confirmation, it can happen that the customer requests cancellation. 2 We should then interrupt the main process (which is already expressed by the nature of the start event) and inform an involved dealer. ## Boundary events as alternative for event based gateways ### Using receive tasks with boundary events The examples above leverage the _event based gateway_. BPMN also allows to model _receive tasks_ that wait for responses. This has the advantage that you now can leverage boundary events to deal with _missing results_ or other _events occuring while you are waiting_ for the response. This is an _alternative_ to the event-based gateways shown in the above models. 1 Instead of modeling an event for receiving a delivery date, we model a _task_ here. 2 The fact that we do not receive such an answer at all can now be modeled as an _interrupting boundary timer event_. We inform the customer about the status, but as the timer is interrupting, do not wait any longer for the delivery date. 3 It might turn out that the ordered good is not deliverable. This can be modeled as _boundary message event_. Upon that message we cancel any further waiting but inform the customer about the status instead. ### Modeling a multi phase escalation path Boundary events are particularly useful when you consider that you might want to remind your dealer that the answer is overdue and give them another chance for transmitting the delivery date before you give up waiting. First, consider how this could be achieved by using event-based gateways: 1 After having realized that the dealer's answer is late, we decide whether we want to remind the dealer and continue to wait - or not. We modeled here that we want to remind the dealer just once. 2 However, note that while we are reminding the dealer, we are strictly speaking not in a state "ready-to-receive" the dealer's answer! According to BPMN execution semantics, the dealer's message might get lost until we are back at the event-based gateway. While you might want to choose to ignore that when modeling for communication purposes only, you will need to get it right for executable models. To get the BPMN execution semantics above fully right, we would now need to attach the two possible answers of the dealer ("Delivery data fixed", "Ordered good not available") as boundary events to the task "Remind dealer", too! Quite a modeling construct, just to properly wait for the dealer's response, right? Therefore, consider the following alternative to this modeling issue using boundary events only: 1 Modeling a _non-interrupting boundary timer event_ directly at a task which waits for the response has the advantage that we never leave the "ready-to-receive" state and therefore avoid troubles with the strict interpretation of BPMN execution semantics. The second alternative is _very compact_ and avoids issues with _not being ready-to-receive_, but typically needs a _deeper understanding_ of BPMN symbols and their consequences for the token flow. Therefore, we sometimes also prefer event-based gateways for showing human flows, and ignore sophisticated token flow issues as discussed here. --- ## Modeling with situation patterns When modeling, you will sometimes realize that some situations share common characteristics. To save work for yourself and spread such knowledge within your organization, collect and document such patterns as soon as you understand their nature and have found a satisfying solution for modeling them. For a start, we collected some typical patterns for you, which we observe quite often in our modeling practice. You do not need to reinvent the wheel over and over again. ## Escalating a situation step by step You need something and hope that it happens. Such a hope for a result may materialize, but it does not have to! After some time, you will typically become impatient and try to do something to make it happen. But if it then still does not happen, there comes a point at which you will have to decide that you must accept a failure. We sometimes also call that very common pattern a **multi-step escalation**. **Example:** "A month ago, I ordered a pair of shoes with that new online shop! After two weeks of waiting: nothing. I contacted them to determine what's up. The clerk promised me that the shoes will leave the warehouse today! But again, nothing, so after another week I just canceled that order. Since then I did not hear a word." In this scenario, the shop clearly did not implement the escalation of the delay properly. They should have applied one of the following patterns in the order delivery process: ### Option 1: Using event-based gateways 1 After ordering the goods, the process passively waits for the success case by means of an event-based gateway: the goods should be delivered. However, in case this does not happen within a reasonable time, we make a first step of escalation: remind the dealer. 2 We still stay optimistic. Therefore, the process again passively waits for the success case by means of another event-based gateway: the goods should still be delivered. However, in case this does not happen again within a reasonable time, we make a second step of escalation: cancel the deal. **Evaluation:** - This solution explicitly shows how the two steps of this escalation are performed. Timers are modeled separately, followed by their corresponding escalation activities. - The usage of separate event-based gateways leads to _duplication_ (for example, of the receiving message events) and makes the model _larger_, even more so in case multiple steps of escalation need to be modeled. - During the time we need to remind the dealer, we are strictly speaking not in a position to receive the goods! According to the BPMN specification, a process can handle a message event only if it is ready to receive at exactly the moment it occurs. Fortunately, Camunda 8 introduced [message buffering](/components/concepts/messages.md#message-buffering), allowing to execute this model properly without loosing messages. Using Camunda 7, the message might get lost until we are at the second event-based gateway. :::note You might want to use that pattern when modeling _simple two phase escalations_. You should not execute it on Camunda 7. ::: ### Option 2: Using gateways forming a loop 1 After having ordered the goods, the process passively waits for the success case by means of an event-based gateway: the goods should be delivered. However, in case this does not happen within a reasonable time... 2 We choose by means of an exclusive gateway to make a _first step of escalation_: remind the dealer. We still stay optimistic. Therefore, the process returns to the event-based gateway and again passively waits for the success case: the goods should still be delivered. However, in case this does not happen again within a reasonable time, we choose a _second step of escalation_: cancel the deal. **Evaluation:** - This model is a more _compact_ and more _generic_ modeling solution to the situation. If it comes to multiple steps of escalation, you will need such an approach to avoid huge diagrams. - The solution is _less explicit_. We could not choose to label the timer with explicit durations, as a single timer is used for both durations. The solution is _less readable_ for a less experienced reading public. For a fast understanding of the two step escalation, this method of modeling is less suitable. - During the time we need to remind the dealer, we are strictly speaking not in a position to receive the goods! According to the BPMN specification, a process can handle a message event only if it is ready to receive at exactly the moment it occurs. Fortunately, Camunda 8 introduced [message buffering](/components/concepts/messages.md#message-buffering), allowing to execute this model properly without loosing messages. Using Camunda 7, the message might get lost until we are at the second event-based gateway. :::note You might want to use that pattern when modeling _escalations with multiple steps_. You should not execute it on Camunda 7. ::: ### Option 3: Using boundary events 1 After having ordered the goods, the process passively waits for the success case by means of a receive task: the goods should be delivered. However, in case this does not happen within a reasonable time... 2 a non-interrupting boundary timer event triggers a _first step of escalation_: remind the dealer. We still stay optimistic. Therefore, we did not interrupt the receive task, but continued to wait for the success case: the goods should still be delivered. 3 However, in case this does not happen within a reasonable time, we trigger a _second step of escalation_ by means of an interrupting boundary timer event: interrupt the waiting for delivery and cancel the deal. **Evaluation:** - This model is even more _compact_ and a very _generic_ modeling solution to the situation. If it comes to multiple steps of escalation, the non-interrupting boundary timer event could even trigger multiple times. - The model complies with BPMN execution semantics. Since we never leave the wait state, the process is always ready to receive incoming messages. - The solution is _less readable_ and _less intuitive_ for a less experienced reading public, because the way the interrupting and non-interrupting timers collaborate requires a profound understanding of boundary events and the consequences for token flow semantics. For communication purposes, this method of modeling is therefore typically less suitable. :::note You might want to use that pattern when modeling _escalations with two steps_ as well as _escalations with multiple steps_ for _executable models._ ::: ## Requiring a second set of eyes For a certain task - typically a critical one in terms of your business - you need the opinion, review, or approval of two different people. We sometimes also call that pattern the **four eyes principle**. **Example:** The manager of a small sized bank's lending department has a problem: "Over the last quarter, we lost €100,000 in unrecoverable medium-sized loans. Controlling now tells me that could probably have been easily avoided by more responsible decisions of our lending department staff! I want that every such decision is signed off by two people from now on." Modeling a process dealing with that requirement can be achieved easily, but the better solution also depends on whether you prefer overall speed over total effort. All of the following modeling patterns assume that the two or more tasks needed to ultimately approve the loan must not be completed by one and the same person. When executing such patterns, you must enforce that with the workflow engine. ### Option 1: Using separate tasks 1 A first approver looks at the loan and decides whether they approve. If they decide not to approve, we are done, but if the loan is approved... 2 ...a second approver looks at the loan. If they also decide to approve, the loan is ultimately approved. **Evaluation:** - This solution _explicitly_ shows how the two steps of this approval are performed. Tasks are modeled separately, followed by gateways visualizing the decision making process. - Note that the approvers work in a _strictly sequential_ mode, which might be exactly what we need in case we want _minimization of effort_ and, for example, display the reasonings of the first approver for the second one. However, we also might prefer _maximization of speed_. If this is the case, observe solution [option 3 (multi-instance)](#option-3-using-a-multi-instance-task) further below. - The usage of separate tasks leads to _duplication_ and makes the model _larger_, even more so in case multiple steps of approvals need to be modeled. You might want to use that pattern when modeling the need for a _second set_ of eyes needed in _sequential_ order, therefore _minimizing effort_ needed by the participating approvers. While it is theoretically possible to model separate, explicit approval tasks in parallel, we do not recommend such patterns due to readability concerns. As a better alternative when looking for _maximization of speed_, observe [option 3 (multi-instance)](#option-3-using-a-multi-instance-task) below. ### Option 2: Using a loop 1 A first approver looks at the loan and decides if they approve. If they decide not to approve, we are done, but... 2 ...if the loan is approved, we turn to a second approver to look at the loan. If they also decide to approve, the loan is ultimately approved. **Evaluation:** - This model is a more _compact_ modeling solution to the situation. If it comes to multiple sets of eyes needed, you will probably prefer such an approach to avoid huge diagrams. - Note that the approvers work in a _strictly sequential_ mode, which might be exactly what we need if we want _minimization of effort_ and, for example, display the reasonings of the first approver for the second one. However, we also might prefer _maximization of speed_. If this is the case, observe [option 3 (multi-instance)](#option-3-using-a-multi-instance-task) below. - The solution is _less explicit_. We could not choose to label the tasks with explicit references to a first and a second step of approval, as a single task is used for both approvals. The solution is _less readable_ for a less experienced reading public. For a fast understanding of the two steps needed for ultimate approval, this method of modeling is less suitable. You might want to use that pattern when modeling the need for _multiple sets_ of eyes needed in _sequential_ order, therefore _minimizing effort_ needed by the participating approvers. ### Option 3: Using a multi-instance task 1 All the necessary approvers are immediately asked to look at the loan and decide by means of a multi-instance task. The tasks are completed with a positive approval. Once all positive approvals for all necessary approvers are made, the loan is ultimately approved. 2 If the loan is not approved by one of the approvers, a boundary message event is triggered, interrupting the multi-instance task and therefore removing all the tasks of all approvers who did not yet decide. The loan is then not approved. **Evaluation:** - This model is a very _compact_ modeling solution to the situation. It can also easily deal with multiple sets of eyes needed. - Note that the approvers work in a _parallel_ mode, which might be exactly what we need in case we want _maximization of speed_ and want the approvers to do their work independent from each other and uninfluenced by each other. However, we also might prefer _minimization of effort_. If this is the case, refer to [option 1 (separate tasks)](#option-1-using-separate-tasks) or [option 2 (loop)](#option-2-using-a-loop) above. - The solution is much _less explicit_ and _less readable_ for a less experienced reading public, because the way the boundary event interacts with a multi-instance task requires a profound understanding of BPMN. For communication purposes, this method of modeling is therefore typically less suitable. You might want to use that pattern when modeling the need for _two_ or _multiple sets_ of eyes needed in _parallel_ order, therefore _maximising speed_ for the overall approval process. ## Measuring key performance indicators (KPIs) You want to measure specific aspects of your process execution performance along some indicators. **Example:** A software developer involved in introducing Camunda gets curious about the business: "How many applications do we accept or decline per month, and how many do we need to review manually? How many are later accepted and declined? How much time do we spend for those manual work cases, and how long does the customer have to wait for an answer? I mean...do we focus on the meaningful cases...?" When modeling a process, we should actually always add some information about important key performance indicators (KPIs) implicitly. For example, specifically [naming start and end events](../naming-bpmn-elements/#naming-events) with the process state reached from a business perspective. Additionally, we might explicitly add additional business milestones or phases. While the following section concentrates on the aspects of modeling KPIs, you might want to learn more about using them for [reporting about processes](../../operations/reporting-about-processes/) from a more technical perspective. For example, when being faced with the task to actually retrieve and present Camunda's historical data collected on the way of execution. ### Option 1: Showing milestones 1 First, we assess the application risk based on a set of automatically evaluable rules. 2 We can then determine whether the automated rules already came to a (positive or negative) conclusion or not. If the rules led to an unsure result, a human must assess the application risk. 3 We use explicit intermediate events to make perfectly clear that we are interested in the applications which never see a human... 4 ...and be able to compare that to the applications which needed to be assessed manually, because the automatic assessment failed to determine a clear result. 5 We also use end events, which are meaningful from a business perspective. We must know whether an application was either accepted... 6 ...or rejected. By means of that process model, we can now let Camunda count the applications which were accepted and declined. We know how many and which instances we needed to review manually, and can therefore also narrow down our _accpeted/declined statistics_ to those manual cases. Furthermore, we will be able to measure the _handling time_ needed for the user task; for example, by measuring the time needed from claiming the task to completing it. The customer will need to wait a _cycle time_ from start to end events, and these statistics, for example, could be limited to the manually assessed applications and will then also include any idle periods in the process. *By comparing the economic _value_ of manually assessed insurance policies to the *effort\* (handling time) we invest into them, we will also be able to learn whether we focus our manual work on the meaningful cases and eventually improve upon the automatically evaluated assessment rules. ### Option 2: Emphasizing process phases As an alternative or supplement to using events, you might also use subprocesses to emphasize certain phases in your process. 1 By introducing a separate embedded subprocess, we emphasize the _phase_ of manual application assessment, which is the critical one from an economic perspective. Note that this makes even more sense if multiple tasks are contained within one phase. ## Evaluating decisions in processes You need to come to a decision relevant for your next process steps. Your actual decision depends on a number of different factors and rules. We sometimes also call that pattern **business rules** in BPMN. **Example:** The freshly hired business analyst is always as busy as a bee: "Let's see... Category A customers always get their credit card applications approved, whereas Category D gets rejected by default. For B and C it's more complicated. Right, in between 2500 and 5000 Euros, we want a B customer, below 2500 a C customer is OK, too. Mmh. Should be no problem with a couple of gateways!" ### Showing decision logic in the diagram? When modeling business processes, we focus on the flow of work and just use gateways to show that following tasks or results fundamentally differ from each other. However, in the example above, the business analyst used gateways to model the logic underlying a decision, which clearly is considered to be an anti-pattern! It does not make sense to model the rules determining a decision inside the BPMN model. The rules decision tree will grow exponentially for every additional criteria. Furthermore, we typically will want to change such rules much more often than the process (in the sense of tasks needed to be carried out). ### Using a single task for a decision 1 Instead of modeling the rules determining a decision inside the BPMN model, we just show a single task representing the decision. Of course, when preparing for executing such a model in Camunda, we can wire such a task with a DMN decision table or some other programmed piece of decision logic. 2 While it would be possible to hide the evaluation of decision logic behind the exclusive gateway, we recommend always showing an explicit node with which the data is retrieved, which then might be used by subsequent data-based gateways. ## Distinguishing undesired results from fatal problems You model a certain step in a process and wonder about undesired outcomes and other problems hindering you to achieve the result of the step. **Example:** What today is a problem for the business might become part of the happy path in a less successful future: "Before we can issue a credit card, we must ensure that a customer is credit-worthy. Unfortunately sometimes it might also turn out that we cannot even get any information about the customer. Then we typically also reject at the moment. Luckily, we do have enough business with safe customers anyway." ### Option 1: Using gateways to check for undesired results 1 Showing the check for the applicant's creditworthiness as a gateway also informs about the result of the preceding task: the applicant might be creditworthy - or not. Both outcomes are _valid results_ of the task, even though one of the outcomes here might be _undesired_ from a business perspective. ### Option 2: Using boundary error events to check for fatal problems 1 Not to know anything about the creditworthiness (because we cannot even retrieve information about the applicant) is not considered to be a valid result of the step, but a _fatal problem_ hindering us to achieve any valid result. We therefore model it as a boundary error event. The fact that both problems (an unknown applicant number or an applicant which turns out not to be credit-worthy) lead us at the moment to the same reaction in the process (we reject the credit card application) does not influence that we need to model it differently. The decision in favor of a gateway or an error boundary event solely depends on the exact definition of the result of a process step. Refer to the next section. ### Understanding the definition of the result What we want to consider to be a valid result for a process step depends on assumptions and definitions. We might have chosen to model the process above with slightly different execution semantics, while achieving the same business semantics: 1 The only valid result for the step "Ensure credit-worthiness" is knowing that the customer is in fact credit-worthy. Therefore, any other condition must be modeled with an error boundary event. To advance clarity by means of process models, it is absolutely crucial for modelers to have a clear mental definition of the _result_ a specific step produces, and as a consequence, to be able to distinguish _undesired results_ from _fatal problems_ hindering us to achieve any result for the step. While there is not necessarily a right way to decide what to consider as a valid result for your step, the business reader will typically have a mental preference to observe certain business issues, either more as undesired outcomes or more as fatal problems. However, for the executable pools, your discretion to decide about a step's result might also be limited when using, for example, service contracts which are already pre-defined. The same distinction applies to a step carried out by an [AI agent](/reference/glossary.md#ai-agent). The response the agent returns when its loop ends is a valid result you check with a gateway, even when that response is a negative one. An error the agent itself raises, such as reaching its configured maximum number of model calls, is a fatal problem you catch with a boundary [error event](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess.md#error-handling). ## Asking multiple recipients for a single reply You offer something to or request something from multiple communication partners, but you actually just need the first reply. We sometimes also call that pattern **first come, first serve**. **Example:** A well-known personal transportation startup works with a system of relatively independent drivers. "Of course, when the customer requests a tour, speed is everything. Therefore, we need to limit a tour to those of our drivers who are close by. Of course, there might be several drivers within a similar distance. We then just offer the tour to all of them!" ### Using a multi-instance task 1 After determining all drivers currently close enough to serve the customer, we push the information about the tour to all of those drivers. 2 We then wait for the reply of a single driver. Once we have it, the process won't wait any longer, proceeds to the end event, and informs the customer about the approaching driver. According to the process model, it is possible that another driver accepts the tour as well. However, as the process in the tour offering system is not waiting for the message anymore, it will get lost. As our process proceeded to the end event after the first reply, all subsequent messages are intentionally ignored in this process design. ## Processing a batch of objects You need to process many objects at once, which were already created before one by one, or which were updated one by one to reach a certain status. We sometimes also call that pattern simply the **1-to-n problem**. **Example:** A lawyer explains to a new client the way he intends to bill him: "Of course, if you need advice, you can call me whenever you want! We will agree about any work that needs to be done and my assistant will track those services which are subject to a charge. Once a month mostly you will receive a neatly-structured invoice providing you with all the details!" ### Using data stores and multi instance activities 1 The client asks for advice whenever they need it. Note that we create one process instance per request for advice. 2 The lawyer makes sure to record the billable hours needed for the client. 3 As he does not directly inform anybody by doing this, but rather collects data, we show this with a data store representing the time sheet and a data association pointing in its direction - representing the write operation. 4 The assistant starts their invoicing process on a monthly basis. In other words, we create one process instance per monthly billing cycle. 5 As a first step, the assistant determines all the billable clients. This are the clients for which time sheet entries exist in the respective month. Note that we have _many_ legal advice instances who have a relationship to _one_ billing instance and that the connection is implicitly shown by the read operation on the current status of data in the time sheet. 6 Now that the assistant knows the billable clients, they can iterate through them and invoice all of them. We use a sequential multi-instance subprocess to illustrate that we need to do this for every billable client. 7 On the way, the assistant is also in charge of checking and correcting time sheet entries, illustrated with a parallel multi-instance task. Note that these time sheet entries (and hence task instances) relate here 1:1 to the instances of the lawyer's "legal consulting" process. In real life, the lawyer might have created several time sheet entries per legal advice process, but this does not change the logic of the assistant's process. 8 Once the client is invoiced, the assistant starts a "payment processing" instance per invoice, the details of which are not shown in this diagram. We can imagine that the assistant needs to be prepared to follow up with reminders until the client eventually pays the bill. ## Concurring dependent instances You need to process a request, but need to make sure that you don't process several similar requests at the same time. **Example:** A bank worries about the increasing costs for creditworthiness background checks: "Such a request costs real money, and we often have packages of related business being processed at the same time. So we should at least make sure that if one credit check of a customer is already running, we do not want another credit check for the same customer to be performed at the same time." ### Using message events 1 Once an instance passes this event and moves on to the subsequent actual determination of the creditworthiness... 2 ...other instances will determine that there already exists an active instance and wait to be informed by this instance. 3 When the active instance has determined the creditworthiness, it will move on to inform the waiting instances... 4 ...which will receive a message with a creditworthiness payload and be finished themselves with the needed information. The model explicitly shows separate steps (_determine_ and _inform_ waiting instances) which you might want to implement more efficiently within one single step doing both semantic steps at once by means of a small piece of programming code. ### Using a timer event While using timer events can be a feasible approach in case you want to avoid communication between instances, we do not recommend it. For example, one downside is that such solutions cause delays and overhead due to the perdiodical queries and the loop. 1 Once an instance passes this event and moves on to the subsequent actual determination of the creditworthiness... 2 ...all other instances will go into a wait state for some time, but check periodically, if the active instance is finished. 3 When the active instance has determined the creditworthiness and finishes... 4 ...all other instances will also finish after some time. --- ## Naming BPMN elements Name all elements in your BPMN diagrams by focusing on the business perspective. For activities, use a verb to describe what to do. For events, describe in which (business) state the process or domain object is currently in. For (data-based) gateways, pose a question and describe the conditions under which the process moves on along the outgoing flows. ## Essential practices ### Naming activities Name a _task_ using an object and a verb in the infinitive. By doing this, you consistently describe _what you do with an object_. Name a _subprocess_ (or _call activity_) by using an object and a (by convention _nominalized_) verb. Similar to tasks, you should always describe _what you do with an object_. :::note Avoid very broad and general verbs like "Handle invoice" or "Process order." Try to be more specific about what you do in your activity from a business perspective. ::: ### Naming events Wherever possible, name an _event_ using an object and a verb reflecting a state. Always try to describe _which state an object is in_ when the process is about to leave the event. This naming approach does not always work perfectly. In those cases, precisely describe the business semantics when the process is about to leave the event. The following names are also valid: Be specific about the state you reached with your event from a business perspective. Often, you will reach "success" and "failure" like events from a business perspective: 1 "Invoice paid" better qualifies the "successful" business state than "Invoice processed" would... 2 ...because in principle, you can call the failed state "Invoice processed", too, but the reader of the diagram is much better informed by calling it "Invoice rejected". :::note Avoid very broad and general verbs like "Invoice processed" or "Order handled"! ::: ### Naming gateways Label a data-based _exclusive gateway_ with a question. Label the outgoing sequence flows with the conditions they are executed under. Formulate the conditions as answers to the question posed at the gateway. This naming approach does not always work for _inclusive gateways_, because the outgoing flows' conditions can be completely independent from each other. Still, use a question whenever possible. If this is not possible, leave out the question completely but describe the conditions under which the outgoing paths are executed. _Avoid naming event-based gateways_, but ensure you name their subsequent events. Also, avoid naming _parallel gateways_ and all forms of _joining gateways_. You don't need to specify anything about those gateways, as the flow semantics are always the same. ### Naming processes A _pool_ should be given the same name as the process the pool contains using an object and a nominalized verb. Optionally, add the organizational role responsible for the process shown in the pool as a whole. If you have more than one lane in a pool, name each _lane_ using the organizational role or technical system responsible for carrying out the activities shown in the lane. Name a _diagram_ (file) with same name as the process shown in the diagram. In case of a collaboration diagram, use a name reflecting the end-to-end perspective shown in that diagram. ## Recommended practices ### Using sentence case Use [sentence case](https://en.wiktionary.org/wiki/sentence_case) when naming BPMN symbols. This is standard capitalization of an English sentence, with the first letter uppercase and subsequent letters lowercase, with exceptions such as proper nouns or acronyms. ### Avoiding technical terms Avoid using purely _technical terms_ when naming activities or other BPMN symbols, for example. These are not always clear to every reader. Completely avoid using names of coding artifacts like classes, methods, technical services, or purely technical systems. ## Helpful practices ### Avoiding abbreviations Avoid using _abbreviations_ as they are not always clear to every reader. This is especially true for abbreviations which are specific to companies or departments. Try to avoid them completely. If you want to use an abbreviation in your model (to save space or sometimes even to improve understandability) make sure you explain the abbreviation in the model in brackets, by text annotations, or use an accessible glossary. --- ## Naming technically relevant IDs For executable flows, properly name all relevant technical element IDs in your BPMN diagrams. :::note Keep technical IDs and names short enough for the target environment. Most user-defined BPMN and DMN IDs, names, deployed resource names, and form IDs support up to 32,768 characters with Elasticsearch/OpenSearch-backed storage and 256 characters with RDBMS-backed storage. If you use RDBMS, or might migrate to it later, keep these values comfortably within the 256-character limit. ::: Focus on process, activity, message, and error IDs, but also consider events as well as gateways and their sequence flows that carry conditional expressions. Those elements can show up regularly (e.g. in your logs) and it makes things easier if you can interpret their meaning. ## Using naming conventions for BPMN IDs Define developer-friendly and business-relevant IDs for the process itself, as well as all activities, messages, and errors. Also consider events, gateways, and the sequence flows that carry conditional expressions. Even though IDs are just identifiers, keep in mind that they will show up regularly on the technical level. Meaningful IDs will help a lot. One case where an ID is more than an identifier is an element inside an [ad-hoc sub-process](/reference/glossary.md#ad-hoc-sub-process) that an [AI agent](/reference/glossary.md#ai-agent) uses as a tool. There, the ID is the tool name passed to the model, and it directly affects which tool the model selects, so describe what the tool does rather than applying the type prefixes below. See [write a tool name and description](/components/agentic-orchestration/add-tool-to-ai-agent.md#write-a-tool-name-and-description) for more details. Examine the IDs shown in the following example: The following table provides you with a guideline that we would use in a context where developers are comfortable with _Java_ and _PascalCase_ naming style. You may adapt these suggestions to typical naming conventions used in your programming context. | | | XML Attribute | Prefix or Suffix | Resulting ID | | ----- | ----------------- | -------------------- | ---------------- | ----------------------------- | | **1** | Tweet Approval | process/@id | Process | TweetApprovalProcess | | **2** | New tweet written | startEvent/@id | StartEvent\_ | StartEvent_NewTweetWritten | | | | message/@id | Message\_ | Message_NewTweetWritten | | | | message/@name | Msg\_ | Msg_NewTweetWritten | | **3** | Review tweet | userTask/@id | Task\_ | Task_ReviewTweet | | **4** | Tweet approved? | exclusiveGateway/@id | Gateway\_ | Gateway_TweetApproved | | **5** | No | sequenceFlow/@id | SequenceFlow\_ | SequenceFlow_TweetApprovedNo | | **6** | Tweet duplicated | boundaryEvent/@id | BoundaryEvent\_ | BoundaryEvent_TweetDuplicated | | | | error/@id | Error\_ | Error_TweetDuplicated | | | | error/@errorCode | Err\_ | Err_TweetDuplicated | | **7** | Tweet published | EndEvent\_/@id | EndEvent\_ | EndEvent_TweetPublished | ### Editing IDs Camunda recommends using the properties panel on the right side of the modeling interface in [Camunda Hub](/components/hub/index.md) or Desktop Modeler to edit technical identifiers and change them according to your naming conventions. For example, this is shown here for the process ID: ![Properties Panel](naming-technically-relevant-ids-assets/camunda-modeler-properties-panel.png) We strongly recommend you **do not edit existing identifiers directly in the XML**, as it might accidentally corrupt your BPMN file. You must keep the identifiers in the section about the graphical layout (so called "DI" for diagram interchange) further down in sync with the execution semantics at the top of the XML. However, we include an XML example of all those identifiers mentioned for illustration: ```xml ... ``` 8 Elements in the diagram interchange section (DI) reference identifiers from above; you have to adjust them accordingly. This happens automatically in Camunda Hub and Desktop Modeler. Changing IDs can potentially break your tests or even process logic if done at a late stage of development. Therefore, consider using meaningful IDs right from the beginning and perform the renaming as part of the modeling. ### Aligning the BPMN file name with the process ID It is a good practice to _align_ the _file name_ of your BPMN models with the process ID of the executable process that is inside the file. ![BPMN file name](naming-technically-relevant-ids-assets/aligning-the-bpmn-file-names.png) ## Generating ID constants classes If you have lots of process, case, and decision definitions with lots of IDs, consider generating constant classes (e.g. via XSLT) directly from your BPMN or DMN XML files. For example, this can be used for testing. ## Using a modeler plugin to generate meaningful ids You can use [this modeler plugin community extension](https://github.com/camunda-community-hub/camunda-modeler-plugin-rename-technical-ids) to automatically convert your IDs to comply with our best practices. Of course, you could also use this as a basis to create your own modeler plugin to generate IDs that follow your custom naming conventions. Or, you could implement a similar plugin to implement checks if all relavant IDs follow your naming conventions. --- ## Reporting about processes The Camunda engine automatically collects audit information about historical process or decision instances. Leverage this data by generating and displaying business relevant reports. Add business relevant phases and milestones to your process models serving as a basis for key performance indicators (KPIs). ## Modeling key performance indicators (KPIs) When modeling a process, you always add information about important key performance indicators implicitly; for example, by introducing **start and end events**. Additionally, you can explicitly add the following: - Meaningful additional business **milestones** by modeling **intermediate events**, for example. This might not have any execution semantics other than leaving a trace in the history of the workflow engine. The milestone is met as soon as the process has passed the event. Its status can therefore be **passed** or **not passed**. - Meaningful business **phases** by modeling things like (embedded) **subprocesses**. In contrast to a milestone, a phase's state can be **not entered**, currently **active**, or **passed**. Consider the following example - a "Tweet Approval Process" shows start and end events as well as **milestones**: " 3 After one business day, the reviewer is reminded to speed up - and such reviews are internally _marked_ by passing the end event 'Review done slowly'. 4 **Approved tweets** will pass the additional **intermediate event**. The **cycle time** up until that point is automatically captured too. 5 Furthermore, when tweets are successfully published, we are interested in the **ratio** of those tweets... 6 ...when compared to tweets that do not get published. Therefore, we model _two different end events_ representing those two business end states of the process. :::note Duplicate tweets will _not be published_ even though they have been _approved_ before. The more precisely we describe and _name_ the business semantics of events, the better our KPI's will reflect the reality we want to measure! ::: When you do not (only) want to concentrate on milestones, but _phases_ in your process, model the phases as subprocesses: 1 The phase _Review_—modeled with a subprocess—will be active, while the human reviewer will need to find time to complete the task... 2 ...whereas the phase _Publication_ will be completed automatically - hence process instances "remaining" there for longer than a few seconds will probably indicate ongoing problems with the uptime and reachability of the used services. ## History architecture It is useful to understand the architecture around history data in Camunda 8. :::caution Camunda 7 Note that the history architecture is very different in Camunda 7.x, refer to [Camunda 7 User Guide](https://docs.camunda.org/manual/latest/user-guide/process-engine/history/). ::: ![History architecture](reporting-about-processes-assets/history-architecture.png) Camunda saves historical data not just when a process instance finishes, but on the go, while a process instance is active. By doing this, Camunda separates runtime data from history data. A growing history will not influence the runtime behavior, and you should never need to access runtime data for reporting purposes. Historical data can be leveraged via three possible mechanisms: - **Camunda tools**: Leverage Camunda Operate or Camunda Optimize. This is a very simple approach that works out-of-the-box and should satisfy many requirements already. Camunda Operate focuses on operational use cases ("Where is my process? Why did this fail?") whereas Camunda Optimize provides business intelligence about your processes. Optimize allows you to build reports and dashboards including setting alerts for thresholds. For processes containing AI agents, the [agentic control plane](/components/optimize/userguide/agentic-control-plane.md) in Optimize adds agent-specific metrics such as token usage, reliability, and tool calls without additional setup. - **Query API**: Using the public API (currently under development), this has the advantage that you can make use of the history data within your own applications. - Pushing **events**: Pushing Camunda events by using [exporters](/components/zeebe/technical-concepts/architecture.md#exporters). Note that you can only add own exporters in a Self-Managed setting, not in Camunda 8 SaaS. Exporters have the advantage that you can push the data into any infrastructure you have, and possibly even filter or enrich the data in that step. ## Connecting custom business intelligence systems (BI), data warehouses (DWH), or monitoring solutions You might move data from the Camunda History to a decoupled system like a Business Intelligence (BI) solution, a Data Warehouse (DWH), some Data Lake, or an own monitoring solution, for example based on Prometheus. Leveraging typical BI system's **ETL** (extract, transform, and load) features allows you to optimize data structure for your reporting purposes (to _speed up_ report generation) or to combine generic process engine data with business entities (to allow for _more in-depth analysis_). To get the data into the BI system, leverage one of the mechanisms described above. Our recommendation generally is: - In SaaS, leverage the Operate API to regularly pull data, as custom exporters are not supported there. --- ## Versioning process definitions For real-life applications, it's crucial to understand how Camunda deals with evolving process definitions by means of versioning. As a rule of thumb, we recommend to version just the process and decision models, but not other process solution artifacts (like e.g. code classes or scripts). Often you might not even want to run multiple model versions at the same time, then you have to think about migrate running process instances to new versions. When modeling very long-running processes (> 6 months), consider cutting them into reasonable pieces to ease managing your versioning requirements. ## Understanding versioning By default, deploying a process or decision definition means that the workflow engine will check if the version has changed. If it has, it will register that deployment as a new version of the definition. By default, running instances will continue to run on the basis of the version they started with, new instances will be created based on the latest version of that definition. ![Versions](versioning-process-definitions-assets/database-versions.png) Agents in your process are versioned the same way. Deploying a new version of a process creates a new [agent definition](/components/agentic-orchestration/agent-definitions-and-instances.md#agent-definitions) for each of its agent elements, bound to that process definition version. ## Selecting the best versioning approach ### Running versions in parallel You can run several versions of a model in parallel. The big _advantage_ of that default behavior is that you can deploy changed process definitions without caring about running process instances. The process engine is able to manage running instances based on different process definitions in parallel. The _disadvantage_ is that one needs to deal with the operational complexity of different versions of the process running in parallel as well as the additional complexity in case those processes call subprocesses which have different versions of their own. Run versions _in parallel_ for - _Development_ or _test systems_ for which you do not care about old instances - _Phasing out_ existing instances as the existing instances need to finish based on the model they where created with, which often has _legal reasons_. - Situations in which _migration is not advisable_, because it is too complex and too much effort when weighed against its upsides. ### Migrating process instances to a new version _Migrate_ running instances to the newest definition when: - Deploying _patches or bug fixes_ of a process model. - _Avoiding operational complexity_ due to different versions running in production is a priority. Migrating process instances can be achieved either by using the operations tooling or by calling the Zeebe API. You can use the [Orchestration Cluster API (REST)](../../../apis-tools/orchestration-cluster-api-rest/specifications/migrate-process-instance.api.mdx) or the [Zeebe API (gRPC)](../../../apis-tools/zeebe-api/gateway-service.md#migrateprocessinstance-rpc) to migrate a process instance. Learn more about the concepts of [process instance migration](../../../components/concepts/process-instance-migration.md) in the components section. You can also learn [how to migrate process instances in Operate](../../../components/operate/userguide/process-instance-migration.md) in it's dedicated section. It's important to understand that process instance migration _maintains the full 'identity' of the migrated process instances_ including their unique IDs and their full history audit trail. However, as the process definition also might change fundamentally in between versions, this can have effects on the history log of a process instance which might be unexpected from an end user's or operator's perspective. ### Things to consider before migration When planning your migration, here are some factors to consider: - _Do I have a good reason to migrate?_ Technically, you do not have to migrate process instances when using Camunda. Previous process definition instances will simply continue to run as intended (with some important caveats, note other things to consider below). Here are some examples of good reasons to migrate: - Your supporting implementation resources have changed. - Your latest process definition represents a substantial change in your business process. - Your latest process definition fixes a bug. - Your latest process definition enforces some time-sensitive legal obligations or rules. - _How big of a difference is there between process definition versions?_ Not only the definition itself, but the data required to be present at any given time in your instance. - _Did supporting implementation resources change from the previous deployment?_ If a service implementation changes in the new deployment and the reference to the implementation did not change from the previous deployment, then older process instances that are in flight will utilize the newer implementation by default upon deployment of the new resources. If that breaks older instances, then you must migrate. - _Do I have a proper infrastructure to support “real data” testing of my migration?_ This might be the most important aspect. An ideal way to test your process instance migration would be to have prod-like data in some kind of staging environment that represents not only the type and quality of existing production data, but also volume, scale, and size. You run your migration there so that you know what to expect when it comes time to migrate in production. You also need the ability to quickly reset this data via some kind of snapshot, so that you can test over and over again. You can expect many iterations of your migration before you move forward. ## Avoid versioning of dependant artifacts When versioning process or decision definitions, you need to be aware that the process of course communicates with the outside world, e.g. by _calling services_ or by _using forms_ to collect data input from human users. All the additional artifacts needed for that might _depend_ on the details of each other in a subtle way. Whenever possible, we recommend that you _avoid to version other artifacts_ beyond the process and/or decision definitions, in other words, just version '.bpmn' and '.dmn' files by using the default mechanism of the process engine. Embed all other artifacts (like e.g. classes, templates, scripts) into your normal application (for example a Java or Node.js application) and don't version them. Of course, this approach requires that you _manage the subtle differences_ needed by running process instances of old versions. There are various options to do that. And even if some of those options discussed below might not sound 'ideal' from a theoretical point of view, they proved to be _good enough_ for real life purposes and _much easier to understand_ than complex approaches. As understandability by every team member is a very important argument, we recommend going for the approach that is as simple as possible. The following options us a Java example of a process solution, containing not only the process model, but also some Java code and an HTML form: ![Sample Process Application](versioning-process-definitions-assets/process-solution-example.png) ### Option 1: Keep the artifacts backwards compatible _Extend_ the functionality of e.g. a method in `MyClass.java` in a way which can still deal with "old" process instances. ```java public class MyClass { public void doSomething(Long customerId) { if(customerId != null) { // <1> // new code introduced } } } ``` 1 Assume you introduced a customerId in the new version of the process. Your code can still deal with old cases not aware of a customerId. ### Option 2: Introduce a new artifact for different versions _Change_ the artifact and add a new version of it to the application. Now you can reference this new artifact from your new version of the process definition, while the old version will continue to use the first version of it. For example: - Change the file name for the form from `task-form.html` to `task-form-v2.html` - Change the `task type` of a service task from `doSomething` to `doSomethingV2` ![Sample Process Application](versioning-process-definitions-assets/process-solution-v2.png) Sometimes it is preferable to manage different versions by means of folders/packages. Just make sure to have a clear and straightforward convention to keep track of the versions. ## Dealing with long running processes In general, _do not be concerned with deploying long-running processes_ which might run days, weeks or even months. After all, this is exactly what Camunda was built to properly deal with. Having said that, also review the possibilities the workflow engine provides with respect to _cutting process definitions_ (e.g. via _message exchange_ or via _call activities_) and _migrating running process instances_. But even though it's possible to migrate running process instances to a new version (refer below), it's typically a bit of _effort_. Therefore, the information presented in the following sections is meant to enable your conscious decision at which points it might make sense for you to avoid the necessity for migration by cutting processes and which aspects of versioning behavior you can control by doing that. ### Cutting very long running processes into pieces The longer the lifespans of process instances are, the bigger the _risks_ that you might want to exchange important software components like e.g. the workflow engine itself. Typically, _very long-running, end-to-end processes_ (running longer than _six months_) have periods without activity (e.g. waiting for a certain date in the future). Cut the process into several independent process definitions at these points. 1 After the mobile phone was shipped, we finish the first process instance and just keep a reminder for the renewal in 24 months. 2 We periodically check due renewals and start new process instances whenever necessary. We typically don't model such processes in one diagram it's shown here as a way to show the message flow. Typically, we would rather use a separate diagram per executable process and either leave out the other process completely or show it as a collapsed pool. Also try to avoid modeling the complete life-cycle of very long living objects, like a life insurance contract. Only capture the active phases as separate processes (e.g. "Policy Issuing", "Address Change", "Cancellation" or "Death"). Having said this, we want to emphasize that the engine is perfectly fine with handling lots of process instances for a long time. So if you want to have process instances waiting for months or years, you can still do so. Just make sure you think about all resulting implications. ### Using call activities to influence versioning behaviour of pieces When calling separately modeled subprocesses (i.e. _Call Activities_), the default behavior of the process engine is to call the _latest_ deployed version of that subprocess. You can change this default 'binding' behavior to call a _specific_ version or the version which was _deployed_ together with the parent process. Keeping in mind pros and cons of versioning as discussed above, we can therefore _encapsulate parts of a process_, for which we want to be able to change the runtime behavior more often into such call activities. This is an especially useful consideration for _long-running processes_. 1 We could decide that we always want to follow the _latest_ shipping process changes, even if the rules for shipping changed while we are in the order acceptance phase. We for example reason that this acceptance phase could sometimes take a long time, because the procurement for goods currently not shelved happens within that phase. 2 Contrary to that, we could decide that the order billing always happens according to the rules valid at the moment we received the order and instantiated the parent process (_deployment_). We for example reason here that it is critical that the billing follows the rules communicated to the customer together with the offer. --- ## Camunda app integrations App integrations bring Camunda tasks, processes, and notifications into the chat tool your team already uses, currently Microsoft Teams and Slack. You can use one platform, the other, or both. :::important App integrations are released as an [early access](/components/early-access/overview.md) alpha feature to allow you to test and participate in development by sharing feedback before general availability, and are subject to alpha feature limitations. ::: ## About One backend, the App Integrations service, serves both platforms: one installation, one database, one notification-rule model, and one CLI. Each platform's app is registered separately, so an organization using only one platform registers only that one. | Platform | Description | | :-------------------------------------- | :------------------------------------------------------------------------------------------ | | [Microsoft Teams](./microsoft-teams.md) | A chatbot and visual tabs for browsing tasks, starting processes, and monitoring incidents. | | [Slack](./slack.md) | A slash command and direct message for browsing tasks and starting processes. | ## What you can do on each platform | Capability | Microsoft Teams | Slack | | :----------------------------------------------- | :------------------------------------------------------------------ | :-------------------------------------------------------------------------------------- | | Browse user tasks | Tasks tab, with filters, sorting, card and list views | `/camunda tasks`, `/camunda tasks my`, 15 per page | | Claim a task | Card action and tab button | **Assign to me** button | | Release a task | Card action and tab button | **Unassign** button | | Complete a task | Card form in chat, or in the tab | Modal, opened from the **Complete** button on the task card in your direct message only | | Assign a task to someone else | Not available | Not available | | Start a process | Chat command and Processes tab | `/camunda start` | | Monitor incidents | Incidents tab, with retry | **Not available** | | Personal notifications | Yes | Yes | | Channel notifications | Yes | Yes | | Create a notification rule | Settings tab | `/camunda subscribe` | | Edit a notification rule | Settings tab | **Not available.** Delete and recreate | | Delete a notification rule | Settings tab | `/camunda subscriptions` | | Switch organization and cluster | Chat command and tab | `/camunda context` | | Wake a suspended cluster | Yes, button in chat and in the tab | **Not available.** You are told to resume it from the Camunda Hub | | Visual app surface | Full tab app: Tasks, Processes, Incidents, Settings, Cluster status | **None.** The Slack Home tab is empty | | Form rendering | Adaptive Cards inline, pop-up dialog for unsupported elements | Block Kit in a modal, JSON fallback, Tasklist link when unrenderable | | File upload tasks | Yes, in the tab | **Not available** | | Conversations for the App Integrations connector | Yes | Yes, feature-equal | | Replies to free text greetings | Yes | **No.** Free text is offered to a process, and otherwise answered with the help card | ## Process variable: `appContext` When a process is started or a user task is completed through Microsoft Teams or Slack, the integration automatically injects an `appContext` variable into the process variables. This allows downstream BPMN processes to know _where_ and _how_ they were triggered. The `appContext` variable has the following shape: | Field | Type | Description | | :--------------- | :------------------------------------ | :------------------------------------------------------------------------- | | `integration` | `"teams"` \| `"slack"` | The platform that initiated the action. | | `externalUserId` | `string` | The platform-specific user ID of the person who triggered the action. | | `email` | `string` | The Camunda account email associated with the user. | | `source` | `"tab"` \| `"message"` \| `"channel"` | The UI surface that triggered the action (see below). | | `channel` | `string` (optional) | The channel or conversation ID. Present only when `source` is `"channel"`. | ### Source values | Value | Meaning | | :---------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `"tab"` | Action was triggered from the Teams tab interface, including forms opened in a pop-up dialog from a bot card. Slack has no tab, so it never produces this value. | | `"message"` | Action was triggered from a bot conversation in a personal or group chat, or from the Slack direct message with the Camunda app. | | `"channel"` | Action was triggered from a channel, either through a bot card posted in the channel, a channel command, or a Slack channel mention. | :::tip You can use `appContext` in your BPMN processes to implement conditional logic based on where an action originated. For example, you could route a process differently depending on whether it was started from a tab or a channel command. ::: ## Get started ### Prerequisites | Prerequisite | Description | | :---------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Camunda 8 SaaS account | You must have a valid working Camunda 8 SaaS account. | | Microsoft Teams, if you are registering the Teams app | Microsoft Teams with permissions to add apps from the Microsoft Store. Microsoft Teams administrators can manage app permissions and availability across the organization. | | Slack, if you are registering the Slack app | A Slack workspace with permission to install apps from the Slack Marketplace. | | Camunda organization and cluster | Access to a Camunda organization and cluster. | | App integrations extensions | For clusters running generation `8.9 gen13` or later, **Enable app integrations extensions** must be turned on in the [cluster settings](/components/saas/clusters/settings.md#enable-app-integrations-extensions) to receive user task notifications. | | Prerequisite | Description | | :---------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Camunda 8 Self-Managed installation | A running and accessible Camunda 8 Self-Managed installation. | | App Integrations backend | The App Integrations backend must be [installed](./installation.md), and the app for each platform you want registered separately by your organization's administrator. | ### Install the app You only need to install the app for the platform, or platforms, you plan to use. Installing Microsoft Teams does not require Slack, and installing Slack does not require Microsoft Teams. #### Microsoft Teams :::info No separate installation is required for SaaS environments. The Camunda app is available in the Microsoft Teams app store for all users with a Camunda SaaS subscription. ::: 1. Open **Microsoft Teams**. 2. Open **Apps** and search for **Camunda**. 3. Click **Add** to install the app. :::note If your organization manages Teams apps centrally, contact your Teams administrator for access. ::: :::info The Self-Managed setup first requires installation and configuration by your organization's administrator. See [Install](./installation.md) for more details. ::: 1. Open **Microsoft Teams**. 2. Open **Apps** and search for **Camunda** in the **Built for your org** section. :::note Your administrator may have renamed the app. Contact them if you cannot find it. ::: 3. Click **Add** to install the app. #### Slack :::info No separate installation is required for SaaS environments. The Camunda app is available in the Slack Marketplace for all users with a Camunda SaaS subscription. ::: 1. Open **Slack**. 2. Find the Camunda app in the Slack Marketplace. 3. Click **Add to Slack** to install the app. :::note Contact your Slack administrator if your workspace restricts app installation. ::: :::info The Self-Managed setup first requires installation and configuration by your organization's administrator, using the CLI. See [Install](./installation.md) for more details. ::: Once your administrator has deployed the app, it appears in your workspace's app directory. Add it from there, or from a direct link your administrator shares with you. ### Sign in and authorize After installing the app for your platform, open Camunda for Microsoft Teams or Camunda for Slack. 1. When prompted, sign in using your **Camunda account credentials**. 2. If requested, grant the necessary permissions to allow the app to access your Camunda workspace. Both platforms use the same sign-in flow, based on your Camunda account. There is no separate "Sign in with Slack". ### Switch organization and cluster Once you are signed in, select the organization and cluster you want to work in to see the relevant tasks, processes, and incidents. - In Microsoft Teams, use the **Switch environment** button at the top of the app to change your active organization and cluster at any time. - In Slack, run `/camunda context`. See [switch organization and cluster](./slack.md#switch-organization-and-cluster). ## Explore further resources :::tip To send messages to Microsoft Teams or Slack from a process, use the [App Integrations connector](/components/connectors/out-of-the-box-connectors/app-integrations.md). The app and the connector complement each other: the app lets users act on Camunda tasks from inside the chat tool they use, while the connector lets a process post into it. ::: --- ## Install app integrations Install and configure the App Integrations backend for a Self-Managed environment using Docker. :::note No installation is needed for SaaS environments. The Camunda app for each platform is already available from the Microsoft Teams app store and the Slack Marketplace, and can be used by all users with a Camunda SaaS subscription. See [get started](./app-integrations.md#get-started) for details. ::: ## About App Integrations is one backend serving Microsoft Teams, Slack, or both. Install it once, then register the app for each platform you want, and skip the rest. An administrator rolling out Slack only can follow this page top to bottom without touching a Microsoft Teams instruction, and the reverse. ## Prerequisites Before you begin, ensure the following are available. Shared, regardless of which platforms you register: - A running Camunda Self-Managed distribution (for example, `camunda.your-domain.com`) with Identity (Keycloak or Microsoft Entra). - Docker installed on the system hosting the App Integrations backend. - A PostgreSQL database accessible from the Docker container. - Node.js 20 or later, for the app integration CLI. - A DNS name for the App Integrations backend (for example, `app-integrations.camunda.your-domain.com`). If you are registering Microsoft Teams: - Microsoft Teams with admin permissions to add apps. If you are registering Slack: - A Slack workspace. - The backend DNS name must be reachable from the internet over **HTTPS**. Slack calls the backend directly, and there is no socket mode. ## Step 1: Create applications in Camunda Identity Before writing the configuration file, register two OAuth 2.0 applications in your identity provider. 1. Access the Identity management in your Camunda Self-Managed distribution. 2. Create the following two applications. ### App Integrations M2M (Machine-to-Machine) | Field | Value | | :------------- | :--------------------------- | | Type | `m2m` | | Access to APIs | Orchestration API (`read:*`) | Note down the generated `clientId` and `clientSecret`. You will need them for `auth.m2m` in the configuration file. ### App Integrations SPA (Single Page Application) | Field | Value | | :------------ | :---------------------------- | | Type | `Confidential` | | Redirect URIs | `https:///*` | Note down the generated `clientId` and `clientSecret`. You will need them for `auth.spa` in the configuration file. ### Grant offline access role In your Keycloak admin console, ensure that users who will use the app integrations have the `offline_access` role assigned. This is required so the application can refresh tokens and act on behalf of users when sending proactive notifications. You can assign `offline_access` at the realm level (**Realm Roles** → `offline_access`) or via a group/client scope, depending on your setup. For Entra-based setups, offline/refresh token access is configured via the App Registration's API permissions in the Azure portal. You must ensure the SPA App Registration has the `offline_access` permission granted under **API permissions** → **Microsoft Graph** → **Delegated permissions**. ## Step 2: Register the chat app Complete only the tabs for the platforms you are registering. Registering Microsoft Teams does not require Slack, and registering Slack does not require Microsoft Teams. Both platforms are set up with `@camunda/app-integration-cli`, version 2, which provides two binaries: `c8-teams` and `c8-slack`. You run only the binary for the platform you are registering. Both share the same commands: `create`, `build`, `deploy`, `show-config`, and `migrate`. :::note The superseded v1 package, `@camunda/teams-app-integration-cli`, still exists as a deprecated alias with the `c8teams` binary. A project created with v1 migrates automatically on first load with the v2 CLI, and `migrate` runs that migration explicitly. ::: Skip this tab if you are not registering Microsoft Teams. Install the CLI: ```bash npm install -g @camunda/app-integration-cli ``` Create a new Teams app project: ```bash c8-teams create my-teams-app ``` The CLI prompts you for: - A project/package name. - The application display name. - Your App Integrations backend URL. - Whether to create a new Teams app and Entra (Azure AD) app or use existing ones. Build and deploy: ```bash cd my-teams-app pnpm install c8-teams build ``` The `build` command compiles the app package from the template and deploys it. You can also deploy separately as follows: ```bash c8-teams deploy ``` This provisions the app in your Microsoft Teams tenant and publishes it to the Teams Admin Portal for approval. After a successful deployment, the CLI automatically prints the `teams` configuration snippet (including `clientId`, `appId`, `appPassword`, `tenantId`, and `tabEndpoint`) ready to paste into your `config.yaml`. Save this output for [Step 4](#step-4-create-the-configuration-file). :::tip If you need to retrieve the configuration snippet again later, run: ```bash c8-teams show-config ``` ::: Skip this tab if you are not registering Slack. Install the CLI: ```bash npm install -g @camunda/app-integration-cli ``` Create a new Slack app project: ```bash c8-slack create my-slack-app ``` The CLI prompts you for: | Question | Default | Constraint | | :---------------- | :-------------------------------------- | :----------------------------------------------------------- | | App name | `Camunda` | 1 to 35 characters | | Bot display name | The app name | 1 to 80 characters | | Short description | `Your perfect integration with Camunda` | 140 characters or fewer | | Background color | `#000000` | `#rgb` or `#rrggbb` | | Slash command | `/camunda` | 32 characters or fewer, no spaces or slashes after the first | | Slack app | Create a new one | Or supply an existing Slack app ID and workspace ID | | Backend URL | None | **Must be `https`.** The CLI rejects `http` | You are not asked for any Slack request URLs. All of them are derived as `/api/slack/events`. Build and deploy: ```bash cd my-slack-app pnpm install c8-slack build c8-slack deploy ``` `c8-slack deploy` publishes the manifest, installs the app, uploads the icon, stores the credentials, and prints the config. Keep these points in mind, since each one otherwise costs an administrator an afternoon: 1. The backend URL must be `https`. The CLI rejects `http` at the prompt. 2. `deploy` needs a Slack app configuration token, resolved in this order: `--token`, then the `SLACK_CONFIG_TOKEN` environment variable, then an interactive sign-in. A non-interactive run with neither fails with "No Slack configuration token available. Set SLACK_CONFIG_TOKEN or pass --token." 3. `show-config` prints secret values only to a terminal. Redirected output withholds them. If the workspace requires admin approval, deployment stops and points at `https://api.slack.com/apps//install-on-team`. :::tip If you need to retrieve the configuration snippet again later, run: ```bash c8-slack show-config ``` ::: ### Slack app permissions The manifest the CLI deploys requests these bot scopes: | Scope | Used for | | :------------------ | :---------------------------------------------------------- | | `app_mentions:read` | Channel mentions reaching a process | | `assistant:write` | Thread status in the Camunda direct message | | `channels:join` | Joining a channel after subscribe or `/camunda chat` | | `channels:manage` | Creating a public channel from the connector | | `channels:read` | Reading channel metadata | | `chat:write` | Posting messages | | `commands` | The slash command | | `groups:read` | Reading private channel metadata | | `groups:write` | Creating a private channel from the connector | | `im:history` | Reading the Camunda direct message | | `im:write` | Opening a direct message to deliver a personal notification | And these bot events: `app_home_opened`, `app_mention`, `app_uninstalled`, `channel_archive`, `channel_deleted`, `channel_left`, `group_left`, and `message.im`. The slash command, event subscriptions, interactivity, and options load all point at the same request URL: `/api/slack/events`. There is no OAuth redirect URL and no Slack OAuth callback in the backend. :::warning Slack does not grant a new permission to an already installed app. If a release adds a scope, an event, or a request URL, every workspace must reinstall the app before the new behavior works. The app does not fail. It silently does less. ::: ## Step 3: Configure the App Integrations exporter The App Integrations backend requires a Zeebe exporter to be configured in your Camunda orchestration cluster. This exporter streams process data to the App Integrations backend. Add the following configuration to your orchestration cluster Helm chart values: ```yaml orchestration: exporters: appIntegrations: apiKey: secret: existingSecret: app-integrations-secret existingSecretKey: apiKey extraConfiguration: - file: application.app-int.yaml content: | zeebe: broker: exporters: appIntegrations: className: "io.camunda.exporter.appint.AppIntegrationsExporter" args: url: /api/event/exporter/batch ``` - Replace `` with the base URL of your App Integrations backend. The URL must point to the `/api/event/exporter/batch` endpoint (for example, `https://app-integrations.camunda.your-domain.com/api/event/exporter/batch`). - The `args.url` must include the full path `/api/event/exporter/batch`. This is the batch event ingestion endpoint of the App Integrations backend. The exporter will POST batches of process events to this endpoint. - The `existingSecret` references a Kubernetes Secret containing the API key. Create this secret in your cluster before deploying: ```bash kubectl create secret generic app-integrations-secret --from-literal=apiKey= ``` :::note The same API key must be set in the `exporter.apiKey` field of the App Integrations [configuration file](#step-4-create-the-configuration-file) so that the backend can authenticate with the exporter endpoint. ::: ## Step 4: Create the configuration file Create a `config.yaml` file with your configuration settings. This file will be mounted into the Docker container. ### Auth configuration The `auth` block must be configured to match the identity provider used in your environment. The `auth.kind` field selects the provider variant, which determines how OIDC discovery, token endpoints, and audience/issuer values are resolved. Audiences are configured under `auth.audiences`. On Self-Managed, set `auth.audiences.zeebe` to the audience of your Orchestration Cluster API; App Integrations also uses it as the identity audience when requesting tokens. `auth.audiences.global` applies to SaaS only and should be omitted. :::warning The singular `auth.audience` field was removed. A configuration that still uses it is rejected at startup with `auth.audience has been removed; use auth.audiences.{global,zeebe,app_integrations}`. ::: Set `auth.kind` to `"keycloak"` for Keycloak-based Camunda Self-Managed installations. | Field | Required | Description | | :-------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------- | | `auth.kind` | Yes | Set to `keycloak`. | | `auth.issuer` | Yes | The Keycloak realm URL. For example, `https:///auth/realms/camunda-platform`. | | `auth.audiences.zeebe` | Yes | The audience of the Orchestration Cluster API. Typically `camunda-platform`. | | `auth.audiences.app_integrations` | No | The audience App Integrations accepts on its own inbound API. Required if the exporter authenticates with OAuth instead of an API key. | | `auth.m2m.clientId` | Yes | M2M client ID from [Step 1](#step-1-create-applications-in-camunda-identity). | | `auth.m2m.clientSecret` | Yes | M2M client secret from [Step 1](#step-1-create-applications-in-camunda-identity). | | `auth.spa.clientId` | Yes | SPA client ID from [Step 1](#step-1-create-applications-in-camunda-identity). | | `auth.spa.clientSecret` | Yes | SPA client secret from [Step 1](#step-1-create-applications-in-camunda-identity). | Example: ```yaml auth: kind: keycloak m2m: clientId: clientSecret: spa: clientId: clientSecret: issuer: https:///auth/realms/camunda-platform audiences: zeebe: camunda-platform ``` Set `auth.kind` to `"entra"` for Microsoft Entra ID (formerly Azure AD) based setups. | Field | Required | Description | | :-------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------- | | `auth.kind` | Yes | Set to `entra`. | | `auth.issuer` | No | Derived automatically as `https://login.microsoftonline.com//v2.0`. Override only if using a custom authority. | | `auth.audiences.zeebe` | Yes | The audience of the Orchestration Cluster API. In Entra setups this is usually the Application ID URI of the M2M app registration. | | `auth.audiences.app_integrations` | No | The audience App Integrations accepts on its own inbound API. Required if the exporter authenticates with OAuth instead of an API key. | | `auth.m2m.clientId` | Yes | Entra App Registration client ID for the M2M application. | | `auth.m2m.clientSecret` | Yes | Entra App Registration client secret for the M2M application. | | `auth.spa.clientId` | Yes | Entra App Registration client ID for the SPA application. | | `auth.spa.clientSecret` | Yes | Entra App Registration client secret for the SPA application. | | `auth.entra.tenantId` | Yes | The Entra tenant ID. | | `auth.entra.tokenUrl` | No | Defaults to `https://login.microsoftonline.com//oauth2/v2.0/token`. | | `auth.entra.jwksUrl` | No | Defaults to `https://login.microsoftonline.com//discovery/v2.0/keys`. | | `auth.entra.scope` | No | Defaults to `openid profile offline_access /.default`. | | `auth.entra.usernameClaim` | No | The ID token claim used to resolve the user's email. Default: `preferred_username`. | Example: ```yaml auth: kind: entra m2m: clientId: clientSecret: spa: clientId: clientSecret: audiences: zeebe: entra: tenantId: ``` :::note Most parameters in the `auth.entra` block are automatically inferred from the required `tenantId` parameter. You only need to override them if your environment uses non-standard Entra endpoints. ::: ### Secret management The configuration file supports referencing environment variables for any value. :::note This is strongly recommended for sensitive fields such as passwords, client secrets, and encryption keys. ::: Instead of hardcoding a secret in `config.yaml`, use the `${{ ENV_VAR_NAME }}` syntax: ```yaml auth: m2m: clientId: ${{ AUTH_M2M_CLIENT_ID }} clientSecret: ${{ AUTH_M2M_CLIENT_SECRET }} db: password: ${{ DB_PASSWORD }} encryptionKey: ${{ DB_ENCRYPTION_KEY }} teams: appPassword: ${{ TEAMS_APP_PASSWORD }} slack: botToken: ${{ SLACK_BOT_TOKEN }} signingSecret: ${{ SLACK_BOT_SIGNING_SECRET }} session: secret: ${{ SESSION_SECRET }} ``` You can also use the shorthand `$ENV_VAR_NAME` without braces. In your deployment, mount the secrets as environment variables on the container (for example, via Kubernetes Secrets, Docker `--env-file`, or your CI/CD pipeline's secret store) rather than storing them in plain text in the config file. ### Example configuration file Replace the placeholder values with your actual settings. `teams` and `slack` are both optional blocks; include only the ones for the platforms you registered in [Step 2](#step-2-register-the-chat-app), and delete the other. - Use the credentials from [Step 1](#step-1-create-applications-in-camunda-identity), the Teams configuration from [Step 2](#step-2-register-the-chat-app), and the Slack `command` value you chose there. - The `exporter.apiKey` must match the API key configured in the Orchestration Cluster Helm chart in [Step 3](#step-3-configure-the-app-integrations-exporter). - For production deployments, replace sensitive values with environment variable references as described in [Secret management](#secret-management). - See [Auth configuration](#auth-configuration) for the full `auth` block reference. ```yaml serverPort: 8080 stage: prod # See "Auth configuration" above for Keycloak and Entra variants. auth: kind: keycloak m2m: clientId: clientSecret: spa: clientId: clientSecret: issuer: https:///auth/realms/camunda-platform audiences: zeebe: camunda-platform db: username: password: database: host: loginType: password encryptionKey: "" # Include this block only if you registered Microsoft Teams in Step 2. # Paste the output of `c8-teams show-config` here: teams: clientId: appId: appPassword: tenantId: tabEndpoint: https:///ms-teams-app # Include this block only if you registered Slack in Step 2. slack: botToken: signingSecret: command: /camunda session: secure: true secret: frontendUrl: https:// backendUrl: https:// flavor: self-managed organisation: name: clusters: - uuid: name: urls: orchestration: https:///orchestration tasklist: https:///tasklist # Extended format (object with base and task): # tasklist: # base: https:///tasklist # task: https:///tasklist/tasks/:userTaskKey/view operate: https:///operate exporter: apiKey: subscriptions: {} ``` :::note The `urls.tasklist` field supports two formats: - **Simple (legacy) format**: a plain URL string (for example, `https:///tasklist`). Deep links to tasks fall back to `{tasklist-url}/tasklist/{userTaskKey}`. - **Extended format**: an object with `base` and `task` fields. The `task` field is a URL template containing a `:userTaskKey` placeholder (for example, `https:///tasklist/tasks/:userTaskKey/view`). When the app generates deep links to tasks (for example, in a notification card), it replaces `:userTaskKey` with the actual task key. This allows customization of the task URL pattern for environments where the default path does not match. ::: :::note On Camunda 8.10, a cluster can host several [Physical Tenants](/self-managed/concepts/physical-tenants/index.md). To serve them from one App Integrations deployment, add a `physicalTenants` array to the cluster. See [App Integrations and Physical Tenants](/self-managed/concepts/physical-tenants/app-integrations.md). ::: ### Stage values The `stage` field controls the environment label. | Value | Description | | :------ | :---------------------------------------------- | | `prod` | Production environment (recommended default). | | `int` | Integration / staging environment. | | `dev` | Development environment. | | `local` | Local development (not for deployed instances). | Use `prod` for production deployment. ## Step 5: Start the application ### Configure your PostgreSQL connection Ensure your `config.yaml` has the correct PostgreSQL database settings. The `host` should be the hostname or IP address of your PostgreSQL server as reachable from within the Docker container. ### Configure your Camunda Self-Managed host Update your `config.yaml` to point to your Camunda Self-Managed distribution: - Set `auth.kind` and the corresponding auth fields for your identity provider (see [Auth configuration](#auth-configuration)). - Set each entry in `clusters[].urls` to the correct Camunda service URLs. - Set `frontendUrl` and `backendUrl` to your public deployment URL. ### Start the backend Run the App Integrations backend container with the configuration file mounted: ```bash docker run -d \ --name app-integrations \ -p 8080:8080 \ -e CONFIG=config/app-integrations.yaml \ -e NODE_ENV=production \ -v ./config.yaml:/app/apps/backend/config/app-integrations.yaml \ --restart unless-stopped \ camunda/app-integrations:SNAPSHOT ``` :::note - **`CONFIG` environment variable**: Must be set to `config/app-integrations.yaml` to point to the mounted configuration file. - **`NODE_ENV` environment variable**: Set to `production` for deployed environments. - **Volume mount**: The `config.yaml` file is mounted to `/app/apps/backend/config/app-integrations.yaml` inside the container. ::: A healthy start logs "Slack integration initialized" if the `slack` block is configured. A blank `slack.botToken` or `slack.signingSecret` disables Slack: every request to `/api/slack/*` then answers `503` with `{"error": "slack_disabled"}`, and the backend logs "Slack integration disabled (no Slack bot token / signing secret configured)". ## Configuration reference Below is a reference of each section in the `config.yaml` file. | Section | Field | Description | | :---------------- | :---------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `serverPort` | — | The port the backend server listens on inside the container (default: `8080`). | | `stage` | — | Environment stage: `local`, `dev`, `int`, or `prod`. | | **auth** | | Authentication configuration for connecting to your Camunda platform. See [Auth configuration](#auth-configuration). | | | `kind` | Identity provider variant: `keycloak` (default) or `entra`. Determines how OIDC endpoints are resolved. | | | `m2m.clientId` / `m2m.clientSecret` | Machine-to-machine OAuth2 credentials for backend services. | | | `spa.clientId` / `spa.clientSecret` | Single Page Application OAuth2 credentials for frontend. | | | `issuer` | The OAuth2/OIDC issuer URL. Required for Keycloak; auto-derived for Entra. | | | `audiences.zeebe` | _(Required)_ The audience of the Orchestration Cluster API. Typically `camunda-platform` on Keycloak. | | | `audiences.app_integrations` | _(Optional)_ The audience App Integrations accepts on its own inbound API, used when the exporter authenticates with OAuth instead of an API key. | | | `entra.tenantId` | _(Entra only, required)_ The Entra tenant ID. | | | `entra.tokenUrl` | _(Entra only, optional)_ Token endpoint. Defaults to `https://login.microsoftonline.com//oauth2/v2.0/token`. | | | `entra.jwksUrl` | _(Entra only, optional)_ JWKS endpoint. Defaults to `https://login.microsoftonline.com//discovery/v2.0/keys`. | | | `entra.scope` | _(Entra only, optional)_ OAuth2 scope. Defaults to `openid profile offline_access /.default`. | | | `entra.usernameClaim` | _(Entra only, optional)_ ID token claim for the user's email. Default: `preferred_username`. | | **db** | | PostgreSQL database connection settings. | | | `username` | Database username. | | | `password` | Database password. | | | `database` | Database name. | | | `host` | Database hostname (as reachable from the container). | | | `loginType` | Authentication type (`password` for username/password auth). | | | `encryptionKey` | 32-character key used for encrypting sensitive data. | | **teams** | | Microsoft Teams integration settings. _(Required only if you registered Microsoft Teams.)_ | | | `clientId` | Azure AD app client ID. | | | `appId` | Teams app ID. | | | `appPassword` | Azure AD app password (client secret). | | | `tenantId` | Azure AD tenant ID. | | | `tabEndpoint` | Public URL endpoint for the Teams tab. | | | `multitenant` | Enable multi-tenant mode (default: `true`). See [note on multitenant](#notes). | | | `serviceUrl` | Bot Framework service URL (default: `https://smba.trafficmanager.net/teams`). See [note on serviceUrl](#notes). | | **slack** | | Slack integration settings. _(Required only if you registered Slack.)_ | | | `botToken` | Slack bot token from [Step 2](#step-2-register-the-chat-app). A blank value disables Slack. | | | `signingSecret` | Slack signing secret from [Step 2](#step-2-register-the-chat-app). A blank value disables Slack. | | | `command` | The slash command, must start with `/`. Defaults to `/camunda`. Must match the slash command configured in the Slack app manifest. | | **session** | | Session management configuration. | | | `secure` | Set to `true` for HTTPS environments. | | | `secret` | A random secret string for signing session cookies. | | **frontendUrl** | — | Public URL where the frontend is accessible. | | **backendUrl** | — | Public URL where the backend is accessible. | | **flavor** | — | Deployment flavor (must be `self-managed`). | | **organisation** | `name` | Display name for your organization. | | **clusters** | — | Array of Camunda cluster configurations. | | | `uuid` | Unique identifier for the cluster. | | | `name` | Display name for the cluster. | | | `urls.orchestration` | Zeebe/Orchestration API URL. | | | `urls.tasklist` | Tasklist URL (string) or object with `base` and `task`. See note below. | | | `urls.tasklist.base` | _(Object format only)_ Root Tasklist URL. | | | `urls.tasklist.task` | _(Object format only)_ URL template for deep-linking to a specific task. Must contain `:userTaskKey` placeholder. | | | `urls.operate` | Operate URL. | | | `exporter.apiKey` | API key for the exporter. Must match the key configured in the [orchestration cluster Helm chart](#step-3-configure-the-app-integrations-exporter). | | | `connector.apiKey` | _(Optional)_ Separate API key for the App Integrations connector endpoints. Rotates independently of `exporter.apiKey`. | | | `auth.audiences.zeebe` | _(Optional)_ Overrides the deployment-wide `auth.audiences.zeebe` for this cluster. | | | `physicalTenants` | _(Optional)_ Physical Tenants hosted on this cluster. See [App Integrations and Physical Tenants](/self-managed/concepts/physical-tenants/app-integrations.md). | | | `exposeDefaultTenant` | _(Optional)_ Offer the `default` tenant alongside the configured Physical Tenants (default: `false`). No effect unless `physicalTenants` is set. | | **subscriptions** | — | Subscription configuration (empty object `{}` for Self-Managed). | ### Notes - **`teams.multitenant`:** Multi-tenant support is no longer available for newly created Azure Bot registrations. Only set `multitenant` to `true` if you have an existing Teams application that was already registered with multi-tenant support enabled. For all new installations, leave this at the default (the CLI creates single-tenant apps) or explicitly set it to `false`. - **`teams.serviceUrl`:** The default value (`https://smba.trafficmanager.net/teams`) works for most deployments. Only override it if your environment requires a different Bot Framework service endpoint. If you are unsure, keep the default value. --- ## Camunda for Microsoft Teams Use the Camunda bot in personal chats, group chats, and channels to run commands or interact through buttons on interactive cards. Use the visual tabs to browse tasks, start processes, and monitor incidents. ## Chatbot Use the Camunda bot in personal chats, group chats, and channels to run commands or interact through buttons on interactive cards. ### Commands You can interact with the bot by typing commands in chat. | Command | What it does | | :------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------- | | **Hi** (or hey, hello, greetings, and similar messages) | The bot greets you. If you have not connected your Camunda account yet, it shows you how to get started. | | **Help** (or ?, support, commands) | Shows a help card with available commands and setup information. | | **Switch context** | Changes your active organization and cluster. | | **Start a process** | Walks you through selecting and starting a Camunda process. If no cluster is selected, it prompts you to choose one. | :::important If you type an unrecognized command, the bot notifies you and suggests using **Help**. ::: Configure notification subscriptions on the [notification rules](./notification-rules.md) page. ### Interactive cards In addition to commands, the bot sends interactive cards with buttons you can use to perform actions directly in chat. :::note When you trigger actions through the bot, such as starting processes or completing tasks, it automatically adds an [`appContext`](./app-integrations.md#process-variable-appcontext) variable. This variable captures who triggered the action and from which surface (`"message"` or `"channel"`). ::: #### Select an organization and cluster Select your organization from a dropdown, then choose a cluster. The bot remembers your selection for future interactions. #### Start processes Select a process definition and complete the start form or provide variables. The bot confirms when the process starts successfully or shows an error. #### Work with tasks | Action | Description | | :---------------- | :------------------------------------------------------------------------------------------------------------------- | | **Assign to me** | Claim a task. If you do this from a channel, the bot also sends you a personal copy so you can work on it privately. | | **Unassign** | Release a task so others can pick it up. | | **Fill in form** | Open the task completion form directly in the chat card. | | **Complete task** | Submit the form and mark the task as done. | | **Reset form** | Discard your form input and return to the task overview. | #### Manage clusters If your cluster is sleeping, the bot shows a **Wake up cluster** button. ### Use pop-up dialogs The bot opens forms in a pop-up dialog when they: - Contain unsupported element types - Use FEEL expressions (values starting with `=`) in supported properties #### Unsupported element types Adaptive Cards support only a subset of Camunda form elements. Forms that include any of the following elements open in a pop-up dialog: - Group - Table - Dynamic list - iframe - HTML viewer - Button - Expression field - Document preview #### FEEL expressions Forms also open in a pop-up dialog if any field uses a FEEL expression in one of the following properties: - **Label** - **Date label** - **Read only** - **Default value** - **Text** - **Source** - **Alt text** - **Accept** - **Multiple** :::note Static values and process variables are supported in Adaptive Cards. Dynamic FEEL expressions are not. ::: ### Notifications The bot sends notifications based on your [notification rules](./notification-rules.md). #### Personal notifications Receive a message in your personal chat when a user task matches one of your notification rules. For example, when a task is assigned to you. #### Channel notifications A channel receives notifications for user tasks that match its configured rules. This allows your team to coordinate who picks them up. To configure notifications, see [notification rules](./notification-rules.md). #### Notification behavior Notification cards are interactive. You can assign, complete, or manage tasks directly from the card. Cards update automatically to reflect the latest task state. For example, when someone else completes or assigns the task. On SaaS, automatic updates require a cluster running generation `8.9 gen13` or later with [app integrations extensions enabled](./notification-rules.md#enable-notification-delivery-for-your-cluster). ### Error handling The bot provides clear feedback when something goes wrong: | Error | Behavior | | :---------------------- | :------------------------------------------------------------------- | | **Cluster is sleeping** | Shows a card with a button to wake it up, then retry your action. | | **Task not found** | Indicates the task no longer exists. | | **Access denied** | Informs you that you don't have permission for the requested action. | | **Unexpected errors** | Shows a message suggesting you retry or contact support. | ## Tabs Camunda for Microsoft Teams adds visual tabs to your Teams sidebar, providing a rich interface for interacting with your Camunda clusters. Each tab focuses on a specific area of process management. ### Tasks With the **Tasks** tab, you can browse and manage user tasks from your Camunda clusters. - **Filter tasks** by category: My tasks, All open tasks, Unclaimed tasks, or Completed tasks. - **Sort and search** tasks using additional filter controls (assignee, candidate group, process, dates, priority). - **Switch between views**: Card view or list view. - **View task details** including the process it belongs to, priority, due date, and creation time. - **Assign a task** to yourself and **complete it**, either by filling in a Camunda form or by providing data manually. - **Navigate pages** in large task lists or load more items on mobile. :::note Tasks update automatically as you complete work in Camunda or Microsoft Teams. ::: :::note When a task is completed from the tab, an [`appContext`](./app-integrations.md#process-variable-appcontext) variable is automatically included in the task variables with `source` set to `"tab"`. ::: ### Processes With the **Processes** tab, you can view and start Camunda process definitions. - Browse all available process definitions displayed as cards. - Select a process and choose a specific version. - Fill in a start form (if the process has one) or provide variables manually, then start a new process instance. - After starting, get a direct link to view the running instance in [Operate](/components/operate/operate-introduction.md). To start a process: 1. Select the **Processes** tab. 2. Select a process definition. 3. Complete any required fields. 4. Click **Start process**. :::note When a process is started from the tab, an [`appContext`](./app-integrations.md#process-variable-appcontext) variable is automatically included in the process variables with `source` set to `"tab"`. ::: ### Incidents With the **Incidents** tab, you can monitor incidents that occurred during process execution. - See a list of active incidents with error type, description, and timestamp. - Retry a failed incident directly from Teams. - Copy a link to the incident or open it in [Operate](/components/operate/operate-introduction.md) for deeper investigation. ### Onboarding When you first open the app, the **Onboarding** page guides you through connecting your Teams account to your Camunda account via single sign-on. If you don't have a Camunda account yet, you can sign up from this page. ### Cluster status The **Cluster Status** page displays the health of your currently selected cluster. If the cluster is suspended (sleeping), you can wake it up directly from this page. --- ## Notification rules Notification rules let you control which user tasks trigger notifications in Microsoft Teams or Slack. The rule model is identical on both platforms; what differs is where you configure a rule and what you can do to it afterwards. Each rule applies to a specific organization and cluster and can filter user task events by process definition, user task elements, candidate users, or candidate groups. ## Enable notification delivery for your cluster On Camunda 8 SaaS, the notifications a cluster delivers depend on its generation. Clusters running generation `8.9 gen13` or later require the **Enable app integrations extensions** setting in the [cluster settings](/components/saas/clusters/settings.md#enable-app-integrations-extensions). Until an organization admin turns it on, you can create and save rules, but the cluster delivers no notifications. Clusters running earlier generations need no configuration. They deliver a notification when a matching user task is created. | Notification | Earlier generations | `8.9 gen13` or later, with app integrations extensions enabled | | :--------------------------------------------------------------- | :------------------ | :------------------------------------------------------------- | | A matching user task is created | Yes | Yes | | A card updates when the task is assigned, completed, or canceled | No | Yes | | An existing task is later assigned to you | No | Yes | Clusters running generation `8.9 gen13` or later also receive new notification capabilities as they become available. ## Channel vs. personal rules Where you create a rule determines who receives notifications. Slack calls the personal case a direct message. | Context | Who receives notifications | Typical use | | :------------------------------ | :-------------------------------------- | :------------------------------------------------------------------------------------------- | | Channel | Everyone in the channel | Coordinate on new unassigned user tasks so a team can decide who picks them up. | | Personal chat or direct message | The user who owns the chat with the app | Get notified when a task is assigned to you, or when a task you can claim becomes available. | :::note On Microsoft Teams, to configure personal notification rules, you must first start a chat with the Camunda bot. Until that chat exists, the **Settings** tab shows a "Personal notifications not set up" message. ::: :::note Slack group direct messages are refused outright: "Group DMs aren't supported. Run `/camunda subscribe` in a channel, or in your direct message with Camunda." ::: ## Access the notification rules page You manage notification rules from the Notification rules page in the Camunda for Microsoft Teams app. **In a personal chat:** 1. Open the Camunda app in Microsoft Teams. 2. Select the **Settings** tab. **In a channel:** 1. Open the channel where you want to receive notifications. 2. Add the **Camunda** tab (**+** at the top of the channel > select **Camunda**). 3. Open the tab. Run `/camunda subscribe` in a channel or in your direct message with Camunda to create a rule. The destination is the conversation you ran it in. Run `/camunda subscriptions` to list this conversation's notification rules, with a **Delete** button per row. **There is no edit on Slack.** To change a rule, delete it and create a new one with `/camunda subscribe`. ## Rule structure A rule triggers a notification only when a user task matches all configured filters. | Field | Required | Description | | :----------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Organization | Yes | The Camunda organization the rule applies to. Auto-selected if you only have access to one. | | Cluster | Yes | The cluster within the organization. Auto-selected if only one cluster is available. On a Self-Managed deployment with [Physical Tenants](/self-managed/concepts/physical-tenants/app-integrations.md), each cluster and tenant pair is listed as its own entry, and the rule is bound to the pair you pick. | | Process definition | No | Limit the rule to user tasks from a single process. Leave empty (**All processes (no filter)**) to match user tasks from every process in the cluster. On Slack, this is a typeahead field. | | User tasks | No | One or more specific user task elements within the selected process. Pick them visually on the BPMN diagram on Microsoft Teams. Only available after you select a process. Leave empty to match all user tasks. On Slack, this is a multi-select, capped at 100 options. | | Candidate users | No | Comma-separated list of user identifiers. Matches tasks assigned to any of these users. | | Candidate groups | No | Comma-separated list of group identifiers. Matches tasks assigned to any of these groups. | ### Match semantics - Empty filters match all user tasks in the selected cluster. A rule with no filters matches every user task in the selected cluster: the broadest possible subscription. - Adding filters narrows the match. Filters combine with AND across fields and OR within each list. For example, a rule with `candidateGroups = "finance, hr"` and a selected process matches tasks from that process that have either `finance` or `hr` as a candidate group. - Multiple matching rules deduplicate. If several rules match the same user task event, the recipient still receives only one notification per event. - The cluster and Physical Tenant are matched exactly and cannot be left empty. A rule created for one Physical Tenant never receives another tenant's user tasks, so a cluster split into Physical Tenants needs its rules recreated per tenant. :::tip Start broad and narrow down. If you are not sure which filters you need, create a rule with no filters first, observe the notifications you receive, and then refine. ::: ## Create a rule The steps below describe the Microsoft Teams flow. On Slack, create a rule with `/camunda subscribe`; see [Access the notification rules page](#access-the-notification-rules-page). 1. Open the [notification rules page](#access-the-notification-rules-page). 2. Click **Create rule** in the page header. 3. Select an **organization** and **cluster** if they are not pre-selected. 4. (Optional) Choose a **process definition**. To target specific tasks, select them on the BPMN diagram. 5. (Optional) Provide candidate users or candidate groups as comma-separated lists. 6. Click **Save**. The new rule appears in the list and starts matching new user task events immediately. ## View existing rules The steps below describe the Microsoft Teams flow. On Slack, list rules with `/camunda subscriptions`; see [Access the notification rules page](#access-the-notification-rules-page). The Notification rules page lists all rules configured for the current channel or personal chat. Each row shows: | Column | Description | | :------------ | :------------------------------------------------------------------------------------------------------------------------------------ | | Org / Cluster | The organization and cluster the rule applies to. Hidden when only one organization and cluster is available. | | Process | The process definition ID, or all processes when no process filter is set. | | User task | The selected user task element IDs, or **—** when no specific task is selected. | | Assignments | The combined list of candidate users and groups. Long lists are truncated with **+N more**. Hover over the cell to see the full list. | Click a row to open the rule for editing. ## Edit a rule :::note Editing is available on Microsoft Teams only. Slack has no edit; delete the rule and create a new one with `/camunda subscribe`. ::: 1. On the Notification rules page, click the row of the rule you want to edit. 2. Change any of the fields. 3. Click **Save**. ## Delete a rule The steps below describe the Microsoft Teams flow. On Slack, delete a rule from the **Delete** button in `/camunda subscriptions`. 1. Open the rule for editing (click its row). 2. Click **Delete rule** in the page header. 3. Confirm the deletion in the dialog. This action cannot be undone. ## What happens when a task matches When a user task matches a rule, the app posts an interactive notification card to the channel, personal chat, or direct message. On Microsoft Teams, you can claim, assign, complete, or open the task without leaving the card. On Slack, the card links to Tasklist, and completion is available from the task card in your direct message. Cards update automatically as the task state changes. See [notification behavior](./microsoft-teams.md#notification-behavior) for the full description of how Microsoft Teams notification cards behave. ## Delivery requirements On Slack, a channel rule needs the Camunda app to be a member of the channel. The app tries to join the channel when you submit the rule, and warns you when it cannot: "Invite the Camunda app to this channel (`/invite @Camunda`) so notifications can be delivered." A direct message rule is always deliverable. ## Automatic cleanup On Slack, rules for a channel are deleted automatically when any of the following happens: - The Camunda app leaves the channel. - The channel is deleted or archived. - The Camunda app is uninstalled from the workspace. ## Limits and permissions - There is currently no limit on the number of rules per channel, personal chat, or cluster on Microsoft Teams. On Slack, up to 20 rules per conversation are listed by `/camunda subscriptions`. - In a channel, any member of the channel can create, edit, and delete notification rules. --- ## Camunda for Slack Slack has no tab app. Everything happens through the `/camunda` slash command, the direct message with the Camunda app, and channel mentions. If you are coming from the Microsoft Teams page looking for tabs, there are none on Slack. ## Slash commands One command, default `/camunda`, configurable per Self-Managed deployment. Routing is on the first word of the argument. | Invocation | What it does | | :---------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- | | `/camunda tasks` | Open user tasks in the active organization and cluster. 15 per page, **Previous** and **Next**, per-row **Open in Tasklist**. | | `/camunda tasks my` | The same list, filtered to your tasks. | | `/camunda start` | Opens the start-process modal: organization, cluster, process definition, then the start form. | | `/camunda context` | Shows or switches the active organization and cluster. With one option available it is selected and confirmed with no modal. | | `/camunda chat` | Opens the chat-channel modal, which configures the App Integrations connector chat key for this channel or direct message. | | `/camunda subscribe` | Opens the create-notification-rule modal. The destination is the conversation you ran it in. | | `/camunda subscriptions` | Lists this conversation's notification rules with a **Delete** button per row. Capped at 20 rows. | | `/camunda`, `/camunda help`, or any unrecognized word | The help card. There is no "unknown command" error on Slack. | `/camunda` with an unrecognized word shows the help card rather than an error. :::note The command name is configurable on Self-Managed and defaults to `/camunda`. If your workspace uses a different name, replace `/camunda` with it in every invocation above. ::: ## Where answers appear In the Camunda direct message, an answer appears as an ordinary message. Anywhere else, the answer is ephemeral and only you see it. ## Work with tasks Run `/camunda tasks` to open your organization's user tasks, or `/camunda tasks my` to filter to tasks assigned to you. Both list 15 tasks per page, with **Previous** and **Next** buttons and a per-row **Open in Tasklist** link. Each task offers these buttons: | Button | Description | | :--------------- | :------------------------------------------------------------------------------------------------ | | **Assign to me** | Claim the task. | | **Unassign** | Release the task so others can pick it up. | | **Complete** | Complete the task. Available only on the task card in your direct message, not on a channel card. | ## Start a process Run `/camunda start` to open a modal that walks you through the organization, the cluster, and the process definition, and then shows the start form. ## Forms in Slack A Camunda form completed from Slack renders in one of three ways, decided by the form itself: | Outcome | When | What you see | | :---------- | :---------------------------------------------------------------------------------------- | :----------------------------------------------------------------- | | Interactive | The form renders in Block Kit | A modal with real inputs | | JSON | The form has no schema | A single input box for a JSON payload | | Unsupported | The form uses FEEL expressions, contains a file picker, or is too large for a Slack modal | "This form can't be filled in Slack. Open it in Tasklist instead." | ## Switch organization and cluster Run `/camunda context` to see or switch your active organization and cluster. With exactly one option available, it is selected and confirmed with no modal. ## Notifications Notification rules work the same way on Slack as on Microsoft Teams. See [notification rules](./notification-rules.md) for how to create, list, and delete them. An assignee with a linked Slack account receives a direct message when a task is assigned to them, independently of any notification rule. ## What Slack does not have Compared to Microsoft Teams, Slack does not support: - Incident monitoring - A Home tab - Waking a suspended cluster. You are told to resume it from the Camunda Hub instead - Editing an existing notification rule. Delete it and create a new one instead - File upload tasks - Completing a task from a channel card. Completion is only available on the task card in your direct message - Conversational replies to free text. Free text is offered to a process instead, and otherwise answered with the help card ## Limits | Limit | Value | Cause | | :---------------------------- | :------------------ | :-------------------------------------------------------- | | Tasks per page | 15 | Slack's 50-block message ceiling | | Notification rules listed | 20 | Same ceiling | | Process options per keystroke | 25 | Slack's options response budget | | User task options in a rule | 100 | Slack's multi-select ceiling | | Modal open time | About three seconds | Slack's `trigger_id` expiry, handled with a loading modal | A modal left open across a newer interaction is rejected with "This dialog is no longer valid. Run the command again." --- ## Troubleshoot app integrations Troubleshoot Camunda app integrations to fix common setup and connectivity issues on Microsoft Teams and Slack. ### The app does not appear in the Microsoft Teams store - Verify your organization allows third-party app installations. - Search for "Camunda" in the Microsoft Teams app store. - Check with your Teams administrator for app approval policies. - The app is provisioned during [installation](./installation.md) and appears in the **Built for your org** section of Microsoft Teams Apps. - If the app is not visible, check with your IT administrator. ### Unable to connect to Camunda organization - Ensure you have the required permissions in your Camunda organization. - Verify your Camunda SaaS account is active and accessible. - If no tasks or incidents are visible, double-check your Camunda organization, cluster, and tenant settings. - Verify your Camunda Self-Managed distribution is running and accessible. - Check your Identity configuration and ensure the user has the required roles. - If no tasks or incidents are visible, double-check your cluster configuration in the `config.yaml` file. ### Tasks not displayed - Check you are connected to the correct Camunda cluster. - If notifications are not shown, ensure notifications are enabled for your platform and verify that the relevant [notification rules](./notification-rules.md) are configured for the channel, personal chat, or direct message. - This could be due to an expired Camunda session or missing permissions. Sign out and sign in again. ### Notifications are not delivered - On SaaS clusters running generation `8.9 gen13` or later, check that **Enable app integrations extensions** is turned on in the [cluster settings](/components/saas/clusters/settings.md#enable-app-integrations-extensions). Microsoft Teams shows an **App Integrations Extensions not enabled** message when the setting is off. - Verify that a [notification rule](./notification-rules.md) matches the user task, and that the rule is configured for the channel, personal chat, or direct message you expect. - If notifications arrive but cards do not update when a task is assigned, completed, or canceled, check that the cluster runs generation `8.9 gen13` or later. ### The Slack app does not respond | Symptom | Cause | Fix | | :----------------------------------------------- | :------------------------------------------------- | :------------------------------------------------------------ | | `/api/slack/events` returns `503 slack_disabled` | `slack.botToken` or `slack.signingSecret` is blank | Set `SLACK_BOT_TOKEN` and `SLACK_BOT_SIGNING_SECRET`, restart | | The slash command does nothing | `slack.command` does not match the manifest | Align the two | | Slack cannot reach the backend | The backend is on `http` | Slack calls `https` only, and there is no socket mode | ### Channel mentions are ignored but direct messages work | Symptom | Cause | Fix | | :------------------------------------------------- | :-------------------------------------------------------------- | :---------------------- | | Channel mentions are ignored, direct messages work | `app_mentions:read` was added to the app after it was installed | Reinstall the Slack app | ### Notifications are not delivered to a Slack channel | Symptom | Cause | Fix | | :--------------------------------------------- | :--------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ | | A channel rule creates no notifications | The Camunda app is not a member of the channel | Invite the app with `/invite @Camunda` | | A previously working channel rule stops firing | The rule was cleaned up automatically | See [automatic cleanup](./notification-rules.md#automatic-cleanup): the app left the channel, the channel was deleted or archived, or the app was uninstalled | ### A modal fails to open or a dropdown returns nothing | Symptom | Cause | Fix | | :------------------------- | :----------------------------------------------------------- | :------------------- | | A modal fails to open | The interactivity request URL is missing from the app config | Reapply the manifest | | A dropdown returns nothing | The options load request URL is missing from the app config | Reapply the manifest | Both the interactivity and options load requests point at the same request URL as the slash command: `/api/slack/events`. ### Other Slack messages worth searching for If you were shown one of these messages, look up the row for what it means and what to do: | Message | Meaning | | :---------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | "No Camunda context selected. Run `/camunda context` to choose an organisation and cluster." | No organization and cluster is active yet. Run `/camunda context`. | | "Couldn't tell which tasks are yours. Your Camunda account isn't a member of this organisation." | Your Camunda account has no access to the active organization. Ask an administrator for access. | | "Start with a letter or a number, then use letters, numbers, dots, dashes or underscores. 64 characters at most." | The chat key you entered in `/camunda chat` or a [chat conversation start event](/components/connectors/out-of-the-box-connectors/app-integrations.md#route-a-chat-to-the-right-process) is not in a valid format. | | "`default` is the key an unconfigured channel already uses. Choose 'Default process' above, or pick another key." | You tried to set the chat key to `default` explicitly. Select **Default process** instead, or choose a different key. | ## Get help - Contact [Camunda support](/reference/contact.md) for assistance. - Provide feedback through the [Camunda roadmap portal](https://roadmap.camunda.com). --- ## Microsoft Teams Camunda for Microsoft Teams is part of [Camunda app integrations](./app-integrations/app-integrations.md), which connect Camunda to the chat tool your team uses. You can run it without Slack. :::important App integrations are released as an [early access](/components/early-access/overview.md) alpha feature to allow you to test and participate in development by sharing feedback before general availability, and are subject to alpha feature limitations. ::: Find the documentation you're looking for: - [Camunda for Microsoft Teams](./app-integrations/microsoft-teams.md), the chatbot and tabs. - [Notification rules](./app-integrations/notification-rules.md). - [Install](./app-integrations/installation.md). - [Troubleshoot](./app-integrations/troubleshoot.md). This page is not the [Microsoft Teams connector](/components/connectors/out-of-the-box-connectors/microsoft-teams.md), which is a separate product that uses your own app registration and credentials in the process model. If your organization uses app integrations, the [App Integrations connector](/components/connectors/out-of-the-box-connectors/app-integrations.md) sends through the app your users already have connected. --- ## Camunda integrations Extend the power of your process orchestration by connecting Camunda with your core enterprise systems. Our pre-built integrations simplify communication across platforms and enable true end-to-end automation in your technology landscape. ## App integrations App integrations bring Camunda process management into the chat tool your team uses, currently Microsoft Teams and Slack. You can use one platform, the other, or both. With app integrations, you can: - **Manage tasks and processes** View, claim, assign, and complete user tasks, and start new process instances, directly from Microsoft Teams or Slack. - **Get notified** Receive personal and channel notifications for user tasks based on configurable notification rules. :::note App integrations are released as an [early access](/components/early-access/overview.md) alpha feature. ::: ## SAP integration Camunda’s SAP integration allows you to include SAP S/4HANA, ECC, Advanced Event Mesh (AEM) and Business Technology Platform (BTP) functionality in your orchestrated processes. With this integration, you can: - **Execute core SAP functions** Call BAPIs and Remote Function Modules (RFCs) in SAP S/4HANA or ECC directly from Camunda. - **Extend event driven architecture** Receive CloudEvents from SAP Advanced Event Mesh (AEM) and publish CloudEvents to AEM. - **Connect to SAP BTP** Integrate with services and applications on the SAP Business Technology Platform to build comprehensive, cross-platform workflows. :::note This integration is ideal for organizations standardizing on SAP systems while extending automation to non-SAP applications and services. ::: ## ServiceNow integration Camunda’s ServiceNow integration bridges your business processes with IT service management (ITSM) to streamline and automate service delivery. With this integration, you can: - **Manage ServiceNow data** Create, read, update, and delete records in any ServiceNow table directly from a Camunda process. - **Trigger ServiceNow flows** Initiate automations built in ServiceNow's Flow Designer as part of an end-to-end process. :::note Use this integration when you need to orchestrate processes that involve ITSM tasks or require seamless collaboration between ServiceNow and other business systems. ::: --- ## SAP integration Use Camunda's [SAP](/reference/glossary.md#sap) integration to bring SAP functionality into your orchestrated processes and connect with SAP BTP services. This integration provides modular components that can be used independently or together to automate business-critical scenarios across SAP and non-SAP systems. :::note The SAP BTP plugin is retired as of Camunda 8.10. There are no changes to the other modules of the SAP integration. For details, see the [Camunda 8.10 announcements](/reference/announcements-release-notes/8100/8100-announcements.md). ::: ## Purpose The SAP integration enables you to: - Extend end-to-end process orchestration into SAP S/4HANA and ECC systems. - Execute SAP-specific functions (such as BAPIs, RFCs, and OData calls) directly within BPMN workflows. - Call SAP functions as tools from an [AI Agent Sub-process](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess.md), letting an agent autonomously trigger BAPIs, RFCs, or OData operations as part of its tool-calling loop. - Surface Camunda Tasklist items directly in SAP Fiori through **SAP Task Center integration**, improving visibility and collaboration for business users. - Enable event-driven automation with **SAP Advanced Event Mesh** (upcoming), allowing real-time responsiveness and better integration with SAP event streams. - Provide IT and business teams with a certified, enterprise-grade solution for SAP automation. :::info The Camunda SAP integration is **SAP Certified**, ensuring compliance with SAP's standards for compatibility, security, and performance. This certification provides added assurance for enterprise deployments. ::: ## Audience This documentation is intended for: - **Developers** who implement process models that call SAP functions, services, or APIs. - **Solution architects** who design system landscapes and need to understand integration options. - **Administrators** who deploy and configure SAP integration modules in SAP BTP or hybrid environments. ## Scope of documentation This section of the documentation covers: - [Prerequisites](./prerequisites.md) for running the integration. - **Integration modules**: What each SAP module does and how they fit into Camunda workflows. - **Setup guidance**: How to configure and deploy integration modules using the [CSAP c8ctl plugin](./csap-cli.md). - **Module-specific documentation**: - [SAP OData outbound connector](./odata-connector.md) - [SAP RFC outbound connector](./rfc-connector.md) ## About the integration Camunda's SAP integration consists of several modules that can be used independently: | Module | What it does | | :--------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------- | | [SAP OData outbound connector](./odata-connector.md) | Interact with an SAP S/4HANA or ECC system via OData v2 + v4 APIs directly from your BPMN model. | | [SAP RFC outbound connector](./rfc-connector.md) | Query Business Application Programming Interfaces (BAPIs) and remote-enabled function modules (RFCs) on SAP ECC systems. | These modules run within your [SAP BTP instance](https://www.sap.com/products/technology-platform.html) or alongside your Camunda deployment, requiring no proprietary Camunda setup and leveraging existing infrastructure with minimal prerequisites. ![SAP integration overview](./img/sap-integration-overview.svg) --- ## CSAP c8ctl plugin The [Camunda SAP Integration CLI](/reference/glossary.md#csap-cli) (CSAP) is a plugin for [c8ctl](/apis-tools/c8ctl/getting-started.md) that simplifies the setup of Camunda's SAP integration modules. It provides a streamlined process for configuring and building these modules for deployment. :::warning The standalone `csap` binary is deprecated and replaced by the `c8ctl-plugin-csap-cli` plugin. The plugin offers the same functionality. It is a Node.js port of the original tool, so you no longer need Deno. See [migrate from the `csap` binary](#migrate-from-the-csap-binary). ::: ## Features - Runs as a c8ctl plugin - no separate binary required. - Interactive prompts for configuration. - Command-line flags for automation. - Support for multiple SAP integration modules. - Automatic handling of dependencies and build processes. - Compatibility with Camunda SaaS deployments. ## Supported modules The plugin supports the following SAP integration modules: | Module | Flag value | Description | | ------------------- | ---------- | -------------------------------------------------------------------------- | | SAP OData connector | `odata` | Facilitates interaction with SAP S/4HANA or ECC systems from a BPMN model. | | SAP RFC connector | `rfc` | Allows querying BAPIs and Remote Function Modules on SAP ECC systems. | | All modules | `all` | Configures all available modules. | The plugin supports Camunda 8.7, 8.8, and 8.9. Support for Camunda 8.6 is deprecated. The plugin downloads the OData and RFC connectors from the [sap-connectors](https://github.com/camunda/sap-connectors) repository. The plugin source code is available in the [c8ctl-plugin-csap-cli](https://github.com/camunda/c8ctl-plugin-csap-cli) repository. ## Prerequisites Check your build system meets the following requirements: - [c8ctl](/apis-tools/c8ctl/getting-started.md#install) is installed. - [Node.js](https://nodejs.org/en) 22 or later. - (Windows) `npm` uses `cmd` as the shell script executor: `npm config set script-shell cmd`. ## Installation Load the plugin into c8ctl from its Git repository: ```bash c8ctl load plugin --from https://github.com/camunda/c8ctl-plugin-csap-cli ``` To pin a specific branch or tag, append `#` and the name: ```bash c8ctl load plugin --from https://github.com/camunda/c8ctl-plugin-csap-cli#v1.2.3 ``` To verify the installation, run `c8ctl help`. The `csap-setup` command appears under **Plugin Commands**. To manage the plugin afterward, see [extend c8ctl with plugins](/apis-tools/c8ctl/plugins.md#manage-plugins). ## Usage The plugin provides a `csap-setup` command to prepare one of Camunda's SAP integration modules for deployment. You can run the command interactively or provide all required options as command-line flags. ### Authentication token Under the hood, the plugin uses the GitHub API to query for releases. The [GitHub API has a rate limit](https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api) for unauthenticated requests. It is thus advisable to provide a GitHub access token to the environment you run the plugin in. #### Local use A personal GitHub access token can be obtained in multiple ways. Either [statically by generating one](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token) or dynamically by using the `gh` CLI to log in (`gh auth login`), which in turn produces an access token. Then, inject the token into your (shell) environment as the variable `GH_TOKEN`. The plugin automatically picks up the token from `GH_TOKEN` and uses it for subsequent requests. Windows (Command Prompt) ```shell for /f "delims=" %i in ('gh auth token') do set GH_TOKEN=%i ``` Windows (PowerShell) ```shell $env:GH_TOKEN = (gh auth token) ``` Linux/macOS (bash) ```shell export GH_TOKEN=$(gh auth token) ``` #### CI/CD use If your CI/CD environment isn't GitHub, you must obtain a GitHub access token, as with [local use](#local-use), to authenticate the plugin's requests to the GitHub API. For GitHub Actions, all runs are provided a `GITHUB_TOKEN` automatically. Declare it for the respective run of `c8ctl csap-setup` with the `env` YAML declaration: ```yaml jobs: your-job: # ... steps: - run: | c8ctl csap-setup --for #... env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ``` ### Interactive mode Run the following command to start the interactive setup: ```bash c8ctl csap-setup ``` The plugin guides you through prompts to collect all required inputs, including the SAP integration module, Camunda version, deployment method, and credentials. ### Command-line options All prompts are also available as command-line flags, allowing you to automate the setup process. #### Command syntax ```bash c8ctl csap-setup [options] ``` #### Options | Option | Type | Description | Default value | | ---------------- | ------ | -------------------------------------------------------------------------------------------------------- | ---------------------------- | | `--for` | string | Specifies the SAP integration module to set up. Choices: `odata`, `rfc`, `all`. | (Prompted if not provided) | | `--camunda` | string | Specifies the Camunda version. Choices: `8.10`, `8.9`, `8.8`, `8.7`, `8.6` (deprecated). | (Prompted if not provided) | | `--deployment` | string | Specifies the Camunda deployment option. Choices: `SaaS`. (`SM` for self-managed is currently disabled.) | (Prompted if not provided) | | `--clusterId` | string | Specifies the Camunda cluster ID. | (Prompted if not provided) | | `--region` | string | Specifies the Camunda cluster region, for example, `bru-2`. | (Prompted if not provided) | | `--clientId` | string | Specifies the Camunda API client OAuth2 client ID. | (Prompted if not provided) | | `--clientSecret` | string | Specifies the Camunda API client OAuth2 client secret. | (Prompted if not provided) | | `--to` | string | Target directory for setup artifacts. | OS-dependent `tmp` directory | ## Environment variables The plugin can detect Camunda API credentials from environment variables. If these variables are set, the plugin reuses them without prompting for input. Flags take precedence over environment variables. | Environment variable | Description | | ------------------------ | ------------------------- | | `CAMUNDA_CLUSTER_ID` | Camunda cluster ID | | `CAMUNDA_CLIENT_ID` | Camunda API client ID | | `CAMUNDA_CLIENT_SECRET` | Camunda API client secret | | `CAMUNDA_CLUSTER_REGION` | Camunda cluster region | ### Examples #### Example 1: Interactive setup ```bash $> c8ctl csap-setup # ... ? SAP integration module ❯ OData connector RFC connector All modules ``` This guides you through the setup process interactively. #### Example 2: Setting up all modules, reusing credentials from environment ```bash $> c8ctl csap-setup --for all \ --camunda 8.8 \ --deployment SaaS # ... i Camunda API credentials found in environment. Reusing ┌────────────────────────┬──────────┐ │ (idx) │ Values │ ├────────────────────────┼──────────┤ │ CAMUNDA_CLUSTER_ID │ "***5ee" │ │ CAMUNDA_CLIENT_ID │ "***icQ" │ │ CAMUNDA_CLIENT_SECRET │ "***XEq" │ │ CAMUNDA_CLUSTER_REGION │ "***d-1" │ └────────────────────────┴──────────┘ ``` This command sets up all available SAP integration modules for Camunda version 8.8. ## Migrate from the `csap` binary To migrate from the deprecated `csap` binary to the c8ctl plugin, install the plugin and replace `csap setup` with `c8ctl csap-setup` in your scripts and pipelines. 1. Install c8ctl and [load the plugin](#installation). 1. Replace `csap setup` with `c8ctl csap-setup` in your scripts and CI/CD pipelines. 1. Remove the `csap` binary from your `PATH`. All flags and environment variables keep their names and meaning. The default values for `--for`, `--camunda`, and `--deployment` can differ from the `csap` binary, so pass them explicitly in non-interactive runs. ## Deploying modules After each Camunda SAP integration module is set up with `c8ctl csap-setup`, it is ready for deployment. The plugin prints `in directory ` after every successful run. Use this path for the deployment. For the OData and RFC connectors, run `cf deploy `. Deploying the module to BTP and integrating it into the application lifecycle management of your organization is the responsibility of your SAP practice. To learn more, see the deployment sections of the [OData connector](./odata-connector.md) and [RFC connector](./rfc-connector.md) pages. --- ## SAP eventing with SAP Advanced Event Mesh (AEM) Receive [CloudEvents](https://cloudevents.io/) from SAP Advanced Event Mesh (AEM) and send CloudEvents to AEM. ## About SAP eventing SAP eventing uses three connectors that enable bidirectional communication between Camunda and AEM. | Connector | Description | | :----------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [SAP Eventing Outbound Connector](https://marketplace.camunda.com/en-US/apps/632606/sap-eventing-outbound-connector) | Sends CloudEvents from Camunda to an AEM topic or queue endpoint. | | [SAP Eventing Message Start Event Connector](https://marketplace.camunda.com/en-US/apps/632607/sap-eventing-message-start-event-connector) | Translates an incoming CloudEvent from AEM into a [BPMN Message Start Event](/components/modeler/bpmn/message-events/message-events.md#message-start-events) to trigger a new process instance. | | [SAP Eventing Intermediate Event Connector](https://marketplace.camunda.com/en-US/apps/632751/sap-eventing-intermediate-event-connector) | Translates an incoming CloudEvent from AEM into a [BPMN Intermediate Catch Event](/components/modeler/bpmn/message-events/message-events.md#intermediate-message-catch-events) to allow an active process to continue based on the event. | The integration uses **HTTP** as the transport protocol: - Incoming connectors act as webhooks, receiving CloudEvent payloads and delivering them into process instances. - The outbound connector sends `HTTP POST` requests to an AEM [topic](https://docs.solace.com/Messaging/Guaranteed-Msg/Topic-Endpoints.htm) or [queue](https://docs.solace.com/Messaging/Guaranteed-Msg/Queues.htm) endpoint. :::info SAP Advanced Event Mesh uses [Solace Event Broker](https://solace.com/products/event-broker/) as its core event broker. The terms **AEM** and **Solace Event Broker** can be used interchangeably when referring to eventing functionality. ::: ## Prerequisites Because **HTTP** is used as the transport protocol, AEM must be configured to use [REST messaging](https://docs.solace.com/API/REST/REST-get-start.htm). This enables publishers and subscribers to communicate over HTTP. The configuration examples below build on the Solace tutorial [Publish/Subscribe REST Messaging](https://tutorials.solace.dev/rest-messaging/publish-subscribe/), which provides detailed steps for setting up REST-based publish/subscribe messaging. ## Installation Install the SAP Eventing connectors directly from the [Camunda Marketplace](https://marketplace.camunda.com/). ## Configuration overview - [SAP Eventing Message Start Event Connector](#sap-eventing-message-start-event-connector) - [SAP Eventing Intermediate Event Connector](#sap-eventing-intermediate-event-connector) - [SAP Eventing Outbound Connector](#sap-eventing-outbound-connector) ## SAP Eventing Message Start Event Connector Inbound CloudEvents → BPMN message. Applying the **SAP Eventing Message Start Event Connector** generates a unique **webhook URL** that starts a new BPMN process instance when invoked. ![Webhook URL](./img/eventing-webhook.png) :::info The webhook URL is generated **only after the initial deployment** of the process. ::: This URL must be registered as a target in **SAP Advanced Event Mesh (AEM)**. The host portion of the URL (`https://.connectors.camunda.io`) is used when configuring the **REST consumer** in AEM. ![AEM REST consumer](./img/eventing-aem-rest-consumer.png) ### Authentication In the REST consumer configuration, set up authentication from AEM to the Camunda webhook endpoint. The credentials configured in the **Authorization** section of the built-in connector must match the **authentication scheme** used in AEM. ![Camunda and AEM credentials](./img/eventing-authorization.png) ### Queue binding The path component of the Camunda webhook URL must be used as the **POST request target** in the **Queue Binding** of the REST consumer. ![AEM Queue Binding](./img/eventing-aem-queue-binding.png) ### Other configuration options The remaining configuration options are identical to those of the [HTTP Webhook Connector](/components/connectors/protocol/http-webhook.md), except for one key difference: > The default webhook response explicitly returns a `200` status code and an `"OK"` message body, confirming that the CloudEvent was successfully received and acknowledged by Camunda. ![Remaining Incoming Webhook config](./img/eventing-incoming-other-config.png) ### Event flow When a CloudEvent is received from AEM, all **header properties** and the **body payload** are relayed to the target process instance, either: - At process creation (Message Start Event), or - During event correlation (Intermediate Event). This ensures that message attributes and payload data from AEM are preserved end-to-end within the Camunda process. ## SAP Eventing Intermediate Event Connector ### Correlate CloudEvents as BPMN messages The **SAP Eventing Intermediate Event Connector** injects a CloudEvent from AEM into an active Camunda process instance using [message correlation](/components/connectors/protocol/http-webhook.md#correlation). Any CloudEvent property can be used as a **correlation key** to match incoming event data to the correct process instance. ### Correlation via CloudEvent body The configuration options of the **Message Start** and **Intermediate Event** connectors are identical, except that the intermediate event requires an additional **Correlation** section. ![Intermediate Message Correlation](./img/eventing-intermediate-correlation.png) - In the example above, the process variable `ENCOMGridID` must exist within the process instance. - Its value is compared against the CloudEvent payload (`request.body.FlynnLocationID`). - If both values match, the CloudEvent is correlated to that process instance. ### Correlation via CloudEvent metadata Since **HTTP** is used as the transport protocol, the [CloudEvents specification](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/bindings/http-protocol-binding.md) requires all CloudEvent metadata to be passed as **HTTP headers**. AEM prepends all user properties with the prefix `Solace-User-Property-`. For example, the CloudEvent property `ce-id` is represented as the HTTP header `Solace-User-Property-ce-id`. To correlate on metadata, reference the header name including this prefix. #### Example: CloudEvent HTTP message ```json { "request": { "body": { "FlynnLocationID": "34.0522,-118.2437" }, "headers": { "host": ".connectors.camunda.io", "authorization": "Basic CaMUnDakZW1v", "content-type": "application/json", "solace-user-property-ce-specversion": "1.0", "solace-user-property-ce-type": "sap.s4.beh.encom.grid.program.v1", "solace-user-property-ce-source": "/alan/pager/buzz", "solace-user-property-ce-subject": "CLU-2.0", "solace-user-property-ce-id": "Argon-T-01", "solace-user-property-ce-time": "2018-04-05T03:56:24Z", "solace-user-property-ce-datacontenttype": "application/json" } } } ``` To correlate on the `ce-id` property, use `request.headers.solace-user-property-ce-id`. ![Mapping to CloudEvent meta data](./img/eventing-correlation-ce-headers.png) :::tip Use backticks to escape dashes in the header name, for example: `` request.headers.`solace-user-property-ce-id` ``. ::: ## SAP Eventing Outbound Connector The **SAP Eventing Outbound Connector** allows you to send CloudEvents to AEM using its REST messaging capability. It publishes the event using an `HTTP POST` request. ![SAP eventing outbound connector configuration](./img/eventing-outbound-connector.png) ### Endpoint The **Endpoint** field specifies the URL of your AEM event broker. You can find this URL in AEM’s web interface: > **Cluster Manager → Your Cluster → (Service Details) → Connect → Connect with Java → Solace REST Messaging API** The **FQDN** (fully qualified domain name) is shown on the right-hand side. ![FQDN of the AEM REST messaging API](./img/eventing-aem-rest-fqdn.png) ### Topic / queue Specify the target **topic** or **queue** path where the CloudEvent will be published. Do not begin the path with `/`. The connector includes a validation check to prevent this. ### Authorization Supported authorization methods: - `None` - `API Key` - `Basic` - `OAuth 2.0` - `Bearer Token` ### CloudEvent CloudEvent metadata (e.g., `ce-id`, `ce-subject`) is automatically handled by the AEM broker, including the required `Solace-User-Property-` prefix. Configure only the standard CloudEvent attributes in the connector. The CloudEvent data is serialized into a **JSON body**, and both metadata and data fields support [FEEL expressions](/components/modeler/feel/what-is-feel.md) for dynamic values. ![Eventing Outbound CloudEvent](./img/eventing-outbound-cloudevent.png) ### Other configuration options All remaining options are identical to those of the [REST Connector](/components/connectors/protocol/rest.md), including: - [Connection timeout](/components/connectors/protocol/rest.md#network-communication-timeouts) - [Output mapping](/components/connectors/protocol/rest.md#output-mapping) These settings control network behavior and define how response data is mapped to process variables. --- ## SAP OData connector The SAP OData connector is a protocol and outbound [connector](/components/connectors/introduction.md) that runs as a Docker image on the SAP Business Technology Platform (BTP). This connector is designed to run in [hybrid mode](/components/connectors/use-connectors-in-hybrid-mode.md), hosted in the customer's SAP BTP sub-account in the [Cloud Foundry environment](https://discovery-center.cloud.sap/serviceCatalog/cloud-foundry-runtime?region=all). This connector works with Camunda 8 SaaS, and utilizes SAP BTP's [Destination](https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/destination-service) and [Connectivity](https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/what-is-sap-btp-connectivity) concepts to query a SAP system via both OData v2 and v4. ## Overview For a standard overview of the steps involved in the SAP OData connector, see the following diagram: ![OData steps](./img/odata-connector-ops.png) ## Prerequisites [Create an API client](/components/saas/clusters/manage-api-clients.md) for your Camunda SaaS cluster with the full scope: `Zeebe,Tasklist,Operate,Optimize,Secrets` To run the SAP OData connector Docker image, the following SAP infrastructure setup is required: - [Cloud Foundry CLI](https://github.com/cloudfoundry/cli) with [multiapps plugin](https://github.com/cloudfoundry/multiapps-cli-plugin) installed on the machine executing the deployment. - SAP BTP subaccount with a [Cloud Foundry environment](https://discovery-center.cloud.sap/serviceCatalog/cloud-foundry-runtime?region=all) enabled and a [created space](https://help.sap.com/docs/btp/sap-business-technology-platform/create-spaces). - Minimum of [1 GB storage quota and 2 GB runtime memory](https://help.sap.com/docs/btp/sap-business-technology-platform/managing-space-quota-plans). - [Entitlements](https://help.sap.com/docs/btp/sap-business-technology-platform/managing-entitlements-and-quotas-using-cockpit) for: - [Connectivity Service](https://discovery-center.cloud.sap/serviceCatalog/connectivity-service?region=all), `lite` plan (to connect to the SAP on-premises). - [Destination Service](https://discovery-center.cloud.sap/serviceCatalog/destination?service_plan=lite®ion=all&commercialModel=btpea), `lite` plan. - One or more instance- or subaccount-level destinations, pointing to the SAP systems to communicate with. ![sample BTP destination configuration](./img/btp-destination.png) - Ensure `Additional Properties` are correctly set on the Destination. For example: ```json HTML5.DynamicDestination: true sap-client: WebIDEEnabled: true WebIDESystem: WebIDEUsage: odata_gen ``` :::info The SAP OData connector currently supports only destination authentication types suitable for straight-through (machine-to-machine) processing: scenarios without user tasks in the execution path. Use `BasicAuthentication` or any `OAuth2`-based mechanism. `PrincipalPropagation` is not supported because it requires a user-bound security context. Camunda user tasks do not expose a JWT (for example, as a transient variable) that the connector can forward at runtime. ::: ## Configuration and deployment A descriptor file is required to deploy the SAP OData connector to a space in a SAP BTP subaccount. An exemplary deployment descriptor `mtad.yaml.example` is provided by Camunda. This is a standard format in SAP BTP's Cloud Foundry environment to describe the application requiring deployment. ### Using the CSAP c8ctl plugin Use the CSAP c8ctl plugin in either: - **Interactive mode**: Follow the on-screen prompts. - **Non-interactive mode**: Provide all required parameters directly to the plugin. Configure the OData connector via the [CSAP c8ctl plugin](./csap-cli.md) (recommended) or manually. Using `c8ctl csap-setup` simplifies the process by automatically gathering all required files and customizing them for your BTP environment based on the details you provide through prompts or command-line options. Use the command `c8ctl csap-setup` to guide you interactively. - Assuming your [Camunda cluster's API credentials](/components/saas/clusters/manage-api-clients.md#create-a-client) are sourced in your shell environment, this will do the configuration for you: ```shell c8ctl csap-setup --for odata \ --camunda 8.7 \ --deployment SaaS ``` ### Manual configuration Follow these steps: 1. Find the matching [Docker image](https://hub.docker.com/r/camunda/sap-odata-connector/tags) for the targeted Camunda 8 SaaS version. The version follows the format `..`. Examples: - `8.6.0` is the OData connector in version `0` for Camunda 8 SaaS version `8.6` - `8.5.1` is the OData connector in version `1` for Camunda 8 SaaS version `8.5` 2. Download the matching `mtad.yaml.example` from [the OData connector's GitHub release page](https://github.com/camunda/sap-odata-connector/releases). Adjust the values for the credentials (`client ID`, client secret, etc.) to match those of the API client of the targeted Camunda 8 SaaS environment and rename it to `mtad.yaml`. 3. Customize the names of the SAP BTP Destination and Connectivity instances as needed. Both will be automatically created during deployment. If instances with the same names already exist in your subaccount, they will be reused. 4. Download the connector template from the [OData connector's GitHub release page](https://github.com/camunda/sap-odata-connector/releases). ### Deploying to SAP BTP 1. Log into the desired SAP BTP subaccount via the [Cloud Foundry `cf-cli`](https://github.com/cloudfoundry/cli): ```shell $> cf login API endpoint: https://api.cf. ... ... ``` 2. Deploy the SAP OData connector via the `cf-cli`. Note that this requires [the "multiapps" plugin of Cloud Foundry](https://github.com/cloudfoundry/multiapps-cli-plugin) to be installed on the machine the deployment runs on: ```shell $> cf deploy ./ # append the -f flag to shortcircuit ongoing deployments Deploying multi-target app archive /some/path/sap-odata-connector in org / space as you@example.org .. ... Application "sap-odata-connector" started and available at "some.url.hana.ondemand.com" ``` ### Deployment in Camunda 8 SaaS - If you are using Camunda Hub, [import the SAP OData connector element](/components/hub/workspace/modeler/element-templates/manage-element-templates.md#importing-an-existing-element-template) you downloaded in the earlier step to use it in your process design. ![sample BPMN diagram with SAP OData connector](./img/sap-odata-connector-task-in-model.png) - If you are using Desktop Modeler, [follow the standard importing procedure](/components/modeler/desktop-modeler/element-templates/configuring-templates.md). ## Working with the SAP OData connector in Camunda Modeler ### Modeling options To use the **SAP OData connector** in your process, either change the type of existing task by clicking on it and selecting the **Change element** menu icon, or create a new connector task by using the **Append connector** context menu. Follow our [guide to using connectors](/components/connectors/use-connectors/index.md) to learn more. :::note The configuration options will dynamically change with the selected HTTP method and the OData protocol version. For example, a `payload` field is only displayed when the HTTP method is something other than "GET". ::: ![SAP OData connector element template](./img/sap-odata-connector-template.png) Specifying the `BTP destination name` allows you to reuse existing Destinations from the subaccount or instance level. Authentication and authorizations are maintained at this level, which is why it's not necessary to maintain credentials for the connector. ### Advanced capabilities In addition to the basic OData settings such as Service, Entity, EntitySet, Method, and OData version, the **Advanced** section allows you to fine tune `GET` queries to the SAP method with all standard parameters. For example, supplying `$filter` and `$select` parameters helps in reducing data transferred over the wire, while `$expand` helps in retrieving additional entities with a single query. ![Advanced options of the SAP OData connector element template](./img/sap-odata-connector-element-template-advanced.png) ### $batch requests The capabilities are in sync with [https://me.sap.com/notes/1869434](https://me.sap.com/notes/1869434), with the exception of XML over the wire. The OData connector uses `JSON` only. When `Batch Request` is selected as **Request type**, the available query options change and reveal the choice of the OData protocol and the input area for the individual requests. Note that the **Batch Request Payload** field requires the mandatory use of `FEEL`. ![batch connector template](./img/sap-odata-connector-batch-connector-template.png) The **Batch Request** payload is an array of objects that can either be of `"type": "batch"` or `"type": "changeset"` - `batch` (denotes read requests), and `changeset` (denotes write operations, including `DELETE`). ```jsonc [ { "type": "batch", "requests": [...] }, { "type": "changeset", "requests": [...] } ] ``` Both `batch` and `changeset` contain a `requests` node that holds the individual requests that should occur within a `batch` (read) or a `changeset` (write, update, delete). Together, they constitute the entirety of the batch request. The `requests` node is also an array of `objects`. Regardless of if a request is inside a `batch` or a `changeset`, it always has `method` and `resourcePath` as mandatory fields. ```jsonc [ { "type": "batch", "requests": [ { "method": "GET", "resourcePath": "A_BusinessPartner('" + bp1 + "')", "options": { "format": "json", "select": "FirstName" } } ] }, { "type": "changeset", "requests": [ { "method": "PATCH", "resourcePath": "A_BusinessPartner('" + bp1 + "')", "payload": { "FirstName": "Bernd (" + string(now()) + ")" } } ] } ] ``` `batch` request entries can contain an additional `options` block, allowing the same query options as the connector template has in [advanced capabilities](#advanced-capabilities), with the `$` prefix omitted. `changeset` request entries are modifying operations and thus require a `payload` in JSON format. ### Query result structure The result of any query, whether it is reading or writing to the SAP system, is in JSON format in the following structure: ```json { result: , statusCode: , countOrInlineCount: } ``` - `result` contains the result of the query, whether it is content retrieved from a SAP system via `GET` or the result of a write or update operation via `POST`, `PUT`, `PATCH`, or `DELETE`. (Note that with the latter, the `result` is always empty.) - `statusCode` holds the [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) of the operation. - `countOrInlineCount` is only present in the response when the corresponding option `$inlinecount` (for OData v2) or `$count` (for OData v4) was checked in the design time of the BPMN task. It then shows the number of results from the `GET` query to the SAP system. ![the output mapping of the SAP OData element template](./img/sap-odata-connector-element-template-result.png) The query result can either be mapped to a single result variable or worked on [via FEEL with an expression](/components/connectors/use-connectors/index.md#result-expression). The same is applicable to `getResponse`, as a result variable contains the described query JSON in its entirety. The result expression `{getStatusCode: statusCode}` would only hold the HTTP status code in the `getStatusCode` process variable. For `$batch` requests, the query result is an array of the result structure above. This is an example for an OData v2 batch request, consisting of one `Read` and one `Update` operation: ```json { "": [ { "result": { "d": { "__metadata": { "id": "/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner('...')", "uri": "/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner('...')", "type": "API_BUSINESS_PARTNER.A_BusinessPartnerType" }, "FirstName": "..." } }, "statusCode": 200 }, { "result": null, "statusCode": 204 } ] } ``` If one of the operations in the batch request fails, the error is relayed to the `result` node for the request. For example, the second request in this sample result where the business partner with number `0000000` couldn't be found: ```jsonc { "": [ { "result": { "d": { "__count": "4", "results": [ {...} ] } }, "statusCode": 200, "countOrInlineCount": 4 }, { "result": { "target": "/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner('0000000')?$format=json&$select=FirstName%2CLastName", "odata": { "error": { "code": "/IWBEP/CM_MGW_RT/020", "message": { "lang": "en", "value": "Resource not found for segment 'A_BusinessPartnerType'" }, "innererror": { "application": { "component_id": "LO-MD-BP", "service_namespace": "/SAP/", "service_id": "API_BUSINESS_PARTNER", "service_version": "0001" }, "transactionid": "D25C6A8CF4D30020E0067DD0610AFCCA", "timestamp": "", "Error_Resolution": { "SAP_Transaction": "", "SAP_Note": "See SAP Note 1797736 for error analysis (https://service.sap.com/sap/support/notes/1797736)", "Batch_SAP_Note": "See SAP Note 1869434 for details about working with $batch (https://service.sap.com/sap/support/notes/1869434)" }, "longtext_url": "/sap/opu/odata/iwbep/message_text/T100_longtexts(MSGID='%2FIWBEP%2FCM_MGW_RT',MSGNO='020',MESSAGE_V1='A_BusinessPartnerType',MESSAGE_V2='',MESSAGE_V3='',MESSAGE_V4='')/$value", "errordetails": [ { "ContentID": "", "code": "/IWBEP/CX_MGW_BUSI_EXCEPTION", "message": "Resource not found for segment 'A_BusinessPartnerType'", "longtext_url": "/sap/opu/odata/iwbep/message_text/T100_longtexts(MSGID='%2FIWBEP%2FCM_MGW_RT',MSGNO='020',MESSAGE_V1='A_BusinessPartnerType',MESSAGE_V2='',MESSAGE_V3='',MESSAGE_V4='')/$value", "propertyref": "", "severity": "error", "transition": false, "target": "" } ] } } } }, "statusCode": 404 } ] } ``` ### Error handling The SAP OData connector allows handling of query errors directly in the model. This means an OData error is relayed to the process instance in the reserved variables `bpmnError` and `error` and can be processed accordingly. 1. Equip the connector task with an error handling expression such as: ```js if error.code = "400" then bpmnError("400", "client request is bad", { errorMessage: error.message, errorCode: error.code }) else if error.code = "404" then bpmnError("404", "queried resource not found", { errorMessage: error.message, errorCode: error.code }) else if error.code = "500" then bpmnError("500", "server error", { errorMessage: error.message, errorCode: error.code }) else if error.code = "503" then bpmnError("503", "I'm just an proxy teapot", { errorMessage: error.message, errorCode: error.code }) else null ``` ![image-20241010160419616](./img/odata-connector-error-expression.png) 2. Specifically note the third parameter to `bpmnError`: ```js { errorMessage: error.message, errorCode: error.code } ``` This relays the error message and code to the next step in the process flow. 3. Equip the BPMN task with an error boundary event: ![error boundary event on SAP OData connector](./img/sap-odata-connector-task-error-handling2.png) If the SAP OData connector encounters an error, the boundary event will catch the error and continue the process flow. The error boundary event can receive these configuration parameters to contain further error details: ![error output mapping](./img/sap-odata-connector-task-error-handling1.png) - `errorMessage`: Contains a verbose version of the error message and cause and relays it into the process scope as `ov_errorMessage`. - `errorCode`: Holds a predefined value describing the scope of the error, relaying it to the process scope as `errorCode`. It can be one of the following: - `INVALID_PAYLOAD`: The payload of the request was detected as erroneous by the server. - `REQUEST_ERROR`: The request contained an error, for example, a wrong combination of `GET` query parameters. - `GENERIC_ERROR` - `DESTINATION_ERROR`: An error occurred while claiming the Destination from the runtime environment. - `error`: The serialized Error object, available in the example above as `ov_error`. ## Tips - Ensure the connection from the Cloud Foundry environment via the destination to the SAP systems works. Using the [Terminal in Business Application Studio](https://community.sap.com/t5/technology-blogs-by-sap/how-to-check-the-connectivity-to-your-backend-system-in-business/ba-p/13479832) is a quick way to verify this. - Validate requests first in an API client before trying with the SAP OData connector in Modeler. Then, copy over to the element template fields. This saves time and reduces potential error. - Any payload size less than or equal to 2.5 MB can be considered safe. --- ## Prerequisites Before setting up the SAP integration, ensure the following requirements are met. ## Camunda setup - **OData and RFC connectors:** 8.7+ - **Advanced Event Mesh integration:** 8.7+ Compatible with both **SaaS** and **Self-Managed** deployments: - **SaaS:** Use [hybrid connectors](/components/connectors/use-connectors-in-hybrid-mode.md) to securely connect to SAP systems. - **Self-Managed:** Ensure outbound network connectivity from your environment to SAP BTP. ## SAP setup You need an **SAP BTP subaccount** with these services enabled: - [**Cloud Foundry Runtime**](https://discovery-center.cloud.sap/serviceCatalog/cloud-foundry-runtime?region=all) - [**Destination Service (Free)**](https://discovery-center.cloud.sap/serviceCatalog/destination?region=all&service_plan=lite&commercialModel=btpea) – for system and service connectivity - [**Connectivity Service (Free)**](https://discovery-center.cloud.sap/serviceCatalog/connectivity-service?region=all) – required for on-premises SAP S/4HANA or ECC - [**SAP Cloud Connector**](https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/cloud-connector) – bridges on-premises systems with BTP Additional services may be required depending on your use case: - [**SAP Advanced Event Mesh (AEM)**](https://discovery-center.cloud.sap/serviceCatalog/advanced-event-mesh?region=all) – enables event-driven integration between Camunda and SAP. ## Authentication and connectivity - An SAP **technical user** with sufficient authorizations for the relevant S/4HANA or ECC systems - [**Authorization and Trust Management Service (Free)**](https://discovery-center.cloud.sap/serviceCatalog/authorization-and-trust-management-service?region=all) – to manage identity and access across integration modules - Secure connectivity between Camunda and SAP, configured either: - Through **SAP Cloud Connector** for on-premises systems - Or directly via **SAP BTP services** for cloud-hosted systems ## Tooling - [**CSAP c8ctl plugin**](./csap-cli.md): A plugin for [c8ctl](/apis-tools/c8ctl/getting-started.md) that runs on any platform with Node.js 22 or later. --- ## SAP RFC connector The [SAP RFC](/reference/glossary.md#rfc) [Connector](/components/connectors/introduction.md) is a [protocol and outbound connector](/components/connectors/connector-types.md). This connector is a Java Spring Boot application that runs as a `.war` on the SAP Business Technology Platform (BTP). It connects to Camunda 8 SaaS, and utilizes SAP BTP's [Destination](https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/destination-service) and [Connectivity](https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/what-is-sap-btp-connectivity) concepts to query a SAP system via the RFC protocol to interact with remote-enabled Function Modules and BAPIs. ## Overview For a standard overview of the steps involved in the SAP RFC connector, see the following diagram: ![RFC overview](./img/rfc-connector-ops.png) ## Prerequisites - **Camunda API Client** - [Create an API client](/components/saas/clusters/manage-api-clients.md) for your Camunda SaaS cluster with the full scope: `Zeebe,Tasklist,Operate,Optimize,Secrets` - **To run the SAP RFC connector**, the following SAP infrastructure setup is required: - [Cloud Foundry CLI](https://github.com/cloudfoundry/cli) with the [multiapps plugin](https://github.com/cloudfoundry/multiapps-cli-plugin) installed on the machine executing the deployment. - SAP BTP subaccount with a [Cloud Foundry environment](https://discovery-center.cloud.sap/serviceCatalog/cloud-foundry-runtime?region=all) enabled and a [created space](https://help.sap.com/docs/btp/sap-business-technology-platform/create-spaces). - A minimum of [1 GB storage quota and 2 GB runtime memory](https://help.sap.com/docs/btp/sap-business-technology-platform/managing-space-quota-plans). - **[Entitlements](https://help.sap.com/docs/btp/sap-business-technology-platform/managing-entitlements-and-quotas-using-cockpit) for**: - [Connectivity Service](https://discovery-center.cloud.sap/serviceCatalog/connectivity-service?region=all), `lite` plan (to connect to the SAP is on-premises). - [Destination Service](https://discovery-center.cloud.sap/serviceCatalog/destination?service_plan=lite®ion=all&commercialModel=btpea), `lite` plan. - [Authorization and Trust Management Service](https://discovery-center.cloud.sap/serviceCatalog/authorization-and-trust-management-service?region=all), `application` plan. - **One or more instance- or subaccount-level Destinations**, pointing to the SAP systems to communicate with. ![btp-destination-rfc](./img/btp-destination-rfc.png) - **Ensure `Additional Properties` is set** on the Destination are aligned with those of your connector or remote SAP system. ## Configuration and deployment Unlike other built-in connectors, the SAP RFC connector must be deployed as a Java `.war` archive. This is because it uses SAP's [JCo Java library](https://support.sap.com/en/product/connectors/jco.html) to connect via RFC to the configured SAP system. the JCo library's license prohibits redistribution, but it is available at runtime on BTP and auto-discovered by Camunda's RFC connector. A descriptor file is required to deploy the SAP RFC connector to a space in a SAP BTP subaccount. An exemplary deployment descriptor `mtad.yaml.example` is provided by Camunda. This is a standard format in SAP BTP's Cloud Foundry environment to describe the application that needs deployment. ### Configuring the RFC connector Configure the SAP RFC connector via the [CSAP c8ctl plugin](./csap-cli.md) (recommended) or manually. Using `c8ctl csap-setup` simplifies the process by automatically gathering all required files and customizing them for your BTP environment based on the details you provide through prompts or command-line options. #### Using the CSAP c8ctl plugin Use the CSAP c8ctl plugin in either: - **Interactive mode:** By following the on-screen prompts. - **Non-interactive mode:** By providing all required parameters directly to the plugin. Use the command `c8ctl csap-setup` to guide you interactively. - Assuming your [Camunda cluster's API credentials](/components/saas/clusters/manage-api-clients.md#create-a-client) are sourced in your shell environment, this will do the configuration for you: ```shell c8ctl csap-setup --for rfc \ --camunda 8.7 \ --deployment SaaS ``` #### Manual configuration 1. Find the matching `.war` archive for the targeted Camunda 8 SaaS version on the [respective GitHub release page](https://github.com/camunda/sap-rfc-connector/releases). The version follows the format `..`. Examples: - `rfc-8.6.0.war` is the RFC connector in version `0` for Camunda 8 SaaS version `8.6` - `rfc-8.5.1.war` is the RFC connector in version `1` for Camunda 8 SaaS version `8.5` 2. Download the matching `mtad.yaml.example` file also from [the GitHub release page](https://github.com/camunda/sap-rfc-connector/releases). Adjust the values for the credentials (such as client ID and client secret) to match those of the API client of the targeted Camunda 8 SaaS environment and rename it to `mtad.yaml`. 3. Donwload the connector template from [the GitHub release page](https://github.com/camunda/sap-rfc-connector/releases). ### Deploying to BTP 1. Log into the desired SAP BTP subaccount via the [Cloud Foundry `cli`](https://github.com/cloudfoundry/cli) (cf-cli): ```shell $> cf login API endpoint: https://api.cf. ... ... ``` 2. Deploy the SAP RFC connector via the `cf-cli`. Note that this requires [the "multiapps" plugin of Cloud Foundry](https://github.com/cloudfoundry/multiapps-cli-plugin) to be installed on the machine the deployment runs on. ```shell $> cf deploy ./ # append the -f flag to shortcircuit ongoing deployments Deploying multi-target app archive /some/path/sap-rfc-connector in org / space as you@example.org .. ... Application "sap-rfc-connector" started and available at "some.url.hana.ondemand.com" ``` ### Deployment in Camunda 8 SaaS - If using Camunda Hub, [import the SAP RFC connector's element template](/components/hub/workspace/modeler/element-templates/manage-element-templates.md#importing-an-existing-element-template) contained in the repository in `element-templates/sap-rfc-connector.json` for use in your process design. ![sap-rfc-connector-task-in-model](./img/sap-rfc-connector-task-in-model.png) - If using Desktop Modeler, [follow the standard importing procedure](/components/modeler/desktop-modeler/element-templates/configuring-templates.md). ## Working with the SAP RFC connector in Camunda Hub and Desktop Modeler ### Modeling options To use the **SAP RFC connector** in your process, either change the type of existing task by clicking on it and selecting the **Change element** menu icon, or create a new connector task by using the **Append connector** context menu. Follow our [guide to using connectors](/components/connectors/use-connectors/index.md) to learn more. ![sap-rfc-connector-task-in-model](./img/sap-rfc-connector-element-template.png) First, choose whether to call a `BAPI` or a Function Module (`FM`). Then, provide the `exporting`-, `importing-`, and `tables` parameters as lists of objects. All object entries in the list look similar to `[{name:"param", type:"type"}]`, pointing to the parameter name of the BAPI/FM and its type. For example, `[{name:"PERSON_IN_CHARGE_FROM", type:"BAPI0012_GEN-PERS_IN_CHRG"}]`. For those with experience in `ABAP`, the configuration options are similar. ### Sending variables to the RFC target The `exporting parameter` is sent to the RFC target. The object structure generally looks like `[{name: "param", type: "type", value: }]`. Example: ```json [ { "name": "CONTROLLINGAREA", "type": "BAPI0012_GEN-CO_AREA", "value": "1000" } ] ``` This corresponds with the BAPI's/FM's `importing` definition, meaning it imports these variables from the RFC call: ```ABAP *" IMPORTING <-- this is the BAPI/FM - don't be confused! In Camunda, this is "exporting" :) *" VALUE(CONTROLLINGAREA) LIKE BAPI0012_GEN-CO_AREA ``` ### Receiving variables from the RFC target `Importing parameter` is what is expected back from the RFC target. They are configured in the same "list of objects" style pattern in the element template as the other parameters and generally look like `[{name: "param", type: "type"}]`. Example: ```json [ { "name": "DETAIL_DATA", "type": " BAPI1079_DETAIL" } ] ``` This corresponds with the BAPI's/FM's `exporting` definition, meaning it exports these variables to the caller: ```ABAP *" EXPORTING *" VALUE(DETAIL_DATA) LIKE BAPI1079_DETAIL ``` ### Special cases: sending and/or receiving a "table" and a "changing" structure #### tables The `tables parameter` can be both "exporting" and "importing". :::danger Sending tables as tabular data to an RFC target is not yet supported. ::: ```json { "name": "COSTCENTERLIST", "type": "BAPI0012_CCLIST" } ``` The above example is an object parameter in the `tables parameter` section that describes a result table to be received back from the RFC call. In conforms with the BAPI `BAPI_COSTCENTER_GETLIST1` parameter definition on the SAP system: ```ABAP *" TABLES *" COSTCENTERLIST STRUCTURE BAPI0012_CCLIST ``` The same is applicable for the return structure `BAPIRET2` that denotes the result status of the RFC call: ```json { "name": "BAPIRET2", "isReturn": true } ``` This aligns with the BAPI definition: ```ABAP *" TABLES *" .... *" RETURN STRUCTURE BAPIRET2 ``` #### changing A `changing parameter` is a variable received by an RFC target that is processed, changed, and returned. It is only available for `FM`-type RFC targets in the SAP RFC connector. The overall structure is `[{name: "param", type: "type", value: }]`. Example: ```json [ { "name": "CV_RESULT", "type": "I", "value": "100" } ] ``` The value `100` is sent to the Fuction Module and sent back as `CV_RESULT`. ## Query result structure ### BAPI The result of a call to a BAPI holds the following JSON structure: ```json { tables: [ { ... } ], importing: { { ... } } ] ``` `tables` holds a representation of the result tables configured. `importing` is the result of what was sent to the BAPI in the `exporting` section above. ### Function Module The result of a call to a Function Module holds the following JSON structure: ```json { tables: [ { ... } ], importing: [ { ... } ], changing: [ { ... } ] ] ``` - `tables` holds a representation of the result tables configured. - `importing` is the result of what was sent to the Function Module in the `exporting` section above. - `changing` is the result of what was sent to the Function Module in the `changing` section above. ## Error handling The SAP RFC connector allows handling of query errors directly in the model. This means an RFC error is relayed to the process instance in the reserved variables `bpmnError` and `error` and can be processed accordingly: ``` DESTINATION_ERROR, REQUEST_EXECUTION_ERROR, REQUEST_SERIALIZATION_ERROR, JCO_RUNTIME_ERROR, GENERIC_ERROR ``` --- ## Best practices(Servicenow) Use these practices to build secure, reliable, and maintainable Camunda–ServiceNow integrations. ### Manage credentials securely - Store and manage ServiceNow credentials using [secrets](../../saas/clusters/manage-secrets.md). - Never hardcode usernames or passwords in connector configurations or BPMN models. - Reference secrets with the `{{secrets.}}` syntax in connector fields. - Apply this consistently across all ServiceNow connectors to maintain a unified security model. ### Reuse variables effectively - Map ServiceNow `sys_id` values to **top-scope process variables** in your BPMN process. - Use these variables for: | Action | Purpose | | ----------- | ---------------------------------------------- | | Lookup | Retrieve existing records reliably | | Update | Modify records without additional queries | | Delete | Remove records when needed | | Correlation | Tie records to notifications or process events | **Example:** ```feel = incidentResponse.body.sys_id ``` --- ## Blueprints Use these blueprints to quickly implement common Camunda–ServiceNow integration scenarios. ![Camunda ServiceNow integration blueprint](./img/sn-blueprint-template.png) Download the blueprint from the [Camunda Marketplace](https://marketplace.camunda.com/) and import it directly into Camunda Hub or Desktop Modeler. It provides **pre-configured processes** demonstrating best practices, reusable patterns, and realistic business use cases. ### Supported integration patterns | Type | Name | Purpose | Example | Built-in connectors | ServiceNow Spoke | | :---------------- | :------------------------------------- | :------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------- | :---------------------------- | :-------------------------------- | | **Manage** | Manage ServiceNow data entries | Execute CRUD (Create, Read, Update, Delete) operations on any ServiceNow table. | Create or modify a change request in ServiceNow. | ServiceNow Outbound Connector | Not needed | | | Create ServiceNow Incident | Raise business errors from BPMN models that trigger ServiceNow incidents. | Create or modify a change request in ServiceNow. | ServiceNow Incident Handler | Not needed | | **Orchestration** | Start Camunda Process | Start a Camunda orchestration process from a ServiceNow Flow. | Trigger an end-to-end orchestration for employee onboarding. | Not needed | Camunda Spoke → Start Process | | | Orchestrate ServiceNow Flows | Trigger a ServiceNow Flow within a Camunda orchestration and correlate using the ServiceNow `sys_id`. | Interrupt a Camunda orchestration for an approval process executed in ServiceNow. | ServiceNow Flow Starter | Camunda Spoke → Correlate Message | | | Orchestrate ServiceNow Data Management | Trigger a ServiceNow Flow when a dataset changes as part of a Camunda orchestration. | Updating a dataset that triggers a flow for further processing. | ServiceNow Outbound Connector | Camunda Spoke → Correlate Message | | | Orchestrate ServiceNow User Task | Create a ServiceNow User Task from a Camunda orchestration. Users interact with the orchestration through this task. | Create a ServiceNow Catalog item for a user who needs to provide additional information. | ServiceNow Outbound Connector | Camunda Spoke → Correlate Message | --- ## Camunda Spoke for ServiceNow The Camunda Spoke for ServiceNow lets you orchestrate workflows between Camunda and ServiceNow, empowering your organization to automate cross-system workflows end to end. With the Spoke installed in ServiceNow, you can start, signal, correlate, or cancel Camunda process instances directly from Flow Designer. ## Spoke actions ### Start process Start a Camunda process from ServiceNow. ![Start Process action](./img/spoke-action-start.png) Supported inputs **Process ID:** The ID of the deployed BPMN process to start. Example: `handle_incident` **Process Version:** (Optional) The version of the process to start. If empty, the latest deployed version is used. Example: `5` **Variables:** (Optional) Process variables passed to Camunda as key-value pairs in JSON format. Example: `{ "invoiceId": "12345", "amount": 250 }` #### Code example ```javascript const returnObject = { request_item_number: fd_data.trigger.request_item.number.toString() || "", request_sys_id: fd_data.trigger.request_item.sys_id.toString() || "", }; return JSON.stringify(returnObject); ``` :::tip When you add the JSON payload as a code snippet, convert ServiceNow types to a JSON-compatible format. In the example above, `sys_id` is a ServiceNow GUID, but it must be converted to a string for the JSON payload. That is why the example uses `fd_data.trigger.request_item.sys_id.toString()`. ::: **Tenant ID:** (Optional) The tenant identifier for multi-tenant Camunda setups. Leave empty for single-tenant setups. Example: `hr-emea` **Operation Reference:** (Optional) A user-defined reference key available in Camunda for tracking the operation. Example: `camID` **Wait for completion:** (Optional) Whether the flow waits until the Camunda process completes. ### Send signal Broadcast BPMN signals to one or more Camunda process instances ![Send Signal action](./img/spoke-action-send.png) Supported inputs **Signal name:** The name of the BPMN signal to send. Must match the signal name defined in the process model. Example: `sla_limit_exceeded` **Variables:** (Optional) Process variables passed to Camunda as key-value pairs in JSON format. Example: `{ "invoiceId": "12345", "amount": 250 }` **Tenant ID:** (Optional) The tenant identifier for multi-tenant Camunda setups. Leave empty for single-tenant setups. Example: `hr-emea` ### Correlate message Correlate a running Camunda process instance from ServiceNow. ![Correlate Message action](./img/spoke-action-correlate.png) Supported inputs **Message name:** The name of the BPMN message to correlate with. Example: `managerApprovalDone` **Correlation key:** The process variable value used to match the message to a specific process instance. Example: `approvalID` ### Cancel process Cancel a Camunda process instance from ServiceNow when needed. ![Cancel Process Action](./img/spoke-action-cancel.png) Supported inputs **Process Instance Key:** The unique key identifying a running Camunda process instance to cancel. Example: `2251799813685252` **Operation Reference:** (Optional) A user-defined reference key available in Camunda for tracking the operation. Example: `camID` ### Start a ServiceNow process from Camunda Camunda can trigger a ServiceNow flow by calling a REST API as the trigger endpoint in ServiceNow. ![REST API trigger configuration](./img/rest-api-trigger.png) Supported inputs **HTTP Method:** The HTTP method accepted by the flow. Example: `POST` **Path:** A custom URL path suffix for the trigger endpoint used by the ServiceNow Flow Starter Connector. Example: `/api/camunda/my_flow_name` **Requires authentication:** Whether incoming requests must include a valid ServiceNow authentication header. Enable this for production integrations. **Roles:** (Optional) ServiceNow roles authorized to access the trigger endpoint. --- ## ServiceNow Flow Starter Use the ServiceNow Flow Starter connector to trigger ServiceNow flows from Camunda processes and correlate them using the ServiceNow **Sys ID**. The connector calls ServiceNow flows via REST API, enabling you to orchestrate complex business logic such as fulfillment, approvals, or catalog flows as part of an end-to-end Camunda process. It also allows Camunda orchestrations to **pause or wait** for ServiceNow-driven approval steps. :::important The ServiceNow Flow Starter connector requires the **ServiceNow Integration Hub Enterprise Pack** and the **Flow Trigger – REST plugin**. See [prerequisites](../prerequisites.md) for installation requirements. ::: ## Supported operations | Operation | Description | Example use case | | :----------- | :------------------------------------------------------------------- | :---------------------------------------------------------------------- | | Trigger Flow | Start a ServiceNow Flow Designer flow via its REST trigger endpoint. | Initiate catalog requests or approval workflows from Camunda processes. | ## Configure the connector In Camunda Hub or Desktop Modeler, select **ServiceNow Flow Starter** from the connector templates or download it from the [Camunda Marketplace](https://marketplace.camunda.com/). ### Required fields | Field | Description | | :-------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | | Instance name | Name of your ServiceNow instance (e.g., `your-instance-name`). | | REST API trigger path | REST API endpoint for the ServiceNow flow (e.g., `/api/camun/my_flow_name`). | | Method | HTTP method for the request (`POST`, `GET`, `PUT`, `PATCH`, `DELETE`). | | Headers | Optional HTTP headers to include in the request (e.g., `{"hello":"header"}`). | | Query parameters | Optional URL query parameters (e.g., `{"hello":"query"}`). | | Request body | Payload sent to the ServiceNow flow, typically containing input variables or correlation data (e.g., `{"correlationValue": camId}`). | | Authentication | ServiceNow credentials (username and password). | :::tip Store ServiceNow credentials securely as [secrets](/components/saas/clusters/manage-secrets.md) and reference them in the connector configuration (for example, `{{secrets.snUser}}`). ::: ![Configuration of the Flow Starter connector in Camunda Hub.](../img/flow-starter.png) _Configuration of the Flow Starter connector in Camunda Hub._ ## Example configuration | Field | Example value | | :-------------------- | :---------------------------- | | Instance name | `your-instance-name` | | REST API trigger path | `/api/camun/your_flow_name` | | Method | `POST` | | Headers | `{"hello": "header"}` | | Query parameters | `{"hello": "query"}` | | Request body | `{"correlationValue": camId}` | | Username | `{{secrets.snUser}}` | | Password | `{{secrets.snPwd}}` | --- ## ServiceNow Incident Handler Use the ServiceNow Incident Handler connector to create, read, update, or delete incidents in ServiceNow directly from Camunda processes. This connector works with the ServiceNow `incident` table, enabling automated IT service management and process-driven incident handling. ## Supported operations | Operation | Description | Example use case | | :-------- | :----------------------------------------------------------- | :---------------------------------------------------------- | | Create | Create a new incident in ServiceNow. | Automatically log an incident when a process task fails. | | Read | Retrieve details of an existing incident using its `sys_id`. | Check the current status of an incident. | | Update | Modify fields on an existing incident. | Change incident priority or assignment group mid-process. | | Delete | Remove an incident by its `sys_id`. | Clean up test or temporary incidents after automation runs. | ## Configure the connector Select **ServiceNow Incident Handler** from [Camunda Hub](/components/hub/workspace/modeler/index.md) or Desktop Modeler connector templates or download it from the [Camunda Marketplace](https://marketplace.camunda.com/). ### Required fields | Field | Description | | :------------- | :------------------------------------------------------------------------- | | Instance name | Name of your ServiceNow instance (e.g., `your-instance-name`). | | Operation | One of `Create`, `Read`, `Update`, or `Delete`. | | Payload | JSON data representing incident fields (for Create and Update operations). | | Sys ID | Unique identifier for `Read`, `Update`, or `Delete` operations. | | Authentication | ServiceNow credentials (username and password). | :::tip Store ServiceNow credentials securely as [secrets](/components/saas/clusters/manage-secrets.md) and reference them in the connector configuration (for example, `{{secrets.snUser}}` and `{{secrets.snPwd}}`). ::: ![ServiceNow Incident Handler example](../img/incident-handler.png) _Configuration of the Incident Handler connector in Camunda Hub._ ## Example configurations ### Create a new incident | Field | Example value | | :------------ | :------------------------------------------------------------------- | | Instance name | `your-instance-name` | | Operation | `Create` | | Payload | `{"short_description": "Create ServiceNow Incident (from Camunda)"}` | | Username | `{{secrets.snUser}}` | | Password | `{{secrets.snPwd}}` | ### Update an existing incident's priority | Field | Example value | | :------------ | :------------------- | | Instance name | `your-instance-name` | | Operation | `Update` | | Sys ID | `{{incidentSysId}}` | | Payload | `{"priority": "2"}` | | Username | `{{secrets.snUser}}` | | Password | `{{secrets.snPwd}}` | --- ## ServiceNow outbound connector Use the ServiceNow outbound connector to perform CRUD operations on any ServiceNow table directly from Camunda processes. This connector interacts with ServiceNow tables via REST APIs, enabling powerful integrations without custom scripts. ## Supported operations | Operation | Description | Example use case | | :-------- | :--------------------------------------------------------------------------- | :--------------------------------------------------------------- | | Create | Insert a new record into a ServiceNow table. | Create a new incident or service request from a Camunda process. | | Read | Retrieve records from a ServiceNow table using query parameters or `sys_id`. | Look up user details or check incident status. | | Update | Modify fields of an existing record identified by `sys_id`. | Update ticket status or assignment group. | | Delete | Remove a record from a table by `sys_id`. | Delete temporary or test records after processing. | ## Configure the connector In Camunda Hub or Desktop Modeler, select **ServiceNow Outbound Connector** from the connector templates or download it from the [Camunda Marketplace](https://marketplace.camunda.com/). ### Required fields | Field | Description | | :--------------- | :------------------------------------------------------------------------------------------------------ | | Instance name | Name of your ServiceNow instance (e.g., `your-instance-name`). | | Operation | One of `Create`, `Read`, `Update`, or `Delete`. | | Target table | The target ServiceNow table (e.g., `incident`, `sc_task`, `sc_req_item`). | | Payload | JSON data sent to ServiceNow for `Create` and `Update` operations. | | Query parameters | For `Read` operations. Use `^` to separate multiple filter conditions (e.g., `active=true^priority=1`). | | Sys ID | Required for `Update` and `Delete` operations to identify the target record. | | Authentication | ServiceNow credentials (username and password). | :::tip Store ServiceNow credentials securely as [secrets](/components/saas/clusters/manage-secrets.md) and reference them in the connector configuration (for example, `{{secrets.snUser}}` and `{{secrets.snPwd}}`). ::: ![ServiceNow Outbound Connector example](../img/outbound-connector.png) _Example configuration of the Create operation in Camunda Hub._ > When using `Read`, `Update`, or `Delete`, the `sys_id` field becomes available in the connector properties to specify the target record. ![Sys ID field example](../img/outbound-sys-id.png) ## Example: Create a requested item | Field | Example value | | :------------ | :--------------------------------------------------------------------------------------------------------------------- | | Instance name | `your-instance-name` | | Operation | `Create` | | Target table | Requested item [sc_req_item] | | Payload | `{"short_description": "Database maintenance scheduled via Camunda process", "category": "Hardware", "priority": "2"}` | | Username | `{{secrets.snUser}}` | | Password | `{{secrets.snPwd}}` | --- ## Glossary Use this glossary to understand key terms and concepts when working with the Camunda–ServiceNow integration. ## Key terms | Term | Definition | Relevance | | :--------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------- | | Spoke | A scoped ServiceNow application containing Flow Designer content for a specific application or record type. Provides pre-built actions and subflows. | Enables Camunda integration. Custom spokes can handle BPMN process orchestration. | | Application | A self-contained ServiceNow package delivering business capabilities, including tables, UI, logic, data model, and security. | Provides the structure for deploying spokes and plugins used in integrations. | | Plugin | An optional add-on that extends or enhances capabilities of installed applications. | Enables additional integration capabilities, such as Integration Hub packs. | | Connection alias | A named reference in ServiceNow linking credentials and connection details for external system integrations. | Required for secure communication between ServiceNow and Camunda. | | REST API | RESTful web services enabling CRUD operations on ServiceNow tables using standard HTTP methods. | Primary integration method between Camunda and ServiceNow. | | SysID | A unique 32-character GUID identifying each record in a ServiceNow instance. | Essential for tracking and correlating records across Camunda process instances. | | Tables | Database objects in ServiceNow that store data and expose endpoints for CRUD operations via REST APIs. | Critical for data exchange between ServiceNow and Camunda workflows. | | Flows | Automated ServiceNow processes consisting of a trigger and a sequence of reusable actions to orchestrate business logic. | Can be triggered by Camunda processes via REST calls or webhooks. | | Subflows | Reusable sequences of actions that encapsulate common business logic and can be called from multiple flows. | Enable reusable ServiceNow operations that can be called by different Camunda processes. | | Actions | Individual steps within flows that perform operations such as creating records, sending notifications, or calling external APIs. | Can be invoked from Camunda to perform ServiceNow operations. | | Triggers | Event conditions that initiate flow execution, such as record operations, schedules, or external system calls. | Can respond to Camunda process events via REST calls. | --- ## Prerequisites(Servicenow) Ensure you have the following prerequisites before setting up the Camunda–ServiceNow integration. ## Camunda - Camunda 8.6+ - SaaS: Supported out of the box. - Self-managed: May require minor adjustments to the Camunda Spoke in ServiceNow. - User roles: Administrative access to create an API client. ## ServiceNow - ServiceNow version: Yokohama or newer. - Required plugins: - Integration Hub Starter Pack - Integration Hub Action Step – REST - Camunda Spoke: Available on the [ServiceNow Store](https://store.servicenow.com/store/app/aac1b64fc3803290ef46d0af050131d0). - User account: A technical or administrator account with permissions to access target tables and Flow Designer. This account will be used in built-in connectors to authenticate and interact with ServiceNow. - Optional plugins (required only for starting ServiceNow flows from Camunda using the Flow Starter connector): - Integration Hub Enterprise Pack - Integration Hub Flow Trigger – REST --- ## ServiceNow Use Camunda to automate workflows that interact with ServiceNow IT Service Management (ITSM), enabling seamless communication between BPMN processes and ServiceNow workflows. ## Key features The integration enables you to: - Bi-directionally integrate Camunda and ServiceNow to orchestrate flows and processes. - Orchestrate activities inside ServiceNow and across systems end-to-end using BPMN. - Manage records in ServiceNow: create, read, modify, or delete any record directly from Camunda processes. ## Audience This documentation is intended for: - Developers implementing workflows that interact with ServiceNow. - Solution architects designing process automation across Camunda and ServiceNow. - Administrators managing integration configuration and security. ## About the integration Camunda’s ServiceNow integration combines: - Custom actions in the ServiceNow Camunda Spoke to start or correlate Camunda processes from ServiceNow. - Built-in connectors and element templates to interact with ServiceNow tables and flows from Camunda processes. ## Architecture ![Camunda ServiceNow integration architecture](./img/sn-camunda-architecture.png) _This diagram shows how Camunda and ServiceNow interact._ ## Integration features The integration provides bi-directional orchestration using two main components: the Camunda Spoke and built-in connectors. ### Camunda Spoke in ServiceNow | Action | Description | | :---------------- | :--------------------------------------------------------------- | | Start process | Start a Camunda process from ServiceNow. | | Correlate message | Correlate a running Camunda process instance from ServiceNow. | | Send signal | Broadcast BPMN signals to one or many Camunda process instances. | | Cancel process | Cancel a Camunda process instance from ServiceNow when needed. | ### ServiceNow connectors in Camunda | Connector | Description | | :---------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------- | | [ServiceNow Outbound Connector](connectors/outbound-connector.md) | Execute CRUD operations on any ServiceNow table. | | [ServiceNow Flow Starter](connectors/flow-starter.md) | Start a ServiceNow flow from a Camunda process (requires ServiceNow Integration Hub Enterprise Pack). | | [ServiceNow Incident Handler](connectors/incident-handler.md) | Create and manage incidents in ServiceNow directly from a Camunda process. | --- ## Setup & configuration Connect your ServiceNow instance with Camunda to enable end-to-end orchestration and secure data exchange. Install and configure the Camunda Spoke in ServiceNow, set up ServiceNow connectors in Camunda, and configure secure credentials. After setup, you can interact with ServiceNow tables and flows from Camunda and start or control Camunda processes from ServiceNow. ## Configure Camunda 1. Create a [Camunda API credential](../../../components/react-components/create-api-credentials.md) for ServiceNow connectivity. 2. Add and configure the ServiceNow connector templates in your Camunda processes like any other connector. ## Configure ServiceNow ### Install the Camunda Spoke 1. Log in to ServiceNow as an administrator. 2. Navigate to **All → Application Manager**. 3. Search for **Camunda Spoke** and click **Install**. ### Verify installation - **Spoke**: Confirm the "Camunda Spoke" menu appears in the Application Navigator. - **Connection alias**: Go to **Connections & Credentials → Connection & Credential Aliases** and ensure the **Camunda alias** exists and is linked to the Camunda Spoke. ### Create an OAuth profile 1. Navigate to **System OAuth → Application Registry** and click **New**. 2. Select **Connect to a third-party OAuth Provider**. 3. Fill in the form: | Field | Value | | :--------------------- | :------------------------------------------- | | Name | `Camunda OAuth` | | Client ID | Paste Client ID from Camunda | | Client Secret | Paste Client Secret from Camunda | | OAuth API Script | `OAuthCamundaUtil` | | Default Grant Type | `Client Credentials` | | Token URL | `https://login.cloud.camunda.io/oauth/token` | | Refresh Token Lifespan | Default `8,640,000` | 4. Click **Submit**. ### Create a credential record 1. Navigate to **Connections & Credentials → Credentials** and click **New**. 2. Select **OAuth 2.0 Credentials**. 3. Fill in the form: | Field | Value | | :------------------- | :------------------------------ | | Name | `Camunda Connection` | | OAuth Entity Profile | `Camunda OAuth default_profile` | 4. Click **Submit**. ### Create a connection record and link to alias 1. Navigate to **Connections & Credentials → Connection & Credential Aliases** and open the **Camunda alias**. 2. In the **Connections** tab, click **New**. 3. Fill in the form: | Field | Value | | :------------------ | :----------------------------------------------------------------- | | Name | `Camunda API Connection` | | Credential | `Camunda Connection` | | Domain | `Global` | | Connection alias ID | `x_camun_camunda.Camunda` | | URL Builder | Enabled | | Host | Camunda cluster Region ID (e.g., `lhr-1.api.camunda.io`) | | Protocol | `https` | | Base path | Camunda Cluster ID (e.g., `/6b6b3969-a65c-4bdd-905e-c29102eebded`) | 4. Click **Submit**. ### Optional: Configure Flow Starter To start ServiceNow flows from Camunda asynchronously, ensure the following: - ServiceNow Integration Hub Enterprise Pack - ServiceNow Integration Hub Flow Trigger – REST plugin --- ## Troubleshooting Resolve common issues encountered while setting up or using the Camunda–ServiceNow integration and follow recommended actions to fix them. ## ServiceNow | Issue | Possible cause | Recommended action | | ------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Camunda Spoke not visible | Spoke not installed correctly or insufficient permissions. | Confirm the Camunda Spoke is installed from the [ServiceNow Store](https://store.servicenow.com/store/app/aac1b64fc3803290ef46d0af050131d0) and that you are logged in as an administrator. | | Flow doesn’t trigger | Trigger conditions misconfigured or Integration Hub inactive. | Review flow trigger settings and ensure the required Integration Hub plugins are active. | | Authentication failures | OAuth profile misconfigured or invalid Camunda credentials. | Verify the Client ID, Client Secret, and Token URL. Check the OAuth profile settings in ServiceNow. | | Missing required plugins | IntegrationHub packs (e.g., Enterprise Pack) not installed. | Install the required plugins listed in [Prerequisites](./prerequisites.md). | ## Camunda | Issue | Possible cause | Recommended action | | ----------------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | | Connector task fails | Invalid credentials or network connectivity issues. | Confirm Camunda API credentials are valid and that the ServiceNow instance is reachable. | | Variables not mapped correctly | Response fields from ServiceNow are not stored or referenced properly. | Map the entire response object and specific fields explicitly in your BPMN model. | | Process not started from ServiceNow | Correlation keys or message names do not match. | Verify that message names and correlation variables are configured correctly in both ServiceNow and Camunda. | | Timeouts or unexpected errors | Large payloads or network latency. | Check logs, increase timeout thresholds, and test the ServiceNow API call separately. | ## General tips - Enable verbose flow logs in ServiceNow Navigate to **Flow Designer → Your flow name → Flow Reporting Settings** and enable full verbose logging to debug flow execution. - Check Camunda Operate for connector errors Inspect failed connector tasks in [Camunda Operate](/components/operate/operate-introduction.md) for detailed error messages and variable mappings. - Validate network connectivity Ensure outbound calls between Camunda and ServiceNow are not blocked by firewalls, VPNs, or proxies. - Check version compatibility Verify that Camunda and ServiceNow versions meet the [prerequisites](./prerequisites.md). ## Frequently asked questions **Do I need IntegrationHub Enterprise Pack for all connectors?** No. It is only required to start ServiceNow flows from Camunda using the Flow Starter connector. **Why do I get 401 Unauthorized errors from ServiceNow?** This usually indicates a misconfigured OAuth profile. Verify the Client ID, Client Secret, and Token URL. --- ## Slack Camunda for Slack is part of [Camunda app integrations](./app-integrations/app-integrations.md), which connect Camunda to the chat tool your team uses. You can run it without Microsoft Teams. :::important App integrations are released as an [early access](/components/early-access/overview.md) alpha feature to allow you to test and participate in development by sharing feedback before general availability, and are subject to alpha feature limitations. ::: Find the documentation you're looking for: - [Camunda for Slack](./app-integrations/slack.md), the `/camunda` slash command and direct message. - [Notification rules](./app-integrations/notification-rules.md). - [Install](./app-integrations/installation.md). - [Troubleshoot](./app-integrations/troubleshoot.md). This page is not the [Slack connector](/components/connectors/out-of-the-box-connectors/slack.md), which is a separate product that uses your own app registration and credentials in the process model. If your organization uses app integrations, the [App Integrations connector](/components/connectors/out-of-the-box-connectors/app-integrations.md) sends through the app your users already have connected. --- ## Using Camunda 8 Learn how to use Camunda to orchestrate your processes. Orchestrate and automate complex business processes for people, systems, devices, and [AI agents](/reference/glossary.md#ai-agent). Build BPMN processes and DMN decisions using powerful tools offering collaborative modeling, operations, and analytics. [Learn how to build agentic solutions](../guides/build-with-ai/overview.md) ## Introduction to Camunda 8 Learn how Camunda 8 components work together to orchestrate and automate business processes, including people, systems, devices, and AI agents. Introduction to Camunda 8 ## Best Practices Camunda Best Practices distill our experience with BPMN and DMN on the Camunda toolstack, incorporating insights from consulting, community feedback, and various interactions. Camunda Best Practices :::tip Learn about [deciding your stack](/components/best-practices/architecture/deciding-about-your-stack.md), and [sizing your environment](/components/best-practices/architecture/sizing-your-environment.md) to make sure your production deployment is sized appropriately, and can scale without any backpressure issues. ::: ## Features and integrations Get started with selected key features and integrations. ## Camunda components Explore and learn about Camunda components and BPMN, DMN, and FEEL expressions. ## Camunda 8 SaaS Reference information for Camunda 8 SaaS, including clusters, regions, and encryption at rest. Camunda 8 SaaS ## Glossary Explore the glossary and understand definitions for key Camunda 8 terms and abbreviations. Glossary --- ## Identity and access management in Camunda 8 Use identity access control to provide secure access for authorized users and systems in Camunda 8. ## Identity types in Camunda 8 Camunda 8 manages access in two separate places: one for Camunda Hub and Optimize, and one for each Orchestration Cluster. These identities serve different purposes. One controls access to process execution and runtime APIs, while the other controls access to management and modeling components. Orchestration Cluster Admin (formerly Orchestration Cluster Identity) Used for authenticating and authorizing users and systems that interact with the Orchestration Cluster (such as Zeebe, Operate, Tasklist, and the Orchestration Cluster REST API).Admin governs access to process execution, task management, and related runtime resources. Each cluster has its own Admin, and what you configure there applies to that cluster only. Organization access (Camunda Hub and Optimize) Used for managing who can use Camunda Hub and Optimize, and what permissions they have. It is separate from the access you configure in Admin. In SaaS, you manage organization access in Camunda Hub, from the [Organization management view](/components/hub/organization/users-and-roles.md). In Self-Managed, [Management Identity](/self-managed/components/management-identity/overview.md) provides organization access. Management Identity is a Self-Managed-only component. :::note Identity object identifiers and names are limited to **256 characters**. This limit applies independently of the secondary storage backend used by the Orchestration Cluster. ::: ### Find out which identity you are configuring The screen you are working in tells you which identity you are configuring. | You are in | You are configuring | Edition | | :------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------- | | [Organization management](/components/hub/organization/users-and-roles.md) | Organization access: who can use Camunda Hub and Optimize, and their organization roles. | SaaS | | [Management Identity](/self-managed/components/management-identity/overview.md) | Organization access: who can use Camunda Hub and Optimize, and their roles. | Self-Managed | | [Orchestration Cluster Admin](/components/admin/admin-introduction.md) | Access to a deployment environment, its runtime applications (Operate, Tasklist, Admin, and Zeebe), and the Orchestration Cluster REST API. | SaaS and Self-Managed | ## Identity provider (IdP) integration In production setups, you can connect both Orchestration Cluster Admin and organization access to an external IdP (such as Entra ID) for unified user management, single sign-on (SSO), and consistent security policies. See [connect to an identity provider](./connect-to-identity-provider.md). | Identity type | Edition | Description | Default IdP | External IdP support | | :---------------------------------------- | :-------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------- | :-------------------------------------------- | | Orchestration Cluster Admin | SaaS and Self-Managed | Built-in user management with support for external IdP integration via OIDC. Connects to enterprise IdPs such as Microsoft Entra ID, Okta, and more. | Built-in user management | OIDC integration with enterprise IdPs | | Organization access (Camunda Hub) | SaaS | Organization-wide sign-in to Camunda Hub, managed by Camunda. | Auth0 | SAML or Microsoft Entra ID (organization SSO) | | Organization access (Management Identity) | Self-Managed | Uses Keycloak by default, but can be configured with an external IdP via OIDC. | Keycloak | OIDC integration with external IdPs | ## Authentication vs. authorization Authentication and authorization are the two fundamental concepts for access control in Camunda 8. ### Authentication Authentication verifies who a user or client is. For example, you log in with a username and password or through SSO. ### Authorization Authorization determines what an authenticated user or client is allowed to access in Camunda 8 and which actions they can perform on those resources. For example, a user's authorizations allow them to access Operate, view running or completed process instances, start new process instances, or claim and complete user tasks in Tasklist and through the Orchestration Cluster REST API. | Identity type | Authorization model | Description | Management interface | | :---------------------------------------- | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- | | Orchestration Cluster Admin | Fine-grained permissions | Controls access to applications, APIs, and runtime resources through specific permissions for each resource type and action (for example, `PROCESS_DEFINITION` and `USER_TASK`). | Camunda Admin UI or API | | Organization access (Camunda Hub, SaaS) | Role-based access control (RBAC) | Uses predefined organization roles to manage access to Camunda Hub and Optimize. | Camunda Hub: **Organization > Manage organization** | | Organization access (Management Identity) | Role-based access control (RBAC) | Uses predefined roles and permissions for users and groups to manage Camunda Hub and Optimize in Self-Managed. | Management Identity, Keycloak admin console, or external IdP | ### How authentication and authorization work together 1. Authentication happens first: The system verifies identity. 2. Authorization happens next: The system verifies permissions. A user must be both authenticated and authorized to access protected resources. ## Authentication methods Camunda 8 supports multiple authentication methods depending on the environment: | Environment | Authentication method | Notes | | --------------------------------------------------------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------- | | [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) | None / Basic authentication / OIDC | No auth or Basic authentication only for local development. OIDC optional if configured. | | [Docker Compose](/self-managed/quickstart/developer-quickstart/docker-compose.md) | None / Basic authentication / OIDC | No auth or Basic authentication only for local development. OIDC optional if configured. | | [Helm Self-Managed](/self-managed/deployment/helm/install/index.md) | Basic authentication / OIDC | Basic authentication default, OIDC optional if configured. | | SaaS | OIDC | OIDC required for all requests. | - No authentication: only for local development (Run, Docker Compose). - Basic authentication: simple to set up; not recommended for production. - OIDC-based authentication: recommended for production Self-Managed and required for SaaS. For API documentation, link to the centralized authentication overview instead of repeating environment defaults. :::warning The Operate, Tasklist, and Zeebe REST APIs are deprecated and should not be used for new development. While they continue to function, new development should use the Orchestration Cluster REST API by referencing the [Orchestration Cluster REST API migration documentation](/apis-tools/migration-manuals/migrate-to-camunda-api.md). Authentication for these APIs works the same way. See [Orchestration Cluster REST API authentication](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md) for details. ::: ### Users and clients Actions in an orchestration cluster can be executed by two kinds of authenticated entities (also known as principals): users and clients. [Users](/components/admin/user.md) typically interact with the cluster through a browser, while [clients](/components/admin/client.md) interact programmatically through the APIs. Although both principal types can use web UIs and APIs, the distinction still matters. Users represent individuals who are granted access to an orchestration cluster, whereas clients represent systems or applications. :::note If you're using Basic authentication to secure your cluster, both users and clients are treated as users. There is no dedicated client concept in this configuration. ::: Distinguishing between users and clients aligns your access management with how identities are modeled in your identity provider. They are usually authenticated differently (for example, username and password for users versus a client certificate for applications), have different authorization requirements (such as administrator access versus deployment permissions). Separating them simplifies auditing and operational clarity. ## How to obtain tokens For environments using OIDC: 1. Generate a JSON Web Token (JWT). 2. Include the token in each API request as: `Authorization: Bearer `. - [Generate a token (SaaS)](/components/saas/clusters/manage-api-clients.md#create-a-client) - [Generate a token (Self-Managed)](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md) Example request using a token: ```shell curl --header "Authorization: Bearer ${ACCESS_TOKEN}" \ ${BASE_URL}/v2/process-instances/search ``` ### Token expiration Tokens expire according to the `expires_in` field returned by the IdP. After expiration, request a new token. ## Learn more ### Organization access (Camunda Hub and Optimize) - [Manage users and roles in Camunda Hub](/components/hub/organization/users-and-roles.md) - [How identity works in Self-Managed](/self-managed/components/identity/how-identity-works.md) ### Orchestration Cluster authentication and authorization - [Introduction to Admin](/components/admin/admin-introduction.md) - [Set up OIDC-based authentication](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md) - [Orchestration Cluster authorization](./authorizations.md) ### Task access control - [User task authorization](../../tasklist/user-task-authorization.md) --- ## Orchestration Cluster authorization The Orchestration Cluster in Camunda 8 provides a fine-grained authorization system for controlling access to web components and APIs. ## Orchestration Cluster authorization overview This system only applies to the following Orchestration Cluster components: - [Zeebe](../../zeebe/zeebe-overview.md) - [Admin](../../admin/admin-introduction.md) - [Operate](../../operate/operate-introduction.md) - [Tasklist](../../tasklist/introduction-to-tasklist.md) - [Orchestration Cluster APIs](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) :::note These authorizations do not apply to other Camunda services, such as Camunda Hub or Optimize. ::: ## How authorization works The authorization system is built on the principle of least privilege. - When enabled, no access is granted by default, and all permissions must be explicitly assigned. - There are no "deny" rules – if a permission is not explicitly granted, access is denied. This model is enforced across both web components and API requests. ### Owners, resources, and permissions At its core, an authorization grants an owner specific permissions on a resource. For example: - User `john.doe` can be authorized to create new users. - Group `devOps` can be authorized to delete the group `sales`. - Role `processOwner` can be authorized to deploy and run all processes. #### Owners An **owner** is an entity that receives permissions. An authorization can be assigned to any of the following owner types: - User - Group - Role - Client - Mapping rule #### Resources A resource is an object that users interact with and that needs to be secured. Each resource has a unique set of permissions that can be granted. Examples of resources: - Process Definition - Decision Definition - System - User #### Permissions A permission is a specific action that an owner is allowed to perform on a resource. Permissions are unique to each resource type. For example, a `Process Definition` resource has a `CREATE_PROCESS_INSTANCE` permission, while a `User` resource has a `DELETE` permission. ## Available resources The following table lists all resources that support authorization in the Orchestration Cluster (Zeebe, Operate, Tasklist, Orchestration Cluster APIs), as well as the available permissions per resource. | Resource type | Resource key example | Resource key type | Supported permissions | | :--------------------------------- | :----------------------------------------------------------- | :--------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `AUDIT_LOG` | `*`, `ADMIN`, `DEPLOYED_RESOURCES`, `USER_TASKS` | All audit logs / Operation category | `READ` | | `AUTHORIZATION` | `*` | All authorizations | `CREATE`, `DELETE`, `READ`, `UPDATE` | | `BATCH` | `*` | All batches | `CREATE`, `READ`, `UPDATE`, and `CREATE_BATCH_OPERATION_*` ([see batch operation permissions](#batch-operation-permissions)) | | `CLUSTER_VARIABLE` | `*`, `myVariable` | An exact cluster variable name or `*` for all cluster variables. | `CREATE`, `UPDATE`, `DELETE`, `READ` | | `COMPONENT` | `*`, `operate`, `tasklist`, `identity` | All components, component name | `ACCESS` | | `DECISION_DEFINITION` | `*`, `order_decision` | All decisions / Decision ID | `CREATE_DECISION_INSTANCE`, `DELETE_DECISION_INSTANCE`, `READ_DECISION_DEFINITION`, `READ_DECISION_INSTANCE` | | `DECISION_REQUIREMENTS_DEFINITION` | `*`, `order_decision` | All DRDs / DRD ID | `READ` | | `DOCUMENT` | `*` | All Documents | `CREATE`, `READ`, `DELETE` | | `EXPRESSION` | `*` | All expressions | `EVALUATE` | | `GLOBAL_LISTENER` | `*` | Only wildcard permissions are currently supported | `CREATE_TASK_LISTENER`, `DELETE_TASK_LISTENER`, `READ_TASK_LISTENER`, `UPDATE_TASK_LISTENER` | | `GROUP` | `*`, `accounting` | All groups / Group ID | `CREATE`, `DELETE`, `READ`, `UPDATE` | | `MAPPING_RULE` | `*`, `my_mapping` | All mappings / Mapping ID | `CREATE`, `DELETE`, `READ`, `UPDATE` | | `MESSAGE` | `*`, `my_message_name` | All messages / Message Name | `CREATE`, `READ` | | `PROCESS_DEFINITION` | `*`, `order_process` | All processes / BPMN Process ID | `CANCEL_PROCESS_INSTANCE`, `CLAIM_USER_TASK`, `COMPLETE_USER_TASK`, `CREATE_PROCESS_INSTANCE`, `DELETE_PROCESS_INSTANCE`, `MODIFY_PROCESS_INSTANCE`, `READ_PROCESS_DEFINITION`, `READ_PROCESS_INSTANCE`, `READ_USER_TASK`, `SUSPEND_PROCESS_INSTANCE`, `UPDATE_PROCESS_INSTANCE`, `UPDATE_USER_TASK` | | `RESOURCE` | `*`, `my_form`, `order_process` | All resources / Form ID / Process ID | `CREATE` (\* resource ID only), `READ`, `DELETE_DRD`, `DELETE_FORM`, `DELETE_PROCESS`, `DELETE_RESOURCE` | | `ROLE` | `*`, `myrole` | All roles / Role ID | `CREATE`, `DELETE`, `READ`, `UPDATE` | | `SECRET` | `*`, `camunda.secrets.MY_KEY`, `camunda.secrets.db-password` | All secrets / Secret reference | `READ`, `REVEAL` | | `SYSTEM` | `*` | All system operations | `READ`, `READ_JOB_METRIC`, `READ_USAGE_METRIC`, `UPDATE` | | `TENANT` | `*`, `tenantA` | All tenants / Tenant ID | `CREATE`, `DELETE`, `READ`, `UPDATE` | | `USER_TASK` | `assignee` (example) | Task property name used with the `PROPERTY` matcher | `READ`, `UPDATE`, `CLAIM`, `COMPLETE` | | `USER` | `*`, `jane.doe` | All users / Username | `CREATE`, `DELETE`, `READ`, `UPDATE` | ### Batch operation permissions The `BATCH` resource has one `CREATE_BATCH_OPERATION_*` permission for each type of batch operation. Grant the permission for an operation to let a user start a batch that runs it. | Permission | Allows a user to start a batch that will | | :-------------------------------------------------- | :--------------------------------------- | | `CREATE_BATCH_OPERATION_CANCEL_PROCESS_INSTANCE` | Cancel process instances | | `CREATE_BATCH_OPERATION_DELETE_DECISION_DEFINITION` | Delete decision definitions | | `CREATE_BATCH_OPERATION_DELETE_DECISION_INSTANCE` | Delete decision instances | | `CREATE_BATCH_OPERATION_DELETE_PROCESS_DEFINITION` | Delete process definitions | | `CREATE_BATCH_OPERATION_DELETE_PROCESS_INSTANCE` | Delete process instances | | `CREATE_BATCH_OPERATION_MIGRATE_PROCESS_INSTANCE` | Migrate process instances | | `CREATE_BATCH_OPERATION_MODIFY_PROCESS_INSTANCE` | Modify process instances | | `CREATE_BATCH_OPERATION_RESOLVE_INCIDENT` | Resolve incidents | | `CREATE_BATCH_OPERATION_SUSPEND_PROCESS_INSTANCE` | Suspend process instances | ### Secret authorizations don't cover broker-side resolution `SECRET:READ` and `SECRET:REVEAL` gate only the `/v2/secrets` API endpoints (`POST /v2/secrets/list` and `POST /v2/secrets/resolve`, respectively). They don't gate the broker resolving `camunda.secrets.` references for job activation. Any process model may reference any configured secret, and any worker that receives a job with a resolved reference sees the value in plaintext, with no `SECRET` grant involved anywhere in that path. See [secret resolution and job activation](../secret-resolution-and-job-activation.md). The resource key for `SECRET` is the full reference, including the `camunda.secrets.` prefix (for example, `camunda.secrets.MY_KEY`). Only an exact match or `*` is supported, so a prefix such as `camunda.secrets.*` matches nothing. ### User task authorizations User task access in the Orchestration Cluster is controlled using a combination of process-level and task-level permissions. - Process-level permissions are granted on the `Process Definition` resource. - Task-level permissions are granted on the `USER_TASK` resource and typically use property-based access control. For Tasklist-specific behavior, permission evaluation, and recommended configuration, see [User task authorization in Tasklist](../../tasklist/user-task-authorization.md). ### Access to linked forms Access to a process definition or a user task includes access to the form linked to it. Reading a linked form through one of the following endpoints requires only the permission on the process definition or user task. No separate `READ` permission on the `Resource` (form) is needed: - Get process start form (`GET /process-definitions/{processDefinitionKey}/form`) requires `READ_PROCESS_DEFINITION` on the `Process Definition`. - Get user task form (`GET /user-tasks/{userTaskKey}/form`) requires `READ_USER_TASK` on the `Process Definition` or `READ` on the `User Task`. Retrieving a form directly with Get form (`GET /forms/{formKey}`) still requires `READ` on the `Resource` (form). ## Configuration ### SaaS configuration In Camunda 8 SaaS, authorizations can be enabled or disabled per cluster. This setting can be changed by: - Organization admins - Organization owners ### Self-Managed configuration In Self-Managed deployments, you can enable the authorization system using: ```yaml camunda.security.authorizations.enabled: true ``` ```yaml CAMUNDA_SECURITY_AUTHORIZATIONS_ENABLED=true ``` ```yaml orchestration.security.authorizations.enabled=true ``` ### Behavior when authorization is disabled The shared authorization service skips resource-ID and property-based permission checks for every resource type. These checks return an authorized result instead of denying access. ## Security considerations Certain permissions grant powerful capabilities and should be assigned with caution. It is critical to ensure that only trusted users and clients are granted these permissions to maintain the security and integrity of your system. ### `CREATE` permission for the Resource (deployment) Granting `CREATE` permission on the **Resource** is equivalent to allowing remote code execution. When a user deploys a BPMN model, it can contain executable code in script tasks, service tasks, or listeners that will be run by the process engine. Only grant this permission to users and clients who are fully trusted to deploy and execute code in your environment. ### `CREATE`/`UPDATE` permissions for the User The `CREATE` and `UPDATE` permissions for the **User** resource are highly sensitive. When a user's password is set or changed via Admin, there are no security controls enforced, such as password complexity policies. This permission should only be assigned to trusted administrators. ### System access permissions Permissions that control system access are particularly security-sensitive. This includes CRUD operations to the following resources: - System - User - Group - Role - Mapping rule - Tenant These permissions should be strictly limited to trusted system administrators who are responsible for managing user access control. ### `REVEAL` permission for the Secret `READ` and `REVEAL` on the `SECRET` resource are not interchangeable. `READ` lets a caller see that a secret reference exists, for example, in a `POST /v2/secrets/list` response. `REVEAL` lets a caller resolve a reference to its actual value. `REVEAL` is never implied by `READ`, and it is never granted automatically alongside it. In particular, a blanket "`READ` for all resources" grant, like the built-in `readonly-admin` role uses, does not extend to `SECRET:REVEAL`. Of the built-in roles, only `admin` is granted `SECRET:REVEAL` by default; see [Default roles](#default-roles). Because `REVEAL` exposes secret values, grant it only to trusted users and clients. ### Authorization disabled and secret resolution When authorization is disabled (see [Behavior when authorization is disabled](#behavior-when-authorization-is-disabled)), `POST /v2/secrets/resolve` does not enforce `SECRET:REVEAL`, and `POST /v2/secrets/list` does not enforce `SECRET:READ`. Resolve returns values for references the store resolves; list returns the matching references. ### No validation of owner and resource IDs When you create an authorization, the Orchestration Cluster validates the owner depending on the `ownerType`. For `USER`, `ROLE`, `GROUP`, and `MAPPING_RULE`, the owner must exist in Admin. For `CLIENT`, the owner is not validated. The Orchestration Cluster does not validate whether the resource exists when creating an authorization. - This behavior lets you create authorizations for entities outside of the system (for example OIDC users) or for entities that will be created in the future (for example creating process definition authorizations before the process is deployed). - However, you should keep this in mind when setting up new users, groups, roles, and so on, and verify that the ID of the new entity does not accidentally match an existing authorization. ### Process-level vs. task-level task permissions When configuring access to user tasks, keep the following in mind: - Granting `READ_USER_TASK` or `UPDATE_USER_TASK` on `Process Definition` gives broad access to all user tasks for that process definition. - These process‑level permissions override task‑level checks: if a user has the required process‑level permission, the engine does not evaluate `User Task` authorizations for the same operation. For most scenarios: - Assign process‑level task permissions only to trusted roles such as task managers or administrators. - Use `User Task` property‑based authorizations (and the default Task Worker role) to limit regular task workers to tasks where they are assignee, candidate user, or in a candidate group. ### Definition XML covers the entire deployed resource A deployed BPMN or DMN file is a single resource. A file can contain multiple processes or decisions, and the endpoints that return definition XML return the entire file, not just the requested definition. These endpoints include: - `GET /v2/process-definitions/{processDefinitionKey}/xml` - `GET /v2/decision-definitions/{decisionDefinitionKey}/xml` - `GET /v2/decision-requirements/{decisionRequirementsKey}/xml` As a result, a user or client with read access to one process or decision definition in a file can also read the other definitions contained in that file, including their model content. This applies only to the XML. Read access to one definition does not grant any other permissions for the other definitions, such as permission to create or modify their instances. The authorization model for these endpoints varies by resource: - For processes, `READ_PROCESS_DEFINITION` on `PROCESS_DEFINITION` is checked against the requested process. No permission exists for the BPMN file as a whole. - For decisions, `DECISION_REQUIREMENTS_DEFINITION:READ` controls access to a DMN file as a whole. The `GET /v2/decision-requirements/{decisionRequirementsKey}/xml` endpoint requires this permission. The `GET /v2/decision-definitions/{decisionDefinitionKey}/xml` endpoint requires `READ_DECISION_DEFINITION` for the requested decision and returns the XML of the DMN file containing it. If you need to keep the model content of definitions separate, deploy the definitions in separate files (resources) rather than combining them in a single file. ## Default roles Camunda provides predefined roles to simplify access management: | Role ID | Purpose | Typical authorizations | | :------------------- | :-------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **admin** | Full control over all Orchestration Cluster resources and components. | All permissions for all resources: `READ`, `CREATE`, `UPDATE`, `DELETE`, including `ACCESS` to all web components and `REVEAL` on `SECRET`. | | **app-integrations** | Technical role for executing app integration calls. | `READ_PROCESS_DEFINITION` on Process Definition (`*`), `CREATE_PROCESS_INSTANCE`, `READ_PROCESS_INSTANCE`, `UPDATE_PROCESS_INSTANCE` on Process Definition (`*`), `READ_USER_TASK`, `UPDATE_USER_TASK`, `CLAIM_USER_TASK`, `COMPLETE_USER_TASK` on Process Definition (`*`), `CREATE` on Document | | **connectors** | Technical role for executing connector calls. | `READ_PROCESS_DEFINITION` on Process Definition (`*`), `UPDATE_PROCESS_INSTANCE` on Process Definition (`*`), `CREATE` on Message (`*`), `CREATE`, `READ`, and `DELETE` on Document | | **readonly-admin** | Audit-focused users who need read-only access across the cluster. | `READ` for all resources, including `READ_PROCESS_DEFINITION`, `READ_PROCESS_INSTANCE`, `READ_USER_TASK`, etc. Does not include `REVEAL` on `SECRET`. | | **rpa** | Role for RPA workers. | `READ` on Resource (`*`), `UPDATE_PROCESS_INSTANCE` on Process Definition (`*`) | | **task-worker** | Default role for task workers to handle their own user tasks. | Property-based `User Task` authorizations on properties `assignee`, `candidateUsers`, `candidateGroups` with permissions `READ`, `CLAIM`, `COMPLETE` (one authorization per property). | ### Role assignment in SaaS - **admin**: Automatically assigned to organization owner and admin. - **connectors**: Automatically assigned to Connector Runtime in cluster deployment. - **app-integrations**: Automatically assigned to app integration clients in cluster deployment. - **readonly-admin**: Automatically assigned to Camunda Support agents for support cases. ## Common authorization use cases ### Web component access Users need specific permissions to access Orchestration Cluster web components: - UI access: Resource type `Component` and a resource key identifying the component: - `operate` for Operate access - `tasklist` for Tasklist access - `admin` for Admin access - `identity` for Admin access (deprecated - please use `admin` instead) - `*` for access to all components - Without these permissions, users cannot access the components. #### Tasklist authorization model Tasklist uses the Orchestration Cluster authorization model, including process-level permissions on `Process Definition` and task-level authorizations on `USER_TASK` (with property-based access control). For Tasklist-specific behavior and recommended patterns, see [User task authorization in Tasklist](../../tasklist/user-task-authorization.md). :::note Tasklist V1 and its user task access restrictions (based on BPMN assignee, candidate users, and candidate groups) were removed in Camunda 8.10. If you're migrating from an earlier version, configure the appropriate `Process Definition` and `USER_TASK` authorizations to control who can see, claim, and complete tasks. ::: ### Resource-level access This section describes authorization for domain resources (such as process and decision definitions), not access to UI components or APIs. Users need additional permissions to access specific resources within web components: - Process-related: Resource type `Process Definition` - `READ_PROCESS_DEFINITION` to view process models - `CREATE_PROCESS_INSTANCE` to start new processes - `UPDATE_PROCESS_INSTANCE` to update running instances - `MODIFY_PROCESS_INSTANCE` to modify running instances - `CANCEL_PROCESS_INSTANCE` to cancel running instances - `DELETE_PROCESS_INSTANCE` to delete completed instances - `SUSPEND_PROCESS_INSTANCE` to suspend or resume running instances - Decision-related: Resource type `Decision Definition` - `READ_DECISION_DEFINITION` to view DMN models - `CREATE_DECISION_INSTANCE` to execute decisions ### API access When implementing your own integrations (for example, using a Camunda client), consider the following: - Job workers: Resource type `Process Definition` - `UPDATE_PROCESS_INSTANCE` to activate or complete jobs for the targeted process definitions --- ## Connect to an Identity Provider Integrate with an external identity provider (IdP) for single sign-on (SSO), centralized user management, and secure authentication. ## About IdP integration Connecting Camunda 8 to an external IdP allows you to: - Use enterprise authentication (for example, Microsoft EntraID, Okta, Keycloak, Auth0). - Centrally manage users in your IdP. - Enable SSO for Camunda components. - Enforce organization-wide security policies. ## Self-Managed Self-Managed deployments only support external IdP integration using **OpenID Connect (OIDC)** (for example, Keycloak, Auth0, Okta, EntraID via OIDC). You can integrate an IdP with both Admin (for the Orchestration Cluster) and Management Identity (for Camunda Hub and Optimize). - [Connect Orchestration Cluster Admin to an identity provider](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md) - [Connect Management Identity to an identity provider](../../../../self-managed/components/management-identity/configuration/connect-to-an-oidc-provider/) ## SaaS Camunda 8 SaaS supports external IdP integration at two levels: - Organization-wide, using **SAML** or **Microsoft Entra ID (formerly Azure AD)**: [connect to an identity provider](../../saas/organization/external-sso.md). This covers sign-in to Camunda Hub, Console, and Web Modeler. - Per-cluster, using **OAuth 2.0 and OpenID Connect (OIDC)**: [connect an external identity provider](../../saas/clusters/connect-external-identity-provider.md). This covers sign-in to a single Orchestration Cluster (Operate, Tasklist, Admin, and the APIs), alongside Camunda's built-in provider. --- ## Mapping rules(Access-control) Mapping rules are used to dynamically manage access control by [connecting your Identity Provider](connect-to-identity-provider.md) and mapping claims from a JWT access token to [Admin](/components/admin/admin-introduction.md) entities in Camunda 8. ## Mapping rules in SaaS and Self-Managed In Camunda 8 SaaS, the Mapping Rules UI in Orchestration Cluster Admin becomes available once you [connect an external identity provider](/components/saas/clusters/connect-external-identity-provider.md) to a cluster. The mapping rules API works regardless, but without an external identity provider there are no custom claims to map against. In Camunda 8 Self-Managed, configure mapping rules in the following components: - Orchestration Cluster Admin: Manage permissions within an [orchestration cluster](../../orchestration-cluster.md). Use mapping rules to assign users to [user groups](../../admin/group.md) and [roles](../../admin/role.md), grant [authorizations](../../admin/authorization.md), and associate them with specific [tenants](../../admin/tenant.md). - Mapping rules are available for Orchestration Cluster Admin only when using [OIDC-based authentication](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md). They do not apply to other authentication methods, such as Basic authentication. - Management Identity: Manage access to components like [Camunda Hub](/self-managed/components/hub/index.md) and [Optimize](/self-managed/components/optimize/overview.md). Mapping rules in [Management Identity](/self-managed/components/management-identity/overview.md) assign users to roles and tenants, granting access to those components. To learn more, see the [guide on managing mapping rules in Management Identity](/self-managed/components/management-identity/mapping-rules.md). ## How to use mapping rules :::info To use mapping rules, you must be familiar with the structure of the JWT access tokens that your OIDC provider issues to clients. ::: A mapping rule has the following properties: - Claim name: The name of a (nested) claim or a [JSONPath expression](https://www.rfc-editor.org/rfc/rfc9535). - Claim value: The expected value of the claim. The mapping rule takes effect only if this value is present in a JWT access token. Using a mapping rule is a two-step process: 1. _Create the mapping rule_ – Define how Camunda identifies a match between a JWT claim and the rule. 2. _Assign the mapping rule_ – Apply it to a group, role, authorization, or tenant. Assume the following payload of an access token issued by your Identity Provider (IdP): ```json { "sub": "1234567890", "name": "John Doe", "isAdmin": true, "orggroups": ["acct", "finance"], "iat": 1516239022 } ``` To make any user a member of the `admin` role when their `isAdmin` claim is set to `true`, first define a mapping rule as follows: - Claim name: `isAdmin` - Claim value: `true` Then, assign the mapping rule to the `admin` role. To make any member of the organizational group `acct` a member of the Orchestration Cluster group `accounting`, define a mapping rule as follows: - Claim name: `orggroups` - Claim value: `acct` Then, assign the mapping rule to the `accounting` group. :::note In this case, the mapping rule matches against an array of objects. Depending on the JWT structure, a claim value is matched using `equals` or `in` semantics. ::: ## References For more details on configuring mapping rules in a Self-Managed environment, see: - [Manage mapping rules in Orchestration Cluster Admin](../../admin/mapping-rules.md) - [Manage mapping rules in Management Identity](/self-managed/components/management-identity/mapping-rules.md) - [Manage mapping rules via API](/apis-tools/orchestration-cluster-api-rest/specifications/create-mapping-rule.api.mdx) --- ## Batch operations A high-level overview of batch operations in Camunda 8. ## About batch operations If a single process instance encounters an incident, or you need to update the instance for any other reason, you can perform an instance operation. However, if you need to perform the same operation on multiple instances, a batch operation: - Optimizes the performance of operations that need to be performed across many process instances. - Provides insights into the progress of the operation across all affected instances. - Gives you control over operation execution, with the ability to suspend, resume, and cancel operations. A **batch operation** is an operation on a selection, or batch, of process instances. Instead of manually operating on each instance, you can specify filter criteria and automatically identify and process matching instances across your cluster in parallel. The individual operation in the batch applied to a process instance is called a [**batch item**](../zeebe/technical-concepts/batch-operations.md#batch-operation-components). Example use cases include: - Many process instances have encountered a critical bug. - You need to skip an activity across multiple instances. - There was in issue when executing a process that corrupted many or all instances of that process. ## Types Here are the types of available batch operations: | Type | Description | | ------------------------- | -------------------------------------------------------------------------------------------------- | | Resolve incidents | Resolves the [incidents](./incidents.md) associated with a batch of process instances. | | Modify process instances | [Moves](./process-instance-modification.md) a batch of process instances from one node to another. | | Migrate process instances | [Migrates](./process-instance-migration.md) a batch of process instances to a new process version. | | Suspend process instances | [Suspends](./process-instance-suspension.md) a batch of process instances. | | Resume process instances | [Resumes](./process-instance-suspension.md) a batch of suspended process instances. | | Cancel process instances | Cancels a batch of process instances. | | Delete process instances | [Deletes](./process-instance-deletion.md) a batch of process instances. | | Delete decision instances | [Deletes](./decision-instance-deletion.md) a batch of decision instances. | Furthermore, depending on the status of the batch operation, you may be able to suspend, cancel, or resume the operation. :::warning Canceling a batch operation does not rollback any changes that have already been produced. ::: ## Filter process instances for batch operations Batch process instance operations only act on instances in a state the operation supports. The following rules apply to the modify, migrate, resolve incidents, suspend, resume, and cancel operations, but not to the delete operations. | Operation | Required state | | :---------------- | :----------------------- | | Modify | `ACTIVE` | | Migrate | `ACTIVE` | | Resolve incidents | `ACTIVE` | | Suspend | `ACTIVE` | | Resume | `SUSPENDED` | | Cancel | `ACTIVE` and `SUSPENDED` | Your filter only narrows the batch. Camunda narrows your `state` filter with the required state, so the batch never includes instances outside the required state. - A top-level `state` filter that uses `$eq` or `$in` with a state the operation doesn't support is rejected with `400 Bad Request`. - States inside `$or` branches aren't validated. A branch that conflicts with the required state matches no instances. - The `$neq`, `$exists`, and `$like` operators on a top-level `state` are applied as given, and the batch remains limited to the required states. - A `parentProcessInstanceKey` filter is applied as given. Cancel only acts on root instances, so a filter that matches only child instances selects no instances. To resolve a rejected request, remove the conflicting top-level `state` filter or move it into an `$or` branch. ## States A batch operation can have one of the following statuses: | Type | Description | | ------------------- | --------------------------------------------------------------------------------------------------------------------- | | Created | The batch operation was created, but the filter hasn't been used yet to determine the affected process instances. | | Active | The batch operation is actively being processed. | | Completed | All items in the batch operation were processed, regardless of whether the individual operations succeeded or failed. | | Partially completed | The batch operation successfully processed at least one partition and failed to process at least one partition. | | Suspended | The batch operation was temporarily stopped. Suspended batch operations can be resumed. | | Canceled | The batch operation was permanently stopped. Canceled batch operations can't be resumed. | | Failed | The batch operation failed on all partitions. | :::info Learn more about batch partitions in our [implementation overview](../zeebe/technical-concepts/batch-operations.md). ::: ## Authorization When executing a batch operation, there are two sets of permissions involved: - Batch operation permissions. - Item-level, or process definition, permissions. To create a batch operation, you always need both the permission to create batch operations as well as permissions to read process instances and execute specific operations on each targeted process instance. To suspend, resume, or cancel an operation, you only need the relevant batch operation permissions. The system stores authorization claims with the batch operation and uses them throughout its lifecycle. :::info Read more about [authorizations](/components/concepts/access-control/authorizations.md) and [how to create them in the Admin UI](/components/admin/authorization.md). ::: ## Next steps - [Learn how batch operations work](../zeebe/technical-concepts/batch-operations.md). - [Use batch operations in the Camunda 8 web interface](../operate/userguide/selections-operations.md). - Use the batch operations APIs: - [Search batch operations](/apis-tools/orchestration-cluster-api-rest/specifications/search-batch-operations.api.mdx). - [Cancel process instances (batch)](/apis-tools/orchestration-cluster-api-rest/specifications/cancel-process-instances-batch-operation.api.mdx). - [Resolve related incidents (batch)](/apis-tools/orchestration-cluster-api-rest/specifications/resolve-incidents-batch-operation.api.mdx). - [Migrate process instances (batch)](/apis-tools/orchestration-cluster-api-rest/specifications/migrate-process-instances-batch-operation.api.mdx). - [Modify process instances (batch)](/apis-tools/orchestration-cluster-api-rest/specifications/modify-process-instances-batch-operation.api.mdx). - [Delete process instances (batch)](/apis-tools/orchestration-cluster-api-rest/specifications/delete-process-instances-batch-operation.api.mdx). - [Delete decision instances (batch)](/apis-tools/orchestration-cluster-api-rest/specifications/delete-decision-instances-batch-operation.api.mdx). --- ## BPMN, DMN, and FEEL Use Business Process Model and Notation (BPMN), Decision Model and Notation (DMN), and Friendly Enough Expression Language (FEEL) in your process diagrams. ## BPMN BPMN was developed as a graphical notation to represent complex processes. It is maintained by the non-profit The Object Management Group (OMG) and employed by numerous organizations globally. The visual nature of BPMN enables greater collaboration between different teams, particularly within Camunda Hub. Modeling with BPMN ## DMN DMN is a modeling approach owned by an institution called the Object Management Group (OMG), which also operates worldwide standards for BPMN. In DMN, decisions are modeled and executed using a language both business analysts and developers can understand. Model a set of rules within a table, and this will yield a decision to rapidly execute a process using a decision engine like Camunda. Modeling with DMN ## FEEL expressions FEEL is designed to write expressions in a way that is easily understood by both business professionals and developers. In Camunda, FEEL is used to define expressions in the context of BPMN diagrams, DMN diagrams, and Forms. FEEL is specified in the DMN specification of the Object Management Group (OMG). What is FEEL? --- ## Clusters A cluster is the infrastructure that runs Camunda 8. It includes the Orchestration Cluster that automates your processes, and the components that run alongside it, such as connectors and Optimize. In Camunda Hub, a cluster is an administrative unit: the infrastructure that organization admins create, size, and maintain. Teams don't deploy to a cluster directly. They deploy to an [environment](./environments.md) hosted on it. ## Create and manage clusters How you create and manage clusters depends on the edition of Camunda that you use. In SaaS, organization admins create clusters in Camunda Hub. When you [create a cluster](/components/saas/clusters/create-cluster.md), you choose its type, size, region, and version. The type defines the availability and uptime of the cluster, and the size defines its capacity. Organization admins and DevOps users [manage clusters](/components/saas/clusters/manage-cluster.md), for example to rename, resume, update, or resize a cluster. Learn more about [SaaS clusters](/components/saas/clusters.md), including cluster types, sizes, and Free Trial clusters. In Self-Managed, you provision clusters outside Camunda Hub, and you don't create them in Camunda Hub. To show a cluster and its environments in Camunda Hub, add it to the Camunda Hub configuration. Learn more about [clusters in Self-Managed](/components/hub/organization/manage-clusters/index.md) and [Physical Tenants in the Camunda Hub configuration](/self-managed/components/hub/configuration/environments.md#physical-tenants). ## Next steps - Read about [environments](./environments.md), [workspaces](./workspaces.md), and [projects](./projects.md). - Learn how [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md) isolate teams inside a cluster. --- ## Introduction to Camunda 8 Use [Camunda 8](https://camunda.io) to orchestrate and automate complex business processes that include people, AI agents, systems, and devices. ## About Camunda 8 You can deploy Camunda 8 in two ways: - **Camunda 8 SaaS**: A fully managed cloud service for rapid deployment and minimal operational overhead. - **Camunda 8 Self-Managed**: A self-hosted solution for organizations requiring full control over their infrastructure. Camunda 8 combines powerful execution engines for BPMN processes and DMN decisions with tools for collaborative modeling, operations, and analytics. Camunda 8 [components](/components/components-overview.md#camunda-components) work together to form the complete Camunda 8 experience, allowing you to design, automate, and improve your business processes. Camunda 8 separates runtime execution data from analytical and operational data by using distinct storage roles. ### Storage architecture In the diagram above, storage systems appear in two distinct roles: - **Primary storage**: The authoritative store for runtime execution state used by the [Orchestration Cluster](/reference/glossary.md#orchestration-cluster) to execute, recover, and replicate workflows. This includes partition logs and snapshots and is tightly coupled to process execution. See [primary storage](/reference/glossary.md#primary-storage). - **Secondary storage**: Systems used for indexing, search, analytics, operational views, and long-term retention. Data is populated from primary storage and optimized for querying rather than execution. See [secondary storage](/reference/glossary.md#secondary-storage). #### Secondary storage implementations Camunda 8 supports multiple secondary storage backends, depending on the deployment model and configuration: - **Embedded H2**: A bundled secondary storage option for local development and lightweight setups. See [H2](/reference/glossary.md#h2). - **External RDBMS**: A user-managed relational database used as secondary storage in Self-Managed deployments. See [RDBMS](/reference/glossary.md#rdbms). - **Elasticsearch / OpenSearch**: Search-optimized backends commonly used for analytics and operational visibility. See [Elasticsearch/OpenSearch](/reference/glossary.md#elasticsearchopensearch). For deployment and configuration guidance, see the Self-Managed documentation: - [About Self-Managed](/self-managed/about-self-managed.md) - [Helm quick install](/self-managed/deployment/helm/install/quick-install.md) :::info - Want to migrate your Camunda 7 process solutions to run on Camunda 8? See our [Camunda 7 migration guide](/guides/migrating-from-camunda-7/index.md). - Deployment guides for Camunda 8 components are available in the [Self-Managed section](/self-managed/about-self-managed.md). ::: ## Camunda 8 use cases With Camunda 8, you can model, execute, and operate complex business processes from end to end, combining systems, human work, and AI agents. Most real-world automation is distributed. A single business outcome (for example, customer onboarding, claims handling, or order fulfillment) often requires many independently deployed services and external systems. That makes it hard to keep the overall process visible, understand where work is waiting, and recover cleanly when something fails. Camunda provides a process orchestration layer that coordinates these endpoints without forcing you into a tightly-coupled architecture. ### Orchestrate distributed systems Define process and decision logic using BPMN and DMN, then run it on a stateful, event-driven workflow engine designed for long-running, high-volume execution. This gives teams a consistent way to: - Trigger work and correlate events across services. - Handle retries and compensation when a step fails. - Troubleshoot incidents across the full business process, not just within a single service. [Learn about processes](/components/concepts/processes.md) ### Coordinate human work Many processes also require human input, either as a normal step (for example, review or approval) or as a fallback when automation can't proceed. With Camunda, you can model human tasks alongside automated steps, then assign, track, and escalate work so the process keeps moving. For example, if customer onboarding is blocked waiting for a verification task, the process can route to the right person, enforce due dates, and make the delay visible in operations tooling. [Learn about Tasklist](/components/tasklist/introduction-to-tasklist.md) ### Orchestrate AI agents Not every step in a process can be fully predetermined. When the right action depends on judgment, such as interpreting a document, deciding which check to run next, or triaging an exception, you can hand that step to an AI agent, then return to the fixed steps that follow. With Camunda, you can: - Treat agents as process endpoints, just like a microservice or API call, and orchestrate them alongside deterministic steps and human checkpoints. - Build agents directly in Camunda by modeling agent behavior such as planning loops, tool use, and reflection, including short-term and long-term memory and retrieval-augmented generation (RAG). - Apply the same guardrails your process requires, such as role-based access control, compliance boundaries, incident recovery, and audit trails, to agent-driven steps. Because agents run inside the same governed process, their actions are observable and auditable by default. [Learn about agentic orchestration](/components/agentic-orchestration/agentic-orchestration-overview.md) ### Common use cases - Orchestrating microservices across complex integrations. - Modernizing long-running processes that cross legacy systems. - Coordinating human work with automation. - Running AI-assisted steps, such as document interpretation or decision support, inside governed, end-to-end processes. ## What are the core quality attributes of Camunda 8? Camunda 8 is designed to operate on a very large scale. To achieve this, it provides: - **Horizontal scalability** and no dependence on an external database; [Zeebe](/components/zeebe/zeebe-overview.md) (the workflow engine inside Camunda 8) writes data directly to the file system on the same servers where it is deployed. Zeebe enables distribution processing across a cluster of machines to deliver high throughput. - **High availability and fault tolerance** via a pre-configured replication mechanism, ensuring Camunda 8 can recover from machine or software failure with no data loss and minimal downtime, including AI agents, which resume with their conversation state and progress intact. This ensures the system as a whole remains available without requiring manual action. - **Audit trail** as all process-relevant events, including every AI agent decision and tool call, are written to an append-only log, providing an audit trail and a history of the state of a process. - **Reactive publish-subscribe interaction model** which enables microservices that connect to Camunda 8 to maintain a high degree of control and autonomy, including control over processing rates. These properties make Camunda 8 resilient, scalable, and reactive. - **Visual processes modeled in ISO-standard BPMN 2.0** so technical and business stakeholders can collaborate on process design in a widely-used modeling language. - **Language-agnostic client model** makes it possible to build a client in nearly any programming language an organization uses to automate work. - **Operational ease of use** because, as a SaaS provider, we take care of all operational details. ## What are the Camunda 8 components? ### Connectors Connectors communicate with any system or technology, reducing the time it takes to automate and orchestrate business processes. [Outbound connectors](/reference/glossary.md#outbound-connector) trigger events outside of Camunda, while [inbound connectors](/reference/glossary.md#inbound-connector) allow processes running on Camunda to receive messages from external systems. Connectors also serve as the tool layer for AI agents, enabling agents to interact with external systems in a governed, reusable way. Browse connectors in [Camunda Marketplace](https://marketplace.camunda.com/). ### AI agents Build governed [AI agents](/reference/glossary.md#ai-agent) with guardrails so they can solve complex problems with autonomy. Camunda's [agentic BPMN](/components/agentic-orchestration/ai-agents.md) lets teams model deterministic process logic and dynamic agentic behavior, such as reasoning loops, memory, prompts, RAG, and human‑in‑the‑loop boundaries, in one unified, executable model. ### Forms Some automated processes require human contribution and interaction. [Create and implement custom forms](/components/hub/workspace/modeler/modeling/utilize-forms.md) that give work instructions, collect information, and help people make decisions about the tasks they need to complete. ### Tasklist [Tasklist](/components/tasklist/introduction-to-tasklist.md) offers a lightweight, user-friendly interface for human work, tightly integrated with custom forms. It provides an out-of-the-box user interface for tasks so teams can rapidly iterate on process development without having to build a custom frontend application. ### Workflow and decision engine [Zeebe](/components/zeebe/zeebe-overview.md) is a distributed workflow and decision engine that replaces a traditional relational database with an event streaming, message-based architecture. This approach eliminates database bottlenecks, ensures horizontal scalability, and provides built-in resilience. ### Operate With [Operate](/components/operate/operate-introduction.md), teams can monitor running processes, troubleshoot and resolve incidents, and modify and migrate process instances. Trace process flows in real time, investigate failures, modify variables, and resume execution where needed, all within the context of the end-to-end business process. For AI-assisted processes, Operate provides visibility into agent actions and decisions at runtime, enabling teams to detect and resolve unexpected behavior. ### Optimize [Optimize](/components/optimize/what-is-optimize.md) leverages process execution data to continuously [provide actionable insights](/components/optimize/improve-processes-with-optimize.md). Optimize specializes in BPMN-based analysis and can show users exactly what their process model needs for successful execution. ### Camunda Hub [Camunda Hub](/components/hub/index.md) is a unified platform for managing organizational resources and delivering business processes. It's organized into two levels: organization and workspace. - **Organization level**: This is the management and governance layer. Center of excellence teams govern the infrastructure and tooling delivery teams need, including managing users, clusters, environments, a catalog of shared reusable resources, and workspaces. - **Workspace level**: This is the process modeling and delivery layer. Delivery teams design business process and decision models, discover and use approved catalog assets, and deploy projects to the [environments](/components/concepts/environments.md) assigned to their workspace. With this separation, center of excellence teams govern infrastructure and standards at the organization level, while delivery teams work within organizational guardrails to design, test, and deploy business solutions at the workspace level. ### Desktop Modeler [Desktop Modeler](/components/modeler/desktop-modeler/index.md) is a standalone desktop application for modeling business processes. Desktop Modeler gives business users and developers an intuitive way to design fully-executable process and decision models so their intent is clear, structured, and directly usable by developers. At the same time, Desktop Modeler integrates into your preferred IDE and local filesystem for a professional software development setup. ## How does Camunda 8 compare to other solutions? ### Composability Camunda's integrated yet flexible architecture facilitates best-of-breed technology and reuse. Combine Camunda task automation with other tools and custom code for maximum flexibility. Choose SaaS or Self-Managed, and use your preferred cloud, Kubernetes, and identity provider. ### Open architecture Integrate with a variety of applications, systems, and services to scale your architecture. Camunda works with your preferred source control, CI/CD pipelines, and programming languages, while well-documented APIs, SDKs, and polyglot clients enable easy integration and customization. ### IT and business collaboration IT and business stakeholders build and test processes in a shared modeling environment using the shared languages of BPMN and DMN. Reusable process assets such as connectors and AI agents make it easy to onboard new teams. ### Scalability Event streaming avoids database bottlenecks and can scale process throughput in a highly effective way. Camunda's distributed architecture ensures continuity in the case of hardware or network failure and supports replication across global data centers for high availability. ### Intelligence Rather than relying on prompt instructions alone to keep agents in bounds, Camunda enforces guardrails structurally, as explicit steps in the process, so agent behavior stays governed as autonomy increases. Camunda is model- and framework-agnostic: bring your own LLM, and orchestrate agents built with other frameworks alongside agents modeled natively in BPMN, all within the same auditable process. ## Next steps - To request information about Camunda 8 performance and benchmarking, refer to our [Contact](/reference/contact.md) page. - [Introduction to Camunda 8](/guides/introduction-to-camunda-8.md) - [Create a Camunda 8 account](/components/saas/organization/manage-plan/create-account.md) - [Migrate from Camunda 7 to Camunda 8](/guides/migrating-from-camunda-7/index.md) - [Automate a process using BPMN](/components/modeler/bpmn/automating-a-process-using-bpmn.md) - [Build your first AI agent](/guides/getting-started-agentic-orchestration.md) --- ## Conditionals Conditional events allow a process to react automatically when variables match a condition. Instead of waiting for a message or signal, the engine evaluates a FEEL expression over process variables and triggers the event when the expression becomes `true`. Conditional events are similar to [messages](messages.md) and [signals](signals.md), but they are triggered by variable changes instead of external correlation or broadcast. For details on which BPMN event types support conditionals and how to model them, see the [conditional events modeling guide](../modeler/bpmn/conditional-events/conditional-events.md). ## Conditional event triggers When a conditional event scope is activated, the engine creates a subscription. It evaluates the condition whenever relevant variables change within the event’s visible scope. If the condition becomes `true` while the event is active and in scope, the event triggers and the process follows the behavior defined by the underlying BPMN event. The following sections describe how conditional events are evaluated and triggered, including how variable filters affect re-evaluation. The semantics described below apply to conditional events within running process instances. For process-level conditional start events, see [trigger root-level conditional start events via API](#trigger-root-level-conditional-start-events-via-api). ### On scope activation When a scope is activated, the engine evaluates the condition for conditional events in that scope. For example, when an activity with an attached conditional boundary event is activated, the engine evaluates the condition for that boundary event immediately at activation. Consider the following process definition: An interrupting conditional boundary event with condition `x > 10` is attached to the service task A. Assume the process instance starts with variable `x` initialized to `11`. When the start event completes and service task A is activated, the boundary condition evaluates to `true`, and the boundary event triggers immediately. Service task A will be terminated and the created job will be canceled. The process instance will continue with the flow after the boundary event, skipping the service task A. ### On variable changes In addition to scope activation, conditional events can also be triggered when relevant variables change within the event’s visible scope. When a variable changes (for example, it is created or updated), the engine evaluates the condition for any active conditional events in the variable's scope and all child scopes whose condition depends on that variable (see [top-down evaluation](#top-down-evaluation)). Given the following process definition: An event sub-process with a conditional start event is defined with condition `x > 10`. Assume the process instance starts with variable `x` initialized to `5`. The main process starts and executes the service task, while the event sub-process is not triggered because the condition is not satisfied. If the process instance updates variable `x` to `11` (for example, via the [update element instance variables API](../../apis-tools/orchestration-cluster-api-rest/specifications/create-element-instance-variables.api.mdx)), the engine evaluates the condition for the conditional start event in the event sub-process. Since the condition is now `true`, the conditional start event triggers and the event sub-process starts. #### Top-down evaluation When a variable changes, the engine evaluates conditions in a top-down order based on scopes. Starting from the scope where the variable changed, the engine evaluates conditions for any active conditional events in that scope. It then evaluates conditions in child scopes, continuing down the hierarchy. This continues until a triggered event interrupts one of the scopes or there are no more child scopes to evaluate. Given the following process definition: If a variable is set within the scope of the root process instance or sub-process instance, the engine evaluates the sub-process’s conditional boundary event first. If the condition is satisfied, execution is interrupted; otherwise, the engine evaluates the conditional boundary event on the inner service task A and triggers it if its condition is satisfied. #### Scope isolation A variable change can trigger only the conditional events that can see that variable in the current scope or one of its child scopes. Unrelated scope instances are not affected. Given the following process definition: Service task A and service task B are active in parallel branches of the process. If a variable is set in the sub-process instance, then only the conditional boundary event on service task A is evaluated. The boundary event on service task B cannot trigger because the variable is not visible in its scope. See [variable scopes](variables.md#variable-scopes) for more details on variable visibility rules. #### Expression-based evaluation When a conditional event is activated, the engine analyzes its FEEL condition and derives which variables the expression depends on. The subscription is re-evaluated only when one of those referenced variables changes within the event’s visible scope. The following rules apply to condition expressions: - Condition `x > 1` is re-evaluated only when variable `x` changes. - Condition `x > 1 and y < 5` is re-evaluated when either variable `x` or variable `y` changes. - Condition `x.y.z = true` is re-evaluated when variable `x` changes, because the expression depends on the value of `x` (even if it accesses a property of `x`). :::warning Use plain FEEL expressions instead of nested variable access (for example, `x > 1` instead of `x.value > 1`). This helps the engine derive variable dependencies correctly and re-evaluate the condition when relevant variables change. If you use nested variable access, the engine treats the entire variable as a dependency. For example, if the condition is `x.value > 1`, the engine treats `x` as a dependency. The condition is re-evaluated every time `x` changes, even if the change does not affect `value`. This can lead to unintended triggers for non-interrupting conditional events if variable `x` is updated frequently. ::: #### Variable filter semantics Conditional events can define variable filters to limit when the engine re-evaluates the condition. By default, the engine derives the set of variables that can trigger an event from the FEEL expression. The subscription is re-evaluated only when one of those referenced variables changes within the event’s visible scope. See how filters can be defined in the [conditional events modeling guide](../modeler/bpmn/conditional-events/conditional-events.md#variable-filters). Variable filters restrict evaluation based on specific variable change types (for example, `create` or `update`). Variable change type filters apply only to conditional events within a running process instance. They do not apply to root-level conditional start events, because no process instance exists yet. #### Considerations Conditional events enable reactive process behavior, but incorrect modeling can lead to unintended triggers. Keep the following behavior in mind. ##### Single evaluation per state update If a single command (for example, an update variables request or job completion) updates multiple variables and more than one satisfies the condition, the subscription triggers only once. Using the process definition shown earlier (a service task with an interrupting conditional boundary event), assume the boundary condition is `x > 10 or y > 5`. If both variables `x` and `y` are updated in a single request (for example, via the [update element instance variables API](../../apis-tools/orchestration-cluster-api-rest/specifications/create-element-instance-variables.api.mdx)) and both satisfy the condition, the boundary event triggers only once. ##### Input mappings behavior If an activity defines an input mapping that sets a variable satisfying the boundary condition, the conditional boundary event can trigger when the activity is activated. ##### Output mappings behavior If an activity defines an output mapping that sets a variable satisfying the boundary condition, the conditional boundary event does not trigger when the activity completes. The engine removes the boundary subscription when the activity completes. Variables written during completion are applied after the subscription is removed. Using the same process definition shown earlier (a service task with an interrupting conditional boundary event), if service task A completes and sets variable `x` to `11`, the boundary event with condition `x > 10` does not trigger because the subscription has already been removed. ##### Multi-instance behavior The behavior of conditional boundary events depends on where the variable is updated. - With an interrupting conditional boundary event on the multi-instance body, a child instance that updates a variable and satisfies the condition can interrupt the entire multi-instance activity. - With a non-interrupting conditional boundary event on the multi-instance body, each child instance that satisfies the condition triggers its own boundary event without interrupting the multi-instance body. - A local-scope variable update on a multi-instance child triggers the boundary event only for that child instance. Other child instances continue unaffected. - A process-scope variable update that is visible to all multi-instance children triggers boundary events on all active child instances whose conditions evaluate to `true`. ## Trigger root-level conditional start events via API You can evaluate root-level conditional start events by using: - The Orchestration Cluster REST API. See [evaluate root-level conditional start events](../../apis-tools/orchestration-cluster-api-rest/specifications/evaluate-conditionals.api.mdx). - The [Zeebe Gateway](/reference/glossary.md#zeebe-gateway) gRPC API. See [`EvaluateConditional` RPC](../../apis-tools/zeebe-api/gateway-service.md#evaluateconditional-rpc). - Client SDKs. For example, use `newEvaluateConditionalCommand()` in the Java client. When you evaluate conditional start events: - Multiple conditional start events in the same process definition can trigger if their conditions evaluate to `true`, which can start multiple process instances from the same definition. - If you do not specify a `processDefinitionKey`, the engine evaluates all root-level conditional start events across all deployed process definitions and starts instances for those whose conditions evaluate to `true`. These APIs apply only to root-level conditional start events. Conditional events inside running process instances are evaluated automatically when variables change. ### Migration from Camunda 7 In Camunda 7, conditional events use the `camunda:variableName` and `camunda:variableEvents` attributes. In Camunda 8, the FEEL condition expression is the single source of truth for which variables can trigger the event. The `camunda:variableName` attribute is not supported, so the engine derives variable dependencies directly from the FEEL expression. During migration, `camunda:variableEvents` is converted into a `variableEvents` filter configuration where applicable. Camunda 8 supports only `create` and `update`. If a model uses `delete` in Camunda 7, migration maps it to the closest supported behavior. --- ## Decision instance deletion Use decision instance deletion to permanently remove all data associated with a decision evaluation instance. :::warning Deletion is irreversible. Restore deleted data only by restoring a backup of your cluster. ::: - Delete a single decision instance using the [delete decision instance endpoint](/apis-tools/orchestration-cluster-api-rest/specifications/delete-decision-instance.api.mdx). - Delete multiple decision instances using the [delete decision instances endpoint](/apis-tools/orchestration-cluster-api-rest/specifications/delete-decision-instances-batch-operation.api.mdx). ## Eventual consistency Decision instance deletion runs asynchronously. Depending on how many decision instances are deleted, it may take time for the data to be removed and for the decision instance to disappear from Operate. ## Technical details This section explains how decision instance deletion is handled internally to clarify timing and consistency behavior. Deleting one or more decision instances uses [batch operations](./batch-operations.md). The Zeebe engine queries [secondary storage](/self-managed/concepts/secondary-storage/index.md) for decision instances to delete. For each instance found, the engine writes a delete command to the log, which produces a deleted event. Exporters consume the deleted event and write a record to secondary storage marking the decision instance for deletion. An asynchronous scheduled task then deletes all data associated with each marked decision instance. ```mermaid sequenceDiagram V2 API->>+Engine: Delete decision instances with filter Engine->>Engine: Create batch operation Engine->>-V2 API: Batch operation create response Engine->>+Secondary storage: Query decision instances activate Engine Secondary storage->>-Engine: Return decision instance keys loop for each decision instance key Engine->>Engine: Write DELETED event for each decision instance key Engine->>+Exporter: Export DELETED event deactivate Engine Exporter->>-Secondary storage: Mark decision instance for deletion end note over V2 API,Deletion job: Everything below this note happens asynchronously. Deletion job->>+Secondary storage: Retrieve marked decision instance keys activate Deletion job Secondary storage->>-Deletion job: Return decision instance keys loop for each decision instance key Deletion job->>Deletion job: Delete associated data end deactivate Deletion job ``` --- ## Element templates An **element template** extends your modeling toolkit with domain-specific diagram elements, such as service and user tasks. They allow you to customize how a BPMN element is displayed and how it can be configured by process developers. The example below shows how a generic service task can be transformed into a customized user interface that guides users through its configuration: | Without an element template | With an element template | | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | ![Service task without an element template](assets/element-templates/service-task-no-template.svg) | ![Service task with an element template](assets/element-templates/service-task-with-template.svg) | | | | :::tip [Connector templates](../connectors/custom-built-connectors/connector-templates.md) are a specific type of element template. For example, the [AI Agent Sub-process connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess.md) template configures an AI agent's ad-hoc sub-process using this same mechanism. ::: ## Next steps Read more about element templates and how to use them: - [Element templates in Modeler](/components/modeler/element-templates/about-templates.md) - [Using element templates in Camunda Hub](/components/hub/workspace/modeler/element-templates/using-templates.md) - [Using element templates in Desktop Modeler](/components/modeler/desktop-modeler/element-templates/using-templates.md) --- ## Environments An environment is a named deployment target where a team runs its processes in Camunda Hub. For example, a `payments` environment gives the payments team its own isolated deployment target to run production processes. Clusters are the infrastructure underneath. Organization admins manage [clusters](./clusters.md), and teams work with the environments assigned to their [workspace](./workspaces.md). ## Environments and clusters Camunda Hub separates the infrastructure you operate from the place your teams work: | | Cluster | Environment | | :------------ | :----------------------------------------------------------- | :---------------------------------------------------------------------------------------- | | Purpose | Administrative unit. The infrastructure that runs Camunda 8. | Operational unit. The deployment target where a team tests, runs, and operates processes. | | Managed by | Organization admins and DevOps | Assigned to workspaces by organization admins | | Typical tasks | Create, size, update, back up, and secure the cluster. | Deploy a project, test a process, and open Operate, Tasklist, or Admin. | Teams deploy to an environment, not to a cluster. A cluster can host more than one environment, and every environment belongs to exactly one cluster. Learn more about [clusters](./clusters.md). ## How an environment maps to infrastructure An environment is backed by an isolated unit of a cluster. The cluster version determines which unit backs it, and you don't choose it: | Cluster | Backed by | Environments on the cluster | | :---------------------------------- | :-------------------------------------------------------------------------- | :------------------------------------------------------------------------- | | Self-Managed, version 8.10 or later | [Physical Tenant](/self-managed/concepts/multi-tenancy/physical-tenants.md) | One for each Physical Tenant. The `default` Physical Tenant always exists. | | SaaS | [Cluster](./clusters.md) | One environment | | Any cluster before version 8.10 | [Cluster](./clusters.md) | One environment | Camunda Hub derives the name of an environment. An environment backed by a Physical Tenant other than `default` uses the Physical Tenant ID. Any other environment uses the cluster name. Camunda Hub also supports [Logical Tenants](/self-managed/concepts/multi-tenancy/logical-tenants.md) for an environment. If the target of a deployment has [multi-tenancy](/components/concepts/multi-tenancy.md) enabled, you choose a Logical Tenant during the deployment. ## Environment tags An environment carries the tags of the cluster that backs it, for example `dev`, `test`, `stage`, or `prod`. Use tags to filter environments and to see which stage of your development lifecycle an environment serves. The `prod` tag also marks an environment as a production environment. Your organization can require an approved project snapshot before anyone deploys to it. See [project deployment settings](/components/hub/workspace/modeler/modeler-settings.md#project-deployment). ## Who can see and use environments Organization owners and admins assign environments to workspaces. DevOps users can also assign environments to workspaces where they are an editor or workspace admin. An environment can be assigned to more than one workspace, and a workspace can have any number of environments, including none. ### Organization level On the **Environments** page and in the navigation: - **SaaS**: Every user in the organization sees every environment and can open its applications. Only organization owners, admins, DevOps users, and legacy Developers can resume a paused environment. - **Self-Managed**: Organization admins and DevOps users see every environment. Other users see only the environments assigned to workspaces where they are a workspace admin or editor. ### Workspace level Within a workspace, users see only the environments assigned to that workspace, whatever their organization role: | Workspace role | What the role sees | | :---------------------------- | :----------------------------------------- | | Workspace admin or editor | The environments assigned to the workspace | | Workspace viewer or commenter | No environments | Every [project](./projects.md) in a [workspace](./workspaces.md) can use all the environments assigned to the workspace. A project can't reach an environment that its workspace doesn't have. Users can open the applications of every environment they see, but the Orchestration Cluster decides who can deploy to an environment and what they can see and do in its applications. See [access control](/components/concepts/access-control/access-control-overview.md). ## What an environment contains Each environment has its own applications and its own health status: - Operate, Tasklist, and Admin for the processes that run in the environment. - Optimize, where it's configured. - Job workers and their job statistics. Cluster-level settings, such as the cluster size, version, backups, and IP allowlists, stay with the [cluster](/components/concepts/clusters.md). ## Next steps - Learn how to [deploy a project](/components/hub/workspace/manage-projects/deploy-project.md). - Read about [clusters](./clusters.md), [workspaces](./workspaces.md), and [projects](./projects.md). - Read about [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md) and [Logical Tenants](/self-managed/concepts/multi-tenancy/logical-tenants.md). - Look up the term in the [glossary](/reference/glossary.md#environment). --- ## Execution listeners An execution listener (EL) allows users to react to various events in the workflow execution lifecycle by executing custom logic. ## About execution listeners You can use execution listeners to provide flexibility and control over process execution, and handle complex data and external system interactions without cluttering the BPMN model with technical details. ### Use cases Execution listeners are useful in the following typical cases: - Pre- and post-processing actions for activities - External calculations of variables for element expressions - Decoupled processes and data synchronization ### Blocking behavior An execution listener is a blocking operation, meaning that the workflow execution lifecycle only continues once the listener is completed. This ensures that all necessary pre- and post-processing actions defined by the listener are fully executed before the workflow proceeds to the next element. ## Define an execution listener You can configure execution listeners for individual BPMN elements, such as tasks, events, and gateways, as well as for the overall process and subprocesses, including the [ad-hoc sub-process](/reference/glossary.md#ad-hoc-sub-process) that hosts an [AI agent](/reference/glossary.md#ai-agent). There are four types of execution listeners: - **Before all**: Invoked only on the [multi-instance](/components/modeler/bpmn/multi-instance/multi-instance.md) body, _before_ any inner instances are created. Useful for initializing variables such as the `inputCollection`. - **Start**: Invoked _before_ the element is processed. Useful for setting variables or executing preconditions. - **End**: Invoked _after_ the element is processed. Useful for executing cleanup or post-processing tasks. - **Cancel**: Invoked on the process element _when the process instance is terminated_. Useful for cleanup, audit logging, or notifying external systems that the process did not complete. Each listener has three properties: | Property | Description | | :---------- | :----------------------------------------------------------------------------------- | | `eventType` | Specifies when the listener is triggered (`beforeAll`, `start`, `end`, or `cancel`). | | `type` | The name of the job type. | | `retries` | The number of job retries. | :::note If multiple listeners of the same `eventType` (such as multiple start listeners) are defined on the same activity, they are executed sequentially in the order defined in the BPMN model. ::: ## Task headers An execution listener can define an arbitrary number of `taskHeaders`; they are static metadata handed to workers along with the job. The headers can be used as configuration parameters for the worker. The job worker receives the listener headers, as well as the custom headers defined for the BPMN element on which the listener is configured. If the BPMN element and the listener both define the same header key, the listener value is used. ## Implement an execution listener Execution listeners are processed by [job workers](/components/concepts/job-workers.md). - Listeners are based on the same concept of jobs and use the same protocol. - You can implement a handler for an execution listener just as you would for a regular job. See the [job worker documentation](/apis-tools/java-client/job-worker.md) for examples of how to create a job worker and handler that can also process execution listener jobs. :::note [Throwing a BPMN error](/components/best-practices/development/dealing-with-problems-and-exceptions.md#throwing-and-handling-bpmn-errors) for an execution listener's job is not supported. ::: ## Variables in an execution listener Similar to regular job workers, a listener can read variables of the process instance and set new variables by completing the job with variables. The scope of variables and the effect of the job variables depend on the listener event type. ### `beforeAll` listeners `beforeAll` listeners are supported only on the [multi-instance](/components/modeler/bpmn/multi-instance/multi-instance.md) body. They are invoked once per multi-instance body activation, before the `inputCollection` is evaluated and inner instances are created. When a multi-instance activity is entered, the engine processes the body and inner-activity listeners in the following order: 1. Variable input mappings of the multi-instance body are applied, if any. 2. All `beforeAll` body listeners are executed sequentially, in the order defined in the BPMN model. 3. The body's `inputCollection` expression is evaluated. 4. Inner instances are created, sequentially or in parallel, depending on the multi-instance configuration. 5. For each inner instance, the existing [Start listeners](#start-listeners) and [End listeners](#end-listeners) of the inner activity are invoked as usual. You can use variables for the following use cases: | Use case | Description | | :------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | | Dynamic collection | Compute the `inputCollection` just before multi-instance evaluation, based on current process state or external data. | | Resolving identifiers into items | Resolve IDs into concrete items and write them into the collection variable that the multi-instance activity iterates over. | | Pre-initializing shared context | Set helper variables used by multi-instance expressions and the body's `completionCondition`. | ### `start` listeners Start listeners are invoked after applying the variable input mappings, and before subscribing to events, evaluating the element's expressions, and executing the element's behavior. - A start listener can read the process variables and local variables created by the variable input mappings. - If a start listener completes the job with variables, those variables are set as [local variables](/components/concepts/variables.md#local-variables) for the element. Subsequent listeners can access these variables. You can use variables for the following use cases: | Use case | Description | | :------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Gateways | [Inclusive](/components/modeler/bpmn/inclusive-gateways/inclusive-gateways.md), [exclusive](/components/modeler/bpmn/exclusive-gateways/exclusive-gateways.md), [event-based gateways](/components/modeler/bpmn/event-based-gateways/event-based-gateways.md): Use ELs to calculate and set variables that determine the outgoing path from these gateways. The custom logic executed by ELs can evaluate current data and set the necessary variables to guide the process flow correctly. | | Intermediate catch events | [Message events](/components/modeler/bpmn/message-events/message-events.md#intermediate-message-catch-events): Variables set by ELs can be used to define the message correlation key, ensuring the correct message is matched with the event.[Timer events](/components/modeler/bpmn/timer-events/timer-events.md#intermediate-timer-catch-events): ELs can define timer expressions based on the calculated variables, enabling dynamic timer configurations.[Signal events](/components/modeler/bpmn/signal-events/signal-events.md#signal-intermediate-catch-events): Variables can determine the signal name, allowing for flexible signal handling based on the current process state. | ### `end` listeners End listeners are invoked after applying the variable output mappings and before leaving the element. - An end listener can read the process variables, the local variables of the element, and the resulting variables of the output mappings. - If an end listener completes the job with variables, those variables are propagated to the element's parent scope, like variables from the output mappings. Subsequent listeners can access these variables. ### `cancel` listeners Cancel listeners run when a process instance is terminated. They execute sequentially after all child elements have terminated and before the process reaches its final terminated state. - A cancel listener can read the process variables. If a cancel listener completes the job with variables, the variables are merged into the process scope and visible to subsequent cancel listeners. ## Incident recovery During execution listener processing, issues can arise that lead to [incidents](/components/concepts/incidents.md). For example, these incidents can occur due to job execution failures or problems during expression evaluation. ### Job execution failure If an execution listener job fails (for example, if an external service is unavailable), it is retried according to the `retries` property. If all retries are exhausted and the job still fails, the process halts, and an incident is raised. Once the incident is resolved, only the listener with the failed job is retried, allowing the process to resume from the point of failure without re-executing successfully completed listeners. ### Expression evaluation failure Incidents can also occur during the evaluation of an execution listener's properties (for example, due to incorrect variable mapping or expression syntax). If this happens, all listeners of the same event type (`start`, `end`, or `cancel`) that were processed before the failure are re-executed once the issue is resolved, even if they had previously completed successfully. ## Limitations Execution listeners have the following limitations: - **Unsupported elements**: The following elements do not support `start` or `end` listeners due to their processing nature: - Start events (start ELs): Use `start` listeners of process instances or subprocesses to cover the missing `start` listeners for specific start events. - Boundary events (start ELs): Place the start logic in the `start` ELs of the main activity to which the boundary event is attached. - Gateways (end ELs): Use `start` ELs on the element following the gateway to execute the required logic. This allows handling of any post-execution tasks in a dedicated element. - Error end event (end ELs): Place the ELs on the related error catch event. - Compensation boundary events: Place the ELs on the compensation handler. - **`beforeAll`**: Supported only for multi-instance activities. - Earlier versions do not support the `beforeAll` event type and will reject deployments that use it. - **`cancel`**: Supported only on the process element. - Earlier versions do not support the `cancel` event type and will reject deployments that use it. - **Duplicate listeners**: Execution listeners must have unique combinations of `eventType` and `type`. Defining multiple listeners with the same `eventType` and `type` results in a validation error. However, you can define listeners with the same `type` if they use different `eventType` values. - **Interrupting escalation events**: For intermediate throw and end events with an interrupting escalation event, `end` listeners are not executed. The escalation event terminates the element's processing immediately upon activation, bypassing any defined `end` listeners. - **Throwing a BPMN error**: This operation is not supported for execution listener jobs. ## Related resources - [Variables](/components/concepts/variables.md) - [Expressions](/components/concepts/expressions.md) - [Incidents](/components/concepts/incidents.md) - [Job workers (basics)](/components/concepts/job-workers.md) - [Job workers (Java client)](/apis-tools/java-client/job-worker.md) - [Multi-instance](/components/modeler/bpmn/multi-instance/multi-instance.md) --- ## Expressions Expressions can be used to access variables and calculate values dynamically. This is particularly useful when [automating a process using BPMN](/components/modeler/bpmn/automating-a-process-using-bpmn.md), [orchestrating human tasks](../../guides/getting-started-orchestrate-human-tasks.md), or implementing [agentic orchestration](/components/agentic-orchestration/agentic-orchestration-overview.md). Some attributes of BPMN elements _require_ an expression, for example, a [sequence flow condition](/components/modeler/bpmn/exclusive-gateways/exclusive-gateways.md#conditions) on an exclusive gateway. Other attributes can define an expression _optionally_ as an alternative to a static value, for example, a [timer definition](/components/modeler/bpmn/timer-events/timer-events.md#timers) of a timer catch event. ## Expressions vs. static values Some attributes of BPMN elements, like the timer definition of a timer catch event, can be defined in one of two ways: - As an expression (e.g. `= remainingTime`) - As a static value (e.g. `PT2H`) Expressions always start with an **equals sign** (**=**). For example, `= order.amount > 100`. The text following the equal sign is the actual expression. For example, `order.amount > 100` checks if the amount of the order is greater than 100. If the element does not start with the prefix, it is used as a static value. A static value is used either as a string (e.g. job type) or as a number (e.g. job retries). A string value must not be enclosed in quotes. :::note An expression can also define a static value by using literals (e.g. `= "foo"`, `= 21`, `= true`, `= [1,2,3]`, `= {x: 22}`, etc.) ::: ## The expression language An expression is written in **Friendly Enough Expression Language (FEEL)**. FEEL is part of the OMG's **Decision Model and Notation (DMN)** specification. It is designed to have the following properties: - Free of side effects - Simple data model with JSON-like object types: numbers, dates, strings, lists, and contexts - Syntax designed for business professionals and developers - Three-valued logic (true, false, null) Camunda 8 integrates the [FEEL Scala](https://github.com/camunda/feel-scala) engine to evaluate [FEEL expressions](/reference/glossary.md#feel-expression). ## Evaluation timeout Use evaluation timeouts to prevent long-running FEEL expressions from blocking processing. Some FEEL expressions may take a long time to evaluate, especially in the following cases: - Expressions with exponential complexity, such as recursive operations without proper bounds - Expressions that process very large input data sets By default, expression evaluation times out after five seconds. You can [configure this timeout](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#expression). If an expression exceeds the timeout, evaluation is interrupted and an incident is raised for the affected process instance. ## Next steps Read more about FEEL expressions and how to use them on the following pages: - [Data types](/components/modeler/feel/language-guide/feel-data-types.md) - [Expressions and operators](/components/modeler/feel/language-guide/feel-expressions-introduction.md) - [Available built-in functions](/components/modeler/feel/builtin-functions/feel-built-in-functions-introduction.md) - [AI agent functions](/components/modeler/feel/builtin-functions/feel-built-in-functions-ai-agent.md) - [FEEL](/components/modeler/feel/what-is-feel.md) - [DMN specification](https://www.omg.org/spec/DMN/About-DMN/) --- ## Access control for global user task listeners Global user task listeners are managed through the Orchestration Cluster authorization model. This page lists the permissions required to manage listeners through the Orchestration Cluster REST API and Admin UI. ## When you need to configure permissions Configure permissions for global user task listeners if all of the following apply: - [Authorizations are enabled for the cluster](/components/concepts/access-control/authorizations.md#configuration). - You manage global user task listeners through one of the following: - The [Orchestration Cluster API](./configuration.md#configure-via-orchestration-cluster-api), or - The [Admin UI](./configuration.md#configure-via-admin-ui). You do not need additional Orchestration Cluster authorizations when: - Defining listeners via [Unified Configuration](./configuration.md#configure-through-unified-configuration). - You only execute processes that are already affected by global listeners. Execution-time behavior is not guarded by separate permissions. ## Required permissions Global user task listeners use the `GLOBAL_LISTENER` resource type in the Orchestration Cluster authorization model. Only the wildcard resource ID `*` is supported. Authorizations for specific listener IDs are not evaluated. To allow a user, group, role, or client to manage listeners through the Orchestration Cluster API or the Admin UI, grant authorizations on `GLOBAL_LISTENER` with resource ID `*` and the following permissions: | Operation | Required permission | Related API endpoint | | :------------------------------------------- | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | List or search global user task listeners | `READ_TASK_LISTENER` | [Search global user task listeners](/apis-tools/orchestration-cluster-api-rest/specifications/search-global-task-listeners.api.mdx) | | View a single global user task listener | `READ_TASK_LISTENER` | [Get global user task listener](/apis-tools/orchestration-cluster-api-rest/specifications/get-global-task-listener.api.mdx) | | Create a new global user task listener | `CREATE_TASK_LISTENER` | [Create global user task listener](/apis-tools/orchestration-cluster-api-rest/specifications/create-global-task-listener.api.mdx) | | Update an existing global user task listener | `UPDATE_TASK_LISTENER` | [Update global user task listener](/apis-tools/orchestration-cluster-api-rest/specifications/update-global-task-listener.api.mdx) | | Delete an existing global user task listener | `DELETE_TASK_LISTENER` | [Delete global user task listener](/apis-tools/orchestration-cluster-api-rest/specifications/delete-global-task-listener.api.mdx) | --- ## Configure global user task listeners You can configure global user task listeners at the cluster level: - [Through the Unified Configuration](#configure-through-unified-configuration). - [Via the Orchestration Cluster API](#configure-via-orchestration-cluster-api). - [Via the Admin UI](#configure-via-admin-ui), which uses the Orchestration Cluster API. Use the Unified Configuration if: - You want to define a static set of global listeners that are always active in the cluster. - You want to use versioning tools to keep track of configuration changes. Use the Orchestration Cluster API if: - You want to dynamically manage listeners without restarting the cluster. - You want to manage permissions for who can create, update, or delete global listeners through API access control. You can use both methods at the same time. After a cluster restart: - Listeners defined through Unified Configuration are recreated, even if they were deleted through the API before restart. - API-defined listeners remain active in addition to configuration-defined listeners. - If an `id` conflict occurs, the listener from Unified Configuration takes precedence and replaces the API-defined listener. ## Configure through Unified Configuration Configure global user task listeners through [Unified Configuration](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#camundaclusterglobal-listeners) under `camunda.cluster.global-listeners.user-task`. Each listener entry supports the properties in [global listener definition](/components/concepts/global-user-task-listeners.md#global-listener-definition), except `source`, which is automatically set to `CONFIGURATION`. Any provided `source` value is ignored. ### How the configuration is validated If configuration entries are invalid, the system rectifies them instead of failing startup. The following validation rules are applied automatically on startup: - If a listener is missing the required `id`, `type`, or `event-types` properties, it is removed. - If a listener defines invalid event types, those event types are removed. If all event types are invalid, the listener is removed. - Valid event types are: `creating`, `assigning`, `updating`, `completing`, `canceling`, or the special value `all`. Event type matching is case-insensitive, so `Creating` and `creating` are both valid, but `create` is not. - If a listener defines duplicate event types, the duplicates are removed and only one instance of each event type is kept. - If a listener defines both the special `all` value and a normal event type for `event-types`, the configuration is corrected to include only `all`. This ensures the listener catches all events as intended. - If a listener defines invalid retry values (non-numeric, negative, or zero), it is removed. Valid retry values are positive integers. - If a listener defines an invalid `priority`, it is removed. Valid priorities are integers from `0` to `100`. In all these cases, the Orchestration Cluster writes a startup warning that identifies the problem location. Invalid listeners are removed while valid listeners remain active. :::note Listeners defined through the API are not subject to this validation. ::: ### Example configuration The following is an example YAML configuration and environment variables: ```yaml camunda: cluster: global-listeners: user-task: - id: "validation-listener" type: "validate-task" event-types: - creating priority: 70 - id: "audit-listener" type: "audit-generic" event-types: all retries: 5 priority: 50 - id: "notification-listener" type: "notify-assignee" event-types: - assigning - updating - canceling after-non-global: true priority: 30 ``` ```bash CAMUNDA_CLUSTER_GLOBAL_LISTENERS_USER_TASK_0_ID=validation-listener CAMUNDA_CLUSTER_GLOBAL_LISTENERS_USER_TASK_0_TYPE=validate-task CAMUNDA_CLUSTER_GLOBAL_LISTENERS_USER_TASK_0_EVENT_TYPES_0=creating CAMUNDA_CLUSTER_GLOBAL_LISTENERS_USER_TASK_0_PRIORITY=70 CAMUNDA_CLUSTER_GLOBAL_LISTENERS_USER_TASK_1_ID=audit-listener CAMUNDA_CLUSTER_GLOBAL_LISTENERS_USER_TASK_1_TYPE=audit-generic CAMUNDA_CLUSTER_GLOBAL_LISTENERS_USER_TASK_1_EVENT_TYPES_0=all CAMUNDA_CLUSTER_GLOBAL_LISTENERS_USER_TASK_1_RETRIES=5 CAMUNDA_CLUSTER_GLOBAL_LISTENERS_USER_TASK_1_PRIORITY=50 CAMUNDA_CLUSTER_GLOBAL_LISTENERS_USER_TASK_2_ID=notification-listener CAMUNDA_CLUSTER_GLOBAL_LISTENERS_USER_TASK_2_TYPE=notify-assignee CAMUNDA_CLUSTER_GLOBAL_LISTENERS_USER_TASK_2_EVENT_TYPES_0=assigning CAMUNDA_CLUSTER_GLOBAL_LISTENERS_USER_TASK_2_EVENT_TYPES_1=updating CAMUNDA_CLUSTER_GLOBAL_LISTENERS_USER_TASK_2_EVENT_TYPES_2=canceling CAMUNDA_CLUSTER_GLOBAL_LISTENERS_USER_TASK_2_AFTER_NON_GLOBAL=true CAMUNDA_CLUSTER_GLOBAL_LISTENERS_USER_TASK_2_PRIORITY=30 ``` ### Apply changes Configuration changes take effect after you restart the cluster. Use rolling restarts to avoid downtime. After the restart, the new configuration applies to new lifecycle events for both running and new instances, without requiring you to redeploy models. ## Configure via Orchestration Cluster API :::note You need [specific authorizations](./access-control.md) to manage global listeners through the API. Read more about [authorizations](/components/concepts/access-control/authorizations.md) and [how to create them in the Admin UI](/components/admin/authorization.md). ::: The [Orchestration Cluster API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) provides CRUD operations to manage global user task listeners at runtime. This allows you to create, update, and delete listeners without restarting the cluster. When you create or update a listener through the API, provide the properties described in [global listener definition](/components/concepts/global-user-task-listeners.md#global-listener-definition). The `source` property is set automatically to `API`. Changes take effect immediately after the API call for new lifecycle events on running and new instances, without requiring model redeployments or a cluster restart. ## Configure via Admin UI :::tip To learn more about the Admin UI for global user task listeners, see the [dedicated page](/components/admin/global-user-task-listeners.md). ::: You can configure global listeners in the Admin UI through the **Global Task Listeners** tab. You can update and delete listeners in the Admin UI without interacting with the API directly or restarting the cluster. The Admin UI uses the Orchestration Cluster API to manage global user task listeners, so the same considerations apply. ## See also - [Global listener configuration properties](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#camundaclusterglobal-listeners). - [Configure properties through Helm charts](/self-managed/deployment/helm/configure/application-configs.md). - [Orchestration Cluster API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). - [Access control for global user task listeners](./access-control.md). --- ## Global user task listeners(Concepts) Global user task listeners are [user task listeners](/components/concepts/user-task-listeners.md) defined once for all processes in a cluster, instead of individually per [user task](/components/modeler/bpmn/user-tasks/user-tasks.md). ## About global user task listeners Use global listeners to react to user task lifecycle events across all processes without modifying BPMN models. Global listeners are configured at the cluster level and behave like model-level user task listeners, using the same lifecycle events, blocking behavior, deny/correction semantics, payload structure, and incident handling. They are particularly useful for: - Replicating user task changes and context to external systems, such as audit, analytics, or custom Tasklist apps. - Centralizing Service Level Agreements and notifications across all processes. - Enforcing governance rules and validations. For example, pre-completion checks. - Consistently applying due date and priority policies. ## Global listener definition Each listener is defined by the following properties: | Property | Required | Description | | :--------------- | :--------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | Yes | User-provided unique identifier for the listener. This identifier is used to interact with the global listener through API | | `eventTypes` | Yes | List of user task event types that trigger the listener.Supported values: `creating`, `assigning`, `updating`, `completing`, `canceling`.The shorthand `all` value is also available if the listener should react to all lifecycle events. | | `type` | Yes | The name of the job type.Used as a reference to specify which job workers request the respective task listener job. For example, `order-items`. | | `retries` | No | Number of retries for the user task listener job. Defaults to `3` if not set. | | `afterNonGlobal` | No | Boolean indicating whether the listener should run after model-level listeners. Defaults to `false` (runs before model-level listeners). | | `priority` | No | The priority of the listener. Higher priority listeners are executed before lower priority ones. It must be an integer between 0 and 100. Defaults to `50` if not set. | | `source` | No (automatically set) | Indicates how the listener was defined, either through configuration or API.Supported values: `CONFIGURATION` and `API`.This property is automatically set by the system and cannot be modified by users. | You have to use a different `id` for each configured listener, since this property is used to uniquely identify the listener and interact with it through the Orchestration Cluster API, for example, to update or delete the listener. You can use the same `type` value for multiple listeners if they should be handled by the same job workers. The `source` property only distinguishes how the listener was defined, but it has no practical effect in how the global listeners are executed. You can configure global user task listeners in multiple ways, as described in [configure global user task listeners](./global-user-task-listeners/configuration.md): - Through the Unified Configuration. - Via the Orchestration Cluster API. - Via the Admin UI. ## Execution order For a given event on a task instance: - Global listeners run in the order defined by the `priority` property of each listener. - Listeners with a higher priority are executed first. - Listeners with the same priority are sorted by their `id` in lexicographical order to ensure a deterministic execution order. - Model-level listeners run next, in the order defined in the BPMN model. - Global listeners marked with `after-non-global: true` (Unified Configuration) or `afterNonGlobal: true` (API) run after model-level listeners. ## Supported features Global listeners support the same features as model-level user task listeners: - [Blocking behavior](/components/concepts/user-task-listeners.md#blocking-behavior). - [Triggering on a specific lifecycle event](/components/concepts/user-task-listeners.md#trigger-a-user-task-listener). - [Accessing user task data in job workers](/components/concepts/user-task-listeners.md#accessing-user-task-data), in particular: - Task-specific data, such as, assignee, due date, or priority. - Attributes changed by the event, either through an `updating` event or because of corrections done by previous listeners. - Headers defined in the user task model. - [Correcting user task data](/components/concepts/user-task-listeners.md#correcting-user-task-data). - [Denying lifecycle transitions](/components/concepts/user-task-listeners.md#denying-the-lifecycle-transition). - [Incident recovery](/components/concepts/user-task-listeners.md#incident-recovery). Additionally, you can configure a single global listener to be triggered by multiple lifecycle events, possibly all of them. ## Limitations The [same limitations as model-level user task listeners](/components/concepts/user-task-listeners.md#limitations) apply. In addition to the above: - **No tenant-specific configuration**: Configuration is cluster-wide, not per tenant. Payloads include `tenantId` for downstream handling. - **Restart required**: Changes made through Unified Configuration take effect only after a cluster restart. This limitation does not apply when you manage listeners through the Orchestration Cluster API. --- ## Incidents In Camunda 8, an [incident](/reference/glossary.md#incident) represents a problem in process execution. This means a [process instance](/reference/glossary.md#process-instance) is stuck at a particular point and requires user interaction to resolve the problem. Incidents are created in different situations, including the following: - A job is failed and it has no retries left. - A condition doesn't return `true` or `false`. - A timer expression doesn't return the expected type. - A decision can't be evaluated. - A BPMN error is thrown and not caught by an error boundary event or error event subprocess. - A job's secret references cannot be resolved, or their resolved values cannot be injected into the job. :::note Not all errors necessarily lead to incidents. For example, unexpected errors in Zeebe do not always result in incidents. ::: ## Resolving To resolve an incident, complete the following steps: 1. Identify and resolve the problem. 2. Mark the incident as resolved, triggering retry process execution. 3. If the problem still exists, a new incident is created. For a job incident, marking the incident as resolved does not immediately re-check the underlying problem. Camunda checks it again only when a worker next activates the job. If no worker is connected for the job type, the job is not activated. As a result, Camunda does not raise a new incident even if the problem still exists, and the process instance can appear healthy in Operate. Keep a worker connected for the affected job type so Camunda can raise the incident again promptly if the cause is not fixed. ### Resolving a job-related incident If a job fails and has no retries remaining, an incident is created. There are many different reasons why the job may have failed. For example, the variables may not be in the expected format, or a service is not available (e.g. a database). If the variables are causing the incident, complete the following steps: 1. Update the variables of the process instance. 2. Increase the remaining retries of the job. 3. Mark the incident as resolved. :::note It's recommended you complete these operations in [Operate](/components/operate/operate-introduction.md). ::: It is also possible to complete these steps via the [client API](/apis-tools/working-with-apis-tools.md). Using the Java client, this could look like the following: ```java client.newSetVariablesCommand(incident.getElementInstanceKey()) .variables(NEW_PAYLOAD) .send() .join(); client.newUpdateRetriesCommand(incident.getJobKey()) .retries(3) .send() .join(); client.newResolveIncidentCommand(incident.getKey()) .send() .join(); ``` When the incident is resolved, the job can be activated by a worker again. ### Resolving secret resolution incidents A job that references secrets can raise one of the following incidents: - `SECRET_RESOLUTION_ERROR` when the secret store cannot return a value or Camunda cannot inject the resolved value into the job. - `MESSAGE_SIZE_EXCEEDED` when the resolved values make the job too large to activate. For diagnosis steps and details about how each incident affects the job, see [Troubleshoot secret resolution failures](secret-resolution-incidents.md). ### Resolving a process instance-related incident If an incident is created during process execution and it's not related to a job, the incident is usually related to the variables of the process instance. For example, a condition expression doesn't return a boolean value. To resolve the incident, update the variables and mark the incident as resolved. :::note It's recommended you complete these operations in [Operate](/components/operate/operate-introduction.md). ::: Using the Java client, this could look like the following: ```java client.newSetVariablesCommand(incident.getElementInstanceKey()) .variables(NEW_VARIABLES) .send() .join(); client.newResolveIncidentCommand(incident.getKey()) .send() .join(); ``` When the incident is resolved, the process instance continues. --- ## Job workers(Concepts) A [job worker](/reference/glossary.md#job-worker) is a service capable of performing a particular task in a process. Each time that task needs to be performed, it is represented by a [job](/reference/glossary.md#job). For example, [AI agent](/reference/glossary.md#ai-agent) tool calls use this mechanism. Each activity inside an [ad-hoc sub-process](/reference/glossary.md#ad-hoc-sub-process) acts as a tool and is executed as a job, like any other task in the process. A job has the following properties: - **Type**: Describes the work item and is defined in each task in the process. The type is referenced by workers to request the jobs they are able to perform. :::important This is a case-sensitive field, if supported by the underlying operating system. For example, `orderProcess` refers to a different worker than `OrderProcess`. ::: :::note Job worker types are subject to backend-dependent length limits: up to **32,768 characters** with Elasticsearch/OpenSearch-backed secondary storage and up to **256 characters** with RDBMS-backed secondary storage. If you use RDBMS, or might migrate to it later, keep job types within the 256-character limit. ::: - **Custom headers**: Additional static metadata that is defined in the process. Custom headers are used to configure reusable job workers (e.g. a `notify Slack` worker might read out the Slack channel from its header.) - **Key**: Unique key to identify a job. The key is used to hand in the results of a job execution, or to report failures during job execution. - **Variables**: The contextual/business data of the process instance required by the worker to do its work. - **Tags**: Immutable labels copied from the process instance at job creation. This is great for providing additional metadata (e.g., `reference:1234`, `team:accounting`, `trace-id:3004`). See [tags](#tags) and [process instance creation tags](/components/concepts/process-instance-creation.md#tags). ## Requesting jobs Job workers request jobs of a certain type on a regular interval (i.e. polling). This interval and the number of jobs requested are configurable in the [Zeebe client](/apis-tools/working-with-apis-tools.md). If one or more jobs of the requested type are available, [Zeebe](/components/zeebe/zeebe-overview.md) (the workflow engine inside Camunda 8) streams activated jobs to the worker. Upon receiving jobs, a worker performs them and sends back a `complete` or `fail` command for each job, depending on if the job could be completed successfully. For example, the following process might generate three different types of jobs: `process-payment`, `fetch-items`, and `ship-parcel`: ![order-process-model](assets/order-process.png) Three different job workers, one for each job type, could request jobs from Zeebe: ![zeebe-job-workers-requesting-jobs](assets/zeebe-job-workers-graphic.png) Many workers can request the same job type to scale up processing. In this scenario, Zeebe ensures each job is sent to only one of the workers. Such a job is considered activated until the job is completed, failed, or the job activation times out. If a job's variables contain secret references, the job is handed out only once those references have been resolved. A job that is still waiting is not returned by the request and does not count against **MaxJobsToActivate**, so jobs behind it are still activated, and it becomes available again on its own once resolution completes. See [secret resolution and job activation](secret-resolution-and-job-activation.md). On requesting jobs, the following properties can be set: - **Worker**: The identifier of the worker used for auditing purposes. - **Timeout**: The time a job is assigned to the worker. If a job is not completed within this time, it can be reassigned by Zeebe to another worker. - **MaxJobsToActivate**: The maximum number of jobs which should be activated by this request. - **FetchVariables**: A list of required variable names. If the list is empty, all variables of the process instance are requested. ### Long polling Ordinarily, a request for jobs can be completed immediately when no jobs are available. To find a job to work on, the worker must poll again for available jobs. This leads to workers repeatedly sending requests until a job is available. This is expensive in terms of resource usage, because both the worker and the server are performing a lot of unproductive work. Zeebe supports **long polling** for available jobs to better utilize resources. With **long polling**, a request is kept open while no jobs are available. The request is completed when at least one job becomes available. **Long polling** is set during [job activation with the parameter `request-timeout`](../../apis-tools/zeebe-api/gateway-service.md#activatejobs-rpc). ### Job queuing Zeebe decouples creation of jobs from performing the work on them. It is always possible to create jobs at the highest possible rate, regardless if there is a job worker available to work on them. This is possible because Zeebe queues jobs until workers request them. This increases the resilience of the overall system. Camunda 8 is highly available so job workers don't have to be highly available. Zeebe queues all jobs during any job worker outages, and progress resumes as soon as workers are available. This also insulates job workers against sudden bursts in traffic. Because workers request jobs, they have full control over the rate at which they take on new jobs. ## Completing or failing jobs After working on an activated job, a job worker informs Camunda 8 that the job has either `completed` or `failed`. - When the job worker completes its work, it sends a `complete job` command along with any variables, which in turn is merged into the process instance. This is how the job worker exposes the results of its work. - If the job worker can not successfully complete its work, it sends a `fail job` command. Fail job commands include the number of remaining retries, which is set by the job worker. - If `remaining retries` is greater than zero, the job is retried and reassigned. - If `remaining retries` is zero or negative, an [incident](/components/concepts/incidents.md) is raised and the job is not retried until the incident is resolved. When failing a job it is possible to specify a `retry back off`. This back off allows waiting for a specified amount of time before retrying the job. This could be useful when a job worker communicates with an external system. If the external system is down, immediately retrying the job will not work. This will result in an incident when the retries run out. Using the `retry back off` delays the retry. This allows the external system some time to recover. If no `retry back off` the job is immediately retried. When completing or failing jobs with [variables](components/concepts/variables.md), the variables are merged into the process at the job's associated task: - When `Completing a job` the variables are propagated from the scope of the task to its higher scopes. - When `Failing a job` the variables are only created in the local scope of the task. :::tip Failing a job with variables There are several advantages when failing a job with variables. Consider the following use cases: - You can fail a job and raise an incident by setting the job `retries` to zero. In this case, it would be useful to provide some additional details through a variable when the incident is analyzed. - If your job worker can split the job into smaller pieces and finish some but not all of these, it can fail the job with variables indicating which parts of the job were successfully finished which weren't. Such a job should be failed with a positive number of retries so another job worker can pick it up again and continue where the other job worker left off. The job can be completed when all parts are finished by a job worker successfully. ::: ### Using job result Job workers can provide a **job result** for [user task listeners](components/concepts/user-task-listeners.md). Job results are used to define: 1. **Corrections**: Updates to specific user task attributes, such as assignee, due date, follow-up date, candidate users, candidate groups, and priority, before the task transition is finalized. For more details, see [correcting user task data](components/concepts/user-task-listeners.md#correcting-user-task-data). 2. **Denial**: Indicates that the lifecycle transition should be explicitly denied. Denying the task lifecycle transition rolls back the user task to the previous state, and discards any corrections made by previous listeners. For more details, see [denying the operation](components/concepts/user-task-listeners.md#denying-the-operation). Below is an example of using job result: ```java final JobHandler userTaskListenerHandler = (jobClient, job) -> { boolean shouldDeny = someValidationLogic(job); jobClient .newCompleteCommand(job) // highlight-start .withResult( r -> r.forUserTask() .correctAssignee("john_doe") .correctPriority(42) .deny(shouldDeny)) // deny based on validation logic // highlight-end .send(); }; ``` If both corrections and denial are provided in the same job result (for example, `.correctAssignee(...)` and `.deny(true)`), the job completion command will be rejected. To avoid this, ensure the job is either completed with corrections (without denial set to `true`) or denied (without corrections). :::info The `corrections` and `deny` features are currently available only for user task listener jobs. ::: ## Timeouts If the job is not completed or failed within the configured job activation timeout, Zeebe reassigns the job to another job worker. This does not affect the number of `remaining retries`. A timeout may lead to two different workers working on the same job, possibly at the same time. If this occurs, only one worker successfully completes the job. The other `complete job` command is rejected with a `NOT FOUND` error. The fact that jobs may be worked on more than once means that Zeebe is an "at least once" system with respect to job delivery and that worker code must be idempotent. In other words, workers **must** deal with jobs in a way that allows the code to be executed more than once for the same job, all while preserving the expected application state. ### Timeout update When a job worker activates a job it can specify a timeout for how long the job should remain activated. For example, this timeout can be updated in the following scenarios: - When there are jobs which have an elastic timespan. They can potentially run for five minutes, but can be also 24+ hours. That can cause a problem when the workers picking up the jobs do not know in advance how long the given process takes, thus they can't accurately estimate a timeout. - In case of a long-running job. The scenario can occur where there is a problem with the job worker, but the task will be unavailable until the timeout is reached. In the scenarios described above, job timeout can be dynamically extended or shortened using `UpdateJobTimeout` gRPC command. This command takes a duration. This is not the duration with which the timeout will be extended or shortened. Instead, this will be the new duration the timeout is set to from the current time. This allows to not only extend the timeout of a job, but also to shorten the timeout. That means the worker does not need to estimate job timeout accurately at the very beginning. It can use some “standard” initial value and then extend or shorten the timeout as necessary. A job worker should not wait until the last second to update a job timeout as some time might be needed to process the update and there is a chance that in between the job could already time out. A buffer should be applied to avoid this issue. Job timeout can be updated [using the `UpdateJobTimeout` command](../../apis-tools/zeebe-api/gateway-service.md#updatejobtimeout-rpc). ## Job streaming It's also possible to use job workers in a streaming fashion, such that jobs are automatically activated and pushed downstream to workers without requiring an extra round of polling, which greatly cuts down on overall latency. ### How it works Job streaming works by having the worker open a long living gRPC unidirectional stream from the client to the gateway. The gateway then aggregates logically equivalent streams and registers each of these aggregated streams to every broker. :::note Two streams are considered logically equivalent if they would both activate the same job in the exact same way. More concretely, this means if they: - Target the same job type - Have the same worker name - Have the same job activation timeout - Have the same fetch variables ::: On the broker side, whenever a job is made activate-able (e.g. a service task is activated, a job failed and is retried, etc.), if there is one or more streams for this job type, a random one is picked, the job is activated and pushed to it. As the job makes its way back to the gateway that owns this stream, a random client associated with it is picked, and the job is forwarded to it. :::note The RNG used to randomly pick streams and clients provides a good uniform distribution for the same underlying set, which is a cheap way of evenly distributing the load _as long as the stream set remains stable_. ::: Job leasing also applies to streaming: a leased job only matches streams opened with a lease request, and streams opened without one only match unleased jobs. See [job leasing](#job-leasing) for details. If a job contains unresolved secret references, the broker requests resolution before pushing the job. Once the references resolve, the broker pushes the job. See [secret resolution and job activation](secret-resolution-and-job-activation.md). To help visualize the process in general, here is a sequence diagram which shows a single worker opening a job stream for jobs of type "foo" against a cluster consisting of a single gateway and a single broker. It receives some jobs, and when it closes, one job that was pushed asynchronously is returned to the broker: ![Sample Sequence Diagram](assets/job-push-sequence.png) ### How job streaming and polling deliver jobs Job streaming and polling are separate delivery paths, not two ways of draining the same queue. Zeebe queues any job that has no registered stream for its type in an internal backlog (the `ACTIVATABLE` state), and long polling (via the [`ActivateJobs` RPC](../../apis-tools/zeebe-api/gateway-service.md#activatejobs-rpc)) is the only path that serves this backlog. A pushed job bypasses the backlog only on its first delivery attempt: as soon as the job becomes `ACTIVATABLE`, Zeebe pushes it directly to a registered stream for its type, if one exists. If the push fails or the job times out before completion, the job returns to the `ACTIVATABLE` backlog like any other job. The following diagram shows both delivery paths, from the broker through the gateway to the worker’s job capacity: ```mermaid flowchart TB created["Job becomes activate-able"] subgraph broker["Broker"] created backlog[["ACTIVATABLE backlog"]] end subgraph gateway["Gateway"] pushFwd["Push forwarding"] pollFwd["Poll forwarding(ActivateJobs)"] end subgraph worker["Worker"] capacity(("Job capacity(worker-defined limit)")) end created -- "no stream registered" --> backlog created -- "stream registered: pushed immediately" --> pushFwd pushFwd --> capacity backlog -- "drained only by polling" --> pollFwd pollFwd --> capacity ``` ### Backpressure To avoid workers overloaded with too many jobs, e.g. running out of memory: - The workers rely on the [built-in gRPC flow control mechanism](https://grpc.io/docs/guides/flow-control/). - Or, if lacking for your language of choice, [the built-in HTTP/2 stream flow control](https://httpwg.org/specs/rfc7540.html#FlowControl), e.g. Golang implementation of gRPC. Essentially, as jobs are pushed downstream from the broker to the client, they're first buffered in the gateway where the direct client connection resides. The gateway only sends as much data as the client can consume over a specific connection. If it notices its send buffers fill up, it marks a client as `not-ready`. This can happen, for example, if the client's receive method is blocked/suspended. If a client is not ready to receive a job, the gateway instead tries to re-route the job to another logically equivalent worker. If this fails (e.g. all workers connected to a specific gateway are not ready), the job is returned to the broker. There, it may be retried to another gateway, if and only if it has a logically equivalent worker. #### Implementing backpressure If you're using the raw `StreamActivatedJobs` RPC, or want to add support for this to your client of choice, the criteria to apply backpressure is to stall the underlying HTTP/2 transport. To do so, you may need to block the thread in which the gRPC stream is running (e.g. Java), or suspend the coroutine (e.g. Kotlin, Go). Once the transport stops receiving, this causes the gateway's send buffers to fill up, and effectively apply backpressure. If you wish to test this, you can do so by simulating a very slow worker with your new implementation. Then, start generating many jobs on the server side (e.g. create many process instances with lots of jobs). You should then observe backpressure via server side metrics, or many `Job.YIELD` commands written to the log. Refer to the [client implementations](https://github.com/camunda/camunda/tree/main/clients) for more information. #### Detecting backpressure There are different ways to detect backpressure. On the client side, you can use the job worker metrics to do so. For example, by subtracting the rate of handled jobs (i.e. `camunda.client.worker.job.handled`) from the rate of activated jobs (i.e. `camunda.client.worker.job.activated`), you can estimate the rate of queued jobs. If this is too close to the `maxJobsActive` consistently, this may indicate you need to scale your worker deployment. :::warning Deprecated metrics The following metrics are deprecated and will be removed in version 8.10: - `zeebe.client.worker.job.activated`, replace with `camunda.client.worker.job.activated` - `zeebe.client.worker.job.handled`, replace with `camunda.client.worker.job.handled` Please update your monitoring integrations to use the new metrics before upgrading to version 8.10. ::: :::note If you're using Prometheus, you can use the following query to estimate the queue size of your workers. We recommend adding a job type label to be able to group per workload, e.g. `sum(zeebe_client_worker_job_activated_total - zeebe_client_worker_job_handled_total) by (jobType)` ::: On the server side (e.g. if you're running a self-managed cluster), you can measure the rate of jobs which are not pushed due to clients which are not ready via the metric `zeebe_broker_jobs_push_fail_try_count_total{code="BLOCKED"}`. If the rate of this metric is high for a sustained amount of time, it may be a good indicator that you need to scale your workers. Unfortunately, on the server side we don't differentiate between clients, so this metric doesn't tell you which worker deployment needs to be scaled. We thus recommend using client metrics whenever possible. ### Proxying If you're using a reverse proxy or a load balancer between your worker and your gateway, you may need to configure additional parameters to ensure the job stream is not closed unexpectedly with an error. If you observe regular 504 timeouts, read our guide on [job streaming](../../../self-managed/components/orchestration-cluster/zeebe/zeebe-gateway/job-streaming). ### Troubleshooting Since this feature requires a good amount of coordination between various components over the network, we've built in some tools to help monitor the health of the job streams. #### Metrics We expose several metrics which help check whether the feature is working. - `zeebe_gateway_job_stream_push_total`: Allows you to derive the rate at which a gateway is pushing jobs out to clients. If this is much less than the broker's pushed count, this could indicate an issue between the broker and the gateway: jobs are getting lost, the gateway is overloaded, etc. - `zeebe_gateway_job_stream_clients`: The number of open job stream client calls on the gateway. This can help you figure out if your workers are well load balanced across your gateways. - `zeebe_gateway_job_stream_streams`: The number of aggregated streams per gateway. - `zeebe_gateway_job_stream_servers`: The amount of known brokers (or stream servers) per gateway. This should always be the number of brokers in your cluster. If this is less, this could indicate a clustering issue between your gateways and brokers (e.g. temporary network partition). - `zeebe_gateway_job_stream_push_fail_try_total`: The count of failed push attempts. This includes pushes which eventually succeeded (e.g. tried worker `A`, failed, rerouted to worker `B` which succeeded), and as such may be higher than the total number of pushes. This can be useful to narrow down on which gateways errors are coming from, or if a gateway has a higher number of faulty or blocked clients. - `zeebe_broker_jobs_pushed_count_total`: Allows you to derive the rate at which a broker is pushing jobs out to all streams. This can help you figure out if the broker is the bottleneck when it comes to throughput. - `zeebe_broker_open_job_stream_count`: The count of job streams registered on the broker. This should be the sum of all gateway aggregated streams. - `zeebe_broker_jobs_push_fail_try_count_total`: The count of failed job push attempts registered by a given broker. This includes pushes which eventually succeeded (e.g. tried all workers on gateway A, failed, then rerouted to gateway B where it succeeded), and as such may be higher than the total number of pushes. It's useful to detect if a specific gateway is producing errors which may otherwise be hidden by other gateways picking up the slack. #### Actuator endpoint Each broker and gateway exposes a new actuator endpoint - `/actuator/jobstreams` - accessible via the monitoring port. For example, if you run Zeebe locally with the default monitoring port, it would be accessible under `localhost:9600/actuator/jobstreams`. This returns the current view of the registered job streams, where `client` refers to client streams opened on the gateway, and `remote` refers to the aggregated gateway streams as opened on each broker. For example, if jobs of a given type are not activated, but a worker is opened for this type, you can verify first if it exists in one of the gateways as a client stream. Once you've found it, grab its ID, and verify that you can find it as a consumer of a remote stream on each broker. If it's not present in the gateway as a client stream, restart your worker. If it's not present as a consumer in one of the brokers, this indicates a bug. As a workaround, restart your gateway, which will cause some interruption in your service, but will force all streams for this gateway to be recreated properly. ## Job prioritization Use job prioritization when you have mixed-urgency workloads and want time-sensitive jobs to be activated ahead of lower-priority ones. Camunda supports job prioritization for pull-based job activation. Job priority affects the order in which jobs are activated for workers. It does not change retries, failures, or completion semantics. Priority ordering is automatic. No worker-level flag or configuration is required — once a job has a priority set, Zeebe activates higher-priority jobs first when a job pull method is used. You can define job priority: - On the process as a default. - On supported job-creating tasks as an override, including service tasks, send tasks, and script or business rule tasks implemented as job workers. ### Process-level priority To set a default priority for all jobs created by a process, add `zeebe:jobPriorityDefinition` to the process `extensionElements`: ```xml ... ``` A task-level priority definition overrides this process-level default. ### Task-level priority Job priority is enforced per partition, not globally across the cluster. Within a partition, higher-priority jobs are activated before lower-priority ones. Within the same priority level, jobs activate in FIFO order. If you don't set any priorities, standard FIFO behavior applies throughout. The default priority is 0. Be aware that low-priority jobs can starve if workers are consistently occupied by higher-priority work. Camunda doesn't provide starvation mitigation, fairness guarantees, or priority aging. If starvation becomes a problem, increase worker capacity or revise your priority assignments. Priority values can be static integers or FEEL expressions. FEEL expressions are evaluated when the job is created, and the resulting integer is stored on the job. Priority accepts any signed 32-bit integer. The engine does not enforce a fixed upper or lower bound such as `0-99`. Jobs created before version 8.10 do not have a stored priority value. For job execution, Camunda treats them as having the default priority of 0. :::warning If you use job APIs that filter or sort by priority, pre-8.10 jobs are excluded from results because no priority value is stored for them. Review any priority-based queries before upgrading. ::: ### Validation and runtime failure behavior Invalid FEEL expressions are handled differently depending on when they fail: - If the expression is invalid at deployment time, BPMN deployment fails and the process is not deployed. - If the expression fails at runtime, for example because a referenced variable is missing, Camunda raises an incident and the job is not created. To recover, provide the missing variable value and resolve the incident manually in Operate. ### Job streaming limitation Job streaming activates jobs as they are created and ignores priorities. If you use job pulling and job streaming together for the same job type, this negates the benefits of job prioritization. ## Tags Tags provide a powerful way to add lightweight metadata to jobs. ### How tags work with jobs When a BPMN element is activated and creates a job: 1. **Snapshot creation**: The job receives a copy of all tags from the process instance at that exact moment. 2. **Immutability**: Once copied to the job, tags cannot be modified, added, or removed. 3. **Worker access**: Job workers can read these tags to implement custom logic. ### Key characteristics - **Case-sensitive**: Tags `Priority:High` and `priority:high` are different. - **Timing**: Tags are copied exactly once when the job is created from the BPMN element. - **Immutable**: The tag set on a job never changes after creation. - **Inherited**: Jobs inherit the complete tag set from their process instance. For detailed information about tag formats, validation rules, limits, and additional use cases, see [process instance creation tags](/components/concepts/process-instance-creation.md#tags). ## Job leasing A job lease is an opt-in, opaque token that fences a specific activation of a job. It lets a worker prove its activation is still current when it interacts with Camunda. For example, consider a job worker that performs a credit check on a loan application and completes the job with its decision: approve or reject. Worker A activates the job and is on its way to approving, but a new negative evaluation appears on the applicant before it completes, and the job's deadline passes. Zeebe reassigns the job to worker B, which sees the new evaluation and decides to reject instead. Without a lease, Zeebe only checks that the job is still activated, not which activation the completion came from, so if worker A's stale approval reaches Zeebe first, it wins: funds get disbursed on outdated information, and worker B's correct rejection is discarded. ```mermaid sequenceDiagram participant A as Worker A participant Z as Zeebe participant B as Worker B A->>Z: Activate job Z-->>A: job Note over A: Deciding: approve Z->>Z: Job times out, reassigned B->>Z: Activate job Z-->>B: job Note over B: Sees new record, decides: reject A->>Z: Complete job (approve) Z-->>A: Accepted B->>Z: Complete job (reject) Z-->>B: Rejected: job already completed ``` With leasing, worker A's completion carries its own activation's token, which becomes stale the moment worker B's activation supersedes it, so Zeebe rejects it regardless of arrival order. The outcome flips: instead of whichever completion arrives first, the most up-to-date activation's completion wins. ```mermaid sequenceDiagram participant A as Worker A participant Z as Zeebe participant B as Worker B A->>Z: Activate job (withLease) Z-->>A: Job with jobLeaseToken A Note over A: Deciding: approve Z->>Z: Job times out, reassigned B->>Z: Activate job (withLease) Z-->>B: Job with jobLeaseToken B Note over B: Sees new record, decides: reject A->>Z: Complete job (jobLeaseToken A) Z-->>A: Rejected: INVALID_STATE, stale lease B->>Z: Complete job (jobLeaseToken B) Z-->>B: Accepted ``` Camunda's own [agentic orchestration](../agentic-orchestration/agentic-orchestration-overview.md) builds on this same guarantee for visibility into [agent instance](../agentic-orchestration/agent-definitions-and-instances.md#agent-instances)'s conversation. Before completing, an agent worker separately reports its reasoning as an [agent instance update](../../apis-tools/orchestration-cluster-api-rest/specifications/update-agent-instance.api.mdx), tied to its lease token. If a later activation supersedes it and completes instead, Zeebe discards the superseded activation's pending update and commits the winning one's, so a retry's contradicting reasoning never gets mixed with the activation that actually gets acted on. ```mermaid sequenceDiagram participant A1 as Activation 1 participant Z as Zeebe participant A2 as Activation 2 A1->>Z: Activate job (withLease) Z-->>A1: Job with jobLeaseToken 1 A1->>Z: Update agent instance (jobLeaseToken 1) Z-->>A1: Update pending Z->>Z: Job times out, reassigned A2->>Z: Activate job (withLease) Z-->>A2: Job with jobLeaseToken 2 A2->>Z: Update agent instance (jobLeaseToken 2) Z-->>A2: Update pending A2->>Z: Complete job (jobLeaseToken 2) Z-->>A2: Accepted Note over Z: Commits activation 2's update,discards activation 1's pending update ``` See [connect an external agent](../agentic-orchestration/connect-external-agent.md#step-2-activate-the-job-with-a-lease) for a concrete walkthrough of activating a job with a lease and reporting history against it. ### How job leasing works To use leasing, request a lease by setting `withLease` to `true` when you activate jobs. Zeebe then returns a `jobLeaseToken` on each activated job. This token identifies that specific activation, not the job itself. Pass the matching lease token back when you complete, fail, or throw an error on the job. You can also include it when you update the job timeout, retries, or priority, to verify the activation is still current before the update applies. ### Enforcement and rejections Complete, fail, and throw-error commands on a leased job require the matching lease token. If the token is missing or doesn't match, Zeebe rejects the command with `INVALID_STATE`. Updating a job's timeout, retries, or priority never requires a lease token, but Zeebe validates one if you supply it. This keeps operator and bulk updates of leased jobs possible without requiring a lease. A lease-mismatch rejection means another activation of the same job has already superseded yours, for example after the job timed out and was reassigned. Treat this as expected, not as an error: don't retry the command, and log it at debug level rather than as an error. ### Leasing is permanent for a job Once a job has been leased by any worker, Zeebe never again serves that job to a non-leasing worker of the same type. A non-leasing poll or stream silently skips the job. The affected process instances may appear stuck with no incident indicating the problem if all leasing workers are stopped. Zeebe exposes a `skipped` action on the `zeebe.job.events.total` metric as the operator signal that a non-leasing worker attempted to activate the leased job. :::note There is currently no operation to remove a lease from a job. To recover, you have two options: - Redeploy any worker for the job type with `withLease` set to `true`, to drain the leased jobs. - Use process instance modification to terminate and reactivate the element, which produces a fresh, unleased job. ::: This also affects rollbacks. If you roll back a leasing worker deployment to a non-leasing version, any jobs leased in the interim stay permanently unavailable to the rolled-back version. Before rolling back, drain in-flight leased jobs of that type first, so the rolled-back version doesn't start out starved of jobs it can never activate. :::tip Run a homogeneous fleet per job type: either all workers for a type request a lease, or none do. Mixed fleets work, but treat them as a transitional state, such as during a rollout. Keep an eye on the `skipped` jobs metric mentioned above to ensure the fleet is homogeneous. ::: ### Example The following example activates jobs with a lease and completes the job using the matching lease token. ```java client .newWorker() .jobType("process-payment") .handler( (jobClient, job) -> { // process the job ... jobClient // highlight-start .newCompleteCommand(job.getKey()) .withJobLeaseToken(job.getJobLeaseToken()) // highlight-end .send(); }) // highlight-start .withLease(true) // highlight-end .open(); ``` When you build a command from the activated job itself, the client carries the job's lease token for you automatically: ```java client .newWorker() .jobType("process-payment") .handler( (jobClient, job) -> { // process the job ... // highlight-start jobClient.newCompleteCommand(job).send(); // highlight-end }) // highlight-start .withLease(true) // highlight-end .open(); ``` --- ## Message aggregation Use message aggregation to collect multiple related messages into a single process instance before proceeding to the next step of your workflow. ## What message aggregation is Message aggregation is a message correlation pattern that allows you to receive, store, and process multiple messages belonging to the same business entity. It’s commonly used when: - You receive multiple events about a single entity (for example, shipments for the same order). - You need to wait for _N_ messages before proceeding. - You want to combine or "map-reduce" data across messages before continuing. Instead of creating a new process instance for each message, messages with the same **correlation key** are routed to the same instance. :::note This guide applies to Camunda 8 (Zeebe). The message aggregation pattern requires understanding of [message correlation](/components/concepts/messages.md#message-correlation-overview) and BPMN message events. ::: ## How it works 1. Each message is published with: - A **message name** (for example, `"ItemReceived"`) - A **correlation key** (for example, `"order-123"`) - Optionally, a **time-to-live (TTL)** greater than `0` 2. The first message starts a new process instance. 3. All subsequent messages with the same correlation key are correlated to that instance. 4. The workflow collects the message data (for example, appending to a list). 5. Once all expected messages are received, the process continues. If additional messages with the same correlation key arrive after the process instance has completed, a new instance is created automatically. ## Example use case Imagine a workflow that collects three messages for each order before processing them together. ### BPMN model Below is the key structure of the BPMN process: - **Start event:** Message start event that starts a process for the first message of each `correlation_key`. - **Intermediate catch event:** Waits for additional messages with the same correlation key. - **Gateway:** Checks if the desired number of messages has been received. - **Service task:** Processes all aggregated messages when complete. ```xml =count(messages) < 3 ``` ## Behavior The first message with a unique correlation key starts a process instance. Each subsequent message with that same key is correlated to the same instance and appended to the list. Once three messages are received (`count(messages) == 3`), the process continues to the **Process Aggregated Messages** task. ## Publishing messages Here’s an example in Java using the Zeebe client: ```java final ZeebeClient client = ZeebeClient.newClientBuilder().build(); for (int i = 0; i < 3; i++) { client.newPublishMessageCommand() .messageName("Message_Received") .correlationKey("order-123") .timeToLive(Duration.ofMinutes(5)) .variables(Map.of( "message", "iteration #" + i + " order-123 " + Instant.now(), "correlation_key", "order-123")) .send() .join(); } ``` The first message starts the workflow; the next two correlate to the existing instance. ## Troubleshooting | Problem | Cause | Solution | | ------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------- | | Every message starts a new process instance | Missing or mismatched `correlationKey` in message variables | Ensure all messages use the same correlation key and name | | Process never completes | The `count(messages)` condition is not met | Verify your condition and that messages are successfully correlated | | Messages not correlated | TTL expired or wrong message name | Use a TTL > 0 and match the BPMN message name exactly | | Duplicate aggregation | The instance ended but more messages arrived | This is expected — a new instance is started | ## Best practices - Always include a unique `correlationKey` in message variables and BPMN definition. - Use a **timer boundary event** to avoid waiting indefinitely for missing messages. - Add logging or audit tasks for tracking message count and correlation. - If using multiple sources, validate messages before appending to the collection. - Test with different message arrival orders to ensure correct behavior. ## Related resources - [Messages](/components/concepts/messages.md) --- ## Messages [Process instances](/reference/glossary.md#process-instance) can respond to incoming [messages](/reference/glossary.md#message). Published messages must be mapped onto a process instance. This step is called [message correlation](/components/modeler/bpmn/message-events/message-events.md#message-correlation). ## Message subscriptions A message is not sent to a process instance directly. Instead, the message correlation is based on subscriptions that contain the `message name` and the `correlation key` (also known as the correlation value). ![Message Correlation](assets/message-correlation.png) A subscription is opened when a process instance awaits a message; for example, when entering a message catch event. The message name is defined either statically in the process (e.g. `Money collected`) or dynamically as an expression. The correlation key is defined dynamically as an expression (for example, `= orderId`). The expressions are evaluated on activating the message catch event. The results of the evaluations are used as message name and as correlation key of the subscription (e.g. `"order-123"`). :::note Message names and correlation keys are subject to backend-dependent length limits. They support up to **32,768 characters** with Elasticsearch/OpenSearch-backed secondary storage and up to **256 characters** with RDBMS-backed secondary storage. Length is enforced using Java string length semantics, so the effective visible-character limit can be lower for characters represented as surrogate pairs in Java. ::: When a message is published and the message name and correlation key match to a subscription, the message is correlated to the corresponding process instance. If no proper subscription is opened, the message is discarded. A subscription is closed when the corresponding element (e.g. the message catch event), or its scope is left. After a subscription is opened, it is not updated (for example, when the referenced process variable is changed.)
Publish message via Orchestration Cluster REST API ``` curl -L 'http://localhost:8080/v2/messages/publication' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d '{ "name": "Money collected", "correlationKey": "order-123" }' ``` See the [API reference for publish message](/apis-tools/orchestration-cluster-api-rest/specifications/publish-message.api.mdx) for more information, including additional request fields and code samples. When you require immediate feedback if the message was correlated to an open subscription, you can use `Correlate message` via Orchestration Cluster REST API. If correlation is successful it will return the first process instance key the message correlated with. ``` curl -L 'http://localhost:8080/v2/messages/correlation' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d '{ "name": "Money collected", "correlationKey": "order-123" }' ``` See the [API reference for correlate message](/apis-tools/orchestration-cluster-api-rest/specifications/correlate-message.api.mdx) for more information, including additional request fields and code samples.
## Message buffering Messages can be buffered for a given time. Buffering can be useful in a situation when it's not guaranteed the subscription is opened before the message is published. A message has a **time-to-live (TTL)** which specifies for how long it's buffered. Within this time, the message can be correlated to a process instance. When a subscription is opened, it polls the buffer for a proper message. If a proper message exists, it is correlated to the corresponding process instance. In case multiple messages match to the subscription, the first published message is correlated (like a FIFO queue). The buffering of a message is disabled when its TTL is set to zero. If no proper subscription is open, the message is discarded.
Publish message with TTL via Orchestration Cluster REST API ``` curl -L 'http://localhost:8080/v2/messages/publication' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d '{ "name": "Money collected", "correlationKey": "order-123", "timeToLive": 3600000 }' ``` See the [API reference for publish message](/apis-tools/orchestration-cluster-api-rest/specifications/publish-message.api.mdx) for more information, including additional request fields and code samples.
## Message cardinality A message is correlated only _once_ to a process (based on the BPMN process ID), across all versions of this process. If multiple subscriptions for the same process are opened (by multiple process instances or within one instance), the message is correlated only to one of the subscriptions. When subscriptions are opened for different processes, the message is correlated to _all_ the subscriptions. A message is _not_ correlated to a message start event subscription if an instance of the process is active and was created by a message with the same correlation key. If the message is buffered, it can be correlated after the active instance is ended. Otherwise, it is discarded. ### Duplicate subscriptions When multiple process instances subscribe to the same message name and correlation key simultaneously, there is no guarantee about which instance receives the message. The selection is non-deterministic by design. An alternative approach, for example, routing to the most recently created instance, would mask correlation key design problems during single-instance development testing, and then fail unexpectedly in production when multiple users run concurrent instances. Non-deterministic selection surfaces the problem early. The optimal solution is to use **unique correlation keys per interaction**, so that each subscription is unambiguous. See [request-reply with unique correlation key](#request-reply-with-unique-correlation-key) for more details. ## Message uniqueness A message can have an optional message ID — a unique ID to ensure the message is published and processed only once (i.e. idempotency). The ID can be any string; for example, a request ID, a tracking number, or the offset/position in a message queue. A message is rejected and not correlated if a message with the same name, the same correlation key, and the same ID is already buffered. After the message is discarded from the buffer, a message with the same name, correlation key, and ID can be published again. The uniqueness check is disabled when no message ID is set.
Publish message with message ID via Orchestration Cluster REST API ``` curl -L 'http://localhost:8080/v2/messages/publication' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d '{ "name": "Money collected", "correlationKey": "order-123", "messageId": "tracking-12345" }' ``` See the [API reference for publish message](/apis-tools/orchestration-cluster-api-rest/specifications/publish-message.api.mdx) for more information, including additional request fields and code samples.
## Message correlation overview By combining the principles of message correlation, message uniqueness, and message buffering, very different behaviors can be achieved. Please note that a message name is mandatory, so it is omitted from the table. | Correlation key | Message ID | Time to live | Receiver type | Behavior | | --------------- | ---------- | ------------ | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | set | not set | set to 0 | Start event | A new instance is started if no instance with the correlation key set at start is active, see [single instance](./#single-instance). | | set | not set | set to 0 | Intermediate event | The message is correlated if a matching subscription is active. | | set | not set | set > 0 | Start event | A new instance is started if no instance with the correlation key set at start is active during the lifetime of the message; new [equal messages](#message-uniqueness) are buffered. | | set | not set | set > 0 | Intermediate event | The message is correlated during the lifetime of the message if a matching subscription is active; new [equal messages](#message-uniqueness) are buffered. | | set | set | set to 0 | Start event | A new instance is started if no instance with the correlation key set at start is active and there is no [equal message](#message-uniqueness) in the buffer. | | set | set | set to 0 | Intermediate event | The message is correlated if a matching subscription is active and there is no [equal message](#message-uniqueness) in the buffer. | | set | set | set > 0 | Start event | A new instance is started if no instance with the correlation key set at start is active during the lifetime of the message and there is no [equal message](#message-uniqueness) in the buffer. | | set | set | set > 0 | Intermediate event | The message is correlated during the lifetime of the message if a matching subscription is active and there is no [equal message](#message-uniqueness) in the buffer. | | empty string | not set | set to 0 | Start event | A new instance is started. | | empty string | not set | set to 0 | Intermediate event | The message is correlated if a matching subscription to the empty string is active. | | empty string | not set | set > 0 | Start event | A new instance is started. | | empty string | not set | set > 0 | Intermediate event | The message is correlated during the lifetime of the message if a matching subscription to the empty string is active; new [equal messages](#message-uniqueness) are buffered. | | empty string | set | set to 0 | Start event | A new instance is started if there is no [equal message](#message-uniqueness) in the buffer. | | empty string | set | set to 0 | Intermediate event | The message is correlated if a matching subscription to the empty string is active and there is no [equal message](#message-uniqueness) in the buffer. | | empty string | set | set > 0 | Start event | A new instance is started if there is no [equal message](#message-uniqueness) in the buffer. | | empty string | set | set > 0 | Intermediate event | The message is correlated during the lifetime of the message if a matching subscription to the empty string is active and there is no [equal message](#message-uniqueness) in the buffer. | ## Business ID in message correlation Starting in Camunda 8.10, you can include a business ID in message publication and correlation. Business ID acts as an additional filter constraint on top of the message name and correlation key. ### Supported combinations Message name is always required. The following additional fields are supported per event type: | Event type | Supported correlation fields | | :--------------------------------------------------------------- | :------------------------------------------- | | Start event | Message name | | Start event | Message name + business ID | | Start event | Message name + correlation key | | Start event | Message name + correlation key + business ID | | Non-start event (intermediate catch, boundary, event subprocess) | Message name + correlation key | | Non-start event | Message name + correlation key + business ID | For non-start events, a business ID can only be used together with an existing correlation key. Business ID alone is not sufficient to correlate to a non-start subscription. When both a correlation key and a business ID are provided, the message correlates only if both fields match the corresponding values stored on the subscription. ### Matching semantics A business ID on a message is an optional narrowing filter, on top of — never a replacement for — the correlation key. Matching is asymmetric between the two sides: | Message business ID | Subscription business ID | Correlates? | | :------------------ | :----------------------- | :----------------------------------------------- | | Not set | Not set | Yes — matches on name and correlation key alone. | | Not set | Set | Yes — the subscription's business ID is ignored. | | Set | Not set | No. | | Set | Set, same value | Yes. | | Set | Set, different value | No. | A message subscription snapshots the process instance's business ID at the time the subscription is opened. If a [late business ID assignment](/components/concepts/process-instance-creation.md#late-business-id-assignment) updates a process instance after a subscription is already open, the existing subscription is not updated. Only subscriptions opened after the assignment carry the new business ID. ### Message-start buffering with uniqueness When business ID uniqueness is enabled, a message-start event that would create a new instance with a business ID already held by an active instance is not dropped immediately. The message stays buffered and is retried until either: - The active instance releases the business ID (by completing or terminating), or - The message's TTL expires. If the TTL expires before the business ID is released, the message is discarded without starting a new instance. :::note `TTL = 0` (fire-and-forget) message-start events are not retried. They activate on first arrival only and are discarded immediately if blocked by uniqueness. ::: ### API reference - [Publish message](/apis-tools/orchestration-cluster-api-rest/specifications/publish-message.api.mdx) — `businessId` request field. - [Correlate message](/apis-tools/orchestration-cluster-api-rest/specifications/correlate-message.api.mdx) — `businessId` request field. - [Search correlated message subscriptions](/apis-tools/orchestration-cluster-api-rest/specifications/search-correlated-message-subscriptions.api.mdx) — `businessId` as a filter and sort field. ## Message patterns The following patterns describe solutions for common problems that can be solved using message correlation. ### Message aggregator **Problem**: Aggregate/collect multiple messages, map-reduce, batching **Solution**: ![Message Aggregator](assets/message-aggregator.png) The messages are published with a `TTL > 0` and a correlation key that groups the messages per entity. The first message creates a new process instance. The following messages are correlated to the same process instance if they have the same correlation key. When the instance ends and messages with the same correlation key are not correlated yet, a new process instance is created. :::note You may also use TTL to wait for messages that may arrive earlier when combining [start events and intermediate catch events](/components/modeler/bpmn/events.md). ::: Learn more in our [message aggregation guide](./message-aggregation.md). ### Single instance **Problem**: Create exactly one instance of a process **Solution**: ![Message Single Instance](assets/message-single-instance.png) The message is published with a `TTL = 0` and a correlation key that identifies the entity. The first message creates a new process instance. The following messages are discarded and do not create a new instance if they have the same correlation key and the created process instance is still active. ### Request-reply with unique correlation key **Problem**: An [AI agent](/reference/glossary.md#ai-agent) or service sends a message to an external system (for example, a chat platform or webhook) and waits for a reply. Multiple process instances may be active concurrently, each waiting for its own reply. **Solution**: Generate a unique correlation key when the outbound message is sent, pass it to the external system, and subscribe with the same key in the reply catch event. This avoids the [duplicate subscription problem](#duplicate-subscriptions) that occurs when all instances share the same correlation key (for example, a static user ID or a fixed string). A common implementation: 1. A service task sends the outbound message and generates a unique key (for example, `chatId + "-" + uuid()`). 2. The external system receives both the message content and the correlation key. 3. When the external system replies, it includes the correlation key. 4. A message catch event subscribes using the same key, so the reply reaches the correct instance. :::note This pattern is the recommended approach for any send-and-wait interaction where process instances can run concurrently. ::: ## Message response Publishing a message is a fire-and-forget action. As a user, you do not know if the correlation is a success. To know if a published message was correlated (and to which process instance), use the [message correlation endpoint](../../apis-tools/orchestration-cluster-api-rest/specifications/correlate-message.api.mdx). The message correlation endpoint works similarly to the message publish endpoint. However, the message correlation endpoint does not support [message buffering](#message-buffering). Any message published using this endpoint is either immediately correlated, or not correlated at all. This is due to the synchronous nature of requiring a response. If a message correlated successfully, it returns a process instance key of an instance the message correlated with. This is only one key. It is possible that the message correlated to other process instances. These keys are not part of the response. The response will always prioritize the creation of a new process instance ([message start event](../modeler/bpmn/message-events/message-events.md#message-start-events)) over correlation with an existing process instance ([message catch event](../modeler/bpmn/message-events/message-events.md#intermediate-message-catch-events) or [receive task](../modeler/bpmn/receive-tasks/receive-tasks.md)). --- ## Multi-tenancy [Multi-tenancy](/reference/glossary.md#multi-tenancy) in Camunda 8 enables a single installation to serve multiple [tenants](/reference/glossary.md#tenant) such as departments, teams, or external clients, while keeping each tenant's data and processes logically isolated. This page describes **logical multi-tenancy**: tenant-ID based isolation within a single cluster. | Deployment | Availability | | :----------- | :----------------------------------------------------- | | SaaS | Available on clusters running generation 8.8 and later | | Self-Managed | Available | :::note Self-Managed also supports stronger isolation models. For a comparison of logical tenants, Physical Tenants, and multi-cluster deployments, see the [Self-Managed multi-tenancy overview](/self-managed/concepts/multi-tenancy/index.md). ::: ## How multi-tenancy works Camunda 8 implements multi-tenancy using tenant identifiers within a single installation. All tenant data is stored in the same database, with isolation enforced by appending a tenant identifier to each data object, such as process definitions, process instances, and jobs. ### Tenant identifier The tenant identifier is added to all data created in Camunda 8. By default, all data is assigned to the `` tenant identifier. :::note The `` tenant identifier is reserved and cannot be changed by users. ::: Organizations can create additional tenants. Tenant identifiers must meet the following requirements: - Use only alphanumeric characters, dashes (`-`), underscores (`_`), or dots (`.`). - Be no longer than 31 characters. ### Multi-tenancy checks Multi-tenancy checks enforce tenant-based access control. By default, multi-tenancy checks are **disabled**. This means that although tenants can be created and assigned, the system does not restrict access based on those assignments. All data is associated with the `` tenant. When **enabled**, the system verifies that users can only access resources associated with their assigned tenants. Users, groups, and roles not assigned to a tenant lose access to resources scoped to that tenant. :::warning Before you enable multi-tenancy checks, assign all users, groups, and roles that need access to their tenants **and** to the `` tenant. Once checks are enforced, any principal not assigned to a tenant loses access to the resources scoped to that tenant. ::: ### Inherited tenant ownership Tenant ownership in Camunda 8 is hierarchical. A user can only deploy resources to authorized tenants. Any data created by those resources inherits the same tenant identifier. ## Example: tenant membership in action When a user deploys a process model or starts a process instance, the system validates the user's tenant assignments. For example, assume a user belongs to `tenant-a` but not `tenant-b`: 1. **Deploying a process model** - If the user deploys to `tenant-a`, the Orchestration Cluster verifies the assignment. If valid, the model is deployed and all related process instances belong to `tenant-a`. - If the user deploys to `tenant-b`, the deployment fails because the user lacks access to that tenant. 2. **Running process instances** - When querying process instances, the user only sees instances belonging to `tenant-a`. This mechanism ensures proper isolation and access control across tenants. ## Enable multi-tenancy checks Tenants can be created and principals assigned regardless of whether checks are enabled. Enabling checks enforces the assignments. How you enable checks depends on your deployment model. ### SaaS On SaaS, enable multi-tenancy checks per cluster using the **Multi-tenancy** toggle in Camunda Hub: 1. Navigate to **Camunda Hub**. 1. In the left navigation, click **Environments**, and then click **Clusters**. 1. Select the cluster you want to manage, and select the **Settings** tab. 1. Enable the **Multi-tenancy** setting. For details on the toggle, its default state, and who can change it, see [cluster settings](/components/saas/clusters/settings.md#multi-tenancy). The **Multi-tenancy** toggle is available for clusters running generation 8.8 and later. It is disabled by default, and only organization admins can change it. Disabling the toggle restores the implicit ``-tenant behavior. ### Self-Managed On Self-Managed, operators enable multi-tenancy checks through configuration properties. See [Orchestration Cluster configuration properties](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#multi-tenancy). ## Manage tenants Administrators can manage all tenants centrally in [Admin](/components/admin/tenant.md). This unified management interface simplifies monitoring, configuration, and maintenance tasks across tenant environments. The **Tenants** tab in Admin is available to organization admins on SaaS clusters running generation 8.8 and later, even before multi-tenancy checks are enabled. This allows admins to set up tenants and assignments before enforcing checks. ## Multi-tenancy in Camunda Hub In Camunda Hub, you deploy to an [environment](/components/concepts/environments.md), which is a Physical Tenant or a cluster. If multi-tenancy is enabled for that environment, you select a tenant separately when you deploy. The tenant you select owns the deployed resources. If the environment has more than one tenant, choose the tenant to deploy to. If it has exactly one, Camunda Hub selects it automatically. Learn more about [deploying to a tenant](/components/hub/workspace/manage-projects/deploy-project.md#logical-tenants). ## Optimize and multi-tenancy Optimize multi-tenancy is available in **Self-Managed only**. On SaaS, Optimize can only access data from the `` tenant. Data scoped to other tenants is not available in Optimize on SaaS. In Self-Managed, [Management Identity](/self-managed/components/management-identity/overview.md) is used for identity and access management of components outside the [Orchestration Cluster](/self-managed/components/orchestration-cluster/overview.md). Of those, only [Optimize](/self-managed/components/optimize/overview.md) is tenant aware and can make use of multi-tenancy. To use it with the same tenants as an Orchestration Cluster, you must manually synchronize the tenants in both the Orchestration Cluster and Management Identity. This means manually creating them, and updating them whenever they change. Two tenants are considered the same if they have the same ID. --- ## Outbound connectors vs. job workers Integrating with external systems can be done with a connector or a [job worker](job-workers.md). You define the domain-specific UI for modeling a connector through a [connector template](/components/connectors/custom-built-connectors/connector-templates.md). A connector template is a type of [element template](/components/modeler/element-templates/about-templates.md). Therefore, you can also build a connector-like system using element templates and job workers. If they both share the same core functionality, how do they differ, and when should you choose what connectors and job workers serve different purposes when it comes to aspects like delivery, reusability, focus, and context. ## Delivery A connector is reusable code, written as an `OutboundConnectorFunction` using the [Connector SDK](/components/connectors/custom-built-connectors/connector-sdk.md#outbound-connector-runtime-logic). It is not a standalone application, you cannot start it and have it work on Camunda 8 jobs. Instead, a connector is delivered as a library and can be used in combination with other connectors in a [Connector runtime environment](/components/connectors/custom-built-connectors/connector-sdk.md#runtime-environments). In contrast, a job worker is usually part of a Zeebe client application that can be directly executed to work on jobs. ## Reusability A job worker usually runs as or inside a standalone Zeebe client. Without effort, you cannot simply run this in Camunda 8 SaaS or any other environment. As a Self-Managed user, you can run it standalone, but often not directly reuse the logic in your existing Zeebe client that you might already have. You can manually extract the job handler from the given job worker, but you also have to ensure that it is still working as expected afterward. In contrast, a connector itself is environment-agnostic. There is a runtime environment for Camunda 8 SaaS that can wrap and call this connector. As the connector developer, you don't have to worry about this as the runtime takes care of it if you developed the connector using the Connector SDK. You can also run the exact same connector (without any modification) in Camunda 8 Self-Managed; either as a standalone job worker, as additional job handler in your existing Zeebe client application, or together with other connectors in one Zeebe client application. This all comes with the Connector SDK, and there is no additional code necessary to get started. However, if you need a custom environment, the Connector SDK provides a guide and default helpers to do that. ## Focus A job worker is often a complete Zeebe client application, dealing with environment tasks like handling variables in and out. The core logic of calling a defined URL is only part of the application. Plus, it handles Camunda 8-specific APIs like the job worker API to handle variables, complete executions, and throw errors. A connector only consists of core business functionality. No environment tasks, no Camunda 8 job worker-related code. You can run this from Camunda 7 as well if you have a runtime that takes care of this. The connector only needs input variables and access to secrets so they can be used in defined input attributes. ## Context Every job worker implementation defines on its own how to handle input data, validating and transforming it. Plus, there is no unified modeling experience for job workers. There can be an element template for the worker, but that template might look completely different for every job worker. Secrets are the exception. The Orchestration Cluster resolves `camunda.secrets.` references in a job's variables and injects the values when it activates the job. The job worker receives the sensitive values at runtime without storing them in its own configuration, regardless of its implementation language or deployment location. See [secret resolution and job activation](secret-resolution-and-job-activation.md). In contrast, connectors provide these capabilities out of the box, along with built-in secret management that lets you provide secrets in different ways. Element templates, called [Connector templates](/components/connectors/custom-built-connectors/connector-templates.md), are a vital part of a connector. There are standardized best practices for developing those. Having used one connector template will make it easy for you to use the next one just the same. ## Which one should you choose? It depends on your use case. - Need access to a low-level API in Camunda 8 to perform a very specific task? You are better off with job workers. - Want to write your worker logic in something other than Java? Job workers are your way to move forward. - Want to create worker logic that is reusable in any environment? Write a connector. - Want to focus on your worker's logic and have no need for using low-level Orchestration Cluster REST API? Write a connector. - Want to provide a standardized modeling experience alongside your runtime behavior? Write a connector. ## Learn more - [Explore connectors](/components/connectors/introduction.md) - [Learn about types of connectors](/components/connectors/connector-types.md) - [Learn about job workers](job-workers.md) --- ## Process instance creation Depending on the process definition, an instance of it can be created in several ways. Camunda 8 supports the following ways to create a process instance: - [`CreateProcessInstance` commands](#commands) - [Message event](#message-event) - [Timer event](#timer-event) ## Commands A process instance is created by sending a command specifying the BPMN process ID, or the unique key of the process. There are two commands to create a process instance, outlined in the sections below. ### Create and execute asynchronously A process that has a [none start event](/components/modeler/bpmn/none-events/none-events.md#none-start-events) is started explicitly using **[CreateProcessInstance](/apis-tools/zeebe-api/gateway-service.md#createprocessinstance-rpc)**. This command creates a new process instance and immediately responds with the process instance ID. The execution of the process occurs after the response is sent. ![create-process](assets/create-process.png)
Create a process instance via Orchestration Cluster REST API ``` curl -L 'http://localhost:8080/v2/process-instances' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d '{ "processDefinitionKey": "2251799813685249”, "processDefinitionVersion": 1 }' ``` Response: ``` { "processDefinitionId": "order-process", "processDefinitionVersion": 1, "processDefinitionKey": "2251799813685249", "processInstanceKey": "2251799813686019" } ``` See the [API reference for process instance creation](/apis-tools/orchestration-cluster-api-rest/specifications/create-process-instance.api.mdx) for more information, including additional request fields and code samples.
### Create and await results Typically, process creation and execution are decoupled. However, there are use cases that need to collect the results of a process when its execution is complete. **[CreateProcessInstanceWithResult](/apis-tools/zeebe-api/gateway-service.md#createprocessinstancewithresult-rpc)** allows you to “synchronously” execute processes and receive the results via a set of variables. The response is sent when the process execution is complete. ![create-process](assets/create-process-with-result.png) This command is typically useful for short-running processes and processes that collect information. If the process mutates system state, or further operations rely on the process outcome response to the client, consider designing your system for failure states and retries. :::note When the client resends the command, it creates a new process instance. :::
Create a process instance and await results via Orchestration Cluster REST API ``` curl -L 'http://localhost:8080/v2/process-instances' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d '{ "processDefinitionId": "order-process”, "processDefinitionVersion": 1, "awaitCompletion": true, "variables": { "orderId": "1234" } }' ``` Response: ``` { "processDefinitionId": "order-process", "processDefinitionVersion": 1, "variables": { "orderId": "1234" } "processDefinitionKey": "2251799813685249", "processInstanceKey": "2251799813686019", } ``` See the [API reference for process instance creation](/apis-tools/orchestration-cluster-api-rest/specifications/create-process-instance.api.mdx) for more information, including additional request fields and code samples.
Failure scenarios applicable to other commands are applicable to this command as well. Clients may not get a response in the following cases even if the process execution is completed successfully: - **Connection timeout**: If the gRPC deadlines are not configured for long request timeout, the connection may be closed before the process is completed. - **Network connection loss**: This can occur at several steps in the communication chain. - **Failover**: When the node processing this process crashes, another node continues the processing. The other node does not send the response because the request is registered on the first one. - **Gateway failure**: If the gateway the client is connected to fails, nodes inside the cluster cannot send the response to the client. ### Run process segment The [`create and execute asynchronously`](#create-and-execute-asynchronously) and [`create and await results`](#create-and-await-results) commands both start the process instance at their default initial element: the single [none start event](/components/modeler/bpmn/none-events/none-events.md#none-start-events). Camunda 8 also provides a way to create a process instance starting or ending at user-defined element(s). :::info This is an advanced feature. Camunda recommends to only use this functionality for testing purposes. The none start event is the defined beginning of your process. Most likely the process is modeled with the intent to start all instances from the beginning. ::: #### Start instructions To start the process instance at a user-defined element, you need to provide start instructions along with the command. Each instruction describes how and where to start a single element. By default, the instruction starts before the given element. This means input mappings of that element are applied as usual. Multiple instructions can be provided to start the process instance at more than one element. You can activate the same element multiple times inside the created process instance by referring to the same element ID in more than one instruction. #### Runtime instructions By default, the process execution continues normally until the end of the process. To change this behavior and end the process instance after a specific element completes or terminates, provide runtime instructions. Each runtime instruction specifies the ID of one element whose completion or termination ends the process instance. You can provide multiple runtime instructions to terminate the process instance after multiple elements—for example, when a process has multiple parallel flows. :::note Start and runtime instructions have the same [limitations as process instance modification](/components/concepts/process-instance-modification.md#limitations), e.g., it is not possible to start or end at a sequence flow. ::: Start and runtime instructions are supported for both `CreateProcessInstance` commands. Both instruction sets can be used separately or together to achieve different scenarios.
Create a process instance with a start and a runtime instruction The example below shows how to create a process instance that starts at a user-defined element and terminates after it, so that only the specified segment of the process is executed. ``` curl -L 'http://localhost:8080/v2/process-instances' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d '{ "processDefinitionId": "order-process”, "processDefinitionVersion": -1, "startInstructions": [ { "elementId": "ship_parcel" } ], "runtimeInstructions": [ { "type": "TERMINATE_PROCESS_INSTANCE", "afterElementId": "ship_parcel" } ] "variables": { "orderId": "1234" } }' ``` See the [API reference for process instance creation](/apis-tools/orchestration-cluster-api-rest/specifications/create-process-instance.api.mdx) for more information, including additional request fields and code samples.
## Events Process instances are also created implicitly via various start events. Camunda 8 supports message start events and timer start events. ### Message event A process with a [message start event](/components/modeler/bpmn/message-events/message-events.md#message-start-events) can be started by publishing a message with the name that matches the message name of the start event. For each new message a new instance is created. ### Timer event A process can also have one or more [timer start events](/components/modeler/bpmn/timer-events/timer-events.md#timer-start-events). An instance of the process is created when the associated timer is triggered. Timers can also trigger periodically. ## FEEL context variables Starting in 8.10, the process instance properties are accessible in [FEEL expressions](/components/concepts/expressions.md) via the `camunda.processInstance` context, resolvable in any FEEL expression across the process: - `camunda.processInstance.key` — the process instance's system-generated key. - `camunda.processInstance.businessId` — the process instance's [business ID](#business-id), or `null` if none is set. These are the only `camunda.processInstance.*` context variables available in 8.10. ## Business ID ### What is a business ID? A business ID is a domain-specific identifier you can assign to a process instance. Unlike the system-generated process instance key, it represents a domain concept such as an order number, case reference, or customer ticket ID. A business ID does **not need to be unique** unless uniqueness control is enabled. See [uniqueness control](#uniqueness-control) for details. For example, consider a process that ships book orders where each order already has an identifier in your order management system. When you start the process to ship an order, you can use the order ID as the business ID. This lets you easily find all process instances related to a particular order. You set the business ID at process instance creation time via the `businessId` field in the creation request. The business ID is **immutable**; once set, it cannot be changed or removed for the lifetime of the process instance. The maximum length for a business ID is **256 characters**. Starting in 8.10, you can also set a business ID when starting a process instance from **Camunda Hub** or **Desktop Modeler**.
Create a process instance with a business ID via Orchestration Cluster REST API ``` curl -L 'http://localhost:8080/v2/process-instances' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d '{ "processDefinitionId": "order-process", "processDefinitionVersion": 1, "businessId": "order-1234" }' ``` See the [API reference for process instance creation](/apis-tools/orchestration-cluster-api-rest/specifications/create-process-instance.api.mdx) for more information, including additional request fields and code samples.
### Propagation to child instances When a process instance with a business ID creates a child process instance via a [call activity](/components/modeler/bpmn/call-activities/call-activities.md), the business ID is automatically propagated to the child by default. Each child instance inherits the same business ID as its parent, letting you trace an entire process hierarchy using a single domain identifier. Starting in 8.10, a call activity can override the inherited business ID by setting a literal value or [FEEL expression](/components/concepts/expressions.md) on the call activity. The value is resolved once at child creation and is then immutable. See [business ID propagation](/components/modeler/bpmn/call-activities/call-activities.md#business-id-propagation) for configuration details. ### Searching and filtering by business ID Starting in 8.10, `businessId` is available across multiple entity types. Searchability varies by entity: | Entity | Searchable | Visible | Notes | | :-------------------- | :--------- | :------ | :---------------------------------------------------------------------------------- | | Process instances | Yes | Yes | Available since 8.9. | | Decision instances | Yes | Yes | Available since 8.10. | | User tasks | Yes | Yes | Available since 8.10. | | Messages | Yes | Yes | API only. Not shown in the Operate process instance details view. | | Message subscriptions | Yes | Yes | API only. | | Jobs | No | Yes | Visible in job activation response. Not searchable, filterable, or shown in the UI. | #### Advanced filter operators The API supports the following operators for `businessId`. Operate and Tasklist expose a subset in their filter UI. | Operator | Description | Wildcards | UI label | | :-------- | :-------------------------- | :---------------------------------- | :-------- | | `$eq` | Exact match | — | Equals | | `$neq` | Does not match | — | API only | | `$exists` | Field is set or absent | — | API only | | `$like` | Pattern match | `*` (multi-char), `?` (single-char) | Contains | | `$in` | Matches any value in a list | — | Is one of | | `$notIn` | Matches no value in a list | — | API only | #### API reference | Entity | Endpoints | | :-------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Process instances | [Get](/apis-tools/orchestration-cluster-api-rest/specifications/get-process-instance.api.mdx) · [Search](/apis-tools/orchestration-cluster-api-rest/specifications/search-process-instances.api.mdx) | | Decision instances | [Get](/apis-tools/orchestration-cluster-api-rest/specifications/get-decision-instance.api.mdx) · [Search](/apis-tools/orchestration-cluster-api-rest/specifications/search-decision-instances.api.mdx) | | User tasks | [Get](/apis-tools/orchestration-cluster-api-rest/specifications/get-user-task.api.mdx) · [Search](/apis-tools/orchestration-cluster-api-rest/specifications/search-user-tasks.api.mdx) | | Message subscriptions | [Search](/apis-tools/orchestration-cluster-api-rest/specifications/search-correlated-message-subscriptions.api.mdx) | | Jobs (visible only) | [Search](/apis-tools/orchestration-cluster-api-rest/specifications/search-jobs.api.mdx) · [Activate](/apis-tools/orchestration-cluster-api-rest/specifications/activate-jobs.api.mdx) | #### Retroactive visibility Business IDs assigned to process instances in 8.9 remain traceable because the business ID is stored on the process instance record. However, related artifacts — user tasks, decision instances, jobs, and message subscriptions — snapshot the business ID at their own creation time. Artifacts created before business ID visibility shipped for that entity type are not retroactively enriched and carry no `businessId` value. ### Late Business ID assignment You can assign a business ID to a running process instance that has none. This is useful when the domain identifier (for example, an order number or case reference) is not available at instance creation time. :::note Late assignment requires business ID uniqueness to be **disabled**. See [uniqueness control](#uniqueness-control) for details. ::: | Constraint | Detail | | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------ | | Single and irreversible | Once set, the business ID cannot be changed or removed. | | Root instances only | Call-activity children are always rejected. | | No re-assignment | Any attempt to assign to an instance that already has a business ID — whether the value matches or differs — returns `INVALID_STATE`. | | Max length | 256 characters. | **Propagation is forward-only.** Only artifacts created after the assignment carry the business ID: future jobs, user tasks, decision instances, message subscriptions, and call-activity children. Pre-existing artifacts retain an empty `businessId`. An instance in this state shows a mixed view — older artifacts have no business ID, newer ones do. **API surfaces:** | Surface | Details | | :------------- | :-------------------------------------------------------------------------------------------------------------------------------------- | | REST | `POST /process-instances/{processInstanceKey}/business-id-assignment` | | gRPC | `AssignProcessInstanceBusinessId` | | Job completion | Include `businessId` in the `CompleteJob` request. If assignment fails, the entire complete is rejected — the job is **not** completed. | **Rejection contract:** | Failure | Rejection | | :----------------------------------------------------------- | :--------------------------- | | Instance not found or wrong tenant | `NOT_FOUND` | | Target is a call-activity child | `INVALID_STATE` | | Business ID uniqueness is enabled | `INVALID_STATE` | | Instance already has a business ID (same or different value) | `INVALID_STATE` | | Value fails validation (for example, exceeds 256 characters) | `INVALID_ARGUMENT` | | Not authorized | `UNAUTHORIZED` / `FORBIDDEN` | Required permission: `UPDATE_PROCESS_INSTANCE` on the process definition. ### Uniqueness control With uniqueness control, you can ensure that only one active root process instance exists for a given **process definition, tenant, and business ID**. This prevents duplicate processing of the same business case. Uniqueness is checked against **active root process instances**. - A **root process instance** is a process instance that was started directly, not created by a call activity. Child process instances created via call activities don't count toward the uniqueness check, even though they inherit the parent's business ID. - When uniqueness control is enabled, creating a root process instance is rejected if another **root** process instance of the same process definition is already active with the same business ID. The rejection returns an `ALREADY_EXISTS` error (HTTP `409 Conflict`). - Once a process instance is no longer active (completed or terminated), you can use its business ID to create a new process instance. :::note Retroactive enforcement Uniqueness control is **retroactive**. When you enable it, business IDs that were already assigned to active process instances _before_ the feature was turned on are taken into account. This prevents duplicate instances from being created after the feature is enabled, even if duplicates already existed before activation. ::: Uniqueness control is opt-in: - For SaaS, configure this in the [cluster configuration via Camunda Hub](/components/saas/clusters/settings.md#enable-business-id-uniqueness). - For Self-Managed, set the [`camunda.process-instance-creation.business-id-uniqueness-enabled`](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#process-instance-creation) property in the application config (for example, `application.yaml` or as an environment variable). :::note When a business ID is specified, the partition for the new process instance is determined deterministically by **hashing the business ID**, rather than using the default round-robin distribution. This ensures that uniqueness checks occur on a single partition. Be aware that this may result in uneven distribution of instances across partitions if business IDs are not well distributed. ::: #### Multi-tenancy scope In a multi-tenant environment, uniqueness is enforced **per tenant and process definition**. Process instances belonging to different tenants can use the same business ID and process definition without conflict. For example, two active root process instances in different tenants may share the same business ID and process definition. ### Process instance migration When a process instance with a business ID is [migrated](/components/concepts/process-instance-migration.md) to a different process definition, the business ID is preserved and carried over to the **target** process definition. The business ID remains immutable; it cannot be changed or removed as part of the migration. After migration, the **source** process definition is no longer associated with the business ID. Migration intentionally **bypasses uniqueness control checks**, because migration operates on existing instances rather than creating new ones. It is a deliberate operator action with accountability provided by the audit log. As a result: - Migration is never rejected due to a business ID conflict at the target process definition. The target definition may end up with more than one active root process instance with the same business ID. - When uniqueness control is enabled, a new process instance with the same business ID can be created for the **source** process definition, since it is no longer associated with the migrated instance. ### Limitations - **Jobs** carry the business ID in the job activation response but cannot be searched or filtered by business ID. No UI surface exposes job-level business ID visibility. - When using [cluster scaling](/self-managed/components/orchestration-cluster/zeebe/operations/cluster-scaling.md) to increase the number of partitions, new process instances created with a business ID are only distributed across the original set of partitions, not to any newly added partitions. ## Tags Process instance tags are lightweight, immutable labels you can attach when creating a process instance via the API or clients. Tags are inherited by all jobs created from that instance. They help downstream workers and external systems make quick routing or decision choices without inspecting full variable payloads. ### Tag format and constraints - A tag is a case-sensitive string. - Format (regex): `^[A-Za-z][A-Za-z0-9_\-:.]{0,99}$` - Must start with a letter (A–Z or a–z). - Remaining characters may be alphanumeric, underscore (`_`), hyphen (`-`), colon (`:`), or dot (`.`). - Length: 1–100 characters. - Maximum of 10 unique tags per process instance (duplicates are ignored). - Order is not guaranteed; treat the set as unordered. - Tags cannot be modified after creation If validation fails during process instance creation (for example, too many tags, invalid pattern, or length), the create request is rejected with a 4xx error. ### Semantics - Tags are included in process instance search responses and in activated job payloads. - Tags are immutable after creation - cannot be added, changed, or removed after process instance has been created. - Search filtering uses AND semantics: an instance must contain all requested tags (it may contain additional tags). Partial or wildcard matching is not supported. - Tags are exported with the process instance and with job entities starting in 8.8 by the default exporters. - Tags are not shown in web applications (such as Operate and Tasklist) — they are API/client-only metadata. ### Use cases - Routing and prioritization (for example, `priority:high`) - Business or domain identifiers from internal or third-party systems (for example, `reference:1234`, `team:accounting`, `origin:crm`) - Cross-system correlation keys without exposing full variable payloads (for example, `trace-id:abcd-1234`, `crm-id:3004`) - Analytics segmentation (for example, `region:emea`, `channel:web`) - Feature rollout or experiment grouping (for example, `experiment:checkout-v2`) ### Guidelines - Do not store secrets or PII; tags propagate with jobs and exports. - Prefer concise `key:value` or `key` patterns for consistency. - Use variables (not tags) for mutable or large data. - Establish internal naming conventions (for example, prefixes like `env:` or `dept:`) for governance. ### Examples Create with tags: ```bash curl -L 'http://localhost:8080/v2/process-instances' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d '{ "processDefinitionId": "order-process", "processDefinitionVersion": 3, "tags": ["channel:web", "reference:1234", "region:emea"], "variables": { "orderId": "1234" } }' ``` ## Next steps - [About process modeling](/components/modeler/about-modeler.md) - [Automating a process using BPMN](/components/modeler/bpmn/automating-a-process-using-bpmn.md) --- ## Process instance deletion Use process instance deletion to permanently remove all data associated with a completed or terminated process instance. :::warning Deletion is irreversible. Restore deleted data only by restoring a backup of your cluster. ::: Delete a process instance using the [delete process instance endpoint](/apis-tools/orchestration-cluster-api-rest/specifications/delete-process-instance.api.mdx), or multiple using the [delete process instances endpoint](/apis-tools/orchestration-cluster-api-rest/specifications/delete-process-instances-batch-operation.api.mdx). :::note You can also delete a process instance in Operate. See the [Operate user guide](../operate/userguide/delete-finished-instances.md). ::: ## Limitations You can delete only process instances in a completed or terminated state. This preserves consistency and integrity within the system. If a process instance is still active, cancel it first using the [cancel process instance API](/apis-tools/orchestration-cluster-api-rest/specifications/cancel-process-instance.api.mdx). ### Limitations with call activities When process instances are linked through call activities, deletion is scoped to the selected instance only. Related parent or child process instances are not deleted automatically. #### Delete a called process instance If you delete a process instance that was created by a call activity, the parent process instance is not affected. Only the called process instance data is deleted. In Operate, the parent process instance remains visible, but you can’t navigate to the deleted called process instance. #### Delete a parent process instance If you delete a parent process instance that contains a call activity, the called process instance is not affected. Only the parent process instance data is deleted. In Operate, the called process instance remains visible, but navigating to it shows an empty screen. ## Eventual consistency Process instance deletion runs asynchronously. Depending on how many process instances you delete, it can take time for the data to be removed and for the process instance to disappear from Operate. ## Technical details This section explains how process instance deletion is handled internally to help you understand timing and consistency behavior. Deleting one or more process instances uses [batch operations](./batch-operations.md). The Zeebe engine queries [secondary storage](/self-managed/concepts/secondary-storage/index.md) for process instances to delete. For each instance found, the engine writes a delete command to the log, which results in a deleted event. Exporters consume the deleted event and write a record to secondary storage to mark the process instance for deletion. An asynchronous scheduled task then deletes all data associated with each marked process instance. ```mermaid sequenceDiagram V2 API->>+Engine: Delete process instances with filter Engine->>Engine: Create batch operation Engine->>-V2 API: Batch operation create response Engine->>+Secondary storage: Query process instances activate Engine Secondary storage->>-Engine: Return process instance keys loop for each process instance key Engine->>Engine: Write DELETED event for each process instance key Engine->>+Exporter: Export DELETED event deactivate Engine Exporter->>-Secondary storage: Mark process instance for deletion end note over V2 API,Deletion job: Everything below this note happens asynchronously. Deletion job->>+Secondary storage: Retrieve marked process instance keys activate Deletion job Secondary storage->>-Deletion job: Return process instance keys loop for each process instance key Deletion job->>Deletion job: Delete associated data end deactivate Deletion job ``` --- ## Process instance migration Process instance migration fits a running process instance to a different process definition. This can be useful when the process definition of a running process instance needs changes due to bugs or updated requirements. While doing so, we aim to interfere as little as possible with the process instance state during the migration. For example, a migrated active user task remains assigned to the same user if no implementation migration occurs. This principle applies to all parts of the process instance. Use the migration command [RPC](/apis-tools/zeebe-api/gateway-service.md#migrateprocessinstance-rpc) or [REST](/apis-tools/orchestration-cluster-api-rest/specifications/migrate-process-instance.api.mdx) to change the process model of a running process instance. You can also migrate process instances using Operate's UI — see the [Operate user guide](../operate/userguide/process-instance-migration.md). :::tip If you need to repair a broken process instance without changing the process definition, use [process instance modification](./process-instance-modification.md) instead. ::: ## Migrating from Camunda 7 This page covers migrating running Camunda 8 process instances to a different Camunda 8 process definition. If you're moving process instances from Camunda 7 to Camunda 8, use the [Data Migrator](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/index.md) instead. The two features restrict different things—check the [limitations of the one you're using](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/limitations.md). ## Change the process instance flow for inactive parts Process instance migration allows you to adjust the process model of your process instance to fit to new requirements. Let's consider an example. The process instance below contains a completed start event, an active service task `A`, and an end event. Once you complete the service task `A`, the process will reach the end event. ![The process instance is waiting at the active service task A.](assets/process-instance-migration/migration-basic_before.png) However, the requirements of our process have changed. Instead of completing the process, we want to add a user task `B` after the service task `A` and before the end event. ![In the new process version the service task A flows to a user task B.](assets/process-instance-migration/migration-basic_target.png) We can create new process instances according to this new process model after deploying it, but we also want our active process instances to receive the update and reach user task `B` when service task `A` is completed. To achieve this, we can migrate our process instance from its current process to the newly deployed one. You must provide a migration plan with mapping instructions to the target process definition to clarify your intentions. In our example, we'll provide a mapping instruction from source element ID `A` to target element ID `A`. This means we expect any active element instances of element with ID `A` to be migrated to the element with ID `A` in the target process. Therefore, our process instance will continue to be active at service task `A`. ![Once the service task A is completed, the process instance will flow to the user task B.](assets/process-instance-migration/migration-basic_after.png) After migrating our process instance, we can now complete the service task `A` to reach user task `B`. Process instance migration allows you to change the inactive parts of the process instance. In our example, we placed a user task `B` between the active service task `A` and the inactive end event. We did not change the active service task `A`, just the steps that follow. ## Change the active elements Sometimes your requirements change so much that the currently active element no longer exists in the new process version. Consider the following example: the process contains a completed exclusive gateway, taking the sequence flow to the service task `A` which is currently active. It did not take the sequence flow to service task `B`, so it is inactive. ![The process instance is waiting at the active service task A while service task B is inactive.](assets/process-instance-migration/migration-active_before.png) Due to changed requirements, our process model no longer contains the exclusive gateway, nor the service tasks `A` and `B`. Instead, it only contains a new service task `C`. ![The target process only contains a service task C.](assets/process-instance-migration/migration-active-elements-target.png) We can migrate the active service task `A` to this new service task `C` by providing a mapping instruction from source element ID `A` to target element ID `C`. ![After migrating the process instance, it is waiting at service task C.](assets/process-instance-migration/migration-active_after.png) :::note You cannot map an element to an element of a different type. An active service task of a process instance can only be mapped to a service task in the target process. It cannot be mapped to a user task as this changes the element type. Also note that the [jobs, expressions, and input mappings](#jobs-expressions-and-input-mappings) are not recreated. So, while service task `C` is active in the target process, the associated job still has the job type from when it was associated to service task `A`. ::: :::tip If you need to adjust the job type to its new element, you can use [process instance modification](./process-instance-modification.md) to recreate the service task. Simply cancel the service task instance, and add a new instance of the service task. ![The process instance can be modified to recreate the service task's job.](assets/process-instance-migration/migration-active_after-modification.png) ::: ## Correct mistakes in a process instance Process instance migration can also be used to correct mistakes that led to an [incident](/components/concepts/incidents.md) in a process instance. Let's consider an example. A user deployed a process with a user task `A`. The instance of that process always receives two variables (one boolean and one string). By accident, the string is used in the condition. To fix the problem, the process model should be updated to use the boolean variable instead. A process instance gets stuck at this user task, and an incident is created to inform the user of this problem. ![After creating the process instance, an incident is created for the service task A.](assets/process-instance-migration/migration-process_instance_with_incident.png) To correct the problem in the process instance, the user can take the following steps: 1. Correct the mistake in Modeler by creating a new version of the process definition. 2. Deploy the new process version where the boolean variable is used in the condition. 3. Migrate the process instance to the new process version. 4. Resolve the incident. Afterward, the process instance will continue as expected: ![After migrating the process instance, the input mapping is corrected and the incident is resolved by retry. Afterward, the process instance will continue as expected:](assets/process-instance-migration/migration-process_instance_with_incident_resolved.png) ## Migrate active elements inside subprocesses Active elements located inside subprocesses can be migrated as part of a process instance migration. Let's consider an example where we want to migrate an active element that is located in a subprocess. ![The service task A is inside the subprocess A.](assets/process-instance-migration/migration-subprocess_before.png) After migrating active element `A` to `B` and `Subprocess A` to `Subprocess B`, the process instance will look like this: ![After migrating the process instance, it is waiting at service task B inside the Subprocess B.](assets/process-instance-migration/migration-subprocess_after.png) :::note A mapping instruction must be provided from the process instance's subprocess ID to the target subprocess ID to migrate subprocesses. ::: :::note You cannot migrate an active embedded subprocess to an event subprocess. Additionally, changing the scope of a subprocesses during migration is not possible. ::: ### Call activities and called process instances Active call activities can be migrated like any other element. The called process instance is not changed when migrating the call activity. You can migrate a called process instance in the same way as a regular process instance. ## Migrate active elements inside ad-hoc subprocesses Active elements located inside ad-hoc subprocesses can be migrated as part of a process instance migration. Consider the following example, where you want to migrate an active element that is located in an ad-hoc subprocess: ![The service task A is inside the ad-hoc subprocess A.](assets/process-instance-migration/migration-adhoc-subprocess_before.png) After migrating active element `A` to `B` and `Ad-Hoc Subprocess A` to `Ad-Hoc Subprocess B`, the process instance will look like this: ![After migrating the process instance, it is waiting at service task B inside the Ad-Hoc Subprocess B.](assets/process-instance-migration/migration-adhoc-subprocess_after.png) :::important To migrate ad-hoc subprocesses, you must provide a mapping instruction from the process instance’s ad-hoc subprocess ID to the target ad-hoc subprocess ID. Changing the scope of ad-hoc subprocesses during migration is not possible. ::: ## Migrate job worker user tasks to Camunda user tasks You can migrate user tasks with a job worker implementation to Camunda user tasks by providing mapping instructions between the source and target user tasks. The target Camunda user task preserves `candidate groups`, `candidate users`, `due date`, `follow-up date`, and the `form id` or `form key` from the source task, as well as `customHeaders` set in the job. The target user task uses the [priority](../../tasklist/userguide/defining-task-priorities) defined in the target user task definition, or a default value of 50 if none is defined. :::important When you migrate a job worker user task to a Camunda user task: - Embedded forms are not supported. The form defined in the target user task definition is used. - The current `assignee` is not preserved. The task is assigned to the initial assignee defined in the target user task definition. ::: :::note Incidents on the job worker user task need to be resolved before migrating to a Camunda user task, otherwise the migration will fail. ::: ## Deal with catch events An exception to changing the process instance state is specific to catch events. This is necessary to ensure that the process instance can be executed according to the new process definition. It allows you to add or remove catch events from an active element. At the same time, you can leave an existing catch event unchanged. This section explains how to deal with catch events when migrating a process instance. You decide what happens to the associated event subscription through the mapping instructions for the catch events: - Migrate catch events: if a catch event is mapped, the associated subscription is migrated. - Remove catch events: if a catch event in the source process is not mapped, then the associated subscription is closed during migration. - Add catch events: if a catch event of the target process is not the target of a mapping instruction, then a new subscription is opened during migration. ### Migrate catch events Catch events can be migrated by providing a mapping instruction between the source and the target catch event. Providing a mapping between catch events ensures that the event subscription in the source process is preserved after the migration. In the following section we will discuss situations where mapping a catch event may or may not be useful. Let's explain both mapping and non-mapping scenarios with examples. #### A mapping instruction is provided between the catch events An active user task has been waiting for a timer boundary event. The timer boundary event is defined as a duration of one week. The user task has not been completed and has already spent five days waiting for the timer. ![The process instance is waiting at the active user task A with a timer boundary event attached.](assets/process-instance-migration/migration-catch-event-source.png) Now we want to [change an inactive part of the process](#change-the-process-instance-flow-for-inactive-parts) by adding a user task after the timer boundary event. Instead of waiting for the full time defined by the target process' timer boundary event, we only want to wait for the remaining two days. To achieve this for the example above, the mapping between active user tasks `A` -> `A` and timer boundary events `Timer1` -> `Timer2` must be provided. This ensures the timer is migrated (_the associated subscription is migrated_) and the duration is preserved. Assuming that the timer boundary event is defined as 2 weeks duration in the target process, the process instance will look as follows after the migration: ![The process instance is waiting at the active user task A with the migrated timer boundary event attached.](assets/process-instance-migration/migration-catch-event-different-target.png) Mapping catch events applies to other event types as well. For example, if you want to keep the message name the same for a message event subprocess, you should map the start event of the event subprocess when migrating the process instance. Another example would be to preserve a signal name for an intermediate signal catch event attached to an event-based gateway. In this case, you should map the signal catch event to the one in the target while migrating the process instance. #### No mapping instruction is provided between the catch events Before moving forward with the example, there are two important scenarios to consider because they affect the output after the migration: - The catch event in the source process is identical to the catch event in the target process. - There are changes between these catch events, for example, the message name is different in the target. Let's consider again the same example above: An active user task has been waiting for a timer boundary event. The timer boundary event is defined as a duration of one week. The user task has not been completed and has already spent five days waiting for the timer. ![The process instance is waiting at the active user task A with a timer boundary event attached.](assets/process-instance-migration/migration-catch-event-source.png) In the first scenario, the catch event in the source process is identical to the catch event in the target process: This time we want to reset the timer and wait for the full week again. To achieve this for the example above, only a mapping between active user tasks `A` -> `A` must be provided. This will cancel the timer (_associated subscription is closed_) and create a new one (_a new subscription is opened_). After the migration the process instance will look like following: ![The process instance is waiting at the active user task A with a new timer boundary event attached.](assets/process-instance-migration/migration-catch-event-identical-target-trigger-updated.png) In the second scenario, there are changes between these catch events: Now, we want to reset the timer and wait for two weeks as in the target process definition. To achieve this for the example above, only a mapping between active user tasks `A` -> `A` must be provided. This will cancel the timer (_associated subscription is closed_) and create a new one (_a new subscription is opened_). After the migration the process instance will look as follows: ![The process instance is waiting at the active user task A with a new timer boundary event attached.](assets/process-instance-migration/migration-catch-event-different-target-trigger-updated.png) Same as above, omitting the mapping instruction for catch events applies to other event types as well. For example, if you want to change the message name for a message event subprocess start event, you must omit mapping the message start event when migrating the process instance. Another example would be to update a signal name for an intermediate signal catch event, you must omit mapping the signal catch event when to the one in the target while migrating the process instance. ### Add or remove catch events You can also add or remove catch events. Let's consider a process instance awaiting at a service task `A`. ![The process instance is waiting at the active service task A without any boundary events attached.](assets/process-instance-migration/migration-boundary-event_before.png) You can migrate it to a process definition where a message event subprocess with message start event `M` is added to the process. To do so, you only have to map element `A` to element `A` in the target process. After migrating active element `A`, the process instance is newly subscribed to the message boundary event `M` (_a new subscription is opened_). ![After migrating, the process instance is subscribed to the newly introduced message event subprocess start event.](assets/process-instance-migration/migration-catch-event-target-added.png) Likewise, you can migrate the process instance back to the previous process definition where no message event subprocess is defined. To do so, you only have to map element `A` to element `A` again. After migrating active element `A`, the process instance is no longer subscribed to the message start event `M` (_associated subscription is closed_). ![After migrating back, the process instance is no longer subscribed to the message boundary event](assets/process-instance-migration/migration-boundary-event_before.png) Likewise, adding and removing catch events applies to all other supported catch event types as well. For instance, an intermediate signal catch event can be added or removed in the same way as the message event subprocess in the example above. :::tip Currently, a mapping instruction must be provided between catch events to migrate message catch events if the target catch event has the same message name. Therefore, it is not possible to re-create message catch events with the same message name in the target process definition. While we're working on resolving this, you can migrate this case by providing a mapping between the boundary events. ::: ### Compensation boundary events Compensation boundary events can be migrated similarly to other catch events, with one exception. If a compensation boundary event is added, a new compensation subscription is only opened for activity instances that are currently active, or haven't been started yet. For activity instances that have already finished, no new compensation subscription will be opened. Consider the following example where the instance is waiting on service task `A`: ![The instance waiting on service task A.](assets/process-instance-migration/migration-compensation_before.png) The target process definition contains a compensation boundary event attached to service task `A`: ![The target process definition contains a compensation boundary event attached to service task A.](assets/process-instance-migration/migration-compensation_after.png) If the process instance is migrated by providing mapping instruction between service tasks `A` -> `A`, the compensation subscription will be created on completion of the element `A`. However, if the process instance is migrated by providing mapping instruction between service tasks `A` -> `B`, then triggering compensation throw event afterward will not compensate `A`. This is because the subscription is only opened when completing a task with a compensation boundary event. ## Deal with gateways Process instance migration allows you to migrate several scenarios for gateways: - An active exclusive gateway with an incident can be migrated like any other active element. - Parallel and inclusive gateways can be involved in [additional scenarios](#migrate-joining-parallel-and-inclusive-gateways). ### Migrate joining parallel and inclusive gateways Joining parallel and inclusive gateways with taken incoming sequence flows, and which are still waiting for more incoming sequence flows, require a mapping instruction similar to active elements. For migrating joining gateways, the following conditions must be true: - The joining gateway in the process instance must be mapped to the target gateway. - The target gateway must have at least the same number of incoming sequence flows as the source gateway. Consider the following example: The process instance is waiting at the joining parallel gateway, with an incoming sequence flow `flow1`, taken after the element `A` completes. Element `B` is still active and waiting at the user task. ![The instance waiting on joining gateway.](assets/process-instance-migration/migration-joining-gateway-before.png) Then, the process definition is updated to include element `C` before the joining gateway. To migrate the process instance, provide the following mapping instructions: - From the active element `B` to the target element `B`. - From the joining parallel gateway instance `gateway1` to the target joining parallel gateway `gateway2`. - From the sequence flow `flow1` to the target sequence flow `flow2`. After the migration, the process instance will look like the following: In the example above, another element `C` is added before the joining gateway in the target process definition. After the migration, element `B` must still be completed for the process instance to continue through the gateway. ## Process definitions and versions So far, we've only discussed migrating a process instance to a new version of its process definition. You are free to migrate your process instance: - From an older version to a newer version of the same process definition. - From a newer version to an older version of the same process definition. - To a different process definition altogether. :::note You do not have to provide a mapping instruction from the process instance's process ID to the target process ID. ::: ## Jobs, expressions, and input mappings We do not recreate jobs, reevaluate expressions, and reapply input mappings of the active elements. We also don't adjust any static values if they differ between the two process definitions. Any existing variables, user tasks, and jobs continue to exist with the same values as previously assigned. Let's consider an active service task that created a job when it was activated with type `send_mail`. In the target process definition, the job type expression is changed as follows: ```feel = order.next_step ``` However, on migrating the process instance this new job type expression is not evaluated. Instead, the job keeps all properties it had before the migration, including the job type. :::tip You can use [process instance modification](./process-instance-modification.md) to terminate and activate the service task if you want to create the job according to the new service task's definitions. This results in new keys for the service task as well as the job. ::: ## Internal Execution In the following example, we will explain the internal execution steps of process instance migration. It is recommended that you read these steps to fully understand the power of process instance migration. Migration of a process instance consists of the following steps: - Validation - Migration of process instance, and global variables - Migration of each active element (including associated jobs, incidents, local variables, and event subscriptions) If any of the steps fail, the migration is rejected and a rejection message that explains the reason is returned. For example, if a mapping is not provided for an active element, the migration is rejected with an error message indicating that the mapping is missing. As a result, the process instance will not be migrated and remains in its current state. The migration runs in a **transactional** manner, meaning that it is migrating all active elements or nothing. ### Validation The migration plan is validated before the migration is executed. The validation starts with validating the mapping instructions provided in the migration plan. For example, the validation checks if the source element ID refers to an existing element in the process instance's process definition. Later, while attempting to migrate each active element, each limitation mentioned in the [limitations section](#limitations) is validated. For example, the flow scope of an active element is validated to ensure that it is not changed during migration. ### Migration of process instance, and global variables After all validations are successful, the migration of the outermost active element which is the process instance itself is started. At this point, the process instance's `processDefinitionKey`, `bpmnProcessId`, and `version` properties are updated to the target process definition. The global variables are also migrated to the target process definition. ### Migration of each active element The execution of the migration is done in a breadth-first manner. The first active child instance of the process instance is migrated followed by the migration of the next active child instance. Later, the migration of the active child instances of each active child instance is executed. In this stage, jobs, incidents, local variables, and event subscriptions contained in each active element is also migrated. While traversing each active element, the migration plan is used to determine the target element for each active element. For each active element, `processDefinitionKey`, `bpmnProcessId`, `elementId`, `version` properties are updated to the target process definition. #### Migration of catch event subscriptions The following operations are performed in respective order for each active element as the execution continues to migrate each active element: - If a catch event exists in the source process instance and is not part of the migration plan, the subscription to the catch event is removed. - If a catch event exists in the target process definition and is not part of the migration plan, a new subscription is created for the catch event. - If a catch event in the source process is mapped to a catch event in the target process, the subscription is migrated. While migrating each catch event subscriptions, the catch event's `processDefinitionKey`, `bpmnProcessId`, `elementId` properties are updated to the target process definition. :::note It is **possible** to change the interrupting status during catch event subscription migration. ::: ## Limitations Not all process instances can be migrated to another process definition. In the following cases, the process instance can't apply the migration plan and rejects the migration command. - Process instance migration can only migrate active process instances, i.e. existing process instances that have not yet been completed, terminated, or banned. - All active elements require a mapping. - The number of active elements cannot be changed. You can use [process instance modification](./process-instance-modification.md) to achieve this instead. - The target process definition must exist in Zeebe, i.e. it must be deployed and not yet deleted. - The migration plan can only map each `sourceElementId` once. - A mapping instruction's `sourceElementId` must refer to an element existing in the process instance's process definition. - A mapping instruction's `targetElementId` must refer to an element existing in the target process definition. - Catch event limitations: - A mapping instruction cannot detach a catch event from an active element. For example, a service task `A` has timer boundary event `T1` and will be migrated to the service task `B` has timer boundary event `T2`. If a mapping instruction between `A` -> `B` is provided, a mapping instruction for `T1` can only refer to `T2`. - Each catch event can only be the target of a mapping instruction once. - Two catch events in the source cannot be mapped to the same catch event in the target. - A catch event in the source cannot be mapped to a different type of catch event in the target. - The message subscription for the message catch event in the source process instance needs to be fully distributed before the migration. - Multi-instance body limitations: - Each child instance of a multi-instance body should be migrated separately because they belong to another process instance. - It is not possible to migrate a parallel multi-instance body to a sequential multi-instance body and vice versa. - Scope limitations: - You cannot migrate an active embedded subprocess to an event subprocess. See [migrate active elements inside subprocesses](#migrate-active-elements-inside-subprocesses). - You cannot change the scope of a subprocess during migration. - Changing the scope of an ad-hoc subprocess during migration is not possible. See [migrate active elements inside ad-hoc subprocesses](#migrate-active-elements-inside-ad-hoc-subprocesses). - Mapping instructions can only change the user task implementation from a job-worker user task to a Camunda user task, but not vice versa. - You cannot migrate embedded forms when migrating a job worker user task to a Camunda user task. The migration uses the form defined in the target user task definition. See [migrate job worker user tasks to Camunda user tasks](#migrate-job-worker-user-tasks-to-camunda-user-tasks). - Provide a mapping instruction between catch events to migrate message catch events when the target catch event has the same message name. You cannot re-create a message catch event with the same message name in the target process definition. See [deal with catch events](#deal-with-catch-events). The following limitations exist that may be supported in future versions: - Only [supported BPMN elements](#supported-bpmn-elements) can be migrated. - The following scenarios cannot be migrated: - An element that becomes nested in a newly added subprocess - An element that was nested in a subprocess is no longer nested in that subprocess - Mapping instructions cannot change the element type - The process instance must be in a wait state, i.e. waiting for an event or external input like job completion. It may not be taking a sequence flow or triggering an event while migrating the instance. A full overview of error codes can be found in the migration command [RPC](/apis-tools/zeebe-api/gateway-service.md#migrateprocessinstance-rpc) or [REST](/apis-tools/orchestration-cluster-api-rest/specifications/migrate-process-instance.api.mdx). :::tip If process instance migration does not yet support your specific case, you can use [cancel process instance](../../apis-tools/zeebe-api/gateway-service.md#cancelprocessinstance-rpc) and [run a process segment](./process-instance-creation.md#run-process-segment) to recreate your process instance in the other process definition. Note that this results in new keys for the process instance and its associated variables, element instances, and other entities. ::: ### Supported BPMN elements The following BPMN elements are supported by the migration tool. #### Subprocesses #### Tasks #### Gateways #### Markers #### Events Type Start Intermediate Event Subprocess Event Subprocess non-interrupting Catch Boundary Boundary non-interrupting Message Timer Error Signal Escalation Compensation Conditional ## Use at your own risk Process instance migration is a powerful tool to change your process instances. However, use it with care. You can migrate the process instance to create situations that are not reachable by the regular execution. Consider the following example: ![The process instance waits on a task after a parallel joining gateway.](assets/process-instance-migration/migration-use-at-your-own-risk.png) The process instance completed the first tasks `A` and `B` and waits on task `C`. We could apply the following migrations, but the process instance may end up in an unintended situation: - If we map task `C` to `A` or `B`, the process instance is stuck on the parallel gateway. - If we map task `C` to `D`, the variables that would be provided by the message are not set, the task `D` could be processed with the wrong input. The process instance doesn't detect these situations. It is up to you to apply suitable migrations. When in doubt, we recommend testing your migration on a non-production cluster or using [Camunda Process Test](../../apis-tools/testing/getting-started.md). :::tip Often it's safer to migrate in multiple smaller steps rather than in one big migration. For example, you can start by migrating the process instance introducing changes to the inactive parts only to keep the current situation unchanged. In some cases it's useful to prepare the process instance before migrating. If your process needs specific variables immediately after migrating, you can set these before migrating the process instance. Both global and local variables are migrated automatically. Before migrating, you can also use process instance modification [activating an element](./process-instance-modification.md#activate-an-element) to avoid the process instance getting stuck on a parallel gateway, or [terminate an element instance](./process-instance-modification.md#terminate-an-element-instance) to get rid of a parallel flow. ::: --- ## Process instance modification Process instance modification is a powerful feature for repairing a running process instance. For example, a process instance may be stuck at an element, waiting for an event, or following an unintended path because an external system is unavailable or doesn't respond as expected. Use the [modification command](/apis-tools/zeebe-api/gateway-service.md#modifyprocessinstance-rpc) to skip or repeat a step in the process. Consider the following example: ![The process instance is stuck in the message catch event.](assets/process-instance-modification/process-instance-modification-example-1.png) The process contains two service tasks and a message catch event in between. The process instance completed the first task `A` and waits on the message catch event `B`. An external system will publish the message, but the external system is not available and can't continue the process. The process instance is stuck. ![We use the modification to skip the event and continue on the next task.](assets/process-instance-modification/process-instance-modification-example-2.png) We use the modification to repair the process instance. We "move the token" from the catch event `B` to the next task `C`. This operation is presented by two instructions in the modification command: - Terminate the instance of the catch event `B`. - Activate the element `C`. ![After the modification is applied, the message catch event is terminated and the next task is active.](assets/process-instance-modification/process-instance-modification-example-3.png) As a result of the command, the process instances terminated the instance of catch event `B` and activated the task `C`. Now, the process instance is not stuck anymore and can continue in the process. Generally, the process instance modification command can contain multiple instructions: - To activate an element of the process. - To terminate an active instance of an element. Read more about the behavior of the instructions in the following sections. :::note Use the process instance modification only in exceptional cases to repair the process instance. It is not recommended using it as a part of the regular flow of the process; find additional details [here](#use-at-your-own-risk). Instead, model all possible cases explicitly in your process. ::: ## Activate an element We can use the modification command to activate an element of the process. Consider the following example: ![The process instance waits on task. We use the modification command to activate a task from a parallel flow.](assets/process-instance-modification/process-instance-modification-activate-an-element.png) The process instance completed the first task `A` and waits on task `B`. Task `C` is connected to task `A` by a non-interrupting message catch event. An external system will publish the message, but it is not available. To correct the state of the process instance, we modify it and activate task `C`. As a result, task `C` is active, and a job worker can pick it up. The process instance activates the element in the same way as the regular flow; for example, if the incoming sequence flow of the element would be taken. The activation of the element can include the following steps: - Apply the input variable mappings. - Create the event subscriptions; for example, of boundary events. - Apply additional logic depending on the element; for example, create a job for a service task. ## Activate a nested element We can use the modification command to activate an element of the process nested inside an embedded or an event subprocess. This is a special case of [activating an element](#activate-an-element). Consider the following example: ![The process instance waits on a task inside a subprocess. We use the modification command to activate a task from a parallel flow in the same subprocess.](assets/process-instance-modification/process-instance-modification-activate-nested-element.png) The process instance completed the first task `A` in the embedded subprocess. It passed the inclusive gateway and waited on task `B`. Task `C` is also connected to the inclusive gateway, but the condition didn't match. The condition should match, but the job worker for task `A` provided unexpected variables. To correct the state of the process instance, we modify it and activate task `C`. As a result, task `C` is active in the same instance of the subprocess as the other task `B`. The process instance activates the element always in an existing instance of its subprocess. If the subprocess doesn't have an active instance, the process instance creates a new instance of the subprocess first. The creation of the subprocess can include the creation of the event subscriptions; for example, of boundary events. In contrast to a regular activation of the subprocess, the process instance doesn't activate the start event of the subprocess or apply any input variable mappings. If the subprocess itself is nested in another subprocess, the same procedure is applied to this subprocess. :::note The process instance can't activate the element if the subprocess has more than one active instance. It can't decide in which instance of the subprocess to activate the element. As a result, the process instance doesn't apply the activation instruction and rejects the command. ::: ## Activate an interrupting event subprocess We can use the modification command to activate an interrupting event subprocess of the process. Consider the following example: ![The process instance waits on task. We use the modification command to activate an interrupting event subprocess in the same scope.](assets/process-instance-modification/process-instance-modification-activate-interrupting-event-subprocess.png) The process instance completed the first task `A` and waits on task `B`. Task `C` is embedded in an interrupting message event subprocess. An external system will publish the message and interrupt the process, but it is not available. To correct the state of the process instance, we modify it and activate the interrupting event subprocess. As a result, the event subprocess is active and enters the start event. But the activation doesn't interrupt the process instance and terminate task `B`. So, both tasks `B` and `C` are active. If we want to simulate the interrupting behavior of the event subprocess, we need to add a modification instruction to [terminate the instance](#terminate-an-element-instance) of the task `B`. If the start event of the event subprocess has output variable mappings, we may need to [set the variables](#set-variables) with the activation instruction. ## Set variables We can use the modification command to set one or more variables together by activating an element of the process. Consider the following example: ![The process instance waits on task. We use the modification command to activate a task inside a non-interrupting message event subprocess.](assets/process-instance-modification/process-instance-modification-set-variables.png) The process instance completed the first task `A` and waited on task `B`. Task `C` is embedded in a non-interrupting message event subprocess. An external system will publish the message, but it is not available. To correct the state of the process instance, we modify it and activate the task `C` inside the non-interrupting message event subprocess. Additionally, we add variable instructions to the modification to set variables that should be provided by the message. The process instance sets the variables before activating the given element. As a result, the variables are available when applying the input variable mappings and creating the event subscriptions of the element. A variable instruction can define the [scope](variables.md#variable-scopes) of the variables. If a scope is defined, the process instance sets the variables as **local** variables in the given scope. For example, set the message variables as local variables of the event subprocess. The scope must be a flow scope of the activating element. If no scope is defined, the process instance sets the variables **globally** in the root scope of the process instance. ## Terminate an element instance We can use the modification command to terminate an active element instance of the process instance. Consider the following example: ![The process instance waits on a task inside a nested interrupting event subprocess. We use the modification command to terminate the event subprocess.](assets/process-instance-modification/process-instance-modification-terminate-element-instance.png) The process instance completed the first task `A` in the embedded subprocess. It triggered the interrupting timer event subprocess and terminated task `B` inside the embedded subprocess. An external system published a message and triggered the non-interrupting message boundary event on the subprocess. The process instance waits on task `C` inside the event subprocess and on task `D` connected to the boundary event. The job worker for task `C` failed to complete the job successfully. To skip the task and continue the process instance, we modify it and terminate the element instance of the event subprocess. As a result, the process instance terminates the event subprocess and the element instance of the task `C` that is inside the event subprocess. Additionally, the process instance terminates the embedded subprocess because it doesn't contain active element instances anymore. Generally, the modification applies the following rules: - If the terminating element instance is a subprocess, it terminates all active instances in the subprocess. - If the terminating element instance is a call activity, it terminates the child process instance. - If the terminating element instance was the last active instance inside a subprocess, it terminates the subprocess. - If the terminating element instance was the last active instance of the process instance, it terminates the process instance. If a terminating element instance is not active, the process instance doesn't apply the termination instruction and rejects the command. :::note The process instance can't terminate the last active element instance of a child process instance. As a result, the process instance doesn't apply the termination instruction and rejects the command. Instead, we can terminate the call activity that created the child process instance. ::: Terminating a user task via modification does not trigger its [`canceling` user task listener](/components/concepts/user-task-listeners.md#trigger-a-user-task-listener). ## Move an element instance Use the modification command to move an element instance. The move operation combines termination and activation in a single instruction. It terminates the source element instance and activates a target element in one atomic operation. :::note This is the recommended way to relocate execution within a process instance, as it ensures the operation is performed atomically and correctly handles complex scenarios such as multi-instance subprocesses. ::: ### Use the move instruction API When using the [modification API](../../apis-tools/orchestration-cluster-api-rest/specifications/modify-process-instance.api.mdx), you can provide a move instruction with the following parameters: - **Source element**: Specify the element instance to move from, either by element ID or by element instance key. - **Target element**: Specify the element ID of the element to activate. - **Ancestor scope instruction** (for nested elements): Controls how the target element is activated within subprocess hierarchies. See [Move elements inside multi-instance subprocesses](#moving-elements-inside-multi-instance-subprocesses) for details on using this with multi-instance activities. ### Move elements inside multi-instance subprocesses Move modifications are supported for elements inside multi-instance subprocesses. When you move an element instance within a multi-instance subprocess, the process instance terminates only that specific element instance and activates exactly one target element in the same instance of the multi-instance subprocess. With move modifications in multi-instance subprocesses, you can relocate execution within a specific iteration of a multi-instance activity without affecting other instances or creating new instances of the multi-instance subprocess. The **ancestor scope instruction** provides three strategies for controlling how the target element is activated: - `direct`: Specify the key of the ancestor scope in which the element instance should be created. - `inferred`: Let the engine automatically find the appropriate ancestor element scope. - `sourceParent`: Use the source element's parent scope (recommended for moving between sibling elements inside multi-instance subprocesses, as it offers better performance than `inferred`). :::note While move modifications are supported for elements inside multi-instance subprocesses, add (activate) modifications are not. You cannot directly add or activate new tokens for elements inside multi-instance subprocesses. Use move modifications to relocate execution in multi-instance contexts. ::: ## Execute the modification instructions A modification command can contain multiple activation and termination instructions. The process instance applies these instructions in a specific order: 1. Apply all activation instructions. 2. Apply all termination instructions. The order of the instructions matters if the modification terminates the last active instances of the process instance or inside a subprocess, and activates an element in the process instance or the subprocess. Since the process instance applies the activation instructions first, the process instance or the subprocess still has an active instance and is not terminated. If the process instance can't apply one of the modification instructions, it rejects the modification command. For example, if one of the terminating element instances is not active. As a result, the process instance is not modified and is in the same state as before. It applies the instructions of a modification command in a **transactional** way (i.e. apply all or nothing). ## Limitations Currently, we can't modify the process instance in all possible ways. In the following cases, the process instance can't apply the modification instructions and rejects the modification command. - If the activating element is a BPMN element of the type: - A start event of a process or a subprocess - A boundary event - An event that belongs to an event-based gateway - A sequence flow - If the modification terminates all active instances of a child process instance. ## Use at your own risk Process instance modification is a powerful tool to repair a process instance. However, use it with care. You can modify the process instance to create situations that are not reachable by the regular execution. Consider the following example: ![The process instance waits on a task after a parallel joining gateway.](assets/process-instance-modification/process-instance-modification-use-at-your-own-risk.png) The process instance completed the first tasks `A` and `B` and waits on task `C`. We could apply the following modifications, but the process instance may end up in an unintended situation: - If we activate task `A` again, the process instance is stuck on the parallel gateway. - If we activate task `D` and don't set all variables that would be provided by the message, the task `D` could be processed with the wrong input. - If we activate task `E` inside the interrupting event subprocess, the process instance doesn't interrupt task `C` and the processing of the tasks could override variables. The process instance doesn't detect these situations. It is up to you to apply suitable modifications. When in doubt, we recommend testing your modification on a non-production cluster or using [Camunda Process Test](../../apis-tools/testing/getting-started.md). --- ## Process instance suspension Process instance suspension lets you temporarily freeze a running process instance without canceling it. The instance retains all its state during suspension, and execution continues from the same point when you resume it. Suspension is not a substitute for cancellation or deletion. The instance continues to occupy cluster resources during suspension, including storage for any commands that accumulate while suspended. Common use cases include: - Pausing execution while an upstream or downstream system is unavailable or misconfigured. - Preventing new jobs from being handed out to workers during a planned maintenance window. - Temporarily halting a batch of instances while an investigation is in progress. ## Suspend and resume a process instance Use the REST API to suspend or resume a single process instance: - [Suspend a process instance](/apis-tools/orchestration-cluster-api-rest/specifications/suspend-process-instance.api.mdx) - [Resume a process instance](/apis-tools/orchestration-cluster-api-rest/specifications/resume-process-instance.api.mdx) You can also suspend or resume multiple instances at once using batch operations: - [Suspend process instances (batch)](/apis-tools/orchestration-cluster-api-rest/specifications/suspend-process-instances-batch-operation.api.mdx) - [Resume process instances (batch)](/apis-tools/orchestration-cluster-api-rest/specifications/resume-process-instances-batch-operation.api.mdx) :::note You can also suspend and resume process instances in Operate. See the [Operate user guide](../operate/userguide/suspend-resume-process-instance.md). ::: A suspended process instance appears in search results with a `SUSPENDED` state. Filtering for `ACTIVE` instances does not include suspended instances. ## Behavior during suspension The following describes what happens to each element type when a process instance is suspended. ### Jobs Job handout is suppressed during suspension: workers can't pick up new jobs from a suspended process instance. Jobs already running in a worker before suspension are not interrupted. They keep executing, but any attempt to complete or fail the job during suspension is rejected. If the instance resumes before the worker finishes, the job can complete normally. If the worker completes or fails the job while the instance is still suspended, the job is re-activated on resume and executed again. Because of this at-least-once behavior, outbound actions performed by the job (for example, an API call or a message) may run more than once if they are not idempotent. ### Timers Timers don't trigger during suspension. When the instance resumes, any due timers fire immediately as a catch-up. For non-interrupting cycle timers, only one firing occurs on resume, even if the timer became due multiple times during suspension. ### Messages Message correlation doesn't happen during suspension. Messages without a TTL that arrive while the instance is suspended are discarded for that instance. Messages with a TTL may still correlate after resume, provided the TTL has not expired at the time the instance resumes. ### Signals Signals received while a process instance is suspended are discarded and never replayed. Unlike timers, there is no catch-up for signals on resume. ### Multi-instance If an active multi-instance body is suspended, child elements are spawned on resume according to the input collection at the time of suspension. Changes to the input collection during suspension (whether items are added or removed) are not taken into account once the multi-instance body has been created. ### Call activities Suspending a root process instance does not automatically suspend its call-activity children. Each child instance continues executing independently. If an active call activity has an interrupting boundary event and the child fires a matching event (for example, an error or escalation) while the parent is suspended, the call activity element of the suspended process instance transitions to terminated, while the boundary event becomes active. Completion of the boundary event is deferred until resume, but this means a suspended instance's state can change for this specific edge case. ## Allowed actions on suspended instances External commands against a suspended process instance are blocked, with the following exceptions: ### Variable updates You can update variables on a suspended process instance, including element-instance variables. The exception is user task variables, for which any operations are blocked during suspension. Variable changes take effect immediately. If a command recorded before suspension depends on a variable you modified or deleted during suspension, that command may fail on resume and create an incident. :::note Variable updates can be overwritten on resume if a buffered completion command carries an older value of the same variable. This happens when the completion was the next processing step at the time suspension was applied, so its command (including the pre-suspension variable value) was already buffered. ::: ### Cancellation You can cancel a suspended process instance. ### Resumption You can resume a suspended process instance. ## Impact on Connectors **Inbound connectors** publish messages to correlate with waiting process instances. Because message correlation is suppressed during suspension, data from an inbound connector that arrives while the instance is suspended is permanently lost for that instance. **Outbound connectors** run as service task jobs. Job handout is suppressed during suspension, so new outbound connector jobs aren't activated. A connector job already running in a worker when suspension occurs is not interrupted, but any completion or failure during suspension is rejected and the job re-executes on resume. If the connector action is not idempotent (for example, sending a message or charging a card), it may run more than once. ## In-flight batch operations Batch operations already queued against process instances that are subsequently suspended may fail on individual items if the instance state is incompatible with the queued operation. An individual item failure does not stop the rest of the batch. ## Technical implications This section explains how suspension is handled internally to help you understand processing impact, resume timing, and consistency behavior. ### Suspension and resumption in the engine At the engine level, suspension for a process instance means that: - Jobs waiting for execution are removed from the execution pool and re-added on resumption. - Completion and failure of already-active jobs are rejected during suspension. - Execution progress is stopped via command buffering: after suspension, the next command that would advance execution is buffered instead of processed. On resume, the engine drains the buffer in order, which restarts normal execution. Additionally: - On suspend, all active message subscriptions for the process instance are removed and re-added on resume. - Timer triggers are buffered during suspension and not rescheduled for recurring timers until they trigger on resumption. Timer expirations during suspension therefore increase the command buffer. ### Implications - **Broker PVC usage**: Buffered commands are stored on the broker's persistent volume (PVC). A process instance with many active tokens or long-lived subscriptions generates more buffered commands during suspension. Monitor PVC utilization when suspending large numbers of instances or instances with complex execution states. - **Suspension processing load**: Suspending process instances with a large number of active jobs or message subscriptions increases cluster load during the suspension window. - **Resumption processing load**: Resuming a process instance is roughly equivalent to simultaneously creating all active element instances that were suspended. Resuming a large number of instances, or an instance with many active tokens, can noticeably reduce processing throughput. - **Stale data in buffered commands**: Buffered commands are recorded at the time of suspension and may rely on data that changes while the instance is suspended. Because some actions are permitted during suspension (such as variable updates), a buffered command may operate on stale values when it drains on resume, potentially producing unexpected behavior or incidents. ### Resume failures In rare cases, a buffered command can't be drained, for example, if a preceding permitted change left the instance in an inconsistent state for that command. This typically surfaces as an incident on resume. In very rare cases, a drain failure may not surface as an incident if the failure is unrelated to process-instance-specific command processing. In that case, the process instance continues to show as `SUSPENDED` but can't complete draining. Check error logs for details and contact support if necessary. ## Limitations The following known limitations apply to process instance suspension. ### Instance size limit Under the default 4 MB `maxMessageSize` [configuration](./secret-resolution-and-job-activation.md#resolved-values-exceed-the-message-size), suspension may fail if a single process instance has: - More than 4,000 active jobs - More than 7,000 active message subscriptions - A mix of active jobs and subscriptions that together exceed the limit ### Suspension vs. banning Suspension is processed through the normal engine command pipeline, which means it isn't applied instantaneously. The suspend command is queued and processed in order behind other pending commands. If the engine is under high backpressure (for example, because a process instance is executing a tight loop or processing a very large input collection), the suspend command may be delayed significantly or rejected entirely. As a result, suspension isn't a reliable mechanism for immediately stopping a process instance that is causing high cluster load. In situations where a runaway instance must be halted urgently, cancellation is more appropriate and should be preferred over suspension. For context on how the engine handles runaway instances internally, see [banned process instances](../zeebe/technical-concepts/internal-processing.md#banned-process-instance). --- ## Processes A [process](/reference/glossary.md#process) is a defined sequence of distinct steps or tasks representing your business logic. For example, an e-commerce shopping experience or onboarding a new employee. Process orchestration is the technology that coordinates the various moving parts, or endpoints, of a business process, and sometimes ties multiple processes together. It helps you work with the people, systems, and devices you already have, while achieving goals around end-to-end process automation. With Camunda, you can orchestrate [human tasks](../../guides/getting-started-orchestrate-human-tasks.md), [microservices](/guides/getting-started-example.md), [APIs](/guides/getting-started-orchestrate-apis.md), and [AI agents](/guides/getting-started-agentic-orchestration.md) as endpoints in the same process. For example, an order fulfillment process could run a fixed sequence of steps, then hand off a step to an [AI agent](/reference/glossary.md#ai-agent) that decides which tools to call, before returning control to the next fixed step. A **[job worker](./job-workers.md)** implements the business logic required to complete a task. You can choose to write a worker as a microservice, or also as part of a classical 3-tier application, as a \(lambda\) function, via command line tools, etc. Running a process broadly requires three steps: 1. Deploy a process to Camunda 8. 2. Implement and register job workers for tasks in the workflows. 3. Create new instances of the process. However, if you haven't yet, design the process: ## BPMN Camunda uses **[Business Process Model and Notation (BPMN) 2.0](/components/modeler/bpmn/bpmn.md)** to represent processes. The visual nature of BPMN enables greater collaboration between different teams, and is employed by numerous organizations globally. ![process example](./assets/order-process.png) :::note New to BPMN? Visit our step-by-step introductory guide on [automating a process using BPMN](/components/modeler/bpmn/automating-a-process-using-bpmn.md) ::: ## Modeling BPMN Camunda offers two modeling tools to design and implement your diagrams: - [Camunda Hub](/components/hub/workspace/modeler/index.md): Integrate seamlessly with Camunda 8 SaaS and Self-Managed installations. - [Desktop Modeler](/components/modeler/desktop-modeler/index.md): Design, view, and edit models using this desktop application. Install and use Desktop Modeler locally, all while integrating your local development environment. :::note New to modeling a process? Visit our [getting started guide](/components/hub/workspace/modeler/collaboration/design-your-process.md). ::: ## Process execution The simplest kind of process is an ordered sequence of tasks. Whenever process execution reaches a task, [Zeebe](/components/zeebe/zeebe-overview.md) (the workflow engine inside Camunda 8) creates a job that can be requested and completed by a job worker. ![process-sequence](assets/order-process.png) Process orchestration typically follows the steps below: 1. A process instance reaches a task, and Zeebe creates a job that can be requested by a worker. 2. Zeebe waits for the worker to request a job and complete the work. 3. Once the work is complete, the flow continues to the next step. 4. If the worker fails to complete the work, the process remains at the current step, and the job could be retried until it's successfully completed. As Zeebe progresses from one task to the next in a process, it can move custom data in the form of [variables](/components/concepts/variables.md). Variables are key-value pairs and part of the process instance. ![data-flow](assets/process-data-flow.png) Any job worker can read the variables and modify them when completing a job so data can be shared between different tasks in a process. ### Agent-driven steps Not every step has to follow a deterministic path you model in advance. Where a decision can't be fixed up front, you can hand part of the process to an [AI agent](/reference/glossary.md#ai-agent). In the following order process, **Fetch items** is an AI agent rather than a fixed task. ![order process with an ai agent](assets/order-process-agent.png) Execution works as described above, with one difference: the AI agent chooses which activity runs next. 1. The process instance reaches the AI agent, which sends the prompt and the available tool definitions to a large language model (LLM). 2. If the LLM selects a tool, Camunda activates the matching activity inside the sub-process. **Check inventory**, **Reserve stock**, **Order from supplier**, and **Ask warehouse team** are service and user tasks, completed by the same job workers and users as any other task. 3. The tool result is passed back to the LLM, which decides whether more tool calls are needed. 4. Once the LLM returns a final response, the flow continues to **Ship parcel**. The LLM decides which tools to call and in what order. Camunda runs them, moves the same [variables](/components/concepts/variables.md), and applies the same retries, incident handling, and audit trail as the fixed steps around them. To learn more, see [agentic orchestration](/components/agentic-orchestration/agentic-orchestration-overview.md). --- ## Projects A project, also called a Camunda project, is the unit you build, test, and deploy together to form a Camunda solution. It contains a collection of process resources, such as an entry point process, supporting processes, DMN decisions, or forms, that often represent an end-to-end use case. A project is the same concept in Camunda Hub and in local development. In Camunda Hub, it's an [entity within a workspace](#projects-in-camunda-hub). In local development, it's a [folder with a `camunda.json` file](#projects-in-local-development). With [Git sync](../hub/workspace/manage-projects/git-sync.md), you can keep both representations of the project in sync. ## Example A consumer loan approval project might bundle: - A BPMN process as an entry point to define the workflow: `consumer-loan-application.bpmn` - DMN decision tables for business rules: `interest-rate-calculation.dmn` and `credit-score-calculation.dmn` - A form for user interactions: `loan-application-review.form` ## Projects in Camunda Hub In Camunda Hub, a [project](../hub/workspace/manage-projects/manage-projects.md) is an entity within a [workspace](./workspaces.md): workspaces contain projects, and projects contain files. You can treat files in a project as a single bundle or as independent resources. For example, you can: - [Take a snapshot](../hub/workspace/manage-projects/project-versioning.md) of the current state of all project files. - Manage individual [file versions](../hub/workspace/modeler/modeling/versions.md). - [Deploy an entire project](../hub/workspace/manage-projects/deploy-project.md) to an [environment](./environments.md) assigned to its workspace. - [Deploy individual project resources](../hub/workspace/modeler/run-or-publish-your-process.md#deploy-a-process). ## Projects in local development In local development with [Desktop Modeler](/components/modeler/desktop-modeler/projects.md) and [`c8ctl`](/apis-tools/c8ctl/getting-started.md), a project is a folder with a [`camunda.json`](/apis-tools/c8ctl/camunda-json.md) file (an empty `{}` is enough). A project works without Camunda Hub. When you deploy a directory inside a project with [`c8 deploy`](/apis-tools/c8ctl/development-workflows.md#deploy-a-directory), `c8ctl` deploys the whole project. Unlike in Camunda Hub, Desktop Modeler always deploys all project resources together. ### Migrate from process applications Camunda projects supersede process applications, but Desktop Modeler and `c8ctl` continue to support the `.process-application` marker file. To migrate, rename `.process-application` in your project root to `camunda.json`. If a folder contains both files, `camunda.json` takes precedence. For `c8 watch`, the `--project` flag replaces `--process-application` and `--pa`, which remain as deprecated aliases. ## Next steps Read more about how to use projects: - [Workspaces](./workspaces.md) - [Environments](./environments.md) - [Using Camunda Hub and Desktop Modeler together](/components/modeler/using-hub-and-desktop-modeler-together.md#camunda-projects) - [Projects in Camunda Hub](/components/hub/workspace/manage-projects/manage-projects.md) - [Projects in Desktop Modeler](/components/modeler/desktop-modeler/projects.md) - [`camunda.json` project descriptor reference](/apis-tools/c8ctl/camunda-json.md) --- ## Resource deletion Use resource deletion to remove resources from a cluster when they are no longer needed or should no longer be used. Deleting resources: 1. **Frees storage space**, as Zeebe no longer needs to keep the definition in its state. 2. **Prevents new instances from being created**, which can help avoid usage of faulty process definitions. The following resource types can be deleted: 1. [Process definitions](./processes.md) 2. [Decision Requirements Graphs (DRG)](../modeler/dmn/decision-requirements-graph.md) Delete a resource using [Operate](../../components/operate/userguide/delete-resources.md) or by sending the [delete resource command](/apis-tools/zeebe-api/gateway-service.md#deleteresource-rpc) to the Zeebe API. ## Deleting a process definition Delete a process definition by sending a [delete resource command](/apis-tools/zeebe-api/gateway-service.md#deleteresource-rpc) and providing the `process definition key` as the `resource key`. You can delete any version of a process definition. After deletion, new process instances cannot be created for it: its start events are deactivated immediately, and attempts to create an instance result in a `NOT_FOUND` exception. If the definition has no running instances, it is removed from Zeebe's state right away. If it still has running instances, its record is retained until they finish (see [Draining](#draining)). Zeebe **never** reuses a process version. Even after deletion, Zeebe continues tracking version numbers. Deploying a new process with the same ID increments the version as usual. ### Deleting the latest version When deleting the `latest` version of a process definition, the previous version becomes the new `latest`. For example, if three versions exist and `Version 3` is the latest, deleting it results in the following: - No new instances can be created for `Version 3`. - Creating a new process instance using `latest` creates an instance of `Version 2`. - If `Version 2` contains timer start events, they are reactivated and triggered according to their schedule. - If `Version 2` contains message or signal start events, they are reactivated. Publishing a message or broadcasting a signal creates a new process instance of `Version 2`. Deleting `Version 2` before `Version 3` produces the same behavior, except `Version 1` becomes the new `latest`. ### Call activities A [call activity](/components/modeler/bpmn/call-activities/call-activities.md) references a process by ID. If all process definitions for that process ID are deleted, Zeebe creates an [incident](/components/concepts/incidents.md) on the call activity indicating that the referenced process cannot be found. ### Draining Deleting a process definition that still has running instances is supported and does not block the cluster-wide command distribution queue. Instead of being rejected, the definition enters the `DRAINING` state and is removed automatically once its instances finish: - **New instances are blocked immediately.** Start events are deactivated at delete time, and attempts to create an instance return `NOT_FOUND`, even though the definition's record still exists while it drains. - **Running instances continue to completion.** The deletion does not cancel them. - **Physical removal is asynchronous and per-partition.** Once a partition's last active instance of the definition finishes or is canceled, that partition removes the definition and transitions it to the deleted state. A process definition moves through the following lifecycle states: | State | Meaning | | :--------- | :------------------------------------------------------------------------------------------------------------------------------------ | | `ACTIVE` | Deployed and able to create new instances. | | `DRAINING` | Marked as deleted (new instances are blocked) while running instances drain. The record is retained until the last instance finishes. | | `DELETING` | The last instance has drained and the definition is being physically removed on the partition. This is a brief internal transition. | | `DELETED` | Fully removed. | The Orchestration Cluster API [process definition `state` field](/apis-tools/orchestration-cluster-api-rest/specifications/get-process-definition.api.mdx) exposes `ACTIVE`, `DRAINING`, and `DELETED`. You can also track draining definitions with the `zeebe_process_definitions_draining_count` [metric](/self-managed/operational-guides/monitoring/metrics.md) and the draining indicator in [Operate](../operate/userguide/delete-resources.md#delete-process-definition). ### Historic data By default, deleting a process definition removes it from Zeebe's runtime state only; new instances can no longer be created from it. Its historic data remains in secondary storage until explicitly deleted, so the definition may continue to appear in Operate and Tasklist history views until then. Optionally enable historic data deletion to permanently remove all data related to the process definition from secondary storage. :::warning Deletion is irreversible. Restore deleted data only by restoring a backup of your cluster. ::: Delete historic data for a process definition using the [Orchestration Cluster API](/apis-tools/orchestration-cluster-api-rest/specifications/delete-resource.api.mdx) and set the `deleteHistory` flag to `true`. You can also delete a process definition with historic data using Operate. See the [Operate user guide](../operate/userguide/delete-resources.md#delete-process-definition). If you only want to delete process instance data, see [process instance deletion](./process-instance-deletion.md). #### Eventual consistency Historic data deletion runs asynchronously. Depending on the amount of data, it may take time for the data to be removed and for it to disappear from Operate and Tasklist. If the definition is [draining](#draining), history deletion is deferred: the `deleteHistory` operation does not run when you submit the delete, but only after the definition has been physically deleted on all partitions. Draining instances keep exporting events until they finish, so their historic data cannot be removed before then. ## Deleting a decision requirements graph Delete a decision requirements graph (DRG) by sending a [delete resource command](/apis-tools/zeebe-api/gateway-service.md#deleteresource-rpc) and providing the `decision requirements key` as the `resource key`. Deleting a DRG also deletes the decisions it contains. Attempts to evaluate a deleted decision result in a `NOT_FOUND` exception. Deleting a DRG also deletes historical data. ### Business rule tasks A [business rule task](/components/modeler/bpmn/business-rule-tasks/business-rule-tasks.md) references a decision by ID. If all versions of that decision are deleted, Zeebe creates an incident on the business rule task indicating that no decision with the given ID can be found. ### Historic data By default, deleting a decision requirements graph removes it from Zeebe's runtime state only. Its historic data remains in secondary storage until explicitly deleted, so it may continue to appear in Operate and Tasklist history views until then. Optionally enable historic data deletion to permanently remove all data related to the decision definition from secondary storage. :::warning Deletion is irreversible. Restore deleted data only by restoring a backup of your cluster. ::: Delete historic data for a decision definition using the [Orchestration Cluster API](/apis-tools/orchestration-cluster-api-rest/specifications/delete-resource.api.mdx) and set the `deleteHistory` flag to `true`. You can also delete historic data for a decision definition in Operate. See the [Operate user guide](../operate/userguide/delete-resources.md#delete-decision-definition). If you only want to delete decision instance data, see [decision instance deletion](./decision-instance-deletion.md). #### Eventual consistency Historic data deletion runs asynchronously. Depending on the amount of data, it may take time for the data to be removed and for it to disappear from Operate and Tasklist. --- ## Secret resolution and job activation ## About Secret resolution lets job workers use secret values at runtime without storing those values in job variables or configuration. A job whose variables contain an [Orchestration Cluster secret reference](/reference/glossary.md#secret-reference-orchestration-cluster) is handed to a worker only after every reference has been resolved. The resolved values reach the worker without being written to any record, runtime state, or log. The broker resolves secret references in the background rather than while processing a command. It injects the resolved values into the job only when handing the job to a worker. As a result, secret resolution can affect when a job becomes available for activation. A cluster whose process models contain no `camunda.secrets.` reference is unaffected by any of this. A model that does use a reference behaves differently depending on whether a secret store is configured: on a cluster with no store configured, every reference fails permanently as not found, and the job gets a [secret resolution error incident](secret-resolution-incidents.md#resolve-secret-lookup-failures) rather than a delay. ## Availability Secret resolution is available in both SaaS and Self-Managed. | Deployment | Secret store | What you configure | | :----------- | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | SaaS | Provisioned and managed | No secret store configuration.Manage secret values on the cluster's **Cluster secrets** tab.Reference them as `camunda.secrets.`.See [manage connector secrets](/components/saas/clusters/manage-secrets.md#reference-connector-secrets-as-camundasecretsname). | | Self-Managed | File, AWS, or GCP | The store type, path, and credentials. See [secrets configuration](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#secrets). | :::note In Self-Managed deployments, you can only configure AWS Secrets Manager and GCP Secret Manager stores. ::: ## Resolve references before activation The broker resolves secret references on a background scheduler, not on the processing path, so a slow or unavailable secret store cannot stall overall processing. Each physical tenant supports exactly one secret store, and that store's id must be `default`. A `camunda.secrets.` reference always addresses it. `camunda.physical-tenants..secrets.*` can override which store backs a given tenant, but never adds a second store alongside it. When the broker creates a job, the variable value itself keeps the placeholder text `camunda.secrets.`. Nothing is read from a secret store at this point. When the job activation is requested, the scheduler then works through the references that are not in the cache within cycles: 1. Each cycle collects up to `camunda.processing.engine.secrets.batch-resolution-limit` pending references. 2. The scheduler requests each store's references. The store's local cache holds successfully resolved values for the next activation. 3. References beyond the limit stay pending and are collected by a later cycle. When a cycle reaches the limit and makes progress, the next cycle starts immediately instead of waiting for `camunda.processing.engine.secrets.interval`. Resolution records carry no secret values. Only the store's cache holds a value, and only for as long as its cache entry lives. A cached value expires a fixed time after it is written, regardless of when it was last read. The store can also evict the value earlier if its cache is full. If the value is no longer cached when the broker tries to activate a job, the broker parks the job and resolves the reference again. Resolving the reference again also makes rotated secrets available to workers without a restart. Configure the store's cache lifetime and size under [`camunda.secrets.cache`](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#camundasecretscache). Two kinds of failure are treated differently: | Failure | Behavior | | :----------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | The store reports a secret as missing, forbidden, or invalid | Treated as permanent. The reference fails immediately, with no retry and no cache write. | | The store itself is unavailable | Treated as transient. The broker retries the store with exponential backoff. After `retry-max-attempts` consecutive failures, the broker fails every reference still pending for that store. | The broker tracks retry state for its store rather than per secret and holds it in memory only. The retry state resets when the broker restarts or the partition changes leader. During backoff, the scheduler skips the store, so its references do not consume batch capacity that a healthy store can use. A reference that fails permanently, or whose store never recovers, raises an incident for the jobs waiting on it. See [resolve secret lookup failures](secret-resolution-incidents.md#resolve-secret-lookup-failures) for the incident message, how to tell the causes apart, and what resolving it does. ## Activate a job that references secrets At activation time, the broker looks up each job reference in the secret store's local cache. The broker does not read the store during activation, so store latency cannot block activation. The broker hands a job to a worker only when every reference has a cached value. If a reference is not yet cached, the broker requests asynchronous resolution instead of handing out the job. The broker does not fail the waiting job or raise an incident. Once the reference resolves, the job becomes available automatically. While it waits, the job is parked internally and is not activatable, so no worker receives it on either delivery path. This parked state isn't exposed through the API, Operate, or exported records. Observe it instead through its effects: the job is missing from an activation response, no `ACTIVATED` event exists for its batch, a `RESOLUTION_REQUESTED` record exists for the pending reference, and the `zeebe_job_events_total` metric counts it under `action="skipped uncached secret"`. The following sections describe this behavior for each delivery path. The broker injects the same resolved values on both paths. ### Long polling During batch collection, a job with a reference that is not yet cached is skipped without consuming a slot in the batch, so jobs behind it can still be activated in the same response. The broker then requests resolution of that job's missing references and parks the job until they resolve. A parked job is not activatable, so a later poll does not collect it again and no worker receives it. Once the reference resolves, the job is made activatable again automatically. Neither redeployment nor client action is required. The broker injects the resolved values into a copy of the batch used only for the response. The event the broker appends to its log still carries the placeholders. ### Job push On the push path, the broker performs the same check before pushing a job to a matching job stream. If a reference is not yet cached, the broker requests resolution and parks the job as it does on the polling path. When the reference resolves, the broker pushes the parked job to a matching stream. Because a worker using job push never polls, the broker must push the reactivated job. The resolved values are injected into the pushed job only. On this path the activation event carries no variables at all, so neither a value nor a placeholder reaches the log. ### Understand what a job worker receives A job worker does not need to handle secret resolution. While a job waits for a reference to resolve, the broker does not include it in an activation response or push it to a worker. A worker receives the job with the placeholders replaced by the resolved values. If your worker logs its input variables, the resolved secret values appear in plaintext. :::warning The guarantees below describe what Camunda stores, not what a worker does with the value it received. A worker can still write the resolved value back into process variables, for example through a completed job's variables, an output mapping, a connector result, or an error message. Doing so writes it to process variables and exposes it in runtime state and every exported record as plaintext, the same as any other variable value. Write resolved secret values into a worker's own request to the credential's consumer, not back into process variables. ::: You don't need to make client-side changes. Existing workers, clients, and job worker libraries continue to work with a cluster that resolves secrets. One exception: a worker's `CompleteJob`, `FailJob`, or `ThrowError` command is rejected with `INVALID_STATE` if the job is parked for secret resolution when the command arrives; the rejection names the state. This can surface as a race: if a worker's activation times out and it completes, fails, or throws an error late, and by then the job's cached secret value has expired and the broker parked it again, that late command is rejected instead of accepted. The work of that activation is lost, and the job is resolved and handed out again once its references are cached, the same outcome as any other lost race between a timed-out worker and a new activation. ## Secret values location Resolved secret values exist only in the activation response and in the pushed job, unless a worker writes the value into a process variable itself (see the warning above). Short of that, everywhere else the placeholder text is what is stored. | Location | What it contains | | :----------------------------------------- | :------------------------------------------------------------ | | The activation response or the pushed job | The resolved values | | The job batch `ACTIVATED` event in the log | The placeholder text, or no variables at all on the push path | | Runtime state and exported records | The placeholder text | | Broker logs, including failure logs | The placeholder text, never a value | | Incident messages | The reference and the variable path, never a value | As a result, Operate shows `camunda.secrets.` for the process instance even though the worker received the resolved value. ## Understand why a job is not activated Two conditions stop a job from being activated even though all of its references have resolved. ### Resolved values exceed the message size The activation response has to stay within `camunda.cluster.network.max-message-size`, which defaults to `4MB`. A resolved value is usually longer than the placeholder it replaces, so a job that fit with placeholders can fail to fit once the values are injected. A value shorter than its placeholder reduces the response size and does not cause this condition. If the resolved values exceed the available message size, the broker removes that job and every subsequent job from the activation. The jobs remain activatable for a later batch. If a job cannot fit even in an otherwise empty batch, the broker raises a message size incident. ### Secret injection fails The broker replaces the placeholder at its recorded position in the job variables. If a later variable merge overwrites the expected placeholder, or if the broker cannot read the variables, the broker does not activate the job and raises an incident. The incident also takes the job out of activation until the incident is resolved, so the same failing injection is not retried on every activation. To learn how to inspect and resolve either incident, see [troubleshoot secret resolution failures](secret-resolution-incidents.md). ## Tune the resolution scheduler Configure the scheduler under `camunda.processing.engine.secrets`. - The defaults are intended for stores that respond in less than a second. - The separate `camunda.secrets.cache.ttl` setting controls how long a resolved value remains cached before the reference must be resolved again. Under a steady stream of pending references, cycles run close to `wake-delay` apart, not `interval`: `interval` only bounds how long a scheduler with nothing to resolve waits before checking again, growing there from `wake-delay` in geometric steps rather than jumping straight to it. | Property | Default | Change it when the following applies: | | :----------------------- | :------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `wake-delay` | `50ms` | Jobs that reference secrets take too long to activate under a steady stream of requests. A shorter delay reduces that latency at the cost of polling the stores more often. | | `interval` | `5s` | A scheduler that is genuinely idle takes too long to notice a newly pending reference, or you want its idle ceiling to be different. Under load this value is rarely reached; see `wake-delay` above. | | `batch-resolution-limit` | `20` | A backlog of pending references builds up faster than it clears. A higher limit clears it faster at the cost of more concurrent load on the stores. | | `retry-max-attempts` | `3` | You want to tolerate brief store outages before raising incidents. | | `retry-initial-delay` | `1s` | You need a longer or shorter delay before the first retry. | | `retry-backoff-factor` | `2` | You want retry delays to increase more slowly. A value of `1` keeps the delay constant. | | `retry-max-delay` | `30s` | You want to retry an unavailable store sooner or less often. | The retry settings apply to an unavailable store as a whole. A secret the store reports as missing or forbidden is never retried, because that failure is permanent. See the [property reference](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md) for the full description of each property and its environment variable form. ## Secret resolution across physical tenants The secret store, its cache, and the resolution scheduler's retry state are all scoped per [physical tenant](/self-managed/concepts/physical-tenants/configuration-reference.md). A multi-tenant cluster resolves each tenant's `camunda.secrets.` references against that tenant's own store: two tenants never share a cache entry, and one tenant's store outage does not affect another tenant's resolution. `camunda.secrets.*` configures the store and cache defaults every physical tenant inherits. Override them for one tenant under `camunda.physical-tenants..secrets.*`. The tenant still supports only one store, under the same `default` id. ## Monitor secret resolution A store that is slow or unavailable shows up as jobs that do not activate, and the job worker does not indicate the cause. The cluster emits meters for secret resolution and secret caches. Use these meters to distinguish a cold cache from a store that is not responding. To scrape and interpret cluster meters, see the [metrics reference](/self-managed/operational-guides/monitoring/metrics.md#secret-resolution-and-cache-metrics). ## Related resources - [Job workers](job-workers.md) describes long polling, job push, and job queuing in general. - [Troubleshoot secret resolution failures](secret-resolution-incidents.md) covers the incidents described here: their messages, how to diagnose them, and what happens after you resolve them. - [Incidents](incidents.md) explains what an incident is and how it is resolved, which applies to the incidents described here. --- ## Troubleshoot secret resolution failures ## About When a job's [secret references](secret-resolution-and-job-activation.md) cannot be delivered, the cluster responds in one of three ways: - Raises a `SECRET_RESOLUTION_ERROR` incident, - Raises a `MESSAGE_SIZE_EXCEEDED` incident, - Defers the job and retries it without raising an incident. Only the two incident cases require operator action. Use this troubleshooting guide to diagnose an existing secret resolution or activation problem. :::tip To understand how secret resolution and job activation work, see [Secret resolution and job activation](secret-resolution-and-job-activation.md). ::: ## Find your symptom Start by identifying the symptom, and then refer to the relevant troubleshooting section: | Symptom | What happened | Section | | :------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------ | | An incident message starts with `Failed to resolve secret` | The secret store could not return the value, either permanently or after exhausting all retries. | [Resolve secret lookup failures](#resolve-secret-lookup-failures) | | An incident message names a job key and variable path | The secret value was available, but Camunda could not inject it into the job variables. | [Resolve secret injection failures](#resolve-secret-injection-failures) | | An incident message reports growth in bytes and the configured message size | The resolved values are too large to fit in an activation batch. | [Reduce oversized secret values](#reduce-oversized-secret-values) | | A job that references secrets is not activated, and no incident is raised | The references have not resolved yet, or the store is in retry backoff. | [Identify failures that raise no incident](#identify-failures-that-raise-no-incident) | | Jobs activate later and in smaller batches than usual, and no incident is raised | The injected values did not fit in the current batch, so the broker deferred the jobs to a later activation. | [Identify failures that raise no incident](#identify-failures-that-raise-no-incident) | | A job in a suspended process instance is not activated after its secret resolves | The process instance remains suspended, so secret resolution does not make the job activatable. | [Resume a suspended job after secret resolution](#resume-a-suspended-job-after-secret-resolution) | These incidents are job incidents. You can view them in [Operate](/components/operate/userguide/resolve-incidents-update-variables.md) or retrieve them through the [search incidents](/apis-tools/orchestration-cluster-api-rest/specifications/search-incidents.api.mdx) endpoint. Filter by `errorType` for `SECRET_RESOLUTION_ERROR` or `MESSAGE_SIZE_EXCEEDED`. Incident messages never contain secret values. They may include the secret reference, variable path, job key, or message size, but not the resolved value. ## Resolve secret lookup failures A secret lookup failure raises a `SECRET_RESOLUTION_ERROR` incident. This happens when the secret store returns a permanent failure or remains unavailable until all retry attempts are exhausted. The broker raises one incident per affected job. If a job already has an incident for another failed secret reference, the broker does not raise a second incident. As a result, a job waiting on multiple failed secrets shows only the first incident. ```text Failed to resolve secret 'API_TOKEN' from the configured secret store. Ensure the secret exists and the store is available, then resolve the incident to retry. ``` For the default store, the incident message uses `the configured secret store`. For any other store, it uses `secret store ''`. The `camunda.secrets.` syntax does not currently identify a store, so every reference addresses the default store. As a result, incident messages currently use the default-store wording. The incident message does not identify the underlying store failure. Check the broker log and follow [Diagnose the cause](#diagnose-the-cause) to determine whether the secret is missing, access is denied, the store is unavailable, or another failure occurred. While the incident is active, the job is not activatable and no worker receives it. The broker does not raise another incident for the same failed reference. ### Retry after fixing the secret store Resolve the incident only after fixing the underlying cause. Resolving the incident makes the job activatable again. On the next activation attempt, the broker requests resolution again because the reference is still uncached. You don't need to redeploy or make client-side changes. Once the reference resolves successfully, the process instance continues from where it stopped. Camunda retries secret resolution only when a worker next activates the job. If no worker is connected for the job type, the broker does not activate the job or request secret resolution again. As a result, the incident does not reappear even if the secret is still missing. The absence of an incident does not mean the secret problem is resolved. Keep a worker connected for the affected job type so Camunda can raise a new incident promptly if the cause remains unresolved. ## Resolve secret injection failures A secret injection failure also raises a `SECRET_RESOLUTION_ERROR` incident, but for a different reason. In this case, the secret value was available, but Camunda could not inject it into the job variables. Each secret reference records the JSON pointer of the variable that contains the placeholder. During activation, Camunda replaces the placeholder at that pointer with the resolved value. Injection fails when both of the following conditions apply: - Camunda cannot replace the placeholder at the recorded pointer. - A `camunda.secrets.` placeholder still remains at that path after all references for the path have been processed. For example, injection fails if the pointer now addresses a list or object that still contains a secret placeholder. If the value at the pointer no longer contains a placeholder, Camunda [continues without raising an incident](#identify-failures-that-raise-no-incident). ```text The job with key '2251799813685260' can not be activated, because the secret reference 'camunda.secrets.API_TOKEN' could not be resolved at '/credentials/token'. Fix the variable's value or the input mapping that sets it, then resolve the incident, or use process instance modification to reactivate the element and create a fresh job. ``` If the failure does not identify a specific reference, for example because Camunda cannot read the job variables, the incident uses the following generic message: ```text The job with key '2251799813685260' can not be activated, because injecting its secret values failed. Resolve the incident, or use process instance modification to reactivate the element and create a fresh job. ``` Long polling and job push use the same incident messages for injection failures. Typical causes include: - A variable merge overwrites the placeholder after the job is created. - An input mapping produces a list or context instead of a single text value. - A cluster variable changes between input mapping evaluation and job creation. While the incident is active, the job is not activatable, so the broker does not retry the same failed injection on every poll. ### Retry after fixing the variables Resolve the incident only after correcting the variable value or the input mapping that produced it. Resolving the incident makes the job activatable again, and Camunda retries injection against the current job variables. Camunda retries injection only when a worker next activates the job. If no worker is connected for the job type, the broker does not activate the job or retry injection. As a result, the incident does not reappear even if the underlying cause is still present. Keep a worker connected for the affected job type so Camunda can raise a new incident promptly if injection still fails. If you cannot restore the placeholder, use [process instance modification](process-instance-modification.md) to reactivate the element. This creates a new job and detects its secret references again. ## Reduce oversized secret values A `MESSAGE_SIZE_EXCEEDED` incident is raised when the resolved secret values make the job too large to fit within the configured activation message size. ```text The job with key '2251799813685260' can not be activated, because injecting its secret values would grow the activation batch by 5.2MiB, more than any batch can grow without exceeding the configured message size (per default is 4 MB). Try to reduce the size of the secret values or of the job variables. ``` The applicable limit is `camunda.cluster.network.max-message-size`, which defaults to `4MB`. Secret values do not have a separate size limit. Camunda raises this incident only when the oversized job is first in the activation batch and still cannot fit within the available message size. If a job does not fit only because of other jobs already included in the batch, Camunda removes it from that batch without raising an incident and activates it later. See [Identify failures that raise no incident](#identify-failures-that-raise-no-incident). ### Retry after reducing the size Resolve the incident only after reducing the size of the secret value or the job variables. Otherwise, the next activation attempt fails in the same way. Camunda checks the job size again only when a worker next activates the job. If no worker is connected for the job type, the broker does not activate the job or run the size check. As a result, the incident does not reappear even if the values are still too large. Keep a worker connected for the affected job type so Camunda can raise a new incident promptly if the job still exceeds the message-size limit. To reduce the job variables included in activation, adjust the worker's `fetchVariables` list. Variables the worker does not fetch are excluded from the activation and do not count toward the message-size limit. ## Identify failures that raise no incident Some secret resolution outcomes do not raise incidents and do not require operator action. Their symptoms can still resemble incident conditions. ### Job does not fit in the current batch If injecting a job's resolved values would exceed the remaining message size in the current activation batch, Camunda removes that job and every subsequent job from the batch and marks the batch as truncated. The removed jobs remain activatable and can be included in a later activation. The visible effect is smaller batches and slightly delayed activation rather than a stuck job. ### Reference no longer has a placeholder Camunda activates the job without changing the value when there is no placeholder left to replace. This can happen in either of the following cases: - The recorded pointer no longer addresses a value, for example because the worker's `fetchVariables` list excludes that variable. - The value at the pointer no longer contains `camunda.secrets.`, for example because a variable update replaced the placeholder with a literal value. In the second case, the worker receives the current literal value instead of the secret. Camunda raises an [injection incident](#resolve-secret-injection-failures) only when a secret placeholder is still present but cannot be replaced. ### Job push does not check injected value size Long polling checks the growth caused by injected secret values against the remaining message size in the activation batch. If the job does not fit, Camunda removes it from the batch. Job push does not perform the same size check on the pushed job because the activation event contains no variables. As a result, the push path does not raise a `MESSAGE_SIZE_EXCEEDED` incident for oversized injected values. Job push does not bypass the transport message-size limit. If the pushed job exceeds the configured transport limit between the broker, gateway, and worker, delivery can fail without raising a `MESSAGE_SIZE_EXCEEDED` incident. ## Resume a suspended job after secret resolution A job waiting for an uncached secret is parked, as described in [activate a job that references secrets](secret-resolution-and-job-activation.md#activate-a-job-that-references-secrets). If you suspend its process instance, the job enters `SUSPENDED` instead. If the secret resolves while the process instance is suspended, the job remains suspended and does not become activatable automatically. Resume the process instance to make the job available again. On the next activation attempt, Camunda injects the resolved value if it is still cached. If the value is no longer cached, Camunda parks the job again and requests secret resolution. In either case, no additional operator action is required and no incident is raised. ## Diagnose the cause Start with the incident error type and message, then confirm the underlying cause in the broker log. Incident messages don't include the store's error category, so use the log to distinguish conditions such as a missing secret, denied access, or an unavailable store. 1. Read the incident in Operate or search for incidents filtered by `errorType`. Note the secret reference, variable path, and job key from the message. 2. For incidents whose message starts with `Failed to resolve secret`, search the broker log for the affected partition and reference name. Match the log entry to the table below. 3. For injection failures, inspect the job variables at the reported path in Operate or through the element instance variables. | Broker log line | Cause | Fix | | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | `Secret '' in secret store '' failed permanently: NOT_FOUND — No secret store configured` | No secret store is configured for this physical tenant, so every reference fails as not found. | Configure a secret store. | | `Secret '' in secret store '' failed permanently: NOT_FOUND — ` | The store does not contain a secret with that name. | Create the secret or correct the reference name in the process. | | `Secret '' in secret store '' failed permanently: ACCESS_DENIED — ` | The credentials used by the broker don't have permission to read the secret. | Grant read access in the secret store. This is store-level access control, not a Camunda authorization. | | `Secret '' in secret store '' failed permanently: INVALID_REF — ` | The store rejects the reference name as invalid. | Rename the secret so the store and Camunda reference syntax both accept it. | | `Secret '' in secret store '' failed permanently: UNREADABLE — ` | The store contains the secret, but its value cannot be read or decoded. | Repair the stored value. | | `Secret store '' unavailable (attempt /), retrying in : ` | The failure is transient. The broker is retrying the store with backoff, and no incident exists yet. | If the failure persists, restore the store's availability. | | `Secret store '' unavailable after / attempts — failing pending refs: ` | The store remained unavailable through `retry-max-attempts`, so its pending references failed. | Restore the store's availability, then resolve the incidents. | The separator in these log lines is an em dash, and `` contains the store's own error text. Search for a distinctive fragment such as `failed permanently` rather than the entire line. Then verify the reference itself: - **Reference name**: Confirm the reference resolves to the intended secret name. In an input mapping, Camunda detects references from the parsed FEEL expression, so FEEL syntax determines where the name ends. For example, FEEL interprets `=camunda.secrets.db-password` as the reference `db` minus the variable `password`. Escape names that aren't valid bare FEEL identifiers with backticks, for example ``=camunda.secrets.`db-password` ``. If the incident names a shorter secret than expected, check whether FEEL interpreted part of the name as an operator. - **Secret name**: Confirm the reference name exactly matches the secret name in the store, including case. Store-managed names that the Camunda secret endpoints reject, such as names containing a period, can still resolve when you escape them with backticks in an input mapping expression. Escaping applies only to expressions. Camunda scans references in [cluster variable](/components/admin/cluster-variables.md) values as plain text rather than parsing them as FEEL. In this case, the name after the prefix must match `[\p{Alnum}_-]+` and cannot be escaped. A period, space, or other unsupported character prevents Camunda from detecting the reference. - **Reference format**: Confirm the reference is a FEEL expression rather than a static string. In an input mapping source or Connector property, use a path such as `=camunda.secrets.TOKEN`. Static values and quoted references such as `={"auth": "camunda.secrets.TOKEN"}` are rejected at deployment. - **Value size**: Confirm the resolved value and other job variables fit within `camunda.cluster.network.max-message-size`. The `SECRET` resource type in [authorizations](access-control/authorizations.md) applies to the secrets API, not broker-side secret resolution. Missing Camunda authorizations don't cause `SECRET_RESOLUTION_ERROR` incidents. ## Retry after fixing the cause Resolve the incident after correcting the underlying problem. Resolving the incident retries the failed operation and makes the job activatable again. | You fixed: | After you resolve the incident: | | :-------------------------------------------- | :------------------------------------------------------------------------------------ | | The secret in the store or store access | The broker requests resolution again, and the job activates once the value is cached. | | The variable or input mapping | Camunda retries injection against the current job variables. | | The size of the secret value or job variables | The job activates once the resolved values fit within the message-size limit. | You don't need to redeploy or make changes to workers, clients, or job worker libraries. Jobs blocked on a reference become activatable automatically once the reference resolves, whether resolution follows an incident or store recovery. ## Related resources - [Secret resolution and job activation](secret-resolution-and-job-activation.md) describes how Camunda resolves references and when a job becomes activatable, including the scheduler settings that control how quickly store failures become incidents. - [Incidents](incidents.md) explains how incidents work and how to resolve them. - [Resolve incidents and update variables](/components/operate/userguide/resolve-incidents-update-variables.md) explains how to resolve incidents in Operate. --- ## Secret resolution ## About Secret resolution replaces a `camunda.secrets.` reference, known as a [secret reference (Orchestration Cluster)](/reference/glossary.md#secret-reference-orchestration-cluster), with the value a configured secret store holds for it, without that value being written into a process model, a job variable literal, or a configuration file. - This is a separate mechanism from the connector runtime's `{{secrets.}}` syntax, now named "Secret reference (legacy)". - By default, the two forms are resolved independently by different components, and each reads only its own store or providers. - Optionally, if the connector runtime is configured for it (`camunda.connector.secret-resolver.legacy.mode` set to `FALLBACK`), a legacy reference the runtime cannot find in its providers falls back to the `camunda.secrets.` store. See [Using `camunda.secrets.*` references](/components/connectors/use-connectors/index.md#using-camundasecrets-references). :::note The legacy form was the subject of [security notice 61](/reference/notices.md#notice-61), where an unscoped reference could resolve outside the field it was written in. The secret filter that notice introduces applies only to the legacy form. `camunda.secrets.` resolution isn't affected: the broker records each reference's position in the job variables and replaces only that position, so a reference can't resolve at a field where it wasn't written. ::: :::tip Desktop Modeler and Camunda Hub flag legacy secret usage when the diagram's selected engine supports `camunda.secrets.`. Use that hint to guide your migration to the new syntax. ::: ### Availability Secret resolution is available in both SaaS and Self-Managed. - See [availability](/components/concepts/secret-resolution-and-job-activation.md#availability) for what each offering provides and what you configure. - For an overview of how secrets work in Camunda, including where values are stored and created, see [secrets](secrets.md). ## Reference syntax A reference has the form `camunda.secrets.`, where `` is a single, non-empty token of ASCII letters, digits, `_`, and `-`. `camunda.secrets.` is authored as a FEEL expression (`=camunda.secrets.`), so a dashed name has to be backtick-escaped. A bare dash is FEEL's minus operator: ```feel =camunda.secrets.`db-password` ``` An unescaped dashed name is not a reference. FEEL reads `=camunda.secrets.db-password` as the reference `db` minus the variable `password`. ### Charset differs by surface The engine detects a reference by parsing the FEEL abstract syntax tree, not by matching characters against a fixed set. A name written in a model can therefore be anything a FEEL identifier allows, including unicode letters, `$`, and any name that is backtick-escaped, such as `` =camunda.secrets.`tls.crt` ``. The gateway API is stricter. `POST /v2/secrets/resolve` and `POST /v2/secrets/list` both reject any name outside `[\p{Alnum}_-]+`. | Name | Resolves in a model | Usable with `/v2/secrets/resolve` or `/v2/secrets/list` | | :----------------------------------------- | :-----------------: | :-----------------------------------------------------: | | `API_TOKEN` | Yes | Yes | | `` `db-password` `` (backtick-escaped) | Yes | Yes (as `db-password`) | | `` `tls.crt` `` (backtick-escaped) | Yes | No: contains a `.` | | `` `résumé` `` (backtick-escaped, unicode) | Yes | No: outside `[\p{Alnum}_-]+` | A name a model can reference is not guaranteed to be creatable or manageable through the API. Use the API's charset for any name you intend to create, list, or grant permissions on through `/v2/secrets/*`. ## Resolution paths There are two resolution paths: | | Broker path | Gateway API path | | :------- | :-------------------------------------- | :------------------------------------------------------------------------------------- | | Used by | Job workers, outbound connectors | Inbound connectors (`POST /v2/secrets/resolve`), Camunda Hub (`POST /v2/secrets/list`) | | When | Asynchronously, ahead of job activation | On demand, per request | | Delivery | Long polling and job push | The HTTP response | ### Broker path The broker path resolves references in a job's variables in the background and injects the resolved values only when the job is activated. See [secret resolution and job activation](secret-resolution-and-job-activation.md) for the scheduler, caching, and delivery mechanics, including why no resolved value reaches a record, runtime state, or log on this path. ### Gateway API path The gateway API path serves callers that have no job to wait on. - An inbound connector resolves the references an expression evaluation used, in batches, through `POST /v2/secrets/resolve`. - Camunda Hub calls `POST /v2/secrets/list` to offer known reference names in a [credential's](/components/hub/organization/credentials/index.md) secret fields. Both endpoints share a request and response contract described in [secrets](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-secrets.md). For the full request and response schema of each, see [resolve secrets](/apis-tools/orchestration-cluster-api-rest/specifications/resolve-secrets.api.mdx) and [list secrets](/apis-tools/orchestration-cluster-api-rest/specifications/list-secrets.api.mdx). ## Physical tenant scope A reference names no store, so it always addresses the physical tenant's `default` secret store: `camunda.secrets.X` means `camunda.secrets.default.X`. Each physical tenant supports exactly one secret store, counted across every store type combined, and that store's ID must be `default`. Configuring a second store under a different ID is rejected at startup. Resolving and listing both read the secret stores of the caller's physical tenant only, never another tenant's stores. See [validation and constraints](/self-managed/concepts/physical-tenants/configuration-reference.md#validation-and-constraints) for how the one-store-per-tenant rule is validated, and [secrets](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#secrets) for store configuration. ## Cache behavior Resolving is cache-first on both paths: the broker's background scheduler and the gateway's resolve endpoint each serve a reference from the store's cache when the cache already holds it, and only read the backing store for a reference the cache doesn't hold yet. Listing is different by design. What a store's cache holds is the values it has resolved so far, not the tenant's full set of secrets. `/v2/secrets/list` always reads the configured stores directly rather than serving from the cache. ## Unsupported features The following features are not currently supported: | Unsupported feature | Details | | :--------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | More than one secret store per physical tenant | A reference always addresses the `default` store. | | Pinning a secret to a non-current version | AWS Secrets Manager secrets cannot be pinned to a version stage other than `AWSCURRENT`, and GCP Secret Manager secrets cannot be pinned to a version other than `latest`. | | Filtering or paginating a `POST /v2/secrets/list` response | See [secrets](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-secrets.md#list-secrets). | ## Testing To supply cluster secrets when you test processes with Camunda Process Test, see [cluster secrets in Camunda Process Test](/apis-tools/testing/connectors.md#cluster-secrets). ## Related resources - [Secret resolution and job activation](secret-resolution-and-job-activation.md) covers the broker path in detail: the scheduler, retries, and both delivery paths. - [Troubleshoot secret resolution failures](secret-resolution-incidents.md) covers the incidents raised when a reference or its injection fails. - [Authorizations](access-control/authorizations.md) covers the `SECRET` resource's `READ` and `REVEAL` permissions. - [Secrets](/apis-tools/java-client/secrets.md) covers the Java client's resolve and list commands, including how a per-reference failure is reported. --- ## Secrets(Concepts) ## About You can use and manage secrets to keep keep sensitive values such as API keys, passwords, and tokens, out of your process models, job variables, and configuration files. Instead of writing a value into a model, you reference a secret stored in a secret store by name. Camunda resolves that reference to its value at runtime. The value is supplied only where it is needed, so it is never stored in the process itself. 1. Use a stored secret (for example, `MY_SECRET`) in a model as a secret reference (`camunda.secrets.MY_SECRET`). 1. Once deployed and when the process instance starts, the Orchestration Cluster logs, stores, and exports the secret reference. 1. Once the job is activated, the secret reference is resolved and replaced with the actual secret value from the secret store. :::tip Terminology For precise definitions of secret-related terms, see the [secret reference](/reference/glossary.md#secret-reference) glossary entries. ::: ## Reference a secret You can reference a secret in Camunda using a [secret reference](/reference/glossary.md#secret-reference). - This is a placeholder written into a model that stands in for a secret value. - The recommended syntax is `camunda.secrets.`. This is resolved centrally by the [Orchestration Cluster](/reference/glossary.md#orchestration-cluster). - To learn how the backing store is configured in each offering, see [using `camunda.secrets.*` references](/components/connectors/use-connectors/index.md#using-camundasecrets-references) and [store and create secrets](#store-and-create-secrets). | Syntax | Resolved by | Where you use it | | :----------------------- | :-------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `camunda.secrets.` | [Orchestration Cluster](/reference/glossary.md#orchestration-cluster) | Input mapping FEEL expressions, and connector or credential fields backed by a `SECRET_REFERENCE` cluster variable.[Secret resolution](secret-resolution.md)[Secret reference (Orchestration Cluster)](/reference/glossary.md#secret-reference-orchestration-cluster). | :::note Legacy connector secrets An older `{{secrets.}}` syntax, resolved by the connector runtime, remains supported for existing connector models. See [Legacy connector secrets](#legacy-connector-secrets). ::: For a working example of referencing `camunda.secrets.` in a model, including the FEEL expression rules, see [Secret references in input mappings](/components/concepts/variables.md#secret-references-in-input-mappings). ## Store and create secrets A reference only resolves to a value once that value is available to the resolving component. How you create and store a secret depends on how you run Camunda 8: | Deployment model | Secret storage and management | | :-------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **SaaS** | The secret store is provisioned and managed for you.Create and update secret values on a cluster's **Cluster secrets** tab.Reference them as `camunda.secrets.`.See [manage connector secrets](/components/saas/clusters/manage-secrets.md) and the [SaaS-managed secret](/reference/glossary.md#saas-managed-secret) glossary entry. | | **Self-Managed** | An operator supplies secret values by configuring a secret store (File, AWS Secrets Manager, or GCP Secret Manager). See [secrets configuration](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#secrets).When deploying with the Helm chart, a [Kubernetes Secret](/reference/glossary.md#kubernetes-secret) can back the configured store, for example as mounted files for a File store. See [Helm charts secret management](/self-managed/deployment/helm/configure/secret-management.md). | | **Local development** | No store configuration is needed. You can manage secrets through `c8ctl` or `c8run` and reference them as `camunda.secrets.`.Manage secrets via `c8ctl` through the `c8ctl cluster secrets` CLI, forwarding to the local development cluster. See [Manage secrets with `c8ctl`](/apis-tools/c8ctl/getting-started.md#manage-secrets).Manage secrets via `c8run` directly through the `c8run secrets` CLI. See [Manage secrets via `c8run`](/self-managed/quickstart/developer-quickstart/c8run/configuration.md#manage-local-secrets). | ## Resolve, list, and troubleshoot - [Secret resolution](secret-resolution.md) covers the reference syntax, the two resolution paths, tenant scope, and caching. - [Secret resolution and job activation](secret-resolution-and-job-activation.md) covers the broker path: the scheduler, caching, and delivery to job workers. - [Troubleshoot secret resolution failures](secret-resolution-incidents.md) covers the incidents raised when a reference or its injection fails. - [Secrets](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-secrets.md) covers the `POST /v2/secrets/resolve` and `POST /v2/secrets/list` API endpoints, and [secrets](/apis-tools/java-client/secrets.md) covers the equivalent Java client commands. ## Control access to secrets The `SECRET` resource's `READ` and `REVEAL` permissions control who can list and reveal a secret via the `/v2/secrets` API. These permissions do not govern the broker resolving a reference for job activation. See [authorizations](access-control/authorizations.md#reveal-permission-for-the-secret) and [secret authorizations don't cover broker-side resolution](access-control/authorizations.md#secret-authorizations-dont-cover-broker-side-resolution). ## Legacy connector secrets The legacy secret reference syntax, `{{secrets.}}` is resolved by the [connector runtime](/reference/glossary.md#connector-runtime) itself at execution time, and can be used in any connector field in the properties panel. This syntax remains fully supported for existing connector models: "legacy" describes its age relative to `camunda.secrets.`, not its support status. See [using secrets](/components/connectors/use-connectors/index.md#using-secrets) to learn how to reference a legacy secret in a model, and the [secret reference (legacy)](/reference/glossary.md#secret-reference-legacy) glossary entry. In Self-Managed, a connector secret provider supplies the values behind legacy references, for example from prefixed environment variables or a custom provider. When deploying with the Helm chart, a [Kubernetes Secret](/reference/glossary.md#kubernetes-secret) can deliver these values as mounted environment variables. See [Connector secrets in Self-Managed](/self-managed/components/connectors/connectors-configuration.md#secrets) and [Helm charts secret management](/self-managed/deployment/helm/configure/secret-management.md). ### Limitations Compared to `camunda.secrets.`, the legacy syntax has the following limitations: | Limitation | Description | | :----------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | No field-scoped resolution | A legacy reference can resolve outside the field it was written in.The [secret filter](/self-managed/components/connectors/connectors-configuration.md#secret-filter) introduced with [security notice 61](/reference/notices.md#notice-61) mitigates this; `camunda.secrets.` scopes resolution to the field instead. See [secret resolution](secret-resolution.md#reference-syntax). | | No resource-based authorization | Legacy secrets have no `SECRET` resource permissions and rely on the secret filter instead. See [control access to secrets](#control-access-to-secrets). | | No external secret store integration | Values come from connector secret providers, not from a File, AWS Secrets Manager, or GCP Secret Manager store. See [secrets configuration](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#secrets) for the stores `camunda.secrets.` supports. | :::tip Migrating from the legacy syntax? In Self-Managed, [set up the secret store](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#secrets) and move your values there first. Then follow [Migrate to `camunda.secrets.`](/components/connectors/use-connectors/migrate-secrets.md), which covers the step-by-step process, including the connector runtime's fallback mode for incremental migration. ::: ## Related resources - The [secret reference](/reference/glossary.md#secret-reference) and its related glossary entries defines the terms used in these sections. ### `camunda.secrets.` (recommended) - [Secret references in input mappings](/components/concepts/variables.md#secret-references-in-input-mappings) covers referencing `camunda.secrets.` in a model, with examples. - [Cluster secrets](/components/saas/clusters/manage-secrets.md#reference-connector-secrets-as-camundasecretsname) covers referencing SaaS-managed secrets as `camunda.secrets.`. - [Secrets configuration](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#secrets) covers configuring the secret store in Self-Managed. - [Migrate to `camunda.secrets.`](/components/connectors/use-connectors/migrate-secrets.md) covers moving from the legacy syntax, including the connector runtime's fallback mode. ### `{{secrets.}}` (legacy) - [Using secrets](/components/connectors/use-connectors/index.md#using-secrets) covers referencing secrets from connector fields. - [Cluster secrets](/components/saas/clusters/manage-secrets.md) covers creating and managing secret values in SaaS. - [Connector secrets in Self-Managed](/self-managed/components/connectors/connectors-configuration.md#secrets) covers configuring connector secret providers. - [Cluster secrets in Camunda Process Test](/apis-tools/testing/connectors.md#cluster-secrets) covers supplying secrets when you test processes. --- ## Signals Signals are a similar concept to [messages](messages.md). However, messages are correlated to a specific process instance, whereas signals can trigger _all_ the matching signal events with a single broadcast. Depending on the type of [signal catch events](../modeler/bpmn/signal-events/signal-events.md), the process instance will respond accordingly. ## Broadcasting signals You can broadcast signals in several ways: - Using a [signal throw event or signal end event](../modeler/bpmn/signal-events/signal-events.md#signal-throw-events) - Using the [Orchestration Cluster REST API Broadcast signal](/apis-tools/orchestration-cluster-api-rest/specifications/broadcast-signal.api.mdx) endpoint - Using Zeebe's [`BroadcastSignal` RPC](/apis-tools/zeebe-api/gateway-service.md#broadcastsignal-rpc) ## Signal subscriptions When you broadcast a signal it triggers _all_ signal subscriptions that match the signal name. When a process instance encounters a signal catch event it creates a new signal subscription. This process instance waits until a signal with a matching name is broadcasted. You can define the signal name in the process definition. Deploying a process with a signal start event also creates a new signal subscription. In this case, the triggered subscription starts a new process instance. ## Signal cardinality A broadcasted signal iterates over _all_ available subscriptions. As a result, a single broadcast triggers _all_ the signal catch events that match the signal name, and _all_ [partitions](../zeebe/technical-concepts/partitions.md). :::caution Signals can negatively impact the performance of Camunda 8. Performance is impacted in two ways: - Signals trigger _all_ available subscriptions that match the signal name, potentially resulting in the continued execution of many processes. - Signals are broadcasted to _all_ partitions, resulting in lots of network traffic. This scales linearly with the number of partitions. ::: --- ## Example user task listener Beginner Time estimate: 35 minutes In Camunda 8, **user task listeners** allow you to run custom logic in response to changes to a user task, such as when the `creating`, `assigning`, or `completing` event occurs. User task listeners facilitate the integration of custom logic into your workflows. For more details, refer to the [User Task Listeners concept](/components/concepts/user-task-listeners.md). This guide walks you through: - Defining a task listener using **Camunda Hub** - Implementing a task listener as a job worker - Verifying the result in **Operate** and **Tasklist** ## Prerequisites You must have access to a Camunda 8 SaaS account.
Have you signed up for Camunda yet?
You must also know how to model a process with a user task. If you haven't done this before, first follow the steps in our guide to [get started with human task orchestration](/guides/getting-started-orchestrate-human-tasks.md). Additionally, you need the following: - Java ≥ 8 - Maven - IDE (IntelliJ, VSCode, or similar) - Download and unzip or clone the [repository](https://github.com/camunda/camunda-8-tutorials), then navigate to: `camunda-8-tutorials/quick-start/task-listeners/worker-java` For all steps in this guide, refer to the following diagram: ## Step 1: Create a process with a user task in Hub 1. Launch **Camunda Hub**. 2. In your workspace, open or create a project. 3. In your project, create a new BPMN file. 4. Add a user task named `Assigned by creating task listener`. ## Step 2: Select the user task 1. Click the **user task**. 2. In the right-hand **properties panel**, scroll to **Task listeners**. ## Step 3: Define a task listener Add a new task listener to the user task and define its properties: 1. Click the plus sign in the **Task listeners** section to add a new task listener. 2. Under **Event type**, select **Creating**. 3. Under **Listener type**, enter `assign_new_task`. ![Creating listener is defined with type assign_new_task](./assets/user-task-listeners-guide/3-creating-listener-defined.png) :::info You've now defined a **creating** task listener for this user task. When a process instance arrives at this user task, the `creating` event is triggered, and a job of type `assign_new_task` is created. A job worker can then activate this job to execute the external logic and complete it, approving the creation of the user task. ::: ## Step 4: Create a cluster ## Step 5: Deploy and run the process 1. In your diagram, click **Deploy & run**. 2. Select your deployment stage. 3. Add an `assignee` or `manager` variable as JSON data. For example, `{ "assignee": "john.doe@camunda.com" }`. 4. Click **Deploy & run**. ## Step 6: Understand what happens to the user task Now, we'll explore what happened to the user task. We'll see that the listener blocks the creation. 1. [Navigate to **Tasklist**](/components/tasklist/userguide/using-tasklist.md#open-tasklist) and notice that there is no task in Tasklist yet. ![No tasks found in Tasklist](./assets/user-task-listeners-guide/6.2-no-tasks-found.png) 2. [Navigate to **Operate**](/components/operate/userguide/basic-operate-navigation.md#open-operate) to see your process instance with a token waiting at the user task by clicking on the active process instance in the **Dashboard**. ![Active process instances in Operate Dashboard](./assets/user-task-listeners-guide/6.3-active-process-instances.png) 3. Click the **Process instance key** to [inspect the process instance](/components/operate/userguide/basic-operate-navigation.md#inspect-a-process-instance). ![Inspect process instance in Operate](./assets/user-task-listeners-guide/6.4-inspect-process-instance.png) 4. Click the user task and then click the **Listeners** tab to see that the **Creating** listener is **Active**. ![Inspect listeners for user task in Operate](./assets/user-task-listeners-guide/6.5-inspect-listeners.png) 5. Take a moment to understand the properties of the listener, for example verify that the listener type is what you defined in the process model. This listener is a job that can be activated and handled by a job worker. ## Step 7: Implement the listener Next, we'll run the listener application to execute our external logic, and complete the listener job to continue the user task's creation. ### Create credentials for your Camunda client ### Create a job worker to implement the task listener Next, we’ll create a worker that listens to the user task's events by associating it with the **Listener type** we specified on the task listener in the BPMN diagram. 1. Open the downloaded or cloned project ([repo](https://github.com/camunda/camunda-8-tutorials), then `cd` into `camunda-8-tutorials/quick-start/task-listeners/worker-java`) in your IDE. 2. Add your credentials to `application.properties`. Your client ID and client secret are available from the previous section in the credential text file you downloaded or copied. [Find your **Region ID** and **Cluster ID**](/components/saas/clusters/manage-api-clients.md#view-connection-informationview-connection-information). 3. In the `Listener.java` file, change the type to match what you specified in the BPMN diagram. If you followed the previous steps for this guide and entered “assign_new_task”, no action is required. 4. After making these changes, perform a Maven install, then run the Listener.java `main` method via your favorite IDE. If you prefer using a terminal, run `mvn package exec:java`. ## Step 8: Verify the result in Operate and Tasklist Now that the task listener is running, the listener job will have been handled and completed. Let's see what effects this has had. 1. Navigate to Operate and see that the listener that was **Active** previously, has now been **Completed**. ![Listener has been completed in Operate](./assets/user-task-listeners-guide/8.1-verify-listener-completed.png) 2. Navigate to Tasklist and see that the task is available and assigned to the assignee or manager that you provided. ![Task is assigned in Tasklist](./assets/user-task-listeners-guide/8.2-verify-task-assigned.png) ## Suggestions for further exploration To build your understanding of task listeners, you can also: - Stop the listener application. - Start a new instance of the process, and notice that the task does not appear in Tasklist. - Check Operate, and notice that a creating listener is active. - Restart the listener application and notice that the listener failed, and an incident is raised. - Set a variable `assignee` or `manager` in the process instance, and resolve the incident. - Check Tasklist, and notice that the task is assigned to the assignee or manager that you provided. Further, you can try the following: - Add a separate listener for another event type, and [trigger that event](/components/concepts/user-task-listeners.md#trigger-a-user-task-listener). - [Access the user task's data](/components/concepts/user-task-listeners.md#accessing-user-task-data) from the **activated job**. - [Correct the assignee](/components/concepts/user-task-listeners.md#correcting-user-task-data) in the listener by adding a **job result** to the complete job command. - [Deny the assignment](/components/concepts/user-task-listeners.md#denying-the-lifecycle-transition) from the listener to avoid the task's assignment altogether. ## Additional resources and next steps - Learn more about Camunda 8 and what it can do by reading [What is Camunda 8](/components/components-overview.md) or watching our [Overview video](https://bit.ly/3TjNEm7) in Camunda Academy. - Get your local environment ready for development with Camunda 8 by [setting up your first development project](/guides/getting-started-example.md). --- ## User task listeners A [user task listener](/reference/glossary.md#user-task-listener) allows users to react to specific user task lifecycle events. :::tip Try out our [getting started with user task listeners guide](/components/concepts/user-task-listeners-guide.md). ::: ## About user task listeners User task listeners provide flexibility and control over [user task](/components/modeler/bpmn/user-tasks/user-tasks.md) behavior: - They can react to user task lifecycle events, such as assigning and completing. - They can access user task data, such as the assignee, to execute task-specific business logic. - They can dynamically correct user task data during execution, allowing adjustments to key attributes such as the assignee, due date, and priority. - They can deny state transitions, rolling back the task to its previous state, which enables validation of task lifecycle changes. ### Use cases User task listeners are useful in the following scenarios: - Implementing complex user task assignment or reassignment logic. - Validating user task lifecycle changes, e.g. completing with valid variables. - Notifying users of new task assignments with contextual information. - Reacting to task completions with custom logic. ### User task lifecycle A user task has the following lifecycle. A user task listener can react to the events highlighted in orange. ```mermaid stateDiagram-v2 direction LR [*] --> creating creating --> created created --> assigning assigning --> created created --> updating updating --> created created --> completing completing --> created completing --> completed creating --> canceling created --> canceling assigning --> canceling updating --> canceling completing --> canceling canceling --> canceled classDef listenerEvent fill:#fc5d0d,color:white,font-weight:bold class creating listenerEvent class assigning listenerEvent class updating listenerEvent class completing listenerEvent class canceling listenerEvent ``` ### Blocking behavior User task listeners operate in a blocking manner, meaning the user task lifecycle transition is paused until the task listener completes. This ensures that any corrections or validations defined by the task listener are fully applied before the task transition continues. ## Trigger a user task listener The supported user task listener events can be triggered in the following ways. | Event | Triggered | | :----------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `creating` | When a process instance reaches a user task. | | `assigning` | When the [assign user task API](/apis-tools/orchestration-cluster-api-rest/specifications/assign-user-task.api.mdx) is called.When activating a user task that [specifies an `assignee`](/components/modeler/bpmn/user-tasks/user-tasks.md#assignments) in the process.When a user task is assigned using the [Tasklist interface](/components/tasklist/userguide/managing-tasks.md#assign-tasks). | | `updating` | When the [update user task API](/apis-tools/orchestration-cluster-api-rest/specifications/update-user-task.api.mdx) is called. When the [update element instance variables API](/apis-tools/orchestration-cluster-api-rest/specifications/create-element-instance-variables.api.mdx) is called on a user task instance.When the [set variables RPC](/apis-tools/zeebe-api/gateway-service.md#setvariables-rpc) is called on a user task instance.When variables are set at a user task scope using the [Operate interface](/components/operate/userguide/resolve-incidents-update-variables.md). | | `completing` | When a user task is completed using the [Tasklist interface](/components/tasklist/userguide/managing-tasks.md#complete-a-task).When the [complete user task API](/apis-tools/orchestration-cluster-api-rest/specifications/complete-user-task.api.mdx) is called. | | `canceling` | When a canceling process instance terminates a user task.When a [catch event](/components/modeler/bpmn/events.md) interrupts a user task. | :::note [Process instance modification](/components/concepts/process-instance-modification.md) does not trigger the `canceling` event. User task listeners are intentionally skipped when a user task is terminated via modification to prevent a failing listener job from blocking the operation. ::: Once triggered, the workflow engine creates a job that you can process using a job worker. ## Define a user task listener You can configure user task listeners either: - Per BPMN user task element. - For all user tasks in the cluster. See [Global user task listeners](/components/concepts/global-user-task-listeners.md). ### User task listener properties Each user task listener has the following properties: | Property | Description | | :---------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `eventType` | (Required) Specifies the user task event type that triggers the listener. Supported values are `creating`, `assigning`, `updating`, `completing`, and `canceling`. | | `type` | (Required) The name of the job type, used as a reference to specify which job workers request the respective task listener job. For example, `order-items`. The `type` can be specified as any static value (`myType`), or as a FEEL expression prefixed with `=`, that evaluates to a FEEL string; for example, `= "order-" + priorityGroup`. This is a case-sensitive field, if supported by the underlying operating system. For example, `orderProcess` refers to a different worker than `OrderProcess`. | | `retries` | (Optional) The number of retries for the user task listener job (defaults to 3 if omitted). | :::note If multiple user task listeners of the same `eventType` (such as multiple `assigning` listeners) are defined on the same user task, they are executed sequentially, one after the other, in the order they are defined in the BPMN model. ::: ## Implement a user task listener User task listeners are implemented using [job workers](/components/concepts/job-workers.md), similar to execution listeners and service task jobs. The job worker processes the task listener job, can apply corrections, and may optionally deny the lifecycle transition. See the [job worker documentation](/apis-tools/java-client/job-worker.md) for examples of how to create a job worker and handler that can also process user task listener jobs. ### Accessing user task data User task-specific data, such as `assignee` and `priority`, are accessible through the `userTask` property of the user task listener job. The following user task attributes can be accessed from the activated job's `userTask` property: | Attribute | Description | | :------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `action` | A custom action value provided along with the request that triggered this event. If none was provided, it defaults to one of `assign`, `claim`, `update`, or `complete`. | | `assignee` | The user assigned to the task. If not specified, the task is unassigned. Refer to [assignments](/components/modeler/bpmn/user-tasks/user-tasks.md#assignments) for more details. | | `candidateGroups` | Specifies the groups of users that the task can be assigned to. Refer to [assignments](/components/modeler/bpmn/user-tasks/user-tasks.md#assignments) for more details. | | `candidateUsers` | Specifies the users that the task can be assigned to. Refer to [assignments](/components/modeler/bpmn/user-tasks/user-tasks.md#assignments) for more details. | | `changedAttributes` | Lists the user task attributes that have changed with the event. Refer to the [changed attributes](#changed-attributes) section below for more details. | | `dueDate` | Specifies the due date of the task. Refer to [scheduling](/components/modeler/bpmn/user-tasks/user-tasks.md#scheduling) for more details. | | `followUpDate` | Specifies the follow-up date of the task. Refer to [scheduling](/components/modeler/bpmn/user-tasks/user-tasks.md#scheduling) for more details. | | `formKey` | The form linked to the user task, referenced by its uniquely identifying key. Refer to [user task forms](/components/modeler/bpmn/user-tasks/user-tasks.md#user-task-forms) for more details. | | `priority` | The task’s priority level. Refer to [priority](/components/modeler/bpmn/user-tasks/user-tasks.md#define-user-task-priority) for more details. | | `userTaskKey` | The unique key identifying the user task. | Below is an example of accessing the `assignee` value from the activated job in Java: ```java final JobHandler userTaskListenerHandler = (jobClient, job) -> { // Access the 'assignee' from the job's user task property // highlight-start final String assignee = job.getUserTask().getAssignee(); // highlight-end System.out.println("The assignee for this user task is: " + assignee); // remaining job handler logic }; ``` This user task data can be leveraged to customize the behavior of the user task listener job worker. #### Changed attributes The `changedAttributes` attribute lists which user task attributes have changed with the event. :::note User task data corrections are taken into account. For example, consider a user task with three `assigning` listeners defined. When assigning the user task, the first listener sees the `assignee` attribute in the `changedAttributes`. If it corrects the priority, a subsequent assigning listener sees both the `assignee` and the `priority` attributes as changed attributes. Now, this second listener corrects the priority back to the value it had before assigning. The third listener sees only the `assignee` attribute as changed attribute, because the priority is no longer changed with the event. ::: #### Task headers Configured [task headers](/components/modeler/bpmn/user-tasks/user-tasks.md#task-headers) on the user task are available in the job's custom headers. ### Correcting user task data User task listeners can correct user task data before the lifecycle transition is finalized. Corrections allow user task listeners to update specific attributes of the user task, such as the assignee, due date, follow-up date, candidate users, candidate groups, and priority. These corrections are immediately available to any subsequent task listeners and are applied to the user task when the lifecycle transition is finalized, without triggering the `UPDATING` lifecycle event. If a lifecycle transition is denied by a listener, no corrections are applied to the user task. Below is an example of how to correct the user task data from a job worker while completing the user task listener job in Java: ```java final JobHandler completeTaskListenerJobWithCorrectionsHandler = (jobClient, job) -> jobClient .newCompleteCommand(job) // highlight-start .withResult( r -> r.forUserTask() .correctAssignee("john_doe") // assigns the user task to 'john_doe' .correctDueDate(null) // preserves the current 'dueDate' .correctFollowUpDate("") // clears the 'followUpDate' .correctCandidateUsers(List.of("alice", "bob")) // sets candidate users .correctCandidateGroups(List.of()) // clears the candidate groups .correctPriority(80)) // sets the priority to 80 // highlight-end .send(); client.newWorker() .jobType("user-task-listener-completion") // type of the user task listener job .handler(completeTaskListenerJobWithCorrectionsHandler) .open(); ``` #### On correcting the assignee The assignee can be corrected in the `creating` listener only if the process hasn't specified an assignee for this user task already. For example, if the user task's `assignee` expression evaluates to `null`. :::tip To set an assignee when creating the user task, review [specifying the assignee in the process](components/modeler/bpmn/user-tasks/user-tasks.md#assignments), or verify the assignee is not yet defined by the process by [accessing the `assignee` attribute in the job headers](#accessing-user-task-data). To change the assignee specified by the process, correct it with the `assigning` event. ::: ### Denying the lifecycle transition User task listeners can deny the user task lifecycle transition belonging to the lifecycle event. For example, it can deny the completion of a task in reaction to the completing event, effectively preventing a user request to complete the task. When a lifecycle transition is denied: - **Corrections discarded**: Any corrections made by preceding listeners within the same lifecycle transition are discarded. - **Task state preserved**: The user task retains its state and data as if the lifecycle event never occurred. This capability is particularly useful for implementing validation logic or enforcing business rules before allowing a user task lifecycle transition to proceed. Below is an example of how to deny a user task lifecycle transition from a job worker while completing the user task listener job in Java: ```java final JobHandler denyUserTaskLifecycleTransitionHandler = (jobClient, job) -> jobClient .newCompleteCommand(job) // highlight-start .withResult(r -> r.forUserTask().deny(true)) // highlight-end .send(); ``` Not all events can be denied. For example, it's not possible to deny the creation or cancelation of a user task. Currently, user task listeners can deny the lifecycle transition for the following events: - `assigning` - `updating` - `completing` ## Expression evaluation and incident behavior ### Expression evaluation User task listener properties, such as job `type` or `retries`, are evaluated right before the job creation for the listener. ### Incident recovery If a user task listener job fails or its expression evaluation raises an [incident](/components/concepts/incidents.md), the lifecycle transition is paused until the incident is resolved. There are two types of incidents for task listeners: - **Expression evaluation failure**: Raised when a listener property expression fails to evaluate. After the incident is resolved, the entire lifecycle transition is retried, re-executing all listeners configured for the transition, including those that previously completed successfully. - **Job failure**: If a listener job fails, it is retried according to the `retries` property. If all retries are exhausted and the job still fails, an incident is raised. Once resolved, only the failed listener job is retried, allowing the lifecycle transition to resume without re-executing successfully completed listeners. ## Limitations User task listeners have the following limitations: - **No variable handling**: User task listener jobs cannot be completed if variables are provided. - **No BPMN error throwing**: Throwing BPMN errors from user task listener jobs is not supported. ### Legacy behavior before 8.10 User task listeners are designed for use with the [Orchestration Cluster API](../../apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). Before Camunda 8.10, Tasklist V1 and the deprecated Tasklist API had the following limitations when used with user task listeners: - **Tasklist v1 does not list tasks with pending task listeners**: If a task's lifecycle transition is blocked by a pending task listener, Tasklist v1 does not display the task in the task queue. However, Tasklist v1 can still show the details of such a task. - **Tasklist v1 incorrectly lists creating tasks when filtering for the "all" status (open and completed)**: If a task's creation is blocked by a pending task listener, Tasklist v1 includes it in the task queue when filtering for the "all" status, even though the task has not yet been created. - **Tasklist v1 API cannot filter for tasks with pending task listeners**: The deprecated Tasklist API cannot filter for the following task states when searching tasks: `CREATING`, `ASSIGNING`, `UPDATING`, `COMPLETING`, `CANCELING`. Tasks in these states are included in responses when not filtering by state. - **Tasklist v1 API responses may not reflect corrections applied by task listeners**: Requests made to the deprecated Tasklist API can trigger a listener. Responses to such requests will appear as if the listener did not make any [corrections](#correcting-user-task-data) to the user task data, even when the listener did make corrections. ## Related resources - [Job workers (basics)](/components/concepts/job-workers.md) - [Job workers (Java client)](/apis-tools/java-client/job-worker.md) - [Incidents](/components/concepts/incidents.md) - [Expressions](/components/concepts/expressions.md) - [Execution listeners](/components/concepts/execution-listeners.md) - [User tasks](/components/modeler/bpmn/user-tasks/user-tasks.md) --- ## Variables [Variables](/reference/glossary.md#variable) are part of a [process instance](/reference/glossary.md#process-instance) and represent the data of the instance. A variable has a name and a JSON value. The visibility of a variable is defined by its variable scope. When [automating a process using BPMN](/components/modeler/bpmn/automating-a-process-using-bpmn.md), [orchestrating human tasks](../../guides/getting-started-orchestrate-human-tasks.md), or implementing [agentic orchestration](/components/agentic-orchestration/agentic-orchestration-overview.md), you can leverage the scope of these variables and customize how variables are merged into the process instance. ## Variable names The name of a variable can be any alphanumeric string including the `_` symbol. For a combination of words, it's recommended to use the `camelCase` or the `snake_case` format. The `kebab-case` format is not allowed because it contains the operator `-`. When accessing a variable in an expression, keep in mind the variable name is case-sensitive. Restrictions of a variable name: - It may not start with a **number** (e.g. `1stChoice` is not allowed; you can use `firstChoice` instead). - It may not contain **whitespaces** (e.g. `order number` is not allowed; you can use `orderNumber` instead). - It may not contain an **operator** (e.g. `+`, `-`, `*`, `/`, `=`, `>`, `?`, `.`). - It may not be a **literal** (e.g. `null`, `true`, `false`) or a **keyword** (e.g. `function`, `if`, `then`, `else`, `for`, `between`, `instance`, `of`, `not`). - It must stay within the length limits of the target backend: up to **32,768 characters** with Elasticsearch/OpenSearch-backed secondary storage and up to **256 characters** with RDBMS-backed secondary storage. :::note Length is enforced using Java string length semantics rather than raw UTF-8 byte counts. Most common characters count as one character, while characters represented as surrogate pairs in Java count as two. Because of that, the effective visible-character limit can be lower for some inputs. ::: ## Variable values The value of a variable is stored as a JSON value. It can have one of the following types: - String (e.g. `"John Doe"`) - Number (e.g. `123`, `0.23`) - Boolean (e.g. `true` or `false`) - Array (e.g. `["item1" , "item2", "item3"]`) - Object (e.g. `{ "orderNumber": "A12BH98", "date": "2020-10-15", "amount": 185.34}`) - Null (`null`) :::note Numbers are subject to the following numeric limits: - Integer numbers are effectively limited to the 64‑bit integer range. - Non‑integer numbers are stored as IEEE‑754 double‑precision values, which provide roughly 15–17 significant decimal digits rather than arbitrary BigDecimal precision. If you need arbitrary-precision or very large numbers, consider storing them as strings or in an external data store instead of process variables. ::: :::note A date-time value with a time zone ID, for example from [`now()`](/components/modeler/feel/builtin-functions/feel-built-in-functions-temporal.md#now), is stored with the zone ID in brackets, such as `2026-09-29T12:31:01.641Z[GMT]`. Some ISO 8601 parsers reject this format, for example Python `datetime.fromisoformat()` and JavaScript `new Date()`. To store the value without the zone ID, [convert it to a fixed offset](/components/modeler/feel/builtin-functions/feel-built-in-functions-conversion.md#date-and-timedate-timezone) before storing it, for example `date and time(now(), "Z")`. ::: ## Variable size limitation The payload of a process instance is limited to 4 MB. This limit includes both process variables and workflow engine–internal data, so less than 4 MB is available for variables alone. The effective limit depends on the operation. As a rule of thumb, ~1.5 MB is considered safe for commands or events that include variables, such as starting a process instance or completing a job. In these cases, the engine may append follow-up records that temporarily duplicate the variable payload within the same batch. To avoid production issues, leave headroom below the limit—for example, target ≤1 MB—and validate with a production-like test case. If the payload size is uncertain, run a quick test to confirm behavior. :::note Regardless, we don't recommend storing much data in your process context. Refer to our [best practice on handling data in processes](/components/best-practices/development/handling-data-in-processes.md). ::: ## Variable scopes Variable scopes define the _visibility_ of variables. The root scope is the process instance itself. Variables in this scope are visible everywhere in the process. When the process instance enters a subprocess or an activity, a new scope is created. Activities in this scope can observe all variables of this and of higher scopes (i.e. parent scopes). However, activities outside of this scope can not observe the variables which are defined in this scope. If a variable has the same name as a variable from a higher scope, it covers this variable. Activities in this scope observe only the value of this variable and not the one from the higher scope. The scope of a variable is defined when the variable is created. By default, variables are created in the root scope. ![variable-scopes](assets/variable-scopes.png) This process instance has the following variables: - `a` and `b` are defined on the root scope and can be seen by **Task A**, **Task B**, and **Task C**. - `c` is defined in the subprocess scope and can be seen by **Task A** and **Task B**. - `b` is defined again on the activity scope of **Task A** and can be seen only by **Task A**. It covers the variable `b` from the root scope. ### Variable propagation When variables are merged into a process instance (e.g. on job completion, on message correlation, etc.) each variable is propagated from the scope of the activity to its higher scopes. The propagation ends when a scope contains a variable with the same name. In this case, the variable value is updated. If no scope contains this variable, it's created as a new variable in the root scope. What an element propagates when it completes, and whether it propagates anything at all, depends on the BPMN element type. See [variable propagation by BPMN element](#variable-propagation-by-bpmn-element) for the full breakdown. ![variable-propagation](assets/variable-propagation.png) The job of **Task B** is completed with the variables `b`, `c`, and `d`. The variables `b` and `c` are already defined in higher scopes and are updated with the new values. Variable `d` doesn't exist before and is created in the root scope. ### Variable propagation by BPMN element What an element propagates to its parent scope when it completes depends on its type: | BPMN element | What propagates on completion | | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Service, send, user, receive, script, and business rule tasks | The task result: the job result, correlated message payload, or evaluated expression. With an output mapping, only the mapped variables propagate; without one, the whole result propagates. | | Embedded and event subprocesses | Nothing by default. Local variables are discarded when the subprocess completes unless an output mapping propagates them. | | Call activity | By default, all variables of the child process instance, because `propagateAllChildVariables` is enabled. With an output mapping, only the mapped variables. If you disable `propagateAllChildVariables` and define no output mapping, nothing propagates. | | Ad-hoc subprocess | Its output collection, if configured, plus any variables an output mapping propagates. Variables written by its activated activities stay local to each activation. | | Multi-instance activity | Its output collection, if configured, pushed to the parent scope when the loop finishes. The `loopCounter` variable and the per-instance input element variable stay local to each instance. | | Start, intermediate catch, and boundary events | The correlated event payload (message, signal, timer, or conditional), or the mapped variables if an output mapping is defined. | | Throw and end events | Only the mapped variables where output mappings are supported, or, for message and signal variants backed by a job or connector, the job result. The none, link, and escalation variants propagate nothing without a mapping. The error, terminate, and compensation variants do not support output mappings and propagate nothing. | A call activity that disables `propagateAllChildVariables` without defining an output mapping discards everything the child process produced. To return only selected child process variables to the caller, keep `propagateAllChildVariables` enabled and define an output mapping on the call activity. :::note An error end event does not propagate variables through output mappings. Any payload it carries travels to the catching boundary event or event subprocess instead. ::: ### Internal engine variables Some variables are created and managed by the engine to control execution. They are scoped locally to the element instance that uses them and are not intended as process data: - `loopCounter`: the current iteration index inside a multi-instance activity. Read it within an iteration, but do not propagate it to higher scopes. - The multi-instance input element variable: the item assigned to the current iteration from the input collection. Its name is set by the `inputElement` attribute, and it stays local to that iteration. Avoid referencing these variables in output mappings. Propagating them beyond their intended scope can produce incorrect results. ### Local variables In some cases, variables should be set in a given scope, even if they didn't exist in this scope before. This is possible with **local variables**. Use local variables to create or update variables in a given scope, regardless of whether they existed in this scope before. ### Define local variables To define a local variable in [Camunda Hub](/components/hub/workspace/modeler/index.md) or Desktop Modeler, add an input mapping on the activity, subprocess, or call activity where you want the variable to exist. For details on input mapping concepts (`source` and `target`) see [input/output variable mappings](#inputoutput-variable-mappings). The `target` of the input mapping becomes a local variable in that element's scope. For example, an input mapping with `source: =customer.name` and `target: reviewerName` creates the local variable `reviewerName` in that scope. ### Scope behavior in common modeling patterns The scope boundary depends on the BPMN element you use: | Pattern | Scope behavior | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Embedded subprocess | Creates a local scope inside the same process instance. Local variables stay inside the subprocess unless you propagate them with output mappings. Root-scope process variables are still shared, so parallel or multi-instance embedded subprocess instances can overwrite the same process variable. | | Call activity | Starts a new process instance with its own variable scope. Configure the call activity's parent variable propagation settings and input mappings to control which variables the child receives. Use the call activity's child variable propagation settings and output mappings to control which variables are returned to the caller. | | Multi-instance activity | Each instance has its own local scope. Use input mappings to create per-instance local variables, especially in parallel multi-instance activities, to avoid race conditions when multiple instances update the same process variable. | If a form field or task variable should be different for each subprocess or each multi-instance instance, define it as a local variable with an input mapping instead of writing it directly to the root process scope. Use local variables to isolate data within a specific scope: per-instance data in multi-instance activities (to avoid race conditions when parallel instances update the same root process variable), subprocess-specific data that shouldn't affect sibling instances or the parent scope, and task-specific context that shouldn't persist to the process level. Local variables are removed when a scope is exited unless you explicitly propagate them with output mappings. :::warning A local variable blocks later writes of the same name If another operation writes a variable with the same name, variable propagation finds the local variable first. This happens when a job completes or an input mapping creates the variable. Later writes update only the local variable, not the process instance. When the scope exits, Camunda discards the local variable and any updates. The operation appears to succeed, but the change never propagates. For example, an input mapping creates a local variable `x`. When the element's job completes with a new value for `x` (without an output mapping), it updates the local `x`, not the process instance. The next element still sees the previous value. To expose a variable outside its scope, use an [output mapping](#inputoutput-variable-mappings). ::: ## Input/output variable mappings Input/output variable mappings can be used to create new variables or customize how variables are merged into the process instance. Variable mappings are defined in the process as extension elements under `ioMapping`. Every variable mapping has a `source` and a `target` expression. The `source` expression defines the **value** of the mapping. It usually [accesses a variable](/components/modeler/feel/language-guide/feel-variables.md#access-variable) of the process instance that holds the value. If the variable or nested property doesn't exist, the value resolves to `null`. The same applies if you do not provide a `source`. The `target` expression defines **where** the value of the `source` expression is stored. It can reference a variable by its name or a nested property of a variable. If the variable or the nested property doesn't exist, it's created. Variable mappings are evaluated in the defined order. Therefore, a `source` expression can access the target variable of a previous mapping. ![variable-mappings](assets/variable-mappings.png) **Input mappings** | Source | Target | | --------------- | ----------- | | `customer.name` | `sender` | | `customer.iban` | `iban` | | `totalPrice` | `price` | | `orderId` | `reference` | **Output mapping** | Source | Target | | -------- | --------------- | | `status` | `paymentStatus` | ### Input mappings Input mappings can be used to create new variables. They can be defined on [service tasks](/components/modeler/bpmn/service-tasks/service-tasks.md), [script tasks](/components/modeler/bpmn/script-tasks/script-tasks.md), [business rule tasks](/components/modeler/bpmn/business-rule-tasks/business-rule-tasks.md), [call activities](/components/modeler/bpmn/call-activities/call-activities.md), [user tasks](/components/modeler/bpmn/user-tasks/user-tasks.md), [send tasks](/components/modeler/bpmn/send-tasks/send-tasks.md), [subprocesses](/components/modeler/bpmn/subprocesses.md), [event subprocesses](/components/modeler/bpmn/event-subprocesses/event-subprocesses.md), and [ad-hoc subprocesses](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md). Support depends on the element type. See the element's own page for details. When an input mapping is applied, it creates a new [**local variable**](#local-variables) in the scope where the mapping is defined. In [Camunda Hub](/components/hub/workspace/modeler/index.md) or Desktop Modeler, define these mappings in the element properties. You can use [expressions](./expressions.md) or static values for input mappings. You can leave the `source` empty to map the `target` variable to `null`. For string literals containing escaped characters (e.g., a newline character `\n`), the string is returned in its original form as expected (no double escaping is applied). Examples: | Process variables | Input mappings | New variables | | -------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------- | | `orderId: "order-123"` | **source:** `=orderId` **target:** `reference` | `reference: "order-123"` | | `customer:{"name": "John"}` | **source:** `=customer.name`**target:** `sender` | `sender: "John"` | | `customer: "John"``iban: "DE456"` | **source:** `=customer` **target:** `sender.name`**source:** `=iban`**target:** `sender.iban` | `sender: {"name": "John", "iban": "DE456"}` | | - | **source:** `"Peter"`**target:** `sender` | `sender: "Peter"` | | `customer:{"name": "John"}` | **source:** (not provided)**target:** `customer` | `customer: null` | ### Secret references in input mappings An input mapping's `source` can reference a secret directly, without first storing it in a process variable. Write the reference as `camunda.secrets.` in a FEEL expression. Using secret references requires a secret store that holds the secret. In SaaS, the store is provisioned for you. You can reference values from the cluster's **Cluster secrets** tab as `camunda.secrets.` without additional setup. See [manage connector secrets](/components/saas/clusters/manage-secrets.md#reference-connector-secrets-as-camundasecretsname). In Self-Managed, an operator must [configure a secret store](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#secrets). Without an available store, Camunda cannot resolve the reference. | Process variables | Input mappings | New variables | | ----------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------- | | - | **source:** `=camunda.secrets.API_TOKEN`**target:** `token` | `token` holds the secret's value when the job reaches the worker | | - | **source:** `="Bearer " + camunda.secrets.API_TOKEN`**target:** `authorization` | `authorization` holds `"Bearer "` followed by the secret's value | Secret references are only resolved in input mappings defined on elements that create a job for a job worker (for example, service tasks, business rule tasks, and ad hoc sub-processes). See [secret resolution and job activation](secret-resolution-and-job-activation.md) for how a reference is resolved and what the worker receives once the job is handed out. The stored process variable always holds the placeholder text `camunda.secrets.`; resolution replaces it only in the payload handed to the worker, not in the variable kept in the process instance's state. Any other consumer of that variable sees the placeholder. A reference must be an expression, and the reference itself must be exactly the three-segment path `camunda.secrets.`. It can still take part in a supported expression, such as the concatenation shown above, but the following rejections apply: - Writing the reference as a plain string, or quoting it inside an expression, is rejected at deployment rather than passed through as literal text: ```feel ="camunda.secrets.API_TOKEN" ``` ```text Secret reference(s) 'camunda.secrets.API_TOKEN' must be used as an expression (e.g. '=camunda.secrets.'), not as a string literal, in input mapping source '="camunda.secrets.API_TOKEN"'. ``` - A trailing path after the name, such as `camunda.secrets.API_TOKEN.length`, is not treated as a reference. It's evaluated as an ordinary FEEL path access instead. - A reference placed inside a list, or inside a context built by an `if` branch, is rejected at deployment, because the reference is no longer a value the mapping assigns directly: ```feel =[camunda.secrets.API_TOKEN] ``` ```text Input mapping source '=[camunda.secrets.API_TOKEN]' puts a secret reference inside a list, or inside a context built by an 'if' branch. Camunda can only replace a secret where the mapping assigns it directly to a value, so this secret would never be filled in. Assign each secret reference to its own input mapping instead. ``` Give each secret its own input mapping. A later mapping that reads the _variable_ created by an earlier one, rather than writing `camunda.secrets.` itself, does not get the secret resolved: | Input mappings | New variables | | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | **source:** `=camunda.secrets.API_TOKEN`**target:** `x`**source:** `=x`**target:** `y` | `x` exposes the resolved secret value to the worker; `y` holds the literal placeholder text, not the secret value | :::note A secret reference can also come from a [cluster variable](/components/modeler/feel/cluster-variable/data-types.md) of kind `SECRET_REFERENCE`. An input mapping that selects such a variable, for example `=camunda.vars.env.MY_CONFIG`, resolves the `camunda.secrets.` references embedded in its value the same way. See [resolve secret references in a cluster variable](/components/modeler/feel/cluster-variable/usage-guide.md#resolve-secret-references-in-a-cluster-variable). ::: :::note Avoid using `camunda` as a process variable name. A process variable literally named `camunda` takes precedence over the secret namespace, so `camunda.secrets.` resolves against that variable and no secret is injected. ::: #### Escape secret names with special characters A secret name containing a character FEEL doesn't allow in a bare identifier (most commonly a dash) must be backtick-escaped, the same way any other FEEL name with special characters is: ```feel =camunda.secrets.`db-password` ``` Without the backticks, `camunda.secrets.db-password` parses as a subtraction (`db` minus `password`). FEEL reads the left operand as a reference named `db`, which is not a valid secret reference, so the deployment fails at evaluation. :::note When a reference is written directly in an input mapping source, backtick escaping accepts any name, including a name your secret store accepts but the [`/v2/secrets`](/apis-tools/orchestration-cluster-api-rest/specifications/list-secrets.api.mdx) endpoints do not. Those endpoints list and resolve only names matching `[\p{Alnum}_-]+`. A name outside that set, such as `tls.crt`, can be backtick-escaped and resolved from a process model (``=camunda.secrets.`tls.crt` ``) if your secret store holds it under that name, but the same secret cannot be listed or resolved through `/v2/secrets`. This applies to references written directly in an input mapping source, not to references embedded in a [`SECRET_REFERENCE`-kind cluster variable value](/components/modeler/feel/cluster-variable/data-types.md#where-references-can-appear-in-a-value), whose names follow the restricted cluster-variable character set. ::: ### Output mappings Output mappings can be used for several purposes: - To customize how variables are merged into the process instance. - They can be defined on most tasks (service, send, user, receive, script, and business rule tasks), [embedded](/components/modeler/bpmn/embedded-subprocesses/embedded-subprocesses.md) and [event subprocesses](/components/modeler/bpmn/event-subprocesses/event-subprocesses.md), [call activities](/components/modeler/bpmn/call-activities/call-activities.md), and [ad-hoc subprocesses](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md). - They can also be defined on many events, including message, signal, timer, and conditional catch events, boundary events, and start events. If **one or more** output mappings are defined, the results variables are set as **local variables** in the scope where the mapping is defined. Then, the output mappings are applied to the variables and create new variables in this scope. The new variables are merged into the parent scope. If there is no mapping for a job/message variable, the variable is not merged. :::note This can lead to a case where some variables with an output mapping are merged into the parent scope, and others without an output mapping are not merged. ::: If **no** output mappings are defined, the behavior depends on the element. Tasks and events that produce a result (a job result, or a correlated message or signal payload) merge that whole result into the parent scope. Subprocesses and events that produce no result, such as none, link, escalation, and compensation events, propagate nothing; their local variables are discarded when the scope is left. For an overview across all element types, see [variable propagation by BPMN element](#variable-propagation-by-bpmn-element). In the case of a subprocess, the behavior is different. There are no results variables to be merged. However, output mappings can be used to propagate **local variables** of the subprocess to higher scopes. By default, all **local variables** are removed when the scope is left. Examples: | Results variables | Output mappings | Process variables | | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------- | | `status: "Ok"` | **source:** `=status`**target:** `paymentStatus` | `paymentStatus: "OK"` | | `result: {"status": "Ok", "transactionId": "t-789"}` | **source:** `=result.status`**target:** `paymentStatus`**source:** `=result.transactionId`**target:** `transactionId` | `paymentStatus: "Ok"``transactionId: "t-789"` | :::note An output mapping `target` that contains a period (for example, `order.status`) updates only the final property and merges it into the existing variable at that path, leaving sibling properties untouched. This is supported, but use it deliberately, as it modifies a property of an existing variable rather than replacing the whole variable. For details, see [nested variables in mappings](#nested-variables-in-mappings). ::: ### Nested variables in mappings Input and output mappings build nested `target` paths in opposite ways: - **Input mappings** treat the `target` as a full path and write the entire nested object at that path, replacing any value already there. - **Output mappings** write only the final property (the leaf) of the `target` path and merge it into the existing structure, preserving sibling properties that are already present. For example, an input mapping with target `order` replaces the whole `order` variable, while an output mapping with target `order.status` sets only `status` inside an existing `order` variable and leaves its other properties unchanged. ### Context variable A context variable is a reserved variable that describes the context of a task. It can group variables together to provide a detailed description of the task or offer more descriptive data about it. The reserved variable name for a context variable is `taskContextDisplayName`. This name is reserved exclusively for this purpose and should not be used for other variables. :::warning Context variables were a Tasklist V1 feature and are not supported in current Tasklist releases. See [Tasklist API changes](../tasklist/api-versions.md). ::: Example: | Input variable | Example | | ------------------------ | ------------------------------------ | | `taskContextDisplayName` | `This is a context variable example` | The data from the variable will be shown on the task tile, as shown in the example below: ![context-variables](assets/context-variables.png) ## Next steps - Understand how to [access variables](/components/modeler/feel/language-guide/feel-variables.md). - Explore how to centrally manage cluster configuration with [cluster variables](../../components/modeler/feel/cluster-variable/overview.md). --- ## Workflow patterns For true end-to-end process orchestration, you must be able to accurately express all the things happening in your business processes which will require simple and advanced workflow patterns. This page describes typical patterns and how you can implement them using Camunda and BPMN. ## The power of BPMN Let's discuss the ISO standard [Business Process Model and Notation (BPMN)](https://camunda.com/bpmn/) first, as this is really a great workflow language to express workflow patterns. BPMN was developed as a collaboration of different vendors rooted in real-life industry experience. It happened during a time when the scientific background of workflow patterns was already well researched, for example by the [Workflow Patterns Initiative](http://www.workflowpatterns.com/). In other words, scientists already wrote down all the patterns that are important to express any problem you might get in a workflow, and BPMN used this knowledge to design a language that implemented all the relevant patterns (refer to this [evaluation](http://www.workflowpatterns.com/evaluations/standard/bpmn.php), for example). Essentially, BPMN is feature complete and will always be able to express what you need to orchestrate your processes. Additionally, BPMN has expressed all real-life problems rather easily when reflecting on our more than 15 years of hands-on experience with the language. If you now try to rely on workflow languages that promise to be simpler than BPMN, what it really means is that they lack important workflow patterns. You might want to look in the blog post on [why process orchestration needs advanced workflow patterns](https://camunda.com/blog/2022/07/why-process-orchestration-needs-advanced-workflow-patterns/), showing exemplary workarounds that are necessary if the language cannot express certain patterns. Typically, this involves emulating advanced patterns with basic constructs plus programming code so that your development takes longer, your solution becomes more brittle, and the resulting process model can't serve as a communication vehicle for business and IT as the model will be contaminated with technical details. ## Routing The most basic workflow patterns are (excuse the play on words) around the basic flow of work. ### Sequence Refer to [Workflow Pattern 1: Sequence](http://www.workflowpatterns.com/patterns/control/basic/wcp1.php): "A task in a process is enabled after the completion of a preceding task in the same process." This is implemented by a [sequence flow](/components/modeler/bpmn/bpmn-primer.md#sequence-flow-controlling-the-flow-of-execution) connecting two activities: The first sequence flow (1) connects the start event with **Task A**. Then, **Task B** is connected using another sequence flow, meaning that it can only happen if **Task A** was completed. If **Task B** completes, the sequence flow routes to the end event so that the process instance can complete. You can read more about it in [our BPMN primer: sequence flows - controlling the flow of execution](/components/modeler/bpmn/bpmn-primer.md#sequence-flow-controlling-the-flow-of-execution). ### Conditions (if/then) Refer to [Workflow Pattern 4: Exclusive Choice](http://www.workflowpatterns.com/patterns/control/basic/wcp4.php): "The thread of control is immediately passed to precisely one of the outgoing branches." This is implemented by an [exclusive gateway (XOR)](/components/modeler/bpmn/exclusive-gateways/exclusive-gateways.md): All outgoing sequence flows of the XOR gateway (1) have a [condition](/components/modeler/feel/language-guide/feel-boolean-expressions.md) configured, which decides if the process continues in **Task B** (2 if `x>42`) or **Task C** (3if `not(x>42)`). You can read more about it in [our BPMN primer: gateways - steering flow](/components/modeler/bpmn/bpmn-primer.md#gateways-steering-flow). ### Invoke subworkflows You need to invoke another process as part of your process. This is implemented by a [call activity](/components/modeler/bpmn/call-activities/call-activities.md): 1 When the call activity is entered, a new process instance of the referenced process is created. Only when the created process instance is completed is the call activity left and the outgoing sequence flow taken. You can reference any other BPMN process, for example: ### Loop Refer to [Workflow Pattern 21: Structured Loop](http://www.workflowpatterns.com/patterns/control/basic/wcp21.php): "The ability to execute a task or subprocess repeatedly. The loop has either a pre-test or post-test condition associated with it." In BPMN, you can simply model a loop: 1 This exclusive gateway contains the expression to decide if to continue or exit the loop. The gateway can be before or after the loop. ### Static parallel branches Imagine you want some tasks known during design time to be carried out in parallel. Refer to [Workflow Pattern 2: Parallel Split](http://www.workflowpatterns.com/patterns/control/new/wcp2.php) and [Workflow Pattern 33: Generalized AND-Join](http://www.workflowpatterns.com/patterns/control/new/wcp33.php): "The divergence of a branch into two or more parallel branches each of which execute concurrently" plus "the convergence of two or more branches into a single subsequent branch." In BPMN, this is implemented using [parallel gateways (AND)](/components/modeler/bpmn/parallel-gateways/parallel-gateways.md): 1 This AND-gateway splits the flow into concurrent paths so that Task A, B, and C are executed in parallel. 2 This AND-gateway waits for Task A, B, and C to complete before the flow can move on. You can read more about it in [our BPMN primer: gateways - steering flow](/components/modeler/bpmn/bpmn-primer.md#gateways-steering-flow). ### Dynamic parallel branches You might want to execute some tasks for every element of a list, like the `for each` construct in programming languages. Refer to [Workflow Pattern 14: Multiple Instances with a priori Run-Time Knowledge](http://www.workflowpatterns.com/patterns/control/new/wcp14.php): "Multiple instances of a task can be created. The required number of instances may depend on a number of runtime factors, but is known before the task instances must be created. Once initiated, these instances are independent of each other and run concurrently. It is necessary to synchronize the instances at completion before any subsequent tasks can be triggered." In BPMN, this is implemented using [multiple instance activities](/components/modeler/bpmn/multi-instance/multi-instance.md): 1 The parallel multiple instance marker defines that this subprocess is executed multiple times - once for each element of a given collection (like a `for each` loop in a programming language). ### Wait A typical situation is that a process needs to wait for some event to happen, e.g. some time to pass or some external message to arrive. This is related to [Workflow Pattern 23: Transient Trigger](http://www.workflowpatterns.com/patterns/control/new/wcp23.php). In BPMN, this is implemented using [events](/components/modeler/bpmn/events.md) (or [receive tasks](/components/modeler/bpmn/receive-tasks/receive-tasks.md)): 1 The timer event causes the process to wait, in this case until a specific point in time is due or some duration has elapsed. Refer to [timer events](/components/modeler/bpmn/timer-events/timer-events.md) for more details. 2 The process will wait for a message to arrive. The message is an external trigger provided by API and can technically be anything, from a callback (e.g. via REST), over real messaging (like AMQP), or to notifications within your system. Refer to [message events](/components/modeler/bpmn/message-events/message-events.md) for more details. You can read more about events in [our BPMN primer: events - waiting for something to happen](/components/modeler/bpmn/bpmn-primer.md#events-waiting-for-something-to-happen). ## Reacting to events The waiting mentioned above is a special case where you react to events while not doing anything else. Oftentimes, you want to react to events even if the process is doing something else at the moment. This is described in this section. Typical examples are customer cancelation requests coming in for running order fulfillment processes, or timeouts if parts of the process take too long. ### Time based You want to react if a certain point in time is due or a specific time duration has passed. This is related to [Workflow Pattern 23: Transient Trigger](http://www.workflowpatterns.com/patterns/control/new/wcp23.php). In BPMN, you can leverage [boundary events](/components/modeler/bpmn/events.md#boundary-events) or [event subprocesses](/components/modeler/bpmn/event-subprocesses/event-subprocesses.md). Those events can be interrupting or non-interrupting, meaning you will either interrupt the current activity, or start something in parallel. 1 This timer is non-interrupting (dashed line), so the **Escalate request approval** task is started in parallel, additionally to the **Approve request** task. The idea is that the escalation task might make a manager double-checking the original task does not slip. Non-interrupting events can also be recurring, so you could also escalate "every two hours". 2 This timer is interrupting (solid line). Once it fires, the **Approve request** task is canceled and the process continues on the alternative path, in this case to automatically reject the request. Note that both timers so far can only happen if the task **Approve request** is active. 3 This is an event subprocess (dotted line). This can be activated from everywhere in the current scope. In this example, the scope is the whole process. 4 So if the process is not completed within the defined SLA, the timer fires and the event subprocess is started. As the timer is non-interrupting (dashed line again), it does not intervene with the typical flow of operations, but starts something additionally in parallel. :::note The above process is not necessarily modeled following all of our [modeling best practices](/components/best-practices/modeling/creating-readable-process-models.md), but intentionally shows different ways to use BPMN to implement certain workflow patterns. ::: ### External messages/events You might also want to react to certain incoming messages or events in an existing process. A good example is a customer canceling the current order fulfillment process. This might be possible only in a certain process phase and lead to different actions. This is related to [Workflow Pattern 23: Transient Trigger](http://www.workflowpatterns.com/patterns/control/new/wcp23.php) and [Workflow Pattern 24: Persistent Trigger](http://www.workflowpatterns.com/patterns/control/new/wcp24.php). As with timers, you can leverage [boundary events](/components/modeler/bpmn/events.md#boundary-events) or [event subprocesses](/components/modeler/bpmn/event-subprocesses/event-subprocesses.md). Assume that an order cancelation message comes in for the current process instance using [message correlation](/components/concepts/messages.md). 1 Subprocesses can be easily used to define phases of a process, as the cancelation is treated differently depending on the current process phase. 2 For example, a cancelation during the clearing phase has no consequences and can simply be executed. 3 But when the process is already in the preparation phase it might need to clean up certain things properly. 4 During delivery, it does not even allow cancelations anymore. This is also why this event is non-interrupting (dashed line), so we keep doing **Delivery**. ### Correlation mechanisms Mapping external messages to an existing process instance is called [message correlation](/components/concepts/messages.md). This is a crucial functionality to ensure you can communicate with process instances from the outside. There are two main problems to solve: 1. How to find the right process instance? In Camunda, this is solved by a `message name` and a `correlation key` (e.g. `orderCanceled` and `order-42`). 2. How to persist messages if a process instance is not yet ready to receive that message yet? In Camunda, this is solved by having an internal message store and a `time to live` attached to messages. This is related to [Workflow Pattern 24: Persistent Trigger](http://www.workflowpatterns.com/patterns/control/new/wcp24.php) You can find more information in [our documentation about messages](/components/concepts/messages.md). ### Events from subprocesses Sometimes, a subprocess needs to communicate with its parent process without ending the subprocess yet. BPMN allows this by an [escalation event](/components/modeler/bpmn/bpmn-coverage.md). 1 An escalation event can be thrown from any of the called subprocesses and is picked up by its parent to start something in parallel, as this is a non-interrupting event (dashed line). The subprocess can raise the escalation any time: ### Broadcasts and engine-wide events While messages are always targeted at one specific process instance, you might also want to inform many processes about an event at once. For example, you might regularly adjust certain customer scoring rules that always should be taken into account immediately. This can be implemented using the [signal event](/components/modeler/bpmn/bpmn-coverage.md). 1 The signal event is caught and in this case interrupts the onboarding to go back to score the customer again. ## Handling errors Handling exceptions well is one of the most important capabilities of a workflow engine, and it needs built-in support from the modeling language. You might also want to look into our [best practice: modeling beyond the happy path](/components/best-practices/modeling/modeling-beyond-the-happy-path.md) to understand possibilities. ### Error scopes The reaction to errors might need to be different depending on the current state of the process. This can be achieved by using [subprocesses](/components/modeler/bpmn/embedded-subprocesses/embedded-subprocesses.md) in combination with either [boundary events](/components/modeler/bpmn/events.md#boundary-events) or [event subprocesses](/components/modeler/bpmn/event-subprocesses/event-subprocesses.md). 1 This boundary error event is attached to the subprocess "clearing" and only catches errors within that subprocess. The idea here would be that in case of any clearing service not being available, the order is assumed cleared. Note that this example is mainly built for illustration, and does not necessarily mean this is the best way to solve this business requirement. 2 Alternatively, this error event subprocess is triggered whenever there is a fraud detected, independent of whether the error occurs in any of the subprocesses or the main process. ### Catch errors per type You might need to react to different event types differently, which is possible by using the [error type](/components/modeler/bpmn/error-events/error-events.md#defining-the-error) known to BPMN: Now there is a different reaction if fraud was detected (1) or the address was found to contain an error (2). ## Business transactions Modern systems are highly distributed across the network. In such systems, you cannot rely on technical ACID transactions for consistency, but need to elevate decisions around consistency or regaining consistency to the business level. Refer to [Achieving consistency without transaction managers](https://blog.bernd-ruecker.com/achieving-consistency-without-transaction-managers-7cb480bd08c) for additional background on this. ### Compensation An important problem to solve is how to roll back a business transaction in case of problems. In other words, how to restore business consistency. One strategy is to leverage compensating activities to undo the original actions whenever the problem occurs. This is also known as the [Saga Pattern](https://blog.bernd-ruecker.com/saga-how-to-implement-complex-business-transactions-without-two-phase-commit-e00aa41a1b1b). In BPMN, you can use [compensation events](/components/modeler/bpmn/bpmn-coverage.md) to easily implement compensations in your processes. 1 For every task in a process model, you can define a compensation task. This can be any valid BPMN task, like a service task, a human task (2), or a subprocess, for example. 3 This compensation task is connected to the original task by a dedicated compensation event. 4 Within your process model, you can define when it is time to compensate. Whenever you trigger the compensation event, all tasks of the current scope that were executed are automatically compensated. This means that their configured compensation task is executed. The big advantage is that you don't have to remodel the routing logic to compensate correctly, like checking again if the customer balance was used. The workflow engine will take care automatically, also in more complicated situations like multiple instance activities. --- ## Workspaces A workspace is a collaboration space in Camunda Hub. It represents a team or business domain, and it brings together the people, the [projects](./projects.md), and the [environments](./environments.md) that the team works with. ## Example A payments team might have a `Payments` workspace: - **Members**: The developers, analysts, and reviewers of the payments team, each with a workspace role. - **Projects**: A `consumer-loan-approval` project and a `refund-handling` project. - **Environments**: A `paymentstest` environment for testing, and a `payments` environment that an organization admin assigned to the team. ## Workspaces in Camunda Hub In Camunda Hub, an organization contains workspaces, and workspaces contain projects: ``` Camunda Hub └─ Organization └─ Workspace ├─ Members ├─ Environments └─ Projects └─ Files and folders ``` A workspace has the following parts: | Part | Description | | :----------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Members | The users who can see the workspace. Each member has a [workspace role](/components/hub/organization/manage-workspaces/manage-workspace-members.md#workspace-roles): Workspace Admin, Editor, Commenter, or Viewer. | | Projects | The [projects](./projects.md) of the workspace. Every project belongs to exactly one workspace. | | Environments | The [environments](./environments.md) that the projects of the workspace can deploy to. An organization admin assigns them. | Members can view only the workspaces they're invited to. Organization owners and admins can access every workspace. ## Workspaces and projects A workspace is the container of projects. Every project belongs to one workspace, and the members of the workspace decide who can work on it. - A project doesn't have its own deployment targets. It deploys to the environments assigned to its workspace, and every project in the workspace sees the same environments. - When you delete a workspace, its projects are deleted with it. You can restore both from [recently deleted](/components/hub/workspace/manage-projects/recently-deleted.md) during the retention period. Learn more about [projects](./projects.md). ## Workspaces and environments A workspace is assigned environments, never clusters. An organization admin decides which environments a workspace can use, so a team can deploy only to the places that were approved for it. An environment can be assigned to more than one workspace, and a workspace can have any number of environments, including none. Workspace admins and editors see the environments of their workspace. Viewers and commenters don't. Learn more about [environments](./environments.md) and [clusters](./clusters.md). ## Workspaces in Desktop Modeler Desktop Modeler doesn't have workspaces. It works with local files and [projects](./projects.md#process-applications-in-desktop-modeler). ## Next steps - [Manage workspaces](/components/hub/organization/manage-workspaces/index.md) - [Assign environments to a workspace](/components/hub/organization/manage-environments/assign-environments.md) - [Manage projects](/components/hub/workspace/manage-projects/manage-projects.md) --- ## Inbound connector deduplication Inbound In the simplest case, each inbound connector element in a BPMN diagram corresponds to a unique endpoint, event consumer, or a polling task. However, the connector runtime can combine multiple compatible inbound connector elements. The runtime can do this automatically, or you can request it. This page explains the concept of deduplication in the connector runtime. ## Purpose of deduplication Consider the following BPMN diagram: ![Connector deduplication use-case example](../img/deduplication-example.png) In this diagram, two connector events are listening to the same message queue that can contain messages of two types: `PAYMENT_COMPLETED` and `PAYMENT_CANCELLED`. When the process execution arrives at the event gateway, the type of the message determines which path the process will take. If each connector event listened to the message queue using a separate subscription, this might lead to race conditions if the message is received by a different consumer (for example, the `PAYMENT_COMPLETED` event being consumed by the consumer that expects `PAYMENT_CANCELLED`). Eventually, this might lead to message loss or delayed processing, while also increasing the load on the message broker as the message is returned to the queue. To avoid this, both events can be assigned to the same subscription by assigning the same deduplication ID to both events. Then, all messages will be consumed by the same subscription, and the connector runtime will evaluate the activation condition of each event to determine which one should be triggered. :::note When using this pattern, ensure each event’s activation condition is mutually exclusive, so only one event is triggered per message. Attempting to trigger multiple events for the same message will result in an error. ::: ## Automatic deduplication By default, the connector runtime assigns the same deduplication ID to connector events with matching properties, and different IDs to events with different properties. In this context, equal properties means the properties that define the business logic of the connector are exactly the same, including whitespace characters. The automatic deduplication only takes into account the properties that are related to the business logic of the connector itself (for example, **Server URL** or **Authentication properties**). It does not take into account the properties that define output mapping (**Result variable**, **Result expression**, **Response expression**), correlation (**Correlation key (process)**, **Correlation key (payload)**, **Activation condition**), or other properties that are handled by the connector runtime and not by the connector itself. This way, two connectors of the same type that are identical in terms of business logic and are defined in the same process definition will be deduplicated automatically. ### Cross-version deduplication In Camunda 8.9+, deduplication is performed across all versions of the same process definition. This means that if you deploy version 1 and version 2 of a process with the same compatible inbound connector, there will only be one executable connector instance serving both versions. This behavior is a natural extension of support for [inbound connectors across multiple process versions](./inbound-lifecycle.md). When connector properties are identical across versions, the connector runtime automatically consolidates them into a single subscription or endpoint. To learn more about how inbound connectors behave across process versions, see [Inbound connector lifecycle](./inbound-lifecycle.md). :::warning Webhook connectors and multiple versions When using webhook connectors, you cannot reuse the same webhook endpoint for multiple process versions if you want to keep them both active simultaneously. The default behavior is to keep the old version running when you deploy a new version that uses the same endpoint. The new version's connector will not become active until no process instances are running on the older version. To keep both versions active: - Complete all process instances on the older version before you deploy the new version. - Or, use a different webhook endpoint path in the new version. For more details, see [Inbound connector lifecycle](./inbound-lifecycle.md). ::: ## Manual deduplication You can manually assign a deduplication ID to each connector event. This allows you to group connectors in a more flexible way based on your requirements. If needed, you can have multiple connectors with the same properties that have different deduplication IDs. This way, you can still have multiple instances of the same connector listening to the same event source, but each instance will have its own deduplication ID and will be treated as a separate entity by the connector runtime. To assign a deduplication ID, take the following steps: 1. Enable the **Manual mode** checkbox in the **Deduplication** section of the connector properties. 2. In **Deduplication ID**, enter the deduplication ID. 3. Repeat the process for all connectors that should share the same deduplication ID. 4. Deploy the BPMN diagram for the changes to take effect. ![Deduplication input example](../img/deduplication-input-example.png) :::note When manual deduplication is used, connectors that have the same deduplication ID must also have the same properties. Attempting to assign the same deduplication ID to connectors with different properties will result in a runtime error. ::: ## Should I use automatic or manual deduplication? Use automatic deduplication if: - You don't need to group connectors in a specific way and don't have any special requirements for deduplication. - Deduplication configuration is not important for your use case (you don't use multiple connectors in the same process that listen to the same event source). Use manual deduplication if: - You need to group connectors in a specific way that is not supported by automatic deduplication. - You are unsure which properties of the connector are used for automatic deduplication. - You want to have more control over the deduplication process. ## How to choose a deduplication ID A deduplication ID can contain alphanumeric characters, dashes, and underscores. We recommend using a descriptive ID that reflects the deduplication group’s purpose, for example, `payment-outcome-event-consumer`. ## Limitations of deduplication While deduplication is a powerful tool that can optimize the execution of your BPMN process, it has some limitations. It is important to understand them to avoid unexpected behavior. 1. **Deduplication ID scope** – The deduplication ID is scoped to a single process definition across all its versions. You can’t deduplicate connectors across different process definitions. 2. **Connector type** – Connectors of different types can’t share the same deduplication ID (for example, a Webhook connector and a Message Queue connector). 3. **Connector properties** – Connectors that share the same deduplication ID must use the same business-logic properties. This means they must have the same **Webhook ID**, **Server URL**, **Authentication properties**, and so on, depending on the connector type. 4. **Activation condition** – Connectors with the same deduplication ID must have mutually exclusive activation conditions. If multiple connectors can evaluate to true for the same message, the connector runtime can’t determine which connector to trigger and returns an error. --- ## Inbound connector lifecycle Inbound Inbound connectors in Camunda have a lifecycle that depends on process definition deployments. It is important to understand this lifecycle to work with inbound connectors effectively. This page explains when inbound connectors are activated and deactivated, what can affect their execution, and how to monitor their status. ## Inbound connector executables An executable is an instance of an inbound connector that is managed by the connector runtime. An executable is mapped to one or more process definitions deployed to the engine. In the simplest case, deploying a new process definition will create a new executable in the connector runtime. However, in general, one executable can be reused for multiple inbound connector elements in the diagram. To learn how the runtime reuses executables across connectors, see [Inbound connector deduplication](./deduplication.md). ## Reacting to process definition deployments ![Inbound connector data sources](../img/inbound-data-source.png) The connector runtime maintains an internal state that tracks: - Latest versions of process definitions. - Previous process-definition versions with active message subscriptions. The following endpoints of the Orchestration Cluster API are used as data sources: - [Search process definitions](/apis-tools/orchestration-cluster-api-rest/specifications/search-process-definitions.api.mdx) - [Search message subscriptions](/apis-tools/orchestration-cluster-api-rest/specifications/search-message-subscriptions.api.mdx) If a process definition matches at least one of these two criteria (i.e. it is either the latest version of that process definition, or it has active instances waiting on message subscriptions), the connector runtime will create an executable for every inbound connector element in that process and keep it active as long as these criteria are met. If a process definition is deleted from the engine; or it is replaced with a newer version of the same process, and has no active message subscriptions, the connector runtime will deactivate the related executables. ## Lifecycle overview ![Inbound connector lifecycle](../img/inbound-lifecycle.png) The lifecycle of an executable starts when the connector runtime detects a change in the engine state and determines that it needs a new executable to reflect that change. - `INVALID_DEFINITION` – The runtime detects an invalid deduplication configuration and stores the executable in this state. For details about supported configurations and common causes, see [Inbound connector deduplication](./deduplication.md). - `FAILED_TO_ACTIVATE` – The runtime attempts to activate the executable, but the activation fails. The executable remains inactive in this state. - `ACTIVE` – The runtime successfully activates the executable. In this state, the executable is ready to receive and process external events from the third-party system. - `CANCELED` – An active executable encounters an unrecoverable error and transitions to this state. This is rare, as most connectors include retry logic to handle transient failures such as temporary network issues. When you deploy a new process version, the connector runtime checks whether it can deduplicate inbound connectors in the new version with any currently active executables. If deduplication is possible, the runtime updates the existing executable. If the executable is inactive (for example, in the `CANCELED` or `FAILED_TO_ACTIVATE` state), the runtime also attempts to restart it. When the executable is no longer needed (for example, when its originating process version is no longer the latest and no active instances are waiting on message subscriptions), the runtime deactivates it. ## Modify an existing inbound connector element Editing certain configuration on an already-deployed inbound connector element can cause unexpected behavior instead of a clean update. The connector runtime can derive internal state, such as a consumer or subscription identity, from an element's configuration only when the element activates. Editing that configuration in place does not necessarily recompute the derived state to match, which can lead to inconsistent or failed behavior. When in doubt, delete the inbound connector element and create a new one instead of editing its configuration in place. [Element templates](/components/modeler/element-templates/about-templates.md) are also versioned, and each BPMN element references the specific template version it was created with. When a newer template version adds a new property, an already-deployed element keeps using its original template version, so the property is simply absent from that element, and the element keeps its prior default behavior. Upgrading the Camunda runtime alone does not change this. To adopt a new template version's property or behavior on an existing element, [update the element to the new template version](/components/modeler/desktop-modeler/element-templates/using-templates.md#updating-templates) in Camunda Modeler, and redeploy the diagram. The Kafka consumer connector illustrates both behaviors: - [Modify an existing inbound Kafka connector](../out-of-the-box-connectors/kafka.md#modify-an-existing-inbound-kafka-connector) shows how editing the **Consumer Group ID** or **Offsets** properties in place can cause message replay or activation failure. - [Upgrade from a version without the Consume unmatched events checkbox](../out-of-the-box-connectors/kafka.md#upgrade-from-a-version-without-the-consume-unmatched-events-checkbox) shows how an existing element keeps its prior default for a checkbox added in a later template version until you update its template. --- ## Intrinsic functions Outbound Intrinsic functions are transformations you can use to preprocess connector input data before invoking a connector. - Intrinsic functions are JSON structures you can define in element template input fields. - Intrinsic functions are executed in the connector runtime. :::note You can only use intrinsic functions with [outbound connectors](/components/connectors/connector-types.md#outbound-connectors) as they transform data obtained from process variables. ::: ## Use cases A common use case is to transform a [Camunda document](/components/document-handling/getting-started.md) to a specific format when using a REST connector. For example: - You have a Camunda document containing some data in JSON format. - You want to send this data in the HTTP request body to a REST API using a connector. The [REST connector](/components/connectors/protocol/rest.md) is capable of handling documents, but the data format in which the document is sent by default may not match the format expected by the REST API. In this example, the document in the Camunda document store is structured as follows, with the document reference stored in the `document` process variable: ```json { "name": "John Doe", "age": 30 } ``` The REST API expects the data in the following format: ```json { "user": { "name": "John Doe", "age": 30 } } ``` In this example, you can use the `getText` intrinsic function to extract the text content from the document and insert it into the request body. The resulting JSON structure of your connector's input would be as follows: ```json { "user": { "camunda.function.type": "getText", "params": [ document ] } } ``` - `document` is the process variable that contains the document reference. - The [`getText`](#gettext) function extracts the text content from the document and inserts it into the request body as a string. :::note - You can use intrinsic functions with any outbound connector to execute an operation with a connector input. - Intrinsic functions are executed in the connector runtime. ::: ## Available functions The following prebuilt functions are available: ### `base64` The `base64` function accepts a document or a string. It encodes the document content or string to Base64 format. ```json { "camunda.function.type": "base64", "params": [ myDocument ] } ``` ### `createLink` The `createLink` function accepts a document and an optional TTL (time-to-live) value. It creates a temporary pre-signed link to the document in the Camunda document storage. The link is returned as a string. - Pre-signed links can only be created for documents that are stored in cloud storage (for example, AWS S3 or Google Cloud Storage), but not local or in-memory storage. - The optional TTL parameter must be a valid [ISO 8601 duration](https://en.wikipedia.org/wiki/ISO_8601#Durations) string. - If not provided, the default TTL is 1 hour. ```json { "camunda.function.type": "createLink", "params": [ myDocument, "PT1H" ] } ``` ### `getText` The `getText` function accepts a document and an optional encoding parameter. It extracts the text content from the document and returns it as a string. - The optional encoding parameter specifies the character encoding to be used when extracting the text. - If not provided, the default encoding is UTF-8. ```json { "camunda.function.type": "getText", "params": [ myDocument, "UTF-8" ] } ``` ### `getJson` The `getJson` function accepts a document and an optional FEEL expression parameter. It extracts the text content from the JSON document and returns it as an object. - The optional FEEL expression parameter specifies the part that will be extracted from the JSON document content. - If not provided, the whole document is returned as a JSON object. ```json { "camunda.function.type": "getJson", "params": [ myDocument, "field1.field2" ] } ``` ### `createGithubAppInstallationToken` The `createGithubAppInstallationToken` function generates a GitHub App installation access token. This is useful when you need to authenticate as a GitHub App installation to access GitHub APIs. The function accepts three required parameters and one optional parameter: - `privateKey`: The RSA private key of your GitHub App (PEM format). Store this value as a secret. - `appId`: The app ID of your GitHub App. - `installationId`: The installation ID of your GitHub App. See [GitHub's documentation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app-installation#generating-an-installation-access-token) for instructions on how to find your installation ID. - `githubApiBaseUrl` (optional): The base URL of a GitHub Enterprise Server or Cloud host to target instead of the public GitHub API. Omit this parameter, or pass `null`, to use the public GitHub API (`https://api.github.com`). ```json { "camunda.function.type": "createGithubAppInstallationToken", "params": ["{{secrets.GITHUB_APP_PRIVATE_KEY}}", "12345", "67890"] } ``` The function returns the installation access token as a string. You can use this token to authenticate GitHub API requests. #### Custom GitHub API base URL (GitHub Enterprise) To target a GitHub Enterprise Server or Cloud host, pass its base URL as the fourth parameter: ```json { "camunda.function.type": "createGithubAppInstallationToken", "params": [ "{{secrets.GITHUB_APP_PRIVATE_KEY}}", "12345", "67890", "https://github.example.com/api/v3" ] } ``` The resolved base URL must be allow-listed on the connector runtime before use: - By default, with no configuration, only the public GitHub API is allowed. - To permit one or more GitHub Enterprise hosts, set the `CAMUNDA_CONNECTOR_GITHUB_APP_ALLOWED_BASE_URLS` environment variable on the connector runtime to a comma-separated list of allowed base URLs: ```bash CAMUNDA_CONNECTOR_GITHUB_APP_ALLOWED_BASE_URLS=https://github.example.com/api/v3,https://github-other.example.com/api/v3 ``` - Setting this variable replaces the default allow-list entirely. The public GitHub API is not implicitly included; add `https://api.github.com` explicitly if you need both. - A call that resolves to a base URL that isn't allow-listed fails with an error rather than falling back silently, so a misconfigured or unexpected base URL is caught rather than masked. ## Declare intrinsic function calls in the process model {#declare-calls} The connector runtime runs an intrinsic function only if the deployed BPMN process declares that exact call in the connector element's own input mappings. Starting with Connectors 8.10.0, the runtime refuses intrinsic function calls that arrive through process variables or job variables. The runtime reads the declared calls from the deployed BPMN model and applies the following rules: - The function name and the field path must both match. For example, a `createLink` call declared on the `body` field doesn't allow a `createLink` call on `body.attachment`, and doesn't allow a `getText` call on `body`. - The call must be written as a literal in the input mapping's FEEL expression, with `camunda.function.type` set to a string literal such as `"getText"`. The runtime recognizes calls inside context literals, list literals, `if`/`then`/`else` expressions, and `for`/`in`/`return` expressions. - A call the runtime can't read from the model isn't declared. Examples include a function name assembled with `prefix + "getText"`, a call returned by a FEEL function, and a call stored in a process variable. - A later input mapping that writes to the same field, or to a field above it, replaces the earlier call. The earlier declaration no longer counts. - Custom functions registered through an `IntrinsicFunctionProvider` follow the same rules. For example, the following input mapping with the target `body` declares a `getText` call at the field path `body.user`: ```feel = { "user": { "camunda.function.type": "getText", "params": [ document ] } } ``` The runtime reads the deployed model only when a job's variables contain an intrinsic function call, so jobs without calls aren't affected. After you change an input mapping, redeploy the process so the runtime reads the updated declarations. Out-of-the-box element templates that use intrinsic functions already declare their calls this way. Values that come from outside the process model never run intrinsic functions. This includes connector results, FEEL evaluation results, and inbound connector payloads. To configure the policy or the lookup cache of the connector runtime, see [intrinsic function allow-list](/self-managed/components/connectors/connectors-configuration.md#intrinsic-function-allow-list). ### Write safe intrinsic function declarations Follow these guidelines when you write an input mapping or an element template that uses intrinsic functions: - Declare each call in its own input, and don't wrap it in an `if`/`then`/`else` expression. When a call sits in one branch of a conditional, the runtime allows it without checking what the other branch produces. To vary a value by mode, use an element template with one hidden property per case, each with a `condition`. - Pass only values you trust as function parameters. The runtime checks the function name and the field path, not the parameters. - Review any use of `createLink` before you add it to a template. The function creates a temporary link to a document, so its parameters decide which document becomes reachable. Don't use it inside a conditional, and don't build its document parameter from process variables that external systems can set. ### Troubleshoot intrinsic function errors #### Intrinsic function is not allowed by the runtime policy The job fails with the following error: ```text Intrinsic function '' at '' is not allowed by the runtime intrinsic-function policy. ``` This happens when the deployed BPMN model doesn't declare the function `` at the field path ``. Common causes: - The call arrives in a process variable instead of being written in the input mapping. - The call is declared at a different field path than the one the runtime reports. - A later input mapping overwrites the field that declares the call. - The deployed process is older than the input mapping you edited. - The [allow-list mode](/self-managed/components/connectors/connectors-configuration.md#intrinsic-function-allow-list) is set to `DISABLED`, which refuses every call. To fix it, write the call directly in the input mapping of the field named in the error, then redeploy the process. If the element uses an element template, update the element to the latest template version. If the runtime can't read the deployed model, the job also fails and Zeebe retries it. The intrinsic function allow-list always fails closed, so no call runs until the runtime can read the model. #### Connector result contains a forbidden literal The job fails with the following error: ```text The connector result contains a forbidden literal 'camunda.function.type'. ``` This happens when the value mapped to a result variable or result expression contains an object with the `camunda.function.type` key, for example because an external system returned it in a response body. The runtime refuses the value so that a response can't introduce an intrinsic function call into your process. To fix it, use a result expression that maps only the fields you need, so the `camunda.function.type` key isn't copied into the process variables. ## Create a custom function In **Self-Managed** deployments, you can create custom intrinsic functions by implementing the `IntrinsicFunctionProvider` interface included with the [Connector SDK](/components/connectors/custom-built-connectors/connector-sdk.md), and registering it in the connector runtime. - The custom function is implemented as a Java method inside the `IntrinsicFunctionProvider` implementation class. - The method must be annotated with the `@IntrinsicFunction` annotation. The method arguments are transformed into a list of intrinsic function parameters. ```java public class MyFunctionProvider implements IntrinsicFunctionProvider { @IntrinsicFunction(name = "concat") public String execute(String s1, String s2) { return s1 + s2; } } ``` --- ## Connector types Connectors are categorized by the direction data flows into or out of Camunda 8. ## Outbound connectors Outbound connectors allow workflows to trigger external systems or services, making it possible to integrate workflows with other parts of a business process or system architecture. The Java code to connect to the external system is executed when the workflow reaches the service task. ![Outbound connectors](img/outbound-connectors.png) Use outbound connectors if something needs to happen in the third-party system if a process reaches a service task. For example, calling a REST endpoint or publishing a message to Slack. Outbound connectors are also what an [AI agent](/reference/glossary.md#ai-agent) calls as its tools: the process still defines which connectors an agent is allowed to use, the same way it defines which service task runs next. ## Inbound connectors Inbound connectors enable workflows to receive data or messages from external systems or services, making it possible to integrate workflows into a wider business process or system architecture. Inbound connectors can be used to create a new process instance, or to send a message to a running process instance. The Java code of the inbound connector has a lifecycle suitable for long-running operations, such as listening for messages on a queue or waiting for a webhook to be called. The connector code is **activated** as soon as the connector Runtime detects an element in a process definition that references an inbound connector. It gets `deactivated` in case of an updated or deleted process definition. Inbound connector instances are linked to process definitions and not to specific process instances. If a process definition contains an element referencing an inbound connector, the connector code will be first executed when the process definition is deployed and the deployment has been detected by the connector Runtime. The connector object created during deployment will be kept active as long as the process is deployed, and it is reused to serve all instances of the process. When the process definition is deleted or replaced with a newer version, the connector object will be removed or updated as well. :::note The connector Runtime currently fetches only the **latest version** of each process definition. Deploying a new process definition version deactivates inbound connectors from older versions. [Migrate older versions](../../concepts/process-instance-migration) if the new inbound connector configuration won't correlate messages for existing instances. ::: ![Inbound connectors](img/inbound-connectors.png) Use inbound connectors if something needs to happen within the workflow engine because of an external event in the third-party system. For example, because a Slack message was published, or a REST endpoint is called. There are three types of inbound connectors: 1. **Webhook connector**: An inbound connector which creates a webhook for a Camunda workflow. 2. **Subscription connector**: An inbound connector that subscribes to a message queue. 3. **Polling connector**: An inbound connector that periodically polls an external system or service for new data using HTTP polling. ## Protocol connectors Protocol connectors can serve as either inbound or outbound connectors, supporting a variety of technical protocols. These connectors are highly generic, designed to provide a flexible and customizable means of integrating with external systems and services. Protocol connectors can be customized to meet the needs of specific use cases using configurable [Connector Templates](manage-connector-templates.md), with no additional coding or deployment required. Examples of protocol connectors include HTTP REST, GraphQL, as well as message queue connectors. ## Next steps Review the current list of [available connectors](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md). --- ## Build a custom connector Can't find the connector you need? Build your own custom connector using connector templates or the SDK. Camunda supports two main ways to build custom connectors: - [Connector templates](/components/connectors/custom-built-connectors/connector-templates.md) for creating custom connectors based on existing built-in connectors, such as REST or gRPC. - [Connector SDK](/components/connectors/custom-built-connectors/connector-sdk.md) for building your own Java-based connectors from scratch. Choose the approach that best fits your requirements and technical expertise. This guide provides an overview of both methods to help you get started. ## Understand the connector architecture A connector consists of the Java backend and an element template that defines the user interface used in [Camunda Hub](/components/hub/workspace/modeler/index.md) and [Desktop Modeler](/components/modeler/desktop-modeler/index.md). - The Java code defines the connector’s functionality and how it interacts with an external system. For example, the [Connector function](/components/connectors/custom-built-connectors/connector-sdk.md#outbound-connector-runtime-logic) used for outbound connectors. - The user interface allows you to configure and use the connector in Camunda Hub and Desktop Modeler. This is defined in a [Connector template](../manage-connector-templates.md) that controls how the BPMN element is shown in the modeling interface and which configuration options are available for the connector. This separation enables a layered approach to building connectors. You can customize the user interface and configuration options in Hub or Desktop Modeler using connector templates, without modifying the underlying Java code. ## Choose your approach Decide which of the following scenarios best describes your use case. ### Protocol-based API call You need to call an API that uses a common protocol such as REST, SOAP, or GraphQL. There is no existing connector on Camunda marketplace that meets your needs. Camunda recommends using a custom connector template based on the [REST connector](/components/connectors/protocol/rest.md), [SOAP connector](/components/connectors/protocol/soap.md), or [GraphQL connector](/components/connectors/protocol/graphql.md). This approach allows you to leverage the existing functionality while customizing it to fit your API requirements. See [Create a custom REST connector](create-connector-from-rest.md) for more information. ### Complex integration logic You need to implement integration logic that goes beyond issuing an API call. Build a custom connector using the [Connector SDK](connector-sdk.md). This approach gives you full control over the connector's behavior. ## Connector templates Connectors use [connector templates](/components/connectors/custom-built-connectors/connector-templates.md) to customize how a BPMN element is shown, and how it can be configured by process developers. Connector templates are a specific type of [element template](/components/modeler/element-templates/about-templates.md). Connector templates Create a connector task and start using connector secrets. Generate a connector template Automatically generate a custom connector template in Camunda Hub. Manage connector templates Create and manage connector templates in a workspace. --- ## Connector SDK The **Connector SDK** allows you to [develop custom connectors](#creating-a-custom-connector) using Java code. You can focus on the logic of the connector, test it locally, and reuse its runtime logic in multiple [runtime environments](#runtime-environments). The SDK achieves this by abstracting from Camunda 8 internals that usually come with [job workers](/components/concepts/job-workers.md). You can find the latest **Connector SDK** version source code [here](https://github.com/camunda/connectors). The SDK provides APIs for common connector operations, such as: - Fetching and deserializing input data - Validating input data - Replacing secrets in input data Additionally, the SDK allows for convenient [testing](#testing) of your connector behavior and [executing it in the environments](#runtime-environments) that suit your use cases best. ## Creating a custom connector Using the Connector SDK, you can create environment-agnostic and reusable connector runtime behavior. This section outlines how to set up a connector project, test it, and run it locally. ### Setup When developing a new **Connector**, we recommend using one of our custom connector templates for custom [outbound](https://github.com/camunda/connector-template-outbound) and [inbound](https://github.com/camunda/connector-template-inbound) connectors. These templates are [Maven](https://maven.apache.org/)-based Java projects, and can be used in various ways such as: - **Create your own GitHub repository**: Click **Use this template** and follow the prompted steps. You can manage code changes in your new repository afterward. - **Experiment locally**: Check out the source code to your local machine using [Git](https://git-scm.com/). You won't be able to check in code changes to the repository due to restricted write access. - **Fetch the source**: Download the source code as a ZIP archive using **Code > Download ZIP**. You can adjust and manage the code the way you like afterward using your chosen source code management tools. To manually set up your connector project, include the following dependency to use the SDK. Ensure you adhere to the project outline detailed in the next section. ```xml io.camunda.connector connector-core ${version.connectors} ``` ```yml implementation "io.camunda.connector:connector-core:${version.connectors}" ``` ### Outbound connector project outline There are multiple parts of a connector that enable it for reuse, modeling, and the runtime behavior. For example, the following parts make up an outbound connector: ``` my-connector ├── element-templates/ │ └── connector.json (1) ├── src/main │ ├── java/io/camunda/example (2) │ │ ├── MyConnector.java (3) │ └── resources/META-INF/services │ └── io.camunda.connector.api.outbound.OutboundConnectorProvider (4) └── pom.xml (5) ``` For the modeling building blocks, the connector provides [Connector templates](/components/connectors/custom-built-connectors/connector-templates.md) with **(1)**. You provide the runtime logic as Java source code under a package like **(2)** including an implementation of your connector, in this case `MyConnector` with **(3)**. For a detectable connector, you are required to expose your function class name in the [`OutboundConnectorProvider` SPI implementation](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/ServiceLoader.html) with **(4)**. A configuration file like **(5)** manages the project setup, including dependencies. In this example, we include a Maven project's `POM` file. Other build tools like [Gradle](https://gradle.org/) can also be used. ### Outbound connector element template [Connector templates](/components/connectors/custom-built-connectors/connector-templates.md) act as the modeling interface for the users of your connector. The template can be written manually but we recommend using the [`element-template-generator`](https://github.com/camunda/connectors/tree/main/element-template-generator) which generates the element template for your connector as part of your build process. Element templates define the data and configuration bindings to your connector on the BPMN element via properties. Properties have different types that define their visual representation. They can also be hidden in the modeling UI but still applied to the BPMN: ```json { ... "properties" : [ { "type": "Hidden", "value": "io.camunda:template:1", "binding": { "type": "zeebe:taskDefinition", "property": "type" } } } ``` This type definition `io.camunda:template:1` is the connection configuring which version of your connector runtime behavior to use. In technical terms, this defines the **Type** of jobs created for tasks in your process model that use this template. Consult the [job worker](/components/concepts/job-workers.md) guide to learn more. Besides the type binding, connector templates also define the input variables of your connector as `zeebe:input` objects. For example, you can create the input variable `message` of your connector in the element template as follows: ```json { "label": "Message", "type": "Text", "feel": "optional", "binding": { "type": "zeebe:input", "name": "message" } } ``` You can also define nested data structures to reflect domain objects that group attributes. For example, you can create the domain object `authentication` that contains the properties `user` and `token` as follows: ```json { "label": "Username", "description": "The username for authentication.", "type": "String", "binding": { "type": "zeebe:input", "name": "authentication.user" } }, { "label": "Token", "description": "The token for authentication.", "type": "String", "binding": { "type": "zeebe:input", "name": "authentication.token" } } ``` You can deserialize these authentication properties into a domain object using the SDK. Visit the [input data](#outbound-connector-input-data) section for further details. Connectors that offer any kind of result from their invocation should allow users to configure how to map the result into their processes. Therefore, connector templates can reuse the two recommended objects, **Result Variable** and **Result Expression**: ```json { "label": "Result Variable", "description": "Name of variable to store the response in", "type": "String", "binding": { "type": "zeebe:taskHeader", "key": "resultVariable" } }, { "label": "Result Expression", "description": "Expression to map the response into process variables", "type": "Text", "feel": "required", "binding": { "type": "zeebe:taskHeader", "key": "resultExpression" } } ``` These objects create custom headers for the jobs created for the tasks that use this template. The connector runtime environments pick up those two custom headers and translate them into process variables accordingly. You can find an example of how to use this in the [out-of-the-box REST connector](/components/connectors/protocol/rest.md#response). All connectors are recommended to offer exception handling to allow users to configure how to map results and technical errors into BPMN errors. To provide this, connector templates can provide an **Error Expression**: ```json { "label": "Error Expression", "description": "Expression to define BPMN Errors to throw", "group": "errors", "type": "Text", "feel": "required", "binding": { "type": "zeebe:taskHeader", "key": "errorExpression" } } ``` This object creates a custom header for the jobs created for the tasks that use this template. The connector runtime environments pick up this custom header and translate it into BPMN errors accordingly. You can observe an example of how to use this in the [BPMN errors in connectors guide](/components/connectors/use-connectors/index.md#bpmn-errors-and-failing-jobs). ### Outbound connector runtime logic The connector implements the `OutboundConnectorProvider` interface of the SDK. This allows the Connector runtime to discover and invoke your Connector. It introspects the `@OutboundConnector` annotation and uses the `type` to register the Connector as a job worker to fetch jobs. A Connector implementation can now declare ```java package io.camunda.example; @OutboundConnector( // (1) name = "MyConnector", type = "io.camunda:my-connector:1" ) @ElementTemplate( // (2) id = "my-connector-template:1", name = "My Connector Template" ) public class MyConnector implements OutboundConnectorProvider { @Operation(id = "operation1") // (3) public String operation1(@Variable(name = "input") String input) { // (4) System.out.println("Received input: " + input); return "Test operation executed successfully!"; } public record MyInput(@NotNull Integer a, @NotNull int b) {} public record MyOutput(int result) {} @Operation(id = "operation2") public MyOutput operation2(@Variable MyInput input) { // (5) return new MyOutput(input.a() + input.b()); } @Operation(id = "operation3") public String operation3(@Header(name = "name") String name) { // (6) System.out.println("Received name: " + name); return name; } @Operation(id = "operation4") public String operation4() { throw new ConnectorException(("MY_ERROR"), "This is a test exception"); // (7) } } ``` A single `@OutboundConnector` annotated connector (**(1)**) can declare one or multiple operations. The element template generation can be configured using the `@ElementTemplate` annotation (**(2)**) Every declared operation (**(3)**) can accept one or multiple inputs as parameters. Describe each operation with the `name`, `description`, and `keywords` attributes of `@Operation`. The element template generator turns them into the template's [`steps` and `presets`](/components/modeler/element-templates/template-metadata.md#predefined-configurations-steps-and-presets), so users can find the operation by searching for the action it performs and select it directly when they apply the template. Once a connector declares more than one operation, every operation must declare at least one keyword, or template generation fails. Using the `@Variable` annotation, a primitive type has to specify the variable name for example `input` as shown in (**(4)**). Binding to a complex type will use the property names (`a`, `b`) of the type for variable mapping (**(5)**). Types can use [Jakarta Validation](https://beanvalidation.org/) annotations. Validation will be applied during binding. It's also possible to bind job headers using the `@Header` annotation (**(6)**) but this is only recommended for static config data defined at modeling time. If the connector handles exceptional cases, it can use any exception to express technical errors. If a technical error should be associated with a specific error code, the connector can throw a `ConnectorException` and define a `code` as shown in **(7)**. We recommend documenting the list of error codes as part of the connector's API. Users can build on those codes by creating [BPMN errors](/components/connectors/use-connectors/index.md#bpmn-errors-and-failing-jobs) in their connector configurations. ### Inbound connector project outline There are multiple parts of a connector that enable it for reuse, as a reusable building block, for modeling, and for the runtime behavior. For example, the following parts make up an inbound connector: ``` my-connector ├── element-templates │ └── inbound-template-connector.json (1) ├── pom.xml (6) ├── src │ ├── main │ │ ├── java/io/camunda/connector │ │ │ └── inbound │ │ │ ├── MyConnectorExecutable.java (2) │ │ │ ├── MyConnectorEvent.java (3) │ │ │ ├── MyConnectorProperties.java (4) │ │ │ └── subscription │ │ │ ├── MockSubscription.java │ │ │ └── MockSubscriptionEvent.java │ │ └── resources/META-INF/services │ │ └── io.camunda.connector.api.inbound.InboundConnectorExecutable (5) ``` For the modeling building blocks, the connector provides [Connector element templates](./connector-templates.md) with **(1)**. You provide the runtime logic as Java source code. Typically, a connector runtime logic consists of exactly one implementation of a `InboundConnectorExecutable` with **(2)** and at least one input object like **(3)**, and connector's properties like **(4)**. For a detectable connector function, you are required to expose your function class name in the [`InboundConnectorExecutable` SPI implementation](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/ServiceLoader.html) with **(5)**. A configuration file like **(6)** manages the project setup, including dependencies. In this example, we include a Maven project's `POM` file. Other build tools like [Gradle](https://gradle.org/) can also be used. ### Inbound connector element template To create reusable building blocks for modeling, you are required to provide a domain-specific [Connector element template](./connector-templates.md). A connector template defines the binding to your connector runtime behavior via the following object: ```json { "type": "Hidden", "value": "io.camunda:mytestinbound:1", "binding": { "type": "zeebe:property", "name": "inbound.type" } } ``` This type definition `io.camunda:mytestinbound:1` is the connection configuring which version of your connector runtime behavior to use. In technical terms, this defines the **Type** of jobs created for tasks in your process model that use this template. Consult the [job worker](../../concepts/job-workers.md) guide to learn more. Besides the type binding, connector templates also define the properties of your connector as `zeebe:property` objects. For example, you can create the input variable `sender` of your connector in the element template as follows: ```json { "type": "String", "label": "Sender", "description": "Message sender name", "value": "Alice", "binding": { "type": "zeebe:property", "name": "sender" } } ``` ### Inbound connector runtime logic To create a reusable runtime behavior for your connector, you are required to implement and expose an implementation of the `InboundConnectorExecutable` interface of the SDK. The connector runtime environments will call this function; it handles input data, executes the connector's business logic. Exception handling is optional since the connector runtime environments handle this as a fallback. The `InboundConnectorExecutable` interface consists of two methods: `activate` and `deactivate`. A minimal recommended outline of a connector function implementation looks as follows: ```java package io.camunda.connector.inbound; @InboundConnector(name = "MYINBOUNDCONNECTOR", type = "io.camunda:mytestinbound:1") public class MyConnectorExecutable implements InboundConnectorExecutable { private MockSubscription subscription; private InboundConnectorContext connectorContext; @Override public void activate(InboundConnectorContext connectorContext) { MyConnectorProperties props = connectorContext.bindProperties(MyConnectorProperties.class); this.connectorContext = connectorContext; subscription = new MockSubscription( props.getSender(), props.getMessagesPerMinute(), this::onEvent); } @Override public void deactivate() { subscription.stop(); } private void onEvent(MockSubscriptionEvent rawEvent) { MyConnectorEvent connectorEvent = new MyConnectorEvent(rawEvent); var result = connectorContext.correlateWithResult(connectorEvent); handleResult(result); } private void handleResult(CorrelationResult result) { switch (result) { case Success ignored -> LOG.debug("Message correlated successfully"); case Failure failure -> { switch (failure.handlingStrategy()) { case ForwardErrorToUpstream ignored -> { LOG.error("Correlation failed, reason: {}", failure.message()); // forward error to upstream } case Ignore ignored -> { LOG.debug("Correlation failed but no action required, reason: {}", failure.message()); // ignore } } } } } } ``` The `activate` method is a trigger function to start listening to inbound events. The implementation of this method has to be asynchronous. Once activated, the inbound connector execution is considered active and running. From this point, it should use the respective methods of `InboundConnectorContext` to communicate with the connector runtime (e.g. to correlate the inbound event or signal the interrupt). The `deactivate` method is just a graceful shutdown hook for inbound connectors. The implementation must release all resources used by the subscription. The `onEvent` method is a callback function that is triggered by the subscription whenever a new event is received. This method is responsible for passing the event to the connector runtime environment for correlation. The `handleResult` method is a helper method to handle the result of the correlation. The `CorrelationResult` object contains the result of the correlation and the handling strategy. The handling strategy defines how the connector implementation should handle the result. Depending on the strategy, the connector implementation should either forward the error to the upstream system or ignore it. The handling strategy is derived by the connector runtime based on user configuration. #### Validation Validating input data is a common task in connectors. The SDK provides an out-of-the-box solution for input validation. A default implementation of the SDK's core validation API is provided in a separate, optional artifact `connector-validation`. If you want to use validation in your Connector, add the following dependency to your project: ```xml io.camunda.connector connector-validation ${version.connectors} ``` ```yml implementation "io.camunda.connector:connector-validation:${version.connectors}" ``` Validation is performed automatically when binding variables to parameters. This instructs the context to prepare a validator that is provided by an implementation of the `ValidationProvider` interface. The `connector-validation` artifact brings along such an implementation. It uses the [Jakarta Bean Validation API](https://beanvalidation.org/) together with [Hibernate Validator](https://hibernate.org/validator/). For your input object to be validated, you need to annotate the input's attributes to define your requirements: ```java package io.camunda.connector; public class MyConnectorRequest { @NotEmpty private String message; @NotNull @Valid private Authentication authentication; } ``` The Jakarta Bean Validation API comes with a long list of [supported constraints](https://jakarta.ee/specifications/bean-validation/2.0/bean-validation_2.0.html#builtinconstraints). It also allows to [validate entire object graphs](https://jakarta.ee/specifications/bean-validation/2.0/bean-validation_2.0.html#constraintdeclarationvalidationprocess-validationroutine-graphvalidation) using the `@Valid` annotation. Thus, the `authentication` object will also be validated. ```java package io.camunda.connector; public class Authentication { @NotEmpty private String user; @NotEmpty @Pattern(regexp = "^xobx") private String token; } ``` Using this approach, you can validate your whole input data structure with one initial call from the central connector function. #### Secrets Connectors that require confidential information to connect to external systems need to be able to manage those securely. As described in the [guide for creating secrets](/components/saas/clusters/manage-secrets.md), secrets can be controlled in a secure location and referenced in a connector's properties using a placeholder pattern `{{secrets.*}}`. To make this mechanism as robust as possible, secret handling comes with the connector SDK out of the box. That way, all connectors can use the same standard way of handling secrets in input data. `{{secrets.*}}` is the legacy syntax, resolved by the connector runtime. The recommended `camunda.secrets.` syntax is resolved by the Orchestration Cluster before the job reaches the connector, so your connector receives the value already in place. See [Migrate to `camunda.secrets.`](/components/connectors/use-connectors/migrate-secrets.md) for how the two syntaxes differ and how to migrate. The SDK allows replacing secrets in input data as late as possible to avoid passing them around in the environments that handle connector invocation. We do not pass secrets into the Connector function in clear text but only as placeholders that you can replace from within the connector function. Secrets are replaced automatically in the connector input when you use the variable binding or properties access methods of the `InboundConnectorContext`. You will always receive inputs with secrets replaced. The Runtime automatically replaces secrets in String fields or in container types. Using the placeholder pattern `{{secrets.*}}` in a String field will replace the placeholder with the secret value. Using the placeholder pattern in a container type will replace the placeholder in all String fields of the container type. ## Runtime environments To integrate connectors with your business use case, you need a runtime environment to act as the intermediary between your business and connectors space. The Connector SDK enables you to write environment-agnostic runtime behavior for connectors. This makes the connector logic reusable in different setups without modifying your connector code. To invoke this logic, you need a runtime environment that knows the connector function and how to call it. In Camunda 8 SaaS, every cluster runs a component that knows the [available out-of-the-box connectors](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md) and how to invoke them. This component is the runtime environment specific to Camunda's SaaS use case. Regarding Self-Managed environments, you are responsible for providing the runtime environment that can invoke the connectors. There are several runtime options provided by Camunda: - [Spring Boot Starter runtime](#spring-boot-starter-runtime) - [Docker runtime image](#docker-runtime-image) ### Spring Boot Starter runtime This option is applicable for Spring Boot users. All you need to do is to include respective starter: ```xml io.camunda.connector spring-boot-starter-camunda-connectors ${version.connectors} org.myorg connector-my-awesome ${version.connector-my-awesome} ``` Upon starting your Spring Boot application, you will have a job worker connected to Zeebe, waiting to receive jobs for your connectors. ### Docker runtime image This option is applicable for those users who prefer Docker. Make sure to have an orchestration cluster running. A good start is the [Camunda Distributions](https://github.com/camunda/camunda-distributions/tree/main/docker-compose) docker compose repository. Clone the repository. Switch into the version folder you would like to run and start the cluster: ```shell docker compose -f docker-compose.yaml up ``` The latest Connectors Docker images can be found at the [Docker Hub](https://hub.docker.com/r/camunda/connectors). You can start the runtime including your Connector jar by running: ```shell docker run --rm -i \ -v $PWD/your-connector.jar:/opt/app/connector.jar \ # Add a connector jar to the classpath --network=camunda \ # Optional: Attach to the orchestration cluster Docker network -e CAMUNDA_CLIENT_MODE=self-managed \ # Connect to a Self-Managed cluster -e CAMUNDA_CLIENT_GRPCADDRESS=http://orchestration:26500 \ # Specify cluster gRPC API address -e CAMUNDA_CLIENT_RESTADDRESS=http://orchestration:8080 \ # Specify cluster REST API address camunda/connectors:X.Y.Z # Connector docker image version ``` If you would like to disable inbound connectors, you can do so by setting `CAMUNDA_CONNECTOR_POLLING_ENABLED=false`. These environment variables map to the same Camunda client properties used across all Camunda 8 components. For the full list of properties, their env var equivalents, and authentication method examples, see [Connectors configuration](/self-managed/components/connectors/connectors-configuration.md). --- ## Connector templates Connectors are available [out-of-the-box (OOTB)](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md) and come with [connector templates](/components/connectors/manage-connector-templates.md) that customize how a BPMN element is shown and how it can be configured by process developers. Connector templates are a specific type of [element template](/components/modeler/element-templates/about-templates.md), which can also be used when creating custom connectors via the [Connector SDK](/components/connectors/custom-built-connectors/connector-sdk.md). Additionally, the [Camunda Marketplace](/components/hub/workspace/modeler/modeling/camunda-marketplace.md) provides connectors by Camunda partners and community contributors. Before developing one, you'll need to decide what you would like to achieve with your connector. Currently, the options are: - Starting a BPMN process, triggered by external service. Use [inbound start event connector template](#inbound-start-event-connector-templates). - Continue process with an intermediate catch event emitted by external service call. Use [inbound intermediate catch event connector templates](#inbound-intermediate-catch-event-connector-templates). - Trigger an external service. Use [outbound connector template](#outbound-connector-templates). :::note Do not confuse **Connector templates** with the **[Connector template](https://github.com/camunda/connector-template-outbound)**, which is used to supply boilerplate code and configuration when developing a new custom connector. ::: ## Inbound start event connector templates You can, for example, allow the user to model and configure the following **HTTP Webhook connector** by providing a simple JSON configuration: ![Webhook Inbound connector Example](./img/custom-connector-template-inbound-start.png) ```json { "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", "name": "Webhook connector", "id": "io.camunda.connectors.webhook.WebhookConnector.v1", "version": 1, "description": "Configure webhook to receive callbacks", "documentationRef": "https://docs.camunda.io/docs/components/connectors/out-of-the-box-connectors/http-webhook/", "category": { "id": "connectors", "name": "Connectors" }, "appliesTo": ["bpmn:StartEvent"], "elementType": { "value": "bpmn:StartEvent" }, "groups": [ { "id": "endpoint", "label": "Webhook Configuration" }, { "id": "activation", "label": "Activation" }, { "id": "variable-mapping", "label": "Variable Mapping" } ], "properties": [ { "type": "Hidden", "value": "io.camunda:webhook:1", "binding": { "type": "zeebe:property", "name": "inbound.type" } }, { "type": "Hidden", "value": "ConfigurableInboundWebhook", "binding": { "type": "zeebe:property", "name": "inbound.subtype" } }, { "label": "Webhook ID", "type": "String", "group": "endpoint", "binding": { "type": "zeebe:property", "name": "inbound.context" }, "description": "The webhook ID is a part of the URL" }, { "id": "shouldValidateHmac", "label": "HMAC authentication", "group": "endpoint", "description": "Choose whether HMAC verification is enabled. See documentation and example that explains how to use HMAC-related fields", "value": "disabled", "type": "Dropdown", "choices": [ { "name": "Enabled", "value": "enabled" }, { "name": "Disabled", "value": "disabled" } ], "binding": { "type": "zeebe:property", "name": "inbound.shouldValidateHmac" } }, { "label": "HMAC secret key", "description": "Shared secret key", "type": "String", "group": "endpoint", "optional": true, "binding": { "type": "zeebe:property", "name": "inbound.hmacSecret" }, "condition": { "property": "shouldValidateHmac", "equals": "enabled" } }, { "label": "HMAC header", "description": "Name of header attribute that will contain the HMAC value", "type": "String", "group": "endpoint", "optional": true, "binding": { "type": "zeebe:property", "name": "inbound.hmacHeader" }, "condition": { "property": "shouldValidateHmac", "equals": "enabled" } }, { "label": "HMAC algorithm", "group": "endpoint", "description": "Choose HMAC algorithm", "value": "sha_256", "type": "Dropdown", "choices": [ { "name": "SHA-1", "value": "sha_1" }, { "name": "SHA-256", "value": "sha_256" }, { "name": "SHA-512", "value": "sha_512" } ], "binding": { "type": "zeebe:property", "name": "inbound.hmacAlgorithm" }, "condition": { "property": "shouldValidateHmac", "equals": "enabled" } }, { "label": "Condition", "type": "String", "group": "activation", "feel": "required", "optional": true, "binding": { "type": "zeebe:property", "name": "inbound.activationCondition" }, "description": "Condition under which the connector triggers. Leave empty to catch all events. See documentation" }, { "label": "Variables", "type": "String", "group": "variable-mapping", "feel": "required", "binding": { "type": "zeebe:property", "name": "inbound.variableMapping" }, "description": "Map variables from the webhook payload (request) to start the process with. When blank, entire payload is copied over. See documentation" } ], "icon": { "contents": "data:image/svg+xml,%3Csvg id='icon' xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 32 32'%3E%3Cdefs%3E%3Cstyle%3E .cls-1 %7B fill: none; %7D %3C/style%3E%3C/defs%3E%3Cpath d='M24,26a3,3,0,1,0-2.8164-4H13v1a5,5,0,1,1-5-5V16a7,7,0,1,0,6.9287,8h6.2549A2.9914,2.9914,0,0,0,24,26Z'/%3E%3Cpath d='M24,16a7.024,7.024,0,0,0-2.57.4873l-3.1656-5.5395a3.0469,3.0469,0,1,0-1.7326.9985l4.1189,7.2085.8686-.4976a5.0006,5.0006,0,1,1-1.851,6.8418L17.937,26.501A7.0005,7.0005,0,1,0,24,16Z'/%3E%3Cpath d='M8.532,20.0537a3.03,3.03,0,1,0,1.7326.9985C11.74,18.47,13.86,14.7607,13.89,14.708l.4976-.8682-.8677-.497a5,5,0,1,1,6.812-1.8438l1.7315,1.002a7.0008,7.0008,0,1,0-10.3462,2.0356c-.457.7427-1.1021,1.8716-2.0737,3.5728Z'/%3E%3Crect id='_Transparent_Rectangle_' data-name='<Transparent Rectangle>' class='cls-1' width='32' height='32'/%3E%3C/svg%3E" } } ``` ## Inbound intermediate catch event connector templates You can, for example, allow the user to model and configure the following **HTTP Webhook connector** by providing a simple JSON configuration: ![Webhook Inbound intermediate connector Example](./img/custom-connector-template-inbound-intermediate.png) ```json { "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", "name": "Webhook connector", "id": "io.camunda.connectors.webhook.WebhookConnectorIntermediate.v1", "version": 1, "description": "Configure webhook to receive callbacks", "documentationRef": "https://docs.camunda.io/docs/components/connectors/out-of-the-box-connectors/http-webhook/", "category": { "id": "connectors", "name": "Connectors" }, "appliesTo": ["bpmn:IntermediateCatchEvent", "bpmn:IntermediateThrowEvent"], "elementType": { "value": "bpmn:IntermediateCatchEvent", "eventDefinition": "bpmn:MessageEventDefinition" }, "groups": [ { "id": "endpoint", "label": "Webhook Configuration" }, { "id": "activation", "label": "Activation" }, { "id": "variable-mapping", "label": "Variable Mapping" } ], "properties": [ { "type": "Hidden", "value": "io.camunda:webhook:1", "binding": { "type": "zeebe:property", "name": "inbound.type" } }, { "type": "Hidden", "generatedValue": { "type": "uuid" }, "binding": { "type": "bpmn:Message#property", "name": "name" } }, { "type": "Hidden", "value": "ConfigurableInboundWebhook", "binding": { "type": "zeebe:property", "name": "inbound.subtype" } }, { "label": "Webhook ID", "type": "String", "group": "endpoint", "binding": { "type": "zeebe:property", "name": "inbound.context" }, "description": "The webhook ID is a part of the URL" }, { "id": "shouldValidateHmac", "label": "HMAC authentication", "group": "endpoint", "description": "Choose whether HMAC verification is enabled. See documentation and example that explains how to use HMAC-related fields", "value": "disabled", "type": "Dropdown", "choices": [ { "name": "Enabled", "value": "enabled" }, { "name": "Disabled", "value": "disabled" } ], "binding": { "type": "zeebe:property", "name": "inbound.shouldValidateHmac" } }, { "label": "HMAC secret key", "description": "Shared secret key", "type": "String", "group": "endpoint", "optional": true, "binding": { "type": "zeebe:property", "name": "inbound.hmacSecret" }, "condition": { "property": "shouldValidateHmac", "equals": "enabled" } }, { "label": "HMAC header", "description": "Name of header attribute that will contain the HMAC value", "type": "String", "group": "endpoint", "optional": true, "binding": { "type": "zeebe:property", "name": "inbound.hmacHeader" }, "condition": { "property": "shouldValidateHmac", "equals": "enabled" } }, { "label": "HMAC algorithm", "group": "endpoint", "description": "Choose HMAC algorithm", "value": "sha_256", "type": "Dropdown", "choices": [ { "name": "SHA-1", "value": "sha_1" }, { "name": "SHA-256", "value": "sha_256" }, { "name": "SHA-512", "value": "sha_512" } ], "binding": { "type": "zeebe:property", "name": "inbound.hmacAlgorithm" }, "condition": { "property": "shouldValidateHmac", "equals": "enabled" } }, { "label": "Correlation key (process)", "type": "String", "group": "activation", "feel": "required", "description": "Sets up the correlation key from process variables", "binding": { "type": "bpmn:Message#zeebe:subscription#property", "name": "correlationKey" }, "constraints": { "notEmpty": true } }, { "label": "Correlation key (payload)", "type": "String", "group": "activation", "feel": "required", "binding": { "type": "zeebe:property", "name": "correlationKeyExpression" }, "description": "Extracts the correlation key from the incoming message payload", "constraints": { "notEmpty": true } }, { "label": "Condition", "type": "String", "group": "activation", "feel": "required", "optional": true, "binding": { "type": "zeebe:property", "name": "inbound.activationCondition" }, "description": "Condition under which the connector triggers. Leave empty to catch all events. See documentation" }, { "label": "Variables", "type": "String", "group": "variable-mapping", "feel": "required", "binding": { "type": "zeebe:property", "name": "inbound.variableMapping" }, "description": "Map variables from the webhook payload (request) to start the process with. When blank, entire payload is copied over. See documentation" } ], "icon": { "contents": "data:image/svg+xml,%3Csvg id='icon' xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 32 32'%3E%3Cdefs%3E%3Cstyle%3E .cls-1 %7B fill: none; %7D %3C/style%3E%3C/defs%3E%3Cpath d='M24,26a3,3,0,1,0-2.8164-4H13v1a5,5,0,1,1-5-5V16a7,7,0,1,0,6.9287,8h6.2549A2.9914,2.9914,0,0,0,24,26Z'/%3E%3Cpath d='M24,16a7.024,7.024,0,0,0-2.57.4873l-3.1656-5.5395a3.0469,3.0469,0,1,0-1.7326.9985l4.1189,7.2085.8686-.4976a5.0006,5.0006,0,1,1-1.851,6.8418L17.937,26.501A7.0005,7.0005,0,1,0,24,16Z'/%3E%3Cpath d='M8.532,20.0537a3.03,3.03,0,1,0,1.7326.9985C11.74,18.47,13.86,14.7607,13.89,14.708l.4976-.8682-.8677-.497a5,5,0,1,1,6.812-1.8438l1.7315,1.002a7.0008,7.0008,0,1,0-10.3462,2.0356c-.457.7427-1.1021,1.8716-2.0737,3.5728Z'/%3E%3Crect id='_Transparent_Rectangle_' data-name='<Transparent Rectangle>' class='cls-1' width='32' height='32'/%3E%3C/svg%3E" } } ``` ## Inbound boundary event connector templates You can, for example, allow the user to model and configure the following **HTTP webhook connector** for boundary events by providing a simple JSON configuration: ![Webhook Inbound boundary connector Example.png](img/custom-connector-template-inbound-boundary.png) ```json { "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", "name": "Webhook Boundary Event connector", "id": "io.camunda.connectors.webhook.WebhookConnectorBoundary.v1", "description": "Configure webhook to receive callbacks", "documentationRef": "https://docs.camunda.io/docs/components/connectors/out-of-the-box-connectors/http-webhook/", "version": 11, "category": { "id": "connectors", "name": "Connectors" }, "appliesTo": ["bpmn:BoundaryEvent"], "elementType": { "value": "bpmn:BoundaryEvent", "eventDefinition": "bpmn:MessageEventDefinition" }, "groups": [ { "id": "endpoint", "label": "Webhook configuration" }, { "id": "authentication", "label": "Authentication" }, { "id": "authorization", "label": "Authorization" }, { "id": "webhookResponse", "label": "Webhook response" }, { "id": "activation", "label": "Activation" }, { "id": "correlation", "label": "Correlation", "tooltip": "Learn more about message correlation in the documentation." }, { "id": "output", "label": "Output mapping" } ], "properties": [ { "value": "io.camunda:webhook:1", "binding": { "name": "inbound.type", "type": "zeebe:property" }, "type": "Hidden" }, { "id": "inbound.method", "label": "Webhook method", "description": "Select HTTP method", "optional": false, "value": "any", "group": "endpoint", "binding": { "name": "inbound.method", "type": "zeebe:property" }, "type": "Dropdown", "choices": [ { "name": "Any", "value": "any" }, { "name": "GET", "value": "get" }, { "name": "POST", "value": "post" }, { "name": "PUT", "value": "put" }, { "name": "DELETE", "value": "delete" } ] }, { "id": "inbound.context", "label": "Webhook ID", "description": "The webhook ID is a part of the URL", "optional": false, "constraints": { "notEmpty": true, "pattern": { "value": "^[a-zA-Z0-9]+([-_][a-zA-Z0-9]+)*$", "message": "can only contain letters, numbers, or single underscores/hyphens and cannot begin or end with an underscore/hyphen" } }, "group": "endpoint", "binding": { "name": "inbound.context", "type": "zeebe:property" }, "type": "String" }, { "id": "inbound.shouldValidateHmac", "label": "HMAC authentication", "description": "Choose whether HMAC verification is enabled. See documentation and example that explains how to use HMAC-related fields", "optional": false, "value": "disabled", "group": "authentication", "binding": { "name": "inbound.shouldValidateHmac", "type": "zeebe:property" }, "type": "Dropdown", "choices": [ { "name": "Enabled", "value": "enabled" }, { "name": "Disabled", "value": "disabled" } ] }, { "id": "inbound.hmacSecret", "label": "HMAC secret key", "description": "Shared secret key", "optional": true, "feel": "optional", "group": "authentication", "binding": { "name": "inbound.hmacSecret", "type": "zeebe:property" }, "condition": { "property": "inbound.shouldValidateHmac", "equals": "enabled", "type": "simple" }, "type": "String" }, { "id": "inbound.hmacHeader", "label": "HMAC header", "description": "Name of header attribute that will contain the HMAC value", "optional": true, "feel": "optional", "group": "authentication", "binding": { "name": "inbound.hmacHeader", "type": "zeebe:property" }, "condition": { "property": "inbound.shouldValidateHmac", "equals": "enabled", "type": "simple" }, "type": "String" }, { "id": "inbound.hmacAlgorithm", "label": "HMAC algorithm", "description": "Choose HMAC algorithm", "optional": false, "value": "sha_256", "group": "authentication", "binding": { "name": "inbound.hmacAlgorithm", "type": "zeebe:property" }, "condition": { "property": "inbound.shouldValidateHmac", "equals": "enabled", "type": "simple" }, "type": "Dropdown", "choices": [ { "name": "SHA-1", "value": "sha_1" }, { "name": "SHA-256", "value": "sha_256" }, { "name": "SHA-512", "value": "sha_512" } ] }, { "id": "inbound.hmacScopes", "label": "HMAC scopes", "description": "Set HMAC scopes for calculating signature data. See documentation", "optional": true, "feel": "required", "group": "authentication", "binding": { "name": "inbound.hmacScopes", "type": "zeebe:property" }, "condition": { "property": "inbound.shouldValidateHmac", "equals": "enabled", "type": "simple" }, "type": "String" }, { "id": "inbound.auth.type", "label": "Authorization type", "description": "Choose the authorization type", "value": "NONE", "group": "authorization", "binding": { "name": "inbound.auth.type", "type": "zeebe:property" }, "type": "Dropdown", "choices": [ { "name": "None", "value": "NONE" }, { "name": "Basic", "value": "BASIC" }, { "name": "API key", "value": "APIKEY" }, { "name": "JWT", "value": "JWT" } ] }, { "id": "inbound.auth.username", "label": "Username", "description": "Username for basic authentication", "optional": false, "feel": "optional", "group": "authorization", "binding": { "name": "inbound.auth.username", "type": "zeebe:property" }, "condition": { "property": "inbound.auth.type", "equals": "BASIC", "type": "simple" }, "type": "String" }, { "id": "inbound.auth.password", "label": "Password", "description": "Password for basic authentication", "optional": false, "feel": "optional", "group": "authorization", "binding": { "name": "inbound.auth.password", "type": "zeebe:property" }, "condition": { "property": "inbound.auth.type", "equals": "BASIC", "type": "simple" }, "type": "String" }, { "id": "inbound.auth.apiKey", "label": "API key", "description": "Expected API key", "optional": false, "feel": "optional", "group": "authorization", "binding": { "name": "inbound.auth.apiKey", "type": "zeebe:property" }, "condition": { "property": "inbound.auth.type", "equals": "APIKEY", "type": "simple" }, "type": "String" }, { "id": "inbound.auth.apiKeyLocator", "label": "API key locator", "description": "A FEEL expression that extracts API key from the request. See documentation", "optional": false, "value": "=split(request.headers.authorization, \" \")[2]", "constraints": { "notEmpty": true }, "feel": "required", "group": "authorization", "binding": { "name": "inbound.auth.apiKeyLocator", "type": "zeebe:property" }, "condition": { "property": "inbound.auth.type", "equals": "APIKEY", "type": "simple" }, "type": "String" }, { "id": "inbound.auth.jwt.jwkUrl", "label": "JWK URL", "description": "Well-known URL of JWKs", "optional": false, "feel": "optional", "group": "authorization", "binding": { "name": "inbound.auth.jwt.jwkUrl", "type": "zeebe:property" }, "condition": { "property": "inbound.auth.type", "equals": "JWT", "type": "simple" }, "type": "String" }, { "id": "inbound.auth.jwt.permissionsExpression", "label": "JWT role property expression", "description": "Expression to extract the roles from the JWT token. See documentation", "optional": false, "feel": "required", "group": "authorization", "binding": { "name": "inbound.auth.jwt.permissionsExpression", "type": "zeebe:property" }, "condition": { "property": "inbound.auth.type", "equals": "JWT", "type": "simple" }, "type": "String" }, { "id": "inbound.auth.jwt.requiredPermissions", "label": "Required roles", "description": "List of roles to test JWT roles against", "optional": false, "feel": "required", "group": "authorization", "binding": { "name": "inbound.auth.jwt.requiredPermissions", "type": "zeebe:property" }, "condition": { "property": "inbound.auth.type", "equals": "JWT", "type": "simple" }, "type": "String" }, { "id": "inbound.responseExpression", "label": "Response expression", "description": "Expression used to generate the HTTP response", "optional": true, "feel": "required", "group": "webhookResponse", "binding": { "name": "inbound.responseExpression", "type": "zeebe:property" }, "type": "Text" }, { "id": "inbound.verificationExpression", "label": "One time verification response expression", "description": "Specify condition and response. Learn more in the documentation", "optional": true, "feel": "required", "group": "webhookResponse", "binding": { "name": "inbound.verificationExpression", "type": "zeebe:property" }, "type": "Text" }, { "id": "activationCondition", "label": "Activation condition", "description": "Condition under which the connector triggers. Leave empty to catch all events", "optional": true, "feel": "required", "group": "activation", "binding": { "name": "activationCondition", "type": "zeebe:property" }, "type": "String" }, { "id": "consumeUnmatchedEvents", "label": "Consume unmatched events", "value": true, "group": "activation", "binding": { "name": "consumeUnmatchedEvents", "type": "zeebe:property" }, "tooltip": "Unmatched events are rejected by default, allowing the upstream service to handle the error. Check this box to consume unmatched events and return a success response", "type": "Boolean" }, { "id": "correlationKeyProcess", "label": "Correlation key (process)", "description": "Sets up the correlation key from process variables", "constraints": { "notEmpty": true }, "feel": "required", "group": "correlation", "binding": { "name": "correlationKey", "type": "bpmn:Message#zeebe:subscription#property" }, "type": "String" }, { "id": "correlationKeyPayload", "label": "Correlation key (payload)", "description": "Extracts the correlation key from the incoming message payload", "constraints": { "notEmpty": true }, "feel": "required", "group": "correlation", "binding": { "name": "correlationKeyExpression", "type": "zeebe:property" }, "type": "String" }, { "id": "messageIdExpression", "label": "Message ID expression", "description": "Expression to extract unique identifier of a message", "optional": true, "feel": "required", "group": "correlation", "binding": { "name": "messageIdExpression", "type": "zeebe:property" }, "type": "String" }, { "id": "messageTtl", "label": "Message TTL", "description": "Time-to-live for the message in the broker (ISO-8601 duration)", "optional": true, "constraints": { "notEmpty": false, "pattern": { "value": "^(PT.*|)$", "message": "must be an ISO-8601 duration" } }, "feel": "optional", "group": "correlation", "binding": { "name": "messageTtl", "type": "zeebe:property" }, "type": "String" }, { "id": "messageNameUuid", "generatedValue": { "type": "uuid" }, "group": "correlation", "binding": { "name": "name", "type": "bpmn:Message#property" }, "type": "Hidden" }, { "id": "resultVariable", "label": "Result variable", "description": "Name of variable to store the response in", "group": "output", "binding": { "name": "resultVariable", "type": "zeebe:property" }, "type": "String" }, { "id": "resultExpression", "label": "Result expression", "description": "Expression to map the response into process variables", "feel": "required", "group": "output", "binding": { "name": "resultExpression", "type": "zeebe:property" }, "type": "Text" } ], "icon": { "contents": "data:image/svg+xml;base64,PHN2ZyBpZD0naWNvbicgeG1sbnM9J2h0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnJyB3aWR0aD0nMTgnIGhlaWdodD0nMTgnIHZpZXdCb3g9JzAgMCAzMiAzMic+CiAgPGRlZnM+CiAgICA8c3R5bGU+LmNscy0xIHsgZmlsbDogbm9uZTsgfTwvc3R5bGU+CiAgPC9kZWZzPgogIDxwYXRoCiAgICBkPSdNMjQsMjZhMywzLDAsMSwwLTIuODE2NC00SDEzdjFhNSw1LDAsMSwxLTUtNVYxNmE3LDcsMCwxLDAsNi45Mjg3LDhoNi4yNTQ5QTIuOTkxNCwyLjk5MTQsMCwwLDAsMjQsMjZaJy8+CiAgPHBhdGgKICAgIGQ9J00yNCwxNmE3LjAyNCw3LjAyNCwwLDAsMC0yLjU3LjQ4NzNsLTMuMTY1Ni01LjUzOTVhMy4wNDY5LDMuMDQ2OSwwLDEsMC0xLjczMjYuOTk4NWw0LjExODksNy4yMDg1Ljg2ODYtLjQ5NzZhNS4wMDA2LDUuMDAwNiwwLDEsMS0xLjg1MSw2Ljg0MThMMTcuOTM3LDI2LjUwMUE3LjAwMDUsNy4wMDA1LDAsMSwwLDI0LDE2WicvPgogIDxwYXRoCiAgICBkPSdNOC41MzIsMjAuMDUzN2EzLjAzLDMuMDMsMCwxLDAsMS43MzI2Ljk5ODVDMTEuNzQsMTguNDcsMTMuODYsMTQuNzYwNywxMy44OSwxNC43MDhsLjQ5NzYtLjg2ODItLjg2NzctLjQ5N2E1LDUsMCwxLDEsNi44MTItMS44NDM4bDEuNzMxNSwxLjAwMmE3LjAwMDgsNy4wMDA4LDAsMSwwLTEwLjM0NjIsMi4wMzU2Yy0uNDU3Ljc0MjctMS4xMDIxLDEuODcxNi0yLjA3MzcsMy41NzI4WicvPgogIDxyZWN0IGlkPSdfVHJhbnNwYXJlbnRfUmVjdGFuZ2xlXycgZGF0YS1uYW1lPScmbHQ7VHJhbnNwYXJlbnQgUmVjdGFuZ2xlJmd0OycgY2xhc3M9J2Nscy0xJwogICAgd2lkdGg9JzMyJyBoZWlnaHQ9JzMyJy8+Cjwvc3ZnPg==" } } ``` ## Outbound connector templates You can, for example, allow the user to model and configure the following **REST connector** by providing a JSON configuration for a service task: ![REST Outbound connector Example](./img/custom-connector-template.png) ```json { "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", "name": "(Conditional) REST connector", "id": "io.camunda.examples.ConditionalRestConnector", "description": "A REST API invocation task.", "appliesTo": ["bpmn:ServiceTask"], "icon": { "contents": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 22 22' fill='none'%3E%3Ccircle cx='11' cy='11' r='9' fill='black'/%3E%3Ctext x='6.9' y='14.9' fill='white' style='font-family: Arial; font-size: 10px;'%3EM%3C/text%3E%3C/svg%3E" }, "properties": [ { "type": "Hidden", "value": "http", "binding": { "type": "zeebe:taskDefinition", "property": "type" } }, { "label": "REST Endpoint URL", "description": "Specify the url of the REST API to talk to.", "type": "String", "binding": { "type": "zeebe:taskHeader", "key": "url" }, "constraints": { "notEmpty": true, "pattern": { "value": "^https?://.*", "message": "Must be http(s) URL." } } }, { "id": "httpMethod", "label": "REST Method", "description": "Specify the HTTP method to use.", "type": "Dropdown", "value": "get", "choices": [ { "name": "GET", "value": "get" }, { "name": "POST", "value": "post" }, { "name": "PATCH", "value": "patch" }, { "name": "DELETE", "value": "delete" } ], "binding": { "type": "zeebe:taskHeader", "key": "method" } }, { "label": "Request Body", "description": "Data to send to the endpoint.", "value": "", "type": "String", "optional": true, "binding": { "type": "zeebe:input", "name": "body" }, "condition": { "property": "httpMethod", "oneOf": ["patch", "post", "delete"] } }, { "id": "authenticationType", "label": "Authentication Type", "description": "Specify the authentication type to use.", "type": "Dropdown", "value": "", "optional": true, "choices": [ { "name": "None", "value": "" }, { "name": "Basic", "value": "basic" }, { "name": "Bearer", "value": "bearer" } ], "binding": { "type": "zeebe:input", "name": "authentication.type" } }, { "label": "Username", "type": "String", "feel": "optional", "binding": { "type": "zeebe:input", "name": "authentication.username" }, "constraints": { "notEmpty": true }, "condition": { "property": "authenticationType", "equals": "basic" } }, { "label": "Password", "type": "String", "feel": "optional", "binding": { "type": "zeebe:input", "name": "authentication.password" }, "constraints": { "notEmpty": true }, "condition": { "property": "authenticationType", "equals": "basic" } }, { "label": "Bearer Token", "type": "String", "feel": "optional", "binding": { "type": "zeebe:input", "name": "authentication.token" }, "constraints": { "notEmpty": true }, "condition": { "property": "authenticationType", "equals": "bearer" } } ] } ``` ## Develop connector templates You can develop connector templates using the [`element template` feature](/components/modeler/element-templates/defining-templates.md). You can also look at existing [examples](https://github.com/camunda/camunda-modeler/blob/master/resources/element-templates/cloud-samples.json). If your connector provides several operations, define them with the [`steps` and `presets` keys](/components/modeler/element-templates/template-metadata.md#predefined-configurations-steps-and-presets). Users can then find each operation through search and select it when they apply the template, instead of applying the template first and choosing the operation in a dropdown afterwards. ## Make custom Connector templates available When using [Camunda Hub](/components/hub/workspace/modeler/index.md), you can create **Connector templates** [directly within the application](/components/hub/workspace/modeler/element-templates/element-template-generator.md) and share them with your respective organization. When using [Desktop Modeler](/components/modeler/desktop-modeler/index.md), you must place the **Connector templates** [within the file system](/components/modeler/desktop-modeler/element-templates/configuring-templates.md) so Desktop Modeler will pick them up. Once available, process developers can directly [use the **Connector templates** from within the modeling canvas](/components/connectors/use-connectors/index.md). --- ## Create a custom REST connector Create a custom REST connector based on and using the [Camunda REST connector](/components/connectors/protocol/rest.md) as a starting point. ## Create a custom connector based on the Camunda REST connector 1. In your Camunda Hub project, add a [task](/components/modeler/bpmn/tasks.md) element to a new or existing BPMN diagram. 1. Change the task type to [REST connector](/components/connectors/protocol/rest.md). 1. In the Properties panel, configure the connector as required. For example, define the Authentication URL, HTTP method, and any headers or payload needed for the API request. 1. Click **Save as Template**. 1. Enter details for the new connector template, such as a name and description. Save and create the new connector template. 1. Open the new connector template in the template editor and customize it as required. For example, add or remove fields, adjust default values and input parameters, and update the description and other metadata. 1. Save your changes to the new connector template, and use it as required in your processes. :::caution When creating a new template based on the REST connector, you must ensure that any field(s) used to set variables are placed **before** any field(s) that uses these variables. For example, in the following code, as the `url` requires the variables defined by `swid` and `swresource`, it is placed after them. Incorrectly placed variables will be considered as `null`. ```json { "id": "swid", "label": "id", "description": "Index of the resource", "feel": "optional", "group": "swapi", "binding": { "name": "index", "type": "zeebe:input" }, "type": "String" }, { "id": "swresource", "label": "Type", "description": "Choose the resource type", "value": "Planets", "group": "swapi", "binding": { "name": "resource", "type": "zeebe:input" }, "type": "Dropdown", "choices": [...] }, { "id": "url", "label": "URL", "optional": false, "constraints": { "notEmpty": true, "pattern": { "value": "^(=|(http://|https://|secrets|\\{\\{).*$)", "message": "Must be a http(s) URL" } }, "group": "endpoint", "binding": { "name": "url", "type": "zeebe:input" }, "type": "Hidden", "value": "=\"https://swapi.dev/api/\" + resource + \"/\" + index" } ``` ::: ## Example: Custom Star Wars API connector The following example shows how you can create a custom connector based on the Camunda REST connector that retrieves data from the [Star Wars API (SWAPI)](https://swapi.dev/). ### Step 1: Create connector template In this first step, create a new connector template, using the REST connector as a starting point. 1. Create a task and change its type to **Send REST Request**. 1. Define the URL as a FEEL (Friendly Enough Expression Language) expression, using the `resource` and `index` variables. 1. Click **Save as** to create a template from your configured connector. 1. Enter the **Template Name** and **Template Description** and click **Save**. These fields are essential for identifying and understanding the purpose of the template. ### Step 2: Edit the new connector template Once the new connector template is created, edit and configure it to retrieve data from the Star Wars API. 1. Open the new connector template in the Template Editor. 1. Hide Unwanted Properties: For properties that are not required in your connector, set the `type` to `Hidden`. For example, since authentication is not required in this example, it is set to `Hidden`. ```json { "id": "authentication.type", "label": "Type", "description": "Choose the authentication type. Select 'None' if no authentication is necessary", "value": "noAuth", "group": "authentication", "binding": { "name": "authentication.type", "type": "zeebe:input" }, "type": "Hidden", "choices": [ { "name": "API key", "value": "apiKey" }, { "name": "Basic", "value": "basic" }, { "name": "Bearer token", "value": "bearer" }, { "name": "None", "value": "noAuth" }, { "name": "OAuth 2.0", "value": "oauth-client-credentials-flow" } ] } ``` Similarly, hide other fields such as `url`, `method`, `headers`, and `queryParameters`. If the `hidden` type does not apply, ensure that you remove the `feel` property. ```json { "id": "url", "label": "URL", "optional": false, "constraints": { "notEmpty": true, "pattern": { "value": "^(=|(http://|https://|secrets|\\{\\{).*$)", "message": "Must be a http(s) URL" } }, "group": "endpoint", "binding": { "name": "url", "type": "zeebe:input" }, "type": "Hidden", "value": "=\"https://swapi.dev/api/\" + resource + \"/\" + index" } ``` 1. Create a Custom Group for the Star Wars API: Add a customized group named `swapi` for organizing your Star Wars-related properties. ```json { "id": "swapi", "label": "Star Wars Payload" } ``` 1. Define the Properties in the SWAPI Group: Map the properties within the new group to the previously defined `resource` and `index` variables. - Set `resource` as a `Dropdown`. - Set `index` as a `String`. ```json [ { "id": "swid", "label": "id", "description": "Index of the resource", "feel": "optional", "group": "swapi", "binding": { "name": "index", "type": "zeebe:input" }, "type": "String" }, { "id": "swresource", "label": "Type", "description": "Choose the resource type", "value": "Planets", "group": "swapi", "binding": { "name": "resource", "type": "zeebe:input" }, "type": "Dropdown", "choices": [ { "name": "Planets", "value": "planets" }, { "name": "Spaceships", "value": "spaceships" }, { "name": "Vehicles", "value": "vehicles" }, { "name": "People", "value": "people" }, { "name": "Films", "value": "films" }, { "name": "species", "value": "Species" } ] } ] ``` 1. Add an appropriate icon if required to enhance your connector's visual appeal. 1. Once configuration is complete, click **Publish** to publish the connector template and make it available for use. 1. Use your newly published SWAPI connector in your BPMN workflows. --- ## Create a credential template Define a credential type for your custom connector, so users can create and select a credential for it in [Camunda Hub](/components/hub/workspace/modeler/index.md) or [Desktop Modeler](/components/modeler/desktop-modeler/index.md), instead of configuring authentication fields directly on the connector task. See [credentials](/components/hub/organization/credentials/index.md) for the concept this page builds on, and [`Configuration` input type](/components/modeler/element-templates/template-properties.md#configuration-input-type) for the generic `Configuration` property type it uses. ## About credential templates A **credential template** is a [configuration template](/components/modeler/element-templates/template-metadata.md#embedding-configurations-configurationtemplates) whose `kind` is `CREDENTIAL`. It defines the fields a credential of that type has, and how they render in the credential editor in Camunda Hub and Desktop Modeler. A credential template is not an element template property. It's a separate, self-contained schema, embedded in your connector's element template under the top-level `configurationTemplates` key. Your connector's element template then declares a `Configuration`-type property that locks to it, which renders as the credential chooser in the properties panel. ## Define a credential template A credential template has the following top-level fields: | Field | Required | Description | | ------------- | -------- | ---------------------------------------------------------------------------------------------------------------- | | `id` | Yes | Uniquely identifies the credential template. Referenced by a `Configuration` property's `configurationTemplate`. | | `name` | Yes | Display name shown in the credential editor, and the default label for the chooser. | | `version` | Yes | An integer. Bump it when the credential's shape changes (see [Versioning](#versioning-a-credential-template)). | | `kind` | Yes | The kind of configuration. Set this to `CREDENTIAL`. | | `description` | No | Short description shown in the credential editor. | | `properties` | Yes | The fields that make up the credential's stored value (see below). | Each property uses the standard [element template property](/components/modeler/element-templates/template-properties.md) shape, with three restrictions: only the `property` binding is supported, `type: "Configuration"` is not (a credential cannot embed another credential), and `optional` is not. Leave a field blank instead, and blank fields are omitted from the stored value. A property's `binding.name` is the key it contributes to the credential's stored value. Use a dotted name, such as `authentication.accessKey`, to nest fields into a sub-object matching the shape your connector expects to receive. Here is a complete example, an AWS credential with two authentication methods and a region: ```json { "id": "io.camunda:aws-credential:1", "name": "AWS Credential", "version": 1, "kind": "CREDENTIAL", "description": "AWS access credentials for S3, Bedrock, and other AWS services", "properties": [ { "id": "authType", "label": "Authentication type", "type": "Dropdown", "choices": [ { "name": "Default credentials chain", "value": "defaultCredentialsChain" }, { "name": "Access key / Secret key", "value": "credentials" } ], "binding": { "type": "property", "name": "authentication.type" } }, { "id": "accessKey", "label": "Access key", "type": "String", "secret": true, "condition": { "property": "authType", "equals": "credentials" }, "binding": { "type": "property", "name": "authentication.accessKey" } }, { "id": "secretKey", "label": "Secret key", "type": "String", "secret": true, "condition": { "property": "authType", "equals": "credentials" }, "binding": { "type": "property", "name": "authentication.secretKey" } }, { "id": "region", "label": "Region", "type": "String", "constraints": { "notEmpty": true }, "binding": { "type": "property", "name": "region" } } ] } ``` A user who selects **Access key / Secret key**, fills in both keys, and sets a region produces this stored value: ```json { "authentication": { "type": "credentials", "accessKey": "camunda.secrets.AWS_ACCESS_KEY", "secretKey": "camunda.secrets.AWS_SECRET_KEY" }, "region": "eu-west-1" } ``` ## Embed the credential template in your element template Add the credential template to your connector's element template under the top-level `configurationTemplates` key: ```json { "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", "name": "My Connector", "id": "io.camunda:my-connector:1", ..., "configurationTemplates": [ { "id": "io.camunda:aws-credential:1", "name": "AWS Credential", "version": 1, "kind": "CREDENTIAL", "properties": [ ... ] } ], "properties": [ ... ] } ``` If more than one connector uses the same credential type, embed the identical `configurationTemplates` entry (same `id` and `version`) in each. The first copy the modeler loads becomes the canonical definition; later copies are only accepted if they match it exactly. If your connector needs to support in-place upgrades of a bound credential to a newer template version, embed both versions. The modeler needs the source version to read the existing value, and the target version to render the upgraded form. :::tip If you build your connector in Java, you don't have to maintain these embedded schemas by hand. The [element template generator](https://github.com/camunda/connectors/tree/main/element-template-generator) generates them from your Java code and inserts them into every element template that needs them. ::: ## Add a credential field to your element template Declare a `Configuration` property that locks to your credential template, and bind it to your connector's dedicated configuration input: ```json { "id": "awsCredential", "label": "AWS Credential", "type": "Configuration", "group": "authentication", "description": "Choose a reusable AWS credential. When set, it is bound as a whole to the connector's 'authentication' input.", "configurationTemplate": "io.camunda:aws-credential:1", "configurationTemplateVersion": 1, "binding": { "type": "zeebe:input", "name": "authentication" } } ``` - `configurationTemplate` is the credential template's `id`. The modeler only offers credentials created from that template. - `configurationTemplateVersion` is optional, and is a **floor**, not a fixed version: the minimum credential template version a selected credential must satisfy. A credential at or above this version is always compatible. Omit it if any version of the template is acceptable. - `binding` uses `zeebe:input` for an outbound connector, or `zeebe:property` for an inbound connector. Point `name` at a dedicated configuration input on your connector, for example `authentication` or `configuration`. Your connector implementation reads this input as one object, and takes whatever fields it needs from it, the same fields your credential template defines. For a complete element template that puts both parts together, see the [example template](/components/modeler/element-templates/template-example.md). ### Supporting inline fields as a fallback For a new connector, use credentials only: declare the `Configuration` property, mark it `constraints: { "notEmpty": true }`, and don't offer inline authentication fields at all. This keeps one way to authenticate the task, and keeps authentication data out of the diagram. Inline fallback fields are a backward-compatibility pattern for a connector that already shipped with inline authentication, where existing diagrams must keep working. It isn't automatic: your connector runtime has to resolve which of the two sources to use. If you need it, hide the inline fields once a credential is selected, using an `isEmpty` condition on the chooser property: ```json { "id": "accessKey", "type": "String", "condition": { "property": "awsCredential", "isEmpty": true } } ``` Your connector then reads one effective value, preferring the bound credential and falling back to the inline field when no credential is selected. When you are ready to drop the inline fields, remove them in a new element template version and mark the `Configuration` property required. Existing diagrams stay on their earlier version and are unaffected. ## Secret fields Mark a credential template field as holding a secret reference with `secret: true`: ```json { "id": "accessKey", "label": "Access key", "type": "String", "secret": true, "binding": { "type": "property", "name": "authentication.accessKey" } } ``` This is a rendering hint for the credential editor in Hub and Desktop Modeler. It doesn't restrict what the field can hold, but it tells the editor to treat entered values as secret references rather than literals. A user enters an existing secret's key, and the editor stores it as `camunda.secrets.`. The engine resolves this reference when the job worker or connector task activates; your connector never sees the marker itself, only the resolved value. See [Secret resolution](/components/concepts/secret-resolution.md) for how `camunda.secrets.` references are resolved. Your connector cannot create the secret itself from a credential field. The secret must already exist on the cluster. Don't design a credential template that requires a secret your users have no way to create ahead of time. ## Versioning a credential template A credential template's `version` must only ever grow more permissive: add optional fields freely, but never remove a field, change its type, or change what an existing value means. If you need a breaking change, publish a new `id` instead (for example `io.camunda:aws-credential:2`) rather than reusing the old one. This works because of floor semantics: a `Configuration` property's `configurationTemplateVersion` is the minimum version a credential must satisfy, not the version it must match. Bumping your credential template's version doesn't invalidate credentials created against an earlier version. They still satisfy any element template that declares a floor at or below their version. Administrators upgrade a shared credential to a newer version at their own pace, independent of when an element template starts requiring it. :::note Because an in-place credential edit takes effect immediately for every process that references it, testing a credential against its target cluster and connected system, before saving any change, is the critical safety step for whoever manages it, not the version number itself. ::: ## Additional resources - [Credentials](/components/hub/organization/credentials/index.md) - [`Configuration` input type](/components/modeler/element-templates/template-properties.md#configuration-input-type) - [Embedding configurations: `configurationTemplates`](/components/modeler/element-templates/template-metadata.md#embedding-configurations-configurationtemplates) - [Example element template](/components/modeler/element-templates/template-example.md) - [Connector templates](/components/connectors/custom-built-connectors/connector-templates.md) - [Connector SDK](/components/connectors/custom-built-connectors/connector-sdk.md) --- ## Host custom connectors This guide explains how to host your own **Connectors** developed with [Connector SDK](/components/connectors/custom-built-connectors/connector-sdk.md). ## Prerequisites - Ensure you have to have a working Camunda cluster in SaaS or Self-Managed. - Ensure you have a distribution version of your connector in the form of "fat" `jar` file. For the purpose of this guide, we will be using a generic [Connector template](https://github.com/camunda/connector-template-outbound) as a reference. Clone the repository, and execute `mvn clean verify package`. This produces two JAR files in the `target/` directory. Use `connector-template-0.1.0-SNAPSHOT.jar` (the JAR that bundles all dependencies). Ignore `original-connector-template-0.1.0-SNAPSHOT.jar` (the JAR without dependencies). In this guide, we will refer to `connector-template-0.1.0-SNAPSHOT.jar` as `connector.jar`. ## Wiring your connector with a Camunda cluster This approach is equivalent to the [hybrid mode](/components/connectors/use-connectors-in-hybrid-mode.md), except you don't need to override existing connectors and instead add a new one. You need to have a running Camunda cluster, and a pair of `Client ID`/`Client Secret` with `Zeebe` and `Operate` scopes. Learn more about [how to obtain required credentials](/components/saas/clusters/manage-api-clients.md). Run the following command: ```shell docker run --rm --name=CustomConnectorInSaaS \ -v $PWD/connector.jar:/opt/custom/connector.jar \ -e LOADER_PATH=/opt/custom \ -e CAMUNDA_CLIENT_CLOUD_CLUSTERID='' \ -e CAMUNDA_CLIENT_AUTH_CLIENTID='' \ -e CAMUNDA_CLIENT_AUTH_CLIENTSECRET='' \ -e CAMUNDA_CLIENT_CLOUD_REGION='' \ camunda/connectors-bundle: ``` The line `-v $PWD/connector.jar:/opt/custom/connector.jar` binds a volume with your connector at the path `$PWD/connector.jar` of your local machine. ## Wiring your connector with Camunda Docker instance (without Keycloak) This option is applicable if you launch your cluster in a Self-Managed version with [Camunda Docker Compose variant without Keycloak](https://github.com/camunda/camunda-distributions/tree/main/docker-compose). Run the following command: ```shell docker run --rm --name=CustomConnectorInSMCore \ -v $PWD/connector.jar:/opt/app/connector.jar \ --network=camunda-platform_camunda-platform \ -e CAMUNDA_CLIENT_BROKER_GATEWAY-ADDRESS=zeebe:26500 \ -e CAMUNDA_CLIENT_SECURITY_PLAINTEXT=true \ -e CAMUNDA_OPERATE_CLIENT_URL=http://operate:8080 \ -e CAMUNDA_OPERATE_CLIENT_USERNAME=demo \ -e CAMUNDA_OPERATE_CLIENT_PASSWORD=demo \ camunda/connectors-bundle: ``` :::note Exact values of the environment variables related to Zeebe, Operate, or network may depend on your own configuration. ::: ## Wiring your connector with Camunda Docker instance (with Keycloak) This option is applicable if you launch your cluster in a Self-Managed version with [Camunda Platform Docker Compose variant with Keycloak](https://github.com/camunda/camunda-distributions/tree/main/docker-compose). Run the following command: ```shell docker run --rm --name=CustomConnectorInSMWithKeyCloak \ -v $PWD/connector.jar:/opt/app/connector.jar \ --network=camunda-platform_camunda-platform \ -e CAMUNDA_CLIENT_BROKER_GATEWAY-ADDRESS=zeebe:26500 \ -e CAMUNDA_CLIENT_SECURITY_PLAINTEXT=true \ -e CAMUNDA_CLIENT_ID= \ -e CAMUNDA_CLIENT_SECRET= \ -e ZEEBE_TOKEN_AUDIENCE=zeebe-api \ -e ZEEBE_AUTHORIZATION_SERVER_URL=http://keycloak-service:18080/auth/realms/camunda-platform/protocol/openid-connect/token \ -e CAMUNDA_IDENTITY_TYPE=KEYCLOAK \ -e CAMUNDA_IDENTITY_AUDIENCE=operate-api \ -e CAMUNDA_IDENTITY_ISSUER_BACKEND_URL=http://keycloak:18080/auth/realms/camunda-platform \ -e CAMUNDA_IDENTITY_CLIENT_ID=connectors \ -e CAMUNDA_IDENTITY_CLIENT_SECRET= \ -e CAMUNDA_OPERATE_CLIENT_URL=http://operate:8080 \ camunda/connectors-bundle: ``` :::note Exact values of the environment variables related to Zeebe, Operate, Keycloak, or network may depend on your own configuration. ::: ## Wiring your connector with Camunda Helm charts There are multiple ways to configure a Helm/Kubernetes Self-Managed cluster. Refer to the [official guide](/self-managed/setup/overview.md) to learn more. For the purpose of this section, imagine you installed Helm charts with `helm install camunda camunda/camunda-platform --version $HELM_CHART_VERSION`, and forwarded the Zeebe and Operate ports. If you use [Keycloak deployed via the Keycloak Operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md), also forward the Keycloak port: ```bash kubectl port-forward svc/camunda-zeebe-gateway 26500:26500 kubectl port-forward svc/camunda-zeebe-gateway 8080:8080 ``` ```bash # Only if using Keycloak kubectl port-forward svc/keycloak-service 18080:18080 ``` Now, you need to obtain both Zeebe and connectors' Operate OAuth clients. You can do it with `kubectl get secret camunda-zeebe-identity-secret -o jsonpath="{.data.*}" | base64 --decode` and `kubectl get secret camunda-connectors-identity-secret -o jsonpath="{.data.*}" | base64 --decode` respectively. Run the following command: ```shell docker run --rm --name=CustomConnectorInSMWithHelm \ -v $PWD/connector.jar:/opt/app/connector.jar \ -e CAMUNDA_CLIENT_BROKER_GATEWAY-ADDRESS=host.docker.internal:26500 \ -e CAMUNDA_CLIENT_SECURITY_PLAINTEXT=true \ -e CAMUNDA_CLIENT_ID=zeebe \ -e CAMUNDA_CLIENT_SECRET= \ -e ZEEBE_TOKEN_AUDIENCE=zeebe-api \ -e ZEEBE_AUTHORIZATION_SERVER_URL=http://host.docker.internal:18080/auth/realms/camunda-platform/protocol/openid-connect/token \ -e CAMUNDA_IDENTITY_TYPE=KEYCLOAK \ -e CAMUNDA_IDENTITY_AUDIENCE=operate-api \ -e CAMUNDA_IDENTITY_ISSUER_BACKEND_URL=http://host.docker.internal:18080/auth/realms/camunda-platform \ -e CAMUNDA_IDENTITY_CLIENT_ID=connectors \ -e CAMUNDA_IDENTITY_CLIENT_SECRET= \ -e CAMUNDA_OPERATE_CLIENT_URL=http://host.docker.internal:8081 \ camunda/connectors-bundle: ``` :::note Exact values of the environment variables related to Zeebe, Operate, Keycloak, or network may depend on your own configuration. ::: Interested in creating a custom connector? Review the related Camunda Academy courses on [creating a custom inbound connector](https://academy.camunda.com/c8-custom-inbound-connectors) or [creating a custom outbound connector](https://academy.camunda.com/c8-custom-outbound-connectors). --- ## Connectors(Connectors) [Built-in connectors](/reference/glossary.md#connector) are reusable building blocks you can use to easily connect processes to external systems, applications, and data. - Use a [Slack](/components/connectors/out-of-the-box-connectors/slack.md) or [Microsoft Teams](/components/connectors/out-of-the-box-connectors/microsoft-teams.md) connector to notify people of pending or completed processes in a human task orchestration onboarding flow. - In microservices orchestration, use a [Kafka](/components/connectors/out-of-the-box-connectors/kafka.md) connector to add real-time messaging to your automated processes. - In [agentic orchestration](/reference/glossary.md#agentic-orchestration), connectors are also the [tools an AI agent calls](/components/connectors/connector-types.md#outbound-connectors) to interact with external systems. Connectors are often configured as a [BPMN process](/components/concepts/processes.md) task, with the required integration parameters already set up for easy configuration. This helps remove the need for you to write custom integration programming code. Find a built-in connector ## Get started New to connectors? Learn about connector types and how to start using connectors in your processes. :::info Get started by learning how to [integrate a Camunda 8 connector](/components/connectors/use-connectors/configuring-out-of-the-box-connector.md). ::: ## Custom connectors Build and deploy your own [custom connectors](../custom-built-connectors/build-connector) using [connector templates](/reference/glossary.md#connector-template) and the connector SDK. ## Featured connectors Get started with our latest and most popular connectors. --- ## Manage connector templates Connector templates are a specific type of [element template](/components/concepts/element-templates.md). To manage them, see the [manage element templates](/components/hub/workspace/modeler/element-templates/manage-element-templates.md) documentation. --- ## Ad-hoc Tools Schema Resolver connector The **Ad-hoc Tools Schema connector** is an outbound connector that implements the tool resolution part of the [**AI Agent connector**](./agentic-ai-aiagent.md). ## About this connector Although its function is also embedded in the AI Agent connector, the Ad-hoc Tools Schema connector can be used independently in combination with other AI connectors. This can be useful for: - **Direct LLM interaction**: If you don't want to use the AI Agent connector but still want to resolve tools for an ad-hoc sub-process, use the `fromAi` function to define the input schema in combination with custom LLM integrations. - **Debugging tool definitions**: If you want to test the tool resolution logic without having to set up a full AI Agent connector, you can use this connector to get the tool definitions and see how they are generated. ## Prerequisites The following prerequisites are required to use this connector: | Prerequisite | Description | | :----------------- | :-------------------------------------------------------------------------- | | Ad-hoc sub-process | Your process must contain an ad-hoc sub-process whose ID you can reference. | ## Create an Ad-hoc Tools Schema connector task 1. Create a service task. 2. [Apply](../use-connectors/outbound.md) the **Ad-hoc Tools Schema** element template. 3. Configure the **Ad-hoc sub-process ID** to reference the element ID of the ad-hoc sub-process. 4. Configure [a result variable or a result expression](../use-connectors/index.md#variableresponse-mapping) to map the connector results to process variables. ## Tool Definitions For more information on how to define tools, see the [tool definitions](agentic-ai-aiagent-tool-definitions.md#tool-definitions) documentation. ## Response Structure This connector returns a list of tool definitions in the following format. Individual tool definitions are modeled after the [list tools response](https://modelcontextprotocol.io/specification/2025-03-26/server/tools#listing-tools) defined in the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) specification, so you should be able to directly use the definitions with different LLMs or transform them into the required format for your AI integration. ```json { "toolDefinitions": [ { "name": "GetDateAndTime", "description": "Returns the current date and time including the timezone.", "inputSchema": { "type": "object", "properties": {}, "required": [] } }, { "name": "Download_A_File", "description": "Download a file from the provided URL.", "inputSchema": { "type": "object", "properties": { "url": { "type": "string", "description": "The URL to download the file from" } }, "required": ["url"] } }, { "name": "SuperfluxProduct", "description": "Calculates the superflux product (a very complicated calculation) given two input numbers.", "inputSchema": { "type": "object", "properties": { "a": { "type": "number", "description": "The first number to be superflux calculated." }, "b": { "type": "number", "description": "The second number to be superflux calculated." } }, "required": ["a", "b"] } } ] } ``` You can either configure a result variable to contain the whole response or use a result expression to map parts of the response into your process. --- ## Customize the AI Agent connector Customize the [AI Agent connector](./agentic-ai-aiagent.md) in Self-Managed or [hybrid](../../../reference/glossary.md#hybrid-mode) deployments by implementing custom conversation storage, supporting additional AI models, or adding logic to the agent execution flow. ## HTTP proxy configuration In Self-Managed environments, the AI Agent connector supports routing HTTP requests to LLM providers through an HTTP proxy. This applies to the AI Agent, [MCP Client](./agentic-ai-mcp-client.md), and [A2A Client](/components/early-access/alpha/a2a-client/a2a-client.md) connectors. These connectors support [plain proxy variables](/self-managed/components/connectors/http-proxy-configuration.md#plain-proxy-variables) in addition to the standard connector proxy variables. Refer to the [HTTP proxy configuration](/self-managed/components/connectors/http-proxy-configuration.md) page for the full list of environment variables and configuration options. The following LLM providers do not support connector proxy variables, but respect standard [JVM proxy properties](/self-managed/components/connectors/http-proxy-configuration.md#jvm-properties): - Google Vertex AI. To disable proxy support entirely (for example, if only an HTTPS-based proxy is available): - **Spring Boot property:** `camunda.connector.agenticai.http.proxy-support.enabled=false`. - **Environment variable:** `CAMUNDA_CONNECTOR_AGENTICAI_HTTP_PROXYSUPPORT_ENABLED=false`. ## Extend the AI Agent connector :::note The agentic AI ecosystem evolves quickly. The APIs used for these customizations can change between minor releases. See the [breaking changes for custom extensions](https://github.com/camunda/connectors/blob/main/connectors/agentic-ai/docs/breaking-changes.md). ::: ### Prerequisites This guide assumes you are starting from a fresh Spring Boot project and intend to run a customized AI Agent connector in a self-managed or hybrid environment. 1. Create a new Spring Boot project. 2. Add the [Camunda Connector Spring Boot Starter](../custom-built-connectors/connector-sdk.md#spring-boot-starter-runtime) and the Agentic AI dependencies to your `pom.xml`: ```xml 8.10.0 io.camunda.connector spring-boot-starter-camunda-connectors ${version.connectors} io.camunda.connector connector-agentic-ai ${version.connectors} ``` 3. Configure the SDK to connect to your cluster according to [the Camunda SDK documentation](../../../apis-tools/camunda-spring-boot-starter/getting-started.md#configuring-the-camunda-8-connection). 4. To only run the AI Agent Client connector, disable the other agentic AI connectors provided by the `connector-agentic-ai` dependency in your `application.yml`: ```yaml camunda: connector: agenticai: ad-hoc-tools-schema-resolver: enabled: false mcp: remote-client: enabled: false a2a: client: outbound: enabled: false polling: enabled: false webhook: enabled: false ``` 5. If the default AI Agent connector is already connected to your engine (for example, if you are connecting to SaaS), you can override the registered AI Agent connector job worker type by setting one of the following type environment variables to a custom value (such as `my-ai-agent`) when starting your application. This allows you to use your custom connector in combination with an [element template configured](../use-connectors-in-hybrid-mode.md) for the `my-ai-agent` job worker type. | Variable | Description | | :----------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------- | | `CONNECTOR_AI_AGENT_TASK_TYPE` | Overrides the type of the [AI Agent Task](./agentic-ai-aiagent-task.md) connector (default `io.camunda.agenticai:aiagent:task:2`). | | `CONNECTOR_AI_AGENT_SUBPROCESS_TYPE` | Overrides the type of the [AI Agent Sub-process](./agentic-ai-aiagent-subprocess.md) connector (default `io.camunda.agenticai:aiagent:subprocess:2`). | If you previously overrode the job types of the legacy element templates, see [job type overrides](./agentic-ai-aiagent-upgrade.md#job-type-overrides). ### Customize individual components Each component of the AI Agent connector is registered as a default Spring bean. You can override any default component by registering your own implementation as a Spring bean in your custom project. The following sections show how to add a custom chat model provider and a custom conversation store. :::tip You can also use other Spring mechanisms to customize the AI Agent connector, such as using Aspect Oriented Programming (AOP) to intercept and modify method calls. ::: ### Custom chat model provider Add support for an LLM provider that the connector doesn't include by registering a `ChatModelFactory` bean. The AI Agent connector routes requests to your factory when a process uses the **Custom implementation** model provider with a matching **Provider type**. A custom provider needs two pieces: - A `ChatModelFactory` bean: `supports(...)` decides whether the factory handles a configuration, and `create(...)` builds the `ChatModel`. - A `ChatModel`: `execute(...)` performs one round-trip against the provider and returns a `ChatResult`. The connector calls `close()` once the agent request is done. The following skeleton handles the provider type `my-provider`: ```java @Component public class MyChatModelFactory implements ChatModelFactory { public static final String PROVIDER_TYPE = "my-provider"; @Override public boolean supports(ChatModelConfiguration configuration) { return configuration instanceof CustomProviderConfiguration custom && PROVIDER_TYPE.equals(custom.providerType()); } @Override public ChatModel create(ChatModelConfiguration configuration) { final var custom = (CustomProviderConfiguration) configuration; // custom.model() is the model ID, custom.parameters() the provider parameters return new MyChatModel(custom.model(), custom.parameters()); } } ``` ```java public class MyChatModel implements ChatModel { public MyChatModel(String model, Map parameters) { // set up your provider client } @Override public ChatResult execute(ChatRequest request) { // 1. Convert request.snapshot() to your provider's request format. // 2. Call the provider. // 3. Convert the response to an AssistantMessage and fill in the AgentMetrics // (model calls, token usage). return new ChatResult.Completed(assistantMessage, metrics); } @Override public void close() { // release provider client resources } } ``` Return `ChatResult.Continuation` instead of `ChatResult.Completed` if the provider pauses mid-turn and must be called again to continue the same turn. :::note The connector throws an error if no factory, or more than one factory, supports a configuration. Make sure `supports(...)` only matches your own provider type. ::: To use the provider in a process: 1. Apply the new **AI Agent Task** or **AI Agent Sub-process** element template, version 2. 2. In the **Model provider** group, set **Provider** to **Custom implementation**. 3. Set **Provider type** to the value your factory matches (`my-provider` in the example above). 4. Set **Model** to the model ID your implementation expects. 5. (Optional) Set **Provider parameters** to a FEEL context that your factory reads, for example `={apiKey: "{{secrets.MY_API_KEY}}"}`. To build on a built-in provider instead of calling an API yourself, inject the built-in factory bean, such as `OpenAiChatModelFactory`, and call it directly from your factory. ### Custom conversation storage The AI Agent connector includes a set of default storage backends for conversation history, but you can also implement your own to meet specific needs. Like other components, you can register a bean that implements the `ConversationStore` interface to provide your own storage implementation. A custom store needs three pieces: - A `ConversationStore` bean: The entry point. Its `type()` value is referenced from the element template. - A `ConversationSession` returned by `createSession(...)`: Performs the actual load and store for a single agent turn. The caller manages its lifecycle via `try-with-resources`, so override `close()` if your session holds external resources (connections, clients). - A `ConversationContext` implementation: The storage cursor persisted as part of the `agentContext` process variable. It must be annotated with `@JsonTypeName` and registered with the runtime `ObjectMapper`. The following example shows how to implement a custom store using a Spring Data JPA repository. The value returned by the `type()` method is used to identify the store type in the AI Agent connector configuration. ```java @Component public class MyConversationStore implements ConversationStore { public static final String TYPE = "my-conversation"; private final MyConversationRepository repository; public MyConversationStore(MyConversationRepository repository) { this.repository = repository; } @Override public String type() { return TYPE; } @Override public ConversationSession createSession( AgentExecutionContext executionContext, AgentContext agentContext) { return new MyConversationSession(repository, executionContext); } @Override public void onJobCompleted( AgentExecutionContext executionContext, AgentContext committedContext) { // Best-effort hook fired after Zeebe accepted the job completion. Optional: use // this to update a projection, archive the previous record, emit an event, etc. } @Override public void onJobCompletionFailed( AgentExecutionContext executionContext, AgentContext failedContext, JobCompletionFailure failure) { // Best-effort hook fired after Zeebe rejected the job completion (or the // connector itself raised an error). The record written by storeMessages during // this job is now an orphan — optional: delete it here so orphans do not // accumulate. } } ``` The session reads and writes the conversation messages for a single agent turn. `loadMessages` returns the message history that the incoming `ConversationContext` references; `storeMessages` persists the updated message list and returns a new `ConversationContext` pointing to the newly written record. The caller assembles the full `AgentContext` from the returned context. ```java public class MyConversationSession implements ConversationSession { @Override public ConversationLoadResult loadMessages(AgentContext agentContext) { // Load the messages referenced by the ConversationContext in agentContext. } @Override public ConversationContext storeMessages( AgentContext agentContext, ConversationStoreRequest request) { // Persist request.messages() to a new record and return a ConversationContext // pointing at it. Never mutate the record the previous context points to — // see the storage contract note below. } } ``` The `ConversationContext` is the storage cursor. It is serialized as part of the `agentContext` process variable, so it must be annotated with `@JsonTypeName` and contain everything needed to locate the stored data on the next turn: ```java @JsonTypeName("my-conversation") public record MyConversationContext(String conversationId, UUID recordId) implements ConversationContext {} ``` Register the subtype with the connector runtime's `ObjectMapper` instances so the connector can deserialize the context back from the process variable. The runtime builds its own mappers, which Spring Boot's `Jackson2ObjectMapperBuilderCustomizer` and `JsonMapperBuilderCustomizer` beans don't configure. Use a `BeanPostProcessor` that registers the subtype on every `ObjectMapper` bean instead: ```java @Component public class ConversationContextSubTypesBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) { if (bean instanceof ObjectMapper objectMapper) { objectMapper.registerSubtypes(MyConversationContext.class); } return bean; } } ``` :::tip Serialize with the connector object mapper If your store serializes messages to JSON, use the connector runtime's `ObjectMapper` bean (qualifier `@ConnectorsObjectMapper`) instead of creating your own. It already supports the connector data types that messages can contain, such as document references. ::: :::note Storage contract `storeMessages` must always write to a **new** record (or document, or branch) and return a `ConversationContext` pointing to it. Never mutate or overwrite the data the previous context points at. If job completion fails, Zeebe retries with the old `AgentContext` (and therefore the old cursor); the old pointer must still resolve to the old data. The newly written record becomes an orphan, which the `onJobCompletionFailed` hook can clean up. See the [storage contract reference](https://github.com/camunda/connectors/blob/main/connectors/agentic-ai/docs/reference/ai-agent.md#storage-contract) for the full rules every implementation must follow. ::: After implementing the custom store, you can reference the store type in your AI Agent connector configuration (see [memory configuration](./agentic-ai-aiagent.md#memory)): 1. In the **Memory** group of the AI Agent connector properties, set the **Memory storage type** to **Custom implementation**. 2. In the **Implementation type** field, enter the type value of your custom store implementation (`my-conversation` in the example above). 3. Run your process model. It should now use your custom conversation store for storing the conversation history. :::info An incident is raised if the AI Agent connector is not able to find a conversation store implementation for the specified type. ::: --- ## AI Agent document support The AI Agent connector can pass [Camunda documents](/self-managed/concepts/document-handling/overview.md) to the LLM from two sources: - The [user prompt](./agentic-ai-aiagent-subprocess.md#user-prompt) **Documents** field. - [Tool call results](./agentic-ai-aiagent-tool-definitions.md#tool-call-responses) and event payloads from event sub-processes. In both cases, supported documents are resolved and passed to the LLM as native content blocks so the model can interpret them directly. ## Supported document types Because file type support varies by LLM provider and model, you must test your document use case with the provider you are using. | File type | Supported | Description | | :---------------- | :-------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Text | Yes | Text files (MIME types matching `text/*`, `application/xml`, `application/json`, or `application/yaml`) are passed as plain text content blocks. | | PDF | Yes | PDF files (MIME types matching `application/pdf`) are passed as base64 encoded content blocks. | | Image | Yes | Image files (MIME types matching `image/jpeg`, `image/png`, `image/gif`, or `image/webp`) are passed as base64 encoded content blocks. | | Audio/video/other | No | Audio and video files are not currently supported, and will result in an error if passed. All other unsupported file types not listed here will also result in an error if passed. | :::info To learn more about storing, tracking, and managing documents in Camunda 8, see [document handling](/components/document-handling/getting-started.md). ::: ## Documents in the user prompt Use the [user prompt](./agentic-ai-aiagent-subprocess.md#user-prompt) **Documents** field to add a list of document references the agent can interact with. The list is internally resolved and passed to the LLM if the document type is supported. LLM APIs allow the user prompt to be specified as a list of content blocks. Each supported document reference is resolved to a corresponding content block and passed as part of the user message. For examples of how LLM providers accept document content blocks, refer to the [Anthropic](https://docs.anthropic.com/en/docs/build-with-claude/vision#base64-encoded-image-example) and [OpenAI](https://platform.openai.com/docs/guides/images-vision#giving-a-model-images-as-input) documentation. ## Documents in tool call results [Tool call responses](./agentic-ai-aiagent-tool-definitions.md#tool-call-responses) can contain document references nested anywhere within the result structure. The agent extracts these documents from the tool call result and passes them to the LLM as native content blocks (plain text for text files, base64 encoded content for PDFs and images). This is the same mechanism used for user prompt documents. In the conversation, the tool call result itself retains a lightweight document _reference_ (for example, the document ID and store, or an external URL). The resolved document content is delivered in a separate follow-up user message immediately after the tool result, allowing the model to correlate each reference with its content. For example, a tool can return a document for the LLM to analyze: - A [REST connector](/components/connectors/protocol/rest.md) tool with the **Store response** option enabled downloads a PDF document. - A user task tool with a [Filepicker](/components/modeler/forms/form-element-library/forms-element-library-filepicker.md) form lets a person upload a document as part of a human-in-the-loop workflow. --- ## AI Agent model providers Configure the LLM model provider used by the AI Agent connectors. Both the [AI Agent Sub-process](./agentic-ai-aiagent-subprocess.md) and [AI Agent Task](./agentic-ai-aiagent-task.md) connectors use the same **Model provider** configuration. :::info This page documents the **native provider configuration** shipped with the new AI Agent element templates (Camunda 8.10+). If you are still using the legacy AI Agent element templates, see [upgrade from the legacy connector](./agentic-ai-aiagent-upgrade.md) to move to the new element templates and their native providers. ::: ## Choose a provider and backend Start from where your organization already permits LLM traffic to be routed, not from a model's native wire format. Security, data residency, procurement, networking, and audit requirements often determine which **backend** (Amazon Bedrock, Microsoft Foundry, Google Cloud, or an internal gateway) is actually available to you. Once you know which backends are approved, pick the **provider** that gives that model the most capable configuration surface: - **Provider** selects the wire format the AI Agent uses to talk to the LLM. For example, the Anthropic Messages API, or the OpenAI Responses/Chat Completions API. This determines which provider-specific capabilities are available, such as Anthropic's extended thinking or Gemini's thinking level. - **Backend** (where more than one is available for a provider) selects which infrastructure actually serves that API: the vendor's own hosted API, a hyperscaler platform that exposes a compatible endpoint, or a custom/self-hosted endpoint. These two choices are independent, so the same model family may be available through multiple backends. Within your approved backend, select the provider that matches the model's native wire format (for example, Anthropic for Claude models, even when hosted on Bedrock) rather than a generic hyperscaler provider: it gives you that provider's own configuration surface, such as reasoning/extended thinking and prompt caching, regardless of where the model is actually hosted. | If your organization requires... | Start with... | Prefer instead when... | | :------------------------------------- | :------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------- | | Traffic routed through Amazon Bedrock | [AWS Bedrock Converse](#aws-bedrock-converse) | Running **Claude** models: use [Anthropic](#anthropic)'s AWS Bedrock Mantle backend instead, to keep Anthropic-specific configuration. | | Traffic routed through Microsoft Azure | [OpenAI](#openai)'s Microsoft Foundry (Azure) backend | Running **Claude** models: use [Anthropic](#anthropic)'s Microsoft Foundry (Azure) backend instead, to keep Anthropic-specific configuration. | | Traffic routed through Google Cloud | [Google Gemini](#google-gemini)'s Enterprise Agent Platform backend | No exception for Gemini models. Use the direct Gemini API only if Google Cloud isn't mandated. | | No specific cloud mandate | The provider matching the model's native wire format | N/A | The most capable option within your organization's approved boundary is the correct choice; the native wire format alone doesn't determine it. ## Supported providers Select and configure the model **Provider** you want to use from the following supported providers. Each section below covers that provider's available backends, authentication, model settings, and provider-specific parameters: - [Anthropic](#anthropic) (Claude models, directly, via AWS Bedrock Mantle, or via Microsoft Foundry/Azure). - [AWS Bedrock Converse](#aws-bedrock-converse). - [OpenAI](#openai) (directly, via Microsoft Foundry/Azure, or via a custom OpenAI-compatible endpoint). - [Google Gemini](#google-gemini) (directly, or via Google Enterprise Agent Platform). - [Mistral AI](#mistral-ai) (directly, or via a custom Mistral-compatible endpoint). - [Custom implementation](#custom-implementation) (Self-Managed/Hybrid only). :::tip Use [connector secrets](/components/saas/clusters/manage-secrets.md) to store credentials and avoid exposing sensitive information directly in the process. ::: ### Anthropic Select this provider to use an Anthropic Claude LLM model. Choose a **Backend** to specify how to access the [Anthropic Messages API](https://docs.anthropic.com/en/api/messages): The native, hosted Anthropic API. | Field | Required | Description | | :-------------------- | :------- | :---------------------------------------------------------------------------------------------------------------------------- | | **Anthropic API key** | Yes | Your Anthropic account API key for authorization to the [Anthropic Messages API](https://docs.anthropic.com/en/api/messages). | Run Anthropic Claude models hosted on Amazon Bedrock while keeping access to Anthropic-specific configuration (reasoning/extended thinking, prompt caching) that the generic [AWS Bedrock Converse](#aws-bedrock-converse) provider doesn't expose. | Field | Required | Description | | :------------------ | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **AWS region** | Yes | The AWS region. For example, `eu-west-1`. | | **Custom endpoint** | No | Custom API endpoint for VPC/PrivateLink configurations or other non-standard deployments. Must be the full Bedrock Mantle base URL, including the `/anthropic` path segment (for example, `https://your-vpce-host/anthropic`). It replaces the default `https://bedrock-mantle..api.aws/anthropic` verbatim. | | **Authentication** | Yes | Select the authentication method used to authenticate with AWS: **Credentials** (access key/secret key), **API key**, or **Default Credentials Chain** (Hybrid/Self-Managed only). See [Amazon Bedrock connector authentication](./amazon-bedrock.md#authentication) for details on each method. | Bedrock Mantle supports a different set of models than Bedrock Runtime, and model availability also varies by AWS Region. Before selecting a model, check [Amazon Bedrock endpoint availability](https://docs.aws.amazon.com/bedrock/latest/userguide/models-endpoint-availability.html) and the linked model details for current endpoint and regional support. Use a Claude model deployed in [Microsoft Foundry](https://ai.azure.com/) (Azure AI Foundry) as the LLM for your Camunda AI agents. | Field | Required | Description | | :----------------- | :------- | :----------------------------------------------------------------------------------------------------------- | | **API endpoint** | Yes | Base URL of the Microsoft Foundry resource, for example `https://your-resource.services.ai.azure.com`. | | **Authentication** | Yes | **API key**, **Entra ID: Client credentials**, or **Entra ID: Managed identity** (Hybrid/Self-Managed only). | Authentication fields per method: - **API key**: an API key for the resource, available in the [Azure AI Foundry portal](https://ai.azure.com/). - **Entra ID: Client credentials**: registers an application in [Microsoft Entra ID](https://go.microsoft.com/fwlink/?linkid=2083908) and authenticates with it. - **Client ID**: the Microsoft Entra application (client) ID. - **Client secret**: the application's client secret. - **Tenant ID**: the Microsoft Entra tenant (directory) ID. - **Authority host**: (optional) overrides the Microsoft Entra authority host, for example for sovereign clouds. Leave unset for the public cloud authority. - **Entra ID: Managed identity** (Hybrid/Self-Managed only): authenticates using the environment's managed identity. - **Client ID**: (optional) the client ID of a user-assigned managed identity. Leave unset to use the system-assigned managed identity. :::note To use a Claude model through Foundry, deploy it first from the [Foundry model catalog](https://learn.microsoft.com/en-us/azure/ai-foundry/foundry-models/concepts/models-sold-directly-by-azure), and enter the deployment name in the **Model** field below. A multi-replica connectors runtime setup means each replica also acquires and caches its own Entra ID token independently. Expect multiple, parallel credential/token requests against Entra ID under load, rather than a single shared token, and size any Entra ID application throttling limits accordingly. ::: Any endpoint implementing the Anthropic Messages API, such as a proxy or gateway in front of Anthropic. | Field | Required | Description | | :----------------- | :------- | :------------------------------------------------------------------------------ | | **API endpoint** | Yes | Base URL of the Anthropic-compatible API. The connector appends `/v1/messages`. | | **Authentication** | Yes | **None** (default), **API key**, or **OAuth 2.0** client credentials. | Authentication fields per method: - **None**: sends no credentials, for an endpoint that doesn't require them. - **API key**: sends the configured API key with each request. - **OAuth 2.0**: requests a bearer token through the [OAuth 2.0 client credentials flow](https://www.rfc-editor.org/rfc/rfc6749#section-4.4) and sends it with each request. - **OAuth 2.0 token endpoint**: the token endpoint of the authorization server. - **Client ID**: the client ID of the OAuth client. - **Client secret**: the client secret of the OAuth client. - **Audience**: (optional) the unique identifier of the target API. Required by some authorization servers only. - **Client authentication**: whether to send the client credentials as a Basic authentication header (default) or in the request body. - **Scopes**: (optional) a space-separated list of scopes to request, for example `read:models read:deployments`. #### Anthropic model and parameters | Field | Required | Description | | :------------------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Model** | Yes | The model ID to use. See the [Claude models overview](https://docs.anthropic.com/en/docs/about-claude/models/all-models). On the Microsoft Foundry backend, enter the deployment name instead. | | **Effort** | No | Controls how many tokens the model spends when responding, trading thoroughness against speed and cost. Not supported on all models. See the [effort documentation](https://platform.claude.com/docs/en/build-with-claude/effort). | | **Thinking mode** | No | Extended thinking mechanism: `enabled` uses a manual token budget (older models), `adaptive` lets the model manage it (newer models), `disabled` turns it off. Support varies by model. | | **Thinking budget tokens** | Depends | Maximum number of tokens the model may spend on extended thinking (minimum 1024). Shown only when **Thinking mode** is `enabled`. | | **Thinking display** | No | Controls how extended thinking is returned when **Thinking mode** is `adaptive`: `summarized` includes a plain-text summary in the response, `omitted` leaves it out. | | **Enable prompt caching** | No | Enables Anthropic's automatic prompt caching. See the [prompt caching documentation](https://platform.claude.com/docs/en/build-with-claude/prompt-caching#automatic-caching). | | **Maximum tokens** | No | The maximum number of tokens per request to generate before stopping. | | **Temperature** | No | Primary response-variation control from 0 to 1. Lower values favor likely tokens more strongly; higher values increase variation. | | **top P** | No | Advanced nucleus-sampling control from 0 to 1. Limits selection to likely tokens whose cumulative probability reaches this value. | | **top K** | No | Advanced sampling control configured as a positive integer. Limits selection to this number of the most likely tokens. | | **Timeout** | No | Maximum time to wait for a model API call. See [model call timeout](#model-call-timeout). | ### AWS Bedrock Converse Select this provider to use a model provided by the [Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html) service through the generic [Converse](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html) API. :::tip This is the right choice for non-Anthropic model families available on Bedrock. For example, Amazon Nova, Meta Llama, or Mistral models hosted on Bedrock. If you're running **Anthropic Claude** models on Bedrock, use the [Anthropic provider](#anthropic)'s AWS Bedrock Mantle backend to access Anthropic-specific configuration. ::: | Field | Required | Description | | :------------------ | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **AWS region** | Yes | The AWS region. For example, `eu-west-1`. | | **Custom endpoint** | No | Custom API endpoint for VPC/PrivateLink configurations or other non-standard deployments. Overrides the default Bedrock Runtime endpoint for the region. | | **Authentication** | Yes | Select the authentication method used to authenticate with AWS: **Credentials** (access key/secret key), **API key**, or **Default Credentials Chain** (Hybrid/Self-Managed only). See [Amazon Bedrock connector authentication](./amazon-bedrock.md#authentication) for details on each method. | Model availability depends on the region and model. See [supported foundation models in Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html) and [access to Amazon Bedrock foundation models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access-modify.html). #### AWS Bedrock Converse model and parameters | Field | Required | Description | | :------------------------ | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Model** | Yes | The model ID to use. See [inference profile support](https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-support.html). | | **Enable prompt caching** | No | Enables Bedrock's automatic prompt caching. See the [prompt caching documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html). | | **Maximum tokens** | No | The maximum number of tokens per request to generate before stopping. Leave unset to use the model default. | | **Temperature** | No | Primary response-variation control. Lower values favor likely tokens more strongly; higher values increase variation. Supported ranges vary by model. | | **top P** | No | Advanced nucleus-sampling control from 0 to 1. Limits selection to likely tokens whose cumulative probability reaches this value. | | **Timeout** | No | Maximum time to wait for a model API call. See [model call timeout](#model-call-timeout). | Bedrock Converse doesn't support a **Reasoning**/**Effort** configuration or a **top K** parameter. ### OpenAI Select this provider to use OpenAI models. Two independent choices apply: - **API**: which OpenAI API family to use. **Responses** (default, recommended for new configurations) or **Chat Completions**. - **Backend**: how the API is accessed. :::tip Use **Responses** by default. It's OpenAI's current API and designed for newer reasoning models. Use **Chat Completions** if your backend doesn't support **Responses**, such as an older Microsoft Foundry/Azure OpenAI deployment or a self-hosted OpenAI-compatible backend serving models such as Qwen, Llama, or Mistral through Ollama. ::: The native, hosted OpenAI API. | Field | Required | Description | | :------------------ | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **OpenAI API key** | Yes | Your OpenAI account API key for authorization. | | **Organization ID** | No | For members of multiple organizations, the organization ID to use for API requests. See the [authentication documentation](https://platform.openai.com/docs/api-reference/authentication). | | **Project ID** | No | For accounts with multiple projects, the project ID to use for API requests. See the [authentication documentation](https://platform.openai.com/docs/api-reference/authentication). | OpenAI models deployed through [Microsoft Foundry](https://ai.azure.com/) or Azure OpenAI. | Field | Required | Description | | :----------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **API endpoint** | Yes | The full resource endpoint, for example `https://your-resource.openai.azure.com` or a Foundry endpoint such as `https://your-resource.services.ai.azure.com`. | | **Authentication** | Yes | **API key**, **Entra ID: Client credentials**, or **Entra ID: Managed identity** (Hybrid/Self-Managed only). | Authentication fields per method: - **API key**: an API key for the resource, available in the [Azure AI Foundry portal](https://ai.azure.com/). - **Entra ID: Client credentials**: registers an application in [Microsoft Entra ID](https://go.microsoft.com/fwlink/?linkid=2083908) and authenticates with it. - **Client ID**: the Microsoft Entra application (client) ID. - **Client secret**: the application's client secret. - **Tenant ID**: the Microsoft Entra tenant (directory) ID. - **Authority host**: (optional) overrides the Microsoft Entra authority host, for example for sovereign clouds. Leave unset for the public cloud authority. - **Entra ID: Managed identity** (Hybrid/Self-Managed only): authenticates using the environment's managed identity. - **Client ID**: (optional) the client ID of a user-assigned managed identity. Leave unset to use the system-assigned managed identity. :::note To use an OpenAI model deployed through Azure, deploy it first in the Azure AI Foundry portal. See [deploy a model in Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/create-resource#deploy-a-model), and enter the deployment name in the **Model** field below. A multi-replica connectors runtime setup means each replica also acquires and caches its own Entra ID token independently. Expect multiple, parallel credential/token requests against Entra ID under load, rather than a single shared token, and size any Entra ID application throttling limits accordingly. ::: Connect to any LLM that exposes an OpenAI-compatible API, including open-weight models such as Qwen, Llama, and Mistral, hosted through Ollama or any compatible inference platform. | Field | Required | Description | | :----------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------- | | **API endpoint** | Yes | Base URL of the OpenAI-compatible API. The connector appends `/chat/completions` or `/responses`, depending on the selected **API**. | | **Authentication** | Yes | **None** (default), **API key**, or **OAuth 2.0** client credentials. | Authentication fields per method: - **None**: sends no credentials, for an endpoint that doesn't require them. - **API key**: sends the configured API key with each request. - **OAuth 2.0**: requests a bearer token through the [OAuth 2.0 client credentials flow](https://www.rfc-editor.org/rfc/rfc6749#section-4.4) and sends it with each request. - **OAuth 2.0 token endpoint**: the token endpoint of the authorization server. - **Client ID**: the client ID of the OAuth client. - **Client secret**: the client secret of the OAuth client. - **Audience**: (optional) the unique identifier of the target API. Required by some authorization servers only. - **Client authentication**: whether to send the client credentials as a Basic authentication header (default) or in the request body. - **Scopes**: (optional) a space-separated list of scopes to request, for example `read:models read:deployments`. #### OpenAI model and parameters | Field | Required | Description | | :------------------------------------------------------------------------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Model** | Yes | The model ID to use. See the [OpenAI models documentation](https://platform.openai.com/docs/models). On the Microsoft Foundry backend, enter the deployment name instead. | | **Effort** | No | Controls how many tokens the model spends when responding, trading thoroughness against speed and cost. Not supported on all models. See the [Responses](https://developers.openai.com/api/reference/resources/responses/methods/create) or [Chat Completions](https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create) API reference. | | **Max output tokens** (Responses) / **Max completion tokens** (Chat Completions) | No | The maximum number of tokens per request to generate before stopping. The field name depends on the selected **API**. | | **Temperature** | No | Primary response-variation control from 0 to 2. Lower values favor likely tokens more strongly; higher values increase variation. | | **top P** | No | Advanced nucleus-sampling control from 0 to 1. Limits selection to likely tokens whose cumulative probability reaches this value. | | **Timeout** | No | Maximum time to wait for a model API call. See [model call timeout](#model-call-timeout). | OpenAI doesn't support a **top K** parameter. Prompt caching is automatic when the request meets OpenAI's caching requirements and isn't user-configurable. ### Google Gemini Select this provider to use Google's Gemini models. Choose a **Backend** to specify how to access the API: The direct, hosted Gemini API. | Field | Required | Description | | :----------------- | :------- | :----------------------------- | | **Gemini API key** | Yes | Your Google AI Studio API key. | Gemini models through Google Cloud's Enterprise Agent Platform (formerly Vertex AI). | Field | Required | Description | | :----------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Project ID** | Yes | The Google Cloud project ID. | | **Region** | Yes | The [region](https://cloud.google.com/vertex-ai/docs/general/locations#feature-availability) where AI inference should take place. | | **Authentication** | Yes | **Service account credentials** (a [service account](https://cloud.google.com/iam/docs/service-account-overview) key in JSON format), or **Application default credentials** (Hybrid/Self-Managed only; uses the default credentials available in the environment; see [setting up ADC locally](https://cloud.google.com/docs/authentication/set-up-adc-local-dev-environment)). | #### Google Gemini model and parameters | Field | Required | Description | | :--------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Model** | Yes | The model ID to use. See the [Gemini models documentation](https://ai.google.dev/gemini-api/docs/models). | | **Thinking budget (tokens)** | No | Gemini 2.5 models: token budget for extended thinking. `-1` = dynamic, `0` = disabled. Mutually exclusive with **Thinking level**. See the [thinking documentation](https://ai.google.dev/gemini-api/docs/thinking). | | **Thinking level** | No | Gemini 3.x models: qualitative thinking effort (`default`/`minimal`/`low`/`medium`/`high`). Mutually exclusive with **Thinking budget**. | | **Maximum tokens** | No | The maximum number of tokens to generate before stopping. | | **Temperature** | No | Primary response-variation control. Lower values favor likely tokens more strongly; higher values increase variation. Supported ranges vary by model. | | **top P** | No | Advanced nucleus-sampling control from 0 to 1. Limits selection to likely tokens whose cumulative probability reaches this value. | | **top K** | No | Advanced sampling control configured as a positive integer. Limits selection to this number of the most likely tokens. | | **Timeout** | No | Maximum time to wait for a model API call. See [model call timeout](#model-call-timeout). | :::note Prompt caching is automatic when the request meets Gemini's caching requirements and isn't user-configurable. ::: ### Mistral AI Select this provider to use Mistral models, such as `mistral-large-4`. :::note The Mistral AI provider is available from Camunda 8.10.5. ::: Choose a **Connection** to specify how to access the Mistral API: The native, hosted Mistral API. The endpoint is fixed to `https://api.mistral.ai/v1`. | Field | Required | Description | | :------------------ | :------- | :---------------------------------------------- | | **Mistral API key** | Yes | Your Mistral account API key for authorization. | Any endpoint implementing the Mistral chat completions API, such as a proxy or gateway in front of Mistral. | Field | Required | Description | | :----------------- | :------- | :--------------------------------------------------------------------------------- | | **API endpoint** | Yes | Base URL of the Mistral-compatible API. The connector appends `/chat/completions`. | | **Authentication** | Yes | **None** (default), **API key**, or **OAuth 2.0** client credentials. | Authentication fields per method: - **None**: sends no credentials, for an endpoint that doesn't require them. - **API key**: sends the configured API key with each request. - **OAuth 2.0**: requests a bearer token through the [OAuth 2.0 client credentials flow](https://www.rfc-editor.org/rfc/rfc6749#section-4.4) and sends it with each request. - **OAuth 2.0 token endpoint**: the token endpoint of the authorization server. - **Client ID**: the client ID of the OAuth client. - **Client secret**: the client secret of the OAuth client. - **Audience**: (optional) the unique identifier of the target API. Required by some authorization servers only. - **Client authentication**: whether to send the client credentials as a Basic authentication header (default) or in the request body. - **Scopes**: (optional) a space-separated list of scopes to request, for example `read:models read:deployments`. #### Mistral AI model and parameters | Field | Required | Description | | :-------------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Model** | Yes | The model ID to use, for example `mistral-large-4`. See the [Mistral models overview](https://docs.mistral.ai/getting-started/models/models_overview/). | | **Effort** | No | Controls how many tokens the model spends thinking before responding: **default** (model default), **none**, or **high**. Only supported on reasoning-capable models. See the [Mistral reasoning documentation](https://docs.mistral.ai/capabilities/reasoning/). | | **Max tokens** | No | The maximum number of tokens per request to generate before stopping. | | **Temperature** | No | Primary response-variation control from 0 to 1.5. Lower values favor likely tokens more strongly; higher values increase variation. | | **top P** | No | Advanced nucleus-sampling control from 0 to 1. Limits selection to likely tokens whose cumulative probability reaches this value. | | **Timeout** | No | Maximum time to wait for a model API call. See [model call timeout](#model-call-timeout). | The Mistral AI provider supports tool calling, structured output, and multimodal input (PDF documents and images). For reasoning models, the connector preserves the model's reasoning content and replays it on subsequent turns of the conversation. ### Custom implementation :::important Available in Self-Managed or [hybrid](/reference/glossary.md#hybrid-mode) deployments only. ::: Select this provider to use a custom chat model provider implementation that you've registered with the connector runtime, instead of one of the built-in providers above. | Field | Required | Description | | :---------------------- | :------- | :----------------------------------------------------------------------------------------------------------------- | | **Provider type** | Yes | Identifier for the custom chat model provider. Must match the identifier configured for the custom implementation. | | **Provider parameters** | No | Parameters for the custom chat model provider implementation, as a FEEL context. | | **Model** | Yes | Identifier of the model to use, interpreted by the custom implementation. | To implement a custom provider, register a `ChatModelFactory` bean with your Self-Managed or hybrid connector runtime. See [custom chat model provider](./agentic-ai-aiagent-customization.md#custom-chat-model-provider). ## Model call timeout Every built-in provider has an optional **Timeout** field. It sets the maximum time to wait for a model API call, in [ISO-8601 duration format](https://en.wikipedia.org/wiki/ISO_8601#Durations). For example, `PT3M`. - The default is three minutes. - The timeout must not exceed the job worker timeout. Otherwise, the job may be reassigned while a model call is still in progress. - In Self-Managed Spring connector runtime instances, you can override the default with the `camunda.connector.agenticai.aiagent.chat-model.api.default-timeout` property. ## Customize AI backend requests Most backends provide advanced, low-level fields for customizing outgoing HTTP requests: - **HTTP headers** - **Query parameters** - **Body properties** Use these fields to add or override values in the request. Whether these fields are available depends on the selected provider or backend: - For backends with a well-known REST-style API, such as the native Anthropic API, OpenAI API, Mistral API, Google Gemini, and Enterprise Agent Platform backends, these fields are reserved for internal or future use, and for use in custom element templates. They are not exposed in the properties panel. - For backends without a fixed request structure, such as AWS Bedrock Converse and custom or compatible endpoints, these fields are available as editable [FEEL](/components/modeler/feel/what-is-feel.md) map expressions. You can use them to adapt the request to your deployment. --- ## Example AI Agent Sub-process connector integration This worked example demonstrates how to use the [AI Agent Sub-process connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess.md) applied to an [ad-hoc sub-process](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md) to model an AI Agent [agent loop for tools and response interaction](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md#agent-loop-use-cases). ## Create an AI Agent element As the **AI Agent Sub-process** implementation implicitly creates an [agent loop](/reference/glossary.md#agent-loop) for tools, you only need to add an ad-hoc sub-process with an applied AI Agent connector template to the process. :::info For more information on how to model the tools available to the AI agent, see [tool definitions](./agentic-ai-aiagent-tool-definitions.md). ::: After adding the element, open the properties panel to configure the connection to your model provider, and modify the system and user prompts as required. ## Response follow-up {#response-loop} Separately from the agent loop for tools, a response follow-up acting on the agent response can be added by re-entering the AI Agent connector with new information. You must model your user prompt so that it adds the follow-up data instead of the initial request. For example, your **User Prompt** field could contain the following FEEL expression to make sure it acts upon follow-up input: ```feel =if (is defined(followUpInput)) then followUpInput else initialUserInput ``` With the **AI Agent Sub-process** implementation, the follow-up needs to be modeled to loop back to the AI Agent ad-hoc sub-process: ![AI Agent Sub-process with response follow-up](../img/ai-agent-subprocess-follow-up.png) :::note How you model this type of follow-up greatly depends on your specific use case. - The example follow-up expects a simple feedback action based on a user task, but this could also interact with other process flows or another agent process. - Instead of the user task, you could also use another LLM connector to verify the response of the AI Agent. For an example of this pattern, see the [fraud detection example](https://github.com/camunda/connectors/tree/main/connectors/agentic-ai/examples/ai-agent/ad-hoc-sub-process/fraud-detection). ::: ## Additional resources - The connectors repository contains a set of [ready-made examples](https://github.com/camunda/connectors/tree/main/connectors/agentic-ai/examples/ai-agent/ad-hoc-sub-process) using the AI Agent Sub-process connector. --- ## AI Agent Sub-process connector Implement an AI agent using an ad-hoc sub-process with an applied AI Agent connector template. ## About this implementation This connector implementation creates an implicit [agent loop](/reference/glossary.md#agent-loop) for tool calls and agent responses, using the [job worker implementation type](../../../components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md#job-worker-implementation) of an [ad-hoc sub-process](../../../components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md). :::info - For more information and usage examples, see [AI Agent Sub-process](./agentic-ai-aiagent.md#ai-agent-sub-process). - The [example integration](agentic-ai-aiagent-subprocess-example.md) outlines how to model an agentic AI process using the AI Agent Sub-process implementation. ::: ## Configuration ### Model provider Select the LLM model **Provider** and **Model**. See [model providers](./agentic-ai-aiagent-model-providers.md) for supported providers and configuration details. --- ## Example AI Agent Task connector integration This worked example demonstrates how to use the [AI Agent Task connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-task.md) and an [ad-hoc sub-process](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md) to model an AI Agent [agent loop for tools and response interaction](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md#agent-loop-use-cases). ## Create an AI Agent element First, an AI Agent connector is added and configured in the process diagram. Next, an ad-hoc sub-process is added in an [agent loop](/reference/glossary.md#agent-loop) to connect the agent to the tools it needs. :::info For more information on how to model the tools available to the AI agent, see [tool definitions](./agentic-ai-aiagent-tool-definitions.md). ::: After adding the element, open the properties panel to configure the connection to your model provider and adapt the system and user prompts as needed. It is important to align the **Agent context** field and the result variable. The following defaults should be set to ensure re-entering the process will pick up the previous context value: - **Agent context**: `agent.context` - **Result variable**: `agent` ## Agent loop for tool calls {#tools-loop} ### Configure ad-hoc sub-process and loop 1. The ad-hoc sub-process is marked as a [parallel multi-instance](../../modeler/bpmn/multi-instance/multi-instance.md). This allows the process to execute the tools in parallel, and wait for all tool calls to complete before continuing with the process. 1. A descriptive ID is configured for the ad-hoc sub-process. This can then be configured in the **Ad-hoc sub-process ID** field in the AI Agent connector [tools](agentic-ai-aiagent-task.md#tools) section. 1. The ad-hoc sub-process is marked as a tool container. Open the **Extension properties** section in the properties panel and add a property named `io.camunda.agenticai.toolContainer` with the value `true`. This enables the Modeler's agent tool configuration features, such as linting and autofill, for the tools in the sub-process. See [declare a sub-process as agentic](/components/modeler/reference/modeling-guidance/rules/agent-fromai-contract.md#declare-a-sub-process-as-agentic). 1. A loop is modeled into the sub-process and back to the AI Agent connector. - The `no` flow of the `Contains tool calls?` gateway is marked as the default flow. - The `yes` flow condition is configured to activate when the AI Agent response contains a list of tool calls. For example, if the suggested default values for the [result variable/expression](#result-variableexpression) are used, this condition could be configured as follows: ```feel not(agent.toolCalls = null) and count(agent.toolCalls) > 0 ``` The process execution routes through the ad-hoc sub-process if the LLM response requests one or more tools to be called. ### Configure multi-instance execution The ad-hoc sub-process in this example is configured as a [parallel multi-instance](../../modeler/bpmn/multi-instance/multi-instance.md) sub-process (instead of sequential multi-instance). This allows: - Tools to be called **independently of each other**, each with its own set of input parameters. This also implies that the same tool can be called **multiple times with different parameters** within the same ad-hoc sub-process execution. For example, a _Lookup user_ tool could be called multiple times with different user IDs. - The process to **wait until all requested tools have been executed** before passing the results back to the AI Agent/LLM. After all tools have been executed, results are passed back to the AI Agent connector. #### Configure properties The following properties for the ad-hoc sub-process must be configured. You can use the following suggested values as a starting point and change as required or if dealing with multiple agents within the same process. - **Input collection**: Set this to the list of tool calls your AI Agent connector returns, for example `agent.toolCalls`. - **Input element**: Contains the individual tool call, including LLM-generated input parameters based on the [tool definition](agentic-ai-aiagent-tool-definitions.md#tool-definitions). Must aways be set to `toolCall`. - **Output collection**: Collects the results of all the requested tool calls. Suggested value: `toolCallResults`. Make sure you pass this value as [Tool Call Results](agentic-ai-aiagent-task.md#tools) in the AI Agent configuration. - **Output element**: Collects the individual tool call result as returned by an individual tool (see [Tool Call Responses](agentic-ai-aiagent-tool-definitions.md#tool-call-responses)). When changing this `toolCallResult` to a different value, make sure you also change your tools to write to the updated variable name. ```feel { id: toolCall._meta.id, name: toolCall._meta.name, content: toolCallResult, completedAt: now() } ``` The `completedAt` field records when the individual tool call completed. If it is missing, the AI Agent connector uses the time at which it processes the tool call results instead, which can be later than the actual completion time for slow or parallel tool calls. As a final step, the element must be configured to activate the ad-hoc sub-process. - When using a multi-instance configuration, this is always the single task ID of the tool being executed in the individual instance. - Configure **Active elements collection** to contain the exact `[toolCall._meta.name]`. For example, the completed ad-hoc sub-process configuration would look as follows: ![AI Agent ad-hoc sub-process multi-instance configuration](../img/ai-agent-ad-hoc-sub-process-multi-instance.png) #### Configure an input mapping for the tool call result variable To prevent interference between tool calls, create an [input mapping](../../concepts/variables.md#input-mappings) for the `toolCallResult` variable. This ensures the variable is created as a local variable within the ad-hoc sub-process. 1. In the **Inputs** section of the ad-hoc sub-process properties panel, add a new entry. 2. In the **Local variable name** field, enter `toolCallResult` (or use your custom variable name if you changed it earlier). 3. Leave the **Variable assignment value** field blank. ## Response follow-up {#response-loop} Separately from the [agent loop for tool calls](#tools-loop), a response follow-up acting on the agent response can be added by re-entering the AI Agent connector with new information. You must model your user prompt so that it adds the follow-up data instead of the initial request. For example, your **User Prompt** field could contain the following FEEL expression to make sure it acts upon follow-up input: ```feel =if (is defined(followUpInput)) then followUpInput else initialUserInput ``` With the **AI Agent Task** implementation, the follow-up needs to be modeled to loop back to the AI Agent task: ![AI Agent Task with response follow-up](../img/ai-agent-task-follow-up.png) :::note How you model this type of follow-up greatly depends on your specific use case. - The example follow-up expects a simple feedback action based on a user task, but this could also interact with other process flows or another agent process. - Instead of the user task, you could also use another LLM connector to verify the response of the AI Agent. For an example of this pattern, see the [fraud detection example](https://github.com/camunda/connectors/tree/main/connectors/agentic-ai/examples/ai-agent/service-task/fraud-detection). ::: ## Additional resources - The connectors repository contains a set of [ready-made examples](https://github.com/camunda/connectors/tree/main/connectors/agentic-ai/examples/ai-agent/service-task) using the AI Agent Task connector. --- ## AI Agent Task connector Implement an AI agent using an AI Agent connector applied to a service task, paired with an optional ad-hoc sub-process to provide tools usable by the AI. :::info - For more information and usage examples, see [AI Agent Task](./agentic-ai-aiagent.md#ai-agent-task). - The [example integration](agentic-ai-aiagent-task-example.md) page outlines how to model an agentic AI process using the AI Agent Task implementation. ::: ## Configuration ### Model provider Select the LLM model **Provider** and **Model**. See [model providers](./agentic-ai-aiagent-model-providers.md) for supported providers and configuration details. ## Limitations ### No event handling support Unlike the AI Agent Sub-process implementation, the AI Agent Task implementation does not support event handling as part of an [event subprocess](../../../components/modeler/bpmn/event-subprocesses/event-subprocesses.md). If you want to handle events while the AI agent is working on a task, use the [AI Agent Sub-process](./agentic-ai-aiagent-subprocess.md) implementation instead. ### Process definition not found errors when running the AI Agent for the first time The AI Agent Task implementation relies on the eventually consistent [Get process definition XML API](../../../apis-tools/orchestration-cluster-api-rest/specifications/get-process-definition-xml.api.mdx) to fetch the BPMN XML source when resolving available tool definitions. - If you deploy a new or changed process and directly run it after (for example using **Deploy & Run**), the process definition might not be available when the AI Agent attempts to fetch the process definition XML. - It will retry to fetch the definition several times, but if the definition is still not available after the retries are exhausted, the connector will fail with a "Process definition not found" error and raise an incident. To avoid this error, wait a few seconds before running a newly deployed new or changed process, to allow the exporter to make the process definition available via the API. --- ## AI agent tool definitions Understand what an AI agent tool is, how tools are defined, and how their names, descriptions, and parameters are resolved for the LLM. ## What is a tool A tool is a single BPMN element, or a flow of BPMN elements, inside an [ad-hoc sub-process](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md) that an [LLM](/reference/glossary.md#large-language-model-llm) can choose to invoke to complete a goal. Each tool has: - A **name**: the element ID, used by the LLM to identify the tool. - A **description**: the element's **Documentation** field, used by the LLM to decide when to call the tool. - **Input parameters**: values the LLM must supply at call time, declared using the [`fromAi()`](../../modeler/feel/builtin-functions/feel-built-in-functions-ai-agent.md#fromaivalue) FEEL function in input mappings. - A **result**: the tool's output, returned to the LLM as `toolCallResult`. Camunda Hub and Desktop Modeler can help you fill in both the input parameters and the result. See [assisted tool configuration](#assisted-tool-configuration) for more details. ### Which elements are resolved as tools When resolving the available tools within an ad-hoc sub-process, the AI agent will take all elements into account which **have no incoming flows** (root nodes within the ad-hoc sub-process) and **are not boundary events**. For example, in the following image the elements marked in green are the ones that will be considered as tools: ![AI Agent tool resolution](../img/ai-agent-tool-resolution.png) You can use any BPMN element or connector as a tool: | Tool type | When to use | | :------------------- | :---------------------------------------------------------------------------------------------------- | | Connectors | Call an external system, for example the [REST connector](../protocol/rest.md) to call an HTTP API. | | Script task | Execute inline logic or data transformation. | | User task | Route to a human for input or approval as part of the agent's decision path. | | Call activity | Invoke another BPMN process as a tool when the target process is on the same cluster. | | MCP client connector | Expose tools from an external [MCP server](./agentic-ai-mcp-client.md) as gateway tools to the agent. | | Sub-process | Model a multi-step sub-flow that the LLM triggers as a single tool. | :::note For instructions on adding tools to an AI agent, see [add tools to an AI agent](/components/agentic-orchestration/add-tool-to-ai-agent.md). ::: ## Tool resolution To resolve available tools, the AI Agent connector either resolves the tools by reling on data provided by the Zeebe engine or reads the BPMN model directly. The approach depends on the chosen AI Agent implementation: When using the **AI Agent Sub-process** implementation, the connector relies on data provided by the [ad-hoc sub-process](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md#special-ad-hoc-sub-process-variables) implementation to resolve the tools. When using the **AI Agent Task** implementation, the connector reads the BPMN model directly to resolve the tools: 1. It reads the BPMN model and looks up the ad-hoc sub-process using the configured ID. If not found, the connector throws an error. 2. Iterates over all activities within the ad-hoc sub-process and checks that they are root nodes (no incoming flows) and not boundary events. 3. For each activity found, analyzes the input mappings and looks for the [`fromAi`](../../modeler/feel/builtin-functions/feel-built-in-functions-ai-agent.md#fromaivalue) function calls that define the parameters that need to be provided by the LLM. 4. Creates a tool definition for each activity found, and passes these tool definitions to the LLM as part of the prompt. :::note Refer to the [Anthropic](https://docs.anthropic.com/en/docs/build-with-claude/tool-use/overview) and [OpenAI](https://platform.openai.com/docs/guides/function-calling) documentation for examples of how tool/function calling works in combination with an LLM. ::: ## Tool definitions :::important The AI Agent connector only considers the **root node** of the sub-flow when resolving a tool definition. ::: A tool definition consists of the following properties which will be passed to the LLM. The tool definition is closely modeled after the [list tools response](https://modelcontextprotocol.io/specification/2025-03-26/server/tools#listing-tools) as defined in the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/). | Property | Description | | :---------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | name | The name of the tool. This is the **ID of the activity** in the ad-hoc sub-process. | | description | The description of the tool, used to inform the LLM of the tool purpose. If the **documentation** of the activity is set, this is used as the description, otherwise the **name** of the activity is used. Make sure you provide a meaningful description to help the LLM understand the purpose of the tool. | | inputSchema | The input schema of the tool, describing the input parameters of the tool. The connector will analyze all input mappings of the activity and create a [JSON Schema](https://json-schema.org/) based on the [`fromAi`](../../modeler/feel/builtin-functions/feel-built-in-functions-ai-agent.md#fromaivalue) function calls defined in these mappings. If no `fromAi` function calls are found, an empty JSON Schema object is returned. | :::note Provide as much context and guidance in tool definitions and input parameter definitions as you can to ensure the LLM selects the right tool and generates proper input values. Refer to the [Anthropic documentation](https://docs.anthropic.com/en/docs/build-with-claude/tool-use/implement-tool-use#example-of-a-good-tool-description) for tool definition best practices. ::: ### AI-generated parameters via `fromAi` Within an activity, you can define parameters which should be AI-generated by tagging them with the [`fromAi`](../../modeler/feel/builtin-functions/feel-built-in-functions-ai-agent.md#fromaivalue) [AI agent function](/reference/glossary.md#ai-agent-function) in input mappings. The function itself does not implement any logic (it simply returns the first argument it receives), but provides a way to configure all the necessary metadata (for example, description, type) to generate an input schema definition. The tools schema resolution will collect all `fromAi` definitions within an activity and combine them into an input schema for the activity. :::important The first argument passed to the `fromAi` function must be a reference to a field within the `toolCall` context which will automatically be populated by the AI Agent connector. Example: `toolCall.myParameter`. ::: By using the `fromAi` tool call as a wrapper function around the actual value, the connector can both **describe the parameter** for the LLM by generating a JSON Schema from the function calls and at the same time **utilize the LLM-generated value** as it can do with any other process variable. You can use the `fromAi` function in: - Input mappings (for example, service task, script task, user task). - Custom input fields provided by an element template if an element template is applied to the activity as technically these are handled as input mappings. :::note `fromAi` only applies to activities resolved as **static tool definitions**. [Gateway tool definitions](#gateway-tool-definitions), such as MCP Client or A2A activities, determine their input schemas dynamically from the external source they connect to. As a result, `fromAi` calls in their input mappings have no effect and are ignored. ::: For example, the following image shows an example of `fromAi` function usage on a [REST outbound connector](../protocol/rest.md): ![AI Agent fromAi tool resolution](../img/ai-agent-tool-resolution-fromAi.png) #### `fromAi` examples The [`fromAi`](../../modeler/feel/builtin-functions/feel-built-in-functions-ai-agent.md#fromaivalue) FEEL function can be called with a varying number of parameters to define simple or complex inputs. The simplest form is to just pass a value. ```feel fromAi(toolCall.url) ``` This makes the LLM aware that it needs to provide a value for the `url` parameter. As the first value to `fromAi` needs to be a variable reference, the last segment of the reference is used as parameter name (`url` in this case). To make an LLM understand the purpose of the input, you can add a description: ```feel fromAi(toolCall.url, "Fetches the contents of a given URL. Only accepts valid RFC 3986/RFC 7230 HTTP(s) URLs.") ``` To define the type of the input, you can add a type (if no type is given, it will default to `string`): ```feel fromAi(toolCall.firstNumber, "The first number.", "number") fromAi(toolCall.shouldCalculate, "Defines if the calculation should be executed.", "boolean") ``` For more complex type definitions, the fourth parameter of the function allows you to specify a JSON Schema from a FEEL context. Note that support for complex JSON Schema features may be limited by the selected provider/model. For a list of examples, refer to the [JSON Schema documentation](https://json-schema.org/learn/miscellaneous-examples). ```feel fromAi( toolCall.myComplexObject, "A complex object", "string", { enum: ["first", "second"] } ) ``` To mark a parameter as optional, you can use the `options` parameter: ```feel fromAi( toolCall.optionalParameter, "An optional parameter", "string", null, { required: false } ) ``` Or using named parameters: ```feel fromAi( value: toolCall.optionalParameter, description: "An optional parameter", options: { required: false } ) ``` You can combine multiple parameters within the same FEEL expression, for example: ```feel fromAi(toolCall.firstNumber, "The first number.", "number") + fromAi(toolCall.secondNumber, "The second number.", "number") ``` For more examples, refer to the [`fromAi`](../../modeler/feel/builtin-functions/feel-built-in-functions-ai-agent.md#fromaivalue) documentation. You can [autofill a starter `fromAi()` call](#autofill-a-fromai-input) into a blank input. ## Message catch events as tools You can use an intermediate message catch event inside an ad-hoc sub-process as a tool. For example, to model a "wait for reply" step where the agent sends a message to an external system and waits for a response before continuing. When using this pattern, each process instance opens a message subscription. If multiple instances run concurrently, and they all subscribe with the same message name and correlation key, Zeebe delivers the reply to one instance non-deterministically. The wrong instance may receive the reply, and there is no warning when this happens. To avoid this, use a [unique correlation key per interaction](../../concepts/messages.md#request-reply-with-unique-correlation-key). ## Tool call responses To collect the output of the called tool and pass it back to the agent, the task within the ad-hoc sub-process needs to set its output to a predefined variable name. For the **AI Agent Sub-process** implementation, this variable is predefined as `toolCallResult`. For the **AI Agent Task** implementation, the variable depends on the configuration of the [multi-instance execution](#tools-loop), but is also typically named `toolCallResult`. Depending on the used task, setting the variable content can be achieved in multiple ways: - A [result variable](../use-connectors/index.md#result-variable) or a [result expression](../use-connectors/index.md#result-expression) containing a `toolCallResult` key - An [output mapping](../../concepts/variables.md#output-mappings) creating the `toolCallResult` variable or adding to a part of the `toolCallResult` variable (for example, an output mapping could be set to `toolCallResult.statusCode`) - A [script task](../../modeler/bpmn/script-tasks/script-tasks.md) that sets the `toolCallResult` variable If a tool consists of multiple elements (for example, a sequence of tasks with a gateway), `toolCallResult` can be set at any point in the flow. The variable is [read from the tool's scope when the flow completes](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md#collect-output). Tool call results can be either primitive values (for example, a string) or complex ones, such as a [FEEL context](../../modeler/feel/language-guide/feel-context-expressions.md) that is serialized to a JSON string before passing it to the LLM. As most LLMs expect _some_ form of response to a tool call, the AI Agent will return a constant string indicating that the tool was executed successfully without returning a result to the LLM if the `toolCallResult` variable is not set or empty after executing the tool. You can [autofill the `toolCallResult` output](#autofill-a-toolcallresult-output). ### Document support Tool call responses can contain [Camunda document references](/self-managed/concepts/document-handling/overview.md) nested anywhere within the result structure. The agent extracts these documents and passes them to the LLM as native content blocks. For supported file types and details on how documents are resolved, see [document support](./agentic-ai-aiagent-documents.md). ## Assisted tool configuration In the properties panel, the modeler helps you fill in both parts of the tool contract: [`fromAi()`](#ai-generated-parameters-via-fromai) inputs and the [`toolCallResult`](#tool-call-responses) output. This assistance appears only inside an ad-hoc sub-process marked as agentic through either the `io.camunda.agenticai.toolContainer` property or an out-of-the-box AI Agent element template. It does not appear in a plain sub-process. Two affordances appear, and they apply to different fields: | Affordance | Where it appears | What it does | | :----------- | :------------------------------------ | :------------------------------------------------------------------------ | | **Autofill** | On a blank field only | Seeds a correctly structured value. It never overwrites an existing value | | **Fix** | On a field that already holds a value | Rewrites only the invalid part of that value and keeps the rest | **Autofill** disappears as soon as a field holds any value, so it can never replace something you entered. **Fix** edits only the part it identifies as invalid, so it preserves the rest of the expression. Both actions are undoable. ### Autofill a `fromAi()` input On a tool's root node (the activity with no incoming flows), an autofill icon appears for a blank input mapping or blank FEEL-capable element-template field. Select the icon to add a correctly structured call: ```feel =fromAi(toolCall.parameterName, "Description of the parameter", "string") ``` Replace the placeholder key and description with values for your tool. The autofill icon appears only on a blank field, so it never replaces a value you entered. The modeler derives the key from the field's target and infers the type argument from the field's description or name. This inferred type is a heuristic, not a guaranteed match for the target's real shape, so check it when a tool call fails with a type mismatch. ### Autofill a `toolCallResult` output If a tool-flow element does not yet produce a contract-readable result, you can use autofill to write `toolCallResult` to the element's native result field: | Element type | Field written | | :----------- | :----------------------------------------------------------- | | Connector | The connector result expression, as `={toolCallResult: ...}` | | Script task | The script result variable | | Other tasks | An output mapping targeting `toolCallResult` | For a multi-instance tool, autofill also sets the output collection and output element so the agent collects a result for every iteration instead of returning `null`. ### Fix an invalid key When a `fromAi()` key or an output key is invalid, the modeler detects it locally by re-parsing the element's own fields and offers a **Fix** button. No modeling-guidance report is involved. The modeler offers **Fix** for: - A `fromAi()` key with a missing `toolCall.` prefix, bracket notation, a quoted string, or an over-long path. - A `fromAi` function name with incorrect casing, since only the exact name is recognized. - A description that is not a string literal. - An output key that is a near-miss of `toolCallResult`, such as `toolcallresult`. - A `fromAi()` key that cannot be recovered as written, such as a missing or numeric key. The modeler fills in a key derived from the field's own target. In every case, **Fix** rewrites only the invalid key or the `fromAi(...)` span and leaves the rest of the field intact. A field such as `=concat("prefix-", fromAi(...), "-suffix")` keeps both surrounding literals, and correcting an invalid key preserves any description and type arguments you entered. The button is always labeled **Fix**, so hover it to see the specific change it makes before you select it. If a field's expression cannot be parsed at all, the modeler cannot identify what to correct and offers no fix. Repair the expression yourself. If a `fromAi()` call is on an element other than the tool's root node, the AI Agent connector cannot resolve it. The modeler offers a move action, labeled with the root node's name, that declares the input on the root node and rewrites only the `fromAi(...)` span on the original field. This assistance complements the agent [modeling-guidance rules](/components/modeler/reference/modeling-guidance/rules/agent-fromai-contract.md), which flag the same contract problems. The rules report what is wrong, assisted configuration offers to fix it. ## Gateway tool definitions Gateway tools are activities that expose multiple tools from an external source, such as an MCP server or an A2A agent. Unlike static tool definitions, gateway tools discover their available tools dynamically during agent initialization by calling the external source. To configure an activity as a gateway tool, set the [extension property](../../modeler/element-templates/defining-templates.md#zeebeproperty) `io.camunda.agenticai.gateway.type` on the activity. The property value specifies which gateway implementation to use (for example, `mcpClient`). The agent must also have access to a handler for the specified gateway type. Custom implementations can be made available to the agent in self-managed or hybrid setups. Because the tools behind a gateway activity are discovered dynamically rather than resolved from the BPMN model, their input schemas are not derived from [`fromAi`](#ai-generated-parameters-via-fromai) function calls in the gateway activity's input mappings. Instead, each discovered tool's input schema is provided by the external source itself, for example, the input schema included in the `tools/list` response returned by an MCP server. Any `fromAi` calls configured on a gateway tool activity's input mappings are ignored. For more details, see the available gateway tool implementations: - [MCP Client connectors](./agentic-ai-mcp-client.md) - [A2A Client connector](/components/early-access/alpha/a2a-client/a2a-client.md) --- ## Upgrade AI Agent element templates Upgrade AI Agent connectors from the legacy element templates to the new element templates, and migrate their model provider configurations. Use c8ctl to do most of the upgrade automatically, or upgrade each element manually in Modeler. ## Legacy and new element templates Camunda 8.10 provides two generations of AI Agent element templates, and each generation runs on its own job types. This page uses the following terms: - **Legacy element templates** are the AI Agent Task and AI Agent Sub-process templates available before Camunda 8.10. Modeler marks them as **(Deprecated)**. They run on the legacy job types. - **New element templates** are the AI Agent Task and AI Agent Sub-process templates introduced in Camunda 8.10 (template ID suffix `.v2`). They run on new job types. | Element | Template generation | Element template ID | Job type | | :------------------- | :------------------ | :------------------------------------------------------- | :------------------------------------------ | | AI Agent Task | Legacy | `io.camunda.connectors.agenticai.aiagent.v1` | `io.camunda.agenticai:aiagent:1` | | AI Agent Task | New | `io.camunda.connectors.agenticai.ai-agent-task.v2` | `io.camunda.agenticai:aiagent:task:2` | | AI Agent Sub-process | Legacy | `io.camunda.connectors.agenticai.aiagent.jobworker.v1` | `io.camunda.agenticai:aiagent-job-worker:1` | | AI Agent Sub-process | New | `io.camunda.connectors.agenticai.ai-agent-subprocess.v2` | `io.camunda.agenticai:aiagent:subprocess:2` | The job type in a process definition determines which connector implementation executes the element. A process definition that was deployed with a legacy template keeps using the legacy job type until you deploy a new version with the new template. ## Why upgrade Starting with Camunda 8.10, new element templates are available for the [AI Agent Task](./agentic-ai-aiagent-task.md) and [AI Agent Sub-process](./agentic-ai-aiagent-subprocess.md) connectors. These templates broaden the ways you can connect AI agents to LLMs. You can select from more model providers and backends to use an LLM route that meets your organization's requirements. The new templates also expose provider-specific capabilities that can support cheaper, faster, and more transparent agent behavior: - Reasoning and extended thinking configuration (Anthropic's **Effort**/**Thinking mode**, OpenAI's **Effort**, and Google Gemini's **Thinking budget**/**Thinking level**). - Prompt caching configuration (Anthropic, AWS Bedrock Converse). - New backend options: [AWS Bedrock Mantle](./agentic-ai-aiagent-model-providers.md#anthropic) for Anthropic Claude models and [Google Gemini API](./agentic-ai-aiagent-model-providers.md#google-gemini) for direct Gemini access. Microsoft Foundry was already available as **Azure OpenAI** in the legacy templates. It is now a backend of both the OpenAI and Anthropic providers, rather than its own top-level provider. Your AI agents can therefore also use Claude models served by Foundry. - A [custom chat model provider](./agentic-ai-aiagent-model-providers.md#custom-implementation) option, for Self-Managed/hybrid deployments. The legacy element templates keep working, and existing implementations don't need to migrate immediately. However, they don't expose the new provider and backend choices or provider-specific configuration described above. Apply a new element template to use these capabilities. :::important Camunda deprecates the legacy element templates in 8.10. They will not receive new provider capabilities. They keep working. New AI Agent implementations should use the new element templates directly. ::: ## How to upgrade The legacy and new element templates are separate templates, not two versions of the same template. You can upgrade an element in one of two ways: - [Upgrade with c8ctl](#upgrade-with-c8ctl) (recommended). c8ctl moves the values of all legacy AI Agent elements in a BPMN file to the new template and reports each change. - [Upgrade manually in Modeler](#upgrade-manually-in-modeler). You apply the new template to each element and enter the values again. After the upgrade, [complete the upgrade](#complete-the-upgrade) for both ways. ### Upgrade with c8ctl Use the `c8ctl element-template update` command to move legacy AI Agent elements to the new element templates. The new templates contain a migration recipe. The recipe tells c8ctl how each legacy field maps to the new fields, including the model provider configuration. Prerequisites: - [c8ctl](/apis-tools/c8ctl/getting-started.md#install) 4.6.1 or later. Earlier versions do not sync the migration recipe that the new templates contain. - The BPMN file sets the execution platform version to Camunda 8.10 or later. c8ctl does not change this version. Set it in Modeler or in the `modeler:executionPlatformVersion` attribute of the BPMN file. Each legacy template has one new template as its target. Use the element template IDs from the [table of legacy and new element templates](#legacy-and-new-element-templates). For a hybrid template, add the `-hybrid` suffix to both IDs. For example, upgrade `io.camunda.connectors.agenticai.aiagent.v1-hybrid` to `io.camunda.connectors.agenticai.ai-agent-task.v2-hybrid`. The `update` command selects elements by their legacy template ID: | Command | Elements that c8ctl updates | | :----------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------- | | `update process.bpmn --to ` | All elements in the file that use ``, in any version. The elements of other templates do not change. | | `update process.bpmn --to ` | Only the element with the ID ``. The command fails if this element does not use ``. | One command accepts one legacy template ID only. If a file contains AI Agent Sub-process elements and AI Agent Task elements, run the command one time for each legacy template ID. To upgrade all legacy AI Agent elements in a BPMN file: 1. Refresh the local element template cache, so c8ctl uses the newest templates and their recipes: ```shell c8ctl element-template sync ``` 2. Preview the upgrade. The `--dry-run` option prints a report and does not change the file: ```shell c8ctl element-template update io.camunda.connectors.agenticai.aiagent.jobworker.v1 process.bpmn \ --to io.camunda.connectors.agenticai.ai-agent-subprocess.v2 --dry-run ``` The migration recipe accepts only elements that use legacy template version 12 or later. If an element uses an older version, c8ctl first updates the element to legacy template version 12. Then c8ctl applies the recipe. The report shows this step. The example that follows shows the report for an AI Agent Sub-process element. The element uses legacy template version 7 and the Azure OpenAI provider: ```text AI Agent (AI_Agent) AI Agent Sub-process v7 → AI Agent Sub-process v1 io.camunda.connectors.agenticai.aiagent.jobworker.v1@7 → io.camunda.connectors.agenticai.ai-agent-subprocess.v2@1 recipe: embedded in io.camunda.connectors.agenticai.ai-agent-subprocess.v2 The element is on v7, which is too old for this recipe. Updating it to v12 first. Model provider ~ Provider Azure OpenAI → OpenAI [openai] set by recipe + API Chat Completions [completions] set by recipe + Backend Microsoft Foundry (Azure) [foundry] set by recipe > API endpoint https://your-resource.openai.azure.com was Endpoint > Authentication Entra ID: Client credentials > Client ID your-client-id > Client secret [REDACTED] > Tenant ID your-tenant-id keys renamed: provider.azureOpenAi.authentication.* → provider.openai.backend.foundry.authentication.* (4) Model > Model gpt-6-sol was Model deployment name + Effort default [modelDefault] template default Model options > Max completion tokens =4096 was Model › Maximum tokens > Temperature =1 was Model › Temperature > top P =1 was Model › top P ✅ 13 updated, 16 kept unchanged, 0 lost 1 element updated. Dry run: nothing was written. ``` :::note Look for moved values (marked `>`) and values that the recipe sets (marked `set by recipe`) in the report. If the report shows neither, c8ctl did not use the migration recipe. c8ctl then copies only the values that have the same key. The report gives the reason for each element. A common cause is that your local template cache does not contain the newest template version with the migration recipe. In this case, run `c8ctl element-template sync`. Then do the dry run again. ::: 3. Read the report for each element. The report shows each added, changed, moved, and dropped value. It also shows the notes of the recipe, for example when the recipe turns on prompt caching. Compare the report with the **With c8ctl** notes in the [model provider configuration mapping](#model-provider-configuration-mapping). :::tip If an AI agent or a script reads the report, add the `--json` option to get machine-readable output. ::: 4. Write the upgrade to the file: ```shell c8ctl element-template update io.camunda.connectors.agenticai.aiagent.jobworker.v1 process.bpmn \ --to io.camunda.connectors.agenticai.ai-agent-subprocess.v2 --in-place ``` The command upgrades all elements that use the legacy template ID. c8ctl writes the file only if all these elements migrate without data loss. If the report lists dropped values, set them manually after the upgrade, or add the `--allow-lossy` option to accept the loss. Use `--allow-lossy` only after you read the dropped values in the report. 5. If the file also contains elements of the other legacy template, do steps 2 to 4 again. Use that legacy template ID and its new template ID from the [table of legacy and new element templates](#legacy-and-new-element-templates). For example, use `io.camunda.connectors.agenticai.aiagent.v1` with `--to io.camunda.connectors.agenticai.ai-agent-task.v2`. If c8ctl cannot use the recipe for an element, the report gives the reason. c8ctl then copies only the values with the same key, and the write requires `--allow-lossy`. Run `c8ctl element-template update --help` to see all options. #### Stay on the legacy templates If you are not ready for the new templates, you can keep your elements on the legacy templates. To update the elements to the newest legacy template version, refresh the template cache as in step 1. Then run the `update` command without the `--to` option: ```shell c8ctl element-template update io.camunda.connectors.agenticai.aiagent.jobworker.v1 process.bpmn --in-place ``` c8ctl moves each element that uses the legacy template ID to the newest compatible legacy template version. The execution platform version of the BPMN file sets which versions are compatible. The elements keep the legacy job type. The report shows that the template is deprecated. The report also gives the new template as the target for a later upgrade. ### Upgrade manually in Modeler :::important The new templates require Desktop Modeler 5.51.0 or later. Earlier versions show them as **Not found**. ::: To upgrade an element manually, apply the new template and enter its values again: 1. Open the AI Agent Task or AI Agent Sub-process element in [Camunda Hub](/components/hub/workspace/modeler/index.md) or [Desktop Modeler](/components/modeler/desktop-modeler/index.md). Set the process' modeler/execution version to Camunda 8.10 or later. This makes the new element template available to select. 2. Select the element in the diagram. Choose **Change element**, then apply the new **AI Agent Task** or **AI Agent Sub-process** template. Camunda deprecates the legacy template. The template picker offers the new template for that element. 3. Re-enter the model provider configuration with the [mapping tables](#model-provider-configuration-mapping) below. The provider fields require the most migration work. 4. Review the rest of the element's configuration. Tools, memory, limits, response, and error handling are conceptually unchanged, but re-check any values that need to be re-entered after you apply the new template. ### Complete the upgrade Do these steps after you upgrade with c8ctl or manually: 1. If you upgrade an AI Agent Task, [update its tools ad-hoc sub-process](#ai-agent-task-tools-sub-process). The new AI Agent Task template and c8ctl do not change this sub-process. 2. Deploy the new process definition version to a non-production environment first. Test a representative prompt and tool-call path. Make sure authentication, endpoint, and model behavior are correct before you promote it. See [testing process definitions](/components/best-practices/development/testing-process-definitions.md) for a test approach. The prior version keeps running until you deploy this one. It remains available as a rollback path. 3. Once verified, promote the new version to production through your normal release process. :::important Swapping the element template affects only the process definition you redeploy. Already-deployed process definitions and their running process instances keep executing on the legacy job worker. They switch only after you deploy a new version with the new template. ::: ## Other changes in the new templates ### Agent definition The new templates mark the element as an agent with the `zeebe:agentDefinition` extension element, so Camunda recognizes it as an [agent definition](/components/agentic-orchestration/agent-definitions-and-instances.md). The template writes this element when you apply it, and you don't need to configure it. ### Job type overrides If you [override the AI Agent job types](./agentic-ai-aiagent-customization.md) with environment variables, for example in a hybrid setup, the legacy variables don't apply to the new job types. The legacy and new job workers are registered separately, so each variable only affects its own job worker. Set the new variables to use your custom type with the new templates: | Element | Legacy variable | New variable | | :------------------- | :----------------------------------- | :----------------------------------- | | AI Agent Task | `CONNECTOR_AI_AGENT_TYPE` | `CONNECTOR_AI_AGENT_TASK_TYPE` | | AI Agent Sub-process | `CONNECTOR_AI_AGENT_JOB_WORKER_TYPE` | `CONNECTOR_AI_AGENT_SUBPROCESS_TYPE` | The element template must reference the same custom job type. Keep the legacy variables set for as long as process definitions that use the legacy templates are deployed or running. ### AI Agent Task: tools sub-process {#ai-agent-task-tools-sub-process} The AI Agent Sub-process template configures its own ad-hoc sub-process. The AI Agent Task is a service task that references a separate ad-hoc sub-process containing the tools, and the new AI Agent Task template does not manage that sub-process. After you apply the new template to an AI Agent Task, update the tools sub-process manually: 1. Mark the ad-hoc sub-process as a tool container. Open the **Extension properties** section of the sub-process and add a property named `io.camunda.agenticai.toolContainer` with the value `true`. See [declare a sub-process as agentic](/components/modeler/reference/modeling-guidance/rules/agent-fromai-contract.md#declare-a-sub-process-as-agentic). Without this property, the Modeler's agent tool configuration features, such as linting and autofill, are not available for the sub-process. 2. Add `completedAt: now()` to the **Output element** of the sub-process' multi-instance configuration: ```feel { id: toolCall._meta.id, name: toolCall._meta.name, content: toolCallResult, completedAt: now() } ``` The agent uses `completedAt` as the time the tool call completed. If it is missing, the AI Agent connector uses the time at which it processes the tool call results instead, which can be later than the actual completion time for slow or parallel tool calls. ## Update custom extensions {#custom-extensions} If you extend the AI Agent connector in a Self-Managed or hybrid deployment, for example with a custom conversation store or chat model, the Java APIs also changed in Camunda 8.10. See the [breaking changes for custom extensions](https://github.com/camunda/connectors/blob/main/connectors/agentic-ai/docs/breaking-changes.md). ## Model provider configuration mapping Model provider configuration changed the most in this redesign, since providers and backends are now decoupled (see [choose a provider and backend](./agentic-ai-aiagent-model-providers.md#choose-a-provider-and-backend)). The sections below cover only the fields that changed, comparing legacy and new template fields. Any fields not mentioned carry over unchanged under the same field label. Each section ends with a **With c8ctl** note. The note tells you which values the c8ctl migration recipe sets and which steps you must still do manually. ### Anthropic **Legacy template Provider**: Anthropic → **New template Provider**: [Anthropic](./agentic-ai-aiagent-model-providers.md#anthropic), **Backend**: Anthropic API. **Anthropic API key**, **Timeout**, **Model**, **Maximum tokens**, **Temperature**, **top P**, and **top K** carry over unchanged. If you had a custom **Endpoint** configured in the legacy template: | Legacy field | New template guidance | | :--------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Endpoint on Microsoft Foundry (`*.azure.com`, `*.azure.us`, or `*.azure.cn`) | Select **Backend**: [Anthropic](./agentic-ai-aiagent-model-providers.md#anthropic) > Microsoft Foundry (Azure), and enter it as **API endpoint**. Use **API key** authentication with your legacy **Anthropic API key**. Remove a trailing `/anthropic/v1` or `/v1` from the endpoint. The new template adds `/anthropic` if it is missing and appends `/v1/messages`. | | Any other endpoint | Select **Backend**: [Anthropic](./agentic-ai-aiagent-model-providers.md#anthropic) > Custom / compatible endpoint, and enter it as **API endpoint**. Remove a trailing `/v1` from the endpoint. The new template appends `/v1/messages`. | The custom or compatible backend also supports **OAuth 2.0 client credentials** for gateways in front of Anthropic that require a bearer token. The new template additionally exposes **Effort**, **Thinking mode**, and **Enable prompt caching**. None of these have a legacy equivalent. **With c8ctl**: The recipe selects the backend from the legacy endpoint, as the table shows. It copies the endpoint unchanged, so remove a trailing path segment manually. The recipe also turns on **Enable prompt caching**. **Effort** and **Thinking mode** use the model default. ### AWS Bedrock The legacy **AWS Bedrock Converse** provider was also commonly used to run **Anthropic Claude** models. In the new template, decide which provider matches your use case: - **Running Claude models**: Migrate to the new template's **Anthropic** provider and **AWS Bedrock Mantle** backend. This keeps access to Anthropic-specific configuration, such as reasoning and prompt caching. See the [Anthropic provider](./agentic-ai-aiagent-model-providers.md#anthropic) above. - **Running any other model family** (Amazon Nova, Meta Llama, Mistral, and so on): Migrate to the new template's **AWS Bedrock Converse** provider. It directly replaces the legacy provider. #### Migrating to Anthropic + AWS Bedrock Mantle **Authentication**, **Timeout**, **Maximum tokens**, **Temperature**, and **top P** carry over unchanged. | Legacy field | New template field | | :----------- | :----------------- | | Region | AWS region | | Endpoint | Custom endpoint | :::important **Custom endpoint** expects the full Bedrock Mantle base URL, including the `/anthropic` path segment (for example, `https://your-vpce-host/anthropic`). This is a different shape than the Bedrock Runtime endpoint you may have configured in the legacy template. **Model** carries over under the same field label. The new template uses Anthropic's model ID scheme, not the AWS Bedrock model ID format from the legacy template. Check the model ID against the [Claude models overview](https://docs.anthropic.com/en/docs/about-claude/models/all-models). Bedrock Mantle requires a different IAM permission policy than Bedrock Runtime. Do not reuse the legacy Bedrock Runtime policy unchanged. Update the policy for the new endpoint before you migrate. Otherwise, the first model call returns an authentication or permission error. ::: #### Migrating to AWS Bedrock Converse **Authentication**, **Timeout**, **Model**, **Maximum tokens**, **Temperature**, and **top P** carry over unchanged. | Legacy field | New template field | | :----------- | :----------------- | | Region | AWS region | | Endpoint | Custom endpoint | The new template additionally exposes **Enable prompt caching** on the AWS Bedrock Converse provider. **With c8ctl**: The recipe keeps all elements on the **AWS Bedrock Converse** provider and turns on **Enable prompt caching**. To run Claude models on **Anthropic** with the **AWS Bedrock Mantle** backend, change the provider manually after the upgrade. See [Migrating to Anthropic + AWS Bedrock Mantle](#migrating-to-anthropic--aws-bedrock-mantle). ### Azure OpenAI **Legacy template Provider**: Azure OpenAI → **New template Provider**: [OpenAI](./agentic-ai-aiagent-model-providers.md#openai), **Backend**: Microsoft Foundry (Azure). The provider itself changes from **Azure OpenAI** to **OpenAI**. Azure/Microsoft Foundry is now a backend of the general-purpose OpenAI provider rather than its own top-level provider. **Authentication: API key**, **Timeout**, **Temperature**, and **top P** carry over unchanged. | Legacy field | New template field | | :--------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------- | | Endpoint | API endpoint | | Authentication: Client credentials (Client ID, Client secret, Tenant ID, Authority host) | Authentication: Entra ID: Client credentials (Client ID, Client secret, Tenant ID, Authority host) | | Model deployment name | Model | | Maximum tokens | Max output tokens (Responses API) or Max completion tokens (Chat Completions API) | The new template additionally offers an **Entra ID: Managed identity** authentication option (Hybrid/Self-Managed only), an optional **Entra ID scope** override, and the **Effort** reasoning parameter. :::note A multi-replica connectors runtime setup means each replica acquires and caches its own Entra ID token independently. Expect parallel credential/token requests against Entra ID rather than a single shared token. ::: **With c8ctl**: The recipe sets **API** to **Chat Completions**, as in the legacy template. It moves **Maximum tokens** to **Max completion tokens**. To use the **Responses** API, change **API** manually and enter the value in **Max output tokens**. ### OpenAI **Legacy template Provider**: OpenAI → **New template Provider**: [OpenAI](./agentic-ai-aiagent-model-providers.md#openai), **Backend**: OpenAI API. **OpenAI API key**, **Organization ID**, **Project ID**, **Timeout**, **Model**, **Temperature**, and **top P** carry over unchanged. | Legacy field | New template field | | :------------------------ | :-------------------------------------------------------------------------------------------------------------------- | | Maximum completion tokens | Max completion tokens (if you keep **API**: Chat Completions) or Max output tokens (if you switch **API**: Responses) | The legacy template always used the Chat Completions API. The new template defaults its **API** field to the newer **Responses** API. Select **Chat Completions** instead if you need closer parity with legacy behavior. The new template additionally exposes the **Effort** reasoning parameter on both API families. **With c8ctl**: The recipe sets **API** to **Chat Completions**, as in the legacy template. To use the **Responses** API, change **API** manually and enter the value in **Max output tokens**. ### OpenAI-compatible **Legacy template Provider**: OpenAI-compatible → **New template Provider**: [OpenAI](./agentic-ai-aiagent-model-providers.md#openai), **Backend**: Custom / compatible endpoint. **API endpoint**, **Headers**, **Query parameters**, **Timeout**, **Model**, **Temperature**, and **top P** carry over unchanged. The legacy **API key** field now sits behind the **Authentication** selector, as described in the notes below. | Legacy field | New template field | | :------------------------ | :------------------------------------------------------------------------ | | Maximum completion tokens | Max completion tokens (Chat Completions) or Max output tokens (Responses) | | Custom parameters | Body properties | :::important The new template replaces the legacy **API key** field with an **Authentication** selector. Resolve your effective credential as follows before you configure it: - If your legacy template's **Headers** contained an `Authorization` header, it took precedence over the **API key** field. Carry this behavior forward manually: - If the header used `Bearer `, select **API key** and move the token value without the `Bearer` prefix into the **API key** field. Remove the `Authorization` header from **Headers**. - For any other scheme, such as `Basic ...`, keep the header in **Headers** and select **None**. - Otherwise, select **API key** and carry your legacy **API key** value over directly. If you did not configure an `Authorization` header or an API key, select **None**. The new template additionally supports **OAuth 2.0** client credentials, for a gateway that issues bearer tokens through the client-credentials flow. ::: Also check the resulting request path. The new template appends `/chat/completions` or `/responses` to **API endpoint** for the selected **API**. This may differ from your legacy endpoint. **With c8ctl**: The recipe sets **API** to **Chat Completions**. It selects **API key** if the legacy **API key** field has a value, and **None** if not. The recipe does not read an `Authorization` header in **Headers**. If your legacy **Headers** contain an `Authorization` header, apply the rules above manually after the upgrade. ### Google Vertex AI **Legacy template Provider**: Google Vertex AI → **New template Provider**: [Google Gemini](./agentic-ai-aiagent-model-providers.md#google-gemini), **Backend**: Enterprise Agent Platform (Vertex AI). The provider itself changes from **Google Vertex AI** to **Google Gemini**. Vertex AI is now the Enterprise Agent Platform backend of the general-purpose Google Gemini provider. A new [Google Gemini API](./agentic-ai-aiagent-model-providers.md#google-gemini) backend is also available if you'd rather not manage a Google Cloud project. **Project ID**, **Region**, **Authentication** (**Service account credentials** / **Application default credentials**), **Model**, **Temperature**, **top P**, and **top K** carry over unchanged. | Legacy field | New template guidance | | :-------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Maximum output tokens | Enter the same value as **Maximum tokens**. | | Endpoint | Not available in Modeler. There's no custom/compatible endpoint backend for Google Gemini to switch to, unlike Anthropic or OpenAI. c8ctl keeps the value (see note below). | The new template additionally exposes **Thinking budget**/**Thinking level** for reasoning configuration. **With c8ctl**: The recipe moves a custom **Endpoint** to a hidden field of the new template. The connector uses this endpoint at runtime, but Modeler does not show it and you cannot edit it there. --- ## AI Agent connector Use the **AI Agent** connector to integrate Large Language Models (LLMs) with AI agents to build solutions using [agentic orchestration](../../agentic-orchestration/agentic-orchestration-overview.md). ## About this connector The AI Agent connector enables AI agents to integrate with an LLM to provide interaction/reasoning capabilities. This connector is designed for use with an ad-hoc sub-process in an [agent loop](/reference/glossary.md#agent-loop), providing automated tool selection. For example, use this connector to enable an AI agent to autonomously select and execute tasks within ad-hoc sub-processes by evaluating the current process context and determining the relevant tasks and tools to use in response. You can also use the AI Agent connector independently, although it is designed to be used with an ad-hoc sub-process to define the tools an AI agent can use. Core features include: | Feature | Description | | :------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | LLM provider support | Supports a range of [model providers](./agentic-ai-aiagent-model-providers.md), such as Anthropic, AWS Bedrock, Google Gemini, and OpenAI. | | Memory | Provides conversational/short-term memory handling to enable multi-turn conversations. For example, this allows a user to ask follow-up questions to an AI agent response. | | Tool calling | Support for an AI agent to interact with tasks within an ad-hoc sub-process, allowing use of all Camunda features such as connectors and user tasks (human-in-the-loop). Automatic **tool resolution** allows an AI agent to identify the tools available in an ad-hoc sub-process. | :::tip New to agentic orchestration? - The [Build your first AI Agent](../../../guides/getting-started-agentic-orchestration.md) guide provides a quick introduction to agentic orchestration and how to use the AI Agent Sub-process connector using a blueprint. - See the [example AI Agent connector integration](agentic-ai-aiagent-subprocess-example.md) for a worked example of a simple AI agent loop model. - See [additional resources](#additional-resources) for examples of how you can use the AI Agent connector. ::: ## Prerequisites The following prerequisites are required to use this connector: | Prerequisite | Description | | :------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Set up your LLM model provider and authentication | Prior to using this connector, you must have previously set up an account with access and authentication details for the supported LLM model provider you want to use.For example:To use an LLM model provided by Amazon Bedrock, you must have an AWS account with an access key and secret key to execute `Converse` actions.For OpenAI, you must configure the [OpenAI model](https://platform.openai.com/docs/models) and obtain an OpenAI API key to use for authentication. | ## Choose an implementation The AI Agent connector is available in two variants, each with different capabilities, suited for different use cases, and available with a dedicated element template: - [AI Agent Sub-process](#ai-agent-sub-process). - [AI Agent Task](#ai-agent-task). The right implementation depends on your use case. | Use case | Implementation | Description | | ------------------------------------------------------------------------------------------------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Agentic workflow with automatic tool calling (most use cases) | AI Agent Sub-process | It handles tool resolution and the [agent loop](/reference/glossary.md#agent-loop) automatically. No explicit loop modeling is needed. You must include at least one activity/tool inside the sub-process. | | Inference tasks that don't require tool calling | AI Agent Task without tools | A single, one-shot LLM call with no ad-hoc sub-process. | | Intercepting tool calls, for example, to add a human approval step or run PII detection before tool execution | AI Agent Task with tools | Tools are provided by an external ad-hoc sub-process that you model explicitly, giving you full control over the agent loop. This is the most complex configuration. | :::info The **recommended approach** for most use cases is to use the **AI Agent Sub-process** implementation due to the simplified configuration and support for event sub-processes. ::: ### Configuration comparison The following table summarizes the key configuration differences between the two implementations. | Configuration field | AI Agent Sub-process | AI Agent Task | | :--------------------- | :------------------------------------------------------------------------ | :---------------------------------------------------------------- | | Model provider | Yes | Yes | | Model | Yes | Yes | | System prompt | Yes | Yes | | User prompt | Yes | Yes | | Tools | Automatic (resolved from activities inside the sub-process) | Optional. Requires ad-hoc sub-process ID and tool call results | | Agent context (memory) | Optional. Only needed when re-entering the agent for a response follow-up | Required. Must be aligned with the output mapping result variable | | Limits | Yes | Yes | | Event handling | Yes | No | | Response | Yes | Yes | | `toolCalls` in output | No | Yes. Returned for routing to the ad-hoc sub-process | | Error handling | Yes | Yes | | Retries | Yes | Yes | | Execution listeners | Yes | Yes | :::note Execution listeners behave differently between the two implementations. On the AI Agent Sub-process, they only run when entering and exiting the ad-hoc sub-process, not on every loop iteration. On the AI Agent Task, they are triggered on every job execution. ::: ### AI Agent Sub-process The [AI Agent Sub-process](./agentic-ai-aiagent-subprocess.md) implementation uses the [job worker implementation type](../../../components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md#job-worker-implementation) of an [ad-hoc sub-process](../../../components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md) to provide an integrated solution to handle tool resolution and an [agent loop](/reference/glossary.md#agent-loop). This is the recommended implementation type for most use cases, and offers: - Simplified configuration as the agent loop is handled internally - Support for handling of event sub-processes within the ad-hoc sub-process #### Restrictions - Because of BPMN semantics, the ad-hoc sub-process must contain at least one activity. This means you cannot create an AI Agent Sub-process without any tools. - As the agent loop is implicitly handled within the AI Agent execution, you have less control over the tool calls. #### Example A basic AI Agent Sub-process might look similar to the following example. - The connector is configured so the AI Agent resolves available tools and activates them as needed to complete it's goal. - Handling of event sub-processes within the ad-hoc sub-process is supported (See [Event Handling](./agentic-ai-aiagent-subprocess.md#event-handling)). The AI Agent Task implementation does not support this. This pattern can also be combined with a response follow-up for verification or follow-up interactions. For example, instead of the showcased user task, this could also be another LLM acting as a judge, or any other task that validates the agent's response. ![AI Agent Sub-process with response follow-up](../img/ai-agent-subprocess-follow-up.png) ### AI Agent Task The [AI Agent Task](./agentic-ai-aiagent-task.md) implementation is the original variant that relies on a BPMN [service task](../../../components/modeler/bpmn/service-tasks/service-tasks.md) in combination with a multi-instance ad-hoc sub-process. Unlike the AI Agent Sub-process implementation, you must model the [agent loop](/reference/glossary.md#agent-loop) explicitly in the BPMN diagram, leading to a more complex configuration. This implementation is best suited for: - Simple, one-shot tasks using the AI Agent connector as a generic LLM connector without any tool calling. - Advanced use cases where you want to model the agent loop explicitly, for example to pre-/post-process tool calls for approval or auditing. #### Example A very simple example of using the AI Agent Task connector for a non-agentic task is as follows: - The connector can be made agentic by adding a multi-instance ad-hoc sub-process and gateways to create an agent loop. - The connector will be able to call tools until it reaches its goal or a configured limit. The multi-instance ad-hoc sub-process acts as a toolbox: The process can also be further enhanced to add a response follow-up outside the agent loop. When the AI Agent completes its task and does not request any tool calls, its response can be verified with a task (such as a user task or another LLM as a judge), with the process set up to loop back to the AI Agent if required. This allows you to create a [human-in-the-loop (HITL)](/reference/glossary.md#human-in-the-loop-hitl) process, for example, such as a chat where the user can ask follow-up questions: If you need more control over the agent loop, you can model pre-/post-processing of tool calls with additional tasks, such as approval or tool call auditing. ## Concepts ### System prompt, user prompt, and tool descriptions Reliable agent behavior depends on three inputs working together: - **System prompt**: Defines the agent's role, boundaries, priorities, and success criteria. - **User prompt**: Carries the current request and immediate context. - **Tool/task descriptions**: Define which actions are available in the ad-hoc sub-process and how each should be used. At runtime, the connector passes this combined context to the LLM. The model then selects which tools to call (if any), along with parameters. #### How task descriptions are used for tools When using an ad-hoc sub-process, each activity can be exposed as a tool. For best results, document each tool with: - A clear task name that describes intent. - A behavior-oriented description that includes when to use it, when not to use it, and the expected outcome. This makes tool selection more predictable and reduces repeated or incorrect calls. ### Execution responsibility split The decision and execution loop is shared between the LLM and Camunda: - **LLM decides**: Which tool to call next, in what order, and with which parameters. - **Camunda orchestrates**: Executes the selected BPMN activity, stores variables, applies retries and incident handling, and routes human tasks and events. This means tools can be called in different orders, repeated, run in parallel, or skipped entirely, while execution remains constrained by the modeled process boundaries. :::tip For a broader overview of how execution works in an AI agent, see [AI agents](/components/agentic-orchestration/ai-agents.md#how-the-agent-loop-works). For architectural guidance, see [Design and architecture](/components/agentic-orchestration/design-architecture.md). ::: ### Agent loop This connector is typically used in an [agent loop](/reference/glossary.md#agent-loop), with the connector implementation repeatedly being executed based on tool call results until it is able to reach its goal. Each pass through the loop is a [loop iteration](/reference/glossary.md#loop-iteration). For example, the following diagram shows an agent loop modeled with the [AI Agent Task](#ai-agent-task) implementation type. - The process loops back to the AI Agent connector task from the ad-hoc sub-process until the agent decides no further tool calls are needed. - With the [AI Agent Sub-process](#ai-agent-sub-process) implementation type, the agent loop is handled internally and so is not explicitly modeled in the BPMN diagram. ![AI Agent loop](../img/ai-agent-loop-overview.png) 1. A request is made to the AI agent connector task, and the LLM determines what action to take. 1. If the AI agent decides that further action is needed, the process enters the ad-hoc sub-process and calls any tools deemed necessary to satisfactorily resolve the request. 1. The process loops back and re-enters the AI agent connector task, where the LLM decides (with contextual memory) if more action is needed before the process can continue. The process repeats this loop iteration until the AI agent decides it is complete, and passes the AI agent response to the next step in the process. #### Agent loop use cases Typical agent loop use cases for this connector include the following: | Use case | Description | | :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Tool calling | In combination with an ad-hoc sub-process, the AI Agent connector will resolve available tools and their input parameters, and pass these tool definitions to the LLM.The LLM generates a response, that might include tool calls (a request to call a tool paired with the input parameters).If tool calls are requested, the tools can be executed by activating the respective activities within the ad-hoc sub-process.With the [AI Agent Sub-process](#ai-agent-sub-process) implementation, activating the activities will be implicitely handled by the agent implementation.With the [AI Agent Task](#ai-agent-task) implementation, it is necessary to model the process to pass these tool calls to the ad-hoc sub-process and to return the tool call results to the AI Agent task. | | Response interaction | After returning a response (and without calling any tools), model the process to act upon the response. For example, present the response to a user who can then ask follow-up questions back to the AI Agent connector as a [human-in-the-loop (HITL)](/reference/glossary.md#human-in-the-loop-hitl) follow-up. | As the agent preserves the context of the conversation, follow-up questions/tasks and handling of tool call results can relate to the previous interaction with the LLM, allowing the LLM to provide more relevant responses. ### Response follow-up Separately from the agent loop's own tool calling, the AI Agent connector's response can also be reviewed before the process continues: the process routes the agent's response to a task, such as a user task or another LLM acting as a judge, and can be modeled to loop back and re-enter the agent with a follow-up request. When that reviewing task is a person, this is an example of [human-in-the-loop (HITL)](/reference/glossary.md#human-in-the-loop-hitl). This pattern is distinct from the agent loop itself, which is the LLM's internal tool-calling cycle rather than an external re-invocation of the process. For a worked example, see [AI Agent Sub-process](#ai-agent-sub-process) or [AI Agent Task](#ai-agent-task) above. ### Agent context An important concept to understand is the use of the **Agent context** process variable to store information required for allowing re-entry to the AI Agent connector task with the same context as before. Depending on which implementation type is used, the Agent context must be configured differently in the model: - [**AI Agent Sub-process**](#ai-agent-sub-process): The agent context is kept within the sub-process scope. This means you only need to configure the agent context when the agent should pick up an existing conversation, for example to model a [human-in-the-loop (HITL)](/reference/glossary.md#human-in-the-loop-hitl) follow-up as in the [quickstart example](../../../guides/getting-started-agentic-orchestration.md). In this case, you must align the configured agent context variable with the used result variable/expression so that the context update is correctly passed to the next execution of the AI Agent connector task. - [**AI Agent Task**](#ai-agent-task): You must align the agent context input variable and the response variable/expression so the context update is correctly passed to the next execution of the AI Agent connector task. ### Response format {#response} The AI Agent connector can be configured to instruct the LLM to respond in a structured JSON format, and to parse the JSON structure into a FEEL context accordingly. This allows the LLM to return structured data that can be easily processed in the BPMN process. ### Example conversation The following is a high-level example conversation with the AI Agent connector, including both an agent loop and a human-in-the-loop (HITL) follow-up. The conversational awareness provided by the agent context allows use cases such as the user only responding with `Yes, please proceed`, with the agent understanding what it should do next. ``` # Initial input/user prompt User: Is John Doe eligible for a credit card? # Agent loop AI Agent: Call the `Check_Credit_Card_Eligibility` tool with the following parameters: {"name": "John Doe"} Tool Call Result: {"Check_Credit_Card_Eligibility": {"eligible": true}} # Human-in-the-loop (HITL) follow-up AI Agent: John Doe is eligible for a credit card. Would you like to proceed? User: Yes, please proceed. AI Agent: Call the `Create_Credit_Card` tool with the following parameters: {"name": "John Doe"} Tool Call Result: {"Create_Credit_Card": {"success": true}} AI Agent: John Doe's credit card has been created successfully. ``` ## Process instance migration :::warning Process instance migration is a powerful feature that should be used with caution. [Use at your own risk](../../concepts/process-instance-migration.md#use-at-your-own-risk). ::: Because AI agent implementations are closely tied to the underlying process definition that determines which tools are available, carefully consider the impact of applying [process instance migrations](../../concepts/process-instance-migration.md) to instances using the AI Agent connector while the agent is mid‑conversation. As a result, some migration scenarios are not supported. ### Supported migration scenarios The following migration scenarios are supported for running AI Agent process instances: | Migration scenario | Description | | :-------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Adding a new tool | Adding a new activity to the ad-hoc sub-process. The new tool is picked up on the next AI Agent execution and added to the agent context. | | Changing an existing tool without affecting the tool definition | For example, updating a form linked to a user task, or changing a script task implementation. No agent changes are necessary because the tool definition remains unchanged. | | Changing an existing tool definition | Updating a tool's description or `fromAi()` parameters is supported, but proceed carefully. See [Considerations when changing tool definitions](#considerations-when-changing-tool-definitions) for details. | | Changing AI Agent configuration (AI Agent Task only) | Updating the system prompt or model parameters on an [AI Agent Task](#ai-agent-task). These changes are picked up on the next execution as input mappings are re-evaluated for each loop iteration. This is **not supported** for the [AI Agent Sub-process](#ai-agent-sub-process) implementation because the parameters are applied via input mappings to the ad-hoc sub-process which are evaluated only once when entering the sub-process. | ### Unsupported migration scenarios The following migration scenarios are **not supported** and will result in an error: | Migration scenario | Description | | :-------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Removing or renaming tools | Removing an existing tool or changing its element ID can lead to stuck executions or validation errors if the agent references tools that no longer exist. The agent throws a `MIGRATION_MISSING_TOOLS` error when it detects a removed tool. | | Adding or removing [gateway tool definitions](./agentic-ai-aiagent-tool-definitions.md#gateway-tool-definitions) (such as MCP or A2A clients) | Gateway tools require a tool discovery flow during initialization. Adding or removing gateway tool definitions to a running agent is not supported as it would require re-executing tool discovery. The agent throws a `MIGRATION_GATEWAY_TOOL_DEFINITIONS_CHANGED` error when it detects such changes. | ### Considerations when changing tool definitions :::info When you change an existing tool definition (such as updating a tool's description or adding/modifying `fromAi()` parameters), the AI Agent detects the change and updates its tool definitions on the next execution. ::: Because a migration can occur between an AI Agent execution and the actual tool call, tools may receive parameters based on the previous definition. Ensure your tool implementations handle this scenario gracefully. - **Description changes**: Updates to tool or parameter descriptions take effect on the next AI Agent execution. The LLM uses the updated descriptions when deciding which tools to call. - **Parameter changes**: Adding, removing, or modifying `fromAi()` parameters is supported. However, the tool implementation must handle potentially missing or changed parameters. For example: - A script task should implement a null-check to return an error message if a newly required parameter is missing from an in-flight tool call. - When changing an input parameter from a numeric type to a complex type (such as an object), the implementation should handle cases where the parameter is still provided using the numeric type. When a tool receives a parameter in a different format than expected, it can either handle the situation gracefully (for example, by using a default value or converting to a suitable format), or return an actionable error message that can instruct the LLM to provide the correct parameters. ## Additional resources - [Intelligent by design: A step-by-step guide to AI task agents in Camunda](https://camunda.com/blog/2025/05/building-your-first-ai-agent-with-camunda-s-new-agentic-ai/). - [AI Agent Chat Quick Start blueprint](https://marketplace.camunda.com/en-US/apps/587865/ai-agent-chat-quick-start) on the Camunda Marketplace. - Agentic AI examples GitHub repository [working examples](https://github.com/camunda/connectors/tree/main/connectors/agentic-ai/examples). - The [MCP Client connector](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-client.md) can be used in combination with the AI agent connector to connect to tools exposed by Model Context Protocol (MCP) servers. --- ## Configure MCP Client connectors Learn how to configure MCP Client connectors, including connector mode, tool access, and availability. ## Connector mode Choose how the connector operates based on your use case. ### AI Agent tool mode Use when the connector is invoked as a tool from within an AI Agent ad-hoc sub-process. This is the default mode. The **Method** and **Parameters** fields accept FEEL expressions and default to `toolCall.method` and `toolCall.params`, which are automatically populated by the AI Agent connector during tool discovery and tool calling. #### Operation Configure the operation to execute on the MCP server. You typically only need to change the default value if the ad-hoc sub-process multi-instance uses an input element other than `toolCall`. | Field | Required | Description | | :--------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------ | | Method | Yes | The [MCP method](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#protocol-messages) to call. Defaults to `toolCall.method`. | | Parameters | Yes | The parameters to pass with the MCP client execution. Defaults to `toolCall.params`. | ### Standalone mode Use when invoking MCP operations directly from a BPMN process without an AI Agent. This mode allows you to call MCP servers independently of the agentic orchestration flow. Select the operation to perform: | Operation | Description | Configuration | | :---------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------ | | List tools | Retrieves a list of tools available on the MCP server. | No additional configuration required. | | Call tool | Invokes a specific tool on the MCP server. | **Tool name**: The name of the tool to invoke.**Tool arguments**: Tool arguments as a FEEL context expression. | | List resources | Retrieves a list of available resources on the MCP server. | No additional configuration required. | | List resource templates | Retrieves a list of available resource templates on the MCP server.These are similar to resources, but include a set of parameters for dynamic resource access. | No additional configuration required. | | Read resource | Retrieves the content of a specific resource defined by its URI. | **Resource URI**: The URI of the resource to read. | | List prompts | Retrieves a list of available prompts on the MCP server. | No additional configuration required. | | Get prompt | Retrieves a specific prompt and its messages, potentially customized based on the provided input arguments. | No additional configuration required. | ## Filters Filter the list of available data provided by the MCP server, such as tools, resources, or prompts. If not configured, all tools provided by the MCP server will be available to the AI agent. Filters can also prevent operations from being executed when applied to retrieval or execution operations, such as tool calls. Below is an example of tool filtering. The same approach applies to resources and prompts: | Field | Required | Description | Example | | :------------- | :------- | :------------------------------------------------------ | :------------------------------------- | | Included tools | No | List of allowed tools provided by the MCP server. | `["read_file", "read_multiple_files"]` | | Excluded tools | No | List of tools to exclude. Overrides any included tools. | `["write_file"]` | For example, an MCP client connected to a [file system MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) could be configured with `["read_file", "read_multiple_files"]` as included tools to only allow read-only operations to the file system. Alternatively, it could be configured with `["write_file"]` as a list of excluded tools to prevent write operations. ## Output mapping Specify the process variables to map and export the tool calling response into. | Field | Required | Description | | :---------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Result variable | Yes | Defaults to `toolCallResult`. Change only if using the AI Agent Task connector and the output mapping of the ad-hoc sub-process multi-instance is configured to use a different variable for the [content mapping](./agentic-ai-aiagent-task-example.md#configure-multi-instance-execution). | | Result expression | No | Optionally unpack the response content into multiple process variables using the **Result expression** field as a [FEEL context expression](../../concepts/expressions.md). | ## HTTP proxy support In Self-Managed environments, the MCP Client connector supports HTTP proxy configuration, including [plain proxy variables](/self-managed/components/connectors/http-proxy-configuration.md#plain-proxy-variables). See the [AI Agent connector proxy configuration](./agentic-ai-aiagent-customization.md#http-proxy-configuration) for details, including how to disable proxy support. --- ## MCP Client connector :::note This connector is not directly available on Camunda 8 SaaS. Instead, you can connect a custom connector runtime configured to run the MCP Client connector to your Camunda 8 SaaS instance. ::: The MCP Client connector integration allows configuring MCP clients to be started as part of the connector runtime. As the runtime manages MCP client connections (unlike the job worker in the [MCP Remote Client connector](./agentic-ai-mcp-remote-client-connector.md#limitations)), this approach enables using both STDIO and remote MCP servers without the overhead of repeatedly opening and closing connections for each interaction. ## Runtime configuration :::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 underscores `_`. For example, the property `camunda.connector.agenticai.mcp.remote-client.client.cache.expire-after` is represented by the environment variable `CAMUNDA_CONNECTOR_AGENTICAI_MCP_REMOTECLIENT_CLIENT_CACHE_EXPIREAFTER`. ::: To use the MCP Client connector, it must be enabled in the connector runtime. Any clients that should be available to the connector must also be defined in the runtime configuration. STDIO servers can use any programming language or execution runtime available on the machine running the connector runtime. The example below starts MCP servers using both Node.js and Docker, and therefore requires a Node.js and Docker environment to be available. :::warning Configuring STDIO servers results in the connector runtime starting and managing the lifecycle of the configured processes. When configuring third-party MCP servers, ensure that the configured commands are trusted and secure. ::: ```yaml camunda: connector: agenticai: mcp: client: enabled: true # <-- disabled by default clients: # STDIO server started as Node.js process (requires Node.js runtime) filesystem: # <-- client ID, needed to reference the client in the MCP Client connector configuration type: stdio stdio: command: npx args: - "-y" - "@modelcontextprotocol/server-filesystem" - "" env: MY_ENV_VAR: "my-value" # <-- optional environment variables # STDIO server started as docker container (requires Docker being available) time: type: stdio stdio: command: docker args: - "run" - "-i" - "--rm" - "mcp/time" # Remote Streamable HTTP MCP server (recommended for remote servers) # fetch: # enabled: true # type: http # http: # url: https://remote.mcpservers.org/fetch/mcp # headers: # X-Dummy: dummy-value # # authentication examples # authentication: # type: basic # or bearer or oauth # basic: # username: my-username # password: my-password # bearer: # token: my-token # oauth: # oauth-token-endpoint: http://example.com/oauth/token # client-id: my-client-id # client-secret: my-client-secret # scopes: my-scope # audience: my-audience # client-authentication: basic-auth-header # or credentials-body # Connection to a remote HTTP/SSE MCP server # some-remote-sse-server: # enabled: false # type: sse # sse: # url: https://example.com/mcp/sse # headers: # X-Dummy: dummy-value # authentication: # ... ``` The YAML structure above describes the overall configuration structure of the MCP Client connector. How to configure this for your specific use case varies on the connector runtime you are using. ### Camunda 8 Run 1. Download and extract the latest [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) release (at least version 8.8.0-alpha6). 2. Before starting Camunda 8 Run, create a config file (for example `mcp-clients.yml`) in the same directory as `connectors-application.properties` and add the MCP Client configuration as shown above. Adapt the configuration as needed. 3. Edit `connectors-application.properties` and add the following line to include your custom config file: ```properties spring.config.import=file:./mcp-client.yml ``` 4. [Start Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run/install-start.md#install-and-start-camunda-8-run). 5. While starting up, you can follow `logs/connectors.log`. If configured correctly, you should see log messages for the initialization of the configured MCP clients and the registration of the MCP Client connector: ```log [...] Creating MCP client with ID 'filesystem' [...] Creating MCP client with ID 'time' [...] Starting job worker: JobWorkerValue{type='io.camunda.agenticai:mcpclient:xxx', name='MCP Client', ...} ``` ### Custom project using the Spring Boot starter runtime 1. Create a new Spring Boot project. 2. Add the [Camunda Connector Spring Boot starter](../custom-built-connectors/connector-sdk.md#spring-boot-starter-runtime) and the Agentic AI dependencies to your `pom.xml`: ```xml 8.8.0 io.camunda.connector spring-boot-starter-camunda-connectors ${version.connectors} io.camunda.connector connector-agentic-ai ${version.connectors} ``` 3. Configure the SDK to connect to your cluster, according to [the Camunda SDK documentation](../../../apis-tools/camunda-spring-boot-starter/getting-started.md#configuring-the-camunda-8-connection). 4. In your application configuration file (e.g., `application.yml`), add the MCP client configuration as shown above. 5. If you only want to run the MCP Client connector (for example, because you're connecting the runtime to SaaS), disable the other Agentic AI connectors provided by the `connector-agentic-ai` dependency: ```yaml camunda: connector: agenticai: aiagent: enabled: false ad-hoc-tools-schema-resolver: enabled: false mcp: remote-client: enabled: false ``` ## Modeling 1. Configure an AI agent ad-hoc sub-process as described in the [example integration](./agentic-ai-aiagent-subprocess-example.md). Do not configure any tools within the ad-hoc sub-process yet. 2. In a Self-Managed environment, install the **MCP Client** element template. Refer to the [element template index](https://github.com/camunda/connectors/blob/main/connectors/agentic-ai/connector-mcp/element-templates/README.md) to find the correct version for your Camunda release. 3. Create a service task within the ad-hoc sub-process and apply the **MCP Client** element template. 4. In the **MCP Client** section of the properties panel, configure the **Client ID** to match the value of the MCP client you used in the runtime configuration (example: `filesystem`). 5. Execute your process. You should see tool discovery calls being routed to the MCP Client service task, and tool definitions provided by the MCP server listed in the agent context variable. As a result, the agent should be able to call the tools provided by the MCP server. --- ## Human in the loop Learn how to build human-in-the-loop AI workflows in Camunda by combining MCP Client tool discovery with BPMN elements like gateways, intermediate events, and user tasks to control and approve tool execution. ## About Using [tool discovery](./agentic-ai-mcp-client-tool-discovery.md), you can combine the MCP Client connector with other BPMN elements, such as user tasks or intermediate events, to create a human-in-the-loop interaction. Instead of directly exposing the MCP Client connector as a tool, an intermediate event marked as an MCP client gateway can serve as the root activity of a tool flow within the ad-hoc sub-process. ## Configure a filesystem MCP server The following is an example of how you can configure a filesystem MCP server: 1. Add a service task to the ad-hoc sub-process and configure one of the [MCP Client connectors](./agentic-ai-mcp-client.md#mcp-client-connectors). 2. Add an intermediate throw event to the ad-hoc sub-process and add an extension property named `io.camunda.agenticai.gateway.type` with the value `mcpClient`. 3. Create an exclusive gateway after the event to decide whether the MCP client tool call should be executed directly or require confirmation. 4. Create a flow from the exclusive gateway to the MCP Client service task for direct execution. - In the condition expression, you can use a FEEL expression like the following to allow tool listing and selected operations directly. This differs from [filtering](#tools), because all tools remain available, but you can decide which tools need user confirmation: ```feel if toolCall.method = "tools/list" then true else toolCall.method = "tools/call" and list contains([ "read_file", "read_multiple_files" ], toolCall.params.name) ``` 5. Create a default flow to a user task for the confirmation. Set up a form for the user task to enable a decision on whether the tool call should be executed. - Add a checkbox to the form so the user can confirm or deny the tool call. - Add a text view to present the tool call with a template such as the following: ``` # MCP Tool Call Confirmation The model requested to call the following MCP tool: {{toolCall.params}} ``` 6. Configure a second exclusive gateway after the user task to decide whether the tool call should be executed depending on the value of the checkbox. - If tool execution is allowed, connect the exclusive gateway to the MCP Client service task. - If tool execution is not allowed, end the tool flow in an intermediate throw event. Configure an output variable `toolCallResult`, to return a denied tool call to the model. Use the following FEEL expression as a variable assignment value: ```feel {"isError": true, "content": [{"type": "text", "text": "Tool call was not allowed by the user"}]} ``` See the example diagram below: --- ## MCP Client tool discovery Learn how AI agents use gateway tool definitions to automatically discover and invoke MCP client tools, enabling distributed, advanced BPMN workflows. ## About AI agents can detect and use tools provided by MCP clients via activities within an ad-hoc sub-process. This is made possible by so-called [_gateway tool definitions_](./agentic-ai-aiagent-tool-definitions.md#gateway-tool-definitions), which provide access to a list of tools instead of a single tool definition. Tool discovery and calling are performed within the ad-hoc sub-process, rather than directly in the AI agent. This enables: - Running the AI agent and individual MCP clients in different deployments. For example, a custom MCP client interacting with a local filesystem could be connected to a Camunda 8 SaaS instance. - More advanced modeling use cases involving other BPMN elements. For example, user tasks can be used to combine MCP clients with an approval flow for individual tool calls. See [Human in the loop](./agentic-ai-mcp-client-human-in-the-loop.md) for more details. The following sequence diagram illustrates the process of tool discovery and calling with MCP clients. Each actor in the diagram can potentially run in a different deployment, making the architecture truly distributed: ```mermaid sequenceDiagram autonumber actor User as User participant AAC as AI Agent Connector participant LLM as LLM participant MCPC as MCP Client Connector participant MCPS as MCP Server User ->>+ AAC: new request %% MCP tool lookup AAC ->>+ MCPC: list tools deactivate AAC MCPC ->>+ MCPS: list tools note left of MCPC: no LLM interaction(process flow only) MCPS -->>- MCPC: MCP tools MCPC -->>- AAC: MCP tools %% LLM call with tool definitions activate AAC AAC ->>+ LLM: request including MCP tools LLM -->>- AAC: tool call requests %% MCP tool call AAC ->>+ MCPC: call tool A deactivate AAC MCPC ->>+ MCPS: call tool A MCPS -->>- MCPC: tool A response MCPC -->>- AAC: tool A response activate AAC %% LLM feedback based on tool call AAC ->>+ LLM: tool call response LLM -->>- AAC: AI response derived from tool call AAC -->>- User: final response ``` ## Tool discovery To mark an activity tool as a gateway tool definition, the agent expects an [extension property](../../modeler/element-templates/defining-templates.md#zeebeproperty) named `io.camunda.agenticai.gateway.type` with the value `mcpClient`. This is automatically applied by the provided [MCP connectors](./agentic-ai-mcp-client-connector.md#mcp-connectors), but it also allows for more advanced use cases, such as tool calling with human-in-the-loop interaction, when added to other activities, like an intermediate event. When the AI agent connector [resolves its available tools](./agentic-ai-aiagent-tool-definitions.md#tool-resolution), it also resolves gateway tool definitions. If required by the gateway tool type, it initiates a tool discovery loop through the ad-hoc sub-process. The implementation of tool discovery depends on the gateway tool type. For MCP clients (gateway type `mcpClient`), it triggers the [`tools/list`](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#listing-tools) method on each MCP Client connector configured within the ad-hoc sub-process. It is the responsibility of the MCP client implementation to fetch tool definitions from the connected MCP server and return them to the AI agent as part of this discovery call. ## Tool definitions Because the AI agent must present unique tool names to the LLM while also being able to map tool calls to specific activities in the ad-hoc sub-process, it applies a naming convention to uniquely identify MCP tool names: ``` MCP____> ``` For example, the `get_current_time` tool provided by a [time MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/time) would resolve to the following tool definition when accessed through an MCP Client activity with the ID `Time`: ```json { "name": "MCP_Time___get_current_time", "description": "Get current time in a specific timezones", "inputSchema": { "properties": { "timezone": { "type": "string", "description": "IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Use 'UTC' as local timezone if no timezone provided by the user." } }, "required": ["timezone"], "type": "object" } } ``` When handling LLM tool call requests, the MCP Client integration of the AI agent connector transparently maps the unique tool names back to the matching activity. The tool name and arguments are then passed to the MCP Client connector for the actual tool call. ### Name restrictions Because the `___` sequence is used as a separator in the tool naming convention, **MCP client activity IDs must not contain `___`**. If an activity ID contains this reserved separator, the AI agent connector throws an error with the error code `MCP_GATEWAY_INVALID_TOOL_DEFINITIONS` when attempting tool discovery. To resolve this error, rename the affected MCP Client activities in your BPMN model so that their IDs do not contain `___`. --- ## MCP Client Integrate [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) clients with [agentic orchestration](../../agentic-orchestration/agentic-orchestration-overview.md). :::tip Camunda as an MCP server The Orchestration Cluster includes two built-in MCP servers that can be used as remote MCP server targets with the MCP Client connectors: - The [Orchestration Cluster MCP Server](/apis-tools/orchestration-cluster-api-mcp/orchestration-cluster-api-mcp-overview.md) exposes Camunda's operational capabilities (incidents, user tasks, process instances…). See [Enable and connect](/apis-tools/orchestration-cluster-api-mcp/orchestration-cluster-api-mcp-setup.md#use-with-the-mcp-client-connectors) for configuration details. - The [Processes MCP Server](/apis-tools/processes-mcp/processes-mcp-overview.md) exposes your deployed BPMN processes as callable tools (served at `/mcp/processes`). See [Enable and connect](/apis-tools/processes-mcp/processes-mcp-setup.md#use-with-the-mcp-client-connectors) for configuration details. ::: ## About Camunda's MCP Client enables you to use the [AI Agent connector](./agentic-ai-aiagent.md) together with MCP clients to access tools provided by MCP servers. :::info The MCP Client supports only tool-related functionality. Other MCP features, such as resources or prompts, are not currently supported. ::: This includes: - Locally started standard input/output servers [STDIO](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#stdio). These are operating system processes launched and managed by the connector runtime. - Remote MCP servers using [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http) and [HTTP with SSE](https://modelcontextprotocol.io/specification/2024-11-05/basic/transports#http-with-sse) (deprecated). See the MCP Client architecture below: ![MCP Client architecture](agentic-ai/img/mcp-clients-architecture.png) ## MCP Client connectors Camunda provides two MCP connectors with distinct purposes. | Connector | STDIO | Remote/HTTP | Configuration | Availability | Description | | :------------------------------------------------------------------------- | :---------- | :---------- | :----------------------------------- | :---------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------- | | [MCP Remote Client connector](./agentic-ai-mcp-remote-client-connector.md) | Unsupported | Supported | Properties panel | Available on SaaS | Suitable for prototyping with remote MCP servers. Uses on-demand HTTP connections instead of persistent ones. | | [MCP Client connector](./agentic-ai-mcp-client-connector.md) | Supported | Supported | Connector runtime + properties panel | Not directly available on SaaS, but a custom runtime running the client connector can be connected to SaaS. | Flexible MCP integration based on persistent connections managed by the connector runtime. Supports STDIO MCP servers. | :::info The two connectors are not mutually exclusive and can be used together as long as your environment is configured accordingly. ::: ## Learn the fundamentals Understand the fundamental concepts of the MCP Client. ## Explore further resources Dive into practical resources to see the MCP Client in action. Try out this example Watch this webinar on using MCP with Camunda --- ## MCP Remote Client connector The MCP Remote Client connector allows connecting an AI agent to a remote MCP server by configuring a connection to a [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http) (recommended) or [HTTP with SSE](https://modelcontextprotocol.io/specification/2024-11-05/basic/transports#http-with-sse) (legacy) endpoint. ## Limitations Since the MCP client functionality is handled by a stateless [job worker](../../concepts/job-workers.md), each activation of an activity using the MCP Remote Client connector requires opening a dedicated HTTP connection/SSE subscription to the MCP server. For example, each of the following actions in an agent loop opens and closes a dedicated MCP client connection to the remote server: 1. Tool discovery 2. Tool call 3. Every subsequent tool call Due to this overhead, the MCP Remote Client connector is primarily intended for prototyping and testing purposes. For production or more efficient usage, consider using the [MCP Client](./agentic-ai-mcp-client-connector.md) connector instead. ## Modeling 1. Configure an AI agent ad-hoc sub-process as described in the [example integration](./agentic-ai-aiagent-subprocess-example.md). Do not configure any tools within the ad-hoc sub-process yet. 2. In a Self-Managed environment, install the **MCP Client** element template. Refer to the [element template index](https://github.com/camunda/connectors/blob/main/connectors/agentic-ai/connector-mcp/element-templates/README.md) to find the correct version for your Camunda release. 3. Create a service task within the ad-hoc sub-process and apply the **MCP Remote Client** element template. 4. Configure the transport type and connection settings as described in [Transport type](#transport-type). 5. Execute your process. You should see tool discovery calls routed to the MCP Client service task, and tool definitions provided by the MCP server listed in the agent context variable. As a result, the agent should be able to call the tools provided by the MCP server. ## Transport type Select the transport protocol for connecting to the remote MCP server. ### Streamable HTTP Use for MCP servers exposing a Streamable HTTP endpoint. :::info This is the recommended transport for new implementations. ::: | Field | Required | Description | | :------ | :------- | :--------------------------------------------------------------------------------- | | URL | Yes | The Streamable HTTP endpoint URL. Typically ends in `/mcp`. | | Headers | No | Custom HTTP headers as a FEEL map. For example, `={ "X-Custom-Header": "value" }`. | | Timeout | No | Connection timeout as an ISO 8601 duration. For example, `PT60S`. | ### Server-Sent Events (SSE) Use for MCP servers exposing a Server-Sent Events (SSE) endpoint. :::info This transport type is considered legacy; use Streamable HTTP for new implementations where possible. ::: | Field | Required | Description | | :------ | :------- | :--------------------------------------------------------------------------------- | | URL | Yes | The SSE endpoint URL. Typically ends in `/sse`. | | Headers | No | Custom HTTP headers as a FEEL map. For example, `={ "X-Custom-Header": "value" }`. | | Timeout | No | Connection timeout as an ISO 8601 duration. For example, `PT60S`. | ## Authentication The MCP Remote Client connector supports multiple authentication methods for connecting to secured MCP servers. Authentication is configured per transport type. ### None Select **None** in the **Authentication** dropdown. No authentication headers are added to requests. You can still configure custom headers for API keys or other header-based authentication mechanisms. ### Basic Sends an `Authorization: Basic ` header with each request. | Field | Required | Description | | :------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- | | Username | Yes | The username for authentication. | | Password | Yes | The password for authentication. Camunda recommends using [secrets](/components/saas/clusters/manage-secrets.md). For example, `{{secrets.MCP_PASSWORD}}`. | ### Bearer token Sends an `Authorization: Bearer ` header with each request. | Field | Required | Description | | :----------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------- | | Bearer token | Yes | The bearer token value. Camunda recommends using [secrets](/components/saas/clusters/manage-secrets.md). For example, `{{secrets.MCP_BEARER_TOKEN}}`. | ### OAuth 2.0 client credentials Automatically retrieves and manages access tokens using the OAuth 2.0 client credentials flow (machine-to-machine authentication). Tokens are cached in memory and automatically refreshed when expired. | Field | Required | Description | | :-------------------- | :------- | :--------------------------------------------------------------------------------------------------------- | | OAuth token endpoint | Yes | The URL to obtain access tokens. | | Client ID | Yes | Your OAuth client identifier. | | Client secret | Yes | Your OAuth client secret. Camunda recommends using [secrets](/components/saas/clusters/manage-secrets.md). | | Audience | No | Target API identifier (required by some OAuth providers). | | Scopes | No | Space-separated list of scopes to request. | | Client authentication | Yes | **Send credentials in header** (Basic authentication) or **send credentials in body**. | For more details on OAuth 2.0 client credentials flow, see [REST connector OAuth](/components/connectors/protocol/rest.md#rest-connector-oauth-token). ## Client caching To reduce the connection overhead described in [Limitations](#limitations), the MCP Remote Client connector can cache MCP clients and reuse connections for calls to the same MCP server. ### Per-client caching options Enable the **Client cache** checkbox in the connector properties to reuse the MCP client connection for as long as configured on the runtime. :::warning Enable client caching **only** when authentication credentials do not depend on process-specific data. Cached clients reuse the authentication context from the first connection, which may conflict with process-specific credentials in subsequent calls. ::: ### Runtime cache configuration In a Camunda 8 Self-Managed setup or a [custom connector runtime](../custom-built-connectors/connector-sdk.md#runtime-environments), configure overall client-caching behavior using the following properties: :::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 underscores `_`. For example, the property `camunda.connector.agenticai.mcp.remote-client.client.cache.expire-after` is represented by the environment variable `CAMUNDA_CONNECTOR_AGENTICAI_MCP_REMOTECLIENT_CLIENT_CACHE_EXPIREAFTER`. ::: ```properties camunda.connector.agenticai.mcp.remote-client.client.cache.enabled=true camunda.connector.agenticai.mcp.remote-client.client.cache.expire-after=PT10M camunda.connector.agenticai.mcp.remote-client.client.cache.maximum-size=15 ``` --- ## MCP start event The **MCP start event** is an [element template](/reference/glossary.md#element-template) applied to a BPMN message start event. When deployed, it registers the process as an MCP tool in the [Processes MCP Server](/apis-tools/processes-mcp/processes-mcp-overview.md). :::note The MCP start event is not handled by the Connector Runtime like other Connectors. The element template configures the start event metadata that the Processes MCP Server uses to expose the process as a tool to MCP clients. ::: ## Apply the template 1. Open your BPMN process in [Camunda Hub](/components/hub/workspace/modeler/index.md) or [Desktop Modeler](/components/modeler/desktop-modeler/index.md). 2. Select a start event (or add a new one). 3. In the properties panel, click the element template picker. 4. Select **MCP start event** from the **AI Tools** category. ## Properties | Property | Required | Description | | :------------------------ | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Name** | Yes | The MCP tool identifier. Alphanumeric characters, hyphens (`-`), underscores (`_`), and dots (`.`) **only**. Maximum 100 characters. Used by MCP clients to call this tool. | | **What it does** | Yes | A plain-language description of the process function. Shown to LLMs as tool metadata. | | **Which inputs it needs** | Yes | A plain-language description of required and optional input parameters, their types, and any constraints. Shown to LLMs as tool metadata. | | **When to use** | No | Specific situations or user intents that should trigger this tool. | | **When not to use** | No | Conditions or situations where this tool should not be invoked. | | **What the tool returns** | No | The outcomes, results, and variable names the process produces on completion. | A hidden `messageNameUuid` property is auto-generated and binds the start event to its BPMN message name. No user action is required for this property. ## How it works When the process is deployed: 1. The Processes MCP Server reads the element template metadata and registers the process as an MCP tool. 2. MCP clients can discover the tool by name and call it. 3. Each tool invocation starts a new process instance with the tool call arguments mapped as process variables. 4. The MCP Server returns the key of the started process instance to the client, which can use it to track execution status or retrieve results. :::important Only the latest deployed version of a process is exposed. See [version binding](/apis-tools/processes-mcp/processes-mcp-version-binding.md) for more details. ::: --- ## Amazon Bedrock AgentCore Long-Term Memory connector With the **Amazon Bedrock AgentCore Long-Term Memory** outbound connector, you can retrieve persistent knowledge, such as facts, preferences, and summaries, from [AWS Bedrock AgentCore Long-Term Memory](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/long-term-memory-long-term.html) in your BPMN process. ## Prerequisites To use the **Amazon Bedrock AgentCore Long-Term Memory connector**, you need the following: - An AWS account with an access key and secret key, or a configured default credentials chain. - An [AgentCore Memory resource](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/long-term-memory-create.html) created in your AWS account. - IAM permissions to execute the `RetrieveMemoryRecords` and `ListMemoryRecords` actions. Learn more about Amazon Bedrock AgentCore Long-Term Memory in the [official documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/long-term-memory-long-term.html). :::note Use secrets to store credentials and avoid exposing sensitive information directly from the process. Refer to [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ::: ## Create an Amazon Bedrock AgentCore Long-Term Memory connector task ## Authentication To authenticate, choose one of the methods from the **Authentication** dropdown. The supported options are: - **Credentials**: Select this option if you have a valid pair of access and secret keys provided by your AWS account administrator. :::note This option is applicable for both SaaS and Self-Managed users. ::: - **Default Credentials Chain**: Select this option if your system is configured as an implicit authentication mechanism, such as role-based authentication, credentials supplied via environment variables, or files on target host. This approach uses the [Default Credential Provider Chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html) to resolve required credentials. :::note This option is applicable only for Self-Managed or hybrid distributions. ::: ## Configuration 1. In the **Region** field, enter the AWS region where your AgentCore Memory resource is deployed. For example, `us-east-1`. 1. In the **Memory ID** field, enter the identifier of the AgentCore Memory resource you want to query. 1. In the **Namespace** field, enter a namespace prefix to scope memory records. For example, `customer/12345`. This is required by AWS to organize and isolate memory records. ## Operations The **Amazon Bedrock AgentCore Long-Term Memory connector** supports the following operations. ### Retrieve memory records Perform a semantic search to find relevant memory records based on a natural language query. #### Parameters | Parameter | Required | Description | | :--------------------- | :------- | :------------------------------------------------------------------------------- | | **Search query** | Yes | Semantic search query to find relevant memory records (up to 10,000 characters). | | **Memory strategy ID** | No | Limits the search to memories created by a specific extraction strategy. | | **Max results** | No | Maximum number of results to return (1–100). Defaults to 10. | | **Next token** | No | Pagination token from a previous response to fetch the next page. | ### List memory records List all memory records within the configured namespace. #### Parameters | Parameter | Required | Description | | :--------------------- | :------- | :---------------------------------------------------------------- | | **Memory strategy ID** | No | Filter memory records by a specific extraction strategy. | | **Max results** | No | Maximum number of results to return (1–100). Defaults to 20. | | **Next token** | No | Pagination token from a previous response to fetch the next page. | ## Response The connector operations use the following response structure: | Field | Description | | :------------ | :------------------------------------------------------------------------------------------- | | `records` | A list of memory record entries. Each entry contains the fields described below. | | `resultCount` | The number of records returned. | | `nextToken` | A token for retrieving additional results in subsequent requests. `null` if no more results. | Each record in the `records` list contains: | Field | Description | | :----------------- | :------------------------------------------------------ | | `memoryRecordId` | Unique identifier for the memory record. | | `content` | The text content of the memory record. | | `memoryStrategyId` | The extraction strategy that created this record. | | `namespaces` | List of namespaces associated with the record. | | `createdAt` | Timestamp when the record was created. | | `score` | Relevance score (only present for retrieve operations). | | `metadata` | Key-value metadata associated with the record. | ## Output mapping 1. Use **Result Variable** to store the response in a process variable. For example, `memoryResult`. 2. Use **Result Expression** to map specific fields from the response into process variables. For example, to extract the content from all records: ```feel = { memories: records.content, count: resultCount } ``` ## Example response The following is an example of the connector response: ```json { "records": [ { "memoryRecordId": "mem-abc123", "content": "Customer prefers email communication over phone calls.", "memoryStrategyId": "preference-extraction", "namespaces": ["customer/12345"], "createdAt": "2024-01-15T10:30:00Z", "score": 0.95, "metadata": { "source": "support-ticket", "confidence": "high" } }, { "memoryRecordId": "mem-def456", "content": "Customer is located in the Pacific timezone.", "memoryStrategyId": "fact-extraction", "namespaces": ["customer/12345"], "createdAt": "2024-01-10T14:20:00Z", "score": 0.87, "metadata": { "source": "profile-update" } } ], "resultCount": 2, "nextToken": null } ``` --- ## Amazon Bedrock AgentCore Runtime connector With the **Amazon Bedrock AgentCore Runtime** outbound connector, you can invoke external agents deployed on [Amazon Bedrock AgentCore Runtime](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agents-tools-runtime.html) from your BPMN process. Use this connector to delegate complex reasoning tasks to specialized AI agents hosted on AWS, such as fraud detection, risk analysis, or document processing agents built with frameworks like Strands, LangGraph, or CrewAI. ## Prerequisites To use the **Amazon Bedrock AgentCore Runtime connector**, you need the following: - An AWS account with an access key and secret key, or a configured default credentials chain. - An agent deployed to [AgentCore Runtime](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-getting-started.html) in an `ACTIVE` state. - IAM permissions to execute the `InvokeAgentRuntime` action on the agent's ARN. Learn more about AgentCore Runtime in the [official documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agents-tools-runtime.html). :::note Use secrets to store credentials and avoid exposing sensitive information directly from the process. Refer to [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ::: ## Create an Amazon Bedrock AgentCore Runtime connector task ## Authentication To authenticate, choose one of the methods from the **Authentication** dropdown. The supported options are: - Use **Credentials** if you have a valid pair of access and secret keys provided by your AWS account administrator. :::note This option is applicable for both SaaS and Self-Managed users. ::: - Use **Default Credentials Chain** if your system is configured as an implicit authentication mechanism, such as role-based authentication, credentials supplied via environment variables, or files on target host. This approach uses the [Default Credential Provider Chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html) to resolve required credentials. :::note This option is applicable only for Self-Managed or hybrid distributions. ::: For more information on authentication and security in Amazon Bedrock, see [Amazon Bedrock security and privacy](https://aws.amazon.com/bedrock/security-compliance/). ## Configuration In the **Region** field, enter the AWS region where your AgentCore Runtime agent is deployed. For example, `us-east-1`. ## Action The **Amazon Bedrock AgentCore Runtime connector** supports the following action. ### Invoke agent runtime #### Parameters | Parameter | Required | Description | | :-------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------ | | **Agent Runtime ARN** | Yes | The ARN of the deployed AgentCore Runtime agent. You can find it in the AgentCore console or in the output of `agentcore deploy`. | | **Prompt** | Yes | The message or task to send to the agent. Supports [FEEL](/components/modeler/feel/what-is-feel.md) expressions. | | **Session ID** | No | An optional session ID for multi-turn conversations. When provided, the agent retains context from previous interactions within the same session. | #### Response The connector returns the following fields: | Field | Description | | :----------- | :--------------------------------------------------------------------------------------- | | `response` | The agent's response text or payload. | | `sessionId` | The runtime session ID. Use this value in subsequent calls to continue the conversation. | | `statusCode` | The HTTP status code of the response. | #### Output mapping 1. Use **Result Variable** to store the response in a process variable. For example, `agentResult`. 2. Use **Result Expression** to map specific fields from the response into process variables. For example, to extract the agent's response: ```feel = { answer: response, session: sessionId } ``` #### Example: Invoke a fraud detection agent **Configuration:** - Agent Runtime ARN: `arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/fraud_agent-1GRoTlCtHi` - Prompt: `Claim: Customer reports stolen vehicle worth $45,000. Filed 2 days after policy activation. No police report.` **Response:** ```json { "response": "{\"result\": {\"role\": \"assistant\", \"content\": [{\"text\": \"{\\\"riskScore\\\": 85, \\\"riskLevel\\\": \\\"HIGH\\\", \\\"flags\\\": [\\\"Policy activated only 2 days before claim\\\", \\\"No police report filed\\\", \\\"High-value claim\\\"], \\\"recommendation\\\": \\\"REJECT\\\"}\"}]}}", "sessionId": "76f079e6-f30b-4416-9fbc-5d09d2ad31de", "statusCode": 200 } ``` #### Multi-turn conversations To maintain context across multiple interactions with the same agent, pass the `sessionId` from the previous response into the **Session ID** field of the next call. This allows the agent to remember prior messages and maintain state. :::note When using the connector as a tool in an [AI Agent Sub-process](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess.md), use the `fromAi()` function for the **Prompt** field to let the orchestrating agent compose the message dynamically based on the user's request. ::: --- ## Amazon Bedrock Code Interpreter connector With the **Amazon Bedrock Code Interpreter** outbound connector, you can execute Python code in a secure [Amazon Bedrock AgentCore Code Interpreter](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/code-interpreter-tool.html) sandbox from your BPMN process. ## Prerequisites To use the **Amazon Bedrock Code Interpreter connector**, you need the following: - An AWS account with an access key and secret key, or a configured default credentials chain. - IAM permissions to execute the following actions on the `bedrock-agentcore` service: - `StartCodeInterpreterSession` - `InvokeCodeInterpreter` - `StopCodeInterpreterSession` - The AgentCore Code Interpreter service must be available in your selected AWS region. Learn more about the AgentCore Code Interpreter in the [official documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/code-interpreter-tool.html). :::note Use secrets to store credentials and avoid exposing sensitive information directly from the process. Refer to [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ::: ## Create an Amazon Bedrock Code Interpreter connector task ## Authentication To authenticate, choose one of the methods from the **Authentication** dropdown. The supported options are: - Use **Credentials** if you have a valid pair of access and secret keys provided by your AWS account administrator. :::note This option is applicable for both SaaS and Self-Managed users. ::: - Use **Default Credentials Chain** if your system is configured as an implicit authentication mechanism, such as role-based authentication, credentials supplied via environment variables, or files on target host. This approach uses the [Default Credential Provider Chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html) to resolve required credentials. :::note This option is applicable only for Self-Managed or hybrid distributions. ::: For more information on authentication and security in Amazon Bedrock, see [Amazon Bedrock security and privacy](https://aws.amazon.com/bedrock/security-compliance/). ## Configuration In the **Region** field, enter the AWS region where Amazon Bedrock AgentCore Code Interpreter is available. For example, `us-east-1`. ## Action The **Amazon Bedrock Code Interpreter connector** supports the following action. ### Code execution #### Parameters | Parameter | Required | Description | | :---------------------------- | :------- | :---------------------------------------------------------------------------------------------------------------- | | **Code** | Yes | The Python code to execute in the sandbox. Supports [FEEL](/components/modeler/feel/what-is-feel.md) expressions. | | **Session timeout (seconds)** | No | How long the sandbox session stays alive, in seconds (60–28,800). Defaults to 300. | :::note The session timeout controls how long the sandbox environment remains available, not how long the code takes to execute. Code execution typically completes in seconds. ::: The sandbox environment includes common Python libraries such as `matplotlib`, `pandas`, `numpy`, and `csv`. Network access is not available in the sandbox. #### Response The connector returns the following fields: | Field | Description | | :---------------- | :------------------------------------------------------------------------------------------------------------------- | | `stdout` | The standard output printed by the code. | | `stderr` | Any error output produced during execution. | | `exitCode` | The process exit code. A value of `0` indicates successful execution. | | `executionTimeMs` | The execution duration in milliseconds. | | `files` | A list of [Camunda documents](/components/document-handling/getting-started.md) for any files generated by the code. | #### File handling Any files created by the code during execution are automatically detected and returned as Camunda documents in the `files` field. This includes images (for example, charts saved with `plt.savefig()`), CSV files, text files, and other output files. :::note A maximum of 10 files are retrieved per execution, with a total size limit of 10 MB. Files exceeding these limits are skipped, and a warning is logged. ::: #### Output mapping 1. Use **Result Variable** to store the response in a process variable. For example, `codeResult`. 2. Use **Result Expression** to map specific fields from the response into process variables. For example, to extract the output and generated files: ```feel = { output: stdout, files: files } ``` #### Example: Run a calculation **Code:** ```python total = sum([12000, 8500, 15000, 9200]) print(f"Total claims: ${total:,}") ``` **Response:** ```json { "stdout": "Total claims: $44,700\n", "stderr": "", "exitCode": 0, "executionTimeMs": 0.05, "files": [] } ``` #### Example: Generate a chart **Code:** ```python plt.bar(["Q1", "Q2", "Q3", "Q4"], [12000, 8500, 15000, 9200]) plt.title("Claims by Quarter") plt.ylabel("Amount ($)") plt.savefig("chart.png") print("Chart saved") ``` **Response:** ```json { "stdout": "Chart saved\n", "stderr": "", "exitCode": 0, "executionTimeMs": 1.67, "files": [ { "storeId": "in-memory", "documentId": "851505b2-61bd-4717-9c86-07678b67f033", "metadata": { "contentType": "image/png", "size": 14734, "fileName": "chart.png" }, "camunda.document.type": "camunda" } ] } ``` #### Example: Generate a CSV report **Code:** ```python with open("claims.csv", "w", newline="") as f: w = csv.writer(f) w.writerow(["ClaimID", "Amount", "Status"]) w.writerows([["C001", 45000, "Rejected"], ["C002", 12000, "Approved"]]) print("Report created") ``` **Response:** ```json { "stdout": "Report created\n", "stderr": "", "exitCode": 0, "executionTimeMs": 0.05, "files": [ { "storeId": "in-memory", "documentId": "c209e3a4-cefa-4622-adc1-88c92bf06bb2", "metadata": { "contentType": "text/csv", "size": 80, "fileName": "claims.csv" }, "camunda.document.type": "camunda" } ] } ``` --- ## Amazon Bedrock Knowledge Base connector With the **Amazon Bedrock Knowledge Base** outbound connector, you can perform semantic search over documents indexed in an [Amazon Bedrock Knowledge Base](https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base.html) from your BPMN process. ## Prerequisites To use the **Amazon Bedrock Knowledge Base connector**, you need the following: - An AWS account with an access key and secret key, or a configured default credentials chain. - A [Bedrock Knowledge Base](https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base-create.html) created and configured with at least one data source. - IAM permissions to execute the [`Retrieve`](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_agent-runtime_Retrieve.html) action on the knowledge base. Learn more about Amazon Bedrock Knowledge Bases in the [official documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base.html). :::note Use secrets to store credentials and avoid exposing sensitive information directly from the process. Refer to [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ::: ## Create an Amazon Bedrock Knowledge Base connector task ## Authentication To authenticate, choose one of the methods from the **Authentication** dropdown. The supported options are: - Use **Credentials** if you have a valid pair of access and secret keys provided by your AWS account administrator. The access key must have permissions to the Bedrock Knowledge Base `Retrieve` action. :::note This option is applicable for both SaaS and Self-Managed users. ::: - Use **Default Credentials Chain** if your system is configured as an implicit authentication mechanism, such as role-based authentication, credentials supplied via environment variables, or files on target host. This approach uses the [Default Credential Provider Chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html) to resolve required credentials. :::note This option is applicable only for Self-Managed or hybrid distributions. ::: For more information on authentication and security in Amazon Bedrock, see [Amazon Bedrock security and privacy](https://aws.amazon.com/bedrock/security-compliance/). ## Configuration In the **Region** field, enter the AWS region where your knowledge base is deployed. For example, `us-east-1`. In the **Knowledge Base ID** field, enter the ID of the Bedrock Knowledge Base you want to query. You can find this ID in the [Amazon Bedrock console](https://console.aws.amazon.com/bedrock/). ## Action The **Amazon Bedrock Knowledge Base connector** supports the following action. ### Retrieve from Knowledge Base Perform a semantic search over the documents indexed in your knowledge base. The connector returns the most relevant passages that match your query. #### Parameters | Parameter | Required | Description | | :-------------------- | :------- | :---------------------------------------------------------------------------------------------------------------------------- | | **Query** | Yes | A natural language query to search the knowledge base. Supports [FEEL](/components/modeler/feel/what-is-feel.md) expressions. | | **Number of results** | No | The maximum number of results to return (1-100). Defaults to five. | #### Response The connector returns the following fields: | Field | Description | | :---------------- | :------------------------------------------------------------------------------------------------------------------- | | `results` | A list of retrieval results. Each result contains the fields described below. | | `resultCount` | The number of results returned. | | `paginationToken` | A token for retrieving additional results in subsequent requests. This value is `null` if there are no more results. | Each result in the `results` list contains the following fields: | Field | Description | | :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `documentReference` | A [Camunda document](/components/document-handling/getting-started.md) reference containing the chunk text, for use with downstream connectors or processing. | | `content` | The matched text passage from the knowledge base. | | `score` | A relevance score between 0 and 1 indicating how well the result matches the query. | | `sourceUri` | The S3 URI of the source document. | | `metadata` | Key-value metadata associated with the source document. | #### Output mapping 1. Use **Result Variable** to store the response in a process variable. For example, `kbResult`. 2. Use **Result Expression** to map specific fields from the response into process variables. For example, to extract just the text content from all results: ```feel = { texts: results.content, count: resultCount } ``` #### Query guidelines For best results, use specific, descriptive queries rather than short keywords: | Query | Quality | Why | | :--------------------------------------------------------------- | :------ | :------------------------------------------------ | | "What does the auto insurance policy cover for stolen vehicles?" | Good | Specific topic with clear intent. | | "What are the exclusions for water damage in home insurance?" | Good | Targets a specific section and policy type. | | "Coverage" | Poor | Too broad. It may return loosely related results. | | "Tell me everything about insurance" | Poor | Too vague. No specific topic to match against. | :::tip When using the connector as a tool in an [AI Agent Sub-process](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess.md), the agent composes the query automatically based on the user's request. Use the `fromAi()` function to let the agent generate targeted queries. ::: #### Example response The following is an example of the connector response: ```json { "results": [ { "documentReference": { "storeId": "in-memory", "documentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }, "content": "Comprehensive auto insurance covers damage to your vehicle from events other than collisions, including theft, vandalism, natural disasters, and falling objects.", "score": 0.92, "sourceUri": "s3://my-bucket/policies/auto-insurance.pdf", "metadata": { "category": "auto", "policyType": "comprehensive" } }, { "documentReference": { "storeId": "in-memory", "documentId": "b2c3d4e5-f6a7-8901-bcde-f12345678901" }, "content": "Liability coverage pays for bodily injury and property damage that you cause to others in an auto accident.", "score": 0.85, "sourceUri": "s3://my-bucket/policies/auto-insurance.pdf", "metadata": { "category": "auto", "policyType": "liability" } } ], "resultCount": 2, "paginationToken": null } ``` --- ## Amazon Bedrock connector The **Amazon Bedrock connector** is an outbound connector that allows you to interact with [Amazon Bedrock](https://aws.amazon.com/bedrock/) from your BPMN process. ## Prerequisites To use the **Amazon Bedrock connector**, you need to have an AWS account with an access key and secret key to execute [`InvokeModel`](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_InvokeModel.html) or [`Converse`](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html) actions. The necessary models must be enabled beforehand on the region you are operating from. See more about this [in the Amazon Bedrock user guide](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html). Learn more about Amazon bedrock in the [official Bedrock documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html). :::note Use secrets to store credentials and avoid exposing sensitive information directly from the process. Refer to [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ::: ## Create an Amazon Bedrock connector task ## Authentication To authenticate, choose one of the methods from the **Authentication** dropdown. The supported options are: - Use **Credentials** if you have a valid pair of access and secret keys provided by your AWS account administrator. The access key provides permissions to the Amazon Bedrock `InvokeModel` and/or `Converse` actions, as mentioned in the [AWS documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/security_iam_id-based-policy-examples.html#security_iam_id-based-policy-examples-perform-actions-pt). :::note This option is applicable for both SaaS and Self-Managed users. ::: - Use **Default Credentials Chain** if your system is configured as an implicit authentication mechanism, such as role-based authentication, credentials supplied via environment variables, or files on target host. This approach uses the [Default Credential Provider Chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html) to resolve required credentials. :::note This option is applicable only for Self-Managed or hybrid distributions. ::: For more information on authentication and security in Amazon Bedrock, see [Amazon Bedrock security and privacy](https://aws.amazon.com/bedrock/security-compliance/). ## Region In the **Region** field write the region of the deployed endpoint. ## Action There are two possible actions with the Amazon Bedrock connector: `InvokeModel` and `Converse`. ### InvokeModel This action is meant to invoke a model with a raw payload. A model ID must be specified. Find all the available options for Amazon Bedrock [in the model ID documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids.html). :::note Ensure the model is available in your region, that your model can invoke the `Invoke Model` action, and you are a user with adequate rights. ::: The payload is dependent on the model used, and you can find the different payloads [in the model parameters documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters.html). 1. Use **Result Variable** to store the response in a process variable. For example, `myResultVariable`. 2. Use **Result Expression** to map fields from the response into process variables. The response is dependent on the model used, and you can find the different responses [in the model parameters documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters.html). #### Example If using the model `Jamba-instruct` with model ID `ai21.jamba-instruct-v1:0`, and looking at the [model parameters Jamba documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-jamba.html), the payload could be as follows: ```json { "messages": [{ "role": "user", "content": "Hello" }], "max_tokens": 256, "top_p": 0.8, "temperature": 0.7 } ``` The FEEL mapping could be as follows: ```feel = { AIResponse: response.body.choices[1].message.content } ``` ### Converse This action is meant to start or continue a conversation with a model. A model ID must be specified. Find all available model IDs for Amazon Bedrock [in the model ID documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids.html). :::note Ensure the model is available in your region, that your model can invoke the `Converse` action, and you are a user with adequate rights. ::: - `New Message` is either the first message (to start a conversation) or is the next message from an already started conversation. - `Documents` is a list of documents to include as part of your **new message**. - Each document uses a [document source](/components/document-handling/send-document-to-external-system.md#document-sources): a **Camunda document** reference, **inline content** built from process data, or an **external document** URL. Use the **Single/Multiple** toggle to provide one document or a FEEL array of documents. - See [Amazon Bedrock supported document formats](https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base-ds.html) for currently supported file formats. - To use a **Camunda document**, upload it first — [using the Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/specifications/create-document.api.mdx) for example — and assign the result to a variable in **Start Process instance** so you can reference it in the **Documents** field. - `Message History` is the history of the conversation that should always be passed. If not set, this will be a new conversation. 1. Use **Result Variable** to store the response in a process variable. For example, `myResultVariable`. 2. Use **Result Expression** to map fields from the response into process variables. The **Response** is a list of consecutive messages of the user and the assistant. :::info important The current implementation supports the assistant's responses only in text format. ::: Ideally, the message's history must transit within the process and be the input of this `Converse` task with the new message. :::note Starting from version 8.7.0, the Amazon Bedrock connector supports consuming documents as inputs for conversations. Review the **Document** field in the properties panel where the document reference can be provided. See additional details and limitations in [document handling](/components/document-handling/getting-started.md). ::: ## Extract data from the response The `InvokeModel` and `Converse` actions return different response shapes, so the FEEL expression you write in **Result Expression** differs between them. The following examples use an Anthropic Claude model and assume the reserved `response` variable, which holds the connector's raw output. ### InvokeModel response shape The connector wraps the raw AWS response in a `body` field without changing it, so `response.body` holds exactly what the model returns. For Anthropic Claude models, `response.body` follows the [Anthropic Claude Messages API response format](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages-request-response.html): | Field | Description | | ---------------- | ---------------------------------------------- | | `content` | Array of content blocks generated by the model | | `content[].type` | Content block type, typically `text` | | `content[].text` | Generated text for a `text` content block | For a simple text prompt, the model returns a single content block, so you can extract the assistant's reply into a process variable named `AIResponse` as follows: ```feel = { AIResponse: response.body.content[1].text } ``` :::note Other model families, such as Amazon Titan and Nova, Meta Llama, and Cohere, return a different `InvokeModel` response shape. Check the [model parameters documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters.html) for the model you use before writing a Result Expression. ::: ### Converse response shape The `Converse` action doesn't return the raw AWS Converse API response. Instead, it returns the full, updated conversation as an array of messages, adding the assistant's reply to any message history you passed in. Each message has a `role` (`user` or `assistant`) and a `contentList` array, and each item in `contentList` has a `text` field. The assistant's reply is always the last message in the returned array. Use FEEL's negative indexing to reference it regardless of how many prior messages the conversation contains: ```feel = { AIResponse: response[-1].contentList[1].text } ``` AWS recommends the [Converse API](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html#model-parameters-anthropic-claude-messages-overview) over `InvokeModel` when you want a process to work with more than one model family, because it uses one request and response shape across all Bedrock model families. ### Configure a system prompt For the `InvokeModel` action, you build the request yourself in the **Payload** field, so you can add a system prompt exactly as the model's native API expects. For Anthropic Claude models, add a top-level `system` field to the payload, as described in [system prompts for Anthropic Claude models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html#model-parameters-anthropic-claude-messages-system-prompts). The `Converse` action doesn't currently expose a system prompt field in the connector's element template. --- ## Amazon Comprehend connector :::info The **Amazon Comprehend connector** is available for `8.7.0` or later. ::: The **Amazon Comprehend connector** allows you to integrate your BPMN service with [Amazon Comprehend](https://docs.aws.amazon.com/comprehend/latest/dg/what-is.html), a service which extracts insights about the content of documents, such as personal identifiable information and key phrases. ## Prerequisites To use the **Amazon Comprehend connector**, you must have an **AWS IAM Access Key** and **Secret Key** with the appropriate Comprehend permissions. Refer to the [AWS Comprehend set up instructions](https://docs.aws.amazon.com/comprehend/latest/dg/setting-up.html). :::note Use **secrets** to avoid exposing your AWS IAM credentials as plain text. Refer to [managing secrets](/components/saas/clusters/manage-secrets.md) for more details. ::: ## Create an Amazon Comprehend connector task ## Make your Amazon Comprehend connector executable To execute the **Amazon Comprehend connector**, ensure all mandatory fields are correctly filled. ## 1. Authentication Choose an authentication type from the **Authentication** dropdown. For details on the different authentication types, refer to the [appendix](#aws-authentication-types). If you select **Credentials**, the following fields must be provided: - **Access Key**: The AWS access key for a user with Comprehend permissions. - **Secret Key**: The corresponding AWS secret key. Both **Access Key** and **Secret Key** are required to use the connector. ## 2. **Configuration (AWS Region)** After authentication, set the AWS **Region** where the Textract service is hosted: - **Region**: Specify the region (for example, `us-east-1`, `eu-west-1`). :::note Ensure the region matches the location of your Comprehend service and S3 buckets to reduce latency and meet compliance requirements. For a full list of AWS regions, refer to the [AWS regional data](https://aws.amazon.com/about-aws/global-infrastructure/regions_az/). ::: ## 3. Configure input ### Execution types Select the desired execution type from the **Execution Type** dropdown. The following options are available: - **Sync** Use **Sync** execution to create a classification request and analyze a single document in real-time. For more details, refer to [sync execution](https://docs.aws.amazon.com/comprehend/latest/APIReference/API_ClassifyDocument.html). - **Async** Use **Async** execution to start an asynchronous document classification job using a custom classification model. This method allows you to submit a document for analysis and receive results at a later time, making it ideal for background processing or batch operations. **Async** execution enables you to process documents without waiting for immediate responses. This is particularly useful for larger files or when handling multiple documents simultaneously. For more details on the fields that can be configured during asynchronous execution, refer to [async execution](https://docs.aws.amazon.com/comprehend/latest/APIReference/API_StartDocumentClassificationJob.html). ### Sync execution fields - **Text (mandatory)**: The document text to be analyzed. - **Endpoint Arn (mandatory)**: The Amazon Resource Number (ARN) of the endpoint. For more details, refer to [Classify Document](https://docs.aws.amazon.com/comprehend/latest/APIReference/API_ClassifyDocument.html#API_ClassifyDocument_RequestSyntax). ### Async execution fields - **Document read mode**: Determines the text extraction actions for PDF files. For more details, refer to [document read mode](https://docs.aws.amazon.com/comprehend/latest/APIReference/API_DocumentReaderConfig.html#comprehend-Type-DocumentReaderConfig-DocumentReadMode). - **Document read action**: This field defines the Amazon Textract API operation that Amazon Comprehend uses to extract text from PDF files and image files. For more details, refer to [document read action](https://docs.aws.amazon.com/comprehend/latest/APIReference/API_DocumentReaderConfig.html#comprehend-Type-DocumentReaderConfig-DocumentReadAction). - **Analyze tables**: Returns additional information about any tables that are detected in the input document. For more details, refer to [feature types](https://docs.aws.amazon.com/comprehend/latest/APIReference/API_DocumentReaderConfig.html#comprehend-Type-DocumentReaderConfig-FeatureTypes). - **Analyze forms**: Returns additional information about any forms that are detected in the input document. For more details, refer to [feature types](https://docs.aws.amazon.com/comprehend/latest/APIReference/API_DocumentReaderConfig.html#comprehend-Type-DocumentReaderConfig-FeatureTypes). - **Inputs' S3 URI (mandatory)**: The Amazon S3 URI for the input data. For more details, refer to [S3 URI](https://docs.aws.amazon.com/comprehend/latest/APIReference/API_InputDataConfig.html#comprehend-Type-InputDataConfig-S3Uri). - **Input file processing mode**: Specifies how the text in an input file should be processed. For more details, refer to [InputFormat](https://docs.aws.amazon.com/comprehend/latest/APIReference/API_InputDataConfig.html#comprehend-Type-InputDataConfig-InputFormat). - **Client request token**: A unique identifier for the request. If you do not set the client request token, Amazon Comprehend generates one. - **Data Access Role's ARN (mandatory)**: The Amazon Resource Name (ARN) of the IAM role that grants Amazon Comprehend read access to your input data. - **Document Classifier's ARN**: The Amazon Resource Name (ARN) of the document classifier to use to process the job. - **Flywheel's ARN**: The Amazon Resource Number (ARN) of the flywheel associated with the model to use. - **Job name**: The identifier of the job. - **Output's S3 URI (mandatory)**: The Amazon S3 location where you want to write the output data. For more details, refer to [output data config](https://docs.aws.amazon.com/comprehend/latest/APIReference/API_OutputDataConfig.html). - **Outputs KMS Key Id**: The ID for the AWS Key Management Service (KMS) key that Amazon Comprehend uses to encrypt the output results from an analysis job. For more details, refer to [output data config](https://docs.aws.amazon.com/comprehend/latest/APIReference/API_OutputDataConfig.html). - **Tags**: Tags to associate with the document classification job. A tag is a key-value pair that adds metadata to a resource used by Amazon Comprehend. **Example:** ```feel = {"status": "active"} ``` - **VolumeKmsKeyId**: ID for the AWS Key Management Service (KMS) key that Amazon Comprehend uses to encrypt data on the storage volume attached to the ML compute instance(s) that process the analysis job. - **Security group Ids**: The ID number for a security group on an instance of your private VPC. For more details, refer to [security group](https://docs.aws.amazon.com/comprehend/latest/APIReference/API_VpcConfig.html#comprehend-Type-VpcConfig-SecurityGroupIds). **Example:** ```feel = ["sg-07a2cc6d96e4ec178"] ``` - **Subnets**: The ID for each subnet being used in your private VPC. For more details, refer to [Subnets](https://docs.aws.amazon.com/comprehend/latest/APIReference/API_VpcConfig.html#comprehend-Type-VpcConfig-Subnets). **Example:** ```feel = ["subnet-013eac53274e1d93f"] ``` :::note To use **VPC** you need at last one VPC endpoint For more details, refer to [create a VPC endpoint](https://docs.aws.amazon.com/vpc/latest/privatelink/create-interface-endpoint.html#create-interface-endpoint-aws). ::: ## Amazon Comprehend connector response The response from the **Amazon Comprehend connector** will mirror the AWS Comprehend service’s response. The type of response you receive depends on the execution mode selected: - **[Sync Response](https://docs.aws.amazon.com/comprehend/latest/APIReference/API_ClassifyDocument.html#API_ClassifyDocument_ResponseSyntax)**: Provides immediate analysis for provided text. - **[Asynchronous Response](https://docs.aws.amazon.com/comprehend/latest/APIReference/API_StartDocumentClassificationJob.html#API_StartDocumentClassificationJob_ResponseSyntax)**: Used for batch processing where results are returned later through job completion. ### Using the Comprehend connector response in your process The **Amazon Comprehend connector** provides the same response structure as the AWS Comprehend API. You can map fields from the response to process variables, depending on your needs. Here's an example of how to extract specific fields using **Result Expression** and **Result Variable**: ### Example Comprehend response (real-time execution) Utilize output mapping to align this response with process variables: 1. Use **Result Variable** to store the response in a process variable. For example, `myResultVariable`. This approach stores the entire Comprehend message as a process variable named `myResultVariable`. 2. Use **Result Expression** to map fields from the response into process variables. This approach allows for more granularity. Instead of storing the entire response in one variable, you can extract specific fields from the **Comprehend connector** message and assign them to different process variables. This is particularly useful when you are only interested in certain parts of the message, or when different parts of the message need to be used separately in your process. Example: ```json { "classes": [ { "name": "CHECKING_AC", "score": 0.5423, "page": null }, { "name": "SAVINGS_AC", "score": 0.4577, "page": null } ], "labels": null, "documentMetadata": null, "documentType": null, "errors": null, "warnings": null } ``` #### Mapping example To store only first **Classes** element information, use the following result **expression**: ```feel = {classInfo: classes[1]} ``` Mapped values **result**: ```json { "name": "CHECKING_AC", "score": 0.5422999858856201, "page": null } ``` ## Appendix & FAQ ### How do I securely store AWS IAM credentials for my Comprehend connector? Store your AWS IAM credentials as **secrets** to avoid exposing sensitive information. Follow our [managing secrets guide](/components/saas/clusters/manage-secrets.md) to learn more. ### AWS authentication types You can authenticate the **Amazon Comprehend connector** in two ways: - **Credentials**: Select this option if you have an AWS **Access Key** and **Secret Key**. This method is applicable for both SaaS and Self-Managed users. - **Default Credentials Chain (Hybrid/Self-Managed only)**: Select this option if your system uses implicit authentication methods like role-based access, environment variables, or files on the target host. This method is applicable only for Self-Managed or Hybrid environments. It uses the [Default Credential Provider Chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html) to resolve credentials. --- ## Amazon DynamoDB connector The **Amazon DynamoDB connector** allows you to connect your BPMN service with Amazon Web Service's [DynamoDB Service](https://aws.amazon.com/dynamodb/). This can be useful for performing CRUD operations on Amazon DynamoDB tables from within a BPMN process. ## Prerequisites To use the **Amazon DynamoDB connector**, you need to have an AWS account with an access key and secret key to access DynamoDB, as well as a region where your DynamoDB instance is located. You can create an account and obtain the access and secret keys from the [AWS Console](https://aws.amazon.com/console/). :::note Use secrets to store credentials so you don't expose sensitive information directly from the process. Refer to [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ::: ## Create an Amazon DynamoDB connector task ## Make your Amazon DynamoDB connector executable To work with **Amazon DynamoDB connector**, choose the required operation type in the **Operation** section and complete the mandatory fields highlighted in red in the connector properties panel on the right side of the screen. :::note All the mandatory and non-mandatory fields depending on the authentication selection you choose are covered in the upcoming sections. ::: ## Operation Choose an operation type of either **Table** or **Item** in the **Operation** section: - **Table**: Perform operations on a DynamoDB table. - **Item**: Perform operations on a specific item in a DynamoDB table. ### Method Choose one of the following methods: #### [Table](#table-operations) - [Create table](#create-table): Creates a new DynamoDB table. - [Delete table](#delete-table): Deletes an existing DynamoDB table. - [Describe table](#describe-table): Returns information about a DynamoDB table. - [Scan table](#scan-table): Returns one or more items and their attributes by accessing every item in a table. You can use filter expressions to selectively scan for items that meet certain criteria. #### [Item](#item-operations) - [Add item](#add-item): Creates a new item or replaces an existing item with a new item. - [Delete item](#delete-item): Deletes a single item in a table by primary key. - [Get item](#get-item): Returns a set of attributes for the item with the given primary key. - [Update item](#update-item): Modifies an existing item's attributes or adds a new item to the table if it does not already exist. ## Authentication Choose an applicable authentication type from the **Authentication** dropdown. Learn more about authentication types in the related [appendix entry](#aws-authentication-types). If you select **credentials** to access the **Amazon DynamoDB service**, the connector requires the appropriate credentials. The following authentication options are available: - **Access key**: Provide an access key of a user with permissions to the Amazon DynamoDB service. - **Secret key**: Provide the secret key of the user with the access key provided above. The Access Key and Secret Key are required properties and must be provided to use the connector. If these properties are not set, the connector will not be able to authenticate with the [DynamoDB Service](https://aws.amazon.com/dynamodb/). For more information on authentication and security in Amazon DynamoDB, refer to the [AWS documentation](https://docs.aws.amazon.com/dynamodb/index.html). ## Configuration The **Region** property in the **Configuration** section specifies the AWS region in which the DynamoDB table exists or will be created. This property is required and must be set to use the connector. For more information on AWS regions, refer to the [AWS documentation](https://docs.aws.amazon.com/general/latest/gr/rande.html). ## Input The **Input** section of the **Amazon DynamoDB connector** specifies the input data for the [selected operation](#operation). The input data varies depending on the [operation type](#operation) and [method](#method) selected. ### Table operations For the **Table** operation type, the following input data is required: #### Create table **Request** | Property name | Data type | Required | Description | | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------: | :------: | :------------------------------------------------------------------------------------: | | [Table name](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateTable.html#DDB-CreateTable-request-TableName) | string | Yes | The name of the DynamoDB table to be created. | | [Partition key](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateTable.html#DDB-CreateTable-request-KeySchema) | string | Yes | The attribute name of the partition key for the table. | | [Partition key role](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateTable.html#DDB-CreateTable-request-KeySchema) | dropdown | Yes | The role of the partition key. Can be set to "HASH" or "RANGE". | | [Partition key attribute data type](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeDefinition.html#DDB-Type-AttributeDefinition-AttributeType) | dropdown | Yes | The data type of the partition key attribute. | | [Sort key](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateTable.html#DDB-CreateTable-request-KeySchema) | string | No | The attribute name of the sort key for the table (if applicable). | | [Sort key role](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateTable.html#DDB-CreateTable-request-KeySchema) | dropdown | No | The role of the sort key. Can be set to "HASH" or "RANGE". | | [Sort key attribute data type](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeDefinition.html#DDB-Type-AttributeDefinition-AttributeType) | dropdown | No | The data type of the sort key attribute. | | [Read capacity units](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateTable.html#DDB-CreateTable-request-ProvisionedThroughput) | number | Yes | The maximum number of strongly consistent reads per second that the table can support. | | [Write capacity units](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateTable.html#DDB-CreateTable-request-ProvisionedThroughput) | number | Yes | The maximum number of writes per second that the table can support. | | [Billing mode](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateTable.html#DDB-CreateTable-request-BillingMode) | dropdown | No | The billing mode of the table. Can be set to "PROVISIONED" or "PAY_PER_REQUEST". | | [Deletion protection](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateTable.html#DDB-CreateTable-request-GlobalSecondaryIndexUpdates) | dropdown | No | Indicates whether to enable or disable deletion protection for the table. | **Response** | Property | Data type | Description | | :-----------------------------------------------------------------------------------------------------------: | :-------: | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | [Table description](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TableDescription.html) | Object | Information about the created table, including the table name, attribute definitions, primary key schema, provisioned throughput settings, and more. | #### Delete table **Request** | Property name | Data type | Required | Description | | :------------------------------------------------------------------------------------------------ | :-------: | :------: | :-------------------------------------------: | | [Table name](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DeleteTable.html) | string | Yes | The name of the DynamoDB table to be deleted. | **Response** | Property | Data type | Description | | :------- | :-------: | :-----------------------------------------------------------------------------------------------------------------------------------: | | action | string | The action performed. In this case, it will always be "delete Table [tableName]", where `tableName` is the name of the deleted table. | | status | string | The status of the operation. In this case, it will always be "OK" to indicate that the table was successfully deleted. | #### Describe table **Request** | Property name | Data type | Required | Description | | :-------------------------------------------------------------------------------------------------- | :-------: | :------: | :---------------------------------------------: | | [Table name](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeTable.html) | string | Yes | The name of the DynamoDB table to be described. | **Response** | Property | Data type | Description | | :-----------------------------------------------------------------------------------------------------------: | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------- | | [Table description](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TableDescription.html) | Object | Information about the table, including the table name, attribute definitions, primary key schema, provisioned throughput settings, and more. | #### Scan table **Request** | Property name | Data type | Required | Description | | ----------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [Table name](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-TableName) | string | Yes | The name of the DynamoDB table to be scanned. | | [Filter expression](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/QueryAndScan.html#FilterExpression) | string | No | The filter expression to apply to the scan results. For more information, refer to the [Expression Attribute Names and Values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ExpressionAttributeNames.html) section of the Amazon DynamoDB Developer Guide. | | [Projection expression](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/QueryAndScan.html#ProjectionExpression) | string | No | A string that identifies one or more attributes to retrieve from the specified table. | | [Expression attribute names](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ExpressionPlaceholders.html#ExpressionAttributeNames) | map | No | A map of attribute names to their replacements in the filter expression or projection expression. For more information, refer to the [Expression Attribute Names and Values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ExpressionAttributeNames.html) section of the Amazon DynamoDB Developer Guide. | | [Expression attribute values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ExpressionPlaceholders.html#ExpressionAttributeValues) | map | No | A map of attribute values to their replacements in the filter expression or projection expression. For more information, refer to the [Expression Attribute Names and Values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ExpressionAttributeValues.html) section of the Amazon DynamoDB Developer Guide. | **Response** | Property | Data type | Description | | :------- | :-------: | :---------------------------------------------------------------------------------------------------------------------------------------: | | action | string | The action performed. In this case, it will always be `scanTable`. | | status | string | The status of the operation. If successful, it will always be "OK". Otherwise, it will be an error message. | | items | list | The list of items returned by the scan operation, in case the operation was successful. If there are no items, this field will be `null`. | ### Item operations :::note The **Amazon DynamoDB connector** does not currently support binary data types. If binary data is input during the creation or update of items, it will be saved as a string. When updating items, if an attribute of type SET is updated, it will be overwritten and saved as a list type. Consider these limitations to prevent unintended data structure modifications in your DynamoDB tables. ::: #### Add item **Request** | Property name | Data type | Required | Description | | --------------------------------------------------------------------------------------------------------------------------- | --------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Table name](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html#DDB-PutItem-request-TableName) | string | Yes | The name of the DynamoDB table to add the item to. | | [Item](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html#DDB-PutItem-request-Item) | object | Yes | The item to add to the table is represented in JSON format. For example: `{"Name": "Example Item", "ID": "123", "Description": "This is an example item"}`.This JSON object succinctly represents the item's attributes through straightforward key-value pairs, without the need to explicitly mention data types. | **Response** | Property | Data type | Description | | ---------------------------------------------------------------------------------------------------------------------- | --------- | ----------------------------- | | [Result](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html#API_PutItem_ResponseElements) | object | The item to add to the table. | #### Delete item **Request** | Property name | Data type | Required | Description | | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -------- | ------------------------------------------------------------ | | [Table name](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DeleteItem.html#DDB-DeleteItem-request-TableName) | string | Yes | The name of the DynamoDB table to delete the item from. | | [Primary Key Components](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html#HowItWorks.CoreComponents.PrimaryKey) | object | Yes | The primary key components that identify the item to delete. | **Response** | Property | Data type | Description | | ---------------------------------------------------------------------------------------------------------------------------------- | --------- | ----------------------------------------------------------------------------- | | [Deleted Item](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DeleteItem.html#API_DeleteItem_ResponseElements) | object | The item that was deleted. This field will be null if the item was not found. | #### Get item **Request** | Property Name | Data type | Required | Description | | --------------------------------------------------------------------------------------------------------------------------------- | --------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Table name](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html#DDB-GetItem-request-TableName) | string | Yes | The name of the table containing the requested item. | | [Primary key components](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html#DDB-GetItem-request-Key) | object | Yes | A map of attribute names to `AttributeValue` objects, representing the primary key of the item to retrieve. For the primary key, you must provide all the attributes. For example, with a simple primary key, you only need to provide a value for the partition key. For a composite primary key, you must provide values for both the partition key and the sort key. | **Response** | Property | Data type | Description | | ----------------------------------------------------------------------------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Attributes](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html#DDB-GetItem-response-Item) | object | A map of attribute names to `AttributeValue` objects, representing the item retrieved. If there is no matching item, the response will contain only the consumed capacity, and a null attributes field. The keys of the attributes map correspond to the column names of the table | #### Update item **Request** | Property name | Data type | Required | Description | | ---------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------- | | [Table name](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html#DDB-UpdateItem-request-TableName) | string | Yes | The name of the table to update the item in. | | [Primary key components](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html#DDB-UpdateItem-request-Key) | map | Yes | A map of attribute names to `AttributeValue` objects, representing the primary key of the item to update. | | [Key attributes](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html#DDB-UpdateItem-request-AttributeUpdates) | map | Yes | A map of attribute names to `AttributeValue` objects, representing the attributes to update. | | [Attribute action](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html#DDB-UpdateItem-request-AttributeUpdates) | dropdown | No | Dropdown option for each attribute to be updated, allowing selection between "PUT" (add or replace) and "DELETE" (remove) | **Response** | Property | Data type | Description | | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [UpdateItemOutcome](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBMapper.Methods.html#DynamoDBMapper.Methods.updateItem) | object | An object representing the outcome of the `UpdateItem` operation. The `UpdateItemOutcome` object contains the updated attributes of the item, as well as other metadata about the operation, such as the consumed capacity. | ## Request example | Section | Field | Description | Example value | | -------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | Operation | Category | Choose the category of the operation to be performed. | Item | | | Action | Select the specific action to update an item in the DynamoDB table. | Update item | | Authentication | Authentication type | The method of AWS authentication; credentials are used here. | Credentials | | | Access key | An example of an AWS access key. | `AKIAU3GOTH...JBYX` | | | Secret key | An example of an AWS secret key. | `bZ/LPpqaw...0igikS` | | | Region | The AWS region where the DynamoDB table is located. | `us-east-1` | | Input | Table name | The name of the DynamoDB table to be updated. | `test` | | | Primary key components | The primary key component(s) of the item to be updated. | `{"id": "5"}` | | | Key attributes | JSON object representing the new values for the item attributes. | `{ "stringValue": "StringValue", "numberValue": 42, "booleanValue": true }` | | | Attribute action | The action to be performed on the attributes. Here it's set to PUT, which means the specified attributes will be added or updated. | PUT | | Output mapping | Result variable | The name of the variable that will store the response from DynamoDB. | `result` | | | Result expression | The FEEL expression used to map the DynamoDB response to process variables. Not provided in the screenshots. | - | ## Response Mapping When using the DynamoDB connector, the response from the DynamoDB connector will be available in a temporary local `response` variable. This variable can be mapped to the process by specifying the **Result Variable**. For example, if you use the **Update Item** method in the DynamoDB connector, the response may look like this: ```json { "action": "updateItem", "status": "OK", "response": { "Attributes": { "ID": { "N": "3" }, "price": { "N": "10" } } } } ``` In this example, the `response` variable contains an `Attributes` object with the updated values for the specified item. The following fields are available in the `response` variable: - `action`: The action that was performed by the DynamoDB connector. - `status`: The status of the response, which will be "OK" if the operation was successful. - `response`: The response from the DynamoDB service, which will contain the updated attributes of the specified item. You can choose to unpack the content of your `response` into multiple process variables using the **Result Expression**, which is a [FEEL Context Expression](/components/modeler/feel/language-guide/feel-context-expressions.md). The **Result Expression** allows you to access specific attributes from the response and assign them to process variables that can be used in subsequent steps of your process. ```feel = { id: response.response.Attributes.ID.N, price: response.response.Attributes.price.N } ``` In this example, we are using the **Result Expression** to extract the **ID** and **price** attributes from the response variable and assign them to the ID and price process variables, respectively. You can then use these variables in subsequent steps of your process. :::note The syntax for accessing attributes in the **Result Expression** may vary depending on the structure of your response object. You can refer to the [FEEL Context Expression](/components/modeler/feel/language-guide/feel-context-expressions.md) documentation for more information on how to use the **Result Expression**. ::: ## Error handling The **Amazon DynamoDB connector** may throw the following exceptions: - AwsDynamoDbConnectionException: Thrown if there is an error connecting to DynamoDB. - AwsDynamoDbExecutionException: Thrown if there is an error executing a DynamoDB operation. - AwsDynamoDbConfigurationException: Thrown if the connector is not properly configured. All of these checked exceptions are wrapped in a `RuntimeException`, so be prepared to handle this type of exception as well. ## Troubleshooting If you are having issues with the **Amazon DynamoDB connector**, try the following: - Ensure your AWS credentials are correct. - Ensure your DynamoDB table exists and is located in the specified region. - Ensure your configuration properties are set correctly. - Check the logs for any error messages. - Contact [Camunda support](https://camunda.com/services/support/) if you need further assistance. For more information on Amazon DynamoDB, visit the [official documentation](https://docs.aws.amazon.com/dynamodb/). ## Using DynamoDB connector best practice When using the DynamoDB connector in a BPMN process, it is important to keep in mind that there is no guarantee that a requested item will be retrieved or updated immediately. In this case, it is recommended to build your BPMN diagram to periodically retry polling until the item is available. :::note To avoid performance issues, it is recommended to limit the number of retries. ::: To learn more about implementing retry logic in your BPMN diagram, you can refer to the [Camunda BPMN examples](https://camunda.com/bpmn/examples/) page, which includes examples of BPMN diagrams with timer and loop configurations. ## Appendix ### AWS authentication types There are two options to authenticate the connector with AWS: - Choose **Credentials** in the **Authentication** dropdown if you have a valid pair of access and secret keys provided by your AWS account administrator. This option is applicable for both SaaS and Self-Managed users. - Choose **Default Credentials Chain (Hybrid/Self-Managed only)** in the **Authentication** dropdown if your system is configured as an implicit authentication mechanism, such as role-based authentication, credentials supplied via environment variables, or files on target host. This option is applicable only for Self-Managed or hybrid distribution. This approach uses the [Default Credential Provider Chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html) to resolve required credentials. --- ## Amazon EventBridge connector The **Amazon EventBridge connector** integrates your BPMN service with [Amazon EventBridge](https://aws.amazon.com/eventbridge/), enabling the sending of events from your workflows for further processing or routing to other AWS services. It provides seamless event-driven integration within your business processes. For more information, refer to the [Amazon EventBridge documentation](https://docs.aws.amazon.com/eventbridge/index.html). ## Prerequisites Before using the **Amazon EventBridge connector**, ensure you have the necessary permissions in your AWS account to send events to EventBridge. You will need an access key and secret key of a user with the appropriate permissions. Refer to the [AWS documentation](https://docs.aws.amazon.com/eventbridge/latest/userguide/auth-and-access-control-eventbridge.html) for more information. :::note Use secrets to avoid exposing your AWS IAM credentials as plain text. Refer to our documentation on [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ::: ## Create an Amazon EventBridge connector task ## Configure the Amazon EventBridge connector Follow these steps to configure the Amazon EventBridge connector: 1. Choose an applicable authentication type from the **Authentication** dropdown. Learn more about authentication types in the related [appendix entry](#aws-authentication-types). 2. In the **Authentication** section, enter the relevant IAM key and secret pair of the user with permissions to send events to [Amazon EventBridge](https://aws.amazon.com/eventbridge). 3. In the **Configuration** section, specify the AWS region where your EventBridge resides. 4. In the **Event Details** section, provide the following information: - **Event bus name**: Enter the name of the destination event bus. Refer to the [Amazon EventBridge documentation](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-event-bus.html) for more details on event buses. - **Source**: Enter the value that identifies the service that generated the event. - **Detail type**: Enter the type of event being sent. Refer to the [Amazon documentation](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-events-structure.html) for more information on these properties. 5. In the **Event Payload** section, enter a JSON object that contains information about the event. 6. (Optional) In the **Output Mapping** section, you can set a **Result variable** or **Result expression**. Refer to the [response mapping documentation](/components/connectors/use-connectors/index.md#response-mapping) to learn more. 7. (Optional) In the **Error Handling** section, define the **Error expression** to handle errors that may occur during the event sending process. Refer to the [response mapping documentation](/components/connectors/use-connectors/index.md#bpmn-errors-and-failing-jobs) to learn more. ## Amazon EventBridge connector response The **Amazon EventBridge connector** returns the [original response](https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutEvents.html) from the Amazon EventBridge service, including the **sdkResponseMetadata** and **sdkHttpMetadata**. Here is an example of the response: ```json { "sdkResponseMetadata": { "requestId": "766647a2-835a-418b-9161-94245d0c93a3" }, "sdkHttpMetadata": { "httpHeaders": { "Content-Length": "85", "Content-Type": "application/x-amz-json-1.1", "Date": "Fri, 23 Jun 2023 08:39:22 GMT", "x-amzn-RequestId": "766647a2-835a-418b-9161-94245d0c93a3" }, "httpStatusCode": 200, "allHttpHeaders": { "x-amzn-RequestId": ["766647a2-835a-418b-9161-94245d0c93a3"], "Content-Length": ["85"], "Date": ["Fri, 23 Jun 2023 08:39:22 GMT"], "Content-Type": ["application/x-amz-json-1.1"] } }, "failedEntryCount": 0, "entries": [ { "eventId": "bb86b1af-9abb-0f8e-28c2-c69c24c35e05", "errorCode": null, "errorMessage": null } ] } ``` ## Appendix ### AWS authentication types There are two options to authenticate the connector with AWS: - Choose **Credentials** in the **Authentication** dropdown if you have a valid pair of access and secret keys provided by your AWS account administrator. This option is applicable for both SaaS and Self-Managed users. - Choose **Default Credentials Chain (Hybrid/Self-Managed only)** in the **Authentication** dropdown if your system is configured as an implicit authentication mechanism, such as role-based authentication, credentials supplied via environment variables, or files on target host. This option is applicable only for Self-Managed or hybrid distribution. This approach uses the [Default Credential Provider Chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html) to resolve required credentials. ## Next steps - [Amazon EventBridge documentation](https://docs.aws.amazon.com/eventbridge/) - Learn about [other connectors available](./available-connectors-overview.md) in Camunda to integrate with different systems and services. - Learn more about [using connectors](../use-connectors/index.md). The **Amazon EventBridge Webhook connector** is an inbound connector enabling you to start a BPMN process instance triggered by an event from [Amazon EventBridge](https://aws.amazon.com/eventbridge/). ## Create an Amazon EventBridge Webhook connector task 1. Start building your BPMN diagram. You can use the **Amazon EventBridge Webhook connector** with either a **Start Event** or an **Intermediate Catch Event** building block. 2. Select the applicable element and change its template to an **Amazon EventBridge connector**. 3. Fill in all required properties. 4. Complete your BPMN diagram. 5. Deploy the diagram to activate the event trigger. ## Configure the Amazon EventBridge Webhook connector ### Fill properties in the Webhook Configuration section 1. Choose one of the required methods in the **Webhook method** property. For example, if you know the webhook will be triggered by the **POST** method, choose **POST**. Alternatively, if it is not essential to specify a specific method for the webhook trigger, select **ANY**. 2. Configure the **Webhook ID**. By default, the **Webhook ID** is pre-filled with a random value. This value will be part of the Webhook URL. For more details about Webhook URLs, refer to the section below on [activating the Amazon EventBridge Webhook connector by deploying your diagram](#activate-the-amazon-eventbridge-connector-by-deploying-your-diagram). 3. (Optional) Fill in the **Event Bus Name** property if you want to specify a specific event bus to subscribe to. If left empty, the default event bus will be used. ### Fill properties in the Authorization section The Amazon EventBridge Webhook connector supports four types of authorization: - **None (without authorization)**: No authentication is required for the webhook. Anyone can trigger the webhook without any credentials. - **JWT (JSON Web Token)**: This authorization type requires the following properties to be filled: - **JWK URL**: A link to the JSON Web Key (JWK) Set containing the public keys used to verify the JWT signature. [Learn more about JWK](https://datatracker.ietf.org/doc/html/rfc7517). - **JWT Role Property Expression** (optional): An expression to extract the roles from the JWT token. These roles will be used to check against the **Required Roles** property. For example, the expression could be: ``` =if admin = true then ["admin"] else roles ``` - **Required Roles** (optional): A list of roles to test JWT roles against. If provided, the webhook will only be triggered if the JWT token contains at least one of the required roles. For example, if the required role is "admin", the property could be: ``` ["admin"] ``` - **Basic**: This authorization type requires the following properties to be filled: - **Username**: The username to authenticate the webhook. - **Password**: The password associated with the provided username. - **API Key**: This authorization type requires the following properties to be filled: - **API Key**: The API key that needs to be provided in the request to authenticate the webhook. - **API Key Locator**: A FEEL expression that extracts the API key from the request. This expression is evaluated in the connector Runtime to retrieve the API key from the incoming request. For example, the API Key Locator could be: ``` =split(request.headers.authorization, " ")[2] ``` or ``` request.headers.mycustomapikey ``` Select the appropriate authorization type based on your security requirements and fill in the corresponding properties accordingly. ### Fill out the properties in the **Activation** and **Correlation** sections 1. (Optional) Configure the **Activation Condition**. This condition will be used to filter the events from the specified event source. For example, if an incoming Amazon EventBridge event has the following body: ``` { "version": "0", "id": "6d3d35b7-5bf2-43ec-9e55-5cfb27ad31b4", "detail-type": "MyEvent", "source": "custom.application", "account": "123456789012", "time": "2023-07-25T12:34:56Z", "region": "us-west-2", "resources": [], "detail": { "shipment": "123456789", "status": "received" } } ``` the Activation Condition value might look like this: ``` =(get value(request.body, "detail-type")="MyEvent" and request.body.detail.status="received") ``` This condition will trigger the Amazon EventBridge Webhook connector only when the detail-type is "MyEvent" and the status is "received". 2. When using the **Amazon EventBridge Webhook connector** with an **Intermediate Catch Event**, fill in the **Correlation key (process)** and **Correlation key (payload)**. - **Correlation key (process)** is a FEEL expression that defines the correlation key for the subscription. This corresponds to the **Correlation key** property of a regular **Message Intermediate Catch Event**. - **Correlation key (payload)** is a FEEL expression used to extract the correlation key from the incoming message. This expression is evaluated in the connector Runtime, and the result is used to correlate the message. - **Message ID expression** and **Message TTL** are optional properties that can be used to set the message ID and time-to-live for the incoming message. Refer to the [message ID expression](#message-id-expression) and [message TTL](#message-ttl) sections for more information. For example, if your correlation key is defined with a process variable named `myCorrelationKey`, and you want to correlate by the `shipment` property in the request detail, which contains: ```json { "version": "0", "id": "6d3d35b7-5bf2-43ec-9e55-5cfb27ad31b4", "detail-type": "MyEvent", "source": "custom.application", "account": "123456789012", "time": "2023-07-25T12:34:56Z", "region": "us-west-2", "resources": [], "detail": { "shipment": "123456789", "status": "received" } } ``` your correlation key settings will look like this: - **Correlation key (process)**: `=myCorrelationKey` - **Correlation key (payload)**: `=request.body.detail.shipment` #### Message ID expression The **Message ID expression** is an optional field that allows you to extract the message ID from the incoming message. The message ID serves as a unique identifier for the message and is used for message correlation. This expression is evaluated in the connector Runtime and the result is used to correlate the message. In most cases, it is not necessary to configure the **Message ID expression**. However, it is useful if you want to ensure message deduplication or achieve a certain message correlation behavior. Learn more about how message IDs influence message correlation in the [messages guide](../../../concepts/messages#message-correlation-overview). For example, if you want to set the message ID to the value of the `detail.transactionId` field in the incoming event, configure the **Message ID expression** as follows: ``` = request.body.detail.transactionId ``` #### Message TTL The **Message TTL** is an optional field that allows you to set the time-to-live (TTL) for the correlated messages. TTL defines the time for which the message is buffered in Zeebe before being correlated to the process instance (if it can't be correlated immediately). The value is specified as an ISO 8601 duration. For example, `PT1H` sets the TTL to one hour. Learn more about the TTL concept in Zeebe in the [message correlation guide](../../../concepts/messages#message-buffering). ## Activate the Amazon EventBridge connector by deploying your diagram Once you click **Deploy**, your Amazon EventBridge Webhook connector will be activated and ready to receive events. The URLs of the exposed Amazon EventBridge Webhooks adhere to the following pattern: `http(s):///webhooks/` - `` is the URL of the connectors component deployment. When using the Camunda 8 SaaS offering, this will typically contain your **region Id** and **cluster Id**, found in your client credentials under the **API** tab within your cluster. - `` is the ID (path) you configured in the properties of your Amazon EventBridge Webhook connector. :::note If you make changes to your Amazon EventBridge Webhook connector configuration, redeploy the BPMN diagram for the changes to take effect. ::: When you click on the event with the Amazon EventBridge Webhook connector applied to it, a new **Webhooks** tab will appear in the properties panel. This tab displays the URL of the Amazon EventBridge Webhook connector for every cluster where you have deployed your BPMN diagram. :::note The **Webhooks** tab is only supported in Camunda Hub as part of the Camunda 8 SaaS offering. You can still use the Amazon EventBridge Webhook connector in Desktop Modeler or with Camunda 8 Self-Managed. In that case, Amazon EventBridge Webhook connector deployments and URLs will not be displayed in Desktop Modeler. ::: ## Output mapping The **Output mapping** section allows you to configure the mapping of the event payload to the process variables. - Use the **Result variable** to store the event data in a process variable. For example, `myEventPayload`. - Use the **Result expression** to map specific fields from the event payload into process variables using [FEEL](/components/modeler/feel/what-is-feel.md). For example, given the Amazon EventBridge connector is triggered with an event payload like: ``` { "id": "6d3d35b7-5bf2-43ec-9e55-5cfb27ad31b4", "detail-type": "MyEvent", "source": "custom.application", "region": "us-west-2", "resources": [], "detail": { "event": "order_created", "customer_id": "12345", "order_total": 100.50 } } ``` and you would like to extract the `customer_id` and `order_total` as process variables `customerId` and `orderTotal`, the **Result Expression** might look like this: ``` = { customerId: request.body.detail.customer_id, orderTotal: request.body.detail.order_total } ``` ## Example of configuring Amazon EventBridge To configure Amazon EventBridge, follow the steps below: 1. Go to the [AWS Management Console](https://aws.amazon.com/console/). 2. Set the required permissions for EventBridge by navigating to: https://aws.permissions.cloud/iam/events. 3. Access Amazon EventBridge service by going to [Amazon EventBridge](https://aws.amazon.com/eventbridge/). 4. Click **Integration > API Destination**. 5. Switch to the **Connection** tab. 6. Create a new connection with the required authorization type (basic, API key, OAuth). 7. Now, create a new API destination with the following information: - Select the previously created **connection**. - Choose the appropriate **HTTP method**. - Specify the **API destination endpoint**, which should be the webhook URL generated after deploying the BPMN diagram. 8. Create a new event bus by following the documentation [here](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-event-bus.html). 9. Lastly, create a rule using the **API destination** that you already created. Refer to the [documentation](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-get-started.html) for guidance. ## Next steps - Learn more about [Amazon EventBridge](https://aws.amazon.com/eventbridge/) and its capabilities. - Explore other [Connectors available](./available-connectors-overview.md) in Camunda to integrate with different systems and services. - Learn more about using connectors [here](../use-connectors/index.md). - Learn more about inbound connectors [here](../use-connectors/inbound.md). --- ## Amazon S3 connector Use the Amazon S3 connector to upload, download, and delete objects in [Amazon Simple Storage Service (Amazon S3)](https://aws.amazon.com/S3/) from your BPMN process. ## About this connector The **Amazon S3 connector** is an outbound connector that works with documents. It can upload a document to a bucket, download an object from a bucket, and delete an object from a bucket. Starting from version 8.7.0, the connector uploads documents from (and downloads documents to) the Camunda document store. For details and limitations, see [document handling](/components/document-handling/getting-started.md). ## Prerequisites To use the **Amazon S3 connector**, you need an AWS account with an access key and secret key. The key needs the following permissions: - `GetObject` - `DeleteObject` - `PutObject` Learn more about Amazon S3 in the [Amazon Simple Storage Service Documentation](https://docs.aws.amazon.com/s3/). :::note Use secrets to store credentials and avoid exposing sensitive information from the process. See [managing secrets](/components/saas/clusters/manage-secrets.md). ::: ## Configuration ### Authentication | Property | Type | Required | Description | Example | | :------------- | :------------ | :-------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | | AWS credential | Configuration | No | Select a saved AWS credential with an optional default region, or enter the authentication details and a region in the fields below. Learn more about [credentials](/components/hub/organization/credentials/index.md). | | | Authentication | Dropdown | Yes, if no AWS credential is set | The AWS authentication strategy:**Credentials** (SaaS/Self-Managed): Use this option if you have a valid pair of access and secret keys provided by your AWS account administrator.**Default Credentials Chain** (Hybrid/Self-Managed only): Use this option if your system is configured for implicit authentication, such as role-based authentication, credentials supplied through environment variables, or files on the target host. This approach uses the [Default Credential Provider Chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html) to resolve the required credentials. | | | Access key | String | Yes, if **Authentication** is **Credentials** | The access key of an IAM user with permissions to the Amazon S3 actions. | | | Secret key | String | Yes, if **Authentication** is **Credentials** | The secret key of the IAM user with the access key above. | | ### Region | Property | Type | Required | Description | Example | | :------- | :----- | :------------------------------------------- | :--------------------------------------------------------------------------------------------------------- | :---------- | | Region | String | Yes, if no AWS credential is set | The region of the deployed endpoint. | `us-east-1` | | Region | String | Yes, if the credential has no default region | Overrides the default region of the selected AWS credential. Shown when an **AWS credential** is selected. | | ## Operations Select the operation in the **Action** dropdown. **Upload object** is selected by default. The properties that apply depend on the action. ### Upload object Upload a document to an Amazon S3 bucket. #### Input parameters | Property | Type | Required | Description | Example | | :--------------- | :------- | :------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | | AWS bucket | String | Yes | The targeted Amazon S3 bucket where the document is uploaded. | | | AWS key | String | No | The key that uniquely identifies the object in the bucket. If not set, the file name from the document metadata is used. | | | Document source | Dropdown | Yes | The [document source](/components/document-handling/send-document-to-external-system.md#document-sources): **Camunda Document**, **Inline Content**, or **From URL**. | | | Camunda document | String | Yes, for **Camunda Document** | A FEEL expression with the reference to the document in the Camunda document store. | | | Content | String | Yes, for **Inline Content** | The content of the document, built from process data. | | | File name | String | No, for **Inline Content** and **From URL** | The file name of the uploaded document. Shown for **Inline Content** and **From URL**. For **Inline Content**, a unique name is generated if you leave it empty. | | | Content type | String | No, for **Inline Content** | The MIME type of the content, for **Inline Content** only. If you leave it empty, the type is inferred from the extension of the file name, with `application/octet-stream` as fallback. | | | URL | String | Yes, for **From URL** | The URL of the external document. | | :::note Use **Inline Content** to build a file (for example, a `.json` error report) directly from process variables without first storing it in the Camunda document store. See [inline documents](/components/document-handling/send-document-to-external-system.md#inline-documents). ::: To learn more about Friendly Enough Expression Language (FEEL) expressions, see [what is FEEL?](/components/modeler/feel/what-is-feel.md). #### Output The following JSON response is returned after a successful document upload: - `bucket`: Echoes back the bucket of the uploaded document. - `key`: Echoes back the **AWS key** you set. - `link`: The document link, built from the bucket and the **AWS key** you set. :::note If you leave **AWS key** empty, the connector stores the object under the file name of the document. The `key` in the response is then `null`, and the `link` ends with `null`. ::: Example response: ```json { "bucket": "mybucket", "key": "test", "link": "https://mybucket.s3.amazonaws.com/test" } ``` ### Download object Download an object from an Amazon S3 bucket. #### Input parameters | Property | Type | Required | Description | Example | | :-------------- | :------- | :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | | AWS bucket | String | Yes | The targeted Amazon S3 bucket that the document is downloaded from. | | | AWS key | String | Yes | The key that uniquely identifies the object in the bucket. | | | Response format | Dropdown | Yes | How the downloaded content is returned. Select a [return format](/components/document-handling/send-document-to-external-system.md#return-formats): **Document reference**, **as text**, or **as JSON**. | | | Encoding | String | No, for **as text** | The character set used to decode the content. Shown when **Response format** is **as text**. The default is `UTF-8`. | | To learn more about FEEL expressions, see [what is FEEL?](/components/modeler/feel/what-is-feel.md). #### Output The following JSON response is returned after a successful document download: - `bucket`: Echoes back the bucket of the downloaded document. - `key`: Echoes back the unique key of the downloaded document. - `element`: An object that holds the downloaded content. Its field depends on the selected **Response format**: - **Document reference**: `element.document` contains a reference to the document created in the Camunda document store. - **as text**: `element.content` contains the content decoded as a string. - **as JSON**: `element.content` contains the content parsed as JSON. :::note **as text** and **as JSON** return the content directly in a process variable and are subject to a size guard (approximately 1.5 MiB). A larger object fails the job with an incident. Use **Document reference** for large files. **as JSON** fails the job when the content is not valid JSON. ::: The following examples show a successful download response. With **Document reference**: ```json { "bucket": "mybucket", "key": "report.json", "element": { "document": { "storeId": "in-memory", "documentId": "20f1fd6a-d8ea-403b-813c-e281c1193495", "metadata": { "contentType": "application/json", "size": 311032, "fileName": "report.json" }, "camunda.document.type": "camunda" } } } ``` With **as JSON**: ```json { "bucket": "mybucket", "key": "report.json", "element": { "content": { "testKey": "testValue" } } } ``` ### Delete object Delete an object from an Amazon S3 bucket. #### Input parameters | Property | Type | Required | Description | Example | | :--------- | :----- | :------- | :------------------------------------------------------------- | :------ | | AWS bucket | String | Yes | The targeted Amazon S3 bucket that the object is deleted from. | | | AWS key | String | Yes | The key that uniquely identifies the object in the bucket. | | To learn more about FEEL expressions, see [what is FEEL?](/components/modeler/feel/what-is-feel.md). #### Output The following JSON response is returned after a successful deletion: - `bucket`: Echoes back the bucket of the deleted document. - `key`: Echoes back the unique key of the deleted document. Example response: ```json { "bucket": "mybucket", "key": "document.txt" } ``` --- ## Amazon SageMaker connector :::info The **Amazon SageMaker connector** is available for `8.6.0-alpha2` or later. ::: The **Amazon SageMaker connector** is an outbound connector that allows you to interact with [Amazon SageMaker](https://aws.amazon.com/sagemaker/) from your BPMN process. ## Prerequisites To use the **Amazon SageMaker connector**, you need to have an AWS account with an access key and secret key to execute [`InvokeEndpoint`](https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_runtime_InvokeEndpoint.html) or [`InvokeEndpointAsync`](https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_runtime_InvokeEndpointAsync.html) actions. The necessary endpoints must be deployed beforehand and point to the operational model. Learn more on how to deploy real-time and asynchronous models in the [official SageMaker documentation](https://docs.aws.amazon.com/sagemaker/latest/dg/how-it-works-deployment.html). :::note Use secrets to store credentials and avoid exposing sensitive information directly from the process. Refer to [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ::: ## Create an Amazon SageMaker connector task ## Make your Amazon SageMaker connector executable To work with the **Amazon SageMaker connector**, fill all mandatory fields. ## Authentication Choose an applicable authentication type from the **Authentication** dropdown. Learn more about authentication types in the related [appendix entry](#aws-authentication-types). If you select **Credentials** to access the **Amazon SageMaker connector**, the connector requires the appropriate credentials. The following authentication options are available: - **Access key**: Provide an access key of a user with permissions to the Amazon SageMaker `InvokeEndpoint` and/or `InvokeEndpointAsync` actions. - **Secret key**: Provide the secret key of the user with the access key provided above. The **Access Key** and the **Secret Key** are required properties and must be provided to use the connector. For more information on authentication and security in Amazon SageMaker, refer to the [AWS Knowledge Center post](https://repost.aws/knowledge-center/sagemaker-minimum-permissions). ## Region In the **Region** field write the region of the deployed endpoint. ## Inference type Learn more about inferences at the [official Amazon SageMaker documentation page](https://docs.aws.amazon.com/sagemaker/latest/dg/deploy-model.html). ### Real-time Ensure you deployed your model with a real-time endpoint. - In the **Inference type** field, select **Real-time**. - In the **Endpoint name** field, enter your deployed real-time endpoint name. - In the **Payload** field, enter data that is required by your deployed model. - In the **Content type** field, enter the content type of the input. Be aware that the **Amazon SageMaker connector** currently supports only JSON-like content types. - In the **Accept** field, enter the content type of the return value. - Fill in the other fields as described in the [SageMaker documentation](https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_runtime_InvokeEndpoint.html). ### Asynchronous - In the **Inference type** field, select **Asynchronous**. - In the **Endpoint name** field, enter your deployed asynchronous endpoint name. - In the **Input location** field, enter an S3 URL where the inference payload is stored. - In the **Content type** field, enter the content type of the input. Unlike the real-time, this inference supports any kind of content type. - In the **Accept** field, enter the content type of the return value. - Fill in the other fields as described in the [SageMaker documentation](https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_runtime_InvokeEndpointAsync.html). ## Handle connector response ## Amazon SageMaker connector response The response of the **Amazon SageMaker connector** depends on the model deployed and an inference type. You can use an output mapping to map the response: 1. Use **Result Variable** to store the response in a process variable. For example, `myResultVariable`. 2. Use **Result Expression** to map fields from the response into process variables. ### Real-time inference response The response of the real-time inference depends on your model deployed. Refer to the [Amazon SageMaker documentation](https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_runtime_InvokeEndpoint.html#API_runtime_InvokeEndpoint_ResponseElements) to learn more. ### Asynchronous inference response The response of the real-time inference depends on your model deployed. Refer to the [AWS SageMaker documentation](https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_runtime_InvokeEndpointAsync.html#API_runtime_InvokeEndpointAsync_ResponseElements) to learn more. ## AWS authentication types There are two options to authenticate the connector with AWS: - Choose **Credentials** in the **Authentication** dropdown if you have a valid pair of access and secret keys provided by your AWS account administrator. This option is applicable for both SaaS and Self-Managed users. - Choose **Default Credentials Chain (Hybrid/Self-Managed only)** in the **Authentication** dropdown if your system is configured as an implicit authentication mechanism, such as role-based authentication, credentials supplied via environment variables, or files on target host. This option is applicable only for Self-Managed or hybrid distribution. This approach uses the [Default Credential Provider Chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html) to resolve required credentials. --- ## Amazon Simple Notification Service connector The **Amazon Simple Notification Service (SNS)** connector is an outbound connector that allows you to connect your BPMN service with [Amazon Simple Notification Service (SNS)](https://aws.amazon.com/sns/) to send messages. ## Prerequisites To use the **Amazon SNS connector**, you need to have an SNS Topic, IAM key, and secret pair with the `sns:Publish` policy relative to your SNS. :::note Use secrets to avoid exposing your AWS IAM credentials as plain text. Refer to our [appendix entry](#how-do-i-store-aws-iam-secrets-for-my-sns-connector) and the [SNS Developer Guide](https://docs.aws.amazon.com/sns/latest/dg/sns-using-identity-based-policies.html#sns-policy-keys) to learn more. ::: ## Create an Amazon SNS connector task ## Make your Amazon SNS connector for sending messages executable To make your Amazon SNS connector for sending messages executable, take the following steps: 1. Choose an applicable authentication type from the **Authentication** dropdown. Learn more about authentication types in the related [appendix entry](#aws-authentication-types). 2. Set the relevant IAM key and secret pair in the **Authentication** section. For example, `{{secrets.MY_AWS_ACCESS_KEY}}`. The value can be plain text, but this is not recommended due to security concerns. 3. In the **Topic Properties** section, set the topic ARN of your SNS topic as well as its region. 4. In the **Input message data** section, fill out the field **Message** with the data you would like to publish to the topic. The field requires FEEL input. 5. (Optional) In the **Input message data** section, fill out the field **Message attributes** to set optional message metadata. This field requires FEEL input. Refer to the relevant [appendix](#what-are-the-message-attributes-and-how-can-i-set-them) section to find out more about this field. 6. (Optional) In the **Input message data** section, fill out the field **Subject** to set optional message subject. FEEL input of the field is optional. Length must be less than 100 characters. 7. (FIFO only) For a FIFO type topic in Amazon SNS, a **Message Group ID** is required. This ID ensures that messages within the same group are delivered in sequence. The [Amazon SNS documentation on FIFO topics](https://docs.aws.amazon.com/sns/latest/dg/sns-fifo-topics.html) provides more details on Message Group ID usage. Additionally, an optional **Message Deduplication ID** can be provided. This is useful for message deduplication in FIFO topics and its necessity depends on the [deduplication settings of your SNS FIFO topic](https://docs.aws.amazon.com/sns/latest/dg/sns-message-deduplication.html). The Message Deduplication ID helps ensure Amazon SNS does not resend the same message within the deduplication interval. ## Amazon SNS connector response The **Amazon SNS connector** returns the SNS message identifier of a newly created message. The response contains a `messageId` variable. You can use an output mapping to map the response: 1. Use **Result Variable** to store the response in a process variable. For example, `myResultVariable`. 2. Use **Result Expression** to map fields from the response into process variables. For example: ``` = { "createdMessageID": response.messageId } ``` ## Appendix & FAQ ### What are the message attributes and how can I set them? Amazon SNS lets you include structured metadata (such as timestamps, geospatial data, signatures, and identifiers) with messages using message attributes. The **Amazon SNS connector** allows you to include non-binary message attributes in the **Input message data** section. The message attribute value must be composed to be compliant with Amazon SNS [message attribute data format](https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html). Example of a valid message attribute as a FEEL value: ``` = { "timestamp":{ "StringValue":today(), "DataType":"String" }, "messageSubmittedBy":{ "StringValue":"user12345", "DataType":"String" } } ``` ### How do I store AWS IAM secrets for my SNS connector? Use secrets to avoid exposing your AWS IAM credentials. Follow our documentation on [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ### AWS authentication types There are two options to authenticate the connector with AWS: - Choose **Credentials** in the **Authentication** dropdown if you have a valid pair of access and secret keys provided by your AWS account administrator. This option is applicable for both SaaS and Self-Managed users. - Choose **Default Credentials Chain (Hybrid/Self-Managed only)** in the **Authentication** dropdown if your system is configured as an implicit authentication mechanism, such as role-based authentication, credentials supplied via environment variables, or files on target host. This option is applicable only for Self-Managed or hybrid distribution. This approach uses the [Default Credential Provider Chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html) to resolve required credentials. The **Amazon Simple Notification Service (SNS) inbound connector** is a connector that allows you to start or continue a BPMN process triggered by an [Amazon SNS](https://console.aws.amazon.com/sns/home) notification. ## Create an Amazon SNS inbound connector task 1. Start building your BPMN diagram. You can use the **Amazon SNS inbound connector** with either a **Start Event** or **Intermediate Catch Event**. 2. Select the applicable element and change its template to an **Amazon SNS connector**. 3. Fill in all required properties. 4. Complete your BPMN diagram. 5. Deploy the diagram to activate the webhook. 6. Navigate to the **Webhooks** tab in the properties panel on the right side of the screen to observe the webhook URL. ## Make your Amazon SNS inbound connector for receiving notifications executable 1. In the **Subscription Configuration** section, configure the **Subscription ID**. By default, **Subscription ID** is pre-filled with a random value. This value will be a part of the topic subscription URL. 2. Set the **Allow to receive messages from topic(s)** value to **any** if the process may be triggered by any topic, or **Specific topic(s)** if you wish to allow-list only certain topics to start a new BPMN process. 3. If you have chosen the **Specific topic(s)**, you have to list comma-separated topics in the field **Topic ARN(s)** as well. In that case, the **Amazon SNS inbound connector** will auto-approve each qualified subscription request. 4. In the section **Activation**, configure **Condition** when the Amazon SNS topic can trigger a new BPMN process. The following example will trigger a new BPMN process for every notification with a subject _Start BPMN_: `=(request.body.Subject = "Start BPMN")`. 5. In the **Output mapping** section, fill in the **Result variable** field to store the response in a process variable. For example, `myResultVariable`. Alternatively, fill in the **Result expression** field to map specific fields from the response into process variables using [FEEL](/components/modeler/feel/what-is-feel.md). The following example will extract both the message and subject from an Amazon SNS message: `={message: request.body.Message, subject: request.body.Subject}`. ### Correlation The **Correlation** section allows you to configure the message correlation parameters. :::note The **Correlation** section is not applicable for the plain **start event** element template of the Amazon SNS connector. Plain **start events** are triggered by process instance creation and do not rely on message correlation. ::: #### Correlation key - **Correlation key (process)** is a FEEL expression that defines the correlation key for the subscription. This corresponds to the **Correlation key** property of a regular **Message Intermediate Catch Event**. - **Correlation key (payload)** is a FEEL expression used to extract the correlation key from the incoming message. This expression is evaluated in the connector Runtime and the result is used to correlate the message. For example, given that your correlation key is defined with `myCorrelationKey` process variable, and the request body contains `"MessageAttributes": {"attrName1" : {"Type":"String","Value":"attrVal"}}`, your correlation key settings will look like this: - **Correlation key (process)**: `=myCorrelationKey` - **Correlation key (payload)**: `=request.body.MessageAttributes.attrName1.Value` Learn more about correlation keys in the [messages guide](../../../concepts/messages). #### Message ID expression The **Message ID expression** is an optional field that allows you to extract the message ID from the incoming message. Message ID serves as a unique identifier for the message and is used for message correlation. This expression is evaluated in the connector Runtime and the result is used to correlate the message. In most cases, it is not necessary to configure the **Message ID expression**. However, it is useful if you want to ensure message deduplication or achieve certain message correlation behavior. Learn more about how message IDs influence message correlation in the [messages guide](../../../concepts/messages#message-correlation-overview). For example, if you want to set the message ID to the value of the `attrName1` attribute in the incoming event, configure the **Message ID expression** as follows: ``` = request.body.MessageAttributes.attrName1.Value ``` #### Message TTL The **Message TTL** is an optional field that allows you to set the time-to-live (TTL) for the correlated messages. TTL defines the time for which the message is buffered in Zeebe before being correlated to the process instance (if it can't be correlated immediately). The value is specified as an ISO 8601 duration. For example, `PT1H` sets the TTL to one hour. Learn more about the TTL concept in Zeebe in the [message correlation guide](../../../concepts/messages#message-buffering). ## Activate the Amazon SNS inbound connector by deploying your diagram Once you click the **Deploy** button, your **Amazon SNS inbound connector** will be activated and publicly available. URLs of the exposed **Amazon SNS Inbound connector** adhere to the following pattern: `https:///inbound/` - `` is the URL of connectors component deployment. When using the Camunda 8 SaaS offering, this will typically contain your **region Id** and **cluster Id**, found in your client credentials under the **API** tab within your cluster. - `` is the ID (path) you configured in the properties of your **Amazon SNS inbound connector**. If you make changes to your **Amazon SNS inbound connector** configuration, you need to redeploy the BPMN diagram for the changes to take effect. When you click on the event with **Amazon SNS inbound connector** applied to it, a new **Webhooks** tab will appear in the properties panel. This tab displays the URL of the **Amazon SNS inbound connector** for every cluster where you have deployed your BPMN diagram. :::note The **Webhooks** tab is only supported in Camunda Hub as part of the Camunda 8 SaaS offering. You can still use Amazon SNS inbound connectors in Desktop Modeler, or with Camunda 8 Self-Managed. In that case, Amazon SNS inbound connector deployments and URLs will not be displayed in Desktop Modeler. ::: ## Wiring with Amazon SNS 1. Sign in to the [Amazon SNS console](https://console.aws.amazon.com/sns/home). 2. On the navigation panel, choose **Topics**. 3. Choose the **Create** subscription. 4. In the **Protocol** drop-down list, select **HTTPS**. 5. In the **Endpoint** box, paste in the URL of the subscription found in at the **Webhooks** tab of your BPMN diagram that you want the topic to send messages. Then, choose **Create subscription**. 6. The confirmation message is displayed. Choose **Close**. Your new subscription's **Subscription ID** displays **PendingConfirmation**. Shortly after it will be confirmed by the BPMN process assuming **Allow to receive messages from topic(s)** contains the SNS topic ARN. ## Security considerations ### Access control The field **Allow to receive messages from topic(s)** and related **Topic ARN(s)** allows you to control which Amazon SNS topics can trigger a BPMN process. You can also achieve the same outcome by specifying **Condition** in the **Activation** section. For example, given **Topic ARN(s)** equals `arn:aws:sns:eu-central-1:1234567890:SNSWebhook`, is the same as **Condition** equals `=(request.body.TopicArn = "arn:aws:sns:eu-central-1:1234567890:SNSWebhook")`. ### Integrity Each Amazon SNS message is digitally signed with an AWS private key. The body of a message contains a digital signature of the entire content. The **Amazon Simple Notification Service (SNS) Inbound connector** verifies every message against the Amazon SNS public certificate to ensure the message is of known origin and has not been tampered with. ## Appendix ### Amazon SNS Subscription message example ``` POST https:///inbound/ connection: close accept-encoding: gzip,deflate user-agent: Amazon Simple Notification Service Agent host: content-length: 9999 content-type: text/plain; charset=UTF-8 x-amz-sns-topic-arn: arn:aws:sns:eu-central-1:1234567890:SNSWebhook x-amz-sns-message-id: b9b4574f-b4ab-4c03-ac14-a3145896747f x-amz-sns-message-type: SubscriptionConfirmation { "Type": "SubscriptionConfirmation", "MessageId": "b9b4574f-b4ab-4c03-ac14-a3145896747f", "Token": "233...18b", "TopicArn": "arn:aws:sns:eu-central-1:1234567890:SNSWebhook", "Message": "You have chosen to subscribe to the topic arn:aws:sns:eu-central-1:1234567890:SNSWebhook.\nTo confirm the subscription, visit the SubscribeURL included in this message.", "SubscribeURL": "https://sns.eu-central-1.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:eu-central-1:1234567890:SNSWebhook&Token=233...18b", "Timestamp": "2023-04-26T15:04:47.883Z", "SignatureVersion": "1", "Signature": "u+0i/F/+qew...zw==", "SigningCertURL": "https://sns.eu-central-1.amazonaws.com/SimpleNotificationService-56e67fcb41f6fec09b0196692625d385.pem" } ``` ### Amazon SNS Notification message example ``` POST https:///inbound/ connection: close accept-encoding: gzip,deflate user-agent: Amazon Simple Notification Service Agent host: webhook.site content-length: 1046 x-amzn-trace-id: Root=1-64493ecd-dcfadf2f053429acb884eee3;Sampled=1 content-type: text/plain; charset=UTF-8 x-amz-sns-subscription-arn: arn:aws:sns:eu-central-1:1234567890:SNSWebhook:4aa14ec3-a492-4a8e-8247-ea658d1aad96 x-amz-sns-topic-arn: arn:aws:sns:eu-central-1:1234567890:SNSWebhook x-amz-sns-message-id: 2e062e6b-a527-5e68-b69b-72a8e42add60 x-amz-sns-message-type: Notification { "Type" : "Notification", "MessageId" : "2e062e6b-a527-5e68-b69b-72a8e42add60", "TopicArn" : "arn:aws:sns:eu-central-1:1234567890:SNSWebhook", "Subject" : "Subject - test", "Message" : "Hello, world", "Timestamp" : "2023-04-26T15:10:05.479Z", "SignatureVersion" : "1", "Signature" : "a2w...A==", "SigningCertURL" : "https://sns.eu-central-1.amazonaws.com/SimpleNotificationService-56e67fcb41f6fec09b0196692625d385.pem", "UnsubscribeURL" : "https://sns.eu-central-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-central-1:1234567890:SNSWebhook:4aa14ec3-a492-4a8e-8247-ea658d1aad96", "MessageAttributes" : { "attrName1" : {"Type":"String","Value":"attrVal"} } } ``` --- ## Amazon Simple Queue Service connector The **Amazon SQS connector** is an outbound connector that allows you to connect your BPMN service with [Amazon Simple Queue Service (SQS)](https://aws.amazon.com/sqs/) to send messages. ## Prerequisites To use the **Amazon SQS connector**, you need to have an SQS Queue, IAM key, and secret pair with the `sqs:SendMessage` policy relative to your SQS. :::note Use secrets to avoid exposing your AWS IAM credentials as plain text. Refer to an [appendix entry](#how-do-i-store-aws-iam-secrets-for-my-sqs-connector) and the [SQS Developer Guide](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-basic-examples-of-iam-policies.html) to learn more. ::: ## Create an Amazon SQS connector task ## Make your Amazon SQS connector for sending messages executable To make your Amazon SQS connector for sending messages executable, take the following steps: 1. Choose an applicable authentication type from the **Authentication** dropdown. Learn more about authentication types in the related [appendix entry](#aws-authentication-types). 2. Set the relevant IAM key and secret pair in the **Authentication** section. For example, `{{secrets.MY_AWS_ACCESS_KEY}}`. The value can be plain text, but this is not recommended due to security concerns. 3. In the **Queue Properties** section, set the URL of your SQS queue, its region, and its type. 4. In the **Input message data** section, fill the **Message body** with the data you would like to submit to the queue. The field requires FEEL input. 5. (Optional) In the **Input message data** section, fill out the field **Message attributes** to set optional message metadata. This field requires FEEL input. Refer to the relevant [appendix](#what-are-the-message-attributes-and-how-can-i-set-them) section to find out more about this field. 6. (FIFO only) If you are using a queue of type **FIFO**, a [**Message Group ID** must be provided](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/using-messagegroupid-property.html). An optional **Message Deduplication ID** can be provided as well, depending on how you [configured](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/using-messagededuplicationid-property.html) the message deduplication of the queue. ## Amazon SQS connector response The **Amazon SQS connector** returns the SQS message identifier of a newly created message. The response contains a `messageId` variable. You can use an output mapping to map the response: 1. Use **Result Variable** to store the response in a process variable. For example, `myResultVariable`. 2. Use **Result Expression** to map fields from the response into process variables. For example: ``` = { "createdMessageID": response.messageId } ``` ## Appendix & FAQ ### What are the message attributes and how can I set them? Amazon SQS lets you include structured metadata (such as timestamps, geospatial data, signatures, and identifiers) with messages using message attributes. The **Amazon SQS connector** allows you to include non-binary message attributes in the section **Input message data**. The message attribute value must be composed to be compliant with Amazon SQS [message attribute data format](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-message-metadata.html#sqs-message-attributes). Example of a valid message attribute as a FEEL value: ``` = { "timestamp":{ "StringValue":today(), "DataType":"String" }, "messageSubmittedBy":{ "StringValue":"user12345", "DataType":"String" } } ``` ### How do I store AWS IAM Secrets for my SQS connector? Store your AWS IAM credentials as secrets to avoid exposing sensitive information. Follow our documentation on [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ### AWS authentication types There are two options to authenticate the connector with AWS: - Choose **Credentials** in the **Authentication** dropdown if you have a valid pair of access and secret keys provided by your AWS account administrator. This option is applicable for both SaaS and Self-Managed users. - Choose **Default Credentials Chain (Hybrid/Self-Managed only)** in the **Authentication** dropdown if your system is configured as an implicit authentication mechanism, such as role-based authentication, credentials supplied via environment variables, or files on target host. This option is applicable only for Self-Managed or hybrid distribution. This approach uses the [Default Credential Provider Chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html) to resolve required credentials. The **Amazon SQS Inbound connector** is an inbound connector that allows you to start or continue a BPMN process triggered by [Amazon Simple Queue Service (SQS)](https://aws.amazon.com/sqs/). ## Prerequisites Before using the Amazon SQS inbound connector, ensure you have the following: 1. An active SQS Queue in your AWS account. 2. IAM credentials with the necessary permissions to receive messages from the SQS Queue. Use secrets to store your AWS IAM credentials securely. Refer to the [secrets documentation](/components/saas/clusters/manage-secrets.md) for more details. ## Create an SQS inbound connector task To receive messages from Amazon SQS in your process, follow these steps: 1. Start building your BPMN diagram. You can use the **Amazon SNS Inbound connector** with either **Start Event** or **Intermediate Catch Event** building blocks. 2. Select the appropriate element and change its template to an SQS inbound connector. 3. Fill in all the required properties for the connector, such as the AWS region, SQS Queue URL, and the visibility timeout. 4. Complete your BPMN diagram by adding other necessary elements and connectors. 5. Deploy the diagram to activate the SQS Inbound connector. ## Configure the SQS inbound connector To configure the SQS inbound connector and receive messages from your SQS Queue, follow these steps: 1. Choose an applicable authentication type from the **Authentication** dropdown. Learn more about authentication types in the related [appendix entry](#aws-authentication-types-1). 2. Set the relevant IAM key and secret pair in the **Authentication** section. For example, `{{secrets.MY_AWS_ACCESS_KEY}}`. The value can be plain text, but this is not recommended due to security concerns. 3. In the **Queue Properties** section, set the URL of your SQS Queue and its region. 4. In the **Message polling properties** section, set the polling wait time. This is the duration (in seconds) for which the call waits for a message to arrive in the queue before returning. Refer to the [Amazon documentation](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-short-and-long-polling.html) for more details. Be aware that setting the value to 0 will not take effect, as it will automatically be overridden with a value of 1 during runtime. 5. (Optional) In the **Use next attribute names for activation condition** section, set an array of **Attribute names** or **Message attribute name** (e.g., `["attributeName1", "attributeName2"]`) to receive messages from the queue with specific metadata. Alternatively, you can leave it empty to get results with all available attributes. Learn more about message metadata [here](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-message-metadata.html). 6. (Optional) Configure the **Activation Condition**. For example, if an external message has the body `{"messageId": 1, "body": "Hi team", "messageAttributes":{"key":{"stringValue":"value"}}...}`, the **Activation Condition** value might look like `=(messageAttributes.key.stringValue="value")`. Leave this field empty to receive all messages every time. 7. Set the **Output mapping**. For example, to get only the message body, you can set `{resultBody: body}` in the **Result expression** field. Learn more about **Output mapping** [here](../use-connectors/index.md). ### Activation condition **Activation condition** is an optional FEEL expression field that allows for fine-tuning of the connector activation. For example, if an external message has the body `{"messageId": 1, "body": "Hi team", "messageAttributes":{"key":{"stringValue":"value"}}...}`, the **Activation Condition** value might look like `=(messageAttributes.key.stringValue="value")`. Leave this field empty to receive all messages every time. By default, messages with unmatched activation conditions are not deleted from the queue. They become available for consumers again after the visibility timeout expires. You can set up a dead-letter queue where messages are forwarded after a certain number of delivery attempts. You can also configure the Amazon SQS inbound connector to delete messages from the queue if they don't match the activation condition. In this case, the message will not end up in the dead-letter queue. To delete messages that don't match the activation condition, check the **Consume unmatched events** box. | **Consume unmatched events** box | Activation condition | Outcome | | -------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------ | | Checked | Matched | Message is removed from the queue | | Unchecked | Matched | Message is removed from the queue | | Checked | Unmatched | Message is removed from the queue | | Unchecked | Unmatched | Message is not removed from the queue and will be redelivered or placed in the dead-letter queue | ### Correlation The **Correlation** section allows you to configure the message correlation parameters. :::note The **Correlation** section is not applicable for the plain **start event** element template of the Amazon SQS connector. Plain **start events** are triggered by process instance creation and do not rely on message correlation. ::: #### Correlation key - **Correlation key (process)** is a FEEL expression that defines the correlation key for the subscription. This corresponds to the **Correlation key** property of a regular **Message Intermediate Catch Event**. - **Correlation key (payload)** is a FEEL expression used to extract the correlation key from the incoming message. This expression is evaluated in the connector Runtime and the result is used to correlate the message. Example for correlation and activation condition properties (correlation by ID in the body and activation condition by message attribute): SQS message: ```json { "messageId": "12345", "receiptHandle": "ABCDE", "mD5OfBody": "1c6bb59997376e5182a88a6f582cd92a", "body": { "id": 4567, "value": "Hello world" }, "attributes": { "ApproximateReceiveCount": "1", "SentTimestamp": "1703062074171", "SenderId": "333293239507", "ApproximateFirstReceiveTimestamp": "1703062074185" }, "messageAttributes": { "messageName": { "stringValue": "myProcess", "binaryValue": null, "stringListValues": [], "binaryListValues": [], "dataType": "String" } }, "md5OfMessageAttributes": "9de691a346c79e4fda4af06248aa9dfc" } ``` - **Correlation key (process)**: `=4567` - **Correlation key (payload)**: `=body.id` - **Activation condition**: `=messageAttributes.key.stringValue="myProcess"` Learn more about correlation keys in the [messages guide](../../../concepts/messages). #### Message ID expression The **Message ID expression** is an optional field that allows you to extract the message ID from the incoming message. The message ID serves as a unique identifier for the message and is used for message correlation. This expression is evaluated in the connector Runtime and the result is used to correlate the message. In most cases, it is not necessary to configure the **Message ID expression**. However, it is useful if you want to ensure message deduplication or achieve certain message correlation behavior. Learn more about how message IDs influence message correlation in the [messages guide](../../../concepts/messages#message-correlation-overview). For example, if you want to set the message ID to the value of the `transactionId` field in the incoming message, you can configure the **Message ID expression** as follows: ``` = body.transactionId ``` #### Message TTL The **Message TTL** is an optional field that allows you to set the time-to-live (TTL) for the correlated messages. TTL defines the time for which the message is buffered in Zeebe before being correlated to the process instance (if it can't be correlated immediately). The value is specified as an ISO 8601 duration. For example, `PT1H` sets the TTL to one hour. Learn more about the TTL concept in Zeebe in the [message correlation guide](../../../concepts/messages#message-buffering). ### Deduplication The **Deduplication** section allows you to configure the connector deduplication parameters. Not to be confused with **message deduplication**, **Connector deduplication** is a mechanism in the connector Runtime that determines how many SQS subscriptions are created if there are multiple occurrences of the **Amazon SQS Consumer connector** in the BPMN diagram. By default, the connector runtime deduplicates connectors based on properties, so elements with the same subscription properties only result in one subscription. For details, see [Inbound connector deduplication](../advanced-topics/deduplication.md). To customize the deduplication behavior, check the **Manual mode** checkbox and configure the custom deduplication ID. ## Activate the SQS inbound connector Once you click the **Deploy** button, your SQS inbound connector will be activated and publicly available. Whenever the SQS inbound connector receives a new message, a new BPMN process will be created. ## Amazon SQS connector response The **Amazon SQS connector** provides the SQS message as a response. Utilize output mapping to align this response with process variables: 1. Use **Result Variable** to store the response in a process variable. For example, `myResultVariable`. This approach stores the entire SQS message as a process variable named `myResultVariable`. 2. Use **Result Expression** to map fields from the response into process variables. This approach allows for more granularity. Instead of storing the entire response in one variable, you can extract specific fields from the SQS message and assign them to different process variables. This is particularly useful when you are only interested in certain parts of the message, or when different parts of the message need to be used separately in your process. Example: SQS message : ```json { "messageId": "12345", "receiptHandle": "ABCDE", "mD5OfBody": "1c6bb59997376e5182a88a6f582cd92a", "body": { "id": 4567, "value": "Hello world" }, "attributes": { "ApproximateReceiveCount": "1", "SentTimestamp": "1703062074171", "SenderId": "33333333333", "ApproximateFirstReceiveTimestamp": "1703062074185" }, "messageAttributes": { "messageName": { "stringValue": "myProcess", "binaryValue": null, "stringListValues": [], "binaryListValues": [], "dataType": "String" } }, "md5OfMessageAttributes": "9de691a346c79e4fda4af06248aa9dfc" } ``` To store the entire body in a process variable `resultBody`, ID from body to `bodyId`, and messageId to `messageId`, use: ``` = `{resultBody:body, bodyId:body.id, messageId: messageId}` ``` Learn more about **Variable mapping** [here](../use-connectors/index.md). ## Appendix ### AWS authentication types There are two options to authenticate the connector with AWS: - Choose **Credentials** in the **Authentication** dropdown if you have a valid pair of access and secret keys provided by your AWS account administrator. This option is applicable for both SaaS and Self-Managed users. - Choose **Default Credentials Chain (Hybrid/Self-Managed only)** in the **Authentication** dropdown if your system is configured as an implicit authentication mechanism, such as role-based authentication, credentials supplied via environment variables, or files on target host. This option is applicable only for Self-Managed or hybrid distribution. This approach uses the [Default Credential Provider Chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html) to resolve required credentials. ## Next Steps - Explore more about [Amazon Simple Queue Service (SQS)](https://aws.amazon.com/sqs/) and its capabilities. - Learn about [other connectors available](./available-connectors-overview.md) in Camunda to integrate with different systems and services. - Learn more about [using connectors](../use-connectors/index.md). - Learn more about [inbound connectors](../use-connectors/inbound.md). --- ## Amazon Textract connector Integrate Amazon Textract to automatically extract document text and data in your BPMN service. ## About this connector Use this connector to orchestrate Amazon Textract-powered extraction as part of business processes that rely on documents. Using machine learning allows you to read and process any type of document, reducing manual work and increasing accuracy in document-centric processes. The [Amazon Textract](https://aws.amazon.com/textract/) machine learning (ML) service can automatically extract text, handwriting, layout elements, and data from scanned documents. :::tip Camunda marketplace The [Amazon Textract connector](https://marketplace.camunda.com/en-US/apps/473161/amazon-textract-connector) is available in the Camunda marketplace. ::: ## Prerequisites The following prerequisites are required to use this connector: | Prerequisite | Description | | :------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [Amazon Web Services (AWS) IAM user and permissions](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users.html) | A valid AWS Identity and Access Management (IAM) user with permissions configured to allow access to Amazon Textract (and Amazon S3 if used), such as:`AmazonTextractFullAccess`: Required`AmazonS3ReadOnlyAccess`: Required if using Amazon S3 as the document source`AmazonS3FullAccess`: Optional if using Amazon S3 as the output location for asynchronous executionThe [access key pair](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) (_access key_ and _secret access key_) for this IAM user. This is required for connector authentication. | :::info For Amazon Textract setup instructions, refer to the [Amazon Textract Developer Guide](https://docs.aws.amazon.com/textract/latest/dg/getting-started.html). ::: ## Authentication Select an authentication type from the **Authentication** dropdown. ### Credentials Use AWS authentication. | Property | Type | Required | Description | Example | | :--------- | :----- | :------- | :--------------------------- | :------------------------------- | | Access Key | String | Yes | AWS access key for Textract. | `AKIAIOSFODNN37` | | Secret Key | String | Yes | AWS secret key for Textract. | `wJalrXUtnFEgfMIK7MDENGbPxRfiCY` | :::note Requires your AWS access key and secret access key (see [prerequisites](#prerequisites)). ::: ### Default Credentials Chain (hybrid/Self-Managed only) Use this authentication type if your system relies on implicit authentication (for example, IAM roles, environment variables, or credentials files). Uses the [Default Credential Provider Chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html) to resolve credentials. ## Configuration ### Region Configure the AWS region for this connector. | Property | Type | Required | Description | Example | | :------- | :----- | :------- | :-------------------------------------------------------------------------------- | :---------- | | Region | String | Yes | Specify the AWS region where the Textract service and your S3 buckets are hosted. | `us-east-1` | ## Operations ### Analyze Document Analyze documents using Textract. Different input parameters are available depending on the **Execution type** you select. #### Input parameters | Property | Type | Required | Description | Example | | :---------------- | :------- | :------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------- | | Execution type | Dropdown | Yes | Specify the inference endpoint type:**Real-time**: For small files requiring immediate text extraction. Only single-page PDFs are supported when using S3. For multi-page PDFs, use **Polling** or **Asynchronous**.**Polling**: Starts analysis and polls every five seconds until the result is available. Best for larger documents where blocking execution is acceptable.**Asynchronous**: For large or complex documents processed in the background. | document | | Document location | Dropdown | Yes | Where the document to be analyzed is stored: **Amazon S3** or **Uploaded document**. Amazon S3 is best for most use-cases. | Amazon S3 | | Document bucket | String | Yes for Amazon S3 | Name of the S3 bucket containing the document. Ensure proper permissions for Textract access. | automation-test | | Document name | String | Yes for Amazon S3 | Full path from the bucket root to the document. | my-document.pdf | | Document version | String | No | Specify if you need to process a specific document version. If not set, the latest version is used. | 5 | | Output S3 Bucket | String | Yes for Asynchronous | The S3 bucket where Textract writes the analysis result for asynchronous execution. | automation-output | When **Document location** is **Uploaded document**, use the **Document source** dropdown to select where that document comes from. Select one of the following options, then complete the field it reveals: | Document source | Reveals field | Description | | :------------------- | :------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | | **Camunda Document** | **Camunda document** | Reference a document in the Camunda document store using a FEEL expression. Supports PNG and JPEG only, and only for real-time execution. | | **From URL** | **URL** | Fetch the document from an external URL. | :::note Textract does not offer **Inline Content** as a document source, since it requires raw image/PDF bytes rather than the UTF-8 text bytes an inline document stores. ::: You must select at least one feature type. Combining multiple options can produce richer extraction results. | Property | Type | Required | Description | Example | | :----------------- | :------ | :------------------------------ | :------------------------------------------------------------------ | :------------------------------- | | Analyze form | Boolean | No | Select this to return information about detected form data. | | | Analyze signatures | Boolean | No | Select this to return the locations of detected signatures. | | | Analyze layout | Boolean | No | Select this to return information about the layout of the document. | | | Analyze queries | Boolean | No | Select this to return an answer to a query. | | | Query | String | Yes, if analyze queries is true | The query to be applied to the document. | What is the IBAN in the invoice? | Additional optional parameters for advanced configuration: | Property | Type | Required | Description | Example | | :--------------------------------- | :------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------- | | Response format | Dropdown | No | How the analysis result is returned: **as JSON** returns it directly in the process variables; **Document reference** uploads it to the Camunda document store and returns a reference. Not shown for **Asynchronous** execution, which always writes the result to the output S3 bucket. | Defaults to **as JSON** | | Client Request Token | String | No | The idempotent token that you use to identify the start request. | | | Job Tag | String | No | An identifier that you specify that's included in the completion notification published to the Amazon SNS topic. | | | KMS Key ID | String | No | The KMS key used to encrypt the inference results. | | | Notification Channel Role ARN | String | No | The Amazon SNS topic role ARN that you want Amazon Textract to publish the completion status of the operation to. | | | Notification Channel SNS Topic ARN | String | No | The Amazon SNS topic ARN that you want Amazon Textract to publish the completion status of the operation to. | | #### Output The connector response mirrors the [AWS Textract API](https://docs.aws.amazon.com/textract/latest/dg/API_Reference.html), depending on the execution type: - [Real-time Execution Response](https://docs.aws.amazon.com/textract/latest/dg/API_AnalyzeDocument.html#API_AnalyzeDocument_ResponseSyntax) - [Polling Execution Response](https://docs.aws.amazon.com/textract/latest/dg/API_GetDocumentAnalysis.html#API_GetDocumentAnalysis_ResponseSyntax) - [Asynchronous Execution Response](https://docs.aws.amazon.com/textract/latest/dg/API_StartDocumentAnalysis.html#API_StartDocumentAnalysis_ResponseSyntax) For **Real-time** and **Polling** execution, the **Response format** dropdown controls how that result reaches your process: - **as JSON** (default) returns the result above directly in the process variables, unchanged from earlier template versions. - **Document reference** uploads the result as a JSON file to the Camunda document store instead, and returns a reference: ```json { "document": { "storeId": "in-memory", "documentId": "8b54b413-b847-4650-b445-de963d5c506d", "contentHash": "ed0f7ad835669698a108a32b2a99e89e4f5aea84127fde68df4248b11197b0e5", "metadata": { "contentType": "application/json", "size": 2048, "fileName": "8b54b413-b847-4650-b445-de963d5c506d" }, "camunda.document.type": "camunda" } } ``` To get the answer of the query when using the Analyze queries feature: ```feel = {"answer": response.blocks[item.blockType = "QUERY_RESULT"][1].text} ``` For example, to get the response, when using asynchronous execution, use a timer event for example and retrieve the result with the S3 connector. ## Further Resources - [Amazon Textract connector](https://marketplace.camunda.com/en-US/apps/473161/amazon-textract-connector) in the Camunda marketplace - [Amazon Textract Documentation](https://docs.aws.amazon.com/textract/latest/dg/what-is.html) --- ## App Integrations connector Send messages to Microsoft Teams and Slack, receive what people write back, and create channels, directly from your BPMN process. ## About this connector The **App Integrations connector** sends messages through your organization's Camunda app integrations. The connection is configured once for the environment, so the task itself carries no credentials and no endpoint. A message can go to a Microsoft Teams channel, user, or conversation, to a Slack channel or user, or to a **Camunda recipient**, an assignee, candidate users, or candidate groups, which are resolved to whichever platforms those people have connected. Alongside the text you can send an [Adaptive Card](https://adaptivecards.io/), a [Block Kit](https://api.slack.com/block-kit) payload, or a Camunda form. Your process can also listen. When someone writes to the Camunda app, a process can start from what they typed, and a running process can wait for their reply. See [receive a chat message](#receive-a-chat-message). ### When to use this connector Use this connector if your organization uses [Camunda app integrations](/components/camunda-integrations/app-integrations/app-integrations.md), the Camunda apps for Microsoft Teams and Slack. Messages sent from a process travel through the same integration your users already have, so they arrive in the same channels and chats, alongside the task notifications those users already receive, rather than through a separate bot with its own identity. If you do not use app integrations, and you would rather register your own app and supply its credentials in the process model, use the [Slack](./slack.md) or [Microsoft Teams](./microsoft-teams.md) connector instead. ## Prerequisites App integrations must be set up before this connector can be used. This is an administrator task, done once per environment. There is nothing to configure on the task itself. An organization administrator must turn on **Enable app integrations extensions** in the [cluster settings](/components/saas/clusters/settings.md#enable-app-integrations-extensions) of every cluster that uses the connector. An administrator must install app integrations and configure the connector runtime to reach it: 1. Install and configure app integrations, as described in [Install app integrations](/components/camunda-integrations/app-integrations/installation.md). The same installation serves this connector, and the Microsoft Teams and Slack apps. 1. Point the connector runtime at that installation, as described in [configure the App Integrations connection](/self-managed/components/connectors/connectors-configuration.md#configure-the-app-integrations-connection). When the runtime authenticates with OAuth 2.0, it also needs the cluster's ID. This is the most common reason a fully installed environment still fails: without it, every job fails with `APP_INTEGRATIONS_NOT_CONFIGURED`. Runtimes that authenticate with an API key do not need it, and SaaS needs no equivalent setting. ## Create an App Integrations connector task ## Send message Send a message to one or more destinations. Choose the **Recipient source** first. It determines which targets and which content formats are available. ### Recipient Address people by their Camunda identity, and let app integrations resolve which platforms to deliver to. At least one of the three fields is required. | Property | Type | Required | Description | Example | | :--------------- | :----- | :------- | :------------------------------ | :------------------- | | Assignee email | String | No\* | Email address of the recipient. | `= assigneeEmail` | | Candidate users | List | No\* | Usernames to notify. | `= ["alice", "bob"]` | | Candidate groups | List | No\* | Group names to notify. | `= ["approvers"]` | \* At least one of the three must be provided. Select a **Teams target**, then fill the field it reveals. | Teams target | Property | Required | Description | Example | | :----------- | :----------- | :------- | :-------------------------------------------------------------------------------------------- | :------------------------------------------ | | Channel | Channel ID | Yes | The Microsoft Teams channel to post into. | `19:xxx@thread.tacv2` | | User | User ID | Yes | Microsoft Entra object ID of the recipient. They must have connected the Camunda app. | `6b1e0f9a-1f3d-4a2b-9d0e-4c1b2a3d4e5f` | | Conversation | Conversation | Yes | The `conversation` value returned by a previous send. The message is posted as a reply in it. | `19:abc@thread.tacv2;messageid=17123456789` | Select a **Slack target**, then fill the field it reveals. There is no Slack **Conversation** target. | Slack target | Property | Required | Description | Example | | :----------- | :--------- | :------- | :---------------------------------------------------------- | :------------ | | Channel | Channel ID | Yes | The Slack channel to post into. | `C0123456789` | | User | User ID | Yes | Slack member ID of the recipient. An email is not accepted. | `U0123456789` | **Thread** is shown for any Slack recipient, channel or user, and is optional. Set it to the `messageId` of a previous send to post the message as a reply in that thread. Leave it empty to post a new message. | Property | Type | Required | Description | Example | | :------- | :----- | :------- | :------------------------------------------------- | :------------------ | | Thread | String | No | Message ID of a previous send, to reply in-thread. | `1712345678.000100` | There is no workspace field on send message. The destination is the workspace the backend's bot token belongs to. ### Message content **Message** is plain text and always available. It is optional. Leave it empty to send only the additional content, or fill both to send text and a card in one message. **Additional content** offers different formats depending on the recipient, because each platform accepts different payloads: | Recipient | Additional content options | | :-------------- | :-------------------------- | | Camunda | None · Form | | Microsoft Teams | None · Adaptive card · Form | | Slack | None · Block Kit · Form | You can select at most one, so a card and a form are mutually exclusive. You must provide a message, additional content, or both. An empty message with **None** is rejected before any call is made. | Additional content | Property | Type | Required | Description | | :----------------- | :--------------- | :--- | :------- | :-------------------------------------- | | Adaptive card | Adaptive card | Text | Yes | Adaptive Card as JSON. | | Block Kit | Block Kit blocks | Text | Yes | Slack Block Kit `blocks` array as JSON. | Both fields accept pasted JSON as well as a FEEL expression referencing a card built earlier in the process, such as `= approvalCard`. A JSON literal is valid FEEL, so pasting works without further quoting. When **Block Kit blocks** is combined with a **Message**, the message text is posted as a leading section block above the supplied blocks. When additional content is **Form**, the connector renders a linked Camunda form: an Adaptive Card on Microsoft Teams, or Block Kit on Slack. Select the form and its binding in the properties panel: | Property | Type | Required | Description | | :----------- | :------- | :------- | :-------------------------------------------------------------- | | Form binding | Dropdown | Yes | `Latest`, `Deployment`, or `Version tag`. Defaults to `Latest`. | | Form ID | String | Yes | ID of the Camunda form to render alongside the message. | | Version tag | String | Yes\* | The version tag to bind to. | \* Required when **Form binding** is **Version tag**. ### Response The connector reports every destination the message reached, and every one it did not: ```json { "deliveries": [ { "platform": "teams", "conversation": "19:abc@thread.tacv2;messageid=17123456789", "messageId": "17123456789", "conversationKey": "teams:19:abc@thread.tacv2;messageid=17123456789" } ], "failures": [ { "platform": "slack", "conversation": "C0123456789", "reason": "not_in_channel" } ] } ``` | Field | Description | | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `deliveries` | Every destination the message was delivered to. | | `deliveries[].platform` | `teams` or `slack`, never `camunda`. A Camunda recipient can fan out to both platforms in one response. | | `deliveries[].conversation` | The conversation the message landed in. Use it to reply later. For a Slack recipient, this is the channel ID, including the direct message channel when the target was a user. | | `deliveries[].messageId` | The message identifier. For Slack, this is the message timestamp, which also serves as the thread anchor. | | `deliveries[].conversationKey` | Identifies the conversation for a chat catch element. For Teams, it is `teams:`. For Slack, it is `slack::`. Compare it, don't parse it. See [receive a chat message](#receive-a-chat-message). | | `failures` | Every destination that could not be reached. | | `failures[].platform` | `teams` or `slack`, never `camunda`. | | `failures[].conversation` | The conversation that could not be reached. | | `failures[].reason` | Why that destination failed. | A single delivery is a one-element list, so with a result variable of `response` you read it as `= response.deliveries[1].conversation`. FEEL lists are 1-indexed. `failures` is non-empty on a partial success. A process that must not continue on an incomplete fan-out can check `= count(response.failures) > 0`. :::tip To continue a conversation, feed the response back in. Pass `conversation` as the Microsoft Teams **Conversation** target, or as the Slack **Channel ID** target with `messageId` as **Thread**. To wait for an answer instead of sending again, pass `conversationKey` to a [chat message catch element](#receive-a-chat-message). ::: ## Create channel Create a channel in Microsoft Teams or Slack. Select the platform first. | Property | Type | Required | Description | Example | | :----------- | :------- | :------- | :-------------------------------------------------------------------------------------------------- | :--------------------- | | Channel name | String | Yes | Display name for the new channel. Maximum 50 characters. | `Releases` | | Team ID | String | Yes | The team's group ID, or a full Teams URL. The `groupId` query parameter is extracted automatically. | `` | | Channel type | Dropdown | Yes | Membership type. Only **Standard** is available. | `Standard` | | Description | String | No | Channel description. | `Release coordination` | :::note Only standard channels are supported. Private and shared channels are not yet available, and a request for either is rejected. ::: | Property | Type | Required | Description | Example | | :-------------- | :------ | :------- | :--------------------------------------------------------------------------------------------------------------------------- | :--------------------- | | Channel name | String | Yes | Lowercase letters, digits, hyphens, and underscores only. Maximum 80 characters. | `releases` | | Workspace ID | String | No | Slack workspace (team) ID. Leave empty for the normal case. | `T0123` | | Private channel | Boolean | No | Create the channel as private rather than public. The connector's Microsoft Teams operation supports only standard channels. | `false` | | Description | String | No | Applied after creation. A failure here is logged and does not fail the operation. | `Release coordination` | :::note The backend holds one bot token and no installation store, so a channel can only be created in that token's workspace. A supplied **Workspace ID** is checked against it and rejected with HTTP `400` when it differs. It can never route the creation to a different workspace. Leave it empty in the normal case. ::: ### Response ```json { "channelId": "19:new-channel@thread.tacv2" } ``` The response has the same `channelId` field on both platforms. Read the new channel with `= response.channelId`. You can pass it straight into a **Send message** task as the channel target. ## Receive a chat message Start a process when someone writes to the Camunda app in Microsoft Teams or Slack, or wait for their reply while a process runs. Receiving needs no connector task and no job worker. A chat catch element is an ordinary BPMN message event, and the App Integrations element templates set it up for you. Apply a template to the element, and the incoming message arrives as a process variable. ### Chat conversation element templates Apply one of these templates in the [Camunda Hub modeler](/components/hub/workspace/modeler/index.md) or [Desktop Modeler](/components/modeler/desktop-modeler/index.md). Each applies to a different BPMN element. | Element template | Apply to | Purpose | | :--------------------------------------------------- | :------------------------------- | :-------------------------------------------------------------------- | | **App Integrations Chat Conversation Start Event** | Message start event | Start a process when someone writes to the Camunda app. | | **App Integrations Chat Message Intermediate Event** | Intermediate message catch event | Wait for the next message in the conversation the process is holding. | | **App Integrations Chat Message Receive Task** | Receive task | Wait for the next message as a task rather than an event. | | **App Integrations Chat Message Boundary Event** | Boundary message event | Receive a message while another activity is running. | A conversation process usually pairs the start event with a single catch element, and loops back to that element for each turn. Desktop Modeler [fetches connector templates automatically](/components/modeler/desktop-modeler/use-connectors.md#automatic-connector-template-fetching), so the templates appear without any setup unless you have turned that off. ### Route a chat to the right process A **chat key** decides which process the messages in a channel or chat start. You set it in two places that must match: the start event in your model, and the channel or chat in the Camunda app. The **App Integrations Chat Conversation Start Event** template carries a **Chat key** property. It holds the full name of the message the process starts on, and must read `io.camunda.appIntegrations.conversationStarted.`, for example `io.camunda.appIntegrations.conversationStarted.hr-intake`. It defaults to `io.camunda.appIntegrations.conversationStarted.default`. The template rejects a value in any other form, so a typo can't subscribe your process to an unrelated message. You don't have to type the value. Configure the channel or chat first: - On Microsoft Teams, open the Camunda app in the channel or chat, and select the **Settings** tab. - On Slack, run `/camunda chat` in the channel or direct message. This is the Slack equivalent of the Teams **Settings** tab. Either surface shows the exact string to copy into the **Chat key** property, and both ask for a short chat key, such as `hr-intake`, and for the organization and cluster the conversation runs in. Each channel and chat is in one of three states. | State | What you set | What a message does | | :------------------- | :--------------------------------------------------------- | :------------------------------------------------------------------------------------------------------- | | **Default process** | Nothing. Every channel and chat starts here. | Starts the process whose chat key is `default`, in the organization and cluster the sender has selected. | | **Specific process** | A chat key, and the organization and cluster to run it in. | Starts the process with that chat key, in the configured cluster, whoever writes the message. | | **Off** | Nothing is processed here. | Reaches no process. The Camunda app answers with its help message instead. | Give a chat key to exactly one process definition per cluster. If two deployed processes carry the same chat key, both of them start on every message and both reply to the person, and nothing detects the clash for you. Changing a chat key applies to the next conversation. A conversation already under way finishes with the process holding it. ### The chat message variable Every chat template writes the incoming message to a `chatMessage` process variable. Rename it with the **Result variable** property. ```json { "platform": "teams", "conversationKey": "teams:19:abc@thread.tacv2;messageid=17123456789", "conversation": "19:abc@thread.tacv2;messageid=17123456789", "messageId": "17123456799", "text": "approved, ship it", "user": { "externalUserId": "6b1e0f9a-1f3d-4a2b-9d0e-4c1b2a3d4e5f", "email": "ada@example.com" }, "receivedAt": "2026-08-26T09:41:02.113Z" } ``` ```json { "platform": "slack", "conversationKey": "slack:D0123ABCD:1712345678.000100", "conversation": "D0123ABCD", "threadId": "1712345678.000100", "messageId": "1712345690.000200", "text": "approved, ship it", "user": { "externalUserId": "T01234ABCDE::U01234ABCDE", "email": "ada@example.com" }, "receivedAt": "2026-08-26T09:41:02.113Z" } ``` | Field | Description | | :-------------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | `platform` | `teams` or `slack`. | | `conversationKey` | Identifies the conversation. Use it as the correlation key of a catch element. Compare it, don't parse it. | | `conversation` | The conversation the message came from. Pass it back to reply. | | `threadId` | The Slack thread anchor. Present on Slack only. On Microsoft Teams, the conversation is already the thread, so the field is absent. | | `messageId` | This message's own identifier, not the thread anchor. | | `text` | The message as typed. In a channel, the mention of the Camunda app is removed. | | `user.externalUserId` | The sender's identifier on the chat platform. | | `user.email` | The sender's Camunda email address. | | `receivedAt` | When app integrations received the message, in ISO 8601 format. | Attachments, files, and edits to an existing message aren't delivered. ### Correlate a message to the right conversation The catch templates set **Conversation** to `=chatMessage.conversationKey`, which is the conversation the process is already in. A process started by the chat start event has `chatMessage` in scope, so a start event and a catch element work together with nothing to configure. Change **Conversation** only when the key comes from somewhere else, such as the `conversationKey` reported by an earlier [Send message](#send-message) delivery. That is how a process that speaks first waits for the answer. ### Reply to the sender Reply with a **Send message** task, using the values from `chatMessage`. | Platform | Recipient source | Property | Value | | :-------------- | :--------------- | :--------------- | :-------------------------- | | Microsoft Teams | Microsoft Teams | **Conversation** | `=chatMessage.conversation` | | Slack | Slack | **Channel ID** | `=chatMessage.conversation` | | Slack | Slack | **Thread** | `=chatMessage.threadId` | There is no Slack **Conversation** target, so a Slack reply is not the same shape as a Teams reply: it uses the **Channel ID** target plus **Thread** rather than a single **Conversation** value. The reply is posted by the Camunda app, in the same thread the person wrote in. ### Where a process can be reached | Surface | A message reaches a process when | | :---------------------------------------- | :------------------------------------------------------------------------------- | | Microsoft Teams personal chat | Someone writes anything. | | Microsoft Teams channel | Someone writes a message that @mentions the Camunda app. | | Slack direct message with the Camunda app | Someone writes anything that is not a help request. | | Slack channel | Someone @mentions the Camunda app, and the app is a member of the channel. | | Slack group direct message | Never. `/camunda chat` refuses to configure one, because the app cannot join it. | In a Microsoft Teams channel or a Slack channel, add the Camunda app to the channel and @mention it to start a conversation. Messages that address nobody aren't delivered to a process, so a process that expects replies in a channel should ask to be @mentioned, or use a personal chat or direct message instead. ### What the sender sees | Situation | What happens | | :---------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ | | A process is holding the conversation. | The message reaches the process, and the Camunda app stays quiet so the two don't overlap. | | The person types a word the Camunda app understands, such as `help`. | The Camunda app answers. Commands take precedence over the process. | | The person hasn't connected their Camunda account. | They're prompted to connect, and no message is sent to a process. | | The channel or chat uses the default process, and the person can reach more than one cluster without having chosen one. | They're asked to select an organization and cluster first. | | The person can't reach the cluster the channel or chat is configured for. | The message reaches no process. Give them access to that cluster, or point the channel at one they can reach. | | The channel or chat is turned off. | The message reaches no process, and the Camunda app answers with its help message. | | No deployed process uses the chat key. | Nothing happens, and the Camunda app doesn't answer. | A reply always reaches the cluster whose process asked the question, even if the person switches to a different cluster while the conversation is open. :::note Tell people which words your conversation shouldn't use. Words the Camunda app already understands, such as `help`, reach the app rather than your process. ::: ## Troubleshooting ### Connector not configured When app integrations are not set up for the environment, every job fails immediately with the error code `APP_INTEGRATIONS_NOT_CONFIGURED` and raises an incident. This failure is **not retried**. The **Retries** and **Retry backoff** settings on the task do not apply, because no amount of retrying can supply missing configuration. Only processes using this connector are affected. The connector runtime keeps serving every other connector. Resolving it is an administrator task. See [prerequisites](#prerequisites). | Environment | Cause | Fix | | :----------- | :----------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | SaaS | **Enable app integrations extensions** is off for the cluster. | Ask an organization administrator to enable it in the [cluster settings](/components/saas/clusters/settings.md#enable-app-integrations-extensions). | | Self-Managed | The connector runtime is not configured to reach app integrations. | Complete the [App Integrations connection settings](/self-managed/components/connectors/connectors-configuration.md#connection-settings) and redeploy the runtime. | | Self-Managed | The runtime authenticates with OAuth, but no cluster ID is set. | Set the cluster ID to the cluster's UUID and redeploy the runtime. See [choose an authentication method](/self-managed/components/connectors/connectors-configuration.md#choose-an-authentication-method). | The incident message names the missing setting, so read it before changing configuration. ### Other error codes | Code | Cause | | :------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- | | `VALIDATION_ERROR` | Additional content is **Form** but no linked form reached the job, or an Adaptive Card or Block Kit payload is not valid JSON of the expected shape. | | `IO_ERROR` | The request could not be serialized, or the response could not be parsed. | | `501` | Slack is not configured on the App Integrations deployment the runtime is pointed at. It is not a process modeling error. | | HTTP status, such as `401` | App integrations returned an error. The error code is the HTTP status. | ## Further resources - [Camunda app integrations](/components/camunda-integrations/app-integrations/app-integrations.md), act on Camunda tasks from inside Microsoft Teams or Slack. - [Camunda for Slack](/components/camunda-integrations/app-integrations/slack.md). - [Notification rules](/components/camunda-integrations/app-integrations/notification-rules.md), automatic user task notifications, configured without modeling a connector. - [Adaptive Cards](https://adaptivecards.io/) and the [Adaptive Card designer](https://adaptivecards.io/designer/). - [Slack Block Kit](https://api.slack.com/block-kit) and the [Block Kit builder](https://app.slack.com/block-kit-builder). --- ## Asana connector Connect your BPMN service with [Asana](https://asana.com/) to get, create, and delete Asana tasks and projects. ## About this connector The Asana connector is an outbound protocol connector built on top of the [REST connector](/components/connectors/protocol/rest.md). It calls the [Asana API](https://developers.asana.com/reference/rest-api-reference) for you, so you select an operation and fill in its parameters instead of building the HTTP request yourself. For example, use this connector to create an Asana task when a process instance reaches a manual review step, or to look up the tasks of a project. ## Prerequisites To use the Asana connector, you need an Asana [personal access token](https://developers.asana.com/docs/personal-access-token). :::note Use secrets to avoid exposing your Asana personal access token as plain text. Refer to our documentation on [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ::: ## Configuration ### Authentication | Property | Type | Required | Description | Example | | :-------------------- | :----- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------- | | Personal access token | String | Yes | Your Asana personal access token. [Learn how to obtain a token](https://developers.asana.com/docs/personal-access-token) in the Asana documentation. | `{{secrets.ASANA_PAT}}` | ### Operation Use the **Operation group** dropdown to choose **Tasks** or **Projects**, then select the operation in the **Operation** dropdown. | Property | Type | Required | Description | Example | | :-------------- | :------- | :------- | :------------------------------------------------------------------------- | :------ | | Operation group | Dropdown | Yes | The kind of Asana object to work with: **Tasks** or **Projects**. | Tasks | | Operation | Dropdown | Yes | The operation to run. The available options depend on the operation group. | | ## Operations ### Tasks #### Get tasks from a project Returns the tasks of a project. Uses the Asana API operation [Get tasks from a project](https://developers.asana.com/reference/gettasksforproject). ##### Input parameters | Property | Type | Required | Description | Example | | :--------- | :----- | :------- | :------------------------------------------ | :------ | | Project ID | String | Yes | Globally unique identifier for the project. | | #### Get a task by ID Returns a single task. Uses the Asana API operation [Get a task](https://developers.asana.com/reference/gettask). ##### Input parameters | Property | Type | Required | Description | Example | | :------- | :----- | :------- | :---------------------- | :------ | | Task ID | String | Yes | The task to operate on. | | #### Create a task Creates a task in a project. Uses the Asana API operation [Create a task](https://developers.asana.com/reference/createtask). ##### Input parameters | Property | Type | Required | Description | Example | | :------------- | :----- | :------- | :--------------------------------------------------------------------------------- | :------ | | Task name | String | Yes | The name of the task. | | | Project ID | String | Yes | Globally unique identifier for the project. | | | Parent task ID | String | No | Globally unique identifier for the parent task. | | | Notes | String | No | Free-form textual information associated with the task (that is, its description). | | #### Delete a task Deletes a task. Uses the Asana API operation [Delete a task](https://developers.asana.com/reference/deletetask). ##### Input parameters | Property | Type | Required | Description | Example | | :------- | :----- | :------- | :---------------------- | :------ | | Task ID | String | Yes | The task to operate on. | | ### Projects #### Get projects Returns multiple projects, optionally filtered. Uses the Asana API operation [Get multiple projects](https://developers.asana.com/reference/getprojects). ##### Input parameters | Property | Type | Required | Description | Example | | :----------- | :----- | :------- | :--------------------------------------------------- | :------ | | Workspace ID | String | No | The workspace or organization to filter projects on. | | | Team ID | String | No | The team to filter projects on. | | #### Get a project by ID Returns a single project. Uses the Asana API operation [Get a project](https://developers.asana.com/reference/getproject). ##### Input parameters | Property | Type | Required | Description | Example | | :--------- | :----- | :------- | :------------------------------------------ | :------ | | Project ID | String | Yes | Globally unique identifier for the project. | | #### Create a project in a workspace Creates a project in a workspace. Uses the Asana API operation [Create a project in a workspace](https://developers.asana.com/reference/createprojectforworkspace). ##### Input parameters | Property | Type | Required | Description | Example | | :----------- | :----- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------- | :------ | | Workspace ID | String | Yes | Globally unique identifier for the workspace or organization. | | | Project name | String | Yes | Name of the project. Generally a short sentence fragment that fits on a line in the UI for maximum readability. However, it can be longer. | | | Project note | Text | No | Free-form textual information associated with the project (that is, its description). | | #### Delete a project Deletes a project. Uses the Asana API operation [Delete a project](https://developers.asana.com/reference/deleteproject). ##### Input parameters | Property | Type | Required | Description | Example | | :--------- | :----- | :------- | :------------------------------------------ | :------ | | Project ID | String | Yes | Globally unique identifier for the project. | | ## Output The Asana connector is built on top of the REST connector, so the response is the HTTP response of the Asana API call. In the **Result expression**, you can use `response.status`, `response.headers`, and `response.body`. Read the [REST connector response](/components/connectors/protocol/rest.md#response) for details. Asana returns the requested object or list in the `data` property of the response body. For example, to keep only the ID of a created task: ```feel = { taskId: response.body.data.gid } ``` ## Further resources - [Asana API reference](https://developers.asana.com/reference/rest-api-reference) --- ## Automation Anywhere connector The **Automation Anywhere connector** allows you to orchestrate an Automation Anywhere queue from your BPMN process with [Automation Anywhere RPA](https://www.automationanywhere.com/) to add work items to the queue and obtain work item results. To start using the connector, you need an instance with a [license](https://docs.automationanywhere.com/bundle/enterprise-v2019/page/enterprise-cloud/topics/control-room/dashboards/cloud-administration-licenses.html) we configured via an API service. Refer to the [official documentation page](https://docs.automationanywhere.com/bundle/enterprise-v2019/page/enterprise-cloud/topics/aae-client/bot-creator/using-the-workbench/cloud-install.html) to learn more about installing and configuring the Automation Anywhere API service. You also need a user account with the `AAE_Queue Admin` role to query and manage workload queues and work items in a Control Room. Read more about roles in the [official documentation](https://docs.automationanywhere.com/bundle/enterprise-v2019/page/enterprise-cloud/topics/control-room/administration/roles/cloud-system-created-roles.html). ## Create an Automation Anywhere connector task ## Make your Automation Anywhere connector executable To work with Automation Anywhere, choose the required operation type in the **Operation** section and authentication type in the **Authentication** section and complete the mandatory fields highlighted in red in the connector properties panel on the right side of the screen. :::note All the mandatory and non-mandatory fields depending on the authentication and operation selections you choose are covered in the upcoming sections. ::: ## Authentication You can choose among the available **Automation Anywhere connector** authentication types according to your authentication requirements. :::note We advise you to keep your authentications and secrets data safe and avoid exposing it in the BPMN XML file by creating a secret: 1. Follow our [guide for creating secrets](/components/saas/clusters/manage-secrets.md). 2. Name your secret (i.e `AUTOMATION_ANYWHERE_PASSWORD`) so you can reference it later in the connector. ::: ### _Authenticate (username and password)_ authentication Select the **Automation Anywhere connector** and fill out the following properties under the **Authentication** section: 1. Select **Authenticate (username and password)** in the **Authentication** section. 2. Set **Password** to `Password` to the secret you created (i.e. `{{secrets.AUTOMATION_ANYWHERE_PASSWORD}}`). 3. Set **Username** to `Username` to the secret you created (i.e. `{{secrets.AUTOMATION_ANYWHERE_UESRNAME}}`). 4. Select needed **Multiple login** type. If this value is set to `true`, you will be allowed multiple API sessions. For more information on multi-login, refer to [multi-login user](https://docs.automationanywhere.com/bundle/enterprise-v2019/page/enterprise-cloud/topics/control-room/administration/users/cloud-multi-login-user.html). ### _Authenticate (username and API key)_ authentication Select the **Automation Anywhere connector** and fill out the following properties under the **Authentication** section: 1. Select **Authenticate (username and API key)** in the **Authentication** section. 2. Set **Password** to `Password` to the secret you created (i.e. `{{secrets.AUTOMATION_ANYWHERE_PASSWORD}}`). 3. Set **API key** as `API key` to the secret you created (i.e. `{{secrets.AUTOMATION_ANYWHERE_API_KEY}}`). The API-Key is a 40-character string generated in the Control Room. Refer to [create and assign API key generation role documentation](https://docs.automationanywhere.com/bundle/enterprise-v2019/page/enterprise-cloud/topics/control-room/administration/roles/cloud-control-room-apikey-role.html) to learn more. ### _Authentication (refresh) token_ authentication Select the **Automation Anywhere connector** and fill out the following properties under the **Authentication** section: 1. Select **Authentication (refresh) token** in the **Authentication** section. 2. Set **Token** to `Token` to the secret you created (i.e. `{{secrets.AUTOMATION_ANYWHERE_TOKEN}}`). It can be an authentication or refresh token. Refer to [authentication API documentation](https://docs.automationanywhere.com/bundle/enterprise-v11.3/page/enterprise/topics/control-room/control-room-api/api-authentication.html) to learn how to generate an authentication token or observe the [refresh token API documentation](https://docs.automationanywhere.com/bundle/enterprise-v11.3/page/enterprise/topics/control-room/control-room-api/refresh-authentication-token.html) to learn how to generate a refresh token. ## Configuration ### Control Room URL 1. Set **Control Room URL** to `Control Room URL`. The Control Room URL is the URL you use to access the Automation Anywhere Control Room. The Control Room URL is typically provided by the Automation Anywhere administrator and is specific to the organization's instance of the platform (i.e. `https://domainname.myautomationanywhere.digital`). ## Operation types The **Automation Anywhere connector** currently supports two operation types in the **Operation type** dropdown list: _Add work item to the queue_ and _Get work item result from queue by ID_. ### Add work item to the queue This operation provides the ability to add a work queue item in the specified queue. It corresponds directly to the respective Automation Anywhere API - [`Add Work Items to the queue API`](https://docs.automationanywhere.com/bundle/enterprise-v11.3/page/enterprise/topics/control-room/control-room-api/add-work-item-data-to-queue-api.html). #### Usage 1. Select **Add work item to the queue** from the **Operation type** dropdown in the **Operation** section. 2. Populate **Authentication section** as described in the [respective section](#authentication). 3. In the **Configuration** section, set the **Control Room URL** field as described in the [respective section](#control-room-url). 4. In the **Input** section, set **Work queue ID**. This is the identifier of a queue, where an item will be fetched from. 5. In the **Input** section, set **Work Item json Data** that you want to pass together with the item. The **Data** has to comply with the Automation Anywhere API, and must contain the following semantics: ```json { "coll_name": "your value", "last_name": "Doe", "email": "jane.doe@example.com" } ``` #### Add work item to the queue response The operation **Add work item to the queue** returns information about the newly created item in the queue. You can use an output mapping to map the response: 1. Use **Result Variable** to store the response in a process variable. For example, `myResultVariable`. 2. Use **Result Expression** to map fields from the response into process variables. It comes with a pre-filled value of `={itemId:response.body.list[1].id}`. To use operation _Get work item result from queue by ID_, you need an `itemId`. This expression will add it to the context for you. Learn more in [get work item result from queue by ID](#get-work-item-result-from-queue-by-id). Response example: ```json { "list": [ { "id": "40957", "createdBy": "25", "createdOn": "2021-11-24T01:53:10.175335900Z", "updatedBy": "25", "updatedOn": "2021-11-24T01:53:10.175335900Z", "version": "0", "json": { "TRN_ID": "A11", "DATA": "mydata" }, "result": "", "deviceId": "0", "status": "NEW", "col1": "A11", "col2": "", "deviceUserId": "0", "queueId": "0", "comment": "", "automationId": "0", "totalPausedTime": "0", "error": "", "col6": "", "jobExecutionId": "" } ] } ``` ### Get work item result from queue by ID This operation provides the ability to return the details of the specified work item from the work queue. It corresponds directly to the respective Automation Anywhere API - [`List Work Items in queue with filter by work item ID`](https://docs.automationanywhere.com/bundle/enterprise-v11.3/page/enterprise/topics/control-room/control-room-api/get-all-work-items-in-queues-api.html). #### Usage 1. Select **Get work item result from queue by ID** from the **Operation type** dropdown in the **Operation** section. 2. Populate **Authentication section** as described in the [respective section](#authentication). 3. In the **Configuration** section, set the **Control Room URL** field as described in the [respective section](#control-room-url). 4. In the **Input** section, set **Work queue ID**. This is the identifier of a queue, where an item will be fetched from. 5. In the **Input** section, set **Work item ID**. This is the identifier of the item to be fetched. #### Get work item result from queue by ID response Given you have a queue work item ID previously added to a queue, the operation **Get work item result from queue by ID** returns information about a certain work item. You can use an output mapping to map the response: 1. Use **Result Variable** to store the response in a process variable. For example, `myResultVariable`. 2. Use **Result Expression** to map fields from the response into process variables. It comes with a pre-filled value of `={itemState:response.body.list[1].status}`. You will observe the `itemState` in the process variables. Its value will let you know if the item was processed or not. Response example: ```json { "page": { "offset": 0, "total": 5, "totalFilter": 1 }, "list": [ { "id": "11804", "createdBy": "24", "createdOn": "2020-05-26T10:19:34.786711300Z", "updatedBy": "24", "updatedOn": "2020-05-26T10:19:34.786711300Z", "version": "1", "json": {}, "result": "", "deviceId": "0", "status": "NEW", "col1": "Brian", "col2": "Matthews", "col3": "bmatthews0@example.com", "deviceUserId": "0", "queueId": "20", "comment": "", "automationId": "0", "totalPausedTime": "0", "error": "", "col6": "", "col10": "" } ] } ``` ## Using Automation Anywhere connector best practice There is no guarantee a queue item will be processed right away. In that case, we suggest building your BPMN diagram to periodically retry polling. To learn more, refer to an entry _Solution with Timer and Loop_ on the [Camunda BPMN examples](https://camunda.com/bpmn/examples/) page. :::note To avoid performance issues, it is recommended to limit the number of loop retries. ::: --- ## Built-in connectors Find technical documentation for our library of built-in connectors. :::tip Don't see the connector you need? Build your own [custom connector](/components/connectors/custom-built-connectors/build-connector.md), or explore the [Camunda Marketplace](https://marketplace.camunda.com/) for more connectors developed by Camunda, Partners, and the Community. ::: --- ## AWS Lambda connector Invoke an [AWS Lambda](https://aws.amazon.com/lambda/) function from your BPMN process and use its response in later steps. ## About this connector The **AWS Lambda connector** is an outbound connector that connects your BPMN service with the [AWS Lambda service](https://aws.amazon.com/lambda/). Use it to run serverless code, such as a calculation or a call into another AWS service, as part of a process, and to pass JSON data to the function and receive its result. The connector supports synchronous invocation only. It waits for the function to finish and returns its response. Asynchronous (event-driven) invocation isn't available. ## Prerequisites To use the AWS Lambda connector, you need the following: | Prerequisite | Description | | :--------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [AWS Lambda function](https://docs.aws.amazon.com/lambda/latest/dg/welcome.html) | A deployed function you want to invoke. | | [AWS IAM user and permissions](https://docs.aws.amazon.com/lambda/latest/dg/lambda-permissions.html) | An IAM access key and secret key pair with permission to run the function. Not required if you use the default credentials chain (hybrid/Self-Managed only) or a saved AWS credential. | :::note Use secrets to avoid exposing your AWS IAM credentials as plain text. Refer to [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ::: ## Configuration ### Authentication Select an authentication method for the connector. Learn more about the available options in [AWS authentication types](#aws-authentication-types). | Property | Type | Required | Description | Example | | :------------- | :------------ | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------ | | AWS credential | Configuration | No | Select a saved AWS credential, which can include a default region. When you select a credential, the **Authentication**, **Access key**, and **Secret key** fields are hidden. Learn more about [credentials](/components/hub/organization/credentials/index.md). | | | Authentication | Dropdown | Yes | The AWS authentication strategy: **Credentials**, or **Default Credentials Chain (Hybrid/Self-Managed only)**. Shown when no AWS credential is selected. | **Credentials** | | Access key | String | Yes | IAM access key of a user with the necessary permissions for this connector. Shown when **Authentication** is **Credentials** and no AWS credential is selected. | `{{secrets.MY_AWS_ACCESS_KEY}}` | | Secret key | String | Yes | IAM secret key of a user with the necessary permissions for this connector. Shown when **Authentication** is **Credentials** and no AWS credential is selected. | `{{secrets.MY_AWS_SECRET_KEY}}` | ### Region | Property | Type | Required | Description | Example | | :------- | :----- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------- | | Region | String | Yes | The AWS region where your function is hosted. Refer to [Regions and Zones](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html) to learn more. When you select an AWS credential, this field is optional: it overrides the credential's default region, and is required if the credential has no default region. | `us-east-1` | ### AWS authentication types You can authenticate the connector with AWS in the following ways: - **Credentials**: Use this option if your AWS account administrator provided a pair of access and secret keys. It applies to SaaS and Self-Managed. - **Default Credentials Chain (Hybrid/Self-Managed only)**: Use this option if your system relies on an implicit authentication mechanism, such as role-based authentication, credentials supplied through environment variables, or files on the target host. It applies only to Self-Managed or hybrid distributions, and uses the [Default Credential Provider Chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html) to resolve credentials. - **AWS credential**: Select a saved credential instead of entering authentication details on the task. ## Operations ### Invoke function Invoke an AWS Lambda function synchronously and return its response. #### Input parameters | Property | Type | Required | Description | Example | | :------------- | :------- | :------- | :-------------------------------------------------------------------------------------------------------------------------- | :------------------------- | | Operation type | Dropdown | Yes | The invocation type. **Invoke function (sync)** is the only option. | **Invoke function (sync)** | | Function name | String | Yes | The name, [ARN](https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html), or alias of your function. | `my-function` | | Payload | Text | Yes | The JSON input passed to your Lambda function when it is invoked. Use a FEEL expression to build it from process variables. | `= { "orderId": orderId }` | #### Output The connector returns an object with the following fields: | Field | Description | | :---------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `statusCode` | The HTTP status code returned by the AWS Lambda Invoke API. It shows whether AWS Lambda accepted and handled the invocation request, not whether the function itself succeeded. | | `executedVersion` | The version of the Lambda function that ran. | | `payload` | The response returned by the Lambda function, or an error object. If your function returns its own `statusCode`, that value describes the function result and is separate from the top-level `statusCode`. | For example, for a function that returns an order status: ```json { "statusCode": 200, "executedVersion": "$LATEST", "payload": { "orderId": "1001", "status": "shipped" } } ``` To read a `statusCode` that your function returns, use `response.payload.statusCode` rather than `response.statusCode`. To map the response into process variables, use **Result variable** to store the whole response, or **Result expression** to map individual fields: ```feel = { "myNewReportStatusCode": response.statusCode, "myNewReportExecutedVersion": response.executedVersion, "myNewReportPayload": response.payload } ``` ## Further resources - [AWS Lambda developer guide](https://docs.aws.amazon.com/lambda/latest/dg/welcome.html) --- ## Azure blob storage connector Upload documents to and download documents from Azure Blob Storage in your BPMN process. ## About this connector The **Azure Blob Storage connector** is an outbound connector that allows you to interact with [Azure Blob Storage](https://azure.microsoft.com/en-us/products/storage/blobs) resources from your BPMN processes. The connector currently supports uploading and downloading documents. ## Prerequisites To begin using the **Azure Blob Storage connector**, ensure you have created the following [resources](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blobs-introduction#blob-storage-resources): - An Azure [storage account](https://portal.azure.com/#browse/Microsoft.Storage%2FStorageAccounts) - An Azure [container](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blobs-introduction#containers) Then set up access for the connector using one of the following authentication methods. ### OAuth 2.0 1. In the [Azure Portal](https://portal.azure.com), open **Microsoft Entra ID** and select **+ Add > App registration** to create a new app registration. - Note the **client ID** and **tenant ID** from the app registration’s **Overview** tab. 2. Go to **Manage > Certificates & secrets**, select **+ New client secret**, and copy the **Value** (this is your client secret). 3. In **Blob Storage**, open your container, select **Access control (IAM) > Add > Add role assignment**, and assign the app registration one of the following roles: - `Storage Blob Data Contributor` - `Storage Blob Data Reader` 4. Enter the client ID, tenant ID, client secret, and account URL in the connector configuration. See [OAuth 2.0 (Microsoft Entra ID)](#oauth-20-microsoft-entra-id). ### SAS token 1. In the **Azure Portal**, open your **Storage account > Containers**, and select your container. 2. From the container’s **toolbar**, click **Generate SAS**. 3. Enter the **SAS token** and **SAS URL** in the connector configuration. See [SAS token authentication](#sas-token-authentication). :::tip Keep your credentials secure by avoiding direct inclusion in the BPMN `xml` file. Store them as [connector secrets](/components/saas/clusters/manage-secrets.md) (for example, `AZURE_SAS_TOKEN`) and reference them as `{{secrets.AZURE_SAS_TOKEN}}`, or use an [Azure Blob Storage credential](#authentication). ::: ## Configuration ### Authentication Choose how the connector authenticates to your storage account. | Property | Type | Required | Description | Example | | :---------------------------- | :------------ | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | | Azure Blob Storage credential | Configuration | No | Select a reusable Azure Blob Storage credential, or leave it empty to configure authentication on the element. When you select a credential, the **Type** field and the authentication fields below are hidden. Available from Camunda 8.11. See [Using an Azure Blob Storage credential](#using-an-azure-blob-storage-credential). | | | Type | Dropdown | Yes | The authentication type: **SAS token** (default) or **OAuth 2.0 (Microsoft Entra ID)**. Shown only when no credential is selected. | | #### Using an Azure Blob Storage credential In Camunda 8.11 and later, you can select a reusable **Azure Blob Storage credential** instead of entering the authentication details on each element. When you select a credential, the manual authentication fields are hidden, and the credential supplies the authentication details. Leave the field empty to configure authentication manually as described in the following sections. For more information, see [using credentials](/components/connectors/use-connectors/index.md#using-credentials) and [credentials](/components/hub/organization/credentials/index.md). #### SAS token authentication Select **SAS token** in the **Type** dropdown. These fields are shown only when no credential is selected. | Property | Type | Required | Description | Example | | :-------- | :----- | :------- | :------------------------------------------------------------------------------------------------------------ | :------ | | SAS token | String | Yes | The shared access signature (SAS) token of the container. See [SAS token](#sas-token) for how to generate it. | | | SAS URL | String | Yes | The shared access signature (SAS) URL of the container. See [SAS token](#sas-token) for how to generate it. | | #### OAuth 2.0 (Microsoft Entra ID) Select **OAuth 2.0 (Microsoft Entra ID)** in the **Type** dropdown. These fields are shown only when no credential is selected. See [OAuth 2.0](#oauth-20) for how to create the app registration and assign roles. | Property | Type | Required | Description | Example | | :------------ | :----- | :------- | :---------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------ | | Tenant id | String | Yes | The ID of the Microsoft Entra ID tenant that owns the application. | | | Client id | String | Yes | The application (client) ID of the registered Microsoft Entra ID application. | | | Client secret | String | Yes | The client secret generated for the registered Microsoft Entra ID application. | | | Account url | String | Yes | The URL of the Azure Blob Storage account to connect to. Replace `` with your account name. | `https://.blob.core.windows.net/` | ### Additional properties | Property | Type | Required | Description | Example | | :------------------- | :----- | :------- | :----------------------------------------------------------------------------------------------------- | :------ | | Timeout (in seconds) | Number | Yes | The time in seconds allowed for the upload to complete. Default: `30`. Shown only for **Upload blob**. | `30` | To change retries and the retry backoff, see [use an outbound connector](/components/connectors/use-connectors/outbound.md#retries). ## Operations Select the desired operation from the **Operation** dropdown: **Upload blob** (default) or **Download blob**. ### Upload document Upload a document to a container in your storage account. #### Input parameters | Property | Type | Required | Description | Example | | :--------------------- | :------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | | Blob Storage container | String | Yes | The container to upload to. A container acts as a directory that organizes a set of blobs. It must match the container the **SAS token** was created for when using SAS token authentication. | `invoices` | | Document source | Dropdown | Yes | The [document source](/components/document-handling/send-document-to-external-system.md#document-sources) of the document to upload: **Camunda Document** (default), **Inline Content**, or **From URL**. | | | Camunda document | String | Yes | A reference to a document in the Camunda document store, as a FEEL expression. Shown when **Document source** is **Camunda Document**. | | | Document file name | String | No | The name of the blob. If empty, the file name from the document metadata is used. Shown when **Document source** is **Camunda Document**. | `invoice.pdf` | | Content | String | Yes | The content of the document, for example built from process data. Shown when **Document source** is **Inline Content**. | | | File name | String | No | The file name of the inline document. Shown when **Document source** is **Inline Content**. | | | Content type | String | No | The content type of the inline document. Shown when **Document source** is **Inline Content**. | | | URL | String | Yes | The URL to fetch the document from. Shown when **Document source** is **From URL**. | | | File name | String | No | The file name of the document fetched from the URL. Shown when **Document source** is **From URL**. | | :::note Use **Inline Content** to build a file (for example, a `.json` file) directly from process variables without first storing it in the Camunda document store. See [inline documents](/components/document-handling/send-document-to-external-system.md#inline-documents). If an uploaded document already exists in the container with the same name, it will be overwritten. Depending on the settings made on the Azure Storage Account, the previous version of the document may still be accessible in the file version history. ::: #### Output The connector returns the container, the name of the uploaded blob, and a link to it: ```json { "container": "invoices", "filename": "invoice.pdf", "link": "https://.blob.core.windows.net/invoices/invoice.pdf" } ``` To keep only the link in a process variable, use a result expression such as: ```feel = {"blobUrl": response.link} ``` ### Download document Download a document from a container in your storage account. #### Input parameters | Property | Type | Required | Description | Example | | :--------------------- | :------- | :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | | Blob Storage container | String | Yes | The container to download from. A container acts as a directory that organizes a set of blobs. It must match the container the **SAS token** was created for when using SAS token authentication. | `invoices` | | File name | String | Yes | The name of the document to download. | `invoice.pdf` | | Response format | Dropdown | Yes | How the downloaded payload is returned: **Document reference** (default) uploads it to the Camunda document store and returns a reference, **as text** decodes it as a string, and **as JSON** parses it into a structure. See [return formats](/components/document-handling/send-document-to-external-system.md#return-formats). | | | Encoding | String | No, defaults to `UTF-8` | The character set used to decode the response. Default: `UTF-8`. Shown when **Response format** is **as text**. | `UTF-8` | :::note **as text** and **as JSON** return the content directly in a process variable and are subject to a size guard (approximately 1.5 MiB); use **Document reference** for large files. **as JSON** fails the job when the content is not valid JSON. ::: #### Output The output depends on the **Response format**. **Document reference** returns a reference to a document created in the Camunda document store in `document`: ```json { "document": { "camunda.document.type": "camunda", "storeId": "in-memory", "documentId": "8b54b413-b847-4650-b445-de963d5c506d", "contentHash": "ed0f7ad835669698a108a32b2a99e89e4f5aea84127fde68df4248b11197b0e5", "metadata": { "contentType": "application/pdf", "size": 2048, "fileName": "invoice.pdf" } } } ``` **as text** returns the decoded content as a string in `content`: ```json { "content": "Invoice 2025-0042" } ``` **as JSON** returns the parsed content in `content`: ```json { "content": { "invoiceId": "2025-0042", "total": 120.5 } } ``` For example, to read a field from a JSON blob, use a result expression such as: ```feel = {"invoiceId": response.content.invoiceId} ``` ## Further resources - [Azure Blob Storage documentation](https://learn.microsoft.com/en-us/azure/storage/blobs/) - [Document handling](/components/document-handling/getting-started.md) --- ## Azure OpenAI connector The **Azure OpenAI connector** is an outbound connector that allows you to interact with [Azure OpenAI](https://azure.microsoft.com/en-us/products/ai-services/openai-service) models from your BPMN processes. The **Azure OpenAI connector** currently supports only prompt operations: [`completions`](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#completions), [`chat completions`](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#chat-completions), and [`completions extensions`](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#completions-extensions). Refer the [official models documentation](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models) to find out if a desired model supports the operations mentioned. ## Prerequisites To begin using the **Azure OpenAI connector**, ensure you have created and deployed an Azure OpenAI resource. A valid Azure OpenAI API key is also required. Learn more at the [official Azure OpenAI portal entry](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/create-resource). ## Create an Azure OpenAI connector task ## Make your Azure OpenAI connector executable To work with the **Azure OpenAI connector**, fill all mandatory fields. ## Authentication Fill the **API key** field with a valid Azure OpenAI API key. [Learn more](https://learn.microsoft.com/en-us/azure/ai-services/openai/quickstart?tabs=command-line%2Cpython-new&pivots=rest-api#retrieve-key-and-endpoint) about obtaining a key. ### Create a new connector secret Keep your **API key** safe and avoid exposing it in the BPMN `xml` file by creating a secret: 1. Follow our [guide for creating secrets](/components/saas/clusters/manage-secrets.md). 2. Name your secret (for example, `AZURE_OAI_SECRET`) so you can reference it later in the connector. ## Operation Select the desired operation from the **Operation** dropdown. Fill in the **Resource name**, the **Deployment ID**, and the **API version** related to your operation. Ensure the deployed [model](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models) supports the selected operation. ### Completion, chat completion, and completion extension - For **completion** details, refer to the related [Microsoft reference documentation](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#completions). - For **chat completion** details, refer to the related [Microsoft reference documentation](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#chat-completions). - For **completion extension** details, refer to the related [Microsoft reference documentation](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#completions-extensions). ## Handle connector response The **Azure OpenAI connector** is a protocol connector, meaning it is built on top of the **HTTP REST connector**. Therefore, handling response is still applicable [as described](/components/connectors/protocol/rest.md#response). ## Usage example ### Chat completions Assume you have deployed a `gpt-35-turbo` model with the following URL: `https://myresource.openai.azure.com/openai/deployments/mydeployment/completions?api-version=2024-02-01`, and created a Connector secret with the name `AZURE_OAI_SECRET`. Consider the following input: - **API key**: `{{secrets.AZURE_OAI_SECRET}}` - **Operation**: `Chat completion` - **Resource name**: `myresource` - **Deployment ID**: `mydeployment` - **API version**: `2024-02-01` - **Message role**: `User` - **Message content**: `What is the age of the Universe?` - **Message context**: `=[{"role": "system", "content": "You are helpful assistant."}]` - Leave the rest of the params blank or default - **Result variable**: `myOpenAIResponse` In the `myOpenAIResponse` you will find the following result: ```json { "status": 200, "headers": { ... }, "body": { "choices": [ { "content_filter_results": { "hate": { "filtered": false, "severity": "safe" }, "self_harm": { "filtered": false, "severity": "safe" }, "sexual": { "filtered": false, "severity": "safe" }, "violence": { "filtered": false, "severity": "safe" } }, "finish_reason": "stop", "index": 0, "message": { "content": "The age of the universe is estimated to be around 13.8 billion years. This age is determined through various scientific methods, such as measuring the cosmic microwave background radiation and studying the expansion rate of the universe.", "role": "assistant" } } ], "created": "...", "id": "...", "model": "gpt-35-turbo", "object": "chat.completion", "prompt_filter_results": [ { "prompt_index": 0, "content_filter_results": { "hate": { "filtered": false, "severity": "safe" }, "self_harm": { "filtered": false, "severity": "safe" }, "sexual": { "filtered": false, "severity": "safe" }, "violence": { "filtered": false, "severity": "safe" } } } ], "usage": { "completion_tokens": 43, "prompt_tokens": 24, "total_tokens": 67 } } } ``` --- ## Blue Prism connector The **Blue Prism connector** allows you to orchestrate a Blue Prism queue from your BPMN process with [Blue Prism RPA](https://www.blueprism.com/). To start using the connector, you must have a running Blue Prism instance configured API service. Refer to the [official documentation page](https://documentation.blueprism.com/bp-7-5/en-us/Guides/bp-api/api-introduction.htm?tocpath=Installation%7CInstall%20the%20Blue%20Prism%20API%7C_____0) to learn more about how to install and configure Blue Prism API service. ## Create a Blue Prism connector task ## Authentication You can choose among the available **Blue Prism connector** authentication types according to your authentication requirements. ### Bearer token authentication #### Create a new connector secret We advise you to keep your **Bearer Token** safe and avoid exposing it in the BPMN `xml` file by creating a secret: 1. Follow our [guide for creating secrets](../../saas/clusters/manage-secrets.md). 2. Name your secret (i.e `BLUE_PRISM_BEARER_TOKEN`) so you can reference it later in the connector. #### Configure the bearer token Select the **Blue Prism connector** and fill out the following properties under the **Authentication** section: 1. Click **Bearer Token** in the **Authentication** section. 2. Set **Bearer** to the secret you created (i.e. `{{secrets.BLUE_PRISM_BEARER_TOKEN}}`). ### OAuth Client Credentials Flow #### Create a new connector secret We advise you to keep your **Client ID** and **Client secret** safe and avoid exposing it in the BPMN `xml` file by creating a secret: 1. Follow our [guide for creating secrets](../../saas/clusters/manage-secrets.md). 2. Name your secret (i.e `BLUE_PRISM_CLIENT_ID`) so you can reference it later in the connector. #### Configure the OAuth Token Select the **Blue Prism connector** and fill out the following properties under the **Authentication** section: 1. Select **OAuth 2.0 client credentials** in the **Authentication** section. 2. Set **Identity token provider URL** to identity provider configured for your Blue Prism instance. 3. Set **Client ID** to the secret you created (i.e. `{{secrets.BLUE_PRISM_CLIENT_ID}}`). 4. Set **Client secret** to the secret you created (i.e. `{{secrets.BLUE_PRISM_CLIENT_SECRET}}`). Find more information about the OAuth client credentials flow in the [RFC reference](https://www.rfc-editor.org/rfc/rfc6749#section-4.4). ## Operation types The **Blue Prism connector** currently supports two operation types in the **Operation type** dropdown list: _Get item from a queue by ID_ and _Create work queue item_. ### Get item from a queue by ID This operation allows you to return details of a specified item from a work queue. It matches directly to respective Blue Prism API endpoint - [`Return details of a specified item from a work queue`](https://documentation.blueprism.com/bp-7-5/en-us/bp-api/bpe-7-5-0-api-spec.html#tag/Work-Queues/operation/getWorkQueueItemFromWorkQueue). #### Usage 1. Select **Get item from a queue by ID** from the **Operation** dropdown. 2. Populate **Authentication section** as described in the [respective section](#authentication). 3. In the **Configuration** section, set **Blue Prism API base URL** field. E.g., `http://my.bp.host.com:9876`. 4. In the **Input** section, set **Work queue ID**. This is the identifier of a queue, where the item is fetched from. 5. In the **Input** section, set **Queue item ID**. This is the identifier of the item to be fetched. #### Get item from a queue by ID response Given you have a queue item ID previously added to a queue, the operation **Get item from a queue by ID response** returns information about a certain item. You can use an output mapping to map the response: 1. Use **Result Variable** to store the response in a process variable. For example, `myResultVariable`. 2. Use **Result Expression** to map fields from the response into process variables. It comes with a pre-filled value of `={itemState:response.body.state}`. You will observe the `itemState` in the process variables. Its value will let you know if the item was processed or not. Response example: ```json { "id": "01234567-89ab-cdef-0123-456789abcdef", "priority": 3, "ident": 123, "state": "Completed", "keyValue": "Example value", "status": "Example status", "tags": ["Example tag 1", "Example tag 2"], "attemptNumber": 1, "loadedDate": "2020-10-02T12:34:56+01:00", "deferredDate": "2020-10-02T12:34:56+01:00", "lockedDate": "0001-01-01T00:00:00Z", "completedDate": "2020-10-02T13:00:00+01:00", "exceptionedDate": "0001-01-01T00:00:00Z", "exceptionReason": "Example reason", "lastUpdated": "2020-10-02T13:00:00+01:00", "workTimeInSeconds": 123, "attemptWorkTimeInSeconds": 123, "resource": "Example resource", "data": { "rows": [] }, "sla": 7200, "sladatetime": "0001-01-01T00:00:00Z", "processname": "Example process name", "issuggested": false } ``` ### Create work queue item This operation allows you to create work queue items in the specified queue. It matches directly to respective Blue Prism API endpoint - [`Create work queue items`](https://documentation.blueprism.com/bp-7-5/en-us/bp-api/bpe-7-5-0-api-spec.html#tag/Work-Queues/operation/createWorkQueueItems). #### Usage 1. Select **Create work queue item** from the **Operation** dropdown. 2. Populate the **Authentication section** as described in the [respective section](#authentication). 3. In the **Configuration** section, set **Blue Prism API base URL** field. E.g., `http://my.bp.host.com:9876`. 4. In the **Input** section, set **Work queue ID**. This is the identifier of a queue, where item will be fetched from. 5. In the **Input** section, set **Item type** of the data entry you wish to submit to the queue. 6. In the **Input** section, set **Item value** of the data entry you wish to submit to the queue. 7. In the **Input** section, set **Defer date**. This field is the earliest time and date that this item is deferred until. 8. In the **Input** section, set **Priority**. This field is the priority value assigned to the item. 9. In the **Input** section, set **Status**. This is the user-supplied status value. _Note: Do not confuse this with queue item 'state' property._ #### Create work queue item response The operation **Create work queue item** returns information about the newly created item in the queue. You can use an output mapping to map the response: 1. Use **Result Variable** to store the response in a process variable. For example, `myResultVariable`. 2. Use **Result Expression** to map fields from the response into process variables. It comes with a pre-filled value of `={itemId:response.body.ids[1]}`. To use operation _Get queue item result by ID_, you need an `itemId`. This expression will add it in the context for you. Learn more in [get queue item result by ID](#get-item-from-a-queue-by-id). Response example: ```json { "ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"] } ``` ### Using Blue Prism connector best practice There is no guarantee a queue item will be processed right away. In that case, we suggest building your BPMN diagram to periodically retry polling. To learn more, refer to an entry _Solution with Timer and Loop_ at [Camunda BPMN examples](https://camunda.com/bpmn/examples/) page. :::note To avoid performance issues, it is recommended to limit the number of loop retries. ::: --- ## Box connector The **Box connector** is an outbound connector that allows you to interact with the [Box](https://box.com/) storage API from your BPMN process. ## Prerequisites To use the **Box connector**, you must have a Box account. You can use an [enterprise account](https://www.box.com/) or create a [developer account](https://developer.box.com/). The **Box connector** supports different Box API authentication methods, all of which require a [Custom App](https://developer.box.com/guides/applications/app-types/custom-apps/) in your Box account. - Camunda recommends enabling the **Generate user access tokens** feature in **Advanced Features** in your app configuration. This allows the connector to login as an [app user](https://github.com/box/box-java-sdk/blob/v4.13.1/doc/authentication.md#obtaining-user-token). You can use the **User ID** shown on the overview page in your Box app console. - Ensure the app is **Authorized** in the **Platform Apps Manager** section of your Box account. :::note A [Custom App](https://developer.box.com/guides/applications/app-types/custom-apps/) is required to interact with the Box API without any manual user interaction during credentials creation when authenticating with the connector. ::: ## Create a Box connector task ## Authentication To authenticate the connector with Box, select an authentication type from the **Authentication** dropdown: - **Client Credentials Enterprise**: Authenticate using an **Enterprise ID**. You must have an enterprise account with Box. - **Client Credentials User**: Authenticate using a **User ID**. You must have a [Custom App](https://developer.box.com/guides/applications/app-types/custom-apps/) set up in Box. - **Devoloper Token** : Authenticate using a developer token. These tokens are usually only valid for 60 minutes, and should be used for testing purposes only. - **JWT JSON Config**: Authenticate using a JSON configuration file created and downloaded from your Box App Configuration page. ## Operations The **Box connector** supports the following operations that allow you to interact with the items stored in your Box account. :::note The Box connector supports two methods for locating items stored in your Box account: - File or folder path properties allow you to either specify the **Item ID** (found in the Box URL when browsing your items) or using the item names separated by slashes (`/`) for items in folders. - A path consisting of only a single `/` denotes the root of your Box folder. For example, `/my-folder` would point to the `my-folder` folder located in the root directory. You can use the path notation for files (`/my/image.png`) and folders (`/my/sub/folder`). ::: :::note Starting from version 8.7.0, the Box connector supports uploading documents from (or downloading documents to) the Camunda document store. Review the **Document** field in the properties panel where the document reference can be provided. See additional details and limitations in [document handling](/components/document-handling/getting-started.md). ::: ### Create Folder Creates a new folder in your store. | Property | Type | Required | Example | | :---------- | :---------- | :------- | :------------------------------- | | Folder name | String | Yes | "my-folder" | | Parent path | Folder path | No | `/`, `123`, `/my/parent/folders` | :::note This operation fails if a folder already exists with the same name, as Box only supports unique names. ::: ### Delete Folder Deletes an existing folder. Optionally include all items and subfolders. | Property | Type | Required | Example | | :---------- | :------ | :------- | :----------- | | Folder path | String | Yes | "/my-folder" | | Recursive | Boolean | No | `true` | ### Delete File Deletes a single file item. | Property | Type | Required | Example | | :-------- | :----- | :------- | :------------- | | File path | String | Yes | "/my-file.png" | ### Move File Moves a file item into a folder. | Property | Type | Required | Example | | :----------------- | :----- | :------- | :---------------- | | File path | String | Yes | "/my-file.png" | | Target folder path | String | Yes | "/another/folder" | ### Download File Downloads a file item and returns its content in the [return format](/components/document-handling/send-document-to-external-system.md#return-formats) you choose. | Property | Type | Required | Example | | ------------- | ------ | -------- | -------------------------------------- | | File path | String | Yes | "/my-file.png" | | Return format | Enum | Yes | Document reference / As text / As JSON | Select a return format for the downloaded content: - **Document reference**: a reference to a document created in the Camunda document store is returned in `document`. - **As text**: the content is decoded to a string (with an optional encoding, default UTF-8) and returned in `content`. - **As JSON**: the content is parsed as JSON and returned in `content`. :::note **As text** and **As JSON** return the content directly in a process variable and are subject to a size guard (approximately 1.5 MiB); use **Document reference** for large files. **As JSON** fails the job when the content is not valid JSON. ::: For example, with **Document reference** you can reference the downloaded file using the example response expression: ```json { "download": document } ``` ### Upload File Upload a file to your Box store. | Property | Type | Required | Example | | ----------- | --------------- | -------- | ---------------- | | File path | String | Yes | "/my-file.png" | | Folder path | String | Yes | "/upload/folder" | | Document | Document source | Yes | `{...}` | Select a [document source](/components/document-handling/send-document-to-external-system.md#document-sources) for the file to upload: a **Camunda document** reference, **inline content** built from process data, or an **external document** URL. The Box connector resolves the source and creates a new file item in your Box store. The result of the upload can be accessed via the `item` property of the result. Example value of a successful file upload: ```json { "item": { "id": "1734104173434", "name": "my_new_file.png", "type": "file" } } ``` ### Search Searches the items stored in the Box account. | Property | Type | Required | Example | | --------------------- | ------ | -------- | ------------- | | Search query | String | Yes | "inci" | | Seach sort column | String | No | `modified_at` | | Search sort direction | String | No | `DESC` | | Search offset | String | No | `0`, `10` | | Search limit | String | No | `50` | Example search result with a single item: ```json { "items": [ { "id": "1733978444906", "name": "incident-wrapped.png", "type": "file" } ] } ``` --- ## CSV connector The **CSV connector** reads CSV files and converts JSON data to CSV format for document or text use. ## Create a CSV connector task ## Operations The **CSV connector** supports operations to [read](#read-csv) and [write](#write-csv) CSVs. ### Read CSV Reads a CSV from a stored document, inline text, or a URL and converts it into an array of JSON records. The **Document source** determines where the connector reads the CSV from. Select one of the following options, then complete the field it reveals: | Document source | Reveals field | Description | | :------------------- | :------------------- | :------------------------------------------------------------------ | | **Camunda Document** | **Camunda document** | Read a document stored in Camunda, referenced by a FEEL expression. | | **Inline Content** | **Content** | Provide the raw CSV text directly. | | **From URL** | **URL** | Fetch the CSV from an external URL. | | Property | Type | Description | Required | Example | | ------------------ | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ---------------------------------------------------- | | Document source | Dropdown | Where the connector reads the CSV from: **Camunda Document**, **Inline Content**, or **From URL**. | Yes | Defaults to **Camunda Document** | | Camunda document | [FEEL](../../../modeler/feel/what-is-feel) expression | Reference to a Camunda document. Shown when **Document source** is **Camunda Document**. | Conditional | | | Content | String | The raw CSV text. Shown when **Document source** is **Inline Content**. | Conditional | [Example CSV](#example-csv-input) | | URL | String | The URL to fetch the CSV from. Shown when **Document source** is **From URL**. | Conditional | | | File name | String | File name for inline content or the URL source. | No | | | Content type | String | Content type for inline content. | No | Defaults to `text/csv` | | Delimiter | String | The delimiter used to separate each column. | No | Defaults to `,` | | Skip header record | Boolean | Whether to skip the first row (header) in the records. | No | Defaults to `true` | | Headers | Array of strings | Used when no header is present or to override column names. | No | Defaults to `[]`. Example: `["name","cost","count"]` | | Row type | String | Determines the structure of the result records. | No | Defaults to `Object`. Either `Object` or `Array`. | | Record mapping | [FEEL](../../../modeler/feel/what-is-feel) script | The FEEL script will be evaluated against every record. All data returned by this mapping script will be part of the results. This can be useful for parsing string-based CSV data into different types like numbers. Returning `null` excludes the record from the final results. | No | [Example script](#example-with-record-mapping) | :::note The **Document source** dropdown is available from element template version 3 (Camunda 8.10). Existing tasks modeled with an earlier template keep working: the connector runtime still accepts the legacy `data` input, whether it holds raw CSV text or a document reference. ::: #### Example CSV `Content` input {#example-csv-input} ```csv product,quantity,price Wireless Mouse,25,29.99 Office Chair,8,149.50 USB Cable,100,12.99 Monitor Stand,15,45.00 Desk Lamp,32,24.95 ``` :::note To pass the CSV as a FEEL string through the **Inline Content** source, end lines with `\r\n`. This is the default line separator when reading CSV files. For example: ``` ="product,quantity,price\r\nWireless Mouse,25,29.99\r\nOffice Chair,8,149.50\r\nUSB Cable,100,12.99\r\nMonitor Stand,15,45.00\r\nDesk Lamp,32,24.95" ``` ::: #### Example output for row type `Object` ```json { "records": [ { "product": "Wireless Mouse", "quantity": "25", "price": "29.99" }, { "product": "Office Chair", "quantity": "8", "price": "149.50" }, { "product": "USB Cable", "quantity": "100", "price": "12.99" }, { "product": "Monitor Stand", "quantity": "15", "price": "45.00" }, { "product": "Desk Lamp", "quantity": "32", "price": "24.95" } ] } ``` Based on the `Object` [example](#example-output-for-row-type-object) above, you can access the CSV data in your result expression for further processing: ``` = { sum: sum(for r in records return number(r.quantity)) } ``` #### Example output for row type `Array` ```json { "records": [ ["Wireless Mouse", "25", "29.99"], ["Office Chair", "8", "149.50"], ["USB Cable", "100", "12.99"], ["Monitor Stand", "15", "45.00"], ["Desk Lamp", "32", "24.95"] ] } ``` Based on the `Array` [example](#example-output-for-row-type-array) above, you can access the CSV data in your result expression for further processing: ``` = { sum: sum(for r in records return number(r[2])) } ``` #### Example with record mapping Based on the data of the `Object` [example](#example-output-for-row-type-object) above, we can use the following [FEEL](../../../modeler/feel/what-is-feel) script to extract only the `product` and the `price` converted to a number per record: ``` = { product: record.product, price: number(record.price) } ``` Leading to the following output: ``` [ {"product":"Wireless Mouse","price":29.99}, {"product":"Office Chair","price":149.5}, {"product":"USB Cable","price":12.99}, {"product":"Monitor Stand","price":45}, {"product":"Desk Lamp","price":24.95} ] ``` We can also use the record mapping as a filter to only include certain records in the final results: ``` = if number(record.price) >= 30 then {product: record.product, price: number(record.price)} else null ``` This will exclude all products with a price lower than 30 from the final results: ``` [ {"product":"Office Chair","price":149.5}, {"product":"Monitor Stand","price":45} ] ``` ### Write CSV Takes an array of JSON objects and creates a CSV from it. The result can either be stored as a document reference for further processing (for example, uploading) or returned as text. | Property | Type | Description | Required | Example | | :----------------- | :--------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Data | Array | The CSV data as an array of objects or arrays | Yes | [Object](#example-for-an-object-based-data-input) and [Array](#example-for-an-array-based-data-input) example. | | Response format | Dropdown | How the generated CSV is returned: **Document reference** stores the CSV in Camunda and returns a reference; **as text** returns the CSV inline as a string. | No | Defaults to **as text**, which returns a [string](#example-output-for-a-csv-returned-as-a-string). Select **Document reference** to store the CSV in Camunda and return a [document](#example-output-for-a-csv-stored-in-a-document) instead. | | Encoding | String | Character set used when returning the CSV as text. Shown when **Response format** is **as text**. | No | Defaults to `UTF-8` | | Delimiter | String | The delimiter used to separate each column. | No | Defaults to `,` | | Skip header record | Boolean | Whether to include the first row in the records or not. | No | Defaults to `true` | | Headers | Array of strings | Can be used when there is no header record present in the record or to change the column names if there is a header record. | No | Defaults to `[]`. Example: `["name","cost","count"]`. Needs to be specified when using object-based arrays as the `Data` input. | :::note The **Response format** dropdown replaces the earlier **Create document** boolean. Existing processes built with the previous template keep working: the legacy `createDocument` field is still honored by the connector runtime. ::: #### Example for an object-based `Data` input Every record for an object-based `Data` input contains all column names as their property (key) names. The values of the properties will be written into the CSV. ```json { "records": [ { "product": "Wireless Mouse", "quantity": "25", "price": "29.99" }, { "product": "Office Chair", "quantity": "8", "price": "149.50" }, { "product": "USB Cable", "quantity": "100", "price": "12.99" }, { "product": "Monitor Stand", "quantity": "15", "price": "45.00" }, { "product": "Desk Lamp", "quantity": "32", "price": "24.95" } ] } ``` :::info `Headers` must be specified when using object-based arrays as the `Data` input when writing a CSV. The `Headers` must match the property names of the objects. For the example above, one would provide the following value for `Headers`: ```json =["product", "quantity", "price"] ``` ::: #### Example for an array-based `Data` input Every record for an array-based `Data` input contains all values in a single array per row. ```json [ ["Wireless Mouse", "25", "29.99"], ["Office Chair", "8", "149.50"], ["USB Cable", "100", "12.99"], ["Monitor Stand", "15", "45.00"], ["Desk Lamp", "32", "24.95"] ] ``` #### Example output for a CSV returned as a string ```json { "content": "Wireless Mouse,25,29.99\r\nOffice Chair,8,149.50\r\nUSB Cable,100,12.99\r\nMonitor Stand,15,45.00\r\nDesk Lamp,32,24.95\r\n" } ``` #### Example output for a CSV stored in a document ```json { "document": { "storeId": "in-memory", "documentId": "8b54b413-b847-4650-b445-de963d5c506d", "contentHash": "ed0f7ad835669698a108a32b2a99e89e4f5aea84127fde68df4248b11197b0e5", "metadata": { "contentType": "text/csv", "size": 114, "fileName": "8b54b413-b847-4650-b445-de963d5c506d" }, "camunda.document.type": "camunda" } } ``` --- ## Trigger a process from Databricks with a webhook Trigger a Camunda process directly from a Databricks notebook with the [HTTP Webhook connector](/components/connectors/protocol/http-webhook.md), using fraud detection as the example scenario. ## Scenario A Databricks job scores a transaction and sends the result to Camunda through a webhook. Camunda starts a process instance, drafts a review email with the OpenAI connector, and routes the transaction based on its risk score: - A risk score of 0.75 or higher triggers an AI-drafted email that a fraud analyst reviews and approves before it's sent. - A risk score below 0.75 ends the process automatically, with no email sent. This scenario fits a webhook integration well because Databricks controls when the process starts, and the payload maps directly onto a small set of process variables. ## Prerequisites - A Camunda 8 SaaS cluster or a Self-Managed cluster running Camunda 8.7 or later. - [Web Modeler](/components/hub/workspace/modeler/index.md) access to build the process, plus permission to deploy it and manage cluster secrets. - A Databricks workspace with Python notebooks enabled and outbound HTTPS access to your Camunda webhook endpoint. - An [OpenAI](https://platform.openai.com/api-keys) API key and a [SendGrid](https://app.sendgrid.com) API key. The process uses these to draft and send a review email for flagged transactions. ## Create the BPMN diagram 1. Log in to [Camunda Hub](https://console.camunda.io), open a workspace, and open a project. 2. Click **Create new > BPMN diagram**. 3. In the top navigation, open the menu next to **New BPMN diagram**, click **Rename**, and name it `Fraud Detection`. Hub Modeler opens a blank diagram with a default start event. The following diagram shows the completed process structure: ![Fraud detection process diagram](./img/fraud-detection-process.png) ## Convert the start event to a webhook The default start event starts a process instance manually. Change it to start the process from an incoming webhook call instead. 1. Click the start event, then click the wrench icon that appears. 2. Search for `webhook` and select **Webhook Start Event Connector**. The start event now shows a webhook icon. Configure it in the properties panel: | Field | Value | Notes | | :-------------- | :---------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Webhook ID | `fraud-detection-trigger` | Becomes part of the webhook URL. | | Authorization | API Key | Authenticates incoming webhook calls. | | API Key | `{{secrets.FraudWebhookKey}}` | The expected value, referenced from a secret rather than typed directly into the field. | | API Key locator | `=split(request.headers.authorization, " ")[2]` | Extracts the key from the `Authorization: Bearer ` header. See [how to configure API key authorization](/components/connectors/protocol/http-webhook.md#how-to-configure-api-key-authorization). | The Databricks notebook in this guide sends the key as a `Bearer` token, so the locator splits the header on the space and takes the second part rather than comparing the raw header value. To reject payloads without a numeric `riskScore` at the webhook, set **Verification expression** to the following. Databricks then gets a `400` response instead of a `200`, and the run fails visibly: ```feel =if request.body.riskScore instance of number then null else {"body": {"error": "riskScore must be a number"}, "statusCode": 400} ``` In **Variable Mapping**, set **Result expression** to map the incoming JSON body onto process variables: ```feel = { caseId: request.body.caseId, customerId: request.body.customerId, emailAddress: request.body.emailAddress, transactionAmount: request.body.transactionAmount, riskScore: request.body.riskScore, modelVersion: request.body.modelVersion, timestamp: request.body.timestamp } ``` ## Route on the risk score Add an [exclusive gateway](/components/modeler/bpmn/exclusive-gateways/exclusive-gateways.md) named `Is Fraud Detected?` after the start event, with two outgoing paths: | Path | Condition | Next step | | :-------- | :------------------ | :--------------------------------------------------------------------- | | High risk | Default flow | Continues into the OpenAI, review, and SendGrid tasks below. | | Low risk | `=riskScore < 0.75` | An end event named `No Fraud Detected`. No email is sent on this path. | Mark the high-risk path as the default flow, so a transaction goes to manual review if the low-risk condition can't be evaluated. Without a default flow, a missing or non-numeric `riskScore` matches no path and raises an incident at the gateway. The rest of this guide builds the high-risk path. ## Draft a review email with OpenAI On the high-risk path, add a service task after the gateway named `Generate Email Inquiry`, and change its type to the [OpenAI connector](/components/connectors/out-of-the-box-connectors/openai.md). | Setting | Value | | :------------- | :---------------------------------------------------- | | OpenAI API key | `{{secrets.OpenAI}}` | | Operation | Chat | | Model | Select a chat model available to your OpenAI account. | Set the prompt as a FEEL expression so the process variables are substituted at runtime: ```feel ="Review this transaction for possible fraud and draft a short customer email if follow-up is needed. Amount: " + string(transactionAmount) + ", customer ID: " + customerId + ", ML risk score: " + string(riskScore) + "." ``` Map the response to a variable with a result expression: ```feel = {emailBody: response.body.choices[1].message.content} ``` ## Review the drafted email Add a [user task](/components/modeler/bpmn/user-tasks/user-tasks.md) after the OpenAI task, named `Review Flagged Transaction`. This gives a fraud analyst a chance to validate the AI's assessment and edit the drafted email before it reaches the customer. Link a [Camunda Form](/components/hub/workspace/modeler/modeling/utilize-forms.md) to the task with a **Text area** field: | Setting | Value | | :---------- | :------------ | | Field label | Email content | | Key | `emailBody` | Binding the field's **Key** to `emailBody` prefills the text area with the AI-drafted email. When the analyst completes the task, any edits they make overwrite the `emailBody` process variable. ## Send the email with SendGrid After the review task, add a service task named `Send Email`, and change its type to the [SendGrid connector](/components/connectors/out-of-the-box-connectors/sendgrid.md). This sends the analyst-approved version of `emailBody`. | Setting | Value | | :--------------- | :------------------------------------------------------------------------------ | | SendGrid API Key | `{{secrets.SendGrid}}` | | Sender Name | Your organization's name, for example `Fraud Detection Team`. | | Sender Email | Your verified sender address, for example `community@camunda.com`. | | Receiver Name | Leave blank, or provide a display name if you map one from the webhook payload. | | Receiver Email | `=emailAddress` | | Subject | `Need more information about your transaction` | | Content Type | `text/plain` | | Body | `=emailBody` | Add an end event named `Fraud Escalated` after this task to close the high-risk path. Here's what your completed diagram should look like in Web Modeler: ![Completed fraud detection process in Web Modeler](./img/fraud-detection-modeler-completed.png) ## Store your API credentials as secrets Store the webhook credential and the OpenAI and SendGrid API keys as [secrets](/components/saas/clusters/manage-secrets.md) before you deploy the process. Create three secrets: | Key | Value | | :---------------- | :------------------------------------------------------------------------------------------ | | `FraudWebhookKey` | The credential Databricks sends to the webhook, for example `fraud-webhook-test-abc123xyz`. | | `OpenAI` | Your OpenAI API key. | | `SendGrid` | Your SendGrid API key. | How you create them depends on where your cluster runs: - **SaaS**: In Camunda Hub, under **Console** in the left navigation, click **Clusters**, select your cluster, and open the **Cluster secrets** tab. Click **Create new secret** for each key in the table. - **Self-Managed**: Secrets aren't managed in the UI. Ask an operator to add these keys as described in [connector secrets configuration](/self-managed/components/connectors/connectors-configuration.md). Reference the secrets from Connector fields so their values aren't stored as plain text in the BPMN model. Reference them from a connector task as `{{secrets.FraudWebhookKey}}`, `{{secrets.OpenAI}}`, or `{{secrets.SendGrid}}`. :::tip On Camunda 8.10 and later, you can also reference these secrets as `=camunda.secrets.FraudWebhookKey`, `=camunda.secrets.OpenAI`, and `=camunda.secrets.SendGrid`. See [reference connector secrets as `camunda.secrets.`](/components/saas/clusters/manage-secrets.md#reference-connector-secrets-as-camundasecretsname). ::: ## Deploy the process and copy the webhook URL In the top right corner of the modeling interface, click the dropdown next to **Deploy & run**, click **Deploy**, select a stage and the resources to deploy, then click **Deploy** again. How you get the webhook URL depends on where your cluster runs: - **SaaS**: Click the start event, open the **Webhook** tab in the properties panel, and copy the webhook URL. This tab is only available for Camunda 8 SaaS. - **Self-Managed**: The **Webhook** tab isn't available. Build the URL yourself as `http(s):////inbound/fraud-detection-trigger`, using your Connectors runtime's base URL and, if configured, its Helm chart context path. See [activate the HTTP Webhook connector](/components/connectors/protocol/http-webhook.md#activate-the-http-webhook-connector-by-deploying-your-diagram) for the full URL format. Save the webhook URL, along with the `FraudWebhookKey` secret value from [Store your API credentials as secrets](#store-your-api-credentials-as-secrets). You need both to call the webhook from Databricks. ## Trigger the process from a Databricks notebook ### Create the notebook 1. In Databricks, open **Workspace**, then **Create > Notebook**. 2. Name it `Fraud_Detection_Trigger` and set the language to Python. ### Add the trigger code Paste the following into the first cell. In production, `transaction` would come from your ML model or a data pipeline; this example uses a hardcoded sample transaction so you can test the integration end to end. :::warning `CAMUNDA_WEBHOOK_SECRET` is hardcoded here for testing only. Don't commit a real webhook credential as plain text. In a production notebook, retrieve it from a [Databricks secret scope](https://docs.databricks.com/en/security/secrets/index.html) instead, for example `dbutils.secrets.get(scope="camunda", key="webhook-key")`. ::: ```python from datetime import datetime, timezone CAMUNDA_WEBHOOK_URL = "https://example-webhook-url.com/" CAMUNDA_WEBHOOK_SECRET = "fraud-webhook-test-abc123xyz" # Test value only; use a secret scope in production transaction = { "caseId": "FRAUD-2026-001", "customerId": "CUST-98765", "emailAddress": "customer@example.com", "transactionAmount": 15000.00, "riskScore": 0.87, "modelVersion": "fraud-detection-v2", "timestamp": datetime.now(timezone.utc).isoformat(), } headers = { "Content-Type": "application/json", "Authorization": f"Bearer {CAMUNDA_WEBHOOK_SECRET}", } response = requests.post( CAMUNDA_WEBHOOK_URL, json=transaction, headers=headers, timeout=30 ) if response.status_code in (200, 201): print(f"Process instance started (status {response.status_code}).") else: print(f"Webhook call failed (status {response.status_code}): {response.text}") ``` Replace `CAMUNDA_WEBHOOK_URL` and `CAMUNDA_WEBHOOK_SECRET` with the values you copied in [Deploy the process and copy the webhook URL](#deploy-the-process-and-copy-the-webhook-url). ## Run the notebook and confirm the process started 1. Click the code cell and press `Ctrl+Enter` to run it. 2. Confirm the cell prints `Process instance started`. 3. Open [Operate](/components/operate/operate-introduction.md), find the `Fraud Detection` process, and open the running instance. The sample transaction has `riskScore: 0.87`, which is above the `0.75` threshold, so the instance should have already run the OpenAI task and be waiting at the `Review Flagged Transaction` user task. The SendGrid task runs only after the analyst completes the review. In Operate, you can see the process variables from the webhook payload (`caseId`, `riskScore`, `emailBody`, and so on), the current execution state, and any incidents, such as a missing secret. If the instance isn't waiting at `Review Flagged Transaction`, check for an incident on the OpenAI task first; a missing or misspelled secret reference is the most common cause. :::tip If the webhook call fails with a `401` status, check that `CAMUNDA_WEBHOOK_SECRET` matches the `FraudWebhookKey` secret value you set in [Store your API credentials as secrets](#store-your-api-credentials-as-secrets). If it fails with a `400` status, the payload's `riskScore` is missing or isn't a number. ::: ## Next steps - Replace the hardcoded sample transaction with your actual model output to trigger the process from a production Databricks job. - Add [inbound connector deduplication](/components/connectors/advanced-topics/deduplication.md) if your Databricks job might send the same transaction more than once. - Read the [Databricks connector](databricks.md) reference if you also want Camunda to call Databricks directly, for example to look up additional transaction history before routing. --- ## Databricks connector The **Databricks connector** allows you to call the [Databricks REST API](https://docs.databricks.com/api/workspace/introduction) from your BPMN process — SQL Statement Execution, SQL Warehouses, Jobs, Model Serving, and Vector Search. This connector reuses the base implementation of the [REST connector](../../protocol/rest.md) by providing a compatible element template. There is no additional runtime to deploy. ## Prerequisites To use the **Databricks connector**, you need an active Camunda 8.9 or later cluster, and a Databricks workspace with — depending on the operation — a SQL warehouse, job, Model Serving endpoint, or Vector Search index to target. You also need credentials to authenticate against your workspace. See [configure authentication](#configure-authentication) below. :::note Use secrets to store credentials so you don't expose sensitive information directly from the process. See [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ::: ## Create a Databricks connector task ## Choose an API and operation In the **Databricks API** dropdown list, select the API you want to call. In the **Operation** dropdown list, select one of the operations supported for that API. The workspace URL, endpoint path, HTTP method, and query parameters are derived automatically from this choice; only the fields the selected operation needs are shown. | API | Operation | Request | | ----------------------- | --------------------------------- | ------------------------------------------------------------------------ | | SQL Statement Execution | Execute statement | `POST /api/2.0/sql/statements` | | SQL Statement Execution | Get statement status and result | `GET /api/2.0/sql/statements/{statement_id}` | | SQL Statement Execution | Get result chunk | `GET /api/2.0/sql/statements/{statement_id}/result/chunks/{chunk_index}` | | SQL Statement Execution | Cancel statement | `POST /api/2.0/sql/statements/{statement_id}/cancel` | | SQL Warehouses | Get warehouse | `GET /api/2.0/sql/warehouses/{id}` | | SQL Warehouses | Start warehouse | `POST /api/2.0/sql/warehouses/{id}/start` | | SQL Warehouses | Stop warehouse | `POST /api/2.0/sql/warehouses/{id}/stop` | | Jobs | Run job now | `POST /api/2.2/jobs/run-now` | | Jobs | Get run | `GET /api/2.2/jobs/runs/get` | | Jobs | Get run output | `GET /api/2.2/jobs/runs/get-output` | | Jobs | Cancel run | `POST /api/2.2/jobs/runs/cancel` | | Model Serving | Invoke chat / LLM endpoint | `POST /serving-endpoints/{name}/invocations` | | Model Serving | Invoke custom model (raw payload) | `POST /serving-endpoints/{name}/invocations` | | Model Serving | Get endpoint | `GET /api/2.0/serving-endpoints/{name}` | | Vector Search | Query index | `POST /api/2.0/vector-search/indexes/{index_name}/query` | :::note Model Serving invocations are the only requests without an `/api/2.0` prefix. Every other operation, including **Get endpoint**, is prefixed. ::: ## Configure authentication | Type | Use | | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | OAuth machine-to-machine (M2M) with a service principal | Recommended for production. Client credentials are sent as a Basic authentication header to `https:///oidc/v1/token` with `scope=all-apis`. Access tokens are valid for one hour. | | Personal access token | Testing only. | The OAuth token endpoint is derived from the workspace URL, so it does not need to be configured separately. ## Handle common workflows ### Run a long SQL statement For SQL statements that take longer than 50 seconds, set **Execute statement**'s `wait_timeout` to `0s` (or `CONTINUE` on timeout). This returns a `statement_id` while the statement is still running. Poll **Get statement status and result** with a BPMN timer until `status.state` reaches a terminal state. If the response contains `result.next_chunk_index`, retrieve the remaining results with **Get result chunk**. ### Trigger a job and wait for completion **Run job now** returns a `run_id`. Poll **Get run** until `state.life_cycle_state` reaches one of these terminal values: - `TERMINATED` - `SKIPPED` - `INTERNAL_ERROR` Do not check only for `TERMINATED`, as this causes the loop to continue indefinitely if a run is skipped or fails internally. When the run reaches a terminal state, use `state.result_state` to determine the result: - `SUCCESS` - `FAILED` - `TIMEDOUT` - `CANCELED` Use **Cancel run** to handle BPMN-side cancellation or a boundary timer. :::note For a multi-task job, **Get run output** requires an individual task's `run_id` from `tasks[].run_id` in the terminal **Get run** response. Do not use the top-level `run_id` returned by **Run job now**, as Databricks accepts only a single task's run ID. ::: ### Avoid duplicate writes on retry **Execute statement** and **Run job now** are non-idempotent, so the **Retries** field defaults to `0`. A retry would otherwise resend the identical request. The SQL Statement Execution API has no idempotency key, so keep **Retries** at `0` for **Execute statement**. **Run job now** accepts an idempotency token. Set **Idempotency token** to a value that remains stable for each process instance. Databricks then returns the existing run instead of starting a new one. You can safely increase **Retries** for read-only operations, such as **Get run**, **Get warehouse**, or **Get statement status and result**. ### Start a warehouse before execution When you use **Execute statement** with a stopped warehouse, Databricks waits for the warehouse to start. To control this explicitly, call **Start warehouse** and poll **Get warehouse** until `state` is `RUNNING`. **Start warehouse** and **Stop warehouse** return immediately and do not wait for the state transition to finish. ### Raise the job timeout for slow calls Model Serving allows up to 597 seconds of model execution — well beyond SQL's own `wait_timeout`, which is capped at 50 seconds. If **Job timeout** stays at its default while you increase **Read timeout in seconds** for a slow Model Serving call, Zeebe can time out the job and reactivate it on another worker while the first HTTP request is still in flight. This can result in a duplicate non-idempotent call that **Retries** = `0` does not prevent because the retry happens outside the connector. Increase both settings together. ### Provide the required vector search inputs **Query index** always needs **Columns**, plus exactly one of **Query text** or **Query vector** — which one depends on the index type. Supplying columns alone passes template validation, but the API rejects the request. ## Handle statement and job outcomes The Databricks SQL Statement Execution API returns HTTP 200 with `status.state = FAILED` when a statement fails at the warehouse, so a plain HTTP success check is not enough. The terminal states are: | State | Meaning | | ----------- | --------------------------------------------------------------------------------------------- | | `SUCCEEDED` | Execution successful, result available for fetch. | | `FAILED` | Execution failed; the reason is in `status.error.message`. | | `CANCELED` | Canceled explicitly, or by `on_wait_timeout=CANCEL`. | | `CLOSED` | Execution succeeded and the statement is closed; the result is no longer available for fetch. | `PENDING` and `RUNNING` are not terminal — they mean the statement is still executing, and you must poll the result with **Get statement status and result**. Branch on the state with a gateway rather than an error expression. Map the state into a variable with the **Result expression**, then route on it with an exclusive gateway: ``` Result expression: =response.body.status.state Gateway conditions: =result = "FAILED" -> error handling path =result = "CANCELED" -> cancellation path =result = "PENDING" or result = "RUNNING" -> poll loop (Get statement status and result) (default, i.e. SUCCEEDED or CLOSED) -> continue ``` Routing everything except `FAILED` or `CANCELED` to `(default)` treats a still-running statement as complete. The `PENDING` or `RUNNING` branch is therefore required when `wait_timeout` is `0s` or the statement continues after a timeout. :::note This template ships with no default error expression. An error expression is evaluated against the mapped output, not the raw response. When you set **Result variable** or **Result expression**, an expression that uses `response.body.status.state` sees `response.body` as `null` and never fires.A failed statement would then complete as a success. Use the gateway pattern above instead. ::: Use the same gateway pattern for job run outcomes. **Get run** reports a failed run in `state.result_state` after `state.life_cycle_state` reaches a terminal state. Map `state.result_state` to a variable and branch on it with the gateway. ## Partner telemetry Every request carries the `User-Agent` header required for Databricks Technology Partner attribution: ``` User-Agent: Camunda_DatabricksConnector/1.0 ``` You can supply additional headers, but this value is merged in last and cannot be overridden. ## API documentation - [SQL Statement Execution API](https://docs.databricks.com/api/workspace/statementexecution/executestatement) - [SQL Warehouses API](https://docs.databricks.com/api/workspace/warehouses/get) - [Jobs API 2.2](https://docs.databricks.com/api/workspace/jobs/runnow) - [Model Serving — query endpoint](https://docs.databricks.com/api/workspace/servingendpoints/query) - [Vector Search — query index](https://docs.databricks.com/api/workspace/vectorsearchindexes/queryindex) - [OAuth M2M for service principals](https://docs.databricks.com/aws/en/dev-tools/auth/oauth-m2m) --- ## EasyPost connector The **EasyPost connector** is an outbound connector that allows you to create addresses, parcels, and shipments, as well as purchase and verify shipments with [EasyPost Service](https://www.easypost.com/) from your BPMN process. ## Prerequisites To use the **EasyPost connector**, sign up for an EasyPost account, enter your carrier-specific credentials on the [Carrier Account Dashboard](https://www.easypost.com/account/carriers), and get your [API key](https://www.easypost.com/account/api-keys). :::note Use secrets to avoid exposing your EasyPost API key as plain text. Refer to our documentation on [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ::: ## Create an EasyPost connector task ## Authentication In the **Authentication** section, provide an **API key**, which you can find in the [API Key dashboard](https://www.easypost.com/account/api-keys). ## Select operation to execute The **EasyPost connector** currently supports the following operations: ### Create address Allows you to create an address, save it, and get an address ID to use in follow-up operations. Set address information in the **Input** section. Refer to [address object documentation](https://docs.easypost.com/docs/addresses) to learn more about address object and to observe response examples. ### Verify a created address Allows you to verify an address by **Address ID** and return verified address object. ### Create a parcel Allows you to create a parcel, save it, and get a parcel ID to use in follow-up operations. Set required properties in the **Input** section. Refer to the [parcel object documentation](https://docs.easypost.com/docs/parcels) to learn more about the parcel object and to observe response examples. ### Create a shipment Allows you to create a shipment, save it, and get the shipment ID for use in follow-up operations. Set required properties in the **Input** section: ID of destination address, ID of origin address, and ID of parcel. Refer to the [shipment object documentation](https://docs.easypost.com/docs/shipments) to learn more about the shipment object and to observe response examples. In the **Output** section, the pre-filled **Result Expression** returns the ID of the shipment and the ID of [rate](https://docs.easypost.com/docs/shipments/rates). FEEL expression: ``` {priorityRate: response.body.rates[item.service = "Priority"], shipmentId: priorityRate[1].shipment_id, rateId: priorityRate[1].id} ``` Response: ``` { "shipment_id": "shp...", "rateId": "rate...." } ``` ### Buy a shipment Allows you to buy a shipment. Set required properties in the **Input** section: IDs of rate and shipment. Refer to the [shipment object documentation](https://docs.easypost.com/docs/shipments#buy-a-shipment) to learn more about the shipment object and to observe response examples. In the **Output** section the pre-filled **Result Expression** returns the ID of a tracker, tracking code, and status of the shipment. FEEL expression: ``` {trackerId: response.body.tracker.id, trackingCode: response.body.tracking_code, shipmentstatus:response.body.status} ``` Response: ``` { "shipmentstatus": "shp...", "trackerId": "trk....", "trackingCode: :"track...." } ``` ### Retrieve a tracker by ID Allows you to retrieve a tracker by ID and get information about the status of the tracker. Set required properties in the **Input** section: Tracker ID fetched after buying shipment). Refer to the [tracker object documentation](https://docs.easypost.com/docs/trackers#tracker-object) to learn more about the tracker object and to observe response examples. In the **Output** section, the pre-filled **Result Expression** returns the ID of the tracker, tracking code, and status of the shipment. FEEL expression: ``` {trackerStatus: response.body.status} ``` ## Handle connector response The **EasyPost connector** is a protocol connector, meaning it is built on top of the **HTTP REST connector**, therefore handling response is still applicable [as described](/components/connectors/protocol/rest.md#response). --- ## Email connector The Email inbound connector allows your BPMN service to receive emails via IMAP. You can start a new process instance when an email arrives or catch email events in an ongoing process. The message start event inbound connector triggers a new process for each incoming email. ## Prerequisites To use the **Email Inbound connector**, you must have an IMAP server available to connect to. :::note Use secrets to avoid exposing your sensitive data as plain text. See [managing secrets](/components/saas/clusters/manage-secrets.md). ::: ## Authentication You can authenticate to a mail server as follows. ### Simple Authentication This method allows the user to connect to any IMAP server using an email address and password. #### Parameters | Parameter | Description | | :--------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `username` | Enter your full email address (for example, user@example.com) or the username provided by your email service. This is used to authenticate your access to the mail server. | | `password` | Enter the password for your email account. Keep your password secure and do not share it with others. | ## Listener information This inbound connector creates a new process each time a new email is received. | Parameter | Description | | :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Folder` | (Optional) Define the folder the inbound connector will monitor.If not specified, the default folder is set to `INBOX`.For subfolders, use `.` or `/` separated path (for example, `inside/folder` or `inside.folder`) | | `Polling Wait Time` | Set the interval between each polling operation. See [timer events](/components/modeler/bpmn/timer-events/timer-events.md#time-duration) for more information on time duration and correct format. | | `Polling Configuration` | This section contains settings related to the polling behavior of the connector.Poll All Emails: Poll every email found in the specified folder.Move to Another Folder After Processing: Move processed emails to a specific folder.Folder: Specify the target folder to move processed emails to. To specify a new folder or a nested hierarchy, use a `.` or `/` separated path (for example, Archive/test or Projects.2023.January). Non-existent folders in the path are automatically created.Delete After Processing: Permanently delete each email after processing.`Poll Unseen Emails`: Poll only emails not marked as read in the specified folder.`Move to Another Folder After Processing`: Move processed unseen emails to a specific folder.`Folder`: Specify the target folder to move processed unseen emails to. To specify a new folder or a nested hierarchy, use a `.` or `/` separated path (for example, Archive/test or Projects.2023.January). Non-existent folders in the path are automatically created.`Delete After Processing`: Permanently delete unseen emails from the folder after processing.`Mark as Read After Processing`: Mark each unseen email as read after it is processed. | ## Response Structure The task returns a JSON object containing detailed information about the email: - `messageId`: The unique identifier of the email message. - `fromAddress`: The email addresses of the sender. - `headers` : A list of the email headers. - `subject`: The subject line of the email. - `size`: The size of the email (in bytes). - `plainTextBody`: The plain text version of the email content. - `htmlBody`: The HTML version of the email content, if it exists. - `attachments` A list of document reference - `receivedDateTime`: The date and time the email was received. :::note As of the 8.8 release, angle brackets (`<` and `>`) are no longer removed from the `messageId`. ::: #### Example Response The following example JSON response shows the data structure produced when an email triggers the creation of a process instance: ```json { "messageId": "messageId", "fromAddress": "example@camunda.com", "subject": "Urgent Test", "size": 65646, "plainTextBody": "Hey how are you?\r\n", "htmlBody": "Hello", "headers": [ { "key": "header1", "value": "example" }, { "key": "header2", "value": "test" } ], "attachments": [ { "storeId": "in-memory", "documentId": "20f1fd6a-d8ea-403b-813c-e281c1193495", "metadata": { "contentType": "image/webp; name=305a4816-b3df-4724-acd3-010478a54add.webp", "size": 311032, "fileName": "305a4816-b3df-4724-acd3-010478a54add.webp" }, "documentType": "camunda" } ], "receivedDateTime": "2024-08-19T06:54:28Z" } ``` This response includes essential email details such as the `messageId`, sender addresses, subject, size, and the content of the email both in plain text and HTML format. This information can be used by the process for various workflows, such as prioritizing tasks, content analysis, and automated responses. --- ## Email connector(Out-of-the-box-connectors) The **Email outbound connector** allows your BPMN service to send and receive emails via an email server. It supports multiple email protocols, including SMTP for sending emails, and POP3 and IMAP for receiving and managing emails. ## Prerequisites To use the **Email connector**, you must have an SMTP, POP3 or IMAP server available to connect to. :::note Use secrets to avoid exposing your sensitive data as plain text. See [managing secrets](/components/saas/clusters/manage-secrets.md). ::: ## Authentication You can authenticate to a mail server as follows. ### Simple authentication This method allows the user to connect to any SMTP, POP3 or IMAP server using an email address and password. | Parameter | Description | | :--------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `username` | Enter your full email address (for example, user@example.com) or the username provided by your email service. This is used to authenticate your access to the mail server. | | `password` | Enter the password for your email account. Keep your password secure and do not share it with others. | ### No authentication For SMTP servers that do not require authentication, select this option to connect without providing credentials. ## POP3 The Post Office Protocol version 3 (POP3) is an Internet standard protocol used by local email clients to retrieve emails from a remote server over a TCP/IP connection. POP3 allows users to download messages from their email server to their local computer, where they can be read, managed, or archived even without an internet connection. It operates on a simple download-and-delete model, meaning emails are typically removed from the server once they are retrieved. | Field | Description | | :----------------------- | :----------------------------------------------------------------------------------------------------- | | `POP3 host` | The host URL of the POP3 server. | | `POP3 port` | The host port of the POP3 server. | | `Cryptographic protocol` | Defines how the connection to the server is secured, `TLS`, `SSL` or `None`. Default is typically TLS. | ### List Emails Allow users to fetch a list of emails from the `INBOX` folder, with customizable sorting and limitation options. #### Parameters | Parameter | Description | | :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Max Emails to read` | Specify the maximum number of emails to retrieve. This parameter determines the cap on the number of emails the task will return. | | `Sort emails by` | Choose the field by which to sort the emails. Supported sorting fields are:`Sent date`: Sorts emails by the date and time they were sent.`Size`: Sorts emails by the size of the email. | | `Sort order` | Define the sort order:`ASC`: Ascending order, from the oldest or smallest value to the most recent or largest.`DESC`: Descending order, from the most recent or largest value to the oldest or smallest. | #### Sorting and Limiting Behavior Emails are initially sorted based on the specified sorting field and order. The list is then limited to the number of emails as defined by the Max Emails to read parameter. For example, if you sort by Sent date in descending order (DESC) with a limit of one email, the task will return the most recently sent email. #### Response Structure The task returns a list of emails in JSON format. Each email object contains the following information: - `messageId`: A unique identifier for the email message. - `fromAddress`: The email addresses of the sender. - `subject`: The subject line of the email. - `size`: The size of the email (in bytes). :::note As of the 8.8 release, angle brackets (`<` and `>`) are no longer removed from the `messageId`. ::: #### Example Response Example of a returned JSON array: ```json [ { "messageId": "RandomId", "fromAddress": "msa@communication.microsoft.com", "subject": "Example", "size": 99865 }, { "messageId": "RandomId2", "fromAddress": "example@camunda.com", "subject": "Example", "size": 48547 } ] ``` ### Read Email Retrieve the contents of an email, using the unique `messageId` associated with the email message. :::danger Reading an email using POP3 protocol will delete the email ::: #### Parameters | Parameter | Description | | :---------- | :---------------------------------------------------------------------------------------------------------- | | `MessageId` | The identifier of the email message you wish to read. Provide this to locate and return the specific email. | #### Response Structure The task returns a JSON object containing detailed information about the email: - `messageId`: The unique identifier corresponding to the email message. - `fromAddress`: The email addresses of the sender. - `headers` : A list containing the email's headers - `subject`: The subject line of the email. - `size`: The size of the email in bytes. - `plainTextBody`: The plain text version of the email's content. - `htmlBody`: The HTML version of the email's content (if content exists). - `attachments`: A list of all the email's attachments, provided as a document reference. - `receivedDateTime`: The email's reception datetime :::note As of the 8.8 release, angle brackets (`<` and `>`) are no longer removed from the `messageId`. ::: :::note The outbound email connector supports sending documents as attachments. Each attachment uses a [document source](/components/document-handling/send-document-to-external-system.md#document-sources): a **Camunda document** reference, **inline content** built from process data, or an **external document** URL. Use the **Single/Multiple** toggle to provide one document or a FEEL array of documents (for example, `=[document1, document2]`). See additional details and limitations in [document handling](/components/document-handling/getting-started.md). ::: #### Example Response Below is an example of the JSON response returned when a specific email is read: ```json { "messageId": "MessageId", "fromAddress": "example@camunda.com", "subject": "Example Subject", "size": 99865, "plainTextBody": "Any text content", "htmlBody": "Any Html Content", "headers": [ { "key": "header1", "value": "example" }, { "key": "header2", "value": "test" } ], "attachments": [ { "storeId": "in-memory", "documentId": "20f1fd6a-d8ea-403b-813c-e281c1193495", "metadata": { "contentType": "image/webp; name=305a4816-b3df-4724-acd3-010478a54add.webp", "size": 311032, "fileName": "305a4816-b3df-4724-acd3-010478a54add.webp" }, "documentType": "camunda" } ], "receivedDateTime": "2024-08-19T06:54:28Z" } ``` ### Delete Email Delete (remove) an email from the server, using the specific `messageId` assigned to the email message. #### Parameters | Parameter | Description | | :---------- | :--------------------------------------------- | | `MessageId` | The identifier of the email message to delete. | #### Response Structure After the deletion task is performed, a JSON object is returned to confirm the action: - `deleted`: A boolean value that indicates whether the deletion was successful (true) or not (false). - `messageId`: The identifier of the email message that was attempted to be deleted. :::note As of the 8.8 release, angle brackets (`<` and `>`) are no longer removed from the `messageId`. ::: #### Example Response The following JSON response shows the result of a successful deletion request: ```json { "deleted": true, "messageId": "MessageId" } ``` ### Search Emails Enable users to perform advanced searches within an email inbox, by constructing a criteria-based query using a JSON object. Supports complex queries that can combine multiple conditions using logical operators. #### Parameters A search query is represented as a JSON object. The following is an example of a JSON object that represents a search criteria using an AND and OR operator to combine multiple conditions: ```json { "operator": "AND", "criteria": [ { "field": "FROM", "value": "example@camunda.com" }, { "operator": "OR", "criteria": [ { "field": "SUBJECT", "value": "urgent" }, { "field": "SUBJECT", "value": "important" } ] } ] } ``` This example query returns emails from "example@camunda.com" with a subject containing either "urgent" or "important". A simpler query without logical operators might look like the following example: ```json { "field": "FROM", "value": "example@camunda.com" } ``` Search supports the following logical operators: - **AND**: Returns emails that match **all** the specified criteria. - **OR**: Returns emails that match **any of** the specified criteria. The following email fields can be used to set search criteria: - **BODY**: The content of the email body. - **SUBJECT**: The subject line of the email. - **FROM**: The email address of the sender. :::note When using an operator such as AND or OR, you must also define a criteria array. This array contains the individual conditions that the search will evaluate against the emails. Each condition within the criteria array is itself a JSON object with a field and a value. - If an operator is set, the criteria array must also be defined. - Each criterion within the criteria array is applied to the specified field based on the value associated with it. ::: #### Response Structure - `subject`: The email subject line. - `messageId`: The identifier of the email message that was attempted to be deleted. :::note As of the 8.8 release, angle brackets (`<` and `>`) are no longer removed from the `messageId`. ::: #### Example Response The following is an example of a returned response: ```json [ { "messageId": "MessageId", "subject": "Important" }, { "messageId": "MessageId2", "subject": "Urgent" } ] ``` ## SMTP Simple Mail Transfer Protocol (SMTP) is the standard communication protocol for sending emails across the Internet. It facilitates mail transfer from a client's email application to the outgoing mail server and between servers for relaying email messages to their final destination. SMTP operates on a push model, where the sending server pushes the message to the receiving server for delivery to the appropriate mailbox. | Field | Description | | :----------------------- | :----------------------------------------------------------------------------------------------------- | | `SMTP host` | The host URL of the SMTP server. | | `SMTP port` | The host port of the SMTP server. | | `Cryptographic protocol` | Defines how the connection to the server is secured, `TLS`, `SSL` or `None`. Default is typically TLS. | ### Send Email Allow users to send an email from the connected email account. #### Parameters | Parameter | Description | | :------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `From` | Specify the sender's email address(es). This can be a single email address (for example, 'example@camunda.com'), a comma-separated list of addresses, or a Friendly Enough Expression Language (FEEL) expression returning a list of email addresses (for example, =["example@camunda.com"]). | | `To` | Defines the email recipient(s). Similar to the `From` parameter, this can be a single email address, a comma-separated list, or a FEEL expression (for example, =["example@camunda.com"]). | | `Cc` | (Optional) Specify the email address(es) to include in the **Carbon Copy (CC)** field. The format is the same as the **From** and **To** fields, and can include a single address, a list, or a FEEL expression. | | `Bcc` | (Optional) Specify the email address(es) to include in the **Blind Carbon Copy (BCC)** field. It follows the same format as the **CC** field and ensures that BCC recipients are not visible to other recipients. | | `Headers` | Feel expression containing all the desired headers to be added to the email's headers. cf. `{ "customHeaders" : "new header value" }` | | `Subject` | The email subject line. | | `Content Type` | The content type of the email. | | `Email Text Content` | The text content of the email. This must only be provided if the `Content Type` is `PLAIN` or `HTML & PlainText`. | | `Html Text Content` | The HTML content of the email. This must only be provided if the `Content Type` is `HTML` or `HTML & PlainText`. | | `Attachment` | The document(s) to attach. Select a [document source](/components/document-handling/send-document-to-external-system.md#document-sources) (Camunda document, inline content, or external document); use the **Single/Multiple** toggle for one document or a FEEL array of documents. | :::info To learn more about Friendly Enough Expression Language (FEEL) expression, see [what is FEEL?](/components/modeler/feel/what-is-feel.md). ::: #### Response Structure Upon successfully sending the email, the following JSON response is returned: - `subject`: Echoes back the subject of the sent email. - `sent`: A boolean value indicating the success status of the email being sent (true for success, false for failure). - `messageId`: A unique identifier for the email message. :::note As of the 8.8 release, angle brackets (`<` and `>`) are no longer removed from the `messageId`. ::: #### Example Response The following is an example of a successful send email operation: ```json { "subject": "Example Subject", "sent": true, "messageId": "" } ``` In this response: - `sent: true` confirms that the email with the specified subject "Example Subject" was successfully sent. - `sent: false` indicates the email failed to send. ## IMAP The Internet Message Access Protocol (IMAP) is a protocol used by email clients to access messages stored on a mail server, allowing users to view and manage their emails from multiple devices. Unlike POP3, IMAP supports both online and offline modes, synchronizes email across devices, and allows manipulation of mailboxes (create, delete, and rename) as well as messages (read, delete, or flag) directly on the server. | Field | Description | | :----------------------- | :----------------------------------------------------------------------------------------------------- | | `IMAP host` | The host URL of the IMAP server. | | `IMAP port` | The host port of the IMAP server. | | `Cryptographic protocol` | Defines how the connection to the server is secured, `TLS`, `SSL` or `None`. Default is typically TLS. | ### List Emails Allow users to fetch a list of emails from a specified folder, with customizable sorting and limitation options. #### Parameters | Parameter | Description | | :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `Max Emails to read` | Specify the maximum number of emails to retrieve. This parameter determines the cap on the number of emails the task will return. | | `Sort emails by` | Choose the field by which to sort the emails. Supported sorting fields are:`Sent date`: Sorts emails by the date and time they were sent.`Size`: Sorts emails by the size of the email. | | `Sort order` | Define the sort order:`ASC`: Ascending order, from the oldest or smallest value to the most recent or largest.`DESC`: Descending order, from the most recent or largest value to the oldest or smallest. | | `Folder` | (Optional) the folder to list emails from, default is `INBOX`. For subfolders, use `.` or `/` separated path (ex: `inside/folder` or `inside.folder`) | #### Sorting and Limiting Behavior - Emails are initially sorted based on the specified sorting field and order. - The list is then limited to the number of emails as defined by the Max Emails to read parameter. For example, sort by Sent date in descending order (DESC) with a limit of one email, to return the most recently sent email. #### Response Structure The task returns a list of emails in JSON format. Each email object contains the following information: - `messageId`: A unique identifier for the email message. - `fromAddress`: the email addresses of the sender. - `subject`: The subject line of the email. - `size`: The size of the email in bytes. :::note As of the 8.8 release, angle brackets (`<` and `>`) are no longer removed from the `messageId`. ::: #### Example Response Example of a returned JSON array: ```json [ { "messageId": "RandomId", "fromAddress": "msa@communication.microsoft.com", "subject": "Example", "size": 99865 }, { "messageId": "RandomId2", "fromAddress": "example@camunda.com", "subject": "Example", "size": 48547 } ] ``` ### Read Email Retrieve an email's details based on the specified `messageId`. #### Parameters | Parameter | Description | | :---------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `MessageId` | The unique identifier of the email that must be read. | | `Folder` | (Optional) Specifies the folder from which the email should be retrieved. If not provided, the default folder is `INBOX`. For subfolders, use `.` or `/` separated path (ex: `inside/folder` or `inside.folder`) | #### Response Structure The task returns a JSON object containing detailed information about the email: - `messageId`: The unique identifier of the email message. - `fromAddress`: The email addresses of the sender. - `headers` : A list of the email headers. - `subject`: The subject line of the email. - `size`: The size of the email (in bytes). - `plainTextBody`: The plain text version of the email content. - `htmlBody`: The HTML version of the email content, if it exists. - `attachments`: A list of all the email's attachments, provided as a document reference. - `receivedDateTime`: The date and time the email was received. :::note As of the 8.8 release, angle brackets (`<` and `>`) are no longer removed from the `messageId`. ::: #### Example Response The following JSON structure shows an expected response after a successful email retrieval: ```json { "messageId": "MessageId", "fromAddress": "example@camunda.com", "subject": "Example Subject", "size": 99865, "plainTextBody": "Any text content", "htmlBody": "Any Html Content", "headers": [ { "key": "header1", "value": "example" }, { "key": "header2", "value": "test" } ], "attachments": [ { "storeId": "in-memory", "documentId": "20f1fd6a-d8ea-403b-813c-e281c1193495", "metadata": { "contentType": "image/webp; name=305a4816-b3df-4724-acd3-010478a54add.webp", "size": 311032, "fileName": "305a4816-b3df-4724-acd3-010478a54add.webp" }, "documentType": "camunda" } ], "receivedDateTime": "2024-08-19T06:54:28Z" } ``` ### Delete Email Delete an email from a specified folder, using the email's unique `messageId`. #### Parameters | Parameter | Description | | :---------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `MessageId` | The identifier of the email message to delete. | | `Folder` | (Optional) Specifies the folder from which the email should be deleted. If this parameter is not supplied, the default folder is assumed to be `INBOX`. For subfolders, use `.` or `/` separated path (ex: `inside/folder` or `inside.folder`) | #### Response Structure The task provides a JSON object in the response, indicating the outcome of the deletion request: - `deleted`: A boolean value that signifies whether the email was successfully deleted (true) or not (false). - `messageId`: Reiterates the `messageId` of the email that was targeted for deletion. :::note As of the 8.8 release, angle brackets (`<` and `>`) are no longer removed from the `messageId`. ::: #### Example Response The following is an example of the JSON response confirming successful email deletion: ```json { "deleted": true, "messageId": "MessageId" } ``` ### Search Emails Enable users to perform advanced searches within an email inbox by constructing a criteria-based query using a JSON object. Search supports complex queries that can combine multiple conditions using logical operators. #### Parameters A search query is represented as a JSON object. Below is an example of a JSON object that represents a search criteria using an AND and OR operator to combine multiple conditions: - `Folder`: (Optional) Specifies the folder from which the email should be deleted. If this parameter is not supplied, the default folder is assumed to be `INBOX`. For subfolders, use `.` or `/` separated path (ex: `inside/folder` or `inside.folder`) - `Criteria`: _See below_ ```json { "operator": "AND", "criteria": [ { "field": "FROM", "value": "example@camunda.com" }, { "operator": "OR", "criteria": [ { "field": "SUBJECT", "value": "urgent" }, { "field": "SUBJECT", "value": "important" } ] } ] } ``` This example query returns emails from "example@camunda.com" with a subject containing either "urgent" or "important". A simpler query without logical operators might look like the following example: ```json { "field": "FROM", "value": "example@camunda.com" } ``` Search supports the following logical operators: - **AND**: Returns emails that match all the specified criteria. - **OR**: Returns emails that match any of the specified criteria. The following email fields can be used to set search criteria: - **BODY**: The content of the email body. - **SUBJECT**: The subject line of the email. - **FROM**: The email address of the sender. :::note When using an operator such as AND or OR, you must also define a criteria array. This array contains the individual conditions that the search will evaluate against the emails. Each condition within the criteria array is itself a JSON object with a field and a value. - If an operator is set, the criteria array must also be defined. - Each criterion within the criteria array is applied to the specified field based on the value associated with it. ::: #### Response Structure - `subject`: The email subject line. - `messageId`: The identifier of the email message that was attempted to be deleted. :::note As of the 8.8 release, angle brackets (`<` and `>`) are no longer removed from the `messageId`. ::: #### Example Response The following is an example returned response: ```json [ { "messageId": "MessageId", "subject": "Important" }, { "messageId": "MessageId2", "subject": "Urgent" } ] ``` ### Move Email Enable users to transfer an email from one folder to another, streamlining inbox organization. #### Parameters | Parameter | Description | | :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `MessageId` | The identifier of the email that needs to be moved. | | `Source folder` | (Optional) The folder from which the email will be moved. If not specified, the default is INBOX. For subfolders, use `.` or `/` separated path (ex: `inside/folder` or `inside.folder`) | | `Target folder` | The destination folder where the email is placed. To specify a new folder or a nested hierarchy, use `.` or `/` separated path (for example, 'Archive/test' or 'Projects.2023.January'). The system automatically creates any non-existent folders in the path. | #### Response Structure Upon successful completion of the move operation, the response contains a JSON object with the following details: - `messageId`: The `messageId` of the email that was moved. - `from`: The source folder from which the email was moved. - `to`: The target folder to which the email has been moved. :::note As of the 8.8 release, angle brackets (`<` and `>`) are no longer removed from the `messageId`. ::: #### Example Response The example below shows the expected JSON response after an email has been successfully moved: ```json { "messageId": "", "from": "INBOX", "to": "TEST" } ``` --- ## Vector Database connector The **Vector Database connector** allows embedding, storing, and retrieving Large Language Model (LLM) embeddings. This enables building AI-based solutions such as context document search, long-term LLM memory, and agentic AI interaction. For an example, see how to [add long-term memory to your AI agents](/components/agentic-orchestration/rag-ai-agents.md). :::note The **Vector Database connector** uses the [LangChain4j](https://docs.langchain4j.dev/) library. Data models and possible implementations are limited to the latest stable released LangChain4j library. ::: ## Prerequisites Before using the **Vector Database connector**, ensure you understand the [concept of LLM embeddings](https://huggingface.co/spaces/hesamation/primer-llm-embedding). To start using the **Vector Database connector**, ensure you have access to a supported LLM embeddings API to convert document content into vectorized embedding form. You will also need to have write access to a supported database. ## Create a connector task ## Operations The **embed document** operation performs the following steps: 1. [Consume a document](#embedding-document-configuration). 2. Parse the document depending on a file format (optionally [split into text chunks](#splitting)). 3. Convert chunks into a vector form with LLM help. 4. Store produced vectors in a vector database. To perform this operation, enter the following: - **Operation** dropdown: **Embed document**. - **Embedding model**: Refer to the [relevant section](#embedding-models). - **Vector store**: Refer to the [relevant section](#vector-stores). - **Document**: Refer to the [relevant section](#embedding-document-configuration). As a result of this operation, you will get an array of created embedding chunk IDs, for example `["d599ec62-fe51-4a91-bbf0-26e1241f9079", "a1fad021-5148-42b4-aa02-7de9d590e69c"]`. ### Updating embedded documents Each time you embed a document, the connector generates a new set of chunks and stores them in the vector database. If the document was previously embedded, this creates duplicate chunks. To prevent duplicates: 1. Delete the existing chunks before re-embedding the document. 2. Use the chunk IDs returned by the previous embedding operation. 3. If the embedded document is from Camunda, use the `filename` metadata field to find the chunk IDs. 4. Follow your vector store’s documentation for deleting chunks. The **retrieve document** operation performs the following steps: 1. Consumes a query. 2. Convert the query into a vector form with LLM help. 3. Perform a vector similarity search on previously-stored LLM embeddings. 4. Store results in Camunda document storage. To perform this operation, enter the following: - **Operation** dropdown: **Retrieve document**. - **Search query**: Enter your search query. - **Max results**: Enter maximum amount of returned results. - **Min score**: Enter the lowest score threshold similarity value; the value should be between 0 and 1, for example, 0.81. - **Embedding model**: Refer to the [relevant section](#embedding-models). - **Vector store**: Refer to the [relevant section](#vector-stores). As a result of this operation, you will get an array of relevant chunks, where each includes a [chunk ID](#splitting), Camunda document reference metadata, similarity score, and the actual text content. ```json { "chunks": [ { "chunkId": "e30d570b-2a3a-4f4a-9a0c-78f0f1acd383", "documentReference": { "storeId": "local", "documentId": "2b36ec67-a78f-4f99-9371-8c6e5b332838", "contentHash": "1c232bc1e553c10d00c3327dcca9012b6b4b0758a1c2afaad8b77c80fa1bd36e", "metadata": { "size": 116, "fileName": null, "processDefinitionId": null, "processInstanceKey": null, "customProperties": {}, "expiresAt": null, "contentType": "text/plain" }, "camunda.document.type": "camunda" }, "score": 0.6721556, "content": "Camunda is a platform for orchestrating and automating business processes. It helps organizations design, execute, and manage workflows, enabling them to optimize processes and improve efficiency." } ] } ``` ## Embedding models The **Vector Database connector** supports [Amazon Titan V1 and V2 models](https://docs.aws.amazon.com/bedrock/latest/userguide/titan-embedding-models.html). You can also specify any custom model that supports text embedding and is available in your Amazon Bedrock account. To use Amazon Bedrock as an embedding model, provide: - **Access key** – Access key for a user with permissions for the Amazon Bedrock `InvokeModel` action. - **Secret key** – Secret key for the user associated with the provided access key. - **Region** – AWS region where the model is hosted (for example, `us-east-1`). See [AWS model region support](https://docs.aws.amazon.com/bedrock/latest/userguide/models-regions.html) for details. - **Model name** – One of: - **Amazon Titan V1** – `amazon.titan-embed-text-v1` - **Amazon Titan V2** – `amazon.titan-embed-text-v2:0` - **Custom model** – Name of your custom Amazon Bedrock embedding model. When using Amazon Titan V2, you can also specify: - **Embedding dimensions** – Number of dimensions for the embedding vector. - **Normalize** – Whether to normalize the embedding vector. See [AWS blog](https://aws.amazon.com/blogs/aws/amazon-titan-text-v2-now-available-in-amazon-bedrock-optimized-for-improving-rag/) for more details. For all models, the following parameter is optional: - **Max retries** – Maximum number of retries for the embedding request in case of failure. To use OpenAI as an embedding model, provide: - **API key** – Your OpenAI account API key for authorization. - **Model name** – The OpenAI model to use for embeddings. See the [OpenAI documentation](https://platform.openai.com/docs/guides/embeddings) for available models. Optional parameters include: - **Organization ID** – For projects accessed through a legacy user API key, specify the organization ID for API requests with this connector. - **Project ID** – For projects accessed through a legacy user API key, specify the project ID for API requests with this connector. - **Embedding dimensions** – Number of dimensions for the embedding vector. If not specified, the default value for the selected model is used. - **Custom headers** – Additional headers to include in the request. - **Custom base URL** – Base URL for API requests when using a custom OpenAI endpoint. - **Max retries** – Maximum number of retries for the embedding request in case of failure. To use Azure OpenAI as an embedding model, provide: - **Endpoint** – The Azure OpenAI endpoint URL, for example `https://.openai.azure.com/`. - **Authentication** – Select the authentication type to use with Azure OpenAI. Optional parameters include: - **Embedding dimensions** – Number of dimensions for the embedding vector. If not specified, the default value for the selected model is used. - **Custom headers** – Additional headers to include in the request. - **Max retries** – Maximum number of retries for the embedding request in case of failure. Two authentication methods are supported: - **API key** – Authenticate using an Azure OpenAI API key from the [Azure AI Foundry portal](https://ai.azure.com/). - **Client credentials** – Authenticate using a client ID and secret. This requires registering an application in [Microsoft Entra ID](https://go.microsoft.com/fwlink/?linkid=2083908). Provide: - **Client ID** – The Microsoft Entra application ID. - **Client secret** – The application’s client secret. - **Tenant ID** – The Microsoft Entra tenant ID. - **Authority host** – _(Optional)_ The authority host URL. Defaults to `https://login.microsoftonline.com/`. Can also be an OAuth 2.0 token endpoint. To use Google Vertex AI as an embedding model, provide: - **Project ID** – The Google Cloud project ID. - **Region** – The [region](https://cloud.google.com/vertex-ai/docs/general/locations#feature-availability) where AI inference should take place. - **Authentication** – Select the authentication type for connecting to Google Cloud. - **Model name** – The Vertex AI model to use for embeddings. Refer to the [Vertex AI documentation](https://cloud.google.com/vertex-ai/docs/generative-ai/embeddings) for available models. - **Embedding dimensions** – Number of dimensions for the embedding vector. Consult the documentation for the selected model for valid ranges. Optional parameters include: - **Publisher** – The publisher of the Vertex AI model. Defaults to `google` if not specified. - **Max retries** – Maximum number of retries for the embedding request in case of failure. Two authentication methods are supported: - **Service Account Credentials** – Authenticate using a [service account](https://cloud.google.com/iam/docs/service-account-overview) key in JSON format. - **Application Default Credentials (ADC)** – Authenticate using the default credentials available in your environment. This method is only supported in Self-Managed or hybrid environments. To set up ADC in a local development environment, follow the instructions [here](https://cloud.google.com/docs/authentication/set-up-adc-local-dev-environment). ## Vector stores Enter the following parameters: - **Base URL** – The Elasticsearch base URL, including protocol, for example `https://host:port`. - **Username** – For the Elasticsearch user that has read/write access. - **Password** – For the Elasticsearch user that has read/write access. - **Index name** – Name of the index where you wish to store embeddings. - When embedding: If the index is not present, the connector will create a new one. - When retrieving: If the index is absent, the connector will raise an error. :::important The Elasticsearch version must be 8+. ::: Enter the following parameters: - **Base URL** – The OpenSearch base URL, including protocol, for example `https://host:port`. - **Username** – For the OpenSearch user that has read/write access. - **Password** – For the OpenSearch user that has read/write access. - **Index name** – Name of the index where you wish to store embeddings. - When embedding: If the index is not present, the connector will create a new one. - When retrieving: If the index is absent, the connector will raise an error. Enter the following parameters: - **Access key** and **Secret key** – Enter AWS IAM credentials for the user that has read/write access. - **Server URL** – An Amazon OpenSearch URL _without_ protocol, for example `my-opensearch.aws.com:port`. - **Region** – Region of the Amazon OpenSearch instance. - **Index name** – Name of the index where you wish to store embeddings. - When embedding: If the index is not present, the connector will create a new one. - When retrieving: If the index is absent, the connector will raise an error. Enter the following parameters: - **Endpoint** – The Azure AI Search endpoint URL, for example `https://.search.windows.net/`. - **Authentication** – Select the authentication type for connecting to Azure AI Search. - **Index name** – Name of the index where embeddings will be stored. - When embedding: If the index is not present, the connector will create it. - When retrieving: If the index is absent, the connector will raise an error. Two authentication methods are supported: - **API key** – Authenticate using an Azure AI Search key. - **Client credentials** – Authenticate using a client ID and secret. This requires registering an application in [Microsoft Entra ID](https://go.microsoft.com/fwlink/?linkid=2083908) and assigning the [required roles](https://learn.microsoft.com/en-us/azure/search/search-security-rbac). Role-based access control must be explicitly enabled for the Azure AI Search resource. Provide the following fields: - **Client ID** – The Microsoft Entra application ID. - **Client secret** – The application’s client secret. - **Tenant ID** – The Microsoft Entra tenant ID. - **Authority host** – _(Optional)_ The authority host URL. Defaults to `https://login.microsoftonline.com/`. This can also be an OAuth 2.0 token endpoint. Enter the following parameters: - **Endpoint** – The Azure Cosmos DB NoSQL endpoint URL, for example `https://.documents.azure.com/`. - **Authentication** – Select the authentication type for connecting to Azure Cosmos DB NoSQL. - **Database name** – The name of the Azure Cosmos DB NoSQL database. - **Container name** – The name of the Azure Cosmos DB NoSQL container. _Note:_ The container must already exist and have an `/id` partition key. - **Consistency level** – The consistency level for the container. Defaults to `Eventual`. - **Distance function** – The distance function to use for vector similarity search. Defaults to `Cosine`. - **Vector index type** – The vector index type to use. Defaults to `Flat`. :::info For more information about Azure Cosmos DB NoSQL vector search, refer to the [official documentation](https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/vector-search). Pay special attention to the vector dimensions limitations as stated in the documentation. ::: Two authentication methods are supported: - **API key** – Authenticate using an Azure Cosmos DB key. - **Client credentials** – Authenticate using a client ID and secret. This requires registering an application in [Microsoft Entra ID](https://go.microsoft.com/fwlink/?linkid=2083908). Provide the following fields: - **Client ID** – The Microsoft Entra application ID. - **Client secret** – The application’s client secret. - **Tenant ID** – The Microsoft Entra tenant ID. - **Authority host** – _(Optional)_ The authority host URL. Defaults to `https://login.microsoftonline.com/`. Can also be an OAuth 2.0 token endpoint. ## Embedding document configuration ### Document source The **Document source** can be either **Plain text** or a **Camunda document**. **Plain text** can be useful when you deal with small size data that can fit into a text field or a process variable. Input will be handled as a regular UTF-8 text. :::note A FEEL [string conversion function](/components/modeler/feel/builtin-functions/feel-built-in-functions-conversion.md#stringfrom) might be useful if you have JSON input. ::: The **Camunda document** might be useful when you deal with larger document pipelines that come from [webhook or user tasks](/components/document-handling/getting-started.md). Input documents will be parsed with [Apache Tika](https://tika.apache.org/), so files can be of any Apache Tika-supported formats. ### Splitting **Splitting** is an action of breaking large documents into smaller pieces. It can be either recursive or no splitting at all. Seek guidance from your local data scientist to determine if you require splitting. Learn more about splitting in the [LangChain4j documentation](https://docs.langchain4j.dev/tutorials/rag#document-splitter). ## HTTP proxy configuration In Self-Managed environments, the Vector Database connector supports routing HTTP requests through an HTTP proxy. This applies to both embedding model API calls and vector store connections. The Vector Database connector supports [plain proxy variables](/self-managed/components/connectors/http-proxy-configuration.md#plain-proxy-variables) in addition to the standard connector proxy variables. Refer to the [HTTP proxy configuration](/self-managed/components/connectors/http-proxy-configuration.md) page for the full list of environment variables and configuration options. The following providers do not support connector proxy variables, but respect standard [JVM proxy properties](/self-managed/components/connectors/http-proxy-configuration.md#jvm-properties): - Google VertexAI (embedding model). - Azure AI Search (vector store). The following providers do not support proxy configuration: - Azure Cosmos DB NoSQL (vector store). To disable proxy support entirely (for example, if only an HTTPS-based proxy is available), set the following environment variable: ```bash CAMUNDA_CONNECTOR_VECTORDB_HTTP_PROXYSUPPORT_ENABLED=false ``` --- ## GitHub connector The **GitHub connector** is an outbound connector that allows you to connect your BPMN service with [GitHub](https://github.com/) to manage [GitHub](https://github.com/) issues and releases. ## Prerequisites To use the **GitHub connector**, you must have a GitHub instance and an [access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) of a user or a service account on whose behalf a BPMN process will be executed. :::note Use secrets to avoid exposing your GitHub access token credentials as plain text. Refer to our documentation on [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ::: ## Create a GitHub connector task ## Authentication In the **Authentication** section, select an **Authentication type** from the dropdown: - **Personal Access Token**: Use a personal access token for authentication. - **GitHub App**: Use a GitHub App for authentication. ### PAT If you select **PAT**, provide the following: - **GitHub Personal token**: Your personal access token. ### GitHub App If you select **GitHub App**, provide the following: - **Private key**: The private key of your GitHub App. It is recommended to use [secrets](/components/saas/clusters/manage-secrets.md) to store this value securely. - **App ID**: The ID of your GitHub App. - **Installation ID**: The installation ID of your GitHub App. Refer to the [GitHub documentation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app-installation#generating-an-installation-access-token) for more information on how to obtain the installation ID. ## Select operation to execute The **GitHub connector** currently supports the following operations. ### Issues #### Create an issue - **GitHub API:** [Create an issue](https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#create-an-issue). - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repo:** The name of the repository. The name is not case-sensitive. - **Title:** The title of the issue. - **Body:** The contents of the issue. - **Assignees:** Logins for users to assign to this issue. Only users with push access can set assignees for new issues. Assignees are silently dropped otherwise. - **Labels:** Labels to associate with this issue. Only users with push access can set labels for new issues. Labels are silently dropped otherwise. - **Milestone:** The number of the milestone to associate this issue with or use null to remove the current milestone. Only users with push access can set the milestone for issues. Without push access to the repository, milestone changes are silently dropped. #### Get an issue - **GitHub API:** [Get an issue](https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#get-an-issue). - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repo:** The name of the repository. The name is not case-sensitive. - **Issue number:** The number that identifies the issue. #### Update an issue - **GitHub API:** [Update an issue](https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#update-an-issue). - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repo:** The name of the repository. The name is not case-sensitive. - **Issue number:** The number that identifies the issue. - **Title:** The title of the issue. - **Body:** The contents of the issue. - **Assignees:** Logins for users to assign to this issue. Only users with push access can set assignees for new issues. Assignees are silently dropped otherwise. - **Labels:** Labels to associate with this issue. Only users with push access can set labels for new issues. Labels are silently dropped otherwise. - **Milestone:** The number of the milestone to associate this issue with or use null to remove the current milestone. Only users with push access can set the milestone for issues. Without push access to the repository, milestone changes are silently dropped. - **Issue state:** The open or closed state of the issue. Can be open or closed. - **State reason:** The reason for the state change. Ignored unless state is changed. Can be one of: completed, not_planned, reopened, null. #### Create an issue comment - **GitHub API:** [Create an issue comment](https://docs.github.com/en/rest/issues/comments?apiVersion=2022-11-28#create-an-issue-comment). - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repo:** The name of the repository. The name is not case-sensitive. - **Issue number:** The number that identifies the issue. - **Body:** The contents of the comment. #### Search issue - **GitHub API:** [Search issue](https://docs.github.com/en/rest/search?apiVersion=2022-11-28#search-issues-and-pull-requests). - **Query:** The query contains one or more search keywords and qualifiers. Qualifiers allow you to limit your search to specific areas of GitHub. The GitHub REST API supports the same qualifiers as the web interface for GitHub. To learn more about the format of the query, refer to [constructing a search query](https://docs.github.com/en/rest/search?apiVersion=2022-11-28#constructing-a-search-query). Refer to [searching issues and pull requests](https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests) for a detailed list of qualifiers. #### List commits - **GitHub API:** [List commits](https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#list-commits). - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repo:** The name of the repository. The name is not case-sensitive. ### Branches #### List branches - **GitHub API:** [List branches](https://docs.github.com/en/rest/branches/branches?apiVersion=2022-11-28#list-branches). - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repo:** The name of the repository. The name is not case-sensitive. #### Get a branch - **GitHub API:** [Get a branch](https://docs.github.com/en/rest/branches/branches?apiVersion=2022-11-28#get-a-branch). - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repo:** The name of the repository. The name is not case-sensitive. - **Branch:** The name of the branch. Cannot contain wildcard characters. To use wildcard characters in branch names, use the GraphQL API. #### Merge a branch - **GitHub API:** [Merge a branch](https://docs.github.com/en/rest/branches/branches?apiVersion=2022-11-28#merge-a-branch). - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repo:** The name of the repository. The name is not case-sensitive. - **Base:** The name of the base branch that the head will be merged into. - **Head:** The head to merge. This can be a branch name or a commit SHA1. ### Code scanning #### List code scanning alerts for an organization - **GitHub API:** [List code scanning alerts for an organization](https://docs.github.com/en/rest/code-scanning?apiVersion=2022-11-28#list-code-scanning-alerts-for-an-organization). - **Organization name:** The organization name. The name is not case-sensitive. #### List code scanning alerts for a repository - **GitHub API:** [List code scanning alerts for a repository](https://docs.github.com/en/rest/code-scanning?apiVersion=2022-11-28#list-code-scanning-alerts-for-a-repository). - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repo:** The name of the repository. The name is not case-sensitive. ### Organization #### Create an organization invitation - **GitHub API:** [Create an organization invitation](https://docs.github.com/en/rest/orgs/members?apiVersion=2022-11-28#create-an-organization-invitation). - **Organization name:** The organization name. The name is not case-sensitive. ### Release #### Create a release - **GitHub API:** [Create a release](https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#create-a-release). - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repo:** The name of the repository. The name is not case-sensitive. - **Body:** Text describing the contents of the tag. - **Tag name:** The name of the tag. - **Release name:** The name of the release. - **Make latest:** Specifies whether this release should be set as the latest release for the repository. Drafts and pre-releases cannot be set as latest. Defaults to true for newly published releases. Legacy specifies that the latest release should be determined based on the release creation date and higher semantic version. Default: true. Can be one of: true, false, legacy. #### Update a release - **GitHub API:** [Update a release](https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#update-a-release). - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repository:** The name of the repository. The name is not case-sensitive. - **Release ID:** The unique identifier of the release. - **Body:** Text describing the contents of the tag. - **Tag name:** The name of the tag. - **Release name:** The name of the release - **Make latest:** Specifies whether this release should be set as the latest release for the repository. Drafts and pre-releases cannot be set as latest. Defaults to true for newly published releases. Legacy specifies that the latest release should be determined based on the release creation date and higher semantic version. Default: true. Can be one of: true, false, legacy. #### Delete a release - **GitHub API:** [Delete a release](https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#delete-a-release). - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repository:** The name of the repository. The name is not case-sensitive. - **Release ID:** The unique identifier of the release. #### List releases - **GitHub API:** [List releases](https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#list-releases). - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repo:** The name of the repository. The name is not case-sensitive. ### Repository #### List organization repositories - **GitHub API:** [List organization repositories](https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-organization-repositories). - **Organization name:** The organization name. The name is not case-sensitive. #### Create an organization repository - **GitHub API:** [Create an organization repository](https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#create-an-organization-repository). - **Organization name:** The organization name. The name is not case-sensitive. - **Repository name:** The organization name. The name is not case-sensitive. - **Description:** A short description of the repository. - **Home page:** A URL with more information about the repository. - **Visibility:** The visibility of the repository. Can be one of: public, private. #### Get a repository - **GitHub API:** [Get a repository](https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#get-a-repository). - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repo:** The name of the repository. The name is not case-sensitive. #### Update a repository - **GitHub API:** [Update a repository](https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#update-a-repository). - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repo:** The name of the repository. The name is not case-sensitive. - **Repository name:** The organization name. The name is not case-sensitive. - **Description:** A short description of the repository. - **Home page:** A URL with more information about the repository. - **Visibility:** The visibility of the repository. Can be one of: public, private. #### Delete a repository - **GitHub API:** [Delete a repository](https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#delete-a-repository). - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repo:** The name of the repository. The name is not case-sensitive. #### List repository contributors - **GitHub API:** [List repository contributors](https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repository-contributors). - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repo:** The name of the repository. The name is not case-sensitive. #### Get repository content - **GitHub API:** [Get repository content](https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28#get-repository-content). - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repo:** The name of the repository. The name is not case-sensitive. - **Path:** The path of the content within the repository. - **Ref:** The name of the commit/branch/tag. Defaults to the repository’s default branch. #### Create or update file contents - **GitHub API:** [Create or update file contents](https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28#create-or-update-file-contents). - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repo:** The name of the repository. The name is not case-sensitive. - **Path:** The path of the content within the repository. - **Branch:** The name of the target branch for the new commit. Defaults to the repository’s default branch. - **Commit message**: The commit message for the file change. - **Content**: A string or [document reference](components/document-handling/getting-started.md). If your content is already base64 encoded, please check the **Content is already base64 encoded** checkbox. - **SHA**: The blob SHA of the file being replaced. If you are creating a new file, this parameter is not required. Use [Get repository content](#get-repository-content) to retrieve the SHA of an existing file. - **Use custom author/committer**: If checked, you can specify custom author and committer information. Otherwise, the author and committer will be the owner of the PAT. ### Actions #### Create workflow dispatch event - **GitHub API:** [Create workflow dispatch event](https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#create-a-workflow-dispatch-event) - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repo:** The name of the repository. The name is not case-sensitive - **Workflow ID:** The ID of the workflow. You can also pass the workflow file name as a string. - **Git reference:** The branch or tag name for the workflow. - **Workflow inputs:** An object containing up to 10 key-value pairs. Inputs are configured in the workflow file. ### References #### Create a reference - **GitHub API:** [Create a reference](https://docs.github.com/en/rest/git/refs?apiVersion=2022-11-28#create-a-reference) - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repo:** The name of the repository. The name is not case-sensitive - **Git reference:** The name of the fully qualified reference (i.e: `refs/heads/master`). - **SHA1:** The SHA1 value to create this reference from. ### Pulls #### Create a pull request - **GitHub API:** [Create a pull request](https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#create-a-pull-request). - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repo:** The name of the repository. The name is not case-sensitive - **Title:** The title of the new pull request. - **Head:** The name of the branch where your changes are implemented. - **Base:** The name of the branch you want the changes pulled into. - **Body:** The contents of the pull request. - **Draft:** Indicates whether the pull request is a draft. ### Collaborators #### List repository collaborators - **GitHub API:** [List repository collaborators](https://docs.github.com/en/rest/collaborators/collaborators?apiVersion=2022-11-28#list-repository-collaborators). - **Owner:** The account owner of the repository. The name is not case-sensitive. - **Repo:** The name of the repository. The name is not case-sensitive - **Page:** The page number of the results to fetch. - **Results per page:** The number of results per page. ## Handle connector response The **GitHub connector** is a protocol connector, meaning it is built on top of the **HTTP REST connector**, therefore handling response is still applicable [as described](/components/connectors/protocol/rest.md#response). The **GitHub Webhook connector** is an inbound connector that allows you to start a BPMN process instance triggered by a [GitHub event](https://docs.github.com/en/developers/webhooks-and-events/webhooks/about-webhooks). ## Create a GitHub Webhook connector task 1. Start building your BPMN diagram. You can use GitHub Webhook connector with either **Start Event** or **Intermediate Catch Event** building blocks. 2. Select the applicable element and change its template to a GitHub Webhook. 3. Fill in all required properties. 4. Complete your BPMN diagram. 5. Deploy the diagram to activate the webhook. 6. Navigate to the **Webhooks** tab in the properties panel on the right side of the screen to observe the webhook URL. ## Make your GitHub Webhook connector for receiving messages executable 1. In the **Webhook Configuration** section, configure the **Webhook ID**. By default, **Webhook ID** is pre-filled with a random value. This value will be part of the Webhook URL. You will find more details about GitHub Webhook URLs [below](#activate-the-github-webhook-connector-by-deploying-your-diagram). 2. Set the **GitHub secret**. This is a shared secret key that has to be defined in both your BPMN and GitHub webhook configuration page. The value is used to calculate HMAC authentication signature. 3. Configure **Activation Condition**. For example, given GitHub triggers a webhook endpoint with a new PR payload `{"action": "opened", "pull_request": ...}`, the **Activation Condition** value might look like as `=(request.body.action = "opened")`. Leave this field empty to trigger your webhook every time. 4. Use **Result Variable** to store the response in a process variable. For example, `myResultVariable`. 5. Use **Result Expression** to map specific fields from the response into process variables using [FEEL](/components/modeler/feel/what-is-feel.md). For example, given that the GitHub webhook is triggered with the body `{"pull_request": {"id": 123}}` and you would like to extract the pull request `id` as a process variable `pullRequestId`, the **Result Expression** might look like this: ``` = { pullRequestId: request.body.pull_request.id } ``` ### Correlation The **Correlation** section allows you to configure the message correlation parameters. :::note The **Correlation** section is not applicable for the plain **start event** element template of the GitHub Webhook connector. Plain **start events** are triggered by process instance creation and do not rely on message correlation. ::: ### Correlation keys - **Correlation key (process)** is a FEEL expression that defines the correlation key for the subscription. This corresponds to the **Correlation key** property of a regular **message intermediate catch event**. - **Correlation key (payload)** is a FEEL expression used to extract the correlation key from the incoming message. This expression is evaluated in the connector Runtime and the result is used to correlate the message. For example, given that your correlation key is defined with `pullRequestId` process variable, and the request body contains `{"pull_request": {"id": 123}}`, your correlation key settings will look like this: - **Correlation key (process)**: `=pullRequestId` - **Correlation key (payload)**: `=request.body.pull_request.id` Learn more about correlation keys in the [messages guide](../../../concepts/messages). #### Message ID expression The **Message ID expression** is an optional field that allows you to extract the message ID from the incoming message. The message ID serves as a unique identifier for the message and is used for message correlation. This expression is evaluated in the connector Runtime and the result is used to correlate the message. In most cases, it is not necessary to configure the **Message ID expression**. However, it is useful if you want to ensure message deduplication or achieve certain message correlation behavior. Learn more about how message IDs influence message correlation in the [messages guide](../../../concepts/messages#message-correlation-overview). For example, if you want to set the message ID to the value of the `pull_request.id` field in the incoming request, you can configure the **Message ID expression** as follows: ``` = request.body.pull_request.id ``` #### Message TTL The **Message TTL** is an optional field that allows you to set the time-to-live (TTL) for the correlated messages. TTL defines the time for which the message is buffered in Zeebe before being correlated to the process instance (if it can't be correlated immediately). The value is specified as an ISO 8601 duration. For example, `PT1H` sets the TTL to one hour. Learn more about the TTL concept in Zeebe in the [message correlation guide](../../../concepts/messages#message-buffering). ## Activate the GitHub Webhook connector by deploying your diagram Once you click the **Deploy** button, your GitHub Webhook will be activated and publicly available. URLs of the exposed GitHub Webhooks adhere to the following pattern: `http(s):///inbound/>` - `` is the URL of connectors component deployment. When using the Camunda 8 SaaS offering, this will typically contain your **region Id** and **cluster Id**, found in your client credentials under the **API** tab within your cluster. - `` is the ID (path) you configured in the properties of your GitHub Webhook connector. If you make changes to your GitHub Webhook connector configuration, you need to redeploy the BPMN diagram for the changes to take effect. When you click on the event with GitHub Webhook connector applied to it, a new **Webhooks** tab will appear in the properties panel. This tab displays the URL of the GitHub Webhook connector for every cluster where you have deployed your BPMN diagram. :::note The **Webhooks** tab is only supported in Camunda Hub as part of the Camunda 8 SaaS offering. You can still use GitHub Webhook connector in Desktop Modeler, or with your Camunda 8 Self-Managed. In that case, GitHub Webhook connector deployments and URLs will not be displayed in Desktop Modeler. ::: ## Configure GitHub 1. Ensure you have administrator rights for the repository where you wish to enable a webhook. 2. Open a repository in your web browser and navigate to the **Settings** page. 3. Click **Webhooks > Add webhook**. 4. Fill out the required fields: 1. **Payload URL** - URL of your webhook. 2. **Content type** - Select `application/json`. 3. **Secret** - Shared secret between GitHub and your BPMN diagram. 5. Confirm by clicking **Add webhook**. Refer to the [GitHub documentation](https://docs.github.com/en/rest/webhooks) for more details. ## Next steps - Learn more about [GitHub webhooks](https://docs.github.com/en/developers/webhooks-and-events/webhooks/about-webhooks). --- ## GitLab connector The **GitLab connector** is an outbound connector that allows you to connect your BPMN service with [GitLab](https://about.gitlab.com/) to manage GitLab issues and releases. ## Prerequisites To use the **GitLab connector**, you must have a GitLab instance and an [access token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html) of a user or a service account on whose behalf a BPMN process will be executed. :::note Use secrets to avoid exposing your GitLab access token credentials as plain text. Refer to our documentation on [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ::: ## Create a GitLab connector task ## Add endpoint and authentication In the **HTTP Endpoint** section, provide a **GitLab base URL**, i.e. `https://gitlab.mycorp.com`, and a **GitLab access token**. ## Select operation to execute The **GitLab connector** currently supports the following operations. ### Issues #### Get an issue by ID - **GitLab API:** [Single project issue](https://docs.gitlab.com/ee/api/issues.html#single-project-issue). - **Project ID:** The global ID or URL-encoded path of the project owned by the authenticated user. - **Issue ID:** The internal ID of a project’s issue. #### Create an issue - **GitLab API:** [New issue](https://docs.gitlab.com/ee/api/issues.html#new-issue). - **Project ID:** The global ID or URL-encoded path of the project owned by the authenticated user. - **Title:** The title of an issue. - **Description:** The description of an issue. #### Delete an issue - **GitLab API:** [Delete an issue](https://docs.gitlab.com/ee/api/issues.html#delete-an-issue). - **Project ID:** The global ID or URL-encoded path of the project owned by the authenticated user. - **Issue ID:** The internal ID of a project’s issue. #### Comment to an issue - **GitLab API:** [Create a new issue note](https://docs.gitlab.com/ee/api/notes.html#create-new-issue-note). - **Project ID:** The global ID or URL-encoded path of the project owned by the authenticated user. - **Issue ID:** The internal ID of a project’s issue. - **Note text:** The content of a note. - **Level of confidentiality:** Indicates if an issue has to be marked as **internal** or not. #### Search issues - **GitLab API:** [List issues](https://docs.gitlab.com/ee/api/issues.html#list-issues). - **Scope:** Return issues for the given scope: **Created by me**, **Assigned to me**, or **all**. - **State:** Return all issues or just those that are **opened** or **closed**. - **Assignee ID:** Return issues assigned to the given user ID. Mutually exclusive with **Assignee username**. **None** returns unassigned issues. **Any** returns issues with an assignee. - **Assignee username:** Return issues assigned to the given username. Similar to **Assignee ID** and mutually exclusive with **Assignee ID**. - **Author ID:** Return issues created by the given user ID. - **Contains text:** Search issues against their **Title** and **Description**. ### Releases #### List all releases by a project ID - **GitLab API:** [List releases](https://docs.gitlab.com/ee/api/releases/#list-releases). - **Project ID:** The global ID or URL-encoded path of the project owned by the authenticated user. #### Get release by a tag name - **GitLab API:** [Get a release by a tag name](https://docs.gitlab.com/ee/api/releases/#get-a-release-by-a-tag-name). - **Project ID:** The global ID or URL-encoded path of the project owned by the authenticated user. - **Tag name:** The Git tag the release is associated with. #### Create a release - **GitLab API:** [Create a release](https://docs.gitlab.com/ee/api/releases/#create-a-release). - **Project ID:** The global ID or URL-encoded path of the project owned by the authenticated user. - **Tag name:** The tag where the release is created from. - **Ref:** A commit SHA, another tag name, or a branch name. - **Release name:** The release name. - **Description:** The description of the release. ### Branches #### List repository branches - **GitLab API:** [List repository branches](https://docs.gitlab.com/ee/api/branches.html#list-repository-branches). - **Project ID:** The global ID or URL-encoded path of the project owned by the authenticated user. - **Search String:** Return list of branches containing the search string. - **Regular Expression:** Return list of branches with names matching a [re2](https://github.com/google/re2/wiki/Syntax) regular expression. ### Create repository branch - **GitLab API:** [Create repository branch](https://docs.gitlab.com/ee/api/branches.html#create-repository-branch). - **Project ID:** The global ID or URL-encoded path of the project owned by the authenticated user. - **Name:** The name of the new branch. - **Reference:** The branch name or commit SHA to create the branch from. ### Repository files #### Create new file in repository - **GitLab API:** [Create new file in repository](https://docs.gitlab.com/ee/api/repository_files.html#create-new-file-in-repository). - **Project ID:** The global ID or URL-encoded path of the project owned by the authenticated user. - **Branch name:** Name of the new branch to create the file in. The commit is added to this branch. - **Commit message:** Message of the commit that adds the new file. - **Content:** The content of the new file. - **File path:** URL-encoded full path to new file. For example, `lib%2Fclass%2Erb`. - **Author email:** The commit author's email address. - **Author name:** The commit author's name. - **Encoding:** The encoding of the content. GitLab's default is `text`. - **Execute file mode:** Enables or disables the `execute` flag on the file. - **Start branch:** The branch to start the new branch from. - **Allow collaboration:** Allow commits from members who can merge to the target branch. ## Merge requests ### Create merge request - **GitLab API:** [Create merge request](https://docs.gitlab.com/ee/api/merge_requests.html#create-mr). - **Project ID:** The global ID or URL-encoded path of the project owned by the authenticated user. - **Source branch:** Name of the source branch. - **Target branch:** Name of the target branch. - **Title:** Title of the merge request. - **Assignee IDs:** The IDs of the users to assign the merge request to as an array of numbers. - **Description:** Description of the merge request. - **Labels:** Comma-separated list of label names for the merge request. - **Milestone ID:** The ID of a milestone to assign the merge request to. - **Remove source branch:** Flag indicating if a merge request should remove the source branch when merging. - **Reviewer IDs:** The ID of the users to set as reviewers of the merge request as an array of numbers. - **Squash:** Flag indicating if commits should be squashed into a single commit when merging. - **Target project ID:** Numeric ID of the target project. ## Handle connector response The **GitLab connector** is a protocol connector, meaning it is built on top of the **HTTP REST connector**, therefore handling response is still applicable [as described](/components/connectors/protocol/rest.md#response). --- ## Google Cloud storage connector The **Google Cloud Storage connector** is an outbound connector that allows you to interact with [Google Cloud Storage](https://cloud.google.com/storage?hl=en) resources from your BPMN processes. :::note The connector currently supports uploading and downloading documents. ::: ## Prerequisites To begin using the **Google Cloud Storage connector**, ensure the following are set up: - A Google [service account](https://cloud.google.com/iam/docs/service-account-overview) - A [JSON key for the service account](https://cloud.google.com/iam/docs/keys-create-delete) - A Google Cloud Storage [bucket](https://cloud.google.com/storage/docs/creating-buckets) The service account must be granted access to the bucket: - To **upload and download** objects, assign the `Storage Object Admin` role. - To **download only**, assign the `Storage Object Viewer` role. ## Authentication ### Create a new connector secret To keep your **JSON key** secure, avoid including it directly in the BPMN XML. Instead, create a secret: 1. Follow our [guide for creating secrets](/components/saas/clusters/manage-secrets.md). 2. Name your secret (e.g., `GOOGLE_SERVICE_ACCOUNT_KEY`) for reference in the connector configuration. ## Operation Select the desired operation from the **Operation** dropdown. ### Upload document 1. Enter the **project ID**. 2. Enter the **bucket name**. 3. (Optional) Enter the **File name**. If left blank, the filename from the document metadata will be used. 4. Select a [document source](/components/document-handling/send-document-to-external-system.md#document-sources) for the **Document to upload**: a **Camunda document** reference, **inline content** built from process data, or an **external document** URL. :::note Use **inline content** to build a file (for example, a `.json` file) directly from process variables without first storing it in the Camunda document store. See [inline documents](/components/document-handling/send-document-to-external-system.md#inline-documents). If a document with the same name already exists in the bucket, it will be overwritten. If object versioning is enabled, previous versions can be restored. ::: ### Download document 1. Enter the **project ID**. 2. Enter the **bucket name**. 3. Enter the **File name** to download. 4. Select a [return format](/components/document-handling/send-document-to-external-system.md#return-formats) for the downloaded content: - **Document reference**: a reference to a document created in the Camunda document store is returned in `document`. - **As text**: the content is decoded to a string (with an optional encoding, default UTF-8) and returned in `content`. - **As JSON**: the content is parsed as JSON and returned in `content`. :::note **As text** and **As JSON** return the content directly in a process variable and are subject to a size guard (approximately 1.5 MiB); use **Document reference** for large files. **As JSON** fails the job when the content is not valid JSON. If the specified file is not found, the operation will fail and return an error message. ::: ## Limitations For detailed information on quotas and limits, including file size constraints, refer to the official [Google Cloud Storage quotas and limits](https://cloud.google.com/storage/quotas). --- ## Google Gemini connector :::info The **Google Gemini connector** is available for `8.7.0` or later. ::: The **Google Gemini connector** is an outbound connector that allows you to access Gemini multimodal models from Google. It is capable of understanding virtually any input, and can combine different types of information in a BPMN process. ## Create a Google Gemini connector task ## Make your Google Gemini connector executable To execute this connector, ensure all mandatory fields are correctly filled. :::note All the mandatory and non-mandatory fields and required settings depending on the operation selection you choose are covered in the upcoming sections. ::: ## Authentication Choose an authentication type from the **Type** dropdown. For details on authentication types, see [Google authentication types](#google-authentication-types). ## Project ID Enter your google cloud project identifier. ## Region Enter the region where your project is located. For example, `us-central1 (lowa)`, `us-west1 (Oregon)`. ## Model Select a model from the dropdown. The following models are currently supported: - gemini-1.5-flash-001 - gemini-1.5-flash-002 - gemini-1.5-pro-001 - gemini-1.5-pro-002 - gemini-1.0-pro-001 - gemini-1.0-pro-002 - gemini-1.0-pro-vision-001 ## Prompt Enter a prompt as a FEEL expression, providing text and media. - To provide text to Gemini, your expression should contain key _"text"_ and text data. For example, _"text"_ : _"your text"_ - To provide media to Gemini, your expression should contain key _"mime"_ and mime type text, and key _"uri"_ and media URI. For example, _"mime"_: _"mime type"_, _"uri"_: _"your URI"_. For example: ```feel = [{"text": "who is this video about"}, {"mime": "video/*", "uri": "https://youtu.be/..."}] ``` ## System instructions Enter system instructions as a string, to determine how the model should respond. To learn more about system instructions, refer to [Google system instructions](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/prompts/system-instructions?hl=en). ## Grounding Grounding connects the model output to the verifiable sources of information. - This is useful in situations where accuracy and reliability are important. - To use grounding, select the _Grounding_ checkbox and input the path to the data store. To learn more about grounding, refer to [Google grounding overview](https://cloud.google.com/vertex-ai/generative-ai/docs/grounding/overview?hl=en). ## Safety Filter Settings You can adjust the likelihood of receiving a model response which might contain harmful content. - Content is blocked based on the probability that it is harmful. - To use safety filter settings, select the _Safety Filter Settings_ checkbox and select the desired level from dropdown. - By default, all filters are set to OFF. To learn more about safety filters, refer to [Google responsible AI safety filters and settings](https://cloud.google.com/vertex-ai/docs/generative-ai/learn/responsible-ai?hl=en#safety_filters_and_attributes). ## Add stop sequence A stop sequence is a series of characters (including spaces) that stops response generation if encountered by the model. The stop sequence should be inserted as a string list. For example: ```feel = ["text 1", "text 2"] ``` ## Temperature The **Temperature** controls the randomness in token selection. - A lower temperature is good when you expect a true or correct response. A temperature of `0` means the highest probability token is usually selected. - A higher temperature can lead to diverse or unexpected results. Some models have a higher temperature max to encourage more random responses. ## Output token limit The **Output token limit** Determines the maximum amount of text output from a single prompt. A token is approximately four characters. ## Seed Setting a **Seed** value is useful if you make repeated requests and want the same model response. Deterministic outcome isn’t guaranteed. Changing the model or other settings can cause variations in the response even when you use the same seed value. ## Top-K The **Top-K** specifies the number of candidate tokens when the model is selecting an output token. - Use a lower value for less random responses and a higher value for more random responses. - Only the _gemini-1.0-pro-vision-001_ model supports Top-K. ## Top-P The **Top-P** changes how the model selects tokens for output. - Tokens are selected from the most probable to the least probable, until the sum of their probabilities equals the top-p value. - For example, if tokens A, B, and C have a probability of .3, .2, and .1 and the top-p value is .5, then the model will select either A or B as the next token (using temperature). - For the least variable results, set top-P to 0. ## Functional call description **Function calling** is a feature of Gemini models that makes it easier to get structured data outputs from generative models. - The **Functional call description** must be provided in fell format. - It is important that all types must be registered with capslock. To learn more about function calling, refer to [Google function calling](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling). For example: ```fell [ { "name": "get_exchange_rate", "description":"Get the exchange rate for currencies between countries", "parameters": { "type": "OBJECT", "properties": { "currency_date": { "type": "STRING", "description": "A date that must always be in YYYY-MM-DD format or the value 'latest' if a time period is not specified" }, "currency_from": { "type": "STRING", "description": "The currency to convert from in ISO 4217 format" }, "currency_to": { "type": "STRING", "description": "The currency to convert to in ISO 4217 format" } }, "required":[ "currency_date", "currency_from", "currency_to" ] } } ] ``` ### Google authentication types The **Google Gemini connector** currently supports two methods for authentication and authorization: - Based on a short-lived JWT bearer token. - Based on a refresh token. Google supports multiple ways to obtain both types of token. Refer to the [official Google OAuth documentation](https://developers.google.com/identity/protocols/oauth2) for current instructions, or see the examples below. #### Example 1: Obtain JWT bearer token with a service account :::danger The following code snippet is for demonstration purposes only and must not be used for real production systems due to security concerns. For production usage, follow the [official Google guidelines](https://developers.google.com/identity/protocols/oauth2/service-account). ::: Assuming you have created a service account and downloaded a JSON file with keys, run the following Python 3 snippet to print the JWT token in the terminal: ```python from google.oauth2 import service_account # Scopes required to execute 'create' endpoind with Google Drive API SCOPES = ['https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/generative-language.retriever'] # File with keys SERVICE_ACCOUNT_FILE = 'google-service-account-creds.json' credentials = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES) auth_req = google.auth.transport.requests.Request() credentials.refresh(auth_req) # Print token print(credentials.token) ``` #### Example 2: Obtain bearer and refresh token with OAuth client :::danger The following code snippet is for demonstration purposes only and must not be used for real production systems due to security concerns. For production usage, follow the [official Google guidelines](https://developers.google.com/identity/protocols/oauth2/web-server). ::: Assuming you have created an OAuth client, you can download key files from the Google [Console](https://console.cloud.google.com/apis/credentials). Run the following Python 3 snippet to print the refresh token in the terminal: ```python from google_auth_oauthlib.flow import InstalledAppFlow SCOPES = ['https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/generative-language.retriever'] OAUTH_KEYS = './oauth-keys.json' # path to your file with OAuth credentials def main(): flow = InstalledAppFlow.from_client_secrets_file(OAUTH_KEYS, SCOPES) creds = flow.run_local_server(port=54948) pprint.pprint(vars(creds)) if __name__ == "__main__": main() ``` --- ## Google Maps Platform connector The **Google Maps Platform connector** in an inbound connector that allows you to validate addresses, retrieve postal addresses, and calculate distances with [Google Maps Platform Service](https://mapsplatform.google.com/) in BPMN process. ## Create a Google Maps Platform connector task ## Make your Google Maps Platform connector executable To work with the Google Maps Platform connector, choose the required operation type in the **Operation** section and enable the required Google Service API (which depends on the operation). Set the API key in the **Authentication** section and complete the mandatory fields highlighted in red in the connector on the right side of the screen under the **Deploy** button. :::note All the mandatory and non-mandatory fields and required settings depending on the operation selection you choose are covered in the upcoming sections. ::: ## Authentication In the **Authentication** section, set the relevant API key. Refer to the [official documentation](https://cloud.google.com/docs/authentication/api-keys#create) for more information on creating an API key. :::note We advise you to keep your authentications and secrets data safe and avoid exposing it in the BPMN XML file by creating a secret: 1. Follow our [guide for creating secrets](/components/saas/clusters/manage-secrets.md). 2. Name your secret (i.e `GOOGLE_MAPS_PLATFORM_API_KEY`) so you can reference it later in the connector. ::: ## Operation types ### Validate address This operation allows you to validate an address and its components, standardize the address for mailing, and determine the best known geocode for it. To use this operation, enable the [Google Address Validation API](https://developers.google.com/maps/documentation/address-validation/overview). Refer to the [official documentation](https://developers.google.com/maps/documentation/address-validation/cloud-setup) for more information on enabling the Google API. ### Get place ID This operation allows you to get the Google Maps place ID by address. To use this operation, enable the [Google Places API](https://developers.google.com/maps/documentation/places/web-service). Refer to the [official documentation](https://developers.google.com/maps/documentation/places/web-service/get-api-key) for more information on enabling the Google API. ### Calculate distance This operation allows you to calculate a distance between two place IDs. To use this operation, enable the [Google Directions API](https://developers.google.com/maps/documentation/directions). Refer to the [official documentation](https://developers.google.com/maps/documentation/directions/get-api-key) for more information on enabling the Google API. ## Usage ### Address validation, formatting, getting postal address 1. Select **Validate Address** from the **Operation type** dropdown in the **Operation** section. 2. Populate the **Authentication** section as described in the [respective section](#authentication). 3. (Optional) In the **Input** section, set **Region Code** (i.e `US`). You can find supported region codes [here](https://developers.google.com/maps/documentation/address-validation/coverage). 4. (Optional) In the **Input** section, set **Locality**, an incorporated city or town political entity (i.e `Mountain View`). 5. In the **Input** section, set **Address**, an incorporated city or town political entity (i.e `1600 Amphitheatre Pkwy`). 6. In the **Output** section set **Result Variable** or **Result Expression**. Refer to the [response mapping documentation](/components/connectors/use-connectors/index.md#response-mapping) to learn more. 7. Find a full example of the **Google Maps Platform connector** response [here](https://developers.google.com/maps/documentation/address-validation/requests-validate-address#address_validation_response). To get postal address and formatted address, set to **Result Expression** in the FEEL expression: ``` { formattedAddress: response.body.result.address.formattedAddress, postalAddress: response.body.result.address.postalAddress } ``` ### Get place ID 1. Select **Get Place ID** from the **Operation type** dropdown in the **Operation** section. 2. Populate the **Authentication** section as described in the [respective section](#authentication). 3. In the **Input** section, set **Address**. This address can be `formatedAddress`, which you can get using [this example](#address-validation-formatting-getting-postal-address). 4. In the **Output** section in the **Result Expression** property, the following expression is preset: ``` { placeId: response.body.candidates[1].place_id } ``` In this way, the response of this method will contain a mapping from the variable 'placeId' and the ID of the place: ```json { "placeId": "place....." } ``` ### Calculate distance 1. Select **Calculate Distance** from the **Operation type** dropdown in the **Operation** section. 2. Populate the **Authentication** section as described in the [respective section](#authentication). 3. In the **Input** section, set **Destination**, the place ID value that you want to use as the destination for calculating distance. 4. In the **Input** section, set **Origin**, the place ID value that you want to use as the starting point for calculating distance. 5. Select the unit system to use when displaying results from the **Units** dropdown in the **Input** section. 6. Select the transportation mode to use when calculating distances and directions from the **Mode** dropdown in the **Input** section. 7. In the **Output** section, set **Result Variable** or **Result Expression**. Refer to the [response mapping documentation](/components/connectors/use-connectors/index.md#response-mapping) to learn more. 8. Find a full example of the **Google Maps Platform connector** response [here](https://developers.google.com/maps/documentation/directions/start#getting-directions). To get a distance, set **Result Expression** in the FEEL expression: ``` { distance: response.body.routes[1].legs[1].distance.text } ``` ## Using Google Maps Platform connector best practice There is no guarantee a queue item will be processed right away. In that case, we suggest building your BPMN diagram to periodically retry polling. To learn more, refer to the entry titled _Solution with Timer and Loop_ on the [Camunda BPMN examples](https://camunda.com/bpmn/examples/) page. :::note To avoid performance issues, it is recommended to limit the number of loop retries. ::: --- ## Google Sheets connector The **Google Sheets connector** is an outbound connector that allows you to work with an existing or new spreadsheet on [Google Drive](https://drive.google.com/) from your BPMN process. ## Prerequisites To start working with the **Google Sheets connector**, a relevant OAuth token must be configured and stored as a secret in your cluster. The token must have permission to read/write and create a file from a desired Google Drive instance. Follow the steps from the [appendix](#appendix--faq) to find out more about creating an OAuth token and assigning relevant permissions. ## Create a Google Sheets connector task Currently, the Google Sheets connector supports next operations: - [Add values to spreadsheet](#add-values-to-spreadsheet) - [Create empty column or row](#create-empty-column-or-row) - [Create row](#create-row) - [Create spreadsheet](#create-spreadsheet) - [Create worksheet](#create-worksheet) - [Delete column](#delete-column) - [Delete worksheet](#delete-worksheet) - [Get row by index](#get-row-by-index) - [Get spreadsheet details](#get-spreadsheet-details) - [Get worksheet data](#get-worksheet-data) ## Make your Google Sheets connector executable To make the **Google Sheets connector** executable, fill out the mandatory fields highlighted in red in the properties panel. ### Add values to spreadsheet To add values to a spreadsheet, take the following steps: 1. Set the required credentials in the **Authentication** section. Refer to the [relevant appendix entry](#how-can-i-authenticate-my-connector) to find out more. 2. In the **Select operation** section, select **Add values to spreadsheet**. 3. In the **Operation details** section, set the field **Spreadsheet ID** to the desired spreadsheet, in which a new value will be added. 4. _(optional)_ In the **Operation details** section, set the field **Worksheet name** to the desired worksheet, in which a new value will be added. Keep in mind that if not specified, a new value will be added to the first available worksheet in the desired spreadsheet. 5. In the **Operation details** section, set the field **Cell ID** to the desired cell, in which a new value a new value will be added. Use format ColumnRow (for example A1). 6. In the **Operation details** section, set the field **Value** to the desired value, which will be added in the desired cell. #### Operation response The following fields are available in the response variable: - `action` - The action performed. In this case, it will always be **Add values to Spreadsheet**. - `status` - The status of the operation. If successful, it will always be "OK". Otherwise, there will be an error message. - `response` - The response of the operation. In this case, it will always be **null**. ### Create empty column or row To create empty column or row, take the following steps: 1. Set the required credentials in the **Authentication** section. Refer to the [relevant appendix entry](#how-can-i-authenticate-my-connector) to find out more. 2. In the **Select operation** section, select **Create empty column or row**. 3. In the **Operation details** section, set the field **Spreadsheet ID** to the desired spreadsheet, in which new columns/rows will be added. 4. In the **Operation details** section, set the field **Worksheet ID** to the desired worksheet, in which new columns/rows will be added. 5. In the **Operation details** section, select the **Dimension**, which will be added. 6. _(optional)_ In the **Operation details** section, set the both of the **Start index** and **End index** fields, in which new columns/rows will be added. Keep in mind that **count starts from 0**. It's possible to leave these fields empty. In this case, a new column/row will be added in the end of the desired worksheet. #### Operation response The following fields are available in the response variable: - `action` - The action performed. In this case, it will always be **Create empty column or row**. - `status` - The status of the operation. If successful, it will always be "OK". Otherwise, it will be an error message. - `response` - The response of the operation. In this case, it will always be **null**. ### Create row To create a row, take the following steps: 1. Set the required credentials in the **Authentication** section. Refer to the [relevant appendix entry](#how-can-i-authenticate-my-connector) to find out more. 2. In the **Select operation** section, select **Create row**. 3. In the **Operation details** section, set the field **Spreadsheet ID** to the desired spreadsheet, in which a new row will be added. 4. _(optional)_ In the **Operation details** section, set the field **Worksheet name** to the desired worksheet, in which a new row will be added. Keep in mind that if not specified, a new row will be added to the first available worksheet in the desired spreadsheet. 5. _(optional)_ In the **Operation details** section, set the field **Row index** to the desired row index, where a new row will be added. Refer to the [relevant appendix entry](#what-is-a-row-index) to find out more. If not set, a new row will be appended to the last row. 6. In the **Operation details** section, set the field **Enter values** to the desired values, which will be added. This property requires [FEEL input](../../../components/modeler/feel/language-guide/feel-expressions-introduction.md). #### Operation response The following fields are available in the response variable: - `action` - The action performed. In this case, it will always be **Create row**. - `status` - The status of the operation. If successful, it will always be "OK". Otherwise, it will be an error message. - `response` - The response of the operation. In this case, it will always be **null**. ### Create spreadsheet To create a spreadsheet, take the following steps: 1. Set the required credentials in the **Authentication** section. Refer to the [relevant appendix entry](#how-can-i-authenticate-my-connector) to find out more. 2. In the **Select operation** section, select **Create spreadsheet**. 3. _(optional)_ In the **Operation details** section, set the field **Parent folder ID** to the desired parent, inside which a new spreadsheet will be created. Keep in mind that if not specified, a new spreadsheet will be created in the Google Drive root folder of a user who owns the OAuth token. 4. In the **Operation details** section, set the field **Spreadsheet name** to the desired spreadsheet name. #### Operation response The following fields are available in the response variable: - `spreadsheetId` - ID of the newly created spreadsheet. - `spreadsheetUrl` - Human-readable URL of the newly created spreadsheet. ### Create worksheet To create a worksheet, take the following steps: 1. Set the required credentials in the **Authentication** section. Refer to the [relevant appendix entry](#how-can-i-authenticate-my-connector) to find out more. 2. In the **Select operation** section, select **Create worksheet**. 3. In the **Operation details** section, set the field **Spreadsheet ID** to the desired spreadsheet, in which a new worksheet will be created. 4. In the **Operation details** section, set the field **Worksheet name** to the desired worksheet name. 5. _(optional)_ In the **Operation details** section, set the field **Worksheet index** to the desired index, in which a new worksheet will be created. Refer to the [relevant appendix entry](#what-is-a-worksheet-index) to find out more. #### Operation response The following fields are available in the response variable: - `action` - The action performed. In this case, it will always be **Create worksheet**. - `status` - The status of the operation. If successful, it will always be "OK". Otherwise, it will be an error message. - `response` - The response of the operation. In this case, it will always be **null**. ### Delete column To delete a column, take the following steps: 1. Set the required credentials in the **Authentication** section. Refer to the [relevant appendix entry](#how-can-i-authenticate-my-connector) to find out more. 2. In the **Select operation** section, select **Delete column**. 3. In the **Operation details** section, set the field **Spreadsheet ID** to the desired spreadsheet, in which a column will be deleted. 4. In the **Operation details** section, set the field **Worksheet ID** to the desired worksheet ID, in which a column will be deleted. 5. In the **Operation details** section, select **Index format** of desired index of column to be deleted. Refer to the [relevant appendix entry](#how-can-i-define-which-column-will-be-deleted) to find out more. 6. In the **Operation details** section, set the **Column letter index** to the desired column index. #### Operation response The following fields are available in the response variable: - `action` - The action performed. In this case, it will always be **Delete column**. - `status` - The status of the operation. If successful, it will always be "OK". Otherwise, it will be an error message. - `response` - The response of the operation. In this case, it will always be **null**. ### Delete worksheet To delete a worksheet, take the following steps: 1. Set the required credentials in the **Authentication** section. Refer to the [relevant appendix entry](#how-can-i-authenticate-my-connector) to find out more. 2. In the **Select operation** section, select **Delete worksheet**. 3. In the **Operation details** section, set the field **Spreadsheet ID** to the desired spreadsheet, in which a worksheet will be deleted. 4. In the **Operation details** section, set the field **Worksheet ID** to the desired worksheet ID, which will be deleted. #### Operation response The following fields are available in the response variable: - `action` - The action performed. In this case, it will always be **Delete worksheet**. - `status` - The status of the operation. If successful, it will always be "OK". Otherwise, it will be an error message. - `response` - The response of the operation. In this case, it will always be **null**. ### Get row by index To get row by index, take the following steps: 1. Set the required credentials in the **Authentication** section. Refer to the [relevant appendix entry](#how-can-i-authenticate-my-connector) to find out more. 2. In the **Select operation** section, select **Get row by index**. 3. In the **Operation details** section, set the field **Spreadsheet ID** to the desired spreadsheet, from which a row will be retrieved. 4. _(optional)_ In the **Operation details** section, set the field **Worksheet name** to the desired worksheet, from which a row will be retrieved. Keep in mind that if not specified, a row will be retrieved from the first available worksheet in the desired spreadsheet. 5. In the **Operation details** section, set the field **Row index** to the desired row index. Refer to the [relevant appendix entry](#what-is-a-row-index) to find out more. #### Operation response The following fields are available in the response variable: - `action` - The action performed. In this case, it will always be **Get row by index**. - `status` - The status of the operation. If successful, it will always be "OK". Otherwise, it will be an error message. - `response` - The response of the operation. If row is empty, the response is **null**, else **array**. ### Get spreadsheet details To get spreadsheet details, take the following steps: 1. Set the required credentials in the **Authentication** section. Refer to the [relevant appendix entry](#how-can-i-authenticate-my-connector) to find out more. 2. In the **Select operation** section, select **Get spreadsheet details**. 3. In the **Operation details** section, set the field **Spreadsheet ID** to the desired spreadsheet, which details will be returned. #### Operation response The response contains spreadsheet properties. For details, read the [official Google documentation](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets#spreadsheetproperties). ### Get worksheet data To get worksheet data, take the following steps: 1. Set the required credentials in the **Authentication** section. Refer to the [relevant appendix entry](#how-can-i-authenticate-my-connector) to find out more. 2. In the **Select operation** section, select **Get worksheet data**. 3. In the **Operation details** section, set the field **Spreadsheet ID** to the desired spreadsheet, from which data will be retrieved. 4. In the **Operation details** section, set the field **Worksheet name** to the desired worksheet, from which data will be retrieved. #### Operation response The following fields are available in the response variable: - `action` - The action performed. In this case, it will always be **Get worksheet data** - `status` - The status of the operation. If successful, it will always be "OK". Otherwise, it will be an error message. - `response` - The response of the operation. If the worksheet is empty, the response is **null**, else **array of rows (also array)**. ## Appendix & FAQ ### How can I authenticate my connector? The **Google Sheets connector** currently supports two methods for authentication and authorization: based on short-lived JWT bearer token, and based on refresh token. Google supports multiple ways to obtain both. Refer to the [official Google OAuth documentation](https://developers.google.com/identity/protocols/oauth2) to get up-to-date instructions or refer to the examples below. You also enable _Google Sheets API_ and _Google Drive API_ for every client intended to use. You can do this from the [Google Cloud API Library](https://console.cloud.google.com/apis/library). #### Example 1: Obtaining JWT bearer token with a service account :::danger The following code snippet is for demonstration purposes only and must not be used for real production systems due to security concerns. For production usage, follow the [official Google guidelines](https://developers.google.com/identity/protocols/oauth2/service-account). ::: Assuming you have created a service account and downloaded a JSON file with keys, run the following Python 3 snippet that prints the JWT token in the terminal: ```python from google.oauth2 import service_account # Scopes required to execute 'create' endpoind with Google Sheets API SCOPES = ['https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/drive.file', 'https://www.googleapis.com/auth/drive.appdata'] # File with keys SERVICE_ACCOUNT_FILE = 'google-service-account-creds.json' credentials = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES) auth_req = google.auth.transport.requests.Request() credentials.refresh(auth_req) # Print token print(credentials.token) ``` #### Example 2: Obtaining bearer and refresh tokens with OAuth client :::danger The following code snippet is for demonstration purposes only and must not be used for real production systems due to security concerns. For production usage, follow the [official Google guidelines](https://developers.google.com/identity/protocols/oauth2/web-server). ::: Assuming you have created an OAuth client, you can download key files from the Google [Console](https://console.cloud.google.com/apis/credentials). Run the following Python 3 snippet that prints the refresh token in the terminal: ```python from google_auth_oauthlib.flow import InstalledAppFlow SCOPES = ['https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/spreadsheets'] OAUTH_KEYS = './oauth-keys.json' # path to your file with OAuth credentials def main(): flow = InstalledAppFlow.from_client_secrets_file(OAUTH_KEYS, SCOPES) creds = flow.run_local_server(port=54948) pprint.pprint(vars(creds)) if __name__ == "__main__": main() ``` ### Where do I get a spreadsheet ID? The spreadsheet ID is located within the URL of the Google Sheets document. Here's how to find it: For example, if the URL looks like this: ``` https://docs.google.com/spreadsheets/d/1xhNL0a6WjZtYRHF2522FrCYUYxHve9ju-DHNkaTm9Sk/edit#gid=0 ``` The spreadsheet ID is the alphanumeric string between `d/` and `/edit`, which in this case is `1xhNL0a6WjZtYRHF2522FrCYUYxHve9ju-DHNkaTm9Sk`. ### Where do I get a worksheet ID? The Worksheet ID (also known as the gid) can be found in the same URL. Here's how to find it: For example, if the URL looks like this: ``` https://docs.google.com/spreadsheets/d/1xhNL0a6WjZtYRHF2522FrCYUYxHve9ju-DHNkaTm9Sk/edit#gid=0 ``` The worksheet ID is the number after `#gid=`, which in this case is `0`. ### What is a worksheet index? You can define the place where a new worksheet will be created. By default, the new worksheet will be created at the end of the worksheet. Keep in mind, **count starts from 0**. For instance, to create a new worksheet on the second place, worksheet index should be set as **1**. ### What is a row index? Row index is the unique identifier for each row in some worksheet, which is used both for reading and writing operations with row. This index is defined to the left of the row. ### How can I define which column will be deleted? There are two ways to define which column will be deleted: by letter index and numeric one. Numeric index can be found at the top of the column. The other option is to use numeric index. Keep in mind **count starts from 0**. To delete column A, numeric index should be 0, B -> 1... --- ## Google Drive connector The **Google Drive connector** is an outbound connector that allows you to create empty folders or files on [Google Drive](https://drive.google.com/) from templates from your BPMN process. ## Prerequisites To start working with the **Google Drive connector**, a relevant OAuth token must be configured and stored as a secret in your cluster. The token must have permission to read and create a folder and/or files from a desired Google Drive instance. Follow the steps from the [appendix](#appendix--faq) to find out more about creating an OAuth token and giving relevant permissions. ## Create a Google Drive connector task Currently, the Google Drive connector supports two types of operations: create a folder and create a file from a template. ## Make your Google Drive connector executable To make the **Google Drive connector** executable, fill out the mandatory fields highlighted in red in the properties panel on the right side of the screen. ### Create a new folder To create a new folder, take the following steps: 1. Set the required credentials in the **Authentication** section. Refer to the [relevant appendix entry](#how-can-i-authenticate-my-connector) to find out more. 2. In the **Select Operation** section, set the field value **Operation Type** as **Create Folder**. 3. In the **Operation Details** section, set the field **Folder name** as the desired name of a folder you wish to create. For example, `MyNewFolder`. Alternatively, you could use a FEEL expression. 4. _(optional)_ In the **Operation Details** section, set the field **Parent folder ID** to the desired parent, inside which a new folder will be created. Keep in mind that if not specified, a new folder will be created in the Google Drive root folder of a user who owns the OAuth token. 5. _(optional)_ In the **Operation Details** section, you can set the **Additional properties or metadata** field to Google Drive compatible properties. For example, _description_ of the folder. This property requires FEEL input. Check [the appendix](#what-are-the-limitations-of-the-additional-properties-or-metadata) for known values and limitations. ### Create a new file from a template To create a new file from a template, take the following steps: 1. Set the required credentials in the **Authentication** section. Refer to the [relevant appendix entry](#how-can-i-authenticate-my-connector) to find out more. 2. In the **Select Operation** section, set the field value **Operation Type** as **Create File from template**. 3. In the **Operation Details** section, set the field **File name** as the desired name of a folder you wish to create. You can use FEEL expressions here. 4. In the **Operation Details** section, set the field **Template ID** of the desired template. 5. _(optional)_ In the **Operation Details** section, set the field **Parent folder ID** to the desired parent, inside which a new file will be created. Keep in mind that if not specified, a new folder will be created in the Google Drive root folder of a user who owns the OAuth token. 6. In the **Operation Details** section, set the field **Template variables** as desired variables that will be applied to the template. The template variables are compatible with the Google Docs [Requests API](https://developers.google.com/docs/api/reference/rest/v1/documents/request). This property requires FEEL input. 7. _(optional)_ In the **Operation Details** section, you can set the **Additional properties or metadata** field to Google Drive compatible properties. This property requires FEEL input. Check [the appendix](#what-are-the-limitations-of-the-additional-properties-or-metadata) for known values and limitations. :::note Starting from version 8.7.0, the Google Drive connector supports uploading documents from (or downloading documents to) the Camunda document store. Review the **Document** field in the properties panel where the document reference can be provided. See additional details and limitations in [document handling](/components/document-handling/getting-started.md). ::: ### Upload file To upload a file, take the following steps: 1. Set the required credentials in the **Authentication** section. Refer to the [relevant appendix entry](#how-can-i-authenticate-my-connector) to find out more. 2. In the **Select Operation** section, set the field value **Operation Type** to **Upload File**. 3. _(optional)_ In the **Operation Details** section, set the field **Parent folder ID** to the desired parent, inside which a new file will be created. If not specified, a new folder is created in the Google Drive root folder of the user who owns the OAuth token. 4. In the **Document** section, select a [document source](/components/document-handling/send-document-to-external-system.md#document-sources): a **Camunda document** reference, **inline content** built from process data, or an **external document** URL. :::note Use **inline content** to build a file directly from process variables without first storing it in the Camunda document store. See [inline documents](/components/document-handling/send-document-to-external-system.md#inline-documents). To upload a **Camunda document**, it must exist first — [using the Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/specifications/create-document.api.mdx) for example. The result of the endpoint must then be assigned to a variable in **Start Process instance** so you can use the variable in the **Document** field. ::: ### Download file To download a file, take the following steps: 1. Set the required credentials in the **Authentication** section. Refer to the [relevant appendix entry](#how-can-i-authenticate-my-connector) to find out more. 2. In the **Select Operation** section, set the field value **Operation Type** to **Download File**. 3. In the **Operation Details** section, set the field _File ID_ to the google drive file that will be downloaded. For more information, refer to the [file id appendix](#Where-do-I-get-File-ID). 4. Select a [return format](/components/document-handling/send-document-to-external-system.md#return-formats) for the downloaded content: **Document reference** (default), **As text** (with an optional encoding, default UTF-8), or **As JSON**. ## Google Drive connector response The following response types can be returned by the _Google Drive connector response_, depending on the **Operation Type** selected. ### Response type for list of operations - **Create Folder** - **Create File from template** - **Upload File** The **Google Drive connector response** exposes the Google Drive API response as a local variable named "response". The following fields are available in the response variable: - `googleDriveResourceId`: The ID of the newly created resource. - `googleDriveResourceUrl`: Human-readable URL of the newly created resource. You can use an output mapping to map the response: 1. Use **Result Variable** to store the response in a process variable. For example, `myResultVariable`. 2. Use **Result Expression** to map fields from the response into process variables. For example: ``` = { "myNewReportFolderId": response.googleDriveResourceId, "myNewReportFolderUrl": response.googleDriveResourceUrl } ``` ### Response type for _Download file_ operation only The response depends on the selected [return format](/components/document-handling/send-document-to-external-system.md#return-formats): - **Document reference** (default) returns a document created in the Camunda document store, identical to the [REST API](/apis-tools/orchestration-cluster-api-rest/specifications/create-document.api.mdx). For example: ``` { "camunda.document.type": "camunda", "storeId": "in-memory", "documentId": "c3c8e499-321d-421c-afa2-4632d2f5ce48", "metadata": { "contentType": "image/png", "fileName": "file name", "size": 66497, "customProperties": {} } } ``` - **As text** returns the content decoded as a string, and **As JSON** returns it parsed as JSON. Both are subject to a size guard (approximately 1.5 MiB); use **Document reference** for large files. ## Appendix & FAQ ### What Google API does the Google Drive connector use to create a folder? The **Google Drive connector** uses the Google Drive [`Files:Create`](https://developers.google.com/drive/api/v3/reference/files/create) API endpoint. ### What Google API does the Google Drive connector use to create a file from template? The **Google Drive connector** uses the Google Drive [`Files:Copy`](https://developers.google.com/drive/api/v3/reference/files/copy) API endpoint to copy an original template. Afterwards, the **Google Drive connector** utilizes Google Docs [Merge](https://developers.google.com/docs/api/how-tos/merge) approach via [`Documents:BatchUpdate`](https://developers.google.com/docs/api/reference/rest/v1/documents/batchUpdate) Google Docs API method. ### How can I authenticate my connector? The **Google Drive connector** currently supports two methods for authentication and authorization: based on short-lived JWT bearer token, and based on refresh token. Google supports multiple ways to obtain both. Refer to the [official Google OAuth documentation](https://developers.google.com/identity/protocols/oauth2) to get up-to-date instructions or refer to the examples below. You also enable _Google Docs API_ and _Google Drive API_ for every client intended to use. You can do this at the [Google Cloud API Library](https://console.cloud.google.com/apis/library) page. #### Example 1: Obtaining JWT bearer token with a service account :::danger The following code snippet is for demonstration purposes only and must not be used for real production systems due to security concerns. For production usage, follow the [official Google guidelines](https://developers.google.com/identity/protocols/oauth2/service-account). ::: Assuming you have created a service account and downloaded a JSON file with keys, run the following Python 3 snippet that prints the JWT token in the terminal: ```python from google.oauth2 import service_account # Scopes required to execute 'create' endpoind with Google Drive API SCOPES = ['https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/drive.file', 'https://www.googleapis.com/auth/drive.appdata'] # File with keys SERVICE_ACCOUNT_FILE = 'google-service-account-creds.json' credentials = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES) auth_req = google.auth.transport.requests.Request() credentials.refresh(auth_req) # Print token print(credentials.token) ``` When using shared drives, you must use an Oauth client instead of a service account. The service account does not have access to shared drives. #### Example 2: Obtaining bearer and refresh tokens with OAuth client :::danger The following code snippet is for demonstration purposes only and must not be used for real production systems due to security concerns. For production usage, follow the [official Google guidelines](https://developers.google.com/identity/protocols/oauth2/web-server). ::: Assuming you have created an OAuth client, you can download key files from the Google [Console](https://console.cloud.google.com/apis/credentials). Run the following Python 3 snippet that prints the refresh token in the terminal: ```python from google_auth_oauthlib.flow import InstalledAppFlow SCOPES = ['https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/documents'] OAUTH_KEYS = './oauth-keys.json' # path to your file with OAuth credentials def main(): flow = InstalledAppFlow.from_client_secrets_file(OAUTH_KEYS, SCOPES) creds = flow.run_local_server(port=54948) pprint.pprint(vars(creds)) if __name__ == "__main__": main() ``` ### Where do I get a parent folder ID? To find the Parent Folder ID for a Google Drive folder, follow these steps: 1. Open the Google Drive folder in your web browser. 2. Look at the URL in the address bar, which will look something like this: ``` https://docs.google.com/drive/folder/1whNL0a6WjZtYRHF2522FrCYUYxHve9ju-DHNkaTm9Sk ``` 3. The Parent Folder ID is the alphanumeric string after `/folders/`, which in this case is `1whNL0a6WjZtYRHF2522FrCYUYxHve9ju-DHNkaTm9Sk`. ### How do I set additional properties or metadata? You can set any property from the Google Drive [Create API](https://developers.google.com/drive/api/v3/reference/files/create). For example: ``` = { "description":"myDescription" } ``` The unknown or mistyped properties will be ignored. ### What are the limitations of the additional properties or metadata? Some properties are applicable only for the token owners, like `folderColorRgb` and `starred`. ### Where do I get Template ID? To find the Template ID for a Google Docs template, follow these steps: 1. Open the link to the Google Docs template in your web browser. The URL will look something like this: ``` https://docs.google.com/document/d/1whNL0a6WjZtYRHF2522FrCYUYxHve9ju-DHNkaTm9Sk ``` 2. The Template ID is the alphanumeric string after `/d/`, which in this case is `1whNL0a6WjZtYRHF2522FrCYUYxHve9ju-DHNkaTm9Sk`. ### Can you show me an example of a valid template? Certainly! Here is an example of a valid template: ```text {{CompanyName}} confidential. {{DocumentDate}} {{RecipientFullName}} {{RecipientAddress}} Dear {{RecipientShortName}}! We are pleased to inform you that your application {{ApplicationNumber}} has been approved. Sincerely, {{SigneeName}}, Executive Director ``` Now, in the **Template variables** field we can apply the following FEEL JSON object which must be compatible with the Google Docs Requests API: ``` = { "requests":[ { "replaceAllText":{ "containsText":{ "text":"{{DocumentDate}}", "matchCase":"true" }, "replaceText":today() } }, { "replaceAllText":{ "containsText":{ "text":"{{RecipientFullName}}", "matchCase":"true" }, "replaceText":"John W. Doe" } }, { "replaceAllText":{ "containsText":{ "text":"{{RecipientAddress}}", "matchCase":"true" }, "replaceText":"Zweibrückenstraße 1845, 80000 Munich" } }, { "replaceAllText":{ "containsText":{ "text":"{{RecipientShortName}}", "matchCase":"true" }, "replaceText":"Mr. Doe" } }, { "replaceAllText":{ "containsText":{ "text":"{{ApplicationNumber}}", "matchCase":"true" }, "replaceText":"0123456789" } }, { "replaceAllText":{ "containsText":{ "text":"{{SigneeName}}", "matchCase":"true" }, "replaceText":"Jane T. Doe" } }, { "replaceAllText":{ "containsText":{ "text":"{{CompanyName}}", "matchCase":"true" }, "replaceText":"Good Company Inc." } } ] } ``` The result should be as follows: ```text Good Company inc. confidential. 2022-08-10 John W. Doe Zweibrückenstraße 1845, 80000 Munich Dear Mr. Doe! We are pleased to inform you that your application 0123456789 has been approved. Sincerely, Jane T. Doe, Executive Director ``` ### Where do I get File ID? To find the File ID for a Google file, follow these steps: 1. Select the desired file on your Google Drive, click on the three horizontal dots to the right of the file name. 2. Click on the share section. 3. Click on copy link. The URL will look something like the following: ``` https://drive.google.com/file/d/1y1td3iIKWOh88gK4hVevGM1WnX7tibCW/view ``` 4. The File ID is the alphanumeric string after `/d/`. In this example, this would be `1y1td3iIKWOh88gK4hVevGM1WnX7tibCW`. ### What kind of templates are currently supported? The **Google Drive connector** currently supports only Google Doc files (MIME type `application/vnd.google-apps.document`). --- ## HubSpot connector The **Hubspot connector** is an outbound connector that allows you to connect your BPMN service with [HubSpot](https://hubspot.com/) to manage HubsSpot contacts, companies, and deals. ## Prerequisites To use the **HubSpot connector**, you must have a HubSpot account and a [Bearer token](https://knowledge.hubspot.com/integrations/how-do-i-get-my-hubspot-api-key) to authenticate requests. When creating a private app, you must grant the permissions required to access the HubSpot API. Different operations require different permissions. To use all HubSpot connector operations, add the following permissions to your app: - `crm.objects.contacts.read` - `crm.objects.contacts.write` - `crm.objects.companies.read` - `crm.objects.companies.write` - `crm.objects.deals.read` - `crm.objects.deals.write` - `crm.lists.read` - `crm.lists.write` - `automation` :::note Use secrets to avoid exposing your token credentials as plain text. Refer to our documentation on [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ::: ## Create a HubSpot connector task ## Select operation to execute The **HubSpot connector** currently supports the following operations. ### Contacts #### Get all contacts - **Next page after object id:** HubSpot limits the number of results to 100 and provides pagination. To retrieve the first page keep this field empty. To retrieve the following page, set it to `response.body.paging.next.after` from the previous page. #### Get contact by id - **Contact ID:** The ID of the contact. - **Properties:** The properties of the contact to receive. Learn more about [properties](https://developers.hubspot.com/docs/reference/api/crm/properties/v1-contacts#get-all-contact-properties) and [default properties of contacts](https://knowledge.hubspot.com/properties/hubspots-default-contact-properties). If you do not set this field, standard properties (firstname,lastname, and email) are returned.
To get all default properties, set properties to the following list: ``` address, annualrevenue, associatedcompanyid, associatedcompanylastupdated, city, closedate, company, company_size, country, createdate, currentlyinworkflow, date_of_birth, days_to_close, degree, email, engagements_last_meeting_booked, engagements_last_meeting_booked_campaign, engagements_last_meeting_booked_medium, engagements_last_meeting_booked_source, favorite_content_topics, fax, field_of_study, first_conversion_date, first_conversion_event_name, first_deal_created_date, firstname, gender, graduation_date, hs_additional_emails, hs_all_accessible_team_ids, hs_all_contact_vids, hs_all_owner_ids, hs_all_team_ids, hs_analytics_average_page_views, hs_analytics_first_referrer, hs_analytics_first_timestamp, hs_analytics_first_touch_converting_campaign, hs_analytics_first_url, hs_analytics_first_visit_timestamp, hs_analytics_last_referrer, hs_analytics_last_timestamp, hs_analytics_last_touch_converting_campaign, hs_analytics_last_url, hs_analytics_last_visit_timestamp, hs_analytics_num_event_completions, hs_analytics_num_page_views, hs_analytics_num_visits, hs_analytics_revenue, hs_analytics_source, hs_analytics_source_data_1, hs_analytics_source_data_2, hs_associated_target_accounts, hs_avatar_filemanager_key, hs_buying_role, hs_calculated_form_submissions, hs_calculated_merged_vids, hs_calculated_mobile_number, hs_calculated_phone_number, hs_calculated_phone_number_area_code, hs_calculated_phone_number_country_code, hs_calculated_phone_number_region_code, hs_clicked_linkedin_ad, hs_contact_enrichment_opt_out, hs_contact_enrichment_opt_out_timestamp, hs_content_membership_email, hs_content_membership_email_confirmed, hs_content_membership_follow_up_enqueued_at, hs_content_membership_notes, hs_content_membership_registered_at, hs_content_membership_registration_domain_sent_to, hs_content_membership_registration_email_sent_at, hs_content_membership_status, hs_conversations_visitor_email, hs_count_is_unworked, hs_count_is_worked, hs_country_region_code, hs_created_by_conversations, hs_created_by_user_id, hs_createdate, hs_document_last_revisited, hs_email_bad_address, hs_email_bounce, hs_email_click, hs_email_customer_quarantined_reason, hs_email_delivered, hs_email_domain, hs_email_first_click_date, hs_email_first_open_date, hs_email_first_reply_date, hs_email_first_send_date, hs_email_hard_bounce_reason, hs_email_hard_bounce_reason_enum, hs_email_is_ineligible, hs_email_last_click_date, hs_email_last_email_name, hs_email_last_open_date, hs_email_last_reply_date, hs_email_last_send_date, hs_email_open, hs_email_optout, hs_email_optout_697354363, hs_email_optout_697354364, hs_email_quarantined, hs_email_quarantined_reason, hs_email_replied, hs_email_sends_since_last_engagement, hs_emailconfirmationstatus, hs_enriched_email_bounce_detected, hs_facebook_ad_clicked, hs_facebook_click_id, hs_first_closed_order_id, hs_first_engagement_object_id, hs_first_order_closed_date, hs_first_outreach_date, hs_first_subscription_create_date, hs_full_name_or_email, hs_google_click_id, hs_has_active_subscription, hs_ip_timezone, hs_is_contact, hs_is_enriched, hs_is_unworked, hs_language, hs_last_metered_enrichment_timestamp, hs_last_sales_activity_date, hs_last_sales_activity_timestamp, hs_last_sales_activity_type, hs_lastmodifieddate, hs_latest_disqualified_lead_date, hs_latest_meeting_activity, hs_latest_open_lead_date, hs_latest_qualified_lead_date, hs_latest_sequence_ended_date, hs_latest_sequence_enrolled, hs_latest_sequence_enrolled_date, hs_latest_sequence_finished_date, hs_latest_sequence_unenrolled_date, hs_latest_source, hs_latest_source_data_1, hs_latest_source_data_2, hs_latest_source_timestamp, hs_latest_subscription_create_date, hs_lead_status, hs_legal_basis, hs_linkedin_ad_clicked, hs_linkedin_url, hs_live_enrichment_deadline, hs_membership_has_accessed_private_content, hs_membership_last_private_content_access_date, hs_merged_object_ids, hs_mobile_sdk_push_tokens, hs_notes_last_activity, hs_notes_next_activity, hs_notes_next_activity_type, hs_object_id, hs_object_source, hs_object_source_detail_1, hs_object_source_detail_2, hs_object_source_detail_3, hs_object_source_id, hs_object_source_label, hs_object_source_user_id, hs_persona, hs_pinned_engagement_id, hs_pipeline, hs_prospecting_agent_actively_enrolled_count, hs_quarantined_emails, hs_read_only, hs_recent_closed_order_date, hs_registered_member, hs_registration_method, hs_role, hs_sa_first_engagement_date, hs_sa_first_engagement_descr, hs_sa_first_engagement_object_type, hs_sales_email_last_clicked, hs_sales_email_last_opened, hs_sales_email_last_replied, hs_searchable_calculated_international_mobile_number, hs_searchable_calculated_international_phone_number, hs_searchable_calculated_mobile_number, hs_searchable_calculated_phone_number, hs_seniority, hs_sequences_actively_enrolled_count, hs_sequences_enrolled_count, hs_sequences_is_enrolled, hs_state_code, hs_sub_role, hs_testpurge, hs_testrollback, hs_time_to_first_engagement, hs_timezone, hs_unique_creation_key, hs_updated_by_user_id, hs_user_ids_of_all_notification_followers, hs_user_ids_of_all_notification_unfollowers, hs_user_ids_of_all_owners, hs_v2_date_entered_customer, hs_v2_date_entered_evangelist, hs_v2_date_entered_lead, hs_v2_date_entered_marketingqualifiedlead, hs_v2_date_entered_opportunity, hs_v2_date_entered_other, hs_v2_date_entered_salesqualifiedlead, hs_v2_date_entered_subscriber, hs_v2_date_exited_customer, hs_v2_date_exited_evangelist, hs_v2_date_exited_lead, hs_v2_date_exited_marketingqualifiedlead, hs_v2_date_exited_opportunity, hs_v2_date_exited_other, hs_v2_date_exited_salesqualifiedlead, hs_v2_date_exited_subscriber, hs_was_imported, hs_whatsapp_phone_number, hubspot_owner_assigneddate, hubspot_owner_id, hubspot_team_id, hubspotscore, industry, ip_city, ip_country, ip_country_code, ip_latlon, ip_state, ip_state_code, ip_zipcode, job_function, jobtitle, lastmodifieddate, lastname, lifecyclestage, marital_status, message, military_status, mobilephone, notes_last_contacted, notes_last_updated, notes_next_activity_date, num_associated_deals, num_contacted_notes, num_conversion_events, num_notes, num_unique_conversion_events, numemployees, phone, preferred_channels, recent_conversion_date, recent_conversion_event_name, recent_deal_amount, recent_deal_close_date, relationship_status, salutation, school, seniority, start_date, state, surveymonkeyeventlastupdated, total_revenue, twitterhandle, webinareventlastupdated, website, work_email, zip ```
#### Get multiple contacts by id - **Contact ids:** The IDs of the contacts to retrieve. This is limited to 100 contacts. #### Search contact - **Search field:** The field to search for. For example, "lastname". - **Search value:** The value to search for. For example, "Smith". :::note All contacts matching the search criteria are returned. ::: #### Create contact - **Properties:** The properties of the contact to create. Learn more about [properties](https://developers.hubspot.com/docs/guides/api/crm/properties) and [default properties of contacts](https://knowledge.hubspot.com/properties/hubspots-default-contact-properties). - **Company ID:** The ID of the company the contact is associated with. HubSpot automatically adds the contact to the company if the mail address domain matches the company domain. For example, "jane.doe@camunda.com" is automatically added to the company with the domain "camunda.com". If you set the company ID manually, the contact is added to the company with the given ID **AND** the company with the matching domain. #### Update contact - **Properties:** The properties of the contact to update. Learn more about [properties](https://developers.hubspot.com/docs/guides/api/crm/properties) and [default properties of contacts](https://knowledge.hubspot.com/properties/hubspots-default-contact-properties). Only add properties you want to adjust to the properties field. - **Contact ID:** The ID of the contact to update. :::note It is not possible to update associations to companies with this operation. To do so use the `add contact to` or `remove contact from company` operations under `Companies`. ::: #### Delete contact - **Contact ID:** The ID of the contact to delete. ### Companies #### Get all companies - **Next page after object id:** HubSpot limits the number of results to 100 and provides pagination. To retrieve the first page keep this field empty. To retrieve the following page, set it to `response.body.paging.next.after` from the previous page. #### Get company by id - **Company ID:** The ID of the company. #### Search company - **Search field:** The field to search for. For example, "name". - **Search value:** The value to search for. For example, "Camunda". :::note All companies matching the search criteria are returned. ::: #### Get all contacts of a company - **Company ID:** The ID of the company. #### Add contact to company - **Contact ID:** The ID of the contact. - **Company ID:** The ID of the company. #### Remove contact from company - **Contact ID:** The ID of the contact. - **Company ID:** The ID of the company. #### Create company - **Properties:** The properties of the company to create. Learn more about [properties](https://developers.hubspot.com/docs/guides/api/crm/properties) and [default properties of companies](https://knowledge.hubspot.com/properties/hubspot-crm-default-company-properties). #### Delete company - **Company ID:** The ID of the company to delete. ### Deals #### Get all deals - **Next page after object id:** HubSpot limits the number of results to 100 and provides pagination. To retrieve the first page keep this field empty. To retrieve the following page, set it to `response.body.paging.next.after` from the previous page. #### Get deal by id - **Deal ID:** The ID of the deal. #### Search deal - **Search field:** The field to search for. For example, "dealname". - **Search value:** The value to search for. For example, "Inital Deal for Camunda". :::note All deals matching the search criteria are returned. ::: #### Delete deal - **Deal ID:** The ID of the deal to delete. ### Miscellaneous #### Submit form - **Portal ID:** The HubSpot account that the form belongs to. [Learn more](https://knowledge.hubspot.com/account-management/manage-multiple-hubspot-accounts#check-your-current-account) - **Form ID:** The unique ID of the form you are sending data to. [Learn more](https://knowledge.hubspot.com/forms/find-your-form-guid) - **Form fields:** The value of the input fields of the form. [Learn more](https://developers.hubspot.com/docs/reference/api/marketing/forms/v3-legacy) #### Add element to list - **List ID:** The ID of the list. - **Object IDs:** The IDs of the objects to add to the list. :::note Adding elements to a list may take a few seconds to be reflected in the HubSpot UI. ::: #### Enroll contact to workflow - **Workflow ID:** The workflow ID. You can retrieve the ID by sending a request to the [get all workflows endpoint](https://developers.hubspot.com/docs/reference/api/automation/create-manage-workflows/v3#get-all-workflows). - **Contact email:** The email of the contact to be enrolled to the workflow. ## Handle connector response As the **HubSpot connector** is a protocol connector (built on top of the **HTTP REST connector**) the handling response is still applicable [as described](/components/connectors/protocol/rest.md#response). --- ## Hugging Face connector The **Hugging Face connector** is an outbound connector that allows you to interact with [Hugging Face](https://huggingface.co/) models from your BPMN processes. ## Prerequisites To begin using the **Hugging Face connector**, you need to have a valid [API key](https://huggingface.co/docs/api-inference/quicktour#get-your-api-token), and a model deployed with [Inference API](https://huggingface.co/docs/api-inference/index). ## Create a Hugging Face connector task ## Make your Hugging Face connector executable To work with the **Hugging Face connector**, fill all mandatory fields. ## Authentication Fill the **Hugging Face API key** field with a valid Hugging Face API key. ### Create a new connector secret Keep your **API key** safe and avoid exposing it in the BPMN `xml` file by creating a secret: 1. Follow our [guide for creating secrets](/components/saas/clusters/manage-secrets.md). 2. Name your secret (for example, `HUGGING_FACE_SECRET`) so you can reference it later in the connector. ## Payload In the **Model** field, enter a model name you wish to use in your BPMN process, for example, `gpt2` if you wish to use the [GPT2 model](https://huggingface.co/openai-community/gpt2). In the **Input** field, enter input parameters for your model, for example, `{"inputs":"What is the Capital of Germany?"}`. ## Handle connector response The **Hugging Face connector** is a protocol connector, meaning it is built on top of the **HTTP REST connector**. Therefore, handling response is still applicable [as described](/components/connectors/protocol/rest.md#response). ## Usage example Let's assume you want to use the [BART (large-sized model), fine-tuned on CNN Daily Mail](https://huggingface.co/facebook/bart-large-cnn) model, and created the `HUGGING_FACE_SECRET` secret containing your Hugging Face API key. Consider the following input: - **Hugging Face API key**: `{{secrets.HUGGING_FACE_SECRET}}` - **Model**: `facebook/bart-large-cnn` - **Input**: ```json { "inputs": "Java is a high-level, class-based, object-oriented programming language that is designed to have as few implementation dependencies as possible. It is a general-purpose programming language intended to let programmers write once, run anywhere (WORA), meaning that compiled Java code can run on all platforms that support Java without the need to recompile. Java applications are typically compiled to bytecode that can run on any Java virtual machine (JVM) regardless of the underlying computer architecture. The syntax of Java is similar to C and C++, but has fewer low-level facilities than either of them. The Java runtime provides dynamic capabilities (such as reflection and runtime code modification) that are typically not available in traditional compiled languages. As of March 2024, Java 22 is the latest version. Java 8, 11, 17, and 21 are previous LTS versions still officially supported.", "parameters": { "max_length": 75, "temperature": 10 }, "options": { "use_cache": "false" } } ``` - **Result variable**: `myHuggingFaceResponse`. In the `myHuggingFaceResponse` you will find the following result: ```json { "status":200, "headers":{ ... }, "body":[ { "summary_text":" Java is a high-level, class-based, object-oriented programming language. It is intended to let programmers write once, run anywhere. Java applications are typically compiled to bytecode that can run on any Java virtual machine (JVM) regardless of the underlying computer architecture. As of March 2024, Java 22 is the latest version." } ] } ``` --- ## Kafka connector ## Reuse a Kafka connection Use a **Kafka Connection** credential to share broker and authentication settings between Kafka producer and Kafka consumer connectors. Reusable credentials require Camunda 8.10 or later and a Kafka element template with the optional **Connection credential** chooser in the **Connection** section. Select the same credential on producer tasks and consumer events, or leave **Connection credential** empty to keep configuring brokers and authentication inline in the **Connection** section. A Kafka Connection credential stores these required fields: | Field | Description | | ------------------ | ---------------------------------------------------------------------------------- | | `bootstrapServers` | Bootstrap server addresses, separated by commas when you use more than one server. | | `username` | Kafka username. For a secret reference, use `camunda.secrets.MY_KAFKA_USERNAME`. | | `password` | Kafka password. For a secret reference, use `camunda.secrets.MY_KAFKA_PASSWORD`. | Create referenced secrets before using the credential. Inside a credential, use `camunda.secrets.NAME`, not the legacy `{{secrets.NAME}}` syntax. Legacy secret references remain supported in inline connector fields. Selecting a credential binds the whole object to `kafkaConnectionConfiguration` using `=camunda.vars.env.`, where `` is the credential's cluster variable name. The credential supplies authentication using `SASL_SSL` with the `PLAIN` mechanism. Keep custom authentication configured inline. The selected credential replaces inline broker and authentication settings. An invalid credential fails execution or consumer activation; the connector doesn't fall back to inline values. Topic, consumer group, offsets, schema settings, and message configuration remain local to each task or event. :::warning **Additional properties** still overrides the final connection and security properties, including `bootstrap.servers`. Selecting a credential doesn't restrict the destination or prevent these overrides. ::: ### Test a Kafka connection **Test connection** performs a read-only Kafka metadata probe to check connectivity and authentication, using the connector runtime's trust configuration. It uses bounded timeouts and reports a failure if the connection can't be established. The result applies only to the stored credential, not to **Additional properties** overrides configured on a task or event. The test doesn't publish or consume messages, join a consumer group, or change offsets. Success doesn't verify topic, consumer group, publishing, or consumption permissions. Updating a credential follows the existing consumer activation and reload lifecycle. Don't rely on credential edits automatically rotating the connection of an already-running consumer. The **Kafka Producer connector** is an outbound connector that allows you to connect your BPMN service with [Apache Kafka](https://kafka.apache.org/) to produce messages. ## Prerequisites To use the **Kafka Producer connector**, you must have a Kafka instance with a configured bootstrap server. :::note Use secrets to avoid exposing your sensitive data as plain text. To learn more, see [managing secrets](/components/saas/clusters/manage-secrets.md). ::: ## Create a Kafka Producer connector task ## Make your Kafka Producer connector for publishing messages executable To make your **Kafka Producer connector** for publishing messages executable, complete the following sections. ### Connection In the **Connection** section, select a **Connection credential** or configure the connection inline: 1. Set **Bootstrap servers** to the URL of the bootstrap server(s). If more than one server is required, use comma-separated values. 2. (Optional) Set **Username** and **Password**. For example, `{{secrets.MY_KAFKA_USERNAME}}`. ### Schema In the **Kafka** section: 1. Select the schema strategy for your messages. - Select **No schema**, **Inline schema** for Avro serialization. - Select **Schema registry** if you have a Confluent Schema Registry. 2. Set the topic name. 3. (Optional) Set producer configuration values in the **Headers** field. Only `UTF-8` strings are supported as header values. 4. (Optional) Set producer configuration values in the **Additional properties** field. :::info The [appendix](#appendix-and-faq) provides more information about: - [Kafka secure authentication](#what-mechanism-is-used-to-authenticate-against-kafka). - [Inline schema](#inline-schema) and [Schema registry](#schema-registry). - [Pre-configured producer configuration values](#what-are-default-kafka-producer-client-properties) for this connector. Additionally, to learn more about supported producer configurations, see the [official Kafka documentation](https://kafka.apache.org/41/configuration/producer-configs/). ::: ### Message In the **Message** section, set the **Key** and the **Value** that will be sent to Kafka topic. ## Schema strategies This connector supports different schema strategies, offering a compact, fast, and binary data exchange format for Kafka messages. When using a schema strategy, each message is serialized according to a specific schema written in JSON format. This schema defines the Kafka message structure, ensuring the data conforms to a predefined format, and enables schema evolution strategies. :::info To learn more about Schema strategies, refer to the official documentation: - [Inline Avro serialization](https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/serdes-avro.html) and [official Apache Avro documentation](https://avro.apache.org/docs/). - [Confluent Schema Registry](https://docs.confluent.io/platform/current/schema-registry/index.html) (Avro, and JSON schemas). ::: ### No schema Select **No schema** to send messages without a schema. This option is suitable for simple messages that do not require a schema. ### Inline schema Select **Inline schema** to send messages with an **Avro schema**. - This option is suitable for messages that require a schema and that are not (or do not need to be) registered in a schema registry. - Enter the Avro schema that defines the message structure into the **Schema** field that appears in the **Message** section. ### Schema registry Select **Schema registry** to send messages with a schema registered in a schema registry. - This option is suitable for messages that require a schema and that are registered in a [schema registry](https://docs.confluent.io/platform/current/schema-registry/index.html). - You must provide: - The **schema registry URL** in the **Kafka** section. - The **schema** itself (that defines the message structure) in the **Message** section. - The **credentials** for the schema registry (if required). Refer to the [Schema Registry documentation](https://docs.confluent.io/platform/current/schema-registry/sr-client-configs.html#basic-auth-credentials-source) for more information. :::info Currently, the Kafka connector supports only [Confluent Schema Registry](https://docs.confluent.io/platform/current/schema-registry/index.html). Other schema registry implementations are not supported at this time. ::: ### Example Avro schema and data The following is an example Avro schema and data: #### Avro schema: ```json { "doc": "Sample schema to help you get started.", "fields": [ { "name": "name", "type": "string" }, { "name": "age", "type": "int" }, { "name": "emails", "type": { "items": "string", "type": "array" } } ], "name": "sampleRecord", "namespace": "com.mycorp.mynamespace", "type": "record" } ``` #### Kafka message - **Key**: `employee1` - **Value**: ```json { "name": "John Doe", "age": 29, "emails": ["johndoe@example.com"] } ``` ## Kafka Producer connector response The **Kafka Producer connector** returns metadata for a record that has been acknowledged by the Kafka instance. The following fields are available in the `response` variable: - `timestamp`: The timestamp of the message. - `offset`: The message offset. - `partition`: The message partition. - `topic`: The topic name. :::info For more information on these fields, refer to the [official Kafka documentation](https://kafka.apache.org/41/getting-started/introduction/). ::: You can use an output mapping to map the response: 1. Use **Result Variable** to store the response in a process variable. For example, `myResultVariable`. 2. Use **Result Expression** to map fields from the response into process variables. For example: ``` = { "messageAcknowledgedAt": response.timestamp } ``` ## Appendix and FAQ ### What mechanism is used to authenticate against Kafka? If the fields **Username** and **Password** are not empty, by default the **Kafka Producer connector** enables the credentials-based SASL SSL authentication and the following properties are set: ``` sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username='' password=''; security.protocol=SASL_SSL sasl.mechanism=PLAIN ``` If any of the fields are not populated, you must configure your security method for your Kafka configuration. You can do this using the **Additional properties** field. ### What are default Kafka Producer client properties? - Authentication properties (only if both **Username** and **Password** are not empty): ``` sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username='' password=''; security.protocol=SASL_SSL sasl.mechanism=PLAIN ``` - Bootstrap server property: ``` bootstrap.servers= ``` - Message properties: ``` key.serializer=org.apache.kafka.common.serialization.StringSerializer value.serializer=org.apache.kafka.common.serialization.StringSerializer ``` - Miscellaneous properties: ``` session.timeout.ms=45000 client.dns.lookup=use_all_dns_ips acks=all delivery.timeout.ms=45000 ``` ### What is the precedence of client properties loading? Properties loading consists of three steps: 1. Construct client properties from the BPMN diagram: authentication, bootstrap server, message properties. If selected, the **Connection credential** supplies authentication and bootstrap servers instead of the inline fields. 2. Load miscellaneous properties. 3. Load and **override** properties from the field **Additional properties**. ### How do I set or override additional client properties? The following example sets a new client property `client.id` and overrides the SASL mechanism to `SCRAM SHA-256` instead of plain text: ``` = { "client.id":"MyDemoClientId", "sasl.mechanism":"SCRAM-SHA-256" } ``` The **Kafka Consumer connector** allows you to consume messages by subscribing to [Kafka](https://kafka.apache.org/) topics and mapping them to your BPMN processes as start or intermediate events. ## Prerequisites To use the **Kafka Consumer connector**, you must have a Kafka instance with a configured bootstrap server. :::note Use secrets to avoid exposing your sensitive data as plain text. To learn more, see [managing secrets](/components/saas/clusters/manage-secrets.md). ::: ## Create a Kafka Consumer connector event 1. Add a **Start Event** or an **Intermediate Event** to your BPMN diagram to get started. 2. Change its template to a Kafka Consumer. 3. Fill in all required properties. 4. Complete your BPMN diagram. 5. Deploy the diagram to activate the Kafka consumer. ## Configure your Kafka Consumer connector To make your **Kafka Consumer connector** executable, fill in the required properties. ### Connection In the **Connection** section, select a **Connection credential** or configure the connection inline: 1. Set **Bootstrap servers** to the URL of the bootstrap server(s). If more than one server is required, use comma-separated values. 2. Select the **Authentication type**. If you selected **Credentials**, set **Username** and **Password**. :::note - Use secrets to avoid exposing your sensitive data as plain text. To learn more, see [managing secrets](/components/saas/clusters/manage-secrets.md). - To learn more about Kafka authentication, see [Kafka secure authentication](#what-mechanism-is-used-to-authenticate-against-kafka-1). ::: ### Kafka properties In the **Kafka** section, you can configure the following properties: - **Consumer Group ID**: Set the consumer group ID for this connector. Always provide an explicit, stable value that identifies the logical consumer group (for example, `my-app-order-processor`). If you leave this field empty, the connector auto-generates an ID from its internal deduplication key. That generated ID can change across connector upgrades, including from 8.8 to 8.9, causing Kafka to treat the connector as a new consumer group and potentially replay already processed messages. - **Schema strategy**: Select the schema strategy for your messages. - Select **No schema**, **Inline schema** for Avro serialization. - Select **Schema registry** If you have a Confluent Schema Registry. - **Topic**: Set the topic name. - **Additional properties**: Set consumer configuration values. - **Offsets**: Set the offsets for the partition. The number of offsets specified should match the number of partitions on the current topic. - **Auto offset reset**: Set the strategy to use when there is no initial offset in Kafka or if the specified offsets do not exist on the server. :::info The [appendix](#appendix-and-faq-1) provides more information about [pre-configured consumer configuration values](#what-are-default-kafka-consumer-client-properties) for this connector. Additionally, to learn more about supported consumer configurations, see the [official Kafka documentation](https://kafka.apache.org/41/configuration/consumer-configs/). ::: ### Modify an existing inbound Kafka connector Editing the **Consumer Group ID** or **Offsets** properties on an already deployed inbound Kafka connector element can cause unexpected behavior. Delete the element and create a new one instead of editing these properties in place. - **Consumer Group ID**: If you clear this field on an existing element, the connector generates a new ID from its internal deduplication key instead of reusing the previous one. Kafka then treats the connector as a new consumer group, which can cause it to replay already processed messages. - **Offsets**: If the number of offsets you provide does not match the topic's actual partition count, activation fails with an error. This is one example of a general pattern for inbound connectors. See [modify an existing inbound connector element](../advanced-topics/inbound-lifecycle.md#modify-an-existing-inbound-connector-element) for more details. ### Schema strategy #### No schema Select **No schema** to send messages without a schema. This option is suitable for simple messages that don’t require a schema. #### Inline schema Select **Inline schema** to send messages with an **Avro schema**. - This option is appropriate for messages that require a schema but are not (or do not need to be) registered in a schema registry. - Enter the Avro schema that defines the message structure into the **Schema** field in the **Message** section. #### Schema registry Select **Schema registry** to send messages using a schema registered in a schema registry. - This option is appropriate for messages that require a schema and are registered in a [schema registry](https://docs.confluent.io/platform/current/schema-registry/index.html). - You must provide: - The **schema registry URL** in the **Kafka** section. - The **schema** itself (defining the message structure) in the **Message** section. - The **credentials** for the schema registry, if required. See the [Schema Registry documentation](https://docs.confluent.io/platform/current/schema-registry/sr-client-configs.html#basic-auth-credentials-source) for more details. :::info Currently, the Kafka connector supports only the [Confluent Schema Registry](https://docs.confluent.io/platform/current/schema-registry/index.html). Other schema registry implementations are not supported at this time. Schema configuration is required only for the outbound connector. It is not required when using Inbound Connectors. ::: ### Example Avro schema and data If the expected Kafka message looks like this: - **Key**: `employee1` - **Value**: ```json { "name": "John Doe", "age": 29, "emails": ["johndoe@example.com"] } ``` The corresponding Avro schema to describe this message's structure would be: ```json { "doc": "Sample schema to help you get started.", "fields": [ { "name": "name", "type": "string" }, { "name": "age", "type": "int" }, { "name": "emails", "type": { "items": "string", "type": "array" } } ], "name": "sampleRecord", "namespace": "com.mycorp.mynamespace", "type": "record" } ``` This schema defines a structure for a record that includes a name (string), an age (integer), and emails (an array of strings), aligning with the given Kafka message's value format. ### Activation condition **Activation condition** is an optional FEEL expression field that allows for the fine-tuning of the connector activation. This condition filters if the process step triggers when a Kafka message is consumed. For example, `=(value.itemId = "a4f6j2")` only triggers the start event or continues the catch event if the Kafka message has a matching itemId in the incoming message payload. Leave this field empty to trigger your process every time. :::danger By default, this connector does not commit the offset if the message cannot be processed. This includes cases where the activation condition is not met. This means that if there is a message in the topic that cannot be processed due to an activation condition mismatch, the Kafka subscription will be stopped. Follow the steps below to configure this behavior. ::: To ignore messages that do not meet the activation condition and commit the offset, select the **Consume unmatched events** checkbox. | **Consume unmatched events** checkbox | Activation condition | Outcome | | ------------------------------------- | -------------------- | ---------------------------------------------------- | | Checked | Matched | connector is triggered, offsets are commited | | Unchecked | Matched | connector is triggered, offsets are commited | | Checked | Unmatched | connector is not triggered, offsets are commited | | Unchecked | Unmatched | connector is not triggered, offsets are not commited | #### Upgrade from a version without the Consume unmatched events checkbox If your inbound Kafka connector element was deployed before the **Consume unmatched events** checkbox existed, the underlying property is absent from that element and defaults to unchecked. Upgrading the runtime does not change this default, so the element keeps its original behavior: it does not commit the offset when a message does not match the activation condition. To adopt the checked behavior on an existing element, update its element template and redeploy it. Creating a new element with a current template also picks up the new default of checked. This is one example of a general pattern for inbound connectors. See [modify an existing inbound connector element](../advanced-topics/inbound-lifecycle.md#modify-an-existing-inbound-connector-element) for more details. ### Correlation The **Correlation** section allows you to configure the message correlation parameters. :::note The **Correlation** section is not applicable for the plain **start event** element template of the Kafka connector. Plain **start events** are triggered by process instance creation and do not rely on message correlation. ::: #### Correlation key - **Correlation key (process)** is a FEEL expression that defines the correlation key for the subscription. This corresponds to the **Correlation key** property of a regular **message intermediate catch event**. - **Correlation key (payload)** is a FEEL expression used to extract the correlation key from the incoming message. This expression is evaluated in the connector Runtime and the result is used to correlate the message. For example, given that your correlation key is defined with `myCorrelationKey` process variable, and the incoming Kafka message contains `value:{correlationKey:myValue}`, your correlation key settings would be as follows: - **Correlation key (process)**: `=myCorrelationKey` - **Correlation key (payload)**: `=value.correlationKey` You can also use the key of the message to accomplish this in the **Correlation key (payload)** field with `=key`. :::info To learn more about correlation keys, see [messages](../../../concepts/messages). ::: #### Message ID expression The **Message ID expression** is an optional field that allows you to extract the message ID from the incoming message. The message ID serves as a unique identifier for the message and is used for message correlation. This expression is evaluated in the connector Runtime and the result is used to correlate the message. In most cases, it is not necessary to configure the **Message ID expression**. However, it is useful if you want to ensure message deduplication or achieve a certain message correlation behavior. :::info To learn more about how message IDs influence message correlation, see [messages](../../../concepts/messages#message-correlation-overview). ::: For example, if you want to set the message ID to the value of the `transactionId` field in the incoming message, you can configure the **Message ID expression** as follows: ``` = value.transactionId ``` #### Message TTL The **Message TTL** is an optional field that allows you to set the time-to-live (TTL) for the correlated messages. TTL defines the time for which the message is buffered in Zeebe before being correlated to the process instance (if it can't be correlated immediately). The value is specified as an ISO 8601 duration. For example, `PT1H` sets the TTL to one hour. Learn more about the TTL concept in Zeebe in the [message correlation guide](../../../concepts/messages#message-buffering). ### Deduplication The **Deduplication** section allows you to configure the connector deduplication parameters. **Connector deduplication** is a mechanism in the connector Runtime that determines how many Kafka subscriptions are created if there are multiple occurrences of the **Kafka Consumer connector** in the BPMN diagram. This is not to be confused with **message deduplication**. By default, the connector runtime deduplicates connectors based on properties, so that elements with the same subscription properties only result in one subscription. :::info To learn more about deduplication, see [deduplication](../advanced-topics/deduplication.md). ::: To customize the deduplication behavior, select the **Manual mode** checkbox, and configure the custom deduplication ID. ### Output mapping The **Kafka Consumer connector** returns the consumed message. The following fields are available in the `response` variable: - `key`: The key of the message. - `value`: The value of the message. - `rawValue`: The value of the message as a JSON string. You can use an output mapping to map the response: 1. Use **Result variable** to store the response in a process variable. For example, `myResultVariable`. 2. Use **Result expression** to map fields from the response into process variables. For example: ``` = { "itemId": value.itemId } ``` ## Activate the Kafka Consumer connector by deploying your diagram When you click the **Deploy** button, your Kafka Consumer is activated and starts consuming messages from the specified topic. ## Appendix and FAQ ### What mechanism is used to authenticate against Kafka? If you selected _Credentials_ as the **Authentication type** and the fields **Username** and **Password** are not empty, by default the **Kafka Consumer connector** enables the credentials-based SASL SSL authentication, and sets the following properties: ``` sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username='' password=''; security.protocol=SASL_SSL sasl.mechanism=PLAIN ``` If any of the field is not populated, you must configure your security method for your Kafka configuration. You can do this using the **Additional properties** field. ### What are default Kafka Consumer client properties? - Authentication properties (only if both **Username** and **Password** are not empty): ``` sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username='' password=''; security.protocol=SASL_SSL sasl.mechanism=PLAIN ``` - Bootstrap server property: ``` bootstrap.servers= ``` - Message properties: ``` key.deserializer=org.apache.kafka.common.serialization.StringDeserializer value.deserializer=org.apache.kafka.common.serialization.StringDeserializer ``` - Miscellaneous properties: ``` session.timeout.ms=45000 client.dns.lookup=use_all_dns_ips acks=all group.id=kafka-inbound-connector-{{bpmnProcessId}} enable.auto.commit=false ``` :::caution The `group.id` value above is auto-generated when no explicit **Consumer Group ID** is configured in the connector. This generated ID is derived from the connector's internal deduplication key and can change across connector upgrades, including from 8.8 to 8.9. When the group ID changes, Kafka treats the connector as a new consumer group, which means committed offsets are not reused and messages may be replayed. To avoid this, always set an explicit **Consumer Group ID**. You can [look up existing consumer groups](https://docs.confluent.io/kafka/operations-tools/manage-consumer-groups.html#list-groups-and-view-offsets) to find the current group ID in use. ::: ### What is the precedence of client properties loading? Properties loading consists of three steps: 1. Construct client properties from the BPMN diagram: authentication, bootstrap server, message properties. If selected, the **Connection credential** supplies authentication and bootstrap servers instead of the inline fields. 2. Load miscellaneous properties. 3. Load and **override** properties from the field **Additional properties**. ### How is the message payload deserialized? As Kafka messages usually use JSON format, we first try to deserialize it as a `JsonElement`. If this fails (for example, because of a wrong format) we use the `String` representation of the original raw value. For convenience, we always store the original raw value as `String` in a different attribute. The deserialized object structure: ``` { key: "String" rawValue: "String" value: {} } ``` ### When is the offset committed? What happens if the connector execution fails? The following outcomes are possible: - If the connector execution is successful and the **Activation condition** was met, the offset is committed. - If the **Activation condition** was not met, the offset is also committed to prevent consuming the same message twice. - If the connector execution fails due to an unexpected error (for example, Zeebe is unavailable), the offset is not committed. ### What lifecycle does the Kafka Consumer connector have? The Kafka Consumer connector is a long-running connector that is activated when the process is deployed, and deactivated when the process is undeployed or overwritten by a new version. --- ## Message Send connector The Message Send connector can **publish** or **correlate** BPMN messages. It either calls the [PublishMessage RPC](/apis-tools/zeebe-api/gateway-service.md#publishmessage-rpc) of the Zeebe API to send messages buffered by Zeebe, or the [Correlate a message REST API](/apis-tools/orchestration-cluster-api-rest/specifications/correlate-message.api.mdx) to get the ID of the process instance that received the message without buffering. The element template allows you to select the mode: - `publish message (with buffer)` - `correlate message (with result)` and fill all parameters directly in the modeler. ## Publish message (buffered) parameters | Parameter | Type | Description | Default / Optional | | --------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------ | | Message name | string | Name of the message. Must match a receiving message event. | Required | | Correlation key | string | Value for correlating with a process instance. Can be empty to start a new instance. | Optional | | Payload | JSON | Variables to transfer to the receiving process instance. Example: `{"customerName": cust_name}`. Multiple variables can be defined. | Required | | Time to live | int | Time in milliseconds to buffer the message. | No buffer | | Message ID | string | Unique message ID for idempotency. Ensures the message is delivered only once until correlated. | Optional | | Tenant ID | string | Tenant ID of the receiving instance. | `` | | Request timeout | int | Timeout for the publish message command. | Connector default | ### Response | Field | Type | Description | | ---------- | ------ | ---------------------------------- | | messageKey | int64 | Unique ID of the published message | | tenantId | string | Tenant ID of the message | ## Correlate message (with result) parameters | Parameter | Type | Description | Default / Optional | | --------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------ | | Message name | string | Name of the message. Must match a receiving message event. | Required | | Correlation key | string | Value for correlating with a process instance. Can be empty to start a new instance. | Optional | | Payload | JSON | Variables to transfer to the receiving process instance. Example: `{"customerName": cust_name}`. Multiple variables can be defined. | Required | | Tenant ID | string | Tenant ID of the receiving instance. | `` | | Request timeout | int | Timeout for the correlate message command. | Connector default | ### Response | Field | Type | Description | | ------------------ | ------ | ------------------------------------------------------------- | | messageKey | int64 | Unique ID of the correlated message | | processInstanceKey | int64 | Key of the first process instance the message correlated with | | tenantId | string | Tenant ID of the message | > The connector raises an incident with a detailed error message containing the 404 status `Not found` if the message could not be correlated. --- ## Microsoft 365 email inbound connector The **Microsoft 365 Email Inbound connector** allows you to consume emails by monitoring [Microsoft 365](https://outlook.office.com/mail/) mailboxes and mapping them to your BPMN processes as start or intermediate events. ## Prerequisites - A [Microsoft 365](https://outlook.office.com/mail/) mailbox to monitor. - Sufficient access rights at [Microsoft Entra](https://entra.microsoft.com) to create a new app and set [Microsoft Graph](https://developer.microsoft.com/en-us/graph) permissions. - Required Microsoft Graph API permissions for your application: - `Mail.Read` - Read emails from mailboxes. - `Mail.ReadWrite` - Read and modify emails (required for operations like mark as read, move, delete). Learn more about [creating, configuring, and authorizing Microsoft Apps](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app). :::note Use secrets to avoid exposing your Microsoft credentials as plain text. Refer to our documentation on [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ::: ## Security considerations ### Authentication security The connector supports multiple authentication methods. For server-to-server scenarios without user interaction, **Client credentials** is recommended. For delegated (user-context) scenarios, use **Refresh token**. **Bearer token** is available but not recommended for polling connectors due to short token lifetimes. All authentication methods provide: - **Scoped permissions**: API permissions are explicitly granted to the application. - **Auditable access**: All API calls are associated with the registered application. ### Restricting mailbox access with RBAC :::warning By default, an Azure AD application with `Mail.Read` or `Mail.ReadWrite` permissions can access _all mailboxes_ in your organization. In the OAuth 2.0 client credentials flow, the application gets access to all mailboxes it has been granted permissions for. To restrict access to specific mailboxes, use _Role-Based Access Control (RBAC) for Applications_. Learn more about [scoping application permissions to specific Exchange Online mailboxes](https://learn.microsoft.com/en-us/graph/auth-limit-mailbox-access). ::: ## Configure the Microsoft 365 Outlook connector To use the Microsoft 365 Email Inbound connector, you must register an application in Microsoft Entra (formerly Azure AD) and configure the required permissions. :::note This is a simplified guide to help you get started. For the full guide, refer to the [official Microsoft documentation](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app). ::: ### Register an application in Microsoft Entra 1. Sign in to the [Microsoft Entra admin center](https://entra.microsoft.com). 2. Navigate to **Identity** > **Applications** > **App registrations**. 3. Click **New registration**. 4. Enter a name for your application (for example, `Camunda Email Connector`). 5. Select **Accounts in this organizational directory only** for supported account types. 6. Click **Register**. ### Configure API permissions 1. In your registered application, navigate to **API permissions**. 2. Click **Add a permission**. 3. Select **Microsoft Graph** > **Application permissions**. 4. Search for and add the following permissions: - `Mail.Read` - Required for reading emails. - `Mail.ReadWrite` - Required for operations like mark as read, move, or delete emails. 5. Click **Add permissions**. 6. Click **Grant admin consent** for your organization. This requires administrator privileges. :::warning The **Grant admin consent** step is critical. Without admin consent, the application cannot access mailbox data. ::: ## Authentication Choose an authentication type in the **Authentication** section of the connector properties panel. The connector uses the [Microsoft Graph API](https://learn.microsoft.com/en-us/graph/overview) and supports the following authentication methods. Visit the [Microsoft Graph auth overview](https://learn.microsoft.com/en-us/graph/auth/) for more information. :::note Choosing an auth type for email polling - **Client credentials** (recommended): Uses Azure SDK's automatic token renewal. Best suited for long-running polling connectors. - **Refresh token**: The connector re-exchanges the refresh token for a fresh access token on every poll cycle, ensuring continuous access. Suitable for delegated (user-context) scenarios. - **Bearer token**: The client is created once with the provided token. Bearer tokens are short-lived (typically 1 hour) and **not recommended for polling connectors** — the token will expire and the connector will stop working. ::: ## Create a Microsoft 365 email inbound connector event 1. Add a **Start Event** or an **Intermediate Catch Event** to your BPMN diagram. 2. Change its template to **Microsoft 365 Email Inbound**. 3. Fill in all required properties. 4. Complete your BPMN diagram. 5. Deploy the diagram to activate the email monitoring. ## Configure your Microsoft 365 email inbound connector To make your **Microsoft 365 Email Inbound connector** executable, fill in the required properties. ### Mailbox configuration In the **Mailbox** section, configure which mailbox and folder to monitor: - **User ID/User Principal Name**: The email address or user principal name of the mailbox to monitor (for example, `user@company.com`). - **Folder Identifier Type**: Select how to identify the folder to monitor: - **Folder ID** (default): Use a [well-known folder ID](https://learn.microsoft.com/en-us/graph/api/resources/mailfolder?view=graph-rest-1.0#properties) such as `inbox`, `drafts`, or `sentitems`, or a custom folder ID. Prefer this option when using well-known folders. - **Folder Name**: Use the display name of the folder. The name must be unique within the mailbox. Use this option for custom folders where you don't know the folder ID. ### Polling configuration Configure how frequently the connector checks for new emails using the **Polling Interval** field. Specify the time between polls in ISO 8601 duration format (for example, `PT30S` for 30 seconds, `PT5M` for 5 minutes). Review [how to configure a time duration](/components/modeler/bpmn/timer-events/timer-events.md#time-duration) for details on the format. :::tip Choose an appropriate polling interval based on your use case. More frequent polling increases API usage but provides faster response times. ::: ### Email filtering Optionally filter which emails trigger the process: #### Simple filter The simple filter provides the following options: - **Only Unread**: When enabled (default), only unread emails (`isRead eq false`) will trigger the process. When disabled, all emails (both read and unread) will trigger the process. - **Subject Contains** (optional): Only fetch emails where the subject contains this text (case-sensitive) - **From Email Address** (optional): Only fetch emails from this sender address (exact match, for example, `invoice@vendor.com`) #### Advanced filter For more complex filtering, use the **Advanced Configuration** section to provide an [OData filter query](https://learn.microsoft.com/en-us/graph/query-parameters#filter-parameter). :::note OData filter queries are evaluated at deployment time and can only use static values or [secrets](/components/saas/clusters/manage-secrets.md). They cannot reference process variables. If you need dynamic filtering based on runtime data, use the [Activation Condition](#activation-condition) section instead. ::: Examples of filters not supported by the simple filter: Filter emails from a specific domain: ``` endswith(from/emailAddress/address, '@example.com') and isRead eq false ``` Filter emails whose body contains specific text: ``` contains(body/content, 'password') and isRead eq false ``` ### Email processing operations Configure what happens to emails after they trigger a process. **At least one operation must be configured.** - **Mark as read**: Mark the email as read after processing - **Delete**: Delete the email after processing - **Force delete**: Bypass the deleted items folder and permanently delete - **Move to folder**: Move the email to a different folder after processing. Select a **Folder Identifier Type** (folder ID or folder name) and specify the destination folder. :::warning Avoid Infinite Reprocessing Loops Ensure your processing operation removes emails from matching your filter criteria. If emails continue to match the filter after processing, they will be reprocessed on every polling cycle, potentially creating multiple process instances for the same email. **Examples of dangerous configurations:** - Disabling **Only Unread** filter (fetching all emails) and only using **Mark as read** (emails remain in the monitored folder and continue matching the filter) - Moving emails to a different folder and then having another process or rule move them back to the monitored folder (they will match the filter again) ::: ### Activation condition **Activation condition** is an optional FEEL expression field that allows for fine-tuning of connector activation. This condition filters whether the process step triggers when an email is consumed. For example, `=(subject = "Order Confirmation")` only triggers the start event or continues the catch event if the email subject matches exactly. Leave this field empty to trigger your process for every email that matches the filtering criteria. :::note The activation condition is evaluated after the email filter. Use email filters for simple conditions and activation conditions for complex FEEL-based logic. ::: When an email matches the filter but does not meet the activation condition, the connector does not trigger and the email is not processed (not marked as read, deleted, or moved). This means the email will be fetched again on the next polling cycle. To change this behavior, enable the **Consume unmatched events** checkbox in the **Activation** section. Learn more about [consuming unmatched events](../use-connectors/inbound.md#consume-unmatched-events). ### Correlation The **Correlation** section allows you to configure message correlation parameters. :::note The **Correlation** section is not applicable for the plain **start event** element template of the Microsoft 365 Email Inbound connector. Plain **start events** are triggered by process instance creation and do not rely on message correlation. ::: #### Correlation key - **Correlation key (process)** is a FEEL expression that defines the correlation key for the subscription. This corresponds to the **Correlation key** property of a regular **message intermediate catch event**. - **Correlation key (payload)** is a FEEL expression used to extract the correlation key from the incoming email. This expression is evaluated in the connector runtime and the result is used to correlate the message. For example, if your correlation key is defined with the `orderId` process variable, and the incoming email subject contains `Order #12345`, you could extract the order ID from the subject: - **Correlation key (process)**: `=orderId` - **Correlation key (payload)**: `=substring(subject, 7)` (extracts "12345" from "Order #12345") :::note To learn more about correlation keys, see [messages](../../../concepts/messages). ::: #### Message ID expression The **Message ID expression** is an optional field that allows you to extract the message ID from the incoming email. The message ID serves as a unique identifier and is used for message deduplication. By default, the connector uses the email's unique identifier from Microsoft Graph. However, you can customize this if needed: ```feel = id ``` :::note To learn more about how message IDs influence message correlation, see [messages](../../../concepts/messages#message-correlation-overview). ::: #### Message TTL The **Message TTL** is an optional field that allows you to set the time-to-live (TTL) for correlated messages. TTL defines the time for which the message is buffered in Zeebe before being correlated to the process instance. The value is specified as an ISO 8601 duration. For example, `PT1H` sets the TTL to one hour. Learn more about the TTL concept in Zeebe in the [message correlation guide](../../../concepts/messages#message-buffering). ### Deduplication The **Deduplication** section allows you to configure connector deduplication parameters. **Connector deduplication** is a mechanism in the connector runtime that determines how many email subscriptions are created if there are multiple occurrences of the **Microsoft 365 Email Inbound connector** in the BPMN diagram. By default, the connector runtime deduplicates connectors based on properties, so elements with the same subscription properties only result in one subscription. :::note To learn more about deduplication, see [deduplication](../use-connectors/inbound.md#connector-deduplication). ::: To customize the deduplication behavior, select the **Manual mode** checkbox and configure a custom deduplication ID. ### Output mapping The **Microsoft 365 Email Inbound connector** returns the consumed email message with the following structure: ```json { "id": "AAMkAGVmMDEzM...", "conversationId": "AAQkAGVmMDEzM...", "subject": "Invoice #12345", "body": "Please find attached...", "bodyContentType": "text/plain", "sender": { "name": "John Doe", "address": "john.doe@example.com" }, "recipients": [ { "name": "Jane Smith", "address": "jane.smith@company.com" } ], "cc": [], "bcc": [], "receivedDateTime": "2024-01-15T14:30:00Z", "attachments": [ { "id": "AAMkAGVmMDEzM...", "name": "invoice.pdf", "contentType": "application/pdf", "size": 125000 } ] } ``` You can use an output mapping to map the response: 1. Use **Result variable** to store the response in a process variable. For example, `emailMessage`. 2. Use **Result expression** to map fields from the response into process variables. For example: ```feel = { "emailSubject": response.subject, "senderEmail": response.sender.address, "receivedAt": response.receivedDateTime, "hasAttachments": count(response.attachments) > 0 } ``` ## Activate the Microsoft 365 email inbound connector by deploying your diagram When you click the **Deploy** button, your Microsoft 365 Email Inbound connector is activated and starts monitoring the specified mailbox for new emails. The connector is a long-running connector that remains active until the process is undeployed or overwritten by a new version. ## Next steps - Learn more about [Microsoft Graph Mail API](https://learn.microsoft.com/en-us/graph/api/resources/mail-api-overview) and its capabilities. - Explore [configuring application permissions to specific mailboxes](https://learn.microsoft.com/en-us/graph/auth-limit-mailbox-access) for enhanced security. - Learn about [other connectors available](./available-connectors-overview.md) in Camunda to integrate with different systems and services. - Learn more about [using connectors](../use-connectors/index.md). - Learn more about [inbound connectors](../use-connectors/inbound.md). ## Appendix and FAQ ### What happens if email processing fails? If the connector fails to process an email (for example, due to Zeebe being unavailable), the email remains unprocessed and the connector will attempt to process it again on the next polling cycle. If you configured a processing operation (mark as read, delete, move), the operation will only be executed after successful process correlation. ### Can I monitor multiple folders? To monitor multiple folders, create separate connector instances with different folder configurations. You can reuse the same authentication credentials across multiple instances. ### How are email attachments handled? Email attachments are automatically fetched and stored using [Camunda document handling](/components/document-handling/getting-started.md). The attachment metadata is included in the connector output (see [Output Mapping](#output-mapping)), and each attachment is available as a document reference that you can use in subsequent process steps. For example, to pass an attachment to another connector or download it, use the document reference from the `attachments` array: ```feel = { "firstAttachment": response.attachments[1], "allAttachments": response.attachments } ``` Learn more about working with documents in [document handling](/components/document-handling/getting-started.md). ### What lifecycle does the Microsoft 365 Email Inbound connector have? The Microsoft 365 Email Inbound connector is a long-running connector that is activated when the process is deployed, and deactivated when the process is undeployed or overwritten by a new version. --- ## Microsoft 365 connector The Microsoft 365 Connector is an outbound connector that allows you to connect your BPMN service with [Microsoft 365](https://outlook.office.com/mail/) mail to send emails, read emails, and manage folders. ## Prerequisites - A [Microsoft 365](https://outlook.office.com/mail/) mail instance. - Sufficient permissions in [Microsoft Entra](https://entra.microsoft.com) to: - Create a new app registration - Configure [Microsoft Graph](https://developer.microsoft.com/en-us/graph) permissions - Assign the app to a user Learn more about [creating, configuring, and authorizing a Microsoft app](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app). :::note Use secrets to avoid exposing your Microsoft credentials as plain text. Refer to our documentation on [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ::: ## Create a Microsoft 365 connector task ## Access control Each operation requires permissions to be assigned by a system administrator. Learn more about [Microsoft permissions](https://learn.microsoft.com/en-us/entra/identity-platform/permissions-consent-overview). :::warning By default, an application with Mail API permissions can access _all mailboxes_ in your organization. To restrict access to specific mailboxes, use _Role-Based Access Control (RBAC) for Applications_. Learn more about [scoping application permissions to specific Exchange Online mailboxes](https://learn.microsoft.com/en-us/graph/auth-limit-mailbox-access). ::: ### Bearer token authentication If you own a bearer token, in the **Authentication** section select **Bearer token** in the **Type** field. Enter a bearer token in the field **Bearer token**. Use [secrets](/components/saas/clusters/manage-secrets.md) to avoid exposing sensitive credentials. :::note The default time-to-live (TTL) for bearer tokens is 3600 seconds. Therefore, this approach might not work for long-living and/or repetitive processes. ::: ### OAuth2 client credentials flow authentication :::note In the client credential flow, an application gets access to all accounts associated with the organization. For example, if an app has permissions `Mail.Read`, it will be able to read emails of all users. ::: To proceed with this step, you'll need the following data: - OAuth 2.0 token endpoint - Client ID (Application ID) - Client secret; can be created on your application page The app must be assigned to a user. If you own a bearer token, in the **Authentication** section select **OAuth 2.0** in the **Type** field. Enter the above data into the respective fields. Learn more about [creating, configuring, and authorizing Microsoft App](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app). ## Select operation to execute Select the desired operation from the **Operations** section. ### Get user folders Related Microsoft Graph API: [user: list mailFolders](https://learn.microsoft.com/en-us/graph/api/user-list-mailfolders) 1. Enter user's email or system UUID to fetch all their folders in the **User ID** field. 2. You can also pass [OData parameters](https://learn.microsoft.com/en-us/graph/query-parameters?tabs=http) in the **Query parameters** field. For example, if you wish to pass an OData `$top` URL parameter, execute the following: ```json { "$top": 10 } ``` ### Create mail folder for a user Related Microsoft Graph API: [user: create mailFolder](https://learn.microsoft.com/en-us/graph/api/user-post-mailfolders) 1. Enter user's email or system UUID to fetch all their folders in the **User ID** field. 2. In the **Request** section, enter a **Folder display name** string value. ### Get user messages Related Microsoft Graph API: [user: list messages](https://learn.microsoft.com/en-us/graph/api/user-list-messages) 1. Enter user's email or system UUID to fetch all their folders in the **User ID** field. 2. You can also pass [OData parameters](https://learn.microsoft.com/en-us/graph/query-parameters?tabs=http) in the **Query parameters** field. For example, if you wish to pass an OData `$top` URL parameter, execute the following: ```json { "$top": 10 } ``` ### Send mail on behalf of a user Related Microsoft Graph API: [user: sendMail](https://learn.microsoft.com/en-us/graph/api/user-sendmail) 1. Enter user's email or system UUID to fetch all their folders in the **User ID** field. 2. In the **Request** section, enter a **Subject** string value. 3. Select **Body content type** from the dropdown. 4. Enter desired content in the field **Body content**. 5. Pass an array of emails into the **To recipients** field, for example `["myuser1@mycompany.com", "myuser2@mycompany.com"]`. 6. (Optional) Pass an array of emails into the **CC recipients** field, for example `["myuser3@mycompany.com", "myuser4@mycompany.com"]`. ## Handle connector response The Microsoft 365 Connector is a protocol connector, meaning it is built on top of the **HTTP REST connector**, therefore [handling response is still applicable](/components/connectors/protocol/rest.md#response). --- ## Microsoft Teams connector The **Microsoft Teams connector** is an outbound connector that allows you to connect your BPMN process with [Microsoft Teams](https://www.microsoft.com/microsoft-teams/) to manage interactions. :::note If your organization uses [Camunda app integrations](/components/camunda-integrations/app-integrations/app-integrations.md), the [App Integrations connector](/components/connectors/out-of-the-box-connectors/app-integrations.md) sends through the app your users already have connected, and carries no credentials in the process model. ::: ## Prerequisites To use the **Microsoft Teams connector**, you need to have a [Microsoft Teams](https://www.microsoft.com/microsoft-teams/) account and relevant [permissions](https://support.microsoft.com/en-us/office/manage-team-settings-92d238e6-0ae2-447e-af90-40b1052c4547) or the registered application in the [Azure Active Directory](https://aad.portal.azure.com/) (visit [how to register the app](https://learn.microsoft.com/en-us/graph/auth-register-app-v2) for more information) alongside the relevant [Microsoft Graph API permissions](https://learn.microsoft.com/en-us/graph/permissions-reference). :::note Use secrets to store credentials so you don't expose sensitive information directly from the process. See [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ::: ## Create a Microsoft Teams connector task ## Make your Microsoft Teams connector executable To work with Microsoft Teams, choose the required connection type in the **Authentication** section and complete the mandatory fields highlighted in red in the connector properties panel on the right side of the screen. :::note All the mandatory and non-mandatory fields depending on the authentication selection you choose are covered in the upcoming sections. ::: ## Authentication Choose an authentication type according to your requirements. The Microsoft Teams connector uses the [Microsoft Graph API](https://learn.microsoft.com/en-us/graph/overview). Visit the [Microsoft Graph auth overview](https://learn.microsoft.com/en-us/graph/auth/) for more information on the Microsoft Graph API authentication. You must have a user account with Microsoft Teams with the necessary permissions. See more at [Microsoft Teams overview](https://learn.microsoft.com/microsoftteams/teams-overview). If you don't have administration roles and permissions, ask your Microsoft Teams administrator to add required permissions to work with the **Microsoft Teams connector**. :::note With **Client credentials** type authentication, some methods of the **Microsoft Teams connector** may not be available. Find more details in the [chat methods table](#chat-methods) and [channel methods table](#channel-methods). ::: ## Conversation type and method In the **Operation** section, choose a conversation type of either **Chat** or **Channel**. Then, choose one of the suggested methods. For example, if you want to send a message in a Microsoft Teams channel, choose the conversation type **Channel** and method **Send message in channel**. ## Data section ### Chat conversation type #### Properties | Property | Methods | Required | Type | Description | | :-------------: | :---------------------------------------------------------------------------------------------------------------------------------------------: | :------: | :-------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | | Chat ID | Get chat by ID List chat members Send message in chat List messages in chat Get message in chat List chat members | Yes | string | Microsoft Teams chat ID | | Content | Send message in chat | Yes | text | Content that will be sent to chat | | Content Type | Send message in chat | Yes | dropdown | Content type of body message | | Chat type | Create a new chat | Yes | dropdown | Click **one on one** to create a one-on-one chat or **group** to create a group chat. | | Topic | Create a new chat | No | string | Topic of chat | | Members | Create a new chat | Yes | FEEL expression | See [members property](#members-property) to learn more. | | Top | List messages in chat | No | numbers | Controls the number of items per response; maximum allowed top value is 50. | | Order by | List messages in chat | Yes | dropdown | Can order by 'lastModifiedDateTime' and 'createdDateTime'. | | Expand response | Get chat by ID | Yes | dropdown | Choose | | Filter | List messages in chat | No | string | Sets the date range filter for the lastModifiedDateTime and createdDateTime properties. [Learn more about filtering](https://learn.microsoft.com/en-us/graph/filter-query-parameter). | | Message ID | Get message in chat | Yes | string | Microsoft Teams chat message ID | ##### Expand response For method **Get chat by ID**, you can get more information in the response by using the dropdown property **Expand response**. You can choose one of the following values: - select **With chat members**, to get information about chat members. - select **With last message preview**, to get last message in chat. **Note:** This function doesn't work with [client credentials type authentication](#client-credentials-type-authentication), make sure that you use another authentication type. - select **Without expand**, to get main information about chat. ##### Members property The **members** property must contain a list of members: | Property | Type | Required | | :---------------: | :----------: | -------------------------------------- | | userId | string | Yes, if 'userPrincipalName' is not set | | userPrincipalName | string | Yes, if 'userId' is not set | | roles | string array | Yes | ```json [ { "userId": "abc01234-0c7f-012c-9876-&812dsfw2", "roles": ["owner"] }, { "principalName": "john.dou@mail.com", "roles": ["owner"] } ] ``` #### Chat methods | Method | Use [protected APIs](https://learn.microsoft.com/en-us/graph/api/resources/teams-api-overview#teams-apis-that-require-rscs-permissions) | Available for [client credentials type authentication](#client-credentials-type-authentication) | Link to method documentation with required permissions and return value | | :-------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------: | | Create a new chat | false | true | [https://learn.microsoft.com/en-us/graph/api/chat-post](https://learn.microsoft.com/en-us/graph/api/chat-post?view=graph-rest-1.0&tabs=http) | | Get chat by ID | false | true | [https://learn.microsoft.com/en-us/graph/api/chat-get](https://learn.microsoft.com/en-us/graph/api/chat-get?view=graph-rest-1.0&tabs=http) | | List chats | false | true | [https://learn.microsoft.com/en-us/graph/api/chat-list](https://learn.microsoft.com/en-us/graph/api/chat-list?view=graph-rest-1.0&tabs=http) | | List chat members | false | false | [https://learn.microsoft.com/en-us/graph/api/chat-list-members](https://learn.microsoft.com/en-us/graph/api/chat-list-members?view=graph-rest-1.0&tabs=http) | | Send message in chat | false | false | [https://learn.microsoft.com/en-us/graph/api/chat-post-messages](https://learn.microsoft.com/en-us/graph/api/chat-post-messages?view=graph-rest-1.0&tabs=http) | | Get message in chat | false | true | [https://learn.microsoft.com/en-us/graph/api/chatmessage-get](https://learn.microsoft.com/en-us/graph/api/chatmessage-get?view=graph-rest-1.0&tabs=http) | | List messages in chat | true | true | [https://learn.microsoft.com/en-us/graph/api/chat-list-messages](https://learn.microsoft.com/en-us/graph/api/chat-list-messages?view=graph-rest-1.0&tabs=http) | ### Channel conversation type #### Properties | Property | Methods | Required | Type | Description | | :---------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: | :---------------: | :--------------------------------------------------------------------------------------------------------------------: | | Group ID | Create channel Get channel List channels Send message to channel Get channel message List channel messages List message replies List members | Yes | string | Microsoft Teams group ID | | Channel ID | Get channel List channels Send message to channel Get channel message List channel messages List message replies List members | Yes | string | Microsoft Teams channel ID | | Display name | Create channel | No | string | Displayed name of new Microsoft Teams channel | | Description | Create channel | No | text | Description of new Microsoft Teams channel | | Channel membership type | Create channel | Yes | dropdown | See [teams-channels-overview](https://learn.microsoft.com/microsoftteams/teams-channels-overview) for more information | | Owner | Create channel (if Channel membership type != STANDARD) | Yes | string | Channel owner; Microsoft Teams user ID or Microsoft Teams principal name | | Filter | List channels | No | string | The search filter. [Learn more about filtering](https://learn.microsoft.com/en-us/graph/filter-query-parameter) | | Content | Send message to channel | Yes | text | Content that will be sent to chat | | Content Type | Send message to channel | Yes | dropdown | Content type of body message | | Message ID | Get channel message | Yes | string | Message ID of Microsoft Teams in channel | | Top | List channel messages | No | numbers | Controls the number of items per response | | With replies | List channel messages | Yes | boolean | Choose **FALSE** for get messages without repliesChoose **FALSE** for get messages without replies | | Message ID | List message replies | Yes | string | Microsoft Teams channel message ID | | Documents | List of documents to attach to a message | No | List of documents | Microsoft Teams channel message ID | :::note Each document in the **Documents** field uses a [document source](/components/document-handling/send-document-to-external-system.md#document-sources): a **Camunda document** reference, **inline content** built from process data, or an **external document** URL. Use the **Single/Multiple** toggle to provide one document or a FEEL array of documents. ::: #### Channel methods | Method | Use [protected APIs](https://learn.microsoft.com/en-us/graph/api/resources/teams-api-overview#teams-apis-that-require-rscs-permissions) | Available for [client credentials type authentication](#client-credentials-type-authentication) | Link to method documentation with required permissions and return value | | :---------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Create channel | false | true | [https://learn.microsoft.com/en-us/graph/api/channel-post](https://learn.microsoft.com/en-us/graph/api/channel-post?view=graph-rest-1.0&tabs=http) | | Get channel | false | true | [https://learn.microsoft.com/en-us/graph/api/channel-get](https://learn.microsoft.com/en-us/graph/api/channel-get?view=graph-rest-1.0&tabs=http) | | List channels | false | true | [https://learn.microsoft.com/en-us/graph/api/channel-list](https://learn.microsoft.com/en-us/graph/api/channel-list?view=graph-rest-1.0&tabs=http) | | Send message to channel | false | false | [https://learn.microsoft.com/en-us/graph/api/channel-post-messages](https://learn.microsoft.com/en-us/graph/api/channel-post-messages?view=graph-rest-1.0&tabs=http) | | Get channel message | true | true | [https://learn.microsoft.com/en-us/graph/api/chatmessage-get](https://learn.microsoft.com/en-us/graph/api/chatmessage-get?view=graph-rest-1.0&tabs=http) | | List channel messages | true | true | [https://learn.microsoft.com/en-us/graph/api/channel-list-messages](https://learn.microsoft.com/en-us/graph/api/channel-list-messages?view=graph-rest-1.0&tabs=http) | | List message replies | true | true | [https://learn.microsoft.com/en-us/graph/api/chatmessage-list-replies](https://learn.microsoft.com/en-us/graph/api/chatmessage-list-replies?view=graph-rest-1.0&tabs=http) | | List members | false | true | [https://learn.microsoft.com/en-us/graph/api/channel-list-members](https://learn.microsoft.com/en-us/graph/api/channel-list-members?view=graph-rest-1.0&tabs=http) | ## Attachments The Microsoft Teams connector supports sending attachments, such as Adaptive Cards, when sending messages to channels or chats. With this feature, you can create rich, interactive messages with structured content. :::note Attachments are only available when the **Content Type** is set to **HTML**. They are not supported with the **Text** body type because Microsoft Teams cannot render attachment tags in plain text messages. When the body type is set to **Text**, the attachments field is hidden and unavailable. ::: ### Attachment fields Each attachment requires the following fields: - `id` (`string`): A unique identifier for the attachment, for example a UUID like `16677edaada34773b62bca2e77ba059b`. This ID is used to reference the card in the message body via `` HTML tags. - `contentType` (`string`): The media type of the attachment. The most common value is `application/vnd.microsoft.card.adaptive` for Adaptive Cards. Other supported Microsoft Bot Framework card content types include `application/vnd.microsoft.card.hero`, `application/vnd.microsoft.card.thumbnail`, and `application/vnd.microsoft.card.receipt`. - `content` (`string`): The JSON payload of the card, provided as a stringified JSON string. For Adaptive Cards, you can [design your card visually](https://adaptivecards.microsoft.com/designer), then copy the resulting JSON and stringify it by escaping quotes and removing newlines before pasting it into this field. If using a FEEL expression, the JSON object can be passed directly. ### Auto-appended attachment tags If you do not manually include `` tags in your HTML message body, the connector automatically appends them for each attachment. If the tags are already present in the body, the connector does not modify anything. ### Example The following example shows how to send a message with an Adaptive Card attachment: **Content (HTML body):** ```html Hello! Here is an important update: ``` **Attachments (FEEL expression):** ```feel = [ { "id": "16677edaada34773b62bca2e77ba059b", "contentType": "application/vnd.microsoft.card.adaptive", "content": "{\"type\":\"AdaptiveCard\",\"$schema\":\"http://adaptivecards.io/schemas/adaptive-card.json\",\"version\":\"1.5\",\"body\":[{\"type\":\"TextBlock\",\"text\":\"Hello from Camunda!\",\"weight\":\"Bolder\",\"size\":\"Large\"},{\"type\":\"TextBlock\",\"text\":\"This is an Adaptive Card sent via the Microsoft Teams connector.\",\"wrap\":true}],\"actions\":[{\"type\":\"Action.OpenUrl\",\"title\":\"Learn More\",\"url\":\"https://camunda.com\"}]}" } ] ``` **Attachments (stringified JSON for direct input):** ```json [ { "id": "16677edaada34773b62bca2e77ba059b", "contentType": "application/vnd.microsoft.card.adaptive", "content": "{\"type\":\"AdaptiveCard\",\"$schema\":\"http://adaptivecards.io/schemas/adaptive-card.json\",\"version\":\"1.5\",\"body\":[{\"type\":\"TextBlock\",\"text\":\"Hello from Camunda!\",\"weight\":\"Bolder\",\"size\":\"Large\"},{\"type\":\"TextBlock\",\"text\":\"This is an Adaptive Card sent via the Microsoft Teams connector.\",\"wrap\":true}],\"actions\":[{\"type\":\"Action.OpenUrl\",\"title\":\"Learn More\",\"url\":\"https://camunda.com\"}]}" } ] ``` :::tip To create an Adaptive Card, use the [Adaptive Cards Designer](https://adaptivecards.microsoft.com/designer) to visually build your card. Once complete, copy the JSON from the **Card Payload Editor** panel, then stringify it by escaping quotes and removing newlines before using it in the `content` field. ::: ## Microsoft Teams connector response The **Microsoft Teams connector** returns the Microsoft Graph API response in `result` wrapper: ```json { "result": { "chatType": "ONE_ON_ONE", "createdDateTime": { "dateTime": { "date": { "year": 2022, "month": 11, "day": 29 }, "time": { "hour": 18, "minute": 10, "second": 33, "nano": 361000000 } }, "offset": { "totalSeconds": 0 } }, "lastUpdatedDateTime": { "dateTime": { "date": { "year": 2022, "month": 11, "day": 29 }, "time": { "hour": 18, "minute": 10, "second": 33, "nano": 361000000 } }, "offset": { "totalSeconds": 0 } }, "tenantId": "0000000-0000-0000-0000-000000000", "webUrl": "https://teams.microsoft.com/l/chat/19%3Aefb08ac3-0000f-0000-0000-example-chat-id_fe35bf61-0000-0000-0000-ddc97d8903d4%40unq.gbl.spaces/0?tenantId=00000-0000-0000-0000-00000000", "id": "19%3Aefb08ac3-0000f-0000-0000-example-chat-id_fe35bf61-0000-0000-0000-ddc97d8903d4%40unq.gbl.spaces" } } ``` See [channel resource type](https://learn.microsoft.com/graph/api/resources/channel?view=graph-rest-1.0) to find the response for the required method for a channel conversation type, or see [chat resource type](https://learn.microsoft.com/graph/api/resources/chat?view=graph-rest-1.0) to find the response for the required method for a chat conversation type. You can use an output mapping to map the response: 1. Use **Result Variable** to store the response in a process variable. For example, `myResultVariable`. 2. Use **Result Expression** to map fields from the response into process variables. For example: ``` = { "chatId": result.id, "tenantId": result.tenantId } ``` --- ## OpenAI connector The **OpenAI connector** is an outbound connector that allows you to use [ChatGPT](https://platform.openai.com/docs/guides/chat/chat-completions-beta) or [Moderation API](https://platform.openai.com/docs/guides/moderation/moderation) in your BPMN process. ## Prerequisites To use the **OpenAI connector**, create an OpenAI account and create an API key. Refer to the [OpenAI Platform](https://platform.openai.com/docs/quickstart) documentation for a detailed setup guide. :::note Use secrets to avoid exposing your sensitive data, such as your OpenAI API key, as plain text. Follow our documentation on [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ::: ## Create an OpenAI connector task ## Make your OpenAI connector executable To work with the **OpenAI connector**, fill all mandatory fields. ## Authentication To use the **OpenAI connector**, obtain an API key from OpenAI. To create an OpenAI account and learn more about API keys, visit the [OpenAI Platform](https://platform.openai.com/) documentation. ### Create a new connector secret Keep your **API key** safe and avoid exposing it in the BPMN `xml` file by creating a secret: 1. Follow our [guide for creating secrets](/components/saas/clusters/manage-secrets.md). 2. Name your secret (i.e `OPENAI_API_KEY`) so you can reference it later in the connector. ### Configure the API key Select the **OpenAI API key** field in the **Authentication** section and set it to the secret you created (e.g. `{{secrets.OPENAI_API_TOKEN}}`). ## Operations The **OpenAI connector** currently supports two operation types in the **Operation** dropdown list: **Chat** and **Moderation**. ## Chat With the **Chat** operation, you can interact with OpenAI chat-based language models. ### Model The **Model** dropdown list allows you to select the model. Refer to the [Models](https://platform.openai.com/docs/models) section of OpenAI documentation for detailed information about models. #### Custom model version The **Model** dropdown list does not contain all available models. To use a model that is not listed, use the **Custom** option and provide the model name in the **Custom model version** field that appears. :::note Selection of models is user-specific and depends on your account privileges. For this reason, GPT-4 may appear as non-existing when you attempt to use it, although it is defined in the element template. ::: ### Temperature The **Temperature** field controls the randomness of the model's output. Lower temperatures make the model more deterministic and less random, while higher temperatures make the model more random. Accepted values are between `0` and `2` (add a leading `0` for values less than `1`), and you can use two digits after the decimal point. ### System message The **System message** field allows you to provide initial instructions for the model, and helps set the behavior of the assistant. For example, if you want ChatGPT to translate the prompt into a different language instead of interpreting the questions contained in the prompt, you can set the **System message** to `You are a translator bot. You provide literal translation of inputs from English into German. You do not interpret the input.` ### Chat history OpenAI API doesn't store message history for ChatGPT. Therefore, it is up to you as a process developer to decide if and how you should retain the chat history. The **Chat history** input field may contain the history of previous messages or examples of the desired behavior. Following the translation example above, you can provide some translation examples to make the expectations clearer. Chat history consumed by this connector follows the chat format described in the corresponding part of [OpenAI documentation](https://platform.openai.com/docs/guides/chat/introduction). ### Prompt While **System message** and **Chat history** fields are optional and provide the model with additional context, **Prompt** is the actual input. This is the query that is used to trigger the model output. To use the **System message**, **Chat history**, and **Prompt** together, you would follow this format: The example below illustrates how you can use **System message**, **Chat history**, and **Prompt** together. **System message** ```text You are a helpful assistant. ``` **Chat history** ``` = [ {"role": "user", "content": "Who won the world series in 2020?"}, {"role": "assistant", "content": "The Los Angeles Dodgers the World series in 2020."} ] ``` **Prompt** ```text Where was it played? ``` In this example, the chat history provides the context of a user asking who won the World Series in 2020, and the assistant providing the correct answer that the Los Angeles Dodgers won. The prompt, "Where was it played?" is the follow-up question that seeks additional information about the location where the World Series took place in 2020. :::note Find more complex examples of prompt engineering and sample real-life use cases of ChatGPT on the OpenAI [examples](https://platform.openai.com/examples) page. ::: ### Choices to generate The numeric **Choices to generate** field determines how many alternative answers the model returns in the API response. ### Sample chat output You can use an output mapping to map the response: 1. Use **Result Variable** to store the response in a process variable. For example, `myResultVariable`. 2. Use **Result Expression** to map fields from the response into process variables. ``` { "status": 200, "headers": { # response headers }, "body": { "id": "chatcmpl-6ws27w7nADFLWp7KD3dhjiUmP0kfu", "object": "chat.completion", "created": 1679488747, "model": "gpt-3.5-turbo-0301", "usage": { "prompt_tokens": 16, "completion_tokens": 79, "total_tokens": 95 }, "choices": [ { "message": { "role": "assistant", "content": "ChatGPT has gained significant attention in recent years, especially with the development and advancement of Natural Language Processing (NLP) tools used in chatbots and virtual assistants." }, "finish_reason": "stop", "index": 0 } ] } } ``` ## Moderation It is recommended to use the Moderation API to sanitize inputs and outputs of the language model. You will be able to prevent violation of OpenAI policies and displaying the potentially unsafe content in your system. ### Evaluation input ### Sample moderation output Output contains the evaluation result broken down by violation categories. To learn more about Moderation output, visit the [OpenAI documentation](https://platform.openai.com/docs/guides/moderation/moderation). ``` { "status": 200, "headers": { # response headers }, "body": { "id": "modr-6wtH8E1f2W533qdQAzq8dUpmKRVCV", "model": "text-moderation-004", "results": [ { "flagged": false, "categories": { "sexual": false, "hate": false, "violence": false, "self-harm": false, "sexual/minors": false, "hate/threatening": false, "violence/graphic": false }, "category_scores": { "sexual": 1.0084246241603978E-5, "hate": 5.5422344303224236E-5, "violence": 8.184280159184709E-5, "self-harm": 1.3117542607687938E-7, "sexual/minors": 4.457491709075612E-9, "hate/threatening": 9.144552337581047E-10, "violence/graphic": 1.770446012017146E-8 } } ] } } ``` --- ## Camunda Orchestration Cluster API connector The **Orchestration Cluster API connector** allows you to query data from the [Camunda 8 Orchestration Cluster REST API (v2)](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) in your BPMN process. This connector is compatible with both Camunda 8 SaaS and Camunda 8 Self-Managed deployments. :::note This connector replaces the deprecated Camunda Operate connector, which was compatible with Camunda 8.8 and earlier. ::: This connector is read-only by design. It only issues `GET` and `POST /search` requests; no state-changing operations (create, update, delete, cancel, migrate, modify, resolve, etc.) are exposed. If you need to trigger state-changing calls against the Orchestration Cluster API, use the [REST connector](../protocol/rest.md) instead. ## Prerequisites To use the **Orchestration Cluster API connector**, you need an active Camunda 8.9 or later cluster. This connector is compatible with both Camunda 8 SaaS and Camunda 8 Self-Managed. You also need OAuth 2.0 client credentials with permission to call the Orchestration Cluster API. Follow the links below to learn more about API client configuration. - [API client configuration in Camunda 8 SaaS](/components/saas/clusters/manage-api-clients.md) - [Token-based authentication in Camunda 8 Self-Managed](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md#using-a-token-oidcjwt) Basic authentication (username and password) is not supported. Only OAuth 2.0 client credentials are accepted. :::note Use secrets to store credentials so you don't expose sensitive information directly from the process. See [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ::: ## Create an Orchestration Cluster API connector task ## Enter your cluster information Choose between **Camunda SaaS** and **Camunda Self-managed** depending on your Camunda 8 installation type. The input fields will update accordingly. ### SaaS clusters If you are using a SaaS cluster, you will be required to provide your **Region** and **Cluster ID**. You will see these values when you [create an API client](/components/saas/clusters/manage-api-clients.md#create-a-client) for your cluster. ### Self-Managed clusters If you are using a Self-Managed cluster, you need to provide two URLs: - URL of your OAuth token endpoint - Base URL of the Orchestration Cluster REST API (must end with `/v2`) If you are testing this connector on your local machine with the Camunda 8 Docker Compose setup, set the following URLs: - OAuth Token endpoint: `http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token` - Base URL: `http://localhost:8080/v2` ## Configure authentication For both SaaS and Self-Managed clusters, you need to provide **Client ID** and **Client secret**. You will see these values when you [create an API client](/components/saas/clusters/manage-api-clients.md#create-a-client) for your cluster. For Self-Managed clusters, you can additionally specify: - **Audience**: The OAuth audience expected by your identity provider. Leave empty unless your identity provider requires a specific value. - **Scopes**: Space-separated OAuth 2.0 scopes. Required by some identity providers, for example Microsoft Entra ID, which requires `api:///.default`. ## Choose endpoint and operation In the **Entity** dropdown list, select the Orchestration Cluster API v2 entity you want to query. The following entities are available: - Process instances - Process definitions - Element instances - Incidents - Variables - User tasks - Jobs - Decision instances - Decision definitions - Decision requirements - Batch operations - Batch operation items - Message subscriptions - Correlated message subscriptions - Audit logs - Authorizations - Groups - Roles - Tenants - Mapping rules In the **Operation** dropdown list, select one of the supported operations: **Search** or **Get by key**. :::note Search-only entities **Batch operation items**, **Message subscriptions**, and **Correlated message subscriptions** only support the **Search** operation; **Get by key** is not available for these entities. ::: Refer to the [Orchestration Cluster API REST documentation](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) for the full list of endpoints, filter fields, and response shapes per entity. ## Configure operation parameters For **Get by key**, you must provide a single input, **Key / ID**: the numeric key (for example `processInstanceKey`, `incidentKey`, `userTaskKey`) or string identifier (for example `groupId`, `roleId`, `tenantId`) of the entity you want to retrieve. For **Search**, the following search parameters can be configured: - **Filter**: A FEEL context with per-entity filter fields. For example, the following filter returns active process instances for the `order-process` process definition: `{state: "ACTIVE", processDefinitionId: "order-process"}` Filters also support advanced operators (`$eq`, `$neq`, `$gt`, `$gte`, `$lt`, `$lte`, `$like`, `$in`, `$notIn`, `$exists`, `$or`). See [advanced search filters](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-data-fetching.md#advanced-search-filters) for details. - **Sort**: A list of sort objects. For example, the following sorts results by start date in ascending order: `[{field: "startDate", order: "ASC"}]` - **Limit**: The maximum number of results to return per page. Defaults to `100`; the maximum is `10000`. Leave empty to use the server default. - **Page after (forward cursor)**: Pass the `page.endCursor` value from the previous response to fetch the next page. - **Page before (backward cursor)**: Pass the `page.startCursor` value from the previous response to fetch the previous page. - **Page from (offset)**: Zero-based index of the first result, for offset-based pagination. **Page after**, **Page before**, and **Page from** are mutually exclusive. See [pagination](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-data-fetching.md#page) for more details. ## Handle the API response You can use an output mapping to map the response: 1. Use **Result variable** to store the response in a process variable. 2. Use **Result expression** to map fields from the response into process variables. By default, this connector sets the result expression to `={orchestrationClusterResponse: response.body}`. Response example for a **Search** operation on process instances: ```json { "status": 200, "headers": { "content-type": "application/json" }, "body": { "items": [ { "processInstanceKey": "2251799814052469", "processDefinitionId": "order-process", "processDefinitionKey": "2251799814052467", "processDefinitionVersion": 1, "startDate": "2023-03-21T08:25:04.499+0000", "endDate": "2023-03-21T08:25:12.093+0000", "state": "COMPLETED" }, { "processInstanceKey": "2251799814052613", "processDefinitionId": "order-process", "processDefinitionKey": "2251799814052610", "processDefinitionVersion": 2, "startDate": "2023-03-21T08:27:49.784+0000", "endDate": "2023-03-21T08:27:58.838+0000", "state": "COMPLETED" } ], "page": { "totalItems": 55, "startCursor": "jfenj8vhekgj98uzfafhu7", "endCursor": "negbkjeh84tzh4gk0kwegj" } } } ``` --- ## RabbitMQ connector The **RabbitMQ connector** is an outbound connector that allows you to connect your BPMN process with [RabbitMQ](https://www.rabbitmq.com/) to send messages to RabbitMQ. ## Prerequisites To use the **RabbitMQ connector**, you need to have installed a RabbitMQ server and create the relevant [credentials](https://www.rabbitmq.com/passwords.html). Use secrets to store credentials, so that you don't expose sensitive information directly from the process. See [this appendix entry](#how-do-i-store-secrets-for-my-connector) to learn more. :::note Ensure you enter the correct exchange name and routing key, as the **RabbitMQ connector** can't throw an exception if they are incorrect. ::: ## Create a RabbitMQ connector task ## Connecting to RabbitMQ and sending messages To connect to RabbitMQ, choose the required connection type in the **Authentication** section and complete the mandatory fields highlighted in red in the connector properties panel on the right side of the screen. :::note All the mandatory and non-mandatory fields depending on the authentication selection you choose are covered in the upcoming sections. ::: ## Authentication You can choose among the available RabbitMQ connectors according to your authentication requirements. First, you must have a user in your RabbitMQ instance with the necessary permissions. See more at the [RabbitMQ access control specification](https://www.rabbitmq.com/access-control.html). Next, we will choose the type of connection. ### URI type connection For a URI connection, take the following steps: 1. Click the **URI** connection type in the **Authentication** section 2. Set **URI** to `URI`. It must contain RabbitMQ username, password, host name, port number, and virtual host. For example, `amqp://userName:password@serverHost:port/virtualHost`; follow the [RabbitMQ URI specification](https://www.rabbitmq.com/uri-spec.html) to learn more. ### Credentials type connection To connect with credentials, take the following steps: 1. Click the **Username/Password** connection type in the **Authentication** section 2. Set the **Password** to `Password`. ## Routing data In the **Routing** section, you must set the routing data attributes: - For a **URI** type connection, the required fields are `exchange` and `routingKey`. - For a **Credentials** type connection, the required fields are `exchange`, `routingKey`, `virtualHost`, `hostName`, and `port`. Refer to the RabbitMQ documentation to learn about routing attributes: - [Exchanges, routing keys, and bindings](https://www.cloudamqp.com/blog/part4-rabbitmq-for-beginners-exchanges-routing-keys-bindings.html) - [Virtual hosts](https://www.rabbitmq.com/vhosts.html) - [Networking, host, and port configuration](https://www.rabbitmq.com/networking.html) ## Message 1. In the **Message** section, insert the message payload. The message can be Text or JSON format. 2. (Optional) In the **Properties** section, insert the message properties in JSON or as a [FEEL](/components/modeler/feel/what-is-feel.md) expression. Go to [RabbitMQ documentation](https://www.rabbitmq.com/publishers.html#message-properties) for learn more about RabbitMQ message properties. example of message : ``` = {"myMessageKey":"Hello Camunda Team"} ``` example of properties: ``` = { "contentEncoding":"UTF-8", "contentType":"text/plain" } ``` ## RabbitMQ connector response The **RabbitMQ connector** returns the `Success` result. The response contains a `messageId` variable. You can use an output mapping to map the response: 1. Use **Result Variable** to store the response in a process variable. For example, `myResultVariable`. 2. Use **Result Expression** to map specific fields from the response into process variables using [FEEL](/components/modeler/feel/what-is-feel.md). For example: ``` = { "myResultVariable": response.statusResult } ``` ## Appendix & FAQ ### How do I store secrets for my connector? Use secrets to avoid exposing your credentials. Follow our documentation on [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. The **RabbitMQ connector** is an inbound connector that allows you to connect your BPMN process with [RabbitMQ](https://www.rabbitmq.com/) to receive messages from RabbitMQ. ## Prerequisites To use the **RabbitMQ connector**, you need to have installed a RabbitMQ server and create the relevant [credentials](https://www.rabbitmq.com/passwords.html). Using secrets to store credentials is recommended so you do not expose sensitive information directly from the process. See [this appendix entry](#how-do-i-store-secrets-for-my-connector) to learn more. ## Create a RabbitMQ connector event See [create a RabbitMQ connector task](#create-a-rabbitmq-connector-task) for additional details. 1. Add a **Start Event** or an **Intermediate Event** to your BPMN diagram to get started. 2. Change its template to a RabbitMQ connector. 3. Fill in all required properties. 4. Complete your BPMN diagram. 5. Deploy the diagram to activate the RabbitMQ consumer. ## Connecting to RabbitMQ and receiving messages ### Authentication You can choose among the available authentication types according to your requirements. First, you must have a user in your RabbitMQ instance with the necessary permissions. See more at the [RabbitMQ access control specification](https://www.rabbitmq.com/access-control.html). Next, we will choose the type of connection. #### URI type connection For a URI connection, take the following steps: 1. Click the **URI** connection type in the **Authentication** section. 2. Set **URI** to `URI`. It must contain RabbitMQ username, password, host name, port number, and virtual host. For example, `amqp://userName:password@serverHost:port/virtualHost`; follow the [RabbitMQ URI specification](https://www.rabbitmq.com/uri-spec.html) to learn more. #### Credentials type connection To connect with credentials, take the following steps: 1. Click the **Username/Password** connection type in the **Authentication** section 2. Set the **Password** to `Password`. ### Routing data - For a **Credentials** type connection, you are required to fill in the `virtualHost`, `hostName`, and `port` fields. - For a **URI** type connection, these values are already included in the URI, so you don't need to fill them in. ### Subscription properties The **Subscription** section allows you to configure the subscription to the RabbitMQ queue. - **Queue name** is a mandatory field that specifies the name of the queue to subscribe to. - **Consumer tag** is an optional field that specifies the consumer tag to use for the consumer. If not set, the server will generate one. - **Exclusive consumer** is an optional dropdown field that specifies whether the consumer is exclusive. Exclusivity allows you to ensure only one consumer at a time consumes from the queue. - **Arguments** is an optional FEEL expression field that specifies the arguments for the queue. The expression must be a [FEEL context expression](/components/modeler/feel/language-guide/feel-context-expressions.md). For example, `={x-message-ttl: 60000}`. See more at the [RabbitMQ queue arguments specification](https://www.rabbitmq.com/queues.html#optional-arguments). :::note When configuring the **Arguments** field, remember that inbound connectors are executed outside the BPMN process context and are not tied to a specific process instance. Therefore, you cannot use process variables in the **Arguments** context expression. However, you can refer to connector secrets using placeholder syntax. For example, `= {x-consumer-timeout: "{{secrets.CONSUMER_TIMEOUT}}"}`. ::: ### Activation condition **Activation condition** is an optional FEEL expression field that allows for fine-tuning of the connector activation. For example, given a RabbitMQ message contains the payload `{"role": "USER", "action": "LOGIN""}`, the **Activation Condition** value might look like `=(message.body.role="USER")`. This way, the connector is triggered only if the message body contains the `role` field with the value `USER`. Leave this field empty to trigger your connector for every incoming message. By default, messages with unmatched activation conditions are rejected without re-queuing. You can set up a dead-letter queue in RabbitMQ to handle these messages. Learn more about dead-letter queues in the [RabbitMQ documentation](https://www.rabbitmq.com/dlx.html). You can also configure the RabbitMQ inbound connector to acknowledge messages that don't match the activation condition. In this case, the message will not end up in the dead-letter queue, but will be acknowledged and removed from the queue. To acknowledge messages that don't match the activation condition, check the **Consume unmatched events** checkbox. | **Consume unmatched events** checkbox | Activation condition | Outcome | | ------------------------------------- | -------------------- | -------------------------------------------------- | | Checked | Matched | Message is acknowledged and removed from the queue | | Unchecked | Matched | Message is acknowledged and removed from the queue | | Checked | Unmatched | Message is acknowledged and removed from the queue | | Unchecked | Unmatched | Message is rejected and re-queued | ### Correlation The **Correlation** section allows you to configure the message correlation parameters. :::note The **Correlation** section is not applicable for the plain **start event** element template of the RabbitMQ connector. Plain **start events** are triggered by process instance creation and do not rely on message correlation. ::: #### Correlation key - **Correlation key (process)** is a FEEL expression that defines the correlation key for the subscription. This corresponds to the **Correlation key** property of a regular **Message Intermediate Catch Event**. - **Correlation key (payload)** is a FEEL expression used to extract the correlation key from the incoming message. This expression is evaluated in the connector Runtime and the result is used to correlate the message. For example, given that your correlation key is defined with `myCorrelationKey` process variable, and the incoming RabbitMQ message contains `message:{body:{correlationKey:myValue}}`, your correlation key settings will look like this: - **Correlation key (process)**: `=myCorrelationKey` - **Correlation key (payload)**: `=message.body.correlationKey` Learn more about correlation keys in the [messages guide](../../../concepts/messages). #### Message ID expression The **Message ID expression** is an optional field that allows you to extract the message ID from the incoming message. The message ID serves as a unique identifier for the message and is used for message correlation. This expression is evaluated in the connector Runtime and the result is used to correlate the message. In most cases, it is not necessary to configure the **Message ID expression**. However, it is useful if you want to ensure message deduplication or achieve a certain message correlation behavior. Learn more about how message IDs influence message correlation in the [messages guide](../../../concepts/messages#message-correlation-overview). For example, to set the message ID to the value of the `transactionId` field in the incoming message, configure the **Message ID expression** as follows: ``` = message.body.transactionId ``` #### Message TTL The **Message TTL** is an optional field that allows you to set the time-to-live (TTL) for the correlated messages. TTL defines the time for which the message is buffered in Zeebe before being correlated to the process instance (if it can't be correlated immediately). The value is specified as an ISO 8601 duration. For example, `PT1H` sets the TTL to one hour. Learn more about the TTL concept in Zeebe in the [message correlation guide](../../../concepts/messages#message-buffering). ### Deduplication The **Deduplication** section allows you to configure the connector deduplication parameters. Not to be confused with **message deduplication**, **Connector deduplication** is a mechanism in the connector Runtime that determines how many RabbitMQ subscriptions are created if there are multiple occurrences of the **RabbitMQ Consumer connector** in the BPMN diagram. By default, the connector runtime deduplicates connectors based on properties, so elements with the same subscription properties only result in one subscription. For details, see [Inbound connector deduplication](../advanced-topics/deduplication.md). To customize the deduplication behavior, check the **Manual mode** checkbox and configure the custom deduplication ID. ### Output mapping The **Output mapping** section allows you to configure the mapping of the RabbitMQ message to the process variables. - Use **Result variable** to store the response in a process variable. For example, `myResultVariable`. - Use **Result expression** to map specific fields from the response into process variables using [FEEL](/components/modeler/feel/what-is-feel.md). For example, given the RabbitMQ connector is triggered with the message body `{"role": "USER", "action": "LOGIN""}` and you would like to extract the pull request `role` as a process variable `messageRole`, the **Result Expression** might look like this: ``` = { "messageRole": message.body.role } ``` ## Appendix & FAQ ### How do I store secrets for my connector? Use secrets to avoid exposing your credentials. Follow our documentation on [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ### What is the output format of the RabbitMQ connector? The RabbitMQ connector returns the following output that can be used in the next steps of your process, including result expressions: ``` { "message": { "consumerTag": "myConsumerTag", "body": { {{ the message body }} }, "properties": { "contentType": "application/json", "contentEncoding": "UTF-8", "headers": { "x-first": "1", "x-second": "2" }, "deliveryMode": 2, "priority": 0, "correlationId": "myCorrelationId", "replyTo": "myReplyTo", "expiration": "myExpiration", "messageId": "myMessageId", "timestamp": "myTimestamp", "type": "myType", "userId": "myUserId", "appId": "myAppId", "clusterId": "myClusterId" } } } ``` :::note The output payload contains a top-level `message` object that contains `consumerTag`, `body`, and `properties` fields. ::: ### How is message body deserialized? The RabbitMQ Consumer connector always tries to deserialize the message body as JSON. If the deserialization fails, the connector will return the message body as a string. However, if the body only contains a primitive value, such as a string, a number, or a boolean, the connector will return the primitive value itself. ### When is the message acknowledged? What happens if the connector execution fails? The following outcomes are possible: - If connector execution is successful and **Activation condition** was met, the message is acknowledged and removed from the queue. - If **Activation condition** was not met, the message is rejected and removed from the queue. - If connector execution fails due to an unexpected error (e.g. Zeebe is unavailable), the message is rejected and re-queued. ### What lifecycle does the RabbitMQ Consumer connector have? The RabbitMQ Subscription connector is a long-running connector that is activated when the process is deployed and deactivated when the process is un-deployed or overwritten by a new version. --- ## Salesforce connector The **Salesforce connector** is an outbound protocol connector that allows you to connect your BPMN service with [Salesforce](https://salesforce.com/) to interact with the [Salesforce APIs](https://developer.salesforce.com/docs/apis). ## Prerequisites To use the **Salesforce connector**, you must have a [Salesforce Connected App with OAuth 2.0 Client Credentials Flow](https://help.salesforce.com/s/articleView?id=sf.connected_app_client_credentials_setup.htm&type=5). :::note Use secrets to avoid exposing your _Salesforce Connected App_ client ID and client secret as plain text. Learn more in our documentation on [managing secrets](/components/saas/clusters/manage-secrets.md). ::: ## Create a Salesforce connector task ## Instance Each operation requires information about the **Salesforce Base URL**. Example: `https://MyDomainName.my.salesforce.com` The Salesforce API version should be the one you want to use. You can search for this information [in your Salesforce API](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_versions.htm). ## Authentication In the **Authentication** section, select **Bearer Token** to provide a static access token or **OAuth 2.0** to configure client credentials. :::note While the static access token is useful for getting started, it is recommended to provide the **OAuth 2.0** client credentials. ::: ## Operation ### Operation types Currently, this connector supports two types of operation: - [SOQL Query](https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql.htm) - [sObject records](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_list.htm) ### SOQL Query The **SOQL Query** only requires the query itself as input. A query is useful for receiving data based on a structured query language. Take a closer look at some available [examples](https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_examples.htm). The response body looks like the following: ```json { "totalSize": 1, "done": true, "records": [ { "attributes": { "type": "", "url": "/services/data//sobjects//" }, "": "", "...": "..." } ] } ``` ### sObject records **sObject records** support **Create record**, **Get record**, **Update record**, and **Delete record**. :::note Every operation explanation contains a link to the Salesforce API docs which will explain the request and provide an example. ::: #### Create record - **Salesforce object:** The Salesforce object to create, e.g. _Account_. - **Record fields:** Field values for the Salesforce object to create, e.g. `{ Name: "Express Logistics and Transport" }`. Review an example including the response body format in the [Salesforce documentation](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_create.htm). #### Get record - **Salesforce object:** The Salesforce object to create, e.g. _Account_. - **Salesforce object ID:** Identifier of the Salesforce object, e.g. _001R0000005hDFYIA2_. - **Relationship field name _(optional)_:** Name of the field that contains the relationship, e.g. _Opportunities_. - **Query Parameters _(optional)_:** Additional query parameters that can be provided along with the request, e.g. `{ fields: "AccountNumber,BillingPostalCode" }`. When omitting the **Relationship field name**, a [get request for a record](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_sobject_retrieve_get.htm) is performed. Otherwise, a [get request for records using sObject relationships](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_sobject_relationships_get.htm) is performed. In the documentation linked above, you can find the possible use case for **Query parameters**; for example, [filtering fields](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_get_field_values.htm). The response body will contain the requested object as the root object: ```json { "attributes": { "type": "", "url": "/services/data//sobjects//" }, "": "", "...": "..." } ``` Find another example [here](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_get_field_values.htm). #### Update object - **Salesforce object:** The Salesforce object to create, e.g. _Account_. - **Salesforce object ID:** Identifier of the Salesforce object, e.g. _001R0000005hDFYIA2_. - **Record fields:** Field values for the Salesforce object to update, e.g. `{ BillingCity : "San Francisco" }`. [These update the record](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_sobject_retrieve_patch.htm) using the given fields. As an update does not return a body, you will not be able to map any data from the response back to the process. #### Delete object - **Salesforce object:** The Salesforce object to create, e.g. _Account_. - **Salesforce object ID:** Identifier of the Salesforce object, e.g. _001R0000005hDFYIA2_. [These delete the record](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_sobject_retrieve_delete.htm). As a delete does not return a body, you will not be able to map any data from the response back to the process. ## Handle connector response The **Salesforce connector** is a protocol connector, meaning it is built on top of the **HTTP REST connector**. Therefore, handling response is still applicable [as described](/components/connectors/protocol/rest.md#response). --- ## SendGrid connector Send emails from your BPMN processes using the SendGrid email API. ## About this connector The **SendGrid connector** is an outbound connector that sends an email through [SendGrid](https://sendgrid.com/). Use it to send transactional emails, such as order confirmations or status notifications, from a service task. You can send an email in one of two ways, selected with the **Mail contents** property: - **Simple (no dynamic template)**: Provide the subject and body, as plain text or HTML, in the connector. - **Using dynamic template**: Use a [SendGrid dynamic template](https://sendgrid.com/solutions/email-api/dynamic-email-templates/) and pass dynamic values to it from process variables. With either option, you can attach documents to the email. ## Prerequisites To use the SendGrid connector, you need the following: | Prerequisite | Description | | :--------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | [SendGrid account](#create-a-sendgrid-account) | A SendGrid account to send emails from. | | [Sender identity](#create-a-sender-identity) | A verified sender identity, which SendGrid requires before you can send emails. | | [API key](#create-an-api-key) | A SendGrid API key, stored as a [connector secret](#create-a-connector-secret) or in a reusable credential. | | Dynamic template (optional) | A SendGrid dynamic template, if you send emails with **Using dynamic template**. | ### Create a SendGrid account If you do not have a SendGrid account yet, create a free account: 1. Go to the [SendGrid sign-up page](https://signup.sendgrid.com/). 2. Set up the account with your email address and a password. 3. Click **Create Account**. 4. Provide the further information SendGrid requires. 5. Click **Get Started**. ### Create a sender identity Before you send your first email, create and verify a sender identity: 1. Click **Settings > Sender Authentication**, or open the [sender authentication page](https://app.sendgrid.com/settings/sender_auth). 2. Choose **Verify a Single Sender** for demo purposes, or **Authenticate Your Domain** for a production setup. 3. Provide the details SendGrid requests in the form and click **Create**. 4. Open the verification email SendGrid sent to your inbox. 5. Click **Verify Single Sender**. ### Create an API key To create an API key in SendGrid: 1. Log in to your SendGrid account. 2. Go to **Settings**. 3. Click **API Keys > Create API Key**. 4. Give your key a name, for example `my-camunda-connector-key`. 5. Click **Create Key**. 6. Copy the API key. SendGrid shows it only once. ### Create a connector secret Keep your API key safe and avoid exposing it in the BPMN XML by storing it as a secret: 1. Follow the [guide for creating secrets](/components/saas/clusters/manage-secrets.md). 2. Name your secret `SEND_GRID_API_KEY` so you can reference it as `{{secrets.SEND_GRID_API_KEY}}` in the connector. ## Configuration Properties in this section apply to every email the connector sends. ### Authentication Choose a reusable credential, or enter an API key in the task. | Property | Type | Required | Description | Example | | :------------------ | :------------ | :---------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------ | | SendGrid credential | Configuration | No | A reusable **SendGrid API Key** credential. Available from Camunda 8.11. If you select a credential, its API key takes precedence and the **SendGrid API key** property is hidden. See [using credentials](/components/connectors/use-connectors/index.md#using-credentials). | | | SendGrid API key | String | Conditional | Your SendGrid API key. Required if you do not select a **SendGrid credential**. Use a secret to avoid exposing the key. | `{{secrets.SEND_GRID_API_KEY}}` | ### Sender The sender identity must match a sender you verified in SendGrid. See [create a sender identity](#create-a-sender-identity). | Property | Type | Required | Description | Example | | :------------ | :----- | :------- | :------------------------------- | :--------------------- | | Name | String | Yes | The name of the sender. | `Jane Doe` | | Email address | String | Yes | The email address of the sender. | `jane-doe@camunda.com` | ### Receiver | Property | Type | Required | Description | Example | | :------------ | :----- | :------- | :---------------------------------- | :----------------------- | | Name | String | Yes | The name of the recipient. | `John Smith` | | Email address | String | Yes | The email address of the recipient. | `john-smith@example.com` | ### Attachments Attach documents to the email. Each attachment is a [document](/components/document-handling/send-document-to-external-system.md#document-sources) with a **Camunda document** reference, **inline content** built from process data, or an **external document** URL. | Property | Type | Required | Description | Example | | :------------------ | :------- | :---------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------- | | Number of documents | Dropdown | Yes | How many documents to attach. Options: **None**, **Single document**, **Multiple documents**. Default: **None**. | | | Document source | Dropdown | Conditional | The source of the document. Options: **Camunda Document**, **Inline Content**, **From URL**. Default: **Camunda Document**. Shown if **Number of documents** is **Single document**. | | | Camunda document | String | Conditional | A reference to a Camunda document, as a FEEL expression. Required if **Document source** is **Camunda Document**. | `= invoiceDocument` | | Content | String | Conditional | The content of the inline document. Required if **Document source** is **Inline Content**. | | | File name | String | No | The name of the inline document. Shown if **Document source** is **Inline Content**. | | | Content type | String | No | The MIME type of the inline document. Shown if **Document source** is **Inline Content**. | | | URL | String | Conditional | The URL the document is downloaded from. Required if **Document source** is **From URL**. | | | File name | String | No | The name of the external document. Shown if **Document source** is **From URL**. | | | Documents | String | Conditional | A FEEL expression that returns a list of documents. Required if **Number of documents** is **Multiple documents**. | `= [document1, document2]` | To use a **Camunda document**, upload it first, for example with the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/specifications/create-document.api.mdx), and assign the result to a variable in **Start process instance**. Then reference the variable in the **Camunda document** property. See [document handling](/components/document-handling/getting-started.md) for details and limitations. ### Job timeout | Property | Type | Required | Description | Example | | :---------- | :----- | :------- | :------------------------------------------------------------------------------------------------------------------------------------- | :------ | | Job timeout | String | No | An ISO-8601 duration after which Zeebe considers the job timed out if it is not completed yet. Leave empty to use the default timeout. | `PT1M` | ## Operations The **Mail contents** property selects how the connector composes the email. | Property | Type | Required | Description | Example | | :------------ | :------- | :------- | :----------------------------------------------------------------------------------------------- | :------ | | Mail contents | Dropdown | Yes | How to compose the email. Options: **Simple (no dynamic template)**, **Using dynamic template**. | | ### Send a simple email Send an email with a subject and a plain text or HTML body. Select **Simple (no dynamic template)** in **Mail contents**. #### Input parameters | Property | Type | Required | Description | Example | | :----------- | :----- | :------- | :----------------------------------------------------------------------------------------------------- | :-------------------------- | | Subject | String | Yes | The subject line of the email. | `Your order has shipped` | | Content type | String | Yes | The MIME type of the body: `text/plain` for plain text or `text/html` for HTML. Default: `text/plain`. | `text/html` | | Body | Text | Yes | The body of the email, in the format of the selected **Content type**. | `Your order is on its way.` | #### Output On success, SendGrid accepts the email for delivery and returns the status code `202`. The connector returns the response from SendGrid: ```json { "statusCode": 202, "body": "", "headers": { "X-Message-Id": "abc" } } ``` The headers depend on the SendGrid response. To store only the status code in a process variable, set the **Result expression** to: ```feel = {sendStatus: response.statusCode} ``` ### Send an email using a dynamic template Send an email that uses a [SendGrid dynamic template](https://sendgrid.com/solutions/email-api/dynamic-email-templates/). SendGrid uses the [Handlebars templating language](https://handlebarsjs.com/) to fill in dynamic values. Select **Using dynamic template** in **Mail contents**. #### Input parameters | Property | Type | Required | Description | Example | | :------------ | :----- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------- | | Template ID | String | Yes | The ID of the dynamic template, which you can copy from the [Dynamic Transactional Templates page](https://sendgrid.com/dynamic_templates) in your SendGrid account. | `d-1234567890abcdef` | | Template data | String | Yes | A [FEEL context expression](/components/modeler/feel/language-guide/feel-context-expressions.md) with the values for the Handlebars placeholders of the template. | `= {name: "Jane"}` | #### Output The output is the same as for [simple emails](#send-a-simple-email): the connector returns the SendGrid response. ```json { "statusCode": 202, "body": "", "headers": { "X-Message-Id": "abc" } } ``` #### Example: weather report email This example sends a weather report with a dynamic template. 1. Open the [Dynamic Transactional Templates page](https://sendgrid.com/dynamic_templates) in SendGrid and click **Create Template**. 2. Add a unique template name and click **Save**. 3. To start editing the new template, click **Add Version**. 4. Select an editor and click **Continue**. 5. Design your template. For more information on Handlebars, see [using Handlebars](https://docs.sendgrid.com/for-developers/sending-email/using-handlebars). Use the following subject and body: ```text Subject: Your Camunda Weather Report for {{location}} ``` ```text Body: Hi {{name}}, Thanks for using Camunda connectors to check your current weather report. Your current weather in {{location}} is {{weather}} with {{actual-temp}}°C and feels like {{feel-temp}}°C The Camunda Team ``` The template uses the following Handlebars placeholders: | Placeholder | Description | | :---------------- | :------------------------------------------------- | | `{{name}}` | The name of the user requesting the weather report | | `{{location}}` | The location used for the weather report | | `{{weather}}` | The current weather condition | | `{{actual-temp}}` | The measured temperature | | `{{feel-temp}}` | How the temperature feels | While you edit the template, switch to **Preview** mode, choose **{} Show Test Data**, and provide test data to preview the email. To send the email, configure the connector task: 1. Set **Mail contents** to **Using dynamic template**. 2. Copy the ID of your template and paste it in **Template ID**. 3. Provide the values in **Template data**: ```feel = { name: "Jane", location: "Berlin", weather: "Clear", actual-temp: 30, feel-temp: 3 } ``` To provide dynamic content from process variables, reference them in **Template data**: ```feel = { name: nameVariable, location: locationVariable, weather: weatherVariable, actual-temp: temperature, feel-temp: windChill } ``` ### Request failed with a SendGrid status code If SendGrid does not accept the email, the connector fails with a message in this format: ```text User request failed to execute with status and error 'SendGrid returned the following errors: ' ``` SendGrid returned a status code other than `202`, and the message lists the errors it reported. Check the following: - The API key is valid and has permission to send emails. - The sender **Email address** matches a verified [sender identity](#create-a-sender-identity). - The **Template ID** is correct, if you use a dynamic template. ### Validation error before sending The connector validates the input before it calls SendGrid and fails if required values are missing. Common messages: | Message | Cause and fix | | :----------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------- | | `No API key provided by the reusable credential or the element template` | Neither a **SendGrid credential** nor a **SendGrid API key** is set. Select a credential or provide an API key. | | `each attached document must contain a file name` | An attached document has no file name in its metadata. Provide a file name for the document. | ## Further resources - [SendGrid dynamic templates](https://sendgrid.com/solutions/email-api/dynamic-email-templates/) - [Using Handlebars in SendGrid](https://docs.sendgrid.com/for-developers/sending-email/using-handlebars) - [Document handling](/components/document-handling/getting-started.md) --- ## Slack connector The **Slack connector** is an outbound connector that allows you to send messages to channels or users in your [Slack](https://slack.com) workspace from your BPMN process. :::note If your organization uses [Camunda app integrations](/components/camunda-integrations/app-integrations/app-integrations.md), the [App Integrations connector](/components/connectors/out-of-the-box-connectors/app-integrations.md) sends through the app your users already have connected, and carries no credentials in the process model. ::: ## Prerequisites To use the Slack connector, a Slack app must be registered with the Slack workspace you would like to send messages to. A respective OAuth token needs to be configured as a secret in your cluster. Follow [these steps in the appendix](#appendix) to learn how to set this up. ## Create a Slack connector task ## Make your Slack connector executable To make the **Slack connector** executable, fill out the mandatory fields highlighted in red in the properties panel on the right side of the screen. ### Authentication Set **Authentication** to your Slack OAuth token, which is stored as a secret. For example, `{{secrets.SLACK_OAUTH_TOKEN}}`. ### Create channel :::info This API uses the Slack [`conversations.create`](https://api.slack.com/methods/conversations.create) method. You need to ensure that your Slack application has related permissions enabled. ::: To create a channel, take the following steps: 1. Set **Method** to `Create Channel`. 2. Set the **New Channel Name**: - The channel name can be up to 80 characters, and can contain lowercase letters, digits, and symbols `-` and `_`. - This can be provided as a FEEL expression. 3. Set channel **Visibility** as required: - **Public** channels are visible to every workspace member. - **Private** channels are visible to explicitly invited people only. ### Invite user to channel :::info This API uses the Slack [`conversations.invite`](https://api.slack.com/methods/conversations.invite) method. You need to ensure that your Slack application has related permissions enabled. ::: To invite users to a channel, take the following steps: 1. Set **Method** to `Invite to Channel`. 2. Set the `Invite by` method: - Invite by **Channel Name**: - The channel name can be up to 80 characters, and can contain lowercase letters, digits, and symbols `-` and `_`. - This can be provided as a FEEL expression. - Invite by **Channel ID**: - The channel ID must be a valid Slack Channel ID. - This can be provided as a FEEL expression. 3. Set the **Users** as required: 1. One single username or email or ID (for example: `@myUser` or `my.user@company.com` or `ABCDEF12345`). 2. A comma separated list of users (for example: `@myUser, my.user@company.com, ABCDEF12345`). 3. FEEL expression. In this case you can provide a valid list of strings (for example: `["@myUser", "my.user@company.com", "ABCDEF12345"]`). - Formats: - If a username starts with an `@` symbol, it will be handled as user name. - If a username is in an email format, it will be handled as an email. - If a username doesn't start with an `@`, and isn't an email, it will be handled as a user ID. - If a null input or an input which is not a type of String or a Collection provided, you will get an Exception. - If all username is provided as any other type than a String, you will get an Exception. - If one of the usernames is provided as any other type than a String, it will be omitted. - If you provide a channel name it will be omitted since it is not possible to invite a channel to another channel. ### Post message :::info This API uses the Slack [`chat.postMessage`](https://api.slack.com/methods/chat.postMessage) method. You need to ensure that your Slack application has related permissions enabled. ::: To post a message, take the following steps: 1. Set **Method** to `Post Message`. 2. Set **Channel/User Name** to either the **channel** or **user** you want to send the message to. 1. A **channel** is specified by a unique identifier starting with a `#` (for example, `#myChannel`) or by using the channel id. 2. A **user** is specified by a username starting with an `@` symbol (for example, `@myUser`). 3. (Optional) A **thread** can be specified to start a thread from a specific message. For example, `ts` in the response can be used (see [here](#post-message)). If the message has been posted by a user, we currently have no way to retrieve the `ts` value. Visit the [Slack documentation](https://api.slack.com/methods/chat.postMessage) for additional details. 4. Select a **Message type**. 1. When **Plain text** is selected, set **Message** to the message string you would like to send (for example, `Hello World!`). 2. When **Message block** is selected, set **Message block** to a formatted rich text block format. Learn more about rich text message block format in the [official Slack documentation](https://api.slack.com/reference/surfaces/formatting#stack_of_blocks). 5. (Optional) **Attachments** are the documents to include with the message. To work with attachments you must add `files:read` and `files:write` permissions for your **Bot Token Scopes** in your Slack app. :::note Each attachment uses a [document source](/components/document-handling/send-document-to-external-system.md#document-sources): a **Camunda document** reference, **inline content** built from process data, or an **external document** URL. Use the **Single/Multiple** toggle to provide one document or a FEEL array of documents. To use a **Camunda document**, upload it first — using the [REST API](/apis-tools/orchestration-cluster-api-rest/specifications/create-document.api.mdx) for example — and assign the result to a variable in **Start Process instance** so you can reference it in the **Attachments** field. ::: :::note Starting from version 8.7.0, the Slack connector works with document handling to support adding attachments and increasing template versions. See additional details and limitations in [document handling](/components/document-handling/getting-started.md). ::: The **Channel/User Name** and **Message** can either be given [static values](/components/concepts/expressions.md#expressions-vs-static-values), or FEEL expressions. FEEL expressions can be used to [access process variables or dynamically create values](/components/concepts/expressions.md). This can be handy if a process variable is used to store the relevant channel or if the message needs to be composed dynamically, for example: `Channel/User Name` property might look like: ``` #slack-connectors ``` `Message` property: ``` = "Order-" + orderId + " was dispatched" ``` In the above example, the Channel/User Name is set to the [static value](/components/concepts/expressions.md#expressions-vs-static-values) "#slack-connectors," which will post the message to the specified Slack channel. The **Message** property uses a FEEL expression to dynamically create the message content. It concatenates the string "Order-" with the value stored in the process variable orderId and adds "was dispatched" to complete the message. This way, the message will vary based on the specific orderId stored during the process execution. :::note Slack's [guidance on formatting](https://api.slack.com/reference/surfaces/formatting#basics) can assist in formatting messages. ::: ## Slack API response The **Slack connector** exposes the Slack API response as a [local variable](/components/concepts/variables.md#variable-scopes) called `response`. Response contents are method-specific. ### Create channel The following fields are available in the `response` variable after executing **Create Channel** method: - **channel**: - **id**: channel ID - **name**: channel name Notice that the **name** field can be subsequently used as an argument of **Post Message** method. ### Post message The following fields are available in the `response` variable after executing the **Post Message** method. Notice that all fields describe state in the Slack workspace: - **ts**: timestamp ID - **channel**: channel ID - **message**: - **type**: message type - **team**: team ID - **user**: user ID - **text**: message text - **ts**: timestamp ID - **appID**: Slack App ID - **botID**: Slack Bot ID ### Output mapping You can use an Output Mapping to map the response: 1. Use **Result Variable** to store the response in a process variable. For example, `myResultVariable`. 2. Use **Result Expression** to map fields from the response into process variables. For example: ``` = { messageText: response.message.text } ``` ## Appendix To use the **Slack connector**, the following prerequisites need to be set up. 1. [Slack workspace](#use-a-well-known-slack-workspace) - The workspace the **Slack connector** will communicate with. 2. [Slack basic app with bot token configured](#configure-a-basic-slack-app) - The **Slack connector** will communicate through this Slack app with the workspace. You can consider the Slack app as _Slack bot representing the Camunda platform_. 3. [Slack bot token stored as secret](#store-slack-bot-token-as-secret) - The secret will store the Slack bot token and can be used to reference it from BPMN processes without revealing it in the BPMN `xml`. ### Use a well-known Slack workspace A Slack workspace consists of channels in which workspace members can communicate and collaborate. A workspace is identified by a unique name, for example `https://myWorkspace.slack.com/`. In most cases you will know which workspace you want to connect with already. If you want to set up a new workspace, refer to the [official Slack documentation](https://slack.com/help/articles/115001344007-Create-a-workspace-on-Enterprise-Grid). ### Configure a basic Slack app :::caution You can only install a Slack app to a workspace in which you are a member or that you own. It is not possible if you have guest-only permissions. See the [guide to apps in Slack](https://slack.com/help/articles/360001537467-Guide-to-apps-in-Slack) for more details. ::: The **Slack connector** communicates through a Slack app with a concrete Slack workspace. For example, when sending a Slack message, the message will be posted by the Slack app. For the **Slack connector** to work, you need to perform the following steps: 1. [Create a Slack app](https://api.slack.com/apps). 2. [Request required scopes](https://api.slack.com/scopes) - The scopes represent what your app can and cannot do (for example, posting messages). 1. For the **Create Channel** method to work, you need to grant at least the [`channels:manage`](https://api.slack.com/scopes/channels:manage) scope. 2. For the **Post Message** method to work, you need to grant at least the [`chat:write`](https://api.slack.com/scopes/chat:write) scope. 3. [Install the Slack app to your workspace](https://api.slack.com/authentication/basics#installing). 4. [Invite the Slack app to your workspace via /invite](https://slack.com/help/articles/201259356-Slash-commands-in-Slack#h_01EPZ2Z81EJ67RA2BGDKZ9M1AN). Once the app is set up, copy the [bot token](https://api.slack.com/authentication/token-types) of the app. It is represented as a string and begins with `xoxb-`. This is the OAuth Bearer token, which the **Slack connector** will use to authenticate with the Slack API. ### Store Slack bot token as secret The **Slack connector** uses an OAuth bearer token (for example, the Slack app bot token) to authenticate with the Slack API. We advise you to keep your Slack bot token safe and avoid exposing it in the BPMN `xml` file by creating a secret: 1. Follow our [guide for creating secrets](/components/saas/clusters/manage-secrets.md). 2. Name your secret `SLACK_OAUTH_TOKEN` so you can reference it later in the connector. The **Slack inbound connector** is a connector that allows you to start or continue a BPMN process triggered by a [Slack](https://slack.com/) message. :::note If your organization uses [Camunda app integrations](/components/camunda-integrations/app-integrations/app-integrations.md), the [App Integrations connector](/components/connectors/out-of-the-box-connectors/app-integrations.md#receive-a-chat-message) receives chat messages through the app your users already have connected. ::: ## Create a Slack inbound connector task 1. Start building your BPMN diagram. You can use the **Slack inbound connector** with either a **Start Event** or **Intermediate Catch Event**. 2. Select the applicable element and change its template to a **Slack Inbound connector**. 3. Fill in all required properties. 4. Complete your BPMN diagram. 5. Deploy the diagram to activate the webhook. 6. Navigate to the **Webhooks** tab in the properties panel to see the webhook URL. ## Make your Slack inbound connector for receiving event notifications executable 1. In the **Webhook Configuration** section, configure the **Webhook ID**. By default, **Webhook ID** is pre-filled with a random value. This value will be a part of the Slack event subscription or slash command URL. 2. In the **Webhook Configuration** section, configure the **Slack signing secret**. This value is unique to your Slack application and used to validate a Slack payload integrity. Read more about signing secrets in the [Slack documentation](https://api.slack.com/authentication/verifying-requests-from-slack). 3. In the **Activation** section, configure **Condition** when the Slack event or command can trigger a new BPMN process. The following example will trigger a new BPMN process for every `app_mention` Slack event type: `=(request.body.event.type = "app_mention")`. 4. In the **Variable mapping** section, fill the field **Result variable** to store the response in a process variable. For example, `myResultVariable`. 5. In the **Variable expression** section, fill the field to map specific fields from the response into process variables using [FEEL](/components/modeler/feel/what-is-feel.md). The following example will extract both Slack message sender ID and text from Slack `app_mention` event: `={senderId: request.body.event.user, text: request.body.event.text}`. ### Correlation The **Correlation** section allows you to configure the message correlation parameters. :::note The **Correlation** section is not applicable for the plain **start event** element template of the Slack connector. Plain **start events** are triggered by process instance creation and do not rely on message correlation. ::: #### Correlation keys - **Correlation key (process)** is a FEEL expression that defines the correlation key for the subscription. This corresponds to the **Correlation key** property of a regular **message intermediate catch event**. - **Correlation key (payload)** is a FEEL expression used to extract the correlation key from the incoming message. This expression is evaluated in the connector Runtime and the result is used to correlate the message. For example, given your correlation key is defined with `myCorrelationKey` process variable, and the request body contains `"event": {"text": "12345"}`, your correlation key settings will look like this: - **Correlation key (process)**: `=myCorrelationKey` - **Correlation key (payload)**: `=request.body.event.text` Learn more about correlation keys in the [messages guide](../../../concepts/messages). #### Message ID expression The **Message ID expression** is an optional field that allows you to extract the message ID from the incoming request. The message ID serves as a unique identifier for the message and is used for message correlation. This expression is evaluated in the connector Runtime and the result is used to correlate the message. In most cases, it is not necessary to configure the **Message ID expression**. However, it is useful if you want to ensure message deduplication or achieve a certain message correlation behavior. Learn more about how message IDs influence message correlation in the [messages guide](../../../concepts/messages#message-correlation-overview). For example, to set the message ID to the value of the `text` field of the incoming message, configure the **Message ID expression** as follows: ``` = request.body.event.text ``` #### Message TTL The **Message TTL** is an optional field that allows you to set the time-to-live (TTL) for the correlated messages. TTL defines the time for which the message is buffered in Zeebe before being correlated to the process instance (if it can't be correlated immediately). The value is specified as an ISO 8601 duration. For example, `PT1H` sets the TTL to one hour. Learn more about the TTL concept in Zeebe in the [message correlation guide](../../../concepts/messages#message-buffering). ## Make your Slack inbound connector for receiving slash command notifications executable 1. In the **Webhook Configuration** section, configure the **Webhook ID**. By default, **Webhook ID** is pre-filled with a random value. This value will be a part of the Slack event subscription or slash command URL. 2. In the **Webhook Configuration** section, configure the **Slack signing secret**. This value is unique to your Slack application and used to validate a Slack payload integrity. Read more about signing secrets in the [Slack documentation](https://api.slack.com/authentication/verifying-requests-from-slack). 3. In the **Activation** section, configure **Condition** when the Slack event or command can trigger a new BPMN process. The following example will trigger a new BPMN process for every `/test` Slack command type: `=(connectorData.command = "/test")`. 4. In the **Variable mapping** section, fill the field **Result variable** to store the response in a process variable. For example, `myResultVariable`. 5. In the **Variable expression** section, fill the field to map specific fields from the response into process variables using [FEEL](/components/modeler/feel/what-is-feel.md). The following example will extract both Slack message sender ID and text from Slack `/test hello` command: `={senderId: connectorData.user_id, text: connectorData.text}`. When using the **Slack inbound connector** with an **Intermediate Catch Event**, fill in the **Correlation key (process)** and **Correlation key (payload)**. - **Correlation key (process)** is a FEEL expression that defines the correlation key for the subscription. This corresponds to the **Correlation key** property of a regular **Message Intermediate Catch Event**. - **Correlation key (payload)** is a FEEL expression used to extract the correlation key from the incoming message. This expression is evaluated in the connector Runtime and the result is used to correlate the message. For example, given that your correlation key is defined with `myCorrelationKey` process variable, and the request body contains `text=hello}`, your correlation key settings will look like this: - **Correlation key (process)**: `=myCorrelationKey` - **Correlation key (payload)**: `=connectorData.text` Learn more about correlation keys in the [messages guide](../../../concepts/messages). ## Activate the Slack inbound connector by deploying your diagram Once you click the **Deploy** button, your **Slack inbound connector** will be activated and publicly available. URLs of the exposed **Slack inbound connector** adhere to the following pattern: `https:///inbound/` - `` is the URL of connectors component deployment. When using the Camunda 8 SaaS offering, this will typically contain your **region Id** and **cluster Id**, found in your client credentials under the **API** tab within your cluster. - `` is the ID (path) you configured in the properties of your **Slack inbound connector**. If you make changes to your **Slack Inbound connector** configuration, you need to redeploy the BPMN diagram for the changes to take effect. When you click on the event with **Slack inbound connector** applied to it, a new **Webhooks** tab will appear in the properties panel. This tab displays the URL of the **Slack inbound connector** for every cluster where you have deployed your BPMN diagram. :::note The **Webhooks** tab is only supported in Camunda Hub as part of the Camunda 8 SaaS offering. You can still use Slack inbound connectors in Desktop Modeler, or with your Camunda 8 Self-Managed. In that case, Slack inbound connector deployments and URLs will not be displayed in Desktop Modeler. ::: ## Wiring with Slack ### Events API This is a simplified guide. For full guide, refer to the [official Slack documentation](https://api.slack.com/apis/connections/events-api). 1. Make sure you have sufficient permissions to modify your Slack application. 2. Open [Slack API portal](https://api.slack.com) and select your Slack application. 3. Navigate to the **Event Subscription** page. 4. Click **Enable Events**. 5. In the **Request URL** field, put the webhook URL. You can find it at the **Webhook** tab in the properties panel of you BPMN diagram. 6. Make sure that the **Request URL** indicates that endpoint is **Verified**. This process may take several seconds. 7. Click **Subscribe to bot events**. 8. Select all events you wish to receive. **Note:** some messages may produce several events. For example, a message `@YourBot test` will generate both `app-mention` and `message` events. 9. Click **Save** to apply new changes. 10. Install or re-install your app into your workspace. ### Slash commands This is a simplified guide. For a full guide, refer to the [official Slack documentation](https://api.slack.com/interactivity/slash-commands). 1. Make sure you have sufficient permissions to modify your Slack application. 2. Open [Slack API portal](https://api.slack.com) and select your Slack application. 3. Navigate to **Slash Commands**. 4. Click **Create New Command**. 5. Fill the fields **Command**, **Short Description**, and **Usage Hint** as you prefer. 6. In the **Request URL** field, put the webhook URL. You can find it at the **Webhook** tab in the properties panel of your BPMN diagram. 7. Click **Save** to apply new changes. ## Security considerations ### Integrity Each Slack message is signed with HMAC using a Slack signing key. The **Slack inbound connector** verifies HMAC integrity for every incoming request. Read more about signing secrets in the [Slack documentation](https://api.slack.com/authentication/verifying-requests-from-slack). ## Appendix ### Slack `app_mention` event example ``` POST https:///inbound/ connection: close content-type: application/json content-length: 429 x-slack-request-timestamp: 1687791117 x-slack-signature: v0=aaaaaaaabbbbbbbbcccccccddddddeeeeeeffffffff accept: application/json,*/* accept-encoding: gzip,deflate user-agent: Slackbot 1.0 (+https://api.slack.com/robots) host: { "token": "XXXXXXXX", "team_id": "XXXXXXXX", "api_app_id": "XXXXXXXX", "event": { "client_msg_id": "ffb7ded2-6f55-468d-926f-cad3195c8056", "type": "app_mention", "text": "<@XXXXXXXX> say hello", "user": "XXXXXXXX", "ts": "11111111.2222222", "blocks": [ { "type": "rich_text", "block_id": "rarsi", "elements": [ { "type": "rich_text_section", "elements": [ { "type": "user", "user_id": "XXXXXXXX" }, { "type": "text", "text": " say hello" } ] } ] } ], "team": "XXXXXXXX", "thread_ts": "1687864866.335329", "parent_user_id": "XXXXXXXX", "channel": "XXXXXXXX", "event_ts": "1687866358.496959" }, "type": "event_callback", "event_id": "XXXXXXXX", "event_time": 1687866358, "authorizations": [ { "enterprise_id": null, "team_id": "XXXXXXXX", "user_id": "XXXXXXXX", "is_bot": true, "is_enterprise_install": false } ], "is_ext_shared_channel": false, "event_context": "XXXXXXXX" } ``` ### Slack slash command example Given the following command is executed: `/test123 test`. ``` POST https:///inbound/ connection: close content-type: application/x-www-form-urlencoded content-length: 429 x-slack-request-timestamp: 1687792480 x-slack-signature: v0=aaaaaaaabbbbbbbbcccccccddddddeeeeeeffffffff accept: application/json,*/* accept-encoding: gzip,deflate user-agent: Slackbot 1.0 (+https://api.slack.com/robots) host: token=qQqQqQqQqQqQqQqQqQ &team_id=T05ABCDEFG &team_domain=yourdomain &channel_id=C05QQQQQQ &channel_name=channel1 &user_id=U05AAAAAAA &user_name=your.user &command=%2Ftest123 &text=test &api_app_id=A05DDDDDDD &is_enterprise_install=false &response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FXXXXXXXXX%2FYYYYYYYYYYY%2FZZZZZZZZ &trigger_id=111111111.222222222.33333333 ``` --- ## SQL connector The **SQL connector** is an outbound connector that allows you to connect your BPMN service with SQL databases (MariaDB, Microsoft SQL Server, PostgreSQL, MySQL). ## Prerequisites To use the **SQL connector**, ensure you have an SQL database instance running. To avoid exposing your sensitive data as plain text, use secrets. Follow our documentation on [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ## Create an SQL connector task ## Make your SQL connector executable To make your **SQL connector** executable, fill out the mandatory fields highlighted in red in the properties panel on the right side of the screen. ### Database Select the database type you want to connect to. The **SQL connector** supports the following databases: - MariaDB - Microsoft SQL Server - MySQL - PostgreSQL - **Oracle:** (See note below.) :::note The Oracle Database connector requires the Oracle JDBC driver, which Camunda cannot distribute due to licensing restrictions. To connect to an Oracle database, you must manually download the JDBC driver from [Oracle](https://www.oracle.com/database/technologies/appdev/jdbc-downloads.html) and run the connector in [hybrid mode](/components/connectors/use-connectors-in-hybrid-mode.md). When building a custom Docker image, include the driver by copying it into the image—for example, add `COPY ojdbc17.jar /opt/custom/` to your Dockerfile. This ensures the driver is on the classpath when the connector runtime starts. ::: ### Connection The **SQL connector** supports two types of connections: - [URI](#uri-connection): Use this option to connect to your database using a URI (similar to a connection string). - [Detailed](#detailed-connection): Use this option to connect to your database by providing detailed connection information (host, port, database name, username, password). #### URI connection If you choose the URI connection type, you need to provide: | Property | Type | Required | Description | Example | | ---------- | ---------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | URI | String | Yes | The URI connection string for your database. The format of the URI depends on the database type you selected. You can find more details about the URI format in the relevant official documentation for [MariaDB](https://mariadb.com/kb/en/about-mariadb-connector-j/#connection-strings), [PostgreSQL](https://jdbc.postgresql.org/documentation/use/#connecting-to-the-database), [MySQL](https://dev.mysql.com/doc/connector-j/en/connector-j-reference-jdbc-url-format.html), and [Microsoft SQL Server](https://learn.microsoft.com/en-us/sql/connect/jdbc/building-the-connection-url?view=sql-server-ver16). | `jdbc:mysql://mysqlHost:3306/mydatabase?someOption=someValue` | | Properties | [Object](/components/modeler/feel/language-guide/feel-data-types.md#context) | No | Optional properties that can be used to configure the connection. These properties are appended to the URI. You can find more details about the properties in the relevant official documentation for [MariaDB](https://mariadb.com/kb/en/about-mariadb-connector-j/#optional-url-parameters), [PostgreSQL](https://jdbc.postgresql.org/documentation/use/#connection-parameters), [MySQL](https://dev.mysql.com/doc/connector-j/en/connector-j-reference-configuration-properties.html), and [Microsoft SQL Server](https://learn.microsoft.com/en-us/sql/connect/jdbc/setting-the-connection-properties?view=sql-server-ver16). | `={useSSL:false, requireSSL:false, user: "john", password:"securePwd"}` | #### Detailed connection If you choose the detailed connection type, provide the following: | Property | Type | Required | Description | Example | | ---------- | ---------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | | Host | String | Yes | The host of your database. | `localhost` | | Port | Number | Yes | The port of your database. | `3306` | | Username | String | No | The username to connect to your database. | `myuser` | | Password | String | No | The password to connect to your database. | `mypassword`, `{{secrets.MY_PASSWORD}}` | | Database | String | No | The name of your database. | `mydatabase` | | Properties | [Object](/components/modeler/feel/language-guide/feel-data-types.md#context) | No | Optional properties that can be used to configure the connection. You can find more details about the properties in the relevant official documentation for [MariaDB](https://mariadb.com/kb/en/about-mariadb-connector-j/#optional-url-parameters), [PostgreSQL](https://jdbc.postgresql.org/documentation/use/#connection-parameters), [MySQL](https://dev.mysql.com/doc/connector-j/en/connector-j-reference-configuration-properties.html), and [Microsoft SQL Server](https://learn.microsoft.com/en-us/sql/connect/jdbc/setting-the-connection-properties?view=sql-server-ver16). | `={useSSL:false, requireSSL:false}` | ### Query :::note You should pay extra attention to the query you are executing. Make sure it is safe and does not expose your database to SQL injection attacks. Use **[variables](#variables)** as much as possible to prevent SQL injection attacks. ::: | Property | Type | Required | Description | Example | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------- | | [Return results](#return-results) | Boolean | Yes (default is `false`) | If the query should return results (when using `SELECT` or `RETURNING`), set this field to `true`. Otherwise (insert, update, delete, create table or database), leave the checkbox unchecked.This property will **change** the response type.See the details [here](#return-results). | `true` | | [Query](#query-description) | String | Yes | The SQL query you want to execute.See the details [here](#query-description). | `SELECT * FROM mytable WHERE field = :field` | | [Variables](#variables) | [List](/components/modeler/feel/language-guide/feel-data-types.md#list) or [object](/components/modeler/feel/language-guide/feel-data-types.md#context) | No | Variables that can be used in the query.See the details [here](#variables). | `={field: "theFieldValue"}`, `=[24]` | #### Return results - When `false`, the response (see the [output](#what-is-the-output-format-of-the-sql-connector) section) will consist of an object containing an integer (`modifiedRows`) representing the number of modified rows. This is applicable for: - `INSERT` - `UPDATE` - `DELETE` This will return `0` for: - `CREATE TABLE` - `CREATE DATABASE`, except for MySQL, where it will return `1` - When `true`, the response will be a list of objects. This list will contain the results of the `SELECT` query.For instance, `SELECT * FROM mytable`, where `mytable` is a table with columns' `name` and `age`, will return: ```json { "resultSet": [ { "name": "John Doe", "age": 29 }, { "name": "Jane Doe", "age": 27 } ] } ``` #### Query {#query-description} The query you want to execute. We currently support the following SQL queries: - `SELECT` - `INSERT` - `UPDATE` - `DELETE` - `CREATE TABLE` - `CREATE DATABASE` The query might contain variables that can be used in the query, and we recommend using them as a best practice to prevent SQL injection attacks. See the [variables](#variables) section for more details. #### Variables Variables need to be provided as a list or an object. We provide three ways to use variables in your query: | Type | Query example | Variables example | | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | | Named parameters | `SELECT * FROM mytable WHERE field = :field``INSERT INTO Employee (id, name, age, department) VALUES (:id, :name, :age, :department)` | `={field: "theFieldValue"}``={id: 1, name: "John", age: 34, department: "Dept"}` | | Positional parameters | `SELECT * FROM mytable WHERE field = ?``INSERT INTO Employee (id, name, age, department) VALUES (?, ?, ?, ?)` | `=["theFieldValue"]``=[1, "John", 34, "Dept"]` | | List parameters | `SELECT * FROM mytable WHERE field IN ()` | `={listField: ["val1", "val2"]}` | ## Appendix & FAQ ### How do I store secrets for my connector? Use secrets to avoid exposing your credentials. Follow our documentation on [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ### What is the output format of the SQL connector? Depending on the type of query you execute, the response will contain either the number of modified rows (an object with a `modifiedRows` attribute) or the result set (an object with a `resultSet` attribute). - If the query **doesn't return results** (insert, update, delete, create table or database), the response will consist of an integer representing the number of modified rows. See the [return results](#return-results) section for more details. In this case, the response will look like this: ```json { "modifiedRows": 1 } ``` - If the query is a `SELECT` query (or uses the `RETURNING` keyword), the response will be an object with a `resultSet` property (as a list). This list will contain the results of the `SELECT` query as explained in the [return results](#return-results) section. For instance, `SELECT * FROM mytable`, where `mytable` is a table with columns' `name` and `age`, will return: ```json { "resultSet": [ { "name": "John Doe", "age": 29 }, { "name": "Jane Doe", "age": 27 } ] } ``` --- ## Twilio connector The **Twilio connector** allows you to integrate your BPMN service with Twilio's messaging API. With this connector, you can send SMS messages, get messages, and more. This documentation will guide you through the process of setting up and using the **Twilio connector**. ## Prerequisites Before you can use the Twilio connector, create a Twilio account and obtain an account SID and auth token from the [Twilio Console](https://www.twilio.com/console). You will also need to have a phone number to use as the sender for your SMS messages. :::note Use secrets to store your account SID and auth token so you don't expose sensitive information directly from the process. See [managing secrets](https://docs.camunda.org/manual/latest/user-guide/process-engine/secrets/) to learn more. ::: ## Create a Twilio connector task ## Make your Twilio connector executable To work with the Twilio connector, choose the required operation type in the **Operation** section and complete the mandatory fields highlighted in red in the connector properties panel on the right side of the screen. ### Operation Choose an operation type of either sendSms, listMessages, or getMessage in the **Operation** section: - **Send SMS**: Send an SMS message to a specified phone number from your Twilio account. - **List messages**: Retrieve a list of messages sent from your Twilio account within specified filters. - **Get message**: Retrieve the details of a specific message sent from your Twilio account. ## Authentication To access the Twilio API, the connector needs the appropriate credentials. The following authentication options are available: - **Account SID**: Provide the Account SID for your Twilio account. - **Auth Token**: Provide the Auth Token for your Twilio account. **OR** - **API Key**: Provide the API Key for your Twilio account. - **API Secret**: Provide the API Secret for your Twilio account. The Account SID and Auth Token or API Key and API secret are required properties and must be provided to use the connector. If these properties are not set, the connector will not be able to authenticate with the Twilio API. For more information on authentication and security in Twilio, refer to the [Twilio documentation](https://www.twilio.com/docs/usage/security). ## Required fields ### Send SMS operation - `Body`: The content of the SMS message. - `To number`: The phone number that you want to send the SMS message to. - `From number`: The phone number to use as the sender of the SMS message. :::note See the [Twilio documentation](https://www.twilio.com/docs/sms/send-messages) for more details. ::: ### List messages operation - `Date sent after`: (Optional) The date and time to start retrieving messages from. Messages sent on or after this date and time will be included in the results. The date and time must be in ISO 8601 format, such as `2023-04-19T08:30:00Z`. - `Date sent before`: (Optional) The date and time to stop retrieving messages at. Messages sent before this date and time will be included in the results. The date and time must be in ISO 8601 format, such as `2023-04-19T08:30:00Z`. - `From`: (Optional) The phone number that the message was sent from. Only messages sent from this phone number will be included in the results. - `To`: (Optional) The phone number that the message was sent to. Only messages sent to this phone number will be included in the results. - `Page size`: (Optional) The maximum number of messages to retrieve per page. This value must be between 1 and 1000. :::note See the Twilio documentation on [filtering by date sent](https://www.twilio.com/docs/sms/api/message-resource?code-sample=code-read-list-messages-filter-by-before-sent-date&code-language=curl&code-sdk-version=json) and [getting filters](https://www.twilio.com/docs/sms/api/message-resource?code-sample=code-read-list-messages-matching-filter-criteria&code-language=curl&code-sdk-version=json) for more information. ::: ### getMessage operation - `Message SID`: The SID of the message you want to retrieve. See the [Twilio documentation](https://www.twilio.com/docs/sms/api/message-resource?code-sample=code-fetch-message&code-language=curl&code-sdk-version=json) for more details. ## Handle connector response The **Twilio connector** is a protocol connector built on top of the HTTP REST connector. Therefore, handling the response is still applicable and can be done as described in the [HTTP REST connector response documentation](/components/connectors/protocol/rest.md#response). When using the **Twilio connector**, the response from the Twilio API will be available in a temporary local response variable. This variable can be mapped to the process by specifying the Result Variable. For example, if you use the **Send SMS Message** method in the Twilio connector, the response may look like this: ```json { "status": 201, "headers": { "content-type": "application/json" }, "response": { "sid": "SM1234567890", "date_created": "2023-04-18T15:30:00Z", "date_updated": "2023-04-18T15:30:00Z", "date_sent": null, "account_sid": "AC1234567890", "from": "+1234567890", "to": "+0987654321", "body": "Hello, World!", "status": "queued", "num_segments": "1", "direction": "outbound-api", "api_version": "2010-04-01", "price": null, "price_unit": "USD", "error_code": null, "error_message": null, "uri": "/2010-04-01/Accounts/AC1234567890/Messages/SM1234567890.json", "subresource_uris": { "media": "/2010-04-01/Accounts/AC1234567890/Messages/SM1234567890/Media.json" } } } ``` In this example, the response variable contains an SID attribute that uniquely identifies the message that was sent. You can choose to unpack the content of your response into multiple process variables using the **Result Expression**, which is a FEEL Context Expression. The Result Expression allows you to access specific attributes from the response and assign them to process variables that can be used in subsequent steps of your process. ```feel = { sid: response.body.sid, date_created: response.body.date_created, from: response.body.from, to: response.body.to, body: response.body.body } ``` In this example, we are using the Result Expression to extract the `sid`, `date_created`, `from`, `to`, and `body` attributes from the response variable and assign them to process variables with the same name. You can then use these variables in subsequent steps of your process. :::note The syntax for accessing attributes in the Result Expression may vary depending on the structure of your response object. You can refer to the [FEEL Context Expression](/components/modeler/feel/language-guide/feel-context-expressions.md) documentation for more information on how to use the **Result Expression**. ::: ## Troubleshooting If you are having issues with the Twilio connector, try the following: - Ensure your Twilio credentials are correct. - Ensure you have set up your Twilio account and have a valid phone number. - Ensure your configuration properties are set correctly. - Check the logs for any error messages. - Contact [Camunda support](https://camunda.com/services/support/) if you need further assistance. For more information on using Twilio, visit the [official documentation](https://www.twilio.com/docs). ## Using Twilio connector Best Practices When using the Twilio connector in a BPMN process, it is important to keep in mind that there may be delays in message delivery or processing, and that some messages may fail to be delivered due to various reasons such as invalid phone numbers, network issues, etc. To ensure that messages are sent and delivered reliably, it is recommended to build your BPMN diagram to handle retries and error scenarios. One way to achieve this is by using an intermediate timer event to trigger a retry after a certain amount of time has elapsed, or by using an error boundary event to catch and handle errors in the process. :::note To avoid performance issues, it is recommended to limit the number of retries and to implement proper error handling mechanisms in your process. ::: To learn more about implementing retry and error handling logic in your BPMN diagram, you can refer to the [Camunda BPMN examples](https://camunda.com/bpmn/examples/) page, which includes examples of BPMN diagrams with timer and error configurations. The **Twilio Webhook connector** is an inbound connector that enables you to start a BPMN process instance triggered by a [Twilio event](https://www.twilio.com/docs/usage/webhooks). ## Create a Twilio Webhook connector task 1. Start building your BPMN diagram. You can use the **Twilio Webhook connector** with either a **Start Event** or an **Intermediate Catch Event** building block. 2. Select the applicable element and change its template to a **Twilio Webhook connector**. 3. Fill in all required properties. 4. Complete your BPMN diagram. 5. Deploy the diagram to activate the webhook. 6. Navigate to the **Webhooks** tab in the properties panel to see the webhook URL. 7. Run the process if you use the **Twilio Webhook Intermediate Catch Event connector**, and only deploy the process if the diagram starts from the **Start Event**. ## Make your Twilio Webhook connector for receiving messages executable ### Fill properties in the Webhook Configuration section 1. Choose one of the required methods in the **Webhook method** property. For example, if you know the webhook will be triggered by the **POST** method, choose **POST**. Alternatively, if it is not essential to specify a specific method for the webhook trigger, select **ANY**. 2. Configure the **Webhook ID**. By default, the **Webhook ID** is pre-filled with a random value. This value will be part of the Webhook URL. For more details about Twilio Webhook URLs, refer to the section below on [activating the Twilio Webhook connector by deploying your diagram](#activate-the-twilio-webhook-connector-by-deploying-your-diagram). 3. Select **Enabled** in **HMAC authentication** if you want to use HMAC authentication. After that, set the [Twilio Auth Token](https://support.twilio.com/hc/en-us/articles/223136027-Auth-Tokens-and-How-to-Change-Them) as the shared secret key in the **HMAC secret key** field property. :::note Use secrets to store your credentials securely. Refer to the [secrets documentation](/components/saas/clusters/manage-secrets.md) for more details. ::: ### Fill in the properties in the **Activation** and **Correlation** sections #### Activation condition Optionally, configure the **Activation Condition**. For example, if an external message has the body: ``` { "body": { "ApiVersion": "2010-04-01", "FromCountry": "EU", "Body": "Hello world", "SmsStatus": "received" ... } ... } ``` The **Activation Condition** value might look like this: ``` =(request.body.SmsStatus="received") ``` Leave this field empty to receive all messages every time. :::note The **Correlation** section is not applicable for the plain **start event** element template of the Twilio connector. Plain **start events** are triggered by process instance creation and do not rely on message correlation. ::: #### Correlation keys - **Correlation key (process)** is a FEEL expression that defines the correlation key for the subscription. This corresponds to the **Correlation key** property of a regular **Message Intermediate Catch Event**. - **Correlation key (payload)** is a FEEL expression used to extract the correlation key from the incoming message. This expression is evaluated in the connector Runtime, and the result is used to correlate the message. For example, if your correlation key is defined with a process variable named `myCorrelationKey`, and you want to correlate by the `Body` property in the request body, which contains: ``` { "body": { "ApiVersion": "2010-04-01", "FromCountry": "EU", "Body": "Continue process", "SmsStatus": "received" ... } ... } ``` your correlation key settings will look like this: - **Correlation key (process)**: `=myCorrelationKey` - **Correlation key (payload)**: `=request.body.Body` Learn more about correlation keys in the [messages guide](../../../concepts/messages). #### Message ID expression The **Message ID expression** is an optional field that allows you to extract the message ID from the incoming request. The message ID serves as a unique identifier for the message and is used for message correlation. This expression is evaluated in the connector Runtime and the result is used to correlate the message. In most cases, it is not necessary to configure the **Message ID expression**. However, it is useful if you want to ensure message deduplication or achieve a certain message correlation behavior. Learn more about how message IDs influence message correlation in the [messages guide](../../../concepts/messages#message-correlation-overview). #### Message TTL The **Message TTL** is an optional field that allows you to set the time-to-live (TTL) for the correlated messages. TTL defines the time for which the message is buffered in Zeebe before being correlated to the process instance (if it can't be correlated immediately). The value is specified as an ISO 8601 duration. For example, `PT1H` sets the TTL to one hour. Learn more about the TTL concept in Zeebe in the [message correlation guide](../../../concepts/messages#message-buffering). ## Activate the Twilio Webhook connector by deploying your diagram Once you click the **Deploy** button, your Twilio Webhook will be activated and publicly available. The URLs of the exposed Twilio Webhooks adhere to the following pattern: `http(s):///inbound/` - `` is the URL of the connectors component deployment. When using the Camunda 8 SaaS offering, this will typically contain your **region Id** and **cluster Id**, found in your client credentials under the **API** tab within your cluster. - `` is the ID (path) you configured in the properties of your Twilio Webhook connector. :::note If you make changes to your Twilio Webhook connector configuration, you need to redeploy the BPMN diagram for the changes to take effect. ::: When you click on the event with the Twilio Webhook connector applied to it, a new **Webhooks** tab will appear in the properties panel. This tab displays the URL of the Twilio Webhook connector for every cluster where you have deployed your BPMN diagram. :::note The **Webhooks** tab is only supported in Camunda Hub as part of the Camunda 8 SaaS offering. You can still use the Twilio Webhook connector in the Desktop Modeler or with Camunda 8 Self-Managed. In that case, Twilio Webhook connector deployments and URLs will not be displayed in the Desktop Modeler. ::: ## Variable mapping The **Variable mapping** section allows you to configure the mapping of the webhook request to the process variables. - Use the **Result variable** to store the response in a process variable. For example, `myResultVariable`. - Use the **Result expression** to map specific fields from the response into process variables using [FEEL](/components/modeler/feel/what-is-feel.md). For example, given that the **Twilio Webhook connector** is triggered with the webhook: ``` { "body": { "ApiVersion": "2010-04-01", "FromCountry": "EU", "Body": "Hello world", "SmsStatus": "received" ... } ... } ``` and you would like to extract the `SmsStatus` as a process variable `mySmsStatus`, the **Result Expression** might look like this: ``` = { mySmsStatus: request.body.SmsStatus } ``` ## Configure your Twilio account To set a webhook URL in Twilio for SMS, follow these steps: 1. Log in to your Twilio account at [www.twilio.com/console](https://www.twilio.com/console). 2. Navigate to the **Phone Numbers** section, which you can find in the left-hand side menu. 3. Click on the phone number for which you want to set the webhook URL. 4. Scroll down to the **Messaging** section and locate the **A message comes in** field. 5. In the input box next to **A message comes in**, enter the URL where you want Twilio to send incoming SMS messages and choose the required method. 6. Save your changes. Once you have set the webhook URL, Twilio will send a `POST` or `GET` request to that URL whenever an incoming SMS message is received on the specified phone number. ## Next steps - Learn more about [Twilio webhooks](https://docs.github.com/en/developers/webhooks-and-events/webhooks/about-webhooks). - Read the [Twilio webhooks FAQ](https://www.twilio.com/docs/usage/webhooks/webhooks-faq). - Understand [Twilio webhooks security](https://www.twilio.com/docs/usage/webhooks/webhooks-security). - Learn about [other connectors available](./available-connectors-overview.md) in Camunda to integrate with different systems and services. - Learn more about using connectors [here](../use-connectors/index.md). - Learn more about inbound connectors [here](../use-connectors/inbound.md). --- ## UiPath connector The **UiPath connector** allows you to orchestrate a UiPath bot from your BPMN process with [UiPath](https://cloud.uipath.com). ## Prerequisites To use the **UiPath connector**, you need to have a [UiPath](https://cloud.uipath.com) account and configure your organization settings. See the [automation cloud guide](https://docs.uipath.com/automation-cloud/docs/introduction) to learn more. ## Create a UiPath connector task ## Operation types The UiPath connector currently supports two operation types in the **Operation type** dropdown list: _Add queue item_ and _Get queue item result by ID_. ### Authentication You can choose among the available UiPath connector authentication types according to your authentication requirements. ### UiPath connector (bearer token) #### Create a new connector secret We advise you to keep your **Bearer Token** safe and avoid exposing it in the BPMN `xml` file by creating a secret: 1. Follow our [guide for creating secrets](/components/saas/clusters/manage-secrets.md). 2. Name your secret (i.e `BEARER_TOKEN_UIPATH`) so you can reference it later in the connector. #### Configure the bearer token Select the **UiPath connector** and fill out the following properties under the **Authentication** section: 1. Click **Bearer Token** in the **Authentication** section. 2. Set **Bearer** to the secret you created (i.e. `{{secrets.UIPATH_BEARER_TOKEN}}`). ### UiPath connector (OAuth token) #### Create a new connector secret We advise you to keep your **Client ID** safe and avoid exposing it in the BPMN `xml` file by creating a secret: 1. Follow our [guide for creating secrets](/components/saas/clusters/manage-secrets.md). 2. Name your secret (i.e `UIPATH_CLIENT_ID`) so you can reference it later in the connector. #### Configure the OAuth Token Select the **UiPath connector** and fill out the following properties under the **Authentication** section: 1. Click **OAuth 2.0** in the **Authentication** section. 2. Set **Client ID** to the secret you created (i.e. `{{secrets.UIPATH_CLIENT_ID}}`). 3. Set **Client secret** to the secret you created (i.e. `{{secrets.UIPATH_CLIENT_SECRET}}`). 4. Choose **Client Authentication** from the dropdown menu (i.e. `{{Send client credentials in body}}`). Find more information about the OAuth client credentials flow in the [RFC reference](https://www.rfc-editor.org/rfc/rfc6749#section-4.4). ### Add queue item This operation allows you to create a new item and add it to a queue from UiPath Orchestrator. To execute it, take the following steps: 1. Select the operation **Add queue item** from the **Operation type** dropdown list. 2. Configure authentication as described in the [authentication](#authentication) section. 3. Fill out the input fields as described in the [configuration](#configuration) section. 4. Fill out the input fields as described in the [input](#input) section. 5. Fill out the response mapping as described in the [add queue item response](#add-queue-item-response) section. #### Configuration For this section, you must fill out the following fields: 1. **Cloud URL**: Comes with a default value of `cloud.uipath.com`. You can always change it, if needed. To use a connector secret, use a double curly braces notation, e.g. `{{secrets.MY_SECRET_VALUE}}`. 2. **Cloud organization**: The name of your organization. See [about organizations](https://docs.uipath.com/automation-cloud/docs/about-organizations) to learn more. To use a connectors secret, use a double curly braces notation, e.g. `{{secrets.MY_SECRET_VALUE}}`. 3. **Cloud tenant**: The name of the tenant. See [about tenants](https://docs.uipath.com/automation-cloud/docs/about-tenants) to learn more. To use a connectors secret, use a double curly braces notation, e.g. `{{secrets.MY_SECRET_VALUE}}`. 4. **Organization Unit ID**: Click **Orchestrator** and you will find the ID in the URL. For example, `https://cloud.uipath.com/MyOrg/MyTenant/orchestrator_/?tid=26929&fid=112233` where the **Organization Unit ID** is `112233`. To use a connectors secret, use a double curly braces notation, e.g. `{{secrets.MY_SECRET_VALUE}}`. #### Input For this section, fill out the following fields: 1. **Queue Name**: The queue where the QueueItem object is to be added. Check [queues and transactions](https://docs.uipath.com/orchestrator/docs/about-queues-and-transactions) to learn more. 2. _(Optional)_ **Defer date**: The earliest date and time at which the item is available for processing. If empty, the item can be processed as soon as possible. Expected date format is `yyyy-MM-dd`. 3. _(Optional)_ **Due date**: The latest date and time at which the item should be processed. If empty, the item can be processed at any given time. Expected date format is `yyyy-MM-dd`. 4. _(Optional)_ **Priority**: Select a value from the dropdown list to represent the priority level of the queue item to be added. This property is a criterion for the prioritization of queue items, alongside **Deadline** and **Postpone**. 5. _(Optional)_ **Specific Content for UiPath Job**: Data that will be passed in to the job. This must be in JSON format. ``` = { "Name":"testItemName", "Value":"testItemValue" } ``` 6. _(Optional)_ **Reference**: A string reference for the queue item. #### Add queue item response The operation **Add Queue Item** returns information about the newly created item in the queue. You can use an output mapping to map the response: 1. Use **Result Variable** to store the response in a process variable. For example, `myResultVariable`. 2. Use **Result Expression** to map fields from the response into process variables. It comes with a pre-filled value of `= {itemId: response.body.Id}`. To use operation _Get queue item result by ID_, you need an `itemId`. This expression will add it in the context for you. Learn more in [get queue item result by ID](#get-queue-item-result-by-id). Response example: ``` = { "status":201, "headers":{ "date":"Fri, 20 Jan 2023 10:13:20 GMT", "content-length":878, "server":"cloudflare", "expires":"-1", "cf-ray":"78c70973ce68153b-CDG", "api-supported-versions":"15.0", "x-frame-options":"Deny", "x-download-options":"noopen", "x-correlation-id":"7a211afe-53f1-4225-b77c-0fa477912685", "cf-cache-status":"DYNAMIC", "x-uipath-correlation-id":"undefined", "pragma":"no-cache", "strict-transport-security":"max-age=15724800; includeSubDomains", "request-context":"appId=cid-v1:354c7cb9-ae5a-4d16-84a7-f13242bbac6d", "content-security-policy":"default-src 'self';script-src 'self' https://orch-cdn.uipath.com https://use.typekit.net/ https://d2c7xlmseob604.cloudfront.net https://platform-cdn.uipath.com https://*.uipath.com https://*.pendo.io;style-src 'self' 'unsafe-inline' https://orch-cdn.uipath.com https://fonts.googleapis.com/css https://use.typekit.net https://p.typekit.net/ https://platform-cdn.uipath.com https://content.usage.uipath.com;img-src 'self' data: https://orch-cdn.uipath.com https://s.gravatar.com https://secure.gravatar.com https://*.wp.com https://*.googleusercontent.com https://i.ytimg.com https://platform-cdn.uipath.com https://*.pendo.io https://*.blob.core.windows.net https://*.amazonaws.com blob:;frame-src 'self' https://*.uipath.com https://*.pendo.io;font-src 'self' https://orch-cdn.uipath.com https://use.typekit.net/ https://fonts.gstatic.com https://platform-cdn.uipath.com data:;connect-src 'self' wss: https://orch-cdn.uipath.com https://primer.typekit.net https://use.typekit.net/ https://sentry.io https://studio-feedback.azure-api.net https://app.launchdarkly.com https://clientstream.launchdarkly.com https://events.launchdarkly.com https://api.smartling.com https://platform-cdn.uipath.com https://*.service.signalr.net https://*.uipath.com https://*.pendo.io https://cloud.uipath.com https://storage.googleapis.com https://*.blob.core.windows.net https://*.amazonaws.com dc.services.visualstudio.com;worker-src 'self' blob:", "x-xss-protection":"1", "x-content-type-options":"nosniff", "x-robots-tag":"noindex,nofollow", "content-type":"application/json; odata.metadata=minimal; odata.streaming=true", "location":"https://cloud.uipath.com/MyOrg/MyTenant/orchestrator_/odata/QueueItems(436141352)", "connection":"keep-alive", "cache-control":"no-cache, no-store, must-revalidate", "odata-version":"4.0" }, "body":{ "@odata.context":"https://cloud.uipath.com/MyOrg/MyTenant/orchestrator_/odata/$metadata#QueueItems/$entity", "QueueDefinitionId":165001, "Encrypted":false, "Status":"New", "ReviewStatus":"None", "Key":"2196eb07-c96a-4f47-a734-326dd5d58a9d", "Reference":"test", "Priority":"Low", "DeferDate":"2023-01-12T00:00:00Z", "SecondsInPreviousAttempts":0, "RetryNumber":0, "SpecificData":"{\"DynamicProperties\":{\"test\":\"test\"}}", "CreationTime":"2023-01-20T10:13:20.6603953Z", "RowVersion":"AAAAAE2f4GY=", "OrganizationUnitId":112233, "Id":436141352, "SpecificContent":{ "test":"test" } } } ``` ### Get queue item result by ID This operation allows you get an item from your UiPath Orchestrator. To execute it, take the following steps: 1. Select the operation **Get Queue Item result by ID** from the dropdown list **Operation type**. 2. Configure authentication as described in the [authentication](#authentication) section. 3. Fill out the **Item ID** field. This field supports FEEL, so you're able to fetch an item ID from the process context; for example, if you exported it while [adding a new queue item](#add-queue-item). #### Get queue item result by ID response Given you have a queue item ID previously added to a queue, the operation **Get queue item result by ID** returns information about a certain item. You can use an output mapping to map the response: 1. Use **Result Variable** to store the response in a process variable. For example, `myResultVariable`. 2. Use **Result Expression** to map fields from the response into process variables. It comes with a pre-filled value of `= {itemStatus: response.body.value[1].Status}`. You will see the `itemStatus` in the process variables. Its value will let you know if the item was processed or not. Response example: ``` { "status":200, "headers":{ "date":"Fri, 20 Jan 2023 10:13:21 GMT", "server":"cloudflare", "expires":"-1", "transfer-encoding":"chunked", "cf-ray":"78c709774a112a34-CDG", "api-supported-versions":"15.0", "x-frame-options":"Deny", "x-download-options":"noopen", "x-correlation-id":"8db50244-5f55-4598-82d3-1d6a00f806b0", "cf-cache-status":"DYNAMIC", "x-uipath-correlation-id":"undefined", "pragma":"no-cache", "strict-transport-security":"max-age=15724800; includeSubDomains", "request-context":"appId=cid-v1:354c7cb9-ae5a-4d16-84a7-f13242bbac6d", "content-security-policy":"default-src 'self';script-src 'self' https://orch-cdn.uipath.com https://use.typekit.net/ https://d2c7xlmseob604.cloudfront.net https://platform-cdn.uipath.com https://*.uipath.com https://*.pendo.io;style-src 'self' 'unsafe-inline' https://orch-cdn.uipath.com https://fonts.googleapis.com/css https://use.typekit.net https://p.typekit.net/ https://platform-cdn.uipath.com https://content.usage.uipath.com;img-src 'self' data: https://orch-cdn.uipath.com https://s.gravatar.com https://secure.gravatar.com https://*.wp.com https://*.googleusercontent.com https://i.ytimg.com https://platform-cdn.uipath.com https://*.pendo.io https://*.blob.core.windows.net https://*.amazonaws.com blob:;frame-src 'self' https://*.uipath.com https://*.pendo.io;font-src 'self' https://orch-cdn.uipath.com https://use.typekit.net/ https://fonts.gstatic.com https://platform-cdn.uipath.com data:;connect-src 'self' wss: https://orch-cdn.uipath.com https://primer.typekit.net https://use.typekit.net/ https://sentry.io https://studio-feedback.azure-api.net https://app.launchdarkly.com https://clientstream.launchdarkly.com https://events.launchdarkly.com https://api.smartling.com https://platform-cdn.uipath.com https://*.service.signalr.net https://*.uipath.com https://*.pendo.io https://cloud.uipath.com https://storage.googleapis.com https://*.blob.core.windows.net https://*.amazonaws.com dc.services.visualstudio.com;worker-src 'self' blob:", "x-xss-protection":"1", "x-content-type-options":"nosniff", "x-robots-tag":"noindex,nofollow", "content-type":"application/json; odata.metadata=minimal; odata.streaming=true", "connection":"keep-alive", "cache-control":"no-cache, no-store, must-revalidate", "odata-version":"4.0" }, "body":{ "@odata.context":"https://cloud.uipath.com/MyOrg/MyTenant/orchestrator_/odata/$metadata#QueueItems", "@odata.count":1, "value":[ { "QueueDefinitionId":165001, "Encrypted":false, "Status":"New", "ReviewStatus":"None", "Key":"2196eb07-c96a-4f47-a734-326dd5d58a9d", "Reference":"test", "Priority":"Low", "DeferDate":"2023-01-12T00:00:00Z", "SecondsInPreviousAttempts":0, "RetryNumber":0, "SpecificData":"{\"DynamicProperties\":{\"test\":\"test\"}}", "CreationTime":"2023-01-20T10:13:20.66Z", "RowVersion":"AAAAAE2f4GY=", "OrganizationUnitId":1964413, "OrganizationUnitFullyQualifiedName":"MyCorporateWorkspace", "Id":436141352, "SpecificContent":{ "test":"test" } } ] } } ``` ## Appendix ### Using UiPath connector best practice There is no guarantee a queue item will be processed right away. In that case, we suggest building your BPMN diagram to periodically retry polling. To learn more, see an entry _Solution with Timer and Loop_ at [Camunda BPMN examples](https://camunda.com/bpmn/examples/) page. :::note To avoid performance issues, it is recommended to limit the number of loop retries. ::: --- ## WhatsApp connector The **WhatsApp connector** is an outbound connector that allows you to send messages to users from your BPMN process. ## Prerequisites To start using the **WhatsApp connector**, you must have an approved Meta WhatsApp application; follow the [official guide](https://developers.facebook.com/docs/whatsapp/cloud-api/get-started) to obtain one. :::note WhatsApp webhooks are currently not supported by Camunda. ::: ## Create a WhatsApp connector task ## Authentication The **WhatsApp connector** supports authentication through Meta access tokens. Take a look at [this blog post](https://developers.facebook.com/blog/post/2022/12/05/auth-tokens/) to learn more on how to obtain one for yourself. Once the token is obtained, put it in the **Access token** field of the **Authentication** section. :::note Use secrets to avoid exposing your WhatsApp access token credentials as plain text. See our documentation on [managing secrets](/components/saas/clusters/manage-secrets.md) to learn more. ::: ## Sender and recipient Your WhatsApp application can have multiple phone numbers registered. Set your phone number ID in the **Sender phone number ID** field of the **Payload** section. You can find the phone number ID at the Meta developer portal. In the **Recipient phone number** field, enter a phone number you wish to send message to. ## Select operation to execute You can select one of the following operations from the **Message type** dropdown. ### Plain text When this option is selected, write any arbitrary text in the **Message text** field. This message will be sent to the target recipient. ### Message template When this option is selected, it is implied that you already have an approved WhatsApp message template. Read more bout message templates at the [official page](https://developers.facebook.com/docs/whatsapp/message-templates/guidelines/). 1. In the field **Template name**, set the name of your WhatsApp template. For example, **my_delivery_scheduled_template**. 2. In the field **Template language code**, specify the language code of your template. For example, **en_US**. 3. In the field **Header variables**, set the values for your variables only if the header has any. For example, `{"type": "text","text": "My header param"}`. 4. In the field **Body variables**, set the values for your variables only if the body has any. For example, `{"type": "text","text": "My body param"}`. See the [official Meta guide](https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-message-templates/) for more information and examples. ## Handle connector response The **WhatsApp connector** is a protocol connector, meaning it is built on top of the **HTTP REST connector**. Therefore, handling response is still applicable [as described](/components/connectors/protocol/rest.md#response). --- ## GraphQL connector The **GraphQL connector** is an outbound protocol connector that allows you to execute a GraphQL query or mutation from your BPMN process. ## Prerequisites The GraphQL connector allows you to connect to a GraphQL API endpoint. To use the GraphQL connector, you need to know the GraphQL endpoint URL, authentication, and available API methods. ## Create a GraphQL connector task ## Make your GraphQL connector executable To make the **GraphQL connector** executable, fill out the mandatory fields highlighted in red in the properties panel on the right side of the screen. :::note All the mandatory and non-mandatory fields are covered in the upcoming sections. Depending on the authentication selection you make, more fields might be required; this is covered in the next section. ::: ### Authentication You can choose among the available authentication types according to your authentication requirements using the **Authentication** section. ### None Click **None** in the **Authentication** section. No extra authentication configuration is required. ### Basic #### Create a new connector secret We advise you to keep your **Password** safe and avoid exposing it in the BPMN `xml` file by creating a secret: 1. Follow our [guide for creating secrets](/components/saas/clusters/manage-secrets.md). 2. Name your secret (i.e `GRAPHQL_PASSWORD`) so you can reference it later in the connector. ### Configure Basic Authentication Select the **GraphQL connector** and fill out the following properties under the **Authentication** section: 1. Click **Basic** in the **Authentication** section. 2. Set **Username** (i.e. `{{secrets.GRAPHQL_USERNAME}}`). 3. Set **Password** to the secret you created (i.e. `{{secrets.GRAPHQL_PASSWORD}}`). ### Bearer Token #### Create a new connector secret We advise you to keep your **Bearer Token** safe and avoid exposing it in the BPMN `xml` file by creating a secret: 1. Follow our [guide for creating secrets](/components/saas/clusters/manage-secrets.md). 2. Name your secret (i.e `GRAPHQL_BEARER_TOKEN`) so you can reference it later in the connector. #### Configure the Bearer Token Select the **GraphQL connector** and fill out the following properties under the **Authentication** section: 1. Click **Bearer Token** in the **Authentication** section. 2. Set **Bearer** to the secret you created (i.e. `{{secrets.GRAPHQL_BEARER_TOKEN}}`). ### OAuth token #### Create a new connector secret We advise you to keep your **OAUTH_TOKEN_ENDPOINT** safe and avoid exposing it in the BPMN `xml` file by creating a secret: 1. Follow our [guide for creating secrets](/components/saas/clusters/manage-secrets.md). 2. Name your secret (i.e `OAUTH_TOKEN_ENDPOINT`) so you can reference it later in the connector. #### Configure the OAuth Token Select the **GraphQL connector** and fill out the following properties under the **Authentication** section: 1. Click **OAuth 2.0** in the **Authentication** section. 2. Set **OAuth Token Endpoint** to the secret you created (i.e. `{{secrets.OAUTH_TOKEN_ENDPOINT}}`). 3. Set **Client ID** to the secret you created (i.e. `{{secrets.CLIENT_ID}}`). 4. Set **Client secret** to the secret you created (i.e. `{{secrets.CLIENT_SECRET}}`). 5. (Optional) Set **Scopes** (i.e. `read:clients`). Depending on the OAuth provider you're using, this may or may not be required. 6. Set **Audience** to the secret you created (i.e. `{{secrets.AUDIENCE}}`). This is an optional field depending on the OAuth provider you're using. 7. Choose **Client Authentication** from the dropdown menu (i.e. `Send client credentials in body`). Find more information about the OAuth client credentials flow in the [RFC reference](https://www.rfc-editor.org/rfc/rfc6749#section-4.4). ## HTTP endpoint Under the **HTTP Endpoint** section, fill in the **URL** with your desired endpoint and select the desired **Method**. ## GraphQL query ### Query/Mutation Insert your query or mutation you wish to execute here. This must be a syntactically valid instruction. For more details, see [the official documentation](https://graphql.org/learn/queries/). You can use [arguments](https://graphql.org/learn/queries/#arguments), [aliases](https://graphql.org/learn/queries/#aliases), [directives](https://graphql.org/learn/queries/#directives), and [fragments](https://graphql.org/learn/queries/#fragments) as well. For example: ```text query Root($id: ID) { person (id: $id) { id name } } ``` :::note Secrets are currently not supported in the **Query/Mutation** of a GraphQL connector. ::: :::note You can test your queries on publicly available GraphQL API [here](https://studio.apollographql.com/public/star-wars-swapi/home?variant=current). ::: #### Example ```text query Query { allFilms { films { title director releaseDate speciesConnection { species { name classification } } } } } ``` ### Variables You can specify [variables](https://graphql.org/learn/queries/#variables) to your queries/mutations. The **Variables** field can be configured using the [FEEL Map](/components/modeler/feel/language-guide/feel-data-types.md#context) data type. ```text = { "id": "{{secrets.GRAPHQL_ENTITY_ID}}", "includeDroids": false, } ``` :::note Secrets are not like regular variables and must be wrapped in double quotes (`"`) when used in an expression. ::: #### Example Query: ```text query Root($id: ID, $includeGender: Boolean!) { person (id: $id) { name, height, gender @include(if: $includeGender) } } ``` Variables: ```text { "id": "cGVvcGxlOjI=", "includeGender": false } ``` ### Network communication timeouts - **Connection timeout in seconds** determines the time frame in which the client will try to establish a connection with the server. If you do not specify a value, the system uses the default of 20 seconds. For cases where you need to wait indefinitely, set this value to 0. - **Read timeout in seconds** is the amount of time the client will wait to read data from the server after the connection has been made. The default is also set to 20 seconds. To allow an unlimited wait time for slow responses, set this to 0. ## Response mapping The HTTP response will be available in a temporary local `response` variable. This variable can be mapped to the process by specifying the **Result Variable**. The following fields are available in the `response` variable: - **status**: Response status - **body**: Response body of your request. Populated when the **Response format** is **As text** or **As JSON**. - **headers**: Response headers - **document**: Populated when the **Response format** is **Document reference**; a reference to the response stored in the Camunda document store. ### Response format Choose how the response body is returned with the **Response format** dropdown, a [return format](/components/document-handling/send-document-to-external-system.md#return-formats): - **As JSON** (default): the body is parsed as JSON and returned in `body`. - **As text**: the body is decoded as a string (with an optional encoding, default UTF-8) and returned in `body`. - **Document reference**: the body is streamed to the Camunda document store and a reference is returned in `document`. :::note The **As JSON** default fails the job when the response body is not valid JSON. Use **As text** for non-JSON responses. **As text** and **As JSON** are subject to a size guard (approximately 1.5 MiB); use **Document reference** for large responses. ::: Additionally, you can choose to unpack the content of your `response` into multiple process variables using the **Result Expression**, which is a [FEEL Context Expression](/components/modeler/feel/language-guide/feel-context-expressions.md). ```text = { person: response.body.data.person } ``` The next steps in your process will have access to the `graphqlQueryResponse` variable that contain the full response and the mapped variable from the result expression: `person`. --- ## HTTP Webhook connector The **HTTP Webhook connector** is an inbound connector that allows you to start a BPMN process instance triggered by external HTTP call. ## Create an HTTP Webhook connector event 1. Start building your BPMN diagram. You can use HTTP Webhook connector with either **Start Event** or **Intermediate Catch Event** building blocks. 2. Select the applicable element and change its template to an HTTP Webhook. 3. Fill in all required properties. 4. Complete your BPMN diagram. 5. Deploy the diagram to activate the webhook. 6. Navigate to the **Webhooks** tab in the properties panel to see the webhook URL. ## Make your HTTP Webhook connector executable 1. In the **Webhook Configuration** section, configure the **Webhook ID**. By default, **Webhook ID** is pre-filled with a random value. This value will be part of the Webhook URL. You will find more details about HTTP Webhook URLs [below](#activate-the-http-webhook-connector-by-deploying-your-diagram). 2. Configure [**HMAC authentication**](https://en.wikipedia.org/wiki/HMAC) if required in the **Authentication** section: If you require HMAC authentication for your webhook, you can set up the HMAC shared secret key, HMAC header, HMAC hash algorithm, and an array of HMAC scopes. - **HMAC secret key**: Set the HMAC shared secret key that will be used to calculate the message hash. The value of this key should be provided by the webhook provider to ensure secure communication. - **HMAC header**: Set the HMAC header whose value contains an encrypted hash message. The exact value of this header should be provided by the external caller when invoking your webhook. - **HMAC hash algorithm**: Select the HMAC hash algorithm to be used in the HMAC signature calculation. The exact value of this algorithm should also be provided by the external caller when invoking your webhook. - **HMAC Scopes** (optional): Here, you can define an array of HMAC scopes to specify which parts of the webhook request are included in the HMAC signature calculation. The available HMAC scopes are: - `BODY` (default value): Includes the body of the webhook request in the HMAC signature calculation. - `URL`: Includes the URL of the webhook request in the HMAC signature calculation. - `PARAMETERS`: Includes the query parameters of the URL in the HMAC signature calculation. Example: `=["URL","PARAMETERS"]` Based on the selected HMAC scopes and the HTTP method used in the webhook request, the system will automatically determine the appropriate HMAC strategy to be used. The supported HMAC strategies are: - **Body Encoding Strategy (default)**: This strategy is used when the HMAC scopes only include `BODY` or empty. The signature data for the HMAC signature is generated from the body of the webhook request. - **URL and Parameters Encoding Strategy**: This strategy is used when the HMAC scopes include `URL`, and `PARAMETERS`, and the HTTP method is `GET`. The signature data for the HMAC signature is generated by combining the URL and the parameters of the webhook request. - **URL and Body Encoding Strategy**: This strategy is used when the HMAC scopes include `URL`, `BODY`. When the HTTP method is `GET`, it uses the **URL and Parameters Encoding Strategy** instead. The signature data for the HMAC signature is generated by combining the URL and the body of the webhook request. **Example for URL and Parameters Encoding Strategy**: Let's consider a sample webhook request: ``` HTTP Method: GET Webhook URL: "https://example.com/webhook?id=123456&name=John%20Doe" ``` In this example, the HMAC strategy will combine the URL and the query parameters to generate the signature data for the HMAC signature. The URL-encoded query parameters will be sorted alphabetically, and then, they will be concatenated with the URL: Signature Data: `https://example.com/webhook?name=John%20Doe&id=123456` The `Signature Data` will then be used to calculate the HMAC signature using the provided secret key and hash algorithm. **Example for URL and Body Encoding Strategy**: Let's consider another sample webhook request: ``` HTTP Method: POST Webhook URL: `https://example.com/webhook` Webhook Body: `{"id": 123456, "name": "John Doe", "age": 30}` ``` In this example, the HMAC strategy will combine the URL and the body of the webhook request to generate the signature data for the HMAC signature: Signature Data: `https://example.com/webhook{"id":123456,"name":"John Doe","age":30}` The `Signature Data` will then be used to calculate the HMAC signature using the provided secret key and hash algorithm. 3. Configure authorization if required in the **Authorization** section. The HTTP Webhook connector supports the following authorization methods: - **Basic** - The incoming requests must contain an `Authorization` header that contains the word `Basic` followed by a space and a base64-encoded string username:password. - Set the **Username** and **Password** properties which will be used to validate the incoming requests. - Provide the values in plain text, not base64-encoded. - **API Key** - The API key can be provided anywhere in the request, for example, in the `Authorization` header or in the request body. - Set the **API Key** property to the expected value of the API key. - Set the **API Key locator** property that will be evaluated against the incoming request to extract the API key. [See the example](#how-to-configure-api-key-authorization). - **[JWT authorization](https://jwt.io/)** - The token should be in the _Authorization_ header of the request in the format of Bearer `{JWT_TOKEN}`. - Set JWK URL which is used as a well-known public URL to fetch the [JWKs](https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets). - Set JWT role property expression which will be evaluated against the content of the JWT to extract the list of roles. See more details on extracting roles from JWT data [here](#how-to-extract-roles-from-jwt-data). - Set the required roles which will be used to validate if the JWT contains all required roles. See more details on extracting roles from JWT data [here](#how-to-extract-roles-from-jwt-data). 4. Configure **Activation Condition**. For example, given external caller triggers a webhook endpoint with the body `{"id": 1, "status": "OK"}`, the **Activation Condition** value might look like `=(request.body.status = "OK")`. Leave this field empty to trigger your webhook every time. 5. Use **Variable Mapping** to map specific fields from the request into process variables using [FEEL](/components/modeler/feel/what-is-feel.md). For example, given the external caller triggers a webhook endpoint with the body `{"id": 1, "status": "OK"}` and you would like to extract `id` as a process variable `myDocumentId`, the **Result Expression** might look like this: ``` = { myDocumentId: request.body.id } ``` 6. Fill in the **Correlation** parameters if they are required by the element template. ### Correlation The **Correlation** section allows you to configure the message correlation parameters. :::note The **Correlation** section is not applicable for the plain **start event** element template of the Webhook connector. Plain **start events** are triggered by process instance creation and do not rely on message correlation. ::: #### Correlation key - **Correlation key (process)** is a FEEL expression that defines the correlation key for the subscription. This corresponds to the **Correlation key** property of a regular **Message Intermediate Catch Event**. - **Correlation key (payload)** is a FEEL expression used to extract the correlation key from the incoming message. This expression is evaluated in the connector Runtime and the result is used to correlate the message. For example, given that your correlation key is defined with `orderId` process variable, and the request body contains `{"orderId": "123"}`, your correlation key settings will look like this: - **Correlation key (process)**: `=orderId` - **Correlation key (payload)**: `=request.body.orderId` Learn more about correlation keys in the [messages guide](../../../concepts/messages). #### Message ID expression The **Message ID expression** is an optional field that allows you to extract the message ID from the incoming request. The message ID serves as a unique identifier for the message and is used for message correlation. This expression is evaluated in the connector Runtime and the result is used to correlate the message. In most cases, it is not necessary to configure the **Message ID expression**. However, it is useful if you want to ensure message deduplication or achieve a certain message correlation behavior. Learn more about how message IDs influence message correlation in the [messages guide](../../../concepts/messages#message-correlation-overview). For example, if you want to set the message ID to the value of the `transactionId` field in the incoming message, you can configure the **Message ID expression** as follows: ``` = request.body.transactionId ``` #### Message TTL The **Message TTL** is an optional field that allows you to set the time-to-live (TTL) for the correlated messages. TTL defines the time for which the message is buffered in Zeebe before being correlated to the process instance (if it can't be correlated immediately). The value is specified as an ISO 8601 duration. For example, `PT1H` sets the TTL to one hour. Learn more about the TTL concept in Zeebe in the [message correlation guide](../../../concepts/messages#message-buffering). ### XML payloads You can send XML to the webhook or return XML using the response expression, but XML content is treated as a plain string. It will not be parsed or extracted into process variables. If you need to use correlation keys with XML payloads, send the correlation key in a request header and retrieve it using the **Correlation key (payload)** property. For example: ```feel =request.headers["x-correlation-id"] ``` ### Response mode **Response mode** controls whether the webhook waits for the correlation to complete (synchronous) or returns immediately (asynchronous). Use synchronous mode only when the caller requires the process result: - **Start event**: Creates a new process instance and returns its result. - **Message start event** (and other message-based events): Correlates the message and returns the matched process instance key. In asynchronous mode: - **Start event**: Returns the process instance key of the newly created process. - **Message-based events**: Publishes the message and returns the message key. ## Activate the HTTP Webhook connector by deploying your diagram Once you click the **Deploy** button, your HTTP Webhook will be activated and publicly available. You can trigger it by making a POST request to the generated URL. :::warning Webhook endpoints and process versions When you deploy a new process version that uses the same webhook endpoint as an existing version, the new version won’t become active until no process instances are running on the older version. Webhook endpoints can’t be shared across multiple active process versions. To activate the new version, complete all process instances on the older version or migrate them using [process instance migration](/components/concepts/process-instance-migration.md). If you need both versions active, use a different webhook endpoint path in the new version. For related behavior across versions, see [Cross-version deduplication](../advanced-topics/deduplication.md#cross-version-deduplication). ::: URLs of the exposed HTTP Webhooks adhere to the following pattern: `http(s):///inbound/>` - `` is the URL of connectors component deployment. When using the Camunda 8 SaaS offering, this will typically contain your **region Id** and **cluster Id**, found in your client credentials under the **API** tab within your cluster. - `` is the ID (path) you configured in the properties of your HTTP Webhook connector. If you make changes to your HTTP Webhook connector configuration, you need to redeploy the BPMN diagram for the changes to take effect. When you click on the event with HTTP Webhook connector applied to it, a new **Webhooks** tab will appear in the properties panel. This tab displays the URL of the HTTP Webhook connector for every cluster where you have deployed your BPMN diagram. :::note The **Webhooks** tab is only supported in Camunda Hub as part of the Camunda 8 SaaS offering. You can still use HTTP Webhook connector in Desktop Modeler, or with your Camunda 8 Self-Managed. In that case, HTTP Webhook connector deployments and URLs will not be displayed in Desktop Modeler. ::: For self-managed installations, the webhook URL format is: `http(s):////inbound/` If no `contextPath` is specified in the Helm chart, it must be omitted from the URL. ### Request limits The Connector Runtime applies the following limits to HTTP Webhook requests: | Request | Default limit | Response when exceeded | | ----------------------------------------- | -------------------------------------------------- | ---------------------- | | Request body except `multipart/form-data` | 10 MB | HTTP `413` | | `multipart/form-data` request body | 10 MB total | HTTP `413` | | `multipart/form-data` file | 10 MB per file | HTTP `413` | | Request rate | 1,000 requests per second across all webhook paths | HTTP `429` | One global rate limiter is shared by all webhook paths, including unregistered paths. For non-multipart requests, the controller applies the rate limit before it returns HTTP `404` for an unregistered path or reads the request body. The servlet container can parse and size-check `multipart/form-data` requests before the controller applies the rate limit. Requests that exceed the global rate limit return HTTP `429`. HTTP `404`, `413`, and `429` responses have an empty body. For Self-Managed deployments, you can [configure the request limits](/self-managed/components/connectors/connectors-configuration.md#configure-inbound-webhook-request-limits). ### Example Give a use-case when you need to configure a GitHub webhook with an **HTTP Webhook connector** in such a way that: (1) your BPMN process starts on every opened PR, and (2) the PR URL is exposed as a process variable. Let's say you choose `mySecretKey` as a shared secret passphrase. GitHub [declares](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks) that they use `X-Hub-Signature-256` header for `SHA-256` HMAC. Therefore, you would need to set the following: 1. **Webhook ID**: any unique to your cluster webhook ID. This will generate a URL to trigger your webhook. In example, `myWebhookPath`. 2. **HMAC Authentication**: `enabled`. 3. **HMAC Secret Key**: `mySecretKey` or `{{secrets.MY_GH_SECRET}}`. 4. **HMAC Header**: `X-Hub-Signature-256`. 5. **HMAC Algorithm**: `SHA-256`. 6. **HMAC Scopes**: `=["BODY"]` or leave empty. 7. **Activation Condition**: `=(request.body.action = "opened")`. 8. **Variable Mapping**: `={prUrl: request.body.pull_request.url}`. 9. Click **Deploy**. ### How to configure API key authorization External callers can provide an API key anywhere in the requests. Some webhook providers use an `Authorization` header, while others pass the API key in the request body. To support any scenario, you can configure the HTTP Webhook connector to extract the API key from the request. Use the **API Key locator** field to provide a FEEL expression that will be evaluated against the request to extract the API key. The result of this expression will be used as the API key and compared against the expected API key value. Use the **API Key** field to provide the expected API key value. #### API key locator examples Suppose an external caller triggers a webhook endpoint with the following request body: ```json { "id": 1, "status": "OK", "secret": "my_secret" } ``` You want to extract the `secret` field and use it as the API key to authorize the webhook request. In this case, you can set the **API Key locator** to: ```feel =request.body.secret ``` The expression above will be evaluated to `my_secret`, which will be used as the API key. Alternatively, you can use the **API Key locator** to extract the API key from the `Authorization` header: ```feel =request.headers.authorization ``` If your `Authorization` header contains the **Bearer** prefix, you can use the [`split`](/components/modeler/feel/builtin-functions/feel-built-in-functions-string.md#splitstring-delimiter) function to remove it: ```feel =split(request.headers.authorization, " ")[2] ``` ### How to extract roles from JWT data To extract roles from the JWT payload, specify the **JWT role property expression** using the FEEL expression syntax. :::note This expression will be evaluated only against the JWT payload, therefore you cannot access process variables or secrets here. ::: #### JWT payload and role property expression example Let's observe a typical JWT payload example below: ```json { "iss": "https://idp.local", "aud": "api1", "sub": "5be86359073c434bad2da3932222dabe", "client_id": "my_client_app", "exp": 1786822616, "iat": 1686819016, "jti": "114f8c84c53703ac2120d302611e358c", "roles": ["admin", "superadmin"], "admin": true } ``` To extract the roles you can set the **JWT role property expression** to: ```feel if admin = true then ["admin"] else roles ``` Note: the result of this expression should always be an array. In this particular case, the if statement is evaluated to true, the extracted roles will be: ```feel ["admin"] ``` If you provide _["admin"]_ for **Required roles**, the message _can be correlated_. If you provide _["superadmin"]_ or _["admin","superadmin"]_, for **Required roles**, for example, the message _can NOT be correlated_ and the connector will throw an exception. :::note For GitHub, there is a simplified [GitHub Webhook connector](/components/connectors/out-of-the-box-connectors/github.md). ::: ## Return data from your HTTP Webhook connector Below, find several ways to return data from your Webhook connector. ### Verification expression The verification expression allows you to return a custom HTTP response without triggering process correlation. This feature supports multiple scenarios beyond one-time verification challenges, including: - **One-time verification challenges**: Respond to webhook provider verification requests (e.g., Slack, GitHub). - **Request validation**: Reject invalid or malformed requests before a process instance is created. - **Conditional responses**: Return different responses based on request content. #### How it works When the verification expression evaluates to a **non-null** value: 1. The webhook returns the specified response immediately. 2. Process correlation does not occur. 3. Activation conditions, HMAC validation, and the rest of the webhook pipeline are skipped. When the verification expression evaluates to **null**: 1. The webhook continues with normal processing. 2. Activation conditions are evaluated. 3. HMAC validation (if configured) is performed. 4. Process correlation proceeds as usual. #### Response format Use the following references to access incoming request data: - Body: `request.body.` - Headers: `request.headers.` - URL parameters: `request.params.` To construct a response, use these fields in the returned object: - **`body`** – for example: `{"body": {"challenge": request.body.challenge}}` - **`statusCode`** – for example: `{"statusCode": 201, "body": {"result": "ok"}}` If omitted, the default status code is **200**. - **`headers`** – for example: `{"headers": {"Content-Type": "application/json"}, "body": {"result": "ok"}}` Use FEEL expressions to build and transform the response content as needed. #### Example: One-time verification challenge A common use-case is a [one-time verification challenge](https://webhooks.fyi/security/one-time-verification-challenge). For example, consider the following verification challenge from [Slack](https://slack.com/): ```json { "token": "Jhj5dZrVaK7ZwHHjRyZWjbDl", "challenge": "3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P", "type": "url_verification" } ``` To confirm the Slack events subscription, you must return the following response: ``` HTTP 200 OK Content-type: application/json {"challenge":"3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P"} ``` To do so, the **Verification expression** field may look like: ```feel =if request.body.type = "url_verification" then {"body": {"challenge": request.body.challenge}, "statusCode": 200} else null ``` #### Example: Request validation You can validate incoming requests and reject invalid payloads before creating a process instance: ```feel =if request.body.requiredField = null then {"body": {"error": "Missing required field"}, "statusCode": 400} else null ``` This expression checks if `requiredField` is missing and returns a `400 Bad Request` status with an error message. If the field is present, it returns `null`, allowing normal webhook processing to continue. #### Example: Custom status codes You can return any HTTP status code, including non-standard or conflict codes: ```feel =if request.body.challenge != null then {"body": {"challenge": request.body.challenge}, "statusCode": 409} else null ``` #### Example: Custom headers You can include custom headers in your response: ```feel =if request.body.challenge != null then { "body": {"challenge": request.body.challenge}, "headers": {"Content-Type": "application/camunda-bin"} } else null ``` #### Example: Nested body structure You can access and respond to nested data structures in the request: ```feel =if request.body.event_type = "verification" then {"body": {"challenge": request.body.event.challenge}} else null ``` This example extracts the challenge from a nested `event` object and returns it in the response. ### Response expression :::note Prior to 8.6, the HTTP Webhook connector supported a response body expression. As of 8.6, this was replaced with a more powerful construct that allows control over not only the response body, but also the headers and the HTTP status returned by the connector. ::: #### Use the request You can use a response expression to return data after the webhook has been invoked. You can use FEEL to return the request body, headers, and the HTTP status to the client invoking the Webhook connector endpoint. For example, given a webhook request with the payload body: ```json { "myDataKey1": "myValue1", "myDataKey2": "myValue2" } ``` You can return `myValue1` in a new key `myCustomKey` with a response expression such as: ```json ={ "body": {"myCustomKey": request.body.myDataKey1} } ``` The default HTTP status code is `200`. You can change it by including a `statusCode` key in your expression: ```json ={ "body": "hello", "statusCode": 201 } ``` Headers are also supported, using the `headers` key in the response expression: ```json ={ "headers": {"Content-Type": "text/html"}, "body": "Hello world!" } ``` When working with `request` data, use the following references to access data: - Body: `request.body.`. - Headers: `request.headers.`. - URL parameters: `request.params.`. You can also use FEEL expressions to modify the data you return. #### Use the `correlation` object When using the Webhook connector with a start event that correlates a message, you can access the `correlation` object in the response expression. In addition to the `request` object you have access to the `correlation` result. The data available via the `correlation` object depends on the type of BPMN element you are using the Webhook connector with. The following overview summarizes the different data points available when using the Webhook connector with different element types and configuration options. **`correlation` payload by response mode** The table below shows which `correlation` result type is produced for each supported BPMN element type and response mode. The result type determines what is available in the **response expression** context (`correlation` property) and in the HTTP response returned to the caller. *Element Type* Response mode: **Synchronous** Response mode: **Asynchronous** **Start Event** ``` { "processInstanceKey": 123, "tenantId": "abc", "variables": {...} } ``` The `variables` contain the result of the process execution. ``` { "processInstanceKey": 123, "tenantId": "abc" } ``` The result of the process execution is not available, but it can be fetched via the API using the `processInstanceKey`. **Message Start Event** **Intermediate Catch Event** **Boundary Event** **Receive Task** ``` { "processInstanceKey": 123, "messageKey": 123, "tenantId": "abc" } ``` The `processInstanceKey` contains the first process instance key with which the message correlated. ``` { "messageKey": 123, "tenantId": "abc" } ``` The message is buffered and only the `messageKey` is returned. Correlation to a subscription happens asynchronously in the engine. A start event with a message definition uses message publishing internally to correlate an incoming request with Zeebe. A successful correlation therefore publishes a message, and the `correlation` object contains the following properties when using the `asynchronous` response mode: ```json { "messageKey": 2251799813774245, "tenantId": "" } ``` If a Webhook request is processed more than once using the same _Message ID_ (for example, because of a retry), the `correlation` object is empty. A start event without a message creates a new process instance. You therefore have access to the newly created process instance key when accessing the `correlation` object: ```json { "processInstanceKey": 6755399441144562, "tenantId": "" } ``` If the `synchronous` response mode is selected, the response also contains the result `variables` from the execution. ```json { "processInstanceKey": 6755399441144562, "tenantId": "", "variables": {...} } ``` #### Use the `documents` object You can access created documents in both the response expression and the result expression. The `documents` object contains the references for created documents. See additional details and limitations in [document handling](/components/document-handling/getting-started.md). **Example response expression** ```json { "body": { "message": "Document created", "documents": documents } } ``` If the `documents` list is not empty, document items are returned in the following format (example values provided): ```json { "storeId": "in-memory", "documentId": "2b7215da-12b1-4374-8743-85d6854fcba5", "metadata": { "size": 405551, "expiresAt": null, "fileName": "my-image.jpg", "customProperties": null, "contentType": "image/jpeg" } } ``` :::note Request parts are automatically stored in the configured document store when sending a multipart request. ::: To extract a document from a base64-encoded field in a non-multipart request body, use the [`createDocument` FEEL function](/components/connectors/use-connectors/index.md#function-createdocument) in the **Result expression** field. --- ## HTTP Polling connector The **HTTP Polling connector** polls an endpoint at regular intervals, enabling periodic data fetching as an intermediate step in your BPMN processes. This connector is built on top of the [REST connector](../protocol/rest.md), ensuring consistent functionality and reliability. :::caution If you use the HTTP Polling connector, ensure you do not have any process variable named in the list below, as these are reserved words for this connector: - body, url, method, headers, authentication, queryParameters, connectionTimeoutInSeconds, httpRequestInterval ::: ## Prerequisites Ensure that you have: - An HTTP endpoint that supports polling. - Necessary credentials if the endpoint demands authentication. :::note Execution Exception Handling If the HTTP Polling connector encounters an execution exception while polling, it will ignore the exception and attempt to execute the request again after the next interval delay. Ensure to monitor your logs for any recurring issues. ::: ## Setting up the HTTP Polling connector 1. Add an **Intermediate Event** to your BPMN diagram. 2. Change its template to the **HTTP Polling connector**. 3. Populate all mandatory fields, like the endpoint URL, polling interval, and required headers. 4. Complete your BPMN diagram. 5. Deploy the diagram to activate the **HTTP Polling connector**. ## Configuring the HTTP Polling connector ### Authentication Navigate to the **Authentication** section and select your desired **Authentication type** (e.g., Basic, OAuth). Refer to the [Authentication section of the REST connector documentation](/components/connectors/protocol/rest.md#authentication) for a comprehensive guide. ### HTTP polling configuration - **Method**: Choose the HTTP method for your request, e.g., GET, POST, PUT. - **URL**: Enter the URL of the targeted HTTP endpoint. - **Headers** (Optional): Input required headers as per the external service. Learn more about headers in the [REST connector headers](/components/connectors/protocol/rest.md#http-headers) section. - **Query Parameters** (Optional): Add necessary query parameters for the endpoint. More details can be found in the [REST connector query parameters](/components/connectors/protocol/rest.md#query-parameters) section. - **Interval** (Optional): Set the frequency for polling the endpoint in ISO 8601 durations format. The default interval is 50 seconds. Review [how to configure a time duration](../../modeler/bpmn/timer-events/timer-events.md#time-duration) for details. - **Connection Timeout**: Define how long (in seconds) the connector waits before timing out. Further information on this can be found [here](/components/connectors/protocol/rest.md#connection-timeout). ### Payload configuration (optional) In the **Payload** section, you can include a **request body**. Learn more about this [here](/components/connectors/protocol/rest.md#request-body). ### Condition to proceed 1. **Correlation key (process)**: Defines the correlation key based on the process instance. - **Example**: Using a process variable named `orderId`: ``` Correlation key (process): =orderId ``` 2. **Correlation key (payload)**: Extracts the correlation key from the polled data. - **Example**: With data like `{"orderId": "123"}`: ``` Correlation key (payload): =body.orderId ``` 3. **Activation Condition**: Checks if the polled data meets criteria to activate the intermediate catch event. - **Example**: If the data should have a `status` of "OK": ``` Activation Condition: =(body.status = "OK") ``` For more information about correlation keys, review the [messages guide](../../../concepts/messages). ## Handling HTTP connector responses The response from any HTTP connector contains the status, headers, and body. Learn more about the response structure [here](/components/connectors/protocol/rest.md#response). To structure and utilize the response: 1. Set a **Result Variable** to store the HTTP response, e.g., `pollingData`. 2. Use a **Result Expression** to extract specific fields from the `={fieldProperty:body.fieldProperty}`. ## Examples ### Scenario 1: Monitoring GitHub issues Monitor a GitHub issue to see when it's closed and if it has a specific label ('needs review'). #### Steps 1. Drag an intermediate event onto your BPMN diagram. 2. Choose the HTTP Polling connector template. 3. Configure the connector with the relevant details: - **URL**: `https://api.github.com/repos/[YourRepoOwner]/[YourRepoName]/issues/[IssueNumber]` - **Authorization Type**: Bearer token - **Bearer token**: `{{secrets.BEARER_TOKEN}}` - **Method**: `GET` - **Headers**: `={"Content-Type": "application/vnd.github+json","X-GitHub-Api-Version": "2022-11-28"}` - **Interval**: `PT10M` (Every 10 minutes) – This checks the GitHub issue every 10 minutes. - **Correlation Key (process)**: `=issueNumber` - **Correlation Key (payload)**: `=body.number` - **Activation Condition**: `=(body.state = "closed")` - **Result Expression**: `={issueUrl:body.html_url, needsReview: list contains((body.labels).name, "needs review")}` - Extract the issue URL and check if the label 'needs review' is present. #### Example response ```json { "status": 200, "body": { "number": 212, "title": "Important Issue", "labels": [{ "name": "bug" }, { "name": "needs review" }], "state": "closed", "html_url": "https://github.com/YourRepoOwner/YourRepoName/issues/212" } } ``` In this scenario, once the issue #212 titled **Important Issue** is closed, the process will proceed. If the issue is also labeled **needs review**, this label can be leveraged in the next steps of the process. For instance, it can trigger the creation of a new issue for review or initiate other related actions. ### Scenario 2: Monitoring product stock levels Suppose you're overseeing an e-commerce platform. It's vital to ensure certain popular products remain stocked to guarantee user satisfaction. Avoiding stock-outs is essential to prevent lost sales and keep customers happy. With Camunda's HTTP Polling connector, you can maintain a real-time stock level check. #### Steps 1. Drag an intermediate event onto your BPMN diagram. 2. Choose the HTTP Polling connector template. 3. Configure the connector as follows: - **URL**: `https://inventory.yourstore.com/api/v2/products/12345/stock` - **Authorization Type**: Basic Authentication - **Username**: `[YourInventoryAPIUsername]` - **Password**: `{{secrets.PASSWORD}}` - **Interval**: `PT1H` (Every hour) - **Correlation Key (process)**: `=productID` - **Correlation Key (payload)**: `=body.productID` - **Activation Condition**: `=(body.stockLevel < 10)` - **Result Expression**: `={stockLevelResponse:body.stockLevel}` #### Example response ```json { "status": 200, "body": { "productID": 12345, "productName": "Wireless Bluetooth Earbuds", "stockLevel": 8, "lastUpdated": "2023-09-17T11:20:32Z" } } ``` Whenever the stock level of this particular product goes below 10 units, the BPMN process can be set up to perform tasks such as notifying the supply chain, alerting marketing teams, or showcasing a "Low in Stock" badge on the product's webpage. ## Next steps - Dive deeper into the [REST connector](/components/connectors/protocol/rest.md) to understand its capabilities and configurations. - Explore [other connectors available](../out-of-the-box-connectors/available-connectors-overview.md) in Camunda to integrate with various systems and services. - Get a comprehensive understanding of how to use connectors in your BPMN processes [here](../use-connectors/index.md). - Learn about the specifics of inbound connectors and how they can be used [here](../use-connectors/inbound.md). --- ## REST connector :::caution If you use the REST connector, ensure you do not have any process variable named in the list below: - `body`, `url`, `method`, `headers`, `authentication`, `queryParameters`, `connectionTimeoutInSeconds`, `readTimeoutInSeconds` ::: The **REST connector** is an outbound protocol connector that allows you to make a request to a REST API and use the response in the next steps of your process. ## Create a REST connector task ## Make your REST connector executable To make the **REST connector** executable, choose the required authentication type and fill out the mandatory fields highlighted in red in the properties panel on the right side of the screen. :::note All the mandatory and non-mandatory fields will be covered in the upcoming sections. Depending on the authentication selection you make, more fields might be required. We will also cover this in the next section. ::: ### Configure a proxy server in Self-Managed In Self-Managed environments, you can configure the connector runtime to route HTTP requests through a proxy server using JVM properties or environment variables. See [HTTP proxy configuration](/self-managed/components/connectors/http-proxy-configuration.md) for details. ### Authentication You can choose among the available authentication type according to your authentication requirements. ### REST connector (None) Click **None** in the **Authentication** section. No extra authentication configuration is required; you can jump to the [next section](#request). ### REST connector (API key) For services that require an API key for authentication, you can configure the REST connector to include your API key in the request. #### Create a new connector secret We advise you to keep your **API key** safe and avoid exposing it in the BPMN `xml` file by creating a secret: 1. Follow our [guide for creating secrets](/components/saas/clusters/manage-secrets.md). 2. Name your secret (i.e `REST_API_KEY_SECRET`) so you can reference it later in the connector. ### Configure API key authentication Select the **REST connector** and fill out the following properties under the **Authentication** section: 1. In the **Authentication** section, select **API key**. 2. Choose the location where the API key should be included: - **Query parameters**: The API key will be added to the URL as a query string. - **Headers**: The API key will be included in the request headers. 3. Specify your API key details: - **API key name**: Enter the parameter name expected by the API (e.g., apiKey). - **API key value**: Reference the secret you created for your API key (e.g., `{{secrets.REST_API_KEY_SECRET}}`). ### REST connector (Basic) ##### Create a new connector secret We advise you to keep your **Password** safe and avoid exposing it in the BPMN `xml` file by creating a secret: 1. Follow our [guide for creating secrets](/components/saas/clusters/manage-secrets.md). 2. Name your secret (i.e `REST_BASIC_SECRET`) so you can reference it later in the connector. ### Configure Basic Authentication Select the **REST connector** and fill out the following properties under the **Authentication** section: 1. Click **Basic** in the **Authentication** section. 2. Set **Username** (i.e. `{{secrets.YOUR_USERNAME}}`). 3. Set **Password** to the secret you created (i.e. `{{secrets.REST_BASIC_SECRET}}`). ### REST connector (Bearer Token) #### Create a new connector secret We advise you to keep your **Bearer token** safe and avoid exposing it in the BPMN `xml` file by creating a secret: 1. Follow our [guide for creating secrets](/components/saas/clusters/manage-secrets.md). 2. Name your secret (i.e `REST_BEARER_TOKEN`) so you can reference it later in the connector. #### Configure the Bearer token Select the **REST connector** and fill out the following properties under the **Authentication** section: 1. Click **Bearer token** in the **Authentication** section. 2. Set **Bearer** to the secret you created (i.e. `{{secrets.REST_BEARER_TOKEN}}`). ### REST connector (OAuth token) #### Create a new connector secret We advise you to keep your **OAUTH_TOKEN_ENDPOINT** safe and avoid exposing it in the BPMN `xml` file by creating a secret: 1. Follow our [guide for creating secrets](/components/saas/clusters/manage-secrets.md). 2. Name your secret (i.e `OAUTH_TOKEN_ENDPOINT`) so you can reference it later in the connector. #### Configure the OAuth token Select the **REST connector** and fill out the following properties under the **Authentication** section: 1. Click **OAuth 2.0** in the **Authentication** section. 2. Set **OAuth token endpoint** to the secret you created (i.e. `{{secrets.OAUTH_TOKEN_ENDPOINT}}`). 3. Set **Client ID** to the secret you created (i.e. `{{secrets.CLIENT_ID}}`). 4. Set **Client secret** to the secret you created (i.e. `{{secrets.CLIENT_SECRET}}`). 5. (Optional) Set **Scopes** (i.e. `read:clients`). Depending on the OAuth provider you're using, this may or may not be required. 6. Set **Audience** to the secret you created (i.e. `{{secrets.AUDIENCE}}`). It is an optional field. Depending on the OAuth provider you're using, you should fill this field or not. 7. Choose **Client authentication** from the dropdown menu (i.e. `Send client credentials in body`). Find more information about the OAuth client credentials flow at the [RFC reference](https://www.rfc-editor.org/rfc/rfc6749#section-4.4). ## Request Under the **HTTP Endpoint** section, select the desired **Method** and fill the **URL** with your desired REST API. ### Query parameters The **Query parameters** field can be configured using the [FEEL Map](/components/modeler/feel/language-guide/feel-data-types.md#context) data type. ```text = { q: "Berlin", appid: "{{secrets.OPEN_WEATHER_MAP_API_KEY}}", units: "metric", lang:"en" } ``` :::note Secrets are not like regular variables and must be wrapped in double quotes (`"`) when used in an expression. ::: ### HTTP Headers Similarly to the Query Parameters, the **HTTP headers** can be specified using the [FEEL Map](/components/modeler/feel/language-guide/feel-data-types.md#context) data type. ``` = { Origin: "https://modeler.camunda.io/" } ``` #### Content-Type If you do not set the `Content-Type` header in your HTTP headers, the connector will automatically set the `Content-Type` to `application/json`. If you set the `Content-Type` header to `multipart/form-data`, only body fields that are documents or plain strings will be sent as multipart form data. All other fields, such as JSON objects, will be ignored. ### Request body When you are making a PUT, POST, or PATCH request, you might need to provide a body. You can provide a body for your request under the **Payload** section in the **Request body** field. ``` = { "temp": 25, "pressure": 1013, "humidity": 44, "temp_min": 16, "temp_max": 30 } ``` #### File upload To upload a file, you can take advantage of [Camunda document handling](/components/document-handling/getting-started.md). Depending on the `Content-Type`, the file will be uploaded as a binary or a JSON object (base64 encoded). - **Binary**: The file will be uploaded as a binary object. The `Content-Type` header **must** be set to `multipart/form-data`. The body must a map, where the key is the name of the file field and the value is a document reference. ![connectors-rest-upload](../../../images/connectors/connectors-rest-upload.png) - **JSON**: The file will be uploaded as a JSON object. The `Content-Type` header **must** be set to `application/json` (this is the default). The body must be a map, where the key is the name of the file field and the value is a document reference, similar to the binary upload. The file will be **base64 encoded** and included in the JSON object. ### Allow redirects - **Follow redirects**: When enabled, the connector automatically follows HTTP 3xx redirect responses, resolving the final destination URL. When disabled (default), the connector returns the original 3xx response including the `Location` header, leaving redirect handling to your process logic. ### Encoding In certain scenarios, such as when working with APIs that require pre-encoded URL elements, the REST connector's default behavior may inadvertently modify encoded segments. To avoid this, set the `skipEncoding` value to `"true"` in the XML. This disables the automatic decoding and re-encoding process, ensuring the URL is sent to the server exactly as provided. ### Network communication timeouts - **Connection timeout in seconds** determines the time frame in which the client will try to establish a connection with the server. If you do not specify a value, the system uses the default of 20 seconds. For cases where you need to wait indefinitely, set this value to 0. - **Read timeout in seconds** is the amount of time the client will wait to read data from the server after the connection has been made. The default is also set to 20 seconds. To allow an unlimited wait time for slow responses, set this to 0. ## Response The following variables are available in the context of the response expression: - **status**: Response status. - **body**: Response body of your request. Populated when the **Response format** is **As text** or **As JSON**. - **headers**: Response headers. - **document**: Populated when the **Response format** is **Document reference**; represents the stored document: - **documentId**: The ID of the stored document. - **contentHash**: The hash of the stored document. - **storeId**: The store ID. - **metadata**: Metadata of the stored document (if available). - **size**: Size of the stored document (in bytes). - **expiresAt**: Expiration date of the stored document. - **fileName**: Name of the stored document. - **customProperties**: Custom properties of the stored document. - **contentType**: Content type of the stored document. ### Response format Choose how the response body is returned with the **Response format** dropdown, a [return format](/components/document-handling/send-document-to-external-system.md#return-formats): - **As JSON** (default): the body is parsed as JSON and returned in `body`. - **As text**: the body is decoded as a string (with an optional encoding, default UTF-8) and returned in `body`. - **Document reference**: the body is streamed to the Camunda document store and a reference is returned in `document`. :::note The **As JSON** default fails the job when the response body is not valid JSON. Use **As text** for non-JSON responses. **As text** and **As JSON** are subject to a size guard (approximately 1.5 MiB); use **Document reference** for large responses. ::: To store part of the response as a document, use the [`createDocument` FEEL function](/components/connectors/use-connectors/index.md#function-createdocument) in the **Result expression** field. ## Output mapping ### Result variable You can export a complete response from an HTTP REST call into a dedicated variable accessible anywhere in a process. To do so, just input a variable name in the **Result variable** field. We recommend using a unique name to avoid variables being overwritten, for example `currentWeather`. ## Result expression Additionally, you can choose to unpack the content of your `response` into multiple process variables using the **Result expression**, which is a [FEEL Context Expression](/components/modeler/feel/language-guide/feel-context-expressions.md). ``` = { actual_temp: response.body.main.temp, feel_temp: response.body.main.feels_like, weather: response.body.weather[1].main, weather_report_id: response.document.documentId } ``` ## Error handling If an error occurs, the connector throws an error and includes the error response in the `error` variable in Operate. Click on the REST connector in Operate to see this variable. The following example shows the `error` variable in an error response: ```json { "code": "400", "variables": { "response": { "headers": { "Content-Length": "70", "Date": "Thu, 17 Oct 2024 09:31:51 GMT", "Content-Type": "application/json" }, "body": { "temperature": 36, "message": "My custom error message", "booleanField": true } } }, "message": "Bad Request", "type": "io.camunda.connector.api.error.ConnectorException" } ``` You can handle this error using an Error Boundary Event and the following error expression: ```json if matches(error.code, "400") and error.variables.response.body.temp = 36 then bpmnError("Too hot", error.variables.response.body.message, error.variables.response.body) else null ``` In this example, passing `error.variables.response.body` as the third argument to the `bpmnError` function allows you to pass additional information about the error to the error boundary event. For example, the `message`, `temperature` and `booleanField` fields from the error response are passed to the error boundary event. ## OData support The **REST connector** supports JSON-based [OData protocol](https://www.odata.org/). ### Requesting resources Requesting resources works the same way regular REST requests work: 1. Under the **HTTP Endpoint** section, select the desired **Method**. 2. Fill in the **URL** with your desired REST API. 3. Depending on your provider, you may also need to set `OData-Version` and `OData-MaxVersion` headers. ### Requesting an individual resource Similar to requesting resources, to request an individual resource you need to have a process variable. Use a FEEL string concatenation function when building **URL**, e.g. `="https://my.odata.service/v4/Service/Resource('" + resourceId + "')"`. ### Queries Additionally, if your provider supports OData queries, such as `$top`, you can use these when defined in the **Query parameters** field, e.g. `={"$top": 3, "$select": "FirstName, LastName"}`. --- ## SOAP connector :::note The **SOAP connector** is only supported by Self-Managed and Hybrid Camunda 8 instances. ::: **Simple Object Access Protocol (SOAP)** is a messaging protocol specification for exchanging structured information in the implementation of web services in computer networks. The **SOAP connector** allows you to interact with [SOAP](https://www.w3.org/TR/soap/) service endpoints from your BPMN process. ## Prerequisites To use the **SOAP connector**, ensure you have an active SOAP service. ## Create a SOAP connector task ## Connection Enter your SOAP service URL in the field **Service URL**, for example `https://myservice.com/service/MyService.wso`. ## Authentication Select the authentication type from the **Authentication** dropdown. ### None Use **None** if the SOAP service does not require authentication. ### WSS username token Use **WSS username token** in the **Authentication** dropdown when the requested SOAP endpoint requires [username token extension](https://docs.oasis-open.org/wss/v1.1/wss-v1.1-spec-pr-UsernameTokenProfile-01.htm#_Toc104276211). Enter **Username**, **Password**, and indicate if the password is encoded. :::note The **SOAP connector** currently supports only `SHA-1` password encoding. ::: ### WSS signature Use the **WSS signature** in the **Authentication** dropdown when the requested SOAP endpoint requires a message to be cryptographically signed with a [signature](http://docs.oasis-open.org/wss-m/wss/v1.1.1/cs01/wss-SOAPMessageSecurity-v1.1.1-cs01.html#_Toc307407954). Enter all necessary fields according to your service specification. ## SOAP message ### SOAP version Select the desired version of the SOAP service. ### SOAPAction HTTP header Enter the SOAPAction HTTP header that will be used in the request. Leave this value blank if the SOAPAction HTTP header won't be used in your request. This field is only required by SOAP version 1.1. ### SOAP header From the dropdown, select whether the **SOAP header** is required, and if so, in which format you wish to provide it. ### SOAP body From the **SOAP body** dropdown, select whether you will provide the SOAP request body in a form of **Template**, or **XML compatible JSON**. #### Template When **Template** is chosen, enter the **XML template** value, for example `{{paramValue}}`. Enter the **XML template context** value, for example `={paramValue: 1234567890}`, and enter the **Namespaces** value, for example `={"camunda":"http://my.service.com/webservicesserver/"}`. #### XML compatible JSON When **XML compatible JSON** is chosen, enter the **JSON definition**, for example ```json = { "camunda:Object01": { "camunda:Object02": myObjectValue } } ``` Enter the **Namespaces** value, for example `={"camunda":"http://my.service.com/webservicesserver/"}`. ## Output mapping ### Result variable You can export a complete response from a SOAP call into a dedicated variable accessible anywhere in a process. To do so, input a variable name in the **Result variable** field. Use a unique name to avoid overwriting variables. A typical response may look like as follows: ```json { "Envelope": { "Header": { "MyHeader": "Header value" }, "Body": { "MyResponseObject": { "MyResponseObjectField": "My result value" } } } } ``` ### Result expression Additionally, you can choose to unpack the content of your `response` into multiple process variables using the **Result expression**, which is a [FEEL Context Expression](/components/modeler/feel/language-guide/feel-context-expressions.md). Given SOAP service response that looks like as follows: ```json { "Envelope": { "Header": { "MyHeader": "Header value" }, "Body": { "MyResponseObject": { "MyResponseObjectField": "My result value" } } } } ``` To extract the `MyResponseObjectField` value into its own variable, you can do: ``` = { MyResponseObjectResult: response.Envelope.Body.MyResponseObject.MyResponseObjectField } ``` ## Usage examples ### Example 1 For example, imagine you want to send the following SOAP request: URL: `https://myservice:8888/webservice.wso` Body: ```xml 12345 ``` In your BPMN diagram, set the field **Service URL** as `https://myservice:8888/webservice.wso`, and **SOAP body** as: ```json { "Object01": { "Object02": 12345 } } ``` ### Example 2: Pre-defined namespaces Consider a namespace is defined within your objects, and you want to send the following request: URL: `https://myservice:8888/webservice.wso` Body: ```xml 12345 ``` In your BPMN diagram, set the field **Service URL** as `https://myservice:8888/webservice.wso`, and **SOAP body** as: ```json { "ns:Object01": { "ns:Object02": 12345 } } ``` :::note Here, we introduced a new `ns:` prefix. The prefix can be any arbitrary string that is not defined as a namespace. ::: Now, you'll need to associate a namespace. Set the following value at the **Namespaces** field. For the given example, it should be set as: ```json { "ns": "http://www.my.namespace.com/namespace/" } ``` ### Example 3: Using templates As an alternative, you can use templates to send SOAP messages. URL: `https://myservice:8888/webservice.wso` Body: ```xml 12345 ``` Set the **SOAP body** dropdown to **Template**. In the **XML template** field, define the template. For example: ```xml {{myObjectValue}} ``` In the **XML template context** field, define context JSON. For example: ```json { "myObjectValue": 12345 } ``` --- ## Integrate a built-in connector Integrate a [built-in connector](/components/connectors/introduction.md) to reduce the time it takes to automate and orchestrate business processes across systems. ## About With built-in connectors, you can automate complex [business processes](/components/concepts/processes.md) by inserting them into [BPMN diagrams](/components/modeler/bpmn/automating-a-process-using-bpmn.md) within your [Camunda Hub](/components/modeler/about-modeler.md) projects and configuring them with the properties panel. You can also orchestrate APIs, for example by working with a [REST connector](/guides/getting-started-orchestrate-apis.md). Learn more about [types of connectors](/components/connectors/connector-types.md). Connectors technically consist of two parts: - The business logic is implemented as a [job worker](/components/concepts/job-workers.md) - The user interface during modeling is provided using an element template. In this tutorial, you'll walk step-by-step through the implementation of a sample connector. ## Prerequisites Before you begin, ensure you have: - [A Camunda 8 account](/components/saas/organization/manage-plan/create-account.md). - [A SendGrid account](https://signup.sendgrid.com/). You'll use SendGrid in the example connector. Once you've created your account, you will immediately be prompted to create a [sender](https://docs.sendgrid.com/ui/sending-email/senders). - [A Camunda Hub workspace](/components/hub/organization/manage-workspaces/index.md). This is typically provisioned by a center of excellence team. ### Create a cluster ## Import a diagram 1. Download the following files: - `submit-expense.bpmn`: A process for submitting expenses for approval. - `upload-receipt.form`: A form for uploading receipts. - `approve-receipt.form`: A form for approving receipts. 2. Log in to your Camunda 8 account. 3. In your Camunda Hub workspace, click **Create project**, and name your project `Expense process`. 4. In your project, delete the default diagram, and click **Create new > Upload files**. 5. Upload `submit-expense.bpmn`, `upload-receipt.form`, and `approve-receipt.form`. Open **submit-expense** to see the process you'll be working with throughout this tutorial: :::note To learn more about building your own BPMN diagram from scratch, visit our guide on [automating a process using BPMN](/components/modeler/bpmn/automating-a-process-using-bpmn.md). ::: ## Add a connector At the beginning of the process, a receipt is ready and uploaded for review. The next task is to notify the manager of the uploaded receipt. To accomplish this, you'll use SendGrid to send an email: 1. With the **submit-expense** diagram open, make sure you're in [**Implement** mode](/components/hub/workspace/modeler/collaboration/implement-your-process.md). 2. Click the **Notify manager of receipt** task. 3. Click **Change element**. 4. Search for **Send Email with SendGrid**. If you're using Self-Managed, you may need to download it from the connector marketplace. You can search for the operation you want to perform, such as `send email`, instead of the connector name. 5. Open the **Details** panel on the right side of the modeling interface. 6. Under **Properties**, configure the following sections: - **Authentication:** [Full Access API Key](https://www.twilio.com/docs/sendgrid/ui/account-and-settings/api-keys#creating-an-api-key) - **Sender:** Name and email address. Make sure [this email is verified by SendGrid](https://www.twilio.com/docs/sendgrid/ui/sending-email/sender-verification). - **Receiver:** Name and email address - **Compose email:** Email contents The connector is ready to use. :::note Camunda offers a variety of available connectors. For example, utilize cloud connectors to communicate with cloud-native applications and conform to REST, GraphQL, or SOAP protocols. Or, employ service connectors to integrate with technology enablers like RPA, AI or IOT services. Learn more about our [available connectors](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md) to find out which may best suit your business needs. ::: ## Execute your process diagram If you change a diagram and it is auto-saved, this has no effect on your cluster(s). When you deploy the diagram, it becomes available on the selected cluster, and new instances can start. To execute your completed process with the diagram open: 1. Make sure you're in [**Implement** mode](/components/hub/workspace/modeler/collaboration/implement-your-process.md). 2. Click **Deploy and run**. You can now monitor your instances in [Operate](/components/operate/userguide/basic-operate-navigation.md#open-operate). :::note Variables are part of a process instance and represent the data of the instance. To learn more about these values, variable scope, and input/output mappings, visit our documentation on [variables](/components/concepts/variables.md). ::: ## Observe your running process After the [user task](/guides/getting-started-orchestrate-human-tasks.md) **Upload receipt** is completed in [Tasklist](/components/tasklist/introduction-to-tasklist.md), an email is automatically sent to the address you specified in the SendGrid connector's properties panel. In [Operate](/components/operate/userguide/basic-operate-navigation.md#open-operate), you will now see the process move forward to **Review receipt**. ## Additional resources and next steps - [Use connectors in your BPMN process](/components/connectors/use-connectors/index.md) - [Available connectors](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md) - [Connectors & Integration Framework](https://camunda.com/platform/modeler/connectors/) - [Camunda BPMN Tutorial](https://camunda.com/bpmn/) --- ## Use an inbound connector [Inbound connectors](/components/connectors/connector-types.md#inbound-connectors) enable workflows to receive data or messages from external systems or services. Review our [list of existing inbound connectors](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md) for more information. ## Creating the connector event Inbound connectors are modeled as **catch events** in [BPMN](/components/modeler/bpmn/bpmn.md). Connectors can be modeled as: - **Start events** – Start a new process instance. - **Message start events** – Start a new process instance with a `messageId` to prevent duplicate process instance creation. - **Intermediate catch events** – Receive messages in an already running process instance. - **Boundary events** – Receive messages in an already running process instance, attached to a task. - **Boundary events** – Receive messages in an already running process instance, attached to a task. - **Receive tasks** - Receive messages in an already running process instance. :::info If **idempotency** is a concern for the process creation and reprocessing of messages should never lead to a duplicate process instance creation, use the **message start event** element for an inbound connector as it relies on publishing a message. Unlike plain **start event** elements, **message start events** support the **message ID expression** property that allows you to derive a unique value from the connector output. This value is used by Zeebe to [guarantee uniqueness](/components/concepts/messages.md#message-uniqueness) in case other messages are published using the same **Message ID**. ::: When you **deploy** such a BPMN diagram with an inbound connector, the connector becomes ready to receive incoming requests. The outcome depends on the connector type: - **Webhook connectors** become available via the **webhook endpoint**. - **Subscription connectors** start listening to the **message queue**. - **Polling connectors** start polling the **external system**. ### Modeling the connector start event 1. Start building your BPMN diagram with a start event building block. 2. Change its template to an inbound connector of your choice (e.g., HTTP webhook, or a message queue subscription). 3. Fill in all required properties. 4. Complete your BPMN diagram. 5. Deploy it to your Camunda 8 instance. :::note You can still start instances of that process manually via Camunda Hub or Desktop Modeler, which is sometimes useful during testing. ::: ### Modeling the connector message start event 1. Start building your BPMN diagram with a message start event (non-interrupting). 2. Change its template to an inbound connector of your choice (for example, HTTP webhook or a message queue subscription), by clicking the **Select** button in the **Template** section in the **Properties** sidebar of the start event element. 3. Fill in all required properties. 4. Configure the **Correlation** section, if an event subprocess is used. - If you are setting up a non-interrupting message start event for a subprocess, select **Correlation required** and specify the **Correlation key (process)** and **Correlation key (payload)** values. - If you are setting up a message start event for a regular process (not a subprocess), skip the correlation settings. 5. Complete your BPMN diagram. 6. Deploy it to your Camunda 8 instance. ### Modeling the connector intermediate message catch event 1. Start building your BPMN diagram with an **Intermediate Catch Event** building block. 2. Change its template to an inbound connector of your choice (e.g., HTTP webhook, or a message queue subscription). 3. Fill in all required properties. 4. Complete your BPMN diagram. 5. Deploy it to your Camunda 8 instance. ### Modeling the connector boundary event 1. Start building your BPMN diagram with any **Task** building block. 2. Attach a **Boundary event** to a **Task** at your diagram. 3. Change its template to an inbound connector of your choice (e.g., HTTP webhook, or a message queue subscription). 4. Fill in all required properties. 5. Complete your BPMN diagram. 6. Deploy it to your Camunda 8 instance. ### Modeling the connector receive task 1. Start building your BPMN diagram with an **Receive Task** building block. 2. Change its template to an inbound connector of your choice (e.g., HTTP webhook, or a message queue subscription). 3. Fill in all required properties. 4. Complete your BPMN diagram. 5. Deploy it to your Camunda 8 instance. ### Example: Configuring an HTTP webhook Different connector types have different configuration options, but parts like **Result expression**, or **Correlation key** are common for all of them. Let's take a look at an example of configuring an HTTP webhook. To deploy and use the webhook, you need to fill in several fields: 1. **Webhook method** - HTTP method for your inbound webhook. You can either set a specific one or choose `Any` if all methods need to be supported. 2. **Webhook ID** - Context path for your inbound webhook. This is used to build a URL endpoint for your webhook. For example, given the `Webhook ID` value is `myWebhookPath`, the complete webhook URL endpoint will be `http(s):///inbound/myWebhookPath`. 3. **HMAC authentication** - If an external caller uses HMAC as a means of request validation and authentication, you can `enable` this property. In that case, you'll need to specify additional field values. Read more about the [generic HTTP webhook configuration](/components/connectors/protocol/http-webhook.md). 4. **Authorization** - Authorization method of the webhook. 5. **Activation condition** - FEEL expression that assesses trigger conditions. Note: Unlike other properties, in the activation condition, you cannot use the process variables. For example, given external caller triggers a webhook endpoint with body `{"id": 1, "status": "OK"}`, the **Activation Condition** value might look like `=(request.body.status = "OK")`. Leave this field empty to trigger your webhook every time. 6. **Result variable** - Name of the process variable that will be created or updated with the result of the webhook. For example, if you want to save the result of the webhook in a variable called `myDocumentId`, you would specify `myDocumentId` as the **Result variable** value. 7. **Result expression** - FEEL expression that transforms incoming body into BPMN process variables. For example, given external caller triggers a webhook endpoint with body `{"id": 1, "status": "OK"}` and you would like to extract `id` as a process variable `myDocumentId`. In that case, the **Variable mapping** might look as `={myDocumentId: request.body.id}`. 8. **Response body expression** - FEEL expression that forces a webhook to return a specific response. This might be useful for one-time challenge verification, or acknowledgement response. Given your webhook triggered with body `{"id": 1, "status": "OK"}`, if you wish to return acknowledgement, you can specify the following expression `={message: "received document ID " + string(request.body.id)}` which will produce `{"message":"received document ID 123"}` as a response. Another example, when you wish to return a one-time subscription challenge. Given your webhook triggered with body `{"event": "subscribe", "challenge":"myRandomChallenge"}`. You can return challenge back with the following expression `=if request.body.event = "subscribe" then request.body.challenge else null` which will produce a plain string `"myRandomChallenge"` as a response. If the Webhook connector is applied to an **intermediate catch event**, you also need to specify the following fields: 9. **Correlation key (process)** - a FEEL expression that defines the correlation key for the subscription. This corresponds to the **Correlation key** property of a regular **message intermediate catch event**. 10. **Correlation key (payload)** is a FEEL expression used to extract the correlation key from the incoming message. This expression is evaluated in the connector Runtime and the result is used to correlate the message. For example, given that your correlation key is defined with `requestIdValue` process variable, and the request body contains `{"request": {"id": 123}}`, your correlation key settings will look like this: - **Correlation key (process)**: `=requestIdValue` - **Correlation key (payload)**: `=request.body.request.id` :::tip For all inbound connectors, the **Activation condition** is evaluated before the **Correlation key (payload)** expression. Use the activation condition as a fail-safe filter to confirm the incoming message has the expected shape before correlation runs. If an unexpected payload reaches the **Correlation key (payload)** expression instead, FEEL evaluation fails, and the outcome depends on the specific connector. For example, the [Kafka connector](/components/connectors/out-of-the-box-connectors/kafka.md#activation-condition) treats this as an unexpected error and does not commit the message offset, which can stop the subscription until the issue is resolved. ::: See the [webhook documentation](/components/connectors/protocol/http-webhook.md) or the documentation of [other connector types](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md) for more details. ## Consume unmatched events You can configure a connector to consume all unmatched events from the event source by enabling the **Consume unmatched events** checkbox in the **Activation** section of the connector properties. - When this option is enabled, the connector will consume all events that do not match the activation condition of any other connector with the same deduplication ID. This is useful in scenarios where you want to ensure that all events are processed, even if they do not match any specific activation condition. - When this option is disabled, the connector will only consume events that match its activation condition. Events that do not match any activation condition will lead to an error. Here are some examples of how this option will affect the behavior of the connector when enabled or disabled, when the activation conditions of all the connectors with the same deduplication ID do not match the incoming event: Connector Consume unmatched events ✅ Enabled ◻️ Disabled Webhook Connector Return a success response (200) Return an error response (422, or specific HTTP status code depending on the error) Kafka Connector Commit the message offset Do not commit the message offset RabbitMQ Connector Acknowledge the message Reject the message AWS SQS Connector Delete the message from the queue Do not delete the message from the queue Email Connector Mark the email as processed (e.g. marked as read, deleted, or moved) Do not mark the email as processed ## Working with request context You can access request context in the **Activation condition**, **Result expression**, and **Response body expression**. Let's consider the following cURL query: `curl -X POST -H "Content-Type: application/json" -H "MyHeader: myValue" -d '{"status": "OK", "id": 123}' "http:///inbound/myWebhook?param1=val1"`. A webhook connector context data will arrive as follows: ```json { "request": { "body": { "status": "OK", "id": 123 }, "headers": { "host": "YOUR_HOST", "user-agent": "curl/7.88.1", "accept": "*/*", "content-type": "application/json", "myheader": "myValue", "content-length": "27" }, "params": { "param1": "val1" } }, "connectorData": {} } ``` This means in scope of the fields **Activation condition**, **Result expression**, and **Response body expression**, you can use not only `request.body.` but also access headers via `request.headers.myheader` or params `request.params.param1`. There is also a connector-specific special case of `connectorData` that is usually empty and used in rare cases, when body has to be crafted in a special way, but a connectors user might still want access context data. See a list of [available inbound connectors](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md) and their respective specific configuration instructions. --- ## How to use connectors Any task can be transformed into a connector task. This guide details the basic functionality all connectors share. Find available connectors in [built-in connectors](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md). To add connectors from your BPMN diagram, visit the [Camunda Marketplace](/components/hub/workspace/modeler/modeling/camunda-marketplace.md). :::note Learn how to [install connectors in Self-Managed](/self-managed/components/connectors/overview.md). New to modeling with Camunda? The steps below assume some experience with Camunda modeling tools. [Model your first diagram](/components/hub/workspace/modeler/modeling/model-your-first-diagram.md) to learn how to model processes in Camunda Hub. ::: ## Using credentials Some connectors let you select a [credential](/components/hub/organization/credentials/index.md) instead of entering authentication and connection settings directly on the task. Create the credential once, then select it on any connector task that supports it. Learn how to select or create one in the [Camunda Hub modeling interface](/components/hub/organization/credentials/modeling-interface.md) or in [Desktop Modeler](/components/modeler/desktop-modeler/credentials.md). ## Using secrets :::warning `secrets.*` is a deprecated syntax. Instead, use `{{secrets.*}}` ::: You can use sensitive information in your connectors without exposing it in your BPMN processes by using a [legacy secret reference](/reference/glossary.md#secret-reference-legacy). Use Camunda Hub to [create and manage secrets](/components/saas/clusters/manage-secrets.md). You can reference a secret like `MY_API_KEY` with `{{secrets.MY_API_KEY}}` in any connector field in the properties panel. Secrets resolve in every field, not only in a specific subset of fields. The [secret filter](/self-managed/components/connectors/connectors-configuration.md#secret-filter) applies to both outbound and inbound connectors. In practice, this means a secret in a connector field only resolves at runtime if that same secret was already referenced in that same field at modeling time, in the deployed BPMN. Secrets are not variables and must be wrapped in double quotes as follows when used in a FEEL expression: ```feel = { myHeader: "{{secrets.MY_API_KEY}}"} ``` Using the secrets placeholder syntax, you can use secrets in any part of a text, like in the following FEEL expression: ```feel = "https://" + baseUrl + "/{{secrets.TENANT_ID}}/accounting" ``` This example assumes there is a process variable `baseUrl` and a configured secret `TENANT_ID`. The engine will resolve the `baseUrl` variable and pass on the secrets placeholder to the connector. Assuming the `baseUrl` variable resolves to `my.company.domain`, the connector receives the input `"https://my.company.domain/{{secrets.TENANT_ID}}/accounting"`. The connector then replaces the secrets placeholder upon execution. For further details on how secrets are implemented in connectors, consult our [Connector SDK documentation](/components/connectors/custom-built-connectors/connector-sdk.md#secrets). :::warning `secrets.*` is a reserved syntax. Don't use this for other purposes than referencing your secrets in connector fields. Using this in other areas can lead to unexpected results and incidents. ::: ### Using `camunda.secrets.*` references You can also reference a secret directly in a Connector input mapping by using `camunda.secrets.` in a FEEL expression. In SaaS, use the [connector secrets](/components/saas/clusters/manage-secrets.md#reference-connector-secrets-as-camundasecretsname) you manage on the cluster. No secret store configuration is required. In Self-Managed, an operator must [configure the secret store](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#secrets). See [secret references in input mappings](/components/concepts/variables.md#secret-references-in-input-mappings) for the syntax and its rules. These forms coexist and are handled differently: - `{{secrets.*}}` remains fully supported for existing process models. It's still resolved by the connector runtime itself, at execution time, exactly as described above; the runtime keeps receiving it as plain placeholder text in the job's input. - `camunda.secrets.` is resolved before the job reaches any worker, including a connector runtime. The connector receives the value already in place, the same way whether the connector runtime is co-located with the cluster or run separately (for example, a self-managed runtime connecting to a SaaS cluster). - A [cluster variable](/components/modeler/feel/cluster-variable/data-types.md) of kind `SECRET_REFERENCE` can hold `camunda.secrets.` references in its value. A connector field that reads such a variable, for example `=camunda.vars.env.MY_CONFIG`, receives the resolved value the same way, because the references are recorded on the job and resolved before activation. See [resolve secret references in a cluster variable](/components/modeler/feel/cluster-variable/usage-guide.md#resolve-secret-references-in-a-cluster-variable). `{{secrets.*}}` values are not scoped per [physical tenant](/self-managed/concepts/physical-tenants/connectors-runtime.md#per-tenant-secret-access) unless you opt in to `physicaltenantaware` in the connector runtime's own configuration. Physical tenant scoping of `camunda.secrets.` is separate from that setting: each physical tenant resolves its own configured secret store. `{{secrets.*}}` and `camunda.secrets.` can be migrated independently of where the value is stored. If you move a secret value into the store that `camunda.secrets.` uses but keep existing process models on the legacy `{{secrets.NAME}}` syntax, set `camunda.connector.secret-resolver.legacy.mode` to `FALLBACK` on the connector runtime: a legacy-style reference whose name isn't found in a configured secret provider is then looked up in that same store. The default, `ON`, only resolves legacy references from the configured providers. See [Migrate to `camunda.secrets.`](migrate-secrets.md) for step-by-step migration guidance. ## Variable and response mapping When a connector is expected to return a result, connectors feature a dedicated section known as **Response Mapping**, comprising two essential fields: **Result Variable** and **Result Expression**. These fields export responses from external connector calls into process variables. ### Result variable This field declares a singular process variable designated for the export of responses from a connector call. The resulting process variable can be subsequently utilized within the ongoing process. #### Example If you set `result` inside the **Result Variable** field of the REST outbound connector, this variable is available: ```json { "result": { "status": 200, "headers": { "date": "Thu, 03 Apr 2025 07:05:19 GMT", "server": "nginx", "content-type": "text/html; charset=UTF-8" }, "body": { "orderNumber": "1234", "date": "2025-04-01", "customerId": "567", "address": { "streetAddress": "1234 Elm Street", "city": "Paris", "state": "CA", "postalCode": "90210", "country": "USA" } }, "reason": "OK", "document": null } } ``` ### Result expression This field facilitates the mapping of a connector response into multiple process variables, providing further flexibility of the variable utilization within the ongoing process. Additionally, the extracted values can be transformed with [FEEL expressions](/components/concepts/expressions.md). To ensure process isolation, note that connectors do not have access to process variables. :::note While using this field, a process variable with the name `response` is reserved. It should only be used when a connector returns atomic values like a string or a number. ::: #### Example If you set `{ "bodyReceived": body }` inside the **Result Expression** field of the REST outbound connector, this variable is available: ```json { "bodyReceived": { "orderNumber": "1234", "date": "2025-04-01", "customerId": "567", "address": { "streetAddress": "1234 Elm Street", "city": "Paris", "state": "CA", "postalCode": "90210", "country": "USA" } } } ``` ### Example Imagine your connector makes an external call to an arbitrary weather service. The weather service returns the following response: ```json { "status": 200, "headers": { "date": "Thu, 19 Jan 2023 14:02:29 GMT", "transfer-encoding": "chunked", "content-type": "application/json; charset=utf-8", "connection": "keep-alive" }, "body": { "latitude": 52.52, "longitude": 13.4, "generationtime_ms": 0.22804737091064453, "utc_offset_seconds": 0, "timezone": "GMT", "timezone_abbreviation": "GMT", "elevation": 45.0, "current_weather": { "temperature": 1.0, "windspeed": 10.1, "winddirection": 186.0, "weathercode": 2, "time": "2023-01-19T14:00" } } } ``` If you declare a variable `myWeatherResponse` in the **Result Variable** field, the entire response is mapped to the declared variable. Now, let's imagine that you wish to extract only temperature into a process variable `berlinWeather` and wind speed into `berlinWindSpeed`. Let's also imagine you need weather in Fahrenheit declared in `berlinWeatherInFahrenheit`. In that case, you could declare **Result Expression** as follows: ``` = { berlinWeather: response.body.current_weather.temperature, berlinWindSpeed: response.body.current_weather.windspeed, berlinWeatherInFahrenheit: response.body.current_weather.temperature * 1.8 + 32 } ``` ### `createDocument` function Starting with 8.10.0, you can use `createDocument` in the **Result expression** and **Error expression** fields. The function converts part of a Connector response into a [document reference](/components/document-handling/getting-started.md), so you can store the relevant content without storing the entire response. | Item | Type | Description | | ------- | ----------------- | -------------------------------------------------------------------- | | `value` | String or context | Content and optional metadata used to create the document reference. | | Result | Context | The generated document reference. | If `value` is a string, `createDocument` treats it as base64-encoded content without metadata: ```feel createDocument(response.body.file[1].document.data) ``` If `value` is a context, it can contain the following fields: | Field | Required | Description | | -------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `content` or `data` | Yes | The base64-encoded document content. Either key name is accepted. | | `name` or `fileName` | No | The filename. If you omit this field, `createDocument` automatically generates a UUID. | | `contentType` | No | The MIME type of the content. If you omit this field, `createDocument` infers the type from the file extension in `name` or `fileName`. If neither field contains an extension, the value defaults to `application/octet-stream`. | ```feel createDocument({ data: response.body.file[1].document.data, name: response.body.file[1].filename }) ``` The Connector runtime does not provide a `createDocuments` function. To extract multiple documents, use a FEEL iteration: ```feel = { documents: for f in response.body.file return createDocument(f.document) } ``` Before using `createDocument`, configure a document store for the Connector runtime. For configuration details, see [Document handling](/components/document-handling/getting-started.md). ## Activation The **Activation** section pertains specifically to [inbound connectors](/components/connectors/connector-types.md). ### Activation condition The **Activation condition** field evaluates conditions against the incoming message payload. It enables filtering of payloads that can initiate a process. If left empty, all valid incoming messages will trigger a new process—except those that fail pre-validation checks, such as HMAC signature verification for specific connectors. ## Correlation ### Correlation key (process) The **Correlation key (process)** field specifies which variable from the connector output should serve as the process correlation key. Learn more about [message correlation](/components/concepts/messages.md#message-correlation-overview). ### Correlation key (payload) The **Correlation key (payload)** field tells the connector how to extract the correlation value from the incoming message payload. ### Message ID expression The **Message ID expression** field defines how to extract a unique identifier from the incoming message payload. Messages that share the same identifier within the defined time-to-live (TTL) will be correlated only once. Leaving this field empty may cause identical messages to be submitted and processed multiple times. ## BPMN errors and failing jobs {#bpmn-errors} Being able to deal with exceptional cases is a common requirement for business process models. Read more about our general best practices around this topic in [dealing with problems and exceptions](/components/best-practices/development/dealing-with-problems-and-exceptions.md). Connectors share this requirement for exception handling like any other task in a model. However, connectors define reusable runtime behavior that is not aware of your specific business use case. Thus, they cannot determine if an exceptional case is a technical or business error. Therefore, a connector's runtime behavior cannot throw BPMN errors, but only technical errors. However, those technical errors can optionally contain an error code as structured data that can be reused when configuring a connector task. :::note There may be situations where technical errors cannot be detected by the runtime and they must be thrown explicitly. ::: ### Error expression To support flexible exception handling, the [out-of-the-box connectors](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md) allow users to define an **Error Expression** in the **Error Handling** section at the bottom of the properties panel. The example below uses this property to automatically inform the right group of people depending on the result of an HTTP request against an internal website. If the website returns a valid result, this data is passed on to the regular team. In case of a [404](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404) website response, the administrator is informed, so they can check why the website cannot be reached. HTTP responses with status [500](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) indicate internal website errors, which is why the website team is informed. ![feel connectors](../img/use-connectors-error-general.png) The **Error Expression** property requires a [FEEL](/components/modeler/feel/what-is-feel.md) expression that yields a BPMN error object in the end. The BPMN error object can be an empty [context](/components/modeler/feel/language-guide/feel-data-types.md#context), [null](/components/modeler/feel/language-guide/feel-data-types.md#null), or a context containing at least a non-empty `errorType` and a non-empty `code` if the error type is `bpmnError`. You can use all available functionality provided by FEEL to produce this result. Use the provided FEEL functions: - [`bpmnError`](#function-bpmnerror) to create a BPMN error object. This triggers a [ThrowError call](/components/best-practices/development/dealing-with-problems-and-exceptions.md) to the workflow engine. - [`jobError`](#function-joberror) to create a fail job object. This triggers a [FailJob call](/components/best-practices/development/dealing-with-problems-and-exceptions.md) to the workflow engine. - [`ignoreError`](#function-ignoreerror) to recover from an error and complete a job successfully. This triggers a [CompleteJob call](/apis-tools/orchestration-cluster-api-rest/specifications/complete-job.api.mdx) to the workflow engine. The `bpmnError` FEEL function can be called with one, two, or three parameters. Optionally, you can pass variables as the third parameter and combine this with a boundary event to use the variables in condition expressions when handling the error event. Example FEEL expression: ``` if response.body.status = "failed" then bpmnError("FAILED", "The action failed", response.body) else null ``` Within the FEEL expression, you access the following temporary variables: - The result of the connector in `response`. - The job of the invocation in `job` with the fields: `retries` - Any result variables created by the **Result Variable** and **Result Expression** properties (see the [REST connector](/components/connectors/protocol/rest.md#response), for example). - The technical exception that potentially occurred in `error`, containing a `message` and optionally a `code`. The code is only available if the connector's runtime behavior provided a code in the exception it threw. :::info If a **Result Variable** or **Result Expression** is configured on the connector task, the raw connector response is **not** available as `response` in the error expression. Instead, `response` contains only the mapped output variables. Reference those variables by name (for example, `myVar.status`) rather than using `response.body`. ::: Building on that, you can cover those use cases with BPMN errors that you consider as exceptional. This can build on technical exceptions thrown by a connector as well as regular results returned by the external system you integrated. The [example expressions](#bpmn-error-examples) below can serve as templates for such scenarios. ### Function bpmnError Returns a context entry with an `errorType`, `errorCode` and `errorMessage`. - parameters: - `errorCode`: string - `errorMessage`: string (optional) - result: context ```feel bpmnError("123", "error received") // { errorType: "bpmnError", errorCode: "123", errorMessage: "error received" } bpmnError("123") // { errorType: "bpmnError", errorCode: "123" } ``` ### Function bpmnError with variables Returns a context entry with an `errorType`, `errorCode`, `errorMessage`, and `variables`. - Parameters: - `errorCode`: string - `errorMessage`: string - `variables`: context - Result: context ```feel bpmnError("123", "error received", {myVar: myValue}) // { errorType: "bpmnError", errorCode: "123", errorMessage: "error received", variables: {myVar: myValue}} ``` ### Function jobError Returns a context entry with an `errorType`, `errorMessage`, `variables`, `retries`, and `retryBackoff`. - Parameters: - `errorMessage`: string - `variables`: context _(optional), default_ `{}` - `retries`: number _(optional), default_ `0` - `retryBackoff`: days-time-duration _(optional), default_ `PT0S` - Result: context Optional parameters can be omitted if no parameter needs to be set after. ```feel jobError("job failed", {myVar: myValue}, 2, @"PT30S") // { errorType: "jobError", errorMessage: "job failed", variables: {myVar: myValue}, retries: 2, retryBackoff: @"PT30S" } ``` ```feel jobError("job failed", {myVar: myValue}, 2) // { errorType: "jobError", errorMessage: "job failed", variables: {myVar: myValue}, retries: 2, retryBackoff: @"PT0S" } ``` ```feel jobError("job failed", {myVar: myValue}) // { errorType: "jobError", errorMessage: "job failed", variables: {myVar: myValue}, retries: 0, retryBackoff: @"PT0S" } ``` ```feel jobError("job failed") // { errorType: "jobError", errorMessage: "job failed", variables: {}, retries: 0, retryBackoff: @"PT0S" } ``` ### Function ignoreError Allows you to complete a job successfully when an error occurs and returns a context entry with an optional `variables` property. These `variables` are used when sending the complete job command to the engine: ```feel ignoreError({"status":"ok"}) ``` You can also ignore the error without providing `variables` leading to a job completion without any variables: ```feel ignoreError() ``` After defining error-handling functions, you can use them in FEEL expressions as shown in the following examples. ### BPMN error examples #### HTTP errors to BPMN errors Using the [REST connector](/components/connectors/protocol/rest.md), you can handle HTTP errors directly in your business process model by setting a header named `errorExpression` with the following value: ```feel if error.code = "404" then bpmnError("404", "Got a 404") else if error.code = "500" then bpmnError("500", "Got a 500") else if response.body.status = "failed" then bpmnError("FAILED", "Action failed", response.body) else if error.code = "409" then ignoreError({"created":true}) else null ``` This will create BPMN errors for HTTP requests that return with a status [404](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404) or [500](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500). You can extend that list to all HTTP errors you can handle as business use cases, for example by informing a website administrator directly via Slack using the [Slack connector](/components/connectors/out-of-the-box-connectors/slack.md). #### Response value to BPMN error Using the [REST connector](/components/connectors/protocol/rest.md) or any other connector that returns a result, you can handle a response as a BPMN error based on its value, by setting a header named `errorExpression` with the following value: ```feel if response.body.main.humidity < 0 then bpmnError("HUMIDITY-FAIL", "Received invalid humidity") else null ``` This is assuming you requested data from a local weather station and received a value that is technically valid for the REST connector. However, you could define that for your business case a humidity value below `0` must be an error that should be checked manually. You could automatically send a message to a technician to check the weather station. #### Generic Header to transform a connectorException to a BPMN Error If the connector throws a `ConnectorException` like: ```java throw new ConnectorException("HUMIDITY-FAIL","Received invalid humidity"); ``` Then you can transform this exception to a BPMN error with this expression in a Header item named `errorExpression`: ```feel if is defined(error) then bpmnError(error.code, error.message) else null ``` ### Fail job examples #### HTTP errors to fail job Using the [REST connector](/components/connectors/protocol/rest.md), you can handle HTTP errors directly in your business process model by setting a header named `errorExpression` with the following value: ```feel if error.code = "404" then jobError("Resource not found") else if error.code = "504" then jobError("Gateway timeout", {},job.retries - 1, @"PT30S") else if response.body.status = "technicalProblem" then jobError("Technical Problem", response.body) else null ``` This will allow you to control the job failure for HTTP requests that return with status [404](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404) or [504](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504). You can extend that list to all HTTP errors you can handle as a custom fail job; for example, to go to 0 retries instantly or increase the retry timeout. --- ## Migrate to `camunda.secrets.` `camunda.secrets.` is the recommended secret reference syntax, resolved centrally by the [Orchestration Cluster](/reference/glossary.md#orchestration-cluster) rather than by the [connector runtime](/reference/glossary.md#connector-runtime). This page explains how to move existing connector models from the legacy `{{secrets.}}` syntax to `camunda.secrets.`, including a fallback mode that lets you migrate incrementally without updating every model at once. :::note The legacy `{{secrets.}}` syntax remains fully supported, so you can migrate at your own pace. ::: ## Why migrate Resolving secrets centrally in the Orchestration Cluster gives you capabilities the connector runtime's own resolution doesn't have: - **Field-scoped resolution**: a reference only resolves at the field where it was written. See [secret resolution](/components/concepts/secret-resolution.md#reference-syntax) and [security notice 61](/reference/notices.md#notice-61) for the legacy behavior this replaces. - **External secret store support**: in Self-Managed, values come from a File, AWS Secrets Manager, or GCP Secret Manager store instead of environment-variable-based [connector secret providers](/self-managed/components/connectors/connectors-configuration.md#secrets). See [secrets configuration](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#secrets). - **Resource-based access control**: the `SECRET` resource's `READ` and `REVEAL` authorizations govern who can list and reveal secrets through the API. See [Control access to secrets](/components/concepts/secrets.md#control-access-to-secrets). - **Resolution kept off the connector runtime path**: references resolve ahead of job activation, so a value never lands in a record, runtime state, or log. See [Secret resolution and job activation](/components/concepts/secret-resolution-and-job-activation.md). ## Feature differences | | `{{secrets.}}` (legacy) | `camunda.secrets.` (recommended) | | :--------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- | | Resolved by | The connector runtime, at execution time | The Orchestration Cluster, ahead of job activation or on demand through the API | | Where you use it | Any connector field in the properties panel | Input mapping FEEL expressions, and connector or credential fields backed by a `SECRET_REFERENCE` cluster variable | | Value source | Connector secret providers, for example prefixed environment variables or a custom provider | A File, AWS Secrets Manager, or GCP Secret Manager store in Self-Managed; managed secrets in SaaS | | Authorization | None; the [secret filter](/self-managed/components/connectors/connectors-configuration.md#secret-filter) restricts which fields may resolve a secret | The `SECRET` resource's `READ` and `REVEAL` authorizations govern the `/v2/secrets` API; broker-side resolution is not governed by them | | Tenant awareness | Not scoped per physical tenant unless you opt in to a tenant-aware provider in the connector runtime configuration | Each physical tenant resolves its own configured secret store | | Caching | Values are read from the provider on each execution | Cache-first with a configurable cache lifetime, so rotated values become available without a restart | For the full behavior of the recommended syntax, see [Secret resolution](/components/concepts/secret-resolution.md) and [Secret resolution and job activation](/components/concepts/secret-resolution-and-job-activation.md). For the legacy syntax, see [Using secrets](index.md#using-secrets). ## Migrate incrementally with fallback mode You don't have to update every model at once. The connector runtime's `camunda.connector.secret-resolver.legacy.mode` setting controls where legacy references resolve from: - `ON` (default): the runtime resolves `{{secrets.}}` only from its configured secret providers. - `FALLBACK`: when a legacy reference's name isn't found in a configured secret provider, the runtime looks the name up in the same secret store that backs `camunda.secrets.`. With `FALLBACK` set, you can move a secret's value into the Orchestration Cluster's store first and keep existing models on the legacy syntax. Models you haven't touched keep resolving, now from the store, while you update them field by field. Once no legacy references remain, set the mode back to `ON` and remove the provider configuration. ## Backend prerequisites by offering Before `camunda.secrets.` can resolve, its store must hold the secret values. What that requires depends on your offering: - **SaaS**: no backend change is needed. The managed secrets you create on a cluster's **Cluster secrets** tab are available to both the legacy syntax and `camunda.secrets.`, so you can start migrating models right away. See [Manage connector secrets](/components/saas/clusters/manage-secrets.md#reference-connector-secrets-as-camundasecretsname). - **Self-Managed**: an operator must configure a secret store (File, AWS Secrets Manager, or GCP Secret Manager) for the Orchestration Cluster. The connector runtime's secret providers alone are not enough: `camunda.secrets.` doesn't read them. See [secrets configuration](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#secrets). ## Migrate step by step 1. **Prepare the store.** In Self-Managed, [configure a secret store](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#secrets) for the Orchestration Cluster. In SaaS, confirm the secrets exist on the cluster's **Cluster secrets** tab. 1. **Copy the secret values.** Move each secret value from your connector secret provider into the store under the same name. Note that a name created or managed through the `/v2/secrets` API must match `[\p{Alnum}_-]+`; see [secret resolution](/components/concepts/secret-resolution.md#reference-syntax) for the naming rules. 1. **(Optional) Enable fallback mode.** Set `camunda.connector.secret-resolver.legacy.mode` to `FALLBACK` on the connector runtime, as described in [Migrate incrementally with fallback mode](#migrate-incrementally-with-fallback-mode). Legacy references whose names are no longer supplied by a provider then resolve from the store, so models keep working while you migrate them. 1. **Update your models field by field.** Replace each `{{secrets.NAME}}` reference with `camunda.secrets.NAME`: - In an input mapping, use the FEEL expression `=camunda.secrets.NAME`. See [secret references in input mappings](/components/concepts/variables.md#secret-references-in-input-mappings) for the syntax rules, including backtick-escaping dashed names. - In a connector or credential field, read the reference from a cluster variable of kind `SECRET_REFERENCE`. See [resolve secret references in a cluster variable](/components/modeler/feel/cluster-variable/usage-guide.md#resolve-secret-references-in-a-cluster-variable). 1. **Remove the legacy configuration.** Once no model contains a `{{secrets.}}` reference, set `camunda.connector.secret-resolver.legacy.mode` back to `ON` (or remove the setting) and remove the connector secret provider configuration. ## Testing To supply cluster secrets when you test processes with Camunda Process Test, see [cluster secrets in Camunda Process Test](/apis-tools/testing/connectors.md#cluster-secrets). ## Related resources - [Secrets](/components/concepts/secrets.md) provides an overview of secret references, stores, and access control across Camunda. - [Using `camunda.secrets.*` references](index.md#using-camundasecrets-references) covers how the two syntaxes coexist on the connector runtime. - [Connector secrets in Self-Managed](/self-managed/components/connectors/connectors-configuration.md#secrets) covers the legacy secret provider configuration. --- ## Use an outbound connector [Outbound connectors](/components/connectors/connector-types.md#outbound-connectors) allow workflows to trigger external systems or services. ## Creating the BPMN task ## Configuring the outbound connector Once a connector task is selected, the available configuration is visible in the properties panel on the right side. The required fields are highlighted with an error message. Fields in the properties panel marked with an equals sign inside a circle indicate that [FEEL](/components/modeler/feel/what-is-feel.md) can be used to configure the property. If the icon includes an equals sign marked with a star, FEEL is required. Using FEEL allows the user to reference process data from variables in the expression to configure the properties. Each connector defines its own set of properties you can fill in. Find the details for connectors provided by Camunda in the [out-of-the-box connectors](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md) documentation. ## Retries By default, connector execution is repeated `3` times if execution fails. The retries are executed sequentially without delays. To change the default retries value, edit the BPMN XML file and set the `retries` attribute at the `zeebe:taskDefinition`. For example: ```xml ... ... ``` The connector runtime also supports custom intervals between retries (**retry backoff**). To configure a custom retry interval, you need to add a special property to the connector element template. The property must bind to the `retryBackoff` task header, and the value must be a valid [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) duration. ```json { "value": "PT30S", "binding": { "key": "retryBackoff", "type": "zeebe:taskHeader" }, "type": "Hidden" } ``` In the example above, the retry attempts will be spaced 30 seconds apart, instead of the default behavior of retrying immediately. If necessary, the **retry backoff** property can be made visible and editable in the properties panel by changing the property `type` to `String`. `retryBackoff` is a reserved task header key recognized by the connector runtime. You don't need to handle this input in your connector implementation, as the runtime handles it automatically for all outbound connectors. ## Job timeout If Zeebe doesn't receive a result for a job within the job timeout, it makes the job available again and another worker can pick it up. A long-running connector call can then run twice. Element templates of outbound connectors can include an optional **Job timeout** property in the **Retries** section of the properties panel. Enter an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) duration, for example `PT15M`, to give the call more time before Zeebe reassigns the job. Leave the property empty to use the default job timeout. ## Downloading documents To download documents without an additional step, review [uploading a document when using connectors](/components/document-handling/upload-document-to-bpmn-process.md#upload-a-document-when-using-any-connector). --- ## Use connectors in hybrid mode Outbound Inbound :::note Hybrid mode is supported as of the connectors `0.23.0` release. Use the latest stable version from the [Camunda connectors Docker registry](https://hub.docker.com/r/camunda/connectors-bundle/tags). ::: **Hybrid mode** is where you can run a Self-Managed connector runtime instance attached to a Camunda SaaS cluster or another Self-Managed cluster that has another instance of the connector runtime attached. To name few use-cases where this approach might be useful: - When you deal with services that must be isolated within private network and must never be exposed to the public internet. - Infrastructure amendments need to be applied to the connector runtime, such as SSL certificates, mounted volumes, etc. - Code modifications applied to connector runtime, or specific connector logic. ## How it works Every connector has its ID (type definition), and name. Every connector element template has a hidden property that defines which connector is to be used to execute with a given template. For example, see a relation between [Kafka element template](https://github.com/camunda/connectors/tree/main/connectors/kafka/element-templates) and [Kafka connector](https://github.com/camunda/connectors/blob/main/connectors/kafka/src/main/java/io/camunda/connector/kafka/inbound/KafkaExecutable.java#L20). For the hybrid connector runtime to work properly, you must override the connector type. For the purpose of this guide, imagine you would like to override an HTTP REST connector with type `io.camunda:http-json:1`. Refer to the [element template](https://github.com/camunda/connectors/blob/main/connectors/http/rest/element-templates/http-json-connector.json#L50) and its related [runtime](https://github.com/camunda/connectors/blob/main/connectors/http/rest/src/main/java/io/camunda/connector/http/rest/HttpJsonFunction.java#L43). ## Start connector runtime in hybrid mode ### Prerequisites Ensure you have a running Camunda cluster, and a pair of `Client ID`/`Client Secret` with `Orchestration Cluster REST API` scope. Learn more about [how to obtain required credentials](/components/saas/clusters/manage-api-clients.md). To use secrets managed by the SaaS environment, add the `Secrets` scope. ### Option 1: Get connector runtime from Docker registry :::note When to use? Use this option when you don't need to make any code modifications to either connector runtime, or a specific connector. This option allows you to start the connector runtime bundle that runs all of [Camunda's officially-supported connectors](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md). ::: Use `camunda/connectors-bundle:` for a customer-managed hybrid runtime, even when connecting to a Camunda SaaS cluster. :::warning Don't use `camunda/connectors-bundle-saas`. This internal image is intended for Camunda-managed SaaS runtimes and won't work for customer-managed hybrid deployments. ::: Run the following script: ```shell docker run --rm --name=HybridConnectorRuntime \ -e CAMUNDA_CLIENT_MODE=saas \ -e CAMUNDA_CLIENT_CLOUD_CLUSTERID='' \ -e CAMUNDA_CLIENT_CLOUD_REGION='' \ -e CAMUNDA_CLIENT_AUTH_CLIENTID='' \ -e CAMUNDA_CLIENT_AUTH_CLIENTSECRET='' \ -e CONNECTOR_HTTP_REST_TYPE='io.camunda:http-json:local' \ camunda/connectors-bundle: ``` ### Option 2: Build your own runtime :::note When to use? Use this option when you make modifications to the original connector runtime, existing connectors, or other related changes. This option allows you to start the connector runtime bundle with provided connectors. ::: 1. Ensure `docker` is installed. 2. Clone [https://github.com/camunda/connectors](https://github.com/camunda/connectors). 3. Go to `/bundle/default-bundle`. 4. Build a connector image, e.g. `docker build -f Dockerfile -t myorg/my-connectors-bundle: .`. 5. Run the same `docker run ...` command as in [Option 1](#option-1-get-connector-runtime-from-docker-registry), using your custom image. ### Explanation Note the line `-e CONNECTOR_HTTP_REST_TYPE='io.camunda:http-json:local'`. This line means we have to override `CONNECTOR_X_TYPE` with a given type. In this case, we want to register a local Self-Managed HTTP REST connector as `io.camunda:http-json:local`. The `X` is normalized to the environment variable connector name. For example, the [HTTP REST connector](https://github.com/camunda/connectors/blob/main/connectors/http/rest/src/main/java/io/camunda/connector/http/rest/HttpJsonFunction.java#L33) `HTTP REST` name becomes `HTTP_REST`, or the [Kafka consumer connector](https://github.com/camunda/connectors/blob/main/connectors/kafka/src/main/java/io/camunda/connector/kafka/inbound/KafkaExecutable.java#L20) name becomes `KAFKA_CONSUMER`. Therefore, to override it one would need to pass in the `CONNECTOR_KAFKA_CONSUMER_TYPE=xxx` environment variable. #### Common connector types | Connector | Environment variable | Example value | | ------------------------- | ---------------------------------- | ------------------------------------- | | HTTP REST | `CONNECTOR_HTTP_REST_TYPE` | `io.camunda:http-json:local` | | AWS SQS (Outbound) | `CONNECTOR_AWS_SQS_OUTBOUND_TYPE` | `io.camunda:aws-sqs:local` | | Kafka consumer (Inbound) | `CONNECTOR_KAFKA_CONSUMER_TYPE` | `io.camunda:connector-kafka:local` | | Kafka producer (Outbound) | `CONNECTOR_KAFKA_PRODUCER_TYPE` | `io.camunda:connector-kafka:local` | | SendGrid | `CONNECTOR_SENDGRID_TYPE` | `io.camunda:sendgrid:local` | | Slack | `CONNECTOR_SLACK_TYPE` | `io.camunda:slack:local` | | Gmail | `CONNECTOR_GMAIL_TYPE` | `io.camunda:gmail:local` | | Google Drive | `CONNECTOR_GOOGLE_DRIVE_TYPE` | `io.camunda:google-drive:local` | | RabbitMQ (Inbound) | `CONNECTOR_RABBITMQ_CONSUMER_TYPE` | `io.camunda:connector-rabbitmq:local` | | RabbitMQ (Outbound) | `CONNECTOR_RABBITMQ_PRODUCER_TYPE` | `io.camunda:connector-rabbitmq:local` | For a complete list of all available connectors and their types, see the [available connectors overview](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md) or check the [official connectors repository](https://github.com/camunda/connectors). ### Use Self-Managed authentication types A hybrid runtime is customer-managed, so it can use authentication types that aren't supported by Camunda-managed SaaS runtimes. Examples include: | Connector | Authentication type | Credential source | | ------------------------------------------ | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- | | AWS connectors, such as Lambda and SQS | **Default Credentials Chain (Hybrid/Self-Managed only)** (`defaultCredentialsChain`) | The AWS SDK credential provider chain, including environment variables, credential files, or an IAM role. | | Embeddings connector with Google Vertex AI | Application default credentials | Credentials available to the runtime through Google's application default credentials mechanism. | Leave `CAMUNDA_CONNECTOR_RUNTIME_SAAS` unset. Some connectors check for the variable's presence, so setting it to `false` still triggers SaaS-only validation. Keep `CAMUNDA_CLIENT_MODE=saas` when connecting to a SaaS cluster; this client setting doesn't mark the connector runtime as Camunda-managed. If AWS default credentials chain authentication is rejected, the validation error includes `defaultCredentialsChainUsedInSaaS` and `must be false`. Google Vertex AI application default credentials are rejected with `Google application default credentials is not supported on SaaS`. These errors indicate the SaaS runtime marker is set. Use `camunda/connectors-bundle` and remove `CAMUNDA_CONNECTOR_RUNTIME_SAAS` from your deployment configuration. If startup fails with an error referencing `camunda.connector.auth.issuer` after selecting the SaaS image, switch images rather than supplying internal SaaS authentication settings. For example, to run an AWS SQS outbound connector on Amazon Elastic Container Service (ECS), use `camunda/connectors-bundle` and set `CONNECTOR_AWS_SQS_OUTBOUND_TYPE=io.camunda:aws-sqs:local` in the ECS task definition, alongside the Camunda client settings shown in the Docker example. Assign the ECS task an IAM task role with the required SQS permissions, and select **Default Credentials Chain (Hybrid/Self-Managed only)** in the hybrid element template. The task role supplies AWS credentials; it doesn't replace the Camunda API client credentials. ## Using SaaS-managed secrets If you add the `Secrets` scope to your API client, you can access [SaaS-managed secrets](/reference/glossary.md#saas-managed-secret) as [legacy secret references](/components/connectors/use-connectors/index.md#using-secrets) in a hybrid setup. Enable the SaaS secret provider via an environment variable or in your application config file: **Environment variable:** ``` CAMUNDA_CONNECTOR_SECRETPROVIDER_CONSOLE_ENABLED = true ``` **Properties file:** ``` camunda.connector.secretprovider.console.enabled = true ``` After enabling Camunda Hub, secret provider secrets used in an external connector's runtime will be resolved by fetching them from Camunda Hub. ## Preparing element template for hybrid mode Hybrid element templates must use the connector type registered by your customer-managed runtime. The standard connector templates available in Modeler use the default connector types, not your custom hybrid types. Modeler can't infer your runtime's type overrides, so you must obtain, configure, and publish a template for your hybrid deployment. Using a distinct type prevents the Camunda-managed runtime from executing work intended for your hybrid runtime. To do this, take the following steps: 1. Obtain a copy of the element template you wish to override. All latest versions of the official element templates can be found in the [official connectors repository](https://github.com/camunda/connectors/tree/main/connectors) at path `connectors//element-templates/`. Where available, use the `hybrid/` subdirectory, for example `connectors/aws/aws-sqs/element-templates/hybrid/`. Choose the repository tag matching your runtime version. 2. Modify the `value` to match your runtime's `CONNECTOR__TYPE` override. For outbound connectors, update the property bound to `zeebe:taskDefinition` with `"property": "type"` (or `zeebe:taskDefinition:type` in older templates). For inbound connectors, update `inbound.type`. Hybrid variants expose this field but can still default to the standard connector type, so verify the value before deploying. 3. Publish the new element template, and use it in your BPMN diagram. There are several options to deliver element templates to the target user: ### Option 1: Hide task definition type value Use this option when you plan to clearly indicate that a specific connector will only be used in a specific use-case. Otherwise, users might be confused between two of the same connector types. For example, if you defined `CONNECTOR_HTTP_REST_TYPE='io.camunda:http-json:local'` argument variable when running connectors runtime, you must implement the following in the element template for it to function properly: ```json { "type": "Hidden", "value": "io.camunda:http-json:local", "binding": { "type": "zeebe:taskDefinition:type" } } ``` ### Option 2: Expose task definition type as plain text Use this option when the target user building a BPMN process is deciding which connector to use, or you have more than one dedicated Self-Managed connector instance. Be mindful that the user will be dealing with different task definition types and has to know which is what. For example, if you defined `CONNECTOR_HTTP_REST_TYPE='io.camunda:http-json:local'` in runtime, you must implement the following in the element template for it to function properly: ```json { "type": "String", "label": "Task definition type", "value": "io.camunda:http-json:local", "binding": { "type": "zeebe:taskDefinition:type" } } ``` However, the target user can change the value back to the original `"value": "io.camunda:http-json:1",` to execute the process in a SaaS environment. You can also add this field to a group for UX purposes. ### Option 3: Expose task definition type as dropdown Use this option if you would like to achieve the most user-friendly experience. However, this approach may take a larger time investment in modifying element templates, plus additional time to support whenever you launch a new Connector runtime or disable an old one. The following example demonstrates this approach: ```json { "label": "Task definition type", "type": "Dropdown", "value": "io.camunda:http-json:1", "choices": [ { "name": "SaaS environment", "value": "io.camunda:http-json:1" }, { "name": "SM environment 1", "value": "io.camunda:http-json:local1" }, { "name": "SM environment 2", "value": "io.camunda:http-json:local2" } ], "binding": { "type": "zeebe:taskDefinition:type" } } ``` ## Appendix See hybrid element template examples for [HTTP REST](https://github.com/camunda/connectors/blob/main/connectors/http/rest/element-templates/hybrid/http-json-connector-hybrid.json), [Kafka consumer](https://github.com/camunda/connectors/tree/main/connectors/kafka/element-templates/hybrid), [AWS Lambda](https://github.com/camunda/connectors/blob/main/connectors/aws/aws-lambda/element-templates/hybrid/aws-lambda-outbound-connector-hybrid.json), and [AWS SQS](https://github.com/camunda/connectors/blob/main/connectors/aws/aws-sqs/element-templates/hybrid/aws-sqs-outbound-connector-hybrid.json). Match the connector type in each template to your runtime override. --- ## Display and download a document In this section, learn how to build a form for document preview and download, and display and download a document from a user task in Tasklist. ## Build a form for document preview and download To display and allow downloading of a document you can use the [document preview component](/components/modeler/forms/form-element-library/forms-element-library-document-preview.md) in [forms](/components/modeler/forms/camunda-forms-reference.md). Although this example focuses on [Camunda Hub](/components/hub/workspace/modeler/index.md), you can also build a form for document preview and download in [Desktop Modeler](/components/modeler/desktop-modeler/index.md). :::note The document preview component offers previews in forms of PDF documents and images as the most common file types. Other document types are supported, but listed without the preview and show the file name with the option to download the file. ::: In the component's configuration, provide a document reference as an array of document metadata. ![document preview for form](./img/document-preview-in-form.png) For example, an array may look as follows: ``` [ { "documentId": "u123", "endpoint": "https://api.example.com/documents/u123", "metadata": { "fileName": "Document.pdf", "contentType": "application/pdf" } } ] ``` For an example of the document reference as an array on the page for the document preview form component, review our documentation on [document preview](/components/modeler/forms/form-element-library/forms-element-library-document-preview.md). ## Display and download a document from a user task in Tasklist A document can be displayed in a user task form in [Tasklist](/components/tasklist/introduction-to-tasklist.md). When a user opens the task, they can view and download the document directly from the form. ![Document preview for task in Tasklist](./img/task-with-document-preview-tasklist.png) ## View and download a document in Operate When a process instance variable references a document, you can preview and download that document directly from the **Variables** tab in the process instance detail view. ![Document reference variables in the Operate Variables tab](./img/document-preview-in-operate.png) Document variables display the document's file name, type, and size. You can preview supported formats (PDF, JSON, plain text, PNG, JPG) directly in Operate, or download any document to your local machine. Use the **All / Documents** filter to show only document variables in the table. This works the same whether the document came from a form, a connector, or an [AI Agent tool call](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-documents.md). To inspect the underlying document reference metadata (such as document ID), expand the variable row. ## Download a document using the Orchestration Cluster REST API You can also download a document from your Camunda 8 cluster using the Orchestration Cluster REST API. :::note This is currently supported for document stores of type: [AWS, GCP, in-memory (non-production), local (non-production)](/self-managed/concepts/document-handling/configuration/index.md). ::: Learn more about this request in the [Orchestration Cluster REST API docs](/apis-tools/orchestration-cluster-api-rest/specifications/get-document.api.mdx). --- ## Getting started Camunda 8 provides built-in support for storing, tracking, and managing documents using Camunda Forms, connectors, Tasklist, and the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/specifications/create-documents.api.mdx) in both SaaS and Self-Managed. :::note For a closer look at storage options for handling documents in Self-Managed environments, visit the [Self-Managed configuration docs](/self-managed/concepts/document-handling/configuration/index.md). ::: Document handling is automatically integrated into each SaaS cluster, allowing you to manage binary data, like PDFs, images and other file types, across development and production environments without needing to configure or maintain storage infrastructure yourself. ## Use cases and capabilities Document handling can be beneficial for different process use cases, such as uploading a document to a BPMN process, displaying and downloading a document, sending a document to an external system via a connector, automating documents with [intelligent document processing](/components/hub/workspace/modeler/idp/idp-example.md), and passing a document to an [AI agent tool](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-documents.md). Step through all of these capabilities in the [use cases](/components/document-handling/overview.md) section. A document can either be routed through the document store as an opaque file, or flow inline as process data the process reads and writes directly. Learn more in [two paths for document handling](/components/document-handling/overview.md#two-paths-for-document-handling). ## Storage options ### SaaS Camunda SaaS manages storage for you by integrating with [**Google Cloud Platform (GCP)**](https://cloud.google.com/storage) and [**AWS S3**](https://aws.amazon.com/s3/) bucket storage. - Each cluster automatically includes one pre-configured storage bucket. Clusters hosted on GCP use a GCP bucket. Clusters hosted on AWS use an AWS S3 bucket. - **Maximum upload size per request (whether you're uploading one or multiple files in that request)**: 10 MB - **File expiration time/time-to-live (TTL) policy**: 30 days. A custom expiration date can be specified via metadata for each document. The [document upload API](/apis-tools/orchestration-cluster-api-rest/specifications/create-document.api.mdx) allows this. You can only set a custom expiration date earlier than the bucket's default TTL; requesting a later date results in it being capped to the bucket's default TTL. For forms, this defaults to the cluster configuration as there is no set custom TTL for forms. ### Self-Managed If you're deploying Camunda in a Self-Managed environment, document storage must be configured manually. To learn more, visit the [Self-Managed configuration docs](/self-managed/concepts/document-handling/configuration/index.md). :::note For storage options in SaaS, you cannot combine AWS and GCP, but you can in Self-Managed (for example, a cluster on GCP and document storage on AWS). This may be the case based on your existing infrastructure. However, having a cluster and document storage by the same provider (GCP or AWS) is more practical. In this case, you may reduce latency, simplify configuration, and avoid potential cross-cloud data transfer costs. ::: --- ## Use cases Offering robust [document handling](/components/document-handling/getting-started.md) capabilities within Camunda 8, users can efficiently manage large volumes of binary data such as PDFs and images across both development and production environments in SaaS and Self-Managed. In this section we will cover three main use cases: - [Upload a document to a BPMN process](/components/document-handling/upload-document-to-bpmn-process.md) - [Display and download a document](/components/document-handling/display-and-download-document.md) - [Send a document to an external system via a connector](/components/document-handling/send-document-to-external-system.md) ## Two paths for document handling When a document flows through a process, it follows one of two paths. Understanding which path you need makes it easier to choose the right connector configuration. ### Path 1: Document Store (opaque pass-through) The document is a blob that moves through the process. You don't need to read or manipulate its content. You just route it. The process never "looks inside" the file. Typical examples: - A webhook receives a PDF, and you upload it to Amazon S3. - You download an image from Google Cloud Storage and send it via email. On this path, the document is held in the [Camunda document store](/components/document-handling/getting-started.md) and passed between systems as a reference. ### Path 2: Inline (data the process works with) The content _is_ actual process data, and it is not stored in the document store. It flows as regular process variables, such as strings or JSON objects, that happen to be written to or read from an external system. You either: - **Write it**: construct a `.json`, `.csv`, or `.txt` file from process variables and upload it to storage (for example, an error report built from process data). See [inline documents](/components/document-handling/send-document-to-external-system.md#inline-documents). - **Read it**: download content from storage as JSON or text and use the values directly in FEEL expressions downstream. See [return formats](/components/document-handling/send-document-to-external-system.md#return-formats). Path 2 does not involve the document store. It is best suited to smaller text or JSON files, since inline content is bounded by the process variable size limit. ## Automate documents with intelligent document processing Document handling can be integrated with intelligent document processing (IDP). This allows you to extract specific data from a high volume of documents using an IDP application, and use the extracted data throughout your BPMN process. Learn more about this in the IDP documentation: --- ## Handle documents with outbound connectors Outbound connectors that support [document handling](/components/document-handling/getting-started.md) share a consistent experience for both directions: - When a connector **consumes** a document (upload or send), you choose a **document source**: a Camunda document, inline content, or an external URL. - When a connector **produces** a document (download or retrieve), you choose a **return format**: a document reference, text, or JSON. This maps onto the [two paths for document handling](/components/document-handling/overview.md#two-paths-for-document-handling): the document store path routes an opaque file, while the inline path lets the process build or read the content directly. The [connector SDK](/components/connectors/custom-built-connectors/connector-sdk.md) provides document support in property/variable bindings. :::note The unified document source and return format described on this page are available from Camunda **8.10** onward and apply to newly created element templates. Processes built on earlier template versions continue to work unchanged. ::: ## Document sources Every document handed to a connector is one of three **document reference types**, distinguished by the `camunda.document.type` field. Connectors that take a `Document` input expose a **document source** dropdown in the properties panel, with one option per type. Each option reveals only the relevant fields: | Source | Reference type | Fields revealed | Use when | | --------------------- | -------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Camunda document** | `camunda` | Document reference (FEEL) | You already have a document in the [Camunda document store](#camunda-documents) (Path 1). | | **Inline content** | `inline` | Content, optional filename, optional content type | You want to work with small text files (for example, `.json` or `.txt`) that are written from or read into process data (Path 2). See [inline documents](#inline-documents). | | **External document** | `external` | URL, optional filename | The file lives at a reachable URL. See [external documents](#external-documents). | ![example REST configuration](./img/rest-outbound-document.png) The three subsections below define the JSON structure of each type. You can also select a type from the source dropdown without writing the JSON by hand. ### Camunda documents A Camunda document is a reference to a file held in the [Camunda document store](/components/document-handling/getting-started.md). This is the document store path (Path 1): the file is routed as an opaque blob. Such references are produced for you by a [form Filepicker, inbound webhook, or the Orchestration Cluster REST API](/components/document-handling/upload-document-to-bpmn-process.md), as the output of another connector, or as an [AI Agent tool call result](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-documents.md), then stored in a process variable. A reference has the following structure: ```json { "camunda.document.type": "camunda", "storeId": "gcp", "documentId": "example-document-id", "contentHash": "fwkhkj34843rfhfwho3297ufdsj0df09", "metadata": { "contentType": "application/pdf", "size": 70266, "fileName": "file.pdf" } } ``` You normally reference the variable directly rather than constructing this object by hand. ### Inline documents An inline document embeds content directly in a process variable, with no document store upload required. This is the inline path (Path 2, write side): useful when you want to generate a document on-the-fly from process data, such as an error report, and pass it immediately to a connector. To create an inline document, set a process variable to the following structure: ```json { "camunda.document.type": "inline", "content": "Invoice #1234 — Amount due: $99.00", "name": "invoice.txt", "contentType": "text/plain" } ``` You can also construct this with a FEEL expression to build the content dynamically from other process variables: ```feel = { "camunda.document.type": "inline", "content": "Invoice #" + invoiceId + " — Amount due: $" + string(amount), "name": "invoice-" + invoiceId + ".txt" } ``` The `content` field is polymorphic: a string is stored as its UTF-8 bytes, while a map, list, number, or boolean is serialized to JSON. This lets you build structured files directly from process variables: ```feel = { "camunda.document.type": "inline", "content": {"orderId": orderId, "status": "failed", "errors": errorList}, "name": "error.json", "contentType": "application/json" } ``` | Field | Required | Description | | ----------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `camunda.document.type` | Yes | Must be `"inline"`. | | `content` | Yes | The document content. A string is stored as UTF-8 bytes; a map, list, number, or boolean is serialized to JSON. | | `name` | No | The filename. Drives content type inference when `contentType` is not set. If omitted, a UUID is generated automatically. | | `contentType` | No | The MIME type of the content. If omitted, the type is inferred from the file extension of `name`. If the extension is unrecognized or no name is provided, defaults to `application/octet-stream`. | :::note Inline documents are held in process variables, so their size is bounded by the [Zeebe variable size limit](/components/concepts/variables.md) (approximately 4 MB). For larger files, use the Camunda document store path instead. There is no base64 field for binary content. To inline binary data, encode it with FEEL's built-in [`to base64` function](/components/modeler/feel/builtin-functions/feel-built-in-functions-string.md#to-base64value). ::: ### External documents An external document points to a file available for download from an unprotected URL. Any connector can consume it directly, without first uploading it to the document store (Path 1: the file is routed, not inspected). To use an external document, set a process variable to the following structure: ```json { "camunda.document.type": "external", "url": "https://www.example.com/file.pdf", "name": "my-test-file.pdf" } ``` | Field | Required | Description | | ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `camunda.document.type` | Yes | Must be `"external"`. | | `url` | Yes | The URL the file is downloaded from. | | `name` | No | The filename. If omitted, the name is taken from the `content-type` and `content-disposition` HTTP response headers, with a random UUID used as the fallback. | ### List of documents Connectors that accept a list of documents (for example, email, Slack, or SendGrid attachments) add a **Single/Multiple** toggle above the source dropdown: - **Single** shows the same document source dropdown for one document. - **Multiple** switches to a FEEL expression field where you construct an array of document references. ## Return formats Connectors that download or retrieve a document let you choose how the content is returned, instead of guessing from the content type. A **return format** dropdown offers three options: | Return format | What you get | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | **Document reference** | The content is uploaded to the [Camunda document store](/components/document-handling/getting-started.md) and a reference is returned (Path 1). | | **As text** | The bytes are decoded to a string and returned in the response (Path 2, read side). An optional **encoding** sub-field defaults to UTF-8. | | **As JSON** | The bytes are parsed as JSON and returned as a structured value you can use directly in FEEL (Path 2, read side). | :::note **As text** and **As JSON** return the content directly in a process variable, so they are subject to a size guard (approximately 1.5 MiB). A larger object fails the job with a controlled incident rather than exhausting runtime memory. Use **Document reference** for large files. The **As JSON** option fails the job when the content is not valid JSON. Use **As text** for non-JSON content. ::: The exact response variable differs per connector (for example, S3 returns `element`, Google Cloud Storage returns `content`, and the REST connector returns `body`). See each connector's page for its response structure. ## Outbound connectors that support document handling | Connector | Support details | | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [Amazon Bedrock](/components/connectors/out-of-the-box-connectors/amazon-bedrock.md) | Supports consuming documents as inputs for conversations. Review the **Document** field in the properties panel where the document reference can be provided. | | [Amazon S3](/components/connectors/out-of-the-box-connectors/amazon-s3.md) | Supports uploading documents from (or downloading documents to) the Camunda document store. Review the **Document** field in the properties panel where the document reference can be provided. | | [Amazon Textract](/components/connectors/out-of-the-box-connectors/amazon-textract.md) | Supports reading the input document from the Camunda document store or an external URL (not inline content, which Textract cannot process), and returning the analysis result as a document reference instead of inline JSON. | | [Azure Blob Storage](/components/connectors/out-of-the-box-connectors/azure-blob-storage.md) | Supports uploading documents from (or downloading documents to) the Camunda document store. Review the **Document** field in the properties panel where the document reference can be provided. | | [Box](/components/connectors/out-of-the-box-connectors/box.md) | Supports uploading documents from (or downloading documents to) the Camunda document store. Review the **Document** field in the properties panel where the document reference can be provided. | | [Email](/components/connectors/out-of-the-box-connectors/email-outbound.md#response-structure-1) | Supports sending Camunda documents as attachments, or storing incoming attachments as Camunda documents. These documents are automatically stored in the Camunda document store and available to map in the result expression. | | [Google Drive](/components/connectors/out-of-the-box-connectors/googledrive.md) | Supports document upload and download. | | [Microsoft Teams](/components/connectors/out-of-the-box-connectors/microsoft-teams.md) | Supports sending documents to channels. | | [REST](/components/connectors/protocol/rest.md) | Supports storing the response as a document. | | [SendGrid](/components/connectors/out-of-the-box-connectors/sendgrid.md) | Provides attachment support. | | [Slack](/components/connectors/out-of-the-box-connectors/slack.md) | Supports adding attachments and increasing template versions. | ## Additional resources - [Camunda Academy: How To Handle Documents with Email Connector](https://academy.camunda.com/c8-h2-handle-documents-email-connector) --- ## Upload a document to a BPMN process You can implement document uploads in your BPMN processes using [forms](#build-a-form-for-document-upload), [inbound connectors](#upload-a-document-via-inbound-webhook-connector), and the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/specifications/create-document.api.mdx). ## Build a form for document upload When [building a form](/components/hub/workspace/modeler/modeling/utilize-forms.md) for a process, you can use the [Filepicker form component](/components/modeler/forms/form-element-library/forms-element-library-filepicker.md) to allow users to upload files. In the Filepicker configuration, you can specify whether users can upload a single file or [multiple files](/components/modeler/forms/form-element-library/forms-element-library-filepicker.md#configurable-properties) and define the list of [supported file formats](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers). Although this example focuses on [Camunda Hub](/components/hub/workspace/modeler/index.md), you can also build a form for document upload in [Desktop Modeler](/components/modeler/desktop-modeler/index.md). The Filepicker form component is available in both environments. ![Form with Filepicker](./img/form-with-file-picker.png) A designed form can be [linked](/components/hub/workspace/modeler/modeling/advanced-modeling/form-linking.md) to a [user task](#upload-a-document-from-a-user-task-in-tasklist) or used to [start a process](#upload-a-document-to-start-a-process). Documents uploaded with the form can then be [referenced](#get-reference-to-an-uploaded-document) later in the process. The Filepicker always returns an array with metadata for a single or multiple files, for example: ```json [ { "documentId": "u123", "endpoint": "https://api.example.com/documents/u123", "metadata": { "fileName": "Document.pdf", "contentType": "application/pdf" } } ] ``` ### Upload a document from a user task in Tasklist When the process is deployed and running, users can access and complete user tasks that include a form with the Filepicker component in [Tasklist](/components/tasklist/introduction-to-tasklist.md): ![User task form with a file picker for uploading an ID document in Tasklist](./img/task-with-file-picker-tasklist.png) ### Upload a document to start a process You can configure a form with the Filepicker for a start event of a BPMN process to allow users to upload documents when initiating the process. This is supported in [Tasklist](/components/tasklist/introduction-to-tasklist.md) and is available to logged-in users. :::note Only logged-in users can upload files when starting a process from Tasklist. ::: ### Get reference to an uploaded document Uploaded documents can be referenced later in the process. Filepicker's output variable is an array of objects with document metadata. It always returns an array of objects, whether a user uploads a single document or multiple documents. Single document uploads are accessible using `value[1]` (since [FEEL](/components/modeler/feel/what-is-feel.md) uses 1-based indexing). Refer to the example array below: ``` [ { "documentId": "u123", "endpoint": "https://api.example.com/documents/u123", "metadata": { "fileName": "Document.pdf", "contentType": "application/pdf" } } ] ``` ## Upload a document when using any connector Documents available for download from an unprotected URL can be added to a process in any connector as an **external document**. The full JSON structure and field reference is documented in [document sources](/components/document-handling/send-document-to-external-system.md#external-documents). ## Upload a document via inbound webhook connector Documents can be added to a process using the [inbound](/components//connectors/connector-types.md#inbound-connectors) [HTTP webhook connector](/components/connectors/protocol/http-webhook.md). You can pass the documents in both the response expression and the result expression, where the `documents` object contains the references for created documents. Below, review an example of a webhook configuration: ![Example payload of inbound webhook connector](./img/inbound-webhook-connector-example.png) In this example, the result expression may look as follows, where `applicationDocument` can be later used by the process to retrieve documents: ``` { applicationDocument: documents[1] } ``` The document reference received as an output of one connector should be stored in process variables by using the result expression or result variable. To call the webhook sending a file, see the following example: ```curl curl --location 'https://some.dev.environment/uploadDocument' \ --form 'file=@"/path-to-file/file.pdf"' ``` :::note This example uses Postman to obtain the result, so your `user-agent` value may look different. ::: The result variable will have the following structure: ``` { "request": { "body": {}, "headers": { "host": "example.host.io", "x-request-id": "34509f2d9293cdfj49875rjf03", "x-real-ip": "some.example.ip.address", "x-forwarded-host": "example.host.io", "x-forwarded-port": "443", "x-forwarded-proto": "https", "x-forwarded-scheme": "https", "x-scheme": "https", "content-length": "70484", "user-agent": "PostmanRuntime/7.43.0", "accept": "*/*", "cache-control": "no-cache", "postman-token": "my-example-token", "accept-encoding": "gzip, deflate, br", "content-type": "multipart/form-data; boundary=--------------------------3007423254435453453514" }, "params": {} }, "connectorData": {}, "documents": [ { "storeId": "gcp", "documentId": "example-document-id", "contentHash": "fwkhkj34843rfhfwho3297ufdsj0df09", "metadata": { "contentType": "application/pdf", "size": 70266, "fileName": "file.pdf" }, "camunda.document.type": "camunda" } ] } ``` ## Upload a document using the Orchestration Cluster REST API You can also upload a document to your Camunda 8 cluster using the Orchestration Cluster REST API. :::note This is currently supported for document stores of type: [AWS, GCP, in-memory (non-production), local (non-production)](/self-managed/concepts/document-handling/configuration/index.md). ::: Learn more about [uploading a single document](/apis-tools/orchestration-cluster-api-rest/specifications/create-document.api.mdx) and [uploading multiple documents](/apis-tools/orchestration-cluster-api-rest/specifications/create-documents.api.mdx) using the Orchestration Cluster REST API. --- ## A2A Client connector The A2A Client connector is used to interact with A2A agents, by retrieving remote agent’s [Agent Card](https://a2a-protocol.org/v0.3.0/specification/#5-agent-discovery-the-agent-card) and sending messages to that agent. ## Create an A2A Client connector task ## Make the A2A Client connector executable To make the A2A Client connector executable, fill out the mandatory fields highlighted in red in the properties panel on the right. The A2A Client connector supports two operational modes to accommodate different use cases: _standalone_ and _AI Agent tool_. ### Standalone mode In standalone mode, you use the connector directly in a BPMN process as a service task. You explicitly configure the operation to perform and all associated parameters. Configure the following: - HTTP connection: - **A2A server URL** (required): Base URL of the A2A server. For example, `http://localhost:10000`. - **Agent Card location** (optional): Path to the Agent Card endpoint relative to the base URL. By default, `.well-known/agent-card.json`. - Connector mode: Select **Standalone**. - Operation: Choose between two operations: 1. [Fetch Agent Card](#fetch-agent-card). 2. [Send message](#send-message). #### Fetch Agent Card Use this operation to retrieve the Agent Card from the remote A2A server. The Agent Card describes the agent's capabilities and skills. ##### Response This operation returns a JSON object containing the most relevant Agent Card details. Here's an example: ```json { "result": { "name": "Weather agent", "description": "Helps with weather information", "skills": [ { "id": "weather-forecast", "name": "Weather Forecast", "description": "Get weather forecast information", "tags": ["forecast", "weather"], "examples": [ "What's the weather like today in Berlin?", "Will it rain tomorrow in London?" ], "inputModes": ["text"], "outputModes": ["text"] } ], "kind": "agentCard" }, "pollingData": { "id": "20be510a-9e12-40c2-ad4d-899ded442db3" } } ``` See the [Response structure](#response-structure) section below for more details. #### Send message Use this operation to send a message to the remote agent and handle the response based on the configured retrieval method. ##### Configuration - **Text** (required): Message to send to the remote agent. - **Context ID** (optional): Identifier for grouping related interactions. Typically generated by the remote server; omit for first message or a new context. - **Task ID** (optional): ID of the task this message belongs to. Generated by the remote server; omit if not part of a specific task. - **Reference task IDs** (optional): List of task IDs this message references for context. FEEL expression; for example: `= ["e240a538-a857-4e88-b3d0-4ddaf4882cc6", "eb086e57-43e3-45c1-a944-9a23a8b823df"]`. - **Documents** (optional): List of [Camunda document references](/components/document-handling/getting-started.md) to include with the message. This supports the same document types as the [AI Agent connector](../../../connectors/out-of-the-box-connectors/agentic-ai-aiagent-documents.md#supported-document-types). - **Response retrieval** (required): How to receive the final response from the remote agent - `Blocking`: Waits synchronously for the agent's response. - `Polling`: Returns immediately with a task with `submitted` or `working` state; use the A2A Client Polling Connector to retrieve results. - `Notification`: Configures a webhook for the remote agent to push task-status updates; use the A2A Client Webhook Connector to receive notifications. - **Webhook URL** (required): URL where the agent will send the response. See [A2A Client Webhook Connector](./a2a-client-webhook-connector.md) for how to set up a webhook endpoint. - **Token** (optional): Unique token for validating notifications. Use a static value or a FEEL expression, such as `= uuid()`. - **Authentication schemes** (optional): List of auth schemes required by the webhook. - **Authentication credentials** (optional): Credentials to authenticate webhook requests. :::note You can use the `Notification` response retrieval method only if the remote agent supports it. Check the agent's capabilities in its Agent Card. ::: - **History length** (optional): Number of most recent messages from task history to include in the response. By default, it is three. - **Response timeout** (optional): Maximum time to wait for (the first) response, as ISO-8601 duration. By default, `PT1M` is one minute. ##### Response This operation returns a JSON object containing the remote agent's response. Here's an example when the remote agent replies with a [message](https://a2a-protocol.org/v0.3.0/specification/#64-message-object): ```json { "result": { "kind": "message", "role": "agent", "messageId": "202dc848-05d7-4aff-a147-45ba42c765b1", "contextId": "5b70baac-065e-4680-ace9-81289e67f51d", "contents": [ { "type": "text", "text": "Based on the weather forecast, it looks like it will be sunny in Berlin today with a high of 25°C and a low of 15°C." } ] } } ``` Here's an example when the remote agent replies with a [task](https://a2a-protocol.org/v0.3.0/specification/#61-task-object) in the `submitted` state: ```json { "result": { "kind": "task", "id": "1f184812-0926-4f2c-881c-65c158a6dd11", "contextId": "8802db7b-8401-46b0-89f8-1ec3d5e81194", "status": { "state": "submitted", "message": { "kind": "message", "role": "agent", "messageId": "3fc30325-d5d2-4d7a-a472-e197fb478acc", "contextId": "8802db7b-8401-46b0-89f8-1ec3d5e81194", "taskId": "1f184812-0926-4f2c-881c-65c158a6dd11", "contents": [ { "type": "text", "text": "Your weather forecast request has been submitted and is being processed. I'm gathering data from multiple weather stations and satellite imagery for your location." } ] }, "timestamp": "2025-11-27T15:45:47.542Z" }, "history": [ { "kind": "message", "role": "user", "messageId": "a35df285-783d-4ad7-87b9-16a0bbff7f30", "contextId": "8802db7b-8401-46b0-89f8-1ec3d5e81194", "taskId": "1f184812-0926-4f2c-881c-65c158a6dd11", "contents": [ { "type": "text", "text": "Please provide a 7-day weather forecast for San Francisco, CA including temperature, precipitation, and wind conditions." } ] } ] }, "pollingData": { "id": "1f184812-0926-4f2c-881c-65c158a6dd11" } } ``` :::note In this example, `pollingData` holds the task identifier that the A2A Client Polling connector uses internally. This assumes the response retrieval method is set to `Polling`. ::: See the [Response structure](#response-structure) section below for more details. ### AI Agent tool mode In AI Agent tool mode, the connector is registered as a tool that the AI Agent connector can invoke dynamically. This enables the AI agent to discover and interact with remote A2A agents autonomously based on user requests. Configure the following: - HTTP connection: Same as in Standalone mode. - Connector Mode: Select **AI Agent tool**. - Operation: In AI Agent tool mode, this is dynamically determined by the AI agent: - **Operation** (required): Automatically populated from `toolCall.operation`.Possible values: `fetchAgentCard`, `sendMessage`. - **Parameters** (optional): Automatically populated from `toolCall.params`. It contains the dynamic parameters for the `sendMessage` operation. - **Response retrieval** (required): Same as in Standalone mode. - **History length** (optional): Same as in Standalone mode. - **Response timeout** (optional): Same as in Standalone mode. #### How it works 1. The AI Agent connector detects this connector as an available tool. 2. The AI agent performs tool discovery using the `fetchAgentCard` operation. 3. When the AI agent decides it needs to interact with the A2A agent, it issues a tool call with the required parameters. 4. The tool call is translated into a `sendMessage` operation. 5. The connector executes `sendMessage`. 6. Results are returned to the AI agent. 7. If needed, the AI agent continues a multi‑turn conversation with the A2A agent by issuing additional tool calls that reference the same context and task IDs (for example, when the remote agent requests more information). 8. If needed, the AI agent can start new interactions with the A2A agent by issuing tool calls without context and task IDs. :::info When the AI Agent connector detects at least one A2A Client connector that is configured as a tool, it injects instructions into the system prompt to guide the LLM on using A2A tools. ::: ## Response structure The response from the A2A Client connector is a JSON object with the following fields: - `result`: The remote agent's response. Depending on the operation, this is an Agent Card, a message, or a task object. - `pollingData`: Internal data used to poll task status when the response retrieval method is `Polling`. - `notificationData`: Internal data used when the response retrieval method is `Notification`. --- ## A2A Client Polling connector The A2A Client Polling connector polls for responses from asynchronous A2A tasks. It is typically paired with the [A2A Client connector](./a2a-client-connector.md) when using the `Polling` response retrieval method. ## Create an A2A Client Polling connector task 1. Start building your BPMN diagram. You can use the A2A Client Polling connector with an **Intermediate Catch Event** or with a **Receive Task**. 2. Select the applicable element and change its template to a **A2A Client Polling connector**. 3. Fill in all required properties. 4. Complete your BPMN diagram. 5. Deploy the diagram to activate the A2A Client Polling connector. ## Make the A2A Client Polling connector executable To make the A2A Client Polling connector executable, fill out the mandatory fields highlighted in red in the properties panel on the right. Configure the following: - Connection: Match values used in the A2A Client connector: - **A2A server URL** (required): Base URL of the A2A server. - **Agent Card location** (optional): Path to the Agent Card endpoint. - **A2A Client response** (required): FEEL expression referencing the response from the A2A Client connector. - **History length** (optional): Number of messages to return as part of the history when polling. By default, it is three. - **Task polling interval** (optional): Delay between polling requests as ISO-8601 duration. By default, `PT10S` is 10 seconds. - **Activation condition**: FEEL expression to determine whether the polled data meet the criteria to activate the intermediate catch event. In most cases, the default value is sufficient; it checks if the polled task is in a final state. - **Message ID expression** (optional): Extracts the message ID from the incoming request. The message ID uniquely identifies the message, is evaluated in the connector runtime, and the result is used to correlate the message. In most cases, you don’t need to configure it, but it’s useful for message deduplication or specific correlation behavior. See [Message correlation](../../../concepts/messages.md#message-correlation-overview) for more details. - **Message TTL** (optional): Sets the time-to-live (TTL) for correlated messages. TTL defines the time a message is buffered in Zeebe before correlation (if it can't be correlated immediately). The value is specified as an ISO-8601 duration. For example, `PT1H` sets the TTL to one hour. See [Message buffering](../../../concepts/messages.md#message-buffering) for more details. :::note **Correlation key (process)** and **Correlation key (payload)** are prefilled with the required values and hidden; there’s no need to modify them. ::: - **Result variable** (optional): Typically leave empty. - **Result expression**: FEEL expression to extract the result from the polled response. Typically, extract the `result` field from the [response](./a2a-client-connector.md#response-structure) using a FEEL expression such as `= {a2aAgentResponse: result}` or `= {toolCallResult: result}`. --- ## A2A Client usage patterns The [A2A Client connectors](./a2a-client.md) often work in combination to interact with remote A2A agents. This page outlines common usage patterns for the A2A Client connectors. ## Synchronous request-response The most straightforward usage pattern is when the A2A Client connector sends a request to a remote A2A agent and waits for the response. This is suitable for scenarios where you need immediate results. The diagram below illustrates this pattern, with an A2A Client connector being used as a tool by an [AI Agent connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md): ![A2A Client connector - Blocking](img/a2a-blocking.png) ### Configure the A2A Client connector - **A2A server URL**: For example `https://a2a-agent.example.com`. - **Agent Card location**: If different from the default `.well-known/agent-card.json`. - **Response retrieval**: `Blocking`. - **Result expression**: `={toolCallResult: result}`. :::note Per the [A2A specification](https://a2a-protocol.org/v0.3.0/specification/#712-messagesendconfiguration-object), the remote A2A server might reject the request if the task is long-running. ::: ## Asynchronous polling For long-running tasks, the A2A Client connector can send a request to a remote A2A agent and then poll for the result at regular intervals. This pattern is suitable when your task may take an extended period to complete. The diagram below shows this pattern, with an A2A Client connector and an A2A Client Polling connector used as a tool by an AI Agent connector: ![A2A Client connector - Polling](img/a2a-polling-direct.png) ### Configure the connectors #### A2A Client connector - **A2A server URL**: For example `https://a2a-agent.example.com`. - **Agent Card location**: If different from the default `.well-known/agent-card.json`. - **Response retrieval**: `Polling`. An A2A Client Polling connector is used to fetch the result. - **Result variable**: A variable name to store the result used by the A2A Client Polling connector, e.g. `a2aAgentResponse`. #### A2A Client Polling connector - **A2A server URL**: Same as used in the A2A Client connector. - **Agent Card location**: Same as used in the A2A Client connector. - **A2A Client response**: The variable name used in the A2A Client connector, e.g. `=a2aAgentResponse`. - **Task polling interval**: For example `PT5S`. - **Result expression**: `={toolCallResult: result}`. ### Polling behavior The A2A Client Polling connector correlates the message immediately when polling is not required. This occurs in the following cases: 1. The operation is _Fetch Agent Card_. 2. The remote agent responds with a _message_. 3. The remote agent responds with a _task_ that is not in a `submitted` or `working` state. To enable the A2A Client Polling connector only when needed, place a gateway before the connector. The diagram below illustrates this pattern, with a gateway controlling whether the A2A Client Polling connector runs: ![A2A Client connector - Conditional Polling](img/a2a-polling-conditional.png) Use the following expression as the exclusive gateway condition to check whether polling is needed: ```feel a2aAgentResponse.result.kind = "task" and list contains(["submitted", "working"], a2aAgentResponse.result.status.state) ``` Configure the following output on the Completed synchronously throw event: - **Process variable name**: `toolCallResult`. - **Variable assignment value**: `=a2aAgentResponse.result`. :::tip You can use a [Receive task](/components/modeler/bpmn/receive-tasks/receive-tasks.md) instead of an intermediate catch event. This allows you to attach boundary events for timeouts or error handling. Assign the A2A Client Polling receive task connector template to the receive task and configure it as described above. ::: ## Asynchronous push notifications When the remote A2A agent supports push notifications, you can configure the A2A Client connector to send a request to the remote agent and then wait for task-update notifications. This pattern is suitable when the task may take an extended period to complete and push notifications are preferred over polling. The diagram below illustrates this pattern, showing an A2A Client connector combined with an A2A Client Webhook connector used as a tool by an AI Agent connector: ![A2A Client connector - Webhook](img/a2a-webhook.png) ### Configure the connectors #### A2A Client connector - **A2A server URL**: For example `https://a2a-agent.example.com`. - **Agent Card location**: If different from the default `.well-known/agent-card.json`. - **Response retrieval**: `Notification`. An A2A Client Webhook connector is used to wait for the notifications. - **Webhook URL**: For example `http://some-camunda-cluster.com/inbound/22083f06-72fa-4f09-b4c5-8e83a0d66cb1`. - **Result variable**: A variable name to store the result used by the A2A Client Polling connector, e.g. `a2aAgentResponse`. #### A2A Client Webhook connector - **Webhook ID**: For example `22083f06-72fa-4f09-b4c5-8e83a0d66cb1`. - **A2A agent response**: The variable name used in the A2A Client connector, e.g. `=a2aAgentResponse`. - **Result expression**: `={toolCallResult: request.body}`. Use the following expression as the exclusive gateway condition to check whether push notifications are needed: ```feel a2aAgentResponse.result.kind = "task" and list contains(["submitted", "working"], a2aAgentResponse.result.status.state) ``` Configure the following output on the Completed synchronously throw event: - **Process variable name**: `toolCallResult`. - **Variable assignment value**: `=a2aAgentResponse.result`. :::tip You can use a [Receive task](/components/modeler/bpmn/receive-tasks/receive-tasks.md) instead of an intermediate catch event. This allows you to attach boundary events for timeouts or error handling. Assign the A2A Client Webhook receive task connector template to the receive task and configure it as described above. ::: --- ## A2A Client Webhook connector The A2A Client Webhook connector receives callbacks from remote A2A agents via HTTP webhooks. It is typically paired with the [A2A Client connector](./a2a-client-connector.md) when using the `Notification` response retrieval method. This connector is based on the [HTTP Webhook connector](../../../connectors/protocol/http-webhook.md) and shares most of its configuration options. ## Create an A2A Client Webhook connector task 1. Start building your BPMN diagram. You can use the A2A Client Webhook connector with an **Intermediate Catch Event** or with a **Receive Task**. 2. Select the applicable element and change its template to a **A2A Client Webhook connector**. 3. Fill in all required properties. 4. Complete your BPMN diagram. 5. Deploy the diagram to activate the A2A Client Webhook connector. 6. Navigate to the **Webhooks** tab in the properties panel to see the webhook URL. :::warning Use the URL from step 6 in the A2A Client connector configuration, then redeploy your BPMN diagram. There’s currently no automatic way to inject the webhook URL into the A2A Client connector configuration. ::: :::note In Camunda 8 Self-Managed, you must construct the webhook URL manually. See [HTTP webhook connector documentation](../../../connectors/protocol/http-webhook.md#activate-the-http-webhook-connector-by-deploying-your-diagram) for more information. ::: ## Make the A2A Client Webhook connector executable To make the A2A Client Webhook connector executable, fill out the mandatory fields highlighted in red in the properties panel on the right. Configure the following: - **Webhook ID** (required): Unique identifier for the webhook endpoint. This ID becomes part of the webhook URL. - **A2A Client Response** (required): FEEL expression referencing the response from the A2A Client connector. :::note For authentication and authorization, see [HTTP Webhook connector documentation](../../../connectors/protocol/http-webhook.md) for more information. ::: - **Activation condition**: FEEL expression to determine if the payload meets the criteria to activate the intermediate catch event. In most cases, the default value is sufficient; it checks whether the received task is in a final state. - **Message ID expression** (optional): Extract the message ID from the incoming request. The message ID uniquely identifies the message, is evaluated in the connector runtime, and the result is used to correlate the message. In most cases you don’t need to configure this, but it’s useful for deduplication or specific correlation behavior. See [Message correlation](../../../concepts/messages.md#message-correlation-overview) for more details. - **Message TTL** (optional): Sets the time-to-live (TTL) for correlated messages. TTL defines the time a message is buffered in Zeebe before correlation (if it can't be correlated immediately). The value is specified as an ISO-8601 duration. For example, `PT1H` sets the TTL to one hour. See [Message buffering](../../../concepts/messages.md#message-buffering) for more details. :::note **Correlation key (process)** and **Correlation key (payload)** are prefilled with the required values and hidden; there’s no need to modify them. ::: - **Result variable** (optional): Typically leave empty. - **Result expression**: FEEL expression to extract the result from the payload. Typically, extract the `request.body` field using a FEEL expression such as `= {a2aAgentResponse: request.body}` or `= {toolCallResult: request.body}`. :::tip As in the HTTP Webhook connector, the payload is accessible via the `request` variable. Use the following references to access data: - Body: `request.body.`. - Headers: `request.headers.`. - URL parameters: `request.params.`. ::: --- ## A2A Client The Agent-to-Agent (A2A) Client connectors enable Camunda processes to interact with remote agents using the [A2A protocol](https://a2a-protocol.org/v0.3.0/specification/). ## About Combined with the [AI Agent connector](../../../connectors/out-of-the-box-connectors/agentic-ai-aiagent.md), the A2A Client suite of connectors supports multi-agent collaboration scenarios. It also provides capabilities to discover remote agents, send messages, and receive responses through multiple mechanisms. :::info A2A Client connectors do not currently support authentication against a remote A2A server. This feature will be added in a future release. ::: ## Prerequisites To use any A2A Client connector, you need: - Access to an A2A-compliant agent. - Its [Agent Card URL](https://a2a-protocol.org/v0.3.0/specification/#5-agent-discovery-the-agent-card). ## A2A Client connectors The A2A Client includes three connectors. Together, these connectors enable seamless integration with A2A-compliant agents, allowing you to build multi-agent workflows within Camunda processes. Explore common A2A Client usage patterns ## HTTP proxy support In Self-Managed environments, the A2A Client connector supports HTTP proxy configuration, including [plain proxy variables](/self-managed/components/connectors/http-proxy-configuration.md#plain-proxy-variables). See the [AI Agent connector proxy configuration](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-customization.md#http-proxy-configuration) for details, including how to disable proxy support. --- ## Alpha features You can use alpha features to learn about upcoming changes, try them out, and share feedback. :::info To understand the difference between an alpha feature and an alpha release, see [alpha features and releases](/reference/announcements-release-notes/release-policy.md#alpha-features-and-releases). ::: ## Alpha Selected Camunda features and components are released as **alpha** versions. We release these in an early state for you to test and participate in development by sharing your feedback before they reach [general availability](/reference/announcements-release-notes/release-policy.md#general-availability-ga). Limitations of alpha features and components include: - Not for production use. - APIs, dependencies, and configuration are likely to change. - Not necessarily feature-complete. - Might lack full documentation. - No guaranteed updates to newer releases. - Alpha releases cannot be updated to newer releases and are not suitable for use in production. - Support based on SLAs agreed with you, but bugs are treated with the same priority as feature or help requests. See [Camunda Enterprise Support Guide](https://camunda.com/services/enterprise-support-guide/). - No maintenance service. - (SaaS) No availability targets. - Released outside the standard [release policy](/reference/announcements-release-notes/release-policy.md). To learn more about using alpha features, see [enabling alpha features](/components/saas/organization/enable-alpha-features.md). ## Available alpha features The following documented alpha features are currently available: :::note - Alpha features can also be included in a minor version (stable) release. - Although there is no maintenance service, customers can still provide feedback through designated support channels, depending on their SLAs. These channels include filing issues in the respective [GitHub repositories](https://github.com/camunda) and submitting questions and suggestions by [contacting us](/reference/contact.md). - Alpha releases can also have **limited availability**, such as features that are only available to enterprise customers. ::: --- ## BPMN Copilot Chat with the AI BPMN Copilot for help generating new BPMN process diagrams in [Camunda Hub](/components/hub/workspace/modeler/index.md) based on a process description. You can also ask the BPMN Copilot questions about existing diagrams. :::note Terms of use By using this tool, you agree to Camunda's use of the anonymized input and output data and anonymized feedback to improve it. While your latest BPMN diagram will be saved in your account, Camunda does not save your prompts and the resulting BPMN diagrams. ::: ## Get started :::warning Creating a BPMN process diagram with the BPMN Copilot will overwrite existing work. ::: To use the BPMN Copilot in Camunda Hub, take the following steps: 1. Log in to Camunda Hub. 2. If you have not already done so, [opt in](/components/saas/organization/enable-alpha-features.md#enable-ai-powered-features) to use this feature. 3. Open a [workspace](/components/hub/organization/manage-workspaces/index.md), or create a new one. 4. In your workspace, create a new project. 5. In your project, click **Create new > BPMN diagram**. This opens the Camunda Copilot chat window. 4. In the chat box, enter your prompt. A prompt should be a simple, clear, and concise request. For example, "Generate a mortgage loan process diagram" or "Explain this process". Note that more complex requests may take longer to process. 5. Wait for the BPMN Copilot to respond. This typically takes between 20-50 seconds, depending on the prompt. :::note Timeouts can occur during this step if your query is too complex. ::: ### Example explanation prompts - "Describe this process in plain language" - "What KPIs would you recommend for this process?" - "Estimate the median and 95th percentile duration for this process" - "What does this symbol do?" [after selecting a BPMN element in the process] - "Give me a list of test cases to ensure this process completes as expected" - "Summarize this process for a new employee" - "Give me a prioritized list of recommended improvements to make the 95% of instances complete within 4 hours" ### Example creation prompts - "Create an absence request diagram" - "Design a process that uses data objects to drive process logic, showing how data influences process flow and decisions" - "Model a complex service orchestration process involving multiple service tasks, conditional routing, and parallel execution of tasks" - "Create a service order management process for a telco company. The process starts with a fraud check. First, calculate price dimensions via a script task, then check if the price dimensions are normal or high with an exclusive gateway. If high, escalate to a fraud investigation subprocess." - Paste existing text documentation of a process or of process requirements (for example, a Confluence page) - Paste a process hard-coded in any language (for example, BPEL, Java, COBOL, Python) - Paste a description of a diagram. Any LLM (like ChatGPT) can generate this description from a screenshot or an image of the diagram. ## Follow-up prompts The BPMN Copilot can also translate business-focused intent into actionable changes for the diagram it creates. For example: - "Modify this process to maximize its changes of adhering to a 1 day SLA" - "Reduce the operating cost of this process" - "Show me ways to integrate AI into the process" - "Consider unhappy paths as well" - "Add error handling" - "Improve the user experience" :::note Requesting specific modifications to one or several sections of the BPMN diagram may impact unrelated sections. ::: ### Limitations - The BPMN Copilot only supports processes up to approximately 400 KB in size. - The BPMN Copilot officially supports only modifying diagrams that were created by the BPMN Copilot itself. It does not officially support modifying other existing diagrams (including diagrams with implementation details). - The BPMN Copilot does not support pools, lanes, and collaborations. --- ## FEEL Copilot Chat with the AI FEEL Copilot for help generating [FEEL (Friendly Enough Expression Language)](/components/modeler/feel/what-is-feel.md) expressions in [Camunda Hub](/components/hub/workspace/modeler/index.md). :::note Terms of use By using this tool, you agree to Camunda's use of the anonymized input and output data and anonymized feedback to improve it. Camunda does not save your prompts and the resulting FEEL expressions to your account. ::: ## Get started :::warning - Clicking **Use Expression** on a FEEL Copilot response will overwrite your existing work. - As the FEEL Copilot can produce errors, you **must** check its output before saving the results to your diagram. ::: To use the FEEL Copilot in Camunda Hub, take the following steps: 1. Log in to Camunda Hub. 1. If you have not already done so, [opt in](/components/saas/organization/enable-alpha-features.md#enable-ai-powered-features) to use this feature. 1. Open a [workspace](/components/hub/organization/manage-workspaces/index.md), or create a new one. 1. In your workspace, create a new project. 1. In your project, click **Create new > BPMN diagram**. 1. Open any FEEL Popup. :::note To open a FEEL popup from an empty diagram: 1. In a new diagram, click the Start Event (the circle in the diagram). 1. In the sidebar, click the plus (**+**) next to **Outputs**, and click **Variable assignment value**. 1. Click the **Open popup editor** icon in the field to open the FEEL popup. ::: 1. Open the FEEL Copilot panel. 1. In the chat box, enter your prompt and click **Send**. A prompt should be a simple, clear, and concise request. See [Example Prompts](#example-prompts) for ideas. Note that more complex requests may take longer to process. 1. Wait for the FEEL Copilot to respond. This typically takes between 15-30 seconds, depending on the complexity of the prompt. 1. Click **Use expression** to save the expression in the FEEL popup. 1. Close the FEEL popup to save the expression to your diagram. :::note If your query is too complex, timeouts can occur when waiting for the FEEL Copilot to process a request. ::: ## Example prompts ### Generate FEEL expressions - "Find the difference between two dates" - "Get the name from \{"name": "Alice", "id", 123\}" - "Check if a number is greater than 10" - "What would this Java be in FEEL: input.trim().toUpperCase().replace(" ", "\_");" ### Translate code to FEEL - "Translate from JUEL" - "Translate from Python" - "Translate from JavaScript" ### Debug & refactor FEEL - "Fix this expression" - "Why am I getting a null response?" - "Make it more compact" ### Explain FEEL - "How does this work?" - "What does [FEEL function] do?" - "Document how this FEEL expression works" ### Examples - "What can the FEEL Copilot do? (list of use cases)" - "Give me sample input data" - "Give me an example FEEL expression" ### Limitations - The FEEL Copilot only supports prompts up to approximately 4 MB in size. --- ## Early access Explore new features and components currently in development by Camunda. ## Alpha features [Alpha features](/components/early-access/alpha/alpha-features.md) are more developed and closer to becoming part of the product but may not yet be fully optimized or supported. Selected Camunda features and components are released as alpha versions to provide early access. By testing these features, you have the opportunity to participate in their development by sharing feedback before they reach [general availability](/reference/announcements-release-notes/release-policy.md#general-availability-ga). ## What to expect from alpha features | | Alpha features | | ---------------------------------- | ------------------------------------------------------------------------------------------------ | | Purpose | Test upcoming features that could make it to GA | | Suitable for production use | No | | Stability | APIs, dependencies, and configuration are likely to change. | | Feature complete | No | | Documentation | May have some documentation. | | Updates | No guaranteed updates to newer releases, but likely to continue development. | | Support | Support based on SLAs, with bugs treated as part of regular feature/help requests. | | Maintenance service | No | | Available on | SaaS/Self-Managed | | Release cycle | Outside the standard [release policy](/reference/announcements-release-notes/release-policy.md). | | Admin/owner access required | Yes | ## Early access release An early access release introduces a new product. By testing it, you can contribute to its development by sharing feedback before it reaches [general availability](/reference/announcements-release-notes/release-policy.md#general-availability-ga). ## What to expect from early access releases | | Early access release | | ---------------------------------- | ------------------------------------------------------------------------------------------------ | | Purpose | Test maturity of a new product before GA | | Suitable for production use | No | | Stability | APIs, dependencies, and configuration are likely to change. | | Feature complete | No | | Documentation | May have some documentation. | | Updates | No guaranteed updates to newer releases, but likely to continue development. | | Support | Support on best efforts. | | Maintenance service | No | | Release cycle | Outside the standard [release policy](/reference/announcements-release-notes/release-policy.md). | --- ## Features and integrations Get started with selected key features and integrations. ## Features Learn more about selected key features. ## Camunda integrations Learn more about Camunda integrations. Camunda integrations ## Early access Explore new features and components currently in development by Camunda Early access --- ## Camunda Hub Manage organizational resources, analyze operations and business value, and deliver agentic processes at scale with Camunda Hub. ## About Camunda Hub Camunda Hub is the centralized platform tailored for: - **Center of Excellence and Platform administration teams**, who manage infrastructure, member access, and workspaces, so delivery teams have the deployment environments and tools they need to ship process solutions at scale. - **Delivery teams and automation domains**, who collaborate in managed workspaces with dedicated deployment environments, discover and use approved catalog assets, and model and deploy business processes. With Hub, teams within your organization can build, deploy, and operate your processes faster with a clear organizational structure and dedicated deployment environments for each workspace. ### Organization structure Camunda Hub's organization view shows the workspaces you belong to, organized in a clear hierarchy: **Organization → Workspace → Project → Files and folders** You can manage organizational resources, including clusters, deployment environments, and workspaces, and govern the use of reusable assets. [Manage organizational resources](/components/hub/organization/index.md) ### Workspaces and deployment environments A deployment environment is a deployment target where a team runs its processes, for example a development, staging, or production environment. Each deployment environment is hosted on a cluster, which remains the infrastructure that your administrators manage. Organization admins assign deployment environments to workspaces. Projects in a workspace can deploy to all the deployment environments assigned to it, so you can deploy and promote your work with clear access controls. [Build within a workspace](/components/hub/workspace/index.md) ## Manage organizational resources Manage organizational resources, including clusters, deployment environments, and workspaces, and govern the use of reusable assets: ## Build within a workspace Discover and use approved reusable assets, manage projects, and deliver business processes: --- ## Assess business value Use the business value dashboard to track process outcomes using cycle time, automation rate, activity, and agentic adoption metrics. Set targets for cycle time and automation rate, and find the processes that miss them. To open the dashboard: 1. In Camunda Hub, go to **Business Value** (`/business-value`). 2. Use the **Environment** picker to select an [environment](/components/concepts/environments.md). 3. Review the portfolio view, then select a process to open its process view. The dashboard shows data for one environment at a time and does not aggregate values across environments. Each value therefore belongs to the environment currently selected in the picker. ## Availability and permissions The business value dashboard is available in Camunda 8.10 and later, for clusters connected to Optimize. The dashboard uses the same organization-level access rules as Optimize in SaaS and Self-Managed. Organization Owner, Organization Admin, and Analyst roles can access the dashboard. Member and DevOps roles can't. See [roles and permissions](/components/hub/organization/users-and-roles.md#roles-and-permissions) for the full permission matrix. Viewing the dashboard and setting targets require the same access. There is no separate read-only or target-editing permission. :::note The standalone `Optimize` role does not grant access to the business value dashboard. It can grant access to Optimize itself, but that alone is not enough to open the dashboard in Camunda Hub. ::: ## How the dashboard calculates metrics Every metric on the dashboard follows the same rules. | Rule | What it means | | :------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------- | | Completed instances only | Only completed process instances are counted. Running and unfinished instances are excluded from every metric, chart, and target comparison. | | Targets are values you set | Camunda doesn't supply a default or benchmark target. Every target on the dashboard is one you entered. | ### Automation rate definition Automation rate is the share of counted tasks that are automated. It is calculated as `automated tasks / (automated tasks + user tasks + manual tasks)`. | Included | Excluded | | :---------------------------------------------------------------------------------------- | :-------------------------------------------- | | Automated tasks, such as service tasks, script tasks, business rule tasks, and send tasks | Events and gateways | | User tasks and manual tasks, which count toward the denominator | Sub-process containers themselves | | Tasks inside embedded sub-processes, counted the same as tasks at the top level | Tasks in a process started by a call activity | Structural elements are excluded because the automation rate counts tasks rather than events, gateways, or sub-process containers. A process built mostly from gateways and events is not automatically 100% automated under this definition. Automation rate is calculated on the root process only. Tasks in a process started by a call activity count toward the automation rate of that called process, not the calling one. ### Cycle time definition Cycle time is the elapsed duration of a completed process instance, from start to end. ## Review the portfolio view The portfolio view summarizes all processes in the selected environment. Use it to see where targets are met and which processes need attention. ![Business value dashboard portfolio view with target overview, activity, automation rate, cycle time, agentic adoption, and off-target processes](img/business-value-dashboard-portfolio-view.png) | Metric | What it shows | How to interpret it | | :----------------------------- | :----------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------- | | Target coverage and attainment | How many active processes have at least one target set, and how many configured targets are currently met. | Low coverage means the summary reflects only a subset of your processes. | | Activity | Completed work handled and how it changes over time. | Use activity as context for the other metrics. Activity isn't compared against a target. | | Automation rate | The aggregated automation rate for the environment, plus a per-process comparison. | Use it to find processes that still rely heavily on manual work. | | Cycle time | A process-by-process comparison and the longest-running processes. | Use it to find where processes take longest to complete. | | Agentic adoption | Which processes use [agentic](/components/agentic-orchestration/agentic-orchestration-overview.md) steps. | Use it to see where AI agents already handle part of the work. | | Off-target processes | Processes with missed targets, ranked first by the number of missed targets and then by the size of the deviation. | Start with the top entry. See [investigate a missed target](#investigate-a-missed-target). | ## Review the process view The process view shows the metrics and targets for a single process. Open it by selecting a process from the portfolio view. ![Business value dashboard process view with cycle time, automation rate, and volume compared against targets, plus momentum, cycle time distribution, and cycle time history charts](img/business-value-dashboard-process-view.png) | Element | What it shows | | :-------------------------- | :------------------------------------------------------------------------------------------------------------------ | | Per-metric value and status | The current value for each metric, whether its target is met, and where available, by how much it is met or missed. | | Activity | Completed work handled for this process in the selected scope. | | Trends | How activity and cycle time change over time, rather than a single snapshot. | | Cycle time distribution | P50, average, and P95 where available, so you can compare typical durations with higher-percentile durations. | | Version context | Version selection or release-over-release comparison where available, so you can see how a release performed. | | Target actions | **Set target** for the metrics this process supports. | ## Set targets Each target is optional and set independently, so you can set a cycle time target without setting an automation rate target. | Metric | Direction | | :-------------- | :--------------- | | Cycle time | Lower is better | | Automation rate | Higher is better | To set a target: 1. Open the process view for the process you want to set a target for. 2. Click **Set target** to open the **Set targets** dialog. 3. Review the current baseline shown beside the target input, where supported. 4. Enter the target value and click **Save targets**. Use the baseline to set a meaningful target relative to current performance. A target that is too far from the baseline may provide little useful information about progress. ## Filter the results Filters change which completed instances are counted, so a target that is met over one date range can be missed over another. | Filter | What it does | | :---------- | :--------------------------------------------------------------------------------- | | Environment | Selects the environment to report on. Required. | | Process | Limits results to one process. | | Date range | Limits results to instances completed within the range. | | Version | Limits results to one process version. Available once you have selected a process. | When you share a number from the dashboard, include the filters it was measured with. An automation rate of 94% over the last 30 days in one environment is not the same as 94% overall. ## Investigate a missed target The dashboard shows which target was missed and by how much, but not which process instances contributed to the missed target or why. 1. In the portfolio view, open **Off-target processes** and start with the top entry. 2. Follow the link to the process view to confirm the missed target, review the trend, and check whether the issue is concentrated in a specific version. 3. If an operational investigation link is available, follow it to inspect the affected instances in [Operate](/components/operate/operate-introduction.md). 4. For process analysis such as bottleneck and outlier detection, continue in [Optimize](/components/optimize/what-is-optimize.md). ## Multi-tenancy In Self-Managed, the business value dashboard follows the [Optimize multi-tenancy](/components/concepts/multi-tenancy.md#optimize-and-multi-tenancy) model. In SaaS, the dashboard shows data from the default tenant only. In a multi-tenant environment: - Targets are created, read, updated, and evaluated per tenant. A target set for one tenant isn't visible to, or inherited by, another tenant using the same process. - In the process view, select a tenant to scope both the process metrics and the target you're editing. - The portfolio view reports across all tenants you're authorized to see. --- ## Analyze operations Monitor cluster health, track job and process execution, and measure business value across your Camunda organization. --- ## Job dashboard Use the job dashboard to see which job types are active, how many jobs are created, completed, and failed, and which job workers are involved. Jobs are available for both environments and clusters, so you can open the job dashboard from an [environment](/components/concepts/environments.md) or from the [cluster](/components/concepts/clusters.md) that hosts it. For a cluster, the job dashboard is an aggregated view across all of its environments, which is relevant in Self-Managed, where a cluster can host more than one environment. In SaaS, each cluster hosts one environment. ## Availability and permissions The job dashboard is available for clusters running Camunda 8.9+. For Software as a Service (SaaS): - Available for clusters running Camunda 8.9+. - Camunda manages the underlying job metrics configuration for you. - If the **Jobs** card is missing or shows **Access restricted**, check that your user has permission to view job metrics in Camunda Hub. If the issue persists, contact your organization owner or Camunda Support. For Self-Managed: - Requires Camunda 8.9+ (Zeebe and Camunda Hub). - Configure job metrics in the engine configuration (`camunda.monitoring.metrics.job-metrics.*`). These options and their default values are available in the auto-generated `defaults.yaml` file and the Helm values. - If the **Jobs** card is missing or shows **Access restricted**, verify that: - The cluster is running Camunda 8.9+. - Job metrics are enabled in the engine configuration. - Camunda Hub can connect to the cluster. - Your user has permission to view job metrics. :::info Camunda Hub is introduced in 8.10. If you're using 8.9, refer to [that version's documentation, which uses Camunda 8 Console](/versioned_docs/version-8.9/components/console/job-dashboard/job-dashboard.md). ::: Access control: - The global `READ_JOB_METRICS` permission is the only Camunda Hub permission required to use the job dashboard. - Operate permissions are still required to view underlying instances when you click **View errors**. ## When to use the job dashboard With the job dashboard, you can: - Check whether jobs flow through the system (created, completed, failed) for each job type. - See which job workers process a given job type and how many jobs they handle. - Investigate error patterns for a job type before drilling into individual process instances in Operate. - Avoid building and maintaining custom job-monitoring dashboards. ## Open the job dashboard ### 1. Open the Jobs overview Open the job dashboard from an environment or from a cluster. **From an environment** In Self-Managed: 1. In Camunda Hub, click **Environments** in the left navigation. 2. Select an environment. 3. On the **Overview** tab, locate the **Jobs (last 24 hours)** card. It shows the number of created, completed, and not completed jobs. 4. Click **View all job types** to open the **Jobs** page of the environment. In SaaS, the **Jobs (last 24 hours)** card on the **Overview** tab of an environment summarizes the jobs of the environment. To see all job types, open the job dashboard from the cluster that hosts the environment. **From a cluster** In SaaS and Self-Managed: 1. In Camunda Hub, click **Environments** in the left navigation, and then click **Clusters**. 2. Select a cluster. 3. On the **Overview** tab, locate the **Jobs** card, and click **View all job types** to open the job types page of the cluster. In Self-Managed, the job types page of a cluster aggregates the jobs of all environments of the cluster. Each SaaS cluster hosts one environment, so the jobs of the cluster are the jobs of that environment. ### 2. Job types overview The **Job types** page, called **Jobs** in Self-Managed, shows all job types running in the selected environment or cluster. ![Jobs overview with Job types table in SaaS](img/jobs-overview.png) Key elements: - **Last updated** timestamp (based on statistics responses). - **Search** box to filter job types. - **Time range** selector (for example, **Last 24 hours**) that controls all metrics on the page. - Table columns: - **Job type** - **Assigned workers** - **Created jobs** - **Completed jobs** - **Not completed jobs** - **Last completed** If the selected date range hits internal limits, Camunda Hub shows a warning that not all data is displayed. Narrow the time range to see a more complete view. Job metrics are stored internally in the engine and exported in batches every five minutes. As a result, metrics in the UI can be delayed by up to five minutes. Configuration limits protect the system and include: - Maximum string lengths for job type, job worker, and tenant ID - Maximum number of unique keys (jobType × tenantId × job worker combinations) If a limit is exceeded, the batch is marked as incomplete, and the UI shows the **Data loading limit reached** warning. In this case: - Narrow the time range to reduce the amount of data - Filter by job type to focus on a smaller subset - In Self-Managed environments, adjust configuration limits if needed Treat the counts as partial for the affected time range. To drill down into a specific job type, click its **Job type** link (for example, `send-email`). ## Job type details The **Job type details** page shows metrics and errors for a single job type. ![Job type details view in SaaS](img/job-activity-log.png) ### Job workload The **Job workload** chart shows how many jobs were **created**, **completed**, and **failed** over time for the selected job type and time range. Failed counts include jobs that ended in an error state (error thrown, failed, or timed out). Canceled, migrated, or retry-update-only states are not counted. ### Job completion rate The **Job completion rate** donut chart shows three groups for the selected time range: - **Created**: All jobs created, regardless of whether they are still running, completed, or failed. - **Completed**: Jobs that have finished executing successfully. - **Failed**: Jobs that ended in an error state (error thrown, failed, or timed out). Multiple failed attempts for the same job are counted separately. Canceled or migrated jobs are not included. Use this chart to see at a glance whether most jobs finish successfully or many end in a failed state. ### Job workers table The **Job workers** table shows which job workers processed this job type and how many jobs they handled: - **Worker name** - **Created jobs** - **Completed jobs** - **Last completed** Use this table to see which job workers are active and whether failures are concentrated on specific job workers. ### Failed jobs by error type The **Failed jobs by error type** table groups failed jobs by error so you can quickly see the most common problems: - Search by error type or message. - Columns: - **Error type** - **Error message** - **Jobs with error** Click **View errors** to open related instances in **Operate**, with the **Error Message** filter prefilled so you see only instances that failed with that error. ## Empty states and access restrictions ### No jobs in the queue If there are no jobs for the environment, cluster, or selected time range, the Jobs page shows: - Heading: **No jobs in the queue** - Message: **No jobs found.** - Link: **Learn more about Jobs and Job Workers** This means there is no job activity to display. ### Jobs access restricted If the feature is disabled for the cluster or you don't have permission, the **Jobs** card on the environment or cluster overview shows: - Status: **Access restricted** - Message explaining that the feature is restricted or disabled and you must contact an administrator. - Link: **Learn more about roles and restrictions** ### Missing permissions when viewing errors If you click **View errors** but lack permissions in Operate, you may see messages like: - **Missing permissions to view the Definition** - **Missing permissions to access Instance History** - **Missing permissions to access Variables** In this case, contact your organization owner or admin to request the necessary Operate permissions. ## SaaS vs Self-Managed The Camunda Hub UI and flows are the same in SaaS and Self-Managed. - **SaaS:** The job dashboard is available for Camunda 8.9+ clusters. Camunda manages the underlying job metrics configuration. - **Self-Managed:** You enable and configure job metrics in the engine and Helm charts. For details on available options and defaults, see the job metrics [configuration reference](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md). --- ## View Console Use Console to view your organization at a high level. Console shows the state of the clusters that host your environments and the usage of your organization. It's available to organization owners, admins, and DevOps users. To open Console, click **Console** in the left navigation. In SaaS, Console shows a dashboard with the following information: - Number of users, task users, clusters, and Admin API credentials - Cluster health, with references to the [clusters](/components/hub/organization/manage-clusters/index.md) - [Process instance usage](/components/saas/organization/usage-history.md) - [Decision instance usage](/components/saas/organization/usage-history.md) - [Usage alerts](/components/saas/organization/usage-alerts.md) - [Recent activity](/components/saas/organization/view-organization-activity.md) In Self-Managed, Console shows dashboard and usage information for your clusters. The dashboard information includes: - Cluster health, including the number of healthy, unhealthy, and unknown clusters, and the [clusters](/components/hub/organization/manage-clusters/index.md) that are unhealthy - Management components, each with its version, cluster, status, and a link to open it - Usage of all clusters in the last 30 days, including task users, process instances, and decision instances - Links to documentation and feedback The usage information shows the usage of each cluster. To view all clusters, see [clusters in Self-Managed](/components/hub/organization/manage-clusters/index.md). The cluster information in Console is about the infrastructure. Cluster links open the cluster pages in **Environments**. To work with the environments that run on the clusters, see [manage environments](./manage-environments/index.md). --- ## Manage credentials A credential is reusable configuration for a job worker, connector, or other element template, so you don't repeat the same authentication and connection settings on every task. ## About credentials - A credential lets you define and manage reusable configuration for job workers, connectors, and other element templates, instead of entering the same settings every time one asks for them. - Credentials are generally usable infrastructure, available in any Camunda 8 distribution, including standalone ones that run without Hub. Camunda Hub adds an organization-wide, federated view and central management across your clusters, which is what the rest of this page covers. - A credential type is defined alongside an element template, through an embedded [configuration template](/components/modeler/element-templates/template-metadata.md#embedding-configurations-configurationtemplates). If you build custom connectors or job workers and want to define your own credential type, see [Create a credential template](/components/connectors/custom-built-connectors/credential-templates.md). - A credential is stored as a cluster variable in each environment you deploy it to, which makes it available by name to any job worker or connector that references it there. A credential is selected as a whole: an element template never renders or edits a credential's fields. It writes a reference to the chosen credential into the diagram, and the engine resolves that reference at runtime, passing the credential's values, including any secrets, to the job worker or connector. Learn more: - [Using credentials](/components/connectors/use-connectors/index.md#using-credentials): how a connector task uses a credential. - [Configuration input type](/components/modeler/element-templates/template-properties.md#configuration-input-type): how an element template declares the `Configuration` property that renders the credential chooser. - [Configure credentials in the modeling interface](./modeling-interface.md): how to choose, create, edit, or upgrade a credential from the properties panel. ## Credentials and environments A credential is deployed to environments, not to clusters. An environment is the unit Camunda Hub tracks a credential's targets, values, and health against. - On Camunda 8 SaaS, and on any cluster before 8.10, a cluster holds a single environment, named after the cluster. - On Self-Managed 8.10 and later, a cluster holds one environment for each [Physical Tenant](/self-managed/concepts/multi-tenancy/physical-tenants.md). The environment for the `default` Physical Tenant uses the cluster name, and every other environment uses its Physical Tenant ID. - Camunda Hub shows an environment by its name, and adds the cluster name in parentheses whenever the two names differ. - On a Self-Managed cluster with several Physical Tenants, each environment is an independent target, with its own copy of the credential, its own values, and its own state. A credential deployed to one environment isn't readable from the other environments on that cluster. ## Terminology | Term | Meaning | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Credential | The reusable object you create and then select on an element template field, such as a connector task or a job worker's configuration. | | Credential type | The shape of a credential, such as **AWS Credential**, **REST Authentication**, or **JDBC Connection**. A credential type defines which fields a credential of that type has. | | Environment | The target a credential is deployed to. On Camunda 8 SaaS an environment is the cluster; on Self-Managed 8.10 and later, a cluster can hold one environment for each Physical Tenant. | | Configuration | The element template property type that renders the credential chooser. A credential is a configuration whose kind is `CREDENTIAL`. See [Configuration input type](/components/modeler/element-templates/template-properties.md#configuration-input-type). | ## Credentials and connector secrets Credentials and [connector secrets](/components/saas/clusters/manage-secrets.md) work together rather than replacing each other: | | Connector secret | Credential | | ------- | -------------------------------------------- | --------------------------------------------------------------- | | Stores | A single sensitive value, such as an API key | A complete, typed set of authentication and connection settings | | Scope | One cluster | One or more environments, managed from your organization | | Used by | Any connector field that supports secrets | A connector's credential field | A credential's sensitive fields reference secrets, so the secret is still where the sensitive value lives. Create the secret first, then reference it from the credential. ### Reference a secret from a credential field To reference a secret from a credential field, enter `camunda.secrets.` followed by the secret key as the whole value of the field. For example, reference a secret with the key `AWS_SECRET_KEY` as follows: ``` camunda.secrets.AWS_SECRET_KEY ``` A secret key can contain letters, digits, underscores, and hyphens. The key ends at the first character outside that set, so a key cannot contain a period. A reference is recognized at the start of a value, or directly after a character that is not a letter, digit, underscore, or period. Anywhere else, `camunda.secrets.` is part of a longer word: | Field value | How it is read | | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `camunda.secrets.AWS_SECRET_KEY` | A reference to the secret `AWS_SECRET_KEY`. | | `(camunda.secrets.AWS_SECRET_KEY)` | A reference to the secret `AWS_SECRET_KEY`. The reference is recognized after the opening parenthesis, and the key ends at the closing parenthesis, because neither character can be part of a key. | | `foo.camunda.secrets.AWS_SECRET_KEY` | Not a reference, because `camunda.secrets.` is in the middle of a word. The value is stored and sent as plain text. | | `camunda.secrets.camunda.secrets.AWS_SECRET_KEY` | A reference to a secret with the key `camunda`, because the key ends at the next period. | | `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY` | Plain text. Camunda Hub warns you, and stores the value as you entered it. | :::note Credential fields use `camunda.secrets.MY_API_KEY`, without braces. This is not the same as the `{{secrets.MY_API_KEY}}` syntax you use in a [connector field](/components/connectors/use-connectors/index.md#using-secrets). Use `camunda.secrets.` inside a credential, and `{{secrets.}}` in connector fields that support secrets. ::: ### Store sensitive values as secrets, not plain text Store every sensitive value, such as a password or API key, as a [secret](/components/saas/clusters/manage-secrets.md) on the cluster, and reference it from the credential field. A credential field accepts any text you type, so a value entered directly is stored as you typed it, outside the secrets vault. To guide you to a secret, Camunda Hub highlights a sensitive field and warns you when its value is not a secret reference. Saving is still allowed, so the value stays exposed until you replace it with a reference. The warning clears as soon as the field references a secret. If the clusters that host the environments you selected hold no secrets yet, the field says so instead of showing an empty suggestion list. Secret suggestions are cluster-scoped, so every environment on a cluster offers that cluster's secret names. In Camunda 8 SaaS, both messages carry an **Open Clusters** link that opens in a new tab, so your part-finished credential survives the detour. Self-Managed shows no link, because secrets come from the connector runtime configuration. The same warning appears in the [modeling interface](./modeling-interface.md#create-a-credential). ## Credential types Camunda provides a credential type for each family of connectors that supports credentials. The following types are available: | Credential type | Bound to the connector's input | Example connectors | | ----------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | AWS Credential | `awsCredential` | Amazon SQS, Amazon S3, Amazon Bedrock, and other Amazon Web Services connectors | | REST Authentication | `authenticationConfiguration` | HTTP Polling, HTTP REST, GraphQL, Azure OpenAI, and Microsoft Office 365 Mail connectors | | JDBC Connection | `configuration` | Execute SQL Statement on Database | | Email Account (Inbound) | `emailAccountConfiguration` | Email Boundary Event, Email Intermediate Catch Event, Email Receive Task, and Email Message Start Event connectors | | Email Account (Outbound) | `emailAccountConfiguration` | Email Connector | | Kafka Connection | `kafkaConnectionConfiguration` | Publish Message to Kafka, and the Kafka Boundary Event, Kafka Intermediate Catch Event, Kafka Receive Task, and Kafka Message Start Event connectors | | Slack Signing Secret | `inbound.slackCredential` | Slack Webhook Boundary Event, Slack Webhook Intermediate Catch Event, Slack Webhook Receive Task, and Slack Webhook Message Start Event connectors | | Slack Token | `slackCredential` | Slack Outbound Connector | | Azure Blob Storage Credential | `authenticationConfiguration` | Azure Blob Storage Outbound Connector | | Microsoft Entra ID | `authenticationConfiguration` | Microsoft Teams Outbound Connector, and the Microsoft O365 Email Boundary Event, Microsoft O365 Email Intermediate Catch Event, and Microsoft O365 Email Message Start Event connectors | More connectors gain credential support over time, so this list grows. Each credential type card in the **Create a credential** wizard lists the connectors that currently use that type under **Used by**. ## Manage credentials in Hub Camunda Hub provides a **Credentials** page for your organization. It has two tabs: - **Managed in Hub**: credentials that Hub creates and manages. Use this tab to create, edit, and delete credentials. - **Environments only**: credentials that exist on a cluster but are not managed in Hub, typically because they were created from Desktop Modeler or directly through the cluster API. ### Managed credentials Before you create your first credential, the **Managed in Hub** tab shows an empty state with a **Create credential** button. ![Managed in Hub tab of the Credentials page in Camunda Hub, showing the message "Your organization has no credentials yet" and a Create credential button](./img/credentials-managed-empty.png) Once credentials exist, this tab lists them with their name, credential type, state, the environments they cover, and when they were last modified. ### Create a credential To create a credential, select **Create credential**, and complete the three steps of the wizard. 1. **Choose credential**: select the credential type for the connector you want to authenticate. Each card describes the credential type and lists the connectors that use it. Use the search field to search by credential type or by connector name. ![Step 1 of the Create a credential wizard, showing a card for each credential type, such as AWS Credential, REST Authentication, and JDBC Connection, each listing the connectors that use it](./img/credentials-choose-credential.png) 2. **Configure**: name the credential, choose which environments it applies to, and fill in the fields for the credential type you selected. Camunda suggests an ID for the credential based on the name you enter. You can change the ID while you are creating the credential, but not afterwards. For a sensitive field, enter a reference to an existing secret, such as `camunda.secrets.AWS_SECRET_KEY`, rather than the value itself. Select the field to pick from the secrets that exist on the clusters that host the environments you selected. Camunda Hub highlights a sensitive field and warns you when its value is not a secret reference. **Continue** stays enabled, so replace the value with a reference before you save. See [store sensitive values as secrets, not plain text](#store-sensitive-values-as-secrets-not-plain-text). If you select more than one environment, keep **Use same credentials for all environments** enabled to apply one set of values everywhere, or disable it to configure each environment separately. 3. **Review**: check the summary, then select **Create** to save the credential and deploy it to the environments you selected. You can also save the credential as a draft at any step. A draft is saved in Hub but is not deployed to any environment. :::note You cannot create a secret while creating a credential. Add the secret to the cluster first in [Connector secrets](/components/saas/clusters/manage-secrets.md), then reference it here. A credential's ID also cannot be changed after you create it, so to rename a credential, delete it and create a new one. ::: ### Credential states The **Managed in Hub** tab shows a state for each credential. Hub checks the state in the background after the list loads, so a state can take a moment to appear. Refresh the list to check again. | State | Meaning | | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | | Active | The credential is deployed to every environment you selected, and every secret it references exists on the clusters that host them. | | Warning | The credential is deployed to at least one environment, but it is missing from another environment, or a secret it references does not exist. | | Not deployed | The credential is not present in any environment. Drafts always have this state. | A credential in the **Warning** state is still deployed. Processes that use it can fail at runtime if the missing secret or environment is the one they rely on. The secret check behind these states is cluster-wide, so a credential can read **Active** while a secret it references doesn't resolve in one of its environments. On a cluster with Physical Tenants, each Physical Tenant needs its own secret store. See [secret resolution](/components/concepts/secret-resolution.md). ### Edit a credential Select a credential from the **Managed in Hub** tab to open it, then edit its values. The credential's ID is shown but cannot be changed. Saving your changes redeploys the credential to its environments. If an environment is deleted, the credential's detail page marks that target **Missing**. Saving from the edit wizard drops the missing target, so select another environment to deploy there instead. ### Delete a credential Deleting a credential removes it from Hub and from every environment it is deployed to. Hub asks you to confirm, and lists the environments that are affected. :::warning Editing or deleting a credential takes effect immediately for every process that references it. Running process instances can fail if the credential no longer works or no longer exists. ::: ### Environments only credentials A credential created outside Hub, such as one created in Desktop Modeler or directly through the cluster API, exists on its cluster but is not tracked in Hub. The **Environments only** tab finds these credentials so you can bring them under Hub management. The scan works per cluster, even though the tab lists environments. Each cluster appears once in the list, under the name of one of its environments. 1. Under **Environments**, select the environments you want to scan. You can scan up to 10 environments at a time. 2. Select **Scan environments**. Hub scans the cluster behind each selected environment for global variables that are tagged as credentials and that match a known credential type. 3. Select **Add to Hub** on a result to manage that credential in Hub. Hub reads the credential's configuration only when you open the **Add to Hub** dialog. Adding a credential to Hub links it where it was found, without redeploying it, and records one environment on that cluster as its target, chosen for you rather than by you. Only the additional entries you select in the **Add to Hub** dialog are deployed to, again one environment for each cluster. Check the targets on the credential's detail page afterwards. Select **Rescan environments** to run the scan again, for example after a credential is created outside Hub. ![Environments only tab of the Credentials page, with one environment selected, a paused environment that can't be selected, and a Scan environments button](./img/credentials-clusters-only.png) If the scan returns no results, none of the clusters behind the environments you selected has a credential-tagged variable that matches a known credential type and version. Environments that are paused, or whose cluster runs a Camunda version without credential support, show a **Paused** or **Unsupported** badge and can't be scanned. When there is nothing to find, Hub shows: "No environment-only credentials found. The scan did not find any unmanaged variables that match an exact known credential type and version." ## Permissions Every member of your organization who has access to Camunda Hub can see the **Credentials** page and every credential it lists, and can open a credential to see its configuration. The same members can create, edit, deploy, and delete credentials. Camunda Hub has no separate credential permission, and it doesn't check your organization role, workspace role, or environment access before it saves a credential. Hub reads from and writes to a cluster with your own identity. It does so when it scans a cluster for **Environments only** credentials, when you open the **Add to Hub** dialog, and when it deploys, redeploys, or deletes a credential. The cluster's own authorizations therefore still apply: - You can't find or open a credential on a cluster whose authorizations don't let you read its variables. - If a cluster refuses a deployment, Hub reports that environment as failed with the message `Not authorized to perform this operation on this cluster.` - If a cluster refuses to remove a credential because you lack permission or its credentials are wrong, Hub keeps the credential, so you can resolve the problem and delete it again. - On a Self-Managed cluster that uses Basic authentication, Hub asks you for the cluster's username and password. When you choose environments for a credential or for a scan, Hub lists only the environments you can see. On SaaS, every user in your organization sees every environment. On Self-Managed, users with the **Organization Admin** or **DevOps** role, or any role with the `admin:*` or `admin:clusters` permission, see every environment. Other users see only the environments assigned to workspaces where they are a **Workspace Admin** or **Editor**. The credential list and detail page still show every credential and all of its targets, and a target in an environment you can't see is shown by its ID. ## Known limitations In this release: - You cannot create a secret from a credential. Create secrets in [Connector secrets](/components/saas/clusters/manage-secrets.md) first. - The plain-text warning checks the whole field value, so a value that combines literal text with a reference, such as `Bearer camunda.secrets.TOKEN`, is flagged even though the reference resolves. - Hub does not show which processes use a given credential, so check the impact yourself before you edit or delete one. - Credentials are visible to everyone in your organization who has access to Camunda Hub. You cannot restrict a credential to a project or a subset of users. - A credential's ID cannot be changed after creation. - Credentials are edited in place, with no history of previous values. - Secret suggestions are cluster-scoped, so a credential field offers every secret name on the cluster that hosts the environment you selected, including names that other environments on that cluster use. - Filtering the **Managed in Hub** tab by environment matches every environment on that environment's cluster. - Camunda Hub checks credential permissions per organization, not per environment, so its own check doesn't follow the isolation between environments on a cluster. The cluster's own authorizations still apply when Hub writes to it. - The **Environments only** scan reads a cluster's shared variables, which on Self-Managed belong to the `default` Physical Tenant. A credential created directly in another Physical Tenant isn't found. ## Next steps - [Configure credentials in the modeling interface](./modeling-interface.md) - [Connector secrets](/components/saas/clusters/manage-secrets.md) --- ## Configure credentials in the modeling interface Select an existing [credential](./index.md) on a connector task in the Camunda Hub modeling interface, or create a new one without leaving the properties panel. :::note This page covers credentials that authenticate connector tasks, such as an AWS Credential. It is unrelated to the [client credentials](/components/hub/workspace/modeler/run-or-publish-your-process.md#missing-client-credentials) required to deploy or run a process, which authenticate Camunda Hub against your cluster. ::: ## Select a credential Connectors that support credentials show a credential field in the properties panel, such as **AWS Credential**. Select the field to open the credential chooser, which lists the credentials deployed to the environment you're [connected to](../../workspace/modeler/modeling/connect-to-a-runtime.md) that match the credential type the connector needs. On a Self-Managed cluster with several environments, the chooser also lists credentials deployed only to another environment on that cluster; those don't resolve at runtime in the environment you are connected to. Selecting a credential stores only a reference to it in your diagram. The credential's values stay in the environment they were deployed to. If no credential matches, the chooser tells you so by name, for example `Cannot find AWS Credential with name AWS_PROD`. This usually means the credential doesn't exist in the connected environment, or it was created for a different credential type. ## What you can do in the chooser What the chooser offers depends on whether you can edit the diagram, that is, whether you have edit access to the project. | Situation | Available actions | | -------------------------------------------------------------------- | ------------------------------------------------- | | You can edit the diagram | Select a credential, or create a new one. | | You can edit the diagram, and the selected credential is compatible | Select a credential, or edit the selected one. | | You can edit the diagram, and the selected credential is out of date | Select a credential, or upgrade the selected one. | | You cannot edit the diagram | Select a credential only. | This only controls what the chooser offers. On the [**Credentials** page](./index.md#permissions), any member of your organization who has access to Camunda Hub can manage credentials, and the cluster's own authorizations apply whenever Hub writes a credential to it. The chooser is unavailable while you aren't connected to an environment, while you work offline, while the connected environment is paused, or when it runs a Camunda version before 8.10. The field tells you which of these applies. A connector declares the minimum credential version it needs. A newer credential always satisfies an older requirement, so upgrading is only needed when a credential is older than the connector requires. ## Create a credential To create a credential from the properties panel: 1. Open the credential field, then select the option to create a new credential. 2. Enter a **Credential name**. Camunda Hub suggests a **Credential ID** based on the name. 3. Change the **Credential ID** if you want a different one. You cannot change it after the credential is created. 4. Fill in the fields for this credential type. For a sensitive field, enter a reference to a secret that already exists on the cluster, using `camunda.secrets.` followed by the secret key, such as `camunda.secrets.AWS_SECRET_KEY`. Select the field to pick from the secrets on the cluster behind the connected environment. 5. Save the credential. Camunda Hub creates it in the connected environment and selects it on the connector task. Camunda Hub highlights a sensitive field and warns you when its value is not a secret reference. Saving is still allowed, so replace the value with a reference to keep the sensitive value in the secrets vault. For the reference syntax, see [reference a secret from a credential field](./index.md#reference-a-secret-from-a-credential-field). Camunda Hub checks whether the secret you referenced exists on the cluster, without revealing its value. If the secret is missing, you see a warning, but you can still save the credential. The connector fails at runtime until the secret exists. A value that embeds `camunda.secrets.` mid-word, such as `foo.camunda.secrets.AWS_SECRET_KEY`, holds no reference, so Camunda Hub reports it as plain text rather than as a missing secret. The plain-text warning replaces the missing secret warning on that field. :::note You cannot create the secret itself here. Add the secret to the cluster first in [Connector secrets](/components/saas/clusters/manage-secrets.md), then reference it from the credential. ::: A credential you create here is managed in Camunda Hub immediately. It appears on the **Managed in Hub** tab of the [**Credentials** page](./index.md#managed-credentials). ## Edit or upgrade a credential Editing a credential opens the same form, pre-filled with its current values. Saving replaces the credential's values in the environment, which takes effect immediately for every process that references it. Upgrading a credential opens the same form and shows the fields that the newer credential version adds. Fill them in and save to make the credential usable with the connector version you are modeling against. ## Additional resources - [Credentials](./index.md) - [Run or publish your process](/components/hub/workspace/modeler/run-or-publish-your-process.md) --- ## Organization Manage organizational resources, including clusters and workspaces, and govern the use of reusable assets. ## About organization-level management With Camunda Hub, you'll manage your infrastructure, member access, and workspaces, so your delivery teams have the environments and tools they need to ship process solutions at scale. This governance happens at the organization level. ## Manage workspaces Create and manage workspaces within your organization: [Get started](./manage-workspaces/index.md) ## Manage environments See your deployment environments and assign them to workspaces: [Get started](./manage-environments/index.md) ## Manage clusters Create, monitor, and maintain the clusters that host your environments: [Get started](./manage-clusters/index.md) ## Manage the catalog Establish a Git repository with catalog assets, upload the assets in a CI/CD pipeline, and approve them for use within your organization: [Get started](./manage-catalog/index.md) ## Manage credentials Create a reusable credential, then select it wherever an element template needs authentication or connection configuration, instead of repeating the settings in every diagram: [Get started](./credentials/index.md) ## Manage organization Manage your organization, its users, and its settings: [Get started](./manage-organization-settings/organization-settings.md) ## Analyze operations Monitor cluster health, track job and process execution, and measure business value across your Camunda organization: [Get started](./analyze-operations/index.md) --- ## Get started with the catalog Set up your repository and CI/CD pipelines to publish assets to the Camunda Hub catalog. ## About the catalog workflow With the following workflow, you can manage and use assets in the Camunda Hub catalog: 1. Store element templates and their metadata in a Git repository. 2. Create a CI/CD pipeline to submit the complete set of element templates to the Camunda Hub catalog API every time the assets change. 3. Camunda Hub compares the submission against its stored state and publishes applicable changes. 4. When modeling in Camunda Hub, delivery teams discover and apply the latest templates from the catalog. This guide covers steps one and two of this workflow. ## Prerequisites You need these prerequisites before following the steps in this guide: - A Camunda 8 [SaaS](https://signup.camunda.com/) or [Self-Managed](/self-managed/about-self-managed.md) account with an active organization. - A Git repository where you'll store your element templates. - CI/CD infrastructure for that repository. This guide assumes you store your catalog assets in a single repository. If you store your assets across several repositories, [consolidate them into one repository](/components/hub/organization/manage-catalog/sync-multiple-repositories.md) before publishing them. ## Use the example repository To get started, create your own repository from the [example catalog repository](https://github.com/camunda/catalog-template): 1. On the [example repository page](https://github.com/camunda/catalog-template), click **Use this template > Create a new repository**, and create the repository in your own account or organization. Don't fork the example repository. Use a template so your repository is an independent copy. 1. (Optional) Clone your new repository to your local machine. This isn't required for the sync, which runs in CI. You only need a local clone to work on assets locally or run the sync script by hand. The example repository contains: - Placeholder element templates. - A ready-to-use [sync script](https://github.com/camunda/catalog-template/blob/main/scripts/sync-catalog.sh). - A [GitHub Actions workflow](https://github.com/camunda/catalog-template/blob/main/.github/workflows/sync-catalog.yml) that submits the full state on every push to `main`. The rest of this guide assumes you're using the sync script and GitHub Actions workflow from this example repository. ## Organize your assets Within your Git repository, each asset is grouped in its own directory. For example: ``` your-repo/ ├── payment-connector/ │ ├── README.md │ └── payment-connector.json ├── approval-task/ │ ├── README.md │ └── approval-task.json └── order-template/ ├── README.md └── order-template.json ``` The asset directories don't have to be at the repository root. The [sync script](https://github.com/camunda/catalog-template/blob/main/scripts/sync-catalog.sh) discovers them wherever they are, so you can nest them under a subfolder alongside other content. Each asset directory contains exactly two files: - **README.md**: A Markdown file with metadata in the frontmatter and a description in the body. The frontmatter references the element template file and provides optional attributes like category and tags. - **Element template file**: The [element template descriptor](/components/modeler/element-templates/defining-templates.md). The `id` and `version` fields in this file are authoritative for the asset's identity and version in the catalog. The asset name, short description, and icon are also read from this file. The sync script only looks at these two files; any other files in the repository are ignored. :::note Only the README and element template file are published to the catalog. The asset's implementation—such as job workers, BPMN files, or linked forms—stays in your repository as the source of truth and is deployed to your cluster through your own deployment pipeline. The catalog holds the element template and its description; it does not run or deploy the underlying implementation. ::: ### Define the asset README The README is the asset's metadata file. Camunda Hub detects an asset by the presence of a README with valid frontmatter: | Field | Type | Required | Description | | ---------- | --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------ | | `template` | string | Yes | File name of the element template in the same directory. If the file is missing, the submission fails. | | `category` | string | No | Groups the asset under a single category for filtering in the catalog. If omitted, the asset won't belong to a category. | | `tags` | list of strings | No | Tags used to search and filter assets in the catalog. If omitted, the asset will have no tags. | Other information about the asset, such as its name, short description, and icon are _not_ set in the frontmatter. They come from the `name`, `description`, and `icon` fields of the element template file. The Markdown content below the frontmatter is displayed as the asset description when browsing the catalog in Hub. Here's a full example `README.md`: ```markdown --- template: payment-connector.json category: Payments tags: - payments - connector --- ## Payment Connector This connector integrates with the company payment gateway. It handles payment initiation, status checks, and refund processing. ### Usage Apply this template to a Service task to configure a payment operation. ``` The README supports Markdown text only. Embedded images and videos are not supported at this time. ### Version your element templates The catalog uses the `id` and `version` fields inside your element template definition to track versions: ```json { "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", "id": "com.example.payment-connector", "name": "Payment Connector", "version": 2, "appliesTo": ["bpmn:ServiceTask"], "properties": [] } ``` While `version` is [optional in the element template schema](/components/modeler/element-templates/defining-templates.md#optional-keys), the catalog _requires_ it. A template submitted without a `version` is rejected. When you submit assets to the catalog API, Camunda Hub compares each submitted template against the latest stored version with the same `id`: - If the template **content is identical** to the latest stored version, no new version is created. Changes to the README, including the description, category, and tags, are still applied to the asset. - If the template **content has changed**, and the `version` field is **greater** than the latest stored version, a new version is published as the latest version. - If the template **content has changed**, but the `version` field is **less than or equal to** the latest stored version, the **entire submission is rejected** with a `400 Bad Request`. The version must always increase. Reusing an older or equal number is not allowed, even if no other asset uses it. Additionally, every `id` in a single submission must be unique. If two templates share the same `id`, the entire submission is rejected with a `400 Bad Request`. :::warning Always increment the `version` when you change an element template's content. The catalog API validates the whole submission as a single transaction. If any asset fails validation, _no_ changes are applied. ::: ### Enforce version increments in CI Instead of relying on reviewers to ensure the version is incremented for all modified assets, validate the version automatically before the submission ever reaches the catalog. Add a check to your CI pipeline that compares each changed element template against its previous state and fails the pull request when the content changed but the `version` field did not increase. The example repository includes a [version-check GitHub Actions workflow](https://github.com/camunda/catalog-template/blob/main/.github/workflows/check-versions.yml) that runs on every pull request, so a template change with a stale `version` is caught before it is merged and synced. You can bypass the check for intentional exceptions by adding the `skip-version-check` label to the pull request. ## Connect the repository to Hub Use a CI/CD job, such as the [example Sync Catalog GitHub Actions workflow](https://github.com/camunda/catalog-template/blob/main/.github/workflows/sync-catalog.yml), in your repository to authenticate with the Camunda Hub API and submit the current set of element templates to the catalog whenever the repository changes. The job calls a single ingestion endpoint: ``` PUT /api/v2/catalog/assets/ingestion ``` The request body is `multipart/form-data` and represents the **complete desired state** of the catalog. For each asset, the request includes two parts: - `readme`: The `README.md` file. - `template`: The element template `.json` file :::tip For the full request and response schema, see the **Ingest catalog assets** reference for [SaaS](/apis-tools/hub-api-saas/specifications/ingest-catalog-assets.api.mdx) and [Self-Managed](/apis-tools/hub-api-sm/specifications/ingest-catalog-assets.api.mdx). ::: Because the submission represents the full desired state, Camunda Hub [unpublishes](./manage-asset-lifecycle.md#unpublish-an-asset) any asset that exists in the catalog but is absent from the submission. :::note For large submissions, review the [ingestion limits](#ingestion-limits). ::: ### Provide credentials and configuration Create and store client credentials securely—for example, as CI/CD secrets—and expose them with environment-specific URLs as environment variables. The token issuer and the Camunda Hub API base URL differ between SaaS and Self-Managed: 1. [Create new client credentials](/apis-tools/hub-api-saas/authentication.md#create-new-client-credentials) with the Hub API `create` and `update` permissions. 2. Securely store the `Client ID` and `Client Secret`. The client secret is only shown once. In a pipeline, store them as CI/CD secrets, such as [GitHub Actions secrets](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions), and reference them from your workflow instead of hardcoding them. 3. Expose the following environment variables in your pipeline: ```bash export CAMUNDA_CONSOLE_CLIENT_ID="" export CAMUNDA_CONSOLE_CLIENT_SECRET="" export CAMUNDA_OAUTH_URL="https://login.cloud.camunda.io/oauth/token" export CAMUNDA_CONSOLE_OAUTH_AUDIENCE="api.cloud.camunda.io" ``` The sync script defaults `CAMUNDA_HUB_BASE_URL` to the SaaS URL (`https://hub.camunda.io`), so you don't need to set it for SaaS. In Self-Managed, tokens are issued by your [Management Identity](/self-managed/components/management-identity/authentication.md) instance, and the Camunda Hub API is served from your own installation. 1. [Create new client credentials](/apis-tools/hub-api-sm/authentication.md#create-a-new-application) with the Hub API `create` and `update` permissions. 2. Securely store the application's `Client ID` and `Client Secret`. 3. Expose the following environment variables in your pipeline: ```bash export CAMUNDA_CONSOLE_CLIENT_ID="" export CAMUNDA_CONSOLE_CLIENT_SECRET="" export CAMUNDA_OAUTH_URL="http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token" export CAMUNDA_HUB_BASE_URL="http://localhost:8088" ``` The URLs used in this example are local defaults, for example, `http://localhost:8088` for the API. In a Helm/Kubernetes deployment, use the service or Ingress host configured for Camunda Hub instead. Adjust all URLs to match your installation. You must set `CAMUNDA_HUB_BASE_URL`, as the sync script defaults it to the SaaS URL. :::note This authorization also adds the required `web-modeler-public-api` audience to tokens issued for this application, so no `CAMUNDA_CONSOLE_OAUTH_AUDIENCE` is needed to request a token during the sync. ::: ### Run the sync The sync script in the example repository: 1. Obtains an access token. 2. Discovers each [asset directory](#organize-your-assets). 3. Builds the multipart request. 4. Submits the full desired state to the ingestion endpoint. The included GitHub Actions workflow syncs the catalog on every push to `main`. You can also run it locally from the root of your asset repository: ```bash bash scripts/sync-catalog.sh ``` A successful ingestion returns `204 No Content`. If the submission is invalid, the request fails with a `4xx` status, and no changes are applied; the ingestion is validated and applied as a single transaction. For the full list of status codes and error responses, see the **Ingest catalog assets** reference for [SaaS](/apis-tools/hub-api-saas/specifications/ingest-catalog-assets.api.mdx) or [Self-Managed](/apis-tools/hub-api-sm/specifications/ingest-catalog-assets.api.mdx). ### Ingestion limits The ingestion endpoint enforces the following limits per request: | Limit | Value | | -------------------- | ----- | | Maximum files | 5,000 | | Maximum payload size | 20 MB | | Maximum file size | 4 MB | Each asset consists of two files (a `README.md` and an element template), so a 5,000-file limit supports up to 2,500 assets. In Self-Managed, you can raise these limits using standard [Spring configuration properties](/self-managed/components/hub/configuration/properties.md#catalog). ## Verify the catalog in Hub After your CI/CD pipeline runs successfully: 1. Open Camunda Hub, and navigate to your workspace. 2. Browse the catalog to confirm your assets appear with the correct version, description, and tags. 3. Open a BPMN diagram in the modeler, and apply one of the published element templates to a task to verify it works as expected. When you publish a new version of an asset, an **Update** option is displayed in the modeler on elements that use an older version of the template. ## Next steps - [Manage the asset lifecycle](/components/hub/organization/manage-catalog/manage-asset-lifecycle.md) — unpublish and delete catalog assets. - [Sync assets from multiple repositories](/components/hub/organization/manage-catalog/sync-multiple-repositories.md) — consolidate templates from several repositories before syncing. - [Use catalog assets in Hub](/components/hub/workspace/modeler/element-templates/use-catalog-assets.md) — learn how delivery teams discover and apply published assets while modeling. --- ## Manage the catalog Manage reusable automation assets in a Git repository, and publish them to Camunda Hub so your delivery teams can discover and apply them when modeling. ## In this section In this section, you learn how to: - [Set up a Git repository](/components/hub/organization/manage-catalog/getting-started.md) for storing, approving, and publishing catalog assets. - [Manage the asset lifecycle](/components/hub/organization/manage-catalog/manage-asset-lifecycle.md), including unpublishing and deleting assets. - [Track catalog asset usage](/components/hub/organization/manage-catalog/track-asset-usage.md) to find outdated assets and the projects that still use them. - [Consolidate templates](/components/hub/organization/manage-catalog/sync-multiple-repositories.md) from multiple repositories before syncing. Delivery teams [discover published assets and apply them](/components/hub/workspace/modeler/element-templates/use-catalog-assets.md) when modeling business processes. :::note To perform these tasks, you must have an **Admin** or **Owner** role in your organization. ::: ## Supported asset types In 8.10, the catalog supports a single asset type: **element templates**. This asset contains a JSON [element template descriptor](/components/modeler/element-templates/defining-templates.md) and a `README.md` that supplies metadata and a description. :::note A catalog asset does not bundle the resources a template references, for example, task forms, called processes, or job workers. Those are deployed separately to your Orchestration Clusters through your existing tooling. Other asset types, like blueprints and process bundles, are not supported in 8.10. ::: --- ## Manage the asset lifecycle Unpublish and delete catalog assets to drive safe migrations for delivery teams. ## Prerequisites Before you start, make sure you have [set up the catalog](/components/hub/organization/manage-catalog/getting-started.md) and can submit assets through your CI/CD pipeline. ## How delivery teams see asset versions As a center of excellence, you control which element templates delivery teams can discover and apply through the catalog. Over time you will update assets, replace them with successors, or remove them entirely. When you do, this affects how delivery teams see and use the assets. ### Browsing assets When browsing the catalog, your delivery teams only see the **latest published version** of an asset. Older versions are not offered for new diagrams, and unpublished and deleted assets are not discoverable at all. ### Used assets Diagrams that already reference a template continue to work with [unpublished assets](#unpublish-an-asset), but when an asset is unpublished, its template is also [deprecated](/components/modeler/element-templates/template-metadata.md#deprecating-a-template-deprecated), so those diagrams show a deprecation hint that prompts teams to migrate. [Deleted assets](#delete-an-asset) used in diagrams are displayed as [missing templates](/components/hub/workspace/modeler/element-templates/using-templates.md#missing-templates) and can't be resolved. ## Unpublish an asset When your CI/CD pipeline submits assets, the submission represents the _full desired state_ of the catalog. Any asset that already exists in Camunda Hub but is not included in the submission is automatically unpublished. Therefore, to unpublish an asset, remove its directory from the asset repository, and run the sync pipeline again. Unpublishing an asset also _deprecates_ its element template. Deprecated templates continue to work in existing diagrams, but are no longer offered when applying templates or creating elements. Diagrams that reference deprecated templates show a [deprecation hint](/components/modeler/element-templates/template-metadata.md#deprecating-a-template-deprecated) in the modeler. This gives delivery teams a clear signal to migrate. For the center of excellence, responsible for managing the catalog, the asset remains visible, so you can: - Track which diagrams still reference the template. - Encourage the migration of those diagrams to a newer version or a different template before the asset is removed entirely. Unpublished assets that are still in use are reported as **Deprecated** in the [asset usage overview](/components/hub/organization/manage-catalog/track-asset-usage.md), where you can see the workspaces and projects that still reference them. :::note Unpublishing is a catalog-level state that indicates the asset is no longer part of the current submission. Use it when an asset should no longer be offered to delivery teams—for example, when you replace a connector with a successor and want to drive migration off the old one. Because unpublishing automatically deprecates the underlying element template, you don't need to manually set the `deprecated` property in the template to flag it as outdated; removing the asset from the submission is enough. ::: ## Republishing an unpublished asset To republish an unpublished asset: - Add the asset's directory back to the repository. - [Increment the element template's version](./getting-started.md#version-your-element-templates). If you don't increment the element template's version, you'll receive a `400` response. ## Delete an asset To permanently remove an asset from the catalog, use the Camunda Hub API delete endpoint: ``` DELETE /api/v2/catalog/assets/{assetKey} ``` For the full request and response schema, see the **Delete a catalog asset** reference for [SaaS](/apis-tools/hub-api-saas/specifications/delete-catalog-asset.api.mdx) or [Self-Managed](/apis-tools/hub-api-sm/specifications/delete-catalog-asset.api.mdx). Your API token must have the Hub API `delete` permission. Deleting an asset removes it and all of its versions entirely: - Center of excellence and delivery teams can no longer find it while browsing the catalog. - Its element template can no longer be resolved in diagrams. Values already configured on diagram elements remain in the BPMN file, but the template definition—including labels, groupings, and validation—is no longer applied. The element is shown as a [missing template](/components/hub/workspace/modeler/element-templates/using-templates.md#missing-templates). :::warning Deletion is irreversible. Use it to correct mistakes or remove assets that should never have been published. To phase out assets, prefer unpublishing so the template is deprecated and delivery teams have a clear migration path. ::: ## Next steps - [Get started with the catalog](/components/hub/organization/manage-catalog/getting-started.md) — set up the repository and CI/CD pipeline. - [Track catalog asset usage](/components/hub/organization/manage-catalog/track-asset-usage.md) — find outdated assets and the projects that still use them. - [Sync assets from multiple repositories](/components/hub/organization/manage-catalog/sync-multiple-repositories.md) — consolidate templates before syncing. - [Use catalog assets in Hub](/components/hub/workspace/modeler/element-templates/use-catalog-assets.md) — learn how delivery teams apply published assets. --- ## Sync assets from multiple repositories Consolidate element templates spread across several repositories into a single collection repository before syncing to the Hub catalog. ## When to use this guide The catalog syncs from a single repository. Depending on your setup, you may or may not need this guide: - ❌ If your element templates already live together in one repository, [sync from it directly](/components/hub/organization/manage-catalog/getting-started.md). You do not need this guide. - ✅ If your element templates are spread across several repositories, you need to consolidate them before syncing. This guide shows you how. ## Consolidate templates into a collection repository Camunda recommends using a CI/CD job in each source repository that copies its element templates and READMEs into a dedicated collection repository whenever a new version is created. For example: ```yaml # Example: GitHub Actions workflow in a source repository name: Push templates to collection repo on: push: branches: [main] jobs: push-to-collection: runs-on: ubuntu-latest steps: - name: Checkout source repository uses: actions/checkout@v4 - name: Push element templates to collection repo run: | git clone https://x-access-token:${{ secrets.COLLECTION_REPO_TOKEN }}@github.com/your-org/catalog-collection.git cp -r element-templates/payment-connector catalog-collection/payment-connector cd catalog-collection git add . git commit -m "Update payment-connector templates" git push ``` The collection repository then [syncs to Camunda Hub](./getting-started.md#run-the-sync). ## Next steps - [Get started with the catalog](/components/hub/organization/manage-catalog/getting-started.md) — set up the repository and CI/CD pipeline. - [Manage the asset lifecycle](/components/hub/organization/manage-catalog/manage-asset-lifecycle.md) — unpublish and delete catalog assets. --- ## Track catalog asset usage See which catalog assets are outdated and which workspaces and projects still use an older version, so you can prioritize migrations. ## Prerequisites To open the **Asset usage** tab, you must have an **Org Admin** or **Org Owner** role in your organization, and your organization must have at least one [published catalog asset](/components/hub/organization/manage-catalog/getting-started.md). Without a published asset, the catalog shows only the setup instructions and no tabs. ## Open the asset usage overview To review adoption across your organization: 1. In Camunda Hub, in the left navigation, select **Catalog**. 2. On the **Organization catalog** page, select the **Asset usage** tab. The **Browse assets** tab shows the same catalog your delivery teams see. The **Asset usage** tab is visible to organization administrators and owners only and adds the governance view described here. ## Asset status values Camunda Hub derives a status for every catalog asset from the versions currently referenced in diagrams: | Status | Meaning | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Unused** | The asset is not used in any diagram. | | **Deprecated** | The asset is used in at least one diagram, and it's [unpublished](/components/hub/organization/manage-catalog/manage-asset-lifecycle.md#unpublish-an-asset) or its latest version is deprecated. | | **Update available** | The asset is used in at least one diagram, and at least one diagram references a version older than the latest version. | | **Up to date** | The asset is used in at least one diagram, and every diagram that references it uses the latest version. | The same four values are used in the usage table, the usage drawer, and the project tile. :::note **Deprecated** takes priority over the version comparison. An unpublished asset that is still in use is reported as **Deprecated**, even if some diagrams already use its latest version. ::: Unpublished assets remain in the usage table even though delivery teams can no longer discover them while browsing, so you keep visibility into the remaining usage you need to migrate away. ## Read the asset usage table Each row in the **Asset usage** table represents one catalog asset. | Column | Description | | ---------------- | ------------------------------------------------------------------------------------------------------------ | | **Asset name** | The name of the catalog asset. | | **Status** | The asset status described in [Asset status values](#asset-status-values). | | **Workspaces** | The number of distinct workspaces that use the asset. | | **Projects** | The number of distinct projects that use the asset. | | **On latest** | The ratio of projects using the latest version compared to projects using any version. For example, `3 / 7`. | | **Last updated** | When the asset was last updated in the catalog. | A project counts toward **On latest** only when _every_ usage of the asset within that project is on the latest version. A project with one outdated diagram is not counted, even if its other diagrams are current. ### Filter, search, and sort the table Use the toolbar above the table to narrow the list: - **Search catalog assets** matches against the asset name only. - **Status** opens a checkbox list. Select any combination of **Up to date**, **Update available**, **Deprecated**, and **Unused**. Matching assets for any selected status are shown, and the trigger displays a count badge while a filter is active. - **Sort by** offers sorting options: | Sort option | Description | | :---------- | :-------------------------------------------------------------------------------------------------------- | | Name (A-Z) | **(Default)** Order assets by name in ascending order (A-Z) | | Name (Z-A) | Order assets by name in descending order (Z-A) | | Newest | Order assets by last updated date, newest first | | Status | Order assets by urgency: **Deprecated**, then **Update available**, then **Up to date**, then **Unused**. | Changing the search, status filter, sort, or page size returns you to page 1. Table headers aren't sortable, and you can page through results with a page size of 20, 50, or 100. ## Find where an asset is used Select any row in the **Asset usage** table to open a drawer on the right showing where that asset is used. The drawer header repeats the asset name as a link to its catalog detail page, shows the asset status, and summarizes the reach of the asset, for example `Used in 7 projects across 3 workspaces.` Below the header, usage is grouped by the asset version in use. Each version group shows: - The version number, for example `v3`. - Either a **Latest** marker or an **Update available** badge. A group for any version other than the latest always means those projects have an update available. - The number of projects using that version. Each entry in a group lists the **Project** and the **Workspace** it belongs to, both linking to the corresponding page in Camunda Hub. Select **Load more** to fetch additional entries. If an asset has no usage, the drawer shows **Unused**. ### How permissions affect the results Because the **Asset usage** tab is limited to organization administrators and owners, the drawer shows every project in your organization that uses the asset, across all workspaces, including workspaces you aren't a member of. ## Drive a migration off an outdated asset To turn the overview into a prioritized list of work: 1. Open the **Asset usage** tab and set the **Status** filter to **Update available** and **Deprecated** to hide assets that need no action. 2. Select **Status** in **Sort by** to bring the most urgent assets to the top. 3. Compare the **On latest** ratio of each asset to judge how much of the migration is left, and the **Workspaces** and **Projects** counts to judge how many teams a change affects. 4. Select an asset to open the usage drawer, and note the projects grouped under any version other than the latest. Those are the projects that still need to migrate. You can't update a diagram from the catalog. The migration itself happens in each project, where teams [resolve outdated assets](/components/hub/workspace/modeler/element-templates/use-catalog-assets.md#review-catalog-updates-for-a-project) from the project page or directly in the diagram. ## Next steps - [Manage the asset lifecycle](/components/hub/organization/manage-catalog/manage-asset-lifecycle.md) — unpublish and delete assets to drive migrations. - [Use catalog assets in Hub](/components/hub/workspace/modeler/element-templates/use-catalog-assets.md) — see how delivery teams find and resolve outdated assets in their own projects and diagrams. --- ## Manage clusters A cluster is the infrastructure that runs Camunda 8 and hosts your [environments](/components/concepts/environments.md). Learn how clusters relate to environments in [clusters](/components/concepts/clusters.md). How you work with clusters in Camunda Hub depends on your deployment. ## Permissions Only organization owners, admins, and DevOps users see the **Clusters** page. Other users work with the environments assigned to their [workspace](/components/concepts/workspaces.md), and they don't see clusters. ## View clusters To view your clusters, click **Environments** in the left navigation, and then click **Clusters** next to the page title. The page lists your clusters. Each cluster shows the following details: | Detail | Description | | :------ | :-------------------------------------------------------------- | | Name | The name of the cluster. Select it to open the cluster details. | | Status | The [status](#cluster-statuses) of the cluster. | | Version | The Camunda version of the cluster. | | Region | The region where the cluster runs. | ### Cluster statuses The status of a cluster reflects its state. It updates automatically while the cluster changes state, and settles on the health of the cluster when the change completes. | Status | Description | | :---------- | :-------------------------------------------------------------------------------------------------------- | | Healthy | The cluster is running. | | Unhealthy | The cluster reports a problem. | | Creating | The cluster is being created. | | Updating | The cluster is being updated. | | Restoring | The cluster is being restored from a backup. | | Maintenance | The cluster is under maintenance. | | Waiting | The cluster is waiting for input. | | Paused | The cluster is paused. You can [resume](/components/saas/clusters/manage-cluster.md#resume-a-cluster) it. | | Resuming | The cluster is starting after being resumed. | ## Add a cluster In SaaS, you create clusters in Camunda Hub. Every cluster you create gets one environment automatically. You choose the [cluster type and size](/components/saas/clusters.md), the region, the version, and a tag, such as `prod`, that appears on the environment of the cluster. Then an organization admin [assigns the environment to a workspace](../manage-environments/assign-environments.md) so teams can deploy to it. See [create a cluster](/components/saas/clusters/create-cluster.md). ## View cluster details Select a cluster to open its details. The header shows the name and status of the cluster. ### Overview The overview summarizes the cluster in the following sections: | Section | Description | | :-------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Environments | The environment the cluster hosts. Select the environment to open its details and its applications: Operate, Tasklist, Admin, and Optimize. | | Cluster details | The status, type, size, generation, and tag of the cluster. From here you can resize the cluster, modify its tag, review an available update, and resume a paused cluster. | | Components | The health of the components of the cluster, such as the connector runtime. Click **Manage** on the **Connectors** tile to open [Connector Management](./manage-connectors.md). | | Jobs | The jobs of the environment of the cluster for the last 24 hours: the number of jobs that were created, completed, and not completed. See the [job dashboard](../analyze-operations/job-dashboard.md). | ### Cluster management actions You can manage a cluster with the following actions: | Task | Where to find it | | :----------------------------------------------------- | :-------------------------------------------------------------------------- | | Rename, resume, update, resize, or delete a cluster | [Manage your cluster](/components/saas/clusters/manage-cluster.md) | | Create and manage API clients | [Manage API clients](/components/saas/clusters/manage-api-clients.md) | | Create secrets for your connectors | [Manage connector secrets](/components/saas/clusters/manage-secrets.md) | | Get notified when process instances stop with an error | [Create an alert](/components/saas/clusters/manage-alerts.md) | | Restrict access to the cluster | [Manage IP allowlists](/components/saas/clusters/manage-ip-allowlists.md) | | Back up the cluster | [Create cluster backups](/components/saas/clusters/cluster-backups.md) | | Enable authorizations and other cluster settings | [Manage cluster settings](/components/saas/clusters/settings.md) | | Record user and client operations | [Configure the audit log](/components/saas/clusters/configure-audit-log.md) | | Check how well the cluster copes with its workload | [Monitor cluster load](/components/saas/clusters/cluster-capacity.md) | | Fix common problems | [Troubleshoot clusters](/components/saas/clusters/troubleshoot-clusters.md) | To monitor and manage the connectors that run on a cluster, see [manage your connectors](./manage-connectors.md). ## Update a cluster In SaaS, you update clusters in Camunda Hub. You can rename, resume, update, resize, and delete a cluster, and configure its settings. See [manage your cluster](/components/saas/clusters/manage-cluster.md) and [manage cluster settings](/components/saas/clusters/settings.md). ## Permissions {#permissions-self-managed} Only organization admins and DevOps users see the **Clusters** page. Other users work with the environments assigned to their [workspace](/components/concepts/workspaces.md), and they don't see clusters. ## View clusters {#view-clusters-self-managed} To view your clusters, click **Environments** in the left navigation, and then click **Clusters** next to the page title. Use the search box to find a cluster by name, and filter the list by status, version, and tag. Each cluster shows the following details: | Detail | Description | | :----------- | :------------------------------------------------------------------------------------ | | Name | The name of the cluster. Select it to open the cluster details. | | Namespace | The Kubernetes namespace of the cluster, if it's configured. | | Version | The Camunda version of the cluster. | | Status | The health of the cluster. See [cluster statuses](#cluster-statuses-self-managed). | | License | The license of the cluster, if the cluster reports it. | | Environments | The number of [environments](/components/concepts/environments.md) the cluster hosts. | ### Cluster statuses {#cluster-statuses-self-managed} Camunda Hub monitors the health of the components of a cluster to determine its status. For details on how the status is determined, see the [Camunda Hub configuration](/self-managed/components/hub/configuration/environments.md#environment-status). | Status | Description | | :-------- | :--------------------------------------------------------------------------------------- | | Healthy | The components of the cluster are healthy. | | Unhealthy | A component of the cluster reports a problem. | | Unknown | Camunda Hub can't determine the status, for example because a component doesn't respond. | ## Add a cluster {#add-a-cluster-self-managed} In Self-Managed, you provision and operate your clusters with your own platform tooling, outside Camunda Hub. Camunda Hub displays the clusters that are defined in its [configuration](/self-managed/components/hub/configuration/properties.md#clusters), and it is read-only for clusters, so you can't create, resize, update, or delete a cluster from it. To make a cluster you provision visible in Camunda Hub: 1. Provision the cluster with your platform tooling, as described in the [Self-Managed installation guide](/self-managed/setup/overview.md). 1. Add the cluster to the [Camunda Hub configuration](/self-managed/components/hub/configuration/properties.md#clusters). Camunda Hub reads this configuration at startup, so perform a rolling restart of Camunda Hub to pick up the change. Click **Register new cluster** on the **Clusters** page to see these steps in Camunda Hub. For the configuration options, see the [clusters](/self-managed/components/hub/configuration/properties.md#clusters) and [Physical Tenants](/self-managed/components/hub/configuration/environments.md#physical-tenants) sections of the Camunda Hub configuration. After the restart, the cluster appears on the **Clusters** page, and each of its [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md) appears as an environment. An organization admin can then [assign the environments to a workspace](../manage-environments/assign-environments.md). ## View cluster details {#view-cluster-details-self-managed} Select a cluster to open its details. The header shows the status of the cluster and the number of environments it hosts. ### Overview {#overview-self-managed} The overview summarizes the cluster in the following sections: | Section | Description | | :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Environments | The environments the cluster hosts. Select an environment to open its details and its applications: Operate, Tasklist, and Admin. Optimize is shown if it's configured. | | Cluster details | The status, namespace, version, cluster ID, license, and the time Camunda Hub last synced with the cluster. It also shows any custom properties from the [Camunda Hub configuration](/self-managed/components/hub/configuration/properties.md#clusters). | | Components | The health and version of the components of the cluster, such as Zeebe, Operate, Tasklist, and Optimize. | | Jobs | The jobs of all environments of the cluster for the last 24 hours: the number of jobs that were created, completed, and not completed. See the [job dashboard](../analyze-operations/job-dashboard.md). | | Connectors | The health and version of the connector runtime. Click **Manage** to open [Connector Management](./manage-connectors.md). | To monitor and manage the connectors that run on a cluster, see [manage your connectors](./manage-connectors.md). ## Update a cluster {#update-a-cluster-self-managed} Updates to clusters happen outside Camunda Hub: - To change how a cluster appears in Camunda Hub, for example its name, tags, [components](/self-managed/components/hub/configuration/properties.md#components), or Physical Tenants, update the [Camunda Hub configuration](/self-managed/components/hub/configuration/properties.md#clusters), and perform a rolling restart. - To upgrade or scale the cluster and its components, use your platform tooling. See the [upgrade guides](/self-managed/upgrade/index.md). If you remove a cluster from the [Camunda Hub configuration](/self-managed/components/hub/configuration/properties.md#clusters) but a workspace still uses its environments, they stay in the workspace with the status **Not reported**. ## Next steps - Learn how to [manage environments](../manage-environments/index.md). - Learn about [cluster types, sizes, and Free Trial clusters](/components/saas/clusters.md) in SaaS. - Review the [Camunda Hub configuration](/self-managed/components/hub/configuration/properties.md#clusters) in Self-Managed. - Learn how to [monitor connectors](./manage-connectors.md). --- ## Manage your connectors Monitor and manage connectors running on your cluster. ## About connector management With cluster connector management, you can monitor and manage running connectors on your cluster. This includes inbound connectors, such as [webhooks, message queue subscriptions, and polling subscriptions](/reference/glossary.md#inbound-connector), and outbound connectors. Use this feature to review connector status and troubleshoot issues. For example, you can inspect connector details and use the [activity log](#activity-log) to investigate failures. ## Manage connectors Manage connectors from the **Connector Management** page: 1. In the left navigation, click **Environments**, click **Clusters**, and then select a cluster. 1. On the **Overview** tab, open the **Connectors** section: - In SaaS, click **Manage** on the **Connectors** component tile. - In Self-Managed, click **View connectors** on the **Connectors** card. The card appears when the cluster has a connector runtime that Camunda Hub can reach. The **Connector Management** page provides an overview of the connectors running on a cluster. - The page shows each connector on a separate row. - Use this page to review connector status, inspect details, and troubleshoot issues. - Available details depend on the connector type. :::note [Webhook connector](/components/connectors/protocol/http-webhook.md) names also include the names of any connector based on the webhook. For example, "_Webhook (aws:eventbridge, GitHubWebhook)_". ::: ## View inbound connectors The **Inbound connectors** tab shows each active inbound connector type on a separate row. The page header shows counts across all inbound connector instances: | Field | Description | | ------------------- | ------------------------------------------------------------------------------------- | | Unhealthy instances | The total number of unhealthy inbound connector instances across all connector types. | | Unknown instances | The total number of inbound connector instances with an unknown status. | | Total instances | The total number of inbound connector instances running. | | Connector types | The number of distinct inbound connector types with active instances. | Use the search box and status filter to narrow the list of active inbound connectors. Each connector type shows aggregated counts for its instances: | Field | Description | | ------------------ | ------------------------------------------------------------------------------------------------------- | | Name | The name and type ID of the inbound connector. Select the connector name to view its running instances. | | Unhealthy | The number of instances currently unhealthy. | | Unknown | The number of instances with an unknown status. | | Healthy | The number of instances currently healthy. | | Triggers | The total number of triggers recorded for this connector type. | | Correlation failed | The total number of correlation failures recorded for this connector type. | ## View inbound connector instances Select an inbound connector to view its running instances. The page header shows counts for the selected connector: | Field | Description | | ---------------------------- | ------------------------------------------------------------------------------- | | Active inbound executables | The number of active instances for this connector. | | Triggers (total) | The total number of triggers recorded since the last runtime start. | | Correlation failures (total) | The total number of correlation failures recorded since the last runtime start. | By default, the page shows each inbound connector instance on a separate row below the header counts. | Field | Description | | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Process | The process ID and version associated with the connector instance. Select it to open the process in Operate. | | Elements | The BPMN element where the connector is active. Use this to locate the connector in your diagram. | | Activated | When the connector instance was activated. | | Status | The current health of the connector instance. **Healthy** means the connector is running without issues. **Unhealthy** means the connector requires attention. Open the instance details to review health details and recent activity log entries. | | Restart | If a connector instance fails to activate or is stuck in an unhealthy state, select **Restart** after you resolve the underlying issue to retry activation. | Select **Show runtime breakdown** to view the header counts by runtime. The breakdown shows each runtime in its own column alongside a total: | Field | Description | | ------- | ----------------------------------------------------------------------------------------------------------------------------------- | | Event | The type of event recorded, for example **Triggered**, **Correlated**, **Correlation failed**, or **Activation condition not met**. | | Runtime | The count of this event recorded by a specific runtime deployment, identified by its deployment ID. | | Total | The sum of this event's count across all runtimes. | ## View inbound connector instance details Select a row to view additional details and troubleshoot issues. The page header shows the process name and version and the BPMN element where the connector instance is active, for example **Process2 v1 › Event_0sb4klr**. The page also shows the following details: | Field | Description | | ----------- | ----------------------------------------------------------------------------------------------------------- | | Instance ID | The ID of the connector instance. | | Connector | The connector type ID. | | Webhook URL | For webhook-based inbound connectors, the URL that triggers the connector. Select the copy icon to copy it. | ### Inbound connector runtimes The page shows each runtime reporting this connector instance on a separate row. | Field | Description | | ------------ | ---------------------------------------------------------- | | Runtime | The runtime deployment reporting the connector instance. | | Status | The health of the connector instance on this runtime. | | Last updated | When this runtime last reported a status for the instance. | ### Activity log The activity log shows recent activities recorded for the connector instance. Use these logs to troubleshoot connector issues. Depending on the connector type, the activity log can include health changes, request details, and runtime events. The activity log redacts sensitive values where needed. Use the filters to narrow the entries shown and toggle the sort order between latest and oldest first: | Field | Description | | ----------- | ------------------------------------------------------------------------------------- | | Tags | Filter entries by tag, for example **Health**. | | Instance | Filter entries by a specific instance when deduplication groups multiple occurrences. | | Severity | Filter entries by severity. | | Time window | Filter entries recorded within a specific time range. | Activity logs are available for active connectors and recent troubleshooting. When a connector is permanently removed, its activity log entries are also removed. ### Process info For inbound connectors, this section shows detailed information about the BPMN process instance and its associated connector as a JSON object. Use this information to review process metadata, the connector template, and connector configuration properties. For example: ```json [ { "bpmnProcessId": "Process_0wjo4ez", "processName": "Order intake", "messageName": "order.received", "version": 1, "processDefinitionKey": 2251799813686169, "elementId": "StartEvent_1", "elementName": null, "elementType": "startEvent", "tenantId": "", "elementTemplateDetails": { "id": "io.camunda.connectors.inbound.KafkaMessageStart.v1", "version": "6", "icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0nMTgnIGhlaWdodD0nMTgnIHZpZXdCb3g9JzAgMCAyNTYgNDE2JyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHByZXNlcnZlQXNwZWN0UmF0aW89J3hNaWRZTWlkJz4KICAgIDxwYXRoIGQ9J00yMDEuODE2IDIzMC4yMTZjLTE2LjE4NiAwLTMwLjY5NyA3LjE3MS00MC42MzQgMTguNDYxbC0yNS40NjMtMTguMDI2YzIuNzAzLTcuNDQyIDQuMjU1LTE1LjQzMyA0LjI1NS0yMy43OTcgMC04LjIxOS0xLjQ5OC0xNi4wNzYtNC4xMTItMjMuNDA4bDI1LjQwNi0xNy44MzVjOS45MzYgMTEuMjMzIDI0LjQwOSAxOC4zNjUgNDAuNTQ4IDE4LjM2NSAyOS44NzUgMCA1NC4xODQtMjQuMzA1IDU0LjE4NC01NC4xODQgMC0yOS44NzktMjQuMzA5LTU0LjE4NC01NC4xODQtNTQuMTg0LTI5Ljg3NSAwLTU0LjE4NCAyNC4zMDUtNTQuMTg0IDU0LjE4NCAwIDUuMzQ4LjgwOCAxMC41MDUgMi4yNTggMTUuMzg5bC0yNS40MjMgMTcuODQ0Yy0xMC42Mi0xMy4xNzUtMjUuOTExLTIyLjM3NC00My4zMzMtMjUuMTgydi0zMC42NGMyNC41NDQtNS4xNTUgNDMuMDM3LTI2Ljk2MiA0My4wMzctNTMuMDE5QzEyNC4xNzEgMjQuMzA1IDk5Ljg2MiAwIDY5Ljk4NyAwIDQwLjExMiAwIDE1LjgwMyAyNC4zMDUgMTUuODAzIDU0LjE4NGMwIDI1LjcwOCAxOC4wMTQgNDcuMjQ2IDQyLjA2NyA1Mi43Njl2MzEuMDM4QzI1LjA0NCAxNDMuNzUzIDAgMTcyLjQwMSAwIDIwNi44NTRjMCAzNC42MjEgMjUuMjkyIDYzLjM3NCA1OC4zNTUgNjguOTR2MzIuNzc0Yy0yNC4yOTkgNS4zNDEtNDIuNTUyIDI3LjAxMS00Mi41NTIgNTIuODk0IDAgMjkuODc5IDI0LjMwOSA1NC4xODQgNTQuMTg0IDU0LjE4NCAyOS44NzUgMCA1NC4xODQtMjQuMzA1IDU0LjE4NC01NC4xODQgMC0yNS44ODMtMTguMjUzLTQ3LjU1My00Mi41NTItNTIuODk0di0zMi43NzVhNjkuOTY1IDY5Ljk2NSAwIDAgMCA0Mi42LTI0Ljc3NmwyNS42MzMgMTguMTQzYy0xLjQyMyA0Ljg0LTIuMjIgOS45NDYtMi4yMiAxNS4yNCAwIDI5Ljg3OSAyNC4zMDkgNTQuMTg0IDU0LjE4NCA1NC4xODQgMjkuODc1IDAgNTQuMTg0LTI0LjMwNSA1NC4xODQtNTQuMTg0IDAtMjkuODc5LTI0LjMwOS01NC4xODQtNTQuMTg0LTU0LjE4NHptMC0xMjYuNjk1YzE0LjQ4NyAwIDI2LjI3IDExLjc4OCAyNi4yNyAyNi4yNzFzLTExLjc4MyAyNi4yNy0yNi4yNyAyNi4yNy0yNi4yNy0xMS43ODctMjYuMjctMjYuMjdjMC0xNC40ODMgMTEuNzgzLTI2LjI3MSAyNi4yNy0yNi4yNzF6bS0xNTguMS00OS4zMzdjMC0xNC40ODMgMTEuNzg0LTI2LjI3IDI2LjI3MS0yNi4yN3MyNi4yNyAxMS43ODcgMjYuMjcgMjYuMjdjMCAxNC40ODMtMTEuNzgzIDI2LjI3LTI2LjI3IDI2LjI3cy0yNi4yNzEtMTEuNzg3LTI2LjI3MS0yNi4yN3ptNTIuNTQxIDMwNy4yNzhjMCAxNC40ODMtMTEuNzgzIDI2LjI3LTI2LjI3IDI2LjI3cy0yNi4yNzEtMTEuNzg3LTI2LjI3MS0yNi4yN2MwLTE0LjQ4MyAxMS43ODQtMjYuMjcgMjYuMjcxLTI2LjI3czI2LjI3IDExLjc4NyAyNi4yNyAyNi4yN3ptLTI2LjI3Mi0xMTcuOTdjLTIwLjIwNSAwLTM2LjY0Mi0xNi40MzQtMzYuNjQyLTM2LjYzOCAwLTIwLjIwNSAxNi40MzctMzYuNjQyIDM2LjY0Mi0zNi42NDIgMjAuMjA0IDAgMzYuNjQxIDE2LjQzNyAzNi42NDEgMzYuNjQyIDAgMjAuMjA0LTE2LjQzNyAzNi42MzgtMzYuNjQxIDM2LjYzOHptMTMxLjgzMSA2Ny4xNzljLTE0LjQ4NyAwLTI2LjI3LTExLjc4OC0yNi4yNy0yNi4yNzFzMTEuNzgzLTI2LjI3IDI2LjI3LTI2LjI3IDI2LjI3IDExLjc4NyAyNi4yNyAyNi4yN2MwIDE0LjQ4My0xMS43ODMgMjYuMjcxLTI2LjI3IDI2LjI3MXonCiAgICAgICAgICBzdHlsZT0nZmlsbDojMjMxZjIwJy8+Cjw" }, "properties": { "deduplicationMode": "AUTO", "deduplicationModeManualFlag": "false", "schemaStrategy.type": "noSchema", "topic.topicName": "rereer", "consumeUnmatchedEvents": "true", "inbound.type": "io.camunda:connector-kafka-inbound:1", "authenticationType": "credentials", "correlationRequired": "notRequired", "topic.bootstrapServers": "eererreer", "autoOffsetReset": "latest" } } ] ``` :::note If you are using deduplication, each connector occurrence in the BPMN diagram is shown in the array. ::: ## View outbound connectors The **Outbound connectors** tab shows each active outbound connector type on a separate row. The page header shows counts across all outbound connector invocations: | Field | Description | | ------------------- | ---------------------------------------------------------------------- | | Connector types | The number of distinct outbound connector types available. | | Invocations (total) | The total number of invocations recorded since the last runtime start. | | Max execution time | The longest execution time recorded across all invocations. | | Failure rate | The percentage of invocations that failed. | Use the search box, status filter, and **With invocations** checkbox to narrow the list of outbound connectors. Each connector type shows aggregated counts: | Field | Description | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Name | The name and type ID of the outbound connector. Select it to view its details. | | Invocations | The total number of invocations recorded for this connector. | | Max time | The longest execution time recorded for this connector. | | Failed | The number of failed invocations recorded for this connector. | | Status | The connectivity status of the connector, for example **All connected**. See [connectivity states](#outbound-connector-runtimes) for what each status means. | ## View outbound connector details Select an outbound connector to view its details. The page shows the connector name with badges indicating its direction (**Outbound**), whether it's enabled, and its connectivity status. The page header shows counts for the selected connector: | Field | Description | | ------------ | -------------------------------------------------------------- | | Calls | The sum of all invocations across runtimes. | | Total time | The sum of execution time for all invocations across runtimes. | | Slowest call | The highest execution time recorded across runtimes. | | Average time | The total time divided by the number of calls. | The **Configuration** section shows the connector's setup: | Field | Description | | --------------- | ---------------------------------------------------------------------------------------- | | Input variables | The input variables used by the connector. | | Timeout | The configured timeout for the connector. If none is set, this shows **Not configured**. | ### Outbound connector runtimes The **Connector runtimes** section shows each runtime deployment reporting the connector, with its connectivity details and invocation metrics. | Field | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------- | | Connector runtime | The runtime deployment reporting the connector and its overall connectivity status, for example **All connected**. | | Connectivity type | The connectivity reported for this runtime, for example **Broker** or **Gateway**, along with its status and number of streams. | | Stream IDs | The stream IDs registered for this connectivity type. Select the copy icon to copy an ID. | Each broker or gateway connectivity type reports one of the following connectivity states: | State | Description | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | All connected | All client streams for this job type are registered as consumers on all broker remote streams. | | Partially connected | Client streams exist on the gateway, but not all of them appear as consumers on every broker's remote stream. This can indicate a transient issue; if it persists, restart the gateway. | | None | Brokers were queried, but no client stream appears as a consumer on any broker's remote stream. This indicates a genuine connectivity problem. | | Unknown | The broker state can't be determined. This can happen when broker monitoring isn't configured; broker monitoring is configured but the query failed and the gateway's remote streams are also empty; or the gateway is a standalone deployment with no embedded broker. Enable and configure broker monitoring to get an accurate connectivity state. | Each runtime also shows its own invocation metrics: | Field | Description | | --------------- | ---------------------------------------------------- | | Completed | The number of invocations completed on this runtime. | | Cumulative time | The total execution time recorded on this runtime. | | Slowest | The highest execution time recorded on this runtime. | | Mean time | The average execution time recorded on this runtime. | ## Connector metrics Connector metrics are available for inbound and outbound connectors. For metric definitions, response fields, and endpoint details, use the connector observability reference. ## Connector management access Access to connector management depends on your cluster and organization permissions. If you cannot open connector management or view connector details, contact your administrator. --- ## Assign environments to a workspace Assign environments to a workspace to decide where its projects can deploy. Every project in the workspace can then deploy to all the environments assigned to the workspace. ## Permissions You can assign environments to a workspace if you're an **Organization owner** or **Organization admin**. A DevOps user can also manage the assigned environments of a workspace if they have the **Editor** or **Workspace admin** role in that workspace. ## Assignment rules - A workspace can have any number of environments, including none. - A workspace can have more than one environment for the same lifecycle phase. For example, you can assign multiple development environments to a workspace. - All projects in a workspace inherit the environments assigned to the workspace. - An environment can be assigned to more than one workspace. Assigning one environment to one workspace is the recommended model, but Camunda Hub doesn't enforce it. - Projects can use only the environments assigned to their workspace. Within a workspace, users see only the environments assigned to it: workspace admins and editors see them, and viewers and commenters see none. At the organization level, every user in SaaS sees every environment. See [environments](/components/concepts/environments.md#who-can-see-and-use-environments). ## Assign environments when you create a workspace 1. In Camunda Hub, click **Workspaces** in the left navigation, and then click **Create workspace**. 2. Complete the **General** and **Members** steps. See [create a workspace](../manage-workspaces/manage-workspace.md#create-a-workspace). 3. Under **Environments**, select the environments for the workspace. You can change this later. 4. Click **Create workspace**. If Camunda Hub creates the workspace but can't assign the environments, it shows a message. Assign the environments from the workspace settings. ## Change the assigned environments 1. In Camunda Hub, click **Workspaces** in the left navigation, find the workspace, and click **Manage**. Alternatively, open the workspace and click **Settings** in the left navigation. 2. Open the **Environments** tab. 3. Click **Edit environments**. If the workspace has no environments, click **Add environments**. 4. Select the environments to assign, and remove the ones you no longer need. Then click **Save**. Saving replaces the assigned environments with your selection. ### Find an environment The selection view lists the available environments. For each environment, it shows the version, status, region, and the other workspaces that use it, or **Not assigned**. - Use **Search environments** to search by name. - Filter by version or tag. - Turn on **Show only unassigned** to hide the environments that other workspaces use. ### Remove the last environment If you remove every environment from a workspace, Camunda Hub asks you to confirm with **Remove environments**. The workspace then has no environment to deploy to. You can assign environments again at any time. ### What happens when you unassign an environment When you unassign an environment, projects in the workspace can no longer select it, and they can't deploy to it. Nothing else changes in the environment or in other workspaces that use it. ## Assign environments with the API Use the Camunda Hub API to assign environments as part of your workspace setup or team onboarding automation. The API follows the same rules as the user interface. | Method | Path | Description | | :----- | :---------------------------------------- | :------------------------------------------------------------------------- | | `GET` | `/environments` | List the environments in your organization. | | `GET` | `/workspaces/{workspaceKey}/environments` | List the environments assigned to a workspace. | | `PUT` | `/workspaces/{workspaceKey}/environments` | Replace the environments assigned to a workspace with the ones you send. | | `GET` | `/projects/{projectKey}/environments` | List the environments a project can use. This is the set of its workspace. | The `PUT` request takes an `environmentIds` list of up to 500 environment IDs, and replaces the complete set. To unassign an environment, send the list without it. To unassign all environments, send an empty list. See the API reference for [SaaS](/apis-tools/hub-api-saas/specifications/update-workspace-environments.api.mdx) and [Self-Managed](/apis-tools/hub-api-sm/specifications/update-workspace-environments.api.mdx). ## Next steps - [Manage environments](./index.md). - [Manage workspace members](../manage-workspaces/manage-workspace-members.md). - Learn how to [deploy a project](/components/hub/workspace/manage-projects/deploy-project.md). --- ## Manage environments Environments are the deployment targets for your teams, where they run their processes. Use the **Environments** page in Camunda Hub to view every environment in your organization, monitor its status, and open its applications. Each environment runs on a [cluster](/components/concepts/clusters.md), which you administer on the [cluster pages](../manage-clusters/index.md). To learn how environments and clusters relate, see [environments](/components/concepts/environments.md). ## Permissions Your role determines which environments you can see and what you can do with them across Camunda Hub, for example on the organization's **Environments** page, in workspaces, and projects: | Role | What you can do | | :-------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Organization owner or admin | View all environments, assign environments to workspaces, and resume a paused environment. | | DevOps or legacy Developer | View all environments, open the cluster pages, and resume a paused environment. You can assign environments to a workspace where you are an editor or workspace admin. | | Any other organization user | At the organization level, view all environments and open their applications. You see which workspaces an environment is assigned to only for workspaces where you are an editor or workspace admin. You can't assign environments, and the option to resume a paused environment isn't available to you. Projects can use only the environments assigned to their workspace. | ## Environment details Camunda Hub provides details for each environment. Select an environment on the **Environments** page to open them. | Detail | Description | | :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- | | Name | The [name of the environment](/components/concepts/environments.md#how-an-environment-maps-to-infrastructure). | | Status | The [status](#environment-statuses) of the environment. | | Tag | The tag of the backing cluster, which represents the lifecycle phase of the environment: `dev`, `test`, `stage`, or `prod`. | | Version | The Camunda version that the environment is running. | | Region | The region of the backing cluster. | | Cluster ID | The ID of the backing cluster. | | REST API | The REST API address of the environment. | | Swagger UI | The Swagger UI address of the environment, if Swagger UI is enabled for the backing cluster. | | Workspaces | The workspaces the environment is assigned to. Organization owners and admins see **Unassigned** if the environment isn't assigned to any workspace. | ### Environment statuses The status of an environment reflects the state of its cluster. The status updates automatically while the cluster changes state, and settles on the health of the cluster when the change completes. | Status | Description | | :---------- | :------------------------------------------------------------------------------------- | | Healthy | The environment is running. | | Unhealthy | The environment reports a problem. | | Unknown | Camunda Hub can't determine the status. | | Creating | The cluster is being created. | | Updating | The cluster is being updated. | | Unavailable | The cluster is under maintenance or waiting for input. | | Paused | The cluster is paused. You can [resume](#resume-a-paused-environment) the environment. | | Resuming | The cluster is starting after being resumed. | #### How environment statuses follow the cluster The status of an environment comes from the state of the cluster behind it. This table shows the status that each cluster state produces, and whether you can deploy: | Cluster state | Environment status | Deploying | | :------------------------------------------------- | :----------------- | :----------------------------------------------------- | | Healthy | Healthy | Allowed. | | Unhealthy | Unhealthy | Allowed, with a **Deployment may fail** warning. | | Missing (its backing resources are gone) | Unhealthy | Allowed, with a **Deployment may fail** warning. | | Paused | Paused | Blocked until you resume the environment. | | Resuming | Resuming | Blocked until the environment is healthy. | | Creating | Creating | Blocked, with **Environment is being created**. | | Updating | Updating | Allowed, with a **Deployment may fail** warning. | | Maintenance | Unavailable | Blocked, until the maintenance is finished. | | Waiting for input | Unavailable | Blocked, until the cluster has the input it waits for. | | No state, or a state Camunda Hub doesn't recognize | Unknown | Not blocked. | A cluster in the **Maintenance** or **Waiting for input** state still exists, but its environment shows **Unavailable**. To see the state of the cluster, open it in [Manage clusters](../manage-clusters/index.md). The status of an environment reads the same wherever it appears: on the **Environments** page, in the environment details, in the environments of a workspace, on the cluster page, in the deploy dialog, and in the runtime connection of the modeler. For how deploying follows the status, see [deploy a project](/components/hub/workspace/manage-projects/deploy-project.md#environment-status). If Console stops reporting a cluster, its environment stays listed from the last known snapshot, with the status **Unknown** and the mark **Not reported**. You can't deploy to it. The Camunda Hub API reports only the health of an environment: `HEALTHY`, `UNHEALTHY`, `PAUSED`, `RESUMING`, and `UNKNOWN`. An environment that is being created, is being updated, is under maintenance, or is waiting for input reads `UNKNOWN` there. The **Creating**, **Updating**, and **Unavailable** statuses appear only in the Camunda Hub interface. #### Resume a paused environment Organization owners, admins, DevOps users, and legacy Developers can resume a paused environment wherever Camunda Hub shows its status, for example on the **Environments** page, in the environment details, or when you select an environment to deploy to. The status changes to **Resuming** while the cluster starts, and then to **Healthy**. ## Search and filter environments To find relevant environments, go to the **Environments** page of your Hub organization. Use the toolbar to narrow down the list: - **Search environments**: Search by the name of an environment or the name of its cluster. - **Status**, **Version**, and **Tag**: Show only the environments that match the selected values. Each filter lists only the values that exist in your organization. ## Environment applications Each environment has its own instances of the Camunda applications. Open an application from the details of the environment. | Application | Description | | :---------- | :---------------------------------------------------------------------------------------------------------------------------------- | | Operate | Monitor and troubleshoot the process instances of the environment. See [Operate](/components/operate/operate-introduction.md). | | Tasklist | Work on the user tasks of the environment. See [Tasklist](/components/tasklist/introduction-to-tasklist.md). | | Admin | Manage authentication, authorization, and administration for the environment. See [Admin](/components/admin/admin-introduction.md). | | Optimize | Analyze and improve your processes. See [Optimize](/components/optimize/what-is-optimize.md). | ## Jobs The details of an environment summarize its jobs for the last 24 hours: the number of jobs that were created, completed, and not completed. For more information, see the [job dashboard](../analyze-operations/job-dashboard.md). ## Workspaces assigned to an environment The details of an environment list the workspaces that the environment is assigned to. Organization owners and admins can open a workspace from this list. An environment that isn't assigned to any workspace is shown as **Unassigned**. To change the assignment, see [assign environments to a workspace](./assign-environments.md). ## Add a new environment In SaaS, you add an environment by creating a cluster. Every SaaS cluster has one environment, which Camunda Hub creates automatically when the cluster is created. 1. [Create a cluster](../../../saas/clusters/create-cluster.md). The environment of the cluster appears on the **Environments** page. 1. [Assign the environment to a workspace](./assign-environments.md) so that teams can deploy to it. ## Permissions {#permissions-self-managed} Your role determines which environments you can see and what you can do with them across Camunda Hub, for example on the organization's **Environments** page, in workspaces, and projects: | Role | What you can do | | :--------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | | Organization admin | View all environments, and assign environments to workspaces. | | DevOps | View all environments, and open the cluster pages. You can assign environments to a workspace where you are an editor or workspace admin. | | Editor or admin in a workspace | View the environments assigned to the workspaces where you are an editor or workspace admin. | | Viewer or commenter in a workspace | No access to environments. | ## Environment details {#environment-details-self-managed} Camunda Hub provides details for each environment. Select an environment on the **Environments** page to open them. | Detail | Description | | :----------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- | | Name | The [name of the environment](/components/concepts/environments.md#how-an-environment-maps-to-infrastructure). | | Status | The [status](#environment-statuses-self-managed) of the environment. | | Tags | The tags of the backing cluster, for example `dev` or `prod`. | | Version | The Camunda version that the environment is running. | | Cluster ID | The ID of the backing cluster. | | Physical tenant ID | The ID of the Physical Tenant, if the environment is backed by one. | | Workspaces | The workspaces the environment is assigned to. Organization owners and admins see **Unassigned** if the environment isn't assigned to any workspace. | ### Environment statuses {#environment-statuses-self-managed} The status of an environment reflects the state of its cluster. Camunda Hub monitors the health of the components of each environment to determine its status. For details on how the status is determined, see the [Camunda Hub configuration](/self-managed/components/hub/configuration/environments.md#environment-status). | Status | Description | | :----------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Healthy | The environment is running. | | Unhealthy | The environment reports a problem. | | Unknown | Camunda Hub can't determine the status, for example because a component doesn't respond. | | Not reported | The environment is assigned to a workspace, but its cluster or Physical Tenant is no longer in the Camunda Hub configuration. It shows no live data, and you can't deploy to it. | ## Search and filter environments {#search-and-filter-environments-self-managed} To find relevant environments, go to the **Environments** page of your Hub organization. Use the toolbar to narrow down the list: - **Search environments**: Search by the name of an environment or the name of its cluster. - **Status**, **Version**, and **Tag**: Show only the environments that match the selected values. Each filter lists only the values that exist in your organization. ## Environment applications {#environment-applications-self-managed} An environment can have its own instances of the Camunda applications. Open an application from the details of the environment. | Application | Description | | :---------- | :---------------------------------------------------------------------------------------------------------------------------------- | | Operate | Monitor and troubleshoot the process instances of the environment. See [Operate](/components/operate/operate-introduction.md). | | Tasklist | Work on the user tasks of the environment. See [Tasklist](/components/tasklist/introduction-to-tasklist.md). | | Admin | Manage authentication, authorization, and administration for the environment. See [Admin](/components/admin/admin-introduction.md). | | Optimize | Analyze and improve your processes, if Optimize is configured. See [Optimize](/components/optimize/what-is-optimize.md). | An application is unavailable for an environment if it isn't configured. ## Jobs {#jobs-self-managed} The details of an environment summarize its jobs for the last 24 hours: the number of jobs that were created, completed, and not completed. Select **View all job types** to see the jobs by type. For more information, see the [job dashboard](../analyze-operations/job-dashboard.md). ## Workspaces assigned to an environment {#workspaces-assigned-to-an-environment-self-managed} The details of an environment list the workspaces that the environment is assigned to. Organization owners and admins can open a workspace from this list. An environment that isn't assigned to any workspace is shown as **Unassigned**. To change the assignment, see [assign environments to a workspace](./assign-environments.md). ## Add a new environment {#add-a-new-environment-self-managed} In Self-Managed, you add an environment by adding a cluster or a [Physical Tenant](/self-managed/concepts/multi-tenancy/physical-tenants.md) to the Camunda Hub configuration. You provision clusters outside Camunda Hub. A cluster has an environment for its `default` Physical Tenant, and, on Camunda 8.10 and later, one for each additional Physical Tenant you declare. 1. Provision the cluster with your platform tooling. 1. Add the cluster to the Camunda Hub configuration, and declare any additional Physical Tenants. See [Physical Tenants in the Camunda Hub configuration](/self-managed/components/hub/configuration/environments.md#physical-tenants). 1. Perform a rolling restart of Camunda Hub. Camunda Hub reads the configuration at startup. After the restart, the environments appear on the **Environments** page. 1. [Assign the environments to a workspace](./assign-environments.md) so that teams can deploy to them. ## Next steps - [Assign environments to a workspace](./assign-environments.md). - [Manage clusters](../manage-clusters/index.md). - Learn how to [deploy a project](/components/hub/workspace/manage-projects/deploy-project.md). --- ## Manage organization An organization is the top-level entity in Camunda Hub. It holds your [workspaces](/components/concepts/workspaces.md), [environments](/components/concepts/environments.md), [clusters](/components/concepts/clusters.md), and users. ## What you manage in an organization - [Users and roles](../users-and-roles.md): Decide who can use Camunda Hub and what they can do. - [Workspaces](../manage-workspaces/index.md): Create workspaces and manage their members. - [Environments](../manage-environments/index.md): See the environments of your organization and assign them to workspaces. - [Clusters](../manage-clusters/index.md): Create, monitor, and maintain the clusters that host your environments. - [Credentials](../credentials/index.md): Create reusable credentials for connectors and other element templates. - [Catalog](../manage-catalog/index.md): Manage reusable automation assets and publish them to Camunda Hub. ## Organization settings How you manage the settings of an organization depends on the edition of Camunda that you use. In the left navigation, click **Organization > Manage organization**. The **Overview** tab provides a summary of the organization, including the organization name and the pricing plan. In other tabs, you can: - [Manage users](/components/saas/organization/create-manage-users.md) - [Manage groups](/components/saas/organization/manage-user-groups.md) - [View activity](/components/saas/organization/view-organization-activity.md) - [View usage](/components/saas/organization/usage-history.md) - Grant API access credentials - Manage organization settings Under the **Settings** tab, you can: - Leave the organization - [Enable alpha features](/components/saas/organization/enable-alpha-features.md) If you are the owner of the organization, you can change the organization name. For all tasks that are specific to SaaS organizations, such as ownership, usage, plans, and billing, see [Organization in SaaS](/components/saas/organization/index.md). Self-Managed has no organization settings page. You manage the users, groups, and roles of your organization in your identity provider and in Management Identity. See [manage users and roles](../users-and-roles.md). --- ## Manage workspaces Create and manage workspaces within your organization. ## About workspaces In Camunda Hub, a workspace is a collaboration space within an organization, representing a team or business domain. A workspace is assigned members, projects, and environments so all related work happens in one shared space. You can create and manage workspaces at the organization level. :::info You can only manage workspaces at the organization level if you're an **Organization admin** or **Organization owner**. ::: ## View existing workspaces To view existing workspaces, click **Workspaces** in the left navigation. ## Manage a workspace --- ## Manage workspace members Manage workspace members. ## About A workspace has members who manage the workspace or work on workspace resources. Workspace members can be managed at the organization or [workspace](../../workspace/manage-workspace/index.md) level. ## Workspace roles Workspace members are assigned one of the following roles: | Role | Access permissions | | :------------------ | :---------------------------------------------------------------------------------------------------------------------------- | | **Workspace Admin** | The user can edit the workspace itself, all folders, and diagrams within the workspace, and invite more users to collaborate. | | **Editor** | The user can edit all folders and diagrams within the workspace. | | **Commenter** | The user cannot edit folders or diagrams or invite users, but can view diagrams and properties and leave comments. | | **Viewer** | The user cannot edit folders or diagrams nor leave comments, but can only view diagrams. | :::note Additionally, users with the **Organization Owner** or **Organization Admin** role always have access to every workspace in the organization, even if they aren't a member. ::: ## Add members Add members to grant access to workspace resources: 1. In Camunda Hub, navigate to **Workspaces**. 2. Find the workspace, and click **Manage**. 3. Under **Members**, click **Add members**. 4. Provide names or email addresses. **(SaaS only)** Alternatively, click the email address input field, and select **All users in the organization**. 5. Select the workspace role, and optionally provide an invitation message. 6. Click **Add**. The members will be added to the workspace and notified via email. Users without email addresses will not receive any kind of notification about workspace invitations. :::note If the individual is not a user in your organization, they will first receive an organization invitation. After accepting the invitation and logging into Camunda Hub, they will be added to the workspace. They will have a "pending" label in the members list until they accept. ::: For Self-Managed non-production installations, the number of members per workspace is [limited to **five**](/reference/licenses.md#web-modeler), including the workspace administrator. :::tip In Self-Managed, you can [hide the **Add members** button](/self-managed/components/hub/configuration/properties.md#feature-flags) for non-organization admins. ::: ## Edit role Edit members to change access privileges to workspace resources: 1. In Camunda Hub, navigate to **Workspaces**. 2. Find the workspace, and click **Manage**. 3. Under **Members**, find the member. 4. In the menu on the right side of the row, click **Edit role**. 5. Under **Workspace role**, select and apply a role. ## Remove members Remove members to deny access to workspace resources: 1. In Camunda Hub, navigate to **Workspaces**. 2. Find the workspace, and click **Manage**. 3. Under **Members**, find the member. 4. In the menu on the right side of the row, click **Remove**. ## Further reading - [Camunda Hub Member API](/apis-tools/hub-api-saas/specifications/add-member.api.mdx) --- ## Manage workspace Create a workspace, update a workspace's information, or delete a workspace. ## About In Camunda Hub, a workspace is a collaboration space within an organization, representing a team or business domain. It groups organizational resources like members, projects, and environments so related work happens in one shared space. :::tip You can also [manage a workspace from within the workspace](../../workspace/manage-workspace/index.md) itself. ::: ## Permissions You can only manage workspaces at the organization level if you're an **Organization admin** or **Organization owner**. ## Create a workspace Create a workspace, invite members, and assign environments: 1. In Camunda Hub, navigate to **Workspaces**. 2. Click **Create workspace**. 3. Under **General**, give the new workspace a name and description, then click **Next**. 4. Under **Members**, [manage the workspace's members](./manage-workspace-members.md), then click **Next**. 5. Under **Environments**, [assign environments](../manage-environments/assign-environments.md) to the workspace, then click **Create workspace**. Only organization owners and admins see this step. You can also assign environments later. ## Assign environments Choose the environments that the projects of the workspace can deploy to. See [assign environments to a workspace](../manage-environments/assign-environments.md). ## Update workspace information Update the workspace name and description: 1. In Camunda Hub, under your workspace, navigate to **Settings**. 2. Under **General > Workspace information**, update the workspace name and description. 3. Click **Update.** ## Delete a workspace Soft delete a workspace and its resources: 1. In Camunda Hub, navigate to **Workspaces**. 2. Find the workspace, and click **Manage**. 3. Under **General > Danger Zone > Delete workspace**, click **Delete**. Your workspace is moved to [**Recently deleted**](../../workspace/manage-projects/recently-deleted.md). It will be permanently deleted after the retention period. ## Further reading - [Camunda Hub Workspace API](/apis-tools/hub-api-saas/specifications/create-workspace.api.mdx) - [Manage workspace members](./manage-workspace-members.md) - [Assign environments to a workspace](../manage-environments/assign-environments.md) --- ## Manage users and roles Camunda Hub controls access to the workspaces, projects, environments, and clusters of your organization through users and roles. Organization admins assign roles and manage users to decide who can use these resources. Access is controlled on three levels: - **Organization**: Users are assigned organization-level roles. They decide what a user can do in Camunda Hub across the organization. - **Workspace**: Within a workspace, the role of a user decides what they can do with its projects and files. See [workspace roles](./manage-workspaces/manage-workspace-members.md#workspace-roles). - **Runtime environments**: Access to the runtime of an environment, such as Operate, Tasklist, Admin, and Zeebe, is managed separately, with authorizations in [Admin](/components/admin/authorization.md) for each cluster, not by organization-level roles. Where and how you manage users, and which roles are available, depends on the edition of Camunda that you use. Select your edition to see the details. ## Manage users You invite and manage users and groups in Camunda Hub, on the **Organization > Manage organization** page. This is where you configure organization access. The Organization Owner has all rights in the organization. Use [roles](#roles-and-permissions) to control access to Camunda Hub. - [Create and manage users](/components/saas/organization/create-manage-users.md) - [Manage user groups](/components/saas/organization/manage-user-groups.md) - [Organization ownership](/components/saas/organization/organization-ownership.md) Users are invited to a Camunda 8 organization via their email address, which must be accepted by the user. The user remains in the `Pending` state until the invitation is accepted. People who do not yet have a Camunda 8 account can also be invited to an organization. To access the organization, the invited individual must first create a Camunda 8 account by following the instructions in the invitation email. ## Groups (legacy) Groups in Camunda Hub are a legacy feature for organizing users in SaaS. They apply only to clusters on version 8.7 or earlier, and they don't control access to Camunda Hub. To manage groups, click **Organization > Manage organization**, and then open the **Groups** tab. See [manage user groups](/components/saas/organization/manage-user-groups.md). For clusters on version 8.8 or later, manage [groups](/components/admin/group.md) and authorizations in Admin. ## Roles and permissions Users are assigned organization-level roles. The following table shows what each role can do. | Role | Organization | Workspaces and projects | Clusters | Environments | Catalog | Optimize and business value | | :----------------- | :----------- | :---------------------- | :------- | :----------- | :-------- | :-------------------------- | | Organization Owner | Full access | Manage | Manage | Manage | Manage | Yes | | Organization Admin | Manage | Manage | Manage | Manage | Manage | Yes | | DevOps | None | Create and collaborate | Manage | Manage | Read-only | No | | Analyst | Read-only | Create and collaborate | None | View all | Manage | Yes | | Member | Read-only | Create and collaborate | None | View all | Read-only | No | - **Organization Owner**: All rights in the organization, including settings, billing, and ownership transfer. Reserved for a single user per organization; transferred rather than assigned or removed like other roles. - **Organization Admin**: Manages the organization, its members, and its workspaces, with full access to every workspace and project by default. No separate mode needs to be enabled. - **DevOps**: A specialized role for infrastructure management, not people management. Grants cluster create and update, cluster clients, connector secrets, IP allowlisting, secure connectivity, encryption, and the connector-management view, plus Member-level modeling. Cannot manage or view organization members, billing, or organization settings. - **Analyst**: Includes everything a Member can do, plus full access to Optimize to build process dashboards and reports. Access to specific dashboards and reports within Optimize is governed separately by [Optimize collection roles](/components/optimize/userguide/user-permissions.md). - **Member**: Full access to create and collaborate on workspaces and projects, plus read-only visibility into the organization. ### Additional roles Beyond the roles above, an organization may show a few legacy roles depending on its history. They aren't recommended with Camunda Hub: - **Developer** _(deprecated)_: No longer offered for new assignment. Existing holders keep their current permissions unchanged; they are not automatically moved to another role. - **Task user** and **Visitor** _(legacy)_: Available only for organizations with at least one cluster on version 8.7 or older, alongside a user's organization-level roles. They govern access to the older cluster apps and disappear once no such clusters remain. ## Access to organization resources The organization-level roles of a user set the level of access to each of the following resources. The table above lists the level for each role. This section describes what each level means. ### Workspace and project access - **Manage**: Full access to every workspace and project in the organization. Organization Owner and Organization Admin have this level. See [elevated workspace access](#elevated-workspace-access). - **Create and collaborate**: Create and collaborate on workspaces and projects. DevOps, Analyst, and Member have this level. What a user can do inside a workspace also depends on their [workspace role](./manage-workspaces/manage-workspace-members.md#workspace-roles). ### Elevated workspace access Organization admins and owners always have **Workspace Admin** access to every workspace in the organization, including workspaces they aren't explicitly a member of. This access is on by default and can't be changed. The main purpose of this access is to assign members to workspaces that have no members. Ordinarily, these workspaces would not be accessible or visible to any other users. The user must be assigned the organization **Organization Owner** or **Organization Admin** role. ### Catalog access - **Manage**: Browse and use catalog items, and also see usage statistics and adoption data. Analyst, Organization Admin, and Organization Owner have this level. - **Read-only**: Browse and use catalog items. Member and DevOps have this level. ### Business value access Access to Optimize includes access to the [business value dashboard](/components/hub/organization/analyze-operations/business-value-dashboard.md), where users view metrics and set targets. The same roles that grant access to Optimize also grant access to business value: Analyst, Organization Admin, and Organization Owner. ### Cluster visibility - **Manage**: Create and update clusters, and manage cluster clients, connector secrets, IP allowlists, secure connectivity, and encryption. Organization Owner, Organization Admin, and DevOps have this level. - **None**: No access to the cluster pages. Analyst and Member have this level. ### Environment visibility - **Manage**: View all environments and resume paused ones. Organization Owner, Organization Admin, and DevOps have this level. Organization Owner and Organization Admin can assign environments to any workspace. DevOps can assign environments to workspaces where the user is an editor or a workspace admin. - **View all**: See every environment in the organization, and open its applications, such as Operate and Tasklist. The user can't assign or add environments, doesn't get the option to resume a paused environment, and sees the workspace assignments only of workspaces where they are an editor or a workspace admin. Analyst and Member have this level. ## Access to runtime environments Organization-level roles control what a user can do in Camunda Hub. They don't control what the user can do in the runtime of an environment, which includes Operate, Tasklist, Admin, and Zeebe. Starting with version 8.8, access to the runtime is managed independently of organization-level roles. To control what a user can access there, define their authorizations in [Admin](/components/admin/authorization.md). If authorizations are disabled in the runtime, the user has full access to the runtime and its components. For clusters before version 8.8, access is controlled with resource-based authorizations and the legacy **Task user** and **Visitor** roles. See [manage resource-based authorizations](/components/saas/organization/resource-based-auth.md). ## Manage users {#manage-users} You create users in your identity provider, and manage users, groups, and roles in Management Identity. This is where you configure organization access for Camunda Hub and Optimize. It doesn't grant access to deployment environments. - [Management Identity user, group, and role management](/self-managed/components/management-identity/application-user-group-role-management/identity-application-user-group-role-management-overview.md) - [Manage groups](/self-managed/components/management-identity/application-user-group-role-management/manage-groups.md) - [Manage roles](/self-managed/components/management-identity/application-user-group-role-management/manage-roles.md) ## Roles and permissions {#roles-and-permissions} Users are assigned organization-level roles. The following table shows what each role can do. | Role | Organization | Workspaces and projects | Clusters | Environments | Catalog | Optimize and business value | | :----------------- | :----------- | :---------------------- | :------- | :------------ | :-------- | :-------------------------- | | Organization Admin | Manage | Manage | Manage | Manage | Manage | Yes | | DevOps | None | Create and collaborate | Manage | Manage | Read-only | No | | Analyst | Read-only | Create and collaborate | None | Assigned only | Manage | Yes | | Member | Read-only | Create and collaborate | None | Assigned only | Read-only | No | - **Organization Admin**: Manages the organization, its members, and its workspaces, with full access to every workspace and project by default. No separate mode needs to be enabled. - **DevOps**: A specialized role for infrastructure management, not people management. Grants the ability to view all clusters and their details, plus Member-level modeling. Cannot manage or view organization members or organization settings. - **Analyst**: Includes everything a Member can do, plus full access to Optimize to build process dashboards and reports. Access to specific dashboards and reports within Optimize is governed separately by [Optimize collection roles](/components/optimize/userguide/user-permissions.md). - **Member**: Full access to create and collaborate on workspaces and projects, plus read-only visibility into the organization. ### Management Identity roles In Self-Managed, you assign the organization-level roles by assigning roles in Management Identity. The following table shows which Management Identity role gives the access of each organization role. | Organization role | Management Identity role | Previous name | | :----------------- | :----------------------- | :---------------- | | Organization Admin | Hub Admin | Web Modeler Admin | | Member | Hub | Web Modeler | | DevOps | DevOps | Console | | Analyst | Analyst | – | The roles listed under **Previous name** still exist and grant the same permissions as the new roles. They are kept for backward compatibility, so existing assignments keep working. See [default roles](/self-managed/components/management-identity/application-user-group-role-management/manage-roles.md#default-roles). ## Access to organization resources {#access-to-organization-resources} The organization-level roles of a user set the level of access to each of the following resources. The table above lists the level for each role. This section describes what each level means. ### Workspace and project access {#workspace-and-project-access} - **Manage**: Full access to every workspace and project in the organization. Organization Admin has this level. See [elevated workspace access](#elevated-workspace-access). - **Create and collaborate**: Create and collaborate on workspaces and projects. DevOps, Analyst, and Member have this level. What a user can do inside a workspace also depends on their [workspace role](./manage-workspaces/manage-workspace-members.md#workspace-roles). ### Elevated workspace access {#elevated-workspace-access} Organization admins always have **Workspace Admin** access to every workspace in the organization, including workspaces they aren't explicitly a member of. This access is on by default and can't be changed. The main purpose of this access is to assign members to workspaces that have no members. Ordinarily, these workspaces would not be accessible or visible to any other users. The user must be assigned the **Hub Admin** role. If the role is not pre-existing, it can be created with the following permissions: - Hub Internal API - `write:*` - Hub Internal API - `admin:*` - Camunda Identity Resource Server - `read:users` Refer to the documentation pages about [assigning roles](/self-managed/components/management-identity/application-user-group-role-management/manage-roles.md) and [adding permissions](/self-managed/components/management-identity/access-management/access-management-overview.md) for detailed instructions. ### Catalog access {#catalog-access} - **Manage**: Browse and use catalog items, and also see usage statistics and adoption data. Analyst and Organization Admin have this level. - **Read-only**: Browse and use catalog items. Member and DevOps have this level. ### Business value access {#business-value-access} Access to Optimize includes access to the [business value dashboard](/components/hub/organization/analyze-operations/business-value-dashboard.md), where users view metrics and set targets. The same roles that grant access to Optimize also grant access to business value: Analyst and Organization Admin. ### Cluster visibility {#cluster-visibility} - **Manage**: View all clusters and their details. In Self-Managed, you provision clusters outside Camunda Hub, and Camunda Hub is read-only for clusters. Organization Admin and DevOps have this level. - **None**: No access to the cluster pages. Analyst and Member have this level. ### Environment visibility {#environment-visibility} - **Manage**: View all environments. Organization Admin and DevOps have this level. Organization Admin can assign environments to any workspace. DevOps can assign environments to workspaces where the user is an editor or a workspace admin. - **Assigned only**: See only the environments assigned to the workspaces where the user is an editor or a workspace admin. Analyst and Member have this level. ## Access to runtime environments {#access-to-runtime-environments} Organization-level roles control what a user can do in Camunda Hub. They don't control what the user can do in the runtime of an environment, which includes Operate, Tasklist, Admin, and Zeebe. Starting with version 8.8, access to the runtime is managed independently of organization-level roles. To control what a user can access there, define their authorizations in [Admin](/components/admin/authorization.md). If authorizations are disabled in the runtime, the user has full access to the runtime and its components. Authorizations for the runtime applications (Zeebe, Operate, and Tasklist) are managed as part of the Orchestration Cluster and configured in Admin. See the [Admin overview](/self-managed/components/orchestration-cluster/admin/overview.md). --- ## Workspace Discover and use approved reusable assets, manage projects, and deliver business processes. ## About the workspace With Camunda Hub, you'll collaborate in managed workspaces, discover and use approved catalog assets, and model and deploy business processes. ## Manage projects Develop project releases through the stages of a typical development lifecycle: [Get started](./manage-projects/manage-projects.md) ## Manage workspace settings Manage workspace members, environments, and general information: [Get started](./manage-workspace/index.md) ## Model business processes Collaboratively design executable processes as the foundation for scalable IT and business automation: [Get started](./modeler/index.md) --- ## Set up a new project Create a project in a workspace. The project can deploy to every environment assigned to the workspace. ## Prerequisites To set up a new project, you first need a [workspace](../../organization/manage-workspaces/manage-workspace.md). ## Create a project Create a project to work on a set of related files: 1. In your workspace, click **Create project**. 2. Provide a project name, and click **Create project**. ## Deployment environments A project doesn't have its own deployment targets. It can deploy to all the [environments](/components/concepts/environments.md) that are assigned to its workspace, and it always reflects changes to that set. - To see the environments you can deploy to, open the [deploy dialog](./deploy-project.md#deploy-your-project) of the project. - If no environment is assigned, an organization admin must [assign environments to the workspace](../../organization/manage-environments/assign-environments.md). ## Next steps You've set up a new project. From here, you can: - [Model your first diagram](../modeler/modeling/model-your-first-diagram.md) - [Write a project README](../modeler/modeling/advanced-modeling/process-documentation-with-readme-files.md) - [Sync with a remote repository](./git-sync.md#sync-with-remote-repository) --- ## Deploy your project Deploy your project to an environment assigned to your workspace, for example a testing, staging, or production environment. ## Deployment environments You deploy a project to an [environment](/components/concepts/environments.md), not to a cluster. The deploy dialog lists the environments that are assigned to the workspace of the project. Each entry shows the following details: | Detail | Description | | :------ | :-------------------------------------------------------------------------------------------------------------------- | | Name | The [name of the environment](/components/concepts/environments.md#how-an-environment-maps-to-infrastructure). | | Cluster | The cluster that hosts the environment. Camunda Hub shows the cluster only if it's needed to tell environments apart. | | Version | The Camunda version of the cluster, for example **Camunda 8.9**. | | Tags | The tags of the cluster, for example `dev`, `test`, `stage`, or `prod`. | | Status | The [status](#environment-status) of the environment. | An organization admin decides which environments a workspace can use. Camunda Hub doesn't offer any other targets, and it doesn't select the next environment for you when you promote a project. You choose the environment you want to deploy to. ### Prerequisites Before you deploy a project: - An organization admin has [assigned at least one environment to the workspace](../../organization/manage-environments/assign-environments.md). - You're a **Workspace Admin** or **Editor** in the workspace. - You have permission to deploy in the target environment. If the cluster or its Physical Tenant has [authorizations](/components/admin/authorization.md) enabled, ensure you have the [`CREATE` permission to the `RESOURCE` resource type](/components/admin/authorization.md#create-an-authorization-in-admin). Camunda Hub doesn't check your permissions before you deploy. The cluster decides whether you can deploy, and Camunda Hub shows you the result. ## Deploy your project Once you've [validated your process](./validate-project.md), deploy your project to an environment in your [development lifecycle](./manage-projects.md#project-development-lifecycle), such as testing, staging, or production. For example, deploy to your testing environment to run automated tests or make it available for testing. 1. In your workspace, open a project. 1. At the top right of the project view, click the **Deploy & run** combo button, and select **Deploy latest changes**. This opens the **Deploy project** dialog. 1. Under **Deployment environment**, select the environment to deploy to. Camunda Hub preselects the first one. 1. If the environment has [Logical Tenants](/self-managed/concepts/multi-tenancy/logical-tenants.md), select one under **Logical tenant**. In Self-Managed, you can enter a **Logical tenant ID**, which is optional. 1. In Self-Managed, if the cluster uses Basic authentication, enter your **Username** and **Password** under **Authentication**. 1. Click **Deploy** to deploy the project to the selected environment. When you deploy from the project homepage, all BPMN, DMN, and form files in the project are deployed as a single bundle. Camunda Hub confirms a successful deployment with **Project deployed!** :::note If any resource fails to deploy, the whole deployment [fails](#deployment-errors) and the environment state remains unchanged. This safely ensures that a project cannot be deployed incompletely or in an inconsistent state. ::: :::tip If you don't want to deploy all resources in a project, you can [deploy an individual resource](../modeler/run-or-publish-your-process.md#deploy-a-process). ::: ### Logical Tenants If multi-tenancy is enabled, provide a [Logical Tenant](/self-managed/concepts/multi-tenancy/logical-tenants.md) for the target environment. If the environment has more than one Logical Tenant, choose the one to deploy to. If it has exactly one, Camunda Hub selects it automatically. On SaaS, a cluster that doesn't support tenants doesn't need one. ### Environment status The status of an environment decides whether you can deploy to it: | Status | What it means for deployment | | :---------- | :-------------------------------------------------------------------------------------------- | | Healthy | You can deploy. | | Unhealthy | You can deploy, but the deployment may fail because the environment is unhealthy. | | Updating | You can deploy, but the deployment may fail because the environment is updating. | | Paused | You can't deploy until the environment is resumed. Resume the environment first. | | Resuming | You can't deploy. Wait until the environment is healthy. | | Creating | You can't deploy. Wait until the environment is ready. | | Unavailable | You can't deploy, for example because the environment is temporarily unavailable. | | Unknown | You can deploy, but the deployment may fail because the status of the environment is unknown. | For how the status follows the state of the cluster, see [environment statuses](/components/hub/organization/manage-environments/index.md#how-environment-statuses-follow-the-cluster). To resume a paused environment, click **Resume** next to it. Only organization owners, admins, DevOps users, and legacy Developers see this button. You can also [resume the environment from the environments page](../../organization/manage-environments/index.md#resume-a-paused-environment). | Status | What it means for deployment | | :----------- | :----------------------------------------------------------------------------------------------------------------- | | Healthy | You can deploy. | | Unhealthy | You can deploy, but the deployment may fail because the environment is unhealthy. | | Unknown | You can deploy, but the deployment may fail because the status of the environment is unknown. | | Not reported | You can't deploy. The cluster or Physical Tenant of the environment is no longer in the Camunda Hub configuration. | A Self-Managed environment reports only its health, so it is never paused, resuming, creating, updating, or unavailable. For more information, see [environment statuses](/components/hub/organization/manage-environments/index.md#environment-statuses-self-managed). ### No environment available If no environment is assigned to the workspace, the dialog shows **No deployment environments available**. An organization admin must assign an environment to the workspace. If you can manage the environments of the workspace, click **Manage workspace environments** in the dialog to open the workspace settings. ### Production environments Camunda Hub treats an environment as a production environment if its tags include `prod`. Your organization can require that a project snapshot is approved before anyone deploys it to a production environment. When this is enabled, and you select a production environment, the dialog shows one of the following messages: | Message | Meaning | | :-------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Approval required** | The project snapshot must be approved by a reviewer before it can be deployed to a production environment. | | **Snapshot required** | Drafts can't be deployed to a production environment. Create a project snapshot, get it approved, and deploy it. Click **Show snapshots** to open the snapshots of the project. | Organization admins configure this in the [project deployment settings](/components/hub/workspace/modeler/modeler-settings.md#project-deployment). Learn how to [request a review](./project-versioning.md#request-a-review). ## Run your project You can manually [run](/components/hub/workspace/modeler/run-or-publish-your-process.md#run-a-process) your project to test it after it has been deployed to an environment. :::note Use [Test mode](/components/hub/workspace/modeler/validation/test-your-process.md) to validate and debug your project against any environment assigned to your workspace. Use Run to execute a full process instance of your already-deployed project, for example to exercise your real job workers and APIs on a testing, staging, or production environment. ::: To run your project: 1. In your workspace, open a project. 1. At the top right of the project view, click **Deploy & run** to open the **Deploy & run** modal. 1. Select the process for which you want to start a new instance in **Process to run**. 1. Select **Deploy & run** to start a new instance. - Before the process instance starts, all resources are redeployed if required so the new instance uses their latest state. - After the process instance starts, you will receive an **Instance started!** notification. Click **View process instance** to open the process instance in the [Operate](/components/operate/operate-introduction.md) of the environment, and monitor it. You can also open the **Deploy & run** modal from the details page of any BPMN file in the project. In that case, the current process is run and the modal includes an additional option to select the resources to deploy. If the target environment has [authorizations](/components/admin/authorization.md) enabled, make sure you have the following permissions to be able to view the process instance in Operate: | Resource type | Permission | | :------------------- | :---------------------------------------------------- | | `PROCESS_DEFINITION` | `READ_PROCESS_DEFINITION` and `READ_PROCESS_INSTANCE` | | `COMPONENT` | `operate` | ## Deployment errors If the deployment of a project fails (for example, because one or more of the contained resources has invalid implementation properties), a modal is shown containing the error message thrown by the Zeebe engine. If the cluster rejects the deployment because you lack the permissions to deploy to the environment, the message says so. Contact your organization admin to request them. The message typically provides the name of the affected resource, the ID of the invalid diagram element, and the error details. ### Deployment of external resources You can link BPMN processes, DMN decisions, or forms that are not part of the project itself (external resources) from any process inside a project. When you deploy the project, linked resources located outside the project are _not_ deployed with the project, so you must deploy them separately. ## Next steps - [Run or publish a process](../modeler/run-or-publish-your-process.md) - [Sync your Git repository](./git-sync.md) --- ## Sync your Git repository Organization owners and administrators can connect their Camunda Hub projects to a remote version control repository, allowing users to keep their Camunda Hub and Desktop Modeler projects in sync. Once the connection is configured by an organization owner or administrator, workspace administrators and editors can use the built-in button to pull changes from the remote repository, integrate contributions from Desktop Modeler users, and merge their own work. ## Connect to a remote repository Select your Git repository host: Create a new GitHub App Camunda Hub requires a GitHub App to sync changes with your GitHub repository. Follow the [GitHub documentation](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app) to create a new GitHub App for your organization or account with the following configuration: - Under **Webhooks**, deselect **Active** - Under **Permissions > Repository permissions**, enable **Read and write** for the following options: - Commit statuses - Contents - Pull requests Click **Create GitHub App** to finish. Generate a private key 1. In your new application's setting page, navigate to **General > Private keys**. 2. Select **Generate a private key**. This key is automatically downloaded as a .pem file when created, and can be opened in a text editor to copy and paste the contents into Camunda Hub. Install the GitHub App 1. In your application's setting page, navigate to **Install app**. 2. Click on the **Install** button for your organization or account. 3. Select **Only select repositories**, and choose the repository to sync with Camunda Hub. 4. Once redirected to your application's installation page, copy the **Installation ID** located at the end of the page's URL: `https://github.com/settings/installations/{installation_id}`. Configure GitHub in Camunda Hub 1. Within Camunda Hub, navigate to the project you would like to connect to GitHub, and click **Connect repository**. 2. Select the **GitHub** tile (if not already selected), located at the top of the modal. 3. Provide the following information in the **Configure GitHub** modal: - **Client ID:** Found in your GitHub App's settings page. You can also use Application ID as an alternative. (If you are using GitHub Enterprise Server 3.13 or prior, Application ID is required.) - **Installation ID:** Found in the URL of your GitHub App's installation page. - **GitHub API Base URL:** The base URL of your [GitHub installation's REST API](https://docs.github.com/en/enterprise-server@3.15/rest/enterprise-admin?apiVersion=2022-11-28#endpoint-urls). This is optional and only required for GitHub Enterprise instances. If left empty, Camunda Hub uses the default GitHub Cloud REST API URL (`https://api.github.com`). :::note If you're using a self-hosted GitHub instance, see [Self-Managed Git sync](/self-managed/components/hub/configuration/properties.md#git-sync) for configuration details. Refer to [GitHub documentation](https://docs.github.com/en/enterprise-server@3.15/rest/enterprise-admin?apiVersion=2022-11-28#endpoint-urls) for more information. ::: - **Private Key:** The contents of the .pem file downloaded from your GitHub App's settings page. - **Repository URL:** The base URL of the repository you want to sync with, for example `https://github.com/camunda/example-repo`. The URL cannot contain the `.git` extension or a folder path. - **Branch name:** The branch name to use for merging and managing changes. - **Repository path:** (optional) The path to the folder containing your project files. If left empty, Camunda Hub syncs with the root of the repository. This path is automatically created if it does not exist. 4. Click **Open repository** to test your configuration. The repository for the provided branch and optional path opens in a new tab. 5. Click **Save Configuration**. When successful, your project will display a new **Sync with GitHub** button. ![The Sync with GitHub within Camunda Hub](./img/git-sync.png) Create a new access token Camunda Hub requires an access token to sync changes with your GitLab repository. You can use one of the following options: - **Project access token** (recommended) - Group access token - Personal access token To generate a project access token, follow the [GitLab documentation](https://docs.gitlab.com/ee/user/project/settings/project_access_tokens.html#create-a-project-access-token) and use the following configuration: - Assign the token to a user with the `developer` or `maintainer` role. - Enable the following [**scopes**](https://docs.gitlab.com/ee/user/project/settings/project_access_tokens.html#scopes-for-a-project-access-token): - `api` - `read_api` - `read_repository` - `write_repository` Get the project ID 1. Navigate to your GitLab project. 2. Click the menu icon in the top right corner and select **Copy project ID**. Configure GitLab in Camunda Hub 1. In Camunda Hub, navigate to the project you want to connect to GitLab, and click **Connect repository**. 2. In the modal, select the **GitLab** tile at the top. 3. In the **Configure GitLab** modal, provide the following information: - **Access token:** The project, group, or personal access token you generated. - **Project ID:** The ID copied from your GitLab project settings. - **GitLab API base URL:** The base URL of your [GitLab installation's REST API](https://docs.gitlab.com/api/rest/#make-a-rest-api-request), for example, `https://gitlab.example.com/api/v4`. This is optional and only required for self-hosted GitLab instances. If left empty, Camunda Hub uses the default GitLab Cloud REST API URL (`https://gitlab.com/api/v4`). :::note If you're using a self-hosted GitLab instance, see [Self-Managed Git sync](/self-managed/components/hub/configuration/properties.md#git-sync) for configuration details. ::: - **Repository URL:** The base URL of the repository you want to sync with (e.g., `https://gitlab.com/camunda/example-repo`). The URL must not include the `.git` extension or a folder path. - **Branch:** The name of the branch to use for merging and managing changes. - **Repository path:** (optional) The folder path that contains your project files. If left empty, Camunda Hub syncs with the root of the repository. The path is created automatically if it doesn't exist. 4. Click **Open repository** to test the configuration. The repository for the selected branch and optional path will open in a new browser tab. 5. Click **Save Configuration**. Once connected successfully, your project will display a **Sync with GitLab** button. Register an App in Microsoft Entra :::note Camunda Hub SaaS supports authenticating against `Microsoft Entra ID (global service)`. Other [national clouds](https://learn.microsoft.com/en-us/entra/identity-platform/authentication-national-cloud#microsoft-entra-authentication-endpoints) can be used in Self-Managed by setting the environment variable `CAMUNDA_HUB_GITSYNC_AZURE_AUTHORITY_BASE_PATH`. ::: Camunda Hub requires an application to be registered with Microsoft Entra ID to sync changes with your Azure repository. 1. Follow the [Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app) documentation to register an application. Be sure to save your `Application (client) ID` and `Directory (tenant) ID`. 2. Configure your application to use [client-certificate credentials](https://learn.microsoft.com/en-us/entra/identity-platform/how-to-add-credentials?tabs=certificate). You need a PEM-encoded, [PKCS#8](https://en.wikipedia.org/wiki/PKCS_8) private key and a PEM-encoded certificate in `X509` format generated from that key. You will need both later when configuring the connection in Camunda Hub. 3. Configure [scoped permissions](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-configure-app-access-web-apis) for your app so it can update the content of your Azure repositories. Ensure `Azure DevOps > vso.code_write` is configured, and `Admin consent required` is set to `No`. Grant access to the App in the desired Azure projects Follow the [documentation on how to add users](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/add-organization-users?view=azure-devops&tabs=browser#add-users-to-your-organization) to add the created application to your Azure organization. Ensure the following: - _Access level_ is set to `Basic`. - Add this to all projects that will be using this integration. - _Azure DevOps Groups_ is set to `Project Contributors`. Configure Azure in Camunda Hub :::note If you're using a self-hosted Azure DevOps Server instance, see [Self-Managed Git sync](/self-managed/components/hub/configuration/properties.md#git-sync) for configuration details. ::: 1. Within Camunda Hub, navigate to the project you would like to connect to Azure, and select **Connect repository**. 2. Select the **Azure** tile, located at the top of the modal. 3. Provide the following information in the **Configure Azure** modal: - **Application (client) ID:** Can be found on the applications registration page. - **Directory (tenant) ID:** Your Microsoft Entra tenant unique identifier. Can also be found on the applications registration page. - **Private Key:** The private key used to generate the certificate in PEM format. - **Certificate:** The certificate used to register the application in PEM format. - **Repository URL:** The base URL of the repository you want to sync with, for example `https://dev.azure.com/camunda/my-project/_git/example-repo`. The URL cannot contain the `.git` extension or a folder path. By default, the first repository you create will have the same name as the project and the URL won't explicitly have the project name in it, for example `https://dev.azure.com/camunda/_git/example-repo`. - **Branch:** The name of the branch to use for merging and managing changes. - **Repository path:** (optional) The path to the folder containing your project files. If left empty, Camunda Hub syncs with the root of the repository. This path is automatically created if it does not exist. 4. Click **Open repository** to test your configuration. The repository for the provided branch and optional path opens in a new tab. 5. Click **Save Configuration**. When successful, your project will display a new **Sync with Azure** button. Generating a private key and certificate Follow these steps to generate a private key and self signed certificate that can be used to connect Camunda Hub with your Azure repository: 1. Generate private key Generate a new RSA private key and save it to a file called `private_key.pem`. ```sh openssl genrsa -out private_key.pem 2048 ``` 2. Create a certificate signing request Generate a Certificate Signing Request (CSR) using the key created in step 1. ```sh openssl req -new -key private_key.pem -out cert.csr ``` 3. Create a self-signed certificate Using the private key and the certificate signing request, create a certificate. ```sh openssl x509 -req -days 365 -in cert.csr -signkey private_key.pem -out cert.crt ``` This generates a self-signed certificate named `cert.crt` valid for 365 days. You can now use it with the private key to register an app in Microsoft Entra, and configure your Azure Git sync configuration. Create a new access token Camunda Hub requires an access token to sync changes with your Bitbucket Cloud repository. You can use one of the following token types: - **Repository access token** (recommended) - Project access token - Workspace access token Follow the [Bitbucket documentation](https://support.atlassian.com/bitbucket-cloud/docs/create-a-repository-access-token/) to generate a new repository access token for your repository with the following configuration: - Enable the following [**scopes**](https://support.atlassian.com/bitbucket-cloud/docs/repository-access-token-permissions/) for your token: - `repositories:read` - `repositories:write` Configure Bitbucket Cloud in Camunda Hub 1. In Camunda Hub, navigate to the project you want to connect to Bitbucket Cloud, and click **Connect repository**. 2. Select the **Bitbucket** tile at the top of the modal. 3. Fill in the **Configure Bitbucket** modal with the following information: - **Access token:** The repository, project, or workspace access token you generated. - **Bitbucket API Base URL:** Leave empty. Only required for [Bitbucket Data Center](./git-sync.md?platform=bitbucket-data-center) instances. - **Repository URL:** The base URL of the repository you want to sync with, e.g., `https://bitbucket.org/camunda/example-repo`. The URL must not include the `.git` extension or any folder path. - **Branch name:** The branch to use for merging and managing changes. - **Repository path:** (optional) The folder path containing your project files. If left empty, Camunda Hub syncs with the repository root. This path will be created automatically if it does not exist. 4. Click **Open repository** to test your configuration. The repository for the specified branch and optional path will open in a new tab. 5. Click **Save Configuration**. Once successful, your project will display a new **Sync with Bitbucket** button. :::info Atlassian Data Center End of Life Bitbucket Data Center reaches its end of life on March 28, 2029. See [Atlassian's announcement](https://www.atlassian.com/licensing/data-center-end-of-life). Consider migrating to Bitbucket Cloud or another supported Git provider for continued support and updates. ::: :::warning Limitations Due to [limitations in the Bitbucket Data Center API](https://jira.atlassian.com/browse/BSERV-14381), Camunda Hub cannot push file deletions to Bitbucket Data Center repositories. If you delete, move, or rename files in Camunda Hub, the original will remain in the remote repository after synchronization. ::: Create a new access token Camunda Hub requires a **user** HTTP access token to sync changes with your Bitbucket Data Center repository. Repository or project access tokens are not supported. Follow the [Bitbucket documentation](https://confluence.atlassian.com/bitbucketserver/http-access-tokens-939515499.html#HTTPaccesstokens-CreateHTTPaccesstokens) to generate a new user access token for your repository with the **Repository write** permission. Configure Bitbucket Data Center in Camunda Hub 1. In Camunda Hub, navigate to the project you want to connect to Bitbucket Data Center, and click **Connect repository**. 2. Select the **Bitbucket** tile at the top of the modal. 3. Fill in the **Configure Bitbucket** modal with the following information: - **Access token:** The user access token you generated. - **Bitbucket API Base URL:** The base URL of your [Bitbucket installation's REST API](https://developer.atlassian.com/server/bitbucket/rest/v1000/intro/#structure-of-the-rest-uris), for example `https://bitbucket.example.com/rest/api/latest`. This is required for Bitbucket Data Center instances. If left empty, Camunda Hub uses the default Bitbucket Cloud REST API URL (`https://api.bitbucket.org/2.0/repositories`). :::note If you're using a self-hosted Bitbucket Data Center instance, see [Self-Managed Git sync](/self-managed/components/hub/configuration/properties.md#git-sync) for configuration details. Refer to [Bitbucket documentation](https://developer.atlassian.com/server/bitbucket/rest/v1000/intro/#structure-of-the-rest-uris) for more information. ::: - **Repository URL:** The base URL of the repository you want to sync with, e.g., `https://bitbucket.example.com/projects/camunda/repos/example-repo`. The URL must not include the `.git` extension or any folder path. For personal repositories, use `~{user}` as the project ID (for example, `projects/~alice/repos/example-repo`). - **Branch name:** The branch to use for merging and managing changes. - **Repository path:** (optional) The folder path containing your project files. If left empty, Camunda Hub syncs with the repository root. This path will be created automatically if it does not exist. 4. Click **Open repository** to test your configuration. The repository for the specified branch and optional path will open in a new tab. 5. Click **Save Configuration**. Once successful, your project will display a new **Sync with Bitbucket** button. ## Sync with remote repository Organization owners/administrators, workspace administrators, and editors can sync their version of Camunda Hub with the connected repository at any time. 1. In your connected project, click **Sync with \**. 2. Enter a [snapshot tag](./project-versioning.md#create-a-snapshot) to create a new snapshot for your project. The new snapshot will be created prior to pushing your changes to the central repository. 3. Click **Synchronize**. In the case of a merge conflict, select between your local Camunda Hub changes and the changes in the remote repository to continue. Once the pull is complete and any merge conflicts are resolved, Camunda Hub will push its changes. The newly created snapshot is now accessible via the **View snapshot** button in the success notification. ## Manage existing configurations Existing Git configurations can be edited from the gear icon beside the **Sync with \** button. Permission to update these settings are limited to **Workspace Admins**. ## Change Git provider To switch between Git providers, update your configuration with the following steps: 1. Disconnect your current Git provider by clicking the gear icon beside the **Sync with \** button, and clicking the **Delete provider connection** button at the bottom of the modal. 2. After confirming the operation, open the **Connect repository** modal and provide the necessary information for the new Git provider, following the steps outlined for [GitHub](./git-sync.md?platform=github#connect-to-a-remote-repository), [GitLab](./git-sync.md?platform=gitlab#connect-to-a-remote-repository), [Azure](./git-sync.md?platform=azure#connect-to-a-remote-repository), or [Bitbucket Cloud](./git-sync.md?platform=bitbucket#connect-to-a-remote-repository). ## Advanced use cases Git sync supports a variety of development workflows, including the following advanced use cases. ### Monorepos A monorepo is a single repository containing multiple logical projects that each have disparate workflows and release cadences. To set up Git sync with a monorepo, you can specify the **path** to your project during the configuration. This allows you to keep multiple projects in one repository, each with its own sync configuration. :::note If you are using Git sync to work with monorepos, you should pull changes regularly, as the GitHub API is limited to a fixed amount of files and commits per synchronization action. See [troubleshooting](#troubleshooting) for more information. ::: ### Parallel feature development Git sync supports parallel feature development by allowing multiple projects to be connected to different feature branches. This allows teams to work on multiple features simultaneously without interfering with each other's work. To use Git sync for parallel feature development: 1. Create a new [project](create-a-project.md) in Camunda Hub for each active feature branch you want to develop. 2. Configure Git sync for each instance by connecting it to the corresponding feature branch in your repository. 3. Work on your feature in Hub, using **Sync with \** to pull and push changes as needed. 4. Once the feature is complete and merged into the main branch, you can delete the project associated with the feature branch. To perform hotfixes or patches of production or production-bound processes, sync a copy of the project to the `main` branch. :::caution Creating multiple copies of a project can complicate navigation and deployment if you have multiple files with the same ID in a project. To avoid this, you can create copies of the project in different projects. ::: ## Self-Managed environment variables Refer to [Configuration of the restapi component](/self-managed/components/hub/configuration/properties.md#git-sync) for details on configuring environment variables. ## Troubleshooting ### File and folder names - Duplicate file names of the same file type are not allowed within the same folder. - Duplicate folder names are not allowed within the same parent folder. - Characters with special meaning in Git (for example, `/`), or characters disallowed by Git, are not allowed in either branch, file, or folder names. ### File extensions - `.json` files are parsed as either a Connector template or a test scenario file. The operation will fail if the file contents are not valid for either type. If the remote repository contains any `.json` files that are not valid Camunda Hub files, place them in a subfolder so they are automatically ignored during synchronization. - Git Sync only supports `.md` files named exactly `README.md` (case-sensitive). Multiple `README.md` files are supported in a single repository, including in subfolders. ### Synchronization - In SaaS, the Git sync payload size must not exceed 4MB. In Self-Managed, you can [configure the `max-in-memory-size`](/self-managed/components/hub/configuration/properties.md#git-sync). If your sync fails because you've exceeded this limit, contact [Camunda success](https://camunda.com/services/support/) for assistance. - Actions which alter the SHA of the commit to which Camunda Hub is synced (for example, squash) may cause synchronization errors. - Timeouts may occur during a sync. In the event of a timeout, close the modal and retry the synchronization. - Using self-hosted instances of Git providers may require additional configuration. Refer to the Camunda Hub configuration part for your [git host](#connect-to-a-remote-repository) and available [environment variables](#self-managed-environment-variables) for more details. - **(GitHub specific)** A single synchronization action is limited to incorporating a maximum of 250 commits or making changes to up to 300 files, regardless of whether these changes affect the Camunda Hub files directly. Camunda Hub does not provide a notification when these thresholds are exceeded. Should you encounter this limitation, it may be necessary to initiate a fresh synchronization. A fresh synchronization fetches all the files in the repository without relying on the incremental changes, thus bypassing the limitations. This can be achieved by either changing the branch or modifying the GitHub repository URL. --- ## Projects(Manage-projects) In Camunda Hub, a [project](/components/concepts/projects.md) contains a set of files. You can consider a project as a bundle of related files you can version and deploy together. You can also consider a project as a container of individual files meant to be versioned and deployed independently. ## About A project can contain: - [BPMN diagrams](/components/modeler/bpmn/bpmn.md) - [DMN diagrams](/components/modeler/dmn/dmn.md) - [Forms](/components/hub/workspace/modeler/modeling/utilize-forms.md) - [RPA scripts](/components/rpa/overview.md) - [Element templates](../modeler/element-templates/manage-element-templates.md) - [READMEs](../modeler/modeling/advanced-modeling/process-documentation-with-readme-files.md) - Folders For example, a project for a consumer loan application might consist of a BPMN diagram as an entry point and a number of additional supporting files, such as DMN diagrams and forms. ## Project development lifecycle In Camunda Hub, you can quickly develop project releases through the stages of a typical project development lifecycle: For business-critical and higher-risk processes that require strict governance and/or quality requirements, you can [integrate Camunda Hub into your CI/CD pipelines](/components/hub/workspace/modeler/integrate-modeler-in-ci-cd.md). ## Known limitations You should be aware of the following limitations when working with projects. ### Deployment limitations - Projects can only be deployed to a Zeebe cluster in version 8.4.0 or higher. - The overall size of the deployment bundle is limited due to a maximum [record](/components/zeebe/technical-concepts/internal-processing.md) size of 4 MB in Zeebe. - The limit is effectively between 2 and 3 MB, as Zeebe writes more data to the log stream than just the raw deployment. - If you exceed the limit, you are shown an [error message](deploy-project.md#deployment-errors): `Command 'CREATE' rejected with code 'EXCEEDED_BATCH_RECORD_SIZE'`. --- ## Manage project settings Manage the settings of your project, such as its name and the workspace it belongs to. ## Permissions To manage the settings of a project, you must have one of the following workspace roles: - **Workspace Admin** - **Editor** However, any user can download a project. ## Change project settings 1. In your workspace, find the project to change. 2. Under **Actions**, click the vertical ellipsis button. From here, you can perform the following actions: | Action | Description | | :----------- | :-------------------------------------------------------------------------------------------------------------------------------- | | **Rename** | Rename the project. | | **Download** | Download the project as a zip file. | | **Move** | Move the project to another workspace. | | **Delete** | This moves the project to [**Recently deleted**](./recently-deleted.md) where you can recover it at any time in the next 30 days. | --- ## Manage and review project snapshots Create distinct snapshots for the entire project. ## About Use snapshots to save a single capture of all the project files in one action. This helps you track a project throughout its development lifecycle and ensures the correct state is referenced. In this context, a [snapshot](/reference/glossary.md#snapshot-project) is a Camunda Hub project snapshot, not a deployed process definition version. ## Create a snapshot To create a project snapshot: 1. In your workspace, open a project. 2. On the right side of the project view, under **Project snapshots**, click **Create snapshot**. 3. Enter a **Snapshot tag** in the snapshot creation modal. 4. Click **Create**. :::note A snapshot references each element template's last published version, not any unpublished draft changes. To include recent template edits in a snapshot, [publish a new element template version](/components/hub/workspace/modeler/element-templates/manage-element-templates.md#versioning-element-templates) first. ::: ## View all snapshots To view all snapshots: 1. In your workspace, open a project. 2. On the right side of the project view, under **Project snapshots**, click **Show full list**. This opens the **Snapshots** page. The page lists every file in the project with its status, file version, last changed date, and creator, so you can review changes at a glance without opening the comparison view. The side panel has two tabs: - **Snapshots**: View the timeline of all project snapshots, including the current **Draft** state. - **Compare Snapshots**: Compare two snapshots. See [manage file versions](/components/hub/workspace/modeler/modeling/versions.md) to learn about more features like comparing and restoring individual file versions. ## Manage a project snapshot To manage a project snapshot: 1. In your workspace, open a project. 2. On the right side of the project view, under **Project snapshots**, next to a snapshot, open the vertical ellipsis menu. From here, you can perform the following actions on a project snapshot: | Action | Description | | :--------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- | | **View details** | Open the snapshot details page to review the contents of all files in the snapshot. | | **Test this snapshot** | Open the snapshot in [Test mode](/components/hub/workspace/modeler/validation/test-your-process.md) to validate and debug it against an environment. | | **Restore as latest** | Revert changes, make further edits, or [sync](git-sync.md), download, or validate your project. | | **Edit details** | Edit the snapshot tag and description. | | **Download** | Download the project as a zip file. | | **Edit review** | Update the [review](#request-a-review) status of the snapshot. (Only available if the snapshot has been reviewed.) | | **Copy to...** | Create a new project with the files from the snapshot. | | **Delete** | Delete the project snapshot. | On the snapshot details page (opened via **View details**), the actions menu also includes **Deploy**, which deploys the project snapshot, especially after it has been [reviewed](#request-a-review). ## Test a snapshot You can test a project snapshot in [Test mode](/components/hub/workspace/modeler/validation/test-your-process.md) without restoring it or changing your latest project files. In the snapshot's vertical ellipsis menu, select **Test this snapshot**. Test mode opens the snapshot in **Read-only** mode. In the **Set up test run** panel: 1. Click **Select environment**, and choose where to run the test. 2. Select the resources to deploy, and click **Deploy**. The option **Only this resource** isn't available for snapshots. 3. Set the start and end elements, and run the test. See [Test mode](/components/hub/workspace/modeler/validation/test-your-process.md#get-started-with-test-mode) for detailed information. ## Compare snapshots You can compare any two project snapshots, including snapshots that are not next to each other in the timeline. 1. In your workspace, open a project. 2. On the right side of the project view, under **Project snapshots**, click **Show full list**. 3. On the right side of the **Snapshots** view, in the **Compare Snapshots** tab, select two snapshots you want to compare. The comparison shows the older snapshot against the newer one, ordered by time regardless of the order you selected them. The selected pair is written to the URL, so you can share or bookmark a specific comparison. The comparison lists every file present in either snapshot, with a status badge per file: | Status | Meaning | | ------------- | ------------------------------------------------------------------ | | **New** | The file exists in the newer snapshot only. | | **Removed** | The file exists in the older snapshot only. | | **Modified** | The file content differs between the two snapshots. | | **Moved** | The file changed location. A move summary is shown under its name. | | **Unchanged** | The file content is identical in both snapshots. | A file gets only one status badge, in this order of priority: **New**, then **Removed**, then **Modified**, then **Moved**, then **Unchanged**. For example, a file that was both moved and had its content changed shows as **Modified**, since the content change is the more significant one. Its move summary is still shown under its name. For each file in the list, you can: - Expand the row to view an inline diff of the file. - Select the **Open file editor** icon to open the file. - Select **Open version history** to open the [version history](/components/hub/workspace/modeler/modeling/versions.md) of the file. ## Restore a snapshot Restoring a snapshot reverts the entire project to its state at the time the snapshot was created. This includes: - Moving and renaming files and folders to their snapshot state. - Soft-deleting files that were created after the snapshot. - Restoring file instances that were deleted after the snapshot was created. - Updating all file content to match the snapshot. To restore a snapshot: 1. In your workspace, open a project. 2. On the right side of the project view, under **Project snapshots**, next to a snapshot, open the vertical ellipsis menu. 3. Select **Restore as latest**. The project state changes to match the snapshot, and the snapshots timeline is refreshed. ### What happens during a project snapshot restore A project snapshot restore is a single bulk operation, not a series of individual [file restores](/components/hub/workspace/modeler/modeling/versions.md#restore-a-version). It affects the project, its files, and any element templates differently: - **The project snapshot itself**: if the project's live state has drifted from its most recent snapshot, a safety snapshot is created first to capture that state before restoring. If nothing has changed since the last snapshot, no safety snapshot is created. - **Files that still exist in the project**: for each file that is still present (in its original location or elsewhere in the project), Camunda Hub moves and renames it back into place, then restores its content the same way as an individual file restore. A safety autosave entry captures the file's state just before the restore (only if it differs from the file's last saved entry), followed by a new "(restored)" entry with the snapshot's content. - **Files that were moved out of the project or permanently deleted**: since there's no existing file to restore into, Camunda Hub creates a new file from the snapshot's content directly. This new file has no autosave step and no prior version history, since none of its own history exists yet. - **Files that exist now but weren't part of the snapshot**: these are soft-deleted so the project matches the snapshot's file set. - **Element templates**: element templates are handled differently depending on their state at the time of restore: - If a template is still in the project, its content is reset in place, and it's moved back to its recorded location. This does **not** publish a new numbered template version the way [restoring an element template directly](/components/hub/workspace/modeler/element-templates/manage-element-templates.md#versioning-element-templates) does. - If a template was soft- or permanently deleted since the snapshot was created, the entire restore fails rather than recreating the template. - If a template is still live but was moved out of the project, it's skipped. Restoring it in place would conflict with whoever now manages it in its new location. Any BPMN element still referencing the template keeps a broken reference; this is an accepted trade-off. - If a template was added to the project after the snapshot was created, it's left in place rather than removed. Unlike other new files, it can't be safely reverted or removed, since it has its own independent versioning. ## Request a review 1. Request a review for the newest snapshot of the project from the snapshots page of the project. Members with edit permission in your project will see a notification on the process diagram page once you have requested a review. Reviews cannot be performed by the user who created the project snapshot unless the user is an organization administrator. 2. Reviewers can view the changes, comment, request changes, or approve the project snapshot. 3. After a user has submitted their review, the project snapshot is marked as reviewed and the review status is shown in the snapshots timeline. 1. Any user with edit permissions can go back and edit the review at any point in time to update the assessment. 4. If the reviewer has marked the snapshot as **Changes requested**, you can address the feedback by performing the requested changes, creating a new snapshot, and requesting a review for the new snapshot. This review capability is most useful for reviews on a business level. For technical reviews, you may instead [sync your Git repository](git-sync.md) to put changes into a technical context with related code changes. After the review is complete, you can deploy the approved project snapshot to an [environment](./deploy-project.md). For example, deploy to your testing environment, then to staging, and finally to production. If your organization requires approval for production, only an approved snapshot can be deployed to an environment tagged `prod`. See [production environments](./deploy-project.md#production-environments). :::info If you want to use your own deployment pipeline after the review is complete, you can [sync your Git repository](git-sync.md) at this point to deploy and promote the project through your own pipeline. ::: --- ## Recover deleted resources Learn how to recover recently deleted resources, such as files, folders, and projects, before they're permanently removed. ## Soft deletion in Camunda Hub When you delete a resource, it's moved to **Recently deleted**. You have 30 days to restore it before permanent deletion. The following resource types are soft deleted: - Files - Folders - Projects - Workspaces - IDP applications - IDP projects If the resource is a parent resource, such as a folder or project, the child resources it contains are also moved to **Recently deleted**. :::note Soft deletion only applies to resources deleted using the Camunda Hub user interface in Camunda 8.10 and later. All items deleted in earlier versions are immediately and permanently deleted, along with their data in project version history, and can't be recovered. ::: ## Permanent deletion in Camunda Hub Permanent deletion occurs 30 days after a resource is deleted. This removes all associated data, including resource content, version history, metadata, and Git links. :::note Using the [public API](/apis-tools/hub-api-saas/specifications/permanently-delete-file.api.mdx), a client with `delete` permissions can permanently delete a resource before the 30-day window has expired: ```bash DELETE /api/v2/files/{fileKey}/permanent ``` ::: ## Purge a file from versions If you delete a file within a project, its data is preserved in [older versions](../modeler/modeling/versions.md), if applicable. To permanently delete the file and its data from the file's entire history, a client with `delete` permissions can call the [public purge endpoint](/apis-tools/hub-api-saas/specifications/purge-file.api.mdx): ```bash DELETE /api/v2/files/{fileKey}/purge ``` ## Restore permissions Only a **Workspace Admin** at the time of the restore attempt can restore a recently deleted workspace. A **Workspace Admin** or **Editor** can restore all other resource types. The role at the time of the original deletion is not considered. Read more about [access rights and permissions](/components/hub/organization/manage-workspaces/manage-workspace-members.md#workspace-roles). ## Browse recently deleted resources In Camunda Hub, in the left-hand navigation, click **Recently deleted**. This page lists all resources deleted within the last 30 days. Each row shows: | Column | Description | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Name** | The resource name | | **Type** | The type of deleted resource | | **Location** | The full path of the resource's current live ancestors as a breadcrumb. Composed at read time, so renames or moves of live ancestors are reflected immediately. | | **Deleted by** | The user who performed the deletion | | **Deleted on** | The date and time of the deletion | | **Days remaining** | Days left before permanent deletion | By default, the list is sorted with the most recently deleted resources first. If the recently deleted resource is a parent, such as a folder or project, you can expand the row to reveal the child resources deleted with it. ## Restore a resource To restore a recently deleted resource: 1. In the left-hand navigation, click **Recently deleted**. 2. Find the resource you want to restore. 3. Click the restore icon at the end of the resource's row. The resource returns to its original location. ### Parent resources If you restore a parent resource, the resources deleted with it are also restored. Child resources that once belonged to the parent resource but were deleted independently are not affected. ### Child resources You can restore a child resource without restoring its parent folder or project. Since the child can't be restored to its original location, it's placed in a new folder at the workspace root using the template `${fileName} - restored`. The actual folder name is presented in the confirmation modal when you restore the resource. If the workspace has been deleted, you must restore the workspace before you can restore any of its child resources. Similarly, you must restore an IDP application before you can restore its IDP projects. --- ## Validate your project Validate your project in development before deploying it to your target environment. ## Prerequisites Before you begin: - Make sure you've [set up a project](./create-a-project.md) in a workspace that has at least one [environment assigned](../../organization/manage-environments/assign-environments.md). - If the target environment has [authorizations](/components/admin/authorization.md) enabled, ensure deploying users have [`CREATE` permission to the `RESOURCE` resource type](/components/admin/authorization.md#create-an-authorization-in-admin). ## Validate your project Use [Test mode](/components/hub/workspace/modeler/validation/test-your-process.md) to validate your project in development. 1. In your workspace, open the project you want to validate. 1. Open a BPMN diagram. 1. At the top left of the modeler canvas, click **Test**. 1. Under **Choose where to run**, select an environment, and click **Deploy**. 1. Validate your process as required. For example, debug your process logic and test the project. :::info To learn more about using Test mode for validation, see [Test your process](/components/hub/workspace/modeler/validation/test-your-process.md) ::: --- ## Manage workspace settings Manage workspace members, view and assign environments, update general information, or delete a workspace. ## About In Camunda Hub, a workspace is a collaboration space within an organization, representing a team or business domain. It groups organizational resources like members, projects, and environments so related work happens in one shared space. :::info You can only manage a workspace's settings at the workspace level if you're a **Workspace Admin**, **Organization admin**, or **Organization owner**. You can also [manage a workspace from the organization level](../../organization/manage-workspaces/index.md). ::: ## Manage workspace members Add members, edit member roles, or delete members: 1. In your workspace, in the left-side navigation, click **Settings**. 2. Under **Members**, follow the organization-level [manage the workspace's members](../../organization/manage-workspaces/manage-workspace-members.md) guide. ## View assigned environments An [environment](/components/concepts/environments.md) is a deployment target where the projects of your workspace run. Your organization admin assigns environments to the workspace, and every project in the workspace can use all of them. If you're a **Workspace Admin** or **Editor**, the left navigation shows a **Workspace environments** section. It lists each assigned environment with a **Details** entry and links to its applications. Organization owners and admins see the same list. Viewers and commenters don't see environments. For each environment, the **Environments** page shows: | Detail | Description | | :----------- | :--------------------------------------------------------------------------- | | Name | The name of the environment. Select it to open the details. | | Cluster | The cluster that hosts the environment. | | Tags | The tags of the cluster, for example `dev` or `prod`. | | Version | The Camunda version of the cluster. | | Status | Whether the environment is healthy, unhealthy, paused, resuming, or unknown. | | Applications | Links to the applications of the environment, such as Operate and Tasklist. | An unhealthy or paused environment stays in the list with its status. If no environment is assigned, the page tells you to contact an organization admin. Select an environment to see its applications, details, and a summary of its jobs from the last 24 hours. Camunda Hub doesn't check your permissions in the applications. Each application enforces its own access. ## Manage assigned environments Organization owners and admins can change the environments assigned to a workspace. DevOps users can too, if they're also **Editor** or **Workspace Admin** in the workspace. 1. In your workspace, in the left-side navigation, click **Settings**. 2. Open the **Environments** tab. 3. Click **Edit environments**, or **Add environments** if none are assigned. 4. Select the environments, then click **Save**. See [assign environments to a workspace](../../organization/manage-environments/assign-environments.md) for details. ## Update workspace information Update the workspace name and description: 1. In your workspace, in the left-side navigation, click **Settings**. 2. Under **General > Workspace information**, update the workspace name and description. 3. Click **Update.** ## Delete a workspace Soft delete a workspace and its resources: 1. In your workspace, in the left-side navigation, click **Settings**. 2. Under **General > Danger Zone > Delete workspace**, click **Delete**. Your workspace is moved to [**Recently deleted**](../manage-projects/recently-deleted.md). It will be permanently deleted after the retention period. ## Further reading - [Camunda Hub Workspace API](/apis-tools/hub-api-saas/specifications/create-workspace.api.mdx) - [Environments](/components/concepts/environments.md) --- ## Collaborate with modes Use **Design**, **Implement**, and **Test** modes to collaborate effectively with members with different technical backgrounds. ## About [**Design**](./design-your-process.md) mode is tailored to business users focused on modeling, sharing, and collaborating. [**Implement**](./implement-your-process.md) and [**Test**](../validation/test-your-process.md) modes are tailored to developers focused on implementation and debugging. When accessing a BPMN diagram for the [first time](/components/hub/workspace/modeler/modeling/model-your-first-diagram.md), the **Design** mode is the first selected option. To switch between modes, you can select one of the tabs on the left side of the modeling interface, above the diagram; any further selection is remembered and kept for the next sessions. :::note When a process template is selected, the default mode is **Implement**. ::: ### Modes and roles | Role | Description | | :------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Workspace Admin** | Read/write access to all modes. | | **Editor** | Read/write access to all modes. | | **Commenter** | Read-only access to all modes. Can comment on diagrams. This role can be assigned to stakeholders who need to see the implementation properties without the ability to modify them. | | **Viewers** | Read-only access to all modes. Cannot comment on diagrams. This role is suitable for stakeholders who only need to view the process design and implementation without making any changes or comments. | Read more about the [different roles and how to assign them](/components/hub/organization/manage-workspaces/manage-workspace-members.md#workspace-roles). --- ## Collaborate with your team Learn about ways to collaborate with your team in Camunda Hub workspaces. ## Share and embed diagrams You can share diagrams with others in read-only mode via a sharing link. This link can also be protected with an additional password. 1. Open a diagram. 2. In the top right corner of the modeler interface, open the vertical ellipsis menu, and select **Share**. 3. Click **Create link**. 4. (Optional: Protect this link with a password) At the bottom of the modal, next to **Password protect**, click **Add**, and type a password. 5. Share or use the link with one of the following methods: - Click **Copy** to share the URL directly. - Click **Embed** to copy an `iframe` HTML tag. - Click **Email** to share the new link with multiple email recipients. :::tip For wiki systems like [Confluence](https://www.atlassian.com/software/confluence), Camunda recommends using the HTML macro and adding the iframe tag from the sharing dialog. This way, diagrams can be easily included in documentation pages. To adjust the dimensions of the diagram, the width and height values of the `iframe` tag can be modified. ::: ## Comments Use comments to discuss your diagram: 1. Open a diagram. 2. On the right side of the modeler interface, in the **Details** panel next to **Properties** and **Test**, select the **Comments** icon. If a single element is selected, the comments in this panel only apply to the selected element. Otherwise, they apply to the entire diagram. :::tip Elements with comments have a **Comment** icon in the diagram. ::: If you have [Workspace Admin, Editor, or Commenter](/components/hub/organization/manage-workspaces/manage-workspace-members.md#workspace-roles) access rights, you can: - Add a new comment. - Edit or delete a comment with the vertical ellipsis menu on the comment. ### Mention others in comments When leaving a comment, type the **@** character to filter and select a workspace member. When submitting the comment, this member will receive an email as a notification about the new comment. :::note Members without email addresses will not receive any kind of notification about being mentioned in a comment. ::: ## Collaborate on diagrams ### Model a diagram together When other members open the same diagram as you, the updates on the diagram are sent in real time. You can also see who is in the diagram with you. ### Canvas lock To prevent conflicts and broken sessions when multiple people open the same diagram, Camunda Hub automatically locks the canvas. When a member with edit permissions starts editing a diagram, the canvas is automatically locked. While the lock is active, no other members can modify the diagram — this prevents conflicting edits. Other members can still do the following: - Open and view the diagram in real time - Switch [modes](./collaborate-with-modes.md) - Navigate the canvas - Drill down into subprocesses - Inspect properties and linked assets - Add comments (if they have permission) #### Take over editing If another member with edit permissions needs to continue working, they can take control by clicking the **Take over** button in the canvas lock bar. This releases the current lock and immediately assigns edit control to the new member. This approach enables predictable handovers and prevents conflicting edits while keeping the diagram accessible to all viewers. ### Undo/redo management limitations When collaborating with others on a diagram, you can only undo or redo your own actions until another member makes a change, as the undo/redo history is reset each time another member makes a change. ### Draw other's attention Whether you are in a presentation or if others are in the same diagram as you are, use the attention grabber pointer to draw attention to a specific part of the diagram. To do this, take the following steps: 1. Switch on the attention grabber pointer from the canvas tools. ![attention grabber](../img/attention-grabber.png) 2. Drop the pointer by clicking anywhere on the canvas. ![attention grabber](../img/attention-grabber-pointer-pulse.png) The pointer will pulsate to draw attention and will match your avatar color. It can also be seen in real-time by others that are looking at the same diagram as you. --- ## Design mode for business users In **Design** mode, access a simplified Camunda Hub modeling interface with a reduced **Properties** panel. ## About In **Design** mode, only the documentation property and comments are shown in the **Properties** panel, which provides a decluttered user interface. All the technicalities, such as triggers to deploy the diagram or start the instance, are hidden. Linting is disabled, and problem annotations are discarded. The sidebar on the right-hand side of the screen is collapsed when switching to the design mode, and when expanded, the state is persisted even when switching to another diagram. As a business user, you can [**link decision models**](/components/hub/workspace/modeler/modeling/advanced-modeling/business-rule-task-linking.md) and [**process models**](/components/best-practices/modeling/creating-readable-process-models.md) via [call activities](/components/modeler/bpmn/call-activities/call-activities.md), and you can still be a [**Workspace Admin**](/components/hub/organization/manage-workspaces/manage-workspace-members.md#workspace-roles), even if you don't execute implementation. With the **Design** mode view, users can model a process without need for a complex development tool that does not speak their language. This provides a clear journey for the user, all while incorporating modeling, sharing, and collaborating in a user-friendly way. ## Switch to Design mode To switch to **Design** mode: 1. In Camunda Hub, open a BPMN diagram. 2. In the top left of the modeling interface, click **Design**. --- ## Implement mode for developers In **Implement** mode, access a full, implementation-focused Camunda Hub modeling interface. ## About **Implement** mode offers all possible implementation details, and the problems panel (accessed by clicking **Problems** in the bottom left corner) shows all implementation problems that need fixing before deployment. The properties panel automatically opens when switching to **Implement** mode, and if collapsed, it stays collapsed as long as you navigate between diagrams. Developers can switch between the modes as they like, and when they open a process template, it opens in **Implement**. ## Switch to Implement mode To switch to **Implement** mode: 1. In Camunda Hub, open a BPMN diagram. 2. In the top left of the modeling interface, click **Implement**. --- ## Best practices for custom-built element templates When creating custom-built element templates, consider the following best practices to ensure they are effective, user-friendly, and maintainable. :::note Connector templates are a specific type of [element template](/components/concepts/element-templates.md). ::: ## Naming, description, and icon ### Sentence case Use sentence case when naming element and connector templates. For example: - ❌ GitHub Webhook Intermediate Catch Event connector - ✅ GitHub webhook intermediate catch event connector ### Choosing a name Choose a clear, easily understandable name. Include the brand name if the template connects to a service or tool; otherwise, describe its main feature. The template's name and description appear in the element template list and properties panel. ### Description Keep descriptions brief and clear. Explain what the template does and why it’s helpful in a couple of lines. Avoid technical jargon or complex language. ## Icon - **Prefer SVG format:** Use SVG files for icons, as they are scalable without losing quality. If an SVG icon isn't available, PNG or JPEG format is acceptable, but may not display well. - **Size:** You can upload an image file with a maximum size of **8 KB**. If you need to use a PNG or JPEG icon, ensure it is **512x512** pixels. Icons appear as **18x18** pixels in the element on the modeling canvas, and as **32x32** pixels in the properties panel. - **Check for official logo:** If the template connects to a service or tool, look for an official logo on the service's or tool's website. - **License compliance:** Ensure icons have the appropriate license (Public Domain CC0 or Creative Commons CC BY) for commercial use. ## Properties panel UI The element template defines interaction methods, visible and hidden entries, and mandatory fields, which are reflected in the properties panel UI. Follow these guidelines for a consistent user experience. ### Property naming - Use readable property names instead of technical identifiers. - Use sentence case. - Align operation or property IDs with Java method naming conventions (IDs are referenced in the template code, not displayed). Example: - ID aligned with Java method: `addQueueItem` - Label: - ✅ Add queue item - ❌ addQueueItem ### Property description - Ensure accuracy in property descriptions. - Use tooltips for brief explanations. - Link to relevant documentation for detailed explanations. Example: - ❌ "Your application's Client ID from the OAuth client." - ✅ "UIPath OAuth Client ID: Retrieve from UIPath external apps configuration." ### Placeholders - Placeholders appear as lighter text inside input fields. Use them to indicate the expected format or example input. - Avoid placeholders that duplicate the label. - Ensure placeholders are understandable and helpful. ### Variable naming Variables are not displayed in the properties panel but are referenced inside the template code. **General rule:** Use lower camel case (start lowercase, capitalize subsequent words). Avoid starting variables with underscores ("\_"). Example: - ❌ \_MyTestVariable - ✅ myTestVariable **Exception for method-specific properties:** Underscores may separate the method indication from the property name. Example: - ✅ chatCompletion_apiVersion - ✅ completion_apiVersion ### Versioning If you plan to make changes to your template over time and want to support [template evolution](https://github.com/bpmn-io/element-templates/blob/main/docs/LIFE_CYCLE.md#overview), include a version number property in your template, starting from 1. Templates with the same ID and different version values offer an upgrade path. ```json { "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", "name": "My Connector", "id": "org.my.connector", "version": 1 } ``` ### JSON structure The JSON structure should mirror the visual presentation to maintain alignment with the UI order. This ensures groups and properties are organized accordingly, even if some elements are hidden. If you want to use a FEEL expression that references other variables, the properties defining those variables must be declared before the property where the FEEL expression is used. ### Secret handling - Follow the rules of the system you’re adding to. - Do not save secret information in the JSON. - Use the connectors' [secret handling](/components/connectors/use-connectors/index.md#using-secrets). ### Field types The following guidelines ensure templates are intuitive and designed to minimize errors. This helps to create a good user experience. #### Hidden vs. visible fields If certain values are static and do not require user input, prefill them. Hidden fields offer no additional benefit by being visible. **Hidden fields example:** - HTTP method - Static URL endpoint - Static header #### Required vs. optional - **Required:** Properties essential for the template to function. Without these, the template cannot operate. - **Optional:** Properties that are not mandatory but provide additional functionality. There are two mechanisms to define property behavior depending on whether a field is required or optional: - [**“Not empty” constraint**](/components/modeler/element-templates/template-properties.md#validating-user-input-constraints): Displays an error if the field is left empty. - [**Optional bindings**](/components/modeler/element-templates/template-properties.md#preventing-persisting-empty-values-optional): Does not persist empty properties in the BPMN XML. Use **optional bindings** when a property is not required and you want to avoid storing empty values in the BPMN XML. In most cases, required fields should use the "Not empty" constraint for validation. #### Mandatory FEEL vs. optional FEEL FEEL expressions should only be required when necessary. For straightforward inputs, expressions can be optional. More details: [FEEL editor support](/components/modeler/element-templates/template-properties.md#adding-feel-editor-support-feel). #### Free input vs. dropdown vs. constraints - Use a dropdown when selection options are predefined. - Use free input with constraints when there is a wide range of input possibilities. **Example:** ```json { "label": "Priority", "id": "priority", "group": "input", "description": "The priority to apply to the queue item.", "value": "Low", "type": "Dropdown", "choices": [ { "name": "Low", "value": "Low" }, { "name": "Normal", "value": "Normal" }, { "name": "High", "value": "High" } ], "binding": { "type": "zeebe:input", "name": "priority0" }, "condition": { "property": "operationType", "oneOf": ["addQueueItem"] } } ``` --- ## Generate an element template Configure and generate a custom element template in Camunda Hub. ## About Use this page for both BPMN element templates and connector templates: - [Generate a BPMN element template](#generate-a-bpmn-element-template). - [Generate a connector template](#generate-a-connector-template). - To customize a generated connector template after creation, see [connector templates](/components/connectors/custom-built-connectors/connector-templates.md). ## Best practices When creating custom-built element templates, consider the [recommended best practices](best-practices.md) to ensure they are effective, user-friendly, and maintainable. ## Generate a BPMN element template Create a BPMN element template in Camunda Hub. 1. Select the Camunda Hub project where you want to create the template. 2. Click **Create new > Element template**. This opens the **Create new element template** screen. 3. Select **BPMN element**. 4. Choose a starting point for your template: - **Call activity** - **Event**. Also select an event type. - **Task**. Also select a task type. - **Blank**. A blank template includes only the basic properties shared by all BPMN elements. With this, you can customize the template from scratch. Next, [**configure the template details**](#configure-the-template-details). ## Generate a connector template Create a connector template in Camunda Hub. You can start from a blank template or import an existing API definition such as an [OpenAPI specification](https://swagger.io/resources/open-api/), [Swagger specification](https://swagger.io/resources/open-api/), or [Postman collection](https://www.postman.com/collection/). For example, download a Postman collection as a YAML file, import it into the generator, and choose which methods to include in the generated template. 1. Select the Camunda Hub project where you want to create the template. 2. Click **Create new > Element template**. This opens the **Create new element template** screen. 3. Select **Connector**. 4. Choose a starting point for your template: - **From API definition**: Import an existing API definition file as a starting point. The **Import data source** section will appear below the template details. - **From blank**: Start from a blank template. Next, [**configure the template details**](#configure-the-template-details). ## Configure the template details Under **Configure template details**, provide the following information: - **Name:** Enter a clear and descriptive name for the template. For example, include the brand name if the template connects to a service, or indicate its main feature. - **Description:** Describe the template’s main features and benefits. - **Icon:** Use a default BPMN symbol or upload a custom icon. Supported formats: SVG, PNG, JPEG. Maximum file size: 8 KB. Minimum dimensions: 512 × 512 pixels. To add an icon, enter the image URL and click **Import icon**, or drag and drop a file into the upload area. :::note If you do not configure template details at this stage, a default name and BPMN symbol are assigned. You can edit them later. ::: ## Only for connector templates: Import an API definition If you're generating a connector template, and you selected **From API definition**, you need to import the data source. 1. Under **Import data source**, select the format to import (OpenAPI or Postman). 2. Import the API definition: - **Import from URL:** Enter the API definition URL, and click **Import file**. - **Upload file:** Drag and drop a file into the upload area, or click the link to select a file. 3. After importing, select which actions to include from the generated list of supported methods. :::info For more information on working with and configuring connector templates, see [Connector templates](/components/connectors/custom-built-connectors/connector-templates.md). ::: ## Create the template Click **Create template** to generate the new template and open it in the element template editor. --- ## Manage element templates export const UploadIcon = () => ; You can create and manage [element templates](/components/concepts/element-templates.md) just as any other asset in a Camunda Hub project. ## Create an element template To create a new element template, follow the steps described in [generate an element template](./element-template-generator.md). You will be taken to the **Element template editor** screen. In this screen, you can define the element template by writing the template JSON. The template editor supports you in writing the template by providing autocompletion, error highlighting, and a live preview. The components of the editor interface are as follows: - On the left, you find the **template JSON editor**. Here, you define the actual [template descriptor](/components/modeler/element-templates/defining-templates.md). The descriptor follows the [most recent element template schema](https://github.com/camunda/element-templates-json-schema). :::info Starting with 8.8, you can freely edit the following properties: - `name`: Human-friendly name shown when selecting a template and in the properties panel after the template has been applied. The value can be different from the file name. - `id`: Identifier of the template. Changing this value creates a new template. We recommend setting a meaningful value (for example, "PaymentConnector", "CreateUserTemplate"). - `version`: Integer-based version number. Combined with the `id`, it defines a unique template version. When [publishing](#publish-an-element-template) a new version, you are prompted to update the version number if you haven't already done so. The value of the `$schema` property is still fixed; manual changes will not be saved. ::: - On the right, you observe the live **Visual Preview**. The live preview shows how the properties panel will look when you apply the template to an element. It automatically updates on every valid change, and reflects the latest valid state of the template. The preview allows you to interactively check your template before publishing it. You can also use the **Update JSON** button to update the template's JSON properties with the current input values from the visual preview. - In the upper right, you can add an icon to your template. You can upload an image file with a maximum size of 8 KB. We recommend using squared SVG graphics. Icons appear as 18x18 pixels in the element on the modeling canvas, and as 32x32 pixels in the properties panel. On every valid change, the template is saved automatically. If there are errors in the JSON file, the template will not be saved. Ensure all [errors are resolved](#fixing-template-problems) for the template to save successfully. ## Publish an element template After finalizing your element template, click **Publish > Publish to workspace** to activate it within the workspace context. In the modal that opens: - Update the version number if necessary. You don't need to change it for the initial version or if you have updated it already in the template editor. The value entered here is saved to the `version` property in the JSON. - Assign a distinct version name for effective version management. - Add a description to explain what changed since the previous version. Camunda Hub checks the template for conflicts with already-published template versions. You cannot publish a new version if: - The template's ID is already used in a published version of a different template file. - The version number is equal to or lower than the last published version of the same template file with the same template ID. Camunda Hub also shows a warning if the template ID has changed since the last published version. You can still publish the new version in this case. :::note Template IDs must be unique across the organization, including for templates that are only published to a project. If a conflicting template ID is already published in another project, publish a new template with a different ID. ::: As a [user with elevated access](/components/hub/workspace/modeler/collaboration/collaboration.md#elevated-access), you can publish an element template version within the organization context, enabling all organization users to use it in their diagrams. To do so, click **Publish > Publish to organization** on the editor screen or promote a template version via the [versions list](#versioning-connector-templates). ### Manage published element templates After publishing, an element template version can be applied across all models within the same project or organization, depending on its publication status. You can review the publication status of template versions in the [versions list](#versioning-connector-templates). At the organization level in Camunda Hub, in the left navigation, click **Shared resources**. Here, you'll can find an overview of all shared resources within your organization. [Users with elevated access](/components/hub/workspace/modeler/collaboration/collaboration.md#elevated-access) can: - View additional details about the published version. - Open the resource's versions list (if they are an [Organization Admin or Organization Owner](/components/hub/organization/users-and-roles.md#elevated-workspace-access) or are a [Workspace Admin or Editor](/components/hub/organization/manage-workspaces/manage-workspace-members.md#workspace-roles) of the resource's workspace). - Unpublish an element template directly from this view. Organization users without special organization permissions can: - View all the resources published within the organization. - Open the resource's versions list (if they are a [Workspace Admin or Editor](/components/hub/organization/manage-workspaces/manage-workspace-members.md#workspace-roles) of the resource's workspace). ### Versioning element templates Element templates use their own numbered versioning, distinct from the [file version history](/components/hub/workspace/modeler/modeling/versions.md) used by diagrams, forms, RPA scripts, README files, and test files. Each publish creates a new numbered version in the template's versions list. To restore an earlier template version, select it in the versions list and select **Restore**. Restoring publishes the earlier content as a new numbered version. It doesn't create an autosave first, since the version you're restoring from remains available in the versions list. If you publish a new version of an element template and an older version is already being used in diagrams, the user can either: - [Update the diagram elements](/components/modeler/desktop-modeler/element-templates/using-templates.md#updating-templates) to use the most recent version of the element template. You cannot undo this action. - Continue using the older version of the element template in their diagrams. ## JSON editor features The JSON editor is based on the [Monaco Editor](https://microsoft.github.io/monaco-editor/). The Monaco Editor is the editor that powers VS Code. As a result, the template editor supports many familiar features, such as auto-formatting, indentation support, code completion, and error highlighting. With code completion, you can add a complete property object when you press `Ctrl+Space` at a location for a new property. When you press `Ctrl+Space` to create a new attribute, you get proposals for all available attributes. When the domain for values is defined, you can select one by pressing `Ctrl+Space` in a value. Read the [Visual Studio Code editor docs](https://code.visualstudio.com/docs/editor/editingevolved) for a full overview of features. ## Fixing problems in your templates {#fixing-template-problems} While working on a template, the template will be in invalid intermediate states. For instance, when you add a new property, it must contain various mandatory attributes. Unless all mandatory attributes are defined, the template will not be saved, and the preview is not updated. This ensures that you can never publish an invalid or broken template. The editor toolbar indicates if the template is currently in a valid state or not. The JSON editor provides you with error highlighting, allowing you to add mandatory values and resolve problems without missing anything. ![Indicating problems in element templates](img/connector-templates/fix-connector-template-problems-1.png) If there are problems at the root level of the JSON (such as a missing or misspelled mandatory attribute), the error is highlighted in the first line of the editor. Click the error marker at the curly bracket to expand the error message. ![Some element template problems highlighted in the first line](img/connector-templates/fix-connector-template-problems-2.png) ## Importing an existing element template If you have created templates for Desktop Modeler and want to reuse them in Camunda Hub, you need to make some adjustments to the template files: 1. **Split the files**. Camunda Hub maintains a 1:1 relation between element templates and files. Since Desktop Modeler allows you to keep multiple template definitions in a single file, you must split the file in advance to one file per template before uploading. 2. **Remove the brackets**. Remove the list brackets from the element template file before uploading. Even if a template file for Desktop Modeler contains only a single template, it is always wrapped in a list. Once your file follows the requirements, you can upload it. There are two ways to do so: 1. Upload it as a new element template via the **Upload files** action in the project view. 2. Update an existing template via the **Replace via upload** action in the breadcrumbs of the editor view. :::info Desktop Modeler support The element template editor is currently only available in Camunda Hub. Refer to the [Desktop Modeler documentation](/components/modeler/desktop-modeler/element-templates/configuring-templates.md) for instructions on configuring element templates in Desktop Modeler. ::: --- ## Save activity and event properties as reusable element templates Element templates allow you to create reusable, configurable building blocks that can be shared across your organization. With the **Save as template** feature, you can convert any supported element into an element template. ## Supported element types The **Save as template** feature is available for the following BPMN element types: **Activities** - Service task - User task - Send task - Receive task - Business rule task - Script task - Manual task - Call activity You cannot create a template for the [undefined task type](../../../../modeler/bpmn/undefined-tasks/undefined-tasks.mdx). **Events** - Message events - Signal events - Timer events - Conditional events ## Create an element template from an element You can save any supported element as an element template directly from the properties panel in Camunda Hub. Configure your element with the desired properties, save it as a template, and optionally customize it further in the template editor. ### Prerequisites Before saving an element as a template: - Ensure the element is properly configured with the desired properties. - Resolve any validation errors on the element. The **Save as template** button is disabled if errors are present. ### Step 1: Configure your element First, configure your element with all the necessary properties you want to include in your template. The configuration depends on your element type and use case. In this example, we'll configure a business rule task for fraud detection: 1. Select an element in your BPMN diagram. 2. Configure the element with the properties you need. For example, set up a business rule task by defining: - **Implementation**: Choose the implementation type (for example, DMN decision). - **Called decision**: Reference the decision to be invoked. - **Binding**: Select the [resource binding type](/components/best-practices/modeling/choosing-the-resource-binding-type.md). We recommend using `versionTag` to ensure that the template always references a compatible resource version. - **Result variable**: Define where to store the decision result. - Add any required input/output mappings for your business logic. ### Step 2: Save the element's properties as a template 1. With your configured element selected, look for the **Save as** button (template icon) in the top-right corner of the properties panel. 2. Click **Save as** to open the template configuration dialog. 3. In the **Save element properties as a new template** dialog, provide: - **Name**: Enter a descriptive name (for example, "Determine fraud rating confidence template"). - **Description**: Provide a detailed explanation of what the template does (for example, "Checks if the transaction is a refund and if there is a high fraud rating"). 4. Click **Save** to create your template. 5. You'll see an "Element template saved" notification with an option to **Edit template**. ### Step 3: Customize your template If you want to further customize or publish your template: 1. Click **Edit template** from the notification to open the template editor. 2. The template editor allows you to: - Modify template properties and [bindings](/components/modeler/element-templates/defining-templates.md#bindings). - Set up validation and [constraints](/components/modeler/element-templates/defining-templates.md#constraints) for user input. - Configure template groups and categories. By default, all properties are set to `"type": "String"`, making them visible and editable in the properties panel. To make your template easier to use, hide unnecessary properties with `"type": "Hidden"`, or use a dropdown (`"type": "Dropdown"`) with limited options. ### Step 4: Publish your template 1. When you're ready to make the template available to your team, click **Publish** to add it to your project or organization's shared resources. 2. If your template references dependencies (for example, another process or a DMN decision), a warning will remind you to ensure these dependencies are deployed in the runtime environment. Dependencies are not included in the template and must be managed separately. ### Step 5: Use your new template Once you've created and published a template, you can use it in a diagram: 1. Add a new element to your diagram or select an existing one. Make sure it matches the `appliesTo` type defined in your template. 2. Apply the template by: - Clicking **Change element** on the element, or - Clicking **Select** in the Template section of the properties panel. 3. In the template selection dialog, find your template (for example, "Determine fraud rating confidence template") under the assigned category. By default, templates are listed under "Templates". 4. Select the template to apply it to your element. The element will automatically be configured with all template settings, including decision references, bindings, and variable mappings. ## Understand template property bindings When you save an element as a template, Camunda Hub automatically converts the element's properties into template bindings: - Input/output mappings - Task headers - Zeebe properties - Element-specific properties (for example, `calledDecision`, `calledElement`) - Message references (for message-related elements) Only properties supported by element templates are included. Unsupported properties remain visible in the properties panel after you apply the template. For a list of supported properties, see the [element templates reference](/components/modeler/element-templates/defining-templates.md#defining-template-properties). ## When template creation is unavailable The **Save as template** button is disabled in the following scenarios: - You are not in Camunda Hub's implementation mode. - The element has validation issues. - The element type is not supported (blank tasks, error events, subprocesses, etc.). - You don't have permissions to create templates. Fixing validation issues will enable the button if the element type is supported. ## Best practices When creating templates from elements: - **Create focused templates**: Each template should serve a clear purpose. - **Hide details**: Expose only the necessary properties. - **Validate input**: Use [constraints](/components/modeler/element-templates/defining-templates.md#constraints) to enforce valid input and provide meaningful errors. - **Manage dependencies**: Ensure referenced decisions or variables exist in the runtime environment. Use `versionTag` bindings for dependencies to avoid version conflicts. - **Use meaningful parameter names**: Give configurable fields descriptive names. - **Test your templates**: Apply them to an element to confirm they work as expected. ## Related topics - [Element templates reference](/components/modeler/element-templates/about-templates.md) - [Custom connectors](/components/connectors/custom-built-connectors/create-connector-from-rest.md) - [Task types](/components/modeler/bpmn/tasks.md) --- ## Use catalog assets in Hub Once your center of excellence (CoE) has [synced element templates to the catalog](/components/hub/organization/manage-catalog/getting-started.md), you can discover and apply them while modeling. ## Discover assets in the catalog Browse the catalog in Camunda Hub to find approved, ready-to-use element templates. For each asset, you can see its name, short description, category, tags, latest version, and icon. The name, description, and icon come from the element template JSON itself, so what you see in the catalog matches what you see when the template is applied. You can narrow the list to find the right asset: - **Free-text search** matches against the asset name, short description, category name, and tag names (case-insensitive). - **Filter by category** to scope the list to a single area (for example, **Connectors** or **Human Tasks**). - **Filter by tags** to combine related assets across categories. - **Sort** by **Newest** or by **Name (A–Z)**. Assets that were recently added or updated are highlighted so you can spot new templates at a glance. Only published assets appear in the catalog — unpublished assets aren't visible to you. ## Apply assets while modeling To use a catalog asset while modeling: 1. Open a BPMN diagram in the Hub modeler. 2. Select an element and [apply a published element template](./using-templates.md#applying-templates) from the catalog. 3. Configure the element through the properties panel as defined by the template. When the CoE publishes a newer version of a template you already use, Hub offers an **update** path on elements that reference the older version, so you can move to the latest version when you're ready. If the CoE unpublishes an asset you already use, the template is deprecated. Elements that reference it keep working but show a deprecation hint in the properties panel, signaling that you should migrate to a newer version or a different template. ## Find reusable assets in the modeling menus When you create, append, or change an element, the compact menu separates what you can insert into two tabs: - **BPMN**: The standard BPMN elements, in their usual categories. - **Reusable assets**: Catalog assets, connectors, and templates from your project, together with resources the project already contains, such as forms, called processes, decisions, and RPA scripts. For the preferred cross-source view, select **Browse all** at the top of the menu. The [resource discovery guide](../modeling/browse-all-resources.md) explains how to search and filter catalog, shared, and project assets alongside standard elements and Marketplace connectors. To insert a catalog asset, select **Browse all > Catalog**, then select the asset. If **Browse all** isn't offered, open the **Reusable assets** tab and select the asset. This creates a new element with the template already applied. To apply a template to an element that already exists, use **Change element** from the context pad or follow [apply assets while modeling](#apply-assets-while-modeling). ### Search the compact menu The compact menu's search field searches both tabs. Search matches asset names, descriptions, and template IDs. While you search, the tabs are hidden and matches appear as a single ranked list. Within **Reusable assets**, entries are grouped with resources already in your project first, followed by named categories, remaining templates, and connectors. ## Find outdated assets in a diagram When an element references a template version that isn't the latest one, Camunda Hub flags it in two places in the modeler: - The problems panel reports `Element has updated template available.` as an information-level hint. Select the hint to focus the affected element on the canvas. - The properties panel of the focused element shows an **Update available** dropdown. It reports the version you can move to, for example `A new version of the template is available: 3`, and offers the **Update** and **Unlink** actions. Select **Update** to apply the latest version of the template to the element. The hint is reported once per affected element, and an element that already uses the latest version produces no hint. This signal covers every element template available to your diagram, whether it comes from the catalog or from your project. ## Review catalog updates for a project The project page collects the same information for a whole project, so you don't have to open each diagram to find outdated assets. To review the catalog assets a project uses: 1. Open the project in your workspace. 2. In the right sidebar of the project page, find the **Catalog updates** tile. The tile lists only the catalog assets that need attention — assets whose status is **Update available** or **Deprecated**. Assets that are up to date aren't listed. For each asset you see its name and status, followed by links to the diagrams in the project that use it. An asset used in more than two diagrams shows a **+N more files** control that expands the full list. Use the filter in the tile header to narrow the list to **All**, **Update available**, or **Deprecated**. Assets load five at a time, a **Showing X of Y** counter tracks how many are listed against the total, and **Load N more assets** appends the next batch. When the project has nothing to act on, including when it uses no catalog assets at all, the tile shows `All catalog assets in this project are up to date.` The tile is visible to any project member with read access, so you don't need catalog administrator permissions to see it. Updating a template still happens in the diagram — the tile points you to the diagrams that need the change. ## Next steps - [Find resources with Browse all](../modeling/browse-all-resources.md). - Learn more about [element templates](/components/hub/workspace/modeler/element-templates/using-templates.md). - For CoE members: Learn how to [manage the catalog](/components/hub/organization/manage-catalog/index.md) and [track catalog asset usage](/components/hub/organization/manage-catalog/track-asset-usage.md) across your organization. --- ## Using templates in Camunda Hub ## Applying templates To create, append, or change an element with a reusable asset, use **Browse all**. See the [resource discovery guide](../modeling/browse-all-resources.md) for the complete flow. The following steps apply a template to an existing element through the properties panel: If you have already published an [element template](/components/modeler/element-templates/about-templates.md) to your project or organization: 1. Open a BPMN diagram. 2. Make sure you're in [**Implementation** mode](../collaboration/implement-your-process.md#switch-to-implement-mode). 3. Select an element in the diagram that matches the element template's type. For example, if you've published a **Task** template, select a **Task** element in your diagram. 4. On the right side of the modeling interface, under **Details > Properties > Template**, click **Select**. This opens the **Choose element template** modal. 5. Select the element template. 6. (Optional) Instead of selecting a published template, click the blue shop icon in the top right of the modal to open the [Camunda Marketplace](/components/hub/workspace/modeler/modeling/camunda-marketplace.md). The Marketplace icon in **Choose element template** is a separate template-selection path. **Browse all** applies to the create, append, and change-element modeling menus. Applying a template stores it via the `modelerTemplate` property and the optional `modelerTemplateVersion` property on the selected element: ```xml ``` It also sets up custom fields on the diagram element and makes these available for inspection and editing. Properties which were not configured in the element template using custom fields will not be available for editing. ### Applying a template that defines operations Templates that define [operations](/components/modeler/element-templates/template-metadata.md#predefined-configurations-steps-and-presets) — for example, a connector template for a service with several operations — show their operations in the popup menu instead of applying the template directly. Select an operation to apply the template with that operation preselected, then complete the remaining fields in the properties panel. Operations are also matched by search, so you can search for the action you want to perform, such as `upload object`, without knowing which template provides it. Search matches an operation's name, description, and keywords together with those of its parent operations and its template. ## Removing templates To remove an applied template from an element, either the _Unlink_ or _Remove_ function can be used: 1. Open a BPMN diagram. 2. Make sure you're in [**Implementation** mode](../collaboration/implement-your-process.md#switch-to-implement-mode). 3. Select an element in the diagram that is linked to an element template. 4. On the right side of the modeling interface, under **Details > Properties > Template**, click **Applied**. 5. Select either: - **Unlink**: Remove the element template from the `modelerTemplate` property but keep the properties which were set. - **Remove**: Remove the element template from the `modelerTemplate` property and reset all properties of the respective element. ## Updating templates If a template is applied and a new version of the template is found, you can _update_ the template: 1. Open a BPMN diagram. 2. Make sure you're in [**Implementation** mode](../collaboration/implement-your-process.md#switch-to-implement-mode). 3. Select an element in the diagram that is linked to an element template. 4. On the right side of the modeling interface, under **Details > Properties > Template**, click **Update available**. Templates are updated according to the following rules: - If the property is set in the new template, it will override the existing value — unless the value was originally set by the old template and has been manually changed since. - If the property is not defined in the new template, it will unset. - Sub-properties of complex properties (for example, `zeebe:input`, `zeebe:output`) are handled according to these rules if they can be identified. ### Replacing templates If a template is deprecated with a new element template and you want to keep the same input values as in the deprecated template, you can: 1. [**Unlink**](#removing-templates): Remove the current template that is deprecated from the `modelerTemplate` property, but keep the properties which were set. 2. Click **Select** and apply the new element template. ## Missing templates If a template was applied to an element but cannot be found, editing of the element is disabled. To re-enable editing, either [_unlink_ or _remove_ the template](#removing-templates), or make it available by publishing a template with the same ID or creating a new one. ## Creating templates from existing elements To save a diagram element as a template: 1. Open a BPMN diagram. 2. Make sure you're in [**Implementation** mode](../collaboration/implement-your-process.md#switch-to-implement-mode). 3. Select an element in the diagram that supports being used as a template. 4. On the right side of the modeling interface, in the top right of the **Details** pane, click **Save as**. 5. Provide a **Name** and **Description**. :::tip If **Save as** is disabled, hover over it to read instructions on how to make the element support being used as a template. ::: The template can be further customized by [editing it](/components/connectors/manage-connector-templates.md). To use the template, you first need to [publish it to the project or organization](/components/hub/workspace/modeler/element-templates/manage-element-templates.md#publish-an-element-template). Learn more about [saving elements as templates](./save-as-element-templates.md). --- ## Configure IDP ## Prerequisites The following prerequisites are required for IDP: ### Amazon Web Services (AWS) When using AWS as your cloud provider, the following AWS-specific prerequisites are required. IDP supports both structured and unstructured document extraction with AWS services: | Prerequisite | Description | | :------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Amazon Web Services (AWS) IAM user and permissions](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users.html) | A valid AWS Identity and Access Management (IAM) user with permissions configured to allow access to Amazon Bedrock, Amazon S3, and Amazon Textract, such as:`AmazonBedrockFullAccess``AmazonTextractFullAccess`Access to the IDP Amazon Bedrock foundation models:For a list of models suported by IDP, see [extraction models](idp-reference.md#extraction-models).To learn more about configuring access to foundation models, refer to [add or remove access to Amazon Bedrock foundation models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access-modify.html).The [access key pair](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) (_access key_ and _secret access key_) for this IAM user. This is required during IDP configuration. | | [Amazon S3 bucket](https://aws.amazon.com/s3/) | An Amazon S3 bucket that can be used by IDP for document storage during document analysis and extraction.The bucket name must be unique across all your AWS accounts. | ### Google Cloud Platform (GCP) When using GCP as your cloud provider, the following GCP-specific prerequisites are required. IDP supports both structured and unstructured document extraction with GCP services: | Prerequisite | Description | | :---------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Google Cloud Vertex AI](https://cloud.google.com/vertex-ai/docs) (required for unstructured extraction) | Access to Vertex AI is required for performing unstructured document extractions using Google's generative AI models.Vertex AI provides access to foundation models and machine learning capabilities that power IDP's intelligent document processing features.To learn more about Vertex AI, refer to [Vertex AI documentation](https://cloud.google.com/vertex-ai/docs/start/introduction-unified-platform). | | [Google Cloud Storage (GCS) bucket](https://cloud.google.com/storage/docs) (required for unstructured extraction) | A Google Cloud Storage bucket that can be used by IDP for temporary document storage during Vertex AI analysis and unstructured extraction.The bucket must be accessible by the service account and located in the same region as your Vertex AI resources for optimal performance.To learn more about creating and managing Cloud Storage buckets, refer to [Cloud Storage documentation](https://cloud.google.com/storage/docs/creating-buckets). | | [Google Cloud Document AI](https://cloud.google.com/document-ai/docs) (required for structured extraction) | Access to Document AI is required for performing structured document extractions from forms, invoices, and other templated documents.Document AI provides specialized processors for extracting structured data from various document types with high accuracy.To learn more about Document AI capabilities, refer to [Document AI overview](https://cloud.google.com/document-ai/docs/overview). | | [Google Cloud Service Account](https://cloud.google.com/iam/docs/service-account-overview) | A valid GCP service account with appropriate permissions configured to allow access to Vertex AI, Document AI, and Cloud Storage services.The service account must have IAM roles that allow use of the services above based on your extraction needs (unstructured and/or structured).The [service account JSON key file](https://cloud.google.com/iam/docs/service-account-creds#user-managed-keys) is required during IDP configuration for authentication. | ### Microsoft Azure When using Azure as your cloud provider, the following Azure-specific prerequisites are required. IDP currently supports unstructured document extraction with Azure services: | Prerequisite | Description | | :----------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Azure AI Document Intelligence](https://learn.microsoft.com/en-us/azure/ai-services/document-intelligence/) | Access to Azure AI Document Intelligence is required for extracting text and structural information from uploaded documents during unstructured extraction.Document Intelligence uses machine learning models to automate data processing and provides essential capabilities for document analysis.Authentication is handled using the service endpoint and access keys provided by the Azure AI Document Intelligence resource.To learn more about Document Intelligence, refer to [Document Intelligence overview](https://learn.microsoft.com/en-us/azure/ai-services/document-intelligence/overview). | | [Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-foundry/) | Access to Azure AI Foundry is required for using different large language models (LLMs) during unstructured document extraction.Azure AI Foundry provides access to various foundation models and AI capabilities that power IDP's intelligent document processing features.Authentication is handled using the service endpoint and access keys provided by the Azure AI Foundry resource.To learn more about Azure AI Foundry, refer to [Azure AI Foundry documentation](https://learn.microsoft.com/en-us/azure/ai-foundry/what-is-azure-ai-foundry). | ### OpenAI Compatible Provider When using an OpenAI Compatible provider, you can bring your own model (BYOM) for unstructured document extraction. Any provider that implements the `/chat/completions` API endpoint is supported: | Prerequisite | Description | | :----------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | OpenAI Compatible API endpoint | Access to any provider that implements the OpenAI `/chat/completions` API for performing unstructured document extractions using your own model.This enables a bring your own model (BYOM) approach, allowing you to use custom or third-party models that are compatible with the OpenAI API specification.Authentication is handled through custom headers that you configure based on your provider's requirements.Examples of compatible providers include [Ollama](https://ollama.com/), self-hosted models, third-party API services, or custom inference endpoints that implement the OpenAI chat completions interface.To learn more about the API specification, refer to the [OpenAI API documentation](https://platform.openai.com/docs/api-reference/chat/create). | ### ABBYY Vantage [ABBYY Vantage](https://www.abbyy.com/vantage/) can be used as a third-party [text extraction engine](idp-reference.md#extraction-engines) for unstructured extraction and document classification. ABBYY runs independently of your cloud provider, so it can be combined with any provider's LLM. | Prerequisite | Description | | :------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ABBYY Vantage tenant | An ABBYY Vantage tenant in one of the supported regions:North America: `https://vantage-us.abbyy.com`Western Europe: `https://vantage-eu.abbyy.com`Australia: `https://vantage-au.abbyy.com`The region is set when the tenant is provisioned. Contact your ABBYY administrator if you do not know which region your tenant uses. | | ABBYY Vantage Public API client | An ABBYY Vantage Public API client used to authenticate via the OAuth2 client credentials flow.Created in the ABBYY Vantage web console under **Settings > Public API Clients**. Requires administrator permissions in your ABBYY Vantage tenant.Both the **Client ID** and **Client Secret** are required during IDP configuration. The Client Secret is only displayed at creation time. | | ABBYY OCR skill | An OCR skill in your ABBYY Vantage tenant that is configured to **output Text format**.The default ABBYY OCR skill output format cannot be edited. Copy the default OCR skill in the Skill Designer and configure the copy to output Text format.The **Skill ID** is the GUID shown in the URL when the skill is opened, and is required during IDP configuration. | ### General requirements The following prerequisites apply regardless of your cloud provider: | Prerequisite | Description | | :-------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Camunda Hub](/components/hub/workspace/modeler/index.md) | Camunda Hub is required to create, manage, publish, and integrate [IDP projects](idp-projects.md), [document extraction](idp-document-extraction.md) templates, and [document classification](idp-document-classification.md) templates.IDP does not support Desktop Modeler. | ## Cluster requirements {#cluster-requirements} When you create an IDP project, you select a deployment environment. Each environment maps to an underlying cluster, and the following requirements apply to that cluster: | Requirement | Description | | :-------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Connector secrets](#aws-secrets) | You must configure the required IDP AWS connector secrets on any cluster used with IDP. | | [Document handling](/components/document-handling/getting-started.md) | IDP requires a cluster that supports document handling. For example, a version 8.7 or higher cluster. | | Cluster health | IDP projects are only fully operational when linked to a healthy, active cluster. If needed, you can select an unstable or unhealthy cluster when first creating an IDP project, and change to a stable cluster when one is available. | :::info To learn more about storing, tracking, and managing documents in Camunda 8, see [document handling](/components/document-handling/getting-started.md). ::: ### Identity {#identity} If you are using an identity-enabled cluster, the following authorizations are required for IDP operations: | Resource type | Permission | Owner type | Owner | Description | | :----------------- | :---------------------- | :--------- | :------------- | :------------------------------------------------------------------- | | DOCUMENT | READ | Role | Connectors | Required for the idp connector to read the document from the cluster | | DOCUMENT | CREATE | User | `user's email` | Required to upload documents to the cluster during IDP extraction | | RESOURCE | CREATE | User | `user's email` | Required to deploy process instances | | PROCESS_DEFINITION | CREATE_PROCESS_INSTANCE | User | `user's email` | Required to start process instances | ## Configure IDP Once you have completed all the required prerequisites, configure IDP in a suitable `dev` cluster as follows. You only need to add the connector secrets for the cloud provider you plan to use. ### Add AWS connector secrets to cluster {#aws-secrets} If you are using AWS as your cloud provider, add the following AWS connector secrets required for IDP. - **SaaS:** Create and configure as [connector secrets](/components/saas/clusters/manage-secrets.md). - **Self-Managed:** Connector secrets are generally provided as environment variables, set via `values.yaml` or the command line. Add these connector secrets as environment variables for the Tasklist and Zeebe components. To learn more about using connector secrets in Self-Managed, see [managing secrets in Helm charts](/self-managed/deployment/helm/configure/secret-management.md) and [secrets in manual installations](/self-managed/components/connectors/connectors-configuration.md#secrets). | Connector secret Key | Required | Description | | :-------------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `IDP_AWS_ACCESSKEY` | Yes | The AWS access key ID used to interact with the Amazon S3 bucket. | | `IDP_AWS_SECRETKEY` | Yes | The AWS secret access key associated with the `IDP_AWS_ACCESSKEY`. | | `IDP_AWS_REGION` | Yes | The AWS region where documents can be temporarily stored during Amazon Textract analysis. This should match the region where the Amazon S3 bucket is located.Example: `us-east-1` (default) | | `IDP_AWS_BUCKET_NAME` | Yes | The name of the Amazon S3 bucket you want to use for document storage during extraction.Example: `idp-extraction-connector` | ### Add GCP connector secrets to cluster {#gcp-secrets} If you are using GCP as your cloud provider, add the following GCP connector secrets required for IDP. The secrets you need depend on which type of extraction you plan to use. | Connector secret Key | Required for Unstructured Extraction | Required for Structured Extraction | Description | | :--------------------------------- | :----------------------------------: | :--------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------- | | `IDP_GCP_SERVICE_ACCOUNT` | Yes | Yes | The GCP service account JSON key file content for authentication with GCP services. | | `IDP_GCP_VERTEX_REGION` | Yes | No | The GCP region where Vertex AI resources are located.Example: `us-central1` | | `IDP_GCP_VERTEX_PROJECT_ID` | Yes | No | The Vertex project ID where Vertex AI resources are configured.Example: `my-gcp-project-id` | | `IDP_GCP_VERTEX_BUCKET_NAME` | Yes | No | The name of the Google Cloud Storage bucket for temporary document storage during Vertex AI analysis.Example: `idp-vertex-extraction-bucket` | | `IDP_GCP_DOCUMENT_AI_REGION` | No | Yes | The GCP region where Document AI resources are located. Must be either `eu` or `us`.Example: `us` | | `IDP_GCP_DOCUMENT_AI_PROJECT_ID` | No | Yes | The DocumentAI project ID where Document AI resources are configured.Example: `my-gcp-project-id` | | `IDP_GCP_DOCUMENT_AI_PROCESSOR_ID` | No | Yes | The Document AI processor ID for the specific processor you want to use for structured extraction.Example: `1234567890abcdef` | ### Add Azure connector secrets to cluster {#azure-secrets} If you are using Azure as your cloud provider, add the following Azure connector secrets required for IDP. | Connector secret Key | Required | Description | | :----------------------------------------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `IDP_AZURE_DOCUMENT_INTELLIGENCE_ENDPOINT` | Yes | The endpoint URL for your Azure AI Document Intelligence resource. | | `IDP_AZURE_DOCUMENT_INTELLIGENCE_KEY` | Yes | The access key for your Azure AI Document Intelligence resource. | | `IDP_AZURE_AI_FOUNDRY_ENDPOINT` | Yes | The endpoint URL for your Azure AI Foundry resource. Construct this URL using the pattern: `https://.services.ai.azure.com/models`. | | `IDP_AZURE_AI_FOUNDRY_KEY` | Yes | The access key for your Azure AI Foundry resource. You can find this key in the details page of deployed base models or on the Azure AI Foundry "Overview" page. | | `IDP_AZURE_OPEN_AI_ENDPOINT` | Optional | The endpoint URL for your Azure OpenAI resource. Required only if you want to use OpenAI models. You can find this endpoint in the "Models + endpoints" page in the Azure AI Foundry dashboard. | | `IDP_AZURE_OPEN_AI_KEY` | Optional | The access key for your Azure OpenAI resource. Required only if you want to use OpenAI models. You can find this key in the "Models + endpoints" page in the Azure AI Foundry dashboard. | ### Add OpenAI Compatible provider secrets to cluster {#openai-compatible-secrets} If you are using an OpenAI Compatible provider, add the following connector secrets required for IDP. | Connector secret Key | Required | Description | | :------------------------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `IDP_OPENAI_COMPATIBLE_ENDPOINT` | Yes | The base URL where IDP can call the chat completions request. This should be the endpoint for your OpenAI Compatible provider's API. | | `IDP_OPENAI_COMPATIBLE_HEADERS` | Yes | Authentication headers for your OpenAI Compatible provider. If no authentication is required, use `{}`. For providers requiring authentication, include the necessary headers in JSON format (e.g., `{"Authorization": "Bearer your-api-key"}`). | ### Add ABBYY Vantage connector secrets to cluster {#abbyy-secrets} If you want to use [ABBYY Vantage](#abbyy-vantage) as a [text extraction engine](idp-reference.md#extraction-engines), add the following connector secrets. ABBYY Vantage can be combined with any cloud provider you have configured. | Connector secret Key | Required | Description | | :------------------------ | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `IDP_ABBYY_BASE_URL` | Yes | The base URL of your ABBYY Vantage instance.Example: `https://vantage-eu.abbyy.com` | | `IDP_ABBYY_CLIENT_ID` | Yes | The OAuth2 Client ID generated in the ABBYY Vantage **Public API Clients** settings. | | `IDP_ABBYY_CLIENT_SECRET` | Yes | The OAuth2 Client Secret paired with `IDP_ABBYY_CLIENT_ID`. | | `IDP_ABBYY_SKILL_ID` | Yes | The Skill ID of the ABBYY Vantage OCR skill used for text extraction. The skill must be configured to **output Text format**. The Skill ID is the GUID shown in the skill URL. | :::note - These connector secrets are used in IDP document extraction templates. See [integrate IDP into your processes](idp-integrate.md). - You can rename these connector secrets if you want to change the testing configuration used in other environments (such as `test`, `stage` or `prod`). If you do this, you must also change these names to match within the **Authentication** section of the Properties panel for any related published document extraction templates. ::: ## Example IDP deployment {#examples} The following examples show how you can deploy and configure IDP in your local development environment. ### Camunda 8 Run {#idp-c8run-example} To use [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) to deploy and run Camunda 8 with IDP in a local development environment: 1. Ensure you have completed the IDP [Amazon Web Services (AWS) prerequisites](#prerequisites) and have obtained your AWS [access key pair](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) (_access key_ and _secret access key_). 1. [Install Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run/install-start.md#install-and-start-camunda-8-run). For example, download the latest release of Camunda 8 Run for your operating system and architecture and open the .tgz file to extract the Camunda 8 Run script into a new directory. 1. Navigate to the `docker-compose-8.x` folder in the new c8run directory. 1. Open the `connector-secrets.txt` file, and add your AWS connector secrets. For example: ``` IDP_AWS_ACCESSKEY=AWSACCESSKEYID IDP_AWS_SECRETKEY=AWSSECRETACCESSKEYGOESHERE IDP_AWS_REGION=us-east-1 IDP_AWS_BUCKET_NAME=idp-extraction-connector ``` 1. Save and close the file. 1. Configure [document handling environment variables](/components/document-handling/getting-started.md) for the Tasklist and Zeebe components (for example, in the `.env` file). 1. Start Camunda 8 with Docker Compose. For example, run `docker compose up -d` in that directory. 1. Launch Camunda Hub at http://localhost:8070 and log in with the username `demo` and password `demo`. 1. Get started with IDP by creating a new [IDP project](idp-projects.md) in Camunda Hub. :::info To learn more about Docker Compose configurations and commands for local development, see [Docker Compose](/self-managed/quickstart/developer-quickstart/docker-compose.md). ::: ### Docker {#idp-docker-example} To use [Docker](/self-managed/deployment/docker/docker.md) to deploy and run Camunda 8 with IDP in a local development environment: 1. Ensure you have completed the IDP [Amazon Web Services (AWS) prerequisites](#prerequisites) and have obtained your AWS [access key pair](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) (_access key_ and _secret access key_). 1. Download the latest Camunda Docker Compose release artifact from the [camunda-distributions](https://github.com/camunda/camunda-distributions/releases) GitHub repository, and extract the file contents to your desired directory. 1. In the extracted directory: 1. Open the `connector-secrets.txt` file, and add your AWS connector secrets. For example: ``` IDP_AWS_ACCESSKEY=AWSACCESSKEYID IDP_AWS_SECRETKEY=AWSSECRETACCESSKEYGOESHERE IDP_AWS_REGION=us-east-1 IDP_AWS_BUCKET_NAME=idp-extraction-connector ``` 1. Save and close the file. 1. Configure [document handling environment variables](/components/document-handling/getting-started.md) for the Tasklist and Zeebe components. 1. [Configure Docker Compose environments](/self-managed/quickstart/developer-quickstart/docker-compose/configuration.md#choose-a-docker-compose-configuration). For example, run the full configuration in the extracted directory: ``` docker compose -f docker-compose-full.yaml up -d ``` 1. Launch Camunda Hub at http://localhost:8070 and log in with the username `demo` and password `demo`. 1. Get started with IDP by creating a new [IDP project](idp-projects.md) in Camunda Hub. :::info To learn more about using Docker Compose to run Camunda Self-Managed locally, see [Docker Compose](/self-managed/quickstart/developer-quickstart/docker-compose.md). ::: --- ## Document automation :::info Document automation has been superseded by [document classification](idp-document-classification.md) templates, which use LLMs to automatically classify documents by type. To learn more, see [document classification](idp-document-classification.md). ::: --- ## Document classification ## About document classification Document classification templates use [LLM foundation models](./idp-key-concepts.md#llms) to analyze and categorize documents into defined types based on their content. For example, incoming documents can be classified as invoices, contracts, identity documents, or any custom type you define. - Create a document classification template to categorize documents before routing them to the appropriate process step or [document extraction](./idp-document-extraction.md) template. - Classification templates are published as connector templates that can be [integrated into your processes](./idp-integrate.md), enabling you to route different document types to the correct downstream automation. - Choose and test different LLM models to find the model that best suits your budget and accuracy requirements. :::important Document classification templates require an environment on version 8.9 or later. ::: ## Create a classification template To create a new document classification template: 1. In your [IDP project](./idp-projects.md), click **Create new** and select **Classification template**. 2. **Name**: Enter a descriptive name for the classification template, such as "Incoming document classifier". 3. **Description**: Enter a description to provide more information about what types of documents this template classifies. 4. **Provider**: Select the cloud provider you want to use for classification. The available providers depend on the [connector secrets](./idp-configuration.md) configured for your environment's cluster. 5. Click **Create** to create and open the new classification template. ## Configure a classification template Complete the following steps to configure and publish a document classification template. ### Step 1: Define document types {#define-document-types} On the **Define classifications** tab, configure the document types that the LLM uses to classify incoming documents. #### Add document types You must define at least two document types before you can publish a classification template. You can add document types from preconfigured types or create custom types: **Add preconfigured document types** IDP provides a set of preconfigured document types (such as invoice, contract, identity document) to help you get started quickly. 1. Click **Add document type**. 2. Browse or search the list of available preconfigured document types. 3. Select the document types you want to add. **Create custom document types** Create your own document types for document categories specific to your business. 1. Click **Add document type** and select **Create custom type**. 2. **Name**: Enter a descriptive name for the document type (for example, "Purchase Order" or "Medical Claim"). 3. **Description**: Enter a description to help the LLM understand the characteristics of this document type. 4. **Classification instructions**: Provide specific instructions to guide the LLM in recognizing this document type. For example, describe key features, typical content, or distinguishing characteristics. #### Edit document types You can edit all aspects of a document type at any time, including: - **Name**: The display name of the document type. - **Description**: A description of the document type's characteristics. - **Classification instructions**: Instructions that guide the LLM in classifying documents of this type. - **Output value**: The value returned in the process when a document is classified as this type. For example, the document type "ID Document" might have an output value of `id-document`. This allows you to align classification output with your business process definitions. To edit a document type, select it from the list and modify the fields as needed. #### Remove document types To remove a document type, select the document type and use the actions menu to delete it. :::note You must have at least two document types defined to publish a classification template. ::: #### Configure fallback behavior {#fallback} You can configure the **fallback output value**, which is the value returned when a document cannot be classified as any of the defined types. By default, this value is `unclassified-document`. You can customize this value to align with your process routing logic. ### Step 2: Test classification {#test-classification} On the **Test template** tab, upload sample documents and evaluate the classification results using different LLM models. #### Upload test documents Upload sample documents that represent the types of documents you expect to classify. 1. Click **Upload documents** to browse for and upload your sample documents. Batch upload is supported. 2. For each upload batch, assign an **expected document type** to enable validation of classification results. The available document types are those you defined in [Step 1](#define-document-types). #### Run classification tests {#run-tests} Select an extraction engine and LLM model, then run classification tests against your uploaded documents. 1. **Extraction engine**: Select the [text extraction engine](./idp-key-concepts.md#extraction-engines) to use for text extraction before classification. 2. **Extraction model**: Select the LLM model to use for classification. 3. Click **Classify documents** to run the classification. #### Review classification results {#review-results} After running a classification test, the results are displayed for each document: - **Classified document type**: The document type assigned by the LLM. - **Reasoning**: The LLM's explanation for why it chose this document type. - **Tokens used**: The number of tokens consumed during classification. - **Latency**: The time taken for classification. Validation indicators show whether the classification matches the expected type. A **summary** of the classification results is shown, allowing you to quickly compare the success rate across different models. :::note Test different combinations of extraction engines and LLM models to find the combination that best suits your document types, budget, and accuracy requirements. ::: ### Step 3: Publish classification template {#publish-template} Publish the classification template to make it available for [integration into your processes](idp-integrate.md). 1. Click **Publish** and select either: - **Publish to workspace**: The classification template is made available to all projects within the workspace. - **Publish to organization**: The classification template is made available as a shared resource within your organization. This option is only available for Organization Owners or Organization Admins. 2. On the **Publish Classification Template** dialog, configure the publish settings: - **Extraction engine**: Select the text extraction engine to use for the published classification template. - **Extraction model**: Select the LLM model to use for the published classification template. - **Version name**: Enter a version name for the published classification template. - **Version description**: Enter a description for this version. 3. Click **Publish** to make the classification template available for [integration into your processes](./idp-integrate.md). :::note You must have at least two document types defined before you can publish a classification template. ::: ## Manage versions {#versions} Click **Versions** to view and manage your published classification template versions. ### Compare versions You can compare the change history between two template versions as JSON code in the diff layout. 1. Ensure that the sidebar **Show changes** toggle is turned on. 2. Select the version that you want to compare. The previous version is automatically selected for comparison. ### Restore a version 1. In the sidebar **Versions** list, hover over the version you want to restore. 2. Select the three vertical dots to open the actions menu. 3. Select **Restore as latest**. ### Update a version 1. In the sidebar **Versions** list, hover over the version you want to update. 2. Select the three vertical dots to open the actions menu. 3. Select **Edit** and enter a new name and/or description for the version. ### Delete a version 1. In the sidebar **Versions** list, hover over the version you want to delete. 2. Select the three vertical dots to open the actions menu. 3. Select **Delete**. 4. You are prompted to confirm the deletion. :::caution Deleting a classification template version is permanent. ::: --- ## Document extraction ## About document extraction Document extraction templates form the basis for using IDP in your end-to-end processes. - Create a separate document extraction template for each type of document you want to categorize and extract data from, such as an invoice, a report, identity document, and so on. - Published document extraction templates can then be used to [integrate IDP into your processes](idp-integrate.md). ## Create document extraction template To create a new document extraction template: 1. In your [IDP project](idp-projects.md), click **Create new** and select **Extraction template** to open the Create new project modal. 1. Select the **Extraction method** depending on whether your documents contain structured or unstructured data. - **Unstructured data extraction**: Extract data from unstructured documents. - **Structured form extraction**: Extract data from structured documents. 1. **Template Name**: Enter a descriptive name for the type of document, such as “Invoice type A” for example. 1. **Description**: Enter a description to provide more detailed information about the document type. 1. **Provider**: Select the cloud provider you want to use for document extraction. The available providers depend on the connector secrets configured for your environment's cluster. The four supported providers are: - **AWS**: Amazon Web Services with Bedrock and Textract (supports both structured and unstructured extraction) - **Azure**: Microsoft Azure with AI Document Intelligence and AI Foundry (unstructured extraction only) - **GCP**: Google Cloud Platform with Vertex AI and Document AI (supports both structured and unstructured extraction) - **OpenAI compatible**: Any provider that implements the OpenAI `/chat/completions` API (unstructured extraction only) :::note If the connector secrets for a specific provider are missing from your environment's cluster configuration, that provider will be unavailable for selection. To enable additional providers, configure the required connector secrets as described in the [IDP configuration guide](idp-configuration.md). ::: 1. Click **Create** to create and open the new document extraction template. 1. Configure and publish the template: - [Extract unstructured data](idp-unstructured-extraction.md): Configure and publish an unstructured data extraction template. - [Extract structured data](idp-structured-extraction.md): Configure and publish a structured data extraction template. :::tip Not sure which extraction method to use? See [structured and unstructured documents](idp-key-concepts.md#structured-and-unstructured-documents) to help determine what type of document(s) you will be processing. ::: --- ## Example IDP integration ## About this worked example This worked example demonstrates how a published [document extraction template](idp-document-extraction.md) can be used to extract data from a document uploaded via [Tasklist](/components/tasklist/introduction-to-tasklist.md). In this example, a process is set up with the following steps: 1. **Upload document**: A PDF document is uploaded manually in Tasklist. 1. **Extract data**: A published document extraction template automatically extracts the required data from the PDF document. 1. **View results**: The extraction results are viewed. ## Document extraction template The document extraction template used in this example uses the following extraction fields and sample document. | Field name | Prompt | | :-------------- | :------------------------ | | invoiceType | Find the type of invoice. | | invoiceCustomer | The invoice customer. | | invoiceId | The invoice ID. | ## Upload document In the first step of the process, a [user task](/components/modeler/bpmn/user-tasks/user-tasks.md) and linked [form](/components/modeler/forms/camunda-forms-reference.md) allows a document to be uploaded in Tasklist. - The form uses the [Filepicker](/components/modeler/forms/form-element-library/forms-element-library-filepicker.md) form element to upload a document. - The Filepicker element **Key** is set to `documents`. This is then bound to the **Document** input in the document extraction template. :::info You can also use the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) to upload documents for IDP. To learn more about storing, tracking, and managing documents in Camunda 8, see [document handling](/components/document-handling/getting-started.md). ::: ## Extract data In this step, the document extraction template is [applied to a task](idp-integrate.md#create-and-configure-an-idp-task) to automatically extract data from the uploaded document. - **Input message data**: The **Document** input uses the FEEL expression `documents[1]` to get the first document in the FEEL array, as per the uploaded document **Key**. - **Output mapping**: The extracted data is stored as JSON in a **Result variable** named `idpResult`. ## View results Once the process completes, the results of the extraction are available in the `idpResult` variable. For example, viewing the process in Operate shows the data was accurately extracted from the document as follows: ``` { "extractedFields": { "invoiceType": "A", "invoiceId": "A/3454", "invoiceCustomer": "Camunda" } } ``` :::note This step in the process could be one of many types of element, depending on what you want to do with the extraction results. For example, you might want to display, check, or summarize the extracted data, or route to further actions in the process depending on the document data extracted by IDP. ::: --- ## Integrate IDP into your processes :::tip New to IDP integration? See the [example IDP integration](idp-example.md) for a worked example of a simple IDP process. ::: ## About IDP integration When you publish a [document extraction](idp-document-extraction.md) or [document classification](idp-document-classification.md) template, IDP generates a connector template that you can apply to tasks in your BPMN diagrams. Each published template appears as a selectable element template in Camunda Hub. ## Create and configure an IDP task You can apply a published IDP template to a task or event via the append menu. For example: - **From the canvas:** Select an element and click the **Change element** icon to change an existing element, or use the append feature to add a new element to the diagram. - **From the properties panel:** Navigate to the **Template** section and click **Select**. - **From the side palette:** Click the **Create element** icon. You can then configure the document extraction template in the properties panel, via the following sections: ## Input message data ### Document Specify the document object variable used for document handling, provided as a [FEEL expression](/components/modeler/feel/what-is-feel.md) with the document reference. For example, if you have uploaded a document via form upload using a `documents` **Key**, you can specify `documents[1]` to retrieve the first document in the array. Example: `documents[1]`. :::info To learn more about storing, tracking, and managing documents in Camunda 8, see [document handling](/components/document-handling/getting-started.md). ::: ## Provider authentication ### Authentication Ensure the **Credentials** AWS authentication type is selected. ### Access key Specify your AWS _access key_ connector secret, provided as a [FEEL expression](/components/modeler/feel/what-is-feel.md). Example: `{{secrets.IDP_AWS_ACCESSKEY}}` ### Secret key Specify your AWS _secret access key_ connector secret, provided as a [FEEL expression](/components/modeler/feel/what-is-feel.md). Example: `{{secrets.IDP_AWS_SECRETKEY}}` ## Provider configuration ### AWS S3 Bucket name Specify the name of the Amazon S3 bucket where documents can be temporarily stored during Amazon Textract analysis as a connector secret, provided as a [FEEL expression](/components/modeler/feel/what-is-feel.md). Example: `{{secrets.IDP_AWS_BUCKET_NAME}}` (for the Amazon S3 bucket used for document storage during extraction). :::note The Amazon S3 bucket name must be unique across all your AWS accounts. ::: ### Region Specify the region where documents can be temporarily stored during Amazon Textract analysis as a connector secret, provided as a [FEEL expression](/components/modeler/feel/what-is-feel.md). This should match the region where the AWS S3 bucket is located. The default region is `us-east-1`. Example: `{{secrets.IDP_AWS_REGION}}` ## Output mapping Specify the process variables that you want to map and export the IDP extraction connector response into. :::info To learn more about output mapping, see [variable/response mapping](/components/connectors/use-connectors/index.md#variableresponse-mapping). ::: ### Result variable You can export the complete IDP extraction connector response (for example, the key value pairs extracted from the document) into a dedicated variable that you can then access anywhere in a process. To do this, enter a unique dedicated variable name in the **Result variable** field. Example: `IDPResult` ### Result expression In addition, you can choose to unpack the content of the response into multiple process variables using the **Result expression** field, as a [FEEL Context Expression](/components/concepts/expressions.md). ## Error handling If an error occurs, the IDP extraction connector throws an error and includes the error response in the error variable in Operate. ### Error expression You can handle an IDP extraction connector error using an Error Boundary Event and [error expressions](/components/connectors/use-connectors/index.md#error-expression). ## Retries ### Retries Specify the number of [retries](/components/connectors/use-connectors/outbound.md#retries) (times) the IDP extraction connector repeats execution if it fails. ### Retry backoff Specify a custom **Retry backoff** interval between retries instead of the default behavior of retrying immediately. ## Execution listeners Add and manage [execution listeners](/components/concepts/execution-listeners.md) to allow users to react to events in the workflow execution lifecycle by executing custom logic. --- ## IDP concepts ## Structured and unstructured documents {#documents} Documents are typically classified as containing either structured or unstructured data. ### Structured documents {#structured} Structured documents have a predefined, consistent layout and fixed format, such as rows and columns in a database or spreadsheet, or fields in a standardized form. Data in a structured document has a fixed location. For example, the ID, date, and company name are always located in the same place. Example structured documents include: - Invoices/ customer records - Forms - Identity documents ### Unstructured documents {#unstructured} Unstructured documents have a less defined, free-form layout that can be more difficult to extract structured data from, such as free-text paragraphs where key information is located in unpredictable places. IDP uses an [LLM foundation model](#llms) to extract data from this document type. Example unstructured documents include: - Emails - Reports - Memos ## Document classification {#classification} Document classification uses an [LLM foundation model](#llms) to analyze, categorize, and assign a document type to incoming documents based on their content. - Create a [document classification template](idp-document-classification.md) to define the document types you want to classify (such as invoices, contracts, or identity documents), test classification accuracy, and publish the template for use in your processes. - Classification enables you to route different document types to the correct downstream process step or [document extraction](idp-document-extraction.md) template. - Classification accuracy is improved with well-defined document types (including clear descriptions and classification instructions) and a set of test documents that accurately represents each type of document you want to process. ### Fallback output value {#fallback} The fallback output value is the value returned when a document cannot be classified as any of the defined types. By default, this value is `unclassified-document`, but you can customize it to align with your process routing logic. ### Preconfigured document types {#preconfigured-types} IDP provides a set of preconfigured document types (such as invoice, contract, identity document) to help you get started quickly when creating a [classification template](idp-document-classification.md#define-document-types). You can also create custom document types for categories specific to your business. ## Extraction model/Large Language Models (LLM) {#llms} LLM Foundation models are large-scale, pre-trained AI models that can be adapted for various document processing tasks without extensive retraining. - For IDP, these models serve as a powerful base for extracting, understanding, and processing data from diverse document types. Algorithms are used to learn document patterns and to improve data extraction accuracy over time. - IDP allows you to work with and test different extraction models until you find the model that best suits your budget and accuracy requirements. - See [extraction models](idp-reference.md#extraction-models) for a list of currently supported LLM extraction models. ## Text extraction engines {#extraction-engines} A text extraction engine determines how text is extracted from a document before the LLM processes its content. Different document types and quality levels benefit from different extraction approaches. - **Lightweight parsing** (Fast Extract): For digitally generated PDFs where text is already embedded, a fast built-in parser can extract text without OCR, reducing processing time and cost. - **OCR-based extraction** (AWS Textract, Azure Document Intelligence, GCP Document AI, ABBYY Vantage): For scanned or image-based documents, OCR engines provide high-accuracy text recognition from images. ABBYY Vantage is a third-party OCR engine that can be combined with any cloud provider's LLM. - **Multimodal**: For LLMs that support vision capabilities, the document can be sent directly to the model for native interpretation, bypassing a separate text extraction step entirely. You can select the extraction engine per unstructured extraction template during [extraction testing](idp-unstructured-extraction.md#extract-data), [validation](idp-unstructured-extraction.md#validate-extraction), and [publishing](idp-unstructured-extraction.md#publish-template), to optimize accuracy, performance, and cost for each document type. :::info For a full list of available extraction engines, see [text extraction engines](idp-reference.md#extraction-engines). ::: ## Extraction fields {#fields} Extraction fields are the data fields you want to extract from a document, such as an invoice ID, date, customer name, and so on. - You must add a separate field for each piece of information you want to extract from a document. - For example, for an invoice, add a separate field for the invoice ID, date, customer name, invoice amount, and so on. --- ## IDP projects ## Create an IDP project :::important Ensure your deployment environment's cluster meets the [IDP project requirements](idp-configuration.md#cluster-requirements). Camunda recommends using a development (`dev`) environment for IDP projects. ::: To create an IDP project: 1. In Camunda Hub, open a workspace. 1. On the **Products** page, click the **IDP projects** tab. 1. Click **Create IDP project**. 1. In the modal provide the following information: - **Name**: Enter a name for the IDP project. - **Deployment environment**: Select the environment you want to use for modeling and testing your document extraction and classification. Each environment is listed with its tag and health status (for example, **Paused** or **Healthy**). 1. Click **Create** to create the IDP project. 1. You can now create templates inside your IDP project folder: - [Extraction Template](idp-document-extraction.md): Extract data from structured or unstructured documents. - [Classification Template](idp-document-classification.md): Automatically classify documents by type. :::note Classification templates require an environment on version 8.9 or later. If the selected environment does not meet this requirement, only extraction templates are available. ::: ## Change the IDP project environment Open the **IDP project settings** modal to change the deployment environment used by an IDP project: 1. In your Camunda Hub workspace, open your IDP project. 1. Click **Settings** to open the IDP project settings. 1. Under **Deployment environment**, select the environment you want to use for the IDP project. 1. Click **Save** to save and apply your changes. --- ## IDP reference ## Technical architecture {#architecture} IDP offers a composable architecture that allows you to customize and extend IDP capabilities as needed. This flexibility enables you to adapt quickly to evolving business needs while maintaining a streamlined and manageable system. IDP allows you to create, configure, and publish **document extraction templates** and **document classification templates**. These are types of [connector templates](/components/connectors/custom-built-connectors/connector-templates.md). - **Document extraction templates** extract specific data fields from documents. See [document extraction](idp-document-extraction.md). - **Document classification templates** classify documents by type using LLMs. See [document classification](idp-document-classification.md). The document extraction template integrates with Camunda document handling connectors and APIs such as [Amazon S3](/components/connectors/out-of-the-box-connectors/amazon-s3.md), [Amazon Textract](/components/connectors/out-of-the-box-connectors/amazon-textract.md), [Amazon Comprehend](/components/connectors/out-of-the-box-connectors/amazon-comprehend.md), and [Amazon Bedrock](/components/connectors/out-of-the-box-connectors/amazon-bedrock.md) to retrieve, analyze, and process documents. 1. **Document upload**: The template accepts uploaded documents as input. These documents can be uploaded to a local document store, and their references used in the extraction process. For example, the connector uploads the document to an Amazon S3 bucket for extraction. 1. **Amazon Textract**: Uploaded documents are analyzed by Amazon Textract, which extracts text data and returns the results. The template configuration includes specifying the document, the S3 bucket name for temporary storage during Amazon Textract analysis, and other required parameters such as extraction fields and Amazon Bedrock Converse parameters. 1. **Amazon Bedrock**: Your [extraction field](idp-key-concepts.md#fields) prompts are used by Amazon Bedrock to extract data from the document. The extracted content is mapped to process variables, and the results stored in a specified result variable. :::note - You may encounter errors during extraction and validation if you have not added your Amazon AWS IAM account `access key` and `secret key` as a [connector secret](/components/saas/clusters/manage-secrets.md) to your environment's cluster. See [configuring IDP](idp-configuration.md). ::: ### Document storage {#storage} IDP stores documents as follows during the different extraction stages: 1. Camunda Hub: [Uploaded sample documents](idp-unstructured-extraction.md#upload-documents) are stored within Camunda Hub itself (SaaS) or the database (Self-Managed). 1. Environment: During [extraction testing](idp-unstructured-extraction.md#extract-fields) (for example, when you click **Extract document**) the document is stored in the environment's cluster using the [document handling](/components/document-handling/getting-started.md) API. 1. Extraction: Finally, when you extract content using a document extraction template, it is stored in an [Amazon AWS S3 bucket](idp-configuration.md#prerequisites), where it can be accessed by AWS Textract. :::info To learn more about storing, tracking, and managing documents in Camunda 8, see [document handling](/components/document-handling/getting-started.md). ::: ## Document file formats {#file-formats} IDP currently only supports data extraction from the following uploaded document file formats. | File format | Description | | :---------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | PDF | PDF documents must not be password protected.Maximum document file size is 4MB for all IDP operations.Both text and image content can be extracted from a PDF document. For example, data can be extracted from a scanned image that has been converted to PDF. | ## Document language support {#languages} IDP supports data extraction and processing of documents in multiple languages. Language support depends on the [text extraction engine](#extraction-engines) you use. For example, with the AWS provider, IDP integrates with [Amazon Textract](/components/connectors/out-of-the-box-connectors/amazon-textract.md), which supports multilingual text extraction and can detect and extract text in multiple languages. Other extraction engines (Azure Document Intelligence, GCP Document AI) also support multiple languages. Refer to the respective provider documentation for details. :::note At the time of the 8.7 release (April 2025), Amazon Textract can detect printed text and handwriting from the Standard English alphabet and ASCII symbols, and can extract printed text, forms and tables in English, German, French, Spanish, Italian and Portuguese. Refer to [Amazon Textract FAQs](https://aws.amazon.com/textract/faqs/) for current information on supported languages. ::: ## Extraction models {#extraction-models} You can choose from the following supported LLM extraction models during [data extraction](idp-unstructured-extraction.md#extract-fields). The available models depend on the cloud provider you configure for your document extraction template. ### AWS extraction models The following models are available when you use the AWS provider with Amazon Bedrock: | Extraction model | Model provider | Documentation | | :------------------- | :----------------------------------------- | :------------------------------------------------------------------------------------------------------ | | Claude Sonnet 4 | [Anthropic](https://www.anthropic.com/) | [Anthropic's Claude in Amazon Bedrock](https://aws.amazon.com/bedrock/claude/) | | Claude 3.5 Sonnet | [Anthropic](https://www.anthropic.com/) | [Anthropic's Claude in Amazon Bedrock](https://aws.amazon.com/bedrock/claude/) | | Claude 3 Sonnet | [Anthropic](https://www.anthropic.com/) | [Anthropic's Claude in Amazon Bedrock](https://aws.amazon.com/bedrock/claude/) | | Claude 3 Haiku | [Anthropic](https://www.anthropic.com/) | [Anthropic's Claude in Amazon Bedrock](https://aws.amazon.com/bedrock/claude/) | | Llama 3 70B Instruct | [Meta](https://www.meta.com/gb/) | [Meta's Llama in Amazon Bedrock](https://aws.amazon.com/bedrock/llama/) | | Llama 3 8B Instruct | [Meta](https://www.meta.com/gb/) | [Meta's Llama in Amazon Bedrock](https://aws.amazon.com/bedrock/llama/) | | Titan Text Premier | [Amazon AWS](https://docs.aws.amazon.com/) | [Amazon Titan Text models](https://docs.aws.amazon.com/bedrock/latest/userguide/titan-text-models.html) | :::note Amazon Bedrock LLM extraction models are only available in specific regions. - You must ensure your selected environment's cluster region supports the LLM extraction model you want to use. For example, if you are using the `eu-central-1` region, you cannot use Claude 3 Haiku as it is only available in US regions. - If you have chosen a model not supported in your region, you will receive a 403 "You don't have access to the model with the specified model ID" exception error. - Some newer models (including Claude Sonnet 4) require cross-region inference profiles and are automatically handled by IDP. When you select these models, IDP infers the appropriate regional prefix (`us.`, `eu.`, `apac.`, or `us-gov.`) from your configured AWS region and adds it to enable access across supported regions within your geographic area. For current regional support information, refer to [supported foundation models in Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html). For more details about cross-region inference, see [inference profiles](https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-support.html). ::: ### Azure, GCP, and OpenAI Compatible extraction models When using Azure, GCP, or an OpenAI Compatible provider, the available extraction models depend on the models deployed and accessible through your provider configuration: - **Azure**: Models available through your [Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-foundry/) deployment, including Azure OpenAI models if configured. See [Azure connector secrets](idp-configuration.md#azure-secrets). - **GCP**: Models available through your [Google Cloud Vertex AI](https://cloud.google.com/vertex-ai/docs) deployment. See [GCP connector secrets](idp-configuration.md#gcp-secrets). - **OpenAI Compatible**: Any model accessible through your OpenAI Compatible API endpoint. The **Extraction model** field accepts custom model IDs, allowing you to specify the exact model to use. See [OpenAI Compatible connector secrets](idp-configuration.md#openai-compatible-secrets). ## Text extraction engines {#extraction-engines} Text extraction engines determine how text is extracted from your documents before the LLM processes the content. You can [select an extraction engine](idp-unstructured-extraction.md#extract-data) per unstructured extraction template to optimize for accuracy, performance, and cost based on your document type. | Extraction engine | Provider | Best for | Description | | :-------------------------- | :------------------ | :---------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Fast Extract | Built-in | Digitally generated PDFs | A lightweight, built-in PDF text parser. Faster and lower cost than OCR-based engines, but does not support scanned or image-based documents. | | Multimodal | Provider LLM | Documents where the LLM has vision capabilities | Sends the document directly to the LLM for native interpretation, bypassing a separate text extraction step. Useful when the LLM supports multimodal (text and image) input. | | AWS Textract | AWS | Scanned or image-based documents | Uses [Amazon Textract](/components/connectors/out-of-the-box-connectors/amazon-textract.md) OCR for high-accuracy text extraction. Requires AWS provider configuration. | | Azure Document Intelligence | Azure | Scanned or image-based documents | Uses [Azure AI Document Intelligence](https://learn.microsoft.com/en-us/azure/ai-services/document-intelligence/) for OCR-based text extraction. Requires Azure provider configuration. | | GCP Document AI | GCP | Scanned or image-based documents | Uses [Google Cloud Document AI](https://cloud.google.com/document-ai/docs) for OCR-based text extraction. Requires GCP provider configuration. | | ABBYY Vantage | ABBYY (third-party) | Scanned or image-based documents | Uses [ABBYY Vantage](https://www.abbyy.com/vantage/) OCR for text extraction. Available across all cloud providers when [ABBYY connector secrets](idp-configuration.md#abbyy-secrets) are configured for your environment's cluster. | :::note The available extraction engines depend on the configuration of your environment's cluster and the cloud provider selected for your document extraction template. For example, AWS Textract is only available when using the AWS provider, while ABBYY Vantage is available across all providers once its connector secrets are configured. See [configuring IDP](idp-configuration.md) for setup details. ::: ## Optical Character Recognition (OCR) {#ocr} Optical Character Recognition (OCR) technology is used by several [text extraction engines](#extraction-engines) to detect and extract text and layout from scanned or digital documents. You can use the following OCR-based extraction engines: - **AWS Textract**: Used for both structured and unstructured extraction with the AWS provider. - **Azure Document Intelligence**: Used for unstructured extraction with the Azure provider. - **GCP Document AI**: Used for both structured and unstructured extraction with the GCP provider. - **ABBYY Vantage**: A third-party OCR engine available for unstructured extraction and document classification across all cloud providers. Requires the [ABBYY connector secrets](idp-configuration.md#abbyy-secrets) to be configured for your environment's cluster. ### AWS Textract OCR capabilities Structured data extraction with the AWS provider uses Amazon Textract: - Extracts text, layout, and key-value pairs. - Supports horizontal text only. - Supports English handwriting. - Supported languages for typed characters: Spanish, German, French, Italian, Portuguese. Known limitations: - No language detection. - No vertical text support. - Limited support for complex custom fields. - No detection of table headers. ## Table data extraction {#table-data} IDP can extract table data using LLM foundation models to identify and structure tabular data based on your prompts. ### Default JSON extraction format When extracting repeated elements from a document, the extraction defaults to JSON format unless instructed. In this format: - Table data is represented as an array of objects. - Each object corresponds to a row. - Column names are used as object keys, with values mapped accordingly. #### Example JSON output **Prompt:** "Extract a list of name and ages of patients on floor 1". ```json [ { "name": "Kaitlin Jones", "age": 41 }, { "name": "Thomas Hampton", "age": 57 } ] ``` ### CSV extraction To extract table data in CSV format, specify this in the prompt. The output is then structured in a CSV-compatible format. #### Example CSV output **Prompt:** "Extract a list of name and ages of patients on floor 1 as CSV". ```csv Name,Age Kaitlin Jones,41 Thomas Hampton,57 ``` ### Customize table data extraction You can further refine table extraction by: - Explicitly specifying column headers. - Defining delimiter preferences for CSV. - Requesting additional context for ambiguous data. ## Access rights and permissions Access to IDP features is determined by your Camunda Hub user role and associated [access rights and permissions](/components/hub/organization/manage-workspaces/manage-workspace-members.md#workspace-roles). For example, users with a Viewer or Commenter role only have read-only access to IDP features, and cannot upload documents, manage extraction fields, or publish templates. | Feature | Viewer/Commenter | Editor/Workspace Admin | Organization Admin or Organization Owner | | :----------------------------------------- | :-----------------------------------------------------------------------: | :-------------------------------------------------------------------: | :-------------------------------------------------------------------: | | View IDP project | | | | | View document extraction | | | | | View document classification | | | | | View documents | | | | | View extraction fields/prompts | | | | | View validate extraction | | | | | Create/edit/delete IDP project | | | | | Create/edit/delete document extraction | | | | | Create/edit/delete document classification | | | | | Add/edit/delete document types | | | | | Upload/delete documents | | | | | Add/edit/delete extraction fields/prompts | | | | | Extract data | | | | | Test classification | | | | | Save as test case | | | | | Validate extraction (test documents) | | | | | Publish template | | | | | View versions | | | | | Manage versions (edit, restore, delete) | | | | **Key:** Full access | Read-only access ## Validation status {#status} During validation, a validation status is shown for extraction fields to indicate the accuracy of the extracted data. | Icon | Status | Description | | :-------------------------------------------------------------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------ | | | Pass | The document validation passed with accurate and expected results. | | | Caution | A test case is missing for comparison. Click **Save test case** to create a test case for this field. | | | Fail | The validation results do not match the expected output for the document. Click **Review document** to investigate and resolve. | ### Example The following example shows the results of a partially successful extraction against three documents. The expanded `contract_start_date` field shows that each document returned different validation results. - The first document passed the validation, with the **Extracted value** matching the **Expected test case output**. - The second document failed validation as the **Extracted value** did not match the **Expected test case output**. Click **Review document** to open the document again and check the prompt for this field. - The third document could not be validated as a test case was not found for comparison. Click **Save test case** to create a test case for the document. --- ## Extract structured data ## About structured data extraction Use this extraction method for documents with a consistent layout, such as invoices, tax forms (for example, W-2s, VAT declarations), and loan or insurance applications. Structured extraction allows you to: - Upload a sample document. - Automatically discover fields and tables. - Configure the fields and tables you want included in your template. - Receive confidence scores for each extracted value. - Build reusable templates. - Integrate extracted data into BPMN processes via variables. :::info Structured extraction uses [Optical Character Recognition (OCR)](idp-reference.md#ocr) technology. ::: ## Data extraction steps Complete the following steps to configure and publish a structured data extraction template: 1. [Create template and upload sample document](#create): Create a new extraction template, add a name and description, select a provider, and upload a sample document. 2. [Configure template](#configure): Review the extracted results, select which fields and tables to include, and adjust field names if necessary. 3. [Test data extraction](#test): (Optional) Test the configuration of your template against other documents to evaluate how well it performs. 4. [Publish](#publish): Publish the template to make it available for use in your BPMN diagrams, processes, and [document automation](idp-document-automation.md) projects. ## Step 1: Create template and upload sample document {#upload} In your [IDP project](idp-projects.md), click **Create new**, select **Extraction template**, then select **Structured form extraction**, and enter a name, description, and select the provider. :::note After publishing, the template name and description is shown in the element selector when used in a process diagram. Use a clear name and concise description to help other users find and understand when to use the template. ::: ![Create form extraction template](img/idp-create-extraction-project.png) You can edit the description and provider later via the vertical ellipses menu button next to the template name, but changes to the template are only applied after republishing. After creating the template, the new template screen opens. You can upload a sample document that represents the type of document you want to extract data from. ![Upload a sample document](img/idp-structured-instructions-upload.png) To upload your sample document: 1. Drag your sample document into the box or click **Drag and drop a PDF file here or click to upload a file** to browse and upload your sample document. 2. Once you have finished uploading your sample document, the extraction process starts automatically. - The extraction process retrieves the fields and tables from the document. - The extracted fields and tables are displayed in the **Fields** and **Tables** tabs. ![Extracted fields and tables](img/extracted-fields-and-tables.png) ## Step 2: Configure template {#configure} After the extraction process of the sample document is complete, you can configure the template to include only the fields and tables you want to be part of your template. 1. Select the fields you want to include in your template by clicking the checkbox next to each field. ![Extracted fields and tables - select fields](img/extracted-fields-and-tables-configure.png) 2. Select the tables you want to include in your template by clicking the checkbox next to each table. ![Extracted fields and tables - select table](img/extracted-fields-and-tables-configure-tables.png) ### Extracted Fields - **Field name:** Enter a descriptive name for the field, used to identify the field in your template. You can change the name as required. - **Key:** The field key. This matches the key of the extracted field from the uploaded document. - **Value:** The extracted value. - **Confidence score:** How confident the model is in the extracted value. ### Extracted Tables - **Table name:** Enter a descriptive name for the table, used to identify the table in your template. You can change the name as required. - **Min confidence score:** The minimum confidence score of a field in the table. - **Average confidence score:** The average confidence score of all fields in the table. Once you are satisfied with your template configuration, you can test it to validate how well it performs on other documents, or you can publish it directly from this tab. ## Step 3: Test data extraction (optional) {#test} Testing is optional but recommended, as you can evaluate the performance of the extraction template before publishing. Testing allows you to see how accurately the template extracts data from other documents of the same type. This ensures better results when using the template in your processes. ![Upload document for testing](img/idp-upload-test-template-empty.png) To test the data extraction: 1. Drag your test document in the box or click **Drag and drop a PDF file here or click to upload a file** to browse and upload your test document. 2. Once you have finished uploading your test document, click **Test extraction template**. 3. The extraction process starts looking for the fields and tables you have selected in your template. ![Extracted fields and tables - test](img/idp-upload-test-template.png) ### Test summary results After the extraction is complete, a summary of the test results is shown. - **Average confidence**: The overall confidence score (as a percentage) for all extracted data. - **Average number of fields extracted**: Number of fields successfully extracted compared to the total expected (for example, "3 / 3"). - **Average number of tables extracted**: Number of tables successfully extracted compared to the total expected (for example, "0 / 1"). ### Detailed results The detailed results section provides a comprehensive view of each tested document: - **Filename**: The name of the uploaded test document. - **Avg. confidence**: A visual confidence bar showing the extraction confidence as a percentage. - **Extracted fields**: Number of successfully extracted fields out of the total. - **Extracted tables**: Number of successfully extracted tables out of the total. - **Actions**: - **View Extraction**: Click to see the detailed extraction results for each field and table. ![View Extraction test template](img/idp-test-template-view-extraction.png) - **Remove**: Delete the test document from the results. You can test multiple documents by: - Clicking **Upload documents** to add more test files. - Clicking **Rerun tests** to test additional documents. ![Extracted test templates](img/idp-extracted-test-template.png) ## Step 4: Publish {#publish} Publish the document extraction template to make it available for [integration into your processes](idp-integrate.md). 1. Click **Publish** and select either: - **Publish to workspace**: The document extraction template is made available to all projects within the workspace. - **Publish to organization**: The document extraction template is made available to all workspaces within the organization. This option is only available for Organization Owners or Organization Admins. 1. On the **Publish Extraction Project** dialog, configure the publish settings. - **Version name**: Enter a version for the published document extraction template. - **Version description**: Enter a description for the published document extraction template version. 1. Click **Publish** to make the document extraction template available for [integration into your processes](idp-integrate.md). ![IDP template element](img/idp-project-in-bpmn.png) ## Versions {#versions} Click **Versions** to view and manage your published document extraction template versions. ### Compare versions You can compare the change history between two template versions as JSON code in the diff layout. 1. Ensure that the sidebar **Show changes** toggle is turned on. 1. Select the version that you want to compare. The previous version is automatically selected for comparison. The JSON for the previous version is shown on the left, with the currently selected version shown on the right. - Differences between the versions are highlighted in the JSON. - For example, if an extraction field was added, this change is highlighted in green. ### Restore a version You can restore a version to revert to a previous snapshot of a document extraction template. 1. In the sidebar **Versions** list, hover over the version you want to restore. 1. Select the three vertical dots to open the actions menu. 1. Select **Restore as latest**. ### Update a version You can update a version name and description at any time. 1. In the sidebar **Versions** list, hover over the version you want to rename. 1. Select the three vertical dots to open the actions menu. 1. Select **Edit** and enter a new name and/or description for the version. ### Delete a version You can permanently delete a document extraction template version. 1. In the sidebar **Versions** list, hover over the version you want to rename. 1. Select the three vertical dots to open the actions menu. 1. Select **Delete**. 1. You are prompted to confirm the deletion. - Select **Delete version** to permanently delete the version. - Select **Cancel** to cancel the deletion and return to the versions list. :::caution Deleting a document extraction template version is permanent. ::: --- ## Extract unstructured data ## Data extraction steps {#date-extraction} Complete the following steps to configure and publish an unstructured data document extraction template: 1. [Upload documents](#upload-documents): Upload a set of sample documents to use for training the extraction model. 1. [Extract fields](#extract-fields): Add and configure the [extraction fields](idp-key-concepts.md#fields) you want to use to extract data. 1. [Validate extraction](#validate-extraction): Test and evaluate the data extraction results, and publish the document extraction template. ## Step 1: Upload documents {#upload-documents} Start by uploading a set of sample PDF documents that represent the specific document type you want to extract data from: 1. Click **Upload documents** to browse for and upload your sample document(s). 1. Once you have finished uploading your sample document(s) and want to start testing data extraction, either: - Select the **Extract data** tab. - Click the **Extract** icon for the document you want to extract data from. You will use these documents throughout the data extraction process. ### Document upload guidelines {#document-upload-guidelines} Start by uploading a sample document that contains all the data fields you want to extract for this type of document. - If a single document does not include all the data fields you require, upload multiple documents to cover all variations of the document type. The number and range of documents you need to upload depends on the complexity of your unstructured data and your requirements. - For example, you must upload at least one sample document for each variation of a document type. This should provide enough extraction accuracy if it is an exact representation of the specific type of document, with no variations in layout or content. However, it is more likely that you will need to upload multiple documents to ensure acceptable extraction accuracy. - When choosing your sample documents, variation is important to ensure the system captures the full range of document types it will encounter. As a general guideline, Camunda recommends starting with three to five documents, and uploading more as needed to represent the full range of possible data types. ## Step 2: Extract data {#extract-fields} On the **Extract data** tab, add the data [extraction fields](idp-key-concepts.md#fields) you want to populate with data from your document(s): - Add a separate extraction field for each piece of information you want to extract. For example, for an invoice, you might add a field for the invoice ID, date, customer name, amount, and so on. - You can then extract data from your sample document(s) using your chosen LLM extraction model, edit and refine your fields, and save the extracted data as a test case to compare outcomes across different extraction models. ### Add extraction fields {#add-fields} Add an extraction field for each piece of data you want to extract from your document(s): 1. **Field name**: Enter a descriptive name for the field. - The name format should follow [FEEL naming convention](/components/modeler/feel/language-guide/feel-variables.md#variable-names), for example it is case sensitive and should not include spaces. - The **Field name** is used as an output variable in a BPMN process. - Example: "invoiceId” or "invoice_id". 1. **Prompt**: Enter a clear and specific prompt to guide the LLM in accurately extracting data. - Try to describe the expected outcome in the prompt in clear and concise terms. For guidance and best practice when writing prompts, refer to the [documentation for your chosen LLM extraction model](idp-reference.md#extraction-models). - Example: For an "invoiceDate" field, you might use "The date when the invoice was issued". 1. Click **Add** to add the field. 1. Repeat the process until you have added all required extraction fields. :::note You can edit and delete extraction fields at any time. Click the three vertical dots next to the field to open the Options menu. There's currently no way to reorder existing fields directly; delete and re-add them in the order you want instead. Extraction fields and prompts are stored as part of the document extraction template in Camunda Hub, not in the BPMN file, so they can't be edited outside Camunda Hub (for example, in an external source control tool). ::: ### Extract data and save as test case {#extract-data} Once you have added your extraction fields, select a text extraction engine and LLM model, and test the data extraction. #### Extraction engine selection With the **Extraction engine** dropdown, you can choose how text is extracted from your documents before the LLM processes the content. Select the engine that best matches your document type and processing needs: - **Fast Extract**: A lightweight, built-in PDF text parser for digitally generated PDFs. This option is faster and lower cost, but does not support scanned or image-based documents. - **Multimodal**: Sends the document directly to the LLM for native interpretation, bypassing a separate text extraction step. Useful when the LLM supports vision/multimodal capabilities. - **AWS Textract**: Uses Amazon Textract OCR for high-accuracy text extraction from scanned or image-based documents. - **Azure Document Intelligence**: Uses Azure AI Document Intelligence for OCR-based text extraction from scanned or image-based documents. - **GCP Document AI**: Uses Google Cloud Document AI for OCR-based text extraction from scanned or image-based documents. - **ABBYY Vantage**: Uses [ABBYY Vantage](https://www.abbyy.com/vantage/) OCR for text extraction. Unlike provider-specific engines, ABBYY Vantage is available across all cloud providers once its [connector secrets](idp-configuration.md#abbyy-secrets) are configured for your environment's cluster. :::note The available extraction engines depend on your environment's cluster configuration and the cloud provider you select for your document extraction template. AWS Textract, Azure Document Intelligence, and GCP Document AI are only available when using their respective provider, while ABBYY Vantage is available across all providers when its connector secrets are configured. ::: #### Model selection The **Extraction model** field is both a dropdown and an input field, giving you flexibility in model selection: - **Dropdown selection**: Choose from a list of pre-configured models available in the dropdown. - **Custom model input**: If you want to use a model ID that is not part of the dropdown, you can type it directly into the field. This is useful for custom models or specific model versions that may not be listed in the default options. #### Extract and test 1. **Extraction engine**: Select the text extraction engine you want to use. 1. **Extraction model**: Select the LLM model you want to use. 1. Select the document you want to test the data extraction against. 1. Click **Extract document**. 1. The **Extraction fields** are populated with the extracted document data. - Check the extracted data is accurate and matches what you require from the document. - For incorrect field results, edit the field **Prompt** and retry the data extraction until the results are accurate. - Add additional fields as required during testing. 1. Click **Save as test case** to save the results as a test case. - The **Expected output** for each field is now shown below the actual extracted value. - Any unexpected extraction results for the field are highlighted. 1. (Optional) Test different LLM models with this test case to compare results and determine which model produces the most accurate extraction. 1. Repeat the process of creating and evaluating a test case for your other uploaded sample documents. 1. Once you are ready to validate your data extraction configuration, select the **Validate extraction** tab. :::note - Running an extraction creates a "test" process instance. You can view this in [Operate](/components/operate/operate-introduction.md). - You will achieve different results using different [extraction models](idp-reference.md#extraction-models) and extraction engines. Test different combinations until you find the one that best suits your document type, budget, and accuracy requirements. - You can save and overwrite a test case at any time with your latest results. ::: ## Step 3: Validate extraction {#validate-extraction} On the **Validate extraction** tab, test and validate the configured data extraction against your uploaded documents. This step evaluates the data extraction results produced by the selected extraction engine and LLM extraction model using your extraction fields and prompts. ### Validate extraction To validate the data extraction: 1. Select the **Extraction engine** you want to use for validation. 1. Select the **Project extraction model** you want to use for validation. 1. Click **Test documents** to run the extraction validation against your uploaded sample documents. 1. The extraction validation results are shown in the **Test Case Results** column. - The [validation status](idp-reference.md#status) is shown for each field to indicate the accuracy of the data extracted from each document. For example, if the extracted value matches the expected test case output, it is shown as a “Pass”. - Click on a field to expand the detailed results for each individual document. - A **Field extraction summary** shows a summary percentage value for the overall extraction accuracy to allow you to quickly compare extraction accuracy between different LLM extraction models. 1. Perform any actions required due to the validation results such as saving missing test cases or reviewing documents. If your validation results remain unsatisfactory, try the following before rerunning the validation: - Change the extraction engine to try a different text extraction approach (for example, switch from PDF Parser to an OCR-based engine for scanned documents). - Change the extraction model to try and obtain more accurate results with a different model. - Edit your extraction field prompts. Select the three vertical dots on a field to open the actions menu, and select **Edit**. - Go back to a previous step and edit your data extraction configuration, or upload more sample documents. 1. Once you are satisfied with the extraction accuracy, extraction engine, and extraction model, publish the document extraction template. ### Publish document extraction template {#publish-template} Publish the document extraction template to make it available for [integration into your processes](idp-integrate.md). 1. Click **Publish** and select either: - **Publish to workspace**: The document extraction template is made available to all projects within the workspace. - **Publish to organization**: The document extraction template is made available to all workspaces within the organization. This option is only available for Organization Owners or Organization Admins. 1. On the **Publish Extraction Project** dialog, configure the publish settings. - **Extraction engine**: Select the text extraction engine to use for the published document extraction template. - **Version name**: Enter a version for the published document extraction template. - **Version description**: Enter a description for the published document extraction template version. - **Extraction model**: Select the extraction model you want to use for the published document extraction template. 1. Click **Publish** to make the document extraction template available for [integration into your processes](idp-integrate.md). :::note - The most recent **Field extraction summary** results are shown for your chosen **Extraction engine** and **Extraction model** combination. ::: ## Versions {#versions} Click **Versions** to view and manage your published document extraction template versions. ### Compare versions You can compare the change history between two template versions as JSON code in the diff layout. 1. Ensure that the sidebar **Show changes** toggle is turned on. 1. Select the version that you want to compare. The previous version is automatically selected for comparison. The JSON for the previous version is shown on the left, with the currently selected version shown on the right. - Differences between the versions are highlighted in the JSON. - For example, if an extraction field was added, this change is highlighted in green. ### Restore a version You can restore a version to revert to a previous snapshot of a document extraction template. 1. In the sidebar **Versions** list, hover over the version you want to restore. 1. Select the three vertical dots to open the actions menu. 1. Select **Restore as latest**. :::note You can't edit a previous version directly. To make changes based on an older version, restore it as the latest version first, then edit it. ::: ### Update a version You can update a version name and description at any time. 1. In the sidebar **Versions** list, hover over the version you want to rename. 1. Select the three vertical dots to open the actions menu. 1. Select **Edit** and enter a new name and/or description for the version. ### Delete a version You can permanently delete a document extraction template version. 1. In the sidebar **Versions** list, hover over the version you want to rename. 1. Select the three vertical dots to open the actions menu. 1. Select **Delete**. 1. You are prompted to confirm the deletion. - Select **Delete version** to permanently delete the version. - Select **Cancel** to cancel the deletion and return to the versions list. :::caution Deleting a document extraction template version is permanent. ::: --- ## Modeling in Camunda Hub Camunda Hub provides a collaborative, browser-based interface for modeling BPMN, DMN, Camunda Forms, and element templates for Camunda 8. It enables teams to collaboratively design, implement, and deploy processes. ## Key features | Feature | Description | | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | [BPMN, DMN, and Forms modeling](../../../modeler/bpmn/bpmn.md) | Create and edit BPMN diagrams, DMN decision tables, and Camunda Forms directly in your browser. | | [Element templates](./element-templates/using-templates.md) | Reuse building blocks to standardize modeling across teams and workspaces. | | [Collaboration](./collaboration/collaboration.md) | Work with team members to speed up the process development cycle. | | [Test mode](validation/test-your-process.md) | Test and validate processes during development. | | [Camunda Marketplace](./modeling/camunda-marketplace.md) | Extend functionality with prebuilt blueprints and connectors. | | [Projects](/components/hub/workspace/manage-projects/manage-projects.md) | Work with sets of files and deploy them in a single bundle. | | [Git synchronization](/components/hub/workspace/manage-projects/git-sync.md) | Connect projects to Git repositories for version control. | | [Process landscape visualization](./process-landscape-visualization.md) | View dependencies and interactions between processes at a glance. | | [Intelligent Document Processing (IDP)](./intelligent-document-processing.md) | Extract and process data from documents to accelerate process automation. | | [AI-powered assistance](modeling/advanced-modeling/camunda-docs-ai.md) | Get intelligent modeling suggestions and automation support. | ## Access the modeler Access the modeler in [Camunda Hub](/components/hub/index.md). No installation is required. ## Get started ## Additional resources - [Self-Managed configuration & troubleshooting](../../../../self-managed/components/hub/index.md) --- ## Integrate Camunda Hub into CI/CD Intermediate Time estimate: 1 hour Empower DevOps with Camunda Hub and integrate into CI/CD pipelines to streamline deployments of projects. ## About [Camunda Hub](/components/modeler/about-modeler.md) serves as a powerful tool for the development and deployment of processes and projects. While Camunda Hub simplifies one-click deployment for development, professional teams often rely on continuous integration and continuous deployment (CI/CD) pipelines for automated production deployments. The [Camunda Hub API](/apis-tools/hub-api-sm/overview.md) facilitates integration of Camunda Hub into these pipelines, aligning with team practices and organizational process governance. - For low-risk processes, develop and progress project releases through the stages of the standard [project development lifecycle](/components/hub/workspace/manage-projects/manage-projects.md#project-development-lifecycle). [Version comparison](/components/hub/workspace/modeler/modeling/versions.md#compare-versions) (Visual and XML diffing), built in [review](/components/hub/workspace/manage-projects/project-versioning.md#request-a-review), and [Git Sync](/components/hub/workspace/manage-projects/git-sync.md) provide a powerful combination for collaboration between team members using both Camunda Hub and Desktop Modeler. - For business-critical and higher-risk processes that require strict governance and/or quality requirements, you can integrate Camunda Hub into your CI/CD pipelines. Continuous integration and deployment are pivotal for rapid and reliable software development, testing, and delivery. These practices automate the building, testing, and deployment processes, leading to shorter development cycles, enhanced collaboration, and higher-quality releases. Integrating Camunda Hub into your CI/CD pipelines can significantly enhance project development and deployment workflows. By automating project deployment, changes can be promptly and accurately reflected in the production environment. This agility empowers teams to swiftly respond to evolving business needs, fostering a flexible and adaptable process orchestration approach. ## Prerequisites Each pipeline is unique. The Camunda Hub API offers flexibility to tailor integrations according to your pipelines. To get started, there are a few prerequisites based on your setup: - A platform to host a version control system (VCS) such as GitHub or GitLab. - An existing pipeline or a plan to set one up using tools like [CircleCI](https://circleci.com/) or [Jenkins](https://www.jenkins.io/), cloud platforms such as [Azure DevOps Pipelines](https://azure.microsoft.com/de-de/products/devops), or built-in solutions of VCS platforms like [GitHub Actions](https://github.com/features/actions) or [GitLab's DevSecOps Lifecycle](https://about.gitlab.com/stages-devops-lifecycle/). - Familiarize yourself with the [Camunda Hub API](/apis-tools/hub-api-sm/overview.md). - Understand how [clusters](/components/concepts/clusters.md) and [environments](/components/concepts/environments.md) work in Camunda 8. - Ensure you’ve [created a Camunda 8 account](/components/saas/organization/manage-plan/create-account.md), or installed [Camunda 8 Self-Managed](/self-managed/about-self-managed.md). ## Setup :::tip CI/CD pipeline process blueprint The Camunda Marketplace offers a customizable [process blueprint for CI/CD pipelines](https://marketplace.camunda.com/en-US/apps/439170/cicd-pipeline) to streamline the setup process described below. This blueprint provides a ready-to-use proof of concept for a CI/CD pipeline for Camunda Hub, enabling you to synchronize Camunda Hub files to GitLab and deploy them across different environments. ::: While a pipeline for project integration and deployment resembles general software CI/CD pipelines, key distinctions exist. Consider the following: - Camunda Hub uses [versions](/components/hub/workspace/modeler/modeling/versions.md) to indicate specific process states, such as readiness for developer handover, review, or deployment. - A project comprises diverse resources, such as processes, subprocesses, forms, DMN decision models, connectors, job workers, and orchestrated services. Some projects bundle these resources, while others focus on a single process for deployment. - Process reviews differ from code reviews, occurring on visual diagrams rather than XML. ![Sample CI/CD setup with Camunda Hub](img/modeler-ci-cd.png) ### Obtain API clients and tokens Before getting started, obtain API clients and tokens for integrating Camunda Hub and accessing the process engine via API: - [Obtain an API token for Camunda Hub](/apis-tools/hub-api-sm/authentication.md) - [Obtain an API client for Zeebe](/components/saas/clusters/manage-api-clients.md#create-a-client) ### Disable manual deployments from Camunda Hub To enforce pipeline-driven deployments to your environments, consider disabling manual deployments. Disable manual deployments for any member by configuring environment variables `ZEEBE_BPMN_DEPLOYMENT_ENABLED` and `ZEEBE_DMN_DEPLOYMENT_ENABLED` as documented [here](/self-managed/components/hub/configuration/properties.md#general). To restrict who can deploy from Camunda Hub, control which [environments are assigned to each workspace](/components/hub/organization/manage-environments/assign-environments.md), and manage the deployment permissions in the clusters. You can also require an approved project snapshot before anyone deploys to an environment tagged `prod`. See the [project deployment settings](/components/hub/workspace/modeler/modeler-settings.md#project-deployment). Read more in the [user roles documentation](/components/hub/organization/users-and-roles.md). ### Triggering CI/CD You need triggers to initiate the pipeline for files or projects. Choose between manual pipeline start or automatic background triggers based on events. Common approaches include: - Initiating the pipeline manually from your CI/CD tool/platform by uploading the file intended for deployment. - Starting the CI pipeline by creating a pull/merge request in the version control system. - Triggering pipelines by listening to versions with certain characteristics. #### Sync files with version control Synchronize files between Camunda Hub and version control systems (VCS) and vice versa. Manage both files and projects by using a complete set of CRUD (create, read, update, delete) operations provided by the Camunda Hub API. By syncing files from Camunda Hub to your VCS, you benefit from full file ownership and avoid duplicated data housekeeping. For automatic file synchronization, consider maintaining a secondary system of record for mapping Camunda Hub projects to VCS repositories. This system also monitors the project-to-repository mapping and updates timestamps. #### Example: Poll latest file edits To listen to file changes in Camunda Hub, you currently need to implement a polling approach that compares the update dates with the last sync dates recorded. [Search for project files](/apis-tools/hub-api-sm/specifications/search-files.api.mdx) that have been updated since the last sync: ```json title="POST /api/v2/files/search" { "filter": { "projectKey": "56a98f55-7c53-4e7b-83b7-c58856ee39e4", "updated": { "$gt": "2026-08-30T09:22:15.665653Z" } }, "page": { "from": 0, "limit": 50 } } ``` :::note All responses for `search` endpoints are paginated. Make sure you obtain all relevant pages. ::: [Get the content for each file](/apis-tools/hub-api-sm/specifications/get-file.api.mdx): ```shell GET /api/v2/files/{fileKey} ``` With this file data, you can create a pull request and sync the file contents with your repository. Real-time synchronization isn't always what you need. Consider Camunda Hub as a local repository, and update your remote repository only after files are committed and pushed. This aligns with the concept of [versions](/components/hub/workspace/modeler/modeling/versions.md). #### Example: Poll new file versions A version reflects a state of a file in Camunda Hub with a certain level of qualification, such as being ready for deployment. You can use this property to trigger deployments when a certain version is created. You can poll the Camunda Hub API to know when a project file has a new version. [Search for all project files](/apis-tools/hub-api-sm/specifications/search-files.api.mdx): ```json title="POST /api/v2/files/search" { "filter": { "projectKey": "56a98f55-7c53-4e7b-83b7-c58856ee39e4" }, "page": { "from": 0, "limit": 50 } } ``` This returns a list of files. You'll use the `fileKey` property to search for versions. [Get the file versions](/apis-tools/hub-api-sm/specifications/search-versions.api.mdx) for all project files. Filter for files whose versions are newer than the last sync date: ```json title="/api/v2/versions/search" { "filter": { "fileKey": { "$in": [ "2afd9a1e-5ea8-43e3-b45b-6fb96b384a14", "2386e244-b2c0-4feb-8b68-4429c0cdf0c5" ] }, "created": { "$gt": "2026-08-31T11:07:45.924036Z" } }, "page": { "from": 0, "limit": 50 } } ``` [Get the content for each version](/apis-tools/hub-api-sm/specifications/get-version.api.mdx): ```shell GET /api/v2/versions/{versionKey} ``` With this version data, you can create a pull request and sync the file contents with your repository. ## Pipeline stages The following examples illustrate setting up **build**, **test**, **review**, and **publish** stages within a pipeline. ### Build stage While there is no distinct concept for a build package in Camunda 8, artifact structuring depends on your overall software architecture. The build stage should primarily focus on acquiring dependencies and deploying them to a preview environment. #### Set up preview environments Offering an automatically testable and review-ready process preview mandates a dedicated preview cluster. Numerous options exist, varying with software development lifecycle design, preferences, and Camunda 8 deployment type (SaaS, Self-Managed, or hybrid). This guide proposes a setup with lightweight local Self-Managed preview clusters (or embedded engines) and full-fledged staging and production clusters (Self-Managed or SaaS). ##### Using fully-featured clusters For local preview environments, you can deploy a comprehensive [Zeebe](https://github.com/camunda/camunda) cluster including Operate and Tasklist. Options include using docker-compose or Kubernetes via Helm. All necessary endpoints and UIs are available for thorough process/application testing. Opt for a cluster version aligned with your production cluster to ensure process compatibility. ##### Using embedded Zeebe engines If you don't need to spawn all apps such as Operate or Tasklist, you can use the lightweight [embedded Zeebe engine](https://github.com/camunda-community-hub/eze), which is a community-maintained project, to set up a cost-effective solution with an in-memory database. Together with the [Zeebe Hazelcast exporter](https://github.com/camunda-community-hub/zeebe-hazelcast-exporter) (community-maintained as well), you can consume data generated from your process for reporting or testing. In the build stage, deploy your process or project to a cluster or embedded engine. Post-pipeline completion, such as deployment to staging or production, preview environments can be discarded. :::tip For GitLab users, consider using [GitLab Review Apps](https://docs.gitlab.com/ee/ci/review_apps/) to provide preview environments. ::: Deploy resources using the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) in this pipeline step, compatible with both SaaS and Self-Managed clusters. Alternately, utilize the [Java](/apis-tools/java-client/getting-started.md) client library or any [community-built alternatives](/apis-tools/community-clients/index.md). :::info Feature branches and Camunda Hub installations To maintain a single source of truth, avoid multiple Camunda Hub instances for different feature branches. Instead, maintain a single Camunda Hub installation for all environments, utilizing versions to signify versioning and pipeline stages. Feature branches can be managed by cloning and merging files or projects, ensuring synchronization using VCS. ::: #### Automate deployment of linked resources/dependencies Pipeline-driven deployment can be executed for a single file or an entire project. A separate system of record, maintained outside Camunda Hub, can handle finer-grained dependency management. Fetch the full project for a file using the `GET /api/v2/files/{fileKey}` endpoint to acquire the project's `projectKey`. Subsequently, use the `POST /api/v2/files/search` endpoint with the following payload to retrieve all project files: ```json title="POST /api/v2/files/search" { "filter": { "projectKey": "56a98f55-7c53-4e7b-83b7-c58856ee39e4" }, "page": { "from": 0, "limit": 50 } } ``` :::info All responses for `search` endpoints are paginated. Make sure you obtain all relevant pages. ::: To retrieve the actual file `content`, iterate over the response and fetch it via `GET /api/v2/files/{fileKey}`. Parse the XML of the diagram for the `zeebe:taskDefinition` tag to retrieve job worker types. Utilizing a job worker registry mapping, deploy these workers along with the process if required. If you are running connectors in your process, you need to deploy the runtimes as well. Parse the process XML for `zeebe:taskDefinition` bindings to identify the necessary runtimes (in addition to job workers). To learn how to deploy connector runtimes, read more [here](/self-managed/components/connectors/overview.md) for Self-Managed, or [here](/components/connectors/custom-built-connectors/connector-sdk.md#runtime-environments) for SaaS. Deploy resources in this pipeline step using the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md), compatible with both SaaS and Self-Managed clusters. Alternatively, utilize the Java client library or any community-built alternatives. #### Add environment variables via secrets If you are running connectors, you need to provide environment variables, such as service endpoints and API keys, for your preview environment. You can manage these via secrets. Read the [Connectors configuration documentation](/self-managed/components/connectors/connectors-configuration.md) to learn how to set these up in SaaS or Self-Managed. ### Test stage Keep strict quality standards for your processes with automatic testing and reporting. #### Lint your diagrams Add a step to your pipeline for automatic process verification using the [bpmnlint](https://github.com/bpmn-io/bpmnlint) and [dmnlint](https://github.com/bpmn-io/dmnlint) libraries. Maintained by the bpmn-io team at Camunda, these open source libraries provide a default set of verification rules, as well as the option to add custom rules. They provide reporting capabilities to report back when the verification fails. These are the same libraries Camunda Hub uses to verify diagrams during modeling. You could even report the wrong diagram patterns together with examples to resolve it using [this extension](https://github.com/bpmn-io/bpmnlint-generate-docs-images). #### Unit and integration tests For unit tests, select a test framework suitable for your environment. If working with Java, the [camunda-process-test](/apis-tools/testing/getting-started.md) library is an excellent option. Alternatively, employ the [Java client](/apis-tools/java-client/getting-started.md) with JUnit for testing your BPMN and DMN diagrams in dev or preview environments. Similar testing can be performed using [community-built clients](/apis-tools/community-clients/index.md). ### Review stage During reviews, you can: 1. Use the Camunda Hub API to [add workspace members](/apis-tools/hub-api-sm/specifications/add-member.api.mdx). 2. [Create a link to a visual diff for reviews](#create-a-link-to-a-visual-diff-for-reviews) 3. Automatically paste them into your GitHub or GitLab pull or merge requests. This provides you the freedom to let reviews happen where you want them. After review, use the [`DELETE /api/v2/workspaces/{workspaceKey}/members/{email}` endpoint](/apis-tools/hub-api-sm/specifications/remove-member.api.mdx) to remove members from the workspace. #### Create a link to a visual diff for reviews Use versions to indicate a state for review. Use the `POST /api/v2/versions` endpoint to create a new version, and provide a description to reflect the state of this version using the `name` property. The current content of the file is copied over on version creation. While it is possible to do a diff of your diagrams by comparing the XML in your VCS, this is often not very convenient, and lacks insight into process flow changes. This approach is also less effective when involving business stakeholders in the review. Instead, you can generate visual diff links for versions: 1. Get the file and version keys from the [search](/apis-tools/hub-api-sm/specifications/search-versions.api.mdx) or [get](/apis-tools/hub-api-sm/specifications/get-version.api.mdx) version API endpoints. 2. Insert the keys into one of the following URL patterns: | Resource type | Template URL | | :--------------- | :------------------------------------------------------------------------------- | | BPMN | `{baseURL}/diagrams/{fileKey}/versions/{versionKey1}...{versionKey2}` | | Element template | `{baseURL}/connector-templates/{fileKey}/versions/{versionKey1}...{versionKey2}` | | Form | `{baseURL}/forms/{fileKey}/versions/{versionKey1}...{versionKey2}` | | RPA | `{baseURL}/rpa-scripts/{fileKey}/versions/{versionKey1}...{versionKey2}` | #### Review a running project If deployed in a review environment, processes can be shared with peers for interactive review. For comprehensive review, full clusters inclusive of Operate and Tasklist can be used for process execution. This closely simulates the final experience. To integrate the preview environment with custom applications, leverage the Operate and Tasklist APIs and deploy them within the review environment. In case you use an embedded Zeebe engine, or want to provide a lightweight, focused review experience, you can use [Zeebe Simple Monitor](https://github.com/camunda-community-hub/zeebe-simple-monitor), which is a community-maintained Web App similar to the [Test mode](/components/hub/workspace/modeler/validation/test-your-process.md) in Camunda Hub. Deploying Zeebe SimpleMonitor allows for thorough process testing and review. ### Publish stage Push approved changes to staging or production by deploying them to the respective clusters. You can use the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) to deploy via your pipeline, which works both for a SaaS or Self-Managed cluster. #### Define authorizations For clusters with [authorizations](/components/concepts/access-control/authorizations.md) enabled, use the Orchestration Cluster REST API to assign the necessary authorizations through the pipeline. This step ensures appropriate accessibility for process/application stakeholders or updating existing authorizations. #### Monitoring and error handling As with any CI/CD integration, it's crucial to set up monitoring and error handling mechanisms. These can include: - Monitoring the CI/CD pipeline execution for errors and failures. - Using Operate to catch incidents and send alerts to the pipeline in the test stage. - Sending notifications or alerts in case of deployment issues in both the build and publish stages. - Implementing rollback mechanisms in case a faulty BPMN diagram gets deployed. ## FAQ #### Can I do blue-green deployments on Camunda 8? Blue-green deployments are possible with Camunda 8 with limitations. While switching clusters is quick for new process instances, audit logs and existing process instances remain tied to the previous cluster. Consider exporting audit logs from Elasticsearch or OpenSearch to your own streams if needed. If you don't have to migrate running process instances, keeping them running on the previous cluster alongside new instances on the new cluster is also an option. #### Can I implement blue-green deployments with Camunda 8 SaaS? While blue-green deployments are more straightforward with Self-Managed setups, you can implement similar deployment strategies with Camunda 8 SaaS. Keep in mind the limitations and differences between clusters when planning your deployment approach. #### How can I prevent manual deployments from Camunda Hub? To enforce CI/CD pipelines and restrict manual deployments, you can disable manual deployments. For Self-Managed setups, set environment variables `ZEEBE_BPMN_DEPLOYMENT_ENABLED` and `ZEEBE_DMN_DEPLOYMENT_ENABLED`. In Camunda 8 SaaS, manage deployment permissions via [user roles](/components/hub/organization/users-and-roles.md). #### How can I sync files between Camunda Hub and version control? Use the Camunda Hub API's CRUD operations to sync files between Camunda Hub and your version control system. Consider maintaining a second system of record to map Camunda Hub projects to VCS repositories and track sync/update dates. #### How do I listen to version creation in Camunda Hub? Currently, you need to poll for version creations using the `POST /api/v2/versions/search` endpoint of the Camunda Hub API. Compare the `created` date of versions with your last sync date to identify newly created versions. #### What is the purpose of the build stage in my pipeline? The build stage focuses on preparing dependencies and deploying them to a preview environment. This environment provides a preview of your process that can be tested and reviewed by team members. #### Can I lint my process diagrams for verification? Yes, you can use the `bpmnlint` and `dmnlint` libraries to automatically verify your process diagrams against predefined rules. These libraries provide reporting capabilities to identify and fix issues during the build stage. #### How can I perform unit and integration tests on my processes? You can use [Camunda Process Test](/apis-tools/testing/getting-started.md) for Java-based unit and integration tests, or community-built clients for other programming languages. These libraries allow you to execute your BPMN and DMN diagrams with assertions in your development or preview environments. #### How do I provide environment variables to connectors in preview environments? You can manage environment variables for connectors using secrets. This can be set up in both Camunda 8 SaaS and Self-Managed. Refer to the [Connectors configuration documentation](/components/connectors/introduction.md) for details. #### How can I monitor and handle errors in my CI/CD pipeline? Implement monitoring mechanisms in your CI/CD pipeline to catch errors and failures during the deployment process. Additionally, consider implementing rollback mechanisms in case a faulty BPMN diagram is deployed. ## Additional resources and next steps - [Camunda 8 overview](https://bit.ly/3TjNEm7) - [Camunda Hub API documentation](/apis-tools/hub-api-sm/overview.md) --- ## Intelligent document processing (IDP) ## About IDP IDP uses artificial intelligence (AI) and machine learning (ML) to identify, extract, and organize data from your structured and unstructured documents into a structured format you can use in your processes. For example, use IDP to extract and use data from invoices and other document types in your document processing workflow. ## Configure IDP [Configure IDP](idp/idp-configuration.md) for your Camunda 8 setup with access to the required components and credentials. ## Get started with IDP Once initial [configuration](idp/idp-configuration.md#configure-idp) is complete, get started with IDP. ## IDP concepts Learn about key [IDP concepts](idp/idp-key-concepts.md) and terms, such as the difference between structured and unstructured documents, document classification, and how IDP uses LLM Foundation models. - [Structured and unstructured documents](idp/idp-key-concepts.md#documents) - [Document classification](idp/idp-key-concepts.md#classification) - [Preconfigured document types](idp/idp-key-concepts.md#preconfigured-types) - [Extraction model/Large Language Models (LLM)](idp/idp-key-concepts.md#llms) - [Extraction fields](idp/idp-key-concepts.md#fields) ## IDP reference Technical [IDP reference](idp/idp-reference.md) information, including technical architecture and document storage, supported documents and types, and extraction models. - [Technical architecture and document storage](idp/idp-reference.md#architecture) - [Document file formats](idp/idp-reference.md#file-formats) - [Document language support](idp/idp-reference.md#languages) - [Document file formats](idp/idp-reference.md#file-formats) - [Extraction field data types](idp/idp-reference.md#data-types) - [Extraction models](idp/idp-reference.md#extraction-models) - [Validation status](idp/idp-reference.md#status) --- ## Camunda Hub modeler settings Navigate to the modeler settings in Camunda Hub by clicking on your user icon in the top right corner of the Camunda Hub and selecting **Settings**. Here, you can configure email notifications and the project deployment policy. ## Email notifications Configure the workspaces for which you will receive email notifications when a member mentions you in a comment: 1. In Camunda Hub, in the top right corner, click the user icon 2. Select **Settings**. 3. Under **Email notifications**, toggle the options to receive email notifications when you are mentioned in a comment. ## Project deployment Organization admins can require an approved project snapshot before anyone deploys a project to a production environment. Camunda Hub treats an [environment](/components/concepts/environments.md) as a production environment if its tags include `prod`. The tags of an environment come from its cluster. In SaaS, [tag the cluster](/components/saas/clusters/create-cluster.md#tag-your-cluster) as `prod`. In Self-Managed, add `prod` to the `tags` of the cluster in the [cluster configuration](/self-managed/components/hub/configuration/properties.md#clusters). To change the policy: 1. In Camunda Hub, in the top right corner, click the user icon. 2. Select **Settings**. 3. Click **Projects deployment**. 4. Turn the **Require approval of project snapshots to deploy to production environments** toggle on or off. | Setting | Effect | | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Off (the default) | Any collaborator with deployment privileges can deploy to an environment tagged `prod`, approved or not. | | On | Any collaborator with deployment privileges can deploy an **approved** project snapshot to an environment tagged `prod`. An unapproved snapshot is blocked, and drafts can't be deployed there. | When the toggle is off, access to production environments is controlled by which environments are [assigned to the workspace](/components/hub/organization/manage-environments/assign-environments.md) and by the deployment permissions in the cluster. When the toggle is on and you try to deploy to a production environment, the deploy dialog explains what's missing. See [production environments](/components/hub/workspace/manage-projects/deploy-project.md#production-environments). To get a snapshot approved, use the [project review](/components/hub/workspace/manage-projects/project-versioning.md#request-a-review) feature. Only organization admins can change this setting. It applies to all projects in the organization. ### Self-Managed In Self-Managed, only users with the **Hub Admin** role can change this setting. If the **Hub Admin** role doesn't exist, you can create it with the following permissions: - Hub Internal API - `write:*` - Hub Internal API - `admin:*` - Camunda Identity Resource Server - `read:users` Refer to the documentation pages about [assigning roles](../../../../self-managed/components/management-identity/application-user-group-role-management/manage-roles.md) and [adding permissions](/self-managed/components/management-identity/access-management/access-management-overview.md) for detailed instructions. :::info The deployment policy applies only to deployments of **projects** made from Camunda Hub. Deployments made from Desktop Modeler and deployments of single BPMN files, for example, are not affected by this setting. ::: --- ## Business rule task linking You can use either of the following approaches to link the DMN decision to be called by a [business rule task](/components/modeler/bpmn/business-rule-tasks/business-rule-tasks.md). ## Using the link button 1. Select a business rule task from the canvas. A link icon appears in the floating menu. 2. Click the link icon, and choose any decision from the same project. 3. Click **Link** to complete the linking process. In the properties panel on the right side of the screen, the value **DMN decision** is chosen for the **Implementation** property, and the Decision ID of the decision you chose to link is automatically copied to the **Called decision** section. :::note For business rule tasks that are already linked, clicking on the link icon opens a dialog which shows the name of the decision the business rule task is linked to. It is possible to navigate to the linked decision by clicking on it, or you can use the **Unlink** button to remove the link. ::: ## Using the properties panel You can also enter the Decision ID directly in the properties panel: 1. Select a business rule task from the canvas. 2. On the right side of the canvas, open the **Details** panel. 3. Under **Properties > Implementation**, select **DMN decision**: 4. Under **Properties > Called decision**, provide the following: - **Decision ID** - **Binding**: You can also select a different binding for the called decision. See [choosing the resource binding type](/components/best-practices/modeling/choosing-the-resource-binding-type.md). - **Version tag**: If you select **version tag** for the binding, you must enter the actual version tag to use. --- ## Call activity linking You can use one of the following approaches to link the process to be called by a [call activity](/components/modeler/bpmn/call-activities/call-activities.md). ## Using the link button 1. Select a call activity task from the canvas. A link icon appears in the floating menu. 2. Click the link icon, and choose any diagram from the same project. 3. Click **Link** to complete the linking process. The process ID of the diagram you chose to link is automatically copied to the **Called element** section in the properties panel on the right side of the screen. For call activities that are already linked, clicking the link button opens a dialog which shows the name of the diagram the call activity is linked to. It is possible to navigate to the linked diagram by clicking it. You can use the **Unlink** button to remove the link. ## Using the properties panel You can also enter the process ID directly in the properties panel: 1. Select a call activity task from the canvas. 2. On the right side of the canvas, open the **Details** panel. 3. Under **Properties > Called element**, provide the following: - **Process ID** - **Binding**: You can also select a different binding for the called decision. See [choosing the resource binding type](/components/best-practices/modeling/choosing-the-resource-binding-type.md). - **Version tag**: If you select **version tag** for the binding, you must enter the actual version tag to use. --- ## Camunda Docs AI Camunda 8 only Alpha :::note Camunda Docs AI is an alpha feature. To use this feature, enable the [AI-powered features](https://camunda.com/blog/2024/02/camunda-docs-ai-developer-experience-new-level/) through the [alpha features](/components/saas/organization/enable-alpha-features.md) menu. Learn more about [alpha features](/components/early-access/alpha/alpha-features.md) and [general availability](/reference/announcements-release-notes/release-policy.md#general-availability-ga). ::: Camunda Docs AI provides a smart AI-powered chatbot that helps you find answers to your technical and non-technical questions about Camunda within Hub, rather than navigating and searching across multiple sources of information like documentation, forums, blog posts, etc. Click the **Camunda Docs AI** widget in the bottom right corner of a BPMN/DMN canvas to open the chatbot: ![camunda docs ai trigger](img/camunda-docs-ai-trigger.png) Here, you can ask questions like _How do I design a process?_, _What is BPMN?_, and more. --- ## Form linking You can use one of the following approaches to link a form to a [user task](/components/modeler/bpmn/user-tasks/user-tasks.md) or a [none start event](/components/modeler/bpmn/none-events/none-events.md#none-start-events). :::tip By linking a Camunda Form to a start event, process instances can be started directly [in Tasklist](/components/tasklist/userguide/starting-processes.md) or through your own application built on the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). ::: ## Using the link button 1. Select a user task or none start event from the canvas. A link icon appears in the floating menu. 2. Click the link icon, and choose any form from the same project. 3. Click **Link** to complete the linking process. In the properties panel on the right side of the screen, the value **Camunda Form (linked)** is chosen for the **Type** property, and the form ID of the form you chose to link is automatically copied to the **Form ID** section. For user tasks/start events that are already linked, clicking the link button opens a dialog which shows a preview of the form the user task is linked to. It is possible to navigate to the linked form by clicking on it. You can use the **Unlink** button to remove the link. ## Using the properties panel You can also connect a form in the properties panel: 1. Select a business rule task from the canvas. 2. On the right side of the canvas, open the **Details** panel. 3. Under **Properties > Form**, select the **Type**: - [Camunda Form](#camunda-form) - External form reference ### Camunda Form (linked) Choosing **Camunda Form**, or **Camunda Form (linked)** in the case of a **Job worker** implementation, as the type and entering the form ID directly produces the same result as [using the link button on the modeling canvas](#using-the-link-button). - **Binding**: You can also select a different binding for the called decision. See [choosing the resource binding type](/components/best-practices/modeling/choosing-the-resource-binding-type.md). - **Version tag**: If you select **version tag** for the binding, you must enter the actual version tag to use. Using a linked form is the recommended approach as it allows you to manage form versions independently from the diagram. When deploying a BPMN diagram, Camunda Hub deploys all resources, including linked forms by default. To deploy the diagram only, select **Resources to deploy > Only this resource** in the deployment modal. You are responsible for ensuring the correct form version is deployed and available to your process. :::info To deploy to a Camunda 8 cluster with a version lower than 8.4, linked forms will be automatically embedded into the BPMN diagram's XML to guarantee backwards compatibility. This conversion will only be applied to the XML deployed to the cluster; the diagram in Camunda Hub will not be changed. ::: ### Camunda Form (embedded) :::info Embedded forms are only supported for job worker-based user tasks. They are not available for the [Camunda user task implementation type](/components/modeler/bpmn/user-tasks/user-tasks.md#user-task-implementation-types). ::: When choosing **Camunda Form (embedded)** as type you have the option to directly paste the form's JSON schema into the **Form JSON configuration** field of the properties panel. The form will be embedded directly into the BPMN diagram's XML representation. This approach is not recommended anymore as it makes it harder to maintain the form and the diagram separately. You will have to manually copy and paste the form's JSON schema into the properties panel every time you make a change to the form instead of benefiting from the advantages of linked forms [described above](#camunda-form-linked). Use this option to ensure that the embedded form does not change when you or someone else makes a change to the source form. ### Custom form key Choose **Custom form key** (only available for job worker-based user tasks) to create a custom reference to an external form, application, or web page, that you can consume in your custom applications. Read more in the [user task forms reference](/components/modeler/bpmn/user-tasks/user-tasks.md#user-task-forms). --- ## Process documentation with README files The README feature brings project documentation directly into modeling projects of business users, developers, and project owners to help capture important context in a way that’s versioned, visible, and maintainable. ## Why use READMEs Process documentation is essential, but often stored outside modeling tools. This makes it hard to find and update, and disconnected from your work. With README support in Camunda Hub, you can: - Document your processes **right where you build them**. - Keep information **versioned and automatically synced** with Git. - Make your projects **easier to understand** for everyone involved — from team members to stakeholders. ## What you can document - Workspace goals, scope, and stakeholders - Links to related assets - Go-live dates and status updates - Requirements, process owners, and change logs ## Where READMEs appear - Every project and folder can have its own README > Git-connected projects sync README content just like any other file - In the [process landscape view](../../process-landscape-visualization.md) as context for a node or the entire app :::note Each README is unique to its folder and can’t be moved through the UI. However, you can download or delete associated README files. ::: ## Writing a README README files use standard [Markdown](https://www.markdownguide.org/), a lightweight markup language. A README should provide a snapshot of what your project or folder is about. Consider including: - What this project, application, or folder is for - Who owns or maintains it - Key requirements and business rules - Links to related documentation or dashboards :::note Refer to [GitHub’s README guide](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-readmes) for general inspiration. ::: ## README interaction README file changes are fully versioned, you can see previous versions and recent changes by selecting the **Show versions** icon. README files can be downloaded by selecting the **Download** icon. To see the final result of a README file, select the **Preview** tab. ## Related resources - [Projects](/components/hub/workspace/manage-projects/manage-projects.md) - [Process landscape visualization](../../process-landscape-visualization.md) --- ## Public start forms were removed Public start forms were removed in Camunda 8.10 together with Tasklist V1. For the release-level summary of this removal, see the [8.10 release announcement](/reference/announcements-release-notes/8100/8100-announcements.md#removal-of-legacy-apis-tasklist-v1-dependent-features-and-zeebe-process-test). ## Current alternatives - For authenticated users inside your organization, link a Camunda Form to a start event and start the process directly [in Tasklist](/components/tasklist/userguide/starting-processes.md). - For public-facing use cases, build your own application with [Camunda Forms](/components/hub/workspace/modeler/modeling/utilize-forms.md) and the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). ## If you are migrating from an older version Remove dependencies on public start form links before moving to 8.10. If you used this feature in older versions, replace it with either authenticated Tasklist starts or a custom public application. --- ## Find resources with Browse all With **Browse all**, you can find and use the resources available for the current modeling action in one place. ## Open Browse all **Browse all** opens the complete set of choices for the create, append, or change-element action you started. 1. Open an editable BPMN diagram in Camunda Hub. 2. Open one of the following modeling menus: - **Create element** from the palette. - **Append element** from an element's context pad. - **Change element** from an element's context pad. 3. Select **Browse all** at the top of the menu. The dialog title identifies the action and target. Each card uses the corresponding **Create**, **Append**, or **Change** action. **Browse all** appears when the contextual menu offers a broad set of choices. If it isn't offered, use the choices in the compact menu. If your search has no matches, a compact menu with Marketplace access can show **Browse Marketplace for more connectors**. Select it to browse Marketplace connectors only. ## Search and filter resources The search field and **Show** control narrow the cards available for the current modeling action. - Search matches resource names, descriptions, groups, and connector operations. A matching operation appears directly in the results. - The summary, **Show** choices, and section badges report how many cards are currently visible. Counts update with your search and source selection. - Browse all orders results automatically and doesn't include a manual sort control. Reusable asset groups appear before standard BPMN elements, and Marketplace appears last. Choices you can use appear before unavailable choices in each section. - Marketplace name matches appear before description matches. Connectors with the same search relevance are ordered by name. The **Show** control contains **All** and the sources available in your environment: | Source | Contents | Availability | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Catalog** | Governed assets published through the [Camunda catalog](/components/hub/organization/manage-catalog/getting-started.md). | Appears when the catalog is enabled and matching assets are available. | | **Shared** | Element templates published directly to your organization's shared resources. This source is separate from the catalog. | Appears when matching shared templates are available. | | **Project assets** | Templates and connectors published or added to the current project, plus linked project resources such as forms, called processes, decisions, and robotic process automation (RPA) scripts. | Appears when matching project resources are available. | | **Built-in** | Camunda's built-in connectors. | Appears when built-in connectors are available. This source isn't available in Self-Managed. | | **Standard elements** | The untemplated BPMN vocabulary valid for the current action, including tasks, gateways, events, and other native modeling choices. | Appears when matching standard BPMN elements are available. | | **Marketplace** | Connectors from Camunda Marketplace. | Appears when Marketplace supports the current action. A Self-Managed administrator can [disable Marketplace](/self-managed/components/hub/configuration/properties.md#feature-flags). | Resource cards identify catalog and shared provenance with the **Catalog assets** and **Shared assets** badges. A source with no matches is hidden. If a search reduces the active source to zero matches, the source remains selected with a count of zero so your filter doesn't change unexpectedly. ![Browse all results grouped into processes, shared services, and project services](./img/browse-all-resources.png) ## Use a resource Each result card identifies the resource, its source, and the action you can take. - Select **Create**, **Append**, or **Change** to use a resource directly. - Select **View options** when a connector or category contains operations, then choose the operation you need. - An unavailable card explains why it can't be used for the current element and doesn't offer an action. ## Browse Marketplace connectors Marketplace is the final section and contains the complete collection that matches the current search and modeling context. Browsing, filtering, searching, or scrolling Marketplace cards doesn't check which connectors are already in your project. Select **View details** on a connector to check only that connector and review its project availability. Marketplace applies BPMN type filtering based on the modeling action: - **Create** and **Append**: Results aren't filtered by BPMN type. - **Change**: For a supported change-element action, results are filtered by Marketplace's supported BPMN type. User tasks and service tasks use the broader task type. Showing a connector doesn't guarantee it is compatible with your configuration or runtime. A self-hosted connector card opens **View setup instructions** instead of connector details. Follow the linked setup guide to make the connector available in your environment. If Marketplace can't load after automatically retrying a temporary problem, your local choices remain available and you can select **Try again**. If a refresh fails after Marketplace has loaded, the previously loaded cards remain visible while you retry. ## Check Marketplace project availability The Marketplace connector details show which connector elements are already available in the current project. | Status | Meaning and next action | | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Not added to this project** | None of the connector's elements are in the project. Review **Included elements**, then select **Add to project**. | | **N of M elements added to this project** | Some elements are already available. You can use those elements immediately or select **Add remaining elements**. | | **Added to this project** | The connector's applicable elements appear under **Available elements** with direct **Create**, **Append**, or **Change** actions. | | **Not available for this modeling action** | The connector is in the project, but none of its elements apply to the action you started. The details identify compatible element types when that information is available. | ![Marketplace connector details showing elements available in the project with direct Create actions](./img/browse-all-marketplace-available-elements.png) Adding a connector can open an import review when files need your attention, for example because of a conflict or target-version compatibility warning. After a successful add, the details refresh and show the newly available elements. The Marketplace serves the latest connector template version. Review [connector template version compatibility](./camunda-marketplace.md#connector-template-versions) if the latest version doesn't support your target Camunda version. ## Use the element-template selector The element-template selector remains a separate template-only path. To apply a template through the properties panel, select the element and go to **Details > Properties > Template > Select**. In the **Choose element template** dialog, you can select a published template or use the blue shop icon to open Marketplace. For complete steps, see [using templates in Camunda Hub](../element-templates/using-templates.md#applying-templates). --- ## Camunda Marketplace Discover the **Camunda Marketplace**, your go-to destination for leveraging various contributions from the Camunda community, trusted partners, and the Camunda team. ## About Follow our intuitive guides to explore and harness connectors and blueprints using Camunda Hub. If you prefer to utilize these resources within Desktop Modeler, download them directly from the [Camunda Marketplace website](https://marketplace.camunda.com). In **[Camunda Hub Self-Managed](/self-managed/components/hub/index.md)**, an administrator can [disable Marketplace](/self-managed/components/hub/configuration/properties.md#feature-flags). When Marketplace is disabled, it doesn't appear in **Browse all** or the element-template selector. ## Browse Marketplace connectors while modeling Connectors created by partners or the community aren't part of the commercial Camunda product. Camunda doesn't support these connectors as part of its commercial services to enterprise customers. Evaluate each connector to make sure it meets your requirements before using it. To browse Marketplace connectors from a modeling menu: 1. In Camunda Hub, navigate to your workspace. 2. Create or open a project. 3. In the project, click **Create new > BPMN diagram**. 4. Open the **Create element**, **Append element**, or **Change element** menu. 5. Select **Browse all**. 6. Under **Show**, select **Marketplace** or search across all sources. 7. Select **View details** for a connector. Marketplace cards don't report whether a connector is already in the project. The details check the selected connector. **Included elements** can be added to the project. **Available elements** are already usable in the project. For a complete guide to source filters, project availability, and direct modeling actions, see [find resources with Browse all](./browse-all-resources.md#browse-marketplace-connectors). ### Browse Marketplace from the element-template selector The element-template selector remains a separate Marketplace path for applying a template to an existing compatible element. 1. Select an element in the diagram. 2. Go to **Details > Properties > Template > Select**. 3. In **Choose element template**, click the blue shop icon. For complete template-selection steps, see [using templates in Camunda Hub](../element-templates/using-templates.md#applying-templates). ## Add a Marketplace connector to your project Connector details show which elements are already available in the current project. - If no elements are available, review **Included elements** and select **Add to project**. - If some elements are available, use them directly or select **Add remaining elements**. - If all applicable elements are available, select a **Create**, **Append**, or **Change** action under **Available elements**. The details can also explain when an installed connector isn't available for the modeling action you started. For a change-element action, Marketplace matching is based on BPMN type only, so confirm the connector's configuration and runtime requirements before using it. Self-hosted connector cards open **View setup instructions** instead of offering **Add to project**. Follow the linked [self-hosted connector guidance](/components/connectors/custom-built-connectors/host-custom-connector.md) to configure the connector in your environment. ## Connector template versions The Camunda Marketplace always serves the **latest** version of a connector template. If that version's [`engines.camunda`](/components/modeler/element-templates/template-metadata.md#engine-compatibility-engines) range doesn't cover your cluster version, the connector is listed under **Requires newer Camunda version** and can't be applied to your diagram. To use an older version, obtain the template file from the connector's source and [upload it as an element template](/components/hub/workspace/modeler/element-templates/manage-element-templates.md#importing-an-existing-element-template) yourself. Camunda's built-in connectors publish previous versions in the `element-templates/versioned` directory of the [`camunda/connectors`](https://github.com/camunda/connectors) repository. For partner and community connectors, availability of previous versions depends on the connector creator. ## Browse Marketplace blueprints 1. In Camunda Hub, navigate to your workspace. 2. Create or open a project. 3. If you initiate a project with a pre-defined blueprint, navigate to the Marketplace modal by clicking on **Browse blueprints**. If you wish to incorporate it into an existing project, click **Create new > Browse blueprints**. 4. Within the modal, you'll discover a variety of blueprints submitted by Camunda, partners, or community members to the **Camunda Marketplace**. These include BPMN, DMN, and/or Form files. Utilize the sidebar to filter blueprints by use case, or leverage the sub-navigation to search and filter by industry, creator, or supported Camunda version. 5. Once you've found the desired blueprint, click **Use blueprint** to open it in Camunda Hub and start your work. The blueprint will be automatically saved within the project you initiated. 6. If you can't find the right blueprint, you can suggest ideas in our [Idea Portal](https://marketplace.camunda.com/en-US/pages/connectorsIdeaPortal) or contribute your own process to the [Camunda Marketplace](https://marketplace.camunda.com/en-US/pages/submissionMenu). ## Additional resources - [Find resources with Browse all](./browse-all-resources.md). - Learn more about available [built-in connectors](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md). - Understand different [Connector types](/components/connectors/connector-types.md). - Learn how to modify BPMN elements with [Connector templates](/components/connectors/custom-built-connectors/connector-templates.md) to create custom modeling experiences. --- ## Connect to a runtime Learn how to choose the runtime connection: the environment that Camunda Hub works against while you model, shown in the **Runtime** selector at the bottom of the modeling interface. ## About the runtime connection With the runtime connection, you model against a real runtime instead of guessing what exists there. These features use the connected runtime: | Feature | What it uses the connection for | | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | [Task testing](../validation/task-testing.md) | Runs the selected task on the connected runtime. | | Connector credentials | Offers the credentials available on the connected runtime in the properties panel of connector templates. Requires a runtime on Camunda 8.10 or later. | | **Webhook** tab of inbound connectors | Shows the webhook status and logs of the connected runtime. | | [Problems panel](./fix-problems-in-your-diagram.md#follow-the-runtime-connection-version) | Validates the diagram against the Camunda version of the connected runtime. | The runtime connection doesn't change your deploy target. **Deploy** and **Run** keep using the target you choose in their own dialog. See [run or publish your process](../run-or-publish-your-process.md). :::note The runtime connection is the Camunda Hub counterpart of the Desktop Modeler [connection manager](/components/modeler/desktop-modeler/connect-to-camunda-8.md). Unlike Desktop Modeler, you don't enter a cluster URL or API client credentials. You choose from the environments assigned to your workspace. ::: ## Choose what to connect to The **Runtime** selector lists the [environments](/components/hub/index.md#workspaces-and-environments) assigned to your workspace, and its title is **Connect to an environment**. On Self-Managed, two Physical Tenants of the same cluster are separate environments, so they are separate connections. The selector shows the name of the environment, not the name of the cluster. ## Change the runtime connection **Runtime** shows **Not connected** until you choose a runtime or deploy the diagram. After a successful deployment, it shows the environment you deployed to, unless you've already chosen a runtime for this diagram. To connect: 1. Open a BPMN diagram. 1. At the bottom of the modeling interface, next to **Check problems against**, click **Runtime**. 1. Select an environment from the list. Each entry shows its status icon, stage, and version, plus a badge if it needs your attention. 1. If the runtime offers more than one logical tenant, choose one. See [choose a logical tenant](#choose-a-logical-tenant). The selector closes, and **Runtime** shows the name, stage, and status of the connected runtime. :::note Your choice, including **Work offline**, takes precedence over your deployments: deploying the diagram doesn't change the runtime you chose. The choice applies only to the current diagram in the current browser session. It isn't saved. When you reload the page or open another diagram, choose the runtime again. ::: ### Understand runtime status Each runtime shows an icon for its current state. Hover over or focus a runtime to see its stage and status as text. | Status | Meaning | | --------- | ---------------------------------------------------------------------------------------------------------- | | Healthy | The runtime is available. | | Unhealthy | The runtime reports a problem. Features that use the connection might fail. | | Paused | The runtime is paused (SaaS only). The runtime shows **Select to resume**. | | Resuming | The runtime is starting after a pause. The runtime shows **This might take a moment**. | | Unknown | Camunda Hub can't determine the state of the runtime, for example because it doesn't report health status. | Below the name, each runtime shows its Camunda version. ### Resume a paused runtime On SaaS, selecting a paused runtime resumes it and connects to it in one step. While it resumes, **Runtime** shows the **Resuming** status. You can keep modeling in the meantime. ### Enter credentials for a runtime Some Self-Managed runtimes require a username and password (Basic authentication). These runtimes show a **Needs credentials** badge in the list and a lock icon next to **Runtime** when connected. 1. Select the runtime. The **Enter environment credentials** dialog opens and shows the runtime you're connecting to. 1. Enter the **Username** and **Password** for the runtime. 1. Click **Connect**. Camunda Hub verifies the credentials before connecting. If verification fails, the dialog shows a **Couldn't connect** message. See [troubleshoot the runtime connection](#troubleshoot-the-runtime-connection). ### Choose a logical tenant If the runtime has [multi-tenancy](/components/concepts/multi-tenancy.md) enabled and you can access more than one tenant, the runtime shows a **Logical tenant** chip. - Camunda Hub selects the `` tenant automatically if you can access it, or your only tenant if you can access exactly one. - Otherwise, the list of tenants opens when you select the runtime. Choose the tenant to connect to. To switch tenants later, click the **Logical tenant** chip next to the runtime and choose another tenant. The tenant ID is shown next to each tenant name, since tenant names aren't unique. If no tenant can be selected automatically and you haven't chosen one, the runtime shows a **Needs a logical tenant** badge, and **Runtime** shows a warning icon. ## Work offline Select **Work offline** at the bottom of the selector to disconnect. While you work offline, task testing and connector credentials are unavailable. Modeling, validation, and deployment keep working. ## Manage the available runtimes The selector only lists the environments that are assigned to your workspace. To change what it offers, use the **Manage workspace environments** link at the bottom of the selector. It opens the environment settings of your workspace, and it's shown if you can manage the environments of the workspace. ## Permissions Camunda Hub doesn't check your permissions before it reads from the connected runtime. If you can't access something there, the feature shows less information, for example no credential suggestions, and doesn't block you from modeling. The runtime decides what you can access. ## Troubleshoot the runtime connection ### No environments assigned to this workspace **What you see**: The selector shows **No environments assigned to this workspace**. **Why it happens**: No environment is assigned to the workspace of the diagram yet. **How to fix it**: Ask a workspace or organization admin to assign an environment to the workspace. Use the **Manage workspace environments** link at the bottom of the selector if you have permission. ### Unable to check environment availability **What you see**: The selector shows **Unable to check environment availability**. **Why it happens**: Camunda Hub couldn't load the environments of your workspace, or you no longer have access to them. **How to fix it**: Wait a few minutes and open the selector again. If the error persists, check with your admin that you still have access to the workspace environments. ### Incorrect username or password **What you see**: The **Enter environment credentials** dialog shows **Incorrect username or password.** **Why it happens**: The runtime rejected the credentials. **How to fix it**: Enter the correct username and password for the runtime. ### Couldn't verify these credentials **What you see**: The **Enter environment credentials** dialog shows **Couldn't verify these credentials. Try again.** **Why it happens**: Camunda Hub couldn't reach the runtime to check the credentials. **How to fix it**: Click **Connect** again. If the error persists, check that the runtime is running and reachable from Camunda Hub. ### These credentials can't access this cluster's logical tenants **What you see**: The **Enter environment credentials** dialog shows **These credentials can't access this cluster's logical tenants.** **Why it happens**: The credentials are valid, but the user isn't authorized to read tenants on the runtime. Camunda Hub reads the tenants to decide which [logical tenant](#choose-a-logical-tenant) to connect to. **How to fix it**: Grant the user permission to read tenants in [Orchestration Cluster Admin](/components/admin/admin-introduction.md), or use credentials of a user who already has it. Entering the same credentials again doesn't help. ### Needs a logical tenant **What you see**: The connected runtime shows **Needs a logical tenant**. **Why it happens**: You can access several tenants on the runtime, but not ``, so Camunda Hub can't choose one for you. **How to fix it**: Open the selector, click the **Logical tenant** chip of the runtime, and choose a tenant. ## Configure the runtime connection in Self-Managed In Self-Managed, the runtime connection is enabled by default. To turn it off, set `camunda.hub.feature.runtime-connection-enabled` (environment variable `CAMUNDA_HUB_FEATURE_RUNTIME_CONNECTION_ENABLED`) to `false`. See the [feature flags reference](/self-managed/components/hub/configuration/properties.md#feature-flags). When the runtime connection is off, task testing keeps using its own cluster selection, and connector credentials aren't offered in the properties panel. Offering connector credentials in the properties panel also requires [credentials](/components/hub/organization/credentials/index.md) to be enabled with `camunda.hub.feature.credentials-enabled` (environment variable `CAMUNDA_HUB_FEATURE_CREDENTIALS_ENABLED`). It's also enabled by default in Self-Managed. --- ## Download files You can download all the files stored in Camunda Hub to your local computer for offline editing or integration into version control. You may also export BPMN diagrams in different image formats. ### Download from the project or folder page To download a single file or folder: 1. In the project view, next to the file or folder, open the vertical ellipsis menu. 2. Click **Download**. A file will be downloaded in form of its XML or JSON definition (depending on the file type). A folder will be downloaded as a zip archive containing all the folder's files and subfolders. To download multiple files or folders together as a zip archive: 1. In the project view, select multiple files or folders. 2. At the top of the file list, from the multi-select actions, click **Download**. :::note Download limit You can download up to 100 files and 20 folders at the same time. ::: ### Download from the file page 1. Open a project file. 2. At the top right of the modeling interface, open the vertical ellipses menu. 3. From this menu, you can perform the following actions: | File type | Actions | | :-------- | :--------------------------------------------------------------------------------- | | DMN | Download the DMN 1.3-compliant XML definition. | | BPMN | Download the BPMN 2.0 XML definition, or export the diagram as a PNG or SVG image. | | Form | Download or copy the form's JSON definition. | For other file types, a download button replaces the menu. --- ## Fix problems in your diagram The **Problems** panel is at the bottom of the modeling interface. Use it to debug and fix errors in your processes. ## Design time errors Based on a set of lint rules, Camunda Hub continuously validates implementation properties for a process diagram while the user is modeling. The validation errors are added to the panel at the bottom of Camunda Hub. Expand the panel to view the errors by clicking the **Problems** header. The panel is collapsed by default and the latest state (expanded or collapsed) is remembered for the next time you open Camunda Hub. :::note An error is shown if any process ID, decision ID, or form ID exceeds the supported length for the target environment. To avoid backend-specific deployment problems, keep those IDs short and consistent with the limits of the cluster you plan to deploy to. ::: ## Camunda version selection The version selector at the top right of the **Problems** panel can be used to choose the Camunda version the diagram is validated against. The chosen version should match the version of the cluster that hosts the environment where the diagram will be deployed so that the correct set of errors is shown. :::tip If you don't know the version click **Deploy & run** at the top right of the modeling interface. In the deployment dialog, next to the name of each environment, you'll see the Camunda version of its cluster. ::: The version selector also shows how many environments (or clusters, if your organization doesn't use environments) are available for each Camunda version. ### Follow the runtime connection version Click **Check problems against** and select **Follow runtime environment** to validate the diagram against the Camunda version of the runtime you're [connected to](./connect-to-a-runtime.md). If your organization doesn't use environments, the option is called **Follow runtime cluster**. When you connect to a different runtime, the validated version follows automatically. To stop following, select a specific Camunda version. Below the option, the menu shows the followed version, for example **Camunda 8.9**. It shows **Not connected to an environment.** (or **Not connected to a cluster.**) if you're working offline, and **Version not recognized.** if Camunda Hub can't map the runtime's version to a supported Camunda version. ## Interactivity Errors in the **Problems** panel are interactive. Clicking on the row highlights the corresponding element in the canvas and points to the specific property in the properties panel where you can resolve the issue. ## Deploy time errors If errors are thrown by the engine when deploying a diagram (whether they were caught by design time errors or not), they will be displayed in the deployment modal. --- ## Upload files Upload a BPMN diagram, DMN diagram, or any other supported file to a modeling project in Camunda Hub: 1. Open a workspace. 2. Open a project. 3. Choose one of the following import methods: - Click **Create new > Upload files**, and select the files from your computer. - Drag one file from your computer and drop it onto the files table. :::tip Alternatively, you can replace the content of an existing file with the content in your uploaded file: - Open an existing file. In the top navigation next to the filename, open the vertical ellipsis menu, and choose **Replace via upload**. Then, select a file from your computer. - Open an existing file. Then, drag one file from your computer and drop it onto the canvas. ::: ### Undo/redo management limitations You cannot undo or redo any actions taken before an upload as the undo/redo history is cleared when a diagram is uploaded. This prevents inconsistencies between the uploaded diagram and any actions taken before the upload. ### Troubleshoot a failed upload If a file doesn't appear after uploading, or you see an error, see [why resources might fail to import](./importing-resources.md#why-your-resources-might-fail-to-import). Common causes include an unrecognized file type or exceeding the per-file size limit. --- ## Import resources into Camunda Hub Learn how to import resources into Camunda Hub, how automatic handling works for template conflicts and ignored templates, and how to troubleshoot common import errors. ## Supported import resources Using Camunda Hub, you can import the following resource types into a project: - BPMN process models. - Forms. - DMN decision tables. - Element/connector templates. - Markdown files such as `README.md`. - Any other resource type supported by Camunda Hub. ## How to add files These are the supported methods you can use to add files to Camunda Hub: | Method | Opens from | Import source | Supported resources | | ------------------------------------------------------------------------------- | ----------------------------------- | -------------------------- | ------------------------------------------------------------------------- | | [**Import**](./preparing-resources-for-import.md) | Camunda Hub `/import/resources` URL | Any publicly available URL | Any type of resource | | [**Browse blueprints**](./camunda-marketplace.md#browse-marketplace-blueprints) | Camunda Hub project or folder page | Camunda Marketplace only | [Blueprints](./camunda-marketplace.md#browse-marketplace-blueprints) only | | [**Upload files**](./import-diagram.md) | Camunda Hub project or folder page | Any downloaded file | Any type of resource | :::note - **Import** and **Browse blueprints**: If the imported resources include at least one BPMN file, Camunda Hub treats them as a project and groups them accordingly. - **Upload files**: Adds uploaded resources as independent files, regardless of whether BPMN files are present. - **Browse all**: Use [Browse all](./browse-all-resources.md) while modeling to find available resources. Adding a Marketplace connector adds its element templates to the current project. ::: ## Template conflicts Element templates, including connector templates, have an associated ID, which is used to find the template when a BPMN process references it. When importing templates, Camunda Hub checks for potential conflicts with existing templates already available in your project, workspace, or organization. A conflict occurs when an imported template has the same ID as an existing one. You can resolve template conflicts using one of these two options: 1. Save as copy: It creates a new file with a new, auto-generated ID. This option is not available when importing projects. 2. [Replace an existing template](#replace-a-template). ### Replace a template You can replace an existing template when: - The template belongs to the project you are importing into. - The imported template has a higher version than the existing one. If an imported template **replaces** an existing template: - The **file contents** of the existing template are overwritten by the imported template. - Due to Camunda Hub safeguards, you can only publish **higher versions** of that template in the future. Older or equal versions are blocked from publication to prevent accidentally overwriting already published versions. This behavior ensures consistency for processes that already use the template, but note that historical versions cannot be republished under the same ID and version. ## Ignore templates Camunda Hub ignores a template if it detects a **functionally equivalent** template already exists in your project or organization. This ensures that importing the template doesn’t break your existing setup. A template is considered functionally equivalent when, after minifying the JSON, the following fields are equal: - `id` - `version` - `appliesTo` - `elementType` - `groups` - `properties` When a template is ignored: - The imported template is not added as a new resource. - The project uses the existing template definition instead. - There may be small differences in: - Display name. - Documentation URL. - Icon. ## Troubleshoot ### Why your resources might fail to import You might not be able to import certain resources because they fail validation or cannot be processed. These are common reasons: - **Existing template**: A newer or equal version of the same template ID already exists, and the incoming template's contents are not available in your project. - **Invalid file**: The file does not conform to the expected schema. For example, malformed element template JSON. - **Network error**: Camunda Hub could not download the file from the given URL. - **Only one README file allowed**: You cannot add additional README files because each project allows only a single README. - **Too large**: The file exceeds Camunda Hub’s per‑file size limit. - **Unknown error**: A generic error for unexpected failures. - **Unrecognized file**: The file type is not supported by this version of Camunda Hub. ### Manually upgrade a conflicting template If a project depends on a template that is being ignored or differs from your existing template, you can manually upgrade it following these steps: 1. Copy the desired template contents: - Open the template JSON file from the import source. - Copy the entire template definition. 2. Navigate to the conflicting template: - In Camunda Hub, open the existing template that shares the same ID and version. - If you do not have access, ask your organization or workspace admin to open it. 3. Replace the contents of the existing template with the copied JSON. 4. Increase the `version` field to a number higher than the highest published version. 5. Publish the updated template to the relevant project and/or organization. 6. Reopen the BPMN process and click on an element that uses the template. 7. Click **Update available**. Then, click **Update**. 8. The updated fields should now be visible in the properties panel. ### Many resources are ignored or not imported If you see many resources that cannot be imported, check the following: - Confirm that the URLs (for single-file imports) or the `.zip` URL are: - Publicly reachable. - Pointing to the correct files. - For `.zip` imports: - Check that the archive size is at most **10 MB**. - Ensure there are no more than **100** meaningful entries; extra files might be ignored. - For templates: - Verify that IDs and versions are what you expect. - Consider whether equal or higher versions already exist. If the issue persists, try importing a small subset of files or a simpler `.zip` to isolate problematic resources. ### Properties panel fields are missing or show “template not found” You may experience this issue when: - A user task or connector task in BPMN shows a warning such as “template not found”. - Some fields you expect to see in the properties panel are missing. - Some templates could not be imported. #### Why this happens - The process expects a **template version** that was not imported: - A higher version already exists and the imported version was ignored. - An intermediate version (for example, version 2) does not exist, while version 1 and 3 do. - An existing template with the **same ID and version** has different contents. #### How you can solve it - Manually upgrade the template to match the process. See the [manual upgrade steps](#manually-upgrade-a-conflicting-template) section for more details. - If a higher version already exists, you may need to: 1. Unlink the older template version in the BPMN editor. 2. Link the task to the newer template. --- ## Start modeling A brief tour of the Camunda Hub modeling interface. A new diagram contains a single start event, the starting point of every process. From here, you can add elements to build out your process. 1. In the modeling interface, next to the start event, click the **Append element** icon. This connects the start event to the next step in the process. 2. Select **Task**. 3. With the task selected, click the **Change element** icon in the floating menu. With this, you can change the element type. 4. Select **User task**. 5. With the user task selected, click the **Link form** icon in the floating menu. If you have already created a form, you can select the form from this menu. 6. At the top left of the modeling interface, make sure you're in **Implement** mode. In **Implement** mode, you can view all properties of an element. 7. With the user task selected, on the right side of the modeling interface, open the **Details** panel. 8. Inspect and edit the technical properties of the element. 9. On the left side of the modeling canvas, at the bottom of the actions menu, select the **Undo** or **Redo** icon to revert or your reapply changes. :::note Undo and redo behavior has limitations when [collaborating](../collaboration/collaboration.md#undoredo-management-limitations) and [importing a diagram](import-diagram.md#undoredo-management-limitations). ::: --- ## Prepare resources for import into Camunda Hub Learn how to prepare resources for import into Camunda Hub, including guidelines and steps to create import URLs. :::note This guide is intended for repository maintainers, blueprint authors, and solution builders who prepare resources for others to import into Camunda Hub. ::: ## Supported methods Prepare resources for import into Camunda Hub in two main ways: 1. [**Individual resources**](#prepare-individual-resources): Each resource, such as a BPMN or DMN file, a template, or a README, is accessible via its own public URL and can be imported one by one. 2. [**Packaged resources**](#prepare-packaged-resources): Bundle all resources, such as an entire project, into a single `.zip` file. This allows importing the full package from a single URL. ## Guidelines for importing ### Hosting requirements Each individual resource (or `.zip` file package) must: - Be hosted at a **publicly accessible URL** that does not redirect to another page. - Not require authentication, VPN, or non‑public network dependencies. ### Template naming - Use stable, distinct template IDs to ensure BPMN files can reference them consistently and to avoid conflicts with other templates users might download. - Increment the version number whenever you introduce changes that could affect existing processes. - Element template IDs must be unique within the set of imported files. - BPMN process IDs must be unique within the set of imported files. :::important - If the imported resources include at least one BPMN, Camunda Hub treats them as a **project** and groups them accordingly. - If no BPMN file is present, the resources are imported as **independent files** into the chosen project or folder. ::: ## Prepare import resources ### Prepare individual resources Use this approach when: - You have a small set of resources. - You don't expect to add or remove files often. Keep each file within Camunda Hub’s per‑resource size limit of **three MB**. ### Prepare packaged resources Use this approach when: - Your solution includes many resources (up to 100), such as multiple BPMN and DMN models, forms, element templates, and documentation. - You want to minimize the risk of missing dependencies during import. #### File and archive limits When preparing the packaged resources into a `.zip` file: - Keep the total `.zip` file size at or below **10 MB**. - Include **at most 100 files** that Camunda Hub can support. - Keep each packaged file within Camunda Hub’s per‑resource size limit of **three MB**. - Include at most one README file. - Note that the folder structure **will not** be imported into Camunda Hub. #### Content and security rules To minimize issues during import: - Do not include files with `..` or leading slashes in their name. - Exclude executables, scripts, and any other files not supported by Camunda Hub. - Ensure each resource file is within Camunda Hub’s per‑resource size limit. - Use clear, stable file names. ## Create an import URL 1. Get the public URL to the resource. For GitHub-hosted resources, see first [Get public URLs from GitHub](#get-public-urls-from-github). 2. Form the Camunda Hub URL like this: ``` /import/resources?source= ``` 3. To add more resources, add a comma after the last URL and paste the next URL: ``` /import/resources?source=, ``` 4. (Optional) Add a title. When the resources are treated as a project, this will be the project's name. ``` /import/resources?title=&source=, ``` ### Get public URLs from GitHub You can host your resources on any public-facing website that allows direct access. See [Hosting requirements](#hosting-requirements) for more details. ### Individual resource URL To get a public URL for a single resource: 1. Open the resource in your public GitHub repository. 2. Click **Raw**. 3. Copy the URL from your browser's address bar. :::important The URL from step 3 is a direct link that Camunda Hub can access without redirects. ::: ### Packaged resources URL To get a public URL for packaged resources into a `.zip` file: 1. Open the `.zip` file in your publicly accessible GitHub repository. 2. In the file view, right‑click the **Raw** button in the top right. 3. Click **Copy link**. 4. In a text editor: - Replace `github.com` with `raw.githubusercontent.com`. - Remove the `/raw` segment that appears once in the URL after the repository name. - Leave everything else unchanged. You can automate this transformation as follows: ```bash GITHUB_RAW_ZIP_URL=your_original_url echo "$GITHUB_RAW_ZIP_URL" \ | sed -E 's#https://github.com/([^/]+)/([^/]+)/raw/(.+)#https://raw.githubusercontent.com/\1/\2/\3#' ``` --- ## Utilize forms Beginner Time estimate: 15 minutes ## About The Camunda Forms feature allows you to easily design and configure forms. Once configured, they can be connected to a user task or start event to implement a task form in your application. After deploying a diagram with a linked form, Tasklist imports this form schema and uses it to render the form on every task assigned to it. ## Quickstart ### Create new form To start building a form, log in to your [Camunda 8](https://camunda.io) account, and take the following steps: 1. In Camunda Hub, open a workspace. 2. In the workspace, create or open a project. 3. In the project, click **Create new > Form**. ### Rename your form Now you can start to build your form by dragging elements from the palette to the canvas, or by using the AI Form Generator at the bottom of the palette. For the purpose of this guide, we'll build a form from scratch. Right after creating your form, rename it: 1. In the top breadcrumb navigation, next to **New form**, click the vertical ellipsis menu. 2. Click **Rename**. 3. Enter `email-form`. 4. Click **Rename**. In this example, we'll build a form to help with a task in obtaining an email message. ### Build your form Within Forms, can add text fields, numerical values, checkboxes, radio elements, selection menus, text components, and buttons. 1. From the **Components** palette on the left, drag and drop a **Text area** to the **Form definition** editor. 2. Select the **Text area**. 3. In the properties panel on the right, open the **General** section. 4. Provide the following data: - **Field label:** "Email content" - **Field description:** "The content of the email message" :::tip Refer to the [camunda forms reference](/components/modeler/forms/camunda-forms-reference.md) to explore all form elements and configuration options in detail. ::: ### Save your form To save your form in Camunda 8, you don't have to do anything. Camunda Hub autosaves every change you make. ### Link your form to a BPMN diagram {#connect-your-form-to-a-bpmn-diagram} Implement a task form into a diagram. In tandem, link your form to a user task or start event: 1. In the same workspace that contains your form, open any project. This does not have to be the same project where you saved the form. 2. In the project, select the diagram where you'd like to apply your form. 3. Select the user task requiring the help of a form. 4. In the floating menu, select the **Link form** icon. 5. Navigate to the form you want to link and click **Link**. When using Camunda Forms, any submit button present in the form schema is hidden so we can control when a user can complete a task. ## Deploy a form To deploy your latest form changes: 1. Open the form. 2. At the top right of the form editor, click **Deploy**. 3. Select a stage and, optionally, a tenant ID. 4. Choose whether to deploy **All resources** or **Only this resource** (the form). 5. Click **Deploy**. ## Deploy your diagram and start an instance To execute your process diagram, click **Deploy & run**. To avoid incidents: - When deploying a project, if the links between resources are configured with the 'deployment' binding, the BPMN diagrams and their forms must be deployed together. - When deploying a BPMN file separately, and linking resources using the 'latest' binding, ensure the forms are deployed beforehand. You can now monitor your instances in [Operate](/components/operate/operate-introduction.md). :::info You have full control over when a form is deployed. When [deploying a project](/components/hub/workspace/manage-projects/deploy-project.md): - If the form is saved in the project you're deploying, the form will be included in the deployment. - If the form is saved in another project, you need to [deploy it separately](#deploy-a-form). When [deploying a diagram](/components/hub/workspace/modeler/run-or-publish-your-process.md), not the form: - If the form is saved in the same project as the diagram and you choose to deploy **All resources**, the form will be included in the deployment. - If the form is saved in the same project as the diagram and you choose to deploy **Only this resource**, you need to [deploy the form separately](#deploy-a-form). - If the form is saved in another project, you need to [deploy it separately](#deploy-a-form). As linked forms are resolved to their latest version (unless you change the [binding type](/components/hub/workspace/modeler/modeling/advanced-modeling/form-linking.md#camunda-form-linked)), make sure the intended form version is available in the target cluster and the binding resolves to that version. When deploying to a Camunda 8 cluster running a version earlier than 8.4, forms linked to user tasks or none start events will be automatically embedded in the user task to guarantee backwards compatibility. Read more about the different ways to reference Camunda Forms in the [user task forms reference](/components/modeler/bpmn/user-tasks/user-tasks.md#user-task-forms). ::: To [complete a user task](/guides/getting-started-orchestrate-human-tasks.md), navigate to [Tasklist](/components/tasklist/introduction-to-tasklist.md). ## Additional resources - [Desktop and Camunda Hub](/components/modeler/about-modeler.md) - [User task reference](/components/modeler/bpmn/user-tasks/user-tasks.md) ## Next steps When building a form for a process, you can also use the [Filepicker form component](/components/modeler/forms/form-element-library/forms-element-library-filepicker.md) to allow users to upload files. Learn more in [building a form for document upload](/components/document-handling/upload-document-to-bpmn-process.md#build-a-form-for-document-upload). You can also use the [document preview component](/components/modeler/forms/form-element-library/forms-element-library-document-preview.md) to display and allow document download with your form. Learn more in [building a form for document preview and download](/components/document-handling/display-and-download-document.md#build-a-form-for-document-preview-and-downloading). Additionally, review the Camunda Academy course on [using the AI-assisted form builder](https://academy.camunda.com/c8-h2-ai-form-builder). --- ## Manage file versions Every BPMN diagram, DMN diagram, form, RPA script, README file, and test file keeps a version history, a single timeline of the autosaves and named versions created as you work. You can open that history to view an earlier state of the file, compare any two entries, restore an entry, or copy one to another project. A version is a Camunda Hub snapshot of a file, not a deployed process definition version. See [version (file)](/reference/glossary.md#version-file). ## Files with a version history The version history page is the same for every file type that uses it: | File type | Version history URL | | ------------------- | ---------------------------- | | BPMN or DMN diagram | `/diagrams//versions` | | Form | `/forms//versions` | | RPA script | `/rpa-scripts//versions` | | README file | `/readmes//versions` | | Test file | `/tests//versions` | Element templates and connector templates do not use this page or version history. They use numbered template versions that you publish to your project or organization. Restoring an element template version publishes its content as a new numbered version; there's no autosave step, since the previous published version remains available in the versions list. See [versioning element templates](/components/hub/workspace/modeler/element-templates/manage-element-templates.md#versioning-element-templates). :::note Links that use the older `/milestones/` path redirect to the equivalent `/versions/` path, so existing bookmarks and shared links keep working. ::: ## Open the version history of a file You can open the version history in the following ways: - From the file editor, in the top right of the modeling interface, click **Versions > Show versions**. - From a deep link to a single entry, such as a **See version** link from Copilot. The link opens the history with that entry selected. ## Create a version You can create a new version either from your file or from the version history. - From your file, in the top right of the modeling interface, click **Versions > Create version**. - From the version history, in the **Versions** tab, next to the draft, click **Create new version**. ## Read the version history The version history lists every entry for the file, newest first. Autosaves and named versions appear in the same list. Each entry shows the following information: | Element | Description | | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Name | The name of the version, or the autosave label. | | Description | The optional description entered when the version was created or edited. | | Date and time | The exact time the entry was captured. | | Created by | The author of the entry. | | **Current state** | Shown at the top of the version history as a draft when the file has unsaved changes. | | **In a snapshot** | Shown when the entry is captured in a [project snapshot](/components/hub/workspace/manage-projects/project-versioning.md). You cannot delete an entry with this badge. | Select an entry to view it in the viewer next to the version history. ## Compare versions You can compare any two entries in the version history, including entries that are not next to each other in the history. The version history page has two tabs: - **Versions**: the version history of every entry for the file. - **Compare versions**: the comparison of two entries you select. To compare two entries: 1. Open the version history for your file. 1. Select the **Compare versions** tab. 1. Select the first entry you want to compare. 1. Select the second entry you want to compare. The comparison shows the older entry against the newer one, ordered by time regardless of the order you selected them. The selected pair is written to the URL as `///versions/...`, so you can share or bookmark a specific comparison. ### Compare versions in visual view To view BPMN diagram changes visually, select the **Visual view** tab. - Differences between the versions are highlighted visually on the diagram. For example, if an element was added, this change is highlighted in green with a plus symbol. Hover over a change to view more details. - Only differences that affect the execution of the BPMN process are highlighted. - The sidebar **Changes** list shows the details of each change, including the type and identifier. Select a change to highlight it. :::note DMN comparisons are available in the **Code view** only. The **Visual view** tab is disabled with the hint "Visual view is not supported for DMN comparisons". ::: ### Compare versions in code view To view BPMN and DMN diagram changes as code in an XML diff layout, select the **Code view** tab. - The XML for the older entry is shown on the left, with the newer entry shown on the right. - Differences between the versions are highlighted in the XML. For example, if an element was added, this change is highlighted in green. ## Version actions To act on an entry in the version history, open the entry's vertical ellipsis: | Action | Description | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | **Restore** | [Revert the file](#restore-a-version) to the content of this entry. Disabled when the entry already matches the current file. | | **Edit** | Update the name and description of the entry. | | **Copy to project** | Create a new file from this entry in a project and folder you choose. | | **Delete** | [Permanently delete](#delete-a-version) the entry. Disabled for an entry with the **In a snapshot** badge. | ### Restore a version The file content changes to the content of the restored version, and the version history is refreshed with up to two new entries: - A safety autosave that captures the state of the file before the restore. This entry is only added if that state differs from the most recent saved entry. - The restored entry, with `(restored)` appended to its name. This entry is selected in the viewer after the restore completes. **Restore** is disabled when the content of the entry already matches the current file. ## Delete a version An entry captured in a project snapshot cannot be deleted. **Delete** is disabled for these entries. :::caution Deleting a version is permanent. You cannot access a deleted version, and it is removed from the version history. ::: --- ## Visualize the process landscape The process landscape view provides a visual map of BPMN files in a project and interfile [call activity](/components/modeler/bpmn/call-activities/call-activities.md) connections between them. - Quickly understand process dependencies, flow relationships, and overall project structure in a single interactive canvas. - The view is automatically generated from all BPMN diagrams within a project, offering insights without the need for manual maintenance or updates. ## Open process landscape view To open the process landscape view, click **View landscape** above the file list from either of the following views: - [Project](/components/hub/workspace/manage-projects/manage-projects.md) view - Folder view ## Landscape view interaction You can interact with the process landscape view in a number of ways. ### View BPMN file information Click any node to view the information for the selected BPMN file, including the latest version of the process on the sidebar. :::note For projects, version tags represent a unified "versioned" snapshot of all project files rather than separate versions for each file (as with simple BPMN files). ::: ### Search You can search the process landscape for a specific BPMN file. 1. Press `Ctrl+F` or `⌘+F` combination to initialize search. 1. Enter the name or identifier of the BPMN file you want to search for and highlight. ### Highlight paths Click a node or connection to highlight the entire hierarchy of related connections. --- ## Run or publish your process When you design a process in Camunda Hub, you have multiple flexible options to either run or publish it on Camunda 8. This page explains the differences between running and publishing a process, and outlines the various options to publish a process into any environment and to any audience. ## Deploy a process Camunda Hub autosaves all your changes on a diagram. If you change a diagram and it is autosaved, this has no effect on deployed or running processes in your environments. To make any change live in an environment, you need to deploy it. If you deploy a process, it becomes available in the selected [environment](/components/concepts/environments.md) and you can run or publish it. :::info You can deploy to any environment assigned to your workspace if you have deployment permissions in the cluster. Your organization can require an approved project snapshot for environments tagged `prod`. See [production environments](../manage-projects/deploy-project.md#production-environments). ::: To deploy: 1. From your Camunda Hub workspace, open a project. 2. Open a process file. 3. In the top right corner of the modeling interface, click the dropdown next to **Deploy & run**. 4. Click **Deploy**. 5. Select the environment, the Logical Tenant if the environment has more than one, and the resources to deploy. You can either deploy **All resources** or **Only this resource**. 6. Click **Deploy**. :::tip In Self-Managed, you can deploy your diagram to the environments of the clusters defined in your Camunda Hub [configuration](/self-managed/components/hub/configuration/properties.md#clusters). ::: ### Before deploying a process - If the target environment has [authorizations](/components/admin/authorization.md) enabled, make sure that the deploying users have `CREATE` permission to the `RESOURCE` resource type. - Make sure your process is free of errors, otherwise it can't be deployed. Use the [problems panel to detect and fix errors](modeling/fix-problems-in-your-diagram.md). - Make sure all dependent files are deployed first, such as DMN diagrams, forms, or called processes. You can use the [link tool](modeling/advanced-modeling/call-activity-linking.md) to drill-down into linked resources and deploy them. If you are using [`versionTag` binding](/components/best-practices/modeling/choosing-the-resource-binding-type.md) for a linked resource, make sure it is deployed with the correct version tag. - Implement and run your [job workers](../../../concepts/job-workers.md) if you use tasks such as service or send tasks. - Ensure there are no missing secrets or misconfigured clients required for the process to run. - When missing secrets or no client credentials with access to the Orchestration Cluster API are detected, a warning is shown in the deployment dialog. Each warning offers a link to manage the missing secrets or misconfigured clients. :::note To perform any of these actions, make sure to be in [**Implement** mode](/components/hub/workspace/modeler/collaboration/implement-your-process.md). ::: ## Run a process Running a process means that you execute the process as a process instance on Camunda 8. It allows you to test and debug your process and observe how it performs in a live environment. ### Before running a process If the target environment has [authorizations](/components/admin/authorization.md) enabled, make sure that the users running the process are assigned to both: - The `CREATE_PROCESS_INSTANCE` permission to the `PROCESS_DEFINITION` resource type - The `CREATE` permission to the `RESOURCE` resource type ### Test run using Test mode Before you publish or run a process, you can test it manually using the [Test mode](validation/test-your-process.md). With the Test mode, you can build and test your process iteratively in small steps. To enter the Test mode, click the Test tab in the top left corner of the modeling screen. ### Run manually from Hub You can also test your process thoroughly in a development environment to observe how it behaves in Operate and Tasklist, in order to run your job workers, and to access your running process instances [programmatically](#deploy-to-run-programmatically). To start a process instance manually, take the following steps: 1. From your Camunda Hub workspace, open a project. 2. Open a process file. 3. In the top right corner of the modeling interface, click **Deploy & run**. 4. Select the environment, the Logical Tenant if the environment has more than one, and the resources to deploy. 5. **(Optional)** Specify variables written to the process context at startup. The variables must be formatted in valid JSON. As an example, you can use the following JSON: ```json { "hello": "world" } ``` 6. Click **Deploy & run** to confirm. This (re-)deploys the process and starts a process instance in the selected environment. After the process instance has been started, you will receive an **Instance started!** notification. Click **View process instance** to open the process instance in the [Operate](../../../operate/operate-introduction.md) of the environment, where you can observe its progress and interact with it if required. If the target environment has [authorizations](/components/admin/authorization.md) enabled, make sure you have the following permissions to be able to view the process instance in Operate: - `READ_PROCESS_DEFINITION` and `READ_PROCESS_INSTANCE` permissions on the `PROCESS_DEFINITION` resource type - `operate` permission to the `COMPONENT` resource type Starting an instance from Camunda Hub [deploys](#deploy-a-process) recent changes to the target environment, which changes future runs of this process definition in case it has already been deployed and used. Existing process instances are not affected. :::tip By [linking a Camunda Form to a start event](/components/hub/workspace/modeler/modeling/advanced-modeling/form-linking.md), process instances can be started with the form's input [via a public form](#publish-via-a-public-form) (SaaS only) or directly [in Tasklist](#publish-to-tasklist). ::: ### Schedule via timer You can also schedule a process to run at a specific time or interval using [timers](../../../modeler/bpmn/timer-events/timer-events.md). Timers can be added to one or multiple start events of your process. To schedule a process using a timer, follow these steps: 1. In the process file, click the start event. 2. Select the **Change element** menu icon. 3. Select **Timer start event**. 4. Open the **Details** panel on the right side of the modeling interface. 5. Under **Properties > Timer**, set the timer to trigger at a specific date and time or to repeat at a certain interval. 6. [Deploy](#deploy-a-process) the process. Once the process is deployed, the timer will be activated and the process will be executed at the scheduled time or interval. ### Best practices for running a process - Use the [Test mode](#test-run-using-test-mode) to run a process instance with test data before running it with live data. - Verify that the process is running as expected in a development environment before running it with live data in your production environment. - Use [Operate](../../../operate/operate-introduction.md) to help you diagnose any problems with the process. :::tip You can also define the success of your processes by setting key performance indicators (KPIs) for your process using [Optimize](/components/optimize/what-is-optimize.md). ::: ## Publishing a process Publishing a process means you make it available to other users inside and outside of Camunda 8. Once published, other users can access and start instances of the process. ### Deploy to run programmatically To call a process programmatically from or inside another application or service, [deploy](#deploy-a-process) it. Once deployed, you can run a process via our APIs, using an API client, or via one the various community SDKs. Read the [documentation on APIs & clients](../../../../apis-tools/working-with-apis-tools.md) to learn more. ### Publish via webhook You can publish a process via [webhook](/components/connectors/protocol/http-webhook.md), which allows you to integrate it easily with any system or service that can make an HTTP request. When a webhook is triggered in another system, it sends a HTTP request to a specified URL, which starts a process instance with the payload of the request. Follow these steps to publish a process via a webhook: 1. In the process file, click the start event. 2. Select the **Change element** menu icon. 3. Select **Webhook Start Event Connector**. 4. Open the **Details** panel on the right side of the modeling interface. 5. Under **Properties > Webhook configuration**, [configure](/components/connectors/protocol/http-webhook.md) the webhook. You have multiple options to ensure that the webhook connection is safe for use by your target audience only. 6. [Deploy](#deploy-a-process) the process. When the process is deployed, the webhook URL can be found in the **Webhook** tab of the **properties panel**, and called from any outside system. ### Publish to Tasklist [Publishing a process to Tasklist](../../../tasklist/userguide/using-tasklist.md#processes) makes it available to users through the web-based [Tasklist application](../../../tasklist/introduction-to-tasklist.md). To publish a process to Tasklist, you first need to [deploy](#deploy-a-process) it. Once the process is deployed, it will automatically appear in the Tasklist application, where users can start new instances of the process. ### Publish via a public form Camunda 8 SaaS only Publishing a process via a public form allows you to share your process with external users who can start instances of the process without requiring access to Camunda 8. This feature is particularly useful when you want to gather data or initiate a process from users who are not part of your organization or do not have direct access to Camunda. It also allows you to rapidly test a process with your peers in a development environment. To publish a process via a public form, you first need to [link a Camunda Form](/components/hub/workspace/modeler/modeling/advanced-modeling/form-linking.md#using-the-link-button) to the process' start event, then you can follow these steps: #### Deploy process to the public 1. Open the **Publication** section in the **properties panel** (not the tab of the same name). 2. Toggle **Public access enabled**. 3. [Deploy](#deploy-a-process) the process. Once the process is deployed, a public URL for the form is generated on the target cluster. #### Get the public link and share it You can access the URL in the **Publication** tab of the **properties panel** and share it with any user via email, social media, or any other communication channel. Public form links were removed in Camunda 8.10. For current start options, use authenticated Tasklist starts or build a custom application with the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). ### Listen to message or signal events Camunda 8 supports message and signal events, which can be used to trigger a process instance when a specific event occurs. Everyone on the platform that knows the message or signal correlation keys can call such a process. To listen to a message or signal event, you need to define a [message](../../../modeler/bpmn/message-events/message-events.md#message-start-events) or [signal start event](../../../modeler/bpmn/signal-events/signal-events.md#signal-start-events) in your process model and configure it to listen for the desired event. Follow these steps to configure a message or signal start event: 1. In the process file, click the start event. 2. Select the **Change element** menu icon. 3. Select **Message start event** or **Signal start event**. 4. Open the **Details** panel on the right side of the modeling interface. 5. Under **Properties**, define the message or signal to listen to. Using messages, you can create a 1:1 relationship between calling processes. With signals, you can create broadcast-like message distributions. 6. [Deploy](#deploy-a-process) the process. As soon as a matching event is received, a process instance will be started. To learn more about message and signal events, refer to our [documentation on events](../../../modeler/bpmn/events.md). ### Best practices for publishing a process - Use the [problems panel](modeling/fix-problems-in-your-diagram.md) to make sure the process is free of errors before publishing it. - Ensure the process works by testing it interactively using [Test mode](validation/test-your-process.md). - Use meaningful names and descriptions for the process and its elements. - Document the process with clear instructions and details on how it should be used. - Make sure the process is accessible to the appropriate users only. :::note When working on Camunda 8 Self-Managed, you can define access permissions on a per-process level using [authorizations](/components/concepts/access-control/authorizations.md). ::: ### Missing client credentials When you deploy a process requiring client credentials, a warning appears in the deployment dialog. The warning offers a link to manage the missing or misconfigured credentials. Client credentials with access to the Orchestration Cluster API are required when at least one of the following elements is used in the process: `service tasks`, `messages`, `signals`, and elements with a `non-connector` task definition. ### Incorrect authorizations When using Camunda Hub to deploy a process model or start a process instance, you may encounter issues related to [resource authorizations](/components/concepts/access-control/authorizations.md). Verify the credentials you're using have the required authorizations to deploy a process model or start a process instance on the selected cluster and tenant. Note that deployments and process starts are performed **as your logged-in user**, so the necessary authorizations must be assigned to your user account. --- ## Task testing You can test a single task directly within Camunda Hub to validate its configuration and logic without executing the entire process. Task testing lets you quickly debug mappings, inputs, and outputs without leaving your implementation context. ## Task testing vs. Test mode While both task testing and Test mode let you validate your BPMN models, they serve different purposes: | Feature / capability | Task testing (Implement mode) | Test mode | | -------------------- | --------------------------------- | --------------------------------- | | Test scope | Single task or sub-process | Process segment or full diagram | | Best for | Quick implementation checks | End-to-end test validation | | Data type | Live data only | Live or mocked data | | Saves test cases | No | Yes | | Mode required | Runs directly in _Implement_ mode | Requires switching to _Test_ mode | Use task testing during implementation for quick feedback, and use Test mode for structured testing with mock data or reusable test cases. ## Prerequisites Before running task testing, ensure you have: - A [runtime connection](../modeling/connect-to-a-runtime.md) to an environment, hosted on an active Camunda 8.8 or later Orchestration Cluster - Permissions to deploy and run processes in the target environment ## Run a task test To test a task in Camunda Hub: 1. In your BPMN diagram, click the task you want to test. 2. Open the **Details** panel on the right side of the modeling interface. 3. Select the **Test** tab. 4. Under **Input**, define the process variables in JSON format to simulate the process context. - Use the **Variables** panel to review available variables in your process. - Confirm that input mappings for your task are configured correctly. - Match variable names and types to those expected by the task. - Provide realistic sample data to reflect actual execution conditions. 5. Click **Run test** to execute the task. Camunda Hub automatically deploys the process before running the test. The task executes on the environment or cluster selected in the **Runtime** selector, using your defined input data. To test against a different runtime, [change the runtime connection](../modeling/connect-to-a-runtime.md#change-the-runtime-connection). If you're connected to a production runtime, the **Test** tab shows **You are connected to a production cluster** (or **production environment**), because running a task there uses production data and can cause real side effects. During execution, the log displays each step in real time, including any states where the test is waiting for an external action to complete. ## View results After the test completes, results appear in the **Details** panel in the **Test** tab under **Result**: | Status | Result description | | :------------------------ | :------------------------------------------------------------------------------------- | | Successful execution | The **Result** section displays the resulting process variables and any updates. | | Incident during execution | Details are shown along with the relevant variable context to help diagnose the issue. | | Execution error | The error message and response details are displayed. | ## Related documentation - [Test a task in Desktop Modeler](../../../../modeler/desktop-modeler/task-testing.md) - [Connect to a runtime](../modeling/connect-to-a-runtime.md) - [Learn about task testing concepts](../../../../modeler/task-testing.md) - [Working with variables](../../../../concepts/variables.md) - [Using Test mode](test-your-process.md) --- ## Test files Test files let you define shareable, low-code test cases for your BPMN processes. They are stored in JSON format and follow the [Camunda Process Test (CPT) JSON schema](https://camunda.com/json-schema/cpt-test-cases/8.9/schema.json), so you can use the same files in Test mode and in an automated CPT test suite. You can create, edit, and manage them directly in Camunda Hub. You can also download these files or synchronize them with your Git repository using Git Sync. ## Create a test file You can create a new test file by [saving a test case in Test mode](test-your-process.md#save-a-test-case). You can also manage test cases and update failing test cases in Test mode. ## Manual editing ### Test case structure Test files follow the [CPT JSON test cases schema](/apis-tools/testing/json-test-cases.md). Test mode adds two optional fields to that schema, `processId` and `metadata`, to link the file to a BPMN process and track test coverage. ```json { "$schema": "https://camunda.com/json-schema/cpt-test-cases/8.9/schema.json", "processId": "order-fulfillment-process", "testCases": [ { "name": "Happy path order processing", "description": "Customer places an order that is processed successfully.", "instructions": [ // Array of instruction objects ], "metadata": { // Optional - for use in Test mode only "processInstanceId": 12345, "coveredFlowNodes": [ { "flowNodeId": "startEvent", "elementType": "START_EVENT" }, { "flowNodeId": "processOrder", "elementType": "SERVICE_TASK" } ], "coveredSequenceFlows": ["flow1", "flow2"] } }, { "name": "Error handling tests", "instructions": [ // Array of instruction objects for error case ] } ] } ``` **Top-level fields** | Field | Required | Description | | ----------- | -------- | ------------------------------------------------------------------------------------------------------------------ | | `processId` | Yes | Test-mode-specific field. The ID of the BPMN process definition the test cases run against. Required by Test mode. | | `testCases` | Yes | An array of test case objects. | **Test case fields** | Field | Required | Description | | -------------- | -------- | --------------------------------------------------------------------------------------------------------------- | | `name` | Yes | A descriptive name for the test case. | | `description` | No | A human-readable description of the test case. | | `instructions` | Yes | An array of instruction objects that define the test steps. | | `metadata` | No | Used by Test mode to show coverage and process instance details. Camunda does not recommend editing this field. | ### Link a process (`processId`) To display the file's test cases in Test mode, you must first link the file to a process. Add a `processId` field with the process ID of the BPMN process you want to test: ```json { "processId": "Process_1" } ``` You can find the BPMN process ID in the properties panel, or in the first ` **Variable**. 2. Select a variable name from the dropdown list, populated from the variables observed in the most recent test run. 3. Enter the expected value for that variable. #### Element assertions Check that a specific element reached an expected state. 1. Click **Add assertion** > **Element**. 2. Select the element on the canvas, or search for it by name in the list. 3. Choose the expected state from the dropdown list (for example, completed, active, or terminated). #### Process instance assertions Check the overall state of the process instance. 1. Click **Add assertion** > **Process instance**. 2. Choose the process instance state from the dropdown list (for example, created, active, completed, or terminated). #### Update or remove an assertion Open a saved test case's detail view in the side panel, then edit or delete any existing assertion the same way you added it. After you save a test case for the first time, assertion changes save automatically—no extra save step is needed. :::note Test mode's variable, path, and process instance assertions use the same instructions as [test files](test-files.md#instructions): `ASSERT_VARIABLES`, `ASSERT_ELEMENT_INSTANCE`, and `ASSERT_PROCESS_INSTANCE`. See the [full instruction reference](/apis-tools/testing/json-test-cases.md#reference-instructions) for the underlying schema. ::: ### Edit a test case Open a test case's detail view in the side panel to edit its name, description, and instructions inline. #### Review test coverage Test coverage is calculated as the percentage of flow nodes in your process that are covered, including all elements, events, and gateways. For example, the coverage is 80% if eight out of ten flow nodes are covered. - On the process definition page, covered paths are highlighted in blue. Click on individual test cases to view their specific coverage. - Once a process instance is completed, the process instance header shows how much your process test coverage would increase if the path was saved as a test case. ![Test coverage indicator](../img/test-coverage.png) :::warning Test coverage will not display as expected if you edit or remove the "metadata" field in the [test file](test-files.md). ::: #### Run a test case You can run test from the **Test cases** panel by clicking **Run all test cases** button or the **Run test case** button for each individual test case. - Test case execution results are marked with either a **Completed** or **Failed** status. - If a test case fails, click **Repair** against each step to update it, especially if diagram changes require further user input (such as when a new flow node is added to a previously saved test case path). See [Repair a test case](#repair-a-test-case). ![Running a test case on the process definition page](../img/test-case-runs.png) #### Review test results A test case passes only when every step, including its assertions, succeeds, not just when the process completes without an incident. - The overall test case status is either **Passed** or **Failed**. - When a test case fails, Test mode highlights the failed step and shows the underlying failure message verbatim (for example, an expected-versus-actual value mismatch for a variable assertion). ### Repair a test case {#repair-a-test-case} When a BPMN change removes or renames an element that an instruction or assertion refers to, the test case can break. It may fail unexpectedly or pass silently because Test mode skips the broken step. ![Broken test case badge and callout](../img/test-broken-test-case-callout.png) - Test mode flags broken test cases with an indicator in the test case list. A callout in the test case detail view explains what's broken. - Use the graphical repair view to fix most breakages without editing JSON: remap an instruction or assertion to a different element, select a new expected value, edit a step in place, or delete it. ![Repair view](../img/test-repair-view.png) - For changes the graphical repair view doesn't cover, open the [test file](test-files.md) in Camunda Hub's file editor and edit the JSON directly. Then, return to Test mode and rerun the test case. ### Limitations {#test-cases-limitations} Test mode displays a warning badge on diagram elements with known limitations. Use the **Show problems**/**Hide problems** toggle near the canvas controls to show or hide these badges. ![Warning badges on diagram elements](../img/test-warning-badges.png) - Call activities are not supported. Test cases containing call activities cannot be executed successfully. - Ad-hoc sub-processes are not supported. Test cases containing ad-hoc sub-processes cannot be executed successfully. - Timer events can't be manually triggered. When a test case reaches a timer event, execution pauses until the timer fires automatically. To skip a timer, use [process instance modification](#modify-a-process-instance) to move the token to the next element. - Test case paths that include process modifications are not supported. - Similarly to process instances, test cases do not run in isolation. For example, if two test case paths are defined for a process and both contain the same message event or signal event, running these test cases simultaneously might lead to unintended consequences. Publishing a message or broadcasting a signal could inadvertently impact the other test case, resulting in the failure of both. - Test mode test cases are compatible with the [CPT JSON instruction format](/apis-tools/testing/json-test-cases.md), but the following [instructions](/apis-tools/testing/json-test-cases.md#reference-instructions) are not supported and will be skipped during execution: - `ASSERT_PROCESS_INSTANCE_MESSAGE_SUBSCRIPTION` - `COMPLETE_JOB_USER_TASK_LISTENER` - `CORRELATE_MESSAGE` - `EVALUATE_CONDITIONAL_START_EVENT` - `EVALUATE_DECISION` - `INCREASE_TIME` - `MOCK_CHILD_PROCESS` - `MOCK_DMN_DECISION` - `MOCK_JOB_WORKER_COMPLETE_JOB` - `MOCK_JOB_WORKER_THROW_BPMN_ERROR` - `SET_TIME` ## Modify a process instance There are two main reasons to modify a process instance in Test mode: 1. **Skip elements**: If your process is stuck, you can continue testing by skipping over elements. For instance, rather than waiting for a 24-hour timer event to elapse or resolving an incident, you can manually advance the active token from the timer event to the next flow node. 2. **Faster prototyping**: Rather than completing the entire process, you can skip over unnecessary sections of a large diagram to debug the changes you made. There are three ways to modify your process instance: - **Add token**: Select the flow node where you'd like to initiate a new token and select **Add** from the modification dropdown. - **Cancel tokens**: Select the flow node where you'd like to cancel active tokens and select **Cancel** from the modification dropdown. - **Move tokens**: Select the flow node from which you'd like to move active tokens and select **Move** from the modification dropdown. Then, select a target flow node to relocate the tokens. :::note Unlike in [Operate](/components/operate/userguide/process-instance-modification.md), these changes are applied immediately. If you need to change variables while modifying a process, use the **Variables** panel to set them separately. Alternatively, for advanced use cases you can modify the process instance from Operate. ::: ![Modifying a process instance](../img/test-modifications.png) ### Limitations {#modifications-limitations} Rewinding a process instance that has modifications applied to is currently not supported. Additionally, some elements do not support specific modifications: - **Add token**/**Move tokens to** modifications are not possible for the following element types: - Start events - Boundary events - Events attached to event-based gateways - **Move tokens from** modification is not possible for a subprocess itself. - **Add token** modifications are not currently supported for elements with multiple running scopes. However, **Move tokens** modifications are supported for elements inside multi-instance subprocesses. The move operation terminates the specific element instance and activates the target element in the same instance of the multi-instance subprocess. ## Rapid iteration To make changes, switch back to **Implement** mode. When returning to Test mode, your process needs to be redeployed. Test mode only shows process instances from the process’s most recent version, so you may not see your previous instances. Test mode saves your inputs when completing user task forms. It auto-fills your last response if you open the same form later in the session. You can click **Reset** to reset the form to its defaults. ## Details Depending on the BPMN element, there may be a different action: - **User tasks** with an embedded form are displayed on click. However, you cannot track assignment logic. - **Call activities** can be navigated into and performed. - **Manual tasks**, **undefined tasks**, **script tasks**, **business rule tasks**, **gateways**, **outbound connectors** and other BPMN elements that control the process's path are automatically completed based on their configuration. - **Service tasks**, **inbound connectors**, message-related tasks, or events are simulated on click or triggered from an external client. However, Test mode attempts message correlation based on the process context but cannot infer keys from FEEL expressions. Therefore, these keys must be manually entered by publishing a message using secondary actions. - Many action icons have secondary actions. For example, **user tasks** can be completed with variables rather than a form, and **service tasks** can trigger an error event. ## Operate vs. Test mode [Operate](/components/operate/operate-introduction.md) is designed to monitor many production process instances and intervene only as necessary, while Test mode is designed to drive a single process instance through the process and mock external systems. Both offer monitoring of a single process instance, its variables and path, incidents, and actions to modify or repair a process instance. Operate offers bulk actions and guardrails against breaking production processes, while Test mode offers a streamlined UX to run through test cases quickly. ## Limitations and availability This section explains why you might not see the **Test** tab, and any additional limitations. For more information about terms, refer to our [licensing and terms page](https://legal.camunda.com/licensing-and-other-legal-terms#c8-saas-trial-edition-and-free-tier-edition-terms). **Version compatibility:** Test mode is compatible with cluster versions starting from 8.10 and higher. ### Camunda 8 SaaS In Camunda 8 SaaS, Test mode is available to all Camunda Hub users with commenter, editor, or admin permissions within a project. Additionally, within their organization, users need to have a [role](/components/hub/organization/users-and-roles.md#roles-and-permissions) which has deployment privileges. [If authorizations are enabled on the environment, users need to have specific permissions instead.](#authorizations) ### Camunda 8 Self-Managed In Self-Managed, Test mode is controlled by the `camunda.hub.feature.test-mode-enabled` [configuration property](/self-managed/components/hub/configuration/properties.md#feature-flags) in Camunda Hub. This is `true` by default for the Docker and Kubernetes distributions. Prior to the 8.10 release, Test mode can be accessed by installing the 8.10.0-alpha [Helm charts](https://github.com/camunda/camunda-platform-helm/blob/camunda-platform-10.4.0/charts/camunda-platform-alpha), or running the 8.10.0-alpha [Docker Compose](https://github.com/camunda/camunda-distributions/tree/main/docker-compose) configuration. ### Features - [Decision table rule](/components/modeler/dmn/decision-table-rule.md) evaluations are not viewable from Test mode. However, they can be inferred from the output variable, or can be viewed from Operate. - Currently, Test mode supports displaying up to 100 flow node instances in the instance history panel, 100 variables in the variables panel, and 100 process instances on the process definition page. To access all related data, you can use Operate. - While you can still interact with your process instance in Test mode (for example, completing jobs or publishing messages), you may be unable to resolve incidents if they occur beyond the 100th flow node instance, as Test mode does not track them. In this case, incident resolution can be managed in Operate. - User tasks with a job worker implementation are deprecated and no longer supported in Test mode from cluster versions 8.8 and above. Please consider migrating to [Camunda user tasks](/components/modeler/bpmn/user-tasks/user-tasks.md#camunda-user-tasks). ## Use Test mode with Camunda Self-Managed After selecting the **Test** tab in Self-Managed, the Test view opens directly. The environment selection and deployment flow is the same as in SaaS, see [opening the Test tab](#opening-the-test-tab). ### Limitations {#self-managed-limitations} - The environment variables `CAMUNDA_CUSTOM_CERT_CHAIN_PATH`, `CAMUNDA_CUSTOM_PRIVATE_KEY_PATH`, `CAMUNDA_CUSTOM_ROOT_CERT_PATH`, and `CAMUNDA_CUSTOM_ROOT_CERT_STRING` can be set in Docker or Helm chart setups. However, these configurations have not been tested with Test mode's behavior, and therefore are not supported when used with Test mode. - Test mode cannot check the presence of connector secrets in Self-Managed setups. If a secret is missing, Test mode will show an incident at runtime. Learn more about [configuring connector secrets](/self-managed/components/connectors/connectors-configuration.md#secrets). ## Test usage and billing considerations The use of Test mode may result in additional charges depending on your organization's [plan](/components/saas/organization/manage-plan/available-plans.md) and the type of cluster you are using. To avoid extra costs, follow these guidelines based on your plan: - **Enterprise plan:** Use a [Basic cluster](/components/saas/clusters.md#cluster-type) for non-production testing to avoid costs. For further assistance, [contact Camunda support](https://camunda.com/services/support/). - **Free trial plan:** You can use any cluster. See [Free Trial clusters](/components/saas/clusters.md#free-trial-clusters). --- ## Token simulation Get a deeper understanding of the BPMN execution semantic. Play, pause, and step through a process just like a BPMN 2.0 compliant engine would. :::tip Token simulation is a BPMN learning tool, not a fully featured BPMN engine. If you are developing your process and want to validate how _exactly_ it would execute on Camunda, [give Test mode a try](test-your-process.md). ::: ### Turn on/off 1. In Camunda Hub, open your BPMN diagram. 2. Switch to [**Design** mode](/components/hub/workspace/modeler/collaboration/collaborate-with-modes.md). 3. In the top right corner of the modeling interface, toggle **Token simulation**. (You can also use the keyboard shortcut `T` inside the canvas). The modeling features will not work while you are in token simulation mode. ### Start simulation The simulation can be started by triggering an event. To do this, select the "play" icon hovering over the event. ### Token simulation palette The palette on the top left side provides the following controls: - Play/pause simulation - Reset simulation - Show simulation log ### Animation speed palette The speed of the simulation can be changed using the arrow controls at the top of the screen. --- ## Process modeling Camunda provides modeling tools for designing and implementing executable diagrams with [BPMN](./bpmn/bpmn.md), [DMN](./dmn/dmn.md), and [Forms](./forms/camunda-forms-reference.md). Choose the modeler that best fits how you work: - [Camunda Hub](../hub/workspace/modeler/index.md): Use Camunda's browser-based modeler in Hub to collaborate on diagrams in Camunda 8 SaaS or Self-Managed. - [Desktop Modeler](./desktop-modeler/index.md): Install the desktop application to model locally and integrate with your development environment. ## Open the modeler in Camunda Hub 1. Log in to Camunda 8 SaaS. 2. Navigate to a workspace. 3. In your workspace, open a project and select a file to edit. ## Get Desktop Modeler If you want to model locally, work offline, or edit the underlying XML directly, download Desktop Modeler for macOS, Windows, or Linux from the [Camunda downloads page](https://camunda.com/download/modeler/). For installation details and features, see [Desktop Modeler](./desktop-modeler/index.md). :::note Desktop Modeler includes an XML editor for [BPMN](./bpmn/bpmn.md) and [Decision Model and Notation (DMN)](./dmn/dmn.md) diagrams, with its own undo and redo history, search and replace, and XML syntax highlighting. ::: If you use both tools, see [using Camunda Hub and Desktop Modeler together](./using-hub-and-desktop-modeler-together.md). ## Next steps - [Modeling BPMN](/components/modeler/bpmn/automating-a-process-using-bpmn.md) - Learn how to model an automated process in this tutorial using Camunda Hub with Camunda 8 SaaS. - [Camunda Forms in Camunda Hub](/components/hub/workspace/modeler/modeling/utilize-forms.md) - Design and configure forms, and connect them to a user task or start event to implement a task form in your application. - [DMN](./dmn/dmn.md) - In DMN, model and execute decisions using the same language. As a business analyst, model the rules that lead to a decision in comprehensive tables, and execute these tables directly by a decision engine like Camunda. - [Out-of-the-box connectors](/components/connectors/use-connectors/configuring-out-of-the-box-connector.md) - Utilize pre-built connectivity with a number of outside systems via our [Camunda Marketplace](../hub/workspace/modeler/modeling/camunda-marketplace.md). Connectors are modular by nature, reusable, and accelerate automation across those systems. - [Element templates](/components/modeler/element-templates/about-templates.md) - Learn how to create element templates to extend the modeler. - [Custom connectors](/components/connectors/manage-connector-templates.md) - Learn how to build custom connectors alongside external systems based on our out-of-the-box connectors. --- ## Ad-hoc sub-processes With ad-hoc sub-processes, you can model a flexible set of BPMN activities that execute in any order, any number of times. :::info With the [AI Agent Sub-process connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess.md), you can implement an AI agent that dynamically selects and invokes tools, using the [job worker implementation](#job-worker-implementation) of an ad-hoc sub-process. ::: ## About Ad-hoc sub-processes are a special kind of [embedded subprocess](../embedded-subprocesses/embedded-subprocesses.md) with an **ad-hoc marker** (represented by a **~** tilde character). Compared to regular subprocesses, ad-hoc sub-processes allow more flexibility for executing inner elements. ![A demo process with an ad-hoc sub-process. Some elements inside the subprocess are active.](assets/ad-hoc-subprocess.png) The inner elements of an ad-hoc sub-process are not connected to a start or end event. Each element can be executed multiple times, in any order, or skipped. If elements depend on each other, the elements can be connected by a sequence flow to build a structured sequence within the ad-hoc sub-process. An ad-hoc sub-process can be handled [internally by Zeebe](#bpmn-implementation), or by using a [job worker](#job-worker-implementation). ### Constraints An ad-hoc sub-process has the following constraints: - Must have at least one activity. - Must not have start events or end events. ## BPMN implementation By default, ad-hoc sub-processes are handled internally in Zeebe. You can model which [elements to activate](#activate-an-element) and when the sub-process is [completed](#completion). Alternatively, use the [ad-hoc sub-process API](/apis-tools/orchestration-cluster-api-rest/specifications/activate-ad-hoc-sub-process-activities.api.mdx) to activate elements manually. ### Activate an element An ad-hoc sub-process can define an expression `activeElementsCollection` that should return a [list](../../feel/language-guide/feel-data-types.md#list) of strings. Each string in the list should match to an ID of an inner element of the ad-hoc sub-process. Usually, the expression accesses a process variable that was created before and holds the list of element IDs. ![A process with an ad-hoc sub-process that shows how a variable is used to active the inner elements.](assets/ad-hoc-subprocess-activation.png) When a process instance reaches an ad-hoc sub-process, it evaluates the expression `activeElementsCollection` and activates all elements whose element IDs are in the list. If the list is empty or the expression is not defined, no element is activated and the ad-hoc sub-process remains active. If the expression doesn't evaluate to a list of strings, or the list contains other values than inner element IDs, the process instance creates an incident. :::note Currently, it is not possible to activate elements dynamically after the ad-hoc sub-process is activated, only on entering the subprocess. ::: ### Completion An ad-hoc sub-process can define an optional `completionCondition` [boolean expression](/components/modeler/feel/language-guide/feel-boolean-expressions.md) that is evaluated every time an inner element is completed. - If the expression evaluates to `true` after completing an inner element, the ad-hoc sub-process is completed and the process instance takes the outgoing sequence flows. - If no `completionCondition` is defined, the ad-hoc sub-process is completed after all [activated elements](#activate-an-element) are completed. A `cancelRemainingInstances` boolean attribute can be configured to influence the ad-hoc sub-process behavior when the completion condition is met. - If set to `true` (default value), all remaining active instances of inner elements are terminated and the ad-hoc sub-process is directly completed. - If set to `false`, the ad-hoc sub-process waits for the completion of all active instances before completing. ## Job worker implementation You can handle an ad-hoc sub-process using a [job worker](/components/concepts/job-workers.md). To do this, define the sub-process with a task definition. The job worker can then control the sub-process by activating inner elements and deciding when it completes. When an ad-hoc sub-process is defined as a job worker, it creates a job upon activation. The worker must decide what the next step is. It can use the `adHocSubProcessElements` variable (see [special ad-hoc sub-process variables](#special-ad-hoc-sub-process-variables)) to determine available elements. When a process instance reaches an ad-hoc sub-process with a job worker implementation: ![A sequence diagram showing the flow of how a job worker interacts with an ad-hoc sub-process.](assets/ad-hoc-subprocess-job-sequence-diagram.png) 1. Zeebe creates a corresponding job and waits for its completion. 2. The job worker decides which elements to activate and completes the job with an [`adHocSubProcess` job result](/apis-tools/orchestration-cluster-api-rest/specifications/complete-job.api.mdx). 3. Zeebe activates the elements from the job result. 4. When any of the flows inside the ad-hoc sub-process completes, Zeebe creates a new job for the ad-hoc sub-process. 5. The job worker decides the next step. It can activate more elements or fulfill the completion condition. If the condition is fulfilled, the job worker can specify whether to cancel active elements. It cannot fulfill both the completion condition and activate new elements at the same time. Because a worker can activate multiple elements at once, and Zeebe creates a job whenever one completes, the job for the ad-hoc sub-process may be recreated during execution. There is only one active job for the ad-hoc sub-process at a time. The job worker should expect that: - A job may be recreated while it is still processing. - Job completion may result in a `NOT_FOUND` rejection. ### Event sub-processes In an ad-hoc sub-process handled by a job worker, the job worker decides which elements to activate and when the ad-hoc sub-process is complete. However, [event sub-processes](../event-subprocesses/event-subprocesses.md) are triggered by events, not by the job worker, so they can run outside the job worker's direct control. When an event sub-process finishes, it triggers the job worker again, handing control back so it can activate new elements (optionally canceling others) or complete the ad-hoc sub-process. :::info Interrupting event sub-process An interrupting event sub-process is a special case: it cancels all active elements and completes the ad-hoc sub-process without waiting for the job worker to decide. ::: ## Collect output You can collect the output of inner flows in an ad-hoc sub-process by defining the `outputCollection` and `outputElement` expressions. - `outputCollection` defines the variable name that stores the collected output (for example, `results`). This variable is created as a local variable of the ad-hoc sub-process and updated whenever an inner flow completes. When the ad-hoc sub-process completes, the `outputCollection` variable is [propagated](components/concepts/variables.md#variable-propagation) to the parent scope. - `outputElement` defines the output of the inner flow (for example, `= result`). This expression usually [accesses a variable](/components/modeler/feel/language-guide/feel-variables.md#access-variable) of the inner flow that holds the output value. This variable should be created with the output value, for example, by a job worker providing a variable with the name `result`. When an inner flow completes, the `outputElement` expression is evaluated and the result is added to the `outputCollection`. ## Special ad-hoc sub-process variables When an ad-hoc sub-process is activated, the `adHocSubProcessElements` variable is created in its scope. This variable provides metadata about the sub-process and its inner elements. Job workers can use it to decide which elements to activate. The variable contains a list of activatable elements. Each element includes: - `elementId`: The ID of the element. - `elementName`: The name of the element. - `documentation`: The documentation of the element. - `properties`: The properties defined on the element. - `parameters`: Parameters defined using the [`fromAi`](/components/modeler/feel/builtin-functions/feel-built-in-functions-ai-agent.md#fromaivalue) FEEL function. :::info Do not update the `adHocSubProcessElements` variable. Changing its value can cause unexpected behavior. ::: ## Variable mappings An ad-hoc sub-process can define input and output [variable mappings](../../../concepts/variables.md#inputoutput-variable-mappings). **Input variable mappings** are applied on activating the ad-hoc sub-process and before evaluating the expression `activeElementsCollection`. They can be used to create local variables for the ad-hoc sub-process. **Output variable mappings** are applied on completing the ad-hoc sub-process. They can be used to propagate local variables from the ad-hoc sub-process into the process instance. By default, no local variables are propagated. Variables written by the activities the ad-hoc sub-process activates stay local to each activation and are not propagated on their own. If an output collection is configured, it is propagated to the parent scope when the ad-hoc sub-process completes. For how an ad-hoc sub-process compares to other elements, see [variable propagation by BPMN element](/components/concepts/variables.md#variable-propagation-by-bpmn-element). ## XML representation An ad-hoc sub-process is represented in BPMN XML using the `adHocSubProcess` element. The following example configures the active elements collection via the Zeebe `adHoc` extension and defines a completion condition. ```xml ... more contained elements ... =myCondition ``` --- ## Iso 8601 Date Time A specific point in time defined as ISO 8601 combined date and time representation. It must contain timezone information, either `Z` for UTC or a zone offset. Optionally, it can contain a zone id. - `2019-10-01T12:00:00Z` - UTC time - `2019-10-02T08:09:40+02:00` - UTC plus two hours zone offset - `2019-10-02T08:09:40+02:00[Europe/Berlin]` - UTC plus two hours zone offset at Berlin --- ## Design a process using BPMN Beginner Time estimate: 20 minutes Business Process Model and Notation (BPMN) is the global standard for process modeling. Combining BPMN, an easy-to-adopt visual modeling language, with Camunda, you can automate your business processes. Processes are the algorithms that determine how an organization runs based on independent tasks. Successful businesses grow from proven, effective processes. Therefore, Camunda’s workflow engine executes processes defined in BPMN to ensure these processes can be swiftly orchestrated within a diagram. Take the following example where we've outlined a process in a BPMN diagram to send an email. Don't worry too much about the symbols as we'll get to that shortly. For now, recognize the start and end of the process, comprised of entering a message, and sending the email. ![sending email bpmn diagram](./img/simple-bpmn-process.png) BPMN offers control and visibility over your critical business processes in a way that is understandable for both experienced engineers and business stakeholders. The workflow engine orchestrates processes that span across a wide variety of elements, including APIs, microservices, business decisions and rules, human work, IoT devices, RPA bots, and more. ## Set up Begin by building your BPMN diagrams with Camunda Hub. To get started, ensure you’ve [created a Camunda 8 account](/components/saas/organization/manage-plan/create-account.md). ## Getting started with BPMN Once logged in to your Camunda 8 account, take the following steps: 1. Within Camunda Hub, navigate to **Workspaces**, and open your workspace. 2. In the workspace, click **Create project**. 3. In the project, click **Create new > BPMN diagram**. 4. In the top navigation, open the menu next to **New BPMN Diagram**, and click **Rename**. 5. Name the diagram "Bake a Cake". ### BPMN elements Before building out the diagram to bake a cake, let's examine the significance of the components on the left side of the screen. You can build out a BPMN diagram for a process using several elements, including the following: - Events: The things that happen. For example, start and end events which begin and terminate the process. - Tasks: For example, user tasks for a particular user to complete, or service tasks to invoke various web services. - Gateways: For example, parallel gateways that move the process along between two tasks at the same time. - Utilize [variables](/components/concepts/variables.md) to reflect the data of process instances. - Leverage [expressions](/components/concepts/expressions.md) to access variables and calculate their value(s). - Subprocesses: For example, a transaction subprocess which can be used to group multiple activities to a transaction. For a complete list of BPMN elements and their capabilities, visit the [BPMN reference material](/components/modeler/bpmn/bpmn.md). ### BPMN in action Using these elements, let's build out a BPMN diagram to examine the process of baking a cake. Take the following steps: 1. On our diagram, we've already been given an element as a start event in the shape of a circle. Click on the element and select the **Change element** menu icon. For now, keep it as a start event. Double click on the circle to add text. 2. Drag and drop an arrow to the first task (the rectangle shape), or click the start event, and then click the task element to automatically attach it. 3. Click on the element and select the **Change element** menu icon to change the type to a manual task, which will be named "Purchase Ingredients." Note that each element added has adjustable attributes. Use the properties panel on the right side of the page to adjust these attributes. 4. Click on the manual task to connect a gateway to it. By clicking on the element and selecting the **Change element** menu icon and declaring it a parallel gateway, you can connect it to two tasks that can happen at the same time: mixing the ingredients, and preheating the oven. ![baking a cake bpmn sample](./img/bake-cake-bpmn.png) 5. Attach the next gateway once these two tasks have completed to move forward. 6. Add a manual task to bake the cake, and finally a manual task to ice the cake. 7. Add an end event, represented by a bold circle. 8. No need to save. Camunda Hub will autosave every change you make. ![completed bpmn diagram](./img/complete-baking-cake-bpmn.png) :::note You can also import a BPMN diagram with Camunda Hub. See how to do that [here](/components/hub/workspace/modeler/modeling/import-diagram.md). ::: ## Execute your process diagram :::note If you change a diagram and it is auto-saved, this has no effect on your cluster(s). When you deploy the diagram, it becomes available on the selected cluster and new instances can start. ::: To execute your completed process diagram: 1. Make sure an [environment is assigned to your workspace](/components/hub/organization/manage-environments/assign-environments.md). 1. Reopen the BPMN diagram. 1. At the top right of the modeling interface, click **Deploy & run**. 1. Select a target **Deployment environment**. 1. Click **Deploy & run**. You can now monitor your instances in [Operate](/components/operate/userguide/basic-operate-navigation.md#open-operate). :::note Variables are part of a process instance and represent the data of the instance. To learn more about these values, variable scope, and input/output mappings, visit our documentation on [variables](/components/concepts/variables.md). ::: ## Additional resources and next steps - [Camunda BPMN tutorial](https://camunda.com/bpmn/) - [BPMN implementation reference](https://docs.camunda.org/manual/latest/reference/bpmn20/) - [Zeebe engine](/components/zeebe/zeebe-overview.md) - [BPMN reference](/components/modeler/bpmn/bpmn.md) - [Camunda Academy: BPMN Overview](https://academy.camunda.com/bpmn-overview) - [Operate](/components/operate/operate-introduction.md) --- ## BPMN coverage export const Highlight = ({children, color}) => ( {children} ); The following BPMN elements are supported by our modeling tools. Elements highlighted in green are supported for execution by Camunda 8. Click on an element to navigate to the documentation. ## Participants ## Subprocesses ## Tasks ## Gateways ## Markers ## Data :::note `DataObject` and `DataStore`, like other BPMN standard IO mappings, are supported by Camunda for modeling purposes only. ::: ## Artifacts ## Events Type Start Intermediate End Normal Event Subprocess Event Subprocess non-interrupting Catch Boundary Boundary non-interrupting Throw None Message Timer Error Signal Conditional Escalation Compensation Cancel Terminate Link Multiple Multiple Parallel --- ## BPMN primer Business Process Model and Notation 2.0 (BPMN) is an industry standard for process modeling and execution. A BPMN process is an XML document that has a visual representation. For example, here is a BPMN process: ![process](assets/process.png)
The corresponding XML ```xml SequenceFlow_1bq1azi SequenceFlow_0ojoaqz SequenceFlow_1bq1azi SequenceFlow_09hqjpg SequenceFlow_09hqjpg SequenceFlow_1ea1mpb SequenceFlow_1ea1mpb SequenceFlow_0ojoaqz ```
This duality makes BPMN very powerful. The XML document contains all the necessary information to be interpreted by workflow engines and modeling tools like Zeebe. At the same time, the visual representation contains just enough information to be quickly understood by humans, even when they are non-technical people. The BPMN model is source code and documentation in one artifact. The following is an introduction to BPMN 2.0, its elements, and their execution semantics. It tries to briefly provide an intuitive understanding of BPMN's power, but does not cover the entire feature set. For more exhaustive BPMN resources, refer to the [reference links](#additional-resources) at the end of this section. ## Modeling BPMN diagrams The best tools for modeling BPMN diagrams for Zeebe are [Camunda Hub](/components/hub/workspace/modeler/index.md) and [Desktop Modeler](/components/modeler/desktop-modeler/index.md). Learn more by [modeling your first diagram](/components/hub/workspace/modeler/modeling/model-your-first-diagram.md). ## BPMN elements ### Sequence flow: Controlling the flow of execution A core concept of BPMN is a **sequence flow** that defines the order in which steps in the process happen. In BPMN's visual representation, a sequence flow is an arrow connecting two elements. The direction of the arrow indicates their order of execution. ![sequence flow](./assets/sequenceflow.png) You can think of process execution as tokens running through the process model. When a process is started, a token is created at the beginning of the model and advances with every completed step. When the token reaches the end of the process, it is consumed and the process instance ends. Zeebe's task is to drive the token and to make sure the job workers are invoked whenever necessary.
### Tasks: Units of work The basic elements of BPMN processes are tasks; these are atomic units of work composed to create a meaningful result. Whenever a token reaches a task, the token stops and Zeebe creates a job and notifies a registered worker to perform work. When that handler signals completion, the token continues on the outgoing sequence flow.
Choosing the granularity of a task is up to the person modeling the process. For example, the activity of processing an order can be modeled as a single _Process Order_ task, or as three individual tasks _Collect Money_, _Fetch Items_, _Ship Parcel_. If you use Zeebe to orchestrate microservices, one task can represent one microservice invocation. Refer to the [tasks](tasks.md) section on which types of tasks are currently supported and how to use them. ### Gateways: Steering flow Gateways are elements that route tokens in more complex patterns than plain sequence flow. BPMN's **exclusive gateway** chooses one sequence flow out of many based on data:
BPMN's **parallel gateway** generates new tokens by activating multiple sequence flows in parallel:
Refer to the [gateways](gateways.md) section on which types of gateways are currently supported and how to use them. ### Events: Waiting for something to happen **Events** in BPMN represent things that _happen_. A process can react to events (_catching_ event) as well as emit events (_throwing_ event). For example:
The circle with the envelope symbol is a catching message event. It makes the token continue as soon as a message is received. The XML representation of the process contains the criteria for which kind of message triggers continuation. Events can be added to the process in various ways. Not only can they be used to make a token wait at a certain point, but also for interrupting a token's progress. Refer to the [events](events.md) section on which types of events are currently supported and how to use them. ### Subprocesses: Grouping elements [**Subprocesses**](/components/modeler/bpmn/subprocesses.md) are element containers that allow defining common functionality. For example, you can attach an event to a subprocess's border. When the event is triggered, the subprocess is interrupted, regardless which of its elements is currently active. Refer to the [subprocesses](subprocesses.md) section on which types of subprocesses are currently supported and how to use them. :::note Swim lanes (pools and lanes) are only available at the top-level process or collaboration level. They cannot be added inside a subprocess. This is a constraint of the BPMN 2.0 specification. ::: ## Additional resources - [BPMN specification](http://www.bpmn.org/) - [BPMN tutorial](https://camunda.com/bpmn/) - [Full BPMN reference](https://camunda.com/bpmn/reference/) - [BPMN book](https://www.amazon.com/dp/1086302095/) --- ## Modeling with BPMN **Business Process Model and Notation (BPMN)** was developed as a graphical notation to represent complex processes. It is maintained by the non-profit [The Object Management Group (OMG)](https://www.omg.org/spec/BPMN/) and employed by numerous organizations globally. The visual nature of BPMN enables greater collaboration between different teams, particularly within [Camunda Hub](/components/hub/workspace/modeler/index.md). :::note BPMN diagrams must be created for the process engine they intend to be deployed on. You cannot run a BPMN diagram modeled for Camunda 7 in Camunda 8, or vice versa, at this time. ::: ## Start modeling ![Start Modeling](./assets/quickstart-2.png) [Camunda Hub](/components/hub/workspace/modeler/index.md) and Desktop Modeler both offer a similar core BPMN 2.0 Modeling experience: - Add BPMN elements from the palette on the left side of the page by dragging and dropping them onto the diagram canvas. - Change the type of element in place by clicking on an element to reveal the context menu. Then, select the **Change element** menu icon to change the type of element to a [service task](./service-tasks/service-tasks.md) or [user task](./user-tasks/user-tasks.md), for example. ## Creating BPMN elements Using BPMN in Hub or Desktop Modeler, you can create more BPMN 2.0 elements like task types and event definitions. For example, [service tasks](/components/modeler/bpmn/service-tasks/service-tasks.md) within Camunda 8 require you to set a task type and implement [job workers](/components/concepts/job-workers.md) to perform a particular task in a process. Review [the BPMN 2.0 coverage documentation](/components/modeler/bpmn/bpmn-coverage.md) for a complete list of BPMN elements supported by our modeling tools. Additionally, visit [creating readable process models](/components/best-practices/modeling/creating-readable-process-models.md) for general guidelines on modeling with BPMN. ## BPMN 2.0 properties for execution ![Save BPMN Diagram](./assets/quickstart-3.png) In the properties panel on the right side, view and edit attributes that apply to the selected element. --- ## Business rule tasks A business rule task is used to model the evaluation of a business rule. For example, a decision modeled in [Decision Model and Notation](https://www.omg.org/dmn/) (DMN). ![task](assets/business-rule-task.png) :::info Camunda 8 supports alternative task implementations for the business rule task. If you want to use your own implementation for a business rule task, refer to the [job worker implementation](#job-worker-implementation) section below. The sections before this job worker implementation apply to the DMN decision implementation only. ::: :::info If you only want to evaluate a DMN decision, you can use the [`EvaluateDecision`](/apis-tools/zeebe-api/gateway-service.md#evaluatedecision-rpc) API. ::: When the process instance arrives at a business rule task, a decision is evaluated using the internal DMN decision engine. Once the decision is made, the process instance continues. If the decision evaluation is unsuccessful, an [incident](/components/concepts/incidents.md) is raised at the business rule task. When the incident is resolved, the decision is evaluated again. Used as a tool inside an [ad-hoc sub-process](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md), a business rule task lets an [AI agent](/reference/glossary.md#ai-agent) delegate part of its decision to a governed DMN decision instead of LLM reasoning, keeping that step deterministic. See [AI agent tool definitions](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-tool-definitions.md) for how tools are declared. ## Defining a called decision {#defining-a-task} A called decision links the business rule task to a DMN decision, either to a [decision table](/components/modeler/dmn/decision-table.md) or to a [decision literal expression](/components/modeler/dmn/decision-literal-expression.md) . It can be defined using the `zeebe:calledDecision` extension element. A business rule task must define the [DMN decision id](/components/modeler/dmn/decision-table.md#decision-id) of the called decision as `decisionId`. Usually, the `decisionId` is defined as a [static value](/components/concepts/expressions.md#expressions-vs-static-values) (for example, `shipping_box_size`), but it can also be defined as an [expression](/components/concepts/expressions.md) ( for example, `= "shipping_box_size_" + countryCode`). The expression is evaluated on activating the business rule task (or when an incident at the business rule task is resolved) after input mappings have been applied. The expression must result in a `string`. The `bindingType` attribute determines which version of the called decision is evaluated: - `latest`: The latest deployed version at the moment the business rule task is activated. - `deployment`: The version that was deployed together with the currently running version of the process. - `versionTag`: The latest deployed version that is annotated with the version tag specified in the `versionTag` attribute. Usually, the `versionTag` is defined as a [static value](/components/concepts/expressions.md#expressions-vs-static-values) (for example, `v1.0`), but it can also be defined as an [expression](/components/concepts/expressions.md) (for example, `= "v" + version`). The expression is evaluated on activating the business rule task or when an incident at the business rule task is resolved, after input mappings have been applied. The expression must result in a `string`. To learn more about choosing binding types, see [choosing the resource binding type](/components/best-practices/modeling/choosing-the-resource-binding-type.md). :::note If the `bindingType` attribute is not specified, `latest` is used as the default. ::: A business rule task must define the process variable name of the decision result as `resultVariable`. The result of the decision is stored in this variable. The `resultVariable` is defined as a static value. ## Variable mappings By default, the variable defined by `resultVariable` is merged into the process instance. This behavior can be customized by defining an output mapping at the business rule task. All variables in scope of the business rule task are available to the decision engine when the decision is evaluated. Input mappings can be used to transform the variables into a format accepted by the decision. :::info Input mappings are applied on activating the business rule task (or when an incident at the business rule task is resolved), before the decision evaluation. When an incident is resolved at the business rule task, the input mappings are applied again before evaluating the decision. This can affect the result of the decision. ::: For more information about this topic, visit the documentation about [input/output variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings). ## Job worker implementation A business rule task does not have to evaluate a decision modeled with DMN. Instead, you can also use [job workers](/components/concepts/job-workers.md) to implement your business rule task. A job worker implementation can be defined using the `zeebe:taskDefinition` extension element. Business rule tasks with a job worker implementation behave exactly like [service tasks](/components/modeler/bpmn/service-tasks/service-tasks.md). The differences between these task types are the visual representation (i.e. the task marker) and the semantics for the model. When a process instance enters a business rule task with alternative task implementation, it creates a corresponding job and waits for its completion. A job worker should request jobs of this job type and process them. When the job is completed, the process instance continues. A business rule task must define a [job type](/components/modeler/bpmn/service-tasks/service-tasks.md#task-definition) the same way as a service task does. This is used as reference to specify which job workers request the respective business rule task job. For example, `order-items`. Note that `type` can be specified as any static value (`myType`) or as a FEEL [expression](../../../concepts/expressions.md) prefixed by `=` that evaluates to any FEEL string; for example, `= "order-" + priorityGroup`. Use [task headers](/components/modeler/bpmn/service-tasks/service-tasks.md#task-headers) to pass static parameters to the job worker (for example, the key of the decision to evaluate). Define [variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings) the [same way as a service task does](/components/modeler/bpmn/service-tasks/service-tasks.md#variable-mappings) to transform the variables passed to the job worker, or to customize how the variables of the job merge. ### Job priority This task type supports `zeebe:jobPriorityDefinition` when implemented as a job worker. You can define job priority on the process as a default and override it on this task. For priority behavior and limitations, see [Job prioritization](../../../concepts/job-workers.md#job-prioritization). ## Additional resources ### XML representation A business rule task with a called decision that does not specify the binding type (`latest` is used implicitly): ```xml ``` A business rule task with a called decision that uses the `deployment` binding type: ```xml ``` A business rule task with a called decision that uses the `versionTag` binding type with a static version tag: ```xml ``` A business rule task with a called decision that uses the `versionTag` binding type with expressions for both `decisionId` and `versionTag`: ```xml ``` A business rule task with a job worker implementation and a custom header: ```xml ``` ### References - [DMN decision](/components/modeler/dmn/dmn.md) - [Job handling](/components/concepts/job-workers.md) - [Variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings) --- ## Call activities A call activity (or reusable subprocess) allows you to call and invoke another process as part of this process. It's similar to an [embedded subprocess](../embedded-subprocesses/embedded-subprocesses.md), but the process is externalized (i.e. stored as separated BPMN) and can be invoked by different processes. ![call-activity](assets/call-activities-example.png) When a call activity is entered, a new process instance of the referenced process is created. The new process instance is activated at the **none start event**. The process can have start events of other types, but they are ignored. When the created process instance is completed, the call activity is left and the outgoing sequence flow is taken. ## Defining the called process A call activity must define the BPMN process ID of the called process as `processId`. Usually, the `processId` is defined as a [static value](/components/concepts/expressions.md#expressions-vs-static-values) (e.g. `shipping-process`), but it can also be defined as [expression](/components/concepts/expressions.md) (e.g. `= "shipping-" + tenantId`). The expression is evaluated on activating the call activity and must result in a `string`. The `bindingType` attribute determines which version of the called process is instantiated: - `latest`: The latest deployed version at the moment the call activity is activated. - `deployment`: The version that was deployed together with the currently running version of the calling process. - `versionTag`: The latest deployed version that is annotated with the version tag specified in the `versionTag` attribute. To learn more about choosing binding types, see [choosing the resource binding type](/components/best-practices/modeling/choosing-the-resource-binding-type.md). :::note If the `bindingType` attribute is not specified, `latest` is used as the default. ::: ## Boundary events ![call-activity-boundary-event](assets/call-activities-boundary-events.png) Interrupting and non-interrupting boundary events can be attached to a call activity. When an interrupting boundary event is triggered, the call activity and the created process instance are terminated. The variables of the created process instance are not propagated to the call activity. When a non-interrupting boundary event is triggered, the created process instance is not affected. The activities at the outgoing path have no access to the variables of the created process instance since they are bound to the other process instance. ## Variable mappings Input mappings can be used to create new local variables in the scope of the call activity. These variables are also copied to the created process instance. If the attribute `propagateAllChildVariables` is set (default: `true`), all variables of the created process instance are propagated to the call activity. This behavior can be customized by defining output mappings at the call activity. The output mappings are applied on completing the call activity and only those variables that are defined in the output mappings are propagated. If you set `propagateAllChildVariables` to `false` and define no output mappings, the variables of the created process instance are discarded when the call activity completes. To return only selected variables to the caller, keep `propagateAllChildVariables` enabled and define output mappings. It's recommended to define output mappings if the call activity is in a parallel flow (e.g. when it is marked as [parallel multi-instance](../multi-instance/multi-instance.md#variable-mappings)). Otherwise, variables can be accidentally overridden when they are changed in the parallel flow. Disable `propagateAllChildVariables` only if the caller does not need the child process variables at all. By default, all variables of the call activity scope are copied to the created process instance. This can be limited to copying only the local variables of the call activity, by setting the attribute `propagateAllParentVariables` to `false`. By disabling this attribute, variables existing at higher scopes are no longer copied. If the attribute `propagateAllParentVariables` is set (default: `true`), all variables are propagated to the child process instance. For how a call activity compares to other elements, see [variable propagation by BPMN element](/components/concepts/variables.md#variable-propagation-by-bpmn-element). ## Business ID propagation When a parent process instance has a business ID, child instances created by call activities inherit it by default. Starting in 8.10, you can configure each call activity to override this behavior. ### Default behavior If no `businessId` configuration is set on the call activity, the child instance inherits the parent's business ID automatically. This is the 8.9 default. Existing models are unaffected. ### Override with a literal value or FEEL expression To set a different business ID on the child instance, configure the `businessId` attribute on the `zeebe:calledElement` extension element. The value is resolved once at child creation and is then immutable. ```xml ``` You can use the FEEL expression context variable `camunda.processInstance.businessId` to reference the parent instance's business ID in the expression. | Configuration | Effect | | :-------------------------------------- | :------------------------------------------------------------------------------------------------- | | Attribute absent | Child inherits the parent's business ID (default). | | Literal string | Child is created with that exact string as its business ID. | | FEEL expression (prefixed with `=`) | Child gets the evaluated result. Access the parent's ID with `camunda.processInstance.businessId`. | | Empty string `""` or expression `=null` | Child starts with no business ID. No incident is raised. | ### Validation and error handling Business ID values go through two validation stages: :::danger Deploy-time rejection The process cannot be deployed if: - The `businessId` attribute contains an invalid FEEL expression syntax. - A static literal value exceeds 256 characters. ::: If the value is empty or intentionally `null` at runtime (for example, `=null` or a FEEL expression that cleanly evaluates to `null`), the child starts with no business ID and no incident is raised. :::warning Runtime incident (resolvable) An incident is raised on the call activity if the FEEL expression: - Evaluates to `null` due to evaluation warnings — for example, a missing variable caused the null rather than an intentional `=null`. - Evaluates to a string exceeding 256 characters. - Evaluates to a non-string, non-null type (number, boolean, or list). ::: To resolve the incident: correct the variables or expression, then retry activation. The child instance is created with the corrected business ID. ## Additional resources ### XML representation A call activity with static process id, propagation of all child variables turned on, and no explicit binding type (`latest` is used implicitly): ```xml ``` A call activity with the `deployment` binding type: ```xml ``` A call activity with the `versionTag` binding type: ```xml ``` A call activity with copying of all variables to the child process turned off: ```xml ``` ### References - [Expressions](/components/concepts/expressions.md) - [Variable scopes](/components/concepts/variables.md#variable-scopes) - [Variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings) --- ## Compensation events Compensation events assist with undoing steps that were already successfully completed in the case that their results are no longer desired and need to be reversed. To revert the effects of an activity, a compensation boundary event is attached to the activity. This activity is called **compensation activity**. The compensation boundary event is associated with the [compensation handler](../compensation-handler/compensation-handler.md), an activity with a compensation marker that is in charge of reverting the effects of the compensation activity. ![Process with compensation throw event](assets/compensation-throw-event.png) The example above shows the execution of compensation events: 1. After the service task `A` is completed, the process reaches the compensation intermediate throw event. 2. This invokes the compensation handler `Undo A` associated with the compensation boundary event. 3. Once the compensation handler `Undo A` is completed, the process completes the compensation intermediate throw event and takes the outgoing sequence flow. ## Triggering compensation When a process instance enters a compensation intermediate throw or end event, it triggers the compensation within its scope and invokes all compensation handlers of completed activities. The compensation handlers of active or terminated activities are not invoked. The compensation throw event remains active until all invoked compensation handlers are completed. :::note The process instance invokes all compensation handlers at once without any specific order. If the order is important, the compensation can be triggered for a specific activity. Read more about this case in [triggering compensation for an activity](#triggering-compensation-for-a-specific-activity). ::: ## Compensating embedded subprocesses If a process instance enters a compensation throw event and there are completed [embedded subprocesses](/components/modeler/bpmn/embedded-subprocesses/embedded-subprocesses.md) in the same scope, it invokes the compensation handlers within these subprocesses and nested subprocesses. The compensation handlers are not invoked if the subprocess is active or terminated. ![Process with embedded subprocesses](assets/compensation-embedded-subprocess.png) If the compensation throw event is inside an embedded subprocess, the process instance invokes only the compensation handlers within the subprocess. It doesn't invoke any compensation handler outside the subprocess. :::info Compensation handlers of child processes are not invoked. The triggering of the compensation stops at the call activity. To revert the effects of a child process, attach a compensation boundary event on the call activity. Read more about this in [call activities as compensation handlers](../compensation-handler/compensation-handler.md#call-activity-as-compensation-handler). ::: ## Compensating multi-instance activities The compensation handler of a multi-instance activity is invoked only once, rather than for each item in the input collection. The compensation handler is responsible for reverting the effects of all instances of the multi-instance activity. ![Process with multi instance activity](assets/compensation-multi-instance-activity.png) To revert the effects of each instance separately, the compensation handler could be marked as multi-instance as well. Read more about this in [multi-instance activities as compensation handlers](../compensation-handler/compensation-handler.md#multi-instance-activity-as-compensation-handler). :::note The process instance invokes the compensation handler only if all instances of the multi-instance activity are completed. ::: ## Triggering compensation for a specific activity By default, a compensation throw event invokes all compensation handlers in its scope. However, it is also possible to trigger the compensation for a specific activity. This can be used to enforce that compensation handlers are invoked synchronously in a given order. ![Trigger compensation for a give activity](assets/compensation-activity-ref.png) On a compensation intermediate throw or end event, it is possible to specify the activity to compensate by using the property `activityRef`. The referenced activity must have a compensation boundary event and must be in the same scope of the compensation throw event. ## Triggering compensation from an event subprocess An interrupting or non-interrupting event subprocess can contain compensation intermediate throw events or a compensation end event. These compensation events can specify an activity or broadcast the compensation within the outer scope of the event subprocess. ![Trigger compensation from an event subprocess](assets/compensation-event-subprocess.png) A common pattern is to use this in combination with an error event subprocess to revert the effects of compensation activities if a failure occurs that can't be recovered from. ## Additional resources ### XML representation An intermediate compensation throw event with a referenced activity: ```xml Flow_0b2blc2 Flow_1goayj7 ``` --- ## Compensation Activities that are associated to a compensation boundary event have a compensation marker. These activities are called **compensation handlers** and are in charge of reverting the effects of the activity with the compensation boundary event, the compensation activity. ![Compensation marker example](assets/compensation-marker-example.png) When a process instance reaches a compensation throw event, it invokes the compensation handlers for all completed activities. If an activity has been completed more than once, the compensation handler is invoked for the same amount. Read more about triggering the compensation in the [compensation events documentation](../compensation-events/compensation-events.md). ## Embedded subprocess as compensation handler ![Process with subprocess as compensation handler](assets/subprocess-compensation-handler.png) The subprocess contains the steps to undo the actions of the compensation activity. Using a subprocess can be useful if a sequence of steps is required to undo the actions of the activity. ## Call activity as compensation handler ![Process with call activity as compensation handler](assets/call-activity-compensation-handler.png) The call activity contains the steps to undo the actions of the compensation activity. Using a call activity as the compensation handler can be useful since the compensation handlers of a child process are not invoked. ## Multi-instance activity as compensation handler ![Process with multi instance activity as compensation handler](assets/multi-instance-compensation-handler.png) The compensation handler for a [multi-instance activity](../multi-instance/multi-instance.md) is invoked only once, rather than for each item in the input collection. To invoke the compensation handler more than once, the handler can be marked as multi-instance too. If the compensation handler should revert the effects of each item in the input collection, it could use the same input collection as the multi-instance activity. ## Interrupting compensation handlers Compensation handlers can be interrupted. If the process instance is canceled, it terminates all compensation handlers. Within a process, the process instance terminates a compensation handler if the compensation throw event that invoked the compensation handler is interrupted. This can happen in the following cases: - If a terminate end event is entered. - If an interrupting event subprocess is triggered. - If the compensation throw event is inside an embedded subprocess and the subprocess is interrupted. ![A compensation handler is interrupted by an event subprocess](assets/interrupt-compensation-handler.png) ## Variable mappings A compensation handler can define input and output [variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings). Input variable mappings are applied before invoking the compensation handler. They can be used to create local variables for the compensation handler. Output variable mappings are applied after completing the compensation handler. They can be used to customize how the result variables of the compensation handler are merged into the process instance. By default, all variables are merged. ## Additional resources ### XML representation A service task with a compensation marker: ```xml ``` --- ## Conditional events Conditional events allow a process to react to changes in process state instead of waiting for a message or signal. For example, when a variable crosses a threshold, a required set of fields becomes complete, a risk score changes, or a business rule flips from false to true. Conditional events are useful when the producer of a change is not a single known sender, when correlation logic would add unnecessary complexity, or when the process logic is naturally expressed as guard conditions over process state. The engine evaluates the FEEL expression and triggers the event when the expression evaluates to `true`. The diagram below shows all four types of conditional events: root-level start, event sub-process start, intermediate catch, and boundary events. ![BPMN diagram showing conditional start, intermediate, and boundary events](assets/all-conditional-event-types.png) In this example, the process starts with a root-level conditional start event. Root-level conditional start events can be triggered via the Orchestration Cluster REST API or Camunda Client SDKs (see [trigger root-level conditional start events via API](../../../concepts/conditionals.md#trigger-root-level-conditional-start-events-via-api) for more details). A new instance is created once the condition `= orderReceived = true` evaluates to `true`. The intermediate conditional catch event acts like a wait-until condition. It continues to “Ship order” only after inventory is successfully reserved. The interrupting conditional boundary event attached to “Review order” handles changes mid-review. If the delivery address is changed, the boundary event triggers and interrupts the user task, routing execution to “Apply changes” before completing the order preparation. Finally, the interrupting event sub-process can cancel the work at any time while the instance is running. If the order is canceled while it is being prepared, the conditional start event inside the event sub-process fires and interrupts the main process, starting the cancellation sub-process to handle the cancellation logic. See [triggering conditional events](../../../concepts/conditionals.md#triggering-conditional-events) for details on how and when conditional events are triggered. ## Conditional start events Conditional start events can be used to start a process instance. Deploying a process with a conditional start event creates a subscription for that event. When the condition of the start event evaluates to `true`, the engine starts a new process instance. When deploying a process with a conditional start event, the following rules apply: - The condition of the conditional start event must be unique across a process definition. If multiple conditional start events have the same condition, the deployment will fail with a validation error. - Upon deployment of a new version, the previous version’s conditional start event subscription is removed and replaced with the new version’s subscription. To start processes via conditional start events from external systems, use the Orchestration Cluster REST API, the Zeebe gRPC API, or a Camunda Client SDK. See [trigger root-level conditional start events via API](../../../concepts/conditionals.md#trigger-root-level-conditional-start-events-via-api) for more details. ## Event sub-process conditional start events Event sub-process conditional start events start an event sub-process within an active process instance when the configured condition evaluates to `true` in the scope of that instance. This allows a running instance to react to state changes (for example, cancellation flags, escalation thresholds, or data corrections) without requiring an external signal or message correlation. An event sub-process conditional start event can be interrupting or non-interrupting: - Interrupting starts the event sub-process and cancels the currently active work in the scope it interrupts, so the instance continues via the event sub-process path. - Non-interrupting starts the event sub-process in parallel while the existing execution continues. A non-interrupting event sub-process conditional start event can trigger more than once while the instance is active. It triggers each time the condition becomes `true`, based on changes to variables referenced in the expression. You can use variable event filters to further restrict which change types (`create`, `update`) trigger evaluation. For runtime evaluation behavior and filter semantics, see [variable filter semantics](../../../concepts/conditionals.md#variable-filter-semantics). ## Intermediate conditional catch events An intermediate conditional catch event waits until its condition becomes `true`. When the process instance reaches the event, it waits until the condition evaluates to `true`, then continues along the outgoing sequence flow. For details on how and when the condition is triggered, see [triggering conditional events](../../../concepts/conditionals.md#triggering-conditional-events). Intermediate conditional catch events are always interrupting, as they represent a waiting point in the process flow. ## Conditional boundary events A conditional boundary event is attached to an activity and monitors data while the activity is active. When the activity is entered, the engine evaluates the boundary event’s condition and triggers immediately if the condition is satisfied. Conditional boundary events can be interrupting or non-interrupting: - Interrupting triggers the boundary event and cancels the attached activity, so execution continues via the boundary event’s outgoing sequence flow. - Non-interrupting triggers the boundary event without canceling the attached activity, starting an additional path via the boundary event’s outgoing sequence flow while the attached activity continues. Like a non-interrupting event sub-process conditional start event, a non-interrupting conditional boundary event can trigger multiple times while the attached activity is active. It triggers each time the condition becomes `true`, based on changes to variables referenced in the expression. You can use variable event filters to further restrict which change types trigger evaluation. See [variable filter semantics](../../../concepts/conditionals.md#variable-filter-semantics) for details. ## Define conditions and variable filters ### Condition expressions (FEEL) Conditional events use a FEEL expression in the `bpmn:condition` element. The expression must evaluate to a boolean value (`true` or `false`). For example: ```xml title="Conditional event definition with FEEL condition" = x > 1 ``` The engine evaluates the FEEL expression using variables available in the event’s scope and derives which variables can trigger the conditional event from the expression. For details, see [expression-based evaluation](../../../concepts/conditionals.md#expression-based-evaluation). ### Variable filters Variable filters restrict when a conditional event is re-evaluated in response to variable changes for those referenced variables. The `variableEvents` attribute applies only to conditional events inside running process instances. It does not apply to root-level conditional start events. Define an event-type filter by adding a `zeebe:conditionalFilter` extension element: ```xml title="Conditional event with Zeebe variable filter" = x > 1 ``` The `zeebe:conditionalFilter` extension element supports: - `variableEvents` specifies which variable events trigger evaluation. Supported values: - `create` - `update` - `create, update` For runtime behavior and limitations of variable filters, see [variable filter semantics](../../../concepts/conditionals.md#variable-filter-semantics). ## Modeling conditional events in Camunda Hub and Desktop Modeler Camunda Hub and Desktop Modeler support conditional start events, intermediate conditional catch events, and interrupting or non-interrupting conditional boundary events. To add a conditional event: 1. Select an existing start event, intermediate event, or boundary event, or use the **Create element** popup and search for _conditional_. 2. Change the element type to **Conditional start event**, **Conditional intermediate catch event**, or **Conditional boundary event** as needed. 3. With the conditional event selected, use the properties panel on the right to configure it: - In the **Condition** field, enter a FEEL expression starting with `=` (for example, `= x > 1`). - In the **Variable filters** section, optionally restrict when the condition is re-evaluated by specifying which variable events (`create`, `update`, or `create, update`) should trigger evaluation. ## XML representation ### Conditional start event ```xml = amount > 100 ``` ### Intermediate conditional catch event ```xml = processorAvailable ``` ### Conditional boundary event ```xml = customerCancelled ``` ## Additional resources - [Conditionals concept documentation](../../../concepts/conditionals.md) --- ## Data flow(Bpmn) Every BPMN process instance can have one or more variables. Variables are key-value-pairs and hold the contextual data of the process instance required by job workers to do their work, or to decide which sequence flows to take. They can be provided when a process instance is created, when a job is completed, and when a message is correlated. ![data-flow](assets/data-flow.png) ## Job workers By default, a job worker gets all variables of a process instance; it can limit the data by providing a list of required variables as **fetchVariables**. The worker uses the variables to do its work. When the work is done, it completes the job. If the result of the work is needed by follow-up tasks, the worker sets the variables while completing the job. These variables [merge](/components/concepts/variables.md#variable-propagation) into the process instance. ![job-worker](assets/data-flow-job-worker.png) If the job worker expects the variables in a different format or under different names, the variables can be transformed by defining **input mappings** in the process. **Output mappings** can be used to transform the job variables before merging them into the process instance. ## Variable scopes vs. token-based data A process can have concurrent paths; for example, when using a parallel gateway. When the execution reaches the parallel gateway, new tokens are created which execute the following paths concurrently. Since the variables are part of the process instance and not of the token, they can be read globally from any token. If a token adds a variable or modifies the value of a variable, the changes are also visible to concurrent tokens. ![variable-scopes](assets/variable-scopes.png) The visibility of variables is defined by the **variable scopes** of the process. ## Concurrency considerations When multiple active activities exist in a process instance (i.e. there is a form of concurrent execution like usage of a parallel gateway, multiple outgoing sequence flows, or a parallel multi-instance marker), you may need to take extra care in dealing with variables. When variables are altered by one activity, it might also be accessed and altered by another at the same time. Race conditions can occur in such processes. We recommend taking care when writing variables in a parallel flow. Make sure the variables are written to the correct [variable scope](/components/concepts/variables.md#variable-scopes) using variable mappings and make sure to complete jobs and publish messages only with the minimum required variables. These type of problems can be avoided by: - Passing only updated variables - Using output variable mappings to customize the variable propagation - Using an embedded subprocess and input variable mappings to limit the visibility and propagation of variables ## Additional resources - [Job handling](/components/concepts/job-workers.md) - [Variables](/components/concepts/variables.md) - [Input/output variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings) - [Variable scopes](/components/concepts/variables.md#variable-scopes) --- ## Embedded subprocess An embedded subprocess allows you to group elements of the process. ![embedded-subprocess](assets/embedded-subprocess.png) An embedded subprocess must have exactly **one** none start event. Other start events are not allowed. When an embedded subprocess is entered, the start event is activated. The subprocess stays active as long as one containing element is active. When the last element is completed, the subprocess is completed and the outgoing sequence flow is taken. Embedded subprocesses are often used together with **boundary events**. One or more boundary events can be attached to a subprocess. When an interrupting boundary event is triggered, the entire subprocess (including all active elements) is terminated. When adding an embedded subprocess to your model, you can either add a collapsed or expanded subprocess. You cannot collapse an existing expanded subprocess in your model. ## Collapsed subprocesses :::caution Collapsed subprocesses are currently only partially supported by Optimize. While diagrams containing collapsed subprocesses can be imported, it is not possible to drill down into the subprocesses. All other Camunda components fully support collapsed subprocesses. ::: A collapsed subprocess conceals its internal details, thereby hiding complexity within an activity and enabling the nesting of multiple levels of subprocesses. This functionality allows you to simplify the view of a process diagram and facilitates drill-down capabilities to examine details. Collapsed subprocesses serve purely display purposes. For the creation of reusable processes, it is recommended to utilize [call activities](../call-activities/call-activities.md). :::info When you add a **collapsed subprocess**, the modeling interface shows a link for drill-down. This link only opens the embedded subprocess within the same diagram. You can’t target or reuse a different process from that link. To reference another process you’ve already created, use a [call activity](../call-activities/call-activities.md) instead. ::: ![collapsed-subprocess](assets/collapsed-subprocess.png) ## Variable mappings Input mappings can be used to create new local variables in the scope of the subprocess. These variables are only visible within the subprocess. By default, the local variables of the subprocess are not propagated (i.e. they are removed with the scope.) This behavior can be customized by defining output mappings at the subprocess. The output mappings are applied on completing the subprocess. An embedded subprocess produces no result of its own, so its own local variables reach the parent scope only through an output mapping. This doesn't block variables set by tasks inside the subprocess: those still propagate upward through the subprocess scope to the parent scope on completion, per [variable propagation](/components/concepts/variables.md#variable-propagation), unless a same-named variable exists in the subprocess scope. For how an embedded subprocess compares to other elements, see [variable propagation by BPMN element](/components/concepts/variables.md#variable-propagation-by-bpmn-element). ## Additional resources ### XML representation An embedded subprocess with a start event: ```xml ... more contained elements ... ``` ### References - [Variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings) --- ## Error events In process automation, you often encounter deviations from the default scenario. One way to resolve these deviations is using a BPMN error event, which allows a process model to react to errors within a task. For example, if an invalid credit card is used in the process below, the process takes a different path than usual and uses the default payment method to collect money. ![process with error event](assets/error-events.png) ## Defining the error In BPMN, **errors** define possible errors that can occur. **Error events** are elements in the process referring to defined errors. An error can be referenced by one or more error events. An error must define an `errorCode`. The value of this `errorCode` is used to determine which catch event can catch the thrown error. For error throw events, it is possible to define the `errorCode` as [an `expression` or a static value](/components/concepts/expressions.md#expressions-vs-static-values). If an `errorCode` expression is configured then it will be evaluated once the event is reached, and used to throw error. For error catch events `errorCode` must be [a static value](/components/concepts/expressions.md#expressions-vs-static-values). Alternatively an error catch event may omit the error reference all together. In this case it catches **all** thrown errors. ## Throwing the error An error can be thrown within the process using an error **end event**. ![process with error throw event](assets/error-throw-events.png) Alternatively, you can inform Zeebe that a business error occurred using a **client command**. This throw error client command can only be used while processing a job. In addition to throwing the error, this also disables the job and stops it from being activated or completed by other job workers. Refer to the [gRPC command](/apis-tools/zeebe-api/gateway-service.md#throwerror-rpc) and [REST request](/apis-tools/orchestration-cluster-api-rest/specifications/throw-job-error.api.mdx) for details. ## Catching the error A thrown error can be caught by an error catch event, specifically using an error **boundary event** or an error **event subprocess**. ![process with error catch event](assets/error-catch-events.png) Starting at the scope where the error was thrown, the error code is matched against the attached error boundary events and error event subprocesses at that level. An error is caught by the first event in the scope hierarchy matching the error code. At each scope, the error is either caught, or propagated to the parent scope. If the process instance is created via call activity, the error can also be caught in the calling parent process instance. It is not possible to define multiple error catch events with the same `errorCode` in a single scope. It is also not permitted to have multiple error catch-all events in a single scope. However, it is possible to define both an error catch event referencing an error with a particular `errorCode` and an error catch-all event within the same scope. When this happens, the error catch event that matches the `errorCode` is prioritized. Error boundary events and error event subprocesses must be interrupting. This means the process instance will not continue along the regular path, but instead follow the path that leads out of the catching error event. If the error is thrown for a job, the associated task is terminated first. To continue the execution, the error boundary event or error event subprocess that caught the error is activated. ## Unhandled errors When an error is thrown and not caught, an [**incident**](/components/concepts/incidents.md) (for example, `Unhandled error event`) is raised to indicate the failure. The incident is attached to the corresponding element where the error was thrown (that is, the task of the processed job or the error end event). When you resolve the incident attached to a task, it ignores the error, re-enables the job, and allows it to be activated and completed by a job worker once again. The incident attached to an error end event cannot be resolved by a user because the failure is in the process itself. The process cannot be changed to catch the error for this process instance. ## Business error vs. technical error In real life, you’ll also have to deal with technical problems that you don't want to treat using error events. Suppose the credit card service becomes temporarily unavailable. You don't want to model the retrying, as you would have to add it to each and every service task. This will bloat the visual model and confuse business personnel. Instead, either retry or fall back to incidents as described above. This is hidden in the visual. In this context, we found the terms **business error** and **technical error** can be confusing, as they emphasize the source of the error too much. This can lead to long discussions about whether a certain problem is technical or not, and if you are allowed to observe technical errors in a business process model. It's much more important to look at how you _react_ to certain errors. Even a technical problem can qualify for a business reaction. For example, you could decide to continue a process in the event that a scoring service is not available, and simply give every customer a good rating instead of blocking progress. The error is clearly technical, but the reaction is a business decision. In general, we recommend talking about business reactions, which are modeled in your process, and technical reactions, which are handled generically using retries or incidents. ## Variable mappings Variables can be passed along into the error catch event with the payload when the error is thrown from the client command. These variables can be merged into the process instance by defining an output mapping at the error catch event. Visit the documentation regarding [variable mappings](../../../concepts/variables/#inputoutput-variable-mappings) for more information. ## Additional resources ### XML representation A boundary error event: ```xml ``` A error boundary catch-all event: ```xml ``` ### References - [Incidents](/components/concepts/incidents.md) --- ## Escalation events Escalation events are events which reference a named escalation, and are used to communicate to a higher flow scope. Unlike an error, an escalation event is non-critical and execution continues at the location of throwing. ![The process reached an escalation event. The escalation gets caught in a higher flow scope. As the escalation throw event is non-critical, the outgoing sequence flow of this event is taken.](assets/escalation-events.png) The example above shows the execution of an escalation event: 1. The process reaches the `Throw` event. 2. This throws an escalation to a higher flow scope. 3. The escalation is caught by the `Catch` event. 4. As escalation events are non-critical, the outgoing sequence flows of `Throw` and `Catch` are both taken. ## Defining an escalation In BPMN, an `escalation event` references an `escalation`. Escalations can be referenced by one or more escalation events. An escalation must define an `escalationCode`. The value of this `escalationCode` is used to determine which catch event can catch the thrown escalation. For escalation throw events, it is possible to define the `escalationCode` as [an `expression` or a static value](/components/concepts/expressions.md#expressions-vs-static-values). If an `escalationCode` expression is configured then it will be evaluated once the event is reached, and used to throw the escalation. For escalation catch events `escalationCode` must be [a static value](/components/concepts/expressions.md#expressions-vs-static-values). Alternatively an escalation catch event may omit the escalation reference all together. In this case it catches **all** thrown escalations. ## Throwing the escalation An escalation can be thrown by an escalation end event, or by an intermediate escalation throw event. Escalation events are non-critical. This means that if the throwing event has any outgoing sequence flows, they will be taken. ## Catching the escalation An escalation can be caught using a boundary event, or using an event subprocess. It is caught by one catch event at most, and this will be the catch event in the nearest parent flow scope. It is not possible to define multiple escalation catch events with the same `escalationCode` in a single scope. It is also not permitted to have multiple escalation catch-all events in a single scope. However, it is possible to define both an escalation catch event referencing an escalation with a particular `escalationCode` and an escalation catch-all event within the same scope. When this happens, the escalation catch event that matches the `escalationCode` is prioritized. If there are no escalation catch events that match the `escalationCode`, the escalation will not be caught. Unlike with [error events](../error-events/error-events.md), no incident is raised. The process will continue without escalating. Even though escalations are non-critical, it is still possible make escalation catch events interrupting. This will behave the same as other interrupting events. The catch event will terminate the scope it is attached to. In this case, the outgoing sequence flows of the throwing escalation event are not taken. ## Additional resources ### XML representation An intermediate escalation throw event with expression: ```xml ``` An escalation boundary catch event: ```xml ``` A escalation boundary catch-all event: ```xml ``` --- ## Event-based gateway An event-based gateway allows you to make a decision based on events. ![process](assets/event-based-gateway.png) An event-based gateway must have at least **two** outgoing sequence flows. Each sequence flow must be connected to an intermediate catch event of type [timer](../timer-events/timer-events.md), [message](../message-events/message-events.md) or [signal](../signal-events/signal-events.md). When an event-based gateway is entered, the process instance waits at the gateway until one of the events is triggered. When the first event is triggered, the outgoing sequence flow of this event is taken. No other events of the gateway can be triggered afterward. ## Additional resources ### XML representation An event-based gateway with two outgoing sequence flows: ```xml PT1H ``` ### References - [Timer events](../timer-events/timer-events.md) - [Message events](../message-events/message-events.md) --- ## Event subprocess An event subprocess is a subprocess triggered by an event. This can be added globally to the process, or locally inside an embedded subprocess. ![event-subprocess](assets/event-subprocess.png) An event subprocess must have exactly **one** start event of one of the following types: - [Timer](../timer-events/timer-events.md) - [Message](../message-events/message-events.md) - [Error](../error-events/error-events.md) - [Signal](../signal-events/signal-events.md) - [Escalation](../escalation-events/escalation-events.md) An event subprocess behaves like a boundary event, but is inside the scope instead of attached to the scope. Like a boundary event, the event subprocess can be interrupting or non-interrupting (indicated in BPMN by a solid or dashed border of the start event). The start event of the event subprocess can be triggered when its containing scope is activated. A non-interrupting event subprocess can be triggered multiple times. An interrupting event subprocess can be triggered only once. When an interrupting event subprocess is triggered, all active instances of its containing scope are terminated, including instances of other non-interrupting event subprocesses. If an event subprocess is triggered, its containing scope is not completed until the triggered instance is completed. ## Variables Unlike a boundary event, an event subprocess is inside the scope. Therefore, it can access and modify all local variables of its containing scope. This is not possible with a boundary event because a boundary event is outside of the scope. Input mappings can be used to create new local variables in the scope of the event subprocess. These variables are only visible within the event subprocess. If no input mappings are defined, the [default behavior](../../../concepts/variables.md#variable-scopes) is applied to the variables alongside the event. By default, the local variables of the event subprocess are not propagated (i.e. removed with the scope). This behavior can be customized by defining output mappings at the event subprocess. The output mappings are applied on completion of the event subprocess. ## Additional resources ### XML representation An event subprocess with an interrupting timer start event: ```xml PT5M ... other elements ``` ### References - [Embedded subprocess](../embedded-subprocesses/embedded-subprocesses.md) - [Variable scopes](/components/concepts/variables.md#variable-scopes) --- ## Overview(Bpmn) **Events** in BPMN represent things that _happen_. A process can react to events (_catching_ event) as well as emit events (_throwing_ event). For example, a catching message event makes the token continue as soon as a message is received. The XML representation of the process contains the criteria for which kind of message triggers continuation. Events can be added to the process in various ways. Not only can they be used to make a token wait at a certain point, but also for interrupting a token's progress. Currently supported events: - [None events](none-events/none-events.md) - [Message events](message-events/message-events.md) - [Timer events](timer-events/timer-events.md) - [Error events](error-events/error-events.md) - [Escalation events](escalation-events/escalation-events.md) - [Terminate events](terminate-events/terminate-events.md) - [Link events](link-events/link-events.md) - [Signal events](signal-events/signal-events.md) - [Compensation events](compensation-events/compensation-events.md) :::note Not all the events are supported yet. For a complete overview of supported events, refer to the [BPMN coverage](../bpmn-coverage#events). ::: ## Events in general Events in BPMN can be **thrown** (i.e. sent) or **caught** (i.e. received), respectively referred to as **throw** or **catch** events (e.g. `message throw event`, `timer catch event`). The distinction is about which side of the event the process is on: a throw event is something the process itself actively does as part of its own execution (for example, sending a message or raising an escalation); a catch event is something the process passively waits for, coming from outside the process (for example, a message arriving, or a timer elapsing). Additionally, a distinction is made between start, intermediate, and end events: - **Start events** (always catch events) react to something external to begin the process or subprocess — a process can't start itself. - **End events** (always throw events) are the process's own final action, such as sending a message or signaling that an error occurred, and denote the end of a particular sequence flow. - **Intermediate events** can go either way: an intermediate throw event performs an action as part of the process's execution, while an intermediate catch event pauses the process and waits to react to something external. Intermediate catch events can be inserted into your process in two different contexts: normal flow, or attached to an activity, and are called boundary events. ## Intermediate events In a typical flow, an intermediate throw event executes its event (e.g. send a message) once the token has reached it. Once complete, the token continues to all outgoing sequence flows (1). An intermediate catch event, however, stops the token and waits until the event it is waiting for occurs, at which point execution resumes and the token moves on (2). ## Boundary events Boundary events provide a way to model what should happen if an event occurs while an activity is active. For example, if a process is waiting on a user task to happen which is taking too long, an intermediate timer catch event can be attached to the task, with an outgoing sequence flow to notification task, allowing the modeler to automate and sending a reminder email to the user. A boundary event must be an intermediate catch event, and can be either interrupting (1)or non-interrupting (2). Interrupting means that once triggered, before taking any outgoing sequence flow the activity the event is attached to is terminated. This allows modeling timeouts where we can prune certain execution paths if something happens (e.g. the process takes too long). --- ## Exclusive gateway An exclusive gateway (or XOR-gateway) selects one outgoing sequence flow based on data such as process variables. ![process](assets/exclusive-gateway.png) For an exclusive gateway with multiple outgoing sequence flows: - All but one sequence flow must have a `conditionExpression`. - The remaining sequence flow can omit the `conditionExpression`, but the gateway must define it as the default flow. - Leaving the `conditionExpression` empty does not automatically make a sequence flow the default flow. In Camunda Hub or Desktop Modeler, set the exclusive gateway's default flow in the gateway properties by selecting the outgoing sequence flow to use as the default. - When a process instance reaches the gateway, the system evaluates the `conditionExpression` values in BPMN XML order and takes the first sequence flow whose condition is fulfilled. - If no condition is fulfilled, the process instance takes the **default flow**. The default flow should not have a condition, so the system does not evaluate it. - If no condition is fulfilled and the gateway has no default flow, an [incident](/components/concepts/incidents.md) is created. For example, if one sequence flow uses the condition `= totalPrice > 100`, you can set another outgoing sequence flow as the gateway's default flow to handle all remaining cases where `totalPrice <= 100`. An exclusive gateway can also join multiple incoming flows to improve BPMN readability. A joining gateway has pass-through semantics and does not merge incoming concurrent flows like a parallel gateway. ## Conditions A `conditionExpression` defines when a flow is taken. It is a [boolean expression](/components/modeler/feel/language-guide/feel-boolean-expressions.md) that can access the process variables and compare them with literals or other variables. The condition is fulfilled when the expression returns `true`. Multiple boolean values or comparisons can be combined as disjunction (`or`) or conjunction (`and`). For example: ```feel = totalPrice > 100 = order.customer = "Paul" = orderCount > 15 or totalPrice > 50 = valid and orderCount > 0 ``` ## Additional resources ### XML representation An exclusive gateway with two outgoing sequence flows: ```xml = totalPrice > 100 ``` ### References - [Expressions](/components/concepts/expressions.md) - [Incidents](/components/concepts/incidents.md) --- ## Overview(3) Gateways are elements that route tokens in more complex patterns than plain sequence flow. BPMN's **exclusive gateway** chooses one sequence flow out of many based on data, whereas BPMN's **parallel gateway** generates new tokens by activating multiple sequence flows in parallel, for example. Currently supported elements: - [Exclusive gateways](exclusive-gateways/exclusive-gateways.md) - [Parallel gateways](parallel-gateways/parallel-gateways.md) - [Event-based gateways](event-based-gateways/event-based-gateways.md) - [Inclusive gateways](inclusive-gateways/inclusive-gateways.md) --- ## Inclusive gateway The inclusive gateway (or OR-gateway) allows for making multiple decisions based on data or process variables. Inclusive gateways can be diverging (a sequence flow is split into multiple paths) or converging (split paths are merged before continuing). ![A process model to prepare lunch at lunchtime can use an inclusive gateway to decide which steps to take to prepare the different lunch components, e.g. cook pasta,stir-fry steak, prepare salad, or any combination of these.](assets/inclusive-gateway.png) If an inclusive gateway has multiple outgoing sequence flows, all sequence flows must have a condition to define when the flow is taken. If the inclusive gateway only has one outgoing sequence flow, then it does not need to have a condition. Optionally, one of the sequence flows can be marked as the default flow. This sequence flow should not have a condition, because its behavior depends on the other conditions. When an inclusive gateway is entered, the conditions are evaluated. The process instance takes all sequence flows where the condition is fulfilled. For example: Courses selected include `pasta` and `salad`. ![An inclusive gateway has decided to take the steps to cook pasta and prepare salad, but not stir-fry steak.](assets/inclusive-gateway-1.png) For example: Courses selected include `steak`, `pasta` and `salad`. ![An inclusive gateway has decided to take the steps to cook pasta, stir-fry steak, and prepare salad.](assets/inclusive-gateway-2.png) If no condition is fulfilled, it takes the **default flow** of the gateway. Note that the default flow is not expected to have a condition, and is therefore not evaluated. If no condition is fulfilled and the gateway has no default flow, an [incident](/components/concepts/incidents.md) is created. For example: No courses selected then the default flow is taken. ![An inclusive gateway has decided to take the step to prepare salad as the default because none of the conditions were fulfilled.](assets/inclusive-gateway-default.png) A converging inclusive gateway (also known as a merging or joining inclusive gateway) merges incoming paths before the sequence flow continues. A converging gateway is completed and merges incoming sequence flows if one of the following conditions is met: - All incoming sequence flows have been taken at least once. - No path exists from any active flow node to the inclusive gateway (excluding incoming paths to the inclusive gateway that have already been taken). For example: Once all selected courses are complete, the table can be cleared. ![An inclusive converging gateway waits until all incoming, executed sequence flows are completed before cleaning the table.](assets/inclusive-gateway-join.png) ## Conditions A `conditionExpression` defines when a flow is taken. It is a [boolean expression](/components/modeler/feel/language-guide/feel-boolean-expressions.md) that can access the process variables and compare them with literals or other variables. The condition is fulfilled when the expression returns `true`. Multiple boolean values or comparisons can be combined as disjunction (`and`) or conjunction (`or`). For example: ```feel = totalPrice > 100 = order.customer = "Paul" = orderCount > 15 or totalPrice > 50 = valid and orderCount > 0 = list contains(courses, "salad") ``` ## Additional resources ### XML representation An inclusive gateway with three outgoing sequence flows and the default sequence flow is `Salad`: ```xml Flow_0mfam08 Flow_0d3xogt Flow_1le3l31 Flow_05d0jjq = list contains(courses, "pasta") = list contains(courses, "steak") ``` ### References - [Conditions](/components/modeler/bpmn/inclusive-gateways/inclusive-gateways.md#conditions) - [Incidents](/components/concepts/incidents.md) --- ## Link events Link events are intermediate events that connect two sections of a process. They have no significance related to content, but facilitate the diagram-creation process. :::tip You can use link events to create loops, to skip sections of a process, or to simplify the sequence flow lines in the diagram. ::: Link events have a throwing link event as the "exit point", and a catching link event as the "re-entrance point". They are linked together by their link name. Multiple throwing link events can link to the same catching link event. A throwing link event cannot link to multiple catching link events. In practice, two paired link events function the same as two [intermediate none events] connected via a sequence flow. ![A pair of link events is equivalent to a pair of intermediate none events connected via a sequence flow](./assets/link-events-example.png) Link events can be very useful if you draw comprehensive process diagrams with many sequence flows. Links help avoid what otherwise might look like a “spaghetti” diagram. In the example below, a retry loop is created using the link events pair `A`. ![A pair of link events is used to form a retry loop](./assets/link-events-example-in-practice.png) :::info Link events are limited to a single scope Link events can only be used to link sections of a process within the same scope. I.e., they can only exist together on the root process level or within the same subprocess. Similarly, a sequence flow cannot be drawn between flow nodes at different scopes. For example, a task in the root process level cannot connect to another task in a subprocess using a sequence flow. Link events have the same limitation. ::: ## Additional resources ### XML representation A manual task: ```xml ``` ### References - [Intermediate none events] [intermediate none events]: ../none-events/none-events.md#intermediate-none-events-throwing --- ## Manual tasks A manual task defines a task that requires human interaction but no external tooling or UI interface. For example, a user reviewing a document or completing a physical task. ![task](assets/manual-task.png) Manual tasks are part of [human task orchestration](/guides/getting-started-orchestrate-human-tasks.md), but differ from [user tasks](/components/modeler/bpmn/user-tasks/user-tasks.md) which define an actionable task assisted by a workflow engine or software application. Within the engine and BPMN model, a manual task is handled as a pass-through activity, automatically continuing the process at the moment the process instance arrives. Manual tasks provide insights into the tasks performed outside the process engine, aiding in modeling a process, though no linked automation process is utilized. ### XML representation A manual task: ```xml ``` --- ## Overview(4) You might want to execute some tasks for every element of a list, like the `for each` construct in programming languages. Refer to [Workflow Pattern 14: Multiple Instances with a priori Run-Time Knowledge](http://www.workflowpatterns.com/patterns/control/new/wcp14.php): "Multiple instances of a task can be created. The required number of instances may depend on a number of runtime factors, but is known before the task instances must be created. Once initiated, these instances are independent of each other and run concurrently. It is necessary to synchronize the instances at completion before any subsequent tasks can be triggered." In BPMN, this is implemented using [multiple instance activities](/components/modeler/bpmn/multi-instance/multi-instance.md): Parallel multiple instance markers define that a subprocess is executed multiple times - once for each element of a given collection (like a `for each` loop in a programming language). Currently supported markers: - [Multi-instance](multi-instance/multi-instance.md) - [Compensation](compensation-handler/compensation-handler.md) - [Ad-hoc](ad-hoc-subprocesses/ad-hoc-subprocesses.md) ## Add a marker to an element Add markers from the element's Context pad rather than drawing them directly. Select the element, then click the **Change type** icon (the wrench). Select a different element behavior, such as converting a sub-process to an ad-hoc sub-process, to apply the corresponding marker automatically. For a walkthrough of the context pad, see [model your first diagram](/components/modeler/desktop-modeler/model-your-first-diagram.md). --- ## Message events Message events are events which reference a message; they are used to wait until a proper message is received. ![process](assets/message-events.png) Message events are used if a single process instance needs to wait for a message from a secondary process or an external system. This is a single sender to a single recipient relationship (1:1), as the message cannot have more than one recipient. These differ from [signal events](/components/modeler/bpmn/signal-events/signal-events.md), which are used if you want to communicate with multiple listeners. For intermediate events, a signal will trigger all process instances with a token waiting at a corresponding catch event, even across different processes. For start events, a signal will start one instance per process that has a corresponding signal start. Unlike message events, signal events form a single sender to several-recipient relationship (1:N). Both event types are methods of collaboration within BPMN. ## Message start events A process can have one or more message start events (besides other types of start events). Each of the message events must have a unique message name. When a process is deployed, it creates a message subscription for each message start event. Message subscriptions of the previous version of the process (based on the BPMN process ID) are closed. ### Message correlation When the message subscription is created, a message can be correlated to the start event if the message name matches. On correlating the message, a new process instance is created and the corresponding message start event is activated. Messages are **not** correlated if they were published before the process was deployed or if a new version of the process is deployed without a proper start event. The `correlationKey` of a published message can be used to control the process instance creation. - If an instance of this process is active (independently from its version) and it was triggered by a message with the same `correlationKey`, the message is **not** correlated and no new instance is created. If the message has a time-to-live (TTL) > 0, it is buffered. - When the active process instance is completed or terminated and a message with the same `correlationKey` and a matching message name is buffered (that is, TTL > 0), this message is correlated and a new instance of the latest version of the process is created. If the `correlationKey` of a message is empty, it creates a new process instance and does not check if an instance is already active. :::note You do not specify a `correlationKey` for a message start event in the BPMN model when designing a process. - When an application sends a message that is caught by a message start event, the application can specify a `correlationKey` in the message. - If a message caught by a start event contains a `correlationKey` value, the `correlationKey` is used to ensure only one process instance is active per key (idempotency). The `correlationKey` is not stored as a tag on the process instance, and the `tags` field remains empty. - Follow-up messages are then checked against this `correlationKey` value (that is, is there an active process instance that was started by a message with the same `correlationKey`?). ::: ## Intermediate message catch events When an intermediate message catch event is entered, a corresponding message subscription is created. The process instance stops at this point and waits until the message is correlated. When a message is correlated, the catch event is completed and the process instance continues. Inside an [ad-hoc sub-process](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md), an intermediate message catch event can also serve as an [AI agent](/reference/glossary.md#ai-agent) tool, for example to model a step where the agent sends a message and waits for a reply. See [message catch events as tools](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-tool-definitions.md#message-catch-events-as-tools) for the unique-correlation-key requirement that pattern needs. :::note An alternative to intermediate message catch events is a [receive task](../receive-tasks/receive-tasks.md), which behaves the same but can be used together with boundary events. ::: ## Message boundary events An activity can have one or more message boundary events. Each of the message events must have a unique message name. When the activity is entered, it creates a corresponding message subscription for each boundary message event. If a non-interrupting boundary event is triggered, the activity is not terminated and multiple messages can be correlated. The `correlationKey` expression of a message boundary event is evaluated in the activity's own (element instance) scope, consistent with the timer duration, message name, and signal name expressions of boundary events. This means the expression can access variables introduced by input mappings on the activity the boundary event is attached to. If the expression fails to evaluate, the resulting incident is raised on the activity itself, not on its flow scope. This behavior is controlled by the `camunda.processing.evaluate-boundary-event-correlation-key-in-activity-scope` property, which requires a broker restart to take effect. It defaults to `true` in Camunda 8.10 and later. It's also available since Camunda 8.9, where it defaults to `false`. :::note Set `camunda.processing.evaluate-boundary-event-correlation-key-in-activity-scope` to `false` to restore the previous behavior, where the expression is evaluated in the flow scope instead. The legacy `zeebe.broker.experimental.features.evaluateBoundaryEventCorrelationKeyInActivityScope` property (or its environment variable equivalents `CAMUNDA_PROCESSING_EVALUATEBOUNDARYEVENTCORRELATIONKEYINACTIVITYSCOPE` and `ZEEBE_BROKER_EXPERIMENTAL_FEATURES_EVALUATEBOUNDARYEVENTCORRELATIONKEYINACTIVITYSCOPE`) has the same effect. ::: ## Message throw events A process can contain intermediate message throw events or message end events to model the publication of a message to an external system; for example, to a Kafka topic. Currently, intermediate message throw events and message end events behave exactly like [service tasks](../service-tasks/service-tasks.md) or [send tasks](../send-tasks/send-tasks.md) , and have the same job-related properties (e.g. job type, custom headers, etc.) The message throw events and the tasks are based on jobs and [job workers](../../../../components/concepts/job-workers.md). The differences between the message throw events and the tasks are the visual representation and the semantics for the model. Read more about the [job properties](../../../../components/concepts/job-workers.md). When a process instance enters a message throw event, it creates a corresponding job and waits for its completion. A job worker should request jobs of this job type and process them. When the job is complete, the process instance continues or completes if it is a message end event. :::note Message throw events are not processed by Zeebe itself (i.e. to correlate a message to a message catch event). Instead, it creates jobs with the defined job type. To process them, provide a job worker. ::: ## Messages A message can be referenced by one or more message events. It must define the name of the message (e.g. `Money collected`) and the `correlationKey` expression (e.g. `= orderId`). If the message is only referenced by message start events, the `correlationKey` is not required. Usually, the name of the message is defined as a [static value](/components/concepts/expressions.md#expressions-vs-static-values) (e.g. `order canceled`), but it can also be defined as [expression](/components/concepts/expressions.md) (e.g. `= "order " + awaitingAction`). If the expression belongs to a message start event of the process, it is evaluated on deploying the process. Otherwise, it is evaluated on activating the message event. The evaluation must result in a `string`. The `correlationKey` is an expression that usually [accesses a variable](/components/modeler/feel/language-guide/feel-variables.md#access-variable) of the process instance that holds the correlation key of the message. The expression is evaluated on activating the message event and must result either in a `string` or in a `number`. To correlate a message to the message event, the message is published with the defined name (e.g. `Money collected`) and the **value** of the `correlationKey` expression. For example, if the process instance has a variable `orderId` with value `"order-123"`, the message must be published with the correlation key `"order-123"`. ## Variable mappings By default, all message variables are merged into the process instance. This behavior can be customized by defining an output mapping at the message catch event. Visit the documentation regarding [variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings) for more information on this topic. ## Additional resources ### XML representation A message start event with message definition: ```xml ``` An intermediate message catch event with message definition: ```xml ``` A boundary message event: ```xml ``` ### References - [Message correlation](/components/concepts/messages.md) - [Expressions](/components/concepts/expressions.md) - [Variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings) - [Incidents](/components/concepts/incidents.md) - [Job handling](/components/concepts/job-workers.md) --- ## Multi-instance A multi-instance activity is executed multiple times - once for each element of a given collection (like a _foreach_ loop in a programming language). We support the multi-instance marker for all [supported activities](/components/modeler/bpmn/bpmn-coverage.md), such as service tasks, receive tasks, embedded subprocceses, and call activities. For example: ![multi-instance](assets/multi-instance-example.png) On the execution level, a multi-instance activity has two parts: a multi-instance body, and an inner activity. The multi-instance body is the container for all instances of the inner activity. When the activity is entered, the multi-instance body is activated and one instance for every element of the `inputCollection` is created (sequentially or in parallel). When all instances are completed, the body is completed and the activity is left. :::note Events with a `JobWorker` implementation, such as intermediate throw events, do not support this marker. ::: ## Sequential vs. parallel A multi-instance activity is executed either sequentially or in parallel (default). In the BPMN, a sequential multi-instance activity is displayed with three horizontal lines at the bottom. A parallel multi-instance activity is represented by three vertical lines. In case of a **sequential** multi-instance activity, the instances are executed one at a time. When one instance is completed, a new instance is created for the next element in the `inputCollection`. ![sequential multi-instance](assets/multi-instance-sequential.png) In case of a **parallel** multi-instance activity, all instances are created when the multi-instance body is activated. The instances are executed concurrently and independently from each other. ![parallel multi-instance](assets/multi-instance-parallel.png) ## Defining the collection to iterate over A multi-instance activity must have an `inputCollection` expression that defines the collection to iterate over (e.g. `= items`). Usually, it [accesses a variable](/components/modeler/feel/language-guide/feel-variables.md#access-variable) of the process instance that holds the collection. The expression is evaluated on activating the multi-instance body. It must result in an `array` of any type (e.g. `["item-1", "item-2"]`). :::tip If you need to iterate `n` times (like with a loop-cardinality), you can use the following expression with a [for-loop](/components/modeler/feel/language-guide/feel-control-flow.md#for-loops): `for i in 1..n return i`. ::: To access the current element of the `inputCollection` value within the instance, the multi-instance activity can define the `inputElement` variable (e.g. `item`). The element is stored as a local variable of the instance under the given name. If the `inputCollection` value is **empty**, the multi-instance body is completed immediately and no instances are created. It behaves like the activity is skipped. ## Collecting the output The output of a multi-instance activity (e.g. the result of a calculation) can be collected from the instances by defining the `outputCollection` and the `outputElement` expression. `outputCollection` defines the name of the variable under which the collected output is stored (e.g. `results`). It is created as a local variable of the multi-instance body and is updated when an instance is completed. When the multi-instance body is completed, the variable is propagated to its parent scope. `outputElement` is an expression that defines the output of the instance (e.g. `= result`). Usually, it [accesses a variable](/components/modeler/feel/language-guide/feel-variables.md#access-variable) of the instance that holds the output value. If the expression only accesses a variable or a nested property, it's created as a **local variable** of the instance. This variable should be updated with the output value; for example, by a job worker providing a variable with the name `result`. Since the variable is defined as a local variable, it is not propagated to its parent scope and is only visible within the instance. When the instance is completed, the `outputElement` expression is evaluated and the result is inserted into the `outputCollection` at the same index as the `inputElement` of the `inputCollection`. Therefore, the order of the `outputCollection` is determined and matches to the `inputCollection`, even for parallel multi-instance activities. If the `outputElement` variable is not updated, `null` is inserted instead. If the `inputCollection` value is empty, an empty array is propagated as `outputCollection`. ## Boundary events ![multi-instance with boundary event](assets/multi-instance-boundary-event.png) Interrupting and non-interrupting boundary events can be attached to a multi-instance activity. When an interrupting boundary event is triggered, the multi-instance body and all active instances are terminated. The `outputCollection` variable is not propagated to the parent scope (i.e. no partial output). When a non-interrupting boundary event is triggered, the instances are not affected. The activities at the outgoing path have no access to the local variables since they are bound to the multi-instance activity. ## Special multi-instance variables Every instance has a local variable `loopCounter`. It holds the index in the `inputCollection` of this instance, starting with `1`. ## Execution listeners in multi-instance bodies Execution listeners on a multi-instance body support a `beforeAll` event type that fires **once** per body activation — before the `inputCollection` expression is evaluated and before any instances are created. This is distinct from `start` listeners, which fire once per instance. For full details on execution listener types and configuration, see [execution listeners](/components/concepts/execution-listeners.md). ## Variable mappings Input and output variable mappings can be defined at the multi-instance activity; they are applied on each instance on activating and on completing. The input mappings can be used to create new local variables in the scope of an instance. These variables are only visible within the instance; it is a way to restrict the visibility of variables. By default, new variables (e.g. provided by a job worker) are created in the scope of the process instance and are visible to all instances of the multi-instance activity as well as outside of it. In case of a parallel multi-instance activity, this can lead to variables that are modified by multiple instances and result in race conditions. If a variable is defined as a local variable, it is not propagated to a parent or the process instance scope and can't be modified outside the instance. The input mappings can access the local variables of the instance (e.g. `inputElement`, `loopCounter`); for example, to extract parts of the `inputElement` variable and apply them to separate variables. The output mappings can be used to update the `outputElement` variable; for example, to extract a part of the job variables. The `loopCounter` variable and the input element variable defined by `inputElement` are managed by the engine and stay local to a single instance. Do not reference them in output mappings, because propagating them to a higher scope produces a value that reflects only one iteration. See [internal engine variables](/components/concepts/variables.md#internal-engine-variables). When the loop finishes, the output collection is propagated to the parent scope. For how a multi-instance activity compares to other elements, see [variable propagation by BPMN element](/components/concepts/variables.md#variable-propagation-by-bpmn-element). **Example:** We have a call activity marked as a parallel multi-instance. When the called process instance completes, its variables are [merged](/components/concepts/variables.md#variable-propagation) into the call activity's process instance. Its result is collected in the output collection variable, but this has become a race condition where each completed child instance again overwrites this same variable. We end up with a corrupted output collection. An output mapping can be used to overcome this, because it restricts which variables are merged. In the case of: - Parallel multi-instance call activity - Multi-instance output element: `=output` - Variable in the child instance that holds the result: `x` The output mapping on the call activity should be: ``` source: =x target: output ``` ## Completion condition A `completionCondition` defines whether the multi-instance body can be completed immediately when the condition is satisfied. It is a [boolean expression](/components/modeler/feel/language-guide/feel-boolean-expressions.md) that will be evaluated each time the instance of the multi-instance body completes. Any instances that are still active are terminated and the multi-instance body is completed when the expression evaluates to `true`. The BPMN 2.0 specification defines the following properties of a multi-instance body: - `numberOfInstances`: The number of instances created. - `numberOfActiveInstances`: The number of instances currently active. - `numberOfCompletedInstances`: The number of instances already completed. - `numberOfTerminatedInstances`: The number of instances already terminated. These properties are available for use in the `completionCondition` expression. For example, using these properties you can express "complete the multi-instance body when 50% or more of the instances already completed" as `= numberOfCompletedInstances / numberOfInstances >= 0.5`. Although these properties are available in this expression, they do not exist as process variables. These properties take precedence over process variables with the same name. Multiple boolean values or comparisons can be combined as disjunction (`and`) or conjunction (`or`). For example: ```feel = result.isSuccessful = count(["a", "b", "c", "d"]) > 3 = orderCount >= 5 and orderCount < 15 = list contains([6,7], today().weekday) = numberOfCompletedInstances = 2 = numberOfCompletedInstances / numberOfInstances >= 0.5 ``` ## Additional resources ### XML representation A sequential multi-instance service task: ```xml = result.isSuccessful ``` ### References - [Variable scopes](/components/concepts/variables.md#variable-scopes) - [Expressions](/components/concepts/expressions.md) - [Variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings) --- ## None events None events are unspecified events, also called "blank" events. ![process](assets/none-events.png) ## None start events At most, a process can have **one** none start event (besides other types of start events). A none start event is where the process instance or a subprocess starts when the process or the subprocess is activated. A none start event is required if you want to [trigger a process via a form](../../../hub/workspace/modeler/run-or-publish-your-process.md#publish-via-a-public-form). ## None end events A process or subprocess can have multiple none end events. When a none end event is entered, the current execution path ends. If the process instance or subprocess has no more active execution paths, it is completed. If an activity has no outgoing sequence flow, it behaves the same as it would be connected to a none end event. When the activity is completed, the current execution path ends. ## Intermediate none events (throwing) Intermediate none events can be used to indicate some state achieved in the process. They are especially useful for monitoring to understand how the process is doing, for example, as milestones or key performance indicators (KPIs). The engine itself doesn't do anything in the event, it just passes through it. ## Variable mappings All none events can have [variable output mappings](../../../../components/concepts/variables.md#output-mappings). For start events, this is often used to initialize process variables. ## Additional resources ### XML representation A none start event: ```xml ``` A none end event: ```xml ``` An intermediate none event: ```xml ``` --- ## Parallel gateway A parallel gateway (or AND-gateway) allows you to split the flow into concurrent paths. ![process](assets/parallel-gateways.png) When a parallel gateway with multiple outgoing sequence flows is entered, all flows are taken. The paths are executed concurrently and independently. The concurrent paths can be joined using a parallel gateway with multiple incoming sequence flows. The process instance waits at the parallel gateway until each incoming sequence is taken. :::note The outgoing paths of the parallel gateway are executed concurrently and not parallel in the sense of parallel threads. All records of a process instance are written to the same partition (single stream processor). ::: ## Additional resources ### XML representation A parallel gateway with two outgoing sequence flows: ```xml ``` --- ## Receive tasks Receive tasks reference a message; these are used to wait until a proper message is received. ![Receive Tasks](assets/receive-tasks.png) When a receive task is entered, a corresponding message subscription is created. The process instance stops at this point and waits until the message is correlated. A message can be published using one of the Zeebe clients. When the message is correlated, the receive task is completed and the process instance continues. :::note An alternative to receive tasks is [a message intermediate catch event](../message-events/message-events.md), which behaves the same way but can be used together with event-based gateways. ::: ## Messages A message can be referenced by one or more receive tasks; it must define the name of the message (e.g. `Money collected`) and the `correlationKey` expression (e.g. `= orderId`). Usually, the name of the message is defined as a [static value](/components/concepts/expressions.md#expressions-vs-static-values) (e.g. `order canceled`), but it can also be defined as [expression](/components/concepts/expressions.md) (e.g. `= "order " + awaitingAction`). The expression is evaluated on activating the receive task and must result in a `string`. The `correlationKey` is an expression that usually [accesses a variable](/components/modeler/feel/language-guide/feel-variables.md#access-variable) of the process instance that holds the correlation key of the message. The expression is evaluated on activating the receive task and must result either in a `string` or `number`. To correlate a message to the receive task, the message is published with the defined name (e.g. `Money collected`) and the value of the `correlationKey` expression. For example, if the process instance has a variable `orderId` with value `"order-123"`, the message is published with the correlation key `"order-123"`. ## Variable mappings Output variable mappings are used to customize how variables are merged into the process instance. These can contain multiple elements that specify which variables should be mapped. The `Process Variable Name` of an output denotes the variable name outside the activity. Visit our documentation on [input and output variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings) for more information on this topic. ## Additional resources ### XML representation A receive task with message definition: ```xml ``` ### References - [Message correlation](/components/concepts/messages.md) - [Expressions](/components/concepts/expressions.md) - [Variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings) - [Incidents](/components/concepts/incidents.md) --- ## Script tasks A script task is used to model the evaluation of a script; for example, a script written in Groovy, JavaScript, or Python. ![task](assets/script-task.png) :::info Camunda 8 supports alternative task implementations for the script task. To use your own implementation for a script task, refer to the [job worker implementation](#job-worker-implementation) section below. The sections before this job worker implementation apply to the [FEEL expression](/components/modeler/feel/language-guide/feel-expressions-introduction.md) implementation only. ::: When the process instance arrives at a script task, the integrated [FEEL Scala](https://github.com/camunda/feel-scala) engine evaluates the script task FEEL expression. Once the FEEL expression is evaluated successfully, the process instance continues. If the FEEL expression evaluation is unsuccessful, an [incident](/components/concepts/incidents.md) is raised at the script task. When the incident is resolved, the script task is evaluated again. ## Defining a script task {#defining-a-task} To define a script task with an inline FEEL expression, use the `zeebe:script` extension element. In the `zeebe:script` extension element, perform the following steps: 1. Define the **FEEL expression** inside the `expression` attribute. 2. Define the name of process variable in the `resultVariable` attribute. This variable will store the result of the FEEL expression evaluation. ### Variable mappings By default, the variable defined by `resultVariable` is merged into the process instance. This behavior can be customized by defining an output mapping at the script task. All variables in scope of the script task are available to the FEEL engine when the FEEL expression in the script task is evaluated. Input mappings can be used to transform the variables into a format accepted by the FEEL expression. :::info Input mappings are applied on activating the script task (or when an incident at the script task is resolved) before the FEEL expression evaluation. When an incident is resolved at the script task, the input mappings are applied again before evaluating the FEEL expression. This can affect the result of the FEEL expression evaluation. ::: For more information about this topic, visit the documentation about [input and output variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings). ## Job worker implementation When the job worker implementation is used, script tasks behave exactly like [service tasks](/components/modeler/bpmn/service-tasks/service-tasks.md). Both task types are based on jobs and [job workers](/components/concepts/job-workers.md). The differences between these task types are the visual representation (i.e. the task marker) and the semantics for the model. When a process instance enters a script task using a job worker implementation, it creates a corresponding job and waits for its completion. A job worker should request jobs of this job type and process them. When the job is complete, the process instance continues. :::note Jobs for script tasks are not processed by Zeebe itself. To process them, provide a job worker. ::: ### Defining a job worker script task A script task must define a [job type](/components/modeler/bpmn/service-tasks/service-tasks.md#task-definition) the same way a service task does. It specifies the type of job workers should subscribe to (e.g. `script`). Use [task headers](/components/modeler/bpmn/service-tasks/service-tasks.md#task-headers) to pass static parameters to the job worker (e.g. the script to evaluate). The community extension [Zeebe Script Worker](https://github.com/camunda-community-hub/zeebe-script-worker) requires certain attributes to be set in the task headers. Define [variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings) the [same way a service task does](/components/modeler/bpmn/service-tasks/service-tasks.md#variable-mappings) to transform the variables passed to the job worker, or to customize how the variables of the job merge. ## Job priority This task type supports `zeebe:jobPriorityDefinition` when implemented as a job worker. You can define job priority on the process as a default and override it on this task. For priority behavior and limitations, see [Job prioritization](../../../concepts/job-workers.md#job-prioritization). ## Additional resources :::tip Community Extension Review the [Zeebe Script Worker](https://github.com/camunda-community-hub/zeebe-script-worker). This is a community extension that provides a job worker to evaluate scripts. You can run it, or use it as a blueprint for your own job worker. ::: ### XML representation A script task with a custom header: ```xml ``` A script task with an inline FEEL expression: ```xml ``` ### References - [Job handling](/components/concepts/job-workers.md) - [Variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings) --- ## Send tasks A send task is used to model the publication of a message to an external system; for example, to a Kafka topic or a mail server. ![task](assets/send-task.png) Send tasks behave exactly like [service tasks](/components/modeler/bpmn/service-tasks/service-tasks.md). Both task types are based on jobs and [job workers](/components/concepts/job-workers.md). The differences between these task types are the visual representation (i.e. the task marker) and the semantics for the model. When a process instance enters a send task, it creates a corresponding job and waits for its completion. A job worker should request jobs of this job type and process them. When the job is complete, the process instance continues. :::note Jobs for send tasks are not processed by Zeebe itself. To process them, provide a job worker. ::: ## Defining a task A send task must define a [job type](/components/modeler/bpmn/service-tasks/service-tasks.md#task-definition) the same way as a service task does. It specifies the type of job that workers should subscribe to (e.g. `kafka` or `mail`). Use [task headers](/components/modeler/bpmn/service-tasks/service-tasks.md#task-headers) to pass static parameters to the job worker (e.g. the name of the topic to publish the message to). Define [variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings) the [same way as a service task does](/components/modeler/bpmn/service-tasks/service-tasks.md#variable-mappings) to transform the variables passed to the job worker, or to customize how the variables of the job merge. ## Job priority This task type supports `zeebe:jobPriorityDefinition`. You can define job priority on the process as a default and override it on this task. For priority behavior and limitations, see [Job prioritization](../../../concepts/job-workers.md#job-prioritization). ## Additional resources :::tip Community Extension Review the [Kafka Connect Zeebe](https://github.com/camunda-community-hub/kafka-connect-zeebe). This is a community extension that provides a job worker to publish messages to a Kafka topic. You can run it, or use it as a blueprint for your own job worker. ::: ### XML representation A script task with a custom header: ```xml ``` ### References - [Job handling](/components/concepts/job-workers.md) - [Variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings) --- ## Service tasks A service task represents a work item in the process with a specific type. ![process](../assets/order-process.png) When a service task is entered, a corresponding job is created. The process instance stops here and waits until the job is complete. A [job worker](/components/concepts/job-workers.md) can subscribe to the job type, process the jobs, and complete them using one of the Zeebe clients. When the job is complete, the service task is completed and the process instance continues. ## Task definition A service task must have a `taskDefinition`. The `taskDefinition` is used to specify which [job workers](../../../concepts/job-workers.md) handle the service task work. A `taskDefinition` specifies the following properties: - `type` (required): Used as reference to specify which job workers request the respective service task job. For example, `order-items`. - `type` can be specified as any [static value](/components/concepts/expressions.md#expressions-vs-static-values) (`myType`) or as a FEEL [expression](../../../concepts/expressions.md) prefixed by `=` that evaluates to any FEEL string; for example, `= "order-" + priorityGroup`. - `retries` (optional): Specifies the number of times the job is retried when a worker signals failure. The default is three. The expressions are evaluated on activating the service task and must result in a `string` for the job type and a `number` for the retries. Refer to an example in the form of the [XML representation](#xml-representation) below. ## Task headers A service task can define an arbitrary number of `taskHeaders`; they are static metadata handed to workers along with the job. The headers can be used as configuration parameters for the worker. ## Variable mappings By default, all job variables merge into the process instance. This behavior can be customized by defining an output mapping at the service task. Input mappings can be used to transform the variables into a format accepted by the job worker. For more information about this topic visit the documentation about [Input/output variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings). ## Job priority This task type supports `zeebe:jobPriorityDefinition`. You can define job priority on the process as a default and override it on this task. For priority behavior and limitations, see [Job prioritization](../../../concepts/job-workers.md#job-prioritization). ## Additional resources ### XML representation A service task with a custom header and priority definition: ```xml ``` ## Next steps Learn more about the concept of job types and how to set up a job worker via our [manual on job workers](/components/concepts/job-workers.md). ### References - [Job handling](/components/concepts/job-workers.md) - [Expressions](/components/concepts/expressions.md) - [Variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings) - [Incidents](/components/concepts/incidents.md) --- ## Signal events Signal events are events which reference a [signal](../../../concepts/signals.md). Broadcasting a signal will trigger _all_ signal events matching the name of the broadcasted signal. ![Process with multiple different signal events](assets/signal-events.png) Signal events are typically used if you want to communicate with multiple listeners. For intermediate events, a signal will trigger all process instances with a token waiting at a corresponding catch event, even across different processes. For start events, a signal will start one instance per process that has a corresponding signal start. Thus, signals form a single sender to several-recipient relationship. These differ from [message events](/components/modeler/bpmn/message-events/message-events.md), which are used if a single process instance needs to wait for a message from a secondary process or an external system. This is a single sender to a single recipient relationship (1:1), as the message cannot have more than one recipient. Both event types are methods of collaboration within BPMN. ## Signal start events Signal start events can be used to start process instances. Deploying several processes with a signal start event enables creation of multiple process instances by performing a single broadcast. Broadcasting a signal iterates over the available subscriptions. If the name of the broadcasted signal matches the name of the signal start event, the process instance is created. Signal subscriptions only exist for the latest version of a process definition. Deploying a new version of the same process (based on the BPMN process ID) will delete the old signal subscription. A new subscription is opened for the new deployed process definition. When the latest version of a process is deleted, the signal subscription is also deleted. If the previous version of the same process (based on the BPMN process ID) contains a signal start event, a new subscription is opened for it. ## Signal intermediate catch events When a signal intermediate catch event is entered, a signal subscription is created. The process instance stops at this point and waits until it is triggered by a broadcasted signal with the same name. Broadcasting a signal will iterate over the available subscriptions. If the name of the broadcasted signal matches the name of the signal subscription, it triggers the signal subscription. When the subscription is triggered, the corresponding signal catch event is completed and the process instance continues. ## Signal boundary events An activity can have one or more signal boundary events. Each of the signal events must have a unique signal name. When the activity is entered, it creates a signal subscription for each boundary signal event. If a non-interrupting boundary event is triggered, the activity is not terminated and multiple broadcasted signals can trigger the boundary events. ## Signal throw events A process can contain signal intermediate throw events or signal end events to model the broadcasting of a signal. When a signal throw events is entered, it broadcasts a signal that can trigger signal subscriptions. ## Signals In BPMN, a signal event references a `signal`. Signals can be referenced by one or more signal events. A signal must define a `name`. The value is used to determine: - The name of the signal to broadcast for a signal throw event. - The name of the signal to subscribe to for a signal catch event. Usually, the name of the signal is defined as a [static value](../../../concepts/expressions/#expressions-vs-static-values) (e.g. `order canceled`), but it can also be defined as an [expression](../../../concepts/expressions/) (e.g. `= "order " + awaitingAction`). If the expression belongs to a signal start event of the process, it is evaluated on deploying the process. Otherwise, it is evaluated on activating the signal event. The evaluation must result in a `string`. ## Variable mappings When broadcasting a signal you can pass along variables. By default, all signal variables are merged into the process instance. This behavior can be customized by defining an output mapping at the signal catch event. When a signal throw event broadcasts a signal, all local variables are passed along. You can use input mappings to define these local variables. For more information about variable scopes, visit the documentation about [variable scopes](../../../concepts/variables#variable-scopes). ## Additional resources ### XML representation A signal start event with signal definition: ```xml ``` A signal boundary event with signal definition: ```xml ``` A signal intermediate catch event with signal definition: ```xml ``` A signal end event with signal definition: ```xml ``` --- ## Overview(5) **Subprocesses** are element containers that allow defining common functionality. For example, you can attach an event to a subprocess's border. When the event is triggered, the subprocess is interrupted, regardless which of its elements is currently active. Currently supported elements: - [Embedded subprocess](embedded-subprocesses/embedded-subprocesses.md) - [Call activities](call-activities/call-activities.md) - [Event subprocess](event-subprocesses/event-subprocesses.md) - [Ad-hoc sub-process](ad-hoc-subprocesses/ad-hoc-subprocesses.md) --- ## Overview(6) The basic elements of BPMN processes are tasks; these are atomic units of work composed to create a meaningful result. Whenever a token reaches a task, the token stops and Zeebe creates a job and notifies a registered worker to perform work. When that handler signals completion, the token continues on the outgoing sequence flow. Choosing the granularity of a task is up to the person modeling the process. For example, the activity of processing an order can be modeled as a single _Process Order_ task, or as three individual tasks _Collect Money_, _Fetch Items_, _Ship Parcel_. If you use Zeebe to orchestrate microservices, one task can represent one microservice invocation. Currently supported elements: - [Service tasks](service-tasks/service-tasks.md) - [User tasks](user-tasks/user-tasks.md) - [Receive tasks](receive-tasks/receive-tasks.md) - [Business rule tasks](business-rule-tasks/business-rule-tasks.md) - [Script tasks](script-tasks/script-tasks.md) - [Send tasks](send-tasks/send-tasks.md) - [Manual tasks](manual-tasks/manual-tasks.md) - [Undefined tasks](undefined-tasks/undefined-tasks.mdx) --- ## Terminate events Terminate end events are the only kind of terminate events. When a process instance reaches a terminate end event, it terminates all element instances in the same flow scope as the end event. They are often used to terminate a concurrent flow that is not required anymore. Consider the following example. ![The process instance reached the terminate end event and canceled the concurrent flow.](assets/terminate-event-on-process-scope.png) The process has two concurrent tasks `B` and `C`. In the process instance, both tasks are active. We complete the task `C`. The process instance reaches the terminate end event and cancels the task `B`. ## On the process scope A terminate end event on the process scope (i.e. not embedded in a subprocess) terminates all element instances of the process instance. After the termination, the process instance completes. If the process instance was created by a call activity from a parent process then the call activity completes and the parent process instance takes the outgoing sequence flows. ## Inside a subprocess A terminate end event inside an embedded or an event subprocess terminates all element instances of the subprocess. After the termination, the subprocess completes, and the process instance takes the outgoing sequence flows. The terminate end event is limited to its subprocess. It doesn't terminate element instances outside the subprocess. ![The process instance reached the terminate end event in the subprocess and canceled the concurrent task in the subprocess. The process instance took the outgoing sequence flow of the subprocess.](assets/terminate-end-event-inside-subprocess.png) If the subprocess is a multi-instance then the terminate end event terminates only the element instances of the current iteration. It doesn't terminate element instances of other multi-instance iterations. ## Additional resources ### XML representation A terminate end event: ```xml Flow_0zv9prm ``` --- ## Timer events Timer events are events triggered by a defined timer. ![process](assets/timer-events.png) ## Timer start events A process can have one or more timer start events (besides other types of start events). Each of the timer events must have either a time date or time cycle definition. When a process is deployed, it schedules a timer for each timer start event. Scheduled timers of the previous version of the process (based on the BPMN process ID) are canceled. When a timer is triggered, a new process instance is created and the corresponding timer start event is activated. ## Intermediate timer catch events An intermediate timer catch event can either be a time duration, or a time date. When an intermediate timer catch event is entered, a corresponding timer is scheduled. The process instance stops at this point and waits until the timer is triggered. When the timer is triggered, the catch event is completed and the process instance continues. ## Timer boundary events An interrupting timer boundary event must have a time duration, or a time date definition. When the corresponding timer is triggered, the activity is terminated. Interrupting timer boundary events are often used to model timeouts; for example, canceling the processing after five minutes and doing something else. A non-interrupting timer boundary event must have either a time duration, a time cycle definition, or a time date definition. When the activity is entered, it schedules a corresponding timer. If the timer is triggered and defined as time cycle with repetitions greater than zero, it schedules the timer again until the defined number of repetitions is reached. It's important to note that a non-interrupting timer boundary event that's defined with a time duration will only trigger a single time once the date is reached. Non-interrupting timer boundary events are often used to model notifications; for example, contacting support if the processing takes longer than an hour. Attached to an [ad-hoc sub-process](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md) hosting an [AI agent](/reference/glossary.md#ai-agent), a timer boundary event lets the process interrupt or redirect a running agent that takes too long, one of the patterns described in [mixing agents with workflow patterns](/components/agentic-orchestration/design-architecture.md#mix-agents-with-workflow-patterns). ## Timers Timers must be defined by providing either a date, a duration, or a cycle. A timer can be defined either as a [static value](/components/concepts/expressions.md#expressions-vs-static-values) (e.g. `P3D`) or as an [expression](/components/concepts/expressions.md). There are two common ways to use an expression: - [Access a variable](/components/modeler/feel/language-guide/feel-variables.md#access-variable) (e.g. `= remainingTime`). - [Use temporal values](/components/concepts/expressions.md#temporal-expressions) (e.g. `= date and time(expirationDate) - date and time(creationDate)`). If the expression belongs to a timer start event of the process, it is evaluated on deploying the process. Otherwise, it is evaluated on activating the timer catch event. The evaluation must result in either a `string` that has the same ISO 8601 format as the static value, or an equivalent temporal value (i.e. a date-time, a duration, or a cycle). :::note Zeebe is an asynchronous system. As a result, there is no guarantee a timer triggers exactly at the configured time. Depending on how much load the system is under, timers could trigger later than their due date. However, timers will never trigger earlier than the due date. ::: ### Time date If the date is in the past at the time of deployment, the timer fires immediately. ### Time duration A duration is defined as a ISO 8601 durations format, which defines the amount of intervening time in a time interval and are represented by the format `P(n)Y(n)M(n)DT(n)H(n)M(n)S`. Note that the `n` is replaced by the value for each of the date and time elements that follow the `n`. The capital letters _P_, _Y_, _M_, _W_, _D_, _T_, _H_, _M_, and _S_ are designators for each of the date and time elements and are not replaced, but can be omitted. - _P_ is the duration designator (for period) placed at the start of the duration representation. - _Y_ is the year designator that follows the value for the number of years. - _M_ is the month designator that follows the value for the number of months. - _W_ is the week designator that follows the value for the number of weeks. - _D_ is the day designator that follows the value for the number of days. - _T_ is the time designator that precedes the time components of the representation. - _H_ is the hour designator that follows the value for the number of hours. - _M_ is the minute designator that follows the value for the number of minutes. - _S_ is the second designator that follows the value for the number of seconds. Examples: - `PT15S` - 15 seconds - `PT1H30M` - 1 hour and 30 minutes - `P14D` - 14 days - `P14DT1H30M` - 14 days, 1 hour and 30 minutes - `P3Y6M4DT12H30M5S` - 3 years, 6 months, 4 days, 12 hours, 30 minutes and 5 seconds If the duration is zero or negative, the timer fires immediately. ### Time cycle A cycle defined as ISO 8601 repeating intervals format; it contains the duration and the number of repetitions. If the repetitions are not defined, the timer repeats infinitely until it is canceled. - `R5/PT10S`: Every 10 seconds, up to five times - `R/P1D`: Every day, infinitely It's possible to define a start time. By doing this, the timer triggers for the first time on the given start time. Afterwards, it will follow the interval as usual. - `R3/2022-04-27T17:20:00Z/P1D`: Every day up to three times, starting from April 27, 2022 at 5:20 p.m. UTC - `R/2022-01-01T10:00:00+02:00[Europe/Berlin]/P1D`: Every day infinitely, starting from January 1, 2022 at 10 a.m. UTC plus 2 hours If the start time is in the past at the time of deployment, the timer fires immediately upon deployment, and then continues with the regular interval from that point on. Additionally, you can specify a time cycle using cron expressions. Refer to the [CronExpression Tutorial](https://spring.io/blog/2020/11/10/new-in-spring-5-3-improved-cron-expressions) for additional information about using cron expressions. - `0 0 9-17 * * MON-FRI`: Every hour on the hour from 9-5 p.m. UTC Monday-Friday ## Additional resources ### XML representation A timer start event with time date: ```xml 2019-10-01T12:00:00Z ``` An intermediate timer catch event with time duration: ```xml PT10M ``` A non-interrupting boundary timer event with time cycle: ```xml R3/PT1H ``` ### References - [Expressions](/components/concepts/expressions.md) - [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) --- ## Undefined tasks An undefined task (also known as abstract task) defines a task for which the type of work is unspecified. This is used to model work done by someone the engine does not need to know of and there is no known system or UI interface. It is also used when modeling a process that is not automated, or while the process automation is in development. For the engine, an undefined task is handled as a pass-through activity, automatically continuing the process at the moment the process instance arrives. Undefined tasks have no real benefit for automating processes, and instead provide insights into the tasks that are performed outside of the process engine. :::tip modeling processes for automation Both Camunda Hub and Desktop Modeler default to undefined tasks when adding tasks to your process. You can change the task type by clicking on it and selecting the **Change element** menu icon. ::: ## Additional resources ### XML representation An undefined task: ```xml ``` --- ## User tasks A user task is used to model work that needs to be done by a human and is assisted by a workflow engine or software application. This differs from [manual tasks](/components/modeler/bpmn/manual-tasks/manual-tasks.md), which are not assisted by external tooling. When the process instance arrives at a user task, a new user task instance is created at Zeebe. The process instance stops at this point and waits until the user task instance is completed. When the user task instance is completed, the process instance continues. ![user-task](assets/user-task.png) Inside an [ad-hoc sub-process](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md), a user task is commonly the [human-in-the-loop](/reference/glossary.md#human-in-the-loop-hitl) tool an [AI agent](/reference/glossary.md#ai-agent) calls to escalate a decision or request approval before continuing. See [designing agent orchestration workflows](/components/agentic-orchestration/design-architecture.md#design-agent-orchestration-workflows) for this pattern. ## User task implementation types A default user task implementation type is the **Camunda user task** with the `zeebe:userTask` extension element. It is the recommended implementation type that is introduced on Camunda version 8.6. Alternatively, user tasks can be implemented with **Job workers** by removing the `zeebe:userTask` extension element. Refer to the [Job worker implementation](#job-worker-implementation) section for details. ## Camunda user tasks Camunda user tasks support assignments, scheduling, task updates, variable mappings, and a form for a user task as detailed in the following sections. :::note The Camunda user task implementation type was previously referred to as the **Zeebe user task**. ::: ### Assignments User tasks support specifying assignments, using the `zeebe:AssignmentDefinition` extension element. You can use this to define which user the task can be assigned to. You can specify one or all of the following attributes simultaneously: - `assignee`: Specifies the user assigned to the task. [Tasklist](/components/tasklist/introduction-to-tasklist.md) will claim the task for this user. - `candidateUsers`: Specifies the users that the task can be assigned to. - `candidateGroups`: Specifies the groups of users that the task can be assigned to. :::info Starting with Camunda 8.8, user task candidate groups should reference group IDs instead of group names. The Zeebe engine will attempt to resolve a candidate group value in the following way: 1. Confirm a group ID exists for the value. 2. If no ID is found, find a group name for the value, and resolve its ID. This behavior can be disabled by setting the `ZEEBE_BROKER_EXPERIMENTAL_ENGINE_CACHES_CANDIDATEGROUPNAMERESOLUTION=false` configuration property, so user task candidate group values are not checked by the Zeebe engine. See [zeebe.broker.experimental.engine.caches](/self-managed/components/orchestration-cluster/zeebe/configuration/broker.md#zeebebrokerexperimentalenginecaches) for all related configuration properties. ::: :::info Usernames and group IDs in the Orchestration Cluster are case-sensitive. When you set `assignee`, `candidateUsers`, or `candidateGroups`, always use the exact value from your identity provider or Identity user record, including case. For example, `abc@example.com` and `Abc@example.com` are treated as different users. Candidate users and candidate groups are not used by current Tasklist releases for task visibility or assignment. Use [user task authorization](/components/tasklist/user-task-authorization.md) and [authorization-based access control](/components/concepts/access-control/authorizations.md) to control who can see and work on a task. ::: Typically, the assignee, candidate users, and candidate groups are defined as [static values](/components/concepts/expressions.md#expressions-vs-static-values) (e.g. `some_username`, `some_username, another_username` and `sales, operations`), but they can also be defined as [expressions](/components/concepts/expressions.md) (e.g. `= book.author` and `= remove(reviewers, book.author)` and `= reviewer_roles`). The expressions are evaluated on activating the user task and must result in a `string` for the assignee and a `list of strings` for the candidate users and a `list of strings` for the candidate groups. For [Tasklist](/components/tasklist/introduction-to-tasklist.md) to claim the task for a known Tasklist user, the value of the `assignee` must be the user's **unique identifier**. The unique identifier depends on the authentication method used to login to Tasklist: - Camunda 8 (login with email, Google, GitHub): `email` - Default Basic authentication (Elasticsearch): `username` - IAM: `username` These assignees are not related to user restrictions, which is related to the visibility of the task in Tasklist for Self-Managed. :::note For example, say you log into Tasklist using Camunda 8 login with email using your email address `foo@bar.com`. Every time a user task activates with `assignee` set to value `foo@bar.com`, Tasklist automatically assigns it to you. You'll be able to find your new task under the task dropdown option `Assigned to me`. ::: ### Scheduling User tasks support specifying a task schedule using the `zeebe:taskSchedule` extension element. You can use this to define when users interact with a given task. You can specify one or both of the following attributes simultaneously: - `dueDate`: Specifies the due date of the user task. - `followUpDate`: Specifies the follow-up date of the user task. :::note For example, you can use the `followUpDate` to define the latest time a user should start working on a task, and then use the `dueDate` to provide a deadline when the user task should be finished. ::: You can define the due date and follow-up date as static values (for example, `2023-02-28T13:13:10+02:00`) or [expressions](/components/concepts/expressions.md) (for example, `= schedule.dueDate` and `= now() + duration("PT15S")`). The expressions are evaluated on activating the user task and must result in a `string` conforming to an ISO 8601 combined date and time representation. :::info ::: ### Define user task priority You can use the `zeebe:priorityDefinition` extension element to specify the priority of a user task. This allows you to prioritize the user task relative to other tasks within the same process, as well as across different processes. To set the priority of a user task, specify the priority in the `priority` attribute. - The priority must be an integer between `0` and `100`. If no value is provided, the default value is `50`. - A higher priority value indicates higher importance. - You can set the priority either as a static integer value or by using an [expression](/components/concepts/expressions.md). Expressions are evaluated when the user task is activated and must result in an integer within the specified range. ### Variable mappings By default, all variables submitted when the user task is completed are merged into the process instance. To propagate only selected variables, define an output mapping on the user task. If one or more output mappings are defined, only the mapped variables are propagated. Use input mappings to create [local variables](/components/concepts/variables.md#local-variables) in the scope of the user task, for example to reshape process variables into the format a form expects. These local variables stay in the user task scope unless an output mapping propagates them. For the mapping syntax, see [input/output variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings). For how a user task compares to other elements, see [variable propagation by BPMN element](/components/concepts/variables.md#variable-propagation-by-bpmn-element). ### User task forms A user task typically includes a form. A form contains work instructions for the user and captures the resulting information in a structured way. However, user tasks are not limited to forms. User tasks can also be used to refer users to other applications or redirect them to a website. You can use [Camunda Forms](/components/hub/workspace/modeler/modeling/utilize-forms.md) that offer visual editing of forms directly in Camunda Hub or Desktop Modeler. Or you can use your own forms. Forms can either be displayed in [Tasklist](/components/tasklist/introduction-to-tasklist.md), or handled by a custom application. To use a form, a user task requires a form reference. Depending on your use case, two different types of form references can be used: 1. **Camunda Forms** provide a flexible way of linking a user task to a Camunda Form via the form ID. Forms linked this way can be deployed together with the referencing process models. To link a user task to a Camunda Form, you have to specify the ID of the Camunda Form as the `formId` attribute of the task's `zeebe:formDefinition` extension element (see the [XML representation](#camunda-form)). The `bindingType` attribute determines which version of the linked form is used: - `latest`: The latest deployed version at the moment the user task is activated. - `deployment`: The version that was deployed together with the currently running version of the process. - `versionTag`: The latest deployed version that is annotated with the version tag specified in the `versionTag` attribute. To learn more about choosing binding types, see [choosing the resource binding type](/components/best-practices/modeling/choosing-the-resource-binding-type.md). :::note If the `bindingType` attribute is not specified, `latest` is used as the default. ::: You can read more about Camunda Forms in the [Camunda Forms guide](/components/hub/workspace/modeler/modeling/utilize-forms.md) or the [Camunda Forms reference](/components/modeler/forms/camunda-forms-reference.md) to explore all configuration options for form elements. 2. A **custom form reference** can specify any custom identifier in the user task using the `externalReference` attribute of the task's `zeebe:formDefinition` extension element (see the [XML representation](#camunda-form-linked)). How the identifier is interpreted depends on your implementation. You can use it to associate a custom form, route to a custom application, or a URL to a web page, for example. A custom form reference will not be shown in Tasklist. :::info For user tasks with a [job worker implementation](#job-worker-implementation), the custom form references are defined on the `formKey` attribute of the `zeebe:formDefinition` extension element instead of the `externalReference` attribute. Furthermore, there is a third form option for job worker-based user tasks: embedded Camunda Forms. You can use them to embed a form's JSON configuration directly into the BPMN process XML as a `zeebe:UserTaskForm` extension element of the process element. The embedded form can then be referenced via the `formKey` attribute (see [XML representation](#camunda-form-embedded)). ::: ### Task headers A user task can define an arbitrary number of `taskHeaders`; they are static metadata stored with the user task in Zeebe. The headers can be used as configuration parameters for Tasklist applications. ### User task listeners User tasks support **user task listeners**, which allow you to react to user task lifecycle events. To define a user task listener, include the `zeebe:taskListeners` extension element within the user task in your BPMN model. This element can contain one or more `zeebe:taskListener` elements, each specifying the following attributes: - The `eventType` (required) that triggers the listener. Possible values are: `creating`, `assigning`, `updating`, `completing`, `canceling`. - The `type` (required) of the listener. Used as a reference to specify which job workers request the respective task listener job. For example, `order-items`. `type` can be specified as any static value (`myType`) or as a FEEL expression prefixed by `=` that evaluates to any FEEL string; for example, `= "order-" + priorityGroup`. - The number of `retries` (optional) for the user task listener job (defaults to 3 if omitted). For more details, see [user task listeners](components/concepts/user-task-listeners.md). ## Job worker implementation A user task does not have to be managed by Zeebe. You can implement custom user task logic using Job workers. To define a Job worker implementation for a user task, simply remove the `zeebe:userTask` extension element from the task. User tasks implemented via Job workers behave similarly to service tasks, with two key differences: - Visual representation: The visual marker distinguishes user tasks from service tasks. - Model semantics: The interpretation and purpose in the process model differ. :::info The job worker implementation for user tasks is deprecated. We recommend using [Camunda user tasks](#camunda-user-tasks) instead for enhanced functionality and adherence to best practices. For a detailed comparison of task implementation types and guidance on migrating to Camunda user tasks, see the [migration guide](/apis-tools/migration-manuals/migrate-to-camunda-user-tasks.md). ::: When a process instance reaches a user task with a Job worker implementation: 1. Zeebe creates a corresponding job and waits for its completion. 2. A Job worker processes jobs of the type io.camunda.zeebe:userTask. 3. Once the job is completed, the process instance resumes execution. Use [task headers](/components/modeler/bpmn/service-tasks/service-tasks.md#task-headers) to pass static parameters to the job worker. Define [variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings) the [same way as a service task does](/components/modeler/bpmn/service-tasks/service-tasks.md#variable-mappings) to transform the variables passed to the job worker, or to customize how the variables of the job merge. ### Limitations User tasks implemented using Job workers come with significant limitations when compared to [Camunda user tasks](#camunda-user-tasks): 1. **API compatibility**: You cannot use the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) to manage user tasks based on job workers. These tasks are restricted to the functionality provided for [service tasks](/components/modeler/bpmn/service-tasks/service-tasks.md). 2. **Lifecycle management**: The Zeebe engine provides no visibility into lifecycle and state management features of Job worker-based user tasks. This means that you must handle these aspects in your custom application, outside the engine. Consider this approach only if your use case requires a highly specific user task implementation that cannot be achieved with Camunda user tasks. 3. **Reduced metrics and reporting**: Metrics and reporting for such user tasks are limited to the capabilities available for service tasks. This means you lose access to user task-specific insights provided by the Zeebe engine. 4. **Task-specific operations**: Assignments, scheduling, and other user task-specific details are included in the job as [task headers](/components/modeler/bpmn/service-tasks/service-tasks.md#task-headers). These must be handled in your custom implementation. Advanced user task features offered by Camunda are not available for Job worker-based user tasks. ## Additional resources ### XML representations #### Camunda Form A user task with a linked Camunda Form that does not specify the binding type (`latest` is used implicitly) as well as an assignment definition and a task schedule: ```xml ``` A user task with a linked Camunda Form that uses the `deployment` binding type: ```xml ``` A user task with a linked Camunda Form that uses the `versionTag` binding type: ```xml ``` #### Custom form reference A user task with an external task form referenced by a custom form reference: ```xml ``` :::info If you choose the [job worker implementation](#job-worker-implementation) for a user task, the custom form reference needs to be set to the `formKey` attribute instead of the `externalReference` attribute. ::: #### Camunda Form (embedded) :::info This is only supported if you choose the [job worker implementation](#job-worker-implementation) for a user task. ::: A job-based user task with an embedded Camunda Form: ```xml ``` #### User task listeners A user task with user task listeners configured: ```xml ``` ### References - [Tasklist](/components/tasklist/introduction-to-tasklist.md) - [Form linking in Camunda Hub](/components/hub/workspace/modeler/modeling/advanced-modeling/form-linking.md) - [Job handling](/components/concepts/job-workers.md) - [Variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings) - [User task listeners](/components/concepts/user-task-listeners.md) --- ## Data handling Process data in Camunda is represented by [variables](/components/concepts/variables.md). A variable has a name and a JSON value, and its visibility is determined by its [variable scope](/components/concepts/variables.md#variable-scopes). BPMN processes do not have an explicit data model or schema. Instead, variables are created implicitly during process execution — for example, when a process instance is started with variables, when a [job worker](/components/concepts/job-workers.md) completes a job, when a message is correlated, or when [input/output variable mappings](/components/concepts/variables.md#inputoutput-variable-mappings) are applied. Because there is no formal schema, the modeler infers the available variables by analyzing what is defined in the process diagram itself. This page explains how that works, and how you can help the modeler provide better editor support. ## How the modeler discovers variables The modeler scans the process diagram and collects variables from the definitions it finds, such as: - Input and output mappings defined on elements (the `target` expressions create variables). - Result variables and result expressions on [service tasks](/components/modeler/bpmn/service-tasks/service-tasks.md), [business rule tasks](/components/modeler/bpmn/business-rule-tasks/business-rule-tasks.md), and [script tasks](/components/modeler/bpmn/script-tasks/script-tasks.md). - Form field bindings on [user tasks](/components/modeler/bpmn/user-tasks/user-tasks.md) linked to [Camunda Forms](/components/modeler/forms/camunda-forms-reference.md). - Example data added to elements (see [Defining example data](#defining-example-data) below). The discovered variables are used in the following places: - FEEL editor suggestions — when writing [expressions](/components/concepts/expressions.md), the FEEL editor suggests variables in scope for the current element. - The **Variables** panel — an overview of all variables found in the diagram (see [Inspecting variables](#inspecting-variables)). :::note The modeler can only discover variables that are explicitly defined in the diagram. Variables created at runtime — for example, by [job workers](/components/concepts/job-workers.md), passed as process start variables, or set through the API — are **not** visible in the modeler unless you define them explicitly using [example data](#defining-example-data). ::: ## Inspecting variables The **Variables** panel helps you explore the variables in your process. Use it to understand which variables are visible in a given scope, where they are written, and what values they hold. 1. Open a BPMN diagram in Camunda Hub or Desktop Modeler. 2. On the right side of the modeling interface, open the **Variables** panel. :::tip If the **Variables** panel is hidden in Desktop Modeler, click **Window > Toggle Variables Panel**. ::: ### Selecting elements The list of variables shown in the panel depends on the element or elements you have selected on the canvas. When you select one or more elements, the panel displays all variables in the scope of those elements, including variables from parent scopes (for example, a sub-process scope or the process scope). If no element is selected, the panel shows variables at the process level. ### Filtering variables Use the search bar at the top of the panel to filter the displayed list of variables by name. To show only the variables written by the selected elements, enable the **Written by selection** toggle. ### Variable details Expand a variable to see its details: - **Written by** — lists all elements in the diagram that write to this variable. - **Value** — shows the value of the variable, if it can be determined from the diagram. ## Defining example data To help with editor support, you can add example data to an element: 1. In a BPMN diagram, on the right side of the modeling interface, open the **Details** panel. 2. On the **Properties** tab, under **Example data**, provide a JSON return value. This is used to derive variable names and types in the FEEL editor. For example, keys and types from your example data are suggested under **Output mapping > Variable assignment value**. Nested objects are also supported. Providing this data is optional, but it's recommended if you want to take full advantage of the FEEL editor's suggestions. It is especially useful for variables that the modeler cannot discover automatically, such as variables created by [job workers](/components/concepts/job-workers.md) or passed as process start variables. This data will also be used while [testing your process](/components/hub/workspace/modeler/validation/test-your-process.md) to set variables from the respective elements when performing the following actions: - Starting a new instance - Completing a job - Publishing a message with variables :::note The provided example data is only used by the FEEL editor to provide variable suggestions while modeling, and by Test mode to prefill variables. It is not used during process execution. ::: Data provided this way is added to the scope of the element. To use the data in other parts of your process, you can use [output mappings](/components/concepts/variables.md#output-mappings) to make the variables available in the parent scope. ## Next steps - Learn about [variables](/components/concepts/variables.md), including variable scopes, propagation, and input/output variable mappings. - Explore how to [access variables in expressions](/components/modeler/feel/language-guide/feel-variables.md). --- ## Connect to Camunda 8 To deploy diagrams, start process instances, or test tasks, you must first connect Desktop Modeler to Camunda. Follow the steps below to connect to **Camunda 8 SaaS**. To connect to a local installation, visit the [Camunda 8 Self-Managed guide](../../../self-managed/components/modeler/desktop-modeler/connect-to-self-managed.md). 1. Click the connection selector. For new installations, this will show **No connection**. If you have previously selected a connection, it will show the name of that connection. ![No connection button in status bar](./img/connection-selector-offline.png) 2. To add a new connection, open the settings. You can either click **Manage connections**, or open the settings directly (Cmd/Ctrl + ,). ![Showing a connection selection popup](./img/connection-selector-offline-open.png) By default, a local c8run connection is already set up. If you have previously used Desktop Modeler to deploy diagrams, those connections will also be available under **Unnamed Connection**. 3. Click **Add connection**. ![Connection manager showing add button](./img/connection-manager-add.png) 4. Enter a name, the connection URL, and the connection credentials (client ID and client secret) for your [API client](../../saas/clusters/manage-api-clients.md). Optionally enter the tenant ID. ![Connection manager with new connection information filled in](./img/connection-manager-new-connection-loading.png) :::caution When connecting to a tenant, the API client must be assigned to it through [Orchestration Cluster Admin](../../admin/admin-introduction.md). ::: Desktop Modeler automatically validates the connection. If you have issues connecting, see the [troubleshooting page](./troubleshooting.md#debug-zeebe-connection-issues). ![Connection manager showing connection error](./img/connection-manager-new-connection-error.png) If the connection is established successfully, you can go back to the connection selection, where your new connection is now available. ![Connection manager showing connection success](./img/connection-manager-new-connection-success.png) 5. Select the connection you just created to use it for [deployment](./deploy-diagram.md), [starting a new process instance](./start-instance.md), or [task testing](./task-testing.md). ![New connection selected in connection manager](./img/connection-selector-new-connection.png) :::note As a next step, [deploy your diagram](./deploy-diagram.md). ::: --- ## Use credentials Select an existing [credential](/components/hub/organization/credentials/index.md) on a connector task in Desktop Modeler, or create a new one without leaving the properties panel. A credential stores authentication and connection configuration you create once and reuse, instead of entering the same values on every task. :::note This page covers credentials that authenticate connector tasks, such as an AWS Credential. It is unrelated to the connection credentials (client ID and client secret) you enter to [connect Desktop Modeler to Camunda](./connect-to-camunda-8.md), which authenticate Desktop Modeler itself. ::: ## Select a credential Connectors that support credentials show a credential field in the properties panel, such as **AWS Credential**. Select the field to open the credential chooser, which lists the credentials on the connected Camunda instance that match the credential type the connector needs. Selecting a credential stores only a reference to it in your diagram. The credential's values stay on Camunda. If no credential matches, the chooser tells you so by name, for example `Cannot find AWS Credential with name AWS_PROD`. This usually means the credential does not exist on the Camunda instance you are connected to, or it was created for a different credential type. ## What you can do in the chooser What the chooser offers depends on whether Desktop Modeler is connected to Camunda, and on your permissions there. Desktop Modeler checks your permissions once per connection. | Situation | Available actions | | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | No connection | Select a credential that is already referenced in the diagram. You cannot browse, create, edit, or upgrade credentials. | | Connected, with permission to create credentials | Select a credential, or create a new one. | | Connected, with permission to update credentials, and the selected credential is compatible | Select a credential, or edit the selected one. | | Connected, with permission to update credentials, and the selected credential is out of date | Select a credential, or upgrade the selected one. | | Connected, without permission to create or update credentials | Select a credential only. | A connector declares the minimum credential version it needs. A newer credential always satisfies an older requirement, so upgrading is only needed when a credential is older than the connector requires. ## Create a credential To create a credential from the properties panel: 1. Open the credential field, then select the option to create a new credential. 2. Enter a **Credential name**. Desktop Modeler suggests a **Credential ID** based on the name. 3. Change the **Credential ID** if you want a different one. You cannot change it after the credential is created. 4. Fill in the fields for this credential type. For a sensitive field, enter a reference to a secret that already exists on Camunda, using `camunda.secrets.` followed by the secret key, such as `camunda.secrets.AWS_SECRET_KEY`. 5. Save the credential. Desktop Modeler creates it on the connected Camunda instance and selects it on the connector task. Desktop Modeler checks whether the secret you referenced exists on Camunda, without revealing its value. If the secret is missing, you see a warning, but you can still save the credential. The connector fails at runtime until the secret exists. :::note Desktop Modeler cannot create the secret itself. Add the secret to the secret store first, then reference it from the credential. ::: ## Edit or upgrade a credential Editing a credential opens the same form, pre-filled with its current values. Saving replaces the credential's values on Camunda, which takes effect immediately for every process that references it. Upgrading a credential opens the same form and shows the fields that the newer credential version adds. Fill them in and save to make the credential usable with the connector version you are modeling against. ## Credential scope {#credential-scope} A credential you create from Desktop Modeler is stored on the Camunda instance you are connected to, and is not registered centrally. It does not appear on the **Managed in Hub** tab of the [**Credentials** page in Camunda Hub](/components/hub/organization/credentials/index.md#managed-credentials). To manage it centrally, find it on the [**Environments only** tab in Camunda Hub](/components/hub/organization/credentials/index.md#environments-only-credentials) and add it. ## Additional resources - [Manage credentials](/components/hub/organization/credentials/index.md) - [Use connectors](./use-connectors.md) - [Connect to Camunda 8](./connect-to-camunda-8.md) --- ## Custom lint rules Through Desktop Modeler plugins, you can add custom lint rules and configure or disable existing rules. [`bpmnlint`](https://github.com/bpmn-io/bpmnlint) is used to validate BPMN diagrams, so the plugins have to be [`bpmnlint` plugins](https://github.com/bpmn-io/bpmnlint#writing--consuming-custom-rules) at the core. ## Getting started Get started with the [custom-linter-rules-plugin template](https://github.com/camunda/camunda-modeler-custom-linter-rules-plugin) and take the following steps: 1. Clone or fork the repository: ``` git clone https://github.com/camunda/camunda-modeler-custom-linter-rules-plugin.git ``` The plugin starter project comes with a client folder referenced in the [plugin entry point](../plugins#plugin-entry-point). It contains the script that adds a `bpmnlint` plugin to the modeler. Since this is a [client plugin](../plugins#extend-the-modeler-and-its-bpmn-and-dmn-components), you must bundle it. 2. Install the dependencies with the following command: ``` npm install ``` 3. Add a custom rule. To do this, add it to the [`bpmnlint-plugin-custom/rules`](https://github.com/camunda/camunda-modeler-custom-linter-rules-plugin/tree/master/bpmnlint-plugin-custom/rules) folder. The example project contains a [`no-manual-task.js`](https://github.com/camunda/camunda-modeler-custom-linter-rules-plugin/blob/master/bpmnlint-plugin-custom/rules/no-manual-task.js) file which implements a custom rule. Every rule must export a function that returns an object with a `check` function: ```javascript /** * Rule that reports manual tasks being used. */ module.exports = function () { function check(node, reporter) { if (is(node, "bpmn:ManualTask")) { reporter.report(node.id, "Element has disallowed type bpmn:ManualTask"); } } return { check: check, }; }; ``` This function is called for every node when `bpmnlint` traverses the [model](https://github.com/bpmn-io/bpmn-moddle/). 4. Change the configuration. Through the configuration in [.bpmnlintrc](https://github.com/camunda/camunda-modeler-custom-linter-rules-plugin/blob/master/.bpmnlintrc), you can add the custom rules you implemented in [`bpmnlint-plugin-custom/rules`](https://github.com/camunda/camunda-modeler-custom-linter-rules-plugin/tree/master/bpmnlint-plugin-custom/rules). ```javascript { "extends": [ "bpmnlint:recommended", "plugin:custom/recommended" ], "rules": { "label-required": "off", "custom/no-manual-task": "warn" } } ``` The example configuration adds all rules specified in the [`recommended`](https://github.com/camunda/camunda-modeler-custom-linter-rules-plugin/blob/master/bpmnlint-plugin-custom/index.js) configuration of the `bpmnlint` plugin. It also adds [all the rules that come with `bpmnlint`](https://github.com/bpmn-io/bpmnlint/tree/master/rules) and configures two rules. 5. Bundle your plugin by running the following command: ``` npm run build ``` The custom lint rules and configuration will be used when validating a BPMN diagram. ![Desktop Modeler with custom lint rule](./img/custom-lint-rule.png) ## Additional resources - [Example plugin](https://github.com/camunda/camunda-modeler-custom-linter-rules-plugin) - [bpmnlint](https://github.com/bpmn-io/bpmnlint) --- ## Deploy a diagram Desktop Modeler can directly deploy diagrams and start process instances in Camunda 8. Follow the steps below to deploy a diagram to Camunda. 1. Set up and select a connection by following [Connect to Camunda 8](./connect-to-camunda-8.md) guide. ![Connection selector showing the Dev connection selected](./img/connection-selector-dev-connection.png) 2. Click the **Deploy** icon: ![deploy icon](./img/deploy-icon.png) 3. Click **Deploy** to perform the actual deployment. ![deploy diagram](./img/deploy-diagram.png) 4. If the deployment is successful, you will see a confirmation message: ![deployment successful](./img/deploy-diagram-success.png) :::note As a next step, [start a new process instance](./start-instance.md). ::: --- ## Configuring templates [Element templates](/components/modeler/element-templates/about-templates.md) are loaded by Desktop Modeler at application startup. Reloading it using `Cmd+R` or `Ctrl+R` reloads all templates. Templates are treated as global or local depending on their location in your file system. ## Global templates For templates to be available for all diagrams store them in the `resources/element-templates` directory containing the Camunda Modeler executable. Alternatively, for element templates to be available across Camunda Modeler installations, you can store them in the `resources/element-templates` directory in the modeler's [user data directory](../../search-paths#user-data-directory). ### Examples ``` └── camunda-modeler-5.10.0-win-x64 ├── Camunda Modeler.exe └── resources └── element-templates └── my-element-templates.json ``` :::note On macOS, the Camunda Modeler is a self-contained `.app` bundle, which makes it difficult to add files to its installation directory. Therefore, we recommend storing [global templates](#global-templates) in the [user data directory](../../search-paths#user-data-directory). ::: ``` └── ~/Library/Application Support └── camunda-modeler └── resources └── element-templates └── my-element-templates.json ``` ``` └── camunda-modeler-5.10.0-linux-x64 ├── camunda-modeler └── resources └── element-templates └── my-element-templates.json ``` ## Local templates For element templates to only be available for specific diagrams, you can store them in a `.camunda/element-templates` directory in the diagrams parent directory or any of their parent directories. ### Example ``` ├── diagram.bpmn └── .camunda └── element-templates └── my-element-templates.json ``` Learn more about search paths [here](../../search-paths). --- ## Using templates ## Applying templates If an [element template](/components/modeler/element-templates/about-templates.md) matches a selected diagram element, the template catalog button is shown in the properties panel on the right side of the screen. ![Template Chooser](./img/chooser.png) Clicking **Select** opens a popup menu, allowing you to browse and search available templates for the selected element. ![Popup menu](./img/modal.png) Applying a template stores it via the `modelerTemplate` property and the optional `modelerTemplateVersion` property on the selected element: ```xml ``` It also sets up custom fields on the diagram element and makes these available for inspection and editing. Properties which were not configured in the element template using custom fields will not be available for editing. ### Applying a template that defines operations Templates that define [operations](/components/modeler/element-templates/template-metadata.md#predefined-configurations-steps-and-presets) — for example, a connector template for a service with several operations — show their operations in the popup menu instead of applying the template directly. Select an operation to apply the template with that operation preselected, then complete the remaining fields in the properties panel. Operations are also matched by search, so you can search for the action you want to perform, such as `upload object`, without knowing which template provides it. Search matches an operation's name, description, and keywords together with those of its parent operations and its template. ## Removing templates To remove an applied template from an element, either the _Unlink_ or _Remove_ function can be used: - **Remove**: Remove the element template from the `modelerTemplate` property and reset all properties of the respective element. - **Unlink**: Remove the element template from the `modelerTemplate` property but keep the properties which were set. ![Unlink or Remove](./img/unlink-remove.png) ## Updating templates If a template is applied and a new version of the template is found you can _update_ the template. ![Update Template](./img/update-template.png) Templates are updated according to the following rules: - If the property is set in the new template, it will override the existing value — unless the value was originally set by the old template and has been manually changed since. - If the property is not defined in the new template, it will unset. - Sub-properties of complex properties (for example, `zeebe:input`, `zeebe:output`) are handled according to these rules if they can be identified. ### Replacing templates If a template is deprecated with a new element template and you want to keep the same input values as in the deprecated template, you can: 1. **Unlink**: Remove the current template that is deprecated from the `modelerTemplate` property, but keep the properties which were set. 2. **Select** and apply the new element template. ## Missing templates If a template is applied to an element but the respective template cannot be found on the system, the editing of the element is disabled. _Unlinking_ or _removing_ the template for the element or adding the element template config enables the editing again. ![Template not Found](./img/template-not-found.png) --- ## Flags Flags allow you to control the availability of certain features within Desktop Modeler. Learn which flags [are available](#available-flags) and how to [configure them](#configuration). ## Configuration You may configure flags in a `flags.json` file or pass them via CLI. ### Configuration via `flags.json` :::note Configuration changes via `flags.json` will only take effect once you restart the application. ::: Place a `flags.json` file inside the `resources` folder of your local [`{USER_DATA}`](../search-paths#user-data-directory) or [`{APP_DATA_DIRECTORY}`](../search-paths#app-data-directory) directory to persist them. ### Configuration via command line Pass flags via the command line when starting the application. ```plain "Camunda Modeler.exe" --disable-plugins ``` ```plain camunda-modeler --disable-plugins ``` ```plain camunda-modeler --disable-plugins ``` Flags passed as command line arguments take precedence over those configured via a configuration file. ## Available flags | flag | default value | | ------------------------------------------------------------- | ----------------------------------- | | ["disable-plugins"](#disable-plug-ins) | false | | "disable-adjust-origin" | false | | "disable-dmn" | false | | "disable-form" | false | | "disable-rpa" | false | | ["disable-httl-hint"](#disable-history-time-to-live-hint) | false | | ["default-httl"](#default-history-time-to-live) | false | | "disable-platform" | false | | "disable-zeebe" | false | | "disable-remote-interaction" | false | | ["single-instance"](#single-instance) | false | | "user-data-dir" | [Electron default](../search-paths) | | ["display-version"](#custom-display-version-label) | `undefined` | | ["zeebe-ssl-certificate"](#zeebe-ssl-certificate) | `undefined` | | ["c7-engine-version"](#default-execution-platform-version) | `undefined` | | ["c8-engine-version"](#default-execution-platform-version) | `undefined` | | ["enable-new-context-pad"](#enable-new-context-pad) | `false` | | ["disable-connector-templates"](#disable-connector-templates) | `false` | ## Examples ### Disable plug-ins Start the modeler without activating installed plug-ins. This is useful to debug modeler errors. ### Single instance By default, Desktop Modeler enforces single-instance mode, preventing more than one instance from running at a time. To run multiple instances simultaneously, pass `--no-single-instance` when starting the application. ### BPMN-only mode To disable the DMN, Form, and RPA script editing capabilities of the App, configure your `flags.json` like this: ```js { "disable-dmn": true, "disable-form": true } ``` As a result, the app will only allow users to model BPMN diagrams. ![BPMN only mode](./img/bpmn-only.png) ### Disable `history-time-to-live` hint Camunda 7 only To disable the [history time to live](https://docs.camunda.org/manual/latest/modeler/history-time-to-live/) hint in scenarios where the engine configures HTTL, configure `flags.json`: ```js { "disable-httl-hint": true } ``` ### Default `history-time-to-live` Camunda 7 only To set a default [history time to live](https://docs.camunda.org/manual/latest/modeler/history-time-to-live/) value to be used in newly created models, configure `flags.json`: ```js { "default-httl": 30 } ``` ### Custom `display-version` label To display a custom version information in the status bar of the app, configure `flags.json`: ```js { "display-version": "1.0.0" } ``` ![Custom version info](./img/display-version.png) ### Zeebe SSL certificate Camunda 8 only > :information_source: Modeler will read trusted certificates from your operating system's trust store. Provide additional certificates to validate secured connections to a Camunda 8 installation. Configure your `flags.json`: ```js { "zeebe-ssl-certificate": "C:\\path\\to\\certs\\trusted-custom-roots.pem" } ``` Additional information adapted from the [upstream documentation](https://nodejs.org/docs/latest/api/tls.html#tlscreatesecurecontextoptions): > The peer (Camunda 8) certificate must be chainable to a CA trusted by the app for the connection to be authenticated. When using certificates that are not chainable to a well-known CA, the certificate's CA must be explicitly specified as trusted or the connection will fail to authenticate. If the peer uses a certificate that doesn't match or chain to one of the default CAs, provide a CA certificate that the peer's certificate can match or chain to. For self-signed certificates, the certificate is its own CA, and must be provided. ### Default execution platform version This setting controls the [execution platform version](/reference/glossary.md#execution-platform-version) used by Desktop Modeler. It does not change the version of a deployed cluster or process definition. To change default execution platform version, configure your `flags.json` as follows: ```json { "c7-engine-version": "7.18.0", "c8-engine-version": "8.0.0" } ``` New diagrams created in Desktop Modeler will use the configured version instead of the latest stable version. ### Enable new context pad To use the new context pad, configure your `flags.json` as follows: ```json { "enable-new-context-pad": true } ``` ![New context pad](./img/new-context-pad.png) ### Disable connector templates Camunda 8 only To [disable automatic connector template fetching](../use-connectors.md#automatic-connector-template-fetching), configure your `flags.json` as follows: ```json { "disable-connector-templates": true } ``` --- ## Desktop Modeler Camunda 7 and 8 Desktop Modeler (also known as Camunda Modeler) is a desktop application for modeling BPMN, DMN, and Forms. As part of the Camunda implementation toolkit, it helps you create executable diagrams while working alongside your preferred IDE, integrated into your professional software development environment. ![Desktop Modeler Screenshot](./img/new-diagram.png) ## Features - Design [BPMN](../bpmn/bpmn.md), [DMN](../dmn/dmn.md), and [Forms](../forms/camunda-forms-reference.md) - Implement process applications for Camunda 7 and 8 - Deploy and run processes directly from the application - Validate your diagrams using [configurable lint rules](https://github.com/camunda/camunda-modeler-custom-linter-rules-plugin) - [Customize](./flags/flags.md) and [extend](./plugins/plugins.md) the application - Work against the local file system ## Download Download the app for Windows, Linux, or macOS from the [Camunda downloads page](https://camunda.com/download/modeler/). After downloading, follow the [installation steps](./install-the-modeler.md) to extract the archive and start the application. > On Windows and Linux you can carry out [additional steps](./install-the-modeler.md#wire-file-associations) to register Modeler as the default editor for BPMN, DMN, and Form files. ## Get started Learn how to [develop your first process](./model-your-first-diagram.md) and [deploy it](./connect-to-camunda-8.md) to Camunda 8. ## Resources - [Report an issue](https://github.com/camunda/camunda-modeler/issues) - [Source code](https://github.com/camunda/camunda-modeler) - [Troubleshooting](./troubleshooting.md) --- ## Install Desktop Modeler This document guides you through Desktop Modeler (also known as Camunda Modeler) installation, our local modeler. Desktop Modeler is a desktop application for modeling BPMN, DMN, and Forms, and supports you in building executable diagrams with Camunda. ## Installation To install [Desktop Modeler](./index.md) for Windows, macOS, and Linux, visit the [Camunda downloads page](https://camunda.com/download/modeler/). Select your preferred version and take the following steps: 1. Unpack the archive (any platform) or open the `.dmg` file (macOS). 2. For macOS users, move the app to your applications folder (macOS). 3. Start the Camunda Modeler (Windows) or `camunda-modeler` (Linux) executable, or the Camunda Modeler application (macOS). Ensure the installation is owned by `root` and accessible to all users of the machine by following the steps below. 1. Unpack the zip archive using the `tar` command: ```shell cd /usr/bin sudo tar xvfz ~/Downloads/camunda-modeler-5.41.0-linux-x64.tar.gz ``` 2. Ensure the access permissions of the `chrome-sandbox` file are correct and create a link to this version: ```shell sudo chmod 4755 camunda-modeler-5.41.0-linux-x64/chrome-sandbox sudo ln -s camunda-modeler-5.41.0-linux-x64/camunda-modeler camunda-modeler ``` ## Wire file associations On Windows and Linux you can carry out additional steps to register Camunda Modeler as the default editor for BPMN, DMN, RPA, and Form files. On macOS, Modeler is automatically registered as the default editor. ### Windows To make Camunda Modeler the default editor for `.bpmn`, `.dmn`, `.rpa`, and `.form` files, execute `support/register_fileassoc.bat` in your terminal. ### Linux To register Camunda Modeler for `.bpmn`, `.dmn`, `.rpa`, and `.form` files, execute `support/xdg_register.sh` from your terminal. ## Next steps - [Model your first diagram](/components/modeler/desktop-modeler/model-your-first-diagram.md) - [Use connectors](/components/modeler/desktop-modeler/use-connectors.md) --- ## Model your first diagram After starting [Desktop Modeler](./index.md), you can model your first BPMN diagram. Follow the steps below: 1. Create a [BPMN](../bpmn/bpmn.md) diagram by selecting **BPMN diagram**: ![empty application](./img/empty.png) The BPMN diagram opens with a start event. [Events](/components/modeler/bpmn/events.md) in BPMN represent things that happen. A process can react to events and emit events, for example: ![new diagram](./img/new-diagram.png) 1. The basic elements of BPMN processes are [tasks](/components/modeler/bpmn/tasks.md), or atomic units of work composed to create a meaningful result. Click on the start event and select the rectangular task element. This creates a task directly following the start event, connected by an arrow. Next, double-click the task and type in a name for the element. For example, `My Service Task`. 1. Click on the task and select the dark circle in the top left. This attaches an end event directly to the task. On the left side of the screen you will find the element palette, where you can also drag and drop elements onto the canvas. ![elements](./img/elements.png) Above, you can see that elements that support different types can be reconfigured by clicking on the corresponding icon. In this case, the task can be converted to a [service task](../bpmn/service-tasks/service-tasks.md), for example, by clicking on the element and selecting the **Change element** menu icon. ![task configuration](img/element-configuration.png) You can also [orchestrate human tasks](/guides/getting-started-orchestrate-human-tasks.md). Review the [complete list of supported BPMN elements](/components/modeler/bpmn/bpmn-coverage.md) 1. Open the properties panel by selecting the light gray arrow on the right side of the page halfway down the canvas. Here, you can edit the properties of the currently selected element: ![properties panel](img/properties-panel.png) For example, you might name your element and give it an ID under the **General** section. 1. Save the diagram using **File > Save**, **File > Save As**, or the keyboard shortcut (`Cmd + S` on macOS or `Ctrl + S` on Windows and Linux). Desktop Modeler saves the file to the location you choose on your local file system; it does not store diagrams in a separate internal workspace. To find the file again, reopen it from that folder or from the recent files list on the Desktop Modeler start screen. 1. Once you finish modeling and configuring your diagram, you can deploy it to [Camunda](./connect-to-camunda-8.md). --- ## Plugins :::note The Desktop Modeler plugins API is not stable and might change in the future. ::: Plugins allow you to change the appearance and behavior of Desktop Modeler and add new features. ## Plugging into Desktop Modeler You can plug into the modeler to change its appearance, add new menu entries, extend the modeling tools for [BPMN](https://github.com/bpmn-io/bpmn-js) and [DMN](https://github.com/bpmn-io/dmn-js), or even slot React.js components into the Desktop Modeler UI. To add a plugin, put it into the `resources/plugins` directory relative to your [`{APP_DATA_DIRECTORY}`](../search-paths#app-data-directory) or [`{USER_DATA_DIRECTORY}`](../search-paths#user-data-directory) directory. Desktop Modeler searches for available plugin entry points via the `resources/plugins/*/index.js` pattern. This means that each plugin must reside in its own folder, which is a direct child of the `plugins` directory. :::note If you download and extract plugins from GitHub, the extracted directory contains the actual plugin, so make sure to copy the plugin, not its parent directory. ::: ## Overview of your possibilities as a plugin developer There are many ways for a developer to extend Desktop Modeler and its modeling tools. The following table shows an overview: | Plugin type | Functionality | Example | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | Menu Entries | Add new entries to the menu bar - useful to interact with your plugins, link to external pages, add settings, etc. | [Menu Example](https://github.com/camunda/camunda-modeler-plugins/tree/master/menu-plugin-example) | | Custom Styles | Change the look and feel of Desktop Modeler by adding stylesheets. | [Styles Example](https://github.com/camunda/camunda-modeler-plugins/tree/master/style-plugin-example) | | React Components | Embed custom React.js components into specific anchor points of Desktop Modeler. | [React Plugin Example](https://github.com/pinussilvestrus/camunda-modeler-autosave-plugin) | | bpmn-js Modules | Extend our BPMN editor by injecting your own custom [bpmn-js](https://github.com/bpmn-io/bpmn-js) modules. | [bpmn-js Module Example](https://github.com/camunda/camunda-modeler-plugins/tree/master/bpmn-js-plugin-example) | | bpmn-moddle Extensions | Extend the BPMN language model by injecting your own custom [bpmn-moddle](https://github.com/bpmn-io/bpmn-moddle) modules. | [bpmn-moddle Extension Example](https://github.com/camunda/camunda-modeler-plugins/tree/master/bpmn-js-plugin-moddle-extension-example) | | dmn-js Modules | Extend our DMN editor by injecting your own custom [dmn-js](https://github.com/bpmn-io/dmn-js) modules. | [dmn-js Module Example](https://github.com/camunda/camunda-modeler-plugins/tree/master/dmn-js-plugin-example) | | dmn-moddle Extensions | Extend the DMN language model by injecting your own custom [dmn-moddle](https://github.com/bpmn-io/dmn-moddle) modules | n/a | | bpmnlint Plugins | Add custom lint rules through [bpmnlint](https://github.com/bpmn-io/bpmnlint) plugins | [Custom lint rules](../custom-lint-rules) | ## Getting started with development ### Plugin entry point Regardless of the type of your plugin, you have to export a [Node.js module](https://nodejs.org/api/modules.html) named `index.js` that acts as a plugin entry point. The following shows an example of such entry point: ```javascript module.exports = { name: "My Awesome Plugin", // the name of your plugin style: "./style.css", // changing the appearance of the modeler menu: "./menu.js", // adding menu entries to the modeler script: "./script.js", // extending the modeler, and its BPMN and DMN components }; ``` The modeler will automatically load your plugins on startup. ### Changing the appearance of the modeler You can change the appearance of the modeler using CSS. Your stylesheet might look like this: ```css body { background: linear-gradient(0deg, #52b415, #eee); } ``` Plug it into the modeler like this: ```javascript module.exports = { style: "./style.css", }; ``` ### Adding menu entries to the modeler You can add new menu entries to the modeler's menu. Describe your menu entries like this: ```javascript module.exports = function (electronApp, menuState) { return [ { label: "Open BPMN Reference", accelerator: "CommandOrControl+[", enabled: function () { // only enabled for BPMN diagrams return menuState.bpmn; }, action: function () { var shell = require("electron").shell; shell.openExternal("https://camunda.org/bpmn/reference/"); }, }, ]; }; ``` Plug them into the modeler like this: ```javascript module.exports = { menu: "./menu-entries", }; ``` :::note The code within the menu entries executes on [the main process](https://www.electronjs.org/docs/latest/tutorial/process-model) of Electron. This comes with the advantage of allowing you to use [Node.js](https://nodejs.org/en/) modules, but you need to consider that you cannot debug the respective code in Chromium. For more information regarding main process debugging, refer to the [official Electron documentation](https://www.electronjs.org/docs/latest/tutorial/debugging-main-process). ::: For more information on how the modeler's menu works, take a look at its [implementation](https://github.com/camunda/camunda-modeler/blob/master/app/lib/menu/menu-builder.js). ### Extend the modeler and its BPMN and DMN components You can extend the modeling tools for [BPMN](https://github.com/bpmn-io/bpmn-js) and [DMN](https://github.com/bpmn-io/dmn-js) with your own modules, as well as embedding React.js components into certain sections of Desktop Modeler. Since the client of the modeler uses [Chromium](https://www.chromium.org/Home), you can't use Node.js modules to extend the modeling tools. You need to bundle your plugin first. The easiest way to get started with client-side plugins is through [this example project](https://github.com/camunda/camunda-modeler-plugin-example). > In this example, we are building a bpmn-js plugin, but this basic structure applies to all extensions besides menu entries and style. The modules themselves will be different however, so refer to our [examples](https://github.com/camunda/camunda-modeler-plugins) for more information on how to build different kinds. Take the following steps: 1. Clone or fork the repository: ``` git clone https://github.com/camunda/camunda-modeler-plugin-example.git ``` The plugin starter project comes with a menu and style folder which are referenced in the plugin entry point. If you do not need those, you can remove them from the entry point and delete the respective folder. 2. Install the dependencies: ``` npm install ``` 3. Create your module: ```javascript function LoggingPlugin(eventBus) { eventBus.on("shape.added", function () { console.log("A shape was added to the diagram!"); }); } module.exports = { __init__: ["loggingPlugin"], loggingPlugin: ["type", LoggingPlugin], }; ``` 4. Require your file in `client.js` and register it via our [helper functions](https://github.com/camunda/camunda-modeler-plugin-helpers): ```javascript var registerBpmnJSPlugin = require("camunda-modeler-plugin-helpers").registerBpmnJSPlugin; var plugin = require("./LoggingPlugin"); registerBpmnJSPlugin(plugin); ``` 5. You may want to create a plugin which specifically targets Camunda 7 or Camunda 8. To do this, use the appropriate variations of the registration helper function for your plugin type. ```javascript registerPlatformBpmnJSPlugin(plugin); // Register plugin for Camunda 7 BPMN diagrams only registerCloudBpmnJSPlugin(plugin); // Register plugin for Camunda 8 BPMN diagrams only registerBpmnJSPlugin(plugin); // Register plugin for Camunda 7 and 8 BPMN diagrams ``` 6. You can use the globally available functions `getModelerDirectory` and `getPluginsDirectory` to load additional resources: ```javascript function LoggingPlugin(eventBus, canvas) { var img = document.createElement(img); img.src = getPluginsDirectory + "/logging-plugin/image.png"; canvas.getContainer().appendChild(img); } ``` 7. Bundle your plugin: ``` npm run build ``` 8. Put the folder into the `resources/plugins` directory relative to your Desktop Modeler installation directory. You can now use your plugin! ### Development workflow When creating a plugin, you can place the directory containing your plugin in the aforementioned `resources/plugins` directory. Plugins will be loaded on application startup (menu plugins) or reload (style and modeling tool plugins). To reload the application, press `F12` to open the developer tools, and press `Ctrl+R` or `Cmd+R`. This will clear all unsaved diagrams. ## Additional resources - [Example Plugins](https://github.com/camunda/camunda-modeler-plugins) - [Plugin Starter Project](https://github.com/camunda/camunda-modeler-plugin-example) --- ## Projects(Desktop-modeler) Desktop Modeler supports [Camunda projects](../../concepts/projects.md). It recognizes a project by the [`camunda.json`](/apis-tools/c8ctl/camunda-json.md) file in the root of your project folder. Within a project, Desktop Modeler offers advanced editor intelligence, deployment, and execution features. :::tip Camunda projects supersede process applications. Your existing process applications keep working; see [Migrate from process applications](/components/concepts/projects.md#migrate-from-process-applications) when you are ready to switch. ::: In professional software development, a typical project contains resources such as BPMN, DMN, and Form files. These live alongside [job workers](/components/concepts/job-workers.md), implementing process logic, additional application code, and tests. How exactly your project is structured may vary depending on the implementation language, libraries, and frameworks you use. ## Example: Consumer loan application Consider a project implementing consumer loan approval. It may contain: - A main BPMN process (for example, `consumer-loan-application.bpmn`) to define the workflow. - DMN decisions (for example, `interest-rate-calculation.dmn`, `credit-score-calculation.dmn`) for business rules. - Forms (for example, `loan-application-review.form`) for user interactions. - Various [job workers](/components/concepts/job-workers.md) that implement process behavior. - Additional application code and tests. In a typical Java/Maven project, the structure of such a project might be as follows: ``` consumer-loan-application/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/example/loan/ │ │ │ │ └── workers │ │ │ │ │ ├── UnderwriteLoanWorker.java │ │ │ │ │ └── ... │ │ └── resources/ │ │ ├── consumer-loan-application.bpmn │ │ ├── dmn/ │ │ │ ├── interest-rate-calculation.dmn │ │ │ └── credit-score-calculation.dmn │ │ └── form/ │ │ └── loan-application-review.form │ └── test/ │ └── java/ │ └── ... ├── camunda.json ├── pom.xml └── README.md ``` ## Editor support for projects When you open a file, Modeler checks for a `camunda.json` file in the same folder or a parent folder to determine whether the file belongs to a project. Within a project, Modeler offers improved navigation and assistance. ### Indicating context Modeler opens and closes projects implicitly. A blue item in the status bar indicates whether a diagram belongs to a project and makes all related diagrams available for navigation. When files from more than one project are open, they are grouped visually. ### Creating a project Create a project by creating a `camunda.json` file in the root of your project folder. An empty object (`{}`) is enough to mark the folder. Alternatively, create it in the Modeler UI: 1. Click **File > New Project...**. 2. Choose a folder. 3. Click **Select folder**. Modeler creates a `camunda.json` file in the selected folder and recognizes the folder as the project root. Any file within the folder or its subfolders is part of the project. ### Linking resources Any file within a project can be linked as a resource. Linking a resource can be achieved in several ways: - Using the append feature - Using the replace feature - Using the create feature - Manually by setting the process, decision, or form ID in the properties panel ### Deploying a project You can deploy projects using the [deploy feature](./deploy-diagram.md). When you deploy a project, Modeler deploys all files that are part of it. ### Starting a process instance :::note Before starting a process instance, all project files will be deployed to reflect the state of the project. ::: You can start an instance for any process in a project using the [start instance feature](./start-instance.md). --- ## Search paths Features like element templates and plugins allow you to add your own resources to Desktop Modeler. For these resources to be found, they have to be in one of two directories depending on how local or global you want them to be. ## App data directory The `resources` directory relative to the directory containing the Camunda Modeler executable file. In our documentation we refer to it as `{APP_DATA_DIRECTORY}`. Resources in the app data directory will be found by any local Camunda Modeler instance. ### Examples ``` └── camunda-modeler-5.10.0-win-x64 ├── Camunda Modeler.exe └── resources ├── element-templates | └── my-element-templates.json └── plugins └── my-plugin └── index.js ``` :::note On macOS, the Camunda Modeler is a self-contained `.app` bundle, which makes it difficult to add files to its installation directory. Therefore, we recommend using the [user data directory](#user-data-directory) instead. ::: ``` └── camunda-modeler-5.10.0-linux-x64 ├── camunda-modeler └── resources ├── element-templates | └── my-element-templates.json └── plugins └── my-plugin └── index.js ``` ## User data directory The `camunda-modeler/resources` directory relative to the per-user application data directory, which by default points to: - `%APPDATA%` on [Windows](https://www.pcworld.com/article/2690709/whats-in-the-hidden-windows-appdata-folder-and-how-to-find-it-if-you-need-it.html) - `$XDG_CONFIG_HOME` or `~/.config` on [Linux](https://wiki.archlinux.org/index.php/XDG_user_directories) - `~/Library/Application Support` on macOS In our documentation we refer to it as `{USER_DATA_DIRECTORY}`. Resources in the user data directory will be found by all Camunda Modeler instances. ### Examples ``` └── %APPDATA% └── Roaming └── camunda-modeler └── resources ├── element-templates | └── my-element-templates.json └── plugins └── my-plugin └── index.js ``` ``` └── ~/Library/Application Support └── camunda-modeler └── resources ├── element-templates | └── my-element-templates.json └── plugins └── my-plugin └── index.js ``` ``` └── ~/.config └── camunda-modeler └── resources ├── element-templates | └── my-element-templates.json └── plugins └── my-plugin └── index.js ``` It is possible to change the user data directory using the `--user-data-dir` option via when starting Camunda Modeler from the command line. Refer to the [flags documentation](../flags) on how to configure the application with a flags file. --- ## Settings Configure and manage your Desktop Modeler application and plugin preferences and defaults via the **Settings** window. ## Manage Desktop Modeler settings Open the **Settings** window either from the application menu or by using the `Cmd/Ctrl + ,` keyboard shortcut. - Changes are saved automatically. If a restart is required to apply your changes, a warning is displayed. - Open additional documentation links for a setting by clicking the blue icon next to the setting name. ## Settings overview The following Desktop Modeler settings are available: | Setting | Description | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Enable new context pad | Enables the alternative context pad interface. | | Disable plugins | Disables all installed plugins when running Desktop Modeler. | | Disable connector templates | If checked, Desktop Modeler will not automatically download or use Camunda connector templates. See [automatic connector template fetching](../use-connectors/#automatic-connector-template-fetching) for details. | | Default Camunda 8 version | Sets the default Camunda 8 [execution platform version](/reference/glossary.md#execution-platform-version) for new diagrams. | | Default Camunda 7 version | Sets the default Camunda 7 [execution platform version](/reference/glossary.md#execution-platform-version) for new diagrams. | :::note Plugins may also provide their own settings in the **Settings** window. ::: ## Settings JSON file Your settings are saved in a `settings.json` file in the [user data directory](../search-paths/search-paths.md#user-data-directory). :::note Only the settings you have changed are stored in this file. ::: --- ## Start a new process instance 1. Ensure you have already set up and selected a connection by following the [Connect to Camunda 8](./connect-to-camunda-8.md) guide. ![Connection selector showing the Dev connection selected](img/connection-selector-dev-connection.png) 2. Click the play icon to start a new process instance. ![start instance icon](./img/start-instance-icon.png) 3. Enter optional variables for starting the process instance. Click **Start BPMN process instance** to deploy and start the instance. ![start instance overlay](./img/start-instance.png) A confirmation message confirms that the instance started successfully. ![start instance successful](./img/start-instance-successful.png) --- ## Task testing(Desktop-modeler) [Test a single task](../task-testing.md) in Desktop Modeler to quickly verify task logic, variable mappings, and configuration—without running the entire process. Task testing deploys the process and executes the selected task on the engine, as if it would be executed in a process flow. You control the context (process variables) that can verify if the outputs match your expectation. ## Prerequisites Task testing requires a **REST connection** to a Camunda instance running 8.8.0+. gRPC connections are not supported. You can set up a connection by following the [Connect to Camunda 8](./connect-to-camunda-8.md) guide. ## Testing a single activity 1. Select the task you want to test. 2. Open the **Task testing** tab in the bottom panel. ![Testing tab](./img/task-testing/testing-tab.png) ### Process variables In the left pane, define the [process variables](../../concepts/variables.md), the context the task will be executed in. As you type, the editor provides autocompletion for variables available in scope, including those generated by previous task testing runs. ### Task execution Click **Test task** to execute the selected activity. Your BPMN diagram will be saved, deployed, and a process instance will start automatically. :::note If you're working in a [project](./projects.md), all its resources will be deployed along with the process. ::: Once the process instance starts, a link to view it in Operate is provided. Task testing waits for the task to complete or for an incident to occur. ## Test results A test run can result in any of the following outcomes: ### Successful execution When the task completes successfully, the results panel displays the process variables _after_ execution. This includes variables created by the task, mapped through defined output mappings. ![Testing success](./img/task-testing/testing-success.png) ### Task incident If an incident occurs during execution, you’ll see its details along with the process variables. ![Testing incident](./img/task-testing/testing-incident.png) ### Error If the task execution fails due to an error, the response message is displayed. ## Related - [Test a task in Camunda Hub](../../hub/workspace/modeler/validation/task-testing.md) - [Learn about task testing concepts](../task-testing.md) - [Working with variables](../../concepts/variables.md) --- ## Telemetry You can opt out for the collection of telemetry data when using Desktop Modeler. This data is used to better understand how the application is used and to improve it based on data. This page summarizes the data collected. ## General structure of the events The events **Desktop Modeler** sends share a similar payload which usually (but not exclusively) includes information like: - **event name**: The name of the event triggered (e.g. `diagram:opened`) - **application version**: The version of Desktop Modeler being used (e.g. Version 5.0.0) - **editor id**: A randomly generated ID assigned to your Desktop Modeler installation ## Definition of events ### Ping event The `Ping Event` is sent in the following situations: - The modeler is opened (given that `Usage Statistics` option is enabled). - `Usage Statistics` option is enabled for the first time. - Once every 24 hours (given that `Usage Statistics` option is enabled). The `Ping Event` also sends the list of plugins installed and flags defined: ```json "plugins": ["PLUGIN_NAME"], "flags": { "FLAG_NAME": "FLAG_VALUE" } ``` ### Diagram opened/closed event The `Diagram Opened Event` is sent in the following situations: - User created a new BPMN diagram - User created a new DMN diagram - User created a new Form - User opened an existing BPMN diagram - User opened an existing DMN diagram - User opened an existing Form The `Diagram Closed Event` is sent in the following situations: - User closed a BPMN diagram - User closed a DMN diagram - User closed a Form These events include the following properties: - `diagramType`: BPMN, DMN, or Form - Engine profile: - `executionPlatform`: <target platform\> - `executionPlatformVersion`: <target platform version\> In the case of a form, the payload also includes the `formFieldTypes`: ```json "formFieldTypes": { "textfield": 6, "group": 2, "image": 4 } ``` ### Deployment and start instance events The `Deployment Event` is sent in the following situations: - User deploys a BPMN or DMN diagram to Camunda 7 or Camunda 8 - User deploys a Form to Camunda 7 The `Deployment Event` and `Start Instance` have the following properties: - `diagramType`: BPMN, DMN, or Form - Engine profile: - `executionPlatform`: <target platform\> - `executionPlatformVersion`: <target platform version\> In the event of an unsuccessful deployment, an `error` property will be present in the payload containing an error code. If provided, as is the case when deploying to a Zeebe-based platform, the payload also includes the target type of the deployment: ```json "targetType": "[camundaCloud or selfHosted]" ``` If the target engine profile is set in the diagram, the payload will also contain it. ```json "executionPlatform": "" ``` ### Tracked click events The `Tracked Click Events` are sent when a user clicks a link or button contained within a tracked parent 'container'. Currently, these containers are: - Each of the welcome page columns - The version info overlay The event supplies: - The `parent` container ID to locate the application section - The button label or link text (generalized as label) for identification of what was specifically clicked - A type to differentiate buttons, internal links, and external links - The link target (optional for external links) Example event: ```json { "type": "[button or external-link or internal-link]", "parent": "welcome-page-learn-more", "label": "Click here to read more about Camunda", "link": "https://camunda.com/" } ``` :::note `"link"` is only present for `"type": "external-link"`. ::: ### Overlay opened event The `Overlay Opened Event` is sent when an overlay is opened via user interaction. Currently, this event is sent in the following circumstances: - Version Info overlay is opened - Deployment overlay is opened - Start instance overlay is opened - Deployment overlay is closed - Start Instance overlay is closed For the **Version Info** overlay, the event also sends `source` of the click (`"menu"` or `"statusBar"`). For the **Deployment** and **Start Instance** overlays, the event also send the `diagramType` (BPMN, DMN or Form). ### Form editor events The `Form editor events` are sent on different interactions with the form builder: - User opened or collapsed a panel in the form editor. The event includes the current open state for each form preview panel and the interaction that triggered the change. ```json { "layout": { "form-input": { "open": true }, "form-output": { "open": true }, "form-preview": { "open": true } }, "triggeredBy": "keyboardShortcut|previewPanel|statusBar|windowMenu" } ``` - User interacted with the form input data panel. - User interacted with the form preview panel. In all events [the execution platform and version](#diagram-openedclosed-event) are sent as well. ### BPMN editor events BPMN editor events are sent on different interactions with the BPMN editor: - User created, appended, or replaced an element on the canvas. These events include the `oldElement`/`sourceElement` (when applicable) and the new element(s). - User selected, updated, unlinked, or removed an element template from an element via the properties panel on the right side of the screen. These events include the current diagram `selection` and respective element templates. - User interacted with the pop-up menu, context menu, or the palette. These events include the target (`entryId`, `entryGroup`, `entryTitle`) and `triggerType` (`"click"`, `"drag"`, or `"keyboard"`) of the interaction, as well as the current diagram `selection`. Example event: ```json { "entryId": "create.start-event", "entryGroup": "event", "entryTitle": "Start Event", "selection": [ { "id": "StartEvent_1", "type": "bpmn:StartEvent", "templateId": "template-start-event" } ], "triggerType": "keyboard" } ``` ### Task testing events Task testing events are sent when using the [task testing](../task-testing.md) feature: - Task execution is started. This event includes `elementType` and `elementTemplate` if applied. - Task testing deployment. [Deployment event](#deployment-and-start-instance-events) is triggered when the process is deployed during task testing. - Task execution finished. This event includes `elementType`, `elementTemplate`, `success` boolean value, and `incidentType` if task testing resulted in an [incident](../../../concepts/incidents.md). Example task testing finished event: ```json { "elementType": "bpmn:ServiceTask", "elementTemplate": "io.camunda.connectors.HttpJson.v2", "success": false, "incidentType": "JOB_NO_RETRIES" } ``` ### Variables panel events Variables panel events are sent on different interactions with the [Variables](../../data-handling.md#inspecting-variables) panel: - User opened the variables panel. - User closed the variables panel. - User filtered variables using the search input. The search term is not included in the event. - User expanded or collapsed a scope section. - User expanded or collapsed a variable row. - User copied a variable name via the copy button. - User copied a variable path or value via the context menu. These events do not include any additional payload data. ### Connection event The `Connection Event` is sent in the following situations: - Desktop Modeler fails to connect to [a configured Camunda instance](../connect-to-camunda-8.md). - Desktop Modeler connects to a configured Camunda instance. The `Connection Event` includes the following properties: - `success`: `true` or `false` - `targetType`: `SaaS` or `Self-Managed` - `isLocal`: `true` or `false` - `reason`: An error reason, or `null` Example connection event: ```json { "success": true, "targetType": "Self-Managed", "isLocal": true, "reason": null } ``` --- ## Troubleshooting(Desktop-modeler) ## How to start Desktop Modeler without plugins You can start Desktop Modeler with the [`--disable-plugins` flag](./flags/flags.md#disable-plug-ins). ## How to obtain Desktop Modeler logs Depending on your operating system, you can find Desktop Modeler logs in different places: ```plain %APPDATA%\camunda-modeler\logs ``` Example: ```plain C:\Users\Camunda\AppData\Roaming\camunda-modeler\logs ``` ```plain ~/Library/Logs/camunda-modeler ``` ```plain ~/.config/camunda-modeler/logs ``` To produce logging output, you can also run Desktop Modeler from the command line. ## I cannot connect to an orchestration cluster {#i-cannot-connect-to-zeebe} You try to connect (i.e., to deploy) to a remote orchestration cluster, and Desktop Modeler tells you it "Cannot connect to Camunda 8." :::tip If you run against a Camunda 8 SaaS free-trial cluster, ensure it is [not paused](/components/saas/clusters.md#auto-pause). ::: To resolve this issue, check if you can connect to Zeebe through another client, for example, community-supported [`zbctl`](https://github.com/camunda-community-hub/zeebe-client-go/blob/main/cmd/zbctl/zbctl.md). If that works, [further debug your Zeebe connection](#debug-zeebe-connection-issues). If that does not work, resolve the [general connection issue](#resolve-a-general-zeebe-connection-issue) first. Additionally, if authorizations are enabled, ensure that your [client](/components/admin/client.md) credentials have the required permissions. These differ from [user](/components/admin/user.md) credentials and are evaluated separately. ## I cannot connect to a local orchestration cluster {#i-cannot-connect-to-local-zeebe} You try to connect (i.e., to deploy) to a local orchestration cluster, and Desktop Modeler tells you it "Cannot connect to Camunda 8." Ensure your local orchestration cluster is running. If you don't have one installed, consider [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md), a lightweight all-in-one distribution. ## Cannot connect to an orchestration cluster in a local network Use this guidance when Desktop Modeler cannot connect to an orchestration cluster running in your local network and shows a "Cannot connect to Camunda 8" error. Verify that your operating system allows Desktop Modeler to access the local network. Ensure your network is set to **Private** and that apps are allowed to communicate on private networks. See [make a network public or private](https://support.microsoft.com/en-us/windows/essential-network-settings-and-tasks-in-windows-f21a9bbc-c582-55cd-35e0-73431160a1b9#ID0EFF). Ensure **Privacy & Security** settings allow Desktop Modeler to access your local network. See [control access to your local network](https://support.apple.com/en-gb/guide/mac-help/mchla4f49138/mac). ## How to configure a REST connection You try out [task testing](./task-testing.md) and Desktop Modeler tells you "Configure a REST connection to Camunda 8." Some features of Desktop Modeler, such as task testing, require a REST connection to Camunda 8. Orchestration clusters from version 8.6 support connections with gRPC or the newer [Orchestration Cluster REST API](../../../apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). Depending on the provided URL, the corresponding client will be used. Ensure you use the REST URL in your deployment configuration: - If you are using Camunda 8 SaaS clusters, create an [API client](../../saas/clusters/manage-api-clients.md) and use the value of `Camunda REST API`. - If you are using [Camunda 8 Run](../../../self-managed/quickstart/developer-quickstart/c8run.md), you should use the value of `Orchestration Cluster API`. :::tip Even if the URL starts with `http://`, it may still be a gRPC endpoint. Ensure you use the correct URL provided by your orchestration cluster. ::: ## Resolve a general Zeebe connection issue You try to connect to Zeebe from both Desktop Modeler _and_ community-supported [`zbctl`](https://github.com/camunda-community-hub/zeebe-client-go/blob/main/cmd/zbctl/zbctl.md), and neither of them works. General connection failures can have a couple of reasons: ### The (remote) orchestration cluster is not reachable {#the-remote-zeebe-instance-is-not-reachable} Ensure your computer has access to the (remote) network. :::tip If you run against a Camunda 8 SaaS free-trial cluster, ensure it is [not paused](/components/saas/clusters.md#auto-pause). ::: ### The connection to Zeebe happens through a proxy [Inspect the connection](#how-can-i-get-details-about-a-secure-remote-connection) to understand if it can be established. Secure connections to Zeebe require [HTTP/2 over TLS with protocol negotiation via ALPN](/self-managed/operational-guides/troubleshooting.md#zeebe-ingress-grpc). Ensure your proxy supports these features and does not forcefully downgrade the connection to HTTP/1. ### The connection to Zeebe should not happen through a proxy If you are using a proxy but do not want to connect to Zeebe through it, exclude Zeebe from proxying by adding it to the `NO_PROXY` environment variable: ```plain set NO_PROXY=localhost,127.0.0.1,some.intranet.host && "Camunda Modeler.exe" ``` ```plain NO_PROXY=localhost,127.0.0.1,some.intranet.host camunda-modeler ``` ```plain NO_PROXY=localhost,127.0.0.1,some.intranet.host camunda-modeler ``` ## Debug Zeebe connection issues You can connect to Zeebe via community-supported [`zbctl`](https://github.com/camunda-community-hub/zeebe-client-go/blob/main/cmd/zbctl/zbctl.md) or another API client. However, connecting through Desktop Modeler fails. ### Secure connection to Zeebe fails When connecting securely to Camunda 8 SaaS, Camunda 8 Self-Managed, or a standalone Zeebe instance (via `https` endpoint URL), Desktop Modeler tries to establish a secure connection. In the process, it strictly validates the server certificates presented against well-known certificate authorities. Failure to connect may have several reasons: #### The (remote) endpoint is not configured for secure connections Ensure you properly configured the remote endpoint. #### The (remote) endpoint presents an untrusted certificate [Inspect the connection](#how-can-i-get-details-about-a-secure-remote-connection) to understand which certificates are being returned by the server. Ensure you configure Desktop Modeler for [custom SSL certificates](#how-can-i-provide-a-custom-ssl-certificate). If intermediate signing authorities sign the server certificate, ensure the remote endpoint [serves both server and intermediate certificates](https://nginx.org/en/docs/http/configuring_https_servers.html#chains) to Desktop Modeler. ## How can I provide a custom SSL certificate? You configured a custom SSL certificate in your (remote) Zeebe endpoint and want Desktop Modeler to accept that certificate. The app [strictly validates](./flags/flags.md#zeebe-ssl-certificate) the remote server certificate trust chain. If you use a custom SSL server certificate, you must make the signing CA certificate known to Desktop Modeler, not the server certificate itself. Desktop Modeler reads trusted certificate authorities from your operating systems trust store. Installing custom CA certificates in that trust store is recommended for most users. Alternatively, you may provide custom trusted CA certificates via the [`--zeebe-ssl-certificate` flag](./flags/flags.md#zeebe-ssl-certificate). ## How can I get details about a secure remote connection? You can use the following command to retrieve information about HTTP/2 over TLS support (ALPN) and certificates provided by a remote endpoint: ```sh > openssl s_client -alpn h2 -connect google.com:443 -servername google.com [...] --- Certificate chain 0 s:/CN=*.google.com i:/C=US/O=Google Trust Services LLC/CN=GTS CA 1C3 1 s:/C=US/O=Google Trust Services LLC/CN=GTS CA 1C3 i:/C=US/O=Google Trust Services LLC/CN=GTS Root R1 2 s:/C=US/O=Google Trust Services LLC/CN=GTS Root R1 i:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA --- [...] --- New, TLSv1/SSLv3, Cipher is AEAD-CHACHA20-POLY1305-SHA256 Server public key is 256 bit Secure Renegotiation IS NOT supported Compression: NONE Expansion: NONE ALPN protocol: h2 SSL-Session: Protocol : TLSv1.3 Cipher : AEAD-CHACHA20-POLY1305-SHA256 Session-ID: Session-ID-ctx: Master-Key: Start Time: 1687516295 Timeout : 7200 (sec) Verify return code: 0 (ok) --- ``` ## How can I debug log gRPC / Zeebe communication? You can also start Desktop Modeler with gRPC logging turned on to get detailed [logging output](#how-to-obtain-desktop-modeler-logs) on communication to Zeebe: ```plain set DEBUG=* && set ZEEBE_NODE_LOG_LEVEL=DEBUG && set GRPC_VERBOSITY=DEBUG && set GRPC_TRACE=all && "Camunda Modeler.exe" ``` ```plain DEBUG=* ZEEBE_NODE_LOG_LEVEL=DEBUG GRPC_VERBOSITY=DEBUG GRPC_TRACE=all camunda-modeler ``` ```plain DEBUG=* ZEEBE_NODE_LOG_LEVEL=DEBUG GRPC_VERBOSITY=DEBUG GRPC_TRACE=all camunda-modeler ``` ## Desktop Modeler does not start on Ubuntu 24 / modern Linux Modern Linux operating systems introduce restrictions on user namespaces, a sandboxing (isolation) mechanism Modeler uses. You may see an error message when you start the application: ```sh $ ./camunda-modeler [46193:1114/170934.837319:FATAL:setuid_sandbox_host.cc(163)] The SUID sandbox helper binary was found, but is not configured correctly. Rather than run without sandboxing I'm aborting now. You need to make sure that [...]/camunda-modeler-[...]-linux-x64/chrome-sandbox is owned by root and has mode 4755. zsh: trace trap (core dumped) ./camunda-modeler ``` To remedy this, configure your system to allow sandboxing by [creating an AppArmor profile](https://github.com/camunda/camunda-modeler/issues/4695#issuecomment-2478458250), or review [this issue](https://github.com/camunda/camunda-modeler/issues/4695#issuecomment-2478581677) for an in-depth explanation of available options. If you don't have the necessary permissions to permit sandboxing, you may choose to disable the sandbox, though this is not recommended. ## Other questions? Head over to the [Modeler category on the forum](https://forum.camunda.io/c/modeler/6) to receive help from the community. --- ## Use connectors Camunda 8 only Use [Camunda 8 connectors](../../connectors/introduction.md) to access a growing range of external services or communication protocols. Desktop Modeler automatically fetches and updates [element templates](/components/modeler/element-templates/about-templates.md) for [pre-built connectors](../../connectors/out-of-the-box-connectors/available-connectors-overview.md) from the [Camunda Marketplace](https://marketplace.camunda.com/en-US/listing?pl=3038&attr=20486&cat=107792&locale=en-US). This happens automatically, in the background, unless explicitly [disabled](#disable-automatic-connector-template-fetching). connector templates can also be provided [manually](#add-connector-templates-manually), offering you full control over the building blocks provided by the modeler. ## Automatic connector template fetching Automatic connector template fetching is enabled by default, and notifies you of any updates or errors. The fetch is triggered whenever you start the application, or every 24 hours if the application is not closed. After an update check has concluded, a notification indicates if the templates are up to date or have been updated: ![Camunda connector templates up to date notification](./img/use-connectors/up-to-date.png) In case of an error you'll see a notification: ![Error updating Camunda connector templates notification](./img/use-connectors/error.png) Once fetched, you can use the templates in the Camunda 8 BPMN editor. ![Using Camunda connector templates in the Camunda 8 BPMN editor](./img/use-connectors/apply.png) ## Disable automatic connector template fetching Disable automatic fetching of connector templates using the `#disable-connector-templates` [feature flag](./flags/flags.md#disable-connector-templates). ## Add connector templates manually For full control over the building blocks offered by Desktop Modeler, download element templates for individual Camunda 8 connectors manually (for example, through the [Camunda Marketplace](https://marketplace.camunda.com/)). Make them available to Desktop Modeler via the element template [search paths](./element-templates/configuring-templates.md). ## Additional resources - [About Camunda 8 connectors](../../connectors/out-of-the-box-connectors/available-connectors-overview.md) - [About element templates](/components/modeler/element-templates/about-templates.md) - [Desktop Modeler flags](./flags/flags.md#disable-connector-templates) --- ## Utilize forms(Desktop-modeler) Beginner Time estimate: 15 minutes ## About The Camunda Forms feature allows you to easily design and configure forms. Once configured, they can be connected to a user task or start event to implement a task form in your application. After deploying a diagram with a linked form, Tasklist imports this form schema and uses it to render the form on every task assigned to it. ## Quickstart ### Create new form To start building a form in Desktop Modeler, click **File > New file > Form (Camunda 8)**. In this example, you'll build a form to help with a task in obtaining an email message. ### Build your form Within Forms, you can add text fields, numerical values, checkboxes, radio elements, selection menus, text components, and buttons. 1. From the **Components** palette on the left, drag and drop a **Text area** to the **Form definition** editor. 1. Select the **Text area**. 1. In the properties panel on the right, open the **General** section. 1. Provide the following data: - **Field label:** "Email content" - **Field description:** "The content of the email message" 1. Deselect the text area, so the form's properties are shown in the properties panel. 1. In the **General** section, copy the form's ID. You'll use this when you link the form to a user task. 1. Click **File > Save File As...** 1. Name the file `email-form`. :::tip Refer to the [camunda forms reference](/components/modeler/forms/camunda-forms-reference.md) to explore all form elements and configuration options in detail. ::: ### Link your form to a BPMN diagram Implement a task form into a diagram. In tandem, link your form to a user task or start event: 1. Open the diagram where you'd like to apply your form. 1. Select the user task requiring the help of a form. 1. In the properties panel on the right, in the **Form** section, select type **Camunda Form**. 1. For the **Form ID**, paste the ID you copied when you built your form. 1. Save your BPMN file. ## Deploy and run You need to deploy both the form and process before running a process instance. Deploy your form: 1. Open your form. 2. At the bottom of the view, configure a connection, and click the rocket-shaped **Deploy** icon to deploy your form. 3. Click **Deploy Form**. Deploy your process: 1. Open your BPMN process. 2. At the bottom of the view, configure a connection, and click the rocket-shaped **Deploy** icon to deploy your process. 3. Click **Deploy BPMN**. With both resources deployed, you can run a process instance. Click the play icon, then click **Start BPMN process instance**. You can now monitor your instances in [Operate](/components/operate/operate-introduction.md). To complete a user task, navigate to [Tasklist](/components/tasklist/introduction-to-tasklist.md). ## Additional resources - [Utilize forms in Camunda Hub](/components/hub/workspace/modeler/modeling/utilize-forms.md) - [User task reference](/components/modeler/bpmn/user-tasks/user-tasks.md) --- ## Decision literal expression ![Decision literal expression](assets/decision-literal-expression/decision-literal-expression.png) A decision literal expression represents decision logic which can be depicted as an expression. It consists of a [literal expression](#literal-expression) and a [variable](#variable). A decision literal expression is represented by a `literalExpression` element inside a `decision` XML element. ```xml calendar.getSeason(date) ``` ## Decision name ![Decision Name](assets/decision-literal-expression/decision-name.png) The name describes the decision for which the literal expression provides the decision logic. It is set as the `name` attribute on the `decision` element. ```xml ``` ## Decision id ![Decision Id](assets/decision-literal-expression/decision-id.png) The ID is the technical identifier of the decision. It is set in the `id` attribute on the `decision` element. Each decision should have an unique ID when it is deployed to Camunda. :::caution The decision ID may not contain any special characters or symbols (e.g. whitespace, dashes, etc.). The decision ID can be any alphanumeric string including the `_` symbol. For a combination of words, it's recommended to use the `camelCase` or the `snake_case` format. The `kebab-case` format is not allowed because it contains the operator `-`. If the decision ID contain a special character or symbol then the decision result can't be accessed in a [dependent decision](decision-requirements-graph.md#required-decisions). ::: ```xml ``` ## Literal expression The literal expression specifies how the value of the decision is generated. It can be used to do a complex calculation, or to combine the output values of [required decisions](decision-requirements-graph.md#required-decisions). The expression language of the literal expression is [FEEL](/components/modeler/feel/language-guide/feel-expressions-introduction.md). The expression is set inside a `text` element that is a child of the `literalExpression` XML element. ```xml calendar.getSeason(date) ``` ## Variable A decision literal expression must have a variable which specifies the name and the type of the decision result. A variable is represented by a `variable` element inside a `decision` XML element. ```xml ``` ### Variable name The name of the variable is used to reference the value of the literal expression in the decision result. It is specified by the `name` attribute on the `variable` XML element. :::caution The variable name may not contain any special characters or symbols (e.g. whitespace, dashes, etc.). The variable name can be any alphanumeric string including the `_` symbol. For a combination of words, it's recommended to use the `camelCase` or the `snake_case` format. The `kebab-case` format is not allowed because it contains the operator `-`. If the variable name contain a special character or symbol then the decision result can't be accessed in a [dependent decision](decision-requirements-graph.md#required-decisions). ::: :::tip It is recommended to use the decision ID as the variable name. In contrast to decision tables, the result of a decision literal expression can be accessed in a [dependent decision](decision-requirements-graph.md#required-decisions) by its variable name instead of its decision id. ::: ```xml ``` ### Variable type definition The type of the decision result can be specified by the `typeRef` attribute on the `variable` XML element. After the expression is evaluated it checks if the result converts to the specified type. The type should be one of the supported [data types](dmn-data-types.md). ```xml ``` Note that the type is not required but recommended since it provides a type safety of the expression result. --- ## Decision requirements graph ![Decision Requirements Graph](assets/decision-requirements-graph/drd.png) A Decision Requirements Graph (DRG) models a domain of decision-making, showing the most important elements involved in it and the dependencies between them. The elements modeled are [decisions](#decision), [input data](#input-data), and [knowledge sources](#knowledge-source). The visual representation of a DRG is called Decision Requirements Diagram (DRD). In the XML a DRG is represented by the `definitions` element. ```xml ``` ## Decision requirements graph name ![Decision Requirements Graph Name](assets/decision-requirements-graph/drg-name.png) The name describes the DRG. It is set as the `name` attribute on the `definitions` element. ```xml ``` ## Decision requirements graph id ![Decision Requirements Graph Id](assets/decision-requirements-graph/drg-id.png) The ID is the technical identifier of the DRG. It is set in the `id` attribute on the `definitions` element. Each DRG should have an unique ID when it is deployed to Camunda. ```xml ``` ## Decision ![Decision](assets/decision-requirements-graph/decision.png) A decision requirements graph can have one or more decisions. A decision has a [name](decision-table.md#decision-name) which is shown in the DRD and an [ID](decision-table.md#decision-id). The decision logic inside the decision must be either a [decision table](decision-table.md) or a [decision literal expression](decision-literal-expression.md). A decision is represented by a `decision` element inside the `definitions` XML element. ```xml ``` ## Required decisions ![Required Decision](assets/decision-requirements-graph/required-decision.png) A decision can have one or more required decisions which it depends on. A required decision is represented by a `requiredDecision` element inside an `informationRequirement` XML element. It has a `href` attribute and the value starts with `#` followed by the [decision ID](decision-table.md#decision-id) of the required decision. :::tip The result of a required decision can be accessed in the dependent decision by its decision ID. If the required decision is a decision table and has more than one output then the output values are grouped under the decision ID and can be accessed by their [output names](decision-table-output.md#output-name) ( e.g. `decisionId.outputName`). The structure of the result depends on the decision table [hit policy](decision-table-hit-policy.md). ::: ```xml ``` ## Input data ![Input Data](assets/decision-requirements-graph/input-data.png) An input data denotes information used as an input by one or more decisions. It is represented by an `inputData` element inside the `definitions` element. ```xml ``` Note that an input data has no execution semantics and is ignored on the evaluation. ## Knowledge source ![Knowledge Source](assets/decision-requirements-graph/knowledge-source.png) A knowledge source denotes an authority for a Decision. It is represented by a `knowledgeSource` element inside the `definitions` element. ```xml ``` Note that a knowledge source has no execution semantics and is ignored on the evaluation. --- ## Hit policy ![Hit Policy](assets/decision-table/hit-policy.png) A decision table has a hit policy that specifies what the results of the evaluation of a decision table consist of. The hit policy is set in the `hitPolicy` attribute on the `decisionTable` XML element. If no hit policy is set, then the default hit policy `UNIQUE` is used. ```xml ``` The following hit policies are supported: Hit Policy XML representation Unique UNIQUE Any ANY First FIRST Rule order RULE ORDER Collect COLLECT ## The role of a hit policy A hit policy specifies how many rules of a decision table can be satisfied and which of the satisfied rules are included in the decision result. The hit policies [Unique](#unique), [Any](#any) and [First](#first) will always return a maximum of one satisfied rule. The hit policies [Rule Order](#rule-order) and [Collect](#collect) can return multiple satisfied rules. ## Unique Only a single rule can be satisfied or no rule at all. The decision table result contains the output entries of the satisfied rule. If more than one rule is satisfied, the Unique hit policy is violated. The decision table is considered invalid, evaluation fails, and — in the context of a process instance — an incident is raised. Unique does **not** select the first matching rule; you must design the rules so that at most one can match for any given input. For example, the following table is valid under Unique because the seasons are mutually exclusive, so at most one rule can match: | Season | Dish | | ------ | ----------- | | Spring | Steak | | Summer | Light salad | | Autumn | Stew | | Winter | Roast beef | In contrast, a table with overlapping conditions such as: | Season | Temperature | Dish | | ------ | ----------- | ----------- | | Summer | > 20 | Light salad | | Summer | < 22 | Cold soup | is **not** safe under Unique. An input of `Summer` and `21` satisfies both rules, which violates the Unique hit policy and causes evaluation to fail. If you want the first matching rule to be selected instead, use the [First](#first) hit policy. ## Any Multiple rules can be satisfied. However, all satisfied rules must generate the same output. The decision table result contains only the output of one of the satisfied rules. If multiple rules are satisfied which generate different outputs, the hit policy is violated. Refer to the following example: ![Hit Policy Any](assets/decision-table/hit-policy-any.png) This is a decision table for the leave application. If the applier has no vacation days left or is currently in the probation period, the application will be refused. Otherwise the application is applied. ## First Multiple rules can be satisfied. The decision table result contains only the output of the first satisfied rule. ![Hit Policy First](assets/decision-table/hit-policy-first.png) Refer to the decision table above for advertisement. Regarding the current age of the user, which advertisement should be shown is decided. For example, the user is 19 years old. All the rules will match, but since the hit policy is set to first only, the advertisement for Cars is used. ## Rule order Multiple rules can be satisfied. The decision table result contains the output of all satisfied rules in the order of the rules in the decision table. ![Hit Policy Rule Order](assets/decision-table/hit-policy-rule-order.png) Again, refer to the advertisement example with the rule order policy. Say we have a user at the age of 19 again. All rules are satisfied so all outputs are given, ordered by the rule ordering. It can perhaps be used to indicate the priority of the displayed advertisements. ## Collect Multiple rules can be satisfied. The decision table result contains the output of all satisfied rules in an arbitrary order as a list. ![Hit Policy Collect](assets/decision-table/hit-policy-collect.png) With this hit policy, the output list has no ordering. So the advertisement will be arbitrary if, for example, the age is 19. Additionally, an aggregator can be specified for the Collect hit policy. If an aggregator is specified, the decision table result will only contain a single output entry. The aggregator will generate the output entry from all satisfied rules. :::info If the Collect hit policy is used with an aggregator, the decision table can only have one output. ::: The aggregator is set as the `aggregation` attribute of the `decisionTable` XML element. ```xml ``` ### Aggregators for collect In the visual representation of the decision table an aggregator can be selected in addition to the `COLLECT` hit policy. The following aggregators are supported: Visual representation XML representation Result of the aggregation Collect (Sum) SUM the sum of all output values Collect (Min) MIN the smallest value of all output values Collect (Max) MAX the largest value of all output values Collect (Count) COUNT the number of output values #### SUM aggregator The SUM aggregator sums up all outputs from the satisfied rules. ![Hit Policy Collect SUM](assets/decision-table/hit-policy-collect-sum.png) The showed decision table can be used to sum up the salary bonus for an employee. For example, the employee has been working in the company for 3.5 years. So the first, second and third rule will match and the result of the decision table is 600, since the output is summed up. #### MIN aggregator The MIN aggregator can be used to return the smallest output value of all satisfied rules. Refer to the following example of a car insurance. After years without a car crash the insurance fee will be reduced. ![Hit Policy Collect MIN](assets/decision-table/hit-policy-collect-min.png) For example, if the input for the decision table is 3.5 years, the result will be 98.83, since the first three rules match but the third rule has the minimal output. #### MAX aggregator The MAX aggregator can be used to return the largest output value of all satisfied rules. ![Hit Policy Collect MAX](assets/decision-table/hit-policy-collect-max.png) This decision table represents the decision for the amount of pocket money for a child. Depending of the age, the amount grows. For example, an input of 9 will satisfy the first and second rules. The output of the second rule is larger then the output of the first rule, so the output will be 5. A child at the age of 9 will get 5 as pocket money. #### COUNT aggregator The COUNT aggregator can be use to return the count of satisfied rules. ![Hit Policy Collect COUNT](assets/decision-table/hit-policy-collect-count.png) For example, refer to the salary bonus decision table again, this time with the COUNT aggregator. With an input of 4, the first three rules will be satisfied. Therefore, the result from the decision table will be 3, which means that after 4 years the result of the decision table is 3 salary bonuses. --- ## Input ![Input](assets/decision-table/input.png) A decision table can have one or more inputs, also called input clauses. An input clause defines the id, label, expression and type of a decision table input. An input can be edited by double-clicking on the respective colum header in the decision table. An input clause is represented by an `input` element inside a `decisionTable` XML element. ```xml season ``` ## Input ID The input ID is a unique identifier of the decision table input. It is used by Camunda to reference the input clause. Therefore, it is required. It is set as the `id` attribute of the `input` XML element. ```xml season ``` ## Input label ![Input Label](assets/decision-table/input-label.png) An input label is a short description of the input. It is set on the `input` XML element in the `label` attribute. Note that the label is not required but recommended, since it helps to understand the decision. ```xml season ``` ## Input expression ![Input Expression](assets/decision-table/input-expression.png) An input expression specifies how the value of the input clause is generated. It is usually simple and references a variable which is available during the evaluation. The expression language of the input expression is [FEEL](/components/modeler/feel/language-guide/feel-expressions-introduction.md). The expression is set inside a `text` element that is a child of the `inputExpression` XML element. ```xml season ``` ## Input type definition ![Input Type Definition](assets/decision-table/input-type-definition.png) The type of the input clause can be specified by the `typeRef` attribute on the `inputExpression` XML element. After the input expression is evaluated, it checks if the result converts to the specified type. The type should be one of the supported [data types](dmn-data-types.md). ```xml season ``` Note that the type is not required but recommended, since it helps to understand the possible input values and provides a type safety to be aware of unexpected input values. --- ## Output ![Output](assets/decision-table/output.png) A decision table can have one or more outputs, also called output clauses. An output clause defines the id, label, name and type of a decision table output. An output clause is represented by an `output` element inside a `decisionTable` XML element. ```xml ``` ## Output ID The output ID is a unique identifier of the decision table output. It is used by Camunda to reference the output clause. Therefore, it is required. It is set as the `id` attribute of the `output` XML element. ```xml ``` ## Output label ![Output Label](assets/decision-table/output-label.png) An output label is a short description of the output. It is set on the `output` XML element in the `label` attribute. Note that the label is not required but recommended, since it helps to understand the decision. ```xml ``` ## Output name ![Output Name](assets/decision-table/output-name.png) The name of the output is used to reference the value of the output. It is specified by the `name` attribute on the `output` XML element. If the decision table has more than one output, then all outputs must have a unique name. :::caution The output name may not contain any special characters or symbols (e.g. whitespace, dashes, etc.). The output name can be any alphanumeric string including the `_` symbol. For a combination of words, it's recommended to use the `camelCase` or the `snake_case` format. The `kebab-case` format is not allowed because it contains the operator `-`. If the output name contain a special character or symbol then the output can't be accessed in a [dependent decision](decision-requirements-graph.md#required-decisions) nor in a calling BPMN process. ::: :::tip If the decision table has only one output then it is recommended that the [decision ID](decision-table.md#decision-id) is used as the output name. The decision result can be accessed in a [dependent decision](decision-requirements-graph.md#required-decisions) by its decision id. Only if the decision table has more than one output then the output values are grouped under the decision id and can be accessed by their output names ( e.g. `decisionId.outputName`). ::: ```xml ``` ## Output type definition ![Output Type Definition](assets/decision-table/output-type-definition.png) The type of the output clause can be specified by the `typeRef` attribute on the `output` XML element. After an output entry is evaluated, it checks if the result converts to the specified type. The type should be one of the supported [data types](dmn-data-types.md). ```xml ``` Note that the type is not required but recommended, since it provides a type safety of the output values. --- ## Rule ![Rule](assets/decision-table/rule.png) A decision table can have one or more rules. Each rule contains input and output entries. The input entries are the condition and the output entries the conclusion of the rule. If each input entry (condition) is satisfied, then the rule is satisfied and the decision result contains the output entries (conclusion) of this rule. A rule is represented by a `rule` element inside a `decisionTable` XML element. ```xml "Winter" "Roastbeef" ``` ## Input entry ![Input Entry](assets/decision-table/input-entry.png) A rule can have one or more input entries, which are the conditions of the rule. Each input entry contains an expression in a `text` element as child of an `inputEntry` XML element. The expression language of the input entry is [FEEL](/components/modeler/feel/language-guide/feel-unary-tests.md) (unary-tests). The input entry is satisfied when the evaluated expression returns `true`. ```xml "Spring" ``` ### Empty input entry In case an input entry is irrelevant for a rule, the expression is empty, which is always satisfied. In FEEL, an empty input entry is represented by a `-`. ```xml ``` ## Output entry ![Output Entry](assets/decision-table/output-entry.png) A rule can have one or more output entries, which are the conclusions of the rule. Each output entry contains an expression in a `text` element as child of an `outputEntry` XML element. The expression language of the output entry is [FEEL](/components/modeler/feel/language-guide/feel-expressions-introduction.md). ```xml "Steak" ``` ## Description ![Description](assets/decision-table/description.png) A rule can be annotated with a description that provides additional information. The description text is set inside the `description` XML element. ```xml Save money ``` --- ## Overview(Dmn) ![Decision Table](assets/decision-table/dish-table.png) A decision table represents decision logic which can be depicted as a table. It consists of [inputs](decision-table-input.md), [outputs](decision-table-output.md) and [rules](decision-table-rule.md). A decision table is represented by a `decisionTable` element inside a `decision` XML element. ```xml ``` ## Decision name ![Decision Name](assets/decision-table/decision-name.png) The name describes the decision for which the decision table provides the decision logic. It is set as the `name` attribute on the `decision` element. It can be changed via the properties panel on the right side of the screen after selecting the respective "Decision" in the Decision Requirements Diagram view. ```xml ``` ## Decision ID ![Decision ID](assets/decision-table/decision-id.png) The ID is the technical identifier of the decision. It is set in the `id` attribute on the `decision` element. Just as the `name`, the `id` can be changed via the Properties Panel after selecting the respective "Decision" in the Decision Requirements Diagram view. Each decision should have an unique ID when it is deployed to Camunda. :::caution The decision ID may not contain any special characters or symbols (e.g. whitespace, dashes, etc.). The decision ID can be any alphanumeric string including the `_` symbol. For a combination of words, it's recommended to use the `camelCase` or the `snake_case` format. The `kebab-case` format is not allowed because it contains the operator `-`. If the decision ID contains a special character or symbol then the decision result can't be accessed in a [dependent decision](decision-requirements-graph.md#required-decisions). ::: ```xml ``` --- ## Data types Camunda supports the following data types for DMN: | Type name | Associated FEEL type | | ----------------- | --------------------------------------------------------------------------------------------------------- | | number | [Number](/components/modeler/feel/language-guide/feel-data-types.md#number) | | string | [String](/components/modeler/feel/language-guide/feel-data-types.md#string) | | boolean | [Boolean](/components/modeler/feel/language-guide/feel-data-types.md#boolean) | | time | [Time](/components/modeler/feel/language-guide/feel-data-types.md#time) | | date | [Date](/components/modeler/feel/language-guide/feel-data-types.md#date) | | dateTime | [Date-Time](/components/modeler/feel/language-guide/feel-data-types.md#date-time) | | dayTimeDuration | [Days-Time-Duration](/components/modeler/feel/language-guide/feel-data-types.md#days-time-duration) | | yearMonthDuration | [Years-Months-Duration](/components/modeler/feel/language-guide/feel-data-types.md#years-months-duration) | | Any | Wildcard for any type | The data types can be used in the type definitions of DMN elements, for example: - [Decision table input](decision-table-input.md#input-type-definition) - [Decision table output](decision-table-output.md#output-type-definition) - [Decision literal expression variable](decision-literal-expression.md#variable-type-definition) --- ## Modeling with DMN Decision Model and Notation (DMN) is a modeling approach owned by an institution called the Object Management Group ([OMG](https://www.omg.org/)), which also operates worldwide standards for [BPMN](/components/modeler/bpmn/automating-a-process-using-bpmn.md). In [DMN](/components/modeler/dmn/dmn.md), decisions are modeled and executed using a language both business analysts and developers can understand. Model a set of rules within a table, and this will yield a decision to rapidly execute a process using a decision engine like Camunda. ## Start modeling ![Start Modeling](assets/desktop-modeler-dmn/main.png) Camunda Desktop and Camunda Hub both offer the same Modeling experience for DMN 1.3 models: Modeling starts in the Decision Requirements Diagram (DRD) view. From there, you can add DMN elements from the palette on the left side by dragging and dropping them onto the diagram canvas. Alternatively, you can add new elements by using the context menu that appears when you select an element in the diagram. To change the type of an element, click on the element and select the **Change element** menu icon. Use the properties panel on the right side to change the name or ID of the DMN diagram. After creating a decision and morphing it into a decision table, you can start editing the table by clicking the overlay on the upper left corner of the decision. Using the overview in the decision table view, you can jump between decision tables. ## DMN coverage Camunda Hub and Desktop Modeler support the following DMN elements: - Decision (tables and literal expressions) - Input data - Knowledge source - Business knowledge model ## Decision tables ![Decision Table](assets/desktop-modeler-dmn/decision-table.png) By clicking the blue icon on a decision table, you can open the decision table view and start to edit it. Add **Input**, **Output**, and **Rule** elements by clicking the plus signs. Edit a table cell by clicking on it. Alternatively, the tabulator and enter keys can be used to walk through the table cells. Delete a rule or a column, copy, or insert a new rule by right clicking in the cell: ![Delete or copy rules](assets/desktop-modeler-dmn/dmn-modeler-right-click.png) Adjust the details of an input or output column (e.g., name, expression, and type) by double clicking in the header row: ![Change input or output column](assets/desktop-modeler-dmn/dmn-modeler-double-click.png) Jump between decision tables or literal expressions in your decision requirement diagram by opening and using the `Overview` on the left side: ![Jump between decision tables](assets/desktop-modeler-dmn/dmn-modeler-toggle-overview.png) ## Literal expressions ![New DMN Literal Expression](assets/desktop-modeler-dmn/literal-expression.png) You can also edit literal expressions. Just as with decision tables, in the decision requirement diagram view, click the blue icon to _drill-down_ into the literal expression view and start editing. ## Business knowledge models :::caution Viewing the result of BKM evaluation is currently not supported in Operate. ::: A _business knowledge model_ (BKM) is a reusable function containing a piece of decision logic. Typically, a BKM instantiates business logic that is required in multiple decisions, such as a common computation. For example, an amortization formula might be used in different loan application processes. You can make BKM elements executable using literal expressions written in FEEL, in almost the same way you would create a decision using a literal expression. A BKM literal expression can optionally accept parameters to be used as inputs to the FEEL expression, and it returns a single result whose name is the same as the BKM element name. Once you’ve created a BKM, it appears in autosuggestions when you’re using literal expressions to create decision logic. Currently, you can only reuse BKMs within the same decision. To create an executable BKM: 1. Add a BKM element to the canvas. 2. Name the BKM element. This name will be used for the result variable. 3. On the context menu, select **Change type > Literal expression**. 4. Click the blue literal expression icon (**{}**) on the BKM element. 5. Add parameters by hovering over the **()** and clicking the edit button. All changes to the BKM are saved automatically. 6. Enter a FEEL expression in the expression box. 7. Select a type for the result. --- ## Element templates(Element-templates) :::note Element templates are available only in BPMN diagrams. ::: Element templates let you extend your modeling toolkit with domain-specific diagram elements, such as service and user tasks. ## Creating and editing element templates Element templates are defined in template descriptor files using JSON format. Connector templates are a specific type of element template. You can edit element templates in Camunda Hub's built-in template editor or any text editor. Depending on the editor, the [JSON schema](defining-templates.md#json-schema-compatibility) may be recognized to provide additional editing support, such as formatting, code completion, and error highlighting. To learn more about customizing your templates, see [defining templates](./defining-templates.md). ## Next steps Read more about using element templates in Camunda Hub and Desktop Modeler: - [Using element templates in Camunda Hub](/components/hub/workspace/modeler/element-templates/using-templates.md) - [Sharing element templates in the Camunda Hub catalog](/components/hub/organization/manage-catalog/index.md) - [Using element templates in Desktop Modeler](/components/modeler/desktop-modeler/element-templates/using-templates.md) - [Element templates with dependencies](./element-template-with-dependencies.md) --- ## Additional resources Try playing around with custom elements and [our example templates](https://github.com/camunda/camunda-modeler/tree/master/resources/element-templates/cloud-samples.json). If you get stuck, ask for help in our [forums](https://forum.camunda.org/c/modeler). --- ## Defining templates An element template is defined in a template descriptor files as a JSON object. The element template object is divided into required and optional key-value pairs: ## Required keys - [`$schema : String`](./template-metadata.md#validation-schema): URI pointing towards the [JSON schema](https://json-schema.org/) which defines the structure of the element template `.json` file. Element template schemas are maintained in the [element templates JSON schema](https://github.com/camunda/element-templates-json-schema) repository. - [`id : String`](./template-metadata.md#identification-id-and-version): Identifier of the template. - [`name : String`](./template-metadata.md#discoverability-name-description-keywords-icon-documentationref-and-category): Name of the template. Shown in the element template selection modal and in the properties panel on the right side of the screen (after applying an element template). - [`appliesTo : Array`](./template-metadata.md#supported-bpmn-types-appliesto-and-elementtype): List of BPMN element types the template can be applied to. - [`properties : Array`](./template-properties.md): List of properties that the template applies to the BPMN element. Each property object defines the type of input and how its value is bound to a BPMN or Camunda extension property. ## Optional keys - [`version : Integer`](./template-metadata.md#identification-id-and-version): Property to support template versioning and upgrading. If you add a version to a template, it is considered unique based on its ID and version. - [`description : String`](./template-metadata.md#discoverability-name-description-keywords-icon-documentationref-and-category): Description of the template. Shown in the element template selection modal and in the properties panel (after applying an element template). - [`keywords : Array`](./template-metadata.md#discoverability-name-description-keywords-icon-documentationref-and-category): List of keywords. Helps users find the template through search. Keywords are used for search and filtering but are not displayed in the UI. - [`category : Object`](./template-metadata.md#discoverability-name-description-keywords-icon-documentationref-and-category): Category for the template. The template appears under the category name in the element template selection modal. - [`documentationRef : String`](./template-metadata.md#discoverability-name-description-keywords-icon-documentationref-and-category): URL pointing to the template's documentation. Shown in the properties panel (after applying an element template). - [`icon : Object`](./template-metadata.md#discoverability-name-description-keywords-icon-documentationref-and-category): Sets the template's icon. The icon is shown in the element template selection modal and in the properties panel (after applying an element template). - [`engines : Object`](./template-metadata.md#engine-compatibility-engines): Dictionary of environments compatible with the template. The environment version is specified using a semantic version range. - [`elementType : Object`](./template-metadata.md#supported-bpmn-types-appliesto-and-elementtype): Sets the type of the element. The element is replaced with the specified type when a user applies the template. - [`groups : Object`](./template-metadata.md#grouping-properties-groups): Defines groups of property input fields. Groups are sections in the properties panel. Properties can be assigned a group. - [`presets : Array`](./template-metadata.md#predefined-configurations-steps-and-presets): Defines reusable sets of property values. A preset is applied when a user selects its associated step. - [`steps : Array`](./template-metadata.md#predefined-configurations-steps-and-presets): Defines a hierarchical menu of predefined configurations shown when the template is applied. Each final step applies a preset. Some keys and values require other keys to be set. If your editor supports the [JSON schema](https://json-schema.org/), it will flag missing keys as errors. Camunda Hub's editor also shows these errors in the template editor problems panel. Here is an example element template with the most commonly used keys: ```json { "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", "name": "Template 1", "id": "sometemplate", "description": "some description", "keywords": ["search alias", "create action"], "version": 1, "engines": { "camunda": "^8.6" }, "appliesTo": ["bpmn:Task"], "elementType": { "value": "bpmn:ServiceTask" }, "properties": [ { "label": "Input Variable", "type": "String", "value": "someProcessVariable", "binding": { "type": "zeebe:input", "name": "anInputVariable" } } ] } ``` ## Creating multiple templates in one file You can define multiple templates in one JSON file by wrapping them in an array. :::warning This is a Desktop Modeler-specific feature. Camunda Hub requires each template to be in a separate file. ::: ```json [ { ... "name": "Template 1", "id": "some-template-id", "description": "some description", ... }, { ... "name": "Template 2", "id": "another-template-id", "description": "another description", ... } ] ``` ## Creating and editing templates You can create and edit element templates in the text editor of your choice. Connector templates are a specific type of element template, so the same applies to them. If your editor supports the [JSON schema](https://json-schema.org/), it will recognize the structure of the template and provide additional editing support, such as formatting, code completion, and error highlighting. [Camunda Hub](/components/hub/workspace/modeler/element-templates/manage-element-templates.md) offers a built-in template editor with validation and error highlighting, as well as a live preview of the properties panel with the applied template. ## Further reading For detailed information about specific aspects of template development, see: - **[Template metadata](./template-metadata.md)** - Learn more about template key-value pairs that help you version your template, make it discoverable, and allow you to define compatability. - **[Template properties](./template-properties.md)** - The properties array is the heart of the element template. Here you can define what properties should be applied to the BPMN element and how these properties should be shown and validated in the properties panel. - **[Example template](./template-example.md)** - A complete example showing how to create an element template. --- ## Element templates with dependencies When creating element templates, you may want to link to a resource like a [form](/components/modeler/forms/camunda-forms-reference.md), or pre-populate a [secret](/components/connectors/use-connectors/index.md#using-secrets) expression. Your template might require a specific [job worker](/components/concepts/job-workers.md) to execute an action. These are all examples of dependencies. ![Element template dependencies](./img/element-template-dependencies.png) Element templates can depend on: - [Camunda forms](/components/modeler/forms/camunda-forms-reference.md): used in user tasks. - [RPA scripts](/components/rpa/overview.md): used in service tasks. - [BPMN process](/components/modeler/bpmn/bpmn.md): used in a call activity. This may introduce nested dependencies (e.g., a called process may depend on other processes and/or resources). - [DMN decisions](/components/modeler/dmn/dmn.md): used in business rule tasks. - [Job workers](/components/concepts/job-workers.md): provide behavior for service tasks such as message send events, send tasks, service tasks, business rule tasks, or custom connector runtime. - Secrets: used in connector elements to access sensitive values (see [secrets in self-managed](/self-managed/components/connectors/connectors-configuration.md#secrets) and [secrets in SaaS](/components/saas/clusters/manage-secrets.md)). To make a template available for use, complete two key steps: 1. **Provision dependencies at runtime**: Make dependencies available in the clusters that need them. - For job workers, ensure the runtime is started and connected to the cluster (see [hosting custom connectors](/components/connectors/custom-built-connectors/host-custom-connector.md)). - Secrets must be configured beforehand. - Other dependency types (e.g., Camunda forms, RPA scripts, DMN decisions) need to be deployed to the cluster. 2. **Make the template available at design time**: Ensure Camunda Hub or Desktop Modeler can access the template for use in your projects. ## Next steps - [Defining element templates](./defining-templates.md) - [Publishing an element template in Camunda Hub](/components/hub/workspace/modeler/element-templates/manage-element-templates.md#publish-an-element-template) - [Using element templates in Camunda Hub](/components/hub/workspace/modeler/element-templates/using-templates.md) - [Configuring element templates in Desktop Modeler](/components/modeler/desktop-modeler/element-templates/configuring-templates.md) - [Using element templates in Desktop Modeler](/components/modeler/desktop-modeler/element-templates/using-templates.md) --- ## Example template This page provides a complete example of an element template that demonstrates how to define user-editable fields and their mapping to BPMN 2.0 XML and Camunda extension elements. ## REST connector template Let us consider the following example that defines a template for invoking a REST API via a service task: ```json { "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", "name": "REST connector", "id": "io.camunda.examples.RestConnector", "description": "A REST API invocation task.", "appliesTo": ["bpmn:ServiceTask"], "properties": [ { "type": "Hidden", "value": "http", "binding": { "type": "zeebe:taskDefinition", "property": "type" } }, { "label": "REST Credential", "description": "Choose a credential to use for the connection", "type": "Configuration", "configurationTemplate": "io.camunda.examples:connection:1", "binding": { "type": "zeebe:input", "name": "connection" } }, { "label": "REST Endpoint URL", "tooltip": "The url of the REST API to talk to.", "type": "String", "binding": { "type": "zeebe:taskHeader", "key": "url" }, "constraints": { "notEmpty": true, "pattern": { "value": "^https?://.*", "message": "Must be http(s) URL." } } }, { "id": "httpMethod", "label": "REST Method", "tooltip": "The HTTP method to use for the request.", "type": "Dropdown", "value": "get", "choices": [ { "name": "GET", "value": "get" }, { "name": "POST", "value": "post" }, { "name": "PATCH", "value": "patch" }, { "name": "DELETE", "value": "delete" } ], "binding": { "type": "zeebe:taskHeader", "key": "method" } }, { "label": "Request Body", "tooltip": "Data to send to the endpoint.", "value": "", "type": "String", "binding": { "type": "zeebe:input", "name": "body" }, "condition": { "oneOf": ["post", "patch", "delete"], "property": "httpMethod" } }, { "label": "Result Variable", "tooltip": "Name of variable to store the response data in.", "value": "response", "type": "String", "optional": true, "binding": { "type": "zeebe:output", "source": "= body" } } ], "configurationTemplates": [ { "id": "io.camunda.examples:connection:1", "name": "Connection", "version": 1, "kind": "CREDENTIAL", "properties": [ { "id": "connectionProperty", "label": "Connection property", "description": "A single connection value, for example an endpoint or token.", "type": "String", "binding": { "type": "property", "name": "connectionProperty" }, "constraints": { "notEmpty": true } } ] } ] } ``` ## How the example works The example defines six custom fields, each mapped to different technical properties: - **Task type**: The value `http` is mapped to the `type` property of a `zeebe:taskDefinition` element in BPMN 2.0 XML. This field is hidden from users since it's a technical requirement. - **REST Credential**: A [`Configuration`](./template-properties.md#configuration-input-type) property that lets users select or create a connection, locked to the embedded `io.camunda.examples:connection:1` configuration template. The chosen configuration is injected as the `connection` job variable. The referenced configuration template is embedded under the top-level [`configurationTemplates`](./template-metadata.md#embedding-configurations-configurationtemplates) key. - **REST Endpoint URL**: Mapped to a `task header` with the key `url`. This field includes validation to ensure it's a valid HTTP(S) URL. - **REST Method**: Mapped to a `task header` with the key `method`. Uses a dropdown to provide predefined HTTP method options. - **Request Body**: Mapped to a local variable via an `input parameter` named `body`. This field shown conditionally only. - **Result Variable**: Mapped into a process variable via an `output parameter`. The response data will be stored in the specified variable name. ## Visual result The task type is hidden to the user, as it is a technical implementation detail. The other properties specified in the template can be edited through the properties panel after the user has applied the template as shown in the following screenshot: ![Custom Fields](./img/overview.png) ## Key concepts demonstrated This example showcases several important template features: - **Hidden properties**: Setting technical values that users shouldn't modify. - **Input validation**: Using constraints to ensure valid URLs. - **Dropdown choices**: Providing predefined options for user selection. - **Optional bindings**: Fields that don't persist empty values in the XML. - **Variable mapping**: How to map data between the process and external systems. - **Configuration references**: Selecting a reusable configuration through an embedded configuration template. You can use this example as a starting point for creating your own element templates. --- ## Template metadata The metadata of an element template contains important information about the template itself, such as its name, description, version, compatibility with different Camunda versions, and the schema that is used to validate the template. ## Validation: `$schema` `$schema` is a required key-value pair and must be set. The application uses the `$schema` property to ensure compatibility for a given element template. You can find [the latest supported versions here](https://www.npmjs.com/package/@camunda/zeebe-element-templates-json-schema). The JSON schema versioning is backward-compatible, meaning that all versions including or below the current one are supported. :::info Camunda Hub only supports element templates pointing to the latest schema version: `https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json` ::: The Desktop Modeler ignores element templates defining a higher `$schema` version and logs a warning message. For example, given the following `$schema` definition, the application takes `0.9.1` as the JSON schema version of the element template: ```json { "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema@0.9.1/resources/schema.json", ... } ``` ## Identification: `id` and `version` - `id : String` is a required key and must be set. - `version : Integer` is optional but Camunda strongly recommends setting it. When using the [Camunda Hub catalog](/components/hub/organization/manage-catalog/getting-started.md), the version is required. The `id` key defines the identifier of the template. If no `version` is set, templates with the same `id` are regarded as equal, independent of their other key-value pairs. If `version` is set, the modeler treats templates with the same `id ` and `version` as identical, independent of their other key-value pairs. Thus, if you plan to make any changes to your template and want to support [template evolution](https://github.com/bpmn-io/element-templates/blob/main/docs/LIFE_CYCLE.md#overview), maintain a `version` key-value pair on your template. Templates with the same `id` and different `version` values offer an upgrade path. ```json { "id": "sometemplate", "version": 1, ... } ``` Once a template with a new `version` is available to users, the editor tooling suggests an upgrade, [preserving element configurations](https://github.com/bpmn-io/element-templates/blob/main/docs/LIFE_CYCLE.md#upgrade-behavior) on a best-effort basis. :::tip Versioning is an important cornerstone of template evolution. Review the [upstream documentation](https://github.com/bpmn-io/element-templates/blob/main/docs/LIFE_CYCLE.md#overview) to understand the foundations of our upgrade mechanism and of the element template lifecycle. ::: ## Discoverability: `name`, `description`, `keywords`, `icon`, `documentationRef`, and `category` - `name` is a required key and must be set. - `description`, `keywords`, `icon`, `documentationRef`, and `category` are optional key-value pairs. These keys define the user-facing metadata of the template. They help the template users to discover and understand the purpose of the template. They are shown when selecting a template and when the template has been applied to an element. - `name : String` defines the name of the template. - `description : String` provides additional information about the template. - `keywords : Array` list of keywords that can help users find this template. Keywords are used for search and filtering but are not displayed in the UI. - `icon : Object` defines the templates icon. The icon contents must be a valid [data](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) or HTTP(s) URL. We recommend using square icons as they are rendered at 18x18 pixels on the canvas and 32x32 pixels in the properties panel. - `documentationRef : String` URL pointing to the template's documentation. It is shown in the properties panel. - `category : Object` defines a category used to group templates in the element template selection list. If not defined, the template will be displayed in the **Templates** section. - `id : String` required key that defines the unique identifier of the category. - `name : String` required key that defines the name of the category that the template is shown in. It is generally a good idea to provide a proper description of you template. This helps users to understand the purpose of the template and how to use it. In case you require more space to explain the template, you can also provide a `documentationRef` pointing to a more detailed documentation page. This is particularly useful for templates that require external dependencies, such as custom connector implementations. Another good practice is to use a custom icon for your template. This helps users to quickly identify the template in the selection modal and in the properties panel. If you use the Camunda Hub's element template editor, you can upload an image and Camunda Hub will take care of encoding it as a data URL. ```json { ..., "name": "Template 1", "description": "some description", "keywords": [ "search alias", "create action" ], "icon": { "contents": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 22 22' fill='none'%3E%3Ccircle cx='11' cy='11' r='9' fill='black'/%3E%3Ctext x='6.9' y='14.9' fill='white' style='font-family: Arial; font-size: 10px;'%3EM%3C/text%3E%3C/svg%3E" }, "documentationRef": "https://example.com/docs/template-1", "category": { "id": "custom-templates", "name": "Custom Templates" }, ... } ``` ## Engine compatibility: `engines` - `engines : Object` is an optional key-value pair. - `camunda : SemanticVersion` is an optional key to define compatibility with Camunda Orchestration Cluster Versions. - `camundaDesktopModeler : SemanticVersion` is an optional key to define compatibility with Desktop Modeler versions. - `camundaWebModeler : SemanticVersion` is an optional key to define compatibility with Camunda Hub versions. Define [template compatibility](https://github.com/bpmn-io/element-templates/blob/main/docs/LIFE_CYCLE.md#compatibility) with execution platforms (Camunda Orchestration Cluster versions) and related components (such as Camunda Hub) using the `engines` key. This key has a dictionary object as its value, where the execution platform names are the keys, and the [semantic version](https://semver.org/) ranges are the values. For example, the following `engines` definition specifies that the template is compatible with Camunda 8.6 or higher. ```json { ..., "engines": { "camunda": ">8.5" } } ``` Compatibility is only validated if the platform version is provided by both the template and the modeler. In the example below, the template is compatible with the specified versions of both Desktop and Camunda Hub, but it requires Camunda version 8.6 or higher for both: ```json { ..., "engines": { "camunda": ">8.5", "camundaDesktopModeler": ">=5.30", "camundaWebModeler": "^8.5.5" } } ``` You can also use this feature to explicitly specify a template's incompatibility with a platform. For instance, the following template is incompatible with all versions of Camunda Hub: ```json { ..., "engines": { "camundaWebModeler": "0" } } ``` If no `engines` are specified, a template is considered compatible with any execution platform version. :::tip Review the [upstream documentation](https://github.com/bpmn-io/element-templates/blob/main/docs/LIFE_CYCLE.md#overview) to learn more about template evolution and the template lifecycle. ::: ## Supported BPMN types: `appliesTo` and `elementType` - `appliesTo` is a required key and must be set. - `elementType` is an optional key that can be required under some circumstances, see below for more information. These two key-value pairs define what BPMN types the template can be applied to (`appliesTo`) and whether the element is replaced with a different type when the template is applied (`elementType`). - `appliesTo : Array`: specifies the BPMN types the template can be applied to. The template will only be selectable for these types of elements in the modeler. Currently, element templates may be used on the following BPMN elements: - `bpmn:Activity` (including user tasks, service tasks, call activities, ad-hoc subprocesses, and others) - `bpmn:SequenceFlow` (for maintaining `condition`) - `bpmn:Process` - `bpmn:Event` - `elementType : Object`: If you configure `elementType` on a template, the element is replaced with the specified type when a user applies the template. - `value : String`: Is a required key. The BPMN element is changed to this type the template is applied. - `eventDefinition: String`: This key is used when templating an event. It can be ignored when templating any other element type. Supported values are: - `"bpmn:MessageEventDefinition"` use this value when you template a message event. - `"bpmn:SignalEventDefinition"` use this value when you template a signal event. - `"bpmn:TimerEventDefinition"` use this value when you template a timer event. Some properties require a specific BPMN type, and thus a specific value for `elementType`, to work correctly. For example, if the template sets `zeebe:calledDecision` on an element and `appliesTo` is set to `bpmn:Task`, the `elementType` must be set to `bpmn:BusinessRuleTask`. These constraints are checked based on the [element template schema](./template-metadata.md#validation-schema) by your editor (if it supports JSON schema) and by the modeler when it loads the templates. ```json { ..., "appliesTo": [ "bpmn:Task" ], "elementType": { "value": "bpmn:ServiceTask" } } ``` ## Grouping properties: `groups` - `groups` is an optional key. You can define `groups` to organize custom fields into. The fields will be shown in their assigned group in the properties panel. You can also specify whether a group is expanded or collapsed by default. This helps you to highlight important fields and to reduce visual clutter. Groups can have the following attributes: - `id : String`: Unique identifier of the group - `label : String`: Label of the group - `tooltip : String`: Tooltip for the group (optional) - `openByDefault : Boolean`: Whether the group will be expanded in the properties panel (optional, default: `false`) A property can be assigned to a group by setting the [`group` key](./template-properties.md#grouping-fields-group) to the group's `id` value. ```json { ..., "groups": [ { "id": "definition", "label": "Task definition", "openByDefault": true }, { "id": "request", "label": "Request payload" }, { "id": "result", "label": "Result mapping" }, { "id": "authentication", "label": "Authentication", "tooltip": "Optional authentication settings" } ], "properties": [ ... ] } ``` ## Predefined configurations: `steps` and `presets` - `steps` is an optional key. - `presets` is an optional key. Use `steps` and `presets` to offer several predefined configurations within a single template. A preset is a named set of property values, and `steps` define the menu users navigate to choose one. This is useful for templates that bundle multiple operations, such as a connector that can create an issue, list issues, or create a branch. When a template defines `steps`, applying the template opens a nested menu built from those steps instead of applying the template directly. Choosing a final step applies the template together with the property values of its referenced preset. While searching, the final steps surface directly so users can pick an operation without navigating the menu. Search matches a step by its `name`, `description`, and `keywords`, combined with those of its parent steps and of the template itself. Use `keywords` on a step to add the terms users search for when they think of the action rather than the product, such as `upload object` for a file storage operation. ### Defining presets with `presets` A preset is a reusable set of property values applied on top of the template's defaults. Leaf steps reference presets through `presetId`. - `presets : Array` defines the available presets. Each preset has the following attributes: - `id : String` is a required key that uniquely identifies the preset. It is referenced by a step's `presetId`. - `properties : Object` is a required key that maps template property names to the values applied when the preset is selected. These values are applied on top of the template's default property values. ### Defining the menu with `steps` `steps` define a hierarchical menu shown when the template is applied. A step is either a category or a final choice: - A category step has nested `steps` and groups other steps. Selecting it navigates into its child steps. - A leaf step references a preset through `presetId` and applies that preset when selected. A step must define either `steps` or `presetId`, but not both. - `steps : Array` defines the menu. Each step has the following attributes: - `name : String` is a required key that defines the step's label in the menu. - `description : String` is an optional key shown alongside the name. - `keywords : Array` is an optional key listing extra terms used to match the step in search. - `steps : Array` is the list of nested child steps. Defining this key marks the step as a category. - `presetId : String` references a preset by its `id`. Defining this key marks the step as a leaf. The following example defines a connector template with two categories, each containing two operations that map to a preset: ```json { ..., "steps": [ { "name": "Issues", "description": "Manage GitHub issues", "steps": [ { "name": "Create Issue", "description": "Create a new issue", "keywords": ["create issue", "open ticket"], "presetId": "createIssue" }, { "name": "List Issues", "description": "List issues in a repository", "keywords": ["list issues", "get issues"], "presetId": "listIssues" } ] }, { "name": "Branches", "description": "Manage GitHub branches", "steps": [ { "name": "List Branches", "keywords": ["list branches", "get branches"], "presetId": "listBranches" } ] } ], "presets": [ { "id": "createIssue", "properties": { "operationGroup": "issues", "issueOperationType": "createIssue" } }, { "id": "listIssues", "properties": { "operationGroup": "issues", "issueOperationType": "listIssues" } }, { "id": "listBranches", "properties": { "operationGroup": "branches", "branchOperationType": "listBranches" } } ] } ``` ## Embedding configurations: `configurationTemplates` - `configurationTemplates` is an optional key. An element template embeds the configuration template definitions referenced by its [`Configuration` properties](./template-properties.md#configuration-input-type) under the top-level `configurationTemplates` key. Each entry defines a configuration template that a `Configuration` property can lock to through its `configurationTemplate` key. - `configurationTemplates : Array` lists the embedded configuration templates. Each entry has the following attributes: - `id : String` is a required key that uniquely identifies the configuration template. It is referenced by a property's `configurationTemplate`. - `name : String` is a required key that defines the configuration template's display name. - `version : Integer` is a required key that defines the configuration template's version. - `kind : String` is a required key that identifies the kind of configuration, for example `CREDENTIAL`. It determines how the configuration is interpreted and where it can be used. For a worked example of a `CREDENTIAL` template, see [create a credential template](/components/connectors/custom-built-connectors/credential-templates.md). - `properties : Array` is a required key that defines the [properties](./template-properties.md) making up a configuration. Each property defines one field of the configuration's stored value; its `binding` names that field. ```json { ..., "configurationTemplates": [ { "id": "io.camunda.examples:connection:1", "name": "Connection", "version": 1, "kind": "CREDENTIAL", "properties": [ { "id": "connectionProperty", "label": "Connection property", "type": "String", "binding": { "type": "property", "name": "connectionProperty" } } ] } ] } ``` ## Deprecating a template: `deprecated` - `deprecated` is an optional key. Use `deprecated` to mark a [template as deprecated](https://github.com/bpmn-io/element-templates/blob/main/docs/LIFE_CYCLE.md#deprecation). :::info By deprecating an element template, you prevent its _future use_. Users will no longer see the template when choosing a template, changing an element's type, or creating a new element. Existing uses continue to work, with a deprecation hint shown in the modeler UI. ::: The `deprecated` property can be an object or a boolean. If an object, it has the following attributes: - `message: String`: A message to display to the user - `documentationRef : String`: A link to documentation, i.e., to describe upgrade migration procedures This information allows users to make sense of the deprecation and understand how to migrate to an undeprecated template: ```json { ..., "deprecated": { "message": "Migrate to My Other Custom Task", "documentationRef": "https://localhost/migrate-to-other" }, ... } ``` When `deprecated` is a boolean (`true`), a deprecation hint appears in the properties panel without further help for the user: ```json { ..., "deprecated": true, ... } ``` :::tip Review the [upstream documentation](https://github.com/bpmn-io/element-templates/blob/main/docs/LIFE_CYCLE.md#overview) to learn more about template evolution and the template lifecycle. ::: --- ## Template properties The `properties` array is where you define what properties should be applied to the BPMN element and how the properties panel will present these properties to the user when the template is applied. The underlying concept is very simple: 1. You will create one property object for each property that should be defined by the template. 2. Each property object contains key-value pairs to define how the property is presented and how its value can be changed by the user. 3. Each property object contains one `binding` object that specifies how the property is mapped to BPMN 2.0 XML. Element templates can only set properties that can be described by bindings supported by the element template schema. You can find the full list of supported bindings in the [bindings](#binding-an-input-to-a-bpmn-or-camunda-element-property-binding) section. When the user applies a template, the properties panel hides all BPMN 2.0 XML and Camunda extension element properties that can be defined by bindings in an element template. The template author must explicitly make the properties user-configurable to show them in the properties panel once the user has applied the template. For example, if a template does not contain a property object with the binding type `zeebe:input`, the template user will not be able to define an input mapping for the element once the template is applied. :::info Some properties — such as element documentation and multi-instance configurations — cannot be set by element templates. They are situational and require knowledge of the process context to be used. As they are never part of any element template, users can configure them independently of an applied template. ::: ## What is part of a property? You build your template by adding property objects to the `properties` array. The property object keys are divided into required and optional keys: ### Required keys - [`binding : Object`](#binding-an-input-to-a-bpmn-or-camunda-element-property-binding): An object specifying how the property is mapped to BPMN or Camunda extensions. ### Optional keys - [`type : "String" | "Text" | "Boolean" | "Dropdown" | "Hidden" | "Configuration"`](#setting-the-input-type-type): Defines the input type in the properties panel. - [`value : String | Number | Boolean`](#setting-a-default-value-value): The default value used if the bound property is not yet set by the user or if the type is `Hidden`. - [`generatedValue : Object`](#generating-a-value-generatedvalue): Configuration used to generate a value when the property is applied to an element. - [`placeholder : String`](#setting-a-text-placeholder-placeholder): Placeholder text shown in the input field when it is empty. - [`feel : "required" | "optional" | "static"`](#adding-feel-editor-support-feel): Defines whether the property supports FEEL expressions. - `label : String`: Label text shown above the property input. - `tooltip : String`: Tooltip text shown when hovering over the label. - `description : String`: Description text shown below the property input. - [`optional : Boolean`](#preventing-persisting-empty-values-optional): Controls whether properties persist empty values in the underlying BPMN 2.0 XML. - [`constraints : Object`](#validating-user-input-constraints): A list of editing constraints applied to the property value. - [`group : String`](#grouping-fields-group): The group that the property belongs to. - [`condition : Object`](#showing-properties-conditionally-condition): A condition that controls when the property is active and visible. - `id : String`: An identifier used to reference the property in conditional properties. - [`editable : Boolean`](#preventing-edits-editable): Controls whether the property is editable in the properties panel. - [`entriesVisible : Boolean | Object`](#control-visibility-of-default-properties-panel-entries-entriesvisible): Controls whether default properties are shown alongside properties defined in the element template. Not all keys and values are compatible with each other. Some keys or values require other keys to be set to a certain value, even if the key is marked as optional above. For more information, see the documentation below. If your editor (e.g. VS Code) offers validation based on JSON schema, these incompatibilities or missing key-value pairs are highlighted as you edit your template. Camunda Hub's element template editor offers an additional problems panel that shows these errors with additional descriptions to help you better understand what needs to be fixed. The Desktop Modeler shows these errors with additional descriptions in the output tab, when it tries to load an invalid template. For most purposes, `binding`, `label`, `type`, and `value` are sufficient to define a property. All property objects are defined inside the `properties` array: ```json { "schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", ..., "properties": [ { "label": "Some property", "type": "String", "binding": { ... } }, { "label": "Some other property", "type": "Number", "binding": { ... } }, ... ] } ``` For a comprehensive example showing how to create a REST connector template with all the concepts covered in this documentation, see the [complete template example](./template-example.md) page. The key-value pairs of the property object are explained in the following sections. ## Setting the input type: `type` The input types `String`, `Text`, `Number`, `Boolean`, `Dropdown`, `Hidden`, and `Configuration` are available. ### String input type The `String` type maps to a single-line input field. Refer to the [FEEL](#adding-feel-editor-support-feel) section to use `Strings` as an expression. ### Text input type The `Text` type maps to a multi-line text area. Refer to the [FEEL](#adding-feel-editor-support-feel) section to use `Text` as an expression. ### Hidden input type The `Hidden` type is not shown in the properties panel. It is used to set [static](#setting-a-default-value-value) or [generated](#generating-a-value-generatedvalue) values that should not be changed by the user. ### Number input type The `Number` type maps to a number input field. Refer to the [FEEL](#adding-feel-editor-support-feel) section to use `Numbers` as expressions. ### Boolean / checkbox input type The `Boolean` type maps to a checkbox that can be toggled by the user. When checked, it maps to `true` in the respective field. Additionally, refer to the [FEEL](#adding-feel-editor-support-feel) section to use `Booleans` as expressions. ### Dropdown input type The `Dropdown` type allows users to select from a number of pre-defined options that are stored in a `choices` array as `{ name : String, value : String }` pairs: ```json { "label": "REST Method", "description": "Specify the HTTP method to use.", "type": "Dropdown", "value": "get", "choices": [ { "name": "GET", "value": "get" }, { "name": "POST", "value": "post" }, { "name": "PATCH", "value": "patch" }, { "name": "DELETE", "value": "delete" } ], "binding": { "type": "zeebe:taskHeader", "key": "method" } } ``` The resulting properties panel control looks like this: ![properties panel drop down](./img/field-dropdown.png) ### Configuration input type The `Configuration` type renders a chooser for a reusable, host-provided configuration selected by reference. It locks to a single [configuration template](./template-metadata.md#embedding-configurations-configurationtemplates), so only compatible configurations are offered. A configuration is stored on the cluster as a cluster variable that is created with metadata identifying its kind (for example `CREDENTIAL`), its configuration template, and its version. This metadata lets the chooser find compatible configurations and lock to a single template. Selection and resolution happen in two phases: - **Design time (selection)**: The modeler queries the connected cluster for configurations whose metadata matches the property's `configurationTemplate` (and `configurationTemplateVersion` floor), then serializes a FEEL reference to the chosen one into the model, for example `=camunda.vars.env.myConnection`. - **Runtime (resolution)**: The engine resolves that FEEL reference against the live cluster variable and injects the configuration's value — the fields defined by its configuration template — into the bound variable (`connection` in the example below). Because it resolves live, changes to a configuration's values take effect on subsequent executions without redeployment. A configuration's field values can be plain literals or secret references (`camunda.secrets.*`) that the engine resolves at runtime, so secrets are never stored in the model. :::note Configurations are the mechanism that powers [Camunda credentials](/components/hub/organization/credentials/index.md): a credential is a configuration whose template `kind` is `CREDENTIAL`. Selecting one requires cluster access from the modeler at design time, consistent with existing connector templates. To define a credential type of your own, see [create a credential template](/components/connectors/custom-built-connectors/credential-templates.md). ::: It supports the following config-specific keys: - `configurationTemplate : String`: The `id` of the compatible configuration template. - `configurationTemplateVersion : Integer`: Optional. The minimum configuration template version a chosen configuration must satisfy (a floor). Omit for no minimum. The `Configuration` type does not introduce a new binding. It reuses the standard [`zeebe:input`](#input-mapping-zeebeinput) (outbound) or [`zeebe:property`](#extension-properties-zeebeproperty) (inbound) bindings. When a configuration is chosen, the modeler writes a FEEL reference to the selected configuration plus two modeler-only display-metadata attributes that are ignored at runtime: - `modelerConfigurationTemplate`: The bound configuration template `id`. - `modelerConfigurationName`: The chosen configuration's display name. ```xml ``` The resulting configuration chooser looks like this: ![properties panel configuration chooser](./img/field-configuration.png) For a complete example, refer to the [example template](./template-example.md). ## Setting a default value: `value` The `value` key defines a static default value for a property. The value is applied to the property defined by the [`binding`](#binding-an-input-to-a-bpmn-or-camunda-element-property-binding) when the template is applied to an element until a user provides their own input value. `value` should be defined whenever the input type is [`Hidden`](#hidden-input-type). The value of `value` must match the `type` of the property and must be a string if the `type` is hidden. ```json { "value": "4", "type": "Hidden", "binding": { "type": "zeebe:taskDefinition", "property": "retries" } } ``` ## Generating a value: `generatedValue` As an alternative to static `value`, you can use a generated value. The value is generated when a property is applied to an element. Currently, the generated value can be a UUID: ```json { "generatedValue": { "type": "uuid" }, "type": "Hidden", "binding": { "type": "zeebe:property", "name": "id" } } ``` ## Setting a text placeholder: `placeholder` The following property types support the `placeholder` attribute: - [`String`](#string-input-type) - [`Text`](#text-input-type) The placeholder is displayed when a field is empty: ```json { "label": "Web service URL", "type": "String", "binding": { ... }, "placeholder": "https://example.com" } ``` ## Adding FEEL editor support: `feel` The following input types support the `feel` property: - `String` - `Text` - `Number` - `Boolean` ### FEEL required The properties panel will display the field as a FEEL editor and will show a visual indication that a FEEL expression is required: ```json { "label": "Required FEEL Expression", "type": "String", "feel": "required", ... } ``` ### FEEL optional The properties panel will show an indicator to switch to a FEEL expression. When activated, the field displays as a FEEL editor: ```json { "label": "Optional FEEL Expression", "type": "String", "feel": "optional", ... } ``` For `Boolean` and `Number` fields, the value will always be persisted as a FEEL expression. This ensures that the value will not be interpreted as a string when evaluated in the engine. ### FEEL static The value of `feel: static` is only valid for `Boolean` and `Number` fields. Similar to [FEEL optional](#feel-optional), the value of the field will be persisted as a FEEL expression. However, there is no toggle to switch to a FEEL editor and only a static value can be entered: ```json { "label": "Static FEEL value", "type": "Number", "feel": "static", ... } ``` For binding types `zeebe:input` and `zeebe:output`, `feel: static` is the default value used in case of missing `feel` property. ## Binding an input to a BPMN or Camunda element property: `binding` The previous sections describe how to display a property to the user in the properties panel and how to configure its value. These inputs need to be mapped to the underlying BPMN 2.0 XML or Camunda extensions by using a `binding` object. The `binding` is an object with a mandatory `type` key and an additional parameter depending on the binding's `type` value. `binding.type` defines what kind of BPMN or Camunda element is targeted by the binding. The additional binding parameter is a key on the binding object--for example, `name`, `key`, or `property`. That key-value pair defines the target for the default [`value`](#setting-a-default-value-value) or user input. Note that adherence to the following bindings s is enforced by design. If the template does not adhere to them, the modeler logs a validation error and ignores the respective element template. To fully grasp the concept of bindings, it is helpful to have a good understanding of BPMN 2.0 XML and Camunda extensions. If you want to learn more about a certain BPMN element and its properties, you can read through the BPMN section on [Tasks](/components/modeler/bpmn/tasks.md), [Events](/components/modeler/bpmn/events.md), and [Subprocesses](/components/modeler/bpmn/subprocesses.md). Each page on an element contains a description of its properties and an example XML representation. :::info If a property cannot be set via any of the bindings described below, it cannot be set by an element template. For example, multi-instance configurations cannot be set by an element template. ::: :::warning If you add multiple properties with equal `binding` objects, the behavior is undefined. ::: The **mapping result** in the following section uses `[userInput]` to indicate where the input provided by the user in the properties panel is set in the BPMN XML. If the user provides no input, the value specified in [`value`](#setting-a-default-value-value) is displayed and used for `[userInput]`. Square brackets, `[]`, are used to indicate what the binding parameters are mapped to in the XML. ### Input mapping: `zeebe:input` | **Binding `type`** | `zeebe:input` | | -------------------------- | -------------------------------------------------------------------------------- | | **Valid property `type`s** | `String` `Text``Hidden``Dropdown``Boolean``Number` | | **Binding parameters** | `name`: The name of the input parameter | | **Mapping result** | `` | Configures a [task header](../../bpmn/service-tasks/#task-headers). ```json { ..., "value": "aHeaderValue", "binding": { "type": "zeebe:taskHeader", "key": "aHeaderKey" } } ``` ### Task definition: `zeebe:taskDefinition` | **Binding `type`** | `zeebe:taskDefinition` | | -------------------------- | --------------------------------------------------------------------------------- | | **Valid property `type`s** | `String` `Text``Hidden``Dropdown` | | **Binding parameters** | `property`: The name of the task definition property. Can be `type` or `retries`. | | **Mapping result** | `` | Configures the [task](../../bpmn/service-tasks/#task-definition) for a service or user task. ```json [ { ..., "value": "aTaskType", "binding": { "type": "zeebe:taskDefinition", "property": "type" } }, { ..., "value": "3", "binding": { "type": "zeebe:taskDefinition", "property": "retries" } }, ... ] ``` ### Task definition: `zeebe:taskDefinition:type` (deprecated) :::danger `zeebe:taskDefinition:type` is a deprecated binding. Instead, use `zeebe:taskDefinition` with `property=type`. ::: | **Binding `type`** | `zeebe:taskDefinition:type` | | -------------------------- | --------------------------------------------------- | | **Valid property `type`s** | `String` `Text``Hidden``Dropdown` | | **Binding parameters** | | | **Mapping result** | `` | Configures the [task type](../../bpmn/service-tasks/#task-definition) for a service or user task. ```json { ..., "value": "aTaskType", "binding": { "type": "zeebe:taskDefinition:type" } } ``` ### Extension properties: `zeebe:property` | **Binding `type`** | `zeebe:property` | | -------------------------- | ----------------------------------------------------- | | **Valid property `type`s** | `String``Text``Hidden``Dropdown` | | **Binding parameters** | `name`: The name of the property | | **Mapping result** | `` | Configures generic BPMN element properties that are text, boolean, and numeric types. Additionally, expression types `completionCondition` and `conditionExpression` are supported. Other properties, such as references and complex property types, are currently NOT supported and will lead to runtime errors when modeling. ```json [ { ..., "value": "= someValue >= 1", "binding": { "type": "property", "name": "completionCondition" } }, { ..., "value": "customPropertyValue", "binding": { "type": "property", "name": "mynamespace:customProperty" } }, ... ] ``` ### Message name: `bpmn:Message#property` | **Binding `type`** | `bpmn:Message#property` | | -------------------------- | -------------------------------------------------- | | **Valid property `type`s** | `String``Text``Hidden``Dropdown` | | **Binding parameters** | `name`: The name of the property | | **Mapping result** | `` | The `bpmn:Message#property` binding allows you to set properties of a `bpmn:Message` referred to by the templated element. This binding is only valid for templates of events with `bpmn:MessageEventDefinition`, receive tasks, and send tasks. ```json { ..., "generatedValue": { "type": "uuid" }, "binding": { "type": "bpmn:Message#property", "name": "name" } } ``` :::note When designing a template for a message receive task or event, it is sufficient to define the binding for the message [name](#message-name-bpmnmessageproperty) and the [correlation key](#message-correlation-key-bpmnmessagezeebesubscriptionproperty). The message ID is automatically generated when the template is applied. The `messageRef` [property](#primitive-bpmn-properties-property) does not have to be defined. Remember that the message [name and correlation key](/components/concepts/messages.md#message-subscriptions) define the correlation characteristics of a message and can be shared by [multiple process definitions](/components/concepts/messages.md#message-cardinality). ::: ### Message correlation key: `bpmn:Message#zeebe:subscription#property` | **Binding `type`** | `bpmn:Message#zeebe:subscription#property` | | -------------------------- | -------------------------------------------------- | | **Valid property `type`s** | `String``Text``Hidden``Dropdown` | | **Binding parameters** | `name`: The name of the property | | **Mapping result** | `` | The `bpmn:Message#zeebe:subscription#property` binding allows you to set properties of a `zeebe:subscription` set within `bpmn:Message` referred to by the templated element. This binding is only valid for templates of events with `bpmn:MessageEventDefinition` and receive tasks. ```json { ..., "value": "=aCorrelationKey", "binding": { "type": "bpmn:Message#zeebe:subscription#property", "name": "correlationKey" } } ``` :::note The binding name of `correlationKey` is not applicable to message start events on a process. In such cases, the property is automatically hidden. ::: ### Signal name: `bpmn:Signal#property` | **Binding `type`** | `bpmn:Signal#property` | | -------------------------- | ---------------------------------------------------------------------- | | **Valid property `type`s** | `String``Text``Hidden``Dropdown` | | **Binding parameters** | `name`: The name of the property. Supported properties: `name`. | | **Mapping result** | `` | The `bpmn:Signal#property` binding allows you to set the name of a `bpmn:Signal` referred to by the templated element. This binding is only valid for templates of events with `bpmn:SignalEventDefinition`. ```json { ..., "value": "aSignalName", "binding": { "type": "bpmn:Signal#property", "name": "name" } } ``` ### Timer event definition property name: `bpmn:TimerEventDefinition#property` | **Binding `type`** | `bpmn:TimerEventDefinition#property` | | -------------------------- | -------------------------------------------------------------------------------------------------------- | | **Valid property `type`s** | `String``Text``Hidden``Dropdown` | | **Binding parameters** | `name`: The name of the property.Supported properties: `timeDate`, `timeCycle`, and `timeDuration`. | | **Mapping result** | `[userInput]` | The `bpmn:TimerEventDefinition#property` binding allows you to configure [timer event definitions](../../bpmn/timer-events/) for timer events. This binding is only valid for templates of events with `bpmn:TimerEventDefinition` set via `elementType.eventDefinition`. Only one of `timeDate`, `timeCycle`, or `timeDuration` can be defined per template. ```json { ..., "value": "0 0 9-17 * * MON-FRI", "binding": { "type": "bpmn:TimerEventDefinition#property", "name": "timeCycle" } } ``` :::note **Property-specific constraints:** - **`timeDuration`**: Cannot be used on process-level start events. Valid on event subprocess start events, intermediate catch events, and boundary events. - **`timeCycle`**: Can only be used on start events and boundary events. When a template with `timeCycle` is applied: - Boundary events are automatically marked as non-interrupting. - Start events in event subprocesses are also marked as non-interrupting. - **`timeDate`**: Can be used on process-level and event subprocess start events, intermediate catch events, and boundary events. ::: ### Conditional event definition property: `bpmn:ConditionalEventDefinition#property` | **Binding `type`** | `bpmn:ConditionalEventDefinition#property` | | -------------------------- | ----------------------------------------------------------------------------------------------------------------- | | **Valid property `type`s** | `String``Text``Hidden` | | **Binding parameters** | `name`: The name of the property.Supported property: `condition`. | | **Mapping result** | `[userInput]` | The `bpmn:ConditionalEventDefinition#property` binding allows you to configure the condition expression for [conditional events](../../bpmn/conditional-events/). This binding is only valid for templates of events with `bpmn:ConditionalEventDefinition` set via `elementType.eventDefinition`. ```json { "label": "Condition Expression", "type": "String", "value": "=orderTotal > 100", "feel": "required", "binding": { "type": "bpmn:ConditionalEventDefinition#property", "name": "condition" } } ``` :::note The `condition` property requires a FEEL expression. When using `String` or `Text` input types, set `feel` to `required`. ::: ### Conditional filter: `bpmn:ConditionalEventDefinition#zeebe:conditionalFilter#property` | **Binding `type`** | `bpmn:ConditionalEventDefinition#zeebe:conditionalFilter#property` | | -------------------------- | ----------------------------------------------------------------------------- | | **Valid property `type`s** | `String``Text``Hidden` | | **Binding parameters** | `name`: The name of the property.Supported properties: `variableEvents`. | | **Mapping result** | `` | The `bpmn:ConditionalEventDefinition#zeebe:conditionalFilter#property` binding allows you to configure the conditional filter for [conditional events](../bpmn/conditional-events/conditional-events.md). The conditional filter controls which variable changes trigger the condition evaluation. ```json [ { "label": "Variable Events", "type": "String", "value": "create,update", "binding": { "type": "bpmn:ConditionalEventDefinition#zeebe:conditionalFilter#property", "name": "variableEvents" } } ] ``` :::note **Property descriptions:** - **`variableEvents`**: A comma-separated list of variable events (`create`, `update`) that trigger condition evaluation. When `bpmn:ConditionalEventDefinition#zeebe:conditionalFilter#property` is used, `bpmn:ConditionalEventDefinition#property` with `condition` should also be set on the same element. ::: ### Called element: `zeebe:calledElement` | **Binding `type`** | `zeebe:calledElement` | | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Valid property `type`s** | `String``Text``Hidden``Dropdown``Boolean` (only for the `propagateAllParentVariables` and `propagateAllChildVariables` properties) | | **Binding parameters** | `property`: The name of the property. Supported properties: `processId`, `bindingType`, `versionTag`, `propagateAllParentVariables`, `propagateAllChildVariables`. | | **Mapping result** | `` | The `zeebe:calledElement` binding allows you to configure a process called by a call activity. You can set the value of the property `bindingType` to control the [resource binding type](../../../best-practices/modeling/choosing-the-resource-binding-type). We recommend setting the property `bindingType` to the value `"versionTag"` and setting the property `versionTag` to the value of the version tag of the process you want to call. ```json [ { ..., "value": "aProcessId", "binding": { "type": "zeebe:calledElement", "property": "processId" } }, { ..., "value": "versionTag", "binding": { "type": "zeebe:calledElement", "property": "bindingType" } }, { ..., "value": "v1", "binding": { "type": "zeebe:calledElement", "property": "versionTag" } }, ... ] ``` #### Variable propagation You can control automatic variable propagation between the parent process and the called process by using the `propagateAllParentVariables` and `propagateAllChildVariables` properties. These properties support only the `Boolean` and `Hidden` types and do not support FEEL expressions. - `propagateAllParentVariables`: When you set this property to `true`, the engine copies all variables from the parent process to the called process. - `propagateAllChildVariables`: When you set this property to `true`, the engine copies all variables from the called process back to the parent process when the called process completes. ```json [ { ..., "type": "Boolean", "value": true, "binding": { "type": "zeebe:calledElement", "property": "propagateAllParentVariables" } }, { ..., "type": "Hidden", "value": "false", "binding": { "type": "zeebe:calledElement", "property": "propagateAllChildVariables" } }, ... ] ``` ### User task implementation: `zeebe:userTask` | **Binding `type`** | `zeebe:userTask` | | -------------------------- | --------------------------------------------------------------------------------------------------------------------- | | **Valid property `type`s** | `Hidden` | | **Binding parameters** | This is a flag-like binding, so it has no parameters and only applies to templates with element type `bpmn:UserTask`. | | **Mapping result** | `` | The `zeebe:userTask` binding allows you to configure the implementation type for a templated `bpmn:UserTask`. When present, it sets the task as a Camunda user task; when omitted, the task defaults to a job worker. ```json { "type": "Hidden", "binding": { "type": "zeebe:userTask" } } ``` ### Form: `zeebe:formDefinition` | **Binding `type`** | `zeebe:formDefinition` | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | **Valid property `type`s** | `String``Text``Hidden``Dropdown` | | **Binding parameters** | `property`: The name of the property. Supported properties: `formId`, `externalReference`, `bindingType`, and `versionTag`. | | **Mapping result** | `` | The `zeebe:formDefinition` binding allows you to configure the [user task form](../../bpmn/user-tasks/#user-task-forms) used by a user task. When setting the `formId` property, you can set the value of the property `bindingType` to control the [resource binding type](../../../best-practices/modeling/choosing-the-resource-binding-type). We recommend setting the property `bindingType` to the value `"versionTag"` and setting the property `versionTag` to the value of the version tag of the form you want to link. ```json [ { ..., "value": "aFormId", "binding": { "type": "zeebe:formDefinition", "property": "formId" } }, { ..., "value": "versionTag", "binding": { "type": "zeebe:formDefinition", "property": "bindingType" } }, { ..., "value": "v1", "binding": { "type": "zeebe:formDefinition", "property": "versionTag" } }, ... ] ``` :::note When `zeebe:formDefinition` is used, [`zeebe:userTask`](#user-task-implementation-zeebeusertask) must be set on the same element. Properties `formId` and `externalReference` are mutually exclusive, meaning that only one of them can be set at a time. The property `externalReference` cannot be used together with `bindingType`. ::: ### User task assignment: `zeebe:assignmentDefinition` | **Binding `type`** | `zeebe:assignmentDefinition` | | -------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | **Valid property `type`s** | `String``Text``Hidden``Dropdown` | | **Binding parameters** | `property`: The name of the property. Supported properties: `assignee`, `candidateGroups`, and `candidateUsers`. | | **Mapping result** | `` | The `zeebe:assignmentDefinition` binding allows you to configure the [user task assignment](../../bpmn/user-tasks/#assignments). ```json [ { ..., "value": "=manager", "binding": { "type": "zeebe:assignmentDefinition", "property": "assignee" } }, { ..., "value": "group1,group2", "binding": { "type": "zeebe:assignmentDefinition", "property": "candidateGroups" } }, { ..., "value": "user1,user2,user3", "binding": { "type": "zeebe:assignmentDefinition", "property": "candidateUsers" } }, ... ] ``` :::note When `zeebe:assignmentDefinition` is used, [`zeebe:userTask`](#user-task-implementation-zeebeusertask) must be set on the same element. ::: ### User task schedule: `zeebe:taskSchedule` | **Binding `type`** | `zeebe:taskSchedule` | | -------------------------- | --------------------------------------------------------------------------------------------- | | **Valid property `type`s** | `String``Text``Hidden``Dropdown` | | **Binding parameters** | `property`: The name of the property.Supported properties: `dueDate` and `followUpDate`. | | **Mapping result** | `` | The `zeebe:taskSchedule` binding allows you to configure the [user task scheduling](../../bpmn/user-tasks/#scheduling). ```json [ { ..., "value": "=dueDateVariable", "binding": { "type": "zeebe:taskSchedule", "property": "dueDate" } }, { ..., "value": "2025-10-14T12:00:00Z", "binding": { "type": "zeebe:taskSchedule", "property": "followUpDate" } }, ... ] ``` :::note When `zeebe:taskSchedule` is used, `zeebe:userTask` must be set on the same element. If the template sets a static `value` for `dueDate` or `followUpDate`, it must be defined as an ISO 8601 combined date and time representation. ::: ### User task priority: `zeebe:priorityDefinition` | **Binding `type`** | `zeebe:priorityDefinition` | | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | **Valid property `type`s** | `Number``String` (only with `feel` set to `required`)`Text` (only with `feel` set to `required`)`Hidden``Dropdown` | | **Binding parameters** | `property`: The name of the property.Supported property: `priority`. | | **Mapping result** | `` | The `zeebe:priorityDefinition` binding allows you to configure the [user task priority](../../bpmn/user-tasks/#define-user-task-priority). ```json { ..., "value": 42, "binding": { "type": "zeebe:priorityDefinition", "property": "priority" } } ``` :::note When `zeebe:priorityDefinition` is used, [`zeebe:userTask`](#user-task-implementation-zeebeusertask) must be set on the same element. If the template sets a static `value` for `priority`, it must be between 0 and 100. ::: ### Called decision: `zeebe:calledDecision` | **Binding `type`** | `zeebe:calledDecision` | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | **Valid property `type`s** | `String``Text``Hidden``Dropdown` | | **Binding parameters** | `property`: The name of the property. Supported properties: `decisionId`, `resultVariable`, `bindingType`, and `versionTag`. | | **Mapping result** | `` | The `zeebe:calledDecision` binding allows you to configure the [called decision](../../bpmn/business-rule-tasks/#defining-a-task) used by a business rule task. You can set the value of the property `bindingType` to control the [resource binding type](../../../best-practices/modeling/choosing-the-resource-binding-type). We recommend setting the property `bindingType` to the value `"versionTag"` and setting the property `versionTag` to the value of the version tag of the decision you want to call. ```json [ { ..., "value": "aDecisionId", "binding": { "type": "zeebe:calledDecision", "property": "decisionId" } }, { ..., "value": "aResultVariable", "binding": { "type": "zeebe:calledDecision", "property": "resultVariable" } }, { ..., "value": "versionTag", "binding": { "type": "zeebe:calledDecision", "property": "bindingType" } }, { ..., "value": "v1", "binding": { "type": "zeebe:calledDecision", "property": "versionTag" } }, ... ] ``` :::note When `zeebe:calledDecision` is used, `zeebe:taskDefinition` cannot be used on the same element. ::: ### Script: `zeebe:script` | **Binding `type`** | `zeebe:script` | | -------------------------- | ---------------------------------------------------------------------------------------------------- | | **Valid property `type`s** | `String``Text``Hidden``Dropdown` | | **Binding parameters** | `property`: The name of the property. Supported properties: `expression` and `resultVariable`. | | **Mapping result** | `` | The `zeebe:script` binding allows you to configure the [FEEL expression](../../bpmn/script-tasks/#defining-a-task) used by a script task. ```json [ { ..., "value": "= a + b", "binding": { "type": "zeebe:script", "property": "expression" } }, { ..., "value": "result", "binding": { "type": "zeebe:script", "property": "resultVariable" } }, ... ] ``` :::note When `zeebe:script` is used, `zeebe:taskDefinition` cannot be used on the same element. If the input `type` is `String` or `Text`, then [`feel`](#adding-feel-editor-support-feel) must be set to `required`" ::: ### Ad-hoc sub-processes: `zeebe:adHoc` | **Binding `type`** | `zeebe:adHoc` | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | **Valid property `type`s** | `Hidden` | | **Binding parameters** | `property`: The name of the property.Supported properties: `outputCollection`, `outputElement`, and `activeElementsCollection`. | | **Mapping result** | `` | The `zeebe:adHoc` binding marks a sub-process as ad-hoc when deployed to Zeebe. When configured, contained activities can be executed independently without following a predefined sequence flow. #### Example ```json { ..., "appliesTo": [ "bpmn:AdHocSubProcess" ], "elementType": { "value": "bpmn:AdHocSubProcess" }, "properties": [ ..., { "type": "Hidden", "binding": { "type": "zeebe:adHoc", "property": "outputCollection" }, "value": "results" }, { "type": "Hidden", "binding": { "type": "zeebe:adHoc", "property": "outputElement" }, "value": "={ id: results._meta.id, name: results._meta.name, content: results }" } ] } ``` #### Example with `activeElementsCollection` ```json { ..., "appliesTo": [ "bpmn:AdHocSubProcess" ], "elementType": { "value": "bpmn:AdHocSubProcess" }, "properties": [ { "type": "Hidden", "binding": { "type": "property", "name": "cancelRemainingInstances" }, "value": "false" }, { "type": "String", "feel": "required", "binding": { "type": "property", "name": "completionCondition" } }, { "type": "Hidden", "binding": { "type": "zeebe:adHoc", "property": "activeElementsCollection" }, "value": "=anActiveElementsCollection" } ] } ``` :::note The `zeebe:adHoc` binding can only be used with elements of type `bpmn:AdHocSubProcess`. The `outputCollection` property defines where ad-hoc execution results are collected, while `outputElement` specifies the structure of each result item. ::: ### Execution listener: `zeebe:executionListener` | **Binding `type`** | `zeebe:executionListener` | | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | **Valid property `type`s** | `Hidden` | | **Binding parameters** | `eventType`: The event type of the listener. Supported values: `start`, `end`.`retries`_(Optional)_: The number of retries for the listener. | | **Mapping result** | `` | With the `zeebe:executionListener` binding, you can configure [execution listeners](/components/concepts/execution-listeners.md) on any BPMN element that supports them. The `value` (or `generatedValue`) of the property sets the listener job type. ```json { ..., "entriesVisible": { "executionListeners": false }, "properties": [ { "type": "Hidden", "value": "my-start-listener-type", "binding": { "type": "zeebe:executionListener", "eventType": "start" } }, { "type": "Hidden", "value": "my-end-listener-type", "binding": { "type": "zeebe:executionListener", "eventType": "end", "retries": "3" } } ] } ``` :::note This binding only supports property `type` set to `Hidden`. You can't configure listeners through the properties panel; the template must fully define them. When using this binding, set `entriesVisible` with `executionListeners` to `false`. Combining user-defined and template-defined execution listeners isn't supported. ::: ### Task listener: `zeebe:taskListener` | **Binding `type`** | `zeebe:taskListener` | | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Valid property `type`s** | `Hidden` | | **Binding parameters** | `eventType`: The event type of the listener. Supported values: `creating`, `assigning`, `updating`, `completing`, `canceling`.`retries`_(Optional)_: The number of retries for the listener. | | **Mapping result** | `` | With the `zeebe:taskListener` binding, you can configure [task listeners](/components/concepts/user-task-listeners.md) on user tasks. The `value` (or `generatedValue`) of the property sets the listener job type. ```json { ..., "appliesTo": [ "bpmn:UserTask" ], "properties": [ { "type": "Hidden", "value": "my-completing-listener-type", "binding": { "type": "zeebe:taskListener", "eventType": "completing" } }, { "type": "Hidden", "value": "my-creating-listener-type", "binding": { "type": "zeebe:taskListener", "eventType": "creating", "retries": "3" } } ] } ``` :::note This binding only applies to elements of type `bpmn:UserTask`. This binding only supports property `type` set to `Hidden`. You can't configure listeners through the properties panel; the template must fully define them. ::: ## Setting a task implementation The following tasks support multiple implementation types: - User task: [`zeebe:userTask`](#user-task-implementation-zeebeusertask) and [`zeebe:taskDefinition`](#task-definition-zeebetaskdefinition) (most related properties--for example, [Assignment](#user-task-assignment-zeebeassignmentdefinition), [Task schedule](#user-task-schedule-zeebetaskschedule), and [Priority](#user-task-priority-zeebeprioritydefinition)--are only supported when `zeebe:userTask` is set) - Business rule task: [`zeebe:calledDecision`](#called-decision-zeebecalleddecision) and [`zeebe:taskDefinition`](#task-definition-zeebetaskdefinition) - Script task: [`zeebe:script`](#script-zeebescript) and [`zeebe:taskDefinition`](#task-definition-zeebetaskdefinition) You pick an implementation type by adding the respective binding with the respective type to your properties array. ## Setting a resource binding type The task types listed below that can reference external resources also let you define the [resource binding type](/components/best-practices/modeling/choosing-the-resource-binding-type.md): - Call activity: [`zeebe:calledElement`](#called-element-zeebecalledelement) - User task form: [`zeebe:formDefinition`](#form-zeebeformdefinition) - Business rule task: [`zeebe:calledDecision`](#called-decision-zeebecalleddecision) Setting a resource binding type helps you define what version of a resource (process, form, or decision) should be used during process execution. Camunda generally recommends using `versionTag` as the resource binding type. This helps to ensure that only resources with a matching `versionTag` are used during process execution. By default, the binding type is `latest`, meaning that the latest deployed version of a resource is used whenever the task is executed. Using `latest` in combination with element templates bears the risk that a resource can be changed in an incompatible way. This can make invocations with old templates fail, for example, because an input mapping is missing from an old template, but is required by the updated resource. The binding type can be set like so: ```json { "properties": [ ..., { "type": "Hidden", "value": "versionTag", // set binding type to versionTag "binding": { "type": "zeebe:calledElement", // or zeebe:formDefinition or zeebe:calledDecision "property": "bindingType" } }, { "type": "Hidden", "value": "v1", // set the version tag to use "binding": { "type": "zeebe:calledElement", "property": "versionTag" } } ] } ``` As you can see in the example above, the properties `bindingType` and `versionTag` are of type `Hidden`. This is because these properties should generally not be changed by the user, unless the template author has very good reasons to allow this. Should an update to the resource be necessary, you can create a new version of the template that uses a different version tag. For further information, see the section on [element templates with dependencies](./element-template-with-dependencies.md). ## Preventing persisting empty values: `optional` We support optional bindings that do not persist empty values in the underlying BPMN 2.0 XML. If a user removes the value from the input field in the properties panel, it will also remove the mapped element. This works as follows: - `true`: When the user removes the value (or leaves the field empty), the empty values are NOT persisted. - `false`: When the user removes the value, the empty values ARE persisted in the XML. This is the default behavior. The following binding types can be `optional`: - [`zeebe:input`](#input-mapping-zeebeinput) - [`zeebe:output`](#output-mapping-zeebeoutput) - [`zeebe:taskHeader`](#header-zeebetaskheader) - [`zeebe:property`](#extension-properties-zeebeproperty) Example: ```json { "label": "Request", "type": "String", "optional": true, "binding": { "type": "zeebe:input", "name": "request" } } ``` ## Grouping fields: `group` Associate a field with a group (ID) via the field's `group` key: ```json { ..., "groups": [ { "id": "definition", "label": "Task definition", "openByDefault": true } ], "properties": [ { "group": "definition", "label": "Task type", "type": "String", "value": "http-job", "binding": { "type": "zeebe:taskDefinition", "property": "type" } }, ... ] } ``` ## Validating user input: `constraints` Custom fields may have a number of constraints associated with them: - `notEmpty : Boolean`: Input must be non-empty, when set to `true`. - `minLength : Integer`: Minimum length for the input. - `maxLength : Integer`: Maximum length for the input. - `pattern : Object`: Regular expression to match the input against. ### Validating against a regex: `pattern` Set `pattern` to a regular expression to ensure the user's input matches the pattern. Together with the `pattern` constraint, you can define a custom error message: ```json { "label": "Web service URL", "type": "String", "binding": { ... }, "constraints": { "notEmpty": true, "pattern": { "value": "https://.*", "message": "Must be https URL" } } } ``` :::warning When a template exposes a property to a user, the template is responsible for showing all validation errors in the properties panel. That includes non-compliance with BPMN and Zeebe schema constraints. You should therefore use `notEmpty` where necessary. The problems panel shows errors for invalid properties, whether or not a template is applied. ::: ## Showing properties conditionally: `condition` Properties may have a condition which determines when they should be active, depending on the value of another property. For example for a HTTP request template, you might not want to show the `Request Body` property when the HTTP method is `GET`. When a property is **active**, it is displayed in the properties panel, and its value is serialized in the XML. If a property is **not active**, it is not displayed, and its value is removed from the XML. For a property value to be used in a condition, the property needs to have an `id` that can be referenced by the conditional property. A property can depend on one or more conditions. If there are multiple conditions, they can be defined using `allMatch`. All the conditions must be met for the property to be active. There are four possible comparison operators: - `equals`: Checks if the value is equal to the value defined in the condition. - `oneOf`: Checks if the value is in the list of values defined in the condition. - `isActive`: Checks if the referenced property is currently active and not hidden by other conditions. - `isEmpty`: Checks if the referenced property's value is empty (empty value or unconfigured). ```json [ { "id": "httpMethod", "label": "HTTP Method", "type": "Dropdown", "choices": [ { "name": "get", "value": "GET" }, { "name": "patch", "value": "PATCH" }, { "name": "post", "value": "POST" } ], "binding": { ... } }, { "label": "Request Body", "type": "String", "binding": { ... }, "condition": { "allMatch": [ { "property": "httpMethod", "oneOf": [ "patch", "post" ] }, { "property": "...", "isActive": "true" }, { "property": "...", "equals": "someValue" }, { "property": "...", "isEmpty": true } ] } }, ... ] ``` ## Preventing edits: `editable` By default, all properties defined in an element template that do not have type `Hidden` are editable. You can prevent edits by setting the `editable` property to `false`. The property will be displayed in the properties panel but cannot be changed. ```json { "label": "Task type", "type": "String", "value": "http-job-type", "editable": false, "binding": { "type": "zeebe:taskDefinition", "property": "type" } } ``` ## Control visibility of default properties panel entries: `entriesVisible` By default, an applied element template causes most properties panel sections to be hidden — element template-defined bindings take precedence over standard groups and entries. This behavior can be customized through the `entriesVisible` property. ### Control specific entry visibility By passing an object to `entriesVisible`, you can override the default display of certain properties panel sections. The key of that object is the ID of a section, the value is a boolean that defines the visibility status. The table below lists what entries may be customized and their default visibility status: | Key | Description | Default visible | | :------------------- | :-------------------------- | :-------------- | | `outputs` | Output mapping section | `false` | | `executionListeners` | Execution listeners section | `true` | | `taskListeners` | Task listeners section | `false` | To show the standard output mapping section, configure your template as shown below: ```json [ { "name": "Template 1", "id": "sometemplate", "entriesVisible": { "outputs": true }, "appliesTo": [ "bpmn:ServiceTask" ], "properties": [ ... ] } ] ``` ### Displaying all entries To show all standard properties panel entries, set `entriesVisible=true`: ```json [ { "name": "Template 1", "id": "sometemplate", "entriesVisible": true, "appliesTo": [ "bpmn:ServiceTask" ], "properties": [ ... ] } ] ``` :::warning As an element template author, you are responsible for ensuring the default sections opened do not conflict with any bindings defined by the element template. ::: --- ## AI agent functions ## fromAi(value) Returns the unmodified `value` parameter, which must be a FEEL variable reference in the form `toolCall.` (for example, `toolCall.userId`). - The purpose of this function is solely to tag the value as being generated by an AI integration. - The actual handling is not performed by the FEEL engine, but by a custom integration such as a connector or a job worker. The main use case of this function is for [tool definitions](../../../connectors/out-of-the-box-connectors/agentic-ai-aiagent-tool-definitions.md) used by the [AI Agent connector](../../../connectors/out-of-the-box-connectors/agentic-ai-aiagent.md). See the following function overloads for additional function parameters. **Function signature** ```feel fromAi(value: Any): Any ``` **Examples** ```feel fromAi(toolCall.searchQuery) // toolCall.searchQuery contents fromAi(toolCall.userId) // toolCall.userId contents ``` ## fromAi(value, description) Returns the unmodified `value` parameter, which must be a FEEL variable reference in the form `toolCall.` (for example, `toolCall.userId`). In addition to the previous overload, it also accepts an optional `description` parameter to provide a textual description of the value. The description must be `null` or a string constant. **Function signature** ```feel fromAi(value: Any, description: string): Any ``` **Examples** ```feel fromAi(toolCall.searchQuery, "The search query used to find the best match.") // toolCall.searchQuery contents fromAi(toolCall.searchQuery, null) // toolCall.searchQuery contents ``` ## fromAi(value, description, type) Returns the unmodified `value` parameter, which must be a FEEL variable reference in the form `toolCall.` (for example, `toolCall.userId`). In addition to the previous overload, it also accepts an optional `type` parameter to provide type information about the value. The type must be `null` or a string constant. **Function signature** ```feel fromAi(value: Any, description: string, type: string): Any ``` **Examples** ```feel fromAi(toolCall.searchQuery, "The search query used to find the best match.", "string") // toolCall.searchQuery contents fromAi(toolCall.userId, "The user's ID", "number") // toolCall.userId contents fromAi(toolCall.userId, null, "number") // toolCall.userId contents fromAi(value: toolCall.userId, type: "number") // toolCall.userId contents ``` ## fromAi(value, description, type, schema) Returns the unmodified `value` parameter, which must be a FEEL variable reference in the form `toolCall.` (for example, `toolCall.userId`). In addition to the previous overload, it also accepts an optional `schema` parameter to provide a (partial) [JSON schema](https://json-schema.org/) for the value. - The schema must be `null` or a context (map) containing only constant values. For example, function calls within the schema are not supported. - The schema is not validated by the FEEL engine but might be by a custom integration consuming the information. - From the engine side it is possible to specify both a `type` and a `schema`, and it depends on the integration as to which value takes precedence. The [AI Agent connector](../../../connectors/out-of-the-box-connectors/agentic-ai-aiagent.md) will override any type specified in the schema if the `type` parameter is also provided. **Function signature** ```feel fromAi(value: Any, description: string, type: string, schema: context): Any ``` **Examples** ```feel fromAi(toolCall.documentType, "The document type to provide", "string", { enum: ["invoice", "receipt", "contract"] }) // toolCall.documentType contents fromAi(value: toolCall.documentType, description: "The document type to provide", schema: { type: "string", enum: ["invoice", "receipt", "contract"] }) // toolCall.documentType contents fromAi(toolCall.tags, "Tags to apply to the blog post", "array", { items: { type: "string" } }) // toolCall.tags contents ``` ## fromAi(value, description, type, schema, options) Returns the unmodified `value` parameter, which must be a FEEL variable reference in the form `toolCall.` (for example, `toolCall.userId`). In addition to the previous overload, it also accepts an optional `options` parameter to provide additional options for the integration handling the value definition. - The options parameter must be `null` or a context (map) containing only constant values. For example, function calls within options are not supported. **Function signature** ```feel fromAi(value: Any, description: string, type: string, schema: context, options: context): Any ``` **Examples** ```feel fromAi(toolCall.documentType, "The document type to provide", "string", null, { required: false }) // toolCall.documentType contents fromAi(value: toolCall.documentType, options: { required: false }) // toolCall.documentType contents ``` --- ## Boolean functions ## not(negand) Returns the logical negation of the given value. **Function signature** ```feel not(negand: boolean): boolean ``` **Examples** ```feel not(true) // false not(null) // null ``` ## is defined(value) Checks if a given value is not `null`. If the value is `null` then the function returns `false`. Otherwise, the function returns `true`. The function requires one argument. Calling `is defined()` without an argument is invalid. **Function signature** ```feel is defined(value: Any): boolean ``` **Examples** ```feel is defined(1) // true is defined(null) // false is defined(x) // false - if no variable "x" exists is defined(x.y) // false - if no variable "x" exists or it doesn't have a property "y" is defined() // error - expected one argument ``` :::caution Breaking change This function worked differently in previous versions. It returned `true` if the value was `null`. Since this version, the function returns `false` if the value is `null`. ::: ## get or else(value, default) Return the provided value parameter if not `null`, otherwise return the default parameter **Function signature** ```feel get or else(value: Any, default: Any): Any ``` **Examples** ```feel get or else("this", "default") // "this" get or else(null, "default") // "default" get or else(null, null) // null ``` ## assert(value, condition) Verify that the given condition is met. If the condition is `true`, the function returns the value. Otherwise, the evaluation fails with an error. **Function signature** ```feel assert(value: Any, condition: Any) ``` **Examples** ```feel assert(x, x != null) // "value" - if x is "value" // error - if x is null or doesn't exist assert(x, x >= 0) // 4 - if x is 4 // error - if x is less than zero ``` ## assert(value, condition, cause) Verify that the given condition is met. If the condition is `true`, the function returns the value. Otherwise, the evaluation fails with an error containing the given message. **Function signature** ```feel assert(value: Any, condition: Any, cause: String) ``` **Examples** ```feel assert(x, x != null, "'x' should not be null") // "value" - if x is "value" // error('x' should not be null) - if x is null or doesn't exist assert(x, x >= 0, "'x' should be positive") // 4 - if x is 4 // error('x' should be positive) - if x is less than zero ``` --- ## Context functions ## get value(context, key) Returns the value of the context entry with the given key. **Function signature** ```feel get value(context: context, key: string): Any ``` **Examples** ```feel get value({foo: 123}, "foo") // 123 get value({a: 1}, "b") // null ``` ## get value(context, keys) Returns the value of the context entry for a context path defined by the given keys. If `keys` contains the keys `[k1, k2]` then it returns the value at the nested entry `k1.k2` of the context. If `keys` are empty or the nested entry defined by the keys doesn't exist in the context, it returns `null`. **Function signature** ```feel get value(context: context, keys: list): Any ``` **Examples** ```feel get value({x:1, y: {z:0}}, ["y", "z"]) // 0 get value({x: {y: {z:0}}}, ["x", "y"]) // {z:0} get value({a: {b: 3}}, ["b"]) // null ``` ## get entries(context) Returns the entries of the context as a list of key-value-pairs. **Function signature** ```feel get entries(context: context): list ``` The return value is a list of contexts. Each context contains two entries for "key" and "value". **Examples** ```feel get entries({foo: 123}) // [{key: "foo", value: 123}] ``` ## context put(context, key, value) Adds a new entry with the given key and value to the context. Returns a new context that includes the entry. If an entry for the same key already exists in the context, it overrides the value. **Function signature** ```feel context put(context: context, key: string, value: Any): context ``` **Examples** ```feel context put({x:1}, "y", 2) // {x:1, y:2} ``` :::info The function `context put()` replaced the previous function `put()` (Camunda Extension). The previous function is deprecated and should not be used anymore. ::: ## context put(context, keys, value) Adds a new entry with the given value to the context. The path of the entry is defined by the keys. Returns a new context that includes the entry. If `keys` contains the keys `[k1, k2]` then it adds the nested entry `k1.k2 = value` to the context. If an entry for the same keys already exists in the context, it overrides the value. If `keys` are empty, it returns `null`. **Function signature** ```feel context put(context: context, keys: list, value: Any): context ``` **Examples** ```feel context put({x:1}, ["y"], 2) // {x:1, y:2} context put({x:1, y: {z:0}}, ["y", "z"], 2) // {x:1, y: {z:2}} context put({x:1}, ["y", "z"], 2) // {x:1, y: {z:2}} ``` ## context merge(contexts) Union the given contexts. Returns a new context that includes all entries of the given contexts. If an entry for the same key already exists in a context, it overrides the value. The entries are overridden in the same order as in the list of contexts. **Function signature** ```feel context merge(contexts: list): context ``` **Examples** ```feel context merge([{x:1}, {y:2}]) // {x:1, y:2} context merge([{x:1, y: 0}, {y:2}]) // {x:1, y:2} ``` :::info The function `context merge()` replaced the previous function `put all()` (Camunda Extension). The previous function is deprecated and should not be used anymore. ::: --- ## Conversion functions Convert a value into a different type. ## string(from) Returns the given value as a string representation. **Function signature** ```feel string(from: Any): string ``` **Examples** ```feel string(1.1) // "1.1" string(date("2012-12-25")) // "2012-12-25" ``` ## number(from) Parses the given string to a number. Returns `null` if the string is not a number. **Function signature** ```feel number(from: string): number ``` **Examples** ```feel number("1500.5") // 1500.5 ``` ## number(from, grouping separator) Parses the given string to a number using the specified grouping separator. Returns `null` if the string is not a number. **Function signature** ```feel number(from: string, grouping separator: string): number ``` **Examples** ```feel number("1,500", ",") // 1500 ``` ## number(from, grouping separator, decimal separator) Parses the given string to a number using the specified grouping and decimal separators. Returns `null` if the string is not a number. **Function signature** ```feel number(from: string, grouping separator: string, decimal separator: string): number ``` **Examples** ```feel number("1 500.5", " ", ".") // 1500.5 ``` ## context(entries) Constructs a context of the given list of key-value pairs. It is the reverse function to [get entries()](feel-built-in-functions-context.md#get-entriescontext). Each key-value pair must be a context with two entries: `key` and `value`. The entry with name `key` must have a value of the type `string`. It might override context entries if the keys are equal. The entries are overridden in the same order as the contexts in the given list. Returns `null` if one of the entries is not a context or if a context doesn't contain the required entries. **Function signature** ```feel context(entries: list): context ``` **Examples** ```feel context([{"key":"a", "value":1}, {"key":"b", "value":2}]) // {a:1, b:2} ``` ## date(from) Returns a date from the given value. Returns `null` if the string is not a valid calendar date. For example, `"2024-06-31"` is invalid because June has only 30 days. **Function signature** ```feel date(from: string): date ``` Parses the given string into a date. ```feel date(from: date and time): date ``` Extracts the date component from the given date and time. **Examples** ```feel date("2018-04-29") // date("2018-04-29") date(date and time("2012-12-25T11:00:00")) // date("2012-12-25") ``` ## date(year, month, day) Returns a date from the given components. Returns `null` if the components don't represent a valid calendar date. For example, `2024,6,31` is invalid because June has only 30 days. **Function signature** ```feel date(year: number, month: number, day: number): date ``` **Examples** ```feel date(2012, 12, 25) // date("2012-12-25") ``` ## time(from) Returns a time from the given value. **Function signature** ```feel time(from: string): time ``` Parses the given string into a time. ```feel time(from: date and time): time ``` Extracts the time component from the given date and time. **Examples** ```feel time("12:00:00") // time("12:00:00") time(date and time("2012-12-25T11:00:00")) // time("11:00:00") ``` ## time(hour, minute, second) Returns a time from the given components. **Function signature** ```feel time(hour: number, minute: number, second: number): time ``` **Examples** ```feel time(23, 59, 0) // time("23:59:00") ``` ## time(hour, minute, second, offset) Returns a time from the given components, including a timezone offset. **Function signature** ```feel time(hour: number, minute: number, second: number, offset: days and time duration): time ``` **Examples** ```feel time(14, 30, 0, duration("PT1H")) // time("14:30:00+01:00") ``` ## date and time(from) Parses the given string into a date and time. The function supports strings in the format `YYYY-MM-DDThh:mm:ss` with optional timezone information either as offset (e.g., `+01:00` or `Z`), as IANA timezone ID (e.g., `@Europe/Berlin`), or as a combination of both (e.g., `+01:00[Europe/Berlin]`). Returns `null` if the string is not a valid calendar date. For example, `"2024-06-31T10:00:00"` is invalid because June has only 30 days. **Function signature** ```feel date and time(from: string): date and time ``` **Examples** ```feel date and time("2018-04-29T09:30:00") // date and time("2018-04-29T09:30:00") date and time("2018-04-29T09:30:00+02:00") // date and time("2018-04-29T09:30:00+02:00") date and time("2018-04-29T09:30:00@Europe/Berlin") // date and time("2018-04-29T09:30:00@Europe/Berlin") date and time("2018-04-29T09:30:00+02:00[Europe/Berlin]") // date and time("2018-04-29T09:30:00@Europe/Berlin") ``` ## date and time(date, time) Returns a date and time from the given components. **Function signature** ```feel date and time(date: date, time: time): date and time ``` ```feel date and time(date: date and time, time: time): date and time ``` Returns a date and time value that consists of the date component of `date` combined with `time`. **Examples** ```feel date and time(date("2012-12-24"),time("T23:59:00")) // date and time("2012-12-24T23:59:00") date and time(date and time("2012-12-25T11:00:00"),time("T23:59:00")) // date and time("2012-12-25T23:59:00") ``` ## date and time(date, timezone) Returns the given date and time value at the given timezone. If `date` has a different timezone than `timezone` then it adjusts the time to match the local time of `timezone`. **Function signature** ```feel date and time(date: date and time, timezone: string): date and time ``` **Examples** ```feel date and time(@"2020-07-31T14:27:30@Europe/Berlin", "America/Los_Angeles") // date and time("2020-07-31T05:27:30@America/Los_Angeles") date and time(@"2020-07-31T14:27:30", "Z") // date and time("2020-07-31T12:27:30Z") ``` ## duration(from) Parses the given string into a duration. The duration is either a days and time duration or a years and months duration. **Function signature** ```feel duration(from: string): days and time duration ``` ```feel duration(from: string): years and months duration ``` **Examples** ```feel duration("P5D") // duration("P5D") duration("P32Y") // duration("P32Y") ``` ## years and months duration(from, to) Returns the years and months duration between `from` and `to`. **Function signature** ```feel years and months duration(from: date, to: date): years and months duration ``` **Examples** ```feel years and months duration(date("2011-12-22"), date("2013-08-24")) // duration("P1Y8M") ``` ## from json(value) Parses a JSON string into a FEEL value. The function converts JSON primitives, objects, and arrays into their corresponding FEEL types. Returns `null` if the string is not a valid JSON value. **Function signature** ```feel from json(value: string): Any ``` **Examples** ```feel from json("{\"a\": 1, \"b\": 2}") // {a: 1, b: 2} from json("true") // true from json("\"2023-06-14\"") // "2023-06-14" ``` ## to json(value) Converts a FEEL value into a JSON string. The function converts FEEL primitives, contexts, and lists into their corresponding JSON types. Temporal values are converted to their ISO 8601 string representation, including timezone information for date and time values (format: `2025-11-24T10:00:00+01:00[Europe/Berlin]`). **Function signature** ```feel to json(value: Any): string ``` **Examples** ```feel to json({a: 1, b: 2}) // "{\"a\":1,\"b\":2}" to json(true) // "true" to json(@"2023-06-14") // "\"2023-06-14\"" to json(@"2025-11-24T10:00:00@Europe/Berlin") // "\"2025-11-24T10:00:00+01:00[Europe/Berlin]\"" to json(@"P3Y") // "\"P3Y\"" ``` --- ## Introduction FEEL includes many built-in functions. These functions can be invoked in [expressions](../language-guide/feel-expressions-introduction.md) and [unary-tests](../language-guide/feel-unary-tests.md). ```feel contains("me@camunda.com", ".com") // invoke function with positional arguments contains(string: "me@camunda.com", match: ".de") // invoke function with named arguments ``` Read more about functions [here](../language-guide/feel-functions.md#invocation). This section is split into functions based on their primary operational data type: - [Boolean](./feel-built-in-functions-boolean.md) - [String](./feel-built-in-functions-string.md) - [Numeric](./feel-built-in-functions-numeric.md) - [List](./feel-built-in-functions-list.md) - [Context](./feel-built-in-functions-context.md) - [Temporal](./feel-built-in-functions-temporal.md) - [Range](./feel-built-in-functions-range.md) Additionally, there are [conversion](./feel-built-in-functions-conversion.md) functions that allow you to construct new values of a data type (factory functions), and [AI agent functions](./feel-built-in-functions-ai-agent.md) used to declare LLM-provided tool parameters (see the [AI agent functions glossary entry](/reference/glossary.md#ai-agent-function)). --- ## List functions ## list contains(list, element) Returns `true` if the given list contains the element. Otherwise, returns `false`. **Function signature** ```feel list contains(list: list, element: Any): boolean ``` **Examples** ```feel list contains([1,2,3], 2) // true ``` ## count(list) Returns the number of elements of the given list. **Function signature** ```feel count(list: list): number ``` **Examples** ```feel count([1,2,3]) // 3 ``` ## min(list) Returns the minimum of the given list. **Function signature** ```feel min(list: list): Any ``` All elements in `list` should have the same type and be comparable. The parameter `list` can be passed as a list or as a sequence of elements. **Examples** ```feel min([1,2,3]) // 1 min(1,2,3) // 1 ``` ## max(list) Returns the maximum of the given list. **Function signature** ```feel max(list: list): Any ``` All elements in `list` should have the same type and be comparable. The parameter `list` can be passed as a list or as a sequence of elements. **Examples** ```feel max([1,2,3]) // 3 max(1,2,3) // 3 ``` ## sum(list) Returns the sum of the given list of numbers. **Function signature** ```feel sum(list: list): number ``` The parameter `list` can be passed as a list or as a sequence of elements. **Examples** ```feel sum([1,2,3]) // 6 sum(1,2,3) // 6 ``` ## product(list) Returns the product of the given list of numbers. **Function signature** ```feel product(list: list): number ``` The parameter `list` can be passed as a list or as a sequence of elements. **Examples** ```feel product([2, 3, 4]) // 24 product(2, 3, 4) // 24 ``` ## mean(list) Returns the arithmetic mean (i.e. average) of the given list of numbers. **Function signature** ```feel mean(list: list): number ``` The parameter `list` can be passed as a list or as a sequence of elements. **Examples** ```feel mean([1,2,3]) // 2 mean(1,2,3) // 2 ``` ## median(list) Returns the median element of the given list of numbers. **Function signature** ```feel median(list: list): number ``` The parameter `list` can be passed as a list or as a sequence of elements. **Examples** ```feel median(8, 2, 5, 3, 4) // 4 median([6, 1, 2, 3]) // 2.5 ``` ## stddev(list) Returns the standard deviation of the given list of numbers. **Function signature** ```feel stddev(list: list): number ``` The parameter `list` can be passed as a list or as a sequence of elements. **Examples** ```feel stddev(2, 4, 7, 5) // 2.0816659994661326 stddev([2, 4, 7, 5]) // 2.0816659994661326 ``` ## mode(list) Returns the mode of the given list of numbers. **Function signature** ```feel mode(list: list): number ``` The parameter `list` can be passed as a list or as a sequence of elements. **Examples** ```feel mode(6, 3, 9, 6, 6) // [6] mode([6, 1, 9, 6, 1]) // [1, 6] ``` ## all(list) Returns `false` if any element of the given list is `false`. Otherwise, returns `true`. If the given list is empty, it returns `true`. **Function signature** ```feel all(list: list): boolean ``` The parameter `list` can be passed as a list or as a sequence of elements. **Examples** ```feel all([true,false]) // false all(false,null,true) // false ``` :::info The function `all()` replaced the previous function `and()`. The previous function is deprecated and should not be used anymore. ::: ## any(list) Returns `true` if any element of the given list is `true`. Otherwise, returns `false`. If the given list is empty, it returns `false`. **Function signature** ```feel any(list: list): boolean ``` The parameter `list` can be passed as a list or as a sequence of elements. **Examples** ```feel any([false,true]) // true any(false,null,true) // true ``` :::info The function `any()` replaced the previous function `or()`. The previous function is deprecated and should not be used anymore. ::: ## sublist(list, start position) Returns a partial list of the given value starting at `start position`. **Function signature** ```feel sublist(list: list, start position: number): list ``` The `start position` starts at the index `1`. The last position is `-1`. **Examples** ```feel sublist([1,2,3], 2) // [2,3] ``` ## sublist(list, start position, length) Returns a partial list of the given value starting at `start position`. **Function signature** ```feel sublist(list: list, start position: number, length: number): list ``` The `start position` starts at the index `1`. The last position is `-1`. **Examples** ```feel sublist([1,2,3], 1, 2) // [1,2] ``` ## append(list, items) Returns the given list with all `items` appended. **Function signature** ```feel append(list: list, items: Any): list ``` The parameter `items` can be a single element or a sequence of elements. **Examples** ```feel append([1], 2, 3) // [1,2,3] ``` ## concatenate(lists) Returns a list that includes all elements of the given lists. **Function signature** ```feel concatenate(lists: list): list ``` The parameter `lists` is a sequence of lists. **Examples** ```feel concatenate([1,2],[3]) // [1,2,3] concatenate([1],[2],[3]) // [1,2,3] ``` ## insert before(list, position, newItem) Returns the given list with `newItem` inserted at `position`. **Function signature** ```feel insert before(list: list, position: number, newItem: Any): list ``` The `position` starts at the index `1`. The last position is `-1`. **Examples** ```feel insert before([1,3],1,2) // [2,1,3] ``` ## remove(list, position) Returns the given list without the element at `position`. **Function signature** ```feel remove(list: list, position: number): list ``` The `position` starts at the index `1`. The last position is `-1`. **Examples** ```feel remove([1,2,3], 2) // [1,3] ``` ## reverse(list) Returns the given list in revered order. **Function signature** ```feel reverse(list: list): list ``` **Examples** ```feel reverse([1,2,3]) // [3,2,1] ``` ## index of(list, match) Returns an ascending list of positions containing `match`. **Function signature** ```feel index of(list: list, match: Any): list ``` **Examples** ```feel index of([1,2,3,2],2) // [2,4] ``` ## union(list) Returns a list that includes all elements of the given lists without duplicates. **Function signature** ```feel union(list: list): list ``` The parameter `list` is a sequence of lists. **Examples** ```feel union([1,2],[2,3]) // [1,2,3] ``` ## distinct values(list) Returns the given list without duplicates. **Function signature** ```feel distinct values(list: list): list ``` **Examples** ```feel distinct values([1,2,3,2,1]) // [1,2,3] ``` ## duplicate values(list) Returns all duplicate values of the given list. **Function signature** ```feel duplicate values(list: list): list ``` **Examples** ```feel duplicate values([1,2,3,2,1]) // [1,2] ``` ## flatten(list) Returns a list that includes all elements of the given list without nested lists. **Function signature** ```feel flatten(list: list): list ``` **Examples** ```feel flatten([[1,2],[[3]], 4]) // [1,2,3,4] ``` ## sort(list, precedes) Returns the given list sorted by the `precedes` function. **Function signature** ```feel sort(list: list, precedes: function<(Any, Any) -> boolean>): list ``` **Examples** ```feel sort(list: [3,1,4,5,2], precedes: function(x,y) x < y) // [1,2,3,4,5] ``` ## string join(list) Joins a list of strings into a single string. This is similar to Java's [joining]() function. If an item of the list is `null`, the item is ignored for the result string. If an item is neither a string nor `null`, the function returns `null` instead of a string. **Function signature** ```feel string join(list: list): string ``` **Examples** ```feel string join(["a","b","c"]) // "abc" string join(["a",null,"c"]) // "ac" string join([]) // "" ``` ## string join(list, delimiter) Joins a list of strings into a single string. This is similar to Java's [joining]() function. If an item of the list is `null`, the item is ignored for the result string. If an item is neither a string nor `null`, the function returns `null` instead of a string. The resulting string contains a `delimiter` between each element. **Function signature** ```feel string join(list: list, delimiter: string): string ``` **Examples** ```feel string join(["a"], "X") // "a" string join(["a","b","c"], ", ") // "a, b, c" ``` ## string join(list, delimiter, prefix, suffix) Joins a list of strings into a single string. This is similar to Java's [joining]() function. If an item of the list is `null`, the item is ignored for the result string. If an item is neither a string nor `null`, the function returns `null` instead of a string. The resulting string starts with `prefix`, contains a `delimiter` between each element, and ends with `suffix`. **Function signature** ```feel string join(list: list, delimiter: string, prefix: string, suffix: string): string ``` **Examples** ```feel string join(["a","b","c"], ", ", "[", "]") // "[a, b, c]" ``` ## is empty(list) Returns `true` if the given list is empty. Otherwise, returns `false`. **Function signature** ```feel is empty(list: list): boolean ``` **Examples** ```feel is empty([]) // true is empty([1,2,3]) // false ``` ## partition(list, size) Returns consecutive sublists of a list, each of the same size (the final list may be smaller). If `size` is less than `0`, it returns `null`. **Function signature** ```feel partition(list: list, size: number): list ``` **Examples** ```feel partition([1,2,3,4,5], 2) // [[1,2], [3,4], [5]] partition([], 2) // [] partition([1,2], 0) // null ``` --- ## Numeric functions ## decimal(n, scale) Rounds the given value at the given scale. **Function signature** ```feel decimal(n: number, scale: number): number ``` **Examples** ```feel decimal(1/3, 2) // .33 decimal(1.5, 0) // 2 ``` ## floor(n) Rounds the given value with rounding mode flooring. **Function signature** ```feel floor(n: number): number ``` **Examples** ```feel floor(1.5) // 1 floor(-1.5) // -2 ``` ## floor(n, scale) Rounds the given value with rounding mode flooring at the given scale. **Function signature** ```feel floor(n: number, scale: number): number ``` **Examples** ```feel floor(-1.56, 1) // -1.6 ``` ## ceiling(n) Rounds the given value with rounding mode ceiling. **Function signature** ```feel ceiling(n: number): number ``` **Examples** ```feel ceiling(1.5) // 2 ceiling(-1.5) // -1 ``` ## ceiling(n, scale) Rounds the given value with rounding mode ceiling at the given scale. **Function signature** ```feel ceiling(n: number, scale: number): number ``` **Examples** ```feel ceiling(-1.56, 1) // -1.5 ``` ## round up(n, scale) Rounds the given value with the rounding mode round-up at the given scale. **Function signature** ```feel round up(n: number, scale: number): number ``` **Examples** ```feel round up(5.5) // 6 round up(-5.5) // -6 round up(1.121, 2) // 1.13 round up(-1.126, 2) // -1.13 ``` ## round down(n, scale) Rounds the given value with the rounding mode round-down at the given scale. **Function signature** ```feel round down(n: number, scale: number): number ``` **Examples** ```feel round down(5.5, 0) // 5 round down (-5.5, 0) // -5 round down (1.121, 2) // 1.12 round down (-1.126, 2) // -1.12 ``` ## round half up(n, scale) Rounds the given value with the rounding mode round-half-up at the given scale. **Function signature** ```feel round half up(n: number, scale: number): number ``` **Examples** ```feel round half up(5.5, 0) // 6 round half up(-5.5, 0) // -6 round half up(1.121, 2) // 1.12 round half up(-1.126, 2) // -1.13 ``` ## round half down(n, scale) Rounds the given value with the rounding mode round-half-down at the given scale. **Function signature** ```feel round half down(n: number, scale: number): number ``` **Examples** ```feel round half down (5.5, 0) // 5 round half down (-5.5, 0) // -5 round half down (1.121, 2) // 1.12 round half down (-1.126, 2) // -1.13 ``` ## abs(number) Returns the absolute value of the given numeric value. **Function signature** ```feel abs(number: number): number ``` **Examples** ```feel abs(10) // 10 abs(-10) // 10 ``` ## modulo(dividend, divisor) Returns the remainder of the division of dividend by divisor. **Function signature** ```feel modulo(dividend: number, divisor: number): number ``` **Examples** ```feel modulo(12, 5) // 2 ``` ## sqrt(number) Returns the square root of the given value. **Function signature** ```feel sqrt(number: number): number ``` **Examples** ```feel sqrt(16) // 4 ``` ## log(number) Returns the natural logarithm (base e) of the given value. **Function signature** ```feel log(number: number): number ``` **Examples** ```feel log(10) // 2.302585092994046 ``` ## exp(number) Returns the Euler’s number e raised to the power of the given number . **Function signature** ```feel exp(number: number): number ``` **Examples** ```feel exp(5) // 148.4131591025766 ``` ## odd(number) Returns `true` if the given value is odd. Otherwise, returns `false`. **Function signature** ```feel odd(number: number): boolean ``` **Examples** ```feel odd(5) // true odd(2) // false ``` ## even(number) Returns `true` if the given is even. Otherwise, returns `false`. **Function signature** ```feel even(number: number): boolean ``` **Examples** ```feel even(5) // false even(2) // true ``` ## random number() Returns a random number between `0` and `1`. **Function signature** ```feel random number(): number ``` **Examples** ```feel random number() // 0.9701618132579795 ``` --- ## Range functions A set of functions establish relationships between single scalar values and ranges of such values. All functions take two arguments and return `true` if the relationship between the argument holds, or `false` otherwise. A scalar value must be of the following type: - number - date - time - date-time - days-time-duration - years-months-duration ![range functions overview](../assets/feel-built-in-functions-range-overview.png) ## before(point1, point2) **Function signature** ```feel before(point1: Any, point2: Any): boolean ``` **Examples** ```feel before(1, 10) // true before(10, 1) // false ``` ## before(range, point) **Function signature** ```feel before(range: range, point: Any): boolean ``` **Examples** ```feel before([1..5], 10) // true ``` ## before(point, range) **Function signature** ```feel before(point: Any, range: range): boolean ``` **Examples** ```feel before(1, [2..5]) // true ``` ## before(range1, range2) **Function signature** ```feel before(range1: range, range2: range): boolean ``` **Examples** ```feel before([1..5], [6..10]) // true before([1..5),[5..10]) // true ``` ## after(point1, point2) **Function signature** ```feel after(point1: Any, point2: Any): boolean ``` **Examples** ```feel after(10, 1) // true after(1, 10) // false ``` ## after(range, point) **Function signature** ```feel after(range: range, point: Any): boolean ``` **Examples** ```feel after([1..5], 10) // false ``` ## after(point, range) **Function signature** ```feel after(point: Any, range: range): boolean ``` **Examples** ```feel after(12, [2..5]) // true ``` ## after(range1, range2) **Function signature** ```feel after(range1: range, range2: range): boolean ``` **Examples** ```feel after([6..10], [1..5]) // true after([5..10], [1..5)) // true ``` ## meets(range1, range2) **Function signature** ```feel meets(range1: range, range2: range): boolean ``` **Examples** ```feel meets([1..5], [5..10]) // true meets([1..3], [4..6]) // false meets([1..3], [3..5]) // true meets([1..5], (5..8]) // false ``` ## met by(range1, range2) **Function signature** ```feel met by(range1: range, range2: range): boolean ``` **Examples** ```feel met by([5..10], [1..5]) // true met by([3..4], [1..2]) // false met by([3..5], [1..3]) // true met by((5..8], [1..5)) // false met by([5..10], [1..5)) // false ``` ## overlaps(range1, range2) **Function signature** ```feel overlaps(range1: range, range2: range): boolean ``` **Examples** ```feel overlaps([5..10], [1..6]) // true overlaps((3..7], [1..4]) // true overlaps([1..3], (3..6]) // false overlaps((5..8], [1..5)) // false overlaps([4..10], [1..5)) // true ``` ## overlaps before(range1, range2) **Function signature** ```feel overlaps before(range1: range, range2: range): boolean ``` **Examples** ```feel overlaps before([1..5], [4..10]) // true overlaps before([3..4], [1..2]) // false overlaps before([1..3], (3..5]) // false overlaps before([1..5), (3..8]) // true overlaps before([1..5), [5..10]) // false ``` ## overlaps after(range1, range2) **Function signature** ```feel overlaps after(range1: range, range2: range): boolean ``` **Examples** ```feel overlaps after([4..10], [1..5]) // true overlaps after([3..4], [1..2]) // false overlaps after([3..5], [1..3)) // false overlaps after((5..8], [1..5)) // false overlaps after([4..10], [1..5)) // true ``` ## finishes(point, range) **Function signature** ```feel finishes(point: Any, range: range): boolean ``` **Examples** ```feel finishes(5, [1..5]) // true finishes(10, [1..7]) // false ``` ## finishes(range1, range2) **Function signature** ```feel finishes(range1: range, range2: range): boolean ``` **Examples** ```feel finishes([3..5], [1..5]) // true finishes((1..5], [1..5)) // false finishes([5..10], [1..10)) // false ``` ## finished by(range, point) **Function signature** ```feel finished by(range: range, point: Any): boolean ``` **Examples** ```feel finished by([5..10], 10) // true finished by([3..4], 2) // false ``` ## finished by(range1, range2) **Function signature** ```feel finished by(range1: range, range2: range): boolean ``` **Examples** ```feel finished by([1..5], [3..5]) // true finished by((5..8], [1..5)) // false finished by([5..10], (1..10)) // false ``` ## includes(range, point) **Function signature** ```feel includes(range: range, point: Any): boolean ``` **Examples** ```feel includes([5..10], 6) // true includes([3..4], 5) // false ``` ## includes(range1, range2) **Function signature** ```feel includes(range1: range, range2: range): boolean ``` **Examples** ```feel includes([1..10], [4..6]) // true includes((5..8], [1..5)) // false includes([1..10], [1..5)) // true ``` ## during(point, range) **Function signature** ```feel during(point: Any, range: range): boolean ``` **Examples** ```feel during(5, [1..10]) // true during(12, [1..10]) // false during(1, (1..10]) // false ``` ## during(range1, range2) **Function signature** ```feel during(range1: range, range2: range): boolean ``` **Examples** ```feel during([4..6], [1..10)) // true during((1..5], (1..10]) // true ``` ## starts(point, range) **Function signature** ```feel starts(point: Any, range: range): boolean ``` **Examples** ```feel starts(1, [1..5]) // true starts(1, (1..8]) // false ``` ## starts(range1, range2) **Function signature** ```feel starts(range1: range, range2: range): boolean ``` **Examples** ```feel starts((1..5], [1..5]) // false starts([1..10], [1..5]) // false starts((1..5), (1..10)) // true ``` ## started by(range, point) **Function signature** ```feel started by(range: range, point: Any): boolean ``` **Examples** ```feel started by([1..10], 1) // true started by((1..10], 1) // false ``` ## started by(range1, range2) **Function signature** ```feel started by(range1: range, range2: range): boolean ``` **Examples** ```feel started by([1..10], [1..5]) // true started by((1..10], [1..5)) // false started by([1..10], [1..10)) // true ``` ## coincides(point1, point2) **Function signature** ```feel coincides(point1: Any, point2: Any): boolean ``` **Examples** ```feel coincides(5, 5) // true coincides(3, 4) // false ``` ## coincides(range1, range2) **Function signature** ```feel coincides(range1: range, range2: range): boolean ``` **Examples** ```feel coincides([1..5], [1..5]) // true coincides((1..5], [1..5)) // false coincides([1..5], [2..6]) // false ``` --- ## String functions ## substring(string, start position) Returns a substring of the given value starting at `start position`. **Function signature** ```feel substring(string: string, start position: number): string ``` The `start position` starts at the index `1`. The last position is `-1`. **Examples** ```feel substring("foobar", 3) // "obar" substring("foobar", -2) // "ar" ``` ## substring(string, start position, length) Returns a substring of the given value, starting at `start position` with the given `length`. If `length` is greater than the remaining characters of the value, it returns all characters from `start position` until the end. **Function signature** ```feel substring(string: string, start position: number, length: number): string ``` The `start position` starts at the index `1`. The last position is `-1`. **Examples** ```feel substring("foobar", 3, 3) // "oba" substring("foobar", -3, 2) // "ba" substring("foobar", 3, 10) // "obar" ``` ## string length(string) Returns the number of characters in the given value. **Function signature** ```feel string length(string: string): number ``` **Examples** ```feel string length("foo") // 3 ``` ## upper case(string) Returns the given value with all characters are uppercase. **Function signature** ```feel upper case(string: string): string ``` **Examples** ```feel upper case("aBc4") // "ABC4" ``` ## lower case(string) Returns the given value with all characters are lowercase. **Function signature** ```feel lower case(string: string): string ``` **Examples** ```feel lower case("aBc4") // "abc4" ``` ## substring before(string, match) Returns a substring of the given value that contains all characters before `match`. **Function signature** ```feel substring before(string: string, match: string): string ``` **Examples** ```feel substring before("foobar", "bar") // "foo" ``` ## substring after(string, match) Returns a substring of the given value that contains all characters after `match`. **Function signature** ```feel substring after(string: string, match: string): string ``` **Examples** ```feel substring after("foobar", "ob") // "ar" ``` ## contains(string, match) Returns `true` if the given value contains the substring `match`. Otherwise, returns `false`. **Function signature** ```feel contains(string: string, match: string): boolean ``` **Examples** ```feel contains("foobar", "of") // false ``` ## starts with(string, match) Returns `true` if the given value starts with the substring `match`. Otherwise, returns `false`. **Function signature** ```feel starts with(string: string, match: string): boolean ``` **Examples** ```feel starts with("foobar", "fo") // true ``` ## ends with(string, match) Returns `true` if the given value ends with the substring `match`. Otherwise, returns `false`. **Function signature** ```feel ends with(string: string, match: string): boolean ``` **Examples** ```feel ends with("foobar", "r") // true ``` ## matches(input, pattern) Returns `true` if the given value matches the `pattern`. Otherwise, returns `false`. **Function signature** ```feel matches(input: string, pattern: string): boolean ``` The `pattern` is a string that contains a regular expression. **Examples** ```feel matches("foobar", "^fo*bar") // true ``` ## matches(input, pattern, flags) Returns `true` if the given value matches the `pattern`. Otherwise, returns `false`. **Function signature** ```feel matches(input: string, pattern: string, flags: string): boolean ``` The `pattern` is a string that contains a regular expression. The `flags` can contain one or more of the following characters: - `s` (dot-all) - `m` (multi-line) - `i` (case insensitive) - `x` (comments) **Examples** ```feel matches("FooBar", "foo", "i") // true ``` ## replace(input, pattern, replacement) Returns the resulting string after replacing all occurrences of `pattern` with `replacement`. **Function signature** ```feel replace(input: string, pattern: string, replacement: string): string ``` The `pattern` is a string that contains a regular expression. The `replacement` can access the match groups by using `$` and the number of the group, for example, `$1` to access the first group. **Examples** ```feel replace("abcd", "(ab)|(a)", "[1=$1][2=$2]") // "[1=ab][2=]cd" replace("0123456789", "(\d{3})(\d{3})(\d{4})", "($1) $2-$3") // "(012) 345-6789" ``` ## replace(input, pattern, replacement, flags) Returns the resulting string after replacing all occurrences of `pattern` with `replacement`. **Function signature** ```feel replace(input: string, pattern: string, replacement: string, flags: string): string ``` The `pattern` is a string that contains a regular expression. The `replacement` can access the match groups by using `$` and the number of the group, for example, `$1` to access the first group. The `flags` can contain one or more of the following characters: - `s` (dot-all) - `m` (multi-line) - `i` (case insensitive) - `x` (comments) **Examples** ```feel replace("How do you feel?", "Feel", "FEEL", "i") // "How do you FEEL?" ``` ## split(string, delimiter) Splits the given value into a list of substrings, breaking at each occurrence of the `delimiter` pattern. **Function signature** ```feel split(string: string, delimiter: string): list ``` The `delimiter` is a string that contains a regular expression. **Examples** ```feel split("John Doe", "\s" ) // ["John", "Doe"] split("a;b;c;;", ";") // ["a", "b", "c", "", ""] ``` ## extract(string, pattern) Returns all matches of the pattern in the given string. Returns an empty list if the pattern doesn't match. **Function signature** ```feel extract(string: string, pattern: string): list ``` The `pattern` is a string that contains a regular expression. **Examples** ```feel extract("references are 1234, 1256, 1378", "12[0-9]*") // ["1234","1256"] ``` ## trim(string) Returns the given string without leading and trailing spaces. **Function signature** ```feel trim(string: string): string ``` **Examples** ```feel trim(" hello world ") // "hello world" trim("hello world ") // "hello world" ``` ## uuid() Returns a UUID (Universally Unique Identifier) with 36 characters. **Function signature** ```feel uuid(): string ``` **Examples** ```feel uuid() // "7793aab1-d761-4d38-916b-b7270e309894" ``` ## to base64(value) Returns the given string encoded in Base64 format. **Function signature** ```feel to base64(value: string): string ``` **Examples** ```feel to base64("FEEL") // "RkVFTA==" ``` ## from base64(value) Returns the given Base64 encoded string decoded to a plain string. **Function signature** ```feel from base64(value: string): string ``` **Examples** ```feel from base64("RkVFTA==") // "FEEL" ``` ## is blank(string) Returns `true` if the given string is blank (empty or contains only whitespaces). **Function signature** ```feel is blank(string: string): boolean ``` **Examples** ```feel is blank("") // true is blank(" ") // true is blank("hello world") // false ``` --- ## Temporal functions ## now() Returns the current date and time including the timezone. **Function signature** ```feel now(): date and time ``` **Examples** ```feel now() // date and time("2020-07-31T14:27:30@Europe/Berlin") ``` In Camunda, `now()` uses the default time zone of the cluster. FEEL shows the zone ID after `@`, for example `@Europe/Berlin`. If you store the result in a variable, the zone ID is written in brackets after the offset instead, for example `2026-09-29T12:31:01.641Z[GMT]`, which some ISO 8601 parsers reject. To store the value without the zone ID, convert it to a fixed offset. See [variable values](/components/concepts/variables.md#variable-values). ```feel date and time(now(), "Z") // date and time("2020-07-31T12:27:30Z") ``` ## today() Returns the current date. **Function signature** ```feel today(): date ``` **Examples** ```feel today() // date("2020-07-31") ``` ## day of week(date) Returns the day of the week according to the Gregorian calendar. Note that it always returns the English name of the day. **Function signature** ```feel day of week(date: date): string ``` ```feel day of week(date: date and time): string ``` **Examples** ```feel day of week(date("2019-09-17")) // "Tuesday" day of week(date and time("2019-09-17T12:00:00")) // "Tuesday" ``` ## day of year(date) Returns the Gregorian number of the day within the year. **Function signature** ```feel day of year(date: date): number ``` ```feel day of year(date: date and time): number ``` **Examples** ```feel day of year(date("2019-09-17")) // 260 day of year(date and time("2019-09-17T12:00:00")) // 260 ``` ## week of year(date) Returns the Gregorian number of the week within the year, according to ISO 8601. **Function signature** ```feel week of year(date: date): number ``` ```feel week of year(date: date and time): number ``` **Examples** ```feel week of year(date("2019-09-17")) // 38 week of year(date and time("2019-09-17T12:00:00")) // 38 ``` ## month of year(date) Returns the month of the year according to the Gregorian calendar. Note that it always returns the English name of the month. **Function signature** ```feel month of year(date: date): string ``` ```feel month of year(date: date and time): string ``` **Examples** ```feel month of year(date("2019-09-17")) // "September" month of year(date and time("2019-09-17T12:00:00")) // "September" ``` ## abs(n) Returns the absolute value of a given duration. **Function signature** ```feel abs(n: days and time duration): days and time duration ``` ```feel abs(n: years and months duration): years and months duration ``` **Examples** ```feel abs(duration("-PT5H")) // "duration("PT5H")" abs(duration("PT5H")) // "duration("PT5H")" abs(duration("-P2M")) // duration("P2M") ``` ## last day of month(date) Takes the month of the given date or date-time value and returns the last day of this month. **Function signature** ```feel last day of month(date: date): date ``` ```feel last day of month(date: date and time): date ``` **Examples** ```feel last day of month(date("2022-10-01")) // date("2022-10-31")) last day of month(date and time("2022-10-16T12:00:00")) // date("2022-10-31")) ``` --- ## Supported data types Understand the data types supported by cluster variables for different configuration needs. ## Simple values - **String**: Text values for URLs, names, identifiers. - **Number**: Numeric values for thresholds, timeouts, counts. - **Boolean**: True/false values for feature flags and toggles. ## Complex values - **Objects**: Nested structures for grouped configuration. - **Arrays**: Lists of values. :::note Access patterns may vary depending on how the array is used. ::: ## Variable kinds Every cluster variable has a kind, which determines how Camunda reads its value. | Kind | Description | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `JSON` | The default. Your value is data, and Camunda reads it exactly as you stored it. | | `SECRET_REFERENCE` | The value can contain an [Orchestration Cluster secret reference](/reference/glossary.md#secret-reference-orchestration-cluster), `camunda.secrets.`, which Camunda resolves. | Only a `SECRET_REFERENCE`-kind variable has its references resolved. A `JSON`-kind variable whose value contains the same text is treated as ordinary text, and that text reaches your process unchanged. ### Where references can appear in a value Camunda scans every string in a `SECRET_REFERENCE`-kind variable's value, including strings nested inside objects and arrays. Object keys are not scanned. A reference has the form `camunda.secrets.`, where `` can contain ASCII letters, digits, underscores, and dashes, up to 240 characters. A name that fails either limit is never resolved; see [secrets](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#secrets). This character set applies to references embedded in a cluster-variable value; a reference [written directly in an input mapping source](/components/concepts/variables.md#escape-secret-names-with-special-characters) can use other characters through backtick escaping. For example, the following value carries two references, one at the top level and one nested: ```json { "apiKey": "camunda.secrets.PAYMENT_API_KEY", "database": { "user": "reporting", "password": "camunda.secrets.REPORTING_DB_PASSWORD" } } ``` Do not place a reference inside an array. Camunda detects such a reference when you create the variable, but it cannot be resolved when a process reads the variable: the job is not activated and raises an incident instead. See [when a job is not activated](/components/concepts/secret-resolution-and-job-activation.md#understand-why-a-job-is-not-activated). ### Create a variable of kind `SECRET_REFERENCE` Set `kind` when you create the variable. If you omit `kind`, the variable is created as `JSON`. ```bash POST /v2/cluster-variables/global Content-Type: application/json { "name": "PAYMENT_API_CONFIG", "kind": "SECRET_REFERENCE", "value": { "endpoint": "https://api.payment.prod.example.com", "apiKey": "camunda.secrets.PAYMENT_API_KEY" } } ``` A variable's kind is fixed at creation. Update requests carry no `kind` field, so updating a `SECRET_REFERENCE`-kind variable keeps its kind and scans the new value for references. To change a variable's kind, delete it and create it again with the kind you want. ### Read a variable of kind `SECRET_REFERENCE` Get and search responses return the stored value, so you see the reference text rather than a resolved value. References are resolved only when a process reads the variable in an input mapping, as described in [resolve secret references in a cluster variable](./usage-guide.md#resolve-secret-references-in-a-cluster-variable). For where resolved values appear and where they do not, see [secret resolution and job activation](/components/concepts/secret-resolution-and-job-activation.md). To find variables of a given kind, use the `kind` filter in [search cluster variables](/apis-tools/orchestration-cluster-api-rest/specifications/search-cluster-variables.api.mdx). ### Required permissions A `SECRET_REFERENCE`-kind variable needs the same permissions as any other cluster variable. There is no additional permission for this kind. | Action | Required permission | | ----------------- | ------------------------------------------------ | | Create a variable | `CREATE` on the `CLUSTER_VARIABLE` resource type | | Get or search | `READ` on the `CLUSTER_VARIABLE` resource type | | Update a variable | `UPDATE` on the `CLUSTER_VARIABLE` resource type | | Delete a variable | `DELETE` on the `CLUSTER_VARIABLE` resource type | The resource identifier is the variable name, or `*` for all cluster variables. See [authorizations](/components/concepts/access-control/authorizations.md#available-resources) for how to grant these permissions. --- ## Common use cases Explore practical ways to use cluster variables with real-world examples, including environment-specific configurations, SLA rules, and integration endpoints. ## Environment-specific configuration You need different API endpoints and timeouts across development, staging, and production environments. **Setup:** Global scope, e.g., production: ```json { "PAYMENT_API": { "endpoint": "https://api.payment.prod.example.com", "timeout_ms": 5000, "retry_count": 3 } } ``` Tenant scope, e.g., dev environment: ```json { "PAYMENT_API": { "endpoint": "https://api.payment.dev.example.com", "timeout_ms": 30000, "retry_count": 1 } } ``` **Usage in BPMN:** For example, in a service task making a payment API call: ``` URL: = camunda.vars.env.PAYMENT_API.endpoint Timeout: = camunda.vars.env.PAYMENT_API.timeout_ms ``` **Benefit:** The same BPMN file works across all environments without modification. ## Feature flags You want to gradually roll out a new approval workflow to specific tenants. **Setup:** Global scope, e.g., production: ```json { "ENABLE_NEW_APPROVAL_FLOW": false } ``` Tenant scope, e.g., `tenant-a`: ```json { "ENABLE_NEW_APPROVAL_FLOW": true } ``` Tenant scope, e.g., `beta-customer`: ```json { "ENABLE_NEW_APPROVAL_FLOW": true } ``` **Usage in BPMN:** For example, in an exclusive gateway condition: ``` Condition for new flow: camunda.vars.env.ENABLE_NEW_APPROVAL_FLOW = true Condition for old flow: camunda.vars.env.ENABLE_NEW_APPROVAL_FLOW = false ``` **Benefit:** Control feature rollout per tenant without deploying different process versions. ## Multi-tenant SLA configuration Different tenants have different Service Level Agreements (SLAs) with varying approval thresholds and escalation timeouts. **Setup:** Tenant scope, e.g., `tenant-a`: ```json { "SLA_CONFIG": { "approval_threshold": 1000, "escalation_hours": 24, "auto_approve_limit": 100 }, "auto_approve_limit": 100 } ``` Tenant scope, e.g., `tenant-b`: ```json { "SLA_CONFIG": { "approval_threshold": 50000, "escalation_hours": 4, "auto_approve_limit": 5000 }, "auto_approve_limit": 5000 } ``` Tenant scope, e.g., `tenant-c`: ```json { "SLA_CONFIG": { "approval_threshold": 5000, "escalation_hours": 48, "auto_approve_limit": 500 }, "auto_approve_limit": 500 } ``` **Usage in BPMN:** For example, in a gateway condition for auto-approval: ``` amount <= camunda.vars.env.SLA_CONFIG.auto_approve_limit ``` Timer boundary event for escalation: ``` Duration: = duration("PT" + string(camunda.vars.env.SLA_CONFIG.escalation_hours) + "H") ``` **Benefit:** Customize business rules per tenant while maintaining a single process definition. ## Integration credentials and endpoints Your processes integrate with multiple external services that have different configurations per environment. **Setup:** Global scope, e.g., production: ```json { "INTEGRATIONS": { "crm": { "base_url": "https://crm.prod.example.com", "api_version": "v2", "timeout_ms": 10000 }, "erp": { "base_url": "https://erp.prod.example.com", "api_version": "v1", "timeout_ms": 15000 }, "notification": { "base_url": "https://notify.prod.example.com", "api_version": "v1", "timeout_ms": 5000 } } } ``` Tenant scope, e.g., `sandbox`: ```json { "INTEGRATIONS": { "crm": { "base_url": "https://crm.sandbox.example.com", "api_version": "v2", "timeout_ms": 30000 } } } ``` **Usage in BPMN:** For example, a service task for CRM integration: ``` URL: = camunda.vars.env.INTEGRATIONS.crm.base_url + "/" + camunda.vars.env.INTEGRATIONS.crm.api_version + "/customers" ``` **Benefit:** Centralize integration configuration and easily switch between environments. --- ## Get started with cluster variables Get started with cluster variables by creating your first one and using it in a BPMN process. Throughout this tutorial, you'll build a payment processing workflow that calls different payment API endpoints depending on the environment. ## Step 1: Create a global cluster variable First, create a global cluster variable that serves as your production API configuration. This variable is available to all processes across your cluster. Use the Orchestration Cluster API to [create](../../../../apis-tools/orchestration-cluster-api-rest/specifications/create-global-cluster-variable.api.mdx) a global variable: ```bash POST /v2/cluster-variables/global Content-Type: application/json { "name": "PAYMENT_API_CONFIG", "value": { "endpoint": "https://api.payment.prod.example.com", "timeout_ms": 5000, "retry_count": 3 } } ``` The API returns a confirmation response with your variable details. ## Step 2: Override for a specific tenant Now, create a tenant-specific override for your development environment. This allows you to use the same BPMN process in both environments without modifications. Replace `{tenantId}` with your actual tenant ID (for example, `dev-environment`): ```bash POST /v2/cluster-variables/tenant/{tenantId} Content-Type: application/json { "name": "PAYMENT_API_CONFIG", "value": { "endpoint": "https://api.payment.dev.example.com", "timeout_ms": 30000, "retry_count": 1 } } ``` :::note Processes running in the `dev-environment` tenant automatically use the development API configuration, while all other tenants use the production configuration. ::: ## Step 3: Access the variable when modeling Open Camunda Hub or Desktop Modeler, and create or open a BPMN process. Add a service task to your process that calls the payment API. To use your cluster variable in a service task: 1. Select the service task in your diagram. 2. In the properties panel, navigate to the **Inputs** section. 3. Add an input mapping. 4. For the **Variable assignment value** field, enter the following FEEL expression to access the API endpoint: ``` = camunda.vars.env.PAYMENT_API_CONFIG.endpoint ``` 5. For the **Local variable name** field, enter `apiUrl`. This creates a local variable for your service task. 6. Add another input mapping for the timeout using the following expression: ``` = camunda.vars.env.PAYMENT_API_CONFIG.timeout_ms ``` 7. Set the local variable name to `timeoutMs`. Your service task now has access to both the `apiUrl` and `timeoutMs` variables, which automatically resolve to the correct values based on whether the process runs in your production cluster or the development tenant. ## Step 4: Deploy and test your process 1. Complete your BPMN diagram by adding any additional tasks and an end event. 2. Click **Deploy** to deploy your process to the cluster. 3. Create a process instance by clicking **Run** (or start it via API). 4. [Navigate to Operate](/components/operate/userguide/basic-operate-navigation.md#open-operate) to view your process instance. 5. Inspect the process variables to see that the cluster variables were resolved correctly based on your tenant context. **What happens during execution** - If the process runs in the `dev-environment` tenant, it uses the development API endpoint (`https://api.payment.dev.example.com`) with a 30-second timeout. - If the process runs in any other context, it uses the production API endpoint (`https://api.payment.prod.example.com`) with a five-second timeout. - The BPMN file remains identical across all environments. ## Next steps Congratulations! You've successfully created and used your first cluster variable in a BPMN process. Now, [learn the fundamentals](./overview.md#learn-the-fundamentals) and [explore further resources](./overview.md#explore-further-resources). --- ## Metadata Attach searchable key-value metadata to cluster variables so consumers can discover and filter them without inspecting their values. ## About metadata Each cluster variable can include optional metadata: a map of string keys to scalar values. Metadata is stored alongside the variable but kept separate from its `value`. You can use metadata to annotate variables with information that helps you find them. For example, you could annotate a cluster variable containing a country’s VAT rate with `category=TAX_RATE`, `region=EU`, and `year=2026`. You can then query for all tax rates for a given region and year without knowing the variables’ actual content. Camunda does not interpret metadata keys or values or assign meaning to missing metadata. You can provide empty metadata or omit it entirely. ## Structure and constraints When you add metadata to a cluster variable, it must meet the following requirements: - Keys must be strings. - Values must be **scalars (strings or numbers only)**. You cannot use booleans, arrays, or objects. - Metadata is limited to **100 entries** and and must not exceed the configurable maximum serialized size. By default, each key can contain up to 256 characters, and each value can contain up to 8,192 characters. The API rejects requests that exceed these limits or contain unsupported value types. ## Metadata is not part of the runtime value Metadata is never exposed as part of the FEEL-accessible value. When a process evaluates `camunda.vars.env.` (or the `cluster`/`tenant` namespaces), the expression resolves only to the variable's `value` field. Metadata keys are not included. See [how to use cluster variables](./usage-guide.md) for FEEL access patterns. ## Set metadata You can set metadata when creating or updating a cluster variable through the [Orchestration Cluster API](/apis-tools/orchestration-cluster-api-rest/specifications/create-global-cluster-variable.api.mdx). For example: ```json { "name": "DE_VAT_RATE", "scope": "GLOBAL", "value": { "rate": 0.19 }, "metadata": { "category": "TAX_RATE", "region": "EU", "year": 2026 } } ``` Metadata is included in every get and search response. ## Filter by metadata You can use the `metadata` filter to find cluster variables with specific metadata. For supported filters and request examples, see [search cluster variables](/apis-tools/orchestration-cluster-api-rest/specifications/search-cluster-variables.api.mdx) in the Orchestration Cluster API reference. --- ## Namespace collisions Understand namespace collisions in cluster variables, how scope priority affects variable resolution, and how to safely handle overrides to avoid unexpected behavior. ## About Namespace collisions occur when the same variable key is defined differently across scopes (process, tenant, global). While scope priority determines which value is used, mismatched data types or structures can cause unexpected results. This guide explains common collision types, shows real examples, and shares best practices to prevent or intentionally manage overrides. ## Collision types The examples below show you common patterns and how to reason about them. ### Process variable shadowing If you create a process variable using the cluster variable namespace, it completely shadows cluster variables. #### Scenario ``` GLOBAL: { API_ENDPOINT: "https://api.global.com" } Process Variable Created: camunda.vars.env.API_ENDPOINT = "https://api.override.com" ``` #### Result ``` camunda.vars.env.API_ENDPOINT → "https://api.override.com" ``` #### Why this happens Process variables [have the highest priority](./scope-and-priority.md) in the resolution hierarchy. When you namespace a process variable under `camunda.vars.env`, it takes precedence over cluster variables. This allows intentional, runtime overrides of cluster configuration when necessary. :::tip Use sparingly and document clearly. Prefer a different namespace for process variables to avoid confusion. ::: ### Structural collisions across scopes It happens when you define the same key with different data types or structures at different scopes. :::note This is the most common source of unexpected behavior. ::: #### Scenario ``` TENANT: { CONFIG_KEY: "simple string value" } GLOBAL: { CONFIG_KEY: { nested: "object", with: "properties" } } ``` #### Result ``` camunda.vars.env.CONFIG_KEY → "simple string value" (TENANT wins) camunda.vars.env.CONFIG_KEY.nested → null (trying to access property on string) ``` #### Why this happens Tenant scope has higher priority and returns a string, so the global object is never evaluated. Accessing properties on a string yields null. ### Kind collisions across scopes A kind collision happens when the same key is defined at both scopes with a different [variable kind](./data-types.md#variable-kinds). #### Scenario ``` GLOBAL: { API_CREDENTIALS: "camunda.secrets.PAYMENT_API_KEY" } (kind SECRET_REFERENCE) TENANT: { API_CREDENTIALS: "camunda.secrets.PAYMENT_API_KEY" } (kind JSON) ``` #### Result When an input mapping on a service task reads the key: ``` camunda.vars.env.API_CREDENTIALS → the literal text "camunda.secrets.PAYMENT_API_KEY" (TENANT wins, kind JSON) camunda.vars.cluster.API_CREDENTIALS → the resolved secret value (GLOBAL, kind SECRET_REFERENCE) ``` #### Why this happens Tenant scope has higher priority, so `camunda.vars.env` selects the tenant variable, and only that variable's kind is considered. A `JSON`-kind value is ordinary text, so its references are not resolved. The global variable's references are reachable only through `camunda.vars.cluster`, which bypasses tenant scope. This assumes the tenant variable has a non-empty value. If it's empty, `camunda.vars.env` skips it and falls through to global instead, so the global `SECRET_REFERENCE` variable wins — the opposite of the outcome above. To avoid this, give a key the same kind at every scope where you define it. ### Detailed collision example #### Scenario ``` GLOBAL scope: { KEY_1: { KEY_2: "hello world", KEY_3: "goodbye world" } } TENANT scope: { KEY_1: "tenant value" } ``` #### Results ``` camunda.vars.env.KEY_1 → "tenant value" ✓ Works as expected, TENANT priority camunda.vars.env.KEY_1.KEY_2 → null ✗ Unexpected! Trying to access property on string camunda.vars.cluster.KEY_1.KEY_2 → "hello world" ✓ Bypasses TENANT scope, accesses GLOBAL directly camunda.vars.tenant.KEY_1 → "tenant value" ✓ Direct TENANT access camunda.vars.tenant.KEY_1.KEY_2 → null ✓ Correctly returns null (KEY_1 in TENANT is string) ``` ## Prevent namespace collisions Avoid collisions by keeping structures consistent, separating fundamentally different data under different keys, and documenting what each key represents at each scope. ### Keep data structures consistent Use the same data type and shape for a given key across all scopes. #### Example ``` GLOBAL: { DATABASE_CONFIG: { host: "global-db.example.com", port: 5432, timeout_ms: 5000 } } TENANT: { DATABASE_CONFIG: { host: "tenant-db.example.com", port: 5432, timeout_ms: 10000 } } ``` Both scopes use the same object structure, with tenant‑specific values. ### Use distinct keys If the structures differ, store them under different keys to avoid type conflicts. #### Example ``` GLOBAL: { DEFAULT_ENDPOINT: "https://api.global.com", DEFAULT_CONFIG: { timeout: 5000, retry: 3 } } TENANT: { TENANT_ENDPOINT: "https://api.tenant.com", TENANT_CONFIG: { timeout: 10000, retry: 5, custom_header: "value" } } ``` ### Detect collisions at runtime Add simple guards when accessing nested properties to avoid nulls if a higher‑priority scope supplies a different type. #### Example ``` if camunda.vars.env.CONFIG.nested_value != null then camunda.vars.env.CONFIG.nested_value else "default" ``` ### Document your schema Maintain a schema registry or documentation that specifies: - Expected keys at each scope. - Data type and structure for each key. - Which keys can be overridden at the tenant scope. - Deprecation notices for keys being phased out. ## Process variable namespace best practices ### Avoid using `camunda.vars` namespace To prevent shadowing cluster variables, avoid creating process variables that use the `camunda.vars` namespace. For example, instead of: ``` Set process variable: camunda.vars.env.TIMEOUT = 5000 ``` Do: ``` Set process variable: processTimeout = 5000 ``` ### When override is intentional If you need to override a cluster variable at the process level, document the intent and scope in the process documentation so consumers understand why the override exists and where it applies. --- ## Cluster variables(Cluster-variable) Manage configuration values centrally across your Camunda cluster with cluster variables. ## About Within your Camunda cluster, you can define [variables](../../../../components/concepts/variables.md) at two levels: globally for the entire cluster, or at the tenant level when multi-tenancy is enabled. Cluster variables allows you to maintain environment-specific configurations, API endpoints, feature flags, and other shared values without hardcoding them into individual process definitions. ### Why use Cluster variables provide **centralized**, **flexible**, and **environment-aware configuration** for your processes. They allow you to: - Define configuration once and reuse it across multiple processes. - Use different values for development, staging, and production without changing BPMN models. - Support multi-tenant setups with tenant-specific overrides on top of global defaults. - Promote processes across environments without modifying process definitions. - Update configuration at runtime without redeploying. - Manage shared settings such as API endpoints and service URLs, feature flags, and integration credentials. ### When not to use Consider alternatives for the following use cases: | Use case | Recommended alternative | | ---------------------------------------------- | -------------------------------- | | Process instance-specific data | Process variables | | Values that change frequently during execution | Process variables | | Sensitive credentials | Secrets management | | Large payloads | External storage with references | ## Get started Get started with cluster variables with the following tutorial. Create your first cluster variable ## Learn the fundamentals Understand the fundamental concepts of cluster variables. ## Explore further resources Dive into common use cases and the API documentation to extend your knowledge. --- ## Scope resolution Learn how cluster variable scope resolution works, the available scopes, and how to access and use them. ## About Cluster variables use a scope priority process to determine which value is used when the same key exists at multiple levels. This guide helps you understand this scope resolution to predict values in your processes and avoid unexpected behavior. ## Scope levels Cluster variables exist at the **global** and **tenant** scope levels. ### Global scope Variables defined at the global scope are available across the entire cluster and accessible by all processes, regardless of tenant context. :::info Global-scope cluster variables are ideal for cluster-wide defaults and shared configuration. ::: They have lowest priority in variable resolution and are managed via the global cluster variables API. ### Tenant scope Variables defined at the tenant scope are specific to a particular tenant and only accessible within that tenant's context. :::note Tenant-scope cluster variables are ideal for tenant-specific customization and overrides. ::: They have higher priority than global-scope ones in variable resolution and are managed via the tenant-specific cluster variables API. :::important They are available only when multi-tenancy is enabled. ::: ## Resolution priority From highest to lowest priority, the cluster variable resolution order is: 1. Process variables. 2. Tenant-scope cluster variables. 3. Global-scope cluster variables. Resolution priority is the same for every [variable kind](./data-types.md#variable-kinds). The `camunda.vars.env`, `camunda.vars.tenant`, and `camunda.vars.cluster` namespaces select a `SECRET_REFERENCE`-kind variable exactly as they select a `JSON`-kind one. The kind of the variable that wins is what decides whether its `camunda.secrets.` references are resolved. ### Process variables Variables defined on the process instance always take precedence over cluster variables. This includes: - Variables set during instantiation. - Variables created or updated during execution. - Variables passed from parent to child processes. :::note If you create a process variable with a key that matches a cluster variable path (for example, `camunda.vars.env.API_URL`), the process variable will shadow the cluster variable completely. ::: ### Tenant-scope cluster variables When multi-tenancy is enabled, tenant-scoped variables: - Override global scope variables with the same key. - Are visible only to processes in that tenant. - Enable tenant-specific customization without affecting other tenants. ### Global-scope cluster variables Global cluster variables provide cluster-wide defaults and baseline configuration. These variables: - Are accessible to all processes across all tenants. - Serve as fallbacks when no tenant-specific override exists. - Provide consistent defaults for new tenants. ### Resolution examples #### Basic scope override ``` GLOBAL: { API_TIMEOUT: 5000 } TENANT: { API_TIMEOUT: 10000 } PROCESS: (none) Result when accessing camunda.vars.env.API_TIMEOUT: → 10000 (TENANT value) ``` #### Partial override ``` GLOBAL: { API_CONFIG: { timeout: 5000, retry: 3, url: "https://api.global.com" } } TENANT: { API_CONFIG: { timeout: 10000 } } PROCESS: (none) Result when accessing camunda.vars.env.API_CONFIG.timeout: → 10000 (TENANT value) Result when accessing camunda.vars.env.API_CONFIG.retry: → null (TENANT defined API_CONFIG as a different object) ``` :::warning When you override a key at the tenant scope, the entire value at that key is replaced, not merged. Partial object merging does not occur. ::: #### Process variable override ``` GLOBAL: { MAX_AMOUNT: 1000 } TENANT: { MAX_AMOUNT: 5000 } PROCESS: { camunda.vars.env.MAX_AMOUNT: 10000 } Result when accessing camunda.vars.env.MAX_AMOUNT: → 10000 (PROCESS variable) ``` ## Multi-tenant considerations ### Tenant isolation Each tenant's cluster variables are fully isolated. A process running in one tenant cannot access cluster variables defined for another tenant, even with explicit namespace access. ### Global defaults pattern Define sensible defaults globally and override only what you need at the tenant level: ``` GLOBAL: { RATE_LIMITS: { requests_per_minute: 100, burst_limit: 150, concurrent_connections: 10 } } TENANT (premium): { RATE_LIMITS: { requests_per_minute: 1000, burst_limit: 1500, concurrent_connections: 100 } } ``` ## Troubleshoot scope resolution 1. **Check all three scope levels**: Verify values at global, tenant, and process levels. 2. **Use scope-specific namespaces**: Test with `camunda.vars.cluster` and `camunda.vars.tenant` to isolate scope. 3. **Verify tenant context**: Ensure the process is running in the expected tenant. 4. **Look for shadowing**: Check for process variables that might shadow cluster variables. 5. **Validate structure**: Ensure objects have consistent structure across scopes. --- ## How to use cluster variables Learn how to use and access cluster variables, from simple to advanced patterns. ## Use in BPMN ### Service task input mapping Map cluster variables to service task inputs. For example: ``` = camunda.vars.env.API_ENDPOINT ``` For the target variable: ``` apiUrl ``` ### Gateway conditions Use cluster variables in conditional sequence flows. For example: ``` orderAmount > camunda.vars.env.APPROVAL_THRESHOLD camunda.vars.env.FEATURE_EXPRESS_SHIPPING = true ``` ### Script tasks Reference cluster variables in script expressions. For example: ``` var endpoint = camunda.vars.env.API_CONFIG.base_url; var timeout = camunda.vars.env.API_CONFIG.timeout_ms; ``` ### Output mappings Use cluster variables in output parameter expressions. For example: ``` = { "endpoint": camunda.vars.env.SERVICE_URL, "timestamp": now(), "threshold": camunda.vars.env.PROCESSING_THRESHOLD } ``` ### Call activities Pass cluster variables as input to called processes. For example: ``` = { "config": camunda.vars.env.SUBPROCESS_CONFIG, "flags": camunda.vars.env.FEATURE_FLAGS } ``` ### Combining multiple variables Create expressions using multiple cluster variables. For example: ``` camunda.vars.env.API_BASE_URL + "/api/v" + camunda.vars.env.API_VERSION + "/resource" ``` ### Resolve secret references in a cluster variable [Orchestration Cluster secret references](/reference/glossary.md#secret-reference-orchestration-cluster) in a [`SECRET_REFERENCE`-kind](./data-types.md#variable-kinds) cluster variable are resolved only when the variable is read by an input mapping on an element that creates a job for a job worker, such as a service task or an ad hoc sub-process. In the other contexts on this page, including gateway conditions, script tasks, output mappings, and call activity input, the variable resolves to its stored value, so the reference text reaches your process unchanged. The following rules apply to an input mapping that reads a `SECRET_REFERENCE`-kind variable: - The mapping source must be a FEEL expression. A static value, written without a leading `=`, is a plain string and holds no references. - A trailing field path narrows what is resolved. `= camunda.vars.env.MY_VAR.a.b` resolves only the references stored inside the `a.b` part of the value. - If the variable does not exist, or if its kind is `JSON`, nothing is resolved and no incident is raised. - Execution listener and task listener jobs never carry resolved values, even when the element they run on has such an input mapping. The reference is recorded on the job at creation, in the same way as a reference written directly into an input mapping, and resolved in the background ahead of activation. The resolved value reaches the worker only once the job is handed out. For what this means for when a job reaches a worker, see [secret resolution and job activation](/components/concepts/secret-resolution-and-job-activation.md). ## Access using FEEL expressions You can reference cluster variables anywhere the Camunda modeling interface supports FEEL expressions. They are exposed through the following namespaces: - `camunda.vars.cluster` - `camunda.vars.tenant` - `camunda.vars.env` These namespaces correspond to the available scope levels. See [scope resolution](./scope-and-priority.md) for more details. :::tip Camunda recommends using the `camunda.vars.env` namespace for most use cases. ::: #### `camunda.vars.cluster` Provides direct access **only** to global-scope variables. Tenant-scope variables are not accessible through this namespace. **Use when:** - You want to access only global variables. - You need to bypass tenant-level overrides. - You're debugging scope resolution. For example: ``` camunda.vars.cluster.GLOBAL_DEFAULT_TIMEOUT ``` #### `camunda.vars.tenant` Provides direct access **only** to tenant-scope variables. Global-scope variables are not accessible through this namespace. **Use when:** - You want to access only tenant variables. - You need to check tenant-specific values. - You're debugging scope resolution. For example: ``` camunda.vars.tenant.TENANT_SPECIFIC_CONFIG ``` #### `camunda.vars.env` Provides a merged view of both global- and tenant-scope variables, applying automatic priority resolution. This is the recommended namespace for most cases. **Use when:** - You want automatic scope resolution. - You need the most specific value available. - You're writing portable process definitions. For example: ``` camunda.vars.env.API_ENDPOINT camunda.vars.env.CONFIG.timeout ``` ### Simple key access Use dot notation after the namespace for simple key-value pairs. For example: ``` camunda.vars.env.API_URL camunda.vars.env.MAX_RETRIES camunda.vars.env.FEATURE_ENABLED camunda.vars.env.TIMEOUT_SECONDS ``` ### Nested object access For cluster variables with nested object structures, use dot notation to access deeper levels. For example: ``` camunda.vars.env.DATABASE_CONFIG.host camunda.vars.env.DATABASE_CONFIG.port camunda.vars.env.DATABASE_CONFIG.credentials.username camunda.vars.env.API_SETTINGS.retry.max_attempts camunda.vars.env.API_SETTINGS.retry.backoff_ms camunda.vars.env.API_SETTINGS.timeout.connection ``` ### Conditional access Provide fallback values when cluster variables might not exist. For example: ``` if camunda.vars.env.CUSTOM_TIMEOUT != null then camunda.vars.env.CUSTOM_TIMEOUT else 5000 ``` ### Dynamic key access While dynamic key access is limited in FEEL, you can structure your variables to support configuration-driven behavior. For example: ``` camunda.vars.env.COUNTRY_CONFIGS[countryCode].tax_rate ``` ## Usage best practices #### Use meaningful names Choose clear, descriptive variable names that indicate purpose and scope. For example: ``` ✓ camunda.vars.env.PAYMENT_API_ENDPOINT ✗ camunda.vars.env.URL1 ``` #### Group related configuration Use nested objects to organize related values. For example: ``` ✓ camunda.vars.env.PAYMENT_CONFIG.endpoint ✓ camunda.vars.env.PAYMENT_CONFIG.timeout ✓ camunda.vars.env.PAYMENT_CONFIG.retry_count ``` #### Follow naming conventions Establish and follow naming patterns across your organization. For example: - `UPPER_SNAKE_CASE` for top-level keys. - `camelCase` or `snake_case` for nested properties. #### Document variable contracts Maintain documentation of expected cluster variables, their structures, and allowed overrides per process. --- ## FEEL Playground Use the FEEL Playground to validate and troubleshoot your FEEL expressions when modeling process diagrams in Camunda Hub. ## About FEEL Playground When using the [FEEL expression language](/components/modeler/feel/what-is-feel.md) in Camunda, your FEEL expressions must be valid. The FEEL Playground allows you to test and validate your FEEL expressions using sample contextual data. The FEEL Playground is integrated into the popup FEEL editor: - **FEEL expression**: Enter and edit the FEEL expression you want to validate. - **Context**: A JSON object containing [sample data and variables](/components/modeler/data-handling.md) for evaluating your expression. Camunda saves the context for the field and restores it when you reopen the FEEL Playground. - **Result**: Shows the result of evaluating the expression against the context, including warnings or errors. - **FEEL Copilot**: (For SaaS only) Open the [FEEL Copilot (alpha feature)](/components/early-access/alpha/alpha-features.md) to chat with the AI FEEL Copilot and get help with generating expressions. :::note The FEEL Playground evaluates expressions in the connected environment when it runs Camunda 8.9 or newer. If runtime evaluation isn't available, it uses the built-in FEEL engine and displays the reason in a warning badge. ::: ## Validate your FEEL expression {#validate} To use the FEEL Playground for validation: 1. Open the properties panel of a diagram element containing the FEEL expression you want to validate. 1. To open the popup FEEL editor, click **fx** on the FEEL expression field, and click the **Open popup editor** icon in the field. 1. In the popup FEEL editor, enter your expression and context. Review the result, warnings, or errors, and edit the expression as required. ## Prompting in FEEL Playground {#prompting} Use prompting in FEEL Playground to help you write FEEL expressions. To prompt in FEEL Playground: 1. Open the properties panel of a diagram element that contains the FEEL expression you want to validate. 1. In the FEEL expression field, click **fx**, then click **Open popup editor**. 1. In the popup editor, enter your prompt details. 1. Click **Use expression** to use your prompt in your FEEL expression. ### LLM provider-specific prompting Prompting supports prompt creation for specific LLM providers. For example, a prompt like **"Please create a prompt for a classification agent that needs to classify text received by a customer to a list of "sentiment". My model is OpenAI o3."** is treated as an OpenAI-specific prompt. --- ## Boolean expressions ### Literal Creates a new boolean value. ```feel true false ``` ### Comparison Compares two values with one of the following operators. Both values must be of the same type. Otherwise, the result is `null`. Operator Description Supported types = equal to any != not equal to any < less than number, string, date, time, date-time, duration <= less than or equal to number, string, date, time, date-time, duration > greater than number, string, date, time, date-time, duration >= greater than or equal number, string, date, time, date-time, duration between [x] and [y] same as (_ >= x and _ <= y) number, string, date, time, date-time, duration ```feel 5 = 5 // true 5 != 5 // false date("2020-04-05") < date("2020-04-06") // true time("08:00:00") <= time("08:00:00") // true duration("P1D") > duration("P5D") // false duration("P1Y") >= duration("P6M") // true 5 between 3 and 7 // true date("2020-04-06") between date("2020-04-05") and date("2020-04-09") // true ``` :::caution Be Careful! The equals operator has only **one** equals sign (e.g. `x = y`). In other languages, the operator has two equals signs (e.g. `x == y`). ::: ### Null check Any value or variable can be compared with `null` to check if it is equal to `null`, or if it exists. Comparing `null` to a value different from `null` results in `false`. It returns `true` if the value is `null` or the variable doesn't exist. Comparing a context entry with `null` results in `true` if the value of the entry is `null` or if the context doesn't contain an entry with this key. ```feel null = null // true "foo" = null // false {x: null}.x = null // true {}.y = null // true ``` ### Conjunction/and Combines multiple boolean values following the ternary logic. - The result is `true` if all values are `true`. - The result is `false` if one value is `false`. - Otherwise, the result is `null` (i.e. if a value is not a boolean.) ```feel true and true // true true and false // false true and null // null true and "otherwise" // null false and null // false false and "otherwise" // false ``` ### Disjunction/or Combines multiple boolean values following the ternary logic. - The result is `true` if at least one value is `true`. - The result is `false` if all values are `false`. - Otherwise, the result is `null` (i.e. if a value is not a boolean.) ```feel true or false // true false or false // false true or null // true true or "otherwise" // true false or null // null false or "otherwise" // null ``` ### Instance of Checks if the value is of the given type. Available type names: - `boolean` - `number` - `string` - `date` - `time` - `date and time` - `days and time duration` - `years and months duration` - `list` - `context` - `function` - `Any` Use the type `Any` to check if the value is not `null`. ```feel 1 instance of number // true 1 instance of string // false 1 instance of Any // true null instance of Any // false duration("P3M") instance of years and months duration // true duration("PT4H") instance of days and time duration // true ``` ### Unary-tests/in Evaluates a [unary-tests](/components/modeler/feel/language-guide/feel-unary-tests.md) with the given value. The keyword `in` separates the value from the unary-tests. ```feel 5 in (3..7) // true date("2021-06-04") in [date("2021-05-01")..date("2021-05-31")] // false 5 in (3,5,7) // true 5 in [2,4,6,8] // false ``` --- ## Context expressions You can use the following FEEL context expressions. Examples are provided to show common use cases. ### Literal Creates a new context with the given entries. - Each entry has a key and a value. - The key is either a name or a string. - The value can be any type. :::info For valid key names, see [naming conventions](./feel-variables.md#variable-names). ::: ```feel { a: 1, b: 2 } // {a:1, b:2} { "a": 1, "b": 2 } // {a:1, b:2} ``` Inside the context, the previous entries can be accessed. ```feel { a: 2, b: a * 2 } // {a:2, b:4} ``` A context value can embed other context values. ```feel { a: 1, b: { c: 2 } } // {a:1, b:{c:2}} ``` ### Get entry/path ```feel a.b ``` Accesses the entry with the key `b` of the context `a`. The path is separated by a dot `.`. If the value of the entry `b` is also a context, the path can be chained (i.e. `a.b.c`). ```feel {a: 2}.a // 2 {a: {b: 3}}.a // {b: 3} {a: {b: 3}}.a.b // 3 ``` If the context `a` doesn't contain an entry with the key `b`, the expression returns `null`. ```feel {a: 1}.b // null {a: 1}.b.c // null ``` ### Filter ```feel a[c] ``` Filters the list of context elements `a` by the condition `c`. The result of the expression is a list that contains all elements where the condition `c` evaluates to `true`. The other elements are excluded. While filtering, the current element is assigned to the variable `item` and its entries can be accessed by their key. ```feel [ { a: "p1", b: 5 }, { a: "p2", b: 10 } ][b > 7] // [{a: "p2", b: 10}] ``` ### Projection ```feel a.b ``` Extracts the entries with the key `b` of the list of context elements `a` (that is, a projection). It returns a list containing the values of the context elements with the key `b`. ```feel [ { a: "p1", b: 5 }, { a: "p2", b: 10 } ].a // ["p1", "p2"] ``` If an element of the list `a` doesn't contain an entry with the key `b`, the result contains `null` of this element. ```feel [ { a: "p1", b: 5 }, { a: "p2", c: 20 } ].b // [5, null] ``` ## Examples ### Validate data Validate journal entries and return all violations. ```feel { check1: { error: "Document Type invalid for current year posting", violations: collection[documentType = "S2" and glDate > startFiscalYear] }, check2: { error: "Document Type invalid for current year posting", violations: collection[ledgerType = "GP" and foreignAmount != null] }, result: [check1, check2][count(violations) > 0] } ``` ### Structure calculation Calculate the minimum age of a given list of birthdays. ```feel { age: function(birthday) (today() - birthday).years, ages: for birthday in birthdays return age(birthday), minAge: min(ages) }.minAge ``` --- ## Control flow ### If conditions ```feel if c then a else b ``` Executes the expression `a` if the condition `c` evaluates to `true`. Otherwise, it executes the expression `b`. ```feel if 5 < 10 then "low" else "high" // "low" if 12 < 10 then "low" else "high" // "high" ``` :::info good to know If the condition `c` doesn't evaluate to a boolean value (e.g. `null`), it executes the expression `b`. ```feel if null then "low" else "high" // "high" ``` ::: ### For loops ```feel for a in b return c ``` Iterates over the list `b` and executes the expression `c` for each element in the list. The current element is assigned to the variable `a`. The result of the expression is a list. If multiple lists are passed to the `for` loop then it iterates over the cross-product of the elements in the given lists. ```feel for x in [1,2,3] return x * 2 // [2,4,6] for x in [1,2], y in [3,4] return x * y // [3, 4, 6, 8] ``` While iterating over the list, the previous elements are assigned to the variable `partial`. ```feel for i in 1..10 return if (i <= 2) then 1 else partial[-1] + partial[-2] // [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] ``` :::tip The variable `partial` can be used to retrieve the index of the current element. ```feel for x in ["a","b","c"] return {value: x, index: count(partial) + 1} // [{"value":"a","index":1},{"value":"b","index":2},{"value":"c","index":3}] ``` ::: Instead of a list, the `for` loop can also iterate over a given range. ```feel for x in 0..8 return 2 ** x // [1, 2, 4, 8, 16, 32, 64, 128, 256] for x in 3..1 return 2 * x // [6,4,2] ``` --- ## Data types(Language-guide) FEEL defines the following types: ### Null Nothing, null, or nil (i.e. the value is not present). - Java Type: `null` ```feel null ``` ### Number A whole or floating point number. The number can be negative. - not-a-number (NaN), positive/negative infinity are represented as `null` - Java Type: `java.math.BigDecimal` ```feel 1 2.3 .4 -5 ``` ### String A sequence of characters enclosed in double quotes `"`. The sequence can contain escaped characters starting with `\` (e.g. `\'`, `\"`, `\\`, `\n`, `\r`, `\t`, Unicode like `\u269D` or `\U101EF`) and can span multiple lines. - Java Type: `java.lang.String` ```feel "valid" ``` ### Boolean A boolean value. It is either true or false. - Java Type: `java.lang.Boolean` ```feel true false ``` ### Date A date value without a time component. - Format: `yyyy-MM-dd`. - Java Type: `java.time.LocalDate` ```feel date("2017-03-10") @"2017-03-10" ``` ### Time A local or zoned time. The time can have an optional timezone offset. The usage of a time zone ID is deprecated because it can lead to ambiguous results without a date component. - Format: `HH:mm:ss` / `HH:mm:ss+/-HH:mm` - Java Type: `java.time.LocalTime` / `java.time.OffsetTime` ```feel time("11:45:30") time("13:30") time("11:45:30+02:00") @"11:45:30" @"13:30" @"11:45:30+02:00" ``` ### Date-time A date with a local or zoned time component. The time can have an offset or time zone ID. - Formats: - `yyyy-MM-dd'T'HH:mm:ss` - `yyyy-MM-dd'T'HH:mm:ssZ` - `yyyy-MM-dd'T'HH:mm:ss+/-HH:mm` - `yyyy-MM-dd'T'HH:mm:ss@ZoneId` - `yyyy-MM-dd'T'HH:mm:ss+/-HH:mm[ZoneId]` - Java Type: `java.time.LocalDateTime` / `java.time.DateTime` ```feel date and time("2015-09-18T10:31:10") date and time("2015-09-18T10:31:10Z") date and time("2015-09-18T10:31:10+01:00") date and time("2015-09-18T10:31:10@Europe/Paris") date and time("2015-09-18T10:31:10+02:00[Europe/Paris]") @"2015-09-18T10:31:10" @"2015-09-18T10:31:10Z" @"2015-09-18T10:31:10+01:00" @"2015-09-18T10:31:10@Europe/Paris" @"2015-09-18T10:31:10+02:00[Europe/Paris]" ``` ### Days-time-duration A duration based on seconds. It can contain days, hours, minutes, and seconds. - Format: `PxDTxHxMxS` - Java Type: `java.time.Duration` ```feel duration("P4D") duration("PT2H") duration("PT30M") duration("P1DT6H") @"P4D" @"PT2H" @"PT30M" @"P1DT6H" ``` ### Years-months-duration A duration based on the calendar. It can contain years and months. - Format: `PxYxM` - Java Type: `java.time.Period` ```feel duration("P2Y") duration("P6M") duration("P1Y6M") @"P2Y" @"P6M" @"P1Y6M" ``` ### List A list of elements. The elements can be of any type. The list can be empty. - Java Type: `java.util.List` ```feel [] [1,2,3] ["a","b"] [["list"], "of", [["lists"]]] ``` ### Context A list of entries. Each entry has a key and a value. The key is either a name or a string. The value can be any type. The context can be empty. - Java Type: `java.util.Map` ```feel {} {a:1} {b: 2, c: "valid"} {nested: {d: 3}} {"a": 1} {"b": 2, "c": "valid"} ``` --- ## Error handling(Language-guide) FEEL doesn't define any error handling. Instead, it follows one simple rule: if something goes wrong, return `null`. ### Null-friendly If an expression can't be evaluated successfully, it returns `null`. For example, in the following cases: - No variable exists with the given name - No context entry exists with the given key - No function exists with the given name - A function can't be invoked successfully with the given arguments - A value is compared to another value of a different type - An operation is not defined for the given values ### Handle null values Expressions and operators can deal with `null` values. In many cases, they result also in `null`. ```feel a.b > 10 // null [{a: 1}, {b: 2}].a // [1, null] [{a: 1}, {b: 2}][a < 10] // [{a: 1}] ``` To handle `null` values explicitly, use a [null-check](/components/modeler/feel/language-guide/feel-boolean-expressions.md#null-check) or the function [`get or else()`](/components/modeler/feel/builtin-functions/feel-built-in-functions-boolean.md#get-or-elsevalue-default). ```feel a != null and a.b > 10 get or else(a, "prio-99") ``` ### Assertions By default, the evaluation of an expression doesn't fail but returns `null`. If there is a special need to fail the evaluation under a certain condition, use the function [`assert()`](/components/modeler/feel/builtin-functions/feel-built-in-functions-boolean.md#assertvalue-condition). ```feel assert(a, a != null) // returns a if a is not null // fails if a is null assert(b, b >= 0, "'b' should be positive") ``` --- ## Introduction(Language-guide) FEEL expressions are powerful and can be used for various cases. This section is split into expressions based on their operational data type: - [Boolean](./feel-boolean-expressions.md) - [String](./feel-string-expressions.md) - [Numeric](./feel-numeric-expressions.md) - [List](./feel-list-expressions.md) - [Context](./feel-context-expressions.md) - [Temporal](./feel-temporal-expressions.md) The following sections cover more general areas that are not restricted to one data type: - [Variables](./feel-variables.md) - [Control flow](./feel-control-flow.md) - [Functions](./feel-functions.md) - [Error handling](./feel-error-handling.md) ### Comments An expression can contain comments to explain it and give it more context. This can be done using Java-style comments: `//` to the end of line, or `/*.... */` for blocks. ```feel // returns the last item [1,2,3,4][-1] /* returns the last item */ [1,2,3,4][-1] /* * returns the last item */ [1,2,3,4][-1] ``` ### Parentheses Parentheses `( .. )` can be used in expressions to separate different parts of an expression or to influence the precedence of the operators. ```feel (5 - 3) * (4 / 2) x < 5 and (y > 10 or z > 20) if (5 < 10) then "low" else "high" ``` --- ## Functions ### Invocation Invokes a built-in function (e.g. [`contains()`](/components/modeler/feel/builtin-functions/feel-built-in-functions-string.md#containsstring-match)) or a user-defined function by its name. The arguments of the function can be passed positional or named. - Positional: Only the values, in the same order as defined by the function (e.g. `f(1,2)`). - Named: The values with the argument name as prefix, in any order (e.g. `f(a: 1, b: 2)`). ```feel contains("me@camunda.com", ".com") // true contains(string: "me@camunda.com", match: ".de") // false ``` :::info GOOD TO KNOW The invocation returns `null` if no function exists with the given name, or if the argument types don't match the function signature. ::: ### User-defined ```feel function(a,b) e ``` Defines a function with a list of argument names, and an expression (i.e. the function body). When the function is invoked, it assigns the values to the arguments and evaluates the expression. Within an expression, a function can be defined and invoked in a context. ```feel { age: function(birthday) (today() - birthday).years } ``` --- ## List expressions You can use the following FEEL list expressions. Examples are provided to show common use cases. ### Literal Creates a new list of the given elements. The elements can be of any type. ```feel [1,2,3,4] ``` A list value can embed other list values. ```feel [[1,2], [3,4], [5,6]] ``` ### Get element ```feel a[i] ``` Accesses an element of the list `a` at index `i`. The index starts at `1`. If the index is out of the range of the list, it returns `null`. ```feel [1,2,3,4][1] // 1 [1,2,3,4][2] // 2 [1,2,3,4][4] // 4 [1,2,3,4][5] // null [1,2,3,4][0] // null ``` If the index is negative, it starts counting the elements from the end of the list. The last element of the list is at index `-1`. ```feel [1,2,3,4][-1] // 4 [1,2,3,4][-2] // 3 [1,2,3,4][-5] // null ``` :::caution be careful! The index of a list starts at `1`. In other languages, the index starts at `0`. ::: ### Filter ```feel a[c] ``` Filters the list `a` by the condition `c`. The result of the expression is a list that contains all elements where the condition `c` evaluates to `true`. The other elements are excluded. While filtering, the current element is assigned to the variable `item`. ```feel [1,2,3,4][item > 2] // [3,4] [1,2,3,4][item > 10] // [] [1,2,3,4][even(item)] // [2,4] ``` ### Some ```feel some a in b satisfies c ``` Iterates over the list `b` and evaluate the condition `c` for each element in the list. The current element is assigned to the variable `a`. It returns `true` if `c` evaluates to `true` for **one or more** elements of `b`. Otherwise, it returns `false`. ```feel some x in [1,2,3] satisfies x > 2 // true some x in [1,2,3] satisfies x > 5 // false some x in [1,2,3] satisfies even(x) // true some x in [1,2], y in [2,3] satisfies x < y // true ``` ### Every Iterates over the list `b` and evaluate the condition `c` for each element in the list. The current element is assigned to the variable `a`. It returns `true` if `c` evaluates to `true` for **all** elements of `b`. Otherwise, it returns `false`. ```feel every x in [1,2,3] satisfies x >= 1 // true every x in [1,2,3] satisfies x >= 2 // false every x in [1,2,3] satisfies even(x) // false every x in [1,2], y in [2,3] satisfies x < y // false ``` ## Examples ### Filter list and return the first element Return the first packaging element which unit is "Palette". ```feel data.attribute.packaging[unit = "Palette"][1] ``` ### Group list Group the given list of invoices by their person. Each invoice has a person. The persons are extracted from the invoices and are used as a filter for the list. ```feel for p in distinct values(invoices.person) return invoices[person = p] ``` #### Evaluation context ```feel {"invoices":[ {"id":1, "person":"A", "amount": 10}, {"id":2, "person":"A", "amount": 20}, {"id":3, "person":"A", "amount": 30}, {"id":4, "person":"A", "amount": 40}, {"id":5, "person":"B", "amount": 15}, {"id":6, "person":"B", "amount": 25} ]} ``` #### Evaluation result ```feel [ [ { id: 1, person: "A", amount: 10 }, { id: 2, person: "A", amount: 20 }, { id: 3, person: "A", amount: 30 }, { id: 4, person: "A", amount: 40 }, ], [ { id: 5, person: "B", amount: 15 }, { id: 6, person: "B", amount: 25 }, ], ] ``` ### Merge two lists Merge two given lists. Each list contains context values with the same structure. Each context has an `id` entry that identifies the value. The result is a list that contains all context values grouped by the identifier. ```feel { ids: union(x.files.id,y.files.id), getById: function (files,fileId) get or else(files[id=fileId][1], {}), merge: for id in ids return context merge(getById(x.files, id), getById(y.files, id)) }.merge ``` #### Evaluation context ```feel { "x": {"files": [ {"id":1, "content":"a"}, {"id":2, "content":"b"} ]}, "y": {"files": [ {"id":1, "content":"a2"}, {"id":3, "content":"c"} ]} } ``` #### Evaluation result ```feel [ { id: 1, content: "a2" }, { id: 2, content: "b" }, { id: 3, content: "c" }, ] ``` --- ## Numeric expressions ### Literal Creates a new numeric value. Leading zeros are valid. ```feel 1 0.5 .5 -2 01 -0002 ``` ### Addition Adds a value to another value. ```feel 2 + 3 // 5 ``` ### Subtraction Subtracts a value from another value. ```feel 5 - 3 // 2 ``` ### Multiplication Multiplies a value by another value. ```feel 5 * 3 // 15 ``` ### Division Divides a value by another value. ```feel 6 / 2 // 3 ``` ### Exponentiation Raises a value to the power of another value. ```feel 2 ** 3 // 8 ``` --- ## String expressions ### Literal Creates a new string value. ```feel "valid" ``` ### Addition/concatenation An addition concatenates the strings. The result is a string containing the characters of both strings. ```feel "foo" + "bar" // "foobar" ``` :::tip The concatenation is only available for string values. For other types, you can use the [`string()`](/components/modeler/feel/builtin-functions/feel-built-in-functions-conversion.md#stringfrom) function to convert the value into a string first. ```feel "order-" + string(123) ``` ::: --- ## Temporal expressions You can use the following FEEL temporal expressions. Examples are provided to show common use cases. ### Literal Creates a new temporal value. A value can be written in one of the following ways: - using a temporal function (for example, `date("2020-04-06")`) - using the `@` - notation (for example, `@"2020-04-06"`) ```feel date("2020-04-06") @"2020-04-06" time("08:00:00") time("08:00:00+02:00") time("08:00:00@Europe/Berlin") @"08:00:00" @"08:00:00+02:00" @"08:00:00@Europe/Berlin" date and time("2020-04-06T08:00:00") date and time("2020-04-06T08:00:00+02:00") date and time("2020-04-06T08:00:00@Europe/Berlin") @"2020-04-06T08:00:00" @"2020-04-06T08:00:00+02:00" @"2020-04-06T08:00:00@Europe/Berlin" duration("P5D") duration("PT6H") @"P5D" @"PT6H" duration("P1Y6M") duration("P3M") @"P1Y6M" @"P3M" ``` The value is `null` if a date or date-time literal doesn't represent a valid calendar date. For example, `@"2024-06-31"` is invalid because June has only 30 days. ### Addition Adds a value to another value. The operator is defined for the following types. If a value has a different type, the result is `null`. First argument Second argument Result date duration date time days-time-duration time date-time duration date-time duration date date duration time time duration date-time date-time duration duration duration ```feel date("2020-04-06") + duration("P1D") // date("2020-04-07") time("08:00:00") + duration("PT1H") // time("09:00:00") date and time("2020-04-06T08:00:00") + duration("P7D") // date and time("2020-04-13T08:00:00") duration("P2D") + duration("P5D") // duration("P7D") ``` ### Subtraction Subtracts a value from another value. The operator is defined for the following types. If a value has a different type, the result is `null`. If one value has a timezone or time-offset, the other value must have a timezone or time-offset too. Otherwise, the result is `null`. First argument Second argument Result date date days-time-duration date duration date time time days-time-duration time days-time-duration time date-time date-time days-time-duration date-time duration date-time days-time-duration days-time-duration days-time-duration years-months-duration years-months-duration years-months-duration ```feel date("2020-04-06") - date("2020-04-01") // duration("P5D") date("2020-04-06") - duration("P5D") // date("2020-04-01") time("08:00:00") - time("06:00:00") // duration("PT2H") time("08:00:00") - duration("PT2H") // time("06:00:00") duration("P7D") - duration("P2D") // duration("P5D") duration("P1Y") - duration("P3M") // duration("P9M") ``` ### Multiplication Multiplies a value by another value. The operator is defined for the following types. If a value has a different type, the result is `null`. First argument Second argument Result days-time-duration number days-time-duration number days-time-duration days-time-duration years-months-duration number years-months-duration number years-months-duration years-months-duration ```feel duration("P1D") * 5 // duration("P5D") duration("P1M") * 6 // duration("P6M") ``` ### Division Divides a value by another value. The operator is defined for the following types. If a value has a different type, the result is `null`. First argument Second argument Result days-time-duration days-time-duration number days-time-duration number days-time-duration years-months-duration years-months-duration number years-months-duration number years-months-duration ```feel duration("P5D") / duration("P1D") // 5 duration("P5D") / 5 // duration("P1D") duration("P1Y") / duration("P1M") // 12 duration("P1Y") / 12 // duration("P1M") ``` ### Properties A temporal value has multiple properties for its components. The following properties are available for the given types: Property Available for Description year date, date-time the year as number month date, date-time the month as number [1..12], where 1 is January day date, date-time the day of the month as number [1..31] weekday date, date-time the day of the week as number [1..7], where 1 is Monday hour time, date-time the hour of the day as number [0..23] minute time, date-time the minute of the hour as number [0..59] second time, date-time the second of the minute as number [0..59] time offset time, date-time the duration offset corresponding to the timezone or null timezone time, date-time the timezone identifier or null days days-time-duration the normalized days component as number hours days-time-duration the normalized hours component as number [0..23] minutes days-time-duration the normalized minutes component as number [0..59] seconds days-time-duration the normalized seconds component as number [0..59] years years-months-duration the normalized years component as number months years-months-duration the normalized months component as number [0..11] ```feel date("2020-04-06").year // 2020 date("2020-04-06").month // 4 date("2020-04-06").weekday // 1 time("08:00:00").hour // 8 date and time("2020-04-06T08:00:00+02:00").time offset // duration("PT2H") date and time("2020-04-06T08:00:00@Europe/Berlin").timezone // "Europe/Berlin" duration("PT2H30M").hours // 2 duration("PT2H30M").minutes // 30 duration("P6M").months // 6 ``` ## Examples ### Compare date with offset Check if a date is at least 6 months before another date. ```feel date1 < date2 + @"P6M" ``` ### Calculate age Return the current age of a person based on a given birthday. ```feel years and months duration(date(birthday), today()).years ``` ### Check for weekend Check if the current day is on a weekend. ```feel day of week(today()) in ("Saturday","Sunday") ``` ### Calculate duration between dates Return the duration between now and the next Tuesday at 08:00. ```feel (for x in 1..7 return date and time(today(),@"08:00:00Z") + @"P1D" * x )[day of week(item) = "Tuesday"][1] - now() ``` ### Calculate duration between times Return the duration between now and the next time it is 09:00 in the Europe/Berlin timezone. ```feel { time: @"09:00:00@Europe/Berlin", date: if time(now()) < time then today() else today() + @"P1D", duration: date and time(date, time) - now() }.duration ``` ### Calculate next weekday Return the next day that is not a weekend at 00:00. ```feel (for x in 1..3 return date and time(today(),@"00:00:00Z") + @"P1D" * x )[not(day of week(item) in ("Saturday","Sunday"))][1] ``` ### Change format of dates Transform a given list of date-time values into a custom format. ```feel for d in dates return { date: date(date and time(d)), day: string(date.day), month: substring(month of year(date), 1, 3), year: string(date.year), formatted: day + "-" + month + "-" + year }.formatted ``` #### Evaluation context ```feel ["2021-04-21T07:25:06.000Z", "2021-04-22T07:25:06.000Z"] ``` #### Evaluation result ```feel ["21-Apr-2021", "22-Apr-2021"] ``` ### Create a Unix timestamp Return the current point in time as a Unix timestamp. ```feel (now() - @"1970-01-01T00:00Z") / @"PT1S" * 1000 ``` #### Evaluation result ```feel 1618200039000 ``` --- ## Unary-tests A unary-tests expression is a special kind of boolean expression. Unary-tests expressions should be used for the input entries of a decision table (i.e. the conditions of a rule). A unary-tests expression returns `true` if one of the following conditions is fulfilled: - The expression evaluates to `true` when the input value is applied to the unary operators. - The expression evaluates to `true` when the input value is assigned to the special variable `?`. - The expression evaluates to a value, and the input value is equal to that value. - The expression evaluates to a list, and the input value is equal to at least one of the values. - The expression is equal to `-` (a dash). ### Comparison Compares the input value with a given value. The input value is passed implicitly as the first argument of the operator. Both values must be of the same type. Otherwise, the result is `null`. Operator Description Supported types (none) equal to any < less than number, string, date, time, date-time, duration <= less than or equal to number, string, date, time, date-time, duration > greater than number, string, date, time, date-time, duration >= greater than or equal number, string, date, time, date-time, duration ```feel "valid" < 10 <= date("2020-04-06") > time("08:00:00") >= duration("P5D") ``` ### Interval Checks if the input value is within a given interval between `x` and `y`. An interval has two boundaries that can be open `(x..y)` / `]x..y[` or closed `[x..y]`. If a boundary is closed, it includes the given value (i.e. less/greater than or equal). Otherwise, it excludes the value (i.e. less/greater than). The input value is passed implicitly to the operator. ```feel (2..5) // input > 2 and input < 5 ]2..5[ // input > 2 and input < 5 [2..5] // input >= 2 and input <= 5 (2..5] // input > 2 and input <= 5 ``` ### Disjunction/or Combines multiple unary-test expressions following the ternary logic. - Returns `true` if at least one unary-test evaluates to `true`. - Otherwise, it returns `false`. ```feel 2, 3, 4 // input = 2 or input = 3 or input = 4 < 10, > 50 // input < 10 or input > 50 ``` ### Negation/not Negates a given unary-test expression. The expression can be a comparison, an interval, or a disjunction. It returns `true` if the given unary-test evaluates to `false`. ```feel not("valid") // input != "valid" not(2, 3) // input != 2 and input != 3 ``` ### Expressions When a unary operator is not enough to express the condition, any expression that returns a boolean value can be used, such as [invoking a function](/components/modeler/feel/language-guide/feel-functions.md#invocation). In the expression, the input value can be accessed by the special variable `?`. ```feel contains(?, "good") // checks if the input value (string) contains "good" ends with(?, "@camunda.com") // checks if the input value (string) ends with "@camunda.com" ``` --- ## Variables(Language-guide) Learn how to access variables, follow valid variable naming rules, and safely use escaped names when working with FEEL expressions. ## Access variables Access the value of a variable by its [variable name](#variable-names). FEEL doesn't use a separate assignment operator to create or update process variables. Instead, FEEL evaluates variables that are already available in the current context, or you can define local values with [context entries](/components/modeler/feel/language-guide/feel-context-expressions.md). ```feel a + b ``` If the value of the variable is a context, a [context entry can be accessed](/components/modeler/feel/language-guide/feel-context-expressions.md#get-entrypath) by its key. ```feel a.b ``` For example, in an [AI agent](/reference/glossary.md#ai-agent) tool definition, the `toolCall` variable is a context holding the parameters an LLM supplies at runtime. See [`fromAi()`](/components/modeler/feel/builtin-functions/feel-built-in-functions-ai-agent.md#fromaivalue) for how tool parameters are declared. If no variable exists with the given name, the expression returns `null`. :::tip Use a [null-check](/components/modeler/feel/language-guide/feel-boolean-expressions.md#null-check) if the variable can be `null` or is optional. ```feel a != null and a.b > 10 ``` ::: ## Variable names The name of a variable can be any alphanumeric string including the `_` symbol. For a combination of words, it's recommended to use the `camelCase` or the `snake_case` format. The `kebab-case` format is not allowed because it contains the operator `-`. When accessing a variable in an expression, keep in mind the variable name is case-sensitive. Restrictions of a variable name: - It may not start with a _number_ (e.g. `1stChoice` is not allowed; you can use `firstChoice` instead). - It may not contain _whitespaces_ (e.g. `order number` is not allowed; you can use `orderNumber` instead). - It may not contain an _operator_ (e.g. `+`, `-`, `*`, `/`, `=`, `>`, `<`, `?`, `.`). - It may not be a _literal_ (e.g. `null`, `true`, `false`) or a _keyword_ (e.g. `function`, `if` , `then`, `else`, `for`, `return`, `between`, `instance`, `of`, `not`, `in`, `and`, `or`, `some`, `every`, `satisfies`). #### Escape variable names If a variable name or a context key contains any special character (e.g. whitespace, dash, etc.) then the name can be wrapped into single backquotes/backticks (e.g. `` `foo bar` ``). ```feel `first name` `tracking-id` order.`total price` ``` :::tip Use the [`get value()`](/components/modeler/feel/builtin-functions/feel-built-in-functions-context.md#get-valuecontext-key) function to retrieve the context value of an arbitrary key. ```feel get value(order, "total price") ``` ::: --- ## Introduction(3) Everything begins with [data types](./feel-data-types.md). Read on this subject first to have a better understanding of the other content like [expressions](./feel-expressions-introduction.md) and [built-in functions](./feel-functions.md). If you're writing an expression for an input entry of a decision table, visit our documentation on [unary-tests](./feel-unary-tests.md). Otherwise, have a look at the more general section about [expressions](./feel-expressions-introduction.md). Looking for a function to call? Visit our documentation on [built-in functions](../builtin-functions/feel-built-in-functions-introduction.md). --- ## What is FEEL? **Friendly Enough Expression Language (FEEL)** is designed to write [expressions](/reference/glossary.md#feel-expression) in a way that is easily understood by both business professionals and developers. In Camunda, FEEL is used to define expressions in the context of [BPMN diagrams](/components/modeler/bpmn/bpmn.md), [DMN diagrams](/components/modeler/dmn/dmn.md), and [Forms](/components/modeler/forms/camunda-forms-reference.md). FEEL is specified in the [DMN specification](https://www.omg.org/spec/DMN/) of the Object Management Group (OMG). ## Learning FEEL To understand FEEL better and how it is integrated into Camunda, you can explore the following resources: - [Expressions in Camunda 8](/components/concepts/expressions.md): Learn how expressions are used within our platform. - [FEEL syntax and operators](./language-guide/feel-expressions-introduction.md): Learn how to write basic expression. - [Built-in functions](./builtin-functions/feel-built-in-functions-introduction.md): Read about functions the FEEL engine offers. ## FEEL engines To evaluate your expressions, Camunda employs two distinct FEEL engines depending on the use-case: FEEL Scala (the Java FEEL engine) and feelin (the JavaScript FEEL engine). Both support the basic FEEL syntax and functions. You can try out your expressions in our playgrounds outlined below. ### FEEL Scala (Java FEEL engine) [**FEEL Scala**](https://github.com/camunda/feel-scala) is a Java-based FEEL engine integrated into the backend of our platform. It is primarily responsible for evaluating expressions in BPMN diagrams and DMN tables. :::info info The FEEL Scala engine supports a set of extensions to standard DMN FEEL. The documentation marks them via the following tag: ::: Try out expressions in the [FEEL Scala Playground](https://camunda.github.io/feel-scala/docs/playground/). ### feelin (JavaScript FEEL engine) [**feelin**](https://github.com/nikku/feelin) is a JavaScript-based FEEL engine designed for use in the browser is used for [Camunda Forms](../forms/camunda-forms-reference.md) and [templating](../forms/configuration/forms-config-templating-syntax.md). [Camunda extensions](#camunda-extensions) are **not** currently supported by feelin. Try out expressions in the [feelin Playground](https://nikku.github.io/feel-playground/). --- ## What are Camunda Forms? :::note Support for Camunda Forms The initial release of Camunda Forms includes a debut minimal feature set, which will be expanded with upcoming versions. The Camunda Forms feature was added with the 7.15.0 release of Camunda 7 and the 4.7.0 release of [Camunda Modeler](../about-modeler.md). Therefore, they can be used within BPMN diagrams running on Camunda 7 version 7.15.0 or later. ::: The Camunda Forms feature allows you to design and configure forms. Once configured, they can be connected to a user task or start event to implement a task form in your application. While you can incorporate Camunda Forms solely within Camunda 8, you can also utilize Camunda Forms in Camunda 7. After deploying a diagram with an embedded form, Tasklist imports this form schema and uses it to render the form on every task assigned to it. :::tip To learn more about how to work with forms, see the following resources: - [Utilize forms in Camunda Hub](/components/hub/workspace/modeler/modeling/utilize-forms.md). - [Utilize forms in Desktop Modeler](/components/modeler/desktop-modeler/utilize-forms.md). ::: Camunda Forms are powered by the open source [bpmn-io form-js library](https://github.com/bpmn-io/form-js). Visit the [open source repository](https://github.com/bpmn-io/form-js) to find out how to render a form using plain JavaScript in a custom application (note that this also requires you to fetch the form from the respective BPMN 2.0 element and provide data as needed to the form.) ## Additional resources - [Form element library](./form-element-library/forms-element-library.md): An overview of the components supported by Camunda Forms. --- ## Data binding ## Binding form fields to process data ### Overview Each **form element** that allows data manipulation is considered a **form field** and has a **Key** attribute. The "Key" serves a dual purpose: 1. To map data to the field during the initial loading of the form. 2. To map the field's data back to the process variable during form submission. When a form is part of a user task or start event and viewed in [Tasklist](../../../tasklist/introduction-to-tasklist.md), the "Key" correlates with a process variable. This variable serves as the initial value of the field and is updated upon form submission. ### Simple key vs path key #### Simple key Traditionally, a key was defined as a simple variable name. This key directly matches a process variable. ``` key: "username" ``` #### Path as key With newer updates, keys can now also be defined as a path, like `user.info.age`. This allows for more complex data structures. ``` key: "user.info.age" ``` Setting this field to value `21` would lead to the following output data: ``` { "user": { "info": { "age": 21 } } } ``` ### Path extension via groups In a form that utilizes **groups**, the group's **path** acts as a prefix to the key of each child field within that group. This effectively extends the key and allows for more intricate data mapping. For example, if the group's path is `user.info`, and a child field has a key of `age`, the complete key would become `user.info.age`. This is particularly useful to deal with nested data structure inputs and outputs. ![Group Example Image](../assets/group-mapping-example.png) If the above group has a path set to `user.info`, and if each field's key matches its label, the resulting output data will look like this: ``` { "user": { "info": { "surname": "Inco", "name": "Gnito", "age": 29 } } } ``` Group paths can be shared by multiple groups, as long as fields themselves do not conflict. However, depending on your use case, it might make sense for your visual and data structure to match. ### Path configuration for dynamic lists While dynamic lists share similarities with groups in form structuring, they differ slightly in terms of path configuration. Since dynamic lists are bound to arrays, they cannot share paths with other dynamic lists or groups. In this instance, consider a dynamic list with the path `contacts`. Each entry in the list might contain fields like `name` and `phone`. The dynamic list directly outputs an array of objects under its own path, and the key of those child variables is used in the creation of the individual objects. The resulting data structure would resemble: ``` { "contacts": [ { "name": "John Doe", "phone": "123-456-7890" }, { "name": "Jane Smith", "phone": "098-765-4321" } // Additional objects for each item in the list ] } ``` This structure is maintained when binding to input data as well. You can programmatically control how many list items are rendered from the number of elements in the array at the binding path, whether individual element data is provided or not. --- ## Options Source ## Configuring form field options sources Certain form fields work from a set of pre-populated options that your user will want to select from, such as the [Radio](../form-element-library/forms-element-library-radio.md) or [Select](../form-element-library/forms-element-library-select.md) fields. This source can be configured several ways, as described below. ### Static configured on the form schema The options will be defined directly on your form schema, the only way to modify them in the future will be to change the form definition itself. The static options group will appear to allow the configuration of the individual options. ![Static Options Group Image](../assets/static-options-example.png) For each of these options, a unique `value` corresponding to the form submitted data must be provided, as well as a `label` which will be displayed to the user filling in the form. You may configure as many options as you want by using the add (+) button in the group header. ### Input data driven by process data The options are mapped from form variables, similarly to how [Form field data binding](./forms-config-data-binding.md) works. Here, the `Input values key` property within the `Dynamic Options` configuration group is used to set which form variable to use as a source. The expected format for the data is an array of options, each defining its label and value in JSON. The below example provides an equivalent configuration to the above statically defined one: ```json { "languageData": [ { "label": "French", "value": "fr" }, { "label": "English (UK)", "value": "en-gb" }, { "label": "German", "value": "de" } ] } ``` ### FEEL expressions The options are populated from a [FEEL expression](../../feel/language-guide/feel-expressions-introduction.md) that returns an array of options. The resulting array must be formatted as described in the [example JSON above](#input-data-driven-by-process-data). #### Shorthand definitions If the value and label are equal, shorthand formats may be used instead: ```json { "languageData": [{ "value": "fr" }, { "value": "en-gb" }, { "value": "de" }] } ``` ```json { "languageData": ["fr", "en-gb", "de"] } ``` #### Supported types The `label` parameter should be a `string`. If a label is provided, the `value` parameter can be any non-null type. Otherwise, it is restricted to types `string`, `number`, and `boolean`. --- ## Table data binding When using the table component, you can bind the table to a context variable to populate the table with data from the process. To do this, set two properties when modeling your form: **Headers source** and **Data source**. ## Headers source For **Headers source**, define the columns of the table. You have two options: - **List of items**: Define the headers statically by adding the headers in the **Headers items** section. From there, you can define a list of columns defined by a **Label** and a **Key**. The **Label** is the text that will be displayed in the header, and the **Key** is the name of the variable that will be used to populate the column. - **Expression**: Define the headers dynamically by using an [expression](../../feel/language-guide/feel-expressions-introduction.md). The [expression](../../feel/language-guide/feel-expressions-introduction.md) must return a list of objects with a **label** and a **key** property. For example, if you have a list of objects with a **name** and a **surname** property, your [expression](../../feel/language-guide/feel-expressions-introduction.md) must evaluate to the following JSON to define the headers: ```json [ { "label": "Name", "key": "name" }, { "label": "Surname", "key": "surname" } ] ``` ## Data source To define the data which will be displayed as table rows, define the **Data source** property. This field accepts only [expressions](../../feel/language-guide/feel-expressions-introduction.md). The [expression](../../feel/language-guide/feel-expressions-introduction.md) must return a list of objects, where each object represents a row in the table. Each object must have a property for each column defined in the **Headers source** property. For example, if you have a list of objects with a **name** and a **surname** property, your [expression](../../feel/language-guide/feel-expressions-introduction.md) must evaluate to the following JSON to define the data: ```json [ { "name": "John", "surname": "Doe" }, { "name": "Jane", "surname": "Doe" } ] ``` --- ## Templating syntax Templated properties configuration allows for dynamic content creation within forms using a templating language called [**feelers**](https://github.com/bpmn-io/feelers). ## Feelers syntax ### Variables/inserts To insert a variable, use double curly braces `{{variable}}`, and the value of this variable will be inserted. You can use **any valid [FEEL expression](../../feel/language-guide/feel-expressions-introduction.md)** within these double braces. ``` Hello {{username}}, you are {{if isAdmin then "an admin" else "a user"}}. ``` ### Iterating through arrays Iterate through arrays using the _loop_ tags. Within the loop, reference each array element with `{{this}}`, or if your array elements are objects, via their properties. To access data outside the scope of the individual items, use the `{{parent}}` accessor. **Data** ```json { "currency": "$", "items": [ { "name": "bananas", "price": 2.5 }, { "name": "mangos", "price": 4 }, { "name": "strawberries", "price": 3 } ] } ``` **Template** ``` {{#loop items}} Item name: {{name}} Item price: {{parent.currency}}{{price}} {{/loop}} ``` ### Conditional sections Conditionally render a section of your template using the `if` tags. This is a quick way to write out large blocks you may or may not want evaluated based on a condition: ``` {{#if user.isCook}} Ingredients list: {{#loop ingredients}} * {{this}} {{/loop}} {{/if}} ``` ## Additional details ### Nest loops If you have an array of users, each with an array of purchases, you may loop over both in a nested manner: **Data** ```json { "users": [ { "name": "jane1995", "purchases": ["mango", "strawberry"] }, { "name": "rob1992", "purchases": ["pineapple", "guava"] } ] } ``` **Template** ``` {{#loop users}} The user '{{name}}' purchased: {{#loop purchases}} * {{this}} {{/loop}} {{/loop}} ``` In this situation, you may need to use the `parent` accessor several times to access data outside the scope. ### More on the `parent` and `this` accessors If the data you are using somehow already makes use of those keywords, there is an alternative syntax which surrounds it with an underscore: `_this_` and `_parent_`. **Data** ```json { "root": "nodes", "nodes": [ { "id": "021321321", "parent": "228321321" }, { "id": "021321321", "parent": "228321321" } ] } ``` **Template** ``` Listing out all node paths: {{#loop nodes}} * http://www.myNodeWebsite/{{_parent_.root}}/{{id}} {{/loop}} ``` In the example above, if you are not surrounding the parent accessor with underscores, you access the parent property of the node, which is not what we're looking for. This also applies to the `this` accessor. --- ## Button A button allowing the user to trigger form actions. ### Configurable properties - **Field label**: Label to be displayed on top of the button. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Action**: The button can either trigger a **Submit** or a **Reset** action. - **Submit**: Submit the form (given there are no validation errors). - **Reset**: Reset the form, all user inputs will be lost. - **Hide if**: [Expression](../../feel/language-guide/feel-expressions-introduction.md) to hide the button. - **Columns**: Space the button will use inside its row. **Auto** means it will automatically adjust to available space in the row. Read more about the underlying grid layout in the [Carbon Grid documentation](https://carbondesignsystem.com/elements/2x-grid/overview/). --- ## Checkbox group A set of checkbox options providing data multi-selection for small datasets. ### Configurable properties - **Field label**: Label displayed on top of the checkbox group. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Field description**: Description provided below the checkbox group. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Key**: Binds the field to a form variable, refer to [data binding docs](../configuration/forms-config-data-binding.md). - **Options source**: Checkbox group components can be configured with an options source defining the individual choices your user can make, refer to [options source docs](../configuration/forms-config-options.md). - **Read only**: Makes the checkbox group read-only, meaning the user can't change but only read its state. Can be dynamically set using an [expression](../../feel/language-guide/feel-expressions-introduction.md). - **Disabled**: Disables the checkbox group, for use during development. - **Hide if**: [Expression](../../feel/language-guide/feel-expressions-introduction.md) to hide the checkbox group. - **Validation**: Given that one of the following properties is set, the form will only submit when the respective condition is fulfilled. Otherwise, a validation error will be displayed. - **Required**: Checkbox group must contain a value. - **Columns**: Space the field will use inside its row. **Auto** means it will automatically adjust to available space in the row. Read more about the underlying grid layout in the [Carbon Grid documentation](https://carbondesignsystem.com/elements/2x-grid/overview/). ### Datatypes Checkbox group components can be bound to data of the `any[]` type, although for most practical cases we recommend `string[]` instead. The checkbox group component correlates the bound data with the values of the different options. The data representation of this checkbox group: ![Checklist Selection Image](../assets/checklist-example.png) Looks like this: ``` { "mailto": [ "regional-manager", "approver" ], } ``` --- ## Checkbox A checkbox allowing the user to read and edit boolean data. ### Configurable properties - **Field label**: Label displayed besides the checkbox. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Field description**: Description provided below the checkbox. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Key**: Binds the field to a form variable, refer to [data binding docs](../configuration/forms-config-data-binding.md). - **Default value**: Provides a default state for the checkbox in case no input data exists for the given key. - **Read only**: Makes the checkbox read-only, meaning the user can't change but only read its state. Can be dynamically set using an [expression](../../feel/language-guide/feel-expressions-introduction.md). - **Disabled**: Disables the checkbox, for use during development. - **Hide if**: [Expression](../../feel/language-guide/feel-expressions-introduction.md) to hide the checkbox. - **Validation**: Given that one of the following properties is set, the form will only submit when the respective condition is fulfilled. Otherwise, a validation error will be displayed. - **Required**: Checkbox must contain a value. - **Columns**: Space the field will use inside its row. **Auto** means it will automatically adjust to available space in the row. Read more about the underlying grid layout in the [Carbon Grid documentation](https://carbondesignsystem.com/elements/2x-grid/overview/). ### Datatypes Checkboxes can be bound to data of the `boolean` type. Any other datatype will be treated as a `false` by default. --- ## Datetime A component allowing the user to read and edit date and time data. ## Configurable properties - **Date label**: Label displayed beside the date input field. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Time label**: Label displayed beside the time input field. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Field description**: Description provided below the datetime component. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Key**: Binds the field to a form variable, refer to [data binding docs](../configuration/forms-config-data-binding.md). - **Subtype**: Selects the type of the datetime component. This can either be **Date**, **Time**, or **Date & Time**. - **Use 24h**: Enables 24-hour time format. - **Read only**: Makes the datetime component read-only, meaning the user can't change but only read its state. Can be dynamically set using an [expression](../../feel/language-guide/feel-expressions-introduction.md). - **Disabled**: Disables the datetime component, for use during development. - **Hide if**: [Expression](../../feel/language-guide/feel-expressions-introduction.md) to hide the datetime component. - **Columns**: Space the field will use inside its row. **Auto** means it will automatically adjust to available space in the row. Read more about the underlying grid layout in the [Carbon Grid documentation](https://carbondesignsystem.com/elements/2x-grid/overview/). - **Time format**: Defines the time data format. This can either be **UTC offset**, **UTC normalized**, or **No timezone**. - **Time interval**: Defines the steps of time that can be selected in the time input field. - **Disallow past dates**: Enables the restriction to not allow past dates. - **Validation**: Given that one of the following properties is set, the form will only submit when the respective condition is fulfilled. Otherwise, a validation error will be displayed. - **Required**: Datetime component must contain a value. ## Datatypes Datetime components can be bound to data of the `string` type. The format of the string depends on the subtype: - **date**: ISO 8601 string of the format `YYYY-MM-DD`. - **datetime**: ISO 8601 string of the format `YYYY-MM-DDTHH:MM`. Note that leading zeroes must be present in the hour and minutes (e.g., 01:30 not 1:30); this is an ISO 8601 requirement. - **time**: String of the format `HH:MM`. Leading zeros can be omitted. --- ## Document preview A form element to preview and download documents. ## Configurable properties #### General - **Title**: Title displayed on top of the element. - Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Document reference**: Data used to render documents. - Accepts an array of objects with document metadata. - Can only be an [expression](../../feel/language-guide/feel-expressions-introduction.md). - The resulting value must have the following structure: ```json [ { "documentId": "u123", "endpoint": "https://api.example.com/documents/u123", "metadata": { "fileName": "Document.pdf", "contentType": "application/pdf" } } ] ``` :::note When previewing documents that were uploaded via [Filepicker](./forms-element-library-filepicker.md) in Tasklist, document references are handled automatically: - Modifying the document’s metadata after upload may prevent the preview from working correctly. - To use Camunda's document service without Filepicker, you must include the `contentHash` and `documentId` values. ::: #### Condition - **Hide if**: [Expression](../../feel/language-guide/feel-expressions-introduction.md) to hide the element. #### Layout - **Columns**: Space the field will use inside its row. - **Auto** means it will automatically adjust to available space in the row. Read more about the underlying grid layout in the [Carbon Grid documentation](https://carbondesignsystem.com/elements/2x-grid/overview/). #### Appearance - **Max height of preview container**: A number which will define the maximum height of each document preview. Any document with a bigger height will display a scroll bar. ## Additional guides Design projects for document handling with the Document preview component. --- ## Dynamic list The **dynamic list** element is designed to dynamically manage a list of form elements. It enables users to add or remove items from the list and is particularly useful in scenarios where the number of items in a list is not fixed. ## Configurable properties - **Group label**: Label displayed on top of the dynamic list. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Path**: Assigns a path that maps its children into a data object, defined as a variable name or a dot separated variable accessor. See the [data binding docs](../configuration/forms-config-data-binding.md) for more details. - **Default number of items**: Specifies the default number of items rendered when no input data is provided. - **Allow add/delete items**: Enables users to add new items to or delete existing items from the list. - **Disable collapse**: Prevents items in the list from being collapsed. - **Number of non-collapsing items**: Defines the number of items in the list that will not collapse. - **Vertical alignment**: Determines the alignment of items in the list. - **Hide if**: [Expression](../../feel/language-guide/feel-expressions-introduction.md) to hide the dynamic list. ## Usage The dynamic list element facilitates the management of data lists in forms. It is highly adaptable, with features like default item count, add/delete capabilities, and control over item collapse. It's the ideal component for scenarios where users need to input a variable number of items, such as adding multiple contacts or entries in a form. --- ## Expression field An expression field allowing the user to compute new data based on form state. ### Configurable properties - **Key**: Binds the field to a form variable, refer to [data binding docs](../configuration/forms-config-data-binding.md). - **Target value**: Defines an [expression](../../feel/language-guide/feel-expressions-introduction.md) to evaluate. - **Compute on**: Defines when the expression should be evaluated. Either whenever the result changes, or only on form submission. - **Deactivate if**: [Expression](../../feel/language-guide/feel-expressions-introduction.md) to disable the expression. - **Columns**: Space the field will use inside its row. **Auto** means it will automatically adjust to available space in the row. Read more about the underlying grid layout in the [Carbon Grid documentation](https://carbondesignsystem.com/elements/2x-grid/overview/). :::info The expression field is a simple way to create intermediary data which may be re-used within your form, or further down your process. To effectively use this component, a good understanding of [FEEL](../../feel/language-guide/feel-expressions-introduction.md) is required. ::: --- ## Filepicker A form element to select files. ## Configurable properties #### General - **Field label**: Label displayed on top of the Filepicker. - It can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Supported file formats**: [Comma-separated list of supported file formats.](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers) - It can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md) or plain text. - **Upload multiple files**: Allows the user to upload multiple files at once. - It can be dynamically set using an [expression](../../feel/language-guide/feel-expressions-introduction.md). - **Key**: Binds the field to a form variable, refer to the [data binding documentation](../configuration/forms-config-data-binding.md). - **Read only**: Makes the Filepicker read-only, meaning the user can't change but only read its state. - It can be dynamically set using an [expression](../../feel/language-guide/feel-expressions-introduction.md). - **Disabled**: Disables the Filepicker, for use during development. #### Condition - **Hide if**: [Expression](../../feel/language-guide/feel-expressions-introduction.md) to hide the element. #### Layout - **Columns**: Space the field will use inside its row. - **Auto** means it will automatically adjust to available space in the row. Read more about the underlying grid layout in the [Carbon Grid documentation](https://carbondesignsystem.com/elements/2x-grid/overview/). #### Validation Given that one of the following properties is set, the form will only submit when the respective condition is fulfilled. Otherwise, a validation error will be displayed. - **Required**: Filepicker must have a selected file. ## Reference uploaded files The Filepicker's output variable is an array of objects with file metadata that represents each uploaded file. It always returns an array of objects, either a user uploads a single file or multiple files. Single file uploads are accessible using `value[1]` (since [FEEL](../../feel/what-is-feel.md) uses 1-based indexing). ## Additional guides Design projects for document handling with the Filepicker component. --- ## Group The group element serves as a container to group various form elements together. It allows for nesting of fields and assists in organizing complex forms. ### Configurable properties - **Group label**: Label displayed on top of the group. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Path**: Assigns a path that maps its children into a data object, may be left empty, defined as a variable name or a dot separated variable accessor. See the [data binding docs](../configuration/forms-config-data-binding.md) for more details. - **Show outline**: Can be toggled on and off to display a separating outline around the group - **Vertical alignment**: Determines the alignment of items in the list. - **Hide if**: [Expression](../../feel/language-guide/feel-expressions-introduction.md) to hide the group. - **Columns**: Space the field will use inside its row. **Auto** means it will automatically adjust to available space in the row. Read more about the underlying grid layout in the [Carbon Grid documentation](https://carbondesignsystem.com/elements/2x-grid/overview/). ### Usage The group element allows for nesting of various form elements. Children elements inherit the group's path as a prefix to their key, streamlining data mapping. You can nest additional group elements for deeper organization. Groups may also be used to show and hide entire sections of the form. --- ## HTML view A flexible display component designed to quickly render HTML content for the user. ## Configurable properties - **Content**: This property accepts HTML content. Define it using [templating syntax](../configuration/forms-config-templating-syntax.md) or as plaintext HTML. The rendered content is sanitized for security reasons, see below for details. - **Hide if**: [Expression](../../feel/language-guide/feel-expressions-introduction.md) to conditionally hide the HTML content. - **Columns**: Space the field will use inside its row. The **Auto** means it will automatically adjust to available space in the row. Read more about the underlying grid layout in the [Carbon Grid documentation](https://carbondesignsystem.com/elements/2x-grid/overview/). ## Our security and sanitation strategy We prevent all usages of JavaScript inside the HTML, this means that this component is solely meant to be used for presentation of information. Additionally, any text rendered through templating will be escaped. This means that you cannot pass HTML and CSS dynamically, by design, as certain attack vectors could make use of it. You may however inject individual style properties in your CSS. We automatically scope all CSS, templated or otherwise, to the root of the component. This ensures the component does not affect and potentially break the entire structure of the form. :::note While these are all strong security mechanisms, we still advise you to ensure the data coming into your form is trusted, and to use templating only where you really need it. ::: ## HTML configurations Below are various ways of configuring your HTML component, depending on your use case and configuration needs. **Basic HTML**: ``` Welcome to Our Site This is a paragraph with some bold text and italic text. Click here to visit our page. ``` **HTML with inline styling**: ``` Styling Example This paragraph is styled with inline CSS. ``` **HTML with style tags**: ``` Styling Example This paragraph is styled with CSS in a style tag. ``` **List and images**: ``` First Item Second Item ``` **Templating notation** ``` {{pageTitle}} Welcome, {{user.name}}! Your selected color is: {{user.favoriteColor}} Your tasks for today are: {{#loop user.tasks}} {{this}} {{/loop}} ``` --- ## iframe This is an element allowing the user to embed external content via an iframe. :::note Every iframe component is a sandbox. This means that the content of the iframe is not able to access the parent page, cookies, browser storage, and others. [Learn more about sandbox iframes](https://www.w3schools.com/tags/att_iframe_sandbox.asp). ::: ## Configurable properties - **Title**: Label displayed on top of the iframe and as the accessible title. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **URL**: Enter an HTTPS URL to a source. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). Ensure the URL is safe as it might impose security risks. Not all external sources can be displayed in the iframe. Read more about it in [the X-FRAME-OPTIONS documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options). - **Height**: Defines the height of the iframe. Defined as number of pixels. - **Security attributes**: Allow the iframe's sandbox more access to various functionality of the browser, at the cost of security. - **Script execution**: Enables script running, essential for interactive websites. - **Allow same origin**: Controls the same-origin policy for the iframe, impacting access to data like cookies, local storage, and DOM storage. - **Open in fullscreen**: Allows the content of the iframe to request fullscreen mode. - **Geolocation**: Grants or denies access to geolocation services. - **Camera/Microphone access**: Required for functionality which makes use of the camera/microphone. You may need to allow it through a browser prompt as well. - **Forms submission**: Enables the submission of forms within the iframe. - **Open modal windows/popups**: Permits the iframe to open modal windows/popups. - **Top level navigation**: Gives the iframe permission to change the URL of the parent page, navigating away entirely from it. - **Storage access by user**: Controls access of local storage based on user interactions, may be expected in addition to allow same origin on certain browsers for functionality depending on storage. - **Hide if**: [Expression](../../feel/language-guide/feel-expressions-introduction.md) to hide the iframe. - **Columns**: Space the field will use inside its row. **Auto** means it will automatically adjust to available space in the row. Read more about the underlying grid layout in the [Carbon Grid documentation](https://carbondesignsystem.com/elements/2x-grid/overview/). ## Security advisory When configuring iframes, it's essential to understand the security implications, especially if you are not certain of what you'll be rendering ahead of time. - **URL Caution**: Be very careful with the URLs loaded into the iframe. This isn't much of a concern if you just statically define a site, but if you are using links from say, some data a user submitted earlier in your process, it is critical that those are validated prior to being rendered in the iframe. - **Script Execution**: Enabling script execution can expose users to cross-site scripting (XSS) attacks if the content source is not secure. Limit this functionality to trusted, verified sources. - **Allow Same Origin**: This allows the iframe to access cookies, local storage, and DOM storage. Enabling this for an untrusted source can lead to data leaks or other security breaches. - **Camera/Microphone Access and Geolocation**: These features should only be enabled for sites where they are absolutely necessary and trusted. Unauthorized access to these can severely compromise user privacy. - **Top-Level Navigation**: This allows the iframe to redirect the parent page. Be cautious, as malicious sites can abuse this to redirect users to harmful websites. - **Modal Windows/Popups**: While useful, these can be exploited for phishing attacks or unwanted advertising. Only enable for trusted content. You should adopt a allowlisting approach to iframe configuration. This means **only enabling the bare minimum functionality** that you need for your use-case, which ensures the attack surface is kept as low as possible. Additionally, if you are rendering an external link you don't have control over, ensure that you specify what that link should look like and **validate it** somewhere in your process prior to rendering it. If the link could be anything, then you should not render it in this component. --- ## Image view An element allowing the user to display images. ## Configurable properties - **Image source**: Specifies the image source via [expression](../../feel/language-guide/feel-expressions-introduction.md), [templating syntax](../configuration/forms-config-templating-syntax.md) or [static value](/components/concepts/expressions.md#expressions-vs-static-values) (hyperlink or data URI). - **Alternative text**: Provides an alternative text to the image in case it cannot be displayed. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Hide if**: [Expression](../../feel/language-guide/feel-expressions-introduction.md) to hide the image. - **Columns**: Space the field will use inside its row. **Auto** means it will automatically adjust to available space in the row. Read more about the underlying grid layout in the [Carbon Grid documentation](https://carbondesignsystem.com/elements/2x-grid/overview/). --- ## Number A number field allowing the user to read and edit numeric data. ### Configurable properties - **Field label**: Label displayed on top of the number field. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Field description**: Description provided below the number field. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Key**: Binds the field to a form variable, refer to [data binding docs](../configuration/forms-config-data-binding.md). - **Default value**: Provides a default value for the number field in case no input data exists for the given key. - **Decimal digits**: Defines the maximum number of digits after the decimal. - **Increment**: Defines the increment between valid field values. - **Read only**: Makes the number field read-only, meaning the user can't change but only read its state. Can be dynamically set using an [expression](../../feel/language-guide/feel-expressions-introduction.md). - **Disabled**: Disables the number field, for use during development. - **Hide if**: [Expression](../../feel/language-guide/feel-expressions-introduction.md) to hide the number. - **Columns**: Space the field will use inside its row. **Auto** means it will automatically adjust to available space in the row. Read more about the underlying grid layout in the [Carbon Grid documentation](https://carbondesignsystem.com/elements/2x-grid/overview/). - **Serialize to string**: Configures the output format of the datetime value. This enables unlimited precision digits. - **Validation**: Given that one of the following properties is set, the form will only submit when the respective condition is fulfilled. Otherwise, a validation error will be displayed. - **Required**: Number field must contain a value. - **Minimum**: Number field value must be at least `n`. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md) or a number. - **Maximum**: Number field value must be no larger than `n`. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md) or a number. - **Appearance**: Changes the visual appearance of the number field. - **Prefix**: Adds an appendage before the input. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Suffix**: Adds an appendage after the input. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). ### Datatypes Number can be bound to numeric data, or `strings` which can be parsed to numeric data (as per [JavaScript's tryParse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt)), but will always output strictly `integer` data. --- ## Radio group A radio group allowing the user to select one of multiple data options for small datasets. ### Configurable properties - **Field label**: Label displayed above the radio group. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Field description**: Description provided below the radio group. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Key**: Binds the radio group component to a form variable, refer to [data binding docs](../configuration/forms-config-data-binding.md). - **Default value**: Provides a default selection in case no input data exists for the given key. Only available for _static_ options sources. - **Read only**: Makes the radio group read-only, meaning the user can't change but only read its state. Can be dynamically set using an [expression](../../feel/language-guide/feel-expressions-introduction.md). - **Disabled**: Disables the radio group, for use during development. - **Options source**: Radio group components can be configured with an options source defining the individual choices the component provides, refer to [options source docs](../configuration/forms-config-options.md). - **Hide if**: [Expression](../../feel/language-guide/feel-expressions-introduction.md) to hide the radio group. - **Columns**: Space the field will use inside its row. **Auto** means it will automatically adjust to available space in the row. Read more about the underlying grid layout in the [Carbon Grid documentation](https://carbondesignsystem.com/elements/2x-grid/overview/). - **Validation**: Given that one of the following properties is set, the form will only submit when the respective condition is fulfilled. Otherwise, a validation error will be displayed. - **Required**: One radio option must be selected. ### Datatypes Radio group components can be bound to `any` data, but we recommend working with `strings`. The component will correlate the data value with the appropriate option defined in the options source. If no option is found, the data will simply be ignored. --- ## Select A Select dropdown allowing the user to select one of multiple data option from larger datasets. ### Configurable properties - **Field label**: Label displayed above the select. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Field description**: Description provided below the select. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Key**: Binds the selected value to a form variable, refer to [data binding docs](../configuration/forms-config-data-binding.md). - **Default value**: Provides a default selection in case no input data exists for the given key. Only available for _static_ options sources. - **Searchable**: Allows the select entries to be searched via keyboard. - **Read only**: Makes the select read-only, meaning the user can't change but only read its state. Can be dynamically set using an [expression](../../feel/language-guide/feel-expressions-introduction.md). - **Disabled**: Disables the select, for use during development. - **Hide if**: [Expression](../../feel/language-guide/feel-expressions-introduction.md) to hide the select. - **Columns**: Space the field will use inside its row. **Auto** means it will automatically adjust to available space in the row. Read more about the underlying grid layout in the [Carbon Grid documentation](https://carbondesignsystem.com/elements/2x-grid/overview/). - **Options source**: Selects can be configured with an options source defining the individual choices the select provides, refer to [options source docs](../configuration/forms-config-options.md). - **Validation**: Given that one of the following properties is set, the form will only submit when the respective condition is fulfilled. Otherwise, a validation error will be displayed. - **Required**: One select entry must be selected. ### Datatypes Select components can be bound to `any` data, but we recommend working with `strings`. The component will correlate the data value with the appropriate option defined in the options source. If no option is found, the data will simply be ignored. --- ## Separator A **separator** element is used to create a visual separation between two elements. ## Usage The separator element is typically utilized when you want to visually distinguish between form elements. For example, you could use a separator line to delineate different sections of form fields, create a visual break between a heading and subsequent content, or to clarify the boundaries between various parts of a form. Incorporating separators can aid in generating more organized and easier-to-read layouts, particularly in intricate forms or pages featuring multiple sections. --- ## Spacer A **spacer** element is used to create a defined amount of vertical space between two elements. ## Configurable properties - **Height**: Defines the vertical space the spacer takes up. Defined as number of pixels. ## Usage The spacer element is generally used in scenarios where you need to add separation between elements in the layout. For instance, you might use a spacer to separate two blocks of form fields, to create a gap between a heading and the subsequent content, or to prevent different sections of the page from visually merging together. Using the spacer accordingly can help create cleaner, more readable layouts, especially in complex forms or pages with many different sections. --- ## Table This is an element allowing the user to render tabular data. ## Configurable properties - **Table label**: Label displayed on top of the table and as the accessible label. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Data source**: Data used to render the rows of the table. This can only be an [expression](../../feel/language-guide/feel-expressions-introduction.md). Review [table data binding](../configuration/forms-config-table-data-binding.md) for the required data source structure. - **Pagination**: Enables pagination in the table. If enabled, it splits the rows provided by **Data source** into chunks with the size defined by the field **Number of rows per page**. - **Number of rows per page**: The size of each page. Used only if pagination is enabled. Must be greater than zero. - **Headers source**: Defines which headers will be used in the table. This can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md) or a list of static headers. Review [table data binding](../configuration/forms-config-table-data-binding.md) for the required header structure. - **Hide if**: [Expression](../../feel/language-guide/feel-expressions-introduction.md) to hide the table. - **Columns**: Space the field will use inside its row. **Auto** means it will automatically adjust to available space in the row. Read more about the underlying grid layout in the [Carbon Grid documentation](https://carbondesignsystem.com/elements/2x-grid/overview/). --- ## Taglist A complex and searchable tag based component providing multi-selection for large datasets. ### Configurable properties - **Field label**: Label displayed on top of the taglist. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Field description**: Description provided below the taglist. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Key**: Binds the field to a form variable, refer to [data binding docs](../configuration/forms-config-data-binding.md). - **Hide if**: [Expression](../../feel/language-guide/feel-expressions-introduction.md) to hide the taglist. - **Columns**: Space the field will use inside its row. **Auto** means it will automatically adjust to available space in the row. Read more about the underlying grid layout in the [Carbon Grid documentation](https://carbondesignsystem.com/elements/2x-grid/overview/). - **Validation**: Given that one of the following properties is set, the form will only submit when the respective condition is fulfilled. Otherwise, a validation error will be displayed. - **Required**: Taglist must contain a value. - **Options source**: Taglists can be configured with an options source defining the individual choices your user can make, refer to [options source docs](../configuration/forms-config-options.md). - **Read only**: Makes the taglist read-only, meaning the user can't change but only read its state. Can be dynamically set using an [expression](../../feel/language-guide/feel-expressions-introduction.md). - **Disabled**: Disables the taglist, for use during development. ### Datatypes Taglists can be bound to data of the `any[]` type, although for most practical cases we recommend `string[]` instead. The Taglist component will correlate the bound data with the values of the different options defined for the component. The data representation of this taglist: ![Checklist Selection Image](../assets/taglist-example.png) Would look like this: ``` { "cc_empl": [ "john_doe", "anna_belle" ] } ``` --- ## Text view A Markdown-powered text component allowing to display simple information to the user. ## Configurable properties - **Text**: Either an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). After evaluation, the result is processed using a Markdown renderer that supports basic HTML and [GitHub-flavored Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). To ensure safety and prevent cross-site scripting in Camunda Forms, potentially harmful HTML elements will not be rendered. - **Hide if**: [Expression](../../feel/language-guide/feel-expressions-introduction.md) to hide the text. - **Columns**: Space the field will use inside its row. **Auto** means it will automatically adjust to available space in the row. Read more about the underlying grid layout in the [Carbon Grid documentation](https://carbondesignsystem.com/elements/2x-grid/overview/). ## Example text configurations Note that these configurations work in combination with one another. You may use templating syntax to leverage Markdown and HTML. You may also mix Markdown and HTML in a single definition. **Markdown**: ``` # This is a heading This shows an image: ![alternative image text](https://someurl.com/image.png) ## This is a sub-heading Text can be shown for example using **bold**, or *italic* font. * This is an unordered list... * ...with two list items 1. This is an ordered list... 2. ...with two list items ``` **HTML**: ``` This is a heading This shows an image: This is a sub-heading Text can be shown for example using bold, or italic font. This is an unordered list... ...with two list items This is an ordered list... ...with two list items ``` :::note The text component supports a small subset of HTML configurations, and cannot be styled. Please use our [dedicated HTML component](./forms-element-library-html.md) for presentation use-cases relying on HTML heavily. ::: **Template syntax**: ``` {{#if usingTemplating}} Hello {{user.name}}, we are inside a conditional template block. Your hobbies are: {{#loop user.hobbies}} * {{this}} {{/loop}} {{/if}} ``` --- ## Text area A text area allowing the user to read and edit multiline textual data. ## Configurable properties - **Field label**: Label displayed on top of the text area. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Field description**: Description provided below the text area. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Key**: Binds the field to a form variable, refer to [data binding docs](../configuration/forms-config-data-binding.md). - **Default value**: Provides a default value for the text area in case no input data exists for the given key. - **Read only**: Makes the text area read-only, meaning the user can't change but only read its state. Can be dynamically set using an [expression](../../feel/language-guide/feel-expressions-introduction.md). - **Disabled**: Disables the text area; for use during development. - **Hide if**: [Expression](../../feel/language-guide/feel-expressions-introduction.md) to hide the text area. - **Columns**: Space the field will use inside its row. **Auto** means it will automatically adjust to available space in the row. Read more about the underlying grid layout in the [Carbon Grid documentation](https://carbondesignsystem.com/elements/2x-grid/overview/). - **Validation**: Given that one of the following properties is set, the form will only submit when the respective condition is fulfilled. Otherwise, a validation error will be displayed. - **Required**: Text area must contain a value. - **Minimum length**: Text area must have at least `n` characters. - **Maximum length**: Text area must not have more than `n` characters. ## Datatypes Text area can be bound to `boolean`, `string`, and `number` data, but will coerce the data into a string, which will lead the data to be written back to the process as a `string` when the form is submitted. --- ## Textfield A text field allowing the user to read and edit textual data. ### Configurable properties - **Field label**: Label displayed on top of the text field. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Field description**: Description provided below the text field. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Key**: Binds the field to a form variable, refer to [data binding docs](../configuration/forms-config-data-binding.md). - **Default value**: Provides a default value for the text field in case no input data exists for the given key. - **Read only**: Makes the text field read-only, meaning the user can't change but only read its state. Can be dynamically set using an [expression](../../feel/language-guide/feel-expressions-introduction.md). - **Disabled**: Disables the text field, for use during development. - **Hide if**: [Expression](../../feel/language-guide/feel-expressions-introduction.md) to hide the text field. - **Columns**: Space the field will use inside its row. **Auto** means it will automatically adjust to available space in the row. Read more about the underlying grid layout in the [Carbon Grid documentation](https://carbondesignsystem.com/elements/2x-grid/overview/). - **Validation**: Given that one of the following properties is set, the form will only submit when the respective condition is fulfilled. Otherwise, a validation error will be displayed. - **Required**: Text field must contain a value. - **Regular expression validation**: Use predefined validation patterns. Available options are: `Email`, `Phone`, and `Custom`. - **Minimum length**: Text field must have at least `n` characters. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md) or a number. - **Maximum length**: Text field must not have more than `n` characters. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md) or a number. - **Regular expression pattern**: Text field value must match the provided [RegEx](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet) pattern. - **Appearance**: Changes the visual appearance of the text field. - **Prefix**: Adds an appendage before the input. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). - **Suffix**: Adds an appendage after the input. Can either be an [expression](../../feel/language-guide/feel-expressions-introduction.md), plain text, or [templating syntax](../configuration/forms-config-templating-syntax.md). :::info The phone pattern adheres to the international [E.164](https://www.twilio.com/docs/glossary/what-e164) standard, omitting spaces; for example, `+491234567890`. For custom formats, use the `Custom` validation option. ::: ### Datatypes Text Fields can be bound to `boolean`, `string`, and `number` data, but will cohere the data into a string, which will lead the data to be written back to the process as a `string` when the form is submitted. --- ## Overview of Form Elements # Form Elements A form configuration starts off as a composition of **Form Elements** to define the structure. The specific behaviors are then defined as properties on the individual elements via the properties panel on the right side of the screen. Most form elements are intended to be bound to a **Form Variable** for the purpose of data entry, in which case we refer to them as **Form Fields**. Other elements may be used for layout purposes or to provide more specific functionality to the form which doesn't directly affect its state. The following form elements are currently available within Camunda Forms: Symbol Name Description Text view Display simple markdown-powered text HTML Display information in stylable HTML views Text field Read and edit textual data Text area Read and edit multiline textual data Number field Read and edit numeric data Datetime Read and edit date and time data Checkbox Read and edit boolean data Radio Small dataset single data selector Select Large dataset single data selector Checklist Small dataset multi data selector Taglist Large dataset multi data selector Group Group multiple form elements Dynamic list Turn sets of form elements into editable lists iFrame Embed external content Table Render tabular data Expression field Compute intermediary data Filepicker Select files Image view Display images Spacer Display empty space Separator Display a line between elements Button Trigger form actions --- ## Modeling guidance Modeler checks your BPMN diagram for problems affecting deployment and execution, and provides guidance on how to fix them. This page lists all the rules used to check your diagram and explains how to fix the problems. ## Rules --- ## Agent fromAi() contract The [`fromAi()`](../../../../modeler/feel/builtin-functions/feel-built-in-functions-ai-agent.md) FEEL function declares a tool's LLM-supplied inputs within an [AI Agent sub-process](../../../../agentic-orchestration/agentic-orchestration-overview.md). A malformed call resolves to nothing at runtime without producing an error, so malformed calls are reported as errors. A call without a description is valid and is not reported. ## Contract breaks The rule reports the following problems: - **Key is not a FEEL path**: The first argument must be a path expression, such as `toolCall.url`, rather than a string literal, number, `null`, bracket notation, or conditional expression. - **Key does not start with `toolCall.`**: The connector populates only fields in the `toolCall` context. - **Key is nested**: The connector uses only the last path segment as the parameter name. Use a single name, such as `toolCall.filter`. - **Key is declared twice in one tool**: Duplicate keys collide because a tool's `fromAi()` calls are combined into a single input schema. - **Wrong function-name casing**: The function name is case-sensitive. Use `fromAi`. - **Description is not a string literal**: The description must be a quoted string, for example, `fromAi(toolCall.url, "The URL to fetch.")`. - **`fromAi()` is in the wrong place**: The function is valid only in an input mapping on the tool's entry element. This is the activity with no incoming sequence flow. Anywhere else, it resolves to `null`: ![Two activities in a tool's sub-flow: the first has no incoming sequence flow and is the tool's entry element, while the second is reached by a sequence flow and is not](./img/agent-fromai-contract/entry-element.png) ## Correct `fromAi()` usage Use a single-segment `toolCall` path with a description: ```feel = fromAi(toolCall.url, "The URL to fetch. Must be an absolute URL including scheme.") ``` A `fromAi()` call within an ad-hoc sub-process that is not recognized as a tool container is also reported. If the tool is not intended to be agent-driven, remove the `fromAi()` call instead. Clicking a tool container report in the Problems panel selects the tool task rather than the ad-hoc sub-process, and the Problems panel does not navigate to the sub-process for you. Select the sub-process directly in the diagram to apply either fix. ## See also - [AI Agent tool definitions](../../../../connectors/out-of-the-box-connectors/agentic-ai-aiagent-tool-definitions.md) - [`fromAi()` FEEL function](../../../../modeler/feel/builtin-functions/feel-built-in-functions-ai-agent.md) - [Rule source](https://github.com/camunda/bpmnlint-plugin-camunda-compat/blob/main/rules/camunda-cloud/agent-fromai-contract.js) --- ## Agent tool documentation Tools within an [AI Agent sub-process](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess.md) require a documentation entry, which the AI agent uses to select tools. Missing documentation does not cause an outright failure, but an undocumented tool might degrade the AI agent's performance. To fix this, select the tool's entry element, open the **Documentation** section in the properties panel, and describe what the tool does and when the agent should use it. The rule checks the tool's entry element, the activity with no incoming sequence flow. Activities reached through a sequence flow are part of the tool's internal flow and do not require their own documentation. Event sub-processes are also skipped because they are triggered by events rather than called by the agent: ![Two activities in a tool's sub-flow: the first has no incoming sequence flow and is the tool's entry element, while the second is reached by a sequence flow and is not](./img/agent-tool-documentation/entry-element.png) ## No documentation The tool's entry activity has no documentation text or contains only whitespace. The agent sees only the element name (for example, `Fetch URL`) and must guess what the tool does, which inputs matter, and when to use it. ## Documentation provided The tool's entry activity has a documentation entry such as: > Fetches the contents of a web page. Use this when the user provides or asks about a URL. Returns the raw response body. A good tool description covers three things: what the tool does, when the agent should use it, and what it returns. ## See also - [Agentic orchestration](../../../../agentic-orchestration/agentic-orchestration-overview.md) - [AI Agent tool definitions](../../../../connectors/out-of-the-box-connectors/agentic-ai-aiagent-tool-definitions.md) - [Rule source](https://github.com/camunda/bpmnlint-plugin-camunda-compat/blob/main/rules/camunda-cloud/agent-tool-documentation.js) --- ## Agent tool output key Tools within an [AI Agent sub-process](../../../../agentic-orchestration/agentic-orchestration-overview.md) return their results to the agent through the `toolCallResult` variable. The rule reports one warning per tool in either of the following situations: 1. **Misdirected result**: The tool sets result variables, but none of them is named `toolCallResult`, for example, because of a typo such as `toolCalResult`. The rule reports the warning on the element that set the wrong variable, which can be downstream of the tool's entry element. 2. **No result**: No element in the tool's flow sets a result variable. The rule reports the warning on the tool's entry element, because no single element is at fault. Even a fire-and-forget tool should report its completion, for example, with `= "Email sent."`. ## How a tool can set `toolCallResult` The result can be set anywhere in the tool's flow through several channels: - **Output mapping**: Target `toolCallResult` or one of its fields, such as `toolCallResult.statusCode`. - **Connectors**: Use the **Result variable** or **Result expression** field, for example, `= { toolCallResult: response.body }`. This is the only available channel because connectors cannot read process variables. - **Script tasks and business rule tasks**: Set the **Result variable** to `toolCallResult`. ### Avoid overwrites when several elements contribute Assigning a value to `toolCallResult` twice overwrites the first value: ![Two activities in a tool's sub-flow, connected by a sequence flow, both mapping an output to toolCallResult: the second activity's value silently replaces the first](./img/agent-tool-output-key/overwrite.png) To add a field without overwriting the existing value, use `context put()` in an output mapping: ```feel = context put(toolCallResult, "confirmation", sendResult) ``` This works only for elements that run in the workflow engine. Connector result expressions cannot read the current value of `toolCallResult`, so a connector tool must build its complete result in a single expression. ### What the rule cannot see Results written by arbitrary FEEL expressions elsewhere, such as a variable set by a called process, cannot be detected statically. Ignore the warning or make the result wiring explicit with an output mapping. Overwrite detection is also skipped for any tool flow that branches, such as at a gateway split, join, or boundary event. This applies even when the branches are guaranteed to converge before the next write. Review these flows manually for potential overwrites. ### The result variable name This rule always checks for `toolCallResult`, the default used by the AI Agent connector. An AI Agent Task's multi-instance ad-hoc sub-process can rename this variable by changing the multi-instance **Output element** value. If you rename it, ignore the warning for that tool. ## Result never reaches the agent The tool maps its output to `result` instead of `toolCallResult`, or no element in its flow sets a result at all. ## `toolCallResult` set on an element in the tool flow An output mapping targets `toolCallResult` or one of its fields, such as `toolCallResult.statusCode`; a connector result expression contains a `toolCallResult` key; or a script task's result variable is `toolCallResult`. ## See also - [AI Agent tool definitions](../../../../connectors/out-of-the-box-connectors/agentic-ai-aiagent-tool-definitions.md) - [Rule source](https://github.com/camunda/bpmnlint-plugin-camunda-compat/blob/main/rules/camunda-cloud/agent-tool-output-key.js) --- ## Called element Call activities must specify the [process ID of the called process](../../../../bpmn/call-activities/#defining-the-called-process). To fix this problem, open the **Called element** group in the properties panel on the right side of the screen and specify the process ID of the called process. ## No process ID specified ![No process ID specified](./img/called-element/wrong.png) ## Process ID specified ![Process ID specified](./img/called-element/right.png) ## References - [Call Activities](../../../../bpmn/call-activities/) - [Rule source](https://github.com/camunda/bpmnlint-plugin-camunda-compat/blob/main/rules/camunda-cloud/called-element.js) --- ## Element type The element you modeled is not [supported for execution](../../../../bpmn/bpmn-coverage/) by the selected Camunda version. It may be supported in a later version. ## Element type not supported by selected version ![Element type not supported by selected version](./img/element-type/wrong.png) ## Element type supported by selected version ![Element type supported by selected version](./img/element-type/right.png) ## References - [BPMN coverage](../../../../bpmn/bpmn-coverage/) - [Rule source](https://github.com/camunda/bpmnlint-plugin-camunda-compat/tree/main/rules/camunda-cloud/element-type) --- ## Error reference An [error event](../../../../bpmn/error-events/) must reference an error defined in the process. The referenced error must have a defined error code. To fix this problem, open the **Error** group in the properties panel on the right side of the screen, select or create an error, and specify its error code. Camunda 8.2 and later support catch-all events and do not require an error reference. ## No error selected ![No error selected](./img/error-reference/wrong-no-error-reference.png) ## No error code specified ![No error code specified](./img/error-reference/wrong-no-error-code.png) ## Error selected and error code specified ![Error selected and error code specified](./img/error-reference/right.png) ## References - [Error events](../../../../bpmn/error-events/) - [Rule source](https://github.com/camunda/bpmnlint-plugin-camunda-compat/blob/main/rules/camunda-cloud/error-reference.js) --- ## Escalation reference An [escalation event](../../../../bpmn/escalation-events/) must reference an escalation defined in the process. The referenced escalation must have a defined escalation code. To fix this problem, open the **Escalation** group in the properties panel on the right side of the screen, select or create an escalation, and specify its escalation code. Camunda 8.2 and later support catch-all events and do not require an escalation reference. ## No escalation selected ![No escalation selected](./img/escalation-reference/wrong-no-escalation-reference.png) ## No escalation code specified ![No escalation code specified](./img/escalation-reference/wrong-no-escalation-code.png) ## Escalation selected and escalation code specified ![Escalation selected and escalation code specified](./img/escalation-reference/right.png) ## Reference - [Escalation events](../../../../bpmn/escalation-events/) - [Rule source](https://github.com/camunda/bpmnlint-plugin-camunda-compat/blob/main/rules/camunda-cloud/escalation-reference.js) --- ## FEEL When using the [FEEL expression language](../../../../feel/what-is-feel/), you must specify a valid expression. ## Invalid FEEL expression A common cause of this warning is using single quotes for a string literal. FEEL requires double quotes for strings, so an expression like `'type'` is unparsable. ![Invalid FEEL expression](./img/feel/wrong.png) ## Valid FEEL expression Use double quotes for string literals, for example `"type"`, to make the expression valid. ![Valid FEEL expression](./img/feel/right.png) ## References - [FEEL expressions](../../../../feel/what-is-feel/) - [FEEL Scala Playground](https://camunda.github.io/feel-scala/docs/playground/) to try out expressions - [Rule source](https://github.com/camunda/bpmnlint-plugin-camunda-compat/blob/main/rules/camunda-cloud/feel.js) --- ## Message reference A message event or receive task must reference a message defined in the process. To fix this problem, open the **Message** group in the properties panel on the right side of the screen and select or create a message. The referenced message must have a defined correlation key (refer to [message subscriptions](../../../../../concepts/messages/#message-subscriptions)). ## No message selected ![No message selected](./img/message-reference/wrong-no-message-reference.png) ## Message selected ![Message selected](./img/message-reference/right.png) ## References - [Messages](../../../../../concepts/messages/) - [Rule source](https://github.com/camunda/bpmnlint-plugin-camunda-compat/blob/main/rules/camunda-cloud/message-reference.js) --- ## Infinite Loop This error occurs when a BPMN model contains an automated loop (without human interaction or external event triggers). It may cause an endless recursion. This may result in: - Endless execution - [Excessive resource usage](../../../../saas/clusters/cluster-capacity.md) To fix this problem, add a timer, user task, or external event (e.g., message catch) to break the straight-through loop and ensure controlled execution. ## Straight-through processing loop ![Straight-through processing loop](./img/no-loop/wrong.png) ## Controlled Loop ![Controlled Loop](./img/no-loop/right.png) ## References - [Rule source](https://github.com/camunda/bpmnlint-plugin-camunda-compat/blob/main/rules/camunda-cloud/no-loop.js) --- ## Task testing(Modeler) Test a BPMN activity directly from the modeler to verify your implementation without running the entire process: 1. In a BPMN diagram, open the **Details** panel. 2. On the **Test > Input** tab, optionally define process variables, and click **Run test**. 3. See the results on the **Test > Result** tab. Task testing provides immediate feedback on your implementation, variable mappings, and configuration within the modeler. The selected element runs on the connected Camunda 8 engine, as it does during normal process execution. ## How it works When you test an element, the following occurs: 1. The modeler deploys the process to the connected Camunda 8.8+ orchestration cluster. 2. You define the process context by providing input variables. 3. The engine executes the selected element: - Input mappings are applied as configured. - The actual task logic (connector, script, or external task) is executed by the engine. - Output mappings are applied as configured. 4. The modeler displays the **Result** tab containing the execution log, process variables, and local variables, as well as any incidents or errors. :::warning Testing executes elements with live data on the connected cluster. Any configured actions (emails, API calls, database updates, payments, etc.) will run as defined. Do not use a production environment. ::: ## Prerequisites - A connection to an active Camunda 8.8 or later orchestration cluster. - Appropriate credentials and permissions to deploy and run processes. After running a test, you can view the resulting process instance in [Operate](../../components/operate/operate-introduction.md) for additional insights into execution details or incidents. Test instances are deployed as standard process instances and can be viewed, managed, or deleted as usual. For configuration steps, see: - [Test in Camunda Hub](../hub/workspace/modeler/validation/task-testing.md) - [Test in Desktop Modeler](./desktop-modeler/task-testing.md) ## Supported elements You can test the following BPMN elements: - **Task elements** — service tasks, script tasks, user tasks, business rule tasks, and send tasks. - **Sub-processes** — embedded sub-processes can be tested directly, executing all contained elements. - **Tasks inside sub-processes** — individual tasks within a sub-process can also be tested. - **Call activities** — call activities can be tested directly, executing the deployed called process. The following elements are not supported: - Events (start, end, boundary) - Gateways ## Variable persistence When a test completes: - Input variables are stored locally for reuse in subsequent test runs. - The last test result of an element is persisted, including output variables. - You can rerun tests with the same input set or modify them to test new values. ## Best practices - Use a staging cluster or sandbox environment for testing live integrations. - Mock external API calls and disable production credentials when possible. - Review results in Operate to confirm behavior and variable mappings. --- ## Using Camunda Hub and Desktop Modeler together [Camunda Hub](/components/hub/workspace/modeler/index.md) and [Desktop Modeler](/components/modeler/desktop-modeler/index.md) are both tools for designing [BPMN](/components/modeler/bpmn/bpmn.md) diagrams, but they serve different purposes and shine in different scenarios. Camunda Hub is great for collaborative, cloud-based process modeling. It allows teams to work together in real-time, manage versions and project snapshots, and store models centrally. It's especially useful when working in distributed teams or when you need tight integration with a remotely hosted Camunda 8 cluster — whether it's Camunda SaaS or your own self-managed environment. Desktop Modeler, on the other hand, is ideal for local development, technical modeling, and full offline control. Among other features, it supports advanced customization, scripting, and deployment to local Camunda 8 runtimes (like Camunda 8 Run), making it a go-to tool for developers working on executable processes. Using both tools together allows you to combine the best of both worlds: - Start collaboratively in Camunda Hub, capturing business requirements and designing high-level processes with stakeholders. - Then switch to Desktop Modeler for more technical refinement, such as adding execution details, scripts, or testing locally. This workflow bridges the gap between business users and developers, ensuring smooth handoffs and better alignment across the team. When using [Git sync](/components/hub/workspace/manage-projects/git-sync.md) to keep your project in sync between a Camunda Hub workspace and your local environment, there are a few considerations to ensure both modelers interpret the [Camunda project](/components/concepts/projects.md) (and its [`camunda.json`](/apis-tools/c8ctl/camunda-json.md) file) consistently. ## Camunda projects | Desktop Modeler | Camunda Hub | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | A [Camunda project](/components/concepts/projects.md) is represented as a folder containing a [`camunda.json`](/apis-tools/c8ctl/camunda-json.md) file. With Desktop Modeler, you always [deploy projects](/components/modeler/desktop-modeler/projects.md#deploying-a-project) as a single bundle. | A project (called process application before Camunda 8.10) [contains process files](/components/hub/workspace/manage-projects/manage-projects.md). With Camunda Hub, you can [take snapshots](/components/hub/workspace/manage-projects/project-versioning.md#create-a-snapshot) and [deploy projects](/components/hub/workspace/manage-projects/deploy-project.md) as a single bundle, or you can [version](/components/hub/workspace/modeler/modeling/versions.md) and [deploy](/components/hub/workspace/modeler/run-or-publish-your-process.md#deploy-a-process) individual files within a project. | :::tip Camunda recommends always including a `camunda.json` file in your project, so Desktop Modeler and `c8ctl` recognize it when you work locally. Camunda Hub does not require the file. ::: ## Element templates Element templates are discovered differently in each tool. The following sections outline the key differences. ### Templates per file | Desktop Modeler | Camunda Hub | | ----------------------------------------------------- | ------------------------------------ | | Can load multiple templates defined in a single file. | Only supports one template per file. | To maintain compatibility, avoid defining multiple templates in a single file. ### Shared element templates | Desktop Modeler | Camunda Hub | | -------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Templates can be installed as [global templates](/components/modeler/desktop-modeler/element-templates/configuring-templates.md#global-templates). | Templates are published to the [organization](/components/hub/workspace/modeler/element-templates/manage-element-templates.md) or the [catalog](/components/hub/organization/manage-catalog/getting-started.md) for reuse across workspaces. | Camunda recommends storing shared templates in a separate repository: - **Desktop Modeler**: Copy templates manually into your global directory. - **Camunda Hub**: Use a [CI/CD pipeline](/components/best-practices/cicd-guidelines/element-templates-at-scale.md) to sync templates with your repository via the [Camunda Hub API](/apis-tools/hub-api-saas/overview.md). ### Project element templates | Desktop Modeler | Camunda Hub | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | | [Local element templates](/components/modeler/desktop-modeler/element-templates/configuring-templates.md#local-templates) are loaded from the `.camunda/element-templates` folder if present. | Loads templates from a single folder. | :::note - If starting in **Desktop Modeler**, use a single folder for your Camunda project. This makes project templates available in both modelers without extra work. - If starting in **Camunda Hub**, after cloning the repository, manually create a file named `camunda.json` that contains an empty JSON object `{}`. Place it in the folder that Git sync uses for your project: the repository root, or the configured **Repository path**. Desktop Modeler and `c8ctl` then recognize the project. ::: ### Handling multiple template versions | Desktop Modeler | Camunda Hub | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Detects versions based on separate files. To support multiple versions, maintain different files with distinct names (e.g., `element-template-v1.json`, `element-template-v2.json`). Otherwise, templates may appear as [missing](/components/modeler/desktop-modeler/element-templates/using-templates.md#missing-templates). | Supports evolving a single template file. Simply update the file and [publish new versions](/components/hub/workspace/modeler/element-templates/manage-element-templates.md#publish-an-element-template). | :::warning - Desktop Modeler can have multiple templates defined in a single file, which is good practice when defining multiple versions of the same template. - Camunda Hub only supports defining one template per file. When you [publish an element template](/components/hub/workspace/modeler/element-templates/manage-element-templates.md#publish-an-element-template), you must update the version. ::: ## Frequently asked questions ### Do I need a `camunda.json` file if I'm only using Camunda Hub? No. A `camunda.json` file is only required if you plan to open the project in Desktop Modeler or deploy it with `c8ctl`. Camunda Hub does not require it, but adding the file makes the project compatible across all tools. ### Can I use the same element template repository for both modelers? Yes. Camunda recommends maintaining a dedicated version control repository for element templates. Desktop Modeler users can copy templates into their global directory, while Camunda Hub users can stay in sync through a CI/CD pipeline and the [Camunda Hub API](/apis-tools/hub-api-saas/overview.md). ### How should I manage multiple versions of the same element template? In Desktop Modeler, each version of the element template must be present. Versions can be stored in a single file as a list of element templates or in separate files (for example, `element-template-v1.json` and `element-template-v2.json`). Otherwise, the template will appear as [missing](/components/modeler/desktop-modeler/element-templates/using-templates.md#missing-templates). Camunda Hub, however, supports [versioning](/components/modeler/element-templates/defining-templates.md#template-versioning) in a single file and allows you to [publish](/components/hub/workspace/modeler/element-templates/manage-element-templates.md#publish-an-element-template) new versions directly. When referencing a dependency such as a form we recommend using a `versionTag` as your [binding type](/components/best-practices/modeling/choosing-the-resource-binding-type.md#supported-binding-types), as this option ensures that the right version of the target resource is always used. --- ## Introduction to Operate Operate is a tool for monitoring and troubleshooting process instances running in [Zeebe](/components/zeebe/zeebe-overview.md). In addition to providing visibility into active and completed process instances, Operate can also: - Carry out key operations such as resolving [incidents](./userguide/resolve-incidents-update-variables.md) and updating process variables. - Retry or cancel [many process instances at once](/components/operate/userguide/selections-operations.md). - [Delete finished instances](/components/operate/userguide/delete-finished-instances.md) and [resources](/components/operate/userguide/delete-resources.md). - [Modify](/components/operate/userguide/process-instance-modification.md) an active process instance to allow execution to continue. - [Batch move](/components/operate/userguide/process-instance-batch-modification.md) active process instances to allow execution to continue. ![operate-introduction](../../images/operate/operate-introduction.png) Learn how to use Operate to monitor process instances and more features in the [Operate user guide](/components/operate/userguide/basic-operate-navigation.md). ## Availability Because Operate can be a helpful tool when getting started with Zeebe and building an initial proof of concept, we make it available under the [Operate trial license](https://camunda.com/legal/terms/cloud-terms-and-conditions/general-terms-and-conditions-for-the-operate-trial-version/). There are no restrictions under this license when it comes to the length of the evaluation period or the available feature set _as long as you use Operate in non-production environments only._ Operate is available for production use (with support) in the Camunda 8 offering. [Try out Operate in Camunda 8](https://signup.camunda.com/accounts?utm_source=docs.camunda.io&utm_medium=referral&utm_content=operate). --- ## Access control(3) If authorization control is enabled for your Orchestration Cluster, users require the following authorizations to work with Operate. :::note You can assign these [in the Admin UI](/components/admin/authorization.md#create-an-authorization-in-admin). See [the introduction to authorizations](/components/concepts/access-control/authorizations.md#available-resources) for a list of all available authorizations. ::: ## Mandatory authorizations The following mandatory authorizations are required to work with Operate: | Authorization type | Resource type | Resource ID | Permission | | :--------------------------------------------- | :------------------- | :--------------------------------------------------------------------------------- | :------------------------------------------------- | | Component access for Operate | `Component` | `operate` or `*` (for access to all web components). | `ACCESS` | | View process definitions and process instances | `Process Definition` | ID of the respective BPMN process definition or `*` (for all process definitions). | `READ_PROCESS_DEFINITION`, `READ_PROCESS_INSTANCE` | ## Optional authorizations The following optional authorizations can also be defined: | Authorization type | Resource type | Resource ID | Permission | | :-------------------------------------------------------------------------------------------------------- | :--------------------------------- | :-------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------- | | View audit log entries. | `AUDIT_LOG` | An operation category (`DEPLOYED_RESOURCES` or `USER_TASKS`) or `*` for all categories. | `READ` | | View decision definitions and decision instances | `Decision Definition` | ID of the respective DMN decision definition or `*` (for all process definitions). | `READ_DECISION_DEFINITION`, `READ_DECISION_INSTANCE` | | View decision requirements definitions | `Decision Requirements Definition` | ID of the respective DRD or `*` (for all process definitions). | `READ` | | View running and completed batch operations | `Batch` | `*` | `READ` | | Update process instances via batch (cancellation, retries). | `Batch` | `*` | `CREATE` and corresponding permissions for the individual batch operation (for example, `CREATE_BATCH_OPERATION_CANCEL_PROCESS_INSTANCE`). | | Update process instance directly (migrate, add/update variables, resolve incidents) | `Process Definition` | ID of the respective BPMN process definition or `*` (for all process definitions). | `UPDATE_PROCESS_INSTANCE` | | Modify process instances | `Process Definition` | ID of the respective BPMN process definition or `*` (for all process definitions). | `MODIFY_PROCESS_INSTANCE` | | Cancel process instance directly | `Process Definition` | ID of the respective BPMN process definition or `*` (for all process definitions). | `CANCEL_PROCESS_INSTANCE` | | Delete process instances | `Process Definition` | ID of the respective BPMN process definition or `*` (for all process definitions). | `DELETE_PROCESS_INSTANCE` | | View `DEPLOYED_RESOURCES` and `USER_TASKS` operation logs for instances of a specific process definition. | `Process Definition` | A process definition ID or `*` for all process definitions. | `READ_PROCESS_INSTANCE` | | View `USER_TASKS` operation logs for instances of a specific process definition. | `Process Definition` | A process definition ID or `*` for all process definitions. | `READ_USER_TASK` | | Delete process definitions | `Resource` | ID of the respective BPMN process definition or `*` (for all resources). | `DELETE_PROCESS` | | Delete decisions | `Resource` | ID of the respective decision ID or `*` (for all resources). | `DELETE_DECISION_INSTANCE` | --- ## Batch operation details A high-level overview of the batch operation details page in Camunda 8 Operate. ## About the batch operation details page On the batch operation's details page, you'll find: - A summary of the batch operation. - A table with details about the items in the batch operation. - An option to take action on the batch operation. This is only available for some batch operations, depending on the state. ![A batch operation details page showing the summary header (batch state Completed, summary of items 3, start date, end date, actor) above the items table listing three process instance keys with their batch state.](./img/batch-operation-details-overview.png) ## Summary header The summary header includes the following details about the batch operation: | Detail | Description | | ---------------- | ------------------------------------------------------------------------------------ | | Batch state | The current state of the batch operation. | | Summary of items | The number of successful, failed, and pending items included in the batch operation. | | Start date | The operation's start date and time. | | End date | The operation's end date and time, if applicable. | | Actor | The user or client responsible for triggering the operation. | If the batch operation finishes with the "failed" state, the header also includes error messages. ## Items table In the table, you can review these details about each item in the batch operation: | Column | Description | | -------------------- | --------------------------------------------------------------------------------------- | | Process instance key | The key of the process instance. Clicking this opens the process instance details page. | | Batch state | The state of the batch operation item. | | Date | The date and time when the operation was performed on this item. | ## Next steps - [Learn how to monitor batch operations](../userguide/monitor-batch-operations.md). - [Learn how to manage batch operations](../userguide/manage-batch-operation.md). --- ## Batch Operations(Overview) A high-level overview of the **Batch Operations** page in Camunda 8 Operate. ## About the Batch Operations page Use the **Batch Operations** page to monitor all batch operations performed across any process instance. ![The Batch Operations page, listing operations with their operation type, batch state, items, actor, and start date.](./img/batch-operations-overview.png) ## Batch operations table In the table, you can review these details about each batch operation: | Column | Description | | ----------- | ------------------------------------------------------------------------------------ | | Operation | The type of operation performed. | | Batch state | The current state of the batch operation. | | Items | The number of successful, failed, and pending items included in the batch operation. | | Actor | The user or client responsible for initiating the operation. | | Start date | The operation's start date and time. | Learn how to monitor batch operations in the [user guide](../userguide/monitor-batch-operations.md). --- ## Audit operations(Userguide) Audit [operations](../../audit-log/overview.md) in Camunda 8 Operate. ## Prerequisites To follow the steps in this guide, you must be [authorized to view operations in the audit log](../../audit-log/overview/access-control.md). ## Audit all operations In Operate, you can audit all [`DEPLOYED_RESOURCES` and `USER_TASKS` operations](../../audit-log/overview/recorded-operations.md) in the general operations log: 1. In the top navigation, click **Operations > Operations log**. 2. To sort the log, click a column header. 3. To see the details of a particular operation, click the info icon at the end of the row. 4. To filter the audit log, expand the **Filter** panel on the left side of the page. Audit log filters are under the **Operation** header. ## Audit process instance operations You can also review operations at the process instance level: 1. On the **Processes** page, in the **Process Instances** table, click the **Process Instance Key** of the instance you want to audit. 2. Under the process diagram, click **Operations Log**. 3. To sort the log, click a column header. 4. To see the details of a particular operation, click the info icon at the end of the row. 5. To filter the audit log, use the **Operation type** and **Entity type** dropdowns. ## Next steps - [Learn about the operation data structure in the operations log.](../../audit-log/overview/operation-structure.md) - [Use the Audit Log REST API to programmatically access the audit log](../../../apis-tools/orchestration-cluster-api-rest/specifications/search-audit-logs.api.mdx). --- ## Get familiar with Operate Learn how to navigate Camunda 8 Operate. ## Before you begin This guide and [resolve incidents and update variables](./resolve-incidents-update-variables.md) assume you’ve deployed a process to Zeebe and created at least one process instance, using the [`order-process.bpmn`](/bpmn/operate/order-process.bpmn) process model. If you’re not sure how to deploy processes or create instances, visit our [guides section](/guides/introduction-to-camunda-8.md) to get started with Camunda. ## Open Operate From Camunda Hub, you can open Operate in any [environment](/components/concepts/environments.md) you have access to: 1. Log in to Camunda Hub. 1. In the left navigation, click **Environments**, and then select an environment. Each environment has its own instance of Operate. 1. Under **Applications**, on the **Operate** card, click **Open**. This opens Operate for the environment in a new tab. You can also expand an environment in the left navigation, and select Operate from its applications. :::tip If the environment is paused, you must [resume it](/components/hub/organization/manage-environments/index.md#resume-a-paused-environment) to access its applications. ::: ## View a deployed process To view a deployed process, take the following steps: 1. On the **Dashboard** page, in the **Process Instances by Name** panel, note the list of your deployed processes and running instances. The dashboard only displays processes with active instances, so processes without running or incident instances are not shown. 2. When you click on the name of a deployed process in the **Process Instances by Name** panel, you’ll navigate to a view of that process model and all running instances. 3. From this **Processes** page, you can cancel a single running process instance by clicking the cancel icon under the **Operations** column of the **Process Instances** table. ## Inspect a process instance Running process instances appear in the **Process Instances** table below the process model. To inspect a specific instance, click the **Process Instance Key**. The process instance page has four parts: - A header showing the process instance's key, version, and state. - A process diagram showing the instance's current progress. - An **Instance History** panel listing the instance's elements, with search and status filter controls. - A bottom panel with tabs: **Variables**, **Listeners**, and **Operations Log** are always available. **Incidents** appears when the instance has one. **Details**, **Input Mappings**, and **Output Mappings** appear once you select a specific element in the diagram. ![A process instance detail page, showing the header with the process instance's key, version, and state, the process diagram, and the bottom panel with Variables, Listeners, and Operations Log tabs.](./img/basic-operate-navigation.png) Click an element in the diagram to select it, then use the tabs in the bottom panel to inspect its details, incidents, and variables. In earlier versions, an element's details and incidents appeared in a metadata popover when you clicked it; the popover is now replaced by the **Details** and **Incidents** tabs. To visualize process instance performance, use [Optimize](/components/optimize/what-is-optimize.md). ## Navigate to a called process instance When a call activity in the diagram calls another process, double-click the call activity element to jump directly to the called process instance. Double-click navigation only works when the process instance has called exactly one process instance in total. If it has called more than one — for example, through multiple call activities, or a call activity that ran more than once — double-clicking does nothing. For a reliable way to find a called process instance, take the following steps: 1. Click the call activity element to select it. 2. Open the **Details** tab. 3. In the row labeled **Called Process Instance**, click the link — shown as the called process's name and instance key — to navigate to that instance. If the call activity has called more than one process instance, the **Details** tab shows a **View all** link instead of a single link. This link filters the **Processes** page by the whole process instance, so it shows every instance it has called, including from other call activities — not only the one you selected. ## Business ID for decision instances Starting in 8.10, a [business ID](/components/concepts/process-instance-creation.md#business-id) is shown for decision instances in Operate: as an optional filter field in the **Decisions** list, and in the header of a decision instance's details page, if one is defined for that instance. Filter decision instances by business ID the same way as [process instances](./filter-process-instances.md#business-id-filter) — using **Equals**, **Contains**, and **Is one of** in the filter UI, or the `$eq`/`$neq`/`$exists`/`$like`/`$in`/`$notIn` operators via the [search decision instances API](/apis-tools/orchestration-cluster-api-rest/specifications/search-decision-instances.api.mdx). Decision instances evaluated before 8.10 do not carry a business ID, since the value is snapshotted from the owning process instance at the decision instance's own creation time. Standalone decision evaluations, which are not tied to a process instance, never carry a business ID. --- ## Delete finished instances Learn how to delete finished process instances and decision instances in Camunda 8 Operate. ## Overview A finished process instance, meaning a canceled or a completed process instance, can be deleted from the **Processes** page or instance detail page. Evaluated and failed decision instances can be deleted from the **Decisions** page. :::warning You may not access process instances or decision instances after you've deleted them. ::: ## Delete process instances ### Batch delete from the Processes page To delete multiple process instances at once from the **Processes** page, take the following steps: 1. On the **Processes** page, in the **Filter** panel, apply the **Finished Instances** filter by checking the appropriate box. 2. Optionally, narrow down the list using additional filters. For detailed guidance on filtering options (including process name, version, and multi-variable filters), see [filter process instances](./filter-process-instances.md). 3. Select the process instances you want to delete by checking the checkbox next to each one, or use the select-all checkbox to select all instances. 4. Click the **Delete** button in the toolbar. 5. Confirm the delete operation by clicking **Delete**. ![The Processes page filtered to finished instances, with two rows selected and the batch action toolbar showing a Delete button.](./img/delete-finished-instances.png) ### Delete a single process instance from the Processes page To delete a single process instance from the **Processes** page, take the following steps: 1. On the **Processes** page, in the **Filter** panel, apply the **Finished Instances** filter by checking the appropriate box. 2. For detailed guidance on using the filter panel, see [filter process instances](./filter-process-instances.md). 3. Click the **Delete** button on any process instance you want to delete. 4. Confirm the delete operation by clicking **Delete**. ![The Processes page filtered to finished instances, showing a Delete link on each row of the Process Instances table.](./img/delete-finished-instances-single-row.png) ### Delete a process instance from the instance detail page 1. On the **Processes** page, in the **Filter** panel, apply the **Finished Instances** filter by checking the appropriate box. For detailed guidance on filtering, see [filter process instances](./filter-process-instances.md). 2. Navigate to the instance detail page by clicking the **Process Instance Key** of the process instance you want to delete. 3. In the page header, click the **Delete** button. 4. Confirm the delete operation by clicking **Delete**. ![A process instance detail page with a Delete link in the page header, next to the process instance's key, version, start date, end date, and called instances.](./img/delete-finished-instances-detail-page.png) ## Delete decision instances Evaluated and failed decision instances can be batch deleted from the **Decisions** page. ### Batch delete from the Decisions page 1. On the **Decisions** page, in the **Filter** panel, select the decision instances you want to delete using the **Evaluated** or **Failed** filters, or both. 2. Optionally, narrow down the list using additional filters such as **Decision Name**, **Decision Version**, or other available filters. 3. Select the decision instances you want to delete by checking the checkbox next to each one, or use the select-all checkbox to select all visible instances. 4. Click the **Delete** button in the toolbar. 5. Confirm the delete operation by clicking **Delete**. ![The Decisions page with three decision instances selected and the batch action toolbar showing a Delete button.](./img/delete-finished-instances-decisions.png) --- ## Delete resources Learn how to delete a specific process or decision definition version in Camunda 8 Operate. ## Delete process definition :::warning Deleting a process definition permanently removes it and has the following effects: - All the deleted process definition's finished process instances will be deleted from the application. - All decision and process instances referenced by the deleted process instances will be deleted. - If a process definition contains user tasks, they will be deleted from [Tasklist](/components/tasklist/introduction-to-tasklist.md). ::: To delete a process definition from the **Processes** page, take the following steps: 1. In the **Filter** panel, select a specific process version by filtering by process name and version. For detailed guidance on using filters, see [filter process instances](./filter-process-instances.md). Make sure the selected process definition version has no running instances, otherwise it is not possible to delete a process definition. You can [cancel or resolve running process instances](/components/operate/userguide/basic-operate-navigation.md) from the process instances list or from the process instance detail page. ![The Processes page filter panel, with arrows pointing at the Process Name and Process Version fields.](./img/delete-resources-process-filters.png) 2. Click the **Delete** button at the top right. ![The Processes page for a selected process, with an arrow pointing at the Delete link next to the Process ID.](./img/delete-resources-process-button.png) 3. Confirm the delete operation by checking the checkbox and clicking **Delete**. ![A confirmation modal for deleting a process definition, with a warning about the impact of deletion and a checkbox to confirm the deletion.](./img/delete-resources-process-modal.png) :::note A process definition that was deleted while it still had running instances (for example, using the [delete resource command](/apis-tools/zeebe-api/gateway-service.md#deleteresource-rpc)) is [draining](/components/concepts/resource-deletion.md#draining). Operate marks it with a **Draining** tag and removes it automatically once its last instance finishes. To see which instances are keeping it alive, open the definition and view its running instances. ::: ## Delete decision definition :::warning Deleting a decision definition will delete the DRD and will impact the following: - Deleting a decision definition removes the DRD that contains it. All other decision tables and literal expressions that are part of the DRD will also be deleted. - Deleting the only existing version of a decision definition could result in process incidents. ::: 1. On the **Decisions** page, select a specific decision version by filtering by decision name and version. For detailed guidance on filtering, see [filter process instances](./filter-process-instances.md). ![The Decisions page filter panel, with arrows pointing at the Decision Name and Decision Version fields.](./img/delete-resources-decision-filters.png) 2. Click the **Delete** button at the top right. ![A selected decision definition's page, with an arrow pointing at the Delete link next to the Decision ID.](./img/delete-resources-decision-button.png) 3. Confirm the delete operation by checking the checkbox and clicking **Delete**. ![A confirmation modal for deleting a decision definition (DRD), with a warning about the impact of deletion and a checkbox to confirm the deletion.](./img/delete-resources-decision-modal.png) --- ## Filter process instances Narrow down large lists of process instances using filters. Combine multiple variable filters with `AND` logic to find the exact instances you need. :::note Related pages - **[Delete finished instances](./delete-finished-instances.md)** — Use filters to select instances for deletion. - **[Resolve incidents and update variables](./resolve-incidents-update-variables.md)** — Use filters to locate instances that need action. ::: ## Filter panel The **Filter** panel appears on the left side of the **Processes** page. Open it by clicking the **Filter** icon or expand existing filters to refine your instance list. ### Available filters **Process filters**: - **Finished Instances** — Completed or canceled instances - **Process Name** — Filter by process definition name - **Process Version** — Filter by process definition version - **Parent Process Instance Key** — Filter by parent instance (for call activities) **Instance filters**: - **Batch Process Instance Key** — Filter by batch operation ID - **Process Instance Key** — Filter by exact instance ID - **Business ID** — Filter by domain-specific identifier (order number, case reference, etc.); see [business ID filter](#business-id-filter) **Variable filters** (new in 8.10): - **Variable Filters** — Filter by variable name, value, and comparison operators (see [multi-variable filters](#multi-variable-filters) below) ## Single variable filters For basic filtering by a single variable, use the inline variable filter in the sidebar. ### How to add a single variable filter 1. In the **Filter** panel, scroll to **Variable Filters**. 2. Click the **Add/Edit conditions** button in the sidebar to open the filter modal. 3. Enter the variable name and select an operator. 4. Enter the comparison value. 5. Click **Apply** to filter the instance list. ### Operators for single variables All operators are also available for multi-variable filters (see table below). ## Multi-variable filters Combine multiple variable filters with AND logic to find instances matching complex criteria in one step. **Example**: Find all pending EMEA orders over €10,000 by filtering: - `status` equals `"pending"` **AND** - `region` equals `"EMEA"` **AND** - `amount` equals `10000` ### How to add multi-variable filters 1. In the **Filter** panel, under **Variable Filters**, click the **Add/Edit conditions** button in the sidebar. 2. The **Variable Filters** modal opens. 3. Click **Add condition** to add the first variable filter. 4. Enter the **Variable name** (free text). 5. Select an **Operator** from the dropdown. 6. Enter the **Value** for comparison. 7. Click **Add condition** again to add more filters. AND indicators appear between filter rows, showing that all conditions must match. 8. Click **Apply** to filter the instance list. The sidebar displays a count of active variable filter conditions. Click **Add/Edit conditions** again to modify existing filters. ### Operators reference | Operator | Behavior | Example | | ------------------ | ------------------------------------------------------------------------- | --------------------------------------------------- | | **equals** | Exact match on variable value (searches truncated value only) | Find instances where `status = "pending"` | | **not equal** | Value does not match (searches truncated value only) | Find instances where `status ≠ "error"` | | **contains** | Case-sensitive substring match (searches truncated value only) | Find order IDs containing `"2024"` | | **is one of** | Match any value in a comma-separated list (searches truncated value only) | Find instances where `priority` in `(high, urgent)` | | **exists** | Variable is present (any value) | Find instances with a specific variable set | | **does not exist** | Variable is not present | Find instances missing a required variable | :::caution Value-based variable filters (`equals`, `not equal`, `is one of`, and `contains`) search only the first ~8,000 characters of a variable value. If a match exists beyond this boundary, it will not be returned. In the Operate UI, the inline warning appears on **contains** only. ::: ### AND logic All conditions are combined with AND logic — an instance must match **all** conditions to appear in results. AND indicators between filter rows make this logical combination explicit. OR logic and nested conditions are not supported. ## Smart value transformations The filter automatically transforms entered values based on context: | You enter | Operator | Interpreted as | Behavior | | ------------ | --------- | ------------------------ | --------------------------------------- | | `hello` | equals | String `"hello"` | Exact match on the string (with quotes) | | `42` | equals | Number `42` | Exact match on numeric value | | `true` | equals | Boolean `true` | Exact match on boolean | | `null` | equals | Null | Exact match on null value | | `val1, val2` | is one of | Array `["val1", "val2"]` | Match either value | | `{"x":1}` | equals | Object `{"x":1}` | Exact match on the JSON object | ### Edge cases - **Leading zeros** (for example, `01234`) are preserved as strings, not converted to numbers. - **Comma-separated values** split into a list only with the **is one of** operator; otherwise, commas are treated as literal characters. To include a literal comma, wrap the value in quotes. - **Null vs. "null"**: The exact string `null` (no quotes) is treated as null; the string `"null"` (with quotes) is treated as a text string. - **Contains operator**: Does NOT transform values; it searches for the literal substring you entered. ## Nested JSON editor For complex variable values (JSON objects), use the JSON editor to construct filters precisely. ### How to use the JSON editor 1. In the **Variable Filters** modal, click on a condition row. 2. In the modal, toggle the **JSON** tab to switch from **Fields** view to **JSON** view. 3. The JSON editor shows the filter structure (or paste valid JSON). 4. Use the **maximize** icon to open the editor in a focused modal for complex values. The parent modal dims for focus, and the JSON editor displays a contextual title indicating which variable/row is being edited. 5. Toggle back to **Fields** to confirm the transformation, or edit directly in JSON. Changes transform losslessly between Fields and JSON views. The JSON structure mirrors the Camunda API filter schema, so you can share or reuse complex filters with teammates. The round-trip between Fields and JSON is lossless — no data is lost in translation. ## Performance considerations Each variable condition adds filtering overhead. At **8+ conditions**, the filter interface displays an informational warning below the list of conditions: _"Filtering by many conditions can be slow. Add conditions only if you need them."_ This warning appears once per session and is informational only — you can add more conditions if needed, but be aware of potential performance impact on large process instance lists. ### Best practices - Start with fewer conditions and add more if needed. - Use **exists** / **does not exist** to check for variable presence before filtering on value. - Combine with other filters (Process Name, Finished Instances) to reduce the initial instance list. ## Filter persistence Filter conditions persist while you remain on the **Processes** page. If you: - **Refresh the page**: Filters are preserved for the current session. - **Navigate away and return**: Filters are cleared. - **Click the reset icon**: All filters are removed. :::note Filter state is stored locally in your browser session; it cannot be shared via URL or exported. ::: ## Common workflows ### Find instances with missing variables Use the **does not exist** operator to locate instances missing a critical variable: 1. Click **Add/Edit conditions**. 2. Enter the variable name (for example, `customerId`). 3. Select **does not exist**. 4. Click **Apply**. ### Find instances matching multiple status values Use the **is one of** operator: 1. Enter variable name: `status` 2. Select **is one of** 3. Enter values: `pending, processing, review` (comma-separated) 4. Click **Apply** ### Find instances by numeric or date values Enter the value as you would compare it (for example, `42` for numbers, `2024-01-15` for dates): 1. Enter variable name: `amount` or `createdDate` 2. Select **equals** or **contains** 3. Enter the value 4. Click **Apply** :::note Numeric comparison operators are not supported. Use **equals** or **contains** as alternatives. ::: ## Business ID filter A [business ID](/components/concepts/process-instance-creation.md#business-id) is a domain-specific identifier assigned to a process instance at creation — for example, an order number, case reference, or ticket ID. Starting in 8.10, you can filter process instances by business ID directly in the Operate UI. To filter by business ID, open the **Filter** panel and use the **Business ID** field. | UI operator | Behavior | Wildcards | | :------------ | :------------------------------------------------- | :---------------------------------------------------------- | | **Equals** | Exact match on the business ID value. | — | | **Contains** | Pattern match. | `*` matches multiple characters, `?` matches one character. | | **Is one of** | Matches any business ID in a comma-separated list. | — | For additional operators (`$neq`, `$exists`), use the [search process instances API](/apis-tools/orchestration-cluster-api-rest/specifications/search-process-instances.api.mdx) with the `businessId` filter field. ## Known limitations - **Numeric operators**: Greater than, less than, and range operators are not available. Use exact match or prefix matching with **contains**. - **OR logic**: Filters are combined with AND logic only; OR conditions are not supported. - **Variable name autocomplete**: Variable names must be entered as free text. Variable name suggestions based on process definitions are a planned future enhancement. - **Nested JSON path filtering**: Filtering on nested properties within JSON objects (for example, `customer.region`) is not supported; filter on the top-level variable only. - **Value truncation**: Value-based operators (`equals`, `not equal`, `is one of`, and `contains`) search only the first ~8,000 characters of a variable value. - **Filter sharing**: Filter state is stored in your browser session and cannot be shared via URL. See [filter persistence](#filter-persistence) for details. ## Related topics - [Delete finished instances](./delete-finished-instances.md) — Use filters to find instances for bulk deletion - [Resolve incidents and update variables](./resolve-incidents-update-variables.md) — Use filters to locate and fix problematic instances - [Operate API](/apis-tools/operate-api/overview.md) — Advanced filtering via REST API --- ## Manage batch operations Learn how to manage [batch operations](../../concepts/batch-operations.md) in Camunda 8 Operate. ## Overview You can suspend, resume, or cancel batch operations when they're in one of the following [states](../../concepts/batch-operations.md#states): | State | Available actions | | --------- | ----------------- | | Active | Suspend, cancel | | Suspended | Resume, cancel | | Created | Cancel | ## Before you begin Before you begin, ensure you're [authorized to update batch operations](../overview/access-control.md#optional-authorizations). ## Manage a batch operation To take an action on a batch operation: 1. In the left navigation, click **Operations > Batch operations**. For more details, see [monitor batch operations](./monitor-batch-operations.md). 2. From the **Batch Operations** page, click the operation you want to manage. 3. On the batch operation details page, on the right side of the page header, click an action, if available. ![A batch operation details page for an Active batch operation, with a Suspend button and an overflow menu available in the page header.](./img/manage-batch-operation.png) --- ## Monitor batch operations Learn how to monitor the [batch operations](../../concepts/batch-operations.md) in Camunda 8 Operate. ## Before you begin To follow the steps in this guide, you must be [authorized to view running and completed batch operations](../overview/access-control.md#optional-authorizations). ## Monitor batch operations 1. In the top navigation, click **Operations > Batch operations**. This opens the [**Batch Operations** page](../overview/batch-operations-overview.md). 2. To see the details of a particular batch operation, click the operation in the table. This opens the [batch operation details page](../overview/batch-operation-details-overview.md). ![The Batch Operations page, listing batch operations with their operation type, batch state, items, actor, and start date.](./img/monitor-batch-operations.png) ## View failure reasons On the batch operation details page, expand a failed item row to view the failure reason. ## Next steps - [Learn how to audit process instance operations](./audit-operations.md). - [Learn how to manage a batch operation](./manage-batch-operation.md). --- ## Move a batch of process instances Learn how to move a [batch](../../concepts/batch-operations.md) of process instances from one node to another in Camunda 8 Operate. ## Overview If there was an issue in process execution that caused you to enter the wrong process branch or data was corrupted, you may need to select multiple process instances and move them to the correct element in the process in a single operation. :::note If you only need to modify a single active process instance to allow execution to continue, use [process instance modification](./process-instance-modification.md) instead, which also provides some additional modification options beyond move modification, like activating an element, terminating an element instance, etc. ::: ## Unsupported modifications Some elements do not support specific modifications: - **Move** modifications are not possible for the following types of elements: - Start events - Boundary events - Events attached to event-based gateways - **Move** modification is not possible for a subprocess itself. ## Enter batch modification mode 1. On the **Processes** page, in the **Filter** panel, select the **Name** and **Version** of the process you want to modify. For detailed guidance on filtering, see [filter process instances](./filter-process-instances.md). 2. Select the element containing the process instances you intend to move. 3. In the **Process Instances** table, start selecting which instances you want to move. As you select instances, the process instance toolbar will appear and you will now see the **Move** action become available. 4. Once you are ready to continue, click **Move**. An information modal will appear indicating that you are switching to process instance batch move mode. 5. Click **Continue** and the UI will change to indicate that you entered batch modification mode. This is represented by a blue border, including a blue banner at the top and two buttons for applying or exiting modifications at the bottom. 6. You can now continue to select or deselect instances as needed. You can also discard all current selected instances by either clicking the **Discard** option in the process instance toolbar, or by clearing the current selection. Exit the modification mode at any time by clicking **Exit** in the footer. ## Move selected process instances from one element to another 1. Select the element you want to move the selected process instances to. 2. When you have made all your intended selections and you are ready to continue, click **Review Modification** in the footer. A confirmation modal will appear indicating that you are about to apply the selected modifications. 3. Click the **Apply** button from the summary modal to start the batch modification. When moving elements inside multi-instance subprocesses, the move operation terminates only that specific element instance and activates the target element in the same instance of the multi-instance subprocess. ## Next steps - [Monitor the batch operation](./monitor-batch-operations.md). - [Learn how batch operations work](../../zeebe/technical-concepts/batch-operations.md). --- ## Migrate process instances Learn how to migrate process instances from one process definition version to another in Camunda 8 Operate. ## Before you begin Before you try to migrate process instances, learn about the [limitations](/components/concepts/process-instance-migration.md#limitations) of process instance migration. ## Select process instances 1. From the **Processes** page, select a specific process and version from the **Filter** panel. For detailed guidance on filtering, see [filter process instances](./filter-process-instances.md). This will be the source process version where instances are migrated from. 2. Select all instances from the **Process Instances** table that should be migrated to another process version. 3. Click **Migrate** to enter the migration view. 4. In the modal, click **Continue**. The migration view features three areas: the source process diagram (top left), the target process diagram (top right) and the element mapping (bottom panel). ![The migration view showing the source and target process diagrams at the top, and the source-to-target element mapping table below.](./img/process-instance-migration.png) ## Select a target process version Above the target process diagram, enter a target process into the **Target** box, and select a version from the dropdown. This will be the process version where all selected process instances are migrated to. ## Map source and target nodes In the bottom panel, you can see a list of all service tasks from the source process. 1. Use the dropdowns to select a target element for each source element that should be part of the migration. It is currently only possible to map elements with migration [supported by Zeebe](/components/concepts/process-instance-migration.md#supported-bpmn-elements). 2. (Optional) Click on an element in the diagram or on a source element row in the bottom panel to see how elements are mapped. 3. In the footer, click **Next** for a preview of the migration plan. Now, you can see a preview of how elements are mapped and how many process instances are expected to be migrated. ## Start the migration 1. In the footer, click **Confirm** to review your migration. 2. When ready, enter the word **MIGRATE** into the text box. 3. Click **Confirm** again to start the migration operation. ## Next steps - [Monitor the batch operation](./monitor-batch-operations.md). - [Learn about the limitations of process instance migration](/components/concepts/process-instance-migration.md#limitations). --- ## Modify a process instance Learn how to modify a process instance in Camunda 8 Operate. ## Overview You may need to modify an active process instance to allow execution to continue. The execution may be stuck, and you may want to continue the execution on a different activity (i.e. skip or repeat activities). :::note If an issue in process execution caused you to enter the wrong process branch instead of moving each process instance individually, use [process instance batch modification](./process-instance-batch-modification.md) instead to select multiple process instances and move them to the correct flow node in the process in a single operation. ::: ## Unsupported modifications Some elements do not support specific modifications: - **Add token**/**Move tokens to** modifications are not possible for the following type of elements: - Start events - Boundary events - Events attached to event-based gateways - **Move tokens from** modification is not possible for a subprocess itself. - **Add token** modifications are not currently supported for elements with multiple running scopes. :::note Multi-instance subprocesses While **Add token** modifications are not supported for elements inside multi-instance subprocesses, **Move** modifications are supported. When you move an element instance within a multi-instance subprocess, the operation terminates only that specific element instance and activates the target element in the same instance of the multi-instance subprocess. ::: ## Enter process instance modification mode To enter modification mode: 1. On the **Processes** page, find the process instance you want to modify in the **Process Instances** table. 2. Click the process instance key. This brings you to the instance detail page. 3. In the header, select **Modify**. 4. In the modal, click **Continue**. The UI will change when you enter modification mode, including a blue banner at the top and two buttons for applying or discarding modifications at the bottom. ![A process instance in modification mode, indicated by the blue "Process Instance Modification Mode" banner at the top of the page.](./img/process-instance-modification.png) ## Cancel all running tokens on an element To cancel all running tokens on an element, take the following steps: 1. Select the flow node you want to cancel all the running tokens on. 2. Click **Cancel instance**. View the pending modification reflected in the **Instance History** panel. ## Add a new token to an element To add a new token to an element, take the following steps: 1. Select the flow node you want to add the new token to. 2. Click **Add**. View the pending modification reflected in the **Instance History** panel. ## Move all running tokens from one flow node to another The move operation is equivalent to the combination of **Cancel** and **Add** modifications. The modifications described previously can also be achieved with one single move modification. 1. Select the flow node you want to move the running tokens from. 2. Click **Move instance**. 3. Select the flow node you want to move the running tokens to. View the pending modification reflected in the **Instance History** panel. ## Add variable to new scopes During the modification mode, if there are new scopes generated it will be possible to add variables to these new scopes by following these steps: 1. Select the new scope from the **Instance History** panel you want to add a variable to. 2. Click **Add Variable** from the **Variables** panel. 3. Fill out the **Name** and **Value** fields for the variable you want to add. 4. Once you blur out of the field (click anywhere on the screen other than the last edited variable field), assuming the fields have the valid values, the new variable will be added to the [pending modifications](#view-summary-of-pending-modifications). ## Edit variable on existing scopes During modification mode it is not possible to edit existing variables in existing local scopes. Only variables in global scope can be edited by following these steps: 1. Plan at least one **Add** or **Move** modification. 2. Select the global scope from the **Instance History** panel. 3. Edit the variable value from the **Variables** panel. 4. Once you blur out of the field (click anywhere in the screen other than the last edited variable field), assuming the new value is valid, the **Edit Variable** modification will be added to the [pending modifications](#view-summary-of-pending-modifications). ## View summary of pending modifications To display the pending modifications, click **Review Modifications** in the footer. A modal will be displayed where all modifications can be seen. Within this modal, you can: - Delete any modification by clicking **Delete**. - View an added/edited variable in a JSON/Diff Viewer. - Cancel/close the modal, and continue with modification mode. - Apply the modifications and exit modification mode. ## Undo modification Clicking **Undo** from the modification footer will undo the latest modification. ## Apply modifications If you click the **Apply** button from the summary modal as described [here](#view-summary-of-pending-modifications), and modification operation is created successfully, you will observe a success notification and changes will be reflected in a short time. ## Next steps - [Learn how to audit process instance operations](./audit-operations.md). - [Learn how to move a batch of process instances](./process-instance-batch-modification.md) --- ## Resolve incidents and update variables Learn how to resolve incidents and update variables in Camunda 8 Operate. ## Overview Every process instance created for the [`order-process.bpmn`](/bpmn/operate/order-process.bpmn) process model requires an `orderValue` so the XOR gateway evaluation will happen properly. Let’s look at a case where `orderValue` is present and was set as a string, but our `order-process.bpmn` model required an integer to properly evaluate the `orderValue` and route the instance. ## Before you begin To follow this guide, install [`zbctl`](https://github.com/camunda-community-hub/zeebe-client-go/blob/main/cmd/zbctl/zbctl.md), a community-supported command line interface for interacting with Camunda 8. ## Create a process instance Create a process instance with an `orderValue` of `"99"`: ``` ./bin/zbctl --insecure create instance order-process --variables '{"orderId": "1234", "orderValue":"99"}' ``` ``` ./bin/zbctl.darwin --insecure create instance order-process --variables '{"orderId": "1234", "orderValue":"99"}' ``` ``` ./bin/zbctl.exe --insecure create instance order-process --variables '{\"orderId\": \"1234\", \ "orderValue\": \"99\"}' ``` ## Advance an instance to an XOR gateway To advance the instance to our XOR gateway, we’ll create a job worker to complete the `Initiate Payment` task: ``` ./bin/zbctl --insecure create worker initiate-payment --handler cat ``` ``` ./bin/zbctl.darwin --insecure create worker initiate-payment --handler cat ``` ``` ./bin/zbctl.exe --insecure create worker initiate-payment --handler "findstr .*" ``` We’ll publish a message that will be correlated with the instance, so we can advance past the `Payment Received` intermediate message catch event: ``` ./bin/zbctl --insecure publish message "payment-received" --correlationKey="1234" ``` ``` ./bin/zbctl.darwin --insecure publish message "payment-received" --correlationKey="1234" ``` ``` ./bin/zbctl.exe --insecure publish message "payment-received" --correlationKey="1234" ``` In the Operate interface, you should now observe the process instance has an [incident](/components/concepts/incidents.md), which means there’s a problem with process execution that must be fixed before the process instance can progress to the next step. ![A process instance with one incident shown in the header, the Order Value? gateway marked with an incident indicator in the diagram, and the Incidents tab open in the bottom panel listing the extract value error.](./img/resolve-incidents-update-variables.png) ## Diagnosing and resolving incidents Operate provides tools for diagnosing and resolving incidents. Let’s go through incident diagnosis and resolution step by step. When we inspect the process instance, we can observe exactly what our incident is: 1. Click the **Process Instance Key** for the process stuck on the **Order Value?** node. 2. Navigate to the **Incidents** tab in the bottom panel. 3. Observe the error message: ```text Expected result of the condition expression 'orderValue >= 100' of sequence flow 'Flow_1n8m1op' to be 'BOOLEAN', but was 'NULL'. The evaluation reported the following warnings: [NOT_COMPARABLE] Can't compare '"99"' with '100' ``` To resolve this incident, we must edit the `orderValue` variable so it’s an integer. To do so, take the following steps: 1. Under **Variables**, click the edit icon next to the `orderValue` variable. 2. Edit the variable by removing the quotation marks. 3. Click the checkmark icon to save the change. We were able to solve this particular problem by **editing** a variable, but it’s worth noting you can also **add** a variable if a variable is missing from a process instance altogether. Lastly, initiate a "retry" of the process instance by selecting **Retry** in the top right corner of the page. You should now see the incident has been resolved, and the process instance has progressed to the next step. :::note Selecting **Retry** marks the incident as resolved and triggers a retry. It does not verify that the underlying cause has been fixed. For a [job incident](/components/concepts/incidents.md#resolving), Camunda checks the problem again only when a worker next activates the job. If no worker is connected for the job type, the incident does not reappear even if the cause is still present. As a result, the process instance can appear healthy in Operate even though the problem remains unresolved. Keep a worker connected for the affected job type so Camunda can raise a new incident promptly if the cause remains unresolved. ::: ## Complete a process instance If you’d like to complete the process instance, create a worker for the `Ship Without Insurance` task: ``` ./bin/zbctl --insecure create worker ship-without-insurance --handler cat ``` ``` ./bin/zbctl.darwin --insecure create worker ship-without-insurance --handler cat ``` ``` ./bin/zbctl.exe --insecure create worker ship-without-insurance --handler "findstr .*" ``` --- ## Initiate a batch operation Learn how to initiate [batch operations](../../concepts/batch-operations.md) in Camunda 8 Operate. ## Overview In some cases, you’ll need to retry or cancel many process instances at once. Operate also supports this type of operation. Imagine a case where many process instances have an incident caused by the same issue. At some point, the underlying problem will have been resolved (for example, maybe a microservice was down for an extended period of time, then was brought back up.) Though the underlying problem was resolved, the affected process instances are stuck until they are “retried." ## Initiate a batch operation Let's create a **selection** in Operate. A selection is a set of process instances on which you can carry out a batch retry or batch cancellation. To create a selection and apply an operation, take the following steps: 1. On the **Processes** page, in the **Process Instances** table, check the box next to the process instances you'd like to include. 2. In the table header, select the operation you want to apply. ![Three process instances selected in the Process Instances table, with the batch action toolbar visible above the table.](./img/selections-operations.png) :::note A batch retry reports an item as completed once Camunda marks its incident as resolved and triggers the retry. This confirms that the retry operation ran, not that the underlying problem was fixed. For example, `12 of 12 completed` means all 12 retries were triggered successfully. For [job incidents](/components/concepts/incidents.md#resolving), Camunda checks the underlying cause again only when a worker next activates each job. Keep a worker connected for the affected job types so Camunda can raise new incidents promptly if the problems remain unresolved. ::: ## Next steps - [Monitor the batch operation](./monitor-batch-operations.md). - [Learn how batch operations work](../../zeebe/technical-concepts/batch-operations.md). --- ## Suspend and resume a process instance In Operate, you can suspend a running process instance to temporarily pause execution, then resume it to continue from where it left off. While a process instance is suspended, no jobs are handed out to workers, and external triggers such as messages and signals aren't processed. For a full explanation of what happens during suspension, see [process instance suspension](../../concepts/process-instance-suspension.md). ## Suspend a process instance ### Suspend a single instance from the instance detail page 1. On the **Processes** page, find the process instance to suspend in the **Process Instances** table. 2. Click the process instance key to open the instance detail page. 3. In the page header, click **Suspend**. The instance state changes to **Suspended**. ### Suspend multiple instances from the Processes page 1. On the **Processes** page, select the process instances to suspend by checking the checkbox next to each one, or use the select-all checkbox to select all visible instances. 2. In the toolbar, click **Suspend**. 3. In the **Apply operation** dialog, click **Apply**. Only active instances in the selection are suspended. Suspended or finished instances are ignored. ## Resume a process instance ### Resume a single instance from the instance detail page 1. On the **Processes** page, in the **Filter** panel, select the **Suspended** checkbox to show suspended instances. 2. Click the process instance key to open the instance detail page. 3. In the page header, click **Resume**. The instance state changes to **Active** and execution resumes. ### Resume multiple instances from the Processes page 1. On the **Processes** page, in the **Filter** panel, select the **Suspended** checkbox to show suspended instances. 2. Select the instances to resume by checking the checkbox next to each one, or use the select-all checkbox. 3. In the toolbar, click **Resume**. 4. In the **Apply operation** dialog, click **Apply**. Only suspended instances in the selection are resumed. Active or finished instances are ignored. --- ## View wait states See what an active process instance is waiting for in Camunda 8 Operate. ## Prerequisites To follow the steps in this guide, you must be [authorized to view wait states](../../wait-states/overview.md#access-control) for the relevant process definition. ## View wait states When a process instance is active, you can inspect its active elements to see what each one is waiting for: 1. On the **Processes** page, in the **Process Instances** table, click the **Process Instance Key** of the instance you want to inspect. 2. In the process diagram, click an active element that has a **Waiting** label indicator on top. 3. Review the wait state details in the element **Details** tab — for example, the message name and correlation key for a receive task, or the due date for a timer. An element shows a single wait state indicator even when multiple tokens are present on it. For timers, Operate shows the earliest due date. ![An active process instance with two elements showing a Waiting label. The Charge payment element is selected, and the Details tab shows the wait state status and properties.](../../../images/operate/operate-wait-states.png) ## Supported wait state types For the full list of wait state types and the details surfaced for each, see [supported wait state types](../../wait-states/overview.md#supported-wait-state-types). ## Next steps - [Use the Camunda REST API to search wait states](/apis-tools/orchestration-cluster-api-rest/specifications/search-element-instance-wait-states.api.mdx). --- ## Getting started(Optimize) Beginner Time estimate: 20 minutes ## Purpose The following document provides a basic end-to-end glance into Optimize and its features for new business users. Optimize offers business intelligence tooling for Camunda customers. By leveraging data collected during process execution, you can access reports, share process intelligence, analyze bottlenecks, and examine areas in business processes for improvement. With Optimize, review heatmap displays for instances which took longer than average to discover long-running flow nodes. As a result, reap actionable insights and rapidly identify the constraints of your system. For an in-depth overview of Optimize's capabilities, visit our [Optimize documentation](/components/optimize/what-is-optimize.md). ## Before you begin Before getting started with Optimize: 1. If using SaaS, [create a Camunda 8 account](/components/saas/organization/manage-plan/create-account.md). 1. [Create a cluster](/components/saas/clusters/create-cluster.md) in Camunda Hub. :::note So long as you are operating with [Camunda 8 1.2+](https://camunda.com/blog/2021/10/camunda-cloud-1-2-0-released/) when creating a cluster, you can access Optimize. From here, Optimize requires no additional set up. You can immediately obtain process insights as Optimize already continuously collects data for analysis. ::: You can begin analyzing reports and dashboards with just two process versions. However, the more process versions you work with in Optimize, the more performance attributes and data trends you’ll be able to study. For the purposes of this guide, we’ve preconfigured several processes to demonstrate Optimize’s capabilities. ## Open Optimize From Camunda Hub, you can open Optimize in any [environment](/components/concepts/environments.md) you have access to: 1. Log in to Camunda Hub. 1. In the left navigation, click **Environments**, and then select an environment. Each environment has its own instance of Optimize. 1. Under **Applications**, on the **Optimize** card, click **Open**. This opens Optimize for the environment in a new tab. You can also expand an environment in the left navigation, and select Optimize from its applications. :::tip If the environment is paused, you must [resume it](/components/hub/organization/manage-environments/index.md#resume-a-paused-environment) to access its applications. ::: ## Create and analyze dashboards Within Optimize, **reports** are based on a _single_ visualization, similar to a single chart or graph. **Dashboards** are aggregations of these visualizations, similar to a full spreadsheet of data collections, or a combination of several comparative charts and graphs. **Collections** are groups of these data sets, similar to project folders for organizational purposes where we can nest a series of dashboards and/or reports within. Once you open Optimize, you’ll first view the homepage for these collections, dashboards, and reports. To create a collection on the **Home** page, select **Create New > Collection**. Then, you can name your collection and select which data sources and processes will be available. Note that you can select up to 10 processes at once. From within your collection, you can again select **Create New** and draft reports and dashboards. Add users and additional data sources by navigating between the tabs inside the collection. Let’s create a dashboard inside our first collection. Take the following steps: 1. Return to the **Home** page to view a list of existing collections, dashboards, and reports. You’ll be able to view all process instances you’ve already run and retrieve additional data on these instances within the Camunda engine. 2. Select the collection where you’d like to create a dashboard. 3. Click **Create New > New Dashboard**. 4. Optimize offers preconfigured dashboard templates, or you can start from a blank dashboard. In this example, we’ll select a preconfigured template by clicking the **Process performance overview** option. Note that you can also create dashboards with multi-process templates. 5. Under **Select Process**, choose the process you’d like to analyze and the version. 6. Click **Create Dashboard**. 7. Name your dashboard, and add any additional existing reports or create filters. Click **Save**. ![dashboard example](./assets/dashboard.png) In the sample above, Optimize drafted a dashboard filled with reports for review. These reports include objectives like process instance counts, aggregated process duration, active incidents, and heatmaps. Select **Edit > Add a Report** to incorporate additional reports you’ve already created (see [create and access reports](#create-and-access-reports) below). Click and drag the reports on the grid to arrange the dashboard to your liking. :::note Optimize offers collaborative capabilities, too. Click the **Share** tab to share your dashboard. Toggle to **Enable sharing**, and copy or embed the provided link. Colleagues without access to Optimize can still view your report with the shared link. ::: ## Create and access reports To create a custom report based on a key performance indicator (KPI) you’d like to analyze, and to incorporate this report into a dashboard, follow the steps below: 1. On the right side of the **Collections** page, select **Create New > Report**. Here we’ll take a look at a single process, though you can also view data from multiple processes. 2. Click the text box under **Select Process** and select the process you’d like to analyze. 3. Select the type of report you’d like to use on the right side of the **Create new Report** box. As with dashboards, Optimize offers preconfigured templates such as heatmaps and tables. We’ll begin with a heatmap. 4. Click **Create Report**. 5. Set up and customize your report. Begin by naming your report in the text box at the top of the page, pre-filled with **New Report**. 6. In the gray text box to the right, confirm your data source, and select what you’d like to review from the process (in this case, we are viewing flow nodes.) You can also group by topics such as duration or start date. 7. If you’d like, filter the process instance or flow nodes. For example, you can filter by duration, only viewing process instances running for more than seven days. 8. Finally, you have the option to view particular sets of data from the instance, like instance count or absolute value, by selecting the gear icon to the left of your data customization. You can also choose how you’d like to visualize your data in the box beneath **Visualization** (i.e. bar chart, pie chart, etc.). Once you’ve made your selections, click **Save**. ## Alerts You don’t have to log in or view reports and dashboards to be alerted that something may need correction or further analysis in your process. For this purpose, you can create new alerts for reports within your collections. These alerts watch reports for you among collections, and email you an alert if a set outlier occurs in your process flow. To create an alert, take the following steps: 1. Create a report with a number visualization inside a collection for a KPI you want to track. 2. Inside your collection, select the **Alerts** tab. 3. Select the type of alert you would like to receive. For example, you can receive an email notification when the backlog on your bottleneck becomes too high. As you’re notified, you can begin to examine if the process is broken and if additional teams need to be notified. ## Collections Within your collection, you can also access the **Users** and **Data Sources** tabs to further customize your collection. ### Users Within the **Users** tab, review the users with access to your collection. Select **Add** to search for a user to add, who can be assigned as a viewer, editor, or manager. ### Data sources Within the **Data Sources** tab, review and add source(s) of your data to create reports and dashboards inside the collection. ## Additional analysis Now that we’ve created data sets within the **Home** page, let’s shift into the **Analysis** tab. Inside this tab, you’ll notice **Task Analysis** and **Branch Analysis**. ### Task analysis Inside **Task Analysis**, we utilize heatmap displays. Click **Select Process**, choose your process, and choose your version. ![heatmap example](./assets/heatmap.png) Within the example above, we notice increased heat (recognized as red) surrounding our invoice approved gateway. Several instances have taken significantly longer than average, so we may choose to take a closer look at these instances by downloading the instance IDs, or viewing the details for further analysis. Here, you can also find if the outliers have a shared variable. ### Branch analysis Inside the **Branch Analysis** tab, we can select a ​​process and analyze how particular gateway branches impact the probability of reaching an end event. Fill in the process field, click on a gateway, and choose your end event. In the example below, we can further analyze the likelihood of an invoice being processed once it reaches the gateway for approval: ![branch analysis example](./assets/analysis.png) Here, we’ve selected a process flow, gateway, and endpoint for a breakdown of all the instances that went through a particular gateway to a specific endpoint. Hover over the gateway for a breakdown of the process itself. ## Additional resources and next steps We’ve only touched the surface of Optimize. The component is full of additional ways to analyze your data for effective process improvement. We recommend taking a look at several resources to catch up on Optimize’s latest release, new features, and many usage examples: - [Camunda Academy: Measure Value Using Optimize](https://academy.camunda.com/c8-measure-value-using-optimize) - [Camunda Optimize 3.6.0 Release](https://camunda.com/blog/2021/10/camunda-optimize-360-released/) - [The Ultimate Guide to Solving Bottlenecks with Camunda: Part 1](https://camunda.com/blog/2021/10/the-ultimate-guide-to-solving-bottlenecks-with-camunda-part-1/) - [Camunda Optimize examples](https://github.com/camunda/camunda-optimize-examples) - [Process performance made transparent](https://camunda.com/platform/optimize/reports/) --- ## Alerts Optimize's alerting functionality can be used to notify you when your report hits a predefined critical value. You can create alerts for any number reports that exist within a collection. To configure an alert, take the following steps: 1. Inside a collection, navigate to the **Alerts** tab to create and view all alerts defined for reports in this collection. You can manage an alert by moving the mouse over the alert entry and clicking the **Edit** or **Delete** buttons in the context menu on the right side of the page. ![Alert overview](./img/alerts-overview.png) 2. Click **Create New Alert** to create a new alert. You will then see the following modal: ![Alert modal overview](./img/alert-modal-description.png) 3. To give the alert a name, select the report and define a target webhook or email address of the person who will receive the alert. :::note In Camunda 7 and Camunda 8 Self-Managed, you must configure the email service to receive notifications. See the [technical guide](/self-managed/components/optimize/configuration/system-configuration.md#email) for which properties need to be defined. ::: :::note Only known users in Camunda Hub (SaaS) and Identity (Self-Managed) can receive Optimize alert emails. ::: Note that alerts can only be created for reports which are visualized as a single number and are in the same collection as the alert. Visit the [reports section](../creating-reports.md) on how to define single-number reports. 4. Set a threshold which defines when an alert should be triggered. A notification is sent to the configured email address or webhook as soon as a report value hits the threshold. If reminder notifications are enabled, the alert will continue to send notifications for as long as the value is above (or below, as defined) the threshold. Finally, you'll get a resolve notification as soon as the report value is within a typical range. For example, say you defined an alert which should be triggered when the report value becomes greater than 50. You also enabled reminder notifications to be sent each hour. Here's what that would look like: ![Notifications graph](./img/alert-notifications-graph.png) --- ## Export and import ## Exporting entities Users can export entities by accessing the **Export** option in the entity menu. This downloads a JSON file which includes all relevant information that defines the selected entity. This file can later be used to import the exported entity into a different Optimize installation. ![Exporting a Process Report](./img/export.png) ## Importing entities ### Prerequisites Exported entities can be imported both as private entities and into a selected collection, provided the following prerequisites are met: - All definitions the entity requires exist in the target Optimize. - When importing into a collection, the collection data sources must include all relevant definitions for the entity. - The importing user is authorized to access the relevant definitions. - The entity data structures match. To ensure matching data structures, confirm the Optimize version of the source is the same as the version of the target Optimize. If any of the above conditions are not met, the import will fail. Optimize will display an error message explaining why the import was not successful to enable you to fix the issue and retry. ### Importing private entities To import an entity as a private entity, use the **Import Report/Dashboard** option from the **Create New** menu on the welcome page. The entity will appear in the entity list once the import is finished and can be interacted with as usual. ![Importing a private entity](./img/private-import.png) ### Importing entities into a collection To add the entity to an existing collection, use the same **Import Report/Dashboard** option from the **Create New** menu from within the selected collection. This will import the entity into the collection. Any user that has access to this collection can now also access the imported entity. :::note The collection must have all data sources required by the imported entity or the import will fail. ::: ![Importing an entity into a Collection](./img/collection-import.png) --- ## Machine learning-ready data set The machine learning-ready data set feature allows the export of data into a single data set, easing performance of advanced analysis with Optimize. The data set generated can contain process information and be assembled by generating a raw data report. The data contained in the raw data reports is already organized and pre-processed in such a way that it would allow a trained model to make predictions for future instances based on existing instances for a given definition. In addition to the previously existing columns in the raw data reports, we added columns for improved machine learning capabilities. These columns allow a user to access information such as the total number of [incidents](/components/concepts/incidents.md) per process instance, the number of open incidents, the number of user tasks, and the total duration of an event. For example, this allows you to predict how long an instance will take to complete based on the number of incidents or user tasks. After navigating to a raw data report, note the added columns are now displayed: ![Raw Data Report](../process-analysis/img/raw-data-report-ml-ready-dataset.png) In most cases, when training a machine learning model the data can be fed to common libraries, such as pandas or scikit-learn in CSV or JSON format. To export all data contained in a raw data report and use it as input for model training, export the raw data reports in JSON format. This can be done after saving the report and utilizing the external Optimize endpoint provided to export it to JSON. More information on how to use the JSON export endpoint can be found [here](/apis-tools/optimize-api/report/get-data-export.md). --- ## Process variants comparison When creating reports, it is possible to copy the data source and set different versions and/or tenants for each copy, as well as give it a custom title. This allows you to compare different variants of the process. To use this feature, navigate to the definition edit window from inside a report. Click the copy icon while hovering over the data source. This will create a copy of the data source with all its parameters. Now click the pencil icon while hovering over the data source you want to edit and select a different version and/or tenant. This way there are two variants of the same process which can be now compared. ![Process version selection](./img/process-version-selection.png) Here is an example of a bar chart report comparing the average process instance duration of two versions of the same process. ![Report with two versions of the same process](./img/report-with-process-variants.png) --- ## Variable labeling(Additional-features) The variable labeling functionality allows users to add, update, and delete batches of variable labels so your data is more understandable by business users. This allows Optimize to display a variable's label instead of its original name anywhere the given process definition is being used. Some examples of that would be when viewing and configuring reports, dashboards, or event-based processes. To use this feature, navigate to the definition edit window from inside a report. Click the pencil icon while hovering over the data source and click **Rename Variables** to access the label edit panel. You will then see the following panel: ![Label Edit panel](./img/variable-labeling-panel.png) Delete a label by inputting an empty field for its value. ## Limitations :::note This feature is currently not supported in task analysis and csv export. This means that during task analysis, the original name of a variable is displayed. ::: Keep in mind that when applying variable filters in multi-definition reports and multi-definition dashboards, the filters are applied to all variables across definitions which have the same name and type. This happens even in the case that the variables are labeled differently across definitions. --- ## Agentic control plane Monitor AI agent adoption, token costs, reliability, and performance across your processes with the agentic control plane in Optimize. ## About With the **agentic control plane** in Optimize, you can monitor [AI agents](/reference/glossary.md#ai-agent) adoption, token usage, reliability, and performance across your processes in one place. This is primarily intended for operators, process owners, and engineering leads who manage AI-agent-powered processes and need to keep them reliable and cost-effective. If you need customized metrics beyond those available in this feature, you can also [build your own AI agent reports and dashboards](/components/agentic-orchestration/evaluate-agents/analyze-ai-agents.md) in Optimize. ## Open the agentic control plane 1. Open **Optimize**. 2. In the navigation, select **Agentic Control Plane**. 3. The **Agentic Control Plane** page loads, showing metrics for all your agentic processes from the **last 30 days**. :::note SaaS trial clusters Optimize is disabled by default on new trial clusters. If the **Agentic Control Plane** tab doesn't appear in the navigation, an admin needs to enable Optimize first using the **Enable Optimize** prompt on the cluster overview. Upgrading from a trial to a paid plan enables Optimize automatically. ::: ## Overview and process views The **Agentic Control Plane** page has two views, controlled by the **Process** filter at the top: - **Overview (default):** No process is selected, so you see every agentic process together. It covers every agentic process in the current Optimize instance (current cluster), not an organization-wide view across multiple clusters. - **Process view:** Select one process (and optionally a **Version**). Every metric now describes just that process, including extra process-specific metrics. To switch views, choose a process in the filter, or clear it to return to the overview. :::note A process instance counts as agentic and is included in the metrics if it contains at least one execution of a [Camunda AI agent](/reference/glossary.md#camunda-ai-agent), for example through the [AI Agent connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md). Processes that call only [external agents](/reference/glossary.md#external-agent) aren't tracked because the Camunda engine has no visibility into their execution. ::: ### Filters Use these filters to narrow the metrics down to the time window, process, or version you want to inspect: - **Date range**: **Last 7 days**, **Last 30 days** (default), **Last 3 months**, **Last 6 months**, or **Last 12 months**. Dates are based on when each execution **completed**. Chart time buckets adjust automatically (for example, daily for a week, monthly for a year). - **Process**: switches between the overview and the process view. - **Version**: appears only in the process view. Focus on a specific process version, the latest, or all versions. ### Metrics available in each view | Metric | Overview | Process view | | ------------------------------------ | :------: | :----------: | | Total executions | ✅ | ✅ | | Average execution duration | ✅ | ✅ | | Incident rate | ✅ | ✅ | | Average tokens per execution | ✅ | ✅ | | Median tokens per execution | ✅ | ✅ | | Token trend | ✅ | ✅ | | Token outlier bands (P5 / P50 / P95) | ✅ | ✅ | | Top token consumers by process | ✅ | — | | Total tool calls | ✅ | ✅ | | Failure rate by process version | — | ✅ | | Tool calls per flow node (heatmap) | — | ✅ | | P50 execution duration | ✅ | ✅ | | P95 execution duration | ✅ | ✅ | | Execution duration stability | ✅ | ✅ | | Duration per flow node (heatmap) | — | ✅ | ## Understand the metrics The available metrics are grouped into four themes: key numbers, token usage, reliability and tool calls, and duration. Each one is described below, along with what it tells you and how to act on it. :::info Metric data scope The metrics only reflect completed process instances that used at least one [Camunda AI agent](/reference/glossary.md#camunda-ai-agent). This excludes both normal in-flight runs and agents stuck mid-execution, such as an agent hanging on a tool call. As a result, a hung agent won’t appear in **Incident rate** until its instance completes. If a metric looks empty, there's usually no agentic data yet for the selected process or period. ::: ### Key numbers The top row gives you an at-a-glance health check, and each number carries a change badge comparing the current period to the previous one. - **Total executions** counts the completed agentic executions in the period. It's your adoption and volume signal: a steady climb means agents are being used more, while an unexpected drop can mean a process has stalled. It also sets the scale for reading every other metric. - **Average execution duration** shows the average end-to-end time of an agentic execution. If it trends upward, the experience is getting slower, and it's worth digging into the duration metrics or a specific process to find out why. - **Incident rate** is the share of completed executions that hit and resolved an [incident](/components/concepts/incidents.md), a quick reliability pulse. When it starts climbing, that's your cue to investigate. In the process view, **Failure rate by process version** helps you narrow down the cause. ### Token usage Tokens are the units of AI model usage, so this group is effectively about cost. - **Average tokens per execution** captures the typical cost of a single run as the combined input and output tokens. When it grows while your execution volume stays flat, prompts or responses are expanding, which is a good moment to review your prompt design. - **Median tokens per execution** is the midpoint: half of executions use less, half use more. Because it ignores extreme outliers, it represents the typical run. Compare it against the average: a large gap means a handful of very expensive runs are inflating your costs, so it's worth hunting down those outliers. - **Token trend** plots input tokens against output tokens over time. It reveals where cost growth comes from: bigger prompts push up the input line, longer responses push up the output line. If tokens rise without more executions, your prompts may be growing, or the model may be producing longer answers, which is worth raising with engineering. - **Token outlier bands (P5 / P50 / P95)** show the spread of per-execution token usage over time, from low to typical to high. A widening gap between the P5 and P95 bands means executions are behaving inconsistently, often a sign of prompt variability or non-determinism. - **Top token consumers by process** ranks your processes by total token spend, showing the top 10 as a bar chart. This is where you see where the money goes across all processes: cutting the top consumer has the biggest impact on overall spend, and you can select that process to drill into it in the process view. A **Top X of Y** note appears when more processes exist beyond those shown. ### Reliability and tool calls - **Total tool calls** is the total number of tool or action calls your agents made, a gauge of how much work they delegate to tools. Sudden changes can point to a behavior shift or a looping agent, so it's useful to read alongside token usage and duration. - **Failure rate by process version** breaks the failure percentage down by process definition version. It answers whether a specific version is more or less reliable, so you can confirm whether a new release improved or regressed things and decide whether to roll it back or promote it. - **Tool calls per flow node** overlays a heatmap on the process diagram, coloring each step by how many tool calls it triggers. It pinpoints where in the process your agents do the most work, so you can target the hottest steps for review, such as a tool an agent calls far more often than expected. ### Duration - **P50 execution duration** is the median execution time: half of runs finish faster, half slower. It gives you the typical performance experience and a stable baseline to compare against P95 and over time. - **P95 execution duration** exposes the slow tail: 95% of executions finish within this time. It surfaces worst-case slowness that averages tend to hide. When P95 sits far above P50, a meaningful minority of runs are slow, and those are the cases worth investigating. - **Execution duration stability (P50 / P95)** plots both the median and 95th-percentile duration over time on a single chart. It shows whether performance is steady or drifting, helping you catch gradual degradation, or a widening gap between typical and worst-case runs, before users start to complain. - **Duration per flow node** overlays a heatmap on the process diagram, coloring each step by its average duration. It shows which steps consume the most time so you can focus performance work on the true bottlenecks. Like the other heatmap, it's only available in the process view. ## Find the right metrics for your goal With these metrics in mind, you can complete a few common tasks: - **Watch adoption:** keep the overview on **Last 30 days** and track **Total executions**. - **Control cost:** check **Average tokens per execution**, **Median tokens per execution**, and **Token trend**. If they're rising, open **Top token consumers by process**, select the top process, and investigate it in the process view. - **Investigate reliability:** when **Incident rate** climbs, switch to the affected process and use **Failure rate by process version** to see whether a release caused it. - **Find slow or busy steps:** in the process view, use **Duration per flow node** to pinpoint bottlenecks and **Tool calls per flow node** to see where agents do the most work. --- ## Collections, dashboards, and reports Optimize uses a single visualization for **reports**, and **[dashboards](./creating-dashboards.md)** combine these visualizations, akin to a spreadsheet or a set of comparative charts. **Collections** organize these datasets, acting as project folders. The Optimize **Collections** page displays all dashboards, reports, and collections, and clicking on a collection reveals associated reports and dashboards. ![home page](./img/home.png) In addition to the name of the dashboard, report, or collection, you can also see modification dates, the number of items they contain, and user access. Optimize enables collaborative sharing through the **Share** tab, allowing the creation of shared links for external viewing. Toggle to **Enable sharing**. :::note Colleagues without access to Optimize can still view your report with the shared link. Learn more about [user permissions](./user-permissions.md). ::: Clicking on a report, dashboard, or collection takes you to its corresponding details page. When moving the mouse over one of these entities, you can access a context menu that allows you to edit, copy, or delete the entity. Multiple entities can be selected and deleted at once using the bulk menu which appears after selecting at least one entity. When copying an entity, you also have the option to move that copy into a collection. ![copy sales dashboard](./img/copy.png) To find a collection, report, or dashboard, use the search field on the top of the page to filter the list by the name of the entity. To [create a dashboard](./creating-dashboards.md) or [report](./creating-reports.md), use the **Create New** button available in the top right corner of the page. ## User permissions For details about roles and access levels in collections (managers, editors, and viewers), see [user permissions](./user-permissions.md). --- ## Create dashboards Efficiently monitor business performance by defining reports in Optimize based on relevant Key Performance Indicators (KPIs). Dashboards, comprising [**Edit mode**](./edit-mode.md) and [**View mode**](./view-mode.md), offer a comprehensive view of system productivity. To create a new dashboard, click the **Create New** button on the homepage or collection page and select the **New dashboard** option. This opens a dialog where you can set the dashboard name and select one of multiple dashboard templates. When not creating a blank dashboard, select a process definition. This process definition is used to create new reports for the dashboard. Creating a dashboard from a template also creates new reports which are saved as soon as the dashboard is saved. ![create new dashboard](./img/dashboardTemplate.png) :::note Click the **Share** tab to share a created dashboard. Toggle to **Enable sharing**, and copy or embed the provided link. Colleagues without access to Optimize can still view your report with the shared link. Learn more about [user permissions](./user-permissions.md). ::: --- ## Creating reports ## Overview Reports offer the ability to view your data from different angles, and thus capture all aspects that influence your processes, show new trends, or depict your current business state. Each report consists of the [edit mode](./process-analysis/report-analysis/edit-mode.md) and [view mode](./process-analysis/report-analysis/view-mode.md) to perform different kinds of actions on it. ## Creating a single report To create a custom report based on a key performance indicator (KPI) you’d like to analyze, and to incorporate this report into a dashboard, follow the steps below: 1. On the right side of the **Collections** page, select **Create New > Report**. Here we’ll take a look at a single process, though you can also view data from multiple processes. 2. Click the text box under **Select Process** and select the process you’d like to analyze. 3. Select the type of report you’d like to use on the right side of the **Create new Report** box. As with dashboards, Optimize offers preconfigured templates such as heatmaps and tables. We’ll begin with a heatmap. 4. Click **Create Report**. ![heatmap example](./img/report-reportEditActions.png) 5. Set up and customize your report. Begin by naming your report in the text box at the top of the page. 6. In the gray text box to the right, confirm your data source, and select what you’d like to review from the process. You can also group by topics such as duration or start date. 7. If you’d like, filter the process instance or flow nodes. For example, you can filter by duration, only viewing process instances running for more than seven days. 8. Additionally, a **Report** can be given a description which is displayed below the **Report** name. Using the **Add/Edit** button, you can add, edit, or remove the description. You are limited to a 400-character plain text description. ![Add description modal](./img/report-descriptionModal.png) 9. You have the option to view particular sets of data from the instance (like instance count or absolute value) by selecting the gear icon to the left of your data customization. You can also choose to visualize your data in the box beneath **Visualization** (i.e. bar chart, pie chart, etc.). Once you’ve made your selections, click **Save**. :::note Click the **Share** tab to share a created report. Toggle to **Enable sharing**, and copy or embed the provided link. Colleagues without access to Optimize can still view your report with the shared link. Learn more about [user permissions](./user-permissions.md). ::: --- ## Data sources If you create a collection, you can add data sources that can be used to create reports. To see the existing data sources or add additional ones, go to the **Data Sources** tab of the collection. ![add source by definition](./img/sources.png) Using the **Add** button, a manager can add one or more sources to the collection by selecting the definitions that need to be added. ![add source by tenant](./img/sourceByTenant.png) The added sources will appear in the process/decision selection list inside the report builder where they can be used to create reports. --- ## Configure dashboards Configure and customize your dashboards in **Edit mode**. It allows you to perform operations such as renaming, modifying or removing descriptions; adding, editing, copying or removing tiles; saving or canceling applied changes; setting dashboard filters; and defining a default auto-refresh rate for periodic updates in [View mode](./view-mode.md). ![edit mode](./img/dashboard-dashboardEditActions.png) ## Adding tiles Assemble reports into a dashboard by clicking "Add a Tile" above the dashboard grid. In the modal, select Optimize reports or external website and text tiles, combining data from various sources as external website tiles and text tiles. ### Optimize reports In the **Add a Tile** modal, use the **Optimize Report** tab to add reports to the dashboard. Use the **Select a Report** field to select one of the previously created reports or click **+ New Report from a template** to create a new report. ![add a report modal](./img/dashboard-addAReportModal.png) :::note If the dashboard is inside a collection, only reports that are in the same collection can be added. If the dashboard is not in a collection, it can only contain reports that are also not in a collection. ::: ### External websites In the **Add a Tile** modal, select **External Website** to input the URL of an external data source to be included in the dashboard. External websites are incorporated as iframes within the dashboard. ![external website editor](./img/dashboard-addAReportModal-externalReport.png) ### Text tiles In the **Add a Tile** modal, choose **Text** to access the text editor. This enables the creation of documents with formatted text, links, and images. ![text editor](./img/dashboard-addAReportModal-textReport.png) ## Editing, copying, and removing tiles Each tile features a set of buttons in its top-right corner, providing you with options to edit and remove the tile. The copy button allows you to duplicate any tile, and the copied version can be conveniently placed anywhere on the dashboard. This duplicated tile is then customizable, enabling you to tailor it to your specific requirements. ## Placing tile on the dashboard Move tiles within the dashboard by dragging them to your preferred location. Upon releasing the tile, it aligns with the nearest grid position. Resize tiles by dragging the handle in the lower right corner. Remove a tile by clicking the button in the top right corner. ![edit actions](./img/dashboard-reportEditActions.png) ## Adding filters in edit mode In dashboard edit mode, click **Add a Filter** to reveal the **Filters** panel. Specify filters for the dashboard, including start and end dates, process instance states (running, completed, or canceled), variable values, assignees, and candidate groups: - Start date: Allows filtering by process instance start date - End date: Allows filtering by process instance end date - Instance state: Allows filtering by process instance state, such as running, completed, or canceled - Variable: Allows filtering by process variable value - Assignee: Allows filtering flow node data by their assignee - Candidate Group: Allows filtering flow node data by their candidate group ![filter edits](./img/filter-editMode.png) For Variable Filters, define the variable and provide a list of values for filtering. Optionally, allow viewers to add their own filter values by checking the **Allow viewer to add filter values** box. Unlike report filters, adding a value here doesn't immediately apply; it only makes the value available for dashboard filtering. For Assignee and Candidate Group filters, specify available options. Similarly, allow viewers to add their values. The list of variable names, values, assignees, and candidate groups is compiled from all reports on the dashboard. ### Setting a default dashboard filter After specifying filters in edit mode, dashboard editors can set a default filter, applied when a user first opens the dashboard. Viewers can remove filter values to see unfiltered reports, but if no manual changes are made, they will view reports with the defined default filter. To set a default filter, use the added filter options in the filter area. The configuration set during dashboard save becomes the default filter. ## Adding or editing description Under the **Dashboard** name, view or modify the description using the **Add/Edit** button. The text editor allows the addition of plain text descriptions, limited to 400 characters. ![Add description modal](./img/dashboad-descriptionModal.png) --- ## Instant process dashboards Optimize serves as a comprehensive process optimization tool, streamlining operations and enhancing efficiency for businesses. Notably, one of its standout features is the automatic generation of dashboards for each process, offering users clear insights into process performance. On first access, Optimize creates an intuitive and user-friendly dashboard on-demand. Users can access these dashboards from the [process dashboards page](./process-dashboards.md) by selecting the desired process. The data presented in the dashboard, along with its corresponding reports, aligns with the user's permissions for that specific process definition. This implies that the dashboard will include data from all authorized tenants. Additionally, both dashboards and reports showcase data from all versions of the relevant process definition. For convenient integration, the dashboard has a consistent URL, allowing embedding into other tools and web pages. The URL format is _https://<OPTIMIZE_URL>/dashboard/instant/<BPMN-PROCESS-ID>/_. Importantly, this URL remains stable across Optimize versions, eliminating the need for updates. :::note Instant process dashboards cannot be shared conventionally. To share, provide the URL, and the recipient will need to sign in to Optimize to access the dashboard. ::: ### Copying the instant process dashboard While the instant preview dashboard offers quick insights, you might want to customize it further to suit your application needs, such as adjusting dashboard filters or configuring reports. To create a copy for customization, use the **Create editable copy** button in the top right corner. This button opens a creation modal with preselected options for an exact copy, but you can also modify the settings to create a different dashboard, choosing from various templates. Additional information on creating a new dashboard can be found [here](./creating-dashboards.md). --- ## Process KPIs Key performance indicators (KPIs) are reports represented by single numerical values with predefined targets. To create a custom KPI, follow the steps below: 1. On the right side of the **Collections** page, select **Create New > Report > Process KPI**. 2. Select one of the predefined KPI templates and the process you’d like to analyze. ![KPI creation step 1](./img/process-kpi-step1.png) 3. Set the target value that you would like to achieve 4. Define the required filters. ![Set creation step 2](./img/process-kpi-step2.png) 5. Click **Create process KPI**. 6. (Optional) Edit the created KPI report and define your own filters/extra configuration. ## Setting existing process reports as KPIs Existing process reports can be configured to act as a KPI. This can be done through the report configuration panel. The checkbox to make the report a KPI is only visible for reports with a number visualization. ![Set KPIs](./img/kpiConfiguration.png) Once a report is set as a KPI, its status is visible on the **Dashboards** page. ![Processes page](./img/processOverview.png) --- ## Branch analysis ## Overview If a process contains more than one end event, it is useful to know which path tokens took to reach a specific end event. Optimize provides you with a statistical analysis for a given end event and a gateway. This analysis includes how tokens were split at the gateway in question, and how many of the tokens of each branch reached the end event. ![branch analysis](./img/analysis-1.png) ## Branch analysis in Optimize Select a process definition using the **Select Process** option in the top left of the page. After selecting a process definition and version, the diagram of the process is displayed on the page. By default, all process instances for the selected process definition are included in the analysis. You can reduce this set of process instances by applying filters. To perform a statistical analysis on the selected process, specify a gateway and an end event. Moving your mouse cursor over the end event and gateway inputs at the top of the screen highlights available elements in the diagram. Likewise, mouse over an element to see whether it is an end event or gateway. Additionally, if you move your mouse over an end event, you see detailed information about this end event, like how many instances reached this end event. Click on an element to select or deselect it. You can also clear the selection using the **x** button in the control panel on top. Changing the process definition also clears the selection. After selecting an end event and gateway, a statistical analysis is shown next to the diagram. The analysis consists of two bar charts and a statistical summary. Both charts contain a bar for every sequence flow leaving the selected gateway. ![branch analysis second example](./img/analysis-2.png) The first chart shows the distribution of process instances over the various sequence flows, showing how often each sequence flow has been executed, independently of whether the process instance then reached the selected end event. The second chart displays the relative amount of process instances that reached the selected end event after taking the respective outgoing sequence flow. Process instances which have taken more than one path (e.g. by looping back to a flow node before the gateway and then taking a different sequence flow) are not considered in the statistical analysis. --- ## Overview(Process-analysis) Locating flaws in your process models can be a huge challenge when you have millions of process instances to sift through. Define filters in Optimize to narrow down your view to only a subset of process instances. Camunda Optimize offers various ways of filtering your data, such as filter by: - [Metadata](./metadata-filters.md) (date, duration, assignee, etc.) - [Instance state](./instance-state-filters.md) (running or canceled instances) - [Flow node](./flow-node-filters.md) (flow node date, flow node duration, etc.) - [Process instance](./process-instance-filters.md) (process instance date, process instance duration) - [Variables](./variable-filters.md) (boolean, string, etc.) ## Filter behavior There are two ways to filter data in Optimize: 1. Instance filters: All filters can be used to filter instances in single reports and during branch analysis. 2. Flow node data filters: These filters can be used if you not only want to filter instances, but you additionally need to filter the content of instances (for example, flow nodes). Since not all filters can be applied on flow nodes, only compatible ones can be used as a flow node data filter. Flow node filters also exclude all instances from the result which do not contain at least one flow node that matches the filter. To summarize, instance filters remove rows, while flow node data filters remove columns. Additionally, if the report contains multiple processes, filters need to specify which definition they apply to. Some filters can apply to multiple definitions at once, while other filters are specific to a certain process definition. For example, because they rely on the flow nodes present in the definition. --- ## Flow node filters ## Flow node filter Retrieve only those process instances that executed certain flow nodes within your process by using the `Flow Node Filter`. Selecting several values at once means that all the selected flow nodes need to have been executed at least once in the process instance lifetime. At the top of the flow node filter modal you can see a preview of the filter you are about to create. You can also filter process instances where certain flow nodes were not executed. ![Flow node filter in Camunda Optimize](./img/flownode-filter.png) ## Flow node selection In flow node and user tasks reports, all flow nodes are included in the result by default. This could result in many table rows or chart entries which makes the visualization hard to read. This filter allows you to specify which flow nodes are included and deselect the ones that are not relevant to the report. ![Specifying which nodes are included in the report](./img/flowNodeSelection.png) ## Flow node status filter Some flow nodes can take a relatively long time to complete (e.g. user tasks or long-running service tasks). By default, a report includes all flow nodes in the calculations, whether they are currently running, canceled, or already completed. You can change this behavior by adding a flow node status filter as a [flow node data filter](./filters.md#filter-behavior). Adding one of the flow node status options will filter both instances and flow nodes according to the selected status: - For instance reports: The filter will only include instances that have at least one flow node matching the filter criteria. This behavior can be seen if you are in variable, incident, or raw data reports. - For flow node reports: Flow nodes that do not match the filter criteria will be excluded from the results. This behavior can be seen if you are in flow nodes or user task reports. Here are the possible options for this filter: - Running flow nodes only: Your report will only collect information from flow nodes that are currently running. - Completed flow nodes only: Considers only successfully completed flow nodes. - Canceled flow nodes only: Considers only canceled flow nodes. - Completed or canceled flow nodes only: Considers all completed flow nodes regardless of whether they were canceled or not. :::note For incident reports, flow node status filters always behave as instance filters and do not filter flow nodes. ::: ## Flow node date filter Similar to process instance date filters, flow node date filters allow you to filter the report based on flow node start or end dates. :::note Reports with a flow node end date filter will only consider data from completed flow nodes. ::: This filter type can be applied either as a [process instance](./filters.md#filter-behavior) or as a [flow node](./filters.md#filter-behavior) filter: - When applied as a process instance filter, you are required to select the flow nodes that are to be relevant to the filter, yielding a report which will only consider those process instances where one or more of the selected flow nodes match the configured filter. ![Flow Node date filter](./img/flowNode-date-filter.png) - When added as a flow node filter, there is no flow node selection. The resulting report automatically only includes data from those flow nodes which match the given filter. ## Flow node duration filter If the **Flow Node Duration Filter** is applied as an instance filter, it will only regard process instances where one or more flow nodes took a certain amount of time for their execution. For instance, you can filter process instances where a flow node took more than three days or less than five seconds. If applied as a flow node filter, it will filter flow nodes and only show the flow nodes that were selected in the filter. ![Flow Node duration filter in Camunda Optimize](./img/flowNode-duration-filter.png) :::note For incident reports, flow node duration filters always behave as instance filters regardless of where they were defined. ::: --- ## Instance state filters ## Running completed instances only filter By default, a report considers all process instances, regardless of whether they are still running. This behavior can be adjusted with the **Running Instances Only** and **Completed Instances Only** filters. Be aware that setting one of those filters (e.g. **Running Instances Only**) while the other one is already set (e.g. **Completed Instances Only**), will show a warning message since these two filters are incompatible with each other and will not show any data. ## Canceled instances only filter If the **Canceled Instances Only Filter** is applied, the report will only consider those instances which were terminated before completion, either internally or externally. Be aware that adding this filter along with the **Running Instances Only** will show a warning message since these filters are incompatible and will not show any data. ## Non canceled instances only filter As opposed to the **Canceled Instances Only Filter**, applying the **Non Canceled Instances Only** filter will make Optimize query only those instances which were _not_ canceled during their execution. This means only active and completed instances are considered. Externally or internally terminated instances are not included in the report. ## Suspended and non suspended instances only filter By default, a report considers all process instances, regardless of whether they are suspended or not. Adding this filter makes it possible to only evaluate process instances that are in the suspension state. Note that if you have enabled history cleanup, this might affect the accuracy of this filer given the suspension state is imported from historic data. --- ## Metadata filters ## Date filters In Optimize, there are two kinds of date filters: the start and the end date filter. Each of these filters can be applied on [process instance](./process-instance-filters.md#process-instance-date-filter) and on [flow node](./flow-node-filters.md#flow-node-date-filter) dates. There are multiple ways in which you can define your date filters: - Set the filter to a current amount of time. For example, today, this week, this month, etc. In such cases, the filter does not remain static, but moves with time to deliver a subset of the data according to the selected time interval. :::note Within date filters, weeks begin on Monday, not Sunday. This is not configurable in Optimize. ::: - Set it to a previous amount of time. For example, yesterday, last week, last month, etc. This filter also moves with time and is automatically adjusted to cover completed periods of time. Take the following example: Today is Wednesday, March 11. If you set a process instance start date filter to `Last... + week`, you get all process instances that were started from Monday, March 2 to Sunday, March 8. A week passes, and we now have Wednesday, March 18. Applying the same filter now filters the process instances which were started from Monday, March 9 to Sunday, March 15. - To cover previous time periods up the current moment of time, you can use the 'Rolling' option. Take the following example: today is March 28. If you set a process instance start date filter to the last three days, you get all process instances that were started from March 26 to March 28. A day passes, and we now have March 29. Applying the same filter now filters the process instances which were started from March 27 to March 29. - If you do not want the filter to be completely dynamic, you can also select `Between`, `Before`, or `After`. - The `Between` option only considers process instances started or ended within a fixed date range (e.g. filter all process instances between 2018-01-01 and 2018-01-26). This range is fixed and does not change. - In the same way, you can select `After` or `Before` options to only consider process instances that started or ended after/before a fixed date. The start and the end date filters are independent and can be applied to a report simultaneously. However, be aware that each of these filters can only exist once. If, for example, you define a new start date filter when another one already exists, the second one will replace the first one. ## Assignee and candidate group filters These filters allow you to include or exclude instances based on the assignee or the candidate group of at least one user task of a particular process instance. ![Assignee/Candidate group filter modal](./img/assignee-filter.png) As shown in the example, it is possible to select one or more assignees or even filter for unassigned instances. This filter has different behavior depending on where it was [defined](./filters.md#filter-behavior): - As a `Flow Node data filter` applied on a user task report: This filter only includes user task instances that satisfy _all_ assignee/candidateGroup filters defined in the report at once. Mutually exclusive filters like having both an inclusive and an exclusive filter on the same assignee do not yield any results in user task reports. - As an `instance filter`: This filter includes all process instances where _at least one_ user task satisfies one particular assignee/candidateGroup criterion. This means multiple mutually exclusive assignee/candidateGroup filter entries might still yield results for these reports (e.g. if the process definition contains multiple user tasks). ## Incident filter This filter has a different behavior depending on where it was [defined](./filters.md#filter-behavior): - As an `instance filter`: This filter will retrieve only those process instances that contain open, resolved, or no incidents (depending on your selection). Here are some examples where this filter can be useful: - Creating reports that contain no incidents since the instances that have incidents have very long durations and are influencing your data. - To monitor all the instances from multiple engines that have open incidents. On the other hand, this filter is not useful for counting the number of incidents because instances with an open or resolved instance filter might still contain instances from the other type. - As a `Flow Node data filter`: This filter will additionally filter the instance incident states to only include incidents of the same type (open or resolved). As an example, This filter can be used to count the number of open or resolved incidents since it considers the incidents of that type exclusively. This filter is currently only useful if you are in an incident view report. :::note The incident filter does not currently filter flow nodes regardless of where it was defined. ::: ## Combined filters All the previously mentioned filters can be combined. Only those process instances which match all the configured filters are considered in the report or analysis. The [duration filter](./process-instance-filters.md#process-instance-duration-filter), [flow node filter](./flow-node-filters.md), and [variable filter](./variable-filters.md) can be defined several times. See the following screenshot for a possible combination of filters: ![Combined filter in Camunda Optimize](./img/combined-filter.png) Everyone who has access to the report can add their own filters. For example, by creating a dashboard that contains that report and using dashboard filters. Note that filters can apply to all processes or a subset of processes. Filters added in such a way are always combined with the filters set in the report edit mode. That way, users can reduce the set of process instances that are considered when evaluating the report, but not increase the number of instances evaluated above the set the report author specified. In essence, if two copies of the same process are present, Optimize combines them with OR logic, and their filters or variables can be combined with the same logic. Therefore, it's possible to compare two differently filtered slices of the same process on the same report (with the group by process feature) or combine them (without group by process). Users can get access to a report via the sharing functionality or if the report is in a shared collection. --- ## Overview(7) The following documentation provides an opportunity to further analyze your reports through several methods: - [Task analysis](./task-analysis.md): Task analysis allows you to easily identify process instances where certain flow node instances took significantly longer than others and subsequently slow down your process. - [Branch analysis](./branch-analysis.md): If a process contains more than one end event, it is useful to know which path tokens took to reach a specific end event. Optimize provides you with a statistical analysis for a given end event and a gateway. --- ## Process instance filters ## Process instance date filter Applying a process instance start or end date filter will result in a report considering only process instances that started or ended within the defined range of dates. :::note Reports with a process instance end date filter applied will only consider completed process instances. ::: As an alternative way to create a process instance start date filter, you can directly select the desired filter interval in the chart itself if your report is visualized as bar or line chart. ## Process instance duration filter The **Process Instance Duration Filter** allows you to only regard process instances whose execution from start to end took a certain amount of time. For instance, you can filter process instances that took more than three days or less than five seconds. :::note This filter shows only completed process instances, since the total duration of running process instances is not yet known. ::: ![Process instance duration filter in Camunda Optimize](./img/duration-filter.png) --- ## Compare target values Based on flow node duration heatmaps, Optimize allows you to specify a target value for every task. For example, if a user task has to be completed within one day, you can set the target value to one day. If the time it takes to complete the user task exceeds this target value, the task is highlighted in the heatmap. To set target values and create a target value comparison heatmap, you need to be in the edit mode of a report which has the following configuration: | View | Flow node duration/user task duration | | ------------ | ------------------------------------- | | Group by | Flow nodes/user tasks | | Visualize as | Heatmap | If your report has this configuration, a target value button is visible. Clicking on the **Target Value** button for the first time opens an overlay containing the process diagram and a table with all flow nodes. You can also see the actual duration value for every flow node. To set a target value for a flow node, use the number and unit fields in the last column. If the target value number field for a flow node is empty, this flow node has no target value set (the selected time unit is ignored in that case). ![Setting Target Values](./img/targetvalue-2.png) If you set a target value for a flow node, this target value is represented as a badge on the flow node in the diagram in the upper part of the overlay. You can click on any flow node in the diagram to jump to the target value input field in the table. If you have a user task report, you can only select user tasks here, as only those are included in the report result. When selecting a target value input field in the table, the corresponding diagram element is highlighted. To save the target value configuration, click **Apply**. After you save the target values, the normal duration heatmap is replaced with a target value visualization. In this new visualization, flow nodes with an average duration larger than the specified target value are highlighted in red. If you mouse over one of the nodes, the tooltip shows the following: - The target duration value - The actual duration - The relative percentage the actual value is of the target value - A button to download a list of process instance IDs that exceed the target value You can also see the target value as a badge on the diagram. ![Target Value Comparison](./img/targetvalue-1.png) After the initial target values for a report are set, you can use the target value button to toggle between the target value and the regular duration view mode. If you want to change target values, use the gear button to open the overlay again. As with any change to a report configuration, to persist target values and show them in the report view mode and on dashboards, you need to save the report using the **Save** button in the upper right corner. --- ## Configure reports The configuration panel groups all the settings that can be applied to a report in one place. To see the panel, click on the cog button available in the edit mode of any report. Every visualization has different settings that can be configured in the panel. When you save changes to the report configuration, they apply to the report view mode and any dashboard this report is displayed on. ## Number Number reports are any reports that are visualized as a single number (e.g. `Process Instance: Count` grouped by `None` or `Process Instance: Duration` Grouped by `None`). In number reports, the following configurations are possible: ## Number precision Number precision can be configured from the panel to limit the most significant units to be shown. For example, we have a report that calculates the total process instances duration. When the precision limit is not set, you will see all possible units, e.g.: `1y 5m 2wk 5d 3h 16min 3s 170ms`. In case you are only interested in certain units - e.g. months - you can omit all insignificant units by limiting the precision as shown in the figure below: ![Number report configurations](./img/NumberConfiguration.png) ## Number goal value (progress bar) Number reports appear as progress bar when the goal option is enabled from the panel as shown. The baseline and the target value of the progress bar can be also set using the panel. ![Progress Bar Visualization](./img/progressbar.png) You can toggle between the progress bar and the single number visualization using the same goal line switch. A red line indicator appears on the progress bar when its value exceeds the goal value. On the right side of the indicator, the bar turns into a darker color to clearly show the exceeded amount. ![Progress Bar Visualization](./img/progressbarExceeded.png) ## Table settings In table reports, the following configurations are possible: ## Show instance count Displays the total instance count on the right side of the visualization. If you save the report while this option is enabled, the number will also be shown on any dashboard this report is added to and when the report is shared. ## Hide, show, and reorder table columns The table settings allow you to hide specific columns using the configuration menu as shown in the figure below: ![raw data configuration](./img/rawdata.png) When working with raw data table reports, you can also re-order the table columns using drag-and-drop on the header of the respective column. ## Sorting by table column To sort a table by a specific column, click on the header of that column. Doing that will show a small caret icon in the header of the column indicating which column the table is currently sorted by and the direction of this sorting (ascending or descending) as shown: ![Sorting a table in Optimize](./img/sorting.png) Clicking again on the same column header will reverse the direction of sorting. Saving the reports will also preserve the applied sorting. The sorting currently works for all table reports except for: - Combined table reports - Reports grouped by integer type variables ## Absolute and relative values When configuring a count report, you have the opportunity to configure which columns are included in the table. You can hide or show the corresponding columns using the switches for absolute and relative value. ## Custom bucket size for date variables When evaluating a report which is grouped by a date variable and displayed as a table, Optimize offers you the option to select your preferred unit specifying the custom result bucket size from the report configuration menu. The available units are year, month, week, day, and automatic. The default unit is automatic, which will create evenly spaced result buckets based on the values of the date variable. This configuration option is also available for charts. ## Custom bucket size and baseline When evaluating a report which is grouped by duration or a number variable, Optimize offers you the option to specify your preferred result bucket size as well as a custom baseline in the report configuration menu. The bucket size determines the width of one bucket, and the baseline specifies the start of the first bucket. For example, say a report contains the variable values 0.3, 6, and 13, and you set a bucket size of 5. By default, Optimize would now return a bucket for the values 0.3 to 5.3, one for 5.3 to 10.3, and one for 10.3 to 15.3. You may prefer your bucket start and end points to be a round number, in which case you should set your baseline to 0. With a baseline of 0 and bucket size 5, the result buckets now span 0 to 5, 5 to 10, and 10 to 15. If these configuration fields are not set, by default Optimize will create evenly spaced result buckets with a range based on the minimum and maximum values of the number variable. This configuration option is also available for charts. ## Charts (line, bar, pie) In bar chart and line chart reports, it is possible to select the color of the graph, add names to the x-axis and y-axis, and edit many other settings as shown in the figure below: ![chart visualization configurations](./img/chartConfiguration.png) In charts, you can hide/show absolute and relative values that appear in the tooltips. ## Show instance count Displays the total instance count on the right side of the visualization. If you save the report while this option is enabled, the number will also be shown on any dashboard this report is added to and when the report is shared. ## Chart goal line Optimize allows you to set a goal line in bar chart and line chart visualizations. Using this feature, it is possible to highlight anything above or below a certain value. A good use case for such functionality is the following example: First, go to the edit mode of a report and choose the following configuration: | View | Count frequency of process instance | | ------------ | ------------------------------------- | | Group by | Start date of process instance: Month | | Visualize as | Bar chart | Let us say that the number of completed process instances should always be above six. A goal line can be used as follows: Set the target value input field to six and select the above button. If the number of process instances is below six, it will be highlighted in red as shown: ![Bar charts goal line](./img/targetValue.png) This feature can be also used with every other bar chart and line chart visualization. Here is another example where the target value is used with line chart visualization: ![Line chart goal line](./img/targetline.png) ## Custom bucket size for date variables When evaluating a report which is grouped by a date variable and displayed as a chart, Optimize offers you the option to select your preferred unit specifying the custom result bucket size in the report configuration menu. The available units are year, month, week, day, and automatic. The default unit is automatic, which will create evenly spaced result buckets based on the values of the date variable. This configuration option is also available for tables. ## Custom bucket size and baseline When evaluating a report which is grouped by duration or a number variable, Optimize offers you the option to specify your preferred result bucket size as well as a custom baseline in the report configuration menu. The bucket size determines the width of one bucket, and the baseline specifies the start of the first bucket. For example, say a report contains the variable values 0.3, 6, and 13 and you set a bucket size of 5. By default, Optimize would now return a bucket for the values 0.3 to 5.3, one for 5.3 to 10.3, and one for 10.3 to 15.3. You may prefer your bucket start and end points to be a round number, in which case you should set your baseline to 0. With a baseline of 0 and bucket size 5, the result buckets now span 0 to 5, 5 to 10, and 10 to 15. If these configuration fields are not set, Optimize will create evenly spaced result buckets with a range based on the minimum and maximum values of the number variable by default. This configuration option is also available for tables. ## Stacked bar chart When evaluating a report which has a second "Group by", Optimize offers you the option to stack the bar chart bars instead of displaying them near each other. Stacking bars would be useful when the focus of the chart is to compare the totals (e.g. flow node count, process instance count, etc.) and one part of the totals (e.g. flow node, variable value, etc.) This configuration option is also available for bar/line charts. ![Stacked bar chart report](./img/stackedBar.png) ## Switch bar chart orientation When evaluating a report, Optimize will automatically set the bar chart orientation according the nature of the data being displayed. You can also switch the orientation manually using the configuration option shown. ![Stacked bar chart report](./img/horizontalBar.png) ## Bar/line chart When evaluating a report which has both count and duration measures, Optimize offers you the option to display one of the measures as bars and the other measure as a line. This would help to differentiate between duration and count values displayed in the visualization. By default, the count measure is displayed as bars and the duration as a line. You can also switch between them by using the configuration option shown. ![Bar/Line chart report](./img/barLine.png) ## Heatmaps When enabling absolute or relative values switches, all tooltips for all flow nodes stay visible. This is also possible when you have defined target values. If you save the report in this state, the tooltips will also be shown on any dashboard this report is added to. ![Heatmap tooltips](./img/heatmap.png) As for charts and table reports, it is possible to display the total instance count on the right-hand side of the visualization. If you save the report while this option is enabled, the number will also be shown on any dashboard this report is added to and when the report is shared. --- ## Define reports In this section of the report builder, you are characterizing the output of the report. Basically, you are defining "I want to view ... grouped by ... visualized as ...". To understand better what "View" and "Group by" mean, you can use the analogy of a graph, where "View" is the y-axis and "Group by" is the x-axis. First, you need to select which part of the data you want to view. Optimize differentiates between the view (e.g. process instance or flow node) and the measure (e.g. count or duration): 1. Raw Data: View just a table with the actual data listed as rows. This can come in handy if you found interesting insights in certain process instances and need detailed information about those instances, or you are exploring a process definition with a limited number of instances. This report type also allows you to inspect raw [object variable values](/self-managed/components/optimize/configuration/object-variables.md). 2. Process instance - Count: View how many process instances were executed. - Duration: View how long the process instances took to complete. 3. Incident - Count: View how many incidents occurred on the process. - Resolution duration: View how long the incident took to get resolved. 4. Flow node - Count: View how often the flow nodes (e.g. tasks) have been executed. - Duration: View how long each flow node took to complete. 5. User task - Count: View how often each user task has been executed. - Duration: View how long each user task took to complete. 6. Variable: View an aggregation of values for a specific numeric variable of the process definition. It is possible to display both count and duration measures for a single view in the same report. Subsequently, you need to define how to group the data. Think of it as applying a metric to your input, where you break up the data by date, flow nodes, variable values, or other properties. For that, you have different options: - **None**: Do not split up the data. - **Flow nodes**: Cluster the data by flow nodes. - **User tasks**: Cluster the data by user tasks. - **Duration**: Cluster the data by duration. Depending on the selected view, this can be the duration of process instances, flow nodes, or user tasks. - **Start date**: Group instances together that were started during the same date period or time, e.g. hour, day or month. Depending on the selected view, this can be the start date of process instances, flow nodes, or user tasks. - **End date**: Group instances together that were finished during the same date period or time, e.g. hour, day or month. Depending on the selected view, this can be the start date of process instances, flow nodes, or user tasks. - **Running date of the process instance**: Group process instances together that were running during the same date period or time, e.g. hour, day, or month. - **Variable**: Process instances with the same value for the selected variable are grouped together. - **Assignee**: Only available for user task views. Tasks are grouped together according to their current assignee. - **Candidate group**: Only available for user task views. Tasks with the same candidate group are grouped together. - **Process**: Only available for process instance reports with multiple definitions. Data from the same process is grouped together. Finally, define how you want the data to be visualized. Examples are heatmap, table, bar, or line chart. Not all the above view, group by, and visualization options can be combined. For instance, if you choose `Flow Node: Count` as view, the data is automatically grouped by flow nodes as no other combination would be valid. All possible combinations can also be found in the following table: | View | Group by | Visualize as | | --------------------------------------------------- | --------------------------------------------------------------- | --------------------- | | Raw Data | None | Table | | Process instance: Count, Process instance: Duration | None | Number | | Process instance: Count | Start Date, End Date, Running Date, Variable, Duration, Process | Table, Chart | | Process instance: Duration | Start Date, End Date, Variable, Process | Table, Chart | | Incident: Count, Incident Duration | None | Number | | Incident: Count, Incident Duration | Flow Nodes | Table, Chart, Heatmap | | Flow Node: Count, Flow Node: Duration | Flow Nodes | Table, Chart, Heatmap | | Flow Node: Count | Start Date, End Date, Duration, Variable | Table, Chart | | Flow Node: Duration | Start Date, End Date, Variable | Table, Chart | | User Task: Count, User Task: Duration | User Tasks | Table, Chart, Heatmap | | User Task: Count, User Task: Duration | Start Date, End Date, Assignee, Candidate Group | Table, Chart | | User Task: Count | Duration | Table, Chart | | Variable | None | Number | :::note You might sometimes see a warning message indicating that the data is limited to a certain number of points. This happens because the available stored data, in this case is very large, and it is not possible to display all the data in the selected visualization. ::: ## Reports with a second "Group by" option Using the second "Group by" option, it is possible to apply another grouping to your data to display extra details such as dates, variable values, or assignees. This option will be shown below the first "Group by" option if the current report combination supports it. Here is an overview of the reports that supports a second "Group by": ## Flow node reports Flow node names can be applied as a second "Group by". If the report contains multiple process definitions, the data can also be grouped by process as a second "Group by". ## User task reports For information about Optimize user task analytics, refer to our [task analysis documentation](../task-analysis.md). ## Process instance reports Refer to the table below for the process instance count and duration reports that support a second "Group by": | View | Group by | Second group by | | -------------------------------- | ---------------------- | ----------------------------------------------------------------- | | Process Instance Count, Duration | Start Date, End Date | Variable, Process (only for multi-definition reports) | | Process Instance Count, Duration | Variable | Start Date, End Date, Process (only for multi-definition reports) | | Process Instance Count | Running Date, Duration | Process (only for multi-definition reports) | The diagram below shows a report grouped by `Start Date` and a boolean variable: ![Distributed process instance report](./img/distributedByVar.png) --- ## Overview(Report-analysis) Edit mode allows you to configure the report and adjust it to your needs. The following operations are possible within edit mode: - Rename your report - Add/edit/remove description - Build a report - Configure your report - Save the current state with your applied changes - Cancel changes you already applied to the report Building a report is the crux of the report edit mode. The building process itself is composed of several steps, which happen in the control panel. :::note In edit mode, you can toggle the automatic preview update of the report by toggling the **Update Preview Automatically** switch. You can also use the **Run** button to run the update manually. By default, the automatic preview update is **disabled**. The toggle setting is stored in the user session and will be applied as a default value next time. ::: In this section, learn how to: - [Select process definitions](./select-process-definitions.md) - [Define reports](./define-reports.md) - [Set durations and variable report aggregation](./measures.md) - [Compare target values](./compare-target-values.md) - [Select process instance parts](./process-instance-parts.md) - [Configure reports](./configure-reports.md) --- ## Measures ## Duration and variable report aggregation For duration and variable views, the default aggregation type is the average. You can add and change different aggregations like minimum, maximum, and median in the report configuration panel. Note that the median is an estimate and the other operations are exact values. ![Duration Aggregation Selection](./img/durationAggregation.png) Reports with multiple aggregations that have a [second "Group by"](./define-reports.md#reports-with-a-second-group-by-option) can only be visualized as table. ## User task duration time For information about Optimize user task analytics, refer to our [task analysis documentation](../task-analysis.md). --- ## Report process analysis ## Edit mode The [edit mode](./edit-mode.md) allows you to configure the report and adjust it to your needs. The following operations are possible within edit mode: - Rename your report - Add/edit/remove description - Build a report - Configure your report - Save the current state with your applied changes - Cancel changes you already applied to the report ## View mode Once you have defined what your report should look like, the [view mode](./view-mode.md) gives you a full view of the report visualization. To see more details about the report, you can interact with it, e.g. by moving your mouse over individual data points in diagrams or zooming in or out of heatmaps. The kind of interaction always depends on the report itself. --- ## Process instance parts In some cases, you may not be interested in the duration of the whole process instance, but only a certain part of it. For that scenario, there is an additional button called **Process Instance Part** available for every process instance duration view that only shows data for a single process definition. Clicking this button opens an overlay letting you select the start and end of the part of the process instance you are interested in. After confirming the selection, the displayed duration refers to the selected part only instead of the whole instance. In some cases it can happen that the same task is executed multiple times in the same process instance, e.g. if the process contains loops or parallel gateways. In such cases, Optimize considers only the part between the start date of the first instance of the start node and the end date of the first instance of the end node. ![Process Instance Part Modal](./img/process-part.png) --- ## Select process definitions Every report relates to one or multiple process definitions, versions, and tenants. You must choose at least one process definition you want to create a report for. To add a process definition to the report, click **Add** at the top of the **Data Source** section of the report control panel. This opens a dialog showing all process definitions you can use in the report. You can select up to 10 definitions to add to the report. If there are many process definitions, you can use the input field to search for the definition you are looking for. ![Process definition selection in the report builder in Camunda Optimize](./img/report-processDefinitionSelection.png) For every added process definition, you can set a display name and a specific version or version range. To do so, click the **Edit** button in the process definition card. There are also buttons to remove the definition from the report or add a copy of the definition. When editing a process definition, using the version dropdown, you can choose between all versions, the latest version, or a specific set of versions. ![Process Version selection in the report builder in Camunda Optimize](./img/report-versionSelection.png) - **All** option: Every process instance across all versions of the process definition will be considered in your report. - **Always display latest** option: Makes your report always refer to the latest version. Keep in mind that if a new version of the process is deployed, the report will automatically consider process instances of this new version only. - **Specific version** option: Specify one or multiple existing versions of the process. Data from older versions is mapped to the most recent version in the selection. Therefore, the report data can seem to be inconsistent, which is due to changes that occurred within the diagram through the different versions. For example, the old versions do not contain newly added tasks or a part of the diagram was removed because it was considered to be obsolete. ![Process definition selection for a multi-tenancy scenario](./img/tenantSelection.png) By default, all process instances for the selected process definitions are included in a report. You can reduce this set of process instances by applying a [filter](../../process-analysis/filters.md). --- ## View mode Once you have defined what your report should look like, the view mode gives you a view of the report visualization and the raw instance data. To see more details about the report, you can interact with it, e.g. by moving your mouse over individual data points in diagrams or zooming in or out of heatmaps. The kind of interaction always depends on the report itself. The view mode also provides you with different kinds of actions, such as: ![report sharing popover in Camunda Optimize](./img/report-sharingPopover.png) - Download CSV: In case you want to download the data of the report, you can click the **Download CSV** button. The downloaded file will include the report information in a table format. - Sharing: In case you want to share the report with other people or want to embed it in a webpage, you can use the sharing feature of the report. Just click on the **Share** button, which opens up a popover. After enabling the **Enable sharing** switch, a link is generated which you can send to people who do not have access to Camunda Optimize and thus enable them to see the report. You can also use the **Embed Link** button if you wish to insert the report into your webpage. Everyone that views the webpage can then see content of the report. The shared versions of the report allow you to view the report itself only. There is no possibility to alter it or interact with any other features of Optimize. You can revoke the sharing any time by disabling the share switch. If you prefer to hide the header of the shared report or specific part of it, you can do that by adding the following parameter to the share URL: ``` header : titleOnly / linkOnly / hidden ``` For example, to completely hide the header from the shared report, you can add `header=hidden` as shown: ``` http://?header=hidden ``` - Alerts: If the created report is inside a collection, you can use the **Alert** dropdown to create and manage Alerts for that report. Since alerts can only be created on reports that have a number visualization, the **Alerts** dropdown will be only be visible for such reports. - Description: If a Report has a description, the description is displayed below the Report name. The **More/Less** button can show or hide the text for descriptions longer than a single line. ![report description](./img/report-showMoreDescription.png) --- ## Task analysis Task analysis allows you to identify process instances that took significantly longer than others to complete a flow node, and subsequently slow down your process. ## Task analysis in action Select a process definition you would like to analyze. Once a definition is selected, a **heatmap** is displayed, highlighting the flow nodes where Optimize identified many duration outliers. In our example, the **Approve Invoice** task has duration outliers. Additionally, in the **Outliers** table, you can see how many instances were identified, how much longer they took than the average duration, and a list of related variables. ![task analysis example 1](./img/outlierExample_1_heatMap.png) Click the node on the **heatmap** or use the **View Details** button in the table to directly see a duration distribution chart for the specific flow node. The duration distribution chart contains information about how long the identified outliers took, and also in comparison to the other flow node instance durations. ![task analysis example 2](./img/outlierExample_2_detailsModal.png) ## Significant variable values When looking at the duration outlier instances, you can analyze the data further to find the root cause of why these instances took so long. Look at the significant variables table that lists significant variable values in the outlier instances. This also allows you to see how many times this variable value occurred in the outlier instances compared to the rest of the process instances. This can give you a good idea of if there is a correlation between a variable value and a flow node taking longer than expected. In our example, we can see that for most of our duration outliers the delay variable was set to `true`. --- ## User task analytics In the realm of automated processes, the efficiency and effectiveness of a system are largely determined by how well it supports the user in performing their tasks. Often, businesses find that, despite automation, there still exist significant bottlenecks affecting overall productivity. These bottlenecks are commonly rooted in user tasks. - **User tasks generating the highest effort:** In an automated environment, certain tasks still demand significant human intervention. These tasks are often complex, error-prone, and time-consuming, leading to a disproportionate allocation of resources. - **Optimizing user tasks as a driver for improvement projects:** As organizations continually seek to improve performance, the optimization of user tasks emerges as a primary driver for new improvement projects. Streamlining these tasks can lead to substantial gains in productivity and cost savings. - **User tasks as a source of operational frictions:** User tasks are frequently the primary source of operational frictions. These frictions can manifest as delays, decreased throughput, and increased error rates, all of which negatively impact the business's bottom line. ![user task heatmap example](./img/userTask_heatMap.png) Optimizing user tasks offers a comprehensive approach to mitigating the challenges associated with user tasks. By leveraging advanced analytical tools, businesses can gain insights into how time and resources are being spent on user tasks and identify opportunities for improvement. Optimize provides the following capabilities to do so: - **Analyze time spent on user tasks for assignment:** By measuring and analyzing the time users spend on specific tasks, organizations can identify patterns and outliers. This analysis allows for a better understanding of task complexity and the allocation of resources. - **Define targets for user tasks:** With concrete data on how long tasks should take, organizations can set realistic performance targets. These targets provide a benchmark for measuring improvements and can drive accountability and motivation among users. ## Use cases ### Analyze task duration on a heatmap The user task heatmap is designed to offer stakeholders a panoramic view of all user tasks within a given process. This visual tool synthesizes complex data into an understandable graphical representation, highlighting areas of high activity and potential bottlenecks within the process flow. Optimize allows different aggregations based on the lifecycle of a task. With filters, it is possible to restrict evaluated process instances to analyze specific cases. ![user task duration heatmap example](./img/userTask_duration_heatMap.png) ### Measure targets on a heatmap To maintain adherence to Service Level Agreements (SLAs), it is essential to establish clear and measurable targets for each user task within a process. These targets serve as performance benchmarks, guiding users and helping maintain the quality and timeliness of task completion. Optimize allows users to define target values for user tasks on the heatmap. ![user task target example](./img/userTask_target_heatMap.png) ### Evaluate task performance For a thorough understanding of task execution within your processes, it is imperative to examine how tasks are performed both by the nature of the user task itself and by the assignee responsible for completing the task. This detailed analysis enables organizations to assess individual and collective task performance, providing insights into workflow efficiency and productivity. Optimize allows breaking down user tasks by user task or assignee for the assigned and unassigned state of the task lifecycle. With filters, it is possible to evaluate only a subset of the process instances, for example, to evaluate the performance by country. ![user filtered report example](./img/userTask_report_filters.png) ## Features ![user features example 1](./img/userTask_features1.png) ![user features example 2](./img/userTask_features2.png) To evaluate user tasks, the following features are available in the report builder: - **View user task** - The `View: User task` option limits the flow nodes evaluated to user tasks only. - **Duration aggregations** - Durations can be aggregated by `Total`, `Assigned` and `Unassigned`. - **Grouping** - Data can be grouped by `User tasks` or `Assignee`. - **Filter** - Data can be filtered by `Assignee`. ## Good to know :::danger Known limitations - Currently, user task analytics can be used only with assigned or unassigned time. We are working on analyzing net-work time. - This will only work with Tasklist and custom task applications implementing the complete [Camunda Tasklist Lifecycle](/apis-tools/frontend-development/01-task-applications/01-introduction-to-task-applications.md). - User task analytics only work correctly if all user tasks in a process are of type `Camunda user task` (formerly Zeebe user task). The `job worker` user type does not contain task lifecycle information and is therefore not displayed in the `User tasks` view. ::: ### How to evaluate task performance per assignee Evaluating performance on assignee level is not allowed in all organizations. All features related to data evaluation on an assignee level can be deactivated via [configuration](/self-managed/components/optimize/configuration/system-configuration-platform-8.md#settings-related-to-camunda-8-zeebe-user-tasks). Evaluation on assignee level is only possible in Camunda 7 and Camunda 8 Self-Managed. ### How the user task duration time is calculated In user task duration reports, you have the opportunity to select which part of the user task's lifecycle you want to see in the report: - **Unassigned:** View how long each user task was considered unassigned (not claimed by an assignee/user) during its execution. - **Assigned:** View how long each user task was considered to be assigned to assignees/users (claimed by an assignee/user) during its execution. - **Total:** View how long each user task took to complete. It is possible to display and compare multiple user task duration times in the same report. Reports with multiple user task duration times that have a [second "Group by"](./report-analysis/define-reports.md#reports-with-a-second-group-by-option) can only be visualized as table. In certain circumstances, user tasks can be completed without being assigned to a user. These user tasks are evaluated as follows: - If the user task was canceled without assignment (for example, by an Operator in Operate), the task duration is considered `Unassigned`. - If the user tasks were completed without assignment (for example, via a custom UI), the time between start and end is considered `Assigned`. - As these user tasks do not have an `assignee` set, they are displayed `Unassigned` in the reports. --- ## Variable filters :::note Exporter-level data filters may exclude variables or process definitions from Optimize before they are imported. If a variable or process definition is missing from Optimize, check whether an export filter is active. On SaaS, configure filters in [cluster settings](/components/saas/clusters/settings.md#data-filters). On Self-Managed, see [Optimize export filtering](/self-managed/components/optimize/configuration/optimize-export-filtering.md). ::: Use the `Variable Filter` to retrieve only those process instances which hold the specified variable value for the selected variable. :::note Variable filters can only filter for the final value of the variable. For instance, assume you want to analyze only those process instances which have the variable `department` with the value `marketing`. Say you also have some instances where this variable had the value `marketing` at the start of the execution, yet this was later reassigned to the value `sales`. These instances will not be included in the filter. ::: To use complex types like object, use the **Variable Import Customization** feature to transform your object variables into primitive type variables. Start creating a variable filter by searching for and selecting a variable from the suggested list of variable names. ![Searching through the variables in variable filter](./img/variable-filter.png) There are four types of variables that you can filter for: ## Boolean variables They can have the state `true`, `false`, `null`, or `undefined`. ## String variables Two types of operators are available for variables of type `String`. You can either filter by an exact variable value (`is` and `is not`) or filter by a substring (`contains` and `does not contain`). For the operators `is` and `is not`, the first 10 values are loaded and displayed. If the variable has more than 10 values, a `Load More` button is shown to be able to extend the list as much as you need. You can also search through the whole list of values using the search input field. The list only contains variable values that already appeared in one of the instances of the process. To filter by a variable value that is not in the list of available values, click the **+ Value** button and add a custom variable value. In case the `is` option of the toggle button is selected, checking one or more values means that you want to see only those process instances where the variable value equals one of the checked values (this corresponds to the `or` operator in boolean logic.) In case the `is not` option of the toggle button is selected, checking one or more values means that you want to see only those process instances where the variable value does not equal any of the checked values (this corresponds to the `and` operator in the boolean logic.) For the operators `contains` and `does not contain`, you can add one or multiple values that should match a substring of the variable value. For the `contains` operator, adding one or more values means that you want to see only those process instances where the variable value contains one of the entered values (this corresponds to the `or` operator in boolean logic). In case the `does not contain` operator is selected, adding one or more values means that you want to see only those process instances where the variable value does not contain any of the entered values (this corresponds to the `and` operator in boolean logic.) There is an option to include the null or undefined values of the selected variable in the result. By using the same option, it is also possible to show all the values except the null or undefined by selecting the `is not` option of the toggle button. ## Numeric variables Here you have an input field to define whether the variable value in the process instance should be equal, not equal, less than, or greater than a certain value. You can even add more input fields and apply the same operation several times at once. If the `is` option of the toggle button is selected, adding one or more values means that you want to see only those process instances where the variable value equals one of the checked values (this corresponds to the `or` operator in boolean logic.) If the `is not` option of the toggle button is selected, adding one or more values means that you want to see only those process instances where the variable value does not equal any of the checked values (this corresponds to the `and` operator in boolean logic.) In case the `is less than` or `is greater than` option is selected, only one value can be entered. Null or undefined options can be included or excluded from the results in a way similar to string variables. ## Date variables This filters all instances where the selected date variable has a value within a specified date range. All the options that are available to configure [date filters](./metadata-filters.md#date-filters) are also available for date variables. Similar to the other variables, there are two input switches that allow you to exclude or include process instances where a particular date variable is either `null` or `undefined`. ## List variable filters To filter based on the value of a [list variable](/self-managed/components/optimize/configuration/object-variables.md#list-variables), the applied filter will depend on the primitive type of items within the list. For example, you will be creating a numeric variable filter for a variable which is a list of numbers, a string variable filter for a list of strings, and so on. It is important to note here that filters are applied on each individual item within the list variable and not the list itself. For example, an "is" filter on a list of string values filters for those instances where any individual list item is equal to the given term. For example, instances whose list variable "contains" the selected value. Similarly, the "contains" filter matches process instances whose list variable contains at least one value which in turn contains the given substring. ## Combine multiple variables filters with OR logic Additionally, to use variable filters individually, there is also the option of combining all the previously mentioned variable filters with OR logic. This means that variables which fulfill the condition specified in at least one filter will be displayed. --- ## Process dashboards ## Overview The **Process Default Dashboards and KPIs** section gives you an overview of all the processes and their status on a single page. This section also allows you to set a process owner and take responsibility of a process, viewing time, and quality KPIs to track process performance. Additionally, an **Adoption Dashboard** can be found at the top of the page, which integrates data from all processes in one view. ![Processes page](./img/processOverview.png) ## Set time and quality KPIs KPIs, or Key Performance Indicators, are reports represented by single numerical values with predefined targets. The process of creating KPI reports is explained in [process KPIs](./process-KPIs.md). The classification into a time or quality KPI occurs internally in Optimize based on the chosen measure. Once a report is set as a KPI, its status is visible on the **Dashboards** page. Hovering over the status provides a preview of specific information related to the KPI, including the set target, the current actual value of the single-number report, and a link to it. Users accessing the report via this link without proper authorization won't be able to view any data in the report view. ![Set time and quality KPIs](./img/kpiConfiguration.png) ## Configuring process owner and digests The process can be configured by clicking the **Configure** option selected from the three dots menu displayed on the right side when hovering over the process. From this modal, you can change the owner of the process, and enable/disable the process digest. The process digest is a scheduled email report summarizing the current and previous state of the KPI reports for that process. It will be emailed to the owner of that process at [globally configurable regular intervals](/self-managed/components/optimize/configuration/system-configuration.md#digest). Note that process digests are an alpha feature. ![Configure Process](./img/configureProcess.png) ## KPI import scheduler Since users might be dealing with hundreds or even thousands of KPIs, a scheduler has been developed which updates the KPI values on a given interval. The default interval in which the KPIs get updates is 10 minutes. To change this interval, modify the configuration value for **entity.kpiRefreshInterval**. For more information, visit the relevant [configuration section](/self-managed/components/optimize/configuration/system-configuration.md). ## Limitations Since the updates on the KPIs will appear on the process overview page after the given KPI import scheduler interval has passed, changes such as creation, update and deletion of KPIs will show with a delay. In case you wish to make these changes apparent more promptly, you can set the KPI scheduler interval to a lower value as described above. Additionally, it is worth mentioning that for the evaluation of the KPI reports, the default timezone of the machine on which Optimize is being run on will be used. --- ## User permissions By default, if you create a collection, only you can access the collection and the contents within. To share a collection with other users, add them to the collection. You are automatically assigned the manager role when creating a new collection. There can be multiple managers for a collection. However, there must be at least one manager for every collection. ## Roles in collections User permissions in collections involve managers, editors, and viewers. ### Managers Managers can: - Add, edit, and remove dashboards and reports in the collection. - Edit the collection name and delete the collection using the context menu in the header. - Add, edit, and remove other users in the collection via the **Users** tab. - Manage collection data sources. A manager can add a new user to the collection using the **Add** button. Use the ID of the user to add them. ### Editors Editors can: - Create, edit, and delete dashboards or reports in the collection. Editors cannot: - Edit the name of the collection. - Delete the collection. - Change anything in the **Users** tab. - Manage collection data sources. ### Viewers Viewers have read-only access. They can: - View the components contained within the collection. - Copy components. Viewers cannot: - Create, edit, or delete components in a collection. - Rename or delete the collection itself. - Change anything in the **Users** tab. - Manage collection data sources. Every user has a role assigned to them that specifies their access rights to the collection. Add users to collections via the **Users** tab, assigning roles that determine access rights. --- ## Monitor dashboards Monitor your dashboards and visualize your processes data with the **View mode**. It provides interactive charts, raw data tables, and sharing features. ## Supported features View mode provides the following features for monitoring your processes: - **Full-screen**: Display the dashboard in full-screen mode to focus solely on the reports, hiding the header, control panel, and footer. While in full-screen mode, toggle between the default light theme and a dark theme using the Toggle Theme button. - **Auto-refresh**: Periodically updates the dashboard with the latest data. You can customize the update frequency from one to 60 minutes. An animation indicates the timing of the next update. You can disable this feature if you no longer wish to use it. :::note The refresh rate will not be saved unless it is selected in the [edit mode](./edit-mode.md) of the dashboard. If it was selected in the view mode, the refresh rate will not be saved when refreshing the dashboard page manually or switching to another page in between. ::: - **Alerts**: For dashboards within a collection, create and manage alerts for reports inside the dashboard. ![process performance overview](./img/dashboard-viewMode-monitorFeatures.png) - **Description**: Displayed beneath the dashboard name, the description can be expanded or collapsed using the **More/Less** button for longer texts. ![dashboard description](./img/dashboard-showMoreDescription.png) - **Sharing**: To share or embed the dashboard, use the **Share** button. After turning the **Enable sharing** switch on, a link is generated for those without Optimize access. Include applied filters in the shared version by enabling the **Share with current filters applied** checkbox. If the checkbox is not checked, the shared dashboard will include the default filters if any have been set. ![sharing](./img/dashboard-sharingPopover.png) :::important Dashboard shared versions only allow you to view the dashboard. You cannot alter it or use any other Optimize features. To revoke the sharing, disable the share switch. ::: - **Embedding**: Click the **Embed Link** button to copy a code to paste into your webpage. Everyone that views the webpage can then see the content of the dashboard. To hide the header of the shared dashboard or specific part of it, add the following parameter to the share URL: `header : titleOnly / linkOnly / hidden` For example, to completely hide the header from the shared dashboard, add `header=hidden` as shown: `http://?header=hidden` ## Interacting with reports To see more details about the report on the dashboard, interact with the reports. The kind of interaction always depends on the report itself. If the interactions do not suffice to get the desired information, or you want to edit the report, directly access the report by clicking on its title. ## Adding filters in view mode In the dashboard view mode, there is a **Filters** button which opens a panel that shows all filters available for this dashboard. More filters can be made available in the dashboard edit mode. If the dashboard editor checked the **Allow viewer to add filter values** box for assignee, candidate group, or variable filters, dashboard viewers can add their own values to filter by. ![filters in view mode](./img/filter-viewMode.png) Filters apply to all process reports on the dashboard. If a report already has filters set, they will be combined with the dashboard filter. For example, if a report has a filter to only show running instances and a dashboard filter for suspended instances is set, the report will only show instances that are both running and suspended. Dashboard filters are not applied to decision reports, external websites, or text tiles. Variable filters are only applied to reports whose process definition includes the variable. Otherwise, the filter is ignored for that report. Other dashboard filters and filters defined directly on the report are still applied. --- ## What is Optimize? :::note New to Optimize? Visit our introductory guide to [Optimize](/components/optimize/improve-processes-with-optimize.md) to get started. ::: Camunda 8 is built to handle three key aspects of process automation: - Design - Automate - Improve Users can design process flows in [Camunda Hub](/components/hub/workspace/modeler/index.md) or [Desktop Modeler](/components/modeler/desktop-modeler/index.md). In a production scenario, users can deploy through Desktop Modeler, Hub, or programmatically. A user can use [Tasklist](/components/tasklist/introduction-to-tasklist.md) to review and complete tasks, and [Operate](/components/operate/operate-introduction.md) to view and analyze process instances. Beyond these design and automate cornerstones lies an important component to leverage our process data and analyze areas for improvement: Optimize. Geared toward business stakeholders, Optimize offers business intelligence tooling for Camunda enterprise customers. By leveraging data collected during process execution, users can collaboratively access reports, share process intelligence, analyze bottlenecks, and examine areas in business processes for improvement. ![process performance dashboard](./img/dashboard-sharingPopover.png) As process instances run through the server, Optimize makes REST API calls to the Camunda server, collecting new historical data and storing it in its Elasticsearch database. This enables users to independently analyze reports and dashboards, gaining actionable insights without affecting runtime. Optimize goes beyond traditional business intelligence tools, guiding users toward continuous process improvement by understanding their goals. It facilitates the rapid identification of constraints within an individual's or organization's system. Explore Optimize for a closer look at process performance dashboards, review heatmaps for instance durations, and visualize activity instances compared to total process instances. In the following sections, we'll guide you through using and analyzing Optimize. --- ## About the Orchestration Cluster The Orchestration Cluster is the core component of Camunda 8, powering the automation and orchestration of processes. It includes: - Zeebe as the workflow engine. - Operate for monitoring and troubleshooting process instances running in Zeebe. - Tasklist for interacting with user tasks (assigning, completing, and so on). - Admin for managing the integrated authentication and authorization. - APIs for interacting with the Orchestration Cluster programmatically. ## Zeebe Zeebe is the process automation engine powering Camunda 8. - [Learn more about Zeebe](/components/zeebe/zeebe-overview.md) ## Operate Operate is a tool for monitoring and troubleshooting process instances running in Zeebe. - [Learn more about Operate](/components/operate/operate-introduction.md) ## Tasklist Tasklist is a ready-to-use application to rapidly implement business processes alongside user tasks. - [Learn more about Tasklist](/components/tasklist/introduction-to-tasklist.md) ## Admin Admin is responsible for managing authentication, authorization, and access-related entities within the Orchestration Cluster. - [Learn more about Admin](/components/admin/admin-introduction.md) --- ## ProcessOS :::info Learn more about [ProcessOS and the great re-engineering](https://camunda.com/learn/the-great-re-engineering). ::: ## Get started Get started with ProcessOS. --- ## Handle project data safely ## About A ProcessOS project reads your organizational knowledge and sends parts of it to an AI platform. Knowing which parts, and controlling what you expose, is part of running a project responsibly. This page covers the practical choices you make as a builder. It doesn't replace your organization's data classification policy or your agreement with an AI platform provider. ## Where project data lives | Location | What's stored there | | ------------------- | ------------------------------------------------------------------------------------ | | Your local machine | The whole project, including sources, generated artifacts, and configuration. | | Your Git repository | Every committed artifact. | | Camunda cluster | Governance process state, including which phase you're in and pending SME questions. | | AI platform | The context your coding agent sends while running a skill. | The AI platform is the boundary worth thinking about. Everything else stays inside infrastructure you already control, so your main decision is what the agent gets to read. ## Control what reaches the AI platform Your AI coding agent determines which AI platform is used and what it sends, so route that choice through the same review any other processor would get. Platforms such as Amazon Bedrock, Azure OpenAI, and Ollama are supported through the agent rather than by ProcessOS Harness directly. Practical controls: - **Keep secrets out of the project directory.** Credentials, tokens, and keys shouldn't be in files the agent can read. Generated workers reference configuration rather than embedding it. - **Review the diff before you commit.** Skills stage changes in your working tree, which is your checkpoint for catching anything that shouldn't enter the repository. ## Choose sources that make discovery succeed Source quality drives the accuracy of the as-is process, and a confident but wrong model is the expensive failure mode here. - Prefer primary artifacts, such as exported legacy process definitions, production configuration, and real forms, over summary presentations. - Include the systems the process integrates with, so integration points surface during discovery rather than during implementation. - Provide current material. Documentation for a version of the process no longer in use produces a model that looks right and isn't. - Prefer anonymized samples to production records. Discovery needs the shape of the data, rarely the data itself. ## Handle personal and regulated data Process documentation often carries personal data by accident, in the form of sample cases, screenshots, and exported records. - Remove or mask personal data from sample files before adding them to the project. - Crop personal information from screenshots used in review packages. - Check whether your process falls under rules requiring disclosure that AI was involved, described in the [AI usage guidelines](/components/agentic-orchestration/ai-usage-guidelines.md). - Treat the generated solution as you would any other code artifact when it contains business rules you consider confidential. ## Share with SMEs and reviewers Review cycles move project material to people outside the builder workspace, so apply the same care there. --- ## How a builder task works A builder task is a unit of work that is executed in the AI Coding Agent. Each builder task runs as a job, with a skill doing the main work and the builder overseeing, judging, and revising it. This shape gives every task three properties at once: guidance from the governance process for what to do, auditability through Camunda and Git, and full flexibility for the builder to do whatever else the job needs. ## The five steps The following steps are executed if the governance process is at a builder task: 1. **Activate the job.** The agent pulls the next pending job from the governance process running on Camunda, along with its skill name, skill mode, and run configuration. 1. **Execute the skill.** The agent runs the ProcessOS Harness skill named in the job, producing or updating files in your project. 1. **Take your builder action.** You review what the skill produced, correct it, rerun it, or do whatever else the situation needs. This is the open part of the task. 1. **Commit to version control.** The result is committed to Git, which makes the change part of the permanent project record. 1. **Complete the job.** The agent pushes the outcome back to the governance process, which then decides the next step. Steps 1 and 5 are handled by the agent against Camunda. You handle step 3. ## How to work on a builder task? 1. Clear agent context. Each builder task should be executed in a new coding session. 1. Execute the next step. Ask the AI coding agent `next` to trigger builder work. ## Builder tasks in Camunda Builder tasks are service tasks within Camunda. They are handled as a job similar to any other service task. ProcessOS Harness contains skills to manage Camunda jobs. ![A builder task represented as a service task in the ProcessOS Harness BPMN model in Camunda Modeler.](../img/builder-task-in-modeler.png) --- ## How the governance process guides a project ## About The governance process is the Camunda Solution Methodology implemented as an executable Camunda process. It holds the state of your ProcessOS project, orchestrates SME feedback, and tells you what to do `next`. The governance process on the Camunda cluster is started as part of `/process-os-governance-start`. From this point, the process guides you through all phases along certain milestones. Your input is required for gates, jobs, and tasks. ## Phases and milestones The engagement runs through four phases in order. The output of one phase is the input to the next. | Phase | Goal | Milestone reached | | --------- | --------------------------------------------------------------------------- | ----------------------------- | | Scope | Define the scope of the process to re-engineer. | Process scope defined | | Discover | Discover the as-is process from provided sources, and close gaps with SMEs. | As-is models finalized | | Transform | Transform the as-is process into a to-be process. | To-be models finalized | | Implement | Generate and implement the executable Camunda solution. | Executable solution generated | ## Phase cycle Each phase starts with defining the run configuration for the [Builder task](builder-task.md). After the Builder task is complete, subject matter experts (SMEs) review the results. The Builder then evaluates the consolidated review feedback and determines whether another iteration is required. You can adjust the configuration for the next iteration. ## Gates and tasks The governance process needs human input to ensure the right outcomes. | Type | Where you complete it | Typical use | | ------------------------------- | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | Project / Process scope | Camunda Tasklist, using a form. | The project and process scope are entered in the first phase. | | Phase configuration | Camunda Tasklist, using a form. | Each phase has configuration parameters. | | Human gates | Skills define human gates. These are directly raised in the AI coding agent session in the terminal. | Skill-specific questions the agent needs input for. | | [Builder task](builder-task.md) | The AI coding agent, driven by a ProcessOS Harness skill. | Anything an agent can generate, transform, or check: discovery specialists, BPMN, forms, tests. | ## Control the governance process with `next` The governance process is controlled via the AI coding agent. As soon as you are done with a job, or you are unsure about the next thing to do, you ask the agent what's `next`. The AI coding agent interacts with Camunda and the governance process to provide the right guidance according to the current state of the project. ## Next steps - Learn how [review cycles](review-cycle.md) bring SMEs into each phase. - Understand [how a builder task works](builder-task.md). - Start the first phase, described in [discover the as-is process](phases/1-discovery.md). --- ## Discover the as-is process ## About Discovery builds a faithful picture of how a process runs today. ProcessOS Harness researches your organizational knowledge with parallel specialists, generates as-is process descriptions and BPMN diagrams, and closes the remaining gaps with SMEs. ## Choose the right data sources "Discovery results depend on the input." That's true, but judging the input is frequently a challenge in itself. Don't waste too much time curating data sources for the first iteration. Start with what you have on hand and add more sources where required. - Prefer primary artifacts, such as exported legacy process definitions, production configuration, and real forms, over summary slide decks. - Include the systems the process touches, not just the process description, so integration points surface early. - Provide recent material. Documentation describing a version of the process no longer in use produces a confident but wrong as-is model. - Name the known gaps in `process-scope.md`. Telling ProcessOS Harness what's missing is more useful than letting it infer the gap later. Check what you're allowed to share before you point a specialist at a source, described in [handle project data safely](../../best-practices/data-handling.md). ## Discovery specialists Discovery specialists extract insights from specific source types. The `specialists` setting determines which sources ProcessOS Harness reads. ProcessOS Harness comes with three specialists. Future versions will have a plugin mechanism to easily add specialists. For now, the Camunda team can support you with client-specific specialists. ## Next step Continue with [transform the process to a to-be design](2-transformation.md). --- ## Transform the process to a to-be design ## About Transformation turns the signed-off as-is process into a to-be design. ProcessOS Harness elicits your criteria, analyzes each phase of the process, and then challenges every system, data flow, and manual step it found. ## Choose a tier The phase offers tiers that range from a safe optimization pass to an agent-first redesign, so you can compare options before committing to one. | Tier | What it proposes | | ------------- | --------------------------------------------------------------------------------------------------- | | `migration` | Lift and shift. Carries the as-is process into executable form without changing it. | | `incremental` | Safe automation. A deterministic optimization pass that always runs as part of a to-be exploration. | | `radical` | A redesign that challenges organizational boundaries. | | `moonshot` | An agent-first design with almost no user tasks. | ## Next step Continue with [implement the Camunda solution](3-implementation.md). --- ## Implement the Camunda solution ## About Implementation turns a chosen to-be tier into a deployable Camunda solution. 1. ProcessOS Harness first generates the execution-ready artifacts, including BPMN, DMN tables, Camunda Forms, and job workers, from the transformation output. 1. It then deploys the solution to a development or test cluster so it can be exercised end-to-end. 1. Finally, it generates and runs three layers of tests, so you can see the solution behaves as intended before it moves into your own release process. :::warning These commands target a development or test cluster. Deploying to production is outside the scope of ProcessOS Harness, and remains your own release process. ::: ## Validate with the test layers ProcessOS Harness generates three layers of tests. | Layer | What it validates | Runtime | | ----------------- | ------------------------------------------------------------------------------------------------------------------- | --------------------- | | Process tests | BPMN orchestration: routing, sequencing, and error handling. All service tasks are mocked, and no real workers run. | Camunda engine | | Integration tests | Real job workers and connectors, with mocking as a last resort. | Camunda engine | | Worker unit tests | A worker's internal business logic and edge cases. | Plain Java, no engine | Process tests aim for complete BPMN element coverage, so every gateway branch, end event, and error boundary is exercised. Integration tests come in three increasing scopes. A single-element test probes one task, connector, or DMN table with no mocking. A segment test runs from the start to a checkpoint, mocking user tasks only. An end-to-end test runs the full process with no mocking, for pre-release confidence. Worker unit tests call the method annotated with `@JobWorker` directly as plain Java, using mocks. They need no Camunda runtime, no Docker, and no process context, which makes them the cheap way to cover logic edge cases. --- ## Review generated results with SMEs ## About A review cycle is how ProcessOS Harness keeps a human in the loop. In each cycle, SMEs answer open questions and review generated artifacts. The cycle repeats until they sign off on the result. ## How a cycle runs 1. ProcessOS Harness generates artifacts and collects the questions it can't answer itself from the sources it has. 1. SMEs [answer review questions](#answer-review-questions) and comment on the generated artifacts. 1. You [feed the answers back](#feed-answers-back) into the project, and ProcessOS Harness regenerates the affected results. 1. You either request another cycle or sign off. Because the interaction model is iterative, plan for more than one cycle per phase. A first-pass result that needs correction is the expected outcome, not a failure. ### Answer review questions ProcessOS Harness stores review questions in structured YAML files, such as `transformation/review-questions.yaml`. SMEs rarely want to edit YAML, so you can export the questions into a more familiar format and import the answers afterwards. | Type | Command | What it produces | | ---------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | Camunda Tasklist Questionnaire (Default) | Camunda Tasklist | SMEs can also answer questions in Camunda Tasklist, because the governance process presents them as Camunda Forms. | | Skill | `/process-os-questions-md-export` | A Markdown copy of the questions, for answering in a text editor. | | Skill | `/process-os-questions-xlsx-export` | A spreadsheet copy, for answering in Excel. | | Skill | `/process-os-review-answer-application` | Applies answered questions back into the discovery documents. | You can also modify the review cycles to your needs by adjusting the `Review XX results` subprocesses in the governance process before the execution. The Camunda Platform is perfectly suited to optimize and automate gathering SME feedback. ### Review artifacts directly Diagrams are often easier to review than prose. ProcessOS Harness provides different tooling for reviews: | Command | What it does | | -------------------------------- | ------------------------------------------------------------------------------------------------ | | Camunda Modeling tools | View and edit BPMN files. | | `/process-os-file-viewer [file]` | Comment BPMN files in a browser-based viewer. | | `/process-os-sme-review-package` | Bundles all BPMN files to hand to an SME. The bundle content can be opened locally in a browser. | Reviewers can add annotations, rename steps, and restructure flows directly in Camunda Modeler. ProcessOS Harness reconciles those hand edits when you run the feedback cycle for the phase. ### Feed answers back Each phase has a default feedback mode that collects reviewer input, reconciles it with the generated artifacts, and regenerates what changed. Collected feedback lands in the iteration's `feedback` folder, described in [manage files during the review cycle](#manage-files-during-the-review-cycle). ## Manage files during the review cycle ProcessOS generates iteration folders to keep generated artifacts and reviewer feedback organized throughout each review cycle. Each iteration works as follows: - Discovery and Transformation create a new folder for each iteration. - Each iteration folder contains `artifacts` and `feedback` subfolders. - The `artifacts` folder contains all files generated by ProcessOS Harness. - Store all feedback—whether collected by ProcessOS Harness or added manually—in the `feedback` folder. --- ## Build a solution with ProcessOS Harness ## About After you [install ProcessOS Harness](installation.md) and start the journey, you build your solution in a governed project. This section covers the main concepts of a ProcessOS project and how they work together. ## Main concepts | Concept | What it does | | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Governance process](build/governance-process.md) | Runs on Camunda, holds the state of your project, and guides you through phases and milestones. You ask the agent what's `next`. | | [Builder task](build/builder-task.md) | The atomic unit of work. The agent activates a job, runs a skill, you act on the result, and the result is committed to Git. | | [Review cycle](build/review-cycle.md) | Keeps a human in the loop. SMEs answer open questions and review generated artifacts until they sign off. | | Phases | Scope, [discover](build/phases/1-discovery.md), [transform](build/phases/2-transformation.md), and [implement](build/phases/3-implementation.md). Each phase ends at a milestone, and the output of one phase is the input to the next one. | --- ## Install ProcessOS Harness and configure a project This page takes you from nothing to a configured ProcessOS project with the journey running. Camunda recommends completing the [organizational setup](setup/project-setup.md) first and checking the [system requirements](setup/system-requirements.md). ## Install ProcessOS Harness :::note For Camunda Enterprise customers and active partners ProcessOS Harness is available to Camunda Enterprise customers and Camunda partners with a signed Camunda partner agreement. To download it, you need access to the [Camunda Download Center (Enterprise Releases)](https://downloads.camunda.cloud/enterprise-release/) with your Camunda Enterprise credentials. If you don't have credentials, contact your Customer Success Manager or Partner Manager. ::: ### Choose a target ProcessOS Harness ships one bundle per AI coding agent. Every bundle carries the same skills, rules, and hooks, generated into the layout that agent expects. A `target` is the label of the bundle that matches your AI coding agent. Use it in the c8 os install command and as the name of the archive you download, because an agent only discovers skills in its own directory. | AI coding agent | Target | Skills directory | | ------------------ | ------------ | ----------------- | | Claude Code CLI | `claudecode` | `.claude/skills/` | | GitHub Copilot CLI | `copilotcli` | `.github/skills/` | Only one bundle can be installed at a time. ### Install 1. Ensure all [builder client](setup/system-requirements.md#builder-client) requirements are covered. 1. Install or update c8ctl, the Camunda 8 CLI: ```bash npm install -g @camunda8/cli@latest ``` This installs both `c8ctl` and its shorter alias `c8`. The commands on this page use `c8`. If another `c8` command, such as the `c8` code coverage tool, takes precedence in your `PATH`, use `c8ctl` instead. 1. Load the ProcessOS Harness plugin: ```bash c8 load plugin @camunda8/c8ctl-plugin-process-os ``` The plugin adds the `c8 os` commands, which install and manage the ProcessOS Harness bundle. 1. Create and enter a project folder: ```bash mkdir -p && cd ``` This folder will hold all project files. 1. Download the ProcessOS Harness [bundle](setup/system-requirements.md#processos-bundle-mapping) for your AI coding agent from the [Camunda Download Center (Enterprise Releases)](https://downloads.camunda.cloud/enterprise-release/). 1. Install ProcessOS Harness from the bundle: ```bash c8 os install --zip /path/to/.zip ``` :::note Replace `` with the bundle name, for example `claudecode`. ::: 1. Initialize a version-controlled project: ```bash git init && git add . && git commit -m "chore(job) commit process-os-harness setup" ``` 1. Start the journey. Launch your AI coding agent, select a smart model (e.g. Opus or similar), then run: ```text /process-os-governance-start ``` ### Keep an installation up to date To install a newer bundle, download it from the Camunda Download Center (Enterprise Releases) and run `c8 os switch`. | Command | What it does | | ---------------------------------------- | ----------------------------------------------------- | | `c8 os switch --zip ` | Remove the current bundle, then install the new one. | | `c8 os uninstall` | Remove the bundle, and restore the files it replaced. | ## Next steps - Learn how the [governance process](build/governance-process.md) guides you between milestones. - Learn how [review cycles](build/review-cycle.md) bring SMEs into each phase. - Start the first phase, described in [discover the as-is process](build/phases/1-discovery.md). --- ## ProcessOS Harness :::caution Early access ProcessOS Harness is available as [early access](/components/early-access/overview.md) for trained customers and enabled partners. See [current scope](#current-scope). ::: ## About ProcessOS Harness is the backbone of ProcessOS. It is a governance process that runs on Camunda and drives AI coding agents in the terminal, such as Claude Code CLI or GitHub Copilot CLI, to perform the building safely and under control. The input for ProcessOS Harness can be any resource that describes the process, such as files, systems, or web pages. The harness guides the agent through a phased approach, asks humans for review, and generates a tested and deployable Camunda solution. ## Who ProcessOS Harness is for ProcessOS Harness targets the builder: someone who implements Camunda end to end and is comfortable directing AI coding agents. Subject matter experts (SMEs) take part as reviewers and approvers rather than as primary contributors, which reduces the time they invest in a project. Builders get the most out of ProcessOS Harness when they bring: - Technical depth in Camunda and software development. - Practical experience with AI and agentic tools, including context management and prompt engineering. - Enterprise production experience, and the judgment to tell a working demo from a working system. - Stakeholder communication skills across engineering, architecture, and business leadership. - Comfort with ambiguity. ## Key principles ### A governance process guides the journey ProcessOS Harness runs your engagement as a governed process with defined phases and milestones. You keep full flexibility between milestones, but the milestones themselves ensure the project progresses. | Phase | Goal | | --------- | ------------------------------------------------------------------------------- | | Scope | Define the scope of the process to re-engineer. | | Discover | Discover the as-is process from organizational memory, and fill gaps with SMEs. | | Transform | Transform the as-is process into an agentic to-be process. | | Implement | Generate and implement the executable Camunda solution. | Each phase ends at a milestone: process scope defined, as-is model finalized, to-be models finalized, and solution ready for production. Between milestones, [SME review cycles](build/review-cycle.md) act as gates that validate progress before the project moves on. The governance process itself runs on Camunda. Project state lives in Camunda and every artifact is committed to Git, so the whole engagement is auditable. For details, see [how the governance process works](build/governance-process.md). ### Progress comes from iterations and your judgment AI isn't deterministic, so ProcessOS Harness doesn't produce a finished solution in a single pass. Project maturity rises through iterations across discovery, transformation, and implementation, and thinking in iterations is the skill that matters most. Your expert judgment is what turns agent output into a working system. ProcessOS Harness generates artifacts, such as process descriptions, BPMN models, DMN tables, Camunda Forms, and job workers, and runs tests, but you assess each result and confirm when it is ready to carry into the next phase. Plan for review, correction, and another iteration rather than for a single hand-off. The AI coding agent supports you throughout. You can ask it to fix problems at any point, including working around defects you hit along the way. ### Two supported use cases ProcessOS Harness runs all phases for both use cases, but uses different modes within them. | Use case | What it does | | ----------------- | ------------------------------------------------------------------------------------------------------- | | Legacy migration | Transforms processes running on a legacy system to Camunda 8, as a first step before AI transformation. | | AI transformation | Transforms any process into an automated, AI-native process executable on Camunda 8. | Legacy migration transformations can be optimized for specific source systems. Get in contact with Camunda to learn more. ## Current scope Today, ProcessOS Harness focuses on taking you from discovery to a generated, tested Camunda solution. The following areas sit outside this release and are on the roadmap for future iterations: - Production deployment and full application lifecycle management. - Camunda platform setup and CI/CD integration. - Custom application and user interface generation beyond Camunda Forms. - Process performance measurement and quantitative analysis such as process mining. - Shared organizational memory across projects. In this release, each project runs locally with its own private memory. Cross-project memory and multi-project management are planned for future releases. ## Get started 1. [Set up your organization for ProcessOS Harness](setup/project-setup.md). 2. [Install ProcessOS Harness and configure a project](installation.md). --- ## Understand the builder workspace ## Parts of the builder workspace | Part | What you use it for | How to use | | ------------------ | ------------------------------------------------------------------------ | ------------------------------------- | | AI coding agent | Does the work in each job, and helps you fix problems at any point. | CLI in Terminal | | Git | Stores every project artifact, so changes are reviewable and reversible. | Terminal | | Governance process | Runs on Camunda, and tells you what to do next. | Access Camunda Operate with a Browser | | Modeler | Opens generated BPMN, DMN, and form files for review. | Open with Camunda Modeler | Camunda recommends using an IDE, such as Visual Studio Code, because it brings most of these parts together in one place. ## Next steps - Check the [system requirements](system-requirements.md). - [Install ProcessOS Harness and configure a project](../installation.md). --- ## Set up your organization for ProcessOS Harness Before you install anything, set your organization up to succeed with ProcessOS Harness. This page covers the organizational preparation. For the technical setup, see [install ProcessOS Harness and configure a project](../installation.md). ProcessOS Harness is a new way of running projects, not a new feature in a familiar tool. The interaction model is unfamiliar, and the early access release assumes a trained builder. The two steps below are the intended path for early access, not optional extras. ## Run your first project with a partner or a Camunda FDE Run your first ProcessOS project together with an enabled partner or a Camunda Forward Deployed Engineer (FDE), rather than on your own. The first journey is where an unfamiliar interaction model costs the most. Someone who has already completed an engagement can tell a recoverable result from a dead end, which saves you iterations. Once your team has one project behind it, later projects are far less dependent on outside support. Contact your Camunda account team to arrange this. ## Complete the Camunda Academy overview course Complete the ProcessOS Harness overview course in [Camunda Academy](https://academy.camunda.com/camunda-8-processos-harness-overview) before you start a project. Academy is where you build the capability to work with ProcessOS Harness, including the mindset shift that working with non-deterministic AI output requires. This documentation is written for a builder who has already done that, and it's here to keep you unblocked rather than to teach the approach from scratch. Treat the overview course as a prerequisite for your first project. ## Plan the effort ProcessOS Harness runs many agent iterations. Plan for six to eight hours for one journey from discovery through implementation. ### Token cost A journey has a real token cost that depends on the scenario: | Scenario | Token cost (USD) | | ------------------------------------------ | ---------------- | | Explorative run, small use case | 300 to 600 | | Lift-and-shift, medium-sized process | 800 to 1,500+ | | AI transformation, medium-to-large process | 1,000 to 2,000+ | Treat these ranges as planning inputs rather than fixed prices. The following factors influence the cost: - Model: For good results with ProcessOS, we recommend using a capable model, such as Opus or a comparable model. Pricing varies by model. - Process complexity and size. - Discovery input: Its size, format, scope, and quality. For example, extracting a process from images in a PDF costs more than reading text-based files, such as BPMN files. - Review iterations: The number of iterations required to reach an agreed-upon result. - Transformation tier: A lift-and-shift transformation costs less than a radical transformation. ### SME involvement Involve your SMEs early. ProcessOS Harness changes their role from main contributor to reviewer, which reduces their total effort, but they still need to be available at the review gates in each phase. A project stalls when a review cycle has no reviewer. ## Next steps 1. [Understand the builder workspace](builder-workspace.md). 2. Check the [system requirements](system-requirements.md). 3. [Install ProcessOS Harness and configure a project](../installation.md). --- ## System requirements ## Overview ProcessOS Harness requires the following: - A Camunda cluster to run the [governance process](#governance-process). - A [Git-compatible version control system](#project-version-control) to store your projects. - A supported [AI coding agent](#ai-coding-agent) to do the re-engineering work. - A [builder client](#builder-client) with local tooling, where the agent runs. ![System overview: the builder client hosts the AI coding agent and a local Camunda server, syncs status with the ProcessOS governance process on the enterprise Camunda server, uses VCS and an AI platform, and generates a ProcessOS solution deployed back to the enterprise Camunda server.](../img/process-os-system-requirements.excalidraw.svg) :::note Agentic tooling is flexible by nature, so ProcessOS Harness may run on more systems than the ones listed here. These are the configurations Camunda currently tests, and the goal is to keep ProcessOS Harness independent of any specific AI coding agent or AI platform. ::: ## Governance process The governance process needs a Camunda cluster that's reachable across your organization, because it holds ProcessOS project state and orchestrates SME feedback. | Requirement | Supported | | ----------- | ------------------------------------------------------ | | Camunda 8 | A supported release of Camunda 8 SaaS or Self-Managed. | ## Project version control ProcessOS projects are stored in repositories, so managing one requires a Git-compatible version control system. You can host it internally or use a provider such as GitHub. ## AI coding agent ProcessOS Harness drives an AI coding agent to do the work of re-engineering. | Aspect | Supported | | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | AI coding agent | Currently supported are **Claude Code CLI** and **GitHub Copilot CLI**, each installed as a [bundles](#processos-bundles). | | AI platform | Platforms such as Amazon Bedrock, Azure OpenAI, and Ollama, where the coding agent supports them. | | Models | Models natively used by a supported coding agent. Anthropic Claude models are tested the most, with Opus or smarter alternatives recommended. | :::note Other agents may be supported in future releases, but aren't yet tested exhaustively. Please get in contact if you need support for additional AI coding agents. ::: The agent needs these permissions in your environment: - Outbound network access, for example `curl` and `wget`. - Write access within the ProcessOS project Git repository. - Permission to execute bash commands and scripts. ## Builder client The AI coding agent runs on the builder's own computer (Windows, macOS, or Unix). The following tools are required in the `latest` version: | Requirement | Details | When to install | | ------------------ | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | | AI coding agent | See [AI coding agent](#ai-coding-agent). | Before [installation](../installation.md#install-processos-harness) | | Camunda Modeler | For editing BPMN and DMN files during review. | Before [installation](../installation.md#install-processos-harness) | | Node.js | Used as scripting language by ProcessOS Harness skills. | Before [installation](../installation.md#install-processos-harness) | | Git and GitHub CLI | `git`, and the `gh` CLI for the GitHub discovery specialist. | Before [installation](../installation.md#install-processos-harness) | | Maven | For Java workers only. | Before [installation](../installation.md#install-processos-harness) | | c8ctl | The Camunda 8 CLI, used to deploy and manage Camunda resources and clusters. Available as `c8ctl` and its alias `c8`. | During [installation](../installation.md#install-processos-harness) | | Camunda c8run | A local cluster started with `c8run`, used to validate generated solutions. | During ProcessOS run | ## Generated solutions Solutions generated by ProcessOS Harness use the following technologies. | Component | Technology | | ----------------- | ------------------------------------------------------------------------------------------------------------- | | Workers | Target SDKs: Spring Boot; TypeScript; Python | | Camunda artifacts | `.bpmn`, `.dmn`, and `.form` files for the latest Camunda 8 version. | | AI platforms | As supported by [agentic orchestration](/components/agentic-orchestration/agentic-orchestration-overview.md). | --- ## Backpressure Warning :::note We recommend reducing the rate of requests. When backpressure is active, the broker may reject any request except _CompleteJob_ RPC and _FailJob_ RPC. These requests are allowed during backpressure and are always accepted by the broker even if it is receiving requests above its limits. ::: --- ## Connector Task You can apply a connector to a task or event via the append menu. For example: - **From the canvas**: Select an element and click the **Change element** icon to change an existing element, or use the append feature to add a new element to the diagram. - **From the properties panel**: Navigate to the **Template** section and click **Select**. - **From the side palette**: Click the **Create element** icon. In each of these menus, you can search by connector name or by the operation you want to perform, such as `upload object` or `send email`. Connectors that provide several operations list them as separate entries, and selecting an operation applies the connector with that operation preselected. After you have applied a connector to your element, follow the configuration steps or see [using connectors](/components/connectors/use-connectors/index.md) to learn more. --- ## Create Api Credentials To interact with your Camunda 8 cluster, you'll use the Camunda client. First, you'll need to create credentials. 1. In Camunda Hub, in the left navigation, click **Environments**, and then click **Clusters**. 2. Select a cluster. 3. Click the **API** tab. 4. Click **Create new client**. 5. Provide a **Client Name** and **Scopes**. 6. Click **Create**. 7. Copy or download your client credentials. You will need your client ID and your client secret when creating a worker in the next section, so keep this window open. Once you close or navigate away from this screen, you will not be able to see them again. --- ## Create Cluster To deploy and run your process, create a cluster in Camunda 8. 1. In **Camunda Hub**, click **Environments > Clusters**, and then click **Create cluster**. 2. Name your cluster. 3. Select a region and backup location. 4. Select a cluster type and tag. 5. Select a channel and generation. For the purpose of this guide, Camunda recommends using the **Stable** channel and the latest generation. 6. Click **Create cluster**. It will take a few moments to create your cluster. Check the status on the **Clusters** page or by clicking into the cluster itself and looking at the **Components** section. :::tip If **Create cluster** is disabled, consider these explanations: - Your organization is on a trial plan, and you have already created a cluster. In this case, you cannot create another cluster, because only one cluster is included in the trial plan. - Your billing reservations do not allow any more clusters. You must increase the [reservations](/components/saas/organization/manage-plan/update-billing-reservations.md) to create more clusters. If you do not have the necessary rights, contact an admin or the owner of the organization. ::: If the cluster shows a status of at least **Creating**, you can start modeling. However, Zeebe must show a status of **Healthy** to properly deploy your model. --- ## Get started with RPA Use the RPA worker and either Camunda Hub or Desktop Modeler to create, test, and automate RPA scripts. ## About the RPA worker The RPA worker is available on all major platforms (Windows, Linux, and macOS). This lets you automate applications on their native platforms, which is typically Windows. For console applications or browser automation, you can use a lightweight distribution such as this [Docker image](../../../self-managed/deployment/docker/). ## Create your first script Get started with RPA by creating your first RPA script. With Camunda Hub and Desktop Modeler, you can edit and test your scripts. You can get started developing and testing your scripts locally without a Camunda connection using [Desktop Modeler](/components/modeler/desktop-modeler/index.md) if you already have a Camunda instance available. ### Using Desktop Modeler 1. **Download Desktop Modeler**: [Download the latest version of Desktop Modeler](https://camunda.com/download/modeler/). 2. **Open the RPA script editor**: Open Desktop Modeler and navigate to the RPA script editor under **Testing**. If you don't see the bottom panel, click **Window > Toggle Bottom Panel**. 3. **Write your RPA script using Robot Framework**: Use the editor to create your first RPA script. Scripts use the [Robot Framework](https://robotframework.org/) syntax. #### Test your script Once you have written your script, you can test it on a local RPA worker. 1. **Start the RPA worker**: 1. Download the latest version of the [RPA worker](https://github.com/camunda/rpa-worker/releases). 2. Unpack the `rpa-worker_*.zip` file. The zip archive contains the worker executable and an example configuration file. 3. Start the worker by running the executable. 2. **Check Desktop Modeler**: Ensure the RPA worker is connected to Desktop Modeler. The worker should automatically connect. If not, click on the connection status to display additional configuration options. 3. **Test the script**: 1. Click the test tube (🧪) icon in the footer of Desktop Modeler to open the run dialog. Add any variables required by the process in JSON format. Once you start the execution, the execution tab will open. 2. Review the execution log and the variables created during the script execution within Modeler. ### Using Camunda Hub 1. In Camunda Hub, open a workspace. 2. In the workspace, open a project. 3. Use the **Create new** menu, and select **RPA script**. 4. **Write your RPA script using Robot Framework**: Use the editor to create your first RPA script. Scripts use the [Robot Framework](https://robotframework.org/) syntax. 5. In a BPMN diagram, you'll now find your new RPA script in the **Append element** menu. #### Test your script Once you have written your script, you can test it on a local RPA worker. 1. **Start the RPA worker**: 1. Download the latest version of the [RPA worker](https://github.com/camunda/rpa-worker/releases). 2. Unpack the `rpa-worker_*.zip` file. The zip archive contains the worker executable and an example configuration file. 3. Configure the RPA worker to connect to your Camunda instance. 4. Start the worker by running the executable. 2. **Test the script**: 1. Select the **Test** tab in the **Details** pane. Add any variables required by the process in JSON format. Once you start the execution, the **Results** section shows the execution output and variables. 2. Review the execution log and the variables created during the script execution within Modeler. ## Automate execution Once you are happy with your script and have tested it locally, you can start automating it with Camunda. ### Link RPA task to BPMN 1. **Deploy the RPA file**: 1. If you have not already, [set up client connection credentials](/components/saas/clusters/manage-api-clients.md#create-a-client) in Camunda Hub. 2. Assign the **RPA role** to the client in the [Orchestration Cluster Admin (formerly Orchestration Cluster Identity)](../../admin/role/#assign-client-to-a-role). 3. Deploy your RPA script file by clicking on the rocket (🚀) icon in Desktop Modeler or the **Deploy** button in Camunda Hub 4. For Desktop Modeler, note the ID of your RPA script. You will need this in the next step. 2. **Add RPA to your process**: 1. Open an existing BPMN file or create a new one. 2. Add the RPA script to your process: - **Camunda Hub**: Search for the RPA script by name directly in the **Append element** menu. No script ID is required. - **Desktop Modeler**: Add a new task and change the type to **Run RPA Script**. 3. **Desktop Modeler only**: Configure the task with the script ID from the previous step. Add any input mappings required for your script to work. 3. **Deploy and run the process**: 1. Deploy the BPMN model with the configured RPA task by clicking on the rocket (🚀) icon in Desktop Modeler, or use the **Deploy** button in Camunda Hub. 2. Start an instance of your process. Once your RPA script and model are deployed, you can also use the **Test** functionality in Camunda Hub. ### Connect worker to Zeebe The last step is to configure the RPA worker to pick up the jobs from Camunda. 1. **Create credentials for the worker**: 1. Create the necessary worker [credentials](/components/saas/clusters/manage-api-clients.md#create-a-client) in Camunda Hub. Give your new client the `Zeebe` and `Secrets` scopes. 2. Add the generated credentials to your `application.properties` in the same directory as your RPA worker executable. 2. **Restart the worker**: If your worker is still running, restart it to apply the new credentials. The RPA worker should now be connected and ready to execute scripts from Zeebe. ## Interact with the process Now that you have integrated your first script, it can be part of a larger BPMN process. The main interaction between the script and your process will be the variables and documents. ### Variables Process variables will be mapped to robot variables automatically. Use the `Camunda` library and the `Set Output Variable` keyword to set return variables. In this example, the input would be the following: ```Robot *** Settings *** Library Camunda *** Tasks *** Log X Log Process variable 'x' is set to ${x} Set Output Variable result We logged x ``` ### Documents :::note Multiple Camunda components can create documents. Visit our [concepts page](/components/document-handling/getting-started.md) to learn how Camunda handles binary data. ::: Documents managed by Camunda can be consumed or created by an RPA script. Use `Download Documents` to resolve a document descriptor to a file and `Upload Documents` to create a document descriptor from a file. The script below downloads a file, appends a line, and uploads the document with the same variable name: ```Robot *** Settings *** Library Camunda Library Camunda.FileSystem *** Tasks *** Log Operation ${path}= Download Documents ${operationLog} Append To File ${path} new Line, appended by RPA script Upload Documents ${path} operationLog ``` ### Handling exceptions You can handle problems in your tasks in two ways: exceptions and errors. See Camunda [best practices](/components/best-practices/development/dealing-with-problems-and-exceptions.md) to understand which strategy is best for your case. #### Incidents If your RPA script runs into an unexpected error during execution, this error (alongside the output) will be reported to Zeebe. If the job retries are exceeded, an [incident](/components/concepts/incidents.md) will be created in [Operate](/components/operate/operate-introduction.md). To ensure your environment is always clean and all open applications are closed, create a cleanup step and tag it as `[Teardown]`. See the [Robot Framework documentation](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#user-keyword-setup-and-teardown) for details on setup and teardown. ``` *** Settings *** Library Camunda Library Camunda.Browser.Selenium *** Tasks *** Main Perform Work [Teardown] Cleanup *** Keywords *** Perform Work Open Browser about:blank Fail Cleanup # Close your application, even when encountering errors Close All Browsers ``` #### BPMN errors If you encounter an error that should be handled as a BPMN error, you can use the `Throw BPMN Error` keyword. Instead of creating an incident, this will create a [BPMN error](/components/best-practices/development/dealing-with-problems-and-exceptions.md#handling-errors-on-the-process-level). :::note A BPMN error cannot be caught in the script. It always stops the script execution and initiates the teardown procedure. ::: ```robot *** Settings *** Library Camunda *** Tasks *** Log Operation Throw BPMN Error MY_ERROR_CODE We encountered a business error [Teardown] Log Teardown is still executed ``` ### Shared script resources Multiple script files are not supported. Each task should be contained within a single script. You can use [pre-run and post-run scripts](/components/rpa/production.md#pre--and-post-run-scripts) for environment setup and cleanup. --- ## Robotic process automation (RPA) Use Robotic process automation (RPA) to integrate legacy systems without APIs into your Camunda processes. ## About RPA RPA allows you to automate manual, repetitive tasks by interacting with legacy mainframe applications, desktop applications such as ERP systems, or websites that do not expose APIs. Camunda uses RPA to integrate legacy applications without accessible APIs into your process orchestration. ## Get started with RPA Camunda RPA uses a standalone RPA worker and Robot Framework–based scripts. This setup lets you build and run RPA scripts that integrate with your existing Camunda components. :::tip Before you begin, review the [RPA known issues](https://github.com/camunda/rpa-worker/discussions/categories/known-issues) to check environment compatibility. ::: To start building RPA scripts, see [Get started with RPA](./getting-started.md). ## Further resources - Learn how to configure your RPA workers for [production use cases](./production.md). - For more details or to leave feedback, see the [RPA product roadmap](https://roadmap.camunda.com/c/212-rpa-1-0). --- ## RPA production setup Use the RPA worker’s production configuration options to run RPA scripts reliably at scale. ## Configuration options ### Transition from a development setup When moving from development to production, consider the following: - **Disable the local sandbox**: If the worker should only accept scripts from Zeebe and not from Desktop Modeler, disable local execution by setting `camunda.rpa.sandbox.enabled=false`. - **Install required third-party tools**: Install any external tools your scripts rely on so the worker can access them. - **Add tags to your workers and scripts**: Tag workers based on their capabilities, such as operating systems or installed applications. See [Labels](#labels) for details. ### Use secrets When running an RPA worker with Camunda SaaS, you can access [connector secrets](/components/connectors/use-connectors/index.md#using-secrets). To do this: 1. [Create client credentials](/components/saas/clusters/manage-api-clients.md#create-a-client) with both the `Orchestration Cluster API` and `Administration API - Resource: Secrets` scopes. 2. Use them in the worker config by adding the secrets endpoint to your `rpa-worker.properties` file: ```properties camunda.rpa.zeebe.secrets.secrets-endpoint=https://cluster-api.cloud.camunda.io ``` In the RPA script, your secrets are stored in the `${secrets}` variable. You can reference a secret like `MY_API_KEY` with `${secrets.MY_API_KEY}`. ### Labels Use tags and labels to differentiate worker capabilities. 1. In the `rpa-worker.properties`, add: ```properties camunda.rpa.zeebe.worker-tags=accounting-system ``` 2. If you also want the worker to work on unlabeled tasks, use: ```properties camunda.rpa.zeebe.worker-tags=default,accounting-system ``` 3. Add a label to your script when configuring the RPA task in your diagram. Labels describe capabilities. If you want your worker to only pick up a specific script, use a unique label on both the worker and the RPA task. If no label is defined, both the task and worker will use the label `default`. ### Pre- and post-run scripts Some scripts require environment setup before they run. To use pre- or post-run scripts: 1. Create and deploy separate RPA scripts. 2. Reference them in the properties panel of the RPA task. :::note The worker removes the job’s working directory after the job completes. ::: ### Timeouts To set timeouts: - **On the RPA task (recommended)**: Set the timeout when configuring the RPA task in your diagram. - **In the worker**: Use the default timeout in `rpa-worker.properties` as a fallback. ### Concurrent jobs To enable concurrent jobs, set `camunda.rpa.zeebe.max-concurrent-jobs` in the worker config. Enable this only if your scripts, such as browser automation, can run safely in parallel. ### Additional libraries To install additional dependencies: 1. Create a `requirements.txt` file listing required packages. 2. Set `camunda.rpa.python.extra-requirements=extra-requirements.txt` in the properties file. 3. Restart the worker to install them. Use [labels](#labels) to ensure scripts run only on compatible workers. For example, to use Playwright: ```txt ## requirements.txt robotframework-browser ``` ```properties ## application.properties camunda.rpa.python.extra-requirements=extra-requirements.txt camunda.rpa.zeebe.worker-tags=default,playwright ``` ## Installation and setup guide An RPA worker is a specialized job worker that runs outside the main Camunda Orchestration Cluster. ### Prerequisites RPA workers are supported on Windows, Linux, macOS, and in Docker for headless automation. #### Hardware requirements The RPA worker runs on bare metal, virtualized, or containerized systems. Use hardware suitable for your automated applications. #### Software requirements The RPA worker is a standalone binary. | Operating system | Required software | Optional software | | ---------------- | ----------------- | ----------------- | | Windows | RPA worker | - | | Linux and macOS | RPA worker | Python 3.12 + pip | #### Network configurations - Ensure connectivity to your Camunda cluster. - Allow internet access if downloading external libraries. ### Installation and configuration This section describes how to set up the RPA host machine. #### Scaling and operation Each machine hosts one RPA worker. For scalable workloads: - Use VMs to quickly spin up new workers. - Use [max-concurrent-jobs](https://github.com/camunda/rpa-worker/?tab=readme-ov-file#configuration-reference) if safe for your use case. #### Setting up a VM Use virtualization to create scalable and repeatable worker deployments. ##### Create a template VM 1. Start with a clean Windows VM. 2. Install and configure the RPA worker. 3. Install all required third-party applications. 4. Test connectivity by running a process in Camunda. 5. Add the RPA worker to run **automatically on startup**. 6. **Configure Windows autologon**. 7. Disable screen saver, sleep, and lock. 8. Set the VM's time zone to match business requirements. 9. Save the configured VM as a **template**. 10. Keep a separate local administrator account and password in escrow for emergency access, and audit all RDP and console logons. ##### Monitoring and scaling To scale using your VM template: 1. **Provision new VMs** from the template. 2. Start the VMs to allow them to connect to Zeebe and begin executing tasks. 3. Use **Operate** and **Optimize** to monitor task execution, wait times, and incidents. If jobs are waiting too long, create additional VMs. Operational tips: 1. **Script handling and versioning**: RPA workers fetch the latest script version automatically. 2. **Labels**: Use them to route tasks to the right worker types. 3. **Maintenance and monitoring**: - Enable OS updates during a maintenance window. - Take regular snapshots. - Monitor health, disk space, and RPA service status. - Surface alerts into your monitoring tools. ## FAQ **My RPA task is never picked up.** Ensure your RPA worker is connected to the correct Zeebe instance and has the correct [label](#labels) configured for the task. **My first script run succeeds, but any subsequent runs fail. I always need to restart the machine.** Your script might not clean up properly. Use [teardown scripts](./getting-started.md#incidents) to close apps after execution. **How do I handle errors and exceptions within RPA scripts?** Use setup and teardown steps in the Robot Framework. Optionally, use `Throw BPMN Error` for BPMN-specific handling. --- ## Auto-updates Camunda 8 SaaS customers can enable auto-updates. When enabled, the cluster is updated once a new patch release is available. You can enable auto-updates when you [create a cluster](/components/saas/clusters/create-cluster.md) or in the **Settings** tab: 1. In Camunda Hub, in the left navigation under **Clusters**, select your cluster. 2. In the **Settings** tab, enable **Automatic cluster patch updates**. Depending on your [role](/components/hub/organization/users-and-roles.md), this option may be disabled. For Camunda 8 SaaS, auto-updates are only for patch releases (x.y.**z**, where **z** is a patch release). Auto-updates are only applied when a cluster is running. If a cluster is sleeping during the update cadence, the auto-update is not applied automatically. However, the update is still available for a manual update in Camunda Hub. Minor updates (x.**y**.z, where **y** is a minor release) are not eligible for auto-updates and require manual steps to initiate. Depending on your [role](/components/hub/organization/users-and-roles.md), you may see that an update is available, but no **Update cluster** button. Contact your organization owner or admin to update your cluster. ## Update availability Camunda Hub manages update path availability separately from the release itself. As a result, a newly released version is not always available as an update target immediately. Camunda also withholds or disables specific update paths when a known issue affects a version, so that clusters do not reach an affected version. An update you expect to see may be unavailable for this reason. If a version you expect is not offered as an update target, [contact Camunda support](/reference/contact.md) rather than assuming the path is permanently unavailable. --- ## Backup and restore overview Camunda Enterprise Camunda 8 SaaS lets you create backups and restore a cluster from a selected backup in Camunda Hub, without opening a support ticket. Backups are designed for disaster recovery, not long-term archival. :::note Related pages - [How to restore a cluster](./how-to-restore.md) - [Restore scenarios](./restore-scenarios.md) - [Restore troubleshooting](./restore-troubleshooting.md) - [Backups](./backups.md) ::: ## What is included in a backup A backup captures a consistent cluster snapshot across Camunda components: - Zeebe data - Operate data - Tasklist data - Optimize data - Exported Zeebe records stored in Elasticsearch ## Restore model Restore operates within the same cluster, organization, and region only. The operation is in-place and overwrites current cluster data. When a restore starts, the cluster enters a restoring state and is unavailable until restore completes. ## Recovery objectives and timing - RPO depends on your backup cadence and the selected backup point. - RTO depends mainly on cluster data volume and backend restore duration. ## Backup retention policy Camunda SaaS retains backups as count-based retention: - Manual backups: up to three recent backups per cluster category - Scheduled backups: up to three backups per schedule category ## Limitations and constraints - Cross-cluster restore is not supported in this release. - Cross-region in-place restore isn't supported. To recover a cluster in another region, see [cross-region cold recovery](./cross-region-cold-recovery.md). - Cross-organization restore is not supported in this release. - Replication factor and node count differences are not blocking constraints for restore. - Backups created before the restore feature was introduced are not eligible for restore. - Cluster endpoints do not change after restore; applications reconnect to the same endpoints. - A new restore request is rejected while another restore is in progress. ## Operations blocked during restore While restore is in progress, mutating cluster operations are blocked. This includes: - Create backup - Start another restore - Create, update, or delete schedules - Rename or delete cluster - Upgrade cluster - Sleep or wake cluster - Change IP allowlists - Change encryption key settings - Change plan or hardware profile These operations remain available: - Delete a stored backup - View backups and schedules - Manage connector secrets - Manage API clients - View alerts ## API access You can also initiate restore through the Administration API. - Endpoint: `POST /api/orgs/:orgId/clusters/:clusterId/backups/:backupId/restore` - Permission: `CreateHotBackups` See the [Administration API reference](/apis-tools/administration-api/administration-api-reference.md). --- ## Backups Camunda Enterprise You can use the backup feature of Camunda 8 SaaS to regularly back up the state of all of its components (Zeebe, Operate, Tasklist, and Optimize) with _zero downtime_. In case of failures that lead to data loss, you can restore a cluster from a backup. A Camunda 8 SaaS backup consists of a data backup of Zeebe, Operate, Tasklist, Optimize, and the backup of exported Zeebe records in Elasticsearch. Since the data of these applications depend on each other, the backup must be consistent across all components. Therefore, the backup of a Camunda 8 cluster is taken as a whole. With backups, you can capture snapshots of your data and applications while they are actively in use, resulting in zero downtime or disruption to your operations. Backups are designed specifically for disaster recovery purposes, and should not be used for archival of process data. :::caution Backups are created and managed on a per-cluster basis. It is important to be aware that deleting a cluster will also delete all associated backups. Exercise caution when deleting clusters to avoid unintended loss of backups. ::: > Your cluster generation needs to be greater or equal to `8.2.4` to support backups. ## Backup location When you create a cluster in Camunda 8 SaaS, you must specify a region for that cluster. You also need to specify where the backups for that cluster will be located: - By default, the backups will be located in the same region as the cluster. - For disaster recovery reasons, you can select a "dual-region" backup location. Backups will be automatically replicated in the secondary region to give you better protection in case the primary region experiences disruption. Dual-region backup is offered at no additional cost. ## Manual backup Manual backups refer to the user-initiated process of creating a consistent snapshot of the state of all system components, including Zeebe, Operate, Tasklist, and Optimize. These backups are managed on a per-cluster basis and are primarily designed for disaster recovery purposes. ### Retention and rate limits To ensure system stability, backup operations are subject to rate limits. Specifically, you can perform a backup operation every 15 minutes. However, users can delete an existing backup to create a new one before the rate limit period ends. The system retains the three most recent completed manual backups per cluster. Manual and scheduled backups are counted separately, so manual backups do not evict scheduled ones. See [scheduled backups](#scheduled-backups) for scheduled backup retention. Failed backup attempts do not count toward the retention count. When a new backup is successful and the retention count is reached, the oldest backup is automatically deleted. ## Scheduled backups Scheduled backups are created periodically (e.g daily, weekly). They are configured to run automatically on the scheduled time. ### Retention A backup schedule retains the last three successful and failed backups. Failed backups are retained to allow further root-cause analysis for backup failures. If a backup fails, it is not retried immediately as the failure can lead to instability. :::note If you require more retained backups or more frequent backups, [contact Camunda support](https://camunda.com/services/support/) to discuss your specific needs. ::: ## Programmatic access The backup operations can be performed programmatically using the Administration API. This provides the flexibility to seamlessly integrate backup-related tasks with your existing systems and automation workflows. For detailed information on using the API, refer to the [Administration API reference](/apis-tools/administration-api/administration-api-reference.md). ## Restore You can restore your cluster from a selected backup directly in Camunda Hub or through the Administration API. - See [backup and restore overview](./backup-restore-overview.md). - See [restore a cluster from backup](./how-to-restore.md). - See [restore scenarios](./restore-scenarios.md). - See [restore troubleshooting](./restore-troubleshooting.md). If the restore action is not available in your organization yet, [contact Camunda support](https://camunda.com/services/support/). --- ## Encryption at rest using external encryption keys Learn how to configure encryption at rest for your Camunda 8 SaaS Orchestration cluster using AWS KMS. ## Prerequisites | Requirement | Description | | --------------------- | --------------------------------------------------------------------------- | | AWS account | Access to an AWS account with AWS KMS permissions. | | AWS KMS permissions | Ability to create and manage AWS KMS keys and attach key policies. | | Cluster region | The AWS KMS key must reside in the same AWS Region as your Camunda cluster. | | Technical familiarity | Some experience with the AWS Management Console, IAM roles, and AWS KMS. | :::warning Important - Deleting or disabling your AWS KMS key will make your cluster and data inaccessible. To understand how Camunda behaves if a key is disabled, deleted, or its policy is changed, see [key state behavior](/components/saas/byok/key-state-behavior.md). - Key management is fully customer-side in AWS KMS. Camunda cannot rotate keys. ::: ## Step 1: Create a Camunda 8 SaaS Orchestration cluster 1. Sign in to the [Camunda Hub](https://console.camunda.io/). 1. In the left navigation, click **Environments**, and then click **Clusters**. 1. Click **Create cluster**. 1. Select an AWS Region for your cluster. 1. Choose **Single region** or **Dual region backup**. - Dual region requires one key per region; keys can be separate. 1. Under **Encryption at rest**, choose **External**. ![external option encryption at rest](./img/external-encryption.png) 1. Click **Create cluster**. After creation, note the **AWS Role ARN** displayed in Camunda Hub for your cluster. The ARN uses the following format: ``` arn:aws:iam:::role/c8-cluster/c8-apps- ``` ## Step 2: Create and configure an AWS KMS key You can create the key either via AWS CLI or manually in the AWS Management Console. ### Option A: Create the key using AWS CLI ![create key using AWS CLI](./img/create-key-cli.png) We provide automated scripts to create the necessary AWS KMS key(s) with the correct policy and permissions. Choose the option that matches your backup configuration. #### Single-region backup Use this script to create a single AWS KMS key in the same Region as the cluster. **What the script does:** - Creates an AWS KMS key with the required policy for Camunda access. - Sets up an alias for easier key management. - Outputs the key ARN to provide to Camunda. **Instructions:** 1. Download [create-byok-kms-key-single-region.sh](https://raw.githubusercontent.com/camunda/camunda-docs/refs/heads/main/docs/components/saas/byok/downloads/create-byok-kms-key-single-region.sh). 2. Modify the following values at the top of the script: - `AWS_ACCESS_KEY_ID` - `AWS_SECRET_ACCESS_KEY` - `AWS_SESSION_TOKEN` (if using temporary credentials) - `YOUR_ACCOUNT_ID` - `ALIAS_NAME` (optional) 3. Make the script executable and run it. 4. Copy the outputted key ARN and provide it to Camunda. #### Dual-region backup Use this script to create a multi-Region primary key in the cluster’s Region and a replica key in the backup Region. **What the script does:** - Creates a multi-Region primary key and replica key. - Applies the correct policies to both keys. - Outputs both key ARNs to provide to Camunda. **Instructions:** 1. Download [create-byok-kms-key-multi-region.sh](https://raw.githubusercontent.com/camunda/camunda-docs/refs/heads/main/docs/components/saas/byok/downloads/create-byok-kms-key-multi-region.sh). 2. Modify the same variables as above. 3. Make the script executable and run it. 4. Copy the two outputted key ARNs and provide them to Camunda. :::note Alternative For dual-region setups, you can also run the single-region script twice—once in the cluster’s Region and once in the backup Region. Make sure to modify the `REGION` variable before creating the second key. ::: ### Option B: Manual key creation in AWS Console ![manual key creation in AWS Console](./img/manual-key-creation.png) #### Single-region backup 1. **Sign in to the AWS Management Console** - Navigate to the AWS KMS service and select the correct Region. 2. **Create a customer managed key** - Click **Create key**. - Choose **Symmetric** and **Encrypt and decrypt** usage. 3. **Add labels** - Add an alias (for example, `camunda-saas-byok`). - Add a description (for example, `AWS KMS key for Camunda SaaS BYOK`). 4. **Define key administrators** - Select IAM users or roles that will administer the key. 5. **Define key usage permissions** - Skip this step; permissions are configured in the next step. 6. **Edit key policy** - Switch to policy view and replace the existing policy with the [provided key policy](https://github.com/camunda/camunda-docs/tree/main/docs/components/saas/byok/downloads/aws-kms-key-policy.json). - Replace `` and `` with your values. 7. **Finish and copy the ARN** - Click **Finish** and copy the key ARN to use in Camunda Hub. #### Dual-region backup You can either create a multi-Region key and replica or create two single-Region keys. ##### Method A: Multi-Region key (recommended) 1. Follow the single-region steps, selecting **Multi-Region key** under **Advanced options**. 2. After creating the primary key in the cluster’s Region, go to **Regional replicas** and click **Create replica key**. 3. Select the Region for the replica and confirm. The Region should be the same as the backup Region. 4. Copy both key ARNs and provide them to Camunda. ##### Method B: Two single-Region keys 1. Create a key in the cluster’s Region using the single-region steps. 2. Repeat the process in the backup Region using a different alias (for example, `camunda-saas-byok-replica`). 3. Provide both key ARNs to Camunda. ### Sample key policy Replace `` with the **AWS Role ARN** from Step 1, and `` with your AWS account ID.
View sample key policy JSON ```json { "Version": "2012-10-17", "Statement": [ { "Sid": "Enable IAM user permissions", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam:::root" }, "Action": "kms:*", "Resource": "*" }, { "Sid": "Allow Camunda tenant IAM role basic key access", "Effect": "Allow", "Principal": { "AWS": "" }, "Action": [ "kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt*", "kms:DescribeKey", "kms:GenerateDataKey*" ], "Resource": "*" }, { "Sid": "Allow Camunda tenant IAM role to create grants for provisioning encrypted EBS volumes", "Effect": "Allow", "Principal": { "AWS": "" }, "Action": ["kms:CreateGrant", "kms:ListGrants", "kms:RevokeGrant"], "Resource": "*" } ] } ```
:::warning Key policy guidance - Don’t restrict the Camunda cluster **role** from required AWS KMS actions. - Key rotation is managed in AWS KMS; Camunda cannot rotate keys. - Revoking access immediately breaks the cluster. ::: ## Step 3: Associate the AWS KMS key with your Camunda cluster 1. Return to **Camunda Hub**, and locate the **AWS KMS key ARN** input field. ![KMS AWS key ARN input field](./img/aws-key-arn.png) - For dual region, two fields will be available—enter the correct key for each Region. 2. Paste your AWS KMS key ARN(s) from Step 2. 3. Confirm and apply. Camunda provisions storage using your key for: - Document handling storage - Backup storage - Orchestration cluster persistent disks - Elasticsearch persistent disks :::note Once a key is applied, it cannot be edited or replaced. To change keys, you must create a new cluster. ::: ## Step 4: Verify encryption and logging - In **Camunda Hub**, check the cluster details **Encryption at rest** tab to confirm the **AWS KMS key ARN** is applied correctly. - In AWS, verify key usage: 1. Navigate to **Customer managed keys**. 2. Select your key and view **Key policy** and **Key usage** tabs. 3. Review **Recent activity** to confirm operations (Encrypt, Decrypt, GenerateDataKey). ### Monitor AWS KMS usage - **AWS CloudTrail** logs all AWS KMS operations. - **Amazon CloudWatch** can trigger alarms for: - Key deletion or disabling - Unauthorized access attempts - Policy or grant modifications - Regularly review logs to detect unauthorized activity. - Optionally, integrate with **Amazon EventBridge** for event-based monitoring and automation. :::warning Monitoring reminder You are responsible for monitoring key usage and access logs within your AWS account. Use AWS CloudTrail, Amazon CloudWatch, and Amazon EventBridge to detect misconfigurations or unauthorized access. ::: ## Additional considerations - **Key rotation**: Enable [automatic rotation](https://docs.aws.amazon.com/kms/latest/developerguide/rotate-keys.html) or rotate manually in AWS KMS. - **Cost**: Using AWS KMS keys incurs storage and management charges in your AWS account. See the [BYOK cost implications](/components/saas/byok/index.md#cost-implications). - **Failure scenarios**: Deleting keys or revoking permissions makes cluster data inaccessible. See [troubleshooting steps](/components/saas/byok/faq-and-troubleshooting.md#troubleshooting-external-encryption-keys). :::note Reference For more information, see the [AWS KMS documentation](https://docs.aws.amazon.com/kms/latest/developerguide/overview.html). ::: --- ## FAQ & troubleshooting Frequently asked questions and troubleshooting guidance for encryption at rest, encryption key types, and external AWS KMS encryption keys in Camunda 8 SaaS. ## General questions ### What is encryption at rest? Encryption at rest protects data on storage media (disks or backups) from unauthorized access. :::note Applies to both Orchestration clusters and Camunda Hub in Camunda 8 SaaS. ::: ### Which encryption options are available? - Provider-managed (default): Cloud provider keys. - Camunda-managed software key: Uses Google KMS at software protection level (FIPS 140-2 Level 1). - Camunda-managed hardware key: Uses Google KMS HSM (FIPS 140-2 Level 3). - External key: Customer-supplied AWS KMS key (AWS only currently). Full comparison: [encryption at rest](/components/saas/encryption-at-rest.md) ### When can I choose the encryption type? Only during cluster creation. It cannot be changed later. ### Is encryption at rest enabled by default? Yes. All clusters use provider-managed encryption by default. ## Camunda-managed keys - Software vs. hardware: Software uses FIPS 140-2 Level 1, hardware uses FIPS 140-2 Level 3. Both support zero downtime rotation. - Backups always use provider-managed keys. ## External encryption keys - Use your own AWS KMS key to encrypt cluster data. You control rotation and revocation and are responsible for monitoring via AWS CloudTrail and Amazon CloudWatch. - Supported on enterprise plans only. - Revoking access immediately blocks cluster access; a new key or restored access is required. - Camunda does not store your key. Setup instructions: [external encryption setup guide](/components/saas/byok/aws-kms-setup.md) ## Other questions - Performance: Minimal impact; handled by AWS KMS. - Per-cluster keys: Supported. - Encryption in transit: TLS enforced. - Cost: AWS KMS key storage and management charges apply in your AWS account. See [cost implications](/components/saas/byok/index.md#cost-implications). ## Troubleshooting external encryption keys | Issue | Possible cause | Resolution | | --------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Cluster cannot access AWS KMS key | Key policy does not grant the Camunda cluster AWS Role access | Update the AWS KMS key policy with the correct AWS Role ARN from Camunda Hub. | | Encryption/decryption errors | Key disabled, deleted, or in wrong Region | Re-enable, restore, or create a new key in the correct AWS Region. | | CloudTrail does not show activity | AWS CloudTrail not enabled or retention too short | Enable AWS CloudTrail in the cluster Region and store logs beyond 90 days. [View CloudTrail events](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/view-cloudtrail-events.html) | | Key rotation issues | Cluster encryption update not supported | Create a new key and associate it with a new cluster. Verify encryption settings before use. | :::note For details on how Camunda responds when an external KMS key becomes disabled, deleted, or misconfigured, see [key state behavior](/components/saas/byok/key-state-behavior.md). ::: :::note Support For persistent issues with key policies, Region, or key status, contact [AWS support](https://docs.aws.amazon.com/awssupport/latest/user/case-management.html). For Camunda-specific cluster provisioning issues, contact [Camunda support](https://camunda.com/services/support-guide/). ::: --- ## Encryption Camunda 8 SaaS encrypts all cluster data at rest. By default, encryption uses cloud provider–managed keys. For stricter compliance or control, you can configure **Bring Your Own Key (BYOK)** with **AWS KMS**, available for clusters hosted in AWS Regions. ## Encryption overview With BYOK, Camunda 8 SaaS uses your customer-managed key stored in your AWS account. You control the key’s lifecycle—creation, access, rotation, and logging—while Camunda handles encryption and decryption operations. | Category | Details | | ----------------- | ------------------------------------------------ | | Availability | AWS-hosted clusters only | | Encrypted storage | Document, backup, Zeebe, and Elasticsearch disks | | Setup | Configure the key during cluster creation | | Rotation | Managed in AWS KMS (not through Camunda) | | Logging | Key usage visible in AWS CloudTrail |
Encryption details for beginners **Encryption at rest** protects stored data from unauthorized access. Camunda 8 SaaS supports three encryption models: | Type | Managed by | Description | | ----------------------- | ---------- | ---------------------------------------------------------- | | Camunda-managed | Camunda | Default encryption, handled automatically | | AWS managed | AWS | Encryption in your account, but AWS controls key lifecycle | | Customer-managed (BYOK) | You | You create, own, and manage the key in your AWS account | Industries such as finance, healthcare, and government often require this level of control for compliance reasons. With BYOK, you maintain visibility through **AWS CloudTrail** and **Amazon CloudWatch**, apply your own rotation policies, and centralize audit logs in your AWS account.
## Responsibilities | Owner | Responsibility | | -------- | -------------------------------------------------------- | | Customer | Create and manage the AWS KMS key | | Customer | Ensure the key and cluster are in the same AWS Region | | Customer | Configure key policies granting Camunda access | | Camunda | Encrypt and decrypt customer data using the provided key | | Camunda | Surface any key-related errors in Camunda Hub | :::warning Key management If your AWS KMS key is disabled, deleted, or permissions are revoked, your cluster and its data become inaccessible. For details on how Camunda responds when an external AWS KMS key becomes disabled, deleted, or misconfigured, see [key state behavior](/components/saas/byok/key-state-behavior.md). ::: ## Cost implications Using external encryption keys with **AWS KMS** incurs costs directly in your AWS account. Camunda does not charge for the feature itself, but you are responsible for AWS KMS key storage, management, and persistence of logs. | Cost type | Description | Notes | | --------------- | ------------------------------------------------------- | ---------------------------------------------------------- | | KMS key storage | Monthly charge for each AWS KMS key | Depends on AWS Region and key type | | CloudTrail logs | Charges for storing and accessing AWS CloudTrail events | Includes encryption/decryption activity by Camunda cluster | :::note Customers are not charged for key usage operations (for example, encrypting or decrypting) as per [AWS KMS pricing](https://aws.amazon.com/kms/pricing/). ::: :::warning Cost responsibility You are responsible for monitoring AWS KMS key storage, management, and log persistence costs. ::: ### Cost optimization tips - Use separate keys only when necessary to avoid extra storage fees. - Review AWS CloudTrail retention settings to balance compliance and storage cost. --- ## Key rotation and audit logging Learn more about key rotation and audit logging when using AWS BYOK with Camunda 8 SaaS. :::note Disclaimer References to Amazon Web Services (AWS) operations may change over time. Camunda does not control AWS features, APIs, or logs. This documentation may become outdated if AWS updates their services. ::: ## Key rotation With BYOK, you manage your own encryption keys in AWS KMS. Camunda cannot rotate customer-managed keys. Only you can rotate keys in AWS KMS. ### Manual key rotation - Rotating a key in AWS KMS does not change the Key ID. - New key material is generated for future encryption; previously encrypted data remains accessible. - Camunda clusters continue using the same Key ID and do not need reconfiguration. - Camunda does not re-encrypt existing data; you are responsible for key management. To use a new AWS KMS key instead of rotating, contact [Camunda support](https://camunda.com/services/support-guide/) to update cluster settings. :::warning Key rotation caution - Do not delete or disable an old key until the cluster uses a replacement. For details on how Camunda responds when an external KMS key becomes disabled, deleted, or misconfigured, see [key state behavior](/components/saas/byok/key-state-behavior.md). - Improper key management may block data access. - Ensure backup storage and persistent volumes remain accessible. - See [KMS key rotation](https://docs.aws.amazon.com/kms/latest/developerguide/rotate-keys.html) and [S3 server-side encryption](https://docs.aws.amazon.com/AmazonS3/latest/userguide/serv-side-encryption.html). ::: ### Best practices | Practice | Description | | ----------------------------- | ------------------------------------------------------------------------- | | Separate keys per environment | Use different keys for production, staging, and development clusters. | | Regular auditing | Periodically review AWS KMS key policies and access logs. | | Monitor rotation | Use Amazon CloudWatch or Amazon EventBridge to track key rotation events. | ## Audit logging AWS KMS integrates with AWS CloudTrail to log all key usage. You are responsible for monitoring and persisting these logs. ### What is logged - Encrypt, Decrypt, GenerateDataKey, and CreateGrant operations - Failed attempts due to denied access :::note Log visibility All AWS KMS operations performed by Camunda appear in AWS CloudTrail in your AWS account. ::: ### Audit best practices 1. Enable **AWS CloudTrail** in the cluster Region and persist logs. 2. Set up **Amazon CloudWatch** or **Amazon EventBridge** alerts for key deletion, disabled keys, or access denied events. 3. Review logs regularly for compliance. 4. Use tools like [CloudTrail Lake](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-lake.html) or [Access Analyzer for KMS](https://docs.aws.amazon.com/kms/latest/developerguide/grants.html) to simplify auditing. 5. Export logs to a centralized SIEM if required. :::warning Audit responsibility You are responsible for monitoring and persisting AWS KMS activity and logs. Camunda does not have access to AWS CloudTrail logs in your account. ::: --- ## Key state behavior Learn how Camunda 8 SaaS responds when your external Amazon KMS encryption key (BYOK) becomes unavailable during cluster startup or runtime. This page applies only to **external customer-managed keys** (AWS KMS). For encryption fundamentals, see the [encryption overview](/components/saas/byok/index.md). :::warning If your external encryption key is disabled, deleted, or its permissions are revoked, your cluster becomes inaccessible and may enter a frozen state. Camunda cannot recover encrypted data if the key is permanently deleted. ::: ## Key state summary The table below provides a high-level overview of how different KMS key states affect cluster startup, operation, and data availability. | Key state | Cluster startup | Cluster runtime | Data accessible? | Recovery possible? | | -------------------------- | ----------------- | --------------------------------------------- | ---------------- | ----------------------------- | | **Enabled** | ✔ Starts normally | ✔ Operates normally | Yes | Not needed | | **Disabled** | ❌ Cannot start | ❌ Freezes: no reads/writes, operations hang | No | ✔ Re-enable key | | **Scheduled for deletion** | ❌ Cannot start | ❌ Same as disabled | No | ✔ Cancel deletion + re-enable | | **Permanently deleted** | ❌ Cannot start | ❌ Cluster remains non-functional permanently | No | ❌ No — encrypted data lost | | **Incorrect key policy** | ❌ Cannot start | ❌ Behaves like disabled key | No | ✔ Fix policy | ## What happens when a key becomes unavailable? When a cluster loses access to its KMS key: - All encryption/decryption requests fail immediately. - Zeebe, Elasticsearch, and backup operations **freeze**. - Camunda Hub may still show the cluster as **Healthy**, even though no work can proceed. - Within ~15 minutes, the **Encryption at rest** panel displays: > **External encryption key is not ready** ### Timeline of effects 1. **Immediate (0–1s):** Key becomes inaccessible. 2. **Seconds:** Storage reads/writes hang. 3. **Backup jobs:** Become stuck “In progress” indefinitely. 4. **Suspend/Resume:** Requests appear accepted but never execute. 5. **Console status:** May incorrectly continue to show “Healthy.” 6. **After re-enabling:** Automatic recovery occurs, but timing depends on reconciliation and exponential backoff. ## Component-level impact | Component/feature | Requires key for | Behavior when key unavailable | | ---------------------- | ------------------------------- | ----------------------------------------- | | **Zeebe brokers** | State storage (encrypted disks) | Execution freezes; no read/write activity | | **Elasticsearch** | Persistent disk encryption | Indexing and queries freeze | | **Backups** | Encrypting/decrypting snapshots | Backup requests hang forever | | **Restore operations** | Decrypting snapshots | Restore cannot proceed | | **Document storage** | Encrypting stored files | Document reads/writes freeze | | **Suspend / resume** | Changing cluster state | Request is logged but not executed | ## Behavior by key lifecycle state ### Disabled key - Cluster cannot start. - If cluster was running: - All operations freeze. - Suspend/resume does not complete. - Backup operations get stuck. - Camunda Hub eventually shows: **External encryption key is not ready** **Recovery:** Re-enable the KMS key. Cluster resumes automatically, but recovery time increases the longer the key was disabled. ### Key scheduled for deletion Scheduling deletion automatically **disables** the key. Behavior is identical to a disabled key. **Recovery:** Cancel deletion → Re-enable the key. ### Permanently deleted key Once the AWS deletion waiting period passes: - The key is irrecoverable. - The cluster becomes permanently unusable. - No encrypted data can be recovered. **Recovery:** Not possible. Create a new cluster. ### Incorrect or missing key policy If the KMS policy does not grant Camunda's AWS Role the required permissions: - Cluster cannot start or becomes frozen. - Behavior mirrors a disabled key. **Recovery:** Update the KMS key policy using the Tenant Role ARN displayed in Hub. ## Error handling and user-visible messages Currently, Camunda displays a single unified message: ```yaml External encryption key is not ready ``` More granular messaging is planned for future iterations. ## Operator responsibilities and best practices ### Customer responsibilities (BYOK model) - Maintain and monitor the key lifecycle in AWS. - Monitor for disable/delete events (CloudWatch & EventBridge). - Ensure policies remain correct. - Understand that deleting or disabling the key freezes the cluster. ### Recommended monitoring Activate: - **CloudWatch alerts** for key disabled, scheduled deletion, access denied - **EventBridge** for policy changes - **CloudTrail** for Encrypt/Decrypt failures and audit logs ## Related documentation - [Encryption overview](/components/saas/byok/index.md) - [External encryption setup guide](/components/saas/byok/aws-kms-setup.md) - [FAQ & troubleshooting](/components/saas/byok/faq-and-troubleshooting.md) - [Key rotation and audit logging](/components/saas/byok/key-rotation-audit-logging.md) --- ## Create cluster backups If your organization works within Camunda's [Enterprise](https://camunda.com/pricing/) plan, you can create a manual and scheduled [backups](/components/saas/backups.md) of your cluster. :::note This page applies to Camunda 8 SaaS. For clusters in Self-Managed, see [clusters in Self-Managed](/components/hub/organization/manage-clusters/index.md). ::: ## Backup limit Only the three most recent successful backups of each type are kept, meaning you can have three manual and three scheduled backups. If you already have three backups of a type, the oldest backup is automatically removed. ## Create a manual backup You can create a manual backup every 15 minutes. To create a manual backup, take the following steps: 1. In the left navigation, click **Environments**, click **Clusters**, and then select a cluster. 1. On the **Backups** tab, click **Create manual backup**. A popup modal will appear with more information about manual backups, including retention. ![cluster-details](./img/cluster-detail-backups.png) 1. Click **Create backup**. ![cluster-details](./img/cluster-detail-backups-manual.png) ## Create a scheduled backup To create a scheduled backup, take the following steps: 1. In the left navigation, click **Environments**, click **Clusters**, and then select a cluster. 1. On the **Backups** tab, click **Set up schedule**. 1. Use the dropdown to schedule the backup frequency. 1. Select the time of day you would like backups to be taken at this frequency. 1. Click **Create schedule**. ![cluster-details](./img/cluster-detail-create-scheduled-backup.png) --- ## Monitor cluster load Use the cluster load metric to view and manage your cluster load and utilization. :::note This page applies to Camunda 8 SaaS. For clusters in Self-Managed, see [clusters in Self-Managed](/components/hub/organization/manage-clusters/index.md). ::: ## About cluster load The cluster load metric provides a high-level overview of how well a cluster is coping with its current workload. - Use this information to check and monitor if a cluster is appropriately sized for its workload. - Cluster load can also be used as an indicator of cluster health. For example, a cluster running at maximum load can indicate poor cluster responsiveness. A general guideline to follow when using the cluster load metric is: - **High cluster load percentage**: The higher the cluster load percentage value, the more likely it is that things will slow down, time out, requests will fail, and so on. For example, if a cluster is continually running at 95% load, this means the cluster is probably overloaded and may not be performing well. - **Low cluster load percentage**: The lower the cluster load percentage value, the more you could increase the cluster workload as the cluster is probably underused. For example, if a cluster load is only 5% then the cluster can probably accept more workload and may be underused. :::note To understand how cluster load is calculated, see [how cluster load is calculated](#load-calculation). ::: ## View cluster load View cluster load in the **Clusters** page: 1. In the left navigation, click **Environments**, and then click **Clusters**. 1. In the table, find the **Cluster load** column. 1. Select a cluster to view detailed cluster load information in the cluster **Overview** tab. ![Example cluster load](./img/cluster-capacity.png) - The current cluster load is shown as a percentage bar at the top of the section. - The chart shows detailed cluster load data for the last 24 hours, 7 days, or 30 days. - Select the time period you want to view data for. - Hover over individual nodes in the chart to view data for a specific time or day. - Click the **Refresh** icon to refresh and update the cluster load data. ## Manage cluster load Cluster load will fluctuate based on incoming user requests and internal processing load: - User requests are those sent directly by an external client. - Internal load refers to all other processing that is **not** directly triggered by a client. For example, timer events, a job being made available after back off, and so on. This means that cluster load could fluctuate throughout the day. For example: - Cluster load might be higher during business hours, but lower overnight when the cluster is unused. - Certain processes can cause a "fan-out" effect, where even though creating a process instance is only a single user request, it could require computation on a very large multi-instance collection, resulting in a high cluster load spike. - You could have a spike in cluster load if a large number of timers are triggered at the same time. ### High cluster load percentage A high cluster load percentage and utilization does not necessarily mean that a cluster needs to be resized, but it could indicate that your cluster is overloaded. > A high cluster load percentage benchmark is relative, but generally **anything above 60%** is considered high. If your cluster load and utilization is too high, but your operations are completing within an acceptable timeframe, requests are successful, and so on, you do not need to take any action. However, if you find yourself experiencing any of the following issues, you might need to investigate and take action: - REST clients are receiving a high number of `429` errors. - gRPC clients are receiving a high number of `RESOURCE_EXHAUSTED` errors. - More and more commands or queries are timing out in your clients. - Web components are slowing down, or showing data that is severely out of date (for example, 1 hour). In this scenario, you should look at reducing the overall load on the cluster. ### Reduce cluster load If your cluster load and utilization is too high, you can help reduce it by: - Scaling down the load. For example, by stopping certain clients. Start with your least critical load, and continue from there. - Check your running process instances for known issues that cause high processing, such as: - Straight-through processing loops, where there are no wait states. For example, a sub process with an error boundary event which loops back to an activity leading into the sub-process. If you have an activity which consistently throws an error, this will result in a subtle infinite loop where the engine is stuck and cannot process anything else. You would have to cancel this instance, or contact support to force cancel it for you. :::important If your cluster load percentage remains high even after attempts to reduce it, you might need to increase your cluster size and scale. See [cluster size](/components/saas/clusters.md#cluster-size) and [sizing your environment](/components/best-practices/architecture/sizing-your-environment.md). ::: ## How cluster load percentage is calculated {#load-calculation} Cluster load percentage is based on the cluster's [flow control configuration](/self-managed/operational-guides/configure-flow-control/configure-flow-control.md). Essentially, if flow control is configured, every partition is assigned a [write rate](/self-managed/operational-guides/configure-flow-control/configure-flow-control.md#exporting-and-write-rate) and a [write rate limit](/self-managed/operational-guides/configure-flow-control/configure-flow-control.md#write-rate-limit). This means a partition's cluster load is defined as: `(writeRate / writeRateLimit)` This gives a value between 0 and 1, which is multiplied by 100 to give the partition load as a percentage. The cluster load percentage is then the average of the load of all partitions in the cluster. :::note - The partition load is _only_ calculated and reported on the current partition leader. - It typically takes about five minutes for cluster load data to update with the latest information. ::: --- ## Configure the audit log Configure the [audit log](../../audit-log/overview.md) in Camunda 8 SaaS. ## About The audit log is an important feature with which you can meet regulatory requirements and maintain operational integrity by accessing a record of operations. These records include who performed the operations, when, and on which entities. The audit log is enabled by default, and the storage it requires may result in increased costs. To mitigate these resource costs, only user operations are tracked by default, not [client](../../zeebe/technical-concepts/architecture.md#clients) operations. In Camunda 8 SaaS, you can change the default behavior in Camunda Hub. You can choose which user and client operations are recorded to fine-tune log thoroughness and resource usage according to your needs. You can also disable the audit log. :::note This feature is only available for SaaS clusters using Camunda 8.9 and above. If you're using Camunda 8 Self-Managed, see the [Self-Managed guide](../../../self-managed/concepts/audit-log/configure.md). ::: ## Configure the audit log 1. In **Camunda Hub**, open **Clusters**. 2. Select an applicable cluster. 3. From the cluster configurations tabs, select **Audit Log**. 4. Select the user operations and client operations to record. 5. Click **Update**. ## Enable or disable the audit log 1. In **Camunda Hub**, open **Clusters**. 2. Select an applicable cluster. 3. From the cluster configurations tabs, select **Audit Log**. 4. Click **Enable audit log** or **Disable audit log**, depending on the state of your cluster. When disabled, new operations are not recorded. Changing this setting doesn't cause the existing audit log data to be immediately purged. Instead, it will be cleaned up according to the secondary storage retention settings. Until then, you can continue to access the data in [Operate](../../operate/userguide/audit-operations.md), [Tasklist](../../tasklist/userguide/audit-task-history.md), [Admin](../../admin/audit-operations.md), and the [Search API](/apis-tools/orchestration-cluster-api-rest/specifications/search-audit-logs.api.mdx). --- ## Connect an external identity provider Register one custom OpenID Connect (OIDC) identity provider for a cluster so your users can sign in with your organization's own identity provider (IdP), alongside Camunda's built-in provider. This setting requires a cluster on Camunda 8.10.1 or any later version. Only organization admins can change it. :::note This configures sign-in to the Orchestration Cluster (Operate, Tasklist, Admin, and the APIs) only. Camunda Hub, Console, and Web Modeler continue to use Camunda's built-in provider. To connect an IdP for your whole organization instead, see [connect to an identity provider](/components/concepts/access-control/connect-to-identity-provider.md). ::: ## Prerequisites - Organization admin access to the cluster in Console. - A cluster on Camunda 8.10.1 or any later version. - An OIDC-compliant identity provider (for example, Microsoft Entra ID, Okta, Ping Identity, or Keycloak) with administrative access to register a new application, and a public (internet-reachable) issuer URL. ## Step 1: Get the cluster's redirect URI 1. In Console, in the left navigation under **Clusters**, select the cluster. 2. On the **Settings** tab, under **External identity provider**, click **Configure** (or **Edit** if a provider is already configured). 3. Copy the **Redirect URI** shown at the top of the dialog. Copy the entire value, including the query string: it identifies this specific cluster to your IdP. Keep this dialog open, or copy the redirect URI somewhere safe. You need it in the next step. ## Step 2: Register an application in your IdP 1. Register a new application/client for this cluster in your IdP. 2. Set the application's redirect URI (sometimes called a callback URL) to the value you copied in [step 1](#step-1-get-the-clusters-redirect-uri). 3. Enable OIDC support and configure the scopes you plan to use (Camunda defaults to `openid profile`). 4. Ensure the client is allowed to access user information. 5. Note the **client ID**, **client secret**, and **issuer URL**. You need these in the next step. ## Step 3: Configure the identity provider in Console Back in the configuration dialog, fill in the following fields: | Field | Description | Required | | :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------- | | **Name (identifier)** | A unique internal identifier for this provider. Lowercase letters and digits, starting with a letter (for example, `acmesso`). `oidc` is reserved for Camunda's built-in provider. You can't change this after you save the configuration. | Yes | | **Display name** | The name shown to users on the cluster's sign-in screen, alongside Camunda's built-in provider (labeled **Camunda**). | Yes | | **Issuer URL** | Your IdP's OIDC issuer URL. Camunda validates this against your provider's discovery document when you save. The issuer must be reachable from the public internet. | Yes | | **Client ID** | The client ID from [step 2](#step-2-register-an-application-in-your-idp). | Yes | | **Client secret** | The client secret from [step 2](#step-2-register-an-application-in-your-idp). Stored securely and never displayed again. When you edit an existing configuration, leave this blank to keep the current secret. | Yes on first configuration | | **Scopes** | Space-separated OIDC scopes to request. Defaults to `openid profile`. | No | | **Username claim** | The access token claim Camunda uses as the username. Defaults to `sub`. | No | | **Client ID claim** | The access token claim Camunda uses to identify machine-to-machine (M2M) clients. See [considerations for Microsoft Entra ID](#considerations-for-microsoft-entra-id) before you set this. | No | | **Audiences** | Space-separated audience (`aud`) values Camunda accepts in tokens from this provider. | No | | **Additional JWKS URLs** | Space-separated HTTPS URLs, for providers that publish more than one JSON Web Key Set (JWKS) endpoint (for example, Ping Identity). | No | Confirm the checkbox acknowledging the lockout risk, then click **Save**. :::warning A misconfigured identity provider can prevent its users from signing in. Camunda's built-in provider remains available as a fallback on the sign-in screen, so test your configuration with a non-critical account before relying on it for all users. ::: ## Enable, edit, or remove the configuration - **Enable** the toggle to let users sign in through the configured provider. Enabling or disabling this setting restarts your cluster; your cluster is briefly unavailable while it restarts. - **Disable** the toggle to stop accepting sign-ins from your provider. Camunda retains the configuration and client secret, so you can re-enable it later without entering the details again. - **Edit** the configuration to change any field. Leave **Client secret** blank to keep the current secret. Saving an edit doesn't immediately restart the cluster: the new secret syncs to the cluster in the background, which can take up to a minute. - **Delete configuration** permanently removes the provider configuration and its client secret, and restarts your cluster. To use the provider again, enter the full configuration and a new client secret. ## Map claims to roles, groups, and authorizations The external identity provider configuration has no groups claim. Instead, use [mapping rules](/components/admin/mapping-rules.md) in the Orchestration Cluster Admin UI to map claims from your provider's access tokens to [roles](/components/admin/role.md), [groups](/components/admin/group.md), [tenants](/components/admin/tenant.md), or [authorizations](/components/concepts/access-control/authorizations.md). Mapping rules become available in Admin as soon as you configure an external identity provider for the cluster. ## Considerations for Microsoft Entra ID Console doesn't let you change the order Camunda uses to resolve a token to a user or to a client. By default, Camunda checks the **Client ID claim** first: if the claim is present in a token, Camunda treats the request as coming from a client; otherwise, it falls back to the **Username claim**. Microsoft Entra ID includes an `azp` (authorized party) claim in both user and application tokens. If you set **Client ID claim** to `azp`, user sign-ins can be misidentified as client authentications. Leave **Client ID claim** blank unless you specifically need machine-to-machine (M2M) authentication through this provider, and if you do set it, use a claim that only appears in your application tokens. ## Known limitations - **Same username across providers resolves to the same principal.** If a user has an account with the same username in your IdP and in Camunda's built-in provider, Operate, Tasklist, and Admin treat them as the same principal for authorization purposes. User-tied setups (one role or authorization per specific username) work as expected. Group- or mapping-rule-based assignments that assume the two providers' users are distinct may not behave as expected. - **Built-in mapping rules remain editable.** Mapping rules that Camunda manages for internal purposes, such as the **Support Access** mapping rule, remain visible and editable by cluster admins once you configure an external identity provider. Changes to these rules either self-restore on the next pod restart, or require Camunda Support to use the built-in provider to sign in and revert them. Avoid editing these rules. - **Issuer URL must be a public, plain HTTPS address.** Camunda requires an issuer URL that's reachable from the public internet, uses `https`, and contains no credentials, query string, or fragment. An internal, VPN-only, or non-routable address, or a URL that doesn't meet this format, is rejected before Camunda attempts to contact your provider. ## Troubleshooting ### "The issuer did not answer at its discovery endpoint. Make sure that the URL is correct, and that the provider is reachable from the internet." Camunda couldn't reach the discovery endpoint derived from your issuer URL. Confirm the issuer URL is correct and that your provider's discovery endpoint is reachable from the internet. ### "The issuer URL does not match the provider's configuration..." The issuer that your provider's discovery document returns doesn't exactly match the issuer URL you entered. A common cause is copying a URL from the wrong tenant or environment. Copy the issuer URL directly from your provider, including its exact path. ### "The provider's configuration is missing authorization_endpoint, token_endpoint, jwks_uri." Your provider's discovery document doesn't include one or more endpoints OIDC discovery requires. Confirm your IdP fully supports OIDC discovery, and that the issuer URL points at the correct tenant or realm. ### The issuer URL is rejected before Camunda even checks for a provider Camunda also rejects an issuer URL that doesn't use `https`, contains credentials, or contains a query string or a fragment. Remove these from the URL and try again. ## Further resources - [Connect Admin to an identity provider](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md): the Self-Managed equivalent configuration, including concepts like redirect URI resolution and principal identification that also apply here. - [Mapping rules](/components/admin/mapping-rules.md) - [Authorizations](/components/concepts/access-control/authorizations.md) - [Connect to an identity provider](/components/concepts/access-control/connect-to-identity-provider.md): organization-level SSO for Camunda Hub, Console, and Web Modeler. --- ## Create a cluster To deploy and run your process, you must create a [cluster](/components/concepts/clusters.md) in Camunda 8. Every cluster you create in SaaS gets one [environment](/components/concepts/environments.md) automatically, and you [assign it to a workspace](../../hub/organization/manage-environments/assign-environments.md) so teams can deploy to it. :::note This page applies to Camunda 8 SaaS. For clusters in Self-Managed, see [clusters in Self-Managed](/components/hub/organization/manage-clusters/index.md). ::: ## Create a cluster To create a cluster, click **Environments** in the left navigation of Camunda Hub, click **Clusters**, and then click **Create cluster**. Then complete the following steps: 1. Name your cluster. 1. Select your [region](/components/saas/regions.md). 1. Select a [cluster type](/components/saas/clusters.md#cluster-type) and [cluster size](/components/saas/clusters.md#cluster-size). 1. Assign a cluster tag that represents the lifecycle phase of the cluster: `dev`, `test`, `stage`, or `prod`. See [tag your cluster](#tag-your-cluster). 1. Select your [encryption at rest protection level](/components/saas/encryption-at-rest.md) (enterprise only). 1. Select a channel and a release. The channel decides which releases you can choose from: - The **Stable** channel provides generally available releases that are ready for most users. See the [stable channel](/reference/announcements-release-notes/release-policy.md#stable-channel). - The **Alpha** channel provides alpha releases, which let you try the upcoming minor release and give feedback before it reaches the stable channel. See the [alpha channel](/reference/announcements-release-notes/release-policy.md#alpha-channel). A release is identified by its [generation](/reference/glossary.md#generation), the set of component versions that the cluster runs. See [generation names](/reference/announcements-release-notes/release-policy.md#generation-names). For the purpose of this guide, we recommend using the **Stable** channel and the latest generation. 1. If you are using a generation of version 8.8 or higher, select if you want to enable [authorization-based access control](/components/concepts/access-control/authorizations.md). 1. Click **Create cluster**. 1. Your cluster will take a few moments to create. Check the status on the **Clusters** page or by clicking into the cluster itself. If you haven't created a cluster yet, the **Clusters** page will be empty. You can start modeling even if the cluster shows a **Creating** status. ![cluster-creating-modal](./img/cluster-creating-modal.png) ## View the created cluster After creating the cluster, click **Environments** in the left navigation, and then click **Clusters** to view the new entry. The cluster is now being set up. During this phase, its state is **Creating**. After one or two minutes, the cluster is ready for use and changes its state to **Healthy**. After the cluster is created, click the cluster name to open the cluster details. ## Tag your cluster A cluster tag represents the lifecycle phase of the cluster. Tag your cluster as `dev`, `test`, `stage`, or `prod`: 1. In the left navigation, click **Environments**, click **Clusters**, and then select your cluster. 1. On the **Overview** tab under **Cluster Details**, click **Modify tag**. Assigning a tag: - Makes it easier for team members to distinguish between the lifecycle phases of your clusters. - Shows the tag on each [environment](/components/concepts/environments.md#environment-tags) of the cluster. - Has no impact on performance and can be changed later in the cluster details section of the cluster overview page. - Disables [authorization-based access control](/components/concepts/access-control/authorizations.md) by default for `dev` and `test` clusters, and enables it for `stage` and `prod` clusters. You can change this setting during and after cluster creation. See [clusters](/components/concepts/clusters.md) for more details. --- ## Create an alert Camunda 8 can notify you when process instances stop with an error. :::note This page applies to Camunda 8 SaaS. For clusters in Self-Managed, see [clusters in Self-Managed](/components/hub/organization/manage-clusters/index.md). ::: ## About alerts There are two forms of notification: - By email to the email address of your user account - By webhook ## Create an alert Create a new alert: 1. In the left navigation, click **Environments**, click **Clusters**, and then select a cluster. 1. On the **Alerts** tab, click **Create an alert**. ![cluster-details](./img/cluster-detail-alerts.png) ![create-alert](./img/cluster-detail-create-alert.png) 1. Choose between **Email** and **Webhook**: - **Email**: Click **Create**. No further information is needed. - **Webhook**: Provide a valid webhook URL that accepts `POST` requests. If your webhook requires [HMAC authentication](https://www.okta.com/identity-101/hmac/), you can specify an HMAC secret. The SHA-256 hash of the request body will then be generated using your HMAC secret, and it is included it in the HTTP header `X-Camunda-Signature-256` each time we send out a webhook alert to your endpoint. You will have one email alert per cluster, but you can create multiple webhook alerts if needed. ## Webhook alerts Webhook alerts contain a JSON body with following structure: ```json { "clusterName": "cluster-name", "clusterId": "88d32bfc-4f8e-4dd3-9ae2-adfee281e223", "operateBaseUrl": "https://console.camunda.io/org/2b3bc239-ad5b-4eef-80e0-6ef5139ed66a/cluster/88d32bfc-4f8e-4dd3-9ae2-adfee281e223/operate", "clusterUrl": "https://console.camunda.io/org/2b3bc239-ad5b-4eef-80e0-6ef5139ed66a/cluster/88d32bfc-4f8e-4dd3-9ae2-adfee281e223", "alerts": [ { "operateUrl": "https://console.camunda.io/org/2b3bc239-ad5b-4eef-80e0-6ef5139ed66a/cluster/88d32bfc-4f8e-4dd3-9ae2-adfee281e223/operate/#/instances/2251799829404548", "processInstanceId": "1234567890123456", "errorMessage": "something went wrong", "errorType": "JOB_NO_RETRIES", "flowNodeId": "node-id", "jobKey": 1234567890123456, "creationTime": "2021-07-22T08:00:00.000+0000", "processName": "process-name", "processVersion": 1, "processVersionTag": "versionTag" } ] } ``` :::warning breaking change The JSON format was changed in 8.8.9. See [release announcements](/reference/announcements-release-notes/880/880-announcements.md#apis--tools#webhook-alerts-json-format) for more information and required actions. ::: --- ## Manage API clients To interact with an orchestration cluster from the outside, every client application must authenticate itself. An **OAuth Flow** is therefore used for authentication: :::note This page applies to Camunda 8 SaaS. For clusters in Self-Managed, see [clusters in Self-Managed](/components/hub/organization/manage-clusters/index.md). ::: ![auth-flow](./img/client-auth.png) The application authenticates itself with OAuth service using `Client Id` and `Client Secret`, OAuth service validates this information and returns an access token, and the application can then use this access token to interact with an orchestration cluster. The client configuration is shown at the bottom of the cluster detail view. Create a new client and all necessary information is displayed. For the `Client Id` and `Client Secret`, a client application can request an access token at the authentication URL (**Steps 1 and 2**). The access token is necessary to interact with an orchestration cluster (**Step 3**). :::note Access tokens have a validity period that can be found in the access token. After this time, a new access token must be requested. ::: ## Create a client To create a client, take the following steps: 1. In Camunda Hub, in the left navigation, click **Environments**, and then click **Clusters**. 2. Select a cluster. 3. Click the **API** tab. 4. Click **Create new client**. ![cluster-details](./img/cluster-detail-clients.png) 5. Provide a **Client Name** and [**Scopes**](#scopes). ![create-client](./img/cluster-details-create-client.png) 6. Click **Create**. 7. Copy or download your client credentials. Ensure you keep the generated client credentials in a safe place. The **client secret** will not be shown again. ![created-client](./img/cluster-details-created-client.png) The downloaded file contains all necessary information to communicate with your Zeebe instance in the future: - `ZEEBE_ADDRESS`: Address where your cluster can be reached. - `ZEEBE_CLIENT_ID` and `ZEEBE_CLIENT_SECRET`: Credentials to request a new access token. - `ZEEBE_AUTHORIZATION_SERVER_URL`: A new token can be requested at this address. - `ZEEBE_TOKEN_AUDIENCE`: The audience for a Zeebe token request. - `CAMUNDA_CLUSTER_ID`: The UUID of the cluster. - `CAMUNDA_CLUSTER_REGION`: The region of the cluster. - `CAMUNDA_CREDENTIALS_SCOPES`: A comma-separated list of the scopes this credential set is valid for. - `CAMUNDA_OAUTH_URL`: A new token can be requested at this address using the credentials. Duplicates the earlier Zeebe-focused variable. Depending on the scopes granted to these client credentials, the following variables may also be present: - `CAMUNDA_TASKLIST_BASE_URL`: The base URL for Tasklist. - `CAMUNDA_OPERATE_BASE_URL`: The base URL for Operate. - `CAMUNDA_OPTIMIZE_BASE_URL`: The base URL for Optimize. ## View connection information To view client connection information: 1. In Camunda Hub, in the left navigation, click **Environments**, and then click **Clusters**. 2. Select a cluster. 3. Click the **API** tab. 4. Select your client. 5. Under **Connection information**, find the following values: - Cluster ID - Region ID - Cluster URL - Tasklist URL - Operate URL - Optimize URL - OAuth URL - Camunda REST API ## Scopes Camunda 8 SaaS supports the following scopes: - **Orchestration Cluster API**: Access the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) and the [Zeebe gRPC API](/apis-tools/zeebe-api/grpc.md). - **Optimize API**: Access the [Optimize REST API](/apis-tools/optimize-api/overview.md). - **Administration API (Secrets resource)**: Access [SaaS-managed secrets](/reference/glossary.md#saas-managed-secret) in a [hybrid setup](/components/connectors/use-connectors-in-hybrid-mode.md). ## 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#official-camunda-clients-and-sdks) (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. --- ## Manage your cluster Learn how to rename, resume, update, resize, or delete your cluster. :::note This page applies to Camunda 8 SaaS. For clusters in Self-Managed, see [clusters in Self-Managed](/components/hub/organization/manage-clusters/index.md). ::: ## View clusters To view your clusters in Camunda Hub, click **Environments** in the left navigation, and then click **Clusters** next to the page title. You only see **Clusters** if you're an organization owner, admin, or DevOps user. The cluster page lists the environments that a cluster hosts. To manage the environments themselves, see [manage environments](../../hub/organization/manage-environments/index.md). ## Rename a cluster You can safely rename a cluster at any time. To rename a cluster: 1. In the left navigation, click **Environments**, click **Clusters**, and then select a cluster. 2. At the top of the view, next to the cluster name, open the vertical ellipsis menu. 3. Click **Rename**. ## Resume a cluster If a cluster is paused, you can resume it during deployment or from the cluster details view. ### Resume during deployment To resume the cluster during deployment: 1. While deploying a [project](/components/hub/workspace/manage-projects/deploy-project.md) or a [file](/components/hub/workspace/modeler/run-or-publish-your-process.md), select a paused cluster. 2. In the **Paused cluster** notification, click **Resume**. ### Resume from cluster details You can resume your paused cluster from Camunda Hub at any time. 1. In the left navigation, click **Environments**, click **Clusters**, and then select the paused cluster. 2. On the **Overview** tab, under **Cluster details**, click **Resume cluster**. ## Update a cluster :::warning Updating a cluster is permanent. Updated clusters cannot be reverted to the previous version. ::: To update a cluster: 1. On the cluster's **Overview** tab, find the **Cluster details** section. 1. If an update is available, you'll see a **Review Update** button in the **Generation** row. Currently, updates do not automatically trigger backups. Camunda recommends [creating a manual backup](./cluster-backups.md#create-a-manual-backup) before updating. ### Minor updates If you update a cluster to another minor version, you cannot immediately update the cluster again until a 24-hour period has elapsed. This ensures all background processes have completed and the cluster is ready for further updates. This does not apply when upgrading between generations of the same minor version. | Example scenario | Time limit applied? | | :------------------------- | :--------------------------------------------------------- | | `8.8 gen22` to `8.9 gen1` | 24 hours required before the cluster can be updated again. | | `8.8 gen22` to `8.8 gen23` | No time limit applied. | :::note Clusters must be healthy before an update can be performed. ::: ### Automated cluster updates You can enable [automated patch updates](/components/saas/auto-updates.md). ## Resize a cluster You can increase or decrease the [cluster size](/components/saas/clusters.md#cluster-size) at any time. For example, increase the cluster size to improve performance and add capacity, or decrease the cluster size to free up reservations for another cluster. 1. In the left navigation, click **Environments**, click **Clusters**, and then select your cluster. 1. On the **Overview** tab, next to the cluster type, click **Resize cluster**. 1. Select the new cluster size from the available sizes. 1. Click **Confirm** to resize the cluster, or **Cancel** to close the modal without resizing the cluster. :::note To increase the cluster size beyond the maximum 4x size, [reach out to Camunda](https://camunda.com/contact-us/). This requires custom sizing and pricing. ::: ## Delete a cluster :::caution Deleting a cluster is **permanent** and cannot be undone. ::: Deleting a cluster also removes the environments it hosts, so they're no longer available to any workspace. You can delete a cluster at any time: 1. In the left navigation, click **Environments**, click **Clusters**, and then select your cluster. 1. On the **Settings** tab, click **Delete**. 1. If you're sure you want to _permanently_ delete the cluster, confirm the deletion. --- ## Manage IP allowlists In Camunda SaaS, Camunda Hub has access to clusters by default, as allowlist assignments for IP addresses used by Camunda Hub are managed automatically. If your organization works within Camunda's [Enterprise](https://camunda.com/pricing/) plan, you can restrict access to clusters with an IP allowlist. :::note This page applies to Camunda 8 SaaS. For clusters in Self-Managed, see [clusters in Self-Managed](/components/hub/organization/manage-clusters/index.md). ::: ## Create an IP allowlist Create an IP allowlist: 1. In the left navigation, click **Environments**, click **Clusters**, and then select a cluster. 1. On the **IP Allowlist** tab, click **Create an entry**. ![cluster-details](./img/cluster-detail-ip-whitelists.png) ![create-alert](./img/cluster-detail-create-ip-whitelist.png) 1. Enter a list of IPs or CIDR blocks separated by commas. 1. Enter an optional description for the allowlist. --- ## Manage connector secrets Create [SaaS-managed secrets](/reference/glossary.md#saas-managed-secret) and reference them in your connectors without exposing sensitive information in your BPMN processes. :::note This page applies to Camunda 8 SaaS. For clusters in Self-Managed, see [clusters in Self-Managed](/components/hub/organization/manage-clusters/index.md), and [connector secrets configuration](/self-managed/components/connectors/connectors-configuration.md). ::: :::warning Secrets on the **Cluster secrets** tab are managed at the cluster level, so ensure you deploy your processes to the cluster that contains the necessary secrets. If you deploy and the secret is missing, [Operate](../../operate/operate-introduction.md) will show an incident. ::: ## Create a secret To manage secrets: 1. In the left navigation, click **Environments**, click **Clusters**, and then select a cluster. 1. On the **Cluster secrets** tab, click **Create new secret**. ![secrets](./img/cluster-detail-secrets.png) 1. Provide a **Key** for your secret that you will use to reference your secret from your connector. 1. Provide the **Value** that will be assigned to the **Key**. ![secrets-create](./img/cluster-detail-secrets-create.png) 1. Click **Create** and view your new secret in the list. :::tip In Self-Managed, review [connector secrets configuration](/self-managed/components/connectors/connectors-configuration.md). ::: ## Use secrets in a workflow Secrets are used inside connector tasks in your BPMN model. Add a connector task, then reference the secret key with a [legacy secret reference](/reference/glossary.md#secret-reference-legacy) in a field that supports secrets. Example for a plain text field (for example, an authorization header value): ```txt Bearer {{secrets.MY_API_KEY}} ``` Example for a FEEL expression (note the double quotes around the placeholder): ```feel = { myHeader: "{{secrets.MY_API_KEY}}" } ``` For more details on where secrets are supported, see the [Connectors guide](/components/connectors/use-connectors/index.md#using-secrets). Now you can reference your secret in any connector as described in the [Connectors guide](/components/connectors/use-connectors/index.md#using-secrets). To reuse a complete set of authentication and connection settings, rather than a single value, see [credentials](/components/hub/organization/credentials/index.md). A credential's sensitive fields reference secrets you create here. ## Reference connector secrets as `camunda.secrets.` In SaaS, you can also reference the connector secrets you create here through centralized secret resolution by using `camunda.secrets.`. You can use these references in input mappings and Connector fields, in addition to the legacy `{{secrets.KEY}}` syntax. To learn how to use these references, see [Secret references in input mappings](/components/concepts/variables.md#secret-references-in-input-mappings). To understand how Camunda resolves a reference before job activation, see [Secret resolution and job activation](/components/concepts/secret-resolution-and-job-activation.md). In SaaS, the secret store is provisioned and managed for you. You don't configure a store type, path, or credentials. Add and update values on the **Cluster secrets** tab; the cluster can resolve them without additional setup. In Self-Managed, an operator must [configure the secret store](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#secrets). A reference name must match `[\p{Alnum}_-]+`. Only keys that contain letters, digits, `_`, and `-` can therefore be referenced as `camunda.secrets.`. A key that contains a period (`.`), such as one used in a file extension, is stored but cannot be referenced this way. A key with a `-` must be backtick-escaped in FEEL (for example, `` =camunda.secrets.`db-password` ``), because a bare `-` is FEEL's minus operator. --- ## Manage cluster settings Manage your cluster settings using authorizations, automatic cluster updates, and user task restrictions, or permanently delete the cluster. :::note This page applies to Camunda 8 SaaS. For clusters in Self-Managed, see [clusters in Self-Managed](/components/hub/organization/manage-clusters/index.md). ::: ## Manage cluster settings To manage your cluster settings: 1. In the left navigation, click **Environments**, click **Clusters**, and then select a cluster. 1. On the **Settings** tab, enable/disable cluster settings as required, or delete the cluster. ![Cluster settings](./img/cluster-settings.png) ## Authorizations You can enable authorizations on a per-cluster basis to control the level of access users and clients have over Orchestration Cluster resources. - Enable this setting to use [authorizations](/components/concepts/access-control/authorizations.md) in the cluster. - Disable this setting if you do not want to use authorizations in the cluster. You can still configure authorizations in the Orchestration Cluster Admin, but they are only applied to the cluster when you enable this setting. :::tip Learn more about [resource-based authorizations](/components/concepts/access-control/authorizations.md). ::: ## External identity provider You can register one custom OpenID Connect (OIDC) identity provider for a cluster, alongside Camunda's built-in provider, so your users can sign in with your organization's own IdP. - Enable this setting after you configure a provider, to let users sign in through it. - Disable this setting to fall back to Camunda's built-in provider only. Your configuration and client secret are retained so you can re-enable it later without entering them again. This setting requires a cluster on Camunda 8.10.1 or any later version. Only organization admins can change it. Enabling or disabling this setting restarts your cluster. For setup steps, a field reference, and known limitations, see [connect an external identity provider](./connect-external-identity-provider.md). ## Multi-tenancy You can enable multi-tenancy checks on a per-cluster basis to enforce tenant-level authorization for Orchestration Cluster resources. :::note This setting applies to Camunda 8 SaaS. In Self-Managed, multi-tenancy checks are not configured through this UI — set them using [configuration properties](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#multi-tenancy) at startup. ::: - Enable this setting to enforce tenant-level authorization checks. Users, groups, and roles not assigned to a tenant lose access to any resources scoped to that tenant. - Disable this setting to allow tenants to be created and principals assigned without enforcing checks. All data maps to the `` tenant. This setting is disabled by default. Only organization admins can change it, and it is available for clusters running generation 8.8 and later. The setting is reversible: disabling it restores the implicit ``-tenant behavior. For details on creating tenants and managing assignments, see [tenant management](/components/admin/tenant.md). :::warning Before you enable multi-tenancy checks, assign all users, groups, and roles that need access to their tenants and to the `` tenant. Once checks are enforced, any principal not assigned to a tenant loses access to the resources scoped to that tenant. ::: ## Secret filter mode You can change the connector secret filter mode on a per-cluster basis to control which secrets outbound and inbound connectors are allowed to resolve. :::note This setting applies to Camunda 8 SaaS. In Self-Managed, configure the mode using [configuration properties](/self-managed/components/connectors/connectors-configuration.md#secret-filter). ::: - **STRICT** (default): a connector can only resolve secrets that are present for a given field in the actually deployed BPMN XML. - **LAX**: for outbound connectors, falls back to `DISABLED` behavior (resolving all secrets) if the BPMN XML cannot be fetched. Inbound connectors behave the same as under `STRICT`, since their allow-list doesn't require a lookup. - **DISABLED**: all secrets resolve freely, matching the behavior before this feature was introduced. This setting is available for clusters running a version where `STRICT` is the shipped default: 8.6.28+, 8.7.25+, 8.8.19+, 8.9.10+, and 8.10.0-alpha5+. Only organization admins can change it. For details on each mode, see [secret filter](/self-managed/components/connectors/connectors-configuration.md#secret-filter). ## Enable business ID uniqueness When enabled, the cluster enforces uniqueness constraints on business identifiers to prevent duplicate process instances. ## Enable app integrations extensions You can allow a cluster to exchange events with App Integrations, such as Camunda for Microsoft Teams or Slack, so App Integrations can deliver task notifications to your chat tool. - Enable this setting to deliver user task notifications to Microsoft Teams or Slack based on your [notification rules](/components/camunda-integrations/app-integrations/notification-rules.md). Notification cards also update as the task is assigned, completed, or canceled. - Disable this setting if you do not want the cluster to exchange events. App Integrations then work with reduced functionality: the cluster delivers no notifications, but you can still use Camunda for Microsoft Teams or Slack to browse tasks, start processes, and act on tasks. This setting is disabled by default. It is available for clusters running generation `8.9 gen13` or later, and organization admins can change it. ## Data filters You can configure data filters on a per-cluster basis to control which process definitions and variables the Optimize exporter processes. :::note This setting applies to Camunda 8 SaaS. On Self-Managed, configure export filters using [Helm values or configuration properties](/self-managed/components/optimize/configuration/optimize-export-filtering.md). ::: Enable the **Enable data filters** toggle to activate filtering. When enabled, the Optimize exporter only processes data matching the configured filters. Enter one pattern per line, or separate values with spaces, in any of the four fields: - **Include process definitions**: process definitions to include, matched by exact `bpmnProcessId`. Leave empty to include all process definitions. - **Exclude process definitions**: process definitions to exclude, matched by exact `bpmnProcessId`. Exclusion takes precedence over inclusion. - **Include variable names**: variable names to include, matched by prefix. For example, entering `business_` includes all variables whose names start with `business_`. Leave empty to include all variables. - **Exclude variable names**: variable names to exclude, matched by prefix. Exclusion takes precedence over inclusion. New SaaS clusters include a default `business_` variable include filter, which limits Optimize to variables whose names start with `business_`. For existing clusters, data filters are disabled by default and can be enabled with one click. No automatic migration occurs. On Self-Managed, no default filter is applied; configure filters manually using [Helm values or configuration properties](/self-managed/components/optimize/configuration/optimize-export-filtering.md). :::warning Filtered records are permanently excluded from Optimize. Optimize cannot import data that was never exported, and dropped records cannot be recovered even if you change the filters later. For details, see [Optimize export filtering](/self-managed/components/optimize/configuration/optimize-export-filtering.md). ::: Clicking **Save filters** restarts the cluster. Your cluster is briefly unavailable while it restarts. For sizing guidance on variable filtering and its impact on Optimize, see [impact of Optimize](/components/best-practices/architecture/sizing-your-environment.md#impact-of-optimize). ## Automatic cluster updates You can set the cluster to automatically update to newer versions of Camunda 8 when they are released. - Enable this setting to automatically update the cluster when a new patch release is available. During an update, the cluster may be unavailable for a short time. You can still manually update the cluster. - Disable this setting if you do not want the cluster to automatically update. You must manually update the cluster. :::tip For more information on updating clusters, see [update your cluster](/components/saas/clusters/manage-cluster.md#update-a-cluster). ::: ## Enforce user task restrictions Starting with Camunda 8.10, this cluster setting is no longer available because user task access restrictions were removed together with Tasklist V1. :::note Use [authorization-based access control](../../concepts/access-control/authorizations.md) and [user task authorization](/components/tasklist/user-task-authorization.md) to control task visibility and operations in current Tasklist deployments. ::: ## Delete this cluster You can _permanently_ delete the selected cluster. :::warning Deleting a cluster is permanent. You cannot reuse a cluster after it has been deleted. ::: --- ## Troubleshoot clusters Review common issues and how to resolve them. :::note This page applies to Camunda 8 SaaS. For clusters in Self-Managed, see [clusters in Self-Managed](/components/hub/organization/manage-clusters/index.md). ::: ## I cannot connect to Zeebe - Check if your [API client](./manage-api-clients.md) has the necessary rights. To interact with Zeebe, the **Scope** `Zeebe` must be set. - Check if your credentials are configured correctly. There is a community-supported CLI tool that allows you to check the status: [`zbctl`](https://www.npmjs.com/package/zbctl). With the command `zbctl status`, you can read the topology. If this command works, the connection can be established. - Check if your cluster is **Healthy**: A Zeebe cluster may be temporarily unavailable. To check if your cluster is healthy, click **Environments** in the left navigation, and then click **Clusters**. Click on the cluster to view its details for a closer view of the status over all components (Zeebe, Operate, Tasklist, Optimize). --- ## SaaS clusters In Camunda 8 SaaS, [creating a cluster](/components/saas/clusters/create-cluster.md) lets you choose the cluster **type** and **size** to meet your organization's availability and scalability needs, and to provide control over cluster performance and availability. This page describes the details of clusters in SaaS. To learn what a cluster is and how it relates to environments and workspaces, see [clusters](/components/concepts/clusters.md). ## Cluster type The cluster type defines the Availability Target for the cluster. You can choose from three different cluster types: - **Basic**: A cluster for non-production use, including experimentation, early development, and basic use cases that don't require a high Availability Target. - **Standard**: A production-ready cluster with a higher Availability Target. - **Advanced**: A production-ready cluster with the highest Availability Target. ### Cluster availability and uptime | Type | Basic | Standard | Advanced | | :----------------------------------------------------------------- | :------------------------------------------------------------------------------------- | :------------------------------------------------------------ | :--------------------------------------------------------------- | | Usage | Non-production use, including experimentation, early development, and basic use cases. | A production-ready cluster with a higher Availability Target. | A production-ready cluster with the highest Availability Target. | | Availability Target(Orchestration Cluster\*) | 99% | 99.5% | 99.9% | * Orchestration Cluster means the core components for process automation and orchestration: Zeebe, Operate, Tasklist, Identity, and the Orchestration Cluster APIs (or any successor or renamed equivalent as specified in the Documentation from time to time). :::info See the terms of your agreement with Camunda for the definitions of Availability Target, Downtime, and Excluded Downtime. ::: ## Recovery objectives by outage type Camunda 8 SaaS sets Recovery Time Objective (RTO) and Recovery Point Objective (RPO) targets for each type of outage, based on how much infrastructure the outage affects. They don't vary by cluster type. These objectives, including those in the **Recovery objectives summary** table and the RTO/RPO assessments for each type of outage below, describe expected behavior on a best-effort basis. They aren't contractual commitments and are separate from the Availability Target. ### Availability Target versus recovery objectives The Availability Target and recovery objectives measure different things. Keep them separate when you plan for disaster recovery. - **The Availability Target** is a contractual commitment. It measures the percentage of minutes in a calendar month during which the Orchestration Cluster is available, based on connection checks by Camunda's monitoring system, as defined in your agreement with Camunda. It doesn't describe what happens during or immediately after an outage. - **RTO (Recovery Time Objective)** measures how long a failure disrupts service, from the moment it starts affecting your cluster until the cluster is fully functional again. For outages that need manual recovery, RTO also includes the time to detect, escalate, and diagnose the problem. - **RPO (Recovery Point Objective)** measures how much data you can lose when that failure happens, expressed as the time between the last recoverable point and the failure. A high Availability Target doesn't imply a fast recovery or minimal data loss during a major infrastructure failure. The Availability Target tells you how rarely a failure disrupts your cluster; RTO and RPO tell you how well the cluster recovers when a major failure does happen. ### Recovery objectives summary | Outage | RPO | RTO | Recovery | Requirement | | :------------------------------------ | :----------------------------- | :------------------------------------------- | :---------------------------------- | :----------------------------------------------------- | | Node | Zero | Near zero | Automatic | None | | Availability zone | Zero | Near zero | Automatic | None | | Region | Time since the restored backup | Depends on data volume and reconnection time | Manual cold recovery that you start | Dual-region backup location in a supported region pair | | Cloud provider or third-party service | Not defined | Not defined | Depends on the provider's recovery | Not available | | Platform or cluster incident | Typically zero | Depends on the incident | Camunda incident response | None | Near zero means service resumes automatically, typically within seconds, without a backup restore. Requests in progress during that time can fail, so configure your clients and job workers to retry them, for example with exponential backoff. Your application's overall recovery time also depends on your own job workers and clients being able to reach the cluster and continue processing. ### Node failure A node failure is the loss of a single Zeebe broker or [secondary storage](/reference/glossary.md#secondary-storage) node within a cluster. Camunda SaaS clusters replicate each partition across multiple brokers, typically one broker per availability zone. When a single broker fails, the remaining brokers take over its partitions automatically, without data loss. Secondary storage is managed by Camunda and also replicated, so losing a single secondary storage node doesn't lose data. To learn more, see [clustering](/components/zeebe/technical-concepts/clustering.md). **RTO/RPO assessment:** RPO is zero, because every committed record is already stored on the remaining nodes. RTO is near zero, and clients recover through their standard retry mechanisms. **Your responsibilities:** Configure your clients and job workers to retry failed requests. Run job workers across multiple availability zones so that the same outage doesn't disrupt them. ### Availability zone failure An availability zone (AZ) failure is the loss of an entire zone in the cluster's region, taking every broker hosted there down at once. All cluster types keep three copies of your data, one in each of three availability zones, so losing one zone leaves the cluster fully operational. **RTO/RPO assessment:** RPO is zero, because the remaining zones already hold every committed record. RTO is near zero. Failover is automatic, doesn't require a restore, and clients recover through their standard retry mechanisms. While the zone is unavailable, the cluster runs with reduced redundancy. A second failure in another zone before recovery can make the cluster unavailable. **Your responsibilities:** Configure your clients and job workers to retry failed requests. Run job workers across multiple availability zones so that the same outage doesn't disrupt them. ### Region failure A region failure is the loss of every availability zone in the cluster's region at once, for example, during a regional cloud provider outage. Camunda SaaS clusters run in a [single region](/components/saas/regions.md). By default, backups are stored in the same region as the cluster. If you select a [dual-region backup location](/components/saas/backups.md#backup-location), backups are also replicated to the secondary backups region. For [supported region pairs](/components/saas/cross-region-cold-recovery.md#supported-region-pairs), you can then use [cross-region cold recovery](/components/saas/cross-region-cold-recovery.md) to recover the cluster in the secondary region. **RTO/RPO assessment:** Recovery from a region failure is a manual cold recovery that you start in Console or the API. Camunda creates a new cluster in the recovery region and restores the backup you select. Without a dual-region backup location in a supported region pair, recovery to another region isn't possible. - **RPO** is the time between the backup you restore and the failure. It depends on your backup schedule, on backups being created and replicated successfully, and on the restore point you select. A 15-minute backup schedule doesn't guarantee a 15-minute RPO. - **RTO** is the time needed to detect the outage, start failover, restore the backup, and reconnect your applications. The more data your cluster holds, the longer the restore takes, which increases the RTO. The recovered cluster has new endpoints, so you must update your DNS or client configuration. Preparing network infrastructure in the recovery region in advance reduces the RTO. **Your responsibilities:** Choose a dual-region backup location in a supported region pair when you create the cluster, and keep your backup schedule healthy. Prepare network infrastructure in the recovery region in advance. After failover, point your clients and job workers at the recovered cluster, and don't use the original cluster again. For the full procedure, see [fail over](/components/saas/cross-region-cold-recovery.md#fail-over). ### Cloud provider or third-party service outage An outage at Camunda's cloud provider, network and edge providers, or other third-party services can make clusters unreachable, or prevent Camunda from starting or replacing infrastructure, until the provider recovers. Camunda SaaS clusters run on a single cloud provider and can't fail over to another provider. This differs from a region failure. If an outage affects only your cluster's region and your secondary backups region is still available, you can recover through cross-region cold recovery. If the outage also affects the secondary backups region, or prevents Camunda from creating infrastructure there, the cluster stays unavailable until the provider recovers. **RTO/RPO assessment:** Clusters stay unavailable until the provider recovers, so Camunda doesn't set an RTO or RPO for this type of outage. Camunda follows its incident response process and publishes updates on the [Camunda status page](/components/saas/status.md). ### Platform or cluster incident Some incidents originate in Camunda's own platform or in a single cluster. Examples include platform configuration issues that affect network access, software defects, and cluster components in an inconsistent state. **RTO/RPO assessment:** RTO depends on how quickly the incident is detected, escalated, diagnosed, and resolved, so Camunda doesn't set a fixed RTO. RPO is typically zero, because these incidents usually affect availability, not stored data. ## Cluster size The cluster size defines the cluster performance and capacity. After you have chosen your cluster type, choose the cluster size that best meets your cluster environment requirements. To learn more about choosing your cluster size, see [sizing your environment](/components/best-practices/architecture/sizing-your-environment.md). - You can choose from four cluster sizes: 1x, 2x, 3x, and 4x. - Larger cluster sizes include increased performance and capacity, allowing you to serve more workload. - Increased usage such as higher throughput or longer data retention requires a larger cluster size. - Each size increase uses one of your available cluster reservations. For example, purchasing two Advanced [hardware package](/components/saas/organization/manage-plan/update-billing-reservations.md) reservations for your production cluster allows you to configure two clusters of size 1x, or one cluster of size 2x. - You can change the cluster size at any time. See [resize a cluster](/components/saas/clusters/manage-cluster.md#resize-a-cluster). :::note To increase the cluster size beyond the maximum 4x size, [reach out to Camunda](https://camunda.com/contact-us/). This requires custom sizing and pricing. ::: ## Free Trial clusters Free Trial clusters have the same functionality as a production cluster, but are of a Basic type and 1x size, and only available during your trial period. You cannot convert a Free Trial cluster to a different type of cluster. Once you sign up for a Free Trial, you are able to create one production cluster for the duration of your trial. When your Free Trial plan expires, you are automatically transferred to the Free plan. This plan allows you to model BPMN and DMN collaboratively, but does not support execution of your models. Any cluster created during your trial is deleted, and you cannot create new clusters. ### Auto-pause Free Trial clusters are automatically paused after a period of inactivity. Auto-pause occurs regardless of cluster usage. You can resume a paused cluster at any time, which typically takes five to ten minutes to complete. See [resume a cluster](/components/saas/clusters/manage-cluster.md#resume-a-cluster). - Clusters tagged as `dev` (or untagged) auto-pause eight hours after the cluster is created or resumed from a paused state. - Clusters auto-pause if there is no cluster activity for 48 hours. - Cluster disk space is cleared when a trial cluster is paused. - You will need to redeploy processes to the cluster once it is resumed from a paused state. - Cluster configuration settings (for example, API Clients, connector secrets, and IP allowlists) are saved so you can easily resume a cluster. :::tip To prevent auto-pause, [upgrade your Free Trial plan](https://camunda.com/pricing/) to an Enterprise plan. ::: --- ## Cross-region cold recovery ## About Cross-region cold recovery creates a new Orchestration Cluster in a secondary region and restores selected backup data after a primary-region outage. A warm standby cluster is not running before the outage. Cross-region cold recovery is generally available starting from Camunda 8.7 or later. It is supported for AWS and GCP clusters, including AWS clusters that use a customer-managed [Bring Your Own Key (BYOK)](/components/saas/byok/index.md) configuration. ## Supported region pairs Cross-region cold recovery is available only for specific region pairs marked **Failover supported** when you select your region and backup location in Console. Each pair supports failover in both directions. | Cloud provider | Region pair | | -------------- | ------------------------------------------------------------------------------- | | AWS | US East (Ohio) (us-east-2) and US East (N. Virginia) (us-east-1) | | AWS | US East (Ohio) (us-east-2) and US West (Oregon) (us-west-2) | | GCP | Council Bluffs, Iowa (us-central1) and Moncks Corner, South Carolina (us-east1) | ## Restored data The failover flow restores backup buckets only. Document buckets follow the standard [backup and restore](./backup-restore-overview.md) process and are not part of the cross-region failover. ## Prepare for recovery Before you can use cross-region cold recovery, ensure the following prerequisites are met: - Dual-region backup is enabled when you create the cluster. - The backup schedule is running and healthy. Backup interval will determine your expected RPO. - Before starting failback, wait until Console indicates that backup synchronization is complete and failback is ready. - Prepare the VPC infrastructure required to connect to a cluster in the recovery region. Pre-provisioning the required endpoints and security groups can reduce recovery time. - After failover, re-establish private connectivity to the recovered cluster by creating or switching the regional VPC endpoint. This is the customer's responsibility. ## Fail over Cross-region cold recovery is primarily designed for recovering from a primary-region outage, but you can also start failover proactively outside of a disaster recovery scenario, for example to move your cluster to a different region. Both cases follow the same cold recovery process, so the trade-offs described in [Limitations](#limitations) still apply, such as data loss since your last backup and the lack of an active-active or warm-standby configuration. Follow these steps to fail over to your recovery region, restore a cluster from an available backup, and redirect client traffic to it: 1. Start failover in Console or API. 2. Select the backup to restore from those available in the recovery region. 3. Camunda creates a replacement cluster in the recovery region and prepares it to restore the selected backup. 4. (Optional) If your AWS cluster uses BYOK, configure the KMS key policies for the failover region. See [Restore external encryption](#restore-external-encryption). 5. Camunda copies and verifies the selected backup data before restore proceeds. You don't need to manually suspend or resume the target cluster during the restore process. 6. (Optional) If you use private connectivity on an AWS cluster, re-establish it to the recovered cluster. Use the endpoint service name shown in Console to create or switch your VPC endpoint. 7. Update your customer-managed DNS or routing configuration to direct client traffic to the recovered cluster. 8. Verify that your applications can connect to the recovered cluster and that requests are reaching it. ## Handle the original region after failover After failover, use only the recovered cluster. The original cluster may still exist while the original region is unavailable. Even after the original region becomes reachable again, you cannot resume the original cluster or route traffic to it. Camunda SaaS automatically attempts to suspend the original cluster when the region is reachable. This is a best-effort operation, so suspension might not happen immediately if the region or cluster remains unavailable. Console deletes the original cluster after a 30-day retention period. :::warning Split-brain risk Do not run both clusters at the same time. The original cluster may contain stale data and does not include changes made in the recovered cluster. Using it after failover can cause conflicting writes and data loss. ::: ## Restore private connectivity This section applies only to AWS clusters that use private connectivity. If you don't use private connectivity, or your cluster is on GCP, skip this section. You are responsible for establishing private connectivity to the recovered cluster. The secondary cluster runs in a different region with different VPC infrastructure. ### Prepare connectivity To minimize your recovery time objective (RTO), pre-provision VPC infrastructure in the secondary region: - VPC and security groups - Private DNS configuration, if you're using Amazon Route 53 failover - Any firewall rules or network policies ### Reconnect after failover After failover: 1. Create or configure the VPC endpoint to use the recovered cluster's new endpoint service name. 2. Update DNS records or Amazon Route 53 failover rules to point to the new endpoint. 3. Test client connectivity before resuming application traffic. Camunda does not create, manage, or modify customer VPC infrastructure. ## Restore external encryption This section applies only to AWS clusters that use a customer-managed [Bring Your Own Key (BYOK)](/components/saas/byok/index.md) configuration. If you don't use BYOK, or your cluster is on GCP, skip this section. You are responsible for configuring the KMS key policies for the recovered cluster. ### Configure the failover cluster's key 1. Open the failover cluster's **Encryption at rest** tab in Console. 2. Follow the instructions shown there to update the AWS KMS key policy for the failover region. The failover cluster remains in a waiting state until this configuration is complete. ### Restore replication to the original region If the original region is still unavailable, Console shows a warning that backup replication to that region isn't working. Update the KMS key policy for the original region once it's reachable to restore replication. Camunda does not create, manage, or modify your AWS KMS keys or policies. ## Recovery objectives You can configure a 15-minute backup schedule for the organization, but this schedule doesn't guarantee a 15-minute recovery point objective (RPO). The actual RPO depends on successful backup creation, completed replication, and the consistent restore point you select. ## After recovery - Verify that the recovered cluster is serving traffic correctly. - Treat the recovered cluster's primary backup bucket as the source for new backups while it is active. - Before starting failback, wait for the backup bucket in the original region to be created and fully synchronized with the active cluster's primary backup bucket. - Start failback only after the system reports that backup storage is ready. ## Limitations - Cluster recovery from a backup restores only the Orchestration cluster state. It does not restore Intelligent Document Processing objects. - Private connectivity must be re-established by the customer and is currently supported for AWS clusters only. - Recovery is cold and creates a new cluster. It is not an active-active or warm-standby configuration. - Failover and failback depend on backup replication and may be affected by replication lag. - Failover requires at least one available backup in the recovery region. If none is available, failover cannot proceed. - Bring Your Own Key (BYOK) failover is supported for AWS clusters only. --- ## Data locations Learn more about where your Camunda 8 SaaS data is located and how data is handled. ## About Camunda 8 SaaS data locations - Camunda Hub is hosted in the EU. - You can create Camunda Orchestration Clusters on AWS (Amazon Web Services) or GCP (Google Cloud Platform) in the [region](regions.md) of your choice. :::caution Processing personal data Unless specifically mentioned, Camunda does not process personal data on behalf of its customers. It is the responsibility of you as the customer to decide whether to use Camunda for processing personal data. ::: ## Alerts Camunda 8 [Alerts](/components/saas/clusters/manage-alerts.md) can notify you when process instances stop with an error. | Host location | Data handled | Personal data processing | | :---------------- | :---------------------------------------------------- | :----------------------- | | Germany, EU (AWS) | Route alerts containing administrative metadata only. | N/A | :::note optional Camunda 8 Alerts are optional. This information only applies if you use Alerts. ::: ## Connector secrets (credentials) This only applies if you want to create [connector secrets](/components/saas/clusters/manage-secrets.md) and are using the Camunda-hosted connector version. Connector secrets are configured and referenced via Camunda Hub. - If you want to control the location where the secrets are stored, you can also [host your own connector runtime](/components/connectors/custom-built-connectors/host-custom-connector.md). - If you want to use your own secret management solution, see [Secrets (Self‑Managed)](/self-managed/components/connectors/connectors-configuration.md). | Host location | Data handled | Personal data processing | | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | GCP Secret Manager, [replicated globally](https://cloud.google.com/secret-manager/docs/secret-manager-secrets-comparison) for high availability.From December 2025: Connector secrets for Camunda Orchestration Clusters >= 8.7 created in an AWS region will be stored inside AWS Secret Manager, in the same AWS region as the Camunda Orchestration Cluster only. | Stores credentials required by connectors (API keys, tokens, passwords), not business process data. | Not intended for personal data processing.If you embed personal data in connector secrets, note the global replication of data. You should review if your company has specific data residency requirements, and use connector secrets accordingly. | :::note optional Connector secrets are optional. This information only applies if you use connector secrets. ::: ## Camunda Hub [Camunda Hub](/components/hub/index.md) is used for cluster and organization management as well as hosting the browser-based modeler. | Host location | Data handled | Personal data processing | | :----------------------- | :------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Germany, EU (AWS) | Stores administrative metadata and settings. | Limited to account/authentication data to access Camunda Platform SaaS. It does not include personal data in scope of [Data Processing Agreements](https://legal.camunda.com/). | | Germany, EU (AWS) | Stores process models (diagrams/designs). | Not intended for personal data processing. | You can use Camunda Hub and/or the [Desktop Modeler](/components/modeler/desktop-modeler/index.md) with Camunda Self‑Managed if you want to control the hosting location. ## Admin [Admin](/components/admin/admin-introduction.md) is managed by Camunda for SaaS. Single Sign-on (SSO) is supported. | Host location | Data handled | Personal data processing | | :---------------------------------- | :------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Germany, EU (Auth0 as part of Okta) | User accounts and authentication metadata. Separate from process payloads. | Limited to account/authentication data by design to access the Camunda Platform SaaS. It does not include personal data in scope of [Data Processing Agreements](https://legal.camunda.com/). | :::info Learn more - [Admin](/components/admin/admin-introduction.md) - [Connect to an identity provider](/components/saas/organization/external-sso.md) ::: ## App Integrations App Integrations enable connection between Camunda 8 clusters and your productivity tools, currently Microsoft Teams and Slack. | Host location | Data handled | Personal data processing | | :---------------- | :------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Germany, EU (AWS) | User accounts and authentication metadata. Separate from process payloads. | Limited to account/authentication data to access the Camunda Platform SaaS. Dependent on the data you send to Camunda over App Integrations. We do not expect that personal data in scope of [Data Processing Agreements](https://legal.camunda.com/) is included. | :::note optional App Integrations are optional. This information only applies if you use App Integrations. ::: ## Orchestration Clusters and backups You can choose a [region](regions.md) in **GCP** or **AWS**. Each [Orchestration Cluster](/components/orchestration-cluster.md) uses a dedicated infrastructure. | Host location | Data handled | Personal data processing | | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | | Orchestration Clusters are created on AWS or GCP, in one of the available [regions](regions.md).Backups are single‑region by default, in the same region as the Orchestration Cluster.Optionally, you can replicate backups in a [secondary region](regions.md), depending on the chosen primary region. | All data uploaded to Camunda in Orchestration Clusters during customers’ process orchestration (used in Zeebe, Operate, Tasklist, Optimize and Connectors). | Dependent on the data you sent to Camunda in the Orchestration Clusters. Camunda does not process personal data by default. | :::info Learn More - [Backups](/components/saas/backups.md) - [Cluster backups](/components/saas/clusters/cluster-backups.md) ::: ## REST connector (traffic routing) For security reasons, REST API requests made by the [REST connector](/components/connectors/protocol/rest.md) are all routed through a dedicated HTTPS proxy hosted by Camunda in the EU. The REST connector uses either an AWS or a GCP-hosted HTTPS proxy, depending on your chosen Orchestration Cluster cloud provider. As a customer, you can either use the Camunda‑hosted REST connector, or [host your own connector runtime](/components/connectors/custom-built-connectors/host-custom-connector.md) (hybrid mode) to keep traffic in your chosen location. | Host location | Data handled | Personal data processing | | :------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------ | | Belgium, EU (GCP clusters)Germany, EU (AWS clusters) | All data uploaded to Camunda in an Orchestration Cluster and processed specifically by the REST API Connector.Use of the REST API Connector is optional, and depends on the customers’ workflows. | Dependent on the data you send to Camunda in an Orchestration Cluster. Camunda does not process personal data by default. | :::note optional Use of the REST connector is optional. This information only applies if you use the REST connector. ::: --- ## Data retention In Camunda 8 SaaS, the following data retention strategies are implemented. This is necessary as the amount of data can grow significantly overtime. These settings are a balance between performance and usability. ## Default retention time of each application The following time-to-live settings are configured in SaaS for each application. These are the defaults for our production clusters in the Enterprise plan. - **Orchestration Cluster**: 30 days - **Optimize**: 180 days - **Zeebe**: 7 days - **Usage metrics**: 730 days (2 years) If there are specific requirements for your use-case, [reach out to us](/reference/contact.md) to discuss your data retention needs under an Enterprise plan. ## Additional information Visit the [Self-Managed documentation](/self-managed/components/orchestration-cluster/core-settings/concepts/data-retention.md), which describes these data retention concepts in more detail. --- ## Encryption at rest Encryption at rest protects stored data by making it unreadable without the appropriate decryption keys. By default, Camunda 8 SaaS uses a provider-managed encryption key with [Google Cloud Platform (GCP) encryption](https://cloud.google.com/docs/security/encryption/default-encryption). Enterprise customers can choose: - Camunda-managed software or hardware keys (Google KMS) - Bring Your Own Key (BYOK) on AWS for full control Key points: - Encryption type is selected only when [creating a cluster](/components/saas/clusters/create-cluster.md) - Each cluster can have its own key - The key applies to all workloads and persists across updates - View encryption details on the cluster's **Overview** tab under **Cluster Details** :::note Backups use default provider GCP encryption. ::: ## Encryption types | Type | Managed by | Notes | | ------------------ | ---------- | ------------------------------------------------------------------------------------------------------------------------------ | | Provider (default) | Google | FIPS 140-2 validated encryption module (certificate 4407) | | Software key | Camunda | Google KMS software protection; operations in software; FIPS 140-2 Level 1; zero downtime rotation | | Hardware key | Camunda | Google KMS hardware (HSM) protection; FIPS 140-2 Level 3; operations in HSM; zero downtime rotation | | BYOK | Customer | AWS KMS key; FIPS 140-3 Security Level 3 certification; full control over lifecycle, rotation, and revocation; enterprise only | ## Provider encryption key Default option, managed by Google. Uses FIPS 140-2 validated module. :::info Learn more about [Google default encryption](https://cloud.google.com/docs/security/encryption/default-encryption) ::: ## Camunda-managed keys ### Software key - Managed by Camunda using Google KMS - FIPS 140-2 Level 1 - Operations in software - Zero downtime rotation ### Hardware key - Managed by Camunda using Google KMS - FIPS 140-2 Level 3 - Operations in HSM - Zero downtime rotation ## Bring Your Own Key (BYOK) Enterprise customers on AWS can use their own AWS KMS key. - You manage the key lifecycle, including rotation and revocation - Camunda never stores the key; access occurs via standard AWS KMS integrations - Zero downtime rotation supported See the [BYOK setup guide](/components/saas/byok/aws-kms-setup.md) for configuration. --- ## Help Center Camunda 8 SaaS only Camunda 8 SaaS offers a Help Center to all users, where you can access additional documentation, step through various use cases in Camunda, share your feedback, and more. ## Visiting the Help Center You can visit the Help Center directly from your Camunda 8 account. From Camunda Hub, click the **Open Help Center** icon in the top right corner of the navigation bar: ![question mark icon to open the help center](./img/open-help-center.png) This launches a modal for the Help Center, where you are taken to the **Your recommendations** tab. ## New user survey At the top of **Your recommendations**, you may note an info box prompting you to complete the new user survey for a list of custom recommendations for additional learning. Click **Resume survey** if you would like custom recommendations. Here, you may add additional details about your profile, goals, use case, and experience level. Fill out these details and click **Next**. Your recommendations will automatically update based on your responses. Click **Retake the survey** at any time for a new list of recommendations. ## Using the Help Center ### Your recommendations As mentioned, the **Your recommendations** tab populates a list of recommended resources and next steps based on your sign-up or in-app onboarding survey. These resources may link to additional material in Camunda Hub, documentation, Camunda Academy, or blog. ### Automation project guide The **Automation project guide** tab provides a set of recommended documentation pages for each phase of an automation project with Camunda SaaS. For example, a set of pages for getting started, for designing and implementing, for testing, and for going live or improving your process. ### Use case guide The **Use case guide** tab provides a few getting started templates, such as process modeling or human task orchestration. These tutorials link to tutorials in Camunda Hub, which may take 10-15 minutes each. ### Camunda Academy The **Camunda Academy** tab introduces the Camunda Academy, where you can step through free on-demand and instructor-led courses for additional learning. For example, explore bite-sized videos for tasks like creating clusters, gain practical skills to BPMN, DMN, and Camunda training, or experience hands-on learning and personalized guidance in a virtual classroom. ### Share your feedback The **Share your feedback** tab offers a quick way to share product feedback, suggestions, or bug reports. This information travels directly to Camunda's Product Management team. You may also click the **Documentation** or **Support forum** links in the bottom left corner of the modal if you would like to be taken to either of these resources at any time. :::note Worried about losing your place in Camunda Hub? Clicking any links in the Help Center will automatically open a new tab for additional learning, and your current tab will remain as-is within the product. ::: --- ## Restore a cluster from backup Camunda Enterprise Use this guide to restore a SaaS cluster from an existing backup. :::note Related pages - [Backup and restore overview](./backup-restore-overview.md) - [Restore scenarios](./restore-scenarios.md) - [Restore troubleshooting](./restore-troubleshooting.md) ::: ## Before you start - You need organization admin permissions for the target cluster. - The backup must be in `Completed` state. - Backups created before the restore feature was introduced are not eligible for restore. - Only one restore can be in progress per cluster at a time. Restore is destructive for current cluster data and causes cluster unavailability during execution. ## Restore in Hub 1. In Camunda Hub, in the left navigation under **Clusters**, select your cluster. 2. Click the **Backups** tab. 3. In the backup list, find the backup you want to restore. 4. Click **Restore** on that backup row. 5. In the confirmation modal, verify: - Backup name - Backup completion date - Backup version 6. If the backup version differs from the current cluster version, review the warning and select the acknowledgment checkbox. 7. Click **Restore** to start. After confirmation, cluster status changes to **Restoring** and the cluster is unavailable until completion. ## Monitor restore progress During restore: - Cluster status shows **Restoring**. - Component cards show **Restoring**. - Restore-conflicting actions are disabled. - Backups tab shows active restore context. Phase-level progress is not shown in Hub in this release. ## Confirm outcome On success: - Cluster status returns to **Healthy**. - A success notification confirms which backup was restored. - You can validate behavior in Operate and other cluster applications. On failure: - Cluster data remains unchanged. - Cluster status returns to prior healthy/available state. - Error notification is shown on the Backups tab until dismissed. For failure handling, see [Restore troubleshooting](./restore-troubleshooting.md). ## Restore with API You can trigger restore from the Administration API: ```http POST /api/orgs/:orgId/clusters/:clusterId/backups/:backupId/restore ``` Expected response: - `202 Accepted` with restore metadata Common error responses: - `400` for invalid backup state, legacy backup, or compatibility failure - `404` for missing cluster or backup - `409` when a restore is already in progress - `501` when feature flag is disabled --- ## Hostnames and IP addresses for Camunda connections Camunda 8 SaaS only Camunda 8 SaaS hostnames and IP addresses for inbound and outbound connections. ## Static outbound IP addresses Camunda SaaS uses static IP addresses for some of its services. These addresses can be retrieved using the [Camunda Management API `/meta/ip-ranges` endpoint](https://console.cloud.camunda.io/customer-api/openapi/docs/#/default/GetMeta). Although changes to these IP addresses are infrequent, they may occur from time to time. Any change will be published through the API at least 24 hours in advance. If you rely on these IP addresses for your network configuration, it is strongly recommended fetching the latest version at least once every 24 hours. :::note Typically, any changes to these IP addresses are communicated in advance. However, in exceptional cases, it may not be possible to provide prior notice. ::: ## Inbound connections When you [create a cluster](/components/saas/clusters/create-cluster.md) in Camunda 8 SaaS, you will receive a set of hostnames for connecting to the different cluster components. The public IP addresses exposed for connecting to the cluster depends on the cloud provider and [region](/components/saas/regions.md) the cluster was created in. - **Amazon Web Services (AWS)**: Each endpoint is served by multiple IP addresses. - **Google Cloud Platform (GCP)**: IP addresses are AnyCast IP addresses and are globally available. ## Outbound connections If you use a [Camunda connector](/components/connectors/introduction.md), your cluster sends requests from the Camunda SaaS infrastructure to the external services you configure in your processes. Depending on the cloud provider, [region](/components/saas/regions.md), and type of configured connector, connections are made from different IP addresses. To ensure the security of incoming connector connections, you can: - Authenticate the requests made by the Camunda connector(s). For example, see [REST connector authentication](/components/connectors/protocol/rest.md#authentication). - Run the connectors into your own infrastructure and remove incoming calls from the Camunda infrastructure to your own services. For example, see [Self-Managed connectors](/self-managed/components/connectors/overview.md). --- ## Configure monitoring systems to scrape metrics Configure your monitoring systems to scrape metrics from a Camunda 8 SaaS cluster using the Cluster Metrics endpoint. ## Before you begin Before configuring metric scraping, ensure that: - The Cluster Metrics endpoint is enabled for your Camunda 8 SaaS cluster. - You have the metrics endpoint URL and authentication credentials. - Your monitoring system can reach the endpoint from an allowlisted IP address. For information about the Cluster Metrics endpoint monitoring model and limitations, see [Cluster Metrics endpoint](/components/saas/monitoring/cluster-metrics-endpoint/index.md). ## Metrics endpoint details The metrics endpoint: - Uses HTTPS - Requires Basic Authentication - Returns metrics in Prometheus format ### Endpoint format The full metrics endpoint follows this format: `https:///` - ``: The domain hosting the metrics endpoint. - ``: The identifier of the Camunda 8 SaaS cluster. ### Verify endpoint access Before configuring your monitoring system, use the following command to verify that the Cluster Metrics endpoint is reachable: ```bash curl -v -u ":" https:/// ``` ## Configure Prometheus scraping Prometheus can scrape the Cluster Metrics endpoint directly. ### Example scrape configuration ```yaml scrape_configs: - job_name: "c8-" scheme: https metrics_path: / static_configs: - targets: - basic_auth: username: password: scrape_timeout: 5s scrape_interval: 30s ``` Configuration notes: - Use HTTPS. - Configure Basic authentication using the credentials provided when the Cluster Metrics endpoint was enabled. - A scrape timeout of less than 10 seconds is recommended. - A scrape interval of at least 15 seconds is recommended. Metrics are collected every 15–30 seconds, so shorter intervals do not produce new data. ## Scrape interval and retention The Cluster Metrics endpoint exposes metrics from the most recent scrape only and does not retain historical data. Configure your monitoring system to store and retain metrics as needed. ## Verify metric collection After configuring scraping: - Confirm that the scrape target reports a healthy state. - Check that metrics correspond to the expected Camunda 8 cluster. If metrics do not appear, review authentication, network access, and scrape configuration. ## Example dashboards Camunda provides example Grafana dashboards in a public [GitHub repository](https://github.com/camunda/camunda/tree/main/monitor/grafana) that you can use to explore and visualize Cluster Metrics. These dashboards serve as reference examples and may rely on additional metric sources, such as [kube-state-metrics](https://github.com/kubernetes/kube-state-metrics) or [node-exporter](https://github.com/prometheus/node_exporter). You can adapt them to match your monitoring conventions, alerting rules, and operational requirements. Available metrics can vary depending on the Camunda version running in your cluster. ## Integrate non-Prometheus monitoring systems The Cluster Metrics endpoint exposes metrics in Prometheus-compatible formats. Some monitoring systems require additional components to ingest these metrics. In these cases, you can deploy a self-managed OpenTelemetry Collector to adapt the metrics to your monitoring system. For more information, see the [OpenTelemetry Collector documentation](https://opentelemetry.io/docs/collector/). ![Integrate non-Prometheus monitoring systems](./img/cluster-metrics-endpoint-non-prometheus-architecture.png) Using an OpenTelemetry Collector allows you to normalize, enrich, and control the flow of metrics scraped from the Cluster Metrics endpoint. For example, you can: - Transform metrics to match internal naming conventions - Filter metrics to reduce noise or control ingestion costs. - Enrich metrics with standard labels such as environment or region. - Forward metrics to one or more monitoring backends. - Manage scrape behavior, buffering, retries, and backpressure without changing how Camunda exposes metrics. ### Push-only monitoring systems If your monitoring system only supports push-based ingestion, use the following approach: 1. Deploy a self-managed OpenTelemetry Collector. 1. Configure the collector to scrape the Cluster Metrics endpoint. 1. Configure the collector to push metrics to your monitoring system. Camunda provides the metrics endpoint only. You are responsible for deploying, configuring, and operating the collector. ### Non-Prometheus metric formats If your monitoring system requires a format other than Prometheus, use an OpenTelemetry Collector with the appropriate exporter. The OpenTelemetry Collector supports a wide range of exporters, allowing you to forward metrics to different monitoring backends. For more information, see [OpenTelemetry Collector exporters](https://opentelemetry.io/docs/collector/components/exporter/). ## Troubleshoot common issues ### Authentication errors **Observed behavior:** Your monitoring system reports an authentication or authorization failure when scraping the endpoint. **Why this happens:** Either the configured credentials don't match what's set on the Cluster Metrics endpoint, or the monitoring system's IP address isn't allowlisted to reach it. **How to fix:** - Verify the configured username and password. - Check that the monitoring system's IP address is allowlisted. ### Scrape timeouts **Observed behavior:** Your monitoring system reports timeouts when scraping the endpoint, or scrapes intermittently fail. **Why this happens:** The configured scrape timeout is too short for the response, or there's a network connectivity issue between your monitoring system and the endpoint. **How to fix:** - Increase the configured scrape timeout. - Verify network connectivity to the metrics endpoint. ### Missing or incomplete metrics **Observed behavior:** Scraping succeeds, but expected metrics are absent or incomplete. **Why this happens:** The Cluster Metrics endpoint may be disabled or unhealthy for that cluster, the cluster may be running a version that doesn't support the metric, or the scrape interval and retention settings may not align with when the data was produced. **How to fix:** - Confirm that the Cluster Metrics endpoint for the cluster is enabled and healthy. - Verify that the cluster is running a supported Camunda version. - Review scrape interval and retention behavior. --- ## Cluster Metrics endpoint The Cluster Metrics endpoint lets you expose metrics from a Camunda 8 SaaS Orchestration cluster and consume them in your own monitoring system. ## About Use this endpoint to monitor cluster performance, set alerts, and correlate Camunda metrics with the rest of your infrastructure using your existing observability tools. This endpoint is a dedicated, customer-facing metrics service that exposes aggregated component-level metrics. The endpoint is separate from Camunda’s internal monitoring and operational systems and provides access only to metrics intended for customer consumption. ## Before you begin Before using the Cluster Metrics endpoint, ensure that: - You have an external monitoring system capable of collecting prometheus metrics. - You understand your organization’s network access and IP allowlisting requirements. ## Supported environments - The Cluster Metrics endpoint is available for all Camunda 8 SaaS Orchestration clusters. - The endpoint is configured per Orchestration cluster and can be enabled without requiring an upgrade or downtime. ## Metrics exposure model The Cluster Metrics endpoint exposes metrics using a pull-based model and Prometheus-compatible format ([Prometheus](https://github.com/prometheus/docs/blob/main/docs/instrumenting/exposition_formats.md#text-based-format) and [OpenMetrics](https://github.com/prometheus/docs/blob/main/docs/instrumenting/exposition_formats.md#openmetrics-text-format) text exposition formats). ![Cluster Metrics endpoint architecture](./img/cluster-metrics-endpoint-prometheus-architecture.png) When the Cluster Metrics endpoint is enabled for a cluster: - Camunda exposes a cluster-scoped metrics endpoint that aggregates metrics from all Orchestration cluster components. - Metrics are exposed in Prometheus-compatible format. - Your monitoring system initiates metric collection by scraping the endpoint. The Cluster Metrics endpoint does not push metrics to customer systems. ## Monitoring endpoint constraints The Cluster Metrics endpoint exposes the application-level metrics produced by the Camunda version running in your cluster. The following constraints apply: - The Cluster Metrics endpoint uses Basic authentication only. - Metric names and labels depend on the Camunda version running in your cluster. - Metric and dashboard compatibility between Camunda versions is not guaranteed. If your monitoring system does not support Prometheus scraping, you can adapt the metrics using a self-managed OpenTelemetry Collector. For more information, see [Integrate non-Prometheus monitoring systems](/components/saas/monitoring/cluster-metrics-endpoint/configure-monitoring-systems-to-scrape-metrics.md#integrate-non-prometheus-monitoring-systems). ## Next steps - To enable the Cluster Metrics endpoint and obtain connection details, see [Set up the Cluster Metrics endpoint](/components/saas/monitoring/cluster-metrics-endpoint/set-up-cluster-metrics-endpoint.md). - To configure Prometheus to scrape metrics, see [Configure monitoring systems to scrape metrics](/components/saas/monitoring/cluster-metrics-endpoint/configure-monitoring-systems-to-scrape-metrics.md). - If your monitoring system does not support Prometheus scraping, see [Integrate non-Prometheus monitoring systems](/components/saas/monitoring/cluster-metrics-endpoint/configure-monitoring-systems-to-scrape-metrics.md#integrate-non-prometheus-monitoring-systems). --- ## Set up the Cluster Metrics endpoint Set up and manage the Cluster Metrics endpoint for a Camunda 8 SaaS Orchestration cluster, including how to activate and deactivate the endpoint and obtain the connection details required to integrate it with an external monitoring system. ## Before you begin Before enabling the Cluster Metrics endpoint, ensure that: - You are using Camunda 8 SaaS. - You have access to Camunda Hub and have permission to manage cluster-level settings. - If allowlisting is configured for your cluster, the monitoring system’s source IP addresses are added to the cluster IP allowlist. ## Enable Cluster Metrics endpoint Enable the Cluster Metrics endpoint per Orchestration cluster via either Camunda Hub or the API. When the endpoint is enabled, Camunda provisions a secure, cluster-scoped metrics endpoint for external scraping. To activate the endpoint: 1. Sign in to Camunda Hub. 1. In the left navigation, click **Environments**, and then click **Clusters**. 1. Select an existing cluster, or create a new one. 1. Open the **Monitoring** tab for the cluster. 1. Click **Activate monitoring endpoint**. 1. Enter a **username** for the monitoring credentials. 1. Click **Activate**. ### Capture connection details When the Cluster Metrics endpoint is activated, Camunda Hub displays a dialog containing the authentication credentials. 1. Copy and store the password securely. 1. Click **Got it** to close the dialog. After closing the dialog, you can find the metrics endpoint URL in the **Monitoring** tab for the cluster. :::warning Copy and safely store the password when it is displayed. The password is not shown again after you close the dialog. If you lose it, generate a new password. ::: The following information is required to connect your monitoring system: - **Metrics endpoint URL**: HTTPS endpoint used by your monitoring system to scrape metrics. - **Username**: Used for Basic authentication. - **Password**: Used for Basic authentication. ## Manage authentication credentials Authentication credentials are created and managed in Camunda Hub. ### Create additional credentials You can create up to 20 credentials per cluster. To create additional credentials: 1. On the **Monitoring** tab, click **Create new credentials**. 1. Enter a username. 1. Generate and copy the password when it is displayed. ### Rotate credentials Ƭo rotate a password: 1. On the **Monitoring** tab, locate the credential. 1. Click the **Generate password** icon next to the username. 1. Generate and copy the new password when prompted. When credentials are removed or rotated, previously issued credentials may continue to work briefly. Access may persist for up to five minutes before the credentials are fully invalidated. To avoid interruptions during credential rotation, you can create multiple credentials for the same cluster and update your monitoring system to switch between credentials, rather than rotating a single credential in place. ## Authentication and IP allowlisting The Cluster Metrics endpoint enforces both authentication and network restrictions. | Restriction | Description | | :-------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Authentication | The endpoint uses Basic authentication. | | IP allowlisting | The endpoint enforces the cluster-level IP allowlist. Requests from non-allowlisted IP addresses are rejected.If an IP allowlist is configured for the cluster, you must add the source IP addresses of your monitoring system to the allowlist to access the endpoint. | ### Error responses The Cluster Metrics endpoint returns standard HTTP status codes to indicate access and availability issues: | Scenario | HTTP status code | | :--------------------------------------------- | :------------------------ | | Request from a non-allowlisted IP address. | `403 Forbidden` | | Invalid or missing authentication credentials. | `401 Unauthorized` | | Request rate exceeds allowed limits. | `429 Too Many Requests` | | Metrics endpoint is temporarily unavailable. | `503 Service Unavailable` | | Request times out due to high load. | `504 Gateway Timeout` | ## Disable the Cluster Metrics endpoint If you no longer want to expose cluster metrics externally, you can disable the Cluster Metrics endpoint: - Click **Deactivate** in the **Monitoring** tab, or - Delete all credentials associated with the endpoint When the Cluster Metrics endpoint is disabled: - The monitoring endpoint is shut down almost immediately (typically within a few seconds). - All existing credentials are deleted and are not retained if the endpoint is reactivated. - Monitoring systems can no longer scrape metrics from the cluster. To use the endpoint again, you must reactivate it and create new credentials. Disabling the Cluster Metrics endpoint does not affect cluster operation or workload execution. ## Next steps - To configure Prometheus to scrape metrics from the Cluster Metrics endpoint, see [Configure monitoring systems to scrape metrics](/components/saas/monitoring/cluster-metrics-endpoint/configure-monitoring-systems-to-scrape-metrics.md). - If your monitoring system does not support Prometheus scraping, see [Integrate non-Prometheus monitoring systems](/components/saas/monitoring/cluster-metrics-endpoint/configure-monitoring-systems-to-scrape-metrics.md#integrate-non-prometheus-monitoring-systems). --- ## Monitoring Use your own monitoring and observability tools to observe the health and performance of your Camunda 8 SaaS Orchestration Clusters. ## Cluster Metrics endpoint Camunda provides a secure, customer-facing metrics endpoint that exposes aggregated cluster metrics for external consumption. [Cluster Metrics endpoint](cluster-metrics-endpoint/index.md) --- ## Create and manage users Create and manage your organization's users in Camunda Hub. ## Add a user To add a user to your organization in Camunda Hub SaaS: 1. In the left navigation, click **Organization > Manage organization**. 1. On the **Users** tab, click **Add new user**. 1. Enter an **Email address** and select one or more [user roles](../../hub/organization/users-and-roles.md#roles-and-permissions). 1. Click **Send invite**. ## Edit or remove a user To edit or remove a user in your organization: 1. In the left navigation, click **Organization > Manage organization**. 1. On the **Users** tab, on a user row, open the vertical ellipsis menu. 1. Click **Edit user** or **Remove**. ## User limits by plan The number of users that can be part of an organization depends on the [plan](/components/saas/organization/manage-plan/available-plans.md) you use. ## Restrict invite email domains In Enterprise plans, the hostname section of the email address for invites can be restricted to meet your internal security policies. [Contact Camunda support](https://camunda.com/services/support/) to get this configured according to your needs. --- ## Delete your Camunda account To delete your Camunda account, take the following steps: 1. At the top right of any Camunda Hub page, click the **User** icon. 2. At the bottom of the menu, click **Delete account**. If you are the only member of this organization, your free trial organization and its cluster will be deleted as well. --- ## Enable alpha features :::note Opting-in to Camunda alpha terms currently only applies to Enterprise SaaS subscriptions. Customers in other subscriptions can still turn on/off these settings directly from Camunda Hub. ::: If you aren't already familiar with accessing **alpha features**, learn more in our [alpha feature documentation](/components/early-access/alpha/alpha-features.md). Alpha terms typically refer to the specific terms and conditions that govern the use and testing of this software during its alpha phase. These terms outline the rights, responsibilities, and limitations of both the software provider (Camunda) and the users (alpha testers or early adopters) during the testing and evaluation period. Alpha terms help protect Camunda´s interests (such as protecting our intellectual property, disclaiming warranties, or limiting our liability for any issues or damages that may arise during the alpha phase), manage user expectations, encourage active participation and feedback, and ensure legal compliance during the pre-release phase of software development. :::note Enabling alpha features is limited to [admin users and owners](/components/hub/organization/users-and-roles.md#roles-and-permissions) of Camunda products. ::: ## Accept alpha terms To accept alpha terms for Camunda products, follow the steps below: 1. In the left navigation, click **Organization > Manage organization**. 2. On the **Settings** tab, click **Opt-in** under the **Alpha features** box. 3. Note the **Alpha Terms** modal. As the admin accepting the alpha terms, you must scroll and read through the terms before accepting. 4. Once you have read the terms and scrolled through the modal, tick the box at the bottom reading **"I understand and agree to Alpha Terms"**. The system will confirm your acceptance and send a copy of the accepted alpha terms to your email address. ## Manage alpha features Once you accept the alpha terms, you can enable and disable any features you would like to use, and learn more about them: 1. In the left navigation, click **Organization > Manage organization**. 2. On the **Settings** tab, click **View docs** under **Documentation** to learn more about a feature. 3. Toggle the switch under **Status** to enable and disable the feature. Admins can know when someone accepts the alpha terms and when features are enabled or disabled under the [**Activity** tab}(./view-organization-activity.md). ### Enable AI-powered features To use AI-powered alpha features, you must agree to the **Terms for AI Usage** before the toggle will appear in the **Status** column: 1. On the **Settings** tab, click the **Opt in to enable** link under **Status**. 2. A dialog will appear containing the **Terms for AI Usage**. You must read to the end of the terms before accepting. 3. To agree to the terms, select the box labeled **I understand and agree to the Terms for AI Usage**. 4. The AI-powered features toggle will now be available in **Settings**. Set this toggle to **Enabled** to turn on AI features. ### Enable Camunda-provided LLM [Camunda-provided LLM](/components/agentic-orchestration/camunda-provided-llm.md) has its own row and toggle, listed under the **Hub** component as **Camunda Provided LLM**. It is separate from **AI-powered features**, which is listed under the **Modeler** component. Enabling **AI-powered features** does not enable it, and disabling **AI-powered features** does not disable it. 1. On the **Settings** tab, set the **Camunda Provided LLM** toggle to **Enabled**. 2. If you have not already accepted the **Terms for AI Usage**, you are prompted to accept them before the feature is enabled. To stop using the feature, set the **Camunda Provided LLM** toggle to **Disabled**. For details of the data processed while the feature is enabled, see [Data processing and AI terms](/components/agentic-orchestration/camunda-provided-llm.md#data-processing-and-ai-terms). --- ## Connect to an identity provider(Organization) Enterprise plan customers can integrate an external identity provider (IdP) with Camunda. This means users within your organization do not need to sign up by creating a Camunda account. ## Onboarding procedure We currently support both SAML and Azure Active Directory (Azure AD). As this requires changes in our environment, first raise a ticket in the [support queue](https://jira.camunda.com/projects/SUPPORT/). :::info Expiring certificates or secrets must be renewed prior to their expiration date to avoid a service interruption. ::: ### SAML After opening the ticket in the support queue, we will provide you: - **Assertion Customer URL**: e.g. `https://weblogin.cloud.camunda.io/login/callback?connection=CUSTOMER_CONNECTION` - **Entity ID**: e.g. `urn:auth0:camunda:CUSTOMER_CONNECTION` You will then need to provide: - The domain used for the login email addresses - A sign-in URL - A x509 signing certificate ### Azure AD For Azure AD, you will need to provide: - The domain used for the login email addresses - The Microsoft Azure AD domain - The generated client ID - The client secret value To generate the client on your end, you will need to use the Camunda **Redirect URL** `https://weblogin.cloud.camunda.io/login/callback `. Ensure you attach the user permissions `Users > User.Read`. ### Default organizations Enterprise only :::info Default organizations for external identity providers are only available for organizations on an Enterprise plan. ::: By setting up an external identity provider, it is possible to configure up to 10 default organizations. The following information must be added in the ticket so that the support team can configure the default organizations: - Organization ID - Default organization roles If a user logs in with the configured connection, the user is automatically assigned to these organizations with the corresponding roles. ### Additional information In some situations, you might need to access `openid-configuration` to establish the connection from your end. See [this OpenID configuration](https://weblogin.cloud.camunda.io/.well-known/openid-configuration) as an example. ## Login If your organization is using social login procedures (like GitHub or Google), these procedures will not work when using your own IdP with Camunda. Users must log in by providing their email address on the login screen. --- ## Organization in SaaS An organization is the top-level entity in Camunda 8 SaaS. It holds your [workspaces](/components/concepts/workspaces.md), [environments](/components/concepts/environments.md), [clusters](/components/concepts/clusters.md), and users. The person who signs up first becomes the Organization Owner and can invite others to help manage it. This section covers the tasks that are specific to SaaS organizations. For the tasks that apply to both SaaS and Self-Managed, see [manage organization](/components/hub/organization/manage-organization-settings/organization-settings.md) and [manage users and roles](/components/hub/organization/users-and-roles.md). ## Users and access Decide who can sign in to your organization and what they can do. - [Organization ownership](/components/saas/organization/organization-ownership.md): Learn how ownership works and how to transfer it to another user. - [Create and manage users](/components/saas/organization/create-manage-users.md): Invite users to your organization and manage them. - [Manage user groups](/components/saas/organization/manage-user-groups.md): Organize users in groups. - [Manage resource-based authorizations](/components/saas/organization/resource-based-auth.md): Control access to individual resources. This is a legacy feature. - [Connect to an identity provider](/components/saas/organization/external-sso.md): Let users sign in with your own identity provider. ## Activity and usage See what happens in your organization and how much of your plan you use. - [View organization activity](/components/saas/organization/view-organization-activity.md): See user and cluster activity. - [View usage history](/components/saas/organization/usage-history.md): Monitor the usage of your organization over time. - [View usage alerts](/components/saas/organization/usage-alerts.md): Get notified when usage reaches a threshold. ## Plan and billing Choose and manage your plan. - [Create an account](/components/saas/organization/manage-plan/create-account.md): Set up your Camunda 8 account. - [Available plans](/components/saas/organization/manage-plan/available-plans.md): Compare the plans and learn how to upgrade. - [Update billing reservations](/components/saas/organization/manage-plan/update-billing-reservations.md): Manage your hardware packages and reservations. ## Account and organization settings Configure features for your organization and manage your account. - [Enable alpha features](/components/saas/organization/enable-alpha-features.md): Opt in to alpha features for your organization. - [Switch organization](/components/saas/organization/switch-organization.md): Work in more than one organization. - [Delete your Camunda account](/components/saas/organization/delete-account.md): Remove your account. --- ## Available plans The following Camunda 8 plans are available: | Plan | Description | | :--------- | :----------------------------------------------------------------------------- | | Free trial | Create a free account to try Camunda in our cloud. | | Enterprise | Made for high volume automation and end-to-end process orchestration. We host. | :::note - To sign up for Camunda 8 and compare plan features, refer to [Camunda 8 pricing](https://camunda.com/pricing/?utm_source=docs.camunda.io&utm_medium=referral). - For more information on Camunda 8, refer to [Camunda 8 product](https://camunda.com/products/cloud/). ::: ## Upgrade to an Enterprise plan To upgrade to the Enterprise plan, click **Organization > Manage organization** in the left navigation, select the **Plans and pricing** tab, and click **Request quote**. ### Benefits of the Enterprise plan The Enterprise plan includes the following. **Included in the annual usage** - Unlimited BPMN/DMN models - Unlimited users to collaborate - High performance clusters - High-volume process instances - High-volume decision instances - High-volume task users **Extra features and support** - [Single Sign On & SAML](/components/saas/organization/external-sso.md) - Custom regions options for clusters - 24/7 technical support - Custom training and consulting - Dedicated customer success manager --- ## Create an account Create a Camunda 8 account to create clusters, deploy processes, and create a new instance. :::note We're gradually rolling out changes that affect this page to users; your experience may vary. ::: Visit the [Camunda 8 sign-up page](https://signup.camunda.com/accounts?utm_source=docs.camunda.io&utm_medium=referral) to create your account. ### Create an account Fill out the form and submit, or sign up using the social sign up buttons like Google or GitHub. When you fill out the form, you'll receive a confirmation email. Click on the link to verify your email address. If you choose to create an account through the social sign up buttons, you'll be redirected to [Camunda Hub](/components/hub/index.md) directly. ## Log in to your Camunda 8 account Log in with the email address and password you used in the previous form, or use the social login buttons. To access the login site directly, navigate to [camunda.io](https://weblogin.cloud.camunda.io/). After login, select the square-shaped **Camunda components** icon in the upper-left corner, and select Camunda Hub to view the Camunda Hub overview page. This is the central place to manage the clusters, diagrams, and forms you want to deploy to Camunda 8. ## Additional resources - [Camunda Academy: Create a Camunda 8 Account](https://academy.camunda.com/c8-h2-create-account?reg=1) --- ## Update billing reservations :::note This setting is only visible in the **Enterprise plan** for owners and admins. ::: ## Managing hardware packages Once signed up for the **Enterprise plan**, you have access to the **Billing** page. - The created process instances from the current period are displayed at the top of the page. - Find a history of the metrics on a monthly basis at the bottom of the page. - View how many hardware packages are included on the right side of the page. Reservations control how many clusters you can deploy. Increasing the number of reservations allows you to deploy more clusters, while decreasing the number of reservations allows you to deploy fewer clusters. To access billing details: 1. In the left navigation, click **Organization > Manage organization**. 1. Click the **Billing** tab. ![billing-overview](./img/billing-overview.png) --- ## Manage user groups Organize users into groups in Camunda Hub. User groups created in Camunda Hub are available in SaaS only. They apply only to access control for clusters on version 8.7 or earlier, and don't control access to Camunda Hub, such as workspaces, projects, or organization settings. For clusters on version 8.8 or later, manage [groups](/components/admin/group.md) and authorizations in Admin, which applies to a single cluster. Groups created in Console are no longer recommended for use. See [identity types in Camunda 8](/components/concepts/access-control/access-control-overview.md#identity-types-in-camunda-8). ## Creating a group To create a group in Camunda Hub: 1. In the left navigation, click **Organization > Manage organization**. 1. On the **Groups** tab, select **Create group**. ## Adding users to a group 1. In the left navigation, click **Organization > Manage organization**. 1. On the **Groups** tab, select a group. 1. Click **Assign members**. ## User task access restrictions :::info User task access restrictions were removed in Camunda 8.10 together with Tasklist V1. Use [user task authorization](/components/tasklist/user-task-authorization.md) and [authorization-based access control](/components/concepts/access-control/authorizations.md) for current task visibility rules. ::: --- ## Organization ownership The first user to sign up for Camunda 8 from an organization, company, or group becomes the Organization Owner. The organization owns Camunda Hub workspaces, projects, and clusters, and the owner and any Organization Admins they assign control access to them. ## Organization Owner The Organization Owner has all rights in an organization and can manage all settings accordingly. An organization cannot have more than one Organization Owner. For the rights of the Organization Owner compared to the other roles, see [roles and permissions](/components/hub/organization/users-and-roles.md#roles-and-permissions). ## Transfer ownership To change the Organization Owner, use the user administration. The current owner selects another user of the organization and clicks **Assign as owner** from the menu. In the dialog that appears, select the role to assign to the current owner after the transfer. --- ## Manage resource-based authorizations :::warning Legacy feature Resource-based authorizations are a legacy feature, and they apply only to clusters before version 8.8. For clusters on version 8.8 and later, manage the access of users with [authorizations in Admin](/components/admin/authorization.md). ::: Resource authorizations control a user's access to specific resources. To create, update, or delete a user's resource authorizations, select the user's row in the users table. ## Prerequisites Before you begin, you need to know the ID of the resource for which you're authorizing the user: 1. Open a BPMN or DMN diagram. 1. On the right side of the modeling interface, in the **Details** panel, open the **General** section. 1. Without selecting an element in the modeling interface, copy the **ID**. ## Create an authorization Create resource authorizations in Camunda Hub: 1. In the left navigation, click **Organization > Manage organization**. 1. On the **Users** tab, select a user. 1. On the **Authorizations** tab, click **Create resource authorization**. 1. Paste the resource ID, and select at least one permission. 1. Click **Create**. ## Update an authorization 1. In the left navigation, click **Organization > Manage organization**. 1. On the **Users** tab, select a user. 1. On the **Authorizations** tab, click **Change permissions**. ## Delete an authorization 1. In the left navigation, click **Organization > Manage organization**. 1. On the **Users** tab, select a user. 1. On the **Authorizations** tab, click **Delete**. --- ## Switch organization If you're assigned to more than one organization, you can change between them: 1. In the top navigation, next to the name of your organization, expand the dropdown menu. 2. Select another organization. --- ## View usage alerts Set up alerts for process instances, decision instances and task users. ## About usage alerts In Camunda Hub, organization owners and admins can set up alerts for process instances, decision instances and task users. Usage is calculated daily. When the threshold for an alert is met, all organization owners and admins are alerted via email and in-app notification. :::note Usage alerts apply **only to production clusters** and are visible only to owners and admins in **Enterprise** organizations. ::: ## Manage usage alert To manage a usage alert, take the following steps: 1. In the left navigation, click **Organization > Manage organization**. 2. On the **Billing** tab, select **Edit alert** next to the metric you want to configure the usage alert for. For example, process instances. ![Edit Usage Alert](./img/edit_usage_alert.png) 3. In the modal, define the percentage threshold, for example 80 for 80%, turn on the alert, and click **Save**. The threshold can be between 1% and 4999%. ![Set Alert](./img/set_up_usage_alert.png) :::note The threshold set is calculated on a percentage ratio between your consumption and the amounts included in your plan. For example, when a threshold is set at 50% for a plan that includes 200 process instances, an alert is sent when 100 process instances are reached. ::: Usage is calculated daily. When the threshold for an alert is met, all org owners and admins are alerted via email and in-app notification. Usage alerts can be edited and turned on or off anytime by selecting **Edit alert** and updating the toggle. ![Turn Off Alert](./img/turn_off_usage_alert.png) ## Viewing an alert change log Users can track changes in the usage alerts under the logs of the **Activity** tab: 1. In the left navigation, click **Organization > Manage organization**. 2. Click the **Activity** tab. ![Usage Alert Logs](./img/usage_alerts_logs.png) --- ## View usage history Monitor our workflow usage. ## About usage history Three key metrics play a role in paid plans: - Started process instances - Decision instances - Task users. Camunda Hub provides a usage view for these metrics across the organization. :::note The usage history is visible only to owners and admins for Trial and Enterprise organizations. ::: ## View usage To view usage history in Camunda Hub: 1. In the left navigation, click **Organization > Manage organization**. 1. Open the **Usage** tab. The section is split into two areas: - **Table view**: Data from production clusters is displayed aggregated on a monthly basis. ![Usage History - Table View](./img/plans_usage_history_table_view.png) - **Chart view**: Data from production and development clusters is displayed in charts and allows a closer look into usage patterns and spikes by customizing the date ranges. ![Usage History - Chart View](./img/plans_usage_history_chart_view.png) --- ## View organization activity View details of all activity within an organization, such as cluster creation, deletion, updates, and user invitations. ## About organization activity This feature is only available in SaaS. ## View activity To view organization activity: 1. In the left navigation, click **Organization > Manage organization**. 1. Open the **Activity** tab. ![activity-view](./img/activity-view.png) On the **Activity** tab, you can click **Export activity** to export the activity record as JSON or CSV. --- ## Regions When you [create a cluster](/components/saas/clusters/create-cluster.md) in Camunda 8 SaaS, you must specify a region for that cluster. The following regions are available for customers on Trial and Enterprise plans. Enterprise customers can also [reach out to Camunda](https://camunda.com/contact-us/) to discuss custom regions. :::info See [Data locations](data-locations.md) for more information about where your Camunda 8 SaaS data is located and how data is handled. ::: ## Google Cloud Platform (GCP) regions The following GCP regions are currently supported in Camunda 8 SaaS. | GCP region | Secondary backups region | | :------------------------------------------------ | :------------------------------------------------ | | Belgium, Europe (europe-west1) | Germany, Europe (europe-west3) | | Iowa, North America (us-central1) | Salt Lake City, North America (us-west1) | | London, Europe (europe-west2) | Germany, Europe (europe-west3) | | Montréal, North America (northamerica-northeast1) | Toronto, North America (northamerica-northeast2) | | Singapore, Asia (asia-southeast1) | Changhua County, Taiwan (asia-east1) | | South Carolina, North America (us-east1) | Iowa, North America (us-central1) | | Sydney, Australia (australia-southeast1) | Melbourne, Australia (australia-southeast2) | | Toronto, North America (northamerica-northeast2) | Montréal, North America (northamerica-northeast1) | To learn more about each region code/location, refer to [Google Cloud locations](https://cloud.google.com/about/locations). ## Amazon Web Services (AWS) regions The following AWS regions are currently supported in Camunda 8 SaaS. | AWS region | Secondary backups region | | :---------------------------------- | :---------------------------------- | | Frankfurt, Europe (eu-central-1) | Ireland, Europe (eu-west-1) | | Ireland, Europe (eu-west-1) | Frankfurt, Europe (eu-central-1) | | Paris, Europe (eu-west-3) | Ireland, Europe (eu-west-1) | | North America, Ohio (us-east-2) | Oregon, North America (us-west-2) | | Oregon, North America (us-west-2) | North America, Ohio (us-east-2) | | North America, Virginia (us-east-1) | Oregon, North America (us-west-2) | | Singapore, Asia (ap-southeast-1) | Jakarta, Indonesia (ap-southeast-3) | To learn more about each region code/location, refer to [AWS regions and availability zones](https://aws.amazon.com/about-aws/global-infrastructure/regions_az/). :::note Secure connectivity (AWS PrivateLink) is available only for AWS-hosted Orchestration Clusters. To learn more, see [secure connectivity (AWS PrivateLink)](/components/saas/secure-connectivity/index.md). ::: --- ## Restore scenarios Camunda Enterprise Use these scenarios to standardize how your team runs restore operations in SaaS. :::note Related pages - [Backup and restore overview](./backup-restore-overview.md) - [Restore a cluster from backup](./how-to-restore.md) - [Restore troubleshooting](./restore-troubleshooting.md) ::: ## Recover from data corruption Use this when cluster data is inconsistent after an operational incident. ### Steps 1. Identify the last known-good backup. 2. Confirm the backup is in `Completed` state. 3. Start restore from Hub. 4. Wait for cluster to return to healthy state. 5. Validate process execution and key business variables in Operate. ### Verification checklist - New process instances can start. - Existing process data is readable. - No restore error notification remains. ## Roll back after a failed change Use this when a recent change caused severe impact and you need to return to a known-good state quickly. ### Steps 1. Select the backup created before the failed change. 2. Start restore and monitor status until completion. 3. Validate core process paths and integration health. 4. Re-plan the failed change after root-cause analysis. ### Verification checklist - Incident volume returns to baseline. - Critical process KPIs recover. - Cluster status remains healthy after restore. ## Run a quarterly DR test Use this to validate operational readiness and runbook quality. ### Steps 1. Define test scope, acceptance criteria, and observer roles. 2. Pick a representative backup for restore rehearsal. 3. Execute restore using the same production runbook. 4. Record timings, blockers, and remediation actions. 5. Update internal runbooks based on lessons learned. ### Verification checklist - Team can execute end-to-end without escalation. - Recovery timing is captured and reviewed. - Follow-up actions are documented and assigned. ## Recover from a regional outage with intact backups Use this when platform services recover but your cluster data requires restoration from a known backup. ### Steps 1. Confirm the outage state is resolved for your target cluster region. 2. Select the most recent valid backup. 3. Execute same-cluster restore. 4. Validate connectivity and application recovery. 5. Communicate recovery completion to stakeholders. ### Verification checklist - Cluster endpoints are reachable. - Applications reconnect without endpoint reconfiguration. - Business-critical process flows are operational. --- ## Restore troubleshooting Camunda Enterprise Use this guide to diagnose and recover from restore failures. :::note Related pages - [Backup and restore overview](./backup-restore-overview.md) - [Restore a cluster from backup](./how-to-restore.md) - [Restore scenarios](./restore-scenarios.md) ::: ## Common restore phases Restore operations pass through these phases: - `VALIDATING` - `DeleteData` - `EnterRestoreMode` - `RestoreSnapshots` - `ExitRestoreMode` ## Failure modes and actions | Phase | Typical symptom | Likely cause | Recommended action | | ------------------ | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `VALIDATING` | Restore request fails quickly with validation error | Backup is not complete, backup is legacy (missing `generationUuid`), partition count mismatch, or restore already in progress | Select a completed non-legacy backup, confirm partition compatibility, and retry after any active restore reaches terminal state. | | `DeleteData` | Restore fails after initiation and cluster returns to previous state | Internal destructive phase could not complete safely | Retry once after verifying cluster health; if repeated, contact support with restore ID and timestamps. | | `EnterRestoreMode` | Cluster remains unavailable and restore does not progress | Controller/operator state transition did not complete | Check cluster status history and activity context, then contact support with restore ID. | | `RestoreSnapshots` | Restore progresses but fails before completion | Snapshot restore failure, backend storage issue, or compatibility issue | Retry with a different backup if available. If all fail, contact support and provide failed restore IDs. | | `ExitRestoreMode` | Restore appears complete but final healthy state is delayed or fails | Post-restore reconciliation did not complete | Wait for reconciliation window, then re-check cluster health. If still degraded, contact support with restore metadata. | ## API error reference | Code | Meaning | | ----- | --------------------------------------------------------------------------------------------------- | | `400` | Invalid restore input (for example, legacy backup, backup not complete, compatibility check failed) | | `404` | Cluster or backup was not found | | `409` | Another restore is already in progress | | `501` | Restore feature flag is not enabled | | `500` | Internal server or Kubernetes API error | ## Activity and audit interpretation For each restore, capture: - Who initiated the restore - Which backup was selected - Start and completion timestamps - Final terminal state (`COMPLETED`, `FAILED`, or `ABORTED`) Use this record for incident timelines and post-incident review. ## Common mistakes - Restoring a backup without confirming partition count compatibility. - Starting restore during other cluster mutation activities. - Assuming restore is non-destructive for current cluster data. - Running restore without a validated rollback/runbook plan. ## When to contact support Contact support when: - Restore repeatedly fails with the same backup and a newer valid backup is not available. - Cluster does not return to healthy status after terminal restore state. - You suspect data integrity issues after a reported successful restore. Include restore ID, cluster ID, selected backup ID, timeframe, and screenshots of status and errors. --- ## Camunda 8 SaaS Run Camunda 8 as a fully managed, cloud-based service. No technical setup or installation is needed, and maintenance and scaling is handled by Camunda. :::warning This documentation is a work in progress and may contain incomplete, placeholder, or evolving content. ::: :::tip Use [Camunda 8 Self-Managed](/self-managed/about-self-managed.md) if you want to deploy and manage Camunda on your own infrastructure, with responsibility for updates, security, and scaling. ::: ## Sign up Sign up and start your developer journey with Camunda 8 SaaS. 1. Visit [accounts.cloud.camunda.io/signup](https://accounts.cloud.camunda.io/signup) to sign up. 1. Fill out the signup form and click **Create account**. 1. Click on the link in your confirmation email to verify your email address. 1. Log in to Camunda 8 SaaS using either the email address and password you signed up with or the social login buttons. You can also log in to Camunda 8 SaaS directly at [camunda.io](https://weblogin.cloud.camunda.io/). ## Manage Camunda 8 SaaS Set up and manage the resources of your Camunda 8 SaaS account: ## Architecture The Camunda 8 SaaS platform is built on Amazon Web Services (AWS) and based on a microservices architecture. ### Clusters Camunda Hub is hosted in AWS in the eu-central-1 [region](/components/saas/regions.md). It is where you manage your organization, workspaces, environments, and clusters. Your processes run on [clusters](/components/saas/clusters.md). Each cluster hosts one or more environments, and you choose its [type](/components/saas/clusters.md#cluster-type) and [size](/components/saas/clusters.md#cluster-size) when you create it. A cluster runs the Orchestration Cluster components such as Zeebe, Tasklist, Operate, Optimize, and Connectors in GCP or Amazon Web Services (AWS) regions. An Orchestration Cluster is a provided group of production-ready nodes that run Camunda 8. By default, each cluster serves a single [logical tenant](/components/concepts/multi-tenancy.md), with all data associated with the `` tenant. A cell-based architecture means that each cluster runs as dedicated processes in a separate cell isolated from all other clusters, allowing secure fault and workload separation. Scaling is achieved by deploying additional clusters for new use cases and/or teams. :::note On clusters running generation 8.8 and later, you can enable [logical multi-tenancy](/components/concepts/multi-tenancy.md) to serve multiple logical tenants from the same cluster. The tenants share the cluster infrastructure, and their data is logically isolated. Each data entry (for example, process definition, process instance, job) is appended with a tenant ID to ensure separation. ::: ### Zeebe The [Zeebe](/components/zeebe/zeebe-overview.md) core process automation engine that powers Camunda 8 is fully managed by Camunda in SaaS, and is already pre-integrated with other Camunda 8 components such as Operate, Optimize, and Tasklist. You can interact with Zeebe in SaaS using both gRPC and REST APIs. See [working with APIs and tools](/apis-tools/working-with-apis-tools.md). ### Deployment You can configure a number of deployment options to meet your specific business and hosting requirements. For example, you can choose where to host your data and what level of data encryption to use. | Deployment option | Description | | :------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [Cluster](/components/saas/clusters.md) | Configure the cluster [type](/components/saas/clusters.md#cluster-type) and [size](/components/saas/clusters.md#cluster-size) to meet your organization's availability and scalability needs, and to provide control over cluster performance and [availability and uptime](/components/saas/clusters.md#cluster-availability-and-uptime).To learn more about choosing your cluster size, see [sizing your runtime environment](/components/best-practices/architecture/sizing-your-environment.md). | | [Region](/components/saas/regions.md) | Choose the region and type of hosting you want to use for your clusters. GCP and AWS region hosting options are available. | | [Encryption at rest](/components/saas/encryption-at-rest.md) | Cluster data is encrypted at rest to provide data security and protection.By default, Camunda 8 SaaS GCP and AWS cluster data at rest is protected with a provider-managed encryption key using GCP encryption. The encryption key is owned and managed by GCP.Enterprise customers requiring a higher level of protection can select a dedicated Camunda-managed software or hardware (HSM) encryption key when creating a new GCP cluster. The encryption key is managed by Camunda using Google Cloud Key Management Service (KMS).Camunda-managed encryption is not currently supported for AWS region clusters. | | [Backups](/components/saas/backups.md) | Back up the state of all Camunda 8 components (Zeebe, Operate, Tasklist, and Optimize) on a regular basis and with zero downtime. In case of failures that lead to data loss, you can restore the cluster from a backup. See [backup and restore overview](/components/saas/backup-restore-overview.md). | | [Auto-updates](/components/saas/auto-updates.md) | Camunda 8 SaaS customers can enable auto-updates. When enabled, the cluster is updated once a new patch release is available. | | [Secure connectivity (AWS PrivateLink)](/components/saas/secure-connectivity/index.md) | Enable private inbound connectivity from your VPC to AWS-hosted Orchestration Clusters using AWS PrivateLink. Available for Enterprise plans. | ### Monitoring Camunda 8 SaaS offers a number of monitoring options to help you keep track of your processes and system health. | Monitoring option | Description | | :------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Operate](/components/operate/operate-introduction.md) | Operate allows you to monitor, manage, and troubleshoot process instances in Camunda 8. It allows you to monitor, search, and resolve [incidents](/components/operate/userguide/resolve-incidents-update-variables.md) across your tenants. | | [Optimize alerts](/components/optimize/improve-processes-with-optimize.md#alerts) | Optimize allows you to create alerts for reports within a collection. These alerts can notify you when a report hits a predefined critical value. For SaaS users, alerts can be sent to the email addresses of Camunda Hub users. | | [Usage alerts](/components/saas/organization/usage-alerts.md) | For Enterprise organizations, Camunda 8 SaaS provides usage alerts for production clusters. Organization owners and admins can set up alerts for process instances, decision instances, and task users.These alerts are triggered when usage reaches a defined threshold, and notifications are sent via email and in-app notifications. | | [Flow control](/self-managed/operational-guides/configure-flow-control/configure-flow-control.md) | Flow control is enabled by default to protect SaaS clusters from excessive load and to maintain a stable state. This feature helps in monitoring and managing cluster performance. | | [Usage metrics](/reference/data-collection/usage-metrics.md) | There are three main usage metrics that have an impact on Camunda 8 pricing. It is important to understand these definitions, their impact on billing, and how to retrieve them. | | [Camunda 8 SaaS status](/components/saas/status.md) | Camunda provides a status page where you can check the current and past service availability of Camunda 8 SaaS. You can also subscribe to updates via Atom and RSS feeds to receive notifications about service status changes. | ## Security and compliance ### Compliance At Camunda, we're committed to Information Security, Privacy and Compliance. Our mission is to establish trust through transparency. - Visit the [Camunda Trust Center](https://camunda.com/trust-center/) to learn more about our standards and certifications, including SOC 2 compliance, ISO/IEC 27001 certification, and GDPR Compliance. - Camunda is a member of the [Cloud Security Alliance](https://cloudsecurityalliance.org/star/registry/camunda/services/camunda). ### Data retention In Camunda 8 SaaS, [data retention](/components/saas/data-retention.md) strategies are implemented. This is necessary as the amount of data can grow significantly overtime. These settings are a balance between performance and usability. ### Data locations See [data locations](data-locations.md) to learn more about where your Camunda 8 SaaS data is located and how data is handled. ### Access controls Camunda 8 SaaS supports the following access controls. | Access control type | Description | | :----------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Single sign-on (SSO)](/components/saas/organization/external-sso.md) | SSO is available for Enterprise plans, using Identity as a bridge between an OpenID Connect (OIDC) provider and the Camunda platform. | | [OAuth](/components/saas/clusters/manage-api-clients.md) | The OAuth service is used to allow client applications to interact with Zeebe in SaaS from the outside. Every client application must authenticate itself using an OAuth Flow. | | [Role based access (RBAC)](/components/hub/organization/users-and-roles.md) | Camunda 8 SaaS supports RBAC through a system of roles and permissions.Each role provides a different level of access to Camunda 8 components, allowing organizations to control user permissions based on their responsibilities. | | [Resource-based authorization](/components/saas/organization/resource-based-auth.md) | Legacy feature for clusters before version 8.8. Resource authorizations allow you to control the level of access a user has to a particular resource in the system. | :::note In Enterprise plans, the hostname section of the email address for invites can be restricted to meet your internal security policies. To learn more, [contact Camunda support](https://camunda.com/services/support/). ::: --- ## Enable secure connectivity This guide explains how to enable secure connectivity (AWS PrivateLink) for an AWS-hosted Camunda 8 SaaS Orchestration Cluster. Secure connectivity must be enabled per cluster. For a conceptual overview, see [secure connectivity (AWS PrivateLink)](./index.md). ## Prerequisites Before enabling secure connectivity: - The cluster must be hosted in AWS. - The cluster must be version 8.8.0+. - You must have [sufficient permissions](/components/hub/organization/users-and-roles.md#roles-and-permissions) to manage clusters in Camunda Hub. - You must know the AWS account IDs or ARNs that should be allowed to connect. - Your organization must be on an Enterprise plan. On the AWS side, you must have: - An existing AWS VPC. - Permission to create VPC interface endpoints. - Appropriate security group configuration. For instructions on creating a VPC interface endpoint, see the [AWS documentation on configuring an interface VPC endpoint](https://docs.aws.amazon.com/vpc/latest/privatelink/create-interface-endpoint.html). ## Enable secure connectivity for a cluster 1. Open Camunda Hub. 1. In the left navigation, click **Environments**, and then click **Clusters**. 1. Select a cluster. 1. Open the **Private networking** tab. The **Private networking** tab is available only for clusters hosted in AWS. It is not displayed for clusters hosted in other cloud providers. 1. Select **Activate PrivateLink endpoint service**. ### Allowed principals 1. In **Principal ARN**, enter the ARN of an AWS principal that should be allowed to connect. For supported principal types, see the [AWS documentation on configuring endpoint service permissions](https://docs.aws.amazon.com/vpc/latest/privatelink/configure-endpoint-service.html#add-remove-permissions). 2. Select **Add principal**. 3. Repeat for additional principals as needed. 4. Select **Next**. Validation requirements for principal ARNs are described in [validation and activation requirements](#validation-and-activation-requirements). ### Supported regions The AWS region where the Orchestration Cluster is located is always supported and is preselected by default. You can add additional AWS regions to allow cross-region endpoint connections. Cross-region connectivity may increase network latency and incur additional AWS charges. 1. Review the cluster's AWS region (preselected). 2. Optionally add additional regions to allow cross-region endpoint connections. 3. Select **Activate service**. After activation, Hub provisions a VPC endpoint service for the cluster and displays the connection details. ## Validation and activation requirements When configuring and activating the PrivateLink endpoint service, Hub validates the provided values during each step: - At least one valid AWS principal ARN must be provided. - Principal ARNs must follow a valid AWS ARN format. - At least one supported region must be configured. - The cluster’s AWS region is preselected by default. You cannot activate the service until all required fields are completed. ## Activation behavior After selecting **Activate service**, Hub provisions the VPC endpoint service for the cluster. The service status is displayed in the **Service details** section. Provisioning may take up to 10 minutes. During provisioning, the endpoint service is not available for new VPC endpoint connections. ## View connection details After activation, the **Private networking** tab displays the **Service details** section. This section shows: - **Status** (for example, Ready). - **Service name** (VPC endpoint service name). - **Service region**. - **Service type** (Interface). - **Private DNS name** (generated by Camunda for the endpoint service). - **Allowed principals**. - **Supported regions**. To connect from AWS, select **Create interface VPC endpoint connections in AWS**. Endpoint connections are created in your AWS account. When creating a VPC interface endpoint, use the **Service name** shown in the **Service details** section. For detailed instructions, see the [AWS documentation on creating an interface endpoint](https://docs.aws.amazon.com/vpc/latest/privatelink/create-interface-endpoint.html). The **Endpoint connections** section lists VPC interface endpoint connections created in AWS that target this cluster’s VPC endpoint service. For each connection, Hub displays: - The VPC endpoint identifier. - The connection status (for example, Pending or Available). New endpoint connections appear in this section after they are created in AWS. ## Manage allowed principals and regions After activation, you can modify the configuration from the **Private networking** tab. In the **Service details** section: - Select the edit icon next to **Allowed principals** to add or remove AWS principal ARNs. - Select the edit icon next to **Supported regions** to add or remove regions. Changes apply to new VPC endpoint connection attempts. Removing a previously allowed principal does not invalidate existing VPC endpoint connections. ### Endpoint connection approval VPC endpoint connections are automatically approved when the AWS principal creating the interface endpoint is included in the **Allowed principals** list. You don’t need to manually approve endpoint connections. ### Removing supported regions Removing a supported region does not affect existing VPC endpoint connections that were created using that region. Existing endpoint connections remain available. ## Create a VPC interface endpoint in AWS After activating the PrivateLink endpoint service in Hub: 1. Copy the **Service name** from the **Service details** section. 2. In your AWS account, create a VPC interface endpoint that connects to this service. 3. In the Amazon VPC console, open the **Create endpoint** wizard, then: - **Select a category** > **PrivateLink Ready partner services**. - Do not select **AWS services**. Otherwise, the Camunda service will not appear. 4. Configure subnets, security groups, and optional private DNS according to your AWS requirements. AWS-side provisioning must follow the standard AWS PrivateLink process. For detailed instructions, see the [AWS documentation on creating an interface endpoint](https://docs.aws.amazon.com/vpc/latest/privatelink/create-interface-endpoint.html), which also covers endpoint configuration and validation. ## Deactivate secure connectivity In the **Private networking** tab, select **Deactivate service** to remove the VPC endpoint service for the cluster. Public connectivity remains available. ## View-only access If you do not have permission to manage private networking for a cluster: - The **Activate PrivateLink endpoint service** button is not displayed. - The **Deactivate service** option is not displayed. - Edit options for **Allowed principals** and **Supported regions** are hidden. You can still view the **Service details** and **Endpoint connections** sections. ## Limits You can create up to 10 VPC endpoint connections per cluster. For organization-wide limits and adjustments, see [secure connectivity (AWS PrivateLink)](./index.md). --- ## Secure connectivity (AWS PrivateLink) Secure connectivity allows you to connect to Camunda 8 SaaS Orchestration Clusters from your AWS Virtual Private Cloud (VPC) using AWS PrivateLink. When enabled, traffic from your AWS VPC to an Orchestration Cluster is routed over private AWS networking rather than the public internet. Secure connectivity: - Applies per cluster. - Is available only for AWS-hosted Orchestration Clusters. - Supports inbound connectivity only — it enables private access from your AWS VPC to Camunda, but does not provide outbound private connectivity from Camunda to your services. - Adds a private connectivity path. Public endpoints remain enabled. - Is available to Enterprise customers. ## How it works ![AWS PrivateLink architecture](./img/aws-privatelink-diagram.jpg) Secure connectivity uses AWS PrivateLink to establish a private network path between your AWS VPC and the Camunda-managed cluster infrastructure. When you enable secure connectivity for a cluster: - Camunda provisions a VPC endpoint service for that cluster in the cluster’s AWS region. - You create one or more VPC interface endpoints in your AWS account that connect to the endpoint service. - Traffic from resources in your VPC (for example, job workers or inbound connectors) is routed privately to the cluster. Each cluster has its own VPC endpoint service and dedicated networking components. Access to the Orchestration Cluster is handled through dedicated load balancing and API gateway components. Secure connectivity relies on standard AWS PrivateLink functionality. For an overview of AWS PrivateLink concepts and terminology, see [the AWS documentation](https://docs.aws.amazon.com/vpc/latest/privatelink/what-is-privatelink.html). ## Connect from your AWS VPC via PrivateLink At a high level: 1. Enable secure connectivity for a cluster in Hub. 2. Review the VPC endpoint service details provided by Camunda (for example, service name, service type, region, and private DNS name). 3. Create one or more VPC interface endpoints in your AWS account and configure the required security groups. 4. Optionally configure private DNS for the endpoint connection in AWS. Enabling private DNS provides a seamless HTTPS experience. 5. Test connectivity from resources inside your VPC. Camunda owns and operates the VPC endpoint service and the associated cluster-side infrastructure. You own and manage resources in your AWS VPC, including: - VPC interface endpoints. - Security groups. - Routing and DNS configuration. - AWS permissions and quotas. For step-by-step Camunda Hub instructions, see [Enable secure connectivity for a cluster](./enable-secure-connectivity.md). ## Security and isolation Secure connectivity restricts private access to your cluster using AWS PrivateLink and an allowlist of AWS principals. When enabling secure connectivity, you define one or more allowed AWS principals (AWS account IDs or ARNs). Only those principals can create VPC endpoints that connect to your cluster’s endpoint service. Requests from AWS accounts that are not explicitly allowed cannot establish a PrivateLink connection. For each cluster: - A separate VPC endpoint service is provisioned. - Cluster-specific networking components are provisioned. Private connectivity does not share entry components across clusters. - Access to the Orchestration Cluster is handled through the cluster's API gateway layer. Traffic between your VPC and the cluster’s API gateway layer is encrypted in transit using TLS. TLS terminates at the cluster’s API gateway layer. Traffic within the cluster follows the same model as public connectivity. ## Limits The following limits apply: - Up to 10 VPC endpoint connections per organization (adjustable on request). - Up to 10 VPC endpoint connections per cluster. Contact Camunda support if you require higher limits. ## Supported connectivity modes The following combinations are supported: | Private connectivity | Public connectivity | Supported | | -------------------- | ------------------- | --------- | | Disabled | Enabled | Yes | | Enabled | Enabled | Yes | | Enabled | Disabled | No | Private-only connectivity is not currently supported. Public connectivity remains enabled even when secure connectivity is configured. ## Public and private connectivity When secure connectivity is enabled, public connectivity remains available. - Orchestration Cluster components like Operate, Tasklist, and Admin can still be accessed using their public URLs. - When creating client credentials for a cluster, you can choose which connectivity type to use: - Public connectivity, which uses the public hostnames shown for the credentials. - Private connectivity, which uses the private DNS hostname of your VPC interface endpoint instead of the public cluster hostname. ### Private DNS hostname When you create a VPC interface endpoint in AWS, the endpoint is assigned a private DNS hostname. This hostname resolves to the private network address of the endpoint within your VPC and is used to route traffic through AWS PrivateLink. When connecting to your Camunda cluster using secure connectivity, replace the `{PRIVATE_DNS}` placeholder in the cluster endpoint URL with the private DNS hostname of your VPC endpoint. You can find this hostname in the details of your VPC interface endpoint in AWS. For more information, see the AWS documentation on VPC interface endpoints. ## What secure connectivity does not change Secure connectivity: - Does not change data location or backup regions. - Does not affect encryption at rest or key management. - Does not provide outbound private connectivity from Camunda to your services. - Does not replace IP allowlists or other access control features. If an IP allowlist is configured, it continues to apply to connections made through private connectivity. For more information, see [Manage IP allowlists](/components/saas/clusters/manage-ip-allowlists.md). Secure connectivity changes only the network path used for inbound connections from your AWS VPC to the Orchestration Cluster. --- ## Camunda 8 SaaS status Camunda 8 SaaS is a hosted service for the Camunda 8 stack that runs on the Google Cloud Platform (GCP). Like any service, it might occasionally undergo availability changes. When availability changes, Camunda makes sure to provide you with a current service status. To see current and past service availability, visit [Camunda 8 SaaS Status](https://status.camunda.io). ## Subscribe to updates Don’t want to check the service status page manually? Get notified about changes to the service status automatically. To receive service status updates: 1. Go to the [Camunda 8 SaaS Status](https://status.camunda.io) page and click **SUBSCRIBE TO UPDATES**. 1. Select **Atom and RSS feeds**. 1. Add the feed URL to your favourite Atom/RSS reader. 1. After you subscribe to updates, you are notified whenever a service status update is posted. ## Support Support can be requested by subscription or enterprise customers via [JIRA](https://jira.camunda.com/projects/SUPPORT/). Otherwise, [contact us](/reference/contact.md). For more information about Enterprise support and additional support resources, see [Enterprise Support](https://camunda.com/services/support/). --- ## Tasklist API changes Tasklist in Camunda 8.10 and later uses only the [Orchestration Cluster REST API](../../apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). The legacy Tasklist V1 API and the Tasklist V1 UI mode were removed in 8.10. For the release-level summary of these removals, see the [8.10 release announcement](/reference/announcements-release-notes/8100/8100-announcements.md#removal-of-legacy-apis-tasklist-v1-dependent-features-and-zeebe-process-test). ## What changed in 8.10 - Tasklist always uses the Orchestration Cluster REST API. - The Tasklist V1 mode toggle is no longer available in SaaS or Self-Managed clusters. - Features that depended on Tasklist V1 are no longer available in the current version. ## Removed legacy behavior The following behavior was tied to Tasklist V1 and is no longer available in 8.10 and later: - Job worker-based user tasks - Draft variables - User task access restrictions - Public start forms - Advanced process filtering that depended on Tasklist V1 - Task context description and context variables - The Tasklist-specific permission and visibility model from V1 ## Recommended replacements - Use the [Orchestration Cluster REST API](../../apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) for task, form, and process interactions. - Use [user task authorization](./user-task-authorization.md) for current Tasklist access control. - Use the [migration guide for Camunda user tasks](../../apis-tools/migration-manuals/migrate-to-camunda-user-tasks.md) if you still have job worker-based user tasks. - Use [migrating to the Orchestration Cluster REST API](../../apis-tools/migration-manuals/migrate-to-camunda-api.md) if you still have integrations that call the removed Tasklist API. ## Historical timeline | Version | Status | | :----------- | :---------------------------------------------------------------------------------------------------------- | | Camunda 8.8 | V2 API is the default and recommended option. V1 API is deprecated but remains available via configuration. | | Camunda 8.9 | V1 API remains deprecated and is not recommended for new implementations. | | Camunda 8.10 | V1 API is removed. V2 API is the only available option. | --- ## Introduction to Tasklist Tasklist is a ready-to-use application to rapidly implement business processes alongside [user tasks](/components/modeler/bpmn/user-tasks/user-tasks.md). With Tasklist you can orchestrate human workflows critical to your business and reduce time-to-value for your process orchestration projects with an out-of-the-box interface. As you model and run business processes using BPMN, users are notified in Tasklist when they're assigned a task, and run the work that's required for the specific task. Also, tasks shows up in Tasklist whether the preceding step ran automatically or an [AI agent](/reference/glossary.md#ai-agent) produced the result a person now acts on. You are not limited to use the [Tasklist user interface](/components/tasklist/userguide/using-tasklist.md). You can extend your case or build a fully custom application using the [Orchestration Cluster API](../../apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). Learn more in the [frontend developer documentation](/apis-tools/frontend-development/01-task-applications/01-introduction-to-task-applications.md). --- ## User task access restrictions were removed User task access restrictions were removed in Camunda 8.10 together with Tasklist V1. For the release-level summary of this removal, see the [8.10 release announcement](/reference/announcements-release-notes/8100/8100-announcements.md#removal-of-legacy-apis-tasklist-v1-dependent-features-and-zeebe-process-test). Use [user task authorization](./user-task-authorization.md) and [authorization-based access control](/components/concepts/access-control/authorizations.md) for current Tasklist access control. If you are migrating from an older installation, review [Tasklist API changes](./api-versions.md) to understand which legacy V1 features were removed. --- ## User task authorization This page explains the recommended way to configure authorization for user tasks in Tasklist V2 when authorization is enabled. It describes how authorization affects Tasklist behavior and which permissions are required to perform common user task operations. ## When you need to configure user task authorization Configure user task authorization if all of the following apply: - Authorization is enabled in your cluster - You use Tasklist V2 - You want to control which users can view, claim, update, or complete user tasks If authorization is disabled, Tasklist does not enforce permission checks for user task operations. ## Recommended authorization model For Tasklist, the recommended authorization model combines: - Process-level permissions to grant broad access to user tasks that belong to a process - Task-level permissions for fine-grained access control on individual user tasks - The default task worker role for common user task operations With this approach, you can grant general access where appropriate while restricting access to specific tasks based on task properties, such as assignee or candidate groups. ## Authorization resources used by Tasklist Tasklist evaluates user task authorization using the following authorization resources: - `PROCESS_DEFINITION`: Controls access to user tasks that belong to a specific process definition (for example: `READ_USER_TASK`, `UPDATE_USER_TASK`, `CLAIM_USER_TASK`, `COMPLETE_USER_TASK`). - `USER_TASK`: Controls access to individual user tasks using property-based access control. For details about authorization resources, permission evaluation, and configuration, see [Authorization concepts](../concepts/access-control/authorizations.md). ## Default task worker role On new installations and upgrades, Camunda Identity automatically creates a default `task-worker` role. This role grants property-based `USER_TASK` permissions: - Permissions: - `READ` - `CLAIM` - `COMPLETE` - Scoped by: - `assignee` - `candidateUsers` - `candidateGroups` This lets typical task workers see, claim, and complete only the tasks they're responsible for. You can use this role as-is or create custom roles with similar property-based authorizations. :::note Upgrade behavior When you upgrade from Camunda 8.8 to 8.9, Identity creates only the default `task-worker` role automatically. Other roles are not created during upgrade. If needed, create additional roles in Identity and assign the appropriate permissions. ::: ## User task operations and required permissions The following table shows which permissions are required to perform common user task operations in Tasklist. The table reflects currently-implemented permissions that are enforced by Tasklist. | Operation | `USER_TASK` permission | `PROCESS_DEFINITION` permission | | ----------------------------------------- | ---------------------- | ------------------------------------------ | | Get user task (by key) | `READ` | `READ_USER_TASK` | | Search user tasks | `READ` | `READ_USER_TASK` | | Get user task form | `READ` | `READ_USER_TASK` | | Claim task | `CLAIM` | `CLAIM_USER_TASK` or `UPDATE_USER_TASK` | | Unassign task | `UPDATE` | `UPDATE_USER_TASK` | | Assign task (override assignee) | `UPDATE` | `UPDATE_USER_TASK` | | Complete task (with or without variables) | `COMPLETE` | `COMPLETE_USER_TASK` or `UPDATE_USER_TASK` | | Update user task | `UPDATE` | `UPDATE_USER_TASK` | Getting a user task form requires no separate permission on the form (`Resource` `READ`). Access to the user task includes access to its linked form. :::note Some operations listed in the table (for example, task reassignment) may not yet be available in the Tasklist UI. ::: ## How Tasklist evaluates permissions Tasklist relies on the Orchestration Cluster authorization model to control access to user tasks. Permissions are evaluated in two layers: 1. Process-level permissions on the `Process Definition` resource (`READ_USER_TASK`, `CLAIM_USER_TASK`, `COMPLETE_USER_TASK`, and `UPDATE_USER_TASK`). 2. Task-level permissions on the `USER_TASK` resource (`READ`, `UPDATE`, `CLAIM`, and `COMPLETE`), often combined with property-based access control on `assignee`, `candidateUsers`, and `candidateGroups`. When both layers are configured, process-level permissions take precedence: - If a user already has the required process-level permission on `Process Definition` for an operation (for example, `READ_USER_TASK`, `CLAIM_USER_TASK`, `COMPLETE_USER_TASK`, or `UPDATE_USER_TASK`), Tasklist does not require or evaluate additional `USER_TASK` permissions for that operation. - If the user does not have sufficient process-level permissions, Tasklist evaluates `USER_TASK` permissions instead, including property‑based authorizations based on task properties. ## Example: claim and complete group tasks To allow users to work on tasks that are assigned to their group: 1. Grant the `READ_USER_TASK` permission on the `PROCESS_DEFINITION` resource for the relevant process. 2. Grant the `CLAIM` and `COMPLETE` permissions on the `USER_TASK` resource using the `candidateGroups` property. This configuration allows users to see tasks for the process and claim or complete only those tasks for which they are listed as a candidate group member. ## Learn more - [Authorization concepts](../concepts/access-control/authorizations.md) --- ## Access control(Userguide) If authorization control is enabled for your Orchestration Cluster, users require the following authorizations to work with Tasklist. :::note You can assign these [in the Admin UI](/components/admin/authorization.md#create-an-authorization-in-admin). See [the introduction to authorizations](/components/concepts/access-control/authorizations.md#available-resources) for a list of all available authorizations. ::: ## Mandatory authorizations The following mandatory authorizations are required to work with Tasklist: | Authorization type | Resource type | Resource ID | Permission | | :----------------------------- | :------------------- | :--------------------------------------------------------------------------------- | :--------------- | | Component access for Tasklist. | `Component` | `tasklist` or `*` (for access to all web components). | `ACCESS` | | Read user tasks. | `Process Definition` | ID of the respective BPMN process definition or `*` (for all process definitions). | `READ_USER_TASK` | ## Optional authorizations The following optional authorizations can also be defined: | Authorization type | Resource type | Resource ID | Permission | | :---------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------- | :--------------------------------------------------------------------------------- | :------------------------------------------------------------- | | View task history records. | `AUDIT_LOG` | `USER_TASKS` or `*` for all categories. | `READ` | | Assign and complete user tasks | Process Definition | ID of the respective BPMN process definition or `*` (for all process definitions). | `CLAIM_USER_TASK`, `COMPLETE_USER_TASK`, or `UPDATE_USER_TASK` | | View BPMN diagrams | Process Definition | ID of the respective BPMN process definition or `*` (for all process definitions). | `READ_PROCESS_DEFINITION` | | View [`USER_TASKS` operations](../../audit-log/overview/recorded-operations.md#user_tasks-operations) for instances of a specific process definition. | `PROCESS_DEFINITION` | A process definition ID or `*` for all process definitions. | `READ_USER_TASK` | | View task history records related to specific tasks the user has access to based on task properties. | `USER_TASK` | A user task property (`assignee`, `candidateUsers`, `candidateGroups`). | `READ` | | Authorization type | Resource type | Resource ID | Permission | --- ## Audit task history Audit [task history](../../audit-log/overview.md) in Camunda 8 Tasklist. ## Prerequisites To follow the steps in this guide, you must be [authorized to view task history records](../../audit-log/overview/access-control.md). ## Audit task history 1. On the **Tasks** page, click a task to audit. 2. In the tasks detail view, click the **History** tab. 3. To sort the log, click a column header. 4. To see the details of a particular operation, click the info icon at the end of the row. ## Next steps - [Learn about `USER_TASKS` operations](../../audit-log/overview/recorded-operations.md#user_tasks-operations). - [Learn about the operation data structure in the history table.](../../audit-log/overview/operation-structure.md) --- ## Defining task priorities You can add prioritization to [user task elements](/components/modeler/bpmn/user-tasks/user-tasks.md) by specifying a priority value for a user task. This determines the task's importance in relation to other tasks within processes. - The task priority is an **integer** value ranging from 0 to 100, with a default value of 50. - A higher priority value indicates higher importance. When displayed in Tasklist, priority values are mapped to the following default labels: | Priority value | Default label | | :------------- | :------------ | | 0-25 | Low | | 26-50 | Medium | | 51-75 | High | | 76-100 | Critical | These labels give Tasklist users a clear view of task priority, making it easier to assess a task's urgency. This also makes sorting and filtering simple, helping users prioritize the most important tasks. ## Step-by-step guide This step-by-step guide shows you how to define task priorities for Tasklist users. ### 1. Model a BPMN process Start by modeling your BPMN process in [Camunda Hub](/components/hub/workspace/modeler/index.md) or [Desktop Modeler](/components/modeler/desktop-modeler/index.md), ensuring that the required user tasks are defined within the process. ### 2. Set a priority for user tasks During user task configuration you can specify a priority value. You can also define the value using an [expression](/components/concepts/expressions.md). The priority value determines the task's importance relative to other tasks. ![set-user-task-priority-in-modeler](img/modeler-user-task-priority.jpg) ### 3. Deploy and start the process After the process is fully defined and all configurations are complete, the process can be deployed and started. The priority values are now associated with each user task within the process. ### 4. View task priority in Tasklist Tasklist users can view the tasks assigned to them within their task list. Each task card displays the assigned priority label, ensuring users have a clear understanding of the task's importance and priority. ![Task cards showing priority labels in Tasklist](img/tasklist-tasks-with-priority.png) ### 5. Sort tasks by priority Task users can sort tasks by priority. This helps users organize their workload by focusing on urgent items first. ![Sorting tasks by priority in Tasklist](img/tasklist-tasks-with-priority-sorting.png) --- ## Localization When creating processes for audiences in non-English speaking regions, you can localize the Tasklist interface to the appropriate language. To do this, design [user task forms](/components/hub/workspace/modeler/modeling/utilize-forms.md) in the local language, and set the Tasklist interface to use the same language. Tasklist supports the following four languages by default: - English - French - German - Spanish The interface language is automatically determined by the device language settings of the user. If the device's language is not supported, the interface will default to English. ## Change Tasklist language settings ![Language options in the Tasklist user menu](img/tasklist-language-settings.png) To change the language used in Tasklist: 1. Click your user avatar in the top-right corner of the header to open the user menu. 2. Under **Language**, select your preferred language. 3. The Tasklist interface updates to the selected language. --- ## Managing tasks When you've successfully logged in, you'll see the **Tasks** page: ![tasklist-start-screen](../img/tasklist-start-screen_light.png) ## Assign tasks To find relevant tasks you can use [filters](./using-filters.md). Select the **Unassigned** filter list and assign a task to yourself using the **Assign to me** button on the top panel: ![tasklist-claim](img/tasklist-claim_light.png) ## Work on assigned tasks Select the **Assigned to me** filter list to see the tasks that are assigned to you. Select a task to work on it. ![tasklist-claimed-by-me-list](img/tasklist-claimed-by-me-list_light.png) ## Complete a task When a task is assigned to you, you can complete the task by filling out the given form, and clicking on the **Complete Task** button. ![tasklist-completing-task](img/tasklist-completing-task_light.png) There are also cases where no form is available. In these cases, you have to add and/or update variables directly. ![tasklist-with-variables-claimed-by-me](img/tasklist-with-variables-claimed-by-me_light.png) Always choose a list of tasks with a specified status. Then, select the task you want to work on. Change variables as needed and begin completion with the **Complete Task** button. Completed tasks will be shown in the [**Completed** task list](#view-completed-tasks). ### Add and update variables Update variables in the **Variables** section by adjusting their text field. To add a new variable, click **Add Variable**. ![tasklist-add-new-variable](img/tasklist-add-new-variable.png) ## View completed tasks You will now see the completed task by selecting the **Completed** task list: ![tasklist-task-completed](img/tasklist-task-completed_light.png) ## Options ### Auto-select first available task If this is enabled, whenever you open tasks, change filter options, or complete a task, Tasklist will automatically select the first task in the list. --- ## Starting processes You can start processes on demand using Tasklist. To do this, click **Processes** in the navigation menu. All the processes you have access to start are listed on the **Processes** page. ![tasklist-processes](img/tasklist-processes.png) In the **Search** box, search for the process definition ID of the process you want to start. ![tasklist-processes-search](img/tasklist-processes-search.png) Tasklist no longer supports the legacy V1 process filters that were available during the migration period before 8.10. To start a process, click **Start process** on the process you want to start. ![tasklist-processes-start](img/tasklist-processes-start.png) If the start event of this process contains a [linked or embedded Camunda Form](/components/hub/workspace/modeler/modeling/advanced-modeling/form-linking.md), a modal window containing that form will automatically open. ![tasklist-processes-start-with-form](img/tasklist-processes-start-with-form.png) Tasklist will then wait for the process to be executed. If the process generates a task, you will be redirected to the generated task. :::info To share a process that has a start form with users inside your organization, but not with external users, click **Copy link** ![Copy link button](img/tasklist-processes-share-button.png) in the start process dialog to copy a link to the process. This link will only be accessible to users that already have access to Tasklist. ::: ## Not seeing a process There could be multiple reasons why you are not seeing any process in the **Processes** tab: - There is no process deployed to your environment. - Permissions to start a process are managed via [Authorizations](/components/concepts/access-control/authorizations.md). It is likely your user does not yet have privileges to create process instances. For all the above scenarios, contact your administrator to understand why no processes are displayed. ## Public start forms Public start forms were removed in Camunda 8.10 together with Tasklist V1. To start processes with forms in the current version, use authenticated Tasklist starts, or build your own public-facing application with [Camunda Forms](/components/hub/workspace/modeler/modeling/utilize-forms.md) and the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). --- ## Using filters When you open the **Tasks** page, you will see the list of all tasks. This is a starting point for managing your workload. ![tasklist-all-tasks](img/task-filters/tasklist-all-tasks.png "All open tasks") ## Apply a filter As you browse through the list, you can utilize filters to locate specific tasks and organize your workload. ### Select a filter You can select one of the default filters or [create a new filter](#create-new-filter). Default filters: - All open tasks - Assigned to me - Unassigned - Completed tasks ![tasklist-default-filters](img/task-filters/tasklist-default-filters.png "List of the default filters") ## Create new filter To create a new filter, click to apply a new filter. This action will open a filter dialog. In the dialog, you can define various rules based on task attributes. The supported attributes are: - Assignees and candidate groups - Status - Processes tasks belong to - Dates (due date and follow up date) - Task ID - Business ID - Task variables ![tasklist-filter-dialog-with-advanced-options](img/task-filters/tasklist-filter-dialog-with-advanced-options.png "Available filter attributes") After defining the rules, click **Apply** to apply the filter. The system will then filter the tasks according to your criteria, displaying only the relevant tasks. ## Business ID filter A [business ID](/components/concepts/process-instance-creation.md#business-id) is a domain-specific process instance identifier, such as an order number, case reference, or ticket ID. Starting in 8.10, you can filter tasks by business ID directly in the Tasklist filter dialog. To filter by business ID, open the filter dialog and use the **Business ID** field. | UI operator | Behavior | Wildcards | | :------------ | :------------------------------------------------- | :---------------------------------------------------------- | | **Equals** | Exact match on the business ID value. | — | | **Contains** | Pattern match. | `*` matches multiple characters, `?` matches one character. | | **Is one of** | Matches any business ID in a comma-separated list. | — | For additional operators (`$neq`, `$exists`, `$notIn`), use the [search user tasks API](/apis-tools/orchestration-cluster-api-rest/specifications/search-user-tasks.api.mdx) with the `businessId` filter field. ## Save filter for future use If you need to use the filter again, you can save it: 1. Click **Save**. 2. You will be prompted to enter a name for the filter. Choose a descriptive name that will help you recognize it later. 3. Click **Save and apply**. ![tasklist-save-filter-dialog](img/task-filters/tasklist-save-filter-dialog.png "Add a descriptive name for the filter") The next time you need this filter, select it from your saved filters. ![tasklist-applied-filter-tasks](img/task-filters/tasklist-applied-filter-tasks.png "List of tasks for the applied filter") :::note Filters you create are saved locally to your device. Therefore, while you can access and reuse them on the same device, these filters will not be available if you switch to a different device. ::: ## Update saved filter You can edit or [delete](#delete-a-filter) filters that you saved. ![tasklist-saved-filter-options](img/task-filters/tasklist-saved-filter-options.png "Saved filter options") To change the criteria of an existing filter, take the following steps: 1. Choose the filter you want to update from your list of saved filters, and click the three vertical dots next to its name. 2. Click **Edit**. This action will open the filter dialog with the current settings of the filter. 3. Update the filter criteria as needed. 4. Confirm the changes by clicking **Save and apply**. ![tasklist-edit-filter-dialog](img/task-filters/tasklist-edit-filter-dialog.png "Edit filter details") ## Delete a filter To delete a filter, take the following steps: 1. Choose the filter you want to delete from your saved filters, and click the three vertical dots next to its name. 2. Click **Delete**. 3. Click **Confirm deletion** in the dialog. ![tasklist-delete-filter-dialog](img/task-filters/tasklist-delete-filter-dialog.png "Confirm filter deletion") After deletion, the default filter will be applied, showing the full list of tasks again. --- ## Overview(Userguide) The user interaction with a task may involve making updates, adding variables, filling out a [Camunda Form](/components/hub/workspace/modeler/modeling/utilize-forms.md), or simply reviewing and completing the task. User tasks can be automatically assigned to users and groups in the BPMN process, or they must be self-assigned from Tasklist. Once assigned to a user, the task can be completed. The user can unassign the task if they do not intend to work on it. Tasklist has two main pages: - [Tasks page](#open-tasklist) to manage tasks. - [Processes page](./starting-processes.md) to start processes. ## Open Tasklist From Camunda Hub, you can open Tasklist in any [environment](/components/concepts/environments.md) you have access to: 1. Log in to Camunda Hub. 1. In the left navigation, click **Environments**, and then select an environment. Each environment has its own instance of Tasklist. 1. Under **Applications**, on the **Tasklist** card, click **Open**. This opens Tasklist for the environment in a new tab. You can also expand an environment in the left navigation, and select Tasklist from its applications. :::tip If the environment is paused, you must [resume it](/components/hub/organization/manage-environments/index.md#resume-a-paused-environment) to access its applications. ::: ## Tasks queue The **Tasks** page lists all tasks available to a user or user group and allows users to assign themselves a task from the list to work on. On the left side of the page you can see task filters and the queue of tasks. On the right side, details of the selected task are displayed. See an overview of the page structure below: The queue shows the preview of available tasks with the following information: - Task name - Name of the process the task belongs to - [Business ID](/components/concepts/process-instance-creation.md#business-id) carried by the task, if one was captured from the process instance when the task was created - Assignee - Priority - Creation date - Due date - Follow up date ## Task details Select a task from the list to view its details. The task includes a form that has to be filled out and submitted to complete a task. ![tasklist-task-details-form](./img/tasklist-task-details-form.png "Task completion form") If the task doesn’t have a form, it will display task variables. ![tasklist-with-variables-claimed-by-me](img/tasklist-with-variables-claimed-by-me_light.png "Task variables") If the task captured a [business ID](/components/concepts/process-instance-creation.md#business-id) from its process instance when it was created, it's also shown in the task details. A task created before its process instance had a business ID displays without one, even if the process instance is later assigned one. ### View process diagram From the task detail page you can switch to the **Process** tab. This provides a visual representation of the BPMN diagram the task is part of, and may help you understand how an individual task fits into the larger workflow, what activities happened earlier, and what’s coming next. ![tasklist-process-diagram](./img/tasklist-task-details-process-diagram.png "Process diagram preview") ## Filtering To group tasks and quickly find relevant assignments, use [task filters](./using-filters.md). [![tasklist-default-filters](img/task-filters/tasklist-default-filters.png "Task filters")](./using-filters.md) ## Ordering Click the order icon ![order-icon](img/order-icon.png) to order the tasks. You can arrange them by the date of creation, due date, follow-up date, or priority. The follow-up date defines the latest time you should start working on a task. The due date provides a deadline for when the task should be finished. The priority defines urgency of a task in relation to others. ![tasklist-task-ordering](img/tasklist-task-ordering.png "Order tasks by dates") ## Document handling Tasklist users may view and download files displayed in the task's form. See additional details and limitations in [document handling](/components/document-handling/getting-started.md). --- ## Wait states A wait state describes what an active process element instance is waiting for before it can continue, so you can tell expected waiting from a stalled instance. ## What wait states show When you inspect an active element, Camunda surfaces the wait state and its details. For example, the message a receive task expects or the due date of a timer. Wait states remain a subset of active instances, not a new top-level state. Camunda surfaces this information at the element instance level. Use wait states to: - **Distinguish healthy waiting from stalled execution:** Determine whether an instance is waiting as designed or needs intervention. - **Detect worker availability issues early:** See whether a job is waiting for activation or is already in progress, and for how long. - **Speed up troubleshooting:** See what an instance is waiting for without digging into logs. ## Supported wait state types Camunda 8.10 tracks the following wait state types: | Wait state type | Applies to | Details surfaced | | :-------------- | :---------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------- | | Timer | Intermediate timer catch event | Due date, repetitions | | Message | Intermediate message catch event, receive task | Message name, correlation key | | Signal | Intermediate signal catch event | Signal name | | Conditional | Intermediate conditional catch event | Condition expression, condition events (for example, variable create or update) | | User task | User task | Task key, due date | | Job | Service task, send task, script task, business rule task (when job-based), and execution and task listeners | Job key, job type, job kind, retries, and `listenerEventType` (populated only for listener-type jobs, otherwise `null`) | Boundary events, event-based gateways, and parallel merging gateways are not tracked in 8.10. ## Impact on secondary storage When wait state tracking is active, Camunda writes a record to [secondary storage](/self-managed/concepts/secondary-storage/index.md) for every applicable element instance. Wait state tracking does not measurably increase secondary storage usage. You can [configure wait state tracking](/self-managed/concepts/wait-states/configure.md) in Camunda 8 Self-Managed to disable it. ## Access control To view wait states, you must have the relevant authorization: | Authorization type | Resource type | Resource ID | Permission | | :----------------------------------------------------- | :------------------- | :---------------------------------------------------------- | :---------------------- | | View wait states for instances of a process definition | `PROCESS_DEFINITION` | A process definition ID or `*` for all process definitions. | `READ_PROCESS_INSTANCE` | Wait state data is isolated by tenant. You can only view wait states for [tenants](/self-managed/concepts/multi-tenancy/index.md) you are authorized to access. ## Get started - [View wait states in Operate](../operate/userguide/view-wait-states.md) - [Use the Camunda REST API to search wait states](/apis-tools/orchestration-cluster-api-rest/specifications/search-element-instance-wait-states.api.mdx) --- ## Architecture(Technical-concepts) There are four main components in Zeebe's architecture: - Clients - Gateways - Brokers - Exporters ![zeebe-architecture](assets/zeebe-architecture.png) In Camunda 8, you work exclusively with clients. Gateways, brokers, and exporters are pre-configured to provide the service, but are not accessible. In local or private cloud deployments, all components are relevant. ## Clients Clients send commands to Zeebe to: - Deploy processes - Carry out business logic - Start process instances - Publish messages - Activate jobs - Complete jobs - Fail jobs - Handle operational issues - Update process variables - Resolve incidents Client applications can be scaled up and down separately from Zeebe. The Zeebe brokers do not execute any business logic. Clients are libraries you embed in an application (e.g. a microservice that executes your business logic) to connect to a Zeebe cluster. Clients connect to the Zeebe Gateway via a mix of REST and [gRPC](https://grpc.io). While REST can be served over any HTTP version, the gRPC part of the API requires an HTTP/2-based transport. To learn more about how REST is used in Zeebe, review the [Orchestration Cluster API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). To learn more about gRPC in Zeebe, review the [Zeebe API (gRPC)](/apis-tools/zeebe-api/grpc.md). Check out the [API & tools section](/apis-tools/working-with-apis-tools.md) to find clients in your programming language of choice or create your own. ### Job workers A job worker is a Zeebe client that uses the client API to first activate jobs, and upon completion, either complete or fail the job. ## Gateways A gateway serves as a single entry point to a Zeebe cluster and forwards requests to brokers. The gateway is stateless and sessionless, and gateways can be added as necessary for load balancing and high availability. ## Brokers The Zeebe Broker is the distributed workflow engine that tracks the state of active process instances. Brokers can be partitioned for horizontal scalability and replicated for fault tolerance. A Zeebe deployment often consists of more than one broker. It's important to note that no application business logic lives in the broker. Its only responsibilities are: - Processing commands sent by clients - Storing and managing the state of active process instances - Assigning jobs to job workers Brokers form a peer-to-peer network in which there is no single point of failure. This is possible because all brokers perform the same kind of tasks and the responsibilities of an unavailable broker are transparently reassigned in the network. ## Exporters The exporter system provides an event stream of state changes within Zeebe. This data has many potential uses, including but not limited to: - Monitoring the current state of running process instances - Analysis of historic process data for auditing, business intelligence, etc. - Tracking [incidents](/components/concepts/incidents.md) created by Zeebe The exporter includes an API you can use to stream data into a storage system of your choice. Zeebe includes an out-of-the-box [Elasticsearch exporter](https://github.com/camunda/camunda/tree/main/zeebe/exporters/elasticsearch-exporter), and other [community-contributed exporters](https://github.com/camunda-community-hub/awesome-camunda-platform-8) are also available. --- ## Batch operations(Technical-concepts) When you create a [batch operation](../../concepts/batch-operations.md), you provide filter criteria (such as process definition, version, or state) rather than a specific list of instances. The system then: 1. Distributes the operation across all partitions in your cluster 2. Each partition queries the secondary database to find matching instances 3. Executes operations in parallel across partitions 4. Tracks progress and aggregates results This distributed approach enables efficient processing of large numbers of instances while maintaining cluster performance. Batch operations execute concurrently with regular partition processing—they do not block it. Just as multiple process instances can run in parallel on the same partition, batch operations process their target instances alongside all other partition activity without interruption. ## Prerequisites To use batch operations, you need: - A Zeebe cluster with secondary storage configured (Elasticsearch or OpenSearch) - Appropriate [authorization permissions](../../concepts/batch-operations.md#authorization) for the operations you want to perform - Access to one of the following: - [Operate UI](/components/operate/operate-introduction.md) (for basic operations) - [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) (for full lifecycle management) - [Camunda Java client](/apis-tools/java-client/getting-started.md) (for programmatic access) ## Batch operation components A Zeebe batch operation always consists of two parts: - **The command:** The batch operation type determines the action performed on the process instances. For example, the type `MIGRATE_PROCESS_INSTANCE` means a request to migrate process instances to a new process definition version. Some batch types require more details, such as a migration plan for process instance migration or a modification plan for process instance modifications. - **The batch operation items:** The items are not directly defined at creation. Instead, a filter describes them. This filter is applied to the configured secondary database (for example, Elasticsearch) to identify matching process instances. ## Batch operation lifecycle Batch operations follow a structured lifecycle with distinct phases. Understanding these phases helps you monitor progress and troubleshoot issues. ### 1. Creation phase **What happens:** - User submits a batch operation request with filter criteria - System performs validation checks - The operation is distributed to all partitions in the cluster - A **leader partition** is designated (the partition that first processes the creation command) - All other partitions become **follower partitions** - A unique batch operation key is generated and assigned **Duration:** Milliseconds to seconds **What can go wrong:** See [Creation request validation](#creation-request-validation) below. ### 2. Initialization phase **What happens:** - Each partition queries the secondary database using the provided filter - Results are paginated and split into manageable chunks to avoid overwhelming exporters (each item in a chunk triggers a separate export operation) - The total number of items to process is determined and fixed at this point - Signals are sent to start the execution phase once initialization is complete **Duration:** Seconds to minutes, depending on the number of matching instances, cluster size, and whether other batch operations are already queued for initialization. If multiple batch operations are created, they are initialized sequentially. Each operation must finish its initialization phase before the next one begins. **API visibility:** In [Get Batch Operation](../../../apis-tools/orchestration-cluster-api-rest/specifications/get-batch-operation.api.mdx) API responses: - The `operationsTotalCount` becomes eventually accurate once initialization completes on all partitions - The batch operation `state` transitions from `CREATED` to `ACTIVE` once initialization finishes and execution is triggered **What can go wrong:** See [Initialization failures](#initialization-failures) below. #### Dependency on secondary storage Although the broker's internal state stores process instance data, the secondary storage is queried because: - RocksDB (our internal state database) is a key-value store, not optimized for complex queries. - The secondary database (for example, Elasticsearch) efficiently supports complex filtering. - The user interface enables filter-based batch creation, which RocksDB cannot support. - Pagination and large result set handling is better supported by secondary databases. ### 3. Execution phase **What happens:** - Each partition processes its assigned items independently - Individual commands (for example, `CANCEL`, `MIGRATE`) are executed on each process instance - Progress is tracked and can be monitored - Failed items are recorded with error details **Duration:** Minutes to hours (depending on the number of instances and operation type) **Key characteristics:** - **Fire-and-forget execution**: Individual operation commands are dispatched immediately without blocking. While the system does not wait for each command to complete before dispatching the next one, you can still monitor overall batch progress and view failed items through the monitoring APIs. - **Independent processing**: Each partition works independently, enabling parallel execution across the cluster **What can go wrong:** See [Execution failures](#execution-failures) below. ### 4. Completion phase **What happens:** - Follower partitions report completion or failure to the leader partition - Leader partition aggregates results from all partitions - Final status is determined (`COMPLETED` if at least one partition succeeded, `FAILED` if all failed) **Duration:** Seconds ## Error handling and recovery ### Creation request validation Before processing begins, the system performs several validation checks: - **Empty filter validation**: Filters cannot be empty or null - **Authorization validation**: User permissions are checked before operation creation - **Command-specific validation**: Each command type has specific requirements (for example, migration plan validity) Failed validations result in immediate rejection with specific error codes and messages (for example, `INVALID_ARGUMENT`, `NOT_AUTHORIZED`). ### Initialization failures Initialization can fail for several reasons: - **Network issues**: Connection problems with the secondary database - **Permission errors**: Insufficient authorization to query the database - **Configuration issues**: Secondary database not properly configured - **Query errors**: Invalid or malformed filter criteria **Error handling behavior:** The system automatically handles initialization failures based on the error type: - **Retryable errors**: Network issues, temporary database unavailability - Uses exponential backoff with configurable retry limits - Maximum delay and retry count can be configured - **Non-retryable errors**: Permission issues, configuration problems - Fail immediately without retries - **Adaptive sizing**: If record size limits are exceeded, page sizes are automatically reduced ### Execution failures Individual items may fail during execution for various reasons: - Process instance was completed or canceled after batch initialization - Incident was already resolved by another operation - Migration or modification plan is invalid for the specific instance - Authorization insufficient for the specific process instance The system marks failed items as `FAILED` and does not retry them within the same batch operation. ### Recovery strategies - **Retry failed batches**: Create a new batch operation with the same filter - **Partial recovery**: Use more specific filters to target only failed items - **Monitoring**: Use provided APIs to track which items failed and why ## Lifecycle management Batch operations support several lifecycle management operations: ### Suspend and resume - **Suspend**: Temporarily stops execution of a running batch operation - **Resume**: Restarts execution of a suspended batch operation - Operations can be suspended during initialization or execution phases ### Cancel - **Cancel**: Permanently stops a batch operation - Cannot be resumed once canceled - Partially processed items remain in their modified state ## Batch operations in distributed clusters ![distributed-batch-operation](assets/batch-operation.png) Zeebe clusters are distributed systems with multiple brokers and partitions. Each partition manages a subset of process instances. When a batch operation starts: 1. The batch operation (command + filter) is created and distributed to all partitions. 2. Each partition processes the batch independently: - It uses the filter to query relevant process instances from the secondary database. - It applies the batch command to each matched instance. 3. After all partitions finish, the final statuses are collected, and the batch is marked `COMPLETED`. This distributed, asynchronous approach allows parallel processing of many process instances. ### Important considerations #### Partition independence Each partition fetches and processes matching instances independently. At the start of very large batches, the total number of known items may vary until all partitions finish fetching. This is due to multiple paged queries to the secondary database. #### Leader-follower coordination The leader partition coordinates the overall operation status while follower partitions focus on execution. Inter-partition communication ensures consistent final status reporting. ## How to create and monitor batch operations You can create batch operations using: - **[Operate UI](/components/operate/operate-introduction.md)**: Start batch operations directly from the Operate interface for process instances and incidents - **[Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md)**: REST endpoints to create, manage, and monitor batches programmatically - **[Camunda Java client](/apis-tools/java-client/getting-started.md)**: APIs for batch operations in Java applications :::warning Important for Operate users When you start a batch operation from Operate UI, the actual instances processed may differ from those displayed in the filter preview. The batch operation uses filter criteria to query the secondary database at execution time, and results may have changed since the preview was generated. The count is fixed when the batch starts and will not include instances created afterward. ::: :::note Lifecycle management (suspend, resume, cancel) is only available via the REST API and Java client, not through Operate UI. More features may be added in future Operate versions. ::: ### Technical monitoring Batch operation performance can be tracked via [Grafana dashboards](/self-managed/operational-guides/monitoring/metrics.md#grafana). **Key metrics to monitor:** - Initialization time and success rate - Execution throughput and latency - Error rates by type and partition - Resource utilization during batch processing - Secondary database query performance ## Performance impact Batch operations share cluster resources with regular process instances, affecting each other's performance. Commands created by batch operations run with the same priority as user-generated commands. ### Performance considerations **During batch creation:** - RocksDB storage impact from batch metadata - Memory usage for operation state management **During initialization:** - Heavy querying of the secondary database - Network bandwidth usage for query results - Partition coordination overhead **During execution:** - Command processing load shared with regular operations - Potential backpressure on high-throughput scenarios - Resource contention with live process instances #### Best practices - **Timing**: Schedule large batch operations during low-traffic periods - **Sizing**: Break very large operations into smaller batches - **Monitoring**: Watch cluster performance metrics during batch execution - **Filtering**: Use precise filters to minimize unnecessary processing :::warning Large batch operations can temporarily impact cluster performance, especially during initialization when partitions query the secondary database individually and in parallel. ::: :::warning Heavy querying of the secondary database can notably affect its performance, especially for large batches with broad filters. ::: ### Configuration Batch operation behavior can be configured through broker settings under `camunda.processing.engine.batch-operations.*`. All settings are validated at startup, and invalid values will cause the broker to fail with a descriptive error message. :::note Default values are optimized for typical workloads. Only adjust these settings if you experience performance issues or have specific requirements. ::: #### Scheduler settings Controls how frequently the batch operation scheduler checks for work: | Parameter | Type | Default | Description | | ------------------- | -------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `schedulerInterval` | Duration | **`PT1S`** | How often the background scheduler runs to progress initialization and continuation of batch operations.**Impact:** Lower values provide faster responsiveness but increase CPU usage. | #### Chunking and pagination settings Controls how batch operations split large result sets into manageable pieces: | Parameter | Type | Default | Description | | ------------------- | ---- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `chunkSize` | int | **`100`** | Maximum number of items per chunk. This controls both the number of items written per chunk record during initialization and the number of items stored per chunk in RocksDB state.**Note:** The default value of 100 is a reasonable tradeoff between the number of records and exporter performance. Values greater than 3000 are not recommended, as the broker logs a warning. Higher values may lead to performance issues in the exporters and can exceed the 4 MB record size limit.**RocksDB optimization:** A chunk size of 3000 item keys results in approximately 24 KB (31 KB with overhead), which aligns well with RocksDB's 32 KB block size for efficient read/write performance. | | `queryPageSize` | int | **`10000`** | Page size when querying the secondary database during initialization.**For Elasticsearch/OpenSearch:** This interacts with the default 10,000 result window limit. | | `queryInClauseSize` | int | **`1000`** | Maximum number of keys in a single IN clause when querying by key list.**Use case:** Primarily for RDBMS-based secondary databases. | #### Retry and error handling settings Controls how the system handles transient failures when querying the secondary database: | Parameter | Type | Default | Description | | ------------------------- | -------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `queryRetryMax` | int | **`0`** | Maximum number of retry attempts for transient query failures (for example, network timeouts, temporary database unavailability).**Default behavior:** Retries are disabled by default (set to `0`). Set to a higher value to enable retries. | | `queryRetryInitialDelay` | Duration | **`PT1S`** | Initial delay before the first retry attempt.**Behavior:** Each subsequent retry uses exponential backoff. | | `queryRetryMaxDelay` | Duration | **`PT60S`** | Maximum delay between retry attempts.**Constraint:** Must be greater than or equal to `queryRetryInitialDelay`.**Purpose:** Prevents excessive wait times. | | `queryRetryBackoffFactor` | double | **`2.0`** | Multiplier applied to the delay between consecutive retries.**Example:** With factor `2.0` and initial delay `PT1S`, retries occur at 1s, 2s, 4s, 8s, and so on (capped by `queryRetryMaxDelay`). | #### Configuration example ```yaml # In your broker configuration file (for example, broker.yaml) camunda: processing: engine: batch-operations: # Scheduler scheduler-interval: PT1S # Chunking and pagination chunk-size: 100 query-page-size: 10000 query-in-clause-size: 1000 # Retry and error handling query-retry-max: 0 query-retry-initial-delay: PT1S query-retry-max-delay: PT60S query-retry-backoff-factor: 2.0 ``` #### Tuning recommendations **For large batch operations (100,000+ instances):** - Consider reducing `chunkSize` to `500` or lower to reduce memory pressure - Reduce `queryPageSize` if you encounter Elasticsearch/OpenSearch query timeouts **For high-throughput environments:** - Increase `schedulerInterval` to `PT5S` or `PT10S` to reduce scheduler overhead - Monitor partition CPU usage and adjust accordingly **For unreliable network connections:** - Increase `queryRetryMax` to `5` or higher - Increase `queryRetryMaxDelay` to `PT120S` for longer retry windows :::warning The engine enforces a 4MB per-record limit. If initialization queries return very large result sets, the scheduler automatically splits them across multiple chunk records. Prefer tuning `chunkSize` and `queryPageSize` rather than increasing global message size limits. ::: :::tip For backward compatibility, the legacy configuration path `zeebe.broker.experimental.engine.batchOperations.*` is still supported. However, the unified configuration format shown above (`camunda.processing.engine.batch-operations.*`) is recommended for new deployments. ::: #### Exporter configuration If you use the Camunda Exporter (Self-Managed with Elasticsearch/OpenSearch), you can control whether pending batch items are exported immediately at batch creation. ##### Camunda exporter exportItemsOnCreation | Parameter | Type | Default | Description | | -------------------------------------- | ------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `batchOperation.exportItemsOnCreation` | boolean | **`true`** | Controls whether pending batch items are exported to Elasticsearch/OpenSearch immediately when batch initialization starts.**When enabled:** Operate can display a loading spinner indicating pending batch operations. | **When to disable:** For very large batches (100,000+ items), enabling this option causes a temporary spike in write load due to the high volume of document insertions into the secondary database. If you experience performance issues during batch creation, consider setting this to `false`. **Trade-offs:** - **Enabled (`true`)**: Operate UI shows real-time progress indicators, but may cause indexing pressure - **Disabled (`false`)**: Reduces initial indexing load, but Operate UI won't show the progress spinner. Use the REST API batch status endpoints and Grafana metrics for monitoring instead. **Configuration example:** ```yaml # In your broker configuration file (for example, broker.yaml) zeebe: broker: ... exporters: camundaexporter: args: batchOperation: exportItemsOnCreation: false ``` --- ## Clustering Zeebe can operate as a cluster of brokers, forming a peer-to-peer network. In this network, all brokers have the same responsibilities and there is no single point of failure. ![cluster](assets/cluster.png) ## Gossip membership protocol Zeebe implements the [gossip protocol](https://en.wikipedia.org/wiki/Gossip_protocol) to know which brokers are currently part of the cluster. The cluster is bootstrapped using a set of well-known bootstrap brokers, to which the others can connect. To achieve this, each broker must have at least one bootstrap broker as its initial contact point in their configuration: ```yaml --- cluster: initialContactPoints: [node1.mycluster.loc:26502] ``` When a broker is connected to the cluster for the first time, it fetches the topology from the initial contact points and starts gossiping with the other brokers. Brokers keep cluster topology locally across restarts. ## Raft consensus and replication protocol To ensure fault tolerance, Zeebe replicates data across servers using the [raft protocol](). Data is divided into partitions (shards). Each partition has a number of replicas. Among the replica set, a **leader** is determined by the Raft protocol, which takes in requests and performs all the processing. All other brokers are passive **followers**. When the leader becomes unavailable, the followers transparently select a new leader. Each broker in the cluster may be both leader and follower at the same time for different partitions. In an ideal world, this leads to client traffic distributed evenly across all brokers. ![cluster](assets/data-distribution.png) :::note There is no active load balancing across partitions. Each leader election for any partition is autonomous and independent of leader elections for other partitions. This may lead to one node becoming the leader for all partitions. This is not a problem for fault tolerance as the guarantees of replication remain. However, this may negatively impact throughput as all traffic hits one node. To reach a well-distributed leadership again, the [Rebalancing API](/self-managed/components/orchestration-cluster/zeebe/operations/rebalancing.md) can be used in Self-Managed environments. Be aware that this is on a best-effort basis. ::: ## Commit Before a new record on a partition can be processed, it must be replicated to a quorum of brokers, and this majority of followers has to confirm the received record. When the leader received these confirmations from half or more of its followers, the leader **commits** the record. Committing ensures a record is durable, even in case of complete data loss on an individual broker. The exact semantics of committing are defined by the raft protocol. ![cluster](assets/commit.png) A well-balanced replication ensures records can be committed even when one or more brokers will become unavailable, but the majority of brokers are still available. **Odd replication factors** [are recommended](partitions.md#replication). Examples for common replication factors and their quorum: | Replication factor | Description | Quorum | Use case | | :----------------: | --------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------- | | 3 | 1 leader, 2 followers | Half or more of 2 followers is 1 follower that confirmed. | Single region, 3 availability zones. One broker can go down without losing data. | | 5 | 1 leader, 4 followers | Half or more of 4 followers are 2 followers that confirmed. | Allows a higher tolerance against the loss of 2 brokers. | The only exception to have **even replication factors** is the [dual region setup](/self-managed/concepts/multi-region/dual-region.md). In this setup, an even replication factor ensures records are always replicated to both regions. In the case of losing a whole region, every new request will be denied, as no replication can get a quorum anymore. All partitions will become unhealthy, and operators start their [failover procedure](/self-managed/deployment/helm/operational-tasks/dual-region-ops.md). No data is lost. Using an odd replication factor in a dual region setup would favor some partitions, where the leader and the majority of followers live in the surviving region, against the partitions that have only a minority of followers survived. This may slow down to detect a region loss, as some process instances still continue while others are stuck. Below is one example for replication and quorum used in the [dual region setup guide](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/dual-region.md#camunda-8-helm-chart-prerequisites): | Replication factor | Description | Quorum | Use case | | :----------------: | --------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 4 | 1 leader, 3 followers | Half or more of 3 followers are 2 followers that confirmed. | Exception for dual-region with minimal replication, records always replicated to both regions [following the recommended setup](/self-managed/concepts/multi-region/dual-region.md#zeebe-cluster-configuration). | --- ## Health Health in Zeebe is not a binary status, but can have three different states: - **Healthy**: everything is working as expected. - **Unhealthy**: at least parts of the system are in a degraded state. - **Dead**: one or more parts of the cluster experienced non-recoverable failure. ## Unhealthy When a node in a Zeebe cluster is unhealthy, it means parts of the system may not work. This is often a transient state. For example, when a node is starting, it starts in an unhealthy state, as several components, such as a partition and the workflow engine, are not yet installed or ready for operation. When this is temporary, and the cluster overall converges to a steady healthy state, you can safely ignore these temporary signals. However, if things persist, or if you see the cluster alternate between healthy and unhealthy, it can be an indicator of an underlying issue that requires investigation. :::note Note that even if parts of the cluster are unhealthy, it can be that other parts are still working fine. ::: ## Dead When something is marked as dead in a cluster, it means it failed in a non-recoverable way. This means it **will** require human intervention to recover. This is a rare status, but it can happen if data corruption is detected, for example. You should promptly investigate any components with a dead status. :::note Note that it's possible that only parts of the system has failed, and the rest is still working fine. For example, a dead broker can be due to a single partition which has data corruption; other partitions on the same broker may still be processing and working as expected. ::: ## Cluster The health of a cluster depends on the deployment topology. More specifically, the health of a cluster is based on the expected number of brokers, gateways, partitions, and the replication factor. :::note If using the single application, or embedded gateways - that is, where brokers act as gateways - then you can ignore the gateway part. ::: :::note Starting with 8.8, we consider a `gateway` to be anything which exposes the Camunda 8 REST API, and optionally, the Camunda 8 gRPC API. While this is often the Zeebe Gateway, it can be a single application combining Operate, Tasklist, and the Zeebe Gateway. ::: Informally, a healthy cluster is one where: - The expected number of [brokers and gateways](../../../self-managed/components/orchestration-cluster/zeebe/operations/health.md) report a healthy status. - Every partition has exactly one leader. - There are `N-1` followers per partition (where `N` is the replication factor). More formally, given `G` the number of expected gateways, `B` the number of expected brokers, `P` the partition count, and `R` the replication factor, we can define the health of a cluster as: - There are `G` gateway nodes that report a healthy status via their health check. - There are `B` broker nodes that report a healthy status via their health check. - For every partition from `1..P`: - There is exactly one leader broker. - There are `R-1` follower brokers. For example, given we expect 3 brokers, 3 partitions, and a replication factor of 3, a healthy cluster would show the following topology: ``` Cluster size: 3 Partitions count: 3 Replication factor: 3 Gateway version: 8.7.1 Brokers: Broker 0 - zeebe-0.internal.local Version: 8.7.1 Partition 1 : Leader, Healthy Partition 2 : Follower, Healthy Partition 3 : Follower, Healthy Broker 1 - zeebe-1.internal.local Version: 8.7.1 Partition 1 : Follower, Healthy Partition 2 : Leader, Healthy Partition 3 : Follower, Healthy Broker 2 - zeebe-2.internal.local Version: 8.7.1 Partition 1 : Follower, Healthy Partition 2 : Follower, Healthy Partition 3 : Leader, Healthy ``` We can see that the topology reports the expected number of brokers, and that for every partition there is exactly one leader, two followers, and all partitions are healthy. ## Partition [Partitions](./partitions.md) are distributed across multiple nodes. In a cluster, all partitions have the same replication factor, which defines on how many nodes a partition's data will live. For example, given a replication factor of three, exactly three brokers in the cluster will store the partition's data. In other words, that partition is a distributed system across those three nodes: it is made of discrete parts that exist on different nodes, separated by a network, but it is acts conceptually as a single system. A partition can have the following health status: `HEALTHY`, `UNHEALTHY`, or `DEAD`. A partition is considered healthy if: - It has exactly one healthy leader in the cluster. A healthy leader is one who can: - Replicate to [a quorum of followers](./clustering.md#raft-consensus-and-replication-protocol). - [Process data](./internal-processing.md). - It has at least `floor(N/2)` healthy followers in the cluster. With this amount of followers and the leader, data can be [committed](./clustering.md#commit). Anything short of this represents an unhealthy partition: - Having multiple leaders in a cluster would prevent Zeebe from guaranteeing processing consistency. - Having fewer than `floor(N/2)` healthy followers would prevent the leader from committing. - This would block processing, which would cause all requests to time out, and make the partition functionally unavailable. - Having **no** leader would be similar: nothing would be committed, and as such, nothing processed. A dead partition represents one that has failed in a non-recoverable way. This is exceptional and always requires human intervention. Examples of dead partitions are: - Data corruption was detected. Since the engine is a stream processing application, we cannot skip any entries, meaning everything is stopped. - This can include detection of gaps or missing data. ## Broker The health of a broker is computed from the health of its component tree. While there are many components in the broker, it's sufficient to understand that a broker is considered: - **Healthy** if and only if all components in the tree are healthy. - **Unhealthy** if at least one component is unhealthy. - **Dead** if at least one component is dead. There are many components in a broker, but most importantly, each partition in a broker is a separate, independent component. As such, you can roughly define the health of a broker as the sum of the health of its partitions. The health of a broker is reported via its [status health check](../../../self-managed/components/orchestration-cluster/zeebe/operations/health.md#broker). --- ## Internal processing Internally, Zeebe is implemented as a collection of **stream processors** working on record streams \(partitions\). The stream processing model is used since it is a unified approach to provide: - Command protocol \(request-response\), - Record export \(streaming\), - Process evaluation \(asynchronous background tasks\) Record export solves the history problem and the stream provides the kind of exhaustive audit log a workflow engine needs to produce. ## State machines Zeebe manages stateful entities like jobs and processes. Internally, these entities are implemented as **state machines** managed by a stream processor. An instance of a state machine is always in one of several logical states. From each state, a set of transitions defines the next possible states. Transitioning into a new state may produce outputs/side effects. Let's look at the state machine for jobs: ![partition](assets/internal-processing-job.png) Every oval is a state. Every arrow is a state transition. Note how each state transition is only applicable in a specific state. For example, it is not possible to complete a job when it is in state `CREATED`. ## Events and commands Every state change in a state machine is called an **event**. Zeebe publishes every event as a record on the stream. State changes can be requested by submitting a **command**. A Zeebe Broker receives commands from two sources: - Clients send commands remotely. For example, deploying processes, starting process instances, creating and completing jobs, etc. - The broker itself generates commands. For example, locking a job for exclusive processing by a worker. Once received, a command is published as a record on the addressed stream. ## Stateful stream processing A stream processor reads the record stream sequentially and interprets the commands with respect to the addressed entity's lifecycle. More specifically, a stream processor repeatedly performs the following steps: 1. Consume the next command from the stream. 2. Determine if the command is applicable based on the state lifecycle and the entity's current state. 3. If the command is applicable, apply it to the state machine. If the command was sent by a client, send a reply/response. 4. If the command is not applicable, reject it. If it was sent by a client, send an error reply/response. 5. Publish an event reporting the entity's new state. For example, processing the **Create Job** command produces the event **Job Created**. ## Driving the engine As a workflow engine, Zeebe must continuously drive the execution of its processes. Zeebe achieves this by also writing follow-up commands to the stream as part of the processing of other commands. For example, when the **Complete Job** command is processed, it does not just complete the job; it also writes the **Complete Activity** command for the corresponding service task. This command can in turn be processed, completing the service task and driving the execution of the process instance to the next step. ## Unexpected error handling When the workflow engine encounters an unexpected error while processing a command, it rejects the command. If the command is related to a process instance, it additionally publishes an error event, applying it to the state machine. As a result, the process instance is banned from the workflow engine, while it's data remains accessible. ### Banned process instance Banning the process instance is a safety mechanism to safeguard against incorrect execution of the process and to prevent a single error from jamming the entire partition's stream processor. - A banned process instance will not continue to execute. - A banned process instance cannot be recovered. - You can still cancel a banned process instance. Internally, the workflow engine skips commands that are applicable to executing a banned process instance. Commands related to canceling a banned process instance are not skipped. ## Handling backpressure When a broker receives a client request, it is written to the **event stream** first, and processed later by the stream processor. If the processing is slow or if there are many client requests in the stream, it might take too long for the processor to start processing the command. If the broker keeps accepting new requests from the client, the backlog increases and the processing latency can grow beyond an acceptable time. To avoid such problems, Zeebe employs [flow control](/self-managed/operational-guides/configure-flow-control/configure-flow-control.md) strategies that apply write rate limits and [backpressure](/self-managed/components/orchestration-cluster/zeebe/operations/backpressure.md) to user requests. In the case of backpressure when the broker receives more requests than it can process with an acceptable latency, it rejects some requests. For flow control, it can be used with static write rate limits or throttling which prevents the partition from building an excessive backlog of records not exported. Backpressure is indicated to the client by throwing a **resource exhausted** exception. If a client sees this exception, it can retry the requests with an appropriate retry strategy. If the rejection rate is high, it indicates the broker is constantly under high load and you need to reduce the rate of requests. Alternatively, you can also increase broker resources to adjust to your needs. In high-load scenarios, it is recommended to [benchmark](https://camunda.com/blog/2022/05/how-to-benchmark-your-camunda-platform-8-cluster/) your Zeebe Broker up front to size it correctly. The maximum rate of requests that can be processed by a broker depends on the processing capacity of the machine, the network latency, current load of the system, etc. There is no fixed limit configured in Zeebe for the maximum rate of requests it accepts. Instead, Zeebe uses an adaptive algorithm to dynamically determine the limit of the number of in-flight requests (the requests that are accepted by the broker, but not yet processed). The in-flight request count is incremented when a request is accepted, and decremented when a response is sent back to the client. The broker rejects requests when the in-flight request count reaches the limit. This is not a single static threshold for the whole broker. Zeebe calculates the in-flight count and the limit per partition, and the current limit changes over time based on the configured backpressure algorithm. To observe the current limit, monitor the `zeebe_backpressure_requests_limit` metric. For more details, see [backpressure](/self-managed/components/orchestration-cluster/zeebe/operations/backpressure.md) and [metrics](/self-managed/operational-guides/monitoring/metrics.md#performance-metrics). --- ## Partitions In Zeebe, all data is organized into **partitions**. A **partition** is a persistent stream of process-related events. In a cluster of brokers, partitions are distributed among the nodes so it can be thought of as a **shard**. When you bootstrap a Zeebe cluster, you can configure how many partitions you need. :::note If you've worked with the [Apache Kafka System](https://kafka.apache.org/) before, the concepts presented on this page will sound very familiar to you. ::: ## Usage examples Whenever you deploy a process, you deploy it to the first partition. The process is then distributed to all partitions. On all partitions, this process receives the same key and version such that it can be consistently identified. :::note To learn more about aligning partitions with the same version of deployment and why it is built this way, visit our [Zeebe Chaos blog](https://camunda.github.io/zeebe-chaos/2021/01/26/deployments/#deployments). ::: When you start an instance of a process, the client library then routes the request to one partition in which the process instance is published. All subsequent processing of the process instance happens in that partition. ## Distribution over partitions When a process instance is created in a partition, its state is stored and managed by the same partition until its execution is terminated. The partition in which it is created is determined by various factors. - When a client sends a command `CreateProcessInstance` or `CreateProcessInstanceWithResult`, the gateway chooses a partition in a round-robin manner and forwards the requests to that partition. The process instance is created in that partition. - When a client publishes a message to trigger a **message start event**, the message is forwarded to a partition based on the correlation key of the message. The process instance is created on the same partition where the message is published. - Process instances created by **timer start events** are always created on partition 1. ## Scalability Use partitions to scale your process processing. Partitions are dynamically distributed in a Zeebe cluster and for each partition there is one leading broker at a time. This **leader** accepts requests and performs event processing for the partition. Let's assume you want to distribute process processing load over five machines; you can achieve that by bootstraping five or more partitions. :::note While each partition has one leading broker, _not all brokers are guaranteed to lead a partition_. A broker can lead more than one partition, and, at times, a broker in a cluster may act only as a replication back-up for partitions. This broker will not be doing any active work on processes until a partition fail-over happens and the broker gets elected as the new leader for that partition. ::: ## Partition data layout A partition is a persistent append-only event stream. Initially, a partition is empty. As the first entry is inserted, it takes the place of the first entry. As the second entry comes in and is inserted, it takes the place as the second entry, and so on and so forth. Each entry has a position in the partition which uniquely identifies it. ![partition](assets/partition.png) ## Replication For fault tolerance, data in a partition is replicated from the **leader** of the partition to its **followers**. Followers are other Zeebe Broker nodes that maintain a copy of the partition without performing event processing. We recommend an **odd replication factor**, as it ensures high fault-tolerance and availability. **Even replication factors** have no benefit over the previous odd value and are weaker than the next. For example, a replication factor of four has no benefit over a replication factor of three. A replication factor for four would be weaker than a replication factor of five. ### Roles A replica which takes part in a partition is assigned a role, which can be one of: `leader`, `follower`, or `inactive`. :::note There are other [Raft specific roles](./clustering.md#raft-consensus-and-replication-protocol) which are used as part of the replication process, but are generally not relevant for the operation of a Zeebe cluster. ::: As mentioned, replication of a partition is from the **leader** to its **followers**. This means _there can only be one leader for a partition at any given time_. More generally, a node will a partition role of: - `leader`: a node which is appending new commands to the stream, and replicating them to the **followers**. - `follower`: a node which is actively taking part in replication, and which could become a `leader`. - `inactive`: a node which is still starting, or which has encounted a non-recoverable error. It can happen that nodes will encounter non-recoverable errors, and will explicitly stop a partition - aka take on an `inactive` role. Non-recoverable here simply means that it cannot automatically recover by itself - human intervention, for example, may allow it to recover. An example would be during a rolling update, it could be that a newer version of a node writes data that is not readable by the older version, which would cause it to stall. Recovery in this case would consist of restarting the older node so that it can update, which will let it recover. ## Partition distribution If no other configuration is specified, partitions are distributed in a guaranteed round-robin fashion across all brokers in the cluster, considering the number of nodes, number of partitions, and the replication factor. For example, the first partition will always be hosted by the first node, plus the following nodes based on the replication factor. The second partition will be hosted on the second node and the following to fulfill the replication factor. As an example, the following partition schemes are guaranteed: ### Example 1 #### Context - Number of nodes: 4 - Number of partitions: 7 - Replication factor: 3 #### Partition layout | | Node 1 | Node 2 | Node 3 | Node 4 | | ----------: | :----: | :----: | :----: | :----: | | Partition 1 | X | X | X | | | Partition 2 | | X | X | X | | Partition 3 | X | | X | X | | Partition 4 | X | X | | X | | Partition 5 | X | X | X | | | Partition 6 | | X | X | X | | Partition 7 | X | | X | X | ### Example 2 #### Context - Number of nodes: 5 - Number of partitions: 3 - Replication factor: 3 #### Partition layout | | Node 1 | Node 2 | Node 3 | Node 4 | Node 5 | | ----------: | :----: | :----: | :----: | :----: | :----: | | Partition 1 | X | X | X | | | | Partition 2 | | X | X | X | | | Partition 3 | | | X | X | X | ## Recommendations Choosing the number of partitions depends on the use case, workload, and cluster setup. Here are some rules of thumb: - For testing and early development, start with a single partition. Note that Zeebe's process processing is highly optimized for efficiency, so a single partition can already handle high event loads. - With a single Zeebe Broker, a single partition is usually enough. However, if the node has many cores and the broker is configured to use them, more partitions can increase the total throughput (around two threads per partition). - Base your decisions on data. Simulate the expected workload, measure, and compare the performance of different partition setups. --- ## Process lifecycles In Zeebe, the process execution is represented internally by events of type `ProcessInstance`. The events are written to the log stream and can be observed by an exporter. Each event is one step in a process instance lifecycle. All events of one process instance have the same `processInstanceKey`. Events which belong to the same element instance (e.g. a task) have the same `key`. The element instances have different lifecycles depending on the type of element. ## (Sub-)Process/Activity/Gateway lifecycle ![activity lifecycle](assets/activity-lifecycle.png) ## Event lifecycle ![event lifecycle](assets/event-lifecycle.png) ## Sequence flow lifecycle ![sequence flow lifecycle](assets/pass-through-lifecycle.png) ## Example ![order process](assets/process.png) Given the above process, a successful execution yields the following records in the commit log: Intent Element ID Element type ELEMENT_ACTIVATING order-process process ELEMENT_ACTIVATED order-process process ELEMENT_ACTIVATING order-placed start event ELEMENT_ACTIVATED order-placed start event ELEMENT_COMPLETING order-placed start event ELEMENT_COMPLETED order-placed start event SEQUENCE_FLOW_TAKEN to-collect-money sequence flow ELEMENT_ACTIVATING collect-money task ELEMENT_ACTIVATED collect-money task ELEMENT_COMPLETING collect-money task ELEMENT_COMPLETED collect-money task SEQUENCE_FLOW_TAKEN to-fetch-items sequence flow ... ... ... SEQUENCE_FLOW_TAKEN to-order-delivered sequence flow ELEMENT_ACTIVATING order-delivered end event ELEMENT_ACTIVATED order-delivered end event ELEMENT_COMPLETING order-delivered end event ELEMENT_COMPLETED order-delivered end event ELEMENT_COMPLETING order-process process ELEMENT_COMPLETED order-process process --- ## Protocols Zeebe clients connect to brokers via a stateless gateway. For the communication between client and gateway, a mix of REST and [gRPC](https://grpc.io/) is used. The gRPC protocol is defined using Protocol Buffers v3 ([proto3](https://developers.google.com/protocol-buffers/docs/proto3)), and you can find it in the [Zeebe repository](https://github.com/camunda/camunda/tree/main/zeebe/gateway-protocol). There, you will also find the OpenAPI v3 spec for the REST portion of the gateway API. Note that while gRPC requires HTTP/2, the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) can work with either HTTP/1.1 or HTTP/2. ## What is gRPC? gRPC was first developed by Google and is now an open source project and part of the Cloud Native Computing Foundation. If you’re new to gRPC, see [What is gRPC](https://grpc.io/docs/guides/index.html) on the project website for an introduction. ## Why gRPC? gRPC has many beneficial features that make it a good fit for Zeebe, including: - Supports bi-directional streaming for opening a persistent connection and sending or receiving a stream of messages between client and server - Uses the common HTTP/2 protocol by default - Uses Protocol Buffers as an interface definition and data serialization mechanism–specifically, Zeebe uses proto3, which supports client generation in ten different programming languages. ## Supported clients Currently, Zeebe officially supports a gRPC client in [Java](/apis-tools/java-client/getting-started.md). [Community clients](/apis-tools/community-clients/index.md) have been created in other languages, including C#, Ruby, and JavaScript. If there is no client in your target language yet, you can [build your own client](/apis-tools/build-your-own-client.md) in a range of different programming languages. ## Intercepting calls Zeebe supports loading arbitrary [gRPC server interceptors](self-managed/components/orchestration-cluster/zeebe/zeebe-gateway/interceptors.md) and [Jakarta servlet filters](self-managed/components/orchestration-cluster/zeebe/zeebe-gateway/filters.md) to intercept incoming calls. --- ## Technical concepts This section gives an overview of Zeebe's underlying technical concepts. - [Architecture](architecture.md) - Introduces you to the internal pillars of Zeebe, as well as interfaces for external systems to interact with Zeebe. - [Batch operations](batch-operations.md) - How distributed batch operations work in the Zeebe engine. - [Clustering](clustering.md) - Discusses the internal structure and properties of a Zeebe cluster. - [Partitions](partitions.md) - Sheds light on how Zeebe achieves horizontal scalability. - [Internal processing](internal-processing.md) - Explains the basics of Zeebe's event processing. - [Process lifecycles](process-lifecycles.md) - Expands on the event processing concept and goes into more detail regarding the lifecycles of selected process elements. - [Protocols](protocols.md) - Explains how external clients communicate with Zeebe. In addition to these sections, you may also be interested in our [Best Practices](/components/best-practices/best-practices-overview.md). --- ## Introduction to Zeebe Zeebe is the process automation engine powering Camunda 8. While written in Java, you do not need to be a Java developer to use Zeebe. A workflow engine is an essential part of any process automation tool. We call it an “engine” because it drives business processes from start to finish, no matter how complex the process and decision logic need to be. ## Why Zeebe? Zeebe doesn’t rely on a central database, so there’s no performance bottleneck as process volumes increase. Deliver high throughput by distributing processing across nodes, or add cluster nodes to execute an unlimited number of processes at consistently low latency. Zeebe distributes data across all brokers in a cluster with storage directly on the server filesystem. If one broker goes down, another can replace it with no data loss. This built-in replication mechanism ensures that Camunda can recover from machine or software failure with no human interaction, no data loss, and minimal downtime. For documentation on deploying Zeebe as part of Camunda 8 Self-Managed, refer to the [deployment guide](../../self-managed/components/orchestration-cluster/zeebe/overview.md). ## Get started New to Zeebe? Learn about clustering, partitions, internal processing, and more. --- ## Agentic orchestration Orchestrate agents, people, and systems in one process. Build and coordinate AI agents, people, and systems across complete processes, combining deterministic execution with AI reasoning and human judgment where each works best. [Get started with agentic orchestration](../../components/agentic-orchestration/agentic-orchestration-overview.md) ## Get started Get started with Camunda agentic orchestration. Learn how it combines deterministic process logic with AI-driven decisions, understand what AI agents are, and build your first agent. ## Build Build AI agents and connect them to LLMs, tools, memory, and other agents. ## Evaluate Monitor your AI agents in real time with Operate, improve performance over time with Optimize, and test them with Camunda Process Test. ## Best practices Follow guidelines and recommendations for designing and running AI agents. ## ProcessOS Use ProcessOS to discover and re-engineer your existing processes against defined outcomes, and generate executable Camunda solutions. ## Design with AI Explore these selected AI features to design processes more easily and faster. ## Integrate via MCP Use MCP servers to let your AI agents interact with Camunda. --- ## Create a cluster(Guides) --- ## Build your first AI agent ## About In this guide, you will build a Camunda [AI agent](/components/agentic-orchestration/ai-agents.md): the native agent type that orchestrates tools in an [ad-hoc sub-process](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md) executed by the Camunda engine. Specifically, you will: - Run your AI agent using [Camunda 8 SaaS](https://accounts.cloud.camunda.io/signup) or locally with [Camunda 8 Self-Managed](/self-managed/about-self-managed.md). - Use an [AI Agent connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md) to provide interaction and reasoning capabilities to the AI agent. - Use an [ad-hoc sub-process](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md) to define the tools the AI agent should use. - Integrate a [Large Language Model (LLM)](/reference/glossary.md#large-language-model-llm) into your AI agent. After completing it, you will have an example AI agent running in Camunda 8. ## Prerequisites To build your first AI agent, see the prerequisites below depending on: - Your working environment. - Your chosen model. ### Camunda 8 environment To run your agent, you must have Camunda 8 (version 8.8 or newer) running, using either: - [Camunda 8 SaaS](/components/saas/saas.md). For example, [sign up for a free SaaS trial account](https://accounts.cloud.camunda.io/signup). - [Camunda 8 Self-Managed](/self-managed/about-self-managed.md). For example, follow [run your first local project](../getting-started-example). ### Supported models With the AI Agent connector, you can integrate frontier models from providers like Anthropic and Amazon Bedrock, or connect to open-weight models you host yourself on any OpenAI-compatible platform. See [supported model providers](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess.md#model-provider) for all options. :::tip Choose the right model setup Frontier models are billed per token. That works well for complex reasoning, but costs scale with volume. Self-hosted open-weight models use a fixed infrastructure cost instead, so routine decisions stay affordable as volume grows. Use a frontier model where nuanced judgment matters, and use a self-hosted model where decisions are simple and high-volume. ::: In this guide, you can try the following use cases: | Setup | Model provider | Model used | Prerequisites | | :----------------------- | :-------------------------------------------------------------------------------- | :----------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | SaaS | [Camunda-provided LLM](/components/agentic-orchestration/camunda-provided-llm.md) | Camunda-managed model (for example, Claude Sonnet 4.6) | Camunda 8 SaaS trial or enterprise organization. Camunda-provided LLM available in your organization. No additional LLM provider credentials are required to run this guide. | | Cloud (customer-managed) | AWS Bedrock | Claude Sonnet 4 | An AWS account with permissions for the [Bedrock Converse API](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html). Anthropic Claude foundation models using the AWS console. See [AWS documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access-modify.html) for details. | | Local | Ollama | GPT-OSS:20b (any Ollama-supported model works) | [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) running locally. Ollama and a model installed. See [set up Ollama](#set-up-ollama) for details. | :::tip Choose a lighter model if needed This guide uses GPT-OSS:20b as an example, but any model supported by Ollama works with the AI Agent connector. GPT-OSS:20b requires more than 20GB of RAM and 14GB of free disk space, which may be more than needed for this guide. Consider a smaller model (for example, `llama3.2` or `qwen2.5`) if your machine has limited resources. ::: ## Step 1: Install the model blueprint To start building your first AI agent, you can use a Camunda model blueprint from [Camunda Marketplace](https://marketplace.camunda.com/en-US/home). In this guide, you will use the [AI Agent Chat Quick Start](https://marketplace.camunda.com/en-US/apps/587865) model blueprint. Depending on your working environment, follow the corresponding steps below. 1. In the [blueprint page](https://marketplace.camunda.com/en-US/apps/587865), click **For SAAS** and select or create a project to save the blueprint. 1. The blueprint BPMN diagram opens in Camunda Hub. 1. In the [blueprint page](https://marketplace.camunda.com/en-US/apps/587865), click **For SM** and download the blueprint files from the repository. :::note If you’re using Camunda 8 Run and installed it using the [starter package](./getting-started-example.md#download-the-getting-started-package), the blueprint was already downloaded as part of it. ::: 2. Open the blueprint BPMN diagram in Desktop Modeler or [upload them to Camunda Hub](/components/hub/workspace/modeler/modeling/import-diagram.md). ### About the example AI agent process The example AI agent process is a chatbot that you can interact with via a [user task form](/components/modeler/forms/camunda-forms-reference.md). This process showcases how an AI agent can: - **Make autonomous decisions** about which tasks to execute based on your input. - **Adapt its behavior** dynamically using the context provided. - **Handle complex scenarios** by selecting and combining different tools. - **Integrate seamlessly** with other process components. The example includes a form linked to the start event, allowing you to submit requests ranging from simple questions to more complex tasks, such as document uploads. :::tip Understand the decision model behind this example To make this agent reliable, treat each activity in the ad-hoc sub-process as a documented tool. Learn why this matters in [Design and architecture: Define your agent tools](/components/agentic-orchestration/design-architecture.md#define-your-agent-tools). For a runtime view of what the LLM decides vs. what Camunda orchestrates, see [AI agents: How the agent loop works](/components/agentic-orchestration/ai-agents.md#how-the-agent-loop-works). For prompt configuration details, see [AI Agent connector: System prompt, user prompt, and tool descriptions](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md#system-prompt-user-prompt-and-tool-descriptions). ::: ## Step 2: Configure the AI Agent connector Depending on your model choice, configure the AI Agent connector accordingly. :::info With Camunda-provided LLM, you do not need additional LLM setup. Camunda-provided LLM is only available in SaaS. It is not available in Self-Managed environments. ::: 1. Verify your organization has the **Camunda Provided LLM** toggle enabled in [Camunda Hub](/components/saas/organization/enable-alpha-features.md#enable-camunda-provided-llm). This is a separate toggle from **AI-powered features**. 1. Keep the AI Agent connector's default settings from the blueprint. Most AI blueprints default to use Camunda-provided LLM in SaaS. You only need to configure a customer-managed provider if you want custom billing, quotas, or provider control. See [Camunda-provided LLM](/components/agentic-orchestration/camunda-provided-llm.md) for more details. Configure the connector's authentication and template for AWS Bedrock. #### Configure authentication The example blueprint downloaded in step one is preconfigured to use AWS Bedrock. For authentication, it uses the following connector secrets: - `AWS_BEDROCK_ACCESS_KEY`: The AWS Access Key ID for your AWS account able to call the Bedrock Converse API. - `AWS_BEDROCK_SECRET_KEY`: The AWS Secret Access Key for your AWS account. You will configure these secrets differently depending on your working environment. Configure the secrets using the [Console](../components/saas/clusters/manage-secrets.md). Export the secrets as environment variables before starting the distribution. See [Connector secrets](/self-managed/components/connectors/connectors-configuration.md#secrets) for details. See [Amazon Bedrock model provider](../components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess.md#amazon-bedrock) for more information about other available authentication methods. #### Configure properties In the blueprint BPMN diagram, the AI agent is implemented using the [AI Agent Sub-process connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess.md). You can keep the default configuration or adjust it to test other setups. To do so, use the properties panel: :::tip When configuring connectors, use [FEEL expressions](/components/modeler/feel/language-guide/feel-expressions-introduction.md), by clicking the `fx` icon, to reference [process variables](/reference/glossary.md#process-variable) and create dynamic prompts based on runtime data. ::: Configure your local LLM with Ollama. #### Set up Ollama 1. **Download and install**: Follow [Ollama's documentation](https://docs.ollama.com/quickstart) for details. 1. **Confirm installation**: Check the installed version in a terminal or command prompt by running `ollama --version`. 1. **Start the local server**: Start it using the application, or run `ollama serve` in a terminal or command prompt. 1. **Pull a model**: This guide uses GPT-OSS:20b as an example. Run `ollama pull gpt-oss:20b` in a terminal or command prompt to download it. Any [model supported by Ollama](https://ollama.com/search) works with the AI Agent connector; substitute a smaller model (for example, `ollama pull llama3.2`) if you prefer a lighter download. 1. **Test**: Ollama serves an API at `http://localhost:11434` by default. To test it, open that URL in a browser or run this command in your terminal: ``` curl -X POST http://localhost:11434/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{"model":"gpt-oss:20b","messages":[{"role":"user","content":"Hello!"}]}' ``` #### Configure properties The example blueprint downloaded in step one is preconfigured to use AWS Bedrock. Update the connector's configuration using the Model provider and Model sections to use Ollama instead. **Model provider** 1. Select **OpenAI Compatible** from the **Provider** dropdown. 1. Enter `http://localhost:11434/v1` in the **API endpoint** field. This is Ollama's default API URL. 1. No authentication or additional headers are required for the local Ollama API, so leave the remaining fields blank. **Model** 1. Enter `gpt-oss:20b` in the **Model** field, or the name of whichever model you pulled instead. This field is case-sensitive, so be sure to enter it in all lowercase. ## Step 3: Test your AI agent Deploy and run your AI agent in your Camunda cluster. :::important Whether you are testing your agent in Camunda 8 SaaS or locally with Camunda 8 Self-Managed, make sure your workspace has an [environment](/components/hub/organization/manage-environments/assign-environments.md) on a cluster with version 8.8 or higher before reading further. ::: Depending on your working environment, test your agent by following the corresponding steps below. 1. In [Camunda Hub](/components/hub/workspace/modeler/index.md), open the BPMN diagram. 1. Select the [**Test**](/components/hub/workspace/modeler/validation/test-your-process.md) tab. 1. Select the environment you want to deploy and test the process on. 1. Click **Deploy**. 1. Open the Start form and add a prompt for the AI agent. For example, enter "Tell me a joke" in the **How can I help you today?** field, and click **Start instance**. 1. The AI agent analyzes your prompt, decides what tools to use, and responds with an answer. Open the **Task form** to view the result. 1. You can [monitor the process execution](#monitor-the-process-execution) in Operate. 1. You can follow up with more prompts to continue testing the AI agent. Select the **Are you satisfied with the result?** checkbox when you want to finish your testing and complete the process. :::important Test mode not supported Because the AI agent in this example is an ad-hoc sub-process, you can't use **Test mode** to run it (ad-hoc sub-processes are a known [limitation](/components/hub/workspace/modeler/validation/test-your-process.md#test-cases-limitations)). Instead, deploy the process within the **Implement** tab using **Deploy & Run**, and use [Tasklist](/components/tasklist/introduction-to-tasklist.md) to complete the form. ::: 1. If using **Desktop Modeler**: 1. [Deploy the process model](/components/modeler/desktop-modeler/deploy-diagram.md) to your local Camunda 8 environment. 1. Open each form, and click **Deploy**. [Linked forms are not deployed automatically with the process](/components/modeler/desktop-modeler/utilize-forms.md#deploy-a-form). Skipping this step causes Tasklist to fail with "We were not able to load the form" when you try to start the process. 1. If using **Camunda Hub**, choose one of the following options: - [Deploy your project](/components/hub/workspace/manage-projects/deploy-project.md) as a bundle. - [Deploy **All resources**](/components/hub/workspace/modeler/run-or-publish-your-process.md) from the diagram or a form. 1. Open Tasklist in your browser at http://localhost:8080/tasklist. 1. On the **Processes** tab, find the `AI Agent Chat With Tools` process and click **Start process**. 1. In the start form, add a prompt for the AI agent. For example, enter "Tell me a joke" in the **How can I help you today?** field, and click **Start process**. 1. The AI agent analyzes your prompt, decides what tools to use, and responds with an answer. 1. Select the **Tasks** tab in Tasklist. When the AI agent finishes processing, you should see a `User Feedback` task waiting for you to complete. 1. You can [monitor the process execution](#monitor-the-process-execution) in Operate. 1. You can follow up with more prompts to continue testing the AI agent. Select the **Are you satisfied with the result?** checkbox when you want to finish the process. ### Monitor the process execution Open [Operate](/components/operate/operate-introduction.md) at http://localhost:8080/operate, and locate your process instance to watch the agent at work. Select the AI agent element to view usage metrics, its full conversation history, available tools, and other details: For a hands-on walkthrough of the agent instance details available in Operate, see [monitor your AI agents with Operate](/components/agentic-orchestration/evaluate-agents/monitor-ai-agents.md). When you run the AI agent process: 1. The AI agent receives your prompt and analyzes it together with the configured system prompt and tool descriptions. 1. The LLM determines which tools from the ad-hoc subprocess should be activated. 1. Camunda executes the selected BPMN activities. 1. Tasks can execute in parallel or sequentially, depending on the agent's decisions and process state. 1. Process variables are updated as each tool completes its execution. 1. The agent may iterate through multiple tool calls to handle complex requests. ## Step 4: Add your first tool You can customize your AI agent by adding tools. In this section, you will add a tool that fetches weather conditions for a given location using the [Open-Meteo API](https://open-meteo.com/). ### Add a REST connector task 1. Inside the AI agent sub-process, add a new task element. 1. Change the task type to [**Send REST Request**](/components/connectors/protocol/rest.md) using the **Change element** menu. 1. Name the task. For example, `Get current weather`. This name is visible to the LLM as the tool name. ### Write a tool description The LLM selects tools based on their description. Open the **Documentation** field in the properties panel and add a clear description of what the tool does and when to use it. For example: ``` Fetches current weather conditions for a given location. Use this tool when the user asks about weather, temperature, wind, or climate conditions for a city or place. Returns temperature in Celsius, wind speed, and a weather description. ``` :::tip Provide as much context as possible in tool descriptions to help the LLM select the right tool and generate proper inputs. ::: ### Configure the REST connector Set up the HTTP request in the properties panel: 1. In the **Authentication** section, select **None**. 1. In the **HTTP Endpoint** section: - Set **Method** to **GET**. - Set **URL** to the following [FEEL expression](/components/modeler/feel/language-guide/feel-expressions-introduction.md) by clicking the `fx` icon: ```feel "https://api.open-meteo.com/v1/forecast" ``` - Set **Query parameters** to: ```feel { latitude: fromAi(toolCall.latitude, "Latitude of the location to check weather for", "string"), longitude: fromAi(toolCall.longitude, "Longitude of the location to check weather for", "string"), current: "temperature_2m,wind_speed_10m,weather_code" } ``` The [`fromAi()`](../components/modeler/feel/builtin-functions/feel-built-in-functions-ai-agent.md#fromaivalue) calls tell the AI Agent connector which parameters the LLM must provide. At runtime, the LLM generates the latitude and longitude values based on the user's request, while the `current` parameter is a fixed value that selects which weather fields to return. ### Map the response to `toolCallResult` Each tool within the AI agent sub-process must return its result in a `toolCallResult` variable so the AI Agent connector can pass it back to the LLM. In the **Output Mapping** section, set **Result Expression** to: ```feel { toolCallResult: { latitude: response.body.latitude, longitude: response.body.longitude, temperature_celsius: response.body.current.temperature_2m, wind_speed_kmh: response.body.current.wind_speed_10m, weather_code: response.body.current.weather_code } } ``` This extracts the relevant fields from the Open-Meteo API response and returns them in a structure the LLM can interpret and summarize for the user. ### Test the new tool Deploy the updated process and start a new instance. Try prompts like: - _"What's the weather in Paris right now?"_ - _"Is it windy in Tokyo?"_ - _"Tell me the temperature in New York"_ The LLM will recognize these as weather requests, select the **Get current weather** tool, provide the appropriate latitude and longitude values, and summarize the response in natural language. ### Add your own tools To add more tools to your agent, follow the same pattern used above. For more details on adding tools beyond this example, see [add tools to an AI agent](/components/agentic-orchestration/add-tool-to-ai-agent.md). ## Next steps Now that you’ve built your first Camunda AI agent, you can tailor it further. For example: - Add and configure more tools. - Update the system prompt to adjust the AI agent's behavior. - Experiment with different model providers and configurations in the AI Agent connector. Learn more about [Camunda agentic orchestration](/components/agentic-orchestration/agentic-orchestration-overview.md) and the [AI Agent connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md). :::tip Register for the free [Camunda 8 - Agentic Orchestration](https://academy.camunda.com/path/c8-lp-agentic) course to learn how to design and integrate AI agents in your end-to-end processes. For more insights and examples, explore our [Camunda AI agents blog posts](https://camunda.com/blog/tag/ai-agent/). ::: --- ## Run your first Spring Boot, Node.js, Python, or C# project with Camunda 8 ## About This guide is tailored for developers who want to implement process automation solutions using Java (Spring), JavaScript (Node.js), Python, or C#. You'll work with a local, self-managed, lightweight Camunda 8 environment. ## Get started with an example project You will: - Run Camunda 8 in a local development environment using **Camunda 8 Run**. - Use the **Desktop Modeler** to deploy and start a business process. - Run workers written in one of: - The **Spring (Java) SDK**, - The **Node.js (JavaScript) SDK**, - The **Python SDK**, or - The **C# SDK** to handle tasks in the process. ## Prerequisites [JDK 21-26](https://www.oracle.com/de/java/technologies/downloads/) [Maven 3](https://maven.apache.org/index.html) [Node.js](https://nodejs.org/en) 20+ NPM 11+ [Python](https://www.python.org/downloads/) 3.10+ pip [.NET SDK](https://dotnet.microsoft.com/download) 8 or later ## Download the Getting Started Package Download the Getting Started Package from [Camunda Downloads](/downloads). The starter package includes the following components: - [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) – A simplified, single-application Camunda configuration for a local development environment. - [Desktop Modeler](/components/modeler/desktop-modeler/index.md) – An application for modeling BPMN, DMN, and Forms. - [Getting started project](https://github.com/camunda/camunda-8-get-started) – An example project with a simple BPMN process and workers in Java, JavaScript, Python, or C#. All of these components are included in the starter package. You do not need to download them separately. The links above are provided for additional information. ## Example project The example project, located in the `camunda-8-get-started/2-order-process-with-service-workers` directory, contains a BPMN process model that represents a simple e-commerce flow with three service tasks. ![Example business process](./img/getting-started-guide-example-process.png) The service tasks in the process are executed by [job workers](/reference/glossary.md#job-worker). The `java`, `nodejs`, `python`, and `csharp` directories inside `2-order-process-with-service-workers` contain code for job workers that correspond to this process model. ## Instructions Unzip the Camunda 8 starter package. Start Camunda 8 Run by changing into the directory and running the command: ```bash ./camunda-start.sh ``` ```bash .\camunda-start.bat ``` Open the Desktop Modeler application from the starter package. In Desktop Modeler, click File > Open File Select `camunda-8-get-started/2-order-process-with-service-workers/bpmn/order-process.bpmn` Click the "Rocket" icon to connect to your Camunda 8 Run instance and deploy the model. You can use the pre-configured `c8run (local)` connection. Click the “Play” icon on the bottom toolbar of Desktop Modeler to deploy and start an instance of the process model. You do not need to set any variables for the process. Optionally, you can set a value for the item variable by pasting in:
{`{"item": "special widget"}`}
![Start a new process instance in Desktop Modeler](./img/get-started-example-start-process.png) ## View the process instance and start job workers A [process instance](/reference/glossary.md#process-instance) is now running in the engine. You can view the process instance in **Operate**, the visual operations tool: 1. Navigate to: [http://localhost:8080/operate](http://localhost:8080/operate) 2. Login with the credentials: `demo` / `demo`. There you will see an active process instance. :::note Data needs to sync to Operate, so the process instance may not be visible immediately. Additionally, when the workers are running, process instances will be completed immediately and further process instances will not appear as active. ::: ![Active process instance visible in Operate](./img/get-started-operate-screenshot.png) Next, start the job workers to allow them to perform the work for the service tasks. The workers are configured to connect to the locally-running engine and retrieve available work for the process instance. Change into the Spring SDK directory: ```bash cd camunda-8-get-started/2-order-process-with-service-workers/java ``` Start the workers with the command: ```bash mvn spring-boot:run ``` You can stop the application via Ctrl+C. Change into the Node.js SDK directory: ```bash cd camunda-8-get-started/2-order-process-with-service-workers/nodejs ``` Install dependencies with the command: ```bash npm i ``` Start the workers with the command: ```bash npm start ``` You can stop the application via Ctrl+C. Change into the Python SDK directory: ```bash cd camunda-8-get-started/2-order-process-with-service-workers/python ``` Create a virtual environment and install dependencies: ```bash python -m venv .venv source .venv/bin/activate # On Windows: .venv\Scripts\activate pip install -r requirements.txt ``` Start the workers with the command: ```bash python main.py ``` You can stop the application via Ctrl+C. Change into the C# SDK directory: ```bash cd camunda-8-get-started/2-order-process-with-service-workers/csharp ``` Start the workers with the command: ```bash dotnet run ``` You can stop the application via Ctrl+C. The workers start, connect to the engine, and request work. You will see the workers processing the jobs for the process instance. ## Complete! Navigate back to Operate and verify that your active process instance in Operate has transitioned to completed. ## Summary You have completed your first process instance by: 1. Running Camunda 8 Run locally. 2. Deploying a process model. 3. Starting a process instance. 4. Completing the service tasks in the process instance using job workers. ## Next steps To learn more about our SDKs, visit their respective documentation pages: - Java SDK - [Camunda Java client documentation](/apis-tools/java-client/getting-started.md) - Spring Boot starter - [Camunda Spring Boot Starter documentation](/apis-tools/camunda-spring-boot-starter/getting-started.md) - Node.js SDK - [Camunda 8 JavaScript SDK API documentation](https://camunda.github.io/camunda-8-js-sdk/) - Python SDK - [Python SDK documentation](/apis-tools/python-sdk.md) - C# SDK - [C# SDK documentation](/apis-tools/csharp-sdk.md) ## Teardown You can stop Camunda 8 Run by executing: ```bash ./camunda-stop.sh ``` ```bash ./camunda-stop.bat ```` --- ## Run your first BPMN process with Camunda 8 ## About In this guide, you’ll deploy and run a **Rocket Launch** BPMN process where you play mission control: you provide a `fuelLevel` variable, and the Camunda 8 engine runs the launch sequence end-to-end: 1. Start the launch sequence. 2. Run a pre-flight check. 3. Decide whether systems are **GO** or **NO-GO**. 4. If **GO**: plot the destination via a DMN decision. 5. Generate a mission report and complete the mission. 6. If **NO-GO**: cancel the mission and end the process. After completing this guide, you will have a BPMN process running locally in Camunda 8 and understand how basic concepts, such as variables, DMN, and FEEL expressions, work together in an automated BPMN workflow. ## Step 1: Download the Getting Started Package Download the Getting Started Package from [Camunda Downloads](/downloads). The starter package includes the following components: - [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md): A simplified, single-application Camunda configuration for a local development environment. - [Camunda Modeler](/components/modeler/about-modeler.md): An application for modeling BPMN, DMN, and Forms. - [Getting started project](https://github.com/camunda/camunda-8-get-started): Example projects including the Rocket Launch process used in this guide. ## Step 2: Deploy and run your model 1. Unzip the Camunda 8 starter package. 2. Start Camunda 8 Run by changing to its directory and running the following command based on your OS: ```bash ./camunda-start.sh ``` ```bash .\camunda-start.bat ``` 3. Open Camunda Modeler from the starter package. 4. Click **File**, then **Open File** to open the process model `camunda-8-get-started/1-rocket-launch/rocket-launch.bpmn`. 5. Deploy your model by clicking the rocket icon in the bottom toolbar. You can use the pre-configured **c8run (local)** connection and click **Deploy Camunda Project**. This automatically deploys all resources in the [project](../components/concepts/projects.md), including the DMN decision table. 6. Click the play icon in the bottom toolbar to start a new [process instance](/reference/glossary.md#process-instance). 7. Run your first model instance by setting the input mission variables. For example: ```json { "missionName": "Odyssey", "fuelLevel": 95 } ``` ## Step 3: Explore your process in Operate 1. Navigate to Operate at [http://localhost:8080/operate](http://localhost:8080/operate) and log in using the `demo` / `demo` credentials. 1. Find the **Rocket Launch** process and click your running instance. :::note Data needs to sync to Operate, so your process instance may not be visible immediately. ::: ### Watch the timer Your process instance pauses at the **Countdown T-10** task for 10 seconds. Open the instance in Operate and watch the [token](/reference/glossary.md#token-process-instance) — the marker showing the current point of execution — move through the countdown in real time. ### Inspect variables Click a completed instance in Operate and open the **Variables** panel. You can see the variables the process created, such as `destination`, `fuelAfterBurn`, and `missionResult`. Compare the variables between a successful launch and a canceled mission to see how the process logic sets different values. ## Step 4: Try different scenarios Start additional process instances in Modeler with different `fuelLevel` values. Here’s what happens based on your `fuelLevel` input: | `fuelLevel` | What happens | | ----------- | ---------------------------------------------------------------- | | `>= 90` | Launch proceeds — destination set to **Venus** (5 experiments) | | `76 – 89` | Launch proceeds — destination set to **Mars** (5 experiments) | | `75` | Launch proceeds — destination set to **Mars** (3 experiments) | | `50 – 74` | Launch proceeds — destination set to **Moon** (3 experiments) | | `< 50` | **Mission canceled** — the process ends on the cancellation path | :::tip Go to Operate to compare how each process instance takes a different path. ::: ## Step 5: Understand how it works The process uses no external code. All logic is expressed using [FEEL expressions](/components/modeler/feel/what-is-feel.md) in script tasks and a [DMN decision table](/components/modeler/dmn/decision-table.md). | Task | FEEL expression | Output variable | | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | | Pre-flight check | `if fuelLevel >= 50 then "ALL SYSTEMS GO" else ...` (sets status to cancel) | `systemStatus` | | Cancel mission | `"Mission " + missionName + " scrubbed — not enough fuel (" + string(fuelLevel) + "%)"` | `missionResult` | | Plot destination | DMN table: `>= 90` → Venus, `>= 75` → Mars, `>= 50` → Moon | `destination` | | Burn stage 1 | `fuelLevel - 25` | `fuelAfterBurn` | | Run experiments | `if fuelLevel > 75 then 5 else 3` | `experimentsRun` | | Mission report | `"Crew " + missionName + " reached " + destination + "! Fuel: " + string(fuelAfterBurn) + "%. Experiments: " + string(experimentsRun)` | `missionResult` | Fixed rules work when you know the decision in advance, as in this launch sequence. When a step needs judgment you can't express as a deterministic rule, you can hand that step to an [AI agent](/reference/glossary.md#ai-agent) instead. The engine runs, retries, and records the agent step in the same way as the script tasks and DMN decision in this process. ## Step 6: Clean up Navigate back to Operate and verify that your process instances have completed successfully (or were canceled, depending on the fuel level). You can now stop your Camunda 8 Run local environment by executing the following command based on your OS: ```bash ./camunda-stop.sh ``` ```bash .\camunda-stop.bat ``` ## Next steps Now that you've run your first BPMN process in Camunda 8, explore more of the platform: - [Build your first AI agent](./getting-started-agentic-orchestration.md) and add AI-driven steps to your process. - [Run your first Spring Boot or Node.js project with service workers](./getting-started-example.md). - [Learn more about the BPMN, DMN, and FEEL elements supported in Camunda](/components/concepts/bpmn-dmn-feel.md). --- ## Get started with API orchestration ## About This guide is designed for users who prefer a low-code approach to process automation. You can follow this tutorial using either a local, Self-Managed lightweight setup, or Camunda 8 SaaS. This guide will walk you through working with a REST connector task as a first time Camunda 8 SaaS user. The REST connector is a [protocol connector](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md#protocol-connectors), where you can make a request to a REST API and use the response in the next steps of your process. :::note New to connectors? Review our [introduction to connectors](/components/connectors/introduction.md) to get familiar with their capabilities, and have a closer look at all of the available [out-of-the-box connectors](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md). :::
Have you signed up for Camunda yet?
The concept of a [connector](/reference/glossary.md#connector) consists of two parts: the business logic is implemented as a [job worker](/reference/glossary.md#job-worker), and the user interface during modeling is provided using an element template. In this guide, you will create a REST connector task in your process, handle the HTTP response, and deploy your process. New to creating a process? Get started by [modeling your first diagram](/components/hub/workspace/modeler/collaboration/design-your-process.md). ## Create a REST connector task To use a REST connector in your process, follow the steps below: 1. In Camunda Hub, open a [workspace](../components/hub/workspace/index.md). 2. Create a new project. 3. In the project, click **Create new > BPMN diagram**. 4. With your new diagram open, make sure you're in [**Implement** mode](/components/hub/workspace/modeler/collaboration/implement-your-process.md). 5. With no diagram elements selected, open the **Details** panel on the right side of the modeling interface. 6. Under **Properties > General**, configure the following properties: - **Name:** `API Orchestration Tutorial` - **ID:** `api-orchestration-tutorial` 7. Click the existing start event, then select the **Append task** icon. 8. Click the new task, then select the **Change element** icon. 9. Search for and select the **REST Outbound Connector**. 10. With the **REST Outbound Connector** selected, under **Properties > General**, name the task `Make a request`. ## Make your REST connector executable Set up your REST connector to get a random cat fact from the [Cat Fact API](https://catfact.ninja/): 1. Select the **REST Outbound Connector**. 2. Under **Properties > HTTP endpoint**, set the **URL** to `https://catfact.ninja/fact`. ## Handle your response The HTTP response will be available in a temporary local response variable. This variable can be mapped to the process by specifying **Result Variable**. In the **Output mapping** section use `={"body" : body}` as the **Result Expression** so you can see the entire JSON object returned if it's successful. ## Deploy your process To deploy your process, take the following steps: 1. Drag the bolded circular end event element from the palette and onto the canvas, or by clicking on the final service task, and then the end event element alongside it. Ensure there is an arrow connecting the service task to the end event. 2. In the top right corner click the blue **Deploy & Run** button. Your diagram is deployed to your cluster and a new [process instance](/reference/glossary.md#process-instance) is started. :::note If you have not yet created a cluster, clicking **Deploy & Run** will take you to the console to create a cluster. Once you make your cluster creation request, you will automatically be redirected back to Modeler. The creation of a cluster can take 1 to 5 minutes. To read more about creating clusters, visit our documentation on [creating a cluster](create-cluster.md). ::: 3. In the top left corner of the screen, click the square-shaped **Camunda components** button. Navigate to Operate to see your process instance with a token waiting at the service task by clicking **View process instances**. ## Wrap up Congratulations! You successfully built your first API orchestration solution with Camunda 8. Camunda 8 empowers users to automate processes faster. With connectors, you can access APIs without writing code. Connector tasks are also how you give an [AI agent](/reference/glossary.md#ai-agent) something to do. An agent can call the same connectors you used here as its tools, and the process still defines which tools the agent is allowed to use. Don't want to build the process yourself? Click this button to create it from a template in Camunda 8 SaaS, or sign up first. Open model in Camunda 8 Sign up ## Additional resources and next steps - Learn more about Camunda 8 and what it can do by reading [What is Camunda 8](/components/components-overview.md) or watching our [Overview video](https://bit.ly/3TjNEm7) in Camunda Academy. - [Build your first AI agent](./getting-started-agentic-orchestration.md) - [Learn about types of connectors](/components/connectors/connector-types.md) - [Use connectors in your BPMN process](/components/connectors/use-connectors/index.md) - [Camunda Academy: Generate a Connector Template from an API Specification](https://academy.camunda.com/c8-h2-generate-connector-from-api/) --- ## Get started with human task orchestration ## About This guide is designed for users who prefer a low-code approach to process automation. You can follow this tutorial using either a local, Self-Managed lightweight setup, or Camunda 8 SaaS. Camunda 8 allows you to orchestrate processes with human tasks of any complexity. Utilizing [user tasks](/reference/glossary.md#user-task), you can create and assign tasks to users. Then, users can perform their work and enter the necessary data to drive the business process. This guide introduces you to the basics of human task orchestration. You will create a simple process to decide on dinner, and drive the process flow according to that decision.
Have you installed Camunda yet?
Have you signed up for Camunda yet?
Take the following five steps to create and run your first process with a human in the loop: ## Step 1: Create a new process In this step, you will design a process that demonstrates how to route the process flow based on a user decision. In this example, you will create a process to decide what is for dinner. ### Create a new file 1. In Camunda Hub, navigate to your workspace. 2. In the workspace, click **Create project**, and name your project. 3. In your project, select **Create new > BPMN diagram**. 4. In the top navigation, next to **New BPMN diagram**, click the vertical ellipsis menu, and rename the file `Decide on Dinner`. 5. Make sure to name the process itself as well. Click the empty canvas, and specify the process name and technical ID in the properties panel on the right side of the screen. This specifies how the process will appear in other tools of Camunda 8. Within Desktop Modeler: 1. Under **Create a new file**, select **BPMN diagram**. 2. Click the empty canvas, and specify the process name and technical ID in the properties panel on the right side of the screen. This specifies how the process will appear in other tools of Camunda 8. ### Design the process :::note To run this guide, make sure to be in [**Implement** mode](../components/hub/workspace/modeler/collaboration/implement-your-process.md) to specify the technical details of the process. ::: 1. A **start event** is automatically added to the canvas. Click it to display configuration and append options. 2. Click the rectangular **Append Task** icon to append a task. 3. In the right properties panel, enter a descriptive name for the task, such as `Decide what's for dinner`. If the properties panel for your task doesn't open automatically, navigate to **Window > Toggle Properties Panel** to open it manually. 4. Change the task type by clicking on the element and selecting the **Change element** menu icon. Select **User Task**. 5. Select the user task and click on the diamond-shaped icon to append an exclusive gateway. The gateway allows you to route the process flow differently, depending on conditions. 6. Select the gateway and append a task by clicking the task icon. Repeat it to create a second process flow. Name the tasks based on what the user decides to eat: in this case, we've named ours `Prepare chicken` and `Prepare salad`. 7. To route the user to the right task, add [expressions](/components/concepts/expressions.md) to the **sequence flows**. Sequence flows are represented by arrows connecting the gateway to the tasks. To add an expression, click on a sequence flow to view the **properties panel**, and open the **Condition** section. 8. Verify the sequence flows have the following expressions: `meal="Chicken"` on one side, and `meal="Salad"` on the other. You will define the variable `meal` later when designing a form for the user task. 9. Connect the split process flows again. Append another exclusive gateway to one of the tasks. Select the other task and drag the arrow-shaped sequence flow tool to connect it to the gateway. 10. Select the gateway and add an **end event** to your process, denoted by the circle with the thick outline. Don't worry about saving your process diagram. Every change you make is automatically saved. 11. Save the process file. :::note New to BPMN or want to learn more? Visit our [BPMN cheat sheet](https://page.camunda.com/wp-bpmn-2-0-business-process-model-and-notation-en) for an overview of all BPMN symbols. Variables are part of a process instance and represent the data of the instance. To learn more about these values, variable scope, and input/output mappings, visit our documentation on [variables](/components/concepts/variables.md). ::: ## Step 2: Design a form You have now designed the process. To allow the user to make the decision, you will now design a [form](../components/modeler/forms/camunda-forms-reference.md). Forms can be added to user tasks and start events to capture user input, and the user input can be used to route the process flow, to make calls to APIs, or to orchestrate your services. 1. Select the user task you created in **[Step 1](#step-1-create-a-new-process)**. 1. In the floating menu, click the link icon. A menu expands that allows you to create a new form. 1. Click **Create new form**. A form will be created and opened in the form editor. The form is automatically named. 1. In the left **Components** pane, under **Presentation**, click and drag the **Text view** component to the empty form. 1. On the right side of the modeling interface, in the properties panel, open the **General** section, and enter a **Text** value, such as `What's for dinner?`. 1. In the left **Components** pane, under **Selection**, click and drag the **Radio group** component to the form. In the properties panel, enter the following: - **Field label**: `Meal*` - **Key**: `meal`. The key maps to a process variable. The value of the component will be stored in this variable, and it can be read by the process that uses this form. You use `meal` here because you already used this key for the conditions you set up in the process. 1. Scroll down to the **Static options** section of the properties panel to add radio options. Since there are two options for the dinner, add an extra value by clicking on the plus sign. Enter the value `Chicken` with the same label as `Chicken` and enter the value `Salad` with the label as `Salad` in the other value. The `meal=" 1. Create a new form in Desktop Modeler by navigating to **File > New File > Form (Camunda 8)**. 1. In the left **Components** pane, under **Presentation**, click and drag the **Text view** component to the empty form. 1. On the right side of the modeling interface, in the properties panel, open the **General** section, and enter a **Text** value, such as `What's for dinner?`. 1. In the left **Components** pane, under **Selection**, click and drag the **Radio group** component to the form. In the properties panel, enter the following: - **Field label**: `Meal*` - **Key**: `meal`. The key maps to a process variable. The value of the component will be stored in this variable, and it can be read by the process that uses this form. You use `meal` here because you already used this key for the conditions you set up in the process. 1. Scroll down to the **Static options** section of the properties panel to add radio options. Since there are two options for the dinner, add an extra value by clicking on the plus sign. Enter the value `Chicken` with the same label as `Chicken` and enter the value `Salad` with the label as `Salad` in the other value. The `meal=" ## Step 3: Link the form to your process Once the form is designed, verify it's linked properly in your process: 1. Navigate to the process you created in [Step 1](#step-1-create-a-new-process). 2. Select the user task. Click the **form link icon** to open the form menu. 3. Verify the task is linked to your new form. 1. Open the process you created in **[Step 1](#step-1-create-a-new-process)** by clicking on the process file's name in the top bar. 2. Select the user task, and open the **Form** section in the properties panel. :::note If the properties panel for your task doesn't open automatically, navigate to **Window > Toggle Properties Panel** to open it manually. ::: 3. In the form section, select **Camunda Form**, and enter the **Form ID** for the form you created in **[Step 2](#step-2-design-a-form)**. ## Step 4: Run your process Your process is now ready to run. Given its human-centric nature, it is well suited to be run in Tasklist. In order to make it accessible from Tasklist, the process must be deployed first. :::tip Human-centric processes involving user tasks seamlessly unfold within Tasklist, offering a cost-effective orchestration solution for human work with forms. However, the versatility of these processes extends beyond Tasklist, encompassing various alternative methods and applications. For instance, users can be redirected to external applications to fulfill tasks, bespoke task applications can be developed for any domain, or interactions with the physical world can be captured through event signals from sensors and IoT devices. ::: ### Deploy and test run If no environment is assigned to your workspace, the deploy dialog tells you so. In SaaS, every cluster you create has one environment, which an organization admin must [assign to your workspace](/components/hub/organization/manage-environments/assign-environments.md) before you move on. 1. At the top right of the modeling interface, click **Deploy & run** to deploy the process to your cluster. 2. Select a target environment. 3. Under **Resources**, select **All resources**. Your project contains two files: the BPMN process diagram and the linked form. This option deploys both together. In other contexts, it might make sense to deploy **Only this resource** for individual files. 4. Click **Deploy & run**. This deploys both project resources and starts a process instance. :::tip Other options to run a process are to start it via Tasklist, test it in the Test mode, or call it via the API or an inbound trigger. Read more about [run options](/components/hub/workspace/modeler/run-or-publish-your-process.md). ::: :::note Ensure your installation of [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) is running prior to deploying your process. ::: You need to deploy both the form and process before running a process instance. Deploy your form: 1. Open your form. 2. Using the **c8run (local)** connection, click the rocket-shaped **Deploy** icon to begin deploying your form. 3. Click **Deploy Form**. Deploy your process: 1. Open your BPMN process. 2. Using the **c8run (local)** connection, click the rocket-shaped **Deploy** icon to begin deploying your process. 3. Click **Deploy BPMN**. With both resources deployed, you can run a process instance. Click the play icon, then click **Start BPMN process instance**. ### Check successful start in Operate 1. At the top right of the screen, a notification message confirms the instance started. Click **View process instance**. If you miss the notification message, next to **Deploy & run**, open the chevron menu, and click **View process in Operate**: 2. In Operate, you will see a visualization of the running process instance. Notice that a green **token** is waiting at the user task. This means that a task is waiting to be worked on in Tasklist. :::tip In production, Operate is used to monitor both long-running and straight-through, high-throughput processes. In development environments, use Operate to confirm if the process flow works as expected. For faster in-place validation during development, use the [Test mode](/components/hub/workspace/modeler/validation/test-your-process.md). ::: 1. Open Operate at `http://localhost:8080/operate`, and select **Processes** from the left panel. 2. In the **Process** panel, use the **Name** drop-down to select your process. 3. A visualization of your running process instance now displays in Operate, and your user task is marked with a green **token** icon. This means that a task is waiting to be worked on in Tasklist. ## Step 5: Complete a user task When the process instance arrives at the user task, a new user task instance is created at Zeebe. The process instance stops at this point and waits until the user task is completed. Applications like [Tasklist](/components/tasklist/introduction-to-tasklist.md) can be used by humans to complete these tasks. In this last step, you will open Tasklist to run the user task you created. :::tip While it may originally seem like the goal of automating a process is to remove humans entirely, efficiently allocating work through user tasks can be even more beneficial. Within this example, we've included a form to demonstrate the completion of a user task. Using the Zeebe or Tasklist API, many other ways to complete a user task are possible, such as redirecting to another application to complete the task, or even listening to IoT devices to capture human interaction with the real world via job workers. ::: 1. In the top navigation, on the far left side next to **Operate**, click the **Camunda components** menu icon. 1. Click **Tasklist**. 1. Open Tasklist at `http://localhost:8080/tasklist`. In Tasklist: 1. On the left, you will notice a list of **tasks**. There should be one open task `Decide what's for dinner`. Click this task to open it in the detail view. 1. In the detail view, the form you created in **[Step 2](#step-2-design-a-form)** appears. It is read only since this task is currently unassigned. You have to claim the task to work on it. Click **Assign to me** to claim the task. 1. Select one of the radio options. 1. Click **Complete Task** to submit the form. ![complete a human task in Tasklist](./img/user-task-tasklist.png) 1. To verify your task completion, you can filter by **Completed** tasks in the left task list panel. You can now navigate back to Operate and notice the process instance has continued as the token has moved forward to the selected option. The token moves through the exclusive gateway (also called the XOR gateway), and is used to model the decision in the process. When the execution arrives at this gateway, all outgoing sequence flows are evaluated in the order in which they have been defined. The sequence flow which condition evaluates to ‘true’ is selected for continuing the process. In this case, the token will move through the gateway and (according to the conditional expressions we outlined earlier) to the selected dinner based on the **Decide what's for dinner** user task we completed. If we select **Chicken**, the token moves forward to **Prepare chicken**. If we select **Salad**, the token moves forward to **Prepare salad**. ![Operate showing the completed process instance](./img/completed-task.png) ## Wrap up At this point, you've successfully crafted a human-centered process that routes the process flow based on a decision made by a user. A core value of Camunda 8 lies in the combination of automation and human interaction. The same pattern applies when an [AI agent](/reference/glossary.md#ai-agent) produces the result a person acts on: the user task, form, and gateway you built here work the same way, whichever kind of step supplied the data. Continue with the [following resources](#additional-resources-and-next-steps) to learn about intelligent task assignments, flexible forms to capture data and decisions, operational insights to refine task efficiency, and pathways to publish your processes to users via Tasklist or even publicly. Don't want to build the process yourself? Click this button to create it from a template in Camunda 8 SaaS, or sign up first. Open model in Camunda 8 Sign up ## Additional resources and next steps - Learn how to use [BPMN user tasks](/components/modeler/bpmn/user-tasks/user-tasks.md) to route tasks to the right users. - Learn how to [build more complex forms](/components/hub/workspace/modeler/modeling/utilize-forms.md) using the form editor. - Learn how to write powerful [expressions](/components/concepts/expressions.md) and utilize [variables](/components/concepts/variables.md) to route complex process flows. - Get an [introduction to Operate](/components/operate/operate-introduction.md). - Learn how to [set up Tasklist](/components/tasklist/introduction-to-tasklist.md) for efficient task management. - Explore start forms and attach the form directly to the start event for authenticated starts in Tasklist. --- ## Get started with Camunda Ready to start? Run your first local Camunda 8 project. Get hands-on with [Camunda 8](https://camunda.io) with our getting started guides. Start by running your first BPMN process, building your first [AI agent](/reference/glossary.md#ai-agent), and orchestrating human tasks and APIs using [connectors](/reference/glossary.md#connector). [Run your first BPMN process with Camunda 8](./getting-started-hello-world.md) ## Run your first BPMN process with Camunda 8 Launch a fully-automated Rocket Launch process on your local machine with no code required. Provide a fuel level, and let Camunda 8 run pre-flight checks, plot a destination with DMN, and generate a mission report using only FEEL expressions. Run your first BPMN process ## Build your first AI agent Get started with Camunda [agentic orchestration](/components/agentic-orchestration/agentic-orchestration-overview.md) by building and running your first [AI agent](/reference/glossary.md#ai-agent). The agent runs as a step inside a BPMN process, so the engine executes, retries, and records it in the same way as the fixed steps around it. Build your first AI agent ## Run your first Spring Boot or Node.js project with Camunda 8 This guide is for developers who want to implement process automation solutions using Spring Boot or Node.js. You'll work with a local, self-managed, lightweight Camunda 8 environment. Run your first local project ## Orchestrate human tasks This guide is for low-code developers using Camunda 8 SaaS to efficiently allocate work through [user tasks](/reference/glossary.md#user-task). Get started with human task orchestration ## Orchestrate APIs using connectors This guide is for users who prefer a low-code approach to process automation, walking you through working with a REST connector task as a first time Camunda 8 SaaS user. You can follow this tutorial using either a local, Self-Managed lightweight setup, or Camunda 8 SaaS. Get started with API orchestration --- ## Conceptual differences When thinking about migration it is important to understand conceptual differences between Camunda 7 and Camunda 8. :::tip Automate migration tasks The [Camunda migration agent skill](./migration-tooling/index.md#agentic-migration) can automate much of the work these differences create, converting BPMN and DMN models and Camunda 7 forms and refactoring code, while flagging what needs your review. ::: ## Architectural differences There are a number of key architectural differences between Camunda 7 and Camunda 8. ### No embedded engine in Camunda 8 Camunda 7 allows embedding the workflow engine as a library in your application. This means both run in the same JVM, share thread pools, and can even use the same data source and transaction manager. In contrast, **the workflow engine** in Camunda 8, Zeebe, is always **a remote resource** for your application, while the embedded engine mode is not supported. If you are interested in the reasons **why** we switched our recommendation from embedded to remote workflow engines, refer to the blog post on [moving from embedded to remote workflow engines](https://blog.bernd-ruecker.com/moving-from-embedded-to-remote-workflow-engines-8472992cc371). The implications for your process solution and the programming model are described below. Conceptually, the only big difference is that with a remote engine, **you cannot share technical [ACID transactions](https://en.wikipedia.org/wiki/ACID)** between your code and the workflow engine. You can read more about it in the blog post on [achieving consistency without transaction managers](https://blog.bernd-ruecker.com/achieving-consistency-without-transaction-managers-7cb480bd08c). ### Different data types In Camunda 7, you can store different data types, including serialized Java objects. Camunda 8 only allows storage of **primary data types or JSON** as process variables. This might require some additional data mapping in your code when you set or get process variables. Camunda 7 provides [Camunda Spin](https://docs.camunda.org/manual/latest/reference/spin/) to ease XML and JSON handling. This is not available with Camunda 8, and ideally you migrate to an own data transformation logic you can fully control (for example, using Jackson). To migrate existing process solutions that use Camunda Spin heavily, you can still add the Camunda Spin library to your application itself and use its API to do the same data transformations as before in your application code. ### Expression language Camunda 7 uses [Java Unified Expression Language (JUEL)](https://docs.camunda.org/manual/latest/user-guide/process-engine/expression-language/) as the expression language. In the embedded engine scenario, expressions can even read into beans (Java object instances) in the application. Camunda 8 uses [Friendly-Enough Expression Language (FEEL)](/components/modeler/feel/what-is-feel.md) and expressions can only access the process instance data and variables. Most expressions can be converted (see [this code in the diagram converter](https://github.com/camunda/camunda-7-to-8-migration-tooling/blob/5e66012c2ef5f301ab6e61b6a3120b13c9c26459/diagram-converter/core/src/main/java/io/camunda/migration/diagram/converter/expression/ExpressionTransformer.java#L21) as a starting point), but you may need to completely rewrite others. Some expressions might even require an additional service task to prepare necessary data that may have been calculated on the fly in Camunda 7. You can also use the [FEEL Copilot](/components/early-access/alpha/feel-copilot/feel-copilot.md) to rewrite complex expressions for you. ### Different connector infrastructure Through Camunda Connect, Camunda 7 provides an HTTP and a SOAP HTTP [Connector](https://docs.camunda.org/manual/latest/reference/connect/). Camunda 8 offers multiple [Connectors](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md) out-of-the-box on a completely different codebase. To migrate existing connectors, consider the following options: - Use the [REST protocol connector](components/connectors/protocol/rest.md) to leverage an out-of-the-box connector. - Create a small bridging layer via custom [job workers](/components/concepts/job-workers.md). ### Multi-tenancy There are several differences between how [multi-tenancy](/components/concepts/multi-tenancy.md) works in Camunda 7 and Camunda 8: 1. The [one engine per tenant approach from Camunda 7](https://docs.camunda.org/manual/develop/user-guide/process-engine/multi-tenancy/#one-process-engine-per-tenant) isn't possible with Camunda 8. Camunda 8 only provides multi-tenancy through a tenant identifier. 2. In Camunda 7, users can deploy shared resources (processes, decisions, and forms) available to all tenants. In Camunda 8, there are no shared resources. This will be added in the future. 3. In Camunda 7, data is mapped to a `null` tenant identifier, meaning resources are shared by default. In Camunda 8, data is mapped to the `` tenant identifier when multi-tenancy is disabled. 4. [Tenant checks in Camunda 7](https://docs.camunda.org/manual/develop/user-guide/process-engine/multi-tenancy/#disable-the-transparent-access-restrictions) can be disabled to perform admin/maintenance operations. This can't be done in Camunda 8, but an admin user can be authorized to all tenants, which would result in the same thing. 5. If a user tries to trigger a command on a resource mapped to multiple tenants in Camunda 7, an exception is thrown, and [the `tenantId` must be explicitly provided](https://docs.camunda.org/manual/develop/user-guide/process-engine/multi-tenancy/#run-commands-for-a-tenant). However, the Camunda 7 engine will try to infer the correct `tenantId` as much as possible. Users in Camunda 7 that are authorized for multiple tenants may perform a lot more operations without providing a `tenantId`. This inference in the Zeebe Broker doesn't happen in Camunda 8, and Zeebe asks users to provide the `tenantId` explicitly. ## Process solutions using Spring Boot With Camunda 7, a frequented architecture to build a process solution (also known as process applications) is composed out of: - Java - Spring Boot - Camunda Spring Boot Starter with embedded engine - Glue code implemented in Java delegates (being Spring beans) This is visualized on the left-hand side of the following image. With Camunda 8, a comparable process solution would look like the right-hand side of the picture and leverage: - Java - Spring Boot - [Camunda Spring Boot Starter](../../apis-tools/camunda-spring-boot-starter/getting-started.md) (embedding the Zeebe client) - Glue code implemented as workers (being Spring beans) ![Diagram showing the spring boot architecture](../img/architecture-spring-boot.png) The difference is that the engine is no longer embedded. If you are interested in the reasons why Camunda switched our recommendation from embedded to remote workflow engines, refer to this blog post on [moving from embedded to remote workflow engines](https://blog.bernd-ruecker.com/moving-from-embedded-to-remote-workflow-engines-8472992cc371). The packaging of a process solution is the same with Camunda 7 and Camunda 8. Your process solution is one Java application that consists of your BPMN and DMN models, as well as all glue code needed for connectivity or data transformation. The big difference is that the configuration of the workflow engine itself is not part of the Spring Boot application anymore. ![Process Solution Packaging](../img/process-solution-packaging.png) :::note Process solution definition is taken from [Practical Process Automation](https://processautomationbook.com/). ::: You can find a complete Java Spring Boot example, showing the Camunda 7 process solution alongside the comparable Camunda 8 process solution in the [Camunda 7 to Camunda 8 migration example](https://github.com/camunda-community-hub/camunda-7-to-8-migration). ## Programming model The programming models of Camunda 7 and Camunda 8 are very similar if you program in Java and use Spring. For example, a worker in Camunda 8 can be implemented as follows (using the [Camunda Spring Boot Starter](../../apis-tools/camunda-spring-boot-starter/getting-started.md)): ```java @JobWorker(type = "payment") public void retrievePayment(ActivatedJob job) { // Do whatever you need to, for example invoke a remote service: String orderId = job.getVariablesMap().get("orderId"); paymentRestClient.invoke(...); } ``` :::info - You can find more information on the programming model in Camunda 8 in this blog post on [how to write glue code without Java Delegates in Camunda Cloud](https://blog.bernd-ruecker.com/how-to-write-glue-code-without-java-delegates-in-camunda-cloud-9ec0495d2ba5). - Check out [code conversion patterns](../migration-tooling/code-conversion/) for more details. ::: ## Other process solution architectures Besides Spring Boot, there are other environments used to build process solutions. ### Container-managed engine (Tomcat, WildFly, WebSphere & co) Camunda 8 doesn't provide integration into Jakarta EE application servers like Camunda 7 does. Instead, Jakarta EE applications need to manually add the Zeebe client library. The implications are comparable to what is described for Spring Boot applications in this guide. ![A diagram showing a container-managed engine](../img/architecture-container-managed.png) ### CDI or OSGI Due to limited adoption, there is no support for CDI or OSGI in Camunda 8. A lightweight integration layer comparable to the [Camunda Spring Boot Starter](../../apis-tools/camunda-spring-boot-starter/getting-started.md) may be provided in the future. ### Polyglot applications (C#, Node.js) When you run your application in Node.js or C#, for example, you exchange one remote engine (Camunda 7) with another (Camunda 8). As Zeebe comes with a different API, you need to adjust your source code. ![A diagram showing a polygot application architecture](../img/architecture-polyglot.png) ## Plugins [**Process engine plugins**](https://docs.camunda.org/manual/latest/user-guide/process-engine/process-engine-plugins/) are not available in Camunda 8, as such plugins can massively change the behavior or even harm the stability of the engine. Some use cases might be implemented using [exporters](/self-managed/concepts/exporters.md) or [interceptors](self-managed/components/orchestration-cluster/zeebe/zeebe-gateway/interceptors.md#implementing-an-interceptor). :::note Exporters are only available for Self-Managed Zeebe clusters and are not available in Camunda 8 SaaS. ::: Migrating **Desktop Modeler Plugins** is generally possible, as the same modeler infrastructure is used. **Cockpit or Tasklist plugins** _cannot_ be migrated. --- ## Camunda 7 to Camunda 8 migration guide Learn how to migrate process solutions developed for Camunda 7 to run on Camunda 8. ## About this guide This guide covers the typical migration journey from Camunda 7 to Camunda 8, describing the decisions to be made along the way, as well as linking to more in-depth material or tooling you might find useful. It is important to understand that **Camunda 8 is not a drop-in replacement for Camunda 7**. It is not sufficient to exchange a library; you might have to adjust your BPMN models, refactor code, and probably also re-architect your solution. This effort greatly depends on how your existing Camunda 7 solution is built. This guide dives deeper into all the aspects involved. ## What you will learn This guide covers the following main aspects involved in migrating from Camunda 7 to Camunda 8. ## Help and further resources Get help with your migration journey from the following additional resources. - The [Camunda forum](https://forum.camunda.io/c/c7-to-c8/) - Camunda Consulting - [Migration workshops](https://camunda.com/wp-content/uploads/2024/03/Camunda_ConsultingWorkshops_5-Migration-Evaluation_2024.pdf) - Professional advisory services - [Camunda Partners](https://camunda.com/de/partners/): - Professional advisory services - Implementation services - [Camunda Academy: Migration Overview](https://academy.camunda.com/c8-migration-overview/) :::info Still need more help? [Reach out to Camunda](https://camunda.com/contact-us/). ::: --- ## Migration journey Although migration projects differ in complexity, there is a common theme in how to approach the migration journey. ## Migration journeys The complexity of Camunda-7-based solutions varies significantly. Solutions range from small implementations with [clean delegates](./migration-tooling/code-conversion.md) to large solutions with hundreds of processes using internal API hacks. This guide covers two typical journeys - a simplified migration journey and an advanced migration journey. ### Simplified migration journey Suitable for solutions that are easy to migrate. If your solution fits into this category, migrating to Camunda 8 is straightforward. ![Diagram showing a simplified migration journey](../img/simple-journey.png) ### Advanced migration journey For more complex solutions that require tackling additional complexities. ![Diagram showing an advanced migration journey](../img/advanced-journey.png) Maybe the truth will be somewhere in the middle for you. As a rule of thumb, Camunda advises keeping things as simple as possible (but of course not simpler). Let's explore the various steps in more detail. You might skip some steps if a simplified journey is sufficient. ## 1Orient yourself First, understand what needs to be done to migrate and what effort to expect. This allows you to plan and budget a migration project properly. Important steps include: - Understand [conceptual differences between Camunda 7 and Camunda 8](./conceptual-differences.md). - [Analyze your solution](#analyze-your-solution) to understand required changes based on your models. - Explore [code conversion patterns](./migration-tooling/code-conversion.md). - [Estimate migration effort](#estimate-migration-effort-and-budget-project) and budget the project. - [Define your target Camunda version](#define-your-target-camunda-version-for-migration) and derive a migration timeline. - [Leverage advisory services](#leverage-guidance-advisory-and-tooling) from Camunda consulting or certified partners. After orientation, you can plan your migration project and start two independent work streams: - [Set up Camunda 8](#2set-up-camunda-8). - [Migrate your solution(s)](#4migrate-solutions). But first, let's understand orientation in more detail. ### Analyze your solution Use the [**Migration Analyzer**](./migration-tooling/index.md#migration-analyzer) to understand migration tasks. ![Sample results from the Migration Analyzer tool](../img/analyzer-result-excel.png) To add some real-world flavor: In a customer scenario, our consultants ran 400 BPMN files through the tool, resulting in roughly 12,000 lines of findings. Analyzing those lines using the spreadsheet reduced that number to roughly 30 different types of tasks, making the migration project manageable. This analysis will help you understand what needs to be done to migrate. ### Estimate migration effort and budget project This analysis is the basis for estimating migration efforts. Our consultants use rule-of-thumb numbers on how much effort typical tasks require. For example, converting a Camunda 7 Java Delegate to a Camunda 8 Job Worker might take one hour if it just involves refactoring the JavaDelegate. It might take more effort if internal Camunda API is used in the delegate. Looking at [code conversion patterns](./migration-tooling/code-conversion.md) allows you to come up with your own estimate, which you can multiply by the number of tasks from the analyzer. In past migration projects, this gave us a good ballpark estimation to plan and budget the project. ### When to migrate? Any new projects should already be started using Camunda 8. For Camunda 7 solutions, understand the support timeline for the Camunda 7 product: - **Camunda 7 CE** (Community Edition) will EOL (end of life) in **October 2025** with a final release (v7.24) on Oct 14, 2025. - There will be no more Camunda 7 CE releases after that date, and the GitHub repo will be archived. The code will still be available, but issues and pull requests will be closed, and the README will reflect the EOL status. - **Camunda 7 EE** (Enterprise Edition) customers will continue to get **patch releases** (security patches & bug fixes) on a rolling basis **till at least 2030**. - Camunda 7 CE users could switch to Camunda 7 EE to benefit from this long-term support to have enough time for migration. While there is some urgency to start migration efforts, you are not yet under hard pressure. Migrating to Camunda 8 gives you additional advantages, which might raise priority for your solution if: - You want to leverage a SaaS offering (for example, to reduce the effort for hardware or infrastructure setup and maintenance). - You need performance at scale and/or improved resilience. - You need certain features that can only be found in Camunda 8 (for example, BPMN message buffering, improved multi-instance handling, the new connectors framework, RPA, IDP, or the improved collaboration features in Camunda Hub). ### When not to migrate? You might wonder if there are cases where migration doesn't make sense? Camunda basically sees two scenarios here: - Your solution is in legacy mode and will approach its own end of life before Camunda ends support. - Your solution relies on an architecture that is not possible with Camunda 8. For example, software vendors that embedded Camunda 7 as a Java library into their own build, relying on shipping exactly one self-contained Java application. Refer to [conceptual differences](./conceptual-differences.md) for technical details. Most often, you could still migrate those scenarios if you rearchitect the solution. ### Define your target Camunda version for migration Because of the improved core architecture of Camunda 8, features need to be re-added to Camunda 8 step-by-step. That means the current Camunda 8 version might not yet have sufficient feature parity for your scenario to migrate. Prominent examples are [task listeners](https://roadmap.camunda.com/c/147-user-task-listeners-for-assigning-and-completing-events), which will be introduced with 8.8. If your solution requires this feature, it might make sense to wait for the Camunda 8 version that will provide it. You can **[check the public feature roadmap](https://roadmap.camunda.com/) to understand timelines**. The Camunda version to target might differ per process solution. For example, Process A might not need additional features and can migrate right away, but Process B might use the task listener planned for 8.8. That means you wait for rolling out migration for Process B till that version. As Camunda is currently running [an architecture streamlining initiative](https://camunda.com/blog/2024/04/simplified-deployment-options-accelerated-getting-started-experience/) to improve the core architecture, which [will be released with Camunda 8.8](https://camunda.com/blog/2025/01/camunda-87-88-release-update/). Unless you have time pressure or momentum to lose, Camunda generally recommends waiting for this to happen and targeting a Camunda version \>= 8.8 for migration. ![A diagram showing the targeted version](../img/target-version.png) For example, targeting the 8.9 release doesn't mean you wait for it to happen before thinking about migration. Typically, you can already perform analysis (which you might need to do anyway to understand the right target version), which is also important to plan your project and apply for the required budget on time. Doing the migration tasks can also happen before the actual release, either independently or based on early alpha versions. ![A diagram showing the on-time targeted version](../img/starting-ontime.png) ### Leverage guidance, advisory, and tooling This guide is the main resource walking you through migration. As part of your migration journey, you might also want to consider engaging professional services to help you. The main starting points are: - [Migration evaluation workshop (Camunda)](https://camunda.com/wp-content/uploads/2025/06/Camunda_ConsultingWorkshops_6-Migration-Evaluation_2025_EN.pdf) - Scoping Workshop (Camunda) - Professional advisory services (Camunda, Partners) - Implementation services (Partners) Furthermore, you can use the [migration tooling](./migration-tooling/index.md) and related resources. ## 2Set Up Camunda 8 To run any solution on Camunda 8, you must have a running Camunda 8 installation. If you used an embedded engine with Camunda 7 in the past, this model is no longer possible (see [conceptual differences between Camunda 7 and Camunda 8](./conceptual-differences.md)). This might be new to your organization to operate Camunda in addition to your solution itself. The most successful operating model is to have a central team in the organization caring about Camunda, offering it as a self-service platform to others. This is also described in our [process automation Center of Excellence playbook](https://camunda.com/process-orchestration/automation-center-of-excellence/). :::note We want to unmask some typical misconceptions with Camunda 8: - Camunda 8 does **not** need to be consumed as SaaS! But you can use SaaS if you want. - Camunda 8 does **not** mean there needs to be one huge cluster to rule them all! But you can run big workloads on one cluster. - Camunda 8 does **not** need to be set up for horizontal scalability! But you can set this up if you want. You can run small Camunda 8 installations, one per solution, if you like. They can all be Self-Managed, meaning they run in your own datacenter. With the [architecture streamlining and the RDBMS initiative](https://camunda.com/blog/2024/04/simplified-deployment-options-accelerated-getting-started-experience/), Camunda provides a very simple Java installation (single JAR) that removes installation complexity and is sufficient for many use cases (RDBMS support for a secondary data store is available since Camunda 8.9). ::: There are multiple ways to set up Camunda 8: - Use **Camunda's SaaS** offering: You don't need to install or operate the platform yourself. This is the most convenient and generally recommended option. If you face legal challenges around information security, privacy, and compliance, check the [Camunda Trust Center](https://camunda.com/trust-center/). However, be aware of the following limitations: - You cannot [migrate historical audit data from Camunda 7](./migration-tooling/index.md). - [Multi-tenancy](/components/concepts/multi-tenancy.md) is not currently supported. - Run the platform **Self-Managed**. You might want to look at the [Camunda 8 Run distribution](/self-managed/quickstart/developer-quickstart/c8run.md). RDBMS support is available since Camunda 8.9, removing the need for Elastic Search and allowing a relatively simple setup that Camunda 7 users often like. Still, you can go for more scalable options (see also the [architecture streamlining blog post](https://camunda.com/blog/2024/04/simplified-deployment-options-accelerated-getting-started-experience/)). Refer to [installation guides](/self-managed/deployment/index.md) for details. While setting up Camunda 8 is not part of the core migration journey, it is a prerequisite and should be tackled early in the migration journey to avoid blockers. ## 3Making Core Decisions There are a small number of core decisions that will influence your overall migration journey. Although you might make them later in your journey once you have a better understanding of the consequences, Camunda presents them here so that you have them top of mind for the remainder of this guide. ### Drain out vs big bang There are two possible migration scenarios: Drain out and big bang. ![A diagram showing drain out vs big-bang migration scenarios](../img/drain-out-vs-big-bang.png) Both are valid approaches. Let's briefly look at the differences. - **Drain out**: Keep running the C7-based solution, but run the C8-based (migrated) solution in parallel. New process instances are started in the new one. After some time, no processes are active in Camunda 7 any more, and the C7-based solution can be decommissioned. - **Big bang**: The solution is migrated from Camunda 7 to Camunda 8, including data migration scripts. At one moment in time, the C7-based solution is stopped, the data is migrated, and the new C8-based solution is started. This Camunda 7 solution can be decommissioned right after. Note that a big bang relates to **one process solution only**. So if you run multiple processes, you typically migrate them one by one in multiple big bangs, not in one super big bang. For any of those processes, when you migrate, you flip the switch and are on Camunda 8 for those processes. Let's look at the pros and cons of each approach. **Drain out** ![A diagram showing the drain out scenario](../img/drain-out.png) Pros: - No downtime. - No data migration required. - Easy fallback to old solution in case of problems. Cons: - Requires code switch (for example, forwarding messages to either Camunda 7 or Camunda 8 depending on where the corresponding process was started). - Duplicate tooling (for example, one Tasklist for Camunda 7 and one for Camunda 8 processes; same for operators with Cockpit and Optimize). - Need to operate two solutions at the same time. **Big bang** ![A diagram showing the big-bang scenario](../img/big-bang.png) Pros: - Only one solution is running. - No code switches necessary. - No need to support the legacy codebase. Cons: - Requires data migration, at least runtime instances, which has some limitations (see [migration tooling](./migration-tooling/index.md)). - Complexity of the necessary data migration might drive effort. - Might require downtime. There are some scenarios where process instances are short-lived, and the big bang approach can simply drain out existing instances and then restart without the need for data migration, making migration simpler. **Recommendation** There is no general recommendation for which strategy to use. You could consider the **big bang approach less complex** in many scenarios. However, there are some **indicators to use drain out** instead: - **Short-lived processes**: If processes finish quickly, the drain out happens fast, and it might even be possible to delay new process starts until after the drain out has happened. - **Latency-sensitive processes**: Some use cases can't stand the outage time required for data migration. Maybe you can delay data migration and do it after you have already switched to the C8-based solution. Otherwise, big bang might simply not be feasible or require a more sophisticated data migration strategy. - **Risk**: If your use case carries a lot of risk, you might not feel comfortable with the big bang. Most often, this can be mitigated by properly testing your migration. - **No switching logic required**: Maybe running the C8-based solution in parallel requires almost no effort on your end (as you don't have user tasks or message receive events), then it might be the simpler choice. - **Complexity of data migration**: If data migration turns out to be complex in your solution, draining out might be the better choice. **Migrating your solution landscape step-by-step** If you run multiple process solutions, you best migrate them one by one. You can also drive this idea one step further. If you have complex solutions with multiple BPMN models, call activities, and further dependencies, even migrating those solutions in one go might be overwhelming. In this case, you could apply a microservices mindset and adjust your call activities to be service calls to other components. This way, you could migrate that solution process by process. This is not a general recommendation, just illustrating possibilities to reduce the migration scope. ### Adapt existing code vs refactoring You need to adjust the code of your solution during migration. There are two general possibilities: 1. Keep existing code written for Camunda 7 and add an **adapter** to run it with Camunda 8. The [Camunda 7 Adapter](https://github.com/camunda-community-hub/camunda-7-to-8-migration/tree/main/camunda-7-adapter) is a starting point for doing this. 2. **Refactor** your code to work with Camunda 8. The [code conversion part of this guide](./migration-tooling/code-conversion.md) will focus on this approach. We generally **recommend refactoring your code.** Let's briefly dive into both options. **Adapt existing code** ![A diagram showing the adapt existing code approach](../img/adapt-code.png) While this approach sounds easy at first glance, it typically works only with very cleanly implemented Java Delegates (which could also be simply refactored to Job Workers). Even if using an adapter, you still need to understand architectural implications (such as transactional boundaries) and might need to rewrite some code. It also does not adapt all assets (for example, the Camunda 7 service API or test cases are not adapted). In general, the adapter approach is rarely used. **Refactor your code** ![A diagram showing the code refactoring approach](../img/refactor-code.png) Rewrite your code. This follows typical patterns and might even be automated to some extent using OpenRewrite recipes. See the [code conversion guide](./migration-tooling/code-conversion.md) for details. This approach has the big advantage that the resulting solution will comply with best practices on how Camunda 8 solutions should be written. Furthermore, architectural differences can be better understood while refactoring the solution. Some projects are also happy to clean up a codebase that has grown over time, reducing technical debt as part of the migration effort. The downside is the effort required to refactor. The best strategy is to not over-engineer the approach for small code bases, but to automate migration of big code bases as much as possible. ## 4Migrate solution(s) The main tasks to migrate your solutions to run on Camunda 8 include: - Prepare your Camunda 7 solution (optional) - Convert models - Convert expressions - Refactor code - Improve Camunda 8 Solution (optional) Let's dive into the details of these tasks. ### Prepare your Camunda 7 solution (optional) It can make sense to do a first refactoring step in your Camunda 7 codebase to prepare for easier migration. The advantage of this approach is that you can make changes in a codebase and environment you understand well, while you can still run existing test cases without changes, derisking any changes you make. Typical preparation steps include: - **Extract complex code from your JavaDelegates** into Camunda-independent classes (for example, Spring beans) that are then invoked from the original JavaDelegate. This way, you do a step towards [Clean Delegates](./migration-readiness.md) that will be easy to refactor. - **Remove the usage of internal APIs** or calls that depend on the transactionally integrated architecture of Camunda 7 (for example, querying the HistoryService within a JavaDelgate and expect it to know current changes already). - **Increase test coverage** of the solution to increase confidence that code conversions during migration do not break anything. This is especially important if your C7-based solutions lack good test coverage. To avoid migration effort on test cases, ideally **abstract the test cases from the Camunda version** used, either by using a small own abstraction layer, or by using a test framework such as [Cucumber](https://cucumber.io/) or [Sentinel](https://developer.hashicorp.com/sentinel/docs/intro). Scenario tests, for example with [camunda-platform-scenario](https://github.com/camunda-community-hub/camunda-platform-scenario), describe what happens at each wait state instead of the exact path through the process, so they need fewer changes when the model changes. Camunda Process Test supports the same idea with [conditional behavior](/apis-tools/testing/utilities.md#conditional-behavior) (8.9 or later), and the [migration agent skill](./migration-tooling/index.md#migrate-process-tests) migrates camunda-platform-scenario tests to it. - **Structural changes in your BPMN model** to make them runnable on Camunda 8. One trigger could be that you are using constructs that are not supported in Camunda 8 (for example, execution listeners on sequence flows). Or you want to get your models into a state that allows runtime data migration (see [data migration](#migrate-data-optional)), for example by adding artificial wait states before multi-instance tasks (as multiple instance is not supported for runtime data migration). - **Replace JUEL expressions with FEEL**. JUEL is not supported in Camunda 8 (see [conceptual differences](./conceptual-differences.md)). We [plan to release a FEEL plugin for the Camunda 7 platform](https://github.com/camunda/camunda-bpm-platform/issues/2384), allowing you to already rewrite your expressions within your Camunda 7 environment. This change goes beyond simply converting JUEL to FEEL, but also removing any calls to Spring beans that are not possible in FEEL. So you might, for example, add execution listeners invoking the code you need, writing a process variable that then can be evaluated in FEEL. This step is optional and can also be skipped, either because the codebase is already in a clean state or you are feeling confident about directly converting your codebase to Camunda 8. ### Convert models Your BPMN and DMN models need to be adjusted. The [Migration Analyzer](./migration-tooling/index.md#migration-analyzer) can do most changes for you. Depending on how you refactor your code and what elements of Camunda 7 you have used, you can extend or customize the diagram convertion to suit your needs. You can dive into the [technical details of model differences](./migration-tooling/index.md#extending-the-conversion-logic) if you are interested in more detail. ### Convert expressions Your models might contain JUEL expressions, which are not supported in Camunda 8. Simple expressions are directly converted by the [Migration Analyzer](./migration-tooling/index.md#expression-conversion). You can also use the [FEEL Copilot](/components/early-access/alpha/feel-copilot/feel-copilot.md) to rewrite more complex expressions for you. Check the [code conversion patterns section](./migration-tooling/code-conversion.md) for more complicated scenarios. ### Refactor code You need to refactor your code to use Orchestration Cluster APIs only. Most prominently you need to **convert any API calls to Camunda** (for example, RuntimeService) and the glue code attached to process models (for example, JavaDelegates). The [code conversion patterns](./migration-tooling/code-conversion.md) goes into more details how to approach this. Some of those changes might be automated using [OpenRewrite recipes](https://docs.openrewrite.org/). Depending on your architecture you might also have to **re-architect** core parts of your solution. This is especially true if you rely on transaction integration, threading, internal API (such as calling the HistoryService from within a JavaDelegate), or features that are deprecated and thus are not planned for Camunda 8 (such as CMMN). See [conceptual differences between Camunda 7 and Camunda 8](./conceptual-differences.md) for more details on this. As part of this effort you also have to **adjust your test cases**. If you used [camunda-bpm-assert](https://github.com/camunda/camunda-bpm-platform/tree/master/test-utils/assert), a natural choice is to migrate to [Camunda Process Test](https://github.com/camunda/camunda/tree/main/testing/camunda-process-test-java) (CPT). The [migration agent skill](./migration-tooling/index.md#migrate-process-tests) migrates Camunda-related process and decision tests to CPT and can check them against the Camunda 7 test results. It also migrates camunda-platform-scenario tests, and it reports BDD layers such as Cucumber or JGiven for manual migration. You can also leverage [code conversion patterns](./migration-tooling/code-conversion.md) or [OpenRewrite recipes](https://docs.openrewrite.org/). The [Code Migration Detector](https://github.com/camunda-community-hub/camunda-7-to-8-migration/tree/main/code-migration-detector) (based on ArchUnit) can check how much Camunda 7 API is used in your codebase to allow you refactor to reduce the footprint step-by-step. Ideally, you can remove any Camunda 7 dependency at the end of your refactoring. ### Improve Camunda 8 solution (optional) One optional step many customers are doing is to adjust their solution to the latest Camunda best practices after migration. This allows you to reduce technical debt or remove workarounds that either crept into the solution over time or during the migration project for going live quickly. This step does not have to block the rollout of your migrated solution, and is often done after the fact to remove time pressure and incorporate any learnings from operating the Camunda 8 solution. While this step can happen during code conversion already, it might also make sense to do it in a separate later step to reduce the number of changes during conversion to reduce the risk of breaking things. While technically it is a great thing to improve your solution, many migration projects are under pressure to run efficiently (read: minimal effort). Therefore it is advisable to at least separate two different goals: - Migrate the solution - Improve the solution and reduce further technical debt While Camunda sees a lot of value in doing both tasks at the same time - as you touch a lot of the code anyway and will probably also retest your solution thoroughly - if the budget is tight it might be better to focus on migration instead of not getting budgeted for the increased scope. ## 5Migrate data (optional) With your solution code migrated, you also need to look at your production data. Camunda provides the **[Data Migrator](./migration-tooling/data-migrator/index.md)** to be used for this. You might need to customize the data migrator, especially if you used complex data formats in Camunda 7 (for example, Java objects) that need to be converted to something Camunda 8 can handle (for example, JSON). As part of this step you might also need to extract big payloads and binaries (such as documents) into an external data store and reference it from the process (using for example upcoming document handling possibilities). Data to be migrated includes **runtime instances, audit data, and optimize data**. Let's look at it one by one. ![A diagram showing data migration](../img/data-migration.png) **Runtime instances** Currently running process instances. Running means that these process instances in Camunda 7 are not yet ended and currently wait in some [wait-state](https://docs.camunda.org/manual/latest/user-guide/process-engine/transactions-in-processes/#wait-states). This state is persisted in the database and a corresponding data entry needs to be created in Camunda 8, so that the process instance can continue from that state in the new solution. Runtime instance migration has limitations, check [migration tooling](./migration-tooling/index.md) for details. As a result you might need to adjust your process models before migration. You can use [process version migration](https://docs.camunda.org/manual/7.22/user-guide/process-engine/process-instance-migration/) in the Camunda 7 environment to migrate process instances to the version that is migratable to Camunda 8. An interesting strategy can be to define dedicated migration states you want your process instances to pile up in. Another common strategy is to use [process instance modification](https://docs.camunda.org/manual/7.22/user-guide/process-engine/process-instance-modification/) in the Camunda 7 environment to move out of states that are not migratable (for example, process instances within a multiple instance task). Migrating runtime instances is only necessary if you target a big bang migration for your process solution (keep in mind that "big bang" in this context means to switch one process solution from Camunda 7 to Camunda 8 on a defined point in time - it doesn't mean that you have to migrate all your processes at once). If you drain out your Camunda 7 processes, or if they are typically very short-lived, you do not need runtime instance migration. The Data Migrator needs to access the Camunda 7 database, but just uses Orchestration Cluster APIs, which means you can also use this tool when you run on SaaS. **Audit data (aka History)** Process instances left traces, often referred to as "history data". These are audit logs when a process instance was started, what path it took, and so on. It is important to note, that audit data can exist for ended processes from the past, but is also available for currently still running process instances, as those process instances also left traces up to the current wait state. If you need to preserve audit data and want to transfer it to Camunda 8, you can also use the [Data Migrator](./migration-tooling/index.md). Migrating audit **data comes with limitations** (most prominently that you need to run [Camunda 8 with **RDBMS**](/self-managed/concepts/secondary-storage/index.md), a feature introduced with 8.9). Migrating audit data is optional. If you need to do it, consider that audit data migration might need to look at a huge amount of data, which can take time to migrate. But you can run audit data migration beside the normal operations after a successful big bang migration over a period of time, which helps you to keep downtimes low. **Optimize data** The process intelligence tool Optimize keeps a lot of audit data to allow various analysis. This data might also want to be migrated when switching to Camunda 8. Optimize data migration **is currently not yet possible**, but this is planned on the roadmap. ## 6Roll out After you migrated the solution and prepared and tested the data migration (if necessary) you need to roll out your changes. - **Drain out**: In a drain out scenario you need to deploy your new solution next to the existing Camunda 7 solution. Then you need to add switching logic in your code, API gateway, or load balancer logic, which redirects traffic to your new solution. However, you still need to correlate callbacks to waiting process instances to the old solution, and might also present user tasks of both solutions to your users. - **Big bang**: In a big bang scenario, you will typically shutdown the old solution (or at least parts of it for the process under migration), then run the runtime instance migration to make sure all waiting process instances are transferred to the Camunda 8 solution. After this step succeeded, you can start up the new solution and route traffic to it. This approach assumes there can be a downtime of the application. If that is not an option, another alternative is to startup the Camunda 8 solution in parallel and just switch the traffic routing without downtime, and then start to migrate runtime instances afterwards. Audit data migration can run after the switch has happened, after a successful drain out (in parallel to normal operations) or of course during the downtime of a big bang. The best approach depends on the amount of data and the possibility for downtimes. ## 7Decommission Camunda 7 solution Once you have successfully big banged to Camunda 8, or all running processes are drained out, you can decommission your Camunda 7 solution. If this was the last Camunda 7 solution, you can completely decommission the Camunda Platform 7 and related practices. --- ## Migration-ready solutions To implement Camunda 7 process solutions that can be easily migrated, follow these rules and development practices. ## Overview These practices might also inform a refactoring step to prepare your existing Camunda 7 solution for migration: - Implement what we call **Clean Delegates** - concentrate on reading and writing process variables, plus business logic delegation. Data transformations will be mostly done as part of your delegate (and especially not as listeners, as mentioned below). Separate your actual business logic from the delegates and all Camunda APIs. Avoid accessing the BPMN model and invoking Camunda APIs within your delegates. - Use **primitive variable types or JSON** payloads only (no XML or serialized Java objects). - Use **simple expressions** or plug-in **FEEL**. FEEL is the only supported expression language in Camunda 8. JSONPath is also relatively easy to translate to FEEL. Avoid using special variables in expressions, for example `execution` or `task`. - Use your own user interface for task forms or Camunda Forms; the other form mechanisms are not supported out of the box in Camunda 8. - **Don’t** rely on an **ACID transaction manager** spanning multiple steps or resources. - **Don’t expose Camunda APIs** (REST or Java) to other services or frontend applications. - **Don’t call Spring beans in expressions** (for example to leverage Java code to do data transformations). - **Avoid** using any **implementation classes** from Camunda; generally, those with `\*.impl.\*` in their package name. - **Avoid** using **process engine plugins**. - **Avoid** using **Cockpit plugins**. ## Clean delegates Given that Java delegates and the workflow engine are embedded as a library, projects can do dirty hacks in their code. Casting to implementation classes? No problem. Using a ThreadLocal or trusting a specific transaction manager implementation? Yeah, possible. Calling complex Spring beans hidden behind a simple Java Unified Expression Language (JUEL) expression? Well, you guessed it — doable! Those hacks are the real showstoppers for migration, as they cannot be migrated to Camunda 8. In fact, [Camunda 8 increased isolation intentionally](https://blog.bernd-ruecker.com/moving-from-embedded-to-remote-workflow-engines-8472992cc371). Concentrate on what a Java delegate is intended to do: 1. Read variables from the process and potentially manipulate or transform that data to be used by your business logic. 2. Delegate to business logic — this is where Java delegates got their name from. In a perfect world, you would simply issue a call to your business code in another Spring bean or remote service. 3. Transform the results of that business logic into variables you write into the process. The following is an example of a good Java delegate: ```java @Component public class CreateCustomerInCrmJavaDelegate implements JavaDelegate { @Autowired private CrmFacade crmFacade; public void execute(DelegateExecution execution) throws Exception { // Data Input Mapping CustomerData customerData = (CustomerData) execution.getVariable("customerData"); // Delegate to business logic String customerId = crmFacade.createCustomer(customerData); // Data Output Mapping execution.setVariable("customerId", customerId); } } ``` Never cast to Camunda implementation classes, use any ThreadLocal object, or influence the transaction manager in any way. Java delegates should always be stateless and not store any data in their fields. You can migrate such delegates according to our [code conversion patterns](./migration-tooling/code-conversion.md#code-conversion-patterns), for example using [OpenRewrite recipes](./migration-tooling/code-conversion.md#refactoring-recipes-using-openrewrite). ## No transaction managers You should not trust ACID [transaction managers](https://blog.bernd-ruecker.com/achieving-consistency-without-transaction-managers-7cb480bd08c) to glue together the workflow engine with your business code. - Instead, embrace eventual consistency and make every service task its own transactional step. If you are familiar with Camunda 7 lingo, this means that all BPMN elements will be `async=true`. - A process solution that relies on five service tasks to be executed within one ACID transaction, probably rolling back in case of an error, will make migration challenging. ## Don’t expose Camunda API You should apply the [information hiding principle](https://en.wikipedia.org/wiki/Information_hiding) and not expose too much of the Camunda API to other parts of your application. In the following example, you should not hand over an execution context to your `CrmFacade`: ```java // DO NOT DO THIS! crmFacade.createCustomer(execution); ``` The same holds true when a new order is placed, and your order fulfillment process should be started. Instead of the frontend calling the Camunda API to start a process instance, provide your own endpoint to translate between the inbound REST call and Camunda. For example: ```java @RestController public class OrderFulfillmentRestController { @Autowired private ProcessEngine camunda; @RequestMapping(path = "/order", method = POST) public ResponseEntity placeOrder(@RequestBody OrderDto orderPayload) throws Exception { // TODO: Somehow extract data from orderPayload OrderData orderData = OrderData.from(orderPayload); ProcessInstance pi = camunda.getRuntimeService() .startProcessInstanceByKey("orderFulfillment", Variables.putValue("order", orderData)); response.setStatus(HttpServletResponse.SC_ACCEPTED); return ResponseEntity.accepted().body(StatusDto.of("pending")); } } ``` ## Use primitive variable types or JSON Camunda 7 provides flexible ways to add data to your process. For example, you could add Java objects that would be serialized as byte code. Java byte code is brittle and also tied to the Java runtime environment. Another possibility is transforming those objects on the fly to JSON or XML using Camunda Spin. It turned out this was black magic and led to regular problems, which is why Camunda 8 does not offer this any more. Instead, you should perform any transformation within your code before communicating with the Camunda API. Camunda 8 only takes JSON as a payload, which automatically includes primitive values. In the following Java delegate example, you can see Spin and Jackson were used in the delegate for JSON to Java mapping: ```java @Component public class CreateCustomerInCrmJavaDelegate implements JavaDelegate { @Autowired private ObjectMapper objectMapper; //... public void execute(DelegateExecution execution) throws Exception { // Data Input Mapping JsonNode customerDataJson = ((JacksonJsonNode) execution.getVariable("customerData")).unwrap(); CustomerData customerData = objectMapper.treeToValue(customerDataJson, CustomerData.class); // ... } } ``` This way, you have full control over what is happening, and such code is also easily migratable. The overall complexity is even lower, as Jackson is quite well known to Java people — a kind of de-facto standard with a lot of best practices and recipes available. ## Simple expressions and FEEL [Camunda 8 uses FEEL as its expression language](/components/modeler/feel/what-is-feel.md). There are big advantages to this decision. Not only are the expression languages between BPMN and DMN harmonized, but also the language is really powerful for typical expressions. One of my favorite examples is the following onboarding demo we regularly show. A decision table will hand back a list of possible risks, whereas every risk has a severity indicator (yellow, red) and a description. ![An onboarding demo DMN diagram](https://camunda.com/wp-content/uploads/2022/05/Migrating-to-Camunda-Platform-8-image-1-1024x367.png) The result of this decision will be used in the process to make a routing decision: ![A BPMN diagram showing the routing decision](https://camunda.com/wp-content/uploads/2022/05/Migrate-to-Camunda-Platform-8-25052022-image-2-1024x481.png) To unwrap the DMN result in Camunda 7, you could write some Java code and attach that to a listener when leaving the DMN task (this is already an anti-pattern for migration as you will read next). The code is not very readable: ```java @Component public class MapDmnResult implements ExecutionListener { @Override public void notify(DelegateExecution execution) throws Exception { List risks = new ArrayList(); Set riskLevels = new HashSet(); Object oDMNresult = execution.getVariable("riskDMNresult"); for (Object oResult : (List) oDMNresult) { Map result = (Map) oResult; risks.add(result.containsKey("risk") ? (String) result.get("risk") : ""); if (result.get("riskLevel") != null) { riskLevels.add(((String) result.get("riskLevel")).toLowerCase()); } } String accumulatedRiskLevel = "green"; if (riskLevels.contains("rot") || riskLevels.contains("red")) { accumulatedRiskLevel = "red"; } else if (riskLevels.contains("gelb") || riskLevels.contains("yellow")) { accumulatedRiskLevel = "yellow"; } execution.setVariable("risks", Variables.objectValue(risks).serializationDataFormat(SerializationDataFormats.JSON).create()); execution.setVariable("riskLevel", accumulatedRiskLevel); } } ``` With FEEL, you can evaluate that data structure directly and have an expression on the "red" path: ``` = some risk in riskLevels satisfies risk = "red" ``` Additionally, you can even hook in FEEL as the scripting language in Camunda 7 (as explained in [Scripting with DMN inside BPMN](https://camunda.com/blog/2018/07/dmn-scripting/) or [User Task Assignment based on a DMN Decision Table](https://camunda.com/blog/2020/05/camunda-bpm-user-task-assignment-based-on-a-dmn-decision-table/)). However, more commonly you will keep using JUEL in Camunda 7. If you write simple expressions, they can be migrated automatically, as you can see in [the test case](https://github.com/camunda-community-hub/camunda-7-to-8-migration-analyzer/blob/main/core/src/test/java/org/camunda/community/migration/converter/ExpressionTransformerTest.java) of the migration community extension. You should avoid more complex expressions if possible. Very often, a good workaround to achieve this is to adjust the output mapping of your Java delegate to prepare data in a form that allows for easy expressions. Avoid hooking in Java code during an expression evaluation. The above listener to process the DMN result was one example of this, but a more diabolic example could be the following expression in Camunda 7: ```java // DON'T DO THIS: #{ dmnResultChecker.check( riskDMNresult ) } ``` Now, the `dmnResultChecker` is a Spring bean that can contain arbitrary Java logic, possibly even querying some remote service to query whether we currently accept yellow risks or not. Such code cannot be executed within Camunda 8 FEEL expressions, and the logic needs to be moved elsewhere. ## Camunda Forms Finally, while Camunda 7 supports [different types of task forms](https://docs.camunda.org/manual/latest/user-guide/task-forms/), Camunda 8 only supports [Camunda Forms](/components/modeler/desktop-modeler/utilize-forms.md) (and will actually be extended over time). If you rely on other form types, you either need to make Camunda Forms out of them or use a bespoke tasklist where you still support those forms. --- ## Code Conversion As Camunda 8 is a complete rewrite of Camunda 7, you must convert your models (BPMN and DMN), Camunda 7 forms (`.form`), and some of your code to work with the Orchestration Cluster REST API. :::tip Agentic migration The [Camunda migration agent skill](./index.md#agentic-migration) uses the Diagram Converter CLI as the default for BPMN, DMN, and Camunda 7 form conversion. After it inventories Java code, select either the AI-first, pattern-guided path or the optional recipe-assisted path. Review every conversion and rearchitect your solution where needed. ::: ## Overview You must especially rewrite code that does the following: - Uses the Client API: Starting process instances, correlating messages, managing tasks, etc. - Implements service tasks, including: - [External tasks](https://docs.camunda.org/manual/latest/user-guide/process-engine/external-tasks/#the-external-task-pattern) where workers subscribe to the engine - [Java code attached to service tasks](https://docs.camunda.org/manual/latest/user-guide/process-engine/delegation-code/) called directly by the engine (in-VM) ### Tools and resources This guide covers tools and approaches to help with code conversion: 1. [AI-assisted migration](#leverage-ai-for-migration): Use the Camunda migration agent skill for a guided Java migration 2. [API Mapping Guide](#api-mapping-guide): Understand how Camunda 7 REST API endpoints map to Camunda 8 3. [Java API mapping reference](#key-api-mapping-reference): Look up Camunda 8 equivalents for Java types, test assertions, and imports 4. [Code Conversion Patterns](#code-conversion-patterns): Apply documented patterns in manual or AI-assisted migration 5. [OpenRewrite Recipes](#refactoring-recipes-using-openrewrite): Optionally create a deterministic first diff for repeated, supported, primarily syntactic transformations Additionally, you will find information about: - [Diagram Converter](#diagram-converter) for BPMN, DMN, and Camunda 7 form conversion - [Complete migration example](#example-adjust-a-spring-boot-application) showing all tools in action ### Choose your migration approach Choose a Java migration path after you inventory your codebase. Before you use a path across a broad migration, run both paths on representative Java code and compare the results. Review is mandatory for both paths. | Approach | Use when | What to expect | | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **AI-first, pattern-guided** (preferred starting point) | You have a capable coding model and can review its output. | The agent reads source code and migration patterns directly. Model quality materially affects the output, so review source-to-output mappings and behavior. | | **Recipe-assisted** (optional) | You have repeated, supported, primarily syntactic transformations, or need a deterministic first diff. | Run the [8.10.0 OpenRewrite recipes](#refactoring-recipes-using-openrewrite), then use AI or manual work to finish the migration. Expect scaffolding, generated names, TODOs, and cleanup. | Neither path guarantees lower token use, cost, or migration time. ## Leverage AI for migration Use the [Camunda migration agent skill](./index.md#agentic-migration) to migrate Java code and [process tests](./index.md#migrate-process-tests). The skill inventories your source code, then lets you select an AI-first, pattern-guided path or an optional recipe-assisted path. [Set up and run the skill](./index.md#set-up-and-run) from your Camunda 7 project directory. Use a capable coding model and review every conversion. Before migrating a broad codebase, compare both Java paths with representative code. ## API mapping guide The Camunda 7 and Camunda 8 Orchestration Cluster APIs share many similarities, but several aspects have been modernized in Camunda 8. ### Key structural changes Streamlined search endpoints: - **Camunda 7**: Separate endpoints like `GET /resource` and `GET /resource/count` - **Camunda 8**: Single `POST /search` endpoint with filtering capabilities Tenant handling: - **Camunda 7**: `tenantId` passed as path parameter with multiple endpoint variants - **Camunda 8**: `tenantId` passed in request body, simplifying the API surface History data: - **Camunda 7**: Separate endpoints for historic data (for example, HistoryService) - **Camunda 8**: No separate historic endpoints; history is managed through Operate ### Using the interactive mapping tool To help you understand the differences between the two APIs, we provide an interactive web application that maps the complete Camunda 7 REST API to its Camunda 8 counterparts. The tool shows: - Direct mappings: Camunda 7 endpoints that map one-to-one to Camunda 8 - Conceptual mappings: Functionality that exists in Camunda 8 but works differently - Roadmap items: Features planned for future Camunda 8 releases - Discontinued features: Camunda 7 endpoints that are no longer available and why [Open the API Mapping Guide](https://camunda.github.io/camunda-7-to-8-migration-tooling/). :::tip When to use this tool Use the API mapping guide to: - Quickly find Camunda 8 equivalents for Camunda 7 API calls - Understand why certain parameters or endpoints changed - Check if a planned feature is on the roadmap - Plan your migration strategy based on API availability ::: ## Java API mapping reference {#key-api-mapping-reference} These tables summarize common Java API, test assertion, and import mappings between Camunda 7 and Camunda 8. Use them when you migrate code manually or review output from the agent skill or OpenRewrite recipes. ### Type mappings | Camunda 7 | Camunda 8 | | ---------------------------------------------- | ------------------------------------------------- | | `ProcessEngine` | `CamundaClient` | | `RuntimeService` | `CamundaClient` (methods directly on client) | | `TaskService` | `CamundaClient` (user task methods) | | `RepositoryService` | `CamundaClient` (deployment/definition methods) | | `ProcessInstance` | `ProcessInstanceEvent` | | `Task` | `UserTask` | | `Deployment` | `DeploymentEvent` | | `Batch` | No direct equivalent (single instance operations) | | `VariableMap` | `Map` | | `TypedValue` (IntegerValue, StringValue, etc.) | Plain Java types | | `DelegateExecution` | `ActivatedJob` | | `ExternalTask` + `ExternalTaskService` | `JobClient` + `ActivatedJob` | | `BpmnError` | `CamundaError.bpmnError(...)` | | `ProcessEngineException` | `CamundaError.jobError(...)` | ### Parameter name changes :::warning The terms `processDefinitionKey` and `processDefinitionId` have **swapped meanings** between Camunda 7 and Camunda 8. Review these carefully during migration. ::: | Description | Camunda 7 | Camunda 8 | | -------------------------------- | -------------------------- | --------------------------------------- | | BPMN model identifier (from XML) | `processDefinitionKey` | `bpmnProcessId` / `processDefinitionId` | | Unique key from deployment | `processDefinitionId` | `processDefinitionKey` | | Process instance identifier | `String processInstanceId` | `Long processInstanceKey` | ### Test assertion mappings Camunda 7 assertions check the state once. Most [Camunda Process Test](/apis-tools/testing/assertions.md) (CPT) assertions wait until the expected state is reached or the [assertion timeout](/apis-tools/testing/assertions.md#assertion-timeout) ends (10 seconds by default). Before a negative assertion such as `hasNoActiveElements`, first assert a positive state, such as an active or completed element, so the negative check doesn't pass before the process gets there. For all public Camunda 7 assertions, see the [complete assertion mapping](https://github.com/camunda/camunda-7-to-8-migration-tooling/blob/main/code-conversion/patterns/40-test-assertions/10-assertions/80-assertion-mapping.md) in the pattern catalog. | Camunda 7 (BpmnAwareTests) | Camunda 8 (CamundaAssert) | | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `assertThat(pi).isNotEnded()` | `assertThat(pi).isActive()` | | `assertThat(pi).isEnded()` | `assertThat(pi).isCompleted()`, or `isTerminated()` for a canceled instance | | `assertThat(pi).isWaitingAt("id")` | `assertThat(pi).hasActiveElements("id")` | | `assertThat(pi).isWaitingAt(findId("name"))` | `assertThat(pi).hasActiveElements(byName("name"))` | | `assertThat(pi).isNotWaitingAt("id")` | `assertThat(pi).hasNoActiveElements("id")` | | `assertThat(pi).hasPassed("id")` | `assertThat(pi).hasCompletedElements("id")`, or `hasTerminatedElements("id")` for an element that a boundary event interrupted | | `assertThat(pi).hasNotPassed("id")` | No exact counterpart. Use `assertThat(pi).hasNotActivatedElements("id")` only if the element must never be activated | | `assertThat(pi).variables().containsEntry(k,v)` | `assertThat(pi).hasVariable(k, v)` | | `assertThat(pi).variables().containsKey(k)` | `assertThat(pi).hasVariableNames(k)` | | `assertThat(task()).hasName("x")` | `assertThatUserTask(UserTaskSelectors.byElementId("id", pi.getProcessInstanceKey())).isCreated().hasName("x")` | | `assertThat(task()).isAssignedTo("u")` | No direct counterpart. Search created user tasks by `pi.getProcessInstanceKey()`, assert exactly one result, then assert its assignee | | `complete(task())` | `processTestContext.completeUserTask(UserTaskSelectors.byElementId("id", pi.getProcessInstanceKey()))` | | `managementService().executeJob(id)` | `processTestContext.increaseTime(Duration)` or `processTestContext.completeJob("type")` | ### Import replacements | Remove | Add | | ------------------------------------------------------------------------- | ---------------------------------------------------------------- | | `org.camunda.bpm.engine.*` | `io.camunda.client.*` | | `org.camunda.bpm.engine.delegate.*` | `io.camunda.client.api.worker.JobHandler` | | `org.camunda.bpm.engine.variable.*` | (plain Java collections) | | `org.camunda.bpm.engine.test.assertions.bpmn.BpmnAwareTests.*` | `io.camunda.process.test.api.CamundaAssert.*` | | `org.camunda.bpm.engine.test.assertions.ProcessEngineTests.*` | `io.camunda.process.test.api.CamundaAssert.*` | | N/A | `io.camunda.process.test.api.assertions.ElementSelectors.byName` | | N/A | `io.camunda.process.test.api.assertions.UserTaskSelectors` | | N/A | `io.camunda.process.test.api.CamundaProcessTestContext` | | N/A | `io.camunda.process.test.api.CamundaSpringProcessTest` | | `org.camunda.bpm.spring.boot.starter.annotation.EnableProcessApplication` | `io.camunda.client.annotation.Deployment` | ## Code conversion patterns Due to the flexibility of Camunda 7, there are many ways to write code and therefore many possible conversion patterns. We maintain a collaborative catalog of these patterns to serve as technical reference material for manual migration and recipe development. ### What are code conversion patterns? Code conversion patterns are detailed, technical examples showing how specific Camunda 7 code constructs translate to Camunda 8. Each pattern includes: - Side-by-side code comparisons between Camunda 7 and Camunda 8 - Explanations of conceptual differences - Parameter mappings and method equivalents - Notes on edge cases and limitations ### When to use the patterns Use the code conversion patterns when: - Starting an AI-first migration: Provide the relevant patterns and source context to the coding model - Reviewing a recipe-assisted migration: Check that the generated diff implements the relevant patterns - Manual migration is needed: The recipes cannot handle your specific code structure - Understanding changes: You want to understand what the recipes are doing under the hood - Extending recipes: You're developing custom recipes for your organization's specific patterns - Complex scenarios: Your code uses advanced features that require careful manual conversion ### Pattern categories The catalog covers the following types of code: General patterns: - Maven dependencies and configuration - Handling process variables Client code (code that calls the Camunda API): - Starting process instances - Correlating messages - Handling user tasks - Managing process variables - Searching process definitions - Broadcasting signals - Canceling process instances - Raising incidents - Handling resources - Class-level changes Glue code (code executed by the process engine): - Converting JavaDelegates to Job Workers - Converting ExecutionListeners to Job Workers - Converting External Task Workers to Job Workers - Converting expressions Test code: - Complete test cases - Process instance assertions - Process variable assertions - User task assertions - Message correlation - Job execution - Test setup (JUnit harness, test deployments, and Spring Boot tests) - Delegate, worker, call activity, and decision mocks - Decision tests - Coverage and scenario tests ### Accessing the patterns The complete pattern catalog with code examples is maintained on GitHub. Browse the complete pattern catalog with code examples in the [Migration Tooling repository](https://github.com/camunda/camunda-7-to-8-migration-tooling/tree/main/code-conversion/patterns). :::tip The pattern catalog is actively maintained by Camunda consultants, partners, and community members. You can contribute your own patterns or request additions via GitHub issues and pull requests. ::: ### Using patterns with OpenRewrite The patterns inform the OpenRewrite recipe development. If you find a pattern that's not yet covered by the recipes, you can: 1. Use the pattern for manual migration 2. Reference the pattern when [extending the recipes](https://github.com/camunda/camunda-7-to-8-migration-tooling/blob/8.10.0/code-conversion/recipes/developer_guide.md) 3. Contribute a new recipe based on the pattern ## Refactoring recipes (using OpenRewrite) [OpenRewrite](https://docs.openrewrite.org/) is an open-source framework that can automate refactorings by so-called recipes. It is provided with an Apache License, making it easy to adopt in any context. Use OpenRewrite as an optional recipe-assisted path for repeated, supported, primarily syntactic Java transformations. It creates a deterministic first diff, but it does not complete a migration. For semantic, cross-cutting, or mixed delegate/client transformations, recipes can be neutral or add rework by constraining downstream AI or manual work. The Camunda 7 to 8 OpenRewrite recipes help you automatically refactor: - Client code using the Camunda 7 Java API - Java delegates and execution listeners (glue code) - External task workers - Test assertions from camunda-bpm-assert The recipes don't migrate test harnesses, test deployments, or timers in tests. To migrate complete tests to Camunda Process Test, use the [Camunda migration agent skill](./index.md#migrate-process-tests). :::note The recipes are still under development. Expect recipes to work out-of-the-box only in simple scenarios. For complex codebases, you may need to extend or customize them to suit your needs. ::: ### How the recipes work When you select the recipe-assisted path, the code transformation is performed in three phases: 1. **Prepare**: Prepares the Camunda 7 code with minimal changes (e.g., converting TypedValue API to Java Object API, adding Maven dependencies). 2. **Migrate**: Replaces Camunda 7 methods with Camunda 8 equivalents. Comments are added where parameters were modified or removed. 3. **Cleanup**: Removes unnecessary dependencies and imports. ### Available recipes The recipes are organized by code type and transformation phase: | Type of change | Client code | Java delegate | External worker | | -------------- | ----------------------- | ------------------------- | ------------------------------- | | **Prepare** | AllClientPrepareRecipes | AllDelegatePrepareRecipes | AllExternalWorkerPrepareRecipes | | **Migrate** | AllClientMigrateRecipes | AllDelegateMigrateRecipes | AllExternalWorkerMigrateRecipes | | **Cleanup** | AllClientCleanupRecipes | AllDelegateCleanupRecipes | AllExternalWorkerCleanupRecipes | | **Combined** | AllClientRecipes | AllDelegateRecipes | AllExternalWorkerRecipes | You can apply recipes individually by phase, or use the _combined_ recipes to run all three phases at once. ### Using the recipes #### Prerequisites - Maven-based Java project (Gradle is also supported via [OpenRewrite's documentation](https://docs.openrewrite.org/running-recipes/getting-started)) - Project under version control (to easily review refactorings) #### Step 1: Add the OpenRewrite Maven plugin Add the following to your `pom.xml`: ```xml org.openrewrite.maven rewrite-maven-plugin 6.46.1 io.camunda.migration.code.recipes.AllClientRecipes io.camunda.migration.code.recipes.AllDelegateRecipes io.camunda.migration.code.recipes.AllExternalWorkerRecipes false io.camunda camunda-7-to-8-code-conversion-recipes 8.10.0 ``` :::warning Important Always back up your code or use version control before running recipes. This ensures you can review and rollback changes if needed. ::: :::note The use of `camunda-7-to-8-code-conversion-recipes` artifact requires access to the Camunda Enterprise Maven repository. See the [Camunda 7 documentation](https://docs.camunda.org/get-started/apache-maven/#camunda-artifact-storage) for instructions on setting up the repository in your Maven configuration. ::: Choose the recipes that match your codebase: - Include `AllClientRecipes` if you have code that calls the Camunda API (starting processes, correlating messages, etc.) - Include `AllDelegateRecipes` if you have Java delegates or execution listeners - Include `AllExternalWorkerRecipes` if you have external task workers #### Step 2: Run the recipes Execute the following command: ```shell mvn rewrite:run ``` #### Step 3: Review the changes Carefully examine all changes using your version control system's diff tool. Recipes can add scaffolding, generated names, and TODO comments that need AI or manual cleanup. The recipes add comments where manual review is needed: - Parameters that were removed or have different semantics in Camunda 8 - Methods with no direct one-to-one replacement (for example, executionId-based operations) - Dummy literal strings that need to be replaced with actual values :::warning Important A successful recipe run, including a successful compile, does not demonstrate a complete migration. Review source-to-output mappings and behavior. Some concepts from Camunda 7 (like executionId) don't exist in Camunda 8, and recipes cannot automatically determine the correct replacement in all cases. ::: ### Recipe completeness and limitations The recipes cover: - Class structure and annotations - Dependencies and imports - Basic types and commonly used methods However, they are incomplete in two aspects: - Some Camunda 7 methods could be transformed but are not yet included - Some Camunda 7 methods have no equivalent in Camunda 8 Recipes do not resolve migration design decisions such as business behavior, eventual consistency, transaction boundaries, or architectural separation. If Camunda 7 code remains after applying recipes: 1. Refer to the [code conversion patterns](#code-conversion-patterns) for manual migration guidance 2. Extend the recipes for your specific use case (see the [developer guide](https://github.com/camunda/camunda-7-to-8-migration-tooling/blob/8.10.0/code-conversion/recipes/developer_guide.md)) 3. Remove or refactor the code if the functionality is no longer available ### Additional resources - [Recipe source code and developer guide](https://github.com/camunda/camunda-7-to-8-migration-tooling/tree/8.10.0/code-conversion/recipes) - [OpenRewrite documentation](https://docs.openrewrite.org/) - [Complete migration example](https://github.com/camunda-community-hub/camunda-7-to-8-migration-example) ## Diagram Converter Your BPMN and DMN models need to be adjusted to work with Camunda 8. Use the [Diagram Converter](./diagram-converter.md) as the default for BPMN, DMN, and static Camunda 7 `.form` conversion. For generated task forms, use the [Camunda migration agent skill](./index.md#agentic-migration), which creates or adapts standard Camunda 8 forms after review. AI-generated model conversion depends materially on model capability and can silently change model semantics, so do not use AI-only model conversion as the default. Review converted models and findings before deployment. :::tip The [Camunda migration agent skill](./index.md#agentic-migration) runs the Diagram Converter CLI as the default and can use AI to help investigate findings. ::: For full documentation, see the [Diagram Converter guide](./diagram-converter.md). ## Example: Adjust a Spring Boot application See the [end-to-end migration example](https://github.com/camunda-community-hub/camunda-7-to-8-migration-example) on GitHub. --- ## Cockpit plugin :::warning Experimental feature - The Cockpit plugin is an **experimental** feature and we don't recommend using it in production environments. - Read more about this plugin's [limitations](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/limitations.md#cockpit-plugin). ::: The Cockpit plugin provides a web-based interface for viewing information about skipped and migrated runtime and history data. It integrates with Camunda 7 Cockpit to give you visibility into which runtime process instances or history data (like variables, flow nodes, etc.) were successfully migrated or skipped during migration and the reasons why. For more information on Camunda 7 plugins, see the [Camunda 7 documentation](https://docs.camunda.org/manual/latest/webapps/cockpit/extend/plugins/). ## Prerequisites - **Database Configuration**: The plugin can only access databases that Camunda 7 is connected to, so the migration schema needs to be created in the Camunda 7 database (default behavior), or both Camunda 7 and Camunda 8 databases must point to the same database instance. For an example, see the [Data Migrator setup example](config-examples.md#data-migrator). - Camunda 7 Webapps are deployed, running, and accessible. - Migration schema is available in Camunda 7 database. - The Cockpit plugin requires running the migrator with `save-skip-reason` enabled. - The plugin doesn't show skip reasons without this setting because they are not stored. - To use the Cockpit plugin, run the migrator with the following setting: ```yaml camunda.migrator: save-skip-reason: true ``` :::warning Heads-Up - Saving the skip reason could result in a large amount of data being stored additionally in your database, depending on the order of magnitude of the data to be migrated or potentially skipped. - We recommend testing your migration in a QA environment before running the Data Migrator against your production database. ::: ## Installation 1. **Download the latest release** from the [releases page](https://github.com/camunda/camunda-7-to-8-migration-tooling/releases). 2. **Deploy the plugin** to your Camunda 7 installation by copying the generated JAR file into the Camunda 7 plugins directory. For example the paths are: - For Tomcat: `./camunda-bpm-ee-tomcat--ee/server/apache-tomcat-/webapps/camunda/WEB-INF/lib/`. - For Run: `./camunda-bpm-run-ee--ee/configuration/userlib/`. 3. **Inspect skipped and migrated data in Cockpit** once the plugin has been deployed. ## Configuration ### Operate URL When viewing migrated process instances, the plugin shows the Camunda 8 process instance key. You can optionally configure the Camunda 8 Operate URL so that these keys link directly to the corresponding process instance in Operate. To configure this, create or edit the Cockpit configuration file (`config.js`) and add the `operateUrl` property: ```javascript export default { operateUrl: "https:///operate", }; ``` Deploy this file to your Camunda 7 installation at `/app/cockpit/scripts/config.js`. If `operateUrl` is not configured, the Camunda 8 key is displayed as plain text without a link. ## Using the Cockpit plugin After installation and configuration, the Cockpit plugin provides: - **Skipped entity overview**: View all entities that were skipped during migration. - **Detailed skip reasons**: Understand why specific entities were not migrated. - **Migration status tracking**: See data that has been migrated successfully. ## Screenshots The following screenshots demonstrate the Cockpit plugin interface and functionality: ### Migrated process instances view Shows a table of successfully migrated process instances from Camunda 7 to Camunda 8, including the process instance ID, process definition key, and the corresponding Camunda 8 key. ![Runtime Migrated Instances](img/runtime-migrated.png) ### Skipped process instances overview Displays process instances that were skipped during migration, allowing users to identify which instances failed and need further attention. ![Runtime Skipped Instances](img/runtime-skipped.png) ### Entity type selection Allows filtering historic entities by type (process instances, variables, tasks, etc.) to simplify analysis of migration issues. ![Skipped Entity Type Selection](img/skipped-select-type.png) ### Variable-specific skip analysis When viewing historic variable data, the type and value of primitives are retrieved to provide additional context. ![Skipped Variables](img/skipped-variables.png) --- ## Configuration examples Configuration examples for the Data Migrator's `configuration/application.yml`. ## Camunda 8 Client ```yaml camunda.client: mode: self-managed # Operation mode: 'self-managed' or 'cloud' grpc-address: http://localhost:26500 # The gRPC API endpoint rest-address: http://localhost:8080 # The REST API endpoint ``` ## Data Migrator ```yaml camunda.migrator: page-size: 500 # Number of records to process in each page job-type: migrator # Job type for actual job activation (used for validation and activation unless validation-job-type is defined) # validation-job-type: '=if legacyId != null then "migrator" else "noop"' # Optional: job type for validation (falls back to job-type if not defined) auto-ddl: true # Automatically create/update database schema table-prefix: MY_PREFIX_ # Optional table prefix for migrator schema interceptors: - class-name: com.example.MyCustomInterceptor # Custom interceptor class - class-name: com.example.AnotherInterceptor # Another custom interceptor class history: partition-count: 3 # Optional: Number of partitions when skipping Camunda 8 REST/topology connectivity (Camunda 8 database access is still required) auto-cancel: cleanup: enabled: true # Populate cleanup dates for auto-canceled entities ttl: P6M # Time-to-live for auto-canceled history (6 months) ``` ## Datasource Configure the Camunda 7 and Camunda 8 datasources. You can use the same or different databases for Camunda 7 and Camunda 8. ### Camunda 7 (runtime and history) ```yaml camunda.migrator.c7.data-source: table-prefix: MY_PREFIX_ # Optional prefix for Camunda 7 database tables auto-ddl: true # Automatically create/update Camunda 7 database schema jdbc-url: jdbc:h2:./h2/data-migrator-source.db username: sa # Database username password: sa # Database password driver-class-name: org.h2.Driver ``` You can apply any HikariCP property (for example, pool size) under `camunda.migrator.c7.data-source`. ### Camunda 8 RDBMS (history) ```yaml camunda.migrator.c8.data-source: table-prefix: MY_PREFIX_ # Optional prefix for Camunda 8 RDBMS database tables auto-ddl: true # Automatically create/update Camunda 8 RDBMS database schema jdbc-url: jdbc:h2:./h2/data-migrator-target.db username: sa # Database username password: sa # Database password driver-class-name: org.h2.Driver ``` You can apply any HikariCP property (for example, pool size) under `camunda.migrator.c8.data-source`. ## Logging ```yaml logging: level: root: INFO # Root logger level io.camunda.migration.data: INFO # Migrator logging io.camunda.migration.data.RuntimeMigrator: DEBUG # Runtime migration logging io.camunda.migration.data.persistence.IdKeyMapper: DEBUG # ID mapping logging file: name: logs/camunda-7-to-8-data-migrator.log ``` --- ## Configuration property reference Reference for all Data Migrator configuration properties, set in the `configuration/application.yml` file. ## `camunda.client` Prefix: `camunda.client` :::info Read more about Camunda Client [configuration options](/apis-tools/camunda-spring-boot-starter/configuration.md). ::: | Property | Type | Description | | :-------------- | :------- | :------------------------------------------------------------------------------------------------- | | `.mode` | `string` | Operation mode of the Camunda 8 client. Options: `self-managed` or `saas`. Default: `self-managed` | | `.grpc-address` | `string` | The gRPC API endpoint for Camunda 8 Platform. Default: `http://localhost:26500` | | `.rest-address` | `string` | The REST API endpoint for Camunda 8 Platform. Default: `http://localhost:8080` | ## `camunda.migrator` Prefix: `camunda.migrator` | Property | Type | Description | | :----------------------------- | :-------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `.page-size` | `number` | Number of records processed per page. Default: `100`. | | `.job-type` | `string` | Job type used for job activation. Default: `migrator`. | | `.validation-job-type` | `string` | Job type for validation purposes. Optional: falls back to `job-type` if not defined. Set to `DISABLED` to disable job type execution listener validation entirely. | | `.auto-ddl` | `boolean` | Automatically create/update migrator database schema. Default: `false`. | | `.table-prefix` | `string` | Optional prefix for migrator database tables. Default: _(empty)_. | | `.tenant-ids` | `string` | Comma-separated list of tenant ids for which process instances should be migrated during runtime migration. For more information, read [tenant in runtime](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/runtime.md#tenants). Default: _(empty)_ (migrate only process instances without assigned tenant id). | | `.database-vendor` | `string` | Database vendor for migrator schema. Options: `h2`, `postgresql`, `oracle`. Default: Automatically detected. | | `.interceptors` | `array` | List of variable interceptors (built-in and custom) to configure during migration. | | `.save-skip-reason` | `boolean` | Whether to persist skip reasons for entities that could not be migrated. Required when using the Cockpit plugin. Default: `false`. | | `.history.partition-count` | `number` | Number of partitions to use for distributing history data. When set, this value takes precedence over querying the topology from the Camunda REST API, so the migrator does not require REST connectivity to a running Camunda 8 cluster (but still requires access to the Camunda 8 database). If not set, the partition count is automatically fetched from the Camunda REST API. | | `.history.legacy-id-prefix` | `string` | Optional prefix added to the start of migrated Camunda 7 history definition IDs (process, decision, and form definitions) to avoid collisions with native Camunda 8 definition IDs. The value must start with a letter or underscore and contain only letters, digits, `.`, `-`, or `_` (maximum 100 characters). A blank value is rejected. Invalid values stop the migrator on startup, and the effective prefix is logged. Configure a different prefix only when you are sure your Camunda 8 definition IDs cannot clash with migrated IDs. Default: `c7-legacy-`. | | `.history.auto-cancel.cleanup` | `object` | Configuration for history auto-cancel cleanup behavior. See [camunda.migrator.history.auto-cancel.cleanup](#camundamigratorhistoryauto-cancelcleanup) for details. | | `.identity.skip-users` | `boolean` | Skip the migration of users (enable when IdP is configured for users). Default: `false`. | | `.identity.skip-groups` | `boolean` | Skip the migration of groups (enable when IdP is configured for groups). Default: `false`. | | `.identity.sync.timeout` | `number` | Timeout for identity sync operations in millis. Default: `3000`. | | `.identity.sync.poll-interval` | `number` | Polling interval for identity sync operations in millis. Default: `250`. | ## `camunda.migrator.history.auto-cancel.cleanup` Prefix: `camunda.migrator.history.auto-cancel.cleanup` Configuration for history cleanup of auto-canceled instances. | Property | Type | Description | | :--------- | :-------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `.enabled` | `boolean` | Whether to populate cleanup dates for auto-canceled entities. When `false`, history cleanup dates are set to `null` for all auto-canceled instances. Default: `true`. | | `.ttl` | `period` | Time-to-live for auto-canceled history records. The cleanup date is calculated as `end_date + ttl`. Accepts ISO-8601 duration format (e.g., `P6M` for 6 months, `P1Y` for 1 year, `P90D` for 90 days). Default: `P6M` (6 months). | ## `camunda.migrator.interceptors.[n]` Prefix: `camunda.migrator.interceptors.[n]` There are two types of interceptors: - **VariableInterceptors** for [runtime migration](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/variables.md#transformation) - **EntityInterceptors** for [history migration](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/history.md#entity-transformation) The configuration is the same for both types. | Property | Type | Description | | :----------- | :-------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | | `class-name` | `string` | **Required.** Fully qualified class name of the interceptor (built-in or custom). | | `enabled` | `boolean` | Whether the interceptor is enabled. Default: `true` for all interceptors. | | `properties` | `map` | Custom properties (key:value pairs) to configure the interceptor. Properties call setter methods on the interceptor class and pass the value. | ### Built-in interceptors The following built-in interceptors are available and can be disabled: **Validators (reject unsupported types):** - `io.camunda.migration.data.impl.interceptor.ByteArrayVariableValidator` - `io.camunda.migration.data.impl.interceptor.FileVariableValidator` - `io.camunda.migration.data.impl.interceptor.ObjectJavaVariableValidator` **Transformers (convert supported types):** - `io.camunda.migration.data.impl.interceptor.PrimitiveVariableTransformer` - `io.camunda.migration.data.impl.interceptor.StringVariableTransformer` - `io.camunda.migration.data.impl.interceptor.NullVariableTransformer` - `io.camunda.migration.data.impl.interceptor.DateVariableTransformer` - `io.camunda.migration.data.impl.interceptor.ObjectJsonVariableTransformer` - `io.camunda.migration.data.impl.interceptor.ObjectXmlVariableTransformer` - `io.camunda.migration.data.impl.interceptor.SpinJsonVariableTransformer` - `io.camunda.migration.data.impl.interceptor.SpinXmlVariableTransformer` **Entity transformers:** - `io.camunda.migration.data.impl.interceptor.history.entity.ProcessInstanceTransformer` - `io.camunda.migration.data.impl.interceptor.history.entity.ProcessDefinitionTransformer` - `io.camunda.migration.data.impl.interceptor.history.entity.FlowNodeTransformer` - `io.camunda.migration.data.impl.interceptor.history.entity.UserTaskTransformer` - `io.camunda.migration.data.impl.interceptor.history.entity.IncidentTransformer` - `io.camunda.migration.data.impl.interceptor.history.entity.VariableTransformer` - `io.camunda.migration.data.impl.interceptor.history.entity.DecisionInstanceTransformer` - `io.camunda.migration.data.impl.interceptor.history.entity.DecisionDefinitionTransformer` - `io.camunda.migration.data.impl.interceptor.history.entity.DecisionRequirementsDefinitionTransformer` ## `camunda.migrator.c7.data-source` Prefix: `camunda.migrator.c7.data-source` | Property | Type | Description | | :------------------- | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------- | | `.table-prefix` | `string` | Optional prefix for Camunda 7 database tables. Default: _(empty)_ | | `.auto-ddl` | `boolean` | Automatically create/update Camunda 7 database schema. Default: `false` | | `.database-vendor` | `string` | The database vendor is automatically detected and can currently not be overridden. | | `.*` | | You can apply all [`HikariConfig` properties](https://github.com/brettwooldridge/HikariCP?tab=readme-ov-file#gear-configuration-knobs-baby). | | `.jdbc-url` | `string` | JDBC connection URL for the source Camunda 7 database. Default: `jdbc:h2:mem:migrator` | | `.username` | `string` | Username for Camunda 7 database connection. Default: `sa` | | `.password` | `string` | Password for Camunda 7 database connection. Default: `sa` | | `.driver-class-name` | `string` | JDBC driver class for Camunda 7 database. Default: `org.h2.Driver` | ## `camunda.migrator.c8` Prefix: `camunda.migrator.c8` | Property | Type | Description | | :---------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `.deployment-dir` | `string` | Define directory which resources like BPMN processes are automatically deployed to C8. In case multi-tenancy is enabled, please perform a manual deployment. | ## `camunda.migrator.c8.data-source` Prefix: `camunda.migrator.c8.data-source` If the `c8.data-source` configuration is absent, the RDBMS history data migrator is disabled. | Property | Type | Description | | :------------------- | :-------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- | | `.table-prefix` | `string` | Optional prefix for Camunda 8 RDBMS database tables. Default: _(empty)_ | | `.auto-ddl` | `boolean` | Automatically create/update Camunda 8 RDBMS database schema. Default: `false` | | `.database-vendor` | `string` | Database vendor for Camunda 8 schema. Options: `h2`, `postgresql`, `oracle`. Default: Automatically detected. | | `.*` | | You can apply all [`HikariConfig` properties](https://github.com/brettwooldridge/HikariCP?tab=readme-ov-file#gear-configuration-knobs-baby). For example: | | `.jdbc-url` | `string` | JDBC connection URL for the target Camunda 8 RDBMS database. Default: `jdbc:h2:mem:migrator` | | `.username` | `string` | Username for Camunda 8 database connection. Default: `sa` | | `.password` | `string` | Password for Camunda 8 database connection. Default: `sa` | | `.driver-class-name` | `string` | JDBC driver class for Camunda 8 database. Default: `org.h2.Driver` | ## `logging` Prefix: `logging` | Property | Type | Description | | :--------------------------------- | :------- | :-------------------------------------------------------------------------------------------------------------------- | | `.level.root` | `string` | Root logger level. Default: `INFO` | | `.level.io.camunda.migration.data` | `string` | Migrator logging level. Default: `INFO` | | `.file.name` | `string` | Log file location. Set to: `logs/camunda-7-to-8-data-migrator.log`. If not specified, logs are output to the console. | --- ## Database Database configuration is required for both Camunda 7 and Camunda 8 (RDBMS history) data sources. The Data Migrator uses JDBC to connect to these databases. ## Setup 1. Download the JDBC driver JAR for your database and place it in `configuration/userlib`. The H2 driver is bundled for development and testing. All other database drivers must be provided by you. 2. Configure connection details in `configuration/application.yml`. 3. Set table prefixes if your installation uses them. 4. Verify connectivity before starting migration. 5. Ensure sufficient disk space for migration data. :::info The database vendor is automatically detected but can be overridden using the `database-vendor` property. ::: ## Transaction isolation level The required isolation level to run the Data Migrator with is `READ COMMITTED`. Other transaction isolation levels are not supported and might lead to unexpected behavior. ## History migration atomicity Read more about [history migration atomicity](../data-migrator/history.md#atomicity). ## Compatibility The migrator supports the following SQL databases: | Database | Version | JDBC Driver | Notes | | ------------------------ | -------------- | ---------------------------------------------- | -------------------------- | | **PostgreSQL** | 15, 16, 17, 18 | `org.postgresql.Driver` | Recommended for production | | **Oracle** | 19c, 23ai | `oracle.jdbc.OracleDriver` | Recommended for production | | **Microsoft SQL Server** | 2022 | `com.microsoft.sqlserver.jdbc.SQLServerDriver` | Recommended for production | | **MariaDB** | 11.8 | `org.mariadb.jdbc.Driver` | Recommended for production | :::note JDBC drivers are not bundled with the Data Migrator distribution, except for H2, which is included for development and testing. Download the driver JAR for your database vendor and place it in `configuration/userlib` before starting the migrator. ::: The migrator supports migration only within the same database vendor: | Migration Path | Status | | ----------------------- | ---------------- | | PostgreSQL → PostgreSQL | ✅ Supported | | PostgreSQL → Oracle | ❌ Not supported | ## Dropping the migration mapping schema The migrator uses the `{prefix}MIGRATION_MAPPING` table to keep track of instances. To drop this table after the migration is complete, use `--drop-schema` when starting the migrator. This will drop the migration mapping schema on shutdown if the migration was successful (no entities were skipped): ```bash # Migrate and drop the migration mapping schema on shutdown if migration was successful ./start.sh --runtime --drop-schema ``` To drop the table regardless of the migration status, use `--force` in combination with `--drop-schema`. This will perform the drop in all cases: ```bash # Migrate and force drop the migration mapping schema on shutdown ./start.sh --runtime --drop-schema --force ``` ```bash # Migrate and drop the migration mapping schema on shutdown if migration was successful start.bat --runtime --drop-schema ``` To drop the table regardless of the migration status, use `--force` in combination with `--drop-schema`. This will perform the drop in all cases: ```bash # Migrate and force drop the migration mapping schema on shutdown start.bat --runtime --drop-schema --force ``` :::warning Using `--force` can lead to data loss. Use with caution. ::: --- ## Coverage The following tables show which Camunda 8 entities and properties are migrated by the History Data Migrator. ## Unsupported entities The following entities are not supported for migration (no properties are migrated): - Batch operation - Batch operation item - Cluster variable - Correlated message subscription - Exporter position - History deletion - Job metrics batch - Message subscription - Sequence flow - Usage metric - Usage metric (TU) - Web session ## Supported entities The following limitations apply to audit log entries: - Audit log entries are migrated only for user tasks, process definitions, process instances, variables, jobs, external tasks, users, groups, and authorizations. - Audit log entries are not migrated for batch operations, identity links, attachments, decisions, job definitions, metrics, operation logs, filters, comments, and properties. The **Migration Supported** column uses the following values: - **Yes** — the property is populated, either from a Camunda 7 value or with a fixed, semantically correct value (for example, a historic job's `state` is always `COMPLETED`). - **Yes, with placeholder** — the property is always populated, but the value may sometimes be a placeholder rather than data migrated from Camunda 7. See the Notes column for the placeholder used. - **Partially** — the property is populated for some records and not others. - **No** — the property is not populated. | Entity Type | Property | Migration Supported | Notes | | -------------------------- | ----------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Audit log** | | | | | | `auditLogKey` | Yes | | | | `entityKey` | Yes, with placeholder | Set to the target entity's key for entities related to user tasks, process definitions, and process instances. For all other entity types, a placeholder (`C7_MIGRATED`) is written. | | | `entityType` | Yes | | | | `operationType` | Yes | | | | `entityVersion` | Yes | | | | `entityValueType` | No | | | | `entityOperationIntent` | No | | | | `batchOperationKey` | No | | | | `batchOperationType` | No | | | | `timestamp` | Yes | | | | `actorType` | Yes | | | | `actorId` | Yes | | | | `agentElementId` | No | | | | `tenantId` | Yes | | | | `tenantScope` | Yes | | | | `result` | Yes | | | | `category` | Yes | | | | `processDefinitionId` | Yes | | | | `decisionRequirementsId` | No | | | | `decisionDefinitionId` | No | | | | `processDefinitionKey` | Yes | | | | `processInstanceKey` | Yes | | | | `rootProcessInstanceKey` | Yes | | | | `elementInstanceKey` | Partially | Migrated only for entities related to user tasks | | | `jobKey` | Partially | Migrated only for entities related to jobs | | | `userTaskKey` | Yes | | | | `decisionRequirementsKey` | No | | | | `decisionDefinitionKey` | No | | | | `decisionEvaluationKey` | No | | | | `deploymentKey` | No | | | | `formKey` | No | | | | `resourceKey` | No | | | | `relatedEntityType` | No | | | | `relatedEntityKey` | No | | | | `entityDescription` | Partially | Migrated only for user, group, tenant, and delete variable operations | | | `partitionId` | Yes | Assigned based on the root process instance, or randomly if not related to a process instance. See [partition distribution](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/history.md#partition-distribution). | | | `historyCleanupDate` | Yes | | | **Decision definition** | | | | | | `decisionDefinitionKey` | Yes | | | | `name` | Yes | | | | `decisionDefinitionId` | Yes | | | | `tenantId` | Yes | | | | `version` | Yes | | | | `decisionRequirementsId` | Yes | For single Camunda 7 decisions that have no decision requirements definition (DRD), a synthetic DRD is generated. Its ID uses the configured legacy ID prefix followed by the decision ID, for example `c7-legacy-` with the default prefix. | | | `decisionRequirementsKey` | Yes | | | | `decisionRequirementsName` | Yes | | | | `decisionRequirementsVersion` | Yes | | | **Decision instance** | | | | | | `decisionInstanceId` | Yes | | | | `decisionInstanceKey` | Yes | | | | `state` | Yes | | | | `evaluationDate` | Yes | | | | `evaluationFailure` | No | | | | `evaluationFailureMessage` | No | | | | `result` | Yes | When the Camunda 7 decision produced no result, the value is migrated as the JSON literal string `"null"` instead of a database `null`. | | | `flowNodeInstanceKey` | Yes | | | | `flowNodeId` | Yes | | | | `processInstanceKey` | Yes | | | | `processDefinitionKey` | Yes | | | | `processDefinitionId` | Yes | | | | `rootProcessInstanceKey` | Yes | | | | `decisionDefinitionKey` | Yes | | | | `decisionDefinitionId` | Yes | | | | `decisionRequirementsKey` | Yes | | | | `decisionRequirementsId` | Yes | | | | `rootDecisionDefinitionKey` | Yes | | | | `decisionType` | Yes | | | | `tenantId` | Yes | | | | `partitionId` | Yes | Assigned based on the root process instance, or randomly for standalone decisions. See [partition distribution](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/history.md#partition-distribution). | | | `evaluatedInputs` | Yes | | | | `evaluatedOutputs` | Yes | | | | `historyCleanupDate` | Yes | | | **Decision requirements** | | | | | | `decisionRequirementsKey` | Yes | | | | `decisionRequirementsId` | Yes | | | | `name` | Yes | | | | `resourceName` | Yes | | | | `version` | Yes | | | | `xml` | Yes | | | | `tenantId` | Yes | | | **Flow node instance** | | | | | | `flowNodeInstanceKey` | Yes | | | | `processInstanceKey` | Yes | | | | `processDefinitionKey` | Yes | | | | `processDefinitionId` | Yes | | | | `flowNodeScopeKey` | Yes | | | | `startDate` | Yes | | | | `endDate` | Yes | | | | `flowNodeId` | Yes | | | | `flowNodeName` | Yes | | | | `treePath` | Yes | | | | `type` | Yes | | | | `state` | Yes | | | | `incidentKey` | No | | | | `numSubprocessIncidents` | No | | | | `tenantId` | Yes | | | | `partitionId` | Yes | Assigned based on the root process instance. See [partition distribution](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/history.md#partition-distribution). | | | `rootProcessInstanceKey` | Yes | | | **Form** | | | | | | `formKey` | Yes | | | | `tenantId` | Yes | | | | `formId` | Yes | | | | `schema` | Yes | | | | `version` | Yes | | | | `isDeleted` | Yes | Always `false`. | | **Incident** | | | | | | `incidentKey` | Yes | | | | `processDefinitionKey` | Yes | | | | `processDefinitionId` | Yes | | | | `processInstanceKey` | Yes | | | | `rootProcessInstanceKey` | Yes | | | | `flowNodeInstanceKey` | Yes | Always populated. Incidents for which a flow node instance key cannot be resolved are skipped; see [Incidents](limitations.md#incidents-1) limitations. | | | `flowNodeId` | Yes | | | | `jobKey` | Partially | Populated only for entities related to jobs of type asynchronous continuation. | | | `errorType` | Yes | Populated when an equivalent mapping is possible. In all other cases, it's set to `UNKNOWN` | | | `errorMessage` | Yes, with placeholder | Set to the Camunda 7 incident message. When the incident has no message, a placeholder (`C7_NO_MESSAGE`) is written. | | | `errorMessageHash` | No | | | | `creationDate` | Yes | | | | `state` | Yes | Always `Resolved` | | | `treePath` | Yes | | | | `tenantId` | Yes | | | | `partitionId` | Yes | Assigned based on the root process instance. See [partition distribution](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/history.md#partition-distribution). | | **Job** | | | | | | jobKey | Yes | | | | type | No | | | | worker | Yes, with placeholder | Set to the hostname of the machine where Camunda 7 executed the job. When the hostname is unavailable (for example, legacy rows or a custom `HostnameProvider`), a placeholder (`C7_MIGRATED`) is written. | | | state | Yes | Always `COMPLETED`. | | | kind | Yes | Always `BPMN_ELEMENT`. | | | listenerEventType | Yes | Always `UNSPECIFIED`. | | | retries | Yes | Always `0`. | | | isDenied | No | | | | deniedReason | No | | | | hasFailedWithRetriesLeft | No | | | | errorCode | No | | | | errorMessage | No | | | | serializedCustomHeaders | No | | | | customHeaders | No | | | | deadline | No | | | | endTime | No | | | | processDefinitionId | Yes | | | | processDefinitionKey | Yes | | | | processInstanceKey | Yes | | | | rootProcessInstanceKey | Yes | | | | elementId | Yes | | | | elementInstanceKey | Yes | Always populated. Jobs for which an element instance key cannot be resolved are skipped; see [Jobs](limitations.md#jobs) limitations. | | | tenantId | Yes | | | | partitionId | Yes | Assigned based on the root process instance. See [partition distribution](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/history.md#partition-distribution). | | | creationTime | Yes | | | | lastUpdateTime | Yes | Sourced from the most recent Camunda 7 historic job log entry. | | **External Task as a Job** | | | | | | jobKey | Yes | | | | type | Yes | Topic name | | | worker | Yes, with placeholder | Always set to a placeholder (`C7_MIGRATED`); Camunda 7 does not record a worker hostname for external tasks. | | | state | Yes | Always `COMPLETED`. | | | kind | Yes | Always `BPMN_ELEMENT`. | | | listenerEventType | Yes | Always `UNSPECIFIED`. | | | retries | Yes | Always `0`. | | | isDenied | No | | | | deniedReason | No | | | | hasFailedWithRetriesLeft | No | | | | errorCode | No | | | | errorMessage | No | | | | serializedCustomHeaders | No | | | | customHeaders | No | | | | deadline | No | | | | endTime | No | | | | processDefinitionId | Yes | | | | processDefinitionKey | Yes | | | | processInstanceKey | Yes | | | | rootProcessInstanceKey | Yes | | | | elementId | Yes | | | | elementInstanceKey | Yes | Always populated. External tasks for which an element instance key cannot be resolved are skipped; see [Jobs](limitations.md#jobs) limitations. | | | tenantId | Yes | | | | partitionId | Yes | Assigned based on the root process instance. See [partition distribution](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/history.md#partition-distribution). | | | creationTime | Yes | | | | lastUpdateTime | Yes | Sourced from the most recent Camunda 7 historic external task log entry. | | **Process definition** | | | | | | `processDefinitionKey` | Yes | | | | `processDefinitionId` | Yes | | | | `resourceName` | Yes | | | | `name` | Yes | | | | `tenantId` | Yes | | | | `versionTag` | Yes | | | | `version` | Yes | | | | `bpmnXml` | Yes | | | | `formId` | Yes | | | **Process instance** | | | | | | `processInstanceKey` | Yes | | | | `rootProcessInstanceKey` | Yes | | | | `processDefinitionId` | Yes | | | | `processDefinitionKey` | Yes | | | | `state` | Yes | | | | `startDate` | Yes | | | | `endDate` | Yes | | | | `tenantId` | Yes | | | | `parentProcessInstanceKey` | Yes | | | | `parentElementInstanceKey` | Yes | | | | `numIncidents` | Yes | Always `0`. | | | `version` | Yes | | | | `partitionId` | Yes | Assigned based on the root process instance, or randomly for root process instances. See [partition distribution](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/history.md#partition-distribution). | | | `treePath` | Yes | | | | `historyCleanupDate` | Yes | | | | `tags` | Yes | Tags do not exist in Camunda 7, but we allow to set them during migration via [interceptors](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/history.md#entity-transformation) and they will be visible in Camunda 8 after migration. Default tags: `legacy-id` | | | `businessId` | Yes | | | **User task** | | | | | | `userTaskKey` | Yes | | | | `elementId` | Yes | | | | `name` | Yes | | | | `processDefinitionId` | Yes | | | | `creationDate` | Yes | | | | `completionDate` | Yes | | | | `assignee` | Yes | | | | `state` | Yes | | | | `formKey` | Yes | | | | `processDefinitionKey` | Yes | | | | `processInstanceKey` | Yes | | | | `rootProcessInstanceKey` | Yes | | | | `elementInstanceKey` | Yes | | | | `tenantId` | Yes | | | | `dueDate` | Yes | | | | `followUpDate` | Yes | | | | `candidateGroups` | Yes | | | | `candidateUsers` | Yes | | | | `externalFormReference` | No | | | | `processDefinitionVersion` | Yes | | | | `customHeaders` | No | | | | `priority` | Yes | | | | `tags` | Yes | Tags do not exist in Camunda 7, but we allow to set them during migration via [interceptors](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/history.md#entity-transformation) and they will be visible in Camunda 8 after migration | | | `partitionId` | Yes | Assigned based on the root process instance. See [partition distribution](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/history.md#partition-distribution). | | **Variable** | | | | | | `variableKey` | Yes | | | | `name` | Yes | | | | `type` | Yes | | | | `doubleValue` | Yes | | | | `longValue` | Yes | | | | `value` | Yes | | | | `fullValue` | Yes | | | | `isPreview` | Yes | | | | `scopeKey` | Yes | | | | `processInstanceKey` | Yes | | | | `rootProcessInstanceKey` | Yes | | | | `processDefinitionId` | Yes | | | | `tenantId` | Yes | | | | `partitionId` | Yes | Assigned based on the root process instance. See [partition distribution](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/history.md#partition-distribution). | | | `elementInstanceKey` | Yes | | --- ## History Use the History Data Migrator to copy process instance audit data to Camunda 8. ## About history migration Process instances leave traces, referred to as [history in Camunda 7](https://docs.camunda.org/manual/latest/user-guide/process-engine/history/). These are audit logs of when a process instance was started, what path it took, and so on. It is important to note that audit data can exist for ended processes from the past, but is also available for currently still running process instances, as those process instances also left traces up to the current wait state. The History Data Migrator can copy this audit data to Camunda 8. For process instances that were still active or suspended in Camunda 7, the migrated history data will be marked as canceled in Camunda 8. This ensures a clear audit trail while preventing confusion with actively running instances in Camunda 8. Audit data migration might need to look at a huge amount of data, which can take time to migrate. You can run audit data migration alongside normal operations (for example, after the successful big bang migration of runtime process instances) so that it doesn't require downtime and as such, the performance might not be as critical as for runtime instance migration. During history migration, the Data Migrator maps the Camunda 7 process instance `businessKey` to Camunda 8 `businessId`. If no business key is present, the process instance is migrated without a business ID. If the business key is blank, it is migrated as a blank business ID. During migration, the History Data Migrator sets a `legacyId` variable in the process instances to link them to their original Camunda 7 process instances. ## Requirements and limitations The following requirements and limitations apply: - Camunda 7 must be stopped, and the Camunda 8 database must be reachable. Zeebe can be stopped during history migration. - The History Data Migrator must be able to access the Camunda 7 database. - The History Data Migrator can migrate data to Camunda 8 only when a relational database (RDBMS) is used. - The History Data Migrator must be able to access the Camunda 8 database. As a result, you can run this tool only in a self-managed environment. - If you manipulate Camunda 7 data between History Data Migrator runs, data consistency might be affected. See [auto-cancellation of active instances](#auto-cancellation-of-active-instances) for details. - **History cleanup during migration**: If you keep Camunda 8 running and history cleanup is enabled during history migration, and cleanup dates are due (for example, past removal times from Camunda 7 or negative auto-cancel TTL values), Camunda 8 history cleanup will run concurrently with migration. This may result in parent entities being cleaned up before their children are migrated, causing child entities to be skipped. See [history cleanup](#history-cleanup) for mitigation strategies. - If you migrate runtime and history data for an active Camunda 7 process instance, two separate records will appear in Operate: 1. **Fresh runtime instance**: The migrated active process instance running on Zeebe. This instance continues execution from the last wait state before migration and produces new history going forward. It does not include historical data from before the migration. 2. **Auditable instance**: A canceled historic process instance that preserves the audit trail (history data) up to the last wait state pre-migration. This instance appears as canceled and serves only as an audit record of what happened in Camunda 7. These two instances are separate entities and are not automatically linked in the UI, although they share the same process variables from the migration point. ## Usage examples ```bash # Run history migration ./start.sh --history # List all skipped history entities ./start.sh --history --list-skipped # List skipped entities for specific types ./start.sh --history --list-skipped HISTORY_PROCESS_INSTANCE HISTORY_USER_TASK # List the Camunda 7 ID and Camunda 8 key for each migrated entity ./start.sh --history --list-migrated # List the Camunda 7 ID and Camunda 8 key for specific entity types ./start.sh --history --list-migrated HISTORY_PROCESS_INSTANCE # Retry skipped history entities ./start.sh --history --retry-skipped ``` ```bash # Run history migration start.bat --history # List all skipped history entities start.bat --history --list-skipped # List skipped entities for specific types start.bat --history --list-skipped HISTORY_PROCESS_INSTANCE HISTORY_USER_TASK # List the Camunda 7 ID and Camunda 8 key for each migrated entity start.bat --history --list-migrated # List mappings for specific entity types start.bat --history --list-migrated HISTORY_PROCESS_INSTANCE # Retry skipped history entities start.bat --history --retry-skipped ``` ## Entity types | Entity type | Description | | :---------------------------- | :------------------- | | `HISTORY_FORM_DEFINITION` | Form definitions | | `HISTORY_PROCESS_DEFINITION` | Process definitions | | `HISTORY_PROCESS_INSTANCE` | Process instances | | `HISTORY_JOB` | Jobs | | `HISTORY_INCIDENT` | Process incidents | | `HISTORY_VARIABLE` | Process variables | | `HISTORY_USER_TASK` | User tasks | | `HISTORY_FLOW_NODE` | Flow node instances | | `HISTORY_DECISION_INSTANCE` | Decision instances | | `HISTORY_DECISION_DEFINITION` | Decision definitions | ## Tenants - Camunda 7's `null` tenant is migrated to Camunda 8's `` tenant. - All other `tenantId`s will be migrated as-is. - For details, see [multi-tenancy](/components/concepts/multi-tenancy.md#tenant-identifier) in Camunda 8. ## Auto-cancellation of active instances When migrating history data, the Data Migrator automatically handles **active or suspended** process instances from Camunda 7 by marking them as **canceled** in Camunda 8. This applies to: - Process instances - Flow nodes - User tasks - Incidents Auto-canceled entities are assigned the migration timestamp as their end date. By default, auto-canceled instances receive a cleanup date calculated as: ```text cleanup_date = end_date + 6 months ``` This keeps auto-canceled instances eligible for history cleanup after six months and helps prevent unbounded history growth. See [configuration for history auto-cancellation](../data-migrator/config-properties.md#camundamigratorhistoryauto-cancelcleanup) for more details. :::warning Negative TTL values If you configure a negative auto-cancel TTL value, calculated cleanup dates are in the past. If Camunda 8 is running during migration, history cleanup can immediately clean up these entities, potentially before their child entities are migrated. See [history cleanup](#history-cleanup) for mitigation strategies. ::: ## History Cleanup Instances that were already completed in Camunda 7 retain their original cleanup dates: - If a `removalTime` exists in Camunda 7, it is migrated as-is. Child entities (user tasks, variables, flow nodes) inherit the root instance's cleanup date. - If no `removalTime` exists, no cleanup date is set. - Auto-cancel cleanup configuration **only applies to instances that were active or suspended** in Camunda 7. If Camunda 8 is running during migration with cleanup dates in the past, history cleanup may delete parent entities before their children are migrated, causing children to be skipped. **Common scenarios:** - Past `removalTime` values from Camunda 7 - Negative auto-cancel TTL (for example, `P-1D`) - Long migrations where dates become due during execution As a result, you will see skipped child entities with messages referencing the deleted parent. These cannot be recovered unless the parent is re-migrated. Choose one approach to prevent cleanup interference: **Stop Camunda 8 during migration** - Stop Camunda 8 cluster before migration - Run migration while offline - Start Camunda 8 when complete ✅ Guarantees no cleanup interference **Set cleanup dates in the future** - Update Camunda 7 removal times before migration (if feasible) - Configure positive TTL for auto-canceled instances (for example, `P6M`, `P1Y`) ✅ Prevents immediate cleanup **Allow concurrent cleanup** - Run Camunda 8 during migration - Accept some entities may be cleaned up immediately - Accept child entity skips ⚠️ Suitable only when historical data loss is acceptable ## Partition distribution The History Data Migrator assigns migrated history data to Zeebe partitions. **Partition assignment is critical for history cleanup to work correctly.** ### How partitions are assigned - **Root process instances** or **standalone decisions**: Randomly assigned to available partitions - **Child entities** (sub-processes, call activities, flow nodes, variables, user tasks, incidents, decision instances, etc.): Inherit partition from root process instance - **Audit logs**: Inherit the root process instance partition, or are randomly assigned when not related to a process instance This ensures all entities in a process hierarchy share the same partition, enabling the RDBMS exporter on that partition to perform cleanup. :::warning Partition configuration must match Zeebe topology Camunda 7 migrated history data can **only be deleted via history cleanup**, which requires: - The partition ID exists in your Camunda 8 Zeebe cluster - That partition has an RDBMS exporter configured If partition IDs assigned during migration don't exist or lack RDBMS exporters, **that data cannot be cleaned up** and will persist indefinitely. ::: ### Partition discovery By default, the migrator queries Zeebe topology through the Camunda REST API at migration start to discover available partitions. ### Offline mode To migrate without Camunda 8 REST API connectivity (no topology query), configure the partition count manually: ```yaml camunda.migrator: history: partition-count: 3 # Must match your Camunda 8 cluster ``` When configured: - Topology is not queried from REST API - Partition IDs generated as sequence: 1, 2, 3, ... - No Camunda 8 REST API connectivity is required at migration start (database connectivity is still required) The configured value must exactly match your Camunda 8 Zeebe cluster's partition count. If the configured value does not match the cluster, then: - Data assigned to non-existent partitions cannot be cleaned up - Data may persist indefinitely with no automatic cleanup path Always verify cluster partition configuration before migration. :::caution Don't change partitions after migration Changing cluster partition count after migration can leave data on removed partitions that cannot be cleaned up. Complete history migration before scaling partitions. ::: ## Forms The History Data Migrator automatically migrates [Camunda Forms](https://docs.camunda.org/manual/latest/user-guide/task-forms/#camunda-forms) from Camunda 7 to Camunda 8. This includes forms linked to process definitions (start forms) and user tasks. The form schema (JSON definition) is extracted from the Camunda 7 deployment resources and migrated to Camunda 8. The form structure, fields, and validation rules are preserved during migration. User tasks that reference non-existent forms will be migrated as well. ### Form linking The migrator automatically detects and links forms configured using `camunda:formRef` with a `camunda:formKey`. This applies to both start events and user tasks. For each form reference, the migrator: 1. Resolves the form definition based on the form key and binding (deployment, latest, or version). 2. Links the element (process definition or user task) to the migrated form in Camunda 8. Example BPMN configurations that will be migrated: ```xml ``` ## Atomicity The History Data Migrator uses the configured Camunda 8 datasource for both the migration mapping schema and the migrated data. This ensures single-transaction atomicity for each entity migration. ### What is migrated atomically Each entity migration writes multiple rows in a single transaction: - Camunda 8 data: The migrated entity (for example, a user task, process instance, or variable) - Child entities, when applicable (for example, decision instances and their related decisions, inputs, and outputs) - Tracking information: A mapping from the Camunda 7 ID to the Camunda 8 key, used for resuming migrations and preventing duplicates If an error occurs, the transaction is rolled back and no partial data is persisted. This prevents inconsistent states such as Camunda 8 data without tracking information (which can cause duplicates on retry) or orphaned child entities. ## Entity transformation Entity transformations are handled by built-in interceptors that transform Camunda 7 historic entities into Camunda 8 database models during migration. The History Data Migrator uses the `EntityInterceptor` interface to allow customization of this conversion process. ### Built-in interceptors The following built-in transformers convert Camunda 7 historic entities: | Interceptor | Camunda 7 entity type | Camunda 8 Model | | ------------------------------------------- | ---------------------------------------- | ----------------------------- | | `AuditLogTransformer` | `UserOperationLogEntry` | `AuditLogDbModel` | | `FormTransformer` | `CamundaFormDefinitionEntity` | `FormDbModel` | | `ProcessInstanceTransformer` | `HistoricProcessInstance` | `ProcessInstanceDbModel` | | `ProcessDefinitionTransformer` | `ProcessDefinition` | `ProcessDefinitionDbModel` | | `FlowNodeTransformer` | `HistoricActivityInstance` | `FlowNodeInstanceDbModel` | | `UserTaskTransformer` | `HistoricTaskInstance` | `UserTaskDbModel` | | `JobTransformer` | `HistoricJobLog` | `JobDbModel` | | `IncidentTransformer` | `HistoricIncident` | `IncidentDbModel` | | `VariableTransformer` | `HistoricVariableInstance` | `VariableDbModel` | | `DecisionInstanceTransformer` | `HistoricDecisionInstance` | `DecisionInstanceDbModel` | | `DecisionDefinitionTransformer` | `HistoricDecisionDefinition` | `DecisionDefinitionDbModel` | | `DecisionRequirementsDefinitionTransformer` | `HistoricDecisionRequirementsDefinition` | `DecisionRequirementsDbModel` | ## Custom transformation The `EntityInterceptor` interface allows you to define custom logic that executes when a Camunda 7 historic entity is being converted to a Camunda 8 database model during migration. This is useful for enriching, auditing, or customizing entity conversion. Custom interceptors are enabled by default and can be restricted to specific entity types. ### Type-safe API The `EntityInterceptor` interface uses Java generics to provide compile-time type safety: ```java public interface EntityInterceptor { void execute(C7 entity, C8 builder); // ... } ``` ### How to implement an `EntityInterceptor` 1. Create a new Maven project with the provided `pom.xml` structure 2. Add a dependency on `camunda-7-to-8-data-migrator-core` (scope: `provided`) 3. Implement the `EntityInterceptor` interface 4. Add setter methods for any configurable properties 5. Package as JAR and deploy to the `configuration/userlib` folder 6. Configure in `configuration/application.yml` ### Create a custom entity interceptor Here's an example of a custom entity interceptor which is only called for process instances: ```java public class ProcessInstanceEnricher implements EntityInterceptor { /** * Restrict this interceptor to only handle process instances. */ @Override public Set> getTypes() { return Set.of(HistoricProcessInstance.class); } @Override public void execute(HistoricProcessInstance entity, ProcessInstanceDbModel.ProcessInstanceDbModelBuilder builder) { // Custom conversion logic // For example, add custom metadata or modify the conversion builder.processDefinitionId(entity.getProcessDefinitionKey()); } } ``` #### Access Camunda 7 process engine To retrieve information from Camunda 7 entities, use the `EntityConversionContext` parameter which provides access to the `processEngine`. Use it to access services such as `RepositoryService` and `RuntimeService`. Fetch additional data as needed from other Camunda 7 entities. ```java public class ProcessInstanceEnricher implements EntityInterceptor { @Override public Set> getTypes() { return Set.of(HistoricProcessInstance.class); } /** * Alternative execute signature with EntityConversionContext access. * This signature gives you access to the process engine and other context. */ @Override public void execute(EntityConversionContext context) { // Access the entity and builder from context HistoricProcessInstance entity = context.getC7Entity(); ProcessInstanceDbModel.ProcessInstanceDbModelBuilder builder = context.getC8DbModelBuilder(); // Use ProcessEngine to retrieve deployment information from Camunda 7 process engine ProcessEngine processEngine = context.getProcessEngine(); // Example: Retrieve the deployment ID from the process definition String deploymentId = processEngine.getRepositoryService() .createProcessDefinitionQuery() .processDefinitionKey(entity.getProcessDefinitionKey()) .singleResult() .getDeploymentId(); // Custom conversion logic using the retrieved data // ... } } ``` **Note:** The `EntityInterceptor` interface provides two `execute` method signatures: - `execute(C7 entity, C8 builder)` - Simple type-safe signature for basic transformations - `execute(EntityConversionContext context)` - Full signature with access to process engine and other context ### Limit interceptors by entity type Entity interceptors can be restricted to specific entity types using the `getTypes()` method. Use Camunda 7 historic entity classes: ```java // Example 1: Handle multiple specific types public class MultiEntityInterceptor implements EntityInterceptor { @Override public Set> getTypes() { // Handle only specific types return Set.of( ProcessDefinition.class, // Process definitions HistoricProcessInstance.class, // Process instances HistoricActivityInstance.class, // Flow nodes/activities HistoricTaskInstance.class, // User tasks HistoricVariableInstance.class, // Variables HistoricIncident.class, // Incidents HistoricDecisionInstance.class // Decision instances ); } @Override public void execute(Object entity, Object builder) { // Handle different entity types if (entity instanceof HistoricProcessInstance) { // Process instance logic } else if (entity instanceof HistoricActivityInstance) { // Flow node logic } // etc. } } // Example 2: Universal interceptor (handles all entity types) public class EntityLogger implements EntityInterceptor { @Override public Set> getTypes() { return Set.of(); // Empty set = handle all types } @Override public void execute(Object entity, Object builder) { // This will be called for all entity types System.out.println("Converting entity: " + entity.getClass().getSimpleName()); } } ``` ### Configure custom interceptors Configure your custom interceptors in `application.yml`: ```yaml # Entity interceptor plugins configuration # These plugins can be packaged in JARs and dropped in the userlib folder camunda: migrator: interceptors: - class-name: com.example.migrator.ProcessInstanceEnricher enabled: true properties: customProperty: "value" enableAudit: true ``` ### Deployment 1. Package your custom interceptor as a JAR file 2. Place the JAR in the `configuration/userlib/` folder 3. Configure the interceptor in `configuration/application.yml` 4. Restart the Data Migrator The `enabled` property is supported for all interceptors (both built-in and custom) and defaults to `true`. See [example interceptor](https://github.com/camunda/camunda-7-to-8-migration-tooling/tree/main/data-migrator/examples/entity-interceptor). ### Execution order - Custom interceptors configured in the `application.yml` are executed in their order of appearance, from top to bottom. - Built-in transformers run first, followed by custom interceptors. ### Required fields Some Camunda 8 entity fields are required to be non-null. The built-in transformers always populate them, but a custom interceptor can overwrite a value with `null`. Camunda 8 enforces non-nullability when these entities are read, so a row written with a `null` in a required field may fail to be read back through the Camunda 8 APIs. When customizing entity conversion, do not set the following required fields to `null`: | Camunda 8 model | Required fields | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `FlowNodeInstance` | `flowNodeInstanceKey`, `processInstanceKey`, `processDefinitionKey`, `flowNodeId`, `type`, `state`, `processDefinitionId`, `tenantId` | | `Incident` | `incidentKey`, `processDefinitionKey`, `processDefinitionId`, `processInstanceKey`, `errorMessage`, `flowNodeId`, `flowNodeInstanceKey`, `creationTime`, `tenantId` | | `Job` | `jobKey`, `type`, `worker`, `state`, `kind`, `listenerEventType`, `retries`, `hasFailedWithRetriesLeft`, `processDefinitionId`, `processDefinitionKey`, `processInstanceKey`, `elementId`, `elementInstanceKey`, `tenantId`, `creationTime`, `lastUpdateTime` | | `UserTask` | `userTaskKey`, `elementId`, `processDefinitionId`, `creationDate`, `state`, `processDefinitionKey`, `processInstanceKey`, `elementInstanceKey`, `processDefinitionVersion`, `tenantId` | | `Variable` | `variableKey`, `name`, `value`, `scopeKey`, `processInstanceKey`, `processDefinitionId`, `tenantId` | | `ProcessInstance` | `processInstanceKey`, `tenantId` | | `ProcessDefinition` | `processDefinitionKey`, `processDefinitionId`, `resourceName`, `version`, `tenantId` | | `DecisionInstance` | `decisionInstanceId`, `decisionInstanceKey`, `state`, `evaluationDate`, `decisionDefinitionId`, `decisionDefinitionKey`, `decisionDefinitionName`, `decisionDefinitionType`, `result`, `tenantId` | | `DecisionDefinition` | `decisionDefinitionKey`, `decisionDefinitionId`, `name`, `version`, `decisionRequirementsId`, `decisionRequirementsKey`, `decisionRequirementsVersion`, `tenantId` | | `DecisionRequirements` | `decisionRequirementsKey`, `decisionRequirementsId`, `name`, `version`, `resourceName`, `tenantId` | | `Form` | `formKey`, `formId`, `schema`, `version`, `tenantId` | | `AuditLog` | `auditLogKey`, `entityKey`, `entityType`, `operationType`, `timestamp`, `result`, `category` | ### Error handling When an entity transformation fails: 1. The entity is marked as _skipped_ in the migration database. 2. After the initial migration completes, the migrator automatically retries all skipped entities. 3. Retries run in multiple passes until no further progress can be made (that is, no additional entities are successfully migrated). 4. Any entities that remain skipped after all automatic retries are logged as warnings, along with their skip reasons. 5. Use `--history --list-skipped` to view entities that remain skipped after automatic retries. 6. After resolving the underlying issue (for example, unsupported variable types), use `--history --retry-skipped` to manually retry the migration. This automatic retry mechanism is particularly useful for resolving cross-entity dependencies, such as: - Flow node instances that depend on their parent flow node (scope) - Child process instances that depend on parent call activities - Variables or user tasks that depend on their parent process instance --- ## Identity Use the Identity Data Migrator to copy authorizations and tenants to Camunda 8. ## About identity migration Identity data in Camunda includes: - **Identities**: Users, groups, tenants, and their related memberships - **Authorizations**: Permission rules that control access to resources For more information about limitations, refer to the [limitations](./limitations.md#identity) of the Identity Data Migrator. ### Users, groups, and group memberships - The Identity Data Migrator does **not** migrate Camunda 7 internally managed users, groups, and group memberships to Camunda 8. - We recommend integrating Camunda 8 with your organization's external Identity Provider (IdP) instead. - Most organizations use an IdP to manage users, groups, and group memberships centrally, making manual migration unnecessary. ## Authorizations Not all authorizations can be migrated from Camunda 7 to Camunda 8 due to differences in the authorization models of both systems. When identity migration is executed, authorizations that are not supported are skipped and the reason for incompatibility is logged by the migrator. If an authorization contains at least one unsupported permission, the whole authorization is skipped. The following tables provide an overview of the supported authorizations. #### By Authorization Type Camunda 8 only supports `GRANT` authorizations. `REVOKE` and `GLOBAL` authorizations from Camunda 7 are not supported for migration. Because in Camunda 7 `GRANT` authorizations take precedence over `REVOKE` authorizations, skipping (and therefore ignoring) `REVOKE` authorizations does not lead to a loss of effective permissions when migrating to Camunda 8. | Authorization Type | Migration supported | | ------------------ | ------------------- | | `GRANT` | Yes | | `REVOKE` | No | | `GLOBAL` | No | #### By Resource Type | C7 Resource Type | Migration supported | C8 Resource Type equivalent | | ---------------------------------- | ----------------------------------------------- | ---------------------------------- | | `Application` | Yes | `Component` | | `Authorization` | [Partial\*](#authorization-compatibility) | `Authorization` | | `Batch` | [Partial\*](#batch-compatibility) | `Batch` | | `Dashboard` | No | - | | `Decision Definition` | [Partial\*](#decision-definition-compatibility) | `Decision Definition` | | `Decision Requirements Definition` | Yes | `Decision Requirements Definition` | | `Deployment` | [Yes\*](#deployment-compatibility) | - | | `Filter` | No | - | | `Group` | Yes | `Group` | | `Group Membership` | [Partial\*](#group-membership-compatibility) | `Group` | | `Historic Process Instance` | No | - | | `Historic Process Instance` | No | - | | `Historic Task` | No | - | | `Process Definition` | [Partial\*](#process-definition-compatibility) | `Process Definition` | | `Process Instance` | No | - | | `Report` | No | - | | `System` | [Partial\*](#system-compatibility) | `System` | | `Task` | No | - | | `Tenant` | Yes | `Tenant` | | `Tenant Membership` | [Partial\*](#tenant-membership-compatibility) | `Tenant` | | `User` | Yes | `User` | | `User Operation Log Category` | No | - | Details for partial migration can be found below. ## Partial migration Some resource types only support partial migration due to differences in the Camunda 7 and Camunda 8 authorization models, which means some permissions (or some combination of permissions) are not supported for migration. The following sections provide details on the supported permissions for these resource types. ### `Authorization` compatibility In Camunda 7, authorizations for `Authorization` resources can be fine-grained to specific resource IDs (`authorizationId`). Camunda 8 only supports the wildcard (`*`) resource ID for the `Authorization` type. Therefore, only authorizations with the wildcard resource ID are migrated. Migration support for individual permissions: | C7 Permission | Migration supported | C8 Permission equivalent | | ------------- | ------------------- | ------------------------------------ | | `READ` | Yes | `READ` | | `UPDATE` | Yes | `UPDATE` | | `CREATE` | Yes | `CREATE` | | `DELETE` | Yes | `DELETE` | | `ALL` | Yes | `READ`, `UPDATE`, `CREATE`, `DELETE` | ### `Batch` compatibility In Camunda 7, authorizations for `Batch` resources can be fine-grained to specific resource IDs (`batchId`). Camunda 8 only supports the wildcard (`*`) resource ID for the `Batch` type. Therefore, only authorizations with the wildcard resource ID are migrated. | C7 Permission | Migration supported | C8 Permission equivalent | | ------------------------------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `READ` | Yes | `READ` | | `UPDATE` | Yes | `UPDATE` | | `CREATE` | Yes | `CREATE` | | `DELETE` | No | - | | `READ_HISTORY` | No | - | | `DELETE_HISTORY` | No | - | | `CREATE_BATCH_MIGRATE_PROCESS_INSTANCES` | Yes | `CREATE_BATCH_OPERATION_MIGRATE_PROCESS_INSTANCE` | | `CREATE_BATCH_MODIFY _PROCESS_INSTANCES` | Yes | `CREATE_BATCH_OPERATION_MODIFY_PROCESS_INSTANCE` | | `CREATE_BATCH_RESTART_PROCESS_INSTANCES` | No | - | | `CREATE_BATCH_DELETE_RUNNING_PROCESS _INSTANCES` | Yes | `CREATE_BATCH_OPERATION_CANCEL_PROCESS_INSTANCE`, `CREATE_BATCH_OPERATION_DELETE_PROCESS_INSTANCE` | | `CREATE_BATCH_DELETE_FINISHED_PROCESS _INSTANCES` | Yes | `CREATE_BATCH_OPERATION_DELETE_PROCESS_INSTANCE` | | `CREATE_BATCH_DELETE_DECISION_INSTANCES` | Yes | `CREATE_BATCH_OPERATION_DELETE_DECISION_INSTANCE` | | `CREATE_BATCH_SET_JOB_RETRIES` | No | - | | `CREATE_BATCH_SET_REMOVAL_TIME` | No | - | | `CREATE_BATCH_SET_EXTERNAL_TASK_RETRIES` | No | - | | `CREATE_BATCH_UPDATE_PROCESS_INSTANCES _SUSPEND` | No | - | | `CREATE_BATCH_SET_VARIABLES` | No | - | | `ALL` | Yes | `CREATE`, `READ`, `UPDATE`, `CREATE_BATCH_OPERATION_CANCEL_PROCESS_INSTANCE`, `CREATE_BATCH_OPERATION_DELETE_PROCESS_INSTANCE`, `CREATE_BATCH_OPERATION_MIGRATE_PROCESS_INSTANCE`, `CREATE_BATCH_OPERATION_MODIFY_PROCESS_INSTANCE`, `CREATE_BATCH_OPERATION_RESOLVE_INCIDENT`, `CREATE_BATCH_OPERATION_DELETE_DECISION_INSTANCE`, `CREATE_BATCH_OPERATION_DELETE_DECISION_DEFINITION`, `CREATE_BATCH_OPERATION_DELETE_PROCESS_DEFINITION`, | ### `Group Membership` compatibility The `Group Membership` resource type does not exist in Camunda 8, but its functionality is covered by the `Group` resource type in combination with the `UPDATE` permission. However, only the `ALL` permission from Camunda 7 is supported for migration, as individual `CREATE` and `DELETE` permissions would result in a more permissive authorization. | C7 Permission | Migration supported | C8 Resource Type equivalent | C8 Permission equivalent | | ------------- | ------------------- | --------------------------- | ------------------------ | | `CREATE` | No | - | - | | `DELETE` | No | - | - | | `ALL` | Yes | `GROUP` | `UPDATE` | ### `Tenant Membership` compatibility The `Tenant Membership` resource type does not exist in Camunda 8, but its functionality is covered by the `Tenant` resource type in combination with the `UPDATE` permission. However, only the `ALL` permission from Camunda 7 is supported for migration, as individual `CREATE` and `DELETE` permissions would result in a more permissive authorization. | C7 Permission | Migration supported | C8 Resource Type equivalent | C8 Permission equivalent | | ------------- | ------------------- | --------------------------- | ------------------------ | | `CREATE` | No | - | - | | `DELETE` | No | - | - | | `ALL` | Yes | `TENANT` | `UPDATE` | ### `System` compatibility | C7 Permission | Migration supported | C8 Permission equivalent | | ------------- | ------------------- | -------------------------------------------------------- | | `READ` | Yes | `READ`, `READ_USAGE_METRIC` | | `SET` | No | - | | `DELETE` | No | - | | `ALL` | Yes | `READ`, `READ_USAGE_METRIC`, `UPDATE`, `READ_JOB_METRIC` | ### `Decision Definition` compatibility As historic definitions are migrated with a prefixed ID to Camunda 8 to avoid collisions between native and migrated definitions ([ref](../data-migrator/limitations.md#history)), authorizations for `Decision Definition` resources are migrated twice: once for the original ID and once for the prefixed ID. This guarantees that authorizations remain effective after migration. | C7 Permission | Migration supported | C8 Permission equivalent | | ----------------- | ------------------- | ------------------------------------------------------------------------------------------------------------ | | `READ` | Yes | `READ_DECISION_DEFINITION`, `READ_DECISION_INSTANCE` | | `UPDATE` | No | - | | `CREATE_INSTANCE` | Yes | `CREATE_DECISION_INSTANCE` | | `READ_HISTORY` | No | - | | `ALL` | Yes | `CREATE_DECISION_INSTANCE`, `READ_DECISION_DEFINITION`, `READ_DECISION_INSTANCE`, `DELETE_DECISION_INSTANCE` | ### `Process Definition` compatibility As historic definitions are migrated with a prefixed ID to Camunda 8 to avoid collisions between native and migrated definitions ([ref](../data-migrator/limitations.md#history)), authorizations for `Process Definition` resources are migrated twice: once for the original ID and once for the prefixed ID. This guarantees that authorizations remain effective after migration. | C7 Permission | Migration supported | C8 Permission equivalent | | -------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `READ` | Yes | `READ_PROCESS_DEFINITION` | | `UPDATE` | No | - | | `DELETE` | No | - | | `SUSPEND` | No | - | | `CREATE_INSTANCE` | Yes | `CREATE_PROCESS_INSTANCE` | | `READ_INSTANCE` | Yes | `READ_PROCESS_INSTANCE` | | `UPDATE_INSTANCE` | Yes | `UPDATE_PROCESS_INSTANCE` | | `RETRY_JOB` | No | - | | `SUSPEND_INSTANCE` | No | - | | `DELETE_INSTANCE` | Yes | `DELETE_PROCESS_INSTANCE` | | `MIGRATE_INSTANCE` | No | - | | `READ_TASK` | Yes | `READ_USER_TASK` | | `UPDATE_TASK` | Yes | `UPDATE_USER_TASK` | | `TASK_ASSIGN` | No | - | | `TASK_WORK` | No | - | | `READ_TASK_VARIABLE` | No | - | | `READ_HISTORY` | No | - | | `READ_HISTORY_VARIABLE` | No | - | | `DELETE_HISTORY` | No | - | | `READ_INSTANCE_VARIABLE` | No | - | | `UPDATE_INSTANCE_VARIABLE` | No | - | | `UPDATE_TASK_VARIABLE` | No | - | | `UPDATE_HISTORY` | No | - | | `ALL` | Yes | `CREATE_PROCESS_INSTANCE`, `READ_PROCESS_DEFINITION`, `READ_PROCESS_INSTANCE`, `READ_USER_TASK`, `UPDATE_PROCESS_INSTANCE`, `UPDATE_USER_TASK`, `MODIFY_PROCESS_INSTANCE`, `CANCEL_PROCESS_INSTANCE`, `DELETE_PROCESS_INSTANCE` | ### `Deployment` compatibility As `Deployment` maps to `Resource` in Camunda 8, when migrating a `Deployment` authorization to Camunda 8, one authorization per resource contained in the deployment is created in Camunda 8, with the same permissions as the original `Deployment` authorization. This operates in combination with the prefix principle described for [Process Definition](#process-definition-compatibility) and [Decision Definition](#decision-definition-compatibility), which also applies for forms. In practice, this means that, if a deployment contains process definitions, decision definitions, and forms, for each of these resources two authorizations are created in Camunda 8: one for the original ID and one for the prefixed ID. | C7 Permission | Migration supported | C8 Permission equivalent | | ------------- | ------------------- | ---------------------------------------------------------------------------------- | | `READ` | Yes | `READ` | | `CREATE` | Yes | `CREATE` | | `DELETE` | Yes | `DELETE_RESOURCE`, `DELETE_FORM`, `DELETE_PROCESS`, `DELETE_DRD` | | `ALL` | Yes | `CREATE`, `READ`, `DELETE_DRD`, `DELETE_FORM`, `DELETE_PROCESS`, `DELETE_RESOURCE` | ### `ALL` permissions compatibility :::info As illustrated in the tables above, the `ALL` permission in Camunda 7 maps to **all the existing permissions in Camunda 8** for the given resource type, independently of whether these exist in Camunda 7 or not. This is true for all resource types. ::: ## Executing identity migration ```bash # Run identity migration ./start.sh --identity # List all skipped identity entities ./start.sh --identity --list-skipped # List the Camunda 7 ID and Camunda 8 key for each migrated entity ./start.sh --identity --list-migrated # Retry skipped identity entities ./start.sh --identity --retry-skipped ``` ```bash # Run identity migration start.bat --identity # List all skipped identity entities start.bat --identity --list-skipped # List the Camunda 7 ID and Camunda 8 key for each migrated entity start.bat --identity --list-migrated # Retry skipped identity entities start.bat --identity --retry-skipped ``` :::warning After migration has been completed, it is strongly recommended to verify the results in Camunda 8 before using the system in production. ::: --- ## Data Migrator Use the Data Migrator to copy runtime and audit data from Camunda 7 to Camunda 8. ![data-migration](../../../img/data-migration.png) ## Modes of operation The Data Migrator offers three modes of operation: - [Runtime migration](runtime.md): Migrate running process instances and continue execution in C8. Production-ready with Camunda 8.8. - [History migration](history.md): Copy audit (history) data to Camunda 8. - [Identity migration](identity.md): Migrate identity data to Camunda 8. Migration details are summarized as follows: | What is migrated | What is NOT migrated | | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Running process instances (state-preserving).Process variables and their values.Execution history.Identity data. | BPMN process models (use the [Diagram Converter](/guides/migrating-from-camunda-7/migration-tooling/diagram-converter.md)).Custom code or integrations (use [Code Conversion Utilities](../code-conversion)).Users, groups (use IdP), and authorizations (use [Identity Migration](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/identity.md)).Runtime task assignments and states (due date, priority, etc.). | ## Key features - Preserves the execution state of running process instances during migration. - Maps Camunda 7 process instance `businessKey` to Camunda 8 `businessId`. This mapping is supported for both runtime and history migration. - Converts and migrates process variables, decision inputs, and decision outputs with proper type handling. - Supports customizable variable interceptors for both runtime and history migration contexts. - Validates data before migration to help ensure a successful run. - Allows you to skip or retry problematic instances during migration. - Provides detailed logging and reporting to monitor migration progress. - Supports multiple database vendors, including H2, PostgreSQL, and Oracle. ## Typical choreography of runtime and history migration As described in [the roll-out phase of the migration journey](../../migration-journey.md), you will typically use the following sequence of tasks when applying both data migrations (while keeping downtimes to a minimum): :::warning Configure the Camunda 8 datasource before your first migration If you plan to migrate both runtime and history data, configure the Camunda 8 datasource before running your first migration. If the Camunda 8 datasource is not configured, the migrator creates the migration schema on the Camunda 7 datasource as a fallback. If you add the Camunda 8 datasource later, the migrator creates a new migration schema on Camunda 8, which resets migration tracking and can result in duplicate migrations. ```yaml # Configure this before your first migration camunda.migrator.c8.data-source: jdbc-url: jdbc:postgresql://localhost:5432/camunda8 username: camunda password: camunda ``` ::: 1. Stop the Camunda 7 solution (normally shut down your application). 2. Start the Data Migrator in "running instance migration mode". 3. Wait until running instance migration is completed. 4. Start the new Camunda 8 solution immediately so migrated process instances can continue right away. 5. Start the Data Migrator in "history migration mode". 6. The migrator runs until all history data is migrated while Camunda 8 process execution continues in parallel. With this approach, the duration of history migration doesn't block big bang migrations. ## Customization You might need to customize the data migration, especially if you used complex data formats in Camunda 7 (for example, Java objects) that need to be converted to something Camunda 8 can handle (for example, JSON). As part of this step, you might also need to extract big payloads and binaries (like documents) into an external data store and reference it from the process (using, for example, upcoming document handling possibilities). ## Repository You can track progress and releases in the [migration tooling repository](https://github.com/camunda/camunda-7-to-8-migration-tooling). ## Cockpit plugin (experimental) The [Cockpit plugin](cockpit-plugin.md) provides a web-based interface to view information about skipped and migrated data during the migration process (experimental). --- ## Install and quick start Install the Data Migrator and run your first data migration. ## Prerequisites - Java 21+ - Running Camunda 8 (self-managed, or SaaS for runtime migration only) - Camunda 7 has been stopped - Access to Camunda 7 database - Models migrated and deployed to C8 ## Installation 1. Download the latest release from the GitHub releases page: - https://github.com/camunda/camunda-7-to-8-migration-tooling/releases 2. Extract the archive to your preferred directory. 3. Navigate to the extracted directory. ## Quick start The Data Migrator supports three main modes of operation: - [Runtime migration](runtime.md): Migrate running process instances - [History migration](history.md): Copy audit trail data - [Identity migration](identity.md): Copy authorizations and tenants Below is a quick start guide to get you started. For detailed information on each mode, see their respective documentation pages. ### Quick start steps 1. Make sure Camunda 8 is up and running, all process models to migrate are deployed, and Camunda 7 has been stopped. For runtime migration, every process model requires: - A blank start event (you must add one if the process model doesn't have one already). - An execution listener at the end of your blank start event with the job type `migrator`. You have to add this manually or let the [Diagram Converter](/guides/migrating-from-camunda-7/migration-tooling/diagram-converter.md) add it. ```xml ``` :::note For automatic resource deployment, you can also drop your BPMN files into the `configuration/resources` folder. ::: 2. Drop your JDBC driver into the `configuration/userlib` folder (for example, `postgresql-$VERSION.jar` for PostgreSQL). 3. Prepare your configuration file (`configuration/application.yml`): ```yaml camunda.client: mode: self-managed grpc-address: http://localhost:26500 rest-address: http://localhost:8080 camunda.migrator: auto-ddl: true # Automatically create migration tracking schema camunda.migrator.c7.data-source: jdbc-url: jdbc:postgresql://localhost:5432/camunda7 username: your-username password: your-password camunda.migrator.c8.data-source: jdbc-url: jdbc:postgresql://localhost:5432/camunda8 username: your-username password: your-password ``` 4. Run the Data Migrator: ```bash # Runtime migration (default mode) ./start.sh --runtime # History migration ./start.sh --history # Identity migration ./start.sh --identity # View all available options ./start.sh --help ``` ```bash # Runtime migration (default mode) start.bat --runtime # History migration start.bat --history # Identity migration start.bat --identity # View all available options start.bat --help ``` 5. Monitor the migration progress in the console output and log files. --- ## Limitations An overview of the current limitations of the Camunda 7 to Camunda 8 Data Migrator, covering general limitations as well as specific limitations related to variables and BPMN elements. :::note These limitations apply to moving data from Camunda 7 to Camunda 8. To migrate a running Camunda 8 process instance to a different Camunda 8 process definition, see [process instance migration](/components/concepts/process-instance-migration.md), which has a different set of limitations. Multi-instance elements in particular are treated differently by the two features. ::: ## Identity The following requirements and limitations apply: - Identity migration includes the migration of: - Users, groups, tenants and their associated memberships. - Supported authorizations (detailed in the [Authorizations](identity.md#authorizations) section). - Once migration has been triggered, it's strongly recommended not to create new identity data on Camunda 7. Even if migration is attempted again, the new data might not be migrated. - In order for authorizations to work correctly after migration, process definitions, forms, DRD and decision definitions need to have the same IDs in Camunda 8 as in Camunda 7. This should be the case if you have already migrated runtime and history data. - Tenant memberships are migrated as part of their respective tenants and are not tracked individually. - If a tenant is migrated, all its memberships are migrated as well. If a tenant is skipped, its memberships are also skipped. - If the migration of an individual tenant membership fails (for example, due to a missing user), it cannot be retried. - The `--list-skipped` and `--list-migrated` options do not list individual tenant memberships. - This is also true for groups and group memberships. - For security reasons, passwords for users cannot be migrated. For this reason, users are migrated with a generated secure password that needs to be reset by an administrator after migration. ### Supported entities | Identity type | Migration supported | | ------------------ | ------------------- | | Users | Yes | | Groups | Yes | | Group Memberships | Yes | | Tenants | Yes | | Tenant Memberships | Yes | ## Runtime The runtime migration has the following limitations. ### General limitations - To migrate running process instances, the historic process instance must exist. - You cannot migrate running instances when you have configured history level to `NONE` or a custom history level that doesn't create historic process instances. - The minimum supported history level is `ACTIVITY`. - You must add an execution listener of type `migrator` to all your start events. - Migration of users, groups, or tenants as well as authorizations is currently not supported. - You must ensure that the users, groups, and authorizations are already migrated to Camunda 8 before migrating process instances. - See https://github.com/camunda/camunda-bpm-platform/issues/5175 - Data changed via user operations - Data set via user operations like setting a due date to a user task cannot be migrated currently. - Job and process priority values are not migrated. During migration a new Camunda 8 process instance is created, so it uses the job priorities defined on its Camunda 8 process definition rather than any values from the Camunda 7 instance. - See https://github.com/camunda/camunda-bpm-platform/issues/5182 ### Variables - [Unsupported Camunda 7 types](../variables#unsupported-types). - [Camunda 8 variable name restrictions](/components/concepts/variables.md#variable-values). - Variables that do not follow the restrictions will cause issues in FEEL expression evaluation. - Variables set into the scope of embedded sub-processes are not supported yet and will be ignored. - See https://github.com/camunda/camunda-bpm-platform/issues/5235 :::info To learn more about variable migration, see [variables](../variables). ::: ### Incidents Due to the [limitation regarding async before/after wait states](#async-beforeafter-wait-states), incident data from instances currently waiting due to failed jobs causing active incidents will not be migrated during runtime migration. We recommend to resolve incidents prior to runtime migration. ### BPMN elements Some BPMN elements and configurations supported in Camunda 7 are not supported in Camunda 8 or have specific limitations during migration. Below is an overview of these limitations and recommendations to address them. #### Elements supported in Camunda 7 but not supported in Camunda 8 Some BPMN elements available in Camunda 7 are not supported in Camunda 8. Before migration, compare the BPMN coverage of both platforms and adjust your models accordingly: - [Camunda 7 BPMN coverage](https://docs.camunda.org/manual/7.24/reference/bpmn20/) - [Camunda 8 BPMN coverage](/components/modeler/bpmn/bpmn-coverage.md) When converting diagrams, the Diagram Converter also helps identify unsupported elements interactively. #### Start events - It is required that a process instance contains a single process level None Start Event to run the data migrator. - If a process definition only has event-based start events (for example, Message, Timer), it is required to add a temporary None Start Event. This change must be reverted after the data migration is completed. - Example adding a None Start Event: ```diff Flow_FromEventStartEvent + + Flow_FromNoneStartEvent + + Flow_FromEventStartEvent Flow_FromNoneStartEvent Flow_1o2i34a Flow_1o2i34a ``` #### Async before/after wait states Camunda 8 does not support [asynchronous continuation before or after](https://docs.camunda.org/manual/latest/user-guide/process-engine/transactions-in-processes/#asynchronous-continuations) any kind of wait state. Service-task-like activities are executed asynchronously by default in Camunda 8 - so for example a service task waiting for asynchronous continuation before will be correctly migrated. But if you need to migrate an instance currently waiting asynchronously at other elements in a Camunda 7 model, such as a gateway for example, this instance would just continue without waiting in the equivalent Camunda 8 model. You might need to adjust your model's logic accordingly prior to migration. #### Message events - Only message catch and throw events are supported for migration. - Depending on your implementation, you may need to add [a correlation variable](/components/modeler/bpmn/message-events/message-events.md#messages) to the instance pre-migration. #### Message and signal start events - If your process starts with a message/signal start event, no token exists until the message/signal is received and hence no migration is possible until that moment. - Once the message/signal is received, the token is created and moved down the execution flow and may be waiting at a migratable element inside the process. However, due to how the migration logic is implemented, at the moment the data migrator only supports processes that start with a normal start event. #### Triggered boundary events - Camunda 7 boundary events do not have a natural wait state. - If the process instance to be migrated is currently at a triggered boundary event in Camunda 7, there may still be a job associated with that event, either waiting to be executed or currently running. In this state, the token is considered to be at the element where the job is created: typically the first activity of the boundary event’s handler flow, or technically the point after the boundary event if asyncAfter is used. - During migration to Camunda 8, the token will be mapped to the corresponding target element. However, if that element expects input data that is normally produced by the boundary event’s job (for example, setting variables), this data may be missing in the migrated instance. - Recommendation: To ensure a consistent migration, allow boundary event executions to complete before initiating the migration. #### Call activity To migrate a subprocess that is started from a call activity, the migrator must set the `legacyId` variable for the subprocess. This requires propagating the parent variables. This can be achieved by updating the Camunda 8 call activity in one of the following ways: - Set `propagateAllParentVariables` to `true` (this is the default) in the `zeebe:calledElement` extension element. - Or, if `propagateAllParentVariables` is set to `false`, provide an explicit input mapping: ```xml ``` #### Multi-instance Processes with active multi-instance elements can currently not be migrated. We recommend to finish the execution of any multi-instance elements prior to migration. #### Parallel gateways Process instances with active joining parallel gateways cannot currently be migrated. The migrator will skip these instances during migration. - This limitation occurs when some execution paths have completed and reached the joining parallel gateway, but other paths are still waiting at activities before the gateway. - Recommendation: Ensure no token waits in a joining parallel gateway. - See https://github.com/camunda/camunda-bpm-platform/issues/5461 #### Timer events - Timer start events: prior to migration, you must ensure that your process has at least one [none start event](/components/modeler/bpmn/none-events/none-events.md#none-start-events). Processes that only have a timer start event cannot be migrated. - If your model contains timer events (start and other), you must ensure that no timers fire during the migration process. - Timers with [date](/components/modeler/bpmn/timer-events/timer-events.md#time-date): ensure the date lies outside the migration time frame. - Timers with [durations](/components/modeler/bpmn/timer-events/timer-events.md#time-duration): ensure the duration is significantly longer than the migration time frame. - Timers with [cycles](/components/modeler/bpmn/timer-events/timer-events.md#time-cycle)): ensure the cycle is significantly longer than the migration time frame and/or use a start time that lies outside the migration time frame. - Note that during deployment and/or migration, the timers may be restarted. If business logic requires you to avoid resetting timer cycles/duration, you need to apply a workaround: - Timers with cycles: - Add a start time to your cycle definition that is equal to the moment in time when the currently running Camunda 7 timer is next due. - You must still ensure that the start time lies outside the migration time frame. - Timers with durations: - Non-interrupting timer boundary events: - Switch to cycle definition with a start time that is equal to the moment in time when the currently running Camunda 7 timer is next due and add a "repeat once" configuration. - This way, for the first post migration run, the timer will trigger at the start time. - For all subsequent runs, the defined cycle duration will trigger the timer. The "repeat once" instruction ensures it only fires once, similar to a duration timer. - You must still ensure that the start time lies outside the migration time frame. - Interrupting boundary and intermediate catching events: - Add a variable to your Camunda 7 instance that contains the leftover duration until the next timer is due. - In your Camunda 8 model, adjust the timer duration definition to use an expression: if the variable is set, the value of this variable should be used for the duration. If the variable is not set or does not exist, you may configure a default duration. - This way, for the first post migration run the variable will exist and the timer will set its duration accordingly. - For all subsequent runs, the variable will not exist and the default duration will be used. - Again, you must ensure the leftover duration for the first post migration run lies outside the migration time frame. #### Event subprocesses - Event subprocesses with interrupting start events can cause unexpected behavior during migration if triggered at the wrong moment. This includes timer, message, and signal start events. - What can go wrong: - A task that already ran in Camunda 7 might run again in Camunda 8. - The process might end up in the wrong state after migration — for example, being one step behind what you see in Camunda 7. - When could it happen: - This can occur when a process instance is already inside an event subprocess in Camunda 7, and the start event of that same subprocess is accidentally triggered again in Camunda 8 during migration. - How to prevent it: - Don't correlate messages or send signals during migration. - Temporarily adjust timer start events in event subprocesses to ensure they do not trigger during migration (see the section on timer events for more details). - If above suggestions are not feasible in your use case make sure service tasks are idempotent — so repeating them does not cause issues. ## History The history migration has the following limitations. ### General - All migrated data is assigned `partitionId=1` so that the RDBMS exporter on partition 1 can perform history cleanup for migrated data. - Migrated data has partition ID 4095 encoded in their keys to avoid key collisions with Zeebe produced data. - Due to this, migrated process instances cannot be deleted via C8 API or in Operate since Zeebe cannot delegate the operation to a partition. - See https://github.com/camunda/camunda/issues/47927 - Please use [RDBMS History Cleanup](/self-managed/concepts/databases/relational-db/configuration.md#history-cleanup-1) to delete the migrated data. - The minimum required history level in Camunda 7 is `FULL` to ensure that sufficient data is available for migration. - To avoid collisions with native Camunda 8 definitions, the Data Migrator prefixes each migrated Camunda 7 history definition ID (process, decision, and form definitions) with `c7-legacy-` by default. - (Optional) You can configure a different prefix with the `camunda.migrator.history.legacy-id-prefix` property. For configuration details and validation rules, see the [property reference](config-properties.md#camundamigrator). - The same effective prefix is applied to all migrated history definition types and to the entities that reference them. - Do not deploy new definitions in Camunda 8 with IDs starting with the effective prefix. Changing or removing the default prefix increases the risk of ID collisions, so only change it when you are sure your Camunda 8 definition IDs cannot clash with migrated IDs. - Migrated definitions are visible in Camunda 8 Tasklist but cannot be started. To start new instances, you need to use the [Diagram Converter](../diagram-converter.md) to migrate your legacy processes to Camunda 8 compatible versions and deploy them to Camunda 8 as described in the [preparation step for runtime migration](runtime.md#1-preparation). - See https://github.com/camunda/camunda-7-to-8-migration-tooling/issues/1000 - Avoid manipulating Camunda 7 data in between History Data Migrator runs to ensure data consistency unless there is a specific migration issue to fix (e.g. moving instances out of states that are not migratable). See [Auto-cancellation of active instances](history.md#auto-cancellation-of-active-instances) for details. - During migration, some entities may be skipped due to unresolved dependencies (for example, when a parent entity has not yet been migrated). - After the initial migration completes, the migrator automatically retries skipped entities to resolve cross-entity dependencies. - Automatic retries run in multiple passes until no further progress can be made. - Entities that remain skipped after all automatic retries are logged as warnings, along with their skip reasons. - After fixing underlying issues, you can manually retry remaining skipped entities using the `--retry-skipped` flag. - Examples of temporary skip reasons include: - Flow node instances whose parent flow node (scope) has not yet been migrated. - Child process instances called from parent call activities, where the parent flow node has not yet been migrated. - The History Data Migrator does not support the following Camunda 7 entity types: - **CMMN entities**: CMMN user tasks and CMMN variables are not supported and are skipped during migration. - **Standalone user tasks**: User tasks that are not associated with a process instance are not supported and are skipped during migration. - Camunda 7 does not store audit data of asyncBefore wait state for flow nodes. Migration of flow nodes is executed in all other cases. - The History Data Migrator does not support the following Camunda 8 entities or properties: - Sequence flow: Sequence flows cannot be highlighted in Operate. - Message subscription and correlated message subscription: These entities are not available in Camunda 7. - Batch operation entity and batch operation item: Camunda 7 does not retain sufficient information about processed instances. - User metrics: Not available in Camunda 7. - Exporter position: This entity does not exist in Camunda 7. - Please note that if any Camunda 7 process instances progress in their state in between multiple runs of the History Data Migrator, data consistency might be affected: for example, if a process instance is completed in Camunda 7 after the first run but before the second run, the History Data Migrator would migrate it as canceled in the first and as completed in the second run. As a result, in Operate you may see that a process instance was canceled in a Flow Node that chronologically precedes the end event in your model, where the instance will be marked as completed. To avoid such situations, ensure that Camunda 7 data remains unchanged between History Data Migrator runs. ### DMN The History Data Migrator supports migration of DMN entities, but with the following limitations: - DMN models version 1.1 are not supported by Operate, decision definition data will be migrated but the decision model itself will not be visible in Operate. ### Forms The History Data Migrator supports migration of Camunda Forms, but with the following limitations: - Only [Camunda Forms](https://docs.camunda.org/manual/latest/user-guide/task-forms/#camunda-forms) are migrated. Other form types are not supported: - Embedded forms (HTML/JSF) - External forms (URL-based forms) - Generated forms (from form data definitions) - Supported form bindings: - `deployment` - Form version deployed together with the process definition - `latest` - Latest version of the form definition - `version` - Specific version of the form definition - Unsupported form bindings: - Expression-based bindings (for example, `${formKey}`) ### Jobs The History Data Migrator migrates only jobs of type [asynchronous continuation](https://docs.camunda.org/manual/7.24/user-guide/process-engine/transactions-in-processes/#configure-asynchronous-continuations). - Jobs associated with multi-instance activities are skipped. (https://github.com/camunda/camunda-7-to-8-migration-tooling/issues/1103) - Jobs whose corresponding Camunda 7 historic activity instance was never persisted are skipped. This happens for async-before activities that fail on all available retries before the activity is entered, so no `elementInstanceKey` can be resolved. Camunda 8.10 enforces non-nullability on `elementInstanceKey`; previously these jobs were migrated with a null value. The skip is recorded in the skip log with a reason. ### Incidents The History Data Migrator supports migration of DMN entities, but with the following limitations: - The incidents are migrated in `resolved` state. Operate does not visualize resolved incidents, therefore incidents of migrated process instances will not be visible in Operate. Audit data related to incidents can be observed by querying APIs. - When there's a failing start timer in Camunda 7, the incident cannot be migrated (as there's no process instance history) and will be skipped. - Incidents associated with multi-instance activities are skipped. (https://github.com/camunda/camunda-7-to-8-migration-tooling/issues/1103) - Incidents whose corresponding Camunda 7 historic activity instance was never persisted are skipped. This happens for async-before activities that fail on all available retries before the activity is entered, so no `flowNodeInstanceKey` can be resolved. Camunda 8.10 enforces non-nullability on `flowNodeInstanceKey`; previously these incidents were migrated with a null value. The skip is recorded in the skip log with a reason. ### Audit logs The following limitations apply: - Audit log entries are migrated only for user tasks, process definitions, process instances, variables, decisions, users, groups, and authorizations. - Audit log entries are not migrated for batch operations, identity links, attachments, job definitions, jobs, external tasks, metrics, operation logs, filters, comments, and properties. ### Data coverage For a detailed overview of which Camunda 8 entities and properties are migrated by the History Data Migrator, see the [History migration coverage](history-coverage.md) page. ## Cockpit plugin The [Cockpit plugin](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/cockpit-plugin.md) has the following limitations: - The migration schema has no authorization mechanism. Anyone with authenticated access to the Camunda 7 Cockpit can see the Cockpit Plugin and read the migration schema. - If the migration of a process instance or any other entity is skipped for multiple reasons, only one reason is stored and displayed. - See https://github.com/camunda/camunda-bpm-platform/issues/5389 - For historic data migration the skip reason is currently only stored for the initial migration attempt. If migration fails again after retry, the skip reason is not updated. - See https://github.com/camunda/camunda-bpm-platform/issues/5390 - There are currently some UI inconsistencies. See: - https://github.com/camunda/camunda-bpm-platform/issues/5422 - https://github.com/camunda/camunda-bpm-platform/issues/5423 - https://github.com/camunda/camunda-bpm-platform/issues/5424 - The Cockpit plugin doesn't have extensive test coverage yet so we cannot guarantee a high level of stability and therefore don't claim it to be production-ready. - See https://github.com/camunda/camunda-bpm-platform/issues/5404 --- ## Runtime(Data-migrator) Migrate currently running process instances. ## About runtime migration Running refers to process instances in Camunda 7 that are not yet ended and are currently waiting in a [wait-state](https://docs.camunda.org/manual/latest/user-guide/process-engine/transactions-in-processes/#wait-states). This state is persisted in the database, and a corresponding data entry must be created in Camunda 8 so the process instance can continue from that state in the new solution. During runtime migration, the Data Migrator maps the Camunda 7 process instance `businessKey` to Camunda 8 `businessId`. If no business key is present, the process instance is migrated without a business ID. If the business key is blank, it is migrated as a blank business ID. ## Requirements and limitations The following requirements and limitations apply: - The Runtime Data Migrator needs to access the Camunda 7 database. - The Runtime Data Migrator needs to access Orchestration Cluster APIs (which means you can also use it when running SaaS). - You must be familiar with the Data Migrator [limitations](limitations.md). If you need to adjust your process models before migration, you can use [process version migration](https://docs.camunda.org/manual/latest/user-guide/process-engine/process-instance-migration/) in the Camunda 7 environment to migrate process instances to versions that are migratable to Camunda 8. One useful strategy is to define dedicated migration states where your process instances can accumulate. Another common strategy is to use [process instance modification](https://docs.camunda.org/manual/latest/user-guide/process-engine/process-instance-modification/) in the Camunda 7 environment to move out of states that are not migratable (for example, process instances within a multiple instance task). To use the Runtime Data Migrator, every Camunda 8 process model must have: - A process-level **None Start Event**, and - An execution listener on that **None Start Event** with the type `migrator` (or a validated job type, see below). Example: ```xml ``` ## Choreography The runtime migration typically follows these phases: ### 1. Preparation - Stop Camunda 7 process execution to avoid starting new instances during migration. - Migrate BPMN models using the [Diagram Converter](/guides/migrating-from-camunda-7/migration-tooling/diagram-converter.md). The Diagram Converter automatically adds the required `migrator` execution listener to None Start Events when the **Add Data Migration Execution Listener** option is enabled. - Adjust Camunda 8 models to comply with migration limitations. - Test migrated models in a Camunda 8 environment. - Back up your Camunda 7 database before migration. ### 2. Migration - Deploy Camunda 8 process models and resources to the target environment. - **Do not start new Camunda 8 process instances** on models that still have the `migrator` execution listener. See [Externally Started Process Instances](#externally-started-process-instances) for details. - Configure the migrator with proper database connections and settings. - Start the migrator and monitor progress through logs. - Verify results in Camunda 8 Operate. - Handle skipped instances by reviewing and addressing validation failures. - After successful migration, clean up models: - Remove `migrator` execution listeners from Camunda 8 models. - Revert temporary model changes. - Redeploy the updated models. - Migrate instances to the latest version of Camunda 8 models if appropriate. ### 3. Validation - Check migrated instances in Camunda 8 Operate. - Verify variable data and migrated state. - Test process continuation by completing some migrated instances. - Monitor system performance and resource usage. - Validate business logic continues to work as expected. ## Validation and skip reasons The migrator validates each process instance before migration and will skip instances that fail validation for the following reasons: | Skip reason | Condition (why it is skipped) | | :------------------------------------ | :--------------------------------------------------------------------------------------------------------------- | | Missing Camunda 8 process definition | No corresponding Camunda 8 process definition is found for the Camunda 7 process ID. | | Multi-instance activities | The process instance has active multi-instance activities. | | Missing flow node elements | The Camunda 7 instance is at a flow node that does not exist in the deployed Camunda 8 model. | | Missing None Start Event | The Camunda 8 process definition does not have a process-level None Start Event. | | Missing `migrator` execution listener | The Camunda 8 process definition does not have an execution listener of type `migrator` on the None Start Event. | | Multi-tenancy | The tenant ids are not configured in the Data Migrator. | When a process instance is skipped: - The skipped process instance is logged. - The instance is marked as skipped in the migration database. - You can list skipped instances. - You can retry migration of skipped instances after fixing the underlying issues. ### Common resolution steps 1. Deploy the missing Camunda 8 process definition 1. Wait for multi-instance activities to complete 1. Ensure all active flow nodes in the Camunda 7 process have corresponding elements in the Camunda 8 process 1. Modify process instance to a supported state ## Usage examples ```bash # Run runtime migration ./start.sh --runtime # List all skipped process instances ./start.sh --runtime --list-skipped # List the Camunda 7 ID and Camunda 8 key for each migrated entity ./start.sh --runtime --list-migrated # Retry skipped process instances ./start.sh --runtime --retry-skipped ``` ```bash # Run runtime migration start.bat --runtime # List all skipped process instances start.bat --runtime --list-skipped # List the Camunda 7 ID and Camunda 8 key for each migrated entity start.bat --runtime --list-migrated # Retry skipped process instances start.bat --runtime --retry-skipped ``` ## Job type configuration By default, the job type is configured as `migrator`. The Diagram Converter adds an execution listener with this type to None Start Events, and the Data Migrator activates jobs matching this type. This works out of the box, and no additional configuration is needed. The job type is relevant because during migration, the Data Migrator creates new Camunda 8 process instances with a `legacyId` variable linking them to their original Camunda 7 instances. It then activates all jobs with the execution listener type `migrator`. These jobs are only processed if `legacyId` is present. Process instances started directly on Camunda 8 are skipped. See [Externally Started Process Instances](#externally-started-process-instances) for details. The migrator supports two job type configurations with fallback behavior: - **`job-type`**: Used for actual job activation (default: `migrator`). - It is used for activating jobs in Camunda 8 and is required for the migrator to function correctly. - It must match the execution listener type defined on the start event in the BPMN model. If the BPMN execution listener is an expression that resolves to a type, then `validation-job-type` needs to be configured as well. - **`validation-job-type`**: Used for validation purposes (optional). - Before starting a process instance in Camunda 8, the Data Migrator verifies that the job type is present in the BPMN. This ensures the process instance execution waits for the Data Migrator at the start event. - When `validation-job-type` is not defined, `job-type` is used for both validation and activation. - You can define a FEEL expression that provides different job types based on the process instance context. - It must match the execution listener type defined on the start event in the BPMN model. - Set to `DISABLED` to disable job type validation entirely. This is not recommended. **Basic Configuration (default):** ```yaml camunda.migrator: job-type: migrator # Used for both validation and activation ``` **Advanced: Separate Validation and Activation:** If you need to use a FEEL expression for the execution listener type (for example, to differentiate between migrated and externally started instances), you can configure `validation-job-type` separately: ```yaml camunda.migrator: job-type: migrator # Used for activation validation-job-type: '=if legacyId != null then "migrator" else "noop"' # Used for validation with FEEL expression ``` When using a FEEL expression in `validation-job-type`, you must also specify the same expression in the execution listener of your BPMN process start events: ```xml ``` :::note Use FEEL expressions only for validation, not for job activation, since during job activation, the FEEL expression is already evaluated to a static value. ::: ### Externally Started Process Instances New Camunda 8 process instances should not be started on models that still have the `migrator` execution listener. Follow the recommended [choreography](#choreography): complete the migration first, then remove the execution listener and redeploy before starting new process instances. If a process instance is started externally (not by the Data Migrator) on a model with the `migrator` execution listener, the Data Migrator will activate the job but skip it because the `legacyId` variable is not present. The process instance will remain at the start event. After the job lock times out, the job becomes available for activation again. This does not cause errors or data corruption, but an externally started process instance will not progress until the execution listener is removed and the model is redeployed. When using the advanced FEEL expression configuration `=if legacyId != null then "migrator" else "noop"`, externally started process instances will generate jobs with the type `noop` instead of `migrator`, so the Data Migrator will not activate them at all. However, to allow these process instances to proceed past the start event, you must implement a **noop job worker** that completes these jobs: ```java @JobWorker(type = "noop") public void handleNoopJobs(ActivatedJob job) { // Simply complete the job without any processing // This allows externally started process instances to continue normally } ``` This approach ensures that: - Process instances started by the Data Migrator are handled by the `migrator` job worker. - Externally started process instances continue their normal execution flow through the `noop` job worker. - Both types of instances can coexist in the same Camunda 8 environment. ## Tenants - Camunda 7 process instances assigned to no tenant (`tenantId=null`) are migrated to Camunda 8 with `` tenant. - The default behavior is to migrate only process instances without assigned tenant. - When migrating process instances, the migrator can be configured to handle specific tenants throughout the migration process. Defining tenant IDs ensures that only process instances associated with those tenants are migrated. - Make sure to create the tenants in Camunda 8 before starting the migration. - Add `Authentication configuration for the client` and assign the user to the all of the tenants ### How Multi-Tenancy Works 1. **Validation**: Pre-migration validation ensures target tenant deployments exist in Camunda 8 2. **Tenant Preservation**: Process instances maintain their original tenant association during migration 3. **Job Activation**: The migrator fetches jobs only for the configured tenants and the default tenant when activating jobs ### Example Use the `camunda.migrator.tenant-ids` [property](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/config-properties.md#camundamigrator) to specify which tenants should be included in the migration process. This property accepts a comma-separated list of tenant identifiers. ```yaml camunda: migrator: tenant-ids: tenant-1, tenant-2, tenant-3 ``` With this configuration, only process instances associated with `tenant-1`, `tenant-2`, `tenant-3`, and the default tenant will be created and migrated. Instances associated with other tenants will be skipped. --- ## Troubleshooting(Data-migrator) Troubleshooting information for common issues when running the Data Migrator. ## Migration fails to start - Verify Java 21+: `java -version`. - Check database connectivity and credentials. - Ensure Camunda 8 is running and accessible. - Review your `configuration/application.yml` configuration. - Check that JDBC driver jar is added to the `configuration/userlib/` directory. ## Process instances are skipped - Ensure Camunda 8 process definitions are deployed. - Verify `migrator` execution listeners are added to None Start Events. - Ensure flow nodes exist in both Camunda 7 and Camunda 8 models. - Review skipped instance logs for exact reasons. List and retry skipped instances: ```bash ./start.sh --runtime --list-skipped ./start.sh --runtime --retry-skipped ``` ```bash start.bat --runtime --list-skipped start.bat --runtime --retry-skipped ``` ## Performance issues - Adjust `camunda.migrator.page-size`. - Ensure database resources are sufficient. - Check network latency between components. - Monitor CPU/memory/disk usage. ## Variable migration errors - Check Camunda 8 variable name restrictions. - Verify variable types are supported. - Implement a custom `VariableInterceptor` if needed. ## Debug logging Increase logging levels to get more detail: ```yaml logging: level: root: INFO io.camunda.migration.data: DEBUG io.camunda.migration.data.RuntimeMigrator: TRACE file: name: logs/camunda-7-to-8-data-migrator.log ``` ## Cockpit plugin ### Plugin not visible in Cockpit - Ensure the plugin JAR is placed in the correct Camunda 7 plugins directory. - Check Camunda 7 logs for any plugin loading errors. - Restart Camunda 7 after deploying the plugin. ### No skip data displayed - Confirm `save-skip-reason: true` is set in the migrator configuration. - Verify migration has been run with this setting enabled. - Check database connectivity between the plugin and the migrator database. ## Getting help - Use the [Camunda forum](https://forum.camunda.io/c/c7-to-c8/). - Search existing issues: https://github.com/camunda/camunda-bpm-platform/issues. - When creating a new issue, include: logs, config, environment, steps to reproduce. --- ## Variables(Data-migrator) The Data Migrator automatically handles the transformation of Camunda 7 variables to Camunda 8 compatible formats during migration. Variable transformation is supported for the following migration types: - Runtime migration: process instance variables. - History migration: process variables, decision inputs, and decision outputs. ## About variables This section documents which variable types are supported and how they are transformed. For complete details on Camunda 7 variable types, see the [official Camunda 7 documentation](https://docs.camunda.org/manual/latest/user-guide/process-engine/variables/#supported-variable-values). ## Supported types The following table shows how the migrator handles different Camunda 7 variable types: | Camunda 7 Type | Example Value | Migration Behavior | Camunda 8 Result | Interceptor Type | | ------------------------------ | --------------------- | ----------------------- | ----------------- | ---------------------------------- | | String | `"hello world"` | Direct migration | String value | `StringValue`, `PrimitiveValue` | | Boolean | `true`, `false` | Direct migration | Boolean value | `BooleanValue`, `PrimitiveValue` | | Integer | `42`, `1234` | Direct migration | Number value | `IntegerValue`, `PrimitiveValue` | | Long | `123456789L` | Direct migration | Number value | `LongValue`, `PrimitiveValue` | | Double | `3.14159` | Direct migration | Number value | `DoubleValue`, `PrimitiveValue` | | Short | `(short) 1` | Direct migration | Number value | `ShortValue`, `PrimitiveValue` | | Null | `null` | Direct migration | Null value | `NullValueImpl` | | Date | `new Date()` | Converted to ISO format | String (ISO 8601) | `DateValue`, `PrimitiveValue` | | Java Object serialized as JSON | Serialized JSON | Converted to Map | JSON object | `ObjectValue`, `SerializableValue` | | Spin JSON | `SpinJsonNode` | Converted to Map | JSON object | `SpinValue`, `SerializableValue` | | Spin XML | `SpinXmlElement` | Converted to String | String (raw XML) | `SpinValue`, `SerializableValue` | | Java Object serialized as XML | XML serialized object | Converted to String | String (raw XML) | `ObjectValue`, `SerializableValue` | ## Unsupported types When a process instance contains unsupported variable types, the migrator will: - Skip the entire process instance - Log a detailed error message indicating the variable type that caused the skip - Mark the instance as skipped for potential retry after manual intervention The following Camunda 7 variable types are **not supported** and will cause the process instance migration to be skipped: | Camunda 7 Type | Example | Interceptor Type | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------- | | Byte Array | `"hello".getBytes()` | `BytesValue`, `PrimitiveValue` | | File | `FileValue` objects | `FileValue` | | Java Serialized Objects | Java objects serialized as `application/x-java-serialized-object` like `List`, `Set`, `Map`, `float`, `byte`, `char` or custom types | `ObjectValue`, `SerializableValue` | ## Transformation Variable transformations are handled by built-in transformers that run in a specific execution order. Validators run first (Order: 1-3) to reject unsupported types, followed by transformers (Order: 10-20) that convert supported types. ### Date - Input: Java `Date` objects from Camunda 7 - Output: ISO 8601 formatted strings (`yyyy-MM-dd'T'HH:mm:ss.SSSZ`) - Example: `2024-07-25T14:30:45.123+0200` - Timezone: Uses the JVM's default timezone setting ### JSON JSON variables are handled differently depending on their origin and migration mode: Spin JSON Variables and JSON Object Variables (serialized with `application/json`): - Runtime migration: - Deserializes JSON into Map structures for Camunda 8. - Preserves the nested object structure. - Example: `{"name": "John", "age": 30}` becomes a Map object. - History migration: - Preserves JSON values as raw strings. - Example: `{"name": "John", "age": 30}` is stored as-is. **Invalid JSON**: If JSON cannot be parsed during runtime migration, the migrator skips the process instance. ### XML Spin XML Variables and XML Object Variables (serialized with `application/xml`): - Raw XML string content is preserved - No parsing or transformation applied ### Name compatibility The migrator handles variable names that are invalid in FEEL expressions: - Names starting with numbers (e.g., `1stVariable`) - Names with spaces (e.g., `my variable`) - Names with special characters (e.g., `var/name`, `var-name`) - Reserved keywords (e.g., `null`) These variables are migrated as-is, but may require special handling in FEEL expressions using bracket notation. ## Disabling built-in interceptors You can disable any built-in transformer or validator using the `enabled` configuration property: ```yaml camunda: migrator: # Variable interceptor plugins configuration interceptors: # Disable date transformation - class-name: io.camunda.migration.data.impl.interceptor.DateVariableTransformer enabled: false ``` You can find a complete list of built-in interceptors in the [property reference](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/config-properties.md#built-in-interceptors). ## Custom transformation The `VariableInterceptor` interface allows you to define custom logic that executes whenever a variable is accessed or modified during migration. This is useful for auditing, transforming, or validating variable values. Custom interceptors are enabled by default and can be restricted to specific variable types. ### How to implement a `VariableInterceptor` 1. Create a new Maven project with the provided `pom.xml` structure 2. Add a dependency on `camunda-7-to-8-data-migrator-core` (scope: `provided`) 3. Implement the `VariableInterceptor` interface 4. Add setter methods for any configurable properties 5. Package as JAR and deploy to the `configuration/userlib` folder 6. Configure in `configuration/application.yml` ### Creating a custom variable interceptor Here's an example of a custom variable interceptor which is only called for string variables: ```java public class MyVariableInterceptor implements VariableInterceptor { /** * Restrict this interceptor to only handle string variables. */ @Override public Set> getTypes() { return Set.of(StringValue.class); } @Override public void execute(VariableContext context) { // Access the variable name String name = context.getName(); // Get the Camunda 7 value Object c7Value = context.getC7Value(); // Get the Camunda 7 entity VariableInstanceEntity variableInstance = context.getEntity(); // Transform and set the Camunda 8 value context.setC8Value(transformedValue); } } ``` ### Type restrictions Variable interceptors can be restricted to specific variable types using the `getTypes()` method. You can find a complete list of available variable types for restriction as subinterfaces of the `TypedValue` interface in the [JavaDoc](https://docs.camunda.org/javadoc/camunda-bpm-platform/7.24/org/camunda/bpm/engine/variable/value/TypedValue.html#:~:text=All%20Known%20Subinterfaces%3A). ```java @Override public Set> getTypes() { // Handle only specific types return Set.of( BooleanValue.class, // Boolean values IntegerValue.class, // Integer values LongValue.class, // Long values DoubleValue.class, // Double values ShortValue.class, // Short values StringValue.class, // String values PrimitiveValue.class, // String, Integer, Boolean, etc. DateValue.class, // Date variables ObjectValue.class // JSON, XML, Java serialized objects ); } ``` ```java // Or handle all types (default behavior) @Override public Set> getTypes() { return Set.of(); // Empty set = handle all types } ``` ### Entity type filtering In addition to filtering by variable type, interceptors can also filter by the source entity type. This is useful when you want different behavior for history/runtime process variables and decision inputs/outputs. #### Available entity types - `VariableInstanceEntity.class`: runtime process variables - `HistoricVariableInstanceEntity.class`: historic process variables - `HistoricDecisionInputInstanceEntity.class`: decision input variables - `HistoricDecisionOutputInstanceEntity.class`: decision output variables #### Example: process variables only ```java public class ProcessVariableInterceptor implements VariableInterceptor { @Override public Set> getEntityTypes() { // Only handle process variables (runtime and history) return Set.of( VariableInstanceEntity.class, HistoricVariableInstanceEntity.class ); } @Override public void execute(VariableContext context) { // This is only called for process variables // not for decision inputs/outputs } } ``` #### Example: decision variables only ```java public class DecisionVariableInterceptor implements VariableInterceptor { @Override public Set> getEntityTypes() { // Only handle decision inputs and outputs return Set.of( HistoricDecisionInputInstanceEntity.class, HistoricDecisionOutputInstanceEntity.class ); } @Override public void execute(VariableContext context) { // This is only called for decision variables } } ``` #### Default behavior ```java // Empty set = handle all entity types @Override public Set> getEntityTypes() { return Set.of(); } ``` ### Detecting migration context Variable interceptors can detect whether they are running in a runtime or history migration context: ```java @Override public void execute(VariableContext context) { if (context.isRuntime()) { // Handle runtime migration // Example: Deserialize JSON to Map } else if (context.isHistory()) { // Handle history migration // Example: Keep JSON as string } } ``` ### Configuring custom interceptors Configure your custom interceptors in `application.yml`: ```yaml # Variable interceptor plugins configuration # These plugins can be packaged in JARs and dropped in the userlib folder camunda: migrator: interceptors: - class-name: com.example.migrator.AuditVariableInterceptor enabled: true properties: prefix: "CUSTOM_PREFIX_" enableLogging: true ``` ### Deployment 1. Package your custom interceptor as a JAR file 2. Place the JAR in the `configuration/userlib/` folder 3. Configure the interceptor in `configuration/application.yml` 4. Restart the Data Migrator The `enabled` property is supported for all interceptors (both built-in and custom) and defaults to `true`. See [example interceptor](https://github.com/camunda/camunda-7-to-8-migration-tooling/tree/main/data-migrator/examples/variable-interceptor). ### Execution order - Custom interceptors configured in the `application.yml` are executed in their order of appearance from top to bottom - Built-in interceptors run first, followed by custom interceptors - In a Spring Boot environment, you can register interceptors as beans and change their execution order with the `@Order` annotation (lower values run first) ### Error handling When variable transformation fails: #### Runtime migration - The migrator skips the entire process instance. - Logs detailed error messages with the variable name and cause. - Marks the instance for potential retry after you fix the underlying issue. #### History migration - Skips only the affected variables, decision inputs, or outputs. - Continues migrating the parent entity. - Logs skipped items with detailed error information. #### Example commands ```bash # List all skipped variables ./start.sh --history --list-skipped HISTORY_VARIABLE # List all skipped decision instances ./start.sh --history --list-skipped HISTORY_DECISION_INSTANCE # List the Camunda 7 ID and Camunda 8 key for each migrated variable ./start.sh --history --list-migrated HISTORY_VARIABLE # Retry skipped entities after fixing issues ./start.sh --history --retry-skipped ``` ```bash # List all skipped variables start.bat --history --list-skipped HISTORY_VARIABLE # List all skipped decision instances start.bat --history --list-skipped HISTORY_DECISION_INSTANCE # Retry skipped entities after fixing issues start.bat --history --retry-skipped ``` --- ## Diagram Converter With **Diagram Converter**, you'll get an initial understanding of the migration tasks you'll need to perform when moving from Camunda 7 to Camunda 8. It analyzes Camunda 7 BPMN, DMN, and Camunda 7 form definition files (`.form`) and generates a list of tasks required for the migration. In a second step, it can also convert these files from the Camunda 7 format to the Camunda 8 format. For example, it updates namespaces, renames XML properties, and updates form metadata, if needed. All BPMN elements supported by Camunda 8 can be transformed. For the full list see the [BPMN coverage page](../../../components/modeler/bpmn/bpmn-coverage.md). :::tip Automate diagram conversion with AI Use the [Camunda migration agent skill](./index.md#agentic-migration) to run the Diagram Converter CLI as part of an end-to-end migration workflow, resolve conversion findings with AI, and focus on reviewing migration-ready results. ::: You can use the Diagram Converter in the following ways: - **Web Interface**: A wizard-like UI built with Java (Spring Boot) and React. Available versions: - Java JAR - Docker - Free, hosted SaaS - **CLI**: A command-line interface implemented in Java. The results are available as: - **XLSX**: A Microsoft Excel file, including pre-built pivot tables for data exploration. - **CSV**: A plain-text comma-separated file, compatible with any spreadsheet tool. - **JSON**: A flat, machine-readable report for AI assistants and other automation. ## Install the Diagram Converter ### Hosted Diagram Converter Use the hosted Diagram Converter at [https://diagram-converter.camunda.io/](https://diagram-converter.camunda.io/). This option requires no local setup and is suitable for quick evaluations or one-off migrations. Your models are not stored on this platform, and all processing happens in-memory. Your data is transmitted securely over HTTPS. :::note The hosted version has a limit on the number of files that can be processed in a single batch request. If you need to convert a larger number of files, use the [local web application](#local-web-application) or the [CLI](#use-the-cli). ::: ### Local web application #### Prerequisites - Java 21 or later #### Steps 1. Download the latest `camunda-7-to-8-diagram-converter-webapp-{version}.jar` from [GitHub Releases](https://github.com/camunda/camunda-7-to-8-migration-tooling/releases). 2. Run the application: ```shell java -jar camunda-7-to-8-diagram-converter-webapp-{version}.jar ``` 3. Access the web application at [http://localhost:8080/](http://localhost:8080/). To run the application on a different port, for example `8090`: ```shell java -jar camunda-7-to-8-diagram-converter-webapp-{version}.jar --server.port=8090 ``` To increase the maximum number of files allowed per batch request (default is 100), configure `server.tomcat.max-part-count`: ```shell java -jar camunda-7-to-8-diagram-converter-webapp-{version}.jar --server.tomcat.max-part-count=200 ``` ### CLI installation #### Prerequisites - Java 21 or later #### Steps 1. Download the latest `camunda-7-to-8-diagram-converter-cli-{version}.jar` from [GitHub Releases](https://github.com/camunda/camunda-7-to-8-migration-tooling/releases). 2. Verify the installation: ```shell java -jar camunda-7-to-8-diagram-converter-cli-{version}.jar --help ``` ## Analyze your diagrams using the web interface Open the Diagram Converter: - For a local installation, open [http://localhost:8080/](http://localhost:8080/). - For the hosted SaaS version, open [https://diagram-converter.camunda.io/](https://diagram-converter.camunda.io/). Upload one or more BPMN, DMN, or `.form` files, then configure the conversion target: ![Upload your diagrams](../../img/analyzer-screenshot-1.png) In **Configure conversion**, select the target Camunda 8 version. The default is the latest stable version, and you can select other supported versions to estimate migration impact for that target runtime. If needed, expand **Advanced options** to fine-tune conversion behavior before starting the run. Click **Analyze and convert to Camunda 8.x**. Review the results: ![See results](../../img/analyzer-screenshot-2.png) On this screen you can: - See the total number of findings for the selected target version - Review findings per file, and open a preview for BPMN, DMN, or form files - Download converted files individually, or download all converted files as a ZIP - Download the analyzer results as a Microsoft Excel file (XLSX) - Download the analyzer results as a CSV file - Download the analyzer results as a JSON file for AI-assisted migration tooling Analysis results contain a list of items where each row represents an action item required for migrating your solution to Camunda 8. Findings are calculated for the selected target Camunda 8 version and grouped by severity: - **INFO**: No action needed. Diagram conversion can successfully map attributes to the Camunda 8 implementation. - **REVIEW**: The conversion will modify some expressions or attributes. Please verify that the intended functionality remains unchanged. - **WARNING**: A Camunda 7 concept cannot be directly mapped to a Camunda 8 equivalent. Consider reviewing the Camunda 8 roadmap or exploring possible workarounds. - **TASK**: Manual changes are required to make the diagram work in Camunda 8. This allows you to focus on the most important findings. Tasks can also be grouped by type. For example, changing a `JavaDelegate` to a `JobWorker` might appear 100 times in your codebase, but still represents just one recurring pattern. Pivot tables can help you identify tasks that appear multiple times across different files, providing a comprehensive overview of migration efforts. Next, you'll learn how to use those results. ### Download JSON analysis results Download the analysis results as a JSON file to use them with AI-assisted migration tools or other automation. - In the web interface, click **Download JSON**. - In the CLI, add `--json` to your command. ### Analyze results in Microsoft Excel ![The MS Excel result](../../img/analyzer-result-excel.png) The XLSX file includes three tabs: - **AnalysisSummary**: Pivot tables and charts that summarize typical migration tasks. - **PivotTable**: A large pivot table for dynamic data exploration. - **AnalysisResults**: The raw data from the analysis, which you can copy, import, or process further. You can open the file using Microsoft Excel (desktop or Office 365). ### Analyze results in Google Sheets or LibreOffice You can also open the XLSX file in Google Sheets, LibreOffice, OpenOffice, or similar tools. The raw data will be imported correctly, but pivot tables won't be preserved. Alternatively, download the results as a CSV file, and import them directly into your preferred tool. In this case, either: - Create your own pivot table in the tool. - Copy the contents of the **AnalysisResults** tab into your own spreadsheet. For Google Sheets, consider using this [Google Spreadsheet template](https://docs.google.com/spreadsheets/d/1ZUxGhj1twgTnXadbopw1CvZg_ZvDnB2VXRQDSrKtmcM/edit?gid=6013418#gid=6013418) created by Camunda consultants. ![The Google Sheet](../../img/analyzer-screenshot.png) ## Use the CLI If you prefer the command line, use the CLI for batch processing or automation. The CLI supports two modes: - **local**: Analyze and convert diagrams from your file system - **engine**: Analyze and convert diagrams directly from a running Camunda 7 process engine ### Local mode The local CLI accepts a file or directory. When you provide a directory, it scans the directory and its subdirectories for `.bpmn`, `.bpmn20.xml`, `.dmn`, `.dmn11.xml`, and `.form` files by default, then processes every supported file it finds (use `-nr, --not-recursive` to disable recursion). ```shell java -jar camunda-7-to-8-diagram-converter-cli-{version}.jar local myDiagram.bpmn --json --xlsx ``` To process all BPMN, DMN, and form files in a directory and its subdirectories: ```shell java -jar camunda-7-to-8-diagram-converter-cli-{version}.jar local ./my-processes/ ``` ```shell java -jar camunda-7-to-8-diagram-converter-cli-{version}.jar local .\my-processes\ ``` Key options for `local` mode: | Option | Description | | -------------------- | ------------------------------------------------------------- | | `--platform-version` | Semantic version of the target platform (defaults to latest) | | `--csv` | Create a CSV file with analysis results | | `--json` | Create a JSON file with analysis results | | `--xlsx` | Create an XLSX file with analysis results | | `--prefix` | Prefix for the generated file name (default: `converted-c8-`) | | `-o, --override` | Override existing files | To see all available options: ```shell java -jar camunda-7-to-8-diagram-converter-cli-{version}.jar local --help ```
Local mode parameter reference | Parameter | Description | | ------------------------------------------------------ | ------------------------------------------------------------------------------- | | `` | File to convert or directory to scan for diagrams and forms | | `--add-data-migration-execution-listener` | Add an execution listener on blank start events for the Camunda 7 Data Migrator | | `--always-use-default-job-type` | Always use the configured default job type | | `--check` | Analyze only, without exporting converted diagrams | | `--csv` | Create a CSV file with analysis results | | `--json` | Create a JSON file with analysis results | | `-d, --documentation` | Also append messages to diagram documentation | | `--data-migration-execution-listener-job-type=` | Override the listener job type from `converter-properties.properties` | | `--default-job-type=` | Override the default job type from `converter-properties.properties` | | `--disable-append-elements` | Disable appending conversion messages to BPMN or DMN XML | | `-h, --help` | Show help and exit | | `--keep-job-type-blank` | Keep job types blank so you can set them manually after conversion | | `--md, --markdown` | Create a Markdown results file | | `-nr, --not-recursive` | Do not scan subdirectories recursively | | `-o, --override` | Override existing files | | `--platform-version=` | Set target Camunda 8 semantic version | | `--prefix=` | Prefix for generated file names (default: `converted-c8-`) | | `-V, --version` | Print version information and exit | | `--xlsx` | Create an XLSX file with analysis results |
### Engine mode Use engine mode to process diagrams directly from a running Camunda 7 engine via its REST API: ```shell java -jar camunda-7-to-8-diagram-converter-cli-{version}.jar engine http://localhost:8080/engine-rest --json --xlsx ``` Key options for `engine` mode: | Option | Description | | ------------------------ | -------------------------------------------------------------- | | `--platform-version` | Semantic version of the target platform (defaults to latest) | | `-u, --username` | Username for Basic authentication | | `-p, --password` | Password for Basic authentication | | `-t, --target-directory` | Directory to save the .bpmn files (default: current directory) | | `--csv` | Create a CSV file with analysis results | | `--json` | Create a JSON file with analysis results | | `--xlsx` | Create an XLSX file with analysis results | To see all available options: ```shell java -jar camunda-7-to-8-diagram-converter-cli-{version}.jar engine --help ```
Engine mode parameter reference | Parameter | Description | | ------------------------------------------------------ | ------------------------------------------------------------------------------------- | | `` | Fully qualified Camunda 7 REST API URL (default: `http://localhost:8080/engine-rest`) | | `--add-data-migration-execution-listener` | Add an execution listener on blank start events for the Camunda 7 Data Migrator | | `--always-use-default-job-type` | Always use the configured default job type | | `--check` | Analyze only, without exporting converted diagrams | | `--csv` | Create a CSV file with analysis results | | `--json` | Create a JSON file with analysis results | | `-d, --documentation` | Also append messages to diagram documentation | | `--data-migration-execution-listener-job-type=` | Override the listener job type from `converter-properties.properties` | | `--default-job-type=` | Override the default job type from `converter-properties.properties` | | `--disable-append-elements` | Disable appending conversion messages to BPMN or DMN XML | | `-h, --help` | Show help and exit | | `--keep-job-type-blank` | Keep job types blank so you can set them manually after conversion | | `--md, --markdown` | Create a Markdown results file | | `-o, --override` | Override existing files | | `-p, --password` | Password for Basic authentication | | `--platform-version=` | Set target Camunda 8 semantic version | | `--prefix=` | Prefix for generated file names (default: `converted-c8-`) | | `-t, --target-directory=` | Directory to save converted `.bpmn` files | | `-u, --username=` | Username for Basic authentication | | `-V, --version` | Print version information and exit | | `--xlsx` | Create an XLSX file with analysis results |
## Convert your diagrams As mentioned, the Diagram Converter can also convert BPMN and DMN diagrams for use with Camunda 8. This includes: - Updating namespaces - Adjusting XML structure and properties - Transforming expressions Converted files can be downloaded via the web interface or generated via the CLI. ## Convert Camunda 7 forms The Diagram Converter supports Camunda 7 form definition files (`.form`). You can upload forms through the web interface or include them in a local CLI conversion to update them for Camunda 8. The web interface provides a form preview, and the converter reports items that need review. For generated task forms, use the [Camunda migration agent skill](./index.md#agentic-migration), which creates or adapts a standard Camunda 8 form during the agentic migration flow. ## Extend the conversion logic You can extend the conversion logic by implementing custom visitors and conversions using the Java Service Provider Interface (SPI). This lets you: - Add custom conversion rules for proprietary extensions - Modify how specific BPMN elements are transformed - Add custom analysis messages For implementation details and examples, see the [extension example on GitHub](https://github.com/camunda/camunda-7-to-8-migration-tooling/tree/main/diagram-converter/extension-example). ## Convert expressions JUEL expressions used in Camunda 7 aren't supported in Camunda 8. The Diagram Converter tries to [convert simple expressions, automatically](https://github.com/camunda/camunda-7-to-8-migration-tooling/blob/8a9a37/diagram-converter/core/src/main/java/io/camunda/migration/diagram/converter/expression/ExpressionTransformer.java). For an overview of what’s supported, see the [ExpressionTransformer test case](https://github.com/camunda/camunda-7-to-8-migration-tooling/blob/8a9a37/diagram-converter/core/src/test/java/io/camunda/migration/diagram/converter/ExpressionTransformerTest.java). You may have to manually rewrite more complex expressions. The [FEEL Copilot](https://feel-copilot.camunda.com/) can help with this. You can also customize or extend the transformer logic as needed. --- ## Migration tools Camunda is invested in supporting and easing your migration from Camunda 7 to Camunda 8 with migration tools. You can use them in two ways: - **[Agentic migration](#agentic-migration)** (recommended): An AI coding agent uses the Diagram Converter for models and forms, lets you select an AI-first or recipe-assisted Java migration path, and migrates your tests to Camunda Process Test (CPT). - **[Manual migration](#manual-migration)**: Run the individual tools yourself for full control or to handle specific migration tasks independently. All tools are available as **ready-to-use builds** from the [GitHub releases page](https://github.com/camunda/camunda-7-to-8-migration-tooling/releases). ## Compare agentic and manual migration This table shows which migration tasks agentic migration covers, compared with the individual tools that you run yourself in a [manual migration](#manual-migration). Agentic migration also runs these tools, such as the Diagram Converter, where they apply. | Task | Agentic migration | Manual migration | | :------------------------------------------------------------------------------- | :---------------: | :--------------: | | Inventory Java code and tests, and estimate effort | Yes | No | | Analyze and convert BPMN, DMN, and Camunda 7 forms | Yes | Yes | | Create Camunda 8 forms from generated task forms | Yes | No | | Create a first diff for supported Java transformations | Yes | Yes | | Complete the migration of Java client code, delegates, and external task workers | Yes | No | | Migrate process and decision tests to CPT | Yes | No | | Validate the migration and compare test results with Camunda 7 | Yes | No | | Migrate runtime instances, history, and identity data | No | Yes | | Produce the same result for the same input | No | Yes | | Run without an AI coding agent | No | Yes | ## Agentic migration The **Camunda migration agent skill** is an AI-driven orchestrator that uses the Diagram Converter CLI as the default for BPMN, DMN, and static Camunda 7 form conversion. After it inventories your Java code, you select either an AI-first, pattern-guided path or an optional recipe-assisted path. It also [migrates your process tests](#migrate-process-tests) to CPT. You can run the skill with an AI coding agent such as Claude Code or GitHub Copilot CLI, or publish it to your organization as an [AWS Transform](https://docs.aws.amazon.com/transform/latest/userguide/custom.html) custom transformation. The setup and run command differ by agent, but the migration flow is the same. ### Set up and run Set up your agent, then run the skill from your Camunda 7 project directory. Every agent runs the same [agent workflow](#agent-workflow). Install the skill: ```bash claude plugin marketplace add camunda/camunda-7-to-8-migration-tooling claude plugin install camunda-migration ``` Run it: ```text /camunda-migration:migrate-c7-to-c8-code ``` Install the skill: ```bash copilot plugin marketplace add camunda/camunda-7-to-8-migration-tooling copilot plugin install camunda-migration@camunda-migration ``` Run it: ```text /camunda-migration:migrate-c7-to-c8-code ``` Use GitHub CLI 2.90 or later to install the skill: ```bash gh skill install camunda/camunda-7-to-8-migration-tooling migrate-c7-to-c8-code --agent ``` Replace `` with the name of your agent. See the [agent-specific installation commands](https://github.com/camunda/camunda-7-to-8-migration-tooling/blob/main/agentic-migration-skills/README.md#install-commands-for-other-agents) for supported values. For manual installation paths, see the [Agentic Migration Skills README](https://github.com/camunda/camunda-7-to-8-migration-tooling/blob/main/agentic-migration-skills/README.md#manual-installation). Then run the `migrate-c7-to-c8-code` skill from your project directory using your agent's command. [AWS Transform](https://docs.aws.amazon.com/transform/latest/userguide/custom.html), Amazon's agentic modernization service, runs the same skill as a custom transformation. Instead of installing the skill per developer, you publish it once to your organization's registry and run it with the [`atx` CLI](https://docs.aws.amazon.com/transform/latest/userguide/custom-get-started.html) (Node.js 22 or later, with configured AWS credentials). The source and test projects must be Git repositories with at least one commit. Check out the tooling and create the transformation from the skill: ```bash git clone https://github.com/camunda/camunda-7-to-8-migration-tooling.git cd camunda-7-to-8-migration-tooling/agentic-migration-skills # Save a private draft to validate first (drafts expire after 30 days) atx custom def save-draft -n "camunda-7-to-camunda-8-migration" \ --description "Migrate your Camunda 7 project to Camunda 8" \ --sd skills/migrate-c7-to-c8-code/ # Run the draft against a test project using the version ID returned above. cd /path/to/test-camunda-7-project atx custom def exec -n "camunda-7-to-camunda-8-migration" \ --tv \ -p . \ -c "" # Publish the tested draft to your organization for anyone with the required IAM permissions cd /path/to/camunda-7-to-8-migration-tooling/agentic-migration-skills atx custom def publish -n "camunda-7-to-camunda-8-migration" \ --tv ``` Replace `` with the command for your project, such as `mvn verify` for Maven or `./gradlew build` for Gradle. After you validate the draft, run the published transformation from the project directory. Running by name uses the latest published version, so you don't pass a version ID: ```bash atx custom def exec -n "camunda-7-to-camunda-8-migration" -p . -c "" ``` The skill asks for your migration scope: | Scope | What the skill does | | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | | **Code + models** _(recommended, default)_ | Uses the Diagram Converter CLI as the default for BPMN, DMN, and Camunda 7 forms, then lets you select a Java migration path after inventory. | | **Code only** | Inventories Java code, then lets you select an AI-first or recipe-assisted path. | | **Models only** | Uses the Diagram Converter CLI as the default for BPMN, DMN, and Camunda 7 forms. | | **Assessment only** | Inventories files and estimates effort without changes. | ### Select a Java migration path After the inventory, select the path that fits your codebase and review capacity: | Path | Use when | | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | **AI-first, pattern-guided** (preferred starting point) | You have a capable coding model and can review source-to-output mappings and behavior. | | **Recipe-assisted** (optional) | You have repeated, supported, primarily syntactic transformations, or need a deterministic first diff. | Run both paths on representative Java code before you use one across a broad migration. A recipe-assisted path can add scaffolding, generated names, TODOs, and cleanup. Neither path guarantees lower token use, cost, or migration time. See [Code Conversion](./code-conversion.md#choose-your-migration-approach) for detailed selection guidance. ### Migrate process tests The skill migrates Camunda 7 tests to [Camunda Process Test](/apis-tools/testing/getting-started.md) (CPT) in the **Code + models** and **Code only** scopes. A test is in scope when it runs a BPMN process or a DMN decision on a Camunda 7 engine and uses a framework or approach that existed for Camunda 7. Test migration needs Camunda 8.9 or later. With target 8.8, the skill lists the tests as **Report only**. | Camunda 7 tests | What the skill does | | :--------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------- | | Engine test support (`ProcessEngineRule`, `ProcessEngineExtension`, `ProcessEngineTestCase`, `@Deployment`) and camunda-bpm-assert | Migrates to CPT | | Spring Boot and Spring process tests | Migrates to CPT | | DMN decision tests (`DmnEngineRule`, `DecisionService`) | Migrates to CPT | | Scenario tests (camunda-platform-scenario) | Migrates to CPT | | Tests that drive a dedicated Camunda 7 engine through REST or the external task client | Migrates to CPT | | Tests that use a shared Camunda 7 engine | Reports for manual migration, because CPT deletes runtime data between tests | | BDD layers such as Cucumber and JGiven, Arquillian, CDI, and Quarkus extension tests | Reports for manual migration | | CMMN tests and tests that depend on engine internals | Reports for manual redesign | The skill also migrates the mocks in these tests, such as camunda-platform-7-mockito and `Mocks` registrations, and by default mocks the same components in CPT. The **Test Inventory** in `MIGRATION_REPORT.md` lists each test with its handling. Tests that the skill doesn't migrate appear as **Report only**, with the reason. Unit tests that don't run an engine, such as delegate or worker unit tests, aren't part of test migration. The skill updates them as ordinary code. If at least one test can be migrated, the skill asks after the inventory whether to run the tests: | Option | What the skill does | | :---------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Run tests** _(recommended)_ | Runs the Camunda 7 tests before it changes any code, test, or model, and records the results as a baseline. Migrates the tests, runs the CPT tests, and compares the results with the baseline. | | **Migrate tests only** | Migrates and compiles the tests, but runs no tests. `MIGRATION_REPORT.md` marks the tests as not verified, reports `NOT READY`, and lists the steps to verify them later. | With **Run tests**, the skill migrates and freezes the tests before it migrates the production code. The validation gate in `MIGRATION_REPORT.md` reports `NOT READY` unless these safeguards hold: - Each Camunda 7 test that passed maps to a CPT test that passes, or you approve its removal. - A frozen test changes only with your approval. - A new mock replaces behavior that ran for real on Camunda 7 only with your approval. - Two CPT runs give the same results. - The CPT tests cover at least the process elements that the Camunda 7 tests covered, if your project measured Camunda 7 coverage. The **Test Parity** table in `MIGRATION_REPORT.md` lists each Camunda 7 test with its result and the results of its CPT tests. CPT starts the Camunda 8 runtime in Docker by default, so running the tests needs Docker or a [remote runtime](/apis-tools/testing/configuration.md#remote-runtime). CPT deletes runtime data between tests, so don't point a remote runtime at a shared or production cluster. ### Agent workflow 1. **Assess migration scope**: Inventories BPMN/DMN diagrams, Camunda 7 `.form` files, Java code files, and Camunda 7 tests, and estimates effort. If the skill can migrate tests, it asks whether to [run them](#migrate-process-tests). 2. **Convert models and forms**: Runs the Diagram Converter CLI as the default. Review converted models and `REVIEW`, `WARNING`, and `TASK` findings. Do not use AI-only model conversion as the default because model capability can materially affect output and silently change model semantics. 3. **Select and migrate code**: After inventory, select an AI-first, pattern-guided path or an optional recipe-assisted path. AI-first migration reads source code and patterns directly. Recipe-assisted migration produces a deterministic first diff for repeated, supported, primarily syntactic transformations, then needs AI or manual cleanup. The skill also migrates the in-scope tests to CPT. 4. **Validate migration results**: Compiles, runs tests, searches for remaining C7 references, verifies converted forms and model findings, and reviews source-to-output mappings and behavior. With **Run tests**, it compares the CPT test results with the Camunda 7 baseline in the **Test Parity** table. With **Migrate tests only**, it compiles the tests but runs no test suite. 5. **Fix remaining issues**: Offers to fix remaining issues, and waits for your review before each change. The agent also handles static and generated Camunda 7 forms by creating or adapting standard Camunda 8 forms and linking them from the converted BPMN models. Unsupported validation rules and ambiguous behavior are flagged for review. The agent can use the flat `analysis-results.json` report generated by the CLI's `--json` option or the web interface's **Download JSON** action. It groups findings by category, checks the target platform version, and cross-references model findings with migrated code before suggesting fixes. See [Download JSON analysis results](./diagram-converter.md#download-json-analysis-results). Before it uses AI for Java changes or model findings, the agent checks whether the active model is suitable for complex reasoning. Select a capable model, but review remains mandatory because model quality materially affects the output. If it finds no local BPMN or DMN models, the agent can use the Diagram Converter's engine mode to retrieve the latest definitions from an accessible Camunda 7 REST endpoint. It asks for the endpoint and authentication details and does not request engine access when local models are available. The agent preserves the original model files, avoids using stale reports or overwriting existing output, and records findings and decisions in `MIGRATION_REPORT.md`. It asks for confirmation before adding deployment configuration for the converted resources. ## Manual migration Camunda provides the following tools for manual migration: | Migration tool | Description | GitHub link | | :---------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | **[Diagram Converter](./diagram-converter.md)** | Analyze and convert BPMN, DMN, and Camunda 7 form files. Available for local installation (Java or Docker) or [hosted as a free SaaS offering](https://diagram-converter.camunda.io/). | [Migration Tooling: Diagram Converter](https://github.com/camunda/camunda-7-to-8-migration-tooling/tree/main/diagram-converter) | | **[Data Migrator](./data-migrator/)** | Copies Camunda 7 runtime instances and history (audit log) to Camunda 8. | [Migration Tooling: Data Migrator](https://github.com/camunda/camunda-7-to-8-migration-tooling/tree/main/data-migrator) | | **[Code Conversion Utilities](./code-conversion.md)** | Mixture of code mapping tables, code conversion patterns, and automatable refactoring recipes. | [Migration Tooling: Code Conversion](https://github.com/camunda/camunda-7-to-8-migration-tooling/tree/main/code-conversion) | ## Examples | Example | Description | GitHub link | | :--------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | **[Simple end-to-end example](https://github.com/camunda-community-hub/camunda-7-to-8-migration-example)** | Shows all tools in action for a simple Spring Boot Java solution. | [Camunda 7 to 8 migration example](https://github.com/camunda-community-hub/camunda-7-to-8-migration-example) | --- ## Update guide In this guide, you'll learn about the breaking changes and migration steps to consider when you upgrade Migration Tooling. :::note Migration Tooling follows [semantic versioning](https://semver.org/). ::: ## Prerequisites Before you update: 1. Check the [version compatibility matrix](version-compatibility.md) to confirm your Migration Tooling version is compatible with your Camunda 7 and Camunda 8 versions. 2. Apply database schema updates for the new version, if required. Check the release notes for details. 3. Review the [Migration Tooling release notes](https://github.com/camunda/camunda-7-to-8-migration-tooling/releases) for your target version. ## Breaking changes by version ### Version 0.3.x to 0.3.3 **Release date:** TBD \ **Camunda 8 compatibility:** 8.9.x #### Data Migrator: History nullability fixes This patch fixes cases where the History Data Migrator wrote `null` values for fields that Camunda 8.10 enforces as non-nullable. Without these fixes, data migrated with the History Data Migrator can fail during migration from Camunda 8.9 to 8.10. The fixes change migration behavior in two ways: - **Some rows are now skipped instead of migrated.** Jobs and incidents whose corresponding Camunda 7 historic activity instance was never persisted (async-before activities that fail on all available retries before entry) were previously migrated with a null `elementInstanceKey` / `flowNodeInstanceKey`. These rows are now skipped and recorded in the skip log with a reason. See [Jobs](data-migrator/limitations.md#jobs) and [Incidents](data-migrator/limitations.md#incidents-1) limitations. - **Some previously-null fields now carry a value.** Job `worker`, incident `errorMessage`, and audit log `entityKey` are now populated, using a placeholder (for example, `C7_MIGRATED` or `C7_NO_MESSAGE`) if the value would have been `null` otherwise. Job `lastUpdateTime` is now populated from the most recent Camunda 7 historic log entry. See the [History migration coverage](data-migrator/history-coverage.md) page for per-field details. :::note If you already migrated history data with 0.3.0, re-running the migrator after this update applies the new behavior only to newly migrated rows. Rows already written with `null` values are not retroactively corrected. ::: ### Version 0.2.x to 0.3.0 **Release date:** 14th of April 2026 \ **Camunda 8 compatibility:** 8.9.x #### Data Migrator: Database schema update Run the Data Migrator with `camunda.migrator.auto-ddl: true` configuration to update the schema. #### Data Migrator: Entity Interceptor API changes The `EntityInterceptor` interface now provides compile-time type safety using Java generics, eliminating the need for manual casting. ##### What changed ```java // 0.2.x public class ProcessInstanceEnricher implements EntityInterceptor { @Override public void execute(EntityConversionContext context) { // Manual casting required HistoricProcessInstance entity = (HistoricProcessInstance) context.getC7Entity(); ProcessInstanceDbModel.ProcessInstanceDbModelBuilder builder = (ProcessInstanceDbModel.ProcessInstanceDbModelBuilder) context.getC8DbModelBuilder(); // Custom logic builder.processDefinitionId(entity.getProcessDefinitionKey()); } } // 0.3.x public class ProcessInstanceEnricher implements EntityInterceptor { @Override public void execute(HistoricProcessInstance entity, ProcessInstanceDbModel.ProcessInstanceDbModelBuilder builder) { // No casting needed! Type-safe access builder.processDefinitionId(entity.getProcessDefinitionKey()); } } ``` #### Data Migrator: Automatic Camunda 8 datasource selection When the Camunda 8 datasource is configured, the migrator now creates and uses the migration schema on the Camunda 8 database automatically. Manual selection is no longer required. ##### What changed - Removed: `camunda.migrator.data-source` - New behavior: The migration schema is created on the Camunda 8 database when `camunda.migrator.c8.data-source` is configured ##### Impact on existing migrations :::warning Risk of duplicate migrations If you ran a migration in 0.2.x without configuring `camunda.migrator.data-source: C8`, upgrading to 0.3.0 creates a new migration schema on the Camunda 8 database. This resets migration tracking and can result in duplicate data migration. ::: ##### Migration steps 1. **If your Camunda 7 and Camunda 8 datasources point to the same database in 0.2.x:** - Remove `camunda.migrator.data-source` from your configuration. - No further action is required. The migration schema is already accessible from both datasources. 2. **If you explicitly configured `camunda.migrator.data-source: C8` in 0.2.x:** - Remove `camunda.migrator.data-source` from your configuration. - No further action is required. The migration schema is already on the Camunda 8 database. 3. **If you used the default configuration in 0.2.x (migration schema on the Camunda 7 database):** - Option A (recommended): Copy the migration schema from the Camunda 7 database to the Camunda 8 database before upgrading. - Option B: Start over without existing migration tracking. - This can result in duplicate migrations. - Before upgrading, you can use `--drop-schema` to remove the migration schema from the Camunda 7 database (for example, to reclaim disk space). 4. **If you have not started a migration yet:** - Configure the Camunda 8 datasource: ```yaml camunda.migrator.c8.data-source: jdbc-url: jdbc:postgresql://localhost:5432/camunda8 username: camunda password: camunda ``` :::note See [history atomicity](data-migrator/history.md#atomicity) for more details. ::: #### Data Migrator: New `StringVariableTransformer` - **New transformer**: `StringVariableTransformer` now handles string variables specifically - **Modified transformer**: `PrimitiveVariableTransformer` no longer handles string variables, only: - `BooleanValue` - `IntegerValue` - `LongValue` - `DoubleValue` - `ShortValue` **If you need to disable the new string transformer:** ```yaml camunda: migrator: interceptors: - class-name: io.camunda.migration.data.impl.interceptor.StringVariableTransformer enabled: false ``` :::note Further reading See the [Variables documentation](data-migrator/variables.md#supported-types) for the complete list of variable types and their interceptors. ::: ### Version 0.1.x to 0.2.0 **Release date:** 17/12/2025 \ **Camunda 8 compatibility:** 8.8.x #### Data Migrator: Package name changes The package name of the Data Migrator has changed from `io.camunda.migrator` to `io.camunda.migration.data`. Make sure you update your custom interceptors and `application.yml` configuration. **Migration steps** Change your import statements: ```java // 0.1.x // 0.2.0 ``` Change your `application.yml` configuration for built-in interceptors: ```yaml # 0.1.x camunda.migrator.interceptors: - class-name: io.camunda.migrator.impl.interceptor.DateVariableTransformer enabled: false # 0.2.0 camunda.migrator.interceptors: - class-name: io.camunda.migration.data.impl.interceptor.DateVariableTransformer enabled: false ``` #### Data Migrator: Variable interceptor API changes This release updates the variable interceptor API to support history migration and improve context awareness. `VariableInvocation` was renamed to `VariableContext`: ```java // 0.1.x public void execute(VariableInvocation invocation) { VariableInstanceEntity variable = invocation.getC7Variable(); String processInstanceId = variable.getProcessInstanceId(); invocation.setVariableValue(transformedValue); } // 0.2.0 public void execute(VariableContext context) { VariableInstanceEntity variable = (VariableInstanceEntity) context.getEntity(); String processInstanceId = variable.getProcessInstanceId(); String name = context.getName(); Object value = context.getC7Value(); context.setC8Value(transformedValue); } ``` `MigrationVariableDto` class was removed: - Use `VariableContext` methods directly - `getName()` and `getC8Value()`/`setC8Value()` replace DTO access **Migration steps** 1. Update method signatures from `VariableInvocation` to `VariableContext`. 2. Replace `invocation.getC7Variable()` with `context.getC7Value()` or `context.getC7TypedValue()`. 3. Replace `invocation.getMigrationVariable().getName()` with `context.getName()`. 4. Replace `invocation.setVariableValue()` with `context.setC8Value()`. 5. (Optional) Add entity type filtering, using `getEntityTypes()`. 6. (Optional) Add runtime/history context detection, using `context.isRuntime()` or `context.isHistory()`. :::note Further reading See the [Variables documentation](data-migrator/variables.md) for more information. ::: #### Code Conversion: Repository and package changes Code Conversion is now officially supported by Camunda. The code repository and package names have changed. - Repository location - **Old**: `camunda-community-hub/camunda-7-to-8-code-conversion` - **New**: `camunda/camunda-7-to-8-migration-tooling` - Package names - **Old**: `org.camunda.migration.rewrite.*` - **New**: `io.camunda.migration.code.*` - Maven module names - **Old**: `org.camunda.community:camunda-7-to-8-rewrite-recipes` - **New**: `io.camunda:camunda-7-to-8-code-conversion-recipes` - Documentation location - **Old**: `https://camunda-community-hub.github.io/camunda-7-to-8-code-conversion/` - **New**: `https://camunda.github.io/camunda-7-to-8-migration-tooling/` All artifacts now use the `io.camunda` groupId instead of `org.camunda.community`. **Migration steps** If you're using OpenRewrite recipes in your project, update your dependencies: ```xml org.camunda.community camunda-7-to-8-rewrite-recipes io.camunda camunda-7-to-8-code-conversion-recipes 0.2.0 ``` Change all imports: ```java // OLD // NEW ``` #### Diagram Converter: Repository and module changes Diagram Converter is now officially supported by Camunda. The code repository location and module names have changed. - Repository location - **Old**: `camunda-community-hub/camunda-7-to-8-migration-analyzer` - **New**: `camunda/camunda-7-to-8-migration-tooling` - Maven module names - **Core Module**: `io.camunda:camunda-7-to-8-diagram-converter-core` - **Web Application**: `io.camunda:camunda-7-to-8-diagram-converter-webapp` - **CLI**: `io.camunda:camunda-7-to-8-diagram-converter-cli` All artifacts now use the `io.camunda` groupId. **Migration steps** If you're embedding the Diagram Converter as a library, update your dependencies: ```xml org.camunda.community.migration camunda-7-to-8-migration-analyzer-core io.camunda camunda-7-to-8-diagram-converter-core 0.2.0 ``` For the web application or CLI, download the [latest releases](https://github.com/camunda/camunda-7-to-8-migration-tooling/releases). --- ## Version compatibility Each version of the Migration Tooling is bound to a specific version of Camunda 7 and 8. Camunda only recommends and supports these combinations: | Migration Tooling Version | Camunda 7 Version | Camunda 8 Version | Support Status | | :------------------------ | :---------------- | :---------------- | :----------------------- | | `0.1.x` \* | `7.24.x` | `8.8.x` | 🔴 **End of Life (EOL)** | | `0.2.x` | `7.24.x` | `8.8.x` | ✅ **Supported** | | `0.3.x` | `7.24.x` | `8.9.x` | ✅ **Supported** | | `8.10.x` | `7.24.x` | `8.10.x` | ✅ **Supported** | \* Data Migrator only. Code Conversion and Diagram Converter were added to the Migration Tooling in version 0.2.0. --- ## 8.10 Release announcements | Minor release date | End of standard maintenance | Release notes | Upgrade guides | | ------------------ | --------------------------- | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | 13 October 2026 | 11 April 2028 | [8.10 release notes](/reference/announcements-release-notes/8100/8100-release-notes.md) | [8.10 upgrade guides](/reference/announcements-release-notes/8100/whats-new-in-810.md#upgrade-guides) | :::info 8.10 resources - See [release notes](/reference/announcements-release-notes/8100/8100-release-notes.md) to learn more about new features and enhancements. - Refer to the [quality board](https://github.com/orgs/camunda/projects/187/views/23) for an overview of known bugs by component and severity. ::: ## Supported environments Change #### Amazon Aurora PostgreSQL 14 removed, 18 added Camunda 8.10 drops support for Amazon Aurora PostgreSQL 14 and adds support for version 18. Supported versions are now 15, 16, 17, and 18. - Aurora PostgreSQL 14 has reached the end of standard support on AWS. - Migrate your Aurora cluster to a supported version before moving to Camunda 8.10. [RDBMS version support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) Change #### Elasticsearch 9.2 and 9.3 no longer supported Camunda 8.10 raises the minimum supported Elasticsearch 9.x version to 9.4. Supported Elasticsearch versions are now 8.19+ and 9.4+. - Upgrade Elasticsearch 9.2 or 9.3 clusters to 9.4 or later before moving to Camunda 8.10. [Supported environments](/reference/supported-environments.md) Change #### H2 2.3 no longer supported Camunda 8.10 drops support for H2 2.3. Only H2 2.4 is now supported. - The bundled H2 driver in Camunda images is on the 2.4 line. - H2 remains supported for development, testing, and evaluation only. Production use is not recommended. [RDBMS version support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) Change #### Microsoft SQL Server 2019 no longer supported Camunda 8.10 drops support for Microsoft SQL Server 2019. Supported versions are now 2022 and 2025. - SQL Server 2019 has reached the end of mainstream support from Microsoft. - Upgrade your SQL Server instance to a supported version before moving to Camunda 8.10. [RDBMS version support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) Change #### OpenSearch 3.4 and 3.5 no longer supported Camunda 8.10 raises the minimum supported OpenSearch 3.x version to 3.6. Supported OpenSearch versions are now 2.19+ and 3.6+. - Upgrade OpenSearch 3.4 or 3.5 clusters to 3.6 or later before moving to Camunda 8.10. [Supported environments](/reference/supported-environments.md) Change #### Oracle 23ai rebranded as Oracle 26ai Oracle has rebranded Oracle Database 23ai as Oracle AI Database 26ai, effective with the October 2025 Release Update (RU 23.26). The internal version continues to use the 23.x code line; the transition requires no database upgrade or application recertification. Camunda 8.10's supported Oracle versions are 19c and 26ai. [RDBMS version support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) Change #### PostgreSQL 14 no longer supported Camunda 8.10 drops support for PostgreSQL 14. Supported versions are now 15, 16, 17, and 18. - PostgreSQL 14 reached the end of its standard support window. - Upgrade your PostgreSQL instance to a supported version before moving to Camunda 8.10. [RDBMS version support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) New #### New GCP region Camunda 8.10 adds support for the Montréal, North America (`northamerica-northeast1`) region in Camunda 8 SaaS. [Supported GCP regions](/components/saas/regions.md#google-cloud-platform-gcp-regions) New #### New AWS region Camunda 8.10 adds support for the Ireland, Europe (`eu-west-1`) region in Camunda 8 SaaS. [Supported AWS regions](/components/saas/regions.md#amazon-web-services-aws-regions) New #### MariaDB 12.3 now supported Camunda 8.10 adds support for MariaDB 12.3 LTS. Supported versions are now 10.11, 11.4, 11.8, and 12.3. [RDBMS version support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) New #### MySQL 9.7 now supported Camunda 8.10 adds support for MySQL 9.7 LTS. Supported versions are now 8.4 and 9.7. [RDBMS version support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) ## Key changes ### 8.10.x patch releases The following key changes were also released as part of an 8.10.x patch release or a Camunda 8 SaaS generation update. | Patch release | Artifact | Type | Key change | | :--------------------------------------------------------------- | :------- | :--------- | :---------------------------------------------------------------------------------------------------------------------------------------- | | [8.10.7](https://github.com/camunda/camunda/releases/tag/8.10.7) | Core | Regression | [Batch cancellation filtered by parent process instance cancels all root process instances](#batch-cancel-parent-process-instance-filter) | ## Agentic orchestration Breaking change #### AI Agent connectors: new element templates replace the legacy templates {#ai-agent-connectors-redesigned-templates-legacy-templates-deprecated} Camunda 8.10 introduces new (`v2`) element templates for the AI Agent Task and AI Agent Sub-process connectors, and deprecates the legacy templates. The new templates run on new job types and use each provider's native SDK. They provide: - Reasoning and extended thinking configuration, and reasoning content kept in the conversation history. - Prompt caching configuration, where the provider supports it. - Decoupled providers and backends, for example Anthropic through AWS Bedrock Mantle or Microsoft Foundry, OpenAI through Microsoft Foundry, and Google Gemini through the Gemini API or Vertex AI. - A custom chat model provider option, for Self-Managed and hybrid deployments. - Support for [agent visibility and monitoring](/reference/announcements-release-notes/8100/8100-release-notes.md#real-time-agent-visibility-and-monitoring) and [agent tool configuration](/reference/announcements-release-notes/8100/8100-release-notes.md#improved-agent-tool-configuration). The legacy templates keep working and don't receive new provider capabilities. Process definitions you deployed earlier keep running on the legacy job workers until you deploy a new version that uses a new template. **Action:** - To use the new capabilities, migrate each AI Agent element to the new template. This is a manual, per-element change: apply the new template, re-enter the model provider configuration, and, for the AI Agent Task, update the tools sub-process. See the [upgrade guide](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-upgrade.md) and the [model providers](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-model-providers.md) page. - If you override the AI Agent job types in a hybrid setup, set the new `CONNECTOR_AI_AGENT_TASK_TYPE` and `CONNECTOR_AI_AGENT_SUBPROCESS_TYPE` variables. See [job type overrides](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-upgrade.md#job-type-overrides). - If you modeled an agent element before Camunda 8.10 and stay on the legacy templates, update it to the latest legacy template version (v1, version 13) or migrate it to a new template. Agent visibility and monitoring and agent tool configuration require it. To update, open the process in Modeler, select the agent element, click **Update element template** in the properties panel, and redeploy the process. Breaking change #### AI Agent connector: SPI and package changes for custom extensions {#ai-agent-connector-spi-and-package-changes} Camunda 8.10 changes the Java APIs that [custom AI Agent extensions](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-customization.md) build on: - The [conversation storage SPI](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-customization.md#custom-conversation-storage) is redesigned. Built-in stores (in-process, Camunda Document, AWS AgentCore) are migrated transparently. - The chat model SPI (`ChatModelFactory` and `ChatModel`) replaces the LangChain4j based model factory. - The message and tool model classes move from `io.camunda.connector.agenticai.model` to `io.camunda.connector.agenticai.aiagent.model`. Only custom implementations are affected. The built-in providers and stores need no changes. **Action:** If you maintain a custom `ConversationStore`, a custom chat model factory, or code that uses the message and tool model classes, update it to the new APIs. See the [breaking changes for custom extensions](https://github.com/camunda/connectors/blob/main/connectors/agentic-ai/docs/breaking-changes.md) for the changes and replacements. ## APIs & tools :::info 8.10 APIs & Tools migration guide Migrate your API integrations, SDKs, and generated clients to Camunda 8.10 using the [8.10 APIs & Tools migration guide](/apis-tools/migration-manuals/migrate-to-810.md). ::: :::tip Client and API compatibility Camunda clients (Java client, Spring SDK, Node.js SDK) and Camunda Process Test are **forward-compatible** with the Orchestration Cluster, meaning you can upgrade the cluster and clients independently. For example, you can run a client on 8.8 against a cluster on 8.10, see [Client and API compatibility](/reference/public-api.md#client-and-api-compatibility). ::: Breaking change #### `POST /v2/message-subscriptions/search` now returns start event subscriptions Starting with 8.10, the `POST /v2/message-subscriptions/search` endpoint returns both start event and intermediate event message subscriptions. Previously, only intermediate event subscriptions were returned. A new `messageSubscriptionType` enum field is included in each result. Existing (legacy) data has `NULL` for this field. **Action:** If your integration expects the endpoint to return only intermediate event subscriptions, add the following filter to restore the previous behavior: ```json { "filter": { "messageSubscriptionType": { "$neq": "START_EVENT" } } } ``` [8.10 APIs & Tools migration guide](/apis-tools/migration-manuals/migrate-to-810.md#message-subscription-type) Breaking change #### `GET /decision-instances/{decisionEvaluationInstanceKey}` now validates the key format The [Get decision instance](/apis-tools/orchestration-cluster-api-rest/specifications/get-decision-instance.api.mdx) endpoint previously returned `404 Not Found` when the `decisionEvaluationInstanceKey` path parameter contained invalid characters that did not match the required pattern `^[0-9]+-[0-9]+$`. The endpoint now correctly returns `400 Bad Request` in this case, while `404 Not Found` is reserved for well-formed keys that do not exist. **Action:** Update any client code or error handling that relied on receiving `404 Not Found` for malformed keys to also handle `400 Bad Request`. Breaking change #### `JobIntent.COMPLETED` follow-up event no longer carries variables by default Starting with 8.10, the `JobIntent.COMPLETED` follow-up event is emitted without variables by default. This prevents `ExceededBatchRecordSizeException` when a job completes with very large variables. Without this setting, the `JobIntent.COMPLETE` command could be rejected and the job could time out. **Action:** If your exporter or integration reads completion variables from the `JobIntent.COMPLETED` event, read them instead from the `JobIntent.COMPLETE` command record or the follow-up `ProcessEvent.TRIGGERING` event, both of which always carry the variables. To restore the pre-8.10 behavior where `JobIntent.COMPLETED` events carry variables, set `camunda.processing.engine.job.include-variables-in-job-completed-event` to `true`. Breaking change #### Optimize `GET /api/readyz` no longer rejects requests that carry an `Authorization` header Starting with Camunda 8.10.0-alpha5, the Optimize [health readiness endpoint](/apis-tools/optimize-api/health-readiness.md) (`GET /api/readyz`) ignores an `Authorization` header instead of rejecting the request. Previously, a request that included the header was rejected with a client error status code. It now returns the readiness status (`200` or `503`), as it does for a request without the header. This aligns the endpoint with the other public endpoints of the Orchestration Cluster, which also accept and ignore a superfluous `Authorization` header. **Action:** No action is required for Kubernetes readiness and liveness probes, as these do not send an `Authorization` header. If you have a client or monitoring check that relies on the endpoint rejecting requests that carry an `Authorization` header, update it to expect the readiness status instead. Breaking change #### Removal of legacy APIs, Tasklist V1-dependent features, and Zeebe Process Test Starting with Camunda 8.10.0-alpha2, Camunda removes the legacy component APIs and related features that were deprecated in 8.8. The following items are removed: - The [Operate API (8.9 documentation)](/versioned_docs/version-8.9/apis-tools/operate-api/overview.md) - The [Tasklist API (8.9 documentation)](/versioned_docs/version-8.9/apis-tools/tasklist-api-rest/tasklist-api-rest-overview.md) and Tasklist V1 mode - Tasklist V1-dependent features such as [user task access restrictions (8.9 documentation)](/versioned_docs/version-8.9/components/tasklist/user-task-access-restrictions.md) and [public start forms](/components/tasklist/userguide/starting-processes.md#public-start-forms) - [Zeebe Process Test](/apis-tools/testing/zeebe-process-test.md) **Action:** Migrate integrations and testing workflows to the current replacements: - Use the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) instead of the removed Operate API and Tasklist API. - Use [user task authorization](/components/tasklist/user-task-authorization.md) and [authorization-based access control](/components/concepts/access-control/authorizations.md) instead of user task access restrictions. - Use authenticated Tasklist starts or build your own application with [Camunda Forms](/components/hub/workspace/modeler/modeling/utilize-forms.md) and the Orchestration Cluster REST API instead of public start forms. - Use [Camunda Process Test](/apis-tools/testing/getting-started.md) instead of Zeebe Process Test. [Migrate to the Orchestration Cluster REST API](/apis-tools/migration-manuals/migrate-to-camunda-api.md) [Migrate from Zeebe Process Test](/apis-tools/migration-manuals/migrate-to-camunda-process-test.md) [Migrate to Camunda user tasks](/apis-tools/migration-manuals/migrate-to-camunda-user-tasks.md) Deprecated #### Console SM and Web Modeler APIs deprecated With Camunda 8.10, the Console Self-Managed API and the Web Modeler API are deprecated in favor of the new [public Camunda Hub API](/reference/announcements-release-notes/8100/8100-release-notes.md#public-camunda-hub-api). The legacy endpoints remain available for at least two minor versions and are scheduled for removal in 8.12. **Action:** Plan to migrate integrations from the Console Self-Managed and Web Modeler APIs to the public Camunda Hub API before 8.12. Deprecated #### `key` sort field on the Tenant search endpoint deprecated The `key` sort field on the [Search tenants](/apis-tools/orchestration-cluster-api-rest/specifications/search-tenants.api.mdx) endpoint (`POST /v2/tenants/search`) is now deprecated. Sorting by this internal numeric identifier is inconsistent with other Identity entities (User, Group, and Mapping Rule), which do not expose key-based sorting, and tenants can no longer be filtered by `key` either. **Action:** Sort by `name` or `tenantId` instead. Change #### Deleting a process definition with running instances defers history deletion The delete resource endpoint now accepts process definition deletion when the definition still has running instances. Instead of rejecting the request or waiting for physical removal, the definition [drains](/components/concepts/resource-deletion.md#draining): new instances are blocked immediately, running instances continue to completion, and the definition is removed automatically afterwards. As a result, when `deleteHistory` is `true`, the `batchOperation` field in the response is `null` for such a definition. Its history is removed as part of the draining lifecycle rather than through an immediately-returned batch operation. The field is still populated for decision requirements definitions and for process definitions that are already fully deleted. **Action:** If you read `batchOperation` from the delete response to track history deletion, handle a `null` value: the definition is draining. Track progress through the process definition `state` (`DRAINING`) or the `zeebe_process_definitions_draining_count` metric instead. [Resource deletion](/components/concepts/resource-deletion.md#draining) [8.10 APIs & Tools migration guide](/apis-tools/migration-manuals/migrate-to-810.md#delete-draining) Change #### Camunda Spring Boot Starter now bundles Spring Boot 4.1.x Starting with Camunda 8.10, the default [Camunda Spring Boot Starter](/apis-tools/camunda-spring-boot-starter/getting-started.md) (`camunda-spring-boot-starter` & `camunda-spring-boot-4-starter`) is bundled with Spring Boot 4.1.x (up from 4.0.x in 8.9). **Action:** Migrate your application to Spring Boot 4.1.x. See the [version compatibility table](/apis-tools/camunda-spring-boot-starter/getting-started.md#version-compatibility) for details. Breaking change #### Broker ping configuration renamed and now requires authentication {#broker-ping-hub} The broker ping configuration namespace is renamed from `camunda.console.ping` to `camunda.hub.ping`, and the corresponding `CAMUNDA_CONSOLE_PING_*` environment variables become `CAMUNDA_HUB_PING_*`. The `POST /clusters` endpoint the ping targets now also requires an OAuth 2.0 machine-to-machine token. A cluster upgraded without these changes stops reporting license information to Camunda Hub. **Action:** Rename the configuration namespace and environment variables, then add a `camunda.hub.ping.credentials` block with your OAuth 2.0 client credentials. See the [property mapping table](/self-managed/upgrade/components/890-to-8100.md#broker-ping-configuration) in the upgrade guide. [Camunda Hub ping configuration](/self-managed/components/orchestration-cluster/zeebe/configuration/broker.md#camunda-hub-ping-configuration) ## Connectors Breaking change #### Connector secret filter now defaults to STRICT {#connector-secret-filter-strict-default} Starting with 8.10.0, the connector [secret filter](/self-managed/components/connectors/connectors-configuration.md#secret-filter) defaults to `STRICT` instead of `DISABLED`. In practice, this means a secret in a connector field only resolves at runtime if that same secret was already referenced in that same field at modeling time, in the deployed BPMN. **Action:** Before upgrading, confirm that every connector field which resolves a secret already references that secret in the deployed BPMN. If a field relies on resolving a secret it doesn't reference, add the reference. To temporarily unblock connector jobs while you update the model, you can set `camunda.connector.secret-resolver.secret-filter.mode` to `DISABLED`, but this restores the affected behavior described in [Notice 61](/reference/notices.md#notice-61). Return to `STRICT` after updating the model. `LAX` doesn't help here — it only changes behavior when the process definition can't be retrieved, not when a field simply doesn't declare the secret. [Secret filter](/self-managed/components/connectors/connectors-configuration.md#secret-filter) Breaking change #### Outbound connectors refuse undeclared intrinsic function calls {#connector-intrinsic-function-allow-list} Starting with Connectors 8.10.0, the connector runtime runs an [intrinsic function](/components/connectors/advanced-topics/intrinsic-functions.md#declare-calls) only if the deployed BPMN process declares that exact call, by function name and field path, in the input mappings of the connector element. The same change is available in the 8.7.28, 8.8.22, and 8.9.13 patch releases. In practice, an intrinsic function call that reaches a connector through a process variable or job variable, instead of being written in the connector element's input mapping, now fails the job. A connector result that contains an object with the `camunda.function.type` key also fails the job when you store it in a result variable or map it with a result expression. **Action:** Before upgrading, check which processes use intrinsic functions. - Write each `camunda.function.type` call as a literal in the input mapping of the field that uses it, and redeploy the process. - Use a result expression that maps only the fields you need if a response from an external system can contain the `camunda.function.type` key. - Don't use `camunda.connector.intrinsic-function.allow-list.mode` set to `DISABLED` as a workaround. This mode refuses every intrinsic function call. No setting restores unrestricted execution. [Intrinsic function allow-list](/self-managed/components/connectors/connectors-configuration.md#intrinsic-function-allow-list) [Troubleshoot intrinsic function errors](/components/connectors/advanced-topics/intrinsic-functions.md#troubleshoot-intrinsic-function-errors) Breaking change #### JWT-authorized inbound webhooks require issuer, audience, and expiration claims Starting with Camunda 8.10, inbound webhooks configured with JWT authorization validate the token's `iss` and `aud` claims and reject tokens without an `exp` claim. The **Issuer** and **Audience** fields are now required in the element templates for the Webhook connector, Amazon EventBridge inbound connector, and A2A Client webhook. Existing JWT-authorized inbound webhooks modeled with earlier template versions don't contain these fields and can't activate after the upgrade. The connector runtime reports the affected connector as **DOWN**. **Action:** Update each affected element to the latest template version, set **Issuer** and **Audience** to the expected claim values, and redeploy the process. Ensure callers provide JWTs with matching `iss` and `aud` claims and a valid `exp` claim. [Webhook connector authorization](/components/connectors/protocol/http-webhook.md#make-your-http-webhook-connector-executable) Breaking change #### Webhook `responseBodyExpression` rejected at deployment {#webhook-response-body-expression} Starting with 8.10, deploying a webhook connector that uses the deprecated `responseBodyExpression` property fails with a validation error. This property was superseded by `responseExpression` in 8.6 and removed from element templates at that time. The connector runtime reports the connector as **DOWN**, and the validation error is included in the connector's status message. **Action:** Replace `responseBodyExpression` with `responseExpression` in your BPMN diagrams before deploying to 8.10. Unlike `responseBodyExpression`, which set only the response body, `responseExpression` returns a full HTTP response: ```json ={ "body": {"myCustomKey": request.body.myDataKey1}, "statusCode": 201, "headers": {"Content-Type": "application/json"} } ``` [Response expression](/components/connectors/protocol/http-webhook.md#response-expression) Change #### Connectors with a single operation are renamed after the operation Connectors that provide a single operation are renamed in Modeler so their name describes the action they perform instead of the product they connect to. For example, the **REST Outbound Connector** is now named **Send REST Request**. Connectors with several operations keep their names and expose their operations as searchable entries instead. Only the name shown in Modeler changed. Template IDs, versions, connector types, and runtime behavior are unchanged, so existing process models continue to run and do not need to be remodeled or redeployed. **Action:** Search for the new name when you add one of these connectors to a process, and update your own documentation, templates, and training material that refer to the previous names. **Renamed connectors:** | Previous name | New name | | :---------------------------------------------- | :------------------------------------------------- | | Amazon EventBridge Outbound Connector | Send Event to AWS EventBridge | | Amazon SNS Outbound connector | Publish Message to AWS SNS | | Amazon SQS Outbound Connector | Send Message to AWS SQS | | AWS Bedrock AgentCore Runtime | Invoke Agent in AWS Bedrock AgentCore Runtime | | AWS Bedrock Code Interpreter Outbound Connector | Run Code with AWS Bedrock Code Interpreter | | AWS Bedrock Knowledge Base Outbound Connector | Retrieve Documents from AWS Bedrock Knowledge Base | | AWS Lambda Outbound Connector | Invoke AWS Lambda Function | | AWS SageMaker Outbound Connector | Run Inference with AWS SageMaker | | AWS Textract Outbound Connector | Extract Text from Document with AWS Textract | | Google Gemini Outbound Connector | Generate Content with Google Gemini | | GraphQL Outbound Connector | Send GraphQL Request | | Hugging Face Outbound Connector | Run Inference on Hugging Face | | Kafka Outbound Connector | Publish Message to Kafka | | RabbitMQ Outbound Connector | Publish Message to RabbitMQ | | REST Outbound Connector | Send REST Request | | RPA Connector | Run RPA Script | | SendGrid Outbound Connector | Send Email with SendGrid | | SOAP Connector | Send SOAP Request | | SQL Database Connector | Execute SQL Statement on Database | Inbound connectors are not renamed. For Kafka and RabbitMQ, only the outbound connector is renamed. [Available connectors](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md) ## Data Breaking change #### Default RocksDB memory allocation strategy changed to `FRACTION` {#rocksdb-memory-allocation-strategy} Starting with Camunda 8.10, the default RocksDB memory allocation strategy changes from `PARTITION` to `FRACTION`. With `FRACTION`, RocksDB memory is allocated as a fraction of total available memory (default `0.1`, or 10%) instead of scaling with the number of partitions per broker. This may result in a different amount of memory being allocated to RocksDB after upgrading. **Action:** Review your broker memory sizing before upgrading. To keep the previous behavior, explicitly set `camunda.data.primary-storage.rocksdb.memory-allocation-strategy` to `PARTITION` (environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_ROCKSDB_MEMORYALLOCATIONSTRATEGY=PARTITION`). To adopt the new default, test the `FRACTION` strategy first to find the right `memory-fraction` value for your deployment. [Zeebe memory allocation](/components/best-practices/architecture/sizing-self-managed.md#memory) Breaking change #### Elasticsearch and OpenSearch exporter defaults changed for Optimize mode and job records Starting with Camunda 8.10, the Elasticsearch and OpenSearch exporters ship with two updated defaults: - `index.optimizeModeEnabled` is now `true` (previously `false`). The exporter restricts exported record value types to those consumed by Optimize and drops other record value types. - `index.job` is now `false` (previously `true`). When `index.optimizeModeEnabled` is `true`, Optimize mode controls which record value types are exported, so the individual `job` flag has no effect. **Action:** Review your exporter configuration before upgrading. If your deployment relies on record value types that Optimize mode does not cover, set `index.optimizeModeEnabled: false` and explicitly configure the record value types you need. [Elasticsearch exporter configuration](/self-managed/components/orchestration-cluster/zeebe/exporters/elasticsearch-exporter.md#configuration) [OpenSearch exporter configuration](/self-managed/components/orchestration-cluster/zeebe/exporters/opensearch-exporter.md#configuration) Breaking change #### Optimize Self-Managed no longer flattens object variables by default Starting with Camunda 8.10, Self-Managed Optimize no longer imports object variable values by default. Object variables are no longer flattened into per-property fields, and their raw values are no longer stored. This significantly reduces Optimize storage and CPU usage, and aligns Self-Managed with the default Camunda 8 SaaS has used for years. This change is **Self-Managed only**; SaaS is unaffected, as it already runs with this behavior disabled. - Object-heavy processes previously measured 5.9-48.8x more Optimize variable storage on Self-Managed than SaaS for identical workloads. - If you rely on object variable properties in reports, filters, or Raw Data Reports, opt in by setting `zeebe.includeObjectVariableValue: true` (environment variable `CAMUNDA_OPTIMIZE_ZEEBE_INCLUDE_OBJECT_VARIABLE=true`). - Optimize logs a `WARN` on startup whenever object variable values are not being imported. The message includes the opt-in setting. **Action:** Decide whether your Self-Managed deployment needs flattened object variables. If it does, set `zeebe.includeObjectVariableValue: true` before upgrading to 8.10. [Object variables configuration](/self-managed/components/optimize/configuration/object-variables.md) Change #### New SaaS clusters default to `business_` variable include filter for Optimize Starting with Camunda 8.10, new SaaS clusters include a default `business_` variable include filter in Optimize data filter settings. Only variables whose names start with `business_` are exported to Optimize. Variables not matching this prefix are permanently excluded from Optimize. This default does not apply to existing clusters. Existing clusters show data filters disabled with a one-click opt-in — no automatic migration occurs. **Action:** If your Optimize reports or dashboards on new SaaS clusters rely on variables not prefixed with `business_`, update the variable include filter in Camunda Hub cluster settings before creating the cluster or immediately after. [Configure Optimize data filters](/components/saas/clusters/settings.md#data-filters) ## Deployment Breaking change #### Bitnami subcharts removed from the Helm chart Camunda 8.10 (chart 15.x) no longer bundles the Bitnami subcharts for PostgreSQL, Elasticsearch, and Keycloak. Camunda 8.9 is the last minor that ships them. Helm installations must connect to external infrastructure instead, such as managed databases and search services, Kubernetes operators, or customer-owned images. **Action:** If you still use Bitnami subcharts on 8.8 or 8.9, migrate to external or vendor-supported infrastructure on 8.9 before upgrading to 8.10; the 8.10 Helm chart has no Bitnami-based fallback. See [Migrate from Bitnami subcharts](/self-managed/deployment/helm/operational-tasks/migration-from-bitnami/index.md). Breaking change #### Individual component Docker images no longer produced Camunda no longer produces the following individual component Docker images in Camunda 8.10 and later, or in Camunda 8.9 from patch release 8.9.12: - [camunda/zeebe](https://hub.docker.com/r/camunda/zeebe) - [camunda/operate](https://hub.docker.com/r/camunda/operate) - [camunda/tasklist](https://hub.docker.com/r/camunda/tasklist) **Action:** Before upgrading to Camunda 8.10, or to Camunda 8.9.12 or later, switch to the unified [camunda/camunda](https://hub.docker.com/r/camunda/camunda) Docker image. Breaking change #### Operate and Tasklist health indicators replaced by a unified schema readiness check Camunda 8.10 removes the Operate- and Tasklist-specific Elasticsearch/OpenSearch health indicators (`indicesCheck` and `searchEngineCheck`). A single `schemaReadinessCheck` now backs the gateways readiness probe; it is set once at startup, after the schema is initialized and the cluster reports green or yellow. `searchEngineStatus` reflects the current health status of Elasticsearch/OpenSearch and can be fetched via `/actuator/health` (it is not part of the readiness probe group). Breaking change #### Unused PVC in Optimize is unmounted An unused volume mounted at `/camunda` in Optimize has been removed from the Helm chart. Optimize did not use this volume. By default, this mount used an `emptyDir`, so no PVC cleanup is required. However, if you set `optimize.persistence.enabled=true` in `values.yaml`, the PVC may still exist in your Kubernetes cluster even though Optimize no longer mounts it. **Action:** If you previously enabled `optimize.persistence.enabled=true`, delete the leftover PVC to reclaim storage quota. The claim name is `-camunda-platform-optimize-data`. Deprecated #### Ingress-nginx annotation defaults deprecated in the Helm chart {#ingress-annotation-defaults-deprecated} The Helm chart used to ship Ingress-nginx-specific defaults in `global.ingress.annotations` and `orchestration.ingress.grpc.annotations`. Helm deep-merges maps, so setting a single annotation of your own still inherited all of them, and they were written onto the `Ingress` whatever `ingressClassName` you configured. On Contour, Traefik, or any other controller they are dead configuration, and removing them meant setting each key to `null`. Starting with Camunda 8.10 (chart 15.x), those annotations come from a compatibility shim controlled by `global.compatibility.nginx.renderAnnotations`, which defaults to `true`. **Nothing changes on upgrade:** the same annotations render, so Ingress-nginx deployments are unaffected. The shim is removed in the next major, after which the annotations are opt-in. **Action:** If you run an Ingress controller other than Ingress-nginx, set `global.compatibility.nginx.renderAnnotations: false` and configure whatever your controller needs through `global.ingress.annotations` and `orchestration.ingress.grpc.annotations`. Keys you set there always win over the shim. That removes the shim's annotations only. The chart still adds `nginx.ingress.kubernetes.io/backend-protocol` to the dedicated Ingress objects it renders when an upstream TLS mode is enabled through `global.tls.orchestration`, `global.tls.connectors`, or `global.tls.optimize`, and only Ingress-nginx reads that annotation. ```yaml global: compatibility: nginx: renderAnnotations: false ingress: annotations: # for example, with Contour kubernetes.io/tls-acme: "true" ``` If you stay on Ingress-nginx, no action is required before the next major. When the shim is removed you will need to set the annotations yourself: ```yaml global: ingress: annotations: nginx.ingress.kubernetes.io/ssl-redirect: "false" nginx.ingress.kubernetes.io/proxy-buffering: "on" nginx.ingress.kubernetes.io/proxy-buffer-size: "128k" # keep in sync with global.config.requestBodySize nginx.ingress.kubernetes.io/proxy-body-size: "10m" orchestration: ingress: grpc: annotations: nginx.ingress.kubernetes.io/ssl-redirect: "false" nginx.ingress.kubernetes.io/backend-protocol: "GRPC" nginx.ingress.kubernetes.io/proxy-buffer-size: "128k" ``` The gRPC Ingress reads `orchestration.ingress.grpc.annotations` only; it inherits nothing from `global.ingress.annotations`, so set all three keys there. Two of those carry behavior rather than cosmetics: `nginx.ingress.kubernetes.io/backend-protocol: "GRPC"` is what makes Ingress-nginx proxy Zeebe gRPC at all, and `nginx.ingress.kubernetes.io/proxy-buffer-size` is the documented fix for gateway timeouts caused by large JWT `Set-Cookie` headers. With [Contour](https://projectcontour.io/), the gRPC upstream is declared on the Orchestration Cluster **Service**, not on the Ingress, so set it through `orchestration.service.annotations` and not `orchestration.ingress.grpc.annotations`. The annotation value lists the gRPC port, and the key depends on whether that upstream uses TLS: | gRPC upstream | Contour annotation | Envoy behavior | | ------------------------------------------------------- | ----------------------------------------- | ---------------- | | Plaintext, the chart default | `projectcontour.io/upstream-protocol.h2c` | Cleartext HTTP/2 | | TLS, with `global.tls.orchestration.grpc.enabled: true` | `projectcontour.io/upstream-protocol.h2` | HTTP/2 over TLS | ```yaml orchestration: service: annotations: # plaintext upstream; use upstream-protocol.h2 if the gRPC upstream has TLS projectcontour.io/upstream-protocol.h2c: "26500" ``` Contour reads `h2c` as cleartext HTTP/2, so leaving it on a TLS-enabled upstream breaks gRPC routing. The chart draws the same distinction on Ingress-nginx, where it swaps `nginx.ingress.kubernetes.io/backend-protocol` from `GRPC` to `GRPCS` for a TLS-enabled gRPC upstream. The chart emits a deprecation warning naming the flag and the removal only when the shim actually injects an annotation: the flag is on, the Ingress it applies to renders, and you have not set that key yourself. Setting every shim key silences the warning even with the flag still on. [Ingress setup](/self-managed/deployment/helm/configure/ingress/ingress-setup.md) Deprecated #### Classic Grafana dashboard format deprecated {#classic-grafana-dashboard-format-deprecated} The Grafana dashboards published in [`monitor/grafana`](https://github.com/camunda/camunda/tree/main/monitor/grafana) of the `camunda/camunda` repository use the classic Grafana dashboard JSON model. Starting with Camunda 8.11, Camunda will update the dashboards to the new [v2 dashboard schema](https://grafana.com/whats-new/2025-04-11-new-dashboards-schema/). Camunda 8.10 is the last release that provides the dashboards in the classic format. The classic dashboards of 8.10 and earlier releases continue to work with your Grafana instance. **Action:** To keep using the dashboards in the classic format, import them from the `stable/8.10` branch or from the branch of the release you run. Before you move to the dashboards of 8.11, check that your Grafana version supports the v2 dashboard schema. Deprecated #### Application configuration Helm keys deprecated {#application-configuration-helm-keys-deprecated} Starting with Camunda 8.10, Helm chart values that only proxy a single application property are deprecated in favor of the component's `extraConfiguration`. The chart keeps the Kubernetes settings it is responsible for, such as resources, scheduling, endpoints, and secrets, and stops mirroring the application's own configuration. The deprecated keys continue to work in 8.10. When you set one to a non-default value, `helm install` and `helm upgrade` log a `[camunda][warning] DEPRECATION` message that names the key and where to configure it instead. **Action:** Move the deprecated keys in your `values.yaml` to the component's `extraConfiguration`. If you set both, the `extraConfiguration` value takes precedence. Use the deprecation messages from your own upgrade as the up-to-date list of keys for your chart version. [Deprecated application configuration Helm keys](/self-managed/upgrade/helm/890-to-8100.md#deprecated-application-configuration-helm-keys) Deprecated #### Unknown Helm values keys rejected by default in Camunda 8.12 {#unknown-helm-values-keys-rejected-in-812} Starting with Helm chart 15.1.0, the Camunda 8.10 chart warns about each chart-owned key in your values that the chart schema doesn't declare. Helm ignores these keys, so the settings they hold aren't applied. The check doesn't cover `global` values, the values of dependency charts, or free-form maps such as annotations and labels. The 8.11 chart keeps this behavior. Starting with Camunda 8.12 (Helm chart 17.x), unknown chart-owned keys stop `helm install` and `helm upgrade` by default. **Action:** Remove or correct the keys that the `[camunda][warning] UNKNOWN VALUES KEY` messages name. To make unknown keys fail now, set `global.strictValues: true`. [Find unknown keys in your values](/self-managed/deployment/helm/operational-tasks/helm-values-schema-validation.md#find-unknown-keys) Change #### Helm CLI v3 and v4 supported for Camunda 8.10 {#helm-v4-required-for-camunda-810} Camunda 8.10 (chart 15.x) supports Helm CLI v3 (3.10 or later) and v4. With Helm v3, the chart shows a warning in the notes that `helm install` and `helm upgrade` print, and in a ConfigMap whose name ends in `-warnings`. The warning does not block the install or upgrade. **Action:** Use Helm CLI v4 for new installations. Switch existing deployments before Helm CLI v3 support ends. Switching CLIs does not require a release-state migration. Helm runs on the client, and both CLIs read and write the same release-storage format. See [Move from the Helm v3 CLI to v4](/self-managed/deployment/helm/operational-tasks/moving-helm-v3-to-v4.md) and [Helm CLI v4](/self-managed/deployment/helm/operational-tasks/helm-v4.md). Change #### Camunda Hub database migration phases The 8.9 to 8.10 Camunda Hub database migration is controlled by `camundaHub.upgrade.phase`. Use `quiesce` to stop all Hub workloads so you can take a verified database backup, `migrate` to run the startup schema migration on a single pod without serving traffic, and `normal` to restore serving capacity. Fresh installs stay on `normal`. **Action:** Run the phases in order as part of your 8.9 to 8.10 upgrade, and plan a maintenance window: Hub serves no traffic in `quiesce` or `migrate`. The migration isn't backward compatible, so take a verified database backup first. See [migrate Camunda Hub](/self-managed/upgrade/helm/890-to-8100.md#migrate-web-modeler-and-console-to-camunda-hub). New #### Deployment topology release roles Camunda 8.10 adds `global.topology.mode` to the Helm chart, so a release declares its role in the wider deployment: `combined`, `hub`, `orchestration`, or `optimize`. One `hub` release running Camunda Hub and Management Identity can serve many independently deployed `orchestration` releases, and an `optimize` release deploys Optimize alone, so each Physical Tenant gets its own Optimize instance. `combined` remains the default and preserves existing single-release behavior, so no existing deployment changes on upgrade. For a new production deployment, the split topology is the baseline. `hub` and `optimize` are 8.10-only roles, because Camunda Hub and its cluster inventory don't exist in the earlier charts. The `orchestration` role is also available in recent 8.9, 8.8, and 8.7 charts, so one 8.10 Hub can manage clusters on older chart versions. Earlier versions of those charts ignore `global.topology.mode` and deploy a combined release. For the minimum chart version of each role, see [release roles](/self-managed/deployment/helm/install/topology/index.md#release-roles). **Action:** None required for an existing deployment. For a new production deployment, see [deployment topology](/self-managed/reference-architecture/reference-architecture.md#deployment-topology) and [install the deployment topology](/self-managed/deployment/helm/install/topology/index.md). To move an existing combined release, see [move from a combined release to the split topology](/self-managed/upgrade/helm/combined-to-split-topology.md). ## Engine Regression #### Batch cancellation filtered by parent process instance cancels all root process instances {#batch-cancel-parent-process-instance-filter} **Affected versions:** 8.10.0–8.10.6. Fixed in 8.10.7. Camunda 8.10.0 introduced a regression in which a batch cancellation filtered by `parentProcessInstanceKey` ignores that filter. Instead of canceling only the called process instances of the given parent, the batch operation cancels every active and suspended root process instance that matches the remaining filter criteria and that the user is authorized to cancel. If `parentProcessInstanceKey` is the only narrowing criterion, this includes every running root process instance in the cluster. Canceled process instances can't be restored. You're affected if you cancel process instances in either of the following ways: - **Operate:** You open a process instance, click **View all called instances**, select all listed instances, and click **Cancel**. The confirmation dialog shows only the selected instances (for example, 1 instance), but the resulting batch operation cancels all running root process instances. - **Orchestration Cluster API:** You call [`POST /v2/process-instances/cancellation`](/apis-tools/orchestration-cluster-api-rest/specifications/cancel-process-instances-batch-operation.api.mdx) with a filter that includes `parentProcessInstanceKey`. This regression affects both Camunda 8 SaaS and Self-Managed. **Fix:** Starting with 8.10.7, the batch operation keeps the `parentProcessInstanceKey` filter and adds the root-only condition to it. A batch cancellation filtered by `parentProcessInstanceKey` now matches no process instances, because called process instances can't be canceled directly. They're canceled together with their root process instance. This matches the behavior of Camunda 8.9 and earlier. **Action:** - **Running 8.10.0–8.10.6:** Don't use select all and **Cancel** on the **View all called instances** page in Operate, and don't include `parentProcessInstanceKey` in batch cancellation filters. To cancel called process instances, cancel their root process instance instead, which also cancels all of its called process instances. - **Upgrading to 8.10.7+:** The regression is fixed. If your integration sends batch cancellation requests filtered by `parentProcessInstanceKey` and expects the called process instances to be canceled, target the root process instance instead. ## Hub Breaking change #### Changed default log file name The log file name in the default Log4j2 configuration changed from `camunda-modeler.log` to `camunda-hub.log`. Logging to a file is still disabled by default. This change is only relevant if you enabled file logging in Web Modeler and use the default configuration. **Action:** Update any external tooling that depends on the log file name. [Logging configuration](/self-managed/components/hub/configuration/logging.md) Change #### Deployments target environments instead of clusters Starting with Camunda 8.10, teams deploy to [environments](/components/concepts/environments.md) instead of the clusters connected to a project. An environment is a named deployment target where a team runs its processes, and it's hosted on a cluster. Clusters remain the infrastructure your administrators manage. - Projects no longer have their own deployment stages or connected clusters. A project can deploy to every environment assigned to its workspace. - Organization admins assign environments to workspaces. - In Self-Managed, Camunda Hub creates environments from the clusters in your `camunda.hub.clusters` configuration, and from any Physical Tenants you declare. **Action:** After you upgrade, assign environments to the workspaces you create. Optionally, tag a cluster with `prod` if you want Camunda Hub to treat its environments as production environments for the project deployment policy. [Environments in the 8.9 to 8.10 upgrade guide](/self-managed/upgrade/components/890-to-8100.md#environments) [Environments](/components/concepts/environments.md) Deprecated #### Deprecated configuration settings In Camunda Hub, the configuration settings previously used by Web Modeler are deprecated in favor of the new Hub equivalents. Application properties prefixed with `camunda.modeler.*` are replaced by `camunda.hub.*`, and environment variables prefixed with `CAMUNDA_MODELER_*` are replaced by `CAMUNDA_HUB_*`. Some properties were also renamed or moved to a different property path. Camunda Hub still accepts the legacy properties in 8.10 and translates the recognized properties to their new equivalents at startup, but will remove them in 8.12. **Action:** Update your Camunda Hub configuration to the new settings ahead of that removal. [Migrate Web Modeler settings in the 8.9 to 8.10 upgrade guide](/self-managed/upgrade/components/890-to-8100.md#migrate-web-modeler-settings) [Property reference](/self-managed/components/hub/configuration/properties.md) ## Identity Deprecated #### Legacy Optimize authentication properties deprecated The authentication properties Optimize used through 8.9 are deprecated in favor of `camunda.security.*`. Optimize still accepts them in 8.10 and translates the recognized properties to their new equivalents at startup, but they will be removed in a future release. **Action:** Migrate Optimize to the `camunda.security.*` settings ahead of that removal. [Optimize component-specific configuration keys](/self-managed/upgrade/components/890-to-8100.md#component-specific-security-configuration-keys-are-deprecated) [Optimize authentication in Self-Managed](/self-managed/concepts/authentication/authentication-to-optimize.md) Change #### Console and Web Modeler Admin roles gain new Hub cluster access on Self-Managed Starting with Camunda 8.10, Camunda Hub replaces Console and Web Modeler. Management Identity only adds roles, applications, and permissions on startup and never removes them, so two existing Self-Managed roles automatically gain access they didn't have in 8.9 — with no role reassignment or opt-in required: - Existing `Console` role holders gain management access to Hub's cluster pages through a new `admin:clusters` permission. `DevOps` is the new name for the same access. - Existing `Web Modeler Admin` role holders gain full access to Hub's cluster pages too, through their existing `admin:*` permission, which now additionally reaches Hub's cluster pages — a broader grant than the Console role's management-only access. `Hub Admin` is the new name for the same access. A new `Analyst` role is also introduced: Hub modeling access, management access to the catalog's usage and adoption data, and full access to Optimize, without modeler-admin or people/org management access — the Self-Managed equivalent of the SaaS Analyst role. **Action:** If you rely on least-privilege access to cluster management, review who holds the `Console` and `Web Modeler Admin` / `Hub Admin` roles before upgrading. [Management Identity roles and permissions in the 8.9 to 8.10 upgrade guide](/self-managed/upgrade/components/890-to-8100.md#management-identity-roles-and-permissions) [Manage roles](/self-managed/components/management-identity/application-user-group-role-management/manage-roles.md) Change #### SaaS organization roles renamed and Catalog access levels introduced Starting with Camunda 8.10, SaaS organization roles are renamed to align with Camunda Hub, and Catalog access is split into two levels. These are display renames and a new access split; existing role holders keep the same effective access, with no reassignment required: - `Owner` → `Organization Owner` - `Admin` → `Organization Admin` - `Modeler` → `Member` (Member additionally gains organization and cluster read access) - `Analyst` stays `Analyst`. - `Operations Engineer` → `DevOps`, with no permission change. - Catalog access is now split into **Read** (Member, DevOps) and **Manage** (Analyst, Organization Admin, Organization Owner, who additionally see usage statistics and adoption data). `Developer`, `Support agent`, `Task user`, and `Visitor` are unaffected by this rename; see [manage users and roles](/components/hub/organization/users-and-roles.md#roles-and-permissions) for their status. [Manage users and roles](/components/hub/organization/users-and-roles.md#roles-and-permissions) Change #### Unified authentication for the Orchestration Cluster and Optimize Optimize can now be configured with the same `camunda.security.authentication.*` settings already used by the Orchestration Cluster. Nothing changes for the Orchestration Cluster, which already used these settings in 8.9. Optimize accepts its 8.9 authentication settings in 8.10 and translates the recognized properties to their new equivalents at startup, but those 8.9 properties are deprecated and will be removed in a future release. User, group, role, tenant, and permission management for Optimize is unchanged and is still handled by Management Identity. **Action:** Migrate Optimize to the `camunda.security.*` settings ahead of that removal. [Optimize component-specific configuration keys](/self-managed/upgrade/components/890-to-8100.md#component-specific-security-configuration-keys-are-deprecated) [Orchestration Cluster security properties](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#security) [Optimize authentication in Self-Managed](/self-managed/concepts/authentication/authentication-to-optimize.md) Change #### Orchestration Cluster warns about an incorrect OIDC configuration at startup The Orchestration Cluster checks its OIDC configuration at startup and writes a warning for each problem it finds. The cluster still starts. - The checks cover the client ID, the set of endpoints, the scope, and the shape of the redirect URI. - A redirect URI with no callback path, or with a path that has no leading slash, falls back to `{baseUrl}/sso-callback`. - Any other unusable value stays as configured, and the login fails later. **Action:** Review your startup logs after the upgrade. The warnings come from the loggers `io.camunda.security.spring.oidc.ScopedClientRegistrationFactory` and `io.camunda.security.spring.oidc.OidcRedirectionEndpoint`. [Redirect URI](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md#redirect-uri) Change #### Orchestration Cluster starts when an identity provider is unreachable The Orchestration Cluster contacts an OIDC provider at the first request that needs it, and not at startup. A provider that is down no longer stops the cluster from starting. - Only the requests that need that provider fail, such as browser login requests and token-validation requests. All other requests succeed, and a failed request recovers when the provider answers, without a restart. - A failed request writes a warning, at most once each minute for each combination of failed step and provider. **Action:** If you used a failed startup to detect an unreachable identity provider, alert on the `DeferredOidcResolution` warning instead. This covers a provider that the cluster resolves through its issuer URI. A provider with a static `jwk-set-uri` or `user-info-uri` gives a different signal, or none, so read the debugging guide before you rely on this alert. [Requests fail when an identity provider is unreachable](/self-managed/components/orchestration-cluster/admin/debugging-authentication.md#requests-fail-when-an-identity-provider-is-unreachable) ## Integrations Deprecated #### SAP BTP Plugin retired The SAP BTP Plugin is retired as of Camunda 8.10. There are no changes to the other modules of the SAP integration. Deprecated #### CSAP CLI replaced by a c8ctl plugin The CSAP CLI is retired and replaced by a plugin for the [c8ctl CLI](/apis-tools/c8ctl/getting-started.md), which becomes the single tool for configuring and deploying the SAP integration modules. [CSAP c8ctl plugin](/components/camunda-integrations/sap/csap-cli.md) ## Optimize Breaking change #### Client bearer tokens are now classified for permission checks Optimize classifies each bearer token as belonging to a user or a machine-to-machine (M2M) client, using `camunda.security.authentication.oidc.username-claim` and `client-id-claim`, and enforces your configured Optimize permission only on tokens it classifies as a user's. A token Optimize can't classify is treated as belonging to a user, and checked against your configured Optimize permission. **Action:** Set `username-claim` and `client-id-claim` to match your identity provider's token shape before upgrading. If you've already configured these claims for the Orchestration Cluster, use the same values for Optimize. Otherwise, M2M clients without an Optimize permission may see new permission errors after upgrading. [Optimize authentication in Self-Managed](/self-managed/concepts/authentication/authentication-to-optimize.md#configure-oidc-for-optimize) Breaking change #### Optimize static API access token is no longer supported In Camunda 8.10, Self-Managed Optimize accepts only OIDC bearer tokens on its API. A request that carries the static token from `api.accessToken` (environment variable `OPTIMIZE_API_ACCESS_TOKEN`) gets a `401` response. This applies to the [Optimize API](/apis-tools/optimize-api/overview.md) and to the [external variable ingestion](/apis-tools/optimize-api/external-variable-ingestion.md) endpoint. The Camunda Helm chart and SaaS do not set this token. They configure OIDC for the Optimize API. You are affected only if you set the property or the environment variable yourself, for example as a property override or an extra environment variable in your Helm values. **Action:** Change the API clients that send the static token to OIDC bearer tokens before you upgrade to 8.10. Then remove `api.accessToken` from your configuration. If you need more time, set `optimize.security.csl.enabled=false`. This opts into the 8.9 component-specific configuration fallback, and the static token works again. Camunda plans to remove this fallback and the component-specific configuration keys in a future release. [Optimize API authentication](/apis-tools/optimize-api/optimize-api-authentication.md) Deprecated #### Component-specific Optimize security configuration keys deprecated The Optimize login and API security keys used through 8.9 are deprecated in favor of `camunda.security.*`. Optimize maps recognized component-specific keys automatically and logs a deprecation warning naming the replacement. Camunda plans to remove these keys in a future release. Keep `CAMUNDA_OPTIMIZE_IDENTITY_BASE_URL` set. It is not deprecated, and Optimize still uses it to look up users, for example when adding users to a collection. **Action:** Migrate to the `camunda.security.*` keys as soon as you can. See [component-specific configuration keys](/self-managed/upgrade/components/890-to-8100.md#component-specific-security-configuration-keys-are-deprecated) for the full mapping and the precedence rules. Deprecated #### `optimize.security.csl.enabled=false` fallback is temporary `optimize.security.csl.enabled=false` temporarily restores the 8.9 component-specific configuration. Use it only if your integrations depend on the static API access token that the 8.9 configuration accepted, or if your migration to the `camunda.security.*` keys was misconfigured and you need a working deployment while you fix it. Camunda plans to remove this fallback, the 8.9 behavior it restores, and the component-specific configuration keys in a future release. **Action:** Treat this as a temporary escape hatch, not a supported long-term mode. Falling back doesn't pause the migration, it only delays it, so the same `camunda.security.*` migration is still required. [Optimize authentication in Self-Managed](/self-managed/concepts/authentication/authentication-to-optimize.md#fall-back-to-the-89-component-specific-configuration) ## Tasklist Breaking change #### Tasklist custom styling uses Camunda design system tokens Starting with Camunda 8.10, the Tasklist UI uses the Camunda design system instead of the Carbon Design System. Custom styles in `custom.css` that override Carbon `--cds-*` tokens or use `:root[data-carbon-theme='g10']` and `:root[data-carbon-theme='g100']` selectors no longer have any effect. Tasklist falls back to its default styling without showing an error. The `custom.css` file location has also changed. In the Docker image, place the file at `/usr/local/camunda/config/custom.css` instead of `/usr/local/tasklist/config/custom.css`. In the distribution archive, place it in the `config` directory. Camunda now serves the file at `/custom.css` instead of `/tasklist/custom.css`. **Action:** When you upgrade to 8.10, rewrite your custom styles to override the Camunda design system tokens using the `html .c4-ui` (light theme) and `html .c4-ui.dark` (dark theme) selectors, and move `custom.css` to the new location. [Tasklist custom styling](/self-managed/components/orchestration-cluster/tasklist/tasklist-custom-styling.md) --- ## 8.10 Release notes | Minor release date | End of standard maintenance | Changelog(s) | Upgrade guides | | :----------------- | :-------------------------- | :--------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------- | | 13 October 2026 | 11 April 2028 | [Patch Releases and Changelogs](#technical-changelogs-for-all-810x-releases) | [8.10 upgrade guides](/reference/announcements-release-notes/8100/whats-new-in-810.md#upgrade-guides) | :::info 8.10 resources - See [What's new in Camunda 8.10](/reference/announcements-release-notes/8100/whats-new-in-810.md) for important changes to consider when planning your upgrade from Camunda 8.9. - See [release announcements](/reference/announcements-release-notes/8100/8100-announcements.md) to learn more about supported environment changes, breaking changes, and deprecations. - Refer to the [quality board](https://github.com/orgs/camunda/projects/187/views/23) for an overview of known bugs by component and severity. ::: ### Technical Changelogs for all 8.10.x releases
Overview of all patch releases and their Changelogs in GitHub [Camunda 8.10.7 (26.10.2026)](https://github.com/camunda/camunda/releases/tag/8.10.7)[Camunda 8.10.6 (22.10.2026)](https://github.com/camunda/camunda/releases/tag/8.10.6)[Camunda 8.10.5 (19.10.2026)](https://github.com/camunda/camunda/releases/tag/8.10.5)[Camunda 8.10.4 (13.10.2026)](https://github.com/camunda/camunda/releases/tag/8.10.4)[Camunda 8.10.3 (12.10.2026)](https://github.com/camunda/camunda/releases/tag/8.10.3)[Camunda 8.10.2 (07.10.2026)](https://github.com/camunda/camunda/releases/tag/8.10.2)[Camunda 8.10.1 (06.10.2026)](https://github.com/camunda/camunda/releases/tag/8.10.1)[Camunda 8.10.0 (29.09.2026)](https://github.com/camunda/camunda/releases/tag/8.10.0)[Connectors 8.10.2 (22.10.2026)](https://github.com/camunda/connectors/releases/tag/8.10.2)[Connectors 8.10.1 (20.10.2026)](https://github.com/camunda/connectors/releases/tag/8.10.1)[Connectors 8.10.0 (30.09.2026)](https://github.com/camunda/connectors/releases/tag/8.10.0)
## Agentic orchestration SaaSSelf-ManagedAgentic orchestrationAI agentsIDP ### Agentic control plane Use the Optimize agentic control plane dashboard to monitor AI agent adoption, token usage, reliability, and performance across your processes in a single view. The dashboard is primarily intended to help operators, process owners, and engineering leads who manage AI-agent-powered processes, and need to keep them reliable and cost-effective. [Agentic control plane](/components/optimize/userguide/agentic-control-plane.md) ### AI Agent connector #### Conversation storage SPI redesign The conversation storage SPI used by [custom AI Agent storage backends](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-customization.md#custom-conversation-storage) has been redesigned. Built-in stores are migrated transparently; custom `ConversationStore` implementations must be updated. [SPI and package changes for custom extensions](/reference/announcements-release-notes/8100/8100-announcements.md#ai-agent-connector-spi-and-package-changes) #### New native element templates The AI Agent Task and AI Agent Sub-process connectors are now available as new, native element templates, running on new job types and giving native access to each LLM provider's own SDK and wire format, including extended thinking and prompt caching configuration where supported. Provider and backend selection are now decoupled. For example, the Anthropic provider can run through AWS Bedrock Mantle, and the OpenAI provider through Microsoft Foundry (Azure), while keeping each provider's own configuration options. The legacy element templates are deprecated as of Camunda 8.10. This is a major redesign of the AI Agent connector, available from 8.10 only, and requires manual [migration](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-upgrade.md) of each element from the current legacy connector. [Upgrade AI Agent element templates](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-upgrade.md) :::note legacy connector deprecated in 8.10 The legacy connector is deprecated in 8.10, but is not removed and continues to work. Adopting the new template is a manual, per-element migration, not an automatic upgrade. ::: #### Claude on Microsoft Foundry and OAuth 2.0 for compatible endpoints Your AI agents can now use Claude models hosted by Microsoft Foundry (Azure), through the Anthropic provider of the new AI Agent element templates. The provider keeps Anthropic-specific configuration such as extended thinking and prompt caching. Authenticate with an API key, Microsoft Entra ID client credentials, or a managed identity (Hybrid and Self-Managed only). The custom or compatible endpoint backends for both the Anthropic and OpenAI providers now support **OAuth 2.0 client credentials**. Use this authentication method with internal gateways that issue bearer tokens instead of accepting static API keys. [AI Agent model providers](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-model-providers.md#anthropic) #### Mistral AI provider Your AI agents can now use Mistral models through the new native **Mistral AI** provider of the AI Agent element templates. Connect to the Mistral API with an API key, or to a custom Mistral-compatible endpoint with no authentication, an API key, or OAuth 2.0 client credentials. The provider supports tool calling, structured output, PDF and image input, and reasoning models. The Mistral AI provider is available from Camunda 8.10.5. [AI Agent model providers](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-model-providers.md#mistral-ai) ### AI agent testing with Camunda Process Test You can now test non-deterministic AI agent behavior in Camunda Process Test (CPT) with conditional behavior controls and evaluation-based assertions, so you can validate agent behavior and output quality with more reliable test outcomes. - Define conditional behavior in tests with a `when(condition).then(action)` API. - Assert output quality with LLM-as-a-judge expectations, or compare semantic similarity with embeddings, when exact matching is not enough. - Use judge and semantic similarity assertions on any string value with AssertJ, or define judge assertions in JSON test cases. - Configure remote or local models through code and properties, for both local development and CI/CD pipelines. [Test your AI agents with CPT](/components/agentic-orchestration/evaluate-agents/test-ai-agents.md) ### Assisted agent tool configuration New features help you more easily configure your agent tools when modeling. | Feature | Description | Available in | | :-------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------ | | Fix | Automatically detect and apply a safe fix for an agent misconfiguration. If a `fromAi()` key or an output key is detected as invalid, click the **Fix** button to apply a fix.Applying a fix only rewrites the invalid part of a field value. The **Fix** button is only shown if the field has an existing (misconfigured) value. | Desktop ModelerHub Modeler | | Input from agent, Output to agent | Automatically fill in the `fromAi()` inputs or the `toolCallResult` output configuration of an agent tool contract. **Input from agent**: Use to add a correctly structured agent-supplied input for a blank input mapping or blank FEEL-capable element-template field.**Output to agent**: Use to map a tool result back to the agent.Autofill is only available for a blank field, and becomes unavailable as soon as a field holds a value (so it can never replace your entered values). | Desktop ModelerHub Modeler | | Lint rule checking | Agent tool configuration lint rule checking helps you avoid agent misconfiguration and errors when modeling.Linting rules identify and highlight malformed `fromAi()` inputs, missing or incorrect `toolCallResult` output mappings, and missing tool descriptions before they cause silent runtime failures.Configuration errors are highlighted in the Modeler. Select an error to navigate to and highlight the affected field (including fields supplied by connector templates). Inline guidance is shown to help you resolve the error. | Desktop ModelerHeadless BPMN linting.Hub Modeler | :::note - Changes are explicit, apply only when the correction is deterministic, and can be undone. - These configuration features are only available inside an ad-hoc sub-process marked as agentic through either the `io.camunda.agenticai.toolContainer` property or an out-of-the-box AI Agent element template. It is not available in a plain sub-process. You might need to [update your element template](/components/modeler/reference/modeling-guidance/rules/agent-fromai-contract.md#declare-a-sub-process-as-agentic) to use this new feature. ::: [Assisted agent tool configuration](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-tool-definitions.md#assisted-tool-configuration) ### Camunda-provided LLM for SaaS You can now run AI Agents on Camunda 8 SaaS in minutes using the Camunda-provided LLM, without your own LLM credentials. - Whether you start from a Camunda-provided agentic blueprint or build your own agent from scratch, the required credentials are populated automatically as cluster secrets, so there is little to no extra setup needed to get started. - The included budget is sufficient for hundreds or thousands of agent runs even on a trial account, depending on the model used. Enterprise organizations must explicitly enable the **Camunda Provided LLM** toggle in Camunda Hub, which is separate from the **AI-powered features** toggle. - This dramatically reduces time-to-first-running-agent by removing the need for external LLM infrastructure or credential setup. [Camunda-provided LLM](/components/agentic-orchestration/camunda-provided-llm.md) ### IDP supports ABBYY for document extraction Intelligent document processing (IDP) now supports [ABBYY](https://www.abbyy.com/) as a document extraction provider. [Intelligent document processing](/components/hub/workspace/modeler/intelligent-document-processing.md) ### MCP start event element template The **MCP start event** element template is now available in Modeler. Apply it to a BPMN message start event to configure the process as an MCP tool with name, purpose, inputs, and usage guidance for LLMs. [MCP start event](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-start-event.md) ### Processes MCP Server AI agents can use the Processes MCP Server to discover and call deployed BPMN processes as [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) tools. - When you deploy a process with an MCP start event it is automatically registered as a callable tool. - MCP clients connect to the `/mcp/processes` endpoint and can invoke any registered process, with the Orchestration Cluster starting a new process instance and immediately returning the process instance key. - The server also exposes [static tools](/apis-tools/processes-mcp/processes-mcp-static-tools.md) for inspecting running process instances, so agents can check variables, state, and incidents without switching servers. [Processes MCP Server](/apis-tools/processes-mcp/processes-mcp-overview.md) ### Real-time agent visibility and monitoring Monitor and evaluate AI agent behavior in Operate. - View each agent's execution [state](/components/agentic-orchestration/agent-states-and-metrics.md#agent-states) (thinking, calling a tool, idle) highlighted on the process diagram, as well as its current tool calls, [usage metrics](/components/agentic-orchestration/agent-states-and-metrics.md#usage-metrics) (tokens, tool calls, and model calls against the configured limit), model, and system prompt. - Trace the full reasoning chain behind AI agent decisions in the [conversation history](/components/agentic-orchestration/agent-definitions-and-instances.md#conversation-history-and-loop-iterations) such as user prompts, assistant messages, tools selected with the agent's reasoning, and tool calls with navigation to the corresponding diagram elements, so you can see exactly which messages, inputs, and tool responses informed each of the agent's next steps. - Operate displays readable model reasoning as an inline **Thinking** entry in the conversation history. [Migrate to the AI Agent element templates introduced in 8.10](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-upgrade.md) to display reasoning where supported. - [External agents](/components/agentic-orchestration/connect-external-agent.md) built with frameworks such as LangGraph or CrewAI get the same visibility through the new [Agent Instance API](/components/agentic-orchestration/agent-definitions-and-instances.md#visibility-for-external-agents). [Monitor your AI agents with Operate](/components/agentic-orchestration/evaluate-agents/monitor-ai-agents.md) :::note If you modeled the agent element before Camunda 8.10, you must [update its element template](/reference/announcements-release-notes/8100/8100-announcements.md#ai-agent-connectors-redesigned-templates-legacy-templates-deprecated) to at least v1 (version 13) or v2 to enable this feature. ::: ### Skills repository for pro-code AI enablement The Camunda Skills repository toolset enables AI coding agents to build, validate, and configure Camunda artifacts. With the Skills installed, your AI agent can: - Build and modify BPMN diagrams with a human-readable layout. - Configure connectors using element templates (no raw XML). - Generate form schemas with validation. - Create and edit DMN decision tables. - Run BPMN lint rules against generated diagrams. - Scaffold and wire Camunda Process Test (CPT) integration tests. [Camunda Skills on GitHub](https://github.com/camunda/skills) ## APIs & tools SaaSSelf-ManagedOrchestration Cluster APIAPI clients ### C# SDK Camunda now offers an officially supported C# Client for the Camunda 8 Orchestration Cluster REST API v2. You can authenticate with your cluster (No Auth for local, Basic authentication, or OIDC access tokens) and use C# methods to deploy resources, start and manage process instances, work with user tasks, and query processes and decisions, complete with pagination helpers and typed responses via generated models. [C# SDK](/apis-tools/csharp-sdk.md) ### Camunda Hub API A new public Camunda Hub API is provided under `/v2/` for programmatic access to the resources previously managed in Console and Web Modeler. - The API aligns with the Orchestration Cluster API guidelines, with standardized error handling and data-fetching patterns. - The Console Self-Managed and Web Modeler APIs are deprecated in favor of the Camunda Hub API. - See the [release announcement](/reference/announcements-release-notes/8100/8100-announcements.md#console-sm-and-web-modeler-apis-deprecated) for details. [Camunda Hub API](/apis-tools/hub-api-saas/overview.md) ### Console API supports external encryption for cluster creation The Camunda Console API now accepts encryption configuration parameters when creating clusters. Organizations using Terraform, custom scripts, or CI/CD pipelines can specify the encryption type (including external customer-managed keys) directly in the cluster creation request, removing the need for a separate manual step in the web console. - Clusters that do not meet the required encryption policy can be blocked at the API level before provisioning begins. ### FEEL evaluation with process instance key The `POST /v2/expression/evaluation` endpoint now optionally evaluates expressions in the context of: - A process instance, via `processInstanceKey`. - A flow node instance, via `elementInstanceKey`. The endpoint: - Combines process instance variables, element-local variables (for element scope), cluster variables, and optional request context into a single evaluation context. - Enforces `EXPRESSION:EVALUATE` plus `PROCESS_DEFINITION:READ_PROCESS_INSTANCE` on the underlying process definition. - Requires exactly one of `processInstanceKey` or `elementInstanceKey` (mutually exclusive); sending both returns `400 Bad Request`. Behavior remains free from side effects and uses the same timeout and guardrails as the existing cluster-scope evaluation. [Evaluate an expression](/apis-tools/orchestration-cluster-api-rest/specifications/evaluate-expression.api.mdx) ### Go and Rust SDKs 8.10 introduces Technical Previews for Go and Rust language SDKs for the Orchestration Cluster API. The Go SDK additionally contains support for gRPC job streaming. During 8.10 these SDKs will be stabilized, but there may be changes to their API surface based on user feedback. These SDKs will be fully supported and guaranteed to be stable in a later release. [Go SDK](/apis-tools/go-sdk.md) [Rust SDK](/apis-tools/rust-sdk.md) ### Invite members via the Hub API who haven't previously logged in Adding a workspace member via the public API no longer requires the invitee to have previously logged in to Hub Modeler. - You can use `PUT /v1/collaborators` or `POST /v2/workspaces/{workspaceKey}/members`. - If the email address belongs to an organization member with no local user yet, a pending invitation is created and an invitation email sent. - The invitee gains workspace access once they accept the invitation. [Add or update a member](/apis-tools/hub-api-saas/specifications/add-member.api.mdx) ### Java client in-memory OAuth credentials cached by default The Camunda Java client now caches OAuth credentials in memory by default. The file-based cache at `$HOME/.camunda/credentials` is no longer enabled by default and is available as an explicit opt-in. - The previous default tried to create `$HOME/.camunda/credentials` on first use. In hardened container environments such as non-root users (Kubernetes `securityContext.runAsUser`, OpenShift), read-only root filesystems, and immutable images, this raised `AccessDeniedException`/`IOException` at first cache write. Affected users had to apply a non-obvious workaround (mount a writable volume and point an environment variable at it) just to get a client to start. - Memory-only caching removes that footgun: clients work out of the box in any deployment topology, and the in-process token cache plus proactive refresh still avoid unnecessary token endpoint calls during a JVM's lifetime. - The file cache had also been a source of latent corruption when multiple JVMs shared the same `$HOME`; making it opt-in restricts its use to deployments where persistence across restarts is genuinely needed. How to opt in to the file-based cache (behavior identical to pre-8.10): | Configuration method | Example | | :------------------- | :------------------------------------------------------------------------------------------------------ | | Java client builder | `new OAuthCredentialsProviderBuilder().credentialsCachePath("/path/to/cache")` | | Spring property | `camunda.client.auth.credentials-cache-path: /path/to/cache` | | Environment variable | `CAMUNDA_CLIENT_CONFIG_PATH=/path/to/cache` (or `ZEEBE_CLIENT_CONFIG_PATH` for the legacy Zeebe client) | If you previously set `CAMUNDA_CLIENT_CONFIG_PATH` / `ZEEBE_CLIENT_CONFIG_PATH` only to work around the non-root container error, you can now remove that configuration and rely on the in-memory default. [Spring Boot starter configuration](/apis-tools/camunda-spring-boot-starter/configuration.md#credentials-cache-path) ### Operate and Tasklist APIs removed The deprecated Operate and Tasklist APIs are removed. Process data, task management, and operational queries are now served through the [Orchestration Cluster API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). [Migrate to the Orchestration Cluster API](/apis-tools/migration-manuals/migrate-to-camunda-api.md) ### Scheduled cluster backups via the Administration API With the Camunda 8 SaaS Administration API, you can now schedule and manage recurring cluster backups programmatically, helping you automate disaster-recovery routines instead of relying only on manual, on-demand backups. [Administration API](/apis-tools/administration-api/administration-api-reference.md) ### Zeebe Client replaced by Camunda Java Client The Zeebe Client is removed and replaced by the [Camunda Java Client](/apis-tools/java-client/getting-started.md). This covers process deployment, message correlation, and job handling. [Migrate to the Camunda Java Client](/apis-tools/migration-manuals/migrate-to-camunda-java-client.md) ### Zeebe Process Test replaced by Camunda Process Test The Zeebe Process Test library is removed and replaced by [Camunda Process Test](/apis-tools/testing/getting-started.md). This provides richer assertions, Spring integration, and alignment with the Orchestration Cluster API surface. [Migrate to Camunda Process Test](/apis-tools/migration-manuals/migrate-to-camunda-process-test.md) ## Camunda 8 Run Self-ManagedCamunda 8 Run ### Camunda 8 Run Java runtime Camunda 8 Run now includes a bundled Java runtime. This means you no longer need to install OpenJDK or set `JAVA_HOME` before starting Camunda 8 Run. [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) ## Camunda design system The new visual Camunda design system is introduced for Admin, Camunda Hub, and Tasklist with the 8.10 release. - The new, streamlined design system offers a cleaner, more consistent look across components. - Accessibility improvements are built in, and the updated navigation menu makes it easier to find your way around. - The new design system is enabled by default in both Self-Managed and SaaS. ## Camunda Hub SaaSCamunda HubConsoleWeb Modeler Camunda Hub is now the single place where you and your teams build, govern, and run process solutions in Camunda. - Hub is where you design, model, manage, and oversee your processes. Hub replaces Web Modeler and Console, keeping their existing features while adding new features within a single unified platform. - Hub is deployed only once, and serves as the single point of entry for all your environments, connecting to all your dev, staging, and production Orchestration Clusters. **Hub changes how you and your teams work**. Instead of managing separate Web Modeler and Console instances per environment, you now use a single Hub that connects to all your clusters. You design once, and manage everything from one place. [Camunda Hub](/components/hub/index.md) ### Business value dashboard SaaSSelf-ManagedCamunda HubOptimize Use the new **Business Value** page in Camunda Hub to track process outcomes using cycle time, automation rate, activity, and agentic adoption metrics, and to set targets for cycle time and automation rate. - A portfolio view compares every process in the selected environment on target coverage, target attainment, activity, automation rate, cycle time, and agentic adoption, and ranks off-target processes by how many targets are missed and by how far. - A process view shows the metrics and targets for a single process, including per-metric target status and a cycle time distribution with P50, average, and P95. - Set optional targets for cycle time and automation rate against the current baseline. Activity is shown as a metric, but you can't set a target for it in 8.10. - Every metric is calculated from completed process instances in the selected environment. No changes to your process models are required. [Business value dashboard](/components/hub/organization/analyze-operations/business-value-dashboard.md) ### Catalog SaaSSelf-ManagedCamunda Hub The new Camunda Hub catalog gives your center of excellence (CoE) a governed, organization-wide place to publish approved element templates, so delivery teams can reuse trusted building blocks instead of rebuilding them in each project. - Manage element templates and their metadata in your own Git repository, and use a CI/CD pipeline to publish them to the catalog through the Hub API whenever the approved set changes. - Browse, search, and filter published assets in Hub, and read each asset's details before you apply it while modeling. - See which assets are outdated and which workspaces and projects still use an older version, so you can prioritize migrations. - Unpublish assets you no longer want used. Elements that already use an unpublished asset keep working and show a deprecation hint. In 8.10, the catalog supports element templates as its only asset type. [Manage the catalog](/components/hub/organization/manage-catalog/index.md) ### Console {#console-hub} SaaSSelf-ManagedCamunda Hub Console is now a top-level entry in the Camunda Hub navigation for organization owners, admins, and DevOps users, and the cluster references in Console point to the **Environments** and **Clusters** pages. - **SaaS:** The cluster health and the cluster links on the Console dashboard open the clusters in **Environments > Clusters**. The cluster references on the organization page point to the same pages. - **Self-Managed:** Console shows the dashboard and usage information. The cluster links open the clusters in **Environments > Clusters**, and the cluster list moved there from Console. - **Self-Managed:** Each instance of a management component has its own entry on the dashboard. [View Console](/components/hub/organization/console.md) ### Duplicate a cluster in Console With Camunda 8.10, you can now duplicate a cluster in Console without manually re-entering its settings. Selecting **Duplicate** opens the create-cluster form pre-filled with the source cluster’s configuration, ready for you to review and submit. Process data is not copied to the new cluster, and each API client receives a new client ID and secret. [Create a cluster](/components/saas/clusters/create-cluster.md) ### Environments SaaSSelf-ManagedCamunda Hub Environments are the new deployment targets where teams run their processes in Camunda Hub. A cluster remains the infrastructure that administrators manage, and an environment is hosted on a cluster. - Organization admins assign environments to workspaces, in the Camunda Hub interface or with the Camunda Hub API. Every project in a workspace can deploy to all the environments assigned to the workspace. - Projects no longer connect clusters to deployment stages. The deploy dialog and the **Test** tab list the environments of the workspace, with their tags, version, and status. - In Self-Managed, each Physical Tenant of a cluster at version 8.10 or later is an environment. In SaaS, each cluster has one environment. - The **Environments** page shows every environment of the organization with its status, opens its applications, and shows a summary of its jobs. - Organization admins can require an approved project snapshot before anyone deploys to an environment tagged `prod`. - When you upgrade, Camunda Hub assigns the clusters that your projects used to their workspaces as environments. [Environments](/components/concepts/environments.md) [Manage environments](/components/hub/organization/manage-environments/index.md) ## Connectors SaaSSelf-ManagedConnectorsModeler ### App Integrations connector Use the App Integrations connector to send and receive messages in Microsoft Teams and Slack. **Send messages**: You can send Microsoft Teams and Slack messages without managing credentials. The connector sends messages to Microsoft Teams and Slack, and creates channels, through your organization's Camunda app integrations. The connection is configured once for the environment, so no endpoint or credentials appear in the process model. **Receive messages**: You can receive Microsoft Teams and Slack messages in a process. A process can start from a message someone writes to the Camunda app in Microsoft Teams or Slack, and a process that is already holding a conversation receives the reply, so an approval, a choice, or a correction can be collected in the chat people are already in rather than in a separate form. [App Integrations connector](/components/connectors/out-of-the-box-connectors/app-integrations.md) ### AWS Connectors updated to AWS SDK for Java v2 All AWS connectors are updated to use AWS SDK for Java v2. This ensures Camunda AWS connector implementations use supported client libraries and reduces maintenance risk, as AWS SDK for Java 1.x reached end of support on 31 December 2025. ### Connector Management observability Connector Management now provides a unified view of inbound and outbound connectors. - The refreshed experience adds status summaries, search, filtering, sorting, per-runtime health and metrics, richer process details, clearer activity logs, and direct links to Operate. - Operators can also reset inbound connector executables from the UI, while webhook activity logs expose redacted request metadata and bounded body previews to make troubleshooting easier. [Manage your connectors](/components/hub/organization/manage-clusters/manage-connectors.md) ### Connector search improvements You can now find a connector by the operation you want to perform. Built-in connector templates now describe their operations, so you can model by the action you want to take instead of the product that provides it. - Searching in the create, append, or change element menu for `upload object` or `send email` returns the matching operations of every connector as their own entries, and selecting one applies the connector with that operation preselected. - Connectors with several operations show their operations as a nested menu, and the operation selection is now the first group in the properties panel. - Connectors that provide a single operation are also renamed to describe their action. For example, the **REST Outbound Connector** is renamed to **Send REST Request**. Existing process models are unaffected. [Integrate a built-in connector](/components/connectors/use-connectors/configuring-out-of-the-box-connector.md) ### Storage connector improvements The following improvements are made to storage connectors (S3, Azure Blob, GCS): - Support for direct object creation from variables and better content extraction for document references. - Generation of .json, .txt, .csv, or binary files inline without relying on the Document Store. Documents with incorrect content-types can be read using conversion options (for example, "read as text", "read as JSON"). ## Helm chart deployment Self-ManagedHelm chartsZeebe ### Application configuration Helm keys deprecated Self-ManagedHelm charts Helm chart values that only proxy a single application property are deprecated in favor of the component's `extraConfiguration`, so application settings live in the component's own configuration. The deprecated keys continue to work in 8.10, and setting one to a non-default value logs a deprecation warning. [Understand Helm and application configuration responsibilities](/self-managed/deployment/helm/configure/configuration-responsibilities.md) ### Bitnami subcharts removed from the Helm chart Starting in 8.10, the Camunda Helm chart no longer includes the bundled Bitnami subcharts for PostgreSQL, Elasticsearch, and Keycloak. Helm installations must connect to external infrastructure, such as managed databases and search services, Kubernetes operators, or customer-owned images. If you still use Bitnami subcharts on 8.8 or 8.9, migrate to external or vendor-supported infrastructure on 8.9 before upgrading to 8.10. The 8.10 Helm chart has no Bitnami-based fallback. See the [release announcement](/reference/announcements-release-notes/8100/8100-announcements.md#bitnami-subcharts-removed-from-the-helm-chart) for details. [Migrate from Bitnami subcharts](/self-managed/deployment/helm/operational-tasks/migration-from-bitnami/index.md) ### Camunda Helm Toolkit The new Helm migration and validation toolkit can help you upgrade from Camunda 8.9 to 8.10 on Kubernetes with Helm. Use the toolkit to: - Read your existing 8.9 Helm values (for example, values.yaml). - Generate a sample 8.10 values file reflecting the recommended Helm CLI v4, Bitnami sub‑charts removal, Hub‑aware deployment patterns, and simplified application configuration. - Create a migration report that lists the keys that were migrated automatically, flags keys that require manual decision (for example, infrastructure endpoints, security‑sensitive options), suggests where to find more information in the documentation, and can validate an existing 8.10 values file (for example, one drafted by hand or AI tool) against Camunda’s migration rules. The CLI is non‑interactive, with clear exit codes and optional JSON output, making it suitable for humans using the command line, CI pipelines, and AI agents (for example, Claude Code, Copilot) that can use it as part of an automated migration workflow. [Prepare Helm upgrades with the Camunda Helm Toolkit](/self-managed/deployment/helm/operational-tasks/camunda-helm-toolkit.md) ### Camunda Hub replaces Console and Web Modeler in the Helm chart Self-ManagedHelm chartsCamunda Hub Camunda Hub is a drop-in replacement for Web Modeler in the Camunda Helm chart, and Console is no longer a standalone deployment. The `camunda/hub` image serves both Console and Web Modeler features, and you enable and configure it with the `camundaHub` key. - For standard deployments, only the top-level key needs to change: replace `console.enabled` and `webModeler.enabled` with `camundaHub.enabled`. - Existing Web Modeler Helm values keep working. A compatibility layer in the application honors the existing value structure, and deprecated keys are logged but not required to change immediately. - Moving your values under `camundaHub` is cleanup that you can do later. The upgrade guide documents the steps. - Review `camundaHub.restapi.resources` after upgrading, because Console now runs in the Hub REST API pod. [Consolidate Console and Web Modeler into Camunda Hub](/self-managed/upgrade/helm/890-to-8100.md#consolidate-console-and-web-modeler-into-camunda-hub) ### Helm chart version matrix improvements The Helm chart version information for Camunda 8 has been redesigned into a clear, tabular version matrix. This makes installation, upgrades, and troubleshooting easier and more predictable, especially in environments with frequent patch releases. You can now: - Quickly see which Helm chart version corresponds to each Camunda 8 minor and patch release, including alpha and stable tags. - Check the release date and support status of each Helm chart. - See which Helm CLI versions are supported by each chart. - Jump directly to change logs and related references via links in the matrix. [Camunda 8 Helm chart version matrix](https://helm.camunda.io/camunda-platform/version-matrix/) ### Helm CLI v3 and v4 support {#helm-v4-required} Self-ManagedHelm charts Camunda 8.10 (chart 15.x) supports Helm CLI v3 (3.10 or later) and v4. With Helm CLI v3, the chart shows a warning when you run `helm install` or `helm upgrade`. Switching CLIs does not require a release-state migration. Helm runs on the client, and both CLIs read and write the same release-storage format. Use Helm CLI v4 for new installations. Switch existing deployments before Helm CLI v3 support ends. [Helm CLI v4](/self-managed/deployment/helm/operational-tasks/helm-v4.md) ### Host network support for Orchestration Cluster pods The 8.10 Helm chart adds `orchestration.hostNetwork` (default: `false`), which lets Orchestration Cluster pods share the host node's network namespace. This is useful in bare-metal or restricted network environments where pods must be reachable directly via the node IP rather than a cluster overlay network. When `orchestration.hostNetwork` is set to `true` and `orchestration.dnsPolicy` is not set, the chart automatically uses `dnsPolicy: ClusterFirstWithHostNet` to preserve in-cluster DNS resolution. You can override this by setting `orchestration.dnsPolicy` explicitly. ```yaml orchestration: hostNetwork: true ``` [Configure pod networking](/self-managed/deployment/helm/configure/pod-networking.md) ### Improved TLS support in the Helm chart Self-ManagedHelm charts The Camunda Helm chart now includes an optional TLS overlay, `values-tls.yaml`, that sets up trust for encrypted connections from Camunda components to your datastores and identity provider. You provide one PEM CA bundle through `global.tls.caBundle`, and the chart makes it available to every component in the format its runtime needs. - Connect to Elasticsearch, OpenSearch, and PostgreSQL over TLS, including with self-signed or private CA certificates. - Trust external OIDC issuers that use a private CA, such as Microsoft Entra, Okta, or an internal Keycloak. - Use cert-manager to manage certificates, update the CA bundle, and verify that no plaintext fallback remains. The overlay doesn't encrypt in-cluster pod-to-pod traffic. For TLS at the pod level, combine it with a service mesh. [Configure TLS](/self-managed/deployment/helm/configure/tls.md) ### Independent REST and gRPC TLS for the Orchestration Cluster Self-ManagedHelm charts The Camunda Helm chart now configures REST TLS and gRPC TLS on the Orchestration Cluster independently, so you can run any combination of the two. You enable each mode with `global.tls.orchestration.rest.enabled` and `global.tls.orchestration.grpc.enabled`. When you enable a mode, the chart also: - Sets the backend protocol on the NGINX Ingress for the Orchestration REST and gRPC endpoints to match the TLS state. - Derives the in-cluster endpoint schemes that clients such as Connectors and Web Modeler use to reach the Orchestration Cluster. Your explicit overrides, such as `webModeler.restapi.clusters` and `connectors.configuration`, remain authoritative. If the Orchestration server certificate is self-signed or issued by a private CA, also set `global.tls.caBundle` so in-cluster clients trust it. [Configure Orchestration REST and gRPC TLS modes](/self-managed/deployment/helm/configure/orchestration-tls-modes.md) ### IRSA Document store support Camunda 8 Self‑Managed now supports using IAM Roles for Service Accounts (IRSA) with the AWS S3 document store: - You can deploy Camunda 8 on Amazon EKS with the document store configured for S3 without providing static AWS credentials. - The Helm chart no longer requires AWS access keys when IRSA is in use and allows pods to rely solely on their IAM role for S3 access. - Existing deployments using static AWS keys can migrate to IRSA following documented steps. Refer to the following updated Helm configuration and secret management documentation for more details: - See [IAM Roles for Service Accounts (IRSA)](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/irsa.md#document-store-s3) for the IAM role and trust policy, Helm chart configuration, service account annotations, and verification steps for the AWS S3 document store. - See [document handling configuration in Helm](/self-managed/concepts/document-handling/configuration/helm.md) for AWS S3 document store options. - See [Helm charts secret management](/self-managed/deployment/helm/configure/secret-management.md#credential-precedence-with-irsa) to learn how static AWS credentials take precedence over IRSA. [Camunda Helm chart](/self-managed/deployment/helm/index.md) ### PostgreSQL databases are highly available by default Self-ManagedHelm charts The operator-based infrastructure reference architecture now deploys each CloudNativePG cluster with two instances instead of one, on a dedicated write-ahead log (WAL) volume, and requires the two instances of a cluster to sit on different nodes. A single-instance cluster gives CloudNativePG no switchover target, so the operator refuses to evict it and `kubectl drain` never completes. Because a Kubernetes upgrade drains one node at a time, that stalls the upgrade on whichever node holds a database. A second instance gives the operator somewhere to switch over to. The dedicated WAL volume keeps the write-ahead log that a standby's replication slot retains from growing into the data directory. Deployments already running the single-instance shape migrate in place: CloudNativePG clones the standby from the running primary and relocates `pg_wal` onto the new volume, with no dump or restore. Environments that cannot host a second instance, such as local Kind clusters, can pass `PG_INSTANCES=1` to `deploy.sh`. [High availability and node maintenance](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#high-availability-and-node-maintenance) ## Integrations Self-ManagedSaaSIntegrations ### Camunda for Slack Camunda for Slack brings tasks, processes, and notifications into Slack through the same App Integrations backend as Microsoft Teams. Everything runs through the `/camunda` slash command, the Camunda direct message, and channel mentions. There is no tab app. - List and filter tasks, claim and release them, and complete a task from a Block Kit modal. - Start a process, and switch organization and cluster. - Subscribe a channel or direct message to user task notifications. - Use Slack with the App Integrations connector in both directions: a process can send a Slack message, and a Slack message can reach a process. Microsoft Teams and Slack are independent. You can run either on its own, or both against the same backend. [Camunda for Slack](/components/camunda-integrations/app-integrations/slack.md) ### Microsoft Teams routing and permission-aware task actions Camunda for Microsoft Teams now supports routing incident and task collaboration to private channels, shared channels, and group chats. Notifications and task actions in Teams now align with Camunda assignment and access rules, ensuring that only eligible users are notified and allowed to act. [Camunda for Microsoft Teams](/components/camunda-integrations/app-integrations/microsoft-teams.md) ## Modeler SaaSSelf-ManagedHub ModelerDesktop Modeler ### BPMN element menu improvements The create, append, and change menus now group BPMN elements by category, such as tasks, gateways, events, and so on. Each category includes a short description so you can quickly find the right element. - Search still searches across all categories. - When appending, elements that continue a flow subtly indicate where the flow continues next. Select this to open the append pad with a prominent **Append** action. [Model a process](/components/modeler/bpmn/bpmn.md) ### Decoupled element template lifecycle Element templates now have a lifecycle independent of process application versioning in Web Modeler. - Publish and version element templates on their own, without creating a process application version. - A clear separation between template publishing and process app versioning removes unexpected coupling and side effects between template changes and business-logic versioning. - Architects managing reusable templates across teams and environments get a more predictable mental model. Existing templates are automatically migrated to the new lifecycle model; no manual action is required. [Publish an element template](/components/hub/workspace/modeler/element-templates/manage-element-templates.md#publish-an-element-template) ### Define operations in your own element templates Element templates support the `steps` and `presets` keys to offer several predefined configurations within a single template. Use `steps` to define the menu users navigate when they apply the template, and `presets` to define the property values each operation applies. Operation names, descriptions, and keywords are matched by search, so your operations are as discoverable as the templates themselves. [Predefined configurations](/components/modeler/element-templates/template-metadata.md#predefined-configurations-steps-and-presets) ### FEEL context variables for the process instance The process instance properties are now accessible in FEEL expressions via the `camunda.processInstance` context, resolvable anywhere in the process. `camunda.processInstance.key` returns the process instance's system-generated key, and `camunda.processInstance.businessId` returns its business ID (or `null` if none is set). [FEEL context variables](/components/concepts/process-instance-creation.md#feel-context-variables) ### Hide the Add members button In Self-Managed, you can now hide the **Add members** button on the workspace **Members** page, preventing non-organization admins from adding members via the UI. They can still add members via the [modify collaborator API endpoint](https://hub.camunda.io/swagger-ui/index.html#/Collaborators/modifyCollaborator) if granted access. [Feature flag reference](/self-managed/components/hub/configuration/properties.md#hide-add-members-button) ### Low-code assertions Turn process instance runs into repeatable tests. Run a process instance, observe the output, then save the input data and assertions as a low-code integration test. That test now catches regressions on every change. - Add variable assertions to saved test cases using CPT-compatible names and values for interoperability. - Add path assertions that require specific elements or end events. - View pass/fail results based on assertions, not just "process completed without incidents". - Manage test metadata and assertions in one place in the **Test** tab. ### Low-code test CI/CD compatibility Test files in Test Studio now use the same schema as Camunda Process Test (CPT). You can record a test in Test Studio and run it in your CI/CD pipeline through CPT without converting formats, and load CPT-authored test files into Test Studio to debug them visually. - Use one JSON schema across Test Studio and CPT: record once, run anywhere. - Existing test scenario files are migrated automatically to the new format. [Test files](/components/hub/workspace/modeler/validation/test-files.md) ### Low-code test repair Saved test cases survive diagram changes. When a BPMN element is deleted, renamed, or changed type, Test Studio now tells you which steps broke and lets you fix them in place instead of re-recording the run. - See a broken-step indicator on the test case in the list, and a per-step warning explaining exactly which reference no longer resolves. - Repair a step inline: re-map it to another element, pick a valid value, or delete the step. - Repair covers execution instructions and assertions, including variable, element-instance, user-task, process-instance, message-subscription, and decision assertions. - Open the test file editor from a broken test case for advanced edits that the graphical flow does not cover. - Rerun immediately after repair to confirm the fix. ### Modeling menu improvements When you create, append, or change an element, the menu groups insertion options into two tabs: - **BPMN**: Standard BPMN elements, organized by their usual categories. - **Reusable assets**: Assets, connectors, and templates from your project, along with existing project resources such as forms, called processes, decisions, and RPA scripts. [Find reusable assets in the modeling menus](/components/hub/workspace/modeler/element-templates/use-catalog-assets.md#find-reusable-assets-in-the-modeling-menus) ### New organizational structure for workspaces and projects A new organizational structure for workspaces and projects is introduced in 8.10. With this new file resource hierarchy: - Workspaces now **only** contain projects and IDP projects. - Files and folders are stored inside projects. - Previously, a workspace (called a project in Web Modeler) could contain process applications (now projects), folders, and files. The new **Workspace > Project > File/folder** hierarchy makes resources more discoverable and your workspaces more scalable. :::note Your SaaS Web Modeler data, now part of Camunda Hub, was updated during the 29 August 2026 maintenance window to support this new structure. ::: ### Project versioning model A new versioning model for workspaces, projects, and file resources is introduced in 8.10. Workspaces now only contain projects and IDP projects on the root level. Folders and files are stored inside projects. **Projects**: The new project versioning model uses snapshots to save the current state of all the project files, in a single action. This helps you track a project throughout its development lifecycle and ensures the correct state is referenced. **File versioning:** Every BPMN diagram, DMN diagram, form, RPA script, README file, and test file keeps a version history, a single timeline of the autosaves and named versions created as you work. You can open that history to view an earlier state of the file, compare any two entries, restore an entry, or copy one to another project. ### Runtime connection targets environments SaaSSelf-ManagedCamunda Hub In Camunda Hub, the runtime connection of the modeler is a connection to an environment instead of a cluster. Select the environment from the modeling toolbar to model against. - Connector-credential names of the connected environment autocomplete in your FEEL expressions. - Task testing runs in the connected environment. - Two Physical Tenants on the same cluster are separate connections. :::note In Self-Managed, the runtime connection is enabled by default. You can [turn it off](/components/hub/workspace/modeler/modeling/connect-to-a-runtime.md#configure-the-runtime-connection-in-self-managed). ::: [Connect to a runtime](/components/hub/workspace/modeler/modeling/connect-to-a-runtime.md) ### Safe deletion with a 30-day recovery window Deleting an item in Camunda Hub no longer removes it immediately. Deleted workspaces, projects, files, folders, and IDP projects are moved to **Recently deleted** for 30 days. During that time, users with the appropriate permissions can see who deleted an item and when, and restore it. After 30 days, items are permanently deleted. Deletion no longer corrupts project version history, as existing snapshots continue to reference deleted files correctly. The recovery window applies to deletions made in 8.10 and later; items deleted before upgrading cannot be recovered. [Recover deleted resources](/components/hub/workspace/manage-projects/recently-deleted.md) ### Start a process instance with a business ID You can now set a business ID when starting a process instance directly from Camunda Hub or Desktop Modeler. The business ID field is available in the start process instance dialog alongside variables. [Business ID](/components/concepts/process-instance-creation.md#business-id) ### Support for configurable headers for execution listeners Execution listeners now support configurable headers, aligned with service task job headers. - In BPMN, execution listeners can define ``. The headers are passed to the listener’s job worker alongside any base-element headers, with listener headers overriding on key conflicts. - In Modeler, you can configure execution listener headers visually (name/value pairs) without editing BPMN XML. - Listener workers can consume these headers as metadata and configuration parameters using the same patterns as service task job workers. ### Support for start forms in Desktop Modeler Desktop Modeler now supports defining form references on [none start events](/components/modeler/bpmn/none-events/none-events.md) in Camunda 8 BPMN models, matching the existing Camunda Hub capability. You can configure start forms directly in Desktop Modeler's properties panel using: - Camunda Form (linked): Reference a deployed Camunda Form by ID. - Camunda Form (embedded): Embed form JSON in the BPMN diagram (deprecated). Start forms can now be defined and edited in both modelers, ensuring a seamless experience when working with diagrams across Camunda Hub and Desktop Modeler. ### Task testing supports call activities Task testing now supports call activities in both Desktop Modeler and Camunda Hub. Testing a call activity starts the deployed called process, shows its progress in the execution log with a link to open it in Operate, and reports incidents raised inside it. [Task testing](/components/modeler/task-testing.md) ### Test process segments in test mode When testing your process within test mode in Camunda Hub, you can now capture and rerun targeted sections of a process as low-code integration tests: - Run segment tests individually or in batches to validate process changes faster. - Test BPMN elements like connectors, DMN, forms, and LLM tasks without a full end-to-end run. - Reuse saved segment tests during iterative model changes to catch regressions earlier. [Test your process](/components/hub/workspace/modeler/validation/test-your-process.md) ### Variables panel improvements When you hover over "written in X elements" or an element ID in the variables panel, the diagram now highlights the corresponding element or elements so you can quickly see where a variable is used. FEEL expressions in the variable outline now use the same syntax highlighting as the FEEL editor, with more granular tokens that distinguish function names from arguments and operators from literals, making complex expressions easier to read. When no element is selected on the canvas, the variables panel now highlights the process (root) scope, matching how it highlights the scope of a selected element. Variable value previews also no longer repeat the opening brackets of nested objects and arrays, so previews are easier to scan. [Inspect variables](/components/modeler/data-handling.md#inspecting-variables) ## Optimize Self-ManagedOptimizeSaaS ### Client bearer tokens are now classified for permission checks Self-ManagedOptimize Optimize now classifies each bearer token as belonging to a user or a machine-to-machine (M2M) client, using `camunda.security.authentication.oidc.username-claim` and `client-id-claim`, and enforces your configured Optimize permission only on tokens it classifies as a user's. A token Optimize can't classify is treated as belonging to a user, and checked against your configured Optimize permission. [Optimize authentication in Self-Managed](/self-managed/concepts/authentication/authentication-to-optimize.md#configure-oidc-for-optimize) ### Delete a process definition's data via API Optimize now exposes a public API endpoint to delete all analytics data for a given process definition, so you can remove data for retired processes or respond to data removal requests without manually touching Elasticsearch or OpenSearch. The deletion runs asynchronously: the API accepts and queues the request, then processes it in the background. [Delete process definition data](/apis-tools/optimize-api/delete-process-definition-data.md) ### Object variables no longer flattened by default in Self-Managed Starting in 8.10, Optimize no longer flattens object variables by default in Self-Managed deployments. Object variables are not flattened into per-property fields, and their raw values are no longer stored. This significantly reduces Optimize storage and CPU usage and aligns Self-Managed with the default Camunda 8 SaaS behavior. - If you rely on object variable properties in reports, filters, or Raw Data Reports, you can opt in by setting `zeebe.includeObjectVariableValue: true` (env `CAMUNDA_OPTIMIZE_ZEEBE_INCLUDE_OBJECT_VARIABLE=true`). - If the setting is not explicitly configured, Optimize logs a `WARN` on startup stating that object variables will not be flattened, and details the opt-in setting. - SaaS deployments are unaffected as this behavior is already disabled. **Recovery:** The Optimize importer is idempotent. As long as the object variables still exist in the `zeebe-record-variable\*` indices (within your Zeebe retention window), you can enable the flag and reset the importer to reimport/flatten historical variables. [Object variables configuration](/self-managed/components/optimize/configuration/object-variables.md) ### Optimize data filters You can now configure Optimize data filters directly in cluster settings, without editing Helm values or configuration files. The **Data filters** section in cluster settings lets you: - Enable or disable Optimize export filtering per cluster. - Include or exclude process definitions by exact `bpmnProcessId`. - Include or exclude variable names by prefix — for example, `business_` includes all variables whose names start with `business_`. - Exclusion takes precedence over inclusion when both are configured. New SaaS clusters include a default `business_` variable include filter, which limits Optimize to variables starting with `business_` to reduce Elasticsearch storage and shard usage. Existing clusters show data filters disabled with a one-click opt-in — no automatic migration occurs. Saving filter changes triggers a rolling restart of the Orchestration Cluster; the cluster is briefly unavailable while it restarts. :::warning Filtered records are permanently excluded from Optimize and cannot be recovered even if you relax the filters later. ::: [Configure Optimize data filters](/components/saas/clusters/settings.md#data-filters) ### Optimize disabled by default on new trial clusters On new trial clusters in Camunda 8 SaaS, Optimize is now disabled by default. When Optimize is disabled, the overview shows a muted tile with an **Enable Optimize** prompt so it stays discoverable. Upgrading from a trial to a paid plan automatically enables Optimize, with no manual action required. ### Scope-aware variable export configuration for Optimize You can now configure variable export behavior by scope: - You can enable or disable root (process instance) variables and local variables independently. - You can exclude all local variables by default, while still allowing specific local variables by name pattern. - Configuration integrates with the existing variable filtering mechanism, using consistent syntax and semantics. Terminology aligns with Camunda 8 docs: - **Root scope/process instance scope**: Variables visible across the process. - **Local variables**: Variables defined in child scopes only. With this, you can configure setups such as: - Export only root variables for all processes. - Export a curated subset of local variables (for example, `taskContextDisplayName` or specific local audit variables) without exposing all locals. ## Orchestration Cluster SaaSSelf-ManagedOrchestration ClusterOperateTasklistZeebe ### Bespoke cluster generations for SaaS Organizations can now access exclusive Camunda 8 generation versions tailored specifically for their organization, available for both new cluster creation and upgrades. These generations are not visible to other organizations. ### Bring your own identity provider per cluster in SaaS SaaSOrchestration Cluster You can now connect your own identity provider to individual clusters in Camunda SaaS. Each Orchestration Cluster authenticates users through its own OIDC connection instead of Camunda's built-in organization identity provider, so you can enforce your own security and compliance policies. - Configure the OIDC connection per cluster, including standard OIDC parameters and custom claims mapping with mapping rules. - Migrate from the built-in provider with minimal disruption to existing user authentication. Web Modeler and Console continue to use Camunda's built-in identity provider. Only Orchestration Clusters use your identity provider directly. ### Business ID in message correlation You can now include a business ID when publishing or correlating a message. Business ID acts as an additional filter alongside the message name and correlation key. Supported combinations for start events: message name alone; name + business ID; name + correlation key; name + correlation key + business ID. For non-start events, business ID is usable alongside name + correlation key. When both a correlation key and business ID are provided, both fields must match the corresponding values stored on the subscription. If business ID uniqueness is enabled, a blocked message-start waits in the buffer until the active instance releases the business ID or the TTL expires — it is not dropped immediately. [Business ID in message correlation](/components/concepts/messages.md#business-id-in-message-correlation) ### Business ID propagation in call activities Call activities now support configuring the business ID assigned to the child process instance. Child instances inherit the parent's business ID by default (unchanged from 8.9). You can override this per call activity with a literal value or FEEL expression. The FEEL context variable `camunda.processInstance.businessId` provides access to the parent's ID within the expression. The resolved value is set once at child creation and is immutable. [Business ID propagation](/components/modeler/bpmn/call-activities/call-activities.md#business-id-propagation) ### Cancel execution listener Execution listeners now support a `cancel` event type on the process element. Cancel listeners run when a process instance is terminated — useful for cleanup, audit logging, or notifying external systems. [`cancel` listeners](/components/concepts/execution-listeners.md#cancel-listeners) ### Centralized Secret Resolution via Zeebe Centralized secret resolution through Zeebe is introduced in 8.10. Processes can reference credentials from customer-managed secret stores without persisting secret values in Camunda. - Reference secrets as `camunda.secrets.NAME` in input mappings, expressions, and output mappings. The legacy `{{secrets.NAME}}` syntax continues to work. - Secrets are resolved automatically for activated jobs and can also be requested through the Gateway APIs `/v2/secrets/resolve` and `/v2/secrets/list`. - Resolved values are not written to engine state, exports, backups, Operate, Tasklist, or application logs. - Self-Managed deployments support AWS Secrets Manager and GCP Secret Manager with workload identity authentication. A file-based provider is available for development and testing. - SaaS requires no configuration and uses Camunda’s managed secret backend. - Camunda 8 Run uses the file-based provider: create one file per secret (filename = secret name, contents = value), and set `camunda.secrets.stores.file.default.path` to that directory in the Camunda 8 Run application configuration. **Migration:** Existing processes continue to work without changes. For new processes, use `camunda.secrets.NAME`. To migrate hardcoded or connector-specific credentials, store the value in a supported secret store and replace it with a centralized secret reference. **Limitations:** This feature does not yet include HashiCorp Vault or Azure Key Vault support, secret access audit logging, per-process secret restrictions, or centralized resolution for Hybrid Connector Runtimes. Cache entries expire after the configured TTL, which is 20 seconds by default. [Secret resolution](/components/concepts/secret-resolution.md) ### Cluster variable metadata You can now add metadata to cluster variables as a map of string keys to scalar values (strings or numbers). Camunda stores the metadata alongside the variable but keeps it separate from its value. Use metadata to discover and filter variables by semantic attributes without inspecting their values. The search endpoint supports metadata filters with equality, numeric range, existence, `in`, and `like` operators for each key. Metadata is not exposed as part of the FEEL-accessible runtime value. [Cluster variable metadata](/components/modeler/feel/cluster-variable/metadata.md) ### Cluster version selection for SaaS You can now create new SaaS clusters on specific supported Camunda 8 minor and patch versions, including: - The latest recommended versions (latest patch of each active minor) - Other still-supported versions that you already run on existing clusters in the same organization. ### Cross-region cold recovery for SaaS SaaSConsole If a primary region fails, you can now recover your SaaS Orchestration Cluster in a secondary region from replicated backups, without depending on a support request. Cross-region cold recovery creates a new Orchestration Cluster in the secondary region and restores the backup you select. - Enable dual-region backup when you create the cluster, and select the backup location in Console. - Start failover in Console or through the API, and select the backup to restore from. - Cold recovery is available for AWS and GCP clusters in region pairs marked **Failover supported**, including AWS clusters that use Bring Your Own Key (BYOK). - Use only the recovered cluster after failover. Don't run the original cluster at the same time, to avoid conflicting writes. The data you can lose depends on the backup you select and your backup interval, because the recovered cluster contains only the data up to that backup. [Cross-region cold recovery](/components/saas/cross-region-cold-recovery.md) ### Dark and light mode persists across Admin, Operate, and Tasklist Dark mode and light mode preferences now persist across Admin, Operate, and Tasklist. Set your preference once and it applies across all Orchestration Cluster applications. ### Default RocksDB memory allocation strategy changed to `FRACTION` The default RocksDB memory allocation strategy changes from `PARTITION` to `FRACTION`. RocksDB memory is now allocated as a fraction of total available memory (default `0.1`, or 10%) instead of scaling with the number of partitions per broker. This may result in a different amount of memory being allocated to RocksDB. To keep the previous behavior, explicitly set the strategy to `PARTITION`. See the [release announcement](/reference/announcements-release-notes/8100/8100-announcements.md#rocksdb-memory-allocation-strategy) for more details. [Zeebe memory allocation](/components/best-practices/architecture/sizing-self-managed.md#memory) ### Docker images Camunda no longer produces the following Docker images in Camunda 8.10 and later, or in Camunda 8.9 from patch release 8.9.12: - [camunda/zeebe](https://hub.docker.com/r/camunda/zeebe) - [camunda/operate](https://hub.docker.com/r/camunda/operate) - [camunda/tasklist](https://hub.docker.com/r/camunda/tasklist) Use the unified [camunda/camunda](https://hub.docker.com/r/camunda/camunda) Docker image instead. ### Edit roles and tenants in Admin Self-ManagedSaaSAdmin You can now edit the name and description of a role or tenant directly in Admin, without deleting and re-creating it. Existing assignments stay in place. - Default roles and the `` tenant are system entities and cannot be edited. - Role and tenant IDs cannot be changed after creation. [Update a role](/components/admin/role.md#update-a-role) [Update a tenant](/components/admin/tenant.md#update-a-tenant) ### Job and process prioritization Camunda 8.10 introduces support for job and process instance prioritization for pull-based job activation via gRPC and REST (including long polling). - Users can assign an integer priority (for example, 0–99) to process instances and jobs via BPMN model attributes. - Job workers can now opt into priority-aware activation, so that ActivateJobs (gRPC) and REST-based long-polling endpoints consider priority when selecting which jobs to return. [Job prioritization](/components/concepts/job-workers.md#job-prioritization) ### Late Business ID assignment You can now assign a business ID to a running process instance that has none, using the `POST /process-instances/{processInstanceKey}/business-id-assignment` REST endpoint, the `AssignProcessInstanceBusinessId` gRPC command, or by including `businessId` in a job completion request. The assignment is single and irreversible, and only available while business ID uniqueness enforcement is disabled. [Late Business ID assignment](/components/concepts/process-instance-creation.md#late-business-id-assignment) ### Multi-instance activity execution listeners Execution listeners can now be configured on the enclosing body of multi-instance activities. `beforeAll` listeners run once per multi-instance body activation, before the `inputCollection` is evaluated and inner instances are created, making listener-produced variables available to the body's `inputCollection` expression. - Replicate Camunda 7 multi-instance and execution listener patterns without redesigning your process. - Dynamically calculate collections using custom logic or external data before instance creation. [`beforeAll` listeners](/components/concepts/execution-listeners.md#beforeall-listeners) ### Multi-tenancy support in SaaS Camunda 8 SaaS now officially supports multi-tenancy via tenant identifiers, bringing the same logical tenant isolation model available in Self-Managed to SaaS clusters. :::note Multi-tenancy is available on SaaS clusters running generation **8.8 and later** — including existing 8.8 and 8.9 clusters. You do not need to upgrade to 8.10 to use this feature. ::: - Owners and Admins can create, update, and delete tenants in Camunda Hub, and assign users, groups, and client credentials to them. - Camunda Hub and Desktop Modeler support tenant-scoped deployments to multi-tenant clusters by specifying a tenant ID. - Tenant usage is reflected in Camunda Hub reporting so org owners can monitor tenant consumption across a cluster. Multi-tenancy is enabled at the cluster level. Process definitions, instances, and decisions are scoped to the tenant they were deployed to, keeping data isolated across teams and applications sharing a single cluster. [Multi-tenancy](/components/concepts/multi-tenancy.md) ### New AWS US West region With the new Camunda 8 SaaS **AWS US West (us-west-2)** region in North America, you can deploy orchestration workloads with full US data residency and improved regional stability. [Supported AWS regions](/components/saas/regions.md#amazon-web-services-aws-regions) ### New GCP Montréal region SaaSConsole Camunda 8 SaaS now supports the Google Cloud Platform (GCP) Montréal, North America (`northamerica-northeast1`) region. Together with the existing Toronto region, you can now run process orchestration with your data hosted in Canada. To use it, select the Montréal region when you create a cluster in Console. [Supported GCP regions](/components/saas/regions.md#google-cloud-platform-gcp-regions) ### OIDC Diagnostic Logging OIDC authentication failures now surface actionable diagnostics in application logs. - Enable `camunda.security.authentication.oidc.diagnostics.enabled` to log the redirect URI Camunda expects against the one your identity provider returned, and to flag a callback that arrives without a valid session, the two most common causes of a login redirect loop. - If you use Microsoft Entra as your identity provider, an app registration issuing v1 tokens now fails authentication with an explicit error naming the fix (`api.requestedAccessTokenVersion = 2`) instead of looping silently. ### Operate batch delete completed process and decision instances Operate now supports deleting completed process instances and evaluated decision instances as a batch operation. When selecting finished instances from the list, you can use the new **Delete** batch action in the toolbar to remove multiple instances at once. A confirmation modal prevents accidental mass deletion. ### Operate Business ID visibility and filtering Business ID is now a first-class searchable attribute across Operate and the Orchestration Cluster API. Operations engineers can search and filter process instances, decision instances and user tasks by Business ID, enabling fast identification and investigation of business cases. - Business ID is visible in Operate process instance lists, decision instance lists, details views, and filters. - Advanced filtering supports exact match, not-equal, exists, and wildcard searches. - Business ID participates in message correlation as an additional constraint alongside the existing correlation key. - Business ID is visible in Tasklist task views for task workers to identify the associated business case. #### Business ID filtering Operate now exposes business ID as a filter field for process instances. You can filter using **Equals**, **Contains** (with `*` and `?` wildcards), and **Is one of** — or use the full operator set (`$eq`, `$neq`, `$exists`, `$like`, `$in`, `$notIn`) via the API. [Business ID filtering](/components/concepts/process-instance-creation.md#searching-and-filtering-by-business-id) #### Visibility for decision instances Business ID is now visible in Operate for decision instances, in both the decision instance list and the decision instance details view. Filter decision instances by business ID using **Equals**, **Contains**, and **Is one of** in the filter UI, or the full operator set (`$eq`, `$neq`, `$exists`, `$like`, `$in`, `$notIn`) via the API. [Business ID for decision instances](/components/operate/userguide/basic-operate-navigation.md#business-id-for-decision-instances) #### Visibility for process instances Business ID is now visible in Operate for process instances. The `businessId` field appears in the process instance list and the process instance details view. [Business ID for process instances](/components/concepts/process-instance-creation.md#business-id) #### Visibility for reference Documents View documents attached to process instances directly in the Operate process instance detail view. Documents display metadata (name, type, size, creation date) and support in-product preview for formats such as PDF, JSON, plain text, PNG, and JPG. Operators can now inspect agent memory, large payloads, connector attachments, and IDP source documents without leaving Operate or making API calls. ### Operate JSON display improvements The JSON display functionality in Operate for SaaS is improved. You can now: - Open JSON variables in a dedicated JSON viewer directly from the variables panel, without entering editing mode. - View JSON values with consistent, easier to understand formatting. - Copy full JSON variable values to the clipboard. - Use the improved in-line variables display. These improvements help you navigate more complex data during operations and troubleshooting. ### Operate multi-variable filtering In Operate, you can now combine multiple variable filters with `AND` logic to find exactly the process instances you need. Filter by variable name, value, and comparison operators, such as `equals`, `contains`, `greater than`, and `less than`, including nested JSON paths. [Multi-variable filters](/components/operate/userguide/filter-process-instances.md#multi-variable-filters) ### Operate wait state visibility Operate now shows what an active process instance is waiting for. - When you inspect an active element, you can see the wait state and its details, for example, a timer's due date, a receive task's message name and correlation key, a signal name, a condition expression, or a job's type and state. - Wait state tracking is enabled by default and writes records to secondary storage. In Camunda 8 Self-Managed, you can [disable it](/self-managed/concepts/wait-states/configure.md) if you do not want to track this data. [Wait states](/components/wait-states/overview.md) ### Opt-in analytics exporter Self-ManagedOrchestration Cluster Camunda 8.10 adds an opt-in analytics exporter for Self-Managed clusters. It is disabled by default, so you decide whether to enable it. When enabled, it shares product usage data with Camunda to help us prioritize improvements. ### Physical tenants: strong tenant isolation in one Orchestration Cluster Self-ManagedOrchestration Cluster Self-Managed Orchestration Clusters can now host multiple physical tenants. Each one is an isolated execution unit with its own Raft partitions, secondary storage (separate RDBMS schema, Elasticsearch/OpenSearch cluster, or prefixed indices), document store, exporters, and identity provider. Teams and business units get strong isolation without running a separate cluster for each. - **Operations:** Back up and restore each tenant on its own, manage its partitions, exporters, and scaling separately, and monitor it with its own metrics and logs. - **APIs and clients:** Clients pick a tenant through `/physical-tenants/{physicalTenantId}/v2/...` on REST or the `Camunda-Physical-Tenant` header on gRPC. `CamundaClient` supports tenant selection, and a single Java/Spring client or Connectors runtime can serve multiple tenants. - **Web apps:** Operate, Tasklist, and Admin are available per tenant at `/physical-tenants//` and show only the selected tenant's data. - **Identity:** Each tenant enforces its own roles, mapping rules, and permissions, so a user can have different roles on different tenants. You define identity providers at the cluster level, and each tenant chooses which ones it accepts. Cluster-wide operations (topology, backups, restore) are protected by a claim-based cluster admin role. Every cluster has a default physical tenant, so existing setups run unchanged. `tenantId`-based logical tenants still work inside each physical tenant. Physical tenants are defined in configuration and applied with a rolling restart. Upgrades apply to the whole cluster, and queries cannot span tenants. Data cannot be moved from a logical tenant into a separate physical tenant. Tenants can share brokers, and quotas between tenants are not included. SaaS is not supported yet. [Physical Tenant isolation model](/self-managed/concepts/physical-tenants/index.md) ### Process instance suspension and resumption You can now suspend and resume a running process instance without canceling it. Suspending halts execution at its current point: no jobs activate or complete, no events correlate, and no timers fire. Resuming picks up from exactly where execution stopped, with no loss of progress or data. - Suspend or resume a single instance, or a batch of instances, from Operate or the REST API. - You can still read and update variables on a suspended instance, so you can fix data before resuming. - Timers that come due during a suspension fire immediately on resume. Messages and signals are not correlated to a suspended instance. [Suspend and resume a process instance](/components/operate/userguide/suspend-resume-process-instance.md) ### Rebalance API for coordinated leadership transfer Coordinated leadership transfer for Orchestration Clusters is introduced in 8.10. The existing rebalance endpoint asks every leader to step down at once and returns immediately, without guarantee that the intended broker wins the resulting election. The new rebalance API transfers leadership deterministically, ensuring transfer in most cases in a way that is both minimally disruptive and observable. | Feature | Description | | :-------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Coordinated rebalancing API | `POST /cluster/v2/rebalance` starts a rebalance, `GET /cluster/v2/rebalance` reports the cluster's balance state and the progress of each partition, and `DELETE /cluster/v2/rebalance` stops a running rebalance once the transfer in flight has finished. The endpoint requires cluster-admin credentials. | | Deterministic transfers | Leadership is handed directly to the partition's highest-priority replica instead of being left to an open election, so a rebalance reaches the intended leader layout. | | Minimal disruptions | Transfers are sequenced one partition at a time across the cluster, so at most one partition is affected at any moment, rather than every partition becoming leaderless simultaneously. | | Configurable | The replication lag a desired leader is allowed to have, how long a partition may wait for that leader to catch up, and how long to wait for a leaderless partition can all be set as cluster defaults and overridden per request. When the desired leader cannot take over in time, the partition resumes under its current leader. | | Observable | `POST /cluster/v2/rebalance?dryRun=true` returns the plan a rebalance would carry out, without pausing any partition or moving any leadership. Each partition reports how its transfer ended or why it was skipped (already led by the desired leader, replication lag too high, replication timed out, and so on), so an incomplete rebalance can be diagnosed easily. | :::important - The previous /actuator/rebalance endpoint continues to work unchanged, and is superseded by the new API. - There are some cases where rebalancing is still not guaranteed, notably where the desired leader is simply not available or becomes unavailable during the operation. Such cases require manual retries once the desired leader of a given partition is back online. ::: ### Region-aware partition placement Camunda 8.10 introduces region awareness to the Orchestration Cluster. Operators declare which region each broker belongs to using a topology label, and the engine uses those declarations to distribute partition replicas across regions, ensuring no single region holds a quorum for any partition. Leader election priorities respect region boundaries, preferring region-local leaders under normal conditions and adjusting automatically when a region becomes unavailable. The same mechanism extends to availability zone or datacenter isolation using the same configuration. [Orchestration Cluster configuration properties](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md) ### Restore API for Orchestration Cluster backups Self-ManagedOrchestration Cluster You can now restore an Orchestration Cluster from a backup through the Restore API, without restarting the brokers or running the restore application on each broker. The Restore API works with Elasticsearch, OpenSearch, and relational database secondary storage. While a restore runs, the cluster is in recovery mode and processes no work. - Switch the cluster into recovery mode and trigger the restore with two non-blocking requests. Each returns a `changeId` that you use to track progress, so you can script and rehearse disaster recovery. - Restore from selected backups, and validate a request first with `dryRun=true`, which returns the planned operations without changing the cluster. - In a cluster with physical tenants, restore your own tenant with `/v2/restore`, or restore one or all tenants with `/cluster/v2/restore` as a cluster admin. [Backup and restore](/self-managed/operational-guides/backup-restore/backup-and-restore.md) ### S3-compatible object stores for Document Handling Document Handling now supports any S3-compatible object store such as MinIO, Cloudian, or Garage alongside Amazon S3, Google Cloud Storage, and Azure Blob Storage. - Configure an S3-compatible backend by pointing the existing AWS S3 document store to your custom provider endpoint. - No migration is required for existing AWS S3 deployments. [Document handling configuration](/self-managed/concepts/document-handling/configuration/index.md) ### Secure connectivity with AWS inbound PrivateLink for Camunda 8.7 Camunda 8.7 SaaS on AWS now supports inbound AWS PrivateLink with cluster authentication. Follow the Secure connectivity guide to configure your VPC endpoint. [Secure connectivity (AWS PrivateLink)](/components/saas/secure-connectivity/index.md) ### Select a DMN version with a FEEL expression You can now call a dynamically calculated version of a DMN decision from a BPMN business rule task by specifying the version with a FEEL expression. [Business rule tasks](/components/modeler/bpmn/business-rule-tasks/business-rule-tasks.md#defining-a-task) ### Select a target version when upgrading a cluster When you upgrade an Orchestration Cluster that has more than one valid upgrade target, Camunda Hub now shows a version selection step in the upgrade wizard. Each option displays the generation name and the Zeebe patch version. The recommended version (the longest upgrade path) is pre-selected and labeled **latest**, and you can choose a different option before proceeding. Clusters with only one upgrade target keep the existing flow. ### Self-service restore for SaaS orchestration clusters from backups Organization admins can now restore a SaaS orchestration cluster directly from a completed backup in Hub and via the Administration API. - Reduced time to recovery for operational incidents. - Operational control without opening a support ticket for standard same-cluster restores. - Clear restore status visibility during execution. During a restore, the cluster is unavailable until it completes. [Restore a cluster from backup](/components/saas/how-to-restore.md) ### Startup no longer depends on a reachable identity provider Self-ManagedCamunda 8 RunOrchestration Cluster The Orchestration Cluster contacts an OIDC provider at the first request that needs it, and not at startup. A provider that is still starting, or that is down, no longer stops the cluster from starting. - Only the requests that need the unreachable provider fail, such as browser login requests and token-validation requests for that provider. All other requests succeed. - A session that the cluster authenticated before the outage keeps its access token until the token expires. The refresh that follows fails, and the session ends. - Each new request tries again. The traffic recovers when the provider answers, and you do not need a restart. The cluster holds no queue of failed requests, and it makes no attempt in the background. - A failed request writes a warning that names the step that failed and the provider with its issuer, at most once each minute for each combination of step and provider. The warning follows the traffic, and it is not a health check of the provider. [Requests fail when an identity provider is unreachable](/self-managed/components/orchestration-cluster/admin/debugging-authentication.md#requests-fail-when-an-identity-provider-is-unreachable) ### Startup warns about an incorrect OIDC configuration Self-ManagedCamunda 8 RunOrchestration Cluster The Orchestration Cluster checks its OIDC configuration at startup, without contacting the provider, and writes a warning for each problem it finds. The cluster still starts. - The checks cover the client ID, the set of endpoints, the scope, and the shape of the redirect URI. - A redirect URI with no callback path, or with a path that has no leading slash, falls back to `{baseUrl}/sso-callback`. Any other unusable value stays as configured, and the login fails later. - The warnings come from the loggers `io.camunda.security.spring.oidc.ScopedClientRegistrationFactory` and `io.camunda.security.spring.oidc.OidcRedirectionEndpoint`. [Redirect URI](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md#redirect-uri) ### Tasklist Business ID Business ID is now visible in Tasklist, in both the task list and task detail views. Filter tasks by business ID using **Equals**, **Contains**, and **Is one of** in the filter dialog, or the `$neq`/`$exists`/`$notIn` operators via the API. [Business ID filter](/components/tasklist/userguide/using-filters.md#business-id-filter) ### Unified authentication for the Orchestration Cluster and Optimize Optimize can now be configured with the same `camunda.security.authentication.*` settings already used by the Orchestration Cluster, so you configure authentication once, in one place. Nothing changes for the Orchestration Cluster, which already used these settings in 8.9. Optimize continues to accept its 8.9 authentication settings in 8.10, translating the recognized properties to their new equivalents at startup, but those 8.9 properties are deprecated and will be removed in a future release. Confirm your `camunda.security.authentication.oidc.issuer-uri` and `.audiences` settings match your IdP before upgrading Optimize. User, group, role, tenant, and permission management for Optimize is unchanged in this release, and is still handled by Management Identity. [Optimize authentication in Self-Managed](/self-managed/concepts/authentication/authentication-to-optimize.md) ### Unified configuration property changes Camunda 8.10 completes the unified configuration of the Orchestration Cluster that began in 8.8. - The 8.10 property changes are documented in [Property changes in Camunda 8.10](/versioned_docs/version-8.10/self-managed/components/orchestration-cluster/core-settings/configuration/configuration-mapping.md), including new properties, removed properties, and changed defaults. - You can search, sort, and filter the table to show breaking changes, direct mappings, and new properties. - For more information on each property (including default values), see the [property reference](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md). [Property changes in Camunda 8.10](/versioned_docs/version-8.10/self-managed/components/orchestration-cluster/core-settings/configuration/configuration-mapping.md) ### Unified frontend application for Admin, Operate, and Tasklist Operate, Tasklist, and Admin are now accessed from a single frontend application with shared navigation, consistent design patterns, and unified deployment. Your user preferences, such as dark or light mode, are applied across all views. [Operate overview](/components/operate/operate-introduction.md) ### Upgrade readiness APIs Self-ManagedOrchestration Cluster Self-Managed Orchestration Clusters now provide upgrade readiness APIs that report whether a cluster has finished the migrations and exports it needs before you upgrade to the next minor version. Use them to avoid back-to-back minor upgrades that can put your secondary storage data at risk. [Upgrade Readiness API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#upgrade-readiness-api) ### Usage & billing metrics for 2025 enterprise license model Camunda Hub and Accounts now support the 2025 enterprise license model. - A new `licensing_model` attribute on `OrganizationMetaData` identifies if an enterprise organization is using the **2025** or **legacy** license model. If unset, it is treated as **legacy**. - If you are an organization with `licensing_model = 2025`, your Usage and Billing views only show **Process Instance (PI)** metrics. **Decision Instance (DI)** and **Unique Task User (TU)** information is no longer shown. Legacy organizations continue to see the existing metric set. - For enterprise (`salesplantype = enterprise`) organizations, the licensing model is shown in the organization details. Admins can edit this by selecting either **legacy** or **2025** via a modal action. - The enterprise onboarding wizard now includes a license selection step (defaults to **2025**). The `ExternalOnboardingRouter` accepts an optional licensing model parameter (defaulting to **2025** if not provided). ## ProcessOS ProcessOSAI agentsEarly access ### ProcessOS Harness Discover your existing processes, re-engineer them against defined outcomes, and generate executable Camunda solutions, with a governed process that keeps AI-generated work auditable. [ProcessOS Harness](/components/process-os-harness/overview.md) ## Reference architectures Self-ManagedDeployment references ### Amazon ECS reference architecture A new reference architecture details how you can run the full Camunda 8 stack on Amazon ECS, including Orchestration Cluster, Camunda Hub, and Management Identity. What’s included: - A reference architecture diagram and dependency overview for ECS. - A Terraform‑based reference deployment paired with step‑by‑step documentation. - Guidance for: - Networking, storage, secrets, and IAM (including IRSA where relevant). - Basic Day‑2 operations (scaling, updates, troubleshooting entry points). This helps support Amazon ECS as a first‑class, documented deployment option for Camunda 8 Self‑Managed, alongside Kubernetes. [Deploy to Amazon ECS](/self-managed/deployment/containers/cloud-providers/amazon/aws-ecs.md) ### Dual-region Amazon ECS reference architecture A new dual‑region reference architecture details how you can run the Orchestration Cluster and Connectors on AWS ECS with RDBMS secondary storage (such as Aurora Global Database). What's included: - Recommended topology, exporter configuration, and RDBMS replication setup. - Step‑by‑step failover and failback procedures so your platform team can design, deploy, and operate an active‑active (or active‑passive) two‑region ECS environment that meets enterprise HA/DR requirements without bespoke architecture work. [Dual-region ECS reference architecture](/self-managed/deployment/containers/cloud-providers/amazon/aws-ecs-dual-region.md) ### Kubernetes reference architecture updated for Camunda Hub With Camunda 8.10, the Kubernetes reference architecture is updated to reflect the Camunda Hub-based deployment model: - The architecture diagrams now separate Camunda Hub from the Orchestration Cluster, aligning with the recommended production topology. - The documentation clarifies how a single Camunda Hub can manage multiple Orchestration Clusters, which components are deployed with each, and how this split maps to Kubernetes namespaces and services. - Updated guidance and configuration examples help you adapt the reference architecture to your environment. Use this updated reference architecture as the starting point for new 8.10+ deployments, and as a guide when you evolve existing clusters toward a Camunda Hub-centric model. [Kubernetes reference architecture](/self-managed/reference-architecture/kubernetes.md) ### Multi-region RDBMS reference architecture Self-ManagedOrchestration Cluster A new multi-region reference architecture details how you can design, deploy, and operate one Orchestration Cluster stretched across three or more Kubernetes regions, where the Zeebe data plane is active-active across every region and the relational secondary storage is active-standby, with a single global writer and replication owned by the database. What's included: - A zone-aware topology for primary storage that keeps its Raft quorum when a region is lost, so processing continues without an operator step. - A multi-region RDBMS as secondary storage, with the asynchronous replication monitoring that lets Zeebe replay exported records after a writer failover. - Cross-region networking, zone activation, region loss, and failback procedures for a reference implementation on Amazon EKS. This architecture removes the recovery procedure rather than the recovery window: no operator step restores Zeebe processing after a region loss, while re-election, client rerouting, and database writer promotion still take time. [Multi-Region RDBMS](/self-managed/concepts/multi-region/multi-region-rdbms.md) ## Secondary storage Self-ManagedData ### Archive by ID for Elasticsearch and OpenSearch Archiving of finished process instance data in Elasticsearch and OpenSearch secondary storage now uses a targeted, incremental approach by default. - Documents are moved in small, targeted batches rather than in a single operation, improving stability and reducing resource pressure during archiving. - The `rolloverBatchSize` and `reindexBatchSize` properties control how many process instances and individual documents are processed per batch. [Data retention](/self-managed/components/orchestration-cluster/core-settings/concepts/data-retention.md) ### Async replication support for RDBMS secondary storage Camunda 8.10 adds first-class support for asynchronously replicated relational databases as secondary storage, including AWS Aurora and PostgreSQL. - The exporter layer detects when the active RDBMS endpoint is unreachable, including during a standby promotion or cross-region failover, and pauses export operations automatically rather than entering an error state. Export position is preserved in the Zeebe log and replayed on reconnection. - After failover, a reconciliation path replays missing events from the Zeebe log to close any replication lag gap, restoring a consistent secondary storage state without manual data repair. A single-exporter configuration is now supported for deployments where the RDBMS handles cross-region replication natively. [RDBMS configuration](/self-managed/concepts/databases/relational-db/configuration.md) ### Elasticsearch 9.x and OpenSearch 3.x support Camunda 8.10 supports Elasticsearch 9.4+, Elasticsearch 8.19+, OpenSearch 3.5+, and OpenSearch 2.19+. Operators can upgrade their search layer to the latest certified versions without impact on process history, active instance visibility, or incident management. [Supported environments](/reference/supported-environments.md) ### Elasticsearch index sizing and replication New comprehensive Elasticsearch configuration documentation explains how to: - Size your Elasticsearch cluster for Camunda 8 workloads. - Configure index replicas to achieve fault‑tolerant indices in multi‑node clusters. - Adjust retention and rollover intervals to avoid oversharding while meeting your data‑retention requirements. This documentation helps Self‑Managed customers: - Avoid oversharding (too many shards per node). - Prevent index unavailability and related Operate/Tasklist errors. - Reduce Elasticsearch‑related incidents in production. [Install Camunda for production with Helm](/self-managed/deployment/helm/install/production/index.md) ### New RDBMS version support Camunda 8.10 adds support for new relational database versions. Operators running Self-Managed Camunda clusters can upgrade their database layer to the latest supported versions without disruption to running process instances. New supported versions include Amazon Aurora PostgreSQL 18, MariaDB 12.3, Microsoft SQL Server 2025, and MySQL 9.7. [RDBMS version support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) ### Rolling upgrades You can now perform rolling upgrades of self-managed Camunda 8 between patch and minor versions with zero downtime across all supported secondary storage backends, including Elasticsearch, OpenSearch, and relational databases. - The cluster stays operational during a rolling upgrade: workflows continue executing, and Operate remains accessible for monitoring and incident response. - Schema changes between versions are strictly backwards-compatible and applied transparently. [Rolling upgrades](/self-managed/deployment/helm/configure/database/rdbms-schema-management.md#rolling-upgrades) ## 8.10.0-alpha5 | Release date | Changelog(s) | Blog | | :--------------- | :-------------------------------------------------------------------------------------------------- | :--- | | 8 September 2026 | [ Camunda 8 core ](https://github.com/camunda/camunda/releases/tag/8.10.0-alpha5) | - | ### Agentic orchestration #### AI Agent connector: new native element templates SaaSSelf-ManagedAgentic orchestrationAI agentsConnectors The AI Agent Task and AI Agent Sub-process connectors are now available as new, native element templates, running on new job types and giving native access to each LLM provider's own SDK and wire format, including extended thinking and prompt caching configuration where supported. Provider and backend selection are now decoupled: for example, the Anthropic provider can run through AWS Bedrock Mantle, and the OpenAI provider through Microsoft Foundry (Azure), while keeping each provider's own configuration options. The legacy element templates are deprecated as of Camunda 8.10. This is a major redesign of the AI Agent connector, available from 8.10 only, and requires manually [migrating](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-upgrade.md) each element from the still-functioning legacy connector. See the [release announcement](/reference/announcements-release-notes/8100/8100-announcements.md#ai-agent-connectors-redesigned-templates-legacy-templates-deprecated) for more details. :::note The legacy connector is deprecated in 8.10, but is not removed and continues to work. Adopting the new template is a manual, per-element migration, not an automatic upgrade. ::: #### Improved agent tool configuration SaaSSelf-ManagedAgentic orchestrationAI agentsDesktop ModelerCamunda Hub New features help you more easily configure your agent tools when modeling. | Feature | Description | Available in | | :-------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------ | | Fix | Automatically detect and apply a safe fix for an agent misconfiguration. If a `fromAi()` key or an output key is detected as invalid, click the **Fix** button to apply a fix.Applying a fix only rewrites the invalid part of a field value. The **Fix** button is only shown if the field has an existing (misconfigured) value. | Desktop ModelerCamunda Hub | | Input from agent, Output to agent | Automatically fill in the `fromAi()` inputs or the `toolCallResult` output configuration of an agent tool contract. **Input from agent**: Use to add a correctly structured agent-supplied input for a blank input mapping or blank FEEL-capable element-template field.**Output to agent**: Use to map a tool result back to the agent.Autofill is only available for a blank field, and becomes unavailable as soon as a field holds a value (so it can never replace your entered values). | Desktop ModelerCamunda Hub | | Lint rule checking | Agent tool configuration lint rule checking helps you avoid agent misconfiguration and errors when modeling.Linting rules identify and highlight malformed `fromAi()` inputs, missing or incorrect `toolCallResult` output mappings, and missing tool descriptions before they cause silent runtime failures.Configuration errors are highlighted in the Modeler. Select an error to navigate to and highlight the affected field (including fields supplied by connector templates). Inline guidance is shown to help you resolve the error. | Desktop ModelerHeadless BPMN linting.Camunda Hub | [Assisted tool configuration](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-tool-definitions.md#assisted-tool-configuration) :::note - Changes are explicit, apply only when the correction is deterministic, and can be undone. - These configuration features are only available inside an ad-hoc sub-process marked as agentic through either the `io.camunda.agenticai.toolContainer` property or an out-of-the-box AI Agent element template. It is not available in a plain sub-process. You might need to [update your element template](/components/modeler/reference/modeling-guidance/rules/agent-fromai-contract.md#declare-a-sub-process-as-agentic) to use this new feature. ::: #### Real-time agent visibility and monitoring SaaSSelf-ManagedAgentic orchestrationAI agentsOperate Monitor and evaluate AI agent behavior in Operate. - View each agent's execution [state](/components/agentic-orchestration/agent-states-and-metrics.md#agent-states) (thinking, calling a tool, idle) highlighted on the process diagram, as well as its current tool calls, [usage metrics](/components/agentic-orchestration/agent-states-and-metrics.md#usage-metrics) (tokens, tool calls, and model calls against the configured limit), model, and system prompt. - Trace the full reasoning chain behind AI agent decisions in the [conversation history](/components/agentic-orchestration/agent-definitions-and-instances.md#conversation-history-and-loop-iterations) such as user prompts, assistant messages, tools selected with the agent's reasoning, and tool calls with navigation to the corresponding diagram elements, so you can see exactly which messages, inputs, and tool responses informed each of the agent's next steps. - [External agents](/components/agentic-orchestration/connect-external-agent.md) built with frameworks such as LangGraph or CrewAI get the same visibility through the new [Agent Instance API](/components/agentic-orchestration/agent-definitions-and-instances.md#visibility-for-external-agents). [Monitor your AI agents with Operate](/components/agentic-orchestration/evaluate-agents/monitor-ai-agents.md) :::note If you modeled the agent element before Camunda 8.10, you must [update its element template](/reference/announcements-release-notes/8100/8100-announcements.md#ai-agent-connectors-redesigned-templates-legacy-templates-deprecated) to at least v1 (version 13) or v2 to enable this feature. ::: ### Camunda design system Self-ManagedCamunda HubOperate The new Camunda visual design system is introduced with this alpha for Self-Managed deployments. - A new, streamlined design system offers a cleaner, more consistent look across components. - Accessibility improvements are built in, and the updated navigation menu makes it easier to find your way around. - The new design system is enabled by default in Self-Managed for Camunda Hub and Operate. :::note The new design system will be introduced for SaaS deployments with the 8.10 minor release. ::: ### Camunda Hub #### Modeling menu improvements SaaSSelf-ManagedCamunda Hub When you create, append, or change an element, the menu groups insertion options into two tabs: - **BPMN**: Standard BPMN elements, organized by their usual categories. - **Reusable assets**: Assets, connectors, and templates from your project, along with existing project resources such as forms, called processes, decisions, and RPA scripts. [Find reusable assets in the modeling menus](/components/hub/workspace/modeler/element-templates/use-catalog-assets.md#find-reusable-assets-in-the-modeling-menus) #### New organizational structure for workspaces and projects SaaSSelf-ManagedCamunda Hub A new organizational structure for workspaces and projects is introduced with this alpha. With this new file resource hierarchy: - Workspaces now **only** contain projects and IDP projects. - Files and folders are stored inside projects. - Previously, a workspace (called a project in Web Modeler) could contain process applications (now projects), folders, and files. The new **Workspace > Project > File/folder** hierarchy makes resources more discoverable and your workspaces more scalable. :::note Your SaaS Web Modeler data, now part of Camunda Hub, was updated during the 29 August 2026 maintenance window to support this new structure. ::: #### Project versioning model SaaSCamunda Hub A new versioning model for workspaces, projects, and file resources is introduced with this alpha. Workspaces now only contain projects and IDP projects on the root level. Folders and files are stored inside projects. **Projects**: The new project versioning model uses snapshots to save the current state of all the project files, in a single action. This helps you track a project throughout its development lifecycle and ensures the correct state is referenced. **File versioning:** Every BPMN diagram, DMN diagram, form, RPA script, README file, and test file keeps a version history, a single timeline of the autosaves and named versions created as you work. You can open that history to view an earlier state of the file, compare any two entries, restore an entry, or copy one to another project. :::note This new versioning model will be introduced for Self-Managed deployments with the 8.10 minor release. ::: #### Runtime connection in Camunda Hub SaaSSelf-ManagedCamunda Hub You can now view and choose which environment you are connected to in Camunda Hub. - Connector-credential names from the connected environment autocomplete in your FEEL expressions. - Task testing runs against the connected environment. - Choose the environment from the bottom panel bar of the diagram, in the **Implement** tab, to model against your real runtime. ### Connectivity SaaSNetworking #### Secure connectivity with AWS inbound PrivateLink for Camunda 8.7 Inbound AWS inbound PrivateLink with cluster authentication is now supported for Camunda 8.7 SaaS on AWS. Follow the Secure Connectivity (AWS PrivateLink) guide to configure your VPC endpoint. [Secure connectivity (AWS PrivateLink)](/components/saas/secure-connectivity/index.md) ### Connectors #### AWS Connectors updated to AWS SDK for Java v2 SaaSSelf-ManagedConnectors All AWS connectors are updated to use AWS SDK for Java v2. This ensures Camunda AWS connector implementations are using supported client libraries and reduces maintenance risk, as AWS SDK for Java 1.x reached end of support on 31 December 2025. #### Connector Management observability improvements SaaSSelf-ManagedConnectors Connector Management now provides a unified view of inbound and outbound connectors in Camunda Hub. - The refreshed experience adds status summaries, search, filtering, sorting, per-runtime health and metrics, richer process details, clearer activity logs, and direct links to Operate. - Operators can also reset inbound connector executables from the UI, while webhook activity logs expose redacted request metadata and bounded body previews to make troubleshooting easier. [Manage your connectors](/components/hub/organization/manage-clusters/manage-connectors.md) #### Storage connector improvements SaaSSelf-ManagedConnectors The following improvements are made to storage connectors (S3, Azure Blob, GCS): - These connectors now support direct object creation from variables and better content extraction for document references. - You can now generate .json, .txt, .csv, or binary files inline without relying on the Document Store. Documents with incorrect content-types can be read using conversion options (for example, "read as text", "read as JSON"). ### Helm chart deployment #### Elasticsearch index sizing and replication Self-ManagedHelm charts New comprehensive Elasticsearch configuration documentation explains how to: - Size your Elasticsearch cluster for Camunda 8 workloads. - Configure index replicas to achieve fault‑tolerant indices in multi‑node clusters. - Adjust retention and rollover intervals to avoid oversharding while meeting your data‑retention requirements. This documentation helps Self‑Managed customers: - Avoid oversharding (too many shards per node). - Prevent index unavailability and related Operate/Tasklist errors. - Reduce Elasticsearch‑related incidents in production. [Install Camunda for production with Helm](/self-managed/deployment/helm/install/production/index.md) #### Helm migration and validation tool Self-ManagedHelm charts The new Helm migration and validation tool can help you upgrade from Camunda 8.9 to 8.10 on Kubernetes with Helm. Use the tool to: - Read your existing 8.9 Helm values (for example, values.yaml). - Generate a sample 8.10 values file reflecting: - Bitnami sub‑charts removal. - Hub‑aware deployment patterns. - Simplified application configuration. - Produce a migration report that: - Lists the keys that were migrated automatically. - Flags keys that require manual decision (for example, infrastructure endpoints, security‑sensitive options). - Suggests where to find more information in the documentation. - Can validate an existing 8.10 values file (for example, one drafted by hand or by an AI tool) against Camunda’s migration rules. The CLI is non‑interactive, with clear exit codes and optional JSON output, making it suitable for humans using the command line, CI pipelines, and AI agents (for example, Claude Code, Copilot) that can use it as part of an automated migration workflow. #### IRSA Document store support Self-ManagedHelm charts Camunda 8 Self‑Managed now supports using IAM Roles for Service Accounts (IRSA) with the AWS S3 document store: - You can deploy Camunda 8 on Amazon EKS with the document store configured for S3 without providing static AWS credentials. - The Helm chart no longer requires AWS access keys when IRSA is in use and allows pods to rely solely on their IAM role for S3 access. - Existing deployments using static AWS keys can migrate to IRSA following documented steps. Refer to the following updated Helm configuration and secret management documentation for more details: - See [IAM Roles for Service Accounts (IRSA)](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/irsa.md#document-store-s3) for the IAM role and trust policy, Helm chart configuration, service account annotations, and verification steps for the AWS S3 document store. - See [document handling configuration in Helm](/self-managed/concepts/document-handling/configuration/helm.md) for the `global.documentStore.type.aws.irsa.enabled` setting and other AWS S3 document store options. - See [Helm charts secret management](/self-managed/deployment/helm/configure/secret-management.md#credential-precedence-with-irsa) to learn how static AWS credentials take precedence over IRSA. [Camunda Helm chart](/self-managed/deployment/helm/index.md) #### REST API, RDBMS, and Document Store support for physical tenants Support for physical tenant isolation in 8.10 is added for the Camunda 8 REST API, RDBMS storage, and Document Store. ### Integrations #### Camunda for Slack Self-ManagedSaaSIntegrations Camunda for Slack brings tasks, processes, and notifications into Slack through the same App Integrations backend as Microsoft Teams. Everything runs through the `/camunda` slash command, the Camunda direct message, and channel mentions; there is no tab app. From Slack, you can list and filter tasks, claim and release them, complete a task from a Block Kit modal, start a process, switch organization and cluster, and subscribe a channel or direct message to user task notifications. Slack also works with the App Integrations connector in both directions: a process can send a Slack message, and a Slack message can reach a process. Microsoft Teams and Slack are independent. You can run either on its own, or both against the same backend. This feature is released as an early access alpha feature. [Camunda for Slack](/components/camunda-integrations/app-integrations/slack.md) ### Optimize #### Delete a process definition's data via API SaaSSelf-ManagedOptimize Optimize now exposes a public API endpoint to delete all analytics data for a given process definition, so you can remove data for retired processes or respond to data removal requests without manually touching Elasticsearch or OpenSearch. The deletion runs asynchronously: the API accepts and queues the request, then processes it in the background. [Delete process definition data](/apis-tools/optimize-api/delete-process-definition-data.md) #### Object variables no longer flattened by default in Self-Managed Self-ManagedOptimize Starting in 8.10, Optimize no longer flattens object variables by default in Self-Managed deployments. Object variables are not flattened into per-property fields, and their raw values are no longer stored. This significantly reduces Optimize storage and CPU usage and aligns Self-Managed with the default Camunda 8 SaaS behavior. - If you rely on object variable properties in reports, filters, or Raw Data Reports, you can opt in by setting `zeebe.includeObjectVariableValue: true` (env `CAMUNDA_OPTIMIZE_ZEEBE_INCLUDE_OBJECT_VARIABLE=true`). - If the setting is not explicitly configured, Optimize logs a `WARN` on startup stating that object variables will not be flattened, and details the opt-in setting. - SaaS deployments are unaffected as this behavior is already disabled. **Recovery:** The Optimize importer is idempotent. As long as the object variables still exist in the `zeebe-record-variable\*` indices (within your Zeebe retention window), you can enable the flag and reset the importer to reimport/flatten historical variables. [Object variables configuration](/self-managed/components/optimize/configuration/object-variables.md) ### Orchestration Cluster #### Centralized Secret Resolution via Zeebe SaaSSelf-ManagedCamunda 8 RunOrchestration ClusterZeebe Centralized secret resolution through Zeebe is introduced with this alpha. Processes can reference credentials from customer-managed secret stores without persisting secret values in Camunda. - Reference secrets as `camunda.secrets.NAME` in input mappings and expressions. The legacy `{{secrets.NAME}}` syntax continues to work. - Secrets are resolved automatically for activated jobs and can also be requested through the Gateway APIs `/v2/secrets/resolve` and `/v2/secrets/list`. - Resolved values are not written to engine state, exports, backups, Operate, Tasklist, or application logs. - Self-Managed deployments support AWS Secrets Manager and GCP Secret Manager with workload identity authentication. A file-based provider, backed by a mounted Kubernetes secret or a local directory, is also available and can be used in production as well as for local development. - SaaS requires no configuration and uses Camunda’s managed secret backend. - Camunda 8 Run uses the file-based provider: create one file per secret (filename = secret name, contents = value), and set `camunda.secrets.stores.file.default.path` to that directory in the Camunda 8 Run application configuration. **Migration:** Existing processes continue to work without changes. For new processes, use `camunda.secrets.NAME`. To migrate hardcoded or connector-specific credentials, store the value in a supported secret store and replace it with a centralized secret reference. **Limitations:** This feature does not yet include HashiCorp Vault or Azure Key Vault support, secret access audit logging, per-process secret restrictions, or centralized resolution for Hybrid Connector Runtimes. Cache entries expire after the configured TTL, which is 20 minutes by default. #### New rebalance API for coordinated leadership transfer SaaSSelf-ManagedOrchestration Cluster Coordinated leadership transfer for Orchestration Clusters is introduced with this alpha. The existing rebalance endpoint asks every leader to step down at once and returns immediately, without guarantee that the intended broker wins the resulting election. The new rebalance API transfers leadership deterministically, ensuring transfer in most cases in a way that is both minimally disruptive and observable. | Feature | Description | | :-------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Coordinated rebalancing API | `POST /cluster/v2/rebalance` starts a rebalance, `GET /cluster/v2/rebalance` reports the cluster's balance state and the progress of each partition, and `DELETE /cluster/v2/rebalance` stops a running rebalance once the transfer in flight has finished. The endpoint requires cluster-admin credentials. | | Deterministic transfers | Leadership is handed directly to the partition's highest-priority replica instead of being left to an open election, so a rebalance reaches the intended leader layout. | | Minimal disruptions | Transfers are sequenced one partition at a time across the cluster, so at most one partition is affected at any moment, rather than every partition becoming leaderless simultaneously. | | Configurable | The replication lag a desired leader is allowed to have, how long a partition may wait for that leader to catch up, and how long to wait for a leaderless partition can all be set as cluster defaults and overridden per request. When the desired leader cannot take over in time, the partition resumes under its current leader. | | Observable | `POST /cluster/v2/rebalance?dryRun=true` returns the plan a rebalance would carry out, without pausing any partition or moving any leadership. Each partition reports how its transfer ended or why it was skipped (already led by the desired leader, replication lag too high, replication timed out, and so on), so an incomplete rebalance can be diagnosed easily. | :::important - The previous /actuator/rebalance endpoint continues to work unchanged, and is superseded by the new API. - There are some cases where rebalancing is still not guaranteed, notably where the desired leader is simply not available or becomes unavailable during the operation. Such cases require manual retries once the desired leader of a given partition is back online. ::: #### Process instance suspension and resumption SaaSSelf-ManagedOrchestration ClusterOperate You can now suspend and resume a running process instance without canceling it. Suspending halts execution at its current point: no jobs activate or complete, no events correlate, and no timers fire. Resuming picks up from exactly where execution stopped, with no loss of progress or data. - Suspend or resume a single instance, or a batch of instances at once, from Operate or the REST API. - Variables are the one exception to the halt — you can still read and update variables on a suspended instance, so you can fix data before resuming. - Timers whose due dates pass during suspension fire immediately on resume rather than waiting out their remaining duration. - Messages and signals are not correlated to a suspended instance; publishing itself is unaffected. [Suspend and resume a process instance](/components/operate/userguide/suspend-resume-process-instance.md) #### Reference architecture for Amazon ECS Self-ManagedOrchestration Cluster A new reference architecture details how you can run the full Camunda 8 stack on Amazon ECS, including Orchestration Cluster, Camunda Hub, and Management Identity. What’s included: - A reference architecture diagram and dependency overview for ECS. - A Terraform‑based reference deployment paired with step‑by‑step documentation. - Guidance for: - Networking, storage, secrets, and IAM (including IRSA where relevant). - Basic Day‑2 operations (scaling, updates, troubleshooting entry points). This helps support Amazon ECS as a first‑class, documented deployment option for Camunda 8 Self‑Managed, alongside Kubernetes. [Deploy to Amazon ECS](/self-managed/deployment/containers/cloud-providers/amazon/aws-ecs.md) #### Reference architecture for dual-region ECS RDBMS Self-ManagedOrchestration Cluster A new dual‑region reference architecture details how you can run the Orchestration Cluster and Connectors on AWS ECS with RDBMS secondary storage (such as Aurora Global Database). What's included: - Recommended topology, exporter configuration, and RDBMS replication setup. - Step‑by‑step failover and failback procedures so your platform team can design, deploy, and operate an active‑active (or active‑passive) two‑region ECS environment that meets enterprise HA/DR requirements without bespoke architecture work. [Dual-region setup (ECS Fargate)](/self-managed/deployment/containers/cloud-providers/amazon/aws-ecs-dual-region.md) #### Task testing supports call activities SaaSSelf-ManagedOrchestration ClusterCamunda HubDesktop ModelerOperate Task testing now supports call activities in both Desktop Modeler and Camunda Hub. Testing a call activity starts the deployed called process, shows its progress in the execution log with a link to open it in Operate, and reports incidents raised inside it. [Task testing](/components/modeler/task-testing.md) ## 8.10.0-alpha4 | Release date | Changelog(s) | Blog | | :------------- | :-------------------------------------------------------------------------------------------------- | :--- | | 11 August 2026 | [ Camunda 8 core ](https://github.com/camunda/camunda/releases/tag/8.10.0-alpha4) | - | ### Agentic orchestration #### Agentic control plane Agentic orchestrationAI agentsOptimize Use the Optimize agentic control plane dashboard to monitor AI agent adoption, token usage, reliability, and performance across your processes in a single view. The dashboard is primarily intended to help operators, process owners, and engineering leads who manage AI-agent-powered processes, and need to keep them reliable and cost-effective. [Agentic control plane](/components/optimize/userguide/agentic-control-plane.md) ### Camunda Hub #### BPMN element menu improvements Camunda HubDesktop Modeler The create, append, and change menus now group BPMN elements by category, such as tasks, gateways, events, and so on. Each category includes a short description so you can quickly find the right element. - Search still searches across all categories. - When appending, elements that continue a flow subtly indicate where the flow continues next. Select this to open the append pad with a prominent **Append** action. [Model a process](/components/modeler/bpmn/bpmn.md) #### Define operations in your own element templates Camunda HubDesktop Modeler Element templates support the `steps` and `presets` keys to offer several predefined configurations within a single template. Use `steps` to define the menu users navigate when they apply the template, and `presets` to define the property values each operation applies. Operation names, descriptions, and keywords are matched by search, so your operations are as discoverable as the templates themselves. [Predefined configurations](/components/modeler/element-templates/template-metadata.md#predefined-configurations-steps-and-presets) #### Hide the Add members button Self-ManagedCamunda Hub In Self-Managed, you can now hide the **Add members** button on the workspace **Members** page, preventing non-organization admins from adding members via the UI. They can still add members via the [modify collaborator API endpoint](https://hub.camunda.io/swagger-ui/index.html#/Collaborators/modifyCollaborator) if granted access. [Feature flag reference](/self-managed/components/hub/configuration/properties.md#hide-add-members-button) #### Optimize data filters in Camunda Hub SaaSCamunda HubOptimize You can now configure Optimize data filters directly in Camunda Hub cluster settings, without editing Helm values or configuration files. The **Data filters** section in cluster settings lets you: - Enable or disable Optimize export filtering per cluster. - Include or exclude process definitions by exact `bpmnProcessId`. - Include or exclude variable names by prefix — for example, `business_` includes all variables whose names start with `business_`. - Exclusion takes precedence over inclusion when both are configured. New SaaS clusters include a default `business_` variable include filter, which limits Optimize to variables starting with `business_` to reduce Elasticsearch storage and shard usage. Existing clusters show data filters disabled with a one-click opt-in — no automatic migration occurs. Saving filter changes triggers a rolling restart of the Orchestration Cluster; the cluster is briefly unavailable while it restarts. :::warning Filtered records are permanently excluded from Optimize and cannot be recovered even if you relax the filters later. ::: [Configure Optimize data filters](/components/saas/clusters/settings.md#data-filters) ### Connectors #### Find a connector by the operation you want to perform ConnectorsCamunda HubDesktop Modeler Built-in connector templates now describe their operations, so you can model by the action you want to take instead of the product that provides it. Searching in the create, append, or change element menu for `upload object` or `send email` returns the matching operations of every connector as their own entries, and selecting one applies the connector with that operation preselected. Connectors with several operations show their operations as a nested menu, and the operation selection is now the first group in the properties panel. Connectors that provide a single operation are also renamed to describe their action — for example, **REST Outbound Connector** is now **Send REST Request**. Existing process models are unaffected. [Integrate a built-in connector](/components/connectors/use-connectors/configuring-out-of-the-box-connector.md) #### Receive Microsoft Teams and Slack messages in a process Connectors The **App Integrations connector** now receives messages as well as sending them. A process can start from a message someone writes to the Camunda app in Microsoft Teams or Slack, and a process that is already holding a conversation receives the reply, so an approval, a choice, or a correction can be collected in the chat people are already in rather than in a separate form. Receiving needs no connector task and no job worker. Element templates for a chat start event, an intermediate catch event, a receive task, and a boundary event set up the correlation, so a start event and a catch event are enough for a working conversation loop. A **Chat key** on the start event decides which chats it answers: configure a Microsoft Teams channel or chat in the Camunda app's **Settings** tab, or a Slack channel or direct message with `/camunda chat`, and give the process the same key. A reply always reaches the cluster whose process asked the question. In a personal chat or direct message every message reaches the process; in a channel on either platform, @mention the Camunda app. [Receive a chat message](/components/connectors/out-of-the-box-connectors/app-integrations.md#receive-a-chat-message) #### Send Microsoft Teams and Slack messages without managing credentials Connectors The new **App Integrations connector** sends messages to Microsoft Teams and Slack, and creates channels, through your organization's Camunda app integrations. The connection is configured once for the environment, so no endpoint or credentials appear in the process model. Messages can address a Microsoft Teams channel, user, or conversation, a Slack channel or user, or a Camunda recipient — an assignee, candidate users, or candidate groups — which the connector resolves to whichever platforms those people have connected. Alongside plain text you can send an Adaptive Card, a Block Kit payload, or a Camunda form. The result reports every destination reached and every one that failed, so a process can react to a partial delivery. [App Integrations connector](/components/connectors/out-of-the-box-connectors/app-integrations.md) ### Helm chart deployment #### Camunda Hub replaces Console and Web Modeler in the Helm chart Self-ManagedHelm chartsCamunda Hub Camunda Hub is a drop-in replacement for Web Modeler in the Camunda Helm chart, and Console is no longer a standalone deployment. The `camunda/hub` image serves both Console and Web Modeler features, and you enable and configure it with the `camundaHub` key. - For standard deployments, only the top-level key needs to change: replace `console.enabled` and `webModeler.enabled` with `camundaHub.enabled`. - Existing Web Modeler Helm values keep working. A compatibility layer in the application honors the existing value structure, and deprecated keys are logged but not required to change immediately. - Moving your values under `camundaHub` is cleanup that you can do later. The upgrade guide documents the steps. - Review `camundaHub.restapi.resources` after upgrading, because Console now runs in the Hub REST API pod. [Consolidate Console and Web Modeler into Camunda Hub](/self-managed/upgrade/helm/890-to-8100.md#consolidate-console-and-web-modeler-into-camunda-hub) ### Operate #### Business ID visibility for decision instances Operate Business ID is now visible in Operate for decision instances, in both the decision instance list and the decision instance details view. Filter decision instances by business ID using **Equals**, **Contains**, and **Is one of** in the filter UI, or the full operator set (`$eq`, `$neq`, `$exists`, `$like`, `$in`, `$notIn`) via the API. [Business ID](/components/operate/userguide/basic-operate-navigation.md#business-id-for-decision-instances) ### Optimize #### Client bearer tokens are now classified for permission checks Self-ManagedOptimize Optimize now classifies each bearer token as belonging to a user or a machine-to-machine (M2M) client, using `camunda.security.authentication.oidc.username-claim` and `client-id-claim`, and enforces your configured Optimize permission only on tokens it classifies as a user's. A token Optimize can't classify is treated as belonging to a user, and checked against your configured Optimize permission. [Optimize authentication in Self-Managed](/self-managed/concepts/authentication/authentication-to-optimize.md#configure-oidc-for-optimize) ### Orchestration Cluster #### Elasticsearch 9.x and OpenSearch 3.x support Self-ManagedOrchestration Cluster Camunda 8.10 supports Elasticsearch 9.4+, Elasticsearch 8.19+, OpenSearch 3.6+, and OpenSearch 2.19+. Operators can upgrade their search layer to the latest certified versions without impact on process history, active instance visibility, or incident management. The Self-Managed [reference architectures](/self-managed/reference-architecture/reference-architecture.md) ship these versions out of the box. The Elasticsearch clusters they deploy through the ECK operator run Elasticsearch 9.x, and the Amazon OpenSearch domains they provision through Terraform run OpenSearch 3.x. If you based your deployment on an earlier copy of a reference architecture, upgrade your search layer to a supported version before you move to 8.10. [Supported environments](/reference/supported-environments.md) #### FEEL context variables for the process instance Orchestration Cluster The process instance properties are now accessible in FEEL expressions via the `camunda.processInstance` context, resolvable anywhere in the process. `camunda.processInstance.key` returns the process instance's system-generated key, and `camunda.processInstance.businessId` returns its business ID (or `null` if none is set). [FEEL context variables](/components/concepts/process-instance-creation.md#feel-context-variables) #### Late Business ID assignment Orchestration Cluster You can now assign a business ID to a running process instance that has none, using the `POST /process-instances/{processInstanceKey}/business-id-assignment` REST endpoint, the `AssignProcessInstanceBusinessId` gRPC command, or by including `businessId` in a job completion request. The assignment is single and irreversible, and only available while business ID uniqueness enforcement is disabled. [Late Business ID assignment](/components/concepts/process-instance-creation.md#late-business-id-assignment) #### Physical Tenant identity support Self-ManagedOrchestration Cluster Physical Tenants now support independent per-tenant authorization. - Each Physical Tenant enforces its own roles, mapping rules, and permissions. - Users can have different roles on different Physical Tenants, such as a developer role on one, and a viewer role on another. - Cluster-wide operations (topology, backups, restore) are protected by a claim-based cluster admin role, with no new infrastructure required. - Identity providers are defined at the cluster level. Each Physical Tenant chooses which IdPs it can accept. [Physical Tenant isolation model](/self-managed/concepts/physical-tenants/index.md) [Set up two isolated Physical Tenants](/self-managed/concepts/physical-tenants/getting-started.md) #### Rolling upgrades Self-ManagedOrchestration Cluster You can now perform rolling upgrades of self-managed Camunda 8 between patch and minor versions with zero downtime across all supported secondary storage backends, including Elasticsearch, OpenSearch, and relational databases. - The cluster stays operational during a rolling upgrade: workflows continue executing, and Operate remains accessible for monitoring and incident response. - Schema changes between versions are strictly backwards-compatible and applied transparently. [Rolling upgrades](/self-managed/deployment/helm/configure/database/rdbms-schema-management.md#rolling-upgrades) #### S3-compatible object stores for Document Handling Self-Managed Document Handling now supports any S3-compatible object store such as MinIO, Cloudian, or Garage alongside Amazon S3, Google Cloud Storage, and Azure Blob Storage. - Configure an S3-compatible backend by pointing the existing AWS S3 document store to your custom provider endpoint. - No migration is required for existing AWS S3 deployments. [Document handling configuration](/self-managed/concepts/document-handling/configuration/index.md) #### Unified authentication for the Orchestration Cluster and Optimize Self-ManagedOrchestration ClusterOptimize Optimize can now be configured with the same `camunda.security.authentication.*` settings already used by the Orchestration Cluster, so you configure authentication once, in one place. Nothing changes for the Orchestration Cluster, which already used these settings in 8.9. Optimize continues to accept its 8.9 authentication settings in 8.10, translating the recognized properties to their new equivalents at startup, but those 8.9 properties are deprecated and will be removed in a future release. Confirm your `camunda.security.authentication.oidc.issuer-uri` and `.audiences` settings match your IdP before upgrading Optimize. User, group, role, tenant, and permission management for Optimize is unchanged in this release, and is still handled by Management Identity. [Optimize authentication in Self-Managed](/self-managed/concepts/authentication/authentication-to-optimize.md) #### Unified frontend application for Admin, Operate, and Tasklist OperateTasklistAdmin Operate, Tasklist, and Admin are now accessed from a single frontend application with shared navigation, consistent design patterns, and unified deployment. Your user preferences (such as dark/light mode) are applied across all views, with consistent navigation patterns throughout the interface. [Operate overview](/components/operate/operate-introduction.md) ### Tasklist #### Business ID in Tasklist Tasklist Business ID is now visible in Tasklist, in both the task list and task detail views. Filter tasks by business ID using **Equals**, **Contains**, and **Is one of** in the filter dialog, or the `$neq`/`$exists`/`$notIn` operators via the API. [Business ID filter](/components/tasklist/userguide/using-filters.md#business-id-filter) ## 8.10.0-alpha3 | Release date | Changelog(s) | Blog | | :----------- | :-------------------------------------------------------------------------------------------------- | :--- | | 14 July 2026 | [ Camunda 8 core ](https://github.com/camunda/camunda/releases/tag/8.10.0-alpha3) | - | ### Agentic orchestration #### AI agent testing assertions in Camunda Process Test AI agentsAgentic orchestration You can now test non-deterministic AI agent behavior in Camunda Process Test (CPT) with conditional behavior controls and evaluation-based assertions. With this, you can validate agent behavior and output quality with clearer, more reliable test outcomes. - Define conditional behavior in tests with a `when(condition).then(action)` API for activation-based flow control. - Assert output quality with LLM-as-a-judge expectations when exact matching is not enough. - Assert semantic similarity with embedding-based comparison for responses that vary in phrasing. - Configure remote or local models through code and properties for both local development and CI/CD pipelines. [Test your AI agents with CPT](/components/agentic-orchestration/evaluate-agents/test-ai-agents.md) ### APIs & tools #### Invite members through the public API who haven't logged in yet SaaSSelf-Managed Adding a workspace member through the public API — `PUT /v1/collaborators` or `POST /v2/workspaces/{workspaceKey}/members` — no longer requires the invitee to have already logged in to Camunda Hub at least once. If the email address belongs to an organization member with no local user yet, Camunda now creates a pending invitation and sends an invitation email, the same as when inviting through the Camunda Hub UI. The invitee gains workspace access once they accept the invitation. [Add or update a member](/apis-tools/hub-api-saas/specifications/add-member.api.mdx) #### Public Camunda Hub API A new Camunda Hub API is provided under `/v2/` for programmatic access to the resources previously managed in Console and Web Modeler. The API aligns with the Orchestration Cluster API guidelines, with standardized error handling and data-fetching patterns. The Console Self-Managed and Web Modeler APIs are deprecated in favor of the Camunda Hub API. See the [release announcement](/reference/announcements-release-notes/8100/8100-announcements.md#console-sm-and-web-modeler-apis-deprecated) for details. [Camunda Hub API](/apis-tools/hub-api-saas/overview.md) :::note The Camunda Hub API is not yet exposed in Camunda 8. To access it, please reach out to [Camunda success](https://camunda.com/services/camunda-success/). ::: ### Camunda Hub #### Bespoke cluster generations for SaaS SaaSCamunda Hub Organizations can now access exclusive Camunda 8 generation versions tailored specifically for their organization, available for both new cluster creation and upgrades. These generations are not visible to other organizations. #### Catalog SaaSSelf-ManagedCamunda Hub The new Camunda Hub catalog gives your center of excellence (CoE) a governed, organization-wide place to publish approved element templates, so delivery teams can reuse trusted building blocks instead of rebuilding them in each project. - Manage element templates and their metadata in your own Git repository, and use a CI/CD pipeline to publish them to the catalog through the Hub API whenever the approved set changes. - Browse, search, and filter published assets in Hub, and read each asset's details before you apply it while modeling. - Track where each asset version is used, and identify projects that still use an outdated version. - Unpublish assets you no longer want used. Elements that already use an unpublished asset keep working and show a deprecation hint. [Manage the catalog](/components/hub/organization/manage-catalog/index.md) #### Safe deletion with a 30-day recovery window Camunda Hub Deleting an item in Camunda Hub no longer removes it immediately. Deleted workspaces, projects, files, folders, and IDP projects are moved to **Recently deleted** for 30 days. During that time, users with the appropriate permissions can see who deleted an item and when, and restore it. After 30 days, items are permanently deleted. Deletion no longer corrupts project version history, as existing snapshots continue to reference deleted files correctly. The recovery window applies to deletions made in 8.10 and later; items deleted before upgrading cannot be recovered. [Recover deleted resources](/components/hub/workspace/manage-projects/recently-deleted.md) #### Select a target version when upgrading a cluster SaaSCamunda HubOrchestration Cluster When you upgrade an Orchestration Cluster that has more than one valid upgrade target, Camunda Hub now shows a version selection step in the upgrade wizard. Each option displays the generation name and the Zeebe patch version. The recommended version (the longest upgrade path) is pre-selected and labeled **latest**, and you can choose a different option before proceeding. Clusters with only one upgrade target keep the existing flow. #### Self-service restore for SaaS orchestration clusters from backups SaaSCamunda HubOrchestration Cluster Organization admins can now restore a SaaS orchestration cluster directly from a completed backup in Camunda Hub and through the Administration API. Key benefits: - Reduced time to recovery for operational incidents. - Operational control without opening a support ticket for standard same-cluster restores. - Clear restore status visibility during execution. This release supports in-place restore for the same cluster only. During restore, the cluster is unavailable until completion. Known limitations: - Same-cluster restore only. - Partition count must match between backup and target cluster. - Cross-region and cross-cluster restore are not supported in this release. [Restore a cluster from backup](/components/saas/how-to-restore.md) #### Start a process instance with a business ID Camunda HubDesktop Modeler You can now set a business ID when starting a process instance directly from Camunda Hub or Desktop Modeler. The business ID field is available in the start process instance dialog alongside variables. [Business ID](/components/concepts/process-instance-creation.md#business-id) #### Test process segments in Play Camunda Hub When testing your process with Play in Camunda Hub, you can now capture and rerun targeted sections of an agentic process as low-code integration tests: - Run segment tests individually or in batches to validate process changes faster. - Test BPMN elements like connectors, DMN, forms, and LLM tasks without a full end-to-end run. - Reuse saved segment tests during iterative model changes to catch regressions earlier. [Play your process](/components/hub/workspace/modeler/validation/test-your-process.md) ### Desktop Modeler #### Variables panel improvements Desktop Modeler When you hover over "written in X elements" or an element ID in the variables panel, the diagram now highlights the corresponding element or elements so you can quickly see where a variable is used. FEEL expressions in the variable outline now use the same syntax highlighting as the FEEL editor, with more granular tokens that distinguish function names from arguments and operators from literals, making complex expressions easier to read. When no element is selected on the canvas, the variables panel now highlights the process (root) scope, matching how it highlights the scope of a selected element. Variable value previews also no longer repeat the opening brackets of nested objects and arrays, so previews are easier to scan. [Inspect variables](/components/modeler/data-handling.md#inspecting-variables) ### Helm chart deployment #### Docker images ZeebeOperateTasklist Camunda no longer produces the following Docker images in Camunda 8.10 and later, or in Camunda 8.9 from patch release 8.9.12: - [camunda/zeebe](https://hub.docker.com/r/camunda/zeebe) - [camunda/operate](https://hub.docker.com/r/camunda/operate) - [camunda/tasklist](https://hub.docker.com/r/camunda/tasklist) Use the unified [camunda/camunda](https://hub.docker.com/r/camunda/camunda) Docker image instead. ### Operate #### Business ID filtering in Operate Operate Operate now exposes business ID as a filter field for process instances. You can filter using **Equals**, **Contains** (with `*` and `?` wildcards), and **Is one of** — or use the full operator set (`$eq`, `$neq`, `$exists`, `$like`, `$in`, `$notIn`) via the API. [Business ID](/components/concepts/process-instance-creation.md#searching-and-filtering-by-business-id) #### Multi-variable filtering Operate In Operate, you can now combine multiple variable filters with `AND` logic to find exactly the process instances you need. Filter by variable name, value, and comparison operators, such as `equals`, `contains`, `greater than`, and `less than`, including nested JSON paths. [Multi-variable filters](/components/operate/userguide/filter-process-instances.md#multi-variable-filters) #### Wait states OperateOrchestration Cluster API Operate now shows what an active process instance is waiting for. When you inspect an active element, you can see the wait state and its details, for example, a timer's due date, a receive task's message name and correlation key, a signal name, a condition expression, or a job's type and state. Wait state tracking is enabled by default and writes records to secondary storage. In Camunda 8 Self-Managed, you can [disable it](/self-managed/concepts/wait-states/configure.md) if you do not want to track this data. [Wait states](/components/wait-states/overview.md) ### Optimize #### Optimize disabled by default on new trial clusters SaaSOptimize On new trial clusters in Camunda 8 SaaS, Optimize is now disabled by default. When Optimize is disabled, the overview shows a muted tile with an **Enable Optimize** prompt so it stays discoverable. Upgrading from a trial to a paid plan automatically enables Optimize, with no manual action required. ### Orchestration Cluster #### Archive by ID for Elasticsearch and OpenSearch Self-ManagedOrchestration Cluster Archiving of finished process instance data in Elasticsearch and OpenSearch secondary storage now uses a targeted, incremental approach by default. Documents are moved in small, targeted batches rather than in a single operation, improving stability and reducing resource pressure during archiving. The `rolloverBatchSize` and `reindexBatchSize` properties control how many process instances and individual documents are processed per batch. [Data retention](/self-managed/components/orchestration-cluster/core-settings/concepts/data-retention.md) #### Async replication support for RDBMS secondary storage Self-ManagedOrchestration Cluster Camunda 8.10 adds first-class support for asynchronously replicated relational databases as secondary storage, including AWS Aurora and PostgreSQL. The exporter layer detects when the active RDBMS endpoint is unreachable, including during a standby promotion or cross-region failover, and pauses export operations automatically rather than entering an error state. Export position is preserved in the Zeebe log and replayed on reconnection. After failover, a reconciliation path replays missing events from the Zeebe log to close any replication lag gap, restoring a consistent secondary storage state without manual data repair. A single-exporter configuration is now supported for deployments where the RDBMS handles cross-region replication natively. [RDBMS configuration overview](/self-managed/concepts/databases/relational-db/configuration.md) #### Business ID in message correlation Orchestration Cluster API You can now include a business ID when publishing or correlating a message. Business ID acts as an additional filter alongside the message name and correlation key. Supported combinations for start events: message name alone; name + business ID; name + correlation key; name + correlation key + business ID. For non-start events, business ID is usable alongside name + correlation key. When both a correlation key and business ID are provided, both fields must match the corresponding values stored on the subscription. If business ID uniqueness is enabled, a blocked message-start waits in the buffer until the active instance releases the business ID or the TTL expires — it is not dropped immediately. [Business ID in message correlation](/components/concepts/messages.md#business-id-in-message-correlation) #### Business ID propagation in call activities Orchestration Cluster Call activities now support configuring the business ID assigned to the child process instance. Child instances inherit the parent's business ID by default (unchanged from 8.9). You can override this per call activity with a literal value or FEEL expression. The FEEL context variable `camunda.processInstance.businessId` provides access to the parent's ID within the expression. The resolved value is set once at child creation and is immutable. [Business ID propagation](/components/modeler/bpmn/call-activities/call-activities.md#business-id-propagation) #### Cluster variable metadata Orchestration Cluster You can now add metadata to cluster variables as a map of string keys to scalar values (strings or numbers). Camunda stores the metadata alongside the variable but keeps it separate from its value. Use metadata to discover and filter variables by semantic attributes without inspecting their values. The search endpoint supports metadata filters with equality, numeric range, existence, `in`, and `like` operators for each key. Metadata is not exposed as part of the FEEL-accessible runtime value. [Cluster variable metadata](/components/modeler/feel/cluster-variable/metadata.md) #### Dual-region ECS reference architecture Self-ManagedOrchestration Cluster Camunda 8.10 adds a dual-region reference architecture for running the Orchestration Cluster and Connectors on AWS ECS with an RDBMS secondary storage such as Aurora Global Database. The documentation covers the recommended topology, exporter configuration, and RDBMS replication setup, and includes step-by-step failover and failback procedures for active-active and active-passive two-region ECS environments. [Dual-region ECS reference architecture](/self-managed/deployment/containers/cloud-providers/amazon/aws-ecs-dual-region.md) #### Multi-tenancy support in SaaS SaaSOrchestration Cluster Camunda 8 SaaS now officially supports multi-tenancy via tenant identifiers, bringing the same logical tenant isolation model available in Self-Managed to SaaS clusters. :::note Multi-tenancy is available on SaaS clusters running generation **8.8 and later** — including existing 8.8 and 8.9 clusters. You do not need to upgrade to 8.10 to use this feature. ::: - Owners and Admins can create, update, and delete tenants in Camunda Hub, and assign users, groups, and client credentials to them. - Camunda Hub and Desktop Modeler support tenant-scoped deployments to multi-tenant clusters by specifying a tenant ID. - Tenant usage is reflected in Camunda Hub reporting so org owners can monitor tenant consumption across a cluster. Multi-tenancy is enabled at the cluster level. Process definitions, instances, and decisions are scoped to the tenant they were deployed to, keeping data isolated across teams and applications sharing a single cluster. [Multi-tenancy](/components/concepts/multi-tenancy.md) #### New RDBMS version support Self-ManagedOrchestration Cluster Camunda 8.10 adds support for new relational database versions. Operators running Self-Managed Camunda clusters can upgrade their database layer to the latest supported versions without disruption to running process instances. New supported versions include Amazon Aurora PostgreSQL 18, MariaDB 12.3, Microsoft SQL Server 2025, and MySQL 9.7. [RDBMS version support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) #### Physical Tenant support Self-ManagedOrchestration Cluster Camunda 8.10 introduces Physical Tenant support for RDBMS, enabling strong isolation across tenants. - The REST API and gRPC API are exposed per Physical Tenant. `CamundaClient` supports tenant selection over both REST and gRPC. - Web apps (Operate, Tasklist, and Admin) are accessible per Physical Tenant at `/physical-tenants//`. - Authentication is configurable as `basic auth` or OIDC at the cluster level, with support for multiple OIDC providers assigned to individual Physical Tenants. [Physical Tenant isolation model](/self-managed/concepts/physical-tenants/index.md) [Set up two isolated Physical Tenants](/self-managed/concepts/physical-tenants/getting-started.md) #### Region-aware partition placement Self-ManagedOrchestration Cluster Camunda 8.10 introduces region awareness to the Orchestration Cluster. Operators declare which region each broker belongs to using a topology label, and the engine uses those declarations to distribute partition replicas across regions, ensuring no single region holds a quorum for any partition. Leader election priorities respect region boundaries, preferring region-local leaders under normal conditions and adjusting automatically when a region becomes unavailable. The same mechanism extends to availability zone or datacenter isolation using the same configuration. [Orchestration Cluster configuration properties](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md) #### Select a DMN version with a FEEL expression Orchestration Cluster You can now call a dynamically calculated version of a DMN decision from a BPMN business rule task by specifying the version with a FEEL expression. [Business rule tasks](/components/modeler/bpmn/business-rule-tasks/business-rule-tasks.md#defining-a-task) ## 8.10.0-alpha2 | Release date | Changelog(s) | Blog | | :----------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--- | | 09 June 2026 | [ Camunda 8 core ](https://github.com/camunda/camunda/releases/tag/8.10.0-alpha2)[ Connectors ](https://github.com/camunda/connectors/releases/tag/8.10.0-alpha2) | - | ### Agentic orchestration #### Judge assertions in CPT JSON Test Cases AI agentsAgentic orchestration Camunda Process Test (CPT) now supports **judge assertions** in JSON test cases. - Define judge assertions using [JSON test case instructions](/apis-tools/testing/json-test-cases.md#reference-instructions). - Use a preconfigured judge from `camunda-container-runtime.properties` or Spring application properties depending on the test execution context. #### Processes MCP Server Orchestration Cluster AI agents can use the [Processes MCP Server](/apis-tools/processes-mcp/processes-mcp-overview.md) to discover and call deployed BPMN processes as [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) tools. When you deploy a process with an MCP start event it is automatically registered as a callable tool. MCP clients connect to the `/mcp/processes` endpoint and can invoke any registered process, with the Orchestration Cluster starting a new process instance and immediately returning the process instance key. The server also exposes [static tools](/apis-tools/processes-mcp/processes-mcp-static-tools.md) for inspecting running process instances, so agents can check variables, state, and incidents without switching servers. #### Skills repository for pro-code AI enablement AI agentsAgentic orchestrationEarly access The Camunda Skills repository toolset enables AI coding agents to build, validate, and configure Camunda artifacts. With the Skills installed, your AI agent can: - Build and modify BPMN diagrams with a human-readable layout. - Configure connectors using element templates (no raw XML). - Generate form schemas with validation. - Create and edit DMN decision tables. - Run BPMN lint rules against generated diagrams. - Scaffold and wire Camunda Process Test (CPT) integration tests. ### APIs & tools #### Camunda 8 Run no longer requires Java Camunda 8 Run now includes a bundled Java runtime. This means you no longer need to install OpenJDK or set `JAVA_HOME` before starting Camunda 8 Run. [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) #### FEEL evaluation with process instance key Orchestration Cluster API The `POST /v2/expression/evaluation` endpoint now optionally evaluates expressions in the context of: - A process instance, via `processInstanceKey`. - A flow node instance, via `elementInstanceKey`. The endpoint: - Combines process instance variables, element-local variables (for element scope), cluster variables, and optional request context into a single evaluation context. - Enforces `EXPRESSION:EVALUATE` plus `PROCESS_DEFINITION:READ_PROCESS_INSTANCE` on the underlying process definition. - Requires exactly one of `processInstanceKey` or `elementInstanceKey` (mutually exclusive); sending both returns `400 Bad Request`. Behavior remains free from side effects and uses the same timeout and guardrails as the existing cluster-scope evaluation. #### Removal of deprecated APIs, Zeebe Client, and Zeebe Process Test Orchestration Cluster API The deprecated Operate and Tasklist APIs are removed. Process data, task management, and operational queries are now served through the [Orchestration Cluster API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). [Migrate to the Orchestration Cluster API](/apis-tools/migration-manuals/migrate-to-camunda-api.md) The Zeebe Client is removed and replaced by the [Camunda Java Client](/apis-tools/java-client/getting-started.md). This covers process deployment, message correlation, and job handling. [Migrate to the Camunda Java Client](/apis-tools/migration-manuals/migrate-to-camunda-java-client.md) The Zeebe Process Test library is removed and replaced by [Camunda Process Test](/apis-tools/testing/getting-started.md). This provides richer assertions, Spring integration, and alignment with the Orchestration Cluster API surface. ### Camunda Hub #### Low-code test CI/CD compatibility Test files in Test Studio now use the same schema as Camunda Process Test (CPT). You can record a test in Test Studio and run it in your CI/CD pipeline through CPT without converting formats, and load CPT-authored test files into Test Studio to debug them visually. - Use one JSON schema across Test Studio and CPT: record once, run anywhere. - Existing Play test scenario files are migrated automatically to the new format. [Test files](/components/hub/workspace/modeler/validation/test-files.md) ### Desktop Modeler #### Support for start forms in Desktop Modeler Desktop Modeler Desktop Modeler now supports defining form references on [none start events](/components/modeler/bpmn/none-events/none-events.md) in Camunda 8 BPMN models, matching the existing Camunda Hub capability. You can configure start forms directly in Desktop Modeler's properties panel using: - Camunda Form (linked): Reference a deployed Camunda Form by ID. - Camunda Form (embedded): Embed form JSON in the BPMN diagram (deprecated). Start forms can now be defined and edited in both modelers, ensuring a seamless experience when working with diagrams across Camunda Hub and Desktop Modeler. ### Helm chart deployment #### Bitnami subcharts removed from the Helm chart Self-ManagedHelm charts The Camunda Helm chart no longer includes the bundled Bitnami subcharts for PostgreSQL, Elasticsearch, and Keycloak. Helm installations must connect to external infrastructure, such as managed databases and search services, Kubernetes operators, or customer-owned images. If you still use Bitnami subcharts on 8.8 or 8.9, migrate to external or vendor-supported infrastructure on 8.9 before upgrading to 8.10; the 8.10 Helm chart has no Bitnami-based fallback. See the [release announcement](/reference/announcements-release-notes/8100/8100-announcements.md#bitnami-subcharts-removed-from-the-helm-chart) for details. [Migrate from Bitnami subcharts](/self-managed/deployment/helm/operational-tasks/migration-from-bitnami/index.md) ### Operate #### Business ID visibility in Operate Operate Business ID is now visible in Operate for process instances. The `businessId` field appears in the process instance list and the process instance details view. [Business ID](/components/concepts/process-instance-creation.md#business-id) ### Optimize #### Scope-aware variable export configuration for Optimize Optimize You can now configure variable export behavior by scope: - You can enable or disable root (process instance) variables and local variables independently. - You can exclude all local variables by default, while still allowing specific local variables by name pattern. - Configuration integrates with the existing variable filtering mechanism, using consistent syntax and semantics. Terminology aligns with Camunda 8 docs: - **Root scope/process instance scope**: Variables visible across the process. - **Local variables**: Variables defined in child scopes only. With this, you can configure setups such as: - Export only root variables for all processes. - Export a curated subset of local variables (for example, `taskContextDisplayName` or specific local audit variables) without exposing all locals. ### Orchestration Cluster #### Default RocksDB memory allocation strategy changed to `FRACTION` Self-ManagedOrchestration Cluster The default RocksDB memory allocation strategy changes from `PARTITION` to `FRACTION`. RocksDB memory is now allocated as a fraction of total available memory (default `0.1`, or 10%) instead of scaling with the number of partitions per broker. This may result in a different amount of memory being allocated to RocksDB. To keep the previous behavior, explicitly set the strategy to `PARTITION`. See the [release announcement](/reference/announcements-release-notes/8100/8100-announcements.md#rocksdb-memory-allocation-strategy) for more details. [Zeebe memory allocation](/components/best-practices/architecture/sizing-self-managed.md#memory) #### Edit roles and tenants in Admin Self-ManagedSaaSAdmin You can now edit the name and description of a role or tenant directly in Admin, without deleting and re-creating it. Existing assignments stay in place. - Default roles and the `` tenant are system entities and cannot be edited. - Role and tenant IDs cannot be changed after creation. [Update a role](/components/admin/role.md#update-a-role) [Update a tenant](/components/admin/tenant.md#update-a-tenant) #### Multi-Instance activity execution listeners Self-ManagedSaaSOrchestration Cluster Execution listeners can now be configured on the enclosing body of multi-instance activities. `beforeAll` listeners run once per multi-instance body activation, before the `inputCollection` is evaluated and inner instances are created, making listener-produced variables available to the body's `inputCollection` expression. - Replicate Camunda 7 multi-instance and execution listener patterns without redesigning your process. - Dynamically calculate collections using custom logic or external data before instance creation. [`beforeAll` listeners](/components/concepts/execution-listeners.md#beforeall-listeners) ## 8.10.0-alpha1 | Release date | Changelog(s) | Blog | | :----------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--- | | 13 May 2026 | [ Camunda 8 core ](https://github.com/camunda/camunda/releases/tag/8.10.0-alpha1)[ Connectors ](https://github.com/camunda/connectors/releases/tag/8.10.0-alpha1) | - | ### Agentic orchestration #### AI Agent connector: Conversation storage SPI redesign AI agentsAgentic orchestrationConnectors The conversation storage SPI used by [custom AI Agent storage backends](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-customization.md#custom-conversation-storage) has been redesigned. Built-in stores are migrated transparently; custom `ConversationStore` implementations must be updated. See the [release announcement](/reference/announcements-release-notes/8100/8100-announcements.md#ai-agent-connector-spi-and-package-changes) for more details. #### Camunda-provided LLM for SaaS AI agentsAgentic orchestrationSaaS You can now run any AI Agent on Camunda 8 SaaS in minutes using the [Camunda-provided LLM](/components/agentic-orchestration/camunda-provided-llm.md), without wiring your own LLM credentials. Whether you start from a Camunda-provided agentic blueprint or build your own agent from scratch, the required credentials are populated automatically as cluster secrets, so there is little to no extra setup needed to get started. The included budget is sufficient for hundreds or thousands of agent runs even on a trial account, depending on the model used. Enterprise organizations must explicitly enable the **Camunda Provided LLM** toggle in Camunda Hub, which is separate from the **AI-powered features** toggle. This dramatically reduces time-to-first-running-agent by removing the need for external LLM infrastructure or credential setup on day one. #### MCP start event element template Self-ManagedSaaSCamunda HubDesktop Modeler The **MCP start event** element template is now available in Camunda Hub and Desktop Modeler. Apply it to a BPMN message start event to configure the process as an MCP tool with name, purpose, inputs, and usage guidance for LLMs. See [MCP start event](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-start-event.md) for the full property reference. #### Standalone evaluation assertions for judge and semantic similarity AI agentsAgentic orchestration Camunda Process Test now exposes **judge-based evaluation** and **semantic similarity evaluation** as standalone AssertJ assertions for arbitrary string values, without requiring process-variable assertions. Semantic similarity checks support configurable embedding models and thresholds, and both assertion types reuse the existing CamundaAssert configuration with optional local overrides. ### APIs & tools #### In-memory OAuth credentials cache by default for the Java client Self-ManagedSaaSJava clientSpring SDK The Camunda Java client now caches OAuth credentials **in memory by default**. The file-based cache at `$HOME/.camunda/credentials` is no longer enabled out of the box and is available as an explicit opt-in. Why this change: - The previous default tried to create `$HOME/.camunda/credentials` on first use. In hardened container environments — non-root users (Kubernetes `securityContext.runAsUser`, OpenShift), read-only root filesystems, immutable images — this raised `AccessDeniedException`/`IOException` at first cache write. Affected users had to apply a non-obvious workaround (mount a writable volume and point an environment variable at it) just to get a client to start. - Memory-only caching removes that footgun: clients work out of the box in any deployment topology, and the in-process token cache plus proactive refresh still avoid unnecessary token endpoint calls during a JVM's lifetime. - The file cache had also been a source of latent corruption when multiple JVMs shared the same `$HOME`; making it opt-in restricts its use to deployments where persistence across restarts is genuinely needed. How to opt in to the file-based cache (behavior identical to pre-8.10): - Java client builder: `new OAuthCredentialsProviderBuilder().credentialsCachePath("/path/to/cache")`. - Spring property: `camunda.client.auth.credentials-cache-path: /path/to/cache`. - Environment variable: `CAMUNDA_CLIENT_CONFIG_PATH=/path/to/cache` (or `ZEEBE_CLIENT_CONFIG_PATH` for the legacy Zeebe client). If you previously set `CAMUNDA_CLIENT_CONFIG_PATH` / `ZEEBE_CLIENT_CONFIG_PATH` only to work around the non-root container error, you can now remove that configuration and rely on the in-memory default. [Spring Boot starter configuration](/apis-tools/camunda-spring-boot-starter/configuration.md#credentials-cache-path) ### Camunda Hub #### Cluster version selection for SaaS Orchestration Clusters SaaSCamunda HubOrchestration Cluster You can now create new SaaS Orchestration Clusters on specific supported Camunda 8 minor and patch versions, including: - The latest recommended versions (latest patch of each active minor) - Other still-supported versions that you already run on existing clusters in the same organization. #### Execution listener configurable header support Self-ManagedSaaSCamunda HubDesktop Modeler Execution listeners now support configurable headers, aligned with service task job headers. - In BPMN, execution listeners can define ``. The headers are passed to the listener’s job worker alongside any base-element headers, with listener headers overriding on key conflicts. - In Modeler, you can configure execution listener headers visually (name/value pairs) without editing BPMN XML. - Listener workers can consume these headers as metadata and configuration parameters using the same patterns as service task job workers. #### Usage & billing metrics for 2025 enterprise license model SaaSCamunda Hub Camunda Hub and Accounts now support the 2025 enterprise license model. - A new `licensing_model` attribute on `OrganizationMetaData` identifies if an enterprise organization is using the **2025** or **legacy** license model. If unset, it is treated as **legacy**. - If you are an organization with `licensing_model = 2025`, your Usage and Billing views only show **Process Instance (PI)** metrics. **Decision Instance (DI)** and **Unique Task User (TU)** information is no longer shown. Legacy organizations continue to see the existing metric set. - For enterprise (`salesplantype = enterprise`) organizations, the licensing model is shown in the organization details. Admins can edit this by selecting either **legacy** or **2025** via a modal action. - The enterprise onboarding wizard now includes a license selection step (defaults to **2025**). The `ExternalOnboardingRouter` accepts an optional licensing model parameter (defaulting to **2025** if not provided). ### Helm chart deployment Helm chartsSelf-Managed #### Host network support for Orchestration Cluster pods The 8.10 Helm chart adds `orchestration.hostNetwork` (default: `false`), which lets Orchestration Cluster pods share the host node's network namespace. This is useful in bare-metal or restricted network environments where pods must be reachable directly via the node IP rather than a cluster overlay network. When `orchestration.hostNetwork` is set to `true` and `orchestration.dnsPolicy` is not set, the chart automatically uses `dnsPolicy: ClusterFirstWithHostNet` to preserve in-cluster DNS resolution. You can override this by setting `orchestration.dnsPolicy` explicitly. ```yaml orchestration: hostNetwork: true ``` For details, see [configure pod networking](/self-managed/deployment/helm/configure/pod-networking.md). ### Integrations #### Microsoft Teams routing and permission-aware task actions Self-ManagedSaaSIntegrations Camunda for Microsoft Teams now supports routing incident and task collaboration to private channels, shared channels, and group chats. Notifications and task actions in Teams now align with Camunda assignment and access rules, ensuring that only eligible users are notified and allowed to act. ### Intelligent document processing (IDP) #### Support for ABBYY as an IDP Provider Self-ManagedSaaSIDP Camunda IDP now supports [ABBYY](https://www.abbyy.com/) as a document extraction provider. ### Operate #### JSON display in Operate SaaSOperate Camunda 8.10 introduces an update to the JSON display functionality in Operate (SaaS). You can now: - Open JSON variables in a dedicated JSON viewer directly from the variables panel, without entering editing mode. - View JSON values with consistent, easier to understand formatting. - Copy full JSON variable values to the clipboard. - Use the improved in-line variables display. This change helps navigate more complex data during operations and troubleshooting. ### Orchestration Cluster #### Cancel execution listener Self-ManagedSaaSOrchestration Cluster Execution listeners now support a `cancel` event type on the process element. Cancel listeners run when a process instance is terminated — useful for cleanup, audit logging, or notifying external systems. For details, see [`cancel` listeners](/components/concepts/execution-listeners.md#cancel-listeners). --- ## What's new in Camunda 8.10 ## Why upgrade to Camunda 8.10? Upgrading to Camunda 8.10 delivers significant benefits and keeps your installation aligned and ready for future releases. - **[Agentic orchestration](#agentic-orchestration)**: Real-time agent visibility and explainability with live agent state, tool calls, and conversation history, providing production-grade agentic trust with testing, visibility, auditability, and control-plane monitoring. - **[ProcessOS](#processos)**: Discover your existing processes, re-engineer them against defined outcomes, and generate executable Camunda solutions, with a governed process that keeps AI-generated work auditable. - **[Camunda Hub](#camunda-hub)**: A new product that replaces Web Modeler and Console as the single place to build, govern, and run process solutions in Camunda. It introduces workspaces to organize your teams' work, along with a [catalog](#catalog) of reusable automation assets, a [business value dashboard](#business-value-dashboard) to track process outcomes against targets, and [credentials](#hub-credentials-manager) you create once and reuse across processes. - **[Multi-region resilience](#multi-region-resilience)**: Failure-domain-aware partition placement replicates process state synchronously across regions, so losing a region costs no committed data (RPO 0). The RDBMS secondary storage replicates asynchronously and catches up from the engine's event stream. - **[Strong tenant isolation via physical tenants](#strong-tenant-isolation-via-physical-tenants)**: Enterprise-grade physical isolation with per-tenant APIs, web apps, roles and identity provider selection. Logical multi-tenancy becomes officially supported on SaaS. :::note Release information For a full summary of what's included in Camunda 8.10, including all breaking changes, deprecations, and supported environment changes, see [release announcements](/reference/announcements-release-notes/8100/8100-announcements.md) and [release notes](/reference/announcements-release-notes/8100/8100-release-notes.md). ::: ## Upgrade guides {#upgrade-guides} Ready to upgrade? The following guides offer detailed information on how to upgrade to Camunda 8.10. **Guide** **Description** **Who is this guide for?** [Self-Managed upgrade guide](/self-managed/upgrade/index.md) Evaluate your infrastructure, understand operational changes, and choose the best update strategy for your environment. Operations and platform administrators of Self-Managed installations. [APIs & tools upgrade guide](/apis-tools/migration-manuals/migrate-to-810.md) Plan and execute an upgrade from Camunda 8.9 to 8.10, focusing on API and tools transitions. Application developers maintaining Camunda-based solutions in Self-Managed Kubernetes or VM environments.Developers using Camunda APIs and tools. ## Summary of important changes Important changes in Camunda 8.10 are summarized as follows: **What's new/changed** **Summary** [Agentic orchestration](#agentic-orchestration) Real-time agent visibility and explainability, production-grade agentic trust with testing, visibility, auditability, and control-plane monitoring. [ProcessOS](#processos) Discover, re-engineer, and generate executable Camunda solutions with a governed, auditable process. [Camunda Hub](#camunda-hub) Build, govern, and run your process solutions. Hub replaces Web Modeler and Console. [Catalog](#catalog) Manage, publish, and reuse vetted automation assets across teams in Hub. [Business value dashboard](#business-value-dashboard) Track cycle time, automation rate, activity, and agentic adoption against targets in Hub. [Hub credentials manager](#hub-credentials-manager) Create connector credentials once and reuse them wherever you need them in Hub. [Environments](#environments) Deploy and run processes in named environments assigned to workspaces. [Environment connection](#environment-connection-in-modeler) Connect the modeler in Hub to an environment to use its credentials and run task tests against it. [Multi-region resilience](#multi-region-resilience) Multi-region resilience framework for Self-Managed Orchestration Cluster deployments. [Strong tenant isolation](#strong-tenant-isolation-via-physical-tenants) Physical Tenants provide strong physical data isolation within a single cluster. [Business ID](#business-id) Business ID is now a first-class, searchable attribute across the Orchestration Cluster. [Camunda design system](#camunda-design-system) The new visual design system is introduced for Admin, Hub, and Tasklist. [Centralized secret resolution](#centralized-secret-resolution-via-zeebe) Reference credentials from customer-managed secret stores. [Connector operations](#connector-operations) Connectors are now discoverable by the operation you want to perform. [Credentials in Desktop Modeler](#credentials-in-desktop-modeler) Select and create reusable credentials on connector tasks in Desktop Modeler. [Helm chart deployment](#helm-chart-deployment) A new Camunda Helm Toolkit helps migrate and validate 8.9-to-8.10 Helm values. [Low-code testing](#low-code-testing) Record, assert, repair, and run low-code tests in Test Studio and in CI/CD with Camunda Process Test. [Optimize](#optimize) Optimize's component-specific authentication configuration keys are deprecated. [Unified authentication](#unified-authentication-for-orchestration-cluster-and-optimize) Shared authentication implementation based on Orchestration Cluster authentication. [Wait states](#wait-states) Operate now shows what an active process instance is waiting for. [APIs & Tools](#apis--tools) Legacy component APIs, Tasklist V1-dependent features, and Zeebe Process Test are removed. [Supported environments](#supported-environments) Camunda 8.10 updates platform and environment baselines. ## Agentic orchestration Important changes and new features for agentic orchestration are available in 8.10: ### Real-time agent visibility and monitoring Monitor and evaluate AI agent behavior in Operate. - View each agent's execution [state](/components/agentic-orchestration/agent-states-and-metrics.md#agent-states) highlighted on the process diagram, as well as its current tool calls, [usage metrics](/components/agentic-orchestration/agent-states-and-metrics.md#usage-metrics), model, and system prompt. - Trace the full reasoning chain behind AI agent decisions in the [conversation history](/components/agentic-orchestration/agent-definitions-and-instances.md#conversation-history-and-loop-iterations) such as user prompts, assistant messages, tools selected with the agent's reasoning, and tool calls with navigation to the corresponding diagram elements, so you can see exactly which messages, inputs, and tool responses informed each of the agent's next steps. - [External agents](/components/agentic-orchestration/connect-external-agent.md) built with frameworks such as LangGraph or CrewAI get the same visibility through the new [Agent Instance API](/components/agentic-orchestration/agent-definitions-and-instances.md#visibility-for-external-agents). [Monitor your AI agents with Operate](/components/agentic-orchestration/evaluate-agents/monitor-ai-agents.md) :::note If you modeled the agent element before Camunda 8.10, you must [update its element template](/reference/announcements-release-notes/8100/8100-announcements.md#ai-agent-connectors-redesigned-templates-legacy-templates-deprecated) to at least v1 (version 13) or v2 to enable this feature. ::: ### Agentic control plane Use the Optimize agentic control plane dashboard to monitor AI agent adoption, token usage, reliability, and performance across your processes in a single view. The dashboard is primarily intended to help operators, process owners, and engineering leads who manage AI-agent-powered processes, and need to keep them reliable and cost-effective. [Agentic control plane](/components/optimize/userguide/agentic-control-plane.md) ### AI Agent connector: New native element templates The AI Agent Task and AI Agent Sub-process connectors are now available as new, native element templates, running on new job types and giving native access to each LLM provider's own SDK and wire format, including extended thinking and prompt caching configuration where supported. Provider and backend selection are now decoupled. For example, the Anthropic provider can run through AWS Bedrock Mantle, and the OpenAI provider through Microsoft Foundry (Azure), while keeping each provider's own configuration options. The legacy element templates are deprecated as of Camunda 8.10. This is a major redesign of the AI Agent connector, available from 8.10 only, and requires manual [migration](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-upgrade.md) of each element from the current legacy connector. [Upgrade AI Agent element templates](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-upgrade.md) :::note legacy connector deprecated in 8.10 The legacy connector is deprecated in 8.10, but is not removed and continues to work. Adopting the new template is a manual, per-element migration, not an automatic upgrade. ::: ### Assisted agent tool configuration New features help you more easily configure your agent tools when modeling. - **Fix**: Automatically detect and apply a safe fix for an agent misconfiguration. If a `fromAi()` key or an output key is detected as invalid, click the **Fix** button to apply a fix. - **Input from agent**, **Output to agent**: Automatically fill in the `fromAi()` inputs or the `toolCallResult` output configuration of an agent tool contract. - **Lint rule checking**: Agent tool configuration lint rule checking helps you avoid agent misconfiguration and errors when modeling. Linting rules identify and highlight malformed `fromAi()` inputs, missing or incorrect `toolCallResult` output mappings, and missing tool descriptions before they cause silent runtime failures. :::note - Changes are explicit, apply only when the correction is deterministic, and can be undone. - These configuration features are only available inside an ad-hoc sub-process marked as agentic through either the `io.camunda.agenticai.toolContainer` property or an out-of-the-box AI Agent element template. It is not available in a plain sub-process. You might need to [update your element template](/components/modeler/reference/modeling-guidance/rules/agent-fromai-contract.md#declare-a-sub-process-as-agentic) to use this new feature. ::: [Assisted agent tool configuration](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-tool-definitions.md#assisted-tool-configuration) ### Camunda-provided LLM for SaaS You can run AI agents on Camunda 8 SaaS in minutes using the [Camunda-provided LLM](/components/agentic-orchestration/camunda-provided-llm.md), without your own LLM credentials. - Whether you start from a Camunda-provided agentic blueprint or build your own agent from scratch, the required credentials are populated automatically as cluster secrets. - The included budget is sufficient for hundreds or thousands of agent runs even on a trial account, depending on the model used. - Enterprise organizations must explicitly enable the **Camunda Provided LLM** toggle in Camunda Hub, which is separate from the **AI-powered features** toggle. [Camunda-provided LLM](/components/agentic-orchestration/camunda-provided-llm.md) ### Processes MCP Server AI agents can use the Processes MCP Server to discover and call deployed BPMN processes as [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) tools. - When you deploy a process with an MCP start event it is automatically registered as a callable tool. - MCP clients connect to the `/mcp/processes` endpoint and can invoke any registered process, with the Orchestration Cluster starting a new process instance and immediately returning the process instance key. - The server also exposes [static tools](/apis-tools/processes-mcp/processes-mcp-static-tools.md) for inspecting running process instances, so agents can check variables, state, and incidents without switching servers. [Processes MCP Server](/apis-tools/processes-mcp/processes-mcp-overview.md) ### Test AI agents with Camunda Process Test You can now test non-deterministic AI agent behavior in Camunda Process Test with conditional behavior controls and evaluation-based assertions. This helps teams validate agent behavior and output quality with clearer, more reliable test outcomes. - Define conditional behavior in tests with a `when(condition).then(action)` API for activation-based flow control. - Assert output quality with LLM-as-a-judge expectations when exact matching is not enough. - Assert semantic similarity with embedding-based comparison for responses that vary in phrasing. - Configure remote or local models through code and properties for both local development and CI/CD pipelines. **Judge assertions in JSON test cases**: Define judge assertions using JSON test case instructions. Use a preconfigured judge from `camunda-container-runtime.properties` or Spring application properties depending on the test execution context. **Standalone evaluation assertions for judge and semantic similarity**: Camunda Process Test now exposes _judge-based evaluation_ and _semantic similarity evaluation_ as standalone AssertJ assertions for arbitrary string values, without requiring process-variable assertions. Semantic similarity checks support configurable embedding models and thresholds, and both assertion types reuse the existing CamundaAssert configuration with optional local overrides. [Test your AI agents with Camunda Process Test](/components/agentic-orchestration/evaluate-agents/test-ai-agents.md) ## ProcessOS ProcessOS is an AI-powered layer on top of Camunda's agentic orchestration platform. It discovers your existing processes, re-engineers them against defined outcomes, and generates executable Camunda solutions. ProcessOS has the following key characteristics: - **A governed process**: The harness runs your engagement through four phases: scope, discover, transform, and implement. Each phase ends at a milestone, and subject matter expert (SME) review cycles act as gates between milestones. - **Auditable by design**: The governance process itself runs on Camunda. Project state lives in Camunda, and every artifact is committed to Git, so AI-generated work stays reviewable at every step. - **Two supported use cases**: Legacy migration transforms processes running on a legacy system to Camunda 8. AI transformation turns any process into an automated, AI-native process that runs on Camunda 8. - **Built for builders**: ProcessOS targets builders who implement Camunda end to end and direct AI coding agents. SMEs take part as reviewers and approvers. [ProcessOS Harness](/components/process-os-harness/overview.md) :::note In this release, each project runs locally with its own private memory. Production deployment, CI/CD integration, and cross-project memory are planned for future releases. ::: ## Camunda Hub [Camunda Hub](/components/hub/index.md) is now the single place where teams build, govern, and run process solutions in Camunda. - On SaaS, your organization is migrated to Hub automatically, and you don't need to take any action. - Hub replaces Web Modeler and Console. It [maintains the features of its predecessors](#mapping-web-modeler-and-console-features-to-hub) and implements new features, all within a unified platform. - Hub is deployed only once, and serves as the single point of entry for all your [environments](/components/concepts/environments.md), connecting to the Orchestration Clusters that host your dev, staging, and production environments. **Hub changes how you and your teams work**. Instead of managing separate Web Modeler and Console instances per environment, you now use a single Hub that connects to all your environments. You design once, and manage everything from one place. In Hub, there is a clear separation of responsibilities: - **Center of excellence teams** manage organizational infrastructure, member access, and workspaces, so delivery teams have the environments and tools they need to ship process solutions at scale. - **Delivery teams** collaborate in managed workspaces and model, test, and deploy business processes. Organization-level resource governance and workspace-level project delivery now happen in one product. ### Terminology Camunda Hub introduces changes to many terms and concepts from Web Modeler and Console: | Web Modeler | Camunda Hub | Description | | :----------------------------- | :------------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Project | [Workspace](/reference/glossary.md#workspace) | When upgrading to Hub, your projects automatically migrate to workspaces. Workspaces in Hub are isolated team collaboration spaces. Members can only view workspaces they're invited to. | | Process application | [Project](/reference/glossary.md#project) | When upgrading to Hub, your process applications automatically migrate to projects. Projects in Hub can be versioned as a bundle of files or used as a folder for loose files. | | IDP application | IDP project | When upgrading to Hub, your IDP applications automatically migrate to IDP projects. | | Collaborator | Member | Workspace members replace project collaborators. Members can be added and managed per workspace. | | Cluster (as deployment target) | [Environment](/reference/glossary.md#environment) | Environment is a new concept that represents a deployment target. A cluster represents the underlying infrastructure that hosts one or more environments. Organization admins assign environments to workspaces. | | Project Admin | [Workspace Admin](/components/hub/organization/manage-workspaces/manage-workspace-members.md#workspace-roles) | This aligns with the project-to-workspace terminology change. | ### Mapping Web Modeler and Console features to Hub The following table shows how you can access the Hub equivalents for key Web Modeler and Console features. | Product (8.9) | Feature | Hub documentation | | :------------ | :---------------------------------------- | :------------------------------------------------------------------------------------------------------------ | | Console | Organization overview | [Console](/components/hub/organization/console.md) | | Console | View clusters | [View clusters](/components/saas/clusters/manage-cluster.md#view-clusters) | | Console | Organization management | [Manage organization](/components/hub/organization/manage-organization-settings/organization-settings.md) | | Web Modeler | View projects | [View workspaces](/components/hub/organization/manage-workspaces/index.md#view-existing-workspaces) | | Web Modeler | Create a project | [Create a workspace](/components/hub/organization/manage-workspaces/manage-workspace.md#create-a-workspace) | | Web Modeler | Manage project collaborators | [Manage workspace members](/components/hub/organization/manage-workspaces/manage-workspace-members.md) | | Web Modeler | Rename/delete project | [Manage workspace](/components/hub/organization/manage-workspaces/manage-workspace.md) | | Web Modeler | Connect clusters to a process application | [Assign environments to a workspace](/components/hub/organization/manage-environments/assign-environments.md) | | Web Modeler | Deploy a process application | [Deploy your project](/components/hub/workspace/manage-projects/deploy-project.md) | | Web Modeler | View shared resources | [Manage catalog](/components/hub/organization/manage-catalog/getting-started.md) or **Shared resources** | | Web Modeler | Recently deleted | [Recently deleted](/components/hub/workspace/manage-projects/recently-deleted.md) | ### Key features Camunda Hub introduces many new features, including the following highlights: #### Catalog Hub introduces the [Hub catalog](/components/hub/organization/manage-catalog/getting-started.md). Center of excellence teams can manage reusable automation assets in a Git repository and publish them to Hub. You can see where assets are being used and which processes are using outdated or deprecated assets. Delivery teams can trust that catalog assets have been vetted and approved by the center of excellence. They can discover assets in the catalog, read asset documentation, and apply them when modeling. [Use catalog assets](/components/hub/workspace/modeler/element-templates/use-catalog-assets.md) #### Business value dashboard Use the **Business Value** page in Camunda Hub to track process outcomes using cycle time, automation rate, activity, and agentic adoption metrics, and to set targets for cycle time and automation rate. - A portfolio view compares every process in the selected Orchestration Cluster and ranks off-target processes by how many targets are missed and by how far. - A process view shows the metrics and targets for a single process, including a cycle time distribution with P50, average, and P95. - Every metric is calculated from completed process instances in the selected environment. No changes to your process models are required. [Business value dashboard](/components/hub/organization/analyze-operations/business-value-dashboard.md) #### Workspaces and projects Hub introduces workspaces and projects. **Workspace**: A workspace is a collaboration environment within an organization, representing a team or business domain. A workspace is assigned members and projects so all related work happens in one shared space. When you migrate to 8.10, all your Web Modeler projects become workspaces. **Project**: A project contains a set of files. You can consider a project as a bundle of related files you can version and deploy together. You can also consider a project as a container of individual files meant to be versioned and deployed independently. When you migrate to 8.10, all your Web Modeler process applications become projects. [Manage workspaces](/components/hub/organization/manage-workspaces/index.md) [Manage projects](/components/hub/workspace/manage-projects/manage-projects.md) #### Environments Hub introduces environments as deployment targets where teams run their processes. An environment is hosted on a cluster, and the cluster remains the infrastructure that your administrators operate. - Organization admins assign environments to workspaces, and projects deploy to the environments of their workspace instead of connecting clusters to deployment stages. - In Self-Managed, each Physical Tenant of a cluster at version 8.10 or later is an environment. In SaaS, each cluster has one environment. - When you upgrade, Hub assigns the clusters that your process applications used to their workspaces as environments. [Environments](/components/concepts/environments.md) #### Project snapshots and file versioning In Web Modeler, a process application and the resources within it were tightly coupled. You could only version and deploy the resources as a single, bundled unit. Camunda Hub introduces an improved model with more granular control over project and file versions: - **[Project snapshots](/components/hub/workspace/manage-projects/project-versioning.md):** You can create project snapshots to capture the current state of all project resources. - **[File-level versions](/components/hub/workspace/modeler/modeling/versions.md):** You can now create new versions for individual files within a project. Every file maintains its own version history. - **Autosave:** All files save their state automatically after edits. - **Decoupled element template versions:** Project versions and element template versions are now created independently of each other.
New to projects? If you're not familiar with projects, the following sections explain how to: - [Deploy to environments](/components/hub/workspace/manage-projects/deploy-project.md#deployment-environments) - [Deploy a project](/components/hub/workspace/manage-projects/deploy-project.md) - [Deploy an individual resource](/components/hub/workspace/modeler/run-or-publish-your-process.md#deploy-a-process) - [Create a project snapshot](/components/hub/workspace/manage-projects/project-versioning.md#create-a-snapshot) - [Create a resource version](/components/hub/workspace/modeler/modeling/versions.md#create-a-version)
#### New file structure and requirements In Camunda 8.9, a project can contain process applications, folders, and files. Camunda 8.10 introduces a new file resource hierarchy in which workspaces only contain projects and IDP projects. Files and folders are always stored inside projects. :::note When comparing the old and new structures, keep the new [Camunda Hub terminology](#terminology) in mind . ::: For example, if this is what your data looks like in 8.9: ```txt title="Camunda 8.9" Payments (Project) ├─ main.bpmn ├─ eligibility.dmn ├─ readme.md ├─ Forms (Folder) │ ├── details.form │ └── review.form ├─ Archive (Folder) │ └── Refunds (Process application) │ ├── refunds.bpmn │ └── refund-request.form └─ Onboarding (Process application) ├── onboarding.bpmn └── kyc-checks.dmn ``` This is what the data looks like in 8.10: ```txt title="Camunda 8.10" Payments (Workspace) ├─ Payments - General (Project - TEMPORARY PLACEMENT) │ ├─ main.bpmn │ ├─ eligibility.dmn │ ├─ readme.md │ ├─ Forms (Folder) │ │ ├── details.form │ │ └── review.form │ └─ Archive (Folder) ├── Refunds (Project - MOVED) │ ├── refunds.bpmn │ └── refund-request.form └─ Onboarding (Project) ├── onboarding.bpmn └── kyc-checks.dmn ``` This strict new **Workspace > Project > File/folder** hierarchy makes resources more discoverable and your projects more scalable. #### Hub credentials manager Before 8.10, you configure a connector's authentication and connection settings directly on each connector task. This doesn't scale well and is hard to maintain. For example, if ten tasks call the same REST API, you configure the same authentication ten times, and you update all ten when something changes. 8.10 introduces credentials. These are authentication and connection configurations you create once and reuse wherever you need them. When you update a credential, that change is applied everywhere the credential is used. Camunda Hub provides an interface for managing your credentials. - Center of excellence teams create and manage credentials centrally in Hub, and see them across all environments. - Delivery teams select a credential from the properties panel of a connector task in the modeler, instead of entering the settings on every task. - Credentials created outside Hub, for example in [Desktop Modeler](#credentials-in-desktop-modeler), can be found by scanning environments and added to Hub for central management. - Credentials are stored as cluster variables, so connectors and job workers can reference them by name. [Manage credentials](/components/hub/organization/credentials/index.md) #### Environment connection in Modeler Connect the modeler in Hub to an [environment](#environments) to model, test, and review against your real runtime, instead of building in isolation. - View and choose which environment you are connected to from the bottom panel bar of the diagram, in the **Implement** tab. - Connector credential names from the connected environment autocomplete in your FEEL expressions and in the properties panel credential picker. - [Task testing](/components/modeler/task-testing.md), connector credentials, and the **Webhook** tab follow this connection. Your deploy target doesn't change. This shortens the build, review, and test cycle, because you validate against the same environment your process runs in. Test Studio doesn't follow this connection. It runs against the environment you select in the **Test** tab. [Connect to a runtime](/components/hub/workspace/modeler/modeling/connect-to-a-runtime.md) #### Recover deleted resources When you deleted a resource, such as a file or process application, in Camunda 8.9, the resource was immediately and permanently deleted, along with: - Their data in process application version history. - Their child resources if the resource is a container, such as a folder or process application. Deleted resources could not be recovered. In Camunda Hub, when you delete a resource, it is moved to **Recently deleted**. You then have 30 days to restore it before it is permanently deleted. [Recover deleted resources](/components/hub/workspace/manage-projects/recently-deleted.md) ### Roles and permissions Camunda Hub includes a number of changes to roles and permissions. #### SaaS roles and permissions SaaS organization-level roles and permissions have changed. Prior to 8.10, users in an organization were assigned either a Modeler, Analyst, or Admin role in Console. In 8.10, users in an organization are assigned one of the following roles in Camunda Hub: | Role | Description | | :----------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Member | Full access to create and collaborate on projects in workspaces they're invited to, plus read-only visibility into the organization. | | Analyst | Includes everything a Member can do, plus full access to Optimize to build process dashboards and reports. Access to specific dashboards and reports within Optimize is governed separately by [Optimize collection roles](/components/optimize/userguide/user-permissions.md). | | Organization Admin | Manages the organization, its members, and its workspaces, with full access to every workspace and project by default. Organization Admins can also assign environments to workspaces. | | DevOps | Grants cluster create and update, cluster clients, connector secrets, IP allowlisting, secure connectivity, encryption, and the connector-management view, plus Member-level modeling. Cannot manage or view organization members, billing, or organization settings. | [Roles and permissions](/components/hub/organization/users-and-roles.md#roles-and-permissions) #### Self-Managed roles and permissions Self-Managed roles and permissions have changed as follows: | 8.9 role | 8.10 equivalent | Changes | | :---------------- | :-------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Console | DevOps | Gains management access to Hub's cluster pages. | | Web Modeler Admin | Hub Admin | Gains full access to Hub's cluster pages. | | Web Modeler | Hub | No change in access. | | - | Analyst | **(New role)** Grants Hub modeling access, management access to the catalog's usage and adoption data, and full access to Optimize, without modeler-admin or people/org management access. | :::note The 8.9 roles are not removed in 8.10, and remain for backward compatibility. ::: [Default roles in Self-Managed](/self-managed/components/management-identity/application-user-group-role-management/manage-roles.md#default-roles) ### Camunda Hub API Before Camunda 8.10, you could interact with Web Modeler and Console resources through the following APIs: | API | Description | Camunda 8.10 status | | :----------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------- | | Web Modeler API v1 | Programmatically manage Web Modeler resources, like projects, process applications, and collaborators. This API now serves Camunda Hub resources, like workspaces, projects, and members under the hood. | Deprecated. Will be removed in 8.12. | | Administration API | Retrieve cluster data, including installed apps and usage metrics | Removed for Self-Managed. Still available for SaaS. | In Camunda 8.10, with Camunda Hub replacing Web Modeler and Console, the new Camunda Hub API succeeds the old Web Modeler API and, for Self-Managed, the Administration API. The Camunda Hub API unifies cluster and workspace management in a single interface. Additionally, it provides new APIs for interacting with Hub-specific features. [Migrate from Web Modeler to the Camunda Hub API](/apis-tools/migration-manuals/migrate-from-web-modeler-to-hub-api.md) ### Self-Managed Hub configuration In 8.10, Console and Web Modeler configurations have been merged to form [Camunda Hub properties](/self-managed/components/hub/configuration/properties.md). Configuration keys have been updated to support feature changes. [Self-Managed migration](/self-managed/upgrade/components/890-to-8100.md#camunda-hub) For Helm, Console is no longer a standalone deployment. The new `camunda/hub` image serves both feature sets. The `camundaHub` key enables and configures Camunda Hub: ```yaml # Before (8.9) console: enabled: true webModeler: enabled: true restapi: resources: requests: memory: 1Gi # After (8.10) camundaHub: enabled: true restapi: resources: requests: memory: 1Gi ``` Additionally, when you upgrade, your data is [migrated](/self-managed/upgrade/components/890-to-8100.md#data-migration) to the [new file structure](#new-file-structure-and-requirements). [Upgrade from Helm 8.9 to 8.10](/self-managed/upgrade/helm/890-to-8100.md) ### Web Modeler data On 29 August 2026, your SaaS Web Modeler data received three updates to prepare for Hub in 8.10, around [Organizational structure](#new-file-structure-and-requirements), data migration, and the process application versioning model.
Web Modeler data migration details ### Data migration As a Camunda 8 SaaS user, your data was migrated to the new organizational structure automatically during a scheduled maintenance window. During the migration: - Any process application nested inside a folder moved to the top level of its project. - Any files or folders located directly in a project, not inside a process application, were automatically grouped in a new process application, named `YOUR PROJECT NAME - General`. You can rename this application, [move content out of it](#organize-the-general-process-application), or otherwise reorganize it as with any other process application. - Git sync and cluster settings on existing process applications migrated unchanged along with your data. During the migration, Web Modeler was briefly unavailable. Clusters and running processes were unaffected and continued executing normally. :::note Camunda extensively tested the migration process before release and created a backup before the migration to ensure your data was recoverable in its original state if anything went wrong. If you notice anything unexpected after the migration, contact support. ::: The migration did not affect the following resources: | Area | Impact | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Running process instances | Orchestration Clusters, engines, and running process instances were unaffected. Web Modeler and Camunda Hub remained independent of the runtime path. | | Redeployment | Existing deployments remained on their clusters and continued running. The migration did not require redeployment. | | Clusters and configuration | Cluster and deployment settings attached to existing process applications migrated with the data and remained unchanged. | | Files, folders, and version history | All files, folders, versions, and history were preserved. Only their location within the project changed. | | Git-synced projects | The migration did not modify process applications or their contents. Files connected through Git sync remained in the same repository with the same history. | | Desktop Modeler | Desktop Modeler was unaffected because it has no direct connection to Web Modeler. Content shared through Git sync was also unaffected. | If you automate against the Web Modeler API, the migration may affect automation that relies on file or folder locations. Web Modeler API v1 returns files and folders from their new locations. Requests that create an item at a project's root are redirected to the new `YOUR PROJECT NAME - General` process application, and the response reflects the new location. Review any automation that relies on file or folder locations. A small number of folder API integrations were affected more directly. If you use the folder API with process applications, contact support to confirm whether your integration needs updates. #### Organize the "General" process application During the migration, any files or folders located directly in a project, not inside a process application, were automatically grouped in a new process application, named "YOUR PROJECT NAME - General". This process application is a temporary container for loose files and folders. Camunda recommends organizing these resources into process applications that reflect their purpose for better long-term discoverability and maintainability. To move files from the "General" process application, first create a new process application: 1. Open your project. 2. At the top right of the project view, click **Create new > Process application**. 3. Enter a name and select a development cluster. 4. Click **Create**. Next, move the files from the "General" process application to the new one: 1. Open your "General" process application. 2. On the left side of the file list, select all the files you want to move. 3. At the top of the file list, click **Move**. 4. Select your new process application. 5. Click **Move**. ### Process application versioning model In addition to the Web Modeler data migration, Camunda is introducing an improved process application versioning model: - File-level versions — process applications can be versioned as a bundle, as before, but now also at the single-file level. - Autosave for all files, plus file-level version history for every file. - Decoupled versioning — process application versions and element template versions are now created independently of each other. Before the new model, a process application and the resources within it were tightly coupled. You could only version and deploy the resources as a single, bundled unit. With the new model, you have more granular control. ### Working with process applications Learn more about using process applications in the following sections. #### Deploy to environments Before 8.10, you connected clusters to deployment stages in each process application. In 8.10, a project has no deployment stages. It deploys to the [environments](/components/concepts/environments.md) that are assigned to its workspace. An organization admin [assigns environments to the workspace](/components/hub/organization/manage-environments/assign-environments.md). #### Deploy a process application You can deploy a process application as a bundle from either the process application view or a resource view. In both cases, all resources in the process application are deployed together. From the process application view: 1. Open a process application. 2. At the top right of the process application view, click **Deploy & run**, or select **Deploy** from the dropdown. 3. Confirm the deployment. From the resource view: 1. In your process application, open a resource, such as a BPMN diagram or form. 2. At the top right of the modeling interface, click **Deploy**. 3. In the deployment modal, under **Resources**, select **All resources**. (This is the default.) 4. Confirm the deployment. #### Deploy an individual resource If you don't want to deploy all resources in a process application, you can deploy an individual resource: 1. In your process application, open a resource, such as a BPMN diagram or Form. 2. At the top right of the modeling interface, click **Deploy**. 3. In the deployment modal, under **Resources**, select **Only this resource**. 4. Confirm the deployment. #### Create a process application snapshot Use a snapshot to capture all files in a process application at once: 1. Open a process application. 2. On the right side of the process application view, under **Snapshots** click **Create snapshot**. 3. Enter a **Snapshot tag** in the snapshot creation modal. 4. Click **Create**. #### Create a resource version In addition to process application snapshots, you can create versions for individual resources: 1. In your process application, open a resource, such as a BPMN diagram or form. 2. At the top right of the modeling interface, click **Versions**. 3. Click **Create version**. 4. Enter a **Version name** in the version creation modal. 5. Click **Create**.
### Environments in the SaaS migration Before 8.10, you connected up to four clusters to a process application, one for each deployment stage. In 8.10, a project has no deployment stages, so Camunda Hub carries these connections forward as [environments](/components/concepts/environments.md) of the workspace. Camunda Hub assigns the environments in a separate step from the Web Modeler data migration described above. For each migrated workspace: - Every cluster that a process application in the workspace connected to any deployment stage becomes an environment assigned to the workspace. This also applies to the cluster of each IDP application. A cluster that several stages or applications used is assigned once. - If several projects are in the same workspace, the workspace receives the environments of all of them. - Projects inherit the environments of their workspace. They have no deployment stages or connected clusters anymore, and the migration doesn't set a default environment for a project. You choose the environment when you deploy. - Workspaces you create after the migration start without environments. An organization admin can change the assigned environments at any time. See [assign environments to a workspace](/components/hub/organization/manage-environments/assign-environments.md). ## Multi-region resilience Camunda 8.10 provides a structured multi-region resilience framework for Self-Managed Orchestration Cluster deployments. - **[Cold Recovery](/self-managed/concepts/multi-region/cold-recovery.md)**: Camunda's lowest-cost multi-region configuration uses scheduled cross-region backups and a manual restore procedure to recover from complete primary-region loss. Recovery measured in hours is operationally acceptable. On SaaS, [cross-region cold recovery](/components/saas/cross-region-cold-recovery.md) is also available for AWS and GCP region pairs marked **Failover supported**. - **[Dual-Region](/self-managed/concepts/multi-region/dual-region.md)**: Dual-region deployment with continuous replication. A full Camunda Orchestration Cluster runs continuously in both a primary and secondary region. - **Three-region active-active (RDBMS)**: A three-region Kubernetes deployment with the Orchestration Cluster running active-active across all three regions, backed by a relational database (RDBMS) with cross-region replication as secondary storage. Losing one region requires no operator intervention, because the cluster never loses quorum. Camunda 8.10 also adds the following capabilities to support multi-region deployments: - **Region-aware partition placement**: Operators declare which region each broker belongs to using a topology label. The engine distributes partition replicas across regions so no single region holds a quorum for any partition, and leader election prefers region-local leaders under normal conditions. The same mechanism works for availability zone or datacenter isolation. - **Async replication support for RDBMS secondary storage**: Asynchronously replicated relational databases, including AWS Aurora and PostgreSQL, are supported as secondary storage. The exporter pauses automatically when the RDBMS endpoint is unreachable, such as during a failover, and replays missing events from the Zeebe log on reconnection without manual data repair. [Multi-region resilience](/self-managed/concepts/multi-region/resilience-tiers.md) ## Strong tenant isolation via Physical tenants Camunda 8.10 introduces Physical Tenants for strong physical data isolation within a single cluster with separate data storage and independent operations per tenant. Physical Tenants still share cluster compute resources such as CPU and memory, so runtime interference is reduced but not fully eliminated. - Physical Tenant isolation is best for multiple teams or organizations needing strong isolation without the cost and complexity of separate clusters. - Physical Tenants and Logical Tenants can be used together. Each Physical Tenant can contain its own set of Logical Tenants, providing two independent layers of isolation: physical separation between top-level tenant groups, and logical separation within each group. - **Per-tenant APIs and web apps**: The REST API and gRPC API are exposed per Physical Tenant, and Operate, Tasklist, and Admin are available at `/physical-tenants//`. - **Per-tenant authorization**: Each Physical Tenant enforces its own roles, mapping rules, and permissions, so a user can have a different role on each Physical Tenant. - **Identity provider selection**: Identity providers are defined at the cluster level, and each Physical Tenant chooses which ones it accepts. Cluster-wide operations such as topology, backups, and restore are protected by a claim-based cluster admin role. - **Logical multi-tenancy on SaaS**: Camunda 8 SaaS officially supports multi-tenancy via tenant identifiers. It is available on clusters running generation 8.8 and later, so you don't need to upgrade to 8.10 to use it. [Physical Tenant isolation model](/self-managed/concepts/physical-tenants/index.md) ## Business ID Business ID is now a first-class, searchable attribute across the Orchestration Cluster. Introduced in 8.9 as an immutable domain-specific identifier, Business ID in 8.10 can be searched and filtered across process instances, decision instances, user tasks, messages, and message subscriptions. Jobs expose the Business ID in the activation response (visible, not searchable). - **Search and filter** across entity types using advanced operators (`$eq`, `$neq`, `$exists`, `$like` with `*`/`?` wildcards, `$in`). Operate and Tasklist expose Equals, Contains, and Is one of in their filter UI. - **Message correlation**: Include a Business ID in published or correlated messages as an additional filter constraint. If both a correlation key and Business ID are supplied, both fields must match the corresponding values stored on the subscription. - **Call Activity propagation**: Child instances inherit the parent's Business ID by default. Configure a literal value or FEEL expression on the call activity to override it. Use `camunda.processInstance.businessId` in FEEL expressions to reference the parent's ID. - **Start with a Business ID** from Camunda Hub or Desktop Modeler. - **Late assignment**: Assign a Business ID to a running instance that has none, when uniqueness is disabled. Assignment is forward-only: only artifacts created after the assignment carry it. [Business ID](/components/concepts/process-instance-creation.md#business-id) ## Camunda design system The new visual Camunda design system and navigation are introduced for all components with the 8.10 release in both SaaS and Self-Managed. - **Updated navigation:** A persistent sidebar makes it easy to jump between pages and accomplish tasks. - **Clear context:** The top bar shows which organization, workspace, and component you are working in. - **Designed for accessibility:** The interface follows accessibility best practices, including keyboard navigation and color contrast. ## Centralized secret resolution via Zeebe Centralized secret resolution through Zeebe is introduced in 8.10. Use and manage secrets to keep sensitive values such as API keys, passwords, and tokens, out of your process models, job variables, and configuration files. Processes can reference credentials from customer-managed secret stores without persisting secret values in Camunda. - Reference secrets as `camunda.secrets.NAME` in input mappings, expressions, and output mappings. The legacy `{{secrets.NAME}}` syntax continues to work. - Secrets are resolved automatically for activated jobs and can also be requested through the Gateway APIs `/v2/secrets/resolve` and `/v2/secrets/list`. - Resolved values are not written to engine state, exports, backups, Operate, Tasklist, or application logs. - Self-Managed deployments support AWS Secrets Manager and GCP Secret Manager with workload identity authentication. A file-based provider is available for development and testing. - SaaS requires no configuration and uses Camunda’s managed secret backend. - Camunda 8 Run uses the file-based provider: create one file per secret (filename = secret name, contents = value), and set `camunda.secrets.stores.file.default.path` to that directory in the Camunda 8 Run application configuration. **Migration:** Existing processes continue to work without changes. For new processes, use `camunda.secrets.NAME`. To migrate hardcoded or connector-specific credentials, store the value in a supported secret store and replace it with a centralized secret reference. **Limitations:** This feature does not yet include HashiCorp Vault or Azure Key Vault support, secret access audit logging, per-process secret restrictions, or centralized resolution for Hybrid Connector Runtimes. Cache entries expire after the configured TTL, which is 20 seconds by default. [Secrets](/components/concepts/secrets.md) ## Connector operations Connectors are now discoverable by the operation you want to perform, not only by the product they connect to. When you search in the create, append, or change element menu, the operations of every built-in connector appear as their own entries, so searching for `upload object` or `send email` takes you straight to the connectors that can do it. Selecting an operation applies the connector with that operation preselected, and connectors with several operations present them as a nested menu. Two changes come with this: - Connectors that provide a single operation are [renamed after the operation they perform](/reference/announcements-release-notes/8100/8100-announcements.md#connectors-with-a-single-operation-are-renamed-after-the-operation). Existing process models keep running unchanged. - Element templates support the `steps` and `presets` keys, so your own templates can offer the same guided operation selection. [Predefined configurations](/components/modeler/element-templates/template-metadata.md#predefined-configurations-steps-and-presets) ## Credentials in Desktop Modeler Desktop Modeler also supports [credentials](#hub-credentials-manager). Select an existing credential on a connector task from the properties panel, or create a new one without leaving it, instead of entering the same authentication and connection settings on every task. [Use credentials in Desktop Modeler](/components/modeler/desktop-modeler/credentials.md) ## Camunda for Slack Camunda for Slack joins Camunda for Microsoft Teams as a second chat platform served by the same App Integrations backend. From Slack, you can browse and complete tasks, start a process, switch organization and cluster, and subscribe a channel or direct message to notifications, all through the `/camunda` slash command and the Camunda direct message. Microsoft Teams and Slack are independent, so you can run either on its own, or both. [Camunda for Slack](/components/camunda-integrations/app-integrations/slack.md) ## Helm chart deployment Important changes to Helm chart deployment in 8.10 are as follows: ### One management plane and one or more execution planes {#a-hub-plane-and-one-or-more-execution-planes} The 8.10 Helm chart adds `global.topology.mode`, so each release declares its role in the deployment: `combined`, `hub`, `orchestration`, or `optimize`. One `hub` release running Camunda Hub and Management Identity can serve many independently deployed `orchestration` releases, each with its own lifecycle, scaling, and upgrade schedule. The new `optimize` role deploys Optimize alone. Because one Optimize instance reads a single index prefix, this is what lets each [Physical Tenant](/self-managed/concepts/multi-tenancy/physical-tenants.md) have its own Optimize instance. `combined` remains the default, so existing deployments are unchanged by the upgrade. For a new production deployment, the split topology is the baseline. `hub` and `optimize` are 8.10-only roles, because Camunda Hub and its cluster inventory don't exist in the earlier charts. The `orchestration` role is also available in recent 8.9, 8.8, and 8.7 charts, so one 8.10 Hub can manage clusters on older chart versions. Earlier versions of those charts ignore `global.topology.mode` and deploy a combined release. For the minimum chart version of each role, see [release roles](/self-managed/deployment/helm/install/topology/index.md#release-roles). [Install the deployment topology](/self-managed/deployment/helm/install/topology/index.md) ### Camunda Helm Toolkit The new Helm migration and validation toolkit can help you upgrade from Camunda 8.9 to 8.10 on Kubernetes with Helm. Use the toolkit to: - Read your existing 8.9 Helm values (for example, values.yaml). - Generate a sample 8.10 values file reflecting the recommended Helm CLI v4, Bitnami sub‑charts removal, Hub‑aware deployment patterns, and simplified application configuration. - Create a migration report that lists the keys that were migrated automatically, flags keys that require manual decision (for example, infrastructure endpoints, security‑sensitive options), suggests where to find more information in the documentation, and can validate an existing 8.10 values file (for example, one drafted by hand or AI tool) against Camunda’s migration rules. The CLI is non‑interactive, with clear exit codes and optional JSON output, making it suitable for humans using the command line, CI pipelines, and AI agents (for example, Claude Code, Copilot) that can use it as part of an automated migration workflow. [Prepare Helm upgrades with the Camunda Helm Toolkit](/self-managed/deployment/helm/operational-tasks/camunda-helm-toolkit.md) ### Helm CLI v3 and v4 support {#helm-v4-required} Camunda 8.10 (chart 15.x) supports Helm CLI v3 (3.10 or later) and v4. With Helm CLI v3, the chart shows a warning when you run `helm install` or `helm upgrade`. Switching CLIs does not require a release-state migration. Helm runs on the client, and both CLIs read and write the same release-storage format. Use Helm CLI v4 for new installations. Switch existing deployments before Helm CLI v3 support ends. [Move from the Helm v3 CLI to v4](/self-managed/deployment/helm/operational-tasks/moving-helm-v3-to-v4.md) ### Host network support for orchestration cluster pods The 8.10 Helm chart adds `orchestration.hostNetwork` (default: `false`), which lets orchestration cluster pods share the host node's network namespace. This is useful in bare-metal or restricted network environments where pods must be reachable directly via the node IP rather than a cluster overlay network. [Configure pod networking](/self-managed/deployment/helm/configure/pod-networking.md) ## Low-code testing Test Studio in Camunda Hub turns process runs into repeatable tests that you can maintain and run in your CI/CD pipeline. - **Assertions**: Run a process instance, then save its input data and assertions as a low-code integration test. Add variable and path assertions, and view pass or fail results in the **Test** tab. - **Shared schema with Camunda Process Test**: Test files use the same schema as Camunda Process Test (CPT). Record a test once, run it in CI/CD through CPT, and load CPT-authored test files into Test Studio to debug them visually. - **Test repair**: When you delete, rename, or change the type of a BPMN element, Test Studio shows which steps broke and lets you fix them in place instead of re-recording the run. - **Segment tests**: In Play, capture and rerun targeted sections of a process as low-code integration tests. Ad-hoc subprocesses, and therefore AI agent elements, are not supported in test mode. See the [limitations](/components/hub/workspace/modeler/validation/test-your-process.md). [Test files](/components/hub/workspace/modeler/validation/test-files.md) ## Optimize Important changes to Optimize in 8.10 are as follows: ### Optimize authentication configuration keys The component-specific Optimize login and API security keys are deprecated in favor of `camunda.security.*`. Camunda plans to remove them in a future release, with the component-specific configuration and its `optimize.security.csl.enabled=false` fallback. `CAMUNDA_OPTIMIZE_IDENTITY_BASE_URL` is not deprecated and stays in use for user lookups. See [component-specific configuration keys](/self-managed/upgrade/components/890-to-8100.md#component-specific-security-configuration-keys-are-deprecated) for the full key mapping. [Optimize authentication in Self-Managed](/self-managed/concepts/authentication/authentication-to-optimize.md) ### Optimize data filters in Camunda Hub On SaaS, you can now configure Optimize export filters directly in Hub cluster settings. No Helm values or configuration files required. Use the **Data filters** section in cluster settings to control which process definitions (by `bpmnProcessId`) and variable names reach Optimize. New SaaS clusters include a default `business_` variable include filter that limits Optimize to variables whose names start with `business_`. This reduces Elasticsearch storage and shard usage significantly. Existing clusters are unaffected and can opt in with one click. [Configure Optimize data filters](/components/saas/clusters/settings.md#data-filters) ## Unified authentication for Orchestration Cluster and Optimize With Camunda 8.10, Optimize can be configured with the same `camunda.security.authentication.*` settings already used by the Orchestration Cluster. - Optimize continues to accept its 8.9 authentication settings in 8.10, translating the recognized properties to new equivalents at startup, but those 8.9 properties are deprecated and will be removed in a future release. - User, group, role, tenant, and permission management for Optimize is unchanged in 8.10 and is still handled by Management Identity. :::note Nothing changes for the Orchestration Cluster as it already uses these settings since Camunda 8.9. ::: ## Unified configuration property changes Camunda 8.10 completes the unified configuration of the Orchestration Cluster that began in 8.8. - The 8.10 property changes are documented in [Property changes in Camunda 8.10](/versioned_docs/version-8.10/self-managed/components/orchestration-cluster/core-settings/configuration/configuration-mapping.md), including new properties, removed properties, and changed defaults. - You can search, sort, and filter the table to show breaking changes, direct mappings, and new properties. - For more information on each property (including default values), see the [property reference](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md). [Property changes in Camunda 8.10](/versioned_docs/version-8.10/self-managed/components/orchestration-cluster/core-settings/configuration/configuration-mapping.md) ## Wait states Operate now shows what an active process instance is waiting for, so you can tell expected waiting from a stalled instance. - When you inspect an active element, you can see the wait state and its details, such as a timer's due date, a receive task's message name and correlation key, a signal name, a condition expression, or a job's type and state. - Wait state tracking is enabled by default and writes records to secondary storage. In Camunda 8 Self-Managed, you can [disable it](/self-managed/concepts/wait-states/configure.md) if you do not want to track this data. [Wait states](/components/wait-states/overview.md) ## APIs & Tools Important changes for APIs & Tools in 8.10 are as follows: ### Removal of legacy APIs, Tasklist V1-dependent features, and Zeebe Process Test In 8.10, Camunda removes the legacy component APIs and related features that were deprecated in 8.8, such as legacy APIs, Tasklist V1-dependent features, and Zeebe Process Test. [Release announcement](/reference/announcements-release-notes/8100/8100-announcements.md#removal-of-legacy-apis-tasklist-v1-dependent-features-and-zeebe-process-test) ### C# SDK Camunda now offers an officially supported C# Client for the Camunda 8 Orchestration Cluster REST API v2. You can authenticate with your cluster (No Auth for local, Basic authentication, or OIDC access tokens) and use C# methods to deploy resources, start and manage process instances, work with user tasks, and query processes and decisions, complete with pagination helpers and typed responses via generated models. [C# SDK](/apis-tools/csharp-sdk.md) ### Go and Rust SDKs 8.10 introduces Technical Previews for Go and Rust language SDKs for the Orchestration Cluster API. The Go SDK additionally contains support for gRPC job streaming. During 8.10 these SDKs will be stabilized, but there may be changes to their API surface based on user feedback. These SDKs will be fully supported and guaranteed to be stable in a later release. [Go SDK](/apis-tools/go-sdk.md) [Rust SDK](/apis-tools/rust-sdk.md) ## Supported environments Camunda 8.10 updates several platform and environment baselines. For complete details, including breaking changes and deprecations, see [release announcements](./8100-announcements.md) and [supported environments](/reference/supported-environments.md). Highlights include: | Environment | Description | | :----------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------- | | [Amazon Aurora PostgreSQL](/reference/announcements-release-notes/8100/8100-announcements.md#amazon-aurora-postgresql-14-removed-18-added) | Version 14 removed, version 18 added. Supported versions are now 15, 16, 17, and 18. | | [Elasticsearch](/reference/announcements-release-notes/8100/8100-announcements.md#elasticsearch-92-and-93-no-longer-supported) | Minimum supported 9.x version raised to 9.4. Supported versions are now 8.19+ and 9.4+. | | [H2](/reference/announcements-release-notes/8100/8100-announcements.md#h2-23-no-longer-supported) | Version 2.3 no longer supported. Only 2.4 is now supported (dev/test/evaluation only). | | [MariaDB](/reference/announcements-release-notes/8100/8100-announcements.md#mariadb-123-now-supported) | Version 12.3 LTS now supported. Supported versions are now 10.11, 11.4, 11.8, and 12.3. | | [Microsoft SQL Server](/reference/announcements-release-notes/8100/8100-announcements.md#microsoft-sql-server-2019-no-longer-supported) | Version 2019 no longer supported. Supported versions are now 2022 and 2025. | | [MySQL](/reference/announcements-release-notes/8100/8100-announcements.md#mysql-97-now-supported) | Version 9.7 LTS now supported. Supported versions are now 8.4 and 9.7. | | [OpenSearch](/reference/announcements-release-notes/8100/8100-announcements.md#opensearch-34-and-35-no-longer-supported) | Minimum supported 3.x version raised to 3.6. Supported versions are now 2.19+ and 3.6+. | | [Oracle](/reference/announcements-release-notes/8100/8100-announcements.md#oracle-23ai-rebranded-as-oracle-26ai) | Oracle 23ai rebranded as Oracle AI Database 26ai. Supported versions are 19c and 26ai. | | [PostgreSQL](/reference/announcements-release-notes/8100/8100-announcements.md#postgresql-14-no-longer-supported) | Version 14 no longer supported. Supported versions are now 15, 16, 17, and 18. | --- ## 8.11 Release announcements ## APIs & tools Breaking change #### Batch process instance operations now narrow and validate the `state` filter {#batch-operation-state-filter} Starting with 8.11, the batch operations to modify, migrate, resolve incidents, suspend, resume, and cancel process instances narrow your `state` filter with the state the operation requires, and validate it. Previously, the API documentation described the `state` filter as "ignored and overridden". This didn't match the actual behavior, which differed by operation. | Operation | Required state | Previous behavior | | :----------------------------------------------------------------------------------------------------------------------- | :----------------------- | :-------------------------------------------- | | [Modify](/apis-tools/orchestration-cluster-api-rest/specifications/modify-process-instances-batch-operation.api.mdx) | `ACTIVE` | Combined your filter with the required state. | | [Migrate](/apis-tools/orchestration-cluster-api-rest/specifications/migrate-process-instances-batch-operation.api.mdx) | `ACTIVE` | Combined your filter with the required state. | | [Resolve incidents](/apis-tools/orchestration-cluster-api-rest/specifications/resolve-incidents-batch-operation.api.mdx) | `ACTIVE` | Combined your filter with the required state. | | [Suspend](/apis-tools/orchestration-cluster-api-rest/specifications/suspend-process-instances-batch-operation.api.mdx) | `ACTIVE` | Replaced your filter with the required state. | | [Resume](/apis-tools/orchestration-cluster-api-rest/specifications/resume-process-instances-batch-operation.api.mdx) | `SUSPENDED` | Replaced your filter with the required state. | | [Cancel](/apis-tools/orchestration-cluster-api-rest/specifications/cancel-process-instances-batch-operation.api.mdx) | `ACTIVE` and `SUSPENDED` | Combined your filter with the required state. | The following rules apply to all six operations: - A top-level `state` filter that uses `$eq` or `$in` with a state outside the required states is rejected with `400 Bad Request`. Modify, migrate, and resolve incidents previously accepted such a filter and created an empty batch. Suspend and resume previously discarded the filter. Cancel already rejected it. - States inside `$or` branches aren't validated. A branch that conflicts with the required state matches no instances. Cancel previously rejected invalid states in `$or` branches. - The `$neq`, `$exists`, and `$like` operators on a top-level `state` are applied as given. The following changes apply to the `parentProcessInstanceKey` filter: - Suspend and resume now apply the filter as given. Previously, they discarded it and could act on instances outside your filter. - Cancel now narrows the filter to root instances instead of replacing it. A filter that matches only child instances selects no instances and doesn't return an error. **Action:** Remove a conflicting top-level `state` filter from your batch requests, or move it into an `$or` branch. If your integration relied on suspend or resume discarding your `parentProcessInstanceKey` filter, review the filter, because it now limits the batch. ## Deployment Breaking change #### Grafana dashboards use the v2 dashboard schema and require Grafana 13 or later {#grafana-dashboards-v2-schema} Starting with Camunda 8.11, the Grafana dashboards published in [`monitor/grafana`](https://github.com/camunda/camunda/tree/main/monitor/grafana) of the `camunda/camunda` repository use the new [v2 dashboard schema](https://grafana.com/whats-new/2025-04-11-new-dashboards-schema/). Camunda 8.10 is the last release that provides the dashboards in the classic format, which [Camunda deprecated in 8.10](/reference/announcements-release-notes/8100/8100-announcements.md#classic-grafana-dashboard-format-deprecated). The classic dashboards of 8.10 and earlier releases continue to work with your Grafana instance. The dashboards of 8.11 require Grafana 13 or later. Earlier Grafana versions cannot import the v2 dashboard schema. **Action:** Before you move to the dashboards of 8.11, upgrade your Grafana instance to version 13 or later. To keep using the dashboards in the classic format, import them from the `stable/8.10` branch or from the branch of the release you run. --- ## 8.11 Release notes ## 8.11.0-alpha1 | Release date | Changelog(s) | | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 10 November 2026 | [ Camunda 8 core ](https://github.com/camunda/camunda/releases/tag/8.11.0-alpha1)[ Connectors ](https://github.com/camunda/connectors/releases/tag/8.11.0-alpha1) | ### APIs & tools #### Read the engine clock and add time without pinning it Orchestration Cluster With two new Orchestration Cluster REST API endpoints, you can read the engine clock and move it without stopping it. | Endpoint | What it does | Required permission | Java client | | :------------------- | :---------------------------------------------------------------------------------------------------------------- | :------------------- | :-------------------------------------- | | `GET /v2/clock` | Returns the current time of the engine clock. If the clock is pinned, it returns the pinned time. | `READ` on `SYSTEM` | `newClockGetRequest()` | | `POST /v2/clock/add` | Moves the clock forward or backward by an ISO 8601 duration, such as `PT1H` or `-PT1H`, and returns the new time. | `UPDATE` on `SYSTEM` | `newAddClockCommand().offset(Duration)` | Both endpoints return the time as an ISO 8601 date-time, for example `{ "timestamp": "2025-01-01T12:00:00.000Z" }`. Unlike pinning, adding time keeps the clock running from the new time, so job timeouts and retry backoffs still run out in tests. If the clock is pinned, it stays pinned and moves to the pinned time plus the offset. To remove the added time, reset the clock with `POST /v2/clock/reset`. [Get internal clock](/apis-tools/orchestration-cluster-api-rest/specifications/get-clock.api.mdx) [Add time to internal clock](/apis-tools/orchestration-cluster-api-rest/specifications/add-clock.api.mdx) ### Orchestration Cluster #### Evenly distributed record indices in Elasticsearch and OpenSearch Self-Managed The Elasticsearch and OpenSearch exporters now spread Zeebe records more evenly across index shards. The `zeebe-record-job` and `zeebe-record-process-instance` indices default to `5` shards, up from `3`. The `zeebe-record-user-task` index defaults to `1` shard, down from `3`. With a three-partition cluster, the records of each partition land on a separate shard instead of two partitions sharing one shard. Only newly created dated indices use the new defaults. An existing index keeps the shard count it was created with. If you set `number-of-shards`, it still overrides the template defaults for all indices. - [Elasticsearch exporter configuration](/self-managed/components/orchestration-cluster/zeebe/exporters/elasticsearch-exporter.md#configuration) - [OpenSearch exporter configuration](/self-managed/components/orchestration-cluster/zeebe/exporters/opensearch-exporter.md#configuration) --- ## What's new in Camunda 8.11 :::note Release information For a full summary of what's included in Camunda 8.11, including all breaking changes, deprecations, and supported environment changes, see [release announcements](/reference/announcements-release-notes/8110/8110-announcements.md) and [release notes](/reference/announcements-release-notes/8110/8110-release-notes.md). ::: ## Upgrade guides {#upgrade-guides} Ready to upgrade? The following guides offer detailed information on how to upgrade to Camunda 8.11. **Guide** **Description** **Who is this guide for?** - - - ## Summary of important changes Important changes in Camunda 8.11 are summarized as follows: **What's new/changed** **Summary** - - --- ## 8.8 Release announcements Supported environment changes and breaking changes or deprecations for the Camunda 8.8 release. | Minor release date | End of standard maintenance | Release notes | Release blog | Upgrade guides | | ------------------ | --------------------------- | ------------------------------------------------------------------------------------ | ------------ | ---------------------------------------------------------------------------------------------- | | 14 October 2025 | 13 April 2027 | [8.8 release notes](/reference/announcements-release-notes/880/880-release-notes.md) | - | [Upgrade guides](/reference/announcements-release-notes/880/whats-new-in-88.md#upgrade-guides) | :::info 8.8 resources - See [release notes](/reference/announcements-release-notes/880/880-release-notes.md) to learn more about new features and enhancements. - See [What's new in Camunda 8.8](/reference/announcements-release-notes/880/whats-new-in-88.md) for important changes to consider when planning your upgrade from Camunda 8.7. - Refer to the [quality board](https://github.com/orgs/camunda/projects/187/views/15) for an overview of known bugs by component and severity. ::: ## Supported environment changes Change #### Elasticsearch and OpenSearch minimal supported versions Elasticsearch 8.16+ and OpenSearch 2.17+ are now supported as minimal versions to ensure you can benefit from the latest, most stable database releases. Older versions are no longer supported. Change #### PostgreSQL, Oracle and Microsoft SQL Server supported versions Management Identity now supports PostgreSQL and Amazon Aurora PostgreSQL versions 16.x and 17.x. Web Modeler now supports PostgreSQL version 18.x, Amazon Aurora PostgreSQL version 17.x, Oracle versions 19c and 23ai and Microsoft SQL Server versions 2019 and 2022. Change #### Zeebe, Operate, Tasklist, and Identity must run on same minor and patch levels From version `8.8.0` onwards, the Zeebe, Operate, Tasklist, and Identity [Orchestration Cluster](/self-managed/reference-architecture/reference-architecture.md#orchestration-cluster) components must run on the exact same `minor`and `patch` level to ensure compatibility. :::info See the [component version matrix](/reference/supported-environments.md#component-version-matrix) or the [Self-Managed reference architecture](/self-managed/reference-architecture/reference-architecture.md#orchestration-cluster) for a component overview. ::: ## Key changes ### 8.8.x patch releases The following key changes were also released as part of an 8.8.x patch release. | Patch release | Artifact | Type | Key change | | :------------------------------------------------------------------ | :--------- | :-------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------- | | [8.8.38](https://github.com/camunda/camunda/releases/tag/8.8.38) | Core | Known issue | [Process definition draining deletion requires an 8.9.19+ upgrade target](#draining-deletion-upgrade-constraint) | | [8.8.34](https://github.com/camunda/camunda/releases/tag/8.8.34) | Core | Regression | [Nested input mappings can silently drop sibling fields](#nested-input-mapping-sibling-fields) | | [8.8.34](https://github.com/camunda/camunda/releases/tag/8.8.34) | Core | Regression | [Chained input mappings can silently drop FEEL temporal value types](#chained-input-mapping-temporal-type-loss) | | [8.8.29](https://github.com/camunda/camunda/releases/tag/8.8.29) | Core | Regression | [Tasklist V1: candidate group task visibility](#tasklist-v1-candidate-group-task-visibility) | | [8.8.23](https://github.com/camunda/camunda/releases/tag/8.8.23) | Core | Regression | [Multi-instance sub-process output mapping variable scope regression](#multi-instance-output-mapping-regression) | | [8.8.23](https://github.com/camunda/camunda/releases/tag/8.8.23) | Core | Regression | [Output mapping behavior change for object variables](#output-mapping-behavior-change) | | [8.8.22](https://github.com/camunda/camunda/releases/tag/8.8.22) | Core | Regression | [`getMessageKeys()` removed from the exporter record](#getmessagekeys-removed-from-the-exporter-record) | | [8.8.22](https://github.com/camunda/camunda/releases/tag/8.8.22) | Core | Change | [Message TTL cleanup batch size pacing change](#message-ttl-cleanup-batch-size-pacing-change) | | [8.8.22](https://github.com/camunda/connectors/releases/tag/8.8.22) | Connectors | Breaking change | [Intrinsic function allow-list](#connector-intrinsic-function-allow-list) | | [8.8.19](https://github.com/camunda/connectors/releases/tag/8.8.19) | Connectors | Breaking change | [Connector secret filter](#connector-secret-filter) | | [8.8.9](https://github.com/camunda/camunda/releases/tag/8.8.9) | Core | Regression | [Webhook alerts JSON format](#webhook-alerts-json-format) | | [8.8.9](https://github.com/camunda/camunda/releases/tag/8.8.9) | Core | Change | [Spring Boot 4.0 support for Camunda Spring Boot Starter and Process Test ](#spring-boot-40-support-for-camunda-spring-boot-starter-and-process-test) | ### APIs & tools Regression #### `getMessageKeys()` removed from the exporter record {#getmessagekeys-removed-from-the-exporter-record} Camunda 8.8.22 unintentionally removed the `getMessageKeys()` method (and the underlying `messageKeys` field) from the public `MessageBatchRecordValue` exporter record. Custom exporters that call `getMessageKeys()` on message batch records fail to compile against, or throw a `NoSuchMethodError` at runtime with, the updated `zeebe-protocol` dependency after upgrading to 8.8.22 or any later 8.8.x patch. The built-in Elasticsearch, OpenSearch, and RDBMS exporters are unaffected. A fix that restores the method (now deprecated, returning an empty list for records produced by newer versions) is tracked in [camunda/camunda#54823](https://github.com/camunda/camunda/issues/54823) and is available in 8.8.28. **Action:** If you maintain a custom exporter that reads message batch records, avoid calling `getMessageKeys()` until you upgrade to a patch that includes the fix. Breaking change #### `GET /decision-instances/{decisionEvaluationInstanceKey}` now validates the key format The [Get decision instance](/apis-tools/orchestration-cluster-api-rest/specifications/get-decision-instance.api.mdx) endpoint previously returned `404 Not Found` when the `decisionEvaluationInstanceKey` path parameter contained invalid characters that did not match the required pattern `^[0-9]+-[0-9]+$`. The endpoint now correctly returns `400 Bad Request` in this case, while `404 Not Found` is reserved for well-formed keys that do not exist. **Action:** Update any client code or error handling that relied on receiving `404 Not Found` for malformed keys to also handle `400 Bad Request`. Breaking change #### Orchestration Cluster: Zeebe Java Client <=8.7.15 with REST API enabled The Zeebe Java Client <=8.7.15 with REST API enabled is incompatible with Camunda 8.8 if you are running: - Either the Zeebe Java Client <=8.7.15 OR the Spring Zeebe SDK <=8.7.15, - AND you opted into preferring REST over gRPC (setting `preferRestOverGrpc=true` explicitly on client setup). In this scenario, you will be affected by the following issue [Camunda 8.7 REST client fails on unknown response properties on job activate (#39675)](https://github.com/camunda/camunda/issues/39675). **Impact** When updating your Orchestration Cluster to 8.8 without updating your clients prior to at least 8.7.16, workers will fail to activate jobs with a log message such as the following: ```bash io.camunda.zeebe.client.api.command.MalformedResponseException: Expected to receive a response body, but got a problem: class ProblemDetail { type: about:blank title: Unexpected server response status: 500 detail: {"jobs":[{"type":"check-generation-usage-job","processDefinitionId":"c[...] instance: null } ``` **Required action** You must update your clients to at least 8.7.16, as this contains the fix for this issue. Alternatively, you can opt out of using `preferRestOverGrpc=true` before upgrading your cluster. Regression #### Webhook alerts JSON format In 8.8.0, a regression was introduced to [Webhooks Alerting](/components/saas/clusters/manage-alerts.md#webhook-alerts). The JSON format was modified so that the `processVersion` field returns a `String` value representing either the process version tag, if it exists, or otherwise the process version. In 8.8.9, the `processVersion` field reverts to returning an integer value representing the process version only. A new `processVersionTag` field is introduced to include the process version tag when available. **Example JSON format change** Before 8.8.9: ```json { "processVersion": "v2.1.0" // String - could be tag or number } ``` After 8.8.9: ```json { "processVersion": 3, // Integer - always the version number "processVersionTag": "v2.1.0" // String - the version tag (if exists) } ``` **Action required** Adapt any webhook-dependent integrations you have created for 8.8.x to handle the updated JSON structure: 1. Update your integration to read `processVersion` as an integer value representing the process version number. 2. If you need the process version tag, use the new `processVersionTag` field that contains the string value of the version tag (if one exists). 3. Ensure your integration handles cases where `processVersionTag` might be null or absent (for processes without version tags). 4. Test your webhook consumers to verify they correctly parse both the integer `processVersion` and string `processVersionTag` fields. Breaking change #### Bug fix: `FormResult.schema` type corrected from object to string The `schema` property in the `FormResult` response was incorrectly specified as `type: object` in the OpenAPI contract, but the server has always returned it as a JSON `string`. This specification bug is now fixed. This is a bug fix that aligns the OpenAPI contract with actual server behavior, improving correctness for typed client integrations. Applications already handling the runtime `string` value are unaffected. The Camunda Java client is also affected: `io.camunda.client.api.search.response.Form::getSchema()` now returns `String` instead of `Object`. If your Java code casts or processes the return value as `Object`, update it to use `String`. What to do: - Official SDK users: update to the latest SDK version. - Java client users: update calls to `Form::getSchema()` to handle the `String` return type instead of `Object`. - Generated-client users: regenerate your client. If your generated code relied on the incorrect `object` typing, update it to handle `string`. - Handwritten integrations: no change needed if you were already handling the actual `string` response. Removed #### Removed: Tasklist GraphQL API With the Camunda 8.8 release, the deprecated Tasklist GraphQL API is removed. Removed #### Removed: Deprecated OpenAPI objects With the Camunda 8.8 release, deprecated API objects containing number keys are removed, including the corresponding `application/vnd.camunda.api.keys.number+json` content type header. - In previous releases, entity keys were transitioned from `integer (int64)` to `string` types, with deprecated `integer (int64)` keys still supported. Support for `integer (int64)` keys is removed in Camunda 8.8. - To update to Camunda 8.8, API objects using `integer (int64)` keys must be updated to use `string` keys and the `application/json` header. Removed #### Removed: Optimize Index Rollover Prior to the Camunda 8.8 release, Optimize used the following configuration properties to apply index rollover to its External Variable Indices: - `externalVariable.variableIndexRollover.maxIndexSizeGB` - `externalVariable.variableIndexRollover.scheduleIntervalInMinutes` These properties are deleted in Camunda 8.8, with External Variables now stored in a single index. Deprecated #### Deprecated: Web Modeler API milestone endpoints With the Camunda 8.8 release, the [Web Modeler API](/apis-tools/web-modeler-api/index.md) endpoints under `/api/v1/milestones` are deprecated and scheduled for removal in 8.9. You can use the corresponding endpoints under `/api/v1/versions` instead. Deprecated #### Deprecated: Operate and Tasklist v1 REST APIs With the Camunda 8.8 release, the deprecation process for the [Operate](/versioned_docs/version-8.9/apis-tools/operate-api/overview.md) and [Tasklist](/versioned_docs/version-8.9/apis-tools/tasklist-api-rest/tasklist-api-rest-overview.md) REST APIs begins. You can begin migrating to the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) for querying to prepare for this change. | Version | Description | | :----------- | :--------------------------------------------------------------------------------------- | | Camunda 8.8 | These APIs remain available and functional. | | Camunda 8.9 | These APIs remain available but deprecated, and not recommended for new implementations. | | Camunda 8.10 | These APIs are removed. | :::warning Impact on user task access restrictions [User task access restrictions](/versioned_docs/version-8.9/components/tasklist/user-task-access-restrictions.md) are only supported with the Tasklist v1 API. After switching to the v2 API with Tasklist, user task access restrictions do not apply. ::: :::info To learn more about the differences between Tasklist v1 and v2 UI modes, see [Tasklist API versions](/components/tasklist/api-versions.md). ::: Deprecated #### Deprecated: Job-based user tasks querying With the Camunda 8.8 release, the deprecation process for job-based user tasks begins. - As job-based user tasks are not supported for querying/task management with Camunda 8.10, Camunda recommends using **Camunda user task** type (formerly **Zeebe user task**) in your process definitions. - You might still see Zeebe user task references in your XML, but this is the same thing as a Camunda user task. | Version | Description | | :----------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Camunda 8.9 | Job-based user tasks are available for querying, but Modeler automatically applies the Camunda user task and shows a warning message for each job-based user task. | | Camunda 8.10 | With the removal of the Tasklist REST API, job-based user tasks are no longer supported for querying and task management. With Camunda 8.9+, customers can still use job-based user tasks as standard jobs with headers to enable open architecture and composable solutions.For customers who require task lifecycle support and task querying, Camunda recommends using the Camunda User Task type. | Deprecated #### Deprecated: Zeebe Client job worker metrics With the Camunda 8.8 release, the deprecation of Zeebe client job worker metrics is announced. These metrics are scheduled for removal in the Camunda 8.10 release. :::info To learn more, see [Zeebe client job worker](/apis-tools/java-client/job-worker.md) and [Zeebe client job worker concept](/components/concepts/job-workers.md). ::: Deprecated #### Deprecated: Zeebe gRPC DeployProcess endpoint The `DeployProcess` endpoint was deprecated with 8.0, replaced with `DeployResource` RPC. This endpoint is scheduled for removal in the Camunda 8.10 release. Deprecated #### Deprecated: File type `connector_template` in Web Modeler API With the Camunda 8.8 release, the `connector_template` file type in the [Web Modeler API](/apis-tools/web-modeler-api/index.md) endpoint for file creation (`POST /api/v1/files`) is deprecated. This file type will be removed in the Camunda 8.10 release. You should use `element_template` instead, which provides equivalent functionality. Deprecated #### Deprecated: Zeebe Process Test With the Camunda 8.8 release, the deprecation of Zeebe Process Test is announced. - Zeebe Process Test is superseded by [Camunda Process Test](../../../apis-tools/testing/getting-started.md). - Zeebe Process Test is scheduled for removal in the Camunda 8.10 release. :::info To learn more, see [migrate to Camunda Process Test](../../../apis-tools/migration-manuals/migrate-to-camunda-process-test.md) and [Introducing Camunda Process Test - The Next Generation Testing Library](https://camunda.com/blog/2025/04/camunda-process-test-the-next-generation-testing-library/). ::: Deprecated #### Deprecated: Operate & Tasklist usage metrics endpoints With the Camunda 8.8 release, the deprecation of usage metrics endpoints in Operate and Tasklist is announced. - [Deprecated Operate endpoints](/self-managed/components/orchestration-cluster/core-settings/concepts/usage-metrics.md) - [Deprecated Tasklist endpoint](/self-managed/components/orchestration-cluster/core-settings/concepts/usage-metrics.md). :::warning Breaking change The Assignees list is removed from the response. ::: - These endpoints are superseded by [usage metrics endpoint](../../../apis-tools/orchestration-cluster-api-rest/specifications/get-usage-metrics.api.mdx). - The Operate and Tasklist usage metrics endpoints are scheduled for removal in the 8.10 release. Deprecated #### Deprecated: start public process via form in Tasklist With the Camunda 8.8 release, the deprecation of the start public process via form feature is announced. - This SaaS feature is deprecated and does not work with [Tasklist running in V2 mode](/components/tasklist/api-versions.md). This feature will be removed in the 8.10 release. - To continue using this feature with Camunda 8.8, you must run [Tasklist in V1 mode](/components/tasklist/api-versions.md). Change #### Public API definition for greater platform stability To enhance predictability and offer a more stable experience for developers, Camunda introduced the official [public API definition for Camunda 8](/reference/public-api.md). - This document explicitly identifies the components and interfaces that are covered by our semantic versioning guarantees. - By formally defining the public API, Camunda commits to avoiding breaking changes in minor and patch releases for these areas. This empowers you to build on Camunda 8 with greater confidence and reduced maintenance effort. Change #### Camunda Java client and Camunda Spring Boot Starter With the Camunda 8.8 release, Camunda Java Client and Camunda Spring Boot Starter replace the Zeebe Java client and Spring Zeebe SDK. This allows you to use a single consolidated client to interact with Camunda orchestration clusters. The `CamundaClient` replaces the `ZeebeClient`, offering the same functionality and adding new capabilities. The Camunda Spring Boot Starter is based on Spring Boot 3.5, see [version compatibility matrix](/apis-tools/camunda-spring-boot-starter/getting-started.md#version-compatibility). - The new `CamundaClient` uses REST as the default communication protocol, and explicitly uses the configuration option `preferRestOverGrpc=false` to switch to gRPC as the default protocol. (Note: job streaming is only supported via gRPC, but can be used alongside REST for other operations). - If you need to continue using the old `ZeebeClient`, you can use the new version 8.8 `CamundaClient` artifact without issues, as it still contains the related `ZeebeClient` classes. Those classes are marked as deprecated, so you can easily identify code you need to adjust to the `CamundaClient`. - The old `zeebe-client-java` artifact is now relocation-only, so your build system is redirected to the new `camunda-client-java` artifact. We will discontinue the old artifact in version 8.10 and recommend using the new one. - The Zeebe Java client will not be developed further and will only receive bug fixes while version 8.7 is officially supported. :::note - The new Camunda Spring Boot Starter provides the `CamundaClient` when requested. - The `CamundaClient` uses REST as the default communication protocol, while the deprecated `ZeebeClient` still prefers gRPC. - If you want to continue using gRPC by default with the `CamundaClient`, you must explicitly set `camunda.client.prefer-rest-over-grpc: false` in your Spring configuration. - The new `CredentialsProvider` bean creation fails if there is a misconfiguration instead of falling back to a non-operational credentials provider ::: :::info To learn more about migrating to the Camunda Java client, see the [migration guide](/apis-tools/migration-manuals/migrate-to-camunda-java-client.md). ::: Change #### Spring Boot 4.0 support for Camunda Spring Boot Starter and Process Test With the 8.8.9 patch release, dedicated Spring Boot 4.0 based modules are released for the [Camunda Spring Boot Starter](../../../apis-tools/camunda-spring-boot-starter/getting-started.md#spring-boot-40-support) and [Camunda Process Test Spring](../../../apis-tools/testing/getting-started.md?client=spring-sdk#spring-boot-40-support) as drop-in replacements for the default Spring Boot 3.5.x-based modules. You must use these if you want to migrate your application to Spring Boot 4.0. :::note With Camunda 8.9, the default Spring Boot version for the Camunda Spring Boot Starter and Camunda Process Test Spring changes to 4.0. ::: Change #### The Node.js SDK is now the TypeScript SDK With the Camunda 8.8 release, the Node.js SDK now becomes the TypeScript SDK. - The **TypeScript SDK** provides clients for all Camunda 8 APIs. Use it in Node.js environments. - The **Orchestration Cluster API TypeScript client** is a lightweight client for the Camunda 8.8+ Orchestration Cluster REST API. Use it in Node.js or in the browser. To learn more, see the [TypeScript SDK](/apis-tools/typescript/typescript-sdk.md) documentation. ### Connectors Breaking change #### Connector secret filter {#connector-secret-filter} Starting with Connectors 8.8.19, the connector runtime introduces the [secret filter](/self-managed/components/connectors/connectors-configuration.md#secret-filter), defaulting to `STRICT`. In practice, this means a secret in a connector field only resolves at runtime if that same secret was already referenced in that same field at modeling time, in the deployed BPMN. **Action:** Before upgrading, confirm all connector fields that resolve a secret already reference that secret in the deployed BPMN. - If a field relies on resolving a secret it doesn't reference, add the reference. - To temporarily unblock connector jobs while you update the model, you can set `camunda.connector.secret-resolver.secret-filter.mode` to `DISABLED`, but be aware that this restores the affected behavior described in [Notice 61](/reference/notices.md#notice-61). Return to `STRICT` after updating the model. - Note that `LAX` is not useful in this scenario as it only changes behavior when the process definition cannot be retrieved, not when a field simply doesn't declare the secret. [Secret filter](/self-managed/components/connectors/connectors-configuration.md#secret-filter) Breaking change #### Outbound connectors refuse undeclared intrinsic function calls {#connector-intrinsic-function-allow-list} Starting with Connectors 8.8.22, the connector runtime runs an [intrinsic function](/components/connectors/advanced-topics/intrinsic-functions.md#declare-calls) only if the deployed BPMN process declares that exact call, by function name and field path, in the input mappings of the connector element. In practice, an intrinsic function call that reaches a connector through a process variable or job variable, instead of being written in the connector element's input mapping, now fails the job. A connector result that contains an object with the `camunda.function.type` key also fails the job when you store it in a result variable or map it with a result expression. **Action:** Before upgrading, check which processes use intrinsic functions. - Write each `camunda.function.type` call as a literal in the input mapping of the field that uses it, and redeploy the process. - Use a result expression that maps only the fields you need if a response from an external system can contain the `camunda.function.type` key. - Don't use `camunda.connector.intrinsic-function.allow-list.mode` set to `DISABLED` as a workaround. This mode refuses every intrinsic function call. No setting restores unrestricted execution. [Intrinsic function allow-list](/self-managed/components/connectors/connectors-configuration.md#intrinsic-function-allow-list) [Troubleshoot intrinsic function errors](/components/connectors/advanced-topics/intrinsic-functions.md#troubleshoot-intrinsic-function-errors) Change #### Connector SDK: Core SDK restructuring The internal structure of the Connector SDK has been updated to make the Core SDK more lightweight, with **no dependency on the Camunda client**. Some classes and interfaces have been relocated, which means external connectors may need to be **recompiled** before they can be used with Connector runtime 8.8. This affects the following classes and interfaces previously located in the `io.camunda.document` package: ``` DocumentFactory Document DocumentLinkParameters ``` These classes and interfaces are now located in the `io.camunda.connector.api.document` package. Additionally, the following classes and interfaces from the official Camunda Java client (`io.camunda.client.api.response`) have been **replicated** in the Connector SDK and are now located in the `io.camunda.connector.api.document` package: ``` DocumentMetadata DocumentReference ``` Change #### Connector SDK: Changes to activity logging in inbound connectors The Connector SDK 8.8 introduces a new way to [log activities](/components/hub/organization/manage-clusters/manage-connectors.md#activity-log) in inbound connectors. Objects of the `InboundConnectorContext` class now provide a new overloaded method: ```java void log(Consumer activityBuilderConsumer) ``` This method works with the new `ActivityBuilder` interface. **Usage example:** ```json connector.context() .log( activity -> activity .withSeverity(Severity.INFO) .withTag("Consumer") .withMessage("Successfully processed message") .andReportHealth(Health.up())); ``` The old builder pattern (`Activity.newBuilder()`) is deprecated and will be removed in upcoming releases. The new `ActivityBuilder` interface provides a more flexible and fluent API for logging activities in inbound connectors. Change #### Harmonized Error Contexts for jobError and bpmnError in Connectors With the Camunda 8.8 release, Camunda has harmonized the error context structures returned by the `jobError` and `bpmnError` functions in connectors to align with the corresponding error handling in Camunda core. This ensures that connector errors now follow the same conventions and structure as errors handled by the core engine, supporting greater consistency across process modeling and execution. Updated context structure: - `bpmnError` now returns an entry containing: `errorType`, `errorCode` `errorMessage` `variables` - `jobError` now returns an entry containing : `errorType`, `errorMessage`, `variables`, `retries`, `retryBackoff` Developers and integrators should review any custom connector logic to take full advantage of the new fields and adapt error handling as necessary. :::note These changes do not introduce new fields or richer context, but instead ensure that error data is structured and surfaced consistently between connectors and Camunda core. This makes error handling more predictable, especially for teams working across both domains. ::: ### Data Change #### Elasticsearch and OpenSearch: Index prefixes must differ When upgrading to Camunda 8.8 or setting up 8.8 for the first time, if you run both the Elasticsearch/OpenSearch Exporter and Camunda Exporter on the same Elasticsearch/OpenSearch cluster, their index prefixes must not match, nor be contained within the other. Do not reuse the same prefix for: - Elasticsearch/OpenSearch Exporter indices (legacy exporter): `zeebe.broker.exporters.{elasticsearch|opensearch}.args.index.prefix` - Orchestration Cluster indices: `camunda.data.secondary-storage.{elasticsearch|opensearch}.index-prefix` If these prefixes are identical, or if one prefix includes the other (for example, `custom` and `custom-zeebe`), ILM/ISM policies and wildcard patterns such as `custom*` can target more indices than intended, which may lead to unexpected data loss. For configuration examples and details, see [Helm chart Elasticsearch/OpenSearch indices prefix](../../../self-managed/deployment/helm/configure/database/elasticsearch/configure-elasticsearch-prefix-indices.md). Breaking change #### Elasticsearch and OpenSearch: Single instance With the Camunda 8.8 release, the use of more than one isolated Elasticsearch/OpenSearch instance for exported Zeebe, Operate, and Tasklist data is no longer supported. - If your environment uses multiple Elasticsearch/OpenSearch instances, you must manually migrate the data from each to a single Elasticsearch/OpenSearch cluster before updating to Camunda 8.8. - The migration should target Zeebe, Operate, and Tasklist indices, index templates, aliases, and ILM policies. Change #### Elasticsearch and OpenSearch: Replica default increased to 1 With the Camunda 8.8 release, the default replica count for Camunda indices in Elasticsearch and OpenSearch changes from 0 to 1. This ensures that if an Elasticsearch node goes down, Camunda is not blocked by a temporary outage of the secondary data store. This change increases storage requirements as follows: - **Single-node clusters:** Running with one node turns the cluster state yellow (replicas unassigned). Run at least two master-eligible nodes. - **Multi-node clusters:** Increase disk capacity to at least 2.5× the previously used disk capacity (accounts for watermarks, overhead, and growth). To revert to 0 replicas, set: - YAML: `camunda.database.index.numberOfReplicas: 0` - Environment variable: `CAMUNDA_DATABASE_INDEX_NUMBER_OF_REPLICAS=0` :::info To learn more, see [Elasticsearch changes in Components update 8.7 to 8.8](/versioned_docs/version-8.8/self-managed/upgrade/components/870-to-880.md#elasticsearch). ::: Change #### Camunda Exporter Previously, Camunda web applications used importers and archivers to consume, aggregate, and archive historical data provided by Elasticsearch or OpenSearch (secondary storage) exporters — see [Elasticsearch/OpenSearch](/reference/glossary.md#elasticsearchopensearch). ![87-orchestration-cluster-state](../../img/87-orchestration-cluster-state.png) With the Camunda 8.8 release, a new Camunda Exporter is introduced: - Importing and archiving logic of web components (Tasklist and Operate) is brought closer to the distributed platform (Zeebe). - Benefits include simplified installation, scalability enabled for the web applications, reduced latency when showing runtime and historical data, and reduced data duplication (resource consumption). ![brown-field-without-optimize](../../img/brown-field-orchestration-cluster-without-optimize.png) The new Camunda Exporter helps Camunda achieve a more streamlined architecture, better performance, and improved stability (especially concerning Elasticsearch/OpenSearch secondary storage). :::info To learn more, see the blog post [One Exporter to Rule Them All: Exploring Camunda Exporter](https://camunda.com/blog/2025/02/one-exporter-to-rule-them-all-exploring-camunda-exporter/). ::: #### Harmonized index schema The existing data schema in the secondary storage has been harmonized, to be used by all Camunda components. - This removes unnecessary duplications over multiple indices due to the previous architecture. - With this change, several Operate indices can and will be used by Tasklist. - New indices have been created to integrate Identity into the system. ![Harmonized indices schema](../../img/harmonized-indices-schema.png) ### Deployment Removed #### Helm chart: Separated Ingress deprecation The separated Ingress Helm configuration for Camunda 8 Self-Managed was deprecated in Camunda 8.6 and is removed from the Helm chart in Camunda 8.8. Only the combined Ingress configuration is officially supported. See the [Ingress guide](/self-managed/deployment/helm/configure/ingress/ingress-setup.md) for more information on configuring a combined Ingress setup. - If you are using the recommended Camunda 8 deployment option ([Helm charts](/self-managed/deployment/helm/install/quick-install.md)), the upgrade path from version 8.7 to 8.8 will be straightforward by changing the values file to the new syntax. - New migration guides are also provided to support you when migrating from a previous Camunda version. :::caution Additional upgrade considerations are necessary for deployments that use custom scripts, such as Docker containers, manual installations, or custom-developed Kubernetes deployments. For these deployments, customers can either continue to deploy with their original 8.7 topology and upgrade each component independently, or adopt our Helm chart approach for the upgrade, which allows for unifying the deployment into a single JAR or container executable. ::: Change #### Helm chart: Custom users and clients for Management Identity You can now configure custom users and OAuth2 clients for Management Identity during Helm installation. See [adding users and clients](/self-managed/deployment/helm/configure/authentication-and-authorization/custom-users-and-clients.md) for details on configuring custom users and clients on Management Identity during initial Helm install. :::caution Additional upgrade considerations are required for deployments that use custom environment variables, such as `KEYCLOAK_CLIENTS_2_PERMISSIONS_0_RESOURCE_SERVER_ID`. For these deployments, remove the environment variables that reference users or clients and use the configuration method described in the guide linked above. ::: Breaking change #### Orchestration Cluster: Unified component configuration With the Camunda 8.8 release, the new unified configuration is introduced. - Camunda 8.8 introduces a unified configuration with a shared YAML schema across Orchestration cluster components. This allows you to define all essential cluster and component behavior in a single, centralized configuration system. - In Camunda 8.7 and earlier, managing and configuring core components (Zeebe, Operate, Tasklist, Identity) was done separately. This means some configuration properties have changed or are replaced with new properties. See [Camunda 8.8 property changes](/versioned_docs/version-8.8/self-managed/components/orchestration-cluster/core-settings/configuration/configuration-mapping.md) for more information. :::note Only a partial set of unified configuration properties are introduced in Camunda 8.8, with remaining properties planned for delivery with Camunda 8.9. ::: Deprecated #### Orchestration Cluster: Docker image unification With the Camunda 8.8 release, the [Orchestration Cluster](/self-managed/components/orchestration-cluster/overview.md) is provided as a [unified Docker image](https://hub.docker.com/r/camunda/camunda): `camunda/camunda`. As a result, the following Docker images are deprecated as of Camunda 8.8: - [camunda/zeebe](https://hub.docker.com/r/camunda/zeebe) - [camunda/operate](https://hub.docker.com/r/camunda/operate) - [camunda/tasklist](https://hub.docker.com/r/camunda/tasklist) Deprecated #### Helm chart: Secret management improvements and deprecations With the Camunda 8.8 release, a consistent secret pattern for Helm charts is introduced. The legacy secret configuration is deprecated and will be removed with 8.9, but remains functional during the transition period. :::info See the [secret management guide](/self-managed/deployment/helm/configure/secret-management.md) for migration instructions and examples. ::: Change #### Helm chart: External database for Web Modeler REST API With the Camunda 8.8 release, the configuration for the external database used by the Web Modeler REST API is updated to align with the Identity component's database configuration. - A new value, `webModeler.restapi.externalDatabase`, is now available and mirrors the structure of `identity.externalDatabase`. - To ensure backward compatibility, the existing `webModeler.restapi.externalDatabase.url` field is retained and will take precedence if set. Change #### Helm chart: Default username claim in Web Modeler With the Camunda 8.8 release, the default ID token claim that Web Modeler uses to assign usernames has changed from `name` to `preferred_username`. - This change aligns the configuration with other Camunda 8 components for consistency across the platform. - To continue using the `name` claim, explicitly set `CAMUNDA_IDENTITY_USERNAMECLAIM=name` as an environment variable for the Web Modeler `webapp`. See [Identity / Keycloak configuration](/self-managed/components/hub/configuration/properties.md#identity--keycloak-1). Change #### Helm chart: Bitnami Docker repository migration With the Camunda 8.8 release, the Camunda Helm charts are updated to use the new Bitnami Docker repository. :::info See [Bitnami Docker repository migration](/self-managed/upgrade/helm/index.md#bitnami-docker-repository-migration) for migration details. ::: Change #### Bitnami: Alternative container images With the Camunda 8.8 release, alternative container images to the previously used Bitnami open source images are added. This improves security, reliability, and support for Camunda 8 Self-Managed deployments. - These images are hosted by Camunda on `registry.camunda.cloud`. - Beginning with Camunda 8.8, these images are considered the default supported option when deploying Camunda 8 via Helm charts, as they ensure faster delivery of security patches (including CVE fixes) and better alignment with supported environments. - To adopt these images, update your Helm deployment to reference the `values-images-ee.yml` file. :::info Full setup instructions are available in the [installation guide](/self-managed/deployment/helm/install/quick-install.md). ::: New #### Helm chart: Alternative infrastructure methods For production environments, use managed or external services first. If not available, prefer [Kubernetes operators](/self-managed/deployment/helm/configure/operator-based-infrastructure.md) for PostgreSQL, Elasticsearch/OpenSearch, and Keycloak over Bitnami subcharts. Bitnami subcharts remain available for evaluation or proof-of-concept use. Change #### Helm chart: Bitnami subcharts bundled The Bitnami subcharts (PostgreSQL, Keycloak, Elasticsearch, and Common) are bundled directly within the Camunda Helm chart instead of being fetched from external Bitnami repositories at install time. This change reduces the risk of unexpected breaking changes from upstream Bitnami chart updates and gives Camunda full control over the lifecycle of these subcharts. No action is required — existing deployments will continue to work as before when applying the next patch release. New #### Reference architecture: EC2 New EC2 manual and VM blueprint for high availability (HA) multi-AZ clusters. Includes managed OpenSearch, optional Aurora PostgreSQL, dual load balancer pattern, VPN/bastion access, and modular Terraform setup. See [Amazon EC2](/self-managed/deployment/manual/cloud-providers/amazon/aws-ec2.md). New #### Reference architecture: Azure AKS New Azure AKS architecture with a zonal AKS baseline, managed or operator-based data services, unified Ingress and Identity patterns, private networking, and a modular Terraform and Helm workflow. See [Microsoft AKS](/self-managed/deployment/helm/cloud-providers/azure/microsoft-aks/microsoft-aks.md). Change #### Reference architecture: General updates - Managed search (EKS single-region & EC2): OpenSearch upgraded 2.15 → 2.19 (aligns with [supported environments](/reference/supported-environments.md)). - Database layer (EKS & EC2): Aurora PostgreSQL baseline raised 15 → 17 (aligns with [supported environments](/reference/supported-environments.md)). - Identity / global reference architecture: Keycloak now uses Bitnami Premium 26 image (see [OIDC configuration](/self-managed/deployment/helm/configure/authentication-and-authorization/index.md)). - Private access (OpenShift ROSA, EKS, EC2): Added optional VPN pattern (see [EC2 architecture](/self-managed/deployment/manual/cloud-providers/amazon/aws-ec2.md#architecture)). - OpenShift (single & dual region): Validated against OpenShift 4.19 (see [dual region guide](/self-managed/deployment/helm/cloud-providers/openshift/dual-region.md)). - EKS networking: Added alternative NAT gateway strategies (see [EKS Helm guide](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/eks-helm.md)). - High availability: Refreshed dual region deployment material (see [EKS dual region](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/dual-region.md)). - Core diagrams: Updated generic reference architecture visuals for 8.8 Orchestration Cluster changes (see [reference architectures](/self-managed/reference-architecture/reference-architecture.md)). - Terraform module upgrade: AWS EKS module v5 → v6 (review [Terraform EKS setup](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/terraform-setup.md) before upgrading). ### Engine Breaking change #### Process definition draining deletion requires an 8.9.19+ upgrade target {#draining-deletion-upgrade-constraint} Before 8.8.38, deleting a process definition that still had running instances stalled the cluster-wide deployment distribution queue. No further process deployments or deletions were distributed until those instances finished running. From 8.8.38, the definition drains asynchronously so deployments are no longer held up, as it is marked for deletion immediately and removed once its instances finish. As a result, this draining introduces new records that older 8.9 patches cannot replay. **Safe upgrade targets:** 8.9.19 or later. - This constraint primarily affects Self-Managed operators, who choose their own target patch. - Camunda 8 SaaS is not affected as its upgrade path is gated to safe targets. **Action:** When upgrading a Self-Managed cluster from 8.8 to 8.9, you must target **8.9.19 or later**. See the [Self-Managed upgrade guide](/self-managed/upgrade/index.md). Regression #### Multi-instance sub-process output mapping variable scope regression {#multi-instance-output-mapping-regression} Camunda 8.8.23 introduced a regression in which output mappings inside a multi-instance sub-process that also defines an output collection cause local variables to propagate to the parent scope. You're affected if your process contains a multi-instance sub-process that meets both of the following conditions: 1. The sub-process defines an output collection. 2. One or more elements inside the sub-process define output mappings. Under these conditions: - Local variables from inside the sub-process appear in the parent scope and are visible in Operate. - If any leaked variable shares a name with a variable on the parent scope, the parent scope value is overwritten. **Workaround:** Ensure all variable names used inside the multi-instance sub-process are unique and do not reuse names that exist on the parent scope. **Fix:** A fix is available in 8.8.28. The fix reverts the input/output mapping changes that introduced this regression. As a side effect, two previously resolved bugs are reintroduced: - [camunda/camunda#11789](https://github.com/camunda/camunda/issues/11789): FEEL expressions used as mapping sources may not evaluate correctly due to ordering. - [camunda/camunda#35251](https://github.com/camunda/camunda/issues/35251): When one value from a nested variable is listed as an output mapping, all values in the nested variable are merged into the parent scope. Workaround: map the full nested variable instead of individual values. **Action:** - Before the fix is available: ensure all variable names inside the multi-instance sub-process are unique and do not reuse names that exist on the parent scope. - After upgrading to the fixed patch: bugs #11789 and #35251 are reintroduced by the fix. If you previously had adaptations in place to work around these bugs and removed them, reapply those adaptations. Regression #### Output mapping behavior change for object variables {#output-mapping-behavior-change} **Affected versions:** 8.8.23–8.8.27. Fixed in 8.8.28. Patches 8.8.23–8.8.27 changed how output mappings behave when writing to object variables. Upgrading to 8.8.28+ reverts this change, which can alter the behavior of your running processes. Before 8.8.23 and from 8.8.28+, assigning an object literal to a variable replaces the variable entirely. In 8.8.23–8.8.27, the behavior changed to _merge_: existing keys in the variable are preserved and new keys are added. Example: task A sets `result = {a: 1}`, then task B sets `result = {b: 2}`: - _Replace_ (before 8.8.23 and from 8.8.28+): `result = {"b": 2}` — task A's value is overwritten. - _Merge_ (8.8.23–8.8.27): `result = {"a": 1, "b": 2}` — task A's value is preserved. Replace is the intended long-term behavior. The merge behavior in the affected patches was an unintended regression. **Action:** - **Running 8.8.23–8.8.27:** your processes use merge behavior. Identify any process where one task writes to a sub-key of a variable and a later task assigns an object literal to the same parent. If found, either switch the later task to path notation `result.b = 2` or include all required keys explicitly in its object literal. - **Upgrading to 8.8.28+:** replace behavior is restored. The same processes identified above will behave differently after upgrading. If your process was relying on earlier tasks' values being kept, you need to fix it before upgrading: instead of assigning a whole object `result = {a: 1, b: 2}`, make sure it includes all the keys it needs explicitly — or write each key separately `result.a = 1, result.b = 2`. Regression #### Nested input mappings can silently drop sibling fields {#nested-input-mapping-sibling-fields} **Affected versions:** 8.8.34. Reverted in 8.8.35. Camunda 8.8.34 introduced a regression affecting elements with two or more input mappings that write to different nested fields of the same parent variable. Only one of the mapped fields retains its expected value, with the other field silently set to `null` without warning or raised incident. **Example:** You have a parent-scope variable `foo: {bar: 1, baz: 2}` and an element with these two input mappings that both write into a local `foo` variable: 1. Target `foo.bar` ← (maps from) source `=foo.bar` 2. Target `foo.baz` ← (maps from) source `=foo.baz` In this scenario, the local `foo` becomes `{bar: 1, baz: null}`, with `baz` (mapped by the later declaration) no longer able to resolve against the parent scope. **Action:** - **Running 8.8.34:** Combine the mappings into a single mapping that rebuilds the whole object at once. For example, target `foo` with source `={bar: foo.bar, baz: foo.baz}` instead of mapping `foo.bar` and `foo.baz` separately. - **Upgrading to 8.8.34+:** The regression is reverted. Mapping individual fields of the same variable works correctly again. The workaround above is no longer required, but is harmless to keep. Regression #### Chained input mappings can silently drop FEEL temporal value types {#chained-input-mapping-temporal-type-loss} **Affected versions:** 8.8.34. Reverted in 8.8.35. Camunda 8.8.34 introduced a regression affecting elements with two or more input mappings where one mapping produces a FEEL temporal value (`duration`, `date`, `time`, `date-time`, or their local variants) and a later mapping on the same element reads a property from it. The temporal value loses its type, becoming a plain string due to serialization between mapping evaluations before the later mapping runs, so the property access silently evaluates to `null` instead of the expected value. Only temporal types are affected — other FEEL types (strings, numbers, booleans, lists, and contexts) work correctly. **Example:** An element with these two input mappings: 1. Target `age` ← (maps from) source `=@"P1D"` (a duration) 2. Target `ageDays` ← (maps from) source `=age.days` In this scenario, the mapping results in `ageDays` as `null`, as `age`'s duration type is not preserved between the two mappings. **Action:** - **Running 8.8.34:** Combine the mappings into a single mapping, so the duration is created and read in the same expression instead of being written to a variable and read back later. For example, target `ageDays` with source `=@"P1D".days` instead of separate `age` and `ageDays` mappings. - **Upgrading to 8.8.35+:** The regression is reverted. Chained mappings on temporal values work correctly again. Change #### Message TTL cleanup batch size pacing change {#message-ttl-cleanup-batch-size-pacing-change} Starting in Camunda 8.8.22, expired-message cleanup `MessageBatchExpireProcessor` no longer resumes its RocksDB scan from a continuation cursor across batches. Each cleanup batch now re-scans from the start of the message deadline index and skips over the tombstones of messages already expired earlier in the same drain sequence. This makes cleanup cost per batch sensitive to `ttlCheckerBatchLimit`: a low value (for example, `10`) requires many more restart-scans to drain a backlog of expired messages, which can cause backpressure on normal process and message processing. `zeebe.broker.experimental.engine.messages.ttlCheckerBatchLimit` defaults to `100`. **Action:** If you previously tuned `ttlCheckerBatchLimit` down (for example, to `10`) on an earlier patch to avoid latency peaks, revalidate it after upgrading to 8.8.22 or later. Increasing it (for example, to `500`) reduces the number of restart-scans needed to drain a backlog and restores expected throughput. ### Identity Removed #### Tenant-providing interceptors With the 8.8 release, Camunda announces the removal of tenant-providing interceptors. It is superseded by built-in [tenant management](/versioned_docs/version-8.8/components/identity/tenant.md). Removed #### User storage in Elasticsearch/OpenSearch for Operate or Tasklist With the Camunda 8.8 release, user storage in Elasticsearch/OpenSearch for Operate or Tasklist is no longer supported. You must transition to using [Basic authentication](/self-managed/concepts/authentication/authentication-to-orchestration-cluster.md#basic-authentication) and recreate users in Orchestration Cluster Identity. Removed #### LDAP authentication for Operate or Tasklist With the Camunda 8.8 release, LDAP authentication for Operate or Tasklist is no longer supported. You must transition to use [OIDC or Basic authentication](/self-managed/concepts/authentication/authentication-to-orchestration-cluster.md). ### Marketplace Deprecated #### AWS Marketplace offering for Self-Managed As of October 2025, the Self-Managed AWS Marketplace offering is deprecated and no longer publicly available. Existing customers can continue to use the product until their contracts expire. For future use, refer to the [new AWS Marketplace listing](https://aws.amazon.com/marketplace/pp/prodview-6y664fcnydiqg?sr=0-1&ref_=beagle&applicationId=AWSMPContessa) for more information. ### Modeler Removed #### Removed: Cluster authentication `OAUTH` and `CLIENT_CREDENTIALS` in Web Modeler Self-Managed With the Camunda 8.8 release, the deprecated authentication methods `OAUTH` and `CLIENT_CREDENTIALS` for configured [clusters in Web Modeler Self-Managed](/self-managed/components/hub/configuration/properties.md#clusters) are no longer supported. For more information on how to migrate, see the [upgrade guide](/versioned_docs/version-8.8/self-managed/upgrade/components/870-to-880.md#cluster-configuration). Breaking change #### Cluster configuration in Web Modeler Self-Managed The available configuration options for [clusters in Web Modeler Self-Managed](/self-managed/components/hub/configuration/properties.md#clusters) now depend on the version of the cluster. For version 8.8 and above, [new configuration options](/self-managed/components/hub/configuration/properties.md#additional-configuration-for-cluster-versions--88) are required. For more information on how to modify your existing configuration, see the [upgrade guide](/versioned_docs/version-8.8/self-managed/upgrade/components/870-to-880.md#changed-configuration-options). Deprecated #### Play job-based user tasks With the Camunda 8.8 release, user tasks with a job worker implementation are deprecated and no longer supported in Play from cluster versions 8.8 and above. - [Deprecated Operate endpoints](/self-managed/components/orchestration-cluster/core-settings/concepts/usage-metrics.md) - [Deprecated Tasklist endpoint](/self-managed/components/orchestration-cluster/core-settings/concepts/usage-metrics.md). You should consider migrating to [Camunda user tasks](/components/modeler/bpmn/user-tasks/user-tasks.md#camunda-user-tasks). ### SaaS Removed #### Removed: Starter plan The Camunda SaaS Starter plan is no longer available. - Existing customers using a Starter plan must upgrade to the Enterprise plan or move to the Free plan. - Compare plan features and contact us for advice and an Enterprise plan quote. See [Camunda 8 pricing](https://camunda.com/pricing/?utm_source=docs.camunda.io&utm_medium=referral). ### Tasklist Regression #### Tasklist V1: candidate group task visibility {#tasklist-v1-candidate-group-task-visibility} Camunda 8.8.26 introduced a regression in which user tasks become invisible in the Tasklist V1 API when a candidate group's name differs from its ID ([camunda/camunda#55576](https://github.com/camunda/camunda/issues/55576)). With this regression, the Zeebe engine resolves candidate group names to IDs at task creation time, while the Tasklist V1 API resolves the authenticated user's group IDs to names before comparing them against the stored candidate groups. When a group's name and ID differ, this mismatch causes tasks to be invisible to all members of that group. You're affected if you use the Tasklist V1 API with user task access restrictions enabled and any group used as a candidate group in your processes has a name that differs from its ID. **Workaround:** Set the Zeebe broker environment variable `ZEEBE_BROKER_EXPERIMENTAL_ENGINE_CACHES_CANDIDATEGROUPNAMERESOLUTION` to `false` (default: `true`) and ensure your BPMN models reference candidate groups by name rather than ID. This restores correct task visibility for newly created tasks. Tasks created while the regression was active remain affected. **Fix:** The fix was released in [8.8.29](https://github.com/camunda/camunda/releases/tag/8.8.29). After upgrading, tasks created while the regression was active are also fixed without requiring manual intervention. :::note The Tasklist V1 API is deprecated and will be removed in Camunda 8.10. Consider [migrating to the Tasklist V2 API](/apis-tools/tasklist-api-rest/tasklist-api-rest-overview.md) to avoid disruption when upgrading to 8.10 or later. ::: --- ## 8.8 Release notes These release notes identify the main new features included in the 8.8 minor release, including [alpha feature releases](/components/early-access/alpha/alpha-features.md). | Minor release date | End of standard maintenance | Changelog(s) | Release blog | Upgrade guides | | ------------------ | --------------------------- | --------------------------------------------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------- | | 14 October 2025 | 13 April 2027 | [Patch Releases and Changelogs](#technical-changelogs-for-all-88x-releases) | - | [Upgrade guides](/reference/announcements-release-notes/880/whats-new-in-88.md#upgrade-guides) | :::info 8.8 resources - See [release announcements](/reference/announcements-release-notes/880/880-announcements.md) to learn more about supported environment changes and breaking changes or deprecations. - See [What's new in Camunda 8.8](/reference/announcements-release-notes/880/whats-new-in-88.md) for important changes to consider when planning your upgrade from Camunda 8.7. - Refer to the [quality board](https://github.com/orgs/camunda/projects/187/views/15) for an overview of known bugs by component and severity. ::: ### Technical Changelogs for all 8.8.x releases
View GitHub changelogs for all patch releases [Camunda 8.8.44 (22.10.2026)](https://github.com/camunda/camunda/releases/tag/8.8.44)[Camunda 8.8.43 (20.10.2026)](https://github.com/camunda/camunda/releases/tag/8.8.43)[Camunda 8.8.42 (14.10.2026)](https://github.com/camunda/camunda/releases/tag/8.8.42)[Camunda 8.8.41 (06.10.2026)](https://github.com/camunda/camunda/releases/tag/8.8.41)[Camunda 8.8.40 (29.09.2026)](https://github.com/camunda/camunda/releases/tag/8.8.40)[Camunda 8.8.39 (18.09.2026)](https://github.com/camunda/camunda/releases/tag/8.8.39)[Camunda 8.8.37 (31.08.2026)](https://github.com/camunda/camunda/releases/tag/8.8.37)[Camunda 8.8.36 (21.08.2026)](https://github.com/camunda/camunda/releases/tag/8.8.36)[Camunda 8.8.35 (14.08.2026)](https://github.com/camunda/camunda/releases/tag/8.8.35)[Camunda 8.8.34 (03.08.2026)](https://github.com/camunda/camunda/releases/tag/8.8.34)[Camunda 8.8.33 (29.07.2026)](https://github.com/camunda/camunda/releases/tag/8.8.33)[Camunda 8.8.32 (22.07.2026)](https://github.com/camunda/camunda/releases/tag/8.8.32)[Camunda 8.8.31 (07.07.2026)](https://github.com/camunda/camunda/releases/tag/8.8.31)[Camunda 8.8.30 (01.07.2026)](https://github.com/camunda/camunda/releases/tag/8.8.30)[Camunda 8.8.29 (25.06.2026)](https://github.com/camunda/camunda/releases/tag/8.8.29)[Camunda 8.8.28 (16.06.2026)](https://github.com/camunda/camunda/releases/tag/8.8.28)[Camunda 8.8.27 (10.06.2026)](https://github.com/camunda/camunda/releases/tag/8.8.27)[Camunda 8.8.26 (09.06.2026)](https://github.com/camunda/camunda/releases/tag/8.8.26)[Camunda 8.8.25 (02.06.2026)](https://github.com/camunda/camunda/releases/tag/8.8.25)[Camunda 8.8.24 (05.05.2026)](https://github.com/camunda/camunda/releases/tag/8.8.24)[Camunda 8.8.23 (22.04.2026)](https://github.com/camunda/camunda/releases/tag/8.8.23)[Camunda 8.8.22 (16.04.2026)](https://github.com/camunda/camunda/releases/tag/8.8.22)[Camunda 8.8.21 (30.03.2026)](https://github.com/camunda/camunda/releases/tag/8.8.21)[Camunda 8.8.20 (30.03.2026)](https://github.com/camunda/camunda/releases/tag/8.8.20)[Camunda 8.8.19 (23.03.2026)](https://github.com/camunda/camunda/releases/tag/8.8.19)[Camunda 8.8.18 (20.03.2026)](https://github.com/camunda/camunda/releases/tag/8.8.18)[Camunda 8.8.17 (17.03.2026)](https://github.com/camunda/camunda/releases/tag/8.8.17)[Camunda 8.8.16 (09.03.2026)](https://github.com/camunda/camunda/releases/tag/8.8.16)[Camunda 8.8.15 (04.03.2026)](https://github.com/camunda/camunda/releases/tag/8.8.15)[Camunda 8.8.14 (20.02.2026)](https://github.com/camunda/camunda/releases/tag/8.8.14)[Camunda 8.8.13 (18.02.2026)](https://github.com/camunda/camunda/releases/tag/8.8.13)[Camunda 8.8.12 (17.02.2026)](https://github.com/camunda/camunda/releases/tag/8.8.12)[Camunda 8.8.11 (05.02.2026)](https://github.com/camunda/camunda/releases/tag/8.8.11)[Camunda 8.8.10 (04.02.2026)](https://github.com/camunda/camunda/releases/tag/8.8.10)[Camunda 8.8.9 (06.01.2026)](https://github.com/camunda/camunda/releases/tag/8.8.9)[Camunda 8.8.8 (11.12.2025)](https://github.com/camunda/camunda/releases/tag/8.8.8)[Camunda 8.8.7 (09.12.2025)](https://github.com/camunda/camunda/releases/tag/8.8.7)[Camunda 8.8.6 (02.12.2025)](https://github.com/camunda/camunda/releases/tag/8.8.6)[Camunda 8.8.5 (27.11.2025)](https://github.com/camunda/camunda/releases/tag/8.8.5)[Camunda 8.8.4 (17.11.2025)](https://github.com/camunda/camunda/releases/tag/8.8.4)[Camunda 8.8.3 (05.11.2025)](https://github.com/camunda/camunda/releases/tag/8.8.3)[Camunda 8.8.2 (27.10.2025)](https://github.com/camunda/camunda/releases/tag/8.8.2)[Camunda 8.8.1 (22.10.2025)](https://github.com/camunda/camunda/releases/tag/8.8.1)[Camunda 8.8.0 (09.10.2025)](https://github.com/camunda/camunda/releases/tag/8.8.0)[Connectors 8.8.24 (22.10.2026)](https://github.com/camunda/connectors/releases/tag/8.8.24)[Connectors 8.8.23 (20.10.2026)](https://github.com/camunda/connectors/releases/tag/8.8.23)[Connectors 8.8.22 (28.09.2026)](https://github.com/camunda/connectors/releases/tag/8.8.22)[Connectors 8.8.21 (18.09.2026)](https://github.com/camunda/connectors/releases/tag/8.8.21)[Connectors 8.8.20 (18.09.2026)](https://github.com/camunda/connectors/releases/tag/8.8.20)[Connectors 8.8.19 (04.09.2026)](https://github.com/camunda/connectors/releases/tag/8.8.19)[Connectors 8.8.18 (29.08.2026)](https://github.com/camunda/connectors/releases/tag/8.8.18)[Connectors 8.8.17 (18.08.2026)](https://github.com/camunda/connectors/releases/tag/8.8.17)[Connectors 8.8.16 (04.08.2026)](https://github.com/camunda/connectors/releases/tag/8.8.16)[Connectors 8.8.15 (26.06.2026)](https://github.com/camunda/connectors/releases/tag/8.8.15)[Connectors 8.8.14 (01.06.2026)](https://github.com/camunda/connectors/releases/tag/8.8.14)[Connectors 8.8.13 (22.05.2026)](https://github.com/camunda/connectors/releases/tag/8.8.13)[Connectors 8.8.12 (20.05.2026)](https://github.com/camunda/connectors/releases/tag/8.8.12)[Connectors 8.8.11 (05.05.2026)](https://github.com/camunda/connectors/releases/tag/8.8.11)[Connectors 8.8.10 (30.03.2026)](https://github.com/camunda/connectors/releases/tag/8.8.10)[Connectors 8.8.9 (26.03.2026)](https://github.com/camunda/connectors/releases/tag/8.8.9)[Connectors 8.8.8 (26.02.2026)](https://github.com/camunda/connectors/releases/tag/8.8.8)[Connectors 8.8.7 (02.02.2026)](https://github.com/camunda/connectors/releases/tag/8.8.7)[Connectors 8.8.6 (27.01.2026)](https://github.com/camunda/connectors/releases/tag/8.8.6)[Connectors 8.8.5 (07.01.2026)](https://github.com/camunda/connectors/releases/tag/8.8.5)[Connectors 8.8.4 (01.12.2025)](https://github.com/camunda/connectors/releases/tag/8.8.4)[Connectors 8.8.3 (19.11.2025)](https://github.com/camunda/connectors/releases/tag/8.8.3)[Connectors 8.8.2 (06.11.2025)](https://github.com/camunda/connectors/releases/tag/8.8.2)[Connectors 8.8.1 (16.10.2025)](https://github.com/camunda/connectors/releases/tag/8.8.1)[Connectors 8.8.0 (10.10.2025)](https://github.com/camunda/connectors/releases/tag/8.8.0)
## Agentic orchestration Agentic orchestrationAI agentsSaaSSelf-Managed Camunda agentic orchestration allows you to build and orchestrate AI agents within your BPMN-based workflows, enabling human tasks, deterministic rule sets, and AI-driven decisions to collaborate in a robust, end-to-end process. Camunda Agentic orchestration Use the following new features to build and integrate AI agents into your processes: **Feature** **Description** [AI agent connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md) Enables AI agents to integrate with an LLM to provide interaction/reasoning capabilities. This connector is designed for use with an ad-hoc sub-process in a feedback loop, providing automated user interaction and tool selection. [MCP Client connector](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-client.md) Connect an AI agent connector to tools exposed by [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers. [Ad-hoc tools schema resolver connector](/components/connectors/out-of-the-box-connectors/agentic-ai-ahsp-tools-schema-resolver.md) Can be used independently with other AI connectors for direct LLM interaction. Use this connector if you don’t want to use the AI agent connector but still want to resolve tools for an ad-hoc sub-process or debug tool definitions. [Vector database connector](/components/connectors/out-of-the-box-connectors/embeddings-vector-db.md) Allows embedding, storing, and retrieving LLM embeddings. Use this connector to build AI-based solutions such as context document search, long-term memory for LLMs, and agentic AI interaction. [fromAi() FEEl function](/components/modeler/feel/builtin-functions/feel-built-in-functions-ai-agent.md) Use in combination with the AI Agent connector. See [AI Agent tool definitions](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-tool-definitions.md). ## APIs & tools APIs & toolsSaaSSelf-Managed ### APIs Use the Orchestration Cluster REST API to interact programmatically with the Orchestration Cluster. - This replaces component APIs (Operate API, Tasklist API, Zeebe API, and much of Identity API) with a single set of endpoints. - This unified API supports both organizational (SaaS) and Self-Managed deployments. - This is now the default and recommended integration point for developers, operators, and automation solutions. - You can also use a Swagger UI to interact with this API. Orchestration Cluster API ### Camunda Java client The new Camunda Java Client replaces the Zeebe Java Client as the official Java library for building process applications that integrate with Camunda 8. This client provides everything you need to programmatically interact with the Orchestration Cluster, whether you are orchestrating microservices, managing human tasks, or visualizing process data. Get started with the Camunda Java client ### Camunda Process Test H2 data layer support Camunda Process Test supports using the [H2 Database Engine](https://www.h2database.com/html/main.html) as the default embedded data layer. - H2 is now automatically provisioned when integrating the Camunda Process Test libraries, eliminating manual database configuration and reducing memory footprint. - H2 support streamlines the developer experience for your Spring Boot and plain Java projects. Process testing is now faster to set up, simpler to maintain, and easier to integrate with your continuous integration workflows. Camunda Process Test ### Camunda Spring Boot Starter The Camunda Spring Boot Starter replaces the Spring Zeebe SDK to simplify interaction between a Spring Boot application and Camunda 8, allowing you to: - Easily integrate process entity management and queries within your workflows. - Seamlessly configure endpoints and authentication via Spring Boot auto-configuration, minimizing boilerplate code. - Rely on an official, standardized approach to guarantee consistency and reduce maintenance costs when upgrading. - Based on Spring Boot 3.5 ([version compatibility matrix](/apis-tools/camunda-spring-boot-starter/getting-started.md#version-compatibility)). Get started with the Camunda Spring Boot Starter ### Process instance tags Introduce optional, immutable **process instance tags** set at creation for lightweight routing, correlation, and future prioritization without inspecting large variable payloads. See [process instance creation](/components/concepts/process-instance-creation.md#tags) and [job workers](/components/concepts/job-workers.md#tags). Process instance creation ### Public API Find out what’s included in Camunda 8's public API, the policies around versioning, and what to expect when upgrading. - The public API is the official contract between Camunda and its users under SemVer. - No breaking changes will be made to the public API in minor or patch releases. - You can safely build on these interfaces with the expectation of stability and backward compatibility. Public API ### Run process segment Manually execute and test individual tasks or segments (connectors, RPA bots, IDP extractions) without running full processes, improving debugging and development efficiency. Run process segment ### Spring Boot 4.1 support The `camunda-spring-boot-4-starter` bundles Spring Boot 4.0.x by default. As the [Spring Boot OSS support](https://spring.io/projects/spring-boot#support) for 4.0.x will end in December 2026, [Spring Boot 4.1.x compatibility](/apis-tools/camunda-spring-boot-starter/getting-started.md#version-compatibility) is verified from the `8.8.30` patch onward. ## Connectors ConnectorsSaaSSelf-Managed ### Ad-hoc tools schema resolver connector This connector implements the tool resolution part of the AI Agent connector, but can also be used independently with other AI connectors for direct LLM interaction. Use this connector if you don’t want to use the AI agent connector but still want to resolve tools for an ad-hoc sub-process or debug tool definitions. Ad-hoc tools schema resolver connector ### AI Agent connector Enable AI agents to integrate with an LLM to provide interaction/reasoning capabilities. Designed for use with an [ad-hoc sub-process](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md) in a [feedback loop](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess-example.md), providing automated user interaction and tool selection. AI Agent connector ### Azure Blob Storage connector Store and retrieve documents within Camunda workflows using Azure Blob Storage. Enables seamless document management directly within processes, improves efficiency, and reduces the need for custom integrations. Azure Blob Storage connector ### CSV connector (SaaS) Read, filter, transform, and write CSV data within processes. Reduces technical debt, accelerates development, and broadens integration capabilities with native support for this universal data format. CSV connector ### Google Cloud storage connector Store and retrieve documents directly within Camunda workflows, streamlining document management without custom development. Google Cloud storage connector ### Hubspot Connector Connect your BPMN service with [HubSpot](https://hubspot.com/) and manage your HubsSpot contacts, companies, and deals. HubSpot connector ### MCP Client connector Enable Camunda processes and AI agents to auto-discover and invoke external tools, eliminating hardwired connectors and enabling dynamic, metadata-driven tool integration. MCP client connector ### Vector database connector Enable embedding, storing, and retrieving Large Language Model (LLM) embeddings. Use to build AI-based solutions for your organizations, such as context document search, long-term LLM memory, and agentic AI interaction in combination with the AI Agent connector (RAG). Vector database connector ### Email connector `Message-ID` The Email connector now exposes the `Message-ID` provided by the client in the connector response payload. This allows for improved traceability, easier correlation between sent messages and logs, and better integration with downstream systems that rely on `Message-ID`. :::note This change is backwards-compatible and does not require any action. You can now optionally use the `messageId` field for enhanced tracking when parsing connector responses. ::: ### Intrinsic functions A new `getJson` intrinsic function accepts a document and an optional FEEL expression. It extracts and returns content from a JSON document as an object. - The optional FEEL expression parameter specifies the part that will be extracted from the JSON document content. - If not provided, the whole document is returned as a JSON object. Intrinsic functions ### Job header binding Use the `@Header` annotation to bind a header value (for example, a FEEL expression) to a model class. ### SQL connector Oracle database connection The SQL connector now supports Oracle Database connections. SQL connector ## Console ConsoleSaaSSelf-Managed ### Connector manage and run supports multiple runtimes Connector manage and run in Console now supports management of multiple connector runtime instances. Manage your connectors ### Docker distribution Console is now available as a Self-Managed Docker distribution. Deploy Camunda Console using a Docker image, run Console outside Kubernetes using Docker Compose or other container services, and use Console with Camunda 8 clusters deployed via Docker. Docker Compose developer quickstart ### Regions New regions are available for SaaS clusters on Amazon Web Services: | Type | Region | | :----------------------- | :---------------------------------- | | AWS region | Singapore (ap-southeast-1) | | Secondary backups region | Ireland, Europe (eu-west-1) | | Secondary backups region | Jakarta, Indonesia (ap-southeast-3) | | Secondary backups region | Oregon, North America (us-west-2) | Use these regions to: - Improve overall processing speed and reduce latency if you operate in countries within that region. - Keep cluster data within that country/region to support your local data residency and compliance needs. Regions ### Usage metrics for licence model and tenant Usage metrics now support per-tenant reporting and align with Camunda’s updated licensing model based on the number of tenants. ## Desktop Modeler Desktop ModelerSaaSSelf-Managed ### Settings The new **Settings** window in Desktop Modeler allows you to configure the application and customize your modeling experience. You can select your default execution platform version, as well as other options that were previously only available as flags. Desktop Modeler settings ## Helm chart deployment Helm chartsSelf-Managed ### Alternative container images Camunda provides alternative container images to the previous Bitnami images, offering better security and faster patch delivery. From 8.8, these are the default supported images. - These images are hosted on `registry.camunda.cloud`. - To use them, update your Helm deployment to reference the `values-images-ee.yml` file. Install Camunda with Helm ### Configurable volumes The Helm chart now supports configurable volumes. You can define `PersistentVolumeClaims` or continue using `EmptyDir` through `values.yaml`. ### ExtraVolumeClaimTemplates You can now add custom `extraVolumeClaimTemplates` to the Zeebe/Core StatefulSet by supplying an array of templates in your Helm values file. This allows you to attach additional persistent volumes to each Zeebe/Core pod for use cases such as custom storage or log directories. :::info important Kubernetes does not allow you to change the `volumeClaimTemplates` of an existing StatefulSet. If you add, remove, or modify `extraVolumeClaimTemplates` after initial deployment, you must delete and recreate the StatefulSet (which will also delete the pods) for the changes to take effect. This may require additional planning and data migration steps to avoid data loss. ::: ### Common labels for Camunda resources A new `commonLabels` value is now available and integrates with `camundaPlatform.labels`. This allows you to define mutable labels that are automatically applied to all Camunda resources. By setting `commonLabels`, you can ensure consistent labeling across deployments, making it easier to manage, organize, and identify resources within your Camunda environment. ### Configure Web Modeler replicas The number of replicas for the Web Modeler REST API and web app deployments can be set with new configuration properties: `webModeler.restapi.replicas` and `webModeler.webapp.replicas`, respectively. ### Alternative infrastructure methods (Helm) Production guidance now recommends running PostgreSQL, Elasticsearch / OpenSearch, and Keycloak using **managed or external services first**. If these are not available, use **vendor-supported operators** instead of Bitnami subcharts when moving beyond evaluation environments. Why it matters: - Managed services such as AWS RDS / Aurora, Amazon OpenSearch, and Azure Database for PostgreSQL provide automatic maintenance, scaling, and backups. - Vendor-supported operators (CloudNativePG, Elastic / ECK, Keycloak Operator) automate upgrades, failover, backups, credentials, and certificate rotation when managed services aren’t available or suitable. - Decouples critical data services from the Camunda Helm release cycle and values file structure, reducing blast radius and enabling independent scaling and maintenance. - Enables faster CVE patch adoption and native observability through built-in monitoring and status conditions. - Continue using Bitnami subcharts for local development and proofs of concept, evaluate managed services or vendor-supported operators for staging, and use managed or vendor-supported options for production. Key updates in 8.8 documentation: - A consolidated vendor-supported infrastructure guide with architecture topology and service hand-off points. - Replacement examples for each subchart, including connection secrets, services, and TLS configuration. - Clarified Keycloak Operator behavior: no embedded Service route by default; use Ingress or on-demand port-forwarding for initial administration. ### Reference architecture: Amazon EC2 (manual / VM) New production reference architecture for running the Camunda 8 Orchestration Cluster directly on Amazon EC2 using Terraform to provision networking, IAM, and managed AWS services. Highlights: - Three-node Orchestration Cluster across three Availability Zones for balanced partition placement and quorum resilience. - Managed OpenSearch (baseline 2.19) and optional Aurora PostgreSQL 17 to reduce operational overhead. - Dual load balancer pattern (ALB for HTTP / web apps and REST, NLB for gRPC) supporting protocol-appropriate routing and future mTLS expansion. - Optional VPN / bastion design for secure private subnet administration; security groups follow least-privilege principles. - Modular Terraform stacks for network, security, compute, search, and database, with reusable outputs for automation or configuration management. ### Reference architecture: Azure AKS New Kubernetes reference architecture for Microsoft Azure that combines AKS with Terraform for infrastructure and Helm for platform lifecycle management. Highlights: - Multi-AZ (zonal) AKS baseline with separate node pools for system and workload components when supported in the region. - Flexible data layer options: managed or embedded PostgreSQL and Elasticsearch (Azure Database for PostgreSQL, or embedded Elasticsearch via chart deployment). - Unified Identity and Ingress patterns for the Orchestration Cluster, including shared or split fronts, TLS termination choices, and private endpoints or internal load balancers. - Azure-native networking and security: Private DNS, NSGs, and managed identities mapped to Camunda component requirements. - Terraform + Helm separation enables idempotent infrastructure updates and predictable application rollouts. See the reference architecture guides for full topology diagrams, sizing guidance, and migration considerations. ## Integrations ### Microsoft Teams The new Camunda for Microsoft Teams app allows you to view, claim, and complete Camunda tasks directly in Microsoft Teams. The app integration allows you to: - Start processes from a channel, chat, or the app **Home** tab. - Fill out start forms in Microsoft Teams and submit them to kick off workflows, with optional links to Operate for monitoring. Camunda for Microsoft Teams app :::note The Microsoft Teams integration is released as an [early access](/components/early-access/overview.md) alpha feature to allow you to test and participate in development by sharing feedback before general availability, and is subject to alpha feature limitations. ::: ### SAP Advanced Event Mesh (AEM) connectivity Connect Camunda directly to SAP Advanced Event Mesh (AEM) for event-driven process automation. - New AEM connectors allow you to receive CloudEvents from SAP systems as BPMN messages and publish CloudEvents from Camunda to AEM. - This enables seamless integration with SAP’s event-driven architecture, allowing your Camunda processes to react in real time to business events across SAP BTP, S/4HANA, or ECC. SAP Eventing with SAP Advanced Event Mesh ### ServiceNow Extend the power of your process automation by integrating Camunda with ServiceNow. This integration enables seamless communication between your BPMN workflows and ServiceNow IT Service Management (ITSM), helping you automate routine tasks and accelerate service delivery. The ServiceNow integration allows you to: - Manage ServiceNow data: Create, read, update, and delete records in any ServiceNow table directly from Camunda workflows. - Trigger ServiceNow flows: Start automations built in ServiceNow's Flow Designer as part of an end-to-end process. - Orchestrate ITSM processes: Integrate Camunda tasks with ServiceNow approvals, incidents, and service requests to create unified workflows. ServiceNow integration ## Intelligent document processing (IDP) IDPSaaS ### Bring your own model (BYOM) When using an OpenAI Compatible provider, you can use your own model for document extraction. Any model provider that implements the `/chat/completions` API endpoint is supported. Supported providers are: - **AWS**: Amazon Web Services with Bedrock and Textract (supports both structured and unstructured extraction). - **Azure**: Microsoft Azure with AI Document Intelligence and AI Foundry (unstructured extraction only). - **GCP**: Google Cloud Platform with Vertex AI and Document AI (supports both structured and unstructured extraction). - **OpenAI compatible**: Supports OpenAI and any provider implementing the OpenAI `/chat/completions` API (unstructured extraction only). Document extraction ### Structured data form extraction You can use form-based structured document extraction to capture data from structured documents. - For example, you can use this extraction method for documents with a consistent layout, such as invoices, tax forms (for example, W-2s, VAT declarations), and loan or insurance applications. - Projects can be shared organization-wide, enhancing accessibility to extraction capabilities. Extract structured data ## Migration from Camunda 7 to Camunda 8 Camunda 7 migrationSaaSSelf-Managed ### Data migration tool Use the Data Migrator to copy running process instances from Camunda 7 to Camunda 8. - Copy running process instances (state-preserving). - Copy process variables and their values. - Handle problematic instances gracefully with retry options. - Write custom code to intercept variable migration. Data Migrator ### Diagram Converter The Diagram Converter helps you get a first understanding of migration tasks when moving from Camunda 7 to Camunda 8. It analyzes Camunda 7 model files (BPMN or DMN) and generates a list of tasks required for the migration. It can also automatically convert these files from Camunda 7 format to Camunda 8 format (updating namespaces, XML structures/properties, and simple expression transforms), with a web UI and CLI that outputs XLSX/CSV reports, for prioritization and batch conversion. Diagram Converter ### Code conversion Code conversion utilities provide code mapping tables, conversion patterns, and automatable refactoring recipes to systematically translate Camunda 7 implementation patterns to Camunda 8 equivalents. Code conversion ## Optimize OptimizeSelf-Managed ### Amazon OpenSearch support Camunda 8 Self-Managed now fully supports the use of Amazon OpenSearch with Optimize. ## Orchestration Cluster Orchestration ClusterZeebeOperateTasklistIdentitySaaSSelf-Managed ### Architecture The primary architectural change in 8.8 is the consolidation of the core Zeebe, Operate, Tasklist, and Identity components into the Orchestration Cluster application as a single deployable artifact, distributed as a JAR file or Docker container. This impacts how Camunda 8 is deployed, managed, and scaled. - The Orchestration Cluster (previously automation cluster) is now the core component of Camunda 8. - Use the Orchestration Cluster REST API to interact programmatically with the Orchestration Cluster. - The new unified exporter architecture improves cluster management and data migration, bringing importer and archiving logic of web components (Tasklist and Operate) closer to the distributed platform (Zeebe). The index schema is also harmonized. - The unified configuration for Orchestration Cluster components allows you to define cluster and component behavior. What's new in Camunda 8.8 ### Archive by ID for Elasticsearch and OpenSearch Archiving of finished process instance data in Elasticsearch and OpenSearch secondary storage now uses a targeted, incremental approach by default. Documents are moved in small, targeted batches rather than in a single operation, improving stability and reducing resource pressure during archiving. The `rolloverBatchSize` and `reindexBatchSize` properties control how many process instances and individual documents are processed per batch. Data retention ### Reference architecture: General updates The 8.8 release cycle includes updates across multiple Self-Managed reference architecture guides and infrastructure baselines. - **Managed search (EKS single-region and EC2)**: Upgraded OpenSearch from 2.15 to 2.19 to align with the latest [supported environments](/reference/supported-environments.md). - **Database layer (EKS and EC2)**: Raised the Aurora PostgreSQL baseline from version 15 to 17. See the updated versions in [supported environments](/reference/supported-environments.md). - **Identity and global architecture**: Standardized Keycloak on the Bitnami Premium 26 image. See [OIDC configuration](/self-managed/deployment/helm/configure/authentication-and-authorization/index.md). - **Private access (OpenShift ROSA, EKS, EC2)**: Documented an optional VPN pattern. See [EC2 architecture](/self-managed/deployment/manual/cloud-providers/amazon/aws-ec2.md#architecture). - **OpenShift (single and dual region)**: Updated validation and guidance for OpenShift 4.19. See [dual region guide](/self-managed/deployment/helm/cloud-providers/openshift/dual-region.md). - **EKS networking**: Added alternative NAT gateway strategies. See [EKS Helm guide](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/eks-helm.md). - **High availability**: Refreshed dual-region operational guides. See [EKS dual region](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/dual-region.md). - **Core diagrams**: Updated generic reference architecture visuals for the unified Orchestration Cluster. See [reference architectures](/self-managed/reference-architecture/reference-architecture.md). - **Terraform module upgrade**: Upgraded the AWS EKS Terraform module from v5 to v6. Review changes in [Terraform EKS setup](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/terraform-setup.md) before upgrading. ### Dynamic activation of ad-hoc sub-processes using job workers Ad-hoc sub-process elements can be activated by job workers: - Define a task in the process model. - When the engine reaches the ad-hoc sub-process, a job is created. Completing this job with a job result lets you define which elements in the ad-hoc sub-process to activate. - Once any flows in the ad-hoc sub-process complete, a new job is created, giving job workers control over what to do next. ### Dynamic partition scaling You can now add new Zeebe partitions to a running cluster to increase capacity without downtime: - New process instances will be started on new partitions immediately. - Existing process instances do not migrate between partitions, so it can take time for the cluster to reach equilibrium. - Existing messages remain on original partitions, potentially causing slight imbalances for message-heavy workloads (future updates will address this) Cluster scaling ### Identity and Management Identity In Camunda 8.8, Orchestration Cluster Identity and Management Identity are two separate components used for Identity management, each with distinct areas of responsibility. - **Identity**: Access and permission management for all Orchestration Cluster components: Zeebe, Operate, Tasklist, and the Orchestration Cluster REST and gRPC API. - **Management Identity**: Continues to manage access for platform components such as Web Modeler, Console, and Optimize. What's new in Camunda 8.8 ### Migrate taken sequence flows flowing to the joining gateway Enhanced migration now supports taken sequence flows leading to joining gateways: - Define migration plans mapping active elements and taken sequence flows - Configure plans via Operate UI or API ### Process instance migration Enhanced process instance migration allows you to solve problems with process definitions and use the latest process improvements. You can now migrate compensation boundary event subscriptions, escalation boundary events, and escalation event subprocesses. Process instance migration ### React to expired messages with a custom exporter You can now use a custom exporter to react to expired messages. - The original message is retrieved from primary storage upon expiration, and the expiry event enhanced with the original message. - Existing functionality remains unchanged, so there is no disruption for current customers or custom exporters. With these updates, developers can subscribe to the expired messages through a custom exporter, examine the event content, and optionally re-publish or handle the message differently. ### Run Orchestration Cluster without secondary storage You can now run Orchestration Cluster in "Zeebe-only" mode without secondary storage: - Ideal for setups not using Query APIs, Operate, or Tasklist - Enable by setting `camunda.database.type=none` - Starts only the required Zeebe components ### User task listeners Task lifecycle management is enhanced with user task listeners, allowing users to react to specific user task lifecycle events. - Process designers can now model task listeners for different events, such as `assigning` and `completing`. - Developers can use the same job infrastructure to activate and complete task listener jobs. - Operations engineers can easily check details of active and completed task listeners within instances, and efficiently resolve task listener incidents. This enhancement streamlines operations and ensures smoother incident handling, improving time to unblock process execution. User task listeners ### Zeebe-managed resilient batch operations All batch operations, such as canceling or resolving incidents in bulk, are now handled by Zeebe instead of Operate. - This change ensures region failovers in the multi-region setup no longer risk losing critical batch commands. - Users will initiate and manage batch operations through the Orchestration Cluster REST API and the Operate UI, but the underlying processing occurs within Zeebe. - By moving batch operations to the core engine, multi-region deployments gain reliability and resilience. ## Robotic Process Automation (RPA) RPASelf-Managed ### Multi-file script support RPA supports multi-file script support to allow you to organize scripts modularly, reuse common automation components, and integrate existing Robot Framework scripts. The execution engine fully supports multi-file scripts and linked resources, improving scalability, maintainability, and flexibility for enterprise automation projects. ### RPA worker offline installer An offline installer package for the [RPA](/components/rpa/overview.md) worker allows installation without internet connectivity. The offline installer removes reliance on external repositories or downloads, ensuring consistent, secure, and hassle-free deployment into air-gapped or restricted environments. ## Web Modeler Web ModelerSaaSSelf-Managed ### Bring your own model (BYOM) BPMN Copilot and FEEL Copilot in the Web Modeler are now available for Camunda Self-Managed customers in a safe, secure way. You can connect these features to your own AI provider and model, even fine-tuning the model yourself to improve its performance and relevance to your organization. ### CI/CD building blocks guide Element templates documentation is improved to support scalable reuse of building blocks, focusing on CI/CD practices for managing templates across teams and environments. Key updates include: - Guidance on integrating element templates and dependencies into CI/CD pipelines - Restructuring the documentation to improve discoverability and usability - Tool-agnostic content covering Web Modeler, dependency management, and local development setups - Moving relevant information from Connectors to the element templates section Element templates in Modeler ### Cluster basic authentication In addition to bearer token authentication, you can now configure Web Modeler in Self-Managed to use Basic authentication for cluster access. - Set the `CAMUNDA_MODELER_CLUSTERS_0_AUTHENTICATION` environment variable value to `BASIC`. - Web Modeler sends a username and password with every request to the cluster. Available authentication methods ### Element template editor onboarding Web Modeler now provides a low-coder friendly UX for creating building blocks. Whether you create an element template from scratch or from a task, you can quickly set and narrow down the properties so process developers using these templates can quickly and confidently wire building blocks together into an E2E process. Generate an element template ### Element template support for all tasks You can now save any configured task as a reusable element template directly from the Web Modeler properties panel: - Save configured tasks (service, user, send, receive, business rule, script tasks, or call activities) as templates. - Edit templates to adjust input/output bindings, validation rules, categories, and more. - Publish templates to your project or organization for reuse. Save tasks as element templates ### Unlock element template fields Element template management is now more flexible for developers and DevOps teams. - You can assign custom semantic IDs and use an intuitive versioning scheme, ensuring templates are portable and retain stable references across different environments. - Template names and file names can be managed independently, and you can quickly import templates using copy and paste, git sync, or CI/CD pipeline. - Safeguards now notify you of ID or version conflicts to prevent accidental overwrites when publishing templates. Manage element templates ### FEEL Copilot Chat with the AI FEEL Copilot for help generating FEEL (Friendly Enough Expression Language) expressions in Web Modeler. FEEL Copilot ### FEEL Playground Use the FEEL Playground to validate and troubleshoot your FEEL expressions when modeling process diagrams in Web Modeler. FEEL Playground ### Git sync Azure DevOps and Bitbucket integration In addition to GitHub and GitLab, Web Modeler now supports integration with Azure DevOps and Atlassian Bitbucket. Git sync ### Low-code process testing You can now save and rerun versioned test scenarios in Web Modeler: - Supports user tasks, connectors, and basic branching logic - Ideal for process developers and CoEs - Enables behavior-driven development with Camunda Process Test Scenarios ### RDBMS support for Oracle and MS SQL in Self-Managed Web Modeler Self-Managed now supports Oracle Database and Microsoft SQL Server for simpler setup and maintenance. Set up Oracle or MS SQL ### Username claim configuration Configure the ID token claim used for usernames via the `CAMUNDA_IDENTITY_USERNAMECLAIM` environment variable to account for differences between identity providers and ensure user-friendly names. ### Version description Use the version **Description** field to track changes alongside the version tag (for example, as a change log or Git commit message). This helps make versioning more intuitive and collaborative, keeps teams aligned, and reduces ambiguity. Web Modeler versioning ## 8.8.0-alpha8 | Release date | Changelog(s) | Blog | | :--------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------- | | 9 September 2025 | [ Camunda 8 core ](https://github.com/camunda/camunda/releases/tag/8.8.0-alpha8)[ Connectors ](https://github.com/camunda/connectors/releases/tag/8.8.0-alpha8) | [Release blog](https://camunda.com/blog/2025/09/camunda-alpha-release-september-2025/) | ### Ad-hoc sub-process dynamic activation using job workers SaaSSelf-ManagedAgentic orchestration Ad-hoc sub-process elements can now be activated by job workers: - Define a task in the process model. - When the engine reaches the ad-hoc sub-process, a job is created. Completing this job with a job result lets you define which elements in the ad-hoc sub-process to activate. - Once any flows in the ad-hoc sub-process complete, a new job is created, giving job workers control over what to do next. :::note The job result also supports fulfilling the completion condition for the ad-hoc sub-process. When this condition is met, the sub-process waits for active children to complete before finishing, unless you explicitly cancel remaining instances in the job result. ::: ### CI/CD building blocks guide SaaSSelf-ManagedModeler Element templates documentation is improved to support scalable reuse of building blocks, focusing on CI/CD practices for managing templates across teams and environments. Key updates include: - Guidance on integrating element templates and dependencies into CI/CD pipelines - Restructuring the documentation to improve discoverability and usability - Tool-agnostic content covering Web Modeler, dependency management, and local development setups - Moving relevant information from Connectors to the element templates section See [element templates in Modeler](/components/modeler/element-templates/about-templates.md) for details. ### Connectors SaaSSelf-ManagedConnectors #### Job header binding Use the `@Header` annotation to bind a header value (for example, a FEEL expression) to a model class. #### SQL connector Oracle database connection The SQL connector now supports Oracle Database connections. See [SQL connector](/components/connectors/out-of-the-box-connectors/sql.md) for more information. :::note You must manually download the Oracle JDBC driver from [Oracle](https://www.oracle.com/database/technologies/appdev/jdbc-downloads.html) due to licensing restrictions. Run the connector in [hybrid mode](/components/connectors/use-connectors-in-hybrid-mode.md) to enable Oracle Database connections. ::: ### Console SaaSSelf-ManagedConsole #### Docker distribution Console is now available as a Self-Managed Docker distribution: - Deploy Camunda Console using a Docker image - Run Console outside Kubernetes using Docker Compose or other container services - Use Console with Camunda 8 clusters deployed via Docker See [Docker Compose developer quickstart](/self-managed/quickstart/developer-quickstart/docker-compose.md) for setup instructions. #### Usage metrics for licence model and tenant Usage metrics now support per-tenant reporting and align with Camunda’s updated licensing model based on the number of tenants. ### Element template support for all tasks SaaSSelf-ManagedModeler You can now save any configured task as a reusable element template directly from the Web Modeler properties panel: - Save configured tasks (service, user, send, receive, business rule, script tasks, or call activities) as templates - Edit templates to adjust input/output bindings, validation rules, categories, and more - Publish templates to your project or organization for reuse See [save tasks as element templates](/components/hub/workspace/modeler/element-templates/save-as-element-templates.md) for details. Additional support includes: - `zeebe:assignmentDefinition` for assigning users or groups - `zeebe:priorityDefinition` for setting task priorities (integer or FEEL expression) - `zeebe:taskSchedule` for templating task scheduling configurations ### Migrate taken sequence flows flowing to the joining gateway Enhanced migration now supports taken sequence flows leading to joining gateways: - Define migration plans mapping active elements and taken sequence flows - Configure plans via Operate UI or API ### Operate and Tasklist API deprecation SaaSSelf-ManagedAPI The Operate and Tasklist APIs are now deprecated in favor of the Orchestration Cluster API for task and process management. :::note The deprecated APIs remain functional in this release but will no longer receive feature updates and will be removed in version 8.10. ::: ### Orchestration Cluster SaaSSelf-ManagedOrchestration Cluster #### Orchestration Cluster Identity **Self-Managed Identity Management**: - Create and manage users, groups, roles, and memberships directly in Identity’s database - Integrate external providers like Keycloak or Microsoft Entra via OIDC - Assign resource-based authorizations using RBAC - Map users, groups, and roles to resources using token claims and application/client mappings - Simplify migration with built-in tools **SaaS enhancements**: - Integrate organizational identity providers for centralized user management - Assign roles, groups, and authorizations per cluster #### Orchestration Cluster scaling Add Zeebe partitions to a running cluster in Self-Managed to increase capacity without downtime: - New partitions start processing tasks immediately - Existing messages remain on original partitions, potentially causing slight imbalances for message-heavy workloads (future updates will address this) #### Run Orchestration Cluster without secondary storage You can now run Orchestration Cluster in "Zeebe-only" mode without secondary storage: - Ideal for setups not using Query APIs, Operate, or Tasklist - Enable by setting `camunda.database.type=none` - Starts only the required Zeebe components ### Web Modeler SaaSSelf-ManagedWeb Modeler #### Cluster Identity compatibility Web Modeler is fully compatible with Camunda 8.8 Orchestration Clusters for a smooth migration. #### Low-code Process Testing You can now save and rerun versioned test scenarios in Web Modeler: - Supports user tasks, connectors, and basic branching logic - Ideal for process developers and CoEs - Enables behavior-driven development with Camunda Process Test #### RDBMS support for Oracle and MS SQL in Self-Managed Web Modeler Self-Managed now supports Oracle Database and Microsoft SQL Server for simpler setup and maintenance. #### Test scenario files You can now save, export, and share test scenarios: - Portable, Git-syncable files enable quick creation of Camunda Process Test scenarios #### Username claim configuration Configure the ID token claim used for usernames via the `CAMUNDA_IDENTITY_USERNAMECLAIM` environment variable to account for differences between identity providers and ensure user-friendly names. ## 8.8.0-alpha7 | Release date | Changelog(s) | Blog | | :------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------- | | 12 August 2025 | [ Camunda 8 core ](https://github.com/camunda/camunda/releases/tag/8.8.0-alpha7)[ Connectors ](https://github.com/camunda/connectors/releases/tag/8.8.0-alpha7) | [Release blog](https://camunda.com/blog/2025/08/camunda-alpha-release-august-2025/) | ### Camunda 8 API SaaSSelf-ManagedAPI Development continues on the single unified Camunda 8 REST API that consolidates multiple fragmented APIs into a single, coherent interface, simplifying development and improving clarity across Camunda components. ### Process instance tags SaaSSelf-ManagedAPI {#process-instance-tags-alpha7} Introduce optional, immutable **process instance tags** set at creation for lightweight routing, correlation, and future prioritization without inspecting large variable payloads. For a full feature overview see [process instance creation](/components/concepts/process-instance-creation.md#tags) and [job workers](/components/concepts/job-workers.md#tags). ### Camunda 8 Run supports 8.8 architecture Self-ManagedDeveloper Camunda 8 Run now includes Identity, allowing all core applications to run locally in configurations similar to production. This simplifies local development by enabling multiple user authentications and credentials. ### Connectors SaaSConnectors {#connectorsalpha7} #### AI Agent connector - **Hybrid mode/customization**: The AI Agent connector can now be [customized](../../../components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-customization.md) in hybrid mode. This includes an API to define custom memory storage backends apart from the provided ones. - **Added model provider support**: Added support for Azure OpenAI and Google Vertex AI models. To learn more, see [AI Agent connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md). #### Azure Blob Storage connector Use the new Azure Blob Storage connector to store and retrieve documents within Camunda workflows using Azure Blob Storage. This enables seamless document management directly within processes, improves efficiency, and reduces the need for custom integrations. To learn more, see [Azure Blob Storage connector](/components/connectors/out-of-the-box-connectors/azure-blob-storage.md). #### CSV connector Use the new CSV connector for SaaS to read, filter, transform, and write CSV data within processes. This reduces technical debt, accelerates development, and broadens integration capabilities with native support for this universal data format. To learn more, see [CSV connector](/components/connectors/out-of-the-box-connectors/csv.md). #### Google Cloud storage connector Use the new Google Cloud storage connector for easy document storage and retrieval directly within Camunda workflows, streamlining document management without custom development. To learn more, see [Google Cloud storage connector](/components/connectors/out-of-the-box-connectors/google-cloud-storage.md). #### MCP Client connector early access Use the new MCP Client connector to allow Camunda processes and AI agents to auto-discover and invoke external tools, eliminating hardwired connectors and enabling dynamic, metadata-driven tool integration. To learn more, see [MCP client](../../../components/connectors/out-of-the-box-connectors/agentic-ai-mcp-client.md). :::note The MCP Client connector is released as an [early access alpha feature](/components/early-access/alpha/alpha-features.md) to allow you to test and participate in development by sharing feedback before general availability, and is subject to alpha feature limitations. ::: #### Vector database connector Improvements are made to the Vector database connector as follows: - Updated the OpenSearch vector store to support non-AWS managed instances. - Added support for OpenAI embedding models. To learn more, see [vector database connector](/components/connectors/out-of-the-box-connectors/embeddings-vector-db.md). #### Fetch latest process definitions The connectors runtime is optimized by fetching only the latest process definition versions from the Orchestration Cluster. This reduces CPU consumption and improves deployment performance, particularly in environments with frequent CI/CD-generated process versions. #### Unlock element template fields [Element template management](/components/connectors/manage-connector-templates.md) is now more flexible for developers and DevOps teams. - You can assign custom semantic IDs and use an intuitive versioning scheme, ensuring templates are portable and retain stable references across different environments. - Template names and file names can be managed independently, and you can quickly import templates using copy and paste, git sync, or CI/CD pipeline. - Safeguards now notify you of ID or version conflicts to prevent accidental overwrites when publishing templates. ### Run process segment SaaSSelf-ManagedAPI This feature allows developers to manually execute and test individual tasks or segments (connectors, RPA bots, IDP extractions) without running full processes, improving debugging and development efficiency. To learn more, see [run process segment](/components/concepts/process-instance-creation.md#run-process-segment) ### Intelligent document processing (IDP) form extraction SaaSIDP You can use form-based structured document extraction to capture data from structured documents. - For example, you can use this extraction method for documents with a consistent layout, such as invoices, tax forms (for example, W-2s, VAT declarations), and loan or insurance applications. - Projects can be shared organization-wide, enhancing accessibility to extraction capabilities. To learn more, see [extract structured data](/components/hub/workspace/modeler/idp/idp-structured-extraction.md). ### Migration to Orchestration Cluster Identity support Self-ManagedSecurity Allows smooth migration from Camunda 8.7 to 8.8 by transferring tenants, roles, and authorizations to the new Orchestration Cluster Identity, minimizing manual administration effort during upgrade. ### RPA multi-file script support RPA Robotic process automation (RPA) now supports multi-file script support, allowing you to organize scripts modularly, reuse common automation components, and integrate existing Robot Framework scripts. The execution engine fully supports multi-file scripts and linked resources, improving scalability, maintainability, and flexibility for enterprise automation projects. ### User task listener metadata and filtering SaaSSelf-ManagedTasklist With this release, user task listener jobs are improved as follows: - Task metadata is now directly embedded in the task listener jobs' properties instead of being exposed as custom headers. This includes attributes such as `assignee`, `dueDate` or `userTaskKey`. - User tasks can now be filtered using partial user task states to understand the current lifecycle state of the user task fully. These improvements simplify job worker development, reduce errors, and enable better observability of the user task lifecycle. ### Unified configuration for the Orchestration Cluster Self-Managed Simplifies configuration by consolidating Operate, Tasklist, and Identity profiles into a unified Camunda 8 Orchestration Cluster application, reducing duplication and complexity for easier deployment and management. ### Zeebe-managed resilient batch operations Self-ManagedZeebe All batch operations, such as canceling or resolving incidents in bulk, are now handled by Zeebe instead of Operate. - This change ensures region failovers in the multi-region setup no longer risk losing critical batch commands. - Users will initiate and manage batch operations through the Orchestration Cluster REST API and the Operate UI, but the underlying processing occurs within Zeebe. - By moving batch operations to the core engine, multi-region deployments gain reliability and resilience. ## 8.8.0-alpha6 | Release date | Changelog(s) | Blog | | :----------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------- | | 8 July 2025 | [ Camunda 8 core ](https://github.com/camunda/camunda/releases/tag/8.8.0-alpha6)[ Connectors ](https://github.com/camunda/connectors/releases/tag/8.8.0-alpha6) | [Release blog](https://camunda.com/blog/2025/07/camunda-alpha-release-july-2025/) | ### Bitbucket sync SaaSWeb Modeler Camunda 8 now supports integration with [Atlassian Bitbucket Cloud](https://bitbucket.org/product/), in addition to GitHub, GitLab, and Azure DevOps. - This helps customers who use Jira for their development processes. - Organization owners and administrators can connect their Web Modeler process applications to Bitbucket Cloud, allowing users to keep their Web Modeler, Desktop Modeler, and official version control projects synced. To learn more, see [Git sync](/components/hub/workspace/manage-projects/git-sync.md?platform=bitbucket). ### Camunda 8 REST API renamed to Orchestration Cluster REST API SaaSSelf-ManagedAPI The Camunda 8 REST API is now called the **Orchestration Cluster REST API**. - This name better reflects its role as a unified REST API for interacting with entities in a [Camunda 8 Orchestration Cluster](/reference/glossary.md#orchestration-cluster), such as processes, tasks, and variables. - The functionality and structure of the API remain unchanged. The name change improves clarity and onboarding across Camunda documentation and resources. To learn more, see [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). ### Connectors SaaSSelf-ManagedConnectors {#connectorsalpha6} #### AI Agent connector - **Structured outputs/JSON mode**: [Configurable response formats](../../../components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-task.md#response) allow you to choose whether the connector returns plain text or JSON for downstream processing. For some models, you can define a JSON schema for returned data. - **Conversation history storage**: History can now be stored in [Camunda's document storage](../../../components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md#memory) rather than in process variables—allowing longer histories without process variable size limits. To learn more, see [AI Agent connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md). #### Intrinsic functions A new `getJson` intrinsic function accepts a document and an optional FEEL expression. It extracts and returns content from a JSON document as an object. - The optional FEEL expression parameter specifies the part that will be extracted from the JSON document content. - If not provided, the whole document is returned as a JSON object. To learn more, see [intrinsic functions](/components/connectors/advanced-topics/intrinsic-functions.md). ### Dynamic partition scaling Self-ManagedZeebe You can now add new Zeebe partitions to a running cluster. - Scaling can be performed concurrently when the cluster is running, with zero downtime. - New process instances also start on new partitions, distributing cluster load evenly across partitions. - Process instances do not migrate between partitions, so it can take time for the cluster to reach equilibrium. - New partitions do not take part in correlating messages/signals, except for message/signal start events. To learn more, see [cluster scaling](/self-managed/components/orchestration-cluster/zeebe/operations/cluster-scaling.md). :::caution This feature is not yet fully compatible with backup/restore. ::: ### Helm charts Self-Managed #### Alternative container images Camunda now provides alternative container images to the previously used Bitnami images. These images are hosted on `registry.camunda.cloud`. - From version **8.8**, these are the default supported images, offering better security and faster patch delivery. - To use them, update your Helm deployment to reference the `values-images-ee.yml` file. See the [installation guide](/self-managed/deployment/helm/install/quick-install.md) for details. #### Configurable volumes The Helm chart now supports configurable volumes. You can define `PersistentVolumeClaims` or continue using `EmptyDir` through `values.yaml`. ### Singapore region available for SaaS on Amazon Web Services SaaS A new Singapore (ap-southeast-1) region is now available for SaaS clusters on Amazon Web Services. Use this region to: - Improve overall processing speed and reduce latency if you operate in Singapore and Southeast Asian (SEA) countries. - Keep cluster data within Singapore to support your local data residency and compliance needs. To learn more about supported SaaS regions, see [regions](/components/saas/regions.md). ### Tasklist uses the Orchestration Cluster REST API SaaSSelf-ManagedTasklist Tasklist now uses the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md), replacing the soon-to-be-deprecated Tasklist V1 API. - This change improves compatibility with Camunda 8 RDBMS support and continues to work with Elasticsearch/OpenSearch. - It ensures consistent functionality, better performance, and access to new features—without breaking existing workflows. ### User task listener types SaaSSelf-ManagedTasklist New user task listener types are available: #### `creating` event This event triggers before a user task is created. | Functionality | Description | | :----------------------------- | :--------------------------------------------------------------------------------------------------------------- | | Configurable creation listener | Executes logic before a task appears to users. Task is visible only after all listener jobs finish. | | Controlled task initialization | The creation continues only after listeners succeed. Incidents are raised if logic fails, enabling safe retries. | | Operate UI insights | A “Creating” event appears in the listener tab in Operate. Incidents are flagged for troubleshooting. | | Assign user task | Assign a task programmatically during creation, useful when assignment depends on an external system. | #### `canceling` event This event triggers when a user task is canceled (e.g., by a boundary event or process termination). | Functionality | Description | | :-------------------------------- | :-------------------------------------------------------------------------------------------------------- | | Configurable cancelation listener | Executes logic when a task is canceled. Allows inspection or modification of task data before completion. | | Consistent lifecycle control | Cancelation waits for listener logic to complete. Failures can raise incidents for safe retry. | | Operate UI insights | A “Canceling” event is shown in the listener tab. Incidents are highlighted for visibility. | To learn more, see [user task listeners](/components/concepts/user-task-listeners.md). ### Documentation SaaSSelf-Managed #### Get started updates The [getting started](/guides/getting-started-example.md) documentation now includes: - Example BPMN files and Spring Boot/NodeJS starter projects. - Practical code snippets, such as payment handling. - Updated code to match recent Camunda versions. - Annotations in BPMN files to guide usage and explain results. #### Public API The new [Public API](/reference/public-api.md) documentation outlines what’s included in Camunda 8's public API, the policies around versioning, and what to expect when upgrading. - The public API is the official contract between Camunda and its users under SemVer. - No breaking changes will be made to the public API in minor or patch releases. - You can safely build on these interfaces with the expectation of stability and backward compatibility. ## 8.8.0-alpha5 | Release date | Changelog(s) | Blog | | :----------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------- | | 10 June 2025 | [ Camunda 8 core ](https://github.com/camunda/camunda/releases/tag/8.8.0-alpha5)[ Connectors ](https://github.com/camunda/connectors/releases/tag/8.8.0-alpha5) | [Release blog](https://camunda.com/blog/2025/06/camunda-alpha-release-june-2025/) | ### Agentic orchestration The following [agentic orchestration](/components/agentic-orchestration/agentic-orchestration-overview.md) features are available in this alpha release: #### AI Agent connector ConnectorsWeb ModelerDektop Modeler The AI Agent connector enables AI agents to integrate with an LLM to provide interaction/reasoning capabilities. This connector is designed for use with an [ad-hoc sub-process](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md) in a [feedback loop](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md#agent-loop), providing automated user interaction and tool selection. This connector provides: - Support for different LLM providers, such as OpenAI or Anthropic. - Conversational/short-term memory handling to enable feedback loops and follow-up tasks. - Tool resolution and orchestration through tools defined in an ad-hoc sub-process. To learn more about this connector, see [AI Agent connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md). #### Vector database connector Connectors The vector database connector allows embedding, storing, and retrieving Large Language Model (LLM) embeddings. This enables building AI-based solutions for your organizations, such as context document search, long-term LLM memory, and agentic AI interaction in combination with the AI Agent connector (RAG). To learn more about this connector, see [vector database connector](/components/connectors/out-of-the-box-connectors/embeddings-vector-db.md). ### Azure DevOps integration for Git sync Web Modeler Camunda now supports [an integration with Azure DevOps](/components/hub/workspace/manage-projects/git-sync.md) that allows for direct syncing with Azure repositories. ### FEEL Copilot Web Modeler Chat with the AI FEEL Copilot for help generating FEEL (Friendly Enough Expression Language) expressions in Web Modeler. To learn more about this feature, see [FEEL Copilot](/components/early-access/alpha/feel-copilot/feel-copilot.md). :::note The FEEL Copilot is released as an [early access alpha feature](/components/early-access/alpha/alpha-features.md) to allow you to test and participate in development by sharing feedback before general availability, and is subject to alpha feature limitations. ::: ### FEEL Playground Web Modeler Use the FEEL Playground to validate and troubleshoot your FEEL expressions when modeling process diagrams in Web Modeler. To learn more about this feature, see [FEEL Playground](/components/modeler/feel/feel-playground.md). ### Identity Identity Self-ManagedSaaS Camunda’s new Identity service enhances authentication and authorization for Self-Managed and SaaS environments: | Feature/enhancement | Description | | :-------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Self-Managed Identity management | Admins can create and manage users, groups, roles, and memberships directly in the Identity database. | | OIDC integration | Integrate external identity providers (IdP) such as Keycloak and Microsoft Entra. | | Role-based access control (RBAC) | Assign roles and group permissions on a per-resource basis for fine-grained access control. Supported resources include Authorization, Claim Mapping Rules, Messages, Batches, Applications, Tenants, Deployments, Process Definitions, Decision Definitions, and more. | | Flexible mapping | In Self-Managed environments, map users, groups, and roles to resource authorizations and tenants. Leverage OIDC token claims and application/client mappings to streamline permission assignments. | | Migration support | Simplified migration tools make it easy for existing customers to transition to the new service. | | Organizational Identity | In SaaS environments, integrate your own IdP to manage organizational users and assign resources cluster-by-cluster. | | Cluster-specific Roles and Groups | In SaaS environments, manage distinct roles, groups, and authorizations for each cluster independently. | #### Identity management for SaaS clusters SaaS [Orchestration Cluster Identity](/versioned_docs/version-8.8/components/identity/identity-introduction.md) is now available for SaaS clusters. Starting with this alpha version, you can manage groups, roles, and authorizations at the cluster level. The following known limitations apply for this alpha version release: | Known limitation | Description | | :------------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Authorizations | Before enabling authorization checks in the cluster settings, users must assign themselves to the admin role in Identity for the Orchestration Cluster.**Note:** As authorizations are disabled by default, no changes are required for initial access.Authorizations cannot be assigned to users via the UI, only to groups.Authorizations are not correctly loaded in the UI. | | Navigation, Notifications, and Logout | Links to the other Camunda components in the Orchestration Cluster web applications (Operate, Tasklist, Identity) do not currently work.SaaS notifications are not displayed in Orchestration Cluster components.Log out from Orchestration Cluster web applications is not fully functional. | | Documentation | Documentation is incomplete. | #### Identity management for Helm Chart setups Self-Managed [Orchestration Cluster Identity](/versioned_docs/version-8.8/self-managed/components/orchestration-cluster/identity/overview.md) is now available for OIDC setups in [Helm chart deployments](/self-managed/deployment/helm/install/quick-install.md). Starting with this alpha version, you can configure the Orchestration Cluster components to use the identity provider (IdP) of your choice and enable single sign-on (SSO). The following known limitations apply for this alpha version release: | Known limitation | Description | | :--------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Authorizations | Authorizations cannot be assigned to users via the UI, only to groups.Authorizations are not correctly loaded in the UI. | | Logout | Log out from Orchestration Cluster web applications is not fully functional. | | Documentation | Documentation is incomplete. | ### React to expired messages with a custom exporter Camunda now introduces a mechanism to react to expired messages. The original message is retrieved from the primary storage upon message expiration and the expiry event is enhanced with the original message. Existing functionality remains unchanged, so there is no disruption for current customers or custom exporters. With these updates, developers can subscribe to the expired messages through a custom exporter, examine the event content, and optionally re-publish or handle the message differently. By providing an enhanced event and re-publish flow, this feature strengthens reliability and transparency in business processes without requiring a major upgrade or modifying existing exporters. ### RPA worker offline installer This feature introduces an offline installer package for the Camunda [RPA](/components/rpa/overview.md) worker, allowing installation without internet connectivity. The offline installer removes reliance on external repositories or downloads, ensuring consistent, secure, and hassle-free deployment into air-gapped or restricted environments. ### Tasklist frontend application migration to use Orchestration Cluster REST API Tasklist The Tasklist frontend application is transitioning from the soon-to-be-deprecated Tasklist V1 API to the unified Orchestration Cluster REST API. - This ensures Tasklist remains fully compatible with Camunda 8’s new RDBMS support while continuing to work seamlessly with Elasticsearch and OpenSearch. - You can expect consistent functionality across different data layers, improved performance, and access to new platform features - all without losing existing capabilities or disrupting task management workflows. ## 8.8.0-alpha4 | Release date | Changelog(s) | Blog | | :----------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------- | | 13 May 2025 | [ Camunda 8 core ](https://github.com/camunda/camunda/releases/tag/8.8.0-alpha4)[ Connectors ](https://github.com/camunda/connectors/releases/tag/8.8.0-alpha4) | [Release blog](https://camunda.com/blog/2025/05/camunda-alpha-release-may-2025/) | ### Camunda Process Test H2 data layer support Self-ManagedZeebe {#h2support} Camunda Process Test now supports using the [H2 Database Engine](https://www.h2database.com/html/main.html) as the default embedded data layer. - H2 is now automatically provisioned when integrating the Camunda Process Test libraries, eliminating manual database configuration and reducing memory footprint. - H2 support streamlines the developer experience for your Spring Boot and plain Java projects. Process testing is now faster to set up, simpler to maintain, and easier to integrate with your continuous integration workflows. To learn more about Camunda Process Test, see [Camunda Process Test](/apis-tools/testing/getting-started.md). ### Connector manage and run supports multiple runtimes SaaSSelf-ManagedConsole {#connector-management} Connector manage and run in Console now supports management of multiple connector runtime instances. To learn more about this feature, see [manage your connectors](/components/hub/organization/manage-clusters/manage-connectors.md). ### Connectors SaaSSelf-ManagedConnectors {#connectorsalpha4} #### Email connector {#emailalpha4} The Email connector now exposes the `Message-ID` provided by the client in the connector response payload. This allows for improved traceability, easier correlation between sent messages and logs, and better integration with downstream systems that rely on `Message-ID`. For example: ```json { "sent": true, "subject": "Email subject" "messageId": "" } ``` :::note This change is backwards-compatible and does not require any action. You can now optionally use the `messageId` field for enhanced tracking when parsing connector responses. ::: #### Hubspot connector {#hubspotalpha4} Hubspot connector enhancements include: - The [Get contact by ID](/components/connectors/out-of-the-box-connectors/hubspot.md#get-contact-by-id) operation now supports the retrieval of properties and default contact properties. - The new [Enroll contact to workflow](/components/connectors/out-of-the-box-connectors/hubspot.md#enroll-contact-to-workflow) operation allows you to enroll contacts into a specified workflow. To learn more about this connector, see [HubSpot connector](/components/connectors/out-of-the-box-connectors/hubspot.md). ### Desktop Modeler settings Self-ManagedDesktop Modeler The new **Settings** window in Desktop Modeler allows you to configure the application and customize your modeling experience. You can select your default execution platform version, along with other options that were previously only available as flags. To learn more about these settings, see [Desktop Modeler settings](/components/modeler/desktop-modeler/settings/settings.md). #### Version descriptionSaaSSelf-ManagedWeb Modeler Use the version **Description** field to track changes alongside the version tag (for example, as a change log or Git commit message). This helps make versioning more intuitive and collaborative, keeps teams aligned, and reduces ambiguity. To learn more about versioning your diagrams, see [versions](components/hub/workspace/modeler/modeling/versions.md). ### Web Modeler cluster Basic authentication Self-ManagedWeb Modeler As well as bearer token and client credentials authentication, you can now configure Web Modeler in Self-Managed to use Basic authentication for cluster access. - To use Basic authentication, set the `CAMUNDA_MODELER_CLUSTERS_0_AUTHENTICATION` environment variable value to `BASIC`. - Web Modeler sends a username and password with every request to the cluster. To learn more about Basic authentication, see [available authentication methods](/self-managed/components/hub/configuration/properties.md#available-authentication-methods). ## 8.8.0-alpha3 | Release date | Changelog(s) | Blog | | :------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--- | | 08 April 2025 | [ Camunda 8 core ](https://github.com/camunda/camunda/releases/tag/8.8.0-alpha3)[ Connectors ](https://github.com/camunda/connectors/releases/tag/8.8.0-alpha3) | - | ### Ad-hoc sub-process activation API & completion configuration SaaSSelf-Managed {#adhocsubprocess} Agentic process orchestration enhancements include: - An optional `completionCondition` boolean expression for ad-hoc sub-processes that is evaluated every time an inner element is completed. A `cancelRemainingInstances` boolean attribute can also be configured to influence the ad-hoc sub-process behavior when the completion condition is met. - An [Activate activities within an ad-hoc sub-process](/apis-tools/orchestration-cluster-api-rest/specifications/activate-ad-hoc-sub-process-activities.api.mdx) API used to activate selected activities within an ad-hoc sub-process. To learn more about these features, see [ad-hoc sub-processes](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md). ### Advanced User Task Listeners for Updating Events SaaSSelf-ManagedTasklist {#listeners} Advanced User Task Listeners for Updating Events allow you to define listeners that trigger whenever certain task properties or variables change. - These listeners generate jobs similar to other event-based task listeners, granting direct access to task data as well as the ability to accept or roll back updates (in certain scenarios). - Operators can also view, manage, and resolve incidents caused by these listeners in Operate, ensuring a unified and transparent approach to handling task changes. To learn more about this feature, see [advanced user task listeners for updating events](/components/concepts/user-task-listeners.md). ### HubSpot connector SaaSSelf-ManagedConnectors {#hubspot} Use the new outbound HubSpot connector to connect your BPMN service with [HubSpot](https://hubspot.com/) and manage your HubsSpot contacts, companies, and deals. This connector supports the following operations: - Contacts: Get all contacts, Get contact by id, Get multiple contacts by id, Search contact, Create contact, Update contact, Delete contact. - Companies: Get all companies, Get company by id, Search company, Get all contacts of a company, Add contact to company, Remove contact from company, Create company, Delete company. - Deals: Get all deals, Get deal by id, Search deal, Delete deal. To learn more about this connector, see [HubSpot connector](/components/connectors/out-of-the-box-connectors/hubspot.md). ## 8.8.0-alpha2 | Release date | Changelog(s) | Blog | | :------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------- | | 11 March 2025 | [ Camunda 8 core ](https://github.com/camunda/camunda/releases/tag/8.8.0-alpha2)[ Connectors ](https://github.com/camunda/connectors/releases/tag/8.8.0-alpha2) | [Release blog](https://camunda.com/blog/2025/03/camunda-alpha-release-march-2025/) | ### Camunda Spring Boot Starter for the Camunda 8 REST API A Spring Boot Starter is provided for the Orchestration Cluster REST API to unify process management, user tasks, and identity features under a single dedicated starter. This simplifies the interaction between a Spring Boot application and Camunda 8, allowing you to: - Easily integrate process entity management and queries within your workflows. - Seamlessly configure endpoints and authentication via Spring Boot auto-configuration, minimizing boilerplate code. - Rely on an official, standardized approach to guarantee consistency and reduce maintenance costs when upgrading. - Based on Spring Boot 3.5 ([version compatibility matrix](/apis-tools/camunda-spring-boot-starter/getting-started.md#version-compatibility)). To learn more about this feature, see the [Camunda Spring Boot Starter](/apis-tools/camunda-spring-boot-starter/getting-started.md). ### Camunda 8 Run authentication updates Camunda 8 Run no longer requires authentication when working with APIs. Authentication and authorizations can be optionally enabled to allow requests using Basic authentication, and to test authorizations and permissions. To learn more about this feature, see the [API documentation](/self-managed/quickstart/developer-quickstart/c8run/configuration.md#use-camunda-apis) for Camunda 8 Run. ### Identity management updates SaaSSelf-Managed The [Identity service](/self-managed/components/management-identity/overview.md) is enhanced to deliver greater flexibility, control, and security for both Self-Managed and SaaS users. These updates are part of our broader effort to streamline the platform’s architecture. #### Cluster-level identity management Identity settings will be configured at the Orchestration Cluster level, allowing each cluster to have unique OIDC configurations. This cluster-specific setup empowers organizations to assign different identity providers (IdPs) across clusters, offering improved control over permissions and user group mappings, resulting in a more streamlined and efficient configuration experience. For SaaS customers, identity management in Camunda 8.8 remains consistent with Camunda 8.7, allowing the attachment of a single IdP per organization. However, cluster-level identity capabilities are provided for SaaS as well as Self-Managed. This means that user groups, roles, and access permissions can now be managed at the cluster level, giving SaaS customers the same granular access control as in Self-Managed environments. #### Decoupling from Keycloak Self-Managed Built-in Keycloak integration in Self-Managed is removed, allowing customers to use any compatible IdP. - Keycloak remains fully supported as an external option. For cluster-level identity management it must be connected as an external OIDC provider moving forward. - OpenID Connect (OIDC) remains the standard for seamless integration with chosen IdPs. #### Resource-based authorizations Resource-based authorizations control read and write permissions per specific resource. See [manage resource-based authorizations](/components/saas/organization/resource-based-auth.md). - Admin users retain full access, but regular users must be granted specific permissions to perform operations or view resources. - For organizations that build custom front-ends and access Camunda via API, users or clients with API permissions can still access data through the V2 API, respecting their resource permissions. ## 8.8.0-alpha1 | Release date | Changelog(s) | Blog | | :--------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | 11 February 2025 | [ Camunda 8 core ](https://github.com/camunda/camunda/releases/tag/8.8.0-alpha1)[ Connectors ](https://github.com/camunda/connectors/releases/tag/8.8.0-alpha1) | [Release blog](https://camunda.com/blog/2025/02/camunda-alpha-release-february-2025/) | :::note Some features available in 8.8.0-alpha1 were [originally released in 8.7.0 alphas](https://camunda.com/blog/2025/01/camunda-87-88-release-update/). ::: ### User task listeners SaaSSelf-ManagedModelerOperate Task lifecycle management is enhanced with user task listeners, allowing users to react to specific user task lifecycle events. - Process designers can now model task listeners for different events, such as `assigning` and `completing`. - Developers can use the same job infrastructure to activate and complete task listener jobs. - Operations engineers can easily check details of active and completed task listeners within instances, and efficiently resolve task listener incidents. This enhancement streamlines operations and ensures smoother incident handling, improving time to unblock process execution. To learn more about this feature, see [user task listeners](/components/concepts/user-task-listeners.md). ### Orchestration Cluster REST API Query API API You can now use a single Query API in the Orchestration Cluster REST API to find process and decision data instead of using multiple component APIs. For example, send a request to the [Search decision definitions](/apis-tools/orchestration-cluster-api-rest/specifications/search-decision-definitions.api.mdx) endpoint to search for decision definitions. New Query API endpoints are added as follows: - Decision definitions - Decision instances - Decision requirements - Flownode instances - Incidents - Process definitions - Process instances - User tasks - Variables To learn more about these endpoints, see the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md#query-api) documentation. ### Amazon OpenSearch Optimize support Self-ManagedOptimize Camunda 8 Self-Managed now fully supports the use of Amazon OpenSearch with Optimize. ### Process instance migration SaaSSelf-ManagedZeebe Enhanced process instance migration allows you to solve problems with process definitions and use the latest process improvements. You can now migrate the following: - Compensation boundary event subscriptions - Escalation boundary events - Escalation event subprocesses To learn more about migration, see [process instance migration](/components/concepts/process-instance-migration.md). ### Camunda Exporter Self-Managed A new Camunda Exporter brings the importer and archiving logic of web components (Tasklist and Operate) closer to the distributed platform (Zeebe). The index schema is also being harmonized. To learn more about this feature, see the [Camunda Exporter documentation](/self-managed/components/orchestration-cluster/zeebe/exporters/camunda-exporter.md). ### Backup and restore improvements Self-Managed Camunda backups have been improved and made easier to use. The web application backups are now merged together under one endpoint. ### Connector Runtime SaaSSelf-ManagedConnectors #### Spring SDK and Orchestration Cluster REST API Migration The Connectors experience is enhanced with the migration from the Spring Zeebe to the Orchestration Cluster REST API, and the removal of dependency on the Operate client. --- ## What's new in Camunda 8.8 Important changes in Camunda 8.8 you should consider when upgrading from Camunda 8.7. ## Why upgrade to Camunda 8.8? Camunda 8.8 introduces important architectural changes and enhancements to simplify deployment, improve maintainability, and empower both operations teams and developers. Upgrading to Camunda 8.8 delivers significant benefits: - **Agentic orchestration**: [Build and orchestrate AI agents](#agentic-orchestration) within your BPMN-based workflows, enabling human tasks, deterministic rule sets, and AI-driven decisions to collaborate in a robust, end-to-end process. - **Unified platform**: Camunda 8.8 combines core components into a single [Orchestration Cluster](#orchestration-cluster), reducing system complexity, centralizing operations, and simplifying both deployment and maintenance. - **Enhanced productivity**: This upgrade introduces streamlined [identity and access management](#identity), a consolidated configuration model, and modernized & consolidated [APIs and SDKs](#apis-and-tools), making development, integration, and permission handling faster and more intuitive. - **Increased efficiency**: The new [unified exporter architecture](#unified-exporter) improves performance with accelerated data visibility in Operate and Tasklist, as well as public query APIs. It also enables easier operation and administration, and improves resilience when deploying across multiple data centers. :::info To learn more about the benefits of upgrading to Camunda 8.8, see the blog posts [streamlined deployment with Camunda 8.8](https://camunda.com/blog/2025/03/streamlined-deployment-with-camunda-8-8/) and [introducing enhanced Identity Management in Camunda 8.8](https://camunda.com/blog/2025/03/introducing-enhanced-identity-management-in-camunda-88/). ::: ## Summary of important changes Important changes introduced in Camunda 8.8 are summarized as follows: **What's new/changed** **Summary** [Agentic orchestration](#agentic-orchestration) Improved features and new connectors for building and orchestrating AI agents within your BPMN-based workflows. [Orchestration Cluster](#orchestration-cluster) The Orchestration Cluster (previously automation cluster) is now the core Camunda 8 component. [Identity, authentication, and authorization](#identity) Identity management is now split into two scopes for improved access management, performance, and flexible integration with any OIDC-compatible Identity Provider:**Identity**: Manages authentication and fine-grained authorizations for the Orchestration Cluster and its APIs. **Management Identity**: Controls access for Web Modeler, Console and Optimize. [APIs & tools](#apis-and-tools) New and changed APIs & tools are introduced in Camunda 8.8. :::info - See [release announcements](/reference/announcements-release-notes/880/880-announcements.md) and [release notes](/reference/announcements-release-notes/880/880-release-notes.md) for more detail on what's included in Camunda 8.8. - Ready to upgrade? See the [upgrade guides](#upgrade-guides) to learn more about upgrading from Camunda 8.7 to 8.8. ::: ## Agentic orchestration Camunda agentic orchestration allows you to build and orchestrate AI agents within your BPMN-based workflows, enabling human tasks, deterministic rule sets, and AI-driven decisions to collaborate in a robust, end-to-end process. Agentic orchestration Use the following new features to build and integrate AI agents into your processes: **Feature** **Description** [AI agent connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md) Enables AI agents to integrate with an LLM to provide interaction/reasoning capabilities. This connector is designed for use with an ad-hoc sub-process in a feedback loop, providing automated user interaction and tool selection. [MCP Client connector](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-client.md) Connect an AI agent connector to tools exposed by [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers. [Ad-hoc tools schema resolver connector](/components/connectors/out-of-the-box-connectors/agentic-ai-ahsp-tools-schema-resolver.md) Can be used independently with other AI connectors for direct LLM interaction. Use this connector if you don’t want to use the AI agent connector but still want to resolve tools for an ad-hoc sub-process or debug tool definitions. [Vector database connector](/components/connectors/out-of-the-box-connectors/embeddings-vector-db.md) Allows embedding, storing, and retrieving LLM embeddings. Use this connector to build AI-based solutions such as context document search, long-term memory for LLMs, and agentic AI interaction. :::info To learn more about agentic orchestration, see the blog post [Enterprise-Grade Agentic Automation Is Here](https://camunda.com/blog/2025/10/enterprise-grade-agentic-automation-is-here/). ::: ## Orchestration Cluster {#orchestration-cluster} The primary architectural change is the consolidation of the core Zeebe, Operate, Tasklist, and Identity components into the Orchestration Cluster (a single unified deployable package). This impacts how Camunda 8 is deployed, managed, and scaled. The Orchestration Cluster (previously automation cluster) is now the core component of Camunda 8. :::note simple deployment The simplest Camunda 8.8 Self-Managed deployment runs as a single Java application or docker container. ::: ### Zeebe, Operate, Tasklist, and Identity In Camunda 8.8, Zeebe, Operate, Tasklist, and Identity are integrated into the Orchestration Cluster application as a single deployable artifact, distributed as a JAR file or Docker container. - [Zeebe](/reference/glossary.md#zeebe) is the [workflow engine](/reference/glossary.md#workflow-engine). - Operate is used for monitoring and troubleshooting [process instances](/reference/glossary.md#process-instance) running in Zeebe. - Tasklist is used for interacting with [user tasks](/reference/glossary.md#user-task) (assigning, completing, and so on). - [Identity](/reference/glossary.md#admin) is used for managing the integrated Orchestration Cluster authentication and authorization. In Camunda 8.7 and earlier, each component (Zeebe, Operate, Tasklist, and Identity) was deployed independently. Orchestration Cluster ### Unified Orchestration Cluster REST API {#orchestration-cluster-api} Camunda 8.8 introduces a single unified Orchestration Cluster REST API you can use to interact programmatically with the Orchestration Cluster. - This replaces component APIs (Operate API, Tasklist API, Zeebe API, and much of Identity API) with a single set of endpoints. - This unified API supports both organizational (SaaS) and Self-Managed deployments. - This is now the default and recommended integration point for developers, operators, and automation solutions. Orchestration Cluster REST API ### Unified Exporter Camunda 8.8 introduces a new unified Camunda Exporter that directly populates data records that are consumable by read APIs on the secondary storage. This significantly reduces latency until eventually consistent data becomes available on Get and Search APIs. In Camunda 8.7 and earlier, dedicated importers/exporters were used for data flows between components (such as Elasticsearch import/export). Camunda Exporter :::info Learn more about the unified Camunda Exporter in the blog post [One Exporter to Rule Them All: Exploring Camunda Exporter](https://camunda.com/blog/2025/02/one-exporter-to-rule-them-all-exploring-camunda-exporter/). ::: ### Unified component configuration Camunda 8.8 introduces unified configuration for Orchestration Cluster components where you can define all essential cluster and component behavior through a single, centralized configuration system. In Camunda 8.7 and earlier, managing and configuring core components (Zeebe, Operate, Tasklist, Identity) was done separately. Core settings and features :::note Only the first partial set of unified configuration properties is introduced in Camunda 8.8. - All remaining unified property changes will be completed by Camunda 8.9. - This remaining work will result in future breaking changes. For example, the secondary database properties will be unified into a secondary-storage properties section. ::: ## Identity, authentication, and authorization {#identity} The Orchestration Cluster Identity component UI handles authentication and authorization for the Orchestration Cluster components and its resources. :::note With this 8.8 change, the source of truth for Identity and Access Management for the Orchestration Cluster (including Zeebe, Operate, Tasklist, and its APIs) is now the Orchestration Cluster itself. This removes the reliance on the separate [Management Identity](/self-managed/components/management-identity/overview.md) (formerly "Identity") component. ::: ### Identity and Management Identity In Camunda 8.8, Orchestration Cluster [Identity](/versioned_docs/version-8.8/components/identity/identity-introduction.md) and [Management Identity](/self-managed/components/management-identity/overview.md) are two separate components used for Identity management, each with distinct areas of responsibility. Identity Management Identity Access and permission management for all Orchestration Cluster components: Zeebe, Operate, Tasklist, and the Orchestration Cluster REST and gRPC API. Unified access management: Authentication and authorizations are handled directly by the Orchestration Cluster across all components and APIs, eliminating any orchestration dependency on Management Identity. Authentication: Supports three authentication modes: No Authentication: No authentication required for API access. Form-based authentication in the UI. Users and groups are managed in Identity. Basic Authentication: Basic authentication for API access. Form-based authentication in the UI. Users and groups are managed in Identity. OIDC: OpenID Connect with any compatible Identity Provider (for example, Keycloak, Microsoft EntraID, Okta). Authorizations per resource: [Authorizations](/components/concepts/access-control/authorizations.md) provide fine-grained access control to Orchestration Cluster resources such as process instances, tasks, and decisions, applied consistently across components and APIs Decoupling from Keycloak: Keycloak is treated as a standard external Identity Provider integrated via OIDC, making it easier to use other providers without special integration. Tenant-Management: Tenants are directly managed within the Orchestration Cluster, allowing for Tenants per Cluster. Continues to manage access for platform components such as Web Modeler, Console, and Optimize. Remains responsible for managing access to platform components such as Web Modeler, Console, and Optimize. Authentication: Supports two authentication modes: Direct Keycloak integration: (default). OIDC: OpenID Connect with any compatible Identity Provider (for example, Keycloak, Microsoft EntraID, Okta). Tenant Management: No longer manages Tenants for the Orchestration Cluster components. Tenants only apply to Optimize. Resource Authorizations: No longer manages resource authorizations for Orchestration Cluster components. ### Tenant interceptors Tenant interceptors are **not supported in Camunda 8.8**. If you are using tenant interceptors in Camunda 8.7, you must migrate to the new Orchestration Cluster Identity [tenant management](/versioned_docs/version-8.8/components/identity/tenant.md). Administrators must: - Migrate tenants into the cluster using either the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/specifications/create-tenant.api.mdx) or the [Orchestration Identity UI](/versioned_docs/version-8.8/components/identity/tenant.md#create-a-tenant). - Assign tenants by: - Direct assignment to [users](/versioned_docs/version-8.8/components/identity/tenant.md#assign-users-to-a-tenant) - Direct assignment to [clients](/versioned_docs/version-8.8/components/identity/tenant.md#assign-clients-to-a-tenant) - Using [mapping rules](/versioned_docs/version-8.8/components/identity/tenant.md#assign-mapping-rules-to-a-tenant) ### Are you affected by 8.8 Identity changes? The 8.8 changes to Identity could affect different user roles in your Organization. For example: | Role | Responsibilities | | :------------------- | :------------------------------------------------------------------------------------------------------------------------------------- | | Administrators | Understand the new Identity setup and migration steps from Camunda 8.7. | | Developers | Learn the new [authorization concepts](/components/concepts/access-control/authorizations.md) and required permissions for API access. | | Information Security | Review and adapt to the new Identity architecture. | ### Benefits Orchestration Cluster Identity provides several advantages: - **Simplified identity management**: The split between Orchestration Cluster Identity and Management Identity provides a clearer separation of concerns, making it easier to manage access for Orchestration Cluster components versus Web Modeler and Console. - **Unified access management**: All identity and authorization features for the Orchestration Cluster components are now contained within the Orchestration Cluster, eliminating any dependency on Management Identity when orchestrating processes. - **Simplified deployment and improved availability**: Removing the dependency on Management Identity streamlines deployment and increases availability by reducing potential points of failure. - **Consistent authorization model**: The new model offers a unified approach to managing permissions across all Orchestration Cluster resources. - **Enhanced security**: Granular access controls improve the overall security posture for cluster resources. - **Improved performance**: Authorization checks are handled internally, reducing latency and improving performance. - **Flexible Identity Provider integration**: Keycloak is now treated as a standard OIDC provider, making it easier to integrate with other Identity Provider and increasing flexibility for users. ### Impact of Identity changes on your 8.7 deployment The impact of these changes and what action you need to take depends on your deployment type. For example: | Area | Impact | | :-------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Authorization | The new [authorization](/components/concepts/access-control/authorizations.md) model introduces fine-grained access control for Orchestration Cluster resources, replacing the previous model. | | Roles, tenants, and mapping rules | Now managed within Orchestration Cluster Identity, replacing the previous Management Identity setup. | | User Task authorizations | [User Task access restrictions](/versioned_docs/version-8.9/components/tasklist/user-task-access-restrictions.md) only apply to the Tasklist v1 API. After switching to the v2 API with Tasklist, user task access restrictions do not apply. | Each deployment type has a clear upgrade path and migration guidance to help administrators transition from Camunda 8.7 to Camunda 8.8. ### Camunda 8 SaaS Resource authorizations, groups, and roles formerly managed via Console are replaced by authorizations, groups, and roles managed within the cluster-specific Identity. - These are automatically migrated during the Camunda 8.8 upgrade to preserve your existing Access Management configuration at the time of the update. - After upgrading a cluster to 8.8, changes to resource authorizations and roles made in Console no longer affect the 8.8 cluster. - Users and clients are created and managed in Console, with their authorizations managed via the Orchestration Cluster. - Console cluster settings in 8.8 allow you to toggle Orchestration Cluster authorizations. Authorizations are enabled by default for any migrated cluster. The automated migration ensures that your users and clients can access the UIs and APIs as before. The following table summarizes where Identity entities are managed in Camunda 8.8 SaaS: | Entity type | Managed via | | :------------- | :----------------------------- | | Users | Console | | Clients | Console | | Roles | Orchestration Cluster Identity | | Groups | Orchestration Cluster Identity | | Authorizations | Orchestration Cluster Identity | | Tenants | n/a (planned for future) | | Mapping rules | n/a (managed by Camunda) | ### Camunda 8 Self-Managed After you deploy all Camunda 8 components in a Self-Managed environment, you will continue to use Management Identity for Web Modeler, Console, and Optimize, but use Orchestration Cluster Identity for Zeebe, Operate, Tasklist, and the Orchestration Cluster REST API. - Roles and permissions for Orchestration Cluster components (previously managed in Management Identity), are now replaced by the new authorizations and roles defined within Orchestration Cluster Identity. - The Identity Migration App that migrates these entities from Management Identity into Orchestration Cluster Identity must be run during your Camunda 8.7 to 8.8 upgrade. Instructions on enabling and configuring the Identity Migration App in the 8.7 to 8.8 migration guide are available for Helm and also docker-compose/bare Java deployments. - Authorization checks are enabled by default for any migrated cluster using the Helm chart. The automated migration ensures that your users and clients can access the UIs and APIs like before. - Management Identity, Keycloak and Postgres are no longer needed for an Orchestration Cluster. They are only needed when using Web Modeler, Console or Optimize. - For the Orchestration Cluster, you can bring your own Identity Provider (for example, Keycloak, Microsoft EntraID, Okta) or use the built-in Basic authentication method. - A special setup is no longer required for Keycloak as it is now integrated like any other Identity Provider via OpenID Connect (OIDC). Management Identity relies by default on Keycloak, but you can also configure it to use any OIDC-compatible Identity Provider. - For OIDC-based Self-Managed deployments, you can reuse groups from your identity provider for authorization, role, and tenant assignment. See [bring your own groups](../../../self-managed/components/orchestration-cluster/admin/bring-your-groups.md). The following table summarizes where Orchestration Cluster Identity entities are managed in Camunda 8.8 Self-Managed: | Entity type | Managed via | | :------------- | :----------------------------- | | Users | Identity Provider | | Clients | Identity Provider | | Roles | Orchestration Cluster Identity | | Groups | Identity Provider | | Authorizations | Orchestration Cluster Identity | | Tenants | Orchestration Cluster Identity | | Mapping Rules | Orchestration Cluster Identity | #### Camunda 8 Self-Managed - Basic Authentication If you are using built-in user management (Basic authentication), Tasklist and Operate specific built-in user management (using Elasticsearch/OpenSearch secondary storage) is no longer supported. - Administrators must migrate their users manually into the Orchestration Cluster. - You must ensure that **usernames are identical**, otherwise users will not be able to see their assigned tasks. In a Basic authentication setup, the Orchestration Cluster provides full functionality: | Entity type | Managed via | | :------------- | :-------------------------------------------- | | Users | Orchestration Cluster Identity | | Clients | n/a (not applicable for Basic authentication) | | Roles | Orchestration Cluster Identity | | Groups | Orchestration Cluster Identity | | Authorizations | Orchestration Cluster Identity | | Tenants | Orchestration Cluster Identity | | Mapping Rules | n/a (not applicable for Basic authentication) | ## APIs & tools {#apis-and-tools} Changes to [APIs & tools](/apis-tools/working-with-apis-tools.md) in 8.8 are summarized as follows: **What's new/changed** **Description** [Orchestration Cluster REST API](#orchestration-cluster) The unified Orchestration Cluster REST API replaces the deprecated V1 component APIs, providing a unified interface for managing and interacting with the Orchestration Cluster. [Camunda Java Client](apis-tools/java-client/getting-started.md) The Camunda Java Client is now the official Java library for connecting to Camunda 8 clusters, automating processes, and implementing job workers. It is designed for Java developers who want to interact programmatically with Camunda 8 via REST or gRPC, and is the successor to the Zeebe Java client. [Camunda Spring Boot Starter](/apis-tools/camunda-spring-boot-starter/getting-started.md) The Camunda Spring Boot Starter replaces the Spring Zeebe SDK. The SDK relies on the Camunda Java client, designed to enhance the user experience and introduce new features while maintaining compatibility with existing codebases. [Camunda Process Test](/apis-tools/testing/getting-started.md) Camunda Process Test (CPT) is a Java library to test your BPMN processes and your process application. CPT is the successor of Zeebe Process Test. Our previous testing library is deprecated and will be removed with version 8.10. [Camunda user tasks](/apis-tools/migration-manuals/migrate-to-camunda-user-tasks.md) Camunda user tasks replace the deprecated job-based user tasks in Camunda 8.8, providing a more robust and flexible way to handle user tasks within process models. [Tasklist GraphQL API](/reference/announcements-release-notes/880/880-announcements.md#deprecated-operate-and-tasklist-v1-rest-apis) The previously deprecated Tasklist GraphQL API is removed in Camunda 8.8. This change is part of the broader architectural evolution towards the Orchestration Cluster REST API, which provides a more unified and consistent interface for managing tasks and workflows. [Zeebe gRPC API endpoints](/reference/announcements-release-notes/880/880-announcements.md#deprecated-zeebe-grpc-api-endpoints) With the 8.8 release, the gRPC API continues but will be disabled by default starting with the 8.10 release. :::info To learn more about upgrading and migrating to 8.8, see the [API & tools upgrade guide](/versioned_docs/version-8.8/apis-tools/migration-manuals/index.md). ::: ## Upgrade guides {#upgrade-guides} Camunda 8.8 lays the foundation for future releases. Upgrading ensures compatibility and access to improved features. The following guides provide detailed information on how you can upgrade to Camunda 8.8. **Guide** **Description** **Who is this guide for?** [Self-Managed upgrade guide](/versioned_docs/version-8.8/self-managed/upgrade/index.md) Evaluate your infrastructure, understand operational changes, and choose the best update strategy for your environment. Operations and platform administrators of Self-Managed installations. [APIs & tools upgrade guide](/versioned_docs/version-8.8/apis-tools/migration-manuals/index.md) Plan and execute an upgrade from Camunda 8.7 to 8.8, focusing on API and tools transitions. Application developers maintaining Camunda-based solutions in Self-Managed Kubernetes or VM environments.Developers using Camunda APIs and tools. ## Migration from Camunda 7 to Camunda 8 Camunda 8.8 includes new tools and enhancements to help you migrate from Camunda 7 to Camunda 8. **What's new** **Description** [Data Migrator](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/index.md) Use the Data Migrator to copy running process instances from Camunda 7 to Camunda 8. [Diagram Converter](/guides/migrating-from-camunda-7/migration-tooling/diagram-converter.md) Analyzes Camunda 7 model files (BPMN or DMN) and generates a list of tasks required for the migration. It can also automatically convert these files from Camunda 7 format to Camunda 8 format. [Code Conversion](/guides/migrating-from-camunda-7/migration-tooling/code-conversion.md) Code conversion utilities provide code mapping tables, conversion patterns, and automatable refactoring recipes to systematically translate Camunda 7 implementation patterns to Camunda 8 equivalents. :::tip Start your migration today with the [Camunda 7 to Camunda 8 migration guide](/guides/migrating-from-camunda-7/index.md). ::: --- ## 8.9 Release announcements | Minor release date | End of standard maintenance | Release notes | Upgrade guides | | ------------------ | --------------------------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- | | 14 April 2026 | 12 October 2027 | [8.9 release notes](/reference/announcements-release-notes/890/890-release-notes.md) | [8.9 upgrade guides](/reference/announcements-release-notes/890/whats-new-in-89.md#upgrade-guides) | :::info 8.9 resources - See [release notes](/reference/announcements-release-notes/890/890-release-notes.md) to learn more about new features and enhancements. - Refer to the [quality board](https://github.com/orgs/camunda/projects/187/views/21) for an overview of known bugs by component and severity. ::: ## Supported environments Change #### Elasticsearch minimum version raised to 8.19+ The minimum supported Elasticsearch version for the Orchestration cluster and Optimize is now 8.19 (previously 8.16+). - This aligns with the Elasticsearch 8 versions maintained by Elastic as of April 2025. - The default Elasticsearch version used by Docker Compose examples and Helm templates has been updated to `8.19.9+` accordingly. - Upgrade your Elasticsearch clusters before moving to Camunda 8.9 to avoid compatibility issues. - For best results, Camunda recommends upgrading to the latest supported Elasticsearch 9.2+ to take advantage of new features and improvements. [OpenSearch and Elasticsearch support](/reference/supported-environments.md#opensearch-and-elasticsearch-support) Change #### Keycloak 25.x no longer supported Camunda 8.9 drops support for Keycloak 25.x. Only Keycloak 26.x is now supported for Management Identity. - Keycloak 25.x has reached end of life and is no longer maintained by the Keycloak project. - This aligns with [Keycloak's updated release strategy](https://www.keycloak.org/2024/10/release-updates), which moved to quarterly minor releases. - Upgrade your Keycloak instance to 26.x before moving to Camunda 8.9. [Supported environments](/reference/supported-environments.md) Change #### OpenSearch minimum version raised to 2.19+ The minimum supported OpenSearch version for the Orchestration cluster and Optimize is now 2.19+ (previously 2.17+). - This aligns with the OpenSearch versions maintained as of April 2025. - Upgrade your OpenSearch clusters before moving to Camunda 8.9 to avoid compatibility issues. - For best results, Camunda recommends upgrading to the latest supported OpenSearch 3.4+ to take advantage of new features and improvements. [Supported environments](/reference/supported-environments.md) New #### New AWS regions Camunda 8.9 adds support for the following new regions in Camunda 8 SaaS. - Paris, Europe (eu-west-3) - North America, Ohio (us-east-2) [Supported AWS regions](/components/saas/regions.md#amazon-web-services-aws-regions) New #### OpenJDK 25 support Camunda 8.9 adds certification for OpenJDK 25 across the Orchestration Cluster, Connectors, Optimize, and supporting tooling. You can now run Self-Managed deployments on OpenJDK 21–25 without additional configuration changes. New #### Support for latest Elasticsearch and OpenSearch versions Camunda 8.9 now supports Elasticsearch 9.2+ and OpenSearch 3.4+, allowing you to take advantage of the latest database features and releases. [OpenSearch and Elasticsearch support](/reference/supported-environments.md#opensearch-and-elasticsearch-support) ## Key changes ### 8.9.x patch releases The following key changes were also released as part of an 8.9.x patch release or a Camunda 8 SaaS generation update. | Patch release | Artifact | Type | Key change | | :------------------------------------------------------------------ | :--------- | :-------------- | :---------------------------------------------------------------------------------------------------------------------------- | | [8.9.15](https://github.com/camunda/camunda/releases/tag/8.9.15) | Core | Regression | [Nested input mappings can silently drop sibling fields](#nested-input-mapping-sibling-fields) | | [8.9.15](https://github.com/camunda/camunda/releases/tag/8.9.15) | Core | Regression | [Chained input mappings can silently drop FEEL temporal value types](#chained-input-mapping-temporal-type-loss) | | `8.9 gen13` | SaaS | Change | [Microsoft Teams notifications require app integrations extensions](#teams-notifications-require-app-integrations-extensions) | | [8.9.13](https://github.com/camunda/connectors/releases/tag/8.9.13) | Connectors | Breaking change | [Intrinsic function allow-list](#connector-intrinsic-function-allow-list) | | [8.9.10](https://github.com/camunda/connectors/releases/tag/8.9.10) | Connectors | Breaking change | [Connector secret filter now defaults to STRICT](#connector-secret-filter-strict-default) | | [8.9.10](https://github.com/camunda/camunda/releases/tag/8.9.10) | Core | Regression | [Tasklist V1: candidate group task visibility](#tasklist-v1-candidate-group-task-visibility) | | [8.9.1](https://github.com/camunda/camunda/releases/tag/8.9.1) | Core | Regression | [Multi-instance sub-process output mapping variable scope regression](#multi-instance-output-mapping-regression) | | [8.9.1](https://github.com/camunda/camunda/releases/tag/8.9.1) | Core | Regression | [Output mapping behavior change for object variables](#output-mapping-behavior-change) | | [8.9.1](https://github.com/camunda/camunda/releases/tag/8.9.1) | Core | Breaking change | [`getMessageKeys()` removed from the exporter record](#getmessagekeys-removed-from-the-exporter-record) | | [8.9.1](https://github.com/camunda/camunda/releases/tag/8.9.1) | Core | Change | [Message TTL cleanup batch size pacing change](#message-ttl-cleanup-batch-size-pacing-change) | ## Agentic orchestration Breaking change #### MCP Client and MCP Remote Client connectors [Camunda 8.9.0-alpha2](/reference/announcements-release-notes/890/890-release-notes.md#890-alpha2) introduces breaking changes to the [MCP Client](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-client.md) element templates and runtime configuration. **Action:** To remain compatible, you should update your MCP Client and MCP Remote Client connectors to use element template version `1`. ## APIs & tools :::info 8.9 APIs & Tools migration guide Migrate your API integrations, SDKs, and generated clients to Camunda 8.9 using the [8.9 APIs & Tools migration guide](/versioned_docs/version-8.9/apis-tools/migration-manuals/migrate-to-89.md). ::: :::tip Client and API compatibility Camunda clients (Java client, Spring SDK, Node.js SDK) and Camunda Process Test are **forward-compatible** with the Orchestration Cluster, meaning you can upgrade the cluster and clients independently. For example, you can run a client on 8.8 against a cluster on 8.9, see [Client and API compatibility](/reference/public-api.md#client-and-api-compatibility). ::: Breaking change #### `getMessageKeys()` removed from the exporter record {#getmessagekeys-removed-from-the-exporter-record} [Camunda 8.9.1](/reference/announcements-release-notes/890/890-release-notes.md) unintentionally removed the `getMessageKeys()` method (and the underlying `messageKeys` field) from the public `MessageBatchRecordValue` exporter record. Custom exporters that call `getMessageKeys()` on message batch records fail to compile against, or throw a `NoSuchMethodError` at runtime with, the updated `zeebe-protocol` dependency after upgrading to 8.9.1 or any later 8.9.x patch. The built-in Elasticsearch, OpenSearch, and RDBMS exporters are unaffected. A fix that restores the method (now deprecated, returning an empty list for records produced by newer versions) is tracked in [camunda/camunda#54823](https://github.com/camunda/camunda/issues/54823) and is available in 8.9.9. **Action:** If you maintain a custom exporter that reads message batch records, avoid calling `getMessageKeys()` until you upgrade to a patch that includes the fix. Breaking change #### `GET /decision-instances/{decisionEvaluationInstanceKey}` now validates the key format The [Get decision instance](/apis-tools/orchestration-cluster-api-rest/specifications/get-decision-instance.api.mdx) endpoint previously returned `404 Not Found` when the `decisionEvaluationInstanceKey` path parameter contained invalid characters that did not match the required pattern `^[0-9]+-[0-9]+$`. The endpoint now correctly returns `400 Bad Request` in this case, while `404 Not Found` is reserved for well-formed keys that do not exist. **Action:** Update any client code or error handling that relied on receiving `404 Not Found` for malformed keys to also handle `400 Bad Request`. Breaking change #### Bug fix: `FormResult.schema` type corrected from object to string The `schema` property in the `FormResult` response was incorrectly specified as `type: object` in the OpenAPI contract, but the server has always returned it as a JSON `string`. This specification bug is now fixed. This is a bug fix that aligns the OpenAPI contract with actual server behavior, improving correctness for typed client integrations. Applications already handling the runtime `string` value are unaffected. The Camunda Java client is also affected: `io.camunda.client.api.search.response.Form::getSchema()` now returns `String` instead of `Object`. If your Java code casts or processes the return value as `Object`, update it to use `String`. **Action:** Take the following action for your integration type: | Integration type | Action required | | :--------------------- | :-------------------------------------------------------------------------------------------------------------------- | | Official SDK users | Update to the latest SDK version. | | Java client users | Update calls to `Form::getSchema()` to handle the `String` return type instead of `Object`. | | Generated-client users | Regenerate your client. If your generated code relied on the incorrect `object` typing, update it to handle `string`. | | Custom integrations | No change needed if you were already handling the actual `string` response. | Breaking change #### Document API response schemas now have explicit required and nullable annotations Starting with 8.9.0, the OpenAPI specification uses distinct schemas for document request and response payloads, and adds explicit `required` / `nullable` annotations to document response types. Previously, a shared `DocumentMetadata` schema was used for both creating and reading documents. Because response fields like `customProperties` are always populated by the server, but optional in requests, a single schema could not accurately express both contracts. This caused incorrect required/optional behavior in generated clients. | Schema | What changed | | :-------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `DocumentMetadata` | Now request-only. `customProperties` is no longer marked as required. | | `DocumentMetadataResponse` (new) | New response schema. `fileName`, `expiresAt`, `size`, `contentType`, `customProperties`, `processDefinitionId`, and `processInstanceKey` are all required. `expiresAt`, `processDefinitionId`, and `processInstanceKey` are nullable. | | `DocumentReference` | `metadata` now references `DocumentMetadataResponse`. Fields `camunda.document.type`, `storeId`, `documentId`, `contentHash`, and `metadata` are now explicitly required. `contentHash` is nullable. | | `DocumentLink` | `url` and `expiresAt` are now explicitly required. | | `candidateGroups` (user task / job API) | Now explicitly marked as a required field in response schemas (`UserTaskResult`, `UserTaskProperties`). | **Action:** Take the following action for your integration type: | Integration type | Action required | | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Official SDK users | Update to the latest SDK version. | | Generated-client users | Regenerate your client.Update any code that references `DocumentMetadata` in response handling as it is now `DocumentMetadataResponse`.Review nullable annotations on `DocumentReference.contentHash` and `DocumentMetadataResponse` fields. | | Custom integrations | No request-side changes needed. Response fields listed above are now guaranteed to be present (though some may be `null`). | [8.9 API migration guide](/versioned_docs/version-8.9/apis-tools/migration-manuals/migrate-to-89.md#request-response-schema-split) Breaking change #### Camunda Spring Boot Starter default now requires Spring Boot 4.0.x Starting with 8.9.0-alpha3, the default [Camunda Spring Boot Starter](../../../apis-tools/camunda-spring-boot-starter/getting-started.md) (`camunda-spring-boot-starter`) is bundled with Spring Boot 4.0.x. **Action:** Migrate your application to Spring Boot 4.0.x and continue using `camunda-spring-boot-starter`. If you're not yet ready to upgrade, switch to `camunda-spring-boot-3-starter`, which is bundled with Spring Boot 3.5.x and has no announced end date. See [dedicated Spring Boot 3 and 4 modules](/apis-tools/camunda-spring-boot-starter/getting-started.md#dedicated-spring-boot-3-and-4-modules). :::info Spring Boot support timeline OSS support for Spring Boot 3.x ends in June 2026. This is a Spring framework lifecycle change. Camunda will continue to maintain `camunda-spring-boot-3-starter` beyond that date. See the [Spring Boot support timeline](https://spring.io/projects/spring-boot#support) for context on Spring's support lifecycle. ::: Breaking change #### OpenAPI enum extensions for new 8.9 capabilities Starting with 8.9.0, new enum literals were added to support expanded functionality. This improves feature coverage, but typed or exhaustive enum-handling code paths may require updates to preserve full completeness checks. Added literals include the following: | Enum | New literal | | :------------------------------------------------------------ | :------------------------- | | `BatchOperationTypeEnum` / `BatchOperationTypeFilterProperty` | `DELETE_DECISION_INSTANCE` | | `ResourceTypeEnum` | `USER_TASK` | | `PermissionTypeEnum` | `COMPLETE` | **Action:** Take the following action for your integration type: | Integration type | Action required | | :--------------------- | :------------------------------------------------------------------------------------------------------- | | Official SDK users | Update to the latest SDK version. | | Generated-client users | Regenerate and add fallback/default handling for enum parsing and matching. | | Custom integrations | Review enum branches (for example, exhaustive `switch`/pattern matches) and add handling for new values. | [8.9 API migration guide](/versioned_docs/version-8.9/apis-tools/migration-manuals/migrate-to-89.md#enum-extensions) Breaking change #### OpenAPI type-safety enhancements for request and schema types Starting with 8.9.0, parts of the OpenAPI contract use stronger domain types and one schema rename to improve semantic correctness in client applications. This increases compile-time safety and helps prevent semantic substitution errors in typed integrations. Affected contract updates include the following: - `CreateDeploymentData.body.tenantId`: `string` → `TenantId` - `CreateDocumentData.query.documentId`: `string` → `DocumentId` - `SearchCorrelatedMessageSubscriptionsData.body.filter.processDefinitionKey.$eq`: `string` → `ProcessDefinitionKey` - `CorrelatedMessageSubscriptionFilter.processDefinitionKey`: `string` → `ProcessDefinitionKeyFilterProperty | undefined` - `CorrelatedMessageSubscriptionSearchQuery.filter.processDefinitionKey.$eq`: `string` → `ProcessDefinitionKey` - `ProcessInstanceIncidentSearchQuery` renamed to `IncidentSearchQuery` Example request payload update for message subscription filtering: - Before: `"processDefinitionKey": "2251799813685251"` - After (for example): `"processDefinitionKey": { "$eq": "2251799813685251" }` **Action:** Take the following action for your integration type: | Integration type | Action required | | :--------------------- | :-------------------------------------------------------------- | | Official SDK users | Update to the latest SDK version. | | Generated-client users | Regenerate clients and update type mappings/imports. | | Custom integrations | Update request payload construction and affected typed helpers. | [8.9 API migration guide](/versioned_docs/version-8.9/apis-tools/migration-manuals/migrate-to-89.md#type-safety-enhancements) Breaking change #### Resource deletion endpoint response body Starting with 8.9.0-alpha4, the resource deletion endpoint `POST /resources/{resourceKey}/deletion` in the [Orchestration Cluster API](../../../apis-tools/orchestration-cluster-api-rest/specifications/delete-resource.api.mdx) returns a response body. This provides explicit deletion feedback, making client-side confirmation, auditing, and follow-up workflow logic more reliable. **Action:** If you use an SDK, update to the latest version for compiler and model support. Breaking change #### Search filter validation errors now return structured error collections REST API search endpoints now collect all filter validation errors and return them together in a single `400 Bad Request` response. Previously, only the first conversion error was returned. This fix improves consistency by collecting all validation issues in a single response. - All search filter validation errors are now collected and returned together, instead of stopping at the first error. - The ProblemDetail `title` changed from `"Bad Request"` to `"INVALID_ARGUMENT"`. - The ProblemDetail `detail` now contains more descriptive, structured messages (for example, `"The provided evaluationDate 'invalid' cannot be parsed as a date according to RFC 3339, section 5.6."` instead of `"Failed to parse date-time: [invalid]"`). **Impact** | Customer type | Affected | | :--------------------------------------------------------------------------------------------- | :--------------------------- | | Parsing error response bodies (specifically `title` or `detail` fields) for validation errors. | Yes | | Only checking HTTP status codes. | No | | Sending valid requests. | No (happy path is unchanged) | **Action:** - If your code parses error response bodies from search endpoints, update it to handle a collection of validation errors instead of a single error message. - If your code checks for `"Bad Request"` in the `title` field, update it to check for `"INVALID_ARGUMENT"`. [8.9 API migration guide](/versioned_docs/version-8.9/apis-tools/migration-manuals/migrate-to-89.md#search-filter-validation-errors) Breaking change #### Streamlined SaaS orchestration architecture Camunda 8.9 introduces the streamlined SaaS orchestration architecture. As part of this change, the primary cluster endpoints have moved to the unified `*.api.camunda.io` domain. The previous component-specific endpoints (for example, `*.zeebe.camunda.io`, `*.operate.camunda.io`, `*.tasklist.camunda.io`) remain available for backward compatibility in 8.9, but are **deprecated** and planned for **removal in Camunda 8.10**. **Impact:** If you use custom applications, scripts, SDKs, or tooling that construct or store cluster URLs (including deployment endpoints), update them to the new unified endpoints to avoid issues when the legacy endpoints are removed. **Action:** Update your integrations to use the new endpoint format and review the streamlined architecture documentation for details. [Streamlined SaaS orchestration architecture](../../../apis-tools/migration-manuals/saas-orchestration-architecture.md) Breaking change #### Frontend application URLs now require explicit application prefix {#frontend-path-prefix-required} In Camunda 8.9, Operate and Tasklist run in a single unified frontend application rather than as separate deployments. This architectural change requires explicit path prefixes to route requests to the correct handler. As a result, the automatic path redirection that previously allowed access without an explicit application prefix has been removed. Before 8.9, both of the following URLs worked: ```text https://.operate.camunda.io//processes/ https://.operate.camunda.io//operate/processes/ ``` From 8.9, only the URL with the explicit application prefix is valid: ```text https://.operate.camunda.io//operate/processes/ https://.tasklist.camunda.io//tasklist/tasks/ ``` Prefix-less paths now return `404 Not Found` instead of redirecting. **Impact:** Any bookmarks, external links, scripts, or applications that use prefix-less frontend URLs will break after upgrading to 8.9. **Action:** Update all stored or constructed frontend URLs to include the explicit application prefix (`/operate/` or `/tasklist/`). If you use reverse proxies or load balancers, consider adding redirect rules to handle legacy URL formats. [Upgrade 8.8 to 8.9](/versioned_docs/version-8.9/self-managed/upgrade/components/880-to-890.md#frontend-application-urls-now-require-explicit-application-prefix-breaking) Breaking change #### Type-safe pagination model in the Camunda Java client Starting with 8.9.0, the Camunda Java client uses type-safe pagination interfaces (`AnyPage`, `OffsetPage`, `CursorForwardPage`, `CursorBackwardPage`) instead of the previous `SearchRequestPage` class. Each search or statistics endpoint now exposes only the pagination methods it actually supports. Previously, the API allowed mixing incompatible pagination styles (for example, `.page(p -> p.from(10).after("cursor"))`), which always resulted in a `400 Bad Request` at runtime. This change surfaces that restriction at compile time. This change is **not binary-compatible**. Code compiled against the old API will fail at runtime without recompilation, because the method signature changed from `page(Consumer)` to `page(Consumer)`. All users must recompile, even if their source code does not require changes. **Action:** Take the following action for your integration type: | Integration type | Action required | | :--------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------- | | Lambda-based pagination (for example, `.page(p -> p.from(5).limit(10))`) | Recompile your application. Source code changes are not required for valid pagination patterns. | | Explicit `SearchRequestPage` references | Replace with `AnyPage` (import `io.camunda.client.api.search.page.AnyPage`). | | Explicit `SearchRequestOffsetPage` references | Replace with `OffsetPage`. | | Custom `TypedSearchRequest` implementations | Add a pagination type parameter (3 → 4 generic params). | | Custom `TypedPageableRequest` implementations | Add a pagination type parameter (1 → 2 generic params). | | Storing direction method returns (for example, `SearchRequestPage r = p.from(10)`) | Use `OffsetPage r = p.from(10)`, `CursorForwardPage r = p.after("c")`, or `CursorBackwardPage r = p.before("c")`. | [8.9 API migration guide](/versioned_docs/version-8.9/apis-tools/migration-manuals/migrate-to-89.md#type-safe-pagination) Breaking change #### `versionTag` now returns `null` instead of empty string when absent Starting with 8.9.0, API response fields for `versionTag` return `null` instead of an empty string `""` when no version tag is set. This properly indicates absence rather than leaking an internal default. Previously, customers had to handle both empty string and absent/null to determine whether a version tag was set. This change simplifies that logic by using `null` consistently to signal absence, aligning with how other optional fields like `businessId` are handled. **Action:** - If your code checks for an empty string (`""`) to detect a missing version tag, update it to check for `null` instead. - Official SDK users: update to the latest SDK version. - Generated-client users: regenerate your client to pick up the updated nullable annotation. Breaking change #### Camunda Process Test: Connectors port changed from 8085 to 8086 Starting with Camunda 8.9.1, the default Connectors REST API port for Camunda Process Test remote runtime has changed from 8085 to 8086. This change aligns with Camunda 8 Run, which exposes Connectors on port 8086 as of version 8.9.1. The previous default port 8085 would break inbound connector invocations when running tests against Camunda 8 Run. **Action:** If you use Camunda Process Test with remote runtime configuration: - Update your `connectors-rest-api-address` configuration to use port 8086 instead of 8085. - If you explicitly configured port 8085 in your test configuration, update it to 8086. - If you rely on the default configuration, no action is needed—the default is now 8086. [Camunda Process Test configuration](/apis-tools/testing/configuration.md#remote-runtime) Deprecated #### Deprecated enum literals in Orchestration Cluster API v2 The following enum literals in the V2 Orchestration Cluster API are now marked as deprecated: - `UNSPECIFIED` in `DecisionDefinitionTypeEnum` - `UNKNOWN` in `DecisionInstanceStateFilterProperty` - `UNKNOWN` in `DecisionInstanceStateEnum` These values were reintroduced and are now explicitly deprecated to preserve backward compatibility while signaling planned cleanup. **Action:** Avoid these values in new integrations. They are planned for removal in a future release, where removal will be a breaking change. [Orchestration Cluster API reference](../../../apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) New #### Task permission management and new task-worker role With task permission management, you can assign restricted permissions for user task operations. Camunda 8.9 introduces a new built-in Identity role, `task-worker`. Use this role to grant users limited access to work on tasks without assigning broader permissions. [Task permission management](../../../../components/tasklist/user-task-authorization) Removed #### Removed: Web Modeler API milestone endpoints The Web Modeler API endpoints under `/api/v1/milestones` that were deprecated in Camunda 8.8 are now removed in 8.9. **Action:** Use the corresponding endpoints under `/api/v1/versions` instead. :::warning Web Modeler SaaS In Web Modeler SaaS, the endpoints will no longer be available as of April 14, 2026. ::: [Web Modeler API](/apis-tools/web-modeler-api/index.md) ## Connectors Breaking change #### Connector secret filter now defaults to STRICT {#connector-secret-filter-strict-default} Starting with Connectors 8.9.10, the connector [secret filter](/self-managed/components/connectors/connectors-configuration.md#secret-filter) defaults to `STRICT` instead of `DISABLED`. In practice, this means a secret in a connector field only resolves at runtime if that same secret was already referenced in that same field at modeling time, in the deployed BPMN. **Action:** Before upgrading, confirm all connector fields that resolve a secret already reference that secret in the deployed BPMN. - If a field relies on resolving a secret it doesn't reference, add the reference. - To temporarily unblock connector jobs while you update the model, you can set `camunda.connector.secret-resolver.secret-filter.mode` to `DISABLED`, but be aware that this restores the affected behavior described in [Notice 61](/reference/notices.md#notice-61). Return to `STRICT` after updating the model. - Note that `LAX` is not useful in this scenario as it only changes behavior when the process definition cannot be retrieved, not when a field simply doesn't declare the secret. - On Camunda 8 SaaS, you can also change this per cluster in [cluster settings](/components/saas/clusters/settings.md#secret-filter-mode) once your cluster is on 8.9.10 or later. [Secret filter](/self-managed/components/connectors/connectors-configuration.md#secret-filter) Breaking change #### Outbound connectors refuse undeclared intrinsic function calls {#connector-intrinsic-function-allow-list} Starting with Connectors 8.9.13, the connector runtime runs an [intrinsic function](/components/connectors/advanced-topics/intrinsic-functions.md#declare-calls) only if the deployed BPMN process declares that exact call, by function name and field path, in the input mappings of the connector element. In practice, an intrinsic function call that reaches a connector through a process variable or job variable, instead of being written in the connector element's input mapping, now fails the job. A connector result that contains an object with the `camunda.function.type` key also fails the job when you store it in a result variable or map it with a result expression. **Action:** Before upgrading, check which processes use intrinsic functions. - Write each `camunda.function.type` call as a literal in the input mapping of the field that uses it, and redeploy the process. - Use a result expression that maps only the fields you need if a response from an external system can contain the `camunda.function.type` key. - Don't use `camunda.connector.intrinsic-function.allow-list.mode` set to `DISABLED` as a workaround. This mode refuses every intrinsic function call. No setting restores unrestricted execution. [Intrinsic function allow-list](/self-managed/components/connectors/connectors-configuration.md#intrinsic-function-allow-list) [Troubleshoot intrinsic function errors](/components/connectors/advanced-topics/intrinsic-functions.md#troubleshoot-intrinsic-function-errors) Breaking change #### Default secret provider prefix change Starting with Camunda 8.9, the environment-based connector secret provider uses `SECRET_` as the default prefix. Unprefixed environment variables are no longer resolved as connector secrets unless you explicitly configure an empty prefix or a different custom prefix. **Action:** Choose one of the following options before or during your upgrade: - Update your secret environment variables to use the `SECRET_` prefix. - Configure a custom prefix via `camunda.connector.secretprovider.environment.prefix` or `CAMUNDA_CONNECTOR_SECRETPROVIDER_ENVIRONMENT_PREFIX`. - Restore the previous behavior by setting an empty prefix, knowing that Camunda does not recommend this mode for production environments. [connector secrets configuration](/self-managed/components/connectors/connectors-configuration.md#secrets) [Upgrade 8.8 to 8.9](/versioned_docs/version-8.9/self-managed/upgrade/components/880-to-890.md#default-secret-provider-prefix-change-breaking) Deprecated #### Deprecated: Operate Connector The Operate Connector is deprecated, following the deprecation of the Operate API in Camunda 8.9 (see [Deprecated: Operate and Tasklist v1 REST APIs](/reference/announcements-release-notes/880/880-announcements.md#deprecated-operate-and-tasklist-v1-rest-apis)). Going forward, you can use the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) via the [REST Connector](/components/connectors/protocol/rest.md). Change #### Inbound connectors now support older process versions Starting with Camunda 8.9, inbound connectors are activated not only for the latest process version, but also for older process versions that have active instances waiting on message subscriptions. Inbound connectors now remain active for any process version that has instances waiting on message subscriptions. This ensures that running process instances can continue to receive messages through inbound connectors, even after a newer version of the process is deployed. This change improves the reliability of long-running processes that depend on inbound connectors to receive external events. [Inbound connector lifecycle](/components/connectors/advanced-topics/inbound-lifecycle.md) Change #### Dedicated artifact for element template generation annotations Starting with Camunda 8.9, use the dedicated artifact `element-template-generator-annotations`. This artifact contains only the annotations required for the Element Template Generator and reduces your project's dependency footprint. Previously, these annotations were included in the `element-template-generator-core` artifact. Replace `element-template-generator-core` with `element-template-generator-annotations` and update the imports. New #### Synchronous webhook results Starting with Camunda 8.9, the Webhook connector supports a synchronous response mode. This allows the connector to wait until a newly created process instance finishes and return the results in the response. For more information, see the [Webhook connector](/components/connectors/protocol/http-webhook.md) documentation. New #### Virtual threads support Camunda 8.9 provides support for virtual threads in the connector runtime. Virtual threads are enabled by default for outbound connectors. For more information on optimizing connector performance with virtual threads, see [connector runtime performance](/self-managed/components/connectors/performance.md). Change #### Kafka consumer connector: auto-generated consumer group ID format changed When upgrading from Camunda 8.8 to 8.9, the auto-generated Kafka consumer group ID changes format. This affects Kafka consumer connectors only when no explicit consumer group ID is configured. The change is a side effect of a runtime improvement that enables cross-version connector deduplication. - **8.8 format:** `kafka-inbound-connector---` (numeric key) - **8.9 format:** `kafka-inbound-connector---` (stable process ID) Because Kafka treats a changed group ID as a brand-new consumer group, affected connectors do not reuse their committed offsets after the upgrade and may reprocess messages. **Action required:** Before upgrading to 8.9, set an explicit **Consumer Group ID** in each Kafka consumer connector configuration to preserve the existing consumer group identity. You can [look up existing consumer groups](https://docs.confluent.io/kafka/operations-tools/manage-consumer-groups.html#list-groups-and-view-offsets) to find the current group ID in use. See the [Kafka connector documentation](/components/connectors/out-of-the-box-connectors/kafka.md?kafka=inbound) for details. ## Data Breaking change #### Camunda 8 Run: Docker Compose support removed The `--docker` flag and bundled Docker Compose files have been removed from Camunda 8 Run in 8.9. Camunda 8 Run and Docker Compose are now separate distribution artifacts. **Action:** If you previously started Docker Compose via `./c8run start --docker`, download the standalone [Docker Compose distribution](/self-managed/quickstart/developer-quickstart/docker-compose.md) from GitHub releases or the Camunda Download Center and run it independently. Breaking change #### Camunda 8 Run with H2 as the default secondary data storage Camunda 8 Run now uses H2 as the default secondary data storage, instead of Elasticsearch. Elasticsearch is no longer bundled with Camunda 8 Run. The `--disable-elasticsearch` flag is also removed, as Elasticsearch is no longer bundled. To use Elasticsearch as secondary storage, configure an external instance via `--config` and the `application.yaml`. When running with H2 (or any other RDBMS) as secondary storage, Camunda is only compatible with the V2 API. As a result, some features are not available in Operate and Tasklist. See [Migrate to the V2 Orchestration Cluster API](/apis-tools/migration-manuals/migrate-to-camunda-api.md) for more details. To continue using features exclusive to the V1 API, configure Camunda 8 Run with an external Elasticsearch instance and switch back to V1 mode. [Camunda 8 Run default secondary storage](/self-managed/quickstart/developer-quickstart/c8run/secondary-storage.md#default-h2-camunda-8-run) Change #### Hierarchy-aware retention for process instance data Starting with Camunda 8.9, retention of process instance data in Elasticsearch/OpenSearch secondary storage becomes hierarchy-aware, meaning child process instances are retained as long as their root process instance is retained. You can control the retention behavior via the process instance retention mode configuration. [Hierarchy-aware retention](/self-managed/components/orchestration-cluster/core-settings/concepts/data-retention.md#hierarchy-aware-retention) New #### Amazon Aurora secondary storage Camunda 8.9 introduces Amazon Aurora as a secondary data store for Orchestration Clusters. [Secondary storage](/self-managed/concepts/secondary-storage/index.md) ## Deployment Breaking change #### Elasticsearch subchart no longer enabled by default Previously, the Elasticsearch subchart was enabled by default. To use OpenSearch, you would need to disable Elasticsearch and enable OpenSearch. With the inclusion of RDBMS as a secondary storage option and the [deprecation of Bitnami subcharts](#helm-chart-bitnami-subcharts-deprecated) (including the bundled Elasticsearch subchart), there is no longer a default secondary storage type. **Action:** - Camunda recommends setting `orchestration.data.secondaryStorage.type` explicitly in your `values.yaml`. - The chart can auto-detect the type from `global.elasticsearch.enabled` or `global.opensearch.enabled`, but Helm will fail with a validation error if no secondary storage is configured. - Alternatively, set `global.noSecondaryStorage: true` to run in engine-only mode without secondary storage. :::note To continue using Elasticsearch provided as a subchart, you must add `global.elasticsearch.enabled: true`, `elasticsearch.enabled: true`, and `orchestration.data.secondaryStorage.type: elasticsearch` to your `values.yaml`. ::: Breaking change #### Helm chart: Default REST port unified to 8080 The Orchestration Cluster's default HTTP port has changed from 8090 to 8080 (`orchestration.service.httpPort`). This aligns the Helm chart with the Orchestration Cluster's default configuration. **Action:** If you have hardcoded port 8090 in network policies, Ingress rules, health check probes, or service mesh configuration, update these references to 8080 or explicitly set `orchestration.service.httpPort: 8090` in your `values.yaml`. Breaking change #### Helm chart: Deprecated secret keys removed Secret configuration keys that were deprecated in Camunda 8.8 are now removed in 8.9. Using any of the removed keys in your `values.yaml` will cause a hard failure during `helm install` or `helm upgrade`. Affected keys by component: | Component | Keys removed in 8.9 | | :-------------------- | :------------------------------------------------------------------------------------------------------------------------------ | | Global image tag | `global.image.tag` | | License | `global.license.key`, `global.license.existingSecret`, `global.license.existingSecretKey` | | Elasticsearch auth | `global.elasticsearch.auth.password`, `global.elasticsearch.auth.existingSecret`, `global.elasticsearch.auth.existingSecretKey` | | OpenSearch auth | `global.opensearch.auth.password`, `global.opensearch.auth.existingSecret`, `global.opensearch.auth.existingSecretKey` | | Identity auth | `global.identity.auth.{admin,identity,optimize}.existingSecret` and `.existingSecretKey` | | Document Store | `global.documentStore.type.{aws,gcp}.existingSecret` and related keys | | Identity | `identity.firstUser.password`, `identity.externalDatabase.password`, and their `existingSecret`/`existingSecretKey` variants | | Connectors | `connectors.security.authentication.oidc.existingSecret`, `.existingSecretKey` | | Orchestration Cluster | `orchestration.security.authentication.oidc.existingSecret`, `.existingSecretKey` | | Web Modeler | `webModeler.restapi.externalDatabase.password`, `webModeler.restapi.mail.smtpPassword`, and their `existingSecret` variants | **Action:** Migrate to the new secret configuration pattern using `*.secret.existingSecret` and `*.secret.existingSecretKey`, or `*.secret.inlineSecret` for non-production environments. [Secret management](/self-managed/deployment/helm/configure/secret-management.md) Breaking change #### Helm chart: `extraConfiguration` format changed from map to ordered list The `.extraConfiguration` Helm value has changed from a **map** (key-value pairs) to an **ordered list** of `file`/`content` entries. This ensures configuration entries are always applied in the order you define them, since maps in Go templates do not guarantee iteration order. The previous map format is **not supported** in Camunda 8.9. If you upgrade without converting to the new list format, Helm will fail during template rendering. **Before (8.8 — map):** ```yaml identity: extraConfiguration: custom-logging.yaml: | logging: level: ROOT: DEBUG ``` **After (8.9 — ordered list):** ```yaml identity: extraConfiguration: - file: custom-logging.yaml content: | logging: level: ROOT: DEBUG ``` **Action:** For every component where you use extraConfiguration, convert each map entry to a list entry. [Migrate extraConfiguration from 8.8 to 8.9](/self-managed/deployment/helm/configure/application-configs.md#migrate-extraconfiguration-from-88-to-89) Breaking change #### Helm chart: Helm CLI v4 does not support duplicate environment variable names Helm v4 enforces that environment variables in a rendered Kubernetes manifest must be unique. If your `values.yaml` overrides an environment variable also set by the chart, you might encounter an error. [Helm 4](/self-managed/deployment/helm/operational-tasks/helm-v4.md) Breaking change #### Helm chart: Secret auto-generation removed The Helm chart no longer automatically generates secrets. The configuration keys `global.secrets.autoGenerated`, `global.secrets.name`, and `global.secrets.annotations` are removed in 8.9. **Action:** All secrets must now be explicitly provided via Kubernetes Secrets referenced in your `values.yaml`. [Secret management](/self-managed/deployment/helm/configure/secret-management.md) Deprecated #### Helm chart: Bitnami subcharts deprecated The four Bitnami-based subcharts (`identityPostgresql`, `identityKeycloak`, `webModelerPostgresql`, `elasticsearch`) are deprecated in Camunda 8.9 and will be removed in Camunda 8.10. If any of these subcharts are enabled, Helm prints a deprecation warning during installation or upgrade. **Action:** Migrate to externally managed services before upgrading to Camunda 8.10. Deprecated #### Helm chart: `global.elasticsearch` and `global.opensearch` deprecated The `global.elasticsearch.*` and `global.opensearch.*` configuration trees are deprecated in Camunda 8.9 and will be removed in Camunda 8.10. These options are not truly global, as only the Orchestration and Optimize components use them. Legacy keys continue to work in Camunda 8.9 with deprecation warnings. Existing deployments will continue to function without changes. **Action:** You should migrate to the new component-specific configuration: | Component | New configuration path | | :------------ | :--------------------------------------------------------------------------------------------------------- | | Orchestration | `orchestration.data.secondaryStorage.elasticsearch.*` / `orchestration.data.secondaryStorage.opensearch.*` | | Optimize | `optimize.database.elasticsearch.*` / `optimize.database.opensearch.*` | Deprecated #### Helm chart: TLS secret configuration pattern The legacy TLS secret configuration using `*.tls.existingSecret` is deprecated. Legacy keys still work in Camunda 8.9 but will cause deprecation warnings and are removed in a future version. This affects the following paths: | Deprecated path | New path | | :---------------------------------------- | :----------------------------------------------- | | `global.elasticsearch.tls.existingSecret` | `global.elasticsearch.tls.secret.existingSecret` | | `global.opensearch.tls.existingSecret` | `global.opensearch.tls.secret.existingSecret` | | `console.tls.existingSecret` | `console.tls.secret.existingSecret` | **Action:** Migrate to the new pattern using `*.tls.secret.existingSecret`. [Secret management](/self-managed/deployment/helm/configure/secret-management.md) Deprecated #### Helm chart: Identity profile renamed to admin The orchestration profile `orchestration.profiles.identity` is deprecated and renamed to `orchestration.profiles.admin`. If your `values.yaml` uses the `identity` profile key, the chart automatically migrates it to `admin` and prints a deprecation warning. **Action:** Update your values file to use `orchestration.profiles.admin`. Deprecated #### Helm chart: Keycloak auth secret configuration The legacy Keycloak auth secret configuration using `global.identity.keycloak.auth.existingSecret` and `global.identity.keycloak.auth.existingSecretKey` is deprecated in Camunda 8.9. **Action:** Migrate to the new standard secret pattern: - `global.identity.keycloak.auth.existingSecret` → `global.identity.keycloak.auth.secret.existingSecret` - `global.identity.keycloak.auth.existingSecretKey` → `global.identity.keycloak.auth.secret.existingSecretKey` Legacy keys continue to work in 8.9 via normalizers, but produce deprecation warnings and will be removed in a future version. See [Secret management](/self-managed/deployment/helm/configure/secret-management.md). Change #### Helm chart: Bitnami subcharts bundled The Bitnami subcharts (PostgreSQL, Keycloak, Elasticsearch, and Common) are bundled directly within the Camunda Helm chart instead of being fetched from external Bitnami repositories at install time. This change reduces the risk of unexpected breaking changes from upstream Bitnami chart updates and gives Camunda full control over the lifecycle of these subcharts. **Action:** No action is required. Existing deployments continue to work as before. Change #### Helm chart: Headless service exposes public API ports The Orchestration component's headless service now includes gRPC and HTTP ports, enabling client-side load balancing for applications that connect directly to individual cluster members. Change #### Helm chart: Connectors OIDC issuer URI preferred over token URL The Connectors component now uses the OIDC issuer URI by preference, making `tokenUrl` optional when `issuerUri` is configured. This simplifies OIDC integration by allowing the token endpoint to be discovered automatically. Change #### Helm chart: Persistent web sessions enabled for RDBMS mode When using RDBMS as the secondary storage type, web sessions now persist across pod restarts. Previously, sessions were discarded, requiring users to re-authenticate after each restart. New #### Helm chart: `springImport` option for `extraConfiguration` entries Each `extraConfiguration` entry now supports an optional `springImport` field (default: `true`). **Action:** Set `springImport: false` to mount configuration files (such as `log4j2.xml` or keystores) into the container without adding them to Spring Boot's `spring.config.import`. This applies to all components that support `extraConfiguration`: orchestration, identity, connectors, optimize, and web-modeler. New #### Helm chart: Authorization, role, and group initialization Camunda 8.9 adds Helm chart support for initializing authorization rules, roles, and groups directly through `values.yaml`. This allows administrators to configure platform access control as part of the initial deployment, reducing manual post-installation setup. New #### Helm chart: Custom users and clients You can now define Identity users and OAuth2 clients directly in `values.yaml`. This simplifies initial deployment setup and enables reproducible, version-controlled Identity configurations. New #### Helm chart: Engine-only mode without secondary storage Camunda 8.9 introduces `global.noSecondaryStorage` mode to allow running the Orchestration engine without any secondary storage (Elasticsearch, OpenSearch, or RDBMS). This is useful for lightweight testing or scenarios where only the core engine is needed. When enabled, Elasticsearch and OpenSearch subcharts must be disabled. Authentication is supported using OIDC (recommended) or Basic authentication with unprotected API mode for development environments. [Learn about authentication with no secondary storage](/self-managed/concepts/secondary-storage/no-secondary-storage.md#authentication) New #### Helm chart `values.yaml` options for RDBMS Camunda 8.9 adds RDBMS configuration options to the Helm chart's `values.yaml` file, providing a first-class alternative to Elasticsearch and OpenSearch. - Configure database connections directly under `orchestration.data.secondaryStorage.rdbms`, including JDBC URL and authentication. - Supports all databases in the [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md): PostgreSQL, Oracle, MariaDB, MySQL, Microsoft SQL Server, H2, and Amazon Aurora. - Advanced authentication and custom JDBC drivers can be configured via init containers or mounted volumes. [Configure RDBMS in Helm charts](/self-managed/deployment/helm/configure/database/rdbms.md) New #### Helm chart: New restore options Camunda 8.9 adds new configuration options for backup restore operations in the Helm chart, giving operators more control over restore behavior via `values.yaml`. New #### Helm chart: Kubernetes Gateway API support Camunda 8.9 adds support for the [Kubernetes Gateway API](https://gateway-api.sigs.k8s.io/) as an alternative to Ingress for routing external traffic to Camunda components. Setting `global.gateway.enabled: true` provisions `Gateway`, `HTTPRoute`, and `GRPCRoute` resources. Configure the gateway class, listeners, and per-component route options under `global.gateway.*` in your `values.yaml`. New #### Helm chart: TLS JKS keystore password configuration Camunda 8.9 adds `global.elasticsearch.tls.jks.password` and `global.opensearch.tls.jks.password` fields, allowing you to set the JKS keystore password directly in `values.yaml` instead of using workarounds. New #### Helm chart: App Integrations exporter configuration Camunda 8.9 adds configuration options for the App Integrations exporter in the Helm chart's `values.yaml`, allowing you to configure how process data is exported for application integrations. New #### Helm chart: Helm template support for pod labels, annotations, and Ingress host `podLabels`, `podAnnotations`, and `global.ingress.host` now support Go template expressions (`tpl`). This enables dynamic configurations such as DataDog APM integration labels and environment-driven GitOps Ingress hostnames. New #### Secure connectivity (AWS PrivateLink) for SaaS Camunda 8.9 introduces Secure connectivity for AWS-hosted Orchestration Clusters in Camunda 8 SaaS. Secure connectivity enables private inbound access from your AWS VPC to your cluster using AWS PrivateLink, without routing traffic over the public internet. - Applies per cluster. - Supports inbound connectivity only. - Public connectivity remains enabled. [Secure connectivity (AWS PrivateLink)](../../../components/saas/secure-connectivity/index.md) New #### Cluster variable support Camunda 8.9 introduces cluster variables, allowing you to centrally manage configuration across your cluster. [Cluster variables](/components/modeler/feel/cluster-variable/overview.md) New #### MySQL and Microsoft SQL Server secondary storage Camunda 8.9 extends RDBMS secondary storage to include MySQL and Microsoft SQL Server as additional options for the Orchestration cluster. New #### RDBMS secondary storage Camunda 8.9 introduces optional RDBMS secondary storage as an alternative to Elasticsearch or OpenSearch. This enables teams to use relational databases such as H2, PostgreSQL, Oracle, or MariaDB for storing and querying process data, reducing operational complexity for non-high-performance use cases. New #### Standardized JDBC driver management for RDBMS Camunda 8.9 adds a standardized JDBC driver management system for manual installations. - A new `/driver-lib` directory separates Camunda-bundled drivers from customer-supplied ones. - Customers can add and configure their own drivers (for example, Oracle JDBC), while maintaining full compliance and version control. Change #### Reference architectures: Operator-based infrastructure All Self-Managed [reference architectures](https://github.com/camunda/camunda-deployment-references) now use Kubernetes operators (CloudNativePG, ECK, Keycloak operator) instead of embedded Bitnami subcharts for infrastructure services. This applies to AKS, EKS, OpenShift, and Kind deployments. **Action:** If you follow a reference architecture and currently use Bitnami-managed infrastructure, use the [migration tooling](/self-managed/deployment/helm/operational-tasks/migration-from-bitnami/index.md) to transition to operator-managed services. Change #### Reference architectures: Generic OIDC replaces Keycloak as default Reference architectures now use auth overlays with generic OIDC instead of Keycloak by default, enabling flexible identity provider integration. **Action:** If you deploy using a reference architecture with the default identity provider configuration, review the updated auth overlay settings. New #### Reference architectures: Amazon ECS on Fargate A new container-based reference architecture is available for deploying Camunda on AWS ECS with Fargate, without requiring Kubernetes. [Deploy to Amazon ECS](/self-managed/deployment/containers/cloud-providers/amazon/aws-ecs.md) New #### Reference architectures: AKS RDBMS variant and Kind local development - A lighter Azure AKS reference architecture using PostgreSQL as secondary storage (no Elasticsearch) with PostgreSQL 17 support is now available. - A Kind (Kubernetes in Docker) reference architecture is available for local development and testing. New #### Reference architectures: OpenShift dual-region with ECK OpenShift dual-region reference architecture now supports ECK (Elastic Cloud on Kubernetes) operator for Elasticsearch. EKS dual-region also uses the ECK operator, and headless service DNS is now used for initial contact points. ## Engine Regression #### Multi-instance sub-process output mapping variable scope regression {#multi-instance-output-mapping-regression} Camunda 8.9.1 introduced a regression in which output mappings inside a multi-instance sub-process that also defines an output collection cause local variables to propagate to the parent scope. You're affected if your process contains a multi-instance sub-process that meets both of the following conditions: 1. The sub-process defines an output collection. 2. One or more elements inside the sub-process define output mappings. Under these conditions: - Local variables from inside the sub-process appear in the parent scope and are visible in Operate. - If any leaked variable shares a name with a variable on the parent scope, the parent scope value is overwritten. **Workaround:** Ensure all variable names used inside the multi-instance sub-process are unique and do not reuse names that exist on the parent scope. **Fix:** A fix is available in 8.9.9. The fix reverts the input/output mapping changes that introduced this regression. As a side effect, two previously resolved bugs are reintroduced: - [camunda/camunda#11789](https://github.com/camunda/camunda/issues/11789): FEEL expressions used as mapping sources may not evaluate correctly due to ordering. - [camunda/camunda#35251](https://github.com/camunda/camunda/issues/35251): When one value from a nested variable is listed as an output mapping, all values in the nested variable are merged into the parent scope. Workaround: map the full nested variable instead of individual values. **Action:** - Before the fix is available: ensure all variable names inside the multi-instance sub-process are unique and do not reuse names that exist on the parent scope. - After upgrading to the fixed patch: bugs #11789 and #35251 are reintroduced by the fix. If you previously had adaptations in place to work around these bugs and removed them, reapply those adaptations. Regression #### Output mapping behavior change for object variables {#output-mapping-behavior-change} **Affected versions:** 8.9.1–8.9.8. Fixed in 8.9.9. Patches 8.9.1–8.9.8 changed how output mappings behave when writing to object variables. Upgrading to 8.9.9+ reverts this change, which can alter the behavior of your running processes. Before 8.9.1 and from 8.9.9+, assigning an object literal to a variable replaces the variable entirely. In 8.9.1–8.9.8, the behavior changed to _merge_: existing keys in the variable are preserved and new keys are added. Example: task A sets `result = {a: 1}`, then task B sets `result = {b: 2}`: - _Replace_ (before 8.9.1 and from 8.9.9+): `result = {"b": 2}` — task A's value is overwritten. - _Merge_ (8.9.1–8.9.8): `result = {"a": 1, "b": 2}` — task A's value is preserved. Replace is the intended long-term behavior. The merge behavior in the affected patches was an unintended regression. **Action:** - **Running 8.9.1–8.9.8:** your processes use merge behavior. Identify any process where one task writes to a sub-key of a variable and a later task assigns an object literal to the same parent. If found, either switch the later task to path notation `result.b = 2` or include all required keys explicitly in its object literal. - **Upgrading to 8.9.9+:** replace behavior is restored. The same processes identified above will behave differently after upgrading. If your process was relying on earlier tasks' values being kept, you need to fix it before upgrading: instead of assigning a whole object `result = {a: 1, b: 2}`, make sure it includes all the keys it needs explicitly — or write each key separately `result.a = 1, result.b = 2`. Regression #### Nested input mappings can silently drop sibling fields {#nested-input-mapping-sibling-fields} **Affected versions:** 8.9.15. Reverted in 8.9.16. Camunda 8.9.15 introduced a regression affecting elements with two or more input mappings that write to different nested fields of the same parent variable. Only one of the mapped fields retains its expected value, with the other field silently set to `null` without warning or raised incident. **Example:** You have a parent-scope variable `foo: {bar: 1, baz: 2}` and an element with these two input mappings that both write into a local `foo` variable: 1. Target `foo.bar` ← (maps from) source `=foo.bar` 2. Target `foo.baz` ← (maps from) source `=foo.baz` In this scenario, the local `foo` becomes `{bar: 1, baz: null}`, with `baz` (mapped by the later declaration) no longer able to resolve against the parent scope. **Action:** - **Running 8.9.15:** Combine the mappings into a single mapping that rebuilds the whole object at once. For example, target `foo` with source `={bar: foo.bar, baz: foo.baz}` instead of mapping `foo.bar` and `foo.baz` separately. - **Upgrading to 8.9.16+:** The regression is reverted. Mapping individual fields of the same variable works correctly again. The workaround above is no longer required, but is harmless to keep. Regression #### Chained input mappings can silently drop FEEL temporal value types {#chained-input-mapping-temporal-type-loss} **Affected versions:** 8.9.15. Reverted in 8.9.16. Camunda 8.9.15 introduced a regression affecting elements with two or more input mappings where one mapping produces a FEEL temporal value (`duration`, `date`, `time`, `date-time`, or their local variants) and a later mapping on the same element reads a property from it. The temporal value loses its type, becoming a plain string due to serialization between mapping evaluations before the later mapping runs, so the property access silently evaluates to `null` instead of the expected value. Only temporal types are affected — other FEEL types (strings, numbers, booleans, lists, and contexts) work correctly. **Example:** An element with these two input mappings: 1. Target `age` ← (maps from) source `=@"P1D"` (a duration) 2. Target `ageDays` ← (maps from) source `=age.days` In this scenario, the mapping results in `ageDays` as `null`, as `age`'s duration type is not preserved between the two mappings. **Action:** - **Running 8.9.15:** Combine the mappings into a single mapping, so the duration is created and read in the same expression instead of being written to a variable and read back later. For example, target `ageDays` with source `=@"P1D".days` instead of separate `age` and `ageDays` mappings. - **Upgrading to 8.9.16+:** The regression is reverted. Chained mappings on temporal values work correctly again. Change #### Message TTL cleanup batch size pacing change {#message-ttl-cleanup-batch-size-pacing-change} Starting in Camunda 8.9.1, expired-message cleanup `MessageBatchExpireProcessor` no longer resumes its RocksDB scan from a continuation cursor across batches. Each cleanup batch now re-scans from the start of the message deadline index and skips over the tombstones of messages already expired earlier in the same drain sequence. This makes cleanup cost per batch sensitive to `ttlCheckerBatchLimit`: a low value (for example, `10`) requires many more restart-scans to drain a backlog of expired messages, which can cause backpressure on normal process and message processing. `zeebe.broker.experimental.engine.messages.ttlCheckerBatchLimit` defaults to `100`. **Action:** If you previously tuned `ttlCheckerBatchLimit` down (for example, to `10`) on an earlier patch to avoid latency peaks, revalidate it after upgrading to 8.9.1 or later. Increasing it (for example, to `500`) reduces the number of restart-scans needed to drain a backlog and restores expected throughput. ## Identity Change #### Orchestration Cluster Identity renamed to Admin Starting with Camunda 8.9, the Orchestration Cluster Identity component has been renamed to **Admin** (also referred to as Orchestration Cluster Admin). Admin is the cluster-level admin UI hosting identity management and other administrative features. Identity management functionality (users, groups, roles, authorizations, tenants, mapping rules, and clients) is unchanged, although some naming, paths, and config keys are updated as follows: - The `admin` Spring profile replaces the `identity` profile. Both profiles work interchangeably in 8.9. The `identity` profile is deprecated and will be removed in a future version. - API paths change from `/identity/*` to `/admin/*`. The old paths redirect to the new paths but are deprecated. - Helm values change from `orchestration.identity.*` to `orchestration.admin.*`. The old values are deprecated. - Documentation paths are updated: `/components/identity/` is now `/components/admin/`. [Introduction to Admin](/components/admin/admin-introduction.md) ## Integrations Change #### Microsoft Teams notifications require app integrations extensions {#teams-notifications-require-app-integrations-extensions} Camunda 8 SaaS clusters running generation `8.9 gen13` or later deliver user task notifications to Microsoft Teams only when **Enable app integrations extensions** is turned on in the cluster settings. The setting is disabled by default, and only organization admins can change it. Clusters running earlier generations are unaffected and continue to deliver notifications without additional configuration. **Action:** After a cluster updates to generation `8.9 gen13` or later, an organization admin must turn on **Enable app integrations extensions** for existing [notification rules](/components/camunda-integrations/app-integrations/notification-rules.md) to keep delivering. Enabling the setting also delivers notifications when an existing task is later assigned to you, and updates notification cards as a task is assigned, completed, or canceled. [Enable app integrations extensions](/components/saas/clusters/settings.md#enable-app-integrations-extensions) ## Modeler Breaking change #### Web Modeler: `webapp` component removed The Web Modeler system architecture has been simplified to enable easier and smoother installation and configuration of Web Modeler in a Self-Managed deployment. The separate `webapp` component has been removed and its functionality is now completely integrated into the `restapi` component. This change might require updates to your application configuration. [Migrate configuration](/versioned_docs/version-8.9/self-managed/upgrade/components/880-to-890.md#migrate-webapp-configuration) Breaking change #### Web Modeler: Default logging format changed By default, Web Modeler's `restapi` component now logs in a simple, readable format to the console instead of `JSON`. This change aligns with the current Orchestration Cluster logging default as defined in its [logging configuration](/self-managed/components/orchestration-cluster/core-settings/configuration/logging.md#pattern-layout-format). Breaking change #### Web Modeler: Embedded web server changed from Undertow to Tomcat Web Modeler now uses [Apache Tomcat](https://tomcat.apache.org/) as an embedded web server instead of [Undertow](https://undertow.io/). This aligns with the Orchestration Cluster. This enhancement ensures consistency across environments and simplifies setup for administrators. [Embedded web server](/versioned_docs/version-8.9/self-managed/upgrade/components/880-to-890.md#embedded-web-server) Breaking change #### Web Modeler: Form deployment changes With Camunda 8.9, you can now deploy forms independently. This enhancement provides greater control over what is deployed and when, enabling more precise management of changes and updates across environments. As part of this improvement, we have removed the automatic deployment of [linked forms](/components/hub/workspace/modeler/modeling/advanced-modeling/form-linking.md). Forms must now be explicitly deployed, giving teams finer control over versioning, release timing, and deployment scope. This change supports more predictable deployments and helps teams manage updates with greater confidence and flexibility. :::info To learn more, see the [8.9.0-alpha5 release notes](/reference/announcements-release-notes/890/890-release-notes.md). ::: Breaking change #### Web Modeler: Invite collaborators who haven't logged in before The behavior across OIDC providers is now aligned. Invitation suggestions only include users who have logged in at least once. This is a breaking change for Web Modeler installations using Keycloak as the OIDC provider. Before 8.9, Keycloak returned all organization users, including those who had never logged in. You can now invite users who have not yet logged in to Web Modeler by entering their email address. They will appear as “invited” in the collaborators panel. After their first log in, they will be added to the project automatically. Inviting the entire organization only applies to users who have logged in at least once. [Add users to projects](/components/hub/organization/manage-workspaces/manage-workspace-members.md#add-members) Breaking change #### Web Modeler: JSON format changes When using JSON as the output for the logs the structure has slightly changed: - `logger`: This field is now renamed to `loggerName`. - `thread`: Previously represented the name of the thread. Now we have an object named `threadContext` with a field `name` that has this value. See [Logging documentation](/self-managed/components/hub/configuration/logging.md#json-structure) for more information. Breaking change #### Web Modeler: Logging framework changed from Logback to Apache Log4j 2 Web Modeler now uses [Apache Log4j 2](https://logging.apache.org/log4j/2.x/) for logging, in alignment with what the Orchestration Cluster uses. This enhancement ensures consistency across environments and simplifies setup for administrators. New #### Web Modeler: RDBMS support (H2, MariaDB, MySQL) Camunda 8.9 adds support for H2, MariaDB, and MySQL as relational databases for Web Modeler. This enhancement aligns Web Modeler's database configuration with the Orchestration cluster, ensuring consistent setup and improved integration across environments. Change #### Web Modeler: The main process label has been removed from process applications Starting with Camunda 8.9, the main process label has been removed from process applications in Web Modeler. This removes all restrictions previously tied to the main process label, aligning the Web Modeler process application experience with the Desktop Modeler. As a result, process applications can now be synced with Git more easily. Additionally, users have the freedom to deploy and run any process within a process application, allowing them to design solutions with more than one primary entry point. Change #### Web Modeler: Users now have full control over version tags in process applications Starting with Camunda 8.9, users have full control over the `versionTag` attribute across all resource types in a process application. The `versionTag` is no longer automatically set on the main process XML when creating a process application version. Instead, users can set the `versionTag` manually in the properties panel for BPMN, DMN, form, and RPA files, allowing them to choose a `versionTag` for each resource independently. ## Tasklist Regression #### Tasklist V1: candidate group task visibility {#tasklist-v1-candidate-group-task-visibility} Camunda 8.9.7 introduced a regression in which user tasks become invisible in the Tasklist V1 API when a candidate group's name differs from its ID ([camunda/camunda#55576](https://github.com/camunda/camunda/issues/55576)). With this regression, the Zeebe engine resolves candidate group names to IDs at task creation time, while the Tasklist V1 API resolves the authenticated user's group IDs to names before comparing them against the stored candidate groups. When a group's name and ID differ, this mismatch causes tasks to be invisible to all members of that group. You're affected if you use the Tasklist V1 API with user task access restrictions enabled and any group used as a candidate group in your processes has a name that differs from its ID. **Workaround:** Set the Zeebe broker environment variable `ZEEBE_BROKER_EXPERIMENTAL_ENGINE_CACHES_CANDIDATEGROUPNAMERESOLUTION` to `false` (default: `true`) and ensure your BPMN models reference candidate groups by name rather than ID. This restores correct task visibility for newly created tasks. Tasks created while the regression was active remain affected. **Fix:** The fix was released in [8.9.10](https://github.com/camunda/camunda/releases/tag/8.9.10). After upgrading, tasks created while the regression was active are also fixed without requiring manual intervention. :::note The Tasklist V1 API is deprecated and will be removed in Camunda 8.10. Consider [migrating to the Tasklist V2 API](/apis-tools/tasklist-api-rest/tasklist-api-rest-overview.md) to avoid disruption when upgrading to 8.10 or later. ::: --- ## 8.9 Release notes | Minor release date | End of standard maintenance | Changelog(s) | Upgrade guides | | :----------------- | :-------------------------- | :-------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------- | | 14 April 2026 | 12 October 2027 | [Patch Releases and Changelogs](#technical-changelogs-for-all-89x-releases) | [8.9 upgrade guides](/reference/announcements-release-notes/890/whats-new-in-89.md#upgrade-guides) | :::info 8.9 resources - See [What's new in Camunda 8.9](/reference/announcements-release-notes/890/whats-new-in-89.md) for important changes to consider when planning your upgrade from Camunda 8.8. - See [release announcements](/reference/announcements-release-notes/890/890-announcements.md) to learn more about supported environment changes, breaking changes, and deprecations. - Refer to the [quality board](https://github.com/orgs/camunda/projects/187/views/21) for an overview of known bugs by component and severity. ::: ### Technical Changelogs for all 8.9.x releases
Overview of all patch releases and their Changelogs in GitHub [Camunda 8.9.26 (22.10.2026)](https://github.com/camunda/camunda/releases/tag/8.9.26)[Camunda 8.9.25 (20.10.2026)](https://github.com/camunda/camunda/releases/tag/8.9.25)[Camunda 8.9.24 (14.10.2026)](https://github.com/camunda/camunda/releases/tag/8.9.24)[Camunda 8.9.23 (06.10.2026)](https://github.com/camunda/camunda/releases/tag/8.9.23)[Camunda 8.9.22 (29.09.2026)](https://github.com/camunda/camunda/releases/tag/8.9.22)[Camunda 8.9.21 (17.09.2026)](https://github.com/camunda/camunda/releases/tag/8.9.21)[Camunda 8.9.19 (04.09.2026)](https://github.com/camunda/camunda/releases/tag/8.9.19)[Camunda 8.9.18 (31.08.2026)](https://github.com/camunda/camunda/releases/tag/8.9.18)[Camunda 8.9.17 (21.08.2026)](https://github.com/camunda/camunda/releases/tag/8.9.17)[Camunda 8.9.16 (14.08.2026)](https://github.com/camunda/camunda/releases/tag/8.9.16)[Camunda 8.9.15 (11.08.2026)](https://github.com/camunda/camunda/releases/tag/8.9.15)[Camunda 8.9.14 (03.08.2026)](https://github.com/camunda/camunda/releases/tag/8.9.14)[Camunda 8.9.13 (15.07.2026)](https://github.com/camunda/camunda/releases/tag/8.9.13)[Camunda 8.9.12 (07.07.2026)](https://github.com/camunda/camunda/releases/tag/8.9.12)[Camunda 8.9.11 (26.06.2026)](https://github.com/camunda/camunda/releases/tag/8.9.11)[Camunda 8.9.10 (25.06.2026)](https://github.com/camunda/camunda/releases/tag/8.9.10)[Camunda 8.9.9 (16.06.2026)](https://github.com/camunda/camunda/releases/tag/8.9.9)[Camunda 8.9.8 (10.06.2026)](https://github.com/camunda/camunda/releases/tag/8.9.8)[Camunda 8.9.7 (09.06.2026)](https://github.com/camunda/camunda/releases/tag/8.9.7)[Camunda 8.9.6 (02.06.2026)](https://github.com/camunda/camunda/releases/tag/8.9.6)[Camunda 8.9.5 (08.05.2026)](https://github.com/camunda/camunda/releases/tag/8.9.5)[Camunda 8.9.4 (06.05.2026)](https://github.com/camunda/camunda/releases/tag/8.9.4)[Camunda 8.9.3 (05.05.2026)](https://github.com/camunda/camunda/releases/tag/8.9.3)[Camunda 8.9.2 (28.04.2026)](https://github.com/camunda/camunda/releases/tag/8.9.2)[Camunda 8.9.1 (21.04.2026)](https://github.com/camunda/camunda/releases/tag/8.9.1)[Camunda 8.9.0 (07.04.2026)](https://github.com/camunda/camunda/releases/tag/8.9.0)[Connectors 8.9.17 (22.10.2026)](https://github.com/camunda/connectors/releases/tag/8.9.17)[Connectors 8.9.16 (20.10.2026)](https://github.com/camunda/connectors/releases/tag/8.9.16)[Connectors 8.9.15 (15.10.2026)](https://github.com/camunda/connectors/releases/tag/8.9.15)[Connectors 8.9.14 (28.09.2026)](https://github.com/camunda/connectors/releases/tag/8.9.14)[Connectors 8.9.13 (28.09.2026)](https://github.com/camunda/connectors/releases/tag/8.9.13)[Connectors 8.9.12 (18.09.2026)](https://github.com/camunda/connectors/releases/tag/8.9.12)[Connectors 8.9.11 (18.09.2026)](https://github.com/camunda/connectors/releases/tag/8.9.11)[Connectors 8.9.10 (04.09.2026)](https://github.com/camunda/connectors/releases/tag/8.9.10)[Connectors 8.9.9 (28.08.2026)](https://github.com/camunda/connectors/releases/tag/8.9.9)[Connectors 8.9.8 (18.08.2026)](https://github.com/camunda/connectors/releases/tag/8.9.8)[Connectors 8.9.7 (04.08.2026)](https://github.com/camunda/connectors/releases/tag/8.9.7)[Connectors 8.9.6 (26.06.2026)](https://github.com/camunda/connectors/releases/tag/8.9.6)[Connectors 8.9.5 (01.06.2026)](https://github.com/camunda/connectors/releases/tag/8.9.5)[Connectors 8.9.4 (20.05.2026)](https://github.com/camunda/connectors/releases/tag/8.9.4)[Connectors 8.9.3 (06.05.2026)](https://github.com/camunda/connectors/releases/tag/8.9.3)[Connectors 8.9.2 (30.04.2026)](https://github.com/camunda/connectors/releases/tag/8.9.2)[Connectors 8.9.1 (22.04.2026)](https://github.com/camunda/connectors/releases/tag/8.9.1)[Connectors 8.9.0 (08.04.2026)](https://github.com/camunda/connectors/releases/tag/8.9.0)
## Agentic orchestration Self-ManagedCamunda 8 RunAgentic orchestrationAI agents ### Agentic orchestration onboarding in Camunda 8 Run Camunda 8 Run now includes an "I don't have credentials" path so you can deploy and run agents without first configuring external LLM provider credentials. A new local LLM quick start also helps self-managed and enterprise-constrained teams try agentic orchestration without external provider setup. [MCP docs](/reference/mcp-docs/mcp-docs.md) [Get started with agentic orchestration](/guides/getting-started-agentic-orchestration.md) ### Orchestration Cluster MCP Server The Orchestration Cluster now includes a built-in MCP server so AI agents and MCP-compatible clients can discover and invoke Camunda tools using a standardized interface. Improvements to the MCP client include OAuth, API key, and custom-header authentication, streamable HTTP transport, and dedicated operation filters in Standalone mode. [Orchestration Cluster MCP Server](/apis-tools/orchestration-cluster-api-mcp/orchestration-cluster-api-mcp-overview.md) [MCP Client connector](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-client.md) ### A2A Client connectors Camunda 8.9 introduces A2A Client connectors for multi-agent collaboration scenarios, including polling and webhook-based response handling. These connectors let agents discover remote agents, exchange messages, and receive responses through multiple retrieval patterns. [A2A Client connectors](/components/early-access/alpha/a2a-client/a2a-client.md) ### Model provider configuration improvements AI agent model configuration in 8.9 adds AWS Bedrock API key authentication, model timeout settings, and query-parameter support for OpenAI-compatible endpoints. These updates make it easier to connect to enterprise-managed or custom model endpoints with stricter networking and authentication requirements. [AI Agent connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md) ## APIs & tools Self-ManagedSaaSOrchestration Cluster APIAgentic orchestrationAI agents ### Camunda c8ctl CLI Use the new command-line interface to interact with the Orchestration Cluster REST API directly from the terminal. Camunda c8ctl CLI is the successor to zbctl, providing a unified way to authenticate, manage processes, and query cluster resources for development, testing, and automation workflows. - Authenticate and connect to clusters, then run common actions directly from your terminal. - Supports core process management and query endpoints, including process instances, user tasks, and workers. [Camunda c8ctl CLI](/apis-tools/c8ctl/getting-started.md) ### Camunda Process Test You can now write and run JSON test cases with Camunda Process Test. - Define process tests in JSON instead of implementing test logic in Java. - Execute JSON test cases as part of standard JUnit 5 runs in local and CI/CD environments. Camunda Process Test also introduces a shared runtime mode as an alternative to managed runtime. Shared runtime uses one runtime across multiple test classes, which can significantly reduce overall test execution time. Camunda recommends enabling it in the configuration. [JSON test cases](/apis-tools/testing/json-test-cases.md) [Shared runtime configuration](/apis-tools/testing/configuration.md#shared-runtime) #### AI agent testing Camunda Process Test now adds dedicated support for AI agent testing, making it easier to test processes where agents choose different actions at runtime and produce non-deterministic output. - Judge assertions use a configured LLM to assess whether AI-generated output satisfies a natural-language expectation, so you can verify response quality and intent. - Conditional behavior helps you model reactive test behavior for agent-driven flows by watching process state changes and completing tasks as they appear, without blocking the test on a predefined path. [Test your AI agents with CPT](/components/agentic-orchestration/evaluate-agents/test-ai-agents.md) ### Camunda Docs MCP server Camunda 8.9 adds a documentation-focused MCP server so IDEs and AI tools can query the latest official Camunda documentation directly. This gives developers a lightweight way to bring product documentation into coding and support workflows without leaving their editor. [Camunda Docs MCP server](/reference/mcp-docs/mcp-docs.md) ### Camunda Spring Boot Starter - Spring Boot 4.1 support As the [Spring Boot OSS support](https://spring.io/projects/spring-boot#support) for the bundled Spring Boot 4.0.x will end in December 2026, [Spring Boot 4.1.x compatibility](/apis-tools/camunda-spring-boot-starter/getting-started.md#version-compatibility) is verified from the `8.9.12` patch onward. Spring Boot 4.0.x remains the default bundled version. ## Camunda 8 Run Self-ManagedCamunda 8 Run ### Streamlined local setup and configuration Camunda 8 Run is easier to use in 8.9 with clearer CLI guidance, improved Elasticsearch startup errors, guided Java detection, clean-state support, and more visible connection and credential details. The 8.9 release also includes the unified configuration file and expanded documentation for local development workflows. The `--docker` flag and bundled Docker Compose files have been removed. Docker Compose is now a standalone distribution artifact. C8Run now opens Operate on startup instead of the quickstart documentation page. [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) ### Flexible local secondary storage Camunda 8 Run now uses H2 as the default secondary store for simpler local development, while also supporting the full set of relational secondary storage options such as PostgreSQL, MariaDB, MySQL, Oracle, and Microsoft SQL Server. This makes it easier to start small locally and still mirror production-like database choices when needed. [Camunda 8 Run secondary storage](/self-managed/quickstart/developer-quickstart/c8run/secondary-storage.md) ## Connectivity SaaSNetworking ### Secure connectivity with AWS inbound PrivateLink You can now connect to Camunda SaaS Orchestration clusters from AWS VPCs using AWS PrivateLink. Inbound traffic stays on private AWS networking instead of traversing the public internet, so job workers and inbound connectors can run inside customer VPCs with stronger network isolation and compliance alignment. [Secure connectivity (AWS PrivateLink)](/components/saas/secure-connectivity/index.md) ## Connectors Self-ManagedSaaSConnectors ### Connector runtime and inbound activation improvements In 8.9, the connectors runtime adopts virtual threads by default for better scalability, and inbound connectors can now stay active for older process versions that still have running instances. Together, these changes improve throughput while avoiding situations where active instances of earlier versions cannot complete. ### Connector authentication and usability updates Connector improvements in 8.9 include Amazon Textract usability updates, OAuth 2.0 support for the Azure Blob Storage connector, and `noAuth` SMTP mode for the Email connector. These additions reduce custom setup work across common document, storage, and messaging integrations. [Amazon Textract connector](/components/connectors/out-of-the-box-connectors/amazon-textract.md) [Azure Blob Storage connector](/components/connectors/out-of-the-box-connectors/azure-blob-storage.md) [Email connector](/components/connectors/out-of-the-box-connectors/email-outbound.md) ## Console Self-ManagedSaaSConsole ### Cluster monitoring and backup operations Console includes a new Cluster Metrics endpoint for SaaS clusters and reduces the backup cooldown between scheduled and manual backups to 15 minutes. This makes it easier to integrate cluster monitoring into existing observability stacks while running more frequent backup routines safely. [Cluster Metrics endpoint](/components/saas/monitoring/cluster-metrics-endpoint/index.md) [SaaS backups](/components/saas/backups.md) ### Secrets, descriptions, and usage reporting Camunda 8.9 adds bulk secret import from `.env` files, cluster-to-cluster secret import and export, editable cluster descriptions, and per-tenant usage metrics for Self-Managed environments. Console also adds a new AWS US East region to expand regional deployment choices for SaaS clusters. [Connector secrets](/components/saas/clusters/manage-secrets.md) [Create a cluster](/components/saas/clusters/create-cluster.md) [Supported AWS regions](/components/saas/regions.md#amazon-web-services-aws-regions) ## Desktop Modeler Self-ManagedSaaSDesktop Modeler ### Connection management Desktop Modeler now lets you save and manage Camunda connections directly in the application, including credentials and deployment targets. This reduces deployment friction by letting you reuse saved connections instead of re-entering cluster details for each deployment. ### Clipboard and autosave improvements Desktop Modeler now supports shared clipboard behavior across BPMN tools together with quick duplication via keyboard shortcuts, and it automatically saves changes when you switch tabs or the app loses focus. These improvements make editing faster while reducing the risk of unsaved work. ## Helm chart deployment Helm chartsSelf-Managed ### Alternative infrastructure services for Self-Managed Camunda 8.9 continues to move away from bundled Bitnami infrastructure in Self-Managed Helm deployments. - In 8.9, PostgreSQL, Elasticsearch, and Keycloak sub-charts are disabled by default. - Reference architectures now use external managed services or vendor-supported operators. This update describes infrastructure deployment patterns in reference architectures, not customer data migration. [EKS dual-region reference architecture](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/dual-region.md) [OpenShift dual-region reference architecture](/self-managed/deployment/helm/cloud-providers/openshift/dual-region.md) [Dual-region operational tasks](/self-managed/deployment/helm/operational-tasks/dual-region-ops.md) ### Migration from Bitnami subcharts Camunda 8.9 adds migration guidance for customers moving Self-Managed Helm deployments away from deprecated Bitnami subcharts. - Use the migration guides to move PostgreSQL, Elasticsearch, and Keycloak from Bitnami subcharts to independent infrastructure services. - Follow the documented migration phases to plan backup, cutover, validation, and cleanup with minimal disruption. [Migrate from Bitnami subcharts](/self-managed/deployment/helm/operational-tasks/migration-from-bitnami/index.md) ### Gateway API, templating, and Helm CLI v4 support {#gateway-api-templating-and-helm-4-support} The 8.9 Helm chart adds Kubernetes Gateway API support, documents templated values in `values.yaml`, and includes guidance for Helm CLI v4 adoption. These updates make it easier to modernize Ingress, reuse dynamic values across environments, and prepare for Helm CLI v3 end of life. [Gateway API setup](/self-managed/deployment/helm/configure/ingress/gateway-api-setup.md) [Helm chart parameters](/self-managed/deployment/helm/chart-parameters.md) [Helm CLI v4](/self-managed/deployment/helm/operational-tasks/helm-v4.md) ### Secondary storage and authorization defaults Helm deployments in 8.9 now support fully integrated RDBMS secondary storage, expose the Orchestration Cluster REST port on 8080 by default, and allow authorization rules to be initialized directly from `values.yaml`. This reduces post-install manual setup and makes Helm-based deployments align more closely with current platform defaults. [Configuring secondary storage](/self-managed/concepts/secondary-storage/configuring-secondary-storage.md) ## Reference architectures Self-ManagedDeployment references Camunda 8.9 brings significant updates to the [deployment references](https://github.com/camunda/camunda-deployment-references) used by Self-Managed reference architectures. Key changes since 8.8 include new deployment options, an operator-based infrastructure model, and migration tooling. ### Operator-based infrastructure All reference architectures now use Kubernetes operators instead of embedded Bitnami subcharts for infrastructure services: - **CloudNativePG operator** replaces Bitnami PostgreSQL. - **ECK (Elastic Cloud on Kubernetes) operator** replaces Bitnami Elasticsearch. - **Keycloak operator** replaces the Bitnami Keycloak Helm subchart. This applies to AKS, EKS, OpenShift, and Kind reference architectures. [AKS reference architecture](/self-managed/deployment/helm/cloud-providers/azure/microsoft-aks/aks-helm.md) [OpenShift reference architecture](/self-managed/deployment/helm/cloud-providers/openshift/redhat-openshift.md) ### Migration from Bitnami to operators A new migration tooling set is available to move existing Self-Managed deployments from Bitnami-managed infrastructure to operator-managed infrastructure. The migration follows a 5-phase process (deploy targets, backup, cutover, validation, cleanup) with typical downtime of 5–40 minutes during the cutover phase. [Migrate from Bitnami subcharts](/self-managed/deployment/helm/operational-tasks/migration-from-bitnami/index.md) ### New reference architectures - **Amazon ECS on Fargate**: A new container-based deployment option on AWS without Kubernetes, using Elastic File System (EFS) for shared storage and restore init containers for backup/restore. - **AKS RDBMS variant**: A lighter Azure AKS reference architecture using PostgreSQL as secondary storage instead of Elasticsearch, with PostgreSQL 17 support. - **Kind local development**: A local Kubernetes-in-Docker reference architecture for development and testing, with Makefile-based commands and support for both PostgreSQL and Elasticsearch secondary storage variants. [Deploy to Amazon ECS](/self-managed/deployment/containers/cloud-providers/amazon/aws-ecs.md) ### Dual-region improvements - EKS and OpenShift dual-region reference architectures now use the ECK operator for Elasticsearch. - Headless service DNS is now used for initial contact points in dual-region setups, improving cross-region connectivity reliability. - OpenShift dual-region with ECK is now available. [EKS dual-region reference architecture](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/dual-region.md) [OpenShift dual-region reference architecture](/self-managed/deployment/helm/cloud-providers/openshift/dual-region.md) ### Additional improvements - **Generic OIDC support**: Reference architectures now use auth overlays with generic OIDC instead of Keycloak by default, enabling flexible identity provider integration. - **Ingress migration**: Kind reference architecture migrated from NGINX Ingress to Contour. - **Storage encryption**: Storage classes now support encryption by default. - **Configurable Elasticsearch index prefixes**: Custom `ES_INDEX_PREFIXES` can now be set for multi-tenant or isolated deployments. - **Helm deprecation checks**: All reference architecture CI workflows now include Helm deprecation detection for unknown keys in deployed values. - **Security**: NGINX Ingress patched to address CVEs; Artifactory authentication added for EC2 artifact downloads. ## Intelligent document processing (IDP) Self-ManagedSaaSIDP ### Classification templates and extraction engine choices Camunda 8.9 adds IDP document classification templates and lets you choose a text extraction engine per template. This gives teams more control over how different document types are categorized and processed, whether they need lightweight parsing, OCR, or multimodal LLM interpretation. ### Azure Blob Storage support for document handling Camunda 8.9 adds Azure Blob Storage as a supported document store for Self-Managed environments, alongside the existing Google Cloud Platform (GCP) and AWS S3 options. Configure it via Camunda 8 Run, Docker Compose, or Helm. [Document handling configuration](/self-managed/concepts/document-handling/configuration/index.md#supported-storage-options) ## Integrations Self-ManagedSaaSIntegrations ### Microsoft Teams app for Self-Managed The Camunda for Microsoft Teams app is now available for Self-Managed environments as well as SaaS. This lets teams claim and complete Camunda tasks directly in Microsoft Teams across more deployment models. [Camunda for Microsoft Teams](/components/camunda-integrations/app-integrations/microsoft-teams.md) ### Live task updates and assignment notifications in Microsoft Teams Camunda for Microsoft Teams now updates a notification card as its task is assigned, completed, or canceled, and notifies you when an existing task is later assigned to you. On SaaS, these capabilities require a cluster running generation `8.9 gen13` or later with **Enable app integrations extensions** turned on in the cluster settings. [Enable notification delivery for your cluster](/components/camunda-integrations/app-integrations/notification-rules.md#enable-notification-delivery-for-your-cluster) [Enable app integrations extensions](/components/saas/clusters/settings.md#enable-app-integrations-extensions) ## Migration from Camunda 7 to Camunda 8 ### Conditional events support in Migration Analyzer and Diagram Converter Migration Analyzer and Diagram Converter now detect and convert Camunda 7 conditional events (start, intermediate catch, and boundary events) into Camunda 8-compatible BPMN. The migration report also flags conditional-event follow-up tasks so teams can plan manual review where needed. [Diagram Converter](/guides/migrating-from-camunda-7/migration-tooling/diagram-converter.md) [Convert expressions](/guides/migrating-from-camunda-7/migration-tooling/diagram-converter.md#convert-expressions) ### History Data Migrator Data Migrator now supports migrating historic audit data from Camunda 7 to Camunda 8. [History Data Migrator](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/history.md) ### Audit log migration coverage Camunda 8.9 expands migration support by converting Camunda 7 `UserOperationLog` entries into the Camunda 8 audit log format. This helps preserve user-operation history for compliance and operational review as part of a broader migration program. [History migration coverage](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/limitations.md#camunda-8-history-migration-coverage) ### Camunda 8 conditional events support Camunda 8.9 adds support for BPMN conditional events in the platform itself, complementing the new migration-tooling support for detecting and converting them. This makes migration planning easier because the modeled behavior and the migration path are both available in the same minor release. [Conditional events](/components/modeler/bpmn/conditional-events/conditional-events.md) ## Operate Self-ManagedSaaSOperate ### Dashboard focuses on active processes The Operate dashboard now only displays processes with active instances. Processes without running or incident instances are no longer shown, providing a clean view of what is currently running in your cluster. ### Unified, context-aware process instance view Operate now unifies process instance information into a single, context-aware view. Instead of switching between diagram, instance history, popovers, and bottom panels, you can inspect incidents, variables, executions, and modeling properties in one overview-to-detail flow. ### Batch operations monitoring Operate now includes a dedicated Batch Operations view backed by the Orchestration Cluster API. This gives operators a centralized way to track progress, review item-level results, and control running batch operations. [Monitor batch operations](/components/operate/userguide/monitor-batch-operations.md) ## Orchestration Cluster Self-ManagedSaaSJobs ### Job Dashboard The Job Dashboard provides a unified view of jobs and associated workers across the cluster, including Connectors and RPA. Use it to monitor job creation, completion trends, failures, and worker activity for faster troubleshooting without custom-built monitoring dashboards. ### Cluster variables and process governance Camunda 8.9 adds cluster variables, Business ID support for process instances, and broader task and listener management capabilities. These additions make it easier to centralize runtime configuration, enforce idempotent process starts, manage global user task listeners, and apply finer-grained task permissions. [Cluster variables](/components/modeler/feel/cluster-variable/overview.md) [Global user task listeners](/components/concepts/global-user-task-listeners.md) ### Platform configuration and deployment model updates The Orchestration Cluster continues its 8.9 consolidation work with completed unified configuration property changes, gateway-served application profiles for Operate, Tasklist, and Identity, and declarative preconfiguration support for Identity entities. Self-Managed deployments also gain support for Amazon ECS and improved dynamic tenant access for machine clients. [Property changes in Camunda 8.9](/versioned_docs/version-8.9/self-managed/components/orchestration-cluster/core-settings/configuration/configuration-mapping.md) [Deploy to Amazon ECS](/self-managed/deployment/containers/cloud-providers/amazon/aws-ecs.md) ### Storage, backup, and runtime improvements Orchestration Cluster enhancements in 8.9 include scheduled backups, Elasticsearch 9 support, Amazon Aurora as secondary storage, per-broker RocksDB memory sizing, and the unified RocksDB cache model introduced in the first alpha. Together, these changes improve backup automation, storage flexibility, and operational tuning for Self-Managed deployments. [Scheduled backup](/self-managed/operational-guides/backup-restore/rdbms/backup.md#scheduled-backup) [Zeebe memory allocation](/components/best-practices/architecture/sizing-self-managed.md#memory) ### Archive by ID for Elasticsearch and OpenSearch Archiving of finished process instance data in Elasticsearch and OpenSearch secondary storage now uses a targeted, incremental approach by default. Documents are moved in small, targeted batches rather than in a single operation, improving stability and reducing resource pressure during archiving. The `rolloverBatchSize` and `reindexBatchSize` properties control how many process instances and individual documents are processed per batch. [Data retention](/self-managed/components/orchestration-cluster/core-settings/concepts/data-retention.md) ### Process and data management improvements Camunda 8.9 adds support for deleting process and decision data, retaining hierarchy data until root-instance deletion, modifying elements inside multi-instance ad-hoc subprocesses, and updating cluster variables directly in the UI. The release also introduces a centralized user operations audit log and expands audit log access across platform APIs and applications. [Process instance modification](/components/concepts/process-instance-modification.md) [Audit log](/components/audit-log/overview.md) ### Tasklist V1 OpenSearch compatibility OpenSearch 3.6.0 is not supported when Tasklist V1 is enabled. Please use OpenSearch 2.19+ or versions 3.4.x–3.5.x, or migrate to Tasklist V2. :::note Tasklist V1 is planned for removal in version 8.10, so we strongly recommend migrating to Tasklist V2 to avoid future compatibility issues. ::: ## RDBMS secondary storage Self-ManagedData ### Expanded database coverage and driver handling Across the 8.9 release cycle, RDBMS secondary storage expands from early support for H2, PostgreSQL, Oracle, and MariaDB to include MySQL, Microsoft SQL Server, and Amazon Aurora. Camunda 8.9 also introduces standardized JDBC driver handling for manual installations so teams can separate bundled and customer-supplied drivers more cleanly. [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) ### Helm, manual, and production deployment guidance Camunda 8.9 adds end-to-end deployment guidance for RDBMS-backed environments, including Helm configuration, manual installation, production installation guides, SQL and Liquibase scripts, and a dedicated setup guide spanning the Orchestration Cluster and Web Modeler. Web Modeler also gains relational database support that aligns with the broader Orchestration Cluster options. [RDBMS setup guide](/self-managed/concepts/databases/relational-db/rdbms-setup-guide.md) [Manual installation with RDBMS](/self-managed/deployment/manual/rdbms/index.md) [RDBMS Helm deployment](/self-managed/deployment/helm/install/helm-with-rdbms.md) ### Backup, restore, and analytics patterns When using RDBMS secondary storage, Camunda 8.9 adds continuous backup and restore support and documents how to combine an RDBMS-backed Orchestration Cluster with Optimize when analytics require Elasticsearch or OpenSearch. This gives teams a clearer path for disaster recovery and mixed operational versus analytics storage strategies. [Back up and restore](/self-managed/operational-guides/backup-restore/backup-and-restore.md) ## Robotic Process Automation (RPA) Self-ManagedSaaSRPAWeb Modeler ### RPA support in Web Modeler Web Modeler now supports RPA scripts as a first-class file type, including visibility, project management, and deployment workflows without relying on Desktop Modeler. [Get started with RPA](/components/rpa/getting-started.md) ## Web Modeler Self-ManagedSaaSWeb Modeler ### Unified deployment experience across file types in Web Modeler Web Modeler now offers a unified deployment experience across BPMN, DMN, forms, and RPA scripts in process applications. - Deploy any BPMN, DMN, form, or RPA script inside a process application directly from its canvas view. - Deploy and run any BPMN file directly with full flexibility, as the main process label and its restrictions have been removed. - See more clearly which linked files are included when deploying within a process application. - Choose whether to deploy the current file or the applicable process application bundle, depending on context. This update reduces hidden deployment behavior, lowers cognitive load, and makes deployment workflows more predictable and extensible for future asset types. [Process applications in Web Modeler](/components/hub/workspace/manage-projects/manage-projects.md) [Validate and deploy your process application](/components/hub/workspace/manage-projects/deploy-project.md) ### Collaboration and import improvements Web Modeler in 8.9 adds more reliable live collaboration with canvas locking and takeover behavior, and it supports importing large process applications with up to 100 supported files in a single step. These changes improve shared editing workflows and make it easier to bring existing assets into Modeler. [Collaborate in Web Modeler](/components/hub/workspace/modeler/collaboration/collaboration.md) [Import resources into Web Modeler](/components/hub/workspace/modeler/modeling/importing-resources.md) ### Modeling and administration improvements Camunda 8.9 expands Web Modeler with broader event template support, email-based project invitations for all supported OIDC providers, and a simpler Self-Managed system architecture by folding the former `webapp` component into `restapi`. Additional 8.9 updates include embedded Tomcat for Self-Managed deployments and a SaaS endpoint for monitoring Web Modeler egress IP ranges. [Element templates in Modeler](/components/modeler/element-templates/defining-templates.md) [Add users to projects](/components/hub/organization/manage-workspaces/manage-workspace-members.md#add-members) [Embedded web server](/versioned_docs/version-8.9/self-managed/upgrade/components/880-to-890.md#embedded-web-server) ## 8.9.0-alpha5 | Release date | Changelog(s) | | :------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 10 March 2026 | [ Camunda 8 core ](https://github.com/camunda/camunda/releases/tag/8.9.0-alpha5)[ Connectors ](https://github.com/camunda/connectors/releases/tag/8.9.0-alpha5) | ### Agentic orchestration Self-ManagedSaaSAgentic orchestrationAI agents #### Orchestration Cluster MCP support The Orchestration Cluster now exposes its operational capabilities via a built-in Model Context Protocol (MCP) server, enabling AI agents and LLM-powered applications to access Camunda data using a standardized interface. - Connect any MCP-compliant client (such as VS Code, GitHub Copilot, or Claude Code) to discover and invoke Camunda tools without custom API integration code. - Available tools cover process definitions, process instances, user tasks, incidents, and variables. - Streamable HTTP transport is supported, using the same authentication model as the REST API. - The MCP server is enabled by default in Camunda 8 Run. You can enable the MCP server in Camunda SaaS via your cluster settings in Console. [Orchestration Cluster MCP Server](/apis-tools/orchestration-cluster-api-mcp/orchestration-cluster-api-mcp-overview.md) ### Audit log Self-ManagedSaaSOperateTasklistIdentity You can use the new user operations audit log to access a record of operations, including who performed an operation, when it was performed, and on which entities the operation was performed. Use the audit log to: - Prove compliance - Meet governance and regulatory requirements - Maintain operational integrity and transparency - Troubleshoot issues This feature is available in Operate, Identity, Tasklist, and the [Camunda 8 REST API](/apis-tools/orchestration-cluster-api-rest/specifications/search-audit-logs.api.mdx). [Audit log](/components/audit-log/overview.md) ### BPMN conditional events Self-ManagedSaaSBPMN Camunda 8 now supports BPMN conditional events, allowing users to start, continue, or interrupt process execution dynamically based on evaluated conditions. This enhancement provides first-class support for conditional start, boundary, and intermediate catch events, making process automation more expressive and migration from Camunda 7 smoother. [Conditional events](/components/modeler/bpmn/conditional-events/conditional-events.md) ### Business ID Self-ManagedSaaSProcess instances You can now create a process instance with a Business ID. - A Business ID is an immutable string identifier that associates a process instance with a meaningful business entity, such as an order ID, claim number, or customer reference. - Business ID uniqueness validation can be enabled at the cluster level to enforce idempotent process starts. If enabled, only one running root process instance per process definition can have the same Business ID. Attempts to start a duplicate instance are deterministically rejected. - Once visibility features are enabled in a future release, you will be able to trace process instances by Business ID retroactively at the process level. ### Camunda 8 Run Self-ManagedCamunda 8 Run #### Expanded RDBMS support You can now configure Camunda 8 Run to use any of the supported secondary storage relational databases instead of the default H2 (for example, PostgreSQL, MariaDB, MySQL, Oracle, or Microsoft SQL Server). This allows you to set up your local environment to match your production deployment, enabling deeper testing, faster debugging, and easier team onboarding. [Camunda 8 Run external relational database options](/self-managed/quickstart/developer-quickstart/c8run/secondary-storage.md#external-relational-database-options) ### Console SaaSConsole #### Reduce manual backup cooldown in SaaS The cooldown time between manual and scheduled backups in SaaS is reduced to 15 minutes. You can now safely combine frequent scheduled backups with additional on‑demand manual backups (for example, hourly scheduled backups plus a daily manual backup) without hitting unexpected rate‑limit conflicts, while still benefiting from separate retention of manual and scheduled backups. [SaaS backups](/components/saas/backups.md) ### Deployment Self-ManagedConfigurationHelmDocker #### ARM Docker production support You can now use the `linux/arm64` image for Docker production environments. [Camunda Docker images](/self-managed/deployment/docker/docker.md) #### Connect to Elastic/OpenSearch with multi-host names You can now configure multiple Elasticsearch or OpenSearch host URLs for Camunda 8 components, removing the requirement for a single endpoint or load balancer in front of your search cluster. - This enables seamless connectivity in environments where managed Elasticsearch or OpenSearch providers supply a list of host URLs rather than a single FQDN. - Multi-host configuration is supported for Zeebe, Operate, Tasklist, Optimize, and Integration components, and can be set via Helm chart values or application configuration. [Property reference](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#data---secondary-storage) #### Proxy support for Elasticsearch and AWS OpenSearch You can now configure outbound proxy settings in Camunda 8 components to connect to Elastic, OpenSearch (AWS OpenSearch). - This enables seamless integration with AWS-hosted OpenSearch services in environments where proxy routing is required (for example, corporate networks or regulated production setups). - This feature is supported for Zeebe, Operate, Tasklist, Optimize, and Integration components. [Property reference](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#data---secondary-storage) #### Gateway API support The Kubernetes Gateway API is now supported in the official Helm chart, allowing you to expose Operate, Tasklist, Console, Web Modeler, Identity, Optimize, and Zeebe (REST and gRPC) using Gateway and Route resources instead of relying on ingress-nginx. You can use new Helm values to choose between Ingress and Gateway per deployment, with documented step-by-step examples and migration guidance so you can move away from ingress-nginx before it is retired in March 2026. [Configure the Helm chart with Gateway API](/self-managed/deployment/helm/configure/ingress/gateway-api-setup.md) #### Helm chart values templating The Helm chart now documents all values supporting Go template expressions, including guidance on how `values.yaml` templating is evaluated. - `podLabels`, `podAnnotations` (all components), and `global.ingress.host` now support templating via `{{ "{{" }} }}` expressions (for example, `{{ "{{" }} .Release.Name }}`). - This feature enables dynamic configuration for multi-environment deployments and integrations, such as Datadog APM. [Helm chart parameters](/self-managed/deployment/helm/chart-parameters.md) #### Helm CLI v4 support {#helm-4-support} Camunda 8.9 (chart 14.x) supports Helm CLI v3 and v4. Camunda recommends Helm CLI v4 and supports it for the full release cycles of Camunda 8.9 and 8.10. Camunda supports Helm CLI v3 (3.10 or later) until February 10, 2027, when upstream support ends. After February 10, 2027, Camunda no longer supports Helm CLI v3. Customers who continue to use Helm CLI v3 after that date do so at their own risk. For Helm CLI v4 behavior changes and workarounds, see [Helm CLI v4](/self-managed/deployment/helm/operational-tasks/helm-v4.md). ### Global user task listeners Self-ManagedSaaSListeners You can now use a new Orchestration Cluster API to manage global user task listeners. - The API is available in both SaaS and Self Managed environments and supports full lifecycle management of listener definitions. - Listener execution follows the same semantics as existing global listener behavior, ensuring consistent payloads, predictable integrations, and uniform governance across environments. - You can configure global user task listeners via [unified configuration](/components/concepts/global-user-task-listeners/configuration.md#configure-through-unified-configuration), [Orchestration Cluster API](/components/concepts/global-user-task-listeners/configuration.md#configure-via-orchestration-cluster-api), or the [Admin UI](/components/concepts/global-user-task-listeners/configuration.md#configure-via-admin-ui). [Global User task Listeners](/components/concepts/global-user-task-listeners.md) :::note Configuration-based global user task listeners were introduced for Self-Managed deployments in alpha 3. ::: ### Intelligent Document Processing (IDP) Self-ManagedSaaSIDP #### IDP document classification templates You can now use document classification templates to automatically categorize incoming documents by type (for example, invoice, contract, or claim form) using LLM-powered analysis. - Configure classification templates directly in the UI without writing code. - Define an explicit list of expected document types, or use auto-classification to let the LLM suggest document types. - Customize the system prompt used for classification to fine-tune results for your organization. - Test templates with sample documents across different LLMs to evaluate accuracy before publishing. - Classification results are output as structured data, making them immediately usable in your BPMN process for routing and downstream automation. #### IDP multiple text extraction engines You can now choose the text extraction engine on a per-template basis, allowing you to optimize accuracy, performance, and cost for different document types. - Use lightweight parsing for digitally generated PDFs to reduce processing time and costs. - Select a high-accuracy OCR engine for scanned or image-based documents. - Bypass text extraction entirely to let multimodal LLMs natively interpret document content. ### Integrations Self-ManagedIntegrations The Camunda for Microsoft Teams app is now available in Self-Managed environments as well as SaaS. You can use this app to view, claim, and complete Camunda tasks directly in Microsoft Teams. [Camunda for Microsoft Teams](/components/camunda-integrations/app-integrations/microsoft-teams.md) ### Modeler Self-ManagedSaaSDesktop ModelerWeb Modeler #### Process application subfolders You can now add subfolders to your process applications, giving you more flexibility when organizing your files and allowing you to sync to your existing version control system without reorganizing the filesystem. [Process applications](/components/concepts/projects.md) #### Web Modeler: Improved Self-Managed installation The Web Modeler system architecture has been simplified to enable easier and smoother installation and configuration of Web Modeler in a Self-Managed deployment. The separate `webapp` component has been removed and its functionality is now completely integrated into the `restapi` component. ### Orchestration Cluster Self-ManagedSaaSOperateTasklist :::caution Bug: process instance distribution A [critical bug in 8.9-alpha5](https://github.com/camunda/camunda/issues/47955) causes uneven distribution of newly created process instances amongst partitions, with partition 1 being heavily favoured. As a result, clusters cannot handle as high a throughput as normal. This bug is fixed in the 8.9.0 release. ::: #### Application profile consolidation The Operate, Tasklist, and Identity application profiles are now merged into the existing gateway profile to provide a simplified but flexible deployment model. - These components are now treated as UIs served by the Zeebe Gateway. - Control their inclusion via configuration properties (for example, `camunda.webapps.enabled=operate,identity`). #### Amazon ECS (EC2+Fargate) support Camunda 8 officially supports running Orchestration Clusters on Amazon Elastic Container Service (Amazon ECS). This makes it easier and safer for teams that rely on Amazon ECS (including Fargate) to run Camunda 8 in production without needing to adopt Kubernetes (EKS). This feature relies on AWS S3. - A new guide covers validated deployment patterns for running brokers and related services on Amazon ECS with both EC2 and Fargate launch types. - Build-in storage safety guardrails, such as checks and guidance to prevent unsafe configurations (for example, multiple brokers writing to the same EFS volume). - Cluster membership handling offers improved handling of broker restarts and Amazon ECS task scheduling to ensure stable cluster operation. [Deploy to Amazon ECS](/self-managed/deployment/containers/cloud-providers/amazon/aws-ecs.md) #### Configure RocksDB memory per-broker You can now configure RocksDB memory on a per-broker basis instead of per-partition, simplifying capacity planning and aligning with familiar JVM-style sizing. - This reduces the risk of unexpected out-of-memory crashes, and is crucial for usage with dynamically adding partitions, making broker provisioning safer and more predictable. Going forward, Camunda recommends you use the `FRACTION` allocation strategy. - The default remains per-partition for backwards compatibility, but this is deprecated and will be changed to a fractional approach in 8.10, with a 10% default (using `camunda.data.primary-storage.rocks-db.memory-allocation-strategy=FRACTION` and `camunda.data.primary-storage.rocks-db.memory-fraction=0.1`). - Camunda recommends you test this out before 8.10 to find the right value, or configure the allocation strategy to `PARTITION`. [Zeebe memory allocation](/components/best-practices/architecture/sizing-self-managed.md#memory) #### Delete processes and decisions (instances and definitions) The Orchestration Cluster API and Operate UI now support secure, consistent deletion of process and decision definitions, as well as batch or individual deletion of finished process and decision instances (including all dependent data) from both primary and secondary storage. This feature ensures compliance, data consistency across regions, and simplified operational management of obsolete process data. #### Dynamic connector access to tenants in multi-tenant environments (Self-Managed) Use the new dynamic client access to tenants `TenantFilter` setting to define tenant access by explicit tenant assignment instead of defining access in the machine client’s configuration. If you use this setting, you no longer need to restart the associated run-time environment when adding new machine clients (such as job workers or connectors). [Java client: Filter by tenant](/apis-tools/java-client/job-worker.md#filtering-by-assigned-tenants) / [Spring Boot: Filter by tenant](/apis-tools/camunda-spring-boot-starter/configuration.md#filtering-by-assigned-tenants) #### Elasticsearch 9 support Camunda 8 now supports Elasticsearch 9 as a secondary data store, allowing you to take advantage of the latest Elasticsearch release for your deployments. - Elasticsearch 9 is a direct 1:1 compatibility update with no changes to Camunda APIs or query behavior. - You can plan your migration to Elasticsearch 9 and benefit from its improved performance and updated features while maintaining full compatibility with Camunda 8 components. - Elasticsearch 8.x continues to be supported for existing deployments. [Supported environments](/reference/supported-environments.md) #### Manage task permissions Granular task-level authorization is now integrated into the Tasklist UI and the Orchestration Cluster REST API. - Property-based task permissions: - Grant users permission to view or work on a task, based on task properties. - Permissions apply when the assignee matches the current user, or when the user belongs to a candidate group (or is listed as a candidate user). This ensures all relevant users have appropriate access, whether directly assigned or eligible to claim the task. - Permissions apply consistently across both the Tasklist UI and the orchestration cluster REST API. - Fine-grained security: Visibility and action permissions are scoped at the individual task level, reducing unauthorized access and improving compliance alignment. This feature strengthens security and usability, and provides a clear, consistent, and secure user experience for task workers, managers, and integrations. [Configure user task authorization](/components/tasklist/user-task-authorization.md) #### Modify elements in multi-instance ad-hoc sub-processes Operators can now dynamically activate, move, or remove element instances inside running multi-instance ad-hoc subprocesses—supporting parallel, sequential, and classic ad-hoc execution patterns. These new runtime capabilities, available in both Operate UI and the API, allow users to adapt, repair, and recover business processes on the fly, supporting flexibility for agentic automation, case management, and critical operations. [Process instance modification](/components/concepts/process-instance-modification.md) #### Monitor batch operations Operate now includes a dedicated Batch Operations view powered by the Orchestration Cluster API. - Users can search and filter batches, view progress and per-item results (including errors), and manage running batches via suspend, resume, and cancel actions. - This capability, aligned with Zeebe-managed batch operations introduced in 8.8, brings consistent and transparent monitoring across distributed partitions for improved operational control. [Monitor batch operations](/components/operate/userguide/monitor-batch-operations.md) #### Operate uses the Orchestration Cluster REST API Operate now uses the Orchestration Cluster REST API as its single interface for accessing and managing process data. This ensures a consistent Operate experience across OpenSearch, Elasticsearch, and RDBMS. You can continue to investigate, manage, and automate process operations without any loss of functionality while also reusing the same REST endpoints for custom tools and integrations. - Operate now uses the Orchestration Cluster REST API as the single backend interface. - The frontend is fully aligned with the Orchestration Cluster REST API (V2) across all supported data stores. - The V2 API has been extended with new endpoints to support Operate functionality. - Batch modification and monitoring behavior is updated to work with the new API. - Proactive permission checks in the UI have been removed: UI elements are now always visible, and permission errors are shown only when an action is attempted. [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) #### Preconfigure Identity entities You can now use declarative configuration for all Identity entities in the Orchestration Cluster, such as groups, tenants, roles, authorizations, and assignments. Previously, you could only use this for users, mapping rules, and default role memberships. #### Retention policy across hierarchy When deleting process instances, all child process and decision instances are retained until the root process instance is deleted. - This feature ensures that audit trails remain intact for the entire process hierarchy. - Even if a called process completes significantly earlier, its data remains available until the parent instance completes and expires under retention policy. - This guarantees continuous visibility and compliance-grade auditability for hierarchical workflows. [Hierarchy-aware data retention](/self-managed/components/orchestration-cluster/core-settings/concepts/data-retention.md#hierarchy-aware-retention) #### Schedule backups with the Orchestration Cluster You can now configure scheduled backup intervals and retention directly in the Orchestration Cluster. - External cron jobs are no longer needed. - Supports setting duration schedules, manual ad‑hoc backups, API‑based updates, metrics, and audit logs. - Backwards compatible with existing backup commands. [Scheduled backup (RDBMS)](/self-managed/operational-guides/backup-restore/rdbms/backup.md#scheduled-backup) #### Update cluster variables You can now update existing cluster variables in the Orchestration Cluster user interface, without needing to delete and recreate the variable. #### User operations audit log A new centralized, queryable audit log records all critical user and client operations across process, identity, and user task domains. - Teams can trace who performed each action and when, what was affected, and if the action was successful. - Audit entries are available via Orchestration Cluster APIs, and integrated into Operate, Tasklist, and Identity with built-in authorization controls. :::note See the [Audit log](#audit-log) entry for more information. ::: ### Process instance migration Self-ManagedSaaS #### Migrate from job-based user tasks to Camunda User Tasks As part of process instance migration, you can now migrate active instances from legacy job‑based user tasks to modern, engine‑managed Camunda User Tasks via both the API and the Operate UI. This lets you standardize on the Orchestration Cluster APIs and the recommended user task type before the removal of job‑based user task querying and management in the consolidated API. [Process instance migration](/components/concepts/process-instance-migration.md) ### RDBMS secondary storage Self-ManagedData #### Installation guide for RDBMS (Orchestration Cluster and Web Modeler) A new installation guide covers how you can configure Camunda 8 with relational databases across the Orchestration Cluster and Web Modeler. The guide covers database provisioning, connections and authentication including Aurora IAM, JDBC driver handling, optional schema management with SQL or Liquibase, and backup and restore considerations so teams can deploy faster with fewer errors and aligned best practices. [RDBMS setup guide](/self-managed/concepts/databases/relational-db/rdbms-setup-guide.md) #### Manual installation supports RDBMS secondary storage Camunda 8 Orchestration Clusters can now be installed manually (VM/bare metal/Java application) with full support for RDBMS (H2, PostgreSQL, Oracle, MariaDB) as secondary storage. [Manual installation with RDBMS](/self-managed/deployment/manual/rdbms/index.md) #### Production installation guides New RDBMS production installation guides for Camunda 8 are available: - Helm: Kubernetes-based orchestration cluster deployment via Helm, using RDBMS secondary storage. - Manual: Deploy and manage Camunda 8 using relational databases in production environments. [RDBMS Helm deployment](/self-managed/deployment/helm/install/helm-with-rdbms.md) #### Run Optimize with Orchestration Cluster RDBMS Orchestration Clusters can run with RDBMS as the sole secondary data store (for straightforward environments) or in combination with Optimize (which requires Elasticsearch or OpenSearch for analytics use cases). Administrators can: - Deploy a simple stack using only RDBMS if analytics are not required. - Add Optimize with Elasticsearch/OpenSearch for teams that require reporting and analytics. - Rely on strict separation of process data ingestion and storage responsibilities between orchestration and analytics. ## 8.9.0-alpha4 | Release date | Changelog(s) | | :--------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 10 February 2026 | [ Camunda 8 core ](https://github.com/camunda/camunda/releases/tag/8.9.0-alpha4)[ Connectors ](https://github.com/camunda/connectors/releases/tag/8.9.0-alpha4) | ### Agentic orchestration Self-ManagedSaaSAgentic orchestrationAI agentsConnectors #### MCP Client connector operations and filter options New operations are added in the `Standalone` mode to the MCP (Model Context Protocol) Client connector: - List resources - List resource templates - Read resource - List prompts - Get prompt In addition, the **Tools** panel in the element template is replaced by dedicated filter options for each operation. :::caution Upgrading the template - The **Tools** section in version 0 and 1 of the template is replaced by a **Filters** section in version 2. - As Tool filters are not migrated during a Template Version upgrade, you must manually upgrade the template with any previously configured Tool filters. ::: [MCP Client connector](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-client.md) ### Camunda 8 Run Self-ManagedCamunda 8 Run #### Expanded RDBMS support You can now configure Camunda 8 Run to use any of the supported secondary storage relational databases instead of the default H2. This allows you to set up your local environment to match your production deployments, enabling deeper testing, faster debugging, and easier team onboarding. #### Streamline your Camunda 8 Run experience Camunda 8 Run is now easier to use with improved setup and configuration. The CLI includes a helpful usage page, clearer error messages, especially for Elasticsearch startup, and prominently displays connection properties and credential information. A revamped Java detection guided setup, log cleanup options, and better defaults for development environments (such as disk watermark thresholds) have been added. You can also start fresh using a new clean-state command, and the unified configuration file is now included and thoroughly documented. [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) ### Connectors Self-ManagedSaaSConnectors #### Fetch active process definitions The connectors runtime now activates inbound connectors for earlier process versions if they have active instances waiting on message subscriptions. Previously, only inbound connectors from the latest process version were activated, which could prevent active instances of older versions from completing. ### Console SaaSConsole #### New AWS US East region With the new Camunda 8 SaaS **AWS US East (us-east-2)** region in North America, you can deploy orchestration workloads with full US data residency and improved regional stability. [Supported AWS regions](/components/saas/regions.md#amazon-web-services-aws-regions) ### Documentation Self-ManagedSaaSDocumentation #### Camunda Docs MCP server Use the Camunda Docs MCP server to query the latest official Camunda 8 documentation in your IDE or AI tool. - If you use an AI coding tool such as Cursor or Copilot, add the MCP server to help ensure more accurate AI responses and code generation using Camunda 8 documentation and context. - Once connected to the MCP server within your editor, you can ask context-aware questions about Camunda. - The MCP server is available at the following URL: `https://camunda-docs.mcp.kapa.ai`. [Camunda Docs MCP server](/reference/mcp-docs/mcp-docs.md) ### Modeler Self-ManagedSaaSDesktop ModelerWeb Modeler #### Collaborate in Web Modeler Live collaboration in Web Modeler is now more reliable with an improved collaboration experience. Once you start editing a diagram, the canvas locks so only you can continue making edits to the diagram. - Other users can view and interact with the diagram, but cannot make changes while it is locked by the current editor. - Users with edit permissions can take over editing of the diagram by clicking **Take over** in the canvas lock bar. This improvement creates a restricted but controlled collaborative environment, and helps prevent conflicts and broken sessions caused by multiple users editing the same diagram. [Collaborate in Web Modeler](/components/hub/workspace/modeler/collaboration/collaboration.md) #### Import large process applications into Web Modeler You can now import large process applications (containing a maximum of 100 supported files such as BPMN, DMN, forms, connector templates, and documentation) in a single step in Web Modeler or via direct import links in Self-Managed environments. [Import resources into Web Modeler](/components/hub/workspace/modeler/modeling/importing-resources.md) #### Shared global clipboard and duplicate elements In Desktop Modeler and Web Modeler, you can copy-paste BPMN elements seamlessly between Camunda 7/8 diagrams and across clipboard-enabled BPMN tools/websites, and also quickly duplicate via **Cmd+D** (Mac) / **Ctrl+D** (Windows). #### Tabs autosave in Desktop Modeler The tabs autosave feature automatically saves diagram changes when you switch tabs in Desktop Modeler or when the app loses focus (for example, app switch, window blur). This ensures work is saved immediately, making it visible to your IDE and other tools without manual intervention. ### Migration from Camunda 7 to Camunda 8 Self-ManagedSaaSCamunda 7 migration #### Audit log coverage for Camunda 7 to Camunda 8 migrations The audit log migrator automatically converts Camunda 7 `UserOperationLog` entries to Camunda 8 AuditLog format, preserving the complete history of user operations including who performed actions, what entities were affected (process instances, tasks, variables, decisions), operation types (create, update, delete, assign, complete), timestamps, and annotations. This ensures uninterrupted audit trail continuity across the migration, enabling customers to meet compliance requirements and maintain operational visibility without manual data reconstruction or workarounds. [Data Migrator history migration coverage](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/limitations.md#camunda-8-history-migration-coverage) ### Orchestration Cluster Self-ManagedSaaSData #### Schedule backups with the Orchestration Cluster You can now configure scheduled backup intervals and retention directly in the Orchestration Cluster. - External cron jobs are no longer needed. - Supports setting duration schedules, manual ad‑hoc backups, API‑based updates, metrics, and audit logs. - Backwards compatible with existing backup commands. #### Unified configuration for the Orchestration Cluster In Camunda 8.9, the remaining unified configuration project property changes are complete. - All 8.9 property changes are documented in the [Camunda 8.9 property changes](/versioned_docs/version-8.9/self-managed/components/orchestration-cluster/core-settings/configuration/configuration-mapping.md#camunda-89-property-changes) table. - You can search, sort, and filter the table to show breaking changes, direct mappings, and new properties. - For more information on each property (including default values), see the [property reference](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md). [Property changes in Camunda 8.9](/versioned_docs/version-8.9/self-managed/components/orchestration-cluster/core-settings/configuration/configuration-mapping.md) :::note Only the first partial set of the unified configuration project properties was introduced in Camunda 8.8. ::: ### Process instance migration Self-ManagedSaaS #### Migrate from job-based user tasks to Camunda User Tasks As part of process instance migration, you can now migrate active instances from legacy job‑based user tasks to modern, engine‑managed Camunda User Tasks via both the API and the Operate UI. This lets you standardize on the Orchestration Cluster APIs and the recommended user task type before the removal of job‑based user task querying and management in the consolidated API. [Process instance migration](/components/concepts/process-instance-migration.md#migrate-job-worker-user-tasks-to-camunda-user-tasks) ### RDBMS secondary storage Self-ManagedData #### Continuous backup and restore You can now back up and restore Camunda 8 when using RDBMS as secondary storage. - Independent backup control plans handle primary and secondary backups separately while ensuring they align when restored. - Disaster recovery is improved as you can recover Camunda instances with greater precision in data consistency. Additionally, the new restore API syntax now supports `--from` and `--to` timestamp flags, enabling automatic selection of a compatible backup range. - When no specific backup or timerange is specified, a restore is performed to the latest known position with no user interaction. - Ensures version compatibility across backups and offers an override via `--allow-version-mismatch`. - Reduces manual restore effort and enhances confidence in backup integrity, with reduced Recovery Time Objective (RTO). [Back up and restore](/self-managed/operational-guides/backup-restore/backup-and-restore.md) ### Helm chart deployment Self-ManagedHelm chart #### Default REST port unified to 8080 The Orchestration Cluster's default HTTP port in the Helm chart has changed from 8090 to 8080 (`orchestration.service.httpPort`), aligning with the Orchestration Cluster's default configuration. #### Authorization initialization via Helm You can now initialize authorization rules directly through `values.yaml`, reducing manual post-installation setup for access control configuration. ## 8.9.0-alpha3 | Release date | Changelog(s) | | :-------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 13 January 2026 | [ Camunda 8 core ](https://github.com/camunda/camunda/releases/tag/8.9.0-alpha3)[ Connectors ](https://github.com/camunda/connectors/releases/tag/8.9.0-alpha3) | ### Agentic orchestration Self-ManagedSaaSAgentic orchestrationAI agentsConnectors #### AWS Bedrock API key authentication support The Amazon Bedrock model configuration now allows authentication using (long-term) Bedrock API keys as an alternative to the already existing authentication methods. #### Model timeout configuration The AI Agent connectors now support setting a timeout value on supported models. #### Query parameters support on OpenAI compatible models The OpenAI compatible model configuration now allows configuration of query parameters to be added to the model endpoint URL. This might be needed for custom API endpoints requiring additional metadata (such as API versions) to be set via query parameters. [AI Agent connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md) ### Camunda 8 Run Self-ManagedCamunda 8 Run #### Streamline your Camunda 8 Run experience Camunda 8 Run is now easier to use with improved setup and configuration. The CLI includes a helpful usage page, clearer error messages, especially for Elasticsearch startup, and prominently displays connection properties and credential information. A revamped Java detection guided setup, log cleanup options, and better defaults for development environments (such as disk watermark thresholds) have been added. You can also start fresh using a new clean-state command, and the unified configuration file is now included and thoroughly documented. [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) #### Use H2 for data storage Camunda 8 Run now includes H2 as the default secondary data store, providing: - A lighter, simpler local development experience. - Lower memory usage. - A fully functional stack that doesn't require an external database. New documentation shows you how to: - Install Camunda 8 Run with H2 as the default secondary storage. - Seamlessly switch from H2 to Elasticsearch or OpenSearch when required. ### Console SaaSConsole Camunda 8.9 provides a Cluster Metrics endpoint for SaaS clusters. - Activate a secure metrics endpoint for your cluster and integrate it with Prometheus, Datadog, or any monitoring system that supports Prometheus scraping. - Get real-time visibility into cluster performance, troubleshoot faster, and integrate with your existing observability stack. [Cluster Metrics endpoint](/components/saas/monitoring/cluster-metrics-endpoint/index.md) ### Global user task listeners Self-Managed Camunda 8.9 introduces configuration-based global user task listeners for Self-Managed deployments. Administrators can define cluster-wide listeners using configuration files or environment variables, ensuring they are applied consistently from cluster startup and preserved across backup and restore operations. All user task lifecycle events emit payloads containing full variable context and metadata, enabling standardized integrations across all processes. [Global User task Listeners](/components/concepts/global-user-task-listeners.md) ### Modeler Self-ManagedSaaSDesktop ModelerWeb Modeler #### Desktop Modeler: Manage Camunda connections You can now manage Camunda connections directly in Desktop Modeler: - Add, edit, delete, and save multiple connections. - Securely store credentials and connection settings. - Deploy directly to saved connections. - Select an existing Orchestration Cluster or add a new one during deployment. This streamlines the deployment workflow and reduces setup friction. #### Web Modeler: Create event templates You can now create, discover, and apply templates for more BPMN event types, including message, signal, and timer, directly within the element template editor. You can also create global event templates that: - Are reusable across projects. - Standardize event configurations (for example, message names or payload structures). - Help ensure consistency across teams and models. [Element templates in Modeler](/components/modeler/element-templates/defining-templates.md) #### Web Modeler: Invite users via email As a Self-Managed administrator, you can now invite users to Web Modeler projects via email across all OIDC providers, eliminating the need to wait for users to log in first. - Email-based invitations work for all OIDC providers (Keycloak, Entra ID, Okta, Auth0), matching SaaS behavior. - Keycloak no longer receives special treatment; all providers follow the same invitation workflow. This enables faster project provisioning and a consistent administrator experience across identity providers. [Add users to projects](/components/hub/organization/manage-workspaces/manage-workspace-members.md#add-members) ### Orchestration Cluster Self-ManagedSaaSDataFEEL expressions #### Manage configuration with cluster variables Camunda 8.9 now supports cluster variables, letting you centrally manage configuration across your cluster. You can access these variables directly in the Modeler using FEEL expressions: | Variable | Scope | Priority | | :--------------------- | :---------------------------------- | :------- | | `camunda.vars.cluster` | Global | Lowest | | `camunda.vars.tenant` | Tenant | Medium | | `camunda.vars.env` | Merged view with automatic priority | Highest | For example, if the same variable exists in multiple scopes, the priority is as follows: - A Tenant variable overrides a Global variable. - A Process-level variable has the highest priority, overriding both. This hierarchy allows you to create cascading configurations, where specific contexts override broader defaults. Cluster variables support simple key-value pairs and nested objects, which you can access with dot notation for complex structures. You can manage all cluster variables via the Orchestration Cluster API. You can edit cluster variables directly [Cluster variables](/components/modeler/feel/cluster-variable/overview.md) #### Use Amazon Aurora for secondary storage Camunda 8.9 now supports Amazon Aurora as a secondary data store for orchestration clusters, in addition to existing options. - Supports Aurora PostgreSQL (compatible with PostgreSQL 14–17). - Designed for secure, high-performance, cloud-native deployments. - Seamless integration with AWS features, including: - IAM / IRSA authentication. - High availability and failover. - Alignment with DBA best practices. Helm charts and manual installation guides now include tested configurations and step-by-step references for Aurora, reducing operational complexity and accelerating adoption for AWS-centric organizations. ### Process instance migration Self-ManagedSaaSAgentic orchestrationAI agents Camunda 8.9 now supports migration of process instances that include ad-hoc subprocesses, covering both single-instance and multi-instance (parallel and sequential) variants. With this enhancement, you can: - Safely migrate running instances. - Update AI agent flows. - Modernize process definitions without losing execution state. This unlocks more flexible, agent-driven orchestration and faster iteration on live automation. [Process instance migration](/components/concepts/process-instance-migration.md#migrate-active-elements-inside-ad-hoc-subprocesses) ### RDBMS secondary storage Self-ManagedData Camunda 8.9 Helm charts now support RDBMS as fully integrated secondary storage options for orchestration clusters, providing a first-class alternative to Elasticsearch and OpenSearch. With this update, administrators can: - Use RDBMS as an alternative to Elasticsearch or OpenSearch. - Configure database connections directly in `values.yaml`. - Enable advanced authentication and custom JDBC drivers. This allows enterprises to run Camunda 8 on familiar, enterprise-managed RDBMS infrastructure aligned with existing security, backup, and compliance requirements. #### No default secondary storage in Helm With RDBMS support, the Helm chart no longer defaults to Elasticsearch as secondary storage. You must now explicitly set `orchestration.data.secondaryStorage.type` to `elasticsearch`, `opensearch`, or `rdbms`. This also introduces `global.noSecondaryStorage` for engine-only deployments without any secondary storage. [Configuring secondary storage](/self-managed/concepts/secondary-storage/configuring-secondary-storage.md) ## 8.9.0-alpha2 | Release date | Changelog(s) | Blog | | :--------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--- | | 09 December 2025 | [ Camunda 8 core ](https://github.com/camunda/camunda/releases/tag/8.9.0-alpha2)[ Connectors ](https://github.com/camunda/connectors/releases/tag/8.9.0-alpha2) | - | ### Agentic orchestration Self-ManagedSaaSAgentic orchestrationAI agentsConnectors #### A2A Client connectors Agent-to-Agent (A2A) Client connectors allow you to interact with remote agents using the [A2A protocol](https://a2a-protocol.org/v0.3.0/specification/). **Connector** **Description** [A2A Client connector](/components/early-access/alpha/a2a-client/a2a-client-connector.md) Interact with A2A agents, by retrieving the remote agent’s Agent Card and sending messages to the agent. [A2A Client Polling connector](/components/early-access/alpha/a2a-client/a2a-client-polling-connector.md) Poll for responses from asynchronous A2A tasks. Typically paired with the A2A Client connector when using the Polling response retrieval method. [A2A Client Webhook connector](/components/early-access/alpha/a2a-client/a2a-client-webhook-connector.md) Receive callbacks from remote A2A agents via HTTP webhooks. Typically paired with the A2A Client connector when using the Notification response retrieval method. These connectors support multi-agent collaboration scenarios when combined with the AI Agent connector, as well as providing the ability to discover remote agents, send messages, and receive responses through multiple mechanisms. [A2A Client connectors](/components/early-access/alpha/a2a-client/a2a-client.md) #### MCP client authentication and transport protocol The Camunda Model Context Protocol (MCP) client now supports OAuth, API key, and custom header–based authentication. - System administrators can configure secure, policy-compliant access for Camunda AI agents. - AI developers can discover and invoke enterprise MCP tools safely without exposing open endpoints. MCP client connectors now also support connections using the [streamable HTTP](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http) transport protocol. [MCP Client](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-client.md) :::note breaking changes This feature introduces breaking changes in the element templates and the runtime configuration of the MCP Client. To learn more, see [announcements](890-announcements.md#agentic-orchestration). ::: ### Connectors Self-ManagedSaaSConnectors #### Amazon Textract connector improvements The Amazon Textract connector is improved with input field visibility and polling fixes, new sections for enhanced usability, and updated documentation. [Amazon Textract connector](/components/connectors/out-of-the-box-connectors/amazon-textract.md) #### Azure Blob Storage connector supports OAuth 2.0 The Azure Blob Storage connector now supports OAuth2.0 authentication with Microsoft Azure. [Azure Blob Storage connector OAuth 2.0](/components/connectors/out-of-the-box-connectors/azure-blob-storage.md#oauth-20) #### Email connector supports SMTP no authentication mode The Email connector now supports `noAuth` authentication mode for SMTP. This feature is useful for customers running local mail servers without authentication requirements. [Email connector](/components/connectors/out-of-the-box-connectors/email-outbound.md) #### Runtime performance improvements with virtual threads executor (Self-Managed) Connectors now use a virtual threads executor by default, using Project Loom to improve performance and scalability. This allows the connector runtime to handle a larger number of concurrent jobs with lower resource consumption, particularly benefiting I/O-bound workloads typical in connector operations. ### Console Self-ManagedSaaSConsole #### Bulk import secrets (SaaS) You can now add/import secrets in Console by directly uploading or pasting the contents of a .env file. - Key–value pairs are automatically parsed, validated, and added as secrets. - This helps reduce configuration errors and copy-pasting when adding secrets. [Connector secrets](/components/saas/clusters/manage-secrets.md) #### Cluster description (SaaS) You can now add a cluster description when creating a cluster or by editing the cluster settings. This helps you document context, ownership, or add operational notes without changing the cluster name. [Create a cluster](/components/saas/clusters/create-cluster.md) #### Import cluster secrets (SaaS) You can now import and export connector secrets between clusters within your organization. Export a cluster’s secrets to a key-value file for backup or external workflows, and import secrets from another cluster in a single action. Imports automatically match keys, update existing values, create missing ones, and provide clear feedback on the result. Permissions are enforced so that only authorized users can perform these actions. [Connector secrets](/components/saas/clusters/manage-secrets.md) #### Usage metrics for licence model and tenant (Self-Managed) Self-Managed environment usage metrics now support per-tenant reporting and align with Camunda’s updated licensing model based on the number of tenants. :::note This feature is already available in the Camunda 8.8 release for Camunda 8 SaaS. ::: ### RDBMS secondary storage Self-ManagedData #### Configure external RDBMS in Helm Configure an external relational database (RDBMS) as secondary storage for the Orchestration Cluster when deploying with Helm. - Supports all databases listed in the RDBMS support policy. - Includes full configuration parameters, history-cleanup options, and exporter settings. - Describes how to load JDBC drivers via init containers, custom images, or mounted volumes. - Provides steps to verify database connectivity. [Configure RDBMS in Helm chart](/self-managed/deployment/helm/configure/database/rdbms.md) #### Open-source OpenSearch support You can now use the [open-source OpenSearch](https://opensearch.org/) project for data storage in a Self-Managed deployment. - This allows you to run a fully open source observability stack without using Elasticsearch or the Amazon OpenSearch Service. - For configuration instructions, see the updated Helm chart values and compatibility matrix. [Secondary storage](/self-managed/concepts/secondary-storage/index.md) #### RDBMS version support policy A new Camunda 8 Relational Database Management System RDBMS support policy provides information about: - Officially supported database versions. - The process for adopting new database versions. - Timelines for phasing out older database versions. [RDBMS version support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) #### SQL and Liquibase database scripts SQL and Liquibase scripts are provided for all Camunda-supported databases. - These scripts include database and schema creation, drop, and upgrade routines. - Scripts follow best practices for each supported database type and version. - The full script package is distributed as part of the official Camunda distribution, available via GitHub or Artifactory. [SQL and Liquibase scripts](/self-managed/deployment/helm/configure/database/access-sql-liquibase-scripts.md) ### Modeler Self-ManagedSaaSDesktop ModelerWeb Modeler #### Element template signal support Element templates now support reusable [BPMN signals](/components/modeler/bpmn/signal-events/signal-events.md). - The `bpmn:Signal#property` binding allows you to set the name of a `bpmn:Signal` referred to by the templated element. - This binding is only valid for templates of events with `bpmn:SignalEventDefinition`. [Element template `bpmn:Signal` binding](/components/modeler/element-templates/template-properties.md#signal-name-bpmnsignalproperty) #### Web Modeler: Embedded web server changed from Undertow to Tomcat (Self-Managed) Web Modeler now uses [Apache Tomcat](https://tomcat.apache.org/) as an embedded web server instead of Undertow. Aligning Web Modeler logging with the Orchestration Cluster makes it easier for administrators to configure and maintain Self-Managed deployments. [Embedded web server](/versioned_docs/version-8.9/self-managed/upgrade/components/880-to-890.md#embedded-web-server) #### Web Modeler: IP egress monitoring (SaaS) A new `/meta/ip-ranges` REST API endpoint allows you to monitor SaaS Web Modeler egress IP addresses. - For example, the endpoint is available at https://api.cloud.camunda.io/meta/ip-ranges. - Send a GET request to the endpoint to retrieve a list of egress IP addresses. - Only IP addresses for the related services are exposed (Web Modeler). :::note IP address changes - You should periodically monitor this list via the API, and make any changes in your systems as required. - Although expected changes are published via the API at least 24 hours in advance, in exceptional cases Camunda might have to update these addresses within 24 hours and without prior notice. See [static outbound IP addresses](/components/saas/ip-addresses.md#static-outbound-ip-addresses). ::: ## 8.9.0-alpha1 | Release date | Changelog(s) | Blog | | :--------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--- | | 13 November 2025 | [ Camunda 8 core ](https://github.com/camunda/camunda/releases/tag/8.9.0-alpha1)[ Connectors ](https://github.com/camunda/connectors/releases/tag/8.9.0-alpha1) | - | ### RDBMS secondary storage #### JDBC driver management for RDBMS integrations Self-ManagedConfiguration Camunda 8.9 introduces a standardized approach to JDBC driver management for RDBMS integrations in manual installations. - A new `/driver-lib` directory separates Camunda-bundled drivers from customer-supplied ones, providing a clear and compliant structure for database connectivity. - Drivers that Camunda can legally distribute are included by default. Customers can add and configure their own drivers (for example, Oracle JDBC). - Configuration options allow full control, including explicit driver-class designation when required. This change simplifies compliance and setup for RDBMS environments, ensuring consistent connectivity across PostgreSQL, Oracle, MariaDB, and H2. #### MySQL and Microsoft SQL Server secondary storage Self-ManagedData Camunda 8.9 extends RDBMS secondary storage to include MySQL and Microsoft SQL Server as additional database options for the Orchestration cluster. - This enhancement provides greater flexibility for enterprises that depend on these databases due to policy, licensing, or ecosystem requirements, enabling smoother onboarding and infrastructure alignment. - Zeebe’s primary execution storage remains Raft + RocksDB. :::note This alpha release introduces foundational support only. External configuration and Operate integration follows in upcoming alpha releases. ::: #### RDBMS secondary storage (H2, PostgreSQL, Oracle, MariaDB) Self-ManagedData Camunda 8.9 introduces RDBMS secondary storage as an alternative to Elasticsearch or OpenSearch for storing and querying process data. This feature enables organizations to use relational databases such as H2, PostgreSQL, Oracle, or MariaDB as the secondary storage layer, reducing operational complexity for teams that do not need the scale or performance of Elasticsearch or OpenSearch and prefer an RDBMS-based solution. Key highlights: - **Flexible database choice:** Use relational databases instead of Elasticsearch or OpenSearch. - **Separation of concerns:** Zeebe’s primary execution storage remains Raft + RocksDB; this update only extends the secondary storage layer. - **Consistent APIs:** Continue using the same REST API and data format as with Elasticsearch or OpenSearch—no query or integration changes needed. - **Simplified operations:** Leverage existing RDBMS expertise without maintaining Elasticsearch or OpenSearch clusters. :::note This alpha release introduces support for H2 in Camunda 8 Run only. Operate and external RDBMS configuration follows in upcoming alpha releases. ::: ### Web Modeler #### RDBMS support (H2, MariaDB, MySQL) Self-ManagedSaaSDataWeb Modeler Web Modeler now supports H2, MariaDB, and MySQL as relational database systems, aligning with the configurations supported by the Orchestration cluster. This enhancement ensures consistency across environments, simplifies setup for administrators, and improves integration for both SaaS and Self-Managed deployments. ### Orchestration Cluster #### Unified cache for RocksDB In Camunda 8.9, RocksDB state storage uses a single shared cache and write buffer per broker instead of per partition. This behavior is controlled by the RocksDB memory allocation strategy (`PARTITION`, `BROKER`, `FRACTION`). See [resource planning](/components/best-practices/architecture/sizing-self-managed.md) for details on the available strategies and recommended settings. :::note In Camunda 8.10, the default memory allocation strategy changes from `PARTITION` to `FRACTION`. This may result in a different amount of memory being allocated to RocksDB. Test the `FRACTION` strategy in Camunda 8.9 to prepare for this change. Alternatively, explicitly set the strategy to `PARTITION` to keep the previous memory allocation behavior. ::: --- ## What's new in Camunda 8.9 ## Why upgrade to Camunda 8.9? Upgrading to Camunda 8.9 delivers significant benefits and keeps your installation aligned and ready for future releases. - **Agentic orchestration**: [Build and orchestrate AI agents](#agentic-orchestration) within your BPMN-based workflows, enabling human tasks, deterministic rule sets, and AI-driven decisions to collaborate in a robust, end-to-end process. - **RDBMS secondary storage**: Flexible data layer via first‑class RDBMS secondary storage options, including Amazon Aurora. - **Observability**: Simpler configuration and observability, with cluster variables, audit logs, and a new SaaS metrics endpoint. - **Improved developer experience**: A smoother developer and operator experience for Camunda 8 Run and Modeler. ## Summary of important changes Important changes in Camunda 8.9 are summarized as follows: **What's new/changed** **Summary** [Agentic orchestration](#agentic-orchestration) Refined AI and MCP connector capabilities and Orchestration Cluster MCP support. [Audit log](#audit-log) Access a detailed record of operations. [Camunda 8 Run](#camunda8run) Improved CLI/configuration experience and secondary storage enhancements. [Global user task listeners](#listeners) Define configuration‑based, cluster‑wide user task listeners. [Helm chart deployment](#helm-chart-deployment) RDBMS and secondary storage configuration, `*.secret.existingSecret` pattern migration, default 8080 REST port, Helm CLI v4 support, and more. [Migration from Camunda 7](#migration) New migration tools History Data Migrator and Identity Data Migrator. [Desktop Modeler](#desktop-modeler) New cluster connection management. [Web Modeler](#web-modeler) Simplified architecture and installation, migration to Log4j2 and Tomcat, RDBMS support, event templates, and email invitations. [Orchestration Cluster](#ocluster) Identity renamed to Admin, Amazon ECS (EC2+Fargate) support, cluster variables, Cluster Metrics endpoint for SaaS clusters, and configuration improvements and enhancements. [RDBMS secondary storage](#rdbms) Relational database support as secondary storage for the Orchestration Cluster. [Supported environments](#environments) Updated support for Java, Elasticsearch/OpenSearch, RDBMS, Helm, and connector runtime (including virtual threads). :::info learn more and upgrade - See [release announcements](/reference/announcements-release-notes/890/890-announcements.md) and [release notes](/reference/announcements-release-notes/890/890-release-notes.md) for a full summary of what's included in Camunda 8.9, including all breaking changes and deprecations, and supported environment changes. - Ready to upgrade? See the [upgrade guides](#upgrade-guides) to learn more about upgrading from Camunda 8.8 to 8.9. ::: ## Agentic orchestration {#agentic-orchestration} Build AI agents more easily with enhanced Camunda AI and MCP connectors in 8.9. ### AI Agent and model configuration improvements Enhancements make AI interactions more robust, configurable, and compatible with a wider range of AI providers. | Feature | Description | | :--------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------- | | [AI Agent connectors](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md) | Timeout configuration at the model level, allowing you to cap how long AI calls can run. | | [Amazon Bedrock connector](/components/connectors/out-of-the-box-connectors/amazon-bedrock.md) | Support for long‑term API key authentication in addition to existing methods. | | OpenAI-compatible models | Custom query parameters in the endpoint URL, useful for setting API versions, and passing additional metadata required by custom API gateways. | ### MCP Client connector authentication The MCP Client connector now supports multiple authentication strategies for connecting to MCP servers (OAuth, API key, and custom header–based authentication). [MCP Client connector](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-client-connector.md) ### Orchestration Cluster MCP support The Orchestration Cluster now exposes its operational capabilities via a built-in Model Context Protocol (MCP) server, enabling AI agents and LLM-powered applications to access Camunda data via a standardized interface. [Orchestration Cluster MCP Server](/apis-tools/orchestration-cluster-api-mcp/orchestration-cluster-api-mcp-overview.md) ## Audit log Use the new user operations audit log to access a record of operations, including who performed an operation, when it was performed, and on which entities the operation was performed. Use the audit log to prove compliance, meet governance and regulatory requirements, maintain operational integrity and transparency, and troubleshoot issues. [Audit log](/components/audit-log/overview.md) ## BPMN conditional events Camunda 8 now supports BPMN conditional events, allowing users to start, continue, or interrupt process execution dynamically based on evaluated conditions. This enhancement provides first-class support for conditional start, boundary, and intermediate catch events, making process automation more expressive and migration from Camunda 7 smoother. [Conditional events](/components/modeler/bpmn/conditional-events/conditional-events.md) ## Camunda 8 Run {#camunda8run} Camunda 8.9 focuses on making Camunda 8 Run simpler to use in local and development scenarios. ### Improved CLI and configuration experience The Camunda 8 Run CLI and configuration is enhanced to: - Provide more helpful usage output and clearer error messages (for example, around Elasticsearch startup). - Include guided Java detection, sensible development defaults (such as disk watermark thresholds), and a clean‑state command for resetting a local environment. - Ship a fully documented unified configuration file by default. These changes reduce friction when setting up Camunda 8 Run for the first time, debugging local setup issues, and switching between different configurations or environments. :::info Breaking change The `--docker` flag and bundled Docker Compose files have been removed from Camunda 8 Run. Docker Compose is now a standalone distribution artifact. See [release announcements](/reference/announcements-release-notes/890/890-announcements.md#camunda-8-run-docker-compose-support-removed) for migration details. ::: [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) ### Secondary storage Camunda 8 Run now uses H2 as the default secondary data store. - You can start a fully functional local stack without provisioning Elasticsearch or OpenSearch. - Memory footprint is reduced compared to a full external search cluster. - Developers can bootstrap quickly with minimal configuration. You can also configure Camunda 8 Run to use any of the supported secondary storage relational databases instead of the default H2 (for example, PostgreSQL, MariaDB, MySQL, Oracle, or Microsoft SQL Server). [Configure Camunda 8 Run secondary storage](/self-managed/quickstart/developer-quickstart/c8run/secondary-storage.md#configure-or-switch-secondary-storage-h2-or-elasticsearch) ## Global user task listeners {#listeners} Use configuration‑based global user task listeners in your Self‑Managed deployments. - Administrators can define cluster‑wide listeners using configuration files or environment variables. - Listeners are active from cluster startup and preserved across backup and restore. - All user task lifecycle events emit rich payloads - full variable context at the time of the event, and standardized metadata for traceability and integration. [Global User task Listeners](/components/concepts/global-user-task-listeners.md) ## Helm chart deployment Important changes to Helm chart deployment in 8.9 are as follows: ### Default REST port changed The Orchestration Cluster's default HTTP port has changed from 8090 to 8080. You should update any hardcoded port references in network policies, Ingress rules, or service configuration. ### Helm CLI v4 support {#helm-4-support} Camunda 8.9 (chart 14.x) supports Helm CLI v3 and v4. Camunda recommends Helm CLI v4 and supports it for the full release cycles of Camunda 8.9 and 8.10. Camunda supports Helm CLI v3 (3.10 or later) until February 10, 2027, when upstream support ends. After February 10, 2027, Camunda no longer supports Helm CLI v3. Customers who continue to use Helm CLI v3 after that date do so at their own risk. For Helm CLI v4 behavior changes and workarounds, see [Helm CLI v4](/self-managed/deployment/helm/operational-tasks/helm-v4.md). ### RDBMS as secondary storage The Helm chart now supports relational databases as a first-class secondary storage option alongside Elasticsearch and OpenSearch. You can configure database connections directly under `orchestration.data.secondaryStorage.rdbms` in your `values.yaml`. ### Secondary storage must be explicitly configured The Helm chart no longer defaults to Elasticsearch as the secondary storage type. - You must now explicitly set `orchestration.data.secondaryStorage.type` in your `values.yaml` to either `elasticsearch`, `opensearch`, or `rdbms`. - Without this configuration, `helm install` or `helm upgrade` will fail with a validation error. - For engine-only deployments that do not require secondary storage, the new `global.noSecondaryStorage` mode is available. #### Are you affected? You are affected if you are upgrading a Self-Managed Helm deployment from 8.8 to 8.9 and have not previously explicitly set a secondary storage type. ### Secret configuration migration Secret configuration keys that were deprecated in Camunda 8.8 are now removed in 8.9. - Using any of these removed keys will result in a hard failure during Helm operations. - In addition, the `global.secrets.autoGenerated`, `global.secrets.name`, and `global.secrets.annotations` keys are removed. This means secrets are no longer automatically generated by the Helm chart. All secret configuration must now use the new standardized pattern: | Key | Description | | :--------------------------- | :------------------------------------------------ | | `*.secret.existingSecret` | Reference an existing Kubernetes Secret. | | `*.secret.existingSecretKey` | Specify the key within the Secret. | | `*.secret.inlineSecret` | Provide a plain-text value (non-production only). | #### Are you affected? You are affected if your `values.yaml` uses any of the legacy secret keys (such as `global.elasticsearch.auth.existingSecret`, `identity.firstUser.password`, `connectors.security.authentication.oidc.existingSecret`, or similar). See the [release announcements](/reference/announcements-release-notes/890/890-announcements.md#helm-chart-deprecated-secret-keys-removed) for the full list of removed keys. ## Reference architectures {#reference-architectures} Camunda 8.9 introduces major changes to the [deployment references](https://github.com/camunda/camunda-deployment-references) used by Self-Managed reference architectures. ### Operator-based infrastructure replaces Bitnami All reference architectures (AKS, EKS, OpenShift, Kind) now use Kubernetes operators — CloudNativePG, ECK, and Keycloak operator — instead of embedded Bitnami subcharts for infrastructure services. If you follow a reference architecture, your next deployment will use operator-managed PostgreSQL, Elasticsearch, and Keycloak. Existing deployments can migrate using the new [migration tooling](/self-managed/deployment/helm/operational-tasks/migration-from-bitnami/index.md). #### Are you affected? You are affected if you deploy Self-Managed using a reference architecture from the `camunda-deployment-references` repository and rely on Bitnami-managed infrastructure. Follow the migration guide to transition to operator-managed services. ### New deployment options - **Amazon ECS on Fargate** is now available as a container-based deployment without Kubernetes. - **AKS RDBMS variant** provides a lighter Azure deployment using PostgreSQL as secondary storage (no Elasticsearch). - **Kind local development** reference architecture is available for local testing with Makefile-based commands. ### Dual-region changes - EKS and OpenShift dual-region now use the ECK operator for Elasticsearch. - Headless service DNS is used for initial contact points, improving cross-region connectivity. ## Migration from Camunda 7 to Camunda 8 {#migration} Camunda 8.9 introduces the following additional tools and features to help you migrate from Camunda 7 to Camunda 8. :::tip Start your migration today with the [Camunda 7 to Camunda 8 migration guide](/guides/migrating-from-camunda-7/index.md). ::: ### History Data Migrator The History Data Migrator migrates audit trail data (the history of process execution, including active, completed and canceled instances) to Camunda 8 when RDBMS is used as secondary storage. [History Data Migrator](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/history.md) ### Identity Data Migrator The Identity Data Migrator migrates authorizations that control access to resources in Camunda, helping you preserve permissions when migrating to Camunda 8. [Identity Data Migrator](/guides/migrating-from-camunda-7/migration-tooling/data-migrator/identity.md) ## Desktop Modeler {#desktop-modeler} ### Manage Camunda connections You can now manage multiple Camunda connections in Desktop Modeler: - Add, edit, delete, and save connection profiles. - Securely store credentials and configuration for each connection. - Deploy directly to a saved connection, and select clusters during deployment. [Connect to Camunda 8 in Desktop Modeler](/components/modeler/desktop-modeler/connect-to-camunda-8.md) ## Web Modeler {#web-modeler} Camunda 8.9 introduces the following enhancements and changes in Web Modeler. ### Event templates and email invitations The following usability improvements simplify collaboration and help teams keep event configurations consistent. | Feature | Description | | :---------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Element templates](/components/modeler/element-templates/defining-templates.md) | Create templates for message, signal, and timer events, and reuse and share templates across projects to standardize message names, payloads, and timer definitions. | | [Email invitations](/components/hub/organization/manage-workspaces/manage-workspace-members.md#add-members) | Invite new users to Web Modeler projects via email, regardless of OIDC provider, and use a consistent invitation flow across Keycloak, Entra ID, Okta, Auth0, and other providers. | ### Improved Self-Managed installation The Web Modeler system architecture has been simplified to enable easier and smoother installation and configuration of Web Modeler in a Self-Managed deployment. The separate `webapp` component has been removed, and its functionality is now completely integrated into the `restapi` component. This change might require updates to your application configuration. [Migrate configuration](/versioned_docs/version-8.9/self-managed/upgrade/components/880-to-890.md#migrate-webapp-configuration) ### Log4j2 and Tomcat changes | Feature | Description | | :-------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Logging framework changes from Logback to Log4j2 | Web Modeler's `restapi` component uses [Apache Log4j 2](https://logging.apache.org/log4j/2.x/) for logging instead of [Logback](https://logback.qos.ch/). You can now also change the log levels at runtime. | | Embedded web server changes from Undertow to Tomcat | Web Modeler's `restapi` component uses [Apache Tomcat](https://tomcat.apache.org/) instead of [Undertow](https://undertow.io/). | These changes could affect your organization if you are in a Self-Managed environment and: - You are using a custom Logback and/or Undertow configuration. - You are using any tools that consume the log files. You should: - Review your logging and server configuration for Web Modeler. - Update any tooling that relies on old log formats or server behavior. - Validate the new setup in a staging environment before upgrading production. [Web Modeler logging](/self-managed/components/hub/configuration/logging.md) ## Orchestration Cluster {#ocluster} Orchestration Cluster changes and highlights in 8.9 include the following: ### Amazon ECS (EC2+Fargate) support Camunda 8 officially supports running Orchestration Clusters on Amazon Elastic Container Service (Amazon ECS). This makes it easier and safer for teams that rely on Amazon ECS (including Fargate) to run Camunda 8 in production without needing to adopt Kubernetes (EKS). This feature relies on AWS S3. [Deploy to Amazon ECS](/self-managed/deployment/containers/cloud-providers/amazon/aws-ecs.md) ### Cluster variables for shared configuration Use cluster variables to define configuration values once and reuse them across processes and tenants using FEEL expressions. | Variable | Scope | Priority | | :--------------------- | :---------------------------------- | :------- | | `camunda.vars.cluster` | Global | Lowest | | `camunda.vars.tenant` | Tenant | Medium | | `camunda.vars.env` | Merged view with automatic priority | Highest | You can use cluster variables to centralize: - URLs, endpoints, and credentials references. - Feature flags and environment names. - Shared configuration used across multiple processes or applications. [Cluster variables](/components/modeler/feel/cluster-variable/overview.md) ### Cluster Metrics endpoint for SaaS clusters Use the new Cluster Metrics endpoint for SaaS clusters to: - Expose cluster metrics compatible with Prometheus‑style scraping. - Integrate directly with tools such as Prometheus, Datadog, or other metrics backends that support Prometheus endpoints. - Gain real‑time visibility into cluster performance and health. This helps operations teams consolidate Camunda monitoring into existing observability stacks, and standardize dashboards, alerts, and SLOs across environments. [Cluster Metrics endpoint](/components/saas/monitoring/cluster-metrics-endpoint/index.md) ### Configure RocksDB memory per-broker You can now configure RocksDB memory on a per-broker basis instead of per-partition, simplifying capacity planning and aligning with familiar JVM-style sizing. [Zeebe memory allocation](/components/best-practices/architecture/sizing-self-managed.md#memory) ### Orchestration Cluster Identity renamed to Admin Starting with Camunda 8.9, the Orchestration Cluster Identity component is renamed to **Admin** (Orchestration Cluster Admin). Admin is the cluster-level admin UI hosting identity management and other administrative features. Only the name has changed. Identity management (users, groups, roles, authorizations, tenants, mapping rules, and clients) is unchanged. - The `admin` Spring profile replaces the `identity` profile. Both profiles work interchangeably in 8.9. The `identity` profile is deprecated and will be removed in a future version. - API paths change from `/identity/*` to `/admin/*`. The old paths redirect to the new paths but are deprecated. - Helm values change from `orchestration.identity.*` to `orchestration.admin.*`. The old values are deprecated. - Documentation paths are updated: `/components/identity/` is now `/components/admin/`. [Introduction to Admin](/components/admin/admin-introduction.md) ### Unified component configuration {#ucc} In Camunda 8.9, the remaining unified configuration project property changes are complete. - All 8.9 property changes are documented in the [Camunda 8.9 property changes](/versioned_docs/version-8.9/self-managed/components/orchestration-cluster/core-settings/configuration/configuration-mapping.md#camunda-89-property-changes) table. - Search, sort, and filter the table to see breaking changes, direct mappings, and new properties. - For more information on each property (including default values), see the [property reference](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md). [Property changes in Camunda 8.9](/versioned_docs/version-8.9/self-managed/components/orchestration-cluster/core-settings/configuration/configuration-mapping.md) :::note Only the first partial set of the unified configuration project properties was introduced in Camunda 8.8. ::: ## RDBMS secondary storage {#rdbms} Camunda 8.9 introduces relational databases as first‑class secondary storage options for the Orchestration Cluster. ### RDBMS secondary storage for orchestration data You can now use relational databases to store and query orchestration data instead of relying solely on Elasticsearch or OpenSearch. Supported options include H2 (for development and test scenarios), PostgreSQL, Oracle, MariaDB, MySQL, Microsoft SQL Server, and Amazon Aurora PostgreSQL. - Zeebe’s primary execution storage remains unchanged (Raft + RocksDB). - The same REST API and data model are used regardless of whether you choose Elasticsearch/OpenSearch or an RDBMS as secondary storage. - You can select the storage option that best matches your operational, compliance, and cost requirements. This is particularly useful if you already standardize on one of the supported RDBMS platforms, want to avoid operating and scaling additional search clusters where they are not required, or prefer existing RDBMS tooling for backup, monitoring, and compliance. [Secondary storage](/self-managed/concepts/secondary-storage/index.md) ### Amazon Aurora as managed secondary storage Camunda 8.9 adds support for Amazon Aurora PostgreSQL as secondary storage, enabling: - A fully managed, cloud‑native RDBMS for secondary storage. - Integration with AWS features such as IAM/IRSA, high availability, and automatic failover. - Helm and manual installation guidance with tested configurations for Aurora. Aurora is a good fit if you run Camunda on AWS and want managed RDBMS operations instead of self‑managing PostgreSQL, and integration with existing AWS security and reliability practices. [Using AWS Aurora PostgreSQL with Camunda](/self-managed/concepts/databases/relational-db/configuration.md#usage-with-aws-aurora-postgresql) ### RDBMS version support policy and database scripts To clarify long‑term support, Camunda 8.9 introduces: - An RDBMS support policy covering supported database versions and adoption of new versions. - SQL and Liquibase scripts for all supported databases, distributed as part of the official Camunda distribution. This helps DBAs and operations teams validate that existing database versions are supported, plan upgrades based on a predictable policy and script set, and standardize roll‑outs and migrations across environments. [RDBMS version support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) ### Standardized JDBC driver management For manual installations, Camunda 8.9 introduces a standardized directory and configuration for JDBC drivers: - A new `/driver-lib` directory separates drivers bundled by Camunda from customer‑supplied drivers (for example, Oracle or specific vendor builds). - Configuration options allow you to choose which drivers are active, and explicitly configure driver classes where required. This structure simplifies license‑compliant driver distribution, multi‑database support in regulated environments, and provides a clear separation between Camunda-managed and customer-managed artifacts. [Loading JDBC drivers into pods](/self-managed/deployment/helm/configure/database/rdbms.md#loading-jdbc-drivers-into-pods) ## Supported environments {#environments} Camunda 8.9 updates several platform and environment baselines. Highlights include: | Environment | Description | | :------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Connector runtime | Virtual threads enabled by default for outbound connectors, improving concurrency and resource usage where supported. | | Elasticsearch/OpenSearch | Updated minimum versions for Self‑Managed deployments.Recommended upgrades to Elasticsearch 9.2+ and OpenSearch 3.4+ to align with Camunda 8.9 defaults and benefit from recent database improvements. | | Helm and RDBMS configuration | New RDBMS configuration options in the Helm values file for orchestration data.Expanded secret and configuration patterns aligned with the Orchestration Cluster architecture. | | Java | Certification for OpenJDK 25 across core components and tooling, alongside existing OpenJDK 21–24 support. | | [SaaS regions](/components/saas/regions.md) | Support for an additional AWS US East (us-east-2) region in North America (and new European region options) so you can select clusters closer to your data and users. | :::info For complete details, including breaking changes and deprecations, see [release announcements](./890-announcements.md) and [supported environments](/reference/supported-environments.md). ::: ## Upgrade guides {#upgrade-guides} The following guides offer detailed information on how to upgrade to Camunda 8.9. **Guide** **Description** **Who is this guide for?** [Self-Managed upgrade guide](/self-managed/upgrade/index.md) Evaluate your infrastructure, understand operational changes, and choose the best update strategy for your environment. Operations and platform administrators of Self-Managed installations. [APIs & tools upgrade guide](/versioned_docs/version-8.9/apis-tools/migration-manuals/migrate-to-89.md) Plan and execute an upgrade from Camunda 8.8 to 8.9, focusing on API and tools transitions. Application developers maintaining Camunda-based solutions in Self-Managed Kubernetes or VM environments.Developers using Camunda APIs and tools. --- ## 8.11 release information ## Release information See what's new in Camunda 8.11, key changes and [supported environments](/reference/supported-environments.md) changes in the release announcements, and new features in the release notes. :::info Upgrade guidance - See [upgrade Camunda](/self-managed/upgrade/index.md) for guidance on upgrading your Self-Managed Camunda 8 application or server installation. - Refer to the [quality board](https://github.com/orgs/camunda/projects/187/views/21) for an overview of known bugs by component and severity. ::: ## Release schedule Key dates for the Camunda 8.11 release. | Version | Release type | Release date | Scheduled end of maintenance | | :------------ | :----------- | :--------------- | :--------------------------- | | 8.11.0 | Minor | 13 April 2027 | 10 October 2028 | | 8.11.0-alpha5 | Alpha | 9 March 2027 | - | | 8.11.0-alpha4 | Alpha | 9 February 2027 | - | | 8.11.0-alpha3 | Alpha | 12 January 2027 | - | | 8.11.0-alpha2 | Alpha | 8 December 2026 | - | | 8.11.0-alpha1 | Alpha | 10 November 2026 | - | :::info Release policy Review the [release policy](/reference/announcements-release-notes/release-policy.md) to learn more about Camunda releases, including alpha features and alpha releases. ::: ## Changelogs Technical changelogs for Camunda 8 are available on GitHub: | Changelog | Repository | | :----------------- | :------------------------------------------------------------------------------------------------------------- | | Camunda 8 Core | [github.com/camunda/camunda/releases](https://github.com/camunda/camunda/releases) | | Connectors | [github.com/camunda/connectors/releases](https://github.com/camunda/connectors/releases) | | Camunda Helm chart | [github.com/camunda/camunda-platform-helm/releases](https://github.com/camunda/camunda-platform-helm/releases) | | Desktop Modeler | [github.com/camunda/camunda-modeler/releases](https://github.com/camunda/camunda-modeler/releases) | ## Previous releases Release dates and information for previous Camunda 8 releases that are still maintained. **Version** **Release date** **Scheduled end of maintenance** **Release information** **Changelogs** 8.10 13 October 2026 11 April 2028 [Announcements](/reference/announcements-release-notes/8100/8100-announcements.md)[Release notes](/reference/announcements-release-notes/8100/8100-release-notes.md) [Camunda 8 core](https://github.com/camunda/camunda/releases/tag/8.10.0)[Connectors](https://github.com/camunda/connectors/releases/tag/8.10.0) 8.9 14 April 2026 12 October 2027 [Announcements](/reference/announcements-release-notes/890/890-announcements.md)[Release notes](/reference/announcements-release-notes/890/890-release-notes.md) [Camunda 8 core](https://github.com/camunda/camunda/releases/tag/8.9.0)[Connectors](https://github.com/camunda/connectors/releases/tag/8.9.0) 8.8 14 October 2025 13 April 2027 [Announcements](/reference/announcements-release-notes/880/880-announcements.md)[Release notes](/reference/announcements-release-notes/880/880-release-notes.md) [Camunda 8 core](https://github.com/camunda/camunda/releases/tag/8.8.0)[Connectors](https://github.com/camunda/connectors/releases/tag/8.8.0) --- ## Release policy ## About Camunda 8 follows the [Camunda release policy](https://camunda.com/release-policy/) with the specific clarifications detailed on this page. :::info You can find deprecation and support announcements on the [announcements](/reference/announcements-release-notes/overview.md#announcements) page. ::: ![Stable and alpha channels when provisioning a cluster](../img/diagram-releases.png) ## Alpha features and releases It is important to understand the different ways the term "alpha" is used in the context of Camunda releases and features. ### Alpha feature Refers to a feature or component released as an alpha version, in an early state for you to test and participate in development by sharing your feedback before the feature reaches [general availability](#general-availability-ga). Some alpha features require turning on for your cluster before you can use them. See [alpha features](/components/early-access/alpha/alpha-features.md). ### Alpha release Refers to a release made available between minor versions that allows you to preview an upcoming minor version and the alpha features included (for example, `8.10.0-alpha1`, `8.10.0-alpha2`, and so on). Camunda strives to release this type of release on a monthly basis. To learn more about the alpha features included in each alpha release, see [release notes](/reference/announcements-release-notes/overview.md#release-notes). :::note - Alpha releases cannot be updated to newer releases and are not suitable for use in production. - An alpha release can also be made available where the entire version is an alpha with [alpha limitations](/components/early-access/alpha/alpha-features.md#alpha). - "Alpha channel" refers to the channel you can use when provisioning a SaaS cluster. See [alpha channel](#alpha-channel). ::: ## General availability (GA) Once features and components are released and considered stable, they become generally available. Stable features and components are: - Ready for production use for most users with minimal risk. - Supported by [L1 Priority-level support](https://camunda.com/services/enterprise-support-guide/) for production use. - Fully documented. A release or component is considered stable if it has passed all verification and test stages and can be released to production. ## SaaS provisioning In Camunda 8 SaaS we differentiate between components that are part of a Camunda 8 cluster (cluster components), and components outside the cluster (non-cluster components). ### Cluster components A cluster typically consists of the following components: - [Zeebe](/components/zeebe/zeebe-overview.md) - [Operate](/components/operate/operate-introduction.md) - [Tasklist](/components/tasklist/introduction-to-tasklist.md) - [Optimize](/components/optimize/what-is-optimize.md) You can provision cluster components using one of two channels, following the [Camunda release policy](https://camunda.com/release-policy/). ![Stable and alpha channels when provisioning a cluster](../img/channels.png) #### Stable channel You can use the **Stable** channel to access [general availability](#general-availability-ga) features for cluster components. - Provides the latest feature and patch releases ready for most users at minimal risk. - Releases follow semantic versioning and can be updated to the next minor or patch release without data loss. - On the stable channel, all supported minor versions are made available for provisioning. #### Alpha channel You can use the **Alpha** channel to access [alpha features](/components/early-access/alpha/alpha-features.md) and patch releases for cluster components. - Provides alpha releases to preview and prepare for the next stable release. - Alpha releases provide a short-term stability point to test new features and give feedback before they are released to the stable channel. Use an alpha release to test the upcoming minor release with your infrastructure. ### Non-cluster components Non-cluster components include: - [Camunda Hub](/components/hub/index.md) - [Connectors](/components/connectors/introduction.md) Non-cluster component versions are released continuously. - Customers are automatically updated to the latest component version when it is ready for release. - Admins can [enable alpha features](/components/saas/organization/enable-alpha-features.md) for non-cluster components in organization settings. ### New Camunda 8 versions When a new Camunda 8 version is released, we try to provide the new version on our managed service at the same time. An **Update available** notification is shown in Camunda Hub, recommending that you update to the latest version. ![Camunda Hub with notice to update the cluster in Camunda 8 SaaS](../img/update-console.png) #### Generation names The generation naming scheme in Camunda 8 SaaS no longer includes the patch version. - The naming scheme used for the Camunda 8.5 generations is `Camunda .+gen`, where `N` is incremented with every atomic change to the component version set. - This decouples the generation name from the particular patch level of the components it contains, as some component versions such as connectors are decoupled from other components. - You can learn about the particular component patch version changes in the update dialogue to the latest generation available. #### Update or restart for critical issues In our managed service, Camunda reserves the right to force update or restart a cluster immediately and without notice in advance if there is a critical security or stability issue. ## Self-Managed You can stay up to date via [release blogs](https://camunda.com/blog/category/releases/), [announcements](/reference/announcements-release-notes/overview.md#announcements), or releases on [GitHub](https://github.com/camunda) and [Docker Hub](https://hub.docker.com/u/camunda). ### Helm chart Since the 8.4 release, the [Camunda 8 Self-Managed Helm chart](https://artifacthub.io/packages/helm/camunda/camunda-platform) version is decoupled from the version of the application. For example, the chart version is 9.0.0 and the application version is 8.4.x. To learn more about the applications version included in the Helm chart, see the [Camunda 8 Helm chart version matrix](https://helm.camunda.io/camunda-platform/version-matrix/). ### New Camunda 8 versions If you are running Camunda 8 Self-Managed, see the [update guide](/self-managed/upgrade/components/index.md) to learn how to update your Camunda 8 application or server installation to a newer version of Camunda 8. ## Dependency maintenance policies Camunda provides [a standard support policy](https://camunda.com/release-policy/) of 18 months for a particular minor version from the date it is released. During this time, patches are regularly released containing security and bug fixes, some of which may come from dependency updates. Therefore, for the vast majority of dependencies Camunda _only_ applies patch updates. However, certain dependencies used by Camunda 8 may have a shorter maintenance policy than Camunda itself. Camunda may adopt a different update policy for these dependencies, as listed below. ### Spring Boot/Framework/Security updates Spring has a [different maintenance window](https://spring.io/projects/spring-boot#support) than Camunda for its open-source software (OSS) offering. Camunda addresses this with Spring Boot/Framework/Security update policies for the Camunda Orchestration Cluster and client libraries. :::info The update policy covers the Spring Framework and Security versions, as well as [other dependencies managed via Spring Boot](https://docs.spring.io/spring-boot/appendix/dependency-versions/coordinates.html). ::: #### Orchestration Cluster The Camunda Orchestration Cluster leverages Spring Boot to implement core functionality, like application configuration, REST infrastructure (including security), and other production-ready features. Camunda ensures that **the latest available patch releases make use of a Spring version within an active support window**. Therefore, Orchestration Cluster patch releases **may contain updates to newer Spring Boot minor versions**. In cases where a Spring major version's OSS support ends before Camunda's own support window, Camunda may utilize Spring enterprise support artifacts from a vendor determined on a case-by-case basis to ensure continued maintenance and security coverage for the Orchestration Cluster components. #### Client libraries and SDKs Camunda clients or SDKs, such as the [Camunda Spring Boot Starter](../../apis-tools/camunda-spring-boot-starter/getting-started.md), that are meant to be included in third-party applications, may depend on a particular Spring Boot release. To ensure backward compatibility, every Camunda library patch release references the same Spring Boot minor version as the one referenced in the Camunda library's latest minor release. However, Camunda tests against newer Spring Boot minor releases and declares compatible versions in the library's compatibility matrix. For an example, refer to the [Camunda Spring Boot Starter Compatibility Matrix](../../apis-tools/camunda-spring-boot-starter/getting-started.md#version-compatibility). If the library is compatible with a newer Spring version with an active OSS support window, Camunda declares compatibility one month before the support window ends, at the latest. With this policy, you can safely update your applications to a new Spring Boot version by overriding the default Spring release with another compatible version. ##### Spring Boot major versions Camunda supports two Spring Boot major versions at a time: - A numbered `camunda-spring-boot-{n}-starter` artifact is published for each supported major, so you can pin to a major and stay on it. - The plain `camunda-spring-boot-starter` always follows the current default major. - The previous major stays supported until the end of Spring Commercial support for that major, after which it receives no further updates. When a new Spring Boot major version reaches GA, Camunda makes it the default artifact in the Camunda release that ships before OSS support for the previous major version ends. --- ## Support and feedback [Support](https://camunda.com/services/enterprise-support-guide/) Request support via the [Enterprise support process](https://camunda.com/services/enterprise-support-guide/).Find support options in the [Help Center](/components/saas/help-center.md) or [Camunda community forum](https://forum.camunda.io/). [Bugs](https://forum.camunda.io/) Send us your bug reports via a support ticket (Enterprise customers) or via the [Camunda community forum](https://forum.camunda.io/).Review our technical bug trackers in [GitHub](https://github.com/camunda/camunda/issues) or the [quality board](https://github.com/orgs/camunda/projects/187/views/5). [Community](https://forum.camunda.io/) Participate in our community via the [Camunda community forum](https://forum.camunda.io/), where you can exchange ideas with other Camunda users and Camunda employees.For Camunda community programs and resources, visit the [Camunda Developer Hub](https://camunda.com/developers). [Feature requests](https://roadmap.camunda.com/c/236-how-to-submit-a-feature-request) Submit a feature request in the [Product Roadmap Portal](https://roadmap.camunda.com/c/236-how-to-submit-a-feature-request).[Open an issue](https://github.com/camunda/camunda/issues) for smaller, technical enhancement requests. [Sales/other](https://camunda.com/contact/) [Contact us](https://camunda.com/contact/) with sales inquiries, information about Camunda 8 performance and benchmarking, or any other queries not covered by the other channels. [Security issues](/reference/notices.md) For security-related issues, see [security notices](/reference/notices.md) for current information on known issues and how to report a vulnerability so we can solve the problem as quickly as possible.Note: Do not use GitHub for security-related issues. ## Locate your Camunda 8 credentials Need help finding your Camunda 8 credentials? Submit a **Help Request** to obtain your credentials: 1. Log in to [Jira](https://jira.camunda.com/secure/Dashboard.jspa). 1. Click **Create** in the navigation bar at the top of the page. This launches a **Create Issue** pop-up. 1. In the **Issue Type** field, select **Help Request**. 1. In the **Help Request Type** field, click the option that reads **I need the credentials for downloading Camunda**. 1. In the **Summary** and **Description** fields, **I need the credentials for downloading Camunda** will populate by default. 1. (Optional) Add more details, such as the priority level or authorized support contacts. 1. Click **Create** at the bottom of the pop-up **Create Issue** box. 1. Once these steps are completed your request is generated. :::tip For more information on how to submit a self-service help request, see the [Camunda support guide](https://camunda.com/services/enterprise-support-guide/). ::: --- ## Data collection ## About Camunda collects technical and usage information about how its products are operated and used. This information, referred to as **telemetry data**, is used to evaluate contractual usage, enhance the user experience, and improve Camunda products. Depending on the source, telemetry data may include deployment and version information, aggregated usage metrics, process metadata, SaaS application usage, and, in limited instances, personal data such as account or device identifiers and a SaaS user's name and email address. For a detailed breakdown, see [telemetry sources](#telemetry-sources). This page explains what telemetry data includes and excludes, the privacy controls Camunda implements, and the available options for modifying the data sent to Camunda. It applies only to interactions with Camunda products and reflects the current configuration for Self-Managed and SaaS customers. The information is updated periodically and does not cover future releases or planned changes to telemetry settings. ## Purposes Camunda collects certain types of telemetry data for the following purposes: - Billing. - Improving the user experience by tracking and analyzing software usage. - Ensuring the security, stability, and functionality of the software. - Providing customers with support and guidance to help them optimize product usage and adopt new functionality. ## Principles The table below summarizes what telemetry data includes and excludes. The principles that follow explain how Camunda handles this data. | Area | Telemetry data includes | Telemetry data does not include | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Deployment | Environment data: Camunda and [Analytics Exporter](/self-managed/components/orchestration-cluster/zeebe/exporters/analytics-exporter.md) versions, cluster and partition identifiers. | Your license key (the exporter sends only a fingerprint of it). | | Commercial usage | Summary usage metrics, for example the number of process instances used compared with the number purchased. | - | | Process execution | Process metadata: root process instance starts, decision evaluations, user task creation and assignment, incidents raised and resolved, agent instance starts and completions, tenant creation and deletion. | Process variables, job variables, and payloads; message content and correlation keys; incident error messages; tenant names and descriptions. | | Process design | Definition deployments and deletions, with user-provided identifiers such as the process ID. | BPMN, DMN, and form resources, resource names, and version tags; how you develop processes, such as keystrokes. | | Data in your processes | - | Data entered by end users, such as form submissions (for example a shipping address); any personal data, PII, or PHI uploaded to a customer cluster. | | AI features | Which AI feature was invoked and how often (SaaS); agent instance status (Orchestration Cluster). | Conversation content submitted to an AI feature (collected separately, see [AI features](#ai-features)); agent system prompts, tool definitions, model configuration, and token counts. | | Camunda SaaS usage | Features used, browser and client information, pages accessed; for user actions, cursor activity, a device identifier, and coarse location. | Browser-based data (cookies, device, location) for actions taken through the API. | | Personal data | SaaS only: account and device identifiers, and the name and email address of Camunda SaaS users. | Orchestration Cluster telemetry: no user names, email addresses, or assignee data. | | Payment | - | Payment information. | Camunda follows these principles when collecting and using telemetry data to protect customer privacy and support successful product development: - Camunda will use telemetry data subject to applicable law (including maintaining opt-in and opt-out functionalities for personal data where necessary). Telemetry data is generally aggregated unless users opt-in for personalized use of their telemetry data (for example, to provide additional support or optimize product usage to customers). Therefore telemetry data is generally not considered to be personal data with the exception of only limited subsets of telemetry data which may be linked to user identifiers. - Telemetry data does not include any data shared in process instances or uploaded in customer clusters. Therefore, no end-user or end-customer personal data/personal information (PII), or protected health information (PHI) uploaded to a customer cluster is part of telemetry data. - Telemetry data does not include payment information. - Camunda does not sell any personal (user) information. - Where identifiers are retained for a lawful purpose, such telemetry data is pseudonymized and treated as personal data, however, where no identifiers are present, telemetry data is otherwise aggregated and anonymized. - Data collected from end-users such as form fills or process variables are not part of telemetry data. For example, if part of your process involves a user filling in a shipping address, that address is not telemetry data. - Assets like the BPMN diagram describing how a process is defined and executed are not telemetry data. Telemetry data does not include information about how customers develop their processes, like keystrokes or BPMN diagrams. Instead, it includes user-provided identifiers like a process ID to track which Camunda software features are used when developing a process. - Customers are responsible for avoiding sharing intellectual property, personal data, or sensitive data through interaction with AI features. The data collected by different AI features is shared [below](#camunda-saas-application-telemetry). - Camunda will not use telemetry data in any way that identifies the source of the telemetry data to third parties except as necessary for Camunda to enforce its rights and contractual obligations, such as charging fees for overage of usage metrics or complying with a lawful subpoena. ## Telemetry collection by deployment type What Camunda collects and how you control it depend on how Camunda is deployed. For more information about each source, see [telemetry sources](#telemetry-sources). Three terms are used throughout: - **Commercial usage data:** Usage counts that Camunda uses to verify usage against your agreement. Overage charges are calculated from [usage metrics reporting](#usage-metrics-reporting). - **Non-commercial usage data:** Product usage information that Camunda uses to improve the software and support your deployment. - **Environment data:** Technical information about the deployment itself, such as the Camunda version, the Analytics Exporter version, and cluster identifiers. Environment data is not usage data because it does not describe how the product is used. Camunda uses this information to detect offline clusters and gaps in the data it receives. | Source | SaaS | Self-Managed | | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Usage metrics reporting (commercial) | Collected automatically. | Not sent to Camunda. Computed locally: you retrieve them through the usage metrics API and share them with Camunda as your agreement requires. | | Orchestration Cluster telemetry, commercial (contractual) | Enabled by default. Required to operate the service and cannot be disabled. | Off by default. Sent only if your administrator enables the Analytics Exporter with the contractual category. See [choose what is sent](/self-managed/components/orchestration-cluster/zeebe/exporters/analytics-exporter.md#choose-what-is-sent). | | Orchestration Cluster telemetry, non-commercial (optional) | Enabled by default. To disable it, contact your Camunda account team (CSM/TAM). | Off by default. Sent only if your administrator enables the Analytics Exporter with the optional category. | | Environment data | Enabled by default. Sent with Orchestration Cluster telemetry and cannot be disabled. | Off by default. Sent whenever the Analytics Exporter is enabled, regardless of the categories selected. Not sent while the exporter is disabled. | | SaaS application telemetry, system actions and backend-sourced events | Collected automatically. Not controlled by cookie preferences. | Not applicable. | | SaaS application telemetry, user actions | Controlled per user through cookie preferences. Accepting essential cookies stops browser-based collection. | Not applicable. | | AI feature usage | Controlled per organization: AI features are opt-in in the Console. | Not applicable. | | SaaS user-lifecycle events | Enabled by default. Required to operate the service and collected under the [Terms of Service](https://legal.camunda.com/licensing-and-other-legal-terms). There is no individual opt-out. | Not applicable. | | Desktop Modeler telemetry | Not applicable. | Enabled at first run unless you turn it off. Controlled per installation. | ## Telemetry sources Telemetry data is grouped by its source. Each source below states what it contains and whether the data is commercial or non-commercial. For how you control each source, see [telemetry collection by deployment type](#telemetry-collection-by-deployment-type). | Source | Commercial usage data | Non-commercial usage data | Applies to | | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------- | --------------------- | | [Usage metrics reporting](#usage-metrics-reporting) | Contractually agreed usage metrics. | None | SaaS and Self-Managed | | [Orchestration Cluster telemetry](#orchestration-cluster-telemetry) | Root process instance starts, decision evaluations, user task assignments, and tenant creation and deletion. | Product usage and operational events. | SaaS and Self-Managed | | [Camunda SaaS application telemetry](#camunda-saas-application-telemetry) | None | System actions, user actions, and [AI feature usage](#ai-features). | SaaS only | | [Camunda SaaS user-lifecycle events](#camunda-saas-user-lifecycle-events) | None | Sign-ins, user records, invitations, and membership or role changes. | SaaS only | | [Desktop Modeler telemetry](#desktop-modeler-telemetry) | None | Feature usage. | Self-Managed only | :::note Environment data is not usage data, so it is not listed in this table. ::: ### Usage metrics reporting **Data category:** Commercial usage data only. This source does not collect non-commercial usage data. **Applies to:** SaaS and Self-Managed. This is a limited set of contractually agreed [usage metrics](/reference/data-collection/usage-metrics.md) used to measure consumption against your agreement and calculate any applicable overage charges. One example is the number of process instances used compared with the number purchased. These are summary usage metrics that contain no sensitive information. They are collected automatically for SaaS customers. On Self-Managed they are computed locally and do not leave your installation. You retrieve them through the [usage metrics API](/apis-tools/orchestration-cluster-api-rest/specifications/get-usage-metrics.api.mdx) and share them with Camunda as your agreement requires. ### Orchestration Cluster telemetry **Data category:** Commercial usage data (contractual) and non-commercial usage data (optional), plus environment data. **Applies to:** SaaS and Self-Managed. The [Orchestration Cluster](/reference/glossary.md#orchestration-cluster) can send product telemetry directly to Camunda through the [Analytics Exporter](/self-managed/components/orchestration-cluster/zeebe/exporters/analytics-exporter.md). Usage telemetry is grouped into two categories, shown below. In addition, environment data is sent whenever the exporter runs, whichever categories you select. See [always-on signals](/self-managed/components/orchestration-cluster/zeebe/exporters/analytics-exporter.md#always-on-signals). | Category | What it contains | Why Camunda collects it | | --------------- | ------------------------- | -------------------------------------------------------------------------------------------- | | **Contractual** | Commercial usage data | To verify usage against the metrics in your agreement. | | **Optional** | Non-commercial usage data | To understand how the product is used, prioritize improvements, and support your deployment. | For a complete field-level list of the data sent, see [what data is sent](/self-managed/components/orchestration-cluster/zeebe/exporters/analytics-exporter.md#what-data-is-sent). ### Camunda SaaS application telemetry **Data category:** Non-commercial usage data only. This source does not collect commercial usage data. **Applies to:** SaaS only. This is limited product usage data from the Camunda SaaS applications, used to make better product improvement decisions and to enable outreach to support users. - **SaaS system actions:** All SaaS organizations submit basic information about which features are being used. When certain features are used, Camunda logs which feature is used and basic information about how it has been used. This information is tied to a pseudonymized organization. - **SaaS user actions:** Users that opt in to personalization cookies gain access to in-app tutorials, whereas analytics cookies cause data to be automatically submitted about which features they interact with in Camunda's UI. In addition to the data collected from system actions described above, Camunda collects cursor activity, geographical area, browser information, a device identifier, and basic biographical information limited to email, name, and city/region/country for user actions. Interactions through the API do not trigger this browser-based collection. - **Cookie preferences:** To change your choice, go to your user settings and open **Cookie preferences** > **Analytics**. If you accept essential cookies only, this browser-based collection stops. Some events generated by the Camunda SaaS backends still reach the analytics platform, keyed by an internal user identifier and not gated by cookie preferences. These carry no browser or location data. #### Example of user action data Below is an example of user action data collected by the platform: ```json { "event": "modeler:deploy:confirm", "properties": { "time": 1721228056.002, "distinct_id": "auth0|669533a8339ceebe5e8f7fed", "$browser": "Microsoft Edge", "$browser_version": 126, "$city": "Gotham City", "$current_url": "https://modeler.camunda.io/diagrams/a8c077ae-22d6-4be3-bebb-a847f40376fe--batsymbol-activate?v=736,217,1", "$device_id": "190b6d254651ec-0a7e1ef548a163-4c657b58-e1000-190b6d2518f1ec", "$initial_referrer": "https://console.camunda.io/", "$initial_referring_domain": "console.camunda.io", "$insert_id": "xjsmufevamu6v5y7", "$lib_version": "2.53.0", "$mp_api_endpoint": "api-js.mixpanel.com", "$mp_api_timestamp_ms": 1721228056805, "$os": "Windows", "$referrer": "https://dsm-1.operate.camunda.io/", "$referring_domain": "dsm-1.operate.camunda.io", "$region": "New Jersey", "$screen_height": 1080, "$screen_width": 1920, "$user_id": "auth0|669533a8339ceebe5e8f7fed", "clusterId": "ea9ddef9-f1e3-4241-a37c-655334c45de8", "clusterTag": "dev", "clusterVersion": "8.5", "connectors": ["io.camunda.connectors.HttpJson.v2"], "containsUserTasks": true, "deployType": "single-file", "deployedForms": { "Form_0ec4ghh": "764a75e7-85a8-448f-8a1f-4952cc8a189d" }, "fileId": "a8c077ae-22d6-4be3-bebb-af97040123fe", "fileType": "bpmn", "license": "Free", "mp_country_code": "US", "mp_lib": "web", "mp_processing_time_ms": 1721228056937, "mp_sent_by_lib_version": "2.53.0", "orgId": "30ba73a-4b2f-433f-80e5-d41176874bb5", "org_id": "30ba73a-4b2f-433f-80e5-d41176874bb5", "organizationId": "30ba73a-4b2f-433f-80e5-d41176874bb5", "stage": "prod", "success": true, "userId": "auth0|669533a8339ceebe5e8f7fed", "version": "8.5.4" } } ``` #### AI features Camunda's AI features are currently available in SaaS only and are clearly labeled as AI features. The customer must opt in to enable them in [Camunda Hub](/components/saas/organization/enable-alpha-features.md#enable-ai-powered-features). Depending on the feature, they may collect different information. - **Usage telemetry:** Which feature was invoked and how often. This is telemetry data. - **Conversation content:** Content you submit to an AI feature is not telemetry data. It is content you provide to the feature, and Camunda collects it separately: - Camunda [Docs AI](/components/hub/workspace/modeler/modeling/advanced-modeling/camunda-docs-ai.md) records the entire conversation to provide ongoing support. - Camunda [Copilots](/components/early-access/alpha/bpmn-copilot/bpmn-copilot.md) automatically log all information sent to and from Camunda's AI models for system monitoring by a limited set of operators. Camunda will only use the data from free users for product and model improvement. ### Camunda SaaS user-lifecycle events **Data category:** Non-commercial usage data only. This source does not collect commercial usage data. **Applies to:** SaaS only. The Camunda SaaS account and organization services record sign-ins, user records, invitations, and membership or role changes. These records include the name and email address of the Camunda SaaS user. Unlike the application telemetry above, these events are not controlled through cookie preferences. They are required to operate the service and are collected under the [Terms of Service](https://legal.camunda.com/licensing-and-other-legal-terms), with no individual opt-out. ### Desktop Modeler telemetry **Data category:** Non-commercial usage data only. This source does not collect commercial usage data. **Applies to:** Self-Managed only. Telemetry collection in [Desktop Modeler](/components/modeler/desktop-modeler/telemetry/telemetry.md) is enabled at first run unless you turn it off, and is controlled per installation. It tracks how certain features are used. To turn it off, disable the **Usage Statistics** option in Desktop Modeler. ## Identifiability This section explains how identifiable the data from each source is. Camunda distinguishes between three states and handles each differently: | State | Meaning | | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | **Anonymous** | No individual can be distinguished, including by combining the data with other records. Aggregate counts fall into this category. | | **Pseudonymized** | Direct identifiers are replaced with substitute values, but an individual can still be distinguished across records. | | **Directly identifiable** | An individual can be identified directly, for example by name or email address. | Pseudonymized data is personal data. Replacing an identifier with a substitute value does not make the data anonymous, because the individual can still be singled out. Where each source sits: | Telemetry | Identifiability | | ---------------------------------------------------------- | -------------------------------------------- | | Usage metrics reporting | Anonymous | | Environment data | Anonymous | | Orchestration Cluster telemetry | Anonymous | | Camunda SaaS application telemetry, system actions | Pseudonymized (organization identifier only) | | Camunda SaaS application telemetry, backend-sourced events | Pseudonymized (internal user identifier) | | Camunda SaaS application telemetry, user actions | Directly identifiable | | Camunda SaaS user-lifecycle events | Directly identifiable | | Desktop Modeler telemetry | Pseudonymized | ### Where identifiers are retained In Camunda SaaS, identifiers that can distinguish an individual are retained in three places: - **SaaS application telemetry, user actions:** An account identifier, a device identifier, coarse location, and the email address and name of the Camunda SaaS user. - **SaaS application telemetry, backend-sourced events:** An internal user identifier. - **SaaS user-lifecycle events:** The name and email address of the Camunda SaaS user, recorded with sign-ins, user records, invitations, and membership or role changes. No other SaaS telemetry described on this page retains an identifier that can distinguish an individual. Desktop Modeler telemetry carries a randomly generated, persistent installation identifier. It can distinguish one installation from another across records and is not linked to a Camunda account. Content you submit to an AI feature, such as a Docs AI conversation, is not telemetry data and is not covered by this section. See [AI features](#ai-features). ### Data subject requests To object to processing, or to make a data subject request, contact Camunda through the route described in the [Privacy Policy](https://legal.camunda.com/privacy-and-data-protection#product-privacy-policy). --- ## Usage metrics ## Definition of metrics Camunda 8 pricing is based on usage metrics, but which ones apply depends on your contract. Customers on the current pricing model are billed on process instances (PI) and tenants. Customers still on the legacy pricing model (2023–2025) are billed on process instances (PI), decision instances (DI), and task users (TU). It is important to understand these definitions, their impact on billing, and how to retrieve them. ### Root process instance The number of **root process instance** executions started. This is also known as process instances (PI). A **root process instance** has no parent process instance, i.e. it is a top-level execution. ### Decision instance The number of evaluated **decision instances** (DI). A **decision instance** is a [DMN decision table](/components/modeler/dmn/decision-table.md) or a [DMN literal expression](/components/modeler/dmn/decision-literal-expression.md). In a Decision Requirements Diagram (DRD) each evaluated decision table or expression is counted separately. ### Task user The number of **task users** (TU) that have served as assignees. ### Tenant A tenant is a logically isolated space within a shared Camunda 8 installation, with its own data, configurations, and user permissions. Usage metrics can report how many tenants were active in a period and, when available, a per-tenant breakdown of process, decision, and task-user usage. ## Retrieve metrics in SaaS In Camunda 8 SaaS an **Owner** or **Admin** of an organization can retrieve the information from the **Billing** page: 1. Open Camunda Hub. 1. In the left navigation, click **Organization > Manage organization**. 1. Click the **Billing** tab. ## Retrieve metrics on Self-Managed :::caution Important note for Enterprise users Some Enterprise agreements require the following indices from Elasticsearch for at least 18 months: Usage metrics are stored in the `camunda-usage-metric-8.8.0_` and `camunda-usage-metric-tu-8.8.0_` indices. ::: On Camunda 8 Self-Managed, you can get the usage metrics via the [Get usage metrics system API][]. For more details about usage metrics, visit the [Usage Metrics concept][] page. [Usage Metrics concept]: ../../self-managed/components/orchestration-cluster/core-settings/concepts/usage-metrics.md [Get usage metrics system API]: ../../apis-tools/orchestration-cluster-api-rest/specifications/get-usage-metrics.api.mdx --- ## Source code and third-party dependencies ## About Camunda provides users and customers with source code access as part of its [written offer](https://legal.camunda.com/licensing-and-other-legal-terms#written-offer-source-code), as well as CycloneDX SBOMs listing all direct and transitive third-party dependencies and their respective licenses. :::info Learn more about CycloneDX SBOMs (International Standard for Bill of Materials) at [https://cyclonedx.org/](https://cyclonedx.org/). ::: ## Camunda 8 | Type | Description | | :----------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Dependencies | Download reports listing all direct and transitive third-party dependencies and their respective licenses from the public portal:SBOM: [CycloneDX](https://portal.fossa.com/p/camunda/release/4905/latest)Attribution: [TXT](https://portal.fossa.com/p/camunda/release/4904/latest) | | Source code | Access the source code for Camunda 8 at [github.com/camunda/camunda](https://github.com/camunda/camunda). | ## Desktop Modeler | Type | Description | | :----------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Dependencies | Download reports listing all direct and transitive third-party dependencies and their respective licenses from the public portal:SBOM: [CycloneDX](https://portal.fossa.com/p/camunda/release/4918/latest)Attribution: [TXT](https://portal.fossa.com/p/camunda/release/4917/latest) | | Source code | Access the source code for Desktop Modeler at [github.com/camunda/camunda-modeler](https://github.com/camunda/camunda-modeler). | ## Camunda Hub | Type | Description | | :----------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Dependencies | Download reports listing all direct and transitive third-party dependencies and their respective licenses from the public portal:SBOM: [CycloneDX](https://portal.fossa.com/p/camunda/release/4920/latest)Attribution: [TXT](https://portal.fossa.com/p/camunda/release/4919/latest) | | Source code | Access to source code is provided [on demand](mailto:dependency-request@camunda.com). | ## Connectors | Type | Description | | :----------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Dependencies | Download reports listing all direct and transitive third-party dependencies and their respective licenses from the public portal:SBOM: [CycloneDX](https://portal.fossa.com/p/camunda/release/4946/latest)Attribution: [TXT](https://portal.fossa.com/p/camunda/release/4945/latest) | | Source code | Access the source code for connectors at [github.com/camunda/connectors](https://github.com/camunda/connectors). | ## Management Identity | Type | Description | | :----------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Dependencies | Download reports listing all direct and transitive third-party dependencies and their respective licenses from the public portal:SBOM: [CycloneDX](https://portal.fossa.com/p/camunda/release/5086/latest)Attribution: [TXT](https://portal.fossa.com/p/camunda/release/5085/latest) | | Source code | Access to source code is provided [on demand](mailto:dependency-request@camunda.com). | ## Optimize | Type | Description | | :----------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Dependencies | Download reports listing all direct and transitive third-party dependencies and their respective licenses from the public portal:SBOM: [CycloneDX](https://portal.fossa.com/p/camunda/release/11295/latest)Attribution: [TXT](https://portal.fossa.com/p/camunda/release/11294/latest) | | Source code | Access the source code for Optimize at [github.com/camunda/camunda/tree/main/optimize](https://github.com/camunda/camunda/tree/main/optimize). | --- ## Glossary(Reference) Explore and understand definitions for key Camunda 8 terms and abbreviations. A B C D E F G H I J K L M N O P Q R S T U V W X Y Z ## A ### Ad-hoc sub-process A special type of BPMN subprocess that allows activities to be executed in any order, skipped, or repeated, without a predefined sequence. Activities are activated dynamically at runtime, either by a human or a system, rather than following a fixed flow. In Camunda, ad-hoc sub-processes are the foundation for AI agent tool execution: each activity inside the sub-process acts as a tool the LLM can select and invoke during a feedback loop. - [Ad-hoc sub-processes](/components/modeler/bpmn/ad-hoc-subprocesses/ad-hoc-subprocesses.md) ### Admin Use Admin in the [Orchestration Cluster](#orchestration-cluster) to administer authentication, authorization, and cluster administration features. :::note Admin was previously named "Identity" in Camunda 8.8. The component was renamed in 8.9 to reflect its expanded scope. ::: - [Admin overview](/components/admin/admin-introduction.md) ### Agent definition A first-class, queryable resource that describes an [AI agent](#ai-agent) deployed to Camunda. Camunda creates one agent definition per agent element (AI Agent sub-process, AI Agent Task, or external agent) when a process containing agents is deployed, analogous to how a [DRD](#drd-decision-requirements-diagram) deployment creates decision definitions. An agent definition is a structural descriptor (type, name, the element and process definition version it belongs to, and tenant), not a store of runtime configuration. - [Agent definitions and instances](/components/agentic-orchestration/agent-definitions-and-instances.md) ### Agent instance A specific runtime execution of an [agent definition](#agent-definition) that can be created for an active agent element. It is identified by an agent instance key, which the [Agent Instance API](/apis-tools/orchestration-cluster-api-rest/specifications/create-agent-instance.api.mdx) uses to represent the agent's state, including conversation, tool calls, and reasoning, for visibility and explainability in tools like Operate. An agent instance can be reused across several element instances within the same process instance, which is what allows an agent to continue a multi-turn conversation when the process returns to the agent element. - [Agent definitions and instances](/components/agentic-orchestration/agent-definitions-and-instances.md) ### Agent loop The feedback loop an [AI agent](#ai-agent) runs to reach its goal: the model reasons over the current context, decides whether to call tools, receives the tool results, and repeats until it returns a final response or reaches a configured limit. The loop is what makes an agent an agent, and it consists of one or more [loop iterations](#loop-iteration). A [Camunda AI agent](#camunda-ai-agent) runs its loop in Camunda's engine, which activates each tool call as a BPMN activity. An [external agent](#external-agent) runs its loop in an external runtime. ### Agent orchestration Agent orchestration is an architectural pattern where a primary AI agent coordinates multiple specialized worker agents. In this pattern, an LLM routes tasks to sub-agents, creating LLM-to-LLM coordination. Agent orchestration can run inside a Camunda agentic process as one of its agents. :::note This is different from [agentic orchestration](#agentic-orchestration), which describes how Camunda orchestrates agents, tools, systems, and people as part of an end-to-end process. ::: ### Agentic orchestration A hybrid orchestration model combining the power of deterministic logic with LLM-driven flexibility, allowing AI to evaluate the prompt, decide on what tools to execute, and decide when the goal is achieved, all with governance and auditability. For example, orchestrate when and how AI agents act within a process, what tools they can use, and how results are validated, with guardrails for reliability, auditability, and human-in-the-loop. You can build AI agents with Camunda using BPMN. :::note This is different from [agent orchestration](#agent-orchestration), which describes LLM-to-LLM coordination between agents. ::: - [Agentic orchestration](/components/agentic-orchestration/agentic-orchestration-overview.md) ### AI agent An addressable execution of an [LLM](#large-language-model-llm)-driven loop with shared memory context across iterations. An agent runs an [agent loop](#agent-loop) where the model decides what to do next, which tools to invoke, and when to stop. The loop is what makes it an agent. A standalone LLM call with no loop and no autonomous tool selection, such as a single connector call that returns output along a fixed execution path, is not an agent. Camunda supports two types of agents: a [Camunda AI agent](#camunda-ai-agent) (native) and an [external agent](#external-agent) (non-native). For example, you can build an invoice-processing AI agent in Camunda with BPMN, using the AI Agent Sub-process template to provide LLM reasoning, tool calling, and short-term memory in a governed feedback loop. - [AI agents](/components/agentic-orchestration/ai-agents.md) - [Build your first AI agent](/guides/getting-started-agentic-orchestration.md) ### AI agent function The category of built-in [FEEL](#feel-expression) functions used to declare LLM-provided tool parameters for an [AI agent](#ai-agent). The primary function is [`fromAi()`](/components/modeler/feel/builtin-functions/feel-built-in-functions-ai-agent.md#fromaivalue), which marks a value as supplied by the LLM at runtime, with an optional description, type, and JSON schema to guide the model. - [AI agent functions](/components/modeler/feel/builtin-functions/feel-built-in-functions-ai-agent.md) ### Artificial intelligence (AI) A broad field of computer science focused on creating machines that can perform tasks requiring human-like intelligence. In practice, AI involves programming computers to learn, reason, and self-correct when solving problems. For example, AI applications include language understanding, image recognition, decision-making, and automation of complex tasks. ### Audit log The [audit log](../components/audit-log/overview.md) is a record of operations, including who performed them, when, and on which entities. Use the audit log to prove compliance, meet governance and regulatory requirements, maintain operational integrity and transparency, and troubleshoot issues. ## B ### Backpressure Backpressure is a protection mechanism that prevents [Zeebe brokers](#zeebe-broker) from being overloaded when they receive more [client](#zeebe-client) requests than they can process with acceptable latency. Zeebe brokers determine backpressure by using dynamic backpressure algorithms or - if enabled - flow control limits, which measure the rate of records written by the [exporter](#zeebe-exporter). When backpressure is activated, client requests are rejected to maintain system stability. - [Backpressure](/self-managed/components/orchestration-cluster/zeebe/operations/backpressure.md) - [Flow control](/self-managed/operational-guides/configure-flow-control/configure-flow-control.md) ### Broker See [Zeebe Broker](#zeebe-broker). ### BPMN model See [Process](#process). ### BTP BTP stands for [SAP](#sap) Business Technology Platform, which is a cloud-based platform that provides tools and services for data management, analytics, application development, and integration within the SAP ecosystem. Camunda can integrate with SAP BTP to orchestrate business processes across SAP and non-SAP systems. By doing so, it enables automation and visibility of workflows that span multiple services and applications hosted on BTP, enhancing agility and process control in enterprise environments. - [SAP integration](/components/camunda-integrations/sap/camunda-sap-integration.md) ## C ### Camunda 8 Camunda 8 is a universal process orchestrator that allows you to orchestrate and automate complex business processes that span people, systems, and devices. Camunda 8 consists of the following key components: | Component | Description | | :--------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Orchestration Cluster](#orchestration-cluster) | Powers the automation and orchestration of [processes](#process). | | [Connectors](#connector) | Out-of-the-box integration with external systems. | | [Optimize](/components/optimize/what-is-optimize.md) | Business intelligence tooling, allowing you to analyze bottlenecks and examine improvements in [processes](#process) automated with Camunda. | | [Camunda Hub](/components/hub/index.md) | The design and management platform for Camunda. Manage organizational resources, manage projects, analyze operations and business value, and deliver agentic processes at scale with Camunda Hub. Part of the [management plane](#management-plane). | | Modelers | Allows business users and developers to design and implement [processes](#process), decisions, and [user task](#user-task) forms:Use [Desktop Modeler](/components/modeler/desktop-modeler/index.md) locally on Mac, Windows, and Linux.Use the [Camunda Hub modeling interface](/components/hub/workspace/modeler/index.md) in the browser. | | [Management Identity](#management-identity) | Self-Managed only. Authorization for the components outside the [Orchestration Cluster](#orchestration-cluster) (Optimize and Camunda Hub). As of 8.10, Optimize authenticates with the same settings as the Orchestration Cluster. Camunda Hub keeps its own authentication properties. | ### Camunda AI agent The native [AI agent](#ai-agent) type. Tool orchestration is executed by Camunda's engine, which activates each tool call as a governed BPMN activity, maintains memory across iterations, and emits lifecycle events. It is implemented via the [AI Agent connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md), which offers two implementations: the [AI Agent Sub-process](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent-subprocess.md) and the [AI Agent Task](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md#ai-agent-task). :::note This is different from an [external agent](#external-agent), which is the non-native AI agent type. ::: ### Camunda project A [project](#project), the unit you build, test, and deploy together. The term Camunda project distinguishes it from other project concepts, such as a software project in your IDE. ### Catalog A collection of reusable automation assets, such as element templates. The catalog is synced with your external Git repositories, governed at the organization level in Camunda Hub, and used by delivery teams across workspaces and projects. - [Catalog](/components/hub/organization/manage-catalog/index.md) ### Child process instance A [process instance](#process-instance) created by a [call activity](/components/modeler/bpmn/call-activities/call-activities.md) in another process instance, the [parent process instance](#parent-process-instance). See also: [Parent process instance](#parent-process-instance), [Root process instance](#root-process-instance) ### Client See [Zeebe Client](#zeebe-client). ### Cluster A cluster is the infrastructure that runs Camunda 8. In Camunda Hub, a cluster is an administrative unit that organization admins create, size, update, and back up. Teams deploy to an [environment](#environment) hosted on the cluster, not to the cluster itself. - [Clusters](/components/concepts/clusters.md) - [Environments](/components/concepts/environments.md) - [Orchestration Cluster](#orchestration-cluster) - [Zeebe cluster](#zeebe-cluster) ### Cluster variable A cluster [variable](../../components/concepts/variables/) is a centrally managed configuration value available across a Camunda cluster. It can be defined globally or at the tenant level and is used to provide environment-specific settings, such as API endpoints, feature flags, and shared configuration. A cluster variable's value can also be an [Orchestration Cluster secret reference](#secret-reference-orchestration-cluster) instead of a literal value. ### Cluster-wide operation An operation that affects the entire [Orchestration Cluster](#orchestration-cluster), such as cluster configuration updates, cluster-level health checks, or cluster backups. Cluster-wide operations are protected by the cluster-admin role, except `GET /cluster/v2/status`, which is deliberately unauthenticated so load balancers can use it as a health check. Cluster-wide operations are not scoped to a specific [Physical Tenant](#physical-tenant). - [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md) ### Command A command represents an action to be taken or executed. Example commands include: deploy a process, execute a process, etc. - [Internal processing](/components/zeebe/technical-concepts/internal-processing.md#events-and-commands) ### Connector [Connectors](/components/connectors/introduction.md) are reusable building blocks you can use to easily connect [processes](#process) to external systems, applications, and data. Connector types: - [Outbound](#outbound-connector) - [Inbound](#inbound-connector) - [Protocol](#protocol-connector) ### Connector runtime The [connector runtime](/components/connectors/custom-built-connectors/connector-sdk.md#runtime-environments) is the execution environment responsible for running connector logic, resolving authentication, resolving [secret references](#secret-reference), and communicating with external systems. In SaaS, the runtime is fully managed. In Self-Managed environments, the runtime can run inside the cluster or in hybrid mode. ### Connector secrets The secret values a connector resolves through a [legacy secret reference](#secret-reference-legacy). How these values are stored and supplied depends on the environment: in SaaS, they are [SaaS-managed secrets](#saas-managed-secret) created in Camunda Hub, in the left navigation under **Clusters**; in Self-Managed, a secret provider supplies them, for example from prefixed environment variables, a Kubernetes Secret, or a custom provider. - [Using secrets](/components/connectors/use-connectors/index.md#using-secrets) - [Connector secrets in Self-Managed](/self-managed/components/connectors/connectors-configuration.md#secrets) ### Connector template A [connector template](/components/connectors/custom-built-connectors/connector-templates.md) is a type of element template used to configure connectors in [Camunda Hub](/components/hub/workspace/modeler/index.md) and [Desktop Modeler](/components/modeler/desktop-modeler/index.md). Templates define UI fields, metadata, and bindings required for connector operations. Camunda Hub and Desktop Modeler internally label all templates as element templates, but connector templates are the subset specifically used to configure connectors. ### Context window The amount of text (in [tokens](#token-ai)) a model can consider at once when generating a response. A larger context window allows the model to handle longer inputs. ### Correlation Correlation refers to the act of matching a [message](#message) with an inflight [process instance](#process-instance). - [Message correlation](/components/concepts/messages.md) ### Correlation key A correlation is an attribute within a [message](#message) that is used to match the message against a certain [variable](#process-variable) within an inflight message. If the value of the correlation key matches the value of the variable within the [process instance](#process-instance), the message is matched. - [Message correlation](/components/concepts/messages.md) ### Credential A reusable set of authentication and connection settings for a job worker, connector, or other element template, so you don't repeat the same settings on every task that asks for them. A credential is selected as a whole on an element template field; the engine resolves the reference at runtime and passes the credential's values, including any secrets, to the job worker or connector. - [Manage credentials](/components/hub/organization/credentials/index.md) ### Credential type The shape of a [credential](#credential), such as AWS Credential, REST Authentication, or JDBC Connection. A credential type defines which fields a credential of that type has, and is defined alongside an element template. - [Credential types](/components/hub/organization/credentials/index.md#credential-types) ### CSAP CLI CSAP CLI stands for Camunda SAP Integration Command-Line Interface. It's a [c8ctl](/apis-tools/c8ctl/getting-started.md) plugin (`c8ctl csap-setup`) that simplifies configuring and building Camunda’s SAP integration modules (like the RFC connector and OData connector) for deployment. Camunda uses the plugin to automate setup steps: it interactively or via scripted flags configures connectors and plugins, resolves dependencies, and produces deployment-ready artifacts. This makes deploying SAP integrations straightforward and repeatable in environments like Camunda SaaS. - [CSAP c8ctl plugin](/components/camunda-integrations/sap/csap-cli.md) ## D ### Deployment A process cannot execute unless it is known by the [broker](#zeebe-broker). Deployment is the process of pushing or deploying processes to the [broker](#zeebe-broker). - [Zeebe Deployment](/apis-tools/zeebe-api/gateway-service.md#deployresource-rpc) ### DRD (Decision Requirements Diagram) The visual representation of a decision requirements graph (DRG), which models a domain of decision-making, showing the decisions involved and the dependencies between them, such as required decisions, input data, and knowledge sources. Deploying a DRD creates one decision definition per decision it contains. - [Decision requirements graph](/components/modeler/dmn/decision-requirements-graph.md) ## E ### Elasticsearch/OpenSearch Elasticsearch and OpenSearch are search and analytics engines commonly used as document-store secondary storage backends for indexing and querying exported runtime data. They are populated with process orchestration data and consumed by components such as Operate, Tasklist, and Optimize. See also: [Secondary storage](#secondary-storage) ### Element A BPMN element is part of a [process](#process), defining one part of its BPMN model. Elements are the building blocks of a process and comprise [flow nodes](#flow-node), sequence flows, participants, data objects, and more. - [BPMN elements](/components/modeler/bpmn/bpmn-primer.md#bpmn-elements) ### Element template Use an element template to extend [Modeler](/components/modeler/about-modeler.md) with domain-specific diagram [elements](#element). The user edits such elements through a UI defined by the element template, and in the process configures BPMN element properties in simple and predictable ways. Element templates are used by [connectors](#connector) to create the connector-specific [element](#element) configuration. - [Element templates](/components/modeler/element-templates/about-templates.md) ### Embedding (vector embedding) A vector representation of data, including words, sentences, images, in a numerical space, where similar items are positioned near each other. Embeddings allow AI systems to compare meaning and perform tasks like semantic search. - [Vector database connector](/components/connectors/out-of-the-box-connectors/embeddings-vector-db.md) ### Environment An environment is a named deployment target where a team runs [processes](#process) in [Camunda Hub](/components/hub/index.md). It's the operational unit for deployment, while the [cluster](#cluster) is the administrative unit. An environment is backed by a [Physical Tenant](#physical-tenant) on Self-Managed 8.10 and later, or by the whole cluster on SaaS and on earlier versions. Organization admins assign environments to workspaces, and every project in a workspace can deploy to the environments assigned to it. - [Environments](/components/concepts/environments.md) - [Cluster](#cluster) - [Physical Tenant](#physical-tenant) ### Event An event represents a state change associated with an aspect of an executing [process instance](#process-instance). Events capture [variable](#process-variable) changes, state transition in process elements, and so on. An event is represented by a timestamp, the variable name, and variable value. Events are stored in an append-only log. - [Internal processing](/components/zeebe/technical-concepts/internal-processing.md#events-and-commands) ### Execution platform version In Desktop Modeler and Camunda Hub, the execution platform version is the Camunda runtime version that a diagram targets. It determines which execution semantics and validation rules are applied during modeling. The execution platform version is not a deployed process definition version, a [file version](#version-file) or [project snapshot](#snapshot-project), or a SaaS cluster generation. - [Desktop Modeler flags](/components/modeler/desktop-modeler/flags/flags.md#default-execution-platform-version) ### Execution listener An execution listener is a mechanism that allows users to execute custom logic at specific points during [workflow](#workflow) execution. Execution listeners can be attached to [BPMN elements](#element) to react to lifecycle events, such as when an element starts or ends. This feature facilitates pre-processing and post-processing tasks without cluttering the BPMN model, functioning similarly to [job workers](#job-worker) by leveraging the same infrastructure. - [Execution listeners](/components/concepts/execution-listeners.md) ### Exporter See [Zeebe Exporter](#zeebe-exporter). ### External agent The non-native [AI agent](#ai-agent) type. Tool orchestration runs in an external runtime, such as LangGraph, Amazon Bedrock, or custom code, instead of Camunda's engine, where the loop itself lives outside Camunda. Camunda orchestrates when and how the agent acts within the broader process, and observes its execution through the [Agent Instance API](/apis-tools/orchestration-cluster-api-rest/specifications/create-agent-instance.api.mdx), even though it does not execute the agent's reasoning loop itself. The process record, governance, and audit trail for that participation live in Camunda. :::note This is different from a [Camunda AI agent](#camunda-ai-agent), which is Camunda's native AI agent type. ::: ## F ### FEEL expression FEEL (Friendly Enough Expression Language) expressions are the unit of computation written in [FEEL](/components/modeler/feel/what-is-feel.md), Camunda's expression language. Camunda evaluates FEEL expressions in BPMN diagrams, DMN tables, and Camunda Forms, for example, in gateway conditions, input/output mappings, and [process variable](#process-variable) references. - [FEEL expressions](/components/modeler/feel/language-guide/feel-expressions-introduction.md) ### File version {#version-file} A file version is a saved snapshot of a single file, such as a BPMN or DMN diagram, form, RPA script, README file, or test file. File versions were previously called milestones. You can compare, restore, and copy file versions. They are distinct from [project snapshots](#snapshot-project). - [Versions](/components/hub/workspace/modeler/modeling/versions.md) ### Fine-tuning The process of adapting a pre-trained AI model to a specific task or dataset, allowing it to specialize while leveraging prior knowledge. ### Flow node Flow nodes refer to a specific subset of BPMN [elements](#element). They comprise events, activities, and gateways. Other elements, such as sequence flows, participants, and data objects, are not considered flow nodes. ### Follower In a clustered environment, a [broker](#zeebe-broker) which is not a [leader](#leader) is a follower of a given partition. A follower can become the new leader when the old leader is no longer reachable. - [Clustering](/components/zeebe/technical-concepts/clustering.md#raft-consensus-and-replication-protocol) ## G ### Gateway Gateway can refer to different concepts in Camunda docs, depending on the context: - In BPMN, a gateway controls how a process flow branches, merges, or synchronizes. - In runtime and API contexts, the [Zeebe Gateway](#zeebe-gateway) is the component that exposes APIs and routes requests to Zeebe brokers. - In Kubernetes deployment docs, Gateway can also refer to Kubernetes Gateway API resources. ### Generative AI Any AI system that can produce new content, such as text, images, or audio, in response to prompts. Generative AI doesn’t just analyze existing data; it creates original output that is often contextually relevant to the input. ### Generation In Camunda 8 SaaS, a generation is the release identifier for the version set running in a cluster. Camunda Hub uses generations instead of a single engine version because the underlying component versions can change independently. A generation is not a process definition version, a version tag, or a [file version](#version-file) or [project snapshot](#snapshot-project). - [Generation names](/reference/announcements-release-notes/release-policy.md#generation-names) ### GPAI model A general-purpose AI model trained on large amounts of data using self-supervision at scale, capable of performing a wide range of tasks. Used in the EU AI Act to classify foundation models. - [AI usage guidelines](/components/agentic-orchestration/ai-usage-guidelines.md#the-ai-models-behind-these-features) ## H ### Hallucination When an AI confidently produces incorrect or fabricated information that seems plausible. It reflects the model’s tendency to produce patterns that appear coherent but lack factual accuracy. ### H2 H2 is a lightweight relational database engine used as a secondary storage backend for local development and evaluation in Camunda. H2 can run in two modes: - **In-memory**: Data is stored only in memory and lost when the application stops. Useful for temporary testing. - **File-based (embedded)**: Database files are persisted to disk on the same host as the component using them. Suitable for local development where data persistence across restarts is needed. H2 is not intended for production usage. For Camunda secondary storage, H2 is a single-broker option and is not a valid backend for multi-broker clusters. - [Secondary storage](/self-managed/concepts/secondary-storage/index.md) See also: [Secondary storage](#secondary-storage) ### Human-in-the-Loop (HITL) A human review and approval step before AI-generated outputs with legal, financial, or safety-relevant effects are acted upon. ### Human task Camunda 8 allows you to orchestrate processes with human tasks, which may be [user tasks](#user-task) or [manual tasks](#manual-task). - [Human task orchestration](/guides/getting-started-orchestrate-human-tasks.md) ### Hybrid mode Hybrid mode, or a hybrid Self-Managed distribution, allows you to run a separate instance of the [connectors](#connector) runtime in a Self-Managed or local fashion. This instance can be attached to either a SaaS cluster, or another Self-Managed cluster with its own connector runtime. For example, this is useful when working with services that must be isolated within a private network and cannot be exposed to the public internet, or if infrastructure amendments need to be applied to the connector runtime, such as SSL certificates or mounted volumes. - [Use connectors in hybrid mode](/components/connectors/use-connectors-in-hybrid-mode.md) ## I ### Inbound connector Inbound [Connectors](#connector) in Camunda 8 enable workflows to receive data or messages from external systems or services, making it possible to integrate workflows into a wider business process or system architecture. Inbound connectors include three subtypes: [webhooks](#webhook-connector), [subscriptions](#subscription-inbound-connector), and polling. Unlike [outbound connectors](#outbound-connector), inbound connectors are **stateful**. The Java code of the inbound connector has a lifecycle suitable for long-running operations, such as listening for messages on a queue or waiting for a webhook to be called. Each element referencing an inbound connector will lead to the creation of one inbound connector instance. A process definition with one webhook start event and two additional webhooks as intermediate catch events would therefore lead to the creation of three inbound connector instances. ### Incident An incident represents an error condition which prevents [Zeebe](#zeebe) from advancing an executing [process instance](#process-instance). Zeebe will create an incident if there was an uncaught exception thrown in your code and the number of retries of the given step is exceeded. - [Incident](/components/concepts/incidents.md) ### Ingress An Ingress is a Kubernetes object that manages external access to the services within a Kubernetes cluster. An **Ingress controller** is required to route traffic to your services according to the rules defined on the Ingress. - [Ingress setup](/self-managed/deployment/helm/configure/ingress/ingress-setup.md) ## J ### Job A job represents a distinct unit of work within a [business process](#process). Service tasks represent such jobs in your process and are identified by a unique id. A job has a type to allow specific job workers to find jobs that they can work on. - [Job workers](/components/concepts/job-workers.md) ### Job activation timeout This is the amount of time the broker will wait for a complete or fail response from the job worker. This comes after a job has been submitted to the job worker for processing and before it marks the job as available again for other job workers. - [Job workers](/components/concepts/job-workers.md#requesting-jobs) ### Job worker A [Zeebe Client](#zeebe-client) that polls for and executes available [jobs](#job). An uncompleted job prevents [Zeebe](#zeebe) from advancing process execution to the next step. - [Job workers](/components/concepts/job-workers.md) ## K ### Kill switch A technically and organizationally secured mechanism that can be triggered at any time by authorized personnel to immediately place an AI use case, together with its connected tools and interfaces, into a safe state. This includes stopping ongoing and planned actions, preventing new executions, revoking or blocking access rights, and logging all measures in an auditable manner. - [AI usage guidelines](/components/agentic-orchestration/ai-usage-guidelines.md#human-oversight) ### Kubernetes Secret A Kubernetes object that stores small amounts of sensitive data, such as passwords or tokens, separately from Pod specifications and container images. The Camunda Helm chart uses Kubernetes Secrets to supply credentials to Camunda's own components at deployment time. A Kubernetes Secret can also store and deliver the value behind a [secret reference](#secret-reference): mounted as an environment variable for a [legacy secret reference](#secret-reference-legacy), or as a file in a file-based secret store for an [Orchestration Cluster secret reference](#secret-reference-orchestration-cluster). Either way, the Kubernetes Secret only supplies the value; the [connector runtime](#connector-runtime) or the [Orchestration Cluster](#orchestration-cluster) still resolves the placeholder in the process. A Kubernetes Secret is unrelated to a [SaaS-managed secret](#saas-managed-secret), which supplies values to a SaaS [Orchestration Cluster](#orchestration-cluster) rather than to a Self-Managed component's configuration. - [Helm charts secret management](/self-managed/deployment/helm/configure/secret-management.md) ## L ### Large language model (LLM) A large language model (LLM) is a type of AI program specifically designed to understand and generate human-like text. These models are trained on massive amounts of text data, enabling them to learn the structure of language and perform a variety of tasks, such as conversation, summarization, and code generation. ### Leader In a clustered environment, one [broker](#zeebe-broker) (the [leader](#leader)) is responsible for process execution and housekeeping of data within a [partition](#partition). Housekeeping includes taking [snapshots](#snapshot), [replication](#replication), and running [exports](#zeebe-exporter). - [Clustering](/components/zeebe/technical-concepts/clustering.md#raft-consensus-and-replication-protocol) ### Log The log is comprised of an ordered sequence of records written to persistent storage. The log is appended-only and is stored on disk within the broker. - [Partitions](/components/zeebe/technical-concepts/partitions.md#partition-data-layout) ### Logical Tenant A [Logical Tenant](#logical-tenant) is an existing, lightweight tenant-ID based multi-tenancy model in Camunda 8. Logical Tenants provide data isolation through tenant identifiers (stored in the `tenantId` field) but share infrastructure with other Logical Tenants. Multiple Logical Tenants can coexist within a single [Physical Tenant](#physical-tenant). - [Multi-tenancy](/components/concepts/multi-tenancy.md) - [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md) ### Loop iteration A loop iteration is one pass through an [AI agent](#ai-agent)’s [agent loop](#agent-loop), during which the model reasons, selects tools, evaluates the result, and decides whether to continue. An AI agent run consists of one loop with one or more loop iterations. Camunda groups an agent's conversation history by loop iteration in Operate, making it easier to reference a specific point in an agent's execution. Operate's UI labels each entry simply as `iteration` as shorthand for loop iteration. - [Conversation history and loop iterations](/components/agentic-orchestration/agent-definitions-and-instances.md#conversation-history-and-loop-iterations) ## M ### Management Identity The Management Identity component, available in Self-Managed only, provides authorization for the [Camunda 8](#camunda-8) components outside the [Orchestration Cluster](#orchestration-cluster): Camunda Hub and Optimize. As of 8.10, Optimize authenticates with the same settings as the Orchestration Cluster. Camunda Hub keeps its own authentication properties. Management Identity remains responsible for managing users, groups, roles, and permissions for both. See [authentication to the management components](/self-managed/concepts/authentication/authentication-to-management-components.md). ### Management plane The management plane is the part of a Camunda 8 deployment used to design and manage processes and clusters. It consists of [Camunda Hub](/components/hub/index.md) and [Management Identity](#management-identity), and serves one or more Orchestration Clusters. In Self-Managed Helm deployments, the management plane is the release with `global.topology.mode` set to `hub`. - [Deployment topology](/self-managed/reference-architecture/reference-architecture.md#deployment-topology) ### Manual task A manual task defines a task that requires human interaction but no external tooling or UI interface. For example, a user reviewing a document or completing a physical task. Manual tasks are part of [human task orchestration](/guides/getting-started-orchestrate-human-tasks.md), but differ from [user tasks](#user-task) which define an actionable task assisted by a workflow engine or software application. - [Manual tasks](/components/modeler/bpmn/manual-tasks/manual-tasks.md) ### Multi-tenancy Multi-tenancy allows a single Camunda 8 installation to serve multiple [Logical Tenants](#logical-tenant) while keeping each tenant's data, configurations, and access logically isolated. For strong physical isolation of separate teams or organizations within a single cluster, see [Physical Tenant](#physical-tenant). - [Multi-tenancy](/components/concepts/multi-tenancy.md) - [Logical Tenant](#logical-tenant) - [Physical Tenant](#physical-tenant) ### Message A message contains information to be delivered to interested parties during execution of a [process instance](#process-instance). Messages can be published via Kafka or [Zeebe](#zeebe)’s internal messaging system. Messages are associated with timestamp and other constraints such as time-to-live (TTL). - [Messages](/components/concepts/messages.md) ## O ### Orchestration Cluster The Orchestration Cluster is the core component of [Camunda 8](#camunda-8), powering the automation and orchestration of [processes](#process). An Orchestration Cluster includes: - [Zeebe](#zeebe) as the [workflow engine](#workflow-engine) - Operate for monitoring and troubleshooting [process instances](#process-instance) running in [Zeebe](#zeebe) - Tasklist for interacting with [user tasks](#user-task) (assigning, completing, and so on) - [Admin](#admin) for managing the integrated authentication and authorization - APIs for interacting with the Orchestration Cluster programmatically ### Outbound connector Outbound [Connectors](#connector) in Camunda 8 allow workflows to trigger with external systems or services, making it possible to integrate workflows with other parts of a business process or system architecture. ## P ### Parent process instance The process instance that contains the [call activity](/components/modeler/bpmn/call-activities/call-activities.md) that created a [child process instance](#child-process-instance). A process instance can be a parent to the instances it calls and, at the same time, a child of the instance that called it. See also: [Child process instance](#child-process-instance), [Root process instance](#root-process-instance) ### Partition A partition represents a logical grouping of data in a [Zeebe Broker](#zeebe-broker). This data includes [process variables](#process-variable) stored in RocksDB, [commands](#command), and [events](#event) generated by [Zeebe](#zeebe) stored in the [log](#log). The number of partitions is defined by configuration. - [Partitions](/components/zeebe/technical-concepts/partitions.md) ### Physical Tenant An isolated execution unit within an [Orchestration Cluster](#orchestration-cluster). Each Physical Tenant has separate data storage, independent lifecycle management, and API access scoped to that tenant. Multiple [logical tenants](#logical-tenant) can coexist within a single Physical Tenant. - [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md) - [Logical Tenant](#logical-tenant) - [Environment](#environment) - [Multi-tenancy](#multi-tenancy) ### Polling connector An inbound polling [connector](#connector) that periodically polls an external system or service for new data using HTTP polling. A [Camunda workflow](#workflow) uses this type of connector to retrieve data from a remote system that does not support real-time notifications or webhooks, but instead requires the client to periodically request updates. ### Primary storage Primary storage is the authoritative store for workflow execution state managed by the Orchestration Cluster. In Self-Managed deployments, Zeebe brokers persist partition logs and snapshots on local disk. This data is required to execute workflows, recover after failures, and replicate state across brokers. Primary storage is not a search or analytics backend, and it is not configured as an external database (such as Elasticsearch/OpenSearch, H2, or an external RDBMS). See also: [Orchestration Cluster](#orchestration-cluster), [Log](#log), [Partition](#partition), [Snapshot](#snapshot), [Secondary storage](#secondary-storage) ### Process The general business construct: what you want to automate. In Camunda, a process is [modeled using BPMN](#process-model), then [deployed as a process definition](#process-definition), and finally [executed as a process instance](#process-instance). In runtime discussions, [_executing a process_](/components/concepts/processes.md) may be used as shorthand for deploying a process definition and starting an instance. ### Process application The former name of the [project](#project): a Web Modeler entity before Camunda 8.10, and a Desktop Modeler concept recognized by a `.process-application` marker file. During the transition, Desktop Modeler and `c8ctl` recognize both `.process-application` and `camunda.json`, so existing process applications keep working. - [Project](#project) ### Process definition A [process model](#process-model) that has been deployed to the engine and versioned. Identified by: - **processDefinitionKey**: Unique identifier generated by the engine - **process ID**: Identifier assigned in the process model - **version**: Version number assigned by the engine The engine uses process definitions to start [process instances](#process-instance). ### Process definition version A process definition version is the numeric version assigned by the Orchestration Cluster each time you deploy a process definition with the same process ID. Operate, Optimize, and APIs often shorten this to version. A process definition version is different from a version tag, which is a user-defined label, and from a [file version](#version-file) or [project snapshot](#snapshot-project) in Camunda Hub. - [Process definition](#process-definition) - [Migrate process instances](/components/operate/userguide/process-instance-migration.md) ### Process instance A [process instance](/components/concepts/process-instance-creation.md) is an execution of a [process definition](#process-definition), uniquely identified by its **processInstanceKey**. Each instance represents one run of the process and carries metadata from its originating process definition (process ID, version, and processDefinitionKey). A process instance can be active (currently running), completed, or terminated. In runtime discussions, [_executing a process_](/components/concepts/processes.md) may be used as shorthand for deploying a process definition and starting an instance. A process can call another process via a [call activity](/components/modeler/bpmn/call-activities/call-activities.md), creating a hierarchy of related process instances: a [parent process instance](#parent-process-instance) that contains the call activity, the [child process instance](#child-process-instance) it creates, and the [root process instance](#root-process-instance) at the top of the hierarchy. ### Process instance tag An optional, immutable, lightweight label attached when a process instance is created. Tags provide fast, structured metadata for routing, correlation, prioritization, and analytics segmentation without inspecting large variable payloads. - Tags on process instance creation: [Process instance creation – Tags](/components/concepts/process-instance-creation.md#tags) - Tags on jobs: [Job workers](/components/concepts/job-workers.md#tags) ### Process model The BPMN representation of a [process](/components/concepts/processes.md), created in a BPMN file (for example, in [Camunda Hub](/components/hub/workspace/modeler/index.md) or [Desktop Modeler](/components/modeler/desktop-modeler/index.md)). Identified by its **process ID** (`bpmn:process id` attribute). A single BPMN file can contain multiple process models. ### Process variable A process variable represents the execution state (i.e data) of a process instance. These variables capture business process parameters which are the input and output of various stages of the process instance and which also influence process flow execution. - [Variables](/components/concepts/variables.md) - [Data flow](/components/modeler/bpmn/data-flow.md) ### ProcessOS An AI-powered intelligence layer on top of Camunda's [agentic orchestration](#agentic-orchestration) platform. ProcessOS discovers existing processes from organizational knowledge, re-engineers them against defined outcomes, and generates executable Camunda solutions. - [ProcessOS](/components/process-os/overview.md) ### ProcessOS Harness The governance process of [ProcessOS](#processos). ProcessOS Harness is delivered as a set of scripts, plugins, and skills that run on Camunda and drive an AI coding agent, such as Claude Code or GitHub Copilot CLI, through the discovery, transformation, and implementation phases of a project. It asks for human review at defined gates and commits every artifact to Git. - [ProcessOS Harness](/components/process-os-harness/overview.md) ### Project The unit you build, test, and deploy together to form a Camunda solution. Also called a Camunda project where it needs to be distinguished from other project concepts, such as a software project in your IDE. It supersedes the [process application](#process-application). In Camunda Hub, a project is an entity within a workspace; locally (Desktop Modeler, `c8ctl`), it is a folder with a `camunda.json` file. - [Projects](/components/concepts/projects.md) - [Project](/components/hub/workspace/manage-projects/manage-projects.md) ### Project snapshot {#snapshot-project} A project snapshot is a saved capture of all files in a project at a specific point in time. You can compare, restore, review, and deploy project snapshots. They are distinct from [individual file versions](#version-file). - [Project snapshots](/components/hub/workspace/manage-projects/project-versioning.md) ### Prompt A prompt is the input provided to a [generative AI model](#generative-ai). Prompt engineering refers to the practice of crafting effective instructions to achieve desired results from the AI. ### Protocol connector Protocol connectors are a type of [Connector](#connector) in Camunda that can serve as either [inbound](#inbound-connector) or [outbound](#outbound-connector) connectors, supporting a variety of technical protocols. These connectors are highly generic, designed to provide a flexible and customizable means of integrating with external systems and services. Protocol connectors can be customized to meet the needs of specific use cases using [element templates](#element-template), with no additional coding or deployment required. Examples of protocol connectors include HTTP REST, SOAP, GraphQL, as well as message queue connectors. ### Public API The public API represents the official set of interfaces in Camunda 8 that are covered by [Semantic Versioning (SemVer)](https://semver.org/) stability guarantees. APIs included in the public API contract will not introduce breaking changes in minor or patch releases, ensuring backwards compatibility for your integrations. The public API is a subset of all available Camunda 8 APIs - many APIs are available for external use but are not included in the formal stability commitment. - [Public API](/reference/public-api.md) ## R ### RDBMS RDBMS (Relational Database Management System) refers to a user-managed relational database used as a secondary storage backend in Camunda 8 Self-Managed deployments, depending on the component and configuration. An external RDBMS is used for query and retention use cases, not for core workflow execution state. - [Helm database configuration (RDBMS)](/self-managed/deployment/helm/configure/database/rdbms.md) See also: [Secondary storage](#secondary-storage) ### Record A record represents a command or an event. For example, a command to create a new [process instance](#process-instance), or a state transition of an executing [process instance](#process-instance) representing an [event](#event) at a given point in time would result to generation of a record. During the execution lifecycle of a process instance, numerous records are generated to capture various commands and events generated. Records are stored in the log. - [Internal processing](/components/zeebe/technical-concepts/internal-processing.md#events-and-commands) ### Recovery Point Objective (RPO) Multi-region resilience: Maximum tolerable amount of data loss, measured as the time between the last persisted consistent backup and the moment of failure. ### Recovery Time Objective (RTO) Multi-region resilience: Maximum tolerable time from failure detection to service restoration in a functional state. ### Reference architecture Reference architectures provide comprehensive blueprints for designing and implementing scalable, robust, and adaptable Camunda 8 self-managed installations. Reference architectures serve as starting points that should be adapted to fit the specific needs and constraints of your organization and infrastructure. - [Reference architectures](/self-managed/reference-architecture/reference-architecture.md) ### Replication Replication is the act of copying data in a [partition](#partition) from a [leader](#leader) to its [followers](#follower) within a clustered [Zeebe](#zeebe) deployment. After replication, the leader and followers of a partition will have the exact same data. Replication allows the system to be resilient to [brokers](#zeebe-broker) going down. - [Clustering](/components/zeebe/technical-concepts/clustering.md#raft-consensus-and-replication-protocol) ### Replication factor This is the number of times data in a [partition](#partition) is copied. This depends on the number of [brokers](#zeebe-broker) in a [cluster](#zeebe-cluster). A cluster with one [leader](#leader) and two [followers](#follower) has a replication factor of three, as data in each partition needs to have three copies. We recommend running an odd replication factor. - [Partitions](/components/zeebe/technical-concepts/partitions.md#replication) ### Request timeout How long a [client](#zeebe-client) waits for a response from the [broker](#zeebe-broker) after the client submits a request. If a response is not received within the client request timeout, the client considers the broker unreachable. - [Orchestration Cluster REST API](/apis-tools/zeebe-api-rest/zeebe-api-rest-overview.md) - [Zeebe API (gRPC)](/apis-tools/zeebe-api/grpc.md) ### RFC RFC stands for Remote Function Call, a protocol used by SAP to enable communication and data exchange between SAP systems or between SAP and external systems. Camunda can use RFC to call SAP functions directly as part of a business process. This allows Camunda to trigger SAP transactions, retrieve data, or update records within an SAP system, integrating SAP functionality seamlessly into broader automated workflows. - [RFC](/components/camunda-integrations/sap/rfc-connector.md) ### Robotic process automation (RPA) The use of software robots to automate repetitive, rule-based business tasks. RPA bots emulate human actions in digital systems, enhancing speed and accuracy. ### Root process instance The [process instance](#process-instance) at the top of a hierarchy of related process instances. It was started directly, not created by a [call activity](/components/modeler/bpmn/call-activities/call-activities.md). See also: [Parent process instance](#parent-process-instance), [Child process instance](#child-process-instance) ## S ### SaaS-managed secret A secret whose value is stored and managed for a SaaS [Orchestration Cluster](#orchestration-cluster), independent of any individual process. Create, update, and delete a SaaS-managed secret in Camunda Hub, in the left navigation under **Clusters**, currently under the **Cluster secrets** tab of a cluster. A SaaS-managed secret is unrelated to a [Kubernetes Secret](#kubernetes-secret), which supplies credentials to a Self-Managed cluster's own components. - [Connector secrets](/components/saas/clusters/manage-secrets.md) ### SAP SAP stands for Systems, Applications, and Products in Data Processing; it's an enterprise software platform used to manage business operations such as finance, supply chain, and HR. Camunda integrates with SAP to automate and orchestrate workflows that involve SAP systems, allowing for greater flexibility, transparency, and control over complex business processes. - [SAP](/components/camunda-integrations/overview.md) ### Secondary storage Secondary storage is used for indexing, search, analytics, and long-term retention. Data in secondary storage is typically exported from [primary storage](#primary-storage) and consumed by tools for observability and analytics. Examples of secondary storage backends include: - [Document store (Elasticsearch/OpenSearch)](#elasticsearchopensearch) - [RDBMS](#rdbms) - [Secondary storage concepts](/self-managed/concepts/secondary-storage/index.md) - [Managing secondary storage](/self-managed/concepts/secondary-storage/managing-secondary-storage.md) ### Secret reference A placeholder written into a [process](#process) model that stands in for a secret value, used in a [connector](#connector) field, an input mapping, or a [cluster variable](#cluster-variable). Camunda resolves a secret reference to its value at runtime instead of storing the value in the process itself. Camunda 8 supports two secret reference syntaxes, which are resolved by different components and are not interchangeable: - The [legacy secret reference](#secret-reference-legacy) syntax, `{{secrets.}}` - The [Orchestration Cluster secret reference](#secret-reference-orchestration-cluster) syntax, `camunda.secrets.` A [Kubernetes Secret](#kubernetes-secret) can back either secret reference syntax as the underlying storage and delivery mechanism. What differs between the two syntaxes is which component resolves the placeholder, not whether a Kubernetes Secret is involved. - [Secret resolution and job activation](/components/concepts/secret-resolution-and-job-activation.md) ### Secret reference (legacy) The `{{secrets.}}` syntax used in a [connector](#connector) field to reference a secret. The [connector runtime](#connector-runtime) resolves the placeholder at execution time, replacing it with the referenced secret's value. :::note "Legacy" describes this syntax's age relative to the [Orchestration Cluster secret reference](#secret-reference-orchestration-cluster), not its support status. Both syntaxes remain supported. ::: - [Using secrets](/components/connectors/use-connectors/index.md#using-secrets) ### Secret reference (Orchestration Cluster) The `camunda.secrets.` syntax used to reference a secret, written directly in an input mapping or embedded in the value of a [cluster variable](#cluster-variable) that an input mapping reads. Unlike a [legacy secret reference](#secret-reference-legacy), the [Orchestration Cluster](#orchestration-cluster) itself resolves this reference through [secret resolution](#secret-resolution), rather than the connector runtime resolving it at execution time. Resolution only happens in an input mapping defined on an element that creates a job for a job worker, such as a service task or an ad hoc sub-process. In any other FEEL expression (gateway conditions, script tasks, output mappings, call activity input, and so on), the placeholder is not resolved and reaches your process unchanged. - [Secret resolution and job activation](/components/concepts/secret-resolution-and-job-activation.md) ### Secret resolution The runtime process by which the [Orchestration Cluster](#orchestration-cluster) retrieves the value behind an [Orchestration Cluster secret reference](#secret-reference-orchestration-cluster) from a configured secret store and makes it available for injection into a job. Secret resolution runs on a background scheduler ahead of job activation, not on the processing path. - [Secret resolution and job activation](/components/concepts/secret-resolution-and-job-activation.md) - [Troubleshoot secret resolution failures](/components/concepts/secret-resolution-incidents.md) ### Segment The [log](#log) consists of one or more segments. Each segment is a file containing an ordered sequence records. Segments are deleted when the log is compacted. - [Resource planning](/components/best-practices/architecture/sizing-self-managed.md#event-log) ### Snapshot The state of all active [process instances](#process-instance), (these are also known as inflight process instances) are stored as records in an in-memory database called RocksDB. A snapshot represents a copy of all data within the in-memory database at any given point in time. Snapshots are binary images stored on disk and can be used to restore execution state of a [process](#process). The size of a snapshot is affected by the size of the data. Size of the data depends on several factors, including complexity of the [model](#bpmn-model), the size and quantity of variables in each process instance, and the total number of executing [process instances](#process-instance) in a [broker](#zeebe-broker). - [Resource planning](/components/best-practices/architecture/sizing-self-managed.md#snapshots) ### Soft pause exporting Soft pause exporting is a feature that allows you to continue exporting records from [Zeebe](#zeebe), but without deleting those [records](#record) ([log](#log) compaction) from Zeebe. This is particularly useful during hot backups. - [Exporting API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md) - [Backup and restore](/self-managed/operational-guides/backup-restore/zeebe-backup-and-restore.md) ### Subscription inbound connector An [inbound connector](#inbound-connector) that subscribes to a message queue. This way, a [Camunda workflow](#workflow) can receive messages from an external system or service (like Kafka or RabbitMQ) using message queuing technology. This type of inbound connector is commonly used in distributed systems where different components of the system need to communicate with each other asynchronously. ## T ### Tenant A [Logical Tenant](#logical-tenant) is a logically isolated space within a shared Camunda 8 installation, with its own data, configurations, and user permissions. For strong physical isolation, see [Physical Tenant](#physical-tenant). - [Tenant management](/components/admin/tenant.md) - [Multi-tenancy](/components/concepts/multi-tenancy.md) - [Logical Tenant](#logical-tenant) - [Physical Tenant](#physical-tenant) ### Tenant-scoped operation An operation that targets a specific [Physical Tenant](#physical-tenant), such as deploying a process to a tenant, backing up a tenant's data, or querying a tenant's process instances. - [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md) ### Telemetry data Technical and usage information that Camunda collects about how its products are operated and used. Camunda uses telemetry data to evaluate contractual usage, enhance the user experience, and improve its products. Depending on the source, telemetry data may include deployment and version information, aggregated usage metrics, process metadata, SaaS application usage, and, in limited instances, personal data such as account or cookie identifiers and a SaaS user's name and email address. - [Data collection](/reference/data-collection/data-collection.md) ### Temperature A parameter that regulates the randomness or creativity of AI-generated text. Lower values result in more focused and predictable responses, while higher values lead to more creative and varied outputs. ### Token (AI) The smallest unit of text such as a word, subword, or character, that a language model processes. Models read and generate text as a sequence of tokens. Often, pricing for AI models is based on the number of input/output tokens. ### Token (process instance) In the context of a running process instance in Camunda, a token represents the current point of execution within the BPMN process model. You can think of it as a marker that moves through the process diagram, following the sequence flows as tasks and events are completed. When a process starts, a token is created at the start event and advances with each completed step. Once the token reaches the end event, it is consumed and the process instance ends. Tokens are not data themselves, but they determine which elements of the process are currently active. ## U ### User task A user task is used to model work that needs to be done by a human and is assisted by a workflow engine or software application. This differs from [manual tasks](#manual-task), which are not assisted by external tooling. With 8.7, Camunda offers job worker-based user tasks managed by Camunda, also known as Camunda user tasks (and formerly known as Zeebe user tasks). Note that you may still see references of **Zeebe user tasks** in your XML, but this is the same thing as Camunda user tasks. Camunda recommends using Camunda user tasks in your process definitions. From 8.7, **job-worker** user tasks are available for querying, but Web Modeler (pre-8.10), Camunda Hub (8.10+), and Desktop Modeler automatically apply the **Camunda user task** and show a warning message for each job worker user task. - [User tasks](/components/modeler/bpmn/user-tasks/user-tasks.md) - [Migrate to Camunda user tasks](/apis-tools/migration-manuals/migrate-to-camunda-user-tasks.md) ### User task listener A user task listener allows users to execute custom logic in response to specific user task lifecycle events, such as assigning or completing a task. User task listeners are attached to BPMN user tasks and facilitate validation, custom task assignment, and other operations during user task execution. They operate similarly to [job workers](#job-worker), leveraging the same infrastructure for processing external logic. - [User task listeners](/components/concepts/user-task-listeners.md) ## V ### Variable A variable stores data for a [process instance](#process-instance) or a local scope within a process. Variables can hold JSON values and are used to pass business data between tasks, expressions, and events. - [Variables](/components/concepts/variables.md) ### Version In Camunda 8, version is an overloaded term. Depending on context, it can refer to a [process definition version](#process-definition-version), a [version tag](#version-tag), a [file version](#version-file) or [project snapshot](#snapshot-project), an [execution platform version](#execution-platform-version), or a SaaS [generation](#generation). ### Version tag A version tag is a user-defined string label for a specific resource or snapshot. For deployed BPMN, DMN, and form resources, a version tag can be used to identify a resource version and to resolve dependencies with `versionTag` binding. In Camunda Hub project snapshots, a version tag labels a saved project snapshot. A version tag is not generated automatically and does not replace the numeric process definition version. - [Resource binding types](/components/best-practices/modeling/choosing-the-resource-binding-type.md#versiontag) - [Project versioning](/components/hub/workspace/manage-projects/project-versioning.md) ## W ### Webhook connector Webhooks are a subtype of [inbound connector](#inbound-connector). A webhook is a way for web applications to send real-time notifications or data to other applications or services when certain events occur. When a webhook is set up, the application sends a POST request containing data to a pre-configured URL, which triggers a workflow. ### Worker See [Job worker](#job-worker). ### Workflow See [process](#process). ### Workflow engine A workflow engine is an essential part of any process automation tool. We call it an “engine” because it drives business processes from start to finish, no matter how complex the process and decision logic need to be. [Zeebe](#zeebe) is the workflow engine powering Camunda 8. ### Workflow instance See [process instance](#process-instance). ### Workflow instance variable See [process variable](#process-variable). ### Workspace A collaboration space within an organization, representing a team or business domain. A workspace is assigned members, roles, projects, and [environments](#environment), so all related work happens in one shared space. - [Workspaces](/components/concepts/workspaces.md) - [Workspace](/components/hub/workspace/index.md) ## Z ### Zeebe Zeebe is a highly scalable, cloud-native workflow engine used to automate business processes. It acts as the core component of Camunda 8. Zeebe is part of the [Orchestration Cluster](#orchestration-cluster) in Camunda 8. The main components of Zeebe are: - [Clients](#zeebe-client) - [Gateways](#zeebe-gateway) - [Brokers](#zeebe-broker) - [Exporters](#zeebe-exporter) A Zeebe deployment typically consists of multiple brokers and gateways, forming a [Zeebe cluster](#zeebe-cluster). ### Zeebe Broker The Zeebe Broker is the distributed [workflow engine](#workflow-engine) that tracks the state of active [process instances](#process-instance). The Zeebe Broker is the main part of the [Zeebe cluster](#zeebe-cluster), which does all the heavy work like processing, replicating, exporting, and everything based on [partitions](#partition). A Zeebe deployment often consists of more than one broker. Brokers can be partitioned for horizontal scalability and replicated for fault tolerance. - [Zeebe Broker](/components/zeebe/technical-concepts/architecture.md#brokers) ### Zeebe Client A Zeebe Client interacts with the [Zeebe Broker](#zeebe-broker) on behalf of the business application. Clients retrieve work from the [Zeebe cluster](#zeebe-cluster) via polling or job push. - [Zeebe Client](/components/zeebe/technical-concepts/architecture.md#clients) ### Zeebe cluster A Zeebe cluster represents a configuration of one or more [brokers](#zeebe-broker) collaborating to execute [processes](#process). Each [broker](#zeebe-broker) in a cluster acts as a [leader](#leader) or a [follower](#follower). - [Clustering](/components/zeebe/technical-concepts/clustering.md) ### Zeebe Exporter The Zeebe Exporter system provides an event stream of state changes within Zeebe. It represents a sink to which Zeebe will submit all [records](#record) within the [log](#log). This gives users of Zeebe an opportunity to persist [records](#record) with the log for future use as this data will not be available after log compaction. - [Zeebe Exporter](/components/zeebe/technical-concepts/architecture.md#exporters) ### Zeebe Gateway The Zeebe Gateway is a component of the [Zeebe cluster](#zeebe-cluster); it can be considered the contact point for the Zeebe cluster that allows [Zeebe clients](#zeebe-client) to communicate with [Zeebe brokers](#zeebe-broker) inside a Zeebe cluster. - [Zeebe Gateway](/self-managed/components/orchestration-cluster/zeebe/zeebe-gateway/zeebe-gateway-overview.md) --- ## Legal center - [Licensing and other legal terms](https://legal.camunda.com/licensing-and-other-legal-terms) - [Privacy and data protection](https://legal.camunda.com/privacy-and-data-protection) - [Environmental, social, and governance](https://legal.camunda.com/ESG) - [Data processing agreement](https://legal.camunda.com/dpa) --- ## Licensing ## Zeebe, Operate, Tasklist, Identity, and Optimize The source code for Zeebe, Operate, Tasklist, Identity, and Optimize is licensed to our users and customers under the [Camunda License v1](https://legal.camunda.com/licensing-and-other-legal-terms#camunda-license). The compiled software of these components is distributed under a proprietary license, which can be either the [Camunda Self-Managed Non-Production license](https://legal.camunda.com/#self-managed-non-production-terms) or the Camunda Self-Managed Enterprise Edition license (a copy of which you obtain when you contact Camunda). To use the software in production, [purchase the Camunda Self-Managed Enterprise Edition](https://camunda.com/platform/camunda-platform-enterprise-contact/). ## Camunda connectors The Connector SDK, REST connector, Connector Runtime Docker image, and Connectors Bundle Docker image are licensed under the [Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0). The source code of all other connectors provided by Camunda out of the box is licensed under the [Camunda License v1](https://legal.camunda.com/licensing-and-other-legal-terms#camunda-license). The compiled software is distributed under a proprietary license, which can be either the [Camunda Self-Managed Non-Production license](https://legal.camunda.com/#self-managed-non-production-terms) or the Camunda Self-Managed Enterprise Edition license (a copy of which you obtain when you contact Camunda). To use the software in production, [purchase the Camunda Self-Managed Enterprise Edition](https://camunda.com/platform/camunda-platform-enterprise-contact/). ## Web Modeler and Console The source code for the Web Modeler and Console is licensed to our users and customers under a proprietary license and is only available to users or customers on demand. The compiled software of these components is distributed under a proprietary license, which can be either the [Camunda Self-Managed Non-Production license](https://legal.camunda.com/#self-managed-non-production-terms) or the Camunda Self-Managed Enterprise Edition license (a copy of which you obtain when you contact Camunda). To use the software in production, [purchase the Camunda Self-Managed Enterprise Edition](https://camunda.com/platform/camunda-platform-enterprise-contact/). ## Camunda Hub The source code for Camunda Hub is licensed to our users and customers under a proprietary license and is only available to users or customers on demand. The compiled software of these components is distributed under a proprietary license, which can be either the [Camunda Self-Managed Non-Production license](https://legal.camunda.com/#self-managed-non-production-terms) or the Camunda Self-Managed Enterprise Edition license (a copy of which you obtain when you contact Camunda). To use the software in production, [purchase the Camunda Self-Managed Enterprise Edition](https://camunda.com/platform/camunda-platform-enterprise-contact/). ## Desktop Modeler The source code of Desktop Modeler is licensed under the MIT license as stated in the [LICENSE file](https://github.com/camunda/camunda-modeler/blob/master/LICENSE) in the root of the source code repository. This file is also shipped as `LICENSE.camunda-modeler.txt` with each modeler distribution. ## Camunda 8 documentation License information for our documentation can be found in the [LICENSE.txt](https://github.com/camunda/camunda-docs/blob/main/LICENSE.txt) of the Camunda 8 documentation repository. ## Terms & conditions For information not covered by the above license links, visit our [Terms and Conditions](https://legal.camunda.com/licensing-and-other-legal-terms). --- ## Camunda Docs MCP server ## About Add the Camunda Docs [(Model Context Protocol) MCP server](https://modelcontextprotocol.io/docs/getting-started/intro) to give your AI agent direct access to the latest official Camunda 8 documentation. For example, if you use an AI coding tool such as [Cursor](https://cursor.com/) or [Copilot](https://code.visualstudio.com/docs/copilot/overview), adding the MCP server can help ensure more accurate AI responses and code generation by accessing the current Camunda 8 documentation and context. The MCP server is available at the following URL: ``` https://camunda-docs.mcp.kapa.ai ``` ## Install ### One-click install You can perform a one-click installation or copy the MCP server URL via the **Use MCP** dropdown in the **Ask AI** button popup at the top of this documentation page. ### Manual install The steps required to manually install the MCP server varies depending on your IDE/AI tool. For example: **Prerequisites:** VS Code 1.102+ with GitHub Copilot enabled. Create an `mcp.json` file in your workspace `.vscode` folder: ```json title=".vscode/mcp.json" { "servers": { "camunda docs": { "type": "http", "url": "https://camunda-docs.mcp.kapa.ai" } } } ``` :::info For more information, refer to the [MCP servers in VS Code](https://code.visualstudio.com/docs/copilot/customization/mcp-servers) documentation. ::: Add the following to your `.cursor/mcp.json` file: ```json title=".cursor/mcp.json" { "mcpServers": { "camunda docs": { "type": "http", "url": "https://camunda-docs.mcp.kapa.ai" } } } ``` :::info For more information, refer to the [Cursor MCP](https://cursor.com/docs/context/mcp) documentation. ::: ChatGPT Desktop supports MCP servers in developer mode: 1. Open ChatGPT Desktop. 1. Navigate to **Settings > Features**. 1. Enable **Developer mode**. 1. Navigate to **Settings > MCP Servers**. 1. Click **Add Server** and enter: - **Name**: `camunda docs` - **URL**: `https://camunda-docs.mcp.kapa.ai` :::info For more information, refer to the [ChatGPT Desktop MCP](https://platform.openai.com/docs/guides/developer-mode) documentation. ::: Use the server URL `https://camunda-docs.mcp.kapa.ai` and refer to your client's documentation for installation instructions. Most clients accept the standard MCP protocol JSON configuration format: ```json { "mcpServers": { "camunda docs": { "url": "https://camunda-docs.mcp.kapa.ai" } } } ``` ## Usage and limits :::caution caution The Camunda Docs MCP server is not designed for use in production environments, high-volume automation, or as part of a CI/CD pipeline. It is provided to help support Camunda developer IDE queries and for coding assistance, evaluation, and testing. You must always check and validate AI generated content and code as responses can be inaccurate. ::: Once connected to the MCP server within your editor, you can ask context-aware questions about Camunda. For example: - "What is BPMN?" - "How do I build an AI agent?" - "What properties are changed for Camunda 8.8?" ### Authentication When connecting to the MCP server for the first time, you must authenticate with either Google or GitHub. This authentication is only used to enforce per-user rate limits and prevent abuse of the Camunda Docs MCP server: - 40 requests per user per hour. - 200 requests per user per day. If you sign in with GitHub, Kapa does not request access to your repositories, organizations, or email address. It uses only a stable GitHub user ID for rate limiting. :::note The MCP server is powered by the Kapa.ai AI assistant. Refer to the [Kapa documentation](https://docs.kapa.ai/integrations/mcp/overview#authentication) to learn more about authentication. ::: ### Tools The MCP server exposes a single semantic search tool: `search_camunda_knowledge_sources` This tool allows AI tools/agents to perform semantic retrieval over the Camunda 8 documentation and other public knowledge sources, such as forum posts, repos, podcasts, and product blogs. ## Example VS Code Copilot integration 1. Install the Camunda Docs MCP server. 1. In VS Code, open Copilot Chat. 1. Select **Agent** mode from the **Set Agent** drop-down menu. 1. Click **Configure Tools** to check the `search_camunda_knowledge_sources` tool is available and selected. 1. Ask context-aware questions about Camunda. --- ## Security notices :::tip Subscribe to security notices Subscribe to the [RSS feed](pathname:///rss/security/notices.xml) for security updates and get automatic notifications when new security notices are published. ::: ## Report a security issue or vulnerability Report security vulnerabilities to Camunda immediately, following the instructions at [Camunda Security](https://camunda.com/security#report-a-vulnerability). :::info To learn more about security at Camunda, including our security policy, security issue management, and more, see [Camunda.com/security](https://camunda.com/security). ::: ## Understand affected and fixed versions Each security notice lists affected and fixed versions by Camunda component (for example, Zeebe, Operate, Tasklist, Identity, Optimize, and Hub), not by Helm chart version. The Helm chart has its own version, tracked separately from the component versions it deploys. For Camunda 8.8 and later, the Orchestration Cluster unifies Zeebe, Operate, and Tasklist, so these components share the same version number. To check whether your Helm deployment is affected: 1. Identify the Helm chart version you are running. 1. In the [Helm chart version matrix](https://helm.camunda.io/camunda-platform/version-matrix/), find the component versions that the chart deploys. 1. Compare those component versions with the affected and fixed versions listed in the notice. ## Notice 70 ### Publication date October 26, 2026 ### Products affected - Camunda Web Modeler ### Impact Web Modeler Self-Managed is affected by [CVE-2026-111497](https://nvd.nist.gov/vuln/detail/CVE-2026-111497), a critical-severity out-of-bounds write in the Node.js Brotli decoder that can allow remote code execution. The Web Modeler Self-Managed 8.7 and 8.8 webapp images contain a Node.js runtime. An authenticated Web Modeler user can reach the affected decoder through the "import resources from URL" capability: the webapp server fetches URLs that the user supplies and automatically decodes Brotli-compressed responses. A server controlled by an attacker can return a crafted Brotli response that triggers the out-of-bounds write in the Node.js process. ### How to determine if the installation is affected You are using: - Web Modeler Self-Managed ≤ 8.7.27, or ≤ 8.8.23 ### Solution Camunda has provided the following releases which contain the fix (Node.js 24.22.1): - Web Modeler Self-Managed 8.7.28, 8.8.24 No action is necessary for Camunda SaaS. ## Notice 69 ### Publication date October 26, 2026 ### Products affected - Hub Self-Managed 8.10 - Web Modeler Self-Managed 8.7, 8.8, and 8.9 ### Impact Two critical vulnerabilities in PHP ([CVE-2026-109618](https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w) and [CVE-2026-109619](https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp)) were disclosed on 20 October 2026. Our security and engineering teams have analyzed both in detail. We found no way to exploit them in any Camunda product. Out of an abundance of caution, we have released updated images with the fixed PHP version. #### Which products are concerned - SaaS: Camunda SaaS does not use PHP and is not affected. - Self-Managed: The websockets component of Hub (version ≤ 8.10.3) and Web Modeler (versions ≤ 8.9.12, ≤ 8.8.23, or ≤ 8.7.27) runs PHP 8.5.11, a version in the affected range. - No other Camunda 8 component uses PHP. #### Why the vulnerabilities cannot be exploited Both vulnerabilities are in the way PHP processes incoming web requests when it runs inside a web server (for example PHP-FPM or Apache with mod_php). The websockets component does not run PHP that way. It runs as a standalone process that reads and validates requests itself, never uses PHP's request handling, and accepts only small, size-limited messages. None of the attack paths described in the CVE advisories can reach it. #### What you need to do No immediate action is required. We recommend updating to the new images as part of your normal update process. If your own policy requires it, you can stop the websockets component until you have updated. Web Modeler and Hub keep working, and all modeling, saving, deployment, versioning and Git sync functionality is unaffected. Only features that rely on real-time updates are affected while the component is stopped. - Real-time collaboration: online presence, live diagram updates, live comments, attention markers and live changes to project permissions are not delivered. Collaborators see each other's changes only after reloading the page, concurrent changes might conflict / overwrite each other. - AI copilot (8.8 and later): copilot responses are delivered through the websockets component and are not returned while it is stopped. - Intelligent Document Processing (8.8 and later): extraction results are delivered through the websockets component and are not shown in the modeler while it is stopped. ### How to determine if the installation is affected You are using: - Hub ≤ 8.10.3 - Web Modeler ≤ 8.9.12, ≤ 8.8.23, or ≤ 8.7.27 ### Solution Camunda has provided the following releases which contain the fix (update to PHP 8.5.12): - Hub 8.10.4 - Web Modeler 8.9.13, 8.8.24, 8.7.28 ## Notice 68 ### Publication date October 20, 2026 ### Products affected - Management Identity Self-Managed 8.8, 8.9, and 8.10 - Orchestration Cluster 8.9 and 8.10, when PostgreSQL is used as secondary storage (RDBMS) - Web Modeler Self-Managed 8.8 and 8.9, when PostgreSQL is used as RDBMS - Hub Self-Managed 8.10, when PostgreSQL is used as RDBMS ### Impact The PostgreSQL JDBC driver (`org.postgresql:postgresql`) versions 42.7.11 to 42.7.13 used by Camunda are affected by [CVE-2026-107314](https://github.com/pgjdbc/pgjdbc/security/advisories/GHSA-rhp9-mr79-r74h). If the `requireAuth` connection property excludes every authentication method, or lists no method at all, the driver doesn't restrict the authentication method. A man-in-the-middle attacker between Camunda and PostgreSQL can then request cleartext password authentication and obtain the database password. The following values trigger the issue: - `requireAuth=!password,!md5,!gss,!sspi,!scram-sha-256,!none` (excludes every method) - `requireAuth=,` (lists no method) Severity: High. The upstream CVSS 3.1 base score for the library is 5.9 (`AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N`). The `requireAuth` property has no default value, and Camunda doesn't set it. Only installations that set the property as described are affected. ### How to determine if the installation is affected Your installation is affected if all of these conditions are true: - You run one of the following Camunda products on a version earlier than the fixed versions listed in the solution: - Management Identity 8.8 to 8.10 - Orchestration Cluster 8.9 or 8.10 with PostgreSQL as secondary storage - Web Modeler 8.8 or 8.9 with PostgreSQL - Hub 8.10 with PostgreSQL - The JDBC URL or datasource properties for the PostgreSQL connection set `requireAuth` to a value that excludes all authentication methods, or that contains no method. - The database connection doesn't use TLS with full server verification (`sslmode=verify-full`). ### Solution Camunda has provided the following releases which contain the fix (driver version 42.7.14): - Orchestration Cluster: 8.10.4, 8.9.24 - Management Identity: 8.10.1, 8.9.11, 8.8.19 - Web Modeler: 8.9.11, 8.8.22 - Hub: 8.10.3 If your configuration sets `requireAuth` to one of the affected values, replace it with a positive list of the authentication methods your PostgreSQL server uses, for example `requireAuth=scram-sha-256`, before you upgrade. After the upgrade, the driver refuses connections that use a value excluding every method (SQLState `08004`), and rejects a value that lists no method as invalid (SQLState `22023`). If you can't upgrade immediately, use one of these workarounds: - Replace the `requireAuth` value with a positive list of allowed methods on your current version, as described above. - Connect to PostgreSQL with `sslmode=verify-full`. ## Notice 67 ### Publication date October 9, 2026 ### Products affected - Camunda Helm chart for Camunda 8.7, 8.8, and 8.9, when the bundled Keycloak subchart (`identityKeycloak`) uses the default Community Edition image ### Impact Keycloak is affected by [CVE-2026-18963](https://github.com/advisories/GHSA-4gv3-mc9p-5wqc), a flaw in the reset-credentials flow. An unauthenticated attacker can force a password reset for any user without the email verification step, set new credentials, and take over the account, including administrator accounts. Severity: Critical (CVSS 9.1). The bundled Keycloak subchart in the Camunda 8.7, 8.8, and 8.9 Helm charts defaults to the Community Edition image `camunda/keycloak:26.3.3`. This image is based on the Bitnami Keycloak image, which Bitnami no longer updates. As a result, Camunda cannot publish a fixed image on this line, and the image will not receive this or any future fix. Camunda SaaS is not affected. ### How to determine if the installation is affected The following Keycloak versions are affected: - ≥ 26.0.0, < 26.4.15 - ≥ 26.5.0, < 26.6.6 - ≥ 26.7.0, < 26.7.2 Your installation is affected if all of these conditions are true: - You deploy Camunda 8.7, 8.8, or 8.9 with the Camunda Helm chart, and the bundled Keycloak subchart is enabled (`identityKeycloak.enabled: true`). - You use the default `camunda/keycloak` image (tag `26.3.3`, any `26.3.3-*` rebuild tag, or the `bitnami-*` and `latest-bitnami` tags), or another Keycloak image with an affected version. Camunda Helm chart 12.13.6 (Camunda 8.7), 13.13.2 (Camunda 8.8), 14.10.1 (Camunda 8.9), and later show a security warning when the rendered Keycloak image is affected. If you run Keycloak outside the Camunda Helm chart, your installation is affected if your Keycloak version is in one of the affected ranges. The Camunda 8.10 Helm chart does not include a bundled Keycloak subchart and is not affected. If you use your own Keycloak with Camunda 8.10, check your Keycloak version as described above. ### Solution The following Keycloak versions contain the fix: - 26.4.15 - 26.6.6 - 26.7.2 Camunda does not provide a fixed Community Edition image for the bundled Keycloak subchart. Use one of these options: - **Use the Camunda enterprise images:** Enterprise customers can switch to the Bitnami Premium images provided by Camunda. The enterprise Keycloak image `registry.camunda.cloud/keycloak-ee/keycloak` receives security updates and contains the fix from version 26.7.2. Camunda Helm chart 12.13.4 (Camunda 8.7), 13.12.8 (Camunda 8.8), 14.8.5 (Camunda 8.9), and later use a fixed Keycloak version in `values-enterprise.yaml`. - **Migrate away from the bundled Keycloak subchart:** Replace the bundled Keycloak with the Keycloak Operator, a managed service, or another maintained Keycloak deployment that runs a fixed version. For step-by-step instructions, see [migrate from Bitnami subcharts](/self-managed/deployment/helm/operational-tasks/migration-from-bitnami/index.md). After you apply the fix, examine Keycloak events and user accounts for password resets that you do not expect, and reset the credentials of any affected accounts. No action is necessary for Camunda SaaS. ## Notice 66 ### Publication date October 7, 2026 ### Products affected - Camunda Optimize Self-Managed ### Impact The Docker images `camunda/optimize:8.10.0` and `camunda/optimize:8.10.1` were available for a short time before the Camunda 8.10 release announcement. These images contain a defect in the Optimize access check. In Self-Managed installations that use Management Identity, users without the Optimize permission can get access to Optimize. This includes read access to Optimize data, and changes to Optimize entities such as reports and dashboards. Exploitation requires a user who can log in to the identity provider that Optimize uses. Unauthenticated access is not possible. We removed the affected images from Docker Hub. No official Camunda Helm chart release referenced these images. Camunda SaaS is not affected. ### How to determine if the installation is affected Your installation is affected if all of these conditions are true: - You run Camunda Optimize Self-Managed with the image `camunda/optimize:8.10.0` or `camunda/optimize:8.10.1`. This includes images with these tags that you copied to your own registry. If you install with an official Camunda Helm chart release and do not override the Optimize image, you do not use these images. - Optimize uses Management Identity for authentication. Optimize 8.9 and earlier versions are not affected. ### Solution Upgrade Optimize to version 8.10.2 immediately. Optimize 8.10.2 contains the fix. If you used the image `camunda/optimize:8.10.0` or `camunda/optimize:8.10.1`: 1. Upgrade to `camunda/optimize:8.10.2`. 1. Remove the affected images from your private registries and image caches. 1. Examine the Optimize data, for example reports, dashboards and collections, for changes that you do not expect. No action is necessary for Camunda SaaS. ## Notice 65 ### Publication date October 7, 2026 ### Products affected - Camunda Orchestration Cluster (Zeebe, Operate, Tasklist) - Camunda Zeebe (8.7) ### Impact The document endpoint of the Orchestration Cluster REST API rendered uploaded document content in the browser instead of downloading it. A user allowed to upload documents could use this to run scripts in the application's origin with the privileges of another user who opens the document, including administrators. Severity: High. ### How to determine if the installation is affected You are using: - Camunda Orchestration Cluster ≤ 8.9.22 or ≤ 8.8.40 - Camunda Zeebe ≤ 8.7.42 ### Solution Camunda has provided the following releases which contain the fix: - Camunda Orchestration Cluster 8.10.0, 8.9.23, 8.8.41 - Camunda Zeebe 8.7.43 The document endpoint now serves all document content with a `Content-Security-Policy: sandbox` header, and returns documents as downloads (`Content-Disposition: attachment`), except for PDF files and images, which are still displayed in the browser. On Camunda 8 SaaS, this fix is included automatically unless you've opted out of [auto-updates](/components/saas/auto-updates.md), in which case you'll need to update your cluster manually. ## Notice 64 ### Publication date September 21, 2026 ### Products affected - Camunda Orchestration Cluster (Zeebe, Operate, Tasklist) - Camunda Connectors - Camunda Web Modeler ### Impact A remote code execution vulnerability was identified in Camunda's FEEL expression engine allowing execution of arbitrary code within the scope of a Camunda 8 server operating system process. Severity: Critical 9.9 (CVSS v3.1: AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H). This vulnerability was identified by Camunda's Security Team, with no known report or exploit beyond our own security investigation. A CVE identifier has been requested and this notice will be updated once it is assigned. For further details, see [GHSA-vx3p-v6cf-vfjw](https://github.com/camunda/feel-scala/security/advisories/GHSA-vx3p-v6cf-vfjw). ### How to determine if the installation is affected You are using: - Camunda Orchestration Cluster ≤ 8.9.19 or ≤ 8.8.37 - Camunda Zeebe, Operate, or Tasklist ≤ 8.7.39 - Camunda Connectors ≤ 8.9.10, ≤ 8.8.19, or ≤ 8.7.25 - Web Modeler ≤ 8.9.8, or ≤ 8.8.19 Any component that bundles `org.camunda.feel:feel-engine` up to and including 1.22.0 is affected. Versions older than the ones listed above, including versions that have reached end of maintenance, are also affected, with the exception of Web Modeler, where versions below 8.8.0 are not affected. Exploitation requires one of the following: - The attacker is an authenticated and authorized user of the Camunda Orchestration Cluster REST API 8.9+ who has the privilege to invoke the [Evaluate Expression API](/apis-tools/orchestration-cluster-api-rest/specifications/evaluate-expression.api.mdx). The authorization requires: - Permission granted to user or client (directly or via role) - Resource type: `EXPRESSION` - Permission: `EVALUATE` - By default, this permission is granted as part of the admin role - The attacker is an authenticated and authorized user who has the privilege to deploy BPMN or DMN to the Camunda Orchestration Cluster: - Prior to 8.8: API access (gRPC or REST) - Starting 8.8: via gRPC or REST - The authorization requires: - Permission granted to user or client (directly or via role) - Resource type: `Resource` - Permission: `CREATE` - By default, this permission is granted as part of the admin role - The attacker is an authenticated and authorized user of Camunda Web Modeler ≤ 8.9.8 or ≤ 8.8.19 ### Solution Camunda has provided the following releases which contain the fix: - Camunda Orchestration Cluster 8.9.21, 8.8.39 - Camunda Zeebe, Operate, Tasklist 8.7.41 - Camunda Connectors 8.9.12, 8.8.21, 8.7.27 - Web Modeler Self-Managed 8.9.9, 8.8.20 These releases bundle a patched FEEL engine (1.22.1, 1.21.1, 1.20.3, 1.19.6, 1.18.6, 1.17.13, 1.16.6, or 1.15.5, depending on the component). If you embed the FEEL engine directly, upgrade `org.camunda.feel:feel-engine` to the patched version of your 1.x line. Camunda 8 SaaS was fully remediated on September 12 and 13, 2026, ahead of this notice. All clusters across all supported versions, and Web Modeler SaaS, were updated to patched builds; no action is required on your part. Clusters that were suspended at that time receive the patched version when they resume. ## Notice 63 ### Publication date September 21, 2026 ### Products affected - Camunda Optimize ### Impact The application was vulnerable to the following vulnerabilities in the embedded Eclipse Jetty web server: - [CVE-2026-19203](https://nvd.nist.gov/vuln/detail/CVE-2026-19203), where a flaw in HTTP/1.1 chunked request parsing accepts a lone line feed (`LF`) character as a terminator in parts of chunked requests. When deployed behind an intermediary reverse proxy that requires strict `CRLF` delimiters, this discrepancy in request boundary interpretation can lead to HTTP request smuggling. - [CVE-2026-12611](https://nvd.nist.gov/vuln/detail/CVE-2026-12611), where a race condition in the HTTP/2 server implementation when handling concurrent `RST_STREAM` and `GOAWAY` frames from a client can cause write-blocked threads to never be unblocked, potentially leading to thread exhaustion and a Denial of Service. The default Camunda Optimize configuration is not exploitable; this vulnerability only applies when HTTP/2 support is explicitly enabled (`container.http2Enabled: true`). ### How to determine if the installation is affected You are using: - Camunda Optimize ≤ 8.7.27 ### Solution Camunda has provided the following releases which contain the fix: - Camunda Optimize 8.7.28 ## Notice 62 ### Publication date September 8, 2026 ### Products affected - Camunda Web Modeler ### Impact The application was vulnerable to [CVE-2026-75140](https://nvd.nist.gov/vuln/detail/CVE-2026-75140) in `jsoup`'s `XmlTreeBuilder` which could allow an attacker to exhaust JVM heap memory by supplying a deeply nested XML document (in this case a BPMN or DMN diagram) with uniquely-namespaced elements. ### How to determine if the installation is affected You are using: - Web Modeler Self-Managed ≤ 8.9.7, ≤ 8.8.18, or ≤ 8.7.25 ### Solution Camunda has provided the following releases that contain the fix: - Web Modeler Self-Managed 8.9.8, 8.8.19, 8.7.26 The fix was deployed to Web Modeler SaaS on August 29, 2026, 10:35 CET. ## Notice 61 ### Publication date September 8, 2026 ### Products affected - Camunda Connectors ### Impact The connector runtime resolves a secret reference (for example, `{{secrets.MY_API_KEY}}`) wherever that literal text appears in a connector's input, without restricting resolution to the field where the reference was declared. Under the following conditions, an attacker may be able to cause a connector to resolve and disclose a secret outside its intended scope: - The process uses [connectors](/components/connectors/introduction.md) and [secrets](/components/saas/clusters/manage-secrets.md). - Untrusted input reaches a process variable — for example, through a user task, an inbound connector such as a webhook or email, or an API call. - That process variable is passed, unsanitized, into a connector field (for example, an email body or an HTTP request field). - The attacker can guess or know the name of a secret that exists in that context. This does not require knowing the secret's value, only its name. - The connector's destination is one the attacker controls or can observe, or the attacker can also influence the destination itself (for example, a recipient address or URL supplied through another process variable). Severity: High (CVSS 7.5). ### How to determine if the installation is affected You are using: - Camunda Connectors 8.1.x through 8.9.x, on Self-Managed or SaaS, with connectors and secrets configured as described above. ### Solution Camunda has released a secret filter for the connector runtime, defaulting to `STRICT` mode. In `STRICT` mode, a connector only resolves a secret that's present for that specific field in the deployed BPMN XML, which structurally closes this vulnerability for both outbound and inbound connectors. This is a breaking change: after upgrading, a connector field that relied on resolving an undeclared secret stops resolving it. See [secret filter](/self-managed/components/connectors/connectors-configuration.md#secret-filter) for configuration details, including how to change the mode. Camunda has provided the following releases which contain the fix: - Camunda Connectors 8.9.10, 8.8.19, 8.7.25, 8.6.28 On Camunda 8 SaaS, this fix is included automatically unless you've opted out of [auto-updates](/components/saas/auto-updates.md), in which case you'll need to update your cluster manually. You can also change the mode per cluster in [cluster settings](/components/saas/clusters/settings.md#secret-filter-mode). **Interim mitigation**: - Review your process deployments against the preconditions above. - If a process may be affected, search its process instance variables for a secret-reference literal — for example, using the [Search process instances API](/apis-tools/orchestration-cluster-api-rest/specifications/search-process-instances.api.mdx) with a `$like` filter on 8.8 and later. This check is not conclusive: it only reflects the variable's current value, within your retention period. - Adjust the affected process so the preconditions above no longer hold, or sanitize the process variable to remove secret-reference syntax before it reaches a connector field. - Sanitize the respective process variable for example by replacing `secrets` keyword: `= replace(emailBody, "secrets?\.", "secretx_", "i")` - Rotate any secret that may have been exposed, following your organization's secret-management procedures. ## Notice 60 ### Publication date September 2, 2026 ### Products affected - Camunda Zeebe - Camunda Tasklist - Camunda Operate - Camunda Optimize ### Impact The application was vulnerable to [CVE-2026-71290](https://nvd.nist.gov/vuln/detail/CVE-2026-71290), where the embedded `httpclient5` library's asynchronous transport, used to connect to the Elasticsearch/OpenSearch secondary storage, skipped hostname verification during the TLS handshake. An attacker positioned to intercept network traffic between the affected component and its Elasticsearch/OpenSearch backend (for example, via DNS spoofing, BGP hijacking, or a compromised host on a shared network segment) could impersonate the backend, intercept the Basic authentication credentials sent to it, read or tamper with process data (including variables and process instance data) in transit, and inject forged responses back to the application. Exploitation requires this man-in-the-middle network position; it does not require bypassing authentication on the application's own API. Camunda is not aware of any known exploitation of this vulnerability. ### How to determine if the installation is affected You are using: - Camunda Zeebe ≤ 8.7.37 - Camunda Tasklist ≤ 8.7.37 - Camunda Operate ≤ 8.7.37 - Camunda Optimize ≤ 8.7.26 ### Solution Camunda has provided the following releases which contain the fix: - Camunda Zeebe 8.7.38 - Camunda Tasklist 8.7.38 - Camunda Operate 8.7.38 - Camunda Optimize 8.7.27 ## Notice 59 ### Publication date July 29, 2026 ### Products affected - Camunda Web Modeler ### Impact The version of `undertow-core` used by Camunda Web Modeler was affected by the following vulnerabilities which could potentially allow an attacker to perform request smuggling by exploiting flaws in the HTTP request header parsing: - [CVE-2026-28367](https://nvd.nist.gov/vuln/detail/CVE-2026-28367) - [CVE-2026-28368](https://nvd.nist.gov/vuln/detail/CVE-2026-28368) - [CVE-2026-28369](https://nvd.nist.gov/vuln/detail/CVE-2026-28369) ### How to determine if the installation is affected You are using: - Web Modeler Self-Managed ≤ 8.8.14, or ≤ 8.7.21 ### Solution Camunda has provided the following releases that contain the fix: - Web Modeler Self-Managed 8.8.15, 8.7.22 ## Notice 58 ### Publication date July 14, 2026 ### Products affected - Camunda Web Modeler ### Impact The application was vulnerable to [CVE-2026-54399](https://nvd.nist.gov/vuln/detail/CVE-2026-54399), where a flaw in the HTTP/1.1 message parser in Apache HttpComponents Core allows a remote attacker to cause a denial of service through memory exhaustion by sending messages with an excessive number of headers or excessively long headers. The vulnerable library is only used in outgoing HTTP requests from Web Modeler to the Camunda 8 Orchestration Cluster API. To exploit the vulnerability, an attacker would need to be able to control or intercept the API responses through a prior attack. Web Modeler's inbound HTTP requests are handled by a different library, so the vulnerable code path is not reachable from external, untrusted client traffic. ### How to determine if the installation is affected - You are using Web Modeler Self-Managed ≤ 8.9.5, ≤ 8.8.16, or ≤ 8.7.23. ### Solution Camunda has provided the following releases which contain the fix: - Web Modeler Self-Managed 8.9.6, 8.8.17, 8.7.24 The fix was deployed to Web Modeler SaaS on July 8, 2026, 08:12 CET. ## Notice 57 ### Publication date July 14, 2026 ### Products affected - Camunda Web Modeler ### Impact The application was vulnerable to [CVE-2026-54291](https://nvd.nist.gov/vuln/detail/CVE-2026-54291), where database connections configured with `channelBinding=require` can be silently downgraded from SCRAM-SHA-256-PLUS with channel binding to plain SCRAM-SHA-256 without it, losing the man-in-the-middle protection the setting is meant to guarantee. ### How to determine if the installation is affected - You are using Web Modeler Self-Managed ≤ 8.9.5, ≤ 8.8.16, or ≤ 8.7.23. - _And_: You are using PostgreSQL with SCRAM authentication over SSL/TLS and `channelBinding=require`. ### Solution Camunda has provided the following releases which contain the fix: - Web Modeler Self-Managed 8.9.6, 8.8.17, 8.7.24 The fix was deployed to Web Modeler SaaS on July 8, 2026, 08:12 CET. ## Notice 56 ### Publication date July 14, 2026 ### Products affected - Camunda Optimize ### Impact The application was vulnerable to [CVE-2026-13006](https://nvd.nist.gov/vuln/detail/CVE-2026-13006), an arbitrary code execution vulnerability in the `logback-core` library used by Camunda Optimize. An attacker who already has write access to the Logback configuration file, or the ability to inject an environment variable evaluated at startup, could execute arbitrary code in the Optimize process. Exploitation also requires the Janino library on the classpath and a Logback configuration that uses conditional (``) processing, both of which are present in Camunda Optimize. This vulnerability is not remotely exploitable; it requires an attacker to already hold privileged local access to the deployment. ### How to determine if the installation is affected You are using: - Camunda Optimize ≤ 8.7.24 ### Solution Camunda has provided the following release which contains the fix: - Camunda Optimize 8.7.25 ## Notice 55 ### Publication date July 1, 2026 ### Products affected - Camunda Identity - Camunda Zeebe - Camunda Tasklist - Camunda Operate - Camunda Optimize ### Impact The application was vulnerable to [CVE-2026-40983](https://nvd.nist.gov/vuln/detail/CVE-2026-40983), where an unauthenticated attacker can send a specially crafted gRPC request to trigger uncontrolled resource consumption inside Micrometer's gRPC instrumentation layer, rendering the affected component unresponsive until restarted. ### How to determine if the installation is affected You are using: - Camunda Identity ≤ 8.9.4, ≤ 8.8.13, or ≤ 8.7.20 - Camunda Zeebe ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.33 - Camunda Tasklist ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.33 - Camunda Operate ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.33 - Camunda Optimize ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.22 ### Solution Camunda has provided the following releases which contain the fix: - Camunda Identity 8.9.5, 8.8.14, 8.7.21 - Camunda Zeebe 8.9.10, 8.8.29, 8.7.34 - Camunda Tasklist 8.9.10, 8.8.29, 8.7.34 - Camunda Operate 8.9.10, 8.8.29, 8.7.34 - Camunda Optimize 8.9.10, 8.8.29, 8.7.23 ## Notice 54 ### Publication date July 1, 2026 ### Products affected - Camunda Identity - Camunda Zeebe - Camunda Tasklist - Camunda Operate - Camunda Optimize - Camunda Web Modeler ### Impact The application was vulnerable to [CVE-2026-40984](https://nvd.nist.gov/vuln/detail/CVE-2026-40984), where an unauthenticated attacker can send a specially crafted HTTP request to trigger uncontrolled resource consumption inside Micrometer's HTTP instrumentation layer, rendering the affected component unresponsive until restarted. ### How to determine if the installation is affected You are using: - Camunda Identity ≤ 8.9.4, ≤ 8.8.13, or ≤ 8.7.20 - Camunda Zeebe ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.33 - Camunda Tasklist ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.33 - Camunda Operate ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.33 - Camunda Optimize ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.22 - Web Modeler Self-Managed ≤ 8.9.4, ≤ 8.8.15, or ≤ 8.7.22 ### Solution Camunda has provided the following releases which contain the fix: - Camunda Identity 8.9.5, 8.8.14, 8.7.21 - Camunda Zeebe 8.9.10, 8.8.29, 8.7.34 - Camunda Tasklist 8.9.10, 8.8.29, 8.7.34 - Camunda Operate 8.9.10, 8.8.29, 8.7.34 - Camunda Optimize 8.9.10, 8.8.29, 8.7.23 - Web Modeler Self-Managed 8.9.5, 8.8.16, 8.7.23 ## Notice 53 ### Publication date July 1, 2026 ### Products affected - Camunda Identity - Camunda Zeebe - Camunda Tasklist - Camunda Operate - Camunda Optimize - Camunda Web Modeler ### Impact The application was vulnerable to [CVE-2026-11400](https://nvd.nist.gov/vuln/detail/CVE-2026-11400), where a successful exploit grants the attacker elevated database privileges on Amazon Aurora PostgreSQL, potentially enabling unauthorized read/write access to all application data stored in the database. ### How to determine if the installation is affected You are using: - Camunda Identity ≤ 8.9.4 - Camunda Zeebe ≤ 8.9.10 - Camunda Tasklist ≤ 8.9.10 - Camunda Operate ≤ 8.9.10 - Camunda Optimize ≤ 8.9.10 - Web Modeler Self-Managed ≤ 8.9.4, ≤ 8.8.15, or ≤ 8.7.22 And your deployment is running on Amazon Aurora PostgreSQL with the AWS Advanced JDBC Wrapper configured to use the GlobalDatabasePlugin (for example, by setting `wrapperPlugins=globaldb` or equivalent in the JDBC connection URL). This is not the default Camunda configuration. ### Solution Camunda has provided the following releases which contain the fix: - Camunda Identity 8.9.5 - Camunda Zeebe 8.9.11 - Camunda Tasklist 8.9.11 - Camunda Operate 8.9.11 - Camunda Optimize 8.9.11 - Web Modeler Self-Managed 8.9.5, 8.8.16, 8.7.23 ## Notice 52 ### Publication date July 1, 2026 ### Products affected - Camunda Zeebe - Camunda Tasklist - Camunda Operate - Camunda Web Modeler ### Impact The application was vulnerable to [CVE-2026-47691](https://nvd.nist.gov/vuln/detail/CVE-2026-47691), where an attacker controlling a subdomain's name server can provide crafted NS records to poison the DNS cache for parent domains, potentially redirecting users to malicious servers and leading to information disclosure or integrity compromise. ### How to determine if the installation is affected You are using: - Camunda Zeebe ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.33 - Camunda Tasklist ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.33 - Camunda Operate ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.33 - Web Modeler Self-Managed ≤ 8.9.4, ≤ 8.8.15, or ≤ 8.7.22 ### Solution Camunda has provided the following releases which contain the fix: - Camunda Zeebe 8.9.10, 8.8.29, 8.7.34 - Camunda Tasklist 8.9.10, 8.8.29, 8.7.34 - Camunda Operate 8.9.10, 8.8.29, 8.7.34 - Web Modeler Self-Managed 8.9.5, 8.8.16, 8.7.23 ## Notice 51 ### Publication date July 1, 2026 ### Products affected - Camunda Zeebe - Camunda Tasklist - Camunda Operate - Camunda Web Modeler ### Impact The application was vulnerable to [CVE-2026-45674](https://nvd.nist.gov/vuln/detail/CVE-2026-45674), where a remote attacker can achieve information disclosure or data manipulation by crafting malicious DNS responses. ### How to determine if the installation is affected You are using: - Camunda Zeebe ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.33 - Camunda Tasklist ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.33 - Camunda Operate ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.33 - Web Modeler Self-Managed ≤ 8.9.4, ≤ 8.8.15, or ≤ 8.7.22 ### Solution Camunda has provided the following releases which contain the fix: - Camunda Zeebe 8.9.10, 8.8.29, 8.7.34 - Camunda Tasklist 8.9.10, 8.8.29, 8.7.34 - Camunda Operate 8.9.10, 8.8.29, 8.7.34 - Web Modeler Self-Managed 8.9.5, 8.8.16, 8.7.23 ## Notice 50 ### Publication date July 1, 2026 ### Products affected - Camunda Identity - Camunda Zeebe - Camunda Tasklist - Camunda Operate - Camunda Optimize ### Impact The application was vulnerable to [CVE-2026-41842](https://nvd.nist.gov/vuln/detail/CVE-2026-41842), where an attacker could send requests that are slow to resolve, keeping HTTP connections busy and potentially causing a Denial of Service. This is a Denial of Service vulnerability in Spring MVC and WebFlux static resource resolution. The default Camunda configuration is not exploitable; the vulnerability requires both versioned resource resolution to be enabled (for example, by setting `spring.web.resources.chain.strategy.content.enabled=true`) and static resources to be served from the filesystem (for example, by setting `spring.web.resources.static-locations=file:/...`), neither of which is present in the default configuration. ### How to determine if the installation is affected You are using: - Camunda Identity ≤ 8.9.4, ≤ 8.8.13, or ≤ 8.7.20 - Camunda Zeebe ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.33 - Camunda Tasklist ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.33 - Camunda Operate ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.33 - Camunda Optimize ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.22 And your deployment has versioned resource resolution enabled and static resources served from the filesystem. ### Solution Camunda has provided the following releases which contain the fix: - Camunda Identity 8.9.5, 8.8.14, 8.7.21 - Camunda Zeebe 8.9.10, 8.8.29, 8.7.34 - Camunda Tasklist 8.9.10, 8.8.29, 8.7.34 - Camunda Operate 8.9.10, 8.8.29, 8.7.34 - Camunda Optimize 8.9.10, 8.8.29, 8.7.23 ## Notice 49 ### Publication date July 1, 2026 ### Products affected - Camunda Identity - Camunda Zeebe - Camunda Tasklist - Camunda Operate - Camunda Optimize - Camunda Web Modeler ### Impact The application was vulnerable to [CVE-2026-41841](https://nvd.nist.gov/vuln/detail/CVE-2026-41841), where an attacker could gain access to a protected static resource if a resource with the same name had previously been resolved from a publicly accessible handler and cached server-side. This is an information disclosure vulnerability in Spring MVC and WebFlux static resource handling. The default Camunda configuration is not exploitable; the vulnerability requires server-side resource-chain caching to be enabled (for example, by setting `spring.web.resources.chain.cache=true`) and at least one resource handler that serves authentication-protected assets, neither of which is present in the default configuration. ### How to determine if the installation is affected You are using: - Camunda Identity ≤ 8.9.4, ≤ 8.8.13, or ≤ 8.7.20 - Camunda Zeebe ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.33 - Camunda Tasklist ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.33 - Camunda Operate ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.33 - Camunda Optimize ≤ 8.9.9, ≤ 8.8.28, or ≤ 8.7.22 - Camunda Web Modeler ≤ 8.9.4, ≤ 8.8.15, or ≤ 8.7.22 And your deployment has server-side resource-chain caching enabled with at least one access-controlled resource handler configured. ### Solution Camunda has provided the following releases which contain the fix: - Camunda Identity 8.9.5, 8.8.14, 8.7.21 - Camunda Zeebe 8.9.10, 8.8.29, 8.7.34 - Camunda Tasklist 8.9.10, 8.8.29, 8.7.34 - Camunda Operate 8.9.10, 8.8.29, 8.7.34 - Camunda Optimize 8.9.10, 8.8.29, 8.7.23 - Camunda Web Modeler 8.9.5, 8.8.16, 8.7.23 ## Notice 48 ### Publication date May 13, 2026 ### Products affected - Camunda Identity - Camunda Zeebe - Camunda Tasklist - Camunda Operate - Camunda Web Modeler ### Impact The application was vulnerable to [CVE-2026-42198](https://nvd.nist.gov/vuln/detail/CVE-2026-42198), where an attacker with the privileges to impersonate or perform a man-in-the-middle (MITM) attack on the PostgreSQL server can force the JDBC driver to perform SCRAM authentication with a very large iteration count. This can lead to high CPU usage during the SCRAM PBKDF2 computation before authentication can fail. Web Modeler and Identity are affected by this CVE when run with their default configuration. Zeebe, Operate, and Tasklist are only affected in a self-managed setup when running the application with RDBMS as secondary storage with a PostgreSQL server configured as the endpoint. ### How to determine if the installation is affected You are using: - Camunda Identity ≤ 8.9.2, ≤ 8.8.11, or ≤ 8.7.18 - Camunda Zeebe ≤ 8.9.2, ≤ 8.8.23, or ≤ 8.7.28 - Camunda Tasklist ≤ 8.9.2, ≤ 8.8.23, or ≤ 8.7.28 - Camunda Operate ≤ 8.9.2, ≤ 8.8.23, or ≤ 8.7.28 - Camunda Web Modeler ≤ 8.9.2, ≤ 8.8.13, or ≤ 8.7.20 ### Solution Camunda has provided the following releases which contain the fix: - Camunda Identity 8.9.3, 8.8.12, 8.7.19 - Camunda Zeebe 8.9.3, 8.8.24, 8.7.29 - Camunda Tasklist 8.9.3, 8.8.24, 8.7.29 - Camunda Operate 8.9.3, 8.8.24, 8.7.29 - Camunda Web Modeler 8.9.3, 8.8.14, 8.7.21 ## Notice 47 ### Publication date May 7, 2026 ### Products affected - Camunda Identity - Camunda Zeebe - Camunda Tasklist - Camunda Operate - Camunda Optimize - Camunda Web Modeler ### Impact The application was vulnerable to [CVE-2026-40973](https://nvd.nist.gov/vuln/detail/CVE-2026-40973), where a local attacker could take control of the embedded Spring Boot `ApplicationTemp` directory. If `server.servlet.session.persistent` is set to `true`, this could allow session hijacking or arbitrary code execution. Default Camunda 8 deployments are not affected. ### How to determine if the installation is affected You are using: - Camunda Identity ≤ 8.9.1, ≤ 8.8.11, or ≤ 8.7.18 - Camunda Zeebe ≤ 8.9.1, ≤ 8.8.23, or ≤ 8.7.28 - Camunda Tasklist ≤ 8.9.1, ≤ 8.8.23, or ≤ 8.7.28 - Camunda Operate ≤ 8.9.1, ≤ 8.8.23, or ≤ 8.7.28 - Camunda Optimize ≤ 8.9.1, ≤ 8.8.8, or ≤ 8.7.20 - Camunda Web Modeler ≤ 8.9.1, ≤ 8.8.13, or ≤ 8.7.20 ### Solution Camunda has provided the following releases which contain the fix: - Camunda Identity 8.9.2, 8.8.12, 8.7.19 - Camunda Zeebe 8.9.2, 8.8.24, 8.7.29 - Camunda Tasklist 8.9.2, 8.8.24, 8.7.29 - Camunda Operate 8.9.2, 8.8.24, 8.7.29 - Camunda Optimize 8.9.2, 8.8.24, 8.7.21 - Camunda Web Modeler 8.9.2, 8.8.14, 8.7.21 ## Notice 46 ### Publication date May 7, 2026 ### Products affected - Camunda Web Modeler ### Impact The version of `axios` used by Camunda Web Modeler was affected by [CVE-2026-42264](https://github.com/advisories/GHSA-q8qp-cvcw-x6jj), a prototype pollution vulnerability in the HTTP adapter that could allow credential injection and request hijacking when `Object.prototype` is polluted by another dependency in the same process. ### How to determine if the installation is affected You are using: - Web Modeler Self-Managed ≤ 8.8.13, or ≤ 8.7.20 ### Solution Camunda has provided the following releases that contain the fix: - Web Modeler Self-Managed 8.8.14, 8.7.21 ## Notice 45 ### Publication date April 24, 2026 ### Products affected - Camunda Tasklist - Camunda Zeebe - Camunda Operate ### Impact The application was vulnerable to [CVE-2026-5588](https://nvd.nist.gov/vuln/detail/CVE-2026-5588), where the PKIX CompositeVerifier in the embedded Bouncy Castle cryptography library could accept an empty signature sequence as valid, weakening certificate signature verification and potentially allowing acceptance of improperly signed certificates. ### How to determine if the installation is affected You are using: - Camunda Tasklist 8.9.0, ≤ 8.8.22, or ≤ 8.7.27 - Camunda Zeebe 8.9.0, ≤ 8.8.22, or ≤ 8.7.27 - Camunda Operate 8.9.0, ≤ 8.8.22, or ≤ 8.7.27 ### Solution Camunda has provided the following releases which contain the fix: - Camunda Tasklist 8.9.1, 8.8.23, 8.7.28 - Camunda Zeebe 8.9.1, 8.8.23, 8.7.28 - Camunda Operate 8.9.1, 8.8.23, 8.7.28 ## Notice 44 ### Publication date April 24, 2026 ### Products affected - Camunda Tasklist - Camunda Zeebe - Camunda Operate ### Impact The application was vulnerable to [CVE-2025-14813](https://nvd.nist.gov/vuln/detail/CVE-2025-14813), where the GOSTCTR implementation in the embedded Bouncy Castle cryptography library used an incorrect one-byte counter and could not securely encrypt or decrypt more than 255 blocks, potentially compromising the confidentiality and integrity of data encrypted with this algorithm. ### How to determine if the installation is affected You are using: - Camunda Tasklist 8.9.0, ≤ 8.8.22, or ≤ 8.7.27 - Camunda Zeebe 8.9.0, ≤ 8.8.22, or ≤ 8.7.27 - Camunda Operate 8.9.0, ≤ 8.8.22, or ≤ 8.7.27 ### Solution Camunda has provided the following releases which contain the fix: - Camunda Tasklist 8.9.1, 8.8.23, 8.7.28 - Camunda Zeebe 8.9.1, 8.8.23, 8.7.28 - Camunda Operate 8.9.1, 8.8.23, 8.7.28 ## Notice 43 ### Publication date Apr 7, 2026 ### Products affected - Management Identity - Camunda Tasklist - Camunda Zeebe - Camunda Operate - Camunda Optimize - Camunda Web Modeler ### Impact When applications specify HTTP response headers for servlet applications using Spring Security, there is the possibility that the HTTP headers will not be written. This is related to [CVE-2026-22732](https://nvd.nist.gov/vuln/detail/CVE-2026-22732). ### How to determine if the installation is affected You are using: - Management Identity 8.7.4 - 8.7.10, 8.7.12 - 8.7.16, 8.8.0 - 8.8.2, or 8.8.5 - 8.8.9 - Zeebe 8.7.21 - 8.7.25 - Tasklist 8.7.21 - 8.7.25 - Operate 8.7.22 - 8.7.25 - Optimize 8.7.14 - 8.7.18 or 8.8.2 - 8.8.7 - Web Modeler Self-Managed ≤ 8.6.26, ≤ 8.7.18, or ≤ 8.8.11 ### Solution Camunda has provided the following releases which contain the fix: - Management Identity 8.7.17, 8.8.10 - Zeebe 8.7.26 - Tasklist 8.7.26 - Operate 8.7.26 - Optimize 8.7.19, 8.8.8 - Web Modeler Self-Managed 8.6.27, 8.7.19, 8.8.12 The fix was deployed to Web Modeler SaaS on March 23, 2026, 17:26 CET. ## Notice 42 ### Publication date Mar 09, 2026 ### Products affected - Management Identity ### Impact The application was vulnerable to [CVE-2026-24734](https://nvd.nist.gov/vuln/detail/CVE-2026-24734), which allowed an attacker to bypass revocation checks of client SSL certificates if a certain server configuration was used. ### How to determine if the installation is affected You are using: - Management Identity ≤ 8.8.7, ≤ 8.7.14, or ≤ 8.6.27 ### Solution Camunda has provided the following releases which contain the fix: - Management Identity 8.8.8, 8.7.15, 8.6.28 ## Notice 41 ### Publication date Mar 9, 2026 ### Products affected - Camunda Web Modeler ### Impact The version of `fast-xml-parser` used by Camunda Web Modeler was affected by [CVE-2026-26278](https://nvd.nist.gov/vuln/detail/CVE-2026-26278), a vulnerability which could be exploited as a vector for denial of service attacks by forcing the parser to do an unlimited amount of entity expansions. ### How to determine if the installation is affected You are using: - Web Modeler Self-Managed ≤ 8.8.8, ≤ 8.7.16, or ≤ 8.6.25 ### Solution Camunda has provided the following releases that contain the fix: - Web Modeler Self-Managed 8.8.9, 8.7.17, 8.6.26 This issue does not affect Web Modeler SaaS. ## Notice 40 ### Publication date Feb 23, 2026 ### Products affected C7 to C8 Migration Tooling ### Impact The version of Tomcat used by the Diagram Converter Webapp was affected by: - [CVE-2025-66614](https://nvd.nist.gov/vuln/detail/CVE-2025-66614) - [CVE-2026-24734](https://nvd.nist.gov/vuln/detail/CVE-2026-24734) ### How to determine if the installation is affected You are using: - C7 to C8 Migration Tooling 0.2.0 **AND** - the Diagram Converter Webapp ### Solution Camunda has released the **C7 to C8 Migration Tooling 0.2.1**, which includes the fix. ## Notice 39 ### Publication date Feb 10, 2026 ### Products affected - Camunda Web Modeler ### Impact The version of `fast-xml-parser` used by Camunda Web Modeler was affected by [CVE-2026-25128](https://nvd.nist.gov/vuln/detail/CVE-2026-25128), a RangeError vulnerability that could crash any application that processes untrusted XML input. ### How to determine if the installation is affected You are using: - Web Modeler Self-Managed ≤ 8.8.6, ≤ 8.7.15, or ≤ 8.6.24 ### Solution Camunda has provided the following releases that contain the fix: - Web Modeler Self-Managed 8.8.7, 8.7.16, 8.6.25 The fix was deployed to Web Modeler SaaS on February 2, 2026, 15:15 CET. ## Notice 38 ### Publication date Jan 8, 2026 ### Products affected - Camunda Web Modeler ### Impact The version of `qs` used by Camunda Web Modeler was affected by CVE-2025-15284, an improper input validation vulnerability that allows HTTP DoS. ### How to determine if the installation is affected You are using: - Web Modeler Self-Managed ≤ 8.8.4, ≤ 8.7.14, or ≤ 8.6.23 ### Solution Camunda has provided the following releases that contain the fix: - Web Modeler Self-Managed 8.8.5, 8.7.15, 8.6.24 The fix was deployed to Web Modeler SaaS on January 7, 2026, 13:45 CET. ## Notice 37 ### Publication date December 12, 2025 ### Products affected - Camunda Tasklist - Camunda Zeebe - Camunda Operate ### Impact The application is vulnerable to [CVE-2025-12183](https://nvd.nist.gov/vuln/detail/CVE-2025-12183), which allows remote attackers to cause denial of service and read adjacent memory via untrusted compressed input. ### How to determine if the installation is affected You are using: - Tasklist/Zeebe/Operate ≤ 8.8.6, ≤ 8.7.20, or ≤ 8.6.32 ### Solution Camunda has provided the following releases which contain the fix: - Tasklist/Zeebe/Operate 8.8.7, 8.7.21, 8.6.33 ## Notice 36 ### Publication date December 03, 2025 ### Products affected - Management Identity ### Impact The application is vulnerable to [CVE-2025-53066](https://nvd.nist.gov/vuln/detail/CVE-2025-53066), which allows an unauthenticated attacker with network access via multiple protocols to compromise Oracle Java SE, Oracle GraalVM for JDK, Oracle GraalVM Enterprise Edition. Successful attacks of this vulnerability can result in unauthorized access to critical data or complete access to all Oracle Java SE, Oracle GraalVM for JDK, Oracle GraalVM Enterprise Edition accessible data. ### How to determine if the installation is affected You are using: - Management Identity ≤ 8.8.2, ≤ 8.7.10, or ≤ 8.6.22 ### Solution Camunda has provided the following releases which contain the fix: - Management Identity 8.8.3, 8.7.11, 8.6.23 ## Notice 35 ### Publication date November 26, 2025 ### Products affected - Camunda Web Modeler Self-Managed - Camunda Management Identity ### Impact The embedded JDBC driver for Amazon Aurora PostgreSQL (`software.amazon.jdbc:aws-advanced-jdbc-wrapper`) was affected by [CVE-2025-12967](https://nvd.nist.gov/vuln/detail/CVE-2025-12967), which may allow for privilege escalation to the `rds_superuser` role. A low privilege authenticated user can create a crafted function that could be executed with permissions of other Amazon Relational Database Service (RDS) users. ### How to determine if the installation is affected You are using: - Web Modeler Self-Managed ≤ 8.8.2, ≤ 8.7.12, or ≤ 8.6.21 with Amazon Aurora PostgreSQL - Management Identity ≤ 8.8.1, ≤ 8.7.9, or ≤ 8.6.21 with Amazon Aurora PostgreSQL ### Solution Camunda has provided the following releases which contain the fix: - Web Modeler Self-Managed 8.8.3, 8.7.13, 8.6.22 - Management Identity 8.8.2, 8.7.10, 8.6.22 ## Notice 34 ### Publication date November 11, 2025 ### Products affected - Camunda Web Modeler Self-Managed ### Impact The version of the MSSQL JDBC driver `com.microsoft.sqlserver:mssql-jdbc` used by Web Modeler was affected by [CVE-2025-59250](https://nvd.nist.gov/vuln/detail/CVE-2025-59250), which allows improper input validation that could enable an attacker to perform spoofing over a network. ### How to determine if the installation is affected You are using Web Modeler Self-Managed version <= 8.8.1 and Microsoft SQL Server as database vendor. ### Solution Camunda has provided the following release which contains the fix: - Web Modeler Self-Managed 8.8.2 ## Notice 33 ### Publication date October 22, 2025 ### Products affected - Camunda Orchestration Cluster ### Impact A bug in signal broadcast command processing allowed unauthorized users to trigger signal start events or signal intermediate catch events in certain process definitions without the required create or update permissions. This did not allow users to access process definitions of other tenants, or leak any information about these process instances back to the unauthorized users. ### How to determine if the installation is affected You are using: - Orchestration Cluster 8.8.0 ### Solution Camunda has provided the following release which contains the fix: - Orchestration Cluster 8.8.1 ## Notice 32 ### Publication date October 21, 2025 ### Products affected - Camunda Management Identity ### Impact The embedded Apache Tomcat was affected by [CVE-2025-48989](https://nvd.nist.gov/vuln/detail/CVE-2025-48989) which made Tomcat vulnerable to the MadeYouReset attack. ### How to determine if the installation is affected You are using: - Management Identity 8.7.0 - 8.7.4 or 8.7.6 - 8.7.7 ### Solution Camunda has provided the following release which contains the fix: - Management Identity 8.7.8 ## Notice 31 ### Publication date October 16, 2025 ### Products affected - Camunda Web Modeler ### Impact The embedded Undertow web server was affected by [CVE-2025-9784](https://nvd.nist.gov/vuln/detail/CVE-2025-9784), a flaw where malformed client requests can trigger server-side stream resets without incrementing abuse counters. This issue, referred to as the "MadeYouReset" attack, allows malicious clients to induce excessive server workload by repeatedly causing server-side stream aborts and could be exploited to cause a denial of service (DoS). ### How to determine if the installation is affected You are using: - Web Modeler Self-Managed 8.8.0, ≤ 8.7.10, or ≤ 8.6.19 ### Solution Camunda has provided the following releases which contain the fix: - Web Modeler Self-Managed 8.8.1, 8.7.11, 8.6.20 The fix was deployed to Web Modeler SaaS on October 14, 2025, 14:26 CET. ## Notice 30 ### Publication date October 7th, 2025 ### Products affected - Camunda Tasklist - Camunda Zeebe - Camunda Operate - Camunda Optimize - Camunda Management Identity ### Impact The embedded Netty was affected by [CVE-2025-58056](https://nvd.nist.gov/vuln/detail/CVE-2025-58056), an HTTP request smuggling vulnerability in Netty. Incorrect parsing of chunked transfer encoding could allow attackers to craft malicious requests that are interpreted inconsistently by proxies and Netty. ### How to determine if the installation is affected You are using: - Tasklist 8.7.0 - 8.7.12 or 8.5.0 - 8.5.22 - Zeebe 8.7.0 - 8.7.12 or 8.5.0 - 8.5.24 - Operate 8.7.0 - 8.7.12 or 8.5.0 - 8.5.20 - Optimize 8.7.0 - 8.7.9 or 8.6.0 - 8.6.16 - Management Identity 8.7.0 - 8.7.6 or 8.6.0 - 8.6.19 or 8.5.0 - 8.5.21 ### Solution Camunda has provided the following releases which contain the fix: - Tasklist 8.7.13, 8.5.23 - Zeebe 8.7.13, 8.5.25 - Operate 8.7.13, 8.5.21 - Optimize 8.7.10, 8.6.17 - Management Identity 8.7.7, 8.6.20, 8.5.22 ## Notice 29 ### Publication date October 3, 2025 ### Products affected - Camunda Zeebe ### Impact Zeebe may be affected by [CVE-2024-41996](https://nvd.nist.gov/vuln/detail/CVE-2024-41996), which allows remote attackers to trigger expensive server-side DHE modular-exponentiation calculations, potentially causing asymmetric resource consumption and DoS attacks. ### How to determine if the installation is affected You are potentially affected if you have configured Zeebe to accept DHE or ECDHE cipher suites through the `server.ssl.ciphers` property or `SERVER_SSL_CIPHERS` environment variable. Default Zeebe installations are not affected. ### Solution Configure the `server.ssl.ciphers` property or `SERVER_SSL_CIPHERS` environment variable to exclude DHE and ECDHE cipher suites. For example: ``` server.ssl.ciphers=TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA ``` There is no known mitigation other than disabling the use of DHE and ECDHE cipher suites. ## Notice 28 ### Publication date September 9, 2025 ### Products affected - Camunda Optimize ### Impact Optimize was affected by [CVE-2025-5115](https://nvd.nist.gov/vuln/detail/CVE-2025-5115), which allows a remote attacker to repeatedly send malformed HTTP/2 frames that exhaust a Jetty server’s CPU and memory, causing a denial-of-service. ### How to determine if the installation is affected You are using: - Optimize 8.7.0 - 8.7.8 or 8.6.0 - 8.6.15 ### Solution Camunda has provided the following releases which contain the fix: - Optimize 8.7.9, 8.6.16 ## Notice 27 ### Publication date August 27, 2025 ### Products affected - Camunda Optimize ### Impact Optimize's email functionality was affected by [CVE-2025-7962](https://nvd.nist.gov/vuln/detail/CVE-2025-7962), which allowed for SMTP injection by providing forged email recipient addresses that could lead to malicious content being sent to arbitrary recipients. ### How to determine if the installation is affected You are using: - Optimize 8.7.0 - 8.7.7 or 8.6.0 - 8.6.14 ### Solution Camunda has provided the following releases which contain the fix: - Optimize 8.7.8, 8.6.15 ## Notice 26 ### Publication date August 27, 2025 ### Products affected - Camunda Optimize ### Impact Optimize was affected by [CVE-2025-53864](https://nvd.nist.gov/vuln/detail/CVE-2025-53864) which allows a remote attacker to cause a denial of service via a deeply nested JSON object supplied in a JWT claim set, because of uncontrolled recursion. ### How to determine if the installation is affected You are using: - Optimize 8.7.0 - 8.7.7 or 8.6.0 - 8.6.14 ### Solution Camunda has provided the following releases which contain the fix: - Optimize 8.7.8, 8.6.15 ## Notice 25 ### Publication date August 27, 2025 ### Products affected - Camunda Tasklist - Camunda Zeebe - Camunda Operate - Camunda Optimize ### Impact The embedded Apache Tomcat was affected by [CVE-2025-48989](https://nvd.nist.gov/vuln/detail/CVE-2025-48989) which made Tomcat vulnerable to the MadeYouReset attack. ### How to determine if the installation is affected You are using: - Tasklist 8.7.0 - 8.7.10 or 8.6.0 - 8.6.24 or 8.5.0 - 8.5.20 - Zeebe 8.7.0 - 8.7.10 or 8.6.0 - 8.6.24 - Operate 8.7.0 - 8.7.10 or 8.6.0 - 8.6.24 or 8.5.0 - 8.5.18 - Optimize 8.7.0 - 8.7.7 or 8.6.0 - 8.6.14 ### Solution Camunda has provided the following releases which contain the fix: - Tasklist 8.7.11, 8.6.25, 8.5.21 - Zeebe 8.7.11, 8.6.25 - Operate 8.7.11, 8.6.25, 8.5.19 - Optimize 8.7.8, 8.6.15 ## Notice 24 ### Publication date August 27, 2025 ### Products affected - Camunda Tasklist - Camunda Zeebe - Camunda Operate - Camunda Management Identity - Camunda Optimize ### Impact The embedded Netty was affected by [CVE-2025-55163](https://nvd.nist.gov/vuln/detail/CVE-2025-55163) which allows malformed HTTP/2 control frames usage that results in resource exhaustion and distributed denial of service. ### How to determine if the installation is affected You are using: - Tasklist 8.7.0 - 8.7.10 or 8.6.0 - 8.6.24 or 8.5.0 - 8.5.20 - Zeebe 8.7.0 - 8.7.10 or 8.6.0 - 8.6.24 or 8.5.0 - 8.5.22 - Operate 8.7.0 - 8.7.10 or 8.6.0 - 8.6.24 or 8.5.0 - 8.5.18 - Management Identity 8.7.0 - 8.7.5 or 8.6.0 - 8.6.18 or 8.5.0 - 8.5.19 - Optimize 8.7.0 - 8.7.7 or 8.6.0 - 8.6.14 ### Solution Camunda has provided the following releases which contain the fix: - Tasklist 8.7.11, 8.6.25, 8.5.21 - Zeebe 8.7.11, 8.6.25, 8.5.23 - Operate 8.7.11, 8.6.25, 8.5.19 - Management Identity 8.7.6, 8.6.19, 8.5.20 - Optimize 8.7.8, 8.6.15 ## Notice 23 ### Publication date July 31, 2025 ### Products affected - Camunda Tasklist - Camunda Zeebe - Camunda Operate - Camunda Management Identity - Camunda Optimize ### Impact The embedded Spring Boot Tomcat was affected by [CVE-2025-53506](https://nvd.nist.gov/vuln/detail/CVE-2025-53506) which allowed for uncontrolled resource consumption that could be used to exhaust system resources in a potential DoS (denial of service) attack. ### How to determine if the installation is affected You are using: - Tasklist 8.7.0 - 8.7.8 or 8.6.0 - 8.6.22 or 8.5.0 - 8.5.18 - Zeebe 8.7.0 - 8.7.8 or 8.6.0 - 8.6.22 - Operate 8.7.0 - 8.7.8 or 8.6.0 - 8.6.22 or 8.5.0 - 8.5.16 - Management Identity 8.7.0 - 8.7.4 or 8.6.0 - 8.6.17 or 8.5.0 - 8.5.18 - Optimize 8.7.0 - 8.7.6 or 8.6.0 - 8.6.12 ### Solution Camunda has provided the following releases which contain the fix: - Tasklist 8.7.9, 8.6.23, 8.5.19 - Zeebe 8.7.9, 8.6.23 - Operate 8.7.9, 8.6.23, 8.5.17 - Management Identity 8.7.5, 8.6.18, 8.5.19 - Optimize 8.7.7, 8.6.13 ## Notice 22 ### Publication date July 31, 2025 ### Products affected - Camunda Tasklist - Camunda Zeebe - Camunda Operate ### Impact Part of our RESTful API that supported multipart file uploads was affected by [CVE-2025-52520](https://nvd.nist.gov/vuln/detail/CVE-2025-52520), which could lead to potential DoS (denial of service) attacks. ### How to determine if the installation is affected You are using: - Tasklist 8.6.0 - 8.6.22 or 8.7.0 - 8.7.8 - Zeebe 8.6.0 - 8.6.22 or 8.7.0 - 8.7.8 - Operate 8.6.0 - 8.6.22 or 8.7.0 - 8.7.8 ### Solution Camunda has provided the following releases which contain the fix: - Tasklist 8.6.22 - Tasklist 8.7.9 - Zeebe 8.6.23 - Zeebe 8.7.9 - Operate 8.6.23 - Operate 8.7.9 ## Notice 21 ### Publication date June 18, 2025 ### Products affected Camunda Web Modeler Self-Managed ### Impact The version of `org.postgresql:postgresql` used by Camunda Web Modeler Self-Managed was affected by [CVE-2025-49146](https://nvd.nist.gov/vuln/detail/CVE-2025-49146) potentially allowing a man-in-the-middle attacker to intercept connections when the PostgreSQL JDBC driver was configured with channel binding set to required. ### How to determine if the installation is affected You are using Camunda Web Modeler Self-Managed version 8.6.0 - 8.6.12, or 8.7.0 - 8.7.3. ### Solution Camunda has provided the following releases which contain the fix: - Camunda Web Modeler Self-Managed 8.6.12 - Camunda Web Modeler Self-Managed 8.7.3 ## Notice 20 ### Publication date June 17, 2025 ### Products affected Camunda Optimize ### Impact Camunda Optimize was affected by a vulnerability that allowed an attacker to gain improper access to Optimize data by using a modified JWT (JSON Web Token). ### How to determine if the installation is affected You are using Camunda Optimize ≤ 8.6.9 or ≤ 8.7.2. ### Solution Camunda has provided the following release which contains a fix: - [Camunda Optimize 8.6.10](https://github.com/camunda/camunda/releases/tag/8.6.10-optimize) - [Camunda Optimize 8.7.3](https://github.com/camunda/camunda/releases/tag/8.7.3-optimize) ## Notice 19 ### Publication date May 21, 2025 ### Products affected Camunda Web Modeler ### Impact The version of `nodejs` used by Camunda Web Modeler was affected by [CVE-2025-23166](https://nvd.nist.gov/vuln/detail/CVE-2025-23166) potentially allowing an adversary to remotely crash the Node.js runtime. ### How to determine if the installation is affected You are using Camunda Web Modeler Self-Managed version ≤ 8.4.17, ≤ 8.5.18, ≤ 8.6.10, or ≤ 8.7.1. ### Solution Camunda has provided the following releases which contain the fix: - Camunda Web Modeler Self-Managed 8.4.18 - Camunda Web Modeler Self-Managed 8.5.19 - Camunda Web Modeler Self-Managed 8.6.11 - Camunda Web Modeler Self-Managed 8.7.2 The fix was deployed to Web Modeler SaaS on May 19, 2025, 15:10 CET. ## Notice 18 ### Publication date April 8, 2025 ### Products affected Camunda Optimize ### Impact Camunda Optimize was affected by a vulnerability that allowed an attacker to modify a JWT (JSON Web Token) so that they would be given improper access to Optimize. ### How to determine if the installation is affected You are using Camunda Optimize ≤ 8.4.15, ≤ 8.5.12, ≤ 8.6.6, ≤ 8.7.0, ≤ 3.11.20, ≤ 3.12.15, ≤ 3.13.12, ≤ 3.14.3, ≤ 3.15.1. ### Solution Camunda has provided the following release which contains a fix: - Camunda Optimize 8.4.16, 8.5.13, 8.6.7, 8.7.0, 3.12.16, 3.13.13, 3.14.4, 3.15.2 ## Notice 17 ### Publication date April 8, 2025 ### Products affected Camunda Zeebe ### Impact When parsing unknown fields in the Protobuf Java Lite and Full library, a maliciously crafted message can cause a StackOverflow error and lead to a program crash. - As Zeebe makes extensive use of Protobuf, this could lead to denial-of-service (DoS) issues on the server side. - This issue allows an attacker to send specific payloads that will always result in `StackOverflowException`. This could lead to Zeebe Gateway performance issues and affect system availability. - Although the Zeebe Gateway will not crash, it will spend more time working on these requests. An attacker could use this opportunity to slow it down and make it unusable by sending a large number of requests within a short time frame. No data is leaked, lost, or corrupted. This issue only affects application availability. [Learn more about this CVE at the GitHub Advisory Database](https://github.com/advisories/GHSA-735f-pc8j-v9w8). ### How to determine if the installation is affected You are using Camunda Zeebe 8.6.11. ### Solution Camunda has provided the following release which contains a fix: - [Camunda Zeebe 8.6.13](https://github.com/camunda/camunda/releases/tag/8.6.13) ## Notice 16 ### Publication date March 14, 2025 ### Products affected Camunda Zeebe ### Impact Some Camunda Zeebe versions were affected by a vulnerability that allowed a malicious attacker to craft network packets that could crash the Zeebe Gateway. ### How to determine if the installation is affected You are using Camunda Zeebe 8.6.0 - 8.6.11 ### Solution Camunda has provided the following release which contains a fix: - [Camunda Zeebe 8.6.12](https://github.com/camunda/camunda/releases/tag/8.6.12) ## Notice 15 ### Publication date March 11, 2025 ### Products affected Camunda Optimize ### Impact Some Camunda Optimize versions were affected by a vulnerability that allowed a malicious attacker to craft Camunda URLs that could execute JavaScript code. ### How to determine if the installation is affected You are using Camunda Optimize ≤ 8.6.5. ### Solution Camunda has provided the following release which contains a fix: - [Camunda Optimize 8.6.6](https://github.com/camunda/camunda/releases/tag/8.6.6-optimize) ## Notice 14 ### Publication date March 11, 2025 ### Products affected Camunda Web Modeler ### Impact The version of `koa` used by Camunda Web Modeler was affected by the following vulnerability: - https://nvd.nist.gov/vuln/detail/CVE-2025-25200 ### How to determine if the installation is affected You are using Camunda Web Modeler Self-Managed version ≤ 8.3.16, ≤ 8.4.14, ≤ 8.5.15, or ≤ 8.6.7. ### Solution Camunda has provided the following releases which contain the fix: - Camunda Web Modeler Self-Managed 8.3.17 - Camunda Web Modeler Self-Managed 8.4.15 - Camunda Web Modeler Self-Managed 8.5.16 - Camunda Web Modeler Self-Managed 8.6.8 The fix was deployed to Web Modeler SaaS on February 14, 2025, 08:50 CET. ## Notice 13 ### Publication date July 18, 2024 ### Products affected Camunda Management Identity ### Impact The version of `Apache Tomcat` used by Camunda Management Identity was affected by the following vulnerability: - https://nvd.nist.gov/vuln/detail/CVE-2024-34750 ### How to determine if the installation is affected You are using Camunda Management Identity version 8.5.3 or previous. ### Solution Camunda has provided the following release which contains a fix: - Camunda Management Identity 8.5.4 ## Notice 12 ### Publication date October 3, 2023 ### Products affected Camunda Desktop Modeler ### Impact The version of `libwebp` shipped with Camunda Desktop Modeler was affected by the following vulnerability: - https://nvd.nist.gov/vuln/detail/CVE-2023-4863 ### How to determine if the installation is affected You are using Camunda Desktop Modeler version 5.15.1 or previous. ### Solution Camunda has provided the following release which contains a fix: - [Camunda Desktop Modeler 5.15.2](https://downloads.camunda.cloud/release/camunda-modeler/5.15.2/) ## Notice 11 ### Publication date April 17, 2023 ### Products affected Tasklist ### Impact The Tasklist REST API functionality of Tasklist 8.2.0 and 8.2.1 allows unauthenticated access to the following methods/URLs: - GET /v1/tasks/\{taskId} - POST /v1/tasks/search - POST /v1/tasks/\{taskId}/variables/search - POST /v1/forms/\{formId} - POST /v1/variables/\{variableId} Find more information about the methods in our [Tasklist REST API documentation](/versioned_docs/version-8.9/apis-tools/tasklist-api-rest/tasklist-api-rest-overview.md). Therefore, if you use Tasklist 8.2.0 or 8.2.1, and if you have sensible data stored in process variables (accessed by user tasks), this data could have been accessed by users knowing the endpoint of the Tasklist instance without authentication. ### How to determine if the installation is affected You are using Tasklist version 8.2.0 or 8.2.1. ### Solution Camunda has provided the following releases which contain a fix - [Tasklist 8.2.2](https://github.com/camunda/camunda-platform/releases/tag/8.2.2) ## Notice 10 ### Publication Date: November 10, 2022 ### Products affected: Tasklist ### Impact: The Tasklist Docker image contain an OpenSSL version 3.0.2 for which the following CVEs have been published: - https://nvd.nist.gov/vuln/detail/CVE-2022-3602 - https://nvd.nist.gov/vuln/detail/CVE-2022-3786 At this point, Camunda is not aware of any specific attack vector in Tasklist allowing attackers to exploit the vulnerability but recommends applying fixes as mentioned in the Solution section below. ### How to determine if the installation is affected You are Tasklist version (8.0.3 ≥ version ≤ 8.0.7) or ≤ 8.1.2 ### Solution Camunda has provided the following releases which contain a fix - [Tasklist 8.1.3](https://github.com/camunda/camunda-platform/releases/tag/8.1.3) - [Tasklist 8.0.8](https://github.com/camunda/camunda-platform/releases/tag/8.0.8) ## Notice 9 ### Publication Date: April 11, 2022 ### Products affected: Zeebe, Operate, Tasklist, IAM ### Impact: Zeebe, Operate, Tasklist and IAM are using the Spring framework for which the following CVE has been published: https://nvd.nist.gov/vuln/detail/CVE-2022-22965 At this point, Camunda is not aware of any specific attack vector in Zeebe, Operate, Tasklist or IAM allowing attackers to exploit the vulnerability but recommends applying fixes as mentioned in the Solution section below. ### How to determine if the installation is affected You are using Zeebe, Operate or Tasklist version ≤ 1.2.11 or ≤ 1.3.6 ### Solution Camunda has provided the following releases which contain a fix - [Zeebe, Operate and Tasklist 1.3.7](https://github.com/camunda-cloud/zeebe/releases/tag/1.3.7) - [Zeebe, Operate and Tasklist 1.2.12](https://github.com/camunda-cloud/zeebe/releases/tag/1.2.12) ## Notice 8 ### Publication Date: December 31, 2021 ### Products affected: Zeebe, Operate, Tasklist ### Impact: Zeebe, Operate and Tasklist bundle log4j-core for which the following CVE has been published: https://nvd.nist.gov/vuln/detail/CVE-2021-44832. At this point, Camunda is not aware of any specific attack vector in Zeebe, Operate or Tasklist allowing attackers to exploit the vulnerability but recommends applying fixes as mentioned in the Solution section below. ### How to determine if the installation is affected You are using Zeebe, Operate or Tasklist version ≤ 1.2.8 or ≤ 1.1.9 ### Solution Camunda has provided the following releases which contain a fix - [Zeebe, Operate and Tasklist 1.2.9](https://github.com/camunda-cloud/zeebe/releases/tag/1.2.9) - [Zeebe, Operate and Tasklist 1.1.10](https://github.com/camunda-cloud/zeebe/releases/tag/1.1.10) ## Notice 7 ### Publication Date: December 31, 2021 ### Products affected: IAM ### Impact: IAM bundles log4j libraries for which the following CVE has been published: https://nvd.nist.gov/vuln/detail/CVE-2021-44832. Specifically, IAM bundles log4j-api and log4j-to-slf4j. However, IAM does not bundle the log4j-core library which contains the vulnerability referred to by the CVE. As a result, Camunda does not consider IAM to be affected by the vulnerability. Still, Camunda recommends applying fixes as mentioned in the Solution section below. ### How to determine if the installation is affected You are using IAM version ≤ 1.2.8 ### Solution Camunda has provided the following releases which contain a fix - [IAM 1.2.9](https://github.com/camunda-cloud/zeebe/releases/tag/1.2.9) ## Notice 6 ### Publication Date: December 22, 2021 ### Products affected: Zeebe, Operate, Tasklist ### Impact: Zeebe, Operate and Tasklist bundle log4j-core for which the following CVE has been published: https://nvd.nist.gov/vuln/detail/CVE-2021-45105. At this point, Camunda is not aware of any specific attack vector in Zeebe, Operate or Tasklist allowing attackers to exploit the vulnerability but recommends applying fixes as mentioned in the Solution section below. ### How to determine if the installation is affected You are using Zeebe, Operate or Tasklist version ≤ 1.2.7 or ≤ 1.1.8 ### Solution Camunda has provided the following releases which contain a fix - [Zeebe, Operate and Tasklist 1.2.8](https://github.com/camunda-cloud/zeebe/releases/tag/1.2.8) - [Zeebe, Operate and Tasklist 1.1.9](https://github.com/camunda-cloud/zeebe/releases/tag/1.1.9) ## Notice 5 ### Publication Date: December 22, 2021 ### Products affected: IAM ### Impact: IAM bundles log4j libraries for which the following CVE has been published: https://nvd.nist.gov/vuln/detail/CVE-2021-45105. Specifically, IAM bundles log4j-api and log4j-to-slf4j. However, IAM does not bundle the log4j-core library which contains the vulnerability referred to by the CVE. As a result, Camunda does not consider IAM to be affected by the vulnerability. Still, Camunda recommends applying fixes as mentioned in the Solution section below. IAM bundles logback libraries for which the following CVE has been published: https://nvd.nist.gov/vuln/detail/CVE-2021-42550. At this point, Camunda is not aware of any specific attack vector in IAM allowing attackers to exploit the vulnerability but recommends applying fixes as mentioned in the Solution section below. ### How to determine if the installation is affected You are using IAM version ≤ 1.2.7 ### Solution Camunda has provided the following releases which contain a fix - [IAM 1.2.8](https://github.com/camunda-cloud/zeebe/releases/tag/1.2.8) ## Notice 4 ### Publication Date: December 17, 2021 ### Products affected: Zeebe, Operate, Tasklist ### Impact: Zeebe, Operate and Tasklist bundle log4j-core for which the following CVE has been published: https://nvd.nist.gov/vuln/detail/CVE-2021-45046. At this point, Camunda is not aware of any specific attack vector in Zeebe, Operate or Tasklist allowing attackers to exploit the vulnerability but recommends applying fixes as mentioned in the Solution section below. ### How to determine if the installation is affected You are using Zeebe, Operate or Tasklist version ≤ 1.2.6 or ≤ 1.1.7 ### Solution Camunda has provided the following releases which contain a fix - [Zeebe, Operate and Tasklist 1.2.7](https://github.com/camunda-cloud/zeebe/releases/tag/1.2.7) - [Zeebe, Operate and Tasklist 1.1.8](https://github.com/camunda-cloud/zeebe/releases/tag/1.1.8) ## Notice 3 ### Publication Date: December 17, 2021 ### Products affected: IAM ### Impact: IAM bundles log4j libraries for which the following CVE has been published: https://nvd.nist.gov/vuln/detail/CVE-2021-45046. Specifically, IAM bundles log4j-api and log4j-to-slf4j. However, IAM does not bundle the log4j-core library which contains the vulnerability referred to by the CVE. As a result, Camunda does not consider IAM to be affected by the vulnerability. Still, Camunda recommends applying fixes as mentioned in the Solution section below. IAM bundles logback libraries for which the following CVE has been published: https://nvd.nist.gov/vuln/detail/CVE-2021-42550. At this point, Camunda is not aware of any specific attack vector in IAM allowing attackers to exploit the vulnerability but recommends applying fixes as mentioned in the Solution section below. ### How to determine if the installation is affected You are using IAM version ≤ 1.2.6 ### Solution Camunda has provided the following releases which contain a fix - [IAM 1.2.7](https://github.com/camunda-cloud/zeebe/releases/tag/1.2.7) ## Notice 2 ### Publication Date: December 14, 2021 ### Products affected: Zeebe, Operate, Tasklist ### Impact: Zeebe, Operate and Tasklist bundle log4j-core for which the following CVE has been published: https://nvd.nist.gov/vuln/detail/CVE-2021-44228. At this point, Camunda is not aware of any specific attack vector in Zeebe, Operate or Tasklist allowing attackers to exploit the vulnerability but recommends applying fixes as mentioned in the Solution section below. ### How to determine if the installation is affected You are using Zeebe, Operate or Tasklist version ≤ 1.2.5 or ≤ 1.1.6 ### Solution Camunda has provided the following releases which contain a fix - [Zeebe, Operate and Tasklist 1.2.6](https://github.com/camunda-cloud/zeebe/releases/tag/1.2.6) - [Zeebe, Operate and Tasklist 1.1.7](https://github.com/camunda-cloud/zeebe/releases/tag/1.1.7) Apply the patches mentioned above or set the JVM option `-Dlog4j2.formatMsgNoLookups=true` ## Notice 1 ### Publication Date: December 14, 2021 ### Products affected: IAM ### Impact: IAM bundles log4j libraries for which the following CVE has been published: https://nvd.nist.gov/vuln/detail/CVE-2021-44228. Specifically, IAM bundles log4j-api and log4j-to-slf4j. However, IAM does not bundle the log4j-core library which contains the vulnerability referred to by the CVE. As a result, Camunda does not consider IAM to be affected by the vulnerability. Still, Camunda recommends applying fixes as mentioned in the Solution section below. ### How to determine if the installation is affected You are using IAM version ≤ 1.2.5 ### Solution Camunda has provided the following releases which contain a fix - [IAM 1.2.6](https://github.com/camunda-cloud/zeebe/releases/tag/1.2.6) --- ## Reference Camunda 8 reference, including release, security, and support information. See what's new in our latest release, read release notes and release announcements, and access published security notices, licensing, supported environments, and source code access information. [8.11 release information](./announcements-release-notes/overview.md) ## Release information What's new, release notes, announcements, and supporting information for Camunda 8.11. :::info Release policy Learn about the [Camunda release policy](/reference/announcements-release-notes/release-policy.md) and specific clarifications about provisioning in SaaS and Self-Managed. ::: ## Security, licensing, and support information Reference information including published security notices, licensing, supported environments, and source code access. ## Support and feedback [Get support](contact.md) for Camunda or send us your feedback. --- ## Camunda 8 public API ## What is the public API? Camunda 8 follows [Semantic Versioning (SemVer)](https://semver.org/) to provide users with a stable and reliable platform. A key requirement of SemVer is a clearly defined public API. The public API is the official contract between Camunda and its users under SemVer. No breaking changes will be made to the public API in minor or patch releases. You can safely build on these interfaces with the expectation of stability and backward compatibility. - This is a subset of all available APIs. Many [APIs](/apis-tools/working-with-apis-tools.md) are public-facing but not covered by the SemVer stability contract. - Only components explicitly listed on this page (see [Included in the public API](#included-in-the-public-api)) are covered. Anything not listed is _not_ guaranteed under SemVer. - The public API contract begins with version 8.8. :::note The term "public API" refers to the SemVer definition of stable interfaces, not external APIs available to users. ::: ### Included in the public API The following components are officially part of the Camunda 8 public API: - [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md): The primary interface for interacting with the Orchestration Cluster. - [Camunda Process Test](/apis-tools/testing/getting-started.md): Testing library to test BPMN processes and process applications. - [Camunda Java client](/apis-tools/java-client/getting-started.md): Java Client to interact with Orchestration Cluster REST API and Zeebe gRPC. - [Camunda Spring Boot Starter](/apis-tools/camunda-spring-boot-starter/getting-started.md): Spring Boot Client to interact with Orchestration Cluster REST API and Zeebe gRPC. ## What is not included in the public API Some APIs are excluded from the public API by design. While we aim for stability, they may evolve more quickly or follow different lifecycles. Only the APIs listed in [Included in the public API](#included-in-the-public-api) are covered by SemVer guarantees. All others are considered outside the public API. The [excluded APIs listed below](#excluded-apis) are commonly used, but this list is not exhaustive. ### Alpha endpoints within the public API Some endpoints in otherwise stable APIs are marked as [alpha features](/components/early-access/alpha/alpha-features.md) and are **not** included in the public API guarantee. Alpha endpoints: - Are clearly marked in API docs. - May introduce breaking changes in minor or patch releases. - Follow the [alpha feature policy](/components/early-access/alpha/alpha-features.md#alpha) rather than SemVer. - Are released for early feedback before general availability. Check the API documentation before building on any alpha endpoint. ### Excluded APIs The following APIs are **explicitly excluded** from the public API: - [Camunda Hub API](/apis-tools/hub-api-saas/overview.md): Manage workspaces, projects, files, folders, versions, and members in Camunda Hub programmatically. - [Web Modeler API](/apis-tools/web-modeler-api/index.md): Used for browser-based modeling. - [Administration API](/apis-tools/administration-api/administration-api-reference.md): For administrative operations and system configuration. - [Optimize API](/apis-tools/optimize-api/overview.md): Used for analytics, reporting, and performance insights. - [Orchestration Cluster MCP Server](/apis-tools/orchestration-cluster-api-mcp/orchestration-cluster-api-mcp-overview.md): Exposes Camunda capabilities through the Model Context Protocol. Tool schemas and behavior may evolve across versions. ### Policy for non-public APIs Though not covered by the public API contract, we aim to provide a consistent experience. For non-public APIs, we commit to: - Following API versioning best practices. - Announcing deprecations at least two minor versions in advance (for example, deprecated in 8.9, removed no earlier than 8.11). - Avoiding breaking changes to configuration, endpoints, or backup-related features within the same minor release range. This balance allows continuous improvement to tools like Web Modeler and Console, while preserving stability for orchestration logic. ## Client and API compatibility Public API components follow strict compatibility guarantees so you can upgrade clients and clusters independently. | Component | Forward-compatible | Backward-compatible | | ----------------------------------------------------------------------------------------------------------------------- | :----------------: | :-----------------: | | [Camunda Java client](/apis-tools/java-client/getting-started.md) | ✅ Yes | ✅ Yes | | [Spring SDK](/apis-tools/camunda-spring-boot-starter/getting-started.md) | ✅ Yes | ✅ Yes | | [Node.js SDK](https://github.com/camunda/camunda-nodejs-sdk) | ✅ Yes | ✅ Yes | | [Camunda Process Test](/apis-tools/testing/getting-started.md) | ✅ Yes | ✅ Yes | | [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) | — | ✅ Yes | - **Forward-compatible**: A client can work with a _newer_ version of the Orchestration Cluster than the client version. - **Backward-compatible**: A client can be upgraded to a _newer_ version without requiring changes to your application code. ### Clients and SDKs The Camunda Java client, Spring SDK, Node.js SDK, and Camunda Process Test (CPT) are both **forward-compatible** with new releases of the Orchestration Cluster while being **backward-compatible** regarding application integration. - **Forward compatibility**: Your application can use an older client version and still work correctly against a newer cluster. For example, an application using Camunda Java client **8.8.3** works against an Orchestration Cluster running **8.9.1**. This allows you to upgrade the Orchestration Cluster first without immediately updating your client libraries. - **Backward compatibility**: When you upgrade the client or SDK version in your application, no code changes are required. New minor and patch versions of the clients do not introduce breaking changes. For example, upgrading the Spring SDK dependency from **8.8.x** to **8.9.x** does not require changes to your application code. ### Camunda Process Test (CPT) CPT is both forward-compatible with the Orchestration Cluster and Connectors runtime and backward-compatible regarding application integration: - **Forward-compatible**: You can change the version of the Orchestration Cluster/Connectors bundle in your tests to a newer version without any changes. - **Backward-compatible**: Upgrading the CPT dependency version does not require changes to your existing tests. ### REST APIs The [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) is **backward-compatible**. No breaking changes are introduced to existing endpoints in newer versions of the Orchestration Cluster. For example, if you build a custom client implementation against the **8.8** REST APIs, it continues to work against a newer Orchestration Cluster version such as **8.9**. :::note As REST API endpoints are part of the Orchestration Cluster itself, forward compatibility does not apply. Your cluster version determines the available endpoints. ::: ### Recommended upgrade order When upgrading across minor versions, the recommended approach is: 1. **Upgrade the Orchestration Cluster first.** Forward compatibility of clients ensures your existing applications continue to work. 2. **Upgrade your client libraries.** Backward compatibility of clients ensures no code changes are needed. --- ## Supported environments ## About supported environments **If a particular technology is not listed below, Camunda cannot resolve issues caused by its usage.** You can: - [Raise a feature request](/reference/contact.md) that will be evaluated by our product teams to provide official support from Camunda. - [Make a help request](/reference/contact.md) to work with Camunda consulting services. Recommendations are denoted with [recommended]. Other listed options are also supported. :::note Minimum versions The versions listed on this page are the minimum version required if appended with a `+`. Pay attention to where the `+` falls, as most of our dependencies follow [semantic versioning](https://semver.org/) (semver), where `x.y.z` correspond to MAJOR.MINOR.PATCH. Higher or more recent versions will be compatible with Camunda, with respect to semver. For example, 1.2+ means support for the minor version 2, and any higher minors (1.3, 1.4, etc.) and patches (1.2.1, 1.2.2, etc.), but not majors, like 2.x. ::: ## Web browsers - Google Chrome latest [recommended] - Mozilla Firefox latest - Microsoft Edge latest ## Desktop Modeler - Windows 10 / 11 - macOS 13 / 14 / 15 / 26 - Ubuntu LTS (latest) ## Clients - **Zeebe Java Client**: OpenJDK 8+ - **Connector SDK**: OpenJDK 17+ - **Camunda Spring Boot Starter**: OpenJDK 17+, Spring Boot 3.5.x, 4.1.x - **Helm CLI**: 4.x [recommended], 3.10+ (supported until February 10, 2027; continued use after that date is at the customer’s own risk). For details, see [Helm CLI support](/self-managed/deployment/helm/operational-tasks/helm-v4.md#camunda-helm-chart-compatibility) and the [version matrix](https://helm.camunda.io/camunda-platform/version-matrix/). ## Camunda 8 Self-Managed We recommend running Camunda 8 Self-Managed in a Kubernetes environment. We provide officially supported [Helm charts](/self-managed/setup/overview.md) for this. See the [installation guide](/self-managed/setup/overview.md) to learn more about the available installation options. ### Deployment options With the correct configuration, Camunda 8 Self-Managed can be deployed on any [Certified Kubernetes](https://www.cncf.io/training/certification/software-conformance/#benefits) distribution (cloud or on-premises), and is not tied to a specific Kubernetes version. The Helm chart supports the Kubernetes [official support cycle](https://kubernetes.io/releases/). The following are tested and supported deployment options for Kubernetes, Docker, and manual installation: - [Stock Kubernetes](/self-managed/deployment/helm/install/quick-install.md) - [Cloud service providers](/self-managed/deployment/helm/install/quick-install.md) [recommended] - [Amazon EKS](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/amazon-eks.md) - [Microsoft AKS](/self-managed/deployment/helm/cloud-providers/azure/microsoft-aks/microsoft-aks.md) - [Google GKE](/self-managed/deployment/helm/cloud-providers/gcp/google-gke.md) - [Red Hat OpenShift](/self-managed/deployment/helm/cloud-providers/openshift/redhat-openshift.md) - [Docker](/self-managed/deployment/docker/docker.md) (`linux/amd64`, `linux/arm64`) - [Manual](/self-managed/deployment/manual/install.md) :::note Helm chart compatibility Ensure the Camunda component versions are compatible with the Helm chart version as defined in the [matrix](https://helm.camunda.io/camunda-platform/version-matrix/). ::: ### Sizing The [sizing of a Camunda 8 installation](/components/best-practices/architecture/sizing-your-environment.md) depends on various influencing factors. Ensure to [determine these factors](../components/best-practices/architecture/sizing-your-environment.md#understanding-influencing-factors), and conduct [benchmarking](../components/best-practices/architecture/sizing-your-environment.md#running-experiments-and-benchmarks) to validate an appropriate environment size for your test, integration, or production environments. ### Persistent volumes Camunda supports different types of storage volumes, including block storage and network file systems (NFS). For details on typical volume usage, refer to these examples: - [Amazon EKS](/self-managed/reference-architecture/kubernetes.md#amazon-eks-1) - [Microsoft AKS](/self-managed/reference-architecture/kubernetes.md#microsoft-aks) - [Google GKE](/self-managed/reference-architecture/kubernetes.md#google-gke) #### Network File Systems Camunda guarantees support for Amazon Elastic File System (EFS). If you want to use another NFS, it must meet these requirements: - Be POSIX-compliant. - Never reorder file operations. - Retry I/O operations across temporary network failures, instead of failing on timeout. - Do not surface network-related failures in the client process. - **Only one container may mount the disk in write mode at a time.** Two containers mounting the same disk in write mode could cause data corruption. #### Performance Regardless of the type, the network storage volumes you use must meet these requirements: - They should provide enough sustained IOPS for your workload, which depends on the number of partitions per broker. See [Disk IOPS](/components/best-practices/architecture/sizing-self-managed.md#disk-iops) for a recommendation. - The latency of write/msync operations must be in the **low single digit milliseconds** under normal conditions. Ideally, it's in the order of microseconds. - The p99 latency must be **lower than 300 milliseconds**. - They must be SSD-backed. HDD-backed volumes typically sustain only tens to a few hundred IOPS with multi-millisecond seek latency, well below the IOPS and single-digit-millisecond latency required for Camunda, so they are not supported. ### Helm charts version matrix Camunda Helm chart version `15.x.x` works with Camunda version `8.10.x`. Check the [Helm chart version matrix](https://helm.camunda.io/camunda-platform/version-matrix/) for more details. ## Component requirements Requirements for components are as follows: | Component | Java version | Other requirements | | :--------------------------------------------------------- | :------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Orchestration Cluster (Zeebe, Operate, Tasklist, Identity) | OpenJDK 21–25 | Elasticsearch 9.4+Elasticsearch 8.19+OpenSearch 3.6+OpenSearch 2.19+For supported relational databases and versions when using an RDBMS (for example, as secondary storage), see the [RDBMS version support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) | | Optimize | OpenJDK 21–25 | Elasticsearch 9.4+Elasticsearch 8.19+OpenSearch 3.6+OpenSearch 2.19+ | | Connectors | OpenJDK 21–25 | – | | Management Identity | OpenJDK 17+ | Keycloak 26.xPostgreSQL 14.x, 15.x, 16.x, 17.x (required for [certain features](/self-managed/components/management-identity/miscellaneous/configuration-variables.md#database-configuration)), or Amazon Aurora PostgreSQL 13.x, 14.x, 15.x, 16.x, 17.xOracle 19cMicrosoft SQL Server 2019, 2022, 2025 | | Camunda Hub | – | Supported relational databases and versions are defined in the [RDBMS version support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) | :::info Optimize compatibility When running Optimize, make sure you use an [Elasticsearch exporter](/self-managed/components/orchestration-cluster/zeebe/exporters/elasticsearch-exporter.md) or [OpenSearch exporter](/self-managed/components/orchestration-cluster/zeebe/exporters/opensearch-exporter.md) version that is compatible with your Optimize version. ::: :::info RDBMS support For a complete list of supported RDBMS versions, JDBC driver information (bundled vs. user-supplied), and component compatibility when using relational databases as secondary storage, see the [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md). ::: ### OpenSearch and Elasticsearch support - Camunda 8 supports both [Amazon OpenSearch](https://aws.amazon.com/opensearch-service) and the open-source [OpenSearch](https://opensearch.org/) distribution. - Due to a [limitation](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/or1.html#or1-considerations) for the index refresh interval, [OR1 instances](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/or1.html) are not supported with Amazon OpenSearch. See [use Amazon OpenSearch Service with the Helm chart](/self-managed/deployment/helm/configure/database/using-external-opensearch.md). - When running Elasticsearch, you must have the [appropriate Elasticsearch privileges](/self-managed/concepts/databases/elasticsearch/elasticsearch-privileges.md). - Camunda 8 works with the Elasticsearch [default distribution](https://www.elastic.co/downloads/elasticsearch) available with the [Free or Gold+ Elastic license](https://www.elastic.co/pricing/faq/licensing#summary). ### Component version matrix The following matrix shows which component versions work together. The components in each cell must share the same `minor` and `patch` version except Desktop Modeler, which follows its own versioning and is not version-locked to other components. For Helm-managed deployments, use the Helm chart [version matrix](https://helm.camunda.io/camunda-platform/version-matrix/) as the source of truth for the component versions bundled in a supported chart release. Do not manually override bundled component image tags unless a specific upgrade guide or release note instructs you to do so. | [Orchestration Cluster](../self-managed/reference-architecture/reference-architecture.md#orchestration-cluster) | [Management and design](../self-managed/reference-architecture/reference-architecture.md#camunda-hub) | | --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | Orchestration Cluster 8.10.XConnectors 8.10.XOptimize 8.10.X | Management Identity 8.10.XCamunda Hub 8.10.XDesktop Modeler 5.52+ | | Orchestration Cluster 8.9.XConnectors 8.9.XOptimize 8.9.X | Management Identity 8.10.XCamunda Hub 8.10.XDesktop Modeler 5.46+ | | Orchestration Cluster 8.9.XConnectors 8.9.XOptimize 8.9.X | Management Identity 8.9.xSelf-Managed Console 8.9.xWeb Modeler 8.9.xDesktop Modeler 5.46+ | | Orchestration Cluster 8.8.XConnectors 8.8.XOptimize 8.8.X | Management Identity 8.10.XCamunda Hub 8.10.XDesktop Modeler 5.40+ | | Orchestration Cluster 8.8.XConnectors 8.8.XOptimize 8.8.X | Management Identity 8.8.xSelf-Managed Console 8.8.xWeb Modeler 8.8.xDesktop Modeler 5.40+ | --- ## Camunda 8 Self-Managed Introduction, QuickStart, Installation, Infrastructure, ReferenceArchitecture, Components, } from "@site/src/components/CamundaSelfManaged"; Get started with our self-hosted alternative to Camunda 8 SaaS. Deploy and manage Camunda in your own infrastructure. Quickstart guides and infrastructure and deployment options help you build a robust process automation platform for your organization. [Developer and administrator quickstart](./quickstart/overview.md) :::info Upgrade to Camunda 8.10 - Existing customer? Upgrade your Self-Managed deployment to 8.10 using the [upgrade to 8.10 guide](/self-managed/upgrade/index.md). - See [what's new in Camunda 8.11](/reference/announcements-release-notes/8110/whats-new-in-811.md), [release announcements](/reference/announcements-release-notes/8110/8110-announcements.md), and [release notes](/reference/announcements-release-notes/8110/8110-release-notes.md). ::: ## Self-Managed or SaaS? ## Quickstart ## Installation methods ## Infrastructure ## Reference architecture ## Components --- ## 880 To 890 --- ## Configuration(Connectors) You can configure the connector runtime environment in the following ways: - Specify the Zeebe instance to connect to. - Define the connector functions to run. - Provide the secrets that should be available to the connectors. :::note Starting from version 8.8, the connector runtime no longer requires a connection to Operate. It now depends only on the Orchestration Cluster REST API and Zeebe. ::: To connect to the **Orchestration Cluster**, the connector runtime uses the [Camunda Spring Boot Starter](/apis-tools/camunda-spring-boot-starter/getting-started.md). Any configuration available in the Spring Boot Starter can also be applied to the connector runtime environment. Below are some of the most common configuration options for the connector runtime. For a complete list, see the [Camunda Spring Boot Starter configuration reference](/apis-tools/camunda-spring-boot-starter/configuration.md#zeebe). :::note This guide presents configuration properties as environment variables, while the Camunda Spring Boot Starter documentation uses Java configuration properties. The two formats are interchangeable. You can also use Java configuration properties in the connector runtime environment. For example, the Java configuration property `camunda.client.grpc-address` can be set as the environment variable `CAMUNDA_CLIENT_GRPCADDRESS` in the connector runtime. ::: ## Configure the Orchestration Cluster connection for Self-Managed ### Connection URL To connect to the Orchestration Cluster, provide the following configuration: ```bash CAMUNDA_CLIENT_MODE=self-managed CAMUNDA_CLIENT_GRPCADDRESS=http://localhost:26500 CAMUNDA_CLIENT_RESTADDRESS=http://localhost:8080 ``` ```yaml camunda: client: mode: self-managed grpc-address: http://localhost:26500 rest-address: http://localhost:8080 ``` ### HTTPS configuration If using an HTTPS connection, you may need to provide a certificate to validate the Zeebe Gateway's certificate chain. ```bash CAMUNDA_CLIENT_CACERTIFICATEPATH=/path/to/certificate.pem ``` ```yaml camunda: client: ca-certificate-path: /path/to/certificate.pem ``` ### Authentication methods Choose the authentication method for your environment: By default, no authentication will be used. **Environment variables** ```bash CAMUNDA_CLIENT_AUTH_METHOD=none ``` **Application.yaml** ```yaml camunda: client: auth: method: none ``` To activate basic authentication: **Environment variables** ```bash CAMUNDA_CLIENT_AUTH_METHOD=basic CAMUNDA_CLIENT_AUTH_USERNAME= CAMUNDA_CLIENT_AUTH_PASSWORD= ``` **Application.yaml** ```yaml camunda: client: auth: method: basic username: password: ``` To activate OIDC-based authentication: **Environment variables** ```bash CAMUNDA_CLIENT_AUTH_METHOD=oidc CAMUNDA_CLIENT_AUTH_CLIENTID=xxx CAMUNDA_CLIENT_AUTH_CLIENTSECRET=xxx CAMUNDA_CLIENT_AUTH_TOKENURL=http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token CAMUNDA_CLIENT_AUTH_AUDIENCE= CAMUNDA_CLIENT_AUTH_SCOPE= ``` **Application.yaml** ```yaml camunda: client: auth: method: oidc client-id: client-secret: token-url: http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token audience: scope: ``` **Notes for Microsoft Entra ID** - Instead of `scope: CLIENT_ID_OC`, use: `scope: CLIENT_ID_OC + "/.default"`. - The `token-url` is typically formatted as: `https://login.microsoftonline.com//oauth2/v2.0/token`. :::note Audience validation If you have configured the audiences property for the Orchestration Cluster (`camunda.security.authentication.oidc.audiences`), the Orchestration Cluster will validate the audience claim in the token against the configured audiences. Ensure your token has the correct audience from the Orchestration Cluster configuration, or add your audience in the configuration. This is often the client ID you used when setting up the Orchestration Cluster. ::: See the [Camunda Spring Boot Starter documentation](../../../../apis-tools/camunda-spring-boot-starter/getting-started#self-managed) for more information on authentication properties. ## Configure the Orchestration Cluster connection for SaaS To use Camunda 8 SaaS, specify the connection properties: ```bash CAMUNDA_CLIENT_MODE=saas CAMUNDA_CLIENT_AUTH_CLIENTID=xxx CAMUNDA_CLIENT_AUTH_CLIENTSECRET=xxx CAMUNDA_CLIENT_CLOUD_REGION=bru-2 CAMUNDA_CLIENT_CLOUD_CLUSTERID=xxx ``` ```yaml camunda: client: mode: saas auth: client-id: xxx client-secret: xxx cloud: region: bru-2 cluster-id: xxx ``` If you are connecting a local connector runtime to a SaaS cluster, you may want to review our [guide to using connectors in hybrid mode](/components/connectors/use-connectors-in-hybrid-mode.md). ## Manual discovery of connectors By default, the connector runtime picks up outbound connectors available on the classpath automatically. To disable this behavior, use the following environment variables to configure connectors explicitly: | Environment variable | Purpose | | :-------------------------------------------- | :------------------------------------------------------------ | | `CONNECTOR_{NAME}_FUNCTION` (required) | Function to be registered as job worker with the given `NAME` | | `CONNECTOR_{NAME}_TYPE` (optional) | Job type to register for worker with `NAME` | | `CONNECTOR_{NAME}_INPUT_VARIABLES` (optional) | Variables to fetch for worker with `NAME` | | `CONNECTOR_{NAME}_TIMEOUT` (optional) | Timeout in milliseconds for worker with `NAME` | Through this configuration, you define all job workers to run. Specifying optional values allows you to override `@OutboundConnector`-provided connector configuration. ```bash CONNECTOR_HTTPJSON_FUNCTION=io.camunda.connector.http.rest.HttpJsonFunction CONNECTOR_HTTPJSON_TYPE=non-default-httpjson-task-type ``` ## Disabling Individual Connectors To disable individual connectors you can provide a comma separated list to `CONNECTOR_INBOUND_DISABLED` and `CONNECTOR_OUTBOUND_DISABLED` respectively. These list must contain the _connector type_ (e.g. `io.camunda:http-json:1`). To disable two outbound connectors, you can set the environment variable as follows: ```bash CONNECTOR_OUTBOUND_DISABLED=io.camunda:example:1,com.acme:custom-connector:2 ``` This can be found as the `` in the BPMN XML, the `zeebe:taskDefinition` property [in the element template](https://github.com/camunda/connectors/blob/8d2304754e202b56ae8c821746e99e1e9ef50c73/connectors/http/rest/element-templates/http-json-connector.json#L48) or in the `OutboundConnector` annotation for outbound connectors. The inbound connector type can be found as ``, the `inbound.type` property [in the element template](https://github.com/camunda/connectors/blob/8d2304754e202b56ae8c821746e99e1e9ef50c73/connectors/webhook/element-templates/webhook-connector-start-message.json#L51) or in the `InboundConnector` annotation. ## Disabling connector discovery :::warning We do not guarantee that all the Camunda provided connectors will be discovered via SPI. If you want to have a connector runtime without out-of-the-box connectors, we recommend building a custom runtime with only the connectors you want to use. ::: To disable the discovery of connectors via SPI or environment variables as explained [in this section](#manual-discovery-of-connectors), set the following environment variables: `CONNECTOR_INBOUND_DISCOVERY_DISABLED` and `CONNECTOR_OUTBOUND_DISCOVERY_DISABLED`. Note that this does not prevent the registration of connectors via Spring Beans or other mechanisms. ## Configure inbound webhook request limits Use the following Spring properties or equivalent environment variables to configure HTTP Webhook request limits: | Spring property | Environment variable | Default | Description | | --------------------------------------------------------- | --------------------------------------------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------- | | `camunda.connector.webhook.max-request-body-bytes` | `CAMUNDA_CONNECTOR_WEBHOOK_MAX_REQUEST_BODY_BYTES` | `10485760` (10 MB) | Maximum request body size in bytes, except for `multipart/form-data`. The value must be a non-negative integer. | | `camunda.connector.webhook.rate-limit.enabled` | `CAMUNDA_CONNECTOR_WEBHOOK_RATE_LIMIT_ENABLED` | `true` | Enables the global request rate limit shared by all webhook paths. | | `camunda.connector.webhook.rate-limit.permits-per-second` | `CAMUNDA_CONNECTOR_WEBHOOK_RATE_LIMIT_PERMITS_PER_SECOND` | `1000` | Maximum sustained requests per second across all webhook paths. The value must be positive and finite. | | `spring.servlet.multipart.max-file-size` | `SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE` | `10MB` | Maximum size of each file in a `multipart/form-data` request. | | `spring.servlet.multipart.max-request-size` | `SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE` | `10MB` | Maximum total size of a `multipart/form-data` request. | Requests that exceed a body or multipart limit receive an HTTP `413` response. Requests that exceed the available rate-limit permits receive an HTTP `429` response. These responses have an empty body. Spring parses `multipart/form-data` requests before the Connector Runtime applies `camunda.connector.webhook.max-request-body-bytes`, so configure multipart limits separately. The global rate limit also applies to multipart requests, but the servlet container can parse them before the rate limit is evaluated. Rate limiting controls sustained throughput across all webhook paths; it does not limit the number of concurrent requests. The Connector Runtime fails to start if the request-body limit is negative or an enabled rate limit has an invalid permits-per-second value. ## Secrets Providing values for [legacy secret references](/reference/glossary.md#secret-reference-legacy) to the runtime environment can be achieved in different ways, depending on your setup. To move to the recommended `camunda.secrets.` syntax, resolved by the Orchestration Cluster from a configured secret store, see [Migrate to `camunda.secrets.`](/components/connectors/use-connectors/migrate-secrets.md). Starting with Camunda 8.9, the environment-based secret provider applies the prefix `SECRET_` by default when resolving secrets. Only environment variables that start with this prefix are available as connector secrets. This improves security by preventing all environment variables from being exposed as connector secrets. Existing secrets that do not use the configured prefix will no longer resolve until you update either the environment variables or the prefix configuration. #### Configure a custom prefix To use a custom prefix, configure it via the Java property or environment variable and name your secrets accordingly: ```bash export CAMUNDA_CONNECTOR_SECRETPROVIDER_ENVIRONMENT_PREFIX='SUPER_SECRETS_' export SUPER_SECRETS_MY_SECRET='foo' # Resolved via {{ secrets.MY_SECRET }} ``` #### Restore the previous behavior (unsafe) To restore the previous behavior where all environment variables can be used as connector secrets, set the prefix to an empty value: ``` camunda.connector.secret-provider.environment.prefix= ``` :::warning When no prefix is configured, the connector runtime logs a warning that this mode is unsafe because all environment variables are exposed as connector secrets. Camunda does not recommend this mode for production environments. ::: The following environment variables can be used to configure the default secret provider: | Name | Description | Default value | | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | `CAMUNDA_CONNECTOR_SECRETPROVIDER_ENVIRONMENT_ENABLED` | Whether the default secret provider is enabled. | `true` | | `CAMUNDA_CONNECTOR_SECRETPROVIDER_ENVIRONMENT_PREFIX` | Prefix applied to the secret name before lookup. Only environment variables starting with this prefix are available as secrets. Set to empty to disable (unsafe). | `SECRET_` | | `CAMUNDA_CONNECTOR_SECRETPROVIDER_ENVIRONMENT_TENANTAWARE` | Whether the secret provider should be tenant-aware. | `false` | If the secret provider is set to be tenant-aware, the secret format will change to `${prefix}${tenantId}_${secretName}`: Example with empty prefix: ```bash export CAMUNDA_CONNECTOR_SECRETPROVIDER_ENVIRONMENT_TENANTAWARE=true export tenant1_MY_SECRET='foo' # This will be resolved by using {{ secrets.MY_SECRET }} from tenant1 ``` Example with prefix set: ```bash export CAMUNDA_CONNECTOR_SECRETPROVIDER_ENVIRONMENT_TENANTAWARE=true export CAMUNDA_CONNECTOR_SECRETPROVIDER_ENVIRONMENT_PREFIX='SUPER_SECRETS_' export SUPER_SECRETS_tenant1_MY_SECRET='foo' # This will be resolved by using {{ secrets.MY_SECRET }} from tenant1 ``` Connector secrets can be used in Helm charts, for example by referencing a [Kubernetes secret](https://kubernetes.io/docs/concepts/configuration/secret/): ```yaml connectors: envFrom: - secretRef: name: camunda-connector-secrets ``` ``` apiVersion: v1 kind: Secret metadata: name: camunda-connector-secrets stringData: SECRET_MY_SECRET: foo ``` Review the documentation on [managing secrets in Helm charts](/self-managed/deployment/helm/configure/secret-management.md) for additional details. To inject secrets into the [Docker images of the runtime](/self-managed/deployment/docker/docker.md#connectors), they must be available in the environment of the Docker container. For example, you can inject secrets when running a container: ```bash docker run --rm --name=connectors -d \ -v $PWD/connector.jar:/opt/app/ \ -e SECRET_MY_SECRET=secret \ -e SECRET_FROM_SHELL \ --env-file secrets.txt \ camunda/connectors-bundle:latest ``` The `-v` option adds a connector JAR to the classpath. The `SECRET_MY_SECRET` value is specified directly in the `docker run` call, whereas the `SECRET_FROM_SHELL` is injected based on the value in the current shell environment when `docker run` is executed. The `--env-file` option allows using a single file with the format `NAME=VALUE` per line to inject multiple secrets at once. In the [manual setup](/self-managed/deployment/manual/install.md#connectors-1), inject secrets during connector execution by providing them as environment variables before starting the runtime environment. You can, for example, export them beforehand as follows: ```bash export SECRET_MY_SECRET='foo' ``` Reference the secret in the connector's input in the prefixed style `{{secrets.MY_SECRET}}`. Create your own implementation of the `io.camunda.connector.api.secret.SecretProvider` interface that [comes with the SDK](https://github.com/camunda/connectors/blob/main/connector-sdk/core/src/main/java/io/camunda/connector/api/secret/SecretProvider.java). Package this class and all its dependencies as a JAR, for example `my-secret-provider-with-dependencies.jar`. This needs to include a file `META-INF/services/io.camunda.connector.api.secret.SecretProvider` that contains the fully qualified class name of your secret provider implementation. Add this JAR to the runtime environment, depending on your deployment setup. Your secret provider will serve secrets as implemented. To use this JAR with [Camunda Helm charts](https://artifacthub.io/packages/helm/camunda/camunda-platform), build an [init container](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) to create a volume with your secret provider, and mount it into the connectors pod. For example, use the following file as input for your `helm install` command: ```bash connectors: extraVolumes: - name: workdir emptyDir: {} extraVolumeMounts: # Mount the secret provider # The Connectors pod will pick up the secret provider from /opt/app during startup - name: workdir mountPath: /opt/app/file-secret-provider-2.1.2.jar subPath: file-secret-provider-2.1.2.jar initContainers: - name: install image: busybox:1.36.1 command: ["sh", "-c"] args: # Download a the custom secret provider into the volume - | wget -O /work-dir/file-secret-provider-2.1.2.jar https://artifacts.camunda.com/artifactory/camunda-consulting/com/camunda/consulting/connector/file-secret-provider/2.1.2/file-secret-provider-2.1.2.jar volumeMounts: - name: workdir mountPath: "/work-dir" securityContext: runAsUser: 1000 # redundant as 1000 is not root but good to have # as the runtime will do verification that no process will # run as root within the container runAsNonRoot: true ``` For Docker images, you can add the JAR by using volumes, for example: ```bash # Mount the secret provider JAR and set the gRPC address of Zeebe docker run --rm --name=connectors -d \ -v $PWD/my-secret-provider-with-dependencies.jar:/opt/app/my-secret-provider-with-dependencies.jar \ -e CAMUNDA_CLIENT_GRPCADDRESS=http://ip.address.of.zeebe:26500 \ camunda/connectors:latest ``` In manual installations, add the JAR to the `-cp` argument of the Java call: ```bash java -cp 'connector-runtime-application-VERSION-with-dependencies.jar:...:my-secret-provider-with-dependencies.jar' \ io.camunda.connector.runtime.ConnectorRuntimeApplication ``` ## Secret filter The secret filter restricts connectors to resolving only the secrets they declare in their own configuration. This prevents a connector from resolving secrets that are available in the runtime environment but not referenced by that connector. ### How the allow-list is built Every field you configure in a connector's properties panel is implemented as a Zeebe input mapping under the hood, whether it's an authentication field or a functional field like an email body, an HTTP header, or a query parameter. If a field contains a literal `{{secrets.NAME}}` reference, the filter allow-lists `NAME` for that specific field, identified by its field path — not for the connector element as a whole. The allow-list is built once per element, from the deployed BPMN model, by scanning the literal text of that element's own fields for `{{secrets.NAME}}` references and recording which field each reference belongs to: - **It's static, not dynamic.** The filter looks at what's literally written in the model, not at what a process variable resolves to at runtime. If a secret value already resolved by one connector task later flows into a different task's field as a plain process variable (for example, `= myVariable`), that's just data at that point. There's no `{{secrets.*}}` placeholder left for the filter to check, so the filter has no say over it either way. - **It's scoped to the field, not just the element.** A secret declared on one field (for example, `authentication.password = {{secrets.AUTH}}`) doesn't become resolvable on a _different_ field of the same task, such as an email body. If that other field's runtime value happens to contain the literal text `{{secrets.AUTH}}` — for example, because it evaluates a process variable crafted to contain that string — the filter checks it against that field's own allow-list entry, not `authentication.password`'s, and leaves it unresolved. - **One exception: fields the model itself chains together.** If one field's FEEL expression assigns from a name that another field's expression also references (for example, `url = baseUrl + "/path"`, where `baseUrl` is itself another input on the same element), the secret declared on the first field is also allowed on the second — the model author's own expressions connect them. This is still resolved statically, from the deployed model's FEEL expressions, not from arbitrary runtime process-variable content. - **It's still per element, not per process.** A secret referenced only on task A never becomes available to task B: task B's allow-list is built only from task B's own fields. This closes the gap an element-wide allow-list would leave open: declaring a secret anywhere on a task no longer makes it resolvable from every field on that task — only from the field it was declared on (and fields the model explicitly chains to it). :::note For inbound connectors, the allow-list comes from data already held in memory on the deployed element, so there's no remote lookup that can fail. As a result, `LAX` and `STRICT` behave identically for inbound connectors: both enforce the allow-list unconditionally. The distinction between `LAX` and `STRICT` described below only affects outbound connectors, where building the allow-list requires a lookup against the process definition. ::: ### Modes Configure the secret filter with the `camunda.connector.secret-resolver.secret-filter.mode` property: | Mode | Behavior | | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `STRICT` | Enforces the allow-list unconditionally. If the process definition cannot be retrieved, the Zeebe job fails and retries are triggered. This is the default. Choose this mode when strict secret isolation is required. | | `LAX` | Enforces the allow-list when the process definition is available. Falls back to allowing all secrets if the process definition cannot be retrieved (for example, due to an API outage or an eventual-consistency delay). Choose this mode when uninterrupted job processing matters more than strict secret isolation. | | `DISABLED` | All secrets resolve freely, matching the behavior before this feature was introduced. Choose this mode only for troubleshooting, or if a custom secret provider needs unrestricted access. | The allow-list is derived automatically from the fields of the deployed connector element. No manual configuration of individual secrets is required. ```bash CAMUNDA_CONNECTOR_SECRETRESOLVER_SECRETFILTER_MODE=LAX ``` ```yaml camunda: connector: secret-resolver: secret-filter: mode: LAX ``` ### Configure the mode in the Helm chart The Helm chart has no dedicated value for the secret filter. Set the mode through the generic `connectors.env` value: ```yaml connectors: env: - name: CAMUNDA_CONNECTOR_SECRETRESOLVER_SECRETFILTER_MODE value: LAX ``` ### Cache configuration The secret filter caches process definition lookups to avoid repeated API calls. You can configure the cache with the following properties: | Property | Environment variable | Description | Default | | ---------------------------------------------------------------- | ------------------------------------------------------------- | ----------------------------------------------- | ------- | | `camunda.connector.secret-resolver.secret-filter.cache.enabled` | `CAMUNDA_CONNECTOR_SECRETRESOLVER_SECRETFILTER_CACHE_ENABLED` | Whether caching is enabled. | `true` | | `camunda.connector.secret-resolver.secret-filter.cache.max-size` | `CAMUNDA_CONNECTOR_SECRETRESOLVER_SECRETFILTER_CACHE_MAXSIZE` | Maximum number of process definitions to cache. | `1000` | ### Secure secret usage best practices - Keep the mode at `STRICT` (the default) in production environments. Reserve `LAX` for cases where a temporary process definition API outage must not block connector jobs, and reserve `DISABLED` for troubleshooting only. - Reference only the secrets a connector task actually needs, in the fields that need them. A task that references fewer secrets has a smaller allow-list, which limits what that task can resolve even when its other field values come from untrusted process variables. - Scope secrets narrowly, for example one API key per integration or tenant, instead of reusing a single broad-access secret across multiple connector tasks. - Under `STRICT`, you don't need to design BPMN diagrams defensively to keep a secret out of a task's other fields. The runtime enforces the allow-list per field: a secret declared on one field of a task isn't resolvable from a different field on that same task, or from a different task, unless the model itself chains them together with a FEEL expression. ### Troubleshooting a secret that stops resolving under STRICT If a secret that previously resolved now comes back unresolved, or the connector job fails, under `STRICT` mode, check the following: - For outbound connectors, the element is a supported BPMN type (`ServiceTask`, `SendTask`, `ScriptTask`, `BusinessRuleTask`, `SubProcess`, `IntermediateThrowEvent`, or `EndEvent`) with a `zeebe:input` mapping that contains the secret reference. Unsupported element types and supported elements without such an input mapping are treated as declaring no secrets and deny all resolution under `STRICT`. - The secret is referenced using the `{{secrets.NAME}}` syntax in the same field where you expect it to resolve. A reference declared on one field doesn't resolve on a different field, unless the model chains the two fields together with a FEEL expression. - The `{{secrets.NAME}}` reference sits inside a JSON string, like any other field value. An unquoted placeholder on a non-string field (for example, `"count": {{secrets.MAX}}`) is never substituted. - The process definition is available to the connector runtime. Under `STRICT`, a Zeebe job fails and retries if the process definition can't be retrieved. If you need to keep jobs processing while you investigate, switch to `LAX` temporarily. It falls back to allowing all secrets when the process definition lookup fails. ## Intrinsic function allow-list {#intrinsic-function-allow-list} The intrinsic function allow-list restricts outbound connectors to running only the [intrinsic function](/components/connectors/advanced-topics/intrinsic-functions.md) calls that the deployed BPMN process declares. Starting with Connectors 8.10.0, the runtime enforces it by default. ### How the allow-list is built The runtime builds the allow-list from the deployed BPMN model. It scans the `zeebe:input` mappings of the connector element for calls written as literals, such as `{"camunda.function.type": "getText", "params": [document]}`, and records each call by function name and field path. - A call runs only if its function name and field path both match a declared call exactly. A declaration doesn't cover a field path beneath it. - A call that arrives through a process variable or a job variable, and that the model doesn't declare, is refused before the connector runs. The job fails with an error. - The runtime reads the deployed model only when a job's variables contain an intrinsic function call. Jobs without calls don't trigger a lookup. - The runtime retrieves the process definition the same way the secret filter does, and caches the result. For the rules that decide which calls count as declared, see [declare intrinsic function calls in the process model](/components/connectors/advanced-topics/intrinsic-functions.md#declare-calls). ### Modes Configure the allow-list with the `camunda.connector.intrinsic-function.allow-list.mode` property: | Mode | Behavior | | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ENABLED` | Runs only the intrinsic function calls that the deployed BPMN model declares. If the runtime can't retrieve the process definition, the Zeebe job fails and retries are triggered. This is the default. | | `DISABLED` | Refuses every intrinsic function call without reading the process definition. This mode doesn't allow all calls. Choose it only if the runtime can't reach the process definition lookup and none of your processes use intrinsic functions. | Unlike the [secret filter](#secret-filter), the allow-list has no `LAX` mode, and `DISABLED` never turns the check off. The allow-list always fails closed. ```bash CAMUNDA_CONNECTOR_INTRINSICFUNCTION_ALLOWLIST_MODE=ENABLED ``` ```yaml camunda: connector: intrinsic-function: allow-list: mode: ENABLED ``` ### Configure the mode in the Helm chart The Helm chart has no dedicated value for the allow-list. Set the mode through the generic `connectors.env` value: ```yaml connectors: env: - name: CAMUNDA_CONNECTOR_INTRINSICFUNCTION_ALLOWLIST_MODE value: ENABLED ``` ### Cache configuration The allow-list caches the declared calls of each process definition to avoid repeated lookups. You can configure the cache with the following properties: | Property | Environment variable | Description | Default | | ---------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------- | | `camunda.connector.intrinsic-function.allow-list.cache.enabled` | `CAMUNDA_CONNECTOR_INTRINSICFUNCTION_ALLOWLIST_CACHE_ENABLED` | Whether caching is enabled. | `true` | | `camunda.connector.intrinsic-function.allow-list.cache.max-size` | `CAMUNDA_CONNECTOR_INTRINSICFUNCTION_ALLOWLIST_CACHE_MAXSIZE` | Maximum number of process definitions to cache. | `1000` | | `camunda.connector.bpmn-model.cache.enabled` | `CAMUNDA_CONNECTOR_BPMNMODEL_CACHE_ENABLED` | Whether the cache of deployed BPMN models is enabled. The secret filter and the allow-list share this cache. | `true` | | `camunda.connector.bpmn-model.cache.max-size` | `CAMUNDA_CONNECTOR_BPMNMODEL_CACHE_MAXSIZE` | Maximum number of deployed BPMN models to cache. | `1000` | ### Troubleshoot refused intrinsic function calls If an intrinsic function call that previously ran now fails the job, see [troubleshoot intrinsic function errors](/components/connectors/advanced-topics/intrinsic-functions.md#troubleshoot-intrinsic-function-errors). ## HTTP proxy configuration You can configure connectors to route HTTP requests through a proxy server. See [HTTP proxy configuration](./http-proxy-configuration.md) for details. ## Truststore If your connector runtime needs to connect to external systems over HTTPS, you might need to provide a custom truststore. To configure the truststore, use the following environment variables: - `JAVAX_NET_SSL_TRUSTSTORE`: Path to the truststore file (e.g., `/path/to/truststore.jks`) - `JAVAX_NET_SSL_TRUSTSTOREPASSWORD`: Password for the truststore ## Configure the App Integrations connection The [App Integrations connector](/components/connectors/out-of-the-box-connectors/app-integrations.md) sends messages to Microsoft Teams and Slack through your organization's Camunda app integrations. The runtime holds the connection, so no process model carries an endpoint or a credential. Configure this only if you have installed app integrations, as described in [Install app integrations](/components/camunda-integrations/app-integrations/installation.md). Until the runtime is configured, every App Integrations job fails with `APP_INTEGRATIONS_NOT_CONFIGURED` and raises an incident. ### Connection settings | Environment variable | Helm value | Required | Description | | :---------------------------- | :------------------------------------- | :------------- | :----------------------------------------------------------------------------------------------------- | | `APP_INTEGRATIONS_BASE_URL` | `connectors.appIntegrations.baseUrl` | Yes | Base URL of your app integrations deployment. | | `APP_INTEGRATIONS_CLUSTER_ID` | `connectors.appIntegrations.clusterId` | With OAuth 2.0 | The cluster's UUID as declared in the app integrations `clusters` configuration, not the cluster name. | App integrations use the cluster ID to tell which cluster a call comes from. When the runtime authenticates with an API key, app integrations identify the cluster from the key instead, so the cluster ID is optional. The physical tenant is not configured here. The connector reads it from the job it is executing, as described in [how the runtime identifies the Physical Tenant](/self-managed/concepts/physical-tenants/connectors-runtime.md#how-the-runtime-identifies-the-physical-tenant). ### Choose an authentication method The runtime authenticates with either OAuth 2.0 client credentials or an API key. The method is selected from the values you set, in this order: 1. If the token endpoint, client ID, and client secret are all set, the runtime uses OAuth 2.0 client credentials. This wins even when an API key is also set. 1. Otherwise, if an API key is set, the runtime uses API key authentication. 1. Otherwise the connector is not configured, and every job fails. | Environment variable | Helm value | Required | Description | | :--------------------------------------------- | :------------------------------------------------------ | :---------- | :------------------------------------------------------------------------------------- | | `APP_INTEGRATIONS_API_KEY` | `connectors.appIntegrations.apiKey.secret` | For API key | Sent in the `X-API-KEY` header. | | `APP_INTEGRATIONS_OAUTH_TOKEN_ENDPOINT` | `connectors.appIntegrations.oauth.tokenEndpoint` | For OAuth | OAuth 2.0 token endpoint. | | `APP_INTEGRATIONS_OAUTH_CLIENT_ID` | `connectors.appIntegrations.oauth.clientId` | For OAuth | OAuth 2.0 client ID. | | `APP_INTEGRATIONS_OAUTH_CLIENT_SECRET` | `connectors.appIntegrations.oauth.secret` | For OAuth | OAuth 2.0 client secret. | | `APP_INTEGRATIONS_OAUTH_AUDIENCE` | `connectors.appIntegrations.oauth.audience` | No | Identifier of the API the token is requested for. | | `APP_INTEGRATIONS_OAUTH_SCOPES` | `connectors.appIntegrations.oauth.scopes` | No | Requested token scopes. | | `APP_INTEGRATIONS_OAUTH_CLIENT_AUTHENTICATION` | `connectors.appIntegrations.oauth.clientAuthentication` | No | How the credentials are transmitted: `credentialsBody` (default) or `basicAuthHeader`. | ### Configure App Integrations with the Helm chart The two secret values, `apiKey` and `oauth`, take the chart's standard secret block. Reference an existing Kubernetes Secret in production, and use `inlineSecret` only for local testing. To authenticate with OAuth 2.0 client credentials: ```yaml connectors: appIntegrations: baseUrl: https://app-integrations.example.com clusterId: 11111111-2222-3333-4444-555555555555 oauth: tokenEndpoint: https://idp.example.com/oauth/token clientId: camunda-app-integrations secret: existingSecret: app-integrations-oauth existingSecretKey: client-secret ``` To authenticate with an API key, where the cluster ID can be omitted: ```yaml connectors: appIntegrations: baseUrl: https://app-integrations.example.com apiKey: secret: existingSecret: app-integrations-api-key existingSecretKey: api-key ``` The chart rejects a partially configured OAuth block, and OAuth without a cluster ID, at install time rather than at first job execution. ## Multi-tenancy The Connector Runtime supports multiple tenants for inbound and outbound connectors. These are configurable in [Orchestration Cluster Admin](/components/admin/tenant.md). A single Connector Runtime can serve a single tenant or can be configured to serve multiple tenants. By default, the runtime uses the tenant ID `` for all Zeebe-related operations like handling jobs and publishing messages. The tenants described on this page are logical tenants. Camunda 8 Self-Managed also supports [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md), which are strongly isolated execution units within a single Orchestration Cluster and are configured separately. One Connector Runtime can serve several Physical Tenants, each with its own client, job workers, and secrets. See [Connectors runtime: Physical Tenant support](/self-managed/concepts/physical-tenants/connectors-runtime.md). :::info Support for **outbound connectors** with multiple tenants requires a dedicated tenant job worker config (described below). **Inbound connectors** automatically work for all tenants the configured Connector Runtime client has access to. This can be configured in Admin via the application assignment. ::: ### Environment variables The Connector Runtime uses the following environment variables to configure multi-tenancy: | Name | Description | Default value | | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | CAMUNDA_CLIENT_TENANTID | The default tenant ID used to communicate with Zeebe. Changing this value will set a new default tenant ID used for fetching jobs and publishing messages. | `` | | CAMUNDA_CLIENT_WORKER_DEFAULTS_TENANTIDS | The default tenant IDs (comma separated) used to activate jobs. To run the Connector Runtime in a setup where a single runtime serves multiple tenants, add each tenant ID to this list. | `` | If you are using an embedded version of the Connector Runtime, you can specify the tenant information in your Spring configuration like in this example `application.properties` file: ```bash camunda.client.tenant-id=myTenant camunda.client.worker.defaults.tenant-ids=myTenant ``` ### Outbound connector config The Connector Runtime uses the default tenant for outbound connector-related features. If support for a different tenant or multiple tenants should be enabled, the tenants need to be configured individually using the following environment variables. If you want to use outbound connectors for a single tenant that is different from the default tenant, you can specify a different default tenant ID using: ```bash CAMUNDA_CLIENT_TENANTID=myTenant ``` This will change the default tenant ID used for fetching jobs and publishing messages to the tenant ID `myTenant`. It is possible to adjust the polling interval of connectors polling process definitions to Operate by setting the environment variable `CAMUNDA_CONNECTOR_POLLING_INTERVAL`. This variable allows you to control how often connectors fetch the process definitions, with the interval specified in milliseconds. For example, setting `CAMUNDA_CONNECTOR_POLLING_INTERVAL=20000` will configure the connectors to poll every 20 seconds. Example: ```bash CAMUNDA_CONNECTOR_POLLING_INTERVAL=10000 ``` :::note Inbound connectors will still be enabled for all tenants the Connector Runtime client has access to. ::: To run the connector Runtime in a setup where a single runtime serves multiple tenants, add each tenant ID to the list of the default job workers: ```bash CAMUNDA_CLIENT_WORKER_DEFAULTS_TENANTIDS=myTenant,otherTenant ``` In this case, the `CAMUNDA_CLIENT_TENANTID` will **not** be used for the configuration of job workers. ### Inbound Connector configuration The Connector Runtime fetches process definitions from the Orchestration Cluster REST API, and executes all inbound connectors within those processes independently of the outbound connector configuration without any additional configuration required from the user. To restrict the Connector Runtime inbound connector feature to a single tenant or multiple tenants, use Admin and assign the tenants the connector application should have access to. ### Troubleshooting To ensure seamless integration and functionality, the multi-tenancy feature must also be enabled across all associated components [if not configured in Helm](../../deployment/helm/configure/configure-logical-tenants.md) so users can view any data from tenants for which they have authorizations configured in Admin. Find more information (including links to component-specific configuration pages) on the [multi-tenancy concepts page](/components/concepts/multi-tenancy.md). ## Logging ### Changing the log level The log level can be changed globally by setting the environment variable `LOGGING_LEVEL_IO_CAMUNDA_CONNECTOR=DEBUG`. This changes the default log level for the `io.camunda.connector` package to `DEBUG`. You can use this package based log level approach also with custom connectors by providing your package (`my.package`) via this variable: `LOGGING_LEVEL_MY_PACKAGE=DEBUG`. To change the log level for all packages, change it for the `root` logger: `LOGGING_LEVEL_ROOT=DEBUG`. ### Google Stackdriver (JSON) logging To enable Google Stackdriver compatible JSON logging, set the environment variable `CONNECTORS_LOG_APPENDER=stackdriver` on the Connector Runtime. --- ## HTTP proxy configuration Configure HTTP proxy settings for Camunda connectors in Self-Managed environments. ## Configuration methods In Self-Managed environments, you can configure connectors to route HTTP requests through a proxy server using one of these two methods: | Configuration type | Scope | Example | | :---------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [JVM properties](https://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html) | JVM, **the whole runtime** will be affected. **Any HTTP client** used internally (for example, in a connector, or the Zeebe client) might be affected. | `-Dhttp.proxyHost=proxy -Dhttp.proxyPort=3128 -Dhttps.proxyHost=proxy -Dhttps.proxyPort=3128 -Dhttp.nonProxyHosts=OTHER_DOMAIN` | | Environment variables | Connector-scoped, **only supported connectors** such as the REST connector will be affected | `CONNECTOR_HTTP_PROXY_HOST=proxy; CONNECTOR_HTTP_PROXY_PORT=3128; CONNECTOR_HTTPS_PROXY_HOST=proxy; CONNECTOR_HTTPS_PROXY_PORT=3128; CONNECTOR_HTTP_NON_PROXY_HOSTS=OTHER_DOMAIN;` | ## HTTP/HTTPS properties Depending on the **target URL**, you can set the proxy as an HTTP or HTTPS protocol handler: - A target URL such as `http://example.com` will use the HTTP protocol handler. - A target URL such as `https://example.com` will use the HTTPS protocol handler. ### JVM properties You can set the following standard JVM properties for HTTP and HTTPS: | Property (HTTP target URL) | Property (HTTPS target URL) | Description | | :------------------------- | :-------------------------- | :------------------------------------------------------- | | `http.proxyHost` | `https.proxyHost` | The host name of the proxy server. | | `http.proxyPort` | `https.proxyPort` | The port number (default is 80 for HTTP, 443 for HTTPS). | Some HTTP clients might offer more properties to configure the proxy. For example, the [Apache HTTP client](https://hc.apache.org/httpcomponents-client-5.6.x/current/httpclient5/apidocs/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.html) used in the REST connector offers the following properties: | Property (HTTP target URL) | Property (HTTPS target URL) | Description | | :------------------------- | :-------------------------- | :------------------------------------------------ | | `http.proxyUser` | `https.proxyUser` | _(optional)_ The username to log in to the proxy. | | `http.proxyPassword` | `https.proxyPassword` | _(optional)_ The password to log in to the proxy. | The `http.nonProxyHosts` property applies to both HTTP and HTTPS target URLs: | Property | Description | | :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `http.nonProxyHosts` | _(optional)_ A list of hosts to connect to directly, bypassing the proxy.Specify as a list of patterns, separated by \|.Patterns can start or end with a `*` for wildcards.Any host matching one of these patterns uses a direct connection instead of a proxy. | :::note To ensure Camunda can properly access Camunda components when using JVM properties, non-proxy hosts must contain `camunda-platform-zeebe|camunda-platform-keycloak`. ::: :::important The connector runtime uses internal HTTP calls between pods (for example, for `/connectors/inbound-instances`) by resolving the headless service name to pod IPs. These internal calls use the resolved IP (for example, `10.7.108.230`), not the original service hostname. If you configure JVM-wide proxies using `-Dhttp.proxyHost` or `-Dhttps.proxyHost`, make sure `http.nonProxyHosts` (and/or `CONNECTOR_HTTP_NON_PROXY_HOSTS`) also includes patterns for your cluster CIDRs (for example, `10.*` or `10.244.*`), not only service DNS names like `*.svc.cluster.local`. Otherwise, internal pod-to-pod traffic may be routed through your corporate proxy and fail with 403 or TLS parsing errors. ::: ### Environment variables As an alternative to using JVM properties, the proxy settings can also be set with environment variables: | Variable (HTTP target URL) | Variable (HTTPS target URL) | Description | | :------------------------------ | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------- | | `CONNECTOR_HTTP_PROXY_HOST` | `CONNECTOR_HTTPS_PROXY_HOST` | The host name of the proxy server. | | `CONNECTOR_HTTP_PROXY_PORT` | `CONNECTOR_HTTPS_PROXY_PORT` | The port number. | | `CONNECTOR_HTTP_PROXY_USER` | `CONNECTOR_HTTPS_PROXY_USER` | _(optional)_ The username to log in to the proxy. | | `CONNECTOR_HTTP_PROXY_PASSWORD` | `CONNECTOR_HTTPS_PROXY_PASSWORD` | _(optional)_ The password to log in to the proxy. | | `CONNECTOR_HTTP_PROXY_SCHEME` | `CONNECTOR_HTTPS_PROXY_SCHEME` | _(optional)_ The scheme of the proxy server. This allows you to use the `https` protocol to contact your proxy. The default is `http`. | The `CONNECTOR_HTTP_NON_PROXY_HOSTS` variable applies to both HTTP and HTTPS target URLs: | Variable | Description | | :------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `CONNECTOR_HTTP_NON_PROXY_HOSTS` | _(optional)_ A list of hosts to connect to directly, bypassing the proxy.Specify as a list of patterns, separated by \|.Patterns can start or end with a `*` for wildcards.Any host matching one of these patterns uses a direct connection instead of a proxy.Connectors will also use the exception list provided by the `http.nonProxyHosts` JVM property if existing. | ### Plain proxy variables The standard proxy variables support configuring a TLS-based proxy (running under `https://`) via the `SCHEME` variable. However, some connector integrations use HTTP clients (such as the JDK HttpClient) that do not support TLS-based proxy connections. For these connectors, an alternative set of **plain proxy variables** is available. When both plain and standard variables are configured, the **plain variables take priority**. If the plain variables are not set, the connector falls back to the standard variables. | Variable (HTTP target URL) | Variable (HTTPS target URL) | Description | | :------------------------------------ | :------------------------------------- | :------------------------------------------------------------------ | | `CONNECTOR_HTTP_PLAIN_PROXY_HOST` | `CONNECTOR_HTTPS_PLAIN_PROXY_HOST` | The host name of the proxy server. | | `CONNECTOR_HTTP_PLAIN_PROXY_PORT` | `CONNECTOR_HTTPS_PLAIN_PROXY_PORT` | The port number. | | `CONNECTOR_HTTP_PLAIN_PROXY_USER` | `CONNECTOR_HTTPS_PLAIN_PROXY_USER` | _(optional)_ The username to log in to the proxy. | | `CONNECTOR_HTTP_PLAIN_PROXY_PASSWORD` | `CONNECTOR_HTTPS_PLAIN_PROXY_PASSWORD` | _(optional)_ The password to log in to the proxy. | | `CONNECTOR_HTTP_PLAIN_PROXY_SCHEME` | `CONNECTOR_HTTPS_PLAIN_PROXY_SCHEME` | _(optional)_ The scheme of the proxy server. The default is `http`. | :::note There is no plain variant of `NON_PROXY_HOSTS`. The standard `CONNECTOR_HTTP_NON_PROXY_HOSTS` variable applies to both standard and plain proxy configurations. ::: The following connectors support plain proxy variables: - [AI Agent connector](/components/connectors/out-of-the-box-connectors/agentic-ai-aiagent.md) - [MCP Client connector](/components/connectors/out-of-the-box-connectors/agentic-ai-mcp-client.md) - [A2A Client connector](/components/early-access/alpha/a2a-client/a2a-client.md) - [Vector database connector](/components/connectors/out-of-the-box-connectors/embeddings-vector-db.md) Not all providers within each connector support proxy configuration. Refer to the individual connector documentation for details on unsupported providers. The [REST connector](/components/connectors/protocol/rest.md) and the core SDK HTTP client use only the standard proxy variables. ## How proxy configuration works The process consists of two main steps: configuration and request handling. ### Set your configuration First, define how the proxy should behave. These are the available configuration options: - Enable or disable proxying. - Define which URLs should skip the proxy, listed as `nonProxyHosts`. - Define which URLs require authentication. ### Handle requests When a connector makes an HTTP request, it's handled according to your previously set configuration: 1. Check if the proxy is enabled: 1. Yes: Proceed with proxying. 1. No: Do not proxy; the request is handled directly. 1. Check if the host is listed in `nonProxyHosts`: 1. Yes: Do not proxy; the request bypasses the proxy. 1. No: Proceed with proxying. 1. Check if the proxy requires authentication: 1. Yes: The request is proxied only if authentication succeeds; otherwise, it returns an authentication error. 1. No: The request is proxied normally. --- ## Overview(Connectors) The concept of a [Connector](/components/connectors/introduction.md) consists of two parts: - The business logic is implemented by a connector function and executed by a [Connector runtime environment](/components/connectors/custom-built-connectors/connector-sdk.md#runtime-environments). - The user interface during modeling is provided using a [Connector template](/components/connectors/custom-built-connectors/connector-templates.md). In a [Self-Managed](/self-managed/about-self-managed.md) environment, you manage the execution environment for connectors yourself. Using our [Connector runtime environments](/components/connectors/custom-built-connectors/connector-sdk.md#runtime-environments), you can consume any set of connectors, including the [out-of-the-box connectors](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md) and custom connectors developed using the **[Connector SDK](/components/connectors/custom-built-connectors/connector-sdk.md)** and [Connector templates](/components/connectors/custom-built-connectors/connector-templates.md). You can find connectors developed by Camunda, partners, and the community in [Camunda Marketplace](https://marketplace.camunda.com/en-US/home). :::note Some out-of-the-box connectors are licensed under the [Camunda Self-Managed Free Edition license](https://camunda.com/legal/terms/cloud-terms-and-conditions/camunda-cloud-self-managed-free-edition-terms/). ::: ## Connector runtime The connector runtime environment can be installed using the supported [deployment options](/self-managed/setup/overview.md#production-installations). Currently, we support an installation of connectors with [Docker](/self-managed/deployment/docker/docker.md#docker-images-and-configuration-references), [Docker Compose](/self-managed/quickstart/developer-quickstart/docker-compose.md), [Helm charts](/self-managed/setup/overview.md), and the [manual setup](/self-managed/deployment/manual/install.md#connectors). ## Connector templates For using connectors in the Web or Desktop Modeler, you need to [provide connector templates](/components/connectors/custom-built-connectors/connector-templates.md#providing-and-using-connector-templates). For the [out-of-the-box connectors](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md) provided by Camunda, the Connectors release provides a set of all connector templates related to one [release version](https://github.com/camunda/connectors/releases). If you use the [Docker Compose](/self-managed/quickstart/developer-quickstart/docker-compose.md) installation, you can thus fetch all connector templates that match the versions of the connectors used in the backend. Alternatively, you can fetch the JSON templates from the respective connector's releases in the respective connectors folder in the [repository](https://github.com/camunda/connectors) at `connectors/{connector name}/element-templates`. :::note Match the template version to your Camunda version The [Camunda Marketplace](https://marketplace.camunda.com/en-US/home) only distributes the **latest** version of each connector element template. If your Self-Managed cluster runs an older Camunda 8 version, the latest template may not be compatible with it. Each element template declares the Camunda versions it supports in its [`engines.camunda`](/components/modeler/element-templates/template-metadata.md) field as a semantic version range, for example: ```json "engines": { "camunda": "^8.10" } ``` To find a compatible template, choose the highest template version whose `engines.camunda` range includes your Camunda version, rather than looking for an exact match. Templates are available from two places: - The [Connectors release](https://github.com/camunda/connectors/releases) matching your Camunda version publishes a `connectors-bundle-templates-{version}` archive containing the bundled connectors' templates, with versioned files named `{template name}-{version}.json`. - Connectors that keep a version history also store superseded templates in `element-templates/versioned/` alongside the current one in the [connectors repository](https://github.com/camunda/connectors). Not every connector has this directory. Import the version-matched file instead of the Marketplace version. ::: You can use the connector templates as provided or modify them to your needs as described in our [Connector templates guide](/components/connectors/custom-built-connectors/connector-templates.md). Review our [Connectors Awesome List](https://github.com/camunda-community-hub/camunda-8-connectors/tree/main) to find more connectors. --- ## Connector runtime performance This guide explains how to optimize the configuration of outbound connectors in your self-managed environment to achieve consistent and predictable performance. ## Overview Connector runtime performance primarily depends on: - The level of concurrency (`max in-flight` or `max-jobs-active`). - The performance and rate limits of the external systems your connectors interact with. - Whether the runtime uses polling or job streaming for job acquisition. Before tuning the runtime, ensure you have proper observability in place and confirm where the bottleneck is. ## Measure outbound connector performance The performance of a connector runtime is typically evaluated by two metrics: - Jobs processed per second (throughput) - Average processing time per activated job The connector runtime exposes various metrics via `/actuator/prometheus`, including: ``` camunda_client_worker_job_total camunda_connector_outbound_execution_time_seconds executor_seconds_count{name="camundaClientExecutor"} executor_seconds_max{name="camundaClientExecutor"} ``` Use Prometheus to scrape the metrics and visualize them. While no official Grafana dashboard is provided for connectors, you can use the [connectors community Grafana dashboard](https://github.com/camunda-community-hub/connectors-grafana-dashboard) as a starting point. ## Identify the bottleneck To better understand what adjustments you need, verify where the bottleneck is: - Is your third-party system limiting throughput? - Is CPU or memory saturated? - Is the connector runtime operating in streaming or polling mode? ### Job streaming :::note Starting with connector runtime version 8.9.0+, job streaming is enabled by default. ::: If your runtime operates in polling mode, you may observe the following side effects: - The runtime does not pick up new jobs immediately and instead waits until the previous batch of jobs is fully processed, even though there are available threads. - Job distribution is uneven between multiple replicas of the connector runtime, causing some replicas to be idle while others are overloaded. To mitigate these issues, you can enable job streaming by setting the following property in your connector runtime configuration: ``` camunda.client.worker.defaults.stream-enabled=true ``` We recommend enabling job streaming unless you have specific reasons to use polling mode. To learn more, see [Job streaming in the gateway](/self-managed/components/orchestration-cluster/zeebe/zeebe-gateway/job-streaming.md). ## Configure `max-in-flight` The maximum concurrency (`max-in-flight`) defines how many jobs the connector runtime can work on in parallel. The right configuration for `max-in-flight` depends on the executor type used by the connector runtime. ### Virtual threads executor (default) Starting with connector runtime version 8.9.0+, the connector runtime uses virtual threads by default. Virtual threads are lightweight threads that allow high concurrency with low resource consumption. They are optimized for I/O-bound workloads such as connectors. This feature builds on Project Loom, which is part of the standard Java Development Kit (JDK) starting with JDK 19. The current long-term support (LTS) release is JDK 21. When you use virtual threads, the `max-in-flight` configuration defines the maximum number of virtual threads that can process jobs in parallel. The connector runtime can efficiently handle a large number of virtual threads, so you can often set a high `max-in-flight` value (for example, 500) without running into resource constraints. To configure the `max-in-flight` for a connector runtime using virtual threads, set the following property: ``` camunda.client.worker.defaults.max-jobs-active=500 ``` There is no practical upper limit for `max in-flight` when using virtual threads, but usually, the performance bottleneck will be due to objective CPU limitations or external system rate limits. We recommend gradually increasing the `max-in-flight` value while monitoring the performance and resource usage of your connector runtime until you reach an optimal configuration. ### Platform threads executor You can disable virtual threads and use platform threads instead by setting the following property: ``` camunda.connector.virtual-threads.enabled=false ``` Connector runtime versions before 8.9.0 use platform threads by default. When using platform threads, use the configuration options provided by the [Camunda Spring Boot Starter](/apis-tools/camunda-spring-boot-starter/configuration.md): ``` camunda.client.worker.defaults.max-jobs-active=20 camunda.client.execution-threads=20 ``` The `max-jobs-active` property defines the maximum number of jobs that are activated in parallel. The `execution-threads` property defines the size of the thread pool used to process jobs. If you set `max-jobs-active` higher than `execution-threads`, the connector runtime queues jobs until a thread becomes available for processing. If you set `execution-threads` higher than `max-jobs-active`, some threads remain idle while waiting for jobs to be activated. --- ## Copilot Alpha Camunda Hub supports using large language models (LLMs) to help users create BPMN diagrams, write FEEL expressions, and build forms. You can configure the default LLM provider for BPMN, FEEL, and form copilots. Copilot supports the following LLM providers: | LLM provider | Configuration value | | ---------------- | ------------------- | | Anthropic | `ANTHROPIC` | | AWS Bedrock | `BEDROCK` | | Azure AI | `AZURE_AI` | | Azure OpenAI | `AZURE_OPENAI` | | Google Vertex AI | `VERTEX_AI` | | Hugging Face | `HUGGING_FACE` | | Ollama | `OLLAMA` | | OpenAI | `OPENAI` | ## Configuration To enable Copilot, set the AI feature flag (`camunda.hub.feature.ai-enabled` / `CAMUNDA_HUB_FEATURE_AIENABLED`) to `true`. Then configure the default LLM provider for BPMN, FEEL, and form copilots. Each provider has its own configuration options described below. | Property | Description | Example value | Default value | | ------------------------------------------------------- | -------------------------------------------------------------- | ------------- | ------------- | | `camunda.hub.feature.ai-enabled` | Enables Copilot. | `true` | `false` | | `camunda.hub.copilot.default-bpmn-copilot-llm-provider` | Default provider for BPMN Copilot. | `BEDROCK` | – | | `camunda.hub.copilot.default-feel-copilot-llm-provider` | Default provider for FEEL Copilot. | `OPENAI` | – | | `camunda.hub.copilot.default-form-copilot-llm-provider` | Default provider for form Copilot. | `VERTEX_AI` | – | | `camunda.hub.client.copilot-request-timeout` | [optional] Overall request timeout for Copilot requests in UI. | `200s` | `300s` | | Environment variable | Description | Example value | Default value | | --------------------------------------------------- | ------------------------------------------------------------------------------ | ------------- | ------------- | | `CAMUNDA_HUB_FEATURE_AIENABLED` | Enables Copilot. | `true` | `false` | | `CAMUNDA_HUB_COPILOT_DEFAULTBPMNCOPILOTLLMPROVIDER` | Default provider for BPMN Copilot. | `BEDROCK` | – | | `CAMUNDA_HUB_COPILOT_DEFAULTFEELCOPILOTLLMPROVIDER` | Default provider for FEEL Copilot. | `OPENAI` | – | | `CAMUNDA_HUB_COPILOT_DEFAULTFORMCOPILOTLLMPROVIDER` | Default provider for form Copilot. | `VERTEX_AI` | – | | `CAMUNDA_HUB_CLIENT_COPILOTREQUESTTIMEOUT` | [optional] Overall request timeout in milliseconds for Copilot requests in UI. | `200000` | `300000` | ### BPMN Copilot | Property | Description | Example value | Default value | | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -------------------- | ------------- | | `camunda.hub.copilot.default-bpmn-copilot-llm-configuration.temperature` | [optional] Sampling temperature. | `0.2` | `0.3` | | `camunda.hub.copilot.default-bpmn-copilot-llm-configuration.top-p` | [optional] Nucleus sampling probability. | `0.90` | `0.95` | | `camunda.hub.copilot.default-bpmn-copilot-llm-configuration.top-k` | [optional] Top-K sampling (if supported by the model). | `100` | `64` | | `camunda.hub.copilot.default-bpmn-copilot-llm-configuration.max-tokens` | [optional] Maximum new tokens per response. | `4096` | `8192` | | `camunda.hub.copilot.default-bpmn-copilot-llm-configuration.timeout` | [optional] Overall request timeout. | `45s` | `60s` | | `camunda.hub.copilot.default-bpmn-copilot-llm-configuration.log-requests` | [optional] Log raw requests (not recommended in production). | `true` | `false` | | `camunda.hub.copilot.default-bpmn-copilot-llm-configuration.log-responses` | [optional] Log raw responses (not recommended in production). | `true` | `false` | | `camunda.hub.copilot.default-bpmn-copilot-llm-configuration.connection-acquisition-timeout` | [optional] Connection pool acquisition timeout. | `10s` | `30s` | | `camunda.hub.copilot.default-bpmn-copilot-llm-configuration.logit-bias` | [optional] JSON object mapping token IDs to bias values (model-specific). | `{"123":-2,"456":3}` | `{}` | | `camunda.hub.copilot.default-bpmn-copilot-llm-configuration.max-connections` | [optional] Maximum HTTP connections. | `300` | `200` | | `camunda.hub.copilot.default-bpmn-copilot-llm-configuration.read-timeout` | [optional] Read timeout per request. | `120s` | `60s` | | Environment Variable | Description | Example Value | Default Value | | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -------------------- | ------------- | | `CAMUNDA_HUB_COPILOT_DEFAULTBPMNCOPILOTLLMCONFIGURATION_TEMPERATURE` | [optional] Sampling temperature. | `0.2` | `0.3` | | `CAMUNDA_HUB_COPILOT_DEFAULTBPMNCOPILOTLLMCONFIGURATION_TOPP` | [optional] Nucleus sampling probability. | `0.90` | `0.95` | | `CAMUNDA_HUB_COPILOT_DEFAULTBPMNCOPILOTLLMCONFIGURATION_TOPK` | [optional] Top-K sampling (if supported by the model). | `100` | `64` | | `CAMUNDA_HUB_COPILOT_DEFAULTBPMNCOPILOTLLMCONFIGURATION_MAXTOKENS` | [optional] Maximum new tokens per response. | `4096` | `8192` | | `CAMUNDA_HUB_COPILOT_DEFAULTBPMNCOPILOTLLMCONFIGURATION_TIMEOUT` | [optional] Overall request timeout. | `45s` | `60s` | | `CAMUNDA_HUB_COPILOT_DEFAULTBPMNCOPILOTLLMCONFIGURATION_LOGREQUESTS` | [optional] Log raw requests (not recommended in production). | `true` | `false` | | `CAMUNDA_HUB_COPILOT_DEFAULTBPMNCOPILOTLLMCONFIGURATION_LOGRESPONSES` | [optional] Log raw responses (not recommended in production). | `true` | `false` | | `CAMUNDA_HUB_COPILOT_DEFAULTBPMNCOPILOTLLMCONFIGURATION_CONNECTIONACQUISITIONTIMEOUT` | [optional] Connection pool acquisition timeout. | `10s` | `30s` | | `CAMUNDA_HUB_COPILOT_DEFAULTBPMNCOPILOTLLMCONFIGURATION_LOGITBIAS` | [optional] JSON object mapping token IDs to bias values (model-specific). | `{"123":-2,"456":3}` | `{}` | | `CAMUNDA_HUB_COPILOT_DEFAULTBPMNCOPILOTLLMCONFIGURATION_MAXCONNECTIONS` | [optional] Maximum HTTP connections. | `300` | `200` | | `CAMUNDA_HUB_COPILOT_DEFAULTBPMNCOPILOTLLMCONFIGURATION_READTIMEOUT` | [optional] Read timeout per request. | `120s` | `60s` | ### FEEL Copilot | Property | Description | Example value | Default value | | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -------------------- | ------------- | | `camunda.hub.copilot.default-feel-copilot-llm-configuration.temperature` | [optional] Sampling temperature. | `0.2` | `0.3` | | `camunda.hub.copilot.default-feel-copilot-llm-configuration.top-p` | [optional] Nucleus sampling probability. | `0.90` | `0.95` | | `camunda.hub.copilot.default-feel-copilot-llm-configuration.top-k` | [optional] Top-K sampling (if supported by the model). | `100` | `64` | | `camunda.hub.copilot.default-feel-copilot-llm-configuration.max-tokens` | [optional] Maximum new tokens per response. | `4096` | `8192` | | `camunda.hub.copilot.default-feel-copilot-llm-configuration.timeout` | [optional] Overall request timeout. | `45s` | `60s` | | `camunda.hub.copilot.default-feel-copilot-llm-configuration.log-requests` | [optional] Log raw requests (not recommended in production). | `true` | `false` | | `camunda.hub.copilot.default-feel-copilot-llm-configuration.log-responses` | [optional] Log raw responses (not recommended in production). | `true` | `false` | | `camunda.hub.copilot.default-feel-copilot-llm-configuration.connection-acquisition-timeout` | [optional] Connection pool acquisition timeout. | `10s` | `30s` | | `camunda.hub.copilot.default-feel-copilot-llm-configuration.logit-bias` | [optional] JSON object mapping token IDs to bias values (model-specific). | `{"123":-2,"456":3}` | `{}` | | `camunda.hub.copilot.default-feel-copilot-llm-configuration.max-connections` | [optional] Maximum HTTP connections. | `300` | `200` | | `camunda.hub.copilot.default-feel-copilot-llm-configuration.read-timeout` | [optional] Read timeout per request. | `120s` | `60s` | | Environment variable | Description | Example value | Default value | | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -------------------- | ------------- | | `CAMUNDA_HUB_COPILOT_DEFAULTFEELCOPILOTLLMCONFIGURATION_TEMPERATURE` | [optional] Sampling temperature. | `0.2` | `0.3` | | `CAMUNDA_HUB_COPILOT_DEFAULTFEELCOPILOTLLMCONFIGURATION_TOPP` | [optional] Nucleus sampling probability. | `0.90` | `0.95` | | `CAMUNDA_HUB_COPILOT_DEFAULTFEELCOPILOTLLMCONFIGURATION_TOPK` | [optional] Top-K sampling (if supported by the model). | `100` | `64` | | `CAMUNDA_HUB_COPILOT_DEFAULTFEELCOPILOTLLMCONFIGURATION_MAXTOKENS` | [optional] Maximum new tokens per response. | `4096` | `8192` | | `CAMUNDA_HUB_COPILOT_DEFAULTFEELCOPILOTLLMCONFIGURATION_TIMEOUT` | [optional] Overall request timeout. | `45s` | `60s` | | `CAMUNDA_HUB_COPILOT_DEFAULTFEELCOPILOTLLMCONFIGURATION_LOGREQUESTS` | [optional] Log raw requests (not recommended in production). | `true` | `false` | | `CAMUNDA_HUB_COPILOT_DEFAULTFEELCOPILOTLLMCONFIGURATION_LOGRESPONSES` | [optional] Log raw responses (not recommended in production). | `true` | `false` | | `CAMUNDA_HUB_COPILOT_DEFAULTFEELCOPILOTLLMCONFIGURATION_CONNECTIONACQUISITIONTIMEOUT` | [optional] Connection pool acquisition timeout. | `10s` | `30s` | | `CAMUNDA_HUB_COPILOT_DEFAULTFEELCOPILOTLLMCONFIGURATION_LOGITBIAS` | [optional] JSON object mapping token IDs to bias values (model-specific). | `{"123":-2,"456":3}` | `{}` | | `CAMUNDA_HUB_COPILOT_DEFAULTFEELCOPILOTLLMCONFIGURATION_MAXCONNECTIONS` | [optional] Maximum HTTP connections. | `300` | `200` | | `CAMUNDA_HUB_COPILOT_DEFAULTFEELCOPILOTLLMCONFIGURATION_READTIMEOUT` | [optional] Read timeout per request. | `120s` | `60s` | ### Form Copilot | Property | Description | Example value | Default value | | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -------------------- | ------------- | | `camunda.hub.copilot.default-form-copilot-llm-configuration.temperature` | [optional] Sampling temperature. | `0.2` | `0.3` | | `camunda.hub.copilot.default-form-copilot-llm-configuration.top-p` | [optional] Nucleus sampling probability. | `0.90` | `0.95` | | `camunda.hub.copilot.default-form-copilot-llm-configuration.top-k` | [optional] Top-K sampling (if supported by the model). | `100` | `64` | | `camunda.hub.copilot.default-form-copilot-llm-configuration.max-tokens` | [optional] Maximum new tokens per response. | `4096` | `8192` | | `camunda.hub.copilot.default-form-copilot-llm-configuration.timeout` | [optional] Overall request timeout. | `45s` | `60s` | | `camunda.hub.copilot.default-form-copilot-llm-configuration.log-requests` | [optional] Log raw requests (not recommended in production). | `true` | `false` | | `camunda.hub.copilot.default-form-copilot-llm-configuration.log-responses` | [optional] Log raw responses (not recommended in production). | `true` | `false` | | `camunda.hub.copilot.default-form-copilot-llm-configuration.connection-acquisition-timeout` | [optional] Connection pool acquisition timeout. | `10s` | `30s` | | `camunda.hub.copilot.default-form-copilot-llm-configuration.logit-bias` | [optional] JSON object mapping token IDs to bias values (model-specific). | `{"123":-2,"456":3}` | `{}` | | `camunda.hub.copilot.default-form-copilot-llm-configuration.max-connections` | [optional] Maximum HTTP connections. | `300` | `200` | | `camunda.hub.copilot.default-form-copilot-llm-configuration.read-timeout` | [optional] Read timeout per request. | `120s` | `60s` | | Environment variable | Description | Example value | Default value | | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -------------------- | ------------- | | `CAMUNDA_HUB_COPILOT_DEFAULTFORMCOPILOTLLMCONFIGURATION_TEMPERATURE` | [optional] Sampling temperature. | `0.2` | `0.3` | | `CAMUNDA_HUB_COPILOT_DEFAULTFORMCOPILOTLLMCONFIGURATION_TOPP` | [optional] Nucleus sampling probability. | `0.90` | `0.95` | | `CAMUNDA_HUB_COPILOT_DEFAULTFORMCOPILOTLLMCONFIGURATION_TOPK` | [optional] Top-K sampling (if supported by the model). | `100` | `64` | | `CAMUNDA_HUB_COPILOT_DEFAULTFORMCOPILOTLLMCONFIGURATION_MAXTOKENS` | [optional] Maximum new tokens per response. | `4096` | `8192` | | `CAMUNDA_HUB_COPILOT_DEFAULTFORMCOPILOTLLMCONFIGURATION_TIMEOUT` | [optional] Overall request timeout. | `45s` | `60s` | | `CAMUNDA_HUB_COPILOT_DEFAULTFORMCOPILOTLLMCONFIGURATION_LOGREQUESTS` | [optional] Log raw requests (not recommended in production). | `true` | `false` | | `CAMUNDA_HUB_COPILOT_DEFAULTFORMCOPILOTLLMCONFIGURATION_LOGRESPONSES` | [optional] Log raw responses (not recommended in production). | `true` | `false` | | `CAMUNDA_HUB_COPILOT_DEFAULTFORMCOPILOTLLMCONFIGURATION_CONNECTIONACQUISITIONTIMEOUT` | [optional] Connection pool acquisition timeout. | `10s` | `30s` | | `CAMUNDA_HUB_COPILOT_DEFAULTFORMCOPILOTLLMCONFIGURATION_LOGITBIAS` | [optional] JSON object mapping token IDs to bias values (model-specific). | `{"123":-2,"456":3}` | `{}` | | `CAMUNDA_HUB_COPILOT_DEFAULTFORMCOPILOTLLMCONFIGURATION_MAXCONNECTIONS` | [optional] Maximum HTTP connections. | `300` | `200` | | `CAMUNDA_HUB_COPILOT_DEFAULTFORMCOPILOTLLMCONFIGURATION_READTIMEOUT` | [optional] Read timeout per request. | `120s` | `60s` | ### AWS Bedrock :::warning When configuring AWS Bedrock, make sure the model is available in the provided AWS region. ::: | Property | Description | Example value | | --------------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------- | | `camunda.hub.copilot.providers.bedrock.default-model-id` | Default model ID for AWS Bedrock provider. | `anthropic.claude-3-5-sonnet-20240620-v1:0` | | `camunda.hub.copilot.providers.bedrock.region` | AWS region for Bedrock. | `us-east-1` | | `camunda.hub.copilot.providers.bedrock.access-key-id` | AWS access key ID for Bedrock (if not using instance or role credentials). | `AKIA...` | | `camunda.hub.copilot.providers.bedrock.secret-access-key` | AWS secret access key for Bedrock (if not using instance or role credentials). | `wJalrXUtnFEMI/K7MDENG/bPxRfiCY...` | | Environment Variable | Description | Example Value | | ------------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------- | | `CAMUNDA_HUB_COPILOT_PROVIDERS_BEDROCK_DEFAULTMODELID` | Default model ID for AWS Bedrock provider. | `anthropic.claude-3-5-sonnet-20240620-v1:0` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_BEDROCK_REGION` | AWS region for Bedrock. | `us-east-1` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_BEDROCK_ACCESSKEYID` | AWS access key ID for Bedrock (if not using instance or role credentials). | `AKIA...` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_BEDROCK_SECRETACCESSKEY` | AWS secret access key for Bedrock (if not using instance or role credentials). | `wJalrXUtnFEMI/K7MDENG/bPxRfiCY...` | ### OpenAI :::note This configuration applies to OpenAI and OpenAI-compatible providers. ::: Provide exactly one of the following: - An API key for OpenAI's public API (no custom endpoint needed). - A custom endpoint for OpenAI-compatible providers or proxies. For OpenAI-compatible providers, you can authenticate with: - A bearer token. - Username and password (Basic authentication). - Custom authentication headers. When using the Bring your own model option in Self-Managed, results may vary depending on your chosen LLM's capabilities. If a weaker or smaller model is used, it may fail to generate a valid BPMN XML. In such cases, the Copilot library attempts automatic repair up to three times. If those attempts fail, the system will return an empty XML and an optional chat message instead of a model. :::tip Camunda recommends using a stronger model, such as GPT-4 or comparable, for reliable BPMN generation. ::: | Property | Description | Example value | | -------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------ | | `camunda.hub.copilot.providers.open-ai.default-model-id` | Default model ID for OpenAI provider. | `gpt-4.1` | | `camunda.hub.copilot.providers.open-ai.api-key` | [conditionally required] API key for OpenAI public API. | `sk-live-********` | | `camunda.hub.copilot.providers.open-ai.endpoint` | [conditionally required] Custom endpoint for OpenAI-compatible APIs (proxies or self-hosted). | `https://my-proxy.example.com/v1` | | `camunda.hub.copilot.providers.open-ai.bearer` | [optional] Bearer token header to use instead of `api-key` with compatible gateways. | `my-shared-bearer-token` | | `camunda.hub.copilot.providers.open-ai.username` | [optional] Username to authenticate with an OpenAI-compatible gateway. | `api_user` | | `camunda.hub.copilot.providers.open-ai.password` | [optional] Password to authenticate with an OpenAI-compatible gateway. | `s3cr3t` | | `camunda.hub.copilot.providers.open-ai.headers` | [optional] Extra HTTP headers as a JSON map (string). | `{"X-Org":"camunda","X-Trace":"on"}` | | Environment variable | Description | Example value | | ----------------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------ | | `CAMUNDA_HUB_COPILOT_PROVIDERS_OPENAI_DEFAULTMODELID` | Default model ID for OpenAI provider. | `gpt-4.1` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_OPENAI_APIKEY` | [conditionally required] API key for OpenAI public API. | `sk-live-********` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_OPENAI_ENDPOINT` | [conditionally required] Custom endpoint for OpenAI-compatible APIs (proxies or self-hosted). | `https://my-proxy.example.com/v1` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_OPENAI_BEARER` | [optional] Bearer token header to use instead of `api-key` with compatible gateways. | `my-shared-bearer-token` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_OPENAI_USERNAME` | [optional] Username to authenticate with an OpenAI-compatible gateway. | `api_user` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_OPENAI_PASSWORD` | [optional] Password to authenticate with an OpenAI-compatible gateway. | `s3cr3t` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_OPENAI_HEADERS` | [optional] Extra HTTP headers as a JSON map (string). | `{"X-Org":"camunda","X-Trace":"on"}` | ### Azure OpenAI | Property | Description | Example value | | -------------------------------------------------------------- | ------------------------------------------------- | ---------------------------------- | | `camunda.hub.copilot.providers.azure-open-ai.default-model-id` | Default model (deployment name) for Azure OpenAI. | `gpt-4o` | | `camunda.hub.copilot.providers.azure-open-ai.api-key` | Azure OpenAI API key. | `az-aoai-key-***` | | `camunda.hub.copilot.providers.azure-open-ai.endpoint` | Azure OpenAI endpoint. | `https://my-aoai.openai.azure.com` | | Environment variable | Description | Example value | | ---------------------------------------------------------- | ------------------------------------------------- | ---------------------------------- | | `CAMUNDA_HUB_COPILOT_PROVIDERS_AZUREOPENAI_DEFAULTMODELID` | Default model (deployment name) for Azure OpenAI. | `gpt-4o` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_AZUREOPENAI_APIKEY` | Azure OpenAI API key. | `az-aoai-key-***` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_AZUREOPENAI_ENDPOINT` | Azure OpenAI endpoint. | `https://my-aoai.openai.azure.com` | ### Azure AI :::note Azure AI supports authentication with an API key or Microsoft Entra ID (formerly Azure AD) using the OAuth 2.0 client credentials flow. ::: | Property | Description | Example value | | --------------------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | | `camunda.hub.copilot.providers.azure-ai.default-model-id` | Default model for Azure AI (Inference). | `gpt-4o-mini` | | `camunda.hub.copilot.providers.azure-ai.endpoint` | Endpoint for Azure AI (Inference). Use the endpoint from `Azure AI Inference SDK`. | `https://********-resource.cognitiveservices.azure.com/openai/deployments/gpt-4o` | | `camunda.hub.copilot.providers.azure-ai.api-key` | [conditionally required] API key for Azure AI (alternative to OAuth credentials). | `az-ai-key-***` | | `camunda.hub.copilot.providers.azure-ai.client-id` | [conditionally required] Azure AI OAuth client ID. | `00000000-0000-0000-0000-000000000000` | | `camunda.hub.copilot.providers.azure-ai.client-secret` | [conditionally required] Azure AI OAuth client secret. | `***` | | `camunda.hub.copilot.providers.azure-ai.tenant-id` | [conditionally required] Azure AD tenant ID for OAuth. | `11111111-2222-3333-4444-555555555555` | | `camunda.hub.copilot.providers.azure-ai.authority-host` | [conditionally required] Authority host for Azure OAuth. | `https://login.microsoftonline.com` | | Environment variable | Description | Example value | | ------------------------------------------------------ | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | | `CAMUNDA_HUB_COPILOT_PROVIDERS_AZUREAI_DEFAULTMODELID` | Default model for Azure AI (Inference). | `gpt-4o-mini` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_AZUREAI_ENDPOINT` | Endpoint for Azure AI (Inference). Use the endpoint from `Azure AI Inference SDK`. | `https://********-resource.cognitiveservices.azure.com/openai/deployments/gpt-4o` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_AZUREAI_APIKEY` | [conditionally required] API key for Azure AI (alternative to OAuth credentials). | `az-ai-key-***` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_AZUREAI_CLIENTID` | [conditionally required] Azure AI OAuth client ID. | `00000000-0000-0000-0000-000000000000` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_AZUREAI_CLIENTSECRET` | [conditionally required] Azure AI OAuth client secret. | `***` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_AZUREAI_TENANTID` | [conditionally required] Azure AD tenant ID for OAuth. | `11111111-2222-3333-4444-555555555555` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_AZUREAI_AUTHORITYHOST` | [conditionally required] Authority host for Azure OAuth. | `https://login.microsoftonline.com` | ### Google Vertex AI | Property | Description | Example value | | ---------------------------------------------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------- | | `camunda.hub.copilot.providers.vertex-ai.default-model-id` | Default model ID for Google Vertex AI (Gemini). | `gemini-1.5-pro-002` | | `camunda.hub.copilot.providers.vertex-ai.project-id` | GCP project ID for Vertex AI. | `my-gcp-project` | | `camunda.hub.copilot.providers.vertex-ai.location` | Vertex AI location or region. | `us-central1` | | `camunda.hub.copilot.providers.vertex-ai.credentials-json` | Vertex AI service account JSON (string). | `{"type":"service_account","project_id":"my-proj","client_email":"...","private_key":"..."}` | | Environment variable | Description | Example value | | -------------------------------------------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------- | | `CAMUNDA_HUB_COPILOT_PROVIDERS_VERTEXAI_DEFAULTMODELID` | Default model ID for Google Vertex AI (Gemini). | `gemini-1.5-pro-002` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_VERTEXAI_PROJECTID` | GCP project ID for Vertex AI. | `my-gcp-project` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_VERTEXAI_LOCATION` | Vertex AI location or region. | `us-central1` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_VERTEXAI_CREDENTIALSJSON` | Vertex AI service account JSON (string). | `{"type":"service_account","project_id":"my-proj","client_email":"...","private_key":"..."}` | ### Anthropic | Property | Description | Example value | Default value | | --------------------------------------------------------------- | ------------------------------------------------------------------------ | ---------------------------- | ------------- | | `camunda.hub.copilot.providers.anthropic.default-model-id` | Default model ID for Anthropic. | `claude-3-5-sonnet-20240620` | - | | `camunda.hub.copilot.providers.anthropic.api-key` | Anthropic API key. | `sk-ant-***` | - | | `camunda.hub.copilot.providers.anthropic.cache-system-messages` | [optional] Enable client-side caching of system messages (if supported). | `false` | `true` | | `camunda.hub.copilot.providers.anthropic.cache-tools` | [optional] Enable client-side caching of tool schemas (if supported). | `false` | `true` | | Environment variable | Description | Example value | Default value | | ------------------------------------------------------------- | ------------------------------------------------------------------------ | ---------------------------- | ------------- | | `CAMUNDA_HUB_COPILOT_PROVIDERS_ANTHROPIC_DEFAULTMODELID` | Default model ID for Anthropic. | `claude-3-5-sonnet-20240620` | - | | `CAMUNDA_HUB_COPILOT_PROVIDERS_ANTHROPIC_APIKEY` | Anthropic API key. | `sk-ant-***` | - | | `CAMUNDA_HUB_COPILOT_PROVIDERS_ANTHROPIC_CACHESYSTEMMESSAGES` | [optional] Enable client-side caching of system messages (if supported). | `false` | `true` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_ANTHROPIC_CACHETOOLS` | [optional] Enable client-side caching of tool schemas (if supported). | `false` | `true` | ### Ollama | Property | Description | Example value | | ------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------ | | `camunda.hub.copilot.providers.ollama.default-model-id` | Default model ID for Ollama. | `llama3.1` | | `camunda.hub.copilot.providers.ollama.base-url` | Ollama server base URL. | `http://localhost:11434` | | `camunda.hub.copilot.providers.ollama.headers` | [optional] Extra HTTP headers to send as a JSON map (string). | `{"X-Org":"camunda","X-Trace":"on"}` | | Environment variable | Description | Example value | | ----------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------ | | `CAMUNDA_HUB_COPILOT_PROVIDERS_OLLAMA_DEFAULTMODELID` | Default model ID for Ollama. | `llama3.1` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_OLLAMA_BASEURL` | Ollama server base URL. | `http://localhost:11434` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_OLLAMA_HEADERS` | [optional] Extra HTTP headers to send as a JSON map (string). | `{"X-Org":"camunda","X-Trace":"on"}` | ### Hugging Face | Property | Description | Example value | Default value | | ------------------------------------------------------------- | ------------------------------------------------------------------------ | --------------------------------------------- | ------------- | | `camunda.hub.copilot.providers.hugging-face.default-model-id` | Default model ID for Hugging Face Inference. | `mistralai/Mixtral-8x7B-Instruct-v0.1` | - | | `camunda.hub.copilot.providers.hugging-face.base-url` | Base URL for Hugging Face Inference endpoint (if self-hosted or custom). | `https://api-inference.huggingface.co/models` | - | | `camunda.hub.copilot.providers.hugging-face.access-token` | Access token for Hugging Face. | `hf_***` | - | | `camunda.hub.copilot.providers.hugging-face.wait-for-model` | [optional] Wait for model to warm up before responding. | `false` | `true` | | `camunda.hub.copilot.providers.hugging-face.return-full-text` | [optional] Return the full generated text (not just the completion). | `true` | `true` | | Environment variable | Description | Example value | Default value | | ---------------------------------------------------------- | ------------------------------------------------------------------------ | --------------------------------------------- | ------------- | | `CAMUNDA_HUB_COPILOT_PROVIDERS_HUGGINGFACE_DEFAULTMODELID` | Default model ID for Hugging Face Inference. | `mistralai/Mixtral-8x7B-Instruct-v0.1` | - | | `CAMUNDA_HUB_COPILOT_PROVIDERS_HUGGINGFACE_BASEURL` | Base URL for Hugging Face Inference endpoint (if self-hosted or custom). | `https://api-inference.huggingface.co/models` | - | | `CAMUNDA_HUB_COPILOT_PROVIDERS_HUGGINGFACE_ACCESSTOKEN` | Access token for Hugging Face. | `hf_***` | - | | `CAMUNDA_HUB_COPILOT_PROVIDERS_HUGGINGFACE_WAITFORMODEL` | [optional] Wait for model to warm up before responding. | `false` | `true` | | `CAMUNDA_HUB_COPILOT_PROVIDERS_HUGGINGFACE_RETURNFULLTEXT` | [optional] Return the full generated text (not just the completion). | `true` | `true` | --- ## Database(Configuration) This page describes advanced database connection configuration for Camunda Hub. For a general setup guide, visit the [configuration overview](properties.md#database). :::tip Need end-to-end guidance? For a unified setup guide covering provisioning, topology decisions, driver management, and backup strategies across both Orchestration Cluster and Camunda Hub, see the [end-to-end RDBMS setup guide](/self-managed/concepts/databases/relational-db/rdbms-setup-guide.md). This guide is useful both when starting a new setup and when harmonizing existing component configurations. ::: Camunda Hub supports multiple database vendors such as PostgreSQL, MySQL, MariaDB, and Microsoft SQL Server. You can choose the one that best fits your environment. | Database | Default driver included | Notes | | ---------- | ----------------------- | ---------------------------------------------------------------------- | | PostgreSQL | ✅ Yes | | | H2 | ✅ Yes | For development, testing, or evaluation only. | | MariaDB | ✅ Yes | Must use a case-sensitive collation. | | MySQL | ❌ No | Driver must be provided manually; must use a case-sensitive collation. | | MSSQL | ✅ Yes | Must use a case-sensitive collation. | | Oracle | ❌ No | Driver must be provided manually. | ## Configuring SSL for the database connection To configure SSL between Camunda Hub and the database: - Modify the JDBC URL using `SPRING_DATASOURCE_URL` and add connection parameters. - Provide SSL certificates and keys to the `restapi` component, if required. Consult the [PostgreSQL documentation](https://jdbc.postgresql.org/documentation/ssl/) for details on SSL modes and their security guarantees. For a full list of available connection parameters, see the [PostgreSQL connection parameters reference](https://jdbc.postgresql.org/documentation/use/#connection-parameters/). Below are examples for common SSL configurations, ordered by increasing security. ### SSL mode `require` An SSL connection is established, but this mode remains vulnerable to person-in-the-middle attacks. Modify the JDBC URL as follows: ```bash jdbc:postgresql://[DB_HOST]:[DB_PORT]/[DB_NAME]?sslmode=require ``` No certificates are required for this mode. ### SSL mode `verify-full` Camunda Hub verifies the server’s identity by checking its certificate. This mode prevents person-in-the-middle attacks. 1. Provide the root certificate that signed the server certificate: `myCA.crt -> ~/.postgresql/root.crt` 2. Modify the JDBC URL: ```bash jdbc:postgresql://[DB_HOST]:[DB_PORT]/[DB_NAME]?sslmode=verify-full ``` ### SSL mode `verify-full` with client certificates In this mode, both the server and Camunda Hub authenticate each other using certificates. 1. Mount client certificates: - `myClientCertificate.pk8 -> ~/.postgresql/postgresql.pk8` - `myClientCertificate.crt -> ~/.postgresql/postgresql.crt` 2. Provide the root certificate: `myCA.crt -> ~/.postgresql/root.crt` 3. Modify the JDBC URL: ```bash jdbc:postgresql://[DB_HOST]:[DB_PORT]/[DB_NAME]?sslmode=verify-full ``` 4. Configure the database server to verify client certificates. See the [PostgreSQL SSL documentation](https://www.postgresql.org/docs/current/ssl-tcp.html). ## Running Camunda Hub on Amazon Aurora PostgreSQL Camunda Hub supports connecting to **Amazon Aurora PostgreSQL**. To connect, update the following environment variables: 1. Set the JDBC URL: ```bash SPRING_DATASOURCE_URL="jdbc:aws-wrapper:postgresql://[DB_HOST]:[DB_PORT]/[DB_NAME]" ``` 2. Set the driver class: ```bash SPRING_DATASOURCE_DRIVERCLASSNAME="software.amazon.jdbc.Driver" ``` For all available driver parameters, see the [AWS Advanced JDBC Driver documentation](https://github.com/awslabs/aws-advanced-jdbc-wrapper/wiki/UsingTheJdbcDriver#aws-advanced-jdbc-driver-parameters). ### AWS IAM authentication To enable IAM database authentication for Aurora PostgreSQL: 1. Modify the JDBC URL: ```bash SPRING_DATASOURCE_URL="jdbc:aws-wrapper:postgresql://[DB_HOST]:[DB_PORT]/[DB_NAME]?wrapperPlugins=iam" ``` 2. Set the username: ```bash SPRING_DATASOURCE_USERNAME="[IAM_DB_USER]" ``` The username must match a database user configured for IAM authentication as described in the [Amazon Aurora documentation](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/UsingWithRDS.IAMDBAuth.DBAccounts.html#UsingWithRDS.IAMDBAuth.DBAccounts.PostgreSQL). 3. Remove the password variable: ```bash unset SPRING_DATASOURCE_PASSWORD ``` When using IAM authentication, ensure Camunda Hub has permission to generate IAM authentication tokens (for example, through an attached IAM role or access key). ## Using alternative database vendors ### H2 The H2 driver is included by default. To use a different driver, set `SPRING_DATASOURCE_DRIVERCLASSNAME` to the fully qualified class name of your driver. Otherwise, omit this variable. ```sh SPRING_DATASOURCE_URL="jdbc:h2:mem:[DB_NAME]" # See https://www.h2database.com/html/features.html SPRING_DATASOURCE_USERNAME="[DB_USER]" SPRING_DATASOURCE_PASSWORD="[DB_PASSWORD]" SPRING_DATASOURCE_DRIVERCLASSNAME="[YOUR_CUSTOM_DRIVER]" # Optional ``` ```yaml camundaHub: restapi: externalDatabase: url: "jdbc:h2:mem:[DB_NAME]" username: "[DB_USER]" secret: inlineSecret: "[DB_PASSWORD]" env: - name: SPRING_DATASOURCE_DRIVERCLASSNAME value: "[YOUR_CUSTOM_DRIVER]" ``` ```yaml spring: datasource: url: jdbc:h2:mem:[DB_NAME] username: [DB_USER] password: [DB_PASSWORD] driver-class-name: [YOUR_CUSTOM_DRIVER] # Optional ``` H2 is intended for local development or testing only, not for production environments. #### Custom schema By default, H2 uses the `PUBLIC` schema. To use a custom schema, add an initialization command to the JDBC URL: ```yaml jdbc:h2:mem:[DB_NAME];INIT=CREATE SCHEMA IF NOT EXISTS [CUSTOM_SCHEMA]\;SET SCHEMA [CUSTOM_SCHEMA] ``` ### MariaDB The MariaDB driver is provided by default, so no additional steps are necessary to provide the driver. To use a custom database driver, set `SPRING_DATASOURCE_DRIVERCLASSNAME` to the fully qualified class name of your driver. Otherwise, omit this variable. ```sh SPRING_DATASOURCE_URL="jdbc:mariadb://[DB_HOST]:[DB_PORT]/[DB_NAME]" SPRING_DATASOURCE_USERNAME="[DB_USER]" SPRING_DATASOURCE_PASSWORD="[DB_PASSWORD]" SPRING_DATASOURCE_DRIVERCLASSNAME="[YOUR_CUSTOM_DRIVER]" # Optional; omit to use default MariaDB driver ``` ```yaml camundaHub: restapi: externalDatabase: url: "jdbc:mariadb://[DB_HOST]:[DB_PORT]/[DB_NAME]" username: "[DB_USER]" secret: inlineSecret: "[DB_PASSWORD]" env: - name: SPRING_DATASOURCE_DRIVERCLASSNAME value: "[YOUR_CUSTOM_DRIVER]" ``` ```yaml spring: datasource: url: jdbc:mariadb://[DB_HOST]:[DB_PORT]/[DB_NAME] username: [DB_USER] password: [DB_PASSWORD] driver-class-name: [YOUR_CUSTOM_DRIVER] # Optional ``` #### Case sensitivity MariaDB uses case-insensitive collations by default. To enable case sensitivity, set the database collation to a case-sensitive one such as `utf8mb4_bin`. :::note If a case-insensitive collation is used, you may encounter unexpected behavior. For example, in [IDP extraction](/components/hub/workspace/modeler/idp/idp-unstructured-extraction.md#extract-fields), a field named `amount` and another named `Amount` would be treated as identical because the database does not distinguish between them. ::: ### MSSQL The MSSQL driver is provided by default, so no additional steps are required. To use a custom database driver, set `SPRING_DATASOURCE_DRIVERCLASSNAME` to the fully qualified class name of your driver. Otherwise, omit this variable. ```sh SPRING_DATASOURCE_URL="jdbc:sqlserver://[DB_HOST]:[DB_PORT];databaseName=[DB_NAME]" SPRING_DATASOURCE_USERNAME="[DB_USER]" SPRING_DATASOURCE_PASSWORD="[DB_PASSWORD]" SPRING_DATASOURCE_DRIVERCLASSNAME="[YOUR_CUSTOM_DRIVER]" # Optional; omit to use default MSSQL driver ``` ```yaml camundaHub: restapi: externalDatabase: url: "jdbc:sqlserver://[DB_HOST]:[DB_PORT];databaseName=[DB_NAME]" username: "[DB_USER]" secret: inlineSecret: "[DB_PASSWORD]" env: - name: SPRING_DATASOURCE_DRIVERCLASSNAME # Optional; omit to use default MSSQL driver value: "[YOUR_CUSTOM_DRIVER]" ``` ```yaml spring: datasource: url: jdbc:sqlserver://[DB_HOST]:[DB_PORT];databaseName=[DB_NAME] username: [DB_USER] password: [DB_PASSWORD] driver-class-name: [YOUR_CUSTOM_DRIVER] # Optional; omit to use default MSSQL driver ``` #### Case sensitivity MSSQL is case-insensitive by default. To enable case sensitivity, set the database collation to a case-sensitive one such as `Latin1_General_CS_AS`. Otherwise, you may encounter unexpected behavior. The only current restriction is that extraction fields in [IDP extraction](../../../../components/hub/workspace/modeler/idp/idp-unstructured-extraction.md#extract-fields) will not be case-sensitive. This means that if you have a field named `amount`, you cannot create another field named `Amount`, because the database treats them as the same identifier. #### Custom schema MSSQL supports custom schemas, but this is not configurable within Camunda Hub. To use a custom schema, set the database user’s **default schema**. ### MySQL The MySQL driver is **not provided by default** in Camunda 8 distributions. You must download and provide it manually for the application to load. 1. Download the appropriate (platform-independent) MySQL driver: [https://dev.mysql.com/downloads/connector/j/](https://dev.mysql.com/downloads/connector/j/) 2. If you are using Docker or Kubernetes, ensure that the folder with the library is properly mounted as a volume at this location: `/driver-lib`. It will be automatically loaded by the application. To use a custom database driver, set `SPRING_DATASOURCE_DRIVERCLASSNAME` to the fully qualified class name of your driver. Otherwise, omit this variable. ```sh SPRING_DATASOURCE_URL="jdbc:mysql://[DB_HOST]:[DB_PORT]/[DB_NAME]" SPRING_DATASOURCE_USERNAME="[DB_USER]" SPRING_DATASOURCE_PASSWORD="[DB_PASSWORD]" SPRING_DATASOURCE_DRIVERCLASSNAME="[YOUR_CUSTOM_DRIVER]" # Optional; omit to use default MySQL driver ``` ```yaml camundaHub: restapi: externalDatabase: url: "jdbc:mysql://[DB_HOST]:[DB_PORT]/[DB_NAME]" username: "[DB_USER]" secret: inlineSecret: "[DB_PASSWORD]" env: - name: SPRING_DATASOURCE_DRIVERCLASSNAME # Optional; omit to use default MySQL driver value: "[YOUR_CUSTOM_DRIVER]" extraVolumeMounts: - name: mysql-driver mountPath: /driver-lib extraVolumes: - name: mysql-driver emptyDir: {} initContainers: - name: fetch-jdbc-drivers image: alpine:3.22.1 imagePullPolicy: "Always" command: [ "sh", "-c", "wget https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-j-9.7.0.tar.gz -O /driver-lib/mysql.tar.gz && tar -xzf /driver-lib/mysql.tar.gz -C /driver-lib --strip-components=1", ] volumeMounts: - name: mysql-driver mountPath: /driver-lib securityContext: runAsUser: 1001 ``` ```yaml spring: datasource: url: jdbc:mysql://[DB_HOST]:[DB_PORT]/[DB_NAME] username: [DB_USER] password: [DB_PASSWORD] driver-class-name: [YOUR_CUSTOM_DRIVER] # Optional; omit to use default MySQL driver ``` #### Case sensitivity MySQL usually uses **case-insensitive** collations by default. To enable case sensitivity, set the database collation to a case-sensitive one such as `utf8mb4_0900_as_cs`. Otherwise, you may encounter unexpected behavior. The only current restriction is that extraction fields in [IDP extraction](../../../../components/hub/workspace/modeler/idp/idp-unstructured-extraction.md#extract-fields) will not be case-sensitive. This means that if you have a field named `amount`, you cannot create another field named `Amount`, because the database treats them as the same identifier. ### Oracle The Oracle driver is **not provided by default** in Camunda 8 distributions. You must download and provide it manually for the application to load. 1. Download the appropriate Oracle driver: [https://www.oracle.com/database/technologies/appdev/jdbc-downloads.html](https://www.oracle.com/database/technologies/appdev/jdbc-downloads.html) 2. If you are using Docker or Kubernetes, ensure that the folder with the library is properly mounted as a volume at this location: `/driver-lib`. It will be automatically loaded by the application. To use a custom database driver, set `SPRING_DATASOURCE_DRIVERCLASSNAME` to the fully qualified class name of your driver. Otherwise, omit this variable. ```sh SPRING_DATASOURCE_URL="jdbc:oracle:thin:@//[DB_HOST]:[DB_PORT]/[DB_NAME]" SPRING_DATASOURCE_USERNAME="[DB_USER]" SPRING_DATASOURCE_PASSWORD="[DB_PASSWORD]" SPRING_DATASOURCE_DRIVERCLASSNAME="[YOUR_CUSTOM_DRIVER]" # Optional; omit to use default Oracle driver ``` ```yaml camundaHub: restapi: externalDatabase: url: "jdbc:oracle:thin:@//[DB_HOST]:[DB_PORT]/[DB_NAME]" username: "[DB_USER]" secret: inlineSecret: "[DB_PASSWORD]" env: - name: SPRING_DATASOURCE_DRIVERCLASSNAME # Optional; omit to use default Oracle driver value: "[YOUR_CUSTOM_DRIVER]" extraVolumeMounts: - name: oracle-driver mountPath: /driver-lib extraVolumes: - name: oracle-driver emptyDir: {} initContainers: - name: fetch-jdbc-drivers image: alpine:3.22.1 imagePullPolicy: "Always" command: [ "sh", "-c", "wget https://download.oracle.com/otn-pub/otn_software/jdbc/237/ojdbc17.jar -O /driver-lib/ojdbc.jar", ] volumeMounts: - name: oracle-driver mountPath: /driver-lib securityContext: runAsUser: 1001 ``` ```yaml spring: datasource: url: jdbc:oracle:thin:@//[DB_HOST]:[DB_PORT]/[DB_NAME] username: [DB_USER] password: [DB_PASSWORD] driver-class-name: [YOUR_CUSTOM_DRIVER] # Optional; omit to use default Oracle driver ``` --- ## Environments and Physical Tenants Camunda Hub creates the [environments](/components/concepts/environments.md) of your organization from the clusters in its configuration. An environment appears only if its cluster is in your configuration. For how an environment maps to a cluster or to a Physical Tenant, and how Camunda Hub names it, see [how an environment maps to infrastructure](/components/concepts/environments.md#how-an-environment-maps-to-infrastructure). Camunda Hub reads the cluster configuration once at startup on every instance, so after you change it, perform a rolling restart. ## Physical Tenants Declare the [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md) of your clusters with `physical-tenants` in the cluster configuration. Camunda Hub surfaces each declared Physical Tenant, and the `default` Physical Tenant of every cluster, as an environment. If you declare `physical-tenants` on a cluster earlier than 8.10, Camunda Hub ignores them and logs a warning. If you deploy with the Helm chart, see [Physical Tenants in the Helm topology](/self-managed/deployment/helm/install/topology/physical-tenants.md) for how the chart maps them. Physical Tenants require OIDC authentication. They don't support Basic authentication. ### Declare Physical Tenants Declare each additional Physical Tenant of a cluster with `physical-tenants`. See [how an environment maps to infrastructure](/components/concepts/environments.md#how-an-environment-maps-to-infrastructure) for how Camunda Hub names the environment of a tenant. | Property | Description | Required | | :------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- | :------- | | `camunda.hub.clusters[0].physical-tenants[0].id` | The ID of the Physical Tenant. Camunda Hub shows it as the environment name. | Yes | | `camunda.hub.clusters[0].physical-tenants[0].name` | Reserved. Camunda Hub accepts the key, but doesn't use it yet. The name of the environment is the ID of the Physical Tenant. | No | | `camunda.hub.clusters[0].physical-tenants[0].components` | The [components](properties.md#components) that differ from the cluster for this tenant. Each component needs a `type` and a `version`. | No | Example configuration: ```yaml camunda: hub: clusters: - id: camunda-platform # other fields... physical-tenants: - id: payments - id: lending ``` Each Physical Tenant of a cluster shows the same [tags](properties.md#clusters) as the cluster. The tenant inherits the web application addresses of the cluster, and Camunda Hub adds the `/physical-tenants/` path for the tenants other than `default`. ### Override components for a Physical Tenant Use `components` on a Physical Tenant to point it at its own component instances. This is a partial override: - Only the component types you list are replaced for the tenant. A listed component replaces the cluster entry completely, so set every address the tenant needs, such as `urls.webapp` and `urls.readiness`. - Every other component keeps using the configuration of the cluster, and follows later changes to it. - A tenant other than `default` never inherits Optimize from the cluster, because Optimize needs its own instance for each Physical Tenant. Add an `optimize` component to the tenant to show Optimize. - If you remove the override and restart Camunda Hub, the component uses the configuration of the cluster again. Example configuration that overrides only Optimize for the `payments` tenant. The other components still come from the cluster: ```yaml camunda: hub: clusters: - id: camunda-platform # other fields... physical-tenants: - id: payments components: - type: optimize version: 8.10.0 urls: webapp: https://optimize-payments.example.com readiness: https://optimize-payments.example.com/api/readyz ``` If a cluster earlier than 8.10 declares `components` on a tenant, Camunda Hub fails to start with the message `must not declare physical tenant 'components' if 'version' is lower than the minimum physical tenant version`. ## Environment status Camunda Hub sends an HTTP request to the `urls.readiness` address of each component of an environment to determine its status. The status of the environment is the worst result of its components. From worst to best, the order is **Unhealthy**, **Unknown**, **Healthy**. One **Unhealthy** component makes the environment **Unhealthy**, even if other components are **Unknown**. An environment is **Healthy** only if all of its components are Healthy. | Component response | Status | | :------------------------------------------------------------------------------------------------------ | :-------- | | A successful response, with no body or with a `status` of `up` or `ready` | Healthy | | An error response, or any other `status` | Unhealthy | | No `readiness` address, no response within five seconds, a redirect, or a body without a `status` field | Unknown | A cluster that you configure with `url` instead of `components` has no readiness address, so its environments always have the status **Unknown**. With the Helm chart, the `readiness` address of the Optimize component of a Physical Tenant is set only if you set `readinessUrl` for that component. Without it, the component has no readiness address, and the environment of the tenant has the status **Unknown**. ## Not reported environments If you remove a cluster or Physical Tenant from the configuration, but its environment is still assigned to a workspace, the environment stays in Camunda Hub with the status **Not reported**. It shows no live data, and you can't select it for a deployment. Remove the assignment from the workspace when you no longer need it. --- ## Authentication(Configuration) Camunda Hub authenticates users with OpenID Connect (OIDC). ## Authentication and user management Camunda Hub authenticates users with its own properties (see [Identity / Keycloak](./properties.md#identity--keycloak)), while Management Identity keeps managing users and their access. For how the responsibilities are split, see [management and modeling component authentication](/self-managed/concepts/authentication/authentication-to-management-components.md#authentication-and-user-management). Management Identity is still required for Camunda Hub in 8.10. For more information, see [manage access and permissions](/self-managed/components/management-identity/access-management/access-management-overview.md). ## Configure OIDC authentication Configure Camunda Hub's OIDC authentication with the properties documented under [Identity / Keycloak](./properties.md#identity--keycloak), not with the Orchestration Cluster's `camunda.security.authentication.oidc.*` settings. For the one exception, the user ID claim, see [change the user ID claim](#change-the-user-id-claim). ## Change the user ID claim Camunda Hub identifies users by the `sub` claim of the access token. To use a different claim, for example `oid` in Microsoft Entra ID, set `camunda.security.authentication.oidc.username-claim`. Despite its name, `username-claim` defines how Camunda Hub identifies users, not the name it displays. Choose a claim that: - Never changes for a user, like `sub` or `oid`. If the value changes, for example when a user's email address changes, the user gets a new, empty account. - Is present in every user access token and contains a non-empty string of at most 255 characters. Otherwise, the user can't log in. With Helm, use [`camundaHub.restapi.extraConfiguration`](/self-managed/deployment/helm/configure/application-configs.md#componentnameextraconfiguration): ```yaml camundaHub: restapi: extraConfiguration: - file: user-id-claim.yaml content: | camunda: security: authentication: oidc: username-claim: oid ``` If you change the user ID claim for an existing installation, users keep their account. On their next login, Camunda Hub moves accounts stored under `sub` to the new claim. :::warning Moving accounts is a one-way operation. If you change the claim again, Camunda Hub doesn't move accounts back, and affected users get a new, empty account. ::: ## Use a different OIDC provider than Keycloak For deployments that include Keycloak, Camunda Hub uses it as its identity provider by default. For installation methods that start Keycloak, see [About Management Identity](/self-managed/components/management-identity/overview.md#about-management-identity). To use a different OIDC provider, follow the steps in the [OIDC connection guide](/self-managed/components/management-identity/configuration/connect-to-an-oidc-provider.md). --- ## Legacy cluster configurations :::note DEPRECATED This method of configuring clusters is deprecated and will be removed in a later version of Camunda Hub Self-Managed. See the [migration guide](/self-managed/upgrade/components/890-to-8100.md#camunda-hub) for more details. ::: Clusters must be configured using the following options to access the cluster from within Camunda Hub. If no clusters are configured, you will not be able to perform any actions that require a cluster (for example, deploy, start an instance, or Play a process). The Camunda 8 [Helm](/self-managed/deployment/helm/install/quick-install.md) and [Docker Compose](/self-managed/quickstart/developer-quickstart/docker-compose.md) distributions provide a local Zeebe cluster configured by default. To add additional clusters, increment the `0` value for each entry (for example `clusters[1]` or `CAMUNDA_HUB_CLUSTERS_1_NAME`). :::info Cluster version The available configuration options depend on the version of the cluster: - [Common configuration (all cluster versions)](#common-configuration-all-cluster-versions) - [Additional configuration for cluster versions >= 8.8](#additional-configuration-for-cluster-versions--88) ::: #### Common configuration (all cluster versions) | Environment variable | Description | Example value | | --------------------------------------- | ------------------------------------------------------------------------------------------------ | ---------------- | | `CAMUNDA_HUB_CLUSTERS_0_ID` | A unique identifier to use for your cluster. | `test-cluster-1` | | `CAMUNDA_HUB_CLUSTERS_0_NAME` | The name of your cluster. | `Test Cluster 1` | | `CAMUNDA_HUB_CLUSTERS_0_VERSION` | The Camunda version used by this cluster. | `8.8.0` | | `CAMUNDA_HUB_CLUSTERS_0_AUTHENTICATION` | The [authentication](./properties.md#available-authentication-methods) to use with your cluster. | `BEARER_TOKEN` | ```yaml camunda.hub.clusters: - id: test-cluster-1 name: Test Cluster 1 version: 8.8.0 authentication: BEARER_TOKEN ``` #### Additional configuration for cluster versions >= 8.8 | Environment variable | Description | Example value | | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | | `CAMUNDA_HUB_CLUSTERS_0_URL_GRPC` | [Internal or external](./properties.md#notes-on-host-names-and-port-numbers) address where the [Zeebe gRPC API](/apis-tools/zeebe-api/grpc.md) can be reached. Optional. If you omit it, Camunda Hub uses the REST API for deployments, instance starts, and job workers, and job streaming is disabled. | `grpc://camunda:26500`,`grpcs://camunda.example.com:26500` | | `CAMUNDA_HUB_CLUSTERS_0_URL_REST` | [Internal or external](./properties.md#notes-on-host-names-and-port-numbers) address where the cluster's REST APIs can be reached. Used as the base URL for requests to the [Orchestration Cluster API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). Required. | `http://camunda:8080`,`https://camunda.example.com` | | `CAMUNDA_HUB_CLUSTERS_0_URL_WEBAPP` | [External](./properties.md#notes-on-host-names-and-port-numbers) address where the cluster's web applications can be reached in a browser. | `https://camunda.example.com` | | `CAMUNDA_HUB_CLUSTERS_0_AUTHORIZATIONS_ENABLED` | Indicates if [authorizations are enabled](/self-managed/components/orchestration-cluster/admin/overview.md#enable-api-authentication-and-authorizations) for the cluster. If `true`, users will see a hint when they deploy from Camunda Hub. | `true` | ```yaml camunda.hub.clusters: - # ...common configuration from above url: grpc: "grpc://camunda:26500" # or grpcs://camunda.example.com:26500 rest: "http://camunda:8080" # or https://camunda.example.com webapp: "https://camunda.example.com" authorizations: enabled: true ``` :::note Cluster communication protocol The REST URL is required for clusters version 8.8 and later. The gRPC URL is optional. - If you configure both URLs, Camunda Hub uses gRPC to interact with the cluster. To use REST instead, set `camunda.client.gateway.preferRestOverGrpc=true` (environment variable `CAMUNDA_CLIENT_GATEWAY_PREFERRESTOVERGRPC`). - If you configure only the REST URL, Camunda Hub uses REST to interact with the cluster. Job streaming is disabled because it is only available over gRPC. Use this setup if the network between Camunda Hub and the cluster only allows HTTP(S) traffic, for example when port `26500` is blocked. - If you configure only the gRPC URL, or neither URL, startup validation fails. ::: --- ## Logging(Configuration) ## Logging configuration for the `restapi` component Camunda Hub's `restapi` component uses [Apache Log4j 2](https://logging.apache.org/log4j/2.x/) for logging. By default, the `restapi` component logs to the Docker container's standard output. To change the default logging behavior, create a custom configuration file and let the `restapi` know of it by specifying the following environment variable: ```properties LOGGING_CONFIG=file:/full/path/to/custom-log4j2-spring.xml ``` Refer to [Spring Boot's logging documentation](https://docs.spring.io/spring-boot/how-to/logging.html#howto.logging.log4j) for more information on how to customize the `log4j2` configuration for specific use cases, such as logging to a file. Enabling `DEBUG` logging for the `restapi` component can be useful for troubleshooting purposes, e.g. for [debugging Zeebe connection issues](../troubleshooting/troubleshoot-zeebe-connection.md#how-can-i-debug-log-grpc--zeebe-communication). By default, Camunda Hub's `restapi` component logs in a simple, readable format to the console. You can configure log levels, output formats, and appenders, and adjust logging dynamically at runtime. ### Changing log level at runtime You can adjust log levels dynamically using Spring Boot Actuator [`loggers`](https://docs.spring.io/spring-boot/api/rest/actuator/loggers.html) endpoints: ```bash curl 'http://localhost:8091/loggers/io.camunda' \ -i -X POST \ -H 'Content-Type: application/json' \ -d '{"configuredLevel":"DEBUG"}' ``` Replace `io.camunda` with the logger you want to adjust. :::note The base URL may differ depending on your environment configuration. The example above assumes execution from the same host running the Camunda Hub `restapi` component, with the default [`management.endpoints.web.base-path`](./properties.md#monitoring) of `/`. If you set a different base path, such as `/actuator`, include it in the URL: `http://localhost:8091/actuator/loggers/io.camunda`. This URL is only callable via the [management port](https://docs.spring.io/spring-boot/reference/actuator/monitoring.html#actuator.monitoring.customizing-management-server-port), usually not publicly available. ::: ### Default Log4j2 configuration The default `log4j2-spring.xml` used by Camunda Hub's `restapi` component is as follows: ```xml ``` :::note This is a simplified example. The actual `log4j2.xml` may include additional appenders, use different file paths, or have slightly different patterns. ::: ### Environment variables | Purpose | Variable | Component(s) | Example / Notes | | ------------------------- | ----------------------- | ------------ | ----------------------------- | | Global log level | `CAMUNDA_LOG_LEVEL` | All | `DEBUG`, `INFO`, `WARN`, etc. | | Camunda Hub package level | `CAMUNDA_HUB_LOG_LEVEL` | RestApi | Overrides global level | ### JSON logging appenders | Appender | Description | Enable / Variable | | ------------------ | ----------------------------------------------------------------------------------------------------- | ---------------------------------------- | | Console | Standard text output. | `CAMUNDA_HUB_LOG_APPENDER=Console` | | Stackdriver (JSON) | JSON output for Google Cloud / Stackdriver. | `CAMUNDA_HUB_LOG_APPENDER=Stackdriver` | | RollingFile | Writes logs to a rotating file in addition to the Console or Stackdriver output. Disabled by default. | `CAMUNDA_LOG_FILE_APPENDER_ENABLED=true` | #### JSON structure When using the `Stackdriver` appender, this is the structure of the entries : | Field | Type | Description | | --------------------------------------- | ----------------- | ---------------------------------------------------------------------------------- | | `timestamp` | string (ISO-8601) | Log event timestamp in UTC using pattern `yyyy-MM-dd'T'HH:mm:ss.SSS'Z'`. | | `severity` | string | Normalized log severity mapped from Log4j level (e.g., TRACE→DEBUG, WARN→WARNING). | | `message` | string | The log message, stringified by the message resolver. | | `logging.googleapis.com/sourceLocation` | object | Metadata describing the location in source code where the log originated. | | ├─ `file` | string | File name where the logging call occurred. | | ├─ `line` | number | Line number in the source file. | | └─ `function` | string | Fully qualified class and method name (via `%C.%M`). | | `logging.googleapis.com/labels` | object (MDC) | Key/value pairs from the Mapped Diagnostic Context. | | `threadContext` | object | Information about the thread producing the log event. | | ├─ `id` | number | Thread ID. | | ├─ `name` | string | Thread name. | | └─ `priority` | number | Thread priority. | | `loggerName` | string | The logger’s name (typically the class name). | | `exception` | string (optional) | Stringified stack trace of any thrown exception. | | `correlationId` | string (optional) | Value of the MDC entry `correlationId`, if present. | See the following example: ```json { "timestamp": "2025-11-20T10:55:57.885Z", "severity": "INFO", "message": "Example log message", "logging.googleapis.com/sourceLocation": { "file": "RequestLoggingFilter.java", "line": 91, "function": "io.camunda.hub.util.logging.RequestLoggingFilter.customAfterRequest" }, "logging.googleapis.com/labels": { "correlationId": "04284456-b95b-4121-a54b-6c48be6d3afd" }, "threadContext": { "id": 55, "name": "http-nio-8081-exec-1", "priority": 5 }, "loggerName": "io.camunda.hub.util.logging.RequestLoggingFilter", "correlationId": "04284456-b95b-4121-a54b-6c48be6d3afd" } ``` ### Pattern layout The default layout displays the full date and time, thread name, MDC context, log level, logger name, and message. A newline and a tab separate the log level from the logger name. #### Default pattern ```perl [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%t] %notEmpty{[%X] }%-5level%n\t%logger{36} - %msg%n ``` | Feature | Pattern | | ------------------- | --------------------------- | | Timestamp | Full date and time | | Logger name | Fully qualified logger name | | Newline after level | Yes | | Tab before logger | Yes | ### Client log level The `restapi` component also serves the client application running in the browser. To control the verbosity of the client logs, adjust the following setting: ```bash CAMUNDA_HUB_CLIENT_LOGGING_LEVEL=DEBUG ``` ```yaml camunda.hub.client.logging.level: DEBUG ``` :::info For log level options, see [understanding log levels](/self-managed/operational-guides/monitoring/log-levels.md#understanding-log-levels). ::: ## Logging configuration for the `websocket` component By default, the `websocket` component logs to the Docker container's standard output. ### Logging to a file To enable additional log output to a file, follow these steps: 1. Mount a volume to the directory `/var/www/html/storage/logs`. The logs will be written to a file named `laravel.log` located inside this directory. 2. Adjust the following environment variable: ```properties LOG_CHANNEL=single ``` --- ## Property reference Camunda Hub Self-Managed consists of two components: [`restapi`](#configuration-of-the-restapi-component) and [`websocket`](#configuration-of-the-websocket-component). Each component is configured separately as described below. - The `restapi` component is a Spring Boot application. Its configuration is stored in a YAML file (`application.yml`) by default. All Camunda Hub-specific settings are prefixed with `camunda.hub`. - The `websocket` (PHP/Laravel) component is configured via environment variables. :::note Configuration methods The two components support configuration through environment variables. For the `restapi` component, environment variables can be used as an alternative to `application.yml` following [Spring Boot conventions](https://docs.spring.io/spring-boot/reference/features/external-config.html#features.external-config.typesafe-configuration-properties.relaxed-binding.environment-variables): convert the property to uppercase, remove any dashes, and replace any delimiters (`.`) with `_`. For example, the property `camunda.hub.clusters[0].name` is represented by the environment variable `CAMUNDA_HUB_CLUSTERS_0_NAME`. If you are using the Camunda 8 Helm chart, read more about the different configuration options in the chart's [Helm chart values documentation](https://artifacthub.io/packages/helm/camunda/camunda-platform#webmodeler-parameters). You can pass environment variables to each component via `camundaHub.restapi.env` and `camundaHub.websockets.env` in your `values.yaml`. For which settings belong in the chart and which belong here, see [Helm and application configuration responsibilities](/self-managed/deployment/helm/configure/configuration-responsibilities.md). The recommended path for the properties on this page is `camundaHub.restapi.extraConfiguration`. ::: For a working example configuration showing how the components are correctly wired together, see the [Docker Compose file for Camunda Hub](https://github.com/camunda/camunda-distributions/blob/main/docker-compose/versions/camunda-8.10/docker-compose-hub.yaml) (`docker-compose-hub.yaml`). For how to run it, see [deploy with Camunda Hub](/self-managed/quickstart/developer-quickstart/docker-compose/connectors-and-modeling.md#deploy-with-camunda-hub) in the Docker Compose quickstart. ## Licensing ## Configuration of the `restapi` component As a Spring Boot application, the `restapi` component supports any standard [Spring configuration](https://docs.spring.io/spring-boot/reference/features/external-config.html) method. The tables below list each setting in two formats: - **Application properties** – the property names used in `application.yml`, the native Spring Boot configuration file format. - **Environment variables** – suitable for Docker Compose or direct shell usage. :::tip Passing JVM options When running the `restapi` component in a container (Docker / Kubernetes), use the `JAVA_TOOL_OPTIONS` environment variable to pass JVM arguments, for example for trust store settings or proxy configuration. ::: ### General | Property | Description | Example value | Default value | | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------- | | `camunda.hub.server.url` | URL at which users access Camunda Hub in the browser (used to construct redirect URLs in the client-side login flow as well as links in notification emails). | `https://hub.example.com`,`https://example.com/hub` | - | | `server.servlet.context-path` | [optional]Context path of the URL. Must be set if `camunda.hub.server.url` does not point to the root path of a (sub-)domain. | `/hub` | - | | `camunda.hub.server.https-only` | [optional]Enforce the usage of HTTPS when users access Camunda Hub (by redirecting from `http://` to `https://`). | `true` | `true` | | Environment variable | Description | Example value | Default value | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------- | | `CAMUNDA_HUB_SERVER_URL` | URL at which users access Camunda Hub in the browser (used to construct redirect URLs in the client-side login flow as well as links in notification emails). | `https://hub.example.com`,`https://example.com/hub` | - | | `SERVER_SERVLET_CONTEXTPATH` | [optional]Context path of the URL. Must be set if `CAMUNDA_HUB_SERVER_URL` does not point to the root path of a (sub-)domain. | `/hub` | - | | `CAMUNDA_HUB_SERVER_HTTPSONLY` | [optional]Enforce the usage of HTTPS when users access Camunda Hub (by redirecting from `http://` to `https://`). | `true` | `true` | ### Clusters To show your Orchestration Clusters in Camunda Hub, use the following configuration options available from Camunda 8.10. If you're migrating from an older version of Camunda Self-Managed, refer to the deprecated [legacy configurations](./legacy-cluster-config.md) and the [migration guide](../../../upgrade/components/890-to-8100.md#camunda-hub). The Camunda 8.10 Helm chart can deploy one Hub release with orchestration releases from supported chart versions: | Hub chart | Orchestration chart | Topology mode | | :-------- | :------------------ | :-------------- | | 8.10 | 8.10 | `orchestration` | | 8.10 | 8.9 | `orchestration` | | 8.10 | 8.8 | `orchestration` | | 8.10 | 8.7 | `orchestration` | Set `global.topology.mode: orchestration` in each orchestration release. The 8.7, 8.8, and 8.9 charts don't support Hub mode or `global.topology.clusters`; configure the Hub release and cluster inventory with the 8.10 chart. An orchestration release must disable its local Management Identity and set `global.identity.service.url` to the Management Identity service in the Hub release. Chart 8.7 uses separate Zeebe, Operate, and Tasklist components, so its Hub inventory must use the legacy component endpoints rather than the unified Orchestration Cluster endpoints. #### Management Identity cluster If `identity.enabled` is `true` for a Hub release, the Helm chart automatically adds a `management-cluster` entry named **Management Identity** to `camunda.hub.clusters`, containing only the Hub's own Management Identity component. This entry appears in the Clusters pages alongside your Orchestration Cluster registrations so Console and DevOps role holders can manage the Hub's Management Identity instance. You don't need to configure this entry manually, and it isn't affected by [dynamic cluster management](#dynamic-cluster-management). :::note Access to the cluster pages in Camunda Hub depends on the user's role: `Console` and `DevOps` role holders (users with the [`admin:clusters` permission](/self-managed/components/management-identity/access-management/access-management-overview.md#permissions)) get management access to the cluster pages, Hub admins (users with the [`admin:*` permission](/self-managed/components/management-identity/access-management/access-management-overview.md#permissions)) get full access, and other Hub members get read-only access. ::: | Property | Description | Example value | | :----------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------- | :------------------------------------------- | | `camunda.hub.clusters[0].id` | An identifier for the cluster. | `camunda-platform` | | `camunda.hub.clusters[0].name` | A readable name for the cluster. | `Camunda Platform` | | `camunda.hub.clusters[0].version` | The cluster version. | `8.10.0` | | `camunda.hub.clusters[0].tags` | A list of tags. The tags appear on every [environment](./environments.md) of the cluster. Use `prod` to mark production. | `['dev', 'test']` | | `camunda.hub.clusters[0].authentication` | The [authentication method](#available-authentication-methods). | `BEARER_TOKEN` | | `camunda.hub.clusters[0].authorizations.enabled` | Enables or disables authorizations for the cluster. If enabled, users see a hint when they deploy from Camunda Hub. | `true` | | `camunda.hub.clusters[0].custom-properties` | A list of custom properties. | See [custom properties](#custom-properties). | | `camunda.hub.clusters[0].components` | A list of components for the clusters. | See [components](#components). | | Environment variable | Description | Example value | | :---------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------- | :------------------------------------------- | | `CAMUNDA_HUB_CLUSTERS_0_ID` | An identifier for the cluster. | `camunda-platform` | | `CAMUNDA_HUB_CLUSTERS_0_NAME` | A readable name for the cluster. | `Camunda Platform` | | `CAMUNDA_HUB_CLUSTERS_0_VERSION` | The cluster version. | `8.10.0` | | `CAMUNDA_HUB_CLUSTERS_0_TAGS` | A list of tags. The tags appear on every [environment](./environments.md) of the cluster. Use `prod` to mark production. | `['dev', 'test']` | | `CAMUNDA_HUB_CLUSTERS_0_AUTHENTICATION` | The [authentication method](#available-authentication-methods). | `BEARER_TOKEN` | | `CAMUNDA_HUB_CLUSTERS_0_AUTHORIZATIONS_ENABLED` | Enables or disables authorizations for the cluster. If enabled, users see a hint when they deploy from Camunda Hub. | `true` | | `CAMUNDA_HUB_CLUSTERS_0_CUSTOMPROPERTIES` | A list of custom properties. | See [custom properties](#custom-properties). | | `CAMUNDA_HUB_CLUSTERS_0_COMPONENTS` | A list of components for the cluster. | See [components](#components). | #### Available authentication methods Clusters must be configured using the following options to access the cluster from within Camunda Hub: | Method | Description | When to use? | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `BEARER_TOKEN` | Camunda Hub sends the authenticated user's token in the `Authorization` header with every request to the cluster. | **Cluster version >= 8.8**The cluster uses [OIDC authentication](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md) with the same identity provider as Camunda Hub._Note_: You need to ensure that the cluster [accepts Camunda Hub's token audience](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md#step-4-configure-the-oidc-connection-details). | | `BASIC` | Camunda Hub sends a username and password with every request to the cluster. The credentials have to be provided by the user in the UI. | **Cluster version >= 8.8**The cluster uses Basic authentication.**Console limitation**Console pages in Camunda Hub don't support clusters configured with Basic authentication. Console requests to the Orchestration Cluster are made automatically in the background, so there is no UI to collect credentials. Clusters using Basic authentication _will not work correctly_ with Camunda Hub's Console functionality. | | `NONE` | Camunda Hub does not send any authentication information. | **Cluster version >= 8.8**The cluster API is [configured as unprotected](/self-managed/components/orchestration-cluster/admin/overview.md#enable-api-authentication-and-authorizations) and can be used without authentication. | #### Custom properties Use custom properties to include helpful links in the **Clusters** user interface: | Property | Description | | :----------------------------------------------------------- | :------------------------------------ | | `camunda.hub.clusters[0].custom-properties[0].description` | A description of the custom property. | | `camunda.hub.clusters[0].custom-properties[0].links` | A list of links. | | `camunda.hub.clusters[0].custom-properties[0].links[0].name` | A name for the link. | | `camunda.hub.clusters[0].custom-properties[0].links[0].url` | The link's URL. | Example configuration: ```yaml camunda: hub: clusters: - id: camunda-platform # other fields... custom-properties: - description: This is the integration environment for the Camunda platform. links: - name: Camunda url: https://camunda.com/ - name: Documentation url: https://docs.camunda.io/ ``` | Environment variable | Description | | :------------------------------------------------------- | :------------------------------------ | | `CAMUNDA_HUB_CLUSTERS_0_CUSTOMPROPERTIES_0_DESCRIPTION` | A description of the custom property. | | `CAMUNDA_HUB_CLUSTERS_0_CUSTOMPROPERTIES_0_LINKS_0_NAME` | A name for the indexed link. | | `CAMUNDA_HUB_CLUSTERS_0_CUSTOMPROPERTIES_0_LINKS_0_URL` | The link's URL. | Example configuration: ```bash CAMUNDA_HUB_CLUSTERS_0_CUSTOMPROPERTIES_0_DESCRIPTION="This is the integration environment for the Camunda platform." CAMUNDA_HUB_CLUSTERS_0_CUSTOMPROPERTIES_0_LINKS_0_NAME=Camunda CAMUNDA_HUB_CLUSTERS_0_CUSTOMPROPERTIES_0_LINKS_0_URL=https://camunda.com/ ``` #### Components Use `components` to set up components in the cluster: | Property | Description | | :----------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `camunda.hub.clusters[0].components[0].name` | The component's name. | | `camunda.hub.clusters[0].components[0].type` | The component's type. | | `camunda.hub.clusters[0].components[0].version` | The component's version. | | `camunda.hub.clusters[0].components[0].urls.webapp` | The API base URL for all components with a web app: Admin, Management Identity, Optimize, Tasklist, Operate. | | `camunda.hub.clusters[0].components[0].urls.rest` | The REST API base URL for Connectors and the Orchestration Cluster. Required. | | `camunda.hub.clusters[0].components[0].urls.grpc` | The [address](#notes-on-host-names-and-port-numbers) of the [Zeebe gRPC API](/apis-tools/zeebe-api/grpc.md). Optional. If you omit it, Camunda Hub uses the REST API for deployments, instance starts, and job workers, and job streaming is disabled. | | `camunda.hub.clusters[0].components[0].urls.readiness` | The address of the health check endpoint. | Use `CAMUNDA_HUB_CLUSTERS_0_COMPONENTS` to set up components in the cluster: | Environment variable | Description | | :--------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `CAMUNDA_HUB_CLUSTERS_0_COMPONENTS_0_NAME` | The component's name. | | `CAMUNDA_HUB_CLUSTERS_0_COMPONENTS_0_TYPE` | The component's type. | | `CAMUNDA_HUB_CLUSTERS_0_COMPONENTS_0_VERSION` | The component's version. | | `CAMUNDA_HUB_CLUSTERS_0_COMPONENTS_0_URLS_WEBAPP` | The API base URL for all components with a web app: Admin, Management Identity, Optimize, Tasklist, Operate. | | `CAMUNDA_HUB_CLUSTERS_0_COMPONENTS_0_URLS_REST` | The REST API base URL for Connectors and the Orchestration Cluster. Required. | | `CAMUNDA_HUB_CLUSTERS_0_COMPONENTS_0_URLS_GRPC` | The [address](#notes-on-host-names-and-port-numbers) of the [Zeebe gRPC API](/apis-tools/zeebe-api/grpc.md). Optional. If you omit it, Camunda Hub uses the REST API for deployments, instance starts, and job workers, and job streaming is disabled. | | `CAMUNDA_HUB_CLUSTERS_0_COMPONENTS_0_URLS_READINESS` | The address of the health check endpoint. | :::note Cluster communication protocol The REST URL is required for clusters version 8.8 and later. The gRPC URL is optional. - If you configure both URLs, Camunda Hub uses gRPC to interact with the cluster. To use REST instead, set `camunda.client.gateway.preferRestOverGrpc=true` (environment variable `CAMUNDA_CLIENT_GATEWAY_PREFERRESTOVERGRPC`). - If you configure only the REST URL, Camunda Hub uses REST to interact with the cluster. Job streaming is disabled because it is only available over gRPC. Use this setup if the network between Camunda Hub and the cluster only allows HTTP(S) traffic, for example when port `26500` is blocked. - If you configure only the gRPC URL, or neither URL, startup validation fails. ::: Available component types and requirements: | Configuration value | Component | Requirements | | :------------------ | :-------------------- | :---------------------------------- | | `connectors` | Connectors | REST URL | | `identity` | Management Identity | - | | `hub` | Camunda Hub | - | | `operate` | Operate | - | | `optimize` | Optimize | - | | `orchestration` | Orchestration Cluster | Cluster version >= 8.8 and REST URL | | `admin` | Admin | - | | `tasklist` | Tasklist | - | :::note Backward compatibility The old values `webModelerWebApp` (replaced by `hub`) and `orchestrationIdentity` (replaced by `admin`) are still accepted for backward compatibility. ::: Example configuration: ```yaml camunda: hub: clusters: - id: camunda-platform # other fields... components: - name: "Orchestration Cluster" type: "orchestration" version: "8.10-SNAPSHOT" urls: grpc: "grpcs://camunda.example.com:26500" rest: "https://camunda.example.com" readiness: "https://camunda.example.com:9600/core/actuator/health/readiness" - name: "Orchestration Admin" type: "admin" version: "8.10-SNAPSHOT" urls: webapp: "https://camunda.example.com" readiness: "https://camunda.example.com:9600/core/actuator/health/readiness" ``` ```bash CAMUNDA_HUB_CLUSTERS_0_COMPONENTS_0_NAME='Orchestration Cluster' CAMUNDA_HUB_CLUSTERS_0_COMPONENTS_0_TYPE=orchestration CAMUNDA_HUB_CLUSTERS_0_COMPONENTS_0_VERSION=8.10-SNAPSHOT CAMUNDA_HUB_CLUSTERS_0_COMPONENTS_0_URLS_GRPC=grpcs://camunda.example.com:26500 CAMUNDA_HUB_CLUSTERS_0_COMPONENTS_0_URLS_REST=https://camunda.example.com CAMUNDA_HUB_CLUSTERS_0_COMPONENTS_0_URLS_READINESS=https://camunda.example.com:9600/core/actuator/health/readiness CAMUNDA_HUB_CLUSTERS_0_COMPONENTS_1_NAME='Orchestration Admin' CAMUNDA_HUB_CLUSTERS_0_COMPONENTS_1_TYPE=admin CAMUNDA_HUB_CLUSTERS_0_COMPONENTS_1_VERSION=8.10-SNAPSHOT CAMUNDA_HUB_CLUSTERS_0_COMPONENTS_1_URLS_WEBAPP=https://camunda.example.com CAMUNDA_HUB_CLUSTERS_0_COMPONENTS_1_URLS_READINESS=https://camunda.example.com:9600/core/actuator/health/readiness ``` #### Mark a cluster as production This step is optional. Tag a cluster with `prod` only if you want Camunda Hub to treat its environments as production environments. Camunda Hub treats an environment as a production environment if the tags of its cluster include `prod`. The match is exact and case-sensitive, so `prod` works but `Prod` and `production` don't. All Physical Tenants of a cluster tagged `prod` are production environments. See the [project deployment settings](/components/hub/workspace/modeler/modeler-settings.md#project-deployment). ```yaml camunda: hub: clusters: - id: camunda-platform # other fields... tags: ["prod"] ``` ### Environments and Physical Tenants Camunda Hub creates environments from your clusters and their Physical Tenants. To declare Physical Tenants, override their components, and see how Camunda Hub determines the status of an environment, see [environments and Physical Tenants](./environments.md). ### Database Camunda Hub currently supports PostgreSQL, Oracle, Microsoft SQL Server (MSSQL), MySQL, MariaDB, and H2 as persistent data storage. :::info Oracle and MySQL driver The Oracle and MySQL drivers are not provided by default and must be downloaded and supplied for the application to load. Refer to the [Oracle](database.md#oracle) and [MySQL](database.md#mysql) database configuration section for details. ::: | Property | Description | Example value | | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------- | | `spring.datasource.url` | JDBC URL of the database | `jdbc:postgresql://postgres.example.com:5432/hub-db` | | `spring.datasource.username` | Database user name | `hub-user` | | `spring.datasource.password` | Database user password | \*\*\* | | `spring.datasource.driver-class-name` | [optional]Java class name of the database driver | `software.amazon.jdbc.Driver` | | `spring.datasource.hikari.schema` | [optional; only supported for PostgreSQL]Database schema.Defaults to the default schema of the database user (usually `public`) if not set.Refer to the [PostgreSQL documentation](https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS) for naming restrictions. | `custom_schema` | | Environment variable | Description | Example value | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------- | | `SPRING_DATASOURCE_URL` | JDBC URL of the database | `jdbc:postgresql://postgres.example.com:5432/hub-db` | | `SPRING_DATASOURCE_USERNAME` | Database user name | `hub-user` | | `SPRING_DATASOURCE_PASSWORD` | Database user password | \*\*\* | | `SPRING_DATASOURCE_DRIVERCLASSNAME` | [optional]Java class name of the database driver | `software.amazon.jdbc.Driver` | | `SPRING_DATASOURCE_HIKARI_SCHEMA` | [optional; only supported for PostgreSQL]Database schema.Defaults to the default schema of the database user (usually `public`) if not set.Refer to the [PostgreSQL documentation](https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS) for naming restrictions. | `custom_schema` | Refer to the [Advanced Database Configuration Guide](./database.md) for additional details on how to configure Camunda Hub's database connection. ### SMTP / email Camunda Hub requires an SMTP server to send notification emails to users. | Property | Description | Example value | Default value | | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------- | --------------------- | ------------- | | `spring.mail.host` | SMTP server host name | `smtp.example.com` | - | | `spring.mail.port` | SMTP server port | `587` | - | | `spring.mail.username` | [optional]SMTP user name | `hub-user` | - | | `spring.mail.password` | [optional]SMTP user password | \*\*\* | - | | `spring.mail.properties.mail.smtp.auth` | [optional]Set to `true` if you provide a user name and password. | `true` | `true` | | `spring.mail.properties.mail.smtp.starttls.enable` | [optional]Enable TLS encryption for SMTP connections (using STARTTLS). | `true` | `true` | | `spring.mail.properties.mail.smtp.starttls.required` | [optional]Enforce the use of STARTTLS (to prevent fallback to non-protected connections). | `true` | `true` | | `camunda.hub.mail.from-address` | Email address used as the sender of emails sent by Camunda Hub. | `noreply@example.com` | - | | `camunda.hub.mail.from-name` | [optional]Name displayed as the sender of emails sent by Camunda Hub. | `Camunda` | `Camunda` | | Environment variable | Description | Example value | Default value | | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------- | --------------------- | ------------- | | `SPRING_MAIL_HOST` | SMTP server host name | `smtp.example.com` | - | | `SPRING_MAIL_PORT` | SMTP server port | `587` | - | | `SPRING_MAIL_USERNAME` | [optional]SMTP user name | `hub-user` | - | | `SPRING_MAIL_PASSWORD` | [optional]SMTP user password | \*\*\* | - | | `SPRING_MAIL_PROPERTIES_MAIL_SMTP_AUTH` | [optional]Set to `true` if you provide a user name and password. | `true` | `true` | | `SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE` | [optional]Enable TLS encryption for SMTP connections (using STARTTLS). | `true` | `true` | | `SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_REQUIRED` | [optional]Enforce the use of STARTTLS (to prevent fallback to non-protected connections). | `true` | `true` | | `CAMUNDA_HUB_MAIL_FROMADDRESS` | Email address used as the sender of emails sent by Camunda Hub. | `noreply@example.com` | - | | `CAMUNDA_HUB_MAIL_FROMNAME` | [optional]Name displayed as the sender of emails sent by Camunda Hub. | `Camunda` | `Camunda` | ### WebSocket Camunda Hub uses a [WebSocket server](#configuration-of-the-websocket-component) to send events (e.g. "file updated", "comment added", "user opened diagram") between the backend and the client application in the browser. This enables features like real-time notifications and immediate UI updates. | Property | Description | Example value | Default value | | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ------------- | | `camunda.hub.pusher.host` | [Internal](#notes-on-host-names-and-port-numbers) host name of the WebSocket server. | `hub-websockets` | - | | `camunda.hub.pusher.port` | [Internal](#notes-on-host-names-and-port-numbers) port number of the WebSocket server. | `8060` | `8060` | | `camunda.hub.pusher.app-id` | _must be the same as_ [`PUSHER_APP_ID`](#configuration-of-the-websocket-component) | `hub` | - | | `camunda.hub.pusher.key` | _must be the same as_ [`PUSHER_APP_KEY`](#configuration-of-the-websocket-component) | \*\*\* | - | | `camunda.hub.pusher.secret` | _must be the same as_ [`PUSHER_APP_SECRET`](#configuration-of-the-websocket-component) | \*\*\* | - | | `camunda.hub.pusher.client.host` | [External](#notes-on-host-names-and-port-numbers) host name on which the Camunda Hub client accesses the WebSocket server from the browser. | `ws.example.com` | - | | `camunda.hub.pusher.client.port` | [External](#notes-on-host-names-and-port-numbers) port number on which the Camunda Hub client accesses the WebSocket server from the browser. | `443` | `80` | | `camunda.hub.pusher.client.path` | [optional]_must be the same as_ [`PUSHER_APP_PATH`](#configuration-of-the-websocket-component) | `/hub-ws` | `/` | | `camunda.hub.pusher.client.force-tls` | Enable TLS encryption for WebSocket connections initiated by the browser. | `true` | `false` | | Environment variable | Description | Example value | Default value | | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ------------- | | `CAMUNDA_HUB_PUSHER_HOST` | [Internal](#notes-on-host-names-and-port-numbers) host name of the WebSocket server. | `hub-websockets` | - | | `CAMUNDA_HUB_PUSHER_PORT` | [Internal](#notes-on-host-names-and-port-numbers) port number of the WebSocket server. | `8060` | `8060` | | `CAMUNDA_HUB_PUSHER_APPID` | _must be the same as_ [`PUSHER_APP_ID`](#configuration-of-the-websocket-component) | `hub` | - | | `CAMUNDA_HUB_PUSHER_KEY` | _must be the same as_ [`PUSHER_APP_KEY`](#configuration-of-the-websocket-component) | \*\*\* | - | | `CAMUNDA_HUB_PUSHER_SECRET` | _must be the same as_ [`PUSHER_APP_SECRET`](#configuration-of-the-websocket-component) | \*\*\* | - | | `CAMUNDA_HUB_PUSHER_CLIENT_HOST` | [External](#notes-on-host-names-and-port-numbers) host name on which the Camunda Hub client accesses the WebSocket server from the browser. | `ws.example.com` | - | | `CAMUNDA_HUB_PUSHER_CLIENT_PORT` | [External](#notes-on-host-names-and-port-numbers) port number on which the Camunda Hub client accesses the WebSocket server from the browser. | `443` | `80` | | `CAMUNDA_HUB_PUSHER_CLIENT_PATH` | [optional]_must be the same as_ [`PUSHER_APP_PATH`](#configuration-of-the-websocket-component) | `hub-ws` | `/` | | `CAMUNDA_HUB_PUSHER_CLIENT_FORCETLS` | Enable TLS encryption for WebSocket connections initiated by the browser. | `true` | `false` | ### Identity / Keycloak Camunda Hub uses Keycloak as the default authentication provider (using OAuth 2.0 + OpenID Connect) and integrates with [Management Identity](/self-managed/components/management-identity/overview.md) for user management and authorization (see [Manage access and permissions](/self-managed/components/management-identity/access-management/access-management-overview.md)). :::note Configure Camunda Hub authentication with the properties on this page, not with the Orchestration Cluster's `camunda.security.authentication.oidc.*` settings. The one exception is the claim that identifies a user: set `camunda.security.authentication.oidc.username-claim` directly. This is unrelated to `CAMUNDA_IDENTITY_USERNAMECLAIM` (`camunda.identity.username-claim`), which only sets a user's display name. See [change the user ID claim](./identity.md#change-the-user-id-claim). See [authentication](./identity.md) for more details. ::: | Property | Description | Example value | Default value | | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------ | | `camunda.identity.base-url` | [Internal](#notes-on-host-names-and-port-numbers) base URL of the Identity API (used to fetch user data). | `http://identity:8080` | - | | `camunda.identity.username-claim` | [optional]ID token claim used to assign usernames. | `preferred_username` | `name` | | `camunda.hub.security.jwt.audience.internal-api` | Expected value of the audience claim in user access tokens (used for JWT validation). | `web-modeler-api` | `web-modeler-api` | | `camunda.hub.security.jwt.audience.public-api` | Expected value of the audience claim in M2M access tokens required for [Camunda Hub's API](/apis-tools/hub-api-sm/authentication.md) (used for JWT validation). | `web-modeler-public-api` | `web-modeler-public-api` | | `camunda.identity.issuer-backend-url` | [optional][Internal](#notes-on-host-names-and-port-numbers) URL used to request Keycloak's [OpenID Provider Configuration](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig); if not set, `spring.security.oauth2.resourceserver.jwt.issuer-uri` is used. | `http://keycloak:18080/auth/realms/camunda-platform` | - | | `spring.security.oauth2.resourceserver.jwt.issuer-uri` | URL of the token issuer (used for JWT validation). | `https://keycloak.example.com/auth/realms/camunda-platform` | - | | `spring.security.oauth2.resourceserver.jwt.jwk-set-uri` | [optional] URL of the JWK Set endpoint (used for JWT validation). Only necessary if URL cannot be derived from the OIDC configuration endpoint. | `https://keycloak.example.com/auth/realms/camunda-platform/protocol/openid-connect/certs` | - | | `spring.security.oauth2.resourceserver.jwt.jws-algorithms` | [optional] List of trusted JWS algorithms used for JWT validation. Only necessary if the algorithms cannot be derived from the JWK Set response. | `ES256` | - | | `spring.security.oauth2.resourceserver.jwt.audiences` | [optional]Comma-separated list of accepted audience claim values, validated in addition to `camunda.hub.security.jwt.audience.internal-api` and `camunda.hub.security.jwt.audience.public-api`. | `web-modeler-api` | - | | `camunda.hub.oauth2.client-id` | Client ID of the Camunda Hub application configured in Identity. | `web-modeler` | - | | `camunda.hub.oauth2.client.scope` | [optional]OIDC scopes requested during authentication, determining what user information is included in the token. | `full` | `openid email profile` | | `camunda.hub.oauth2.client.fetch-request-credentials` | [optional]Configuration whether credentials should be sent along with requests to the OIDC provider, see [documentation](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials#value). Use this if you are using a proxy that requires cookies. | `include` | - | | Environment variable | Description | Example value | Default value | | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------ | | `CAMUNDA_IDENTITY_BASEURL` | [Internal](#notes-on-host-names-and-port-numbers) base URL of the Identity API (used to fetch user data). | `http://identity:8080` | - | | `CAMUNDA_IDENTITY_USERNAMECLAIM` | ID token claim used to assign usernames. | `preferred_username` | `name` | | `CAMUNDA_HUB_SECURITY_JWT_AUDIENCE_INTERNALAPI` | Expected value of the audience claim in user access tokens (used for JWT validation). | `web-modeler-api` | `web-modeler-api` | | `CAMUNDA_HUB_SECURITY_JWT_AUDIENCE_PUBLICAPI` | Expected value of the audience claim in M2M access tokens required for [Camunda Hub's API](/apis-tools/hub-api-sm/authentication.md) (used for JWT validation). | `web-modeler-public-api` | `web-modeler-public-api` | | `CAMUNDA_IDENTITY_ISSUERBACKENDURL` | [optional][Internal](#notes-on-host-names-and-port-numbers) URL used to request Keycloak's [OpenID Provider Configuration](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig); if not set, `SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUERURI` is used. | `http://keycloak:18080/auth/realms/camunda-platform` | - | | `SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUERURI` | URL of the token issuer (used for JWT validation). | `https://keycloak.example.com/auth/realms/camunda-platform` | - | | `SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWKSETURI` | [optional] URL of the JWK Set endpoint (used for JWT validation). Only necessary if URL cannot be derived from the OIDC configuration endpoint. | `https://keycloak.example.com/auth/realms/camunda-platform/protocol/openid-connect/certs` | - | | `SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWSALGORITHMS` | [optional] List of trusted JWS algorithms used for JWT validation. Only necessary if the algorithms cannot be derived from the JWK Set response. | `ES256` | - | | `SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_AUDIENCES` | [optional]Comma-separated list of accepted audience claim values, validated in addition to `CAMUNDA_HUB_SECURITY_JWT_AUDIENCE_INTERNALAPI` and `CAMUNDA_HUB_SECURITY_JWT_AUDIENCE_PUBLICAPI`. | `web-modeler-api` | - | | `CAMUNDA_HUB_OAUTH2_CLIENTID` | Client ID of the Camunda Hub application configured in Identity. | `web-modeler` | - | | `CAMUNDA_HUB_OAUTH2_CLIENT_SCOPE` | [optional]OIDC scopes requested during authentication, determining what user information is included in the token. | `full` | `openid email profile` | | `CAMUNDA_HUB_OAUTH2_CLIENT_FETCHREQUESTCREDENTIALS` | [optional]Configuration whether credentials should be sent along with requests to the OIDC provider, see [documentation](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials#value). Use this if you are using a proxy that requires cookies. | `include` | - | :::note Helm behavior The `restapi` component default for `CAMUNDA_IDENTITY_USERNAMECLAIM` is `name`. In Helm-based setups, OIDC configuration commonly uses `preferred_username`, so usernames may appear as email-style identifiers unless you explicitly set `CAMUNDA_IDENTITY_USERNAMECLAIM=name` for the Camunda Hub `restapi` environment. ::: Refer to the [authentication guide](./identity.md) for additional details on how Camunda Hub authenticates users, and on how to connect a custom OpenID Connect (OIDC) authentication provider. ### Camunda client Camunda Hub uses the [Camunda Java client](/apis-tools/java-client/getting-started.md) to connect to Zeebe. To customize the client configuration, you can provide optional properties. | Property | Description | Example value | Default value | | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | ---------------- | | `camunda.ca-certificate-path` | [optional]Path to a root CA certificate to be used instead of the certificate in the default store. | `/path/to/certificate` | - | | `camunda.client.config-path` | [optional]Path to a file used to cache the client's OAuth credentials on disk. When unset, credentials are cached in memory only. | `/path/to/credentials/cache.txt` | _in-memory only_ | | `camunda.client.request-timeout` | [optional]The request timeout used when communicating with a target Zeebe cluster. | `60000` | `10000` | | `camunda.auth.connect-timeout` | [optional]The connection timeout for requests to the OAuth server. | `30000` | `5000` | | `camunda.auth.read-timeout` | [optional]The data read timeout for requests to the OAuth server. | `30000` | `5000` | | Environment variable | Description | Example value | Default value | | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | ---------------- | | `CAMUNDA_CA_CERTIFICATE_PATH` | [optional]Path to a root CA certificate to be used instead of the certificate in the default store. | `/path/to/certificate` | - | | `CAMUNDA_CLIENT_CONFIG_PATH` | [optional]Path to a file used to cache the client's OAuth credentials on disk. When unset, credentials are cached in memory only. | `/path/to/credentials/cache.txt` | _in-memory only_ | | `CAMUNDA_CLIENT_REQUESTTIMEOUT` | [optional]The request timeout used when communicating with a target Zeebe cluster. | `60000` | `10000` | | `CAMUNDA_AUTH_CONNECT_TIMEOUT` | [optional]The connection timeout for requests to the OAuth server. | `30000` | `5000` | | `CAMUNDA_AUTH_READ_TIMEOUT` | [optional]The data read timeout for requests to the OAuth server. | `30000` | `5000` | For more details, [see the Zeebe connection troubleshooting section](/self-managed/components/hub/troubleshooting/troubleshoot-zeebe-connection.md). ### Logging | Property | Description | Example value | Default value | | ---------------------------------- | --------------------------------------------------- | --------------------------------------------- | ------------- | | `logging.config` | [optional]Path to custom Log4j2 configuration. | `file:/full/path/to/custom-log4j2-spring.xml` | - | | `camunda.hub.client.logging.level` | [optional]Log level for the client. | `DEBUG` | `WARN` | The `CAMUNDA_HUB_LOG_LEVEL`, `CAMUNDA_LOG_FILE_APPENDER_ENABLED`, and `CAMUNDA_HUB_LOG_APPENDER` settings are only available as environment variables. | Environment variable | Description | Example value | Default value | | ----------------------------------- | -------------------------------------------------------------------- | --------------------------------------------- | ------------- | | `LOGGING_CONFIG` | [optional]Path to custom Log4j2 configuration. | `file:/full/path/to/custom-log4j2-spring.xml` | - | | `CAMUNDA_HUB_LOG_LEVEL` | [optional]Defines the log level for the Camunda Hub components. | `DEBUG` | `INFO` | | `CAMUNDA_LOG_FILE_APPENDER_ENABLED` | [optional]To enable logging to a file. | `true` | `false` | | `CAMUNDA_HUB_LOG_APPENDER` | [optional]Defines which appender to use for logging. | `Stackdriver` | `Console` | | `CAMUNDA_HUB_CLIENT_LOGGING_LEVEL` | [optional]Log level for the client. | `DEBUG` | `WARN` | Refer to the [advanced logging configuration guide](./logging.md#logging-configuration-for-the-restapi-component) for additional details on how to customize the `restapi` logging output. :::info - For log level options, see [understanding log levels](/self-managed/operational-guides/monitoring/log-levels.md#understanding-log-levels). ::: ### SSL | Property | Description | Example value | Default value | | ----------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------ | ------------- | | `server.ssl.enabled` | [optional]Whether to enable SSL support. | `true` | `false` | | `server.ssl.certificate` | [optional]Path to a PEM-encoded SSL certificate file. | `file:/full/path/to/certificate.pem` | - | | `server.ssl.certificate-private-key` | [optional]Path to a PEM-encoded private key file for the SSL certificate. | `file:/full/path/to/key.pem` | - | | `management.server.ssl.enabled` | [optional]Whether to enable SSL support for the management server routes. | `true` | `false` | | `management.server.ssl.certificate` | [optional]Path to a PEM-encoded SSL certificate file. | `file:/full/path/to/certificate.pem` | - | | `management.server.ssl.certificate-private-key` | [optional]Path to a PEM-encoded private key file for the SSL certificate. | `file:/full/path/to/key.pem` | - | | `camunda.hub.pusher.ssl-enabled` | [optional]Whether to enable communication via SSL to the `websocket` component. | `true` | `false` | | Environment variable | Description | Example value | Default value | | --------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------ | ------------- | | `SERVER_SSL_ENABLED` | [optional]Whether to enable SSL support. | `true` | `false` | | `SERVER_SSL_CERTIFICATE` | [optional]Path to a PEM-encoded SSL certificate file. | `file:/full/path/to/certificate.pem` | - | | `SERVER_SSL_CERTIFICATEPRIVATEKEY` | [optional]Path to a PEM-encoded private key file for the SSL certificate. | `file:/full/path/to/key.pem` | - | | `MANAGEMENT_SERVER_SSL_ENABLED` | [optional]Whether to enable SSL support for the management server routes. | `true` | `false` | | `MANAGEMENT_SERVER_SSL_CERTIFICATE` | [optional]Path to a PEM-encoded SSL certificate file. | `file:/full/path/to/certificate.pem` | - | | `MANAGEMENT_SERVER_SSL_CERTIFICATEPRIVATEKEY` | [optional]Path to a PEM-encoded private key file for the SSL certificate. | `file:/full/path/to/key.pem` | - | | `CAMUNDA_HUB_PUSHER_SSLENABLED` | [optional]Whether to enable communication via SSL to the `websocket` component. | `true` | `false` | Refer to the [advanced SSL configuration guide](./ssl.md) for additional details on how to set up secure connections (incoming & outgoing) to the Camunda Hub components. ### Monitoring and health probes {#monitoring} The `restapi` component is a Spring Boot application that includes the [Spring Boot Actuator](https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready), providing health check and metrics endpoints out of the box. These endpoints are served on a separate management port (default: `8091`). By default, Camunda Hub uses the following actuator configuration: | Property | Description | Example value | Default value | | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -------------------- | ----------------------------------- | | `management.server.port` | [optional]Port for the management server (health and metrics endpoints). | `8091` | `8091` | | `management.endpoints.access.default` | [optional]Default access level for all actuator endpoints. | `read-only` | `none` | | `management.endpoints.web.exposure.include` | [optional]Comma-separated list of actuator endpoints to expose over the web. | `health, prometheus` | `health, info, prometheus, loggers` | | `management.endpoints.web.base-path` | [optional]Base path for all web-exposed actuator endpoints. | `/actuator` | `/` | | `management.endpoints.web.path-mapping.health` | [optional]Custom path mapping for the health endpoint. | `health` | `health` | | `management.endpoints.web.path-mapping.prometheus` | [optional]Custom path mapping for the Prometheus endpoint. | `prometheus` | `metrics` | | `management.endpoint.prometheus.access` | [optional]Access level for the Prometheus endpoint. | `unrestricted` | `read-only` | | `management.endpoint.health.access` | [optional]Access level for the health endpoint. | `unrestricted` | `read-only` | | `management.endpoint.health.probes.enabled` | [optional]Whether Kubernetes-style readiness and liveness probes are enabled. | `true` | `true` | | `management.endpoint.health.group.readiness.additional-path` | [optional]Expose the readiness probe on an additional path (e.g. on the main server port). | `server:/health` | `server:/health` | | `management.endpoint.info.access` | [optional]Access level for the info endpoint. | `unrestricted` | `read-only` | | `management.endpoint.loggers.access` | [optional]Access level for the loggers endpoint. | `read-only` | `unrestricted` | | `management.info.git.enabled` | [optional]Whether Git info is exposed via the info endpoint. | `true` | `false` | | `management.health.defaults.enabled` | [optional]Whether default health indicators are enabled. | `true` | `false` | | `management.metrics.distribution.percentiles[http.server.requests]` | [optional]Comma-separated list of percentiles to publish for HTTP server request metrics. | `0.5, 0.9, 0.99` | `0.5, 0.9, 0.99` | | Environment variable | Description | Example value | Default value | | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | -------------------- | ----------------------------------- | | `MANAGEMENT_SERVER_PORT` | [optional]Port for the management server (health and metrics endpoints). | `8091` | `8091` | | `MANAGEMENT_ENDPOINTS_ACCESS_DEFAULT` | [optional]Default access level for all actuator endpoints. | `read-only` | `none` | | `MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE` | [optional]Comma-separated list of actuator endpoints to expose over the web. | `health, prometheus` | `health, info, prometheus, loggers` | | `MANAGEMENT_ENDPOINTS_WEB_BASEPATH` | [optional]Base path for all web-exposed actuator endpoints. | `/actuator` | `/` | | `MANAGEMENT_ENDPOINTS_WEB_PATHMAPPING_HEALTH` | [optional]Custom path mapping for the health endpoint. | `health` | `health` | | `MANAGEMENT_ENDPOINTS_WEB_PATHMAPPING_PROMETHEUS` | [optional]Custom path mapping for the Prometheus endpoint. | `prometheus` | `metrics` | | `MANAGEMENT_ENDPOINT_PROMETHEUS_ACCESS` | [optional]Access level for the Prometheus endpoint. | `unrestricted` | `read-only` | | `MANAGEMENT_ENDPOINT_HEALTH_ACCESS` | [optional]Access level for the health endpoint. | `unrestricted` | `read-only` | | `MANAGEMENT_ENDPOINT_HEALTH_PROBES_ENABLED` | [optional]Whether Kubernetes-style readiness and liveness probes are enabled. | `true` | `true` | | `MANAGEMENT_ENDPOINT_HEALTH_GROUP_READINESS_ADDITIONALPATH` | [optional]Expose the readiness probe on an additional path (e.g. on the main server port). | `server:/health` | `server:/health` | | `MANAGEMENT_ENDPOINT_INFO_ACCESS` | [optional]Access level for the info endpoint. | `unrestricted` | `read-only` | | `MANAGEMENT_ENDPOINT_LOGGERS_ACCESS` | [optional]Access level for the loggers endpoint. | `read-only` | `unrestricted` | | `MANAGEMENT_INFO_GIT_ENABLED` | [optional]Whether Git info is exposed via the info endpoint. | `true` | `false` | | `MANAGEMENT_HEALTH_DEFAULTS_ENABLED` | [optional]Whether default health indicators are enabled. | `true` | `false` | | `MANAGEMENT_METRICS_DISTRIBUTION_PERCENTILES_HTTP_SERVER_REQUESTS` | [optional]Comma-separated list of percentiles to publish for HTTP server request metrics. | `0.5, 0.9, 0.99` | `0.5, 0.9, 0.99` | #### Available endpoints | Endpoint | Description | | -------------------------------- | ------------------ | | `:8091/metrics` | Prometheus metrics | | `:8091/health/readiness` | Readiness probe | | `:8091/health/liveness` | Liveness probe | For more details, including Kubernetes probe configuration examples and `websocket` health endpoints, see the [Monitoring](../monitoring.md) page. ### Git Sync Camunda Hub supports syncing files via [Git Sync](/components/hub/workspace/manage-projects/git-sync.md). Provide the base URL for your provider if you are using a self-hosted GitLab, GitHub, or Azure DevOps Server instance. | Provider | Property | Description | Default value | | ------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | | All providers | `camunda.hub.git-sync.max-files` | Maximum number of allowed files for sync operations. | `100` | | All providers | `camunda.hub.git-sync.max-in-memory-size` | Maximum memory size that can be processed by calls to the Git provider. This limits the maximum file size that can be synced. | `4MB` | | GitHub | `camunda.hub.git-sync.github.base-url` | The base URL of your self-hosted GitHub instance. | `https://api.github.com` | | GitLab | `camunda.hub.git-sync.gitlab.base-url` | The base URL of your self-hosted GitLab instance. | `https://gitlab.com/api/v4` | | Azure DevOps | `camunda.hub.git-sync.azure.base-url` | The base URL of your self-hosted Azure DevOps Server instance. | `https://dev.azure.com` | | Azure DevOps | `camunda.hub.git-sync.azure.api-version` | The Azure DevOps API versions to use. | `7.1` | | Azure DevOps | `camunda.hub.git-sync.azure.authority-base-path` | URL used to access authentication and authorization services for Microsoft cloud identities. | `https://login.microsoftonline.com` | | Azure DevOps | `camunda.hub.git-sync.azure.scope` | OAuth scope requested for Azure DevOps authentication. | `https://app.vssps.visualstudio.com/.default` | | Bitbucket | `camunda.hub.git-sync.bitbucket.base-url` | The base URL of Bitbucket Cloud. | `https://api.bitbucket.org/2.0/repositories` | | Provider | Environment variable | Description | Default value | | ------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | | All providers | `CAMUNDA_HUB_GITSYNC_MAXFILES` | Maximum number of allowed files for sync operations. | `100` | | All providers | `CAMUNDA_HUB_GITSYNC_MAXINMEMORYSIZE` | Maximum memory size that can be processed by calls to the Git provider. This limits the maximum file size that can be synced. | `4MB` | | GitHub | `CAMUNDA_HUB_GITSYNC_GITHUB_BASEURL` | The base URL of your self-hosted GitHub instance. | `https://api.github.com` | | GitLab | `CAMUNDA_HUB_GITSYNC_GITLAB_BASEURL` | The base URL of your self-hosted GitLab instance. | `https://gitlab.com/api/v4` | | Azure DevOps | `CAMUNDA_HUB_GITSYNC_AZURE_BASEURL` | The base URL of your self-hosted Azure DevOps Server instance. | `https://dev.azure.com` | | Azure DevOps | `CAMUNDA_HUB_GITSYNC_AZURE_APIVERSION` | The Azure DevOps API versions to use. | `7.1` | | Azure DevOps | `CAMUNDA_HUB_GITSYNC_AZURE_AUTHORITYBASEPATH` | URL used to access authentication and authorization services for Microsoft cloud identities. | `https://login.microsoftonline.com` | | Azure DevOps | `CAMUNDA_HUB_GITSYNC_AZURE_SCOPE` | OAuth scope requested for Azure DevOps authentication. | `https://app.vssps.visualstudio.com/.default` | | Bitbucket | `CAMUNDA_HUB_GITSYNC_BITBUCKET_BASEURL` | The base URL of Bitbucket Cloud. | `https://api.bitbucket.org/2.0/repositories` | ### Catalog Use the following settings to adjust the [limits](/components/hub/organization/manage-catalog/getting-started.md#ingestion-limits) for the [catalog ingestion API endpoint](/apis-tools/hub-api-sm/specifications/ingest-catalog-assets.api.mdx). | Property | Description | Example value | Default value | | ------------------------------------------- | ------------------------------------------------------------------------------- | ------------- | ------------- | | `server.tomcat.max-part-count` | [optional]Maximum number of parts in `multipart/form-data` requests. | `5000` | `5000` | | `spring.servlet.multipart.max-request-size` | [optional]Maximum request size for `multipart/form-data` requests. | `20MB` | `20MB` | | `spring.servlet.multipart.max-file-size` | [optional]Maximum size of a single file in `multipart/form-data` requests. | `10MB` | `4MB` | | Property | Description | Example value | Default value | | ----------------------------------------- | ------------------------------------------------------------------------------- | ------------- | ------------- | | `SERVER_TOMCAT_MAXPARTCOUNT` | [optional]Maximum number of parts in `multipart/form-data` requests. | `5000` | `5000` | | `SPRING_SERVLET_MULTIPART_MAXREQUESTSIZE` | [optional]Maximum request size for `multipart/form-data` requests. | `20MB` | `20MB` | | `SPRING_SERVLET_MULTIPART_MAXFILESIZE` | [optional]Maximum size of a single file in `multipart/form-data` requests. | `10MB` | `4MB` | ### Feature flags | Property | Description | Example value | Default value | | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ------------- | | `camunda.hub.feature.test-mode-enabled` | [optional]Enables the [**Test** mode](../../../../components/hub/workspace/modeler/validation/test-your-process.md) in the BPMN editor, allowing users to test processes in a playground environment. | `false` | `true` | | `camunda.hub.feature.bpmn-deployment-enabled` | [optional]Enables the [**Deploy** and **Run**](../../../../components/hub/workspace/modeler/run-or-publish-your-process.md) actions in the BPMN editor.When disabled, it prevents users from deploying and starting instances of processes via the UI. | `false` | `true` | | `camunda.hub.feature.dmn-deployment-enabled` | [optional]Enables the [**Deploy**](../../../../components/hub/workspace/modeler/run-or-publish-your-process.md) action in the DMN editor.When disabled, it prevents users from deploying decisions via the UI. | `false` | `true` | | `camunda.hub.feature.dynamic-cluster-management-enabled` | [optional]Enables [dynamic cluster management](#dynamic-cluster-management). | `true` | `false` | | `camunda.hub.feature.ui-user-invite-enabled` | [optional]Enables the **Add members** button on the workspace **Members** page for users who aren't **Organization admins**. **Organization admins** always see the button, regardless of this setting. Adding members through the [Hub API](/apis-tools/hub-api-sm/overview.md) is unaffected. | `false` | `true` | | `camunda.hub.feature.runtime-connection-enabled` | [optional]Enables the [runtime connection](../../../../components/hub/workspace/modeler/modeling/connect-to-a-runtime.md) selector in the BPMN editor.When disabled, task testing uses its own cluster selection and connector credentials aren't offered in the properties panel. | `false` | `true` | | `camunda.hub.feature.credentials-enabled` | [optional]Enables [credentials](../../../../components/hub/organization/credentials/index.md) in Camunda Hub.Offering connector credentials in the properties panel of the BPMN editor also requires `camunda.hub.feature.runtime-connection-enabled`. | `false` | `true` | | `camunda.hub.feature.marketplace-enabled` | [optional]Enables the integration of the [Camunda Marketplace](https://marketplace.camunda.com). If enabled, users can browse the Marketplace and download [resources](../../../../components/hub/workspace/modeler/modeling/camunda-marketplace.md) directly inside Camunda Hub. | `false` | `true` | | Environment variable | Description | Example value | Default value | | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ------------- | | `CAMUNDA_HUB_FEATURE_TESTMODEENABLED` | [optional]Enables the [**Test** mode](../../../../components/hub/workspace/modeler/validation/test-your-process.md) in the BPMN editor, allowing users to test processes in a playground environment. | `false` | `true` | | `CAMUNDA_HUB_FEATURE_BPMNDEPLOYMENTENABLED` | [optional]Enables the [**Deploy** and **Run**](../../../../components/hub/workspace/modeler/run-or-publish-your-process.md) actions in the BPMN editor.When disabled, it prevents users from deploying and starting instances of processes via the UI. | `false` | `true` | | `CAMUNDA_HUB_FEATURE_DMNDEPLOYMENTENABLED` | [optional]Enables the [**Deploy**](../../../../components/hub/workspace/modeler/run-or-publish-your-process.md) action in the DMN editor.When disabled, it prevents users from deploying decisions via the UI. | `false` | `true` | | `CAMUNDA_HUB_FEATURE_DYNAMICCLUSTERMANAGEMENTENABLED` | [optional]Enables [dynamic cluster management](#dynamic-cluster-management). | `true` | `false` | | `CAMUNDA_HUB_FEATURE_UIUSERINVITEENABLED` | [optional][Enables the button](#hide-add-members-button) for inviting members to a workspace. | `false` | `true` | | `CAMUNDA_HUB_FEATURE_RUNTIMECONNECTIONENABLED` | [optional]Enables the [runtime connection](../../../../components/hub/workspace/modeler/modeling/connect-to-a-runtime.md) selector in the BPMN editor.When disabled, task testing uses its own cluster selection and connector credentials aren't offered in the properties panel. | `false` | `true` | | `CAMUNDA_HUB_FEATURE_CREDENTIALSENABLED` | [optional]Enables [credentials](../../../../components/hub/organization/credentials/index.md) in Camunda Hub.Offering connector credentials in the properties panel of the BPMN editor also requires `CAMUNDA_HUB_FEATURE_RUNTIMECONNECTIONENABLED`. | `false` | `true` | | `CAMUNDA_HUB_FEATURE_MARKETPLACEENABLED` | [optional]Enables the integration of the [Camunda Marketplace](https://marketplace.camunda.com). If enabled, users can browse the Marketplace and download [resources](../../../../components/hub/workspace/modeler/modeling/camunda-marketplace.md) directly inside Camunda Hub. | `false` | `true` | #### Dynamic cluster management Use dynamic cluster management to automatically register your clusters with Camunda Hub. By default, clusters shown in Camunda Hub are strictly managed by your [configuration](#clusters). Cluster registrations are created, updated, and deleted when you change your cluster configuration values. Dynamic cluster management changes this to a hybrid model. Camunda Hub uses your cluster configuration—if you provide one—alongside the following API endpoints, which are only exposed when dynamic cluster management is enabled: | Name | Path | | :------------------------------------------------------------------------------------------------------------------------- | :------------------------------------ | | [**Create or update a cluster registration**](/apis-tools/hub-api-saas/specifications/create-cluster-registration.api.mdx) | `POST /api/v2/clusters` | | [**Remove a cluster registration**](/apis-tools/hub-api-saas/specifications/remove-cluster-registration.api.mdx) | `DELETE /api/v2/clusters/{clusterId}` | In this mode, you: 1. [Configure your Orchestration Clusters](/self-managed/components/orchestration-cluster/zeebe/configuration/broker.md#camunda-hub-ping-configuration) to send license information directly to the [create or update a cluster registration](/apis-tools/hub-api-saas/specifications/create-cluster-registration.api.mdx) endpoint. If you didn't define the clusters in your configuration, this call registers them with minimal information and no management functionality in the Camunda Hub interface. 2. Remove stale cluster registrations from Camunda Hub using the [remove a cluster registration](/apis-tools/hub-api-saas/specifications/remove-cluster-registration.api.mdx) endpoint. You can still define new clusters in your configuration, though it's not required. When you do, Camunda Hub automatically registers them with all [available settings](#clusters) and full management functionality in the interface. :::note With dynamic cluster management enabled, don't call the create or update cluster registration endpoint manually—only let your cluster configuration do it. The endpoint doesn't yet support creating clusters with all configurable settings. ::: #### Hide add members button Hide the **Add members** button on the workspace **Members** page (which is displayed by default): ```yaml camunda: hub: feature: ui-user-invite-enabled: false ``` ```shell CAMUNDA_HUB_FEATURE_UIUSERINVITEENABLED=false ``` Organization admins always see the button, regardless of this setting. Other users will not see the button. Instead, they must add members with the [Camunda Hub API](/apis-tools/hub-api-sm/overview.md). ### Unstable configuration options These are unstable options that are not officially supported and may be removed without deprecation in future releases. They are intended for testing and feedback purposes only. | Property | Description | Example value | Default value | | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ------------- | | `camunda.hub.resource-import.allow-private-ip-address` | Allow importing resources from a host that resolves to a private IP address. Enabling this option weakens server-side request forgery (SSRF) protections and can significantly increase security exposure. | `true` | `false` | | Environment variable | Description | Example value | Default value | | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ------------- | | `CAMUNDA_HUB_RESOURCEIMPORT_ALLOWPRIVATEIPADDRESS` | Allow importing resources from a host that resolves to a private IP address. Enabling this option weakens server-side request forgery (SSRF) protections and can significantly increase security exposure. | `true` | `false` | ## Configuration of the `websocket` component The [WebSocket](https://en.wikipedia.org/wiki/WebSocket) server shipped with Camunda Hub Self-Managed is based on the [laravel-websockets](https://laravel.com/docs/10.x/broadcasting#open-source-alternatives-php) open source package and implements the [Pusher Channels Protocol](https://pusher.com/docs/channels/library_auth_reference/pusher-websockets-protocol/). The `websocket` component is configured via environment variables. When using the Camunda Helm chart, you can pass these variables via `camundaHub.websockets.env` in your `values.yaml`. See the [Helm chart values docs](https://artifacthub.io/packages/helm/camunda/camunda-platform#webmodeler-parameters) for all available configuration options. | Environment variable | Description | Example value | Default value | | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ------------- | | `PUSHER_APP_ID` | ID of the single application/tenant configured for Camunda Hub. | `hub` | - | | `PUSHER_APP_KEY` | A unique key used for authentication. Provide a random alphanumeric string of at least 20 characters. | \*\*\* | - | | `PUSHER_APP_SECRET` | A unique secret used for authentication. Provide a random alphanumeric string of at least 20 characters. | \*\*\* | - | | `PUSHER_APP_PATH` | [optional]Base path of the WebSocket endpoint. Can be used to expose the endpoint on a sub path instead of the domain root (e.g. `https://example.com/hub-ws`). | `/hub-ws` | `/` | ### Logging | Environment variable | Description | Example value | Default value | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------- | ------------- | | `LOG_CHANNEL` | [optional]Log channel driver, see [Laravel documentation](https://laravel.com/docs/10.x/logging#available-channel-drivers) | `single` | `stack` | Refer to the [Advanced Logging Configuration Guide](./logging.md#logging-configuration-for-the-websocket-component) for additional details on how to customize the `websocket` logging output. ### SSL | Environment variable | Description | Example value | Default value | | ----------------------- | ------------------------------------------------------------------------------ | ------------------------------- | ------------- | | `PUSHER_SSL_CERT` | [optional]Path to a PEM-encoded SSL certificate file. | `/full/path/to/certificate.pem` | - | | `PUSHER_SSL_KEY` | [optional]Path to a PEM-encoded private key file for the SSL certificate. | `/full/path/to/key.pem` | - | | `PUSHER_SSL_PASSPHRASE` | [optional]Passphrase for the private key file. | `change-me` | - | Refer to the [advanced SSL configuration guide](./ssl.md) for additional details on how to set up secure connections (incoming & outgoing) to the Camunda Hub components. ## Notes on host names and port numbers - _Internal_ refers to host names and port numbers that are only used inside a Docker Compose network or Kubernetes cluster for backend-to-backend communication. - _External_ refers to host names and port numbers that are exposed to the outside and can be reached from a web browser. --- ## SSL By default, communication between Camunda Hub and Identity and the Camunda Hub components is not encrypted, as it usually happens backend-to-backend within the same [Docker](/self-managed/deployment/docker/docker.md) network or [Kubernetes](/self-managed/deployment/helm/install/quick-install.md) cluster. However, you can enable TLS-encrypted communication by following the steps below (for example, if backend-to-backend communication is not possible in a custom Camunda 8 installation setup). ## Configuring secure connections to Identity ### Configure the Identity base URL For the `restapi` container, provide a URL that starts with `https://` (for example `https://identity.example.com`) as the base URL of the Identity instance. ``` CAMUNDA_IDENTITY_BASEURL=https://identity.example.com ``` ```yaml camunda.identity.base-url: https://identity.example.com ``` ## Configuring secure connections for Camunda Hub components ### Configure `restapi` SSL certificate SSL can be configured declaratively by setting the respective properties offered by Spring Boot (make sure that the provided certificate path is accessible from the container, for example via a mounted volume): ``` CAMUNDA_HUB_SERVER_URL=https://hub.example.com SERVER_SSL_ENABLED=true SERVER_SSL_CERTIFICATE=file:/full/path/to/certificate.pem SERVER_SSL_CERTIFICATEPRIVATEKEY=file:/full/path/to/key.pem ``` Additionally, you can configure SSL separately for the management routes of the `restapi` component: ``` MANAGEMENT_SERVER_SSL_ENABLED=true MANAGEMENT_SERVER_SSL_CERTIFICATE=file:/full/path/to/certificate.pem MANAGEMENT_SERVER_SSL_CERTIFICATEPRIVATEKEY=file:/full/path/to/key.pem ``` ```yaml camunda.hub.server.url: https://hub.example.com server: ssl: enabled: true certificate: file:/full/path/to/certificate.pem certificate-private-key: file:/full/path/to/key.pem ``` Additionally, you can configure SSL separately for the management routes of the `restapi` component: ```yaml management: server: ssl: enabled: true certificate: file:/full/path/to/certificate.pem certificate-private-key: file:/full/path/to/key.pem ``` Refer to the [Spring Boot documentation](https://docs.spring.io/spring-boot/how-to/webserver.html#howto.webserver.configure-ssl) for more information on configuration options. #### Use secure connections between the `restapi` and `websocket` components To use secure connections between the `restapi` and `websocket` components: ``` CAMUNDA_HUB_PUSHER_SSLENABLED=true ``` ```yaml camunda.hub.pusher.ssl-enabled: true ``` ### Configure `websocket` SSL certificate SSL can be configured by setting the following environment variables (make sure that the provided certificate path is accessible from the container, e.g. via a mounted volume): ``` PUSHER_SSL_CERT=/full/path/to/certificate.pem PUSHER_SSL_KEY=/full/path/to/key.pem PUSHER_SSL_PASSPHRASE=your-passphrase ``` :::info Currently, there is no option to configure SSL for the `websocket` management routes separately from the application routes. ::: ## (Optional) Provide a custom certificate If you are using a custom (self-signed) TLS certificate for either the `restapi` or Identity, you need to make Camunda Hub accept the certificate. For the `restapi` container: - Add the certificate to a custom Java trust store (using the [`keytool`](https://docs.oracle.com/en/java/javase/21/docs/specs/man/keytool.html) utility). - Configure the trust store as described in the [Zeebe connection troubleshooting guide](../troubleshooting/troubleshoot-zeebe-connection.md#provide-the-certificate-to-the-jvm-trust-store). --- ## Camunda Hub(Hub) Install and configure Camunda Hub components in your Self-Managed environment. ## Installation and configuration Camunda Hub is a unified platform that provides governance, observability, and delivery capabilities in one place. ### Install Camunda Hub Choose the installation method that matches your environment: - **Kubernetes with Helm:** [Install the Camunda Hub release](/self-managed/deployment/helm/install/topology/hub-release.md), which runs Camunda Hub and Management Identity as the management plane of your [deployment topology](/self-managed/deployment/helm/install/topology/index.md). - **Docker Compose for local development:** [Deploy with Camunda Hub](/self-managed/quickstart/developer-quickstart/docker-compose/connectors-and-modeling.md#deploy-with-camunda-hub) using the standalone `docker-compose-hub.yaml` configuration, or use the full configuration, which includes Camunda Hub. ### Configure Camunda Hub After installation, configure Camunda Hub and connect it to your Orchestration Clusters across environments: - [Property reference](./configuration/properties.md): settings for the `restapi` and `websocket` components. - [Environments and Physical Tenants](./configuration/environments.md): how Camunda Hub creates environments from your clusters and Physical Tenants. - [Legacy cluster configurations](./configuration/legacy-cluster-config.md): the deprecated way to configure clusters. - [Database](./configuration/database.md): connect Camunda Hub to a supported database. - [Authentication](./configuration/identity.md): configure authentication and connect an OIDC provider. - [Logging](./configuration/logging.md): additional logging configuration. - [SSL](./configuration/ssl.md): additional SSL configuration. - [Copilot](./configuration/copilot.md): configure Copilot with a custom LLM provider. To operate and maintain Camunda Hub, see [usage metrics](./usage-metrics.md), [monitoring](./monitoring.md), and [version upgrade](./version-upgrade.md). If something goes wrong, see the Camunda Hub troubleshooting pages, starting with [troubleshoot database connection](./troubleshooting/troubleshoot-database-connection.md). --- ## Monitoring(Hub) Camunda Hub Self-Managed consists of two components (`restapi` and `websocket`), each exposing their own endpoints for health monitoring and metrics collection. For configuration details, including the default Actuator settings and the management port, see the [Monitoring and health probes](./configuration/properties.md#monitoring) section on the configuration page. ## Available endpoints ### `restapi` The `restapi` component is a Spring Boot application that includes the [Spring Boot Actuator](https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready), providing health check and metrics endpoints out of the box. These endpoints are served on a separate management port (default: `8091`). You can configure it with the [`management.server.port`](./configuration/properties.md#monitoring) property or the `MANAGEMENT_SERVER_PORT` environment variable. | Endpoint | Description | | -------------------------------- | ------------------ | | `:8091/metrics` | Prometheus metrics | | `:8091/health/readiness` | Readiness probe | | `:8091/health/liveness` | Liveness probe | ### `websocket` The `websocket` component provides a basic health check endpoint on its default application port (`8060`). | Endpoint | Description | | ------------------ | ------------ | | `:8060/up` | Health check | :::note The `websocket` component does not expose a metrics endpoint. ::: ## Using probes in Kubernetes For details on setting Kubernetes probe parameters, see [Kubernetes configure probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#configure-probes). **Readiness probe:** ```yaml readinessProbe: httpGet: path: /health/readiness port: 8091 initialDelaySeconds: 30 periodSeconds: 30 ``` **Liveness probe:** ```yaml livenessProbe: httpGet: path: /health/liveness port: 8091 initialDelaySeconds: 30 periodSeconds: 30 ``` --- ## Troubleshoot database connection issues You try to start Camunda Hub, and encounter issues with the database connection. ## Using a non-empty schema As Camunda Hub uses [Flyway](https://www.red-gate.com/products/flyway/community/) to manage schema updates, the schema should not be shared. Before the first initialization, ensure no tables or functions are present in your schema. If your database setup requires mandatory tables or functions, Flyway may throw an exception like `Found non-empty schema(s) "" without schema history table!` To overcome this issue, add the property `spring.flyway.baselineOnMigrate: true` to your Camunda Hub configuration and remove it after the schema has been initialized. ## Secure connection to standard PostgreSQL Refer to the [database configuration guide](../configuration/database.md#configuring-ssl-for-the-database-connection) for details on how to configure a secure connection to PostgreSQL. ## Secure connection to Amazon Aurora fails You configured a custom SSL certificate in your remote Amazon Aurora PostgreSQL instance and want Camunda Hub to accept that certificate. ### Add Amazon Root CA to trust store By default, the Java version used by `modeler-restapi` ships with the Amazon Root CA. If you passed a custom trust store to `modeler-restapi`'s JVM process (e.g. via `JAVA_TOOL_OPTIONS` as described in [the Zeebe connection troubleshooting guide](./troubleshoot-zeebe-connection.md#provide-the-certificate-to-the-jvm-trust-store)), ensure the Amazon Trust Services CA are in `modeler-restapi`'s trust store (see the [Amazon Aurora documentation](https://aws.amazon.com/blogs/security/how-to-prepare-for-aws-move-to-its-own-certificate-authority/)). ## IAM authentication against Amazon Aurora fails You switched from standard username/password authentication to IAM authentication and Camunda Hub can't obtain a connection to the database. ### Ensure the IAM account has all privileges to the Camunda Hub database After switching from standard username/password authentication to IAM authentication, privileges to Camunda Hub's database might still be associated with the old username. Ensure the IAM account has all privileges to the Camunda Hub database. --- ## Troubleshoot missing data Troubleshoot and resolve your Camunda Hub missing data issues. ## Issue When logged in to Camunda Hub, all your previous data appears to be missing. ## Cause You must ensure the externally managed user ID does not change. Camunda Hub uses the value of the `sub` (subject) claim in the JSON Web Token (JWT) issued by the configured OIDC provider (default Keycloak) to identify users and correlate them with their data created in Camunda Hub. It is important that this value does not change over time, for example when the user is deleted and recreated in Keycloak, reimported from an external user directory, or when reinstalling/updating/switching Keycloak instances. - If the `sub` claim value changes for an existing user, Camunda Hub creates a new user record for this user in the database the next time the user logs in. - In this case, the user no longer sees any of the projects they previously had access to, because the project permissions are still assigned to the old user record. :::note The missing/orphaned projects and all contained files remain in the Camunda Hub database. ::: ## Resolution To restore workspace access for the affected users, users with the **Hub Admin** role already have the [access](/components/hub/organization/users-and-roles.md?edition=self-managed#elevated-workspace-access-self-managed) needed to reassign members to projects that have no members. --- ## Troubleshoot other problems Troubleshoot and resolve problems that are not covered by the other troubleshooting guides. ## Unable to create BPMN or DMN diagrams ### Issue When creating a new BPMN or DMN diagram via the Camunda Hub UI, the diagram creation request fails with a `HTTP 403 Forbidden` response. Other requests to the application succeed. ### Cause When your Camunda Hub installation is running behind a web application firewall (WAF), the firewall may block the request. Some WAF rules block requests that contain certain characters in the request body, such as XML payloads used in BPMN and DMN diagrams. ### Resolution Ensure that your WAF doesn't block requests to Camunda Hub. A rule that is known to interfere with Camunda Hub requests is AWS WAF's `CrossSiteScripting_Body` rule included in its [core rule set](https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-baseline.html#aws-managed-rule-groups-baseline-crs). Similar solutions like [ModSecurity](https://modsecurity.org/) may also block requests to Camunda Hub based on the used rule set. --- ## Troubleshoot proxy configuration issues Troubleshoot and resolve issues in Camunda Hub caused by incorrect or incomplete proxy configuration. ## Issue Users experience a variety of failures when Camunda Hub attempts to communicate with external services. These issues can manifest as: - Failed authentication due to the inability to access the JWKS (JSON Web Key Set) endpoint. Error message: "Expected 200 OK from the JSON Web Key Set HTTP response." - Failure to reach other external services, such as the Camunda Marketplace. ## Cause Proxy settings must be correctly configured for Camunda Hub to route outgoing requests through a network proxy. Common issues occur when: - The proxy server is not properly configured or unreachable. - Requests to external services are being blocked by the proxy configuration. - Authentication requests, such as those to the OIDC provider, fail when the JWKS endpoint is unreachable via the proxy. ## Resolution Ensure a correct proxy configuration for the `restapi` component. The proxy is configured with standard JVM system properties passed via the environment variable `JAVA_TOOL_OPTIONS`: ```properties JAVA_TOOL_OPTIONS=-Dhttps.proxyHost= -Dhttps.proxyPort= ``` --- ## Troubleshoot Zeebe connection issues You try to connect (i.e., to deploy) to a remote Zeebe cluster and Camunda Hub reports an error. To resolve this issue, check if you can connect to Zeebe through another client. If that doesn't work, resolve the general connection issue first (see [the platform deployment troubleshooting section](self-managed/operational-guides/troubleshooting.md), for example.) If that works, further debug your Zeebe connection with the help of the information stated below. Enabling [debug logging in `modeler-restapi`](#how-can-i-debug-log-grpc--zeebe-communication) may also help to understand the issue. ## Zeebe connection times out ### Increase the Zeebe client timeout Camunda Hub uses the [Zeebe Java client](/apis-tools/java-client/getting-started.md) to connect to Zeebe. Depending on your infrastructure, the default timeouts configured may be too short. You can pass custom timeouts in milliseconds for Camunda Hub's Zeebe client to `modeler-restapi` via three individual environment variables: ```shell CAMUNDA_CLIENT_REQUESTTIMEOUT=30000 # limit the time to wait for a response from the Zeebe Gateway CAMUNDA_AUTH_CONNECT_TIMEOUT=60000 # limit the time to wait for a connection to the OAuth server CAMUNDA_AUTH_READ_TIMEOUT=60000 # limits the time to wait for a response from the OAuth server ``` ## Secure connection to Zeebe fails If you provide a cluster URL starting with `https`, Camunda Hub will try to establish a secure connection to the Zeebe instance. In the process, it strictly validates the server's Application-Layer Protocol Negotiation (ALPN) support and its certificates presented against well-known certificate authorities. Failure to connect may have several reasons: ### Configure the gateway to accept secure connections Ensure you properly configure the remote cluster URL to accept secure connections. Refer to the [Zeebe Gateway configuration documentation](/self-managed/components/orchestration-cluster/zeebe/security/secure-client-communication.md#gateway) for additional information. ### Configure the gateway to support ALPN [Inspect the connection](#how-can-i-get-details-about-a-secure-remote-connection) to understand if ALPN is supported by the server. Secure connections to Zeebe require an Ingress controller that supports HTTP/2 over TLS with protocol negotiation via ALPN. Ensure you properly [configured your Zeebe Ingress to support ALPN](self-managed/operational-guides/troubleshooting.md#zeebe-ingress-grpc). ### Configure `modeler-restapi` to trust a custom Zeebe SSL certificate [Inspect the connection](#how-can-i-get-details-about-a-secure-remote-connection) to understand which certificates are being returned by the server and ensure you configure Camunda Hub for [custom SSL certificates](#how-can-i-provide-a-custom-zeebe-ssl-certificate). If intermediate signing authorities sign the server certificate, ensure the remote endpoint [serves both server and intermediate certificates](https://nginx.org/en/docs/http/configuring_https_servers.html#chains) to Camunda Hub. ### OAuth token cache for the `modeler-restapi` process When using the `OAuth` authentication method for deploying to Zeebe, Camunda Hub caches OAuth tokens in memory by default. No filesystem access is required, so running `modeler-restapi` as a non-root user (for example, via Kubernetes' `securityContext.runAsUser` option) or on a read-only container filesystem works out of the box. If you want to persist tokens across restarts of `modeler-restapi`, opt in to a file-based cache by setting the `CAMUNDA_CLIENT_CONFIG_PATH` environment variable to a writeable file location. If you are using legacy Zeebe client environment variables, you can also use `ZEEBE_CLIENT_CONFIG_PATH`. Ensure the directory exists and is writeable by the process user: ```shell CAMUNDA_CLIENT_CONFIG_PATH=/path/to/credentials/cache.txt ``` :::note Before Camunda 8.10, the file-based cache was enabled by default and pointed at `$HOME/.camunda/credentials`. Running as a non-root user without overriding this path caused `IOException`s on first cache write. If you previously set `CAMUNDA_CLIENT_CONFIG_PATH` or `ZEEBE_CLIENT_CONFIG_PATH` (legacy) solely to work around that error, you can now remove the variable and rely on the in-memory default. ::: ## How can I provide a custom Zeebe SSL certificate? You configured a custom SSL certificate in your (remote) Zeebe deployment and want Camunda Hub to accept that certificate. Camunda Hub strictly validates the remote server certificate trust chain. If you use a custom SSL server certificate, you must make the signing CA certificate known to Camunda Hub, not the server certificate itself. ### Provide the certificate via an environment variable `modeler-restapi` reads a trusted certificate from the environment variable `CAMUNDA_CA_CERTIFICATE_PATH`. This solution is recommended for most users: ```shell CAMUNDA_CA_CERTIFICATE_PATH=/path/to/certificate ``` The provided path has to be accessible from the `modeler-restapi` container (e.g. via a mounted volume). ### Provide the certificate to the JVM trust store Alternatively, you may pass a custom trust store to `modeler-restapi` via the environment variable `JAVA_TOOL_OPTIONS`: ```shell JAVA_TOOL_OPTIONS="-Djavax.net.ssl.trustStore=/path/to/truststore.jks -Djavax.net.ssl.trustStorePassword=changeit" ``` Analogous to above, the provided path has to be accessible from the `modeler-restapi` container (e.g. via a mounted volume). :::caution Be aware that passing a custom trust store via `JAVA_TOOL_OPTIONS` overrides the default JVM trust store. This means that all certificates shipped by default with the Java runtime (e.g. the Amazon Trust Services CA for connecting to a secured Aurora instance) are no longer trusted unless explicitly added. ::: ## How can I get details about a secure remote connection? You can use the following command to retrieve information about HTTP/2 over TLS support (ALPN) and certificates provided by a remote endpoint: ```shell > openssl s_client -alpn h2 -connect google.com:443 -servername google.com [...] --- Certificate chain 0 s:/CN=*.google.com i:/C=US/O=Google Trust Services LLC/CN=GTS CA 1C3 1 s:/C=US/O=Google Trust Services LLC/CN=GTS CA 1C3 i:/C=US/O=Google Trust Services LLC/CN=GTS Root R1 2 s:/C=US/O=Google Trust Services LLC/CN=GTS Root R1 i:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA --- [...] --- New, TLSv1/SSLv3, Cipher is AEAD-CHACHA20-POLY1305-SHA256 Server public key is 256 bit Secure Renegotiation IS NOT supported Compression: NONE Expansion: NONE ALPN protocol: h2 SSL-Session: Protocol : TLSv1.3 Cipher : AEAD-CHACHA20-POLY1305-SHA256 Session-ID: Session-ID-ctx: Master-Key: Start Time: 1687516295 Timeout : 7200 (sec) Verify return code: 0 (ok) --- ``` ## How can I debug log gRPC / Zeebe communication? You can also start `modeler-restapi` with gRPC debug logging turned on to get detailed [logging output](../configuration/logging.md) on communication to Zeebe: ```shell LOGGING_LEVEL_IO_GRPC=TRACE LOGGING_LEVEL_IO_CAMUNDA_MODELER=DEBUG ``` --- ## Usage metrics(Hub) With Camunda Hub, you can access usage metrics from Operate and Tasklist. The corresponding page allows you to select the time period and the clusters, and displays the subsequent consumption data. The same endpoints are queried as described [in the usage metrics documentation](/self-managed/components/orchestration-cluster/core-settings/concepts/usage-metrics.md). :::note To retrieve usage metrics, you must be running Tasklist or Operate. ::: --- ## Version upgrade Learn how Camunda Hub upgrades from one version to another. ## About For a single instance of the Hub application, a version upgrade—whether between patch or minor versions—involves the following steps: 1. The current version stops. 2. The new version starts. 3. The new version applies any migrations to the database schema. 4. HTTP traffic and background operations resume; the Hub application is fully functional again. For practical guidance on how to perform a Camunda version upgrade, consult the [upgrade guide](/self-managed/upgrade/index.md). ## Upgrade the application To upgrade the application, start the new version's executable, pointing it at the same database the old version used. :::info Camunda strongly recommends you [back up your database](/self-managed/operational-guides/backup-restore/modeler-backup-and-restore.md) before performing a version upgrade. If the migration does not complete successfully, you can use a backup to restore the previous version's data. ::: ## Database migration Camunda Hub uses [Flyway](https://www.red-gate.com/products/flyway/community/) to migrate its database schema from one version to another. It applies changes incrementally, determined automatically from the specific version gap. The migration is performed during application startup, before any HTTP traffic is served. You can monitor the applied changes and their progress in the application logs. For example: ```text [2026-09-11 15:05:05.794] [main] INFO org.flywaydb.core.internal.command.DbValidate - Successfully validated 167 migrations (execution time 00:00.116s) [2026-09-11 15:05:05.808] [main] INFO org.flywaydb.core.internal.command.DbMigrate - Current version of schema "public": 20260826.3 [2026-09-11 15:05:06.401] [main] INFO org.flywaydb.core.internal.command.DbMigrate - Migrating schema "public" to version "20260827.1 - create hub projects and backfill" [2026-09-11 15:05:06.427] [main] INFO org.flywaydb.core.internal.command.DbMigrate - Migrating schema "public" to version "20260827.2 - drop process applications" [2026-09-11 15:05:06.443] [main] INFO org.flywaydb.core.internal.command.DbMigrate - Migrating schema "public" to version "20260827.3 - add hub project id indexes" [non-transactional] [2026-09-11 15:05:06.460] [main] INFO org.flywaydb.core.internal.command.DbMigrate - Migrating schema "public" to version "20260827.4 - add migration diff indexes" [non-transactional] [2026-09-11 15:05:06.487] [main] INFO org.flywaydb.core.internal.command.DbMigrate - Migrating schema "public" to version "20260828 - drop milestones legacy pa version fk idx and columns" [2026-09-11 15:05:06.501] [main] INFO org.flywaydb.core.internal.command.DbMigrate - Successfully applied 33 migrations to schema "public", now at version v20260828 (execution time 00:00.380s) ``` In the Flyway logs, you'll see: 1. The initial schema version. 2. The applied migrations. 3. The outcome of the migration. Following these logs, you can review how far the migration has advanced and whether it completes successfully. If the migration fails, the application will exit and not serve any traffic. Since the migration runs during application startup, the [readiness and liveness probes](/self-managed/components/hub/monitoring.md#restapi) only report healthy after: 1. The migration has completed successfully. 2. The application has fully started. If the migration fails, the application exits before the probes become reachable at all. ### Duration If you employ automated liveness checks that can trigger application restarts, take into account that database migrations can run for a time that exceeds your liveness probe's timeout. This depends on the concrete version gap as well as the size and resources of your database. Camunda recommends performing the migration on a test system of similar specification first, so you can measure the actual migration duration and adjust your liveness probe timeout if needed. ### Database lock Only one application instance needs to perform the database migration. Once the schema is migrated, any number of instances can work with it. To prevent multiple application instances attempting the migration simultaneously, Flyway acquires a pessimistic database lock before starting the migration. If two application instances attempt the migration at the same time, the first one acquires the lock and begins the migration procedure. The second instance is blocked until the first instance has completed the migration and releases the lock. ## Rolling upgrades Camunda Hub database migrations are often backward compatible, meaning an old application version can temporarily continue working with the new database schema. Because of this, you can perform a rolling upgrade, where multiple instances of Hub are running in parallel and they migrate to the new version one after the other. With this strategy, instances continue serving traffic at all times. The [component upgrade guide](/self-managed/upgrade/components/index.md) informs whether the Hub upgrade between two specific minor versions requires downtime or can be performed in a rolling fashion. Review the guide before performing an upgrade between minor versions. If your upgrade spans multiple minor versions, consult the guide for all minor versions. For example, if you upgrade from version 8.8 to 8.10, read the upgrade guide from version 8.8 to 8.9 and the guide from version 8.9 to 8.10. --- ## How identity works in Camunda Camunda Self-Managed uses two separate identity subsystems. Understanding which subsystem controls what helps you avoid a common misconfiguration. If you connect your identity provider (IdP) to one system but not the other, some components authenticate while others don't. ## The two identity subsystems ```mermaid graph LR IdP["Your identity provider"] MI["Management Identity"] Admin["Admin"] Hub["Camunda Hub"] Opt["Optimize"] OC["Operate, Tasklist, Zeebe,Orchestration Cluster API"] IdP -->|"OIDC app registration 1"| MI IdP -->|"OIDC app registration 2"| Admin MI --> Hub MI --> Opt Admin --> OC ``` | Subsystem | Governs | Controls | | :---------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **[Management Identity](/self-managed/components/management-identity/overview.md)** | [Camunda Hub](/self-managed/components/hub/index.md), [Optimize](/self-managed/components/optimize/overview.md) | Who can log in, organization/project membership, role assignments | | **[Admin](/self-managed/components/orchestration-cluster/admin/overview.md)** | Operate, Tasklist, Zeebe, Orchestration Cluster API (per cluster) | Who can log in, role assignments, machine-to-machine (M2M) credentials, fine-grained resource-level authorizations (for example, access to specific process definitions, decisions, or tasks) | For the full breakdown, see [Admin vs Management Identity](/self-managed/reference-architecture/reference-architecture.md#admin-vs-management-identity). In most deployments, both subsystems share the same IdP. You create a separate OIDC application registration for each subsystem but manage users in one place at the IdP level. ## How identity changed in Camunda 8.8 Before Camunda 8.8, [Management Identity](/self-managed/components/management-identity/overview.md) (then called just Identity) managed access for every component, including Zeebe, Operate, and Tasklist. Camunda 8.8 split identity management into two subsystems. The [Orchestration Cluster](/self-managed/reference-architecture/reference-architecture.md#orchestration-cluster) began managing its own authentication and authorization through [Admin](/self-managed/components/orchestration-cluster/admin/overview.md) (formerly called Orchestration Cluster Identity). See [Identity, authentication, and authorization](/reference/announcements-release-notes/880/whats-new-in-88.md#identity) for the full migration details. Admin becomes the single source of truth for the migrated cluster's roles and authorizations after this change. Existing roles and authorizations carry over automatically during the upgrade, so nothing needs to be recreated. From that point on, manage access to Operate and Tasklist in Admin. Role or authorization changes made in Camunda Hub (then called Console) or Management Identity no longer apply to the migrated cluster. ## Configure the identity subsystems If you are deploying the full Camunda Self-Managed stack, you configure both subsystems, in this order: 1. **Management Identity first**: configure your IdP connection and verify users can log in to Camunda Hub. 2. **Admin second**: configure a separate IdP application registration and verify users can log in to Operate and Tasklist. If you are deploying only the Orchestration Cluster (Operate, Tasklist, Zeebe) without the management plane, you only need to configure Admin. ```mermaid flowchart TD Q1{"Deploying Camunda Hubor Optimize?"} Q2{"Deploying Operate, Tasklist,or using the Zeebe API?"} A["Configure Management Identity"] B["Skip Management Identity"] C["Configure Admin"] D["Skip Admin"] Q1 -->|Yes| A Q1 -->|No| B Q2 -->|Yes| C Q2 -->|No| D ``` For most full deployments, configure both Management Identity and Admin. If you deploy with the Helm chart and your IdP presents a certificate signed by a private or internal certificate authority, Camunda components don't trust certificates signed by that CA by default. This can cause `PKIX path building failed` errors. Configure [TLS trust](/self-managed/deployment/helm/configure/tls.md#external-oidc-issuer-with-private-ca) before connecting either subsystem to your IdP. ## Key terms | Term | Meaning | | :------------ | :------------------------------------------------------------------------------------------------------------------------------------------- | | Client ID | The identifier your IdP assigns to an OIDC application registration. Camunda needs one per subsystem. | | Client secret | The credential your IdP issues alongside the client ID. Treat it as a password. | | Issuer URL | The base URL of your IdP's OIDC authorization server. Camunda uses this to discover token and JWKS endpoints. | | Redirect URI | The URL Camunda registers with your IdP so the IdP knows where to send users after authentication. | | Scopes | The OIDC permission sets Camunda requests from your IdP (typically `openid profile email`). | | Claims | The fields in the ID token your IdP returns. Camunda reads specific claims (such as `sub`, `email`, `preferred_username`) to identify users. | | JWKS endpoint | The URL your IdP exposes to publish its public signing keys. Camunda uses this to validate token signatures. | ## Next steps --- ## Manage access and permissions With Management Identity, you can manage and control access to management and modeling component REST APIs and custom applications using permissions and roles. :::note This section describes how to manage access to Camunda Hub and Optimize. For access control to Orchestration Cluster components and their resources, refer to the [Orchestration Cluster authorizations](/components/concepts/access-control/authorizations.md) instead. ::: ## About permissions When using and managing permissions, it is important to understand the following key concepts: - APIs represent the different Camunda 8 management and modeling components, such as Hub, Optimize, and so on. - Each [API defines its own set of permissions](#permissions) to control API access. - Permissions are [organized using roles](./manage-permissions.md#manage-role-permissions) that can be assigned to users either directly or via Groups. - You can also [assign permissions to your custom application](./manage-permissions.md#manage-application-permissions), such as a job worker for example. :::tip For detailed instructions, see the [guide about managing permissions](./manage-permissions.md). ::: ## Permissions Each API (representing a component) defines its own set of permissions to control API access. The following permissions are available: | Component | API | Permissions available | | :------------------ | :--------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Management Identity | `Camunda Identity Resource Server` | `read`: Read access to Management Identity UI`read:users`: Access only the Users UI and related subpages.`write`: Write access to Management Identity UI. | | Optimize | `Optimize API` | `write:*`: Read and Write access to Optimize UI and APIs. | | Hub | `Hub Internal API` | `write:*`: Access to Hub UI, further managed by [workspace roles](/components/hub/organization/manage-workspaces/manage-workspace-members.md#workspace-roles).`admin:*`: Elevated access to Hub UI (see [organization admin and owner access](/components/hub/organization/users-and-roles.md?edition=self-managed#elevated-workspace-access-self-managed), [publishing connector templates](/components/connectors/manage-connector-templates.md#publish-a-connector-template), and accessing [cluster pages](/self-managed/components/hub/configuration/properties.md#clusters)).`admin:clusters`: Management access to Hub's [cluster pages](/self-managed/components/hub/configuration/properties.md#clusters) (granted to the `Console` and `DevOps` roles), without modeler-admin access.`admin:catalog`: Management access to the [catalog's](/components/hub/organization/manage-catalog/index.md) usage and adoption data (granted to the `Analyst` role), without modeler-admin access.`admin:bi`: Reserved for Hub business-intelligence features (granted to the `Analyst` role). Access to Optimize itself is granted separately by the Optimize permission below. | | Hub | `Hub API` | `create:*`: Access to `POST` endpoints of the [API](/apis-tools/web-modeler-api/index.md).`read:*`: Access to `GET` endpoints of the [API](/apis-tools/web-modeler-api/index.md).`update:*`: Access to `PATCH` and `PUT` endpoints of the [API](/apis-tools/web-modeler-api/index.md).`delete:*`: Access to `DELETE` endpoints of the [API](/apis-tools/web-modeler-api/index.md). | :::note Permissions granted to a user or M2M application are added to the `permissions.{audience}` claim of the access token. ::: --- ## Manage permissions This guide describes how to manage permissions for roles and applications in Management Identity: - You can grant permissions to either [roles](../application-user-group-role-management/manage-roles.md) or [applications](../application-user-group-role-management/applications.md). - When a permission is granted to a role, all users assigned to the role and members of a group with the assigned role, are granted the respective access. ## Manage role permissions ### Assign a permission to a role To assign a permission to a role, take the following steps: 1. Navigate to the **Roles** tab, click the role, and select **Permissions > Assign permissions**. 2. Select the API which contains the permission(s) you want to assign. 3. Select the permission(s) you would like to assign and click **Add**. On confirmation, the modal will close, the table will update, and your assigned permission will be shown. ### Delete a permission from a role To delete a permission from a role, take the following steps: 1. Navigate to the **Roles** tab. Click the role you would like to delete permissions from. 2. Navigate to the **Permissions** tab. 3. Click the trash icon next to the permission you want to remove. On confirmation, the modal will close, the table will update, and the assigned permission will be removed from the role. ## Manage application permissions ### Assign a permission to an application To assign a permission to an application using Identity, take the following steps: 1. Navigate to the **Applications** tab and click on an application. Then, select the **Access to APIs** tab and click **Assign Permissions**: ![assign-a-permission-application-tab](../img/assign-a-permission-application-tab.png) 2. Select the API which contains the permission you want to assign. 3. Select the permissions you would like to assign and click **Add**. On confirmation, the modal will close, the table will update, and your assigned permissions will be shown: ![assign-a-permission-application-refreshed-table](../img/assign-a-permission-application-refreshed-table.png) ### Delete a permission from an application To delete a permission from an application, take the following steps: 1. Navigate to the **Applications** tab. Click the application you would like to delete permissions from. 2. Navigate to the **Access to APIs** tab. 3. Click the trash icon next to the permission you want to remove. On confirmation, the modal will close, the table will update, and the assigned permission will be removed from the application. --- ## Manage applications In Management Identity, an application represents an entity that can request Management Identity to authenticate a user or a service for accessing management and modeling components (Camunda Hub and Optimize). ## About applications Camunda 8 provides a set of preconfigured applications. The following applications are managed by Management Identity: - Hub - Optimize - Management Identity As a Management Identity user you can also add your own custom applications. For example, you can provide a service with M2M access to management and modeling component REST APIs, such as a custom application that needs to access Optimize or Hub APIs. ## Application types To align with the [OAuth 2.0 standard](https://oauth.net/2/client-types/), Camunda distinguishes between _confidential_ and _public_ clients. Applications are also categorized by usage pattern, using the _M2M_ application type in Identity, for systems to communicate using _confidential_ clients without direct user interaction. - Confidential - Machine-to-machine (M2M) - Public The application type is selected when you [create an application](#add-an-application), based on its ability to securely store and use secrets, as well as the mode of authentication it uses. | Application type | Secret | User login flow | M2M authentication | | :--------------- | :----- | :-------------- | :----------------- | | Confidential | Yes | Yes | Yes | | M2M | Yes | No | Yes | | Public | No | Yes | No | :::info - To learn more about OAuth client types, refer to [OAuth 2.0 Client Types](https://oauth.net/2/client-types/), - To learn more about confidential and public applications, refer to [confidential and public applications](https://auth0.com/docs/get-started/applications/confidential-and-public-applications). ::: ## Manage applications ### Add an application 1. Log in to the Identity interface and navigate to the **Applications** tab: ![add-application-tab](../img/add-application-tab.png) 2. Click the **Add application** button located on the top right of the table and a modal will open. 3. Enter a name for your application. In this guide we will use a set of example values. Select the type of your application based on our [guide](/self-managed/components/management-identity/application-user-group-role-management/applications.md#application-types). Depending on the selected type, you might need to enter at least one redirect URI. Once you have entered the required details, click **Add**: 4. On confirmation, the modal will close, and the list updates to show your new application. Click on your new application to view the details. This includes your generated client ID and client secret depending on the selected [application type](/self-managed/components/management-identity/application-user-group-role-management/applications.md#application-types). ![add-application-refreshed-table](../img/add-application-refreshed-table.png) ## Application permissions You can control the access an application has to Camunda 8 management and modeling components by [assigning permissions](/self-managed/components/management-identity/access-management/manage-permissions.md#assign-a-permission-to-an-application) to the application. :::info For an overview of permissions available, see [available permissions](/self-managed/components/management-identity/access-management/access-management-overview.md#permissions). ::: --- ## Manage users, groups, roles, and applications Manage and organize access to Camunda 8 management and modeling components ([Camunda Hub](../../hub/index.md) and [Optimize](../../optimize/overview.md)) using users, groups, roles, and applications. ## About managing users in Management Identity In Management Identity, a user represents a human who interacts with Camunda 8 management and modeling components. - You do not create or manage users in Management Identity itself. Users are managed in Keycloak or your connected IdP. - Management Identity allows you to organize and manage access to the components for these users with groups and roles. ## Groups Use groups to manage user access by organizing users into groups (group members) and assigning roles. Instead of assigning roles to individual users, map a set of roles to a group, with all group members automatically inheriting the role permissions. - [Manage Groups](./manage-groups.md) ## Roles Use roles to simplify and standardize access control across your system, help enforce consistent permission sets, reduce errors, and scale access management as your organization grows. Roles define the actions a user or application can perform in Camunda 8 management and modeling components by grouping together a set of related [permissions](../access-management/manage-permissions.md). You can assign roles [directly to a user](manage-roles.md#assign-a-role-to-a-user) or [using groups](/self-managed/components/management-identity/application-user-group-role-management/manage-groups.md#assign-roles-to-a-group). - [Manage roles](./manage-roles.md) ## Applications In Management Identity, an application represents an entity that can request Management Identity to authenticate a user or a service for accessing management and modeling components. Camunda 8 has a set of preconfigured applications, but as a user of Management Identity you can also add your own applications. For example, you can provide a service with M2M access to management and modeling component APIs, such as a custom application that needs to access Optimize or Hub APIs. - [Manage applications](./applications.md) --- ## Groups(Application-user-group-role-management) Use groups to manage and organize user access to Camunda 8 management and modeling components (Camunda Hub and Optimize). ## About groups Organize your users into groups as an efficient and scalable way of controlling access to Camunda 8 management and modeling components. - Instead of assigning roles to individual users, [map a set of roles to a group](#assign-roles-to-a-group). - All group members automatically inherit the role permissions of a group. :::note You do not create or manage users in Management Identity itself. Users are managed in Keycloak or your connected IdP. ::: ## Manage groups ### Create a group 1. Navigate to the **Groups** tab. 2. Click the **Create group** button located on the top right of the table and a modal will open. 3. Enter the group name and click **Create group**. On confirmation, the modal closes, the table updates, and your new group is shown. ### Modify or delete a group 1. Navigate to the **Groups** tab. 2. Use the pencil icon to edit the group or the trash icon to remove a group. ## Manage group users ### Assign users to a group 1. Navigate to the **Groups** tab. Select the group you want to assign a member to from the table. 2. Click **Assign members** and a modal will open. 3. Search and select the member to assign to the group. After selecting the member, click **Assign**. On confirmation, the modal closes, the table updates, and your assigned members are shown. ### Remove a user from a group 1. Navigate to the **Groups** tab. 2. Click the trash icon next to the member you want to remove from the group. On confirmation, the modal closes, the table updates, and your member is removed from the group. ## Manage group roles ### Assign roles to a group 1. Navigate to the **Groups** tab. Select the group you want to assign a role to from the table, and click on the **Roles** tab. 2. Click **Assign roles** and a modal will open. 3. Select the roles to assign to the group. When you have selected the roles, click **Add**. On confirmation, the modal closes, the table updates, and your assigned roles are shown. ### Remove a role from a group 1. Navigate to the **Groups** tab. Select the group. 2. Navigate to the **Roles** tab. 3. Click the trash icon next to the role you want to remove from the group. On confirmation, the modal closes, the table updates, and your role is removed from the group. --- ## Manage roles Use Management Identity roles to simplify and standardize access control for Camunda 8 management and modeling components (Camunda Hub and Optimize). ## About roles Roles help you to enforce consistent permission sets, reduce errors, and scale access management as your organization grows. - Roles define the actions a user or application can perform in Camunda 8 management and modeling components by grouping together a set of related [permissions](../access-management/manage-permissions.md). - You can assign roles [directly to a user](#assign-a-role-to-a-user) or [using groups](../application-user-group-role-management/manage-groups.md#assign-roles-to-a-group). ## Default roles Management Identity includes a set of default roles that are available out-of-the-box. These roles are designed to cover common use cases and can be assigned to users and groups to grant them access to different management and modeling components. The following table lists the default roles and their descriptions. | Name | Description | Notes | | :------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------- | | Default user role | The role does not grant any permissions by default. It is applied to all users, including service accounts. | | | Management Identity | Provides full access to [Management Identity](../overview.md). | | | DevOps | Grants management access to Hub's cluster pages, without modeler-admin or people/org management access. | Previously known as `Console`. | | Analyst | Grants access to Hub for creating and collaborating on projects, management access to the catalog's usage and adoption data, and full access to [Optimize](../../optimize/overview.md), without modeler-admin or people/org management access. | | | Optimize | Grants full access to [Optimize](../../optimize/overview.md). | | | Hub | Grants access to Hub for creating and collaborating on projects. | Previously known as `Web Modeler`. | | Hub Admin | Grants full access to Hub, including all projects and the ability to manage workspace members. | Previously known as `Web Modeler Admin`. | `DevOps`, `Hub`, and `Hub Admin` were previously known as `Console`, `Web Modeler`, and `Web Modeler Admin`. The earlier roles still exist and are kept for backward compatibility. They grant the same permissions as the new roles, so existing assignments keep working without reassignment. See [Management Identity roles and permissions](../../../upgrade/components/890-to-8100.md#management-identity-roles-and-permissions) in the 8.9 to 8.10 upgrade guide for why both exist. ## Add a role To add a role, take the following steps: 1. Navigate to the **Roles** tab. 2. Click the **Add role** button located on the top right of the table and a modal will open to enter the name and description. On confirmation, the modal will close, the table will update, and the new role will be shown. ## Delete a role Roles can be deleted in two ways: through the trash button in the table view, or in the overflow menu once the role is selected. 1. Navigate to the **Roles** tab. 2. Search for a role by clicking the magnifying glass next to **Add role**. 3. Click the trash button next to the role or click the role, click the overflow menu, then **Delete**. On confirmation, the modal will close, the table will update, and the role will be removed. ## Assign a role to a user To assign a role to a user, take the following steps: 1. Navigate to the **Users** tab. 2. Click on the user you want to assign a role to view their details. 3. Click on **Assigned roles** to view the roles currently assigned to the user. 4. Click the **Assign roles** button located on the top right of the table and a modal will open. 5. Select the role you want to assign to the user and click **Add**. On confirmation, the modal will close, the table will update, and the newly assigned role will be shown for the user. ## Delete an assigned role from a user To delete an assigned role from a user, take the following steps: 1. Navigate to the **Users** tab. 2. Click on the user you want to remove a role from to view their details. 3. Click on **Assigned roles** to view the roles currently assigned to the user. 4. Click the trash icon next to the role. On confirmation, the modal will close, the table will update, and the role will be removed from the user. --- ## Authentication(Management-identity) Depending on your [configuration](/self-managed/components/management-identity/configuration/identity-configuration-overview.md), users and applications authenticate with Camunda 8 via the IdP using the [OAuth 2.0 protocol](https://oauth.net/2/), using either a login page or [M2M tokens](#m2m-machine-to-machine-authentication). ## Login page authentication When a user accesses a Camunda 8 web application, they are shown the login page provided by the configured IdP. ## M2M (machine-to-machine) authentication Applications can authenticate with Camunda 8 using M2M (machine-to-machine) tokens. ### Prerequisites The following prerequisites are required for M2M authentication: | Prerequisite | Description | | :----------------------------------------------------------------------------------------------------------------- | :------------------------------------- | | [Management Identity](/self-managed/components/management-identity/overview.md) | A running Management Identity service. | | [Application](/self-managed/components/management-identity/application-user-group-role-management/applications.md) | An application for your service. | | Client ID | The client ID of your application. | | Client secret | The client secret of your application. | | REST client | A REST client of your choice. | ### Generate a token In the following example, the Keycloak instance that supports Management Identity can be found via `http://localhost:18080`. This might be different for your Management Identity configuration, adjust the host name (and port if required) if required. To request a token, use the following cURL command, replacing the placeholders with your application details: ``` 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' ``` The following shows an example successful authentication response: ``` { "access_token": "", "expires_in": 300, "refresh_expires_in": 0, "token_type": "Bearer", "not-before-policy": 0 } ``` You can use the token to authenticate an application with Camunda components, for example to access Operate REST APIs. --- ## Use an alternative database for Management Identity Use an alternative database for Management Identity if your internal policies or compliance requirements prevent the use of PostgreSQL. ## Database versions Identity is tested against the following alternative relational databases: | Camunda version | Database version | Driver version | | --------------- | ---------------- | -------------- | | 8.7.1 | Oracle 19C | 21.3.0.0 | | 8.7.1 | SQL Server 2019 | 12.10.0.jre11 | ## Oracle database configuration ### Driver provision As the Oracle driver is not provided by default in each of the Camunda 8 distributions, you must download the driver and supply it for the application to load. 1. Download the appropriate Oracle driver and save it as `ojdbc.jar`: https://download.oracle.com/otn-pub/otn_software/jdbc/237/ojdbc17.jar. 2. When starting the application, set `-cp "/app/ojdbc.jar:/app/identity.jar"` in the `java` command during startup. This is only required for Oracle. 3. If you are using docker or kubernetes, ensure that the folder with the library is properly mounted as a volume. ```sh SPRING_DATASOURCE_URL="jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=${IDENTITY_DATABASE_HOST:})(PORT=${IDENTITY_DATABASE_PORT:}))(CONNECT_DATA=(SERVICE_NAME=${IDENTITY_DATABASE_NAME:}))(SECURITY=(SSL_SERVER_CERT_DN=\"CN={CERT_CN}, O={CERT_ORG},L={..},ST={..},C={..}\")))" SPRING_DATASOURCE_DRIVER_CLASS_NAME=oracle.jdbc.OracleDriver SPRING_JPA_DATABASE=oracle JAVA_TOOL_OPTIONS=$JAVA_OPTS ``` ```yaml identity: externalDatabase: enabled: true # These three configuration options are added so that spring knows to connect to oracledb using it's client library env: - name: SPRING_DATASOURCE_URL value: 'jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=${IDENTITY_DATABASE_HOST:})(PORT=${IDENTITY_DATABASE_PORT:}))(CONNECT_DATA=(SERVICE_NAME=${IDENTITY_DATABASE_NAME:}))(SECURITY=(SSL_SERVER_CERT_DN="CN={CERT_CN}, O={CERT_ORG},L={..},ST={..},C={..}")))' - name: SPRING_DATASOURCE_DRIVER_CLASS_NAME value: oracle.jdbc.OracleDriver - name: JAVA_TOOL_OPTIONS value: $JAVA_OPTS - name: SPRING_JPA_DATABASE value: oracle # Overriding identity.command is required so that the new driver in /extraDrivers will be loaded upon startup. command: - /bin/sh - -c - | java -cp "/extraDrivers/ojdbc.jar:/app/identity.jar" org.springframework.boot.loader.launch.JarLauncher # Extra volumes are mounted for any TLS certs necessary for the database: extraVolumeMounts: - name: "keystore-secret" mountPath: "/usr/local/certificates" - name: jdbcdrivers mountPath: /extraDrivers extraVolumes: - name: "keystore-secret" secret: secretName: "keystore-secret" - name: jdbcdrivers emptyDir: {} initContainers: - name: fetch-jdbc-drivers image: alpine:3.19 imagePullPolicy: "Always" command: [ "sh", "-c", "wget https://download.oracle.com/otn-pub/otn_software/jdbc/237/ojdbc17.jar -O /extraDrivers/ojdbc.jar", ] volumeMounts: - name: jdbcdrivers mountPath: /extraDrivers securityContext: runAsUser: 1001 ``` ```yaml spring: datasource: url: jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=${IDENTITY_DATABASE_HOST:})(PORT=${IDENTITY_DATABASE_PORT:}))(CONNECT_DATA=(SERVICE_NAME=${IDENTITY_DATABASE_NAME:}))(SECURITY=(SSL_SERVER_CERT_DN=\"CN={CERT_CN}, O={CERT_ORG},L={..},ST={..},C={..}\"))) driver-class-name: oracle.jdbc.OracleDriver jpa: database: oracle ``` ## MSSQL database configuration ### Driver provision As the driver for MSSQL is provided by default in identity, you do not need to download it or supply it in the classpath. ```sh SPRING_DATASOURCE_URL="jdbc:sqlserver://${IDENTITY_DATABASE_HOST:}:${IDENTITY_DATABASE_PORT:};databaseName=${IDENTITY_DATABASE_NAME:};encrypt=true;hostNameInCertificate={CACERT_/CN};trustServerCertificate=false" SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.microsoft.sqlserver.jdbc.SQLServerDriver SPRING_JPA_DATABASE=sql_server JAVA_TOOL_OPTIONS=$JAVA_OPTS ``` ```yaml identity: externalDatabase: enabled: true # These configuration options are added so that Spring knows how to connect to SQL Server env: - name: SPRING_DATASOURCE_URL value: "jdbc:sqlserver://${IDENTITY_DATABASE_HOST:}:${IDENTITY_DATABASE_PORT:};databaseName=${IDENTITY_DATABASE_NAME:};encrypt=true;hostNameInCertificate={CACERT_/CN};trustServerCertificate=false" - name: SPRING_DATASOURCE_DRIVER_CLASS_NAME value: com.microsoft.sqlserver.jdbc.SQLServerDriver - name: SPRING_JPA_DATABASE value: sql_server - name: JAVA_TOOL_OPTIONS value: $JAVA_OPTS # Extra volumes are mounted for any TLS certs necessary for the database: extraVolumeMounts: - name: "keystore-secret" mountPath: "/usr/local/certificates" extraVolumes: - name: "keystore-secret" secret: secretName: "keystore-secret" ``` ```yaml spring: datasource: url: jdbc:sqlserver://${IDENTITY_DATABASE_HOST:}:${IDENTITY_DATABASE_PORT:};databaseName=${IDENTITY_DATABASE_NAME:};encrypt=true;hostNameInCertificate={CACERT_/CN};trustServerCertificate=false username: user password: driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver jpa: database: sql_server ``` ## Troubleshooting The following troubleshooting tips are provided to help you with common issues: | Tip | Description | | :----------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Check Keystore path | Access (or "exec into") the running container where the application is deployed and confirm that the Java process running inside the container is configured with the correct keystore path. | | Check certificates | Confirm that any SSL/TLS certificate required for secure communication with the database exists in the mounted location on the filesystem. | | Test database connection | Test and verify the connection from the pod to the database using simple tools and utilities, such as JDBC tool, ping, curl, and so on. | --- ## Configure an external IdP using Keycloak If you are using the default (built-in) Keycloak you can configure an external identity provider (IdP) for user authentication, such as OpenID Connect, SAML, LDAP, or Active Directory. :::info Deploying with Helm? If you deploy Camunda 8 Self-Managed with Helm, use the [Helm chart guide for configuring an external IdP with Keycloak](/self-managed/deployment/helm/configure/authentication-and-authorization/internal-keycloak.md). ::: ## Configuration steps 1. Log in to the Keycloak Administrator Console. Open the URL you have configured for Keycloak in your browser. :::tip When using the example [Docker Compose](/self-managed/quickstart/developer-quickstart/docker-compose.md) setup, Keycloak is available at [http://localhost:18080/](http://localhost:18080/). ::: 2. Click **Administrator Console** and log in using the Keycloak administrator credentials. - The default administrator username is `admin`. - When deploying Camunda 8 with [Helm charts](/self-managed/setup/overview.md), the Keycloak operator stores the administrator credentials in the `keycloak-initial-admin` secret. Extract the password with `kubectl get secret keycloak-initial-admin --namespace -o jsonpath='{.data.password}' | base64 -d`. For details, see [deploy Keycloak and connect Camunda](/self-managed/deployment/helm/configure/authentication-and-authorization/internal-keycloak.md#deploy-keycloak-and-connect-camunda). - Using the example [Docker Compose](/self-managed/quickstart/developer-quickstart/docker-compose.md) setup, the password is set via `KEYCLOAK_ADMIN_PASSWORD` environment variable and is `admin` per default. 3. Select the realm you are using with Camunda 8. By default, this is **Camunda-platform**. ![keycloak-realm-select](../img/keycloak-admin-realm-select.png) 4. Add an identity provider using either of the following methods: - To add an OpenID Connect or SAML provider, select **Identity Providers** in the main menu, click **Add provider...**, and enter all the required configuration settings. ![keycloak-add-identity-provider](../img/keycloak-add-identity-provider.png) - To connect to your LDAP, Active Directory, or Kerberos server, select **User Federation** in the main menu, click **Add provider...**, and fill in all required configuration settings. ![keycloak-add-user-federation](../img/keycloak-add-user-federation.png) :::tip Keycloak supports a wide variety of authentication options, such as mapping external user groups, roles, or scopes to internal roles, and configuring the login screen and flow when multiple providers are added. Visit the Keycloak documentation for your version of Keycloak for details on [adding a provider](https://www.keycloak.org/docs/latest/server_admin/#adding-a-provider), [configuring authentication](https://www.keycloak.org/docs/latest/server_admin/index.html#configuring-authentication_server_administration_guide), and [integrating identity providers](https://www.keycloak.org/docs/latest/server_admin/index.html#_identity_broker). ::: --- ## Connect to an existing Keycloak instance This guide describes how to connect Management Identity to your existing Keycloak instance. :::info Deploying with Helm? If you deploy Camunda 8 Self-Managed with Helm, use the [Helm chart guide for connecting to an external Keycloak instance](/self-managed/deployment/helm/configure/authentication-and-authorization/external-keycloak.md) instead. ::: ## Prerequisites - Access to your [Keycloak Admin Console](https://www.keycloak.org/docs/latest/server_admin/#using-the-admin-console) - A basic understanding of [administering realms and clients](https://www.keycloak.org/docs/latest/server_admin/#assembly-managing-clients_server_administration_guide) in Keycloak :::note Clients in Camunda 8 SaaS and applications in Camunda 8 Self-Managed serve a similar purpose. One key difference is that for Camunda 8 SaaS, you can set up specific [client connection credentials](/components/saas/clusters/manage-api-clients.md#create-a-client), whereas in Management Identity, an application is created with credentials automatically assigned. ::: ## Steps :::caution Keycloak URLs As of version 8.5.3, Management Identity uses the Keycloak frontend URL instead of the backend URL. This change may affect you if the frontend URL is blocked from other services (including Camunda applications) and could impact Management Identity functionality. To avoid connectivity issues, ensure your Keycloak frontend URL is accessible by adjusting your network, firewall, or security settings as needed. This adjustment is crucial to maintain the integration with Keycloak and ensure compatibility. ::: To connect Management Identity to an existing Keycloak instance, take the following steps for your Camunda installation. ### Prepare an existing Keycloak realm Management Identity can either create a Keycloak realm called `camunda-platform` with all settings from scratch, or it can use an already existing Keycloak realm. If you would like to use an existing Keycloak realm, prepare following the steps below. Otherwise you can skip this section. 1. Log in to your Keycloak Admin Console. 1. Select the realm you want to connect Management Identity to (for example, **camunda-platform**). ![keycloak-admin-realm-select](../img/keycloak-admin-realm-select.png) :::warning Management Identity only supports Keycloak realms where the realm name and realm ID are the same value. This is not the case for realms created through the Keycloak UI (where the ID becomes a generated value). You can specify both name and ID when using [Keycloak's JSON import feature](https://www.keycloak.org/server/importExport). ::: 1. In the navigation menu, select **Clients**, then click **Create**. 1. Enter a client ID and click **Next**. :::note What client ID should I use? By default, Management Identity uses the client ID `camunda-identity`. If you use a different ID, set it in the Management Identity application [environment variables](/self-managed/components/management-identity/miscellaneous/configuration-variables.md). ::: ![keycloak-admin-client-add-1](../img/keycloak-admin-client-add-1.png) 1. Turn **Client authentication** on, select **Service accounts roles**, ensure **Authorization** is off, and click **Next**. ![keycloak-admin-client-add-2](../img/keycloak-admin-client-add-2.png) 1. In the **Root URL** field, enter the URL where your Management Identity instance will be hosted, then click **Save**. ![keycloak-admin-client-add-3](../img/keycloak-admin-client-add-3.png) 1. Open the created client, then select the **Service account roles** tab. ![keycloak-admin-client-update-1](../img/keycloak-admin-client-update-1.png) 1. Click **Assign role**, then change the filter to **Filter by clients**. ![keycloak-admin-client-update-2](../img/keycloak-admin-client-update-2.png) 1. Select the `manage-clients`, `manage-realm`, and `manage-users` roles, then click **Assign**. :::note Why does Management Identity need these roles? Management Identity allows users to manage entities related to Camunda. These roles provide the necessary access to the Keycloak realm. ::: 1. Open the **Credentials** tab and copy the client secret. ### Configure and start the application Configure Management Identity with the following environment variables: 1. `IDENTITY_CLIENT_ID`: The ID of the client you created (or `camunda-identity` if you let Management Identity create it automatically). 1. `KEYCLOAK_REALM`: The realm name you want to use (or `camunda-platform` if using the default). 1. `KEYCLOAK_SETUP_USER`: The username of an administrative Keycloak user. 1. `KEYCLOAK_SETUP_PASSWORD`: The password for the administrative user. :::tip If you use a non-default realm, you need to set additional Keycloak-specific variables. See [environment variables](/self-managed/components/management-identity/miscellaneous/configuration-variables.md) for details. ::: Start the Management Identity application. :::note What does Management Identity create when starting? When starting, Management Identity creates a base configuration required for operation. See [starting configuration](/self-managed/components/management-identity/miscellaneous/starting-configuration.md) for details. ::: :::tip Helm chart setup To run a full Camunda cluster with an existing Keycloak instance, see [Helm chart setup for existing Keycloak](/self-managed/deployment/helm/configure/authentication-and-authorization/external-keycloak.md#create-a-secret). ::: ## Considerations When connecting Management Identity to a shared realm, accurately determining what clients should and should not be displayed in the UI is not possible. Therefore, the clients in the realm you connect Management Identity to will be shown in the UI and can have their secrets viewed and updated. Users with access to Management Identity should be considered as having administrator-level access to the system. --- ## Connect Management Identity to an identity provider Configure Management Identity for your Camunda 8 Self-Managed deployment. This guide covers application-level configuration, including environment variables and IdP settings. :::note - A full list of supported and unsupported features when using an OIDC provider is available in the [OIDC features table](#supported-and-unsupported-oidc-features). - To connect to a Keycloak authentication provider, see [connect to an existing Keycloak instance](/self-managed/components/management-identity/configuration/connect-to-an-existing-keycloak.md). ::: :::info Deploying with Helm? If you deploy Camunda 8 Self-Managed with Helm, use the [Helm chart authentication and authorization guides](/self-managed/deployment/helm/configure/authentication-and-authorization/index.md) to configure OIDC and Management Identity. ::: ## Prerequisites - Information about your OIDC provider's configuration, including the issuer URL. - Ability to create applications in your OIDC provider. - Ability to access the following information about the applications you have created in your OIDC provider: - Client ID - Client secrets - Audience - A [claim name and value](/self-managed/components/management-identity/miscellaneous/configuration-variables.md#oidc-configuration) to use for initial access. - Your OIDC provider must issue access tokens that contain an `aud` (audience) claim matching the configured audience, as Camunda validates this claim for auth flows. Providers that are not configured to emit, or do not emit, the `aud` claim in their access tokens are not supported. Configure your identity provider to emit this claim if supported. See [known limitations](#oidc-provider-known-limitations) for details. - A relational database that Identity can connect to. The database is required regardless of feature flags. See [database configuration](/self-managed/components/management-identity/miscellaneous/configuration-variables.md#database-configuration) for connection details and supported databases. :::note The steps below are a general approach for the Camunda components; it is important you reference the [component-specific configuration](#component-specific-configuration) to ensure the components are configured correctly. ::: ## Configuration Upgrading from Camunda 8.9? Console's cluster-management pages are now part of the Hub UI application, so a separate OIDC application/client for Console is no longer needed. See [Management Identity roles and permissions](/self-managed/upgrade/components/890-to-8100.md#management-identity-roles-and-permissions) in the 8.9 to 8.10 upgrade guide. Steps 1. Identify what management and modeling components you need to use in Camunda 8: [Camunda Hub](../../hub/index.md) and [Optimize](../../optimize/overview.md). 2. In your OIDC provider, **create an application for each of the management and modeling components you want to connect**. Hub requires two applications: one for the UI, and one for the API. - The expected redirect URI of the component you are configuring an app for can be found in [component-specific configuration](#component-specific-configuration). :::note Redirect URIs serve as an approved list of destinations across identity providers. Only the URLs specified in the redirect URIs configuration will be permitted as valid redirection targets for authentication responses. This security measure ensures that tokens and authorization codes are only sent to pre-approved locations, preventing potential unauthorized access or token theft. ::: 3. For each management and modeling components, ensure the appropriate application type is used: - Web applications requiring confidential access/a confidential client: - **Optimize** - **Management Identity** - **Hub API** - Web applications requiring public access/a public client: - **Hub UI** 4. Make a note of the following values for each application you create: - Client ID - Client secret - Audience 5. Set the following environment variables or Helm values for the component you are configuring an app for: :::note You can connect to your OIDC provider through either environment variables or Helm values. Ensure only one configuration option is used. ::: ```yaml camunda: identity: type: "GENERIC" baseUrl: authUrl: tokenUrl: jwksUrl: clientId: clientSecret: audience: identity: initialClaimName: initialClaimValue: spring: profiles: active: oidc ``` ``` CAMUNDA_IDENTITY_TYPE=GENERIC CAMUNDA_IDENTITY_BASE_URL= CAMUNDA_IDENTITY_ISSUER= # Used for container to container communication CAMUNDA_IDENTITY_ISSUER_BACKEND_URL= CAMUNDA_IDENTITY_CLIENT_ID= CAMUNDA_IDENTITY_CLIENT_SECRET= CAMUNDA_IDENTITY_AUDIENCE= IDENTITY_INITIAL_CLAIM_NAME= IDENTITY_INITIAL_CLAIM_VALUE= SPRING_PROFILES_ACTIVE=oidc ``` :::note Once set, you cannot update your initial claim name and value using environment variables or Helm values. You must change these values directly in the database. ::: Additional considerations For authentication, the Camunda components use the scopes `email`, `openid`, `offline_access`, and `profile`. :::tip Optional scopes The `offline_access` scope is optional. If this scope is included, your OIDC provider issues a refresh token to Management Identity on user login. Management Identity uses the refresh token to renew the user's access token when it expires, so that sessions remain active without requiring the user to log in again. If `offline_access` is not included, users will be redirected to the OIDC provider for re-authentication whenever their access token expires. For more information, see the [OpenID Connect Core specification](https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess). If your organization restricts this scope for security reasons, you can adjust the scopes with: ``` CAMUNDA_IDENTITY_AUTH_SCOPES="openid profile email" ``` This configuration allows login without the `offline_access` scope. ::: Steps :::note Ensure you register a new application for each component. ::: 1. Identify what management and modeling components you need to use in Camunda 8: [Camunda Hub](../../hub/index.md) and [Optimize](../../optimize/overview.md). 2. Within the Entra ID admin center, [register a new application](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app) for **each component you would like to connect**. Hub requires two applications: one for the UI, and one for the API. 3. Navigate to the new application's **Overview** page, and make note of the **Client ID**. This will also be used as the audience ID. 4. Within your new application, [configure a platform](https://learn.microsoft.com/en-gb/entra/identity-platform/quickstart-register-app#configure-platform-settings) for the appropriate component: - **Web**: - Optimize - Management Identity - Hub API - **Single-page application**: - Hub UI 5. Add your component's **Microsoft Entra ID** redirect URI, found under [Component-specific configuration](#component-specific-configuration). :::note Redirect URIs serve as an approved list of destinations across identity providers. Only the URLs specified in the redirect URIs configuration will be permitted as valid redirection targets for authentication responses. This security measure ensures that tokens and authorization codes are only sent to pre-approved locations, preventing potential unauthorized access or token theft. ::: 6. [Create a new client secret](https://learn.microsoft.com/en-gb/entra/identity-platform/quickstart-register-app?tabs=client-secret#add-credentials), and note the new secret's value for later use. The secret ID is not needed, only the secret value is required. 7. Set the following environment variables or Helm values for the component you are configuring an app for: :::note You can connect to your OIDC provider through either environment variables or Helm values. Ensure only one configuration option is used. ::: ```yaml camunda: identity: type: "MICROSOFT" baseUrl: authUrl: https://login.microsoftonline.com//v2.0 tokenUrl: https://login.microsoftonline.com//v2.0 clientId: clientSecret: audience: identity: initialClaimName: initialClaimValue: spring: profiles: active: oidc ``` ``` CAMUNDA_IDENTITY_TYPE=MICROSOFT CAMUNDA_IDENTITY_BASE_URL= CAMUNDA_IDENTITY_ISSUER=https://login.microsoftonline.com//v2.0 CAMUNDA_IDENTITY_ISSUER_BACKEND_URL=https://login.microsoftonline.com//v2.0 CAMUNDA_IDENTITY_CLIENT_ID= CAMUNDA_IDENTITY_CLIENT_SECRET= CAMUNDA_IDENTITY_AUDIENCE= IDENTITY_INITIAL_CLAIM_NAME= IDENTITY_INITIAL_CLAIM_VALUE= SPRING_PROFILES_ACTIVE=oidc ``` :::danger Once set, your initial claim name and value cannot be updated using environment variables or Helm values, and must be changed directly in the database. ::: Additional considerations Due to technical limitations regarding [third party content](https://openid.net/specs/openid-connect-frontchannel-1_0.html#ThirdPartyContent), front channel single sign out is not supported. This means that when a user logs out of one component, they will not be logged out of the other components. For authentication, the management and modeling components use the scopes `email`, `openid`, `offline_access`, `profile`, and `/.default`. To ensure your users are able to successfully authenticate with Entra ID, you must ensure that either there is an [admin consent flow configured](https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-admin-consent-workflow) or grant consent on behalf of your users using the [admin consent](https://learn.microsoft.com/en-gb/entra/identity/enterprise-apps/user-admin-consent-overview#admin-consent) process. The client should be configured to support `grant_type`: - To **create** an M2M token, the `client_credentials` grant type is required. The response contains an access token. - To **renew** a token using a refresh token, the `refresh_token` grant type is required. - To **create** a token via authorization flow, the `authorization_code` grant type is required. The response contains both access and refresh tokens. To successfully authenticate with Entra ID, you should use the `v2.0` API. This means that the `CAMUNDA_IDENTITY_ISSUER_BACKEND_URL` value should end with `/v2.0`. Follow the [Microsoft Entra instructions](https://learn.microsoft.com/en-us/entra/identity-platform/reference-microsoft-graph-app-manifest#configure-the-app-manifest-in-the-microsoft-entra-admin-center) to configure the app manifest, and set the [requestedAccessTokenVersion](https://learn.microsoft.com/en-us/entra/identity-platform/reference-microsoft-graph-app-manifest#api-attribute) under `Api:` to `2`: ```json "requestedAccessTokenVersion": 2, ``` ### Component-specific configuration | Component | Redirect URI | Notes/Limitations | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Management Identity | **Microsoft Entra ID:** `https:///auth/login-callback` **Helm:** `https://` | | | Optimize | **Microsoft Entra ID:** `https:///api/authentication/callback` **Helm:** `https://` | There is a fallback if you use the existing environment variables to configure your authentication provider. If you use a custom `yaml`, update your properties to match the new values in this guide.When using an OIDC provider, the following Optimize features are not currently available: - The **User permissions** tab in collections- The **Alerts** tab in collections- Digests- Accessible usernames for owners of resources (the `sub` claim value is displayed instead). | | Hub | **Microsoft Entra ID:** `https:///login-callback` **Helm:** `https://` | Hub requires two clients: one for the UI, and one for the API. Required configuration variables for the `restapi` component: `CAMUNDA_HUB_OAUTH2_CLIENTID=[ui-client-id]` `CAMUNDA_IDENTITY_BASEURL=[identity-base-url]` `CAMUNDA_IDENTITY_TYPE=[provider-type]` `CAMUNDA_HUB_SECURITY_JWT_AUDIENCE_INTERNALAPI=[ui-audience]` `CAMUNDA_HUB_SECURITY_JWT_AUDIENCE_PUBLICAPI=[api-audience]` `SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUERURI=[provider-issuer]` `SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWKSETURI=[provider-jwks-url]`. | #### Example component values The examples below use these public URLs: - Management Identity: `https://identity.example.com` - Optimize: `https://optimize.example.com` - Hub: `https://hub.example.com` With those URLs, configure the following redirect URIs in your OIDC provider: - Management Identity: `https://identity.example.com/auth/login-callback` - Optimize: `https://optimize.example.com/api/authentication/callback` - Hub UI: `https://hub.example.com/login-callback` For Hub, the `restapi` component configuration varies by provider. The example below uses Keycloak; for other providers, retrieve the issuer and JWK set URLs from your provider's [OpenID configuration endpoint](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig) (typically `https:///.well-known/openid-configuration`): ```shell CAMUNDA_HUB_OAUTH2_CLIENTID=web-modeler CAMUNDA_IDENTITY_BASEURL=http://identity:8080 CAMUNDA_IDENTITY_TYPE=GENERIC CAMUNDA_HUB_SECURITY_JWT_AUDIENCE_INTERNALAPI=web-modeler-api CAMUNDA_HUB_SECURITY_JWT_AUDIENCE_PUBLICAPI=web-modeler-public-api SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUERURI=https://idp.example.com/realms/camunda SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWKSETURI=https://idp.example.com/realms/camunda/protocol/openid-connect/certs ``` Use the internal Identity base URL for `CAMUNDA_IDENTITY_BASEURL`, not the public redirect URL. For Microsoft Entra ID, replace the provider-specific values: ```shell CAMUNDA_IDENTITY_TYPE=MICROSOFT SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUERURI=https://login.microsoftonline.com//v2.0 SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWKSETURI=https://login.microsoftonline.com//discovery/v2.0/keys ``` ## Supported and unsupported OIDC features When using [Management Identity](/self-managed/components/management-identity/overview.md) with an OIDC provider, not all features of authentication and authorization are available. The following table lists OIDC supported and unsupported features. | Feature name | Description | Availability | | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------: | | Single sign-on (SSO) | Redirects users to the identity provider for authentication, and enables seamless login across applications. Zeebe, Operate, Tasklist, Optimize, Identity, and Connectors use an identity provider-issued JWT. Hub uses PKCE (Proof Key for Code Exchange) to connect to the Camunda Identity service. | | | Authentication flows (Authorization code flow with PKCE) | Securely handles user login using the recommended authorization code flow with PKCE (Proof Key for Code Exchange) for security. Hub uses PKCE, but does not directly connect to the identity provider with OIDC. Hub uses PKCE to connect to the Camunda Identity service. | | | ID token handling | Validates and extracts user identity details from the ID token after authentication. | | | Access token management | Uses access tokens issued by the identity provider to securely access protected APIs. | | | Session management | Ensures users remain logged in across sessions and applications or automatically re-authenticate when needed. | | | Logout handling (RP-initiated logout) | Triggers user logout from the application and notifies the identity provider to sign out users across all connected systems. | | | Role and group synchronization | Maps user roles and permissions from the identity provider into the client application for access control. | | | User profile management | Fetches user details from the UserInfo endpoint after authentication to personalize the user experience. | | To request a missing feature, please [contact us](/reference/contact.md). ## OIDC provider known limitations Camunda requires the `aud` (audience) claim in JWT access tokens for authentication, including machine-to-machine (M2M) flows. The claim must match the configured audience (see [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3)). OIDC providers that do not include the `aud` claim in access tokens issued via the OAuth 2.0 Client Credentials flow are not compatible with Camunda for M2M authentication. ### AWS Cognito AWS Cognito does not include the `aud` claim in Client Credentials access tokens, using `client_id` instead. Camunda rejects these tokens with errors such as `Token audiences are [], expected at least one of [...]`, resulting in `UNAUTHENTICATED` errors for Connectors and other M2M clients. Cognito is not currently supported for M2M authentication. For tracking and updates, see [camunda/camunda#44650](https://github.com/camunda/camunda/issues/44650). --- ## Configuration(Configuration) Configure Management Identity for your Camunda 8 Self-Managed deployment. This guide covers application-level configuration, including environment variables and IdP settings. :::info Deploying with Helm? If you deploy Camunda 8 Self-Managed with Helm, use the [Helm chart authentication and authorization guides](/self-managed/deployment/helm/configure/authentication-and-authorization/index.md) to configure OIDC and Management Identity. ::: ## Configure Management Identity IdP When a deployment includes Management Identity, it uses Keycloak as its identity provider (IdP) by default. You can configure your Management Identity IdP using the following options: | IdP configuration | Description | | :------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------- | | [Connect to an identity provider](./connect-to-an-oidc-provider.md) | Connect to an OpenID Connect (OIDC) authentication provider to replace Keycloak. | | [Connect to an existing Keycloak instance](./connect-to-an-existing-keycloak.md) | Connect Management Identity to your existing Keycloak instance. | | [Configure an external IdP using Keycloak](./configure-external-identity-provider.md) | Configure an external identity provider using Keycloak, such as OpenID Connect, SAML, LDAP, or Active Directory. | :::note Management Identity relies on a PostgreSQL database. When running Management Identity with an external OIDC provider, you can [connect to an alternative Database](./alternative-db.md) if your internal policies or compliance requirements prevent the use of PostgreSQL. ::: --- ## Initialize tenants for Optimize :::note This page describes **logical tenants** for Optimize. For strong physical isolation of separate teams or organizations within a single cluster, see [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md), including the [Optimize deployment guidance](/self-managed/concepts/physical-tenants/index.md#optimize-deployment). ::: Configure initial [tenants](/components/concepts/multi-tenancy.md) for Optimize in Camunda 8 Self-Managed. ## About Optimize tenants Tenants managed within Management Identity only apply to [Optimize](../../optimize/overview.md). Furthermore, they're only effective when the following conditions are met: - You've [enabled multi-tenancy checks for your Orchestration Cluster](/components/admin/tenant.md). - Your tenants have the same identifiers in Orchestration Cluster Admin and Management Identity. In this guide, you'll learn how to initialize tenants in your app configuration. ## Before you begin Before you begin, [configure a database](../miscellaneous/configuration-variables.md#database-configuration). Management Identity requires a database to support multi-tenancy When deploying Camunda 8 with Docker, you can programmatically configure tenants in Management Identity in two ways: - `application.yaml` - Environment variables When using Helm to deploy Camunda 8, you must configure tenants using environment variables. Configuration using [Helm values](https://artifacthub.io/packages/helm/camunda/camunda-platform#parameters) is not supported. ## Initialize tenants in Management Identity First, enable the Management Identity multi-tenancy flag: ```yaml identity: flags: multi-tenancy: "true" ``` ```sh MULTITENANCY_ENABLED=true ``` With multi-tenancy enabled, initialize your tenants: ```yaml identity: tenants: - name: My tenant tenantId: my-tenant members: - type: USER username: username - type: GROUP group-name: group name - type: APPLICATION application-id: application-id ``` Each member type has a corresponding property you use to set the member identifier: | Member type | Property | | ------------- | ---------------- | | `USER` | `username` | | `GROUP` | `group-name` | | `APPLICATION` | `application-id` | In some contexts, like the Management Identity UI, the "Application ID" is referred to as the "Client ID". ```sh IDENTITY_TENANTS_0_NAME="My tenant" IDENTITY_TENANTS_0_TENANTID="my-tenant" IDENTITY_TENANTS_0_MEMBERS_0_TYPE="USER" IDENTITY_TENANTS_0_MEMBERS_0_USERNAME="username" IDENTITY_TENANTS_0_MEMBERS_1_TYPE="GROUP" IDENTITY_TENANTS_0_MEMBERS_1_GROUPNAME="group name" IDENTITY_TENANTS_0_MEMBERS_2_TYPE="APPLICATION" IDENTITY_TENANTS_0_MEMBERS_2_APPLICATIONID="application-id" ``` Each member type has a corresponding property you use to set the member identifier. Replace `` with the position of the member in the tenant's member list, starting at 0: | Member type | Property | | ------------- | -------------------------------------------------- | | `USER` | `IDENTITY_TENANTS_0_MEMBERS__USERNAME` | | `GROUP` | `IDENTITY_TENANTS_0_MEMBERS__GROUPNAME` | | `APPLICATION` | `IDENTITY_TENANTS_0_MEMBERS__APPLICATIONID` | In some contexts, like the Management Identity UI, the "Application ID" is referred to as the "Client ID". ## Next steps - [Enable multi-tenancy checks for your Orchestration Cluster](/components/admin/tenant.md) - [Manage tenants in the Management Identity application](../manage-tenants.md) --- ## Get started with Management Identity Get started with Management Identity in Self-Managed by learning how to open and log in to the Management Identity interface. ## Log in to Management Identity Once Management Identity has successfully started, you can open the **Log in** page and log in to Management Identity. If you are running the default configuration, you can access the Management Identity interface via the following URLs: - [Docker Compose full or standalone configuration](/self-managed/quickstart/developer-quickstart/docker-compose/configuration.md#choose-a-docker-compose-configuration): `http://localhost:8084/` - [Helm](/self-managed/deployment/helm/install/quick-install.md): Follow your [`port-forward` or Ingress configuration](/self-managed/deployment/helm/configure/ingress/accessing-components-without-ingress.md) ## Default user In the default configuration, Management Identity creates a default example user during installation. You can log in with this example user account using the following credentials: ```text Username: demo Password: demo ``` :::tip Want to create more users? Management Identity uses the users managed in Keycloak. To create a user, refer to [Keycloak's documentation on creating a user](https://www.keycloak.org/docs/latest/server_admin/#proc-creating-user_server_administration_guide) for your version of Keycloak. ::: ## Management Identity home page You are directed to the home page once you have successfully logged in. ![identity-landing-page](./img/identity-landing-page.png) ## Next steps Once you log in to Management Identity, you can start managing authentication, access, and permissions for Camunda Hub and Optimize. - [Manage users, groups, roles, and applications](application-user-group-role-management/identity-application-user-group-role-management-overview.md) - [Manage access and permissions](access-management/access-management-overview.md) - [Manage tenants](manage-tenants.md) :::info Learn more about how to [configure Management Identity](configuration/identity-configuration-overview.md) and [authentication](authentication.md). ::: --- ## Tenants for Optimize Multi-tenancy in Camunda 8 allows a single installation to host multiple tenants (such as departments, teams, or external clients) while maintaining per-tenant isolation of data and processes in a shared environment. :::info To learn more, see [multi-tenancy](/components/concepts/multi-tenancy.md). ::: ## About Optimize tenants Tenants managed within Management Identity **apply only to [Optimize](../optimize/overview.md)**, allowing you to isolate data access for reports and dashboards. This is effective only if you have [multi-tenancy checks enabled for your Orchestration Cluster](/components/admin/tenant.md). If you enable multi-tenancy for Optimize, you must create tenants with the same identifiers as those configured for your Orchestration Cluster. This alignment is important for correct data isolation and association in Optimize. ## Enable multi-tenancy for Optimize By default, multi-tenancy is disabled in Management Identity. To enable multi-tenancy: 1. Enable the [`MULTITENANCY_ENABLED` feature flag](/self-managed/components/management-identity/miscellaneous/configuration-variables.md#feature-flags). If you use the Camunda Helm chart, also set `global.multitenancy.enabled: true` in your Helm values. Without it, the **Tenants** tab doesn't appear in Management Identity. 2. [Configure a database](/self-managed/components/management-identity/miscellaneous/configuration-variables.md#database-configuration). ## Create a tenant :::note A `` tenant is automatically created during Identity startup. ::: 1. Log in to Management Identity and select the **Tenants** tab. ![tenant-management-tab](./img/tenant-management-tab.png) 2. Click **Create Tenant** and a modal will open. 3. Enter a name and ID for the tenant, and click **Create tenant**: ![tenant-management-modal-1](./img/tenant-management-modal-1.png) On creation, the modal closes and the table updates with your new tenant. 4. Click on your new tenant to view the details: ![tenant-management-details](./img/tenant-management-details.png) ## Tenant assignments To assign [users, groups and applications](./application-user-group-role-management/identity-application-user-group-role-management-overview.md) to a tenant: ### Assign users to a tenant 1. Click **Assigned users** to view the users assigned to the tenant, and click **Assign users**: ![tenant-management-assign-users](./img/tenant-management-assign-users-tab.png) 1. Search and select the users to assign to the tenant. After selecting the users, click **Assign users**: ![tenant-management-assign-users-modal](./img/tenant-management-assign-users-modal.png) On confirmation, the modal closes, the table updates, and the assigned users are shown: ![tenant-management-assign-users-refreshed](./img/tenant-management-assign-users-refreshed.png) ### Assign groups to a tenant 1. Click **Assigned groups** to view the groups assigned to the tenant, and click **Assign groups**: ![tenant-management-assign-groups](./img/tenant-management-assign-groups-tab.png) 1. Search and select the groups to assign to the tenant. After selecting the groups, click **Assign groups**: ![tenant-management-assign-groups-modal](./img/tenant-management-assign-groups-modal.png) On confirmation, the modal closes, the table updates, and the assigned groups are shown: ![tenant-management-assign-groups-refreshed](./img/tenant-management-assign-groups-refreshed.png) ### Assign applications to a tenant 1. Click **Assigned applications** to view the applications assigned to the tenant, and click **Assign applications**: ![tenant-management-assign-applications](./img/tenant-management-assign-applications-tab.png) 1. Search and select the applications to assign to the tenant. After selecting the applications, click **Assign applications**: ![tenant-management-assign-applications-modal](./img/tenant-management-assign-applications-modal.png) On confirmation, the modal closes, the table updates, and the assigned applications are shown: ![tenant-management-assign-applications-refreshed](./img/tenant-management-assign-applications-refreshed.png) --- ## Mapping rules(Management-identity) Use mapping rules to dynamically assign Management Identity entities to your users based on claims in your JWT tokens. :::note You can only use mapping rules if Management Identity is configured to use [OIDC-based authentication](/self-managed/components/management-identity/configuration/connect-to-an-oidc-provider.md). ::: ## About mapping rules You can assign two types of entities with mapping rules: - Tenants - Roles :::note A `Default` mapping rule is created during startup using the [IDENTITY_INITIAL_CLAIM_NAME and IDENTITY_INITIAL_CLAIM_VALUE environment variables](/self-managed/components/management-identity/miscellaneous/configuration-variables.md#oidc-configuration) to allow an initial user access to the Identity interface. Once you have access to the Identity interface, configure the additional mapping rules to ensure your users have the correct access to the Camunda components. ::: ## Add a mapping rule 1. Log in to the Identity interface and navigate to the **Mappings** tab. ![mapping-rule-management-tab](./img/mapping-rule-management-tab.png) 1. Click the **Add mapping** button and select the type of mapping to create. You can create a mapping for a role or tenant. ![mapping-rule-add](./img/mapping-rule-add-mapping.png) 1. Fill in the fields for the mapping rule and click **Create**. ![mapping-rule-add-modal](./img/mapping-rule-add-mapping-modal.png) :::note The operator option is used to define how we evaluate the rules against your tokens. The options are: - **Contains**: Used for array-based claims, such as a list of roles. - **Equals**: Used for string-based claims, such as a string ID. ::: The created mapping rule can be seen in the table. ![mapping-rule-refreshed-table](./img/mapping-rule-refreshed-table.png) ## Edit a mapping rule 1. Click the pencil icon on the row of the mapping rule you want to update. 2. Update the fields for the mapping rule and click **Update**. :::note You can only update the entities applied by the mapping rule and the claims used to map the entities. You cannot update the mapping rule type. ::: ## Delete a mapping rule Click on the **trash can** icon on the row of the mapping rule you want to delete. ![mapping-rule-delete-modal](./img/mapping-rule-delete-modal.png) After confirming, the mapping rule is deleted and no longer appears in the table. --- ## Monitor Management Identity Monitor the health and operation of the Management Identity component in your Self-Managed deployment. ## Monitoring endpoints You can use the following default exposed endpoints to monitor how the Management Identity component is operating. | Endpoint | Default port | Purpose | | :--------------------- | :----------- | :------------------------------------------------------------------------- | | `/actuator/health` | `8082` | Provide the health status of the application, often used in health checks. | | `/actuator/prometheus` | `8082` | Provide operational application metrics. | --- ## Configuration variables As a Spring Boot application, Identity supports any standard [Spring configuration](https://docs.spring.io/spring-boot/reference/features/external-config.html) method. ## Core configuration The core configuration variables are as follows: | Environment variable | Description | Default value | | :----------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `IDENTITY_AUTH_PROVIDER_BACKEND_URL` | Used to support container to container communication. | http://localhost:18080/auth/realms/camunda-platform | | `IDENTITY_AUTH_PROVIDER_ISSUER_URL` | Used to denote the token issuer. | http://localhost:18080/auth/realms/camunda-platform | | `IDENTITY_AUTH_PROVIDER_USEBACKENDAUTHURL` | Whether logout requests are sent to `IDENTITY_AUTH_PROVIDER_BACKEND_URL` (`true`) or `IDENTITY_AUTH_PROVIDER_ISSUER_URL` (`false`). Token refresh always uses `IDENTITY_AUTH_PROVIDER_BACKEND_URL`. | false | | `IDENTITY_BASE_PATH` | Used to configure Identity to run on a subpath (Requires HTTPs for `IDENTITY_URL`). | | | `IDENTITY_CLIENT_ID` | The client ID for the Identity client. | camunda-identity | | `IDENTITY_CLIENT_SECRET` | The client secret for the Identity client. | | | `IDENTITY_LOG_LEVEL` | The level of which to log messages at. | INFO | | `IDENTITY_LOG_PATTERN` | The pattern to use when logging. | `%clr{%d{yyyy-MM-dd HH:mm:ss.SSS}}{faint} %clr{%5p} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n%xwEx` | | `IDENTITY_URL` | The URL of the Identity service. | http://localhost:8080 | | `KEYCLOAK_REALM` | The name of the Keycloak Realm to connect to. | camunda-platform | | `KEYCLOAK_SETUP_USER` | The username of a user with admin access to Keycloak. | admin | | `KEYCLOAK_SETUP_PASSWORD` | The password of a user with admin access to Keycloak. | admin | | `KEYCLOAK_SETUP_REALM` | The realm that the setup user is in. | master | | `KEYCLOAK_SETUP_CLIENT_ID` | The client to use for authentication during setup of the provided Keycloak. | admin-cli | | `KEYCLOAK_URL` | The URL of the Keycloak instance to use. | http://localhost:18080/auth | ## Camunda Identity SDK configuration Use the following names and values for the Identity SDK to ensure proper authentication and authorization with Identity and the IdP for all components. | Environment variable | Property | Description | Default value | | :----------------------------------- | :-------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | | `CAMUNDA_IDENTITY_ISSUERBACKENDURL` | `camunda.identity.issuer-backend-url` | The back-channel URL to the Identity provider, used for token verification. | - | | `CAMUNDA_IDENTITY_AUDIENCE` | `camunda.identity.audience` | The required audience of the auth token. | - | | `CAMUNDA_IDENTITY_TYPE` | `camunda.identity.type` | Define what kind of authentication type you will use (`KEYCLOAK`, `MICROSOFT`, `GENERIC`). | `KEYCLOAK` | | `CAMUNDA_IDENTITY_BASEURL` | `camunda.identity.base-url` | The base URL of the Camunda Identity instance. | - | | `CAMUNDA_IDENTITY_ISSUER` | `camunda.identity.issuer` | The front-channel URL to the Identity provider, used for the login redirect. Also used for logout when `camunda.identity.use-backend-auth-url` is `false`. | - | | `CAMUNDA_IDENTITY_JWKSURL` | `camunda.identity.jwks-url` | Defines the JWKS URL, which is used by the services to validate the JWT tokens. If nothing is set, it will use the WellKnownEndpoint. | - | | `CAMUNDA_IDENTITY_CLIENTID` | `camunda.identity.client-id` | Defines the client ID, which is used by Zeebe in authentication flows. | - | | `CAMUNDA_IDENTITY_CLIENTSECRET` | `camunda.identity.client-secret` | The client secret for the Identity client. | - | | `CAMUNDA_IDENTITY_AUTHSCOPES` | `camunda.identity.auth-scopes` | Defines the scopes that should be applied to the token, provided as list separated by spaces. | `openid email offline_access` | | `CAMUNDA_IDENTITY_USEBACKENDAUTHURL` | `camunda.identity.use-backend-auth-url` | Whether logout is performed against the `issuer-backend-url` (`true`) or the `issuer` (`false`). Fetching refresh tokens always uses the `issuer-backend-url`, falling back to the `issuer` when it is not set. | `false` | | `CAMUNDA_IDENTITY_CLOCKSKEW` | `camunda.identity.clock-skew` | Sets the allowed clock skew when validating JWT issuance and expiration. Format: ISO 8601 | `60S` | | `CAMUNDA_IDENTITY_MAXSAVEPOSTSIZE` | `camunda.identity.max-save-post-size` | Maximum bytes Tomcat buffers per connection during the HTTP/1.1 to HTTP/2 (h2c) upgrade. Set to `-1` to remove the limit. | `131072` | | `CAMUNDA_IDENTITY_USERNAMECLAIM` | `camunda.identity.username-claim` | Defines the claim used to populate a user's username. Defaults to `preferred_username`, except when `camunda.identity.type` is `MICROSOFT`, where it defaults to `unique_name`. | `preferred_username` (`unique_name` for `MICROSOFT`) | | `CAMUNDA_IDENTITY_GROUPSCLAIM` | `camunda.identity.groups-claim` | Defines the claim used to read a user's groups directly from the token. If unset, groups are instead resolved via a call to the Identity groups API. | - | | `CAMUNDA_IDENTITY_USERIDCLAIM` | `camunda.identity.user-id-claim` | Defines the claim used to identify a user. If unset, or if the configured claim is missing from both the token and the userinfo response, falls back to the token's `sub` claim. Useful when migrating between identity providers, for example using Azure AD's `oid` claim to preserve user identities carried over from Keycloak. | - | ## License configuration See [licensing](/self-managed/components/orchestration-cluster/core-settings/configuration/licensing.md). ## OIDC configuration Claims are name/value pairs used to represent an individual identity. Configure your initial claim and value to match the claim used with your OIDC provider. For example, to use your Microsoft Entra unique account ID, set `IDENTITY_INITIAL_CLAIM_NAME` to `oid`, and `IDENTITY_INITIAL_CLAIM_VALUE` to the ID. :::caution Once set, you cannot update your initial claim name and value using environment or Helm values. You must change these values directly in the database. ::: | Environment variable | Description | Default value | | :----------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------- | | `IDENTITY_INITIAL_CLAIM_NAME` | The type of claim to use for the initial user. Examples can include `oid`, `name` or `email`. | `oid` | | `IDENTITY_INITIAL_CLAIM_VALUE` | The value of the claim to use for the initial user. For the default `oid`, the value usually corresponds to the unique ID of your user account. | | | `CAMUNDA_ORCHESTRATION_CLUSTER_PING_CLAIM_NAME` | The claim used to identify the identity that calls the [clusters endpoint](/self-managed/components/orchestration-cluster/zeebe/configuration/broker.md#camunda-hub-ping-configuration) (also known as ping) to register the Orchestration Cluster with Camunda Hub. | `sub` | | `CAMUNDA_ORCHESTRATION_CLUSTER_PING_CLAIM_VALUE` | The expected value of the claim above. See the note below before relying on the default with a non-Keycloak OIDC provider. | `service-account-` | | `KEYCLOAK_INIT_ORCHESTRATION_CLIENT_ID` | The client ID of the Orchestration Cluster application that Management Identity registers in Keycloak. It supplies the `` in the default `CAMUNDA_ORCHESTRATION_CLUSTER_PING_CLAIM_VALUE` (`service-account-`). If `CAMUNDA_ORCHESTRATION_CLIENT_ID` is set, it takes priority. | `orchestration` | ### Ping client claim for non-Keycloak providers The default `CAMUNDA_ORCHESTRATION_CLUSTER_PING_CLAIM_VALUE` (`service-account-`) is a Keycloak-specific naming convention for a client's service account subject. The OIDC specification doesn't define it. Other OIDC providers can populate the `sub` claim differently for a client credentials grant (for example, some reuse the client ID directly). In that case the mapping rule no longer matches, and the ping request fails closed. If you don't use Keycloak, set `CAMUNDA_ORCHESTRATION_CLUSTER_PING_CLAIM_VALUE` (and `CAMUNDA_ORCHESTRATION_CLUSTER_PING_CLAIM_NAME`, if your provider doesn't use `sub`) to match the actual subject value your provider issues for the Orchestration Cluster's service account. ## Component configuration Identity supports component configuration using preset values. To configure a component for use within Identity, set two variables: | Environment variable | Description | Default value | | :------------------------------------ | :---------------------------------------------- | :------------ | | `KEYCLOAK_INIT__SECRET` | The secret used for authentication flows. | No default | | `KEYCLOAK_INIT__ROOT_URL` | The root URL of where the component is hosted. | No default | | `KEYCLOAK_INIT__CLIENT_ID` | The client to create and use for the component. | `` | :::note Identity supports the following values for the `` placeholder: `OPERATE`, `OPTIMIZE`, `TASKLIST`, and `WEBMODELER`. For the `WEBMODELER` value, only the `KEYCLOAK_INIT__ROOT_URL` variable is required to be set. For the `KEYCLOAK_INIT__CLIENT_ID` value, the default is the component name in lowercase except for `WEBMODELER`, which is`web-modeler`. ::: ## Database configuration Identity requires a database in the following cases: - Connecting to either a generic OIDC provider or Microsoft Entra ID requires a database, regardless of feature flags. Identity stores roles, permissions, mapping rules, and groups in the database, and resolves authorization against the database on every request. See [connect Management Identity to an identity provider](/self-managed/components/management-identity/configuration/connect-to-an-oidc-provider.md). - With the default Keycloak-based setup, Identity requires a database when you enable the `resource-permissions` or `multi-tenancy` [feature flags](#feature-flags). With the default Keycloak-based setup and no feature flags enabled, Identity does not require a database, as Keycloak stores this data separately. | Environment variable | Description | | :--------------------------- | :-------------------------------------------------- | | `IDENTITY_DATABASE_HOST` | The host of the database. | | `IDENTITY_DATABASE_PORT` | The port of the database. | | `IDENTITY_DATABASE_NAME` | The name of the database to connect to. | | `IDENTITY_DATABASE_USERNAME` | The username of a user with access to the database. | | `IDENTITY_DATABASE_PASSWORD` | The password of a user with access to the database. | :::note There are no default values for the variables above. See [supported environments](/reference/supported-environments.md#camunda-platform-8-self-managed) for a list of supported databases. You may also review the [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md). ::: ### Running Identity on Amazon Aurora PostgreSQL Identity supports running on Amazon Aurora PostgreSQL. To connect Identity with your Amazon Aurora PostgreSQL instance, make the following configuration adjustments: 1. Modify the `SPRING_DATASOURCE_URL` environment variable: `jdbc:aws-wrapper:postgresql://[DB_HOST]:[DB_PORT]/[DB_NAME]`. 2. Add the environment variable `SPRING_DATASOURCE_DRIVER_CLASS_NAME` with the value `software.amazon.jdbc.Driver`. For a full list of available driver parameters visit the [AWS JDBC Driver documentation](https://github.com/awslabs/aws-advanced-jdbc-wrapper/wiki/UsingTheJdbcDriver#aws-advanced-jdbc-driver-parameters). #### AWS IAM authentication To use AWS Identity and Access Management (IAM) database authentication with your Amazon Aurora PostgreSQL instance, in addition to the adjustments described [above](#running-identity-on-amazon-aurora-postgresql), follow these steps: 1. Modify the `SPRING_DATASOURCE_URL` environment variable as follows: `jdbc:aws-wrapper:postgresql://[DB_HOST]:[DB_PORT]/[DB_NAME]?wrapperPlugins=iam`. 2. Modify the `SPRING_DATASOURCE_USERNAME` environment variable to match the database user you configured for AWS IAM authentication as described in the [Amazon Aurora documentation](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/UsingWithRDS.IAMDBAuth.DBAccounts.html#UsingWithRDS.IAMDBAuth.DBAccounts.PostgreSQL). 3. Remove the `SPRING_DATASOURCE_PASSWORD` environment variable. ## Feature flags Identity uses feature flag environment variables to enable and disable features; the supported flags are: | Environment variable | Description | Default value | | :--------------------------- | :----------------------------------------------- | :------------ | | RESOURCE_PERMISSIONS_ENABLED | Controls the resource authorizations feature. | false | | MULTITENANCY_ENABLED | Controls the multi-tenancy feature for Optimize. | false | :::note Setting either of the feature flags to `true` requires a database connection. To configure a database connection, see [database configuration](#database-configuration). ::: :::note When using the Camunda Helm chart, setting `MULTITENANCY_ENABLED: true` alone does not enable the Tenants tab in Management Identity. You must also set `global.multitenancy.enabled: true` in your Helm values. Without the global flag, the Tenants tab does not appear in Management Identity even when `MULTITENANCY_ENABLED` is set to `true`. ::: ## Logging ### Google Stackdriver (JSON) logging To enable Google Stackdriver compatible JSON logging, set the environment variable `IDENTITY_LOG_APPENDER=Stackdriver` on Identity. --- ## Configure logging Configure and use logging to access detailed operational information for Identity. ## Identity logging configuration The Identity component uses the [Apache Log4j2](https://logging.apache.org/log4j/2.x/) framework to control the log level and log format. The logging configuration included in the Identity image is as follows: ```xml %clr{%d{yyyy-MM-dd HH:mm:ss.SSS}}{faint} %clr{%5p} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n%xwEx %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{1.} %enc{%msg}%n logs/identity.%d{yyyy-MM-dd-mm-ss}.log ``` ## General configuration options Identity provides support for configuring the log level: | Environment variable | Accepted values | | -------------------- | ------------------------------------------------ | | `IDENTITY_LOG_LEVEL` | OFF, FATAL, ERROR, WARN, INFO, DEBUG, TRACE, ALL | ## Supported logging outputs As part of configuration, Identity provides multiple appenders for outputting logs. To configure which logging appender is used, set the `IDENTITY_LOG_APPENDER` environment variable to either `Console`, `Stackdriver`, or `File`. ### `Console` Console logging produces messages to standard output and is the default log appender. The Console log appender offers additional configuration options as follows: | Environment variable | Accepted values | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `IDENTITY_LOG_PATTERN` | _See the [Log4j2 pattern layout docs](https://logging.apache.org/log4j/2.x/manual/layouts.html#PatternLayout) for possible placeholders._ | ### `Stackdriver` The Stackdriver log appender produces messages to standard output in a format that is compatible with the GCP cloud platform. This appender uses the [GCP layout](https://github.com/apache/logging-log4j2/blob/2.x/log4j-layout-template-json/src/main/resources/GcpLayout.json) provided by the [Log4j2](https://logging.apache.org/log4j/2.x/manual/) library. ### `File` The File log appender produces messages to a rotating log file. The File log appender offers additional configuration options as follows: | Environment variable | Accepted values | | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `IDENTITY_LOG_FILE_PATTERN` | _See the [Log4j2 pattern layout docs](https://logging.apache.org/log4j/2.x/manual/layouts.html#PatternLayout) for possible placeholders._ | | `IDENTITY_LOG_FILE_ROTATION_DAYS` | _See the [Log4j2 time-based triggering policy interval](https://logging.apache.org/log4j/2.x/manual/appenders.html#timebased-triggering-policy) for possible values._ | | `IDENTITY_LOG_FILE_ROTATION_SIZE` | _See the [Log4j2 size-based triggering policy](https://logging.apache.org/log4j/2.x/manual/appenders.html#sizebased-triggering-policy) for possible values._ | ## Custom logging configuration You can provide your own logging configuration by mounting a configuration file to the Identity container and setting the path to the file using the following variable: | Environment variable | Purpose | | -------------------- | ------------------------------------------------------------------------------------------------------------- | | `LOGGING_CONFIG` | The path to your [Log4j2 config XML](https://logging.apache.org/log4j/2.x/manual/configuration.html#XML) file | :::note To write logs to a file in a containerized environment, the mounted directory containing the log file has to be writable under the user running Identity. ::: --- ## Prepare Identity for production When moving Identity to a production environment, you should consider the following. ## Keycloak dependency As Keycloak is an external-based dependency of Identity, Camunda recommends looking at [Keycloak's documentation on production configuration](https://www.keycloak.org/server/configuration-production) to ensure your Keycloak instance is production-ready. ### Backing up To ensure recovery is possible, Camunda recommends regularly backing up the database that supports Keycloak. #### Helm deployment If you deployed Camunda 8 using Camunda [Helm charts](/self-managed/setup/overview.md), by default there will be a Postgres database deployed with it. In this case, Camunda recommends reading the [Postgres documentation](https://www.postgresql.org/docs/current/backup.html) for guidance on backing up. #### Alternative deployment If your Keycloak service uses a different database provider than Postgres, Camunda recommends referencing the backup section of the documentation for your chosen provider and version. ## Enable TLS A safe and healthy exchange of secure data requires Transport Layer Security (TLS). - TLS support for Identity can be enabled by setting configuration values. Refer to [Spring - Configure SSL](https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.webserver.configure-ssl) for more information. - To enable TLS alongside Keycloak, refer to the Keycloak documentation regarding [TLS enablement](https://www.keycloak.org/server/enabletls). ## Setting Identity URL To ensure authentication flows are successful, the `IDENTITY_URL` should be set to the URL of the Identity service. --- ## Keycloak resource management When using Keycloak as an IdP, Identity uses the following Keycloak resources: | Identity resource | Keycloak resource (and how it is filtered) | | :---------------------------- | :---------------------------------------------- | | Application | Client (without authorization) | | Application/permissions | Client/Service Account roles | | API | Client (with authorization) | | API/permissions | Client/Roles | | Role | Realm role (with attribute `camunda_role=true`) | | Role/permissions | Realm role/Associated role | | Group | Group | | Group/Member | Group/Member | | Group/Authorization\* | (not saved to Keycloak) | | Group/Roles | Group/Role mapping | | User | User | | User/Assigned role | User/Role mapping | | User/Authorization\* | (not saved to Keycloak) | | Tenant\* | (not saved to Keycloak) | | Tenant/Assigned user\* | (not saved to Keycloak) | | Tenant/Assigned group\* | (not saved to Keycloak) | | Tenant/Assigned application\* | (not saved to Keycloak) | \* This resource is only activated with the relevant feature flag. --- ## Starting configuration for Identity Identity requires a set of base configurations to operate correctly. When Identity is started, it will create or update the following entities in Keycloak. ## Clients | Name | Client ID | Service accounts | Created/updated with component | | :------------------------------- | :------------------------------- | :--------------- | :----------------------------- | | Identity | camunda-identity | enabled | All | | Camunda Identity Resource Server | camunda-identity-resource-server | enabled | All | | Operate | operate | enabled | Operate | | Operate API | operate-api | enabled | Operate | | Optimize | optimize | enabled | Optimize | | Optimize API | optimize-api | enabled | Optimize | | Tasklist | tasklist | enabled | Tasklist | | Tasklist API | tasklist-api | enabled | Tasklist | | Hub | web-modeler | disabled | Hub | | Hub API | web-modeler-api | enabled | Hub | The Hub client IDs (`web-modeler`, `web-modeler-api`) are retained for backward compatibility with existing Web Modeler installations. ## Roles Both `Web Modeler`/`Web Modeler Admin` and the equivalent `Hub`/`Hub Admin` roles are provisioned and grant identical permissions. See [Management Identity roles and permissions](../../../upgrade/components/890-to-8100.md#management-identity-roles-and-permissions) in the 8.9 to 8.10 upgrade guide for details. | Name | Created/updated with component | | :--------------------- | :----------------------------- | | Identity | All | | Operate | Operate | | Optimize | Optimize | | Tasklist | Tasklist | | Web Modeler | Hub | | Web Modeler Admin | Hub | | Hub | Hub | | Hub Admin | Hub | | Console | Hub | | DevOps | Hub | | Analyst | Hub, Optimize | | Hub API - Cluster Ping | Hub | ## Client scopes | Name | Protocol | Description | | :--------------- | :------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | camunda-identity | openid-connect | A default client scope that contains mappers to augment the token generated with information required by the components of Camunda. Contains the mappers described in the [mappers](#mappers) section. | ## Mappers | Name | Protocol Mapper | Description | | :--------------- | :-------------------------------- | :-------------------------------------------------------------------------------------------------------- | | email | oidc-usermodel-property-mapper | Adds the email user attribute to the `access`, `ID`, and `user info` tokens using the claim name `email`. | | full name | oidc-full-name-mapper | Adds the user's full name to the `access`, `ID`, and `user info` tokens. | | permissions | oidc-usermodel-client-role-mapper | Adds the user's client roles to the `access` token with the claim name `permissions.${client_id}`. | | audience resolve | oidc-audience-resolve-mapper | Adds the audiences the user has access to in the `audience` claim. | --- ## Troubleshooting Management Identity Troubleshoot common issues with Management Identity in a Self-Managed deployment. ## Management Identity pod crashloops/continually restarts If the Management Identity pod crash loops, or the pod continually restarts, it is likely there is an issue with the Keycloak connection. ### Solution 1. Find the name of the Management Identity pod by running: ``` kubectl get pods ``` The output should look similar to: ``` NAME READY STATUS RESTARTS AGE c8-local-identity-6fd96d59c4-8lzxv 1/1 Running 2 (24s ago) 85s c8-local-keycloak-0 1/1 Running 0 30m c8-local-operate-69b765f7bb-hjcts 1/1 Running 0 30m c8-local-postgresql-0 1/1 Running 0 30m c8-local-zeebe-0 1/1 Running 0 30m c8-local-zeebe-gateway-678f4c7bfb-w8ght 1/1 Running 0 30m elasticsearch-master-0 1/1 Running 0 30m ``` 2. Using the pod name from the output above, view the logs: ``` kubectl logs ``` 3. Observe the most recent logs for an error message or stacktrace, for example: ``` 2022-07-04 15:52:04.250 ERROR 1 --- [main] i.c.i.i.k.config.KeycloakConfiguration : Failure #1. Unable to connect to Keycloak. 2022-07-04 15:52:09.252 WARN 1 --- [main] i.c.i.i.k.config.KeycloakConfiguration : Retrying... ``` ## Management Identity is unable to connect to Keycloak If you are seeing an error message like the one below in your Management Identity service logs, there is an issue with the connection Management Identity is trying to make and the Keycloak service: ``` 2022-07-04 15:52:04.250 ERROR 1 --- [main] i.c.i.i.k.config.KeycloakConfiguration : Failure #1. Unable to connect to Keycloak. ``` This can be caused by: - The Keycloak service has not started/is not ready. - Management Identity making requests from an external IP address. See details on resolving these issues below. ### Solution 1: The Keycloak service has not started/is not ready The Keycloak service can take time to start due to the supporting systems. Keycloak is ready to accept connections when the following log lines are visible: ``` 15:24:24,094 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: Keycloak 16.1.1 (WildFly Core 18.0.4.Final) started in 33171ms - Started 718 of 1020 services (699 services are lazy, passive or on-demand) 15:24:24,098 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:9990/management 15:24:24,100 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:9990 ``` When the Keycloak service is ready for connections, start (or restart) the Management Identity pod. ### Solution 2: Management Identity making requests from an external IP address By default, Keycloak requires TLS on requests that originate from what it considers to be an external source. The Keycloak documentation for [setting up SSL](https://www.keycloak.org/docs/latest/server_admin/#_ssl_modes) maintains a list of what they consider to be an external IP address under the `external requests` section. The solution to this issue depends largely on your environment. As a starting point, consider these options: - Configure communication between the services (for example in a cluster) to use IP ranges within the ranges Keycloak expects. - If configuring IP ranges is not an option, you can disable the SSL requirement in Keycloak itself: 1. In the `master` realm, set `Require SSL` to `none` by following the steps in [SSL modes](https://www.keycloak.org/docs/latest/server_admin/#_ssl_modes) for your version of Keycloak. 2. Restart the Management Identity service. 3. In the `camunda-platform` realm, set `Require SSL` to `none` by following the steps in [SSL modes](https://www.keycloak.org/docs/latest/server_admin/#_ssl_modes) for your version of Keycloak. 4. Restart the Management Identity service again. Management Identity should now start successfully. :::danger warning Camunda would only recommend that requirements for SSL are disabled in a development environment. ::: --- ## Management Identity The Management Identity component in Camunda 8 Self-Managed is used to manage authentication, access, and authorization for components outside the [Orchestration Cluster](/self-managed/components/orchestration-cluster/overview.md): [Camunda Hub](../hub/index.md) and [Optimize](../optimize/overview.md). Management Identity controls who can sign in to Camunda Hub and Optimize, which is separate from the cluster identity stack provided by [Admin (formerly Orchestration Cluster Identity)](/self-managed/components/orchestration-cluster/admin/overview.md). Admin controls access to Zeebe, Operate, Tasklist, and the Orchestration Cluster API within each cluster. See [how identity works in Camunda](/self-managed/components/identity/how-identity-works.md) for a decision tree covering which one to configure. ## About Management Identity - Administrators can use Management Identity to manage Camunda 8 users, groups, roles, permissions, and applications. - Users (interacting via Camunda web components) and applications (interacting via Camunda APIs, such as job workers) are supported, using secure authorization based on OAuth 2.0 standards. - Users log in to web components via an IdP login page. Applications authenticate via machine-to-machine (M2M) tokens. - You can integrate Management Identity with an [external OIDC provider](./configuration/connect-to-an-oidc-provider.md), [connect to an existing Keycloak instance](./configuration/connect-to-an-existing-keycloak.md), or [configure an external IdP using Keycloak](./configuration/configure-external-identity-provider.md). ## Get started If you're new to Management Identity, learn how to access and log in to the Management Identity UI. - [Get started with Management Identity](identity-first-steps.md) ## Core concepts Learn about the core concepts of Management Identity. ### Authentication Management Identity supports two types of authentication: - **Web login**: Users access web applications through an IdP login page. - **Machine-to-machine (M2M)**: Applications authenticate using tokens for API access. Both methods use the [OAuth 2.0 protocol](https://oauth.net/2/) for secure authentication. - [Learn about authentication methods](authentication.md) ### User, group, role, and application management Organize and control access using a role-based access control (RBAC) model. - [Manage users, groups, roles, and applications](application-user-group-role-management/identity-application-user-group-role-management-overview.md) ### Access management Control who can access what by assigning permissions through roles. - [Manage access and permissions](access-management/access-management-overview.md) ### Multi-tenancy :::note This section describes **logical tenants** for Optimize. For strong physical isolation of separate teams or organizations within a single cluster, see [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md), including the [Optimize deployment guidance](/self-managed/concepts/physical-tenants/index.md#optimize-deployment). ::: Isolate data and access in Optimize between different customers or business units by organizing resources into tenants. This is effective only if you have [multi-tenancy checks enabled for your Orchestration Cluster](/components/admin/tenant.md). - [Manage tenants for Optimize](manage-tenants.md) ### Mapping rules Automatically assign roles and tenants to users based on information in their authentication tokens (JWT claims). This enables dynamic access control when integrating with external identity providers. - [Configure mapping rules](mapping-rules.md) ## Reference - [Configure Management Identity](configuration/identity-configuration-overview.md) - [Monitor Management Identity](miscellaneous/application-monitoring.md) - [Configuration variables](miscellaneous/configuration-variables.md) - [Configure logging](miscellaneous/configure-logging.md) - [Prepare Management Identity for production](miscellaneous/making-identity-production-ready.md) - [Keycloak resource management](miscellaneous/resource-management.md) - [Starting configuration](miscellaneous/starting-configuration.md) - [Troubleshoot Management Identity](miscellaneous/troubleshoot-identity.md) --- ## Set up a connection To deploy diagrams, start process instances, or test tasks, you must first connect Desktop Modeler to Camunda. Follow the steps below to set up a connection. To connect to **Camunda 8 SaaS**, visit the [Camunda 8 SaaS guide](../../../../components/modeler/desktop-modeler/connect-to-camunda-8.md). 1. Click the **Connection manager**. For new installations, this will show **No connection**. If you have previously selected a connection, it will show the name of that connection. ![Connection manager button](./img/connection-selector-offline.png) 2. To add a new connection, open the settings. You can either open the connection manager and click **Manage connections** or open the settings directly (`Cmd/Ctrl + ,`). By default, a local c8run connection is already configured. If you have previously used Desktop Modeler to deploy a diagram, that connection will also be available as **Unnamed Connection**. You can rename this connection to something more descriptive in the connection manager settings. ![Connection manager is opened](./img/connection-selector-offline-open.png) 3. Click **Add connection**. ![Connection manager showing add button](./img/connection-manager-add.png) 4. Select **Camunda 8 Self-Managed** as the target. ![empty self-managed connection](./img/connection-with-sm.png) 5. Enter the connection URL, and optionally the tenant ID and Operate URL. :::caution You can connect to Camunda 8 both securely and insecurely through the `https` and `http` protocols. Secured connections to a remote endpoint are established only if the remote server certificate is trusted by the app. Ensure that root and intermediate certificates you trust are [known to the app](/components/modeler/desktop-modeler/flags/flags.md#zeebe-ssl-certificate). Multi-tenancy is available only when authentication is enabled through [Orchestration Cluster Admin](../../orchestration-cluster/admin/overview.md). ::: ![deployment via Camunda 8](./img/connection-with-endpoint.png) 6. Select your authentication method, and enter the required credentials. For **Basic authentication**, enter your username and password. ![Basic authentication configuration](./img/connection-with-basic-auth.png) For **OAuth**, enter the credentials for your OAuth provider. These credentials are configured during the default [Helm installation](/self-managed/deployment/helm/install/quick-install.md). You can find them in [Orchestration Cluster Admin](/self-managed/components/orchestration-cluster/admin/overview.md) or set them using Zeebe [environment variables](/self-managed/components/orchestration-cluster/zeebe/security/client-authorization.md#environment-variables). :::note When using Modeler to deploy a process model or start a process instance, you may run into issues with [resource authorizations](/components/concepts/access-control/authorizations.md). Make sure your [client](/components/admin/client.md) has the right authorizations assigned to it. ::: ![oauth configuration](./img/connection-with-oauth.png) | Name | Description | Example value | | --------------- | --------------------------------------- | ---------------------------------------------------------------------------------------- | | Client ID | The name of your Zeebe client. | `zeebe` | | Client secret | The password of your Zeebe client. | `zecret` | | OAuth token URL | The full path to the token endpoint. | `https:///auth/realms/camunda-platform/protocol/openid-connect/token` | | OAuth audience | The permission name for Zeebe. | `zeebe.example.com` | | OAuth scope | The permissions available to the token. | `Zeebe,Tasklist,Operate` | If the connection is established successfully, you can leave the settings and go back to the connection manager, where your new connection is now available. 7. Select the connection you just created to use it for [deployment](./deploy-to-self-managed.md) or other tools like [task testing](../../../../components/modeler/desktop-modeler/task-testing.md) or [starting a new process instance](../../../../components/modeler/desktop-modeler/start-instance.md): ![New connection selected in connection manager](./img/connection-selector-new-connection.png) :::note As a next step, [deploy your diagram](./deploy-to-self-managed.md). ::: --- ## Deploy diagram Desktop Modeler can directly deploy diagrams and start process instances in Camunda 8 Self-Managed. Follow the steps below to deploy a diagram: 1. Ensure you have already [set up a connection](./connect-to-self-managed.md) and selected it. ![Connection selector showing a Self-Managed connection selected](./img/deploy-selection.png) 2. Click the **Deploy** icon: ![deploy icon](./img/deploy-icon.png) 3. Click **Deploy** to perform the actual deployment: ![deploy diagram](./img/deploy-diagram.png) 4. If the deployment is successful, you will see a confirmation message: ![deployment successful](./img/deploy-success.png) --- ## History cleanup To satisfy data protection laws or just for general storage management purposes, Optimize provides an automated cleanup functionality. There are two types of history cleanup: - Process data cleanup - External variable cleanup By default, all types of history cleanup are disabled. They can be enabled individually by config and the cleanup is applied accordingly. ## Setup The most important settings are `cronTrigger` and `ttl`; their global default configuration is the following: ``` historyCleanup: cronTrigger: '0 1 * * *' ttl: 'P2Y' ``` `cronTrigger` - defines at what interval and when the history cleanup should be performed in the format of a cron expression. The default is 1AM every day. To avoid any impact on daily business, it is recommended to schedule the cleanup outside of business hours. See the [Configuration Description](./system-configuration.md#history-cleanup-settings) for further insights into this property and its format. `ttl` - is the global time to live period of data contained in Optimize. The field that defines the age of a particular entity differs between process, decision, and event data. Refer to the corresponding subsection in regard to that. The default value is `'P2Y'`, which means by default data older than _2 years_ at the point in time when the cleanup is executed gets cleaned up. For details on the notation, see the [Configuration Description](./system-configuration.md#history-cleanup-settings) of the ttl property. All the remaining settings are entity type specific and will be explained in the following subsections. ## Process data The age of process instance data is determined by the `endDate` field of each process instance. Running instances are never cleaned up. To enable the cleanup of process instance data, the `historyCleanup.processDataCleanup.enabled` property needs to be set to `true`. Another important configuration parameter for process instance cleanup is the `historyCleanup.processDataCleanup.cleanupMode`. It determines what in particular gets deleted when a process instance is cleaned up. The default value of `all` results in the whole process instance being deleted. For other options, review the [configuration description](./system-configuration.md#history-cleanup-settings) of the `historyCleanup.processDataCleanup.cleanupMode` property. To set up a process definition-specific `ttl` or different `cleanupMode` you can also provide process specific settings using the `perProcessDefinitionConfig` list which overrides the global settings for the corresponding definition key. In this example, process instances of the key `MyProcessDefinitionKey` would be cleaned up after two months instead of two years, and when the cleanup is performed, only their associated variables would be deleted instead of the complete process instance. ``` historyCleanup: ttl: 'P2Y' processDataCleanup: enabled: true cleanupMode: 'all' perProcessDefinitionConfig: 'MyProcessDefinitionKey': ttl: 'P2M' cleanupMode: 'variables' ``` ## Example Here is an example of what a complete cleanup configuration might look like: ``` historyCleanup: cronTrigger: '0 1 * * 0' ttl: 'P1Y' processDataCleanup: enabled: true cleanupMode: 'variables' perProcessDefinitionConfig: 'VeryConfidentProcess': ttl: 'P1M' cleanupMode: 'all' 'KeepTwoMonthsProcess': ttl: 'P2M' ``` The above configuration results in the following setup: - The cleanup is scheduled to run every Sunday at 1AM. - The global `ttl` of any data is one year. - The process data cleanup is enabled. - The `cleanupMode` performed on all process instances that passed the `ttl` period is just clearing their variable data but keeping the overall instance data like activityInstances. - There is a process specific setup for the process definition key `'VeryConfidentProcess'` that has a special `ttl` of one month and those will be deleted completely due the specific `cleanupMode: 'all'` configuration for them. - There is another process specific setup for the process definition key `'KeepTwoMonthsProcess'` that has a special `ttl` of two months. ## Diagnosing stalled cleanup If history cleanup runs on schedule but consistently completes in zero seconds, this is expected when all process instances are either still running or have not yet exceeded the configured TTL. No action is needed in that case. If you know that completed instances are past their TTL but cleanup still finishes in zero seconds, orphaned ACTIVE documents are likely accumulating. Cleanup only deletes process instances that have an `endDate` set. If Optimize never received the completion event for an instance, it has no `endDate` and cleanup cannot remove it regardless of how old it is. This happens when the Zeebe exporter's ILM retention window is shorter than Optimize's import lag: the exporter deletes Zeebe records before Optimize imports them, so Optimize writes an ACTIVE entry for the creation event but never sets an endDate. These orphaned documents grow the Optimize process instance index without a cleanup path. To prevent this, set the Zeebe exporter `retention.minimum-age` to at least 3-7 days when running Optimize. See [Zeebe record ILM retention](/components/best-practices/architecture/sizing-your-environment.md#zeebe-record-ilm-retention) in the sizing guide for details. --- ## Localization(Configuration) To present a localized version of Optimize to users corresponding to their default browser language, Optimize provides the possibility to configure localizations. ## Default locale configuration The distributions of Optimize contain the default localization files under `./config/localization/`. The default localizations available are `en` for English and `de` for German. You can also find community maintained localizations in [this repository](https://github.com/camunda/camunda-optimize-translations). Additionally, English is configured as the default `fallbackLocale`. Fallback in this case means whenever a user has a browser configured with a language that is not present in the `availableLocales` list, Optimize will use the `fallbackLocale`. The default locale configuration in `./config/environment-config.yaml` looks like the following: ``` locales: availableLocales: ['en', 'de'] fallbackLocale: 'en' ``` For more details on the configuration keys, refer to the [localization configuration section](./system-configuration.md#localization). ## Custom locale configuration Custom locales can be added by creating a locale file under `./config/localization/` and adding it to the `availableLocales` configuration. :::note Configuring a custom locale means you have to maintain it yourself and update it in the context of an Optimize upgrade. There is currently no changelog of new localization entries available, and it is required that each localization file contains an entry for each key used by Optimize. ::: As an example, a custom localization can be created by making a copy of the `./config/localization/en.json` named `/config/localization/es.json` and adding it to the available locales in `./config/environment-config.yaml` ``` locales: availableLocales: ['en', 'de', 'es'] fallbackLocale: 'en' ``` --- ## Logging(3) Camunda Optimize provides logging facilities that are preconfigured to use _INFO_ logging level which provides minimal output of information in log files. This level can be adjusted using the `environment-log4j2.xml` configuration file. ## Google Stackdriver (JSON) logging To enable Google Stackdriver compatible JSON logging, set the environment variable `OPTIMIZE_LOG_APPENDER=Stackdriver` before starting Optimize. ## Default logging configuration Although one could potentially configure logging levels for all packages, it is recommended to set logging levels for the following three Optimize parts using only exact package reference as follows: - Optimize runtime environment: ```xml ``` - Optimize upgrade: ```xml ``` - Communication to Elasticsearch: ```xml ``` If you are running Optimize with Docker, use the following environment variables to configure its logging levels: - `OPTIMIZE_LOG_LEVEL`: Sets the logging level for the Optimize log. - `UPGRADE_LOG_LEVEL`: Sets the logging level for the Optimize upgrade log. - `ES_LOG_LEVEL`: Sets the logging level for Elasticsearch. Whether using the configuration file or Docker environment variables, to define the granularity of the information shown in the log you can set one of the following log levels: - **error**: Shows errors only. - **warn**: Like **error**, but displays warnings as well. - **info**: Logs everything from **warn** and the most important information about state changes or actions in Optimize. - **debug**: In addition to **info**, writes information about the scheduling process, alerting as well as the import of the engine data. - **trace**: Like **debug**, but in addition, writes all requests sent to the Camunda engine as well as all queries towards Elasticsearch to the log output. --- ## Multi-tenancy(Configuration) Camunda 8 Self-Managed only :::note This page describes logical tenants for Optimize. For strong physical isolation of separate teams or organizations within a single cluster, see [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md). If you run one Optimize instance per Physical Tenant behind a shared Management Identity, see [Optimize and Physical Tenants](/self-managed/concepts/physical-tenants/optimize.md#known-limitation-logical-tenants-with-the-same-id-across-physical-tenants) for a limitation with reusing the same logical tenant ID across tenants. ::: Multi-tenancy is the ability of Camunda 8 to serve multiple distinct [tenants](/self-managed/components/management-identity/manage-tenants.md) or clients within a single installation. From version 8.3 onwards, Optimize has been enhanced to support multi-tenancy for Self-Managed setups. More information about the feature can be found in the [multi-tenancy concepts](/components/concepts/multi-tenancy.md). Optimize imports the relevant tenant information from Zeebe records and retrieves each user's tenant authorizations from Identity, so that the logged-in users only have access to the data on tenants that they are authorized to see in Identity. Because tenant authorizations are cached in Optimize to improve performance, there could be a delay until any changes made to tenant authorizations in Identity are visible in Optimize. ## Default tenant authorizations in Optimize If multi-tenancy is enabled across components, users will be allowed to view any data from tenants for which they have authorizations configured in Identity. If multi-tenancy is disabled in Optimize, all users will be allowed to view data from the `` tenant only and no data from other tenants. If multi-tenancy is enabled in Optimize, but disabled in Identity or Identity is not reachable for other reasons, users will not have any tenant authorizations in Optimize and will not be able to access the data of any tenants in Optimize. ## Configuration In a Self-Managed Camunda 8 environment, the following two configurations settings are required for multi-tenancy: | YAML path | Environment variable | Default value | Description | | ---------------------------- | --------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------ | | `multitenancy.enabled` | `CAMUNDA_OPTIMIZE_MULTITENANCY_ENABLED` | false | Enables the Camunda 8 multi-tenancy feature in Optimize. | | `security.auth.ccsm.baseUrl` | `CAMUNDA_OPTIMIZE_IDENTITY_BASE_URL` | null | The internal URL of the Management Identity service, reachable by Optimize from within the deployment environment. | The `CAMUNDA_OPTIMIZE_MULTITENANCY_ENABLED` environment variable enables the feature in Optimize. The multi-tenancy feature must be enabled in all other components as well using their respective multi-tenancy feature flags. Set `CAMUNDA_OPTIMIZE_IDENTITY_BASE_URL` to the Management Identity service URL. Optimize uses this to retrieve tenant authorizations. The URL must be the internal cluster URL reachable by Optimize from within the deployment environment; external-facing URLs may not be accessible from inside the cluster. If this base URL is not configured, Optimize cannot retrieve tenant authorizations and users cannot access any tenant's data in Optimize. If required, the tenant authorization cache in Optimize can also be configured via these optional settings: | YAML path | Environment variable | Default value | Description | | --------------------------------------------------- | -------------------------------------------------------------------------------- | ------------- | ------------------------------------------------------------------ | | `caches.cloudTenantAuthorizations.maxSize` | `CAMUNDA_OPTIMIZE_CACHES_CLOUD_TENANT_AUTHORIZATIONS_MAX_SIZE` | 10000 | The maximum size of the Camunda 8 tenant authorizations cache. | | `caches.cloudTenantAuthorizations.defaultTtlMillis` | `CAMUNDA_OPTIMIZE_CACHES_CLOUD_TENANT_AUTHORIZATIONS_MIN_FETCH_INTERVAL_SECONDS` | 300000 | The time in milliseconds the tenant authorizations will be cached. | ### Troubleshooting To ensure seamless integration and functionality, the multi-tenancy feature must also be enabled across **all** associated components [if not configured in Helm](/self-managed/deployment/helm/configure/configure-logical-tenants.md) so users can view any data from tenants for which they have authorizations configured in Identity. --- ## Object and list variable support ## Object variables Complex object variables can be imported into Optimize and thereafter be used in reports and filters. During import, Optimize flattens the given object variable to create individual variables for each property of the object, resulting in multiple "sub variables" for each imported object variable. For example, an object variable called `user` with the properties `firstName` and `lastName` will result in two flattened variables: `user.firstName` and `user.lastName`. These variables can be used within reports and filters. In addition to the flattened properties, Optimize also imports the entire raw value of the object variable. In the example above, this creates a variable called `user` with the value `{"firstName": "John", "lastName": "Smith"}`. You can inspect this raw object variable in Raw Data Reports, but other report types and filters do not support it. ## List variables Optimize also supports object variables that are JSON-serialized lists of primitive types, such as a list of strings or numbers. For Camunda 7 and external variables, the `type` of list variables must still be set to `Object`. During import, Optimize evaluates the number of entries in each list and persists it in an additional `_listSize` variable. For example, a list variable with the name `users` and the values `["John Smith", "Jane Smith"]` will result in two imported variables: one `users` variable with the two given values, and one variable called `users._listSize` with value `2`. Both can be used in reports and filters. However, filters are not yet fully optimized for list support, and some filter terms may be initially misleading. This is because filters currently apply to each list item individually rather than the entire list. For example, an "is" filter on a list of string values filters for those instances where any individual list item is equal to the given term, for example, instances whose list variable "contains" the selected value. Similarly, the "contains" filter matches process instances whose list variable contains at least one value which in turn contains the given substring. The value of list properties within objects as well as variables which are lists of objects rather than primitives can be inspected in the raw object variable value column accessible in raw data reports. ## Optimize configuration As of Camunda 8.10, the import of object variable values is disabled by default. It can be enabled using the `zeebe.includeObjectVariableValue` configuration. Alternatively, this can be set using the `CAMUNDA_OPTIMIZE_ZEEBE_INCLUDE_OBJECT_VARIABLE` environment variable. When enabled, each flattened property and the raw object itself are stored as separate variables. As a result, object-heavy processes can significantly increase Optimize's storage and CPU usage. See [Impact of Optimize](/components/best-practices/architecture/sizing-your-environment.md#impact-of-optimize) for sizing guidance. When disabled (the default), Optimize logs a `WARN` on startup as a reminder, and object variables are neither flattened nor stored. Depending on where the imported object variables originate, the following configuration is required to ensure that your system produces object variable data that Optimize can import correctly: If you are creating object variables using a Zeebe process, ensure date properties within the JSON object are stored using a common **date format** (for example `yyyy-MM-dd'T'HH:mm:ss.SSSZ`) other than unix timestamps. If Optimize imports unix timestamp date properties, these properties cannot be identified and parsed as dates and will instead be persisted as number variables. External variables of type object require an additional field called `serializationDataFormat` which specifies which data format was used to serialize the given object. Refer to the [external object variable API section](../../../../apis-tools/optimize-api/external-variable-ingestion.md) for further details on how to ingest external variables. --- ## Optimize export filtering Filter which processes and variables the [Elasticsearch](/self-managed/components/orchestration-cluster/zeebe/exporters/elasticsearch-exporter.md) and [OpenSearch](/self-managed/components/orchestration-cluster/zeebe/exporters/opensearch-exporter.md) exporters write for Optimize to reduce storage costs and scope analytics data. :::note Exporter-side filters require Camunda 8.9 or later. On earlier versions, exporters always write a complete, unfiltered event stream. ::: ## How export filtering works The Elasticsearch and OpenSearch exporters run inside Zeebe brokers and write raw engine events to export indices. Optimize reads those indices and builds its own analytics indices. Export filters run **inside the exporter** and permanently drop matching records from the exported stream. Optimize cannot import data that was never exported, and dropped records cannot be recovered later, even if you relax the filters afterward. **These filters affect Optimize only.** Operate and Tasklist read through the Camunda Exporter, so their data stays intact regardless of what you configure here. ## Process definition filtering Use process definition filters to include or exclude entire processes from Optimize. Records tied to excluded processes (including process instances, variables, and incidents) are dropped before they reach Optimize's import indices. This is useful when you have high-volume processes that don't need analytics in Optimize. | Goal | Option | | ------------------------------ | ------------------------ | | Export only specific processes | `bpmnProcessIdInclusion` | | Exclude specific processes | `bpmnProcessIdExclusion` | Both options can coexist in the same configuration, with exclusion taking precedence when an ID appears in both lists. Value types without a `bpmnProcessId`, such as `DEPLOYMENT` and `DECISION`, are not affected by these filters. **Example** ```yaml camunda: data: exporters: elasticsearch: args: index: bpmnProcessIdInclusion: - orderProcess bpmnProcessIdExclusion: - debugProcess ``` ```yaml camunda: data: exporters: opensearch: args: index: bpmnProcessIdInclusion: - orderProcess bpmnProcessIdExclusion: - debugProcess ``` ## Variable filtering Use variable filtering to control which variables are exported to Optimize. It is an effective mitigation for Optimize's impact on Elasticsearch or OpenSearch sizing. See [impact of Optimize](/components/best-practices/architecture/sizing-your-environment.md#impact-of-optimize) for details. You can disable variable export entirely, filter by name, or filter by value type. ### Disable all variable export Set `variable: false` to stop the exporter from writing any variable records to Optimize indices. ```yaml camunda: data: exporters: elasticsearch: args: index: variable: false ``` ```yaml camunda: data: exporters: opensearch: args: index: variable: false ``` ### Filter by variable name Use name-based inclusion and exclusion lists to export only the variables you need. You can match by exact name, prefix (`startsWith`), or suffix (`endsWith`). Exclusion wins over inclusion when both rules match the same variable name. | Goal | Options | | -------------------------- | ---------------------------------------------------------------------------------------------- | | Include specific variables | `variableNameInclusionExact`, `variableNameInclusionStartWith`, `variableNameInclusionEndWith` | | Exclude specific variables | `variableNameExclusionExact`, `variableNameExclusionStartWith`, `variableNameExclusionEndWith` | **Example** ```yaml camunda: data: exporters: elasticsearch: args: index: variableNameInclusionStartWith: - business variableNameExclusionStartWith: - business_debug ``` ```yaml camunda: data: exporters: opensearch: args: index: variableNameInclusionStartWith: - business variableNameExclusionStartWith: - business_debug ``` ### Filter by variable type Use type-based inclusion and exclusion lists to filter variables by their inferred JSON type. Valid types are `String`, `Number`, `Boolean`, `Object`, and `Null`. | Goal | Options | | --------------------------- | ---------------------------- | | Include specific types only | `variableValueTypeInclusion` | | Exclude specific types | `variableValueTypeExclusion` | **Example** ```yaml camunda: data: exporters: elasticsearch: args: index: variableValueTypeInclusion: - String variableValueTypeExclusion: - Object ``` ```yaml camunda: data: exporters: opensearch: args: index: variableValueTypeInclusion: - String variableValueTypeExclusion: - Object ``` ## Trade-offs Filtered data is permanently unavailable in Optimize: - **Process filtering:** Excluded processes don't appear in Optimize reports. If you later re-enable a process, Optimize shows a permanent gap for the period when it was excluded, because records from that window were never exported. - **Variable filtering:** Filtered variables are unavailable in Optimize reports, including variable filters, variable-based grouping, and raw-data variable columns. For guidance on non-retroactivity, changing filters on running clusters, and safely applying filters mid-stream, see [Camunda 8 system configuration](./system-configuration-platform-8.md#exporter-side-filters-and-optimize-data-completeness). --- ## Process definition data deletion Optimize processes [process definition data deletion](/apis-tools/optimize-api/delete-process-definition-data.md) requests asynchronously, using a persisted job registry and a background dispatcher. ## Job registry dispatcher The dispatcher polls the job registry for queued deletion requests and executes them in the background. The dispatcher is disabled by default. Enable it on only one Optimize instance per cluster. Enabling it on more than one instance can lead to race conditions, such as the same job running more than once. See the [job registry dispatcher settings](./system-configuration.md#job-registry-dispatcher) for the available configuration. ## Deleted process definition cache As long as a process definition deletion job record exists in the job registry, Optimize suppresses reimporting data for that process definition. To minimize the impact of the suppression check on the import path and on Elasticsearch/OpenSearch, the suppression is backed by a cache of the most recently deleted process definition IDs, sized by `caches.deletedProcessDefinitions.maxSize`. If more process definitions have pending or completed deletions than fit in this cache, the oldest ones fall out of suppression and their data can be reimported. See the [deleted process definition cache settings](./system-configuration.md#deleted-process-definition-cache) for the available configuration. --- ## Security instructions This page provides an overview of how to secure a Camunda Optimize installation. For Camunda's security policy, a list of security notices, and a guide on how to report vulnerabilities, visit the [general security documentation](https://docs.camunda.org/security/). This guide also identifies areas where we consider security issues to be relevant for the Camunda Optimize product and list those in the subsequent sections. Compliance for those areas is ensured based on common industry best practices and influenced by security requirements of standards like OWASP Top 10 and others. For how Optimize authenticates users and API requests, see [Optimize authentication in Self-Managed](/self-managed/concepts/authentication/authentication-to-optimize.md). Optimize already comes with a myriad of settings and security mechanism by default. In the following you will find the parts that still need manual adjustments. ## Disable HTTP For security reasons, we recommend using Optimize over HTTPS and disabling HTTP. You can disable HTTP by setting the HTTP property in the container settings to an empty/null value. Consult the respective section in the [configuration guide](./system-configuration.md#container) for the more details. ## Fine tune Optimize security headers Over time, various client-side security mechanisms have been developed to protect web applications from various attacks. Some of these security mechanisms are only activated if the web application sends the corresponding HTTP headers in its server responses. Optimize adds several of these headers which can be fine-tuned in the [configuration](./system-configuration.md#security) to ensure appropriate security. Optimize stores its data in Elasticsearch or OpenSearch, which are search engines that act as document-store backends. To protect access to this data, the database should be configured carefully as well. Refer to the official security guidelines for [ElasticSearch](https://www.elastic.co/guide/en/elasticsearch/reference/master/secure-cluster.html#secure-cluster) or [OpenSearch](https://opensearch.org/docs/latest/getting-started/security). Within the Optimize configuration, you can then enable SSL and/or the credentials to be used when Camunda Optimize connects to the database. See [Elasticsearch Security](./system-configuration.md#elasticsearch-security) or [OpenSearch Security](./system-configuration.md#opensearch-security) for details. --- ## Shared Elasticsearch/OpenSearch cluster In case you have a large shared Elasticsearch/OpenSearch cluster that you want to operate multiple Optimize instances on that are intended to run in complete isolation from each other, it is required to change the `*.settings.index.prefix` setting ([Elasticsearch](./system-configuration.md#elasticsearch-index-settings), [OpenSearch](./system-configuration.md#opensearch-index-settings)) for each Optimize instance. :::note Although a shared Elasticsearch/OpenSearch cluster setup is possible, it's recommended to operate a dedicated Elasticsearch/OpenSearch cluster per Optimize instance. A dedicated cluster provides the highest reliability (no resource sharing and no breaking side effects due to misconfiguration) and flexibility (for example, Elasticsearch/OpenSearch and Optimize upgrades can be performed independently between different Optimize setups). ::: The following illustration demonstrates this use case with two Optimize instances that connect to the same Elasticsearch/OpenSearch cluster but are configured with different `*.settings.index.prefix` values. This results in different indexes and aliases created on the cluster, strictly isolating the data of both Optimize instances, so no instance accesses the data of the other instance. To deploy two Optimize instances against one Camunda 8.10 orchestration cluster with Helm, follow [deploy multiple Optimize instances with Helm](/self-managed/deployment/helm/operational-tasks/deploy-multiple-optimize-instances.md). :::warning Changing the value of `*.settings.index.prefix` after an instance was already running results in new indexes being created with the new prefix value. There is no support for migrating data between indexes based on different prefixes. ::: \* Elasticsearch index prefix settings path: `es.settings.index.prefix` \* OpenSearch index prefix settings path: `opensearch.settings.index.prefix` ![Shared Elasticsearch Cluster Setup](img/shared-elasticsearch-cluster.png) --- ## Camunda 8 system configuration ### General settings Optimize imports process, variable, incident, and user task data from the Zeebe Elasticsearch or OpenSearch exporters in the Orchestration Cluster. For exporter configuration options (including filters), see: - [Elasticsearch exporter](/self-managed/components/orchestration-cluster/zeebe/exporters/elasticsearch-exporter.md). - [OpenSearch exporter](/self-managed/components/orchestration-cluster/zeebe/exporters/opensearch-exporter.md). The settings below control how Optimize connects to and paginates this exporter data. | YAML path | Default value | Description | | ----------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | zeebe.enabled | false | Toggles whether Optimize should attempt to import data from the connected Zeebe instance. | | zeebe.name | zeebe-record | The index prefix used for exported Zeebe records. This must match the `index.prefix` configured in the Elasticsearch or OpenSearch exporter that Optimize reads from. | | zeebe.partitionCount | 1 | The number of partitions configured for the Zeebe record source. | | zeebe.maxImportPageSize | 200 | The max page size for importing Zeebe data. Increasing it can help Optimize catch up faster when imports lag, but it uses more memory per fetch; decreasing it reduces memory use and per-query load on Elasticsearch/OpenSearch, but it can increase import lag. | ### Exporter-side filters and Optimize data completeness This section describes how exporter-side filters affect Optimize data imports and data completeness. For YAML configuration details and property syntax, refer to the [Elasticsearch](/self-managed/components/orchestration-cluster/zeebe/exporters/elasticsearch-exporter.md#configuration) and [OpenSearch](/self-managed/components/orchestration-cluster/zeebe/exporters/opensearch-exporter.md#configuration) exporter documentation. Starting from Camunda 8.9, the Elasticsearch and OpenSearch exporters provide optional filters that can reduce the amount of data written for Optimize: - Variable names: Inclusion and exclusion lists with match modes such as exact, starts with, and ends with. - Variable value types: Inclusion and exclusion lists for inferred types such as `String`, `Number`, `Boolean`, `Object` and `Null`. - Variable scope: Disable export of all local-scope variables (`export-local-variables-enabled: false`) and apply separate name/type inclusion and exclusion lists for root vs. local variables. - BPMN process IDs: Inclusion and exclusion lists by `bpmnProcessId` that drop all records tied to selected processes. - Optimize mode: Keeps only the record value types and intents required by Optimize and drops other record types not used by Optimize. :::warning These filters run **inside the exporter** and permanently drop matching records from the exported stream. Optimize cannot import data that was never exported, and dropped records cannot be recovered later, even if you relax the filters. ::: #### Non‑retroactive filters and permanent gaps Exporter-side filters are not retroactive: - Filters only affect records produced after the configuration change. - If a process is excluded (or not included) for some period and later re‑enabled, records from the excluded period were never exported. - As a result, Optimize will always show a permanent gap in that time window for the affected process, even if you later remove the filter or inclusion list. The same principle applies to variable‑name and variable‑type filters and other exporter-side filters: records that were dropped during a given time window cannot be recovered in Optimize, even if you subsequently relax the filters. #### Changing filters on existing clusters (sequence vs position) On clusters that have already exported data to Optimize, enabling or changing exporter-side filters mid‑stream can cause Optimize to miss some re‑exported events. As long as the exporter configuration (including any existing filters) remains unchanged, the exporter assigns a monotonically increasing `sequence` to each exported record, and Optimize can reliably resume imports from the “last seen” sequence. The Elasticsearch/OpenSearch exporter uses at-least-once delivery: after a failover, restart, or snapshot replay, it may export the same event again. If you change exporter-side filters between the original export and the re‑export, some records that were previously exported may now be filtered out. The remaining records are renumbered in this shorter stream. As a result, an event that Optimize already imported can reappear with a different sequence and be skipped when Optimize resumes from the previous “last seen” sequence. To keep Optimize imports consistent and avoid gaps, we recommend changing exporter-side filters only when the exporter has no pending records and Optimize has already imported all available data from that exporter. #### Supported versions - Exporter-side filters and Optimize mode are introduced in Camunda 8.9 and are not backported to earlier 8.x versions. - On clusters running earlier versions, the exporters always write an unfiltered event stream and this section does not apply. #### Required record types for Optimize Optimize requires at least the following record value types to populate standard reports: - `PROCESS` - `PROCESS_INSTANCE` - `INCIDENT` - `USER_TASK` - `VARIABLE` Starting with Camunda 8.10, `index.optimizeModeEnabled` defaults to `true`, which automatically restricts exported record types to those required by Optimize. If you disable `index.optimizeModeEnabled`, ensure these record value types remain enabled in the exporter configuration. If exporter settings or Optimize mode disable these value types, Optimize data and reports will become incomplete or fail to load. ### Licensing Camunda 8 Self-Managed only Installations of Camunda 8 Self-Managed which require a license can provide their license key to the components as an environment variable: | Environment variable | Description | Default value | | --------------------- | -------------------------------------------------------------------- | ------------- | | `CAMUNDA_LICENSE_KEY` | Your Camunda 8 license key, if your installation requires a license. | None | For Helm installations, license keys can be configured globally in your `values.yaml` file. See [License key](/self-managed/deployment/helm/configure/license-key.md) for more details. :::note Camunda 8 components without a valid license may display **Non-Production License** in the navigation bar and issue warnings in the logs. These warnings have no impact on Optimize startup or functionality. To obtain a license, visit the [Camunda Enterprise page](https://camunda.com/platform/camunda-platform-enterprise-contact/). ::: ### Settings required for multi-tenancy Camunda 8 Self-Managed only For more information on multi-tenancy in Camunda 8 Self-Managed environments, refer to [this page](./multi-tenancy.md). To use multi-tenancy, the feature must be enabled across all components. | YAML path | Default value | Description | | -------------------------- | ------------- | -------------------------------------------------------- | | multitenancy.enabled | false | Enables the Camunda 8 multi-tenancy feature in Optimize. | | security.auth.ccsm.baseUrl | null | The base URL of Identity. | ### Settings related to Camunda user tasks (formerly Zeebe user tasks) Camunda 8 Self-Managed only For more information on user task reporting in Camunda 8 Self-Managed, refer to our [user task analytics documentation](/components/optimize/userguide/process-analysis/user-task-analytics.md). | YAML path | Default value | Description | | ----------------------------------- | ------------- | -------------------------------------------------------------------- | | ui.userTaskAssigneeAnalyticsEnabled | true | Enables assignee based analytics in Camunda 8 Self-Managed Optimize. | --- ## Overview(Configuration) All distributions of Camunda Optimize come with a predefined set of configuration options that can be overwritten by the user, based on current environment requirements. To do that, have a look into the folder named `config` which contains a file called `environment-config.yaml` with values that override the default Optimize properties. You can see a sample configuration file with all possible configuration fields and their default values [here](service-config.yaml). In the following section, you will find descriptions and default values of the configuration fields with their respective YAML path and environment variable. :::note Heads Up For changes in the configuration to take effect, you need to restart Optimize! ::: ### Java system properties & OS environment variable placeholders To externalize configuration properties from the `environment-config.yaml`, Optimize provides variable placeholder support. The order in which placeholders are resolved is the following: 1. Java system properties 2. OS environment variables The placeholder format is `${VARIABLE_NAME}` and allows you to refer to a value of a Java system property or OS environment variable of your choice. The `VARIABLE_NAME` is required to contain only lowercase or uppercase letters, digits and underscore `_` characters and shall not begin with a digit. The corresponding regular expression is `([a-zA-Z_]+[a-zA-Z0-9_]*)`. The following example illustrates the usage: ``` security: auth: token: secret: ${AUTH_TOKEN_SECRET} ``` Given this variable is set before Optimize is started, for example on Unix systems with: ``` export AUTH_TOKEN_SECRET=sampleTokenValue ``` The value will be resolved at startup to `sampleTokenValue`. However, if the same variable is provided at the same time as a Java system property, for example via passing `-DAUTH_TOKEN_SECRET=othertokenValue` to the Optimize startup script: ``` ./optimize-startup.sh -DAUTH_TOKEN_SECRET=othertokenValue ``` The value would be resolved to `othertokenValue` as Java system properties have precedence over OS environment variables. :::note For Windows users, to pass Java system properties to the provided Windows Batch script `optimize-startup.bat`, you have to put them into double quotes when using the `cmd.exe` shell, as shown below. ::: ``` optimize-startup.bat "-DAUTH_TOKEN_SECRET=othertokenValue" ``` For the Windows Powershell in three double quotes: ``` ./optimize-startup.bat """-DAUTH_TOKEN_SECRET=othertokenValue""" ``` #### Default values For variable placeholders it's also possible to provide default values using the following format: `${VARIABLE_NAME:DEFAULT_VALUE}`. The `DEFAULT_VALUE` can contain any character except `}`. The following example illustrates the usage: ``` security: auth: token: secret: ${AUTH_TOKEN_SECRET:defaultSecret} ``` ### Security These values control mechanisms of Optimize related security, e.g. security headers and authentication. The table marks the settings that Camunda 8.10 deprecates or no longer uses. For the full mapping to `camunda.security.*`, see [component-specific configuration keys](/self-managed/upgrade/components/890-to-8100.md#component-specific-security-configuration-keys-are-deprecated). | YAML path | Environment variable | Default value | Description | | ------------------------------------------------ | ------------------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | security.auth.token.lifeMin | | 60 | Not used in 8.10 unless you opt into the 8.9 component-specific configuration fallback. Optimize uses token-based authentication to keep track of which users are logged in. Define the lifetime of the token in minutes. | | security.auth.token.secret | CAMUNDA_OPTIMIZE_SECURITY_AUTH_TOKEN_SECRET | null | Not used in 8.10 unless you opt into the 8.9 component-specific configuration fallback. Optional secret used to sign authentication tokens, it's recommended to use at least a 64-character secret. If set to `null` a random secret will be generated with each startup of Optimize. | | security.responseHeaders.HSTS.max-age | CAMUNDA_OPTIMIZE_SECURITY_RESPONSE_HEADERS_HSTS_MAX_AGE | 63072000 | Deprecated in 8.10. Use `camunda.security.http-headers.hsts.max-age-in-seconds`. HTTP Strict Transport Security (HSTS) is a web security policy mechanism which helps to protect websites against protocol downgrade attacks and cookie hijacking. This field defines the time, in seconds, that the browser should remember that this site is only to be accessed using HTTPS. If you set the number to a negative value no HSTS header is sent. | | security.responseHeaders.HSTS.includeSubDomains | | true | HTTP Strict Transport Security (HSTS) is a web security policy mechanism which helps to protect websites against protocol downgrade attacks and cookie hijacking. If this optional parameter is specified, this rule applies to all the site’s subdomains as well. | | security.responseHeaders.X-XSS-Protection | | 1; mode=block | Not used in 8.10. This header enables the cross-site scripting (XSS) filter in your browser. Can have one of the following options: `0`: Filter disabled. `1`: Filter enabled. If a cross-site scripting attack is detected, in order to stop the attack, the browser will sanitize the page. `1; mode=block`: Filter enabled. Rather than sanitize the page, when a XSS attack is detected, the browser will prevent rendering of the page. `1; report=http://[YOURDOMAIN]/your_report_URI`: Filter enabled. The browser will sanitize the page and report the violation. This is a Chromium function utilizing CSP violation reports to send details to a URI of your choice. | | security.responseHeaders.X-Content-Type-Options | | true | Setting this header will prevent the browser from interpreting files as a different MIME type to what is specified in the Content-Type HTTP header (e.g. treating text/plain as text/css). | | security.responseHeaders.Content-Security-Policy | | base-uri 'self' | A Content Security Policy (CSP) has significant impact on the way browsers render pages. By default Optimize uses the base-uri directive which restricts the URLs that can be used to the Optimize pages. Find more details in [Mozilla's Content Security Policy Guide](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy). | ### Import These values relate to Optimize data import. | YAML path | Environment variable | Default value | Description | | ------------------------------------------ | --------------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | import.skipDataAfterNestedDocLimitReached | CAMUNDA_OPTIMIZE_IMPORT_DATA_SKIP_DATA_AFTER_NESTED_DOC_LIMIT_REACHED | false | Some data can no longer be imported to a given document if its number of nested documents has reached the configured limit. Enable this setting to skip this data during import if the nested document limit has been reached. | | import.currentTimeBackoffMilliseconds | | 300000 | The time interval the import backs off from the current tip of the time, to reread potentially missed concurrent writes. | | import.elasticsearchJobExecutorThreadCount | | 1 | Number of threads being used to process the import jobs per data type that are writing data to Elasticsearch. | | import.elasticsearchJobExecutorQueueSize | | 5 | Adjust the queue size of the import jobs per data type that store data to Elasticsearch. A too large value might cause memory problems. | ### External API These values control how Optimize validates tokens on its External REST API. In Camunda 8.10, the External REST API accepts only OIDC bearer tokens. Configure `camunda.security.authentication.oidc.issuer-uri` and `camunda.security.authentication.oidc.audiences`. Optimize gets the JWK set URI from the OIDC discovery document of the issuer. To use a different URI, set `camunda.security.authentication.oidc.jwk-set-uri`. The External REST API needs OIDC configuration. Without it, Optimize rejects all API requests. For the upgrade steps, see [Optimize static API access token is no longer supported](/reference/announcements-release-notes/8100/8100-announcements.md#optimize-static-api-access-token-is-no-longer-supported). :::note `api.jwtSetUri` and `api.audience` still work in 8.10, from either the YAML path or the environment variable, but they are deprecated. Camunda plans to remove them in a future release. Behavior differs from 8.9 in three ways: - The `optimize` default shown below is not carried over. Only an audience you set explicitly takes effect, so if you configure no audience at all, any audience is accepted, and if you configure a different audience but not this one, a token audienced `optimize` is rejected. Set the audience explicitly rather than relying on the default. - A JWK set applies to the whole API surface rather than to the public API and ingestion endpoints alone, so a token signed by it is accepted on any `/api/**` endpoint that its audience allows. - `api.jwtSetUri` is added alongside the JWK set that verifies logins instead of replacing it, so configuring a separate identity provider for your API no longer affects logging in to Optimize. This only works where no `camunda.security.authentication.oidc.issuer-uri` is configured. With one set, tokens from any other issuer are rejected regardless of which JWK set can verify them. ::: | YAML path | Environment variable | Default value | Description | | --------------- | ----------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | api.accessToken | OPTIMIZE_API_ACCESS_TOKEN | null | Not functional in 8.10 unless you opt into the 8.9 component-specific configuration fallback, which Camunda plans to remove in a future release. Through 8.9, a static shared token that a client sends to the secured REST API in the authorization header. | | api.jwtSetUri | SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI | null | Deprecated in 8.10. Use `camunda.security.authentication.oidc.additional-jwk-set-uris`. Complete URI of the public keys for JWT validation. | | api.audience | CAMUNDA_OPTIMIZE_API_AUDIENCE | optimize | Deprecated in 8.10. Use `camunda.security.authentication.oidc.audiences`. Camunda 8.10 does not apply this default. Optimize matches this value with the `aud` field in the JWT. | ### Container Settings related to embedded Jetty container, which serves the Optimize application. | YAML path | Environment variable | Default value | Description | | -------------------------------- | ------------------------------------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | container.host | CONTAINER_HOST | localhost | A host name or IP address to identify a specific network interface on which to listen. | | container.contextPath | CAMUNDA_OPTIMIZE_CONTEXT_PATH | null | Allows you to specify a custom context path. If set, must start with a leading '/' | | container.ports.http | CAMUNDA_OPTIMIZE_CONTAINER_PORTS_HTTP | 8090 | A port number that will be used by Optimize to process HTTP connections. If set to null, or left empty, HTTP connections won't be accepted. | | container.ports.https | CAMUNDA_OPTIMIZE_CONTAINER_PORTS_HTTPS | 8091 | A port number that will be used by Optimize to process secure HTTPS connections. | | container.ports.actuator | MANAGEMENT_SERVER_PORT | 8092 | A port number that will be used by Optimize's Actuator management server, defaults to 8092 | | container.keystore.location | CAMUNDA_OPTIMIZE_CONTAINER_KEYSTORE_LOCATION | keystore.jks | HTTPS requires an SSL Certificate. When you generate an SSL Certificate, you are creating a keystore file and a keystore password for use when the browser interface connects. This field specifies the location of this keystore file. | | container.keystore.password | CAMUNDA_OPTIMIZE_CONTAINER_KEYSTORE_PASSWORD | optimize | Password of keystore file. | | container.status.connections.max | CAMUNDA_OPTIMIZE_CONTAINER_STATUS_CONNECTIONS_MAX | 10 | Maximum number of web socket connections accepted for status report. | | container.accessUrl | CONTAINER_ACCESS_URL | null | Optional URL to access Optimize (used for links to Optimize in e.g. alert emails). If no value specified the container host and port are used instead. | | container.http2Enabled | CAMUNDA_OPTIMIZE_CONTAINER_HTTP2_ENABLED | false | Enable use of HTTP/2 for Optimize | | container.enableSniCheck | CAMUNDA_OPTIMIZE_CONTAINER_ENABLE_SNI_CHECK | true | Determines whether SNI checking should be enabled. | ### Elasticsearch These settings are only relevant when operating Optimize with Elasticsearch. #### Elasticsearch connection settings Everything that is related to building the connection to Elasticsearch. Please note that you can define a number of connection points in a cluster. Therefore, everything that is under `es.connection.nodes` is a list of nodes Optimize can connect to. If you have built an Elasticsearch cluster with several nodes it is recommended to define several connection points so that if one node fails, Optimize is still able to talk to the cluster. | YAML path | Environment variable | Default value | Description | | --------------------------------------------- | -------------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | es.connection.timeout | | 10000 | Maximum time without connection to Elasticsearch that Optimize should wait until a timeout triggers. | | es.connection.responseConsumerBufferLimitInMb | | 100 | Maximum size of the Elasticsearch response consumer heap buffer. This can be increased to resolve errors from Elasticsearch relating to the entity content being too long | | es.connection.pathPrefix | | | The path prefix under which Elasticsearch is available. | | es.connection.nodes[*].host | OPTIMIZE_ELASTICSEARCH_HOST | localhost | The address/hostname under which the Elasticsearch node is available. | | es.connection.nodes[*].httpPort | OPTIMIZE_ELASTICSEARCH_HTTP_PORT | 9200 | A port number used by Elasticsearch to accept HTTP connections. | | es.connection.proxy.enabled | | false | Whether an HTTP proxy should be used for requests to Elasticsearch. | | es.connection.proxy.host | | null | The proxy host to use, must be set if es.connection.proxy.enabled = true. | | es.connection.proxy.port | | null | The proxy port to use, must be set if es.connection.proxy.enabled = true. | | es.connection.proxy.sslEnabled | | false | Whether this proxy is using a secured connection (HTTPS). | | es.connection.proxy.username | | null | Optional username for HTTP Basic proxy authentication. If both username and password are set, a preemptive `Proxy-Authorization` header is sent with each request. | | es.connection.proxy.password | | null | Optional password for HTTP Basic proxy authentication. | | es.connection.skipHostnameVerification | CAMUNDA_OPTIMIZE_ELASTICSEARCH_CONNECTION_SKIP_HOSTNAME_VERIFICATION | false | Determines whether the hostname verification should be skipped. | #### Elasticsearch index settings | YAML path | Environment variable | Default value | Description | | ---------------------------------------- | -------------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | es.settings.index.prefix | CAMUNDA_OPTIMIZE_ELASTICSEARCH_SETTINGS_INDEX_PREFIX | optimize | The prefix prepended to all Optimize index and alias names. Custom values allow to operate multiple isolated Optimize instances on one Elasticsearch cluster. NOTE: Changing this after Optimize was already run before will create new empty indexes. | | es.settings.index.number_of_replicas | CAMUNDA_OPTIMIZE_ELASTICSEARCH_SETTINGS_INDEX_NUMBER_OF_REPLICAS | 1 | How often data should be replicated to handle node failures. | | es.settings.index.number_of_shards | CAMUNDA_OPTIMIZE_ELASTICSEARCH_SETTINGS_INDEX_NUMBER_OF_SHARDS | 1 | How many shards should be used in the cluster for process instance and decision instance indices. All other indices will be made up of a single shard. Note: this property only applies the first time Optimize is started and the schema/mapping is deployed on Elasticsearch. If you want this property to take effect again, you need to delete all indices (and with that all data) and restart Optimize. | | es.settings.index.refresh_interval | | 2s | How long Elasticsearch waits until the documents are available for search. A positive value defines the duration in seconds. A value of -1 means that a refresh needs to be done manually. | | es.settings.index.nested_documents_limit | CAMUNDA_OPTIMIZE_ELASTICSEARCH_SETTINGS_INDEX_NESTED_DOCUMENTS_LIMIT | 10000 | Optimize uses nested documents to store list information such as activities or variables belonging to a process instance. This setting defines the maximum number of activities/variables/incidents that a single process instance can contain. This limit helps to prevent out of memory errors and should be used with care. See [troubleshooting](#troubleshooting) if you hit this limit, and refer to the Elasticsearch documentation on this topic for more information. | #### Elasticsearch Security Define a secured connection to be able to communicate with a secured Elasticsearch instance. | YAML path | Environment variable | Default value | Description | | --------------------------------------- | ------------------------------------------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | es.security.username | CAMUNDA_OPTIMIZE_ELASTICSEARCH_SECURITY_USERNAME | | The Basic authentication (x-pack) username. | | es.security.password | CAMUNDA_OPTIMIZE_ELASTICSEARCH_SECURITY_PASSWORD | | The Basic authentication (x-pack) password. | | es.security.ssl.enabled | CAMUNDA_OPTIMIZE_ELASTICSEARCH_SSL_ENABLED | false | Used to enable or disable TLS/SSL for the HTTP connection. | | es.security.ssl.certificate | CAMUNDA_OPTIMIZE_ELASTICSEARCH_SECURITY_SSL_CERTIFICATE | | The path to a PEM encoded file containing the certificate (or certificate chain) that will be presented to clients when they connect. | | es.security.ssl.certificate_authorities | CAMUNDA_OPTIMIZE_ELASTICSEARCH_SECURITY_SSL_CERTIFICATE_AUTHORITIES | [ ] | A list of paths to PEM encoded CA certificate files that should be trusted, e.g. ['/path/to/ca.crt']. Note: if you are using a public CA that is already trusted by the Java runtime, you do not need to set the certificate_authorities. | | es.security.ssl.selfSigned | CAMUNDA_OPTIMIZE_ELASTICSEARCH_SECURITY_SSL_SELF_SIGNED | false | Used to specify that the certificate was self-signed. | #### Elasticsearch backup settings | YAML path | Environment variable | Default value | Description | | ------------------------ | --------------------------------------- | ------------- | ------------------------------------------------------------------------ | | es.backup.repositoryName | CAMUNDA_OPTIMIZE_BACKUP_REPOSITORY_NAME | "" | The name of the snapshot repository to be used to back up Optimize data. | ### OpenSearch These settings are only relevant when operating Optimize with OpenSearch. #### OpenSearch connection settings This section details everything related to building the connection to OpenSearch. :::note You can define a number of connection points in a cluster. Therefore, everything under `opensearch.connection.nodes` is a list of nodes Optimize can connect to. If you have built an OpenSearch cluster with several nodes, it is recommended to define several connection points so if one node fails, Optimize is still able to talk to the cluster. ::: | YAML path | Environment variable | Default value | Description | | ---------------------------------------------- | ----------------------------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | | CAMUNDA_OPTIMIZE_DATABASE | `elasticsearch` | Type of database being used. Either `elasticsearch` or `opensearch` | | opensearch.connection.timeout | | 10000 | Maximum time without connection to OpenSearch that Optimize should wait until a timeout triggers. | | opensearch.connection.pathPrefix | | | The path prefix under which OpenSearch is available. | | opensearch.connection.nodes[*].host | CAMUNDA_OPTIMIZE_OPENSEARCH_HOST | localhost | The address/hostname under which the OpenSearch node is available. | | opensearch.connection.nodes[*].httpPort | CAMUNDA_OPTIMIZE_OPENSEARCH_HTTP_PORT | 9200 | A port number used by OpenSearch to accept HTTP connections. | | opensearch.connection.skipHostnameVerification | CAMUNDA_OPTIMIZE_OPENSEARCH_CONNECTION_SKIP_HOSTNAME_VERIFICATION | false | Determines whether the hostname verification should be skipped. | | opensearch.connection.awsEnabled | CAMUNDA_OPTIMIZE_OPENSEARCH_AWS_ENABLED | false | Determines if AWS credentials shall be used for authentication | | opensearch.connection.proxy.enabled | | false | Whether an HTTP proxy should be used for requests to OpenSearch. | | opensearch.connection.proxy.host | | null | The proxy host to use, must be set if opensearch.connection.proxy.enabled = true. | | opensearch.connection.proxy.port | | null | The proxy port to use, must be set if opensearch.connection.proxy.enabled = true. | | opensearch.connection.proxy.sslEnabled | | false | Whether this proxy is using a secured connection (HTTPS). | | opensearch.connection.proxy.username | | null | Optional username for HTTP Basic proxy authentication. If both username and password are set, a preemptive `Proxy-Authorization` header is sent with each request. | | opensearch.connection.proxy.password | | null | Optional password for HTTP Basic proxy authentication. | #### OpenSearch index settings | YAML path | Environment variable | Default value | Description | | ------------------------------------------------ | ----------------------------------------------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | opensearch.settings.index.prefix | CAMUNDA_OPTIMIZE_OPENSEARCH_SETTINGS_INDEX_PREFIX | optimize | The prefix prepended to all Optimize index and alias names. Custom values allow you to operate multiple isolated Optimize instances on one OpenSearch cluster. NOTE: Changing this after Optimize has already run will create new empty indexes. | | opensearch.settings.index.number_of_replicas | CAMUNDA_OPTIMIZE_OPENSEARCH_SETTINGS_INDEX_NUMBER_OF_REPLICAS | 1 | How often data should be replicated to handle node failures. | | opensearch.settings.index.number_of_shards | CAMUNDA_OPTIMIZE_OPENSEARCH_SETTINGS_INDEX_NUMBER_OF_SHARDS | 1 | How many shards should be used in the cluster for process instance and decision instance indices. All other indices will be made up of a single shard. NOTE: This property only applies the first time Optimize is started and the schema/mapping is deployed on OpenSearch. If you want this property to take effect again, you need to delete all indices (and with that all data) and restart Optimize. | | opensearch.settings.index.refresh_interval | | 2s | How long OpenSearch waits until the documents are available for search. A positive value defines the duration in seconds. A value of -1 means a refresh needs to be done manually. | | opensearch.settings.index.nested_documents_limit | CAMUNDA_OPTIMIZE_OPENSEARCH_SETTINGS_INDEX_NESTED_DOCUMENTS_LIMIT | 10000 | Optimize uses nested documents to store list information such as activities or variables belonging to a process instance. This setting defines the maximum number of activities, variables, or incidents that a single process instance can contain. This limit helps to prevent out of memory errors and should be used with care. See [troubleshooting](#troubleshooting) if you hit this limit, and refer to the OpenSearch documentation on this topic for more information. | #### OpenSearch security Define a secured connection to be able to communicate with a secured OpenSearch instance. | YAML path | Environment variable | Default value | Description | | ----------------------------------------------- | ---------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | opensearch.security.username | CAMUNDA_OPTIMIZE_OPENSEARCH_SECURITY_USERNAME | | The Basic authentication username. | | opensearch.security.password | CAMUNDA_OPTIMIZE_OPENSEARCH_SECURITY_PASSWORD | | The Basic authentication password. | | opensearch.security.ssl.enabled | CAMUNDA_OPTIMIZE_OPENSEARCH_SSL_ENABLED | false | Used to enable or disable TLS/SSL for the HTTP connection. | | opensearch.security.ssl.certificate | CAMUNDA_OPTIMIZE_OPENSEARCH_SECURITY_SSL_CERTIFICATE | | The path to a PEM encoded file containing the certificate (or certificate chain) that will be presented to clients when they connect. | | opensearch.security.ssl.certificate_authorities | CAMUNDA_OPTIMIZE_OPENSEARCH_SECURITY_SSL_CERTIFICATE_AUTHORITIES | [ ] | A list of paths to PEM encoded CA certificate files that should be trusted, for example ['/path/to/ca.crt']. NOTE: if you are using a public CA that is already trusted by the Java runtime, you do not need to set the certificate_authorities. | | opensearch.security.ssl.selfSigned | CAMUNDA_OPTIMIZE_OPENSEARCH_SECURITY_SSL_SELF_SIGNED | false | Used to specify that the certificate was self-signed. | #### OpenSearch backup settings | YAML path | Environment variable | Default value | Description | | -------------------------------- | --------------------------------------- | ------------- | ------------------------------------------------------------------------ | | opensearch.backup.repositoryName | CAMUNDA_OPTIMIZE_BACKUP_REPOSITORY_NAME | "" | The name of the snapshot repository to be used to back up Optimize data. | ### Email Settings for the email server to send email notifications, e.g. when an alert is triggered. | YAML path | Environment variable | Default value | Description | | ------------------------------------- | ------------------------------------------------------- | ------------- | --------------------------------------------------------------------------------------------------------------- | | email.enabled | CAMUNDA_OPTIMIZE_EMAIL_ENABLED | false | A switch to enable the email sending functionality. | | email.address | CAMUNDA_OPTIMIZE_EMAIL_ADDRESS | | Email address that can be used to send notifications. | | email.hostname | CAMUNDA_OPTIMIZE_EMAIL_HOSTNAME | | The smtp server name. | | email.port | CAMUNDA_OPTIMIZE_EMAIL_PORT | 587 | The smtp server port. This one is also used as SSL port for the security connection. | | email.checkServerIdentity | CAMUNDA_OPTIMIZE_EMAIL_CHECK_SERVER_IDENTITY | false | A switch to control checking the identity of the email server. | | email.authentication.enabled | CAMUNDA_OPTIMIZE_EMAIL_AUTHENTICATION_ENABLED | | A switch to enable email server authentication. | | email.authentication.username | CAMUNDA_OPTIMIZE_EMAIL_AUTHENTICATION_USERNAME | | Username of your smtp server. | | email.authentication.password | CAMUNDA_OPTIMIZE_EMAIL_AUTHENTICATION_PASSWORD | | Corresponding password to the given user of your smtp server. | | email.authentication.securityProtocol | CAMUNDA_OPTIMIZE_EMAIL_AUTHENTICATION_SECURITY_PROTOCOL | | States how the connection to the server should be secured. Possible values are 'NONE', 'STARTTLS' or 'SSL/TLS'. | ### Digest Settings influencing the process digest feature. | YAML path | Default value | Description | | ------------------ | --------------- | -------------------------------------------------------------------- | | digest.cronTrigger | 0 0 9 \* \* MON | Cron expression to define when enabled email digests are to be sent. | ### History cleanup settings Settings for automatic cleanup of historic process instances based on their end time. :::note Two types of history cleanup are available for Camunda 8 users at this time - process data cleanup and external variable cleanup. For more information, see [History cleanup](/self-managed/components/optimize/configuration/history-cleanup.md). ::: | YAML path | Environment variable | Default value | Description | | -------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | historyCleanup.cronTrigger | CAMUNDA_OPTIMIZE_HISTORY_CLEANUP_CRON_TRIGGER | `'0 1 * * *'` | Cron expression to schedule when the cleanup should be executed, defaults to 01:00 A.M. As the cleanup can cause considerable load on the underlying database, it is recommended to schedule it outside of office hours. You can either use the default Cron (5 fields) or the Spring Cron (6 fields) expression format here. | | historyCleanup.ttl | CAMUNDA_OPTIMIZE_HISTORY_CLEANUP_TTL | 'P2Y' | Global time-to-live (TTL) period for process/event data. The relevant property differs between entities. For process data, it's the `endDate` of the process instance. For ingested events, it's the `time` field. The format of the string is ISO_8601 duration. The default value is two years. For details on the notation, refer to [https://en.wikipedia.org/wiki/ISO_8601#Durations](https://en.wikipedia.org/wiki/ISO_8601#Durations) Note: The time component of the ISO_8601 duration is not supported. Only years (Y), months (M), and days (D) are supported. | | historyCleanup.processDataCleanup.enabled | CAMUNDA_OPTIMIZE_HISTORY_CLEANUP_PROCESS_DATA_CLEANUP_ENABLED | false | A switch to activate the history cleanup of process data. \[true/false\] | | historyCleanup.processDataCleanup.cleanupMode | CAMUNDA_OPTIMIZE_HISTORY_CLEANUP_PROCESS_DATA_CLEANUP_CLEANUP_MODE | 'all' | Global type of the cleanup to perform for process instances, possible values: 'all' - delete everything related and including the process instance that passed the defined TTL 'variables' - only delete variables of a process instance. | | historyCleanup.processDataCleanup.batchSize | CAMUNDA_OPTIMIZE_HISTORY_CLEANUP_PROCESS_DATA_CLEANUP_BATCH_SIZE | 10000 | Defines the batch size in which Camunda engine process instance data gets cleaned up. It may be reduced if requests fail due to request size constraints. In most cases, this should not be necessary and has only been experienced when connecting to an AWS Elasticsearch instance. | | historyCleanup.processDataCleanup.perProcessDefinitionConfig | | | A list of process definition specific configuration parameters that will overwrite the global cleanup settings for the specific process definition identified by its $\{key}. | | historyCleanup.processDataCleanup.perProcessDefinitionConfig.$\{key}.ttl | | | TTL to use for process instances of the process definition with the $\{key}. | | historyCleanup.processDataCleanup.perProcessDefinitionConfig.$\{key}.cleanupMode | | | Cleanup mode to use for process instances of the process definition with the $\{key}. | ### Job registry dispatcher Settings for the background dispatcher that processes queued asynchronous jobs. See [Process definition data deletion](./process-definition-deletion.md) for details. | YAML path | Environment variable | Default value | Description | | -------------------------------------- | --------------------------------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------- | | jobRegistry.dispatcher.enabled | CAMUNDA_OPTIMIZE_JOB_REGISTRY_DISPATCHER_ENABLED | false | Toggles whether this Optimize instance dispatches queued job registry entries to be processed by handlers. | | jobRegistry.dispatcher.intervalSeconds | CAMUNDA_OPTIMIZE_JOB_REGISTRY_DISPATCHER_INTERVAL_SECONDS | 30 | The delay, in seconds, between the end of one poll for queued jobs and the start of the next. | | jobRegistry.dispatcher.batchSize | CAMUNDA_OPTIMIZE_JOB_REGISTRY_DISPATCHER_BATCH_SIZE | 4 | The maximum number of queued job registry entries dispatched per poll cycle. | | jobRegistry.dispatcher.threadCount | CAMUNDA_OPTIMIZE_JOB_REGISTRY_DISPATCHER_THREAD_COUNT | 2 | The number of threads used to dispatch queued jobs concurrently. | ### Deleted process definition cache Settings for the cache that suppresses reimporting data for a process definitions with deletion job record. See [Process definition data deletion](./process-definition-deletion.md) for details. | YAML path | Environment variable | Default value | Description | | ------------------------------------------------- | -------------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------ | | caches.deletedProcessDefinitions.maxSize | CAMUNDA_OPTIMIZE_DELETED_PROCESS_DEFINITION_CACHE_MAX_SIZE | 10000 | The maximum number of deleted process definition IDs kept in the import-suppression cache. | | caches.deletedProcessDefinitions.defaultTtlMillis | CAMUNDA_OPTIMIZE_DELETED_PROCESS_DEFINITION_CACHE_DEFAULT_TTL_MILLIS | 300000 | The time, in milliseconds, the import-suppression cache is kept before it's refreshed from the job registry. | ### Localization Define the languages that can be used by Optimize. | YAML path | Default value | Description | | ------------------------ | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | locales.availableLocales | ['en','de'] | All locales available in the Optimize Frontend. Note: for languages other than the default there must be a `.json` file available under ./config/localization. | | locales.fallbackLocale | 'en' | The fallback locale used if there is a locale requested that is not available in availableLocales. The fallbackLocale is required to be present in locales.availableLocales. | ### UI configuration Customize the Optimize UI e.g. by adjusting the logo, head background color etc. | YAML path | Environment variable | Default value | Description | | ----------------------------- | ----------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | ui.logoutHidden | CAMUNDA_OPTIMIZE_UI_LOGOUT_HIDDEN | false | Setting this property to true will hide the logout option from the user menu. This is useful if you are using single sign-on and it is not possible for users to logout. | | ui.maxNumDataSourcesForReport | CAMUNDA_OPTIMIZE_UI_MAX_NUM_REPORT_DATA_SOURCES | 100 | The maximum number of data sources available for a report. The minimum value is two, the maximum is 1024. | ### External variable ingestion REST API configuration | YAML path | Default value | Description | | ----------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------- | | externalVariable.import.enabled | false | Controls whether external ingested variable data is processed and imported to process instance data. | | externalVariable.import.maxPageSize | 10000 | Determines the page size for the import of ingested external variable data to process instance data. | ### Other Settings of Sharing, Optimize entities, and CSV Export. | YAML path | Environment variable | Default value | Description | | -------------------------- | --------------------------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | entity.authorizedEditors | | 'all' | Define which users are authorized to Create, Edit, Copy and Delete Optimize entities outside of a collection. Available options: 'all', 'none'. | | entity.kpiRefreshInterval | | 600 | Define the interval in which the kpi import scheduler should run in seconds | | entity.nameMaxLength | CAMUNDA_OPTIMIZE_ENTITY_NAME_MAX_LENGTH | 32768 | Maximum number of characters allowed in the name of a report, collection, dashboard, or alert. Longer names are rejected with a validation error when the entity is created or updated. Lower this value to enforce a stricter naming policy. | | export.csv.authorizedUsers | | 'all' | Define which users are authorized to download CSVs. Available options: 'all', 'superuser', 'none'. | | export.csv.limit | | 1000 | Maximum number of records returned by CSV export. Note: Increasing this value comes at a memory cost for the Optimize application that varies based on the actual data. As a rough guideline, an export of a 50000 raw data report records containing 8 variables on each instance can cause temporary heap memory peaks of up to ~200MB with the actual CSV file having a size of ~20MB. Please adjust the heap memory accordingly, see [Adjust Optimize heap size](../overview.md#adjust-optimize-heap-size) on how to do that. | | export.csv.delimiter | | , | The delimiter used for the CSV export. The value defaults to a comma, however other common CSV delimiters such as semicolons (";") and tabs ("\\t") can also be used. | | sharing.enabled | CAMUNDA_OPTIMIZE_SHARING_ENABLED | true | Enable/disable the possibility to share reports and dashboards. | ### Troubleshooting #### Optimize reports `nested_limit_exceeded` **Observed behavior:** Optimize's [error metrics](/self-managed/operational-guides/monitoring/metrics.md#optimize-error-metrics) show `optimize_error_total{ERROR_TYPE="nested_limit_exceeded"}` increasing, or data stops appearing for very large process instances. **Why this happens:** A process instance's nested documents (activities, variables, or incidents) exceeded the configured [`nested_documents_limit`](#elasticsearch-index-settings) (default `10000`). Elasticsearch and OpenSearch cap nested documents per parent document to prevent out-of-memory errors. **How to fix:** - Raise `es.settings.index.nested_documents_limit` (or the [OpenSearch equivalent](#opensearch-index-settings)) if your process instances legitimately need more than the default. - Alternatively, enable [`import.skipDataAfterNestedDocLimitReached`](#import) to skip further data for that instance once the limit is reached, instead of the import failing. --- ## Enable or disable variable import Learn how to configure Camunda Optimize to control variable import and enhance performance. ## Overview By default, Camunda Optimize imports process variables to provide deep insights into both process performance and business context. However, in high-throughput environments or data-sensitive scenarios, importing variables may impact system performance and resource usage. Variable import affects several areas of Optimize’s operation: - **Import performance**: Importing large or numerous variables can slow down data import. - **Memory usage**: Variable data increases memory consumption during processing. - **Storage requirements**: Imported variables contribute to overall storage demands. - **Indexing duration**: Additional variable data extends indexing time. If your organization primarily focuses on process performance metrics rather than detailed business context, disabling variable import can help improve scalability and responsiveness. :::note Variable import controls what Optimize **imports** from already-exported data. To reduce what the broker **exports** in the first place (by variable name, type, BPMN process, or Optimize mode), use the [exporter-side filters](/self-managed/components/optimize/configuration/system-configuration-platform-8.md#exporter-side-filters-and-optimize-data-completeness). See [impact of Optimize](/components/best-practices/architecture/sizing-your-environment.md#impact-of-optimize) for guidance on sizing considerations and when to reduce Optimize data volume. ::: ## Optimize configuration Variable import is controlled using the `CAMUNDA_OPTIMIZE_ZEEBE_VARIABLE_IMPORT_ENABLED=true|false` environment variable. ### Configuration options | Value | Behavior | Recommended use case | | ------- | -------------------------------------------- | -------------------------------------------------------- | | `true` | Variables are imported and indexed (default) | Standard deployments requiring complete business context | | `false` | Variable import is disabled | High-throughput or performance-optimized environments | :::note Disabling variable import means variable-based reports and filters will no longer be available in the Optimize interface. Ensure this setting aligns with your reporting and monitoring needs before applying the configuration. ::: :::note When re-enabled, partial or full variable import may happen depending on your cluster [retention](/self-managed/components/orchestration-cluster/zeebe/exporters/elasticsearch-exporter.md#retention) configuration. ::: --- ## Optimize on Self-Managed All distributions of Optimize come with a predefined set of configuration options that can be overwritten by the user, based on current environment requirements. To do that, have a look into the folder named `config`. There are two files, one called `environment-config.yaml` with values that override the default Optimize properties and another called `environment-logback.xml`, which sets the logging configuration. You can see all supported values and read about logging configuration [here](./configuration/system-configuration.md). ## Optimize web container configuration Refer to the [configuration section on container settings](./configuration/system-configuration.md) for more information on how to adjust the Optimize web container configuration. ## Elasticsearch/OpenSearch configuration You can customize the connection settings for [Elasticsearch](./configuration/system-configuration.md#elasticsearch-connection-settings) or [OpenSearch](./configuration/system-configuration.md#opensearch-connection-settings), as well as the index settings for [Elasticsearch](./configuration/system-configuration.md#elasticsearch-index-settings) or [OpenSearch](./configuration/system-configuration.md#opensearch-index-settings). ## Camunda 8 specific configuration For Camunda 8, Optimize imports process data from Zeebe records exported by the [Elasticsearch exporter](../orchestration-cluster/zeebe/exporters/elasticsearch-exporter.md) or [OpenSearch exporter](../orchestration-cluster/zeebe/exporters/opensearch-exporter.md) from the same cluster that Optimize uses to store its own data. For the relevant Optimize import options and version support, refer to the [Camunda 8 system configuration](./configuration/system-configuration-platform-8.md). Starting with Camunda 8.9, you can optionally reduce the amount of data exported for Optimize by configuring exporter-side filters (for example, by variable name, variable type, or BPMN process ID) in the Elasticsearch/OpenSearch exporters. See [Optimize export filtering](./configuration/optimize-export-filtering.md) for configuration options and examples. ## Recommended additional configurations ### Adjust Optimize heap size By default, Optimize is configured with 1GB JVM heap memory. Depending on your setup and actual data, you might still encounter situations where you need more than this default for a seamless operation of Optimize. To increase the maximum heap size, you can set the environment variable `OPTIMIZE_JAVA_OPTS` and provide the desired JVM system properties; for example, for 2GB of Heap: ```bash OPTIMIZE_JAVA_OPTS=-Xmx2048m ``` --- ## Bring your own groups When Camunda 8 Self-Managed is configured with OpenID Connect (OIDC), the Orchestration Cluster can read a configurable claim from each token and treat its values as Camunda group IDs. This lets you use groups that already exist in your identity provider (IdP) as the basis for Camunda's authorization, role, and tenant assignment. :::note This feature is Self-Managed only. Bring your own groups (external IdP groups) is not available in Camunda 8 SaaS. ::: ## When to use external IdP groups Use external IdP groups if: - Your IdP is already the source of truth for user-to-group membership. - You want a single place to manage group membership for both sign-in and Camunda authorization. - You need group-based authorization in Camunda but do not want to duplicate group data between your IdP and Camunda. Do not use external IdP groups if: - You need to manage groups through the Orchestration Cluster REST API or Identity UI. - You need Camunda to list or browse IdP-managed groups. - You are running Camunda 8 SaaS. ## Prerequisites - A Self-Managed Camunda 8 deployment running 8.8 or later. - OIDC authentication configured for the Orchestration Cluster. See [connect to an external identity provider](./connect-external-identity-provider.md). - An IdP that can include a groups claim in the issued ID or access token. The claim value must be a JSON array of strings, where each string is a group ID. ## Configure the groups claim Set `camunda.security.authentication.oidc.groups-claim` to the name of the claim that contains the user's groups. The claim value must be a JSON array of strings, where each entry is a group ID. For nested claims, use a [JSONPath expression](https://www.rfc-editor.org/rfc/rfc9535.html) — the same mechanism used by `username-claim`. For example, `$['camundaorg']['groups']` resolves to the `groups` array inside a nested `camundaorg` object. ```yaml camunda.security.authentication.oidc.groups-claim: ``` ``` CAMUNDA_SECURITY_AUTHENTICATION_OIDC_GROUPSCLAIM= ``` See the [`groupsClaim` configuration reference](../core-settings/configuration/properties.md) for the full property definition. For multi-IdP setups where each provider may use a different claim name, see [connect multiple identity providers](./connect-multiple-identity-providers.md). ## Use IdP groups in Camunda Once `groups-claim` is set, the group IDs extracted from each token can be used anywhere a Camunda group ID can be used. The group ID in the token and the Camunda-side group ID must match **exactly**: comparisons are case-sensitive and treat group IDs as opaque strings. ### Role assignment Assign IdP groups to Camunda roles to grant every member of that group the role's permissions on sign-in. See [assign users, clients, groups, or mapping rules to roles via configuration](./overview.md#assign-users-clients-groups-or-mapping-rules-to-roles-via-configuration). ### Authorizations Grant authorizations directly to IdP groups to control access to Camunda resources such as process definitions, decisions, or tenants. See [authorizations](../../../../components/concepts/access-control/authorizations.md). ### Tenant assignment Add IdP groups to tenants so that every member of that group gains access to the tenant. See [tenants](../../../../components/admin/tenant.md). ## Limitations and behavior - Self-Managed only. The feature is not available in Camunda 8 SaaS. - No group sync or listing. Groups exist only at the moment a token is evaluated. They are not persisted in Camunda and are not browsable in the Identity UI or the REST API. - REST group management is disabled while the claim is set. When `groups-claim` is set, the `/v2/groups/**` endpoints return HTTP 403 with a problem detail stating that the Groups API is disabled in group claim mode. This is a hard switch, not a merge — you cannot manage some groups via REST and others via the claim at the same time. - Per-token evaluation. Group membership is re-read from every token. Changes in your IdP take effect on the next sign-in or token refresh; they do not propagate to already-active sessions. - Array shape required. The claim value must always be a JSON array, even for users who belong to a single group. - Clients use the same claim. Machine-to-machine tokens resolve their groups through the same `groups-claim` configuration as user tokens. ## Troubleshooting If groups from your IdP are not being applied in Camunda, check the following: - The claim is present in the issued token. Decode an ID or access token for a user who should have the groups and verify the claim appears with the expected array value. Your IdP may require explicit scope or claim-mapping configuration to include it. - JSONPath matches the actual structure. If your groups claim is nested, make sure the JSONPath expression in `groups-claim` resolves to the array itself, not its parent object. - Group IDs match exactly. Camunda matches group IDs as literal strings. A trailing space, different case, or stray prefix will cause Camunda-side assignments to silently miss. For deeper investigation, see [debugging authentication](./debugging-authentication.md). --- ## Connect Admin to an identity provider Configure Admin to use an external identity provider (IdP) via OpenID Connect (OIDC) at the application level, including claims and mapping rules. :::info Deploying with Helm? If you deploy Camunda 8 Self-Managed with Helm, use the [Helm chart authentication and authorization guides](/self-managed/deployment/helm/configure/authentication-and-authorization/index.md) to configure OIDC and Admin. ::: ## About Authentication and authorization You can configure IdP integration to control authentication and authorization for both the web components and machine-to-machine (M2M) API access (for connectors and workers). | Access type | Description | | :------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [Web components](#web-components) | Users authenticate via the OIDC [Authorization Code Flow](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth).Users are redirected to your IdP to log in, and Camunda receives a token to establish the session.Claims from the token are used to identify the user's username, and (optionally) groups for easier assignment and management.Mapping Rules can be used to map token claims to Camunda roles, authorizations, or tenants.As user information is not stored in the Orchestration Cluster using OIDC, features such as user search and user validation on assigning authorizations or tenants and user management are not available in the Orchestration Cluster Admin UI. | | [Machine-to-machine (M2M) API access](#machine-to-machine-m2m-api-access) | Connectors, job workers or other implementations that use the Orchestration Cluster REST or gRPC APIs (for example, by using one of the Camunda Clients) use the [OAuth Client Credentials Flow](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4) to obtain a JWT access token for authentication.This authentication method for Camunda Clients is separate from the interactive user login and typically requires additional configuration in your IdP and Camunda. | ## Web components Configure Admin so your users can use your IdP to authenticate to the Orchestration Cluster. ### Prerequisites - A Camunda 8 Orchestration Cluster (Self-Managed). - Access to an OIDC-compliant IdP (for example, Keycloak, Auth0, Okta, Microsoft EntraID). - Client credentials (client ID, client secret, issuer URI) from your IdP. ### Step 1: Prepare your IdP Before configuring Camunda, you must first prepare your IdP: 1. Register a new application/client for the Orchestration Cluster in your IdP. - Set the application type to "Web" or "Confidential". - Enable OIDC support. - Configure the necessary scopes (for example, `openid`, `profile`, `email`). - Ensure the client is allowed to access user information. 2. Set the **redirect URI** to match your Camunda deployment (default: `http://localhost:8080/sso-callback`). 3. Assign the users or groups who require access to Camunda 8. 4. (Optional) Configure group or other claims if you want to use group-based authorizations or mapping rules in Camunda. 5. Note the **client ID**, **client secret**, and **issuer URI** as these are required during Camunda configuration. :::note For most IdPs, the default claim for the username is `sub` (subject). If you want to use a different claim (for example, `preferred_username` or `email`), configure your IdP to include it in the token, and update the [Orchestration Cluster configuration](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#camunda.security.authentication.oidc). ::: ### Step 2: Choose your deployment and configuration method You can configure OIDC using `application.yaml` or environment variables. Select the option that best fits your deployment approach. ### Step 3: Set the authentication method to OIDC Set the authentication method to OIDC using the following settings: ```yaml camunda.security.authentication.method: oidc ``` ``` CAMUNDA_SECURITY_AUTHENTICATION_METHOD=oidc ``` ### Step 4: Configure the OIDC connection details Set the following properties using the respective values from your IdP: ```yaml camunda.security.authentication.oidc.client-id: camunda.security.authentication.oidc.client-secret: camunda.security.authentication.oidc.issuer-uri: camunda.security.authentication.oidc.redirect-uri: camunda.security.authentication.oidc.username-claim: camunda.security.authentication.oidc.audiences: camunda.security.authentication.oidc.scope: ["openid"] ``` ``` CAMUNDA_SECURITY_AUTHENTICATION_OIDC_CLIENTID= CAMUNDA_SECURITY_AUTHENTICATION_OIDC_CLIENTSECRET= CAMUNDA_SECURITY_AUTHENTICATION_OIDC_ISSUERURI= CAMUNDA_SECURITY_AUTHENTICATION_OIDC_REDIRECTURI= CAMUNDA_SECURITY_AUTHENTICATION_OIDC_USERNAMECLAIM= CAMUNDA_SECURITY_AUTHENTICATION_OIDC_AUDIENCES= CAMUNDA_SECURITY_AUTHENTICATION_OIDC_SCOPE=["openid"] ``` :::tip If your OIDC provider needs to be reached using different URLs from the browser and backend (for example, when running in Docker or behind a reverse proxy), you can configure separate URIs instead of a single `issuer-uri`. See [use separate OIDC provider URIs for browser and backend](./special-oidc-cases.md#use-separate-oidc-provider-uris-for-browser-and-backend) for details. ::: - **Username claim**: By default, the `sub` (subject) claim from the token is used as the username. If you want to use a different claim (such as `preferred_username` or `email`), ensure your IdP includes it in the token and set the `username-claim` property accordingly. You can use a [JSONPath expression](https://www.rfc-editor.org/rfc/rfc9535.html) to locate the username claim in the token (for example, `$['camundaorg']['username']`). :::info If you're using Camunda Hub and want to allow deployments to the Orchestration Cluster from there (with the [`BEARER_TOKEN` authentication](/self-managed/components/hub/configuration/properties.md#available-authentication-methods)), both applications must use the same IdP. You also need to make the cluster accept the token passed by Camunda Hub. To do so, include the Camunda Hub UI's token audience in the configured list of audiences. ::: #### Example IdP configuration The following examples show typical OIDC settings in `application.yaml`. Adapt the values to your environment and IdP configuration. ```yaml camunda.security.authentication.oidc.client-id: camunda.security.authentication.oidc.client-secret: camunda.security.authentication.oidc.issuer-uri: "https://login.microsoftonline.com//v2.0" camunda.security.authentication.oidc.redirect-uri: "http://localhost:8080/sso-callback" camunda.security.authentication.oidc.username-claim: "oid" camunda.security.authentication.oidc.audiences: camunda.security.authentication.oidc.scope: ["openid", "profile", "/.default"] ``` ```yaml camunda.security.authentication.oidc.client-id: camunda.security.authentication.oidc.client-secret: camunda.security.authentication.oidc.issuer-uri: "https:///realms/" camunda.security.authentication.oidc.redirect-uri: "http://localhost:8080/sso-callback" camunda.security.authentication.oidc.username-claim: "preferred_username" camunda.security.authentication.oidc.audiences: camunda.security.authentication.oidc.scope: ["openid", "profile", "email"] ``` ### Redirect URI Use the redirect URI to define where the Identity Provider (IdP) sends users back after successful authentication. By default, the redirect URI is: `{baseUrl}/sso-callback` At runtime, `{baseUrl}` resolves to the URL used to access your Orchestration Cluster deployment. In most cases, you do not need to change this value. You may need to customize the redirect URI in advanced scenarios, such as: - When your deployment is accessed through reverse proxies or load balancers - When you need to pass context information while configuring multiple OIDC providers Regardless of customization, the redirect URI must always point to the `/sso-callback` endpoint of your Orchestration Cluster deployment. The Orchestration Cluster checks the redirect URI at startup and writes a warning if the value cannot expand to a usable callback URL. It still starts. A usable value: - Starts with `{baseUrl}`, or has an `https` or `http` scheme and a host - Has a port between 1 and 65535, if it has a port - Has a callback path - Has no fragment (`#`) A value with no callback path, or with a path that has no leading slash, falls back to `{baseUrl}/sso-callback`, and the login completes. Any other unusable value stays as configured, so the login fails when the browser returns from the IdP. The cluster serves the callback at the path that the redirect URI resolves to. The path must be one that the cluster routes to its web applications, and `/sso-callback` is the path that the cluster keeps for this purpose. A path outside that set passes the startup check, but the callback request does not reach the cluster's login handling, and the login does not complete. The default value `{baseUrl}/sso-callback` is correct. The cluster also checks the redirect URI if you only use API clients, which never use the redirect URI. Most Identity Providers require you to explicitly configure allowed redirect URIs for security reasons. Ensure the value configured in your IdP exactly matches the redirect URI used here, whether it is static or dynamically resolved using `{baseUrl}`. :::note `{baseUrl}` is dynamically resolved for each request based on the URL used to access the Orchestration Cluster instance. It is composed of the following parts: - `{scheme}`: The transport scheme (`http` or `https`) - `{host}`: The hostname used to connect to the instance - `{port}`: The port number, if specified (omitted if not used) - `{contextPath}`: The context path of the Orchestration Cluster instance, if configured (omitted if none) For example: - Accessing the instance via `https://camunda.acme.com/identity` resolves `{baseUrl}` to `https://camunda.acme.com` - Accessing the instance via `https://services.acme.com:18080/camunda/` resolves `{baseUrl}` to `https://services.acme.com:18080/camunda` ::: ### Step 5: Restart the Orchestration Cluster After updating your configuration, (re)start the Orchestration Cluster for the configuration changes to be applied. A successful start does not confirm that your IdP is reachable. The cluster contacts a provider at the first request that needs it. If the cluster is up but authentication fails, see [requests fail when an identity provider is unreachable](debugging-authentication.md#requests-fail-when-an-identity-provider-is-unreachable). ### Step 6: Test user authentication At this point, you should be able to log in to the Orchestration Cluster using any user account from your IdP that is assigned to this client (application). :::note If login is successful, you will see that you are not authorized to access the Orchestration Cluster UIs. This is expected, as you have not yet configured an Admin user or any authorizations for the user. ::: ### Step 7: Assign Admin role to users [Authorizations](../../../../components/concepts/access-control/authorizations.md) are enabled by default. This means users cannot access any Orchestration Cluster UI or APIs - except authorizations that have been granted to them. To allow users to access the Orchestration Cluster UI, you can assign the "Admin" role to a user from your IdP: ```yaml camunda.security.initialization.defaultRoles.admin.users: [] ``` ``` CAMUNDA_SECURITY_INITIALIZATION_DEFAULTROLES_ADMIN_USERS_0= ``` 1. Replace `` with the username provided by your IdP (matching the value of the claim configured as `username-claim`). 1. Restart your Orchestration Cluster and verify that the chosen user has the Admin Role, for example by visiting `localhost:8080/identity/roles/admin/users`. 1. If the username is shown, continue configuring your own groups, mapping rules, or setting up [authorizations](../../../../components/concepts/access-control/authorizations.md) for other users. :::tip For more details on assigning users to the default roles, see the [corresponding documentation](overview.md#assign-users-clients-groups-or-mapping-rules-to-roles-via-configuration). ::: ### (Optional) Step 8: Configure bring your own groups You can manage groups in the Orchestration Cluster or bring groups that you have already configured in your IdP. For the latter, proceed as follows: 1. Configure your IdP to include a groups claim in the token (for example, `groups` or `roles`). The value should be an array of Strings where each entry is the ID of a group. 1. Set the `groups-claim` property in your Camunda configuration to match the claim name. Similar to the `username-claim`, you can use a [JSONPath expression](https://www.rfc-editor.org/rfc/rfc9535.html) to locate the groups claim in the token (for example, `$['camundaorg']['groups']`). You can then use these groups for role and authorization assignment, and tenant assignment. ```yaml camunda.security.authentication.oidc.groups-claim: ``` ``` CAMUNDA_SECURITY_AUTHENTICATION_OIDC_GROUPSCLAIM= ``` ### (Optional) Step 9: Mapping rules You can use mapping rules for advanced scenarios, such as mapping IdP claims to Camunda roles, authorizations, or tenants. See [mapping rules documentation](/components/admin/mapping-rules.md) for more information on how to define mapping rules. ## Machine-to-machine (M2M) API access Configure job workers, connectors, or custom client applications to use the Orchestration Cluster REST or gRPC API with OAuth access tokens provided by your Identity Provider. ### Prerequisites - The Orchestration Cluster (Self-Managed) user interface is [configured against your IdP](#web-components). - Client credentials (client ID, client secret, authorization server URI) from your IdP. ### Step 1: Configure the OIDC client id claim When it receives a request with an access token, the Orchestration Cluster needs to identify the client based on a claim in the token's payload. You can determine this claim by applying the following setting to the Orchestration Cluster: ```yaml camunda.security.authentication.oidc.client-id-claim: ``` ``` CAMUNDA_SECURITY_AUTHENTICATION_OIDC_CLIENTIDCLAIM= ``` As an example, assume that your client's access token payload looks like this: ```json { "sub": "client123", "client-identifier": "123" } ``` If you have set `camunda.security.authentication.oidc.client-id-claim` to `client-identifier`, then the Orchestration Cluster will use `123` as your client's ID when it applies memberships in groups, roles, and tenants, as well as authorizations. #### How principal identification works When both `camunda.security.authentication.oidc.username-claim` and `camunda.security.authentication.oidc.client-id-claim` are configured, and a token containing both claims is presented to the platform, the Orchestration Cluster uses the following logic to identify a single principal for the request: 1. If the client id claim is present, the request is treated as a client request with the corresponding ID. 1. If the client id is not present, the request is treated as a user request with the corresponding username matching the username-claim. 1. If neither the client id claim nor the username claim are set, then the request is rejected. #### Controlling the principal identification order In most cases we expect the default detection logic to be sufficient, however if you have a use-case for changing the order, the configuration property `camunda.security.authentication.oidc.prefer-username-claim` can be set to `true`. In this case the logic will be: 1. If the username claim is present, the request is treated as a user request with the corresponding username. 1. If the username claim is not present, the request is treated as a client request with the corresponding ID matching the client id claim. 1. If neither the client id claim nor the username claim are set, then the request is rejected. #### Recommendations for client ID and username claim configuration We recommend to set client id claim and username claim as follows: - The client id claim should not be the same as the username claim. - The claim that is checked first (based on the ordering defined in [How principal identification works](#how-principal-identification-works) and [Controlling the principal identification order](#controlling-the-principal-identification-order)) should not be present in tokens for the opposite principal type. As an example, using the default logic in [How principal identification works](#how-principal-identification-works), if the client ID claim is `client_id`, then tokens issued for users should not contain a `client_id` claim. - For ease of use, the client id claim's value should be the client id from the Identity Provider. This way, you can use the same value across both your Identity Provider and the Orchestration Cluster as the identifier of your client. ### Step 2: Prepare your IdP Next, configure a client in your IdP: 1. Register a new application/client for your job worker in your IdP. - Create a new application/client in your IdP. - Configure the necessary scopes (for example, `openid`). - Create a new client secret. - Ensure that the client's access tokens include the client id claim as configured in the previous step. 2. Note the **client ID**, **client secret**, and **authorization URI** as these are required during Camunda configuration. ### Step 3: Configure your worker application Depending on your application type (for example, standalone Java application, Spring Boot application), the configuration steps may vary. Refer to the documentation of your chosen Camunda Client for details on how to configure authentication using client credentials. - Orchestration Cluster REST and gRPC API clients: See [REST API authentication](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md#oidc-based-authentication-using-client-credentials). - Java Client: See [Java client authentication](/apis-tools/java-client/getting-started.md?authentication=oidc-self-managed#step-2-connect-to-your-camunda-8-cluster). - Spring Boot Starter: See [Spring Boot Starter authentication](/apis-tools/camunda-spring-boot-starter/getting-started.md?authentication=oidc#step-3-configure-the-camunda-8-connection). - Connectors: See [Connector authentication](/self-managed/components/connectors/connectors-configuration.md). - **Audience Validation**: If you have configured the audiences property for the Orchestration Cluster (`camunda.security.authentication.oidc.audiences`), the Orchestration Cluster will validate the audience claim in the token against the configured audiences. Make sure your token has the correct audience from the Orchestration Cluster above, or add your audience in the Orchestration Cluster configuration. :::note As per default authorizations are enabled, your application will only be able to retrieve the topology, with other requests requiring you to configure [authorizations](../../../../components/concepts/access-control/authorizations.md) for the client. You should use your `client id` when configuring authorizations. ::: 1) Add the dependency to your Java Project: ```xml io.camunda camunda-client-java 8.8.x ``` 2. Update your Java code to configure and verify authentication: ```java private static final String clientId = ""; private static final String clientSecret = ""; private static final String authorizationServer = ""; private static final String audience = ""; private static final String ocAudience = ""; private static final String clusterGrpcLocal = "grpc://localhost:26500"; private static final String clusterRestLocal = "http://localhost:8080"; // Build a new OAuthCredentialsProvider final OAuthCredentialsProvider credentialsProvider = new OAuthCredentialsProviderBuilder() .authorizationServerUrl(authorizationServer) .audience(audience) .clientId(clientId) .clientSecret(clientSecret) .scope(ocAudience) // for Microsoft EntraID typically use: ocAudience + "/.default" .build(); // Build a new Camunda Client with the CredentialsProvider try (CamundaClient client = CamundaClient.newClientBuilder() .grpcAddress(URI.create(clusterGrpcLocal)) .restAddress(URI.create(clusterRestLocal)) .credentialsProvider(credentialsProvider) .build()) { // Send a topology request to verify authentication Topology t = client.newTopologyRequest().send().join(); System.out.println(t.toString()); } ``` 1. Add the dependency to your Java Project: ```xml io.camunda camunda-spring-boot-starter ${version.camundastarter} ``` 2. Configure your application.yaml: ```yaml camunda: client: mode: self-managed auth: client-id: client-secret: token-url: audience: scope: grpc-address: grpc://localhost:26500 rest-address: http://localhost:8080 ``` 3. Verify authentication in code: ```java @SpringBootApplication public class App implements CommandLineRunner { @Autowired private CamundaClient client; public static void main(String[] args) { SpringApplication.run(App.class, args); } @Override public void run(final String... args) { Topology t = client.newTopologyRequest().send().join(); System.out.println(t.toString()); } } ``` 1. Configure your application.yaml: ```yaml camunda: client: mode: self-managed auth: client-id: client-secret: token-url: audience: scope: grpc-address: grpc://localhost:26500 rest-address: http://localhost:8080 ``` 2. Add the following dependencies to your project: ```xml io.camunda.connector spring-boot-starter-camunda-connectors ${version.connectors} ``` Note: You can run the Connector Runtime simply using Helm or Docker Image. #### Identity Provider Example Configurations ```java private static final String clientId = ""; private static final String clientSecret = ""; private static final String authorizationServer = "https://login.microsoftonline.com//oauth2/v2.0/token"; private static final String audience = ""; private static final String ocAudience = "" + "/.default"; private static final String clusterGrpcLocal = "grpc://localhost:26500"; private static final String clusterRestLocal = "http://localhost:8080"; ``` ```java private static final String clientId = ""; private static final String clientSecret = ""; private static final String authorizationServer = "https:///realms//protocol/openid-connect/token"; private static final String audience = ""; private static final String ocAudience = ""; private static final String clusterGrpcLocal = "grpc://localhost:26500"; private static final String clusterRestLocal = "http://localhost:8080"; ``` ## Logout handling (RP-initiated logout) When Orchestration Cluster Admin is configured with an external OIDC-compliant identity provider (IdP), you can enable relying party (RP)-initiated logout so that signing out of Camunda also triggers a logout at the IdP. :::note This applies to Self-Managed Orchestration Clusters only. RP-initiated logout is not supported in SaaS. ::: ### Configure RP-initiated logout #### Enable or disable RP-initiated logout Enable or disable RP-initiated logout using the following setting: ```yaml camunda: security: authentication: oidc: idp-logout-enabled: true|false ``` ``` CAMUNDA_SECURITY_AUTHENTICATION_OIDC_IDPLOGOUTENABLED= ``` :::note RP-initiated logout is enabled by default for all new deployments. ::: **Logout behavior depends on this setting:** - **Orchestration Cluster-only logout (RP-initiated logout disabled)** - Clears only the Orchestration Cluster session. - No request is sent to the IdP, so the user remains signed in there. - **RP-initiated logout (enabled)** (default for new 8.9+ installations) - Clears the Orchestration Cluster session and calls the IdP logout endpoint. - Signs the user out of Orchestration Cluster Admin, Tasklist, Operate, and the IdP. - Redirects the user to the login page of the Camunda app where logout was initiated. - Logout behavior for other applications using the same IdP depends on the IdP’s RP-initiated logout implementation. Existing Self-Managed deployments upgraded from earlier versions can continue to use Orchestration Cluster-only logout. To preserve the previous behavior after upgrading, explicitly set the `IDPLOGOUTENABLED` configuration property to `false`. #### Configure the IdP logout endpoint By default, Admin retrieves the IdP logout endpoint from the OIDC discovery document (the `.well-known` endpoint). If you do not use `issuer-uri` and instead configure endpoints manually, define the logout endpoint alongside `authorization-uri`, `token-uri`, and `jwk-set-uri`, as shown in the [special OIDC configuration cases](./special-oidc-cases.md): ```yaml camunda.security: authentication: oidc: authorization-uri: https://login.microsoftonline.com//oauth2/v2.0/authorize token-uri: https://login.microsoftonline.com//oauth2/v2.0/token jwk-set-uri: https://login.microsoftonline.com//discovery/v2.0/keys end-session-endpoint-uri: https://login.microsoftonline.com//oauth2/v2.0/logout ``` ```bash CAMUNDA_SECURITY_AUTHENTICATION_OIDC_ENDSESSIONENDPOINTURI=https://login.microsoftonline.com//oauth2/v2.0/logout CAMUNDA_SECURITY_AUTHENTICATION_OIDC_AUTHORIZATIONURI=https://login.microsoftonline.com//oauth2/v2.0/authorize CAMUNDA_SECURITY_AUTHENTICATION_OIDC_TOKENURI=https://login.microsoftonline.com//oauth2/v2.0/token CAMUNDA_SECURITY_AUTHENTICATION_OIDC_JWKSETURI=https://login.microsoftonline.com//discovery/v2.0/keys ``` #### Configure the post-logout redirect URL in the IdP To ensure users are redirected correctly after logout, configure a post-logout redirect URL in your IdP. The post-logout URL is the Camunda hostname, context path (if applicable) plus `/post-logout`. For example, if Admin is accessible at `http://localhost:8080`, configure the following post-logout redirect URL in your IdP: ``` http://localhost:8080/post-logout ``` For hostname and context path configuration, adjust the URL accordingly. For example, if your Camunda deployment is accessible at `http://localhost:8080/orchestration`, configure the following post-logout redirect URL in the IdP: ``` http://localhost:8080/orchestration/post-logout ``` #### Refresh token revocation On logout, Camunda asks the identity provider to revoke the session's refresh token, so that a copy of it cannot be used to obtain new access tokens after the user has signed out. This is an [RFC 7009](https://datatracker.ietf.org/doc/html/rfc7009) revocation request sent from Camunda to the provider, separate from the browser redirect used for RP-initiated logout. Revocation is enabled by default and requires no configuration where the provider publishes a `revocation_endpoint` in its discovery document, as Keycloak, Okta, and Auth0 do. Where it does not, set the endpoint explicitly: ```yaml camunda: security: authentication: oidc: revocation-endpoint-uri: https:///oauth2/revoke ``` To turn revocation off for a provider, set `revocation-enabled: false`. This also suppresses an endpoint that was discovered, so it is the way to opt out of the default behavior. Two consequences are worth planning for: - Logout now makes an outbound call from Camunda to the identity provider, so the provider must be reachable from the Camunda deployment. The call is bounded at roughly three seconds and fails soft: if the provider is unreachable or rejects the request, the user is still logged out locally and a warning is logged. - Microsoft Entra ID publishes no revocation endpoint, so refresh tokens are not revoked there. Entra deployments are otherwise unaffected. ## Troubleshooting ### General authentication issues If authentication does not work as expected: - Check your application logs for authentication errors. - Ensure your IdP client is configured to allow the specified redirect URI. - Verify the claim names match your IdP's token claims. ### RP-initiated logout If RP-initiated logout does not behave as expected, check your application logs for the following messages. #### Unable to determine end-session endpoint If you configure OIDC using explicit authorization, token, or logout URIs instead of the issuer URI, and do not specify a logout endpoint, the following message is logged: ``` Unable to determine end-session endpoint for OIDC logout. Falling back to {baseLogoutUrl} without logout hint. ``` Ensure you either: - Configure the `issuer-uri` so Admin can retrieve the logout endpoint from the OIDC discovery document, or - Explicitly set the `end-session-endpoint-uri`. #### Refresh token was not revoked If the identity provider cannot be reached, or rejects the revocation request, logout still completes and a warning similar to the following is logged: ``` Failed to revoke the refresh token for OIDC registration '' at [...]. The local session is still terminated, but the refresh token may remain valid until it expires. ``` Check that the provider's revocation endpoint is reachable from the Camunda deployment and that the client credentials Camunda uses are accepted there. To disable revocation for the provider instead, set `revocation-enabled: false`. #### No client registration found If the `registrationId` used for logout (the identifier of the configured OIDC client registration) cannot be resolved, Admin cannot construct an RP-initiated logout request. The following message is logged: ``` No client registration found for id `{registrationId}`. Falling back to {baseLogoutUrl} without logout hint. ``` Verify that the configured client registration ID matches the OIDC client definition in your IdP configuration. #### Missing login_hint / logout_hint Some IdPs require a `logout_hint` parameter for RP-initiated logout. Admin derives `logout_hint` from the OIDC user's `login_hint` claim. This claim typically contains a user identifier, such as a username or email, which the IdP uses to identify the session to terminate. If no `login_hint` is present, the following message is logged and the logout request is sent without a logout hint: ``` No 'login_hint' claim found in OIDC user. Falling back to '{baseLogoutUrl}' without logout hint. ``` Ensure that your IdP includes a `login_hint` claim in the ID token if your IdP requires `logout_hint` during logout. #### No post-logout redirect URL configured You must explicitly configure the post-logout redirect URL in your IdP. If no valid post-logout redirect URL is available, Admin falls back to a default path. In this case, the following message is logged: ``` No valid post-logout redirect URL found in session, falling back to default: '/' ``` Ensure that the post-logout redirect URL (`/post-logout`) is registered in your IdP configuration. #### IdP `end_session_endpoint` not available If RP-initiated logout is enabled but the IdP does not expose an `end_session_endpoint`, the local Orchestration Cluster session is terminated and the following message is displayed: ``` The identity provider's end_session_endpoint is not available. The local session has been terminated, but the IdP session will still be active. ``` The user is signed out of Camunda, but their IdP session remains active. To resolve this: - Verify that your IdP supports RP-initiated logout and exposes an `end_session_endpoint`. - If your IdP does not support RP-initiated logout, disable it by setting `idp-logout-enabled` to `false`. ## Further resources - [OIDC configuration reference](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md) - [OpenID Connect (OIDC) overview](https://openid.net/connect/) - [Camunda authentication and authorization](../../../../components/concepts/access-control/authorizations.md) - [Helm chart authentication and authorization configuration](/self-managed/deployment/helm/configure/authentication-and-authorization/index.md) --- ## Connect to multiple identity providers Camunda 8 Orchestration Cluster supports multiple OpenID Connect (OIDC) identity providers (IdPs). This lets users from different organizations authenticate using their preferred provider. With multiple IdPs configured: - Users choose an IdP when logging in to a cluster. - Camunda validates and accepts access tokens from trusted issuers for UI and API access. - You can support identity federation and multi-tenancy scenarios with more flexible authentication. ## Overview When multiple OIDC providers are set up, the cluster login page lets users select the provider they wish to use. For API access, any bearer token issued by one of the configured and trusted issuers is accepted; all others are denied. ## Prerequisites - Camunda 8 Orchestration Cluster (Self-Managed) deployed. - At least two OIDC-compliant IdPs (for example, Microsoft Entra ID, Keycloak, Okta). - Administrative access to each IdP (to register applications and obtain credentials). ## Configure multiple identity providers Configure multiple IdPs by defining each provider as a separate registration in `application.yaml` or using environment variables. Each provider is identified by a unique ``, which is a user-defined label (e.g., `ENTRA`, `KEYCLOAK`, `PARTNER_X`). ### Configure OIDC connection details for each provider Define each provider’s settings using the following pattern (`` is your chosen ID): ```yaml camunda.security.authentication.providers.oidc..client-id: camunda.security.authentication.providers.oidc..client-name: camunda.security.authentication.providers.oidc..client-secret: camunda.security.authentication.providers.oidc..issuer-uri: camunda.security.authentication.providers.oidc..redirect-uri: camunda.security.authentication.providers.oidc..token-uri: camunda.security.authentication.providers.oidc..jwk-set-uri: camunda.security.authentication.providers.oidc..scope: ["openid"] camunda.security.authentication.providers.oidc..audiences: camunda.security.authentication.providers.oidc..grant-type: ``` ``` CAMUNDA_SECURITY_AUTHENTICATION_PROVIDERS_OIDC__CLIENTID= CAMUNDA_SECURITY_AUTHENTICATION_PROVIDERS_OIDC__CLIENTNAME= CAMUNDA_SECURITY_AUTHENTICATION_PROVIDERS_OIDC__CLIENTSECRET= CAMUNDA_SECURITY_AUTHENTICATION_PROVIDERS_OIDC__ISSUERURI= CAMUNDA_SECURITY_AUTHENTICATION_PROVIDERS_OIDC__REDIRECTURI= CAMUNDA_SECURITY_AUTHENTICATION_PROVIDERS_OIDC__AUTHORIZATIONURI= CAMUNDA_SECURITY_AUTHENTICATION_PROVIDERS_OIDC__TOKENURI= CAMUNDA_SECURITY_AUTHENTICATION_PROVIDERS_OIDC__JWKSETURI= CAMUNDA_SECURITY_AUTHENTICATION_PROVIDERS_OIDC__SCOPE= CAMUNDA_SECURITY_AUTHENTICATION_PROVIDERS_OIDC__AUDIENCES= CAMUNDA_SECURITY_AUTHENTICATION_PROVIDERS_OIDC__GRANTTYPE= ``` Example configuration for two providers: ```yaml # Keycloak camunda.security.authentication.providers.oidc.keycloak.client-name: "Keycloak - MyCompany" camunda.security.authentication.providers.oidc.keycloak.client-id: camunda.security.authentication.providers.oidc.keycloak.client-secret: camunda.security.authentication.providers.oidc.keycloak.issuer-uri: "https:///realms/" camunda.security.authentication.providers.oidc.keycloak.redirect-uri: "http://localhost:8080/sso-callback" camunda.security.authentication.providers.oidc.keycloak.audiences: camunda.security.authentication.providers.oidc.keycloak.scope: ["openid", "profile", "email"] # Microsoft EntraID camunda.security.authentication.providers.oidc.entraid.client-name: "Microsoft EntraID - ContractorCompany" camunda.security.authentication.providers.oidc.entraid.client-id: camunda.security.authentication.providers.oidc.entraid.client-secret: camunda.security.authentication.providers.oidc.entraid.issuer-uri: "https://login.microsoftonline.com//v2.0" camunda.security.authentication.providers.oidc.entraid.redirect-uri: "http://localhost:8080/sso-callback" camunda.security.authentication.providers.oidc.entraid.audiences: camunda.security.authentication.providers.oidc.entraid.scope: ["openid", "profile", "/.default"] ``` :::tip The `issuer-uri` property is required for each provider configuration. ::: You can repeat these variables for any number of OIDC IdPs by using a unique `` for each. ### Configure global OIDC claims The following OIDC-related properties are shared by all configured providers and must be the same for every IdP: ```yaml camunda.security.authentication.oidc.client-id-claim: camunda.security.authentication.oidc.groups-claim: camunda.security.authentication.oidc.username-claim: camunda.security.authentication.oidc.organization-id: ``` ``` CAMUNDA_SECURITY_AUTHENTICATION_OIDC_CLIENTIDCLAIM= CAMUNDA_SECURITY_AUTHENTICATION_OIDC_GROUPSCLAIM= CAMUNDA_SECURITY_AUTHENTICATION_OIDC_USERNAMECLAIM= CAMUNDA_SECURITY_AUTHENTICATION_OIDC_ORGANIZATIONID= ``` These define: - Which claim to use as the client ID and username - The claim containing group information (if applicable) - The claim for organization or tenant assignment All IdPs must use the same claims for these purposes. ### Login page behavior After you configure multiple IdPs and restart Camunda 8 Orchestration Cluster, users can select an identity provider on the login page and sign in. ### API authentication For machine-to-machine (M2M) API calls (REST/gRPC), Camunda accepts access tokens (bearer tokens) from any of the configured IdPs, as long as the `issuer` claim matches one of the trusted issuer URIs. If an access token’s issuer is not configured, the request is denied. ### Best practices - Each IdP should use a unique and meaningful ``. - Ensure the `issuer URI` is present and correct for each provider. - Configure claims consistently across all IdPs (for example, client ID, username, and groups). ### Overall workflow 1. Prepare and register applications/clients in each IdP. Obtain the client ID, client secret, and issuer URI. 2. Configure each IdP using environment variables or `application.yaml` (or Helm values). 3. Configure global OIDC claims that work across all providers. 4. Restart Camunda 8 Orchestration Cluster to apply the changes. 5. Test login with accounts from each IdP. 6. Assign roles and authorizations as needed. For details, see [Authorizations](../../../../components/concepts/access-control/authorizations.md). ### Troubleshooting - If users cannot log in, verify that the client configuration, issuer URI, and claims match the values configured in the IdP. - Check the Orchestration Cluster logs for missing or mismatched issuer and client configuration. - If a provider does not appear on the login page, verify that its configuration is complete. ### Further resources - [Configure external Identity Providers](./connect-external-identity-provider.md) - [Camunda authentication and authorization](../../../../components/concepts/access-control/authorizations.md) - [OIDC configuration reference](../core-settings/configuration/properties.md) --- ## Debugging the authentication flow This guide explains how to debug issues in the **authentication and authorization flow** of the Orchestration Cluster. These techniques help identify where and why access may be denied or restricted. Common questions you can answer with these steps: - Why can’t I log into the web applications? - Why does my search request return empty results? The flow consists of three key steps: 1. **Request authentication** - **Input:** HTTP request - **Output:** Spring `Authentication` object with user identity - **Layer:** Spring Security 2. **Establish Orchestration Cluster user context** - **Input:** Spring `Authentication` - **Output:** `CamundaAuthentication` object with roles, groups, and tenant memberships - **Layer:** Orchestration Cluster authentication 3. **Apply authorizations** - **Input:** `CamundaAuthentication` - **Output:** Application data, filtered by authorizations - **Layer:** Orchestration Cluster search and workflow engine Typical failure points: - Step 1: Invalid credentials (for example, failed Basic authentication). - Step 2: Missing role or group memberships. - Step 3: Authorizations not yet configured or missing. To isolate the issue, use: - [Review logs](#review-logs) - [Review the startup warnings](#review-the-startup-warnings) - [Requests fail when an identity provider is unreachable](#requests-fail-when-an-identity-provider-is-unreachable) - [Review data](#review-data) - [Review configuration](#review-configuration) - [Inspect the JWT](#inspect-the-jwt) - [Test the IdP directly](#test-the-idp-directly) ## Review logs Enable detailed logging to trace authentication decisions: ```yaml logging.level: org.springframework.security: TRACE io.camunda: authentication: DEBUG security: DEBUG ``` ``` LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_SECURITY=TRACE LOGGING_LEVEL_IO_CAMUNDA_AUTHENTICATION=DEBUG LOGGING_LEVEL_IO_CAMUNDA_SECURITY=DEBUG ``` With these settings, you can trace request handling and how Spring Security filter chains determine authentication outcomes. ## Review the startup warnings The Orchestration Cluster checks its OIDC configuration at startup, without contacting the provider, and writes a warning for each problem it finds. It still starts, so a login can fail later for a problem that was already reported at startup. Read these warnings first. - The logger `io.camunda.security.spring.oidc.ScopedClientRegistrationFactory` reports a missing client ID, an incomplete set of endpoints, an unusable scope, and a redirect URI that cannot expand to a usable callback URL. Each entry names the provider. - The logger `io.camunda.security.spring.oidc.OidcRedirectionEndpoint` reports a redirect URI with no callback path, or with a path that has no leading slash. The cluster then uses `{baseUrl}/sso-callback`, and the login completes. A redirect URI that is unusable in any other way stays as configured. See [redirect URI](connect-external-identity-provider.md#redirect-uri). These checks run once while the cluster starts. The cluster writes each warning one time, and it does not repeat or limit them. The configuration cannot change while the cluster runs, so a new warning needs a restart. A change on the identity provider, such as a different list of permitted redirect URIs, writes no warning at all. Use a synthetic login to detect such a change. ## Requests fail when an identity provider is unreachable The Orchestration Cluster contacts an OIDC provider at the first request that needs it, and not at startup. The cluster starts and serves traffic while a provider is unreachable. Only the requests that need that provider fail. While a provider is unreachable: - A request that needs a provider the cluster did not resolve yet fails with a server error, and not with an authentication error. This can include a browser login and an API request with a token from that provider. - A session that the cluster authenticated before the outage keeps its access token until the token expires. The refresh that follows fails, the cluster ends the session, and the request gets an authentication error. - All other requests succeed. - Each new request tries again. The cluster serves the failed traffic again when the provider answers. You do not need to restart the cluster. - The cluster holds no queue of failed requests, and it makes no attempt in the background. One request makes one attempt, so the load on the provider is the rate of the requests that need it. The cluster keeps the first result that it gets, so the attempts stop when the provider answers. - A failed request writes a warning that names the step that failed — a client registration, the access-token decoder, or a UserInfo endpoint lookup — and the provider with its issuer. The cluster writes at most one warning each minute for each combination of step and provider. A minute without a failed request writes nothing. An unreachable provider no longer stops the cluster from starting. For a provider that the cluster resolves through its issuer URI, this warning is your only signal that part of the authentication traffic fails. Monitor your log pipeline for `WARN` entries of the logger `io.camunda.security.spring.oidc.DeferredOidcResolution`. Each entry starts with `Failed to resolve`. The warning follows the traffic, and it is not a health check of the provider. A cluster that gets no request for an unreachable provider writes no warning. Use a synthetic login or a synthetic API request if you must detect such an outage before a user does. The warning gives the step that failed and the provider with its issuer. It does not give the endpoint that did not answer. Read the exception that the warning attaches to find that endpoint. For a provider that is configured with an issuer URI, this is the discovery endpoint `/.well-known/openid-configuration`. Then make sure that the cluster can reach that endpoint. See [test the IdP directly](#test-the-idp-directly). A provider that sets `jwk-set-uri` or `user-info-uri` needs no discovery. A failure of these endpoints therefore gives a different signal: - An unreachable `jwk-set-uri` fails the token validation, and it writes no warning or error of its own. Set `LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_SECURITY=DEBUG` to see the cause. No log entry tells you about this failure at the default level, so use a synthetic API request with a valid token to detect it. - An unreachable `user-info-uri` writes an `ERROR` entry of the logger `io.camunda.security.spring.oidc.CachingOidcClaimsProvider` that gives the issuer and the endpoint. The request then succeeds with the claims of the token only, so the user can lose the groups, roles, or tenants that the UserInfo response adds. ## Review data To review the assignment of users and clients to roles, groups, or tenants—as well as which authorizations are in place—you can use the [Admin UI](/components/admin/admin-introduction.md). If you do not have access to the API, you can also check the same data in the following Elasticsearch/OpenSearch indexes: - `camunda-authorization` - `camunda-group` - `camunda-mapping-rule` - `camunda-role` - `camunda-tenant` - `camunda-user` - `camunda-web-session` ## Review configuration To review the effective configuration of your Orchestration Cluster, you can call the [Spring Boot Actuator endpoint](https://docs.spring.io/spring-boot/reference/actuator/endpoints.html#actuator.endpoints) at: ``` :/actuator/configprops ``` For example, with a Camunda 8 Run installation, this endpoint is available at `http://localhost:9600/actuator/configprops`. In other setups, replace `http://localhost:9600` with the URL to your Orchestration Cluster's actuator port and endpoint. Note that the actuator port differs from the Orchestration Cluster API port and may not always be accessible, depending on your deployment setup. Here is an excerpt from an example installation: ```json { ... "camunda.security-io.camunda.application.commons.security.CamundaSecurityConfiguration$CamundaSecurityProperties": { "prefix": "camunda.security", "properties": { ... "authentication": { "method": "OIDC", "authenticationRefreshInterval": "PT30S", "unprotectedApi": false, "oidc": { "issuerUri": "https://myoidcprovider.example.com", "clientId": "my-oidc-client", "clientSecret": "******", "grantType": "authorization_code", "redirectUri": "http://localhost:8080/sso-callback", "scope": [ "openid", "profile" ], "usernameClaim": "preferred_username", "clientIdClaim": "oid", "authorizeRequest": {} } } ... } } } ``` In the response, review the settings in the `camunda.security` section, compare them against the [configuration reference](../core-settings/configuration/properties.md#camundasecurityauthentication), and confirm they match your intended values. This is especially useful if you are applying the configuration via Helm values or environment variables and want to double-check that your configuration was applied correctly. ## Inspect the JWT Most "insufficient permissions" or "empty results" issues at step 2 or 3 of the flow trace back to a mismatch between what's in the access token and what Camunda expects. Decode the token presented to the Orchestration Cluster and check: - Confirm the claim configured as `usernameClaim` or `clientIdClaim` is present and has the value you expect. - Compare any claims your mapping rules match against with the claim name and value configured on each [mapping rule](/components/admin/mapping-rules.md). A wrong claim name, unexpected casing, or incorrect operator for an array claim can prevent a mapping rule from granting the expected role, group, or tenant. - Confirm the `aud` claim matches the `audience` configured for that client. ## Test the IdP directly To determine whether a failure originates at your identity provider or within Camunda, request a token directly from the IdP, bypassing Camunda entirely: ```bash curl -X POST '' \ -d 'client_id=' \ -d 'client_secret=' \ -d 'grant_type=client_credentials' \ -d 'scope=openid' ``` - If the request fails or returns an error, investigate the IdP configuration, grant type, network, or firewall. The problem isn't specific to Camunda. - If the request succeeds, decode the returned token as described in [inspect the JWT](#inspect-the-jwt) and confirm it contains the claims Camunda expects. If the token contains the expected claims but Camunda still rejects the request, check Camunda's authorization configuration, including mapping rules, roles, and authorizations. For interactive browser logins, complete the login flow directly on your IdP's hosted login page before troubleshooting Camunda. This confirms whether the user can authenticate with the IdP. --- ## MCP processes The Orchestration Cluster admin UI provides an overview of the processes currently registered as MCP tools in the [Processes MCP Server](/apis-tools/processes-mcp/processes-mcp-overview.md). ## View registered MCP processes Navigate to **MCP Processes** in the Orchestration Cluster admin UI to see all processes currently exposed as MCP tools. ![Admin UI page listing processes registered as MCP tools, showing tool name, tool description, process name, version, and tenant](img/mcp-tools-admin.png) The following information is displayed for each registered process: | Column | Description | | :------------------- | :------------------------------------------------------------------------------------------------ | | **Tool name** | The MCP tool identifier configured in the MCP start event element template. | | **Tool Description** | The plain-language description of the tool's function, as configured in the MCP start event. | | **Process Name** | The name of the BPMN process registered as an MCP tool. | | **Version** | The process definition version currently registered. Only the latest deployed version is exposed. | | **Tenant** | The tenant the process belongs to. | ## Troubleshoot - **A process is not appearing after deployment**: Verify that the process was deployed successfully and that the MCP start event element template is applied to the start event. Check for deployment errors in Operate. - **A process shows an unexpected version:** The Processes MCP Server always exposes the latest deployed version. If a previous version is showing, a newer deployment may have failed. See [version binding](/apis-tools/processes-mcp/processes-mcp-version-binding.md) for more details. --- ## Admin in Self-Managed Admin (formerly Orchestration Cluster Identity) is included by default with the [Orchestration Cluster](/self-managed/reference-architecture/reference-architecture.md#orchestration-cluster) in all Self-Managed installation methods. Within a cluster, Admin provides unified, cluster-level identity management and authorization. See [how identity works in Camunda](/self-managed/components/identity/how-identity-works.md) for how Admin relates to Management Identity and which one to configure. :::note Profile transition In Camunda 8.9, the `admin` Spring profile replaces the `identity` profile. Both profiles work interchangeably in 8.9. The `identity` profile is deprecated and will be removed in a future version. ::: :::info The following guides cover Admin configuration in Self-Managed environments. For information on using and managing Admin, see [Admin user guides](/components/admin/admin-introduction.md). ::: ## Initial setup Using the default setup for [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) will result in a cluster with: 1. Web components login enabled 2. API authentication disabled 3. Authorizations disabled 4. An initial user with username/password: `demo` / `demo` 5. An `admin` role with full permissions, applied to the `demo` user To modify the [initial configuration](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md), define your custom values in `application.yaml`, and pass this file at startup using the `--config` flag. See [this section](/self-managed/quickstart/developer-quickstart/c8run/configuration.md#enable-authentication-and-authorization) for details. :::note - In Helm installations, API authentication and authorization are enabled by default. You can adjust these settings in `application.yaml` or using environment variables. - As a Spring Boot application, the Orchestration Cluster supports standard [Spring configuration](https://docs.spring.io/spring-boot/reference/features/external-config.html) methods. [Review configurations which apply to all components within the Orchestration Cluster](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md). ::: ## Configure initial users If users are managed within the Orchestration Cluster (that is, without an external Identity Provider), you can create an initial user in three ways: If you use an external Identity Provider instead, continue with [connect Admin to an identity provider](./connect-external-identity-provider.md). - Using the [Orchestration Cluster UI](#option-1-orchestration-cluster-ui) - Using the [Setup endpoint of the Orchestration Cluster REST API](#option-2-setup-rest-api) - Using the [configuration](#option-3-configuration) :::info Production and no-clickops setups For production environments, prefer **Option 3: configuration** so your admin bootstrap, role assignments, and authentication settings are defined declaratively in `application.yaml`, environment variables, or Helm-managed application configuration. The UI and Setup API options are useful for manual bootstrap, but they are not the best fit for repeatable, Git-managed deployments. Typical production next steps are: - Define initial users with the [configuration examples](#option-3-configuration) and assign them to roles with the [role assignment examples](#assign-users-clients-groups-or-mapping-rules-to-roles-via-configuration). - If you deploy with Helm, provide these settings through [application configs](/self-managed/deployment/helm/configure/application-configs.md) and the [Helm authentication and authorization guides](/self-managed/deployment/helm/configure/authentication-and-authorization/index.md). ::: :::warning After completing the initial setup, ensure at least one user remains assigned to the `admin` role. If no admin user exists, a third party could create a new admin account and gain full access. ::: ### Option 1: Create an initial admin user in Orchestration Cluster UI {#option-1-orchestration-cluster-ui} If no admin user exists, the orchestration cluster web applications show a screen for creating the initial user: ![identity-create-initial-user](./img/create-initial-user.png) This user will be assigned to the `admin` role and granted all permissions in the system. Once an admin user exists, this screen is no longer shown. ### Option 2: Create an initial admin user via the Setup endpoint of Orchestration Cluster REST API{#option-2-setup-rest-api} You can create the first admin user by calling the Setup API endpoint: `POST /v2/setup/user` ([API documentation](/apis-tools/orchestration-cluster-api-rest/specifications/create-admin-user.api.mdx)) with the following JSON request body: ```json { "username": "", "password": "", "name": "", "email": "" } ``` This endpoint is only available if **no user is assigned to the `admin` role**. This option is convenient for scripted bootstrap, but for long-lived production environments we recommend keeping the desired state in configuration. ### Option 3: Define initial users via the configuration{#option-3-configuration} To configure initial users programmatically, include the relevant definitions in your `application.yaml` or environment variables. ```yaml camunda: security: initialization: users: - username: password: name: email: # add more users to this list as desired ``` ```shell CAMUNDA_SECURITY_INITIALIZATION_USERS_0_USERNAME= CAMUNDA_SECURITY_INITIALIZATION_USERS_0_PASSWORD= CAMUNDA_SECURITY_INITIALIZATION_USERS_0_NAME= CAMUNDA_SECURITY_INITIALIZATION_USERS_0_EMAIL= # add more users as desired by repeating the variables with an incremented index, # like CAMUNDA_SECURITY_INITIALIZATION_USERS_1_USERNAME ``` ```yaml orchestration: security: initialization: users: - username: password: name: email: # add more users to this list as desired ``` :::note By default, a user is not assigned to any roles and therefore has no permissions. See the following section to learn how to assign a user to a role via configuration. ::: #### Assign users, clients, groups, or mapping rules to roles via configuration The orchestration cluster provides a number of [built-in roles](/components/concepts/access-control/authorizations.md#default-roles) with predefined permissions for easier setup. To assign users, clients, groups, or [mapping rules](/components/concepts/access-control/mapping-rules.md) to roles, add the appropriate properties to your `application.yaml` or set them as environment variables. ```yaml camunda: security: initialization: defaultRoles: : users: - # add more users to this list as desired clients: - # add more clients to this list as desired groups: - # add more groups to this list as desired mappings: - # add more mappings to this list as desired ``` ```shell CAMUNDA_SECURITY_INITIALIZATION_DEFAULTROLES__USERS_0= CAMUNDA_SECURITY_INITIALIZATION_DEFAULTROLES__CLIENTS_0= CAMUNDA_SECURITY_INITIALIZATION_DEFAULTROLES__GROUPS_0= CAMUNDA_SECURITY_INITIALIZATION_DEFAULTROLES__MAPPINGS_0= # add more members as desired by repeating the variables with an incremented index, # like CAMUNDA_SECURITY_INITIALIZATION_DEFAULTROLES__USERS_1 ``` Replace `` with the ID of the role you want to configure. :::note Helm deployments When you deploy with Helm, configure these properties via an `application.yaml` file using [application configs](/self-managed/deployment/helm/configure/application-configs.md) (for example with `orchestration.extraConfiguration`), rather than as dedicated Helm values. ::: Here is an example how to configure a user `demo` to become a member of the admin role: ```yaml camunda: security: initialization: defaultRoles: admin: users: - demo ``` ```shell CAMUNDA_SECURITY_INITIALIZATION_DEFAULTROLES_ADMIN_USERS_0=demo ``` You can assign a user to multiple roles by listing them in the respective section of each role. ## Enable API authentication and authorizations In Camunda 8 Run installations, Basic authentication is enabled for the Orchestration Cluster web components, but the API is unprotected, and [authorizations](/components/admin/authorization.md) are disabled. API protection and authorizations can both be enabled by modifying your `application.yaml` or environment variables: ```yaml camunda: security: authentication: unprotected-api: false authorizations: enabled: true ``` ```shell CAMUNDA_SECURITY_AUTHENTICATION_UNPROTECTEDAPI=false CAMUNDA_SECURITY_AUTHORIZATIONS_ENABLED=true ``` ```yaml orchestration: security: authentication: unprotectedApi: false authorizations: enabled: true ``` :::note To enable authorizations, API protection must also be enabled. ::: Basic authentication credentials are then required when making API requests, as in the following: ```shell curl --request POST 'http://localhost:8080/v2/process-definitions/search' \ -u demo:demo \ --header 'Content-Type: application/json' \ --data-raw '{}' ``` --- ## Special OIDC configuration cases This page provides guidance for less common OIDC configuration scenarios. ## Use separate OIDC provider URIs for browser and backend In some cases, the Orchestration Cluster backend and the user’s browser may need to communicate with the OIDC provider using different URLs. For example, during the [OIDC authorization code flow](https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow): - The browser is redirected to the OIDC provider for login. - Once the user grants consent, the Orchestration Cluster backend exchanges the authorization code for an ID/access token by calling the provider’s token endpoint. In most environments—such as production or centrally managed OIDC providers—both backend and browser can use the same URL. In that case, configure only the [OIDC issuer property](../core-settings/configuration/properties.md#camundasecurityauthenticationoidc) `camunda.security.authentication.oidc.issuer-uri`. The Orchestration Cluster will then request the provider’s well-known endpoint and resolve the required URIs (e.g., authorization and token endpoints). However, in some development environments (for example, Docker Compose), the backend and browser may need different URLs: - The Orchestration Cluster backend might access the OIDC provider through a domain name valid only inside the Docker network. - The browser may need to use `localhost` with a mapped port. In these cases, omit the `camunda.security.authentication.oidc.issuer-uri` property and configure the endpoints explicitly. ```yaml camunda.security: authentication: oidc: authorization-uri: http://localhost:18080/protocol/openid-connect/auth token-uri: http://:18080/protocol/openid-connect/token jwk-set-uri: http://:18080/protocol/openid-connect/certs end-session-endpoint-uri: http://:18080/protocol/openid-connect/logout ``` ``` CAMUNDA_SECURITY_AUTHENTICATION_OIDC_AUTHORIZATIONURI=http://localhost:18080/protocol/openid-connect/auth CAMUNDA_SECURITY_AUTHENTICATION_OIDC_TOKENURI=http://:18080/protocol/openid-connect/token CAMUNDA_SECURITY_AUTHENTICATION_OIDC_JWKSETURI=http://:18080/protocol/openid-connect/certs CAMUNDA_SECURITY_AUTHENTICATION_OIDC_ENDSESSIONENDPOINTURI=http://:18080/protocol/openid-connect/logout ``` The exact property values depend on your OIDC provider and environment. ## Specify resources in token and authorization requests The Orchestration Cluster supports [RFC 8707 (Resource Indicators for OAuth 2.0)](https://datatracker.ietf.org/doc/html/rfc8707). With this, you can specify target resources when requesting access tokens and performing authorization with your OIDC provider. This is useful when your OIDC provider issues different tokens depending on the intended resource (audience). By including resource indicators in token and authorization requests, you can ensure the Orchestration Cluster receives tokens with the appropriate audience claims for your environment. Configure resource indicators using the `camunda.security.authentication.oidc.resource` property: ```yaml camunda: security: authentication: oidc: resource: - https://api.example.com - https://another-resource.example.com ``` Configure resource indicators using the `CAMUNDA_SECURITY_AUTHENTICATION_OIDC_RESOURCE_0` environment variable: ``` CAMUNDA_SECURITY_AUTHENTICATION_OIDC_RESOURCE_0=https://api.example.com CAMUNDA_SECURITY_AUTHENTICATION_OIDC_RESOURCE_1=https://another-resource.example.com ``` ## Use the Bearer JWT client authentication method In environments that require additional security, you can configure the Orchestration Cluster backend to use the Bearer JWT client authentication method, **private key JWT**, instead of the standard client ID and secret method. With this approach, no client secret is used as a credential. Instead, a client assertion JWT is generated and signed using the client’s certificate. For more details on the private key JWT authentication method, refer to [OAuth 2.0 Private Key JWT](https://oauth.net/private-key-jwt/). The OIDC client credentials flow continues to operate normally, the only difference is the type of client credentials used to authenticate with the IdP. Consult your IdP’s documentation for instructions on configuring private key JWT. For example, see [Keycloak documentation](https://www.keycloak.org/securing-apps/authz-client#_client_authentication_with_signed_jwt). Below is the minimal required client credential configuration when using the private key JWT method. Note the absence of `clientSecret`: ```yaml camunda: security: authentication: oidc: clientId: clientAuthenticationMethod: private_key_jwt assertion: keystore: path: password: keyAlias: keyPassword: ``` A comprehensive list of available configuration properties can be found in [OIDC configuration reference](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#camundasecurityauthenticationoidc). --- ## Backups(Concepts) When running an orchestration cluster with [secondary storage](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#data---secondary-storage), you must configure a snapshot repository in your chosen database: - [Elasticsearch snapshot repository](https://www.elastic.co/guide/en/elasticsearch/reference/current/snapshot-restore.html) - [OpenSearch snapshot repository](https://docs.opensearch.org/docs/latest/tuning-your-cluster/availability-and-recovery/snapshots/snapshot-restore/) The Orchestration Cluster is configured with the snapshot repository name to trigger database snapshots. This ensures coherent backups. :::info Learn more about the backup procedure and why it must be triggered in the [backup guide](/self-managed/operational-guides/backup-restore/backup-and-restore.md). For disaster recovery using these backups, see [Cold Recovery](/self-managed/concepts/multi-region/cold-recovery.md). ::: ## Configure this in Kubernetes with Helm When you deploy Camunda 8 Self-Managed on Kubernetes, set the backup repository name as an application configuration value. See [application configurations](/self-managed/deployment/helm/configure/application-configs.md) for how to provide configuration keys using Helm (for example, via values files or environment variable mappings). ## Configuration parameters | Configuration key | Description | Default value | | --------------------------------------------------------------------- | -------------------------------------- | ------------- | | `camunda.data.secondary-storage.elasticsearch.backup.repository-name` | Elasticsearch snapshot repository name | - | | `camunda.data.secondary-storage.opensearch.backup.repository-name` | OpenSearch snapshot repository name | - | --- ## Data retention(Concepts) The Orchestration Cluster centrally manages data retention for all data using unified storage and policy configuration. All cluster data, including deployed process definitions, process instance state, user operations, and technical metadata, is written to secondary storage. Depending on your configuration, this secondary storage uses a document-store backend ([Elasticsearch/OpenSearch](/reference/glossary.md#elasticsearchopensearch)) or an [RDBMS](/reference/glossary.md#rdbms). The data representing process instance state becomes immutable after the process instance is finished, and it becomes eligible for archiving. :::note Secondary storage is configurable. Choose the backend that best fits your requirements for indexing, querying, retention, and operations. See [configuring secondary storage](/self-managed/concepts/secondary-storage/configuring-secondary-storage.md) for setup guidance, and refer to [secondary storage](/reference/glossary.md#secondary-storage) for terminology and conceptual context. ::: When using Elasticsearch/OpenSearch, finished data is moved to a dated index (for example, `operate-variable_2020-01-01`), with the suffix representing the completion date of the associated process or operation. Data from both main and dated indices remains searchable and visible in the UI. For RDBMS backends, the exporter does not create dated indices. Data remains in the same tables and stays visible until retention policies delete it. ## Archive period The time between a process instance finishing and being moved to a dated index can be configured using the [waitPeriodBeforeArchiving](/self-managed/components/orchestration-cluster/zeebe/exporters/camunda-exporter.md?configuration=history#options) parameter. Refer to that configuration for the current default value. ## Archive by ID When `archiveByIdEnabled` is `true` (the default), archiving moves documents in small, targeted batches instead of all matching records at once. Archiving continues incrementally until every document for the selected process instances reaches the relevant dated indices. The core archiving concept is unchanged: data still moves to the same dated destination indices (for example, `operate-variable_2020-01-01`). Archiving by ID uses fewer resources, which improves stability. `rolloverBatchSize` controls how many process instances are selected per run, and `reindexBatchSize` controls how many individual Elasticsearch/OpenSearch documents are archived in each targeted batch. When `archiveByIdEnabled` is `true`, `rolloverBatchSize` defaults to 500 (and to 100 when `false`); keep `rolloverBatchSize` at 500 or higher to maintain a healthy pipeline of data. See [history archiving settings](/self-managed/components/orchestration-cluster/zeebe/exporters/camunda-exporter.md?configuration=history#options) for the full configuration reference. ## Hierarchy-aware retention Starting with Camunda 8.9, retention in Elasticsearch/OpenSearch secondary storage becomes hierarchy-aware for process instance data. This means child process instances (for example, started via Call Activities) are retained as long as their root process instance is retained, instead of being cleaned up independently based on their own end time. ### Retention modes The following retention modes determine how process instance hierarchies are retained. When using Elasticsearch or OpenSearch, you can control how process instance hierarchies and legacy process instance hierarchies (where the root process instance was started before upgrading to 8.9) are handled by configuring the following properties: - `camunda.data.secondary-storage.elasticsearch.history.process-instance-retention-mode` - `camunda.data.secondary-storage.opensearch.history.process-instance-retention-mode` See the [property reference](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md) for details and all available secondary storage settings. The following values are available: - `PI_HIERARCHY` (default starting with 8.9) - For process instance hierarchies started on 8.9 and later, the archiver treats the entire hierarchy (root + descendants + related records) as one retention unit. - For legacy process instance hierarchies (where the root process instance was started before upgrading to 8.9), the archiver keeps the pre-8.9 per-process-instance behavior. This also applies to any process instances started after the upgrade that belong to such legacy hierarchies. - `PI_HIERARCHY_IGNORE_LEGACY` - Applies hierarchy-based retention only to process instance hierarchies started on 8.9 and later. - Ignores legacy process instance hierarchies (no automated per-instance archiving/deletion), intended when legacy cleanup is handled separately. - `PI` (legacy per-process-instance behavior) - Preserves the pre-8.9 behavior and performs retention per process instance (as in 8.8 and earlier). - Child process instances can be archived/deleted independently of the root instance. ## Data cleanup The amount of stored data can grow significantly over time. Therefore, we recommend implementing a data cleanup strategy. Dated indices, which contain only finished process instances, may be safely removed from Elasticsearch/OpenSearch. In the Orchestration Cluster, strategies for the deletion of archived data can be defined via the [retention configuration](/self-managed/components/orchestration-cluster/zeebe/exporters/camunda-exporter.md?configuration=retention#options). --- ## Monitoring(Concepts) The Orchestration Cluster includes the [Spring Boot Actuator](https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready), which provides health checks, metrics, and other monitoring endpoints out of the box. ## Default configuration By default, the Orchestration Cluster uses the following Actuator configuration (differences noted inline): ```yaml # Disable default health indicators # https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-health-indicators management.health.defaults.enabled: false # Enable Kubernetes health groups # https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-kubernetes-probes management.health.probes.enabled: true # (Operate) management.endpoint.health.probes.enabled: true # (Tasklist) # Expose selected Actuator endpoints management.endpoints.web.exposure.include: health, prometheus, loggers, usage-metrics, backup(s) ``` Operate uses `backup`, while Tasklist uses `backups`. ## Available endpoints With this configuration, the following endpoints are available in the Orchestration Cluster: - `:9600/actuator/prometheus` – Prometheus metrics - `:9600/actuator/health/liveness` – Liveness probe - `:9600/actuator/health/readiness` – Readiness probe You can override these defaults by adjusting the configuration parameters. ## Using probes in Kubernetes For details on setting Kubernetes probe parameters, see [Kubernetes configure probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#configure-probes). **Readiness probe:** ```yaml readinessProbe: httpGet: path: /actuator/health/readiness port: 9600 initialDelaySeconds: 30 periodSeconds: 30 ``` **Liveness probe:** ```yaml livenessProbe: httpGet: path: /actuator/health/liveness port: 9600 initialDelaySeconds: 30 periodSeconds: 30 ``` --- ## Schema and data migration The orchestration cluster persists runtime and task data in secondary storage. This page describes schema and migration behavior for document-store secondary storage (Elasticsearch/OpenSearch), where indices and templates are created automatically on first startup. For RDBMS secondary storage guidance, see [RDBMS configuration](/self-managed/concepts/databases/relational-db/configuration.md). ## Schema For document-store backends, cluster data is stored in indices governed by a schema version. Index names follow this pattern: ```text {cluster-index-prefix}-{legacy-prefix}-{datatype}-{schemaversion}_[{date}] ``` - `cluster-index-prefix` – Prefix for index names. It's empty by default and set with `camunda.data.secondary-storage.{elasticsearch|opensearch}.index-prefix`. - `legacy-prefix` – Legacy component prefix (e.g. `operate`, `tasklist`, `camunda`). - `datatype` – Identifies the type of data stored (e.g., `user`, `variable`, `task`). - `schemaversion` – Version of the index schema. - `date` – Optional date for archived data. > The version in the index name is specific to the schema and may differ from the cluster software version. See [data retention](/self-managed/components/orchestration-cluster/core-settings/concepts/data-retention.md). For configuration details related to document-store secondary storage, see the [secondary storage properties](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#data---secondary-storage) and the [Camunda Exporter](/self-managed/components/orchestration-cluster/zeebe/exporters/camunda-exporter.md) options. From 8.8 onwards, no schema migrations are required when upgrading the orchestration cluster. > See also: [Version compatibility checks](./version-compatibility.md) for the rules governing supported upgrade paths and how schema version metadata is validated. --- ## Usage metrics(Concepts) :::warning Removed endpoints The actuator usage metrics endpoints (`/actuator/usage-metrics/*`) were deprecated in 8.8 and removed in 8.10. Use the [usage metrics endpoint](#usage-metrics-endpoint-recommended) instead. ::: ## Usage metrics endpoint (recommended) ### Overview The usage metrics endpoint retrieves metrics for a specific date range, including: - **Process instances:** Total created - **Decision instances:** Total executed - **User tasks assigned:** Total unique assignees - **Active tenants:** Total active tenants - **Tenants:** List of active tenants with per-tenant metrics > **Export interval** > Usage metrics are exported every 5 minutes by default. This may cause a short delay in reported metrics. > You can adjust this interval in `application.properties` or via environment variables: > > ```properties > zeebe.broker.experimental.engine.usageMetrics.exportInterval=5m > ``` ### API details | Parameter | Required | Description | Format / Default | | ------------- | -------- | ---------------------------- | ----------------------------------------- | | `startTime` | Yes | Start of date range | ISO 8601: `2025-09-16T12:30:45.123-00:00` | | `endTime` | Yes | End of date range | ISO 8601: `2025-09-16T12:30:45.123-00:00` | | `tenantId` | No | Filter by tenant | String | | `withTenants` | No | Include per-tenant breakdown | `false` (default) | **Endpoint:** ``` http://:/v2/system/usage-metrics?startTime={startTime}&endTime={endTime}&tenantId={tenantId}&withTenants={withTenants} ``` More info: [usage metrics API](/apis-tools/orchestration-cluster-api-rest/specifications/get-usage-metrics.api.mdx) ### Examples **Without tenant breakdown:** ``` http://:/v2/system/usage-metrics?startTime={startTime}&endTime={endTime}&withTenants=false ``` _Response:_ ```json { "processInstances": 5, "decisionInstances": 23, "activeTenants": 2, "assignees": 3, "tenants": {} } ``` **With tenant breakdown:** ``` http://:/v2/system/usage-metrics?startTime={startTime}&endTime={endTime}&withTenants=true ``` _Response:_ ```json { "processInstances": 5, "decisionInstances": 23, "activeTenants": 2, "assignees": 3, "tenants": { "tenant1": { "processInstances": 1, "decisionInstances": 2, "assignees": 1 }, "tenant2": { "processInstances": 4, "decisionInstances": 21, "assignees": 3 } } } ``` ## Best practices for monitoring - Monitor overall cluster activity by combining process, decision, and task metrics. - Track trends over time to better understand resource usage and user engagement. - Integrate metrics into dashboards or automation scripts for centralized monitoring and alerting. --- ## Version compatibility checks This page describes how Camunda 8 validates version compatibility when you upgrade a Self-Managed Orchestration Cluster. It covers: - What defines a compatible or incompatible upgrade path - How the **broker** enforces version rules - How **secondary storage management** performs similar checks ## Semantic version basics Camunda 8 versions follow the `MAJOR.MINOR.PATCH` format (for example, `8.8.3`). Early access builds include a pre-release suffix (for example, `8.8.0-alpha1`). - **Major**: No cross-major upgrades or downgrades are supported directly. - **Minor**: Feature releases. You must move only one minor step at a time and follow the required upgrade procedure described below. - **Patch**: Bug or security fixes. You can move forward within the same minor (for example, `8.8.1 → 8.8.3`). - **Pre-release (alpha)**: Builds tagged with `-alpha*` are not valid endpoints in a supported upgrade path. ## Required upgrade procedure All minor version upgrades in a Self-Managed Orchestration Cluster must follow this procedure: 1. **Upgrade to the latest patch version of your current minor.** For example, before upgrading from `8.7.x` to `8.8.y`, first upgrade to the latest `8.7` patch. This is strongly recommended for fix coverage. The compatibility check itself compares minor versions, so any patch of the source minor is accepted. Skipping this step can still expose you to bugs already fixed in later patches. 2. **Upgrade to the next minor version.** Do not skip minor versions. For example, `8.7.x → 8.8.y` is supported, but `8.6.x → 8.8.y` is not. This rule is enforced: skipping a minor fails the compatibility check. The broker marks itself unhealthy, and the schema manager fails startup. 3. After reaching the target minor, **upgrade to the latest patch version of the target minor.** For example, after upgrading to `8.8.0`, update to the latest `8.8` patch. You can execute this procedure across multiple validation environments (for example, dev → test → stage → prod) before production rollout. You must not: - Skip minor versions. - Downgrade minor or major versions. - Include pre-release (`-alpha*`) versions in an upgrade chain. Failure to follow these rules results in an unsupported upgrade path. To prevent unsafe migrations, the broker marks itself unhealthy and applies no migration, and the schema manager fails startup. ## Supported upgrade paths The examples below show representative compatible and incompatible paths. Patch versions can vary as long as minor-version rules are followed. | Scenario | Example | Compatibility | | -------------------------------- | -------------------- | ---------------------------------------------------------- | | Patch upgrade | 8.8.1 → 8.8.3 | Compatible | | Minor upgrade (single step) | 8.7.5 → 8.8.3 | Compatible | | Minor upgrade (skipping a minor) | 8.6.9 → 8.8.3 | Incompatible | | Patch downgrade | 8.8.3 → 8.8.1 | Incompatible (broker); secondary storage skips (see below) | | Minor downgrade | 8.8.3 → 8.7.5 | Incompatible (broker); secondary storage skips (see below) | | Major change | 8.x ↔ 9.x | Incompatible | | Alpha build involved | 8.8.0-alpha1 ↔ 8.8.0 | Incompatible | ## Broker behavior The broker validates the previous persisted version against the new binary version during startup: - **Compatible:** Proceeds with state (DB) migration if required (skipped for restarts or no-op cases). - **Incompatible:** Starts but marks itself unhealthy and does not apply any state or data migration. Check startup logs, fix the unsupported upgrade path, and restart. ### Why incompatible paths are blocked Skipping minors, downgrading, or involving pre-release versions can result in an incompatible state that the broker cannot safely reconcile. Blocking these paths prevents partial or irreversibly invalid migrations. ## Secondary storage (Elasticsearch or OpenSearch) schema manager Secondary storage holds exported data. Starting with Camunda 8.8, schema upgrades are designed to be backward compatible, allowing older application nodes to continue writing to newer schemas. ### When version checks are available | Capability | Introduced in patches | | --------------------------------------------------------------------------------------- | ----------------------- | | Store schema version metadata (no enforcement) | 8.6.31 / 8.7.18 / 8.8.3 | | Enforce compatibility rules (same matrix as broker, with tolerant skips for downgrades) | 8.8.3 | If you upgrade from an earlier patch that does **not** store schema version metadata, the schema manager treats it as an indeterminate case (assumed fresh install) and proceeds. ### Schema manager rules All minor version upgrades must follow the required upgrade procedure described above and proceed strictly minor-by-minor. The schema manager compares the stored schema version (the last successful schema upgrade) with the current application version: | Case | Action | Metadata updated? | Notes | | --------------------------- | ------------- | -------------------- | --------------------------------------------- | | Patch upgrade | Update schema | Yes (to new version) | | | Minor upgrade (single step) | Update schema | Yes | | | Minor downgrade | Skip | No | Tolerated for rolling update restarts | | Patch downgrade | Skip | No | Avoids churn; schema stays forward compatible | | Skipped minor (multi-step) | Fail startup | No | Prevents unsupported jump | | Alpha build involved | Fail startup | No | Must use stable releases for upgrade path | | Major change | Fail startup | No | Not supported | ### Where the schema version is stored The schema version metadata is stored in a dedicated metadata index. For current versions, this appears as an index named: ```text operate-metadata-8.8.0_ ``` Within that index, the document holding the current schema baseline uses the identifier: ```text id = "schema-version" ``` The document value reflects the last successfully applied version (for example, `8.8.3`). This value is updated only after a successful schema upgrade. If the document or index is missing (for example, if you upgraded from an older patch that didn’t write it), the system treats the startup as a fresh baseline and writes it after initialization. ### Failure behavior If the schema manager detects an incompatible path, it fails fast during application startup before modifying indices. Existing indices remain unchanged. ### Rolling updates and restarts Because schema upgrades are backward compatible from 8.8 onward, a temporary mix of versions during a rolling update is safe. Older patch or minor nodes encountering a newer stored schema version skip schema changes automatically. ## Pre-release (alpha) builds Alpha builds (`-alpha*`) are for evaluation and are **not** valid sources or targets for a supported production upgrade path. Always upgrade between stable releases. ## Recommended operational steps 1. Follow the [required upgrade procedure](#required-upgrade-procedure) for every minor upgrade. 1. Do not include pre-release builds in production upgrade chains. 1. Investigate any broker health status showing `brokerStatus: DOWN` after an upgrade. This typically indicates a rejected upgrade path. ## See also - [Schema and data migration](./schema-and-migration.md) --- ## Admin: Identity as Code This page explains how to configure Identity as Code in the Camunda 8 Self-Managed Orchestration Cluster. Use Identity as Code to create users, roles, groups, authorizations, mapping rules, and tenants at application start. ## Use cases Identity as Code simplifies configuring Self-Managed orchestration clusters across multiple stages. You can create [all identity-related entities](/components/admin/admin-introduction.md#manage-access) on one stage and then deploy them to other stages without further interaction, reducing the chance of error. Another use case is local development, where a cluster might be recreated regularly. After Admin creates an entity, changing its configuration does not update the existing entity. Admin checks only the ID to decide whether an entity already exists. When you deploy with Helm, the most reliable approach is to provide Identity as Code settings through [application configs](/self-managed/deployment/helm/configure/application-configs.md) using `orchestration.extraConfiguration`. The Helm examples below use this pattern so you can apply the same approach consistently across all entity types. ## Configure authorizations ```bash CAMUNDA_SECURITY_INITIALIZATION_AUTHORIZATIONS_0_OWNER_TYPE=USER CAMUNDA_SECURITY_INITIALIZATION_AUTHORIZATIONS_0_OWNER_ID=john.doe CAMUNDA_SECURITY_INITIALIZATION_AUTHORIZATIONS_0_RESOURCE_TYPE=RESOURCE CAMUNDA_SECURITY_INITIALIZATION_AUTHORIZATIONS_0_RESOURCE_ID=* CAMUNDA_SECURITY_INITIALIZATION_AUTHORIZATIONS_0_PERMISSIONS=CREATE,READ ``` ```yaml orchestration: extraConfiguration: - file: identity-as-code.yaml content: | camunda: security: initialization: authorizations: - ownerType: USER ownerId: john.doe resourceType: RESOURCE resourceId: "*" permissions: - CREATE - READ ``` ## Configure groups ```bash CAMUNDA_SECURITY_INITIALIZATION_GROUPS_0_GROUP_ID=test-group CAMUNDA_SECURITY_INITIALIZATION_GROUPS_0_NAME="Test Group" CAMUNDA_SECURITY_INITIALIZATION_GROUPS_0_DESCRIPTION="A cool test group!" CAMUNDA_SECURITY_INITIALIZATION_GROUPS_0_CLIENTS="ClientA,ClientB,ClientC" CAMUNDA_SECURITY_INITIALIZATION_GROUPS_0_MAPPING_RULES="RuleA,RuleB,RuleC" CAMUNDA_SECURITY_INITIALIZATION_GROUPS_0_USERS="UserA,UserB,UserC" ``` ```yaml orchestration: extraConfiguration: - file: identity-as-code.yaml content: | camunda: security: initialization: groups: - groupId: test-group name: Test Group description: A cool test group! clients: - ClientA - ClientB - ClientC mappingRules: - RuleA - RuleB - RuleC users: - UserA - UserB - UserC ``` ## Configure mapping rules ```bash CAMUNDA_SECURITY_INITIALIZATION_MAPPINGRULES_0_CLAIMNAME=isAllowedToDoStuff CAMUNDA_SECURITY_INITIALIZATION_MAPPINGRULES_0_CLAIMVALUE=true CAMUNDA_SECURITY_INITIALIZATION_MAPPINGRULES_0_MAPPINGRULEID=my-mapping-rule ``` ```yaml orchestration: extraConfiguration: - file: identity-as-code.yaml content: | camunda: security: initialization: mappingRules: - claimName: isAllowedToDoStuff claimValue: "true" mappingRuleId: my-mapping-rule ``` ## Configure Roles ```bash CAMUNDA_SECURITY_INITIALIZATION_ROLES_0_ROLE_ID=test-role CAMUNDA_SECURITY_INITIALIZATION_ROLES_0_NAME="Test Role" CAMUNDA_SECURITY_INITIALIZATION_ROLES_0_DESCRIPTION="A cool test role!" CAMUNDA_SECURITY_INITIALIZATION_ROLES_0_CLIENTS="client1,client2" CAMUNDA_SECURITY_INITIALIZATION_ROLES_0_GROUPS="group1,group2" CAMUNDA_SECURITY_INITIALIZATION_ROLES_0_MAPPING_RULES="m1,m2" CAMUNDA_SECURITY_INITIALIZATION_ROLES_0_USERS="UserA,UserB,UserC" ``` ```yaml orchestration: extraConfiguration: - file: identity-as-code.yaml content: | camunda: security: initialization: roles: - roleId: test-role name: Test Role description: A cool test role! clients: - client1 - client2 groups: - group1 - group2 mappingRules: - m1 - m2 users: - UserA - UserB - UserC ``` ## Configure tenants ```bash CAMUNDA_SECURITY_INITIALIZATION_TENANTS_0_TENANT_ID=tenantId CAMUNDA_SECURITY_INITIALIZATION_TENANTS_0_NAME="test tenant" CAMUNDA_SECURITY_INITIALIZATION_TENANTS_0_DESCRIPTION="test tenant description" CAMUNDA_SECURITY_INITIALIZATION_TENANTS_0_CLIENTS='R1,R2,R3,R4' CAMUNDA_SECURITY_INITIALIZATION_TENANTS_0_GROUPS='R1,R2,R3,R4' CAMUNDA_SECURITY_INITIALIZATION_TENANTS_0_MAPPING_RULES='R1,R2,R3,R4' CAMUNDA_SECURITY_INITIALIZATION_TENANTS_0_ROLES='R1,R2,R3,R4' CAMUNDA_SECURITY_INITIALIZATION_TENANTS_0_USERS='UserA,UserB,UserC' ``` ```yaml orchestration: extraConfiguration: - file: identity-as-code.yaml content: | camunda: security: initialization: tenants: - tenantId: tenantId name: test tenant description: test tenant description clients: - R1 - R2 - R3 - R4 groups: - R1 - R2 - R3 - R4 mappingRules: - R1 - R2 - R3 - R4 roles: - R1 - R2 - R3 - R4 users: - UserA - UserB - UserC ``` ## Configure users When configuring users, never hardcode the password. Resolve it from a vault instead. ```bash CAMUNDA_SECURITY_INITIALIZATION_USERS_0_EMAIL=john.doe@example.com CAMUNDA_SECURITY_INITIALIZATION_USERS_0_NAME="john doe" CAMUNDA_SECURITY_INITIALIZATION_USERS_0_PASSWORD=***** CAMUNDA_SECURITY_INITIALIZATION_USERS_0_USERNAME=john.doe ``` ```yaml orchestration: extraConfiguration: - file: identity-as-code.yaml content: | camunda: security: initialization: users: - email: john.doe@example.com name: John Doe password: "*****" username: john.doe ``` --- ## CSRF protection Cross-Site Request Forgery (CSRF) is a type of malicious exploit where unauthorized commands are transmitted from a user that the web application trusts. In a CSRF attack, an attacker tricks a victim's browser into making unwanted requests to a web application where the victim is authenticated. For a comprehensive understanding of CSRF attacks and prevention methods, refer to the [MDN Web Docs on CSRF](https://developer.mozilla.org/en-US/docs/Glossary/CSRF). Review the configuration details in the [properties documentation](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#camundasecuritycsrf). :::caution Disabling CSRF protection is not recommended for production environments as it leaves your application vulnerable to cross-site request forgery attacks. ::: ## How CSRF protection works in Camunda - **Token generation**: A unique CSRF token is generated and stored in a cookie named `X-CSRF-TOKEN`. The cookie is `HttpOnly` only when [`camunda.security.csrf.cookie-http-only`](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#camundasecuritycsrf) is `true`. The default is `false`, so browser applications can read the token with JavaScript. - **Token validation**: For state-changing requests (POST, PUT, DELETE, etc.), the server validates that the CSRF token in the request header `X-CSRF-TOKEN` matches the one in the cookie. - **Safe methods**: GET, HEAD, TRACE, and OPTIONS requests are considered safe and don't require CSRF validation. ## Protected vs unprotected paths ### Protected paths (require CSRF token) - `/api/**` – API endpoints (except specifically excluded paths) - `/v2/**` – Versioned API endpoints - `/login` – The form-login endpoint (see [Log in with a CSRF token](#log-in-with-a-csrf-token)) - All state-changing operations (POST, PUT, DELETE, PATCH) ### Unprotected paths (no CSRF token required) - `/actuator/**` – Health and monitoring endpoints - `/v2/license` – Public license endpoint - `/error` – Error handling - `/logout` – Ends the user session If a path matches both lists, it is unprotected. For example, `/v2/license` requires no token although `/v2/**` is protected. To exempt more paths, set [`camunda.security.csrf.ignored-path-patterns`](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#camundasecuritycsrf) to a list of Ant-style path patterns. ## Log in with a CSRF token To log in programmatically, fetch a CSRF token with `GET /login`, then send it as the `X-CSRF-TOKEN` header on `POST /login`. `POST /login` requires a valid CSRF token like any other state-changing request. It is not exempt, even for a browser that does not have a session yet. This prevents an attacker-controlled page from silently logging a victim's browser into an attacker-chosen account. A client driving login itself (a script, health check, or custom frontend) must fetch a token before submitting credentials: 1. Send a `GET` request to `/login`. The response includes the token both as an `X-CSRF-TOKEN` cookie and as an `X-CSRF-TOKEN` response header. 2. Submit `POST /login` with `username` and `password`. Send the token from step 1 as the `X-CSRF-TOKEN` request header, and send the `X-CSRF-TOKEN` cookie from step 1 with the request. Omitting the header on `POST /login` now fails with `401`/`403` instead of succeeding with `204`. If the request fails, fetch a new token with `GET /login` and retry. ## Security considerations - Always use HTTPS in production to prevent token interception. - Consider additional security headers configured in the security settings. - Regularly review the patterns in `camunda.security.csrf.ignored-path-patterns` and keep each pattern as narrow as possible. --- ## Licensing(Configuration) --- ## Logging(4) This page explains how to configure and manage logging in the **Camunda 8 Self-Managed Orchestration Cluster**, which includes **Zeebe**, **Operate**, **Tasklist**, and **Admin**. All components use the **Log4j2** framework. You can configure log levels, output formats, appenders, and adjust logging dynamically at runtime. ## Default Log4j2 configuration The default `log4j2.xml` (Zeebe, Operate, Tasklist, Admin) representation: ```xml ``` :::note This is a simplified example. The actual `log4j2.xml` may include additional appenders, different file paths, or slightly different patterns. See the full [logging documentation](/self-managed/components/orchestration-cluster/core-settings/configuration/logging.md#default-log4j2-configuration) for the official default file and additional settings. ::: ## Environment variables | Purpose | Variable | Component(s) | Example / Notes | | ------------------- | ------------------- | ------------------- | ----------------------------------------------- | | Global log level | `CAMUNDA_LOG_LEVEL` | All | `DEBUG`, `INFO`, `WARN`, etc. | | Zeebe package level | `ZEEBE_LOG_LEVEL` | Zeebe | Overrides global level | | Atomix / clustering | `ATOMIX_LOG_LEVEL` | Atomix / Raft | Defaults to `CAMUNDA_LOG_LEVEL`, otherwise INFO | | Elasticsearch logs | `ES_LOG_LEVEL` | `org.elasticsearch` | | ## JSON logging appenders | Appender | Description | Enable / Variable | | ------------------ | -------------------------------------------------- | ------------------------------------------------------ | | Console | Standard text output | `*_LOG_APPENDER=Console` | | Stackdriver (JSON) | JSON output for Google Cloud / Stackdriver | `*_LOG_APPENDER=Stackdriver` | | RollingFile | Writes logs to a rotating file, enabled by default | Disable with `CAMUNDA_LOG_FILE_APPENDER_ENABLED=false` | ## Pattern layout/format - The default layout shows the full date and time, thread name, MDC context, log level, logger name, and message. **Default pattern:** ```perl [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%t] %notEmpty{[%X] }%-5level \t%logger{36} - %msg%n ``` ## Changing log level at runtime You can adjust log levels dynamically using Spring Boot Actuator endpoints: ```bash curl 'http://localhost:9600/actuator/loggers/io.camunda' \ -i -X POST \ -H 'Content-Type: application/json' \ -d '{"configuredLevel":"DEBUG"}' ``` Replace `io.camunda` with the logger you want to adjust. ## Sensitive data By default, loggers that may handle sensitive information (e.g., variable values, actor names) are set to **INFO**. To capture more detail (DEBUG or TRACE), explicitly configure those loggers via their logger names or component-specific variables. :::warning Enabling verbose logging may expose sensitive data. Use debug/trace levels only temporarily for troubleshooting. ::: ## Best practices - Always check the inline default configuration before customizing. - Use component-specific `*_LOG_LEVEL` and `*_LOG_APPENDER` variables for fine-grained control. - Select the Stackdriver appender only when you need JSON output; the console appender is simpler for stdout logs. Disable the RollingFile appender with `CAMUNDA_LOG_FILE_APPENDER_ENABLED=false` if you collect logs from stdout only. - Monitor logging setup across environments (dev/test/prod) to avoid surprises caused by log verbosity. --- ## Property reference(Configuration) As a Spring Boot application, the Orchestration Cluster supports standard [Spring configuration](https://docs.spring.io/spring-boot/reference/features/external-config.html) methods. The following configurations apply to all components within the Orchestration Cluster. --- ## Webserver & security ## Setting a custom context path All components support customizing the **context-path** using the default Spring configuration. Example for `application.yml`: ```yaml server.servlet.context-path: / ``` Example for environment variable: ``` SERVER_SERVLET_CONTEXT_PATH=/ ``` The default context-path is `/`. Replace `` with the identifier for the service (for example, `operate`, `tasklist`, or another component). :::note - The same context-path and security configuration patterns apply across most Camunda 8 Self-Managed components. - Component-specific property names and defaults may vary. Check each service’s reference documentation for details. ::: ## Propagation of membership changes to active sessions When a user logs in, the Orchestration Cluster determines their associations at once (membership in roles, groups, tenants; application authorizations) and stores them into the user's active web session. When these associations change (e.g. user is removed from a group; authorizations change), then this is not reflected in this cached state immediately, but only after the next refresh interval elapses. The default interval is 30 seconds but can be configured via the `camunda.security.authentication.authentication-refresh-interval` property to a higher/lower value if needed considering a trade-off between the extra load for session refresh and the criticality of having sync authentications. The property format is an ISO8601 duration, for example `PT10M` to set it to 10 minutes. For more information on ISO8601 duration format, refer to [ISO8601](https://en.wikipedia.org/wiki/ISO_8601#Durations). --- ## Core settings and features Learn about unified configuration for the Camunda 8 Orchestration Cluster components Operate, Tasklist, Zeebe, and Admin (formerly Orchestration Cluster Identity). ## About Orchestration Cluster configuration With the release of version 8.8, configuration and operational concepts for the Orchestration Cluster are unified. This means: - Many configuration options previously documented separately for each component are now consolidated into a single, centralized configuration file and management approach. - Several conceptual topics—such as security headers, authentication, data retention, and usage metrics—apply across all Orchestration Cluster components rather than to just one. - This shared section provides a single source of truth for these cross-cutting concerns, making it easier to find, understand, and maintain orchestration cluster configuration and concepts. This section includes: - **Unified configuration topics:** Centralized documentation on all cluster-wide configuration settings grouped by theme (e.g., security, logging, monitoring). - **Conceptual documentation:** Explanations of cluster-wide behaviors, features, and best practices relevant across multiple components. - **Migration and legacy information:** Guidance related to upgrading and migrating to version 8.8+, including notes on deprecated features and legacy support. - **Cross-component topics:** Information on shared aspects such as HTTP security headers, authentication mechanisms, data retention policies, and usage metrics. :::note Camunda 8.10 unified configuration complete The unified configuration properties are complete in Camunda 8.10. For the properties introduced, removed, or changed in 8.10, see [Property changes in Camunda 8.10](/versioned_docs/version-8.10/self-managed/components/orchestration-cluster/core-settings/configuration/configuration-mapping.md). ::: --- ## Configuration(Operate) As a Spring Boot application, Operate supports any standard [Spring configuration](https://docs.spring.io/spring-boot/reference/features/external-config.html) method. By default, the configuration for Operate is stored in a YAML file (`application.yml`). All Operate-related settings are prefixed with `camunda.operate`. :::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 `_`. For example, the property `camunda.operate.elasticsearch.clustername` is represented by the environment variable `CAMUNDA_OPERATE_ELASTICSEARCH_CLUSTERNAME`. ::: The following parts are configurable: ## Licensing See the [core settings documentation](/self-managed/components/orchestration-cluster/core-settings/configuration/licensing.md). ## Webserver and security See the [core settings documentation](/self-managed/components/orchestration-cluster/core-settings/configuration/webserver.md). ## Secondary storage Review the [secondary storage documentation](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#data---secondary-storage) and [secondary storage configuration](/self-managed/concepts/secondary-storage/configuring-secondary-storage.md). ## Operation executor Operations are user operations, like cancellation of process instance(s) or updating the variable value. Operations are executed in a multi-threaded manner. | Name | Description | Default value | | ---------------------------------------------- | -------------------------------- | ------------- | | camunda.operate.operationExecutor.threadsCount | How many threads should be used. | 3 | ### Snippet from application.yml ```yaml camunda.operate: operationExecutor: threadsCount: 3 ``` ## Tasklist link Operate shows an **Open Tasklist** link for user tasks in the process instance **Details** tab and in the operations log. The link targets the Tasklist of the same Orchestration Cluster. | Name | Description | Default value | | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | | camunda.operate.tasklist-url | Base URL of Tasklist used for the link. Accepts an absolute URL or a path relative to the Operate host (for example `/tasklist`). | `/tasklist` (derived, see below) | If the property is not set and the Operate and Tasklist UIs are served by the same application, Operate derives the default from the configured context path, so the link works without extra configuration. If Tasklist is served by a separate application, the property remains unset and Operate hides the Tasklist links, even if a proxy exposes Tasklist on the same host. In this case, set the property explicitly, for example in a split deployment: ```yaml camunda.operate: tasklist-url: https://tasklist.example.com ``` :::note The derived default and support for relative paths such as `/tasklist` are only available in patch releases that include this fix. In earlier patch releases, the property has no default and requires an absolute URL, so set an absolute URL to show the links. ::: The corresponding environment variable is `CAMUNDA_OPERATE_TASKLISTURL`. ## Monitoring Operate See the [core settings documentation](/self-managed/components/orchestration-cluster/core-settings/concepts/monitoring.md). ## Logging See the [core settings documentation](/self-managed/components/orchestration-cluster/core-settings/configuration/logging.md). ## Backups See the [core settings documentation](/self-managed/components/orchestration-cluster/core-settings/concepts/backups.md). --- ## Operate overview Overview about Operate. --- ## Deployment configuration The Orchestration Cluster REST API supports file and multipart uploads when deploying BPMN diagrams and other resources. By default, the maximum allowed request size is 4MB. You can increase this limit in Self-Managed cluster through configuration of the Zeebe Gateway, Broker, and the REST layer, for example, Spring Boot and Tomcat. This guide walks you through adjusting these settings. ### Gateway and Broker configuration The `maxMessageSize` default value is 4MB for raw Zeebe deployments (10MB for Helm). You can configure this setting in the: - [Gateway config](../orchestration-cluster/zeebe/configuration/gateway.md#zeebegatewaynetwork) - [Broker config](/self-managed/components/orchestration-cluster/zeebe/configuration/broker.md#camundaclusternetwork) If you deploy with Helm, the chart defaults to 10MB via `global.config.requestBodySize`, applying this value to the Zeebe Gateway and broker message size, the REST multipart file and request size, and the Tomcat HTTP form post size on the Zeebe Gateway. Change `global.config.requestBodySize` only if you need a value other than 10MB. If you expose the REST API through Ingress, you must also explicitly set `global.ingress.annotations.nginx.ingress.kubernetes.io/proxy-body-size` to the same value. This annotation is not propagated automatically from `requestBodySize`. This setting aligns the HTTP, multipart, Tomcat, Gateway, and Broker transport limits. Deployments can still be rejected by Zeebe's internal record batch processing if the deployed resources and follow-up records exceed the engine batch limit. This engine-level rejection returns an HTTP 500 response, not HTTP 413. Multipart requests include metadata and boundary overhead in addition to the uploaded file content. Set these limits slightly above the largest file you expect users or Connectors to upload. If you are not using Helm and increase this value, you must also adjust the configuration for the deployment REST endpoint to match. ### Consequences of increasing maxMessageSize Raising `maxMessageSize` can have the following consequences, especially with large, frequent payloads: - **Broker memory**: In-flight log entries consume broker memory, so larger entries increase memory usage. - **Raft timeouts**: An entry too large to replicate in time can trigger unexpected leader changes or broken replication. - **Latency**: Large entries can slow down overall processing. - **Network limits**: Load balancers, ingresses, and firewalls often cap request and response sizes lower than Zeebe does, and may reject larger messages. ### REST API server configuration If you're increasing the `maxMessageSize` and using the REST API for multipart uploads (for example, using `POST /v2/deployments`), you must configure your application to accept the updated request sizes. If you increase the `maxMessageSize` to 10MB, increase these property values to 10MB as well. For Spring Boot applications: ```properties spring.servlet.multipart.max-file-size=10MB spring.servlet.multipart.max-request-size=10MB server.tomcat.max-http-form-post-size=10MB ``` ### Tomcat multipart settings Tomcat, which underlies many Java web applications, also applies multipart limits. If you're uploading multiple files as part of a multipart request, note that Tomcat limits the number of parts per request using the `server.tomcat.max-part-count` property. By default, this is set to 50 in the orchestration API. You can increase the limit to allow more files by setting the property in your configuration: ```properties server.tomcat.max-part-count=100 ``` or using an environment variable: ```properties SERVER_TOMCAT_MAX_PART_COUNT=100 ``` The default is `50` in the orchestration API layer. #### Max parameter count Tomcat also enforces a separate limit on the total number of request parameters via the `server.tomcat.max-parameter-count` property. Since each file upload typically counts as both a part and a parameter, the lower of these two limits will determine how many files can be uploaded. ### Reference For more information on defaults and options, refer to the [Apache Tomcat documentation](https://tomcat.apache.org/). --- ## Orchestration Cluster Install and configure Orchestration Cluster components in your Self-Managed environment. ## Installation and configuration The Orchestration Cluster is the core component of Camunda 8, powering the automation and orchestration of processes. The Orchestration Cluster includes: - Zeebe as the workflow engine - Operate for monitoring and troubleshooting process instances running in Zeebe. - Tasklist for interacting with user tasks (assigning, completing, and so on). - Admin (formerly Orchestration Cluster Identity) for managing the integrated authentication and authorization. - APIs for interacting with the Orchestration Cluster programmatically. In this section, you will learn how to install, and configure these components in your Self-Managed environment. --- ## Tasklist overview Overview about Tasklist. --- ## Configuration(Tasklist) As a Spring Boot application, Tasklist supports any standard [Spring configuration](https://docs.spring.io/spring-boot/reference/features/external-config.html) method. By default, the configuration for Tasklist is stored in a YAML file `application.yml`. All Tasklist-related settings are prefixed with `camunda.tasklist`. :::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 `_`. For example, the property `server.servlet.context-path` is represented by the environment variable `SERVER_SERVLET_CONTEXT_PATH`. ::: The following components are configurable: ## Licensing See the [core settings documentation](/self-managed/components/orchestration-cluster/core-settings/configuration/licensing.md). ## Webserver See the [core settings documentation](/self-managed/components/orchestration-cluster/core-settings/configuration/webserver.md). ## Secondary storage Review the [secondary storage documentation](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#data---secondary-storage) and [secondary storage configuration](/self-managed/concepts/secondary-storage/configuring-secondary-storage.md). ## Intra-cluster secure connection You can enable intra-cluster TLS-secured connections between Tasklist and Zeebe by applying the following configuration properties: | Name | Description | Example value | | :---------------------------------------------------- | :----------------------------------------------------------------------------------------------------------- | :---------------------------------- | | `zeebe.gateway.cluster.initialContactPoints` | Zeebe Gateway initial contact points. | `[gateway-0:26502,gateway-1:26502]` | | `zeebe.gateway.cluster.security.enabled` | Enables secure connections via Transport Layer Security (TLS). | `true` | | `zeebe.gateway.cluster.security.certificateChainPath` | Path to the certificate used by Zeebe. Required if the certificate isn't registered in the operating system. | `/path/to/cert.pem` | | `zeebe.gateway.cluster.security.privateKeyPath` | Path to the certificate's private key used by Zeebe. | `/path/to/private.key` | | `zeebe.gateway.cluster.advertisedHost` | Advertised hostname in the cluster. | `tasklist` | | `zeebe.gateway.cluster.memberId` | Member ID for the cluster. | `tasklist` | For extended configuration and guidelines, refer to [Secure cluster communication](../zeebe/security/secure-cluster-communication.md) and [Gateway configuration](../zeebe/configuration/gateway.md). ## Monitoring and health probes See the [core settings documentation](/self-managed/components/orchestration-cluster/core-settings/concepts/monitoring.md). ## Logging See the [core settings documentation](/self-managed/components/orchestration-cluster/core-settings/configuration/logging.md). ## Allow non-self assignment :::danger The `allow-non-self-assignment` flag applied only to the removed Tasklist V1 API. In Camunda 8.10 and later, Tasklist uses only the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md), so this flag is not part of the current configuration model. Use [authorization-based access control](/components/concepts/access-control/authorizations.md#available-resources) to manage who can assign or update user tasks. ::: ## Tasklist API mode configuration Starting with Camunda 8.10, Tasklist no longer supports switching between V1 and V2 modes. Tasklist always uses the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md), and the legacy `CAMUNDA_TASKLIST_V2_MODE_ENABLED` / `camunda.tasklist.V2ModeEnabled` settings are no longer part of the current configuration model. ## Backups See the [core settings documentation](/self-managed/components/orchestration-cluster/core-settings/concepts/backups.md). --- ## Custom styling You can customize the Tasklist user interface (UI) to visually align it with your organization's brand identity. You can adjust the appearance of various UI elements, such as backgrounds, surfaces, controls, buttons, borders, and text. The Tasklist UI uses the Camunda design system, which defines colors, borders, and corner radius as CSS custom properties called design tokens. You can override these tokens with your own values. :::note In Camunda 8.9 and earlier, Tasklist used the Carbon Design System. Custom styles that override `--cds-*` tokens or use `data-carbon-theme` selectors no longer have any effect. Rewrite them using the tokens and selectors described on this page. ::: ## Customize the Tasklist user interface To customize the user interface, override specific design tokens in a `custom.css` file. For example, you can override the `--background` token to change the Tasklist UI background color. Place the `custom.css` file in the `config` directory, which is on the Camunda classpath: | Installation type | File location | | :------------------- | :------------------------------------------ | | Docker image | `/usr/local/camunda/config/custom.css` | | Distribution archive | `camunda-zeebe-/config/custom.css` | Camunda serves the file at `/custom.css` and reads it once at startup. Restart Camunda after you create or change the file. A typical workflow to customize the Tasklist UI is as follows: 1. **Identify design tokens**: Review your own visual identity guidelines and identify the [design tokens](#common-design-tokens) you want to update. 2. **Override the tokens in custom.css**: Add or modify token values in the `custom.css` file. The syntax for styling is plain CSS. For example, to change the background color, use the following syntax, replacing the `--background` values with your own colors: ```css /* Light theme customization */ html .c4-ui { --background: #ffff00; } /* Dark theme customization */ html .c4-ui.dark, html .dark .c4-ui { --background: #008000; } ``` The `html` prefix makes your selectors more specific than the default token definitions, so your values take precedence regardless of the order in which the stylesheets load. 3. **Test your custom styles**: Test your custom styles in both light and dark modes to verify that they are applied correctly across all Tasklist UI components. 4. **Validate accessibility and visual contrast**: Check that your custom styles maintain good visual contrast between elements. For example, verify that text is easily readable against backgrounds, buttons are distinguishable, and important elements such as links and icons stand out properly. Contrast is especially important for accessibility and readability in both light and dark modes. 5. **Iterate based on results**: If necessary, refine your customizations based on the results of your testing. Adjust values in the `custom.css` file to ensure a consistent look and feel throughout the Tasklist UI. :::note If you don't provide a `custom.css` file, or the file contains no custom CSS configuration, the Tasklist UI defaults to its original visual identity. ::: ## Common design tokens You can override the following commonly used design tokens to customize the Tasklist UI. Most tokens have separate values for the light and dark themes, so override them in both the light and dark selectors. The `--ring` and `--radius` tokens are only defined once, so an override in the light theme selector applies to both themes. | Design token | Description | | :---------------------------- | :--------------------------------------------------------------------------------- | | `--background` | Default background color of the Tasklist UI. | | `--foreground` | Primary color for body text. | | `--border` | Color for dividers and card outlines. | | `--popover` | Background color for floating surfaces, such as popovers, dropdowns, and tooltips. | | `--popover-foreground` | Text color on floating surfaces. | | `--input` | Border color for input fields. | | `--input-background` | Fill color for input fields. | | `--ring` | Color for the keyboard focus ring. Defaults to `--accent-foreground-subtle`. | | `--radius` | Base corner radius for components such as buttons, inputs, and cards. | | `--primary-action-default` | Fill color for primary buttons. | | `--primary-action-hover` | Hover state color for primary buttons. | | `--primary-action-active` | Active state color for primary buttons. | | `--primary-action-disabled` | Color for disabled primary buttons. | | `--primary-action-foreground` | Text and icon color on primary buttons. | | `--accent-action-default` | Fill color for selected or checked controls, such as checkboxes and switches. | | `--accent-action-hover` | Hover state color for accent controls. | | `--accent-action-foreground` | Text and icon color on accent controls. | | `--accent-foreground-subtle` | Accent color for text and icons. | | `--neutral-action-default` | Fill color for secondary actions. | | `--neutral-action-hover` | Hover state color for secondary actions. | | `--neutral-background-subtle` | Background color for elevated surfaces, such as containers, panels, and cards. | | `--neutral-background-strong` | Background color for stronger fills, such as selected rows and table headers. | | `--neutral-foreground-subtle` | Secondary color for less prominent text and icons. | | `--neutral-foreground-strong` | Color for emphasized text and icons. | | `--neutral-border-subtle` | Color for subtle borders. | | `--neutral-border-strong` | Color for stronger borders. | --- ## User task access restrictions were removed(Tasklist) User task access restrictions were removed in Camunda 8.10 together with Tasklist V1. For the release-level summary of this removal, see the [8.10 release announcement](/reference/announcements-release-notes/8100/8100-announcements.md#removal-of-legacy-apis-tasklist-v1-dependent-features-and-zeebe-process-test). Use [user task authorization](/components/tasklist/user-task-authorization.md) and [authorization-based access control](/components/concepts/access-control/authorizations.md) for current Tasklist access control. If you are migrating from an older installation, review [Tasklist API changes](/components/tasklist/api-versions.md) to understand which legacy V1 features were removed. --- ## Broker configuration A complete list of broker configuration properties and their defaults is available in the [Zeebe repo](https://github.com/camunda/camunda/blob/main/dist/src/main/config/defaults.yaml). ## Conventions Take the following conventions into consideration when working with the broker configuration. ### Byte sizes Buffers and data values referencing sizing must be specified as strings and follow the following format: "10U" where U (unit) must be replaced with KB = Kilobytes, MB = Megabytes or GB = Gigabytes. If unit is omitted then the default unit is simply bytes. Example: `sendBufferSize = "16MB"` (creates a buffer of 16 Megabytes) ### Time units Timeouts, intervals, and the likes, must be specified either in the standard ISO-8601 format used by java.time.Duration, or as strings with the following format: "VU", where: - V is a numerical value (e.g. 1, 5, 10, etc.) - U is the unit, one of: ms = Millis, s = Seconds, m = Minutes, or h = Hours ### Paths Relative paths are resolved relative to the installation directory of the broker. ## Configuration We provide tables with environment variables, application properties, a description, and corresponding default values in the following sections. For Camunda 8.9+, use the unified `camunda.*` properties and corresponding `CAMUNDA_*` environment variables where they are documented on this page. Configuration names are noted as the **header** of each documented section, while the **field** values represent properties to set the configuration. :::note The Zeebe Broker is a Spring Boot application. As such, [many common Spring Boot properties will work out of the box](https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html). Additionally, its REST server is a Spring Boot server (powered by Spring MVC), and can be configured using the standard `server.*` properties. Its management server (for example, where actuator endpoints live) is configured as a child application context, and is also a Spring MVC server. It can be configured via `management.server.*` properties. Finally, the REST server is only serving requests _if, and only if, the embedded gateway is enabled via_ `zeebe.broker.gateway.enable: true`. ::: ### server The `server` configuration allows you to configure the main REST server. Below are a few common ones, but you can find a more exhaustive list [in the official Spring documentation](https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#appendix.application-properties.server). | Field | Description | Example value | | ------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------- | | address | Sets the address the REST server binds to. This setting can also be overridden using the environment variable `SERVER_ADDRESS`. | 0.0.0.0 | | port | Sets the port the REST server binds to. This setting can also be overridden using the environment variable `SERVER_PORT`. | 8080 | #### server.compression | Field | Description | Example value | | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | enabled | If true, enables compression of responses for the Orchestration Cluster REST API. This setting can also be overridden using the environment variable `SERVER_COMPRESSION_ENABLED`. | false | #### server.ssl Allows you to configure the SSL security for the REST server. | Field | Description | Example value | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | enabled | If true, enables TLS for the Orchestration Cluster REST API. This setting can also be overridden using the environment variable `SERVER_SSL_ENABLED`. | false | | certificate | The path to a PEM encoded certificate. This setting can also be overridden using the environment variable `SERVER_SSL_CERTIFICATE`. | | | certificate-private-key | The path to a PKCS1 or PKCS8 private key for the configured certificate. This setting can also be overridden using the environment variable `SERVER_SSL_CERTIFICATEPRIVATEKEY`. | | #### YAML snippet ```yaml server: address: 0.0.0.0 port: 8080 compression: enabled: true ssl: enabled: true certificate: /path/to/my/cert.pem certificate-private-key: /path/to/my/private.key ``` ### server.servlet | Field | Description | Example value | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------- | | context-path | The context path prefix for all REST API requests. For example, if you configure `/zeebe`, then the client's REST address would be `http://localhost:8080/zeebe`. This setting can also be overridden using the environment variable `SERVER_SERVLET_CONTEXTPATH`. | `/` | #### YAML snippet ```yaml server.servlet: context-path: / ``` ### management.server The `management.server` configuration allows you to configure the management server. | Field | Description | Example value | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | address | Sets the address the management server binds to. This setting can also be overridden using the environment variable `MANAGEMENT_SERVER_ADDRESS`. | 0.0.0.0 | | port | Sets the port the management server binds to. This setting can also be overridden using the environment variable `MANAGEMENT_SERVER_PORT`. | 9600 | | base-path | The context path prefix for all management endpoints. For example, if you configure `/zeebe`, your actuator endpoints will be at `http://localhost:9600/zeebe/actuator/configprops`. This setting can also be overridden using the environment variable `MANAGEMENT_SERVER_BASEPATH`. | `/` | #### YAML snippet ```yaml management.server: address: 0.0.0.0 port: 9600 base-path: / ``` ### zeebe.broker.gateway Use `zeebe.broker.gateway.*` properties to configure the embedded gateway. For a standalone gateway, use `zeebe.gateway.*` properties. Where a specific embedded gateway property has a unified `camunda.*` equivalent, use the `camunda.*` property documented on this page instead. To configure the embedded gateway, see [Gateway configuration](./gateway.md). | Field | Description | Example value | | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | enable | Enables the embedded gateway on broker startup. Enabled by default. This setting can also be overridden using the environment variable `ZEEBE_BROKER_GATEWAY_ENABLE`. | true | #### YAML snippet ```yaml zeebe: broker: gateway: enable: true ``` ### camunda.cluster.network This section contains the network configuration. Particularly, it allows to configure the hosts and ports the broker should bind to. The broker exposes two sockets: 1. command: the socket which is used for gateway-to-broker communication 2. internal: the socket which is used for broker-to-broker communication | Field | Description | Example Value | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------- | | host | Controls the default host the broker should bind to. Can be overridden on a per-binding basis for the command API and internal API. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_NETWORK_HOST`. | 0.0.0.0 | | advertised-host | Controls the advertised host, which is the contact point advertised to other brokers. If omitted, it defaults to the host. This is particularly useful if your broker stands behind a proxy. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_NETWORK_ADVERTISEDHOST`. | 0.0.0.0 | | port-offset | If a port offset is set, it is added to all ports specified in the config or the default values. This is a shortcut to avoid specifying every port manually. The offset is added to the second-last position of the port, as Zeebe requires multiple ports. For example, a `port-offset` of `5` increments all ports by `50`, so `26500` becomes `26550`. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_NETWORK_PORTOFFSET`. | 0 | | max-message-size | Sets the maximum size of incoming and outgoing messages, such as commands and events. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_NETWORK_MAXMESSAGESIZE`. | 4MB | | socket-receive-buffer | Sets the size of the socket receive buffer for the broker. If omitted, it defaults to `1MB`. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_NETWORK_SOCKETRECEIVEBUFFER`. | 4MB | | socket-send-buffer | Sets the size of the socket send buffer for the broker. If omitted, it defaults to `1MB`. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_NETWORK_SOCKETSENDBUFFER`. | 4MB | | heartbeat-timeout | Sets the timeout used for cluster network heartbeats. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_NETWORK_HEARTBEATTIMEOUT`. | 10s | | heartbeat-interval | Sets the interval used for cluster network heartbeats. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_NETWORK_HEARTBEATINTERVAL`. | 250ms | #### YAML snippet ```yaml camunda: cluster: network: host: 0.0.0.0 advertised-host: 0.0.0.0 port-offset: 0 max-message-size: 4MB socket-receive-buffer: 4MB socket-send-buffer: 4MB heartbeat-timeout: 10s heartbeat-interval: 250ms ``` ### camunda.security.transport-layer-security.cluster | Field | Description | Example Value | | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | enabled | Enables TLS authentication between cluster nodes. This setting can also be overridden using the environment variable `CAMUNDA_SECURITY_TRANSPORTLAYERSECURITY_CLUSTER_ENABLED`. | false | | certificate-chain-path | Sets the path to the certificate chain file. This setting can also be overridden using the environment variable `CAMUNDA_SECURITY_TRANSPORTLAYERSECURITY_CLUSTER_CERTIFICATECHAINPATH`. | | | certificate-private-key-path | Sets the path to the private key file. This setting can also be overridden using the environment variable `CAMUNDA_SECURITY_TRANSPORTLAYERSECURITY_CLUSTER_CERTIFICATEPRIVATEKEYPATH`. | | | key-store.file-path | Configures the keystore file containing both the certificate chain and the private key. Currently only PKCS12 format is supported. This setting can also be overridden using the environment variable `CAMUNDA_SECURITY_TRANSPORTLAYERSECURITY_CLUSTER_KEYSTORE_FILEPATH`. | /path/key.p12 | | key-store.password | Sets the password for the keystore file. If not set, it is assumed there is no password. This setting can also be overridden using the environment variable `CAMUNDA_SECURITY_TRANSPORTLAYERSECURITY_CLUSTER_KEYSTORE_PASSWORD`. | changeme | #### YAML snippet ```yaml camunda: security: transport-layer-security: cluster: enabled: false certificate-chain-path: null certificate-private-key-path: null key-store: file-path: null password: null ``` ### camunda.cluster.network.command-api | Field | Description | Example Value | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------- | | host | Overrides the host used for gateway-to-broker communication. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_NETWORK_COMMANDAPI_HOST`. | 0.0.0.0 | | port | Sets the port used for gateway-to-broker communication. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_NETWORK_COMMANDAPI_PORT`. | 26501 | | advertisedHost | Controls the advertised host. If omitted, it defaults to the host. This is particularly useful if your broker stands behind a proxy. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_NETWORK_COMMANDAPI_ADVERTISEDHOST`. | 0.0.0.0 | | advertisedPort | Controls the advertised port. If omitted, it defaults to the port. This is particularly useful if your broker stands behind a proxy. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_NETWORK_COMMANDAPI_ADVERTISEDPORT`. | 25601 | #### YAML snippet ```yaml camunda: cluster: network: command-api: host: 0.0.0.0 port: 26501 advertisedHost: 0.0.0.0 advertisedPort: 25601 ``` ### camunda.cluster.network.internal-api | Field | Description | Example Value | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | host | Overrides the host used for internal broker-to-broker communication. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_NETWORK_INTERNALAPI_HOST`. | 0.0.0.0 | | port | Sets the port used for internal broker-to-broker communication. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_NETWORK_INTERNALAPI_PORT`. | 26502 | | advertisedHost | Controls the advertised host. If omitted, it defaults to the host. This is particularly useful if your broker stands behind a proxy. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_NETWORK_INTERNALAPI_ADVERTISEDHOST`. | 0.0.0.0 | | advertisedPort | Controls the advertised port. If omitted, it defaults to the port. This is particularly useful if your broker stands behind a proxy. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_NETWORK_INTERNALAPI_ADVERTISEDPORT`. | 25602 | #### YAML snippet ```yaml camunda: cluster: network: internal-api: host: 0.0.0.0 port: 26502 advertisedHost: 0.0.0.0 advertisedPort: 25602 ``` ### camunda.data.primary-storage | Field | Description | Example Value | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | directory | Specify the directory in which data is stored. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_DIRECTORY`. | data | | runtime-directory | Specify the directory in which runtime is stored. By default, runtime is stored in the data directory. If `runtime-directory` is configured, that directory is used instead. It contains a subdirectory for each partition to store its runtime. There is no need to store runtime on persistent storage. This configuration allows you to place runtime on another disk to optimize performance and disk usage. Note: If runtime is on a different disk than the data directory, files must be copied to the data directory while taking a snapshot. This can affect disk I/O or performance during snapshotting. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_RUNTIMEDIRECTORY`. | null | #### YAML snippet ```yaml camunda: data: primary-storage: directory: data runtime-directory: null ``` ### camunda.data.primary-storage.logstream | Field | Description | Example Value | | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | log-segment-size | The size of data log segment files. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_LOGSTREAM_LOGSEGMENTSIZE`. | 128MB | #### YAML snippet ```yaml camunda: data: primary-storage: logstream: log-segment-size: 128MB ``` ### camunda.data | Field | Description | Example Value | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | snapshot-period | How often snapshots of streams are taken (time unit). This setting can also be overridden using the environment variable `CAMUNDA_DATA_SNAPSHOTPERIOD`. | 5m | #### YAML snippet ```yaml camunda: data: snapshot-period: 5m ``` ### camunda.data.primary-storage.disk | Field | Description | Example Value | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------- | | monitoring-enabled | Configure disk monitoring to prevent getting into a non-recoverable state due to running out of disk space. When monitoring is enabled, the broker rejects commands and pauses replication when the required free space is not available. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_DISK_MONITORINGENABLED`. | true | | monitoring-interval | Sets the interval at which disk usage is monitored. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_DISK_MONITORINGINTERVAL`. | 1s | #### YAML snippet ```yaml camunda: data: primary-storage: disk: monitoring-enabled: true monitoring-interval: 1s ``` ### camunda.data.primary-storage.disk.free-space | Field | Description | Example Value | | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | processing | When the available free space is less than this value, the broker rejects all client commands and pauses processing. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_DISK_FREESPACE_PROCESSING`. | 2GB | | replication | When the available free space is less than this value, the broker stops receiving replicated events. This value must be less than `free-space.processing`. It is recommended to configure enough free space for at least one log segment and one snapshot. This is because a partition needs enough space to take a new snapshot so it can compact log segments and make disk space available again. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_DISK_FREESPACE_REPLICATION`. | 1GB | #### YAML snippet ```yaml camunda: data: primary-storage: disk: free-space: processing: 2GB replication: 1GB ``` ### camunda.data.primary-storage.backup Configure backup store. :::note Use the same configuration on all brokers of this cluster. ::: :::caution Backups created with one store are not available or restorable from another store. This is especially relevant if you were using GCS through the S3 compatibility mode and want to switch to the new built-in support for GCS now. Even when the underlying storage bucket is the same, backups from one are not compatible with the other. ::: | Field | Description | Example Value | | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | store | Set the backup store type. Supported values are [NONE, S3, GCS, AZURE, FILESYSTEM]. Default value is NONE. When NONE, no backup store is configured and no backup will be taken. Use S3 to use any S3 compatible storage, including, but not limited to, Amazon S3. Use GCS to use Google Cloud Storage. Use AZURE to use Azure Cloud Storage. Use FILESYSTEM to store backups directly via the filesystem to a particular folder. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_STORE`. | NONE | #### YAML snippet ```yaml camunda: data: primary-storage: backup: store: NONE ``` ### camunda.data.primary-storage.backup.s3 Configure the following if store is set to S3. :::note You can use any S3 compatible storage, including, but not limited to, Amazon S3. ::: :::note Backup encryption Zeebe does not support backup encryption natively, but it _can_ use encrypted S3 buckets. For AWS S3, this means [enabling default bucket encryption](https://docs.aws.amazon.com/AmazonS3/latest/userguide/default-bucket-encryption.html). Using default bucket encryption gives you control over the encryption keys and algorithms while being completely transparent with Zeebe. Combined with TLS between Zeebe and the S3 API, backups are fully encrypted in transit and at rest. Other S3 compatible services might have similar features that should work as well. Set `ssecKey` only if your bucket enforces [server-side encryption with customer-provided keys (SSE-C)](https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerSideEncryptionCustomerKeys.html), where every request must supply the encryption key. When set, Zeebe attaches the key to every backup object write and read. SSE-S3 and SSE-KMS are handled transparently through default bucket encryption and need no Zeebe configuration. - The value must be a **base64-encoded 32-byte (AES-256) key**, which is a 44-character base64 string. - The **same key** must be configured on the brokers and on the restore application. S3 does not store the key and cannot return objects without it, so a lost key means the backup cannot be restored. - Source the key from a secret rather than a plaintext value, and rotate it deliberately: objects written with one key can only be read back with that same key. ::: :::note Backup compression Backups can be large depending on your usage of Zeebe. To reduce S3 storage costs and upload times, you can enable backup compression. Zeebe compresses backup data immediately before uploading to S3 and buffers the compressed files in a temporary directory. Compression and buffering of compressed files can have a negative effect if Zeebe is heavily resource constrained. You can enable compression by specifying a compression algorithm to use. We recommend using [zstd](https://github.com/facebook/zstd) as it provides a good trade-off between compression ratio and resource usage. More compression algorithms are available; check [commons-compress](https://commons.apache.org/proper/commons-compress/) for a full list. ::: | Field | Description | Example Value | | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | bucket-name | Name of the bucket where the backup will be stored. The bucket must already be created. The bucket must not be shared with other Zeebe clusters. `bucket-name` must not be empty. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_S3_BUCKETNAME`. | | | endpoint | Configure URL endpoint for the store. If no endpoint is provided, it is determined based on the configured region. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_S3_ENDPOINT`. | | | region | Configure region. If no region is provided, it is determined as documented by your S3-compatible storage provider. If you use Amazon S3, it is determined as [documented](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/region-selection.html#automatically-determine-the-aws-region-from-the-environment). This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_S3_REGION`. | | | access-key | Configure access credentials. If either `access-key` or `secret-key` is not provided, it is determined as [documented](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/credentials.html#credentials-chain), not based on your S3-compatible storage provider. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_S3_ACCESSKEY`. | | | secret-key | This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_S3_SECRETKEY`. | | | api-call-timeout | Configure a maximum duration for all S3 client API calls. Lower values ensure that failed or slow API calls do not block other backups, but may increase the risk that backups cannot be stored if uploading parts of the backup takes longer than the configured timeout. Amazon S3 users can refer [here](https://github.com/aws/aws-sdk-java-v2/blob/master/docs/BestPractices.md#utilize-timeout-configurations). This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_S3_APICALLTIMEOUT`. | PT180S | | force-path-style-access | When enabled, forces the S3 client to use path-style access. By default, the client automatically chooses between path-style and virtual-hosted-style. Enable this only if the S3-compatible storage cannot support virtual-hosted-style. Refer to your S3-compatible storage provider or the [Amazon S3 docs](https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-bucket-intro.html) for more information. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_S3_FORCEPATHSTYLEACCESS`. | false | | compression | When set to an algorithm such as `zstd`, enables compression of backup contents. When not set or set to `none`, backup content is not compressed. Enabling compression reduces the required storage space for backups in S3 but also increases CPU and disk utilization while taking a backup. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_S3_COMPRESSION`. | none | | base-path | When set, all objects in the bucket use this prefix. Must be non-empty and not start or end with `/`. Useful for using the same bucket for multiple Zeebe clusters. In this case, `base-path` must be unique. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_S3_BASEPATH`. | | | max-concurrent-connections | Maximum number of connections allowed in a connection pool. This is used to restrict the maximum number of concurrent uploads to avoid connection timeouts when uploading backups with large or many files. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_S3_MAXCONCURRENTCONNECTIONS`. | | | connection-acquisition-timeout | Timeout for acquiring an already-established connection from a connection pool to a remote service. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_S3_CONNECTIONACQUISITIONTIMEOUT`. | | | support-legacy-md5 | Enables the AWS-provided `LegacyMd5Plugin` to extend backwards compatibility of the client. Useful when using an S3-compatible object storage as your backup store that is not up to date with the latest AWS SDK guidelines. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_S3_SUPPORTLEGACYMD5`. | false | | ssec-key | Base64-encoded 32-byte (AES-256) key enabling [server-side encryption with customer-provided keys (SSE-C)](https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerSideEncryptionCustomerKeys.html). When set, the key is attached to every backup object write and read; leave unset to disable SSE-C. The same key must be configured on the brokers and on the restore application, as S3 does not store it and cannot return objects without it. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_S3_SSECKEY`. | | #### YAML snippet ```yaml camunda: data: primary-storage: backup: store: S3 s3: bucket-name: null endpoint: null region: null access-key: null secret-key: null api-call-timeout: PT180S force-path-style-access: false compression: none base-path: null max-concurrent-connections: null connection-acquisition-timeout: null support-legacy-md5: false ssec-key: null ``` ### camunda.data.primary-storage.backup.gcs Configure the following if store is set to GCS. :::note The GCS backup strategy utilizes the [Google Cloud Storage REST API](https://cloud.google.com/storage/docs/request-endpoints). ::: :::note Backup encryption There are multiple [data encryption options](https://cloud.google.com/storage/docs/encryption), some of which are supported by Zeebe: - [Default server-side encryption](https://cloud.google.com/storage/docs/encryption/default-keys) is fully supported. This is enabled by default for all GCS buckets. - [Customer-managed encryption keys](https://cloud.google.com/storage/docs/encryption/customer-managed-keys) are supported if they are [set as the default key](https://cloud.google.com/storage/docs/encryption/using-customer-managed-keys#set-default-key) for your bucket. - [Customer-supplied encryption keys](https://cloud.google.com/storage/docs/encryption/customer-supplied-keys) are not supported. - [Client-side encryption keys](https://cloud.google.com/storage/docs/encryption/client-side-keys) are not supported. ::: | Field | Description | Example Value | | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | bucket-name | Name of the bucket where the backup will be stored. The bucket **must already exist**. The bucket must not be shared with other Zeebe clusters unless `base-path` is also set. Zeebe checks at startup that the specified bucket exists and can be accessed, and logs at WARN level if the bucket does not exist. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_GCS_BUCKETNAME`. | | | base-path | When set, all blobs in the bucket use this prefix. Useful for using the same bucket for multiple Zeebe clusters. In this case, `base-path` must be unique. Should not start or end with `/`. Must be non-empty and not consist only of `/` characters. See [Google documentation on naming](https://cloud.google.com/storage/docs/objects#naming). This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_GCS_BASEPATH`. | | | endpoint | Configure the endpoint for the GCS backup store. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_GCS_ENDPOINT`. | | | host | When set, this overrides the host that the GCS client connects to. By default, this is not set because the client can automatically discover the correct host to connect to. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_GCS_HOST`. | | | auth | Configures which authentication method is used for connecting to GCS. Can be either `auto` or `none`. Choosing `auto` means that the GCS client uses application default credentials, which automatically discover appropriate credentials from the runtime environment: [https://cloud.google.com/docs/authentication/application-default-credentials](https://cloud.google.com/docs/authentication/application-default-credentials). Choosing `none` means that no authentication is attempted, which is only applicable for testing with emulated GCS. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_GCS_AUTH`. | auto | #### YAML snippet ```yaml camunda: data: primary-storage: backup: store: GCS gcs: bucket-name: null base-path: null endpoint: null host: null auth: auto ``` ### camunda.data.primary-storage.backup.azure Configure the following if store is set to Azure. :::note Backup encryption - [Default server-side encryption](https://learn.microsoft.com/en-us/azure/storage/common/storage-service-encryption?toc=%2Fazure%2Fstorage%2Fblobs%2Ftoc.json&bc=%2Fazure%2Fstorage%2Fblobs%2Fbreadcrumb%2Ftoc.json#about-azure-storage-service-side-encryption) is fully supported, is enabled by default, and can't be switched off. - [Customer-managed encryption keys](https://learn.microsoft.com/en-us/azure/storage/common/customer-managed-keys-overview?toc=/azure/storage/blobs/toc.json&bc=/azure/storage/blobs/breadcrumb/toc.json) are supported if they are configured in the Azure store. - [Customer-provided encryption keys](https://learn.microsoft.com/en-us/azure/storage/blobs/encryption-customer-provided-keys) are not supported. - [Client-side encryption keys](https://learn.microsoft.com/en-us/azure/storage/blobs/client-side-encryption?tabs=dotnet) are not supported. ::: | Field | Description | Example Value | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | endpoint | Name of the endpoint where the backup is stored. Sets the blob service endpoint. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_AZURE_ENDPOINT`. | | | account-name | Account name used to connect to the service. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_AZURE_ACCOUNTNAME`. | | | account-key | Account key used to connect to the service. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_AZURE_ACCOUNTKEY`. | | | connection-string | The [connection string](https://learn.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string?toc=/azure/storage/blobs/toc.json&bc=/azure/storage/blobs/breadcrumb/toc.json) used to connect to the service. If this is defined, it overrides `account-name`, `account-key`, and `endpoint`. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_AZURE_CONNECTIONSTRING`. | | | base-path | Used to define the container name where the blobs are saved. This value must not be empty. When `base-path` is set, Zeebe creates and accesses objects only under this path. This can be any string that is a valid [container name](https://learn.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names), for example the name of your cluster. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_AZURE_BASEPATH`. | | | create-container | Defines if the container is created initially or if an existing one should be used. If set to `true` and the container already exists, it is not recreated. This configuration is `true` by default and generally should not be changed unless an authentication key is being used that does not have container-level permissions. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_AZURE_CREATECONTAINER`. | `true` | | sas-token.type | This setting defines the [SAS token](https://learn.microsoft.com/en-us/rest/api/storageservices/delegate-access-with-shared-access-signature) to use. These can be of user delegation, service, or account type. Note that user delegation and service SAS tokens do not support the creation of containers, therefore `create-container` is overridden to `false` if `sas-token.type` is configured as `delegation` or `service`. In this case, the user must make sure that the container already exists, or it will lead to a runtime error. The SAS token must be one of: `delegation`, `service`, or `account`. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_AZURE_SASTOKEN_TYPE`. | | | sas-token.value | Specifies the key value of the SAS token. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_AZURE_SASTOKEN_VALUE`. | | #### YAML snippet ```yaml camunda: data: primary-storage: backup: store: AZURE azure: endpoint: null account-name: null account-key: null connection-string: null base-path: null create-container: true sas-token: type: null value: null ``` ### camunda.data.primary-storage.backup.filesystem To store your backups in the local filesystem, choose the `FILESYSTEM` backup store and specify where to store the backups locally. :::caution Since the durability of the backups largely depends on the target file system and underlying storage, it is recommended to use known durable solutions in production, such as S3, GCS, or Azure. To ensure that this can be used properly in production, you must use a POSIX-compliant file system and, at a minimum, replicated disks (for example, RAID-configured disks). ::: :::note Backup encryption Zeebe does not support backup encryption natively, but it _can_ use filesystem-based encryption. This is then a feature of the filesystem and not Zeebe itself. ::: | Field | Description | Example Value | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | | base-path | The base path is used to define the parent directory of all created backups and backup manifest files. **This directory must exist and be writable by the Zeebe broker**. This setting can also be overridden using the environment variable `CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_FILESYSTEM_BASEPATH`. | /mnt/backups/zeebe | #### YAML snippet ```yaml camunda: data: primary-storage: backup: store: FILESYSTEM filesystem: base-path: /mnt/backups/zeebe ``` ### camunda.cluster This section contains cluster-related configuration used to set up a Zeebe cluster. | Field | Description | Example Value | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------ | | node-id | Specifies the unique ID of this broker node in a cluster. The ID should be between 0 and the number of nodes in the cluster (exclusive). This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_NODEID`. | 0 | | partition-count | Controls the number of partitions that should exist in the cluster. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_PARTITIONCOUNT`. | 1 | | replication-factor | Controls the replication factor, which defines the number of replicas per partition. The replication factor cannot be greater than the number of nodes in the cluster. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_REPLICATIONFACTOR`. | 1 | | size | Specifies the Zeebe cluster size. This value is used to determine which broker is responsible for which partition. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_SIZE`. | 1 | | initial-contact-points | Specifies a list of known other nodes to connect to on startup. The contact points of the internal network configuration must be specified. The format is `[HOST:PORT]`. To help the cluster survive network partitions, all nodes must be specified as initial contact points. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_INITIALCONTACTPOINTS` with a comma-separated list of contact points. Default is an empty list. | [ 192.168.1.22:26502, 192.168.1.32:26502 ] | | id | Unique identifier for the cluster. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_ID`. | zeebe-cluster-123 | | name | Specifies a name for the cluster. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_NAME`. | zeebe-cluster | | compression-algorithm | Configure the compression algorithm for all messages sent between the gateway and brokers. Available options are `NONE`, `GZIP`, and `SNAPPY`. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_COMPRESSIONALGORITHM`. | NONE | #### YAML snippet ```yaml camunda: cluster: node-id: 0 partition-count: 1 replication-factor: 1 size: 1 initial-contact-points: [] id: zeebe-cluster-123 name: zeebe-cluster compression-algorithm: NONE ``` ### camunda.cluster.raft This section contains properties required to configure Raft. | Field | Description | Example Value | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | priority-election-enabled | When this flag is enabled, the leader election algorithm attempts to elect leaders based on a predefined priority. As a result, it tries to distribute leaders uniformly across brokers. Note that this is only a best-effort strategy and does not guarantee a strictly uniform distribution. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_RAFT_PRIORITYELECTIONENABLED`. | true | | flush-enabled | If `false`, explicit flushing of the Raft log is disabled, and flushing only occurs right before a snapshot is taken. You should disable explicit flushing only if you are willing to accept potential data loss in exchange for performance. Before disabling it, try `flush-delay` first, which provides a trade-off between safety and performance. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_RAFT_FLUSHENABLED`. | true | | flush-delay | If the delay is greater than `0`, flush requests are delayed by at least the given period. Start with the smallest delay that achieves your performance goals. Values above `30s` are unlikely to be useful because this is typically the default Linux OS flush interval. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_RAFT_FLUSHDELAY`. | 0s | | heartbeat-interval | The leader sends a heartbeat to a follower every heartbeat interval. Note: This is an advanced setting. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_RAFT_HEARTBEATINTERVAL`. | 250ms | | election-timeout | If a follower does not receive a heartbeat from the leader within an election timeout, it can start a new leader election. `election-timeout` should be greater than `heartbeat-interval`. Larger values delay leader-failure detection; smaller values can increase false positives and unnecessary leader changes. If network latency between nodes is high, use a higher election timeout. Note: This is an advanced setting. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_RAFT_ELECTIONTIMEOUT`. | 2500ms | #### camunda.cluster.raft.rebalance This section contains properties that control how partition rebalancing operations execute. Operators can override these defaults per rebalance request; see [rebalancing](/self-managed/components/orchestration-cluster/zeebe/operations/rebalancing.md). | Field | Description | Example Value | | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | replication-lag-threshold | The maximum replication lag, in bytes, a desired leader may have for the current leader to attempt a transfer. Above this threshold the partition is skipped with `LAG_TOO_HIGH`. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_RAFT_REBALANCE_REPLICATIONLAGTHRESHOLD`. | 8MB | | replication-timeout | How long the current leader waits (paused, declining writes) for the desired leader to finish replicating before the transfer is canceled with `REPLICATION_TIMED_OUT`. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_RAFT_REBALANCE_REPLICATIONTIMEOUT`. | 10s | | max-transfer-attempts | The maximum number of `TimeoutNow` requests the current leader sends (including the initial request) before reporting `TIMEOUT_NOW_EXHAUSTED`. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_RAFT_REBALANCE_MAXTRANSFERATTEMPTS`. | 3 | | leader-wait-timeout | How long the coordinator waits for a partition with no contactable leader to get one before giving up with `NO_LEADER`. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_RAFT_REBALANCE_LEADERWAITTIMEOUT`. | 1m | #### YAML snippet ```yaml camunda: cluster: raft: priority-election-enabled: true flush-enabled: true flush-delay: 0s heartbeat-interval: 250ms election-timeout: 2500ms rebalance: replication-lag-threshold: 8MB replication-timeout: 10s max-transfer-attempts: 3 leader-wait-timeout: 1m ``` ### camunda.cluster.membership Configure parameters for the SWIM protocol used to propagate cluster membership information among brokers and gateways. | Field | Example Value | Description | | ------------------ | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | broadcast-updates | false | Configure whether to broadcast member updates to all members. If set to `false`, updates are gossiped among members. If set to `true`, network traffic may increase, but membership changes are detected faster. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_MEMBERSHIP_BROADCASTUPDATES`. | | broadcast-disputes | true | Configure whether to broadcast disputes to all members. If set to `true`, network traffic may increase, but membership changes are detected faster. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_MEMBERSHIP_BROADCASTDISPUTES`. | | notify-suspect | false | Configure whether to notify a suspect node on state changes. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_MEMBERSHIP_NOTIFYSUSPECT`. | | gossip-interval | 250ms | Sets the interval at which membership updates are sent to a random member. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_MEMBERSHIP_GOSSIPINTERVAL`. | | gossip-fanout | 2 | Sets the number of members to which membership updates are sent at each gossip interval. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_MEMBERSHIP_GOSSIPFANOUT`. | | probe-interval | 1s | Sets the interval at which to probe a random member. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_MEMBERSHIP_PROBEINTERVAL`. | | probe-timeout | 100ms | Sets the timeout for a probe response. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_MEMBERSHIP_PROBETIMEOUT`. | | suspect-probes | 3 | Sets the number of failed probes before declaring a member suspect. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_MEMBERSHIP_SUSPECTPROBES`. | | failure-timeout | 10s | Sets the timeout before a suspect member is declared dead. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_MEMBERSHIP_FAILURETIMEOUT`. | | sync-interval | 10s | Sets the interval at which this member synchronizes its membership information with a random member. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_MEMBERSHIP_SYNCINTERVAL`. | #### YAML snippet ```yaml camunda: cluster: membership: broadcast-updates: false broadcast-disputes: true notify-suspect: false gossip-interval: 250ms gossip-fanout: 2 probe-interval: 1s probe-timeout: 100ms suspect-probes: 3 failure-timeout: 10s sync-interval: 10s ``` ### camunda.cluster.metadata Configure the parameters used to propagate the dynamic cluster configuration across brokers and gateways. | Field | Description | Example Value | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------- | | sync-delay | Sets the interval between two synchronization requests to other members of the cluster. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_METADATA_SYNCDELAY`. | 10s | | sync-request-timeout | Sets the timeout for the synchronization requests. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_METADATA_SYNCREQUESTTIMEOUT`. | 2s | | gossip-fanout | Sets the number of cluster members the configuration is gossiped to. This setting can also be overridden using the environment variable `CAMUNDA_CLUSTER_METADATA_GOSSIPFANOUT`. | 2 | #### YAML snippet ```yaml camunda: cluster: metadata: sync-delay: 10s sync-request-timeout: 2s gossip-fanout: 2 ``` ### camunda.system | Field | Description | Example Value | | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | cpu-thread-count | Controls the number of non-blocking CPU threads to be used. WARNING: You should never specify a value that is larger than the number of physical cores available. Good practice is to leave 1-2 cores for io threads and the operating system. For example, when running Zeebe on a machine with 4 cores, a good value would be 2. This setting can also be overridden using the environment variable `CAMUNDA_SYSTEM_CPUTHREADCOUNT`. | 2 | | io-thread-count | Controls the number of io threads to be used. These threads are used for workloads that write data to disk. While writing, these threads are blocked, which means that they yield the CPU. This setting can also be overridden using the environment variable `CAMUNDA_SYSTEM_IOTHREADCOUNT`. | 2 | #### YAML snippet ```yaml camunda: system: cpu-thread-count: 2 io-thread-count: 2 ``` ### camunda.processing.flow-control.request Configure flow control for user requests. | Field | Description | Example Value | | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | enabled | Set this to enable flow control for user requests. When enabled, the broker rejects user requests when the number of inflight requests is greater than the limit. The value of the limit is determined by the configured algorithm. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_REQUEST_ENABLED`. | true | | algorithm | Configures which algorithm to use for flow control. It should be one of `vegas`, `aimd`, `fixed`, `gradient`, or `gradient2`. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_REQUEST_ALGORITHM`. | aimd | | windowed | If enabled, uses average latencies over a window as the current latency to update the limit. It is not recommended to enable this when the algorithm is `aimd`. This setting does not apply to the `fixed` algorithm. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_REQUEST_WINDOWED`. | false | #### YAML snippet ```yaml camunda: processing: flow-control: request: enabled: true algorithm: aimd windowed: false ``` ### camunda.processing.flow-control.request.aimd | Field | Description | Example Value | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------- | | request-timeout | The limit is reduced if the observed latency is greater than `request-timeout`. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_REQUEST_AIMD_REQUESTTIMEOUT`. | 200ms | | initial-limit | The initial limit to use when the broker starts. The limit is reset to this value when the broker restarts. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_REQUEST_AIMD_INITIALLIMIT`. | 100 | | min-limit | The minimum limit. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_REQUEST_AIMD_MINLIMIT`. | 1 | | max-limit | The maximum limit. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_REQUEST_AIMD_MAXLIMIT`. | 1000 | | backoff-ratio | A double value between `0` and `1` that determines the factor by which the limit is decreased. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_REQUEST_AIMD_BACKOFFRATIO`. | 0.9 | #### YAML snippet ```yaml camunda: processing: flow-control: request: algorithm: aimd aimd: request-timeout: 200ms initial-limit: 100 min-limit: 1 max-limit: 1000 backoff-ratio: 0.9 ``` ### camunda.processing.flow-control.request.fixed | Field | Description | Example Value | | ----- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | limit | Set a fixed limit. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_REQUEST_FIXED_LIMIT`. | 20 | #### YAML snippet ```yaml camunda: processing: flow-control: request: algorithm: fixed fixed: limit: 20 ``` ### camunda.processing.flow-control.request.vegas | Field | Description | Example Value | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | initial-limit | The initial limit to use when the broker starts. The limit is reset to this value when the broker restarts. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_REQUEST_VEGAS_INITIALLIMIT`. | 20 | | alpha | The limit is increased if the queue size is less than this value. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_REQUEST_VEGAS_ALPHA`. | 3 | | beta | The limit is decreased if the queue size is greater than this value. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_REQUEST_VEGAS_BETA`. | 6 | #### YAML snippet ```yaml camunda: processing: flow-control: request: algorithm: vegas vegas: initial-limit: 20 alpha: 3 beta: 6 ``` ### camunda.processing.flow-control.request.gradient | Field | Description | Example Value | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | min-limit | The minimum limit. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_REQUEST_GRADIENT_MINLIMIT`. | 10 | | initial-limit | The initial limit to use when the broker starts. The limit is reset to this value when the broker restarts. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_REQUEST_GRADIENT_INITIALLIMIT`. | 20 | | rtt-tolerance | Tolerance for changes from minimum latency. A value `>= 1.0` indicating how much change from minimum latency is acceptable before reducing the limit. For example, a value of `2.0` means that a 2x increase in latency is acceptable. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_REQUEST_GRADIENT_RTTTOLERANCE`. | 2.0 | #### YAML snippet ```yaml camunda: processing: flow-control: request: algorithm: gradient gradient: min-limit: 10 initial-limit: 20 rtt-tolerance: 2.0 ``` ### camunda.processing.flow-control.request.gradient2 | Field | Description | Example Value | | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | min-limit | The minimum limit. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_REQUEST_GRADIENT2_MINLIMIT`. | 10 | | initial-limit | The initial limit to use when the broker starts. The limit is reset to this value when the broker restarts. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_REQUEST_GRADIENT2_INITIALLIMIT`. | 20 | | rtt-tolerance | Tolerance for changes from minimum latency. A value `>= 1.0` indicating how much change from minimum latency is acceptable before reducing the limit. For example, a value of `2.0` means that a 2x increase in latency is acceptable. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_REQUEST_GRADIENT2_RTTTOLERANCE`. | 2.0 | | long-window | Length of the window, in number of samples, used to calculate the exponentially smoothed average latency. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_REQUEST_GRADIENT2_LONGWINDOW`. | 600 | #### YAML snippet ```yaml camunda: processing: flow-control: request: algorithm: gradient2 gradient2: min-limit: 10 initial-limit: 20 rtt-tolerance: 2.0 long-window: 600 ``` ### camunda.processing.flow-control.write | Field | Description | Example Value | | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | enabled | Set this to enable or disable flow control for all writes. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_WRITE_ENABLED`. | false | | ramp-up | Time period during which the write limit gradually increases to the configured limit. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_WRITE_RAMPUP`. | 10s | | limit | The maximum number of records that can be written per second. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_WRITE_LIMIT`. | 1000 | #### YAML snippet ```yaml camunda: processing: flow-control: write: enabled: false ramp-up: 10s limit: 1000 ``` ### camunda.processing.flow-control.write.throttle | Field | Description | Example Value | | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | enabled | Set this to enable or disable write throttling based on the exporting backlog. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_WRITE_THROTTLE_ENABLED`. | false | | acceptable-backlog | The number of records that can be in the exporting backlog. The write rate is throttled so that the backlog stabilizes around this value when exporting is a bottleneck. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_WRITE_THROTTLE_ACCEPTABLEBACKLOG`. | 100000 | | minimum-limit | The minimum write limit that is guaranteed even when throttling. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_WRITE_THROTTLE_MINIMUMLIMIT`. | 100 | | resolution | The frequency at which throttling is updated. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_FLOWCONTROL_WRITE_THROTTLE_RESOLUTION`. | 15s | #### YAML snippet ```yaml camunda: processing: flow-control: write: throttle: enabled: false acceptable-backlog: 100000 minimum-limit: 100 resolution: 15s ``` ### camunda.data.exporters.elasticsearch An example configuration for the Elasticsearch exporter can be found [here](../exporters/elasticsearch-exporter.md#example). ### camunda.data.exporters.opensearch An example configuration for the OpenSearch exporter can be found [here](../exporters/opensearch-exporter.md#example). ### Camunda exporter An example configuration for the Camunda exporter can be found [here](../exporters/camunda-exporter.md#example). ### camunda.processing | Field | Description | Example Value | | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | max-commands-in-batch | Sets the maximum number of commands processed within one batch. The processor continues until no more follow-up commands are created by the initial command or the configured limit is reached. By default, up to `100` commands are processed in one batch. Set to `1` to disable batch processing. Must be a positive integer. Note that the resulting batch size can contain more entries than this limit because it includes follow-up events. When the resulting batch size is too large, processing is rolled back and retried with a smaller maximum batch size. Lowering the command limit can reduce the frequency of rollback and retry. This setting can also be overridden using the environment variable `CAMUNDA_PROCESSING_MAXCOMMANDSINBATCH`. | 100 | #### YAML snippet ```yaml camunda: processing: max-commands-in-batch: 100 ``` ### Experimental configuration See the experimental section of the [defaults.yaml](https://github.com/camunda/camunda/blob/main/dist/src/main/config/defaults.yaml). Be aware that all configurations which are part of the experimental section are subject to change and can be dropped at any time. #### zeebe.broker.experimental.engine.caches | Field | Description | Example value | | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | groupNameCacheCapacity | Maximum number of group names held in the in-memory name-to-ID cache used during candidate group resolution. This setting can also be overridden using the environment variable `ZEEBE_BROKER_EXPERIMENTAL_ENGINE_CACHES_GROUPNAMECACHECAPACITY`. | 1000 | | candidateGroupNameResolution | If true, the Zeebe engine attempts to resolve user task candidate group names to group IDs at task creation. Set to false to disable this resolution and pass candidate group values through unchanged. This setting can also be overridden using the environment variable `ZEEBE_BROKER_EXPERIMENTAL_ENGINE_CACHES_CANDIDATEGROUPNAMERESOLUTION`. | true | ### Multitenancy configuration For embedded gateway configuration, use the current gateway configuration properties documented in the gateway configuration guide. This page documents broker configuration and should use the unified `camunda.*` properties where available. #### zeebe.broker.gateway.multitenancy :::note This section describes configuration for **logical tenants**. For strong physical isolation of separate teams or organizations within a single cluster, see [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md). ::: Multi-tenancy in Zeebe can be configured with the following configuration properties. Multi-tenancy is disabled by default. Read more [in the multi-tenancy documentation](/components/concepts/multi-tenancy.md). :::note For now, multi-tenancy is only supported in combination with Identity. To use multi-tenancy, you must set [`authentication.mode`](#zeebebrokergatewaysecurityauthentication) to `'identity'` and specify the `camunda.identity.baseUrl` property or the [corresponding Camunda Identity environment variable](/self-managed/components/management-identity/miscellaneous/configuration-variables.md#core-configuration) as well. ::: :::note If you are using a standalone gateway, refer to the [gateway configuration guide](./gateway.md). ::: | Field | Description | Example value | | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------- | | enabled | Enable multitenancy in the embedded gateway. This setting can also be overridden using the environment variable `ZEEBE_BROKER_GATEWAY_MULTITENANCY_ENABLED`. | false | ##### YAML snippet ```yaml broker: gateway: multitenancy: enabled: false ``` #### zeebe.broker.gateway.security.authentication | Field | Description | Example value | | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | mode | Controls which authentication mode is active; supported modes are `none` and `identity`. If `identity` is set, authentication will be done using [camunda-identity](/self-managed/components/management-identity/overview.md), which needs to be configured in the corresponding subsection. This setting can also be overridden using the environment variable `ZEEBE_BROKER_GATEWAY_SECURITY_AUTHENTICATION_MODE`. | none | ##### YAML snippet ```yaml security: authentication: mode: none ``` #### zeebe.broker.gateway.security.authentication.identity :::note The Zeebe configuration properties for Camunda Identity are deprecated as of version `8.4.0`. Use the dedicated Camunda Identity properties or the [corresponding environment variables](/self-managed/components/management-identity/miscellaneous/configuration-variables.md#core-configuration). ::: | Field | Description | Example value | | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | | issuerBackendUrl | The URL to the auth provider backend, used to validate tokens. This setting can also be overridden using the environment variable `ZEEBE_BROKER_GATEWAY_SECURITY_AUTHENTICATION_IDENTITY_ISSUERBACKENDURL`. | http://keycloak:18080/auth/realms/camunda-platform | | audience | The required audience of the auth token. This setting can also be overridden using the environment variable `ZEEBE_BROKER_GATEWAY_SECURITY_AUTHENTICATION_IDENTITY_AUDIENCE`. | zeebe-api | | baseUrl | The URL to the Identity instance. This setting can also be overridden using the environment variable `ZEEBE_BROKER_GATEWAY_SECURITY_AUTHENTICATION_IDENTITY_BASEURL`. | http://identity:8084 | ##### YAML snippet ```yaml security: authentication: mode: identity identity: issuerBackendUrl: http://keycloak:18080/auth/realms/camunda-platform audience: zeebe-api type: keycloak ``` ### Camunda Hub ping configuration This feature enables components like the Zeebe Broker, Tasklist, Operate, and Zeebe Gateway to ping Camunda Hub with license information. For this feature to work, you must enable [dynamic cluster management](/self-managed/components/hub/configuration/properties.md#dynamic-cluster-management), which exposes the `POST /clusters` API endpoint. #### camunda.hub.ping | Field | Description | Example value | | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | | `enabled` | Enables or disables the ping to Hub feature. Disabled by default. This setting can also be overridden using the environment variable `CAMUNDA_HUB_PING_ENABLED`. | `true` | | `endpoint` | The `POST /clusters` API endpoint where pings are sent. This setting can also be overridden using the environment variable `CAMUNDA_HUB_PING_ENDPOINT`. | `https://hub.endpoint.com/api/v1/clusters` | | `clusterName` | Cluster name sent with telemetry. This setting can also be overridden using the environment variable `CAMUNDA_HUB_PING_CLUSTERNAME`. | `test_cluster_name` | | `pingPeriod` | Frequency of pings (for example, `1s`, `1h`, `1d`). This setting can also be overridden using the environment variable `CAMUNDA_HUB_PING_PINGPERIOD`. | `1h` | | `properties` | Additional properties to include in the ping payload (as key-value pairs). This setting can also be overridden using the environment variable `CAMUNDA_HUB_PING_PROPERTIES`. | `testProperty: 123` | | `retry.maxRetries` | Maximum number of retry attempts after a failed ping. Uses exponential backoff. This setting can also be overridden using the environment variable `CAMUNDA_HUB_PING_RETRY_MAXRETRIES`. | `1` | | `retry.minRetryDelay` | Minimum delay between retries. This setting can also be overridden using the environment variable `CAMUNDA_HUB_PING_RETRY_MINRETRYDELAY`. | `1s` | | `retry.maxRetryDelay` | Maximum delay between retries. This setting can also be overridden using the environment variable `CAMUNDA_HUB_PING_RETRY_MAXRETRYDELAY`. | `10s` | | `retry.retryDelayMultiplier` | Multiplier applied to delay between retries. This setting can also be overridden using the environment variable `CAMUNDA_HUB_PING_RETRY_RETRYDELAYMULTIPLIER`. | `2` | #### camunda.hub.ping.credentials The `POST /clusters` endpoint requires an OAuth 2.0 M2M (machine-to-machine) token. Configure credentials so the cluster can authenticate with Camunda Hub when sending pings. The client must be granted the `create:*` and `update:*` permissions on the `web-modeler-public-api` application in [Management Identity](/self-managed/components/management-identity/access-management/access-management-overview.md#permissions). | Field | Description | Example value | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | `tokenEndpoint` | The OAuth 2.0 token endpoint used to request an access token. This setting can also be overridden using the environment variable `CAMUNDA_HUB_PING_CREDENTIALS_TOKENENDPOINT`. | `https://keycloak.example.com/realms/camunda/protocol/openid-connect/token` | | `clientId` | The client ID of the M2M application registered in your identity provider. This setting can also be overridden using the environment variable `CAMUNDA_HUB_PING_CREDENTIALS_CLIENTID`. | `zeebe-ping-client` | | `clientSecret` | The client secret for the M2M application. This setting can also be overridden using the environment variable `CAMUNDA_HUB_PING_CREDENTIALS_CLIENTSECRET`. | `***` | | `tokenRequestParameters` | Additional parameters sent in the token request, for example `audience` or `scope`, as key-value pairs. Some identity providers require them. You can't override `grant_type`, `client_id`, or `client_secret`. This setting can also be overridden using environment variables such as `CAMUNDA_HUB_PING_CREDENTIALS_TOKENREQUESTPARAMETERS_AUDIENCE`. | `audience: https://hub.example.com/api` | ##### YAML snippet ```yaml camunda: hub: ping: enabled: true endpoint: https://hub.endpoint.com/api/v1/clusters clusterName: test_cluster_name pingPeriod: 1h properties: testProperty: 123 retry: maxRetries: 1 minRetryDelay: 1s maxRetryDelay: 10s retryDelayMultiplier: 2 credentials: tokenEndpoint: https://keycloak.example.com/realms/camunda/protocol/openid-connect/token clientId: zeebe-ping-client clientSecret: "***" tokenRequestParameters: audience: https://hub.example.com/api ``` ### Continuous backups configuration Configuration options for primary storage continuous backups. #### camunda.data.primary-storage.backup | Field | Description | Example value | | :--------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------- | | `continuous` | Enables or disables the continuous backups feature. | `true` | | `required` | Forces the continuous backups feature to be properly configured during broker startup. | `true` | | `schedule` | The interval at which a primary storage backup is taken. Can be a CRON expression, an ISO-8601 duration, or `none`. | `PT12H` | | `checkpoint-interval` | The interval at which checkpoints are ingested into the log stream. Uses an ISO-8601 duration. | `PT5M` | | `offset` | Optional offset for the generated backup identifiers. | `20260215115715` | | `retention.window` | The active window of backups available for restore in the configured backup store. Uses an ISO-8601 duration. | `P1W` | | `retention.cleanup-schedule` | The interval at which the retention mechanism checks for backups outside the active window. Can be a CRON expression, an ISO-8601 duration, or `none`. | `PT1H` | ```yaml camunda: data: primary-storage: backup: required: "false" continuous: "true" schedule: "PT10M" checkpoint-interval: "PT1M" offset: 20260215115715 retention: window: "P1W" cleanup-schedule: "PT1H" ``` ```bash CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_REQUIRED=false CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_CONTINUOUS=true CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_SCHEDULE="PT10M" CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_CHECKPOINTINTERVAL="PT1M" CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_OFFSET=20260215115715 CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_RETENTION_WINDOW="P1W" CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_RETENTION_CLEANUPSCHEDULE="PT1H" ``` --- ## Configuration(6) Zeebe can be configured through the following: - Configuration files - Environment variables - A mix of both If both configuration files and environment variables are present, environment variables overwrite settings in configuration files. To make small changes to the configuration, we recommend using environment variables. To make big changes to the configuration, we recommend using a configuration file. The configuration is applied during startup of Zeebe. It is not possible to change the configuration at runtime. ## Default configuration The default configuration is located in `config/application.yaml`. This configuration contains the most common configuration settings for a standalone broker. It also lists the corresponding environment variable for each setting. :::note The default configuration is not suitable for a standalone gateway node. To run a standalone gateway node, take a look at [the gateway configuration](gateway.md) or `/config/gateway.default.yaml`. ::: ## Configuration file templates We provide templates that contain all possible configuration settings, along with explanations for each setting, though you may find it easier to search through our [broker](broker.md) and [gateway](gateway.md) configuration documentation to adjust the templates: - [`config/defaults.yaml` Standalone Broker (with embedded gateway)](https://github.com/camunda/camunda/blob/main/dist/src/main/config/defaults.yaml) - Complete configuration template for a standalone broker with embedded gateway. Use this as the basis for a single broker deployment for test or development. - [`config/gateway.default.yaml`](https://github.com/camunda/camunda/blob/main/zeebe/gateway/src/test/resources/configuration/gateway.default.yaml) - Complete configuration template for a standalone gateway. :::note These templates also include the corresponding environment variables to use for every setting. ::: ## Editing the configuration You can either start from scratch or start from the configuration templates listed above. If you use a configuration template and want to uncomment certain lines, make sure to also uncomment their parent elements: ```yaml Valid Configuration zeebe: gateway: network: # host: 0.0.0.0 port: 26500 Invalid configuration # zeebe: # gateway: # network: # host: 0.0.0.0 port: 26500 ``` Uncommenting individual lines is a bit finicky, because YAML is sensitive to indentation. The best way to do it is to position the cursor before the `#` character and delete two characters (the dash and the space). Doing this will consistently give you a valid YAML file. When it comes to editing individual settings, two data types are worth mentioning: - Data size (e.g. `logSegmentSize`) - Human-friendly format: `500MB` (or `KB, GB`) - Machine-friendly format: size in bytes as long - Timeouts/intervals (e.g. `requestTimeout`) - Human-friendly format: `15s` (or `m, h`) - Machine-friendly format: either duration in milliseconds as long, or [ISO-8601 duration](https://en.wikipedia.org/wiki/ISO_8601#Durations) format (e.g. `PT15S`) ## Passing configuration files to Zeebe Rename the configuration file to `application.yaml` and place it in the following location: ```shell script ./config/application.yaml ``` ### Other ways to specify the configuration file Zeebe uses Spring Boot for its configuration parsing. All other ways to [configure a Spring Boot application](https://docs.spring.io/spring-boot/reference/features/external-config.html) should also work. In particular, you can use: - `SPRING_CONFIG_ADDITIONAL_LOCATION` to specify an additional configuration file. - `SPRING_APPLICATION_JSON` to specify settings in JSON format. Details can be found in the Spring documentation. :::note We recommend not to use `SPRING_CONFIG_LOCATION` as this will replace all existing configuration defaults. When used inappropriately, some features will be disabled or will not be configured properly. ::: If you specify `SPRING_CONFIG_LOCATION`, specify it like this: ```shell script export SPRING_CONFIG_LOCATION='classpath:/,file:./[path to config file]' ``` This will ensure the defaults defined in the classpath resources will be used (unless explicitly overwritten by the configuration file you provide). If you omit the defaults defined in the classpath, some features may be disabled or will not be configured properly. ## Verifying configuration Start Zeebe to verify the configuration was applied. If the configuration could be read, Zeebe will expose it via the monitoring port at [http://localhost:9600/actuator/configprops/zeebe](http://localhost:9600/actuator/configprops/zeebe). This will show you both the resolved configuration and its inputs. :::note Zeebe uses the Spring Boot [configprops](https://docs.spring.io/spring-boot/docs/current/actuator-api/htmlsingle/#configprops) actuator for this, so any documentation listed there applies as well. ::: In some cases of invalid configuration, Zeebe will fail to start and log a warning that explains which configuration setting could not be read. ``` 17:17:38.796 [] [main] ERROR org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter - *************************** APPLICATION FAILED TO START *************************** Description: Failed to bind properties under 'zeebe.broker.network.port-offset' to int: Property: zeebe.broker.network.port-offset Value: false Origin: System Environment Property "ZEEBE_BROKER_NETWORK_PORTOFFSET" Reason: failed to convert java.lang.String to int Action: Update your application's configuration ``` ## Licensing See the [core settings documentation](/self-managed/components/orchestration-cluster/core-settings/configuration/licensing.md). ## Logging Zeebe uses Log4j2 framework for logging. In the distribution and the Docker image, find the default log configuration file in `config/log4j2.xml`. For details on logging, refer to the Orchestration Cluster [core settings and features](/self-managed/components/orchestration-cluster/core-settings/configuration/logging.md). ### Google Stackdriver (JSON) logging To enable Google Stackdriver compatible JSON logging, set the environment variable `ZEEBE_LOG_APPENDER=Stackdriver` before starting Zeebe. ### Change log level dynamically Zeebe brokers expose a [Spring Boot Actuators web endpoint](https://docs.spring.io/spring-boot/docs/current/actuator-api/html/#loggers) for configuring loggers dynamically. To change the log level of a logger, make a `POST` request to the `/actuator/loggers/{logger.name}` endpoint as shown in the example below. Change `io.camunda.zeebe` to the required logger name and `debug` to required log level. ``` curl 'http://localhost:9600/actuator/loggers/io.camunda.zeebe' -i -X POST -H 'Content-Type: application/json' -d '{"configuredLevel":"debug"}' ``` ## Health probes Health probes are set to sensible defaults which cover common use cases. For specific use cases, it might be necessary to customize health probes: - [Gateway health probes](gateway-health-probes.md) ## Experimental configuration options You may have already noticed a special section of Zeebe's configuration templates titled `experimental`. This section refers to settings which are potentially not backwards compatible. In other words, any configuration setting found there may or may not be dropped in any minor version. These settings are there primarily for incubating features and/or very advanced settings for which the team has not found a good general default configuration. Once one is found, or the incubating feature is promoted, the setting(s) may be moved into a different section. Only at that point do they fall under the same backwards compatibility guarantees as the rest of the project. We may choose to drop support for specific experimental configurations in any minor version update. Most users should not have to change anything in this section for a good experience. However, if you have a unique set up, or simply wish to try out new experimental features, it can be worth investigating these (ideally with the guidance of the Zeebe community). --- ## Environment variables ## Environment variables for configuration As a Spring Boot application, Zeebe supports any standard [Spring configuration](https://docs.spring.io/spring-boot/reference/features/external-config.html) method. This configuration can be provided as a configuration file, through environment variables, or both. When both sources are used, environment variables have precedence over the configuration file. All available environment variables are documented in the [configuration file templates](configuration.md#configuration-file-templates). ## Environment variables for operators The following environment variables are intended for operators: - `ZEEBE_LOG_LEVEL`: Sets the log level of the Zeebe Logger (default: `info`). - `ZEEBE_LOG_APPENDER`: Sets the console log appender (default: `Console`). We recommend using `Stackdriver` if Zeebe runs on Google Cloud Platform to output JSON formatted log messages. ## Environment variables for developers The following environment variables are intended for developers: - `SPRING_PROFILES_ACTIVE=dev`: If this is set, the broker starts in a temporary folder and all data is cleaned up upon exit. - `ZEEBE_DEBUG=true/false`: Activates a `DebugLogExporter` with default settings. The value of the environment variable toggles pretty printing. :::note It is not recommended to use these settings in production. ::: --- ## Fixed partitioning Starting with 1.2.0, there is a new experimental configuration option which lets you specify a fixed partitioning scheme; this means you can manually configure which partitions belong to which brokers. The partitioning scheme is controlled via a new configuration option under `zeebe.broker.experimental.partitioning`, more specifically `zeebe.broker.experimental.partitioning.scheme`. This option currently takes the following values: - `ROUND_ROBIN`: When set, this applies the round-robin partition distribution, which is the default partition distribution. _This is the default option, and requires no extra configuration if you want to use it._ - `FIXED`: When set, this applies a manually configured partition distribution, configured separately. To use the `FIXED` partitioning scheme, _you must provide an exhaustive map of all partitions to a set of brokers_. This is achieved via the `zeebe.broker.experimental.partitioning.fixed` configuration option. The example below outlines a cluster of `5` brokers, `3` partitions, and a replication factor of `3`. ```yaml partitioning: scheme: FIXED fixed: - partitionId: 1 nodes: - nodeId: 0 - nodeId: 2 - nodeId: 4 - partitionId: 2 nodes: - nodeId: 1 - nodeId: 3 - nodeId: 4 - partitionId: 3 nodes: - nodeId: 0 - nodeId: 2 - nodeId: 3 ``` This configuration will produce the following distribution: | | Node 0 | Node 1 | Node 2 | Node 3 | Node 4 | | ----------: | :----: | :----: | :----: | :----: | :----: | | Partition 1 | X | | X | | X | | Partition 2 | | X | | X | X | | Partition 3 | X | | X | X | | ## Validation Each broker performs reasonableness checks on the `FIXED` configuration provided. Namely, the configuration must uphold the following conditions: - All partitions _must be explicitly configured_. - All partitions configured must have valid IDs, i.e. between 1 and `zeebe.broker.cluster.partitionsCount`. - All partitions must configure exactly the replicas count, i.e. `zeebe.broker.cluster.replicationFactor`. - All nodes configured for a partition have a valid node ID, i.e. between 0 and `zeebe.broker.cluster.clusterSize - 1`. - If priority election is enabled, all priorities configured for a partition are different. The broker will fail to start if any of these conditions are not met. ## Priority election If you're using the priority election feature, you must also specify the priorities of each broker. In fact, the broker will fail to start if the nodes do not have different priorities, as otherwise you may encounter lengthy election loops. Here is the same example configuration as above, but this time with priorities configured: ```yaml partitioning: scheme: FIXED fixed: - partitionId: 1 nodes: - nodeId: 0 priority: 1 - nodeId: 2 priority: 2 - nodeId: 4 priority: 3 - partitionId: 2 nodes: - nodeId: 1 priority: 1 - nodeId: 3 priority: 3 - nodeId: 4 priority: 2 - partitionId: 3 nodes: - nodeId: 0 priority: 3 - nodeId: 2 priority: 2 - nodeId: 3 priority: 1 ``` :::note The only condition is that the priorities for the nodes of a given partition must be different from one another. We recommend, however, that you use a simple monotonic increase from 1 to the replica count, as shown above. ::: --- ## Gateway health probes The health status for a standalone gateway is available at `{zeebe-gateway}:9600/actuator/health`. The following health indicators are enabled by default: - **Gateway Started** - Checks if the gateway is running (i.e. not currently starting and not yet shut down). - **Gateway Responsive** - Checks if the gateway can handle a request within a given timeout. - **Gateway Cluster Awareness** - Checks if the gateway is aware of other nodes in the cluster. - **Gateway Partition Leader Awareness** - Checks if the gateway is aware of partition leaders in the cluster. - **Disk Space** - Checks that the free disk space is greater than 10 MB. - **Memory** - Checks that at least 10% of max memory (heap) is still available. Health indicators are set to sensible defaults. For specific use cases, it might be necessary to customize health probes. ## Startup probe The started probe is available at `{zeebe-gateway}:9600/actuator/health/startup`. In the default configuration this is merely an alias for the **Gateway Started** health indicator. Other configurations are possible (see below). ## Liveness probe The liveness probe is available at `{zeebe-gateway}:9600/actuator/health/liveness`. It is based on the health indicators mentioned above. In the default configuration, the liveness probe is comprised of the following health indicators: - **Gateway Started** - Checks if the gateway is running (i.e. not currently starting and not yet shut down). - **Liveness Gateway Responsive** - Checks if the gateway can handle a request within an ample timeout, but will only report a `DOWN` health status after the underlying health indicator is down for more than 10 minutes. - **Liveness Gateway Cluster Awareness** - Based on gateway cluster awareness, but will only report a `DOWN` health status after the underlying health indicator is down for more than five minutes. - **Liveness Gateway Partition Leader Awareness** - Based on gateway partition leader awareness, but will only report a `DOWN` health status after the underlying health indicator is down for more than five minutes. - **Liveness Disk Space** - Checks that the free disk space is greater than 1 MB. - **Liveness Memory** - Checks that at least 1% of max memory (heap) is still available. :::note Health indicators with the _liveness_ prefix are intended to be customized for the liveness probe. This allows defining tighter thresholds (e.g. for free memory 1% for liveness vs. 10% for health), as well as adding tolerance for short downtimes (e.g. gateway has no awareness of other nodes in the cluster for more than five minutes). ::: ## Customizing health probes Global settings for all health indicators: - `management.health.defaults.enabled=true` - Enables (default) or disables all health indicators. - `management.endpoint.health.show-details=always/never` - Toggles whether a summary or details (default) of the health indicators will be returned. ### Startup probe Settings for started probe: - `management.endpoint.health.group.startup.show-details=never` - Toggles whether a summary (default) or details of the startup probe will be returned. - `management.endpoint.health.group.startup.include=gatewayStarted` - Defines which health indicators are included in the startup probe. ### Liveness probe Settings for liveness probe: - `management.endpoint.health.group.liveness.show-details=never` - Toggles whether a summary (default) or details of the liveness probe will be returned. - `management.endpoint.health.group.liveness.include=gatewayStarted,livenessGatewayResponsive,livenessGatewayClusterAwareness,livenessGatewayPartitionLeaderAwareness,livenessDiskSpace,livenessMemory` - Defines which health indicators are included in the liveness probe. :::note The individual contributing health indicators of the liveness probe can be configured as well (see below). ::: ### Gateway started Settings for gateway started health indicator: - `management.health.gateway-started.enabled=true` - Enables (default) or disables this health indicator. ### Gateway responsive Settings for gateway responsiveness health indicator: - `management.health.gateway-responsive.enabled=true` - Enables (default) or disables this health indicator. - `management.health.gateway-responsive.requestTimeout=500ms` - Defines the timeout for the request; if the test completes before the timeout, the health status is `UP`, otherwise it is `DOWN`. - `management.health.liveness.gateway-responsive.requestTimeout=5s` - Defines the timeout for the request for liveness probe; if the request completes before the timeout, the health status is `UP`. - `management.health.liveness.gateway-responsive.maxdowntime=10m` - Defines the maximum downtime before the liveness health indicator for responsiveness will flip. ### Gateway cluster awareness Settings for gateway cluster awareness health indicator: - `management.health.gateway-clusterawareness.enabled=true` - Enables (default) or disables this health indicator (and its liveness counterpart). - `management.health.liveness.gateway-clusterawareness.maxdowntime=5m` - Defines the maximum downtime before the liveness health indicator for cluster awareness will flip. In other words, this health indicator will report `DOWN` after the gateway was unaware of other members in the cluster for more than five minutes. ### Gateway partition leader awareness Settings for gateway partition leader awareness health indicator: - `management.health.gateway-partitionleaderawareness.enabled=true` - Enables (default) or disables this health indicator (and its liveness counterpart). - `management.health.liveness.gateway-partitionleaderawareness.maxdowntime=5m` - Defines the maximum downtime before the liveness health indicator for partition leader awareness will flip. In other words, this health indicator will report `DOWN` after the gateway was unaware of partition leaders for more than five minutes. ### Disk space This is arguably the least critical health indicator given the standalone gateway does not write to disk. The only exception may be the writing of log files, which depend on the log configuration. Settings for disk space health indicator: - `management.health.diskspace.enabled=true` - Enables (default) or disables this health indicator (and its liveness counterpart). - `management.health.diskspace.threshold=10MB` - Defines the threshold for the required free disk space. - `management.health.diskspace.path=.` - Defines the path for which the free disk space is examined. - `management.health.liveness.diskspace.threshold=1MB` - Defines the threshold for the required free disk space for liveness. - `management.health.liveness.diskspace.path=.` - Defines the path for which the free disk space for liveness is examined. ### Memory This health indicator examines free memory (heap). Settings for memory health indicator: - `management.health.memory.enabled=true` - Enables (default) or disables this health indicator (and its liveness counterpart). - `management.health.memory.threshold=0.1` - Defines the threshold for the required free memory. The default is 0.1 which is interpreted as 10% of max memory. - `management.health.liveness.memory.threshold=0.01` - Defines the threshold for the required free memory for liveness. The default is 0.01 which is interpreted as 1% of max memory. --- ## Gateway configuration The Zeebe Gateway can be configured similarly to the broker via the `application.yaml` file or environment variables. A complete gateway configuration template is available in the [Zeebe repository](https://github.com/camunda/camunda/blob/main/zeebe/gateway/src/test/resources/configuration/gateway.default.yaml). :::info Configure an embedded gateway When you deploy with Helm (Camunda 8.8+), the gateway runs embedded in the broker by default. In that case, use `zeebe.broker.gateway.*` instead of `zeebe.gateway.*` for any configuration options below, and `ZEEBE_BROKER_GATEWAY_*` instead of `ZEEBE_GATEWAY_*` for environment variables. For a standalone gateway, keep using the `zeebe.gateway.*` / `ZEEBE_GATEWAY_*` prefix. ::: ## Conventions Take the following conventions into consideration when working with the gateway configuration. ### Byte sizes Buffers and data values referencing sizing must be specified as strings and follow the following format: "10U" where U (unit) must be replaced with KB = Kilobytes, MB = Megabytes or GB = Gigabytes. If unit is omitted then the default unit is simply bytes. For example, `sendBufferSize = "16MB"` creates a buffer of 16 Megabytes. ### Time units Timeouts and intervals must be specified either in the standard ISO-8601 format used by `java.time.Duration`, or as strings with the following format: "VU", where: - V is a numerical value (e.g. 1, 5, 10, etc.) - U is the unit, one of: ms = Milliseconds, s = Seconds, m = Minutes, or h = Hours ### Paths Relative paths are resolved relative to the installation directory of the broker. ## Configuration The following sections describe Zeebe Gateway configuration options. Each section includes a table with: - Environment variables - Application properties - A description - Default values The configuration name appears as the section header. Table fields show the property used to set the configuration. For deployments, environment variables are typically easier to use. Table entries use the standalone prefix (`ZEEBE_GATEWAY_*`). Because the embedded gateway is the default, replace this prefix with `ZEEBE_BROKER_GATEWAY_*` unless you run a standalone gateway. If you deploy Camunda 8 with Helm (the recommended approach), the gateway runs embedded in the Orchestration Cluster and you configure it through the [global and orchestration cluster parameters](/self-managed/deployment/helm/chart-parameters.md#global-and-orchestration-cluster-configuration) of the chart. :::note The Zeebe Gateway is a Spring Boot application. In addition to the configuration options documented here, you can use standard Spring Boot properties. For example, the REST server can be configured using `server.*` properties, and the management server (for actuator endpoints) using `management.server.*`. See the [Spring Boot application properties reference](https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html). ::: ### server The `server` configuration allows you to configure the main REST server. Below are a few common ones, but you can find a more exhaustive list [in the official Spring documentation](https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#appendix.application-properties.server). | Field | Description | Example value | | ------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------- | | address | Sets the address the REST server binds to. This setting can also be overridden using the environment variable `SERVER_ADDRESS`. | 0.0.0.0 | | port | Sets the port the REST server binds to. This setting can also be overridden using the environment variable `SERVER_PORT`. | 8080 | #### server.compression | Field | Description | Example value | | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | enabled | If true, enables compression of responses for the Orchestration Cluster REST API. This setting can also be overridden using the environment variable `SERVER_COMPRESSION_ENABLED`. | false | #### server.ssl Allows you to configure the SSL security for the REST server. | Field | Description | Example value | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | enabled | If true, enables TLS for the Orchestration Cluster REST API. This setting can also be overridden using the environment variable `SERVER_SSL_ENABLED`. | false | | certificate | The path to a PEM encoded certificate. This setting can also be overridden using the environment variable `SERVER_SSL_CERTIFICATE`. | | | certificate-private-key | The path to a PKCS1 or PKCS8 private key for the configured certificate. This setting can also be overridden using the environment variable `SERVER_SSL_CERTIFICATEPRIVATEKEY`. | | #### YAML snippet ```yaml server: address: 0.0.0.0 port: 8080 compression: enabled: true ssl: enabled: true certificate: /path/to/my/cert.pem certificate-private-key: /path/to/my/private.key ``` ### server.servlet | Field | Description | Example value | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------- | | context-path | The context path prefix for all REST API requests. For example, if you configure `/zeebe`, then the client's REST address would be `http://localhost:8080/zeebe`. This setting can also be overridden using the environment variable `SERVER_SERVLET_CONTEXTPATH`. | `/` | #### YAML snippet ```yaml server.servlet: context-path: / ``` ### management.server The `management.server` configuration allows you to configure the management server. | Field | Description | Example value | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | address | Sets the address the management server binds to. This setting can also be overridden using the environment variable `MANAGEMENT_SERVER_ADDRESS`. | 0.0.0.0 | | port | Sets the port the management server binds to. This setting can also be overridden using the environment variable `MANAGEMENT_SERVER_PORT`. | 9600 | | base-path | The context path prefix for all management endpoints. For example, if you configure `/zeebe`, your actuator endpoints will be at `http://localhost:9600/zeebe/actuator/configprops`. This setting can also be overridden using the environment variable `MANAGEMENT_SERVER_BASEPATH`. | `/` | #### YAML snippet ```yaml management.server: address: 0.0.0.0 port: 9600 base-path: / ``` ### zeebe.gateway.network The network configuration allows configuration of the host and port details for the gateway. | Field | Description | Example value | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------- | | host | Sets the host the gateway binds to. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_NETWORK_HOST`. | 0.0.0.0 | | port | Sets the port the gateway binds to. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_NETWORK_PORT`. | 26500 | | minKeepAliveInterval | Sets the minimum keep alive interval. This setting specifies the minimum accepted interval between keep alive pings. This value must be specified as a positive integer followed by 's' for seconds, 'm' for minutes, or 'h' for hours. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_NETWORK_MINKEEPALIVEINTERVAL`. | 30s | | maxMessageSize | Sets the maximum size of the incoming and outgoing messages (i.e. commands and events). Apply the same setting on the broker too, see `ZEEBE_BROKER_NETWORK_MAXMESSAGESIZE`. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_NETWORK_MAXMESSAGESIZE`. | 4MB | | maxConnectionAge | Sets the maximum age of a gRPC connection before the gateway proactively closes it (via GOAWAY), forcing the client to reconnect. This lets client-side load balancing pick a different node, keeping request distribution more even, and forces a refresh of the client's name resolution so nodes that weren't yet available when the client's original connections were established can still be discovered. If omitted, the connection age is unbounded (disabled). This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_NETWORK_MAXCONNECTIONAGE`. | | | maxConnectionAgeGrace | Sets the grace period, after `maxConnectionAge` elapses, during which existing calls on the connection may finish before it's forcibly terminated. Only takes effect when `maxConnectionAge` is set. If omitted, the grace period is infinite (disabled). This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_NETWORK_MAXCONNECTIONAGEGRACE`. | | | socketReceiveBuffer | Sets the size of the socket's receive buffer for the gateway. If omitted defaults to 1MB. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_NETWORK_SOCKETRECEIVEBUFFER`. | 4MB | | socketSendBuffer | Sets the size of the socket's send buffer for the gateway. If omitted defaults to 1MB. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_NETWORK_SOCKETSENDBUFFER`. | 4MB | #### YAML snippet ```yaml network: host: 0.0.0.0 port: 26500 minKeepAliveInterval: 30s maxMessageSize: 4MB maxConnectionAge: null maxConnectionAgeGrace: null socketReceiveBuffer: 4MB socketSendBuffer: 4MB ``` ### zeebe.gateway.cluster As mentioned, the gateway needs to connect to the Zeebe brokers. It is important to configure the cluster's initial contact point to the Zeebe brokers. You may set only one of the Zeebe brokers, but keep in mind that resiliency will be lower than using all the Zeebe brokers available. The corresponding environment variable is called `ZEEBE_GATEWAY_CLUSTER_INITIALCONTACTPOINTS`. It is necessary to use the same cluster name for the broker and gateway. Otherwise, a connection will not be possible. The related configuration property is `zeebe.gateway.cluster.clusterName` and as an environment variable, it is called `ZEEBE_GATEWAY_CLUSTER_CLUSTERNAME`. If you use the Helm charts, both properties are configured for you already. | Field | Description | Example value | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | | initialContactPoints | Sets initial contact points (brokers), which the gateway should contact. The contact points of the internal network configuration must be specified. The format is [HOST:PORT]. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_INITIALCONTACTPOINTS` specifying a comma-separated list of contact points. | [ 192.168.1.22:26502, 192.168.1.32:26502 ] | | requestTimeout | Sets the timeout of requests sent to the broker cluster. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_REQUESTTIMEOUT`. | 15s | | clusterName | Sets name of the Zeebe cluster to connect to. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_CLUSTERNAME`. | zeebe-cluster | | memberId | Sets the member ID of the gateway in the cluster. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_MEMBERID`. This is a deprecated property; use `camunda.cluster.gateway-id` instead. | gateway | | host | Sets the host the gateway node binds to for internal cluster communication. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_HOST`. | 0.0.0.0 | | port | Sets the port the gateway node binds to for internal cluster communication. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_PORT`. | 26502 | | advertisedHost | Controls the advertised host; if omitted defaults to the host. This is particularly useful if your gateway stands behind a proxy. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_ADVERTISEDHOST`. | 0.0.0.0 | | advertisedPort | Controls the advertised port; if omitted defaults to the port. This is particularly useful if your gateway stands behind a proxy. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_ADVERTISEDPORT`. | 25602 | #### YAML snippet ```yaml cluster: initialContactPoints: 127.0.0.1:26502 requestTimeout: 15s clusterName: zeebe-cluster memberId: gateway host: 0.0.0.0 port: 26502 advertisedHost: 0.0.0.0 advertisedPort: 25602 ``` ### zeebe.gateway.cluster.membership To configure how the gateway connects and distributes information with other nodes (brokers or gateways) via SWIM, the following properties can be used. It might be useful to increase timeouts for setups that encounter a high latency between nodes. | Field | Description | Example value | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | broadcastUpdates | Configure whether to broadcast member updates to all members. If set to `false`, updates will be gossiped among the members. If set to `true`, the network traffic may increase but reduce the time to detect membership changes. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_MEMBERSHIP_BROADCASTUPDATES`. | false | | broadcastDisputes | Configure whether to broadcast disputes to all members. If set to `true`, the network traffic may increase but reduce the time to detect membership changes. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_MEMBERSHIP_BROADCASTDISPUTES`. | true | | notifySuspect | Configure whether to notify a suspect node on state changes. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_MEMBERSHIP_NOTIFYSUSPECT`. | false | | gossipInterval | Sets the interval at which the membership updates are sent to a random member. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_MEMBERSHIP_GOSSIPINTERVAL`. | 250ms | | gossipFanout | Sets the number of members to which membership updates are sent at each gossip interval. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_MEMBERSHIP_GOSSIPFANOUT`. | 2 | | probeInterval | Sets the interval at which to probe a random member. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_MEMBERSHIP_PROBEINTERVAL`. | 1s | | probeTimeout | Sets the timeout for a probe response. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_MEMBERSHIP_PROBETIMEOUT`. | 100ms | | suspectProbes | Sets the number of probes failed before declaring a member is suspect. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_MEMBERSHIP_SUSPECTPROBES`. | 3 | | failureTimeout | Sets the timeout for a suspect member is declared dead. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_MEMBERSHIP_FAILURETIMEOUT`. | 10s | | syncInterval | Sets the interval at which this member synchronizes its membership information with a random member. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_MEMBERSHIP_SYNCINTERVAL`. | 10s | #### YAML snippet ```yaml membership: broadcastUpdates: false broadcastDisputes: true notifySuspect: false gossipInterval: 250ms gossipFanout: 2 probeInterval: 1s probeTimeout: 100ms suspectProbes: 3 failureTimeout: 10s syncInterval: 10s ``` ### zeebe.gateway.cluster.configManager.gossip Configure the parameters used to propagate the dynamic cluster configuration across brokers and gateways. | Field | Description | ExampleValue | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | | syncDelay | Sets the interval between two synchronization requests to other members of the cluster. This setting can also be overridden using the environment variable ZEEBE_GATEWAY_CLUSTER_CONFIGMANAGER_GOSSIP_SYNCDELAY | 10s | | syncRequestTimeout | Sets the timeout for the synchronization requests. This setting can also be overridden using the environment variable ZEEBE_GATEWAY_CLUSTER_CONFIGMANAGER_GOSSIP_SYNCREQUESTTIMEOUT | 2s | | gossipFanout | Sets the number of cluster members the configuration is gossiped to. This setting can also be overridden using the environment variable ZEEBE_GATEWAY_CLUSTER_CONFIGMANAGER_GOSSIP_GOSSIPFANOUT | 2 | #### YAML snippet ```yaml configManager: gossip: syncDelay: 10s syncRequestTimeout: 2s gossipFanout: 2 ``` ### zeebe.gateway.cluster.security The cluster security configuration options allow securing communication between the gateway and other nodes in the cluster. :::note You can read more about intra-cluster security on [its dedicated page](../security/secure-cluster-communication.md). ::: | Field | Description | Example value | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | enabled | Enables TLS authentication between this gateway and other nodes in the cluster. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_SECURITY_ENABLED`. | false | | certificateChainPath | Sets the path to the certificate chain file. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_SECURITY_CERTIFICATECHAINPATH`. | | | privateKeyPath | Sets the path to the private key file location. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_SECURITY_PRIVATEKEYPATH`. | | | keyStore | Configures the keystore file containing both the certificate chain and the private key; currently only supports PKCS12 format. | | | keyStore.filePath | The path for keystore file; This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_SECURITY_KEYSTORE_FILEPATH`. | /path/key.pem | | keyStore.password | Sets the password for the keystore file, if not set it is assumed there is no password; This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_SECURITY_KEYSTORE_PASSWORD` | changeme | #### YAML snippet ```yaml security: enabled: false certificateChainPath: null privateKeyPath: null keyStore: filePath: null password: null ``` ### zeebe.gateway.security.authentication | Field | Description | Example value | | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | mode | Controls which authentication mode is active; supported modes are `none` and `identity`. If `identity` is set, authentication will be done using [camunda-identity](/self-managed/components/management-identity/overview.md), which needs to be configured in the corresponding subsection. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_SECURITY_AUTHENTICATION_MODE`. | none | #### YAML snippet ```yaml security: authentication: mode: none ``` ### zeebe.gateway.security.authentication.identity :::note The Zeebe configuration properties for Camunda Identity are deprecated as of version `8.4.0`. Use the dedicated Camunda Identity properties or the [corresponding environment variables](/self-managed/components/management-identity/miscellaneous/configuration-variables.md#core-configuration). ::: | Field | Description | Example value | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | | issuerBackendUrl | The URL to the auth provider backend, used to validate tokens. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_SECURITY_AUTHENTICATION_IDENTITY_ISSUERBACKENDURL`. | http://keycloak:18080/auth/realms/camunda-platform | | audience | The required audience of the auth token. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_SECURITY_AUTHENTICATION_IDENTITY_AUDIENCE`. | zeebe-api | | type | The identity auth type to apply, one of `keycloak` or `auth0`. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_SECURITY_AUTHENTICATION_IDENTITY_TYPE`. | keycloak | | baseUrl | The URL to the Identity instance. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_SECURITY_AUTHENTICATION_IDENTITY_BASEURL`. | http://identity:8084 | #### YAML snippet ```yaml security: authentication: mode: identity identity: issuerBackendUrl: http://keycloak:18080/auth/realms/camunda-platform audience: zeebe-api type: keycloak ``` ### zeebe.gateway.cluster.messageCompression To configure the compression algorithm for all messages sent between the gateway and the brokers, the following property can be set. Available options are NONE, GZIP, and SNAPPY. This feature is useful when the network latency between the nodes is very high (for example, when nodes are deployed in different data centers). When latency is high, the network bandwidth is severely reduced. Therefore, enabling compression helps improve the throughput. You need to decide between reducing bandwidth or reducing resources required for compression. :::caution When this flag is enabled, you must also enable compression in the standalone broker configuration. When there is no latency enabling, this may have a performance impact. ::: | Field | Description | Example value | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | messageCompression | Configure compression algorithm for all messages sent between the gateway and the brokers. Available options are NONE, GZIP, and SNAPPY. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_CLUSTER_MESSAGECOMPRESSION`. | NONE | #### YAML snippet ```yaml messageCompression: NONE ``` ### zeebe.gateway.threads To handle many concurrent incoming requests, the user can do two things: scale the deployed gateways (if the standalone mode is in use), or increase the used resources and threads. The Zeebe Gateway uses one thread by default, but this should be set to a higher number if the gateway doesn’t exhaust its available resources and doesn’t keep up with the load. The corresponding environment variables look like this: `ZEEBE_GATEWAY_THREADS_MANAGEMENTTHREADS`. During benchmarking and when increasing the thread count, it may also make sense to increase the given resources, which are quite small in the Helm chart. With the Helm charts, the gateway is embedded in the Orchestration Cluster nodes, so the number of gateways follows `orchestration.clusterSize`. Running more than one node enables quick failover: if one node dies, the remaining nodes can handle the traffic. A separate thread pool is used to run the gRPC business logic. The thread pool is elastic (meaning it will start/stop threads dynamically), but will always keep a minimum number of threads, and only start up to a maximum number of threads. By default, this range is from one thread per core, up to two threads per core. To explore how the gateway behaves, or what it does, metrics can be consumed. By default, the gateway exports Prometheus metrics, which can be scrapped under `:9600/actuator/prometheus`. | Field | Description | Example value | | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | managementThreads | Sets the number of threads the gateway will use to communicate with the broker cluster. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_THREADS_MANAGEMENTTHREADS`. | 1 | | grpcMinThreads | Sets the minimum number of threads in the gRPC thread pool. Only accepts static values; defaults to the number of cores available. | 8 | | grpcMaxThreads | Sets the maximum number of threads in the gRPC thread pool. Only accepts static values; defaults to twice the number of cores available. | 16 | #### YAML snippet ```yaml threads: managementThreads: 1 grpcMinThreads: 8 grpcMaxThreads: 16 ``` ### zeebe.gateway.security The client security configuration options allow securing the communication between a gateway and clients. :::note You can read more about client-gateway security on [its dedicated page](../security/secure-client-communication.md). ::: | Field | Description | Example value | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | enabled | Enables TLS authentication between clients and the gateway. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_SECURITY_ENABLED`. | false | | certificateChainPath | Sets the path to the certificate chain file. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_SECURITY_CERTIFICATECHAINPATH`. | | | privateKeyPath | Sets the path to the private key file location. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_SECURITY_PRIVATEKEYPATH`. | | #### YAML snippet ```yaml security: enabled: false certificateChainPath: privateKeyPath: ``` ### zeebe.gateway.longPolling It's possible to configure gateway long-polling behavior. Read more on long-polling behavior [here](../../../../../components/concepts/job-workers.md#long-polling). | Field | Description | Example value | | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | enabled | Enables long polling for available jobs. This setting can also be overridden using the environment `variable ZEEBE_GATEWAY_LONGPOLLING_ENABLED`. | true | | timeout | Set the timeout for long polling in milliseconds. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_LONGPOLLING_TIMEOUT`. | 10000 | | probeTimeout | Set the probe timeout for long polling in milliseconds. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_LONGPOLLING_PROBETIMEOUT`. | 10000 | | minEmptyResponses | Set the number of minimum empty responses, a minimum number of responses with jobCount of 0 infers that no job are available. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_LONGPOLLING_MINEMPTYRESPONSES`. | 3 | #### YAML snippet ```yaml longPolling: enabled: true timeout: 10000 probeTimeout: 10000 minEmptyResponses: 3 ``` ### zeebe.gateway.interceptors It is possible to intercept requests in the gateway, which can be configured via environment variables or the `application.yaml` file. For more details, read about [interceptors](/self-managed/components/orchestration-cluster/zeebe/zeebe-gateway/interceptors.md). Each interceptor should be configured with the values described below: Field Description Example value id Identifier for this interceptor. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_INTERCEPTORS_0_ID`. jarPath Path (relative or absolute) to the JAR file containing the interceptor class and its dependencies. All classes must be compiled for the same language version as Zeebe or lower. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_INTERCEPTORS_0_JARPATH`. className Entry point of the interceptor, a class which must: implement io.grpc.ServerInterceptor have public visibility have a public default constructor (i.e. no-arg constructor) This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_INTERCEPTORS_0_CLASSNAME`. #### YAML snippet ```yaml interceptors: id: null jarPath: null className: null ``` ### zeebe.gateway.filters It is possible to filter REST API requests in the gateway, which can be configured via environment variables or the `application.yaml` file. For more details, read about [filters](/self-managed/components/orchestration-cluster/zeebe/zeebe-gateway/filters.md). Each filter should be configured with the values described below: Field Description Example value id Identifier for this filter. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_FILTERS_0_ID`. jarPath Path (relative or absolute) to the JAR file containing the filter class and its dependencies. All classes must be compiled for the same language version as Zeebe or lower. This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_FILTERS_0_JARPATH`. className Entry point of the filter, a class which must: implement jakarta.servlet.Filter have public visibility have a public default constructor (i.e. no-arg constructor) This setting can also be overridden using the environment variable `ZEEBE_GATEWAY_FILTERS_0_CLASSNAME`. #### YAML snippet ```yaml filters: id: null jarPath: null className: null ``` --- ## Priority election Priority election is an alternative to the default raft leader election, where leader election is implemented by a random timer-based algorithm. It aims to achieve a more uniform leader distribution by assigning each node a priority per partition and modifying the election algorithm to ensure nodes with higher priority have a higher chance of becoming leader. ## Configuration Priority election is enabled by default. To enable or disable it explicitly, set `camunda.cluster.raft.priority-election-enabled` to `true` or `false` in your config, or set the equivalent environment variable `CAMUNDA_CLUSTER_RAFT_PRIORITYELECTIONENABLED`. For details, see [`camunda.cluster.raft`](broker.md#camundaclusterraft). If you are using the fixed partitioning scheme (experimental), you may need [additional configuration](fixed-partitioning.md#priority-election). ## Limitations With priority election enabled, election latency and thus failover time increases. The result of a leader election is not deterministic, and priority election can only increase the chance of having a uniform leader distribution, not guarantee it. Factors such as high load can prevent high-priority nodes from becoming the leader. --- ## Zone-aware clusters With zone awareness, an Orchestration Cluster distributes its brokers and partition replicas across multiple zones (regions or availability zones). Spreading replicas across zones lets the cluster survive the loss of an entire zone and bias partition leadership toward a preferred zone. Zone awareness controls where the application places partition replicas among brokers. It does not control where Kubernetes schedules the broker pods themselves — on Kubernetes, also configure [topology spread constraints](/self-managed/deployment/helm/configure/pod-scheduling.md#spread-orchestration-cluster-pods-across-availability-zones) so the broker pods actually land in different zones; without it, brokers assigned to different logical zones can still be scheduled onto nodes in the same physical zone. Configure both together: topology spread constraints put each broker pod in the zone Kubernetes actually schedules it into, and zone awareness places partition replicas according to each broker's assigned zone. Zone awareness is required for topologies with three or more zones. It also simplifies managing zones: brokers are named after the zone they belong to, so you describe the topology in terms of zones rather than individual numeric node IDs. Because zones are named explicitly, zone awareness supports topologies that the round-robin numbering strategy cannot express, such as three or more zones. Adding a zone means changing the zone list rather than renumbering every broker. It isn't a configuration-only change, though: existing partitions have to be told about the new zone through the [cluster management API](../operations/management-api.md#add-or-re-add-a-zone). Zone awareness is also useful in a single-region setup. By mapping zones to availability zones (AZs) and giving one AZ a higher priority, you can skew partition leaders to stay in that AZ. Keeping leaders in one AZ reduces cross-AZ traffic to the single writer instance of a relational database (RDBMS), which lowers the associated cost. This optimization matters less for Elasticsearch, which distributes load across all three zones. ## When to use zone awareness Zone awareness is the recommended approach for new deployments, whether single-region (to skew leaders to a preferred AZ), dual-region, or three or more zones. ## What is a zone? A zone is a failure domain, typically a cloud region or availability zone, into which brokers are grouped. Each broker declares which zone it belongs to, and the cluster uses that information to: - Place partition replicas across zones, so no single zone holds all replicas of a partition. - Assign Raft election priorities per zone, so partition leaders are skewed toward the highest-priority zone. Compared to using even/odd node ID depending on the zone, zone awareness makes multi-region and multi-AZ setups simpler to configure across all deployment targets (Kubernetes, Amazon ECS, bare metal). ## How zone awareness works You assign each broker to a zone through the `camunda.cluster.zone` setting. Internally, brokers are identified by a composite node ID of the form `_`, combining the zone name and the broker's index within that zone. For a two-broker `us-east1` zone and a two-broker `us-west1` zone, the brokers are named: ```text us-east1_0 us-east1_1 us-west1_0 us-west1_1 ``` The zone is part of the name, so you can read the topology directly from the broker identifiers. Zone names are not reserved for three or more zones: a single-region or dual-region cluster can use them too, and gets the same readable identities. Numeric node IDs remain available for existing setups. ### Partitioning scheme The `ZONE_AWARE` partitioning scheme drives partition distribution and leadership. When you select this scheme, you describe every zone in the cluster as a list, giving each zone the following properties: | Property | Description | | :------------------- | :---------------------------------------------------------------------------------------------- | | `name` | The zone identifier. Must match the `camunda.cluster.zone` of the brokers in that zone. | | `number-of-brokers` | How many brokers are deployed in the zone. | | `number-of-replicas` | How many replicas of each replication group live in the zone. | | `priority` | Higher values give the zone higher Raft election priority, biasing partition leaders toward it. | ### Comparison to dual-region broker numbering In the [dual-region](../../../../concepts/multi-region/dual-region.md) setup, brokers are numbered `0, 1, 2, 3, …` and the region is inferred from the parity of the node ID: even IDs (`0, 2, 4, …`) belong to one region and odd IDs (`1, 3, 5, …`) to the other. This parity-based approach only works for exactly two regions and hides the region in the numbering. Zone awareness replaces it with explicit zone names, which works for any number of zones. Changing the zone list afterwards is possible, but it is not a configuration-only change: existing partitions have to be told about the new zone through the [cluster management API](../operations/management-api.md#add-or-re-add-a-zone). ## Example configuration The following configures a three-zone cluster in which `us-east1` is the preferred zone for partition leaders: ```yaml camunda: cluster: size: 5 replication-factor: 5 # set per broker; env: CAMUNDA_CLUSTER_ZONE zone: us-east1 partitioning: scheme: ZONE_AWARE zone-aware: zones: - name: us-east1 number-of-brokers: 2 number-of-replicas: 2 priority: 1000 - name: us-west2 number-of-brokers: 2 number-of-replicas: 2 priority: 500 - name: eu-west1 number-of-brokers: 1 number-of-replicas: 1 priority: 10 ``` Each broker sets its own `camunda.cluster.zone`, while the `zone-aware.zones` list is the same across all brokers in the cluster. For the full list of properties and their environment-variable equivalents, see the [cluster configuration reference](../../core-settings/configuration/properties.md). ## Related resources - [Dual-region](../../../../concepts/multi-region/dual-region.md): synchronous two-region setup. - [Multi-Region RDBMS](../../../../concepts/multi-region/multi-region-rdbms.md): a multi-region architecture built on zone awareness, using three or more zones, in which a zone loss does not stop processing. - [Configure zone-aware multi-region deployments](/self-managed/deployment/helm/configure/multi-region-zone-awareness.md): set these properties through the Camunda Helm chart. - [Zeebe clustering](/components/zeebe/technical-concepts/clustering.md): how brokers, partitions, and replication work. --- ## Analytics Exporter Understand how the Analytics Exporter sends product telemetry data from your Self-Managed Orchestration Cluster to Camunda, how to enable and configure it, and exactly what data it sends. ## About The Analytics Exporter sends product telemetry from your Orchestration Cluster to a Camunda-operated analytics endpoint over OTLP/HTTP. Camunda uses this data to verify contractual usage, understand how the product is used, and support your deployment. For what Camunda collects and why across all products, see [data collection](/reference/data-collection/data-collection.md). The exporter is **disabled by default**. No data leaves your cluster until you add the exporter to your broker configuration. :::info The exporter sends process metadata only. It never sends process variables, payloads, message contents, job variables, incident error messages, or BPMN, DMN, and form resources. ::: ## How it works The exporter reads records from the Zeebe log stream, keeps a fixed set of event types, converts each one into an OpenTelemetry log record, and pushes it to the configured endpoint in batches. Three properties are worth understanding before you enable it: - **It is designed not to slow down your brokers.** The exporter is fire-and-forget. Records are handed to a background thread and the broker acknowledges the log position immediately. If the queue fills or the endpoint is unreachable, records are dropped rather than back-pressuring the engine. - **Delivery is best effort.** Records can be dropped if the endpoint is unreachable or a broker restarts, so the data is not guaranteed to be complete. Camunda uses the contractual signals to verify usage against your agreement, not to calculate charges: billing and overage charges are based on [usage metrics](/reference/data-collection/usage-metrics.md). Do not rely on the exporter for audit or anything that depends on a complete record. - **It runs on the partition leader only.** No additional high-availability setup is required. ## Enable the exporter Add an `analytics` exporter to your broker configuration. To disable it again, remove the declaration and restart the brokers. ```yaml camunda: data: exporters: analytics: class-name: io.camunda.exporter.analytics.AnalyticsExporter args: categories: - contractual - optional ``` These are the equivalent environment variables: ```sh CAMUNDA_DATA_EXPORTERS_ANALYTICS_CLASSNAME=io.camunda.exporter.analytics.AnalyticsExporter CAMUNDA_DATA_EXPORTERS_ANALYTICS_ARGS_CATEGORIES_0=contractual CAMUNDA_DATA_EXPORTERS_ANALYTICS_ARGS_CATEGORIES_1=optional ``` No further setup is required. The exporter resolves your [cluster ID](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#cluster) and [license key](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#licensing) from the broker automatically. ### Network requirements The exporter makes outbound HTTPS requests to `telemetry.camunda.io`, the Camunda analytics endpoint. Allowlist this host in your egress firewall rules on every broker. :::warning If the endpoint is unreachable, the exporter fails **silently**. No incident is raised, no error is surfaced to operators, and the brokers continue running normally. You will not be told if the exporter stops reaching the endpoint. ::: ### Authentication The exporter authenticates using your Camunda 8 Self-Managed license key. There is nothing extra to configure. The exporter derives everything it needs from the license key already set on the cluster, and computes the credentials itself on startup. **The raw license key is never transmitted.** The exporter sends a SHA-256 fingerprint of the key in the `x-camunda-fingerprint` header, and uses the key as an HMAC secret to sign each batch. Camunda maps the fingerprint to your organization. If you rotate your license key, the exporter picks up the new key the next time the broker starts. ### Verify the exporter is running When the exporter starts on a partition leader, look for the following log line: ``` Analytics exporter configured: endpoint=, clusterId=, partitionId=, exporterDigest=, activeCategories= ``` `activeCategories` shows which categories the exporter is sending. This line confirms the exporter loaded its configuration; it does not confirm that the endpoint is reachable. ## Choose what is sent The [`categories`](#configuration-reference) option controls which signals are exported: | Category | What it contains | Why Camunda collects it | | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | contractual | Root process instance starts, decision evaluations, user task assignments, and tenant creation and deletion. | To verify usage against the metrics in your agreement. | | optional | Process, decision, and form definition deployments and deletions; incidents raised and resolved; user task creation; and agent instance starts and completions. | To understand how the product is used, prioritize improvements, and support your deployment. | Every signal in each category, with its attributes, is listed in [what data is sent](#what-data-is-sent). Both categories are active by default. Narrow the set by removing entries. For example, to send contractual signals only: ```yaml camunda: data: exporters: analytics: class-name: io.camunda.exporter.analytics.AnalyticsExporter args: categories: - contractual ``` Explicitly setting `categories: []` disables data-category telemetry entirely. Only the heartbeat and export-window signals below continue to be sent. The `camunda.telemetry.heartbeat` event and the `camunda.metric.export_window` metric are sent whenever the exporter runs, regardless of the categories you select. Camunda uses them to detect data gaps and offline clusters. ## What data is sent Each signal is an [OpenTelemetry log record](https://opentelemetry.io/docs/specs/semconv/general/events/) identified by its `event.name`. ### Resource attributes Attached to every record, metric point, and heartbeat: | Attribute | Type | Description | | ---------------------------- | ------ | ---------------------------------------------------------------- | | `camunda.cluster.id` | string | Cluster identifier. | | `camunda.partition.id` | long | Partition ID. | | `camunda.tenant.physical_id` | string | Physical tenant of the broker instance that produced the signal. | | `service.name` | string | Always `camunda-zeebe`. | ### Common event attributes Set on every event record: | Attribute | Type | Description | | ------------------------------- | ------ | --------------------------------------------------------------------- | | `event.name` | string | Signal identifier. | | `camunda.log.position` | long | Log stream position. Used for deduplication. | | `camunda.event.sequence_number` | long | Monotonic per-partition counter, used for ordering and gap detection. | ### Contractual signals **`camunda.process.instance.activated`**: a root process instance was activated. | Attribute | Type | Description | | ----------------------------------- | ------ | -------------------------- | | `camunda.process.id` | string | BPMN process ID. | | `camunda.process.version` | long | Deployed process version. | | `camunda.process.definition_key` | long | Process definition key. | | `camunda.process.instance_key` | long | Process instance key. | | `camunda.process.root_instance_key` | long | Root process instance key. | | `camunda.tenant.id` | string | Tenant ID. | Taken from activation of the root process element, the single point every process instance passes through however it was started: client API, message, timer, signal, or conditional start event. Instances started by a call activity are excluded, so this counts root instances only. **`camunda.user_task.assigned`**: a user task was assigned to a user. This signal carries no assignee-derived data; it only counts assignment events. | Attribute | Type | Description | | ------------------------------ | ------ | --------------------- | | `camunda.user_task.key` | long | User task key. | | `camunda.process.instance_key` | long | Process instance key. | | `camunda.tenant.id` | string | Tenant ID. | Assignments with an empty assignee produce no event. **`camunda.tenant.created`** and **`camunda.tenant.deleted`** | Attribute | Type | Description | | ------------------- | ------ | ----------- | | `camunda.tenant.id` | string | Tenant ID. | The tenant name, description, and associated entity are not exported. **`camunda.decision.instance.evaluated`** (counter metric): pre-aggregated count of evaluated decision instances, dimensioned by `camunda.tenant.id`. Counts evaluation records rather than the decisions inside them: a decision requiring sub-decisions counts once, and failed evaluations are not counted. This matches the counting rule for the decision instance usage metric. ### Optional signals **`camunda.user_task.created`**: a user task was created. | Attribute | Type | Description | | -------------------------------- | ------ | --------------------------------- | | `camunda.process.id` | string | BPMN process ID. | | `camunda.process.definition_key` | long | Process definition key. | | `camunda.process.instance_key` | long | Process instance key. | | `camunda.element.id` | string | BPMN element ID of the user task. | | `camunda.tenant.id` | string | Tenant ID. | **`camunda.process.incident.created`** and **`camunda.process.incident.resolved`** | Attribute | Type | Description | | -------------------------------- | ------ | ----------------------- | | `camunda.incident.key` | long | Incident key. | | `camunda.process.id` | string | BPMN process ID. | | `camunda.process.definition_key` | long | Process definition key. | | `camunda.process.instance_key` | long | Process instance key. | | `camunda.tenant.id` | string | Tenant ID. | **The incident error message is not exported**, because it can quote expressions and variable values. **`camunda.process.definition.created`** and **`camunda.process.definition.deleted`** | Attribute | Type | Description | | -------------------------------- | ------ | ----------------------- | | `camunda.process.id` | string | BPMN process ID. | | `camunda.process.version` | long | Process version. | | `camunda.process.definition_key` | long | Process definition key. | | `camunda.tenant.id` | string | Tenant ID. | The BPMN resource, resource name, and version tag are not exported. **`camunda.decision.definition.created`** and **`camunda.decision.definition.deleted`** | Attribute | Type | Description | | -------------------------- | ------ | ------------------------- | | `camunda.decision.id` | string | Decision ID from the DMN. | | `camunda.decision.key` | long | Decision key. | | `camunda.decision.version` | long | Decision version. | | `camunda.tenant.id` | string | Tenant ID. | The decision name and version tag are not exported. **`camunda.form.definition.created`** and **`camunda.form.definition.deleted`** | Attribute | Type | Description | | ---------------------- | ------ | ------------- | | `camunda.form.id` | string | Form ID. | | `camunda.form.key` | long | Form key. | | `camunda.form.version` | long | Form version. | | `camunda.tenant.id` | string | Tenant ID. | The form resource, resource name, and version tag are not exported. **`camunda.agent.instance.created`** and **`camunda.agent.instance.completed`** | Attribute | Type | Description | | ----------------------------------- | ------ | ----------------------------------------------------------------- | | `camunda.agent.instance_key` | long | Agent instance key. | | `camunda.agent.definition_key` | long | Agent definition key. | | `camunda.agent.status` | string | Agent instance status, for example `INITIALIZING` or `COMPLETED`. | | `camunda.process.id` | string | BPMN process ID. | | `camunda.process.definition_key` | long | Process definition key. | | `camunda.process.instance_key` | long | Process instance key. | | `camunda.process.root_instance_key` | long | Root process instance key. | | `camunda.tenant.id` | string | Tenant ID. | The agent definition (model, provider, system prompt), its tools, its token counts and other collected metrics, and its configured limits are **not** exported. ### Always-on signals Together with the resource attributes attached to every record, these signals make up the environment data described on the [data collection](/reference/data-collection/data-collection.md) page. **`camunda.telemetry.heartbeat`**: periodic liveness signal from the partition leader. | Attribute | Type | Description | | ---------------------------------------------- | ------ | ------------------------------------- | | `event.name` | string | Always `camunda.telemetry.heartbeat`. | | `camunda.telemetry.heartbeat.broker_version` | string | Broker version. | | `camunda.telemetry.heartbeat.exporter_version` | string | Analytics Exporter version. | **`camunda.metric.export_window`** (gauge metric): accompanies every metrics export, carrying the window total and log position range. Camunda uses it for deduplication and gap detection. ### What is never sent Regardless of configuration, the exporter never sends: - Process variables, job variables, or any payload - Message contents or correlation keys - Incident error messages - BPMN, DMN, and form resources, resource names, or version tags - Tenant names and descriptions - Agent system prompts, tool definitions, model configuration, or token counts - Raw user names, email addresses, assignee data, or your license key ## Configuration reference All options live under `args`. The defaults suit typical Self-Managed deployments and rarely need changing. | Option | Type | Description | Default | | ------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | | `endpoint` | string | OTLP/HTTP base URL for the analytics endpoint. The path `/v1/logs` is appended automatically. | `https://telemetry.camunda.io` | | `categories` | list | Signal categories to export: `contractual`, `optional`. Omitted enables both (the default); an explicit empty list (`[]`) disables both. | `[contractual, optional]` | | `push-interval` | duration | Maximum time between batch pushes, as an [ISO 8601 duration](https://en.wikipedia.org/wiki/ISO_8601#Durations). | `PT5M` | | `heartbeat-interval` | duration | Interval between heartbeat events carrying the broker and exporter versions. | `PT10M` | | `max-queue-size` | int | Maximum number of records buffered in memory before new records are dropped. | `2048` | | `max-batch-size` | int | Maximum number of records per OTLP request. Must not exceed `max-queue-size`. | `512` | | `sampling-rate` | double | Sampling rate applied to all signals, including contractual signals, between `0.0` and `1.0`. | `1.0` | | `http-connect-timeout` | duration | Maximum time to establish a connection to the endpoint. | `PT3S` | | `http-request-timeout` | duration | Maximum time for a single export request to complete. | `PT3S` | | `http-max-retry-attempts` | int | Maximum number of attempts per export request, including the first attempt. | `3` | :::warning A `sampling-rate` below `1.0` also samples contractual signals, so they undercount your actual usage. Keep the default of `1.0` unless Camunda asks you to change it. ::: ## Failure behavior The exporter is designed so that failures do not affect broker throughput. Records may be dropped without notice. Records are lost when: - **The in-memory queue is full**, typically because the endpoint is slow or unreachable. New records are dropped without retry. - **A broker crashes or restarts.** The queue is not persisted. - **The endpoint returns an error.** For transient errors, the exporter makes up to `http-max-retry-attempts` attempts per request, then drops the batch. It does not buffer to disk. Because every record carries `camunda.cluster.id`, `camunda.partition.id`, and `camunda.log.position`, Camunda deduplicates redelivered records downstream. ## Limitations - **Analytics-grade only.** No exactly-once delivery, no reconciliation, no gap filling. - **Fixed signal set.** Individual signals cannot be toggled; only whole categories. - **No feedback on failure.** A misconfigured endpoint produces no operator-visible error. --- ## Camunda Exporter The Camunda Exporter exports Zeebe records directly to Elasticsearch or OpenSearch. Unlike the Elasticsearch and OpenSearch exporters, it exports records in the format required by Operate and Tasklist, so you don’t need to configure additional importers or data transformations. Using the Camunda Exporter can increase process instance throughput and reduce the latency of changes appearing in Operate and Tasklist. :::note When exporting, indexes are created as required and not recreated if they already exist. However, disabling the exporter does not delete indexes. Administrators must handle deletions. You can configure a [retention policy](./camunda-exporter.md?configuration=retention#options) to automatically delete data after a set number of days. ::: ## Configuration Camunda Exporter is enabled by default if secondary storage is configured to use Elasticsearch or OpenSearch. See the properties prefixed with `CAMUNDA_DATA_SECONDARYSTORAGE` in [secondary-storage configuration properties](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#data---secondary-storage). To connect to an external Elasticsearch or OpenSearch cluster, configure the secondary storage endpoint with `camunda.data.secondary-storage.elasticsearch.url` or `camunda.data.secondary-storage.elasticsearch.urls`. If you're using OpenSearch, use `camunda.data.secondary-storage.opensearch.url` or `camunda.data.secondary-storage.opensearch.urls` instead. The Camunda Exporter reuses that secondary storage connection automatically. If you're deploying with Helm, configure the external cluster in your Elasticsearch or OpenSearch values, then set the secondary storage type for Orchestration Cluster. For a complete Helm example, see [configure secondary storage](/self-managed/concepts/secondary-storage/configuring-secondary-storage.md#configuration-options) and [using external Elasticsearch](/self-managed/deployment/helm/configure/database/elasticsearch/using-external-elasticsearch.md). :::info Helm values mapping The option names in the tabs below (for example, `rolloverInterval`) are exporter option names, not top-level Helm values keys. In Helm, configure these as Orchestration Cluster application properties using `orchestration.extraConfiguration` (or `orchestration.configuration`). For example, set history rollover using: - `camunda.data.secondary-storage.elasticsearch.history.rollover-interval` - `camunda.data.secondary-storage.opensearch.history.rollover-interval` ::: ### Options Helm property path prefix for these options: `camunda.data.secondary-storage.{elasticsearch|opensearch}.` Use `url` or `urls` to define the Elasticsearch or OpenSearch endpoint. The `connect` options in this section only fine-tune how Camunda connects to the secondary storage cluster after you configure the endpoint. :::note Please refer to [supported environments](/reference/supported-environments.md#camunda-8-self-managed) to find out which versions of Elasticsearch and/or OpenSearch are supported in a Camunda 8 Self-Managed setup. ::: | Option | Description | Default | | -------------- | ----------------------------------------------------------------------------------------------------------------------------- | --------------------------- | | dateFormat | Defines a custom date format that should be used for fetching date data from the engine (should be the same as in the engine) | yyyy-MM-dd'T'HH:mm:ss.SSSZZ | | socketTimeout | Defines the socket timeout in milliseconds, which is the timeout for waiting for data. | | | connectTimeout | Determines the timeout in milliseconds until a connection is established. | | :::note If you are using `opensearch` on AWS, the AWS SDK's [DefaultCredentialsProvider](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/auth/credentials/DefaultCredentialsProvider.html) is used for authentication. For more details on configuring credentials, refer to the [AWS SDK documentation](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/credentials-chain.html#credentials-default). ::: Helm property path prefix for these options: `camunda.data.secondary-storage.{elasticsearch|opensearch}.` | Option | Description | Default | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | numberOfShards | The number of [shards](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#_static_index_settings) used for each created index. | 1 | | numberOfReplicas | The number of shard [replicas](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#dynamic-index-settings) used for each created index. | 1 | | variableSizeThreshold | Defines a threshold for variable size. Variables exceeding this threshold are split into two properties: `FULL_VALUE` (full content, not indexed) and `VALUE` (truncated content, indexed). | 8191 | | shardsByIndexName | A map where the key is the index name and the value is the number of shards, allowing you to override the default `numberOfShards` setting for specific indices. | | | replicasByIndexName | A map where the key is the index name and the value is the number of replicas, allowing you to override the default `numberOfReplicas` setting for specific indices. | | Helm property path prefix for these options: `camunda.data.secondary-storage.{elasticsearch|opensearch}.bulk.` To avoid too many expensive requests to the Elasticsearch/OpenSearch cluster, the exporter performs batch updates by default. The size of the batch, along with how often it should be flushed (regardless of size) can be controlled by configuration. | Option | Description | Default | | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | delay | Delay, in seconds, before force flush of the current batch. This ensures that even when we have low traffic of records, we still export every once in a while. | `1` | | size | The amount of records a batch should have before we flush the batch. | `1000` | With the default configuration, the exporter will aggregate records and flush them to Elasticsearch/OpenSearch: 1. When it has aggregated 1000 records. 2. One seconds have elapsed since the last flush (regardless of how many records were aggregated). Helm property path prefix for these options: `camunda.data.secondary-storage.retention.` A retention policy can be set up to delete old data. When enabled, this creates an Index Lifecycle Management (ILM) Policy that deletes the data after the specified `minimumAge`. All index templates created by this exporter apply the created ILM Policy. | Option | Description | Default | | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | | enabled | If `true` the ILM Policy is created and applied to the index templates. | `false` | | minimumAge | Specifies how old the data must be, before the data is deleted as a duration. | `30d` | | policyName | The name of the created and applied ILM policy. | `camunda-retention-policy` | | usageMetricsMinimumAge | Specifies how old the usage metrics data must be, before the data is deleted as a duration. Applies to `camunda-usage-metric-8.8.0_` and `camunda-usage-metric-tu-8.8.0_` indices. | `730d` | | usageMetricsPolicyName | The name of the created and applied usage metrics ILM policy. | `camunda-usage-metrics-retention-policy` | | applyPolicyJobInterval | The interval at which the ILM policy is periodically applied to all historical indices (starting from version 8.8.1). | `PT1H` | :::note The duration can be specified in days `d`, hours `h`, minutes `m`, seconds `s`, milliseconds `ms`, and/or nanoseconds `nanos`. ::: Helm property path prefix for these options: `camunda.data.secondary-storage.{elasticsearch|opensearch}.history.` For example, `rolloverInterval` maps to: `camunda.data.secondary-storage.{elasticsearch|opensearch}.history.rollover-interval`. To keep the main runtime index performant, documents are periodically moved into historical indices. The history can be configured as follows: | Option | Description | Default | | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | | elsRolloverDateFormat | Defines how date values are formatted for historical indices using Java DateTimeFormatter syntax. If no format is specified, the first date format defined in the field mapping is used. | `date` | | rolloverInterval | The rollover period before an active index is rolled over. This means that `rolloverInterval` is the time gap between updates of historical indexes, therefore for an index `index-abc` and a `rolloverInterval` of 7 days (`7d`), we will have the historical indexes `index-abc-2025-01-01`, `index-abc-2025-01-08`, and so on. The `elsRolloverDateFormat` must have sufficient resolution to compute the `rolloverInterval`. For example, if the `rolloverInterval` is `1h`, then the `elsRolloverDateFormat` should be `yyyy-MM-dd-HH`. Additionally, `rolloverInterval` cannot use seconds (`s`) or minutes (`m`) as units. | `1d` | | usageMetricsRolloverInterval | The rollover period for usage metric indices before an active index is rolled over. Behaves the same way as the `rolloverInterval` but applies only to `usage-metric`, `usage-metric-tu` indices and only values of `1-4w` and `1M` are allowed. | `1M` | | rolloverBatchSize | Maximum number of process instances selected for archiving per run. Defaults to 500 when `archiveByIdEnabled` is `true`, and 100 when `false`. Set to 500 or higher when using `archiveByIdEnabled` to maintain a healthy pipeline of data. | `500` | | archiveByIdEnabled | When `true`, archiving moves documents in small, targeted batches instead of attempting to move all relevant records in a single attempt. This incrementally continues until all documents for the selected process instances have been archived to the relevant dated indices. There are no changes to the core archiving concept - data is still moved to the same dated destination indices. When archiving by ID, archiving is executed in a less resource-intensive manner, which improves stability. | `true` | | reindexBatchSize | Number of individual Elasticsearch/OpenSearch documents archived in each targeted batch when `archiveByIdEnabled` is `true`. | `2500` | | waitPeriodBeforeArchiving | Grace period during which completed process instances are excluded from archiving. For example, with a value of `1h`, any process instances completed within the last hour will not be archived. | `1h` | | delayBetweenRuns | Time in milliseconds between archiving runs for completed process instances. | `2000` | | maxDelayBetweenRuns | The maximum delay between archive runs when using an exponential backoff strategy in case of unsuccessful archiving attempts. | `60000` | | processInstanceEnabled | If `true`, enables the archiving of the completed process instances and their related objects. | `true` | | processInstanceRetentionMode | Controls the retention behavior for process instance data in secondary storage. Allowed values: `PI_HIERARCHY`, `PI_HIERARCHY_IGNORE_LEGACY`, `PI`. | `PI_HIERARCHY` | | retention | Refer to [Retention](./camunda-exporter.md?configuration=retention#options) for retention configuration options. | | Helm property path prefix for this Tasklist-specific export option: `camunda.data.secondary-storage.{elasticsearch|opensearch}.` | Option | Description | Default | | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | skipVariableWriteWithoutUserTasks | If `true`, the exporter skips writing task variable data to the `tasklist-task` index for process definitions that do not contain any user task flow node instances. This reduces index size and improves write and read performance by avoiding the persistence of task data that is never queried through Tasklist. The exporter uses the process cache to determine whether a deployed process definition contains at least one user task. | `false` | :::warning Process instance migration limitation When `skipVariableWriteWithoutUserTasks` is enabled, task variable data is not exported for process definitions without user tasks. If a process instance is later [migrated](/components/concepts/process-instance-migration.md) to a process definition version that **does** contain user tasks, variable data that was previously skipped is **not** retroactively backfilled. As a result, process-level variable filters in task search only match process instances that were created on a process definition version that already contained user tasks (that is, on or after the version introducing user tasks). For migrated instances, only variables exported **after** the migration (from new variable update events) appear in task search results. ::: Helm property path prefix for these options: `camunda.data.secondary-storage.{elasticsearch|opensearch}.batch-operations.` Other miscellaneous properties: | Option | Description | Default | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | batchOperation.exportItemsOnCreation | Defines whether the pending items of a started batch operation should be exported from the beginning. For very large batch operations involving more than 100,000 process instances, this can cause temporary performance issues due to the high volume of document insertions. If set to `false`, the "has pending batch operations" spinner in the Operate UI will not function properly. | `true` | ## Example Here is an example configuration of the exporter: ```yaml --- exporters: # Camunda Exporter ---------- # An example configuration for the camunda exporter: # # These setting can also be overridden using the environment variables "ZEEBE_BROKER_EXPORTERS_CAMUNDAEXPORTER_..." # To convert a YAML formatted variable to an environment variable, start with the top-level property and separate every nested property with an underscore (_). # For example, the property "zeebe.broker.exporters.camundaexporter.args.index.numberOfShards" would be converted to "ZEEBE_BROKER_EXPORTERS_CAMUNDAEXPORTER_ARGS_INDEX_NUMBEROFSHARDS". # camundaexporter: args: connect: dateFormat: yyyy-MM-dd'T'HH:mm:ss.SSSZZ socketTimeout: 1000 connectTimeout: 1000 bulk: delay: 5 size: 1000 index: numberOfShards: 3 numberOfReplicas: 0 history: elsRolloverDateFormat: "date" rolloverInterval: "1d" rolloverBatchSize: 500 archiveByIdEnabled: true reindexBatchSize: 2500 waitPeriodBeforeArchiving: "1h" delayBetweenRuns: 2000 maxDelayBetweenRuns: 60000 processInstanceEnabled: true retention: enabled: false minimumAge: 30d policyName: camunda-retention-policy usageMetricsMinimumAge: 730d usageMetricsPolicyName: camunda-usage-metrics-retention-policy applyPolicyJobInterval: PT1H batchOperation: exportItemsOnCreation: true skipVariableWriteWithoutUserTasks: false createSchema: true ``` --- ## Elasticsearch exporter :::note For supported Elasticsearch versions in Camunda 8 Self-Managed, see [supported environments](/reference/supported-environments.md#camunda-8-self-managed). Starting with Camunda 8.8, Camunda uses the [Camunda exporter](/self-managed/components/orchestration-cluster/zeebe/exporters/camunda-exporter.md) to consume new records. Records from 8.7 and earlier are consumed only during migration. The Elasticsearch and OpenSearch exporters remain fully usable after migration (for example, for existing setups, Optimize, or other custom use cases). Their functionality is not limited to the migration period. From 8.9 onward, the Elasticsearch exporter also supports Optimize-focused export filters (for example, variable-name filters, variable-type filters, BPMN process include/exclude, and an Optimize mode flag). For Optimize-specific guidance and recommended settings, see [Camunda 8 system configuration](../../../optimize/configuration/system-configuration-platform-8.md). ::: The Zeebe Elasticsearch exporter acts as a bridge between [Zeebe](https://camunda.com/platform/zeebe/) and [Elasticsearch](https://www.elastic.co/products/elasticsearch) by exporting records written to Zeebe streams as documents into several indices. ## Concept The exporter operates on the idea that it should perform as little as possible on the Zeebe side of things. In other words, you can think of the indexes into which the records are exported as a staging data warehouse. Any enrichment or transformation on the exported data should be performed by your own ETL jobs. When configured to do so, the exporter will automatically create an index per record value type (see the value type in the Zeebe protocol). Each of these indexes has a corresponding pre-defined mapping to facilitate data ingestion for your own ETL jobs. You can find those as templates in [the resources folder of the exporter's source code](https://github.com/camunda/camunda/tree/main/zeebe/exporters/elasticsearch-exporter/src/main/resources). :::note The indexes are created as required, and will not be created twice if they already exist. However, once disabled, they will not be deleted (that is up to the administrator.) Similarly, data is never deleted by the exporter, and must be deleted by the administrator when it is safe to do so. A [retention](#retention) policy can be configured to automatically delete data after a certain number of days. ::: ## Configuration :::note As the exporter is packaged with Zeebe, it is not necessary to specify a `jarPath`. ::: The exporter can be enabled by configuring it with the `classpath` in the broker settings. For a Spring Boot application or Camunda 8 with unified configuration: **Application config (YAML):** ```yaml camunda: data: exporters: elasticsearch: className: io.camunda.zeebe.exporter.ElasticsearchExporter args: # Refer to the table below for the available args options ``` **Environment variables:** Set environment variables in the format `CAMUNDA_DATA_EXPORTERS_ELASTICSEARCH_...` (for example, `CAMUNDA_DATA_EXPORTERS_ELASTICSEARCH_URL`). **Helm:** Add the same configuration under `orchestration.configuration` in your `values.yaml` file. :::warning Do not configure both legacy (`zeebe.broker.exporters.*`) and unified (`camunda.data.exporters.*`) exporter properties at the same time. Exporter properties are a breaking-change mapping in unified configuration, and the application fails to start until legacy properties are removed. ::: The exporter can be configured by providing `args`. The table below explains all the different options, and the default values for these options: | Option | Description | Default | | ----------------------- | ---------------------------------------------------------------------------------------- | ----------------------- | | url | Valid URLs as comma-separated string. | `http://localhost:9200` | | request-timeout-ms | Request timeout (in ms) for Elasticsearch. client | `30000` | | index | Refer to [index](#index) for the index configuration options. | | | bulk | Refer to [bulk](#bulk) for the bulk configuration options. | | | retention | Refer to [retention](#retention) for the retention configuration options. | | | authentication | Refer to [authentication](#authentication) for the authentication configuration options. | | | include-enabled-records | If `true` all enabled record types will be exported. | `false` | In most cases, you will not be interested in exporting every single record produced by a Zeebe cluster, but rather only a subset of them. This can also be configured to limit the kinds of records being exported (for example, only events, no commands), and the value type of these records (for example, only job and process values). For Optimize-specific filtering, see [Optimize export filtering](/self-managed/components/optimize/configuration/optimize-export-filtering.md). | Option | Description | Default | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- | | bpmnProcessIdExclusion | Excludes all records with these BPMN process IDs. Exclusion wins over inclusion. | `[]` | | bpmnProcessIdInclusion | Restricts exported records to these BPMN process IDs. If empty, all processes are included. Value types without a `bpmnProcessId` (such as `DEPLOYMENT`, `DECISION`) are not affected by this filter. | `[]` | | checkpoint | If `true` records related to checkpoints will be exported. | `false` | | command | If `true` command records will be exported. | `false` | | command-distribution | If `true` records related to command distributions will be exported. | `true` | | create-template | If `true` missing indexes will be created automatically. | `true` | | decision | If `true` records related to decisions will be exported. | `true` | | decision-evaluation | If `true` records related to decision evaluations will be exported. | `true` | | decision-requirements | If `true` records related to decisionRequirements will be exported. | `true` | | deployment | If `true` records related to deployments will be exported. | `true` | | deployment-distribution | If `true` records related to deployment distributions will be exported. | `true` | | error | If `true` records related to errors will be exported. | `true` | | escalation | If `true` records related to escalations will be exported. | `true` | | event | If `true` event records will be exported. | `true` | | exportLocalVariablesEnabled | If `false`, local (child-scope) variables are not exported. Root variables are scoped to the process instance; local variables are scoped to sub-processes, call activities, and tasks. When all scope-specific lists are empty, global `variableName*` and `variableValueType*` filters apply to all variables. When any scope-specific list is non-empty, that scope uses both its own filters and the global filters. | `true` | | form | If `true` records related to forms will be exported. | `true` | | incident | If `true` records related to incidents will be exported. | `true` | | index-suffix-date-pattern | This suffix will be appended to every index created by the exporter; The pattern is based on the Java [DateTimeFormatter](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/time/format/DateTimeFormatter.html) and supports the same syntax. This is useful when indexes should be created in a different interval, like hourly instead of daily. | `"yyyy-MM-dd"` | | job | If `true` records related to jobs will be exported. | `false` | | job-batch | If `true` records related to job batches will be exported. | `false` | | localVariableNameExclusionEndWith | Excludes local variables whose names end with these suffixes. Exclusion wins over inclusion. | `[]` | | localVariableNameExclusionExact | Excludes local variables whose names exactly match. Exclusion wins over inclusion. | `[]` | | localVariableNameExclusionStartWith | Excludes local variables whose names start with these prefixes. Exclusion wins over inclusion. | `[]` | | localVariableNameInclusionEndWith | Includes local variables whose names end with these suffixes. If empty, no local name inclusion filter applies. | `[]` | | localVariableNameInclusionExact | Includes local variables whose names exactly match. If empty, no local name inclusion filter applies. | `[]` | | localVariableNameInclusionStartWith | Includes local variables whose names start with these prefixes. If empty, no local name inclusion filter applies. | `[]` | | localVariableValueTypeExclusion | Excludes local variables of these inferred JSON types. Exclusion wins over inclusion. | `[]` | | localVariableValueTypeInclusion | Restricts exported local variables to these inferred JSON types (`String`, `Number`, `Boolean`, `Object`, `Null`). | `[]` | | message | If `true` records related to messages will be exported. | `true` | | message-batch | If `true` records related to message batches will be exported. | `false` | | message-start-event-subscription | If `true` records related to message start event subscriptions will be exported. | `true` | | message-subscription | If `true` records related to message subscriptions will be exported. | `true` | | number-of-replicas | The number of shard [replicas](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#dynamic-index-settings) used for each new record index created. | 1 | | number-of-shards | The number of [shards](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#_static_index_settings) used for each new record index created. | varies | | optimizeModeEnabled | If `true`, restricts exported record types to those consumed by Optimize. Other value types are dropped. Use only when these indices are dedicated to Optimize. | `true` | | prefix | This prefix will be appended to every index created by the exporter; must not contain `_` (underscore). | zeebe-record | | process | If `true` records related to processes will be exported. | `true` | | process-event | If `true` records related to process events will be exported. | `false` | | process-instance | If `true` records related to process instances will be exported. | `true` | | process-instance-batch | If `true` records related to process instances batches will be exported. | `false` | | process-instance-creation | If `true` records related to process instance creations will be exported. | `true` | | process-instance-migration | If `true` records related to process instance migrations will be exported. | `true` | | process-instance-modification | If `true` records related to process instance modifications will be exported. | `true` | | process-message-subscription | If `true` records related to process message subscriptions will be exported. | `true` | | rejection | If `true` rejection records will be exported. | `false` | | resource-deletion | If `true` records related to resource deletions will be exported. | `true` | | rootVariableNameExclusionEndWith | Excludes root variables whose names end with these suffixes. Exclusion wins over inclusion. | `[]` | | rootVariableNameExclusionExact | Excludes root variables whose names exactly match. Exclusion wins over inclusion. | `[]` | | rootVariableNameExclusionStartWith | Excludes root variables whose names start with these prefixes. Exclusion wins over inclusion. | `[]` | | rootVariableNameInclusionEndWith | Includes root variables whose names end with these suffixes. If empty, no root name inclusion filter applies. | `[]` | | rootVariableNameInclusionExact | Includes root variables whose names exactly match. If empty, no root name inclusion filter applies. | `[]` | | rootVariableNameInclusionStartWith | Includes root variables whose names start with these prefixes. If empty, no root name inclusion filter applies. | `[]` | | rootVariableValueTypeExclusion | Excludes root variables of these inferred JSON types. Exclusion wins over inclusion. | `[]` | | rootVariableValueTypeInclusion | Restricts exported root variables to these inferred JSON types (`String`, `Number`, `Boolean`, `Object`, `Null`). | `[]` | | signal | If `true` records related to signals will be exported. | `true` | | signal-subscription | If `true` records related to signal subscriptions will be exported. | `true` | | templatePriority | Priority of the index templates created for the `zeebe-record-*` indices. Raise it when other templates match the same pattern, so the exporter's template wins. Must be `>= 0`. | `20` | | timer | If `true` records related to timers will be exported. | `true` | | user-task | If `true` records related to user tasks will be exported. | `true` | | variable | If `true` records related to variables will be exported. | `true` | | variable-document | If `true` records related to variable documents will be exported. | `true` | | variableNameExclusionEndWith | Excludes variables whose names end with these suffixes. Exclusion wins over inclusion. | `[]` | | variableNameExclusionExact | Excludes variables whose names exactly match. Exclusion wins over inclusion. | `[]` | | variableNameExclusionStartWith | Excludes variables whose names start with these prefixes. Exclusion wins over inclusion. | `[]` | | variableNameInclusionEndWith | Includes variables whose names end with these suffixes. If empty, no name inclusion filter applies. | `[]` | | variableNameInclusionExact | Includes variables whose names exactly match. If empty, no name inclusion filter applies. Inclusion rules are evaluated first; if a variable also matches an exclusion rule, exclusion takes precedence. | `[]` | | variableNameInclusionStartWith | Includes variables whose names start with these prefixes. If empty, no name inclusion filter applies. | `[]` | | variableValueTypeExclusion | Excludes variables of these inferred JSON types. Exclusion wins over inclusion. | `[]` | | variableValueTypeInclusion | Restricts exported variables to these inferred JSON types (`String`, `Number`, `Boolean`, `Object`, `Null`). If empty, all types are included. Use to drop large object or array payloads at export time. | `[]` | :::note The number of shards varies by index template. Most indices use `1` shard by default. The following high-volume index templates default to `5` shards: - `zeebe-record-job` - `zeebe-record-process-instance` The exporter routes every record by its partition ID, so an index only ever sees as many distinct routing values as your cluster has partitions. Five is the smallest shard count that places the records of a three-partition cluster on three distinct shards. The number of shards is a static index setting, so only newly created dated indices use these defaults. An existing index keeps the shard count it was created with. If you set `number-of-shards`, it overrides the template defaults for all indices, including the two listed above. ::: To avoid too many expensive requests to the Elasticsearch cluster, the exporter performs batch updates by default. The size of the batch, along with how often it should be flushed (regardless of size) can be controlled by configuration. | Option | Description | Default | | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | | delay | Delay, in seconds, before force flush of the current batch. This ensures that even when we have low traffic of records, we still export every once in a while. | `5` | | size | The amount of records a batch should have before we flush the batch | `1000` | | memory-limit | The size of the batch, in bytes, before we flush the batch | `10485760` (10 MB) | With the default configuration, the exporter will aggregate records and flush them to Elasticsearch: 1. When it has aggregated 1000 records. 2. When the batch memory size exceeds 10 MB. 3. Five seconds have elapsed since the last flush (regardless of how many records were aggregated). A retention policy can be set up to delete old data. When enabled, this creates an Index Lifecycle Management (ILM) Policy that deletes the data after the specified `minimumAge`. All index templates created by this exporter apply the created ILM Policy. | Option | Description | Default | | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | | enabled | If `true` the ILM Policy is created and applied to the index templates | `false` | | minimum-age | Specifies how old the data must be, before the data is deleted as a duration | `30d` | | policy-name | The name of the created and applied ILM policy | `zeebe-record-retention-policy` | | manage-policy | If `true` the exporter creates, updates, and removes the ILM policy on its own. Set to `false` to leave an externally managed policy untouched (the exporter neither creates nor removes it) | `true` | :::note The duration can be specified in days `d`, hours `h`, minutes `m`, seconds `s`, milliseconds `ms`, and/or nanoseconds `nanos`. ::: :::note Externally managed policy When `manage-policy: false`, the exporter still attaches the policy named by `policy-name` (default `zeebe-record-retention-policy`) to the indices it creates. The policy must already exist in your Elasticsearch cluster under that name when the exporter starts. If the policy does not exist, Elasticsearch silently accepts the dangling `index.lifecycle.name` setting. The exporter starts successfully, but ILM logs `policy_not_found` warnings on each cycle and skips retention actions until the policy is created. Once you create the policy under the configured name, ILM picks it up retroactively on its next cycle. ::: Providing these authentication options will enable Basic authentication on the exporter. | Option | Description | Default | | -------- | ----------------------------- | ------- | | username | Username used to authenticate | N/A | | password | Password used to authenticate | N/A | ## Example The following is an example configuration of the exporter: ```yaml --- camunda: data: exporters: elasticsearch: # Elasticsearch exporter ---------- # An example configuration for the elasticsearch exporter: # # These settings can also be overridden using environment variables "CAMUNDA_DATA_EXPORTERS_ELASTICSEARCH_..." # className: io.camunda.zeebe.exporter.ElasticsearchExporter args: # A comma separated list of URLs pointing to the Elasticsearch instances you wish to export to. # For example, if you want to connect to multiple nodes for redundancy: # url: http://localhost:9200,http://localhost:9201 url: http://localhost:9200 bulk: delay: 5 size: 1000 memoryLimit: 10485760 retention: enabled: true minimumAge: 30d policyName: zeebe-records-retention-policy authentication: username: elastic password: changeme index: prefix: zeebe-record createTemplate: true # Priority of the index templates created by the exporter. Raise it if other templates match the same index pattern. templatePriority: 20 indexSuffixDatePattern: "yyyy-MM-dd" command: false event: true rejection: false commandDistribution: true decisionRequirements: true decision: true decisionEvaluation: true deployment: true deploymentDistribution: true error: true escalation: true form: true incident: true job: true jobBatch: false message: true messageStartSubscription: true messageSubscription: true process: true processEvent: false processInstance: true processInstanceCreation: true processInstanceMigration: true processInstanceModification: true processMessageSubscription: true resourceDeletion: true signal: true signalSubscription: true timer: true userTask: true variable: true variableDocument: true ``` ## Self-signed certificates The Zeebe Elasticsearch exporter does not [currently support](https://github.com/camunda/camunda/issues/9839) connecting to Elasticsearch using self-signed certificates. If you must use self-signed certificates, it is possible to build your own trust store and have the application use it. In this case, it is recommended to create a new custom trust store based on the default one. This way, it will also be able to verify certificates signed using trusted root certificate authorities. 1. First, create a new custom trust store which contains the same data as the default one, using PKCS12 format. To do so, find the location of the default `cacerts` trust store: - On Linux systems, find it at `$JAVA_HOME/lib/security/cacerts`. - For macOS, find it under `$(/usr/libexec/java_home)/jre/lib/security/cacerts`. Once you have the right location, for example, `$JAVA_HOME/lib/security/cacerts`, run the following to create a new trust store: ```sh keytool -importkeystore -srckeystore $JAVA_HOME/lib/security/cacerts -destkeystore zeebeTrustStore.jks -srcstoretype PKCS12 -deststoretype JKS ``` Set any password, so long as it's at least 6 characters. 2. Add your custom certificate to the new trust store. For example, if your custom certificate is located at `/tmp/myCustomCertificate.pem`: ```sh keytool -import -alias MyCustomCertificate -keystore zeebeTrustStore.jks -file /tmp/myCustomCertificate.pem ``` :::note Replace the `-file` parameter with the actual path to your certificate, and make sure to replace the `-alias` parameter with something descriptive, like `WebServerCertificate`. ::: When prompted to trust the certificate, make sure to answer **yes**. 3. Update the application to use this trust store. First, make sure the file is readable by the application. For example, on Unix systems, run: ```sh chmod a+r zeebeTrustStore.jks ``` Then, specify the following properties when running the application: - `javax.net.ssl.trustStore`: must be set to the path of your custom trust store. - `javax.net.ssl.trustStorePassword`: set to your trust store password. The following example uses a trust store location of `/tmp/zeebeTrustStore.jks`, and a password of `changeme`. When using the official distribution (whether Docker image or the bundled shell scripts), these propertiescan be provided using the following environment variable: ```sh JAVA_OPTS="-Djavax.net.ssl.trustStore=/tmp/zeebeTrustStore.jks -Djavax.net.ssl.trustStorePassword=changeme ${JAVA_OPTS}" ``` :::warning If you're using containers, you will need to mount the trust store to the container such that it can be found by the `java` process. This will depend on your deployment method (for example, Helm chart, Docker Compose). The simplest way is to build a custom image which already contains your trust store, and specifies the environment variable. ::: ## Legacy Zeebe records and Optimize filters With the introduction of the Camunda exporter, the Elasticsearch and OpenSearch exporters no longer export all record types by default. Instead, they emit only the record value types and intents required by Optimize. To export additional record types, enable the [`include-enabled-records`](#configuration) configuration property. When you enable exporter-side filters (`optimize-mode-enabled`, `variable-name`, `variable-type`, or `bpmn-process-id`), filtering applies only to newly produced records. Existing documents in Elasticsearch or OpenSearch are not rewritten. :::info Upgrade notes **Upgrading 8.8 to 8.9:** Exporter filtering behavior may affect data completeness. **Upgrading 8.9 to 8.10:** `index.optimizeModeEnabled` defaults to `true` (previously `false`) and `index.job` defaults to `false` (previously `true`). When `index.optimizeModeEnabled` is `true`, Optimize mode controls which record value types are exported and the individual `job` flag has no effect. If you use these exporters for purposes beyond Optimize and need record value types that Optimize mode does not cover, set `index.optimizeModeEnabled: false` and enable the [`include-enabled-records`](#configuration) configuration property to export the record value types you need. For more information, see the [Camunda 8 system configuration](../../../optimize/configuration/system-configuration-platform-8.md). ::: --- ## Exporters The Orchestration Cluster comes packaged with built-in exporters: - [Analytics Exporter](analytics-exporter.md) - [Camunda Exporter](camunda-exporter.md) - [RDBMS Exporter](rdbms-exporter.md) - [Elasticsearch](elasticsearch-exporter.md) - [OpenSearch](opensearch-exporter.md) This section of the docs explains how these exporters can be [installed](install-zeebe-exporters.md) and configured. For a general overview on the exporters concept, refer to our [exporters concept](../../../../concepts/exporters.md) page. --- ## Install Zeebe exporters Camunda 8 Self-Managed Helm chart supports the addition of Zeebe exporters by using `initContainer`. The following is an example to install the community-supported Zeebe [Hazelcast](https://github.com/camunda-community-hub/zeebe-hazelcast-exporter) exporter. ```yaml extraInitContainers: - name: init-exporters-hazelcast image: busybox:1.35 command: ["/bin/sh", "-c"] args: [ "wget --no-check-certificate https://repo1.maven.org/maven2/io/zeebe/hazelcast/zeebe-hazelcast-exporter/0.8.0-alpha1/zeebe-hazelcast-exporter-0.8.0-alpha1-jar-with-dependencies.jar -O /exporters/zeebe-hazelcast-exporter.jar; ls -al /exporters", ] volumeMounts: - name: exporters mountPath: /exporters/ env: - name: ZEEBE_BROKER_EXPORTERS_HAZELCAST_JARPATH value: /exporters/zeebe-hazelcast-exporter.jar - name: ZEEBE_BROKER_EXPORTERS_HAZELCAST_CLASSNAME value: io.zeebe.hazelcast.exporter.HazelcastExporter - name: ZEEBE_HAZELCAST_REMOTE_ADDRESS value: "{{ .Release.Name }}-hazelcast" ``` This example is downloading the exporters' JAR from a URL and adding the JAR to the `exporters` directory, which will be scanned for JARs and added to the Zeebe Broker classpath. Then, with `environment variables`, you can configure the exporter parameters. :::note During startup, the Zeebe pods will copy the exporters located in `/exporters/` to `/usr/local/zeebe/exporters/`. If the pods have a read-only root file system, a writable volume must be mounted to `/usr/local/zeebe/exporters/`. ::: --- ## OpenSearch exporter :::note For supported OpenSearch versions in Camunda 8 Self-Managed, see [Supported Environments](../../../../../reference/supported-environments.md#camunda-8-self-managed). Starting with Camunda 8.8, Camunda uses the [Camunda exporter](/self-managed/components/orchestration-cluster/zeebe/exporters/camunda-exporter.md) to consume new records. Records from 8.7 and earlier are consumed only during migration. The Elasticsearch and OpenSearch exporters remain fully usable after migration (for example, for existing setups, Optimize, or other custom use cases). Their functionality is not limited to the migration period. From 8.9 onward, the OpenSearch exporter also supports Optimize-focused export filters (for example, variable-name filters, variable-type filters, BPMN process include/exclude, and an Optimize mode flag). For Optimize-specific guidance and recommended settings, see [Camunda 8 system configuration](../../../optimize/configuration/system-configuration-platform-8.md). ::: The Zeebe OpenSearch exporter acts as a bridge between [Zeebe](https://camunda.com/platform/zeebe/) and [OpenSearch](https://opensearch.org) by exporting records written to Zeebe streams as documents into several indices. ## Concept The exporter operates on the idea that it should perform as little as possible on the Zeebe side of things. In other words, you can think of the indexes into which the records are exported as a staging data warehouse. Any enrichment or transformation on the exported data should be performed by your own ETL jobs. When configured to do so, the exporter will automatically create an index per record value type (see the value type in the Zeebe protocol). Each of these indexes has a corresponding pre-defined mapping to facilitate data ingestion for your own ETL jobs. You can find those as templates in this module's resources folder. :::note The indexes are created as required, and will not be created twice if they already exist. However, once disabled, they will not be deleted (that is up to the administrator). Similarly, data is never deleted by the exporter, and must be deleted by the administrator when it is safe to do so. ::: ## Configuration :::note As the exporter is packaged with Zeebe, it is not necessary to specify a `jarPath`. ::: The exporter can be enabled by configuring it with the classpath in the broker settings. For a Spring Boot application or Camunda 8 with unified configuration: **Application config (YAML):** ```yaml camunda: data: exporters: opensearch: className: io.camunda.zeebe.exporter.opensearch.OpensearchExporter args: # Refer to the table below for the available args options ``` **Environment variables:** Set environment variables in the format `CAMUNDA_DATA_EXPORTERS_OPENSEARCH_...` (for example, `CAMUNDA_DATA_EXPORTERS_OPENSEARCH_URL`). **Helm:** Add the same configuration under `orchestration.configuration` in your `values.yaml` file. :::warning Do not configure both legacy (`zeebe.broker.exporters.*`) and unified (`camunda.data.exporters.*`) exporter properties at the same time. Exporter properties are a breaking-change mapping in unified configuration, and the application fails to start until legacy properties are removed. ::: The exporter can be configured by providing `args`. The table below explains all the different options, and the default values for these options: | Option | Description | Default | | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | | url | Valid URLs as a comma-separated string. | `http://localhost:9200` | | request-timeout-ms | Request timeout (in ms) for the OpenSearch client. | `30000` | | index | Refer to [index](#index) for index configuration options, including record/value-type switches, Optimize-focused filters, and the Optimize mode flag. | | | bulk | Refer to [bulk](#bulk) for the bulk configuration options. | | | retention | Refer to [retention](#retention) for the retention configuration options. | | | authentication | Refer to [authentication](#authentication) for the authentication configuration options. | | | aws | Refer to [AWS](#aws) for the AWS configuration options. | | | include-enabled-records | If `true`, exports all enabled record types configured under `index`. If `optimizeModeEnabled` is `true`, Optimize mode takes precedence. Use mainly for migration or compatibility scenarios. | `false` | In most cases, you will not be interested in exporting every single record produced by a Zeebe cluster, but rather only a subset of them. This can also be configured to limit the kinds of records exported (for example, only events, no commands), and the value type of these records (for example, only job and process values). For Optimize-specific filtering, see [Optimize export filtering](/self-managed/components/optimize/configuration/optimize-export-filtering.md). | Option | Description | Default | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------ | | bpmnProcessIdExclusion | Excludes all records with these BPMN process IDs. Exclusion wins over inclusion. | `[]` | | bpmnProcessIdInclusion | Restricts exported records to these BPMN process IDs. If empty, all processes are included. Value types without a `bpmnProcessId` (such as `DEPLOYMENT`, `DECISION`) are not affected by this filter. | `[]` | | checkpoint | If `true` records related to checkpoints will be exported. | `false` | | command | If `true` command records will be exported. | `false` | | command-distribution | If `true` records related to command distributions will be exported. | `true` | | create-template | If `true` missing indexes will be created automatically. | `true` | | decision | If `true` records related to decisions will be exported. | `true` | | decision-evaluation | If `true` records related to decision evaluations will be exported. | `true` | | decision-requirements | If `true` records related to decision requirements will be exported. | `true` | | deployment | If `true` records related to deployments will be exported. | `true` | | deployment-distribution | If `true` records related to deployment distributions will be exported. | `true` | | error | If `true` records related to errors will be exported. | `true` | | escalation | If `true` records related to escalations will be exported. | `true` | | event | If `true` event records will be exported. | `true` | | exportLocalVariablesEnabled | If `false`, local (child-scope) variables are not exported. Root variables are scoped to the process instance; local variables are scoped to sub-processes, call activities, and tasks. When all scope-specific lists are empty, global `variableName*` and `variableValueType*` filters apply to all variables. When any scope-specific list is non-empty, that scope uses both its own filters and the global filters. | `true` | | form | If `true` records related to forms will be exported. | `true` | | incident | If `true` records related to incidents will be exported. | `true` | | job | If `true` records related to jobs will be exported. | `false` | | job-batch | If `true` records related to job batches will be exported. | `false` | | localVariableNameExclusionEndWith | Excludes local variables whose names end with these suffixes. Exclusion wins over inclusion. | `[]` | | localVariableNameExclusionExact | Excludes local variables whose names exactly match. Exclusion wins over inclusion. | `[]` | | localVariableNameExclusionStartWith | Excludes local variables whose names start with these prefixes. Exclusion wins over inclusion. | `[]` | | localVariableNameInclusionEndWith | Includes local variables whose names end with these suffixes. If empty, no local name inclusion filter applies. | `[]` | | localVariableNameInclusionExact | Includes local variables whose names exactly match. If empty, no local name inclusion filter applies. | `[]` | | localVariableNameInclusionStartWith | Includes local variables whose names start with these prefixes. If empty, no local name inclusion filter applies. | `[]` | | localVariableValueTypeExclusion | Excludes local variables of these inferred JSON types. Exclusion wins over inclusion. | `[]` | | localVariableValueTypeInclusion | Restricts exported local variables to these inferred JSON types (`String`, `Number`, `Boolean`, `Object`, `Null`). | `[]` | | message | If `true` records related to messages will be exported. | `true` | | message-batch | If `true` records related to message batches will be exported. | `false` | | message-start-event-subscription | If `true` records related to message start event subscriptions will be exported. | `true` | | message-subscription | If `true` records related to message subscriptions will be exported. | `true` | | number-of-replicas | The number of shard [replicas](https://opensearch.org/docs/latest/install-and-configure/configuring-opensearch/index-settings/#dynamic-index-level-index-settings) used for each new record index created. | 1 | | number-of-shards | The number of [shards](https://opensearch.org/docs/latest/install-and-configure/configuring-opensearch/index-settings/#static-index-level-index-settings) used for each new record index created. | varies | | optimizeModeEnabled | If `true`, restricts exported record types to those consumed by Optimize. Other value types are dropped. Use only when these indices are dedicated to Optimize. | `true` | | prefix | This prefix will be appended to every index created by the exporter; must not contain `_` (underscore). | zeebe-record | | priority | Priority of the index templates created for the `zeebe-record-*` indices. Raise it when other templates match the same pattern, so the exporter's template wins. Must be `>= 0`. | `20` | | process | If `true` records related to processes will be exported. | `true` | | process-event | If `true` records related to process events will be exported. | `false` | | process-instance | If `true` records related to process instances will be exported. | `true` | | process-instance-batch | If `true` records related to process instances batches will be exported. | `false` | | process-instance-creation | If `true` records related to process instance creations will be exported. | `true` | | process-instance-migration | If `true` records related to process instance migrations will be exported. | `true` | | process-instance-modification | If `true` records related to process instance modifications will be exported. | `true` | | process-message-subscription | If `true` records related to process message subscriptions will be exported. | `true` | | rejection | If `true` rejection records will be exported. | `false` | | resource-deletion | If `true` records related to resource deletions will be exported. | `true` | | rootVariableNameExclusionEndWith | Excludes root variables whose names end with these suffixes. Exclusion wins over inclusion. | `[]` | | rootVariableNameExclusionExact | Excludes root variables whose names exactly match. Exclusion wins over inclusion. | `[]` | | rootVariableNameExclusionStartWith | Excludes root variables whose names start with these prefixes. Exclusion wins over inclusion. | `[]` | | rootVariableNameInclusionEndWith | Includes root variables whose names end with these suffixes. If empty, no root name inclusion filter applies. | `[]` | | rootVariableNameInclusionExact | Includes root variables whose names exactly match. If empty, no root name inclusion filter applies. | `[]` | | rootVariableNameInclusionStartWith | Includes root variables whose names start with these prefixes. If empty, no root name inclusion filter applies. | `[]` | | rootVariableValueTypeExclusion | Excludes root variables of these inferred JSON types. Exclusion wins over inclusion. | `[]` | | rootVariableValueTypeInclusion | Restricts exported root variables to these inferred JSON types (`String`, `Number`, `Boolean`, `Object`, `Null`). | `[]` | | signal | If `true` records related to signals will be exported. | `true` | | signal-subscription | If `true` records related to signal subscriptions will be exported. | `true` | | timer | If `true` records related to timers will be exported. | `true` | | user-task | If `true` records related to user tasks will be exported. | `true` | | variable | If `true` records related to variables will be exported. | `true` | | variable-document | If `true` records related to variable documents will be exported. | `true` | | variableNameExclusionEndWith | Excludes variables whose names end with these suffixes. Exclusion wins over inclusion. | `[]` | | variableNameExclusionExact | Excludes variables whose names exactly match. Exclusion wins over inclusion. | `[]` | | variableNameExclusionStartWith | Excludes variables whose names start with these prefixes. Exclusion wins over inclusion. | `[]` | | variableNameInclusionEndWith | Includes variables whose names end with these suffixes. If empty, no name inclusion filter applies. | `[]` | | variableNameInclusionExact | Includes variables whose names exactly match. If empty, no name inclusion filter applies. Inclusion rules are evaluated first; if a variable also matches an exclusion rule, exclusion takes precedence. | `[]` | | variableNameInclusionStartWith | Includes variables whose names start with these prefixes. If empty, no name inclusion filter applies. | `[]` | | variableValueTypeExclusion | Excludes variables of these inferred JSON types. Exclusion wins over inclusion. | `[]` | | variableValueTypeInclusion | Restricts exported variables to these inferred JSON types (`String`, `Number`, `Boolean`, `Object`, `Null`). If empty, all types are included. Use to drop large object or array payloads at export time. | `[]` | :::note The number of shards varies by index template. Most indices use `1` shard by default. The following high-volume index templates default to `5` shards: - `zeebe-record-job` - `zeebe-record-process-instance` The exporter routes every record by its partition ID, so an index only ever sees as many distinct routing values as your cluster has partitions. Five is the smallest shard count that places the records of a three-partition cluster on three distinct shards. The number of shards is a static index setting, so only newly created dated indices use these defaults. An existing index keeps the shard count it was created with. If you set `number-of-shards`, it overrides the template defaults for all indices, including the two listed above. ::: To avoid too many expensive requests to the OpenSearch cluster, the exporter performs batch updates by default. The size of the batch, along with how often it should be flushed (regardless of size) can be controlled by configuration. | Option | Description | Default | | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | | delay | Delay, in seconds, before force flush of the current batch. This ensures that even when we have low traffic of records, we still export every once in a while. | `5` | | size | The amount of records a batch should have before we flush the batch. | `1000` | | memory-limit | The size of the batch, in bytes, before we flush the batch. | `10485760` (10 MB) | With the default configuration, the exporter would aggregate records and flush them to OpenSearch either: 1. When it has aggregated 1000 records. 2. When the batch memory size exceeds 10 MB. 3. Five seconds have elapsed since the last flush (regardless of how many records were aggregated). A retention policy can be set up to delete old data. When enabled, this creates an Index State Management (ISM) policy that deletes the data after the specified `minimumAge`. All index templates created by this exporter apply the created ISM policy. | Option | Description | Default | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | | enabled | If `true` the ISM policy is created and applied to the index templates. | `false` | | minimum-age | Specifies how old the data must be, before the data is deleted as a duration. | `30d` | | policy-name | The name of the created and applied ISM policy. | `zeebe-record-retention-policy` | | policy-description | The description of the created and applied ISM policy. | `Zeebe record retention policy` | | manage-policy | If `true` the exporter creates, updates, and removes the ISM policy on its own. Set to `false` to leave an externally managed policy untouched (the exporter neither creates nor removes it). | `true` | :::note The duration can be specified in days `d`, hours `h`, minutes `m`, seconds `s`, milliseconds `ms`, and/or nanoseconds `nanos`. ::: :::note Externally managed policy When `manage-policy: false`, the exporter still attaches the policy named by `policy-name` (default `zeebe-record-retention-policy`) to the indices it creates. The policy must already exist in your OpenSearch cluster under that name when the exporter starts. If the policy does not exist, OpenSearch's ISM `add` API rejects the call and the exporter fails to start with `OpensearchExporterException: Failed to add policy to indices`. Provision the ISM policy before starting Camunda when using `manage-policy: false`. ::: Providing these authentication options will enable Basic authentication on the exporter. | Option | Description | Default | | -------- | ----------------------------- | ------- | | username | Username used to authenticate | N/A | | password | Password used to authenticate | N/A | When running OpenSearch in AWS, you may require requests to be signed. By enabling AWS in the configuration, a request interceptor will be added to the exporter. This interceptor will take care of signing the requests. Signing requests requires credentials. These credentials are not directly configurable in the exporter. Instead, they are resolved by following the [Default Credential Provider Chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html). | Option | Description | Default | | ------------ | --------------------------------------------------------------------------------------- | -------------------------------------------------- | | enabled | Enables AWS request signing | `false` | | service-name | AWS' name of the service to where requests are made. For OpenSearch this should be `es` | `es` | | region | The region this exporter is running in | The value of the `AWS_REGION` environment variable | ## Example The following is an example configuration of the exporter: ```yaml --- camunda: data: exporters: opensearch: # Opensearch exporter ---------- # An example configuration for the opensearch exporter: # # These settings can also be overridden using environment variables "CAMUNDA_DATA_EXPORTERS_OPENSEARCH_..." className: io.camunda.zeebe.exporter.opensearch.OpensearchExporter args: # A comma separated list of URLs pointing to the Opensearch instances you wish to export to. # For example, if you want to connect to multiple nodes for redundancy: # url: http://localhost:9200,http://localhost:9201 url: http://localhost:9200 bulk: delay: 5 size: 1000 memoryLimit: 10485760 retention: enabled: true minimumAge: 30d policyName: zeebe-record-retention-policy policyDescription: Zeebe record retention policy authentication: username: opensearch password: changeme aws: enabled: true serviceName: es region: eu-west-1 index: prefix: zeebe-record createTemplate: true # Priority of the index templates created by the exporter. Raise it if other templates match the same index pattern. priority: 20 # Optimize mode (default: true) overrides the per-value-type flags below, such as job. optimizeModeEnabled: false command: false event: true rejection: false commandDistribution: true decisionRequirements: true decision: true decisionEvaluation: true deployment: true deploymentDistribution: true error: true escalation: true form: true incident: true job: true jobBatch: false message: true messageStartEventSubscription: true messageSubscription: true process: true processEvent: false processInstance: true processInstanceCreation: true processInstanceMigration: true processInstanceModification: true processMessageSubscription: true resourceDeletion: true signal: true signalSubscription: true timer: true userTask: true variable: true variableDocument: true ``` ## Self-signed certificates The Zeebe OpenSearch exporter does not [currently support](https://github.com/camunda/camunda/issues/9839) connecting to OpenSearch using self-signed certificates. If you must use self-signed certificates, it is possible to build your own trust store and have the application use it. In this case, it is recommended to create a new custom trust store based on the default one. This way, it will also be able to verify certificates signed using trusted root certificate authorities. 1. First, create a new custom trust store which contains the same data as the default one, using PKCS12 format. To do so, find the location of the default `cacerts` trust store: - On Linux systems, find it at `$JAVA_HOME/lib/security/cacerts`. - For macOS, find it under `$(/usr/libexec/java_home)/jre/lib/security/cacerts`. Once you have the right location, for example, `$JAVA_HOME/lib/security/cacerts`, run the following to create a new trust store: ```sh keytool -importkeystore -srckeystore $JAVA_HOME/lib/security/cacerts -destkeystore zeebeTrustStore.jks -srcstoretype PKCS12 -deststoretype JKS ``` Set any password, so long as it's at least 6 characters. 2. Add your custom certificate to the new trust store. For example, if your custom certificate is located at `/tmp/myCustomCertificate.pem`: ```sh keytool -import -alias MyCustomCertificate -keystore zeebeTrustStore.jks -file /tmp/myCustomCertificate.pem ``` :::note Replace the `-file` parameter with the actual path to your certificate, and make sure to replace the `-alias` parameter with something descriptive, like `WebServerCertificate`. ::: When prompted to trust the certificate, make sure to answer **yes**. 3. Update the application to use this trust store. First, make sure the file is readable by the application. For example, on Unix systems, run: ```sh chmod a+r zeebeTrustStore.jks ``` Then, specify the following properties when running the application: - `javax.net.ssl.trustStore`: must be set to the path of your custom trust store. - `javax.net.ssl.trustStorePassword`: set to your trust store password. The following example uses a trust store location of `/tmp/zeebeTrustStore.jks`, and a password of `changeme`. When using the official distribution (whether Docker image or the bundled shell scripts), these properties can be provided using the following environment variable: ```sh JAVA_OPTS="-Djavax.net.ssl.trustStore=/tmp/zeebeTrustStore.jks -Djavax.net.ssl.trustStorePassword=changeme ${JAVA_OPTS}" ``` :::warning If you're using containers, you will need to mount the trust store to the container such that it can be found by the `java` process. This will depend on your deployment method (for example, Helm chart, Docker Compose). The simplest way is to build a custom image which already contains your trust store, and specifies the environment variable. ::: ## Legacy Zeebe records and Optimize filters With the introduction of the Camunda exporter, the Elasticsearch and OpenSearch exporters no longer export all record types by default. By default, they emit only the record value types and intents required by Optimize. To export additional record types, enable the [`include-enabled-records`](#configuration) configuration property. When you enable exporter-side filters (`optimizeModeEnabled`, `variable-name`, `variable-type`, or `bpmn-process-id`), filtering applies only to newly produced records. Existing documents in Elasticsearch or OpenSearch are not rewritten. :::info Upgrade notes **Upgrading 8.8 to 8.9:** Exporter filtering behavior may affect data completeness. **Upgrading 8.9 to 8.10:** `index.optimizeModeEnabled` defaults to `true` (previously `false`) and `index.job` defaults to `false` (previously `true`). When `index.optimizeModeEnabled` is `true`, Optimize mode controls which record value types are exported and the individual `job` flag has no effect. If you use these exporters for purposes beyond Optimize and need record value types that Optimize mode does not cover, set `index.optimizeModeEnabled: false` and enable the [`include-enabled-records`](#configuration) configuration property to export the record value types you need. For more information, see the [Camunda 8 system configuration](../../../optimize/configuration/system-configuration-platform-8.md). ::: --- ## RDBMS Exporter The RDBMS Exporter writes Orchestration Cluster secondary storage data to relational database tables. It is enabled when secondary storage is configured as `rdbms`. ## How it works - The RDBMS Exporter consumes records from the log stream, transforming relevant records and writing them to secondary storage database tables. - Operate and Tasklist query this secondary storage data through the Orchestration Cluster APIs. ## Configuration Configure the exporter through secondary storage settings: - [RDBMS secondary storage configuration](/self-managed/concepts/databases/relational-db/configuration.md) - [Secondary storage properties](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#data---secondary-storage) --- ## Backpressure(Operations) When a broker receives a client request, it is written to the **event stream** first (see section [internal processing](/components/zeebe/technical-concepts/internal-processing.md) for details), and processed later by the stream processor. If the processing is slow or if there are many client requests in the stream, it might take too long for the processor to start processing the command. If the broker keeps accepting new requests from the client, the backlog increases and the processing latency can grow beyond an acceptable time. To avoid such problems, Zeebe employs a backpressure mechanism. When the broker receives more requests than it can process with an acceptable latency, it rejects some requests (see [technical error handling](/apis-tools/zeebe-api/technical-error-handling.md)). Alternatively, [flow control write rate limits](/self-managed/operational-guides/configure-flow-control/configure-flow-control.md) can also be used with static write rate limits or throttling. This prevents the partition from building an excessive backlog of records not exported. ### Terminology - **RTT** - Round-Trip Time, known as the time between when the request is accepted by the broker and when the response to the request is sent back to the gateway. - **Inflight count** - The number of requests accepted by the broker but the response is not yet sent. - **Limit** - Maximum number of flight requests. When the inflight count is above the limit, any new incoming request is rejected. :::note The limit and inflight count are calculated per partition. ::: ### Backpressure algorithms Zeebe uses adaptive algorithms from [concurrency-limits](https://github.com/Netflix/concurrency-limits) to dynamically calculate the limit. Configure Zeebe with one of the backpressure algorithms in the following sections. The default values can be found in the [Zeebe repo](https://github.com/camunda/camunda/blob/main/dist/src/main/config/defaults.yaml) in the `# backpressure` section. #### Fixed limit With **fixed limit**, one can configure a fixed value of the limit. Zeebe operators are recommended to evaluate the latencies observed with different values for limit. Note that with different cluster configurations, you may have to choose different limit values. #### AIMD **Additive increase/multiplicative decrease (AIMD)** calculates the limit based on the configured _requestTimeout_. When the RTT for a request is shorter than _requestTimeout_, the limit is increased by 1. When the RTT is longer than _requestTimeout_, the limit will be reduced according to the configured _backoffRatio_. #### Vegas Vegas is an adaptive limit algorithm based on TCP Vegas congestion control algorithm. Vegas estimates a base latency as the minimum observed latency. This base RTT is the expected latency when there is no load. Whenever the RTT deviates from the base RTT, a new limit is calculated based on the Vegas algorithm. Vegas allows you to configure two parameters - _alpha_ and _beta_. The values correspond to a queue size estimated by the Vegas algorithm based on the observed RTT, base RTT, and current limit. When the queue size is below _alpha_, the limit is increased. When the queue size is above _beta_, the limit is decreased. #### Gradient Gradient is an adaptive limit algorithm that dynamically calculates the limit based on observed RTT. In the gradient algorithm, the limit is adjusted based on the gradient of observed RTT and an observed minimum RTT. If gradient is less than 1, the limit is decreased. Otherwise, the limit is increased. #### Gradient2 Gradient2 is similar to Gradient, but instead of using observed minimum RTT as the base, it uses an exponentially smoothed average RTT. ## Backpressure tuning The goal of backpressure is to keep the processing latency low. The processing latency is calculated as the time between the command is written to the event stream until it is processed. To see how backpressure behaves, run a benchmark on your cluster and observe the following metrics: - `zeebe_stream_processor_latency_bucket` - `zeebe_dropped_request_count_total` - `zeebe_received_request_count_total` - `zeebe_backpressure_requests_limit` You may want to run the benchmark with different loads: 1. With low load - Where the number of requests sent per second is low. 2. With high load - Where the number of requests sent per second is above what Zeebe can process within a reasonable latency. If the value of the limit is small, the processing latency will be small, but the number of rejected requests may be high. If the value of the limit is large, fewer requests may be rejected (depending on the request rate), but the processing latency may increase. When using **fixed limit**, you can run the benchmark with different values for the limit. You can then determine a suitable value for a limit for which the processing latency (`zeebe_stream_processor_latency_bucket`) is within the desired latency. When using **AIMD**, you can configure a `requestTimeout` which corresponds to a desired latency. Note that during high load, AIMD can lead to a processing latency two times more than the configured `requestTimeout`. It is also recommended to configure a `minLimit` to prevent the limit from aggressively dropping during constant high load. When using **Vegas**, you cannot configure the backpressure to a desired latency. Instead, Vegas tries to keep the RTT as low as possible based on the observed minimum RTT. Similar to Vegas, you cannot configure the desired latency in Gradient and Gradient2. They calculated the limit based on the gradient of observed RTT from the expected RTT. The higher the value of _rttTolerance_, the higher deviations are tolerated that results in higher values for limit. If a lot of requests are rejected due to backpressure, it might indicate that the processing capacity of the cluster is not enough to handle the expected throughput. If this is the expected workload, you might consider a different configuration for the cluster, such as provisioning more resources and increasing the number of nodes and partitions. ## Potential issues The rate limiter used by Zeebe to implement backpressure may use `System.nanoTime()` to measure the RTT of requests. In some systems, we've observed consecutive calls to this method can return equal or even decreasing values. [Low clock resolution](https://shipilev.net/blog/2014/nanotrusting-nanotime) and [monotonicity](https://bugs.openjdk.java.net/browse/JDK-6458294) [issues](https://stackoverflow.com/questions/3657289/linux-clock-gettimeclock-monotonic-strange-non-monotonic-behavior) are some of the most likely culprits of this. If this happens, it's recommended to configure the backpressure to use the **fixed** algorithm. Without a clock with sufficient resolution, adaptive backpressure algorithms are not useful. ## Next steps Looking for more information on backpressure? Review our documentation on [internal processing and backpressure](/components/zeebe/technical-concepts/internal-processing.md#handling-backpressure). --- ## Backups(Operations) :::note Refer to our documentation on [backup and restore](/self-managed/operational-guides/backup-restore/backup-and-restore.md) to learn how to take backups without downtime. For disaster recovery context, see [Multi-region resilience tiers](/self-managed/concepts/multi-region/resilience-tiers.md). ::: As Zeebe fully manages the state of your process instances, consider taking backups of Zeebe data; this is crucial to prevent data loss, roll back application-level errors, and more. Zeebe is fault-tolerant and replicates state internally. Backups are only necessary if you'd like to protect against the loss of entire replica sets or data corruption bugs. State of other components, such as Operate and Tasklist, is not managed by Zeebe and must be backed up separately. Taking cold backups is a manual process that is highly dependent on your infrastructure and deployment. Camunda does not provide an automated mechanism or tool for cold backups. However, we do offer the following guidance to create and execute a successful backup. ## Cold backups Cold backups, also called offline backups, require **downtime**. During the downtime, processes don't make progress and clients can't communicate with Zeebe. To make sure that the downtime doesn't cause issues for your clients, you should test how your clients behave during the downtime, or shut them down as well. ### Shutting down all brokers in the cluster To take a consistent backup, all brokers must be shut down first. As soon as brokers shut down, partitions become unhealthy and clients lose connections to Zeebe or experience full backpressure. To prevent unnecessary failovers during the shutdown process, we recommend shutting down all brokers at the same time instead of a gradual shutdown. Wait for all brokers to fully shut down before proceeding to the next step. ### Creating the backup :::note The `data` folder contains symbolic and hard links which may require special attention when copying, depending on your environment. ::: To create the backup, take the following steps: 1. Each broker has a data folder where all state is persisted. The location of the data folder is [configured](../configuration/broker.md#camundadataprimary-storage) via `camunda.data.primary-storage.directory`. Create a copy of the data folder and store it in a safe location. If you have direct access to the broker, for example in a bare-metal setup, you can do this by creating a tarball like this: `tar caf backup.tar.gz data/`. You may also use filesystem snapshots or [Kubernetes volume snapshots](https://kubernetes.io/docs/concepts/storage/volume-snapshots/) if that fits your environment better 2. Double-check that your tool of choice supports symbolic and hard links. 3. Do not merge or otherwise modify data folders as this might result in data loss and unrestorable backups. 4. Save the broker configuration to ensure the replacement cluster can process the backed-up data. See the following example on how a backup may look: ```bash $ tree zeebe-backup-* zeebe-backup-2021-01-31 ├── zeebe-broker-0-config.yml ├── zeebe-broker-0-data.tar.gz ├── zeebe-broker-1-config.yml ├── zeebe-broker-1-data.tar.gz ├── zeebe-broker-2-config.yml └── zeebe-broker-2-data.tar.gz ``` ### Resuming After taking the backup, brokers can be started again and will automatically resume with processing. ## Restore from backup ### Prepare replacement cluster :::warning Always use the same or the next minor version of Zeebe that you were using when taking the backup. Using a different version may result in data corruption or data loss. See the [upgrade guide](/self-managed/upgrade/components/index.md) for more details. ::: Ensure your replacement cluster has the same number of brokers as the old cluster and uses the [same node IDs](setting-up-a-cluster.md#configuration). ### Shutting down all brokers in the replacement cluster Before installing the backup, ensure all brokers are fully shut down. ### Installing the backup To install the backup, take the following steps: 1. Delete the existing data folder on each broker of your replacement cluster. 2. For each broker, copy over the configuration and the data folder. 3. You may need to slightly adjust the configuration for your replacement cluster, for example to update IP addresses. ### Starting the Zeebe cluster After replacing the data folders, brokers can be started again and will automatically resume with processing. --- ## Cluster scaling Zeebe allows scaling an existing cluster by adding or removing brokers and by adding new partitions. Partitions are automatically redistributed over the set of brokers to spread the load evenly. Zeebe provides a REST API to manage the cluster scaling. The cluster management API is a custom endpoint available via [Spring Boot Actuator](https://docs.spring.io/spring-boot/docs/3.1.x/reference/htmlsingle/#actuator.endpoints). This is accessible via the management port of the Zeebe Gateway. :::important - Partition count can only be increased and not decreased. - Backups are disallowed during partition scaling but can be taken before or after. A backup taken before scaling can only be restored to a cluster with the same partition count. After restoring, you can request scaling again to the desired partition count. - When scaling up the number of partitions, consider the resulting RocksDB size per partition. Allocate **at least 32 MB of RocksDB memory per partition** after scaling. For details, see the [resource planning guide](/components/best-practices/architecture/sizing-self-managed.md). ::: ## Broker id naming scheme How brokers are identified and scaled depends on whether the cluster is [zone-aware](/self-managed/components/orchestration-cluster/zeebe/configuration/zone-aware-clusters.md). By default a cluster is **not zone-aware**. - **Non-zone-aware** clusters use **integer** broker ids: (`0`, `1`, `2`, ...). They are used as examples in this page. - **Zone-aware** clusters use **string** broker ids: `${zone}_${n}` (for example, `"zone-a_0"` with double quotes). ## Considerations - Scaling operations occur while the cluster remains online. During scaling, data is redistributed and new leaders are elected for affected partitions. - Existing partitions continue processing data, but you may notice temporary performance impacts until scaling completes. Plan scaling ahead of anticipated load increases to minimize disruption. - When adding new partitions or brokers, partitions are redistributed across both old and new brokers. Depending on the number of brokers and partitions, this may increase the load per broker. Use the API endpoints in [dry run](#dry-run) mode to preview partition distribution. - Always take a backup before scaling to ensure you can restore if needed. - When you add partitions, each new partition is bootstrapped from a snapshot of the global state of partition 1, such as process definitions, decisions, forms, and identity data. This snapshot is transferred in chunks of [`camunda.cluster.raft.snapshot-chunk-size`](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#camundaclusterraft), and each request must complete within `camunda.cluster.raft.bootstrap-snapshot-request-timeout`. If a partition bootstrap repeatedly fails with snapshot request timeouts on a cluster with a large global state, increase that timeout. - Scaling is a planned configuration change. The cluster rejects a new configuration change while another one is still running. - A dynamic scaling operation does not require a rolling restart. Static configuration changes, such as adding a Physical Tenant, still follow the [provisioning and lifecycle](/self-managed/concepts/physical-tenants/provisioning-and-lifecycle.md) restart procedure. - Gateway replicas are scaled separately from brokers and partitions. In Helm deployments, adjust `zeebe-gateway.replicas`. Gateway scaling changes shared request capacity but does not change partition placement. ## Scale a cluster with multiple Physical Tenants Self-Managed only In a cluster running multiple [Physical Tenants](/self-managed/concepts/physical-tenants/index.md), each tenant owns its own partition group, while brokers, gateways, and the replication factor are shared. Which scaling dimension you change therefore determines whether the operation is tenant-scoped or cluster-wide. | Dimension | Scope | How to target it | | ---------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Partition count | Per tenant | [`PATCH /actuator/cluster?physicalTenant={physicalTenantId}`](/apis-tools/management-api/specifications/cluster/reconfigure-cluster.api.mdx) | | Broker count | Cluster-wide | [`PATCH /actuator/cluster`](/apis-tools/management-api/specifications/cluster/reconfigure-cluster.api.mdx) or [`POST /actuator/cluster/brokers`](/apis-tools/management-api/specifications/cluster/reconfigure-the-cluster-with-the-given-brokers.api.mdx), without a `physicalTenant` parameter | | Replication factor | Cluster-wide | [`PATCH /actuator/cluster`](/apis-tools/management-api/specifications/cluster/reconfigure-cluster.api.mdx), without a `physicalTenant` parameter | | Partition join, leave, or priority | Per tenant | `POST` ([add or change priority](/apis-tools/management-api/specifications/cluster/add-a-broker-to-a-partitions-replication-group-or-change-its-priority.api.mdx)) or `DELETE` ([remove](/apis-tools/management-api/specifications/cluster/remove-a-broker-from-a-partitions-replication-group.api.mdx)) `/actuator/cluster/brokers/{brokerId}/partitions/{partitionId}?physicalTenant=` | | Routing state | Per tenant | [`PATCH /actuator/cluster/routing-state?physicalTenant={physicalTenantId}`](/apis-tools/management-api/specifications/cluster/update-the-routing-state.api.mdx) | Partition ids restart at `1` in every Physical Tenant, so a partition is only identified by its id together with its tenant. ### Scale the partitions of a single Physical Tenant Send the partition count change with the `physicalTenant` query parameter. Only the named tenant's partition group gains partitions, and every other tenant is left untouched: ``` curl -X 'PATCH' \ 'http://localhost:9600/orchestration/actuator/cluster?physicalTenant=tenanta' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "partitions": { "count": 6 } }' ``` Requests that combine `physicalTenant` with a broker change or a replication factor change are rejected with `400`, because neither dimension has a tenant to scope it to. An unknown `physicalTenant` is rejected with `404`. :::note A partition count change sent **without** the `physicalTenant` parameter targets the default Physical Tenant only. Read operations behave differently: `GET /actuator/cluster` without the parameter reports every Physical Tenant. Always pass `physicalTenant` explicitly when you intend to scale a non-default tenant. ::: ### Verify a tenant-scoped scaling operation Monitor the change with the [monitoring API](#monitoring-api) or [`GET /actuator/cluster/changes`](/apis-tools/management-api/specifications/cluster/list-configuration-changes.api.mdx), then confirm the result through topology: ``` curl "http://localhost:8080/physical-tenants/tenanta/v2/topology" curl "http://localhost:8080/cluster/v2/topology" ``` Confirm that every expected partition has a leader, that the targeted tenant's partition count matches the requested value, and that the other tenants retain their previous partition counts. Cluster-wide topology requires [cluster admin](/components/admin/cluster-admin.md) access. Because brokers and gateways are shared, a scaling operation for one tenant changes the capacity available to all of them. Compare tenant-scoped and cluster-wide partition, latency, and storage metrics against your pre-scaling baseline before returning the cluster to normal traffic. ## Scale up brokers The following shows how to scale up a Zeebe cluster using an example of scaling from cluster size 3 to cluster size 6. The target partition count is 6. This example assumes the cluster was deployed with the following configurations, depending on what we want to scale: #### Initial State - scale brokers only: - clusterSize 3 - partitionCount 6 - scale brokers and partitions: - clusterSize 3 - partitionCount 3 #### Target state - clusterSize 6 - partitionCount 6 ### 1. Start new brokers If you have deployed Zeebe using [Helm](/self-managed/deployment/helm/install/quick-install.md), you can start new brokers by using the `kubectl scale` command. Otherwise, refer to the corresponding installation methods on how to start a new broker. ``` kubectl scale statefulset camunda --replicas=6 ``` You can see new pods being created when running `kubectl get pods`. The new brokers will be assigned ids `3`, `4`, and `5` respectively. ``` camunda-zeebe-0 1/1 Running 0 3m24s camunda-zeebe-1 1/1 Running 0 3m24s camunda-zeebe-2 1/1 Running 0 3m24s camunda-zeebe-3 0/1 Init:0/1 0 11s camunda-zeebe-4 0/1 Init:0/1 0 11s camunda-zeebe-5 0/1 Init:0/1 0 11s ``` :::info Starting brokers in a zone-aware cluster On a [zone-aware cluster](#broker-id-naming-scheme), each zone is a separate StatefulSet, so you need to scale brokers in each zone. ::: ### 2. Send scale request to the Zeebe Gateway Send a PATCH request to the Zeebe Gateway's management endpoint to add new brokers to the cluster or redistribute partitions. See the [API reference](#api-reference) for details. If you are running on Kubernetes and haven’t set up Ingress, port-forward to access the Zeebe Gateway on your local machine: ``` kubectl port-forward svc/camunda-zeebe-gateway 9600:9600 ``` Choose the appropriate request depending on whether you are adding new partitions (see section 2.a or 2.b). Verify partition distribution after scaling by calling the endpoints in [dry run](#dry-run) mode. #### 2.a Scale brokers only Run the following to send the request to the Zeebe Gateway: ``` curl -X 'PATCH' \ 'http://localhost:9600/orchestration/actuator/cluster' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "brokers": { "add": [3,4,5] } }' ``` Here `3`, `4`, and `5` are the newly-added brokers. :::note Zone aware clusters Make sure to use the correct [broker ids](#broker-id-naming-scheme), for example `["zone-a_3", "zone-a_4", "zone-a_5"]` Brokers from different zones can be added with a single request. Make sure to scale each zone's statefulsets with the required replica count beforehand. ::: #### 2.b Scaling brokers and partitions Run the following to send the request to the Zeebe Gateway to add 3 new brokers to the cluster and set the number of partition to 6. ``` curl -X 'PATCH' \ 'http://localhost:9600/orchestration/actuator/cluster' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "brokers": { "add": [3,4,5] }, "partitions": { "count": 6, "replicationFactor": 3 } }' ``` For [zone-aware cluster](#broker-id-naming-scheme) you need to change the broker ids accordingly as outlined in [section 2.a](#2a-scale-brokers-only) :::warning Changing replication factor in a zone-aware cluster You cannot change replication factor in a zone-aware cluster with this API. You need to use `PUT /actuator/cluster/partitioning/` instead. ::: #### 2.c Scaling only partitions If you don't intend to add new brokers to the cluster, you can skip the `"brokers"` section: ``` curl -X 'PATCH' \ 'http://localhost:9600/orchestration/actuator/cluster' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "partitions": { "count": 6, "replicationFactor": 3 } }' ``` You can omit `replicationFactor` if you don't want to change it. The response includes a `changeId`, `currentTopology`, planned changes, and the expected topology, as shown below: ``` { "changeId": 2, "currentTopology": [ ... ], "plannedChanges": [ { "operation": "BROKER_ADD", "brokerId": 3 }, { "operation": "BROKER_ADD", "brokerId": 4 }, { "operation": "BROKER_ADD", "brokerId": 5 }, { "operation": "PARTITION_JOIN", "brokerId": 4, "partitionId": 5, "priority": 3 }, { "operation": "PARTITION_LEAVE", "brokerId": 1, "partitionId": 5 }, ... ], "expectedTopology": [ { "id": 1, "state": "ACTIVE", "version": 7, "lastUpdatedAt": "2023-12-22T13:37:43.403615966Z", "partitions": [ { "id": 1, "state": "ACTIVE", "priority": 2 }, { "id": 2, "state": "ACTIVE", "priority": 3 }, { "id": 6, "state": "ACTIVE", "priority": 1 } ] }, ... ] } ``` ### 3. Query the Zeebe Gateway to monitor progress of scaling The scaling operation can take a while because data needs to be moved to the newly-added brokers. Therefore, you have to monitor the status by querying Zeebe and sending the following GET request to the Zeebe Gateway: ``` curl --request GET 'http://localhost:9600/orchestration/actuator/cluster' ``` When the scaling has completed, the `changeId` from the previous response will be marked as completed: ``` { "version": 3, "brokers": [ ... ], "lastChange": { "id": 2, "status": "COMPLETED", "startedAt": "2023-12-22T13:37:43.405094261Z", "completedAt": "2023-12-22T13:40:06.90159187Z" } } ``` ### 4. (Optional) Verify the partitions are distributed to new brokers This step is optional, but it is useful when you are testing to see if scaling works as expected. Port-forward to access the Zeebe Gateway if required: ``` kubectl port-forward svc/camunda-gateway 8080:8080 ``` Run the following command to see the current status of the cluster. If security is enabled, first obtain an access token from your identity provider and export it as `ACCESS_TOKEN`, then include it as a Bearer token in the request header. ``` curl -L 'http://localhost:8080/orchestration/v2/topology' \ -H 'Accept: application/json' ``` The response would show that partitions are distributed to new brokers:
Example response ```json { "brokers": [ { "nodeId": 0, "host": "camunda-zeebe-0.camunda-zeebe", "port": 26501, "partitions": [ { "partitionId": 1, "role": "leader", "health": "healthy" }, { "partitionId": 5, "role": "follower", "health": "healthy" }, { "partitionId": 6, "role": "follower", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 1, "host": "camunda-zeebe-1.camunda-zeebe", "port": 26501, "partitions": [ { "partitionId": 1, "role": "follower", "health": "healthy" }, { "partitionId": 2, "role": "leader", "health": "healthy" }, { "partitionId": 6, "role": "leader", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 2, "host": "camunda-zeebe-2.camunda-zeebe", "port": 26501, "partitions": [ { "partitionId": 1, "role": "follower", "health": "healthy" }, { "partitionId": 2, "role": "follower", "health": "healthy" }, { "partitionId": 3, "role": "leader", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 3, "host": "camunda-zeebe-3.camunda-zeebe", "port": 26501, "partitions": [ { "partitionId": 2, "role": "follower", "health": "healthy" }, { "partitionId": 3, "role": "follower", "health": "healthy" }, { "partitionId": 4, "role": "leader", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 4, "host": "camunda-zeebe-4.camunda-zeebe", "port": 26501, "partitions": [ { "partitionId": 3, "role": "follower", "health": "healthy" }, { "partitionId": 4, "role": "follower", "health": "healthy" }, { "partitionId": 5, "role": "leader", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 5, "host": "camunda-zeebe-5.camunda-zeebe", "port": 26501, "partitions": [ { "partitionId": 4, "role": "follower", "health": "healthy" }, { "partitionId": 5, "role": "follower", "health": "healthy" }, { "partitionId": 6, "role": "follower", "health": "healthy" } ], "version": "8.8.0" } ], "clusterSize": 6, "partitionsCount": 6, "replicationFactor": 3, "gatewayVersion": "8.8.0", "clusterId": "clusterId" } ```
## Scale down We will explain how to scale down a Zeebe cluster via an example of scaling from cluster size 6 to cluster size 3. We assume the cluster is running with 6 brokers. :::warning Scale down can be performed only on brokers, partition count cannot be decreased ::: ### 1. Send the scale request to the Zeebe Gateway Now we should tell Zeebe to move partitions away from the brokers that will be removed. For that, we send a PATCH request to the Zeebe Gateway's management endpoint. See [API reference](#api-reference) for more details. If you haven't set up Ingress, you can first port-forward to access the Zeebe Gateway in your local machine: ``` kubectl port-forward svc/camunda-zeebe-gateway 9600:9600 ``` ``` curl -X 'PATCH' \ 'http://localhost:9600/orchestration/actuator/cluster' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "brokers": { "remove": [3,4,5] } }' ``` Similar to scaling up, the response to this request would contain a `changeId`, `currentTopology`, planned changes, and expected topology. :::warning scaling down a zone-aware cluster For a zone-aware cluster, the same [changes](#2a-scale-brokers-only) as for scaling up are required. ::: ### 2. Query the Zeebe Gateway to monitor progress of scaling ``` curl --request GET 'http://localhost:9600/orchestration/actuator/cluster' ``` When the scaling has completed, the changeId from the previous response will be marked as completed: ``` { "version": 5, "brokers": [ ... ], "lastChange": { "id": 4, "status": "COMPLETED", "startedAt": "2023-12-22T13:43:05.936882692Z", "completedAt": "2023-12-22T13:43:41.138424552Z" } } ``` ### 3. (Optional) Verify partitions have been moved to the remaining brokers This step is optional, but it is useful when you are testing to see if scaling worked as expected. Run the following command to see the current status of the cluster. If security is enabled, first obtain an access token from your identity provider and export it as `ACCESS_TOKEN`, then include it as a Bearer token in the request header. ``` curl -L 'http://localhost:8080/orchestration/v2/topology' \ -H 'Accept: application/json' ``` The response would show that the partitions are moved away from brokers `3`, `4`, and `5`:
Example response ```json { "brokers": [{ "nodeId": 0, "host": "camunda-zeebe-0.camunda-zeebe.camunda", "port": 26501, "partitions": [{ "partitionId": 1, "role": "leader", "health": "healthy" }, { "partitionId": 2, "role": "follower", "health": "healthy" }, { "partitionId": 3, "role": "follower", "health": "healthy" }, { "partitionId": 4, "role": "leader", "health": "healthy" } { "partitionId": 5, "role": "follower", "health": "healthy" }, { "partitionId": 6, "role": "follower", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 1, "host": "camunda-zeebe-1.camunda-zeebe.camunda", "port": 26501, "partitions": [{ "partitionId": 1, "role": "follower", "health": "healthy" }, { "partitionId": 2, "role": "leader", "health": "healthy" }, { "partitionId": 3, "role": "follower", "health": "healthy" }, { "partitionId": 4, "role": "follower", "health": "healthy" }, { "partitionId": 5, "role": "leader", "health": "healthy" }, { "partitionId": 6, "role": "leader", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 2, "host": "camunda-zeebe-2.camunda-zeebe", "port": 26501, "partitions": [{ "partitionId": 1, "role": "follower", "health": "healthy" }, { "partitionId": 2, "role": "follower", "health": "healthy" }, { "partitionId": 3, "role": "leader", "health": "healthy" }, { "partitionId": 4, "role": "follower", "health": "healthy" }, { "partitionId": 5, "role": "follower", "health": "healthy" }, { "partitionId": 6, "role": "follower", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 3, "host": "camunda-zeebe-3.camunda-zeebe", "port": 26501, "partitions": [], "version": "8.8.0" }, { "nodeId": 4, "host": "camunda-zeebe-4.camunda-zeebe", "port": 26501, "partitions": [], "version": "8.8.0" }, { "nodeId": 5, "host": "camunda-zeebe-5.camunda-zeebe", "port": 26501, "partitions": [], "version": "8.8.0" } ], "clusterSize": 3, "partitionsCount": 6, "replicationFactor": 3, "gatewayVersion": "8.8.0", "clusterId": "clusterId" } ```
### 4. Shut down the brokers when the scaling operation has completed :::danger If you shut down brokers before Zeebe has scaled down and moved all partitions away from the brokers, scaling operation would never complete and may result in data loss. ::: ``` kubectl scale statefulset --replicas=3 ``` When monitoring the pods via `kubectl get pods`, we can see that pods 3, 4, and 5 have been terminated. ``` camunda-zeebe-0 1/1 Running 0 9m55s camunda-zeebe-1 1/1 Running 0 9m55s camunda-zeebe-2 1/1 Running 0 9m50s ``` :::note After scaling down the statefulset, you may have to delete the PVCs manually. ::: #### Shut down brokers in a zone-aware cluster On a zone-aware cluster, scale down the StatefulSet of the zone you scaled, once its scaling operation has completed: ``` kubectl scale statefulset --replicas=3 ``` ## API reference OpenAPI spec for this API can be found [here](https://github.com/camunda/camunda/blob/main/zeebe/dynamic-config/openapi/src/main/resources/api/cluster-api.yaml). ### Reconfiguration API This API lets you reconfigure a cluster by adding or removing brokers, adding partitions, or changing the `replicationFactor`. You can use this instead of the Scale API. :::note This endpoint does not support clusters configured with the `FIXED` partitioning scheme. Scaling requests for these clusters are rejected. ::: #### Request ``` PATCH actuator/cluster { brokers: { add: [] remove: [] count: zone: } { partitions: { count: replicationFactor: } } } ``` `zone` is only used on zone-aware clusters, together with `count`, to select which zone's broker count is changed. It must be omitted on non-zone-aware clusters. Broker ids in `add` and `remove` follow the [broker id naming scheme](#broker-id-naming-scheme). The `physicalTenant` query parameter scopes `partitions.count` to a single [Physical Tenant](/self-managed/concepts/physical-tenants/index.md). See [scale a cluster with multiple Physical Tenants](#scale-a-cluster-with-multiple-physical-tenants).
Example request ``` curl -X 'PATCH' \ 'http://localhost:9600/orchestration/actuator/cluster' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "brokers": { "add": [3,4,5] }, "partitions": { "count": 6, "replicationFactor": 3 } }' ```
##### Dry run You can do a dry run without executing the reconfiguration by setting the `dryRun` request parameter to `true`. By default, `dryRun` is set to `false`. ##### Force :::caution This is a dangerous operation and must be used with caution. Incorrect use may result in split-brain scenarios or an unhealthy, unrecoverable cluster. ::: Usually, changes can only be made when all brokers are up. If some brokers are unreachable, you can remove them from the cluster by setting the `force` request parameter to `true`. This operation is mainly useful for [dual-region setups](/self-managed/concepts/multi-region/dual-region.md). For details, see the [dual-region operational procedure](/self-managed/deployment/helm/operational-tasks/dual-region-ops.md). Deviations from the process may make the cluster unusable. :::note Don’t send more than one `force` request at a time. ::: Example request: ``` curl -X 'PATCH' \ 'http://localhost:9600/orchestration/actuator/cluster?force=true' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "brokers": { "remove": [0,2] } }' ``` This operation doesn’t redistribute the partitions from the removed brokers. The resulting cluster has fewer replicas for the affected partitions. ### Scale request API :::note See also the [Reconfiguration API](#reconfiguration-api). ::: Use this endpoint to scale a cluster up or down by changing the cluster size and redistributing partitions. :::note This endpoint does not support clusters configured with the `FIXED` partitioning scheme. Scaling requests for these clusters are rejected. ::: #### Request ``` POST actuator/cluster/brokers/ [ , ,.. ] ``` The input is a list of _all_ broker ids that will be in the final cluster after scaling. On zone-aware clusters, broker ids follow the [broker id naming scheme](#broker-id-naming-scheme).
Example request ``` curl --request POST 'http://localhost:9600/orchestration/actuator/cluster/brokers' \ -H 'Content-Type: application/json' \ -d '[0, 1, 2, 3]' ```
##### Dry run You can also do a dry run without actually executing the scaling by specifying the request parameter `dryRun` to `true` as follows. By default, `dryRun` is set to false: ``` curl --request POST 'http://localhost:9600/orchestration/actuator/cluster/brokers?dryRun=true' \ -H 'Content-Type: application/json' \ -d '[0, 1, 2, 3]' ``` ##### Replication factor The replication factor for all partitions can be changed with the `replicationFactor` request parameter. If not specified, the replication factor remains unchanged. The new replicas are assigned to the brokers based on the round robin partition distribution strategy. ``` curl --request POST 'http://localhost:9600/orchestration/actuator/cluster/brokers?replicationFactor=4' \ -H 'Content-Type: application/json' \ -d '[0, 1, 2, 3]' ``` ##### Force remove brokers :::caution This is a dangerous operation and must be used with caution. When not used correctly, split-brain scenarios or unhealthy, unrecoverable clusters may result. ::: Usually, changes can only be made to a cluster when all brokers are up. When some brokers are unreachable, you may want to remove them from the cluster. You can force remove a set of brokers by setting the request parameter `force` to `true`. This operation is mainly useful for [dual-region setups](/self-managed/concepts/multi-region/dual-region.md), and additional information can be found in the [dual-region operational procedure](/self-managed/deployment/helm/operational-tasks/dual-region-ops.md). Any deviations from the described process can result in the cluster being unusable. :::note Do not send more than one force request at a time. ::: The following request force removes all brokers that are _not_ provided in the request body: ``` curl --request POST 'http://localhost:9600/orchestration/actuator/cluster/brokers?force=true' \ -H 'Content-Type: application/json' \ -d '[0, 1, 2]' ``` This operation does not re-distribute the partitions that were in the removed brokers. As a result, the resulting cluster will have a reduced number of replicas for the affected partitions. #### Response The response is a JSON object. See detailed specs [here](https://github.com/camunda/camunda/blob/main/zeebe/dynamic-config/openapi/src/main/resources/api/cluster-api.yaml): ``` { changeId: currentTopology: [...] plannedChanges: [...] expectedTopology: [...] } ``` - `changeId`: The ID of the changes initiated to scale the cluster. This can be used to monitor the progress of the scaling operation. The ID typically increases so new requests get a higher ID than the previous one. - `currentTopology`: A list of current brokers and the partition distribution. - `plannedChanges`: A sequence of operations that has to be executed to achieve scaling. - `expectedToplogy`: The expected list of brokers and the partition distribution once the scaling is completed.
Example response ``` { "changeId": 2, "currentTopology": [ { "id": 1, "state": "ACTIVE", "version": 0, "lastUpdatedAt": "-999999999-01-01T00:00:00+18:00", "partitions": [ { "id": 1, "state": "ACTIVE", "priority": 2 }, { "id": 2, "state": "ACTIVE", "priority": 3 }, { "id": 3, "state": "ACTIVE", "priority": 1 }, { "id": 4, "state": "ACTIVE", "priority": 1 }, { "id": 5, "state": "ACTIVE", "priority": 3 }, { "id": 6, "state": "ACTIVE", "priority": 2 } ] }, { "id": 2, "state": "ACTIVE", "version": 0, "lastUpdatedAt": "-999999999-01-01T00:00:00+18:00", "partitions": [ { "id": 1, "state": "ACTIVE", "priority": 1 }, { "id": 2, "state": "ACTIVE", "priority": 2 }, { "id": 3, "state": "ACTIVE", "priority": 3 }, { "id": 4, "state": "ACTIVE", "priority": 2 }, { "id": 5, "state": "ACTIVE", "priority": 1 }, { "id": 6, "state": "ACTIVE", "priority": 3 } ] }, { "id": 0, "state": "ACTIVE", "version": 0, "lastUpdatedAt": "-999999999-01-01T00:00:00+18:00", "partitions": [ { "id": 1, "state": "ACTIVE", "priority": 3 }, { "id": 2, "state": "ACTIVE", "priority": 1 }, { "id": 3, "state": "ACTIVE", "priority": 2 }, { "id": 4, "state": "ACTIVE", "priority": 3 }, { "id": 5, "state": "ACTIVE", "priority": 2 }, { "id": 6, "state": "ACTIVE", "priority": 1 } ] } ], "plannedChanges": [ { "operation": "BROKER_ADD", "brokerId": 3 }, { "operation": "BROKER_ADD", "brokerId": 4 }, { "operation": "BROKER_ADD", "brokerId": 5 }, { "operation": "PARTITION_JOIN", "brokerId": 4, "partitionId": 5, "priority": 3 }, { "operation": "PARTITION_JOIN", "brokerId": 5, "partitionId": 5, "priority": 2 }, { "operation": "PARTITION_LEAVE", "brokerId": 1, "partitionId": 5 }, { "operation": "PARTITION_LEAVE", "brokerId": 2, "partitionId": 5 }, { "operation": "PARTITION_RECONFIGURE_PRIORITY", "brokerId": 0, "partitionId": 5, "priority": 1 }, { "operation": "PARTITION_JOIN", "brokerId": 3, "partitionId": 4, "priority": 3 }, { "operation": "PARTITION_JOIN", "brokerId": 4, "partitionId": 4, "priority": 2 }, { "operation": "PARTITION_JOIN", "brokerId": 5, "partitionId": 4, "priority": 1 }, { "operation": "PARTITION_LEAVE", "brokerId": 1, "partitionId": 4 }, { "operation": "PARTITION_LEAVE", "brokerId": 2, "partitionId": 4 }, { "operation": "PARTITION_LEAVE", "brokerId": 0, "partitionId": 4 }, { "operation": "PARTITION_JOIN", "brokerId": 3, "partitionId": 2, "priority": 1 }, { "operation": "PARTITION_LEAVE", "brokerId": 0, "partitionId": 2 }, { "operation": "PARTITION_JOIN", "brokerId": 3, "partitionId": 3, "priority": 2 }, { "operation": "PARTITION_JOIN", "brokerId": 4, "partitionId": 3, "priority": 1 }, { "operation": "PARTITION_LEAVE", "brokerId": 1, "partitionId": 3 }, { "operation": "PARTITION_LEAVE", "brokerId": 0, "partitionId": 3 }, { "operation": "PARTITION_JOIN", "brokerId": 5, "partitionId": 6, "priority": 3 }, { "operation": "PARTITION_LEAVE", "brokerId": 2, "partitionId": 6 }, { "operation": "PARTITION_RECONFIGURE_PRIORITY", "brokerId": 1, "partitionId": 6, "priority": 1 }, { "operation": "PARTITION_RECONFIGURE_PRIORITY", "brokerId": 0, "partitionId": 6, "priority": 2 } ], "expectedTopology": [ { "id": 1, "state": "ACTIVE", "version": 7, "lastUpdatedAt": "2023-12-22T13:37:43.403615966Z", "partitions": [ { "id": 1, "state": "ACTIVE", "priority": 2 }, { "id": 2, "state": "ACTIVE", "priority": 3 }, { "id": 6, "state": "ACTIVE", "priority": 1 } ] }, { "id": 2, "state": "ACTIVE", "version": 6, "lastUpdatedAt": "2023-12-22T13:37:43.403558726Z", "partitions": [ { "id": 1, "state": "ACTIVE", "priority": 1 }, { "id": 2, "state": "ACTIVE", "priority": 2 }, { "id": 3, "state": "ACTIVE", "priority": 3 } ] }, { "id": 3, "state": "ACTIVE", "version": 8, "lastUpdatedAt": "2023-12-22T13:37:43.401971149Z", "partitions": [ { "id": 2, "state": "ACTIVE", "priority": 1 }, { "id": 3, "state": "ACTIVE", "priority": 2 }, { "id": 4, "state": "ACTIVE", "priority": 3 } ] }, { "id": 4, "state": "ACTIVE", "version": 8, "lastUpdatedAt": "2023-12-22T13:37:43.40214448Z", "partitions": [ { "id": 3, "state": "ACTIVE", "priority": 1 }, { "id": 4, "state": "ACTIVE", "priority": 2 }, { "id": 5, "state": "ACTIVE", "priority": 3 } ] }, { "id": 5, "state": "ACTIVE", "version": 8, "lastUpdatedAt": "2023-12-22T13:37:43.40345971Z", "partitions": [ { "id": 4, "state": "ACTIVE", "priority": 1 }, { "id": 5, "state": "ACTIVE", "priority": 2 }, { "id": 6, "state": "ACTIVE", "priority": 3 } ] }, { "id": 0, "state": "ACTIVE", "version": 8, "lastUpdatedAt": "2023-12-22T13:37:43.403675185Z", "partitions": [ { "id": 1, "state": "ACTIVE", "priority": 3 }, { "id": 5, "state": "ACTIVE", "priority": 1 }, { "id": 6, "state": "ACTIVE", "priority": 2 } ] } ] } ```
The scaling is executed asynchronously. Use the Query API below to monitor the progress. ### Monitoring API The current cluster topology and any ongoing scaling operations can be monitored via this endpoint. #### Request ``` GET actuator/cluster ``` #### Response The response is a JSON object. See detailed specs [here](https://github.com/camunda/camunda/blob/main/zeebe/dynamic-config/openapi/src/main/resources/api/cluster-api.yaml): ``` { version: brokers: [...] lastChange: {} pendingChange: {} } ``` - `version`: The version of current cluster topology. The version is updated when the cluster is scaled up or down. - `brokers`: A list of current brokers and the partition distribution. - `lastChange`: The details about the last completed scaling operation. - `pendingChange`: The details about the ongoing scaling operation.
Example response ``` { "version": 6, "brokers": [ { "id": 1, "state": "ACTIVE", "version": 14, "lastUpdatedAt": "2023-12-22T13:43:29.718491365Z", "partitions": [ { "id": 1, "state": "ACTIVE", "priority": 2 }, { "id": 2, "state": "ACTIVE", "priority": 3 }, { "id": 3, "state": "ACTIVE", "priority": 1 }, { "id": 4, "state": "ACTIVE", "priority": 1 }, { "id": 5, "state": "ACTIVE", "priority": 3 }, { "id": 6, "state": "ACTIVE", "priority": 2 } ] }, { "id": 2, "state": "ACTIVE", "version": 12, "lastUpdatedAt": "2023-12-22T13:43:30.951499449Z", "partitions": [ { "id": 1, "state": "ACTIVE", "priority": 1 }, { "id": 2, "state": "ACTIVE", "priority": 2 }, { "id": 3, "state": "ACTIVE", "priority": 3 }, { "id": 4, "state": "ACTIVE", "priority": 2 }, { "id": 5, "state": "ACTIVE", "priority": 1 }, { "id": 6, "state": "ACTIVE", "priority": 3 } ] }, { "id": 0, "state": "ACTIVE", "version": 16, "lastUpdatedAt": "2023-12-22T13:43:28.482560705Z", "partitions": [ { "id": 1, "state": "ACTIVE", "priority": 3 }, { "id": 2, "state": "ACTIVE", "priority": 1 }, { "id": 3, "state": "ACTIVE", "priority": 2 }, { "id": 4, "state": "ACTIVE", "priority": 3 }, { "id": 5, "state": "ACTIVE", "priority": 2 }, { "id": 6, "state": "ACTIVE", "priority": 1 } ] } ], "lastChange": { "id": 4, "status": "COMPLETED", "startedAt": "2023-12-22T13:43:05.936882692Z", "completedAt": "2023-12-22T13:43:41.138424552Z" }, "pendingChange": { "id": 6, "status": "IN_PROGRESS", "completed": [], "pending": [ { "operation": "BROKER_ADD", "brokerId": 3 }, { "operation": "PARTITION_JOIN", "brokerId": 3, "partitionId": 3, "priority": 2 }, { "operation": "PARTITION_LEAVE", "brokerId": 1, "partitionId": 3 }, { "operation": "PARTITION_RECONFIGURE_PRIORITY", "brokerId": 0, "partitionId": 3, "priority": 1 }, { "operation": "PARTITION_JOIN", "brokerId": 3, "partitionId": 6, "priority": 2 }, { "operation": "PARTITION_LEAVE", "brokerId": 0, "partitionId": 6 }, { "operation": "PARTITION_RECONFIGURE_PRIORITY", "brokerId": 1, "partitionId": 6, "priority": 3 }, { "operation": "PARTITION_RECONFIGURE_PRIORITY", "brokerId": 2, "partitionId": 6, "priority": 1 }, { "operation": "PARTITION_RECONFIGURE_PRIORITY", "brokerId": 1, "partitionId": 5, "priority": 1 }, { "operation": "PARTITION_RECONFIGURE_PRIORITY", "brokerId": 2, "partitionId": 5, "priority": 2 }, { "operation": "PARTITION_RECONFIGURE_PRIORITY", "brokerId": 0, "partitionId": 5, "priority": 3 }, { "operation": "PARTITION_JOIN", "brokerId": 3, "partitionId": 2, "priority": 1 }, { "operation": "PARTITION_LEAVE", "brokerId": 0, "partitionId": 2 }, { "operation": "PARTITION_JOIN", "brokerId": 3, "partitionId": 4, "priority": 3 }, { "operation": "PARTITION_LEAVE", "brokerId": 2, "partitionId": 4 }, { "operation": "PARTITION_RECONFIGURE_PRIORITY", "brokerId": 0, "partitionId": 4, "priority": 2 } ] } } ```
--- ## Disk space Zeebe uses the local disk for storage of its persistent data. Therefore, if the Zeebe Broker runs out of disk space, the system is in an invalid state as the broker cannot update its state. To prevent the system from reaching an unrecoverable state, Zeebe expects a minimum size of free disk space available. If this limit is violated, the broker rejects new requests to allow the operations team to free more disk space, and allows the broker to continue to update its state. Zeebe can be configured with the following settings for the disk usage: - **camunda.data.primary-storage.disk.monitoring-enabled**: Configure if disk usage should be monitored (default: true) - **camunda.data.primary-storage.disk.monitoring-interval**: The interval in which the disk space usage is checked (default: 1 second) - **camunda.data.primary-storage.disk.free-space.replication**: When the free space available is less than this value, Zeebe pauses receiving replicated events. (default: 1GB) - For **production** use cases, we recommend to increase this value and set it approximately to `number of partitions x logSegmentSize + 1GB`. - **camunda.data.primary-storage.disk.free-space.processing**: When the free space available is less than this value, Zeebe rejects all user commands and pauses processing. (default: 2GB) - This must be greater than `free-space.replication`. - For **production** use cases, we recommend increasing this value and setting it at a minimum of `number of partitions x 2 x logSegmentSize + 1GB`. --- ## Health status ## Broker The Zeebe Broker exposes three HTTP endpoints to query its health status: - Startup check - Ready check - Health check ### Startup check Startup check endpoint is exposed via `http://{zeebe-broker-host}:9600/actuator/health/startup`. This endpoint returns a 200 response. If it is not ready, it will return a 503 error. A broker has successfully started when: - The broker has found other brokers in the cluster. - All partitions owned by this broker have started and participate in replication. - Other necessary services have started. A successful startup does not mean the broker is ready to process requests. The broker is ready only after startup has successfully completed. ### Ready check Ready check endpoint is exposed via `http://{zeebe-broker-host}:9600/actuator/health/readiness`. This endpoint returns a 200 response. If it is not ready, it will return a 503 error. A broker is ready when it installs all necessary services to start processing in all partitions. If a broker is ready, it doesn't mean it's the leader for the partitions. It means it is participating in the replication and can be either a leader or a follower of all the partitions that are assigned to it. Once it is ready, it never becomes unready again. A ready check is useful, for example, to use as a `readinessProbe` in a Kubernetes configuration to control when a pod can be restarted for rolling update. Depending on the cluster configuration, restarting one pod before the previous one is ready might make the system unavailable because the quorum of replicas is not available. By configuring a `readinessProbe` that uses the ready check endpoint, we can inform Kubernetes when it is safe to proceed with the rolling update. ### Health check Health check endpoint is exposed via `http://{zeebe-broker-host}:9600/actuator/health/status`. This endpoint returns a 200 response if the broker is healthy. If it is not healthy, it will return a 503 error. A broker is never healthy before it is ready. Unlike ready check, a broker can become unhealthy after it is healthy. Hence, it gives a better status of a running broker. A broker is healthy when it can process processes, accept commands, and perform all its expected tasks. If it is unhealthy, it may mean three things: - **It is only temporarily unhealthy**: For example, due to environmental circumstances such as temporary I/O issues. - **It is partially unhealthy**: One or more partitions could be unhealthy, while the rest of them are able to process processes. - **It is completely dead**: one or more partitions has failed in a non-recoverable way, and human intervention is required to recover. [Metrics](/self-managed/operational-guides/monitoring/metrics.md) give more insight into which partition is healthy or unhealthy. When a broker becomes unhealthy, it's recommended to check the logs to see what went wrong. (The default broker port can be configured using environment variables - respectively `MANAGEMENT_SERVER_PORT` and `MANAGEMENT_SERVER_ADDRESS` - or system properties - respectively `-Dmanagement.server.port=` or `-Dmanagement.server.address=` - to configure them) ## Gateway The Zeebe Gateway exposes three HTTP endpoints to query its health status: - Health status - `http://{zeebe-gateway}:9600/actuator/health` - Startup probe - `http://{zeebe-gateway}:9600/actuator/health/startup` - Liveness probe - `http://{zeebe-gateway}:9600/actuator/health/liveness` (The default gateway port can be configured using the environment variable `MANAGEMENT_SERVER_PORT` or the system property `-Dmanagement.server.port=`) ### Health status The gateway is **healthy** if it: - Started successfully - Has sufficient free memory and disk space to work with - Is able to respond to requests within a defined timeout - Is aware of other nodes in the cluster - Is aware of leaders for partitions - All its partitions are healthy The gateway is **degraded** if it also meets the **healthy** standards above, with the exception that at least **one** partition is healthy instead of **all** partitions. ### Startup probe The gateway starts if it finished its boot sequence successfully and is ready to receive requests. It no longer starts when it initiates the shutdown sequence. The started probe can be used as Kubernetes startup probe. ### Liveness probe The gateway is live if it: - Started successfully - Has a minimal amount of free memory and disk space to work with - Is able to respond to requests within a defined timeout, or misses the timeout for less than 10 minutes - Is aware of other nodes in the cluster, or lost awareness of other nodes for less than five minutes - Is aware of leaders for partitions, or lost awareness of partition leaders for less than five minutes The liveness probe can be used as Kubernetes liveness probe. ### Status responses Each endpoint returns a status which can be one of the following: - `UNKNOWN` (HTTP status code 200) - `UP` (HTTP status code 200) - `DEGRADED` (HTTP status code 200) - `DOWN` (HTTP status code 503) - `OUT_OF_SERVICE` (HTTP status code 503) The gateway's cluster health indicator reports `DEGRADED` when at least one partition is healthy, but not all of them are. If details are enabled (default), the response will also contain additional details. ### Customization Health indicators are set to sensible defaults. For specific use cases, it might be necessary to [customize health indicators](../configuration/gateway-health-probes.md). --- ## Management API As well as the [REST](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) and [gRPC API](/apis-tools/zeebe-api/grpc.md) for process instance execution, the Zeebe Gateway exposes an HTTP endpoint for cluster management operations. ## About this API This API is not expected to be used by a typical user, but by a privileged user such as a cluster administrator. It is exposed via a different port, and configured using configuration `management.server.port` (or via environment variable `MANAGEMENT_SERVER_PORT`). By default, this is set to `9600`. The API is a custom endpoint available via [Spring Boot Actuator](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints). :::info For additional configurations such as security, refer to the official [Spring Boot documentation](https://spring.io/guides). ::: The management port is typically not publicly exposed. If the machine where you run these commands cannot reach the gateway, create a private connection such as `kubectl port-forward svc/camunda-zeebe-gateway 9600:9600`, then use `localhost` as the gateway host. The examples use `http://` for a management endpoint without TLS. If your endpoint uses TLS, use `https://` and the appropriate `curl` TLS options. ### Operations This API currently supports the following operations: - [Rebalancing](/self-managed/components/orchestration-cluster/zeebe/operations/rebalancing.md) - [Pause and resume exporting](#exporting-api) - [Enable and disable exporter](#exporters-api) - [Update partition distribution](#partitioning-api) - [Add or re-add a zone](#add-or-re-add-a-zone) - [Remove a zone](#remove-a-zone) - [Migrate a zone](#migrate-a-zone-to-a-zone-aware-topology) ## Exporting API Use the Exporting API for the followings: - As a debugging tool. - When taking a backup of Camunda 8 (see [backup and restore](/self-managed/operational-guides/backup-restore/backup-and-restore.md)). :::warning This endpoint always returns HTTP `200`. Check the `status` field in the response body to determine whether the operation succeeded: `204` indicates success and `500` indicates failure. If the request fails, verify that all brokers are running and retry. ::: The operation requires a complete cluster topology. If a broker is unavailable, the request fails entirely — no partitions are paused or resumed. Retry when all brokers are available. **Success response:** ```json { "body": null, "status": 204, "contentType": null } ``` **Failure response:** ```json { "body": { "message": "Expected 3 members of partition 1 but found 2, current topology: ..." }, "status": 500, "contentType": null } ``` ### Pause exports To pause exporting on all partitions, send the following request to the gateway's management endpoint. ``` POST actuator/exporting/pause ``` When all partitions pause exporting, the response contains `"status": 204`. If the request fails, some partitions may have paused exporting. Therefore, it is important to either retry until success or revert the partial pause by resuming exporting. ### Resume exports After exporting is paused, it must eventually be resumed. Otherwise, the cluster could become unavailable. To resume exporting, send the following request to the gateway's management endpoint: ``` POST actuator/exporting/resume ``` When all partitions have resumed exporting, the response contains `"status": 204`. If the request fails, only some partitions may have resumed exporting. Therefore, it is important to retry until successful. ### Soft pause exports The soft pause feature can be used when you want to continue exporting records, but don't want to delete those records (log compaction) from Zeebe. This is particularly useful during hot backups. Learn more about [using this feature for hot backups](/self-managed/operational-guides/backup-restore/backup-and-restore.md). ``` POST actuator/exporting/pause?soft=true ``` When all partitions soft pause exporting, the response contains `"status": 204`. If the request fails, some partitions may have soft paused exporting. Therefore, either retry until success or revert the partial soft pause by resuming the export. :::warning Broker disk usage grows throughout the soft-pause window because log compaction is blocked. Keep the window as short as possible and resume exporting promptly once the backup completes. Avoid restarting brokers while soft pause is active. After a restart, exporters resume from the last persisted position (before soft-pausing started) and re-export all records from the soft-pause window. Recovery time is proportional to how long soft pause was active. For a real-world example of disk growth and recovery, see the [full-disk chaos day report](https://camunda.github.io/zeebe-chaos/2026/06/18/Full-disk-due-to-soft-pause-exporters). ::: ## Exporters API The Exporters API allows for enabling, disabling or deleting configured exporters. By default, all configured exporters are enabled. The enable and disable functionality is specifically useful for [dual region deployment](/self-managed/deployment/helm/operational-tasks/dual-region-ops.md) operations. - **Enabled**: Records are exported to the exporter. The log is compacted only after the records are exported. - **Disabled**: Records are _not_ exported to the exporter, and the log is compacted. :::info See the [Exporters API reference](/apis-tools/management-api/specifications/exporters/exporters-management-api.info.mdx) for all endpoints and schemas. ::: :::note The `camunda-zeebe-gateway` service on port 9600 exposes the exporter endpoints. ::: ### Enable an exporter Enable a configured, disabled exporter: ```bash POST actuator/exporters/{exporterId}/enable ``` When you enable the exporter, you can also optionally initialize it from another exporter using `initializeFrom`: ```bash POST actuator/exporters/{exporterId}/enable { "initializeFrom": "" } ``` `initializeFrom` accepts an existing exporter's ID. Both the exporter you're enabling and the exporter you're initializing from must be the same [type](../exporters/exporters.md). For example, you can't use an Elasticsearch exporter's ID to initialize an OpenSearch exporter. After you enable the exporter, new records will be exported to it. ### Disable an exporter To disable an exporter, send the following request to the gateway's management API: ``` POST actuator/exporters/{exporterId}/disable ``` After disabling the exporter, no records will be exported to this exporter. Other exporters continue exporting. Removing an exporter from the cluster configuration through Helm values only drops it from the static configuration. For example, disabling Optimize removes the Elasticsearch or OpenSearch exporter. The exporter is still declared in the dynamic cluster configuration, which prevents log compaction and increases disk usage. To fully deactivate it, explicitly disable it using the request above, and confirm that every broker reports the exporter as `DISABLED` (see [Monitor an exporter](#monitor-an-exporter)). ### Delete an exporter To delete an exporter permanently from the system, first remove the configuration of the exporter from the application. Then send the following request to the gateway's management API: ``` DELETE actuator/exporters/{exporterId} ``` If the configuration is deleted, the exporter remains in the system but enters a blocked state. This prevents log compaction and thus increases the disk usage. - To fully remove the exporter, it must be deleted using the Management API to ensure all references to it are removed. - To re-add the exporter, restore its configuration in the application properties and restart the system. Alternatively, if you no longer wish to use an exporter, you can disable it using the management API. The exporter can be re-enabled at any time without requiring a system restart. ### Monitor an exporter All requests to change the state of the exporters are processed asynchronously. To monitor the status of the exporters, send the following request to the gateway's management API: ``` GET actuator/exporters/ ``` The response is a JSON object that lists all configured exporters with their status: ```json [ { "exporterId": "elasticsearch0", "status": "ENABLED" }, { "exporterId": "elasticsearch1", "status": "DISABLED" } ] ``` An exporter can also report `CONFIG_NOT_FOUND`. It is still enabled in the dynamic cluster configuration, but its configuration has been removed from the application properties, for example after Optimize or the Elasticsearch subchart was disabled in Helm values. In that state it cannot export and blocks log compaction. Disable it explicitly with the request above rather than relying on the configuration change alone. ## Cluster API See the [cluster topology management API reference](/apis-tools/management-api/specifications/cluster/cluster-topology-management-api.info.mdx) for all endpoints and schemas. ### Monitoring API If you just submitted an operation, use the `changeId` returned in the response with the [configuration change endpoint](#monitor-a-configuration-change) to monitor it. Use `GET actuator/cluster` to retrieve the current cluster topology. For clusters with multiple Physical Tenants, always use the configuration change endpoint instead of relying on the pending change reported by `GET actuator/cluster`. #### Request ``` GET actuator/cluster ``` #### Response The response is a JSON object. See the [API reference](/apis-tools/management-api/specifications/cluster/cluster-topology-management-api.info.mdx) for details: ``` { "version": , "brokers": [ { "id": , "state": "ACTIVE", "version": , "lastUpdatedAt": "", "partitions": [ { "id": , "state": "ACTIVE", "priority": } ] } ], "lastChange": { "id": , "status": "COMPLETED", "startedAt": "", "completedAt": "" }, "pendingChange": { "id": , "status": "IN_PROGRESS", "completed": [], "pending": [ { "operation": "BROKER_ADD", "brokerId": } ] }, "partitioning": { ... }, "routingState": { ... } } ``` - `version`: The version of the current cluster topology. The version is updated when the cluster is scaled up or down. - `brokers`: A list of current brokers. Each broker includes its ID, state, version, last update timestamp, and partition distribution. - `partitions`: A list of partitions assigned to a broker, including each partition's ID, state, and priority. - `lastChange`: Details about the last completed scaling operation, including its ID, status, and start and completion timestamps. - `pendingChange`: Details about the ongoing scaling operation, including completed and pending operations. Pending operations can include broker additions, partition joins, partition leaves, and partition priority reconfigurations. - `partitioning`: The cluster's partitioning configuration. - `routingState`: The current routing state of the cluster. #### Monitor a configuration change Use this endpoint to retrieve the status and operations of one configuration change. Use the `changeId` from an asynchronous operation's response to poll the change every five seconds until it reaches a terminal status. ##### Request ``` GET actuator/cluster/changes/{changeId} ``` Poll the change every five seconds until it reaches a terminal status. Set `CHANGE_ID` to the `changeId` returned by the operation: ```bash CHANGE_ID="{changeId}" while true; do curl -s "http://{zeebe-gateway}:9600/actuator/cluster/changes/${CHANGE_ID}" echo sleep 5 done ``` ##### Response The response is a JSON object with the following properties: ```json { "id": , "status": "IN_PROGRESS", "startedAt": "", "completedAt": "", "completed": [...], "pending": [...] } ``` - `id`: The ID of the configuration change. - `status`: The status of the change. Possible values are `IN_PROGRESS`, `COMPLETED`, `FAILED`, and `CANCELLED`. - `startedAt`: The time when the change started. - `completedAt`: The time when the change completed, if it has completed. - `completed`: The operations completed so far. - `pending`: The operations that are still pending. ### Partitioning API Use this endpoint to update the [zone-aware](/self-managed/components/orchestration-cluster/zeebe/configuration/zone-aware-clusters.md) partition distribution configuration. Exactly one of `config` or `zonePriorities` must be set in the request body. - Setting `config` persists a new partition distribution configuration and applies it immediately, computing the necessary partition join, leave, and priority-reconfiguration operations. When migrating a bare or partially zoned cluster to zone-aware, list zones in `config.zones` in the order they should receive the existing (bare) nodes: the first zone receives node `0`, the second node `1`, and so on, wrapping around by zone count. This order only matters for that one-time migration; once all zones are migrated, every other operation addresses zones by name. - Setting `zonePriorities` reorders the zones' priorities on a fully zone-aware cluster. The existing priority values are reused and reassigned to a different zone based on the order of the zones in the request: the first zone gets the highest existing priority value, the second zone the next highest, and so on. No new priority values are introduced. This only updates the priorities; it does not itself move partition leaders — leaders move to the newly-preferred zone on the next election (for example, one triggered by a separate rebalance). The request must list exactly the currently configured zones, and is idempotent. #### Request ``` PUT actuator/cluster/partitioning ```
Example request: set partition distribution config ``` curl -X 'PUT' \ 'http://localhost:9600/actuator/cluster/partitioning' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "config": { "scheme": "ZONE_AWARE", "zones": [ { "name": "zone-a", "numberOfReplicas": 2, "priority": 1000 }, { "name": "zone-b", "numberOfReplicas": 1, "priority": 500 } ] } }' ```
Example request: reorder zone priorities ``` curl -X 'PUT' \ 'http://localhost:9600/actuator/cluster/partitioning' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "zonePriorities": ["zone-b", "zone-a"] }' ```
##### Dry run You can do a dry run without executing the change by setting the `dryRun` request parameter to `true`. By default, `dryRun` is set to `false`. #### Response {#partitioning-response} The response is a JSON object. See the [API reference](/apis-tools/management-api/specifications/cluster/cluster-topology-management-api.info.mdx) for details: ``` { "changeId": , "currentTopology": [...], "plannedChanges": [...], "expectedTopology": [...] } ``` - `changeId`: The ID of the changes initiated by this request. This can be used to monitor the progress of the operation. - `currentTopology`: A list of current brokers and the partition distribution. - `plannedChanges`: A sequence of operations that must be executed to reach the new configuration. - `expectedTopology`: The expected list of brokers and the partition distribution once the change has completed. ### Zones API Use the Zones API to add, remove, or migrate zones in a [zone-aware](/self-managed/components/orchestration-cluster/zeebe/configuration/zone-aware-clusters.md) cluster. The operations update the persisted partition distribution and run asynchronously. Use the [Monitoring API](#monitoring-api) to follow each change until its status is `COMPLETED`. #### Add or re-add a zone To add a zone, first deploy its brokers and connect them to the existing cluster. Configure the brokers to use the zone you want to add. The new brokers join cluster membership, but they do not host partitions until you add the zone through the Zones API. To re-add a previously removed zone, start the operator-supplied brokers before sending the request. If you re-add only some of the zone's brokers, list their broker IDs explicitly in the `brokers` array. The request adds the supplied brokers to the persisted partition distribution and schedules the partition-join operations needed to assign their partitions. ##### Request ``` POST actuator/cluster/zones/{zoneId} { "numberOfReplicas": , "priority": , "numberOfBrokers": , "brokers": [, , ...] } ``` The request body must include `numberOfReplicas`, `priority`, and exactly one of `numberOfBrokers` or `brokers`. Use `numberOfBrokers` when the zone's broker IDs are contiguous. The value is the number of brokers deployed in the zone, from which the broker IDs `_0` through `_` are derived. These are the IDs the brokers of a zone-aware cluster assign themselves, so a zone whose brokers are numbered from zero without gaps needs nothing else. `numberOfBrokers` must be at least `1`; a lower value is rejected with HTTP `400`. Use `brokers` when the broker IDs are not contiguous. List each broker ID explicitly in this array. Setting both `numberOfBrokers` and `brokers`, or omitting both, is rejected with HTTP `400`.
Example requests ``` curl -X 'POST' \ 'http://localhost:9600/actuator/cluster/zones/zone-b' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "numberOfReplicas": 2, "priority": 500, "numberOfBrokers": 3 }' ``` The same request naming the brokers explicitly: ``` curl -X 'POST' \ 'http://localhost:9600/actuator/cluster/zones/zone-b' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "numberOfReplicas": 2, "priority": 500, "brokers": ["zone-b_0", "zone-b_1", "zone-b_2"] }' ```
###### Dry run You can do a dry run without executing the change by setting the `dryRun` request parameter to `true`. By default, `dryRun` is set to `false`. ##### Response The response is a JSON object with the same shape as the [partitioning response](#partitioning-response). The `changeId` identifies the asynchronous operation. Poll the [Monitoring API](#monitoring-api) and wait until the operation is `COMPLETED` before shutting down brokers or taking further action. After the operation completes, verify that the zone is present under `partitioning` and that its brokers host their assigned partitions in the `brokers` array. You can also query the [Orchestration Cluster REST API specification for `GET /v2/topology`](/apis-tools/orchestration-cluster-api-rest/specifications/get-topology.api.mdx) to verify the broker and partition assignments. #### Remove a zone By default, this operation gracefully drains the zone's partitions to the remaining zones before removing its brokers from cluster membership. Set `force=true` only if the zone is down or its brokers are unreachable. :::warning Forced removal of nodes that are running/reachable may cause data loss in extreme circumstances ::: ##### Request ``` DELETE actuator/cluster/zones/{zoneId}?force={force} ``` The `force` parameter defaults to `false`.
Example request ``` curl -X 'DELETE' \ 'http://localhost:9600/actuator/cluster/zones/zone-b?force=false' \ -H 'accept: application/json' ```
###### Dry run You can do a dry run without executing the change by setting the `dryRun` request parameter to `true`. By default, `dryRun` is set to `false`. ##### Response The response is a JSON object with the same shape as the [partitioning response](#partitioning-response). The `changeId` identifies the asynchronous operation. Poll the [Monitoring API](#monitoring-api) and wait until the operation is `COMPLETED` before shutting down brokers or taking further action. After the operation completes, verify that the removed zone is no longer present under `partitioning` and that its brokers no longer host partitions. Only then shut down the removed zone's brokers or scale down its StatefulSet. #### Migrate a zone to a zone-aware topology Migrates one zone of a bare or partially zoned cluster to a zone-aware topology. The request contains only the zone name. Before migrating a zone, update the persisted partition distribution with [`PUT /cluster/partitioning`](#partitioning-api), using a zone-aware partition distribution. :::note For dual-region clusters, migrate the secondary zone first (odd-numbered nodes), then migrate the primary zone. ::: The zone must already exist in the persisted partitioning configuration. When all configured zones have been migrated, the cluster becomes fully zoned and subsequent operations address zones by name. ##### Request ``` PUT actuator/cluster/zones { "zone": } ```
Example request ``` curl -X 'PUT' \ 'http://localhost:9600/actuator/cluster/zones' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "zone": "zone-b" }' ```
###### Dry run You can do a dry run without executing the change by setting the `dryRun` request parameter to `true`. By default, `dryRun` is set to `false`. ##### Response The response is a JSON object with the same shape as the [partitioning response](#partitioning-response). The `changeId` identifies the asynchronous operation. Poll the [Monitoring API](#monitoring-api) and wait until the operation is `COMPLETED` before taking further action. ### Upgrade Readiness API Use the Upgrade Readiness API to check if the Orchestration Cluster is ready for an upgrade to the next minor version. The API reports the status of each registered upgrade-readiness condition, grouped by physical tenant. #### Check the upgrade readiness status To check the upgrade readiness status, send a request to the Upgrade Readiness API. The API returns whether every registered condition has reached the `MIGRATED` state for every known physical tenant. ##### Request ``` GET actuator/upgradeReadiness ``` ##### Response The response is a JSON object. See the [API reference](/apis-tools/management-api/specifications/upgrade-readiness/upgrade-readiness-api.info.mdx) for details: ``` { "upgradeable": true, "physicalTenants": { "default": { "rdbmsSchemaMigrated": { "state": "MIGRATED", "detail": "schema version 8.10.0 matches the application version" }, "brokerVersionMigrated": { "state": "MIGRATED", "detail": "every broker is running 8.10.0" }, "exporterMigrated": { "state": "MIGRATED", "detail": "All partitions migrated" }, "rocksDbMigrated": { "state": "MIGRATED", "detail": "All partitions migrated" } } } } ``` - `upgradeable`: Whether the cluster is upgradeable to the next minor version. - `true`: Every registered condition reported `MIGRATED` for every known physical tenant. - `false`: At least one condition is not `MIGRATED`, or readiness data is not available yet. Check the detailed status reports. - `physicalTenants`: A map of physical tenants and their upgrade readiness status. - `rdbmsSchemaMigrated`: Whether the RDBMS schema has been migrated to the latest minor version. - `brokerVersionMigrated`: Whether all brokers are running the expected version. - `exporterMigrated`: Whether the exporters have exported and acknowledged all records of the previous minor version. - `rocksDbMigrated`: Whether the RocksDB has been migrated and the last snapshot is of the latest minor version. Each condition has a `state` of `MIGRATED`, `MIGRATION_IN_PROGRESS`, or `UNKNOWN`, and a human-readable `detail` message. The set of conditions depends on the providers registered in the cluster. Do not treat `upgradeable` as meaningful until all planned conditions are registered. --- ## Cluster mode A cluster mode change transitions every broker in an Orchestration Cluster between processing and recovery mode. The selected mode determines whether the partitions process data or remain inactive while you restore the cluster from a backup. A mode change is a cluster configuration change, similar to [cluster scaling](cluster-scaling.md). The API acknowledges the request when it accepts the change, and each broker applies the transition asynchronously. ## Cluster modes | Mode | Behavior | | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PROCESSING` | The default broker operational mode. Each broker installs the full set of partition services. | | `RECOVERING` | Each local partition is registered as `inactive` and does not join its Raft group, so the cluster does not elect a leader, process, replicate, or export records. Each broker starts a reduced set of services. | ### Operations available in recovery mode - Query the cluster state and topology. - Query the primary storage backup store. - Restore primary storage from a backup. ## Change the cluster mode Send a `PATCH` request to the `/mode` endpoint of the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/specifications/change-cluster-mode.api.mdx). The endpoint uses the Orchestration Cluster REST API port, which is `8080` by default. ### Required authorizations When authorization is enabled, changing the cluster mode requires one of the following permissions: | Resource type | Permission | | ------------- | ---------- | | `SYSTEM` | `UPDATE` | | `BACKUP` | `RESTORE` | For how permissions are granted to users, clients, groups, and roles, see [available resources](/components/concepts/access-control/authorizations.md#available-resources). ### Send the mode change request To enter recovery mode: ```bash curl -X PATCH 'http://localhost:8080/v2/mode?mode=RECOVERING' \ -H 'Accept: application/json' ``` To return to processing mode: ```bash curl -X PATCH 'http://localhost:8080/v2/mode?mode=PROCESSING' \ -H 'Accept: application/json' ``` ### Request parameters | Parameter | Required | Description | | --------- | -------- | ----------------------------------------------------------------------------------------------------------------- | | `mode` | Yes | The target mode, either `RECOVERING` or `PROCESSING`. | | `dryRun` | No | If `true`, validates the request and returns the resulting plan without applying the change. Defaults to `false`. | ### Response A successful request returns `200` with the ID of the triggered cluster change and the ordered list of operations that will be applied. Use `dryRun=true` to review the change plan before applying it: ```bash curl -X PATCH 'http://localhost:8080/v2/mode?mode=RECOVERING&dryRun=true' \ -H 'Accept: application/json' ``` ## Monitor the transition The mode change request returns before the transition completes. Track the transition after you send the request. Query the [cluster topology](/apis-tools/orchestration-cluster-api-rest/specifications/get-topology.api.mdx) to see the state of each broker's partitions: ```bash curl 'http://localhost:8080/v2/topology' ``` While a broker is in recovery mode, its partitions report `role: inactive` and `state: recovering`. After the cluster returns to processing mode, each partition reports `role: leader` or `role: follower` and `state: active`. You can also query the cluster management API on the management port (`9600` by default) to follow the change by its ID: ```bash curl 'http://localhost:9600/orchestration/actuator/cluster' ``` ## Cluster mode change considerations - Entering recovery mode stops all processing in the cluster. Plan the change as a maintenance operation, and expect client requests to fail while the cluster is recovering. - Brokers already in the target mode are not included in the plan. Repeating the same request after the change completes results in an empty plan. - A mode change is a cluster configuration change, so only one cluster configuration operation can be in progress at a time. You can cancel an active cluster configuration change. --- ## Move Raft leadership between zones You can move Raft partition leadership from one zone to another by reordering zone priorities and then rebalancing the cluster. This procedure changes only the priorities used for leader election. It doesn't change the partitioning or move replicas between zones. Use this procedure to: - Perform a planned zone switchover. - Restore the preferred leadership placement after a zone failover. - Follow the sun by moving leadership to the preferred active region. ## Align leadership with secondary storage Raft leadership placement is especially important when the writer for RDBMS secondary storage, such as an Amazon Aurora writer instance, and Raft leaders are in different regions. Cross-region communication with the secondary storage writer adds network round-trip latency. After the RDBMS secondary storage writer moves to another region, use this procedure to move Raft leadership to the same region when possible. The placement of Elasticsearch or OpenSearch secondary storage has less impact on this decision. In a multi-region setup, records are exported to secondary storage in both regions concurrently, so exporting has less dependency on cross-region network round-trip latency. Consider the location of the RDBMS secondary storage writer first when choosing the preferred leader zone. ## Prerequisites Before moving leadership, confirm the following conditions: - The cluster is fully [zone-aware](/self-managed/components/orchestration-cluster/zeebe/configuration/zone-aware-clusters.md). - All brokers and partitions are healthy. - The target zone's replicas are caught up with the current leaders. - You can access the Zeebe Gateway [Management API](management-api.md) on its management port. The default port is `9600`. - You have identified a low-load maintenance window. Rebalancing can cause temporary unavailability while partition leaders are re-elected. The management port is typically not publicly exposed. Its access and TLS configuration are separate from the v2 REST API. If the gateway isn't reachable from the machine where you run these commands, use a private connection such as `kubectl port-forward svc/camunda-zeebe-gateway 9600:9600`, then use `localhost` for `{zeebe-gateway}`. For Amazon ECS deployments, use AWS Systems Manager port forwarding through ECS Exec, an SSH tunnel through a bastion host, or another private network path to reach port `9600`. You can also execute the commands from a broker task or pod that can reach the gateway. The examples use `http://` for a management endpoint without TLS. If your management endpoint uses TLS, use `https://` and the appropriate `curl` TLS options. ## Check the current zone priorities Use the Management API to retrieve the current cluster topology and partitioning: ```bash curl \ 'http://{zeebe-gateway}:9600/actuator/cluster' \ -H 'accept: application/json' \ | jq '.partitioning.zones | sort_by(-.priority)[] | {name, priority}' ``` The command returns each configured zone's name and priority, sorted from highest to lowest priority. If `partitioning.zones` is missing or empty, the cluster isn't zone-aware and can't use this procedure. If `jq` isn't installed, omit the pipe to `jq` and manually inspect `partitioning.zones` in the JSON response. You can also inspect `brokers[].partitions[]` in the full response to see the priority assigned to each partition replica. A higher priority makes a replica the preferred leader during an election. The zone with the highest configured priority is therefore the preferred zone for Raft partition leaders. Recording the current order also ensures that you preserve the relative priorities of any zones you aren't swapping. ## Reorder the zone priorities The [Partitioning API](management-api.md#partitioning-api) accepts a `zonePriorities` list. The first zone in the list receives the highest existing priority, the second zone receives the next highest priority, and so on. The request must list exactly the currently configured zones. If the list doesn't exactly match the currently configured zones, the request is rejected. The operation is idempotent. To exchange the priorities of two zones, reverse their positions and leave the other zones in their current order. For example, if `zone-a` currently has the highest priority and `zone-b` has the next highest priority, use the following request to make `zone-b` the preferred leader zone: ```bash curl -X PUT \ 'http://{zeebe-gateway}:9600/actuator/cluster/partitioning?dryRun=true' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "zonePriorities": ["zone-b", "zone-a"] }' ``` Review the `plannedChanges` and `expectedTopology` fields in the dry-run response. When the result matches the intended priority order, submit the same request without the `dryRun` parameter: ```bash curl -X PUT \ 'http://{zeebe-gateway}:9600/actuator/cluster/partitioning' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "zonePriorities": ["zone-b", "zone-a"] }' ``` The priority change is asynchronous. Use the [configuration change monitoring API](management-api.md#monitor-a-configuration-change) with the `changeId` from the response to monitor the change until it reaches a terminal status. Wait until the change completes before rebalancing. ## Rebalance the cluster Changing zone priorities doesn't trigger a leader election. After the priority change completes, [manually rebalance the cluster](rebalancing.md#manual-rebalancing) to move partition leadership toward the newly preferred zone. Before rebalancing, review the guide's [limitations](rebalancing.md#limitations), [impact](rebalancing.md#rebalancing-impact), and [readiness checks](rebalancing.md#when-to-rebalance). During rebalancing, partitions can be temporarily unavailable while new leaders are elected. After rebalancing completes, use the Orchestration Cluster REST API [`GET /v2/topology`](/apis-tools/orchestration-cluster-api-rest/specifications/get-topology.api.mdx) endpoint to verify the partition leaders. You can also use `GET /actuator/cluster` and your cluster metrics to confirm the updated priorities, partition assignments, and replication health. The topology endpoint uses the v2 API rather than the Management API and requires different access permissions. Ensure your credentials are authorized for the v2 API before using it. --- ## Network ports The broker cluster sits behind the gateway, which handles all requests (via REST and gRPC servers) from clients/workers and forwards events to brokers. To communicate with clients/workers, the gateway will start two different ports to communicate via its REST API (default port 8080) and gRPC (default port 26500). These are respectively controlled via `server.port: 8080` (REST) and `zeebe.gateway.network.port: 26500` (gRPC). Additionally, it will need to communicate with other nodes (mostly brokers) in the cluster (default port 26502), configured via `zeebe.gateway.cluster.port: 26502`. To join the cluster, it will also need at least one initial contact point, typically a broker, configured via `zeebe.gateway.cluster.initialContactPoints: [127.0.0.1:26502]`. :::note You can use all broker connections instead of one to make the startup process of the Zeebe Gateway more resilient. ::: The relevant [configuration](../configuration/configuration.md) settings are: ``` Config file server: port: 8080 # REST zeebe: gateway: network: port: 26500 # gRPC cluster: port: 26502 initialContactPoints: [127.0.0.1:26502] Environment Variables SERVER_PORT = 8080 ZEEBE_GATEWAY_NETWORK_PORT = 26500 ZEEBE_GATEWAY_CLUSTER_PORT = 26502 ZEEBE_GATEWAY_CLUSTER_INITIALCONTACTPOINTS = 127.0.0.1:26502 ``` The broker needs to receive communication from the gateway and from other brokers. It also exposes a port for monitoring. - `camunda.cluster.network.command-api.port: 26501`: Gateway-to-broker communication, using an internal SBE (Simple Binary Encoding) protocol. This is the Command API port. This should be exposed to the gateway. - `camunda.cluster.network.internal-api.port: 26502`: Inter-broker clustering using the Gossip and Raft protocols for partition replication, broker elections, topology sharing, and message subscriptions. This should be exposed to other brokers and the gateway. - `management.server.port: 9600`: Metrics and Readiness Probe. Prometheus metrics are exported on the route `/actuator/prometheus`. There is a readiness probe on `/actuator/health/readiness`. This is the same port the [management API](./management-api.md) is served on. The relevant [configuration](../configuration/configuration.md) settings are: ``` Config file camunda: cluster: network: command-api: port: 26501 internal-api: port: 26502 management: server: port: 9600 Environment Variables CAMUNDA_CLUSTER_NETWORK_COMMANDAPI_PORT = 26501 CAMUNDA_CLUSTER_NETWORK_INTERNALAPI_PORT = 26502 MANAGEMENT_SERVER_PORT = 9600 ``` --- ## Rebalancing Rebalancing re-elects partition leaders according to the configured partitioning and priority settings. In a round-robin cluster, rebalancing can distribute leadership more evenly across brokers. In a zone-aware cluster, rebalancing can move leadership toward the zone with the highest priority. Zeebe prefers an even leader distribution in round-robin clusters when electing new leaders, but it doesn't trigger a re-election unless a leader becomes unavailable or you request a rebalance. When a Zeebe cluster uses an uneven leader distribution, for example because it lost a leader and elected a suboptimal broker, manually requesting a rebalance can restore a more suitable distribution. ## Manual rebalancing Request rebalancing through the coordinated rebalancing API under `/cluster/v2/rebalance` using [cluster admin](/components/admin/cluster-admin.md) credentials. Unlike an open election, this API transfers leadership for each partition directly to its highest-priority replica, one partition at a time, so at most one partition is affected at any moment. Start a rebalance with `POST /cluster/v2/rebalance`: ```bash curl -X POST https://{cluster-host}/cluster/v2/rebalance ``` Check the cluster's balance state and the progress of each partition with `GET /cluster/v2/rebalance`: ```bash curl -X GET https://{cluster-host}/cluster/v2/rebalance ``` Stop a running rebalance once the transfer in flight has finished with `DELETE /cluster/v2/rebalance`: ```bash curl -X DELETE https://{cluster-host}/cluster/v2/rebalance ``` To preview the plan a rebalance would carry out, without pausing any partition or moving any leadership, pass `dryRun=true`: ```bash curl -X POST "https://{cluster-host}/cluster/v2/rebalance?dryRun=true" ``` Each partition reports how its transfer ended or why it was skipped (already led by the desired leader, replication lag too high, replication timed out, and so on). A `POST /cluster/v2/rebalance` request accepts an optional JSON body to override the rebalancing parameters for the request. Any omitted parameters use the configured defaults: ```bash curl -X POST https://{cluster-host}/cluster/v2/rebalance \ -H 'Content-Type: application/json' \ -d '{ "replicationLagThreshold": 8388608, "replicationTimeout": "PT10S", "maxTransferAttempts": 3, "leaderWaitTimeout": "PT1M" }' ``` - `replicationLagThreshold`: maximum replication lag, in bytes, a desired leader may have for its transfer to be accepted. Defaults to `8388608` (8 MB). - `replicationTimeout`: an ISO-8601 duration for how long a partition may stay frozen waiting for its desired leader to catch up before the transfer is abandoned. Defaults to `PT10S` (10 seconds). - `maxTransferAttempts`: how many times the current leader prompts the desired leader to take over before giving up. Defaults to `3`. - `leaderWaitTimeout`: an ISO-8601 duration for how long the coordinator waits for a leaderless partition to elect a leader before reporting `NO_LEADER` and moving on. Defaults to `PT1M` (1 minute). The default values for these parameters are configurable as broker options (see the [`camunda.cluster.raft.rebalance` properties reference](/self-managed/components/orchestration-cluster/zeebe/configuration/broker.md#camundaclusterraftrebalance)). :::note The previous `/actuator/rebalance` endpoint is deprecated but still available. It implements a simpler and more disruptive rebalancing mechanism by triggering simultaneous elections on all unbalanced partitions. This document otherwise describes only the behavior of the new rebalancing endpoint. ::: Track the rebalancing progress with `GET /cluster/v2/rebalance`, or by observing the `zeebe_cluster_rebalance_elapsed`, `zeebe_cluster_rebalance_partition_duration`, `zeebe_cluster_rebalance_partition_state`, and `zeebe_cluster_partition_balanced` [metrics](/self-managed/operational-guides/monitoring/metrics.md). The Zeebe Grafana dashboard has a dedicated `Rebalancing` section covering cluster balance, per-partition rebalance state and outcomes, and how long partitions stay paused for transfer. ### Limitations Manual rebalancing is not guaranteed to succeed for every partition in every attempt, but it never fails silently: each partition transfer ends in an explicit, reported outcome, and a partition that cannot be rebalanced keeps its current leader rather than being left leaderless. Before transferring leadership for a partition, the coordinator checks the desired leader's replication lag against `replicationLagThreshold`. If the lag is already too high, the transfer is rejected immediately with `LAG_TOO_HIGH` and the partition does not enter the paused state. If the lag is within tolerance, the partition is paused and the desired leader is given up to `replicationTimeout` to catch up: - If it catches up in time, leadership transfers to it and the pause is lifted. - If it does not catch up in time, the transfer ends with `REPLICATION_TIMED_OUT` and the partition resumes under its current leader. Once caught up, the coordinator prompts the desired leader to take over, retrying up to `maxTransferAttempts` times. If leadership still hasn't moved, the transfer ends with `TIMEOUT_NOW_EXHAUSTED` and the partition again resumes under its current leader. ### Rebalancing impact :::note Rebalancing transfers partitions one at a time, so at most one partition is affected at any moment. If the desired leader for a partition (the node with the highest priority) is _already_ the leader, rebalancing is a no-op for this partition. If a cluster is already perfectly balanced, a rebalancing call is a no-op. ::: While a partition is paused for transfer, it cannot process, export, or accept new commands, and it briefly has no leader during the handoff itself. Because partitions transfer one at a time, this affects only the partition currently being rebalanced, not the whole cluster. This is typically observed externally as: - Increased error rates from clients, for requests routed to the paused partition - Increased processing latency, as service tasks on that partition complete later and process instances on it progress more slowly - Increased exporting latency, as new data from that partition appears in Operate later than expected ### When to rebalance The `GET /cluster/v2/rebalance` endpoint exposes a live balance status for the cluster, indicating which (if any) partitions are not currently led by the desired leader. If you are using the Zeebe Grafana dashboard, this information is also visible in the `Rebalancing` section. :::warning Generally, you should rebalance when your cluster is under low load to avoid disruptions to throughput and latency, and to maximize the percentage of unbalanced partitions that are successfully transferred. ::: If it is necessary to rebalance while under load, you can use the available parameters to manage the trade-off between disruption and effectiveness of the rebalance: tuning `replicationLagThreshold` and `replicationTimeout` higher will increase effectiveness at the cost of greater temporary disruption, while lowering them will decrease disruption at the cost of potentially transferring fewer partitions. As described in [Limitations](#limitations), rebalancing only works if the desired leader for each partition is not lagging too far behind the current leader. You can verify this using the `zeebe_raft_replication_lag_bytes` metric, filtering by the `partition` and `follower` labels to determine how far a replica lags behind, in bytes. This is the same value the coordinated rebalancing API checks against `replicationLagThreshold` before accepting a transfer. The closer this value is to `0`, the more likely rebalancing is to succeed. If the desired leader has a significant lag, triggering a rebalance will likely cause a temporary performance drop without achieving a better distribution. :::note If you are using Prometheus, you can query the replication lag for a given partition and desired leader with: ```promql sum(zeebe_raft_replication_lag_bytes{partition=~"$partition", follower=~"$follower"}) by (partition, follower) ``` Replace `$partition` and `$follower` by the desired combination. The Zeebe Grafana dashboard visualizes this information in the `Raft` section (in a graph named `Follower replication lag`). ::: Once you have confirmed that rebalancing is likely to succeed, consider the trade-off: rebalancing can improve long-term cluster performance by achieving an optimal leader distribution, but it causes a temporary performance impact and potential unavailability window. Decide whether the long-term benefit outweighs the short-term disruption. :::note If you are using Prometheus, you can query the total replication rate across all partitions with: ```promql sum(rate(atomix_append_entries_data_rate_total[1m])) ``` This returns the cluster replication rate in bytes per second. You can find this in the Zeebe Grafana dashboard under the `Raft` section, visualized as a graph named `Leader append data rate`. ::: --- ## Setting up a Zeebe cluster To set up a cluster, you need to adjust the `cluster` section in the Zeebe configuration file. Below is a snippet of the default Zeebe configuration file: ```yaml --- cluster: # This section contains all cluster related configurations, to setup a zeebe cluster # Specifies the unique id of this broker node in a cluster. # The id should be between 0 and number of nodes in the cluster (exclusive). # # This setting can also be overridden using the environment variable ZEEBE_BROKER_CLUSTER_NODEID. nodeId: 0 # Controls the number of partitions, which should exist in the cluster. # # This can also be overridden using the environment variable ZEEBE_BROKER_CLUSTER_PARTITIONSCOUNT. partitionsCount: 1 # Controls the replication factor, which defines the count of replicas per partition. # The replication factor cannot be greater than the number of nodes in the cluster. # # This can also be overridden using the environment variable ZEEBE_BROKER_CLUSTER_REPLICATIONFACTOR. replicationFactor: 1 # Specifies the zeebe cluster size. This value is used to determine which broker # is responsible for which partition. # # This can also be overridden using the environment variable ZEEBE_BROKER_CLUSTER_CLUSTERSIZE. clusterSize: 1 # Allows to specify a list of known other nodes to connect to on startup # The contact points of the internal network configuration must be specified. # The format is [HOST:PORT] # Example: # initialContactPoints : [ 192.168.1.22:26502, 192.168.1.32:26502 ] # # To guarantee the cluster can survive network partitions, all nodes must be specified # as initial contact points. # # This setting can also be overridden using the environment variable ZEEBE_BROKER_CLUSTER_INITIALCONTACTPOINTS # specifying a comma-separated list of contact points. # Default is empty list: initialContactPoints: [] # Allows to specify a name for the cluster # This setting can also be overridden using the environment variable ZEEBE_BROKER_CLUSTER_CLUSTERNAME. # Example: clusterName: zeebe-cluster ``` ## Example In this example, we will set up a Zeebe cluster with five brokers. Each broker needs to get a unique node id. To scale well, we will bootstrap five partitions with a replication factor of three. For more information about this, take a look into the [clustering](/components/zeebe/technical-concepts/clustering.md) section. The clustering setup will look like this: ![cluster](assets/example-setup-cluster.png) ## Configuration The configuration of the first broker could look like this: ```yaml --- cluster: nodeId: 0 partitionsCount: 5 replicationFactor: 3 clusterSize: 5 initialContactPoints: [ ADDRESS_AND_PORT_OF_NODE_0, ADDRESS_AND_PORT_OF_NODE_1, ADDRESS_AND_PORT_OF_NODE_2, ADDRESS_AND_PORT_OF_NODE_3, ADDRESS_AND_PORT_OF_NODE_4, ] ``` For the other brokers, the configuration will slightly change: ```yaml --- cluster: nodeId: NODE_ID partitionsCount: 5 replicationFactor: 3 clusterSize: 5 initialContactPoints: [ ADDRESS_AND_PORT_OF_NODE_0, ADDRESS_AND_PORT_OF_NODE_1, ADDRESS_AND_PORT_OF_NODE_2, ADDRESS_AND_PORT_OF_NODE_3, ADDRESS_AND_PORT_OF_NODE_4, ] ``` Each broker needs a unique node id. The ids should be in the range of zero and `clusterSize - 1`. You need to replace the `NODE_ID` placeholder with an appropriate value. Additionally, the brokers need an initial contact point to start their gossip conversation. Make sure you use the address and internal API port (default `26502`) of another broker. You need to replace the `ADDRESS_AND_PORT_OF_NODE_0` placeholder. To guarantee a cluster can properly recover from network partitions, it is currently required that all nodes be specified as initial contact points. It is not necessary for a broker to list itself as an initial contact point, but it is safe to do so, and likely simpler to maintain. ## Partitions bootstrapping On bootstrap, each node will create a partition matrix. This matrix depends on the partitions count, replication factor and the cluster size. If you completed the configuration correctly and used the same values for `partitionsCount`, `replicationFactor`, and `clusterSize` on each node, all nodes will generate the same partition matrix. For the current example, the matrix will look like the following: | | Node 0 | Node 1 | Node 2 | Node 3 | Node 4 | | ----------- | -------- | -------- | -------- | -------- | -------- | | Partition 0 | Leader | Follower | Follower | - | - | | Partition 1 | - | Leader | Follower | Follower | - | | Partition 2 | - | - | Leader | Follower | Follower | | Partition 3 | Follower | - | - | Leader | Follower | | Partition 4 | Follower | Follower | - | - | Leader | The matrix ensures the partitions are well distributed between the different nodes. Furthermore, it guarantees each node knows exactly which partitions it has to bootstrap and for which it will become the leader at first (this could change later, if the node needs to step down for example.) ## Keep alive intervals It's possible to specify how often Zeebe clients should send keep alive pings. By default, the [official Zeebe clients](/apis-tools/working-with-apis-tools.md#official-zeebe-clients) send keep alive pings every 45 seconds. This interval can be configured through the clients' APIs and through the `ZEEBE_KEEP_ALIVE` environment variable. When configuring the clients with the environment variable, the time interval must be expressed a positive amount of milliseconds (e.g., 45000). It's also possible to specify the minimum interval allowed by the gateway before it terminates the connection. By default, gateways terminate connections if they receive more than two pings with an interval less than 30 seconds. This minimum interval can be modified by editing the network section in the respective configuration file or by setting the `ZEEBE_GATEWAY_NETWORK_MINKEEPALIVEINTERVAL` environment variable. --- ## Upgrade Zeebe Zeebe versions can be upgraded: - From any patch version to a newer patch of the same minor version - To any patch of the next minor version. Since Zeebe 8.5, upgrades to a newer version can be rolling or offline. Zeebe 8.4 and older don't contain necessary safety checks that make rolling upgrades safe, and we recommend offline upgrades instead to ensure processing behaves correctly. :::info Review the [upgrade guide](/self-managed/upgrade/components/index.md) for general upgrade procedures you must follow and to check for known issues relating to the specific upgrade you are planning. ::: ## Rolling update A **rolling update** ensures the Zeebe cluster stays available by upgrading brokers and gateways one by one instead of all at once. There are three parts to a rolling update: the Zeebe Broker, Zeebe Gateway, and clients. We recommend upgrading brokers first, then gateways, and finally clients. This ensures clients don't use new APIs that are not yet supported by the brokers or gateways. Because Zeebe is backwards compatible with the previous minor version, upgrading gateways and clients is not strictly necessary and can happen any time after upgrading the brokers. While upgrading brokers, leadership for partitions will rotate which may cause brief unavailability or loss of performance on the affected partitions. The procedure to do a rolling update of Zeebe brokers is the following: 1. Pick the broker with the highest ID that runs the old version. 2. Shut down the broker. 3. Upgrade the broker software to the new version. 4. Start the broker and wait for it to become ready and healthy. 5. Repeat until all brokers are upgraded. Gateways are upgraded with the same procedure, upgrading each replica one by one. Clients can be upgraded according to your requirements and environment, for example by simply deploying a new version of your worker applications. For disaster recovery, you may want to take [backups](/self-managed/operational-guides/backup-restore/backup-and-restore.md) before the upgrade. If you plan to immediately upgrade again, wait to give all brokers a chance to take new snapshots. The snapshot period is five minutes by default but is [configurable via `camunda.data.snapshot-period`](../configuration/broker.md#camundadata). ### Using Helm If your Zeebe deployment is managed by our [Helm charts](/self-managed/deployment/helm/install/quick-install.md), the rolling update procedure is already automated. :::note Zeebe brokers are managed by a [`StatefulSet`](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies). Zeebe Gateways are managed by a [`Deployment`](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#updating-a-deployment). ::: #### Upgrading brokers Ensure the `StatefulSet` for brokers is ready to do a rolling update by checking that: - The update strategy is `RollingUpdate`. - All replicas are ready. - The version is at least 8.5.0. The following is an example how to verify these properties. Depending on your environment, you may have to adjust these commands slightly. ``` $ kubectl get statefulsets -l app.kubernetes.io/component=zeebe-broker -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.updateStrategy.type}{"\n"}{end}' camunda-platform-zeebe RollingUpdate $ kubectl rollout status statefulset -l app.kubernetes.io/component=zeebe-broker statefulset rolling update complete 3 pods at revision camunda-platform-zeebe-d69689fbc... $ kubectl get services -l app.kubernetes.io/component=zeebe-gateway NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE camunda-platform-zeebe-gateway ClusterIP 10.96.227.153 9600/TCP,26500/TCP,8080/TCP 21m $ kubectl port-forward services/camunda-platform-zeebe-gateway 8080:8080 & $ curl localhost:8080/v2/topology | jq .brokers[].version && kill %1 8.5.0 8.5.0 8.5.0 ``` To start the rolling update, upgrade the Helm deployment to use a new version of Zeebe. Set `$NEW_ZEEBE_VERSION` to the version you want to upgrade to, for example `8.5.2`. Remember to read the [upgrade guide](/self-managed/upgrade/components/index.md) to check for known issues. Then, start the rolling update with `helm upgrade`. ``` $ export NEW_ZEEBE_VERSION=8.5.2 $ helm upgrade camunda-platform camunda/camunda-platform --reuse-values --set zeebe.image.tag=$NEW_ZEEBE_VERSION ``` Then, wait for the rolling update to complete: ``` $ kubectl rollout status statefulset -l app.kubernetes.io/component=zeebe-broker Waiting for 3 pods to be ready... Waiting for 2 pods to be ready... Waiting for 1 pods to be ready... statefulset rolling update complete 3 pods at revision camunda-platform-zeebe-5b7f7d6477... ``` When the command finishes, all Zeebe brokers are upgraded to the new version and should be ready. We can verify this by running the command to check versions again: ```shell $ kubectl port-forward services/camunda-platform-zeebe-gateway 8080:8080 & $ curl localhost:8080/v2/topology | jq .brokers[].version && kill %1 8.5.2 8.5.2 8.5.2 ``` #### Upgrading gateways Ensure the deployment of gateways is ready to do a rolling update by checking that: - All replicas are ready. - The version is at least 8.5. You can use the following command to verify this: ``` $ kubectl get deployment -l app.kubernetes.io/component=zeebe-gateway NAME READY UP-TO-DATE AVAILABLE AGE camunda-platform-zeebe-gateway 2/2 2 2 4h25m ``` Then, upgrade the version via Helm: ``` $ helm upgrade camunda-platform camunda/camunda-platform --reuse-values --set zeebe-gateway.image.tag=$NEW_ZEEBE_VERSION ``` Wait for the upgrade to complete: ``` $ kubectl rollout status deployment -l app.kubernetes.io/component=zeebe-gateway ``` At this point, both brokers and gateways are upgraded. Your client applications are ready to be upgraded as well and can start using features added in the new version. ### Troubleshooting #### Rolling update is not completing A rolling update can become stuck due to outside interference such as failing Kubernetes pods. To recover from this, the upgrade can be forced by not waiting on each broker to become ready and instead directly upgrading all brokers at once. Assuming you use our Helm charts, this can be done via the following command: ``` $ kubectl delete pod -l app.kubernetes.io/component=zeebe-broker ``` This will recreate all broker pods from scratch, running the new version. Because the upgrade is no longer rolling and all brokers are shut down at the same time, a short downtime is to be expected. #### Failed to install partition If upgraded brokers log the error message `Failed to install partition` and do not become healthy, look for more details to understand if this is caused by the rolling update. If the error is caused by `Cannot upgrade to or from a pre-release version`, Zeebe detected that either the version you started from or the version you upgraded to is a pre-release version. This is not permitted because pre-release versions such as alpha releases are considered unstable and do not guarantee compatibility with any other version. :::note If you attempted to upgrade from a minor release to a pre-release or alpha version, it is possible to roll back to the previous version of Zeebe. Note that version rollbacks are not supported in most other instances. ::: If the log message includes `Snapshot is not compatible with current version`, the rolling update failed and manual recovery is required. :::note This message can also be logged by not yet upgraded brokers, in which case it should resolve itself automatically as soon as the [rolling update completes](#rolling-update-is-not-completing). ::: The exact scenario is further described in the log message and can be one of the following: ##### Snapshot is not compatible with current version: `SkippedMinorVersion` This normally occurs when attempting an upgrade from one minor version to a newer one while skipping minor versions in between. For example, upgrading from 8.5 to 8.7 directly without first upgrading to 8.6. This is not supported and Zeebe refuses to run when detecting this. To recover, you may be able to roll back to the previous version and then upgrade to the next minor version first. Another much more unlikely cause may be that you upgraded multiple times before Zeebe brokers could take snapshots. For example, if you first upgrade from 8.5 to 8.6 and then immediately to 8.7, the upgraded brokers running 8.7 may find snapshots taken by 8.5 and refuse to run. You can recover from this manually. ##### Snapshot is not compatible with current version: `PatchDowngrade` or `MinorDowngrade` These indicate a deliberate version downgrade which Zeebe does not support. If you mistakenly tried to downgrade either the patch or minor version, you can restore by switching to the original version again. :::note This message may show up during a rolling update on not yet upgraded broker. In that case, it is caused by upgraded brokers sharing snapshots with not yet upgraded brokers. This should resolve automatically once the broker is upgraded. ::: If this persists, you can [force the upgrade](#rolling-update-is-not-completing). Alternatively, it is possible to restart Zeebe with the "skipped" minor version. Note that version rollbacks are not supported in most other instances. ## Offline upgrade See the [upgrade guide](/self-managed/upgrade/components/index.md) for specific instructions per Zeebe version. To upgrade a Zeebe cluster, take the following steps: 1. Shut down all Zeebe brokers and other components of the system. 1. Take a [backup](./backups.md) of your Zeebe brokers and Elasticsearch `data` folder if used. 1. Upgrade all Zeebe brokers and gateways to the new version. 1. Restart the system components. ## Partitions admin endpoint This endpoint allows querying the status of the partitions and performing operations to prepare an upgrade. The endpoint is available under `http://{zeebe-broker}:{management.server.port}/actuator/partitions` (default port: `9600`). It is enabled by default. It can be disabled in the configuration by setting: ``` management.endpoint.partitions.enabled=false ``` ### Query the partition status The status of the partitions can be queried with a `GET` request: ``` /actuator/partitions ``` The response contains all partitions of the broker mapped to the partition-id.
Full Response ``` { "1":{ "role":"LEADER", "snapshotId":"399-1-1601275126554-490-490", "processedPosition":490, "processedPositionInSnapshot":490, "streamProcessorPhase":"PROCESSING" } } ```
--- ## Operating Zeebe in production This chapter covers topics relevant to anyone who wants to operate Zeebe in production. - [Resource planning](/components/best-practices/architecture/sizing-self-managed.md) - Gives an introduction for calculating how many resources need to be provisioned. - [Network ports](network-ports.md) - Discusses which ports are needed to run Zeebe. - [Setting up a Zeebe cluster](setting-up-a-cluster.md) - Quick guide on how to set up a cluster with multiple brokers. - [Health status](health.md) - Lists available high-level health and liveness probes. - [Backpressure](backpressure.md) - Discusses the backpressure mechanism used by Zeebe brokers. - [Disk space](disk-space.md) - Explains how to set limits for the amount of free disk space. Once these limits are undercut, Zeebe degrades gracefully to allow the operations team to provide more disk space. - [Upgrade Zeebe](update-zeebe.md) - Contains information on how to perform a shutdown upgrade. - [Rebalancing](rebalancing.md) - Describes how to rebalance a cluster. --- ## Zeebe on Self-Managed [Zeebe](/components/zeebe/zeebe-overview.md) is the process automation engine component within the Orchestration Cluster. Within this section you will find detailed information about: - [Zeebe Gateway](zeebe-gateway/zeebe-gateway-overview.md) - The Zeebe Gateway is a component of the Zeebe cluster; it can be considered the contact point for the Zeebe cluster which allows Zeebe clients to communicate with Zeebe brokers inside a Zeebe cluster. - [Configuration](configuration/configuration.md) - Explains the configuration options. These configuration options apply to both environments, but not to Camunda 8. In Camunda 8, the configuration is provided for you. - [Security](security/security.md) - Discusses the security aspects of running Zeebe and how to use them. - [Operation](operations/zeebe-in-production.md) - Outlines topics that become relevant when you want to operate Zeebe in production. - [Exporters](exporters/exporters.md) - The Orchestration Cluster includes built-in exporters for [Elasticsearch](exporters/elasticsearch-exporter.md), [OpenSearch](exporters/opensearch-exporter.md), [Camunda Exporter](exporters/camunda-exporter.md), and RDBMS (see [RDBMS configuration](/self-managed/concepts/databases/relational-db/configuration.md)). This section explains how exporters can be configured. For a general overview, refer to our [exporters concept](/self-managed/concepts/exporters.md) page. For broader guidance on secondary storage options, see [secondary storage](/self-managed/concepts/secondary-storage/index.md). :::note New to BPMN and want to learn more before moving forward? [Visit our getting started guide](/components/modeler/bpmn/automating-a-process-using-bpmn.md) to learn about automating a process using BPMN. ::: --- ## Client authorization The Zeebe Gateway supports [Orchestration Cluster Admin](/self-managed/components/orchestration-cluster/admin/overview.md)-based auth token validation. In the Camunda 8 Self-Managed Helm chart, authentication is enabled by default via Camunda Admin. ## Camunda Admin authorization [Orchestration Cluster Admin](/self-managed/components/orchestration-cluster/admin/overview.md)-based OAuth token validation can be enabled by setting `security.authentication.mode` to `identity` and providing the corresponding `camunda.identity.*` properties. You can find more details about these in the [Camunda Management Identity documentation](/self-managed/components/management-identity/miscellaneous/configuration-variables.md#core-configuration). The Camunda 8 Self-Managed Helm chart is already fully preconfigured by default. ### YAML snippet ```yaml security: authentication: mode: identity identity: issuerBackendUrl: http://keycloak:18080/auth/realms/camunda-platform audience: zeebe-api type: keycloak ``` With authentication enabled, every request to the Gateway requires a valid auth token in the `Authorization` header, granting access to the configured `security.authentication.identity.audience`, issued by the configured `security.authentication.identity.issuerBackendUrl`. The `zeebe-api` audience is already pre-configured in Camunda Admin. The authentication could be disabled by setting `security.authentication.mode: none` in the Gateway configuration file or via `ZEEBE_GATEWAY_SECURITY_AUTHENTICATION_MODE=none` as environment variable. ## Client Zeebe clients also provide a way for users to modify request headers, namely to contain access tokens. Users can modify request headers using Zeebe's built-in `OAuthCredentialsProvider`, which uses user-specified credentials to contact an OAuth authorization server. The authorization server should return an access token that is then appended to each request. Although, by default `OAuthCredentialsProvider` is configured to use a Camunda 8 authorization server, it can be configured to use any user-defined server. Users can also write a custom [CredentialsProvider](https://github.com/camunda/camunda/blob/main/clients/java/src/main/java/io/camunda/client/CredentialsProvider.java). In the following sections, we'll describe the usage of the default `OAuthCredentialsProvider` as well as the `CredentialsProvider` interface that can be extended for implementing a custom provider. ### OAuthCredentialsProvider The `OAuthCredentialsProvider` requires the specification of a client ID and a client secret. These are then used to request an access token from an OAuth 2.0 authorization server through a [client credentials flow](https://tools.ietf.org/html/rfc6749#section-4.4). By default, the authorization server is the one used by Camunda 8, but any other can be used. Using the access token returned by the authorization server, the `OAuthCredentialsProvider` adds it to the gRPC headers of each request as a bearer token. Requests that fail due to authentication errors (i.e. HTTP 401 or `UNAUTHENTICATED` gRPC code) are seamlessly retried only if a new access token can be obtained. #### Java To use the Zeebe client with Camunda 8, first an `OAuthCredentialsProvider` must be created and configured with the appropriate client credentials. The `audience` should be equivalent to the cluster endpoint without a port number. ```java public class AuthorizedClient { public void main(String[] args) { final OAuthCredentialsProvider provider = new OAuthCredentialsProviderBuilder() .clientId("clientId") .clientSecret("clientSecret") .audience("cluster.endpoint.com") // optional .scope("scope") .build(); final CamundaClient client = new CamundaClientBuilderImpl() .gatewayAddress("cluster.endpoint.com:443") .credentialsProvider(provider) .build(); System.out.println(client.newTopologyRequest().send().join().toString()); } } ``` For security reasons, client secrets should not be hard coded. Therefore, it's recommended to use environment variables to pass client secrets into Zeebe. See [`ZEEBE_*` environment variables](#environment-variables) for the supported variable names. After setting the environment variables corresponding to the properties set on `OAuthCredentialsProviderBuilder` to the correct values, the following would be equivalent to the previous code: ```java public class AuthorizedClient { public void main(final String[] args) { final CamundaClient client = new CamundaClientBuilderImpl() .gatewayAddress("cluster.endpoint.com:443") .build(); System.out.println(client.newTopologyRequest().send().join().toString()); } } ``` The client creates an `OAuthCredentialsProvider` with the credentials specified through the environment variables and the audience is extracted from the address specified through the `CamundaClientBuilder`. :::note Zeebe's Java client will not prevent you from adding credentials to requests while using an insecure connection, but you should be aware that doing so will expose your access token by transmitting it in plaintext. ::: #### Environment variables Since there are several environment variables that can be used to configure an `OAuthCredentialsProvider`, we list them here along with their uses: - `ZEEBE_CLIENT_ID` - The client ID used to request an access token from the authorization server. - `ZEEBE_CLIENT_SECRET` - The client secret used to request an access token from the authorization server. - `ZEEBE_TOKEN_AUDIENCE` - The audience for which the token should be valid. - `ZEEBE_TOKEN_SCOPE` - The [OAuth scope](https://oauth.net/2/scope/) which can be set optionally, not sent if left unset. - `ZEEBE_AUTHORIZATION_SERVER_URL` - The URL of the authorization server from which the access token will be requested (by default, configured for Camunda 8). - `ZEEBE_CLIENT_CONFIG_PATH` - Optional path to a file used to cache access tokens on disk. When set, tokens are persisted across JVM restarts. When unset (the default), credentials are cached in memory only. ### Custom Credentials provider As previously mentioned, the `CredentialsProvider`'s purpose is to modify the HTTP headers with an authorization method. The interface consists of an `applyCredentials(CredentialsApplier)` method and a `shouldRetryRequest(StatusCode)` method. - `applyCredentials(CredentialsApplier)`: Called on every request (both REST and gRPC). The applier lets you add any headers to the request before it's sent. - `shouldRetryRequest(StatusCode)`: Called every time a request completed with a non-successful status code. The `StatusCode` argument lets you inspect the raw HTTP or gRPC code, and provides a convenient method to check the request had wrong credentials (`StatusCode#isUnauthorized`). The following sections implement a custom provider in Java: #### Java ```java public class MyCredentialsProvider implements CredentialsProvider { /** * Logs in as demo:demo */ @Override public void applyCredentials(final CredentialsApplier applier) { applier.put("Authorization", "Basic ZGVtbzpkZW1vCg=="); } @Override public boolean shouldRetryRequest(final StatusCode status) { return status.isUnauthorized(); } } ``` After implementing the `CredentialsProvider`, we can provide it when building a client: ```java public class SecureClient { public static void main(final String[] args) { final CamundaClient client = CamundaClient.newClientBuilder().credentialsProvider(new MyCredentialsProvider()).build(); // continue... } } ``` --- ## Secure client communication Zeebe supports transport layer security (TLS v1.3) between the gateway and all the officially supported clients. In this section, we will review how to configure these components. ## Gateway TLS in the gateway is disabled by default. This means that if you are just experimenting with Zeebe or in development, there is no configuration needed. However, if you want to enable authentication, we strongly recommend you enable TLS between the client and the gateway. To do so, you will need to configure both the REST and gRPC parts of the gateway separately. :::note You can use different certificates, or even reuse the same configuration for both protocols, as each are configured independently from the other. ::: ### gRPC You can configure TLS for the gRPC gateway in the `security` section of the configuration files. The following configurations are present in both `gateway.default.yaml` and `config/defaults.yaml`, the file you should edit depends on whether you are using a standalone gateway or an embedded gateway. ```yaml --- security: # Enables TLS authentication between clients and the gateway enabled: false # Sets the path to the certificate chain file certificateChainPath: # Sets the path to the private key file location privateKeyPath: ``` `enabled` should be either `true` or `false`, where true will enable TLS authentication between client and gateway, and false will disable it. `certificateChainPath` and `privateKeyPath` are used to configure the certificate with which the server will authenticate itself. `certificateChainPath` should be a file path pointing to a certificate chain in PEM format representing the server's certificate, and `privateKeyPath` is a file path pointing to the certificate's PKCS8 private key, also in PEM format. Additionally, as you can see in the configuration file, each value can also be configured through an environment variable. The environment variable to use again depends on whether you are using a standalone gateway or an embedded gateway. The Orchestration Cluster also accepts these settings through the unified configuration keys `camunda.api.grpc.ssl.enabled`, `camunda.api.grpc.ssl.certificate`, and `camunda.api.grpc.ssl.certificate-private-key`, which map to `enabled`, `certificateChainPath`, and `privateKeyPath`. For the full list, including keystore options and environment variables, see the [gRPC SSL properties](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#camundaapigrpcssl). ### REST The REST server is simply a Spring Boot server, and as such, [any of the common server properties can be applied to it](https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#appendix.application-properties.server). Note that, as of 8.5.0, it is a _reactive_ server, meaning **none of the servlet properties will have any effect**. To enable TLS for the REST server, you will need to configure the properties under the `server.ssl` tree. To enable TLS, you will need to at least specify a certificate and private key. This can be done using a PEM certificate chain file with its private key, as seen in the gRPC section, or via a key store. For example, with a PEM file: ```yaml server: ssl: enabled: true certificate: /path/to/my/certificate.pem certificate-private-key: /path/to/my/private.key ``` [This Spring blog post](https://spring.io/blog/2023/06/07/securing-spring-boot-applications-with-ssl) provides a great tutorial on how to use other options to configure the server security. ## Clients Unlike the Zeebe Gateway, TLS is enabled by default in all of Zeebe's supported clients. The following sections show how to disable or properly configure each client. :::note Disabling TLS should only be done for testing or development. During production deployments, clients and gateways should be properly configured to establish secure connections. ::: ### Java Without any configuration, the client looks in the system's certificate store for a CA certificate with which to validate the Zeebe Gateway's certificate chain. If you wish to use TLS without having to install a certificate in client's system, you can specify a CA certificate: ```java public class SecureClient { public static void main(final String[] args) { final CamundaClient client = CamundaClient.newClientBuilder().caCertificatePath("path/to/certificate").build(); // ... } } ``` Alternatively, use the `ZEEBE_CA_CERTIFICATE_PATH` environment variable to override the code configuration. To disable TLS in a Java client, use the `ZEEBE_INSECURE_CONNECTION` environment variable. To enable an insecure connection, set it to **true**. To use a secure connection, set it to any non-empty value other than **true**. Setting the environment variable to an empty string is equivalent to unsetting it. ## Self signed certificates It may be useful, for testing or development purposes, to use TLS between the client and the gateway; to simplify things, we can use self-signed certificates for this. ### Testing & example To generate your own self-signed certificates for testing/development, you will need `openssl` install on your local machine. Then you can run: ```sh openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365 --nodes -addext 'subjectAltName=IP:127.0.0.1' ``` This will generate a new certificate, `cert.pem`, and a new passwordless key, `key.pem`. :::danger Do not use these in production! Again, this is for development and testing purposes only. ::: Then start up your gateway with the certificate and key specified above. For example, if we run a broker with an embedded gateway directly using Docker: ```sh docker run -p 26500:26500 -e ZEEBE_BROKER_NETWORK_HOST=0.0.0.0 -e ZEEBE_BROKER_GATEWAY_SECURITY_ENABLED=true -e ZEEBE_BROKER_GATEWAY_SECURITY_CERTIFICATECHAINPATH=/usr/local/zeebe/cert.pem -e ZEEBE_BROKER_GATEWAY_SECURITY_PRIVATEKEYPATH=/usr/local/zeebe/key.pem --mount type=bind,source="$(pwd)"/cert.pem,target=/usr/local/zeebe/cert.pem --mount type=bind,source="$(pwd)"/key.pem,target=/usr/local/zeebe/key.pem camunda/zeebe ``` There is one caveat: in order for the client to accept this self-signed certificate, you will need to trust it. The simplest way is to specify it as part of the client's configuration. Refer to the documentation above on how to configure your clients. ## Troubleshooting authentication issues The following are common ways clients and the gateway can be misconfigured for TLS, and how to recognize and fix each one. :::note `zbctl` is a [community-supported client](https://github.com/camunda-community-hub/zeebe-client-go/blob/main/cmd/zbctl/zbctl.md). ::: ### TLS is enabled in `zbctl` but disabled in the gateway **Observed behavior:** The client fails with the following error: ``` Error: rpc error: code = Unavailable desc = all SubConns are in TransientFailure, latest connection error: connection error: desc = "transport: authentication handshake failed: tls: first record does not look like a TLS handshake" ``` The gateway logs the following error via Netty: ``` Aug 06, 2019 4:23:22 PM io.grpc.netty.NettyServerTransport notifyTerminated INFO: Transport failed io.netty.handler.codec.http2.Http2Exception: HTTP/2 client preface string missing or corrupt. Hex dump for received bytes: 1603010096010000920303d06091559c43ec48a18b50c028 at io.netty.handler.codec.http2.Http2Exception.connectionError(Http2Exception.java:103) at io.netty.handler.codec.http2.Http2ConnectionHandler$PrefaceDecoder.readClientPrefaceString(Http2ConnectionHandler.java:306) at io.netty.handler.codec.http2.Http2ConnectionHandler$PrefaceDecoder.decode(Http2ConnectionHandler.java:239) at io.netty.handler.codec.http2.Http2ConnectionHandler.decode(Http2ConnectionHandler.java:438) at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:505) at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:444) at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:283) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352) at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1421) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:930) at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:794) at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:424) at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:326) at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:918) at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) at java.lang.Thread.run(Thread.java:748) ``` **Why this happens:** The client is attempting a TLS handshake, but the gateway isn't configured to accept one. **How to fix:** Either enable TLS in the gateway as well, or specify the `--insecure` flag when using `zbctl`. ### TLS is disabled in `zbctl` but enabled for the gateway **Observed behavior:** `zbctl` fails with the following error: ``` Error: rpc error: code = Unavailable desc = all SubConns are in TransientFailure, latest connection error: connection closed ``` **Why this happens:** The gateway expects a TLS handshake, but the client is connecting in plaintext. **How to fix:** Either enable TLS in the client by specifying a path to a certificate, or disable it in the gateway by editing the appropriate configuration file. ### TLS is enabled for both client and gateway but the CA certificate can't be found **Observed behavior:** `zbctl` fails with the following error: ``` Error: rpc error: code = Unavailable desc = all SubConns are in TransientFailure, latest connection error: connection error: desc = "transport: authentication handshake failed: x509: certificate signed by unknown authority ``` **Why this happens:** The client can't verify the gateway's certificate chain because it doesn't trust the certificate authority (CA) that issued it. **How to fix:** Either install the CA certificate in the appropriate location for the system, or specify a path to the certificate using the methods described above. --- ## Secure cluster communication :::note TLS between nodes in the same cluster is disabled by default. ::: Zeebe supports transport layer security (TLS v1.3) between all nodes in a Zeebe cluster. This means it's possible to encrypt all TCP traffic between all nodes of a given cluster. Enabling TLS for cluster communication is an all or nothing feature: either all nodes are configured to use TLS, or none are. It's not currently possible to only configure some nodes to enable TLS. Additionally, a small portion of Zeebe traffic is done over UDP, which is left unencrypted. This is purely used for the nodes to gossip topology information amongst themselves, and no sensitive or user-given data is transmitted this way. ## Configuration To enable TLS for cluster communication, provide a certificate chain and a private key. Two mutually exclusive formats can be provided, and attempting to use both at the same time will result in an error. ### Keystore file A keystore file can be provided. Currently, this only supports PKCS#12. Per the [RFC](https://datatracker.ietf.org/doc/html/rfc7292) it must: - Not have more than **one** certificate-key entry in the file. - Have the same password for the keystore and the key password of the only entry. The certificate will remain the same as the PEM certificate approach, and it should be an x.509 public certificate. The private key must also be generated using PKCS#8. ### PEM certificate and keys The certificate chain file is expected to be a PEM public certificate file, which should contain a x509 public certificate, and may additionally contain an entire certificate chain. If it does include the chain, it should simply be concatenated after the node's certificate. For example, a simple certificate file with only a single certificate: ``` -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- ``` If you wanted to include its signing authority, for example, you would append the contents of the authority's public certificate to the end of the certificate chain file: ``` -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- -----BEGIN TRUSTED CERTIFICATE----- ... -----END TRUSTED CERTIFICATE----- ``` While each node uses the default Java trust store to verify incoming certificates (configurable via `javax.net.ssl.trustStore`), which by default uses the system's root certificates, it's recommended to include the complete certificate chain in the file. These will also be used by each node to verify the other nodes' certificates. :::note More specifically, the certificate chain will be part of the trust store of the node, and will be used to verify other node's certificates. ::: This will allow you to configure each node with a different leaf certificate sharing the same root certificate (or at least an intermediate authority), as long as they're contained in the chain. If all nodes use the same certificate, or if you're certain the certificate is trusted by the root certificates available on each node, it's sufficient for the file to only contain the leaf certificate. The private key file should be a PEM private key file, and should be the one during generation of the node's public certificate. Algorithms supported for the private keys are RSA, DSA, and EC. The private key must be generated using [PKCS8](https://datatracker.ietf.org/doc/html/rfc5208) or [PKCS #1](https://datatracker.ietf.org/doc/html/rfc2437); any other format will not work with Zeebe. If you're unsure what format your private key is, you can quickly run it through the `openssl` utility to convert it to PKCS8: ```shell > openssl pkcs8 -topk8 -nocrypt -in my_private_key -out my_private_pkcs8_key.pem ``` Remove the `-nocrypt` parameter if your private key has a password. If your certificate is already in the right format, it will simply do nothing. See the [OpenSSL manpages](https://www.openssl.org/docs/man1.1.1/man1/openssl-pkcs8.html) for more options. :::caution Note that currently, Zeebe does not support password protected private keys. Since storing the certificates and private keys unencrypted on disk is a security risk, we recommend you use a secret management solution like Vault to inject your certificates in memory at runtime. ::: ## Broker To configure secure communication for a broker, configure its `camunda.security.transport-layer-security.cluster` section, which looks like this: ```yaml camunda: security: transport-layer-security: cluster: # Enables TLS authentication between this broker and other nodes in the cluster # This setting can also be overridden using the environment variable CAMUNDA_SECURITY_TRANSPORTLAYERSECURITY_CLUSTER_ENABLED. enabled: false # Sets the path to the certificate chain file. # This setting can also be overridden using the environment variable CAMUNDA_SECURITY_TRANSPORTLAYERSECURITY_CLUSTER_CERTIFICATECHAINPATH. certificate-chain-path: # Sets the path to the private key file location # This setting can also be overridden using the environment variable CAMUNDA_SECURITY_TRANSPORTLAYERSECURITY_CLUSTER_CERTIFICATEPRIVATEKEYPATH. certificate-private-key-path: # Configures the keystore file containing both the certificate chain and the private key. # Currently only supports PKCS#12 format. key-store: # The path for the keystore file # This setting can also be overridden using the environment variable CAMUNDA_SECURITY_TRANSPORTLAYERSECURITY_CLUSTER_KEYSTORE_FILEPATH file-path: # Sets the password for the keystore file, if not set it is assumed there is no password # This setting can also be overridden using the environment variable CAMUNDA_SECURITY_TRANSPORTLAYERSECURITY_CLUSTER_KEYSTORE_PASSWORD password: ``` > The `certificate-chain-path`, `certificate-private-key-path` and `key-store.file-path` can be relative to your broker's working directory, or can be absolute paths. ## Gateway To configure secure communication for a standalone gateway with the rest of the cluster, configure its `zeebe.gateway.cluster.security` section, which looks like this: ```yaml security: # Enables TLS authentication between this gateway and other nodes in the cluster # This setting can also be overridden using the environment variable ZEEBE_GATEWAY_CLUSTER_SECURITY_ENABLED. enabled: false # Sets the path to the certificate chain file. # This setting can also be overridden using the environment variable ZEEBE_GATEWAY_CLUSTER_SECURITY_CERTIFICATECHAINPATH. certificateChainPath: # Sets the path to the private key file location # This setting can also be overridden using the environment variable ZEEBE_GATEWAY_CLUSTER_SECURITY_PRIVATEKEYPATH. privateKeyPath: # Configures the keystore file containing both the certificate chain and the private key. # Currently only supports PKCS#12 format. keyStore: # The path for the keystore file # This setting can also be overridden using the environment variable ZEEBE_GATEWAY_CLUSTER_SECURITY_KEYSTORE_FILEPATH filePath: # Sets the password for the keystore file, if not set it is assumed there is no password # This setting can also be overridden using the environment variable ZEEBE_GATEWAY_CLUSTER_SECURITY_KEYSTORE_PASSWORD password: ``` :::note The `certificateChainPath`, `privateKeyPath`, and `keyStore.filePath` can be relative to the gateway's working directory, or can be absolute paths. ::: ## Tasklist and Operate Using the same set of configuration properties, you can configure Tasklist and Operate to enable TLS-secured connectivity within a Camunda 8 cluster. Refer to the documentation on [Tasklist configuration](../../tasklist/tasklist-configuration.md#intra-cluster-secure-connection) and [Operate configuration](../../operate/operate-configuration.md) for additional details. ## How it works When enabled for each node, communication over TCP between these is securely encrypted using the provided certificates in a client-server model. For example, let's take two nodes (`A` and `B`). When `A` (the client) sends a request to `B` (the server), they perform a TLS handshake, wherein `B`'s certificate is exchanged and verified by `A`. Afterwards, the request is encrypted such that only a node with `B`'s private key may decrypt it (i.e. in this instance, `B`). When the roles are reversed (e.g. `B` sends a request to `A`), the same handshake occurs, but the other way around. As`B` is now the client, and `A` the server, `A`'s certificate is exchanged and verified by `B`. Afterwards, all communication is encrypted and can only be decrypted with `A`'s private key. :::note In this model, only the client verifies the identity of the server, as opposed to mTLS, in which both client and server exchange and verify one another's identities. If you need mTLS, it's currently recommended to explore a solution which provides this transparently like a service mesh (e.g. Linkerd or Istio). ::: ## Self signed certificates If you wish to use self-signed certificates for testing or development purposes, the simplest way is to have all nodes share the same certificate. As aforementioned, the certificate chain configured on a node is also part of its trust store. As such, if all nodes share the same certificate, they will have no trouble verifying the identity of the other nodes. You can still configure a different self-signed certificate for each node, _provided they can be verified by the other nodes' certificate chain_. For example, let's say you have your own root certificate authority you use to sign your own certificates, and one certificate for each node that you signed with that authority. For each node, you can then create a certificate chain file which would consist of the node's public certificate, followed by the root certificate authority's public certificate. Though each node would have a different leaf certificate it uses to identify itself, the other nodes could verify its identity since their certificate chain contains an authority used to sign it. ### Testing & example To generate your own self-signed certificates for testing, you must first create a certificate authority. :::note For this example, whenever you are asked for input, feel free to just press enter and leave the defaults there. ::: ```shell openssl req -config <(printf "[req]\ndistinguished_name=dn\n[dn]\n[ext]\nbasicConstraints=CA:TRUE,pathlen:0") -new -newkey rsa:2048 -nodes -subj "/C=DE/O=Test/OU=Test/ST=BE/CN=cluster.local" -x509 -extensions ext -keyout ca.key -out ca.pem ``` Once we have our certificate authority, we can now generate certificates for each node. Let's say we have a cluster of three nodes, `A`, `B`, and `C`. Take the following steps: 1. Generate a private key for each node: ```shell openssl genpkey -out nodeA.key -algorithm RSA -pkeyopt rsa_keygen_bits:2048 openssl genpkey -out nodeB.key -algorithm RSA -pkeyopt rsa_keygen_bits:2048 openssl genpkey -out nodeC.key -algorithm RSA -pkeyopt rsa_keygen_bits:2048 ``` :::note Generating a private key using RSA and `openssl` will generate a PKCS8 private key by default. ::: 2. Create a certificate signing request (CSR) for each as well: ```shell openssl req -new -key nodeA.key -out nodeA.csr openssl req -new -key nodeB.key -out nodeB.csr openssl req -new -key nodeC.key -out nodeC.csr ``` 3. Create the final certificates for each node: ```shell openssl x509 -req -days 365 -in nodeA.csr -CA ca.pem -CAkey ca.key -set_serial 01 -extfile <(printf "subjectAltName = IP.1:127.0.0.1") -out nodeA.pem openssl x509 -req -days 365 -in nodeB.csr -CA ca.pem -CAkey ca.key -set_serial 01 -extfile <(printf "subjectAltName = IP.1:127.0.0.1") -out nodeB.pem openssl x509 -req -days 365 -in nodeC.csr -CA ca.pem -CAkey ca.key -set_serial 01 -extfile <(printf "subjectAltName = IP.1:127.0.0.1") -out nodeC.pem ``` Make sure to replace `IP.1:127.0.0.1` with the advertised host of the broker. If it's an IP address, then keep the `IP.1` prefix. If it's a hostname/DNS entry, then you can write it out as `DNS.1:advertisedHost`. To be flexible, you can also use a wildcard host. For example, if you're deploying in Kubernetes, you could use `subjectAltName = DNS.1:*.cluster.local"`. You can also omit the whole `-extfile` parameter if you do not wish to use hostname verification at all. 4. Create the certificate chain so that each node is able to verify the identity of the others: ```shell cat nodeA.pem ca.pem > chainNodeA.pem cat nodeB.pem ca.pem > chainNodeB.pem cat nodeC.pem ca.pem > chainNodeC.pem ``` 5. You can now configure each node using its respective final `chainNode*.pem` file and `node*.key` file. For example, if node `A` was a broker: ```yaml camunda: security: transport-layer-security: cluster: enabled: true certificate-chain-path: chainNodeA.pem certificate-private-key-path: nodeA.key ``` --- ## Security Zeebe supports the following security features: - **[Client-gateway authorization](client-authorization.md)** - Allows you set up authorization for the client and the gateway. - **[Secure client-gateway communication](secure-client-communication.md)** - Allows you to secure communication between clients and gateways. - **[Secure cluster communication](secure-cluster-communication.md)** - Allows you to secure communication between all nodes in a cluster. --- ## Filters :::danger Filters are only applied to the Orchestration Cluster REST API of the gateway, and do not affect any gRPC calls. For gRPC-related middleware, review the [gateway interceptors documentation](interceptors.md). ::: All communication from a client to a broker must first pass through a gateway. There, it can be filtered before being dispatched. Zeebe provides a way to load arbitrary REST API filters into the gateway. Some typical examples of what you can accomplish with this include: - Enforcing custom authorization rules on incoming calls - Monitoring and logging incoming calls ## Implementing a filter To communicate between client and gateway, Zeebe uses [REST](components/zeebe/technical-concepts/protocols.md). A filter is then implemented as a Jakarta servlet [filter](https://www.javadoc.io/doc/jakarta.servlet/jakarta.servlet-api/6.0.0/jakarta.servlet/jakarta/servlet/Filter.html). An implementation must adhere to the following requirements: - It implements a [filter](https://www.javadoc.io/doc/jakarta.servlet/jakarta.servlet-api/6.0.0/jakarta.servlet/jakarta/servlet/Filter.html) - It has public visibility - It has a public default constructor (for example, no-arg constructor) Consider a filter logging incoming calls as an example: ```java package io.camunda.zeebe.example; /** * A simple filter that logs each incoming call. The class must be public * since we will load it via JAR into the gateway. */ public final class LoggingFilter implements Filter { private static final Logger LOGGER = LoggerFactory.getLogger("LoggingFilter"); @Override public void doFilter( final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException { LOGGER.trace("filtered a call"); filterChain.doFilter(servletRequest, servletResponse); } } ``` This example filter will log `filtered a call` at `TRACE` level for each incoming call it filters. This specific filter always dispatches all incoming calls to the target broker, but it is possible to stop the message from being filtered by other filters, and even block it from dispatching to the broker. ## Compiling your filter Our source code for the filter class can now be compiled. There are many ways to do this, but for simplicity we'll use `javac` directly. When compiling your class, ensure all compile-time dependencies are provided. In the example above, you'll need the `jakarta.servlet-api` and `slf4j-api` libraries available when compiling. Since the filter will be running inside the Zeebe Gateway, the language level of the compiled code must be the same as Zeebe's (i.e. currently JDK 21) or lower. This example thus assumes you're using version 21 of `javac`. ```sh # to compile LoggingFilter.java, we'll need to provide the api libraries # the -d option places the class file in a directory that matches its package javac -d . -classpath .:lib/jakarta.servlet-api.jar:lib/slf4j-api.jar ./LoggingFilter.java ``` ## Packaging a filter Next, package the filter class into a fat JAR. Such a JAR must contain all classes (for example, all classes your own classes depend upon at runtime). Like compiling, there are many ways to do this, but we'll use `jar` directly. This means we must define a Java manifest file by hand to place the libraries' classes on the classpath. Similar to your filter class, any libraries you package must be compiled for the same language level as Zeebe's (currently JDK 21) or lower. :::note The file path for `jar` should match the package name. For example, if your package name is `com.example`, you should package `jar` as `jar cvfm LoggingFilter.jar ./MANIFEST.MF ./com/example/*.class ./lib`. ::: ```sh # both runtime libraries and the manifest must be packaged together with the compiled classes jar cvfm LoggingFilter.jar ./MANIFEST.MF ./io/camunda/zeebe/example/*.class ./lib # let's verify the contents of the JAR jar tf ./LoggingFilter.jar # META-INF/ # META-INF/MANIFEST.MF # io/camunda/zeebe/example/LoggingFilter.class # lib/ # lib/jakarta.servlet-api.jar # lib/slf4j-api.jar # lib/slf4j.jar ``` ## Loading a filter into a gateway A filter can be loaded into your gateway as a fat JAR. For each filter, provide the gateway with: - A filter order index - An identifier to identify this specific filter - Where to find the JAR with the filter class - The [fully qualified name](https://docs.oracle.com/javase/specs/jls/se17/html/jls-6.html#jls-6.7) of the filter class. For example, `com.acme.ExampleFilter`. Continuing with the `LoggingFilter` example, provide these [configurations](/self-managed/components/orchestration-cluster/zeebe/configuration/configuration.md) using a gateway config file, environment variables, or a mix of both. We'll be using a config file in this example. The following gateway config file configures our `LoggingFilter` so it can be loaded into the gateway at start-up: ```yaml zeebe: gateway: ... # allows specifying multiple filters filters: - # identifier, can be used for debugging id: logging-filter # name of our Filter implementation # this must be the fully qualified name of the class className: io.camunda.zeebe.example.LoggingFilter # path to the fat JAR, can be absolute or relative jarPath: /tmp/LoggingFilter.jar # you can add additional filters by listing them - id: ... className: ... jarPath: ... ``` :::note Multiple filters can be configured (for example, `zeebe.gateway.filters` expects a list of filter configurations). The listing order determines the order in which a call is filtered by the different filters. The first filter in the list wraps the second, etc. The first filter is thus the outermost filter. In other words, calls are filtered first by the first listed filter, followed by the second listed filter, etc. ::: This configuration can also be provided using environment variables. Provide an index for the filter in the variable name to distinguish the ordering of the different filters. For example, to configure the `className` of the first filter use `zeebe_gateway_filters_0_className`. Likewise, a second filter's `jarPath` can be configured using `zeebe_gateway_filters_1_jarPath`. ## Class loading [Previously](#packaging-a-filter), we stated that you need to package the filter class into a fat JAR. Although good general advice, this is not entirely true. To understand why, let's discuss how the class loading of your filter works. When your JAR is loaded into the gateway, Zeebe provides a special class loader for it. This class loader isolates your filter from the rest of Zeebe, but it also exposes our own code to your filter. When loading classes for your filter, it will always first look in this special class loader. If it is not available, it will look in Zeebe's main class loader. Therefore, you can access any classes from Zeebe's main class loader when they are not provided by your JAR. For internal class loading, Zeebe will still only look in its main class loader. You can reduce your JAR size by leaving out libraries already provided by Zeebe's class loader. Additionally, if your filter depends on a different version of a class than the one provided by Zeebe, you can provide your own version without having to worry about breaking Zeebe. ## Troubleshooting The gateway won't start with a misconfigured filter. The following errors identify what went wrong and how to fix it. :::note Environment variables can overwrite your gateway configuration file. The gateway logs the configuration it uses during startup, use this to verify your configuration. ::: ### `java.lang.ClassNotFoundException` **Observed behavior:** The gateway fails to start and logs `java.lang.ClassNotFoundException` for your filter class. **Why this happens:** Your `Filter` implementation couldn't be found on the classpath. **How to fix:** Confirm the `className` is configured correctly in the [gateway configuration](#loading-a-filter-into-a-gateway), and that your [JAR contains your class](#packaging-a-filter). ### `io.camunda.zeebe.gateway.rest.impl.filters.FilterLoadException` **Observed behavior:** The gateway fails to start and logs `FilterLoadException`. **Why this happens:** Something went wrong loading your filter. The exception message describes the specific cause, but the two common ones are: - **Unable to instantiate your class**: your class doesn't meet the [filter requirements](#implementing-a-filter). - **The JAR could not be loaded**: the filter isn't configured correctly in the [gateway configuration](#loading-a-filter-into-a-gateway), or the [JAR isn't packaged correctly](#packaging-a-filter) (for example, missing runtime dependencies in the manifest's classpath). **How to fix:** Match the specific cause in the exception message to the list above, then apply the corresponding fix. ### `io.camunda.zeebe.util.jar.ExternalJarLoadException` **Observed behavior:** The gateway fails to start and logs `ExternalJarLoadException`. **Why this happens:** The JAR itself couldn't be loaded. **How to fix:** Confirm your filter is configured correctly in the [gateway configuration](#loading-a-filter-into-a-gateway). ### `java.lang.UnsupportedClassVersionError` **Observed behavior:** The gateway fails to start and logs `UnsupportedClassVersionError`. **Why this happens:** Your filter was compiled with a more recent Java Runtime version than the gateway supports. **How to fix:** Recompile your [class](#packaging-a-filter) with JDK 21. --- ## Interceptors :::danger Interceptors are only applied to the gRPC API of the gateway, and do not affect any REST calls. For REST-related middleware, read the [gateway filters documentation](filters.md). ::: All communication from a client to a broker must first pass through a gateway. There they can be intercepted before being dispatched. Zeebe provides a way to load arbitrary interceptors into the gateway. Some typical examples of what you can accomplish with this include: - Enforcing custom authorization rules on incoming calls - Monitoring and logging of incoming calls (e.g. https://github.com/grpc-ecosystem/java-grpc-prometheus) - Distributed tracing (e.g. https://github.com/open-telemetry/opentelemetry-java-instrumentation) ## Implementing an interceptor For the communication between client and gateway, Zeebe uses the gRPC [protocol](components/zeebe/technical-concepts/protocols.md). An interceptor is thus implemented as a gRPC [ServerInterceptor](https://grpc.github.io/grpc-java/javadoc/io/grpc/ServerInterceptor.html). An implementation must adhere to the following requirements: - It implements [ServerInterceptor](https://grpc.github.io/grpc-java/javadoc/io/grpc/ServerInterceptor.html) - It has public visibility - It has a public default constructor (i.e. no-arg constructor) Let's consider an interceptor that provides logging of incoming calls as an example. Other ServerInterceptor examples can be found in the official grpc-java [examples](https://github.com/grpc/grpc-java/tree/v1.41.0/examples). ```java package io.camunda.zeebe.example; /** * A simple interceptor that logs each incoming call. The class must be public * since we will load it via JAR into the gateway. */ public final class LoggingInterceptor implements ServerInterceptor { private static final Logger LOGGER = LoggerFactory.getLogger("LoggingInterceptor"); @Override public Listener interceptCall( final ServerCall call, final Metadata headers, final ServerCallHandler next) { final var listener = next.startCall(call, headers); return new SimpleForwardingServerCallListener<>(listener) { @Override public void onMessage(final ReqT message) { LOGGER.trace("intercepted a call"); super.onMessage(message); } }; } } ``` This example interceptor will log `"intercepted a call"` at `TRACE` level for each incoming call it intercepted. This specific interceptor always dispatches all incoming calls to the target broker, but it would also be possible to stop the message from interception by other interceptors and even to block it from dispatch to the broker. ## Compiling your interceptor Our source code for the interceptor class can now be compiled. There are many ways to do this, but for simplicity we'll use `javac` directly. When compiling your class, you need to make sure all compile-time dependencies are provided. In the example above, that means we need the `grpc-api` and `slf4j-api` libraries available when compiling. Since the interceptor will be running inside the Zeebe Gateway, the language level of the compiled code must be the same as Zeebe's (i.e. currently JDK 21) or lower. This example thus assumes you're using version 21 of `javac`. ```sh # to compile LoggingInterceptor.java, we'll need to provide the api libraries # -d . places the class files in a directory that matches the package name javac -d . -classpath .:lib/grpc-api.jar:lib/slf4j-api.jar ./LoggingInterceptor.java ``` ## Packaging an interceptor Next, you need to package the interceptor class into a fat JAR. Such a JAR must contain all classes (i.e. including all classes your own classes depend upon at runtime). Like compiling there are many ways to do this, but for simplicity we'll use `jar` directly. Note, that means we have to define a java manifest file by hand, in order to place the libraries' classes on the classpath. Similar to your interceptor class, any libraries you package must be compiled for the same language level as Zeebe's (i.e. currently JDK 21) or lower. :::note The file path for `jar` should match the package name. For example, if your package name is `com.example`, you should package `jar` as `jar cvfm LoggingInterceptor.jar ./MANIFEST.MF ./com/example/*.class ./lib`. ::: ```sh # both runtime libraries and the manifest must be packaged together with the compiled classes jar cvfm LoggingInterceptor.jar ./MANIFEST.MF ./io/camunda/zeebe/example/*.class ./lib # let's verify the contents of the JAR jar tf ./LoggingInterceptor.jar # META-INF/ # META-INF/MANIFEST.MF # io/camunda/zeebe/example/LoggingInterceptor$1.class # io/camunda/zeebe/example/LoggingInterceptor.class # lib/ # lib/grpc-api.jar # lib/grpc.jar # lib/slf4j-api.jar # lib/slf4j.jar ``` ## Loading an interceptor into a gateway An interceptor can be loaded into your gateway as a fat JAR. For each interceptor, you need to provide your gateway with: - An interception order index - An identifier to identify this specific interceptor - Where to find the JAR with the interceptor class - The [fully qualified name](https://docs.oracle.com/javase/specs/jls/se17/html/jls-6.html#jls-6.7) of the interceptor class, e.g. `com.acme.ExampleInterceptor` Let's continue with the LoggingInterceptor example. We can provide these [configurations](/self-managed/components/orchestration-cluster/zeebe/configuration/configuration.md) using a gateway config file, environment variables or a mix of both. We'll be using a config file here. The following gateway config file configures our LoggingInterceptor so it can be loaded into the gateway at start-up. ```yaml zeebe: gateway: ... # allows specifying multiple interceptors interceptors: - # identifier, can be used for debugging id: logging-interceptor # name of our ServerInterceptor implementation # this must be the fully qualified name of the class className: io.camunda.zeebe.example.LoggingInterceptor # path to the fat JAR, can be absolute or relative jarPath: /tmp/LoggingInterceptor.jar # you can add additional interceptors by listing them - id: ... className: ... jarPath: ... ``` Note that multiple interceptors can be configured (i.e. `zeebe.gateway.interceptors` expects a list of interceptor configurations). The listing order determines the order in which a call is intercepted by the different interceptors. The first interceptor in the list wraps the second, etc. The first interceptor is thus the outermost interceptor. In other words, calls are intercepted first by the first listed interceptor, followed by the second listed interceptor, etc. This configuration can also be provided using environment variables. You'll need to provide an index for the interceptor in the variable name, to distinguish the ordering of the different interceptors. For example, to configure the `className` of the first interceptor use: `zeebe_gateway_interceptors_0_className`. Likewise, a second interceptor's `jarPath` can be configured using `zeebe_gateway_interceptors_1_jarPath`. ## About class loading [Previously](#packaging-an-interceptor), we stated that you need to package the interceptor class into a fat JAR. Although good general advice, this is not entirely true. To understand why, let's discuss how the class loading of your interceptor works. When your JAR is loaded into the gateway, Zeebe provides a special class loader for it. This class loader isolates your interceptor from the rest of Zeebe, but it also exposes our own code to your interceptor. When loading classes for your interceptor, it will always first look in this special class loader and only if it is not available it will look in Zeebe's main class loader. In other words, you can access any classes from Zeebe's main class loader when they are not provided by your JAR. For internal class loading, Zeebe will still only look in its main class loader. This means you can reduce your JAR size by leaving out libraries that are already provided by Zeebe's class loader. In addition, if your interceptor depends on a different version of a class than the one provided by Zeebe, then you can provide your own version without having to worry about breaking Zeebe. ## Troubleshooting The gateway won't start with a misconfigured interceptor. The following errors identify what went wrong and how to fix it. :::note Environment variables can overwrite your gateway configuration file. The gateway logs the configuration it uses during startup, use this to verify your configuration. ::: ### `java.lang.ClassNotFoundException` **Observed behavior:** The gateway fails to start and logs `java.lang.ClassNotFoundException` for your interceptor class. **Why this happens:** Your `ServerInterceptor` implementation couldn't be found on the classpath. **How to fix:** Confirm the `className` is configured correctly in the [gateway configuration](#loading-an-interceptor-into-a-gateway), and that your [JAR contains your class](#packaging-an-interceptor). ### `io.camunda.zeebe.gateway.interceptors.impl.InterceptorLoadException` **Observed behavior:** The gateway fails to start and logs `InterceptorLoadException`. **Why this happens:** Something went wrong loading your interceptor. The exception message describes the specific cause, but the two common ones are: - **Unable to instantiate your class**: your class doesn't meet the [interceptor requirements](#implementing-an-interceptor). - **The JAR could not be loaded**: the interceptor isn't configured correctly in the [gateway configuration](#loading-an-interceptor-into-a-gateway), or the [JAR isn't packaged correctly](#packaging-an-interceptor) (for example, missing runtime dependencies in the manifest's classpath). **How to fix:** Match the specific cause in the exception message to the list above, then apply the corresponding fix. ### `io.camunda.zeebe.util.jar.ExternalJarLoadException` **Observed behavior:** The gateway fails to start and logs `ExternalJarLoadException`. **Why this happens:** The JAR itself couldn't be loaded. **How to fix:** Confirm your interceptor is configured correctly in the [gateway configuration](#loading-an-interceptor-into-a-gateway). ### `java.lang.UnsupportedClassVersionError` **Observed behavior:** The gateway fails to start and logs `UnsupportedClassVersionError`. **Why this happens:** Your interceptor was compiled with a more recent Java Runtime version than the gateway supports. **How to fix:** Recompile your [class](#packaging-an-interceptor) with JDK 21. --- ## Job streaming [Job streaming](../../../../../components/concepts/job-workers.md#job-streaming) is a long-lived process designed to reduce the latency involved with re-creating and propagating job workers. When using a reverse proxy or a load balancer between your worker and your gateway, you may need to configure additional parameters to ensure the job stream is not closed unexpectedly. Impacted proxies will see HTTP 504 (gateway timeout) errors returned to the job streaming worker at regular intervals. :::note This configuration is _only_ required for reverse proxies which do not support forwarding HTTP/2 keepalive (on either side). See [this nginx ticket](https://trac.nginx.org/nginx/ticket/1887), for example. Proxies which support forwarding HTTP/2 keepalive do not require any change. ::: The following configuration is recommended for impacted reverse proxies: - On your client, set an explicit stream timeout of one hour. See additional examples in [Java](../../../../../apis-tools/java-client/job-worker.md). - On your reverse proxy, ensure the read response timeout is set to slightly higher than your client (for example, an hour and ten minutes). ## Nginx Nginx is a known proxy which does not support forward HTTP/2 pings from either side as a form of keepalive. To resolve related gateway timeouts, configure an appropriate `grpc_read_timeout` that is _higher_ than your job worker stream timeout configuration. --- ## Zeebe Gateway The Zeebe Gateway is a component of the Zeebe cluster; it can be considered the contact point for the Zeebe cluster which allows Zeebe clients to communicate with Zeebe brokers inside a Zeebe cluster. For more information about the Zeebe Broker, visit our [additional documentation](../../../../../components/zeebe/technical-concepts/architecture.md#brokers). To summarize, the Zeebe Broker is the main part of the Zeebe cluster, which does all the heavy work like processing, replicating, exporting, and everything based on partitions. The Zeebe Gateway acts as a load balancer and router between Zeebe’s processing partitions. ![Zeebe Gateway overview](assets/zeebe-gateway-overview.png) To interact with the Zeebe cluster, the Zeebe client sends a command to the gateway either as a gRPC message (to port `26500` by default), or a plain HTTP request to its REST API (to port `8080` by default). Given the gateway supports gRPC as well as an OpenAPI spec, the user can use several clients in different languages to interact with the Zeebe cluster. For more information, read our [overview](../../../../../apis-tools/working-with-apis-tools.md). :::note Be aware Zeebe brokers divide data into partitions (shards), and use RAFT for replication. Read more on RAFT [here](../../../../../components/zeebe/technical-concepts/clustering.md#raft-consensus-and-replication-protocol). ::: When the Zeebe Gateway receives a valid message, it is translated to an internal binary format and forwarded to one of the partition leaders inside the Zeebe cluster. The command type and values can determine to which partition the command is forwarded. For example, creating a new process instance is sent in a round-robin fashion to the different partitions. If the command relates to an existing process instance, the command must be sent to the same partition where it was first created (determined by the key). To determine the current leader for the corresponding partition, the gateway must maintain the topology of the Zeebe cluster. The gateway(s) and broker(s) form a cluster using gossip protocol to distribute information. ## Why do we have the Zeebe Gateway and what problems does it solve? The Zeebe Gateway protects the brokers from external sources. In standalone mode, it allows the creation of a demilitarized zone ([DMZ]()) in which the Zeebe Gateway is the only contact point. With an embedded gateway, the Orchestration Cluster nodes are the contact point, and the DMZ boundary is usually a load balancer or reverse proxy in front of them. The Zeebe Gateway also allows you to easily create clients in your language of choice while keeping the client implementation as thin as possible. The clients can be kept thin, since the gateway takes care of the cluster topology and forwards the requests to the right partitions. There are already several client implementations available, officially-supported, and community-maintained. Check the list [here](../../../../../apis-tools/working-with-apis-tools.md). In standalone mode, the gateway can be run and scaled independently of the brokers, which means it translates the messages, distributes them to the correct partition leaders, and separates the concerns of the applications. For example, if your system encounters a spike of incoming requests, and you have set up enough partitions on the broker side up front, but not enough gateways to handle the load, you can easily scale them up. ## Embedded versus standalone The Zeebe Gateway can be run in two different ways: embedded and standalone. Running the gateway in embedded mode means it will run as part of the Zeebe Broker. The broker will accept gRPC client messages via the embedded gateway and distribute the translated requests inside the cluster. This means the request accepted by the embedded gateway does not necessarily go to the same broker, where the embedded gateway is running. The embedded gateway is the default since Camunda 8.8, where the Orchestration Cluster components are bundled into a single application. `zeebe.broker.gateway.enable` defaults to `true`. Every Orchestration Cluster node then accepts client traffic on port `26500` for gRPC and port `8080` for REST, and you distribute that traffic across the nodes with a load balancer. Both the [Kubernetes reference architecture](/self-managed/reference-architecture/kubernetes.md) and the [manual reference architecture](/self-managed/reference-architecture/manual.md) run in this mode. The embedded gateway also reduces the burden of deploying and running multiple applications in development and testing. For example, in [zeebe-process-test](https://github.com/camunda/zeebe-process-test) an embedded gateway is used to accept the client commands and write directly to the engine. :::note Be aware If the gateway is running in the embedded mode, it will consume resources from the broker, which might impact the performance of the system. ::: Running the gateway in standalone mode means the gateway will be executed as its own application, started with the `gateway` entry point of the distribution instead of `camunda`. This separates concerns and lets you scale the gateway independently of the brokers, based on the current workload. Since Camunda 8.8, standalone mode is not part of a Camunda reference architecture: - The Helm charts no longer deploy a gateway workload. They deploy a single Orchestration Cluster StatefulSet with the embedded gateway, sized with `orchestration.clusterSize`. - The [manual reference architecture](/self-managed/reference-architecture/manual.md) runs the same single application on every node. Use the embedded gateway unless you have a specific reason to run a standalone gateway. --- ## Configure the audit log(Audit-log) Configure the audit log in Camunda 8 Self-Managed. ## Configure recorded operations The audit log is an important feature with which you can meet regulatory requirements and maintain operational integrity by accessing a record of operations. These records include who performed the operations, when, and on which entities. The audit log is enabled by default, and the storage it requires may result in increased costs. To mitigate these resource costs, only user operations are tracked by default, not [client](../../../components/zeebe/technical-concepts/architecture.md#clients) operations. To change the default behavior in Camunda 8 Self-Managed, such as to disable the audit log or configure recorded operations, you must configure your deployment: ```yaml camunda: data: audit-log: enabled: true user: categories: [DEPLOYED_RESOURCES, USER_TASKS, ADMIN] # User operations are recorded by default client: categories: [DEPLOYED_RESOURCES, USER_TASKS, ADMIN] # You must opt in to client operations ``` ```bash CAMUNDA_DATA_AUDITLOG_ENABLED=true # User operations are recorded by default CAMUNDA_DATA_AUDITLOG_USER_CATEGORIES_0=DEPLOYED_RESOURCES CAMUNDA_DATA_AUDITLOG_USER_CATEGORIES_1=USER_TASKS CAMUNDA_DATA_AUDITLOG_USER_CATEGORIES_2=ADMIN # You must opt in to client operations CAMUNDA_DATA_AUDITLOG_CLIENT_CATEGORIES_0=DEPLOYED_RESOURCES CAMUNDA_DATA_AUDITLOG_CLIENT_CATEGORIES_1=USER_TASKS CAMUNDA_DATA_AUDITLOG_CLIENT_CATEGORIES_2=ADMIN ``` ```yaml orchestration: extraConfiguration: - file: additional-spring-properties.yaml content: | camunda: data: audit-log: enabled: true user: categories: [DEPLOYED_RESOURCES, USER_TASKS, ADMIN] # User operations are recorded by default client: categories: [DEPLOYED_RESOURCES, USER_TASKS, ADMIN] # You must opt in to client operations ``` See [all configuration options](../../components/orchestration-cluster/core-settings/configuration/properties.md#camundadataaudit-log) to learn more. If you disable the audit log, new operations are no longer recorded. Changing this setting doesn't cause the existing audit log data to be immediately purged. Instead, it will be cleaned up according to the secondary storage retention settings. Until the data is cleaned up, you can continue to access the data in [Operate](../../../components/operate/userguide/audit-operations.md), [Tasklist](../../../components/tasklist/userguide/audit-task-history.md), [Admin](../../../components/admin/audit-operations.md), and the [Search API](/apis-tools/orchestration-cluster-api-rest/specifications/search-audit-logs.api.mdx). ## Configure secondary storage retention With Camunda 8 Self-Managed, you control the [secondary storage retention policy](../../components/orchestration-cluster/core-settings/configuration/properties.md#data---secondary-storage), which applies to audit log records: ```yaml camunda: data: secondary-storage: retention: enabled: true minimum-age: 30d ``` ```bash CAMUNDA_DATA_SECONDARYSTORAGE_RETENTION_ENABLED=true CAMUNDA_DATA_SECONDARYSTORAGE_RETENTION_MINIMUMAGE=30d ``` ```yaml orchestration: extraConfiguration: - file: retention.yaml content: | camunda: data: secondary-storage: retention: enabled: true minimum-age: 30d ``` See [Configure data retention](../../deployment/helm/configure/data-retention.md) for more information about the Helm configuration. --- ## Audit log(Audit-log) Use the [audit log](../../../components/audit-log/overview.md) to access a record of operations, including who performed the operation, when it was performed, and on which entities the operation was performed. ## Impact on secondary storage When the audit log is active, a record is written to [secondary storage](../../concepts/secondary-storage/index.md) for every applicable operation instance. By default, only user operations are tracked, not [client](/components/zeebe/technical-concepts/architecture.md#clients) operations. With this default behavior, you can expect a 3.5% increase in disk usage. :::warning The audit log is enabled by default. Because of the increase in resource usage on secondary storage, you may see increased costs associated with this feature. ::: You can [configure the audit log](./configure.md) to fine tune log thoroughness and resource usage according to your needs. ## Audit logs and Physical Tenants Audit records are stored in the secondary-storage location configured for the Physical Tenant where the operation occurred. Query a tenant's records through its tenant-scoped API path, for example `POST /physical-tenants/{physicalTenantId}/v2/audit-logs/search`. The unprefixed `POST /v2/audit-logs/search` endpoint queries the default Physical Tenant. See [search audit logs](/apis-tools/orchestration-cluster-api-rest/specifications/search-audit-logs.api.mdx) for request filters and response fields. The API's `tenantId` field and filter refer to a Logical Tenant within the selected Physical Tenant. They do not select a Physical Tenant. Physical Tenant scope comes from the request path. For details about tenant-scoped API routing, see [API routing for Physical Tenants](/self-managed/concepts/physical-tenants/api-routing.md). --- ## Management and modeling component authentication in Self-Managed [Optimize](/self-managed/components/optimize/overview.md) authenticates with the same `camunda.security.*` settings as the [Orchestration Cluster](authentication-to-orchestration-cluster.md). [Camunda Hub](/self-managed/components/hub/index.md) keeps its own authentication properties in 8.10; see [Camunda Hub authentication](/self-managed/components/hub/configuration/identity.md) for details. User, group, role, tenant, and permission management for Camunda Hub and Optimize stays in [Management Identity](/self-managed/components/management-identity/overview.md). The Orchestration Cluster is not affected, because it manages its own users, groups, roles, and authorizations. ## Authentication and user management Authentication and user management are handled separately: | Concern | Handled by | | :------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------- | | Authenticating users and clients | The component itself. Optimize uses `camunda.security.authentication.*`, and Camunda Hub uses its own properties. | | Managing users, groups, roles, tenants, and permissions | [Management Identity](/self-managed/components/management-identity/overview.md) | | Storing user identities and issuing tokens | Your Identity Provider (IdP) | Management Identity is still required for the management and modeling components. For more information, see [manage access and permissions](/self-managed/components/management-identity/access-management/access-management-overview.md). For the Camunda Hub authentication settings, see [authentication](/self-managed/components/hub/configuration/identity.md). ## Choose an Identity Provider setup Authentication relies on the **OpenID Connect (OIDC)** and **OAuth 2.0** protocols. Where your user identities live, and which IdP issues tokens, is a separate choice from how authentication is configured. Three primary setups are supported: - Use Keycloak as the default Identity Provider (IdP). - Configure Keycloak to connect to an external IdP. - Connect directly to an external OIDC IdP. ## Use Keycloak as the default IdP Management Identity uses Keycloak as its Identity Provider (IdP) by default. With Helm, Keycloak is an option. The Helm default is Basic authentication. See [Helm chart authentication and authorization](/self-managed/deployment/helm/configure/authentication-and-authorization/index.md). In this setup: - **User authentication:** Users log in through the Keycloak's login page. - **Application authentication:** Applications authenticate using Machine-to-Machine (M2M) tokens. - **User management:** Users are managed in Keycloak. Groups, roles, and permissions are managed in [Management Identity](/self-managed/components/management-identity/overview.md). This method is convenient for getting started quickly and is suitable for environments that do not need to integrate with an existing corporate identity management system. :::info For more information, see [connect to an existing Keycloak instance](/self-managed/components/management-identity/configuration/connect-to-an-existing-keycloak.md). ::: ## Connect to an external IdP via Keycloak You can configure Keycloak to act as an identity broker, connecting to an external corporate Identity Provider. This allows you to leverage your existing user base from providers that support protocols like **SAML**, **LDAP**, or **OpenID Connect**. In this setup, Keycloak remains the direct IdP for Camunda management and modeling components, but it delegates the authentication process to your configured external provider. - **User authentication:** Users are redirected from Keycloak's login page to your external IdP. - **User management:** Users are managed in your external IdP and federated into Keycloak. - **Application authentication:** Applications use M2M tokens issued by Keycloak. This method is useful when you need to integrate with an IdP that does not use OIDC, or when you want to use Keycloak's advanced features to manage roles and map claims from your external provider. :::info For more information, see [configure an external IdP using Keycloak](/self-managed/components/management-identity/configuration/configure-external-identity-provider.md). ::: ## Connect to an external OIDC IdP You can connect the management and modeling components to an external Identity Provider (IdP) that supports **OpenID Connect (OIDC)** (e.g., Microsoft Entra ID, Keycloak, Auth0, Okta). In this setup: - Users are managed in your external IdP. - User groups from your IdP can be used to manage permissions. - Clients for M2M authentication are managed in your external IdP. :::tip Recommendation If you have configured the [authentication to Orchestration Cluster](authentication-to-orchestration-cluster.md#oidc) with an external OIDC provider, we recommend using the same provider for the management and modeling components. Optimize reads the same `camunda.security.authentication.oidc.*` settings, so you maintain one authentication configuration and manage users in one place. Camunda Hub connects to the same provider through its own properties; see [Camunda Hub authentication](/self-managed/components/hub/configuration/identity.md). ::: :::info For more information, see [connect Management Identity to an external IdP](/self-managed/components/management-identity/configuration/connect-to-an-oidc-provider.md). ::: --- ## Optimize authentication in Self-Managed ## About Optimize authentication [Optimize](/self-managed/components/optimize/overview.md) authenticates against an external OIDC identity provider (IdP). Optimize doesn't offer Basic authentication as a login method. Starting with Camunda 8.10, authentication is unified across the Camunda components: Optimize is configured with the same `camunda.security.*` settings as the [Orchestration Cluster](authentication-to-orchestration-cluster.md). :::tip Recommendation If you've already configured [OIDC for the Orchestration Cluster](authentication-to-orchestration-cluster.md#oidc), use the same identity provider for Optimize. This gives your users a single login experience across both components. ::: ## Configure OIDC for Optimize Set the following properties, shared with the other Camunda components: - `camunda.security.authentication.oidc.issuer-uri` - `camunda.security.authentication.oidc.client-id` - `camunda.security.authentication.oidc.client-secret` - `camunda.security.authentication.oidc.audiences` - `camunda.security.authentication.oidc.username-claim` - `camunda.security.authentication.oidc.client-id-claim` See the [OIDC configuration properties reference](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#camundasecurityauthenticationoidc) for the full list and defaults. Note the following: - `issuer-uri` must match the issuer your IdP puts in the `id_token`. - `audiences` must contain every audience your IdP issues for Optimize, plus the audience of any other application that calls Optimize on a user's behalf, such as Camunda Hub. See [component-specific configuration keys](/self-managed/upgrade/components/890-to-8100.md#component-specific-security-configuration-keys-are-deprecated) for the audiences the component-specific keys covered. - Optimize classifies each bearer token as belonging to a user or a machine-to-machine (M2M) client, using `username-claim` and `client-id-claim`, and enforces your configured Optimize permission only on tokens it classifies as a user's. A token it can't classify as an M2M client's is treated as a user's, and checked against your configured Optimize permission. :::note If you deploy with the Camunda Helm chart, you don't need to set `issuer-uri`, `client-id`, `client-secret`, or `audiences` directly. The chart continues to read the same `global.identity.auth.optimize.*` values you already use, and renders them into the properties above for you. ::: The chart doesn't set `username-claim` or `client-id-claim` for Optimize. Left unset, `username-claim` falls back to its software default of `sub`, and `client-id-claim` has no default at all, so Optimize can't classify any client token as M2M until you set it — every bearer token is then checked against your configured Optimize permission. Set both explicitly through `optimize.extraConfiguration`, matching the values your identity provider uses (for example, `preferred_username` and `client_id` for Keycloak). See the [setup instructions for your identity provider](/self-managed/deployment/helm/configure/authentication-and-authorization/index.md) for other providers: ```yaml optimize: extraConfiguration: - file: security.yaml content: | camunda: security: authentication: oidc: username-claim: preferred_username client-id-claim: client_id ``` ## Authenticate API requests The Optimize API accepts OIDC bearer tokens. For the client steps, see [Optimize API authentication](/apis-tools/optimize-api/optimize-api-authentication.md). Camunda 8.10 no longer accepts the static token `api.accessToken` on this API. The token works only if you opt into the [8.9 component-specific configuration fallback](#fall-back-to-the-89-component-specific-configuration), which Camunda plans to remove in a future release. Migrate the affected API clients to OIDC bearer tokens during 8.10. ## Component-specific configuration keys are deprecated The Optimize login and API security keys used through 8.9 are deprecated in favor of `camunda.security.*`. Optimize maps recognized component-specific keys automatically and logs a deprecation warning naming the replacement. Keep `CAMUNDA_OPTIMIZE_IDENTITY_BASE_URL` set. It is not deprecated, and Optimize still uses it to look up users, for example when adding users to a collection. If you're deploying Camunda 8.10 for the first time, none of this applies to you: configure the `camunda.security.*` properties above and skip this section and the next one. See [Upgrade Camunda components from 8.9 to 8.10](/self-managed/upgrade/components/890-to-8100.md#component-specific-security-configuration-keys-are-deprecated) for the full key mapping, precedence rules, and the keys that no longer have any effect. ## Fall back to the 8.9 component-specific configuration If the 8.10 authentication changes cause a regression in your deployment, you can temporarily revert Optimize to its 8.9 behavior. Use this only if your integrations depend on the static API access token that the 8.9 stack accepted, or if your migration to the `camunda.security.*` keys was misconfigured and you need a working deployment while you fix it: ```yaml optimize: security: csl: enabled: false ``` Treat this as a temporary escape hatch, not a supported long-term mode. Camunda plans to remove `optimize.security.csl.enabled=false`, the 8.9 behavior it restores, and the component-specific configuration keys in a future release. Falling back doesn't pause the migration, it only delays it, so the same `camunda.security.*` migration is still required. --- ## Orchestration Cluster authentication in Self-Managed Authentication to the Orchestration Cluster components and their resources is managed by [Admin](../../components/orchestration-cluster/admin/overview.md). ## About Orchestration Cluster authentication Orchestration Cluster authentication includes components such as [Zeebe](/components/zeebe/zeebe-overview.md), [Admin](/components/admin/admin-introduction.md), [Operate](/components/operate/operate-introduction.md), [Tasklist](/components/tasklist/introduction-to-tasklist.md), and the [Orchestration Cluster API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). The Orchestration Cluster supports two authentication methods: - [Basic authentication](#basic-authentication) - [OIDC](#oidc) ### Comparison of authentication methods | | **API access** | **Web UI access** | **User management** | | ------------------------ | --------------------- | --------------------- | ------------------------------ | | **Basic authentication** | Username and password | Username and password | Via Admin | | **OIDC** | OAuth 2.0 (via IdP) | OIDC (via IdP) | Via External Identity Provider | Additionally, an [Unprotected API mode](#unprotected-api-mode) is available for development purposes, which can be applied to either method. ## Basic authentication With Basic authentication, Orchestration Cluster components are protected with a username and password. User management is handled within the built-in Admin service. :::note This is the default authentication method for all installation options: [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md), [Docker Compose](/self-managed/quickstart/developer-quickstart/docker-compose.md), [Helm charts](/self-managed/deployment/helm/index.md), and [Manual installation](/self-managed/deployment/manual/install.md). ::: ### Example configuration ```shell CAMUNDA_SECURITY_AUTHENTICATION_METHOD=basic ``` ```yaml camunda.security.authentication.method: basic ``` ```yaml orchestration: security: authentication: method: basic ``` ### Security considerations While Basic authentication provides a simple layer of protection suitable for development or testing environments, it has several security limitations: - **No multi-factor authentication (MFA):** Basic authentication does not support MFA, increasing the risk of unauthorized access through credential stuffing attacks, where attackers use stolen credentials from other services. - **No account locking:** The system does not lock accounts after multiple failed login attempts, leaving it vulnerable to brute-force attacks where an attacker can try to guess passwords repeatedly without being blocked. - **Insecure password recovery:** The password recovery process for administrators can be insecure and may require direct, risky manual intervention with the system. - **No single sign-on (SSO):** It leads to a higher likelihood of weak or reused passwords. #### Mitigation and recommendations For a secure, production-ready setup, Camunda **strongly recommends using [OIDC](#oidc)**. OIDC delegates authentication to a dedicated identity provider (IdP), allowing you to leverage advanced security features such as MFA, SSO, and password policies. ## OIDC With OIDC, authentication is delegated to an [external Identity Provider (IdP)](/components/concepts/access-control/connect-to-identity-provider.md) using OpenID Connect (OIDC). This is the **recommended method for production environments**. - [Users](/components/admin/user.md) are managed in your external IdP and [mapped through rules](/components/concepts/access-control/mapping-rules.md) in Admin. - [User groups](/components/admin/group.md) can be managed in Admin or configured to use groups from your IdP. - [Clients](/components/admin/client.md) are managed in your external IdP and [mapped through rules](/components/concepts/access-control/mapping-rules.md) in Admin. Using OIDC provides several security benefits: - **Centralized user management:** Manage all users and their access from a single, central IdP. - **Single Sign-On (SSO):** Provide a seamless login experience for your users across multiple applications. - **Enhanced security:** Enforce MFA, password rotation policies, and other advanced security measures offered by your IdP. :::info For more information, see [connect Orchestration Cluster Admin to an external IdP](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md). ::: ### Example configuration ```shell CAMUNDA_SECURITY_AUTHENTICATION_METHOD=oidc ``` ```yaml camunda.security.authentication.method: oidc ``` ```yaml orchestration: security: authentication: method: oidc ``` If OIDC authentication is enabled, additional configuration values must be set. See [supported OIDC configuration properties](../../components/orchestration-cluster/core-settings/configuration/properties.md#camundasecurityauthenticationoidc). ## Unprotected API mode In this mode, API access is unprotected with no authentication required for APIs. This mode can be enabled with both Basic authentication and OIDC. By default, [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) and [Docker Compose](/self-managed/quickstart/developer-quickstart/docker-compose.md) are configured in unprotected API mode for local development quick start. :::caution This mode should never be used in production environments. ::: :::note If you need to use authorizations for access control, you must protect APIs by disabling the unprotected API mode. To learn more, see [Orchestration Cluster authorization](../../../components/concepts/access-control/authorizations.md). ::: ### Example configuration ```shell CAMUNDA_SECURITY_AUTHENTICATION_UNPROTECTEDAPI=true ``` ```yaml camunda.security.authentication.unprotected-api: true ``` ```yaml orchestration: extraConfiguration: - file: unprotected-api.yaml content: | camunda: security: authentication: unprotected-api: true ``` --- ## Machine-to-machine (M2M) tokens A **machine-to-machine (M2M)** token is a token requested by one service so it can communicate with another service acting as itself. In [Management Identity](/self-managed/components/management-identity/overview.md), we provide the ability to assign permissions to an application. This functionality allows an application to perform the `client_credentials` flow to retrieve a JWT token with permissions. The token generated can then be used to communicate with other applications in Camunda without the need for user intervention. :::tip Want to learn how to generate an M2M token? Head to our guide, [generating M2M tokens](/self-managed/components/management-identity/authentication.md) to find out more! ::: --- ## Elasticsearch privileges If you implement Camunda 8 with Elasticsearch as a service provider, the following privileges may be required: ## Cluster privileges :::note Running Elasticsearch with limited cluster privileges If an application cannot be granted cluster privileges, the schema manager can be run as a standalone application separate from the main application. In this setup, the single application does not need cluster privileges. To set this up, see [Elasticsearch without cluster privileges](./elasticsearch-without-cluster-privileges.md). ::: - `monitor` - Required to check the Elasticsearch cluster health. This privilege provides read-only cluster operations permissions. - `manage_index_templates` - Creates the necessary index templates when the Orchestration Cluster and Optimize are started for the first time, or when upgrading to a newer version of Camunda 8. Once the index templates are created, you can stop the Component, remove this privilege, and then start Component again. - `manage_ilm` - _Required when index lifecycle management (ILM) is enabled._ Required to create the necessary ILM policies when the Orchestration Cluster is started for the first time, or when upgrading to a newer version of Camunda 8. Once the ILM policies are created, you can stop the Orchestration Cluster, remove this privilege, and then start the Orchestration Cluster again. ### Backup privileges To use the [backup feature](/self-managed/operational-guides/backup-restore/backup-and-restore.md), you must have snapshot privileges. You can provide these privileges to each Component before you create a backup, and then revoke them after the backup has been completed: - `create_snapshot` - Creates a backup, or snapshot, of a running cluster. - `monitor_snapshot` - Provides read-only permissions to list and view snapshot details. ### Upgrade privileges When [upgrading](/self-managed/upgrade/components/index.md) to a newer version of Camunda 8 which requires data migration, the following are required: - `manage_pipeline` - Allows any data transformations to occur when upgrading. - `manage_index_templates` - See [cluster privileges](#cluster-privileges). - `manage_ilm` - _Required when index lifecycle management (ILM) is enabled._ See [cluster privileges](#cluster-privileges). These privileges can be granted temporarily during an upgrade: - Stop the Component, and grant the required privileges - Start the Component and perform the upgrade - Stop the Component when the upgrade is complete, and remove any privileges - Start the Component normally ## Indices privileges The following permissions are required to read, write, view, and update Elasticsearch indices. More information on indices privileges can be found in the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-privileges.html#privileges-list-indices). - `create_index` - `delete_index` - `read` - `write` - `manage` - `manage_ilm` - _Required when index lifecycle management (ILM) is enabled._ --- ## Elasticsearch without cluster privileges If the Camunda single application cannot access Elasticsearch with cluster-level privileges, you can run the schema manager as a standalone application, separate from the main application. ## Standalone schema manager When running the schema manager as a standalone application, cluster-level privileges are required only during schema creation. The single application itself does not need cluster-level privileges. - Database support: This setup supports Elasticsearch only. For OpenSearch, see [Run OpenSearch without cluster privileges](./opensearch-without-cluster-privileges.md). - Privileges required by the single application: The Camunda single application still requires an index-level privilege of at least `manage` to function properly. To run the schema manager as a standalone application: 1. [Initialize the schema manager](#initialize): The database schema must first be initialized. 2. [Start the Camunda single application](#start): Once the schema is initialized, start the application without cluster-level privileges. ### 1. Initialize the schema manager {#initialize} The schema manager is a separate standalone Java application responsible for creating and managing the database schema, and applying database settings (e.g., retention policies). :::note - Initialization requires a user with cluster-level privileges (e.g., `superuser`) in the database. - Initialization needs to be executed only once per installation. ::: #### Configure the schema manager Create a custom configuration for the schema manager with the following values: ```yaml camunda: data: secondary-storage: type: elasticsearch elasticsearch: # Example assuming an existing user called 'camunda-admin' who has 'superuser' privileges username: camunda-admin password: camunda123 url: https://localhost:9200 # If custom SSL configuration is necessary security: enabled: true self-signed: true verify-hostname: false certificate-path: PATH_TO_CA_CERT # Optional, only if ILM is enabled database: retention: enabled: true # Optional, only if legacy Elasticsearch exporter is used zeebe.broker.exporters.elasticsearch: class-name: io.camunda.zeebe.exporter.ElasticsearchExporter args: url: https://localhost:9200 index: createTemplate: true retention: enabled: true # Example assuming an existing user called 'camunda-admin' who has 'superuser' privileges authentication: username: camunda-admin password: camunda123 ``` For additional configuration options, see the [common secondary storage configuration](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#data---secondary-storage). #### Start the schema manager Using the custom configuration file, start the Java application `schema` (or `schema.bat` on Windows) located in the `bin` folder of the delivered JAR package. The schema manager will create the necessary indices and templates in the database and apply the configured settings. Assuming your custom configuration is saved as `schema-manager.yaml`, you can start the application with the following command: ```shell SPRING_CONFIG_ADDITIONALLOCATION=/path/to/schema-manager.yaml ./bin/schema ``` Verify that the application executed successfully. ### 2. Start the Camunda single application {#start} The Camunda single application can now be started without cluster-level privileges. It will connect to the database and use the schema previously created by the schema manager. #### Elasticsearch user with sufficient privileges Ensure that an Elasticsearch user with sufficient privileges exists. The application requires a database user with at least `manage` privileges on the indices it needs to access. You can either use an existing user with the required privileges or assign the necessary privileges to an example user named `camunda-app` by sending the following request to the Elasticsearch REST API: ```yaml PUT _security/role/read_write_role { "indices": [ { "names": [ "*" ], "privileges": [ "read", "write", "view_index_metadata" ], "allow_restricted_indices": false }, { "names": [ "camunda-*", "operate-*", "tasklist-*", "zeebe-*" ], "privileges": [ "manage" ], "allow_restricted_indices": false } ], "applications": [], "run_as": [], "metadata": {}, "transient_metadata": { "enabled": true } } ``` Next, assign the user to the role defined above. For example, if Elasticsearch is running on Docker, use the following command: ```shell docker exec -t elasticsearch elasticsearch-users useradd camunda-app -p camunda123 docker exec -t elasticsearch elasticsearch-users roles camunda-app -a read_write_role ``` #### Configure the Camunda single application Create a configuration for the Camunda single application with the following values. This essentially disables schema creation for the application. ```yaml camunda: data: secondary-storage: type: elasticsearch elasticsearch: # Example assuming an existing user called 'camunda-app' with the privileges described in 2.1 username: camunda-app password: camunda123 url: https://localhost:9200 # If custom SSL configuration is necessary security: enabled: true self-signed: true verify-hostname: false certificate-path: PATH_TO_CA_CERT database: schema-manager: createSchema: false # only required for upgrades from 8.7 tasklist: zeebe-elasticsearch: username: camunda-app password: camunda123 url: https://localhost:9200 # If custom SSL configuration is necessary ssl: self-signed: true verify-hostname: false certificate-path: PATH_TO_CA_CERT # only required for upgrades from 8.7 operate: zeebe-elasticsearch: # Example assuming an existing user called 'camunda-app' with the privileges described in 2.1 username: camunda-app password: camunda123 url: https://localhost:9200 # If custom SSL configuration is necessary ssl: self-signed: true verify-hostname: false certificate-path: PATH_TO_CA_CERT zeebe.broker.exporters: camundaexporter: class-name: io.camunda.zeebe.exporter.CamundaExporter args: createSchema: false history: # Optional, only if ILM is enabled retention: enabled: true # Optional, only if legacy Elasticsearch exporter is used elasticsearch: class-name: io.camunda.zeebe.exporter.ElasticsearchExporter args: url: https://localhost:9200 index: create-template: false retention: enabled: false manage-policy: false # Example assuming an existing user called 'camunda-app' with the privileges described in 2.1 authentication: username: camunda-app password: camunda123 ``` #### Start the application You can start the application using the custom configuration either from the JAR file or with Helm charts. #### Start the application from the JAR file Start the Java application `camunda` (or `camunda.bat` on Windows), located in the `bin` folder of the delivered JAR package. Assuming the configuration is saved in a file named `application-custom.yaml`, start the application with the following command: ``` SPRING_CONFIG_ADDITIONALLOCATION=/path/to/application-custom.yaml ./bin/camunda ``` #### Starting the application using Helm charts ##### Case 1: Auto-generated app configuration by Helm chart [Spring Boot convention](https://docs.spring.io/spring-boot/reference/features/external-config.html#features.external-config.typesafe-configuration-properties.relaxed-binding.environment-variables) environment variables can be used to override configuration. The following Helm values are needed to disable the schema manager in the Camunda apps. ```yaml # Helm chart values file. orchestration: env: - name: CAMUNDA_DATABASE_SCHEMAMANAGER_CREATESCHEMA value: "false" - name: CAMUNDA_DATA_SECONDARYSTORAGE_ELASTICSEARCH_HEALTHCHECKENABLED value: "false" - name: ZEEBE_BROKER_EXPORTERS_CAMUNDAEXPORTER_ARGS_CREATESCHEMA value: "false" - name: ZEEBE_BROKER_EXPORTERS_ELASTICSEARCH_ARGS_INDEX_CREATETEMPLATE value: "false" - name: ZEEBE_BROKER_EXPORTERS_ELASTICSEARCH_ARGS_RETENTION_ENABLED value: "false" - name: ZEEBE_BROKER_EXPORTERS_ELASTICSEARCH_ARGS_RETENTION_MANAGEPOLICY value: "false" ``` ##### Case 2: Manually-managed app config by the user If the application configurations are managed directly and do not rely on the Helm chart auto-generated configuration. ```yaml # Helm chart values file. orchestration: configuration: | [...] # Any other custom config. camunda.database: schema-manager: create-schema: false camunda.data: secondary-storage: elasticsearch: health-check-enabled: false zeebe.broker.exporters: camundaexporter: class-name: io.camunda.zeebe.exporter.CamundaExporter args: create-schema: false elasticsearch: class-name: io.camunda.zeebe.exporter.ElasticsearchExporter args: index: create-template: false retention: enabled: false manage-policy: false [...] # Any other custom config. ``` ### Minor version upgrades using the standalone schema manager {#minor-upgrades} Prepare a Camunda minor version upgrade by running the standalone schema manager for the target version (`N+1`). This pre-creates or adjusts index templates and mappings. You can then upgrade the Camunda single application, minimizing downtime for upgrades that require only schema adjustments. :::important Upgrading from 8.7 → 8.8 requires migration steps. Follow the relevant guides and plan a maintenance window: - [Components upgrade 8.7 to 8.8](/versioned_docs/version-8.8/self-managed/upgrade/components/870-to-880.md) - [Helm chart upgrade guide: 8.7 to 8.8](/versioned_docs/version-8.8/self-managed/upgrade/helm/870-to-880.md) These steps may require stopping or scaling down the Camunda application before running the migration. ::: If the target upgrade also requires a data or application migration (as documented in [Upgrade to Camunda 8.8](/versioned_docs/version-8.8/self-managed/upgrade/index.md)), follow the migration sequence: 1. Stop the Camunda application (or scale it down) before executing the migration logic. 2. Run the schema manager for version `N+1` with a privileged user if schema changes are part of the upgrade. 3. Execute any required migration tooling or steps described in the upgrade documentation. 4. Start or roll out the Camunda application at version `N+1` with schema creation disabled. If no migration is required, you can keep the application running at version `N` while you run the schema manager for version `N+1`. #### High-level flow 1. Current state: Camunda single application is running at version `N` (for example, 8.7) and processing traffic with its indices in Elasticsearch. 2. Verification: Check the upgrade documentation for version `N → N+1` (for example, 8.7 → 8.8) to determine if migrations are required. - If migrations are not required, continue while keeping `N` running. - If migrations are required, schedule downtime and stop the application before running migration steps. 3. Preparation: Obtain the Camunda distribution for version `N+1`. 4. Run the schema manager for version `N+1` with a configuration that grants the required cluster privileges (see [Initialize the schema manager](#initialize)). Keep the existing application at version `N` running. The schema manager applies any new or updated templates, mappings, and ILM policies (if enabled) required by version `N+1`. 5. Completion check: Wait until the schema manager logs successful completion and exits without errors. 6. Application upgrade: Upgrade or perform a rolling update of the Camunda single application from version `N` to `N+1`, using a configuration that disables schema creation. The new version will reuse the already-prepared indices. #### Example timeline | Time | Action | | ---- | ----------------------------------------------------------------------- | | T0 | App v8.6.X running, serving workload | | T1 | Launch schema manager v8.7.Y with elevated cluster privileges | | T2 | Schema manager completes successfully and exits | | T3 | Upgrade or roll out application to v8.7.Y with schema creation disabled | | T4 | Traffic now served by app v8.7.Y | This staged approach reduces or eliminates downtime for minor upgrades that require only schema adjustments. ### Update index settings with the standalone schema manager {#settings-updates} You can use the standalone schema manager to roll out certain index template setting changes without granting cluster privileges to the continuously running Camunda application. Supported settings (see [configuration references](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#index--retention-settings) and the [Elasticsearch exporter configuration](../../../components/orchestration-cluster/zeebe/exporters/elasticsearch-exporter.md#configuration)): - **numberOfShards** (Operate / Tasklist / Camunda / Zeebe Elasticsearch exporter) — static: applies only to new indices created after the change. Existing indices keep their shard count. - **numberOfReplicas** (Operate / Tasklist / Camunda) — dynamic: applied to existing indices and index templates. - **numberOfReplicas** (Zeebe Elasticsearch exporter) — static: applies only to new indices created after the change. Existing indices keep their replica count. - **templatePriority** (Operate / Tasklist / Camunda / Zeebe Elasticsearch exporter): determines precedence when multiple index templates match. Higher priority templates override lower ones. #### When to use the schema manager for settings updates Use the standalone schema manager if you need to: - Adjust index template-level settings for future indices. - Trigger a global index replicas count change. - Modify index template priority. #### Procedure 1. Prepare a schema manager configuration that includes the new settings. - For Operate and Tasklist version 8.7.11+, set `updateSchemaSettings: true`. Example configuration: ```yaml zeebe.broker.exporters.elasticsearch: class-name: io.camunda.zeebe.exporter.ElasticsearchExporter args: index: create-template: true number-of-shards: 3 # affects only new Zeebe record indices number-of-replicas: 1 # affects only new Zeebe record indices template-priority: 25 # optional, overrides default priority 20 ... # other settings camunda: database: index: number-of-shards: 1 # only new Operate/Tasklist/Camunda indices number-of-replicas: 1 # updates existing Operate/Tasklist/Camunda indices template-priority: 25 # optional, overrides default priority 0 ... # other settings ``` 2. Run the standalone schema manager with a user that has the required cluster privileges (see [Initialize the schema manager](#initialize)). You can keep the Camunda application online without cluster privileges. 3. Check the logs to confirm the schema manager completed successfully. ### Limitations - This feature only works with Elasticsearch installations. - Camunda Optimize cannot be used with this setup. ## Standalone backup application If the Camunda application(s) cannot access Elasticsearch with cluster-level privileges, you can run the backup for Operate and Tasklist data as a standalone application, separate from the main application. Creating a snapshot in Elasticsearch requires `manage_snapshots` cluster-level privileges. These privileges are only needed by the application responsible for creating the backups; the Camunda application(s) do not require cluster-level privileges. - Database support: This setup supports Elasticsearch only. For OpenSearch, see [Run OpenSearch without cluster privileges](./opensearch-without-cluster-privileges.md). - Indices: The standalone application only handles Operate and Tasklist indices. Optimize is not included in this procedure. :::note Before using the standalone backup manager: - A user with cluster-level privileges (including snapshot creation) must be configured in Elasticsearch. A user with the [snapshot_user](https://www.elastic.co/guide/en/elasticsearch/reference/current/built-in-roles.html#:~:text=related%20to%20rollups.-,snapshot_user,-Grants%20the%20necessary) role should be sufficient to run the backup application. However, restoring snapshots also requires index-level permissions. - An [Elasticsearch snapshot repository](https://www.elastic.co/guide/en/elasticsearch/reference/current/snapshot-restore.html) must be configured. ::: ### 1. Configure the backup application Create a custom `backup-manager.yaml` configuration file for the standalone backup application using the following values: ```yaml camunda: data: backup: # Example assuming an existing snapshot repository 'els-test' repository-name: els-test secondary-storage: type: elasticsearch elasticsearch: # Example assuming an existing user called 'camunda-admin' who has 'snapshot_user' privileges username: camunda-admin password: camunda123 url: https://localhost:9200 # If custom SSL configuration is necessary security: enabled: true self-signed: true verify-hostname: false certificate-path: PATH_TO_CA_CERT ``` For additional configuration options, see the [common database configuration guide](../../../components/orchestration-cluster/core-settings/configuration/properties.md#data---secondary-storage). ### 2. Start the backup application Start the Java application `backup-webapps` (or `backup-webapps.bat` on Windows), located in the `bin` folder of the delivered JAR package. This application requires a `` argument—a unique identifier of type `java.lang.Long`, used as part of the snapshot names. To learn more, see the [backup and restore guide](/self-managed/operational-guides/backup-restore/backup-and-restore.md). Assuming your custom configuration is saved in a file named `backup-manager.yaml`, start the application using the following command: ```shell SPRING_CONFIG_ADDITIONALLOCATION=/path/to/backup-manager.yaml ./bin/backup-webapps ``` The standalone application will log the current state of the backup every five seconds until it completes. Verify that the application executed successfully. Example output logs: ``` 11:42:13.713 [main] INFO i.c.a.StandaloneBackupManager - Snapshot observation: 11:42:13.714 [main] INFO i.c.a.StandaloneBackupManager - Indices snapshot is COMPLETED. Details: [GetBackupStateResponseDto{backupId=12345, state=COMPLETED, failureReason='null', details=[GetBackupStateResponseDetailDto{snapshotName='camunda_webapps_12345_snapshot_part_1_of_7', state='SUCCESS', startTime=2025-06-25T11:42:08.495+02:00, failures=null}, GetBackupStateResponseDetailDto{snapshotName='camunda_webapps_12345_snapshot_part_2_of_7', state='SUCCESS', startTime=2025-06-25T11:42:08.695+02:00, failures=null}, GetBackupStateResponseDetailDto{snapshotName='camunda_webapps_12345_snapshot_part_3_of_7', state='SUCCESS', startTime=2025-06-25T11:42:08.897+02:00, failures=null}, GetBackupStateResponseDetailDto{snapshotName='camunda_webapps_12345_snapshot_part_4_of_7', state='SUCCESS', startTime=2025-06-25T11:42:08.897+02:00, failures=null}, GetBackupStateResponseDetailDto{snapshotName='camunda_webapps_12345_snapshot_part_5_of_7', state='SUCCESS', startTime=2025-06-25T11:42:08.897+02:00, failures=null}, GetBackupStateResponseDetailDto{snapshotName='camunda_webapps_12345_snapshot_part_6_of_7', state='SUCCESS', startTime=2025-06-25T11:42:09.097+02:00, failures=null}, GetBackupStateResponseDetailDto{snapshotName='camunda_webapps_12345_snapshot_part_7_of_7', state='SUCCESS', startTime=2025-06-25T11:42:09.097+02:00, failures=null}]}] 11:42:13.714 [main] INFO i.c.a.StandaloneBackupManager - Backup with id:[12345] is completed! ``` The backup manager creates a backup of Elasticsearch data. The backup includes several Elasticsearch snapshots containing sets of Camunda, Operate and Tasklist indices. For example, a backup with an ID of `123` might contain the following Elasticsearch snapshots: ``` camunda_webapps_123_8.8.0_part_1_of_7 camunda_webapps_123_8.8.0_part_2_of_7 camunda_webapps_123_8.8.0_part_3_of_7 camunda_webapps_123_8.8.0_part_4_of_7 camunda_webapps_123_8.8.0_part_5_of_7 camunda_webapps_123_8.8.0_part_6_of_7 camunda_webapps_123_8.8.0_part_7_of_7 ``` This application replaces steps 2 and 4 of the [backup procedure](/self-managed/operational-guides/backup-restore/elasticsearch/backup.md#back-up-process). Once it completes, continue with the remaining steps of that procedure. ### Limitations - This feature only works for installations using Elasticsearch. - Camunda Optimize data cannot be backed up with this setup. - Some operations that are supported by the backup actuator API are not supported by this feature. As a workaround, you can use the Elasticsearch API as follows: #### List the snapshots of a backup ``` GET /_snapshot//*__* ``` #### Delete the snapshots of a backup :::warning Make sure the `` you provide is not a single digit integer, otherwise the following command will delete more snapshots than desired. ::: ``` DELETE /_snapshot//*__* ``` --- ## OpenSearch privileges If you implement Camunda 8 with OpenSearch as a service provider, you must configure OpenSearch with the following [permissions](https://opensearch.org/docs/latest/security/access-control/permissions/) and [default action groups](https://opensearch.org/docs/latest/security/access-control/default-action-groups/) in mind. Action groups are a set of permissions. Permissions have the `cluster|indices` prefix; all others are action groups. ## Cluster - `cluster_monitor` - Necessary for health check. - `cluster:admin/reindex/rethrottle` - Necessary to archive and migrate indices. - `cluster_manage_pipelines` - Necessary to migrate indices. - `manage_snapshots` - Necessary to take backups. - `indices:admin/index_template/put` - Necessary to create and manage index schema on start up and migration. - `indices:admin/index_template/get` - Necessary to create and manage index schema on start up and migration. - `indices:admin/index_template/delete` - Necessary to create and manage index schema on start up and migration. - `indices:data/write/reindex` - Necessary to reindex during archiving. Required to move data from runtime indices to dated indices. - `indices:data/read/scroll` - Necessary to scroll through data when reading large result sets. - `indices:data/read/scroll/clear` - Necessary to clear scroll contexts after paging through large result sets. - `cluster_manage_index_templates` - Necessary to create and manage index schema on start up. ## Indices - `data_access` - Necessary to query and read. - `get` - Necessary to read. - `delete` - Necessary to create, archive, and migrate data. - `create_index` - Necessary to create index schema and archive. - `search` - Necessary to query. - `manage` - Necessary to create index schema, archive, and migrate. ## Index state management Add in the cluster section of permissions for using index state management (ISM): - `cluster:admin/opendistro/ism/managedindex/add` - `cluster:admin/opendistro/ism/managedindex/change` - `cluster:admin/opendistro/ism/managedindex/remove` - `cluster:admin/opendistro/ism/policy/write` - `cluster:admin/opendistro/ism/policy/get` - `cluster:admin/opendistro/ism/policy/delete` --- ## OpenSearch without cluster privileges If the Camunda single application cannot access OpenSearch with cluster-level privileges (for example, due to restrictive IAM or fine‑grained access control policies), you can run the schema manager as a standalone application, separate from the main application. This approach mirrors the Elasticsearch procedure, but uses OpenSearch-specific configuration and privileges. For Elasticsearch, see [Elasticsearch without cluster privileges](./elasticsearch-without-cluster-privileges.md). ## Standalone schema manager When you run the schema manager as a standalone application, it requires cluster-level privileges only during schema creation and settings updates. The Camunda application then runs with minimal privileges (primarily index-level permissions, with one specific cluster-level requirement for clearing scrolls due to the OpenSearch security model). - Database support: This setup is supported only for OpenSearch installations (Elasticsearch procedure uses a different configuration). - Required privileges: The Camunda application requires the `manage`, `read`, and `write` index-level privileges, and the `indices:data/read/scroll/clear` cluster permission to operate (see [OpenSearch privileges](./opensearch-privileges.md)). To run the schema manager as a standalone application: 1. [Initialize the schema manager](#initialize): Create templates, indices (as required), and apply retention ISM policies. 2. [Start the Camunda single application](#start): Run without cluster-level privileges using a restricted user. ### Initialize the schema manager {#initialize} The schema manager is a separate Java application responsible for creating and managing the database schema and applying index template settings (for example, shard/replica counts and retention policies). :::note - Initialization requires a user with cluster-level privileges. For example, use an OpenSearch administrative role with access to action groups such as `cluster_manage_index_templates`, `cluster_monitor`, and the index template CRUD permissions listed in [OpenSearch privileges](./opensearch-privileges.md). - Initialization needs to be executed only once per installation (and again for minor upgrades requiring schema adjustments). ::: #### Configure the schema manager settings Create a custom configuration file for the schema manager with the following values: ```yaml camunda: data: secondary-storage: type: opensearch opensearch: # Example assuming an existing privileged user 'camunda-admin' username: camunda-admin password: camunda123 url: https://localhost:9200 # If custom SSL configuration is necessary security: enabled: true self-signed: true verify-hostname: false certificate-path: PATH_TO_CA_CERT # Optional if retention / ISM policies are enabled via schema manager database: retention: enabled: true # Optional only if legacy OpenSearch exporter (pre Camunda Exporter adoption) is used zeebe.broker.exporters.opensearch: class-name: io.camunda.zeebe.exporter.opensearch.OpensearchExporter args: url: https://localhost:9200 index: createTemplate: true retention: enabled: true authentication: username: camunda-admin password: camunda123 ``` For additional configuration options, review the [secondary storage configuration](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#secondary-storage) and the [OpenSearch exporter configuration](/self-managed/components/orchestration-cluster/zeebe/exporters/opensearch-exporter.md). #### Start the schema manager Start the `schema` Java application (or `schema.bat` on Windows) from the `bin` directory by using your custom configuration file. Assuming your configuration is saved as `schema-manager-opensearch.yaml`: ```bash SPRING_CONFIG_ADDITIONALLOCATION=/path/to/schema-manager-opensearch.yaml ./bin/schema ``` Wait for successful completion (application exits cleanly) before moving to step 2. ### Start the Camunda single application {#start} Start the application with a less privileged OpenSearch user. Most operations require only index-level privileges (`manage`, `read`, and `write` for required indices), but `indices:data/read/scroll/clear` must be assigned as a cluster permission. OpenSearch treats the permission to clear scrolls as a cluster-wide action because scroll IDs are not bound to a specific index endpoint in the request URL. #### OpenSearch user with required privileges Create or reuse a user with at least the following index privileges on all Camunda indices: - `manage` (covers index administration, such as create index and mapping updates) - `read` and `write` (cover search, read, and write, which `manage` does not include) You can create a role using OpenSearch Security plugin APIs (for IAM roles on AWS OpenSearch Service, please refer to [Identity and Access Management in Amazon OpenSearch Service](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/ac.html)). Example role definition: ```bash curl -XPUT https://localhost:9200/_plugins/_security/api/roles/camunda_app_role \ -H 'Content-Type: application/json' \ -u admin:admin \ -d '{ "cluster_permissions": [ "indices:data/read/scroll/clear" ], "index_permissions": [ { "index_patterns": [ "zeebe-*", "operate-*", "tasklist-*", "camunda-*" ], "allowed_actions": [ "manage", "indices:data/write/*", "indices:data/read/*" ] } ] }' ``` Then assign the role to the user (example using Security plugin user API): ```bash curl -XPUT https://localhost:9200/_plugins/_security/api/internalusers/camunda-app \ -H 'Content-Type: application/json' \ -u admin:admin \ -d '{"password": "camunda123", "opendistro_security_roles": ["camunda_app_role"]}' ``` #### Configure the Camunda single application Disable schema creation in the application configuration so it reuses what the standalone schema manager prepared: ```yaml camunda: data: secondary-storage: type: opensearch opensearch: # Example restricted user 'camunda-app' username: camunda-app password: camunda123 url: https://localhost:9200 security: enabled: true self-signed: true verify-hostname: false certificate-path: PATH_TO_CA_CERT database: schema-manager: createSchema: false tasklist: opensearch: health-check-enabled: false # Only required for upgrades from 8.7 (legacy component configs) zeebe-elasticsearch: # legacy key retained for backward compatibility username: camunda-app password: camunda123 url: https://localhost:9200 ssl: self-signed: true verify-hostname: false certificate-path: PATH_TO_CA_CERT operate: opensearch: health-check-enabled: false # Only required for upgrades from 8.7 (legacy component configs) zeebe-elasticsearch: # legacy key retained for backward compatibility username: camunda-app password: camunda123 url: https://localhost:9200 ssl: self-signed: true verify-hostname: false certificate-path: PATH_TO_CA_CERT zeebe.broker.exporters: camundaexporter: class-name: io.camunda.zeebe.exporter.CamundaExporter args: createSchema: false history: retention: enabled: true # Only if ISM retention enabled globally opensearch: class-name: io.camunda.zeebe.exporter.opensearch.OpensearchExporter args: url: https://localhost:9200 index: createTemplate: false retention: enabled: false managePolicy: false authentication: username: camunda-app password: camunda123 ``` #### Start from the JAR distribution ```bash SPRING_CONFIG_ADDITIONALLOCATION=/path/to/application-opensearch.yaml ./bin/camunda ``` #### Start using Helm charts If using Helm, disable schema creation via environment variables: ```yaml # values.yaml snippet orchestration: env: - name: CAMUNDA_DATABASE_SCHEMAMANAGER_CREATESCHEMA value: "false" - name: CAMUNDA_DATA_SECONDARYSTORAGE_OPENSEARCH_HEALTHCHECKENABLED value: "false" - name: ZEEBE_BROKER_EXPORTERS_CAMUNDAEXPORTER_ARGS_CREATESCHEMA value: "false" - name: ZEEBE_BROKER_EXPORTERS_OPENSEARCH_ARGS_INDEX_CREATETEMPLATE value: "false" - name: ZEEBE_BROKER_EXPORTERS_OPENSEARCH_ARGS_RETENTION_ENABLED value: "false" ``` Or when managing configuration manually: ```yaml # values.yaml snippet (manual configuration block) orchestration: configuration: | camunda.database: schema-manager: create-schema: false camunda.data.secondary-storage.opensearch: health-check-enabled: false zeebe.broker.exporters: camundaexporter: class-name: io.camunda.zeebe.exporter.CamundaExporter args: create-schema: false opensearch: class-name: io.camunda.zeebe.exporter.opensearch.OpensearchExporter args: index: create-template: false retention: enabled: false manage-policy: false ``` ### Minor version upgrades with the standalone schema manager {#minor-upgrades} For a minor upgrade (N → N+1), pre-run the standalone schema manager of version N+1 with a privileged user to apply new templates/mappings. Then upgrade the application with schema creation disabled. If the upgrade requires a data/application migration (see [Upgrade overview](/versioned_docs/version-8.8/self-managed/upgrade/index.md)): 1. Stop or scale down the application. 2. Run schema manager (version N+1) with elevated privileges. 3. Execute migration tooling. 4. Start application at version N+1. If no migration is required you can keep N serving traffic while running the schema manager for N+1. ### Update index settings with the standalone schema manager {#settings-updates} You can roll out index template setting changes (shards, replicas, template priority) via the standalone schema manager without providing cluster privileges to the running application. Supported settings (see [configuration references](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#index--retention-settings) and [OpenSearch exporter configuration](/self-managed/components/orchestration-cluster/zeebe/exporters/opensearch-exporter.md)): - numberOfShards (new indices only; Operate / Tasklist / Camunda / Zeebe exporter) - numberOfReplicas (dynamic for Operate / Tasklist / Camunda; static for Zeebe exporter indices) - templatePriority (precedence when multiple templates match) Procedure: 1. For Operate and Tasklist 8.7.11+, set `updateSchemaSettings: true` if applicable. 2. Run schema manager with privileged user while application remains online. 3. Verify successful completion. ### Limitations - This procedure applies to OpenSearch installations only. - Optimize supports OpenSearch (see release notes). However, you cannot include Optimize in this reduced-privilege model if it requires additional cluster features, such as advanced aggregations. Evaluate required privileges before including Optimize. - Does not remove the need for appropriate ISM permissions if retention is enabled. ## Standalone backup application Snapshot backups require the `manage_snapshots` cluster-level privilege. If the main application cannot hold this privilege, run a separate backup application with an elevated user. - Database support: Supported only for OpenSearch installations in this procedure. - Indices covered: Operate and Tasklist indices (Optimize indices excluded from this simplified procedure; handle separately if used). Before running the standalone backup manager: 1. Ensure snapshot repository is configured (for example, S3, filesystem) with appropriate permissions. 2. Ensure elevated user has `manage_snapshots` and access to index patterns. 3. Prepare backup configuration file referencing OpenSearch connection under `camunda.data.secondary-storage.opensearch`. (Backup command usage mirrors Elasticsearch steps; adjust endpoints to OpenSearch.) ### High-level flow recap | Step | Action | | ---- | -------------------------------------------------------------------- | | 1 | Run the privileged schema manager to prepare templates and indices | | 2 | Start the application with a restricted user | | 3 | (Upgrade) Run the next version of the schema manager with privileges | | 4 | Upgrade the application with schema creation disabled | | 5 | (Optional) Run the standalone backup application | This staged approach reduces or eliminates downtime for minor upgrades and isolates cluster-level privileges to short-lived administrative tasks rather than long-running services. --- ## Overview(Databases) Camunda applications depend on a secondary storage backend to read workflow and decision data exported from the Zeebe engine. This storage layer can use either a document-store backend or a relational database (RDBMS), depending on your requirements. For an architectural explanation of how secondary storage fits into Camunda 8, see the [secondary storage overview](/self-managed/concepts/secondary-storage/index.md). ## App database support | App | RDBMS | Non-SQL | | --------------------- | ----- | ------- | | Orchestration Cluster | Yes | Yes | | Optimize | No | Yes | | Camunda Hub | Yes | No | | Management Identity | Yes | No | Use this matrix as a compatibility summary for the main Camunda components and their supported database backends. ## Document-store backends Camunda supports document-store backends such as Elasticsearch and OpenSearch. These systems are optimized for high-volume ingestion and flexible search queries. Related documentation: - [Elasticsearch privileges](/self-managed/concepts/databases/elasticsearch/elasticsearch-privileges.md) - [OpenSearch privileges](/self-managed/concepts/databases/elasticsearch/opensearch-privileges.md) - [Elasticsearch exporter](/self-managed/components/orchestration-cluster/zeebe/exporters/elasticsearch-exporter.md) - [OpenSearch exporter](/self-managed/components/orchestration-cluster/zeebe/exporters/opensearch-exporter.md) ## Relational databases (RDBMS) Camunda also supports several relational databases for secondary storage, enabling the Orchestration Cluster API, Operate, Tasklist, and Admin to run without Elasticsearch or OpenSearch. RDBMS and document-store backends are both valid secondary storage options. Select based on your workload, operational model, and platform standards. A full list of supported vendors and versions, JDBC driver information, and component compatibility is published in the [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md). For configuration details in Helm deployments, see the [RDBMS configuration guide](/self-managed/deployment/helm/configure/database/rdbms.md). ### RDBMS behavior and limitations When using an RDBMS as secondary storage, keep the following limitations in mind: - **Length limits for user-defined values:** Most user-defined strings that are exported to secondary storage or stored in identity management are length-limited. See [string length limits for user-defined values](#string-length-limits-for-user-defined-values). - **Variable comparisons:** For String and JSON variables, comparison operators (`equals`, `notEquals`, `in`, `notIn`) only consider the first 8191 characters (or 4000 characters with Oracle). `LIKE` comparisons are not affected. - **Sorting may differ by vendor:** Because collation behavior varies across database vendors, results sorted by string fields may differ between systems. #### String length limits for user-defined values Camunda limits most user-defined strings before they are exported to secondary storage or stored in identity management. | Scope | Limit | Notes | | ------------------------------------------ | --------------------- | ---------------------------------------------------------------------------------------------------- | | Elasticsearch/OpenSearch secondary storage | **32,768 characters** | This matches the practical limit for the relevant Elasticsearch / OpenSearch string fields. | | RDBMS secondary storage | **256 characters** | This limit is chosen to align exported field lengths with the supported RDBMS schema. | | Identity objects | **256 characters** | This limit applies independently of the secondary storage backend used by the Orchestration Cluster. | These limits are enforced using Java string length semantics rather than raw UTF-8 byte counts. In practice, most common Latin, CJK, and Arabic characters count as one character, while characters represented as surrogate pairs in Java count as two. Because of that, the effective visible-character limit can be lower for some inputs. The limits apply to the following user-defined fields: - BPMN element IDs and names - DMN element IDs and names - Job worker types in task definitions - Variable names - Resource names of deployed BPMN, DMN, and form files - Form IDs - Message names and correlation keys - Identity object identifiers and names Special case for DMN on RDBMS: if a rule does not define its own ID, Camunda generates one from the decision ID, decision version, and rule index. In that case, keep the decision ID at **235 characters or fewer** so the generated rule ID stays within the RDBMS limit. These limits are currently not configurable. In particular, the RDBMS-backed limits are tied to the database schema and do not increase automatically if you change application configuration. #### Working with variables When retrieving variables through the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md), the following comparison operators only apply to the first 8191 characters (or 4000 characters with Oracle) of large String or JSON variables: - equals - notEquals - in - notIn This ensures consistent performance on large datasets. For details, see the [get variable specification](/apis-tools/orchestration-cluster-api-rest/specifications/get-variable.api.mdx). ## Related database topics These pages provide deeper detail for operators, DBAs, and administrators: - [RDBMS benchmark results](/self-managed/concepts/secondary-storage/rdbms-benchmark-results.md) - [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) - [Configure RDBMS in Helm](/self-managed/deployment/helm/configure/database/rdbms.md) - [Elasticsearch privileges](/self-managed/concepts/databases/elasticsearch/elasticsearch-privileges.md) - [OpenSearch privileges](/self-managed/concepts/databases/elasticsearch/opensearch-privileges.md) - [Configure the RDBMS exporter](/self-managed/concepts/databases/relational-db/configuration.md) :::note For guidance on coordinating backups between Zeebe (primary storage) and your configured secondary storage backend—whether Elasticsearch, OpenSearch, or an RDBMS—see the [backup and restore overview](/self-managed/operational-guides/backup-restore/backup-and-restore.md). ::: --- ## RDBMS configuration overview Camunda can use a relational database (RDBMS) as the secondary storage backend for Operate, Tasklist, Identity, and the Camunda REST API. This page explains how RDBMS configuration works at the application level. If you are deploying with Helm, see: - [RDBMS configuration in Helm](/self-managed/deployment/helm/configure/database/rdbms.md) - [Access native SQL and Liquibase scripts](/self-managed/deployment/helm/configure/database/access-sql-liquibase-scripts.md) For supported database vendors and versions, see the [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md). :::tip Need end-to-end guidance? For a unified setup guide covering provisioning, topology decisions, driver management, and backup strategies across both Orchestration Cluster and Camunda Hub, see the [end-to-end RDBMS setup guide](/self-managed/concepts/databases/relational-db/rdbms-setup-guide.md). This guide is useful both when starting a new setup and when harmonizing existing component configurations. ::: ## Enable RDBMS as secondary storage Set the `camunda.data.secondary-storage.type` property to `rdbms` to activate the full RDBMS backend in a single step. This automatically enables the RDBMS exporter, which streams workflow data to the database, and configures the application layer (Operate, Tasklist, Identity, REST API) to use RDBMS for secondary storage. Example configuration: ```yaml # Configure secondary storage for Camunda applications camunda: data: secondary-storage: type: rdbms rdbms: url: jdbc:postgresql://localhost:5432/camunda username: camunda password: camunda ``` The RDBMS exporter can be used alongside other exporters, but enabling multiple exporters may affect performance. ## Schema management Camunda uses Liquibase to automatically create and update the database schema on startup. Liquibase creates two internal management tables: - `DATABASECHANGELOG` - `DATABASECHANGELOGLOCK` These tables must not be modified or deleted. For Helm deployments requiring manual schema control or access to vendor-specific SQL, see [access SQL and Liquibase scripts](/self-managed/deployment/helm/configure/database/access-sql-liquibase-scripts.md). ### Configure table prefix To add a prefix to all Camunda-managed database tables: ```yaml camunda.data.secondary-storage.rdbms.prefix: c8_ ``` ## Disable automatic schema creation If your organization manages schema manually: ```yaml camunda.data.secondary-storage.rdbms.auto-ddl: false ``` SQL scripts for manual schema creation are documented in the Liquibase/SQL access guide linked above. ## Database privileges The configured database user must have the following privileges on all Camunda tables: - SELECT - INSERT - UPDATE - DELETE ### Additional privileges for automatic schema management If Liquibase schema management is enabled, the following privileges must be granted before the first startup: - CREATE - ALTER - DROP ### Additional privilege for purge operations If using the RDBMS purge feature, the following privilege is required: - TRUNCATE ## Database driver Camunda images include JDBC drivers for all supported databases except Oracle and MySQL. If you use one of these databases, you must provide the driver yourself. ### Docker Compose When running Camunda with Docker Compose, mount the driver into `/driver-lib`: ```yaml services: camunda: image: camunda/camunda: volumes: - /driver-lib:/driver-lib ``` Place the driver JAR directly inside the mounted directory (not in subfolders). ### Helm When deploying with Helm, see [JDBC driver management](/self-managed/deployment/helm/configure/database/rdbms-jdbc-drivers.md). ## Database configuration RDBMS configuration properties are defined under: ```yaml camunda.data.secondary-storage.rdbms.* ``` | Property | Description | Default | | ----------------------- | ---------------------------------------------------------------- | ------- | | `url` | JDBC connection URL | _empty_ | | `username` | Username for the connection | _empty_ | | `password` | Password for the connection | _empty_ | | `auto-ddl` | Enables Liquibase schema management | `true` | | `prefix` | Optional table name prefix | `""` | | `database-vendor-id` | Manually override vendor detection (`postgres`, `mariadb`, etc.) | _empty_ | | `ddl-lock-wait-timeout` | Max time Liquibase can hold a lock on the database | PT15M | ## Connection pool configuration Camunda uses HikariCP for JDBC connection pooling. The following properties can be adjusted: | Property name | Description | Default | | ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------- | | `camunda.data.secondary-storage.rdbms.connection-pool.maximum-pool-size` | Maximum number of simultaneous connections | 10 | | `camunda.data.secondary-storage.rdbms.connection-pool.minimum-idle` | Minimum number of idle connections | 2 | | `camunda.data.secondary-storage.rdbms.connection-pool.idle-timeout` | Timeout (ms) before closing an idle connection | 600000 | | `camunda.data.secondary-storage.rdbms.connection-pool.max-lifetime` | Maximum lifetime (ms) of each connection before it is closed and replaced | 1800000 | | `camunda.data.secondary-storage.rdbms.connection-pool.connection-timeout` | Maximum time (ms) the application waits for a connection from the pool | 30000 | ## Exporter configuration The RDBMS exporter is automatically enabled when: ```yaml camunda.data.secondary-storage.type: rdbms ``` The following additional configuration options are available under `camunda.data.secondary-storage.rdbms`: ### Exporter performance settings | Property name | Description | Default | | ------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------- | | `flush-interval` | Maximum time a record waits in the flush queue before being flushed and committed to the database | PT0.5S | | `max-queue-size` | Maximum number of records allowed in the flush queue before a forced flush | 1000 | | `queue-memory-limit` | Maximum memory usage (MB) allowed for queued records before a forced flush | 20 | | `export-batch-operation-items-on-creation` | If due items should be exported at the beginning of a batch operation or only after processing | true | | `insert-batching.max-audit-log-insert-batch-size` | Maximum number of rows to batch into a single insert statement into the AUDIT_LOG table | 50 | | `insert-batching.max-flow-node-insert-batch-size` | Maximum number of rows to batch into a single insert statement into the FLOW_NODE table | 25 | | `insert-batching.max-job-insert-batch-size` | Maximum number of rows to batch into a single insert statement into the JOB table | 25 | | `insert-batching.max-variable-insert-batch-size` | Maximum number of rows to batch into a single insert statement into the VARIABLE table | 25 | ## History cleanup The RDBMS exporter provides automatic history cleanup, which works in two stages: 1. **TTL marking** When a root process instance finishes, the entire process instance hierarchy (the root and any child process instances started via Call Activities), and all related data are marked for deletion once the root instance's time-to-live expires. 2. **Periodic cleanup job** A scheduled cleanup job deletes marked records in batches and adjusts future intervals dynamically: - If no records are deleted → interval doubles (up to `max-history-cleanup-interval`) - If the batch size is fully used → interval halves (down to `min-history-cleanup-interval`) - Otherwise → interval remains unchanged - Additionally, cleanup execution is capped by `max-history-cleanup-usage`. The current cleanup run is not interrupted, but the next interval is adjusted. ### History cleanup configuration RDBMS history configuration properties are defined under: ```yaml camunda.data.secondary-storage.rdbms.history.* ``` | Property name | Description | Default | | ---------------------------------------------- | ----------------------------------------------------------------------------------------------- | ---------- | | `default-history-ttl` | TTL for finished process instances and related data (ISO-8601 duration) | P30D | | `default-batch-operation-ttl` | TTL for batch operation history | P5D | | `batch-operation-cancel-process-instance-ttl` | TTL for cancel-process-instance batch operations | P5D | | `batch-operation-migrate-process-instance-ttl` | TTL for migrate-process-instance batch operations | P5D | | `batch-operation-modify-process-instance-ttl` | TTL for modify-process-instance batch operations | P5D | | `batch-operation-resolve-incident-ttl` | TTL for resolve-incident batch operations | P5D | | `min-history-cleanup-interval` | Minimum duration between cleanup runs (ISO-8601 duration) | PT1M | | `max-history-cleanup-interval` | Maximum duration between cleanup runs (ISO-8601 duration) | PT60M | | `max-history-cleanup-usage` | Maximum percentage of usage time the history cleanup is allowed to use (values between 0 and 1) | 0.25 (25%) | | `history-cleanup-process-instance-batch-size` | Number of process instances to be cleaned per cleanup run | 500 | | `history-cleanup-batch-size` | Number of rows to be cleaned per cleanup run in each table | 10000 | | `usage-metrics-ttl` | TTL for usage metrics | P730D | | `usage-metrics-cleanup` | Interval between usage metrics cleanup runs (ISO-8601 duration) | PT24H | ## Exporter cache configuration | Property name | Description | Default | | -------------------------------- | ---------------------------------------------------------------- | ------- | | `process-cache.max-size` | Maximum number of process definitions held in the exporter cache | 1000 | | `batch-operation-cache.max-size` | Maximum number of cached batch operations | 1000 | ## Multi-region support Multi-region support for RDBMS uses the asynchronous replication feature of the underlying database and is highly dependent on the database vendor. While most multi-region replication is performed by the database itself, Camunda provides additional features to enhance automatic recovery in the event of a failure. For an architecture built on this model, in which every region writes to a single endpoint and a region loss does not stop processing, see [Multi-Region RDBMS](/self-managed/concepts/multi-region/multi-region-rdbms.md). Asynchronous replicated databases are synchronized with a delay, meaning that after a failover, the new primary database may not contain all the data written to the old primary database. This can lead to data loss in secondary storage. While this data can be reproduced by replaying past records from the Zeebe log stream, the relevant segments and records must still be present on all brokers. Zeebe's logstream segments are usually compacted as soon as all exporters have acknowledged the records. Camunda supports different strategies to handle this situation and prevent Zeebe log stream segments from being compacted prematurely. The following strategies are supported: - **LSN replication monitoring:** dynamic monitoring of the replication lag based on the database LSN. This is the most preferred strategy and should be used whenever possible with the used database vendor. - **Time-based replication monitoring:** monitoring of the replication lag that the primary replica reports. This is less precise than LSN replication monitoring and may lead to less frequent acknowledgements. - **Delay backoff replication monitoring:** Adds a static delay to the acknowledgement of records to the broker. :::note Deferring the logstream compaction with any strategy may drastically increase the disk space usage of the logstream. It is recommended to monitor the disk space usage and adjust the disk size or delay limit accordingly. ::: ### LSN replication monitoring The exporter monitors the replication lag to the secondary databases based on the Log Sequence Number (LSN) of the last exported record. Only when an RDBMS redo log segment is replicated to a minimum quorum of secondary databases, the exporter will acknowledge the records in the logstream. ```yaml camunda.data.secondary-storage.rdbms.async-replication.enabled: true camunda.data.secondary-storage.rdbms.async-replication.type: LOG_SEQ camunda.data.secondary-storage.rdbms.async-replication.min-sync-replicas: 2 ``` | Property name | Description | Default | | --------------------------------------------- | ----------------------------------------------------------------------------- | ------- | | `async-replication.enabled` | If the async replication monitoring should be enabled | false | | `async-replication.min-sync-replicas` | The minimum number of replicas in sync | 1 | | `async-replication.polling-interval` | The interval in which to check the replicas | PT15S | | `async-replication.max-lag` | The max tolerated lag of a replication (ISO-8601 duration) | PT15M | | `async-replication.pause-on-max-lag-exceeded` | If the exporter should pause exporting when the maximum lag limit is exceeded | false | #### Vendor support The following databases are supported for LSN replication monitoring: - Aurora Global Database with PostgreSQL - Aurora Global Database with MySQL - MSSQL - PostgreSQL - Oracle Oracle uses system change numbers (SCNs) to track replication progress. To use LSN replication monitoring with Oracle, grant the database user `SELECT` access to the following views: ```sql GRANT SELECT ON v_$database TO ; GRANT SELECT ON v_$archive_dest TO ; GRANT SELECT ON v_$archive_dest_status TO ; ``` To use the LSN replication monitoring with PostgreSQL, the database user must have the following additional privileges: - `PG_MONITOR` role ```sql GRANT PG_MONITOR TO ; ``` To use the LSN replication monitoring with MSSQL, the database user must have the following additional privileges: - `VIEW SERVER STATE` role on SQL Server 2019 and earlier versions ```sql GRANT VIEW SERVER STATE TO ; ``` - `VIEW SERVER PERFORMANCE STATE` role on SQL Server 2022 and newer versions ```sql GRANT VIEW SERVER PERFORMANCE STATE TO ; ``` ### Time based replication monitoring The exporter monitors the replication lag to the secondary databases based on the reported replication lag from the primary replica. The exporter will only acknowledge records which have been exported before this lag time to a minimum quorum of secondary databases. The exporting will come to a stop when the lag time is exceeded and will only continue when the lag time is back within the configured limit. Note, that this strategy is not as precise as LSN replication monitoring and may lead to less frequent acknowledgements. It is recommended to use LSN replication monitoring whenever possible. ```yaml camunda.data.secondary-storage.rdbms.async-replication.enabled: true camunda.data.secondary-storage.rdbms.async-replication.type: TIME_LAG camunda.data.secondary-storage.rdbms.async-replication.min-sync-replicas: 2 ``` | Property name | Description | Default | | --------------------------------------------- | ----------------------------------------------------------------------------- | ------- | | `async-replication.enabled` | If the async replication monitoring should be enabled | false | | `async-replication.min-sync-replicas` | The minimal number of replicas in sync | 1 | | `async-replication.polling-interval` | The interval in which to check the replicas | PT15S | | `async-replication.max-lag` | The max tolerated lag of a replication (ISO-8601 duration) | PT15M | | `async-replication.pause-on-max-lag-exceeded` | If the exporter should pause exporting when the maximum lag limit is exceeded | false | #### Vendor support The following databases are supported for time lag replication monitoring: - Aurora Global DB with PostgreSQL - Aurora Global DB with MySQL - MSSQL - PostgreSQL To use the time lag replication monitoring with PostgreSQL, the database user must have the following additional privileges: - `PG_MONITOR` role ```sql GRANT PG_MONITOR TO ; ``` To use the time lag replication monitoring with MSSQL, the database user must have the following additional privileges: - `VIEW SERVER STATE` role on SQL Server 2019 and earlier versions ```sql GRANT VIEW SERVER STATE TO ; ``` - `VIEW SERVER PERFORMANCE STATE` role on SQL Server 2022 and newer versions ```sql GRANT VIEW SERVER PERFORMANCE STATE TO ; ``` ### Delay backoff replication monitoring The exporter always waits for a configured amount of time until an exported record is acknowledged to the broker as exported. This is supported for all databases. This is a fallback strategy for databases that do not support any other direct replication monitoring — prefer [LSN replication monitoring](#lsn-replication-monitoring) whenever your database vendor supports it. Delay backoff does not directly monitor any replication state, but instead adds a static delay to the acknowledgement of records to the broker. This can be used as a safety net to ensure that the Zeebe logstream segments are not compacted too early, even if the database replication is not fully in sync. This strategy requires external monitoring of the actual replication lag to ensure that the configured delay is sufficient for the database replication to catch up in case of a failover. :::warning The disk space used by the logstream is heavily influenced by the `delay` parameter: records accumulate on disk for the entire delay interval before they can be compacted. The exporter never acknowledges records before the configured delay period has elapsed, but may acknowledge them after this period has elapsed. Size the persistent volume to hold all records produced during the delay interval. If the volume is too small, Zeebe will run out of disk space and stop processing. ::: ```yaml camunda.data.secondary-storage.rdbms.async-replication.enabled: true camunda.data.secondary-storage.rdbms.async-replication.type: DELAY ``` | Property name | Description | Default | | --------------------------------------- | --------------------------------------------------------------------------------- | ------- | | `async-replication.enabled` | If the async replication monitoring should be enabled | false | | `async-replication.delay` | The delay to wait until a flushed record is acknowledged to the broker | -- | | `async-replication.queue-capacity` | Size of the internal queue of record positions to acknowledge | 8192 | | `async-replication.queue-debounce-time` | A debounce time to not add every record to the queue but only one every X seconds | PT5S | ### Compatibility matrix Use LSN-based monitoring when your database supports it. Otherwise, use time-based monitoring if available. Use delay backoff only when neither strategy is supported. Delay backoff doesn't monitor replication state directly and requires external monitoring of replication lag; see [delay backoff replication monitoring](#delay-backoff-replication-monitoring). | Database Vendor | LSN-based | Time-based | Delay backoff | | ----------------- | ------------------ | ------------------ | ------------------ | | Aurora PostgreSQL | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Aurora MySQL | :white_check_mark: | :white_check_mark: | :white_check_mark: | | PostgreSQL | :white_check_mark: | :white_check_mark: | :white_check_mark: | | MSSQL | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Oracle | :white_check_mark: | :x: | :white_check_mark: | | MariaDB | :x: | :x: | :white_check_mark: | | MySQL | :x: | :x: | :white_check_mark: | ## Usage with AWS Aurora PostgreSQL / MySQL Camunda supports **PostgreSQL** and **MySQL** as secondary storage backends. AWS Aurora PostgreSQL and AWS Aurora MySQL are compatible managed services and work when you configure them like standard PostgreSQL or MySQL databases. In addition to the standard PostgreSQL and MySQL JDBC drivers, you can use the **AWS Advanced JDBC Wrapper** to take advantage of Aurora-specific features such as improved failover handling and IAM-based authentication. To use the AWS JDBC wrapper with an Aurora PostgreSQL database, configure the JDBC URL for your Aurora engine: ```yaml camunda: data: secondary-storage: type: rdbms rdbms: url: jdbc:aws-wrapper:postgresql://aurora-postgresql-host:5432/camunda username: camunda password: camunda ``` To use the AWS JDBC wrapper with an Aurora MySQL database, configure the JDBC URL for your Aurora engine: ```yaml camunda: data: secondary-storage: type: rdbms rdbms: url: jdbc:aws-wrapper:mysql://aurora-mysql-host:3306/camunda username: camunda password: camunda ``` The AWS JDBC wrapper supports standard username/password authentication as well as IAM-based authentication. To use IAM authentication, enable the corresponding wrapper plugin and configure a database user without a password that has the required IAM permissions: ```yaml camunda: data: secondary-storage: type: rdbms rdbms: url: jdbc:aws-wrapper:postgresql://aurora-host:5432/camunda?wrapperPlugins=iam username: camunda ``` The AWS JDBC wrapper supports automatic failover detection when using Aurora GlobalDB. To use automatic failover detection, enable the corresponding wrapper plugin and optionally configure a failover timeout: ```yaml camunda: data: secondary-storage: type: rdbms rdbms: url: jdbc:aws-wrapper:postgresql://aurora-host:5432/camunda?wrapperPlugins=failover ``` In addition, you can override the default failoverTimeoutMs (60 seconds) by adding the `failoverTimeoutMs` parameter to the JDBC URL: `jdbc:aws-wrapper:postgresql://aurora-host:5432/camunda?wrapperPlugins=failover&failoverTimeoutMs=30000`. The AWS JDBC wrapper JAR is shipped with the Camunda distribution alongside most of the other JDBC drivers. There is no need to provide it separately. ### Per-physical-tenant credentials on Aurora When using [physical tenants](/self-managed/concepts/physical-tenants/index.md), each tenant can connect to Aurora with its own database user. Database-level permissions (schema grants, row-level security) are administered entirely in PostgreSQL, so tenant isolation is enforced by the database rather than by the application. For standard username/password authentication, override the connection settings per tenant: ```yaml camunda: data: secondary-storage: type: rdbms rdbms: url: jdbc:aws-wrapper:postgresql://aurora-host:5432/camunda?currentSchema=default_schema username: camunda password: camunda physical-tenants: tenanta: data: secondary-storage: rdbms: url: jdbc:aws-wrapper:postgresql://aurora-host:5432/camunda?currentSchema=tenant_a_schema username: tenant_a_user password: tenant-a-secret ``` For IAM authentication, the same pattern applies with the `iam` wrapper plugin and passwordless database users: ```yaml camunda: data: secondary-storage: type: rdbms rdbms: url: jdbc:aws-wrapper:postgresql://aurora-host:5432/camunda?wrapperPlugins=iam¤tSchema=default_schema username: camunda physical-tenants: tenanta: data: secondary-storage: rdbms: url: jdbc:aws-wrapper:postgresql://aurora-host:5432/camunda?wrapperPlugins=iam¤tSchema=tenant_a_schema username: tenant_a_user ``` With IAM authentication, the wrapper driver generates short-lived authentication tokens using the application's AWS identity (for example, the pod's IAM role when running on EKS with IRSA). The IAM permission `rds-db:connect` is granted **per database user**, so the single application identity is granted access to exactly the tenant database users it should reach — one AWS identity, many tenant-scoped database users. --- ## Relational databases Relational database (RDBMS) support is a core capability of the Orchestration Cluster in Camunda 8 Self-Managed. RDBMS is a standard secondary storage option for Orchestration Cluster installations, alongside Elasticsearch and OpenSearch. With RDBMS secondary storage, the RDBMS exporter writes orchestration data to your relational database, and the Orchestration Cluster API queries it. Operate, Tasklist, and Admin use that API rather than querying the database directly. In this context: - [Primary storage](/reference/glossary.md#primary-storage) is the broker-managed store for workflow execution state management. - [Secondary storage](/reference/glossary.md#secondary-storage) is an external store used for indexing, querying, analytics, and retention. For a deeper conceptual comparison, see [understanding primary vs secondary storage](/self-managed/concepts/secondary-storage/index.md#about-secondary-storage). ```mermaid graph LR exporter["RDBMS Exporter"] api["Orchestration Cluster API"] rdbms["RDBMS\n(secondary storage)"] exporter -->|"Write"| rdbms api -->|"Query"| rdbms style exporter fill:#e4eef8,stroke:#2272c9,color:#14082c style api fill:#e4eef8,stroke:#2272c9,color:#14082c style rdbms fill:#fde8da,stroke:#fc5d0d,color:#14082c ``` ## Deployment guides - Helm: [Configure RDBMS in Helm](/self-managed/deployment/helm/configure/database/rdbms.md) - Manual: [Configure RDBMS for manual installation](/self-managed/deployment/manual/rdbms/configuration.md) ## Related concepts - [Secondary storage](/self-managed/concepts/secondary-storage/index.md) - [Databases overview](/self-managed/concepts/databases/overview.md) --- ## End-to-end RDBMS setup guide This guide provides a unified approach to configuring relational databases for Camunda 8 across the Orchestration Cluster (Zeebe, Operate, Tasklist, Identity) and Camunda Hub. It answers the key setup questions and links to detailed, component-specific configuration reference. :::info - For **Orchestration Cluster configuration reference**, see [RDBMS configuration overview](/self-managed/concepts/databases/relational-db/configuration.md). - For **Camunda Hub configuration reference**, see [Camunda Hub database configuration](/self-managed/components/hub/configuration/database.md). - For **supported vendors and versions**, see the [RDBMS version support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md). - For **deployment-specific setup**, see [Helm RDBMS configuration](/self-managed/deployment/helm/configure/database/rdbms.md) or [manual RDBMS configuration](/self-managed/deployment/manual/rdbms/configuration.md). ::: ## Step 1: Decide on topology Before provisioning, choose whether to use a **shared database** or **separate databases** for each component. | Aspect | Shared database | Separate databases | | ------------ | --------------------------------------------------------------- | ----------------------------------------------------------------- | | **Use case** | Small deployments, unified DBA team | Large production, multi-team environments | | **Setup** | Single instance with different schemas/databases for OC and Hub | Independent instances per component | | **Pros** | Simplified administration, single backup policy | Independent scaling, isolated credentials, easier troubleshooting | | **Cons** | Shared resources, requires schema/database separation | Additional operational overhead, higher infrastructure costs | Both topologies are fully supported. Choose based on your organizational model and scaling needs. ## Step 2: Provision the database ### Prerequisites - **Supported RDBMS**: PostgreSQL (recommended), MariaDB, MySQL, SQL Server, Oracle, or H2 (development only). - **Versions**: See the [RDBMS version support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md). - **Network and credentials**: Ensure reachable database and user with DDL permissions (CREATE TABLE, ALTER TABLE) for schema initialization. - **SSL/TLS**: Optional but recommended. See [Camunda Hub SSL configuration](/self-managed/components/hub/configuration/database.md#configuring-ssl-for-the-database-connection) for guidance on JDBC URL parameters. ### Create database and user Choose your topology (shared or separate) and database vendor: ```sql -- Shared topology: single database for both OC and Hub CREATE DATABASE camunda ENCODING 'UTF8'; CREATE USER camunda WITH PASSWORD 'your-secure-password'; GRANT CONNECT ON DATABASE camunda TO camunda; GRANT USAGE ON SCHEMA public TO camunda; GRANT CREATE ON SCHEMA public TO camunda; GRANT CREATE ON DATABASE camunda TO camunda; -- Separate topology: independent instances -- CREATE DATABASE camunda_oc ENCODING 'UTF8'; -- CREATE USER camunda_oc WITH PASSWORD 'oc-password'; -- GRANT CONNECT ON DATABASE camunda_oc TO camunda_oc; -- GRANT USAGE ON SCHEMA public TO camunda_oc; -- GRANT CREATE ON SCHEMA public TO camunda_oc; -- GRANT CREATE ON DATABASE camunda_oc TO camunda_oc; ``` ```sql -- Shared topology CREATE DATABASE camunda CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER camunda@'%' IDENTIFIED BY 'your-secure-password'; GRANT ALL PRIVILEGES ON camunda.* TO camunda@'%'; FLUSH PRIVILEGES; -- Separate topology: repeat with different database and user names ``` For SQL Server and Oracle, follow your database vendor's user provisioning guidelines. The user must have CREATE TABLE and ALTER permissions. Consult your DBA and the detailed setup guides linked below. ## Step 3: Configure connections and authentication Configuration is component-specific but follows consistent principles across OC and Hub. ### Orchestration Cluster connection In your `values.yaml`: ```yaml orchestration: data: secondaryStorage: type: rdbms rdbms: url: jdbc:postgresql://postgres.example.com:5432/camunda_oc username: camunda_oc secret: existingSecret: rdbms-credentials existingSecretKey: password ``` For full Helm reference, see [RDBMS configuration in Helm](/self-managed/deployment/helm/configure/database/rdbms.md). Set environment variables before starting the Orchestration Cluster: ```bash export CAMUNDA_DATA_SECONDARY_STORAGE_TYPE=rdbms export CAMUNDA_DATA_SECONDARY_STORAGE_RDBMS_URL="jdbc:postgresql://postgres.example.com:5432/camunda_oc" export CAMUNDA_DATA_SECONDARY_STORAGE_RDBMS_USERNAME="camunda_oc" export CAMUNDA_DATA_SECONDARY_STORAGE_RDBMS_PASSWORD="your-secure-password" ``` For full manual setup, see [RDBMS configuration for manual installations](/self-managed/deployment/manual/rdbms/configuration.md). ### Camunda Hub connection Camunda Hub uses Spring Boot datasource configuration (separate from Orchestration Cluster). In your `values.yaml`: ```yaml camundaHub: restapi: externalDatabase: enabled: true url: jdbc:postgresql://postgres.example.com:5432/camunda_wm username: camunda_wm secret: inlineSecret: your-secure-password # Or use existingSecret for production ``` For full Camunda Hub reference, see [Camunda Hub database configuration](/self-managed/components/hub/configuration/database.md). Set environment variables before starting Camunda Hub: ```bash export SPRING_DATASOURCE_URL="jdbc:postgresql://postgres.example.com:5432/camunda_wm" export SPRING_DATASOURCE_USERNAME="camunda_wm" export SPRING_DATASOURCE_PASSWORD="your-secure-password" ``` ### Secrets management For production, use a secrets store (Kubernetes Secrets, Vault, AWS Secrets Manager) instead of inline passwords. **Kubernetes Secrets example**: ```bash kubectl create secret generic rdbms-credentials \ --from-literal=user=camunda_oc \ --from-literal=password=your-very-secure-password ``` Then reference it in `values.yaml`: ```yaml orchestration: data: secondaryStorage: rdbms: secret: existingSecret: rdbms-credentials existingSecretKey: password ``` ### Aurora IAM authentication If using Amazon Aurora PostgreSQL, configure IAM database authentication for enhanced security (no stored passwords). **Orchestration Cluster (Helm)**: ```yaml orchestration: data: secondaryStorage: type: rdbms rdbms: url: "jdbc:aws-wrapper:postgresql://aurora-cluster.123456789012.us-east-1.rds.amazonaws.com:5432/camunda?wrapperPlugins=iam&sslmode=require" username: db_user # IAM database user # No password needed; IAM token generated at runtime # Requires IAM role attached to pod (IRSA or Karpenter) ``` **Camunda Hub (Helm)**: ```yaml camundaHub: restapi: externalDatabase: enabled: true url: "jdbc:aws-wrapper:postgresql://aurora-cluster.123456789012.us-east-1.rds.amazonaws.com:5432/camunda?wrapperPlugins=iam" username: db_user # IAM database user # No password needed; IAM token generated at runtime env: - name: SPRING_DATASOURCE_DRIVERCLASSNAME value: software.amazon.jdbc.Driver ``` For detailed Aurora setup, see [Orchestration Cluster RDBMS configuration](/self-managed/concepts/databases/relational-db/configuration.md) and [Camunda Hub configuration](/self-managed/components/hub/configuration/database.md). ## Step 4: JDBC driver management Camunda bundles JDBC drivers for PostgreSQL, MariaDB, SQL Server, and H2. **You must provide a user-supplied driver for Oracle and MySQL.** For detailed driver provisioning strategies (init containers, custom images, volume mounts), see: - **Helm**: [JDBC driver management in Helm](/self-managed/deployment/helm/configure/database/rdbms-jdbc-drivers.md) - **Manual**: [Manual installation driver setup](/self-managed/deployment/manual/rdbms/configuration.md#jdbc-driver-management) - **Camunda Hub**: [Camunda Hub database configuration](/self-managed/components/hub/configuration/database.md) ## Step 5: Schema management **Orchestration Cluster uses Liquibase** → automatically creates and updates schema on startup (configurable via `camunda.data.secondary-storage.rdbms.auto-ddl`). **Camunda Hub uses Flyway** → migrations applied automatically on startup; **manual DBA execution not supported**. This is the key difference: Orchestration Cluster schema can be managed manually by a DBA if preferred, while Camunda Hub schema is automatic only. For access to SQL/Liquibase scripts or manual DBA procedures, see [Access SQL and Liquibase scripts](/self-managed/deployment/helm/configure/database/access-sql-liquibase-scripts.md). ## Step 6: Validation ### Orchestration Cluster checklist 1. Confirm the RDBMS exporter is enabled. 2. Check logs for Liquibase initialization: `[INFO] io.camunda.application.commons.rdbms.MyBatisConfiguration - Initializing Liquibase for RDBMS` 3. Verify database schema was initialized. See [access SQL and Liquibase scripts](/self-managed/deployment/helm/configure/database/access-sql-liquibase-scripts.md) for the complete table list for your database platform. 4. Run [RDBMS validation tests](/self-managed/deployment/helm/configure/database/validate-rdbms.md). ### Camunda Hub checklist 1. Confirm the database connection is configured. 2. Check logs for Flyway schema initialization. 3. Verify database contains tables (Flyway creates these automatically on startup). 4. Test the health endpoint: `/health`. ### Common issues | Issue | Resolution | | ------------------ | ------------------------------------------------------------------------------------------------------------------------ | | Schema not created | Grant `CREATE TABLE`, `ALTER TABLE` permissions to the database user. | | Connection timeout | Check firewall rules, security groups, and VPC peering. Verify network connectivity. | | TLS/SSL error | Verify database certificate and adjust JDBC SSL mode parameters. See component-specific configuration pages for details. | | Driver not found | Load user-supplied drivers via init container, custom image, or volume mount. | For detailed troubleshooting, see: - [RDBMS troubleshooting](/self-managed/deployment/helm/configure/database/rdbms-troubleshooting.md) - [Camunda Hub database troubleshooting](/self-managed/components/hub/troubleshooting/troubleshoot-database-connection.md) ## Step 7: Backup and restore Both components require RDBMS backups: - **Orchestration Cluster**: Zeebe exports data to RDBMS; backups are DBA responsibility. - **Camunda Hub**: All data stored in RDBMS; backups are DBA responsibility. Use vendor-native tools: PostgreSQL (`pg_dump`), MariaDB/MySQL (`mysqldump`), SQL Server (native backup), Oracle (RMAN). Test restore procedures in non-production environments regularly. ## Related guides - [Secondary storage overview](/self-managed/concepts/secondary-storage/index.md) - [RDBMS example deployment](/self-managed/deployment/helm/install/helm-with-rdbms.md) - [Operations and maintenance](/self-managed/deployment/manual/rdbms/operations.md) --- ## RDBMS version support policy Confirm which relational databases and versions Camunda supports for **Camunda 8 Self-Managed**, including lifecycle rules, managed PostgreSQL guidance, JDBC driver responsibilities, and component compatibility. ## Scope and applicability This policy applies to: - **Camunda 8 Self-Managed** deployments. It covers relational databases used for: - [Secondary storage for the Orchestration Cluster](/self-managed/concepts/secondary-storage/index.md) - [Camunda Hub](/self-managed/components/hub/configuration/database.md) :::info Camunda follows an **"all LTS versions"** rule for database support. All listed database versions are official **LTS releases still supported by the vendor**. Camunda tests against both the **oldest** and **newest** supported version of each database in CI. ::: ## Supported RDBMS The following relational databases are officially supported when used as an RDBMS backend (including as secondary storage where applicable): | Database | Supported versions | | ------------------------ | ----------------------- | | PostgreSQL | 15, 16, 17, 18 | | Amazon Aurora PostgreSQL | 15, 16, 17, 18 | | MariaDB | 10.11, 11.4, 11.8, 12.3 | | MySQL | 8.4, 9.7 | | Microsoft SQL Server | 2022, 2025 | | Oracle | 19c, 26ai | | H2 | 2.4 | :::info Changes to supported versions are announced in the [release announcements](/reference/announcements-release-notes/8110/8110-announcements.md). ::: ### Recommended database versions For **new deployments**, standardize on **one of the latest two supported versions** of a given database whenever possible. Older supported versions remain valid for existing deployments but are not recommended for new deployments. ## Managed PostgreSQL services Camunda supports PostgreSQL as a database engine, not individual managed service implementations. Managed PostgreSQL services are supported when: - They are fully compatible with the PostgreSQL versions listed in this policy, and - The service provider guarantees compatibility and support for those versions. Provider-specific operational behavior and service characteristics remain the responsibility of the service provider. Amazon Aurora PostgreSQL is listed separately because it is explicitly tested by Camunda, while other managed PostgreSQL services are supported based on compatibility guarantees provided by the service provider. ## New version support New database versions are added based on the following criteria: - **Vendor release:** The version must be officially released by the database vendor. - **Availability window:** The version must be available at least three months before the next Camunda minor release. - **Validation:** The version must be tested and validated across relevant Camunda components. If the availability window is missed, support is deferred to a subsequent minor release. ## Version deprecation and removal Versions are deprecated and removed using the following rules: - **Vendor end of support (EoS):** Versions are deprecated when vendor support has ended or is scheduled to end within six months. - **Removal:** Deprecated versions may be removed in a subsequent Camunda minor release once vendor support has ended. - **Advance notice:** Deprecations and removals are communicated in advance via release notes. - **Exceptions:** Support may be extended in exceptional cases, but any extension is explicitly documented and time-bound. Camunda may remove support for a version if it contains known issues that prevent reliable operation. ## Database-specific support notes | Database | LTS policy | Production use | Notes | | :------------------- | :-------------------------------------------- | :------------- | :---------------------------------------------------------------------------------------- | | PostgreSQL | All active major LTS versions | ✅ | — | | MariaDB | LTS releases only | ✅ | — | | MySQL | LTS releases only | ✅ | — | | Microsoft SQL Server | Mainstream or extended vendor support | ✅ | — | | Oracle Database | LTS releases | ✅ | — | | H2 | Development, testing, and evaluation use only | ❌ | Not for multi-broker clusters. File-based H2 persists on disk; in-memory H2 is ephemeral. | ## Supported JDBC driver versions Camunda bundles JDBC drivers for databases where redistribution is permitted and expects you to provide drivers where licensing or distribution constraints apply (for example, Oracle). Bundled driver versions are not pinned as a formal support guarantee. Each bundled driver is tested in CI against every supported version of the corresponding database listed above, and the exact version may change between Camunda patch releases as dependencies are updated. To inspect the precise driver version bundled in a given Camunda release, see the `lib/` directory of the distribution archive. ### Bundled drivers The following JDBC drivers and wrappers are included in the Camunda application images. Each is known to be compatible with every supported version of the corresponding database listed above: | Database / platform | Driver artifact | Notes | | :----------------------- | :----------------------------------------------- | :------------------------------------------------------------- | | PostgreSQL | `org.postgresql:postgresql` | Bundled in Camunda images. | | MariaDB | `org.mariadb.jdbc:mariadb-java-client` | Bundled in Camunda images. | | Microsoft SQL Server | `com.microsoft.sqlserver:mssql-jdbc` | Bundled in Camunda images (JRE 11). | | H2 | `com.h2database:h2` | Bundled in Camunda images. | | Amazon Aurora (AWS JDBC) | `software.amazon.jdbc:aws-advanced-jdbc-wrapper` | JDBC wrapper for AWS Aurora; requires a supported base driver. | ### User-supplied drivers The following databases require you to provide a compatible JDBC driver at runtime: | Database | Driver artifact | Tested version | Notes | | :------- | :-------------------------------- | :------------- | :---------------------------------------------------------------------- | | Oracle | `com.oracle.database.jdbc:ojdbc*` | 23.7.0.25.01 | Must be provided by you. May be OS/architecture-specific (amd64/arm64). | | MySQL | `com.mysql:mysql-connector-j` | 9.7.0 | Must be provided by you. | :::info Camunda validates driver compatibility in CI by testing against the oldest and newest supported database versions. A single driver version is expected to work across the supported database versions listed on this page. For deployment instructions, see [loading JDBC drivers into pods](/self-managed/deployment/helm/configure/database/rdbms.md#bundled-vs-custom-jdbc-drivers). ::: ## JDBC driver policy - Camunda supports the latest vendor-compatible JDBC driver for each supported RDBMS. - You are responsible for providing JDBC drivers when required (for example, Oracle or MySQL). - Driver versions are not pinned unless a specific version is required for compatibility or stability reasons. ## Component support matrix This table shows RDBMS support status by component (including RDBMS as secondary storage where applicable): | Component | Support status | Notes | | :------------------------ | :----------------- | :---------------------------------------------------------------------------------------- | | Orchestration Cluster | ✅ Fully supported | Supports RDBMS as secondary storage. | | Tasklist UI | ✅ Fully supported | All functionality available. | | Operate UI | ✅ Fully supported | All functionality available. | | Optimize | ❌ Not supported | Out of scope for RDBMS support. | | Camunda Hub | ✅ Fully supported | See [Hub database configuration](/self-managed/components/hub/configuration/database.md). | | Identity | ✅ Fully supported | All functionality available. | | Management API (REST API) | ✅ Fully supported | All functionality available. | :::note "Orchestration Cluster" refers to the secondary storage of the Orchestration Cluster. UI products are listed separately because their support status can differ by component. ::: ## Known limitations When using RDBMS (including as secondary storage), be aware of the following limitations: ### ID size limits Most user-defined strings exported to RDBMS-backed secondary storage are limited to **256 characters**. This includes BPMN and DMN IDs and names, job worker types, variable names, resource names, form IDs, and message names or correlation keys. If a DMN rule does not define its own ID, Camunda generates one from the decision ID, decision version, and rule index. In that case, keep the decision ID at **235 characters or fewer** so the generated rule ID stays within the RDBMS limit. Identity objects are also limited to **256 characters**, regardless of which secondary storage backend the Orchestration Cluster uses. For the full backend comparison, including the Elasticsearch/OpenSearch limit of 32,768 characters and details on how length is counted, see [string length limits for user-defined values](/self-managed/concepts/databases/overview.md#string-length-limits-for-user-defined-values). ### Variable comparison limits When retrieving variables through the REST API, the following comparison operators only apply to the first **4000 characters** (or **8191 characters**, depending on the database vendor) of large String or JSON variables: - `equals` - `notEquals` - `in` - `notIn` The `LIKE` comparison operator is not affected by this limitation. ### Collation and sorting behavior Because collation behavior varies across database vendors, results sorted by string fields may differ between systems. Ensure your application accounts for potential sorting variations when migrating between different RDBMS vendors. ## Installation and deployment guides For hands-on instructions to deploy Camunda with RDBMS, start with: - [End-to-end RDBMS setup guide](/self-managed/concepts/databases/relational-db/rdbms-setup-guide.md) - Unified guide for provisioning, topology decisions, driver management, and backup strategies. Then choose your deployment pattern: - [Secondary storage architecture](/self-managed/reference-architecture/reference-architecture.md#secondary-storage-architecture) - Canonical backend trade-offs and production architecture guidance. - [Production architecture with RDBMS](/self-managed/deployment/manual/rdbms/rdbms-production-architecture.md) - Manual deployment topology guidance for RDBMS. - [Manual installation with RDBMS](/self-managed/deployment/manual/rdbms/index.md) - Entry point for manual installation, configuration, and operations. - [RDBMS example deployment for Helm](/self-managed/deployment/helm/install/helm-with-rdbms.md) - Kubernetes/Helm-based example walkthrough. --- ## Document handling configuration in Camunda 8 Run :::note [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) can be used for local development only, and none of the storage options below are suitable for production. ::: Document Store configuration uses the unified `camunda.document.*` Spring property model. The sections below show the new configuration format. If you're migrating from legacy `DOCUMENT_*` environment variables, see [property mapping reference](#property-mapping-reference). Provide these properties in an `application.yaml` file. For example, set `camunda.document.default-store-id` to specify the active store. If no storage configuration is provided, the default document storage is **in-memory**. Documents are lost when the application is stopped. :::warning Deprecated: `DOCUMENT_*` and `DOCUMENT_STORE_*` environment variables The legacy `DOCUMENT_*` and `DOCUMENT_STORE_*` environment variables (for example, `DOCUMENT_STORE_AWS_BUCKET`, `DOCUMENT_DEFAULT_STORE_ID`) are deprecated. They continue to work for at least one release cycle via a backward compatibility bridge, but will be removed in a future release. When both the unified `camunda.document.*` properties and the legacy environment variables are set, `camunda.document.*` takes precedence. ::: ## Storage options By using **external cloud file bucket storage** with [**AWS S3**](https://aws.amazon.com/s3/), documents can be stored in a secure and scalable way. Buckets are integrated per cluster to ensure proper isolation and environment-specific management. ```yaml camunda: document: default-store-id: aws1 # the instance ID defined below aws: aws1: # store instance ID — must match default-store-id bucket-name: my-bucket bucket-path: documents/ # optional bucket-ttl: 30 # optional, days ``` | Property | Required | Description | | ---------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `camunda.document.aws..bucket-name` | Yes | Name of the AWS S3 bucket where documents are stored. | | `camunda.document.aws..bucket-path` | No | Folder-like path within the S3 bucket. Defaults to `""`. | | `camunda.document.aws..bucket-ttl` | No | Time-to-live for documents in the bucket, in days. | | `camunda.document.aws..endpoint` | No | Custom endpoint URL for an [S3-compatible object store](#s3-compatible-object-stores) such as MinIO, Cloudian, or Garage. When unset, the AWS SDK default endpoint is used. | | `camunda.document.aws..force-path-style` | No | Forces path-style addressing on the S3 client. Most S3-compatible backends require this. Automatically enabled when `endpoint` is set, so explicit configuration is rarely needed. | | `camunda.document.aws..chunked-encoding-enabled` | No | Controls AWS chunked transfer encoding. Set to `false` for S3-compatible backends that do not support `aws-chunked` streaming-signed uploads (for example, Garage). When unset, the SDK default (`true`) is used, which is correct for AWS S3 and MinIO. | | `camunda.document.default-store-id` | Yes | Instance ID of the store to use as the default. | | `camunda.document.thread-pool-size` | No | Number of threads in the document store thread pool. |
Deprecated: legacy environment variable equivalents ``` DOCUMENT_STORE_AWS_CLASS=io.camunda.document.store.aws.AwsDocumentStoreProvider DOCUMENT_STORE_AWS_BUCKET=my-bucket DOCUMENT_STORE_AWS_BUCKET_PATH=documents/ DOCUMENT_STORE_AWS_BUCKET_TTL=30 DOCUMENT_DEFAULT_STORE_ID=aws ```
### S3-compatible object stores To use an S3-compatible object store (MinIO, Cloudian, Garage, etc.), set `endpoint` on the store instance, in addition to the standard properties above. The bucket must already exist on the backend — Camunda does not create it. **Example (local MinIO):** ```yaml camunda: document: default-store-id: aws1 aws: aws1: bucket-name: camunda-docs endpoint: http://localhost:9000 ``` For Garage, add `chunked-encoding-enabled: false` to the same block. ### Troubleshooting checksum issues Some S3-compatible implementations cannot properly handle the checksum feature of the S3 client introduced with version 2.30.0. For more details, refer to [the AWS documentation](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/s3-checksums.html). If checksum-related errors appear, disable automated checksum creation by setting these environment variables before running `./c8run start`: ``` AWS_REQUEST_CHECKSUM_CALCULATION=WHEN_REQUIRED AWS_RESPONSE_CHECKSUM_VALIDATION=WHEN_REQUIRED ``` If you're still encountering issues with MD5 checksums required by your provider, enable legacy MD5 support by setting: ``` DOCUMENT_STORE_AWS_SUPPORT_LEGACY_MD5=true ``` AWS SDK credentials (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`) are resolved by the AWS SDK directly and are not part of `camunda.document.*`. Set them as environment variables as before. | Credentials variable | Required | Description | | ----------------------- | -------- | ------------------------------------------------------------------------------------ | | `AWS_ACCESS_KEY_ID` | Yes | Access key ID used to interact with AWS S3 buckets. | | `AWS_SECRET_ACCESS_KEY` | Yes | The AWS secret access key associated with `AWS_ACCESS_KEY_ID`, used to authenticate. | | `AWS_REGION` | Yes | Region where the bucket is. |
By using **external cloud file bucket storage** with [**Google Cloud Platform (GCP)**](https://cloud.google.com/storage), documents can be stored in a secure and scalable way. Buckets are integrated per cluster to ensure proper isolation and environment-specific management. ```yaml camunda: document: default-store-id: gcp1 # the instance ID defined below gcp: gcp1: # store instance ID — must match default-store-id bucket-name: my-gcp-bucket prefix: documents/ # optional ``` | Property | Required | Description | | --------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `camunda.document.gcp..bucket-name` | Yes | Name of the Google Cloud Storage bucket where documents are stored. | | `camunda.document.gcp..prefix` | No | Folder-like prefix within the GCS bucket. Defaults to `temp/` when unset. The value is used as written, with no separator appended, so a prefix isn't necessarily a folder. | | `camunda.document.default-store-id` | Yes | Instance ID of the store to use as the default. |
Deprecated: legacy environment variable equivalents ``` DOCUMENT_STORE_GCP_CLASS=io.camunda.document.store.gcp.GcpDocumentStoreProvider DOCUMENT_STORE_GCP_BUCKET=my-gcp-bucket DOCUMENT_DEFAULT_STORE_ID=gcp ```
The GCP credential variable (`GOOGLE_APPLICATION_CREDENTIALS`) is resolved by the GCP SDK directly and is not part of `camunda.document.*`. Set it as an environment variable as before. | Credentials variable | Required | Description | | -------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------- | | `GOOGLE_APPLICATION_CREDENTIALS` | Yes | Specifies the file path to a JSON key file that contains authentication credentials for a Google Cloud service account. |
By using **external cloud file bucket storage** with [**Azure Blob Storage**](https://azure.microsoft.com/en-us/products/storage/blobs), documents can be stored in a secure and scalable way. Azure Blob Storage supports two authentication methods: connection string and DefaultAzureCredential (Managed Identity). #### Prerequisites - An Azure Storage account with a Blob container. - For connection string authentication: The connection string from the Azure portal (**Settings > Access keys**). - For Managed Identity/DefaultAzureCredential authentication: The `Storage Blob Data Contributor` RBAC role assigned on the storage account. **Example (connection string):** ```yaml camunda: document: default-store-id: az1 # the instance ID defined below azure: az1: # store instance ID — must match default-store-id container-name: my-container connection-string: "DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=...;EndpointSuffix=core.windows.net" container-path: documents/ # optional ``` **Example (DefaultAzureCredential/Managed Identity):** ```yaml camunda: document: default-store-id: az1 # the instance ID defined below azure: az1: # store instance ID — must match default-store-id container-name: my-container endpoint: "https://myaccount.blob.core.windows.net" container-path: documents/ # optional ``` | Property | Required | Description | | ----------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | `camunda.document.azure..container-name` | Yes | Name of the Azure Blob Storage container. | | `camunda.document.azure..connection-string` | Conditional | Azure Storage connection string. Required unless using DefaultAzureCredential. | | `camunda.document.azure..endpoint` | Conditional | Storage account endpoint (for example, `https://myaccount.blob.core.windows.net`). Required when using DefaultAzureCredential/Managed Identity. | | `camunda.document.azure..container-path` | No | Optional path/prefix within the container. | | `camunda.document.default-store-id` | Yes | Instance ID of the store to use as the default. |
Deprecated: legacy environment variable equivalents Connection string: ``` DOCUMENT_STORE_AZURE_CLASS=io.camunda.document.store.azure.AzureBlobDocumentStoreProvider DOCUMENT_STORE_AZURE_CONTAINER=my-container DOCUMENT_STORE_AZURE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=...;EndpointSuffix=core.windows.net DOCUMENT_DEFAULT_STORE_ID=azure ``` DefaultAzureCredential/Managed Identity: ``` DOCUMENT_STORE_AZURE_CLASS=io.camunda.document.store.azure.AzureBlobDocumentStoreProvider DOCUMENT_STORE_AZURE_CONTAINER=my-container DOCUMENT_STORE_AZURE_ENDPOINT=https://myaccount.blob.core.windows.net DOCUMENT_DEFAULT_STORE_ID=azure ```
**In-memory** storage can be used to store documents during the application's runtime. When the application is stopped, documents are lost. In-memory storage is not suitable for production use, as pods and memory are not shared across components. Files stored in memory are not persisted and will be lost on application restart. In-memory is the default when no storage configuration is provided. To use in-memory explicitly when other stores are also configured: ```yaml camunda: document: default-store-id: inmemory1 # the instance ID defined below in-memory: inmemory1: {} # store instance ID — must match default-store-id ``` | Property | Required | Description | | ----------------------------------- | -------- | --------------------------------------------------------------------------- | | `camunda.document.in-memory.` | Yes | Declares an in-memory store instance. The value is an empty mapping (`{}`). | | `camunda.document.default-store-id` | Yes | Instance ID of the store to use as the default. |
Deprecated: legacy environment variable equivalents ``` DOCUMENT_STORE_INMEMORY_CLASS=io.camunda.document.store.inmemory.InMemoryDocumentStoreProvider DOCUMENT_DEFAULT_STORE_ID=inmemory ```
**Local storage** can be configured for a cluster to store documents in a local folder. It can be used only for local development with [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md). Local storage is not suitable for production use, as file paths are not shared across components or machines. This prevents components like Tasklist and Zeebe from accessing the same data. Files are stored locally, and their retention must be managed manually. ```yaml camunda: document: default-store-id: local1 # the instance ID defined below local: local1: # store instance ID — must match default-store-id path: /path/to/documents ``` | Property | Required | Description | | ----------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `camunda.document.local..path` | Yes | Path to the directory where uploaded files are stored. The directory must already exist and be writable by the user that runs Camunda 8 Run. Camunda 8 Run doesn't create it. | | `camunda.document.default-store-id` | Yes | Instance ID of the store to use as the default. |
Deprecated: legacy environment variable equivalents ``` DOCUMENT_STORE_LOCAL_CLASS=io.camunda.document.store.localstorage.LocalStorageDocumentStoreProvider DOCUMENT_STORE_LOCAL_PATH=/path/to/documents DOCUMENT_DEFAULT_STORE_ID=local ```
## API client permission requirements ### AWS S3 The API client must have the following AWS Identity and Access Management (IAM) permissions: | Permission | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `s3:DeleteObject` | This permission authorizes the API client to remove objects from the specified S3 bucket. | | `s3:GetObject` | This permission is required to retrieve contents and metadata of objects from Amazon S3. The API client will utilize this permission to download or access the contents of the documents that have been uploaded to the bucket. | | `s3:ListBucket` | This permission allows the application to verify it has access to the specified S3 bucket. Lack of this permission does not prevent the application from starting, but it logs a warning on application start-up. | | `s3:PutObject` | To upload documents to an Amazon S3 bucket, the API client must have this permission. | ### GCP The API client must have the following permissions: | Permission | Description | | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `storage.buckets.get` | This permission allows the application to verify it has access to the specified bucket. Lack of this permission does not prevent the application from starting, but it logs a warning on application start-up. | | `storage.objects.get` | This permission allows the API client to retrieve objects from Google Cloud Storage. It is vital for downloading or accessing the contents of stored objects. | | `storage.objects.create` | With this permission, the API client can upload new objects to a bucket. It is essential for adding new documents to the storage. | | `storage.objects.update` | This permission enables the API client to update contents and metadata of existing objects within a bucket. | | `storage.objects.delete` | This permission grants the API client the ability to delete objects from a bucket. | | `iam.serviceAccounts.signBlob` | This permission allows the service account to sign data as part of the process to create secure, signed URLs for accessing uploaded documents. | ### Azure Blob Storage To use document handling with Azure Blob Storage, assign the `Storage Blob Data Contributor` RBAC role to the identity on the storage account. This role grants the following permissions: | Permission | Description | | ------------------------------------------------------------------------ | -------------------------------- | | `Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read` | Read blob content and metadata. | | `Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write` | Create or update blobs. | | `Microsoft.Storage/storageAccounts/blobServices/containers/blobs/delete` | Delete blobs from the container. | ## Property mapping reference Use this table to migrate from legacy `DOCUMENT_*` environment variables to the unified `camunda.document.*` properties. ### Root-level | Legacy environment variable | Unified property | | --------------------------- | ----------------------------------- | | `DOCUMENT_DEFAULT_STORE_ID` | `camunda.document.default-store-id` | | `DOCUMENT_THREAD_POOL_SIZE` | `camunda.document.thread-pool-size` | ### AWS S3 | Legacy environment variable | Unified property | | ------------------------------------------------------- | ---------------------------------------------------- | | `DOCUMENT_STORE__CLASS=...AwsDocumentStoreProvider` | Implicit — use the `aws` namespace | | `DOCUMENT_STORE__BUCKET` | `camunda.document.aws..bucket-name` | | `DOCUMENT_STORE__BUCKET_PATH` | `camunda.document.aws..bucket-path` | | `DOCUMENT_STORE__BUCKET_TTL` | `camunda.document.aws..bucket-ttl` | | `DOCUMENT_STORE__ENDPOINT` | `camunda.document.aws..endpoint` | | `DOCUMENT_STORE__FORCE_PATH_STYLE` | `camunda.document.aws..force-path-style` | | `DOCUMENT_STORE__CHUNKED_ENCODING_ENABLED` | `camunda.document.aws..chunked-encoding-enabled` | ### GCP | Legacy environment variable | Unified property | | ------------------------------------------------------- | --------------------------------------- | | `DOCUMENT_STORE__CLASS=...GcpDocumentStoreProvider` | Implicit — use the `gcp` namespace | | `DOCUMENT_STORE__BUCKET` | `camunda.document.gcp..bucket-name` | | `DOCUMENT_STORE__PREFIX` | `camunda.document.gcp..prefix` | ### Azure Blob | Legacy environment variable | Unified property | | ------------------------------------------------------------- | ----------------------------------------------- | | `DOCUMENT_STORE__CLASS=...AzureBlobDocumentStoreProvider` | Implicit — use the `azure` namespace | | `DOCUMENT_STORE__CONTAINER` | `camunda.document.azure..container-name` | | `DOCUMENT_STORE__CONTAINER_PATH` | `camunda.document.azure..container-path` | | `DOCUMENT_STORE__CONNECTION_STRING` | `camunda.document.azure..connection-string` | | `DOCUMENT_STORE__ENDPOINT` | `camunda.document.azure..endpoint` | ### Local storage | Legacy environment variable | Unified property | | ---------------------------------------------------------------- | ------------------------------------ | | `DOCUMENT_STORE__CLASS=...LocalStorageDocumentStoreProvider` | Implicit — use the `local` namespace | | `DOCUMENT_STORE__PATH` | `camunda.document.local..path` | ### In-memory | Legacy environment variable | Unified property | | ------------------------------------------------------------ | ---------------------------------------- | | `DOCUMENT_STORE__CLASS=...InMemoryDocumentStoreProvider` | Implicit — use the `in-memory` namespace | ## Startup validation The application validates the Document Store configuration at startup and fails with a clear error message in the following cases: - **Duplicate store IDs across namespaces**: Each store instance ID must be unique across all provider namespaces (`aws`, `gcp`, `azure`, `local`, `in-memory`). - **Missing required fields**: Required properties (for example, `bucket-name` for AWS or `container-name` for Azure) must be set. - **Unknown `default-store-id`**: The value of `camunda.document.default-store-id` must match a configured store instance ID. --- ## Document handling configuration in Docker Compose :::note None of the storage options below with Docker Compose are suitable for production. ::: Document Store configuration uses the unified `camunda.document.*` Spring property model. The sections below show the new configuration format. If you're migrating from legacy `DOCUMENT_*` environment variables, see [property mapping reference](#property-mapping-reference). For Docker Compose, add the `camunda.document.*` properties to the Orchestration Cluster application file that is already mounted by the distribution: - For the lightweight configuration, edit the selected file under `configuration/`. - For the full configuration, edit `.orchestration/application.yaml`. For example, set `camunda.document.default-store-id` in that file to specify the active store. You do not need to mount a second application file. If no storage configuration is provided, the default document storage is **in-memory**. Documents are lost when the application is stopped. :::warning Deprecated: `DOCUMENT_*` and `DOCUMENT_STORE_*` environment variables The legacy `DOCUMENT_*` and `DOCUMENT_STORE_*` environment variables (for example, `DOCUMENT_STORE_AWS_BUCKET`, `DOCUMENT_DEFAULT_STORE_ID`) are deprecated. They continue to work for at least one release cycle via a backward compatibility bridge, but will be removed in a future release. When both the unified `camunda.document.*` properties and the legacy environment variables are set, `camunda.document.*` takes precedence. ::: In the [Docker Compose distribution](/self-managed/quickstart/developer-quickstart/docker-compose.md), Zeebe and Tasklist run in the consolidated `orchestration` service and share one application configuration. Configure the document store once for that service. In-memory documents are still lost when the service restarts; use persistent or external storage when documents must survive restarts. By using **external cloud file bucket storage** with [**AWS S3**](https://aws.amazon.com/s3/), documents can be stored in a secure and scalable way. Buckets are integrated per cluster to ensure proper isolation and environment-specific management. ```yaml camunda: document: default-store-id: aws1 # the instance ID defined below aws: aws1: # store instance ID — must match default-store-id bucket-name: my-bucket bucket-path: documents/ # optional bucket-ttl: 30 # optional, days ``` | Property | Required | Description | | ---------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `camunda.document.aws..bucket-name` | Yes | Name of the AWS S3 bucket where documents are stored. | | `camunda.document.aws..bucket-path` | No | Folder-like path within the S3 bucket. Defaults to `""`. | | `camunda.document.aws..bucket-ttl` | No | Time-to-live for documents in the bucket, in days. | | `camunda.document.aws..endpoint` | No | Custom endpoint URL for an [S3-compatible object store](#s3-compatible-object-stores) such as MinIO, Cloudian, or Garage. When unset, the AWS SDK default endpoint is used. | | `camunda.document.aws..force-path-style` | No | Forces path-style addressing on the S3 client. Most S3-compatible backends require this. Automatically enabled when `endpoint` is set, so explicit configuration is rarely needed. | | `camunda.document.aws..chunked-encoding-enabled` | No | Controls AWS chunked transfer encoding. Set to `false` for S3-compatible backends that do not support `aws-chunked` streaming-signed uploads (for example, Garage). When unset, the SDK default (`true`) is used, which is correct for AWS S3 and MinIO. | | `camunda.document.aws..support-legacy-md5` | No | Enables legacy MD5 checksum support on the S3 client for providers that require MD5 checksums. See [troubleshooting checksum issues](#troubleshooting-checksum-issues). When unset, legacy MD5 support is disabled. | | `camunda.document.default-store-id` | Yes | Instance ID of the store to use as the default. | | `camunda.document.thread-pool-size` | No | Number of threads in the document store thread pool. |
Deprecated: legacy environment variable equivalents ``` DOCUMENT_STORE_AWS_CLASS=io.camunda.document.store.aws.AwsDocumentStoreProvider DOCUMENT_STORE_AWS_BUCKET=my-bucket DOCUMENT_STORE_AWS_BUCKET_PATH=documents/ DOCUMENT_STORE_AWS_BUCKET_TTL=30 DOCUMENT_DEFAULT_STORE_ID=aws ```
### S3-compatible object stores To use an S3-compatible object store (MinIO, Cloudian, Garage, etc.), set `endpoint` on the store instance, in addition to the standard properties above. The bucket must already exist on the backend — Camunda does not create it. **Example (MinIO running alongside Camunda in the same Compose network):** ```yaml camunda: document: default-store-id: aws1 aws: aws1: bucket-name: camunda-docs endpoint: http://minio:9000 ``` For Garage, add `chunked-encoding-enabled: false` to the same block. ### Troubleshooting checksum issues Some S3-compatible implementations cannot properly handle the checksum feature of the S3 client introduced with version 2.30.0. For more details, refer to [the AWS documentation](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/s3-checksums.html). If checksum-related errors appear, disable automated checksum creation by setting these environment variables on your orchestration and connectors containers: ``` AWS_REQUEST_CHECKSUM_CALCULATION=WHEN_REQUIRED AWS_RESPONSE_CHECKSUM_VALIDATION=WHEN_REQUIRED ``` If you're still encountering issues with MD5 checksums required by your provider, enable legacy MD5 support on the store instance: ```yaml camunda: document: aws: aws1: support-legacy-md5: true ``` AWS SDK credentials (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`) are resolved by the AWS SDK directly and are not part of `camunda.document.*`. Set them as environment variables as before. | Credentials variable | Required | Description | | ----------------------- | -------- | ------------------------------------------------------------------------------------ | | `AWS_ACCESS_KEY_ID` | Yes | Access key ID used to interact with AWS S3 buckets. | | `AWS_SECRET_ACCESS_KEY` | Yes | The AWS secret access key associated with `AWS_ACCESS_KEY_ID`, used to authenticate. | | `AWS_REGION` | Yes | Region where the bucket is. |
By using **external cloud file bucket storage** with [**Google Cloud Platform (GCP)**](https://cloud.google.com/storage), documents can be stored in a secure and scalable way. Buckets are integrated per cluster to ensure proper isolation and environment-specific management. ```yaml camunda: document: default-store-id: gcp1 # the instance ID defined below gcp: gcp1: # store instance ID — must match default-store-id bucket-name: my-gcp-bucket prefix: documents/ # optional ``` | Property | Required | Description | | --------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `camunda.document.gcp..bucket-name` | Yes | Name of the Google Cloud Storage bucket where documents are stored. | | `camunda.document.gcp..prefix` | No | Folder-like prefix within the GCS bucket. Defaults to `temp/` when unset. The value is used as written, with no separator appended, so a prefix isn't necessarily a folder. | | `camunda.document.default-store-id` | Yes | Instance ID of the store to use as the default. |
Deprecated: legacy environment variable equivalents ``` DOCUMENT_STORE_GCP_CLASS=io.camunda.document.store.gcp.GcpDocumentStoreProvider DOCUMENT_STORE_GCP_BUCKET=my-gcp-bucket DOCUMENT_DEFAULT_STORE_ID=gcp ```
The GCP credential variable (`GOOGLE_APPLICATION_CREDENTIALS`) is resolved by the GCP SDK directly and is not part of `camunda.document.*`. Set it as an environment variable as before. | Credentials variable | Required | Description | | -------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------- | | `GOOGLE_APPLICATION_CREDENTIALS` | Yes | Specifies the file path to a JSON key file that contains authentication credentials for a Google Cloud service account. |
By using **external cloud file bucket storage** with [**Azure Blob Storage**](https://azure.microsoft.com/en-us/products/storage/blobs), documents can be stored in a secure and scalable way. Azure Blob Storage supports two authentication methods: connection string and DefaultAzureCredential (Managed Identity). #### Prerequisites - An Azure Storage account with a Blob container. - For connection string authentication: The connection string from the Azure portal (**Settings > Access keys**). - For Managed Identity/DefaultAzureCredential authentication: The `Storage Blob Data Contributor` RBAC role assigned on the storage account. **Example (connection string):** ```yaml camunda: document: default-store-id: az1 # the instance ID defined below azure: az1: # store instance ID — must match default-store-id container-name: my-container connection-string: "DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=...;EndpointSuffix=core.windows.net" container-path: documents/ # optional ``` **Example (DefaultAzureCredential/Managed Identity):** ```yaml camunda: document: default-store-id: az1 # the instance ID defined below azure: az1: # store instance ID — must match default-store-id container-name: my-container endpoint: "https://myaccount.blob.core.windows.net" container-path: documents/ # optional ``` | Property | Required | Description | | ----------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | `camunda.document.azure..container-name` | Yes | Name of the Azure Blob Storage container. | | `camunda.document.azure..connection-string` | Conditional | Azure Storage connection string. Required unless using DefaultAzureCredential. | | `camunda.document.azure..endpoint` | Conditional | Storage account endpoint (for example, `https://myaccount.blob.core.windows.net`). Required when using DefaultAzureCredential/Managed Identity. | | `camunda.document.azure..container-path` | No | Optional path/prefix within the container. | | `camunda.document.default-store-id` | Yes | Instance ID of the store to use as the default. |
Deprecated: legacy environment variable equivalents Connection string: ``` DOCUMENT_STORE_AZURE_CLASS=io.camunda.document.store.azure.AzureBlobDocumentStoreProvider DOCUMENT_STORE_AZURE_CONTAINER=my-container DOCUMENT_STORE_AZURE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=...;EndpointSuffix=core.windows.net DOCUMENT_DEFAULT_STORE_ID=azure ``` DefaultAzureCredential/Managed Identity: ``` DOCUMENT_STORE_AZURE_CLASS=io.camunda.document.store.azure.AzureBlobDocumentStoreProvider DOCUMENT_STORE_AZURE_CONTAINER=my-container DOCUMENT_STORE_AZURE_ENDPOINT=https://myaccount.blob.core.windows.net DOCUMENT_DEFAULT_STORE_ID=azure ```
**In-memory** storage can be used to store documents during the application's runtime. When the application is stopped, documents are lost. In-memory storage is not suitable for production use, as pods and memory are not shared across components. In-memory is the default when no storage configuration is provided. To use in-memory explicitly when other stores are also configured: ```yaml camunda: document: default-store-id: inmemory1 # the instance ID defined below in-memory: inmemory1: {} # store instance ID — must match default-store-id ``` | Property | Required | Description | | ----------------------------------- | -------- | --------------------------------------------------------------------------- | | `camunda.document.in-memory.` | Yes | Declares an in-memory store instance. The value is an empty mapping (`{}`). | | `camunda.document.default-store-id` | Yes | Instance ID of the store to use as the default. |
Deprecated: legacy environment variable equivalents ``` DOCUMENT_STORE_INMEMORY_CLASS=io.camunda.document.store.inmemory.InMemoryDocumentStoreProvider DOCUMENT_DEFAULT_STORE_ID=inmemory ```
```yaml camunda: document: default-store-id: local1 # the instance ID defined below local: local1: # store instance ID — must match default-store-id path: /usr/local/camunda/documents ``` | Property | Required | Description | | ----------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `camunda.document.local..path` | Yes | The path to the directory which will host the uploaded files. Use `/usr/local/camunda/documents` — it is pre-created with the right permissions for the process user. | | `camunda.document.default-store-id` | Yes | Instance ID of the store to use as the default. |
Deprecated: legacy environment variable equivalents ``` DOCUMENT_STORE_LOCAL_CLASS=io.camunda.document.store.localstorage.LocalStorageDocumentStoreProvider DOCUMENT_STORE_LOCAL_PATH=/usr/local/camunda/documents DOCUMENT_DEFAULT_STORE_ID=local ```
## API client permission requirements ### AWS S3 The API client must have the following AWS Identity and Access Management (IAM) permissions: | Permission | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `s3:DeleteObject` | This permission authorizes the API client to remove objects from the specified S3 bucket. | | `s3:GetObject` | This permission is required to retrieve contents and metadata of objects from Amazon S3. The API client will utilize this permission to download or access the contents of the documents that have been uploaded to the bucket. | | `s3:ListBucket` | This permission allows the application to verify it has access to the specified S3 bucket. Lack of this permission does not prevent the application from starting, but it logs a warning on application start-up. | | `s3:PutObject` | To upload documents to an Amazon S3 bucket, the API client must have this permission. | ### GCP The API client must have the following permissions: | Permission | Description | | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `storage.buckets.get` | This permission allows the application to verify it has access to the specified bucket. Lack of this permission does not prevent the application from starting, but it logs a warning on application start-up. | | `storage.objects.get` | This permission allows the API client to retrieve objects from Google Cloud Storage. It is vital for downloading or accessing the contents of stored objects. | | `storage.objects.create` | With this permission, the API client can upload new objects to a bucket. It is essential for adding new documents to the storage. | | `storage.objects.update` | This permission enables the API client to update contents and metadata of existing objects within a bucket. | | `storage.objects.delete` | This permission grants the API client the ability to delete objects from a bucket. | | `iam.serviceAccounts.signBlob` | This permission allows the service account to sign data as part of the process to create secure, signed URLs for accessing uploaded documents. | ### Azure Blob Storage To use document handling with Azure Blob Storage, assign the `Storage Blob Data Contributor` RBAC role to the identity on the storage account. This role grants the following permissions: | Permission | Description | | ------------------------------------------------------------------------ | -------------------------------- | | `Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read` | Read blob content and metadata. | | `Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write` | Create or update blobs. | | `Microsoft.Storage/storageAccounts/blobServices/containers/blobs/delete` | Delete blobs from the container. | ## Property mapping reference Use this table to migrate from legacy `DOCUMENT_*` environment variables to the unified `camunda.document.*` properties. ### Root-level | Legacy environment variable | Unified property | | --------------------------- | ----------------------------------- | | `DOCUMENT_DEFAULT_STORE_ID` | `camunda.document.default-store-id` | | `DOCUMENT_THREAD_POOL_SIZE` | `camunda.document.thread-pool-size` | ### AWS S3 | Legacy environment variable | Unified property | | ------------------------------------------------------- | ---------------------------------------------------- | | `DOCUMENT_STORE__CLASS=...AwsDocumentStoreProvider` | Implicit — use the `aws` namespace | | `DOCUMENT_STORE__BUCKET` | `camunda.document.aws..bucket-name` | | `DOCUMENT_STORE__BUCKET_PATH` | `camunda.document.aws..bucket-path` | | `DOCUMENT_STORE__BUCKET_TTL` | `camunda.document.aws..bucket-ttl` | | `DOCUMENT_STORE__ENDPOINT` | `camunda.document.aws..endpoint` | | `DOCUMENT_STORE__FORCE_PATH_STYLE` | `camunda.document.aws..force-path-style` | | `DOCUMENT_STORE__CHUNKED_ENCODING_ENABLED` | `camunda.document.aws..chunked-encoding-enabled` | | `DOCUMENT_STORE__SUPPORT_LEGACY_MD5` | `camunda.document.aws..support-legacy-md5` | ### GCP | Legacy environment variable | Unified property | | ------------------------------------------------------- | --------------------------------------- | | `DOCUMENT_STORE__CLASS=...GcpDocumentStoreProvider` | Implicit — use the `gcp` namespace | | `DOCUMENT_STORE__BUCKET` | `camunda.document.gcp..bucket-name` | | `DOCUMENT_STORE__PREFIX` | `camunda.document.gcp..prefix` | ### Azure Blob | Legacy environment variable | Unified property | | ------------------------------------------------------------- | ----------------------------------------------- | | `DOCUMENT_STORE__CLASS=...AzureBlobDocumentStoreProvider` | Implicit — use the `azure` namespace | | `DOCUMENT_STORE__CONTAINER` | `camunda.document.azure..container-name` | | `DOCUMENT_STORE__CONTAINER_PATH` | `camunda.document.azure..container-path` | | `DOCUMENT_STORE__CONNECTION_STRING` | `camunda.document.azure..connection-string` | | `DOCUMENT_STORE__ENDPOINT` | `camunda.document.azure..endpoint` | ### Local storage | Legacy environment variable | Unified property | | ---------------------------------------------------------------- | ------------------------------------ | | `DOCUMENT_STORE__CLASS=...LocalStorageDocumentStoreProvider` | Implicit — use the `local` namespace | | `DOCUMENT_STORE__PATH` | `camunda.document.local..path` | ### In-memory | Legacy environment variable | Unified property | | ------------------------------------------------------------ | ---------------------------------------- | | `DOCUMENT_STORE__CLASS=...InMemoryDocumentStoreProvider` | Implicit — use the `in-memory` namespace | ## Startup validation The application validates the Document Store configuration at startup and fails with a clear error message in the following cases: - **Duplicate store IDs across namespaces**: Each store instance ID must be unique across all provider namespaces (`aws`, `gcp`, `azure`, `local`, `in-memory`). - **Missing required fields**: Required properties (for example, `bucket-name` for AWS or `container-name` for Azure) must be set. - **Unknown `default-store-id`**: The value of `camunda.document.default-store-id` must match a configured store instance ID. --- ## Document handling configuration in Helm This page covers two configuration approaches: - **Legacy approach (deprecated)**: `global.documentStore.*` in `values.yaml` — generates `DOCUMENT_*` environment variables. Still works via a backward compatibility bridge but will be removed in a future release. - **New approach (recommended)**: `extraConfiguration` with `camunda.document.*` Spring properties — the unified configuration model. See [using the unified configuration format](#using-the-unified-configuration-format). :::warning Deprecated: `DOCUMENT_*` and `DOCUMENT_STORE_*` environment variables The `global.documentStore.*` Helm values generate legacy `DOCUMENT_*` and `DOCUMENT_STORE_*` environment variables (for example, `DOCUMENT_STORE_AWS_BUCKET`, `DOCUMENT_DEFAULT_STORE_ID`). These are deprecated. They continue to work for at least one release cycle via a backward compatibility bridge, but will be removed in a future release. When both `camunda.document.*` properties and legacy environment variables are set, `camunda.document.*` takes precedence. ::: Helm offers external cloud file bucket storage options (recommended for production use when deploying with Helm) and in-memory storage (not suitable for production use): - By using **external cloud file bucket storage options**, documents can be stored in a secure and scalable way. Buckets are integrated per cluster to ensure proper isolation and environment-specific management. The following file bucket storage options are supported: - [**Google Cloud Platform (GCP)**](https://cloud.google.com/storage) - [**AWS S3**](https://aws.amazon.com/s3/) — including [S3-compatible object stores](#s3-compatible-object-storage) such as MinIO, Cloudian, or Garage (configured through the AWS S3 store with a custom endpoint) - [**Azure Blob Storage**](https://azure.microsoft.com/en-us/products/storage/blobs) - **In-memory** storage can be used to store documents during the application's runtime. When the application is stopped, documents are lost. In-memory storage is not suitable for production use, as pods and memory are not shared across components. Files stored in memory are not persisted and will be lost on application restart. If no storage configuration is provided, the default document storage is in-memory. However, in-memory storage is not supported in production environments, so a configuration update is required. This is because memory is not shared between pods or components, preventing services like Tasklist and Zeebe from accessing the same data. Additionally, all uploaded files are lost when the application restarts. To change the storage to **Google Cloud Platform**, **AWS S3**, or **Azure Blob Storage**, update the `values.yaml` file with the storage configuration parameters. The following `values.yaml` example shows the legacy configuration format. It generates deprecated `DOCUMENT_*` environment variables that are bridged to `camunda.document.*` properties at runtime. This approach still works but is deprecated. To use the new unified format instead, see [using the unified configuration format](#using-the-unified-configuration-format). The example below represents part of the [default Helm chart values](https://github.com/camunda/camunda-platform-helm/blob/main/charts/camunda-platform-8.10/values.yaml) and shows the fields you need to change to enable the storage type of your preference. :::note Azure Blob Storage configuration differs from AWS and GCP. Only the connection string secret is managed in `values.yaml` under `global.documentStore.type.azure`. All other Azure configuration (container name, class, endpoint, etc.) must be provided via [`extraConfiguration`](/self-managed/deployment/helm/configure/application-configs.md). See the [Azure Blob Storage configuration](#azure-blob-storage-configuration) section below for details. ::: ``` # Global configuration for variables which can be accessed by all sub charts global: ## Document Store Configuration documentStore: activeStoreId: "inmemory" type: aws: ## @param global.documentStore.type.aws.enabled Enable AWS document store configuration. enabled: false ## @extra global.documentStore.type.aws.irsa configuration for AWS IAM role authentication, supporting both IRSA (IAM Roles for Service Accounts) and EKS Pod Identity. irsa: ## @param global.documentStore.type.aws.irsa.enabled if true, no static AWS credentials are injected into pods, so the AWS SDK resolves credentials through its default provider chain; this supports both IRSA and EKS Pod Identity. If false (default), credentials are required via existingSecret or accessKeyId/secretAccessKey configuration. enabled: false ## @param global.documentStore.type.aws.storeId Custom prefix for AWS. Default will generate env vars containing 'storeId' such as DOCUMENT_STORE_AWS_CLASS. storeId: "AWS" ## @param global.documentStore.type.aws.region AWS region for the S3 bucket. (example: us-east-1) region: "" ## @param global.documentStore.type.aws.bucket Name of the AWS S3 bucket. bucket: "your-aws-bucket" ## @param global.documentStore.type.aws.bucketPath [string, nullable] (Optional) Path/prefix within the S3 bucket. bucketPath: "" ## @param global.documentStore.type.aws.bucketTtl [int, nullable] (Optional) Time-to-live for documents in the S3 bucket (number in days). bucketTtl: 0 ## @param global.documentStore.type.aws.endpoint (Optional) Endpoint URL for an S3-compatible object store (for example, http://minio.minio.svc.cluster.local:9000). When unset, the AWS SDK default endpoint is used. endpoint: "" ## @param global.documentStore.type.aws.forcePathStyle [nullable] (Optional) Force path-style addressing on the S3 client. When unset, path-style is auto-enabled if an endpoint is configured, otherwise the SDK default is used. forcePathStyle: ## @param global.documentStore.type.aws.chunkedEncodingEnabled [nullable] (Optional) Enable AWS chunked transfer encoding. Set to false for S3-compatible backends that do not support it (for example, Garage). When unset, the SDK default (true) is used. chunkedEncodingEnabled: ## @param global.documentStore.type.aws.class Fully qualified class name for the AWS document store provider. class: "io.camunda.document.store.aws.AwsDocumentStoreProvider" ## @extra global.documentStore.type.aws.accessKeyId configuration to provide the AWS access key ID. accessKeyId: ## @param global.documentStore.type.aws.accessKeyId.secret.existingSecret can be used to reference an existing Kubernetes Secret containing the access key ID. ## @param global.documentStore.type.aws.accessKeyId.secret.existingSecretKey defines the key within the existing secret object. secret: existingSecret: "" existingSecretKey: "access-key-id" ## @extra global.documentStore.type.aws.secretAccessKey configuration to provide the AWS secret access key. secretAccessKey: ## @param global.documentStore.type.aws.secretAccessKey.secret.existingSecret can be used to reference an existing Kubernetes Secret containing the secret access key. ## @param global.documentStore.type.aws.secretAccessKey.secret.existingSecretKey defines the key within the existing secret object. secret: existingSecret: "" existingSecretKey: "secret-access-key" gcp: ## @param global.documentStore.type.gcp.enabled Enable GCP document store configuration. enabled: false ## @param global.documentStore.type.gcp.storeId Custom prefix for GCP. Default will generate env vars containing 'storeId' such as DOCUMENT_STORE_GCP_CLASS. storeId: "GCP" ## @param global.documentStore.type.gcp.bucket Name of the GCP bucket. bucket: "your-gcp-bucket" ## @param global.documentStore.type.gcp.class Fully qualified class name for the GCP document store provider. class: "io.camunda.document.store.gcp.GcpDocumentStoreProvider" ## @param global.documentStore.type.gcp.existingSecret Reference to an existing Kubernetes secret containing GCP credentials. existingSecret: "gcp-credentials" ## @param global.documentStore.type.gcp.credentialsKey Key in the GCP credentials secret that contains the service-account JSON. credentialsKey: "service-account.json" ## @param global.documentStore.type.gcp.mountPath Mount path for the GCP credentials secret. mountPath: "/var/secrets/gcp" ## @param global.documentStore.type.gcp.fileName The file name for the GCP credentials JSON. fileName: "service-account.json" inmemory: ## @param global.documentStore.type.inmemory.enabled Enable in-memory document store configuration. enabled: true ## @param global.documentStore.type.inmemory.storeId Custom prefix for in-memory. Default will generate env vars containing 'storeId' such as DOCUMENT_STORE_INMEMORY_CLASS. storeId: "INMEMORY" ## @param global.documentStore.type.inmemory.class Fully qualified class name for the in-memory document store provider. class: "io.camunda.document.store.inmemory.InMemoryDocumentStoreProvider" ``` ## Using the unified configuration format To adopt the new `camunda.document.*` model for any provider, disable the corresponding `global.documentStore.type..enabled` flag and configure the store via `extraConfiguration`. This also enables advanced scenarios such as multiple named store instances. ### AWS S3 ```yaml global: documentStore: activeStoreId: "aws1" # must match the store instance ID in extraConfiguration type: aws: enabled: false # disable legacy env var generation accessKeyId: secret: existingSecret: "aws-credentials" existingSecretKey: "awsAccessKeyId" secretAccessKey: secret: existingSecret: "aws-credentials" existingSecretKey: "awsSecretAccessKey" orchestration: extraConfiguration: - file: aws-documentstore.yaml content: | camunda: document: aws: aws1: # store instance ID — must match activeStoreId bucket-name: my-bucket bucket-path: documents/ # optional bucket-ttl: 30 # optional, days connectors: extraConfiguration: - file: aws-documentstore.yaml content: | camunda: document: aws: aws1: # store instance ID — must match activeStoreId bucket-name: my-bucket ``` #### S3-compatible object storage Camunda's AWS S3 store can also target self-hosted S3-compatible object stores such as [MinIO](https://min.io/), [Cloudian](https://cloudian.com/), or [Garage](https://garagehq.deuxfleurs.fr/). Set these additional properties on the store instance in `extraConfiguration`: | Property | Description | | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `camunda.document.aws..endpoint` | URL of the S3-compatible server. Setting this switches the store into S3-compatible mode. Example: `http://minio.minio.svc.cluster.local:9000`. | | `camunda.document.aws..force-path-style` | Forces path-style bucket addressing on the S3 client. Most S3-compatible servers (MinIO, Garage) require this. Automatically enabled when `endpoint` is set, so explicit configuration is rarely needed. | | `camunda.document.aws..chunked-encoding-enabled` | Controls AWS chunked transfer encoding. Some S3-compatible backends (notably Garage) do not implement the `aws-chunked` streaming-signed upload mode and require this to be `false`. When unset, the SDK default (`true`) is used, which is correct for MinIO and similar. | Credentials are configured the same way as AWS S3, via `accessKeyId.secret` and `secretAccessKey.secret` (each with `existingSecret` and `existingSecretKey`) under `global.documentStore.type.aws`. The bucket must exist on the backend before Camunda starts. The chart doesn't create it. ##### Example: in-cluster MinIO ```yaml global: documentStore: activeStoreId: "aws1" type: aws: enabled: false accessKeyId: secret: existingSecret: "minio-credentials" existingSecretKey: "access-key-id" secretAccessKey: secret: existingSecret: "minio-credentials" existingSecretKey: "secret-access-key" orchestration: extraConfiguration: - file: aws-documentstore.yaml content: | camunda: document: aws: aws1: bucket-name: camunda-docs endpoint: http://minio.minio.svc.cluster.local:9000 connectors: extraConfiguration: - file: aws-documentstore.yaml content: | camunda: document: aws: aws1: bucket-name: camunda-docs endpoint: http://minio.minio.svc.cluster.local:9000 ``` MinIO accepts the AWS SDK's default streaming-signed uploads, so `chunked-encoding-enabled` is not set. For Garage, add `chunked-encoding-enabled: false` to the same block. ### GCP ```yaml global: documentStore: activeStoreId: "gcp1" # must match the store instance ID in extraConfiguration type: gcp: enabled: false # disable legacy env var generation existingSecret: "gcp-credentials" credentialsKey: "service-account.json" mountPath: "/var/secrets/gcp" fileName: "service-account.json" orchestration: extraConfiguration: - file: gcp-documentstore.yaml content: | camunda: document: gcp: gcp1: # store instance ID — must match activeStoreId bucket-name: my-gcp-bucket prefix: documents/ # optional, defaults to temp/ when unset connectors: extraConfiguration: - file: gcp-documentstore.yaml content: | camunda: document: gcp: gcp1: # store instance ID — must match activeStoreId bucket-name: my-gcp-bucket ``` ### Azure Blob Storage See [Azure Blob Storage configuration](#azure-blob-storage-configuration) below for Azure-specific setup, including connection string and Managed Identity options. ### Troubleshooting checksum issues Some S3-compatible implementations cannot properly handle the checksum feature of the S3 client introduced with version 2.30.0. For more details, refer to [the AWS documentation](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/s3-checksums.html). If checksum-related errors appear, disable automated checksum creation by adding these environment variables under `orchestration.env` and `connectors.env`: ```yaml orchestration: env: - name: AWS_REQUEST_CHECKSUM_CALCULATION value: WHEN_REQUIRED - name: AWS_RESPONSE_CHECKSUM_VALIDATION value: WHEN_REQUIRED connectors: env: - name: AWS_REQUEST_CHECKSUM_CALCULATION value: WHEN_REQUIRED - name: AWS_RESPONSE_CHECKSUM_VALIDATION value: WHEN_REQUIRED ``` If you're still encountering issues with MD5 checksums required by your provider, enable legacy MD5 support by adding to the same `env` lists: ```yaml orchestration: env: - name: DOCUMENT_STORE_AWS_SUPPORT_LEGACY_MD5 value: "true" connectors: env: - name: DOCUMENT_STORE_AWS_SUPPORT_LEGACY_MD5 value: "true" ``` ## Azure Blob Storage configuration Azure Blob Storage uses a different configuration pattern than AWS and GCP. Only the connection string secret is managed via `values.yaml` under `global.documentStore.type.azure`. All other configuration (container name, class, endpoint, etc.) must be provided by the user via `orchestration.extraConfiguration` and `connectors.extraConfiguration`. This follows the same [`extraConfiguration` pattern](/self-managed/deployment/helm/configure/application-configs.md) used by other application-level settings in the 8.9+ chart. ### Prerequisites - An Azure Storage account with a Blob container. - For connection string authentication: The connection string from the Azure portal (**Settings > Access keys**). - For Managed Identity/DefaultAzureCredential authentication: The `Storage Blob Data Contributor` RBAC role assigned on the storage account. ### Authentication options Azure Blob Storage supports two authentication methods: 1. **Connection string** — simplest setup. The connection string is injected as a secret via `global.documentStore.type.azure.connectionString.secret`. 2. **DefaultAzureCredential** (recommended for AKS): Uses Workload Identity or Managed Identity. Set the `endpoint` in `extraConfiguration` instead of providing a connection string. Requires the `Storage Blob Data Contributor` RBAC role on the storage account. ### Connection string authentication This example uses a connection string stored in a Kubernetes secret. ```yaml global: documentStore: activeStoreId: "az1" # must match the store instance ID in extraConfiguration type: azure: connectionString: secret: existingSecret: "azure-storage-credentials" existingSecretKey: "connection-string" orchestration: extraConfiguration: - file: azure-documentstore.yaml content: | camunda: document: azure: az1: # store instance ID — must match activeStoreId container-name: my-container connectors: extraConfiguration: - file: azure-documentstore.yaml content: | camunda: document: azure: az1: # store instance ID — must match activeStoreId container-name: my-container ``` ### Managed Identity/DefaultAzureCredential When using AKS Workload Identity or Managed Identity, omit the connection string secret and set the `endpoint` instead: ```yaml global: documentStore: activeStoreId: "az1" # must match the store instance ID in extraConfiguration # No connectionString secret needed — DefaultAzureCredential handles auth orchestration: extraConfiguration: - file: azure-documentstore.yaml content: | camunda: document: azure: az1: # store instance ID — must match activeStoreId container-name: my-container endpoint: https://myaccount.blob.core.windows.net connectors: extraConfiguration: - file: azure-documentstore.yaml content: | camunda: document: azure: az1: # store instance ID — must match activeStoreId container-name: my-container endpoint: https://myaccount.blob.core.windows.net ``` ## Startup validation The application validates the Document Store configuration at startup and fails with a clear error message in the following cases: - **Duplicate store IDs across namespaces**: Each store instance ID must be unique across all provider namespaces (`aws`, `gcp`, `azure`, `local`, `in-memory`). - **Missing required fields**: Required properties (for example, `bucket-name` for AWS or `container-name` for Azure) must be set. - **Unknown `default-store-id`**: The value of `camunda.document.default-store-id` (or `activeStoreId` in Helm values) must match a configured store instance ID. --- ## Document handling configuration Camunda supports multiple storage options for handling documents in Self-Managed environments. Depending on your deployment setup and production requirements, you can choose from cloud-based, local, or in-memory storage methods. The following section outlines supported storage options, their intended use cases, and configuration guidance. Jump to the environment configuration options below: - [Camunda 8 Run](/self-managed/concepts/document-handling/configuration/camunda-8-run.md) - [Docker Compose](/self-managed/concepts/document-handling/configuration/docker.md) - [Helm](/self-managed/concepts/document-handling/configuration/helm.md) :::note Camunda 8 Run is a fast way for users to test the capabilities of the platform, but it is not necessarily scalable. Docker Compose is not recommended for use in production. Therefore, Helm is currently the **only** option to deploy the platform in a Self-Managed environment for production. ::: ## Supported storage options - By using **external cloud file bucket storage**, documents can be stored in a secure and scalable way. Buckets are integrated per cluster to ensure proper isolation and environment-specific management. The following file bucket storage options are supported: - [**Google Cloud Platform (GCP)**](https://cloud.google.com/storage) - [**AWS S3**](https://aws.amazon.com/s3/) — including [S3-compatible object stores](/self-managed/concepts/document-handling/configuration/helm.md#s3-compatible-object-storage) such as MinIO, Cloudian, or Garage (configured through the AWS S3 store with a custom endpoint) - [**Azure Blob Storage**](https://azure.microsoft.com/en-us/products/storage/blobs) - Configuring these storages is supported in [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md), [Docker Compose](/self-managed/quickstart/developer-quickstart/docker-compose.md), and [Helm](/self-managed/deployment/helm/install/quick-install.md). - **Local storage** can be configured for a cluster to store documents in a local folder. - It can be used only for local development with [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md). - Local storage is not suitable for production use, as pods and file paths are not shared across components. This prevents components like Tasklist and Zeebe from accessing the same data. Files are stored locally, and their retention must be managed manually. - If you're using a container image and a mounted volume for your storage, you can use the path `/usr/local/camunda/documents` as the mount path, as it will already have the right permissions for the Camunda process to read and write to it. - **In-memory** storage can be used to store documents during the application's runtime. When the application is stopped, documents are lost. - It can be used with [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md), [Docker Compose](/self-managed/quickstart/developer-quickstart/docker-compose.md) and [Helm](/self-managed/deployment/helm/install/quick-install.md). - In-memory storage is not suitable for production use, as pods and memory are not shared across components. Files stored in memory are not persisted and will be lost on application restart. ## Physical Tenant isolation When running Physical Tenants, each tenant must be assigned a distinct document store location. Camunda validates uniqueness at startup and fails if two tenants resolve to the same `provider, bucket/container, path` tuple, or if one tenant's path is nested inside another's. For the per-tenant configuration model, including the root catalog, `assigned` restriction, field-level overrides, and startup collision examples, see [document store storage](/self-managed/concepts/physical-tenants/storage-isolation.md#document-store-storage). ## Storage policies - **Maximum upload size for one or multiple files**: 10 MB - **File expiration time/time-to-live (TTL) policy**: With Self-Managed, users may define their own lifecycle policies. A custom expiration date can be specified via metadata for each document. The [document upload API](/apis-tools/orchestration-cluster-api-rest/specifications/create-document.api.mdx) allows this. You can only set a custom expiration date earlier than the bucket's TTL; requesting a later date results in it being capped to the bucket's TTL. For forms, this defaults to the cluster configuration as there is no set custom TTL for forms. --- ## Getting started with document handling Camunda 8 Self-Managed supports document storage and management using Camunda Forms, connectors, Tasklist, and the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/specifications/create-document.api.mdx). You can store, track, and manage binary data, like PDFs, images and other file types, across development and production environments. If you need tenant-isolated document storage details for Physical Tenants, see [Physical Tenant isolation model](/self-managed/concepts/physical-tenants/index.md). Storage must be configured according to your infrastructure and operational needs. :::note For SaaS-focused document handling content, visit our [SaaS documentation](/components/document-handling/getting-started.md). ::: ## Use cases and capabilities Document handling can be beneficial for different process use cases, such as uploading a document to a BPMN process, displaying and downloading a document, sending a document to an external system via a connector, and automating documents with [intelligent document processing](/components/hub/workspace/modeler/idp/idp-example.md). Step through all of these capabilities in the [use cases section](/components/document-handling/getting-started.md). ## Storage integration and configuration You can configure document storage based on your deployment setup and production requirements. Supported options include an external cloud storage such as Google Cloud Platform (GCP), AWS S3, or Azure Blob Storage, local file storage or an in-memory storage. Learn how to set it up in the [storage configuration guide](/self-managed/concepts/document-handling/configuration/index.md). --- ## Camunda exporters As Zeebe processes jobs and workflows, or performs internal maintenance (for example, Raft failover), it produces an ordered stream of records. :::note Exporters are not available in Camunda 8 Software-as-a-Service (SaaS). ::: ![record-stream](img/exporters-stream.png) Although clients cannot directly inspect this stream, Zeebe can load user-defined code, known as an exporter, to process each record. An exporter provides a single entry point to handle every record written to the stream. Exporters can be used for various purposes: - Persist historical data by pushing it to an external data warehouse - Export records to visualization tools (for example, [zeebe-simple-monitor](https://github.com/camunda-community-hub/zeebe-simple-monitor)) Zeebe loads exporters only if they are configured in the main Zeebe YAML configuration file. Exporters are initialized when Zeebe starts. Exporters receive only records produced after they are configured. Camunda 8 Self-Managed ships several built-in exporters, including the [Camunda exporter](../components/orchestration-cluster/zeebe/exporters/camunda-exporter.md), [Elasticsearch](../components/orchestration-cluster/zeebe/exporters/elasticsearch-exporter.md), [OpenSearch](../components/orchestration-cluster/zeebe/exporters/opensearch-exporter.md), and the RDBMS exporter (see [RDBMS configuration](/self-managed/concepts/databases/relational-db/configuration.md)). Use a custom exporter only when you need a different target system or behavior. Zeebe manages data deletion through two distinct mechanisms to reduce disk usage: 1. Internal state deletion: Zeebe automatically deletes data from its internal state (RocksDB) when it's no longer operationally required, such as when a process instance completes. This deletion is independent of exporters. 2. Log stream compaction: The event log stream (which stores all runtime and historical records) is compacted based on positions acknowledged by exporters. Each exporter acknowledges the position of the last record it has successfully processed. The stream processor also marks the position it has processed. Log compaction then occurs up to the lowest acknowledged position across all exporters and the stream processor, ensuring the log can be safely truncated without losing data that exporters haven't yet consumed. :::note If no exporters are configured, Zeebe automatically deletes data when it's no longer needed. To retain historical data, you must configure an exporter to stream records to an external system. ::: :::warning A custom exporter that does not acknowledge record positions prevents log compaction and can take down the broker. Because compaction only advances up to the **lowest** acknowledged position across all exporters, a single exporter that never advances its position pins the log indefinitely. The event log then grows without bound until it fills the disk, which stops the broker. When implementing or activating a custom exporter: - **Verify that record acknowledgment is done.** Confirm the exporter calls `controller.updateLastExportedRecordPosition(record.getPosition())` after it has successfully processed each record (see [Custom exporter to filter specific records](#custom-exporter-to-filter-specific-records)), and that the acknowledged position keeps advancing during normal operation. - **Check disk (PVC) sizing before activating a new exporter.** A newly configured exporter starts from the current log position and temporarily becomes the lowest acknowledged position. Until it catches up, the log cannot be compacted past that point, so ensure the broker has enough disk headroom and that the exporter keeps pace with the record stream. To detect this early, monitor the broker's disk usage and the exporter throughput metric (`zeebe_exporter_events_total`). See [metrics](../operational-guides/monitoring/metrics.md) for details. ::: All exporters—whether loaded from an external JAR or not—interact with the broker through the [exporter interface](https://github.com/camunda/camunda/blob/main/zeebe/exporter-api/src/main/java/io/camunda/zeebe/exporter/api/Exporter.java). ## Loading Exporters are loaded during broker startup, before any processing begins. The broker validates each exporter configuration during loading and will fail to start if: - An exporter ID is not unique - The exporter references a non-existent or inaccessible JAR - The specified class does not exist or can't be instantiated - The exporter throws an exception in its `Exporter#configure` method This validation step allows exporters to perform lightweight configuration checks. During this phase, the context provides a partition ID value of `Context#NULL_PARTITION_VALUE`. At runtime, this will be replaced with the actual partition ID. :::note Zeebe instantiates the exporter for validation and then discards it. Exporters should avoid heavy computations during instantiation. ::: ### Metrics The Micrometer [MeterRegistry](https://docs.micrometer.io/micrometer/reference/concepts/registry.html) is available via the `Exporter#configure(Context)` method for exporters to record metrics: ```java public class SomeExporter implements Exporter { @Override public void configure(final Context context) { // ... registry = context.getMeterRegistry(); // ... } public void flush() { try (final var ignored = Timer.resource(registry, "meter.name")) { exportBulk(); } } } ``` When an exporter is validated, it receives an in-memory register that is discarded afterward. :::note Zeebe creates an isolated class loader for each JAR referenced in exporter configurations. If the same JAR is used by multiple exporters, they will share the same class loader. This design allows different exporters to depend on the same third-party libraries without concerns about version conflicts or class name collisions. System classes and those bundled with the Zeebe JAR are loaded via the system class loader. ::: Exporter-specific configuration is defined in the `[exporters.args]` nested map. This map is passed as a `Map` to the exporter's `Exporter#configure(Configuration)` method using the [Configuration](https://github.com/camunda/camunda/tree/main/zeebe/exporter-api/src/main/java/io/camunda/zeebe/exporter/api/context/Configuration.java) object. Configuration takes place in two phases: once during broker startup and again each time a partition elects a new leader. ## Processing At any given time, there is exactly one leader node for each partition. When a node becomes the leader for a partition, it starts an instance of the [exporter stream processor](https://github.com/camunda/camunda/tree/main/zeebe/broker/src/main/java/io/camunda/zeebe/broker/exporter/stream/ExporterDirector.java). This stream processor creates exactly one instance of each configured exporter and forwards every record on the stream to each exporter in sequence. :::note This means there is exactly one instance of each exporter per partition. For example, if you have four partitions and four processing threads, potentially four instances of your exporter may run simultaneously. ::: Zeebe guarantees at-least-once delivery semantics. This means that each record will be seen by an exporter at least once, but possibly more. Duplicate delivery can occur in scenarios such as: - Reprocessing after Raft failover (i.e., leader re-election) - Errors occurring before the exporter updates its position To reduce duplicates, the stream processor tracks the position of the last successfully exported record for each exporter. Because the stream is an ordered sequence of records with monotonically increasing positions, tracking the position is sufficient. Exporters set this position once they can ensure the corresponding record was exported successfully. :::note Although Zeebe minimizes duplicate record delivery, exporters must be designed to handle duplicates. Export operations must be idempotent. This can be implemented within the exporter, but if exporting to an external system, it's recommended to handle deduplication there to minimize load on Zeebe. Refer to the exporter-specific documentation for implementation details. ::: ### Error handling If an error occurs during the `Exporter#open(Context)` phase, the stream processor fails and is restarted. This may resolve transient issues automatically. In the worst case, no exporters will run until the errors are resolved. If an error occurs during the `Exporter#close` phase, it is logged, but other exporters are still allowed to finish their work and shut down gracefully. If an error occurs during record processing, the same record is retried continuously until the error no longer occurs. In the worst case, a single failing exporter can block all exporters for that partition. Currently, exporters are expected to implement their own retry and error-handling strategies—though this behavior may evolve in future Zeebe versions. ### Performance impact Each loaded exporter introduces some performance overhead. In extreme cases, a slow exporter may block a processing thread entirely. Each partition passes the next record to its exporters only after every exporter has handled the current record. Therefore, a custom exporter with a slow `export()` method has the following effects: - It delays all other exporters on the partition, including exporters that write to secondary storage, such as the Camunda Exporter. As a result, data becomes available later in Operate, Tasklist, and the REST Query API. - It holds back the exported position of the entire partition, delaying event log compaction and increasing disk usage. - When [flow-control throttling](/self-managed/operational-guides/configure-flow-control/configure-flow-control.md) is enabled, flow control measures the export rate and backlog across all exporters on the partition. A slow `export()` method reduces the export rate and increases the backlog. Consequently, throttling reduces the write rate of the entire partition, lowering user-visible throughput. Exporters handle each record sequentially, so no exporter can progress faster than the slowest one. Acknowledged positions do not expose this problem because each exporter determines when to acknowledge a position—for example, after flushing a batch. To identify a slow exporter, compare the average export time for each exporter. Group the `zeebe_exporter_exporting_duration_seconds_sum` and `zeebe_exporter_exporting_duration_seconds_count` metrics by the `exporter` label, and divide the rate of the former by the rate of the latter. Check custom exporters first. A different problem occurs when an exporter returns quickly from `export()` but acknowledges positions late or not at all, for example, because it flushes batches asynchronously. Its `zeebe_exporter_last_updated_exported_position` falls behind those of the other exporters. This does not slow the other exporters, but it delays log compaction, causing the event log in the broker’s journal to grow and use more disk space. To avoid performance bottlenecks, keep exporters simple and lightweight. Delegate heavy data transformation or enrichment to external systems. Batch writes to the target system and acknowledge a position only after the flush succeeds. Larger batches and longer flush delays reduce per-request overhead on the target system, but data takes longer to become available there. Batches that are too large can overwhelm the target system or be rejected. Acknowledgement also comes later, which delays log compaction and increases disk usage. :::warning When you enable or change exporter-side filters on an existing cluster, the exported record stream can change shape. If another component (such as Optimize) relies on a previously unfiltered sequence, this may lead to gaps or inconsistencies unless you follow the recommended upgrade flow. For Optimize‑specific guidance and examples, see the [Camunda 8 system configuration](../components/optimize/configuration/system-configuration-platform-8.md) documentation. ::: ## Purging The data purge feature allows you to delete all historical (and runtime) data from your cluster. Therefore every exporter needs to implement the `Exporter#purge` method. When a purge is happening, the `Exporter#purge` method is called. This method: - Deletes all data exported so far. - Is blocking and only returns when all data has been deleted. - May be retried and therefore must be idempotent. When the purge cluster operation is executed, the following steps are taken: - All nodes leave existing partitions resulting in a cluster with no partitions. This means all exporters are closed. At this point all runtime data is deleted. - For all previously configured exporters, the following steps are executed sequentially for each exporter: - It is configured via `Exporter#configure(Context)` - The `Exporter#purge` method is called. - The exporter is closed via `Exporter#close`. - Partitions are bootstrapped and nodes rejoin the partitions with the same configuration as before the purge. :::note `Exporter#open(Context)` is not called during the purge operation. ::: When an exporter is purged, it is expected to delete all data, but not schemas. In the case that an exporter exports to a database, only the records are deleted, not the tables themselves. :::note All resources required for purging need to be closed afterwards to avoid memory leaks. ::: ## Custom exporter to filter specific records The exporter interface supports record filtering through the [`Context#RecordFilter`](https://github.com/camunda/camunda/blob/main/zeebe/exporter-api/src/main/java/io/camunda/zeebe/exporter/api/context/Context.java) interface. At a high level, filtering happens in two phases: - Metadata-level filtering via `acceptType`, `acceptValue`, and `acceptIntent`, which runs before records are deserialized and is very cheap. - Record-level filtering via `acceptRecord(Record)`, which can inspect the fully deserialized record value when you need richer conditions (for example, inspecting variables or BPMN process IDs). Valid record types and value types can be found in the [protocol definition](https://github.com/camunda/camunda/blob/main/zeebe/protocol/src/main/resources/protocol.xml), while intents are listed in the [Intent enum class](https://github.com/camunda/camunda/blob/main/zeebe/protocol/src/main/java/io/camunda/zeebe/protocol/record/intent/Intent.java). For example, you can implement a custom exporter that only exports records with: - Record type: `EVENT` - Value type: `JOB` - Intent: `CREATED` ```java public class CustomExporterFilter implements RecordFilter { @Override public boolean acceptType(RecordType recordType) { return recordType == RecordType.EVENT; } @Override public boolean acceptValue(ValueType valueType) { return valueType == ValueType.JOB; } @Override public boolean acceptIntent(Intent intent) { return intent == JobIntent.CREATED; } } ``` You can then set this filter in the `Exporter#configure` method of your custom exporter: ```java public class CustomExporter implements Exporter { // ... private Controller controller; @Override public void open(final Controller controller) { this.controller = controller; // ... } @Override public void configure(final Context context) { // ... context.setFilter(new CustomExporterFilter()); } @Override public void export(final Record record) { // ... // after handling the record, acknowledge the position controller.updateLastExportedRecordPosition(record.getPosition()); } // ... } ``` :::note - After handling the record, you must acknowledge the position by calling `controller.updateLastExportedRecordPosition(record.getPosition())`. Because the stream is an ordered sequence of records with monotonically increasing positions, tracking the position is sufficient. Exporters set this position once they can ensure the corresponding record was exported successfully. - All filter methods are combined with logical `AND`. A record is exported only if it passes `acceptType`, `acceptValue`, `acceptIntent`, and (when implemented) `acceptRecord`. In simple cases you can implement only the metadata methods; use `acceptRecord` when you need to inspect full record values. ::: ### Listen to expired messages with a custom filter You can also create a custom filter to listen to expired messages. This can be useful if you want to take specific actions on messages that have expired, such as logging them or re-publishing them. For example, if you want to allow exporting only message events with `EXPIRED` intent, follow the steps below: 1. Implement the `RecordFilter` interface: ```java public class MessageExpiredExporterFilter implements RecordFilter { @Override public boolean acceptType(RecordType recordType) { return recordType == RecordType.EVENT; } @Override public boolean acceptValue(ValueType valueType) { return valueType == ValueType.MESSAGE; } @Override public boolean acceptIntent(Intent intent) { if (intent instanceof MessageIntent messageIntent) { return messageIntent == MessageIntent.EXPIRED; } return true; } } ``` :::note This filter will only accept records of type `EVENT`, value type `MESSAGE`, and intent `EXPIRED`. To accept more record types, value types, and intents, modify the `acceptType`, `acceptValue`, and `acceptIntent` methods accordingly. ::: 2. Set the `MessageExpiredExporterFilter` filter in the `Exporter#configure` method of your custom exporter: ```java public class MessageExpiredExporter implements Exporter { // ... private Controller controller; @Override public void open(final Controller controller) { this.controller = controller; // ... } @Override public void configure(final Context context) { // ... context.setFilter(new MessageExpiredExporterFilter()); } @Override public void export(final Record record) { // ... // after handling the record, acknowledge the position controller.updateLastExportedRecordPosition(record.getPosition()); } // ... } ``` :::info Messages with zero TTL will also be exported with this filter. If a message with zero TTL is republished after expiration, it will immediately expire again, causing the exporter to receive it again. This creates an infinite loop of republishing and expiring the same message, potentially leading to the engine blocked from processing other records. To avoid this, check the message TTL before republishing it. ::: 3. (Optional) By default, the exporter will not receive the full message body, only the message key with the empty message body is exported. To receive the full message body with the expired message, enable it via YAML configuration or environment variable. ```sh CAMUNDA_PROCESSING_ENABLEMESSAGEBODYONEXPIRED=true ``` ```yaml camunda: processing: enable-message-body-on-expired: true ``` :::caution Enabling the full message body for expired messages can impact the performance of message expiration. - When this feature flag is enabled, every deleted message is appended to the Zeebe engine's record stream **including the full message body**. - Because each expired message now carries its entire payload, the expiration checker's write buffer fills up faster. As a result, the checker requires more time (or more roundtrips) to process the same number of expired messages. - This can lead to an increasing backlog of messages waiting to be expired. For finer control over the expiration checker's behavior, see the [message TTL checker configuration](https://github.com/camunda/camunda/blob/main/dist/src/main/config/defaults.yaml) (`enable-async-message-ttl-checker`). ::: ### Optimize-oriented built-in filters Camunda‑maintained exporters for Elasticsearch and OpenSearch include built‑in filters that reduce the volume of data exported when their indices are used primarily by Optimize. These filters can: - Restrict exported variables (by name pattern or inferred value type). - Include or exclude whole processes (based on `bpmnProcessId`). - Enable an Optimize mode that exports only the record types and intents Optimize actually consumes. - Control variable scope for root and local variables, including options to drop all local variables or apply different filters to each scope. For concrete arguments, examples, and version details, see: - [Elasticsearch exporter](../components/orchestration-cluster/zeebe/exporters/elasticsearch-exporter.md#configuration) - [OpenSearch exporter](../components/orchestration-cluster/zeebe/exporters/opensearch-exporter.md#configuration) - [Camunda 8 system configuration](../components/optimize/configuration/system-configuration-platform-8.md) ## Schema In earlier Camunda versions, some upgrades required manual data migrations. These migrations often introduced: - Operational downtime - Risk of human error - Complex backup and rollback procedures Starting with Camunda 8.8, exporters are designed to support upgrades without requiring data migrations. This approach reduces complexity, minimizes downtime, and enables faster, more reliable releases. --- ## Cold Recovery Cold Recovery is Camunda's lowest-cost multi-region resilience configuration. It provides a documented, repeatable recovery path from complete primary-region loss using scheduled backups exported to cross-region object storage and a manual restore procedure into a secondary region. Cold Recovery is suited for production workloads in which recovery measured in hours is operationally acceptable. | Consideration | Value | | :----------------------------------- | :----------------------------------------------- | | **Recovery time (RTO)** | ~1–4 hours (operator and environment dependent) | | **Data loss (RPO)** | 15 minutes – 4 hours (backup-interval dependent) | | **Failover mode** | Manual, operator-initiated | | **Standing second region required?** | No. Restore into a newly provisioned region | :::important Cold Recovery [RTO](/reference/glossary.md#recovery-time-objective-rto) and [RPO](/reference/glossary.md#recovery-point-objective-rpo) depends on data volume, backup frequency, operator familiarity with the restore procedure, and the speed at which a secondary region can be provisioned. The ranges above are planning targets, not contractual commitments. ::: ## Architecture In Cold Recovery, a single active region runs the Camunda Orchestration Cluster. - Automated backup jobs export Zeebe partition snapshots and secondary storage backups (Elasticsearch, OpenSearch) to an S3-compatible object storage bucket replicated to a separate region. RDBMS secondary storage relies on database backup tools and must ensure durability across multiple regions. - There is no warm standby. For example, a second cluster does not run during normal operations. - On primary-region failure, an operator provisions a new environment in the secondary region and restores from the most recent consistent backup set. ## Backup scope You must back up **both** Primary and Secondary storage layers for a complete Cold Recovery backup. See [backup and restore](/self-managed/operational-guides/backup-restore/backup-and-restore.md). | Layer | Backup target | Backup mechanism | | :----------------------------------------------- | :--------------------------------------- | :----------------------------------------------------------------------- | | **Primary storage** (Zeebe log stream) | Zeebe partition snapshots | Zeebe Backup Management API | | **Secondary storage** (Elasticsearch/OpenSearch) | Elasticsearch/OpenSearch index snapshots | Orchestration cluster backup API | | **Secondary storage** (RDBMS) | Database dump or continuous backup | Database-native tools (`pg_dump`, Oracle RMAN, AWS RDS automated backup) | The RDBMS backup path is the **first phase** of new backup capabilities and currently covers a narrower set of components than the Elasticsearch/OpenSearch path. See [relational database backup](/self-managed/operational-guides/backup-restore/rdbms/backup.md) for the components it includes. ### Component coverage The following table shows backup component coverage: | Component | Included in backup? | Notes | | :-------------------------- | :------------------------------------------------- | :--------------------------------------------- | | **Zeebe** (primary storage) | Yes (partition snapshots) | Required | | **Operate** | Yes (via Elasticsearch/OpenSearch or RDBMS backup) | State is stored in secondary storage | | **Tasklist** | Yes (via Elasticsearch/OpenSearch or RDBMS backup) | State is stored in secondary storage | | **Admin** | Yes (via Elasticsearch/OpenSearch or RDBMS backup) | Authentication and authorization configuration | | **Optimize** | Elasticsearch/OpenSearch path only | Standalone component; back up independently | | **Management Identity** | Not included | Standalone component; back up independently | | **Camunda Hub** | Not included | Standalone component; back up independently | | **Connectors** | Not included | Stateless; redeploy from source | ## Cross-region backup replication Cross-region replication of the backup storage is the **necessary requirement** of Cold Recovery. - If backups exist only in the primary region, there is no disaster recovery when that region is lost, as the backups are lost with it. - As such, **replication is mandatory**, and not optional or just best practice. It is a prerequisite for Cold Recovery to work. The primary backup bucket must replicate objects to a bucket in a **separate region** so that backup data remains accessible if the primary region becomes unavailable. For S3-compatible object storage, this is typically configured as continuous cross-region replication on the bucket itself. For RDBMS secondary storage, use the database's native cross-region backup replication instead of S3 bucket replication. This means: - The replica bucket lives in a different region from the primary cluster. - Replication runs continuously and automatically - there is no manual copy step. - Replication lag is monitored and bounded; an unreplicated backup is not yet a recoverable backup. - The replica bucket's access policies allow restore from a freshly provisioned secondary-region environment that does not yet exist at backup time. For provider-specific guidance, see [backup and restore](/self-managed/operational-guides/backup-restore/backup-and-restore.md). ## Backup requirements In addition to cross-region replication, you must make sure the following is in place before a failure occurs: **Regular backup schedule**: Both Orchestration cluster and secondary storage must be backed up on a consistent schedule. Backup frequency directly determines RPO: a 1-hour backup interval means up to 1 hour of data loss in the worst case. For configuration instructions and scheduling guidance, see [backup and restore](/self-managed/operational-guides/backup-restore/backup-and-restore.md). ## Recovery flow When the primary region fails, Cold Recovery follows this sequence: 1. **Detect and declare**: Confirm the primary region is unavailable and initiate the recovery process. 2. **Fence the old region**: Before redirecting traffic, ensure the original region's cluster, job workers, and connectors are stopped or otherwise prevented from acting. See [fencing the old region](#fencing-the-old-region). 3. **Provision**: Spin up a new Camunda environment in the secondary region (or activate a pre-provisioned standby if one exists). 4. **Select restore point**: Identify the most recent complete, consistent backup set from the replica S3 bucket. 5. **Restore Camunda Orchestration cluster**: Follow the [restore procedure](/self-managed/operational-guides/backup-restore/backup-and-restore.md). 6. **Start and verify**: Deploy the Orchestration Cluster against the restored data and confirm health before routing traffic. 7. **Redirect traffic**: Update applications, DNS or load balancer configuration to point production traffic at the secondary region. The total elapsed time across all these steps is the realized RTO. For step-by-step restore instructions, see [backup and restore](/self-managed/operational-guides/backup-restore/backup-and-restore.md). :::tip Test the recovery flow We recommend testing this recovery flow end-to-end before a real incident occurs. Prior validation ensures each step works as expected in your environment. ::: :::important Detection and decision time count toward RTO Step 1 (detect and declare) is **not instantaneous** and is often the largest variable contributor to total RTO. Time-to-detect (how long until the outage is recognized) and time-to-decide (how long until the on-call operator authorizes a failover) can each run from minutes to hours depending on monitoring coverage, paging rotation, and escalation policy. The published 1–4 hour RTO assumes these phases are exercised regularly; otherwise they dominate the realized recovery time. ::: ### Fencing the old region If the region is truly gone, nothing is running there and no fencing is needed. The problem is when the region only _looks_ gone — a **network partition** means you cannot reach it, but the Orchestration Cluster, job workers, and connectors are still running inside it. If you start the restored cluster without first stopping the old one, you will have **two clusters that both think they own the same process instances**. Each one can run the same action twice, such as sending the same payment, email, or API call a second time. Repointing DNS or load balancers does not solve this, as job workers and connectors do not route through the front-door traffic layer - they poll Zeebe and call external systems directly. Before redirecting traffic, ensure the old region cannot act. Use one or more of the following mechanisms, in order of preference: - **Stop or scale down the old deployment** if it is reachable (for example, scale Zeebe brokers, gateways, workers, and connector deployments to zero). - **Revoke the old region's credentials and identity** so it can no longer authenticate to external systems or downstream APIs. - **Isolate the old region at the network layer** (security groups, firewall rules, or VPC routing) so it cannot reach external systems even if it remains running. Document and rehearse the specific mechanism you will use as part of your DR drill — fencing is operationally specific to your environment and must be tested before a real incident. ## Related documentation - [Multi-region resilience overview](./resilience-tiers.md) - [Dual-Region](./dual-region.md) - [Backup and restore overview](/self-managed/operational-guides/backup-restore/backup-and-restore.md) - [Zeebe backup management API](/self-managed/operational-guides/backup-restore/zeebe-backup-and-restore.md) - [Elasticsearch backup](/self-managed/operational-guides/backup-restore/elasticsearch/backup.md) - [Elasticsearch restore](/self-managed/operational-guides/backup-restore/elasticsearch/restore.md) - [RDBMS backup](/self-managed/operational-guides/backup-restore/rdbms/backup.md) - [RDBMS restore](/self-managed/operational-guides/backup-restore/rdbms/restore.md) --- ## Dual-Region Dual-Region is Camunda's certified, continuous-replication multi-region configuration. A Camunda Orchestration Cluster runs in both a primary and a secondary region at all times, with Zeebe replicating its log stream across both regions using the Raft protocol. Secondary storage (Elasticsearch) is populated independently in each region via the [Camunda Exporter](/self-managed/components/orchestration-cluster/zeebe/exporters/camunda-exporter.md). It includes published RTO/RPO targets, a reference architecture, and a documented failover runbook. :::caution Before you begin Before implementing a dual-region setup, review the [limitations](#limitations) and [infrastructure considerations](#infrastructure-and-deployment-platform-considerations) for this configuration. ::: | Consideration | Value | | :---------------------------------- | :------------------------------------------------------------ | | **Recovery time (RTO)** | ~15 minutes (see [Recovery objectives](#recovery-objectives)) | | **Data loss (RPO)** | 0 | | **Failover mode** | Manual, operator-initiated | | **Standing second region required** | Yes. Full Orchestration Cluster running in both regions | ## Architecture The dual-region setup uses two Kubernetes clusters, each running a complete set of Camunda 8 components. - With v2 APIs (default in 8.9+), both regions serve user traffic simultaneously. - With v1 APIs, **Region 0** is the primary region serving user traffic; **Region 1** is operational but doesn't serve user traffic under normal conditions. :::note The diagram shows both regions as operational. Any grayed-out appearance represents user traffic routing, not system operational status. All components in both regions must be running. ::: | Component | Mode | Both regions running | User traffic | RPO | | --------------------------------------------: | :------------------------------------------------------ | :------------------- | :---------------------------------------- | :-- | | **Orchestration Cluster** | | ✅ Required | | | | Zeebe | Active-active | ✅ Required | Both regions process data | 0 | | Admin | Active-active | ✅ Required | Cluster-level identity | 0 | | Operate | Active-active with v2 API (active-passive with v1) | ✅ Required | Both regions serve users with v2 API | 0 | | Tasklist | Active-active with Tasklist V2 (active-passive with v1) | ✅ Required | Both regions serve users with Tasklist V2 | 0 | | **Elasticsearch** | Active-active | ✅ Required | Data replicated to both | 0 | In a dual-region setup, each Orchestration Cluster component operates as follows: - **Orchestration cluster** runs across both regions using the [Raft protocol](), distributing partition leaders and followers for continuous replication. - **Camunda exporters** push identical data to the Elasticsearch instance in each region. Camunda orchestration cluster dual export mechanism (not Elasticsearch replication) maintains data consistency. The two Elasticsearch clusters don't communicate directly with each other. - **Operate and Tasklist** maintain synchronized data state across both regions via the [Camunda Exporter](/self-managed/components/orchestration-cluster/zeebe/exporters/camunda-exporter.md). See [Active-active and active-passive modes](#active-active-and-active-passive-modes). - **Admin** is embedded in the Orchestration Cluster and provides cluster-level identity management. ### Component requirements | Component | Mode | Requirement | Function | Data loss risk | | --------------------------------------------: | :------------------------------------------------------ | :------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Orchestration Cluster** | | | | | | Zeebe | Active-active | All brokers in both regions must run | Leaders and followers distributed across regionsContinuous replication via Raft protocolBoth regions required for quorum maintenance | Can handle region failure without data loss when properly configured | | Admin | Active-active | Embedded in the Orchestration Cluster | Provides unified, cluster-level identity management and authorization | Can handle region failure without data loss | | Operate | Active-active with v2 API (active-passive with v1) | Embedded in the Orchestration Cluster | Both regions maintain synchronized data stateBoth regions serve users when using v2 API**Region-specific data**: Uncompleted batch operations only when using v1 API | Data loss possible only when using v1 API, as changes are isolated to the initiated region | | Tasklist | Active-active with Tasklist V2 (active-passive with v1) | Embedded in the Orchestration Cluster | Both regions maintain synchronized data stateBoth regions serve users when using Tasklist V2**Region-specific data**: Task assignments only when using v1 API | Data loss possible only when using v1 API, as changes are isolated to the initiated region | | **Elasticsearch** | Active-active | Both clusters must run | Independent clusters in each regionThe Camunda Exporter writes identical data to both continuously and directlyThe Camunda Exporter's dual-write mechanism (not Elasticsearch replication) maintains data consistencyThe clusters don't communicate with each other | If the secondary Elasticsearch cluster is unreachable, the Camunda Exporter position stalls and [flow control](/self-managed/operational-guides/configure-flow-control/configure-flow-control.md) applies backpressure, throttling user commands cluster-wide | ## Management platform and Orchestration Cluster {#management-platform-and-orchestration-cluster} A dual-region deployment stretches the Orchestration Cluster across both regions and runs the management platform in a single region. The management platform groups the design and management components that sit outside the Orchestration Cluster. Whether a component belongs to the Orchestration Cluster or to the management platform determines what happens to it when a region is lost, and how you protect it. | Layer | Components | Stretched across regions | On region loss | Protection | | :------------------ | :-------------------------------------------------------------------------------- | :----------------------- | :------------------------------------------------------ | :----------------------------- | | Runtime | Orchestration Cluster (Zeebe, Operate, Tasklist, Admin) and its secondary storage | Yes | Processing stops until failover completes, then resumes | Dual-region failover procedure | | Management platform | Management Identity, Camunda Hub (Web Modeler and Console), and Optimize | No | Unavailable until you restore it | Backup and restore | ### Deploy the management platform in a single region Deploy the management platform as a separate release in one region, next to the two Orchestration Clusters rather than inside them. That region can be one of the two dual-region regions or a third region. Camunda doesn't stretch these components across regions, and they take no part in the dual-region failover procedure. The dual-region reference architecture doesn't deploy the management platform. It uses Basic authentication, disables Management Identity, and sets `optimize.enabled: false`. That's the scope of the reference configuration, not a product restriction: you can run Optimize and Camunda Hub alongside a dual-region Orchestration Cluster. Both components authenticate through Management Identity, so a management platform requires OpenID Connect (OIDC) authentication rather than the Basic authentication the reference configuration uses. Point each component at your Management Identity instance with `global.identity.service.url`, and give Camunda Hub its own PostgreSQL database. Optimize imports the `zeebe-record` indices that the legacy Elasticsearch exporter writes. The dual-region reference configuration doesn't write them: it sets `orchestration.exporters.zeebe.enabled: false`, and the Helm chart doesn't enable this exporter automatically when the Orchestration Cluster spans two regions. Each broker exports only the partitions it leads, so configure the [Elasticsearch exporter](/self-managed/components/orchestration-cluster/zeebe/exporters/elasticsearch-exporter.md) on every broker in both regions, with the URL of the Elasticsearch cluster that Optimize reads. Add it as an additional exporter through environment variables, the same way the reference configuration adds its regional Camunda Exporters. With the 8.10 Helm chart, deploy the management platform as two single-region releases: a release with `global.topology.mode: hub` for Camunda Hub and Management Identity, and a release with `global.topology.mode: optimize` for Optimize. A `hub` release doesn't deploy Optimize, even with `optimize.enabled: true`. Point the Optimize release at the Management Identity in the Hub release with `global.identity.service.url`. Set `global.topology.mode: orchestration` in each regional Orchestration Cluster release. The Hub release requires `identity.enabled: true`. Each regional release requires `identity.enabled: false` and `global.identity.auth.enabled: true`, so it can't keep the `global.identity.auth.enabled: false` setting of the reference configuration. Point `global.identity.service.url` at the Management Identity in the Hub release. See [Clusters](/self-managed/components/hub/configuration/properties.md#clusters). :::note The Helm chart doesn't reject `optimize.enabled: true` in a release that has no Management Identity. That combination installs successfully and then fails to authenticate at runtime. Confirm Management Identity is reachable before you enable Optimize. ::: ### Region loss behavior for management platform components Management platform components don't replicate across regions, so losing the region they run in makes them unavailable until you restore them. If the management platform runs in one of the two dual-region regions, losing that region also stops the Orchestration Cluster until the dual-region failover procedure completes. Failover restores process execution, and deployed processes keep running. It doesn't restore the management platform, which you recover from backups. Broker processing and authenticated client access recover separately. If the Orchestration Cluster uses OIDC authentication with a provider, such as Keycloak, that runs in the lost region, failover restores broker processing only. Workers, Connectors, and other clients can't get new tokens after their current tokens expire, and users can't sign in to the Orchestration Cluster. Process instances that wait for job workers then stop progressing. Run the OIDC provider where it survives the loss of either dual-region region, or recover it as part of the failover. | Component | State it holds | If its region is lost | | :------------------------------ | :--------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------- | | Management Identity | Users, groups, roles, tenants, and OIDC clients | Authentication to Optimize and Camunda Hub fails until you restore it | | OIDC provider, such as Keycloak | Users, credentials, and client registrations | Clients of the Orchestration Cluster can't get new tokens, and users can't sign in, until you restore it | | Camunda Hub | Diagrams, projects, and collaboration history in PostgreSQL. Console holds no state of its own | Modeling and deployment from Camunda Hub stop until you restore it | | Optimize | Reports, dashboards, collections, alerts, and its own import position | Reporting stops until you restore it, and content created since the last backup is lost | ### Protect the management platform with backup and restore Back up Management Identity and Camunda Hub on their own schedule. If Optimize shares the Elasticsearch instance of the Orchestration Cluster, back it up together with the Orchestration Cluster, using the same backup ID. Replicate all backups to a second region, so they survive the loss of the management platform region. | Component | Backup method | | :------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Management Identity | Back up its PostgreSQL database using your database's native tooling. Also back up your OIDC provider, such as Keycloak, and keep user IDs unchanged when you restore it | | Camunda Hub | Back up its PostgreSQL database. Console needs no backup of its own. See [Web Modeler backup and restore](/self-managed/operational-guides/backup-restore/modeler-backup-and-restore.md) | | Optimize | Use the Optimize backup API with the same backup ID as the Orchestration Cluster backup. See [Optimize backup and restore](/self-managed/operational-guides/backup-restore/optimize-backup-and-restore.md) | The management platform's recovery point and recovery time follow from your backup interval and restore procedure. The dual-region [recovery objectives](#recovery-objectives) don't cover them, because those objectives apply to the Orchestration Cluster only. ## Active-active and active-passive modes {#active-active-and-active-passive-modes} Starting in Camunda 8.9, **active-active** is the default user traffic routing for dual-region deployments: - **Active-active**: Both regions serve user traffic simultaneously. All writes flow through the Camunda Exporter, which maintains data consistency regardless of which region handles the request. This is the default with v2 REST API and Tasklist V2 (8.9+). - **Active-passive**: One region handles all user traffic; the other is operational but doesn't serve user requests. Required for deployments using v1 APIs, because v1 stores some state locally per region (batch operations, task assignments). In both modes, both regions participate in data processing and replication at all times. "Passive" refers only to the user traffic layer. :::info Active-active Starting in Camunda 8.8, the **v2 REST API** removed previous region-specific limitations. In current releases, Tasklist also uses only the Orchestration Cluster REST API, so user task operations are no longer tied to the legacy Tasklist V1 behavior. These improvements make a user-facing **active-active** setup possible. Starting with version 8.9, **active-active** routing is the default for dual-region deployments. ::: ## Traffic routing ### Primary and secondary regions In v2 API deployments (default in 8.9+), both regions serve user traffic simultaneously and there's no primary/secondary distinction at the UI layer. See [Active-active and active-passive modes](#active-active-and-active-passive-modes). In v1 API deployments, one region is designated as primary and the other as secondary: - **Primary region**: Serves user traffic (UI access, API calls). - **Secondary region**: Operational but doesn't serve user traffic under normal conditions. In both cases, both regions are operationally active with all components running and replicating data. ### Managing user traffic With v2 APIs (default in 8.9+), distribute traffic across both regions using DNS, a load balancer, or network routing policies. With v1 APIs, route all user traffic exclusively to the primary region. You're responsible for configuring and maintaining: - DNS routing to the primary region - Load balancer rules and health checks - Traffic redirection to the secondary region during primary region failure, as part of the complete failover procedure :::warning Redirecting traffic without following the full [operational procedure](/self-managed/deployment/helm/operational-tasks/dual-region-ops.md) can cause system inconsistencies and data issues. ::: ## Requirements :::caution Running dual-region setups requires you to develop, test, and execute custom [operational procedures](/self-managed/deployment/helm/operational-tasks/dual-region-ops.md) specific to your environment. ::: ### Installation requirements To install with the Helm chart, you need two Kubernetes clusters. :::note Database support Dual-region configurations don't support OpenSearch or RDBMS (relational database) secondary storage. ::: #### Network requirements - Kubernetes clusters, services, and pods must use distinct, non-overlapping CIDRs to avoid routing issues. - Both regions must be able to communicate with each other (for example, via VPC peering). See the [example AWS EKS implementation](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/dual-region.md). - Kubernetes services in one cluster must be resolvable and reachable from the other cluster and vice-versa: - For AWS EKS, configure DNS chaining. See the [Amazon EKS setup guide](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/dual-region.md). - For OpenShift, use [Submariner](https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/2.11/html/networking/networking#submariner) for multi-cluster networking. See the [OpenShift dual-region setup guide](/self-managed/deployment/helm/cloud-providers/openshift/dual-region.md). - Network round-trip time (**RTT**) between regions directly affects Raft commit latency and throughput. As a guideline, keep RTT at or below **100 ms**. Higher latencies degrade performance, but are not a hard limit enforced by the engine. - Required open ports between regions: - **9200**: Elasticsearch (cross-region data pushed by Zeebe) - **26500**: Zeebe Gateway (client/worker communication) - **26501** and **26502**: Zeebe broker and Zeebe Gateway communication ### Zeebe cluster configuration Zeebe supports the following broker and replication configurations for dual-region setups: - `clusterSize` must be a multiple of **2** and at least **4** to distribute brokers evenly across both regions. - `replicationFactor` must be **4** to ensure even partition distribution across regions. - `partitionCount` is unrestricted but should be based on workload requirements. See [understanding sizing and scalability behavior](../../../components/best-practices/architecture/sizing-your-environment.md#understanding-sizing-and-scalability-behavior) and [partitions](../../../components/zeebe/technical-concepts/partitions.md). Zeebe creates partitions in a [round-robin fashion](/components/zeebe/technical-concepts/partitions.md#partition-distribution). The Helm chart places all brokers with even numbers (0, 2, 4, 6, ...) in one region and all brokers with odd numbers (1, 3, 5, 7, ...) in the other. This distribution ensures even partition replication across both regions. :::info Zone-aware brokers This numbered, parity-based broker distribution was the default multi-region configuration before Camunda 8.10 and remains supported. Starting with 8.10, [zone-aware clusters](/self-managed/components/orchestration-cluster/zeebe/configuration/zone-aware-clusters.md) name brokers after their zone instead of inferring the region from node ID parity, which extends beyond two regions and simplifies managing zones. To move an existing dual-region cluster to zone-aware brokers, see [Migrate to zone-aware brokers](/self-managed/deployment/helm/operational-tasks/zone-aware-migration.md). ::: #### Scaling the Zeebe cluster When scaling, follow the [cluster scaling steps](../../components/orchestration-cluster/zeebe/operations/cluster-scaling.md) and ensure you meet the [Zeebe cluster configuration](#zeebe-cluster-configuration) requirements. Keep both regions balanced. They should always have the same number of brokers. ### Infrastructure and deployment platform considerations Multi-region setups require careful planning. You must manage the following areas independently. Camunda doesn't control or document them: - **Kubernetes cluster management**: Managing multiple Kubernetes clusters and deployments across regions - **Monitoring and alerting**: Dual-region monitoring with cross-region correlation - **Cost implications**: Multiple clusters and cross-region traffic increase costs - **Network reliability**: Increased latency can affect data consistency and synchronization. Even short latency bursts can have an impact. - **Traffic management**: DNS and incoming traffic routing - **Security**: Consistent security policies and network controls across regions :::tip Operational readiness Before implementing dual-region, ensure your organization has: - Experience managing multi-cluster Kubernetes environments - Established procedures for cross-region networking and security - Monitoring and alerting systems with cross-region correlation capability - Defined RTO/RPO requirements and tested recovery procedures ::: ### Upgrade considerations Follow the upgrade recommendations in the [Camunda Helm chart](/self-managed/upgrade/helm/index.md) and the [component-specific upgrade guides](/self-managed/upgrade/components/index.md). Review the [upgrade overview](/self-managed/upgrade/index.md) before starting, and always create a [Camunda-supported backup](/self-managed/operational-guides/backup-restore/backup-and-restore.md) first. For dual-region setups, use a **staged upgrade approach**: upgrade one region at a time. Upgrading both regions simultaneously risks **quorum loss** in Zeebe partitions. Complete the upgrade in one region before starting the other, updating only one Zeebe broker at a time. Certain **minor version upgrades** might require you to upgrade both regions simultaneously to complete migration steps. Always check the release notes and migration instructions for your version before proceeding. ## Limitations | **Aspect** | **Details** | | :-------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Installation methods | Only Kubernetes with the [Camunda Helm chart](/self-managed/deployment/helm/install/quick-install.md) is supported. Alternative installation methods such as docker-compose aren't covered by our guides. | | v1 API user traffic | Deployments using v1 APIs don't support active-active user traffic routing. All user traffic must be routed to a single primary region. See [Active-active and active-passive modes](#active-active-and-active-passive-modes). | | Management Identity | Not deployed by the dual-region reference configuration, which uses Basic authentication. The Orchestration Cluster-level Admin provides multi-tenancy and role-based access control (RBAC) instead. Deploy Management Identity in a single region if you need Optimize or Camunda Hub. See [Management platform and Orchestration Cluster](#management-platform-and-orchestration-cluster). | | Optimize | Runs in a single region alongside a dual-region cluster, with no multi-region guarantees. Requires OIDC authentication and Management Identity. If its region is lost, Optimize is unavailable until you restore it. See [Management platform and Orchestration Cluster](#management-platform-and-orchestration-cluster). | | Connectors deployment | Connectors can be deployed in a dual-region setup, but you must account for [idempotency](../../../components/connectors/use-connectors/inbound.md#creating-the-connector-event) to avoid event duplication. With two connector deployments running, message idempotency is critical. | | Connectors | If you run Connectors with an inbound connector deployed in a dual-region setup: To delete a process deployment, do so via Operate, otherwise the inbound connector won't deregister.If you have multiple Operate instances running, delete the process in both instances. This is a [known limitation](https://github.com/camunda/camunda/issues/17762). | | Zeebe cluster scaling | Supported. See [Zeebe cluster configuration](#zeebe-cluster-configuration). | | Camunda Hub | Runs in a single region alongside a dual-region cluster, with no multi-region guarantees. Requires OIDC authentication, Management Identity, and PostgreSQL. If its region is lost, Camunda Hub is unavailable until you restore it. See [Management platform and Orchestration Cluster](#management-platform-and-orchestration-cluster). | ## Region failure and recovery In a dual-region setup, losing either region affects Camunda 8 processing because of Zeebe's quorum requirements. When a region becomes unavailable, the Zeebe cluster loses quorum (half of its brokers become unreachable) and **immediately stops processing** new data. All components stop processing until the failover procedure completes. This section covers the Orchestration Cluster. Failover doesn't recover Management Identity, Camunda Hub, or Optimize. For their behavior on region loss, see [Management platform and Orchestration Cluster](#management-platform-and-orchestration-cluster). ### Physical Tenant topology during failover A Physical Tenant removed from configuration is disabled, but it remains in the persisted cluster topology until it is logically removed. Multi-region failover operations require every tenant in the topology to be accounted for, so a disabled tenant can block failover. Before starting failover, compare configured tenants with the persisted topology and resolve any disabled tenants. See [logically remove a disabled tenant](/self-managed/concepts/physical-tenants/provisioning-and-lifecycle.md#logically-remove-a-disabled-tenant) for lifecycle details. :::warning Immediate impact Region failure causes **immediate service interruption**: - No new process instances can start. - Running process instances are suspended. - User interfaces become unavailable if the primary region is lost. ::: See the [operational procedure](/self-managed/deployment/helm/operational-tasks/dual-region-ops.md) for recovery and re-establishment steps. :::caution Monitor for region failures and execute the [operational procedures](/self-managed/deployment/helm/operational-tasks/dual-region-ops.md) promptly to ensure smooth recovery. ::: ### Primary region failure If the primary region fails: - **Service disruption**: User traffic is unavailable. - **Zeebe halt**: Processing stops due to quorum loss. - **Data loss**: With v1 APIs, region-specific data (batch operations, task assignments) is lost. With v2 REST API and Tasklist V2, the Camunda Exporter replicates all data to both regions, so data remains available after region failure. #### Recovery steps for primary region failure 1. **Temporary recovery**: Follow the [operational procedure](/self-managed/deployment/helm/operational-tasks/dual-region-ops.md#failover-phase) to restore functionality and unblock the process automation engine (Zeebe). 2. **Traffic rerouting**: With v2 APIs (default in 8.9+), remove the failed region from serving traffic (for example, via DNS or load balancer health checks). With v1 APIs, redirect user traffic to the secondary region (now primary). 3. **Data and task management** (v1 API setups only): - Reassign uncompleted tasks lost from the previous primary region. - Recreate batch operations in Operate. 4. **Permanent region setup**: Follow the [operational procedure](/self-managed/deployment/helm/operational-tasks/dual-region-ops.md#failback-phase) to create a new secondary region. ### Secondary region failure If the secondary region fails: - **Zeebe halt**: Processing stops due to quorum loss. - **UI availability**: Operate and Tasklist remain accessible from the primary region. - **Process execution**: No new process instances can start. Zeebe suspends running instances until quorum is restored. #### Recovery steps for secondary region failure 1. **Temporary recovery**: Follow the [operational procedure](/self-managed/deployment/helm/operational-tasks/dual-region-ops.md#failover-phase) to restore processing. 2. **Permanent region setup**: Follow the [operational procedure](/self-managed/deployment/helm/operational-tasks/dual-region-ops.md#failback-phase) to create a new secondary region. :::note Unlike primary region failure, no user-facing data is lost and no traffic rerouting is necessary. ::: ## Recovery objectives (RPO and RTO) {#recovery-objectives} Based on the requirements and limitations outlined in this page, you can use the **Recovery Point Objective (RPO)** and **Recovery Time Objective (RTO)** values below to inform your risk assessment. The **RPO** is the maximum tolerable data loss measured in time. The **Recovery Time Objective (RTO)** is the time required to restore services to a functional state. For Operate, Tasklist, and Zeebe, the **RPO** is **0**. The **RTO** applies to both the failover and failback procedures: - **Failover** RTO: **< 1 minute** to restore a functional state, excluding DNS reconfiguration and network considerations. - **Failback** RTO: **Five minutes plus** the time required to back up and restore Elasticsearch. This depends on your setup and chosen [Elasticsearch backup type](https://www.elastic.co/guide/en/elasticsearch/reference/current/snapshots-register-repository.html#ess-repo-types). In internal tests, reinstalling and reconfiguring Camunda 8 takes approximately five minutes. Treat this as a general guideline. Actual times vary depending on available resources and your familiarity with the procedure. ## Related resources - [Multi-region resilience overview](./resilience-tiers.md) - [Cold Recovery](./cold-recovery.md) - [Amazon EKS dual-region setup guide](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/dual-region.md): an example blueprint using managed EKS and VPC peering with Terraform. The concepts are mainly cloud-agnostic and can be adopted by other cloud providers. - [OpenShift dual-region setup guide](/self-managed/deployment/helm/cloud-providers/openshift/dual-region.md) - [Dual-region operational procedure](/self-managed/deployment/helm/operational-tasks/dual-region-ops.md): covers how to respond to a total region loss and how to prepare your environment for smooth recovery. - [Camunda backup and restore overview](/self-managed/operational-guides/backup-restore/backup-and-restore.md) --- ## Grow a Multi-Region RDBMS cluster Learn how to grow a [Multi-Region RDBMS](./multi-region-rdbms.md) cluster from two regions to three or more. You can add a zone to a running cluster without changing the brokers that already run. Each broker ID combines a zone name and an index, such as `london_0`. A new zone brings new IDs and leaves the existing ones as they are. ## Declare only the zones you deploy Every zone in the zone list must have its brokers running when the cluster bootstraps. Don't declare a zone to reserve it for later growth. Add it through the management API after its brokers run. A zone in the zone list receives partition replicas even if its brokers don't run. A declared zone without brokers leaves every partition one zone short. For example, if you declare a `2-2-1` layout but deploy only the first two zones, each partition runs four of five replicas. Losing either database zone then stops processing. ## Add a zone to the running cluster 1. Start the brokers of the new zone. 1. Add the zone with the [Add or re-add a zone](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#add-or-re-add-a-zone) request. The request body needs `numberOfReplicas`, `priority`, and either `numberOfBrokers` or `brokers`. 1. Wait for the change to report `COMPLETED`. Follow it with the [Monitoring API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#monitoring-api). The engine places the zone's replicas and raises the replication factor in one change. No broker is renumbered, and the regions already running aren't restarted. | Zones running | Layout | Replication factor | After losing one zone | | :------------ | :------ | :----------------- | :---------------------------------------------------- | | Two | `2-2` | 4 | Two of four replicas: processing stops | | Three | `2-2-1` | 5 | Three of five replicas at worst: processing continues | ## Plan the partition count Adding a zone doesn't change the partition count. To raise it, use [partition scaling](/self-managed/components/orchestration-cluster/zeebe/operations/cluster-scaling.md#2c-scaling-only-partitions), before or after you add the zone but not during the same change. The reference implementation sizes the partition count on the provisioned region slots, so it already fits the largest topology it can grow into. --- ## Region loss and recovery in Multi-Region RDBMS Learn how a [Multi-Region RDBMS](./multi-region-rdbms.md) cluster behaves when it loses a region, and how it recovers. A region loss is an unplanned change, unlike [growing the cluster](./multi-region-rdbms-growth.md). With a `2-2-1` replica layout across three regions, each partition has five replicas. Losing one region leaves three or four replicas per partition, preserving quorum as long as every declared zone runs and no zone holds half the replicas or more. Partitions whose leader was in the lost region briefly pause while a new leader is elected; other partitions continue processing. No operator action is needed to restore Zeebe quorum. :::warning Two things need attention If the writer was in the lost region, promote a surviving member. A planned switchover loses no data. An unplanned promotion loses whatever had not replicated at the time of the outage, bounded by the replication lag your [asynchronous replication monitoring](/self-managed/concepts/databases/relational-db/configuration.md#multi-region-support) strategy allows. Camunda itself needs no reconfiguration as long as the JDBC URL keeps resolving to the current writer. Camunda clients take one REST address and one gRPC address, not a list of endpoints. Point them at one address that fails over. For example, use a DNS record with health checks and failover routing, or a global load balancer in front of the regional load balancers. Most providers offer both, for example Amazon Route 53 and AWS Global Accelerator, Azure Traffic Manager and Azure Front Door, or Google Cloud DNS routing policies and Cloud Load Balancing. ::: To recover, redeploy the region. There is nothing to restore: its brokers catch up from the surviving replicas, as they would after a node restart. For the step-by-step procedure, see [Multi-Region RDBMS operational procedure](/self-managed/deployment/helm/operational-tasks/multi-region-rdbms-ops.md). ## Recovery objectives {#recovery-objectives} With three or more running regions, and no zone holding half the replicas or more, a region loss needs no recovery procedure, but it still opens a recovery window. For other layouts, see [Remove a lost zone](#remove-a-lost-zone). Under those conditions, recovery needs no procedure, because on the engine a zone loss is the same class of event as a broker loss. A single-region cluster that loses a broker holds a Raft re-election for the partitions that broker led, and its clients reconnect to the new leaders. Losing a zone runs the same sequence over the same protocol: - No restore, and no backup to replay. - No judgment call about whether the failure is temporary or permanent. That is the difference from [Dual-Region](./dual-region.md), where the same event costs the quorum and processing stops until an operator intervenes. Recovery has a window, because reconfiguration takes time. Most of that window depends on settings outside the engine: client timeouts and retries, traffic routing, database failover, and Camunda's own SQL connection timeouts. This section therefore gives the order of magnitude of each part instead of one RTO figure. Data loss depends on which store you mean. Primary storage contains Zeebe's replicated log and runtime state. With the quorum-preserving layout described above, losing one region causes no loss of committed primary-storage data. Retention of log records for secondary-storage recovery depends on the configured replication-monitoring strategy, as described below. Raft commits a record only once a majority of its replicas hold it. With a `2-2-1` replica layout across three regions, a commit needs three replicas of five. Losing one zone removes at most two, so at least one surviving replica has the record. Secondary storage is different, because the database replicates asynchronously. In the table, `min-sync-replicas` stands for `camunda.data.secondary-storage.rdbms.async-replication.min-sync-replicas`, the number of standbys that must confirm a record. An unplanned promotion can omit records that had not reached the promoted standby. | Strategy | Secondary-storage RPO | Condition | | :--------- | :-------------------- | :------------------------------------------------------------------------------------------------------------------------------- | | `LOG_SEQ` | 0 | The promoted standby is one of the standbys counted by `min-sync-replicas`. This is always the case with a single standby. | | `TIME_LAG` | 0 | Same as `LOG_SEQ`: the promoted standby is one of the standbys counted by `min-sync-replicas`. | | `DELAY` | 0 | The actual replication lag stays below the configured delay. The exporter observes no replication state, so you monitor the lag. | The database's own failover can still lose up to its replication lag. Every strategy holds back Zeebe log compaction, so Camunda replays the missing records from the retained log after promotion. The table describes that combined result. That makes the guarantee conditional on replication configuration and disk capacity rather than on the architecture alone. Retained log segments accumulate for as long as records remain unacknowledged. Size the volume for your write rate and the longest replication outage you plan to tolerate, and alert on broker disk usage. `pause-on-max-lag-exceeded` decides what happens once the lag passes the configured threshold. It is off by default, and it caps neither data loss nor retained log growth. With `LOG_SEQ` and `TIME_LAG`, acknowledgement waits for confirmed replication either way, so the unacknowledged position holds the log either way. With `DELAY`, acknowledgement only waits for the configured delay and confirms no replication state. With it off, exporting continues against a database that is already behind. With it on, exporting stops, so secondary storage receives nothing new and the APIs reading it fall behind the engine until replication recovers. Enable it deliberately, once you have alerting on replication lag. The window has three parts, and only the first happens inside the engine: | What | Typical duration | Why it takes time | | :------------------------ | :------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Raft re-election | A few seconds | Partitions whose leader was in the lost zone have no leader until a follower misses the leader's heartbeats. A follower starts an election after the [`election-timeout`](/self-managed/components/orchestration-cluster/zeebe/configuration/broker.md#camundaclusterraft), 2.5 seconds by default, which is ten 250 ms heartbeat intervals. With priority election, the preferred replica stands first, and the vote itself is short compared to the timeout. These partitions don't process during that window. | | Client traffic rerouting | Tens of seconds to minutes | The gateway in the lost region is unreachable. Clients pointed at it fail until your traffic management reroutes them. For example, an Amazon Route 53 health check probes every 10 or 30 seconds and marks the endpoint unhealthy after several failed probes. Clients then follow the new record once its TTL expires. | | Database writer promotion | Under a minute to a few minutes | Only if the writer was in the lost region. Exporting stops until a surviving member is promoted, while the engine keeps processing. This may [affect processing throughput during secondary-storage recovery](#processing-during-secondary-storage-recovery). For Aurora Global Database, a switchover typically takes under 30 seconds on the engine versions listed in the [AWS disaster recovery guide](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-global-database-disaster-recovery.html). A failover after an unplanned outage typically completes within a few minutes. The APIs and web applications that read secondary storage serve stale data until exporting resumes. | These durations come from defaults and vendor documentation, not from a measurement of this architecture. Measure your own window, because your timeouts and your database decide it. Skewing partition leadership to the writer's zone makes the first of these worse in one specific case. Losing that zone loses most partition leaders at once, so more partitions re-elect simultaneously. That is the price of avoiding an inter-region round trip on every export flush. Client configuration decides whether the re-election and rerouting windows are visible. A re-election is a window a client retries through, not an outage. That only holds if its timeout and retry budget survives one. A client that gives up on the first refused connection sees the re-election as downtime, in a single-region cluster as much as here. The window is longer here, because the new leader and the rerouted client can both be a region away. Size client timeouts and retries for a leader change that crosses a region boundary. ## Processing during secondary-storage recovery {#processing-during-secondary-storage-recovery} Pausing the exporter stops secondary-storage updates but doesn't directly throttle processing. If [write flow control with dynamic throttling](/self-managed/operational-guides/configure-flow-control/configure-flow-control.md#enable-flow-control) is enabled, it adjusts the write rate based on the exporting rate and backlog. During secondary-storage recovery, a growing backlog can reduce processing throughput and increase client backpressure. Write flow control is disabled by default in Self-Managed. For log-retention behavior, see [LSN replication monitoring](/self-managed/concepts/databases/relational-db/configuration.md#lsn-replication-monitoring), [time-based replication monitoring](/self-managed/concepts/databases/relational-db/configuration.md#time-based-replication-monitoring), and [delay backoff replication monitoring](/self-managed/concepts/databases/relational-db/configuration.md#delay-backoff-replication-monitoring). The operational procedure covers [capacity planning](/self-managed/deployment/helm/operational-tasks/multi-region-rdbms-ops.md#prerequisites) and [monitoring during recovery](/self-managed/deployment/helm/operational-tasks/multi-region-rdbms-ops.md#monitor-secondary-storage-recovery). ## Remove a lost zone Whether the lost zone has to be removed depends on the replicas it held, not on how many zones there are. A partition keeps its quorum as long as the lost zone held fewer than half its replicas: - Under a layout that satisfies this, such as a `2-2-1` replica layout across three zones, the majority holds. Processing continues whether or not you remove the zone. - When one zone holds half the replicas or more, losing that zone costs the quorum. Processing only resumes once the zone is removed from the partition distribution. An evenly split two-zone cluster, such as the `2-2` bootstrap, always loses its quorum with a zone. Bring the lost zone back before you add the third region. The same limit applies to [Dual-Region](./dual-region.md). An uneven two-zone layout keeps its quorum only when it loses the smaller zone. In a cluster with three or more zones, remove a lost zone once you confirm that it is down and won't come back soon. The [operational procedure](/self-managed/deployment/helm/operational-tasks/multi-region-rdbms-ops.md#4-remove-the-lost-zone) has the command. After the removal, raise the replicas of the remaining zones if the layout needs it. Losing a two-replica zone of a `2-2-1` layout leaves `2-1`. Raise the one-replica zone to two, to get `2-2`, through the [Partitioning API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#partitioning-api). Losing the one-replica zone leaves `2-2`, so the remaining zones need no change. Removal makes failback slower. You must add the zone back explicitly, and its brokers then rebuild their state. The rebuild is automatic and can take a few minutes. Its duration depends on the number of active process instances, not on the data in secondary storage. --- ## Multi-Region RDBMS Multi-Region RDBMS spreads a single Orchestration Cluster across two or more regions. It uses a relational database as its secondary storage, and leaves replication to that database. You need three or more regions to keep processing through a region loss. Every partition then keeps a majority when one region disappears, as long as no region holds half the replicas of a partition or more. The engine keeps processing instead of stopping for an operator. :::caution Before you begin Running a multi-region setup requires you to develop, test, and execute [operational procedures](/self-managed/deployment/helm/operational-tasks/multi-region-rdbms-ops.md) specific to your environment. Review the [limitations](#limitations) and [requirements](#requirements) before you commit to this configuration. To have your multi-region setup covered by Camunda enterprise support, get your configuration and runbooks reviewed by Camunda before going to production. Contact your Customer Success Manager as soon as you plan your multi-region setup. That review covers the architecture you build. It does not make the [reference implementation](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/multi-region-rdbms.md) a supported product. That repository is explicitly experimental. It is meant for learning, evaluation, and design review, and is not production-ready as published. ::: ## How Multi-Region RDBMS differs from Dual-Region The two multi-region architectures differ in region count and in secondary storage. [Dual-Region](./dual-region.md) is the two-region architecture with Elasticsearch secondary storage and parity-numbered brokers. It has two properties that come from the region count rather than from any implementation choice. With two regions, no replica placement survives losing half of them. A region loss therefore costs the Raft quorum, and Zeebe stops processing until an operator force-removes the lost brokers. Each region also owns its own copy of the secondary storage, so a returning region has to be re-seeded. Failback therefore includes a secondary storage snapshot and a cross-region restore. Multi-Region RDBMS removes both. It changes the number of regions, and it changes who owns replication of the secondary storage. | Consideration | Dual-Region | Multi-Region RDBMS | | :---------------- | :-------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------- | | Regions | Exactly two | Two or more. Three or more to keep processing through a region loss | | Region loss | Quorum lost, processing stops until brokers are force-removed | With three or more regions, quorum preserved and processing continues | | Failback | Multi-step runbook including a secondary storage snapshot and restore | Redeploy the region, nothing to restore | | Secondary storage | Elasticsearch, one cluster per region, one Camunda exporter per region | RDBMS, one database, one exporter, replication inside the database | | Optimize | Supported | Not available, Optimize requires Elasticsearch or OpenSearch | | Relative cost | **$$$**: two regions of Orchestration Cluster capacity, plus cross-region traffic | **$$$$**: two or more regions of Orchestration Cluster capacity, three or more to survive a region loss, plus cross-region traffic | Choose Multi-Region RDBMS when processing must continue through a region loss without operator intervention, and when you can run without Optimize. Choose [Dual-Region](./dual-region.md) when two regions are sufficient, or when you need Optimize on the same cluster. ## Architecture Three infrastructure layers let one cluster span several regions. One Orchestration Cluster spans every region. Each region runs its own brokers and connectors, and all of them are members of the same Zeebe cluster. Three infrastructure layers make that possible: | Layer | Responsibility | | :------------------------------ | :-------------------------------------------------------------------------------------- | | Inter-region network | Carries broker-to-broker traffic, including Raft, between regions on private addresses. | | Cross-cluster service discovery | Publishes each region's Zeebe service under a name every other region can resolve. | | Relational secondary storage | Accepts writes from every region through a single endpoint, and replicates them itself. | The Camunda layer sees one cluster and one database. Everything region-specific lives in the infrastructure layers, which keeps the architecture portable across deployment platforms. ### Partition placement across zones Multi-Region RDBMS relies on [zone-aware clusters](/self-managed/components/orchestration-cluster/zeebe/configuration/zone-aware-clusters.md). Each region is one zone, and every zone declares how many brokers it holds and how many replicas of each partition live in it. A partition survives while a majority of its **replicas** answer. Zeebe counts replicas, not zones. A zone holds as many replicas as you give it. The replication factor is the sum across the zones rather than the number of zones. The simplest case is one replica per zone, which is what the following illustration uses. The replication factor then equals the zone count, and a partition keeps its majority while `N - 1 > N / 2`. | Zones | Replicas per zone | Replication factor | Zone losses tolerated | | :---- | :---------------- | :----------------- | :-------------------- | | 2 | 1 | 2 | 0 | | 3 | 1 | 3 | 1 | | 4 | 1 | 4 | 1 | | 5 | 1 | 5 | 2 | Three zones is the smallest topology in which losing one does not stop the engine. A fourth zone at one replica each does not change that. The replication factor becomes four, and a majority is still three. A second loss leaves two. Tolerating two losses takes five replicas. #### Choosing an asymmetric layout Zones do not have to be equal, and making them equal is rarely what you want. A common shape places more replicas in the regions that also host a database member. It places one replica in a region that exists to break ties: | Zone | Database member | Replicas | Losing this zone leaves | | :--- | :-------------- | :------- | :---------------------- | | A | yes | 2 | 3 of 5, majority holds | | B | yes | 2 | 3 of 5, majority holds | | C | no | 1 | 4 of 5, majority holds | That is `replicationFactor: 5` across three zones. The third region carries a vote without carrying a database, and it is the zone that decides a quorum when the other two disagree. It is still a full region: the brokers there hold data and process work like any others, and the region serves clients. Only the database member is absent. This is not a lightweight arbiter or a "2.5 region" topology. The only rule is that **no single zone may hold half the replicas or more**, or losing that zone stops the engine. A `4-1-1` layout across three zones fails it: losing the first leaves two replicas of six. Zone awareness also assigns a Raft election priority per zone. Give the zone that hosts the database writer the highest priority. Elections then favor leaders next to the writer, which reduces inter-region round trips on export flushes. The priority biases elections but does not pin leaders. Move existing leaders with the coordinated rebalancing API (`POST /cluster/v2/rebalance`), described in [rebalancing](/self-managed/components/orchestration-cluster/zeebe/operations/rebalancing.md). ### Replication-agnostic secondary storage Camunda uses one JDBC URL per Orchestration Cluster, through a connection pool on each broker, and the RDBMS exporter has no multi-region mode. As the [RDBMS multi-region support](/self-managed/concepts/databases/relational-db/configuration.md#multi-region-support) documentation states, multi-region replication must be handled within the database itself. Multi-Region RDBMS adopts that constraint rather than working around it: - Every broker in every region writes to the **same JDBC URL**. - There are **no per-region exporters** to enable, disable, or reinitialize. - Region loss desynchronizes nothing at the Camunda layer, so failback has no restore step. - Swapping the database changes one value. Any database that presents a single endpoint following its own writer fits. See [multi-region support](/self-managed/concepts/databases/relational-db/configuration.md#multi-region-support) for the supported databases. For example: - A globally replicated managed database. - A PostgreSQL cluster behind a floating endpoint. - A connection proxy. - A DNS record you repoint during failover. Whichever mechanism you choose, test it with the [failover procedure](/self-managed/deployment/helm/operational-tasks/multi-region-rdbms-ops.md) before you go to production. The Camunda configuration does not change between them. ### Monitor asynchronous replication Asynchronous replication monitoring is required, not a tuning option. Without it the RDBMS exporter acknowledges records the standby has not received yet, and a writer failover loses exported data. This architecture treats a writer failover as a routine operation rather than an incident, so set `camunda.data.secondary-storage.rdbms.async-replication.enabled` to `true`. Camunda turns this monitoring off by default. The default suits a single database without asynchronous replicas, and the monitoring needs extra database privileges, for example the `PG_MONITOR` role on PostgreSQL. This architecture needs it, so the reference implementation sets it to `true`. Once you turn it on, the strategy defaults to `LOG_SEQ`. The strategy you can use depends on the database engine, not on the cloud provider. Use `LOG_SEQ` when your database is in its [vendor support list](/self-managed/concepts/databases/relational-db/configuration.md#lsn-replication-monitoring). Otherwise, choose `TIME_LAG` or `DELAY`. The reference implementation covers only Aurora Global Database. Managed databases on other providers, such as Azure or Google Cloud, follow the same rules but have no reference implementation. {/* TODO: replace this paragraph with a link to a per-database table of the preferred multi-region replication settings once that reference exists. */} | Strategy | When to use it | What you configure | | :------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------ | | `LOG_SEQ` (LSN monitoring) | Default and preferred. Reads the database's own replication position. See the [supported databases](/self-managed/concepts/databases/relational-db/configuration.md#lsn-replication-monitoring). | `async-replication.type: LOG_SEQ` | | `TIME_LAG` | Less precise than `LOG_SEQ`. Reads the replication lag the primary reports, and acknowledges less often. Not supported on Oracle. See the [supported databases](/self-managed/concepts/databases/relational-db/configuration.md#time-based-replication-monitoring). | `async-replication.type: TIME_LAG` | | `DELAY` | Fallback for a database that supports neither `LOG_SEQ` nor `TIME_LAG`, for example Azure SQL Database. Carries no replication signal. | `async-replication.type: DELAY`, a `delay` value, and your own monitoring of the actual lag | Camunda doesn't switch strategies for you. See [multi-region support](/self-managed/concepts/databases/relational-db/configuration.md#multi-region-support) for the supported backends and the settings. ### The database tier is active-standby The Zeebe data plane is active-active: every region processes. The database tier is not. A single writer serves every region, and brokers that are not co-located with it pay the inter-region round trip on every export flush. Two consequences follow, and both are sizing decisions rather than configuration: - Keep regions inside the round-trip time budget described in [network requirements](#network-requirements). - Size the exporter queue for the latency of the furthest region, not the nearest. Skewing partition leadership to the writer's zone through zone priority reduces how often that round trip is paid, but it does not remove it. ## Requirements The architecture only works under the cluster, network, platform, and upgrade requirements below. ### Zeebe cluster configuration | Setting | Requirement | | :---------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------- | | Partitioning scheme | `ZONE_AWARE`. The parity-based broker numbering only supports exactly two regions. | | Zones | One zone per region, two or more. Three or more to keep processing through a region loss. | | `number-of-replicas` per zone | Declared per zone, and free to differ between them. To survive a zone loss, no zone may hold half the replication factor or more. | | `number-of-brokers` per zone | Declared per zone. Keep zones balanced so a zone loss removes an equal share of capacity. | | Surviving capacity | Size the cluster so the regions left after a loss carry the full workload. Quorum surviving is not the same as the cluster keeping up. | | `priority` per zone | Highest for the zone hosting the database writer, to keep partition leaders next to it. | | `partitionCount` | Unrestricted. Size it from your workload. See [sizing your environment](/components/best-practices/architecture/sizing-your-environment.md). | Each broker sets its own zone, while the zone list is identical in every region. For the full property reference, see [zone-aware clusters](/self-managed/components/orchestration-cluster/zeebe/configuration/zone-aware-clusters.md). For the matching Helm keys, see [multi-region zone awareness](/self-managed/deployment/helm/configure/multi-region-zone-awareness.md). ### Network requirements - Kubernetes clusters, services, and pods must use distinct, non-overlapping CIDRs across every region. - Every region must reach every other region. Zeebe uses a full mesh, not a hub and spoke, so partial connectivity leaves partitions unable to form a quorum. - Every other cluster must resolve and reach the Kubernetes services in one cluster. The name must be the same from each region's point of view. - Round-trip time between regions directly affects Raft commit latency and throughput. As a guideline, keep it at or below **100 ms**. Higher latencies degrade performance, but are not a hard limit enforced by the engine. - Required open ports between regions: - **26500**: Zeebe gateway, client and worker communication - **26501** and **26502**: Zeebe broker and gateway communication, including Raft - **8080**: Orchestration Cluster REST API - **53**: DNS, for cross-cluster service resolution These are the default ports. Change them if you customize them in your configuration. A private inter-region network is preferred for the database, but it is not required. A public path also works, and it adds egress cost and exposure. You must measure the latency between the regions, and the inter-region connectivity must stay stable. ### Infrastructure and deployment platform considerations Multi-region setups require careful planning. You must manage the following areas independently, and Camunda does not control or document them: - **Kubernetes cluster management**: managing three or more Kubernetes clusters and their deployments - **Monitoring and alerting**: multi-region monitoring with cross-region correlation - **Cost implications**: three or more clusters and cross-region traffic increase costs, and inter-region data transfer is billed per gigabyte - **Network reliability**: increased latency affects Raft commit latency and export throughput. Even short latency bursts have an impact. - **Traffic management**: DNS and incoming traffic routing across more than two regions - **Database operations**: replication, failover, and backup of the secondary storage are the database's responsibility, and therefore yours - **Security**: consistent security policies and network controls across every region ### Upgrade considerations {/* TODO: multi-region upgrade paths are not tested yet. Document them once they are. */} Upgrade **one region at a time**, so the other regions keep the quorum. The [operational procedure](/self-managed/deployment/helm/operational-tasks/multi-region-rdbms-ops.md#upgrade-the-cluster) lists the steps. ## Growth and region loss Two companion pages describe how the cluster changes over its life: - [Grow a Multi-Region RDBMS cluster](./multi-region-rdbms-growth.md): add a region to a running cluster through the cluster management API. - [Region loss and recovery in Multi-Region RDBMS](./multi-region-rdbms-region-loss.md): what the cluster does when a region disappears, the recovery window, and when to remove a lost zone. ## Limitations Recovery behaves as described only inside the boundaries this architecture sets. The following table lists them. | Aspect | Details | | :-------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Installation methods | Kubernetes with the [Camunda Helm chart](/self-managed/deployment/helm/install/quick-install.md). Alternative installation methods are not covered by these guides. | | Secondary storage | RDBMS only. Elasticsearch and OpenSearch replicate per region and do not fit the single-endpoint model this architecture depends on. | | Database availability | The database tier is active-standby. A single writer serves every region, and regions further from it pay more export latency. | | Management Identity support | Not deployed by the reference configuration, which uses Basic authentication. The Orchestration Cluster-level Admin supports multi-tenancy and role-based access control instead. To use Camunda Hub, deploy Management Identity in a single region as part of a [management platform](./dual-region.md#management-platform-and-orchestration-cluster). | | Optimize support | Not available. Optimize requires Elasticsearch or OpenSearch, regardless of the region count. | | Camunda Hub | Not deployed by the reference configuration. Camunda Hub runs in a single region, with no multi-region guarantees, and requires OIDC authentication, Management Identity, and PostgreSQL. See [Management platform and Orchestration Cluster](./dual-region.md#management-platform-and-orchestration-cluster). | | Connectors deployment | Connectors run in every region and are not deduplicated. Account for [idempotency](/components/connectors/use-connectors/inbound.md#creating-the-connector-event) to avoid event duplication. | | Zone list changes | Adding a zone is online, through the cluster management API: the engine places the new zone's replicas without renumbering brokers. Adding a zone leaves the partition count unchanged. | | Backup and restore | RDBMS backup relies on continuous primary storage backups plus a database-native backup. See [backup and restore](/self-managed/operational-guides/backup-restore/backup-and-restore.md). | ## Reference implementation Camunda publishes one implementation of this architecture, on Amazon Web Services: - [Multi-region setup with RDBMS on Amazon EKS](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/multi-region-rdbms.md) deploys three EKS clusters connected by AWS Transit Gateway. It uses Submariner for cross-cluster service discovery and Aurora Global Database as secondary storage. - [Multi-Region RDBMS operational procedure](/self-managed/deployment/helm/operational-tasks/multi-region-rdbms-ops.md) covers region loss, failback, and adding a region. The architecture is not AWS-specific. Each of its three layers has an equivalent on other platforms. For example, Red Hat OpenShift provides Submariner through Advanced Cluster Management, as the [OpenShift dual-region setup](/self-managed/deployment/helm/cloud-providers/openshift/dual-region.md) already uses. ## Related resources These pages cover the concepts and settings this page refers to. - [Multi-region resilience](./resilience-tiers.md): compare all multi-region strategies. - [Dual-Region](./dual-region.md): the two-region configuration with Elasticsearch secondary storage. - [Zone-aware clusters](/self-managed/components/orchestration-cluster/zeebe/configuration/zone-aware-clusters.md): how zones, replicas, and priorities are configured. - [Relational database configuration](/self-managed/concepts/databases/relational-db/configuration.md): RDBMS secondary storage settings, including asynchronous replication monitoring. - [Zeebe clustering](/components/zeebe/technical-concepts/clustering.md): how Raft replication and quorum work. --- ## Multi-region resilience ## About Camunda provides a structured multi-region resilience framework for Self-Managed Orchestration Cluster deployments. - **[Cold Recovery](./cold-recovery.md)**: Camunda's lowest-cost multi-region configuration uses scheduled cross-region backups and a manual restore procedure to recover from complete primary-region loss. Recovery measured in hours is operationally acceptable. - **[Dual-Region](./dual-region.md)**: Dual-region deployment with continuous replication. A full Camunda Orchestration Cluster runs continuously in both a primary and secondary region. - **[Multi-Region RDBMS](./multi-region-rdbms.md)**: One Orchestration Cluster runs active-active across two or more regions, and survives a region loss with three or more. A relational database (RDBMS) with cross-region replication holds the secondary storage. Losing one region preserves the cluster quorum. You must fail over the database writer if the lost region held the writer. :::tip Looking for single-region high availability? To protect against availability-zone outages within one region, see [high-availability architecture](/self-managed/reference-architecture/kubernetes.md#high-availability-ha) and [topology spread constraints](/self-managed/deployment/helm/install/production/index.md#topology-spread-constraints). Multi-AZ deployment doesn't protect against a complete region outage. ::: ## Get started: choose your strategy Choosing the right recovery strategy is determined by how critical your process automation is to your business. How much downtime and data loss can you tolerate, and what compliance obligations do you have? First, determine how critical your workload is: | If your business can accept the following outcome: | Choose this option | | :--------------------------------------------------------------------------------- | :-------------------------------------------- | | Recovery measured in **hours**, and **minutes to hours of data loss**. | [Cold Recovery](./cold-recovery.md) | | Recovery in **~15 minutes**, with **no data loss**, and audit-ready posture. | [Dual-Region](./dual-region.md) | | Processing continues after a single region loss, and you can run without Optimize. | [Multi-Region RDBMS](./multi-region-rdbms.md) | What each strategy asks of you: - **Cold Recovery** is a manual procedure built on the [backup and restore](/self-managed/operational-guides/backup-restore/backup-and-restore.md) guide. There is no reference architecture. Validate the procedure in your own environment. - **Dual-Region** includes a reference architecture and an operational runbook, with documented [Recovery Time Objective (RTO)](/reference/glossary.md#recovery-time-objective-rto) and [Recovery Point Objective (RPO)](/reference/glossary.md#recovery-point-objective-rpo) targets. A region loss stops processing until an operator runs the failover. - **Multi-Region RDBMS**, with three or more regions, keeps processing through a region loss with no Zeebe operator step. The database handles secondary-storage replication. In exchange, it costs a third region of capacity. Optimize is unavailable, because Optimize requires Elasticsearch or OpenSearch instead of a relational secondary storage. ## Comparison of multi-region resilience The following table provides a detailed comparison of the available multi-region deployment options: | Consideration | Cold Recovery | Dual-Region (Elasticsearch) | Multi-Region RDBMS | | :---------------------- | :-------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | | **Regions** | One, plus cross-region backups | Exactly two | Two or more. Three or more for [region-loss continuity](#region-loss-continuity). | | **Recovery time (RTO)** | ~1–4 hours | ~15 minutes | Zeebe: seconds. Client and database recovery can take minutes. See [recovery time](#recovery-time). | | **Data loss (RPO)** | 15 minutes–4 hours, backup-dependent | RPO 0 | Primary storage: RPO 0. Secondary storage: RPO 0 after replay. See [data loss and replay](#data-loss-and-replay). | | **Failover** | Manual restore | Manual failover | Automatic Zeebe recovery. Promote the database writer if it was lost | | **Secondary storage** | Elasticsearch or OpenSearch, restored from backup | Elasticsearch, one cluster per region | One RDBMS, replicated by the database | | **Architecture** | Scheduled cross-region backups. Manual restore into a recovery region. | One Orchestration Cluster across two regions, with dual-region exporters and manual failover. | One Orchestration Cluster across all regions, with zone-aware replica placement and one exporter. | | **Typical use case** | Hours-long recovery is acceptable | Region recovery with operator intervention | Processing resumes without a Zeebe operator step | | **Optimize** | Supported | Supported | Not available. Optimize requires Elasticsearch or OpenSearch | | **Compliance fit** | Basic business continuity | Published, auditable recovery runbook | Published, auditable runbook, plus processing continuity | | **Relative cost** | **$**: Lower cost; cross-region object storage, with no standing recovery region. | **$$$**: Two running regions, with spare capacity to sustain the workload after region loss, plus cross-region traffic. | **$$$$**: Two or more running regions; three or more for region-loss continuity, with sufficient surviving capacity and cross-region traffic. | ### Cold Recovery recovery conditions Cold Recovery RTO and RPO are bounded by data volume, backup frequency, and operator restore speed. Treat published ranges as planning targets, not contractual commitments. ### Dual-Region recovery conditions Dual-Region RTO is based on internal operational tests. Actual times can vary depending on your environment, level of automation, and the manual steps performed during recovery. See [Dual-Region](./dual-region.md#recovery-objectives) for the failover and failback recovery objectives. ### Multi-Region RDBMS recovery conditions #### Region-loss continuity Region-loss continuity requires quorum-preserving replica placement, where no zone holds half the replicas or more, and enough surviving capacity to carry the load. In this reference layout, one zone maps to one region. A zone can also be an availability zone (AZ). #### Recovery time Multi-Region RDBMS removes the Zeebe recovery procedure, not the recovery window. Zeebe recovers affected partitions in seconds; other partitions continue processing. Client rerouting and database writer promotion can take minutes and depend on your configuration. Operate, Tasklist, and APIs that read secondary storage may show outdated data until the exporter catches up with process execution. See [recovery objectives](./multi-region-rdbms-region-loss.md#recovery-objectives). #### Data loss and replay Primary storage contains Zeebe's replicated log and runtime state. Secondary-storage RPO 0 depends on the replication-monitoring strategy, an eligible failover target, enough disk capacity to retain the unacknowledged log, and replay completion. See the strategy-specific conditions in [recovery objectives](./multi-region-rdbms-region-loss.md#recovery-objectives). For Multi-Region RDBMS, measure the actual recovery window, including client reconnection, with a real failover test in your environment. --- ## Multi-tenancy(Multi-tenancy) ## About Camunda 8 supports three distinct multi-tenancy models, each with different isolation levels and operational characteristics. All models run on the same platform and tooling. They differ in how much they isolate, from a shared database separated by a tenant ID, to a fully separate cluster per tenant. ### Choose your model Choose the model that best fits your isolation requirements and operational constraints: | Aspect | Logical Tenant | Physical Tenant | Multi-Cluster | | :------------------------- | :------------------------------- | :-------------------------------------- | :---------------------------------------- | | **Availability** | Self-Managed and SaaS | Self-Managed only | Self-Managed and SaaS | | **Isolation** | Logical only | Strong physical data isolation | Full physical isolation | | **Data sharing** | Single shared database | Separate data per tenant | Separate per cluster | | **Backup/restore** | Cluster-level only | Independent per tenant | Independent per cluster | | **Cost** | Most efficient | Balanced | Most expensive | | **Operational complexity** | Low | Medium | High | | **Use case** | Small teams, low-risk separation | Multiple teams, strong isolation needed | Separate organizations, maximum isolation | ## Logical Tenants Lightweight tenant-ID based multi-tenancy for cost-efficient subdivision within a single cluster. Logical Tenants share infrastructure but have logically isolated data, configurations, and access controls. This model is best for departments or teams within the same organization with low-risk separation needs. [Logical Tenants](logical-tenants.md) ## Physical Tenants Strong physical data isolation within a single cluster with separate data storage and independent operations per tenant. Physical Tenants still share cluster compute resources such as CPU and memory, so runtime interference is reduced but not fully eliminated. This model is best for multiple teams or organizations requiring strong isolation without the cost and complexity of separate clusters. In Camunda Hub, each Physical Tenant appears as an [environment](/components/concepts/environments.md) that organization admins assign to workspaces. Physical Tenants and Logical Tenants can be used together. Each Physical Tenant can contain its own set of Logical Tenants, providing two independent layers of isolation: physical separation between top-level tenant groups, and logical separation within each group. [Physical Tenants](physical-tenants.md) [Set up two isolated Physical Tenants](../physical-tenants/getting-started.md) ## Multi-Cluster Full isolation through dedicated infrastructure with separate clusters per tenant. Maximum isolation and operational independence, but highest infrastructure cost and complexity. This model is best for separate organizations with maximum isolation requirements or strict data residency needs. On SaaS, this means provisioning a separate cluster per tenant rather than configuring a distinct mode. See [Clusters](/components/concepts/clusters.md). For example, a retail bank and an investment bank under the same parent company might each run their own dedicated cluster, with no shared processes, storage, or networking between them: ## Next steps - Configure [Logical Tenants](/self-managed/deployment/helm/configure/configure-logical-tenants.md) for lightweight subdivision. - Explore [Physical Tenants](physical-tenants.md) for strong isolation. - Manage [tenants in Identity](/self-managed/components/management-identity/manage-tenants.md). --- ## Logical Tenants ## About Logical Tenants provide data isolation through tenant identifiers (stored in the `tenantId` field) but share infrastructure with other Logical Tenants. Multiple Logical Tenants can coexist within a single Physical Tenant or cluster. Logical Tenants are best for cost-efficient sub-division of teams or departments within the same organization. See [multi-tenancy overview](index.md) to compare with other isolation models. ## How Logical Tenants work Camunda 8 implements Logical Tenancy using tenant identifiers within a single installation. All tenant data is stored in the same database, with isolation enforced by appending a tenant identifier to each data object (for example, process definitions, process instances, jobs). ### Tenant identifier The tenant identifier is added to all data created in Camunda 8. By default, all data is assigned to the `` tenant identifier. :::note The `` tenant identifier is reserved and cannot be changed by users. ::: Organizations can create additional tenants. Tenant identifiers must meet the following requirements: - Use only alphanumeric characters, dashes (`-`), underscores (`_`), or dots (`.`). - Be no longer than 31 characters. ### Multi-tenancy checks Multi-tenancy checks enforce tenant-based access control. By default, multi-tenancy checks are **disabled**. This means that although tenants can be created and assigned, the system does not restrict access based on those assignments. All data is associated with the `` tenant. When **enabled**, the system verifies that users can only access resources associated with their assigned tenants. ### Inherited tenant ownership Tenant ownership in Camunda 8 is hierarchical. A user can only deploy resources to authorized tenants. Any data created by those resources inherits the same tenant identifier. ## Isolation of data and processes Each tenant's data and processes are logically isolated from others. This ensures that one tenant's workflows, data models, and configurations do not interfere with or affect other tenants. Each tenant operates in a secure, independent space within the same Camunda 8 instance. ## Resource sharing Logical multi-tenancy provides cost efficiency by allowing multiple tenants to share the same Camunda 8 installation and infrastructure. This reduces operational overhead while maintaining logical isolation between tenants. ## Efficient administration Administrators can manage all tenants centrally using [Admin](/components/admin/tenant.md). This unified management interface simplifies monitoring, configuration, and maintenance tasks across tenant environments. ## Security Strong access control mechanisms prevent tenants from accessing each other's data or processes. These controls ensure tenant-level security and maintain data integrity across all environments. ## Example: tenant membership in action When a user deploys a process model or starts a process instance, the system validates the user's tenant assignments. For example, assume a user belongs to `Tenant A` but not `Tenant B`: 1. **Deploying a process model** - If the user deploys to `Tenant A`, the Orchestration Cluster verifies the assignment. If valid, the model is deployed and all related process instances belong to `Tenant A`. - If the user deploys to `Tenant B`, the deployment fails because the user lacks access to that tenant. 2. **Running process instances** - When querying process instances, the user only sees instances belonging to `Tenant A`. This mechanism ensures proper isolation and access control across tenants. ## Management Identity [Management Identity](/self-managed/components/management-identity/overview.md) is a component of Camunda 8 Self-Managed used for identity and access management of components outside the [Orchestration Cluster](/self-managed/components/orchestration-cluster/overview.md). Of those, only [Optimize](/self-managed/components/optimize/overview.md) is tenant aware, and can make use of logical multi-tenancy. If you wish to use it with the same tenants as an Orchestration Cluster, you will have to manually synchronize the tenants in both the Orchestration Cluster and Management Identity. This means manually creating them, and updating them whenever they change. Two tenants are considered the same if they have the same ID. ## Configuration and management All Logical Tenant configuration and management pages are consolidated here. Each page focuses on a specific aspect: ### Deployment configuration - [Configure Logical Tenants in Helm chart](/self-managed/deployment/helm/configure/configure-logical-tenants.md). Set up multi-tenancy flags and prerequisites for Orchestration Cluster and Management Identity. ### Tenant initialization - [Initialize tenants for Optimize](/self-managed/components/management-identity/configuration/initialize-tenants.md). Programmatically create tenants at startup using configuration files or environment variables. ### Runtime management - [Manage tenants in Identity](/self-managed/components/management-identity/manage-tenants.md). Create, view, and manage tenants through the Identity UI. Assign users, groups, and applications. ### Component-specific setup - [Optimize multi-tenancy](/self-managed/components/optimize/configuration/multi-tenancy.md). Enable and configure multi-tenancy features specific to Optimize. ## Logical Tenants in Camunda Hub If multi-tenancy is enabled, you provide a Logical Tenant when you [deploy to a target environment](/components/hub/workspace/manage-projects/deploy-project.md#logical-tenants) in Camunda Hub. The Logical Tenant is separate from the [environment](/components/concepts/environments.md), which is a Physical Tenant or a cluster. ## Next steps - Need stronger isolation? See [Physical Tenants](physical-tenants.md). - Compare all models: [Multi-tenancy overview](index.md). --- ## Physical Tenants(Multi-tenancy) ## About A Physical Tenant is an isolated execution unit within an Orchestration Cluster. Multiple Physical Tenants can run in a single cluster, each with fully isolated data, its own partition group, and independent lifecycle management. Isolation covers data and management, not compute. Physical Tenants share the cluster's brokers and gateways, so runtime interference between tenants is reduced but not eliminated. See [what is not isolated](/self-managed/concepts/physical-tenants/index.md#what-is-not-isolated). Physical Tenants provide a balanced approach to multi-tenancy. They offer strong isolation without the operational complexity and cost of running separate clusters. See [multi-tenancy overview](index.md) to compare isolation models. ![Two Physical Tenants, payments and lending, each with its own database, identity provider, backup and restore, and web apps, running inside one Orchestration Cluster. Logical Tenants remain available inside each Physical Tenant.](./img/physical-tenant-summary.png) ## Why Physical Tenants **Strong isolation without complexity:** Run multiple teams or organizations on one cluster with complete data separation and independent operations, without the overhead of managing multiple orchestration clusters. **Independent operations:** Back up, restore, scale, and manage each Physical Tenant independently, without coordinating a window across every tenant in the cluster. **Cost efficiency:** Share infrastructure while maintaining tenant autonomy, reducing operational overhead compared to multi-cluster deployments. ## Terminology ### Physical Tenant An isolated execution unit within an Orchestration Cluster. Each Physical Tenant has separate data storage, independent lifecycle management, and API access scoped to that tenant. ### Default Physical Tenant Every Orchestration Cluster automatically includes a default Physical Tenant created at provisioning time. The default Physical Tenant is immutable and cannot be renamed, disabled, or deleted. For backward compatibility, REST API traffic not explicitly scoped to a Physical Tenant is internally routed to the default Physical Tenant. This routing rule is specific to the `/v2/...` REST API; the actuator surface used for scaling and purging does not follow it (see [data purge](/self-managed/operational-guides/data-purge.md) for an operation where an unscoped request instead targets every tenant). ### Cluster-wide operation An operation that affects the entire Orchestration Cluster, such as cluster configuration updates, cluster-level health checks, or cluster backups. Cluster-wide operations are protected by the cluster-admin role and are not scoped to a specific Physical Tenant. ### Tenant-scoped operation An operation that targets a specific Physical Tenant, such as deploying a process to a tenant, backing up a tenant's data, or querying a tenant's process instances. ## API and access patterns Tenant-scoped APIs are accessible at `/physical-tenants/{physicalTenantId}/v2/`: - REST API: `POST /physical-tenants/mytenant/v2/process-definitions` - Webapps: `https://your-cluster/physical-tenants/mytenant/operate` Cluster-wide APIs use a dedicated `/cluster/v2/...` path prefix. Cluster-wide management endpoints require the cluster-admin role, except `GET /cluster/v2/status`, which is deliberately unauthenticated so load balancers can use it as a health check. Endpoints at the standard `/v2/...` paths, including `/v2/topology`, are scoped to a Physical Tenant, not the cluster. gRPC clients specify the Physical Tenant using the `Camunda-Physical-Tenant` custom header. ## Logical and Physical Tenants together Logical Tenants remain available within each Physical Tenant as a lightweight subdivision mechanism. You can use Logical Tenants for cost-efficient sub-division (for example, multiple departments within a team) while relying on Physical Tenants for strong isolation (for example, separate teams within an organization). See [Logical Tenants](logical-tenants.md) for details on the lightweight tenant-ID based model. :::warning There is no migration path from Logical Tenants to Physical Tenants. Logical Tenants created in a Physical Tenant remain associated with that tenant and cannot be migrated to another Physical Tenant. ::: ## Physical Tenants in Camunda Hub In Camunda Hub, each Physical Tenant is an [environment](/components/concepts/environments.md), and teams deploy to it instead of to the cluster. See [how an environment maps to infrastructure](/components/concepts/environments.md#how-an-environment-maps-to-infrastructure) for the naming. ## Wording conventions When referencing Physical Tenants and Logical Tenants in documentation and code: - Use **`physicalTenantId`** when referencing Physical Tenant API parameters, configuration keys, or system identifiers. - Use **`tenantId`** only when referencing Logical Tenants (backward-compatible with existing API). - Existing API keys remain unchanged. - Use **Physical Tenant** and **Logical Tenant** (capitalized) as the canonical terms. ## Learn more - For detailed technical information about isolation model, architecture, and storage configuration, see [physical tenant isolation model](/self-managed/concepts/physical-tenants/index.md). - To set up a second isolated Physical Tenant end to end, see [set up two isolated Physical Tenants](/self-managed/concepts/physical-tenants/getting-started.md). - For tenant configuration defaults, overrides, validation, and examples, see [configuration reference](/self-managed/concepts/physical-tenants/configuration-reference.md). - For adding tenants and lifecycle expectations in 8.10, see [provisioning and lifecycle](/self-managed/concepts/physical-tenants/provisioning-and-lifecycle.md). - For how REST API requests are routed to Physical Tenants, including default tenant compatibility and HTTP status codes, see [API routing](/self-managed/concepts/physical-tenants/api-routing.md). - For identity deployment models, token routing, and per-tenant authorization, see [authentication and authorization](/self-managed/concepts/physical-tenants/authentication-authorization.md). - For how authorization is divided between cluster-wide and tenant-local operations, see [authorization model](/self-managed/concepts/physical-tenants/authorization-model.md). - For how Physical Tenant storage isolation works across primary storage, secondary storage, and document stores, see [storage isolation](/self-managed/concepts/physical-tenants/storage-isolation.md). - For how Physical Tenants map onto Helm releases, including the per-tenant Optimize release, index prefixes, and tenant lifecycle operations, see [configure Physical Tenants across releases](/self-managed/deployment/helm/install/topology/physical-tenants.md). --- ## API routing for Physical Tenants ## Tenant-scoped REST API routing To target a specific Physical Tenant, use the tenant-prefixed path format: ``` /physical-tenants/{physicalTenantId}/v2/{resource} ``` For example: ``` GET /physical-tenants/riskproduction/v2/process-definitions/search POST /physical-tenants/riskproduction/v2/process-instances ``` The `physicalTenantId` in the path must match a configured Physical Tenant. The tenant segment comes **before** `/v2/`. Tenant-facing endpoints available at the standard `/v2/...` paths are also available at their tenant-prefixed equivalents. ## Default tenant routing On the REST API, requests that omit the Physical Tenant prefix are routed to the `default` Physical Tenant. This rule is specific to the `/v2/...` REST API; the actuator surface does not follow it uniformly. For example, an unscoped `POST /actuator/cluster/purge` targets every configured tenant, not just the default one. See [data purge](/self-managed/operational-guides/data-purge.md). ``` /v2/{resource} → /physical-tenants/default/v2/{resource} ``` This means existing integrations that do not use the tenant-prefixed paths continue to work without modification. They interact with the default Physical Tenant. You can also use `default` explicitly as the `physicalTenantId`: ``` GET /physical-tenants/default/v2/process-definitions/search ``` ## Cluster-wide endpoints A cluster-wide endpoint applies to the whole cluster rather than a single Physical Tenant. Cluster-wide operations are exposed under a dedicated `/cluster/v2/...` path prefix. See [cluster admin](/components/admin/cluster-admin.md) for the operations this prefix serves, their authentication requirements, and how to configure access. The `/v2/status` endpoint is a special case; see [the exception below](#exception-v2status). Most other endpoints are scoped to a Physical Tenant, even when they are not tenant-specific in nature. A plain `/v2/...` request targets the `default` tenant. For example: - `/v2/topology` returns the topology for the targeted Physical Tenant (the `default` tenant when no tenant prefix is used), not a cluster-wide view. For the cluster-wide topology, use `/cluster/v2/topology`. - `/v2/license` returns the license status and is available per Physical Tenant, including on the default path (`/v2/license`). It is not a separate cluster-wide endpoint. ### Exception: /v2/status `/v2/status` is scoped to the default Physical Tenant. It is available unprefixed and at `/physical-tenants/default/v2/status`, and returns `404` for any other tenant ID. It is unauthenticated, so load balancers can probe it without credentials. For cluster-wide status, use `/cluster/v2/status`; for one tenant's partition health, use `/physical-tenants/{id}/v2/topology`. ## HTTP status codes | Scenario | HTTP status | | ------------------------------------------------------------------ | ------------------ | | Request to a configured tenant with valid credentials | `2xx` | | Request to a configured tenant with missing or invalid credentials | `401 Unauthorized` | | Request to an unknown or unconfigured tenant | `404 Not Found` | A `404` for an unknown tenant does not indicate an authorization failure. The tenant does not exist in the cluster configuration, so authentication has not yet been attempted. ## Per-tenant endpoint reference There is no separate API specification for Physical Tenants. All endpoints in the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) are available per Physical Tenant. To target a specific tenant, prepend `/physical-tenants/{physicalTenantId}` to the standard `/v2/...` path. For example, the standard `/v2/process-definitions/search` endpoint is available per tenant at `/physical-tenants/{physicalTenantId}/v2/process-definitions/search`. Replace `{physicalTenantId}` with the configured tenant ID (for example, `tenanta` or `default`). ## Cross-tenant queries Each tenant-scoped API call targets exactly one Physical Tenant via the path prefix. There is no cross-tenant query in a single request. If you need data from multiple tenants, you must make separate calls per tenant. This is the core isolation guarantee of Physical Tenants: no tenant-scoped API call can read or write across tenant boundaries. The [cluster-wide endpoints](#cluster-wide-endpoints) under `/cluster/v2/...` and the actuator endpoints are the explicit exception. ## Webapp routing Camunda web applications (Operate, Tasklist, and Admin) follow the same path convention: | Web app | URL pattern | Example | | :------- | :---------------------------------------------- | :-------------------------------------------------------------- | | Operate | `/physical-tenants/{physicalTenantId}/operate` | `https://your-cluster/physical-tenants/riskproduction/operate` | | Tasklist | `/physical-tenants/{physicalTenantId}/tasklist` | `https://your-cluster/physical-tenants/riskproduction/tasklist` | | Admin | `/physical-tenants/{physicalTenantId}/admin` | `https://your-cluster/physical-tenants/riskproduction/admin` | All data shown is scoped to that one Physical Tenant. No cross-tenant data appears within a single web app session. There is no global tenant switcher dropdown. To switch Physical Tenants, navigate to the target tenant's URL. Each tenant loads its own isolated session. ### Access flow ```mermaid flowchart LR A[User navigates to\n/physical-tenants/tenantA/operate] --> B{Session cookie\nfor tenantA present?} B -- Yes --> C[Operate loads\nTenant A data only] B -- No --> D[OAuth redirect to\nTenant A IdP] D --> E[Login and callback\nto /physical-tenants/tenantA/sso-callback] E --> C ``` ### Session behavior Each Physical Tenant has its own path-scoped session cookie, so sessions from different tenants do not interfere. See [session isolation](./authentication-authorization.md#session-isolation) for the cookie-scoping details. - **Simultaneous access**: users can be logged into multiple Physical Tenants at once using different browser tabs. - **Logout**: completes per Physical Tenant. Navigate to the target tenant's logout endpoint to end that tenant's session. - **Role changes mid-session:** Changing a user's roles does not invalidate their Operate or Tasklist session or log them out. The resolved authentication context, including role, group, and tenant membership, is cached in the HTTP session and re-resolved after `camunda.security.authentication.authentication-refresh-interval` (default `PT30S`) elapses. Permission changes are evaluated per request and take effect as soon as they reach secondary storage. Role or group changes sourced from IdP token claims are only picked up after the access token refreshes or the user logs in again. For Optimize deployment guidance, see [Optimize deployment](./index.md#optimize-deployment) and [Optimize and Physical Tenants](./optimize.md). ## MCP routing MCP server endpoints follow the same path convention as the REST API and webapps: ``` /physical-tenants/{physicalTenantId}/mcp/... ``` There is no cluster-wide MCP endpoint. ## Tenant discovery There is no cross-tenant discovery endpoint. A client cannot request a list of Physical Tenants it has access to in a single call. If you need to enumerate accessible tenants, probe each tenant's endpoint individually. ## gRPC routing gRPC clients specify the target Physical Tenant using the `Camunda-Physical-Tenant` request header (metadata in gRPC terms). Requests that omit the header route to the `default` Physical Tenant. ## Backward compatibility and migration Physical Tenants are designed to be backward-compatible for single-tenant and existing multi-tenant deployments: - All existing `/v2/...` calls continue to work without modification. They route to the `default` Physical Tenant. - There is no breaking change for single-tenant users upgrading to 8.10. - To access a non-default Physical Tenant, update your clients to use the tenant-prefixed path. For users migrating from Logical Tenants, the `tenantId` parameter in existing API calls remains unchanged and continues to refer to Logical Tenants within a Physical Tenant. --- ## App Integrations and Physical Tenants ## About A single App Integrations deployment can serve every Physical Tenant of an orchestration cluster. Each tenant gets its own API endpoint, web app links, and notification rules, while the backend, its database, and the Microsoft Teams and Slack app registrations stay shared. Physical Tenant support in App Integrations is available in Camunda 8.10 Self-Managed only. It is not available on SaaS. :::note Related pages - [Physical Tenant isolation model](/self-managed/concepts/physical-tenants/index.md): How Physical Tenants isolate execution and storage - [Authentication and authorization](/self-managed/concepts/physical-tenants/authentication-authorization.md): Identity deployment models and token routing - [Microsoft Teams installation](/components/camunda-integrations/app-integrations/installation.md): The full `config.yaml` reference ::: ## Terminology Three different concepts on this page use the word "tenant". Keep them apart: | Term | Identifier | What it is | | :--------------------- | :----------------- | :--------------------------------------------------------------------------------------------------- | | **Physical Tenant** | `physicalTenantId` | An isolated execution unit inside one orchestration cluster. This is what the page describes. | | **Logical Tenant** | `tenantId` | Camunda's multi-tenancy within a single Physical Tenant. App Integrations does not route on it. | | Microsoft Entra tenant | `teams.tenantId` | The Entra directory hosting the Teams app registration. Unrelated to Camunda tenancy of either kind. | ## How App Integrations resolves the Physical Tenant App Integrations resolves a tenant independently on each of its four paths. ```mermaid graph TD subgraph ai["App Integrations deployment (shared)"] be["Backend"] db[("Database\nusers, notification rules")] bot["Microsoft Teams app\nand bot registration"] end subgraph cluster["Orchestration cluster"] td["default\ncluster URLs"] ta["tenanta\n/physical-tenants/tenanta"] tb["tenantb\n/physical-tenants/tenantb"] end be --- db be --- bot be -->|"API calls per tenant"| td be --> ta be --> tb ta -->|"exporter events"| be tb --> be ``` **Outbound API calls** are issued against the tenant's own orchestration URL, derived as `/physical-tenants/` unless the tenant configures an explicit one. The `default` tenant uses the cluster URL unchanged. **Inbound exporter events** carry their tenant in the `X-Physical-Tenant-Id` header. When the header is absent, App Integrations falls back to the tenant whose `exporter.apiKey` authenticated the request, and finally to `default`. See [event routing](#event-routing-and-notifications). **Inbound connector calls** carry the same header. The App Integrations connector reads the tenant from the job it is executing, so a linked form is fetched from that tenant's orchestration endpoint. See [connector calls](#connector-calls). **User context** is the `(organization, cluster, Physical Tenant)` triple stored per user. It determines which tenant a chat command reads from, and which tenant a new notification rule is scoped to. ## Configuration Declare the tenants of a cluster in the `physicalTenants` array of your `config.yaml`: ```yaml flavor: self-managed clusters: - uuid: name: urls: orchestration: https:// tasklist: https:///tasklist operate: https:///operate exporter: apiKey: physicalTenants: - id: tenanta name: Tenant A urls: tasklist: https:///physical-tenants/tenanta/tasklist operate: https:///physical-tenants/tenanta/operate - id: tenantb name: Tenant B exporter: apiKey: auth: audiences: zeebe: urls: tasklist: https:///physical-tenants/tenantb/tasklist operate: https:///physical-tenants/tenantb/operate ``` | Field | Required | Description | | :----------------------------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | Yes | The `physicalTenantId`, matching the tenant configured on the orchestration cluster. | | `name` | Yes | Display name shown in the cluster selector, for Microsoft Teams or Slack. | | `urls.tasklist` | Yes | The tenant's Tasklist URL. Accepts a plain URL or the `{ base, task }` object described in the [installation guide](/components/camunda-integrations/app-integrations/installation.md#example-configuration-file). | | `urls.operate` | Yes | The tenant's Operate URL. | | `urls.orchestration` | No | Overrides the tenant's API base URL. Defaults to `/physical-tenants/`. Specify it without the `/v2` suffix, as at cluster level. | | `exporter.apiKey` | No | Identifies this tenant on an inbound exporter request that carries no `X-Physical-Tenant-Id` header. | | `connector.apiKey` | No | A separate key for the App Integrations connector endpoints. Rotates independently of `exporter.apiKey`. | | `auth.audiences.zeebe` | No | Overrides the audience requested for this tenant's API calls. See [authentication](#authentication). | | `exposeDefaultTenant` _(on cluster)_ | No | Whether the `default` tenant is selectable alongside the configured ones. Defaults to `false`. | Note that `urls.tasklist` and `urls.operate` are required on every tenant: a tenant never inherits the cluster's web app URLs, because those point at the cluster's own `default` tenant. ### The default tenant The `default` Physical Tenant represents the cluster itself and always uses the cluster's own URLs, never a `/physical-tenants/default/…` path. - **No `physicalTenants` configured**: App Integrations synthesizes a single `default` tenant that takes the cluster's name and URLs. This is the behavior of every cluster configured before 8.10, and it needs no migration. - **`physicalTenants` configured**: only the tenants you declare are offered. The `default` tenant is hidden, so users cannot accidentally read from the cluster-wide endpoint. - **`physicalTenants` configured with `exposeDefaultTenant: true`**: the `default` tenant is added at the top of the list, in addition to the configured tenants. ## Authentication App Integrations defines **one** identity provider for the whole deployment: a single `auth.issuer` and `auth.kind`, and one M2M/SPA client pair. Tenants are distinguished by **audience**, configured per tenant under `auth.audiences.zeebe`. This implements [Model B: single IdP, multiple role-level clients](./authentication-authorization.md#model-b-single-idp-multiple-role-level-clients). [Model C](./authentication-authorization.md#model-c-multiple-idps-advanced), a separate identity provider per Physical Tenant, is **not supported**, since App Integrations cannot hold more than one issuer. The audience for an outbound call is resolved in this order, first match wins: 1. `clusters[].physicalTenants[].auth.audiences.zeebe` 2. `clusters[].auth.audiences.zeebe` 3. `auth.audiences.zeebe` (the deployment-wide value) Only the resource audience can be overridden per tenant. `auth.audiences.global` and `auth.audiences.app_integrations` are deployment-wide and are rejected inside a per-cluster or per-tenant `auth` block. :::note The shapes differ from the orchestration cluster's own configuration: Camunda's identity provider takes a list of allowed audiences, whereas App Integrations sends a single audience string per tenant. The value you set here must be one of the audiences the tenant's client registration accepts. ::: User sign-in is deployment-wide. A user links their Camunda identity once, not per Physical Tenant, so the OIDC redirect URIs registered for App Integrations are not tenant-scoped. See [IdP redirect URI registration](./authentication-authorization.md#idp-redirect-uri-registration). ## Event routing and notifications Each tenant's exporter posts its events to the same App Integrations endpoint. The tenant is resolved per request: 1. **The `X-Physical-Tenant-Id` header**, if present. It must name a tenant configured for that cluster, or the request is rejected with `400 Invalid X-Physical-Tenant-Id header`. 2. **The authenticating API key**, if it is a tenant's `exporter.apiKey`. 3. **`default`**, otherwise. :::warning A request authenticated with the **cluster-level** `exporter.apiKey` never resolves to a named tenant, because the cluster key is matched before the per-tenant keys. If such a request omits the header, its events are attributed to `default` and silently match no rule on a cluster whose `default` tenant is hidden. Give every tenant its own `exporter.apiKey`, send the header, or both. ::: Notification rules are scoped to the `(organization, cluster, Physical Tenant)` triple, and the tenant is matched on exact equality. Unlike the process and element filters, it has no wildcard: a rule scoped to `default` never receives a named tenant's events. ## Connector calls The App Integrations connector posts to the same deployment, and its tenant is resolved the same way as an exporter event's: the `X-Physical-Tenant-Id` header first, then the tenant whose `connector.apiKey` authenticated the request, then `default`. The connector reads the tenant from the job it is executing and sends it on every call, so there is nothing to configure on the connector task or in the process model. What matters is that the connector runtime knows its own tenant. Set `physical-tenant-id` on each client entry, as described in [Connectors runtime](./connectors-runtime.md#how-the-runtime-identifies-the-physical-tenant). A client without it produces jobs that carry no tenant, so the connector omits the header and the call resolves to `default`. Because the tenant reaches the backend, a form referenced by the connector is fetched from that tenant's own orchestration endpoint using that tenant's audience, and notification rule matching is scoped to the same tenant. A call naming a tenant that is not configured for the cluster is rejected with `400 Invalid X-Physical-Tenant-Id header` rather than delivered to `default`. Keep the tenant IDs in `physicalTenants` and the runtime's `physical-tenant-id` values in sync. ## User experience in Microsoft Teams A Physical Tenant is not a separate selector. The Teams cluster picker lists one row per `(cluster, tenant)` pair, and choosing a row sets both at once: - A row for the `default` tenant is labeled with the **cluster's** name. - A row for a named tenant is labeled with the **tenant's** name. - The cluster's health and support status is appended to every one of its rows. - When exactly one pair is available, it is selected automatically. Users switch tenants the same way they switch clusters, and all task lists, process starts, and deep links follow the selected pair. ## Migrate an existing cluster Adding `physicalTenants` to a cluster that already runs App Integrations changes which tenant its traffic is attributed to. Plan for the following: 1. **Existing notification rules stop matching.** Rules created before the change are scoped to `default`. Once events arrive tagged with a named tenant, those rules no longer match and deliver nothing. Recreate them per tenant. 2. **Users must reselect their cluster.** A stored context pointing at a tenant that is no longer offered is not migrated. 3. **The `default` tenant disappears from the picker** unless you set `exposeDefaultTenant: true`. 4. **Give each tenant's exporter its own credentials.** Configure a per-tenant `exporter.apiKey`, and confirm the exporter sends `X-Physical-Tenant-Id`. To keep the cluster-wide view available during a migration, set `exposeDefaultTenant: true` and remove it once every rule has been recreated. ## Known limitations - A separate identity provider per Physical Tenant ([Model C](./authentication-authorization.md#model-c-multiple-idps-advanced)) is not supported. - Identity linking and sign-in are deployment-wide, not per tenant. - Cluster health and version are reported per cluster, not per Physical Tenant. - Physical Tenants are a Self-Managed feature; App Integrations on SaaS is single-tenant. [Physical Tenant isolation model](./index.md) --- ## Authentication and authorization for Physical Tenants ## About Learn how identity providers connect to Physical Tenants and how tokens are routed to the correct tenant. For the resource and permission model and cluster-wide versus tenant-local authorization, see [authorization model](./authorization-model.md). For configuration properties used to assign identity providers to tenants, see [configuration reference](./configuration-reference.md). ## Centralized identity model Identity is **centralized at the cluster boundary**. This means: - Identity providers (IdPs) are defined once at the cluster level. - Each Physical Tenant selects which cluster-defined providers it accepts using `providers.assigned`. - Physical Tenants cannot introduce their own IdP definitions outside the cluster-level list. This design keeps identity management simple and avoids per-engine IdP fragmentation, which is explicitly not recommended for Physical Tenants. Configuring a separate identity provider per engine increases operational complexity without additional security benefit for most use cases. ## Identity deployment models Physical Tenants support two recommended identity deployment models. A third model for advanced or managed-service scenarios is available but not recommended as a baseline. ### Model A: Single IdP, single client All Physical Tenants use one identity provider and one client registration. Roles and permissions are differentiated within that client by tenant-specific mapping rules. Use Model A when: - You have one organization or team using a single IdP. - You want simple configuration with a single client. - Role differentiation per tenant is handled through token claims or group mappings in Camunda. ### Model B: Single IdP, multiple role-level clients All Physical Tenants use one identity provider, but each tenant (or role level) has a dedicated client registration. Camunda distinguishes clients by matching both the **issuer** (`iss` claim) to identify the IdP and the **audience** (`aud` claim) to identify the specific role-level client. Use Model B when: - You need stricter per-tenant token isolation. - Different teams or departments require separate client configurations. - You want role-level client separation within one IdP. Model B is the recommended baseline for most customers deploying Physical Tenants. ### Model C: Multiple IdPs (advanced) Each Physical Tenant uses a separate identity provider. This model is intended for managed services or advanced deployments where tenants are fully autonomous organizations with their own IdPs. Model C is not a recommended baseline. Use it only when: - Tenants are separate organizations that each manage their own IdP. - You are operating a managed service where per-tenant IdP autonomy is required. ## How token routing works When a request arrives at an authenticated endpoint, Camunda matches the JWT token to the correct identity provider using two steps: 1. **Issuer matching:** The token's `iss` claim identifies which configured IdP issued the token. 2. **Audience matching (Model B):** For deployments with multiple role-level clients under the same IdP, the token's `aud` claim identifies the specific client registration. Camunda rejects tokens whose audience does not match the tenant's allowed configuration. Both checks are enforced at the API security filter chain level. A token whose `iss` claim matches no configured provider fails with an authentication error naming the unrecognized issuer. A token with a valid issuer but a non-matching audience is also rejected. ## Per-tenant authorization Roles, permissions, and mapping rules are local to each Physical Tenant. They are **not** stored or managed in the identity provider. - Each Physical Tenant has its own roles and permission definitions. - Mapping rules translate IdP token claims into Camunda roles independently per tenant. - A user can be admin in one tenant and read-only in another, defined independently in each tenant. The IdP only authenticates users and supplies claims. The tenant's local mapping rules in Camunda determine what a user can do within that tenant. ## Per-tenant role and permission definitions Each Physical Tenant defines its own roles, permissions, and mapping rules independently. There is no automatic cross-tenant role inheritance from the cluster level. Role definitions within a tenant cover: - What operations a role can perform within that tenant (for example, deploy processes, start instances, complete tasks) - Which token claims or values map to which Camunda roles within that tenant Because each tenant manages its own authorization, the same user can have different permissions in different Physical Tenants. ### Per-tenant initialization configuration Per-tenant roles, mapping rules, and authorizations use the same configuration shape as the cluster-wide [`camunda.security.initialization`](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#camundasecurityinitializationauthorizations) properties, declared under `camunda.physical-tenants..security.initialization` instead: ```yaml camunda: physical-tenants: tenanta: security: initialization: roles: - roleId: tenant-a-admin name: Tenant A Admin mappingRules: - team-a-admins-mapping mappingrules: - mapping-rule-id: team-a-admins-mapping claim-name: groups claim-value: team-a-admins authorizations: - ownerType: ROLE ownerId: tenant-a-admin resourceType: PROCESS_DEFINITION resourceId: "*" permissions: - READ_PROCESS_DEFINITION - UPDATE_PROCESS_INSTANCE ``` Every explicitly configured Physical Tenant must declare its own `security.initialization` block when authorization is enabled for that tenant. The block is not inherited from the root configuration. Reusing the cluster-wide seed across tenants would create identical admin users and authorizations in every tenant, defeating tenant isolation. Two cases are exempt: - The **default** Physical Tenant, which keeps the top-level `camunda.security.initialization`, whether synthesized from the root or declared explicitly. - Any tenant with `security.authorization.enabled: false` (per-tenant override, or inherited from the root), since the initialization block only takes effect when authorization is enabled. If a non-default tenant with authorization enabled omits the block, startup fails: ```text Each explicitly-configured physical tenant must declare its own initialization block under 'camunda.physical-tenants..security.initialization.*' when authorization is enabled for that tenant; it may not be inherited from the root (the 'default' tenant keeps the top-level 'camunda.security.initialization'). Physical tenants missing a required initialization block: [tenanta, tenantb] ``` ## Token claim mappings Mapping rules define how token claims from the IdP translate into Camunda roles within a Physical Tenant. Each tenant applies its own mapping rules independently, enabling different access levels for the same user across tenants. For example, a token claim `groups: ["team-a-admins"]` might map to an admin role in one Physical Tenant but have no effect in another tenant where that claim is not configured. ## IdP provider assignment Every explicitly configured Physical Tenant must declare which identity providers it accepts using `providers.assigned`. If no providers are assigned to a configured tenant, the cluster fails to start with a configuration validation error: ```text Invalid physical-tenant provider selection: non-default physical tenant '' must declare a non-empty 'camunda.physical-tenants..security.authentication.providers.assigned' selecting which cluster OIDC providers apply to it ``` The one exception is the **implicit default tenant**: when no `camunda.physical-tenants.*` configuration is present, the default tenant falls back to the full cluster provider set. Once the default tenant is explicitly configured under `camunda.physical-tenants.default`, it must also declare its assigned providers. ```yaml camunda: security: authentication: method: oidc providers: oidc: corp-idp: issuer-uri: https://corp-idp.example.com/realms/camunda client-id: camunda-client client-secret: ${CORP_IDP_CLIENT_SECRET} audiences: - camunda-api username-claim: preferred_username physical-tenants: tenanta: security: authentication: providers: assigned: - corp-idp ``` For complete configuration examples, see [configuration reference](./configuration-reference.md). ## IdP redirect URI registration When users log in to a non-default Physical Tenant via a browser (for example, `https://your-cluster/physical-tenants/tenanta/operate`), the OAuth redirect URI includes the tenant path prefix, such as `/physical-tenants/tenanta/sso-callback`. Your IdP must be configured to allow this URI. Register the redirect URI for each Physical Tenant you add. For example, in Keycloak, add `/physical-tenants/{tenantId}/sso-callback` to the allowed redirect URIs for the relevant client. Some IdPs support wildcard matching for redirect URIs, which simplifies configuration when adding many tenants. This is standard procedure for registering a new application with an IdP. The exact configuration depends on your IdP and how you expose the tenant URL (path prefix, subdomain, or other pattern). ## Session isolation Each Physical Tenant has its own path-scoped session cookie. The browser only sends the session cookie for that tenant's URL prefix (`/physical-tenants/`), so sessions from different tenants do not interfere. For example: - Tenant A: `camunda-session-tenanta`, scoped to `/physical-tenants/tenanta` - Default tenant: `camunda-session-default`, scoped to `/physical-tenants/default` ## Cluster-admin role Cluster-wide management endpoints use the cluster-admin role. Broker startup does not fail if the role is not configured. Configure the role to restrict cluster-wide operations, such as backup, restore, and topology management, to authorized operators. The cluster-admin role is resolved from JWT token claims using configurable mapping rules. No persisted cluster-level role bindings or new cluster identity service is required. Multiple mechanisms are supported: claim-based mapping rules, a dedicated cluster-admin configuration, and explicit user assignment for Basic authentication. ## gRPC authentication gRPC clients specify the target Physical Tenant using the `Camunda-Physical-Tenant` request header (metadata in gRPC terms). Requests that omit the header route to the `default` Physical Tenant. --- ## Authorization model for Physical Tenants ## About Learn how Camunda authorizes Physical Tenant operations at the cluster-wide and tenant-local scopes. For identity provider connections and token routing, see [authentication and authorization](./authentication-authorization.md). For the cluster-admin role itself, see [cluster admin](/components/admin/cluster-admin.md). Authorization is divided into two scopes: cluster-wide operations, which affect the entire orchestration cluster, and tenant-local operations, which are scoped to a single Physical Tenant. Tenant-local operations are fully available in Camunda 8.10. Two new authorization resource types were added for the per-tenant management APIs introduced alongside Physical Tenants: | Resource type | Permissions | Backs | | ------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `BACKUP` | `CREATE`, `READ`, `DELETE`, `RESTORE` | Per-tenant runtime and history backup endpoints (`/v2/backups/runtime`, `/v2/backups/history`) and restore (`/v2/restore`) | | `EXPORTER` | `PAUSE` | Per-tenant exporting pause/resume endpoints (`/v2/exporting/pause`, `/v2/exporting/resume`) | The default **admin** role receives all four `BACKUP` permissions and `EXPORTER:PAUSE` automatically. The default **readonly-admin** role receives only `BACKUP:READ` (there is no read-only permission for `EXPORTER`, since `PAUSE` isn't a read operation). An Elasticsearch or OpenSearch history backup needs both `BACKUP:CREATE` and `EXPORTER:PAUSE`, because exporting must be paused for the duration of the backup. Grant the two together to any role that performs backups. Permissions apply to the whole resource type. Fine-grained authorization for an individual backup ID or exporter is not available; only the `*` resource ID is supported. For the operational procedures that use these permissions, see [back up a cluster with multiple Physical Tenants](/self-managed/operational-guides/backup-restore/backup-and-restore.md#multiple-physical-tenants). ## Scope of the 8.10 authorization model The Physical Tenant authorization model is designed around per-engine, per-tenant role and permission management. Key design principles: - **Per-tenant authorization is independently managed.** Each Physical Tenant defines its own roles, permissions, and mapping rules. A change in one tenant's authorization configuration does not affect other tenants. - **Cluster-wide governance via Camunda Hub is a future capability.** Cross-tenant administration using Camunda Hub is not available in 8.10. Cluster-wide management endpoints use the dedicated cluster-admin role. - **Identity provider setup is covered separately.** See [authentication and authorization](./authentication-authorization.md) for the supported identity deployment models and why per-engine IdP fragmentation is discouraged. ## Cluster-wide operations Cluster-wide operations affect the entire orchestration cluster rather than a single Physical Tenant. Examples include viewing cluster topology, triggering cluster backups, and modifying Physical Tenant configuration at runtime. They are protected by the [cluster-admin role](#cluster-admin-role), exposed under the `/cluster/v2/...` path prefix, except `GET /cluster/v2/status`, which is deliberately unauthenticated so load balancers can use it as a health check. Endpoints served at `/v2/...` without a `/physical-tenants/{physicalTenantId}` prefix, including `/v2/topology`, are scoped to the default Physical Tenant. They are not cluster-wide endpoints. Cluster-wide endpoints use the `/cluster/v2/...` prefix. ## Tenant-local operations Tenant-local operations are scoped to a single Physical Tenant and are accessed using the tenant-prefixed URL: `/physical-tenants/{physicalTenantId}/v2/...`. Authorization for these operations is determined by the requesting user's roles and permissions **within that specific tenant**. The **default** Physical Tenant is accessed at `/v2/...` for backward compatibility, and also at `/physical-tenants/default/v2/...`. ### Tenant-local authorization scope Tenant-local operations cover everything needed to run and manage process automation within a tenant: | Category | Description | | ------------------------ | ---------------------------------------------------------------------- | | **Deployment** | Deploying and managing process definitions, decision tables, and forms | | **Process instances** | Starting, canceling, modifying, and querying process instances | | **User tasks** | Assigning, completing, and querying user tasks | | **Variables** | Reading and writing process and scope variables | | **Messages and signals** | Publishing messages and broadcasting signals | | **History and audit** | Querying completed instances, audit events, and incident history | ### Tenant-local endpoint path examples The following examples show the URL structure for tenant-scoped operations. Replace `{physicalTenantId}` with the configured tenant ID (for example, `tenanta` or `default`). | Operation | Endpoint path | | ------------------------ | ---------------------------------------------------------------------------------- | | List process definitions | `GET /physical-tenants/{physicalTenantId}/v2/process-definitions` | | Deploy process | `POST /physical-tenants/{physicalTenantId}/v2/deployments` | | Start process instance | `POST /physical-tenants/{physicalTenantId}/v2/process-instances` | | List process instances | `GET /physical-tenants/{physicalTenantId}/v2/process-instances` | | Get user tasks | `GET /physical-tenants/{physicalTenantId}/v2/user-tasks` | | Complete user task | `POST /physical-tenants/{physicalTenantId}/v2/user-tasks/{userTaskKey}/completion` | | Get variables | `GET /physical-tenants/{physicalTenantId}/v2/variables/{variableKey}` | | Publish message | `POST /physical-tenants/{physicalTenantId}/v2/messages` | For the full API reference, see the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). ## How to determine who can access a tenant Access to a Physical Tenant is determined by two independent checks: 1. **Authentication:** The requesting user's JWT token must be issued by a provider that is in the tenant's `providers.assigned` list. If the provider is not assigned to that tenant, the request fails with `401 Unauthorized`. 2. **Authorization:** The user's roles and permissions (derived from token claims via the tenant's local mapping rules) must allow the requested operation. If the user is authenticated but lacks permission, the request fails with `403 Forbidden`. An unknown tenant ID returns `404 Not Found` on the tenant-prefixed REST paths. The equivalent gRPC error code is not yet documented. ## Role inheritance and override behavior There is **no automatic role inheritance** from the cluster level to individual Physical Tenants, or across Physical Tenants. Each tenant's role and permission configuration is independent. A user with cluster-admin access does not automatically have admin rights within any specific Physical Tenant. Cluster-admin is limited to cluster-wide operations only. ## Audit implications Audit log queries follow the same Physical Tenant scope as other tenant-local API requests. Records from one Physical Tenant are not returned by another tenant's audit-log query. See [audit logs and Physical Tenants](/self-managed/concepts/audit-log/index.md#audit-logs-and-physical-tenants) for storage scope and query paths. ## Cluster-admin role The cluster-admin role protects operations that span all Physical Tenants or affect the entire cluster, such as: - Viewing cluster status and topology - Triggering a cluster restore or a cluster mode change - Running runtime and history backups across every tenant Cluster-admin is resolved from JWT token claims using configurable mapping rules, a dedicated cluster-admin configuration, or explicit user assignment for Basic authentication. There is no separate persisted cluster-level role binding service. Authorization is coarse-grained. Cluster-admin grants access to all cluster-level operations, with no fine-grained sub-roles. Cluster-admin credentials are verified against an isolated user store bound to `/cluster/v2/**`, so a cluster admin cannot reach tenant-scoped `/physical-tenants/{physicalTenantId}/v2/...` endpoints. See [cluster admin](/components/admin/cluster-admin.md) for configuration examples. --- ## Configuration reference(Physical-tenants) ## Configuration model Configuration is static. You define Physical Tenants in application configuration, then apply changes with a rolling restart. At startup, Camunda resolves tenant configuration using this model: 1. Root-level `camunda.*` acts as the implicit base configuration. 2. The `default` Physical Tenant is always present. 3. Optional `camunda.physical-tenants.default.*` overrides the root-level values for the default Physical Tenant. 4. Additional tenants are configured under `camunda.physical-tenants..*`. ## Section structure Use this structure for Physical Tenants: ```yaml camunda: # Root-level defaults (implicit default tenant base) data: secondary-storage: rdbms: url: jdbc:postgresql://db/shared security: authentication: method: oidc providers: # Cluster-level provider definitions oidc: my-idp: issuer-uri: https://my-idp.example.com/realms/camunda client-id: camunda-client client-secret: ${MY_IDP_CLIENT_SECRET} audiences: - camunda-api username-claim: preferred_username physical-tenants: # Optional overrides for the always-present default tenant default: cluster: # Required when you override default-tenant values partition-count: 3 data: secondary-storage: rdbms: url: jdbc:postgresql://db/default_tenant security: authentication: providers: assigned: - my-idp # Additional Physical Tenant tenanta: cluster: partition-count: 3 data: secondary-storage: rdbms: url: jdbc:postgresql://db/tenanta security: authentication: providers: assigned: - my-idp ``` ## Required and optional properties per tenant Each configured tenant under `camunda.physical-tenants.` must assign at least one cluster-defined identity provider through `security.authentication.providers.assigned`. All other tenant-level properties are optional overrides of the root-level defaults. Identity providers are defined at the cluster level, then assigned per tenant. Physical Tenants do not define tenant-local provider objects. For the full list of available properties, see the [Orchestration Cluster configuration properties](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md) reference. ## Cluster-wide defaults and per-tenant overrides Use root-level `camunda.*` for shared defaults across all tenants. Use `camunda.physical-tenants..*` only for tenant-specific differences. Some properties are cluster-scoped and cannot be overridden per tenant. Per-tenant override behavior is indicated in the [Orchestration Cluster configuration properties](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md) reference. ## Default tenant behavior and compatibility The `default` Physical Tenant is always present and immutable. For backward compatibility: - Existing root-level single-tenant configuration maps to the `default` Physical Tenant. - If you upgrade from 8.9 to 8.10, no manual migration step is required for this mapping. - `camunda.physical-tenants.default.*` is interpreted as overrides for the existing default tenant, not as creation of a new tenant. ## Validation and constraints At startup, configuration validation enforces tenant-level constraints. Any validation failure prevents the cluster from starting. Most validation failures throw a `UnifiedConfigurationException`. Secret store and cache validation is an exception and throws an `IllegalStateException` or `IllegalArgumentException` directly. These validation failures don't have a separate error code. Camunda reports the message at startup instead of logging it as a warning. For the exact error message when a tenant is missing `providers.assigned`, see [IdP provider assignment](./authentication-authorization.md#idp-provider-assignment). Known constraints and behavior: - Tenant keys in `camunda.physical-tenants.` must be lowercase alphanumeric (`[a-z0-9]+`) with a maximum length of 64 characters. - Validation rejects unsupported or colliding storage configurations across tenants. - For RDBMS-backed secondary storage, the combination of `camunda.data.secondary-storage.rdbms.url` and the effective table prefix must be unique per tenant. - For Elasticsearch and OpenSearch, the effective index prefix must be unique per tenant. - For object stores, backend-specific location combinations must be unique per tenant: - AWS S3: Bucket name and bucket path. - GCP: Bucket name and prefix. - Azure: Container name, container path, and endpoint. - Local filesystem: Path. - Validation failures are startup failures, not runtime warnings. - **Document store**: non-default tenants must declare `document.assigned`. Startup also fails if two tenants resolve to the same provider, bucket or container, and path. The error names the conflicting tenants. - **Secrets**: each physical tenant supports at most one secret store, and its ID must be `default`; any other ID is rejected. Camunda validates the cache settings per tenant. `ttl` must be at least `1m` and use whole minutes, and `max-size` must be at least `1`. To override the root-level `camunda.secrets.*` defaults for a physical tenant, use `camunda.physical-tenants..secrets.*`. ### Startup error message formats **Secondary storage (RDBMS, Elasticsearch, or OpenSearch) location conflict:** ```text Physical tenants must not share a secondary-storage location, or they would write into the same database. Use a distinct connection, or a distinct index/table prefix per tenant. Conflicts: tenants [tenanta, tenantb] share the same secondary-storage location [type=rdbms, connection=jdbc:postgresql://db/shared, namespace=''] ``` For Oracle, a colliding RDBMS location additionally appends a hint to isolate by schema-per-user instead: `To isolate Oracle physical tenants by schema-per-user (distinct DB users on a shared jdbc url), set data.secondary-storage.rdbms.database-vendor-id: oracle on each tenant.` **Document store location conflict:** ```text Physical tenants must not share a document store location, or they would read and write into the same backing storage. Use a distinct bucket, container, or path per tenant, and never nest one tenant's path inside another's. A nested path is reachable through a caller-supplied document id, which no object store bounds at '/'. Conflicts: tenants [tenanta, tenantb] share the same document store location [provider=aws, namespace=[company-docs-bucket], keyPrefix='tenant-a'] ``` If one tenant's path is nested inside another's rather than identical, the message instead reads: `tenant 's document store location [...] encloses tenant 's [...]`. **Secret store or cache misconfiguration:** ```text Physical tenant 'riskprod' has 2 secret stores configured, but only one is supported at this time ``` ```text Physical tenant 'riskprod' configures secret store 'primary', but the only supported store id is 'default'; rename camunda.physical-tenants.riskprod.secrets.stores.file.primary to camunda.physical-tenants.riskprod.secrets.stores.file.default ``` ```text Physical tenant 'riskprod' has an invalid secret cache configuration: camunda.secrets.cache.ttl must be at least 1 minute, but was PT30S ``` ```text Physical tenant 'riskprod' has an invalid secret cache configuration: camunda.secrets.cache.ttl must be a whole number of minutes, but was PT1M30S ``` ```text Physical tenant 'riskprod' has an invalid secret cache configuration: camunda.secrets.cache.max-size must be at least 1, but was 0 ``` ```text File store 'default' for physical tenant 'riskprod' has no path configured ``` The cache messages always report the canonical `camunda.secrets.cache.*` property path, even when the value came from a `camunda.physical-tenants..secrets.cache.*` override; the tenant name in the surrounding sentence is what identifies which tenant's override is at fault. ## Configuration examples ### application.yaml ```yaml camunda: data: secondary-storage: rdbms: url: jdbc:postgresql://db/default document: default-store-id: shared-s3 aws: shared-s3: bucket-name: company-docs-bucket bucket-path: default/ database: url: jdbc:postgresql://db/default security: authentication: method: oidc providers: oidc: corp-idp: issuer-uri: https://corp-idp.example.com/realms/camunda client-id: camunda-client client-secret: ${CORP_IDP_CLIENT_SECRET} audiences: - camunda-api username-claim: preferred_username physical-tenants: default: cluster: partition-count: 3 document: default-store-id: shared-s3 assigned: - shared-s3 # inherits bucket-path: default/ from root security: authentication: providers: assigned: - corp-idp riskprod: cluster: partition-count: 3 data: secondary-storage: rdbms: url: jdbc:postgresql://db/riskprod database: url: jdbc:postgresql://db/riskprod document: default-store-id: shared-s3 assigned: - shared-s3 aws: shared-s3: bucket-path: riskprod/ # distinct path, no collision with default security: authentication: providers: assigned: - corp-idp initialization: roles: - roleId: riskprod-admin name: Risk Production Admin mappingRules: - riskprod-admins-mapping mappingrules: - mapping-rule-id: riskprod-admins-mapping claim-name: groups claim-value: risk-admins authorizations: - ownerType: ROLE ownerId: riskprod-admin resourceType: RESOURCE resourceId: "*" permissions: - CREATE - ownerType: ROLE ownerId: riskprod-admin resourceType: PROCESS_DEFINITION resourceId: "*" permissions: - CREATE_PROCESS_INSTANCE - UPDATE_PROCESS_INSTANCE - READ_PROCESS_INSTANCE - READ_PROCESS_DEFINITION ``` Every explicitly configured tenant needs its own `security.initialization` block when authorization is enabled; it is not inherited from the root or from other tenants. ### Environment variables Spring environment variable mapping follows canonical property conversion. For example, a root-level property and its per-tenant override: ```bash CAMUNDA_DATA_SECONDARYSTORAGE_RDBMS_URL=jdbc:postgresql://db/default CAMUNDA_PHYSICALTENANTS_RISKPROD_DATA_SECONDARYSTORAGE_RDBMS_URL=jdbc:postgresql://db/riskprod ``` If YAML and environment variables are used together, use the same normalized tenant key in both forms. :::note Related pages - [Physical Tenant isolation model](./index.md) - [Provisioning and lifecycle](./provisioning-and-lifecycle.md) - [Multi-tenancy overview](../multi-tenancy/index.md) - [Cluster admin](/components/admin/cluster-admin.md) for configuring access to cluster-wide operations ::: --- ## Connectors runtime: Physical Tenant support ## About Learn how one Connectors runtime instance can serve multiple Physical Tenants with tenant-specific clients, workers, secrets, and inbound paths. ## Architecture ```mermaid graph TD runtime["Connectors Runtime\n(single instance)"] subgraph ptA["Physical Tenant A"] workerA["Job Worker\n(type, tenanta)"] inboundA["Inbound Connector\n/inbound/tenanta/..."] end subgraph ptB["Physical Tenant B"] workerB["Job Worker\n(type, tenantb)"] inboundB["Inbound Connector\n/inbound/tenantb/..."] end subgraph ptDefault["Physical Tenant default"] workerDefault["Job Worker\n(type, default)"] inboundDefault["Inbound Connector\n/inbound/default/..."] end runtime --> workerA runtime --> workerB runtime --> workerDefault runtime --> inboundA runtime --> inboundB runtime --> inboundDefault classDef runtime fill:#e4eef8,stroke:#2272c9,color:#14082c,stroke-width:2px classDef tenant fill:#fde8da,stroke:#fc5d0d,color:#14082c classDef worker fill:#e8fdf1,stroke:#10c95d,color:#14082c class runtime runtime class ptA,ptB,ptDefault tenant class workerA,workerB,workerDefault,inboundA,inboundB,inboundDefault worker ``` ## How the runtime identifies the Physical Tenant The activated job record carries the `physicalTenantId`, propagated through the broker request. The runtime uses this value to determine which Physical Tenant a job belongs to. - The runtime registers one job worker per configured client per connector type, so each Physical Tenant gets its own worker for each job type. - Registration covers **statically configured** `camunda.clients.*` entries only. A Physical Tenant only gets a job worker if it is explicitly configured as a client. - Always set `physical-tenant-id` explicitly on each client entry. If it is omitted, the runtime falls back to a derived value and per-tenant attribution in metrics and the connector instance listing becomes unreliable. ## Configuration Use the `camunda.clients.*` multi-client configuration to connect the runtime to multiple Physical Tenants. The shared `camunda.client.*` block sets base connection details inherited by all entries; per-client entries override only what differs (typically `physical-tenant-id` and `auth.*`). ```yaml camunda: client: # Shared base inherited by all client entries grpc-address: https://your-cluster.example.com:26500 rest-address: https://your-cluster.example.com clients: default: mode: self-managed physical-tenant-id: default auth: client-id: connector-default client-secret: ${SECRET_DEFAULT} primary: true # resolves the default @Autowired CamundaClient tenanta: mode: self-managed physical-tenant-id: tenanta auth: client-id: connector-tenanta client-secret: ${SECRET_TENANTA} tenantb: mode: self-managed physical-tenant-id: tenantb auth: client-id: connector-tenantb client-secret: ${SECRET_TENANTB} ``` - The client name (`tenanta`) is a free-form label and does not have to match the `physical-tenant-id`. - `physical-tenant-id` must be lowercase alphanumeric, maximum 64 characters. - Mark one entry `primary: true` when configuring multiple clients. With a single client, it is the primary implicitly. - Existing single-client deployments using `camunda.client.*` are unaffected. At startup, `camunda.client.*` transparently maps to `camunda.clients.default.*`. ## Outbound connectors ### Tenant context propagation The runtime resolves each job's Physical Tenant from the activated job record and uses it to select the per-tenant secret provider, document store, and metrics attribution for that job. The `physicalTenantId` is not exposed as a process variable, so it cannot be referenced directly from a FEEL expression in a connector's element template. To route a connector to a tenant-specific endpoint, set a variable at process start and reference that variable instead. ### Per-tenant secret access Per-tenant secret isolation is **opt-in and disabled by default**. Enable it explicitly in any multi-tenant deployment: ```yaml camunda: connector: secretprovider: environment: physicaltenantaware: true ``` Equivalent environment variable: `CAMUNDA_CONNECTOR_SECRETPROVIDER_ENVIRONMENT_PHYSICALTENANTAWARE`. With this enabled, the runtime resolves each secret against a name scoped to the job's Physical Tenant: `${prefix}${physicalTenantId}_${name}`. With the default secret prefix `SECRET_`, a reference to `MY_SECRET` from a job on `tenanta` resolves the environment variable `SECRET_tenanta_MY_SECRET`. :::warning Secrets are shared across tenants by default With `physicaltenantaware` left at its default of `false`, all configured clients resolve secrets from a single flat namespace. A reference to `{{secrets.MY_SECRET}}` resolves the same `SECRET_MY_SECRET` value regardless of which Physical Tenant the job belongs to. Enable `physicaltenantaware` in any deployment where tenants must not share secret values. ::: Reference secrets in connector properties using a [legacy secret reference](/reference/glossary.md#secret-reference-legacy), `{{secrets.MY_SECRET}}`. The runtime replaces references when it binds the job's variables, and does not write resolved values back to the variable store, Operate, Tasklist, or logs. Independently of tenant scoping, `camunda.connector.secret-resolver.secret-filter.mode` controls whether a connector element may reference secret keys it has not declared. It defaults to `DISABLED`, meaning no key-level restriction is enforced. Set it to `LAX` or `STRICT` to restrict each element to the secret keys declared in its process definition. ## Inbound connectors The runtime polls each configured Physical Tenant for process definitions using that tenant's own client, and tracks inbound connector state per tenant, so inbound executables and their state stay isolated across tenants. ### Webhook path routing Set `camunda.connector.webhook.append-physical-tenant-and-tenant-to-path: true` to activate namespaced paths: ``` /inbound/// ``` Equivalent environment variable: `CAMUNDA_CONNECTOR_WEBHOOK_APPEND_PHYSICAL_TENANT_AND_TENANT_TO_PATH`. If unset, the property is inferred automatically: multi-client configurations default to `true`, single-client configurations default to `false`. An explicit value always overrides inference. Without namespaced paths, the first registered inbound connector matching a path claims the request regardless of Physical Tenant. Enable namespaced paths in any multi-tenant deployment. :::warning Isolation relies on path uniqueness, not tenant-scoped credentials The routing layer performs pure path-segment matching on `physicalTenantId/tenantId/path`. No credential or signature check happens at the routing layer. HMAC and signature verification is handled per connector downstream, inside each webhook element's own logic, and has no awareness of `physicalTenantId`. Tenant isolation depends entirely on each tenant's webhook paths being distinct and unguessable. ::: ### Known limitations - Each inbound request targets exactly one Physical Tenant. Routing a single webhook event to multiple tenants is not supported. - Cross-tenant inbound routing is not supported. ## Authorization Each `camunda.clients.*` entry authenticates independently using its own `auth.*` credentials, so grant permissions per Physical Tenant to the identity configured for that client. Each client's identity needs the standard permissions to activate, complete, and fail jobs for the process definitions it serves, plus read access to process definitions for inbound connector polling. See [Authorization model](/self-managed/concepts/physical-tenants/authorization-model.md) for how permissions are scoped per Physical Tenant. ## Operational considerations ### Scaling One runtime instance per cluster is standard. Deploy multiple instances only when you need runtime-layer resource isolation, such as performance SLA separation. Physical Tenants already isolate data structurally. ### Monitoring Every connector metric is tagged with `physicalTenantId`, so outbound and inbound activity can be filtered and grouped per Physical Tenant. Two tenants running the same connector type report separately rather than collapsing into one series. Tagged metrics include outbound invocation counts and execution times, the last-completed and last-failed timestamps, and inbound activation and trigger counts. Jobs handled by a single-client deployment that configures no `physical-tenant-id` are reported under the tag value `default`. ### Secret rotation Secrets resolved through the environment secret provider are read from the runtime's environment, so rotating a value requires updating the environment variable and restarting the runtime. Because a single runtime instance serves all configured Physical Tenants, rotating one tenant's secret restarts the shared runtime and therefore briefly interrupts every configured tenant. Plan rotations accordingly. ### Failure handling A failure in one tenant's job workers does not affect workers registered for other tenants. On the inbound side, a polling failure for one Physical Tenant is logged and does not stop the other tenants from completing their imports in the same cycle. The runtime's readiness signal is shared across tenants, however, so a persistent polling failure for a single tenant marks the whole runtime instance as not ready. :::note App integrations (MS Teams and similar) App integrations with Physical Tenant support are implemented and will be documented in a follow-up section once the configuration details are finalized. Multiple Keycloak (separate IdP per tenant in the connector context) is not supported. ::: [Physical Tenant isolation model](/self-managed/concepts/physical-tenants/index.md) --- ## Custom exporters for Physical Tenants ## About Learn how custom exporters interact with Physical Tenants: exporters defined once at the root level and assigned to specific tenants, and exporters a tenant declares only for itself. For the base exporter configuration properties (`camunda.data.exporters.*`), see the [Orchestration Cluster configuration properties](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#camundadataexporters) reference. ## Two ways to use a custom exporter with Physical Tenants - **Global exporter, assigned to specific tenants**: Define the exporter once under the root `camunda.data.exporters..*` catalog, then list its ID under `camunda.physical-tenants..data.exporters-assigned` for every tenant that should run it. A tenant can only adjust the exporter's `args`; the `class-name` and `jar-path` always come from the root definition. - **Tenant-private exporter**: Declare the exporter entirely under `camunda.physical-tenants..data.exporters..*`, using an ID that does not exist in the root catalog. The exporter is not shared with, or visible to, other tenants. ## Assigning a root-level exporter to specific tenants ```yaml camunda: data: exporters: audit-log: class-name: com.example.AuditLogExporter jar-path: /usr/local/camunda/exporters/audit-log.jar args: endpoint: https://audit.example.com/ingest physical-tenants: tenanta: data: exporters-assigned: - audit-log exporters: audit-log: args: # Overrides/extends the root args for this tenant only endpoint: https://audit.example.com/ingest/tenanta tenantb: data: exporters-assigned: [] # tenantb does not run the audit-log exporter ``` - `exporters-assigned` is the tenant's complete list of generic (non-autoconfigured) exporter IDs. It is mandatory for every tenant once the root catalog is non-empty or the tenant declares its own exporter; pass an empty list to explicitly run no generic exporters. - Listing an ID in `exporters-assigned` without also declaring it under `data.exporters` is enough to run the exporter with its root `args` unchanged. - Declaring an exporter under `data.exporters` without also listing it in `exporters-assigned` is rejected at startup: configuring an exporter is not a way to activate it. - The autoconfigured `camundaexporter` and `rdbms` exporters (created automatically from a tenant's secondary-storage configuration) must never appear in `exporters-assigned`. ## What a tenant can and cannot override When a tenant is assigned a root-declared exporter ID: | Field | Overridable per tenant | | :----------- | :---------------------------------------------------------------------------------------------------------------------------- | | `class-name` | No — must match the root value if restated; startup fails otherwise | | `jar-path` | No — must match the root value if restated; startup fails otherwise | | `args` | Yes — deep-merged with the root args when the exporter supports it, otherwise replaces the root args entirely for that tenant | Assigning a root exporter ID means running the root's exporter implementation, optionally with adjusted arguments. To run a different exporter class, declare it under a new, tenant-private exporter ID instead of reusing a root ID. ## Declaring a tenant-private exporter Use an exporter ID that is not defined in the root catalog to scope an exporter entirely to one tenant: ```yaml camunda: physical-tenants: tenanta: data: exporters-assigned: - tenanta-only-exporter exporters: tenanta-only-exporter: class-name: com.example.TenantAExporter jar-path: /usr/local/camunda/exporters/tenant-a.jar args: region: us-east ``` A tenant-private exporter is not visible to, or usable by, other Physical Tenants, and it does not need to match any root definition. :::note Related pages - [Configuration reference](./configuration-reference.md) - [Storage isolation](./storage-isolation.md) - [Physical Tenant isolation model](./index.md) ::: --- ## Set up two isolated Physical Tenants ## About Set up a second, strongly isolated Physical Tenant on an existing Camunda 8 Self-Managed cluster, with its own storage, identity, and backups. In this scenario, a bank runs its day-to-day operations on one Camunda 8 cluster today, the always-present `default` Physical Tenant. Its Risk team is now onboarding, and compliance requires Risk's process data, identity provider, and backups to be fully separate from Operations, without a second cluster to operate. This is the internal-domain pattern from [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md): strong isolation for teams that must not blur, on one platform. This guide assumes Kubernetes with the Camunda Helm chart and a shared Keycloak or external OIDC provider. This guide's examples isolate the new tenant with a separate schema on the same PostgreSQL instance `default` already uses. The same steps apply, but isolate the new tenant with a distinct index prefix instead of a schema. See [Elasticsearch and OpenSearch storage](./storage-isolation.md#elasticsearch-and-opensearch-storage) for the prefix rules. Before starting, read [Physical Tenant isolation model](./index.md) for the concepts this guide builds on. It links out to the [configuration reference](./configuration-reference.md), [Helm configuration guide](/self-managed/deployment/helm/configure/configure-physical-tenants.md), and [API routing](./api-routing.md) pages for full property and endpoint detail rather than repeating them here. ## Pre-flight checklist Confirm each of these before you start. Every item here has caused a real setup failure: | Check | Why it matters | | :------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Running Camunda 8.10 Self-Managed Helm deployment | Physical Tenants are a Self-Managed feature only, not available on SaaS in this release. | | RDBMS (or Elasticsearch/OpenSearch) reachable for a second, distinct schema or index prefix | Startup fails if the new tenant's storage location collides with an existing one. | | Permission to register a new redirect URI in your identity provider | The new tenant needs its own callback URI registered before its first browser login. | | Decide whether the new tenant uses the same identity provider as `default` | Both tenants can share one OIDC provider and still authorize independently. See [choose an identity provider setup](./authentication-authorization.md#identity-deployment-models). |
Two behaviors that are easy to miss on a first rollout - **RDBMS table prefixes are compared case-insensitively.** Configuring `riskprod_` for one tenant and `RISKPROD_` for another is treated as the same storage location and fails startup, not two distinct ones. Pick table prefixes that are unique regardless of case. - **Elasticsearch, OpenSearch, and custom exporters need explicit per-tenant assignment.** Only the built-in Camunda exporter and the RDBMS exporter merge their configuration from the root automatically, based on the tenant's configured secondary storage type. The Elasticsearch and OpenSearch exporters are treated as custom exporters: like any custom exporter loaded from a JAR, they need a full configuration block per tenant, and that tenant must list them under its own exporter assignment configuration rather than inheriting from the root. If your secondary storage is RDBMS and you don't use any custom exporters, skip this.
Deploying from Desktop Modeler? To target a Physical Tenant from Desktop Modeler, change the cluster URL from `.../v2` to `.../physical-tenants//v2` and leave the client's tenant ID field unset. That field is for Logical Tenants, not Physical Tenants.
## Plan Before touching configuration, decide three things for the new tenant: 1. **Storage**: a distinct RDBMS schema, database, or index prefix. This guide uses a separate schema on the same PostgreSQL instance `default` already uses, the lowest-effort option. See [RDBMS storage](./storage-isolation.md#rdbms-storage) for the tradeoffs against a dedicated database instance. 2. **Identity**: whether Risk reuses the platform's existing Keycloak/OIDC provider (recommended to start, since it's one less moving part) or connects its own identity provider. 3. **Authorization**: who administers Risk's tenant, and what roles their process applications need. Physical Tenants don't inherit authorization from the cluster or from other tenants. Each tenant's `security.initialization` block is independent. See [per-tenant role and permission definitions](./authentication-authorization.md#per-tenant-role-and-permission-definitions). For this walkthrough, the new tenant is named `riskprod`, reusing the existing Keycloak provider, with its own schema and its own authorization block. ## Configure :::tip Local development and evaluation To try Physical Tenants on your machine without Kubernetes, use Camunda 8 Run, directly or through c8ctl. See [configure Physical Tenants in Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run/configuration.md#configure-physical-tenants). The rest of this guide configures Physical Tenants in a Kubernetes deployment with Helm. ::: Add the following to your Helm values, either inline under `orchestration.configuration` or as a separate file under `orchestration.extraConfiguration`. Both approaches, and the full property list, are covered in [configure Physical Tenants in Helm chart](/self-managed/deployment/helm/configure/configure-physical-tenants.md). This example assumes the base `camunda.security.authentication` block for `default` is already in your values file. The minimum needed is storage and an assigned identity provider: ```yaml orchestration: extraConfiguration: - file: physical-tenants.yaml content: | camunda: physical-tenants: riskprod: # Shared PostgreSQL instance, isolated by schema. data: secondary-storage: rdbms: url: jdbc:postgresql://db.example.com:5432/camunda?currentSchema=riskprod_schema security: authentication: providers: assigned: - oidc # reuse the cluster-level OIDC provider ``` This example uses `elasticsearch`; substitute `opensearch` if that's your secondary storage type. Use the same type `default` already uses, since a cluster can't mix secondary storage types across Physical Tenants. ```yaml orchestration: extraConfiguration: - file: physical-tenants.yaml content: | camunda: physical-tenants: riskprod: # Shared Elasticsearch/OpenSearch cluster, isolated by index prefix. data: secondary-storage: elasticsearch: index-prefix: riskprod # must be unique across tenants security: authentication: providers: assigned: - oidc # reuse the cluster-level OIDC provider ``` Add an authorization block so `riskprod` has an admin role of its own. Every explicitly configured tenant needs one; authorization isn't inherited from the cluster:
Full authorization example for riskprod ```yaml security: initialization: roles: - roleId: riskprod-admin name: Risk Production Admin mappingRules: - riskprod-admins-mapping mappingrules: - mapping-rule-id: riskprod-admins-mapping claim-name: groups claim-value: risk-admins authorizations: - ownerType: ROLE ownerId: riskprod-admin resourceType: RESOURCE resourceId: "*" permissions: - CREATE - ownerType: ROLE ownerId: riskprod-admin resourceType: PROCESS_DEFINITION resourceId: "*" permissions: - CREATE_PROCESS_INSTANCE - UPDATE_PROCESS_INSTANCE - READ_PROCESS_INSTANCE - READ_PROCESS_DEFINITION ``` Add this block alongside `security.authentication` above, under the same `riskprod` tenant.
If you're using RDBMS, create the `riskprod_schema` schema on your PostgreSQL instance before applying this. Camunda validates that the schema exists at startup; it does not create it for you. If you're using Elasticsearch/OpenSearch, no manual step is needed: Camunda creates indices under the `riskprod` prefix automatically at startup. See [validation and operations](./storage-isolation.md#validation-and-operations). Now register `riskprod`'s redirect URI in your identity provider. In Keycloak, add `/physical-tenants/riskprod/sso-callback` to the client's allowed redirect URIs. See [identity provider redirect URI registration](./authentication-authorization.md#idp-redirect-uri-registration). ## Deploy Apply the updated values with a rolling restart: ```bash helm upgrade camunda camunda/camunda-platform -f values.yaml ``` Adding a Physical Tenant always requires a rolling restart. `default` keeps serving requests throughout the rollout. See [rolling restart expectations](./provisioning-and-lifecycle.md#rolling-restart-expectations). Confirm `riskprod` is up before deploying a process to it. This and every other request in this guide requires an access token from the identity provider you assigned to `riskprod`; the examples omit the auth header for brevity. See [authentication](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md) for how to obtain one: ```bash curl https://your-cluster/physical-tenants/riskprod/v2/topology ``` Deploy your process to `riskprod` from Desktop Modeler by targeting its tenant URL (see the pre-flight checklist), or from the CLI/Java client by connecting a client scoped to `riskprod` (see [API walkthrough](#api-walkthrough) below) and calling the deploy operation as usual. ## Run and verify 1. Start a process instance scoped to `riskprod` (see [API walkthrough](#api-walkthrough)). 2. Open `https://your-cluster/physical-tenants/riskprod/operate` and confirm the instance is visible. 3. In a second browser tab, open `https://your-cluster/physical-tenants/default/operate`. Confirm the `riskprod` instance does **not** appear, and that `default`'s own instances are unaffected. Each tenant has its own path-scoped session, so you can be logged into both at once without one logout affecting the other. See [session behavior](./api-routing.md#session-behavior). 4. Try opening `riskprod`'s Operate with a user who only has a role in `default`. Expect a `403`, not a redirect to `default`'s data, confirming the two tenants don't fall back to each other on authorization failure. This confirms the isolation the rest of the guide assumes: two tenants, one cluster, no visibility across the boundary. ## API walkthrough | Client | How to target `riskprod` | | :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | REST | Prefix every request with `/physical-tenants/riskprod/`, for example `POST /physical-tenants/riskprod/v2/process-definitions/search`. Plain `/v2/...` requests always resolve to `default`. | | gRPC | Send the `Camunda-Physical-Tenant: riskprod` header (metadata) on each call. Omitting it routes to `default`. | | Java client | Set `.physicalTenantId("riskprod")` when building the `CamundaClient`. This scopes both REST and gRPC calls made through that client, no separate REST configuration needed. See [Physical Tenants in the Java client](/apis-tools/java-client/physical-tenants.md). | | Desktop Modeler | Change the deployment target's cluster URL to end in `/physical-tenants/riskprod/v2`, leave the tenant ID field unset. | To filter search results by tenant, you don't need a request parameter. The tenant is already fixed by which prefix, header, or client you used to make the call. A `riskprod`-scoped search never returns `default`'s data and vice versa. For example, [create a process instance](/apis-tools/orchestration-cluster-api-rest/specifications/create-process-instance.api.mdx) scoped to `riskprod`: ```bash curl -X POST https://your-cluster/physical-tenants/riskprod/v2/process-instances \ -H "Content-Type: application/json" \ -d '{"processDefinitionId": "your-process-id"}' ``` And [complete a job](/apis-tools/orchestration-cluster-api-rest/specifications/complete-job.api.mdx) for that same tenant, once a worker activates it: ```bash curl -X POST https://your-cluster/physical-tenants/riskprod/v2/jobs/{jobKey}/completion \ -H "Content-Type: application/json" \ -d '{}' ``` Both requests use the `riskprod` prefix from the table above; the same pattern applies to any REST operation. For the full REST path reference and status code meanings, see [tenant-scoped REST API routing](./api-routing.md#tenant-scoped-rest-api-routing) and [HTTP status codes](./api-routing.md#http-status-codes). ## Web app walkthrough | Web app | `riskprod` URL | | :------- | :-------------------------------------------------------- | | Operate | `https://your-cluster/physical-tenants/riskprod/operate` | | Tasklist | `https://your-cluster/physical-tenants/riskprod/tasklist` | To try a user task end to end in the new tenant: 1. Deploy a process containing a user task to `riskprod`, and start an instance. 2. Open Tasklist at `riskprod`'s URL, claim the task, complete it. 3. Open Tasklist at `default`'s URL. Confirm the task is not visible there. There's no tenant switcher inside either web app. Switching tenants means navigating to the other tenant's URL, which loads a fully separate session. See [webapp routing](./api-routing.md#webapp-routing). ## Day-2 operations These operations are documented in full elsewhere. This section only covers the tenant-specific part of each. **Scale `riskprod`'s partitions independently of `default`:** ```bash curl -X PATCH "https://your-cluster/actuator/cluster?physicalTenant=riskprod" \ -d '{"partitions": {"count": 6}}' ``` Only `riskprod`'s partition group changes. See [scale a cluster with multiple Physical Tenants](/self-managed/components/orchestration-cluster/zeebe/operations/cluster-scaling.md#scale-a-cluster-with-multiple-physical-tenants) for broker count, replication factor, and the other scaling dimensions. **Back up `riskprod` specifically:** call the tenant-scoped endpoint rather than the cluster-wide one, `POST /physical-tenants/riskprod/v2/backups/history`, using a role granted both `BACKUP:CREATE` and `EXPORTER:PAUSE` (exporting must pause for the duration of a history backup). The history backup endpoint is available only with Elasticsearch or OpenSearch as secondary storage. With RDBMS, back up the tenant's schema, database, or table prefix with the database's own tooling. See [back up a cluster with multiple Physical Tenants](/self-managed/operational-guides/backup-restore/backup-and-restore.md#multiple-physical-tenants) for the full endpoint reference and required permissions. **Restore `riskprod` without affecting `default`:** the recovery-mode and restore endpoints target one tenant at a time by default. Prefix them with `/physical-tenants/riskprod`. See [restore a cluster with multiple Physical Tenants](/self-managed/operational-guides/backup-restore/backup-and-restore.md#multiple-physical-tenants) for the full procedure. This is destructive to the targeted tenant's data. Follow that guide directly rather than improvising from this summary. **Add a third tenant:** add its configuration block (following the same shape as `riskprod` above) and apply with a rolling restart. It's provisioned automatically, with no separate creation step. Removing a tenant from configuration disables it and retains its data; re-adding it later re-enables the tenant with that same data intact. There's no permanent-delete operation in this release. An actuator endpoint can logically remove an already-disabled tenant from the cluster topology (useful so a disabled tenant doesn't block operations like multi-region failover), but it deletes no data. See [disable, rename, and delete](./provisioning-and-lifecycle.md#disable-rename-and-delete). Before adding more tenants, check broker memory, database connections, and noisy-neighbor limits in [size clusters with Physical Tenants](/components/best-practices/architecture/sizing-physical-tenants.md). Once you're validating a third tenant's rollout, check its topology alongside the cluster's overall status: `GET /physical-tenants//v2/topology` for the tenant, `GET /cluster/v2/topology` for the whole cluster (requires [cluster admin](/components/admin/cluster-admin.md) access). ## Troubleshooting this scenario For issues beyond this specific setup flow, see [troubleshoot Physical Tenants](./troubleshooting.md). These are the failures most likely to hit you during this walkthrough: | Symptom | Likely cause | Fix | | :-------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Startup fails naming `riskprod` and another tenant sharing a storage location | Two tenants resolved to the same schema, database, or index prefix, or to RDBMS prefixes that differ only by case | Pick a unique schema or prefix. RDBMS table prefixes must also differ by more than case. See [validation and operations](./storage-isolation.md#validation-and-operations). | | Startup fails: `riskprod` must declare `providers.assigned` | A non-default tenant was configured without assigning an identity provider | Add `security.authentication.providers.assigned` under `riskprod`, even if it reuses the cluster's only provider. See [identity provider assignment](./authentication-authorization.md#idp-provider-assignment). | | First login to `riskprod` fails at the identity provider, before reaching Camunda | The tenant's redirect URI was never registered | Register `/physical-tenants/riskprod/sso-callback` in your identity provider. See [identity provider redirect URI registration](./authentication-authorization.md#idp-redirect-uri-registration). | | A user with a role in `riskprod` gets `403` in `default`, or vice versa | Expected behavior, not a bug | Authorization is per tenant by design. Grant the role in each tenant where the user needs access. | | A custom exporter's per-tenant settings seem to be ignored | Custom exporters don't inherit root-level config unless they implement their own merge logic | Declare the exporter's full configuration under each tenant, and assign it explicitly to that tenant rather than relying on inheritance from the root. | | `riskprod` stays disabled after being re-added to configuration | Configuration wasn't applied with a rolling restart | Re-adding a tenant to configuration takes effect on the next rolling restart, not immediately. | **Finding logs**: broker and gateway logs aren't split per tenant. A single broker pod can host partitions for both `default` and `riskprod`. Get logs the normal Kubernetes way (`kubectl logs `), then filter for the tenant ID. Transition and validation log lines name the affected tenant directly, so grepping for `riskprod` isolates its entries from a shared pod's log. ## Lessons learned - **Start with one shared identity provider.** Connecting a second tenant to its own separate identity provider is supported, but it's a second thing to get wrong on your first rollout. Prove the pattern with a shared provider first, then split identity later if compliance requires it. - **Storage isolation errors happen at startup, not at runtime.** Getting the schema, database, or prefix wrong fails the rollout immediately and names both conflicting tenants. It doesn't silently share data. Treat a failed rollout here as the isolation check working, not a bug. - **Authorization doesn't compose across tenants, budget for it.** Every explicitly configured tenant needs its own complete `security.initialization` block. For two tenants this is a few extra lines; for ten, template it rather than hand-writing each one. - **Add tenants for isolation boundaries, not for scale.** If Risk just needs more throughput on the same data and identity as Operations, that's a partition-count or broker-count change within one tenant, not a new tenant. Reach for a new Physical Tenant when a team needs its own storage, identity, or backup, not just more capacity. ## Related pages - [Physical Tenant isolation model](./index.md) - [Configuration reference](./configuration-reference.md) - [Provisioning and lifecycle](./provisioning-and-lifecycle.md) - [Storage isolation](./storage-isolation.md) - [Size clusters with Physical Tenants](/components/best-practices/architecture/sizing-physical-tenants.md) - [Authentication and authorization](./authentication-authorization.md) - [API routing](./api-routing.md) - [Troubleshoot Physical Tenants](./troubleshooting.md) - [Configure Physical Tenants in Helm chart](/self-managed/deployment/helm/configure/configure-physical-tenants.md) --- ## Physical Tenant isolation model ## About A Physical Tenant is an isolated execution unit inside one Orchestration Cluster, with its own storage, identity, and backups. This page covers one Orchestration Cluster with multiple Physical Tenants. Multi-region and multi-cluster topologies are separate topics. :::tip New to Physical Tenants? Start with the [Physical Tenants overview](/self-managed/concepts/multi-tenancy/physical-tenants.md) to compare tenancy models, or jump straight to [set up two isolated Physical Tenants](./getting-started.md) for a hands-on walkthrough. ::: ## Isolation model Isolation applies differently at each layer of the stack: | Layer | Isolation model | Shared or isolated | | :---------------- | :------------------------------------------------------------------------------------------------------- | :-------------------- | | Primary storage | Dedicated Raft groups per Physical Tenant. A single tenant can span multiple brokers. | Isolated | | Brokers | Brokers are co-located and can host more than one Physical Tenant. | Shared infrastructure | | Gateways | Gateways route requests to the targeted tenant. | Shared | | Secondary storage | Use a tenant-specific schema, index prefix, or separate backend, depending on the storage type. | Isolated | | Document store | Use a tenant-specific bucket, container, or subpath. The exact convention depends on the cloud provider. | Isolated | ## Architecture ```mermaid graph TD subgraph cluster["Single Orchestration Cluster"] cp["Cluster control plane\nshared"] gw["Gateways\nshared"] subgraph tenantA["Physical Tenant A"] raftA["Primary storage\nRaft group A"] secA["Secondary storage A"] docA["Document store A"] end subgraph tenantB["Physical Tenant B"] raftB["Primary storage\nRaft group B"] secB["Secondary storage B"] docB["Document store B"] end cp --> gw gw --> raftA gw --> raftB cp --> tenantA cp --> tenantB end ``` The diagram shows one Orchestration Cluster boundary with shared control-plane components and tenant-specific execution and storage boundaries. The same isolation extends to authentication and authorization, and web apps. Each Physical Tenant authenticates through its own identity provider, gets its own Operate, Tasklist, and Admin, and its own backup and restore, while Logical Tenants remain available for lightweight subdivision inside each one: ![Two Physical Tenants inside one Orchestration Cluster, each with its own identity provider, web apps, and secondary storage.](./img/physical-tenant-architecture.png) ## API routing Use tenant-scoped routes for tenant-specific requests: - REST: `/physical-tenants/{physicalTenantId}/v2/...` - gRPC: `Camunda-Physical-Tenant` header (routes to `default` when omitted) - Default tenant compatibility: plain `/v2/...` requests route to the default Physical Tenant Cluster-wide management endpoints use a dedicated `/cluster/v2/...` path prefix and require the cluster-admin role, except `GET /cluster/v2/status`, which is deliberately unauthenticated so load balancers can use it as a health check. Tenant-scoped endpoints use `/physical-tenants/{physicalTenantId}/v2/...`; endpoints at the standard `/v2/...` paths, including `/v2/topology`, are scoped to the default Physical Tenant. See [cluster admin](/components/admin/cluster-admin.md) for the operations served under this prefix. ## Day-2 operations To configure tenant defaults, per-tenant overrides, validation expectations, and property examples, see [configuration reference](./configuration-reference.md). To provision new tenants and understand lifecycle behavior in 8.10, including rolling restart expectations and unsupported operations, see [provisioning and lifecycle](./provisioning-and-lifecycle.md). Learn how Operate, Tasklist, and Optimize behave per Physical Tenant, including URL navigation, data scoping, and session behavior, in [web app routing](./api-routing.md#webapp-routing). To size broker memory, secondary storage, and noisy-neighbor protection as you add tenants, see [size clusters with Physical Tenants](/components/best-practices/architecture/sizing-physical-tenants.md). For post-deployment operations, see [back up and restore](/self-managed/operational-guides/backup-restore/backup-and-restore.md#multiple-physical-tenants) and [cluster scaling](/self-managed/components/orchestration-cluster/zeebe/operations/cluster-scaling.md#scale-a-cluster-with-multiple-physical-tenants). To serve several Physical Tenants from one App Integrations deployment, including per-tenant audiences and notification routing for Microsoft Teams, see [App Integrations](./app-integrations.md). ## Camunda Spring Boot Starter applications with multiple clients When you configure multiple clients in a [Camunda Spring Boot Starter application](/apis-tools/camunda-spring-boot-starter/getting-started.md), the starter registers every `@JobWorker` against all configured clients and deploys every `@Deployment` resource to all configured clients. Workers can therefore poll and process jobs across multiple Physical Tenants, and the same BPMN resources can be deployed to each tenant. See [Physical Tenant behavior for job workers](/apis-tools/camunda-spring-boot-starter/configuration.md#physical-tenant-fan-out-for-multi-client-applications) and [deployment behavior for multi-client applications](/apis-tools/camunda-spring-boot-starter/configuration.md#deploy-resources-on-start-up). ## Optimize deployment Deploy Optimize separately for each Physical Tenant, as its own release, and point each instance at that tenant's exported records. For how to deploy Optimize per Physical Tenant and share one Management Identity across them, see [Optimize and Physical Tenants](./optimize.md). ## What is not isolated - Gateways are shared between tenants, so a saturated gateway can still affect multiple tenants. - Brokers are co-located and shared infrastructure remains part of the deployment. - Full performance isolation is out of scope for the first version. - Future versions may reduce sharing further, for example through more isolated actor-thread or runtime placement, but that is not part of 8.10. ## Storage validation Camunda validates storage configuration at startup. If two tenants resolve to the same backend location, startup fails and the error names the conflicting tenants. For document stores, uniqueness is validated against the resolved provider, bucket or container, and path tuple. ## Health and status endpoints Physical Tenants expose three distinct endpoints for health and status: | Endpoint | Scope | Use when | | :----------------------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `/actuator/health` | Node | Checking whether the individual broker or gateway node is healthy, ready, or live (for example, Kubernetes probes). Exposed on port 9600 by default (brokers and gateways); the other endpoints below are exposed on the Gateway REST port (8080 by default). | | `/cluster/v2/status` | Cluster | Determining whether the cluster as a whole is operational. | | `/physical-tenants/{id}/v2/topology` | Tenant | Checking whether a specific Physical Tenant can accept work and which of its partitions are available. | `/physical-tenants/{id}/v2/topology` is the tenant-prefixed form of `/v2/topology`: the same endpoint, reached through the tenant prefix. An unprefixed `/v2/topology` request returns the `default` tenant's topology, not a cluster-wide view. For the cluster-wide aggregate, use `/cluster/v2/topology`. The `/v2/status` endpoint is scoped to the default Physical Tenant. Use `/cluster/v2/status` for overall cluster status or `/physical-tenants/{id}/v2/topology` for per-tenant status. ## Readiness When configuring Kubernetes readiness probes, point the probe at `/actuator/health/readiness` for node-level readiness. To check whether a specific Physical Tenant can accept work independently of the node probe, poll `/physical-tenants/{id}/v2/topology` from your own health-check logic. For Elasticsearch and OpenSearch deployments, the secondary-storage readiness check uses schema-initialization state. The check is `UP` while at least one Physical Tenant is serviceable and `DOWN` when none are serviceable, but the overall readiness group can still be `DOWN` because of other readiness contributors. If one tenant's secondary storage is unusable, that tenant is degraded on its own: its storage-dependent REST endpoints return `503` with a `Retry-After` header while every other serviceable tenant continues to serve traffic. Camunda retries the degraded tenant in the background, so it recovers without a restart once you repair the underlying cause. To see each tenant's schema-initialization state and the reason a tenant is degraded, inspect the `physicalTenantSchemaInitialization` contributor of `/actuator/health`. See [schema-initialization health](./storage-isolation.md#schema-initialization-health). For diagnosis steps, see [troubleshooting](./troubleshooting.md). ## Document store details Document stores are declared once in the root `camunda.document.*` catalog. Each Physical Tenant inherits the catalog and overrides only the fields it needs, typically the bucket path or prefix, to ensure its data is written to a distinct location. Isolation is enforced by validating the resolved `provider, bucket/container, path` tuple at startup. If two tenants resolve to the same tuple, Camunda fails startup and names the conflicting tenants in the error. For configuration examples covering shared buckets with per-tenant paths, dedicated buckets per tenant, and GCP prefix isolation, see [document store storage](./storage-isolation.md#document-store-storage). For the storage backends used by tenant-scoped data, see [secondary storage](../secondary-storage/index.md) and [document handling configuration](../document-handling/configuration/index.md). ## Deploying Physical Tenants with Helm The Helm chart passes tenant configuration through rather than modeling it: there's no `orchestration.physicalTenants` values key, and tenants are declared as `camunda.physical-tenants.*` application configuration through `orchestration.extraConfiguration`. See [Helm and application configuration responsibilities](/self-managed/deployment/helm/configure/configuration-responsibilities.md). What the chart does own is the release shape around your tenants. Each tenant needs its own Optimize release, its own index prefixes, and its own OIDC client, and adding or removing a tenant is an ordered operation across several releases. For the release-level view, see [configure Physical Tenants across releases](/self-managed/deployment/helm/install/topology/physical-tenants.md). For the delivery mechanics alone, see [configure Physical Tenants in Helm chart](/self-managed/deployment/helm/configure/configure-physical-tenants.md). ## Explore the docs --- ## Optimize and Physical Tenants Deploy a separate Optimize instance for each Physical Tenant, and share one Management Identity across all of them. With distinct Optimize audiences and roles, each Optimize instance authorizes access to its own data. If you also use logical tenants inside those instances, avoid the logical-tenant-ID collision described below. :::note This page assumes familiarity with the [Physical Tenant isolation model](./index.md) and the identity deployment models in [authentication and authorization](./authentication-authorization.md). ::: ## Deploy Optimize per Physical Tenant Native multi-tenant Helm support does not manage multiple Optimize instances for you. Deploy Optimize separately for each Physical Tenant, as its own release with `global.topology.mode: optimize`, and point it at that tenant's exported records. Optimize doesn't connect to the Orchestration Cluster's `/physical-tenants/{physicalTenantId}` endpoints. It imports records from Elasticsearch or OpenSearch, so its reader prefix must exactly equal the prefix of the exporter configured for that tenant. Each Optimize instance imports records from, and serves data for, exactly one Physical Tenant. See [install an Optimize release](/self-managed/deployment/helm/install/topology/optimize-release.md) for the release-level requirements and a complete `optimize-values.yaml` example. The Hub release provisions the Management Identity side of this for you: declare each Physical Tenant's Optimize instance under `global.topology.clusters[].physicalTenants[]` in the Hub release, and the chart registers a distinct OAuth2 client and resource server for it. See [configure Physical Tenants across releases](/self-managed/deployment/helm/install/topology/physical-tenants.md) for how this maps across the Hub, Orchestration Cluster, and Optimize releases, including index prefix isolation and release ordering. ```yaml global: topology: clusters: - id: production-a # ... physicalTenants: - id: tenanta components: optimize: enabled: true clientId: optimize-production-a-tenanta audience: optimize-production-a-tenanta-api roleName: "Optimize Tenant A" redirectUrl: https://production-a.example.com/optimize-tenanta secret: existingSecret: optimize-production-a-tenanta-oidc existingSecretKey: client-secret ``` ## How one Management Identity isolates multiple Optimize deployments You do not need a separate Management Identity per Physical Tenant's Optimize instance. A single Management Identity instance can serve every Optimize deployment, provided each one is configured with its own `audience` and its own `roleName`, as shown above. Isolation between Optimize instances is enforced by the role grant, not by the audience alone: - Every Optimize instance also accepts a cluster-wide shared audience (the Hub or Web Modeler client audience), so a user's Hub session token can authenticate against any Optimize instance in the deployment. This is intentional. It lets Hub's business value dashboard call Optimize's API on the user's behalf. Authentication alone does not grant access to Optimize data. - Management Identity stores permissions per audience. When an Optimize instance checks whether the current user may access it, it asks Management Identity for the permissions granted against its own configured audience, not the audience the token happened to authenticate with. - A user only sees data from an Optimize instance if they hold a role that grants `write:*` on that instance's specific audience. Holding the shared Hub audience in a token is not sufficient on its own. :::warning Set a distinct `roleName` on every Optimize entry (cluster-level and per Physical Tenant) if you want isolated access between them. Every declared Optimize instance contributes its audience to the canonical `Optimize` role by default, and assigning that role grants access to every one of those instances at once. Set `components.optimize.roleName` to a unique value per instance when users must be authorized per Physical Tenant. ::: ## Known limitation: logical tenants with the same ID across Physical Tenants [Logical tenants](/self-managed/components/optimize/configuration/multi-tenancy.md) remain available inside a Physical Tenant's Optimize instance, so a team can still subdivide its own workload by tenant ID. This is independent of Physical Tenant isolation, and it has a gap when Management Identity is shared: Management Identity's tenant-authorization lookup has no concept of Physical Tenant. It returns a flat set of logical tenant IDs the user is assigned to, without saying which Physical Tenant's Optimize instance that assignment was meant for. If the same logical tenant ID exists in two different Physical Tenants (for example, both tenant A and tenant B define a logical tenant called `b`), and a user: 1. is assigned that logical tenant ID in Management Identity, and 2. holds the Optimize role for both Physical Tenants' Optimize instances (see the role grant above), that user sees the logical tenant's data in both Optimize instances, even though the underlying data belongs to two different, and otherwise isolated, Physical Tenants. Neither Optimize instance can tell that the two identically-named logical tenants are unrelated. This only applies to a user who is already independently granted the Optimize role on both Physical Tenants' Optimize instances. A user granted the Optimize role for only one Physical Tenant's Optimize instance never reaches the other one, regardless of logical tenant assignment. ### How to avoid this Choose one of the following when running several Physical Tenants' Optimize instances behind one shared Management Identity: - Use unique logical tenant IDs across every Physical Tenant. This is the simplest option and removes the collision entirely. Camunda does not validate logical tenant ID uniqueness across Physical Tenants, so this is a naming convention you enforce yourself. - Avoid granting the Optimize role for more than one Physical Tenant's Optimize instance to the same user, if you do reuse logical tenant IDs across tenants and need to keep them isolated. - Run a separate Management Identity per Physical Tenant if you need both identical logical tenant IDs across tenants and independent enforcement of them. This trades the simpler single-Identity setup described above for full per-tenant identity isolation. ## Related pages - [Physical Tenant isolation model](./index.md) - [Authentication and authorization](./authentication-authorization.md) - [Optimize multi-tenancy (logical tenants)](/self-managed/components/optimize/configuration/multi-tenancy.md) - [Install an Optimize release](/self-managed/deployment/helm/install/topology/optimize-release.md) - [Configure Physical Tenants across releases](/self-managed/deployment/helm/install/topology/physical-tenants.md) --- ## Provisioning and lifecycle ## Provisioning model Physical Tenants are provisioned through static application configuration. - Add or change tenant configuration in application config. - Apply the change with a rolling restart. - Validate startup status for every affected component. Dynamic runtime tenant creation and runtime tenant updates are not available. ## Add a new Physical Tenant To add a tenant: 1. Add a new `camunda.physical-tenants.` section in configuration. 2. Define the tenant-specific initialization and required assignments. 3. Ensure required storage and identity configuration is valid. 4. Apply the change through a rolling restart. You can add multiple new Physical Tenants in the same configuration change and rolling restart. You do not need to add them one at a time. ### Rolling restart expectations Existing Physical Tenants keep running during a rolling restart to add a new tenant. Any interference they experience is the normal interference of a rolling restart itself, not something caused specifically by the new tenant's addition. During a rolling restart for tenant provisioning: - Existing tenants continue processing requests throughout the restart, subject to your normal rollout strategy. - New tenant availability starts after updated components are running and ready. - Startup validation failures block readiness for affected components. ## Default tenant lifecycle The default Physical Tenant is always present and immutable: - You cannot delete the default tenant. - You cannot rename the default tenant. - You cannot disable the default tenant. If tenant scope is omitted in compatibility paths, requests resolve to the default tenant. ## Disable, rename, and delete - Disabling and re-enabling a Physical Tenant is supported through configuration. There is no dedicated API for this operation. - Renaming a Physical Tenant is not supported. - Deleting a Physical Tenant is not supported. No single API removes a tenant's configuration and its data together. [Purging](/self-managed/operational-guides/data-purge.md) removes the data of one tenant when you pass `physicalTenant={physicalTenantId}`, and the data of every tenant when you omit it. Removing a tenant from configuration disables it without deleting persisted data. These are separate operations. A Physical Tenant's enabled state follows its configuration directly: - **Present in configuration:** The tenant is enabled. - **Removed from configuration:** The tenant is disabled. The cluster stops processing requests for that tenant, and the API returns `404 Not Found` for requests scoped to it. No data is deleted. - **Re-added to configuration:** The tenant is re-enabled with its existing data. Nothing needs to be re-created. Each of these transitions takes effect through the same rolling restart used for any other configuration change. ### Logically remove a disabled tenant A disabled tenant still appears in the persisted cluster topology, which blocks operations that require every tenant to be accounted for, such as multi-region failover. The Cluster API logically removes a tenant that you have already removed from configuration with `DELETE /actuator/cluster/physical-tenants/{physicalTenantId}`. It drops the tenant from the cluster topology and **deletes no data**. This is not a delete API, and it is not a way to reclaim storage. To delete a tenant's data, take these steps in order: 1. Purge the tenant with `POST /actuator/cluster/purge?physicalTenant={physicalTenantId}`. Always include the `physicalTenant` parameter. Without it, the purge removes the data of every Physical Tenant. See [data purge](/self-managed/operational-guides/data-purge.md). 1. Wait until the purge completes. The purge is asynchronous: poll `GET /actuator/cluster` until `lastChange.id` reaches the `changeId` the purge returned, as shown in [data purge](/self-managed/operational-guides/data-purge.md). The cluster rejects another configuration change while one is in progress. 1. Remove the tenant from configuration and apply the change with a rolling restart. 1. Logically remove the tenant with the endpoint above. ## Out of scope The following capabilities are out of scope: - Dynamic tenant creation without restart - Tenant deletion - Runtime tenant updates ## Upgrade behavior from 8.9 For single-tenant 8.9 clusters upgrading to 8.10: - Existing root-level configuration becomes the `default` Physical Tenant behavior. - No explicit migration step is required for this default mapping. ## Operational guidance Before applying provisioning changes: - Validate tenant IDs and property paths. - Validate identity provider assignments. - Validate storage isolation settings per tenant. - Plan and execute a rolling restart window. After rollout: - Verify tenant-scoped APIs route to expected tenant context. - Verify storage isolation and startup health. - Verify authentication behavior for assigned providers. :::note Related pages - [Configuration reference](./configuration-reference.md) - [Physical Tenant isolation model](./index.md) - [Backup and restore](../../operational-guides/backup-restore/zeebe-backup-and-restore.md) ::: --- ## Storage isolation ## About Learn how to configure isolated secondary storage for Physical Tenants across RDBMS, Elasticsearch/OpenSearch, and Document Store backends. ## RDBMS storage Each Physical Tenant can have its own schema or database instance. ### Configuration models **Separate schema** (recommended for cost-efficiency): ```yaml camunda: physical-tenants: default: data: secondary-storage: rdbms: url: jdbc:postgresql://db.example.com:5432/camunda?currentSchema=default_schema tenanta: data: secondary-storage: rdbms: url: jdbc:postgresql://db.example.com:5432/camunda?currentSchema=tenant_a_schema # The 'default_schema' and 'tenant_a_schema' schemas must exist before startup ``` **Separate database instance** (maximum isolation): ```yaml tenanta: data: secondary-storage: rdbms: url: jdbc:postgresql://db-tenant-a.example.com:5432/camunda ``` **Mixed vendors**: Different Physical Tenants can use PostgreSQL, MySQL, Oracle, etc. in the same cluster. ### Validation and operations - **Configuration**: Misconfiguration (duplicate schema/URL) causes a startup error with a clear message. For Oracle, schema isolation uses distinct authenticated users rather than URL differences, so set `database-vendor-id: oracle` on each tenant to avoid a false conflict on identical URLs. - **Pre-startup**: Ensure each tenant's schema exists, is empty, and has valid credentials - **Manual DDL**: If running Liquibase scripts separately, apply to every tenant's schema before each upgrade - **Resource scaling**: Each tenant gets its own JDBC datasource per cluster node; add memory/CPU for many tenants :::note RDBMS table prefixes are normalized to uppercase Camunda converts RDBMS table prefixes to uppercase before applying them, so `tenanta_` and `TENANTA_` produce the same schema objects. Two consequences follow: - Prefixes that differ only in case resolve to the same storage location. Configuring `ta_` for one tenant and `TA_` for another fails the startup uniqueness check. - Prefixes that are invalid SQL identifiers for other reasons, such as hyphens, spaces, or a leading digit, are still accepted at configuration time and fail later during schema migration. Earlier 8.10 alpha releases carried the prefix through verbatim, which failed the Liquibase migration at startup for a lowercase prefix. See [camunda/camunda#56093](https://github.com/camunda/camunda/issues/56093). ::: :::note Isolate Oracle tenants by schema-per-user Oracle isolates Physical Tenants by distinct authenticated database users rather than by differing JDBC URLs, so two Oracle tenants can share one URL and still be isolated. Because the URLs are identical, startup validation reports a storage-location conflict unless you tell Camunda the vendor explicitly. Set `data.secondary-storage.rdbms.database-vendor-id: oracle` on each tenant. The startup error includes this hint. ::: ## Elasticsearch and OpenSearch storage Each Physical Tenant can use a shared Elasticsearch or OpenSearch cluster with isolated index prefixes, or a dedicated cluster per tenant. ### Naming and collision prevention Startup validation fails only when two tenants resolve to an identical index prefix. Overlapping prefixes are not detected, so `eu` and `eu-west` both pass validation even though `eu*` matches both tenants' indices. Use the full tenant ID as the prefix, and make sure no tenant's prefix is the leading substring of another tenant's prefix. ### Configuration models **Shared cluster with index prefix isolation** (recommended for cost-efficiency): ```yaml camunda: data: secondary-storage: type: elasticsearch # or opensearch elasticsearch: url: https://es.example.com:9200 index-prefix: default physical-tenants: tenanta: data: secondary-storage: elasticsearch: index-prefix: tenanta # must be unique per tenant tenantb: data: secondary-storage: elasticsearch: index-prefix: tenantb ``` **Separate cluster per tenant** (maximum isolation): ```yaml camunda: data: secondary-storage: type: elasticsearch elasticsearch: url: https://es-default.example.com:9200 index-prefix: default physical-tenants: tenanta: data: secondary-storage: elasticsearch: url: https://es-tenanta.example.com:9200 index-prefix: tenanta ``` For AWS-hosted OpenSearch Service, including authentication with AWS credentials, see [Amazon OpenSearch Service storage](#amazon-opensearch-service-storage) below. ## Amazon OpenSearch Service storage When secondary storage runs on Amazon OpenSearch Service, each physical tenant can authenticate with its own credentials. The connection settings `url`, `username`, `password`, and `index-prefix` are all overridable per physical tenant, supporting either a shared OpenSearch instance or a dedicated OpenSearch instance per tenant. ### Basic authentication with fine-grained access control Enable [fine-grained access control](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/fgac.html) (FGAC) with the internal user database on the domain, then create one internal user per tenant and map it to an OpenSearch security role whose index permissions are restricted to that tenant's index pattern. All permission administration happens on the AWS side; Camunda only supplies the per-tenant credentials: ```yaml camunda: data: secondary-storage: type: opensearch opensearch: url: https://my-domain.eu-central-1.es.amazonaws.com username: camunda-default password: default-secret index-prefix: default physical-tenants: tenanta: data: secondary-storage: opensearch: username: tenant-a-user password: tenant-a-secret index-prefix: tenant-a ``` A tenant can also point at a dedicated OpenSearch instance (including one in a different AWS account) by overriding `url` as well: ```yaml camunda: physical-tenants: tenanta: data: secondary-storage: opensearch: url: https://tenant-a-domain.eu-central-1.es.amazonaws.com username: tenant-a-user password: tenant-a-secret index-prefix: tenant-a ``` ### IAM authentication (request signing) Alternatively, set `aws-enabled: true` to sign requests with AWS Signature Version 4 instead of Basic authentication. Credentials are resolved from the AWS SDK default provider chain. On EKS this is the pod's IAM role via IRSA, and the region is taken from the environment (for example, `AWS_REGION`): ```yaml camunda: data: secondary-storage: type: opensearch opensearch: url: https://my-domain.eu-central-1.es.amazonaws.com aws-enabled: true ``` With request signing, the AWS identity is also the principal that OpenSearch authorizes: all physical tenants share the pod's single IAM role, and tenant separation is provided by per-tenant index prefixes together with the instance's access policy or FGAC role mapping for that role. Per-tenant IAM identities are not supported; if tenants require authentication isolation from each other, use fine-grained access control with per-tenant internal users as described above. IAM authentication also works with a dedicated OpenSearch instance per tenant. Camunda creates a separate client per tenant, each signing requests against its own endpoint with the same pod identity. `aws-enabled` is inherited from the root configuration, so tenants only override their `url`: ```yaml camunda: data: secondary-storage: type: opensearch opensearch: url: https://default-instance.eu-central-1.es.amazonaws.com aws-enabled: true index-prefix: default physical-tenants: tenanta: data: secondary-storage: opensearch: url: https://tenant-a-instance.eu-central-1.es.amazonaws.com index-prefix: tenant-a tenantb: data: secondary-storage: opensearch: url: https://tenant-b-instance.eu-central-1.es.amazonaws.com index-prefix: tenant-b ``` For this to authenticate correctly, two conditions must hold: - **Every instance must authorize the pod's IAM role**: attach an IAM policy to the role allowing `es:ESHttp*` on each instance's ARN, and allow the role in each instance's access policy (or map the role ARN in its fine-grained access control configuration). This also works for an instance in a different AWS account, granted through that instance's resource-based access policy. The signing identity is still the single pod role. - **All instances must be in the same AWS region as the pod**: the request signature is scoped to the region resolved from the pod's environment (for example `AWS_REGION`), not derived from each endpoint. An instance in a different region rejects the signature with an authentication error. ## Document Store storage Store documents globally with per-tenant subpaths, or use dedicated stores per tenant. Camunda validates the resulting layout at startup and refuses to start if two tenants would read and write into the same storage. | Layout | Use when | Tenant configuration | | ------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | Shared store with sibling paths | You want lower operational overhead while preserving structural isolation. | Assign the same store and configure a distinct sibling path or prefix for each tenant. | | Dedicated store per tenant | You need the strongest operational separation. | Assign each tenant a separate bucket, container, or directory. | ### Configuration models **What Camunda compares.** Three properties. `bucket-name` and `endpoint` form the namespace, and `bucket-path` becomes the key prefix. Bucket names are compared case-insensitively, endpoints by scheme, host, port, and path only, and bucket paths case-sensitively after being coerced to end in `/`. Every other AWS property is ignored: `region`, because S3 bucket names are globally unique across regions, and `bucket-ttl`, `force-path-style`, `chunked-encoding-enabled`, and `support-legacy-md5`, because none of them change which objects a store reads and writes. **Global store with per-tenant subpaths** (recommended): ```yaml camunda: document: default-store-id: shared-s3 aws: shared-s3: bucket-name: "camunda-documents" physical-tenants: default: document: assigned: [shared-s3] aws: shared-s3: bucket-path: "default" tenanta: document: assigned: [shared-s3] aws: shared-s3: # Sibling paths. Neither tenant's path may be nested inside the other's, # and neither tenant may leave the path unset to use the bucket root. bucket-path: "tenant-a" ``` **Dedicated store per tenant.** Distinct buckets are distinct namespaces, so `bucket-path` is optional here: ```yaml camunda: physical-tenants: default: document: assigned: [default-s3] default-store-id: default-s3 aws: default-s3: bucket-name: "camunda-documents-default" tenanta: document: assigned: [tenant-a-s3] default-store-id: tenant-a-s3 aws: tenant-a-s3: bucket-name: "camunda-documents-tenant-a" ``` **Startup outcomes.** | Tenant A | Tenant B | Outcome | | --------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------ | | `bucket-path: tenant-a` | `bucket-path: tenant-b` | Accepted. Sibling prefixes. | | `bucket-path` unset | `bucket-path: tenant-b` | Rejected. The bucket root is a prefix of every key in the bucket. | | `bucket-path: tenant-a` | `bucket-path: tenant-a/nested` | Rejected. One prefix is nested inside the other. | | `bucket-path: tenant` | `bucket-path: tenant-b-` | Accepted. Both are coerced to end in `/`, so neither encloses the other. | | `bucket-path: Tenant-A/` | `bucket-path: tenant-a/` | Accepted. S3 keys are case-sensitive. | | `region: us-east-1` | `region: eu-west-1`, rest identical | Rejected. Region isn't part of the location. | | `endpoint: https://minio-a` | `endpoint: https://minio-b`, same bucket and path | Accepted. Different endpoints are different namespaces. | | `endpoint: https://MINIO/` | `endpoint: https://minio`, same bucket and path | Rejected. Host case and trailing slashes are ignored. | **What Camunda compares.** `bucket-name` forms the namespace, and `prefix` becomes the key prefix. Bucket names are compared case-insensitively, prefixes case-sensitively. Unlike AWS and Azure, the GCP prefix is used exactly as written. No trailing separator is appended, so a prefix is not necessarily a folder. An unset `prefix` resolves to `temp/`. **Global store with per-tenant subpaths** (recommended): ```yaml camunda: document: default-store-id: shared-gcs gcp: shared-gcs: bucket-name: "camunda-documents" physical-tenants: default: document: assigned: [shared-gcs] gcp: shared-gcs: prefix: "default/" tenanta: document: assigned: [shared-gcs] gcp: shared-gcs: # Sibling prefixes. Because no separator is appended, end each prefix in '/' # yourself so one tenant's prefix can't run into another's. prefix: "tenant-a/" ``` **Dedicated store per tenant.** Distinct buckets are distinct namespaces, so `prefix` is optional here: ```yaml camunda: physical-tenants: default: document: assigned: [default-gcs] default-store-id: default-gcs gcp: default-gcs: bucket-name: "camunda-documents-default" tenanta: document: assigned: [tenant-a-gcs] default-store-id: tenant-a-gcs gcp: tenant-a-gcs: bucket-name: "camunda-documents-tenant-a" ``` **Startup outcomes.** | Tenant A | Tenant B | Outcome | | ------------------ | ----------------------------- | --------------------------------------------------------------------------- | | `prefix: a/` | `prefix: b/` | Accepted. Sibling prefixes. | | `prefix: docs/` | `prefix: docs/archive/` | Rejected. One prefix is nested inside the other. | | `prefix: tenant` | `prefix: tenant-b-` | Rejected. No separator is appended, so `tenant` is a prefix of `tenant-b-`. | | `prefix` unset | `prefix: temp/` | Rejected. An unset prefix resolves to `temp/`. | | `prefix` unset | `prefix: temp` | Rejected. Both address keys under `temp`. | | `prefix: ""` | `prefix: tenant-b-` | Rejected. The bucket root is a prefix of every object name in it. | | `prefix: TenantA/` | `prefix: tenanta/` | Accepted. GCS object names are case-sensitive. | | `bucket-name: a` | `bucket-name: b`, same prefix | Accepted. Different buckets are different namespaces. | **What Camunda compares.** `container-name` and the blob endpoint the store resolves to form the namespace, and `container-path` becomes the key prefix. Container names are compared case-insensitively, container paths case-sensitively after being coerced to end in `/`. A `connection-string` is resolved to the endpoint the store actually uses and reduced to scheme, host, port, and path. A query, fragment, or user info is dropped, so a shared access signature (SAS) token is neither part of the location nor printed in the error message. **Global store with per-tenant subpaths** (recommended): ```yaml camunda: document: default-store-id: shared-blob azure: shared-blob: container-name: "camunda-documents" physical-tenants: default: document: assigned: [shared-blob] azure: shared-blob: container-path: "default" tenanta: document: assigned: [shared-blob] azure: shared-blob: # Sibling paths. Neither tenant may leave the path unset to use the # container root, which encloses every blob name in the container. container-path: "tenant-a" ``` **Dedicated store per tenant.** Distinct containers are distinct namespaces, so `container-path` is optional here: ```yaml camunda: physical-tenants: default: document: assigned: [default-blob] default-store-id: default-blob azure: default-blob: container-name: "camunda-documents-default" tenanta: document: assigned: [tenant-a-blob] default-store-id: tenant-a-blob azure: tenant-a-blob: container-name: "camunda-documents-tenant-a" ``` **Startup outcomes.** | Tenant A | Tenant B | Outcome | | ------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------- | | `container-name: docs-a` | `container-name: docs-b` | Accepted. Different containers are different namespaces. | | `container-path: a` | `container-path: a/nested` | Rejected. One prefix is nested inside the other. | | Container root | `container-path: tenant-c`, same container | Rejected. The container root is a prefix of every other key. | | `connection-string` for `accta` | `connection-string` for `acctb`, same container and path | Accepted. The two connection strings resolve to different endpoints. | | `connection-string` for `acct` | `endpoint: https://acct.blob.core.windows.net` | Rejected. One account reached two ways. | | `UseDevelopmentStorage=true` | `endpoint: http://127.0.0.1:10000/devstoreaccount1` | Rejected. The emulator shorthand resolves to that endpoint. | | `endpoint: …?sig=A` | `endpoint: …?sig=B`, same account | Rejected. A SAS token is a credential, not a location. | | `container-path: Tenant-A/` | `container-path: tenant-a/` | Accepted. Blob names are case-sensitive. | **What Camunda compares.** Nothing. In-memory stores are ephemeral and process-local, so they can't collide in backing storage and are excluded from the check. Declare one store per tenant if you want documents kept apart within the process, or share one store ID across tenants if you don't. Either way there's no cross-tenant validation to satisfy. ```yaml camunda: physical-tenants: default: document: assigned: [scratch] default-store-id: scratch in-memory: scratch: {} tenanta: document: assigned: [scratch] default-store-id: scratch in-memory: scratch: {} ``` :::warning In-memory stores provide no isolation guarantee and lose every document when the process stops. Use them for local development only, never to separate tenants in production. ::: **What Camunda compares.** The configured `path` forms the namespace, with separators resolved per platform. The key prefix is always empty because the directory alone decides. Paths are compared case-insensitively on every platform because a case-insensitive filesystem makes `/var/Docs` and `/var/docs` one directory. Local stores have no subpath field, so each tenant needs its own directory: ```yaml camunda: physical-tenants: default: document: assigned: [shared-local] default-store-id: shared-local local: shared-local: path: "/var/camunda/documents/default" tenanta: document: assigned: [shared-local] default-store-id: shared-local local: shared-local: # Use a sibling directory rather than one nested under another tenant's path. path: "/var/camunda/documents/tenant-a" ``` **Startup outcomes.** | Tenant A | Tenant B | Outcome | | ----------------- | -------------------------- | ----------------------------------------------------------------------------------------------------- | | `path: /var/docs` | `path: /var/other` | Accepted. Different directories. | | `path: /var/docs` | `path: /var/docs/` | Rejected. Trailing separators aren't part of the location. | | `path: /var/Docs` | `path: /var/docs` | Rejected. Paths are compared case-insensitively on every platform. | | `path: \var\docs` | `path: /var/docs` | Rejected on Windows, accepted on Linux. Separators are resolved per platform. | | `path: /var/docs` | `path: /var/docs/tenant-b` | Accepted, though not recommended. Nesting isn't compared for local stores. See the limitations below. | ### Combine providers and per-tenant overrides A tenant's `assigned` stores don't have to share a provider, and a global store can be combined with a tenant-specific one. Every store still has to satisfy the comparison rules for its own provider. **Hybrid:** A global default store plus a tenant-specific store. ```yaml camunda: document: aws: default-s3: bucket-name: "camunda-documents-default" physical-tenants: default: document: assigned: [default-s3] default-store-id: default-s3 aws: default-s3: # Required. Without a path, this tenant owns the bucket root, which # encloses every other tenant's path in the same bucket. bucket-path: "default" tenanta: document: assigned: [default-s3, tenant-a-compliance] default-store-id: tenant-a-compliance aws: tenant-a-compliance: bucket-name: "camunda-documents-tenant-a-compliance" default-s3: bucket-path: "tenant-a" ``` **Mixed providers:** A shared GCP store for both tenants, plus an Azure store for one of them. ```yaml camunda: document: gcp: default-gcs: bucket-name: "camunda-documents-default" physical-tenants: default: document: assigned: [default-gcs] default-store-id: default-gcs gcp: default-gcs: prefix: "default/" tenanta: document: assigned: [default-gcs, tenant-a-blob] default-store-id: tenant-a-blob gcp: default-gcs: prefix: "tenant-a/" azure: tenant-a-blob: container-name: "camunda-documents-tenant-a" ``` Overlap is only ever reported between two different tenants. One tenant may spread its documents across several stores whose prefixes overlap, because reaching its own documents isn't a leak. ### Availability and validation - **At startup**: Warning if bucket is missing or credentials are invalid; cluster continues - **At runtime**: An error is returned when a tenant tries to create/retrieve a document if the store is unavailable - **Validation**: The cluster fails to start if two Physical Tenants resolve to overlapping document store locations. See [Compare document store locations across tenants](#compare-document-store-locations-across-tenants) - **Subpath structure**: Each tenant writes to the prefix you configure, such as `bucket-path` for AWS. Camunda doesn't insert the tenant ID into the path for you ### Compare document store locations across tenants Camunda resolves a location for every configured document store at startup, then compares the locations of all tenants. A location is the provider, a namespace, and a key prefix: - The **namespace** is the container no key can escape. It can be a bucket, a blob container, or a directory. - The **key prefix** is the string every key inside that namespace starts with. Two tenants overlap when the provider and namespace match and one key prefix is a prefix of the other. Overlap is broader than equality because a document ID is caller-supplied and appended to the key prefix as given. With the prefixes `tenant` and `tenant-b-` in one bucket, a request against the first store for the document ID `-b-invoice` resolves to the second store's `tenant-b-invoice`. A separator changes nothing: `docs/` reaches `docs/archive/` through the document ID `archive/invoice`, because no object storage service treats `/` in a key as a path boundary. Any prefix nested inside another tenant's prefix is therefore rejected, including a bucket or container root paired with a path inside it. Give every tenant that shares a bucket or container its own sibling prefix. No layout lets one tenant own the root while another owns a path within it, and isolation is enforced by this check at startup rather than by inspecting document IDs at runtime. When the check fails, the cluster doesn't start, and the error names each conflict: ``` Physical tenants must not share a document store location, or they would read and write into the same backing storage. Use a distinct bucket, container, or path per tenant, and never nest one tenant's path inside another's. A nested path is reachable through a caller-supplied document id, which no object store bounds at '/'. Conflicts: tenant default's document store location [provider=aws, namespace=[camunda-documents, ], keyPrefix=''] encloses tenant tenanta's [provider=aws, namespace=[camunda-documents, ], keyPrefix='tenant-a/'] ``` #### Limitations of location comparison | Limitation | Effect | | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Aliases aren't resolved | Two endpoint URLs or DNS names fronting the same backend are treated as separate locations, so a genuine overlap isn't detected. | | Local paths are compared case-insensitively on every platform | On a case-sensitive filesystem, two directories differing only in case are reported as a collision even though they're isolated. | | Nested local directories aren't compared | `path: /var/docs` and `path: /var/docs/tenant-b` are two namespaces with empty prefixes. The local store rejects `/`, `\`, and `..` in a document ID, so the parent can't descend into the child. | ## Operational considerations To size database connections, instances, and Elasticsearch or OpenSearch capacity as you add tenants, see [size clusters with Physical Tenants](/components/best-practices/architecture/sizing-physical-tenants.md#size-secondary-storage). For the backup, restore, and scaling procedures that use these storage locations, see [back up and restore](/self-managed/operational-guides/backup-restore/backup-and-restore.md#multiple-physical-tenants) and [cluster scaling](/self-managed/components/orchestration-cluster/zeebe/operations/cluster-scaling.md#scale-a-cluster-with-multiple-physical-tenants). ### Backup and restore - **Per-tenant**: Trigger runtime and history backups through the tenant-scoped endpoints. Back up RDBMS schemas and document stores with the storage system's tools. - **Full cluster**: Back up all schemas, all index prefixes, all buckets simultaneously - **Restore options**: Individual tenant or full cluster from backup Example: back up Tenant A only. ```bash # RDBMS pg_dump -h db.example.com -U user tenant_a_schema > backup.sql # Document store (S3) aws s3 sync s3://camunda-documents/tenant-a/ ./backup/ ``` For tenant-scoped and cluster-wide backup and restore endpoints, see [back up a cluster with multiple Physical Tenants](/self-managed/operational-guides/backup-restore/backup-and-restore.md#multiple-physical-tenants). ### Cross-tenant isolation Storage isolation prevents data leakage structurally: - RDBMS: Each schema is logically separate - ES/OS: Index prefixes are distinct boundaries - Document Store: Separate buckets or subpaths Risks to avoid: - Don't share JDBC connection URLs between tenants (for Oracle, two tenants can share the same URL while remaining isolated by distinct authenticated database users) - Don't overlap index prefixes - Don't point two tenants to the same bucket or container without distinct sibling subpaths. Don't nest one tenant's subpath inside another's, and don't leave one tenant on the bucket or container root ### Secondary storage failures during startup and runtime Camunda initializes the secondary-storage schema for each Physical Tenant independently. A tenant becomes ready only after its schema initialization succeeds. If one tenant cannot initialize, Camunda marks only that tenant as degraded and keeps other tenants independent. #### Startup behavior For multi-tenant initialization, Camunda starts one schema-initialization task per tenant. Retryable failures, such as temporary connectivity problems, are retried according to the schema manager retry settings. With the default settings, schema initialization continues retrying until it succeeds. On a node with multiple Physical Tenants, startup uses the following rules: | Node and storage type | Startup behavior | | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Elasticsearch or OpenSearch with an HTTP gateway | Startup waits until every tenant has produced an initial result. If at least one tenant is serviceable, the node starts serving traffic. A tenant that failed keeps retrying in the background. | | Elasticsearch or OpenSearch without an HTTP gateway | The node does not wait for schema initialization. It starts while each tenant retries in the background. | | RDBMS, with or without an HTTP gateway | Every node waits until at least one tenant is serviceable or no tenant can make further progress. One tenant's failure does not abort the node when another tenant is serviceable. | On nodes that wait at startup, Camunda retries temporary failures before allowing traffic. If every tenant has a failure that retrying cannot fix, startup aborts and the node exits with a non-zero status. An RDBMS node with exactly one Physical Tenant keeps the existing synchronous, fail-fast behavior. An unreachable database or an unrepairable schema failure aborts startup instead of being retried in the background. If you configure a finite retry limit and all attempts stop before any tenant becomes ready, startup can complete but the affected tenant remains degraded. On nodes whose readiness probe includes secondary storage, the node remains not ready. The application logs identify the tenant and report that its retry limit was exhausted. #### Readiness and health Readiness and health answer different questions: | Endpoint or signal | Meaning | | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `/actuator/health/readiness` | Node-level readiness. For Elasticsearch and OpenSearch deployments, the secondary-storage readiness contributor uses schema-initialization state and is `UP` when at least one Physical Tenant is ready and `DOWN` when no tenant is ready. The overall readiness group can still be `DOWN` because of other readiness contributors. It does not mean that every tenant is healthy. | | `/actuator/health` | Full node health, including live secondary-storage checks. On multi-tenant nodes, inspect the per-tenant `rdbmsStatus` or `searchEngineStatus` contributors for storage connectivity, and the `physicalTenantSchemaInitialization` contributor for each tenant's [schema-initialization state](#schema-initialization-health). | | `camunda_physical_tenant_secondary_storage_ready` | Prometheus gauge with `physicalTenant` labels. A value of `1` means that the tenant's schema is initialized; `0` means that the tenant is degraded. | The secondary-storage readiness signal is based on schema initialization and does not continuously probe storage connectivity. As a result, a storage outage after startup does not automatically make a ready node fail its readiness probe. The full health endpoint, logs, and operation-specific errors provide the live storage status. The full `/actuator/health` result can be `DOWN` for one failed tenant even when `/actuator/health/readiness` remains `UP` because another tenant is serviceable. When a tenant is degraded because its schema has not initialized, REST query API requests, that require secondary storage for that tenant, return `HTTP 503 Service Unavailable` and a `Retry-After: 5` header. Other tenants continue to be served. After the storage problem is fixed, a retryable failure recovers in the background without restarting the node. #### Schema-initialization health The `physicalTenantSchemaInitialization` contributor of `/actuator/health` reports the schema-initialization state of every Physical Tenant on the node. Use it to find out which tenant is degraded, whether it recovers on its own, and why it failed. Each tenant reports one of the following states: | Tenant status | State | Meaning | Action | | ------------- | -------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | | `UP` | `INITIALIZED` | The schema is applied and the tenant is serviceable. | None. | | `DEGRADED` | `INITIALIZING` | No attempt has finished yet. | None. | | `DEGRADED` | `RETRYING` | An attempt failed with a retryable error, and another attempt is scheduled. | Fix the reported cause. The tenant recovers on its own. | | `DEGRADED` | `RECOVERING` | Schema initialization is held back while the cluster is in recovery mode, for example during a restore. | None. This isn't a failure. | | `DOWN` | `FAILED` | An attempt failed with an error that retrying can't repair, so no further attempt is made. | Fix the reported cause, then restart the node. | | `DOWN` | `GAVE_UP` | Every configured retry attempt failed, so no further attempt is made. | Fix the reported cause, then restart the node. | | `DOWN` | `ABORTED` | The initialization task couldn't start, or ended outside an attempt. | Check the logs for the tenant, then restart the node. | Unless a tenant is `INITIALIZED`, its entry also includes `failedAttempts`, the number of attempts that failed so far, once an attempt has failed. It includes `error`, the exception class and message of the most recent failure, when there is one. The error is truncated to 256 characters. The application logs contain the full error. This contributor is informational. Camunda keeps it out of the liveness, readiness, and startup groups, so one tenant's state never restarts or removes the node. The contributor is `UP` when every tenant is `UP`, `DOWN` when every tenant is `DOWN`, and `DEGRADED` otherwise. A node that still serves at least one tenant therefore never reports this contributor as `DOWN`, and `/actuator/health` doesn't return `503` because of a single failed tenant's schema. The per-tenant `rdbmsStatus` and `searchEngineStatus` contributors still report `DOWN` while one tenant's storage is unreachable. For example, a node whose `default` tenant is serviceable while `tenanta` failed terminally reports: ```json "physicalTenantSchemaInitialization": { "status": "DEGRADED", "details": { "default": { "status": "UP", "state": "INITIALIZED" }, "tenanta": { "status": "DOWN", "state": "FAILED", "failedAttempts": 1, "error": "io.camunda.search.schema.exceptions.IndexSchemaValidationException: Index names: [tenantaprefix-camunda-role-8.8.0_]. Unsupported index changes have been introduced. Data migration is required. Changes found: [PropertyDifference[name=roleId, ... (see logs)" } } } ``` #### Troubleshoot startup and readiness failures - **The node stays at startup.** Check the application logs for the Physical Tenant named in the schema-initialization messages. Verify the tenant's storage endpoint, credentials, network access, and schema permissions. For Elasticsearch or OpenSearch, also verify that the cluster is at least yellow when the startup health check is enabled. - **Readiness is `DOWN`.** Inspect the `camunda_physical_tenant_secondary_storage_ready` gauge for each tenant. If every tenant reports `0`, no tenant can currently serve secondary-storage-dependent requests. - **One tenant returns `503` while another works.** This is expected partial degradation. Check the tenant's state in the [`physicalTenantSchemaInitialization` contributor](#schema-initialization-health). For `RETRYING`, fix the affected tenant's storage problem and wait for its background initialization retry. No restart is required for a retryable failure. - **An RDBMS tenant fails before schema initialization starts.** If the JDBC URL uses a wrapper or a non-standard format, Camunda might not be able to determine the database vendor without connecting to the database. Set `database-vendor-id` in the tenant's RDBMS configuration. See [RDBMS database configuration](../databases/relational-db/configuration.md). - **A tenant reports `FAILED` or `GAVE_UP`, or the logs report a terminal schema failure.** Fix the problem reported in the tenant's `error` detail or in the logs, then restart the node. These failures aren't retried, so the tenant stays degraded until the node restarts. ### Scaling and capacity planning - **RDBMS**: Monitor schema size per tenant; high-traffic tenants may need dedicated instances - **ES/OS**: Monitor index size per prefix; set retention policies independently per tenant. If ES/OS is shared across multiple Physical Tenants, also monitor overall cluster health and capacity. - **Document Store**: Monitor bucket size per tenant; set lifecycle policies for archival ### Migration scenarios | Scenario | Steps | | ---------------- | ----------------------------------------------------------------------------- | | **Add tenant** | Create storage backend → Validate connectivity → Add config → Rolling restart | | **Consolidate** | Backup source → Create new backend → Update config → Restore → Verify | | **Split tenant** | Plan data distribution → Backup → Create stores → Restore to each → Restart | ## Known limitations **Secondary storage types must be compatible across tenants.** Use RDBMS for every tenant, Elasticsearch and OpenSearch in any combination, or `none` for every tenant. Do not mix RDBMS or `none` with another type. For example, a cluster where tenant A uses RDBMS and tenant B uses Elasticsearch is not supported. ## Storage configuration matrix | Aspect | RDBMS | Elasticsearch/OpenSearch | Document Store | | ------------------------ | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ---------------------------------- | | **Isolation** | Separate schema/database OR table prefix | Separate cluster OR index prefix | Separate bucket OR sibling subpath | | **Per-tenant config** | JDBC URL | `url` + `index-prefix` | Bucket + prefix | | **Collision detection** | Startup error | Startup error on identical prefix only; overlaps aren't detected | Startup error | | **Unavailable behavior** | Tenant degraded ([details](#secondary-storage-failures-during-startup-and-runtime)) | Tenant degraded ([details](#secondary-storage-failures-during-startup-and-runtime)) | Runtime error (no fallback) | | **Mixed vendors** | Yes | Yes (ES or OpenSearch) | Yes (different cloud providers) | --- ## Troubleshoot Physical Tenants ## About Learn how to diagnose problems specific to running multiple Physical Tenants in one Orchestration Cluster, and separate them from general cluster faults. Most symptoms in a multi-tenant cluster fall into one of two categories: the whole cluster is unhealthy, or a single Physical Tenant is degraded while its peers keep serving traffic. Start by determining which one you have, because the two have different causes and different fixes. ## Determine the scope of the problem ```mermaid graph TD start["Symptom reported"] cluster{"Is /cluster/v2/status healthy?"} allTenants{"Do all tenants fail\nthe same way?"} oneTenant["Single degraded tenant\nCheck that tenant's storage,\nconfiguration, and authorization"] clusterWide["Cluster-wide fault\nTroubleshoot as a normal\nsingle-tenant cluster"] infra["Shared infrastructure\nCheck gateways, brokers,\nand network"] start --> cluster cluster -->|No| clusterWide cluster -->|Yes| allTenants allTenants -->|Yes| infra allTenants -->|No| oneTenant classDef decision fill:#e4eef8,stroke:#2272c9,color:#14082c classDef outcome fill:#fde8da,stroke:#fc5d0d,color:#14082c class cluster,allTenants decision class oneTenant,clusterWide,infra outcome ``` Use these endpoints to answer the questions above: | Endpoint | Scope | Tells you | | :----------------------------------- | :------ | :--------------------------------------------------------------------------------------------- | | `/cluster/v2/status` | Cluster | Whether the cluster as a whole is operational. Requires no credentials. | | `/cluster/v2/topology` | Cluster | Physical Tenant topology and per-tenant status. Requires cluster-admin. | | `/physical-tenants/{id}/v2/topology` | Tenant | Whether one tenant can accept work, and which partitions are available. | | `/actuator/cluster` | Cluster | Cluster topology, plus `pendingChange` and `lastChange` for configuration changes in progress. | | `/actuator/health` | Node | Whether an individual broker or gateway node is healthy. | `/physical-tenants/{id}/v2/topology` is the tenant-prefixed form of `/v2/topology`, not a separate endpoint. An unprefixed `/v2/topology` request returns the `default` tenant's topology only; for the cluster-wide aggregate, use `/cluster/v2/topology`. The `/cluster/v2/...` and `/physical-tenants/...` endpoints are served on the Gateway REST port, 8080 by default. The `/actuator/...` endpoints are served on the management port, 9600 by default. If `/cluster/v2/status` is healthy but one tenant is failing, the problem is scoped to that tenant. Troubleshoot it with the sections below rather than treating it as a cluster outage. ## Degraded tenants A Physical Tenant most often becomes **degraded** because its secondary storage is unusable, for example because its schema could not be initialized or its database is unreachable. Secondary storage is not the only cause of a degraded tenant, but it's the one this section covers. ### What you observe - Storage-dependent `/v2/...` REST endpoints for that tenant return `503 Service Unavailable` with a `Retry-After` header and a problem-detail body. - Other Physical Tenants continue serving requests normally. - On Elasticsearch and OpenSearch deployments where the secondary-storage readiness check is enabled, the check is `UP` as long as at least one tenant is serviceable. The overall readiness group can still be `DOWN` because of other readiness contributors. - The per-tenant readiness gauge `camunda.physical.tenant.secondary.storage.ready` reports `0` for the affected tenant. - The `physicalTenantSchemaInitialization` contributor of `/actuator/health` reports the affected tenant as `DEGRADED` or `DOWN`, with its schema-initialization state, the number of failed attempts, and the last error. See [schema-initialization health](./storage-isolation.md#schema-initialization-health). - Per-tenant transition logs name the tenant and state whether an operator needs to act. ### How recovery works Camunda retries initialization for each degraded tenant in the background, with exponential backoff starting at 500 ms and climbing to 10 seconds. By default the retries are effectively unbounded. When you repair the underlying cause, such as restoring network access to the database, the tenant recovers on its own. **No restart is required.** If you have capped the retry count in your retry configuration, a tenant that exhausts the cap stays degraded until you restart the node, rather than recovering on its own. The same applies to a failure that retrying can't repair. The `physicalTenantSchemaInitialization` contributor reports these tenants as `GAVE_UP` and `FAILED`, while a tenant that still recovers on its own reports `RETRYING`. :::note Request rejection for degraded tenants applies to REST endpoints. gRPC and MCP requests are not rejected on this basis. ::: If a tenant's JDBC URL uses a prefix Camunda does not recognize, such as jTDS or a driver proxy, Camunda falls back to opening one connection at startup to identify the vendor. That tenant is no longer isolated from an unreachable database, and the startup log warns and names the property that removes the fallback. Set `database-vendor-id` explicitly for these tenants. ## Startup and configuration errors Camunda validates Physical Tenant configuration at startup and fails fast with an error that names the offending tenant. | Symptom | Cause | Resolution | | :-------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Startup fails naming two tenants that share a storage location | Two tenants resolve to the same schema, index prefix, or document store path | Give each tenant a distinct location. See [storage isolation](./storage-isolation.md#rdbms-storage). | | Startup fails on provider selection for a non-default tenant | A configured tenant does not declare `providers.assigned` | Assign at least one cluster OIDC provider to the tenant. | | Startup fails naming an unresolvable database vendor | The JDBC URL prefix is unrecognized and no `database-vendor-id` is set | Set `database-vendor-id` explicitly for that tenant. | | Schema migration fails on an identifier | The RDBMS table prefix is not a valid SQL identifier | Remove hyphens, spaces, and leading digits from the prefix. | | Startup reports an Oracle storage conflict for distinct tenants | Oracle tenants isolated by schema-per-user share one JDBC URL, so they look identical | Set `data.secondary-storage.rdbms.database-vendor-id: oracle` on each tenant. The startup error includes this hint. | | Per-tenant exporter settings appear to be ignored | The exporter isn't listed in the tenant's `exporters-assigned`, or the tenant's `args` replace the root `args` because the exporter type has no merger | List the exporter ID in the tenant's `exporters-assigned`. If the `args` replace the root `args`, repeat every root argument the tenant still needs. See [custom exporters for Physical Tenants](./custom-exporters.md). | The provider selection error names the exact property path it expects: ```text Invalid physical-tenant provider selection: non-default physical tenant '' must declare a non-empty 'camunda.physical-tenants..security.authentication.providers.assigned' selecting which cluster OIDC providers apply to it ``` The one exception is the implicit default tenant. When no `camunda.physical-tenants.*` configuration is present at all, the default tenant inherits the full cluster provider set. Once you configure `camunda.physical-tenants.default` explicitly, it must declare its assigned providers like any other tenant. ## API routing problems ### Requests reach the wrong tenant A request without a tenant prefix routes to the `default` Physical Tenant. If calls you expect to reach `tenanta` are returning `default` tenant data, the tenant segment is missing from the path or the gRPC header. Check the following: - REST calls use the `/physical-tenants/{id}/v2/...` form. The tenant segment comes **before** `/v2/`. - gRPC calls send the `Camunda-Physical-Tenant` metadata header. - If you use the Java client, `physicalTenantId` is set on the client. A single client instance targets exactly one tenant. - If your REST address already contains a `/physical-tenants/{id}` segment, for example behind a reverse proxy, `prefixPhysicalTenantPath(false)` is set so the client does not insert the segment twice. For client-side configuration, see [Physical Tenants in the Java client](/apis-tools/java-client/physical-tenants.md). ### Interpret the status code | Status | Meaning | | :------------------------ | :--------------------------------------------------------------------------------------------------------------------------- | | `401 Unauthorized` | The tenant exists, but credentials are missing or invalid, or the token was issued by a provider the tenant does not assign. | | `403 Forbidden` | The caller authenticated but lacks authorization within that tenant. | | `404 Not Found` | The tenant is not configured in the cluster, or has been removed from configuration and is disabled. | | `503 Service Unavailable` | The tenant exists but its secondary storage is degraded. Retry after the interval in `Retry-After`. | A `404` for a tenant path is not an authorization failure. The tenant does not exist in the cluster configuration, so authentication was never attempted. A tenant that was removed from configuration is disabled and also returns `404`, with its data retained. :::note When no Physical Tenant is explicitly configured, requests to `/physical-tenants/default/...` can return `404` even though the implicit default tenant is addressable at the unprefixed path. Configure the default tenant explicitly if you need the prefixed form to resolve. ::: ### Existing integrations after upgrading Unprefixed paths continue to work in 8.10 and route to the default Physical Tenant. When Physical Tenants are configured, `/operate` and `/physical-tenants/default/operate` address the same application, and the same holds for Tasklist. Upgrading a single-tenant cluster does not require client changes. ## Authentication and authorization problems ### Browser login fails for a non-default tenant When a user logs in to a non-default tenant through the browser, the OAuth redirect URI includes the tenant path prefix, such as `/physical-tenants/tenanta/sso-callback`. If your identity provider does not have that URI registered, the redirect fails. Register the redirect URI for every Physical Tenant you add. Some identity providers support wildcard matching, which avoids a change per tenant. ### A user cannot access a tenant they should have access to Each Physical Tenant applies its own mapping rules independently. The same token claim can grant a role in one tenant and nothing in another. Confirm the following for the specific tenant: - The tenant assigns the identity provider that issued the token, through `providers.assigned`. - The tenant's mapping rules cover the claim present in the token. - The user's authorizations are defined within that tenant, not only in the default tenant. ### Cluster-wide operations are rejected Endpoints under `/cluster/v2/...` require the cluster-admin role, except `GET /cluster/v2/status`, which is deliberately unauthenticated so load balancers can use it as a health check (see [health and status endpoints](./index.md#health-and-status-endpoints)). Brokers start successfully when the role is not configured, so a missing cluster-admin configuration only surfaces when someone calls a cluster-wide endpoint other than `/status`. Configure cluster-admin access under `camunda.security.cluster-admin.oidc.*` for OIDC, or `camunda.security.cluster-admin.basic.users` for Basic authentication. ### Backup or exporting requests are rejected Per-tenant backup and exporting endpoints are governed by two resource types, described in the [authorization model](./authorization-model.md): `BACKUP` (`CREATE`, `READ`, `DELETE`, `RESTORE`) and `EXPORTER` (`PAUSE`). An Elasticsearch or OpenSearch history backup needs **both** `BACKUP:CREATE` and `EXPORTER:PAUSE`, because exporting is paused for the duration of the backup. A role granted only `BACKUP:CREATE` fails partway through. The default **admin** role holds both; **readonly-admin** holds only `BACKUP:READ`. A `403` on a history backup endpoint has two possible causes, and the problem detail states which one applies: - The caller lacks the required `BACKUP` permission. - The tenant's secondary storage is neither Elasticsearch nor OpenSearch, so it cannot serve history backups at all. Granting permissions will not resolve this one. Permissions apply to the whole resource type. There is no per-backup-ID or per-exporter grant, so only the `*` resource ID is supported. ### Sessions behave unexpectedly across tenants Each tenant has its own path-scoped session cookie, scoped to `/physical-tenants/`. A session established for one tenant is not sent to another. Logging in to a second tenant in the same browser creates a second, independent session rather than replacing the first. For the full model, see [authentication and authorization](./authentication-authorization.md). ## Storage problems ### Index or schema not found for a specific tenant Confirm the tenant's resolved storage location matches what exists in the backend: - **RDBMS**: the schema or table prefix in the tenant's configuration exists and the tenant's credentials can reach it. - **Elasticsearch or OpenSearch**: the tenant's `index-prefix` matches the indices actually present. - **Document store**: the resolved provider, bucket or container, and path tuple points where you expect. Startup validation catches two tenants resolving to the _same_ location, but it does not catch a tenant pointing at a location that does not exist yet. ### Overlapping index prefixes Startup validation only rejects prefixes that are exactly identical. Prefixes where one is the leading substring of another, such as `eu` and `eu-west`, pass validation but cause `eu*` wildcard queries to match both tenants' indices. Use full tenant IDs as prefixes. ### Database rejects new connections on RDBMS On RDBMS secondary storage, the number of database connections grows with the number of nodes multiplied by the number of Physical Tenants, and can exceed the database's connection limit. This is relevant when the Physical Tenants share the same database instance. #### What you observe - Brokers fail to start or restart repeatedly, and their logs show the database refusing new connections. For example: - PostgreSQL: `FATAL: sorry, too many clients already` - MySQL and MariaDB: `Too many connections` - Oracle: `ORA-00018: maximum number of sessions exceeded` or `ORA-00020: maximum number of processes exceeded` - The failures start after you add Physical Tenants or broker nodes, not after an increase in load. - Most connections open on the database are idle. #### Why it happens Each node opens a separate database connection pool for every Physical Tenant it serves. Nodes don't share pools, so the connections to one database instance scale with both the cluster size and the number of tenants stored on that instance. This is a known limitation, tracked in [camunda/camunda#61935](https://github.com/camunda/camunda/issues/61935). To plan connection capacity before you add tenants, see [Size RDBMS connections](/components/best-practices/architecture/sizing-physical-tenants.md#size-rdbms-connections). | Connections | Formula | Example: 10 nodes, 10 tenants, default pool settings | | :------------------------------------- | :------------------------------------ | :--------------------------------------------------- | | Held open while idle | nodes × tenants × `minimum-idle` | 10 × 10 × 2 = 200 | | Maximum, when every pool is fully used | nodes × tenants × `maximum-pool-size` | 10 × 10 × 10 = 1,000 | When the total passes the database's connection limit, the database rejects further connections. Default limits differ per vendor: | Database | Default connection limit | | :------------------- | :----------------------------------------------------- | | PostgreSQL | 100 (`max_connections`) | | MySQL and MariaDB | 151 (`max_connections`) | | Oracle | Set by `processes` and `sessions`. Check your instance | | Microsoft SQL Server | Up to 32,767 (`user connections`) | In the example above, the idle connections alone exceed the PostgreSQL default before the cluster processes any load. Managed database services often set the limit from the instance size, so check the effective value for your instance. #### How to fix it Apply one or more of the following mitigations: | Mitigation | How | Trade-off | | :--------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Reduce pool sizes | Lower `minimum-idle` and `maximum-pool-size` under `camunda.data.secondary-storage.rdbms.connection-pool.*`, or per tenant under `camunda.physical-tenants..data.secondary-storage.rdbms.connection-pool.*`. See [connection pool configuration](/self-managed/concepts/databases/relational-db/configuration.md#connection-pool-configuration). | Pools that are too small make requests wait for a connection, and fail after `connection-timeout`. Monitor the Hikari pending-connection metrics after the change. | | Raise the database's connection limit | Increase `max_connections` on PostgreSQL, MySQL, or MariaDB, or `processes` and `sessions` on Oracle. | Each connection consumes database memory, and scaling further eventually reaches the new limit. | | Pool connections with a proxy | Run a connection pooling proxy between Camunda and the database, and point the tenants' JDBC URLs at the proxy. Examples include [PgBouncer](https://www.pgbouncer.org/) for PostgreSQL, [ProxySQL](https://proxysql.com/) or [MariaDB MaxScale](https://mariadb.com/docs/maxscale/) for MySQL and MariaDB, and [Database Resident Connection Pooling (DRCP)](https://docs.oracle.com/en/database/oracle/oracle-database/23/jjdbc/database-resident-connection-pooling.html) for Oracle. | Adds a component to deploy and operate. Refer to the proxy's documentation for its configuration. | | Spread tenants across database instances | Store groups of Physical Tenants on separate database instances, so each instance only receives connections for the tenants it stores. See [storage isolation](./storage-isolation.md#rdbms-storage). | Requires additional database instances. | ### Verify isolation To confirm two tenants are genuinely isolated: - Inspect the backend directly and confirm each tenant's schema, index prefix, or document path contains only that tenant's data. - Call the same search endpoint under two different tenant prefixes and confirm the result sets do not overlap. - Confirm no single API call returns data from more than one tenant. Every request targets exactly one Physical Tenant, so cross-tenant results indicate a shared storage location rather than a query problem. ## Performance and noisy neighbors Full performance isolation is out of scope. Some infrastructure is shared, so heavy load in one tenant can affect others. | Shared resource | Effect under load | | :-------------- | :-------------------------------------------------------------------------------- | | Gateways | A saturated gateway affects requests for every tenant it serves. | | Brokers | Brokers are co-located and host partitions for more than one tenant. | | Actor threads | Partition processing threads are shared. There is no per-tenant thread isolation. | To identify a noisy neighbor, compare per-tenant throughput and latency over the same window and look for one tenant's load rising as others degrade. Scope every panel by the `physicalTenant` label so a single tenant's behavior is visible on its own. ## Monitor per tenant Camunda tags tenant-scoped metrics with a `physicalTenant` label. Filter by this label, and by `partition`, to isolate one tenant's behavior. | Metric or label | Use for | | :------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `physicalTenant` label | Scoping any tenant-aware metric to a single tenant. | | `camunda.physical.tenant.secondary.storage.ready` | Detecting a degraded tenant. Reports `0` when storage is unusable. | | `camunda.schema.init.time` | Diagnosing slow or stuck schema initialization per tenant. | | Hikari connection pool metrics | Spotting per-tenant connection pool exhaustion on RDBMS backends, and tracking total connections against the database's limit. See [database rejects new connections](#database-rejects-new-connections-on-rdbms). | The Zeebe dashboard aggregates over `(physicalTenant, partition)`, so you can filter it to one tenant without changing the queries. Metric names above are given in their Micrometer form. When scraping through the Prometheus endpoint, dots become underscores, so `camunda.physical.tenant.secondary.storage.ready` is scraped as `camunda_physical_tenant_secondary_storage_ready`. Recommended alerts: - `camunda.physical.tenant.secondary.storage.ready` at `0` for longer than your expected recovery window. - Sustained `503` responses on a single tenant's REST endpoints. - Connection pool saturation for one tenant while others are idle. ## Known limitations | Limitation | Impact | | :------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Mixing RDBMS or `none` with other secondary storage types is not supported | Elasticsearch and OpenSearch can be mixed across tenants in any combination. If any tenant uses RDBMS or `none`, every tenant must use that same type. See [storage isolation](./storage-isolation.md#known-limitations). | | Deterministic schema mismatches retry indefinitely | A schema mismatch that cannot succeed on retry is treated as retryable, so the tenant stays degraded instead of failing clearly. See [camunda/camunda#61063](https://github.com/camunda/camunda/issues/61063). | | Generic exporter assignment and arguments | Root-declared generic exporters must be explicitly assigned to each tenant. Tenant arguments merge with root arguments only for exporter types that provide a merger; otherwise, tenant arguments replace the root arguments. See [custom exporters for Physical Tenants](./custom-exporters.md). Built-in Camunda and RDBMS exporters are configured separately under `camunda.data.secondary-storage.*`. | | Tenant deletion | Removing a tenant from configuration disables it and retains its data. There's no single API that removes a tenant's configuration and its data together. To delete its data, [purge](/self-managed/operational-guides/data-purge.md) the tenant (`POST /actuator/cluster/purge?physicalTenant={physicalTenantId}`) before removing it from configuration, then [logically remove](./provisioning-and-lifecycle.md#logically-remove-a-disabled-tenant) it from the cluster topology. | | RDBMS connections scale with nodes and tenants | Every node opens a connection pool per Physical Tenant, so connections to a shared database grow as nodes × tenants and can exceed the database's connection limit. See [database rejects new connections](#database-rejects-new-connections-on-rdbms) and [camunda/camunda#61935](https://github.com/camunda/camunda/issues/61935). | | Full performance isolation | Gateways, brokers, and actor threads remain shared. | ## Related pages - [Physical Tenant isolation model](./index.md) - [Storage isolation](./storage-isolation.md) - [API routing](./api-routing.md) - [Authentication and authorization](./authentication-authorization.md) - [Provisioning and lifecycle](./provisioning-and-lifecycle.md) --- ## Configure secondary storage Configure secondary storage to enable features such as Operate, Tasklist, Identity, and search-based REST APIs in Camunda Self-Managed environments. Use "secondary storage" as the general concept. The backend can be a supported RDBMS or a document-store backend such as Elasticsearch or OpenSearch, depending on your deployment requirements. ## Configuration options You can configure secondary storage using Helm charts, Docker Compose, or manual configuration files. Camunda uses the `data.secondary-storage` configuration to define which secondary storage backend supports Orchestration Cluster web applications and APIs (for example, Operate, Tasklist, Identity, and search endpoints). Once a selection is made and the cluster is deployed, the secondary storage backend is fixed. Switching between backend families (document-store and RDBMS) or migrating between backends within the same family is not supported. :::note For the latest list of supported relational databases and versions, see the [RDBMS version support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md). ::: When deploying with Helm, set the secondary storage type, connection details, and exporter settings in your `values.yaml` file. ```yaml orchestration: exporters: camunda: enabled: false rdbms: enabled: true data: secondaryStorage: type: rdbms rdbms: url: jdbc:postgresql://hostname:5432/camunda username: camunda secret: existingSecret: camunda-db-secret existingSecretKey: password ``` More information about RDBMS in the Camunda Helm chart can be found on this [configuration page](/self-managed/deployment/helm/configure/database/rdbms.md). If you choose Elasticsearch as the secondary storage backend, configure it as follows: ```yaml orchestration: data: secondaryStorage: type: elasticsearch elasticsearch: url: http://hostname:443 auth: username: elastic secret: existingSecret: camunda-db-secret existingSecretKey: password ``` More information about Elasticsearch in the Camunda Helm chart can be found in [using external Elasticsearch](/self-managed/deployment/helm/configure/database/elasticsearch/using-external-elasticsearch.md). To explicitly disable secondary storage (for example, when running only the Zeebe engine), set: ```yaml global: noSecondaryStorage: true ``` When this flag is set, all secondary-storage-dependent components are automatically disabled. If you’re using Docker Compose, configure your environment variables within the relevant service definition. The examples below use the `CAMUNDA_DATA_SECONDARY_STORAGE_*` naming family consistently. ```yaml environment: - CAMUNDA_DATA_SECONDARY_STORAGE_TYPE=rdbms - CAMUNDA_DATA_SECONDARY_STORAGE_RDBMS_URL=jdbc:postgresql://postgres:5432/camunda - CAMUNDA_DATA_SECONDARY_STORAGE_RDBMS_USERNAME=camunda - CAMUNDA_DATA_SECONDARY_STORAGE_RDBMS_PASSWORD=camunda ``` If you choose Elasticsearch as the secondary storage backend: ```yaml environment: - CAMUNDA_DATA_SECONDARY_STORAGE_TYPE=elasticsearch - CAMUNDA_DATA_SECONDARY_STORAGE_ELASTICSEARCH_URL=http://elasticsearch:9200 ``` If you choose OpenSearch as the secondary storage backend: ```yaml environment: - CAMUNDA_DATA_SECONDARY_STORAGE_TYPE=opensearch - CAMUNDA_DATA_SECONDARY_STORAGE_OPENSEARCH_URL=http://opensearch:9200 ``` To disable secondary storage: ```yaml environment: - CAMUNDA_DATA_SECONDARY_STORAGE_TYPE=none ``` For end-to-end backend-specific examples, including PostgreSQL, MariaDB, MySQL, Oracle, SQL Server, H2, Elasticsearch, and OpenSearch, see the [Docker Compose developer quickstart](/self-managed/quickstart/developer-quickstart/docker-compose.md). In Self-Managed or Camunda 8 Run deployments, you can also configure storage directly in the `application.yaml` file: ```yaml camunda: data: secondary-storage: type: rdbms rdbms: url: jdbc:h2:file:./camunda-data/h2db username: sa password: ``` If you choose Elasticsearch as the secondary storage backend: ```yaml camunda: data: secondary-storage: type: elasticsearch elasticsearch: url: http://localhost:9200/ ``` ## Choosing a storage backend - Local testing or Camunda 8 Run quickstart: H2 is fast, lightweight, and runs entirely in memory or file-based. - Production workloads: Use a supported RDBMS or document-store backend. Choose based on operational needs and validate with [benchmarking and sizing guidance](/components/best-practices/architecture/sizing-your-environment.md). - Debugging and troubleshooting: H2 or PostgreSQL are often easier to inspect and visualize. ### H2 limitations Use H2 only for development, testing, and evaluation. - H2 is non-production only. - H2 in-memory mode does not persist data across restarts. - H2 file-based mode persists only to local node disk and is intended for local/dev usage. - H2 does not provide a shared database across brokers. - Multi-broker clusters with H2 are not a valid architecture; query results can be broker-local and incomplete. - H2 has limited concurrency and scalability compared to external production backends. For Helm deployments, if you choose H2 you must run a single broker (`clusterSize: 1`, `partitionCount: 1`, `replicationFactor: 1`). For multi-broker Helm clusters, use a shared external backend (for example, PostgreSQL). ### Migration from invalid H2 setups If you currently run an invalid H2 topology, use one of these paths: 1. Local/dev only: Move to file-based H2 with a single broker. 2. Shared or clustered deployment: Move to an external persistent backend (for example, PostgreSQL, MariaDB, MySQL, Oracle, SQL Server, Elasticsearch, or OpenSearch according to support and architecture). :::note Starting in 8.9, H2 is the default secondary storage for lightweight Camunda 8 Run setups and quickstarts. H2 remains suitable for local testing, demos, and file-based setups, but it is not recommended for production workloads where persistence, scaling, and full analytics are required. For production use, Orchestration Cluster applications and APIs (including Operate, Tasklist, Identity, and search endpoints) should run against a persistent secondary storage backend such as a supported RDBMS or a document-store backend (Elasticsearch/OpenSearch). Both are valid production choices when supported for your deployment. Consult the [RDBMS version support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) when choosing a relational database, and [supported environments](/reference/supported-environments.md) for Elasticsearch/OpenSearch versions. ::: Once a selection is made and the cluster is deployed, the secondary storage backend is fixed. Switching between backend families (document-store and RDBMS) or migrating between backends within the same family is not supported. Plan migration as a fresh cluster deployment, with new primary and secondary storage, and validate the procedure in a non-production environment before rollout. For upgrade planning, see [prepare for upgrade](/self-managed/upgrade/prepare-for-upgrade.md). ## Run without secondary storage If you want to run the Zeebe engine without secondary storage or web applications, you can use **no secondary storage** mode. In this mode: - Operate, Tasklist, Identity, Optimize, and the REST API are disabled. - The Zeebe engine and primary storage remain active for process execution. - This configuration is best suited for local development or minimal-resource environments. See [run without secondary storage](./no-secondary-storage.md) for configuration examples and limitations. --- ## Secondary storage Camunda uses a layered storage model that separates workflow execution data from data used by web applications and APIs. If you are designing Physical Tenant isolation, see [Physical Tenant isolation model](../physical-tenants/index.md) for the tenant-level execution and routing boundaries that sit on top of secondary storage. ## About secondary storage Secondary storage is one of the two complementary layers in Camunda’s data model: | Layer | Purpose | Technologies you can use | | :---------------- | :-------------------------------------------------------------------------------------------------------------------------------- | :-------------------------- | | Primary storage | Persists real-time workflow execution state managed by [Zeebe](/self-managed/components/orchestration-cluster/zeebe/overview.md). | RocksDB (embedded in Zeebe) | | Secondary storage | Stores workflow, decision, and task data for querying, visualization, and API access. | Document-store or RDBMS | :::note Secondary storage is not a duplicate of primary data. It represents exported workflow and decision data optimized for querying and visualization. ::: ### Supported storage options Camunda supports multiple secondary storage backends. Both document-store and RDBMS backends are valid choices in Self-Managed deployments. Support maturity can vary by product area and version (for example, the Orchestration Cluster API, Operate, Tasklist, Admin, or Optimize), so confirm current compatibility details before choosing a backend. For supported database versions, see the [RDBMS version support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md). | Database type | Availability | Use case | | :--------------------- | :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Document-store (ES/OS) | General availability | Secondary storage for indexing, search, and analytics. | | RDBMS | 8.9+ | Secondary storage for relational database deployments. See the [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) for supported vendors and versions. | :::info OpenSearch support Camunda 8 supports both [Amazon OpenSearch](https://aws.amazon.com/opensearch-service) and the open-source [OpenSearch](https://opensearch.org/) distribution. ::: ```mermaid graph TD subgraph oc["Orchestration Cluster"] broker["Zeebe Broker"] exp_rdbms["RDBMS Exporter"] exp_camunda["Camunda Exporter\n(for Orchestration Cluster on ES/OS)"] exp_opt["Elasticsearch/OpenSearch Exporter\n(for Optimize)"] api["Orchestration Cluster API"] apps["Operate · Tasklist · Admin"] end rdbms["RDBMS\n(PostgreSQL, Oracle, MariaDB, MySQL, SQL Server)"] es["Elasticsearch/OpenSearch"] opt["Optimize\n(document-store required)"] broker -->|"Enable for RDBMS backend"| exp_rdbms broker -.->|"Enable for document-store backend"| exp_camunda broker -.->|"Enable when Optimize is deployed"| exp_opt exp_rdbms -->|"Write"| rdbms exp_camunda -->|"Write"| es exp_opt -.->|"Write"| es apps -->|"Use"| api api -->|"Query selected backend"| rdbms api -.->|"Query selected backend"| es opt -.->|"Read/Write (if deployed)"| es style broker fill:#e4eef8,stroke:#2272c9,color:#14082c style exp_rdbms fill:#e4eef8,stroke:#2272c9,color:#14082c style exp_camunda fill:#e4eef8,stroke:#2272c9,color:#14082c style exp_opt fill:#e4eef8,stroke:#2272c9,color:#14082c style api fill:#e4eef8,stroke:#2272c9,color:#14082c style apps fill:#e4eef8,stroke:#2272c9,color:#14082c style rdbms fill:#fde8da,stroke:#fc5d0d,color:#14082c style es fill:#fde8da,stroke:#fc5d0d,color:#14082c style oc fill:#f0f5ff,stroke:#2272c9 style opt fill:#e8fdf1,stroke:#10c95d,color:#14082c ``` :::note Starting in 8.9, Camunda 8 Run and default lightweight installs use H2 as the default secondary storage. Elasticsearch remains a supported alternative in Camunda 8 Run. OpenSearch and RDBMS-based secondary storage are supported in Self-Managed deployments. Enable the backend you need explicitly when required. H2 is a convenience default for local development, testing, and evaluation. It is not a production reference architecture and is not a valid backend for multi-broker Helm clusters. ::: ### How secondary storage works The Zeebe Broker executes workflow instances and stores state in primary storage. Exporters running as part of Zeebe write orchestration data to the configured secondary storage backend and can write to multiple targets when needed. Operate, Tasklist, and Admin use the Orchestration Cluster API, which reads from the configured secondary storage backend. ## Choosing a secondary storage backend Camunda supports multiple secondary storage backends, and the right choice depends on your workload and operational constraints. For guidance on supported vendors, versions, and configuration, see: - [Secondary storage configuration](/self-managed/concepts/secondary-storage/configuring-secondary-storage.md) - [RDBMS configuration](/self-managed/deployment/helm/configure/database/rdbms.md) - [RDBMS version support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) - [RDBMS benchmark results](./rdbms-benchmark-results.md) :::note The documentation is intentionally descriptive rather than prescriptive. Use benchmarking and sizing based on your own workload to choose the secondary storage backend that best meets your requirements. ::: :::note Although you should use secondary storage in nearly all production environments, you can choose to disable secondary storage in limited scenarios, such as lightweight development environments, specialized technical use cases, or resource-constrained deployments. See [run without secondary storage](no-secondary-storage.md). ::: --- ## Manage secondary storage Manage your secondary storage carefully to maintain data integrity, performance, and system stability. For definitions and conceptual context, see [secondary storage](/reference/glossary.md#secondary-storage). ## Modifying secondary storage data :::warning You should never manually modify data stored in secondary storage unless instructed by Camunda Support during an active support case. Do not make direct edits to data in secondary storage outside of explicit Camunda Support guidance. ::: ### Risks of manual modification Unsupervised changes to secondary storage data can lead to severe issues, such as the following: | Risk | Description | | :------------------------- | :------------------------------------------------------------------------------------------------------------- | | Data loss | Manual edits may delete or overwrite essential records that cannot be recovered without backups. | | Data corruption | Structural or value changes can leave indices or tables in inconsistent states, leading to application errors. | | Unsupported system states | Unapproved modifications may break compatibility with upgrades, patches, or new features. | | Troubleshooting challenges | Custom edits make it difficult for support engineers to diagnose and resolve issues. | | Security vulnerabilities | Unauthorized changes can expose sensitive data or weaken access controls. | | Compliance issues | Altered records may violate internal or external data-integrity regulations. | ## Configuring capacity and redundancy Secondary storage configuration depends on the backend you choose (for example, a document-store backend such as Elasticsearch/OpenSearch, or an RDBMS). Both are valid options in supported scenarios. Use the documentation for your selected backend and validate decisions against your expected workload. :::note Backend selection and sizing should be based on benchmarking and realistic workload expectations. Prefer configuration choices that you can validate with measured throughput, latency, and retention needs. ::: ### Document-store backends (Elasticsearch/OpenSearch): shards and replicas :::warning When Elasticsearch/OpenSearch Exporter indices and Orchestration Cluster indices share the same Elasticsearch or OpenSearch cluster, they must use different index prefixes. One prefix must not be the beginning of the other (for example, avoid `custom` and `custom-zeebe` together because `custom*` matches both). Do not use `operate`, `tasklist`, or `camunda` as the full exporter prefix, and do not use `zeebe-record` as the Orchestration Cluster index prefix, as `zeebe-record` is the default prefix for Elasticsearch/OpenSearch Exporter indices. For detailed requirements, configuration examples, and common mistakes, see [Index prefix configuration](/self-managed/deployment/helm/configure/database/elasticsearch/configure-elasticsearch-prefix-indices.md#index-prefix-configuration). ::: If you use [Elasticsearch/OpenSearch](/reference/glossary.md#elasticsearchopensearch) as your secondary storage backend, configure shards and replicas to support resilience and scalability. #### Shards Define shard count according to your data size and expected growth. - Start with **1–5 primary shards** per index. - More shards can improve scalability but increase management complexity. - Avoid over-sharding, which can reduce performance and add unnecessary overhead. #### Replicas The default replica count for Elasticsearch/OpenSearch Exporter indices is `1`. - **Single-node clusters:** Set `number-of-replicas: 0` explicitly. On a single node, replicas cannot be assigned to another node and remain unassigned, causing the cluster to report yellow health. This is expected behavior but may trigger monitoring alerts. - **Multi-node clusters:** The default of `1` replica ensures fault tolerance. Keep or increase this value based on your availability requirements. :::note Each replica stores a full copy of the primary shard data, so one replica per index approximately doubles the total disk required for those indices. Account for replica storage when [sizing your Elasticsearch/OpenSearch cluster](/components/best-practices/architecture/sizing-self-managed.md#elasticsearch-scaling). ::: ## Backups Regular backups of your secondary storage are critical for disaster recovery and business continuity. - Follow the official Camunda [backup procedure](/self-managed/operational-guides/backup-restore/backup-and-restore.md) step by step. For backend-specific steps, see [Elasticsearch/OpenSearch](/self-managed/operational-guides/backup-restore/elasticsearch/backup.md) or [RDBMS](/self-managed/operational-guides/backup-restore/rdbms/backup.md). - Schedule backups regularly based on data volume and business requirements. - Periodically test restore operations to confirm that your backups are valid and usable. ## Index templates If you use [Elasticsearch/OpenSearch](/reference/glossary.md#elasticsearchopensearch), Camunda uses index templates to define settings and mappings for indices. To prevent issues: - Avoid using custom index templates that conflict with Camunda’s defaults. Templates with higher priority may override Camunda mappings and cause incorrect index creation. - Do not delete or modify existing Camunda index templates without explicit guidance from Camunda Support. If your provider includes predefined wildcard index templates, set a higher priority for the Camunda templates to prevent conflicts. ## Monitoring secondary storage health Use the [Data Layer Dashboard](/self-managed/operational-guides/monitoring/metrics.md) to monitor performance and detect issues with your secondary storage integration. For example: - Track exporter and indexing latency. - Detect shard or replica imbalances for document-store backends (Elasticsearch/OpenSearch). - Identify degraded query performance early across secondary storage backends (document-store or RDBMS). ## Advanced: Elasticsearch deprecation logging If you are using Elasticsearch version ≥8.16.0, you may want to turn off deprecation logging to reduce log noise: ```bash curl -X PUT "http://localhost:9200/_cluster/settings" \ -H "Content-Type: application/json" \ -d '{ "persistent": { "logger.org.elasticsearch.deprecation": "OFF" } }' ``` This is optional but can help keep logs clean in production environments. --- ## Run without secondary storage Use **no secondary storage** mode to run Zeebe clusters with only the process engine and its primary storage layer. :::warning Disabling secondary storage removes key Orchestration Cluster capabilities, including Operate, Tasklist, Identity, and search-based REST endpoints. This mode is suitable only for lightweight development, testing, or specialized technical use cases. ::: ## About this mode Typically, you should use secondary storage in nearly all production environments to enable monitoring, analytics, querying, and human-task management through Orchestration Cluster applications. You should **only** disable/run without secondary storage in limited scenarios, such as lightweight development environments, specialized technical use cases, or resource-constrained deployments. - In this mode, Operate, Tasklist, Identity, and web-based APIs are automatically disabled. - For Helm deployments, Optimize is also disabled by default when secondary storage is not configured. - For Docker or manual deployments, you must **explicitly disable Optimize** in your configuration, as it cannot function without secondary storage. This setup provides core process execution and orchestration capabilities through Zeebe, but excludes the full Camunda experience, such as analytics, search, and human-task management. ## Enable **no secondary storage** mode You can enable this mode in several ways depending on your deployment method. To disable secondary storage in Helm-based installations, set the following flag in your `values.yaml` file: ```yaml global: noSecondaryStorage: true ``` When this value is set, the Helm charts automatically disable all components that depend on secondary storage. To disable secondary storage in Camunda 8 Run or other manual setups, set the following property in your configuration file: ```yaml spring: profiles: active: broker,standalone camunda: data: secondary-storage: type: none ``` Or use environment variables: ```bash SPRING_PROFILES_ACTIVE=broker,standalone CAMUNDA_DATA_SECONDARYSTORAGE_TYPE=none ``` If brokers and gateways run separately, apply the same configuration for gateways: ```yaml spring: profiles: active: gateway,standalone camunda: data: secondary-storage: type: none ``` ```bash SPRING_PROFILES_ACTIVE=gateway,standalone CAMUNDA_DATA_SECONDARYSTORAGE_TYPE=none ``` In a Docker Compose setup, you can disable secondary storage by setting the following environment variable for the relevant service: ```yaml environment: - CAMUNDA_DATA_SECONDARYSTORAGE_TYPE=none ``` ## Authentication Authentication works with no secondary storage mode. OIDC authentication is configured the same way as with secondary storage enabled. For details, see [Orchestration Cluster authentication](/self-managed/concepts/authentication/authentication-to-orchestration-cluster.md). :::note Basic authentication with no secondary storage If you use Basic authentication, you must also enable unprotected API mode because Basic auth requires access to user data in secondary storage. ```yaml global: noSecondaryStorage: true orchestration: security: authentication: method: basic unprotectedApi: true authorizations: enabled: false ``` This configuration should **only be used for development** or testing environments, as the unprotected API mode disables authentication checks on API endpoints. ::: ## Components and features disabled If secondary storage is disabled, the following components and features are unavailable: | Category | Component or feature | Behavior | | :--------------- | :--------------------------------------------------------------------------------- | :--------------------- | | Web applications | Operate, Tasklist, Admin UI, Optimize, Play (Modeler Play tab) | Disabled | | APIs & services | Orchestration Cluster REST API (search endpoints), batch operations, usage metrics | Return `403 Forbidden` | | Data & storage | Secondary storage exporters, Schema Manager, secondary storage backups | Disabled | :::note - Outbound connectors remain supported. However, inbound connectors and any features that require process definition lookup are unavailable. - Custom exporters (for example, Kafka, Prometheus, or MongoDB) continue to work as they interact directly with the engine’s primary storage. ::: ## Error handling and API responses If you attempt to access a disabled feature, the system returns a descriptive error response. For example: ```json { "status": 403, "detail": "This endpoint requires secondary storage, but none is set. Configure it using the 'camunda.data.secondary-storage.type' property.", "instance": "/v2/decision-instances/search" } ``` - At startup, affected components log a warning and shut down gracefully. - SDKs and clients will also return a `403 Forbidden` error when interacting with unsupported endpoints. ## Limitations and considerations Using this mode significantly reduces Camunda’s capabilities: | Limitation | Impact | | ------------------------------- | -------------------------------------------------------------- | | No visual monitoring | Operate, Tasklist, and the Identity UI are unavailable. | | No historical data or analytics | Optimize, dashboards, and audit records cannot be accessed. | | Limited API access | Most search and query endpoints return `403 Forbidden`. | | Reduced observability | Built-in metrics and secondary storage exporters are disabled. | | No human task management | Tasklist and identity-based task assignment are unavailable. | :::note You can still deploy and execute BPMN and DMN processes using the Zeebe client or REST API endpoints that only rely on the engine. ::: ## When to use this mode :::caution Before using this mode in production, consult your Camunda support or field team to ensure it meets your requirements. ::: Use this mode only in limited, specialized scenarios, such as the following: | Scenario | Description | | :------------------------------------------ | :-------------------------------------------------------------------------------------------- | | Local development with Camunda 8 Run | Run Zeebe locally without secondary storage components for lightweight testing and iteration. | | Resource-constrained environments | Deploy only the Zeebe engine when secondary storage or web applications cannot be supported. | | Temporary migration or diagnostic scenarios | Minimal orchestration functionality during transition or troubleshooting. | | Custom monitoring or exporter development | Use custom exporters or external observability tools instead of secondary storage. | --- ## RDBMS benchmarking results This page summarizes current benchmark results for using PostgreSQL as Orchestration Cluster secondary storage. Use these results as directional guidance, not strict guarantees. Actual performance depends on process complexity, hardware, database configuration, retention settings, and query patterns. ## Test scope and environment Current published results are based on PostgreSQL benchmarking only. ### Scenarios tested - **Single-task workload**: A process with one task. - **Multi-step timer workload**: A 10-task process with multiple timers. - **Complex business workload**: A process with call activities, subprocesses, and DMN. ### Baseline setup used in benchmark discussions - Orchestration cluster: three nodes, three partitions. - Orchestration resources: 3.5 CPU and 2 GB RAM per node. - PostgreSQL: single-node containerized setup, typically 3-6 CPU and 6-8 GB RAM. - Retention baseline used in several tests: TTL 60 minutes. Published results currently reflect short-retention benchmark windows. ## Key results summary ### Write throughput - In scale tests, RDBMS write behavior was described as approximately linear with additional cluster capacity. ### Scenario observations - **Single-task workload**: Stable exporter behavior under low-complexity flow patterns. - **Multi-step timer workload**: Stable exporter behavior in multi-step flows, with engine pressure becoming visible before exporter pressure in some runs. - **Complex business workload**: Stable exporter behavior in complex business flows, where engine and workload characteristics can dominate end-to-end throughput. ### Data availability and cleanup - History cleanup overhead was minimal in short-retention scenarios and did not impact write throughput. With longer retention periods, allocate additional database resources for cleanup operations. ### Read/query behavior Read performance is currently the main trade-off for RDBMS secondary storage in query-heavy workloads: - Key-based access patterns scale better. - Broad filters, sorting, and statistics/count queries can degrade with data growth. - Statistics queries for process and dashboard views are known sensitive paths in the Orchestration Cluster API, whether called directly through the API or indirectly by Operate dashboards. Recommended mitigation: - Validate your most common read queries with production-like data. - Add and tune indexes for high-frequency filters used in your environment. - Re-evaluate index strategy as data volume and query patterns evolve. ### RDBMS vs Elasticsearch/OpenSearch comparison - With the same hardware setup, RDBMS write performance was observed at roughly **~70%** of Elasticsearch/OpenSearch write performance across tested scenarios. - API read performance (including Operate dashboards) with RDBMS can be significantly slower than Elasticsearch/OpenSearch on large datasets, especially for complex queries, multi-field filtering, and sorting. ## Choosing RDBMS secondary storage Choose RDBMS secondary storage when: - You already operate relational databases at scale. - You prefer operational consistency with existing database tooling. - Workloads are low-to-moderate and read/query pressure is manageable. Prefer Elasticsearch/OpenSearch when: - You need high-throughput, high-volume execution at scale. - You rely on heavy filtering/sorting/search workloads. - You require Optimize without introducing a second storage technology. ## Caveats and interpretation guidance - These results are from controlled benchmark scenarios and should not be interpreted as guaranteed production numbers. - Results can change as product and exporter optimizations evolve. - Always validate final sizing with production-like workload tests. --- ## Configure wait state tracking Configure [wait state tracking](/components/wait-states/overview.md) in Camunda 8 Self-Managed. ## Enable or disable wait state tracking Wait state tracking is enabled by default. You can disable it if you do not want to track this data. To change the default behavior in Camunda 8 Self-Managed, configure your deployment: ```yaml camunda: data: wait-states: enabled: true # Wait state tracking is enabled by default ``` ```bash CAMUNDA_DATA_WAITSTATES_ENABLED=true ``` ```yaml orchestration: extraConfiguration: - file: additional-spring-properties.yaml content: | camunda: data: wait-states: enabled: true # Wait state tracking is enabled by default ``` See [all configuration options](../../components/orchestration-cluster/core-settings/configuration/properties.md#camundadatawait-states) to learn more. Disabling wait state tracking stops new wait states from being tracked. It doesn't purge existing wait state data immediately. The secondary storage retention settings clean up that data over time. Until the data is cleaned up, you can continue to access it in [Operate](/components/operate/userguide/view-wait-states.md) and the [Search API](/apis-tools/orchestration-cluster-api-rest/specifications/search-element-instance-wait-states.api.mdx). ## Configure secondary storage retention With Camunda 8 Self-Managed, you control the [secondary storage retention policy](../../components/orchestration-cluster/core-settings/configuration/properties.md#data---secondary-storage), which applies to wait state records: ```yaml camunda: data: secondary-storage: retention: enabled: true minimum-age: 30d ``` ```bash CAMUNDA_DATA_SECONDARYSTORAGE_RETENTION_ENABLED=true CAMUNDA_DATA_SECONDARYSTORAGE_RETENTION_MINIMUMAGE=30d ``` ```yaml orchestration: extraConfiguration: - file: retention.yaml content: | camunda: data: secondary-storage: retention: enabled: true minimum-age: 30d ``` See [Configure data retention](../../deployment/helm/configure/data-retention.md) for more information about the Helm configuration. --- ## Dual-region operational procedure (ECS Fargate) This procedure removes a lost region from a [dual-region ECS Fargate deployment](./aws-ecs-dual-region.md) and adds it back once it recovers. It uses the [`failover.sh`](https://github.com/camunda/camunda-deployment-references/blob/main/aws/containers/ecs-dual-region-fargate/procedure/failover.sh) and [`failback.sh`](https://github.com/camunda/camunda-deployment-references/blob/main/aws/containers/ecs-dual-region-fargate/procedure/failback.sh) scripts from the reference repository. Failover is manual. No automated, health-check-driven failover is included. ## Zones API operations the scripts run The Zeebe cluster is [zone-aware](/self-managed/components/orchestration-cluster/zeebe/configuration/zone-aware-clusters.md), with one zone per AWS region, so the scripts restore [quorum after a region loss](/self-managed/concepts/multi-region/dual-region.md#region-failure-and-recovery) by removing and re-adding a whole zone through the [Zones API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#zones-api): | Script | Request | | ------------- | ---------------------------------------------------- | | `failover.sh` | `DELETE /actuator/cluster/zones/{zoneId}?force=true` | | `failback.sh` | `POST /actuator/cluster/zones/{zoneId}` | ## Prerequisites - A deployment created with the [dual-region ECS Fargate guide](./aws-ecs-dual-region.md). - `jq` and the `session-manager-plugin` installed (see [Tooling](./aws-ecs-dual-region.md#tooling)). The scripts reach the management API through a [Session Manager port-forward](./aws-ecs-dual-region.md#method-b--session-manager-port-forward), which ECS Exec already supports in this reference architecture. - The environment variables the scripts read from the Terraform outputs. Export them from `aws/containers/ecs-dual-region-fargate`: ```bash source ./procedure/export_environment_prerequisites.sh ``` Set `AWS_PROFILE` if you don't use the default credential chain. Both scripts take `--failed-region 0|1` to name the region being failed away from and restored. For `failover.sh`, the default is `0`. `failback.sh` has no default, so you must set it. That script empties the EFS primary storage of that region. ## Fail over to the surviving region Run `failover.sh` against the region you lost: ```bash ./procedure/failover.sh --failed-region 0 ``` The script: 1. Checks that the surviving region's gateway answers on `/v2/topology`, and prints the topology before the change. 1. Scales every ECS service in the failed region to zero tasks, then waits 30 seconds for its brokers to drop out of cluster membership. 1. Sends `DELETE /actuator/cluster/zones/?force=true` through the tunnel and waits for the change to complete. 1. If the Aurora writer is in the failed region, runs a planned switchover to the surviving region and returns only after the global cluster reports the switchover complete. See [Recover when the Aurora writer's region is lost](#recover-when-the-aurora-writers-region-is-lost) if that region's Aurora cluster is gone too. 1. Confirms the zone is gone from the partition distribution and that every partition has a leader. After a successful failover, the cluster runs on the four brokers of the surviving region, with `clusterSize` 4 and `replicationFactor` 2. If `failover.sh` stops while it moves the Aurora writer, you can run it again. It waits for an Aurora switchover that is still in progress, and it doesn't start a second switchover. | Option | Effect | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--dry-run` | Sends the zone request with `dryRun=true` and prints the planned operations. ECS, the cluster, and Aurora aren't changed. | | `--keep-tasks` | Skips the ECS scale-down and makes no AWS calls to the failed region, so the zone is removed directly through the Camunda management API. Use it when the failed region's tasks are already down or its AWS API doesn't respond. The surviving region's ECS and Systems Manager APIs must still respond. | | `--keep-writer` | Skips the Aurora writer switchover. Use it when the failed region's Aurora cluster is gone. | `failover.sh` [forces the zone removal](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#remove-a-zone), so run it only when the failed region's brokers are stopped or can't reach the rest of the cluster, which `--keep-tasks` doesn't ensure. ## Recover when the Aurora writer's region is lost A planned switchover needs the failed region's Aurora cluster to still be available. If the region is gone, its Aurora cluster included, run `failover.sh` with `--keep-writer`: ```bash ./procedure/failover.sh --failed-region 0 --keep-tasks --keep-writer ``` The script removes the zone, leaves the writer where it is, and finishes. Camunda keeps processing in the surviving region, and exporting to secondary storage waits until a writer is available again. Without `--keep-writer`, `failover.sh` still removes the zone, then stops with one of these errors instead of switching the writer: ```text ERROR: the Aurora writer in is , so a planned switchover cannot run. ERROR: could not read the Aurora writer in (AWS error above). If is down, a planned switchover cannot run. ERROR: the planned switchover to did not complete. ``` The second one shows the AWS error first. If that error is about credentials or permissions, fix it, and run the script again. The third one appears when AWS still reports the old writer as available early in an outage, then rejects the switchover or doesn't finish it. If the script tells you to run it again, the switchover is still in progress. Run the script again. In the other cases, promote the surviving region's Aurora cluster with the [Aurora Global Database unplanned recovery procedure](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-global-database-disaster-recovery.html#aurora-global-database-failover). The scripts don't automate it, because it can lose data that hasn't replicated yet: 1. List the global cluster members and copy the ARN of the surviving region's cluster: ```bash aws rds describe-global-clusters \ --global-cluster-identifier "${AURORA_GLOBAL_CLUSTER_ID}" \ --query "GlobalClusters[0].GlobalClusterMembers[*].{Cluster:DBClusterArn,Writer:IsWriter}" \ --output table ``` 1. Promote that cluster with `aws rds failover-global-cluster`. Run the command in the surviving region and pass `--allow-data-loss`. Without the flag, Aurora runs a switchover, which needs a healthy writer and fails when the writer's region is lost. ```bash aws rds failover-global-cluster \ --region \ --global-cluster-identifier "${AURORA_GLOBAL_CLUSTER_ID}" \ --target-db-cluster-identifier \ --allow-data-loss ``` 1. Run the command from the first step again and confirm that the surviving cluster shows `Writer` as `True`. The Orchestration Cluster connects through the global writer endpoint, and the AWS JDBC Wrapper `failover` plugin reconnects to the new writer once the promotion completes. As described in [Secondary storage replication lag](./aws-ecs-dual-region.md#secondary-storage-replication-lag), the promoted cluster may be missing records that hadn't replicated yet. Zeebe replays that gap from its log. ## Fail back to both regions When the lost region is available again, restore it: 1. If you failed over with `--keep-tasks`, scale the failed region's ECS services to zero once its AWS API responds again. `failback.sh` scales them back up in a later step. It stops with an error if the orchestration cluster service still has desired, pending, or running tasks. ```bash # Use REGION_1 and CLUSTER_1 if region 1 failed FAILED_REGION="${REGION_0}" FAILED_CLUSTER="${CLUSTER_0}" for service in $(aws ecs list-services --region "${FAILED_REGION}" --cluster "${FAILED_CLUSTER}" --query 'serviceArns[]' --output text); do aws ecs update-service --region "${FAILED_REGION}" --cluster "${FAILED_CLUSTER}" \ --service "${service}" --desired-count 0 --no-cli-pager > /dev/null done ``` 1. Run `failback.sh` against the recovered region: ```bash # Restore region 0 and re-add its zone ./procedure/failback.sh --failed-region 0 # Also switch the Aurora writer back to region 0 ./procedure/failback.sh --failed-region 0 --switch-writer ``` The script: 1. Prints the topology before the change. 1. Makes sure the recovered region's Aurora cluster is a member of the Aurora Global Database. If an unplanned recovery left it detached but intact, the script reattaches it. If the cluster was destroyed, recreate it with `terraform apply` in `terraform/infra` and run the script again. 1. Stops the wipe tasks that an earlier run left, if any. Then it waits until every task that is stopping in the cluster stops. 1. Empties the recovered region's EFS primary storage, if its zone is absent from the partition distribution. A one-off Fargate task removes everything under `/usr/local/camunda/data`. The brokers then rejoin without the Raft state from before the failover. If the zone is still present, the script keeps the data. 1. Scales the recovered region's ECS services back up, to four orchestration cluster tasks and one Connectors task. 1. Waits for the recovered brokers to rejoin cluster membership. 1. Sends `POST /actuator/cluster/zones/` with `numberOfReplicas`, `priority`, and `numberOfBrokers`, then waits for the partition redistribution to complete. 1. Verifies the expected broker count, that every partition has a leader, and that none of the recovered brokers is idle. 1. With `--switch-writer`, moves the Aurora writer back to the recovered region with a planned switchover. The failback needs a clean region, as the [dual-region operational procedure](/self-managed/deployment/helm/operational-tasks/dual-region-ops.md) describes for Kubernetes. On ECS, the old data stays on EFS after the failover, so `failback.sh` removes it. Use `--keep-data` only if you emptied that storage yourself. If the wipe task does not stop within `WIPE_TIMEOUT` seconds (default `3600`), the script stops it and exits. Increase `WIPE_TIMEOUT` for a large data directory, then run the script again. If `failback.sh --switch-writer` stops while it moves the Aurora writer back, you can run it again. It waits for an Aurora switchover that is still in progress. If the writer is already in the recovered region, it doesn't start a second switchover. | Option | Default | Effect | | ----------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `--switch-writer` | Off | Moves the Aurora writer back to the recovered region after the zone is restored. | | `--dry-run` | Off | Sends the zone request with `dryRun=true` and prints the planned operations. ECS, the cluster, and Aurora aren't changed. | | `--keep-data` | Off | Keeps the EFS primary storage of the recovered region. The script doesn't run the wipe task. | | `--replicas N` | `2` | `numberOfReplicas` for the zone. Matches `replication_factor / 2` in `terraform/app/locals.tf`. | | `--brokers N` | `4` | `numberOfBrokers` for the zone. Also sets the task count of the orchestration cluster service. | | `--priority N` | `1000` for region 0, `500` for region 1 | Zone `priority`. Matches `CAMUNDA_CLUSTER_PARTITIONING_ZONEAWARE_ZONES_*_PRIORITY` in `terraform/app/locals.tf`. | Keep the defaults unless you changed the cluster sizing in `terraform/app/locals.tf`. After a successful failback, the topology summary that `failback.sh` prints shows eight brokers, eight partitions, 32 partition replicas, and a leader for every partition. Confirm the deployment is healthy in both regions: ```bash ./procedure/verify_dual_region.sh ``` --- ## Dual-region setup (ECS Fargate) This reference architecture deploys Camunda 8 Self-Managed across two AWS regions on ECS Fargate in an active-active configuration, with Aurora Global Database as secondary storage and Camunda 8.10 unified `/v2/*` REST API. :::note Reference architecture This guide covers the **Orchestration Cluster** and Connectors. ::: ## What you get The reference architecture creates two identically configured ECS Fargate clusters, one per AWS region, with Zeebe brokers distributed across both regions. - Active-active deployment across two AWS regions (default `eu-west-2` and `eu-west-3`; pick your own pair). - Eight Zeebe brokers (four per region) with `cluster_size=8`, `replication_factor=4`, and `partition_count=8`. Asymmetric initial contact points use ECS Service Connect locally and the cross-region NLB for inter-region traffic. - Aurora Global Database with a single writer endpoint per cluster, and with the [AWS JDBC Wrapper](https://github.com/aws/aws-advanced-jdbc-wrapper) `failover` plugin enabled for automatic reconnection after a writer change. PostgreSQL is the default engine; MySQL is available through [`db_engine`](#secondary-storage-engine). - A zone-aware Zeebe cluster: each AWS region is a zone, and broker IDs take the form `_` (for example, `eu-west-2_0`). Failover and failback add or remove a whole zone through the [Zones API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#zones-api). - Cross-region connectivity via [VPC peering](https://docs.aws.amazon.com/vpc/latest/peering/what-is-vpc-peering.html) (recommended default) or [AWS Transit Gateway](https://aws.amazon.com/transit-gateway/) for Enterprise scenarios. - [Route 53 Resolver](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resolver.html) endpoints to forward Cloud Map service-discovery DNS queries across regions. - Camunda 8.10 or later. The unified Orchestration Cluster `/v2/*` REST API requires Basic authentication; see [Verify connectivity to Camunda 8](#verify-connectivity-to-camunda-8). :::note Active-active scope Active-active in this guide refers to the Zeebe data plane: one stretched cluster whose brokers and partitions live in both regions, accepting and processing work concurrently from either region. The Aurora-backed secondary storage tier is active-standby: region 0 hosts the writer and region 1 hosts a cross-region reader. Promoting region 1 to writer is an explicit operator step during failover, not an automatic property of the deployment. See [Recover when the Aurora writer's region is lost](./aws-ecs-dual-region-ops.md#recover-when-the-aurora-writers-region-is-lost). ::: ![Clients reach the Ingress in each of two AWS regions, eu-west-2 and eu-west-3, over HTTP(S) and gRPC. Each Ingress forwards to an Orchestration Cluster of four Zeebe brokers on ECS Fargate, and the two clusters exchange Zeebe Raft traffic. Each region has an S3 bucket for the broker-ID lease. Only region 0 has an S3 backup bucket, which both regions write to. Both regions connect to the writer cluster of an Aurora Global Database in eu-west-2, which replicates to a reader cluster in eu-west-3.](./assets/aws-ecs-dual-region.svg) Both regions write backups to the single S3 backup bucket in region 0. Each region keeps its own S3 bucket for the broker-ID lease. Both regions connect to the Aurora global writer endpoint in region 0, and the writer replicates to the reader cluster in region 1. :::note This reference architecture is not a turnkey module. Clone the repository and adapt it to your environment — you are responsible for operating and maintaining the resulting infrastructure. ::: ## Prerequisites ### AWS permissions Your AWS IAM principal needs permissions for the following services in both target regions: - ECS (clusters, task definitions, services) - RDS (Aurora Global, DB clusters, parameter groups) - EC2 (VPCs, subnets, security groups, Transit Gateway or VPC peering) - ELB (ALB, NLB, target groups) - IAM (roles, policies, instance profiles) - KMS (key creation and grants) - S3 (bucket creation and policy) - EFS (file systems, mount targets) - CloudWatch Logs (log groups) - Secrets Manager (secret creation) - Systems Manager Session Manager (`ssm:StartSession`), required for the [Session Manager access path](#method-b--session-manager-port-forward) and the [failover and failback scripts](./aws-ecs-dual-region-ops.md) - Route 53 Resolver — required only when `enable_cross_region_dns_resolver = true`: `route53resolver:CreateResolverEndpoint`, `route53resolver:CreateResolverRule`, `route53resolver:AssociateResolverRule` ### AWS service quotas Dual-region deployments may require quota increases. Before deploying, verify the following quotas in both regions and request increases as needed: - Aurora Global Database (some accounts require a support request to enable Aurora Global). - Elastic IPs (NAT gateways consume one per AZ per region). - Transit Gateway attachments (default account limit). - Fargate vCPU quota per region. - VPC count per region. ### Tooling | Tool | Purpose | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `terraform` | Infrastructure provisioning. The infra layer requires Terraform 1.9 or later. Pin to the version in [`.tool-versions`](https://github.com/camunda/camunda-deployment-references/blob/main/.tool-versions). | | `aws` CLI v2 | AWS resource inspection and authentication. | | `jq` | JSON parsing in verification commands and the failover and failback scripts. | | `session-manager-plugin` | Required for the [Session Manager access path](#method-b--session-manager-port-forward) and for the [failover and failback scripts](./aws-ecs-dual-region-ops.md). Install with `brew install --cask session-manager-plugin` on macOS or follow the [AWS instructions]. | | `just` (optional) | Task runner for common operations in the reference repository. | | `asdf` (optional) | Tool version management. | [AWS instructions]: https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html Configure valid AWS credentials before starting. The [AWS Terraform provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration) supports several authentication methods: - For development or testing, configure the AWS CLI — Terraform automatically detects and uses those credentials: ```bash aws configure ``` - For production, export credentials as environment variables: `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`. ### Obtain a copy of the reference architecture Download a copy of the reference architecture from the [GitHub repository](https://github.com/camunda/camunda-deployment-references). The reference architectures are versioned according to Camunda releases (for example, `stable/8.x`). The copy lets you reuse and extend the provided Terraform examples without the constraints of a third-party-maintained module: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/containers/ecs-dual-region-fargate/procedure/get-your-copy.sh ``` With the reference architecture in place, you can proceed with the remaining steps. Make sure you're in the correct directory before continuing. ## Terraform layout The reference architecture splits infrastructure into three independent state layers. Deploy them in order; each layer reads the previous layer's outputs via `terraform_remote_state`. ``` terraform/ ├── vpc/ ← VPCs + cross-region networking. Supports BYO-VPC. ├── infra/ ← Aurora Global, ECS clusters, ALB/NLBs, KMS, S3, secrets, IAM. └── app/ ← Camunda task definitions + ECS services. ``` | Layer | Directory | Contents | Change frequency | | ----- | ------------------ | --------------------------------------------------------------------------------------------- | ---------------- | | VPC | `terraform/vpc/` | VPCs, subnets, NAT gateways, Transit Gateway or VPC peering, optional Route 53 Resolver | Low | | Infra | `terraform/infra/` | Aurora Global Database, ECS clusters, ALB, NLB, KMS, S3, EFS, Secrets Manager, IAM | Low | | App | `terraform/app/` | Camunda orchestration cluster and Connectors task definitions, plus the matching ECS services | High | ### Terraform state backend All three layers keep their state in one S3 bucket that you create beforehand. The state key of each layer is `/terraform.tfstate`, and the infra and app layers read the previous layer's state from that key through `terraform_remote_state`. All three layers must therefore use the same bucket and key prefix. [Step 1](#step-1--configure) sets them once for every layer. ## Deploy time and cost Wall-clock time for a greenfield deploy to a first healthy `/v2/topology` response with eight brokers: | Phase | Wall clock | What's slow | | -------------- | ---------------------- | ------------------------------------------------------------------------------------------ | | `vpc/ apply` | 3–5 min | VPC creation plus cross-region peering or Transit Gateway attachment. | | `infra/ apply` | 15–20 min | Aurora Global Database creation (primary first, then secondary attaches). | | `app/ apply` | ~30 s plan + 15–20 min | ECS service rollout waits for steady state; first cross-region Raft quorum takes the most. | | **Total** | **35–45 min** | | `terraform destroy` is faster: about 15–20 minutes end-to-end, with Aurora teardown again the bottleneck. Actual costs depend on region, instance sizing, commit discounts, and cross-region data egress. Use the [AWS Pricing Calculator](https://calculator.aws/#/) to estimate for your configuration. ## Architecture decisions Make these decisions before running any `terraform apply`. They affect every layer of the deployment. ### Networking mode | Option | `networking_mode` value | When to use | | --------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | VPC peering (default) | `vpc_peering` | Simpler to manage. Start here if you don't have a cross-region network in place. | | Transit Gateway | `transit_gateway` | Enterprise scenarios — existing Transit Gateway deployments or hub-and-spoke topologies that need to integrate this cluster. Choose this only when you actually need it and understand the hourly and per-GB charges. | :::note Neither Transit Gateway nor VPC peering encrypts traffic at the network layer. Raft replication between Zeebe brokers crosses the AWS backbone in cleartext unless you add an encryption layer (for example, IPsec on the TGW attachment, or application-layer TLS on the Zeebe broker channel). Regulated workloads should evaluate this before choosing. ::: ### VPC source | Option | `byo_vpc` value | When to use | | ---------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | Greenfield | `false` (default) | Terraform creates two VPCs, subnets across three availability zones, NAT gateways, internet gateways, and the cross-region link. | | BYO-VPC | `true` | You supply existing VPCs and subnets. Terraform skips VPC creation but still provisions the cross-region link and optional DNS resolver. | BYO-VPC is the preferred path for customers integrating with an existing AWS landing zone. Supply the following per region (replace `N` with `0` or `1`): | Variable | Constraint | | ---------------------------------- | ---------------------------------------------------------------------------------------------- | | `region_N_vpc_id` | Existing VPC ID (`vpc-xxxxxxxx`). | | `region_N_vpc_cidr` | CIDR of the existing VPC. | | `region_N_private_subnet_ids` | At least three private subnet IDs in distinct AZs (used by ECS tasks and Aurora). | | `region_N_public_subnet_ids` | At least three public subnet IDs in distinct AZs with an internet gateway route (used by ALB). | | `region_N_private_route_table_ids` | At least one private route table ID per region (for cross-region routes). | The full validation contract — including the plan-time checks that fail with a descriptive error when a constraint is missing — lives in [`terraform/vpc/README.md`](https://github.com/camunda/camunda-deployment-references/blob/main/aws/containers/ecs-dual-region-fargate/terraform/vpc/README.md) in the reference repository. ### Secondary storage engine The `db_engine` variable in `terraform/infra/terraform.tfvars` selects the Aurora engine for RDBMS secondary storage. It drives the Aurora clusters, the security group rules, the IAM database user seeding, and the generated JDBC URL. Choose the engine before the first `terraform apply`. Camunda doesn't support in-place migration between secondary storage backends, so switching engines later means creating a new deployment. | `db_engine` value | Aurora engine | Port | | ---------------------- | ------------------- | ---- | | `postgresql` (default) | `aurora-postgresql` | 5432 | | `mysql` | `aurora-mysql` | 3306 | :::warning Changing `db_engine` on an existing deployment replaces the global cluster and both regional clusters without a final snapshot. All secondary storage data is permanently lost. ::: The published Camunda image doesn't include the MySQL JDBC driver. To use `db_engine = "mysql"`, build a custom image that adds it. See [user-supplied drivers](/self-managed/deployment/manual/rdbms/configuration.md#user-supplied-drivers-oracle-mysql). ### Secondary storage replication lag Aurora Global Database replicates asynchronously, so promoting a new writer can leave it missing whatever had not reached it yet. Camunda retains the source records needed to recover that gap; Aurora replication alone does not prevent it. Exporting and acknowledging are separate steps. The RDBMS exporter writes a record to the Aurora writer, then tells the broker the record is safe only after the required replica quorum confirms the flush marker. This architecture sets `min-sync-replicas` to one, so the single Aurora reader must confirm the marker before the exporter acknowledges the position. Until then, the record continues to occupy the Zeebe log. Holding that position back is enough to keep segments on disk; releasing them is not this exporter's decision alone, since [compaction](/self-managed/concepts/exporters.md) tracks the slowest consumer on the partition. If the required replica falls behind, acknowledgement is held back and the Zeebe log grows. The replica catches up from the writer, not from Zeebe. The retained records matter when the writer itself is lost: the promoted reader resumes from its own position, and Zeebe replays the gap. The reference architecture pins four properties under `camunda.data.secondary-storage.rdbms.`, shortened in the table below. [Multi-region support](/self-managed/concepts/databases/relational-db/configuration.md#multi-region-support) documents what each one does, including which vendors support `LOG_SEQ` and how the `DELAY` alternative behaves. The table records only which values this architecture picks and why. | Setting | Value | Why this value here | | --------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `async-replication.enabled` | `true` | Off by default. This architecture delegates replication to Aurora and treats a writer failover as routine, so the monitoring is not optional. | | `async-replication.type` | `LOG_SEQ` | The preferred strategy, and Aurora Global Database with PostgreSQL supports it. The `DELAY` fallback would add a static wait to every acknowledgement instead. | | `async-replication.max-lag` | `PT1H` | Sized for a cross-region promotion under load, which runs past the `PT15M` default. | | `async-replication.pause-on-max-lag-exceeded` | `false` | The engine default, kept deliberately. | `max-lag` is pinned even though pausing is off, so the budget is already sized if you turn pausing on later, which is then a one-line change. Under `LOG_SEQ`, this value is compared with the age of the oldest exporter position still waiting for confirmation, not with a lag figure reported by Aurora. Turn pausing on only once you have alerting on replication lag. It makes a stall loud, because the exporter logs a warning and every later export raises an `ExporterException`, but writes to Aurora stop, so secondary storage stays stale until replication recovers. It protects nothing that acknowledgement does not already protect, since a record is reported safe to the broker only after confirmed replication either way. EFS is elastic rather than a fixed-size volume, but a long outage still increases stored data, throughput use, and cost. Monitor EFS storage growth and throughput, and alert on replication lag. An unsupported vendor or a non-global Aurora instance fails while the exporter is starting, and the message names the reason, so the deployment never comes up quietly without the replication signal. Later failures differ by where they happen. A replication status read that fails is logged and retried at the next poll. A failure to capture the replication marker while flushing pauses exporting instead, until the periodic checks recover. On the Aurora path the database privileges are exercised by those reads rather than checked at startup. ## Deployment walkthrough ### Step 1 — Configure Set the Terraform state backend once, from `aws/containers/ecs-dual-region-fargate`, in the shell you use for the following steps. The infra and app layers read the `TF_VAR_terraform_backend_*` variables, and `terraform init` reads `backend.hcl` in every layer: ```bash export TF_VAR_terraform_backend_bucket="" export TF_VAR_terraform_backend_region="" # defaults to eu-central-1 export TF_VAR_terraform_backend_key_prefix="/" # same prefix for all three layers cat > backend.hcl <"] # defaults to 0.0.0.0/0; restrict to the CIDR range that should reach the load balancers registry_username = "" # optional; only needed for images from registry.camunda.cloud registry_password = "" ``` #### `terraform/app/terraform.tfvars` ```hcl aws_profile = "" # optional; omit when authenticating via env vars region_0 = "" # must match the vpc and infra layers region_1 = "" camunda_image = "registry.camunda.cloud/camunda/camunda:" # 8.10 or later connectors_image = "camunda/connectors-bundle:" # pulled from Docker Hub without registry credentials default_tags = { Environment = "reference", Team = "" } ``` ### Step 2 — Deploy VPC ```bash cd terraform/vpc terraform init -backend-config=../../backend.hcl \ -backend-config="key=${TF_VAR_terraform_backend_key_prefix}vpc/terraform.tfstate" terraform plan terraform apply ``` - **Greenfield:** creates two VPCs, six subnets (three private and three public per region), NAT gateways, internet gateways, and the cross-region link. Expect 3–5 minutes. - **BYO-VPC:** creates only the cross-region link and optional DNS resolver. Expect under 1 minute. ### Step 3 — Deploy infra ```bash cd ../infra terraform init -backend-config=../../backend.hcl \ -backend-config="key=${TF_VAR_terraform_backend_key_prefix}infra/terraform.tfstate" terraform plan terraform apply ``` This layer creates the Aurora Global Database, ECS clusters, load balancers, KMS keys, S3 buckets, EFS file systems, Secrets Manager secrets, and IAM roles. :::note Aurora Global Database creation takes 15–20 minutes. If Terraform reports a timeout, first check the Aurora cluster status in the AWS console or with `aws rds describe-global-clusters` before re-running `terraform apply`. A real failure (quota exceeded, KMS grant failure, IAM permission gap) does not resolve by re-running and will repeat each cycle. Only re-run when the cluster status is `creating` or `available`. ::: After `apply` completes, a one-time `db_seed` ECS task runs automatically to create an IAM-authenticated `camunda` database user. The orchestration cluster connects to Aurora as this user using its ECS task role (no password). Monitor the seed task in CloudWatch Logs at `/ecs/-r0-db-seed`. A successful run ends with the task exiting with status code `0`; if the task fails, re-running `terraform apply` re-triggers it. ### Step 4 — Deploy app ```bash cd ../app terraform init -backend-config=../../backend.hcl \ -backend-config="key=${TF_VAR_terraform_backend_key_prefix}app/terraform.tfstate" terraform plan terraform apply ``` `terraform apply` itself completes in approximately 30 seconds. The actual wait is ECS reaching steady state and the eight Zeebe brokers forming a Raft quorum, which can take up to 20 minutes on a cold start in a dual-region setup. If the cluster has not stabilized after 30 minutes, treat it as a real failure rather than continued patience and investigate (see below). :::note The `wait_for_steady_state` provider timeout may expire before all brokers stabilize. Before concluding success, verify cluster health with two checks: 1. In the ECS console for each region, confirm both the orchestration cluster and Connectors services show **steady state** with the expected running task count (four orchestration tasks per region, one Connectors task per region) and no recent task failures in the **Events** tab. 2. Run the `/v2/topology` check from Step 5 and confirm eight brokers are visible with healthy partitions. If either check fails, do not proceed to verification. Inspect CloudWatch Logs for the orchestration-cluster log group and the ECS service event stream. The most common causes are image pull failures, IAM or Secrets Manager misconfiguration, and cross-region security group rules blocking ports 26500–26502. ::: ### Step 5 — Verify Run the helper script from the reference repository to validate that the deployment is healthy in both regions. The script checks ECS service counts, the Zeebe topology, and Aurora Global Database status, and starts a test process instance. It sources `procedure/export_environment_prerequisites.sh` automatically to read the Terraform outputs: ```bash cd ../../ # back to aws/containers/ecs-dual-region-fargate ./procedure/verify_dual_region.sh ``` When `enable_cross_region_dns_resolver = true`, also confirm that cross-region service-discovery DNS works: ```bash source ./procedure/export_environment_prerequisites.sh ./procedure/test_cross_region_dns.sh ``` To check the cluster yourself, retrieve the admin password and ALB endpoint from the infra layer, then call `/v2/topology` — Camunda 8.10 requires Basic authentication on all `/v2/*` endpoints: ```bash cd terraform/infra ALB_R0=$(terraform output -raw region_0_alb_endpoint) ADMIN_PASS=$(terraform output -raw admin_user_password) curl -s -u "admin:${ADMIN_PASS}" "http://${ALB_R0}/v2/topology" | jq '.brokers | length' # Expected: 8 once Raft has settled ``` Confirm the full topology response shows: - Eight brokers in `brokers`. - Eight partitions across the cluster. - `replicationFactor` equal to 4. - All partition roles are `leader` or `follower` (all lowercase; no `null` entries). - Zero unhealthy partitions. Also verify Aurora Global health: ```bash aws rds describe-global-clusters \ --query "GlobalClusters[*].{Status:Status,Members:GlobalClusterMembers[*].IsWriter}" \ --output table ``` The output must show `Status=available` with two members, one writer and one reader. :::warning The verification commands use `http://` because TLS is not configured by default. HTTP transmits Basic authentication credentials and process data in cleartext. Before exposing the cluster to any non-trusted network, attach a TLS certificate to the ALB (see [Next steps](#next-steps)) and rerun the verification over `https://`. ::: ### Step 6 — Cleanup If you failed over and the Aurora writer is still in region 1, move it back first with `./procedure/failback.sh --failed-region 0 --switch-writer` (see [Fail back to both regions](./aws-ecs-dual-region-ops.md#fail-back-to-both-regions)). Then destroy resources in reverse order to respect layer dependencies: ```bash cd terraform/app && terraform destroy cd ../infra && terraform destroy cd ../vpc && terraform destroy ``` :::warning Cleanup caveat The reference architecture sets `s3_force_destroy = true` by default so `terraform destroy` removes backup S3 buckets without manual emptying. Flip `s3_force_destroy` to `false` in `terraform/infra/terraform.tfvars` and re-apply the infra layer before running any real workload through the stack. Otherwise, `terraform destroy` will delete backup data permanently. ::: If `terraform destroy` fails, check the teardown inputs `s3_force_destroy` and `db_force_destroy` in the [infra layer README](https://github.com/camunda/camunda-deployment-references/blob/main/aws/containers/ecs-dual-region-fargate/terraform/infra/README.md). ## Verify connectivity to Camunda 8 Using Terraform, you can obtain the HTTP endpoint of each Application Load Balancer and interact with Camunda through the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). :::warning HTTPS To keep dependencies minimal and non-blocking for a quick start, this reference architecture omits a custom domain and TLS configuration. You can add TLS by attaching an AWS Certificate Manager (ACM) certificate to each Application Load Balancer. For details, see the AWS documentation on [creating an HTTPS listener](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html). Information on configuring a custom domain is available in the [Application Load Balancer documentation](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html#dns-name). Without these additions, traffic is transmitted in cleartext and is therefore insecure. ::: 1. Navigate to the infra Terraform folder: ```bash cd terraform/infra ``` 2. Retrieve an ALB endpoint. The dual-region setup is active-active, so either region's ALB works: ```bash terraform output -raw region_0_alb_endpoint terraform output -raw region_1_alb_endpoint ``` Both ALBs expose the Orchestration Cluster and Connectors through the same port and use listener rules to determine the path they're on: - ALB:80 - `/*` routes to the Orchestration Cluster UI and REST API. - `/connectors*` routes to the Connectors. - ALB:9600 - Not forwarded to the management API (see [Endpoint reference](#endpoint-reference)). - Connectors combines the management port with the web server by default. - NLB:26500 (TCP) - Exposes the Orchestration Cluster Zeebe Gateway over gRPC. Retrieve the endpoint with `terraform output -raw region_0_nlb_grpc_endpoint` or `terraform output -raw region_1_nlb_grpc_endpoint`. 3. Access the URL of `region_0_alb_endpoint` (or `region_1_alb_endpoint`), which presents a login screen. The admin user is `admin`. The password is randomly generated and shared between regions. Retrieve it with: ```bash terraform output -raw admin_user_password ``` 4. Use the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) to communicate with Camunda. Follow the [authentication example](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md) to authenticate and retrieve the cluster topology. ### Seeded users Camunda 8.10 requires Basic authentication on the unified `/v2/*` REST API. Two users are seeded at first boot: - `admin` — full access; use this user to log into Operate, Tasklist, and other Web UIs. - `connectors` — used by the Connectors bundle to call the orchestration cluster. Both passwords are auto-generated (32 random characters) and stored in AWS Secrets Manager. They are not `demo:demo`, matching the single-region ECS Terraform reference. ### Retrieve credentials The admin password is generated once during the infra layer apply and shared by both regions: ```bash cd terraform/infra ADMIN_PASS=$(terraform output -raw admin_user_password) echo "admin / $ADMIN_PASS" ``` The Connectors password is also stored in Secrets Manager. Retrieve it with the AWS CLI: ```bash SECRET_ARN=$(terraform output -raw connectors_password_secret_region_0_arn) CONNECTORS_PASS=$(aws secretsmanager get-secret-value \ --secret-id "$SECRET_ARN" \ --query SecretString --output text) ``` You can also locate the secrets in the AWS console at **Secrets Manager > `-r0-oc-admin-user-password-*`**. ### Method A — direct via ALB Use the ALB when your source IP is in `limit_access_to_cidrs`. This is the easiest path for an internet-facing demo. ```bash ALB_R0=$(terraform output -raw region_0_alb_endpoint) # Topology (auth required on 8.10+) curl -s -u "admin:${ADMIN_PASS}" "http://${ALB_R0}/v2/topology" | jq '.brokers | length' # Expected: 8 (four brokers per region once Raft has settled) # Open Operate in a browser open "http://${ALB_R0}/operate" ``` If `limit_access_to_cidrs` is restricted to a corporate CIDR your laptop is not in, use [Method B](#method-b--session-manager-port-forward). ### Method B — Session Manager port-forward Use this method when the ALB is not reachable from your machine — for example, a private deployment, a locked-down CIDR allow-list, or a customer audit requirement that forbids opening a public endpoint. The session piggybacks on the ECS Exec channel; no bastion host is needed. Requirements: - `task_enable_execute_command = true` on the orchestration cluster module. The reference architecture sets this to `true` by default — see [`terraform/app/camunda.tf`](https://github.com/camunda/camunda-deployment-references/blob/main/aws/containers/ecs-dual-region-fargate/terraform/app/camunda.tf). - The task IAM role must allow `ssmmessages:CreateControlChannel`, `ssmmessages:CreateDataChannel`, `ssmmessages:OpenControlChannel`, and `ssmmessages:OpenDataChannel`. The `ecs_exec_policy` in the orchestration-cluster module already grants these. - [AWS Session Manager plugin](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html) installed locally (`brew install --cask session-manager-plugin` on macOS). Start the port-forwarding session: ```bash # 1. Pick a running orchestration-cluster task in region 0 CLUSTER=$(cd terraform/infra && terraform output -raw cluster_name) TASK_ARN=$(aws ecs list-tasks \ --cluster "${CLUSTER}-r0-cluster" \ --service-name "${CLUSTER}-r0-oc-orchestration-cluster" \ --query 'taskArns[0]' --output text) TASK_ID=${TASK_ARN##*/} # 2. Resolve the ECS-managed runtime ID (Session Manager target) RUNTIME_ID=$(aws ecs describe-tasks \ --cluster "${CLUSTER}-r0-cluster" \ --tasks "$TASK_ID" \ --query 'tasks[0].containers[?name==`orchestration-cluster`].runtimeId' \ --output text) # 3. Start a port-forwarding session: localhost:8080 → container 8080 aws ssm start-session \ --target "ecs:${CLUSTER}-r0-cluster_${TASK_ID}_${RUNTIME_ID}" \ --document-name AWS-StartPortForwardingSession \ --parameters '{"portNumber":["8080"],"localPortNumber":["8080"]}' ``` In another shell, open the UI and log in as `admin` with `$ADMIN_PASS`: ```bash open http://localhost:8080 ``` To reach the [management API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md) on port 9600, start the same session with `{"portNumber":["9600"],"localPortNumber":["9600"]}`, then call `http://localhost:9600/actuator/...`. The [failover and failback scripts](./aws-ecs-dual-region-ops.md) open this tunnel for you. ### Endpoint reference | Endpoint | Port | Protocol | Purpose | | ------------------------- | ----- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ALB (region 0/1) | 80 | HTTP | Camunda REST API and Web UI (routes to container 8080). | | ALB (region 0/1) | 9600 | HTTP | Listener with a fixed empty response. No rule forwards it to the management API, because `enable_alb_http_management_listener_rule = false` in `terraform/app/camunda.tf`. Reach the management API through [Method B](#method-b--session-manager-port-forward). | | NLB external (region 0/1) | 26500 | TCP | Zeebe gRPC for clients. | | NLB internal (region 0/1) | 26502 | TCP | Zeebe Raft, cross-region, private. | ## Operations ### Backup and restore The general [backup and restore procedure](/self-managed/operational-guides/backup-restore/backup-and-restore.md) applies, with two dual-region specifics to keep in mind: - **Backups share one bucket.** The infra layer creates a single S3 backup bucket in region 0, exposed as the `backup_bucket_region_0_name` output. Both orchestration clusters write to it through `CAMUNDA_DATA_BACKUP_S3_BUCKETNAME`, and region 1 brokers set `CAMUNDA_DATA_BACKUP_S3_REGION` to region 0 so they reach the bucket cross-region. All backup data stays in one place, whichever region you trigger the backup from. If you lose region 0, you lose access to the backup bucket until the region recovers, so plan S3 replication yourself if you need the backups in both regions. - **Restore is not exposed by the dual-region app layer.** The underlying orchestration-cluster module supports an init-container restore (`restore_enabled`, `restore_backup_id` — see [restore options when using RDBMS](/self-managed/operational-guides/backup-restore/rdbms/restore-application.md#restore-options)), but these variables are not surfaced in `terraform/app/camunda.tf` in this reference. Enabling restore for a dual-region deployment requires customizing the app layer to pass the restore variables to both regional module invocations and to coordinate broker IDs that span both regions. Treat dual-region restore as an advanced scenario; validate it against your specific topology before relying on it. :::note Camunda recommends restoring to a fresh cluster rather than reusing an existing one. A newly created cluster has empty S3 backup buckets and EFS volumes, so no additional cleanup is needed. If you restore into an existing cluster, manually empty the S3 bucket configured for the node ID provider and fully clear the EFS volumes in both regions before starting the restore. ::: ### Failover and failback To recover from a region loss, follow the [ECS dual-region operational procedure](./aws-ecs-dual-region-ops.md). It fails over and back with the `failover.sh` and `failback.sh` scripts from the reference repository. ## Troubleshooting ### Logs ECS task logs are exported to CloudWatch by default unless you configure otherwise. They are visible in the CloudWatch console and inline in the ECS service view alongside each task. Retrieve the log group names from the app layer output: ```bash cd terraform/app terraform output -raw region_0_log_group_name terraform output -raw region_1_log_group_name ``` ### Accessing task or management API ECS tasks are not reachable from outside the VPC without a workaround. Options include: - Use the [Session Manager port-forward method](#method-b--session-manager-port-forward) to reach a running orchestration cluster task without a public IP. The reference architecture sets `task_enable_execute_command = true` by default, so the channel is already available. - Run an EC2 or ECS debug task inside the same VPC and call the [management API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md) over the private network. - Connect via an [AWS Client VPN](https://aws.amazon.com/vpn/client-vpn/) attached to the VPC. - Use Lambda or Step Functions to invoke the API. - Temporarily forward the ALB's port 9600 to the management API (not recommended for production). See [Endpoint reference](#endpoint-reference). To open an interactive shell on a running task via [AWS ECS Exec](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec-run.html): ```bash CLUSTER=$(cd terraform/infra && terraform output -raw cluster_name) TASK_ARN=$(aws ecs list-tasks \ --cluster "${CLUSTER}-r0-cluster" \ --service-name "${CLUSTER}-r0-oc-orchestration-cluster" \ --query 'taskArns[0]' --output text) aws ecs execute-command \ --cluster "${CLUSTER}-r0-cluster" \ --task "${TASK_ARN##*/}" \ --container orchestration-cluster \ --command "/bin/sh" \ --interactive ``` For general troubleshooting, see the [operational guides troubleshooting documentation](/self-managed/operational-guides/troubleshooting.md). ## Known limitations - **Experimental.** This reference architecture is intended for learning and validation. Validate it against your own requirements before you run production workloads on it. - **Manual failover only.** No automated health-check-driven failover is included. Run the [failover and failback scripts](./aws-ecs-dual-region-ops.md) yourself. - **Components.** Only the Orchestration Cluster and Connectors are deployed. Management Identity (Keycloak), Web Modeler, and Console aren't included, and Optimize isn't available with RDBMS secondary storage. - **ECS deployment circuit breaker disabled.** The circuit breaker is off for both orchestration cluster services. On a first deploy, brokers fail ECS health checks for a while as Aurora IAM authentication warms up and the cross-region Raft quorum forms, which takes about 20 minutes. The circuit breaker would roll the deployment back before the cluster recovers. With the breaker off, ECS keeps retrying until the 30-minute `service_timeouts.create` deadline. Once the cluster is stable, you can re-enable it for later deployments. ## Next steps After you have a working dual-region deployment, consider the following: - [Connect to an identity provider](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md) to integrate with an external identity system. - Add TLS by attaching an [AWS Certificate Manager (ACM) certificate](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html) to the Application Load Balancers. - Review the [dual-region concept documentation](/self-managed/concepts/multi-region/dual-region.md) for current limitations and operational considerations. - Browse the [single-region ECS Fargate guide](/self-managed/deployment/containers/cloud-providers/amazon/aws-ecs.md) for a comparison with the simpler single-region pattern. --- ## Deploy to Amazon ECS This guide walks you through deploying the [Camunda 8 Orchestration Cluster](/reference/glossary.md#orchestration-cluster) and Connectors on AWS Elastic Container Service (ECS) using Fargate and Aurora PostgreSQL, and verifying that all components are working. :::tip New to AWS ECS? If you are new to AWS ECS or Terraform, consider reviewing the [AWS ECS documentation](https://docs.aws.amazon.com/ecs/) and [Terraform documentation](https://developer.hashicorp.com/terraform/docs) before proceeding with this guide. ::: ## Prerequisites - **AWS account** – An AWS account to provision resources with permissions for **ecs**, **iam**, **elasticloadbalancing**, **kms**, **logs**, and **rds** services. - For detailed permissions, refer to this [example policy](https://github.com/camunda/camunda-deployment-references/tree/main/aws/containers/ecs-single-region-fargate/example/policy.json). - **Terraform** – Infrastructure as code tool (v1.7 or later). [Install Terraform](https://developer.hashicorp.com/terraform/install). - **AWS CLI** – Command-line tool to manage AWS resources, used for `local-exec` to trigger the initial Aurora PostgreSQL user seeding. [Install AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html). For the exact tool versions used during testing, refer to the repository's [.tool-versions](https://github.com/camunda/camunda-deployment-references/blob/main/.tool-versions) file. ### Considerations If you want a simpler setup, consider using [Camunda 8 SaaS](https://accounts.camunda.io/signup). :::note Cost management This guide provisions resources that can incur costs in your cloud provider account. Review your provider's pricing before you begin. ::: - Unlike our other guides, which usually separate infrastructure setup from the deployment of Camunda 8, this is not the case with ECS. Since the infrastructure is largely managed by AWS, deploying Camunda 8 and provisioning the required AWS resources happens in a single step. - This guide focuses on AWS ECS with Fargate but can work with managed instances for more predictable performance. You can find more information about how to migrate from Fargate to managed instances from the [AWS migration guide](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/migrate-fargate-to-managed-instances.html). - This guide relies on a shared [multi-AZ replicated](https://docs.aws.amazon.com/efs/latest/ug/efs-replication.html) EFS network disk. - Cost and performance may differ from a related Kubernetes setup with block storage. - The EFS volume is shared among all brokers to support the native ECS Service capabilities. - AWS does not support block storage options in combination with ECS Services and Fargate. For a detailed overview, have a look at the [AWS documentation](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_data_volumes.html). - Scaling is a manual process as it requires invoking the [cluster scaling API](/self-managed/components/orchestration-cluster/zeebe/operations/cluster-scaling.md) for joining and removing a [Zeebe Broker](../../../../../components/zeebe/technical-concepts/architecture.md#brokers). Autoscaling may not have effects as the brokers have to be explicitly joined into the [Zeebe cluster](../../../../../components/zeebe/technical-concepts/clustering.md) or when removed result in partitions or data becoming inaccessible. - A node-id provider is integrated into Zeebe that assigns an available node-id based on Zeebe cluster information, instead of relying on a statically-configured node-id. - This guide focuses on Aurora PostgreSQL for the secondary datastorage as it is a newly supported offering by Camunda 8 and potentially more familiar for customers. - You may still use Elasticsearch or OpenSearch but need to adjust the required configuration. More information about the configuration can be found in [our documentation](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#data---secondary-storage). - Examples for how to deploy AWS OpenSearch can be found in other existing reference architectures for AWS. :::warning Reference architectures and examples provided in this guide are not turnkey modules. Camunda recommends cloning the repository and modifying it locally. You are responsible for operating and maintaining the infrastructure. Camunda updates the reference architecture over time, and changes may not be backward compatible. You can use these updates to upgrade your customized codebase as needed. ::: ### Outcome The result is a fully functioning Camunda Orchestration Cluster deployed in a high-availability setup using AWS ECS with Fargate and a managed Aurora PostgreSQL instance using IAM authentication. All ECS tasks share a single EFS volume dedicated to Camunda. #### Architecture The architecture outlined below describes a standard Zeebe three-node deployment, distributed across three [availability zones](https://aws.amazon.com/about-aws/global-infrastructure/regions_az/) within a single AWS region. It includes a managed Aurora PostgreSQL instance deployed under the same conditions. This approach ensures high availability and redundancy in case of a zone failure. ![A single AWS region with one VPC across three availability zones. Users and API clients reach an NLB over gRPC and an ALB over HTTP(S). The NLB forwards gRPC to the Orchestration Cluster, and the ALB routes to the ECS tasks. The Orchestration Cluster, Connectors, and the optional Management Identity and Camunda Hub tasks register with Cloud Map for service discovery. The Orchestration Cluster uses EFS over NFS, an S3 bucket for node IDs, an S3 bucket for backups, and Aurora PostgreSQL. Management Identity and Camunda Hub also use Aurora PostgreSQL.](./assets/aws-ecs-single-region.svg) _Infrastructure diagram for the single-region ECS architecture. Management Identity and Camunda Hub are optional and are only deployed when you enable OIDC authentication._ Users and API clients reach the load balancers, which route to the ECS services, and each service connects to the data services it uses: the Orchestration Cluster uses EFS, the S3 node-ID bucket, the S3 backup bucket, and the `camunda` database, while Management Identity and Camunda Hub each use a dedicated database on the same Aurora cluster. All four services register with [AWS Cloud Map](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-connect.html) for internal service-to-service discovery, so they reach each other by name inside the VPC without going through the load balancer. Every component also reads its credentials from AWS Secrets Manager and writes logs to Amazon CloudWatch, which are omitted from the diagram to keep it readable. After completing this guide, you will have: - A [Virtual Private Cloud](https://docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.html) (VPC), which is a logically isolated virtual network. - A [Private Subnet](https://docs.aws.amazon.com/vpc/latest/userguide/configure-subnets.html), which does not have direct internet access. - [Elastic Container Service (ECS) Cluster](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/clusters.html) - ECS Services for the Orchestration Cluster and Connectors, and optionally for Management Identity and Camunda Hub - These spawn ECS tasks running on [Fargate](https://aws.amazon.com/fargate/) - [Elastic File System (EFS)](https://aws.amazon.com/efs/) as primary datastore for the Zeebe cluster - [Aurora PostgreSQL](https://aws.amazon.com/rds/aurora/) as secondary datastore - A [Public Subnet](https://docs.aws.amazon.com/vpc/latest/userguide/configure-subnets.html), which has internet access via an [Internet Gateway](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Internet_Gateway.html). - (Optional) An [Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html) (ALB) to expose web interfaces such as Operate, Tasklist, Connectors, and the Orchestration Cluster REST API. This uses sticky sessions, as requests are otherwise distributed round-robin across ECS instances. - (Optional) A [Network Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/introduction.html) (NLB) to expose the gRPC endpoint of the Zeebe Gateway, if external applications need to connect. - [Security Groups](https://docs.aws.amazon.com/vpc/latest/userguide/vpc-security-groups.html) to control network traffic to and from the ECS instances. - An [Internet Gateway](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Internet_Gateway.html) to route traffic between the VPC and the internet. - An [S3 bucket](https://aws.amazon.com/s3/) used by the Orchestration Cluster’s ECS-specific node-id provider. - A versioning-enabled [S3 bucket](https://aws.amazon.com/s3/) for backups. - Use a separate bucket for backups. The node-id bucket has versioning disabled because frequent metadata changes would incur additional cost without any benefit. - [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) for application credentials and optional container registry credentials. - [AWS CloudWatch](https://aws.amazon.com/cloudwatch/) for logs. - [ECS Service Connect](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-connect.html) to connect ECS services directly with each other. - [IAM authentication](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html) to connect the Orchestration Cluster with the Aurora PostgreSQL cluster. Both subnet types are distributed across three availability zones in a single AWS region, supporting a high-availability architecture. You can also scale this setup to a single ECS task. In that case, a zone failure makes the environment unavailable. ## Configure AWS and initialize Terraform :::note Terraform infrastructure example We do not recommend using the following Terraform-based infrastructure as a module, since we cannot guarantee compatibility. Instead, we suggest reusing or extending components of the Terraform example to ensure alignment with your environment. ::: ### Obtain a copy of the reference architecture Start by downloading a copy of the reference architecture from the GitHub repository. This content will be used throughout the rest of the guide. The reference architectures are versioned according to Camunda releases (e.g., stable/8.x). The reference architecture repository allows you to reuse and extend the provided Terraform examples. This flexible implementation avoids the constraints of relying on third-party-maintained Terraform modules: ```bash reference https://github.com/camunda/camunda-deployment-references/tree/main/aws/containers/ecs-single-region-fargate/procedure/get-your-copy.sh ``` With the reference architecture in place, you can proceed with the remaining steps in this documentation. Make sure you're in the correct directory before continuing with the instructions. ### Terraform prerequisites To manage Camunda 8 infrastructure on AWS using Terraform, you need to configure Terraform's backend to store the state file remotely in an S3 bucket. This provides secure, persistent primary storage for your infrastructure. :::note Advanced users may choose to configure a different backend. The setup described here is a recommended starting point for new users. ::: #### Set up AWS authentication The [AWS Terraform provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) is required to provision resources in AWS. Before using the provider, you must authenticate it with your AWS credentials. :::caution Ownership of the created resources Any user who creates AWS resources retains administrative access to them. For better control and security, it is recommended to create a dedicated [AWS IAM user](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users.html) specifically for Terraform. This ensures the resources are properly managed and owned by a single identity. ::: You can customize the region and authentication settings as needed. Terraform supports multiple [authentication methods](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration), including: - For development or testing, you can use the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html). If your AWS CLI is already configured, Terraform will automatically detect and use those credentials. To configure the AWS CLI: ```bash aws configure ``` Enter your `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, region, and output format. These can be retrieved from the [AWS Console](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html). - For production environments, it is recommended to use a dedicated IAM user. Create [access keys](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) for that user via the AWS console, and export them as environment variables: `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`. #### Create an S3 bucket for Terraform state management Before initializing Terraform, you must create an S3 bucket to store the state file. This is essential for collaborative work and helps prevent issues such as state file corruption. Begin by setting your preferred AWS region as an environment variable to avoid repeating it in every command: ```bash export AWS_REGION= ``` Replace `` with your chosen AWS region (e.g., `eu-central-1`). Next, follow these steps to create an S3 bucket with versioning enabled: 1. Open your terminal and ensure that the AWS CLI is installed and properly configured. 2. Run the following command to create an S3 bucket for storing your Terraform state. Be sure to choose a unique bucket name, and ensure that the `AWS_REGION` environment variable is already set: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/s3-bucket/s3-bucket-creation.sh ``` 3. Enable versioning on the S3 bucket to track changes and protect the state file from accidental deletions or overwrites: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/s3-bucket/s3-bucket-versioning.sh ``` 4. Secure the bucket by blocking public access: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/s3-bucket/s3-bucket-private.sh ``` 5. Verify versioning is enabled on the bucket: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/s3-bucket/s3-bucket-verify.sh ``` The S3 bucket is now ready to securely store your Terraform state files, with versioning enabled for added protection. #### Initialize Terraform Once authentication is configured, you can initialize your Terraform project. Earlier, you created a dedicated S3 bucket (`S3_TF_BUCKET_NAME`) for storing the state file. In this step, Terraform will use that bucket along with a specific key to manage your infrastructure state. Initialize the backend and download the required provider plugins: :::note Make sure you are in the `terraform/cluster` subfolder: `camunda-deployment-references/aws/containers/ecs-single-region-fargate/terraform/cluster`. ::: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/s3-bucket/s3-bucket-tf-init.sh ``` Terraform will now use the S3 bucket to manage the state file, ensuring remote and persistent storage. ## Terraform setup The root workspace houses the overall implementation to keep things configurable and interchangeable as needed. While each Camunda component is kept as a separate module to abstract the need of each component as it's required with their base setup. If wanting to deploy multiple Camunda 8 setups, it may make sense to abstract the root workspace to a common module as well to allow easier scaling. If not otherwise indicated, the `.tf` file is corresponding to the [root workspace path](https://github.com/camunda/camunda-deployment-references/tree/main/aws/containers/ecs-single-region-fargate/terraform/cluster). ### Elastic Container Service `ecs.tf` contains the ECS cluster, which is just a logical component to group ECS resources. `../../modules/ecs/fargate/orchestration-cluster` is the main component `Orchestration Cluster` of Camunda and contains the definitions for: - ECS Service and task definition - Defines the base setup for the Orchestration Cluster, including the node ID provider, EFS configuration, and initial cluster endpoints. - Automatically sets the Zeebe cluster size based on the task count. - Resolves initial contact points using DNS with multiple A records instead of requiring explicit Zeebe Broker addresses. - Task-specific IAM role - Grants access to AWS services required by this component, such as the S3 bucket and Aurora PostgreSQL. - S3 bucket - Used by the ECS-specific node ID provider. - CloudWatch log group - Used for Orchestration Cluster logs. - Can be shared with other Camunda components that have a one-to-one relationship with the Orchestration Cluster, such as Connectors. - Networking configuration - Integrates with ECS Service Connect and Amazon Route 53 to enable access from within the VPC, including from resources outside the ECS cluster (for example, EC2 instances or Kubernetes clusters). - Load balancer configuration - Adds listener rules to a shared load balancer for the Orchestration Cluster and Connectors. - EFS file system The base terraform documentation for this module can be found [alongside the repository](https://github.com/camunda/camunda-deployment-references/tree/main/aws/modules/ecs/fargate/orchestration-cluster). `../../modules/ecs/fargate/connectors` is a secondary component `Connectors` and contains the definitions for: - ECS Service and Task definition - Task specific IAM role to allow access to AWS services isolated to this component - Load Balancer related configurations to add listener rules to a shared Load Balancer between Orchestration Cluster and Connectors The base terraform documentation for this module can be found [alongside the repository](https://github.com/camunda/camunda-deployment-references/tree/main/aws/modules/ecs/fargate/connectors). `camunda.tf` contains the module invocations with an example base configuration for the Orchestration Cluster and Connectors: - Aurora PostgreSQL configuration with the [AWS JDBC Wrapper](https://github.com/aws/aws-advanced-jdbc-wrapper) that comes as part of the Camunda distribution - Basic authentication Admin setup - Admin user with random password - Connectors user with random password configured and pre-configured for Connectors to consume to connect to the Orchestration Cluster In `camunda.tf` you can pass in any configuration adjustment required for the component or increase the resources. A few configuration options as mentioned above are kept as part of the modules to ensure the user can't interfere with the base setup. If you need to adjust those, then you have to adjust those in your copy of the modules. ### Aurora PostgreSQL :::info Optional module If you do not want to use this module, you can skip this section. However, you will need to adjust the remaining steps to remove any references to it. If you choose not to use this module, you must provide your own PostgreSQL, Elasticsearch or OpenSearch instance and make required config adjustments. Additionally, be sure to delete the `postgres.tf` and `postgres_seed.tf` file in your reference copy—otherwise, the resources defined in it will still be created. Additionally, remove the references for the IAM access in `iam.tf` following the `RDS IAM Auth Support` section. ::: `postgres.tf` provisions an Aurora PostgreSQL cluster with a pre-created `camunda` database and admin credentials saved in the AWS Secrets Manager. This Aurora PostgreSQL cluster is used as secondary storage for the Orchestration Cluster. `postgres_seed.tf` provisions a temporary ECS task to pre-seed the database for IAM authentication to work. The Aurora PostgreSQL cluster is not easily accessible from the outside as we don't expose it, therefore a workaround is required to do the initial user creation for the IAM authentication to work instead of using hard-coded username/password combinations. If you're fine with username/password, you can remove the `postgres_seed.tf` and reuse the admin user that was created on creation to configure the Orchestration Cluster to consume said user. ### Miscellaneous Resources `registry-auth.tf` contains the basics to create a secret via the AWS Secrets Manager for any kind of registry to access the Camunda images or bypass rate limitations. `lb.tf` contains the creation of the main Network Load Balancer (NLB) and the Application Load Balancer (ALB). `iam.tf` contains various IAM roles and policies. `secrets.tf` contains the creation of random passwords and storage in AWS Secrets Manager. `s3.tf` contains a bucket for backup purposes with versioning and encryption enabled. Access is handled through IAM role policies. ### Advanced Topics #### Camunda components configuration The Terraform implementation does not abstract any configuration and anything you need to configure for the Camunda components can be found within their own documentation. Camunda components can be configured for example via environment variables or an application YAML. ##### Environment Variables The base configuration is done via environment variables and defined directly as is in the invocation of the module. An alternative approach, still with environment variables, could be to load them from an external file. Example: ```bash title="orchestration-cluster-env" KEY=VALUE KEY2=VALUE2 KEY3=${template} ``` The file `orchestration-cluster-env` could then be loaded in Terraform via: ```hcl locals { # Combine with templatefile to replace dynamic values derived from Terraform env_lines = split("\n", templatefile("orchestration-cluster-env", { template = "SOME_TF_VALUE" })) # Splits KEY=VALUE into expected JSON env_kv_pairs = [ for line in local.env_lines : { name = trim(split("=", line)[0], " ") value = trim(join("=", slice(split("=", line), 1, length(split("=", line)))), " ") } if length(split("=", line)) > 1 # Filter out lines without '=' ] } ``` This can then be passed to the module invocation ```hcl environment_variables = local.env_kv_pairs # or mixed with the concat function ``` ##### Application YAML 1. This can either be baked into a custom image permanently by you 2. Pull application YAML on startup via init container from external store or integrate in Terraform Example: ```hcl init_container_enabled = true init_container_image = "public.ecr.aws/amazonlinux/amazonlinux:minimal" init_container_command = ["sh", "-c", "curl -fsSL https://example.com/additional-properties.yaml -o /config/additional-properties.yaml"] ``` Add this as part of your module environment usages to let Spring know to load the additional file. ```hcl { name = "SPRING_CONFIG_IMPORT" value = "file:./config/additional-properties.yaml" } ``` `/config` is a shared ephemeral volume between the init container and the running container. #### Terraform Configuration The base terraform documentation for the Orchestration can be found [alongside the repository](https://github.com/camunda/camunda-deployment-references/tree/main/aws/modules/ecs/fargate/orchestration-cluster) as well as for [Connectors](https://github.com/camunda/camunda-deployment-references/tree/main/aws/modules/ecs/fargate/connectors). Some common topics to potentially change: ##### Resources ```hcl # both modules task_cpu = 4096 task_cpu_architecture = "X86_64" task_memory = 8192 ``` The EFS file system uses the `elastic` throughput mode by default. This mode automatically scales for most workloads. If you need a fixed throughput configuration, adjust the `efs_throughput_mode` and `efs_provisioned_throughput_in_mibps` variables. Example: ```hcl # Orchestration Cluster efs_throughput_mode = "provisioned" efs_provisioned_throughput_in_mibps = 50 ``` ##### Camunda ###### Image ```hcl # both modules image = "camunda/camunda:VERSION" # "camunda/connectors-bundle:VERSION" ``` You could supply your custom registry and version this way like: ```hcl image = "ghcr.io/NAMESPACE/IMAGE_NAME:VERSION" ``` ###### Sizing ```hcl # both modules task_desired_count = X # in case of Orchestration Cluster automatically changes the `camunda.cluster.size` ``` ###### Wait for ready This flag ensures that Terraform waits until the ECS service is successfully deployed. It is useful when other components, such as Connectors, depend on the Orchestration Cluster, because it prevents them from being deployed before the cluster is ready. If you disable this flag, Terraform deploys all resources at once without waiting for service readiness. ```hcl # both modules wait_for_steady_state = true ``` #### Aurora PostgreSQL initial user seeding When wanting to use IAM authentication to simplify the authentication between Orchestration Cluster and Aurora PostgreSQL cluster then an initial seeding of the database is required to have a passwordless user with the `rds_iam` role assigned. More information about IAM authentication with Aurora can be found in the [AWS documentation](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.DBAccounts.html#UsingWithRDS.IAMDBAuth.DBAccounts.PostgreSQL). Terraform itself can't do this initial seeding as the Aurora PostgreSQL is not publicly exposed, so a workaround is needed. In our reference architecture to keep things simple and working ideally immediately, a local execution was used that triggers a one time seeding job to do the required steps as the ECS has access to Aurora PostgreSQL cluster. As previously mentioned, if you don't want to do this local execution, you can delete the `postgres_seed.tf` and remove the `depends_on` in `camunda.tf`. Alternatives are for example: - Externally supplied PostgreSQL - Rely on username / password of e.g. admin user - Manual seeding via an EC2 instance or ECS task in the same VPC or a connected VPN - Temporarily exposing the Aurora PostgreSQL cluster (not recommended) - AWS Lambda function that does the seeding - AWS Step function that does the seeding It was implemented as a `local exec` with an ECS task since we wanted to provide a fully working reference end-to-end while still relying on something like IAM authentication. #### Rolling deployments The Orchestration Cluster is stateful and overprovisioning will not help the deployment to reach a ready state quicker as we're limited by the Zeebe node-ids and brokers only becoming ready when successfully joining a cluster. Therefore, the Orchestration Cluster does a deployment of maximum `100%` of tasks and minimum `66%` to ensure quorum is kept. If using smaller task sizes, you may have to consider using `service_force_new_deployment = true` to force a new deployment as otherwise the minimum and maximum task size will block a successful update. For the Connectors task, it's kept at a maximum of `200%` and minimum of `50%` as the application is stateless and can therefore scale above the initial target during upgrades. ## Deploy Management Identity with OIDC authentication [Management Identity](/self-managed/components/management-identity/overview.md) is the Camunda 8 component responsible for authentication and authorization of the components outside the Orchestration Cluster, such as Camunda Hub. In this reference architecture, Management Identity is deployed as an additional ECS service and is only created when you switch the platform to OpenID Connect (OIDC) authentication. Authentication is controlled by a single `authentication_mode` input in `terraform/cluster`: | Mode | Behavior | | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `basic` (default) | The Orchestration Cluster and Connectors use built-in users with generated passwords. No identity provider and no Management Identity are deployed. | | `oidc` | The Orchestration Cluster, Connectors, and Management Identity authenticate through an OIDC provider. Management Identity is deployed, and Camunda Hub becomes available for deployment. | To enable OIDC, set the following in your `terraform.tfvars` or pass it with `-var`: ```hcl authentication_mode = "oidc" ``` Use `admin_claim_value` to choose which principal becomes the platform administrator. It defaults to `admin`, the user the bundled provider creates. With your own provider, set it to a principal that exists in your directory, otherwise nobody is granted the administrator role. ### Choose an OIDC provider The reference architecture ships a bundled OIDC provider so the stack runs end-to-end without an external dependency. Every Camunda component reads a single provider-agnostic OIDC interface, so the bundled provider and your own provider are wired the same way. To use your own provider, such as Microsoft Entra ID or Okta, set the `external_oidc` object. The bundled provider is then skipped entirely. | Field | Required | Description | | --------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------- | | `issuer_uri` | Yes | Issuer URI of your OIDC provider, used for discovery and token validation. | | `token_uri` | Yes | Token endpoint used for machine-to-machine authentication. | | `orchestration_audience` | Yes | Audience the Orchestration Cluster validates on incoming tokens. | | `identity_audience` | Yes | Audience for Management Identity's own resource server. Keep it separate from the Orchestration Cluster audience. | | `identity_client_id` | Yes | Client ID registered for Management Identity. | | `identity_client_secret_arn` | Yes | Secrets Manager ARN holding the Management Identity client secret. | | `orchestration_client_id` | Yes | Client ID registered for the Orchestration Cluster. | | `orchestration_client_secret_arn` | Yes | Secrets Manager ARN holding the Orchestration Cluster client secret. | | `connectors_client_id` | Yes | Client ID registered for Connectors. | | `connectors_client_secret_arn` | Yes | Secrets Manager ARN holding the Connectors client secret. | | `username_claim` | No | Claim that carries the user name. Defaults to `preferred_username`. | | `client_id_claim` | No | Claim that identifies the calling application. Defaults to `client_id`. Microsoft Entra ID uses `azp` (v2) or `appid` (v1). | | `connectors_token_scope` | No | Scope Connectors requests on the client credentials call. Empty by default. Microsoft Entra ID v2 requires `/.default`. | Register one client per component in your provider, store each client secret in AWS Secrets Manager, and reference the secrets by ARN. Terraform never accepts raw secret values here. Every required field must be non-empty once `external_oidc` is set, and `external_oidc` is only valid together with `authentication_mode = "oidc"`. Both rules are enforced by plan-time preconditions. The Orchestration Cluster and Management Identity are separate resource servers, so they need separate audiences. Pointing both at one value aims Management Identity's permissions at the Orchestration Cluster API. :::note Encrypt the client secrets either with the AWS-managed Secrets Manager key or with the same customer-managed key this stack uses through `secrets_kms_key_arn`. The ECS task role can decrypt only one key, so a secret under any other customer-managed key fails at task start with `ResourceInitializationError`. ::: ```hcl authentication_mode = "oidc" external_oidc = { issuer_uri = "https://login.example.com/realms/camunda" token_uri = "https://login.example.com/realms/camunda/protocol/openid-connect/token" orchestration_audience = "camunda-api" identity_audience = "camunda-identity-resource-server" identity_client_id = "camunda-identity" identity_client_secret_arn = "arn:aws:secretsmanager:eu-central-1:123456789012:secret:identity-client-secret" orchestration_client_id = "orchestration" orchestration_client_secret_arn = "arn:aws:secretsmanager:eu-central-1:123456789012:secret:orchestration-client-secret" connectors_client_id = "connectors" connectors_client_secret_arn = "arn:aws:secretsmanager:eu-central-1:123456789012:secret:connectors-client-secret" } ``` :::warning Browser-based OIDC login does not complete over plain HTTP. Set `alb_certificate_arn` to an AWS Certificate Manager (ACM) certificate ARN before enabling OIDC, and set `alb_public_hostname` to the DNS name clients use to reach the load balancer, such as `camunda.example.com`. Both are needed together: every OIDC URL is built from the public host name, and ACM cannot issue a certificate for the load balancer's own `*.elb.amazonaws.com` name. Terraform fails at plan time if you set the certificate without the host name. The Application Load Balancer then serves an HTTPS listener on port 443 and redirects port 80 to it. Use `alb_ssl_policy` to change the negotiated SSL policy. ::: ### Resources created for Management Identity `../../modules/ecs/fargate/management-identity` is deployed when `authentication_mode = "oidc"` and contains the definitions for: - ECS Service and task definition, running Management Identity in generic OIDC mode. - Task-specific IAM role, isolated to this component. - Load balancer configuration to add a listener rule to the shared Application Load Balancer, serving Management Identity under the `/identity` context path. - Networking configuration that registers Management Identity with ECS Service Connect, reachable inside the VPC as `identity` on port `8084`, with the management endpoint on port `8082`. Management Identity uses a dedicated `identity` database on the shared Aurora PostgreSQL cluster with IAM database authentication, the same mechanism the Orchestration Cluster uses. The image ships the AWS Advanced JDBC wrapper, and the reference architecture points the Spring datasource at it, so the task authenticates with short-lived IAM tokens and never receives a static database password. The database name and role are configurable through `identity_db_name` and `identity_db_username`. A password for the role still exists in AWS Secrets Manager, used only by the database seed task to bootstrap the role. In generic OIDC mode, Management Identity validates tokens and handles login. The identity provider owns clients and users, and role-to-principal mapping is done on the Camunda side. The base Terraform documentation for this module can be found [alongside the repository](https://github.com/camunda/camunda-deployment-references/tree/main/aws/modules/ecs/fargate/management-identity). ### How components authenticate in OIDC mode | Component | Flow | | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | Orchestration Cluster | Authorization code flow for the web components. The user identifier is taken from the `preferred_username` claim, and `admin` is granted the admin role. | | Connectors | Client credentials (machine-to-machine) against the token endpoint, mapped to the Connectors role. | | Management Identity | Generic OIDC client against the same issuer, using its own client ID and audience. | For your own scripts and clients, retrieve the machine-to-machine values from the Terraform outputs: ```sh terraform output -raw oidc_token_url terraform output -raw orchestration_oidc_client_id terraform output -raw orchestration_oidc_client_secret terraform output -raw connectors_oidc_client_id terraform output -raw connectors_oidc_client_secret ``` These outputs are empty in `basic` mode. The client secret outputs are only populated for the bundled provider, because an external provider issues and stores its own secrets. ### Retrieve the administrator sign-in credentials In `oidc` mode, you sign in to Operate, Tasklist, and Camunda Hub as the `admin` user of the identity provider, not as the built-in `admin` user that Basic authentication creates. The two accounts have separate passwords, and `terraform output -raw admin_user_password` only returns the built-in one. When you use the bundled provider, the identity provider password is generated at apply time and stored in AWS Secrets Manager under `-oc1-realm-admin-user-password`. No Terraform output exposes it. For the command that reads it, see step 3 of [Verify connectivity to Camunda 8](#verify-connectivity-to-camunda-8). ## Deploy Camunda Hub [Camunda Hub](/self-managed/components/hub/index.md) is deployed as one additional ECS task running two containers: the REST API with the web interface, and a websockets relay used for real-time collaboration. Camunda Hub is designed to interact with multiple orchestration clusters, so you deploy it once and connect it to every cluster it manages. This reference architecture registers the single Orchestration Cluster it deploys. To manage more clusters from the same Camunda Hub, add entries to the [`camunda.hub.clusters`](/self-managed/components/hub/configuration/properties.md#clusters) list the module passes to the task. Camunda Hub is optional and disabled by default. It authenticates through OIDC and cannot use Basic authentication, so it requires `authentication_mode = "oidc"`. It also requires `enable_camunda_hub_authorization`, which seeds Management Identity with the roles and permissions Camunda Hub checks against. Terraform fails during `terraform plan` with a precondition error if either is missing. ```hcl authentication_mode = "oidc" enable_camunda_hub = true enable_camunda_hub_authorization = true ``` The following inputs control the deployment: | Input | Description | Default | | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------ | | `enable_camunda_hub` | Deploy the Camunda Hub ECS task. Requires `authentication_mode = "oidc"` and `enable_camunda_hub_authorization`. | `false` | | `enable_camunda_hub_authorization` | Seed Management Identity with the resource servers, permissions, and roles Camunda Hub needs, and grant them to the administrator principal. | `false` | | `camunda_hub_restapi_image` | Container image for the Camunda Hub REST API and web interface. | The matching `camunda/hub` 8.10 image | | `camunda_hub_websockets_image` | Container image for the Camunda Hub websockets relay. | The matching `camunda/hub-websockets` 8.10 image | | `camunda_hub_db_name` | Name of the dedicated Camunda Hub database on the shared Aurora cluster. | `camunda-hub` | | `camunda_hub_db_username` | Database role for Camunda Hub. It authenticates with an IAM token, so it carries no password. | `camunda-hub` | | `camunda_license_key` | Camunda license key. Leave empty to run Camunda Hub in trial mode. When set, it's stored in AWS Secrets Manager and injected as `CAMUNDA_LICENSE_KEY`. | `""` | Without the authorization seed, Camunda Hub still signs users in, so the deployment looks healthy while every project call is denied. Management Identity declares no roles by default, so the roles Camunda Hub asks about do not exist. ### Resources created for Camunda Hub `../../modules/ecs/fargate/camunda-hub` is deployed when `enable_camunda_hub = true` and contains the definitions for: - ECS Service and task definition with both Camunda Hub containers sharing a task. - Task-specific IAM role, isolated to this component. - Load balancer configuration to add listener rules and target groups for the web interface and the websockets relay. Alongside the module, the root workspace creates a dedicated `camunda-hub` database on the shared Aurora PostgreSQL cluster using IAM authentication, seeded by a one-time ECS task in the same way as the Orchestration Cluster database. It also generates the Pusher application key and secret shared by both containers, and an optional license secret, storing all of them in AWS Secrets Manager. Camunda Hub connects to the Orchestration Cluster over ECS Service Connect using the signed-in user's bearer token, so no additional cluster credentials are required. The base Terraform documentation for this module can be found [alongside the repository](https://github.com/camunda/camunda-deployment-references/tree/main/aws/modules/ecs/fargate/camunda-hub). ### Use the private Camunda registry The default images pull from public Docker Hub and need no credentials. To use the private enterprise images, point `camunda_hub_restapi_image` and `camunda_hub_websockets_image` at `registry.camunda.cloud` and set `registry_username` and `registry_password`. Registry credentials are attached to the task only when an image targets that private registry. ### Access Camunda Hub Camunda Hub is served through the shared Application Load Balancer, in addition to the paths documented in [Verify connectivity to Camunda 8](#verify-connectivity-to-camunda-8): | Path | Target | | ---------- | -------------------------------------- | | `/hub*` | Camunda Hub REST API and web interface | | `/hub-ws*` | Camunda Hub websockets relay | Open `https:///hub` and sign in with the identity provider `admin` user, using the password retrieved in step 3 of [Verify connectivity to Camunda 8](#verify-connectivity-to-camunda-8). To troubleshoot a task that doesn't reach a healthy state, use the [Camunda Hub health and metrics endpoints](/self-managed/components/hub/monitoring.md) and the CloudWatch logs of the ECS service. :::note The reference architecture configures a placeholder sender address and leaves the SMTP host unset, so Camunda Hub doesn't send user invitation emails. Configure your own SMTP server if you need email invitations. ::: ## Execution :::note Secret management We strongly recommend managing sensitive information using a secure secrets management tool such as HashiCorp Vault. For guidance on injecting secrets into Terraform via Vault, refer to the [Terraform Vault Secrets Injection Guide](https://developer.hashicorp.com/terraform/tutorials/secrets/secrets-vault). ::: :::info Terraform Flow Due to the `postgres_seed.tf` it is required that the machine executing it has the `AWS CLI` installed and configured to be able to start and wait for the seeding task to have finished. If that is not wanted or can't be done then please either execute it as two steps with manual seeding, fallback to username/password or supply a pre-configured secondary storage as previously mentioned. ::: 1. Open a terminal in the reference directory containing `config.tf` and the other `.tf` files. 2. Perform a final initialization to apply any changes made throughout this guide: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/s3-bucket/s3-bucket-tf-init.sh#L7 ``` 3. Plan the configuration files: ```bash terraform plan -out cluster.plan # describe what will be created ``` 4. After reviewing the plan, you can confirm and apply the changes: ```bash terraform apply cluster.plan # apply the creation ``` Terraform will now provision the Amazon ECS resources and the Aurora PostgreSQL cluster with all necessary configurations. This process may take approximately 20–30 minutes to complete. The Terraform flow is as follows: - Creation of the VPC and related resources, among that IAM roles - Creation of the Aurora Postgres Cluster within the VPC - Creation of the temporary Aurora Postgres seeding task and wait for it to finish - Creation of the Orchestration Cluster and wait for it to be ready - Creation of the Connectors and wait for it to be ready ## Verify connectivity to Camunda 8 Using Terraform, you can obtain the HTTP endpoint of the Application Load Balancer and interact with Camunda through the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). :::warning HTTPS To keep dependencies minimal and non-blocking for a quick start, this reference architecture omits a custom domain and TLS configuration. You can easily add TLS by attaching an AWS Certificate Manager (ACM) certificate to the Application Load Balancer (ALB). For details, see the AWS documentation on [creating an HTTPS listener](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html). Information on configuring a custom domain and understanding the ALB DNS name is available in the [Application Load Balancer documentation](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html#dns-name). Without these additions, information is transmitted in plaintext and is therefore insecure. ::: 1. Navigate to the Terraform folder: ```sh cd terraform ``` 2. Retrieve the Application Load Balancer output: ```sh terraform output -raw alb_endpoint ``` The ALB exposes both the Orchestration and Connectors through the same port and uses listener rules with weights to determine the path they're on. - ALB:80 (ALB:443 when you set `alb_certificate_arn`) - `/*` routes to the Orchestration Cluster UI/REST API - `/connectors*` routes to the Connectors - `/hub*` routes to Camunda Hub and `/hub-ws*` to its websockets relay, when `enable_camunda_hub = true` - `/identity*` routes to Management Identity, when OIDC is enabled - ALB:9600 (optional - not recommended to be exposed publicly) - `/*` routes to the Orchestration Cluster - Connectors has the management port with the web server combined by default - NLB:26500 (TCP) - Exposes the Orchestration Cluster - Zeebe Gateway with gRPC 3. Access the URL of `alb_endpoint` which should present you a login screen. The administrator user name is `admin` in both authentication modes, but the password is stored in a different place. Select the mode you deployed: The admin user name as pre-configured in `camunda.tf` is `admin` and the password is randomly generated and can be retrieved via: ```sh terraform output -raw admin_user_password ``` You sign in as the `admin` user of the identity provider, which is a different account from the built-in user used in Basic authentication. The `admin_user_password` output does not return this password, and no Terraform output exposes it. Read it from AWS Secrets Manager instead, where `` is the value of the `prefix` input (`camunda` by default): ```sh aws secretsmanager get-secret-value \ --secret-id -oc1-realm-admin-user-password \ --query SecretString \ --output text ``` When you bring your own provider through `external_oidc`, this secret is not created. Sign in with an account from your provider instead, and make sure the claim named by `username_claim` matches the value of `admin_claim_value`. 4. Use the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) to communicate with Camunda: Follow the example in the [Orchestration Cluster REST API documentation](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md) to authenticate and retrieve the cluster topology. ## Troubleshooting ### Logs Logs are by default exported to CloudWatch unless configured otherwise by you. Those are both visible in the CloudWatch dashboard and the ECS Service alongside each task. ### Accessing task or management API ECS tasks are not easily accessible without workarounds, some options are the following: - EC2 / ECS debug instance / task within the same VPC to try to ping and use the [management API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md) - AWS VPN connected to the VPC - Lambda functions - Step functions - Temporarily exposing the management API - Temporarily set `task_enable_execute_command` to `true` and redeploy to allow accessing the running container ```sh aws ecs execute-command \ --cluster $ECS_CLUSTER \ --task $ECS_TASK_ID \ --container orchestration-cluster \ --command "/bin/sh" \ --interactive ``` You can find more information about `AWS ECS Exec` within the [AWS documentation](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec-run.html). For general troubleshooting assistance, consult the [operational guides troubleshooting documentation](/self-managed/operational-guides/troubleshooting.md). ## Operations ### Backup and restore The general [backup and restore procedure](/self-managed/operational-guides/backup-restore/backup-and-restore.md) applies. The backup process, itself, doesn't require changes. Restoring, however, introduces additional complexity because each broker's data directory (persistent volume) must be restored in a coordinated manner. To support this, an init container is introduced as part of the Orchestration Cluster, responsible for restoring the data directory for the broker running in that task. This mechanism corresponds to the step of [restoring the Zeebe Cluster](/self-managed/operational-guides/backup-restore/rdbms/restore-application.md#restore-zeebe). This approach is implemented in the example module. Set the `restore_enabled` parameter to `true` to enable it. You can optionally provide the `restore_backup_id` parameter to target a specific backup (see [restore options when using RDBMS](/self-managed/operational-guides/backup-restore/rdbms/restore-application.md#restore-options)). On startup, the init container leverages the node-id provider to determine its broker ID in alignment with the other tasks. It restores the partitions associated with that broker, then blocks execution until all brokers have completed their restore operations. Afterward, the init container exits, allowing the Orchestration Cluster container to start. You must configure the init container and the Orchestration Cluster container identically. If you use environment variables, this requirement is automatically satisfied. If configuration is distributed through other mechanisms, those must also be explicitly applied to the init container. As long as the `restore_enabled` parameter remains set to `true`, the init container remains part of the task definition. After the backup has been successfully restored, subsequent executions will effectively be no-ops until the parameter is removed. :::note Camunda recommends restoring to a fresh cluster rather than reusing an existing one. A newly created cluster already has an empty S3 bucket and EFS volume, so no additional cleanup is needed. If you choose to restore into an existing cluster instead, you must manually ensure the S3 bucket configured for the node ID provider is empty and the EFS volume is fully cleared before starting the restore. ::: ## Next steps After setting up your cluster, many users typically do the following: - [Connect to an identity provider](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md) – integrate with an external identity system for authentication. - [Add TLS](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html) and configure a [custom domain](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html#dns-name) for the Application Load Balancer (ALB). --- ## Amazon ECS Deploy the Camunda 8 Orchestration Cluster and Connectors to Amazon Elastic Container Service (ECS) to benefit from containerization without having to manage Kubernetes infrastructure. ## Get started Get started with Amazon ECS and Fargate: Deploy the Orchestration Cluster to Amazon ECS ## Learn the fundamentals This deployment targets a single AWS Region with multiple Availability Zones, running the Orchestration Cluster and Connectors on Amazon ECS with Fargate. You can additionally deploy Management Identity and Camunda Hub by switching the deployment to OIDC authentication. Other dependencies include: - [Amazon EFS](https://aws.amazon.com/efs/) as primary storage - [Aurora PostgreSQL](https://aws.amazon.com/rds/aurora/) as secondary storage - [Amazon S3](https://aws.amazon.com/s3/) for node ID metadata and backups For more implementation details, read the [Architecture](./aws-ecs.md#architecture) section of our deployment guide. ## Dynamic node ID provider Camunda 8 is designed for Kubernetes StatefulSet deployments where each broker manages data on dedicated disk storage. Amazon ECS presents a challenge, as tasks are stateless by design and typically rely on external databases for state management. To deploy Camunda 8 to Amazon ECS, we introduce a dynamic node ID provider service backed by Amazon S3. This service enables each ECS task to assume the role of a Camunda 8 broker and safely manage the corresponding data in a dedicated directory on a shared EFS disk. The node ID provider operates using a lease mechanism stored in Amazon S3. A task acquires a broker role when it obtains the lease for a specific node ID. If the lease cannot be renewed, the task shuts down immediately to maintain data integrity. ## Amazon ECS vs. Amazon EKS If you're already invested in Kubernetes tooling and patterns and you want to remain in that environment, Amazon EKS might be a better option for you. More reasons to consider deploying to Amazon EKS over Amazon ECS include: - **Multi-region support:** When deploying to Amazon ECS, the only supported and tested pattern is a single AWS Region with multiple Availability Zones (multi‑AZ). All core pieces of the Orchestration Cluster are expected to live in one region. - **Performance:** The Amazon ECS with Fargate deployment uses Amazon EFS as primary storage. This may result in higher costs and lower performance than a Kubernetes setup with block storage. - **Scaling:** The autoscaling feature of ECS is not currently supported. Check out our [Amazon EKS deployment guide](../../../helm/cloud-providers/amazon/amazon-eks/amazon-eks.md) if a Kubernetes environment better suits your needs. ## Explore further resources --- ## Camunda Docker images Camunda provides [official Docker images](https://hub.docker.com/u/camunda) for all major components. ## Docker images vs. Docker Compose Docker images are suitable for production deployments. By contrast, the provided [Docker Compose files](../../quickstart/developer-quickstart/docker-compose.md) are intended only for quick start, development, and testing. For production, we recommend using [Kubernetes with Helm](../helm/install/quick-install.md). Advanced users can create their own hardened Docker Compose configuration, but this requires additional effort. ## Platform support - Use the `linux/amd64` or `linux/arm64` image for production environments. - All images are publicly accessible. Docker images are supported for production only on Linux systems. Windows and macOS are supported for development environments only. ## Docker images and configuration references | Component | Docker image | Configuration docs | | --------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | Orchestration Cluster | [camunda/camunda](https://hub.docker.com/r/camunda/camunda) | [Environment variables](../../components/orchestration-cluster/overview.md) | | Management Identity | [camunda/identity](https://hub.docker.com/r/camunda/identity) | [Management Identity configuration](../../components/management-identity/configuration/identity-configuration-overview.md) | | Optimize | [camunda/optimize](https://hub.docker.com/r/camunda/optimize) | [Optimize configuration](../../components/optimize/overview.md) | | Connectors | [camunda/connectors](https://hub.docker.com/r/camunda/connectors) | [Connectors configuration](../../components/connectors/overview.md) | | Connectors Bundle | [camunda/connectors-bundle](https://hub.docker.com/r/camunda/connectors-bundle) | [Connectors configuration](../../components/connectors/overview.md) | | Camunda Hub | [camunda/hub](https://hub.docker.com/r/camunda/hub)[camunda/hub-websockets](https://hub.docker.com/r/camunda/hub-websockets) | [Camunda Hub configuration](../../components/hub/configuration/properties.md) | --- ## Helm chart parameters Helm chart parameters let you configure the components and behavior of your Camunda Self-Managed installation. The main way to customize these parameters is by using a `values.yaml` file. In Helm charts, the `values.yaml` file defines configuration for your deployment. To tailor your installation to your needs, you can override parameters in this file or provide your own values file. It's best practice to keep the original `values.yaml` unchanged and maintain a separate file with your custom settings. :::tip Templating support Some values in `values.yaml` support Go template expressions (for example, `{{ .Release.Name }}`). Values that support templating are marked with "Supports templating" in their description in `values.yaml`. This includes `podLabels`, `podAnnotations`, and `global.host`, among others. ::: The following tables show the **top-level configuration sections** in `values.yaml`. Each section controls a specific area of the chart. ### Global and orchestration cluster configuration | Section | Purpose | | --------------- | ------------------------------------------------------- | | `global` | Configures shared settings that apply across components | | `orchestration` | Configures orchestration cluster settings | For pod-level networking options such as `dnsPolicy`, `dnsConfig`, and `orchestration.hostNetwork`, see [configure pod networking](/self-managed/deployment/helm/configure/pod-networking.md). For service-level options such as the `appProtocol` hint per port, see [configure Kubernetes Service ports](/self-managed/deployment/helm/configure/service-configuration.md). ### Other Camunda applications | Section | Purpose | | ------------ | ------------------------------------------ | | `camundaHub` | Configures Camunda Hub | | `connectors` | Configures the Connector runtime | | `identity` | Configures the Management Identity service | | `optimize` | Configures the Optimize web application | ### External infrastructure Camunda 8.10 no longer bundles infrastructure subcharts. Deploy PostgreSQL, Elasticsearch or OpenSearch, and Keycloak separately from the Camunda Helm chart. This lets you use your preferred deployment methods, leverage managed services, and manage infrastructure lifecycles independently of Camunda. See [deploy required dependencies with Kubernetes operators](/self-managed/deployment/helm/configure/operator-based-infrastructure.md). :::note Configure the secondary storage backend that fits your requirements. Depending on the component, topology, and version, you can use a document-store backend (Elasticsearch/OpenSearch) or an RDBMS-based secondary store. See [RDBMS configuration](/self-managed/concepts/databases/relational-db/configuration.md) and the glossary entry [RDBMS](/reference/glossary.md#rdbms). ::: ### Observability | Section | Purpose | | -------------------------- | ---------------------------------------------------------- | | `prometheusServiceMonitor` | Creates a Prometheus `ServiceMonitor` resource for metrics | ## Where to find all chart parameters For a full list of supported Helm chart parameters, including default values and descriptions, see [Helm chart parameters on Artifact Hub](https://artifacthub.io/packages/helm/camunda/camunda-platform/#parameters). Check this page when installing or upgrading to ensure you use the latest options for your chart version. ### Provided values files In addition to the default `values.yaml`, the Helm chart repository includes several additional values files for special use cases. You can use these files individually or combine them with your own overrides. | File | Purpose | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | `values.yaml` | The default configuration. Includes all chart parameters with baseline values. | | `values-local.yaml` | Optimized for local development (for example, kind or Minikube). Adjusts resource requests and limits for smaller environments. | | `values-tls.yaml` | An overlay that enables TLS for the connections the chart can configure. Requires a CA bundle secret in the namespace. | | `values-latest.yaml` | Tracks the latest versions of the applications. This may include breaking changes and is intended for early testing. | | `values-digest.yaml` | Uses the latest snapshot images referenced by digest (for internal development only). | ### Creating your own values files To customize parameters, create an override file (for example, `my-overrides.yaml`) with custom settings. This approach is recommended over editing `values.yaml` directly. You can [validate the keys in your overrides with the Camunda Helm Toolkit](operational-tasks/camunda-helm-toolkit.md#validate-override-files). This checks the supplied configuration, not the completeness or deployment readiness of all merged Helm values. ### Combining multiple values files Helm lets you specify multiple values files. You can layer them to build the configuration you need: ```bash helm install camunda camunda/camunda-platform \ -f values.yaml \ -f values-local.yaml \ -f my-overrides.yaml ``` If the same parameter is defined in more than one file, the value in the last file listed takes precedence. In the example above, settings from `my-overrides.yaml` override values from both `values-local.yaml` and `values.yaml`. --- ## Amazon EKS Amazon Elastic Kubernetes Service ([Amazon EKS](https://aws.amazon.com/eks/)) is a managed container service to run and scale Kubernetes applications in the cloud or on-premises. Camunda 8 Self-Managed can be deployed on any Kubernetes cluster using [Helm charts](/self-managed/deployment/helm/install/quick-install.md). Deployment requirements, including cloud provider-specific information, are available on our [Kubernetes deployment reference](/self-managed/reference-architecture/kubernetes.md). --- ## Dual-region setup (EKS) :::caution Review our [dual-region concept documentation](/self-managed/concepts/multi-region/dual-region.md) before continuing to understand the current limitations and restrictions of this blueprint setup. ::: This guide explains how to deploy two [Amazon Web Services (AWS) Elastic Kubernetes Service (EKS) clusters](https://docs.aws.amazon.com/eks/latest/userguide/what-is-eks.html) in a dual-region setup using Terraform, a widely used Infrastructure as Code (IaC) tool. The dual-region EKS clusters serve as the infrastructure foundation for running Camunda 8 with disaster recovery capabilities. For advanced EKS scenarios, see the [Amazon EKS documentation](https://docs.aws.amazon.com/eks/latest/userguide/). :::tip New to Terraform or Infrastructure as Code? Start with the [Terraform IaC documentation](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code) and try the [interactive quick start](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code#quick-start). Additionally, review the [single-region EKS Terraform setup](./terraform-setup.md) for the essentials of setting up an Amazon EKS cluster and configuring AWS IAM permissions. ::: ## Requirements - **AWS account** – Required to create AWS resources. See [What is an AWS account?](https://docs.aws.amazon.com/accounts/latest/reference/accounts-welcome.html). - **AWS CLI** – Command-line tool to manage AWS resources. [Install AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html). - **Terraform** – IaC tool used to provision resources. [Install Terraform](https://developer.hashicorp.com/terraform/downloads). - **kubectl** – CLI for interacting with Kubernetes clusters. [Install kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl). - **Helm CLI v4 (recommended; see [supported versions](/reference/supported-environments.md#clients))** – Package manager for Kubernetes. [Install Helm](https://helm.sh/docs/intro/install/). - **AWS service quotas** – Verify your quotas before deployment: - At least 6 Elastic IPs (three per availability zone, per region). - Adequate quotas for **VPCs, EC2 instances, and storage** in both regions. - Request increases if needed via the AWS console. You pay only for used resources. See [AWS service quotas](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html) and [Amazon EC2 service quotas](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-resource-limits.html). For the tool versions used in testing, see the repository's [.tool-versions](https://github.com/camunda/camunda-deployment-references/blob/main/.tool-versions) file. It contains an up-to-date list of versions used for testing. ### Considerations This setup provides a solid starting point for running Camunda 8 on AWS in a dual-region configuration. It is not optimized for peak performance. Use it as a foundation you can extend and adapt for production with [Infrastructure as Code (IaC) tools](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code). - To test or develop against Camunda 8, consider signing up for our [SaaS offering](https://camunda.com/platform/). - If you already have two Amazon EKS clusters (peered together) and an Amazon S3 bucket, skip ahead to [deploy Camunda 8 via Helm charts](#3-deploy-camunda-8-via-helm-charts). :::warning Reference architectures and examples provided in this guide are not turnkey modules. Camunda recommends cloning the repository and modifying it locally. You are responsible for operating and maintaining the infrastructure. Camunda updates the reference architecture over time and changes may not be backward compatible. You can use these updates to upgrade your customized codebase as needed. ::: :::note Cost management This guide provisions resources that can incur costs in your cloud provider account. Review your provider's pricing before you begin. ::: ### Outcome _Infrastructure diagram for a dual-region EKS setup (click on the image to open the PDF version)_ [![Infrastructure Diagram EKS Dual-Region](./assets/eks-dual-region.jpg)](./assets/eks-dual-region.pdf) After completing this guide, you will have: - Two Amazon EKS Kubernetes clusters in two different geographic regions with each four nodes ready for the Camunda 8 dual-region installation. - The [EBS CSI driver](https://docs.aws.amazon.com/eks/latest/userguide/ebs-csi.html) installed and configured, which is used by the Camunda 8 Helm chart to create [persistent volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/). - A [VPC peering](https://docs.aws.amazon.com/vpc/latest/peering/what-is-vpc-peering.html) between the two EKS clusters, allowing cross-cluster communication between different regions. - An [Amazon Simple Storage Service](https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html) (S3) bucket for [Elasticsearch backups](https://www.elastic.co/guide/en/elasticsearch/reference/current/repository-s3.html). - The [Elastic Cloud on Kubernetes (ECK) operator](https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-overview.html) installed in both clusters for managing Elasticsearch lifecycle. - ECK-managed Elasticsearch clusters running in both regions. :::note This guide uses the ECK operator as the preferred approach for managing Elasticsearch. You are not limited to this approach; you can use managed Elasticsearch services or other Elasticsearch instances. If you choose an alternative, you are responsible for adapting the scripts and configuration accordingly. ::: ## 1. Configure AWS and apply Terraform ### Obtain a copy of the reference architecture The first step is to download a copy of the reference architecture from the [GitHub repository](https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-dual-region/). This material will be used throughout the rest of this documentation. The provided reference architecture repository allows you to directly reuse and extend the existing Terraform example base. This sample implementation is flexible to extend to your own needs without the potential limitations of a Terraform module maintained by a third party. ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-dual-region/procedure/get-your-copy.sh ``` With the reference architecture copied, you can proceed with the remaining steps outlined in this documentation. Ensure that you are in the correct directory before continuing with further instructions. ### Export environment variables To streamline the execution of the subsequent commands, it is recommended to export multiple environment variables within your terminal. There are two regions (`REGION_0` and `REGION_1`), each with its own Kubernetes cluster (`CLUSTER_0` and `CLUSTER_1`). It is recommended to save these changes for future interactions with the dual-region setup. 1. Navigate to the `procedure` folder within your cloned repository: ```bash cd aws/kubernetes/eks-dual-region/procedure ``` 2. The folder provides a helper script [export_environment_prerequisites.sh](https://github.com/camunda/camunda-deployment-references/tree/main/aws/kubernetes/eks-dual-region/procedure/export_environment_prerequisites.sh) to export various environment variables to ease the interaction with a dual-region setup. Consider permanently changing this file for future interactions. 3. Adjust the environment variable values within the script to your needs. :::caution You have to choose unique namespaces for Camunda 8 installations. The namespace for Camunda 8 installation in the cluster of region 0 (`CAMUNDA_NAMESPACE_0`), needs to have a different name from the namespace for Camunda 8 installation in the cluster of region 1 (`CAMUNDA_NAMESPACE_1`). This is required for proper traffic routing between the clusters. For example, you can install Camunda 8 into `CAMUNDA_NAMESPACE_0` in `CLUSTER_0`, and `CAMUNDA_NAMESPACE_1` on the `CLUSTER_1`, where `CAMUNDA_NAMESPACE_0` != `CAMUNDA_NAMESPACE_1`. Using the same namespace names on both clusters won't work as CoreDNS won't be able to distinguish between traffic targeted at the local and remote cluster. ::: 4. Execute the script via the following command: ```bash . ./export_environment_prerequisites.sh ``` The dot is required to export those variables to your shell and not a spawned subshell. ```bash reference https://github.com/camunda/camunda-deployment-references/tree/main/aws/kubernetes/eks-dual-region/procedure/export_environment_prerequisites.sh ``` ### Dual-region EKS cluster module setup This module provides the foundational configuration for AWS access and Terraform usage. We use [Terraform modules](https://developer.hashicorp.com/terraform/language/modules) to abstract resources into reusable components and simplify infrastructure management. The [Camunda AWS EKS cluster module](https://github.com/camunda/camunda-deployment-references/tree/main/aws/modules/eks-cluster/) is publicly available and serves as a robust starting point for deploying Amazon EKS clusters. Review the module before implementation to understand its structure and capabilities. The module is locally sourced in your clone. Any changes you make to the module in your repository take effect immediately in your setup. #### Terraform files overview From your cloned repository, navigate to `aws/kubernetes/eks-dual-region/terraform/clusters`. This contains the Terraform base configuration for the dual-region setup. ##### config.tf This file contains the [backend](https://developer.hashicorp.com/terraform/language/backend) and [provider](https://developer.hashicorp.com/terraform/language/providers/configuration) configuration, meaning where to store the [Terraform state](https://developer.hashicorp.com/terraform/language/state) and which providers to use, their versions, and potential credentials. The important part of `config.tf` is the initialization of two AWS providers, as you need one per region and this is a limitation by AWS given everything is scoped to a region. ##### clusters.tf This file is using [Terraform modules](https://developer.hashicorp.com/terraform/language/modules), which allows abstracting resources into reusable components. The Terraform modules of [AWS EKS](https://github.com/camunda/camunda-deployment-references/tree/main/aws/modules/eks-cluster) are an example implementation and can be used for development purposes or as a starting reference. This contains the declaration of the two clusters. One of them has an explicit provider declaration, as otherwise everything is deployed to the default AWS provider, which is limited to a single region. ##### vpc-peering.tf For a multi-region setup, you need to have the [virtual private cloud (VPC)](https://aws.amazon.com/vpc/) peered to route traffic between regions using private IPv4 addresses and not publicly route the traffic and expose it. For more information, review the [AWS documentation on VPC peering](https://docs.aws.amazon.com/vpc/latest/peering/what-is-vpc-peering.html). For dual- or tri-region setups, VPC peering is preferred over [transit gateways](https://aws.amazon.com/transit-gateway/) as it is simpler to manage and has fewer AWS limitations. Other AWS networking options remain valid - choose what best fits your environment. For a complete comparison, review the [AWS documentation](https://docs.aws.amazon.com/whitepapers/latest/building-scalable-secure-multi-vpc-network-infrastructure/transit-vpc-solution.html#peering-vs). The previously mentioned [Camunda module](https://github.com/camunda/camunda-deployment-references/tree/main/aws/modules/eks-cluster) will automatically create a VPC per cluster. This file covers the VPC peering between the two VPCs and allow any traffic between those two by adjusting each cluster's security groups. ##### s3.tf For Elasticsearch, an S3 bucket is required to allow [creating and restoring snapshots](https://www.elastic.co/guide/en/elasticsearch/reference/current/repository-s3.html). There are [alternative ways](https://www.elastic.co/guide/en/elasticsearch/reference/current/snapshot-restore.html), but since this is focused on AWS, it makes sense to remain within the same cloud environment. This file covers the declaration of an S3 bucket to use for the backups. Additionally, a service account with access to use within the Kubernetes cluster to configure Elasticsearch to access the S3 bucket. ##### output.tf [Terraform outputs](https://developer.hashicorp.com/terraform/language/values/outputs) allow you to reuse generated values in future steps. For example, the access keys of the service account with S3 access. ##### variables.tf This file contains various variable definitions for both [local](https://developer.hashicorp.com/terraform/language/values/locals) and [input](https://developer.hashicorp.com/terraform/language/values/variables) types. The difference is that input variables require you to define the value on execution. While local variables are permanently defined, they are namely for code duplication purposes and readability. #### Preparation 1. Adjust any values in the [variables.tf](https://github.com/camunda/camunda-deployment-references/tree/main/aws/kubernetes/eks-dual-region/terraform/clusters/variables.tf) file to your liking. For example, the target regions and their name or CIDR blocks of each cluster. 2. Make sure that any adjustments are reflected in your [environment variables](#export-environment-variables) to ease the [cluster configuration](#2-preparation-for-camunda-8-installation). 3. Set up the authentication for the `AWS` provider. :::note The [AWS Terraform provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) is required to create resources in AWS. You must configure the provider with the proper credentials before using it. You can further change the region and other preferences and explore different [authentication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration) methods. There are several ways to authenticate the `AWS` provider: - (Recommended) Use the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html) to configure access. Terraform will automatically default to AWS CLI configuration when present. - Set environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`, which can be retrieved from the [AWS Console](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html). ::: ### Terraform prerequisites To manage the infrastructure for Camunda 8 on AWS using Terraform, we need to set up Terraform's backend to store the state file remotely in an S3 bucket. This ensures secure and persistent storage of the state file. :::note Advanced users may want to handle this part differently and use a different backend. The backend setup provided is an example for new users. ::: #### Set up AWS authentication The [AWS Terraform provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) is required to create resources in AWS. Before you can use the provider, you must authenticate it using your AWS credentials. :::caution Ownership of the created resources A user who creates resources in AWS will always retain administrative access to those resources, including any Kubernetes clusters created. It is recommended to create a dedicated [AWS IAM user](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users.html) for Terraform purposes, ensuring that the resources are managed and owned by that user. ::: You can further change the region and other preferences and explore different [authentication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration) methods: - For development or testing purposes you can use the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html). If you have configured your AWS CLI, Terraform will automatically detect and use those credentials. To configure the AWS CLI: ```bash aws configure ``` Enter your `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, region, and output format. These can be retrieved from the [AWS Console](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html). - For production environments, we recommend the use of a dedicated IAM user. Create [access keys](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) for the new IAM user via the console, and export them as `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`. #### Create an S3 bucket for Terraform state management Before setting up Terraform, you need to create an S3 bucket that will store the state file. This is important for collaboration and to prevent issues like state file corruption. To start, set the region as an environment variable upfront to avoid repeating it in each command: ```bash export AWS_REGION= ``` Replace `` with your chosen AWS region (for example, `eu-central-1`). Now, follow these steps to create the S3 bucket with versioning enabled: 1. Open your terminal and ensure the AWS CLI is installed and configured. 2. Run the following command to create an S3 bucket for storing your Terraform state. Make sure to use a unique bucket name and set the `AWS_REGION` environment variable beforehand: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/s3-bucket/s3-bucket-creation.sh ``` 3. Enable versioning on the S3 bucket to track changes and protect the state file from accidental deletions or overwrites: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/s3-bucket/s3-bucket-versioning.sh ``` 4. Secure the bucket by blocking public access: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/s3-bucket/s3-bucket-private.sh ``` 5. Verify versioning is enabled on the bucket: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/s3-bucket/s3-bucket-verify.sh ``` This S3 bucket will now securely store your Terraform state files with versioning enabled. #### Initialize Terraform Once your authentication is set up, you can initialize your Terraform project. The previous steps configured a dedicated S3 Bucket (`S3_TF_BUCKET_NAME`) to store your state, and the following creates a bucket key that will be used by your configuration. Configure the backend and download the necessary provider plugins: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/s3-bucket/s3-bucket-tf-init.sh ``` Terraform will connect to the S3 bucket to manage the state file, ensuring remote and persistent storage. ### Create the EKS clusters :::note Secret management We strongly recommend managing sensitive information using a secure secrets management solution like HashiCorp Vault. For details on how to inject secrets directly into Terraform via Vault, see the [Terraform Vault Secrets Injection Guide](https://developer.hashicorp.com/terraform/tutorials/secrets/secrets-vault). ::: 1. Open a terminal in the reference folder where `config.tf` and other `.tf` files are. ```bash cd ./aws/kubernetes/eks-dual-region/terraform/clusters/ ``` 2. Perform a final initialization for anything changed throughout the guide: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/s3-bucket/s3-bucket-tf-init.sh#L7 ``` 3. Plan the configuration files: ```bash terraform plan -out cluster.plan # describe what will be created ``` 4. After reviewing the plan, you can confirm and apply the changes. ```bash terraform apply cluster.plan # apply the creation ``` Terraform will now create the Amazon EKS clusters with all the necessary configurations. The completion of this process may require approximately 20-30 minutes for each component. ## 2. Preparation for Camunda 8 installation Now that you have created two peered Kubernetes clusters with Terraform, you will need to configure various components to make the dual-region setup work. ### Access the created EKS clusters To ease working with two clusters, create or update your local `kubeconfig` to contain those new contexts. Using an alias for those new clusters allows you to directly use kubectl and Helm with a particular cluster. Update or create your kubeconfig via the [AWS CLI](https://docs.aws.amazon.com/eks/latest/userguide/create-kubeconfig.html): ```bash # the alias allows for easier context switching in kubectl aws eks --region $REGION_0 update-kubeconfig --name $CLUSTER_0 --alias $CLUSTER_0 aws eks --region $REGION_1 update-kubeconfig --name $CLUSTER_1 --alias $CLUSTER_1 ``` The region and name must align with the values you have defined in Terraform. ### Configure DNS chaining DNS chaining allows for easier communication between the two clusters by forwarding DNS queries from the region 0 cluster to the region 1 cluster and vice versa. You are configuring the CoreDNS from the cluster in **Region 0** to resolve certain namespaces via **Region 1** instead of using the in-cluster DNS server. Camunda applications (e.g. Zeebe brokers) to resolve DNS record names of Camunda applications running in another cluster. #### CoreDNS configuration 1. Expose `kube-dns`, the in-cluster DNS resolver via an internal load-balancer in each cluster: ```bash kubectl --context $CLUSTER_0 apply -f https://raw.githubusercontent.com/camunda/camunda-deployment-references/refs/heads/main/aws/kubernetes/eks-dual-region/procedure/manifests/internal-dns-lb.yml kubectl --context $CLUSTER_1 apply -f https://raw.githubusercontent.com/camunda/camunda-deployment-references/refs/heads/main/aws/kubernetes/eks-dual-region/procedure/manifests/internal-dns-lb.yml ``` 2. Execute the script [generate_core_dns_entry.sh](https://github.com/camunda/camunda-deployment-references/tree/main/aws/kubernetes/eks-dual-region/procedure/generate_core_dns_entry.sh) in the folder `aws/kubernetes/eks-dual-region/procedure/` of the repository to help you generate the CoreDNS config. Make sure that you have previously exported the [environment variables](#export-environment-variables) since the script builds on top of it. ```bash ./generate_core_dns_entry.sh ``` 3. The script will retrieve the IPs of the load balancer via the AWS CLI and return the required config change. 4. The script prints the `kubectl edit` commands to change the DNS settings of each cluster inline. Copy the statement between the placeholders to edit the CoreDNS configmap in cluster 0 and cluster 1, depending on the placeholder. An alternative to inline editing is to create two copies of the file [./manifests/coredns.yml](https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-dual-region/procedure/manifests/coredns.yml), one for each cluster. Add the section generated by the script to each file. Apply the changes to each cluster with e.g. `kubectl --context cluster-london -n kube-system apply -f file.yml`. Replace the `context` parameter with your current values.
Example output :::caution For illustration purposes only. These values will not work in your environment. ::: ```bash ./generate_core_dns_entry.sh Please copy the following between ### Cluster 0 - Start ### and ### Cluster 0 - End ### and insert it at the end of your CoreDNS configmap in Cluster 0 kubectl --context cluster-london -n kube-system edit configmap coredns ### Cluster 0 - Start ### camunda-paris.svc.cluster.local:53 { errors cache 30 forward . 10.202.19.54 10.202.53.21 10.202.84.222 { force_tcp } } ### Cluster 0 - End ### Please copy the following between ### Cluster 1 - Start ### and ### Cluster 1 - End ### and insert it at the end of your CoreDNS configmap in Cluster 1 kubectl --context cluster-paris -n kube-system edit configmap coredns ### Cluster 1 - Start ### camunda-london.svc.cluster.local:53 { errors cache 30 forward . 10.192.27.56 10.192.84.117 10.192.36.238 { force_tcp } } ### Cluster 1 - End ### ```
Full configmap example :::caution For illustration purposes only. This file will not work in your environment. ::: ```yaml title="coredns-cm-london.yml" apiVersion: v1 kind: ConfigMap metadata: labels: eks.amazonaws.com/component: coredns k8s-app: kube-dns name: coredns namespace: kube-system data: Corefile: | .:53 { errors health { lameduck 5s } ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa } prometheus :9153 forward . /etc/resolv.conf cache 30 loop reload loadbalance } camunda-paris.svc.cluster.local:53 { errors cache 30 forward . 10.202.19.54 10.202.53.21 10.202.84.222 { force_tcp } } ```
5. Check that CoreDNS has reloaded for the changes to take effect before continuing. Make sure it contains `Reloading complete`: ```bash kubectl --context $CLUSTER_0 logs -f deployment/coredns -n kube-system kubectl --context $CLUSTER_1 logs -f deployment/coredns -n kube-system ``` #### Verify DNS chaining The script [test_dns_chaining.sh](https://github.com/camunda/camunda-deployment-references/tree/main/aws/kubernetes/eks-dual-region/procedure/test_dns_chaining.sh) within the folder `aws/kubernetes/eks-dual-region/procedure/` of the repository will help to test that the DNS chaining is working by using nginx pods and services to ping each other. 1. Execute the [test_dns_chaining.sh](https://github.com/camunda/camunda-deployment-references/tree/main/aws/kubernetes/eks-dual-region/procedure/test_dns_chaining.sh). Make sure you have previously exported the [environment variables](#export-environment-variables) as the script builds on top of it. ```bash ./test_dns_chaining.sh ``` 2. Watch how a nginx pod and service will be deployed per cluster. It will wait until the pods are ready and finally ping from nginx in cluster 0 the nginx in cluster 1 and vice versa. If it fails to contact the other nginx five times, it will fail. ### Configure a high-performance StorageClass Camunda 8 requires high IOPS for performance-critical components such as Zeebe. To achieve this, use AWS `gp3` volumes instead of the default `gp2`. This step defines a custom `StorageClass` that: - Uses `gp3` EBS volumes with optimized IOPS and throughput. - Sets a `Retain` reclaim policy. - Uses `WaitForFirstConsumer` volume binding. - Becomes the default `StorageClass` for the cluster. #### Apply the StorageClass Run the following script from the context of the `aws/kubernetes/eks-dual-region/procedure/` folder to apply the new [storage class](https://github.com/camunda/camunda-deployment-references/tree/main/aws/kubernetes/eks-dual-region/procedure/manifests/storage-class.yml) and set it as default: ```bash reference https://github.com/camunda/camunda-deployment-references/tree/main/aws/kubernetes/eks-dual-region/procedure/storageclass-configure.sh ``` To verify completion of the operation, run: ```bash reference https://github.com/camunda/camunda-deployment-references/tree/main/aws/kubernetes/eks-dual-region/procedure/storageclass-verify.sh ``` You must apply the custom `StorageClass` before installing the Camunda Helm chart so that PersistentVolumeClaims (PVCs) are provisioned with the correct performance characteristics. ## 3. Deploy Camunda 8 via Helm charts :::warning Migration from Bitnami Elasticsearch to ECK in dual-region There is currently no dedicated migration procedure for moving from the Bitnami Elasticsearch subchart to the ECK operator in a dual-region setup. If you need to perform this migration, follow the [single-region migration procedure (documented for Camunda 8.9)](/self-managed/deployment/helm/operational-tasks/migration-from-bitnami/index.md) and apply it individually to each region. ::: ### Deploy Elasticsearch using ECK Elasticsearch is managed using the [Elastic Cloud on Kubernetes (ECK)](https://www.elastic.co/guide/en/cloud-on-k8s/current/index.html) operator. This provides automated lifecycle management and built-in security with auto-generated credentials. For more details on ECK-based deployments, see [Elasticsearch deployment in the operator-based infrastructure guide](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#elasticsearch-deployment). #### Create the S3 backup secret for Elasticsearch Elasticsearch requires an S3 bucket for data backup and restore procedures during regional failback. The ECK operator injects these credentials into the Elasticsearch keystore at startup. You can pull the data from Terraform since you exposed those via `output.tf`. 1. From the Terraform code location `aws/kubernetes/eks-dual-region/terraform/clusters`, execute the following to export the access keys to environment variables: ```bash export AWS_ACCESS_KEY_ES=$(terraform output -raw s3_aws_access_key) export AWS_SECRET_ACCESS_KEY_ES=$(terraform output -raw s3_aws_secret_access_key) ``` 2. From the folder `aws/kubernetes/eks-dual-region/procedure` of the repository, execute the script [create_elasticsearch_secrets.sh](https://github.com/camunda/camunda-deployment-references/tree/main/aws/kubernetes/eks-dual-region/procedure/create_elasticsearch_secrets.sh). This creates the ECK-compatible `elasticsearch-env-secret` in both regions with the S3 credentials. Those have previously been defined and exported via the [environment variables](#export-environment-variables). ```bash ./create_elasticsearch_secrets.sh ``` 3. Unset environment variables to reduce the risk of potential exposure: ```bash unset AWS_ACCESS_KEY_ES unset AWS_SECRET_ACCESS_KEY_ES ``` :::caution Bucket vulnerable to region outages The Elasticsearch backup [bucket is tied to a specific region](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingBucket.html). If that region becomes unavailable and you want to restore to a different region or S3 services remain disrupted, you must create a new bucket in another region and reconfigure your Elasticsearch cluster to use the new bucket. ::: #### Deploy the ECK operator and elasticsearch clusters Before deploying the Elasticsearch cluster, install the ECK operator and its Custom Resource Definitions (CRDs) in both clusters. The ECK operator manages the lifecycle of Elasticsearch resources in Kubernetes. Run [deploy.sh](https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/elasticsearch/deploy.sh) from `generic/kubernetes/operator-based/elasticsearch/`: ```bash cd generic/kubernetes/operator-based/elasticsearch export ELASTICSEARCH_CLUSTER_FILE="elasticsearch-cluster-dual-region.yml" KUBE_CONTEXT=$CLUSTER_0 ./deploy.sh KUBE_CONTEXT=$CLUSTER_1 ./deploy.sh cd - ``` This performs the following actions: - Installs the ECK CRDs. - Deploys the operator to the `elastic-system` namespace. - Waits for operator readiness. - Creates the Elasticsearch cluster in both regions using the ECK operator.
Review the Elasticsearch deploy.sh script ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/elasticsearch/deploy.sh ```
The dual-region Elasticsearch cluster manifest is located at `generic/kubernetes/operator-based/elasticsearch/elasticsearch-cluster-dual-region.yml`.
Review the Elasticsearch cluster configuration ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/elasticsearch/elasticsearch-cluster-dual-region.yml ```
For more details on the ECK operator deployment, see the [operator-based infrastructure guide](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#elasticsearch-deployment). #### Synchronize Elasticsearch passwords across regions Each ECK-managed Elasticsearch cluster auto-generates its own `elasticsearch-es-elastic-user` secret with a unique password. For Zeebe exporters to authenticate against the remote region's Elasticsearch, each region needs access to the other region's password. Run [sync_elasticsearch_passwords.sh](https://github.com/camunda/camunda-deployment-references/tree/main/aws/kubernetes/eks-dual-region/procedure/sync_elasticsearch_passwords.sh) from `aws/kubernetes/eks-dual-region/procedure` to create cross-region password secrets: ```bash cd aws/kubernetes/eks-dual-region/procedure ./sync_elasticsearch_passwords.sh cd - ``` This script reads the ECK-generated passwords and creates region-specific secrets in both regions: - `elasticsearch-es-password-region-0`: password from region 0's Elasticsearch - `elasticsearch-es-password-region-1`: password from region 1's Elasticsearch These secrets are referenced by the Zeebe exporter configuration in `camunda-values.yml` to authenticate against Elasticsearch in each region. ### Camunda 8 Helm chart prerequisites Within the cloned repository, navigate to `aws/kubernetes/eks-dual-region/helm-values`. This contains a dual-region example setup. The approach is to work with layered Helm values files: - Have a base `camunda-values.yml` that is generally applicable for both Camunda installations - Two overlays that are for region 0 and region 1 installations ##### camunda-values.yml This forms the base layer that contains the basic required setup, which applies to both regions. Key changes of the dual-region setup: - `global.security.authentication.method: basic` - Uses Basic authentication for inter-component communication since Management Identity (Keycloak) is not deployed in dual-region. - `global.identity.auth.enabled: false` - This reference uses Basic authentication instead of Management Identity. For more details, see [Management platform and Orchestration Cluster](/self-managed/concepts/multi-region/dual-region.md#management-platform-and-orchestration-cluster) on the dual-region concept page. - `identity.enabled: false` - This reference doesn't deploy Management Identity. - `optimize.enabled: false` - This reference doesn't deploy Optimize. You can run Optimize in a single region alongside a dual-region cluster, which requires OIDC authentication and Management Identity. - `orchestration.exporters.zeebe.enabled: false` - Disables the automatic Elasticsearch Exporter configuration in the Helm chart. This exporter was previously used with Optimize and earlier setups. - `orchestration.exporters.camunda.enabled: false` - Disables the automatic Camunda Exporter configuration in the Helm chart. Values are supplied manually through environment variables. - `orchestration.env` - `CAMUNDA_CLUSTER_INITIALCONTACTPOINTS` - These are the contact points for the brokers to know how to form the cluster. Find more information on what the variable means in [setting up a cluster](../../../../../components/orchestration-cluster/zeebe/operations/setting-up-a-cluster.md). - `CAMUNDA_DATA_EXPORTERS_CAMUNDAREGION0_ARGS_CONNECT_URL` - The Elasticsearch endpoint for region 0 (the ECK-managed **headless** service, for example: `elasticsearch-es-masters` for a cluster named `elasticsearch`). The headless service is required because VPN/peering routes pod IPs, not ClusterIPs — so DNS must return individual pod addresses that are routable cross-cluster. You can discover the exact service name with `kubectl get svc -n `. - `CAMUNDA_DATA_EXPORTERS_CAMUNDAREGION1_ARGS_CONNECT_URL` - The Elasticsearch endpoint for region 1 (the ECK-managed **headless** service, for example: `elasticsearch-es-masters` for a cluster named `elasticsearch`). - `CAMUNDA_DATA_EXPORTERS_CAMUNDAREGION0_CLASSNAME` - `io.camunda.exporter.CamundaExporter` explicitly creates the new Camunda Exporter. - `CAMUNDA_DATA_EXPORTERS_CAMUNDAREGION1_CLASSNAME` - `io.camunda.exporter.CamundaExporter` explicitly creates the new Camunda Exporter. - `CAMUNDA_DATA_EXPORTERS_CAMUNDAREGION0_ARGS_CONNECT_USERNAME` / `CAMUNDA_DATA_EXPORTERS_CAMUNDAREGION0_ARGS_CONNECT_PASSWORD` - Elasticsearch authentication credentials for region 0. The password is sourced from the `elasticsearch-es-password-region-0` secret created by the [password synchronization step](#synchronize-elasticsearch-passwords-across-regions). - `CAMUNDA_DATA_EXPORTERS_CAMUNDAREGION1_ARGS_CONNECT_USERNAME` / `CAMUNDA_DATA_EXPORTERS_CAMUNDAREGION1_ARGS_CONNECT_PASSWORD` - Elasticsearch authentication credentials for region 1. The password is sourced from the `elasticsearch-es-password-region-1` secret. - A cluster of eight Zeebe brokers (four in each of the regions) is recommended for the dual-region setup - `orchestration.clusterSize: 8` - `orchestration.partitionCount: 8` - `orchestration.replicationFactor: 4` - Elasticsearch is managed via the ECK operator and configured through a separate manifest (`elasticsearch-cluster-dual-region.yml`). The Elasticsearch overlay (`camunda-elastic-values.yml`) configures the **local** Elasticsearch connection (URL and authentication) for components that **read** data (orchestration secondary storage, Optimize). This is distinct from the cross-cluster Elasticsearch exporter URLs configured via environment variables above, which handle **writing** data across regions. ##### region0/camunda-values.yml This overlay contains the multi-region identification for the cluster in region 0. It sets `orchestration.partitioning.numberOfZones: 2` and `orchestration.partitioning.zoneIndex: 0`. These two keys replace the deprecated `global.multiregion.regions` and `global.multiregion.regionId`. ##### region1/camunda-values.yml This overlay contains the multi-region identification for the cluster in region 1. It sets `orchestration.partitioning.numberOfZones: 2` and `orchestration.partitioning.zoneIndex: 1`. These two keys replace the deprecated `global.multiregion.regions` and `global.multiregion.regionId`. ### Configure Zeebe environment variables :::caution You must change the following environment variables for Zeebe. The default values will not work for you and are only for illustration. ::: The base `camunda-values.yml` in `aws/kubernetes/eks-dual-region/helm-values` requires adjustments before installing the Helm chart: - `CAMUNDA_CLUSTER_INITIALCONTACTPOINTS` - `CAMUNDA_DATA_EXPORTERS_CAMUNDAREGION0_ARGS_CONNECT_URL` - `CAMUNDA_DATA_EXPORTERS_CAMUNDAREGION1_ARGS_CONNECT_URL` 1. The bash script [generate_zeebe_helm_values.sh](https://github.com/camunda/camunda-deployment-references/tree/main/aws/kubernetes/eks-dual-region/procedure/generate_zeebe_helm_values.sh) in the repository folder `aws/kubernetes/eks-dual-region/procedure/` helps generate those values. You only have to copy and replace them within the base `camunda-values.yml`. It uses the exported environment variables of the [export environment variables](#export-environment-variables) section for namespaces and regions. The script derives the number of Zeebe Brokers from your Helm values. For a dual-region setup, the default is `8` (four brokers per region). ```bash ./generate_zeebe_helm_values.sh ```
Example output :::danger For illustration purposes only. These values will not work in your environment. ::: ```bash ./generate_zeebe_helm_values.sh Use the following to set the environment variable CAMUNDA_CLUSTER_INITIALCONTACTPOINTS in the base Camunda Helm chart values file for Zeebe: - name: CAMUNDA_CLUSTER_INITIALCONTACTPOINTS value: camunda-zeebe.camunda-london.svc.cluster.local:26502,camunda-zeebe.camunda-paris.svc.cluster.local:26502 Use the following to set the environment variable CAMUNDA_DATA_EXPORTERS_CAMUNDAREGION0_ARGS_CONNECT_URL in the base Camunda Helm chart values file for Zeebe: - name: CAMUNDA_DATA_EXPORTERS_CAMUNDAREGION0_ARGS_CONNECT_URL value: http://elasticsearch-es-masters.camunda-london.svc.cluster.local:9200 Use the following to set the environment variable CAMUNDA_DATA_EXPORTERS_CAMUNDAREGION1_ARGS_CONNECT_URL in the base Camunda Helm chart values file for Zeebe. - name: CAMUNDA_DATA_EXPORTERS_CAMUNDAREGION1_ARGS_CONNECT_URL value: http://elasticsearch-es-masters.camunda-paris.svc.cluster.local:9200 ```
2. As the script suggests, replace the environment variables within `camunda-values.yml`. :::note The script generates only the environment-specific values (initial contact points and Elasticsearch URLs). The authentication variables (`CAMUNDA_DATA_EXPORTERS_*_ARGS_CONNECT_USERNAME` / `CAMUNDA_DATA_EXPORTERS_*_ARGS_CONNECT_PASSWORD`) use the cross-region secrets created in the [password synchronization step](#synchronize-elasticsearch-passwords-across-regions) and do not require modification. ::: ### Install Camunda 8 using Helm From the terminal context of `aws/kubernetes/eks-dual-region/helm-values`, and ensure you have previously exported the [environment variables](#export-environment-variables), execute the following: ```bash helm install $CAMUNDA_RELEASE_NAME camunda-platform \ --repo https://helm.camunda.io \ --version $HELM_CHART_VERSION \ --kube-context $CLUSTER_0 \ --namespace $CAMUNDA_NAMESPACE_0 \ -f camunda-values.yml \ -f ../../../../generic/kubernetes/operator-based/elasticsearch/camunda-elastic-values.yml \ -f region0/camunda-values.yml helm install $CAMUNDA_RELEASE_NAME camunda-platform \ --repo https://helm.camunda.io \ --version $HELM_CHART_VERSION \ --kube-context $CLUSTER_1 \ --namespace $CAMUNDA_NAMESPACE_1 \ -f camunda-values.yml \ -f ../../../../generic/kubernetes/operator-based/elasticsearch/camunda-elastic-values.yml \ -f region1/camunda-values.yml ``` ## 4. Verify connectivity to Camunda 8 :::info Authentication changes in 8.8+ Starting from version 8.8, the Orchestration Cluster is configured by default with [Admin](/self-managed/components/orchestration-cluster/admin/overview.md) and is protected by Basic authentication using `demo:demo` as the default username and password. ::: 1. Open a terminal and port-forward the Zeebe Gateway via `kubectl` from one of your clusters. Zeebe is stretching over both clusters and is `active-active`, meaning it doesn't matter which Zeebe Gateway to use to interact with your Zeebe cluster. ```bash kubectl --context "$CLUSTER_0" -n $CAMUNDA_NAMESPACE_0 port-forward services/$CAMUNDA_RELEASE_NAME-zeebe-gateway 8080:8080 ``` 2. Open another terminal and use e.g. `cURL` to print the Zeebe cluster topology: ``` # authentication may vary depending on your setup, the following is just an example call. curl -u demo:demo -L -X GET 'http://localhost:8080/v2/topology' \ -H 'Accept: application/json' ``` 3. Make sure that your output contains all eight brokers from the two regions:
Example output ```json reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/dual-region/procedure/check-zeebe-cluster-topology-output.json ```
## Next steps After successfully deploying Camunda 8 in a dual-region setup, consider the following next steps: - [Dual-region operational procedures](/self-managed/deployment/helm/operational-tasks/dual-region-ops.md) - Learn how to perform failover and failback operations. --- ## Install Camunda 8 on an EKS cluster This guide provides a comprehensive walkthrough for installing the Camunda 8 Helm chart on your existing AWS Kubernetes EKS cluster. It also includes instructions for setting up optional DNS configurations and other optional AWS-managed services, such as OpenSearch and PostgreSQL. Lastly you'll verify that the connection to your Self-Managed Camunda 8 environment is working. :::note Using Amazon Aurora PostgreSQL as secondary storage This page covers both backends. Follow it end to end and pick the **RDBMS** tabs wherever a step offers them, which configures Amazon Aurora PostgreSQL as the secondary storage for the Orchestration Cluster. Keep the default tabs to use Amazon OpenSearch Service instead. For the chart-level reference behind those tabs, see [RDBMS example deployment](/self-managed/deployment/helm/install/helm-with-rdbms.md) and [configure RDBMS in Helm](/self-managed/deployment/helm/configure/database/rdbms.md). ::: ## Requirements - A Kubernetes cluster; see the [eksctl](./eksctl.md) or [Terraform](./terraform-setup.md) guide. - [Helm CLI v4](https://helm.sh/docs/intro/install/) (recommended; see [supported versions](/reference/supported-environments.md#clients)). - [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) to interact with the cluster. - [jq](https://jqlang.github.io/jq/download/) to interact with some variables. - [GNU envsubst](https://www.man7.org/linux/man-pages/man1/envsubst.1.html) to generate manifests. - (optional) Domain name/[hosted zone](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/hosted-zones-working-with.html) in Route53. This allows you to expose Camunda 8 and connect via community-supported [zbctl](https://github.com/camunda-community-hub/zeebe-client-go/blob/main/cmd/zbctl/zbctl.md) or [Camunda Modeler](https://camunda.com/download/modeler/). - A namespace to host Camunda. For the tool versions used, check the [.tool-versions](https://github.com/camunda/camunda-deployment-references/blob/main/.tool-versions) file in the repository. It contains an up-to-date list of versions that we also use for testing. ### Considerations While this guide is primarily tailored for UNIX systems, it can also be run under Windows by utilizing the [Windows Subsystem for Linux](https://learn.microsoft.com/windows/wsl/about). Multi-tenancy is disabled by default and is not covered further in this guide. If you decide to enable it, you may use the same PostgreSQL instance and add an extra database for multi-tenancy purposes. :::caution Optimize compatibility with OpenSearch **Migration:** The migration step will be disabled during the installation. For more information, refer to [using Amazon OpenSearch Service](/self-managed/deployment/helm/configure/database/using-external-opensearch.md). ::: ## Architecture In addition to the infrastructure diagram provided in the [Terraform setup guide](./terraform-setup.md), this section installs Camunda 8 following the architecture described in the [reference architecture](/self-managed/reference-architecture/reference-architecture.md). The architecture includes the following core components: - **Orchestration Cluster**: Core process execution engine (Zeebe, Operate, Tasklist, and Admin) - **Management plane**: Design and management tools (Camunda Hub and Management Identity) To demonstrate how to deploy with a custom domain, the following stack is also included: - **cert-manager**: Automates TLS certificate management with [Let's Encrypt](https://letsencrypt.org/) - **external-dns**: Manages DNS record in Route53 for domain ownership confirmation - **Contour**: Ingress controller backed by the Envoy proxy, providing HTTP/HTTPS load balancing and routing to Kubernetes services ### Secondary storage This guide supports two [secondary storage](/self-managed/concepts/secondary-storage/index.md) backends. Select a variant using the authentication and values tabs throughout this guide. | Backend | Setup | Optimize | Reference architecture | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | ------------- | ------------------------------------------------------- | | **Elasticsearch/OpenSearch** | [Managed Amazon OpenSearch Service](/self-managed/deployment/helm/configure/database/using-external-opensearch.md) | Supported | `eks-single-region` (`eks-single-region-irsa` for IRSA) | | **RDBMS (Amazon Aurora PostgreSQL)** | [Configure RDBMS in Helm](/self-managed/deployment/helm/configure/database/rdbms.md) | Not available | `eks-single-region-rdbms` | Optimize requires Elasticsearch or OpenSearch and is not available with the RDBMS backend. For the RDBMS workflow, see the [RDBMS example deployment](/self-managed/deployment/helm/install/helm-with-rdbms.md). :::note Select your backend using the authentication and values tabs throughout this guide. The **RDBMS** tabs configure Amazon Aurora PostgreSQL as secondary storage. ::: ## Export environment variables To streamline the execution of the subsequent commands, it is recommended to export multiple environment variables. ### Export the AWS region and Helm chart version The following are the required environment variables with some example values: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/procedure/setting-region.sh ``` ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/chart-env.sh ``` - `CAMUNDA_NAMESPACE` is the Kubernetes namespace where Camunda will be installed. - `CAMUNDA_RELEASE_NAME` is the name of the Helm release associated with this Camunda installation. ### Export database values When using either standard authentication (network based or username and password) or IRSA authentication, specific environment variables must be set with valid values. Follow the guide for either [eksctl](./eksctl.md#configuration-1) or [Terraform](./terraform-setup.md#export-values-for-the-helm-chart) to set them correctly. Verify the configuration of your environment variables by running the following loop: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/procedure/check-env-variables.sh ``` ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-irsa/procedure/check-env-variables.sh ``` The RDBMS variant configures Amazon Aurora PostgreSQL as the secondary storage and requires additional orchestration database variables on top of the base configuration: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-rdbms/procedure/check-env-variables.sh ``` ## (Optional) Ingress Setup :::info Domain or domainless installation If you do not have a domain name, external access to Camunda 8 web endpoints from outside the AWS VPC will not be possible. In this case, you may skip the DNS setup and proceed directly to [deploying Camunda 8 via Helm charts](#deploy-camunda-8-via-helm-charts). Alternatively, you can use `kubectl port-forward` to access Camunda without a domain or Ingress configuration. For more information, see the [kubectl port-forward documentation](https://kubernetes.io/docs/reference/kubectl/generated/kubectl_port-forward/). Throughout the rest of this installation guide, we will refer to configurations as **"With domain"** or **"Without domain"** depending on whether the application is exposed via a domain. ::: In this section, we provide an optional setup guide for configuring an Ingress with TLS and DNS management, allowing you to access your application through a specified domain. If you haven't set up an Ingress, refer to the [Kubernetes Ingress documentation](https://kubernetes.io/docs/concepts/services-networking/ingress/) for more details. In Kubernetes, an Ingress is an API object that manages external access to services in a cluster, typically over HTTP, and can also handle TLS encryption for secure connections. For more details on the Ingress controller used here and its configuration options, refer to the [Contour documentation](https://projectcontour.io/docs/). Contour uses the [Envoy proxy](https://www.envoyproxy.io/) as its data plane to route and load balance Ingress traffic. ### Export Values Set the following values for your Ingress configuration: ```shell reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/export-ingress-setup-vars.sh ``` Additionally, obtain these values by following the guide for either [eksctl](./eksctl.md) or [Terraform](./terraform-setup.md), as they will be needed in later steps: - `EXTERNAL_DNS_IRSA_ARN` - `CERT_MANAGER_IRSA_ARN` - `REGION` ### Contour [Contour](https://projectcontour.io/) is a CNCF Incubating, open-source Kubernetes Ingress controller that uses the [Envoy proxy](https://www.envoyproxy.io/) as its data plane. It manages external access to services within a Kubernetes cluster, acting as a reverse proxy and load balancer that routes incoming traffic to the appropriate services based on rules defined in the Ingress resource. The following installs `contour` in the `projectcontour` namespace via Helm. For more configuration options, consult the [Contour Helm chart](https://projectcontour.github.io/helm-charts/). ```shell reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/procedure/install-contour.sh ``` ### external-dns [External-dns](https://github.com/kubernetes-sigs/external-dns) is a Kubernetes add-on that automates the management of DNS records for external resources, such as load balancers or Ingress controllers. It monitors the Kubernetes resources and dynamically updates the DNS provider with the appropriate DNS records. The following installs `external-dns` in the `external-dns` namespace via Helm. For more configuration options, consult the [Helm chart](https://github.com/kubernetes-sigs/external-dns/tree/master/charts/external-dns). Consider setting `domainFilters` via `--set` to restrict access to certain hosted zones. :::tip Make sure to have `EXTERNAL_DNS_IRSA_ARN` exported prior by either having followed the [eksctl](./eksctl.md#policy-for-external-dns) or [Terraform](./terraform-setup.md#outputs) guide. ::: :::danger Uniqueness of txtOwnerId for DNS If you are already running `external-dns` in a different cluster, ensure each instance has a **unique** `txtOwnerId` for the TXT record. Without unique identifiers, the `external-dns` instances will conflict and inadvertently delete existing DNS records. In the example below, it's set to `external-dns` and should be changed if this identifier is already in use. Consult the [documentation](https://kubernetes-sigs.github.io/external-dns/v0.15.0/#note) to learn more about DNS record ownership. ::: ```shell reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/procedure/install-external-dns.sh ``` ### cert-manager [Cert-manager](https://cert-manager.io/) is an open-source Kubernetes add-on that automates the management and issuance of TLS certificates. It integrates with various certificate authorities (CAs) and provides a straightforward way to obtain, renew, and manage SSL/TLS certificates for your Kubernetes applications. To simplify the installation process, it is [recommended](https://cert-manager.io/docs/installation/helm/#3-install-customresourcedefinitions) to install the cert-manager `CustomResourceDefinition` resources before installing the chart. This separate step allows for easy uninstallation and reinstallation of cert-manager without deleting any custom resources that have been installed. ```shell reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/install-cert-manager-crds.sh ``` The following installs `cert-manager` in the `cert-manager` namespace via Helm. For more configuration options, consult the [Helm chart](https://artifacthub.io/packages/helm/cert-manager/cert-manager). The supplied settings also configure `cert-manager` to ease the certificate creation by setting a default issuer, which allows you to add a single annotation on an Ingress to request the relevant certificates. :::tip Make sure to have `CERT_MANAGER_IRSA_ARN` exported prior by either having followed the [eksctl](./eksctl.md#policy-for-cert-manager) or [Terraform](./terraform-setup.md#outputs) guide. ::: ```shell reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/procedure/install-cert-manager.sh ``` Create a `ClusterIssuer` via `kubectl` to enable cert-manager to request certificates from [Let's Encrypt](https://letsencrypt.org/): ```shell reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/procedure/install-cert-manager-issuer.sh ``` ## Identity Provider (IdP) setup ## Deploy Camunda 8 via Helm charts For more configuration options, refer to the [Helm chart documentation](https://artifacthub.io/packages/helm/camunda/camunda-platform#parameters). Additionally, explore our existing resources on the [Camunda 8 Helm chart](/self-managed/deployment/helm/install/quick-install.md) and [guides](/self-managed/deployment/helm/configure/index.md). Depending of your installation path, you may use different settings. For easy and reproducible installations, we will use yaml files to configure the chart. ### 1. Create the `values.yml` file Start by creating a `values.yml` file to store the configuration for your environment. This file will contain key-value pairs that will be substituted using `envsubst`. You can find a reference example of this file here: :::note Database initialization prerequisite If you're using an external Aurora PostgreSQL database, you must create the individual component databases (Identity and Web Modeler) before installing the Helm chart. This initialization step is covered in the infrastructure setup guides: - **Terraform**: See [Configure the database and associated access](./terraform-setup.md#configure-the-database-and-associated-access) in the Terraform setup guide. - **eksctl**: See [Create the databases](./eksctl.md#create-the-databases) in the eksctl guide. Without this step, Management Identity and Web Modeler will fail to connect to their databases. ::: The following makes use of the [combined Ingress setup](/self-managed/deployment/helm/configure/ingress/ingress-setup.md#configuration) by deploying a single Ingress for all HTTP components and a separate Ingress for the gRPC endpoint. :::info Cert-manager annotation for domain installation The annotation `kubernetes.io/tls-acme=true` will be [interpreted by cert-manager](https://cert-manager.io/docs/usage/ingress/) and automatically results in the creation of the required certificate request, easing the setup. ::: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/helm-values/values-domain.yml ``` :::danger Exposure of the Zeebe Gateway Service For production-grade security, keep the Zeebe Gateway on a private network (no public Ingress) and access it only from internal workloads or through a secure VPN connection. This limits the attack surface and ensures process and job traffic remain inside your trusted network boundary. See the [VPN module setup](./terraform-setup.md#vpn-module-setup) for guidance on establishing secure remote access to a private EKS cluster. ::: #### Reference the credentials in secrets Before installing the Helm chart, create Kubernetes secrets to store the database authentication credentials. To create the secrets, run the following commands: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/procedure/create-external-db-secrets.sh ``` ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/helm-values/values-no-domain.yml ``` #### Reference the credentials in secrets Before installing the Helm chart, create Kubernetes secrets to store the database authentication credentials. To create the secrets, run the following commands: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/procedure/create-external-db-secrets.sh ``` The following makes use of the [combined Ingress setup](/self-managed/deployment/helm/configure/ingress/ingress-setup.md#configuration) by deploying a single Ingress for all HTTP components and a separate Ingress for the gRPC endpoint. :::info Cert-manager annotation for domain installation The annotation `kubernetes.io/tls-acme=true` will be [interpreted by cert-manager](https://cert-manager.io/docs/usage/ingress/) and automatically results in the creation of the required certificate request, easing the setup. ::: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-irsa/helm-values/values-domain.yml ``` :::danger Exposure of the Zeebe Gateway Service For production-grade security, keep the Zeebe Gateway on a private network (no public Ingress) and access it only from internal workloads or through a secure VPN connection. This limits the attack surface and ensures process and job traffic remain inside your trusted network boundary. See the [VPN module setup](./terraform-setup.md#vpn-module-setup) for guidance on establishing secure remote access to a private EKS cluster. ::: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-irsa/helm-values/values-no-domain.yml ``` The following makes use of the [combined Ingress setup](/self-managed/deployment/helm/configure/ingress/ingress-setup.md#configuration) by deploying a single Ingress for all HTTP components and a separate Ingress for the gRPC endpoint. :::info Cert-manager annotation for domain installation The annotation `kubernetes.io/tls-acme=true` will be [interpreted by cert-manager](https://cert-manager.io/docs/usage/ingress/) and automatically results in the creation of the required certificate request, easing the setup. ::: The RDBMS values file configures Amazon Aurora PostgreSQL as the secondary storage for the Orchestration Cluster and disables Optimize. ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-rdbms/helm-values/values-domain.yml ``` :::danger Exposure of the Zeebe Gateway Service For production-grade security, keep the Zeebe Gateway on a private network (no public Ingress) and access it only from internal workloads or through a secure VPN connection. This limits the attack surface and ensures process and job traffic remain inside your trusted network boundary. See the [VPN module setup](./terraform-setup.md#vpn-module-setup) for guidance on establishing secure remote access to a private EKS cluster. Additionally, implement fine-grained [Kubernetes NetworkPolicies](https://kubernetes.io/docs/concepts/services-networking/network-policies/) to explicitly allow only required internal components to initiate connections to the Zeebe Gateway Service. Deny all other Ingress traffic at the network layer to reduce blast radius if another workload in the cluster is compromised. ::: #### Reference the credentials in secrets Before installing the Helm chart, create Kubernetes secrets to store the database authentication credentials. To create the secrets, run the following commands: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-rdbms/procedure/create-external-db-secrets.sh ``` The RDBMS values file configures Amazon Aurora PostgreSQL as the secondary storage for the Orchestration Cluster and disables Optimize. ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-rdbms/helm-values/values-no-domain.yml ``` #### Reference the credentials in secrets Before installing the Helm chart, create Kubernetes secrets to store the database authentication credentials. To create the secrets, run the following commands: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-rdbms/procedure/create-external-db-secrets.sh ``` ### 2. Configure your deployment #### Enable Enterprise components Web Modeler, Console, and Management Identity are not enabled by default in this deployment. To enable these enterprise components in an OIDC-enabled full cluster, first deploy the required infrastructure (PostgreSQL, Elasticsearch/OpenSearch, and an IdP, such as Keycloak) using the official operators, then apply the Helm values examples shown in [deploy required dependencies with operators](/self-managed/deployment/helm/configure/operator-based-infrastructure.md). #### Secondary storage options This guide supports a managed Amazon OpenSearch domain (provisioned in the [eksctl](./eksctl.md) or [Terraform](./terraform-setup.md) setup) or Amazon Aurora PostgreSQL as secondary storage. For a comparison of both backends and their reference architectures, see [Secondary storage](#secondary-storage), then select your backend using the authentication and values tabs shown earlier in this guide. RDBMS as secondary storage disables Optimize unless you also deploy Elasticsearch or OpenSearch alongside it. To use Elasticsearch instead of managed OpenSearch, deploy it with [Elastic Cloud on Kubernetes (ECK)](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#elasticsearch-deployment), then configure the component-scoped connection values. The bundled Bitnami Elasticsearch subchart is removed in Camunda 8.10. #### Use an in-cluster PostgreSQL instead of the managed Aurora To run PostgreSQL inside the cluster instead of managed Aurora, deploy it with the [CloudNativePG operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#postgresql-deployment) and point Management Identity and Camunda Hub at it through their `externalDatabase` values. The bundled Bitnami PostgreSQL subchart is removed in Camunda 8.10, so there is no in-chart PostgreSQL to enable. #### Fill your deployment with actual values Once you've prepared the `values.yml` file, run the following `envsubst` command to substitute the environment variables with their actual values: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/assemble-envsubst-values.sh ``` :::note Web Modeler SMTP secret If you plan to enable Web Modeler, create the SMTP secret required for email notifications ([see how it's used by Web Modeler](/self-managed/components/hub/configuration/properties.md#smtp--email)): ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/procedure/create-webmodeler-secret.sh ``` ::: ### 3. Install Camunda 8 using Helm Now that the `generated-values.yml` is ready, you can install Camunda 8 using Helm. Run the following command: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/install-chart.sh ``` This command: - Installs (or upgrades) Camunda using the Helm chart. - Substitutes the appropriate version using the `$CAMUNDA_HELM_CHART_VERSION` environment variable. - Applies the configuration from `generated-values.yml`. You can track the progress of the installation with the deployment readiness check script, which requires [jq](https://jqlang.github.io/jq/) to be installed.
Understand how each component interacts with IRSA #### Web Modeler As the Web Modeler REST API uses PostgreSQL, configure the `restapi` to use IRSA with Amazon Aurora PostgreSQL. Check the [Web Modeler database configuration](../../../../../components/hub/configuration/database.md#running-camunda-hub-on-amazon-aurora-postgresql) for more details. Web Modeler already comes fitted with the [aws-advanced-jdbc-wrapper](https://github.com/awslabs/aws-advanced-jdbc-wrapper) within the Docker image. #### Identity Identity uses PostgreSQL, and `identity` is configured to use IRSA with Amazon Aurora PostgreSQL. Check the [Identity database configuration](/self-managed/components/management-identity/miscellaneous/configuration-variables.md#running-identity-on-amazon-aurora-postgresql) for more details. Identity includes [aws-advanced-jdbc-wrapper](https://github.com/awslabs/aws-advanced-jdbc-wrapper) within the Docker image. :::info Keycloak with IRSA If you deploy Keycloak via the Keycloak Operator and want it to use IRSA for database access, refer to the [official Keycloak documentation](https://www.keycloak.org/server/db#preparing-keycloak-for-amazon-aurora-postgresql) for instructions on configuring Amazon Aurora PostgreSQL with a custom JDBC wrapper. ::: #### Amazon OpenSearch Service ##### Internal database configuration The default setup is sufficient for Amazon OpenSearch Service clusters without **fine-grained access control**. Fine-grained access control adds another layer of security to OpenSearch, requiring you to add a mapping between the IAM role and the internal OpenSearch role. Visit the [AWS documentation](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/fgac.html) on fine-grained access control. There are different ways to configure the mapping within Amazon OpenSearch Service: - Via a [Terraform module](https://registry.terraform.io/modules/idealo/opensearch/aws/latest) in case your OpenSearch instance is exposed. - Via the [OpenSearch dashboard](https://opensearch.org/docs/latest/security/access-control/users-roles/). - Via the **REST API**. To authorize the IAM role in OpenSearch for access, follow these steps: Use the following `curl` command to update the OpenSearch internal database and authorize the IAM role for access. Replace placeholders with your specific values: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-irsa/setup-opensearch-fgac.yml#L28-L48 ``` - Replace `OPENSEARCH_MASTER_USERNAME` and `OPENSEARCH_MASTER_PASSWORD` with your OpenSearch domain admin credentials. - Replace `OPENSEARCH_HOST` with your OpenSearch endpoint URL. - Replace `OPENSEARCH_ROLE_ARN` with the IAM role name created by Terraform, which is output by the `opensearch_role` module. :::note Security of Basic authentication usage **This example uses Basic authentication (username and password), which may not be the best practice for all scenarios, especially if fine-grained access control is enabled.** The endpoint used in this example is not exposed by default, so consult your OpenSearch documentation for specifics on enabling and securing this endpoint. ::: Ensure that the `iam_role_arn` of the previously created `opensearch_role` is assigned to an internal role within Amazon OpenSearch Service. For example, `all_access` on the Amazon OpenSearch Service side is a good candidate, or if required, extra roles can be created with more restrictive access.
## Verify connectivity to Camunda 8 First, we need an OAuth client to be able to connect to the Camunda 8 cluster. ### Generate an M2M token using Identity Generate an M2M token by following the steps outlined in the [Identity getting started guide](/self-managed/components/management-identity/overview.md), along with the [incorporating applications documentation](/self-managed/components/management-identity/application-user-group-role-management/applications.md). Below is a summary of the necessary instructions: 1. Open Identity in your browser at `https://${CAMUNDA_DOMAIN}/managementidentity`. You will be redirected to your IdP and prompted to log in. 2. Log in with the initial user `admin` (defined in `identity.firstUser` of the values file). Retrieve the generated password (created earlier when running the secret creation script) from the Kubernetes secret and use it to authenticate: ```shell kubectl get secret identity-secret-for-components \ --namespace "$CAMUNDA_NAMESPACE" \ -o jsonpath='{.data.identity-first-user-password}' | base64 -d; echo ``` 3. Select **Add application** and select **M2M** as the type. Assign a name like "test." 4. Select the newly created application. Then, select **Access to APIs > Assign permissions**, and select the **Orchestration API** with "read" and "write" permission. 5. Retrieve the `client-id` and `client-secret` values from the application details ```shell export ZEEBE_CLIENT_ID='client-id' # retrieve the value from the identity page of your created m2m application export ZEEBE_CLIENT_SECRET='client-secret' # retrieve the value from the identity page of your created m2m application ``` 6. Open the Orchestration Cluster Admin in your browser at `https://${CAMUNDA_DOMAIN}/admin` and log in with the user `admin` (defined in `identity.firstUser` of the values file). 7. In the Admin navigation menu, select **Roles**. 8. Either select an existing role (for example, **Admin**) or [create a new role](/components/admin/role.md) with the appropriate permissions for your use case. 9. In the selected role view, open the **Clients** tab and click **Assign client**. 10. Enter the client ID of your application created in Management Identity (for example, `test`) and click **Assign client** to save. This operation links the OIDC client to the role's permissions in the Orchestration Cluster, granting the application access to the cluster resources. For more information about managing roles and clients, see [Roles](/components/admin/role.md#manage-clients). Identity and the Orchestration cluster must be port-forwarded to be able to connect to the cluster. If using Keycloak via the Keycloak Operator, you also need to port-forward the Keycloak service. ```shell kubectl port-forward "services/$CAMUNDA_RELEASE_NAME-identity" 8085:80 --namespace "$CAMUNDA_NAMESPACE" kubectl port-forward "services/$CAMUNDA_RELEASE_NAME-zeebe-gateway" 8080:8080 --namespace "$CAMUNDA_NAMESPACE" # If using Keycloak Operator: kubectl port-forward "services/keycloak-service" 18080:18080 --namespace "$CAMUNDA_NAMESPACE" ``` 1. Open Identity in your browser at `http://localhost:8085/managementidentity`. You will be redirected to your IdP and prompted to log in. 2. Log in with the initial user `admin` (defined in `identity.firstUser` of the values file). Retrieve the generated password (created earlier when running the secret creation script) from the Kubernetes secret and use it to authenticate: ```shell kubectl get secret identity-secret-for-components \ --namespace "$CAMUNDA_NAMESPACE" \ -o jsonpath='{.data.identity-first-user-password}' | base64 -d; echo ``` 3. Select **Add application** and select **M2M** as the type. Assign a name like "test". 4. Select the newly created application. Then, select **Access to APIs > Assign permissions**, and select the **Orchestration API** with "read" and "write" permission. 5. Retrieve the `client-id` and `client-secret` values from the application details ```shell export ZEEBE_CLIENT_ID='client-id' # retrieve the value from the identity page of your created m2m application export ZEEBE_CLIENT_SECRET='client-secret' # retrieve the value from the identity page of your created m2m application ``` 6. Open the Orchestration Cluster Admin in your browser at `http://localhost:8080/admin` and log in with the user `admin` (defined in `identity.firstUser` of the values file). 7. In the Admin navigation menu, select **Roles**. 8. Either select an existing role (for example, **Admin**) or [create a new role](/components/admin/role.md) with the appropriate permissions for your use case. 9. In the selected role view, open the **Clients** tab and click **Assign client**. 10. Enter the client ID of your application created in Management Identity (for example, `test`) and click **Assign client** to save. This operation links the OIDC client to the role's permissions in the Orchestration Cluster, granting the application access to the cluster resources. For more information about managing roles and clients, see [Roles](/components/admin/role.md#manage-clients). ### Use the token For a detailed guide on generating and using a token, consult the relevant documentation on [authenticating with the Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md). Export the following environment variables: ```shell reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/export-verify-zeebe-domain.sh ``` This requires to port-forward the Zeebe Gateway to be able to connect to the cluster. ```shell kubectl port-forward "services/$CAMUNDA_RELEASE_NAME-zeebe-gateway" 8080:8080 --namespace "$CAMUNDA_NAMESPACE" ``` Export the following environment variables: ```shell reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/export-verify-zeebe-local.sh ``` Generate a temporary token to access the Orchestration Cluster REST API, then capture the value of the `access_token` property and store it as your token. Use the stored token (referred to as `TOKEN` in this case) to interact with the Orchestration Cluster REST API and display the cluster topology: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/check-zeebe-cluster-topology.sh ``` ...and results in the following output:
Example output ```json reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/check-zeebe-cluster-topology-output.json ```
Follow our existing [Modeler guide on deploying a diagram](/self-managed/components/modeler/desktop-modeler/deploy-to-self-managed.md). Below are the helper values required to be filled in Modeler: The following values are required for the OAuth authentication: - **Cluster endpoint:** `https://zeebe-$CAMUNDA_DOMAIN`, replacing `$CAMUNDA_DOMAIN` with your domain - **Client ID:** Retrieve the client ID value from the identity page of your created M2M application - **Client Secret:** Retrieve the client secret value from the Identity page of your created M2M application - **OAuth Token URL:** Your IdP's token endpoint (for example, `https://$CAMUNDA_DOMAIN/auth/realms/camunda-platform/protocol/openid-connect/token` for Keycloak), replacing `$CAMUNDA_DOMAIN` with your domain - **Audience:** `orchestration-api`, the default for Camunda 8 Self-Managed This requires port-forwarding the Zeebe Gateway to be able to connect to the cluster: ```shell kubectl port-forward "services/$CAMUNDA_RELEASE_NAME-zeebe-gateway" 26500:26500 --namespace "$CAMUNDA_NAMESPACE" ``` The following values are required for OAuth authentication: - **Cluster endpoint:** `http://localhost:26500` - **Client ID:** Retrieve the client ID value from the identity page of your created M2M application - **Client Secret:** Retrieve the client secret value from the Identity page of your created M2M application - **OAuth Token URL:** Your IdP's token endpoint (for example, `http://keycloak-service:18080/auth/realms/camunda-platform/protocol/openid-connect/token` for Keycloak Operator) - **Audience:** `orchestration-api`, the default for Camunda 8 Self-Managed
## Advanced topics The following are some advanced configuration topics to consider for your cluster: - [Cluster autoscaling](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md) To get more familiar with our product stack, visit the following topics: - [Operate](/components/operate/operate-introduction.md) - [Tasklist](/components/tasklist/introduction-to-tasklist.md) - [Optimize](/components/optimize/what-is-optimize.md) --- ## Deploy an EKS cluster with eksctl This guide explores the streamlined process of deploying Camunda 8 Self-Managed on Amazon Elastic Kubernetes Service (EKS) using the `eksctl` command-line tool. [Eksctl](https://eksctl.io/) is a common CLI tool for quickly creating and managing your Amazon EKS clusters and is [officially endorsed](https://docs.aws.amazon.com/eks/latest/userguide/getting-started-eksctl.html) by Amazon. While this guide is suitable for testing purposes, building a robust, scalable, and reproducible infrastructure is better achieved using Infrastructure as Code (IaC) tools like those described in the [Terraform guide](./terraform-setup.md), which offers more flexibility and control over your cloud environment. This guide provides a user-friendly approach for setting up and managing Amazon EKS clusters. It covers everything from the prerequisites, such as AWS IAM role configuration, to creating a fully functional Amazon EKS cluster and a managed Aurora PostgreSQL instance. Ideal for those seeking a practical and efficient method to deploy Camunda 8 on AWS, this guide provides detailed instructions for setting up the necessary environment and AWS IAM configurations. ## Prerequisites - An [AWS account](https://docs.aws.amazon.com/accounts/latest/reference/accounts-welcome.html) is required to create resources within AWS. - [kubectl (1.33+)](https://kubernetes.io/docs/tasks/tools/#kubectl), a CLI tool to interact with the cluster. - [AWS CLI (2.23+)](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html), a CLI tool for creating AWS resources. - [eksctl (0.215+)](https://eksctl.io/getting-started/), a CLI tool for creating and managing Amazon EKS clusters. - This guide uses GNU/Bash for all the shell commands listed. ### Considerations This is a basic setup to get started with Camunda 8 but does not reflect a high performance setup. For a better starting point towards production, we recommend utilizing [Infrastructure as Code tooling](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code) and following our [Terraform guide](./terraform-setup.md). We refer to this architecture as the **standard installation**, which can be set up with or without a **domain** ([Ingress](https://docs.aws.amazon.com/eks/latest/userguide/alb-ingress.html)). The standard installation utilizes a username and password connection for the Camunda components (or simply relies on network isolation for certain components). This option is straightforward and easier to implement, making it ideal for environments where simplicity and rapid deployment are priorities, or where network isolation provides sufficient security. To try out Camunda 8 or develop against it, consider signing up for our [SaaS offering](https://camunda.com/platform/), or if you already have an Amazon EKS cluster, consider skipping to the [Helm guide](./eks-helm.md). While the guide is primarily tailored for UNIX systems, it can also be run under Windows by utilizing the [Windows Subsystem for Linux](https://learn.microsoft.com/windows/wsl/about). :::note Cost management This guide provisions resources that can incur costs in your cloud provider account. Review your provider's pricing before you begin. ::: ### Outcome This guide results in the following: - An Amazon EKS Kubernetes cluster running the latest Kubernetes version with four nodes ready for Camunda 8 installation. - Installed and configured [EBS CSI driver](https://docs.aws.amazon.com/eks/latest/userguide/ebs-csi.html), which is used by the Camunda 8 Helm chart to create [persistent volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/). - A [managed Aurora PostgreSQL 17.x](https://aws.amazon.com/rds/aurora/) instance that will be used by the Camunda 8 components. - A [managed OpenSearch domain](https://aws.amazon.com/opensearch-service/) created and configured as a secondary storage option for Camunda (Elasticsearch/OpenSearch variants only; the RDBMS variant uses Amazon Aurora PostgreSQL instead). - [IAM Roles for Service Accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) (IRSA) configured and [Pod Identities](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html). - This simplifies the setup by not relying on explicit credentials, but instead allows creating a mapping between IAM roles and Kubernetes service accounts based on a trust relationship. A [blog post](https://aws.amazon.com/blogs/containers/diving-into-iam-roles-for-service-accounts/) by AWS visualizes this on a technical level. - This allows a Kubernetes service account to temporarily impersonate an AWS IAM role to interact with AWS services like S3, RDS, or Route53 without supplying explicit credentials. - [AWS Quotas](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html) - Ensure at least **3 Elastic IPs** (one per availability zone). - Verify quotas for **VPCs, EC2 instances, and storage**. - Request increases if needed via the AWS console ([guide](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-resource-limits.html)). Costs apply only for resources you provision and use. This basic cluster setup is required to continue with the Helm setup as described in our [AWS Helm guide](./eks-helm.md). ## 1. Configure AWS and eksctl ### Set up AWS authentication Use the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html) to run the following commands: ```bash # set your region export AWS_REGION="eu-central-1" aws configure ``` Enter your `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, and output format. These can be retrieved from the [AWS Console](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html). :::caution Ownership of the created resources A user who creates resources in AWS will always retain administrative access to those resources, including any Kubernetes clusters. It is recommended to create a dedicated [AWS IAM user](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users.html) to ensure that the resources are managed and owned by that specific user. This ensures that the user maintains admin access to Kubernetes and associated resources unless those resources are explicitly deleted. [Create access keys](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) for the new IAM user via the console and export them as `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` variables to use with the AWS CLI and `eksctl`. ::: ### Set up eksctl [eksctl](https://eksctl.io/) is a tool that allows the creation of clusters via a single command, but does not support all configuration options. This setup supplies a YAML file that can be used with the CLI to create the cluster preconfigured with various settings. Review the [installation guide](https://eksctl.io/installation/) for additional details. ### Configure your infrastructure In this guide, we will set up multiple environment variables to configure the components. Each component starts with a section that configures the different variables according to your needs. ## 2. EKS cluster ### Configuration ```shell ##### Kubernetes parameters # The name used for the Kubernetes cluster export CLUSTER_NAME=camunda-cluster # Your standard region that you host AWS resources in export REGION="$AWS_REGION" # Multi-zones, derived from the region export ZONES="${REGION}a ${REGION}b ${REGION}c" # The AWS Account ID export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) # CIDR range used for the VPC subnets export CIDR=10.192.0.0/16 # Optional # Default node type for the Kubernetes cluster export NODE_TYPE=m7i.xlarge # Initial node count to create the cluster with export NODE_COUNT=4 ``` ### Kubernetes secret encryption The following enables [envelope encryption](https://aws.amazon.com/about-aws/whats-new/2020/03/amazon-eks-adds-envelope-encryption-for-secrets-with-aws-kms/) to add another layer of protection to your Kubernetes secrets. We recommend enabling KMS encryption as a first step in creating the cluster. Enabling this configuration afterward can take up to 45 minutes. The KMS key is required in the [eksctl cluster YAML](#create-the-cluster-using-eksctl). Create AWS KMS Key via the aws-cli. For additional settings, visit the [documentation](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/kms/create-key.html). ```shell export KMS_ARN=$(aws kms create-key \ --description "Kubernetes Encryption Key" \ --query "KeyMetadata.Arn" \ --output text) ``` The variable `KMS_ARN` contains the required output. It should look something like this: `arn:aws:kms:eu-central-1:1234567890:key/aaaaaaa-bbbb-cccc-dddd-eeeeeeee`. For more information concerning the KMS encryption, refer to the [eksctl documentation](https://eksctl.io/usage/kms-encryption/). ### Create the cluster using eksctl Execute the following script, which creates a file called `cluster.yaml` with the following contents: ```shell cat <./cluster.yaml --- apiVersion: eksctl.io/v1alpha5 metadata: name: ${CLUSTER_NAME:-camunda-cluster} # e.g. camunda-cluster region: ${REGION:-eu-central-1} # e.g. eu-central-1 version: "1.33" availabilityZones: - ${REGION:-eu-central-1}c # e.g. eu-central-1c, the minimal is two distinct Availability Zones (AZs) within the region - ${REGION:-eu-central-1}b - ${REGION:-eu-central-1}a cloudWatch: clusterLogging: {} iam: vpcResourceControllerPolicy: true addons: - name: vpc-cni resolveConflicts: overwrite version: latest useDefaultPodIdentityAssociations: true - name: kube-proxy resolveConflicts: overwrite version: latest useDefaultPodIdentityAssociations: true - name: aws-ebs-csi-driver resolveConflicts: overwrite version: latest useDefaultPodIdentityAssociations: true - name: coredns resolveConflicts: overwrite version: latest useDefaultPodIdentityAssociations: true - name: eks-pod-identity-agent version: latest kind: ClusterConfig kubernetesNetworkConfig: ipFamily: IPv4 managedNodeGroups: - amiFamily: AmazonLinux2023 desiredCapacity: ${NODE_COUNT:-4} # number of default nodes spawned if no cluster autoscaler is used disableIMDSv1: true iam: withAddonPolicies: albIngress: true autoScaler: true cloudWatch: true ebs: true awsLoadBalancerController: true instanceSelector: {} instanceTypes: - ${NODE_TYPE:-m6i.xlarge} # node type that is selected as default labels: alpha.eksctl.io/cluster-name: ${CLUSTER_NAME:-camunda-cluster} # e.g. camunda-cluster alpha.eksctl.io/nodegroup-name: services maxSize: 10 # maximum node pool size for cluster autoscaler minSize: 1 # minimum node pool size for cluster autoscaler name: services privateNetworking: true releaseVersion: "" securityGroups: withLocal: null withShared: null ssh: allow: false publicKeyPath: "" tags: alpha.eksctl.io/nodegroup-name: services alpha.eksctl.io/nodegroup-type: managed volumeIOPS: 3000 volumeSize: 80 volumeThroughput: 125 volumeType: gp3 privateCluster: enabled: false skipEndpointCreation: false vpc: autoAllocateIPv6: false cidr: ${CIDR:-10.192.0.0/16} clusterEndpoints: privateAccess: false publicAccess: true manageSharedNodeSecurityGroupRules: true nat: gateway: HighlyAvailable secretsEncryption: keyARN: ${KMS_ARN} autoModeConfig: enabled: false EOF ``` With eksctl you can execute the previously created file as follows and takes 25-30 minutes. ```shell cat cluster.yaml eksctl create cluster --config-file cluster.yaml ``` ### (Optional) IAM access management Kubernetes access is divided into two distinct layers. The **first layer** involves **AWS IAM permissions**, which enable basic Amazon EKS functionalities such as using the Amazon EKS UI and generating Amazon EKS access through the AWS CLI. The **second layer** provides **cluster access**, determining the user's permissions within the Kubernetes cluster. As a result, we must initially grant the user adequate AWS IAM permissions and subsequently assign them a specific role within the Kubernetes cluster for proper access management.
First Layer: IAM Permissions A minimum set of permissions is required to gain access to an Amazon EKS cluster. These two permissions allow a user to execute `aws eks update-kubeconfig` to update the local `kubeconfig` with cluster access to the Amazon EKS cluster. The policy should look as follows and can be restricted further to specific Amazon EKS clusters if required: ```shell cat <./policy-eks.json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "eks:DescribeCluster", "eks:ListClusters" ], "Resource": "*" } ] } EOF ``` Via the AWS CLI, you can run the following to create the policy above in IAM. ```shell aws iam create-policy --policy-name "BasicEKSPermissions" --policy-document file://policy-eks.json ``` The created policy `BasicEKSPermissions` has to be assigned to a group, a role, or a user to work. Consult the [AWS documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_manage-attach-detach.html#add-policy-cli) to find the correct approach for you.
Second Layer: Cluster Access By default, the user creating the Amazon EKS cluster has admin access. To allow other users to access it, we have to adjust the `aws-auth` configmap. This can either be done manually via `kubectl` or via `eksctl`. In the following sections, we explain how to do this. #### eksctl With `eksctl`, you can create an AWS IAM user to Kubernetes role mapping with the following command: ```shell eksctl create iamidentitymapping \ --cluster=$CLUSTER_NAME \ --region=$REGION \ --arn arn:aws:iam::0123456789:user/ops-admin \ --group system:masters \ --username admin ``` - `arn` is the identifier of your user. - `group` is the Kubernetes role and as an example `system:masters` is a Kubernetes group for the admin role. - `username` is either the username itself or the role name. It can also be any arbitrary value as it is used for the audit logs to identify the operation owner. Example: ```shell eksctl create iamidentitymapping \ --cluster=$CLUSTER_NAME \ --region=$REGION \ --arn arn:aws:iam::0123456789:user/ops-admin \ --group system:masters \ --username admin ``` More information about usage and other configuration options can be found in the [eksctl documentation](https://eksctl.io/usage/iam-identity-mappings/). #### kubectl The same can also be achieved by using `kubectl` and manually adding the mapping as part of the `mapRoles` or `mapUsers` section. ```shell kubectl edit configmap aws-auth -n kube-system ``` For detailed examples, review the [documentation provided by AWS](https://docs.aws.amazon.com/eks/latest/userguide/auth-configmap.html).
### Access the created EKS cluster Access the Amazon EKS cluster via the `AWS CLI` using the following command: ```shell aws eks --region "$REGION" update-kubeconfig --name "$CLUSTER_NAME" --alias "$CLUSTER_NAME" ``` After updating the kubeconfig, verify your connection to the cluster with `kubectl`: ```shell kubectl get nodes ``` Create a namespace for Camunda: ```shell export CAMUNDA_NAMESPACE="camunda" kubectl create namespace "$CAMUNDA_NAMESPACE" ``` In the remainder of the guide, we reference the `CAMUNDA_NAMESPACE` variable as the namespace to create some required resources in the Kubernetes cluster, such as secrets or one-time setup jobs. ### Check existing StorageClasses We recommend using **gp3** volumes with Camunda 8 (see [volume performance](/self-managed/reference-architecture/kubernetes.md#amazon-eks-1)). It may be necessary to create the `gp3` StorageClass, as the default configuration only includes **gp2**. For detailed information, refer to the [AWS documentation](https://aws.amazon.com/ebs/general-purpose/). :::danger Reclaim policy Using `reclaimPolicy: Delete` can cause **permanent data loss** if a PVC is deleted. Consider using `Retain` for production. See [troubleshooting](/self-managed/operational-guides/troubleshooting.md#zeebe-data-loss-after-pvc-deletion) for details. ::: To see the available StorageClasses in your Kubernetes cluster, including which one is set as default, use the following command: ```bash kubectl describe storageclass ``` To check if `gp3` is set as the default StorageClass, look for the annotation `storageclass.kubernetes.io/is-default-class: "true"` in the output of the previous command. If `gp3` is not installed, or is not set as the default StorageClass, complete the following steps to install it and set it as default: 1. Create the `gp3` StorageClass: ```shell cat << EOF | kubectl apply -f - --- apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: ebs-sc annotations: storageclass.kubernetes.io/is-default-class: "true" provisioner: ebs.csi.aws.com parameters: type: gp3 reclaimPolicy: Retain # CRITICAL: Prevents data loss when PVCs are deleted volumeBindingMode: WaitForFirstConsumer EOF ``` This manifest defines an `ebs-sc` StorageClass to be created. This StorageClass uses the `ebs.csi.aws.com` provisioner, which is supplied by the **aws-ebs-csi-driver** addon installed during cluster creation. For more information, refer to the [official AWS documentation](https://docs.aws.amazon.com/eks/latest/userguide/ebs-csi.html). 2. Modify the `gp2` StorageClass to mark it as a non-default StorageClass: ```shell kubectl patch storageclass gp2 -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}' ``` 3. Verify the changes by running the `kubectl get storageclass` command. After executing these commands, you will have a `gp3` StorageClass set as the default and the `gp2` StorageClass marked as non-default, provided that **gp2** was already present. ### Domain deployment requirements If you plan to deploy Camunda using an external domain associated with an external certificate, you will need to set up some IAM policies to allow both **external-dns** and **cert-manager** to interact with Route 53, which controls the DNS. By default, the cluster uses **Pod Identity** to manage IAM roles for your applications. This means that service accounts are associated with IAM roles, allowing your pods to securely access AWS resources without hardcoding credentials. For more information on configuring Pod Identity, refer to the [AWS documentation](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html). #### Enable OIDC and IAM roles for Service Accounts (IRSA) To [enable OpenID Connect (OIDC) and IAM Roles for Service Accounts (IRSA)](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html) on your cluster, complete the following steps: 1. Determine the OIDC issuer ID for your cluster. First, ensure that your EKS cluster is set up with an OIDC provider. The following command should show you the OIDC issuer: ```bash export oidc_id=$(aws eks describe-cluster --name "$CLUSTER_NAME" --query "cluster.identity.oidc.issuer" --output text | cut -d '/' -f 5) echo "$oidc_id" ``` Determine whether an IAM OIDC provider with your cluster’s issuer ID is already in your account: ```bash aws iam list-open-id-connect-providers | grep $oidc_id | cut -d "/" -f4 ``` If output is returned, an IAM OIDC provider is already set up for your cluster, so you can skip the next step. If no output is returned, you will need to set up an IAM OIDC provider for your cluster. 1. Create an IAM OIDC identity provider for your cluster with the following command: ```bash eksctl utils associate-iam-oidc-provider --region "$REGION" --cluster "$CLUSTER_NAME" --approve ``` #### Policy for external-dns The following instructions are based on the [external-dns](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/aws.md) guide concerning the AWS setup and only covers the required IAM setup. The Helm chart will be installed in the [follow-up guide](./eks-helm.md). The following relies on the previously mentioned feature around IAM Roles for Service Accounts (IRSA) to simplify the external-dns setup. The IAM policy document below allows external-dns to update Route53 resource record sets and hosted zones. You need to create this policy in AWS IAM first. In our example, we will call the policy `AllowExternalDNSUpdates`. You may fine-tune the policy to permit updates only to explicit Hosted Zone IDs. ```shell cat <./policy-dns.json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["route53:ChangeResourceRecordSets"], "Resource": ["arn:aws:route53:::hostedzone/*"] }, { "Effect": "Allow", "Action": [ "route53:ListHostedZones", "route53:ListResourceRecordSets", "route53:ListTagsForResource" ], "Resource": ["*"] } ] } EOF ``` Create AWS IAM policy with the AWS CLI: ```shell aws iam create-policy --policy-name "AllowExternalDNSUpdates" --policy-document file://policy-dns.json # example: arn:aws:iam::XXXXXXXXXXXX:policy/AllowExternalDNSUpdates export EXTERNAL_DNS_POLICY_ARN=$(aws iam list-policies \ --query 'Policies[?PolicyName==`AllowExternalDNSUpdates`].Arn' \ --output text) echo "EXTERNAL_DNS_POLICY_ARN=$EXTERNAL_DNS_POLICY_ARN" ``` The `EXTERNAL_DNS_POLICY_ARN` will be used in the next step to create a role mapping between the Kubernetes Service Account and AWS IAM Service Account. Use `eksctl` to create the required role mapping for external-dns: ```shell eksctl create iamserviceaccount \ --cluster $CLUSTER_NAME \ --name "external-dns" \ --namespace "external-dns" \ --attach-policy-arn $EXTERNAL_DNS_POLICY_ARN \ --role-name="external-dns-irsa" \ --role-only \ --approve ``` ```shell export EXTERNAL_DNS_IRSA_ARN=$(aws iam list-roles \ --query "Roles[?RoleName=='external-dns-irsa'].Arn" \ --output text) echo "EXTERNAL_DNS_IRSA_ARN=$EXTERNAL_DNS_IRSA_ARN" ``` The variable `EXTERNAL_DNS_IRSA_ARN` contains the `arn` (it should look like this: `arn:aws:iam::XXXXXXXXXXXX:role/external-dns-irsa`). Alternatively, you can deploy the Helm chart first and then use `eksctl` with the option `--override-existing-serviceaccounts` instead of `--role-only` to reconfigure the created service account. #### Policy for cert-manager The following instructions are taken from the [cert-manager](https://cert-manager.io/docs/configuration/acme/dns01/route53/) guide concerning the AWS setup and only covers the required IAM setup. The Helm chart will be installed in the [follow-up guide](./eks-helm.md). The following relies on the previously mentioned feature around IAM Roles for Service Accounts (IRSA) to simplify the cert-manager setup. The IAM policy document below allows cert-manager to update Route53 resource record sets and hosted zones. You need to create this policy in AWS IAM first. In our example, we call the policy `AllowCertManagerUpdates`. If you prefer, you may fine-tune the policy to permit updates only to explicit Hosted Zone IDs. ```shell cat <./policy-cert.json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "route53:GetChange", "Resource": "arn:aws:route53:::change/*" }, { "Effect": "Allow", "Action": [ "route53:ChangeResourceRecordSets", "route53:ListResourceRecordSets" ], "Resource": "arn:aws:route53:::hostedzone/*" }, { "Effect": "Allow", "Action": "route53:ListHostedZonesByName", "Resource": "*" } ] } EOF ``` Create AWS IAM policy with the AWS CLI: ```shell aws iam create-policy --policy-name "AllowCertManagerUpdates" --policy-document file://policy-cert.json # example: arn:aws:iam::XXXXXXXXXXXX:policy/AllowCertManagerUpdates export CERT_MANAGER_POLICY_ARN=$(aws iam list-policies \ --query 'Policies[?PolicyName==`AllowCertManagerUpdates`].Arn' \ --output text) echo "CERT_MANAGER_POLICY_ARN=$CERT_MANAGER_POLICY_ARN" ``` The `CERT_MANAGER_POLICY_ARN` is used in the next step to create a role mapping between the Amazon EKS Service Account and the AWS IAM Service Account. Use `eksctl` to create the required role mapping for cert-manager: ```shell eksctl create iamserviceaccount \ --cluster=$CLUSTER_NAME \ --name="cert-manager" \ --namespace="cert-manager" \ --attach-policy-arn=$CERT_MANAGER_POLICY_ARN \ --role-name="cert-manager-irsa" \ --role-only \ --approve ``` ```shell export CERT_MANAGER_IRSA_ARN=$(aws iam list-roles \ --query "Roles[?RoleName=='cert-manager-irsa'].Arn" \ --output text) echo "CERT_MANAGER_IRSA_ARN=$CERT_MANAGER_IRSA_ARN" ``` The variable `CERT_MANAGER_IRSA_ARN` will contain the `arn` (it should look like this: `arn:aws:iam::XXXXXXXXXXXX:role/cert-manager-irsa`). Alternatively, you can deploy the Helm chart first and then use `eksctl` with the option `--override-existing-serviceaccounts` instead of `--role-only` to reconfigure the created service account. ## 3. PostgreSQL database Creating a PostgreSQL database can be accomplished through various methods, such as using the AWS Management Console or the AWS CLI. This guide focuses on providing a reproducible setup using the CLI. For information on creating PostgreSQL using the UI, refer to the [AWS documentation](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_GettingStarted.CreatingConnecting.PostgreSQL.html). :::info Optional service If you don't want to use the Amazon RDS Aurora managed service for PostgreSQL, you can skip this section. However, note that you may need to adjust the following instructions to remove references to it. If you choose not to use this service, you'll need to either provide a managed PostgreSQL service or use the internal deployment by the Camunda Helm chart in Kubernetes. ::: The following components use the PostgreSQL database: - Keycloak - Identity - Web Modeler ### Configuration ```shell ##### Postgres parameters # Name for the Postgres DB cluster and instance export RDS_NAME=camunda-postgres # Postgres DB admin username export AURORA_USERNAME=secret_user # Postgres DB password of the admin user export AURORA_PASSWORD=camundarocks123 # The PostgreSQL version export POSTGRESQL_VERSION=17.5 # For each database, we need to generate a username, password and database name export DB_KEYCLOAK_NAME="keycloak_db" export DB_KEYCLOAK_USERNAME="keycloak-pg" export DB_KEYCLOAK_PASSWORD="CHANGE-ME-PLEASE" export DB_IDENTITY_NAME="identity_db" export DB_IDENTITY_USERNAME="identity-pg" export DB_IDENTITY_PASSWORD="CHANGE-ME-PLEASE" export DB_WEBMODELER_NAME="webmodeler_db" export DB_WEBMODELER_USERNAME="webmodeler-pg" export DB_WEBMODELER_PASSWORD="CHANGE-ME-PLEASE" ``` ### Step-by-step setup 1. Identify the VPC associated with the Amazon EKS cluster: ```shell export VPC_ID=$(aws ec2 describe-vpcs \ --query "Vpcs[?Tags[?Key=='alpha.eksctl.io/cluster-name']|[?Value=='$CLUSTER_NAME']].VpcId" \ --output text) echo "VPC_ID=$VPC_ID" ``` The variable `VPC_ID` contains the output value required for the next step (the value should look like this: `vpc-1234567890`). 2. Create a security group within the VPC to allow connections to the Aurora PostgreSQL instance: ```shell export GROUP_ID_AURORA=$(aws ec2 create-security-group \ --group-name aurora-postgres-sg \ --description "Security Group to allow the Amazon EKS cluster $CLUSTER_NAME to connect to Aurora PostgreSQL $RDS_NAME" \ --vpc-id $VPC_ID \ --query 'GroupId' \ --output text) echo "GROUP_ID_AURORA=$GROUP_ID_AURORA" ``` The variable `GROUP_ID_AURORA` contains the output (the value should look like this: `sg-1234567890`). 3. Create a security Ingress rule to allow access to PostgreSQL: ```shell aws ec2 authorize-security-group-ingress \ --group-id $GROUP_ID_AURORA \ --protocol tcp \ --port 5432 \ --cidr $CIDR ``` 4. Retrieve subnets of the VPC to create a database subnet group: ```shell export SUBNET_IDS=$(aws ec2 describe-subnets \ --filter Name=vpc-id,Values=$VPC_ID \ --query "Subnets[?Tags[?Key=='aws:cloudformation:logical-id']|[?contains(Value, 'Private')]].SubnetId" \ --output text | expand -t 1) echo "SUBNET_IDS=$SUBNET_IDS" ``` The variable `SUBNET_IDS` contains the output values of the private subnets (the value should look like this: `subnet-0123456789 subnet-1234567890 subnet-9876543210`). 5. Create a database subnet group to associate PostgreSQL within the existing VPC: ```shell aws rds create-db-subnet-group \ --db-subnet-group-name camunda-postgres \ --db-subnet-group-description "Subnet for Camunda PostgreSQL $RDS_NAME" \ --subnet-ids $(echo "$SUBNET_IDS") ``` 6. Create a PostgreSQL cluster within a private subnet of the VPC: For the latest Camunda-supported PostgreSQL engine version, check our [documentation](../../../../../../reference/supported-environments.md#camunda-8-self-managed). ```shell aws rds create-db-cluster \ --db-cluster-identifier $RDS_NAME \ --engine aurora-postgresql \ --engine-version $POSTGRESQL_VERSION \ --master-username $AURORA_USERNAME \ --master-user-password $AURORA_PASSWORD \ --vpc-security-group-ids $GROUP_ID_AURORA \ --availability-zones $(echo $ZONES) \ --db-subnet-group-name camunda-postgres ``` More configuration options can be found in the [AWS documentation](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/create-db-cluster.html). 7. Wait for the PostgreSQL cluster to be ready: ```shell aws rds wait db-cluster-available \ --db-cluster-identifier $RDS_NAME ``` 8. Create a database instance within the DB cluster: Ensure that the `engine-version` matches the previously created PostgreSQL cluster. ```shell aws rds create-db-instance \ --db-instance-identifier $RDS_NAME \ --db-cluster-identifier $RDS_NAME \ --engine aurora-postgresql \ --engine-version $POSTGRESQL_VERSION \ --no-publicly-accessible \ --db-instance-class db.t3.medium ``` More configuration options can be found in the [AWS documentation](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/create-db-instance.html). 9. Wait for changes to be applied: ```shell aws rds wait db-instance-available \ --db-instance-identifier $RDS_NAME ``` This command will wait until the instance is ready. ### Create the databases Now that you have a database, you need to create dedicated databases for each Camunda component along with associated users that have configured access. We will also use this step to verify connectivity to the database from the created EKS cluster. The creation of the databases will be performed by spawning a pod job in the Kubernetes cluster, using the main user to create the different databases. 1. Retrieve the writer endpoint of the DB cluster: ```shell export DB_HOST=$(aws rds describe-db-cluster-endpoints \ --db-cluster-identifier $RDS_NAME \ --query "DBClusterEndpoints[?EndpointType=='WRITER'].Endpoint" \ --output text) echo "DB_HOST=$DB_HOST" ``` 2. Create a secret that references the environment variables: ```bash kubectl create secret generic setup-db-secret --namespace "$CAMUNDA_NAMESPACE" \ --from-literal=AURORA_ENDPOINT="$DB_HOST" \ --from-literal=AURORA_PORT="5432" \ --from-literal=AURORA_DB_NAME="postgres" \ --from-literal=AURORA_USERNAME="$AURORA_USERNAME" \ --from-literal=AURORA_PASSWORD="$AURORA_PASSWORD" \ --from-literal=DB_KEYCLOAK_NAME="$DB_KEYCLOAK_NAME" \ --from-literal=DB_KEYCLOAK_USERNAME="$DB_KEYCLOAK_USERNAME" \ --from-literal=DB_KEYCLOAK_PASSWORD="$DB_KEYCLOAK_PASSWORD" \ --from-literal=DB_IDENTITY_NAME="$DB_IDENTITY_NAME" \ --from-literal=DB_IDENTITY_USERNAME="$DB_IDENTITY_USERNAME" \ --from-literal=DB_IDENTITY_PASSWORD="$DB_IDENTITY_PASSWORD" \ --from-literal=DB_WEBMODELER_NAME="$DB_WEBMODELER_NAME" \ --from-literal=DB_WEBMODELER_USERNAME="$DB_WEBMODELER_USERNAME" \ --from-literal=DB_WEBMODELER_PASSWORD="$DB_WEBMODELER_PASSWORD" ``` This command creates a secret named `setup-db-secret` and dynamically populates it with the values from your environment variables. After running the above command, you can verify that the secret was created successfully by using: ```bash kubectl get secret setup-db-secret -o yaml --namespace "$CAMUNDA_NAMESPACE" ``` This should display the secret with the base64 encoded values. 3. Save the following manifest to a file, for example, `setup-postgres-create-db.yml`: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/setup-postgres-create-db.yml ``` This manifest creates the component databases only. The **RDBMS** secondary storage variant needs an additional `camunda_orchestration` database and `orchestration_db` user, which this eksctl path does not provision. To run the Orchestration Cluster on Aurora PostgreSQL, follow the RDBMS variant of the [EKS Terraform setup](./terraform-setup.md#variants) instead. 4. Apply the manifest: ```bash kubectl apply -f setup-postgres-create-db.yml --namespace "$CAMUNDA_NAMESPACE" ``` Once the secret is created, the **Job** manifest from the previous step can consume this secret to securely access the database credentials. 5. Once the job is created, monitor its progress using: ```bash kubectl get job/create-setup-user-db --namespace "$CAMUNDA_NAMESPACE" --watch ``` Once the job shows as `Completed`, the users and databases will have been successfully created. 6. View the logs of the job to confirm that the users were created and privileges were granted successfully: ```bash kubectl logs job/create-setup-user-db --namespace "$CAMUNDA_NAMESPACE" ``` 7. Cleanup the resources: ```bash kubectl delete job create-setup-user-db --namespace "$CAMUNDA_NAMESPACE" kubectl delete secret setup-db-secret --namespace "$CAMUNDA_NAMESPACE" ``` Running these commands will clean up both the job and the secret, ensuring that no unnecessary resources remain in the cluster. ## 4. OpenSearch domain Creating an OpenSearch domain can be accomplished through various methods, such as using the AWS Management Console or the AWS CLI. This guide focuses on providing a reproducible setup using the CLI. For information on creating an OpenSearch domain using the UI, refer to the [AWS OpenSearch documentation](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/create-managed-domain.html). The resulting OpenSearch domain is intended for use with Camunda, the following components utilize OpenSearch: - Orchestration Cluster (Zeebe, Operate, Tasklist, Identity) - Optimize If you are deploying the **RDBMS** secondary storage variant, skip this section. That variant uses Amazon Aurora PostgreSQL as the secondary storage for the Orchestration Cluster and does not create an OpenSearch domain. For details, see [configure RDBMS in Helm](/self-managed/deployment/helm/configure/database/rdbms.md). :::info Optional service If you don't want to use the Amazon OpenSearch managed service, you can skip this section. However, note that you may need to adjust the following instructions to remove references to it. If you choose not to use this service, you can either: - Provide a managed OpenSearch or Elasticsearch service, or use the internal deployment by the Camunda Helm chart in Kubernetes. - Use RDBMS as the secondary storage backend for the Orchestration Cluster. See [configure RDBMS in Helm](/self-managed/deployment/helm/configure/database/rdbms.md) for details and the [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) for supported engines. ::: :::note Migration to OpenSearch is not supported Using Amazon OpenSearch Service requires [setting up a new Camunda installation](/self-managed/setup/overview.md). Migration from previous Camunda versions or Elasticsearch environments is currently not supported. Switching between Elasticsearch and OpenSearch, in either direction, is also not supported. ::: ### Configuration ```shell ##### OpenSearch parameters # Name for the OpenSearch domain export OPENSEARCH_NAME=camunda-opensearch ``` :::caution Network based security The standard deployment for OpenSearch relies on the first layer of security, which is the Network. While this setup allows easy access, it may expose sensitive data. To enhance security, consider implementing IAM Roles for Service Accounts (IRSA) to restrict access to the OpenSearch cluster, providing a more secure environment. For more information, see the [Amazon OpenSearch Service fine-grained access control documentation](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/fgac.html#fgac-access-policies). ::: ### Step-by-step setup 1. Identify the VPC associated with the Amazon EKS cluster: ```shell export VPC_ID=$(aws ec2 describe-vpcs \ --query "Vpcs[?Tags[?Key=='alpha.eksctl.io/cluster-name']|[?Value=='$CLUSTER_NAME']].VpcId" \ --output text) echo "VPC_ID=$VPC_ID" ``` The variable `VPC_ID` contains the output value required for the next steps (the value should look like this: `vpc-1234567890`). 2. Create a security group within the VPC to allow connections to the OpenSearch domain: ```shell export GROUP_ID_OPENSEARCH=$(aws ec2 create-security-group \ --group-name opensearch-sg \ --description "Security Group to allow internal connections From EKS $CLUSTER_NAME to OpenSearch $OPENSEARCH_NAME" \ --vpc-id $VPC_ID \ --query 'GroupId' \ --output text) echo "GROUP_ID_OPENSEARCH=$GROUP_ID_OPENSEARCH" ``` The variable `GROUP_ID_OPENSEARCH` contains the output (the value should look like this: `sg-1234567890`). 3. Create a security Ingress rule to allow access to OpenSearch over HTTPS (port 443) from within the VPC: ```shell aws ec2 authorize-security-group-ingress \ --group-id $GROUP_ID_OPENSEARCH \ --protocol tcp \ --port 443 \ --cidr $CIDR ``` Ensure that the CIDR range is appropriate for your environment. OpenSearch uses `443` as the https transport port. 4. Retrieve the private subnets of the VPC: ```shell export SUBNET_IDS=$(aws ec2 describe-subnets \ --filter Name=vpc-id,Values=$VPC_ID \ --query "Subnets[?Tags[?Key=='aws:cloudformation:logical-id']|[?contains(Value, 'Private')]].SubnetId" \ --output text | expand -t 1) # format it with coma export SUBNET_IDS=$(echo "$SUBNET_IDS" | sed 's/ /,/g') echo "SUBNET_IDS=$SUBNET_IDS" ``` The variable `SUBNET_IDS` now contains the output values of the private subnets (the value should look like this: `subnet-0123456789 subnet-1234567890`). 5. Create the OpenSearch domain: ```shell aws opensearch create-domain --domain-name $OPENSEARCH_NAME \ --engine-version OpenSearch_2.19 \ --cluster-config "InstanceType=m7i.large.search,InstanceCount=3,ZoneAwarenessEnabled=true,ZoneAwarenessConfig={AvailabilityZoneCount=3}" \ --node-to-node-encryption-options Enabled=true \ --ebs-options "EBSEnabled=true,VolumeType=gp3,VolumeSize=50,Iops=3000,Throughput=125" \ --encryption-at-rest-options Enabled=true \ --access-policies "{ \"Version\": \"2012-10-17\", \"Statement\": [{\"Effect\": \"Allow\", \"Principal\": { \"AWS\": \"*\" }, \"Action\": \"es:*\", \"Resource\": \"arn:aws:es:$REGION:*:domain/$OPENSEARCH_NAME/*\" }]}" \ --vpc-options "SubnetIds=${SUBNET_IDS},SecurityGroupIds=${GROUP_ID_OPENSEARCH}" ``` - **Domain Name**: `$OPENSEARCH_NAME` is the name of the OpenSearch domain being created. - **Engine Version**: Uses OpenSearch version `2.19`. - **Cluster Configuration**: - `InstanceType=m7i.large.search` specifies the instance type for the domain. - `InstanceCount=3` creates a cluster with 3 instances. - `ZoneAwarenessEnabled=true` and `ZoneAwarenessConfig={AvailabilityZoneCount=3}` enable zone awareness and spread the instances across 3 availability zones to improve fault tolerance. - **Node-to-Node Encryption**: Encryption for traffic between nodes in the OpenSearch cluster is enabled (`Enabled=true`). - **EBS Options**: - `EBSEnabled=true` enables Elastic Block Store (EBS) for storage. - `VolumeType=gp3` specifies the volume type as `gp3` with 50 GiB of storage. - `Iops=3000` and `Throughput=125` set the IOPS and throughput for the storage. - **Encryption at Rest**: Data stored in the domain is encrypted at rest (`Enabled=true`). - **Access Policies**: The default access policy allows all actions (`es:*`) on resources within the domain for any AWS account (`"Principal": { "AWS": "*" }`). This is scoped to the OpenSearch domain resources using the `arn:aws:es:$REGION:*:domain/$OPENSEARCH_NAME/*` resource ARN. - **VPC Options**: The domain is deployed within the specified VPC, restricted to the provided subnets (`SubnetIds=${SUBNET_IDS}`) and associated security group (`SecurityGroupIds=${GROUP_ID_OPENSEARCH}`). This configuration creates a secure OpenSearch domain with encryption both in transit (between nodes) and at rest, zonal fault tolerance, and sufficient storage performance using `gp3` volumes. The access is restricted to resources in the VPC of the EKS cluster and is governed by the specified security group. :::tip The instance type `m7i.large.search` in the above example is a suggestion, and can be changed depending on your needs. ::: 6. Wait for the OpenSearch domain to be active: ```shell while [ "$(aws opensearch describe-domain --domain-name $OPENSEARCH_NAME --query 'DomainStatus.Processing' --output text)" != "False" ]; do echo "Waiting for OpenSearch domain to become available; this can take up to 20-30 minutes..."; sleep 30; done && echo "OpenSearch domain is now available!" ``` 7. Retrieve the endpoint of the OpenSearch domain: ```shell export OPENSEARCH_HOST=$(aws opensearch describe-domains --domain-names $OPENSEARCH_NAME --query "DomainStatusList[0].Endpoints.vpc" --output text) echo "OPENSEARCH_HOST=$OPENSEARCH_HOST" ``` This endpoint will be used to connect to your OpenSearch domain. ### Verify connectivity from within the EKS cluster To verify that the OpenSearch domain is accessible from within your Amazon EKS cluster, follow these steps: 1. Deploy a temporary pod to test connectivity: Create a temporary pod using the `amazonlinux` image in the `camunda` namespace, install `curl`, and test the connection to OpenSearch—all in a single command: ```bash kubectl run amazonlinux-opensearch -n camunda --rm -i --tty --image amazonlinux -- sh -c "curl -XGET https://$OPENSEARCH_HOST/_cluster/health" ``` 2. Verify the response: If everything is set up correctly, you should receive a response from the OpenSearch service indicating its health status. You have successfully set up an OpenSearch domain that is accessible from within your Amazon EKS cluster. For further details, refer to the [OpenSearch documentation](https://opensearch.org/docs/latest/index/). ## 5. Install Camunda 8 using the Helm chart Now that you've exported the necessary values, you can proceed with installing Camunda 8 using Helm charts. Follow the guide [Camunda 8 on Kubernetes](./eks-helm.md) for detailed instructions on deploying the platform to your Kubernetes cluster. --- ## IAM Roles for Service Accounts (IRSA) Camunda 8 components running on Amazon EKS can authenticate to AWS services, such as Amazon S3, Amazon Aurora PostgreSQL, and Amazon OpenSearch Service, without static access keys. Each component assumes an AWS IAM role through its Kubernetes service account. Amazon EKS provides two mechanisms for this, and Camunda supports both: - [IAM Roles for Service Accounts (IRSA)](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) maps an IAM role to a service account through an OIDC identity provider. IRSA is the mechanism used throughout this page and in the [Terraform reference architecture](terraform-setup.md). - [EKS Pod Identity](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html) maps an IAM role to a service account through the EKS Pod Identity Agent, without an OIDC provider. It is a simpler alternative to IRSA, and is the default for clusters created with the [eksctl guide](eksctl.md). Both mechanisms deliver credentials through the [AWS SDK default credentials provider chain](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/credentials-chain.html), so Camunda components need no code or configuration change to switch between them. Camunda validates its deployments and reference architecture with IRSA, and because credential resolution is handled entirely by the AWS SDK, EKS Pod Identity is supported as well. :::warning Do not configure IRSA and Pod Identity on the same service account Use only one mechanism per Kubernetes service account. If a service account is annotated for IRSA and also has an EKS Pod Identity association, the AWS SDK default credentials provider chain resolves the IRSA web identity token before the Pod Identity credentials. IRSA then takes precedence, and the Pod Identity association is ignored. ::: ## IRSA configuration validation of a Camunda 8 helm deployment The [c8-sm-checks](self-managed/operational-guides/troubleshooting.md#anomaly-detection-scripts) utility is designed to validate IAM Roles for Service Accounts ([IRSA](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html)) configuration in EKS Kubernetes clusters on AWS. It ensures that key components in a Camunda 8 deployment, such as PostgreSQL and OpenSearch, are properly configured to securely interact with AWS resources via the appropriate IAM roles. ### IRSA check script The `/checks/kube/aws-irsa.sh` script verifies IRSA setup in your AWS Kubernetes environment by performing two types of checks: 1. **Configuration Verification**: Ensures key IRSA configurations are correctly set, using specific checks on IAM roles, policies, and mappings to service accounts. 2. **Namespace Commands and Job Execution**: Runs commands within the specified namespace using Kubernetes jobs (if necessary) to verify network and access configurations. This utility is non-intrusive and will not alter any deployment settings. If the `-s` flag is provided, the script skips spawning debugging pods for network flow verification, which can be helpful if pod creation is restricted or not required for troubleshooting. :::info Compatibility with Helm Deployments The script relies on Helm chart values and is compatible only with deployments installed or updated through standard Helm commands. It will not work with other deployment methods, such as those using `helm template` (e.g., [ArgoCD](https://argo-cd.readthedocs.io/en/latest/faq/#after-deploying-my-helm-application-with-argo-cd-i-cannot-see-it-with-helm-ls-and-other-helm-commands)). Compatibility is confirmed for [Camunda Helm chart releases version 11 and above](https://artifacthub.io/packages/helm/camunda/camunda-platform). ::: #### Key features - **Helm values retrieval**: Extracts deployment values using Helm to ensure all required configurations are set. - **EKS and OIDC configuration check**: Confirms that EKS is configured with IAM and OIDC, matching the minimum required version for IRSA compatibility. - **Service account role validation**: For each specified component, verifies that the service account exists and has the correct IAM role annotations. - **Network access verification**: Ensures that PostgreSQL (Aurora) or OpenSearch instances are accessible from within the cluster. This step involves an `nmap` scan through a Kubernetes job. Use the `-s` option to skip this step if network flow verification is unnecessary. - **IRSA value check**: Validates that the Helm deployment values are correctly configured to use IRSA for secure service interactions with AWS. - **Aurora PostgreSQL and OpenSearch IAM configuration**: Confirms that these services support IAM login, ensuring secure access configurations. - **Access and Trust Policy verification**: Checks that access and trust policies are correctly set. Note that the script performs basic checks; if issues arise with these policies, further manual verification may be needed. - **Service Account Role association test**: Tests that the IAM role association with the service account is functioning as expected by spawning a job with the specified service account and validating the resulting ARN. This step can also be skipped using the `-s` option. - **OpenSearch Access Policy check**: Validates that the OpenSearch access policy is configured correctly to support secure connections from the cluster. #### Example usage You can find the complete usage details in the [c8-sm-checks repository](https://github.com/camunda/c8-sm-checks/). Below is a quick reference for common usage options: :::note `identityKeycloak` in the default component list The default PostgreSQL component list below still contains `identityKeycloak`, the Bitnami subchart removed in Camunda 8.10. The script cannot yet detect an operator-managed or external Keycloak from a boolean `global.identity.keycloak.internal: false`, so it still runs the `identityKeycloak` checks and reports them as failures. Pass `-p "identity,webModeler"` to drop it from the list. ::: ```bash Usage: ./checks/kube/aws-irsa.sh [-h] [-n NAMESPACE] [-e EXCLUDE_COMPONENTS] [-p] [-l] [-s] Options: -h Display this help message -n NAMESPACE Specify the namespace to use (required) -e EXCLUDE_COMPONENTS Comma-separated list of Components to exclude from the check (reference of the component is the root key used in the chart) -p Comma-separated list of Components to check IRSA for PostgreSQL (overrides default list: identityKeycloak,identity,webModeler) -l Comma-separated list of Components to check IRSA for OpenSearch (overrides default list: orchestration,optimize) -s Disable pod spawn for IRSA and connectivity verification. By default, the script spawns jobs in the specified namespace to perform IRSA checks and network connectivity tests. These jobs use the amazonlinux:latest image and scan with nmap to verify connectivity. ``` **Example Command:** ```bash ./checks/kube/aws-irsa.sh -n camunda-primary -p "identity,webModeler" -l "orchestration" ``` In this example, the script will check **`identity`** and **`webModeler`** components (references of the component name in the helm chart) for Aurora PostgreSQL access and only **`orchestration`** for OpenSearch access in the `camunda-primary` namespace. #### Script output overview The script offers detailed output to confirm that each component is properly configured for IRSA. Below is an outline of the checks it performs and the expected output format: **Example Output:** ``` [OK] AWS CLI version 2.15.20 is compatible and user is logged in. [OK] AWS environment detected. Proceeding with the script. [INFO] Chart camunda-platform is deployed in namespace camunda-primary. [INFO] Retrieved values for Helm deployment: camunda-platform-11.0.1. [FAIL] The service account keycloak-sa does not have a valid eks.amazonaws.com/role-arn annotation. You must add it in the chart, see https://docs.camunda.io/docs/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/eks-helm/ [FAIL] RoleArn name for component 'identityKeycloak' is empty. Skipping verification. ``` The script highlights errors with the `[FAIL]` prefix, and these are directed to `stderr` for easier filtering. We recommend capturing `stderr` output to quickly identify failed configurations. If the script returns a false positive—indicating success when issues are actually present—manually review each output line to ensure reported configuration details (like Role ARNs or annotations) are accurate. For example, ensure that each service account has the correct Role ARN and associated permissions to avoid undetected issues. ### Advanced troubleshooting for IRSA configuration The troubleshooting script provides essential checks but may not capture all potential issues, particularly those related to IAM policies and configurations. If IRSA is not functioning as expected and no errors are flagged by the script, follow the steps below for deeper troubleshooting. #### Spawn a debug pod to simulate the pod environment To troubleshoot in an environment identical to your pod, deploy a debug pod with the necessary service account. Here are examples of debug manifests you can customize for your needs: - [OpenSearch client pod](https://github.com/camunda/camunda-deployment-references/blob/main/aws/modules/fixtures/opensearch-client.yml) - [PostgreSQL client pod](https://github.com/camunda/camunda-deployment-references/blob/main/aws/modules/fixtures/postgres-client.yml) 1. Adapt the manifests to use the specific `serviceAccountName` (e.g., `aurora-access-sa`) you want to test. 2. Insert a sleep timer in the command to allow time to exec into the pod for live debugging. 3. Create the pod with the `kubectl apply` command: ```bash kubectl apply -f debug-client.yaml ``` 4. Once the pod is running, connect to it with a bash shell (make sure to adjust the app label with your value): ```bash kubectl exec -it $(kubectl get pods -l app=REPLACE-WITH-LABEL -o jsonpath='{.items[0].metadata.name}') -- /bin/bash ``` 5. Inside the pod, display all environment variables to check for IAM and AWS configurations: ```bash env ``` This command will print out all environment variables, including those related to IRSA. Inside the pod, validate that key environment variables are correctly injected: - `AWS_WEB_IDENTITY_TOKEN_FILE`: Path to the token (JWT) file for WebIdentity. - `AWS_ROLE_ARN`: ARN of the associated IAM role. - `AWS_REGION`, `AWS_STS_REGIONAL_ENDPOINTS`, and other AWS configuration variables. To ensure that IRSA and role associations are functioning: - Check that the expected `AWS_ROLE_ARN` and token are present. - Decode the JWT token to validate the correct trust relationship with the service account and namespace. #### Verify OpenSearch fine-grained access control (fgac) configuration For OpenSearch clusters, ensure [fine-grained access control](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/fgac.html) is set up to allow the role’s access to the cluster. If you deployed OpenSearch with the [terraform reference architecture implementation for EKS](terraform-setup.md), fgac should already be configured. For manual deployments, follow the process outlined in the [OpenSearch configuration guide](terraform-setup.md#configure-opensearch-fine-grained-access-control) to apply similar controls. #### Confirm PostgreSQL IAM role access Verify that PostgreSQL roles are correctly configured to support IAM-based authentication. The database user should have the `rds_iam` role to allow IAM authentication. If the setup was automated with the [terraform reference architecture implementation for EKS](terraform-setup.md), the necessary access configuration should already be in place. For manual configurations, refer to [PostgreSQL configuration instructions](terraform-setup.md#configure-the-database-and-associated-access). To test connectivity: - Run a manual connection test using the [PostgreSQL client manifest](https://raw.githubusercontent.com/camunda/camunda-deployment-references/refs/heads/main/aws/modules/fixtures/postgres-client.yml). - Use `psql` within the pod to verify the correct roles are assigned. Run: ```bash SELECT * FROM pg_roles WHERE rolname=''; ``` Confirm that `rds_iam` is listed among the assigned roles. #### Validate IAM Policies for each role Both trust and permission policies are crucial in configuring IAM Roles for Service Accounts (IRSA) in AWS. Each IAM role should have policies that precisely permit necessary actions and correctly trust the relevant Kubernetes service accounts associated with your components. ##### AssumeRole policies In AWS, [AssumeRole](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html) allows a user or service to assume a role and temporarily gain permissions to execute specific actions. Each role needs an **AssumeRole policy** that precisely matches AWS requirements for the specific services and actions your components perform. For each IAM role, ensure the **trust policy** includes: 1. The correct `Service` field, allowing the pod’s service account to assume the role. 2. An `Action` for `sts:AssumeRoleWithWebIdentity`, as IRSA uses WebIdentity to enable IAM role assumption. Verify that the policy is configured according to [AWS’s role trust policy guidelines](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html) for Kubernetes IRSA. ##### Trust policies For each role, verify that the [trust policy syntax is correct](https://aws.amazon.com/fr/blogs/security/how-to-use-trust-policies-with-iam-roles/), allowing the appropriate service accounts to assume the role. Refer to AWS’s [trust policy validation tool](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_policy-validator.html) for [accurate syntax and configuration](https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-reference-policy-checks.html). ##### Permission policies Each IAM role should also have appropriate permission policies attached. These policies define what actions the role can perform on AWS resources. Verify that permission policies: - Are configured correctly to allow the necessary operations for your resources (e.g., read and write access to S3 buckets or access to RDS). - Align with your security model by only granting the minimum required permissions. The AWS’s [policy simulator](https://policysim.aws.amazon.com/) is a valuable tool for testing how permissions are applied and for spotting misconfigurations. #### If issues persist If issues remain unresolved, compare your configuration with Camunda’s [reference architecture](terraform-setup.md) deployed with Terraform. This setup has been validated to work with IRSA and contains the correct permissions. By comparing it to your setup, you may identify discrepancies that are causing your issues. ## Instance Metadata Service (IMDS) [Instance Metadata Service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html) is a default fallback for the AWS SDK due to the [default credentials provider chain](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/credentials-chain.html). Within the context of Amazon EKS, it means a pod will automatically assume the role of a node. This can hide many problems, including whether IRSA was set up correctly or not, since it will fall back to IMDS in case of failure and hide the actual error. If nothing within your cluster relies on the implicit node role, Camunda recommends disabling it by configuring the `http_put_response_hop_limit` to 1. This decreases the default value from two to one, so pods are not allowed to assume the role of the node. ### Configure IMDS hop limit **For new node groups** that use a Terraform module such as the [Amazon EKS module](https://registry.terraform.io/modules/terraform-aws-modules/eks/aws/latest), you can define the following: ```hcl eks_managed_node_groups = { services = { metadata_options = { http_put_response_hop_limit = 1 } } } ``` :::note Enabled by default in the terraform reference architecture of EKS In the [reference architecture with terraform](terraform-setup.md), this setting is configured like that by default. ::: **For existing worker node instances**, you can modify the hop limit using the AWS CLI: ```bash aws ec2 modify-instance-metadata-options \ --instance-id \ --http-put-response-hop-limit 1 ``` Replace `` with your actual EC2 instance ID. You'll need to run this command for each worker node in your cluster. More details can be found in the [AWS documentation on modifying IMDS for existing instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-IMDS-existing-instances.html#modify-PUT-response-hop-limit). Overall, this will disable the role assumption of the node for the Kubernetes pod. ## Document store (S3) When using the [AWS S3 document store](/self-managed/concepts/document-handling/configuration/helm.md) on Amazon EKS, you can authenticate to S3 with IRSA instead of static AWS credentials. This removes long-lived access keys from your Kubernetes secrets and enables Camunda components to assume an IAM role through their service account. ### Prerequisites - An [IAM OIDC provider associated with your EKS cluster](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html). - An existing S3 bucket for documents. ### Create the IAM role and policies Following the [AWS IRSA documentation](https://docs.aws.amazon.com/eks/latest/userguide/associate-service-account-role.html), create an IAM role that the Camunda service accounts can assume. Attach a **permission policy** that grants the required S3 actions on your bucket: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:DeleteObject", "s3:ListBucket" ], "Resource": ["arn:aws:s3:::", "arn:aws:s3:::/*"] } ] } ``` The role's **trust policy** must allow the relevant Kubernetes service accounts to assume it through web identity. Replace the account ID, region, OIDC provider ID, namespace, and release name with your values: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam:::oidc-provider/oidc.eks..amazonaws.com/id/" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "oidc.eks..amazonaws.com/id/:sub": [ "system:serviceaccount::-orchestration" ] } } } ] } ``` ### Configure the Helm chart Set `global.documentStore.type.aws.irsa.enabled` to `true` so the chart does not inject `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` into the pods, and annotate the service account of each component that accesses the document store with the IAM role ARN: ```yaml global: documentStore: activeStoreId: "aws" type: aws: enabled: true irsa: enabled: true bucket: "" region: "" orchestration: serviceAccount: annotations: eks.amazonaws.com/role-arn: arn:aws:iam:::role/ ``` Annotate the `orchestration` service account shown above. :::note With `irsa.enabled: true`, no AWS credentials secret is required. The AWS SDK resolves credentials through its [default provider chain](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/credentials-chain.html), which picks up the IRSA web identity token. ::: ### Configure connector task credentials separately Connectors access documents through the Orchestration REST API, not the document store directly. Don't grant the document-store IAM role to the Connectors service account. Starting with Camunda 8.10 (Helm chart 15.x), the chart no longer propagates document-store credentials to the Connectors pod. If a connector task uses cloud credentials from the pod environment, configure a role or Secret scoped to the connector tasks under `connectors`, as shown in [migrate document-store cloud credentials](/self-managed/upgrade/helm/890-to-8100.md#migrate-document-store-cloud-credentials). Don't reuse the document-store role or credentials. ### Verify 1. Confirm the pods start successfully without an AWS credentials secret: ```bash kubectl get pods -n ``` 2. Confirm the IRSA environment variables are injected and the static credentials are absent: ```bash kubectl exec -n -- env | grep AWS ``` You should see `AWS_ROLE_ARN` and `AWS_WEB_IDENTITY_TOKEN_FILE`, and no `AWS_ACCESS_KEY_ID`. 3. Upload and download a document to confirm S3 access works end to end. ### Authenticate with EKS Pod Identity instead To authenticate the document store with [EKS Pod Identity](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html) rather than IRSA, keep `global.documentStore.type.aws.irsa.enabled` set to `true`. Despite its name, this flag only stops the chart from injecting the static `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` credentials. Pod Identity requires this too, so the AWS SDK resolves credentials through its default provider chain. The remaining steps differ from the IRSA setup: - Skip the OIDC trust policy and the `eks.amazonaws.com/role-arn` service account annotation. Both apply to IRSA only. - Grant the IAM role to the `orchestration` service account by creating an [EKS Pod Identity association](https://docs.aws.amazon.com/eks/latest/userguide/pod-id-association.html). The role still needs the same S3 permission policy as the IRSA setup, with a trust policy for the `pods.eks.amazonaws.com` service principal. - When you verify the pods, expect the `AWS_CONTAINER_CREDENTIALS_FULL_URI` environment variable instead of `AWS_ROLE_ARN` and `AWS_WEB_IDENTITY_TOKEN_FILE`. ## Elasticsearch backup with IRSA When implementing [backup and restore procedures](/self-managed/operational-guides/backup-restore/backup-and-restore.md) for Elasticsearch in your Camunda deployment, you can use IRSA to securely access S3 buckets. ### Elasticsearch backup configuration :::note The bundled Bitnami Elasticsearch subchart is removed in Camunda 8.10, so the snapshot configuration that targeted that subchart no longer applies. For those steps, see the [8.9 documentation](https://docs.camunda.io/docs/8.9/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/irsa/). ::: In Camunda 8.10, deploy Elasticsearch with the [ECK operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#elasticsearch-deployment) or use a managed service. IRSA covers the ECK deployment only, because a managed service runs outside your cluster and exposes no Kubernetes service account. For an ECK deployment, create an IAM role mapped to the Elasticsearch service account with the required S3 permissions, following the [AWS IRSA documentation](https://docs.aws.amazon.com/eks/latest/userguide/associate-service-account-role.html) and the [Elasticsearch S3 repository documentation](https://www.elastic.co/docs/deploy-manage/tools/snapshot-and-restore/s3-repository#repository-s3-permissions). Then configure the ECK deployment to recognize the IRSA token and register the S3 snapshot repository, as described in the [Elasticsearch documentation](https://www.elastic.co/docs/deploy-manage/tools/snapshot-and-restore/s3-repository#iam-kubernetes-service-accounts). The `` and IAM role ARN come from the [AWS IRSA documentation](https://docs.aws.amazon.com/eks/latest/userguide/associate-service-account-role.html). For a managed service, grant snapshot access through the provider's own IAM configuration instead. Amazon OpenSearch Service registers the repository with a dedicated IAM role, as described in [Creating index snapshots in Amazon OpenSearch Service](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/managedomains-snapshots.html). :::info `$AWS_WEB_IDENTITY_TOKEN_FILE` is automatically injected into the pod by EKS when the pod is using a service account annotated with a valid `eks.amazonaws.com/role-arn`. ::: --- ## Multi-region setup with RDBMS (EKS) This guide deploys one Camunda 8 Orchestration Cluster across three AWS regions. It starts the cluster on two regions, then adds the third to the running cluster, the same path the reference implementation tests. It uses [Amazon EKS](https://docs.aws.amazon.com/eks/latest/userguide/what-is-eks.html) for compute and [AWS Transit Gateway](https://docs.aws.amazon.com/vpc/latest/tgw/what-is-transit-gateway.html) for inter-region routing. It uses [Submariner](https://submariner.io/) for cross-cluster service discovery and [Aurora Global Database](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-global-database.html) as relational secondary storage. :::caution Review the [Multi-Region RDBMS concept documentation](/self-managed/concepts/multi-region/multi-region-rdbms.md) before continuing, to understand the limitations and requirements of this configuration. ::: The result is a cluster where losing a region does not stop processing. Bringing the region back is a redeployment rather than a data restore. For the reasoning behind the topology, see [partition placement across zones](/self-managed/concepts/multi-region/multi-region-rdbms.md#partition-placement-across-zones). ## High-level design Each layer of the design has one job, and the layers are independent: | Layer | Component | What it provides | | :----------------------- | :------------------------- | :---------------------------------------------------------------------------------------------- | | Compute | One EKS cluster per region | Zeebe brokers, gateway, and connectors for one Camunda zone. | | L3, inter-region routing | AWS Transit Gateway | Carries all cross-region traffic, including Raft and the database writes, on private addresses. | | L7, service discovery | Submariner | Publishes each region's Zeebe service under a name every other region can resolve. | | Secondary storage | Aurora Global Database | One writer and its readers, replicated by the database, reached through a single JDBC URL. | The database regions are **decoupled** from the compute regions. The Aurora members live in London and Paris, while compute spans all three regions. That keeps the database cheaper, and the two topologies stay independent. :::tip New to Terraform or to running Camunda on EKS? Start with the [single-region EKS Terraform setup](./terraform-setup.md), which covers AWS authentication, Terraform state management, and the essentials of an EKS cluster. This guide assumes you have completed a single-region deployment at least once. ::: ## Requirements - **AWS account**: required to create AWS resources in every target region. See [What is an AWS account?](https://docs.aws.amazon.com/accounts/latest/reference/accounts-welcome.html). - **AWS CLI**: command-line tool to manage AWS resources. [Install AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html). - **Terraform**: IaC tool to provision resources. [Install Terraform](https://developer.hashicorp.com/terraform/downloads). - **kubectl**: CLI to interact with Kubernetes clusters. [Install kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl). - **Helm**: package manager for Kubernetes. [Install Helm](https://helm.sh/docs/intro/install/). - **jq**: lightweight JSON processor. [Download jq](https://jqlang.github.io/jq/download/). - **subctl**: Submariner CLI. The reference architecture installs it for you. For the tool versions used in testing, see the repository's [.tool-versions](https://github.com/camunda/camunda-deployment-references/blob/main/.tool-versions) file. ### AWS service quotas Verify your quotas in **every** region before deploying, and request increases where needed: - **Elastic IPs**: at least three per region, one per availability zone. - **VPCs, EC2 instances, and EBS storage**: enough for one EKS cluster per region. - **Transit Gateways**: one per region, plus one peering attachment per region pair. - **Aurora Global Database**: available in the regions you choose for the database. Aurora Global Database is not offered in every region. Some AWS regions are **opt-in** and must be enabled on the account before anything can be created in them. `eu-central-2` (Zurich), used as the third region in this guide, is one of them: ```bash aws account enable-region --region-name eu-central-2 ``` ### Considerations - **This is a multi-region deployment, and costs scale with the region count.** You pay for three EKS control planes and node groups. You also pay for three Transit Gateways with a full peering mesh, billed per attachment-hour. An Aurora Global Database adds a member per database region. AWS bills inter-region data transfer per gigabyte. Destroy the environment when you are done evaluating. - **Non-overlapping CIDRs are mandatory.** Transit Gateway cannot route duplicate prefixes, and Submariner runs without Globalnet, so every CIDR must identify exactly one cluster. - **Round-trip time between regions matters.** Keep it at or below 100 ms. The regions used in this guide are London, Paris, and Zurich, whose pairwise round-trip times are well inside that budget. - **Management Identity, Web Modeler, Console, and Optimize are not part of this deployment.** See [limitations](/self-managed/concepts/multi-region/multi-region-rdbms.md#limitations). - **This guide is a blueprint, not a production deployment.** It shows the moving parts and how they fit together. Adapt sizing, security, and traffic routing to your environment. ### Outcome Following this guide gives you: - Three EKS clusters, one per region, each with its own VPC and a dedicated non-overlapping CIDR. - A Transit Gateway per region, peered in a full mesh, routing every VPC and Kubernetes service range between regions. - Submariner service discovery, publishing each region's Zeebe service as `...svc.clusterset.local`. - An Aurora Global Database with a writer in one region and readers in the others, reached through a single JDBC URL. - One Orchestration Cluster that starts on two regions and grows to three. At the end, it has six brokers, six partitions, and a replication factor of five. Each database region holds two replicas of every partition, and the third region holds one. ## Topology The default topology provisions three region slots. The table shows the cluster after you add the third region. While two regions run, the cluster has four brokers and a replication factor of four. | Setting | Default | Meaning | | :---------------------------------- | :--------------------------------------- | :-------------------------------------------------------------- | | Regions | `eu-west-2`, `eu-west-3`, `eu-central-2` | London, Paris, Zurich | | Zone names | `london`, `paris`, `zurich` | One zone per region | | `orchestration.partitioning.scheme` | `zone-aware` | Zone-aware partitioning | | `numberOfBrokers` per zone | `2` | Brokers deployed in that zone | | `numberOfReplicas` per zone | `2`, `2`, `1` | Two in each database region, one in the tie-breaker | | `orchestration.clusterSize` | `6` | Sum of `numberOfBrokers` across zones. The zone list derives it | | Replication factor | `5` | Sum of `numberOfReplicas` across zones | | `orchestration.partitionCount` | `6` | One partition per broker | | Database regions | Slots `0` and `1` | Aurora members, writer first | Each broker has the name `_`, so `paris_1` is the second broker in the Paris zone. The zone list is identical in every region, as the [values section](#review-the-helm-values) shows. ### CIDR allocation Every region owns a distinct VPC range and a distinct Kubernetes service range. Both are routed over the Transit Gateway: | Slot | Region | VPC and pod CIDR | Kubernetes service CIDR | | :--- | :------------- | :--------------- | :---------------------- | | 0 | `eu-west-2` | `10.192.0.0/16` | `10.190.0.0/16` | | 1 | `eu-west-3` | `10.202.0.0/16` | `10.200.0.0/16` | | 2 | `eu-central-2` | `10.212.0.0/16` | `10.210.0.0/16` | A fourth slot (`eu-south-1`, VPC `10.222.0.0/16`, service CIDR `10.220.0.0/16`) is prepared but disabled. Enable it in `variables.tf` before you bootstrap the cluster. There is no separate pod range. With the [AWS VPC CNI](https://docs.aws.amazon.com/eks/latest/userguide/pod-networking.html), pod IPs are VPC addresses. The Transit Gateway routes VPC CIDRs to enable cross-region pod-to-pod traffic. The connectivity check verifies reachability using the per-pod DNS records that Zeebe brokers dial; it does not test remote ClusterIP access. Service CIDRs are also routed in this reference topology. ## 1. Configure AWS and apply Terraform ### Obtain a copy of the reference architecture ### Review the region topology The region slots are declared in [variables.tf](https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-multi-region-rdbms/terraform/clusters/variables.tf). Adjust the regions, short names, and CIDR blocks to your environment before applying. Two variables control the topology, and they are not interchangeable: | Variable | Meaning | | :-------------------- | :---------------------------------------------------------------------------------------------------------- | | `regions` | The full list of region slots the cluster can grow into. A slot contributes a zone once Camunda runs in it. | | `active_region_count` | How many of those slots are deployed. At least two. | This guide follows the path the reference implementation tests: it bootstraps two of the three slots, then adds the third region. Deploying fewer slots than you provision is the supported growth path. The Camunda zone list covers only the deployed slots, so each partition holds all of its replicas at every size. The cluster survives a region loss once three or more slots run. With two slots, losing either zone leaves no majority, and processing stops until the zone returns. A spare slot joins later through the [add-zone procedure](/self-managed/deployment/helm/operational-tasks/multi-region-rdbms-ops.md#add-a-region), which adds its zone to the running cluster. ### Apply the infrastructure The root module creates every EKS cluster, the Transit Gateway mesh, the security group rules, and the Aurora Global Database in a single state. Keep your settings in a variable file. The reference architecture ships no variable file, so create one: ```hcl title="terraform-cluster.tfvars" cluster_name = "camunda" active_region_count = 2 np_desired_node_count = 4 single_nat_gateway = false database_instance_class = "db.r6g.large" default_tags = { environment = "evaluation" } ``` :::note Start with three regions instead This guide starts on two regions and then adds the third, because that path shows how the cluster grows. You can also start directly with all three regions: set `active_region_count = 3`. The cluster then forms with three zones at bootstrap. Skip [step 6](#6-add-the-third-region), and expect six brokers and a replication factor of five in [step 5](#5-verify-the-deployment). ::: Then apply it: ```bash cd terraform/clusters terraform init terraform apply -var-file=terraform-cluster.tfvars ``` Expect roughly 25 minutes for the EKS clusters and 15 minutes for the Aurora Global Database. They are created in parallel. With `active_region_count = 2`, the cluster runs two zones, `2-2` at replication factor four. Until you add the third region, it survives no zone loss: losing either zone leaves two replicas of four, which is not a majority. :::note Set up remote Terraform state before deploying anything you intend to keep. The [single-region EKS guide](./terraform-setup.md#initialize-terraform) covers creating an S3 backend. ::: ### Bring your own database To run this architecture on a database other than Aurora Global Database, set `deploy_database = false` and supply your own JDBC URL through `CAMUNDA_RDBMS_URL`. Anything that presents a single endpoint following its own writer works the same way. Examples are a PostgreSQL cluster behind a floating endpoint, a connection proxy, or a DNS record you repoint during failover. The generated Helm values use the `LOG_SEQ` replication strategy, which fails at startup on a backend without LSN support. For such a backend, see [replication-agnostic secondary storage](/self-managed/concepts/multi-region/multi-region-rdbms.md#replication-agnostic-secondary-storage) to choose `DELAY` instead. ## 2. Prepare the environment ### Export the Terraform outputs The procedure scripts derive the entire environment from the Terraform state, down to the kubectl context aliases, so nothing has to be typed twice. ```bash cd ../../procedure . ./export-terraform-outputs.sh . ./export_environment_prerequisites.sh ``` :::note The dot is required. These scripts export variables into your current shell, not into a subshell. ::: `export_environment_prerequisites.sh` defines the environment contract of the architecture. You can override every value by exporting it beforehand. Region-indexed values are space-separated lists in slot order. `export-terraform-outputs.sh` always sets `CAMUNDA_RDBMS_URL`, `CAMUNDA_RDBMS_USERNAME`, and `CAMUNDA_RDBMS_PASSWORD` from the Terraform outputs. They are empty with `deploy_database = false`. If you bring your own database, export these three values after that script and before `export_environment_prerequisites.sh`.
See the export_environment_prerequisites.sh script ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-multi-region-rdbms/procedure/export_environment_prerequisites.sh ```
The script refuses to continue if the topology is inconsistent, for example if more than one slot is left empty. :::note One namespace in every cluster The [dual-region setup](./dual-region.md) needs a different namespace per region, because CoreDNS stub forwarding cannot distinguish local from remote traffic. This architecture instead uses the **same namespace name in every cluster**. Submariner disambiguates identically named services with the cluster ID prefix. ::: ### Register the kubectl contexts Create one kubectl context per active region, named after the region's short name, for example `cluster-london`. The rest of the guide selects regions by these names. ```bash ./register-kubecontexts.sh ```
See the register-kubecontexts.sh script ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-multi-region-rdbms/procedure/register-kubecontexts.sh ```
### Configure the storage class Zeebe brokers need a storage class backed by fast disks. Apply it in every region: ```bash ./storageclass-configure.sh ```
See the storageclass-configure.sh script ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-multi-region-rdbms/procedure/storageclass-configure.sh ```
Verify it before continuing. A missing storage class leaves broker PVCs unbound and the pods pending, which is easy to misread later as a networking failure: ```bash ./storageclass-verify.sh ```
See the storageclass-verify.sh script ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-multi-region-rdbms/procedure/storageclass-verify.sh ```
## 3. Connect the clusters Two layers connect the regions, and they have different jobs: - **Transit Gateway**: carries the traffic. - **Submariner**: publishes service names across clusters. ### Install subctl Install the Submariner CLI and put it on your `PATH`. Source the script rather than executing it, so the `PATH` change survives in your shell. ```bash source ./submariner/install-subctl.sh ```
See the install-subctl.sh script ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-multi-region-rdbms/procedure/submariner/install-subctl.sh ```
### Deploy the Submariner broker Deploy the ClusterSet broker into one region. It stores ClusterSet metadata only, so any cluster can host it and its loss does not interrupt anything already established. ```bash ./submariner/deploy-broker.sh ```
See the deploy-broker.sh script ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-multi-region-rdbms/procedure/submariner/deploy-broker.sh ```
Submariner is deployed with its **service-discovery component only**. It provides multi-cluster DNS and nothing else: no gateway nodes, no IPsec tunnel, no route agent. `subctl show connections` is empty by design.
Why there is no encrypted overlay Running Submariner's connectivity component alongside the AWS VPC CNI puts two owners on the same prefixes. Submariner installs node routes for every remote cluster CIDR so it can pull that traffic into its tunnel. With the VPC CNI those CIDRs are the VPC ranges, which the Transit Gateway also routes, including the node addresses the tunnels are built on. The result is tunnels that report `connected`, cross-cluster DNS that resolves correctly, and Raft messages that are silently dropped. Removing one of the two owners removes the whole class of problem. You cannot remove the Transit Gateway, so remove the other owner. The traffic is still encrypted. AWS encrypts inter-region Transit Gateway peering itself. AES-256 protects the traffic at the virtual network layer as it travels between regions. AWS encrypts it again at the physical layer, on links outside its physical control. See [transit gateway peering attachments](https://docs.aws.amazon.com/vpc/latest/tgw/tgw-peering.html) and [encryption in transit](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/data-protection.html#encryption-transit). You give up control of the encryption. The keys are AWS-managed. If a control requires customer-managed keys, enable TLS in the workload, or replace the VPC CNI with Cilium in ENI mode plus WireGuard or IPsec. In ENI mode pod addresses stay ordinary VPC addresses, so the Transit Gateway remains the only owner of the routes.
### Join the clusters to the ClusterSet Join every active region to the ClusterSet, so each one can publish and resolve the others' services. ```bash ./submariner/join-clusters.sh ```
See the join-clusters.sh script ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-multi-region-rdbms/procedure/submariner/join-clusters.sh ```
Then verify: ```bash ./submariner/verify-submariner.sh ```
See the verify-submariner.sh script ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-multi-region-rdbms/procedure/submariner/verify-submariner.sh ```
### Verify the cross-region substrate Check that pods in one region can reach pods in another before you deploy Camunda. Submariner does not carry this traffic, so nothing else covers the Transit Gateway routes and the security group rules. ```bash ./setup-namespaces.sh ./verify-cross-region-connectivity.sh ``` If this fails, the problem is routing or firewalling, not Camunda. See [troubleshooting](#troubleshooting). The probe image is set in [diagnose-submariner.sh](https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-multi-region-rdbms/procedure/submariner/diagnose-submariner.sh). Set `PROBE_IMAGE` before the script to pull from an approved mirror instead: ```bash export PROBE_IMAGE=my-registry.example.com/busybox ``` ### Ports open between regions The security group rules are declared explicitly in [security.tf](https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-multi-region-rdbms/terraform/clusters/security.tf) rather than allowing all traffic between VPCs: | Port | Protocol | Purpose | | :------------ | :------- | :------------------------------------------------------------------ | | 26500 - 26502 | TCP | Zeebe gateway gRPC, command API, and the internal API carrying Raft | | 8080 | TCP | Orchestration Cluster REST API | | 53 | TCP/UDP | CoreDNS and Submariner service discovery | | n/a | ICMP | Cross-region connectivity diagnostics | Terraform creates each rule once per remote VPC range and once per remote service range. The rule count therefore grows linearly with the region count: 20 inbound rules at three regions and 30 at four. The AWS limit is 60 per security group. Terraform asserts that budget at plan time rather than letting the apply fail after the clusters exist. ## 4. Deploy Camunda 8 ### Create the database secret Create the Kubernetes secret holding the database password, in every active region. The Helm values reference it by name rather than carrying the password. ```bash ./create-rdbms-secret.sh ```
See the create-rdbms-secret.sh script ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-multi-region-rdbms/procedure/create-rdbms-secret.sh ```
### Generate the region-dependent values Three values cannot be hardcoded in the Helm values, because they depend on the deployed topology: | Value | Content | | :------------------------------------- | :-------------------------------------------------------------------------------------------------------------- | | `CAMUNDA_CLUSTER_INITIALCONTACTPOINTS` | One entry per active region, pointing at that region's per-zone headless Zeebe service, `camunda-zeebe-`. | | `REGION__ZEEBE_SERVICE_NAME` | The suffix each broker advertises, so peers in other regions can resolve it. | | `CAMUNDA_MULTIREGION_ZONES` | The zone list, covering the deployed slots only. | ```bash . ./generate-zeebe-helm-values.sh ./assemble-envsubst-values.sh ``` :::note Contact points are fully qualified The generated contact points end with a trailing dot, which marks them as fully qualified names. Without it, the resolver walks the pod's search domains first. On a cold multi-region start, a broker whose peer is not yet published can then exhaust its DNS budget and never finish starting. Always keep the trailing dot. ::: ### Review the Helm values The values file is the same in every region. Only `orchestration.partitioning.zone` and the advertised host differ, so one file describes the whole topology.
See the full camunda-values.yml ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-multi-region-rdbms/helm-values/camunda-values.yml ```
The parts worth reading before you install: - `orchestration.partitioning.scheme: zone-aware` selects [zone-aware partitioning](/self-managed/components/orchestration-cluster/zeebe/configuration/zone-aware-clusters.md). The chart rejects `numberOfZones` and `zoneIndex` with this scheme, because the zone list describes the topology instead. The chart derives the cluster size, replication factor, and broker node IDs from that list. See [configure zone-aware multi-region deployments](/self-managed/deployment/helm/configure/multi-region-zone-awareness.md). - `orchestration.partitioning.zones` lists every running zone with its broker count, replica count, and priority. Zone 0 has the highest priority because it hosts the database writer. - `orchestration.data.secondaryStorage.type: rdbms` with a single `url` shared by every broker in every region. - The AWS Advanced JDBC Wrapper uses `initialConnection,failover`. `initialConnection` discovers the current writer when a broker starts after a switchover. `failover` follows a writer change on an established connection. - `CAMUNDA_DATA_SECONDARYSTORAGE_RDBMS_ASYNCREPLICATION_ENABLED: "true"` is required. Without it the exporter acknowledges records the standby has not received, and a writer failover loses exported data. - The reference architecture also pins `CAMUNDA_DATA_SECONDARYSTORAGE_RDBMS_ASYNCREPLICATION_TYPE: LOG_SEQ`, `CAMUNDA_DATA_SECONDARYSTORAGE_RDBMS_ASYNCREPLICATION_MAXLAG: PT1H`, and `CAMUNDA_DATA_SECONDARYSTORAGE_RDBMS_ASYNCREPLICATION_PAUSEONMAXLAGEXCEEDED: "false"`. The first two set a strategy and a lag budget. The third keeps the engine default, because pausing stops exporting. Enable pausing only on purpose. The lag budget has no effect until you enable pausing. The engine compares it only inside the pause condition. - Cross-region SWIM membership timeouts are relaxed. The defaults are tuned for intra-region latency, and on a cold start brokers otherwise see remote peers as unreachable, eject them, and never converge. - `identity`, `console`, and `optimize` are disabled. See [limitations](/self-managed/concepts/multi-region/multi-region-rdbms.md#limitations). ### Install the chart Install the same release in every active region, from the values assembled in the previous step. ```bash ./install-chart.sh ```
See the install-chart.sh script ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-multi-region-rdbms/procedure/install-chart.sh ```
Then export the Camunda services to the ClusterSet so brokers in other regions can resolve them: ```bash ./submariner/export-services.sh ```
See the export-services.sh script ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-multi-region-rdbms/procedure/submariner/export-services.sh ```
## 5. Verify the deployment Verify that every broker joined and that the partition distribution matches the zone list: ```bash ./check-cluster-topology.sh ```
See the check-cluster-topology.sh script ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-multi-region-rdbms/procedure/check-cluster-topology.sh ```
Expect roughly 10 minutes for the Zeebe cluster to converge across regions. At this point, a healthy two-zone cluster reports four brokers, six partitions, and a replication factor of four. Measure the cost of the write path from each region to the database writer. Regions that are not co-located with the writer pay the inter-region round trip on every export flush. That number tells you whether the exporter queue is sized correctly: ```bash ./measure-rdbms-latency.sh ```
See the measure-rdbms-latency.sh script ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-multi-region-rdbms/procedure/measure-rdbms-latency.sh ```
## 6. Add the third region Add the third region slot to the running cluster. The two running regions keep processing and don't restart. When you finish, the cluster runs three zones in the `2-2-1` layout and survives the loss of any one region. This is the same sequence that the reference implementation runs in its CI. The [Add a region](/self-managed/deployment/helm/operational-tasks/multi-region-rdbms-ops.md#add-a-region) runbook explains each step in more detail. ### Prerequisites - Steps 1 to 5 are complete, and the cluster runs on two regions. - The third slot is in `regions` in `variables.tf`. The default topology already has it. ### Provision the third region In `terraform-cluster.tfvars`, change the `active_region_count` line to `3`, then apply the file: ```hcl active_region_count = 3 ``` ```bash cd ../terraform/clusters terraform apply -var-file=terraform-cluster.tfvars ``` Terraform creates the EKS cluster, the Transit Gateway attachments, and the security group rules of the third region. It doesn't change the two running regions. ### Refresh the environment Clear the values derived from the old region count, then export the environment again and register the new kubectl context: ```bash cd ../../procedure unset CAMUNDA_ACTIVE_REGIONS CAMUNDA_CLUSTER_SIZE CAMUNDA_REPLICATION_FACTOR . ./export-terraform-outputs.sh . ./export_environment_prerequisites.sh ./register-kubecontexts.sh ``` The export prints the new topology. Check that it reports three running regions, a cluster size of six, and a replication factor of five. ### Add the region to the cluster Run the procedure for slot `2`, the third slot: ```bash ./activate-region.sh 2 ```
See the activate-region.sh script ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-multi-region-rdbms/procedure/activate-region.sh ```
The script joins the new cluster to the ClusterSet and installs Camunda in it. Then it adds the zone with `POST /actuator/cluster/zones/` and waits for the change to report `COMPLETED`. ### Check the three-zone cluster The script ends with a topology check. To run it again: ```bash ./check-cluster-topology.sh ``` The check passes when the gateway reports six brokers, two in each zone. It also checks for a replication factor of five, the configured partition count, and no unhealthy partition. If it times out, see [Zeebe never reaches the expected broker count](#zeebe-never-reaches-the-expected-broker-count). ## 7. Operate the cluster Day-2 procedures, including region loss, failback, and adding a region, are documented separately in the [Multi-Region RDBMS operational procedure](/self-managed/deployment/helm/operational-tasks/multi-region-rdbms-ops.md). ## Troubleshooting ### Cross-region pod traffic is dropped Prove the substrate before investigating Camunda: ```bash ./verify-cross-region-connectivity.sh ``` Submariner does not carry this traffic, so do not start with `subctl`. The data plane is the Transit Gateway. Check that the remote ranges are routed, expecting one route per remote VPC and service CIDR: ```bash aws ec2 describe-route-tables --region eu-west-2 \ --filters "Name=vpc-id,Values=" \ --query 'RouteTables[].Routes[?TransitGatewayId!=null].[DestinationCidrBlock]' --output text ``` Then check that the remote security group allows the port: ```bash aws ec2 describe-security-group-rules --region eu-west-3 \ --filters "Name=group-id,Values=" \ --query 'SecurityGroupRules[?!IsEgress].[CidrIpv4,IpProtocol,FromPort,ToPort]' --output text ``` ### Names do not resolve across regions If routing is correct but names do not resolve, the problem is service discovery: ```bash subctl show networks --contexts cluster-london kubectl --context cluster-london -n submariner-operator get clusters.submariner.io kubectl --context cluster-london -n camunda get serviceexports,serviceimports ``` A full diagnostic dump, including cross-cluster name resolution, is available: ```bash ./submariner/diagnose-submariner.sh ```
See the diagnose-submariner.sh script ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-multi-region-rdbms/procedure/submariner/diagnose-submariner.sh ```
### Zeebe never reaches the expected broker count This is almost always cross-region DNS. Check the service discovery layer first, then the broker's startup gate: ```bash ./submariner/verify-submariner.sh kubectl --context cluster-london -n camunda logs camunda-zeebe-london-0 -c wait-clusterset-dns ``` If brokers are `Pending` rather than `Running`, the storage class is missing in that region. See [configure the storage class](#configure-the-storage-class). ## Next steps - [Multi-Region RDBMS operational procedure](/self-managed/deployment/helm/operational-tasks/multi-region-rdbms-ops.md): region loss, failback, and adding a region. - [Multi-Region RDBMS concept](/self-managed/concepts/multi-region/multi-region-rdbms.md): the architecture and its trade-offs. - [Zone-aware clusters](/self-managed/components/orchestration-cluster/zeebe/configuration/zone-aware-clusters.md): the full zone configuration reference. - [Relational database configuration](/self-managed/concepts/databases/relational-db/configuration.md): RDBMS secondary storage settings. --- ## Deploy an EKS cluster with Terraform This guide explains how to provision an [Amazon Web Services (AWS) Elastic Kubernetes Service (EKS) cluster](https://docs.aws.amazon.com/eks/latest/userguide/what-is-eks.html) using Terraform, a widely used Infrastructure as Code (IaC) tool. The EKS cluster serves as the infrastructure foundation for running Camunda 8. Camunda recommends this approach for a robust, sustainable setup. For a quicker trial or proof of concept, or if the Terraform module doesn’t meet your needs, see [Deploy an EKS cluster with eksctl](./eksctl.md). For advanced EKS scenarios, see the [Amazon EKS documentation](https://docs.aws.amazon.com/eks/latest/userguide/). :::tip New to Terraform or Infrastructure as Code? Start with the [Terraform IaC documentation](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code) and try the [interactive quick start](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code#quick-start). ::: ## Requirements - **AWS account** – Required to create AWS resources. See [What is an AWS account?](https://docs.aws.amazon.com/accounts/latest/reference/accounts-welcome.html). - **AWS CLI** – Command-line tool to manage AWS resources. [Install AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html). - **Terraform** – IaC tool used to provision resources. [Install Terraform](https://developer.hashicorp.com/terraform/downloads). - **kubectl** – CLI for interacting with Kubernetes clusters. [Install kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl). - **jq** – Lightweight JSON processor. [Download jq](https://jqlang.github.io/jq/download/). - **IAM Roles for Service Accounts (IRSA)** – Configure IRSA to map IAM roles to Kubernetes service accounts. This removes the need for long-lived credentials and lets Kubernetes services assume IAM roles to interact with AWS services (for example, S3, RDS, Route 53). - See the AWS [IRSA deep dive](https://aws.amazon.com/blogs/containers/diving-into-iam-roles-for-service-accounts/). - IRSA is recommended as an [EKS best practice](https://aws.github.io/aws-eks-best-practices/security/docs/iam/). - **AWS service quotas** – Verify your quotas before deployment: - At least 3 Elastic IPs (one per availability zone). - Adequate quotas for **VPCs, EC2 instances, and storage**. - Request increases if needed via the AWS console. You pay only for used resources. See [AWS service quotas](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html) and [Amazon EC2 service quotas](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-resource-limits.html) - **Shell** – Examples use GNU Bash. For the tool versions used in testing, see the repository’s [.tool-versions](https://github.com/camunda/camunda-deployment-references/blob/main/.tool-versions) file. It contains an up-to-date list of versions used for testing. ### Considerations #### General considerations This setup provides a solid starting point for running Camunda 8 on AWS. It is not optimized for peak performance. Use it as a foundation you can extend and adapt for production with [Infrastructure as Code (IaC) tools](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code). If Terraform feels complex at first, try the [eksctl guide](./eksctl.md) for a simpler entry point, which shows the resources created and how they interact with each other. - To test or develop against Camunda 8, consider signing up for our [SaaS offering](https://camunda.com/platform/). - If you already have an Amazon EKS cluster, skip ahead to the [Helm setup guide](./eks-helm.md) to deploy Camunda 8. #### Security considerations The following security considerations were flagged by [Trivy](https://trivy.dev/) to simplify adoption and development. Review and address these before production. You can look up each ID in the [Aqua vulnerability database](https://avd.aquasec.com/). ``` AVD-AWS-0040 #(CRITICAL): Public cluster access is enabled. AVD-AWS-0041 #(CRITICAL): Cluster allows access from a public CIDR: 0.0.0.0/0 AVD-AWS-0104 #(CRITICAL): Security group rule allows egress to multiple public internet addresses. AVD-AWS-0343 #(MEDIUM): Cluster does not have Deletion Protection enabled AVD-AWS-0178 #(MEDIUM): VPC does not have VPC Flow Logs enabled. AVD-AWS-0038 #(MEDIUM): Control plane scheduler logging is not enabled. AVD-AWS-0077 #(MEDIUM): Cluster instance has very low backup retention period. AVD-AWS-0133 #(LOW): Instance does not have performance insights enabled. ``` :::warning Reference architectures and examples provided in this guide are not turnkey modules. Camunda recommends cloning the repository and modifying it locally. You are responsible for operating and maintaining the infrastructure. Camunda updates the reference architecture over time and changes may not be backward compatible. You can use these updates to upgrade your customized codebase as needed. ::: :::note Cost management This guide provisions resources that can incur costs in your cloud provider account. Review your provider's pricing before you begin. ::: ### Variants We support the following variants of this architecture: - **Standard installation** - Uses username and password connection for the Camunda components (or relies on network isolation for specific components). This option is straightforward and easier to implement, making it ideal for environments where simplicity and rapid deployment are priorities, or where network isolation provides sufficient security. - **IRSA** (IAM Roles for Service Accounts) - Uses service accounts to perform authentication with IAM policies. This approach offers stronger security and better integration with AWS services, as it eliminates the need to manage credentials manually. It is especially beneficial in environments with strict security requirements, where fine-grained access control and dynamic role-based access are essential. - **RDBMS** - Uses an Amazon Aurora PostgreSQL database as the [secondary storage](/self-managed/concepts/secondary-storage/index.md) for the Orchestration Cluster instead of Amazon OpenSearch. This variant (`eks-single-region-rdbms`) builds on the standard installation, provisions an additional `camunda_orchestration` database, and does not create an OpenSearch domain, which results in a lighter infrastructure footprint. For configuration details, see [configure RDBMS in Helm](/self-managed/deployment/helm/configure/database/rdbms.md) and [install Camunda 8 with RDBMS](/self-managed/deployment/helm/install/helm-with-rdbms.md). #### How to choose - If you prefer a simpler setup with Basic authentication or network isolation, and your security needs are moderate, the **standard installation** is a suitable choice. - If you require enhanced security, dynamic role-based access management, and want to leverage AWS’s identity services for fine-grained control, the **IRSA** variant is the better option. - If you want a lighter infrastructure without an OpenSearch domain and do not need Optimize, choose the **RDBMS** variant. Optimize requires Elasticsearch or OpenSearch and is not available with RDBMS secondary storage. Each variant can be set up with or without a **Domain** ([Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/)). ### Outcome _Infrastructure diagram for a single region EKS setup (click on the image to open the PDF version)_ [![Infrastructure Diagram EKS Single-Region](./assets/eks-single-region.jpg)](./assets/eks-single-region.pdf) After completing this guide, you will have: - An Amazon EKS Kubernetes cluster running with four nodes ready for Camunda 8 installation. - The [EBS CSI driver](https://docs.aws.amazon.com/eks/latest/userguide/ebs-csi.html), installed and configured. This is used by the Camunda 8 Helm chart to create [persistent volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/). - (Optional) A managed [Aurora PostgreSQL](https://aws.amazon.com/rds/postgresql/) instance for Camunda. - (Optional) A managed [Amazon OpenSearch Service](https://aws.amazon.com/opensearch-service/) domain created and configured for use with Camunda. The RDBMS variant uses Amazon Aurora PostgreSQL as secondary storage and does not create this domain. - (Optional) [IRSA](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) configured so Kubernetes workloads can assume IAM roles without stored credentials. ## 1. Configure AWS and initialize Terraform ### Obtain a copy of the reference architecture The first step is to download a copy of the reference architecture from the [GitHub repository](https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/). This material will be used throughout the rest of this documentation. The reference architectures are versioned using the same Camunda versions (`stable/8.x`). The provided reference architecture repository allows you to directly reuse and extend the existing Terraform example base. This sample implementation is flexible to extend to your own needs without the potential limitations of a Terraform module maintained by a third party. ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/procedure/get-your-copy.sh ``` ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-irsa/procedure/get-your-copy.sh ``` ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-rdbms/procedure/get-your-copy.sh ``` With the reference architecture copied, you can proceed with the remaining steps outlined in this documentation. Ensure that you are in the correct directory before continuing with further instructions. #### Initialize Terraform Once your authentication is set up, you can initialize your Terraform project. The previous steps configured a dedicated S3 Bucket (`S3_TF_BUCKET_NAME`) to store your state, and the following creates a bucket key that will be used by your configuration. Configure the backend and download the necessary provider plugins: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/s3-bucket/s3-bucket-tf-init.sh ``` Terraform will connect to the S3 bucket to manage the state file, ensuring remote and persistent storage. ### EKS cluster module setup This module provides the foundational configuration for AWS access and Terraform usage. We use [Terraform modules](https://developer.hashicorp.com/terraform/language/modules) to abstract resources into reusable components and simplify infrastructure management. The [Camunda AWS EKS cluster module](https://github.com/camunda/camunda-deployment-references/tree/main/aws/modules/eks-cluster/) is publicly available and serves as a robust starting point for deploying an Amazon EKS cluster. Review the module before implementation to understand its structure and capabilities. The module is locally sourced in your clone. Any changes you make to the module in your repository take effect immediately in your setup. #### Set up the EKS cluster module 1. Go to the [reference architecture directory of the cloned repository](#obtain-a-copy-of-the-reference-architecture). Verify the layout, and switch into the cluster module: ```bash cd ./aws/kubernetes/eks-single-region(-irsa)/terraform/ ls # Example output: # cluster vpn cd cluster ``` 1. Review `cluster.tf`. It references the local Terraform module and contains a basic cluster setup that you can adjust to your needs. The file is available here: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/terraform/cluster/cluster.tf ``` ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-irsa/terraform/cluster/cluster.tf ``` The RDBMS variant uses the same cluster module as the standard variant, so this `cluster.tf` is identical to the standard one: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/terraform/cluster/cluster.tf ``` 1. Configure user access to the cluster. By default, the user who creates the Amazon EKS cluster has administrative access.
Grant cluster access to other users If you want to grant access to other users, you can configure this by using the `access_entries` input. Amazon EKS access management is divided into two distinct layers: - The **first layer** involves **AWS IAM permissions**, which allow basic Amazon EKS functionalities such as interacting with the Amazon EKS UI and generating EKS access through the AWS CLI. The module handles this part for you by creating the necessary IAM roles and policies. - The **second layer** controls **cluster access** within Kubernetes, defining the user's permissions inside the cluster (for example, policy association). This can be configured directly through the module's `access_entries` input. To manage user access, use the `access_entries` configuration: ```hcl access_entries = { example = { kubernetes_groups = [] principal_arn = "" policy_associations = { example = { policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy" access_scope = { namespaces = ["default"] type = "namespace" } } } } } ``` In this configuration: - Replace `principal_arn` with the ARN of the IAM user or role. - Use `policy_associations` to define policies for fine-grained access control. For a full list of available policies, refer to the [AWS EKS Access Policies documentation](https://docs.aws.amazon.com/eks/latest/userguide/access-policies.html).
1. Customize the cluster setup. The module offers various input options that allow you to further customize the cluster configuration. For a comprehensive list of available options and detailed usage instructions, refer to the [EKS module documentation](https://github.com/camunda/camunda-deployment-references/blob/main/aws/modules/eks-cluster/README.md). :::note Private cluster By default, this cluster is accessible from the internet. To restrict access, set `private_vpc = true`. This creates a private cluster that is only accessible through the [private subnets](https://docs.aws.amazon.com/vpc/latest/userguide/configure-subnets.html) of your VPC. Optionally, you can expose a public Ingress by setting the `expose_public_elb` variable in the EKS module. ⚠️ Because private subnets are not reachable from the internet, you must connect your network to the cluster using either a [bastion host](https://docs.aws.amazon.com/mwaa/latest/userguide/tutorials-private-network-bastion.html) or a Client VPN. See the [VPN module setup](#vpn-module-setup) to configure an [AWS Client VPN endpoint](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/cvpn-getting-started.html) for secure access to the private cluster. ::: ### PostgreSQL module setup :::info Optional module If you don't want to use this module, you can skip this section. However, you may need to adjust the remaining instructions to remove references to this module. If you choose not to use this module, you must either provide a managed PostgreSQL service or use the internal deployment by the Camunda Helm chart in Kubernetes. Additionally, you must delete the `db.tf` file in the `terraform/cluster` directory of your chosen reference. Otherwise, it will create the resources. ::: In the [reference architecture](/self-managed/reference-architecture/reference-architecture.md), PostgreSQL database is required for Web Modeler and Management Identity. These components require persistent storage for user data, configuration, and authentication information. If you deploy Keycloak inside the cluster (for example, via the Keycloak Operator), it also needs a PostgreSQL database. :::note Management Identity and multi-tenancy Management Identity also requires PostgreSQL, but only when multi-tenancy is enabled, which is not used in this reference architecture. For more information, see [Multi-tenancy](/components/concepts/multi-tenancy.md). ::: We separated the cluster and PostgreSQL modules to offer you more customization options. #### Set up the Aurora PostgreSQL module 1. Go to the [reference architecture directory of the cloned repository](#obtain-a-copy-of-the-reference-architecture). Verify the layout and switch into the cluster module. For simplicity, the PostgreSQL file is located in the cluster module: ```bash cd ./aws/kubernetes/eks-single-region(-irsa)/terraform/ ls # Example output: # cluster vpn cd cluster ``` 1. The `db.tf` file references the local Terraform module and contains a basic Aurora PostgreSQL setup that you can adjust to your needs. The file is available here: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/terraform/cluster/db.tf ``` In addition to using standard username and password authentication, you can opt to use [**IRSA (IAM Roles for Service Accounts)**](https://aws.amazon.com/blogs/opensource/introducing-fine-grained-iam-roles-service-accounts/) for secure, role-based access to your Aurora database. This method allows your EKS workloads to assume IAM roles without needing to manage AWS credentials directly. :::note Using IRSA is optional. If preferred, you can continue using traditional password-based authentication for database access. ::: If you choose to use IRSA, you'll need to take note of the **IAM role** created for Aurora and the **AWS Account ID**, as these will be used later to annotate the Kubernetes service account. ##### Aurora IRSA role and policy The Aurora module uses outputs from the EKS cluster module to configure the IRSA role and policy. Below are the required parameters: Here's how to define the IAM role trust policy and access policy for Aurora: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-irsa/terraform/cluster/db.tf ``` Once the IRSA configuration is complete, ensure you **record the IAM role name** (from the `iam_aurora_role_name` configuration), it is required to annotate the Kubernetes service account in the next step. The RDBMS variant extends the Aurora PostgreSQL setup with an additional `camunda_orchestration` database and a dedicated `orchestration_db` user, used as the secondary storage for the Orchestration Cluster: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-rdbms/terraform/cluster/db.tf ``` 1. Customize the Aurora cluster setup through various input options. Refer to the [Aurora module documentation](https://github.com/camunda/camunda-deployment-references/blob/main/aws/modules/aurora/README.md) for more details on other customization options. ### OpenSearch module setup :::info Optional module If you don't want to use this module, you can skip this section. However, you may need to adjust the remaining instructions to remove references to this module. If you choose not to use this module, you can either: - Provide a managed Elasticsearch or OpenSearch service, or deploy Elasticsearch in your cluster via ECK. - Use RDBMS as the secondary storage backend for the Orchestration Cluster. See [configure RDBMS in Helm](/self-managed/deployment/helm/configure/database/rdbms.md) for details and the [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) for supported engines. Additionally, you must delete the `opensearch.tf` file within the `terraform/cluster` directory of your chosen reference as it will otherwise create the resources. ::: The OpenSearch module creates an OpenSearch domain intended for Camunda. OpenSearch is a powerful alternative to Elasticsearch. For more information on using OpenSearch with Camunda, refer to the [Camunda documentation](/self-managed/deployment/helm/configure/database/using-external-opensearch.md). :::note Migration to OpenSearch is not supported Using Amazon OpenSearch Service requires [setting up a new Camunda installation](/self-managed/setup/overview.md). Migration from previous Camunda versions using Elasticsearch environments is currently not supported. Switching between Elasticsearch and OpenSearch, in either direction, is also not supported. ::: #### Set up the OpenSearch domain module 1. Go to the [reference architecture directory of the cloned repository](#obtain-a-copy-of-the-reference-architecture). Verify the layout and switch into the cluster module. For simplicity, the OpenSearch file is located in the cluster module: ```bash cd ./aws/kubernetes/eks-single-region(-irsa)/terraform/ ls # Example output: # cluster vpn cd cluster ``` 1. The `opensearch.tf` references the local Terraform module and contains a basic AWS OpenSearch setup that you can adjust to your needs. The file is available here: :::caution Network based security The standard deployment for OpenSearch relies on the first layer of security, which is the Network. While this setup allows easy access, it may expose sensitive data. To enhance security, consider implementing IAM Roles for Service Accounts (IRSA) to restrict access to the OpenSearch cluster, providing a more secure environment. For more information, see the [Amazon OpenSearch Service Fine-Grained Access Control documentation](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/fgac.html#fgac-access-policies). ::: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/terraform/cluster/opensearch.tf ``` In addition to standard authentication, which uses anonymous users and relies on the network for access control, you can also use [**IRSA (IAM Roles for Service Accounts)**](https://aws.amazon.com/blogs/opensource/introducing-fine-grained-iam-roles-service-accounts/) to securely connect to OpenSearch. IRSA enables your Kubernetes workloads to assume IAM roles without managing AWS credentials directly. :::note Using IRSA is optional. If you prefer, you can continue using password-based access to your OpenSearch domain. ::: If you choose to use IRSA, you'll need to take note of the **IAM role name** created for OpenSearch and the **AWS Account ID**, as these will be required later to annotate the Kubernetes service account. ##### OpenSearch IRSA role and policy To configure IRSA for OpenSearch, the OpenSearch module uses outputs from the EKS cluster module to define the necessary IAM role and policies. Here's an example of how to define the IAM role trust policy and access policy for OpenSearch, this configuration will deploy an OpenSearch domain with advanced security enabled: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-irsa/terraform/cluster/opensearch.tf ``` Once the IRSA configuration is complete, ensure you **record the IAM role name** (from the `iam_opensearch_role_name` configuration), it is required to annotate the Kubernetes service account in the next step. As the OpenSearch domain has advanced security enabled and [fine-grained access control](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/fgac.html), we will later use your provided master username (`advanced_security_master_user_name`) and password (`advanced_security_master_user_password`) to perform the initial setup of the security component, allowing the created IRSA role to access the domain. The RDBMS variant uses Amazon Aurora PostgreSQL as the secondary storage for the Orchestration Cluster, so it does not provision an OpenSearch domain. The `eks-single-region-rdbms` reference does not include an `opensearch.tf` file, so skip this OpenSearch module setup and do not create the OpenSearch module. For details, see [configure RDBMS in Helm](/self-managed/deployment/helm/configure/database/rdbms.md). 1. Customize the cluster setup using various input options. For a full list of available parameters, see the [OpenSearch module documentation](https://github.com/camunda/camunda-deployment-references/blob/main/aws/modules/opensearch/README.md). :::tip The instance type `m7i.large.search` in the above example is a suggestion, and can be changed depending on your needs. ::: ### Create the EKS cluster, PostgreSQL, and OpenSearch domain After configuring the `cluster` module (which includes EKS, PostgreSQL, and OpenSearch), use Terraform to plan and create the resources. :::note Secret management We strongly recommend managing sensitive information such as the OpenSearch, Aurora username and password using a secure secrets management solution like HashiCorp Vault. For details on how to inject secrets directly into Terraform via Vault, see the [Terraform Vault Secrets Injection Guide](https://developer.hashicorp.com/terraform/tutorials/secrets/secrets-vault). ::: 1. Open a terminal in the chosen reference folder where `config.tf` and other `.tf` files are. ```bash cd ./aws/kubernetes/eks-single-region(-irsa)/terraform/ ls # Example output: # cluster vpn cd cluster ``` 2. Perform a final initialization for anything changed throughout the guide: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/s3-bucket/s3-bucket-tf-init.sh#L7 ``` 3. Plan the configuration files: ```bash terraform plan -out cluster.plan # describe what will be created ``` 4. After reviewing the plan, you can confirm and apply the changes. ```bash terraform apply cluster.plan # apply the creation ``` Terraform will now create the Amazon EKS cluster with all the necessary configurations. The completion of this process may require approximately 20-30 minutes for each component. #### Define outputs Terraform lets you define outputs, which make it easier to retrieve important values generated during execution, such as database endpoints and other configuration details needed for the Helm setup. Each module definition set up in the reference contains an output definition at the end of the file. You can adjust them as needed. Outputs let you easily reference values such as the **cert-manager** ARN, **external-dns** ARN, and the endpoints for **PostgreSQL** and **OpenSearch** in subsequent steps or scripts, streamlining your deployment process. ### VPN module setup You can optionally set up an AWS VPN endpoint to access a private cluster. This step is only required if you configured a private cluster. Using a VPN offers a flexible and secure way to connect to the private subnets within your VPC. It can be used either by a user to access cluster resources or to enable cross-site communications via [PrivateLink](https://docs.aws.amazon.com/vpc/latest/privatelink/what-is-privatelink.html). This module focuses on user access. _Infrastructure diagram for a single region EKS setup with VPN (click on the image to open the PDF version)_ [![Infrastructure Diagram EKS Single-Region VPN](./assets/eks-single-region-vpn.jpg)](./assets/eks-single-region-vpn.pdf) AWS VPN technology is compatible with OpenVPN clients. It uses [x509 certificates](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/mutual.html) for mutual authentication and source verification. The encryption provided by these certificates ensures that traffic can securely transit over the internet to the AWS VPN endpoint, which performs NAT and routes the traffic directly into the private subnets. This VPN endpoint then becomes the sole access point to the private cluster. #### Retrieve the VPC cluster ID To create the VPN Endpoint in your cluster's VPC, you need to retrieve the VPC ID using [Terraform outputs](https://developer.hashicorp.com/terraform/language/values/outputs) from the [EKS cluster module](#eks-cluster-module-setup). Follow these steps: 1. Go to the [reference architecture directory of the cloned repository](#obtain-a-copy-of-the-reference-architecture) and switch into the `cluster` module directory: ```bash cd ./aws/kubernetes/eks-single-region/terraform/ ls # Example output: # cluster vpn cd cluster ``` 2. Export the [VPC ID](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc) to an environment variable: ```bash export CLUSTER_VPC_ID="$(terraform output -raw vpc_id)" echo "CLUSTER_VPC_ID=$CLUSTER_VPC_ID" ``` #### Set up the VPN module From the parent directory of your cluster module, go to the `vpn` directory which holds the VPN endpoint configuration. This setup creates a Certificate Authority (CA) for AWS VPN to perform encryption and mutual client authentication. For simplicity, the CA and generated certificates are stored in the project’s Terraform state (`tfstate`). You may customize this as needed. 1. Review `config.tf`, which configures the S3 backend for Terraform state management: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/terraform/vpn/config.tf ``` 2. Review `vpn.tf`, which defines the VPC Client endpoint configuration: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/terraform/vpn/vpn.tf ``` This VPN Client Endpoint follows [AWS best practices and constraints](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/what-is-best-practices.html): - Uses a client CIDR range that does not overlap with the VPC CIDR or any manually added VPN route table routes. - Implements [split-tunnel routing](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/split-tunnel-vpn.html), so only traffic for the VPC goes through the VPN, minimizing bandwidth use. - Supports IPv4 only and is bound to the VPC’s private subnets. **Initialize and configure Terraform**: 1. Set your Terraform state key, initialize Terraform with the S3 [backend](#create-an-s3-bucket-for-terraform-state-management), and download necessary provider plugins: ```bash export S3_TF_BUCKET_KEY_VPN="camunda-terraform/vpn.tfstate" echo "Storing cluster terraform state in s3://$S3_TF_BUCKET_NAME/$S3_TF_BUCKET_KEY_VPN" terraform init -backend-config="bucket=$S3_TF_BUCKET_NAME" -backend-config="key=$S3_TF_BUCKET_KEY_VPN" -backend-config="region=$S3_TF_BUCKET_REGION" ``` Terraform will connect to the S3 bucket to manage the state file, ensuring remote and persistent storage. 2. For each client connecting to the cluster, assign a unique name in `client_key_names` to simplify certificate revocation. 3. By default, VPN access is allowed from any IP address. Restrict access by adjusting the `vpn_allowed_cidr_blocks` variable. 4. Review and adjust the configuration for your network design. 5. For customization options, see the [VPN module documentation](https://github.com/camunda/camunda-deployment-references/blob/main/aws/modules/vpn/README.md). #### Outputs The module stores certificates and VPN client configurations in the Terraform state. The next section explains how to retrieve and use client configurations. #### Execution 1. Generate a Terraform plan for the VPN Client endpoint configuration. This will use the private subnets of the designated VPC: ```bash # describe what will be created terraform plan -out vpn.plan \ -var vpc_id="$CLUSTER_VPC_ID" ``` 2. Review and apply the plan to create the resources: ```bash terraform apply vpn.plan # creates the resources ``` Creating the VPN Client endpoint typically takes about 10 minutes. After completion, the Terraform output `vpn_client_configs` provides the client configuration files. ## 2. Preparation for Camunda 8 installation ### Access to the private network using the VPN This section applies if you have previously created a private cluster and want to access it using the [VPN module configured earlier](#vpn-module-setup). 1. Navigate to the VPN module directory (`vpn`): ```bash pwd # Example output: # ./camunda-deployment-references/aws/kubernetes/eks-single-region(-irsa)/terraform/vpn/ ``` 2. Generate your client’s VPN configuration file. This file is compatible with [OpenVPN (ovpn)](https://openvpn.net/) format: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/vpn/gather-vpn-config.sh ``` 3. Import the generated configuration file (`my-client.ovpn`) into an OpenVPN client: - _(preferred)_ [Official AWS VPN Client](https://docs.aws.amazon.com/vpn/latest/clientvpn-user/connect-aws-client-vpn-connect.html) - [Other OpenVPN Clients](https://docs.aws.amazon.com/vpn/latest/clientvpn-user/connect.html) 4. Once the VPN client is connected, you will have secure access to the VPC’s private network. ### Access the created EKS cluster You can gain access to the Amazon EKS cluster via the `AWS CLI` using the following command: ```bash export CLUSTER_NAME="$(terraform console << ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/procedure/export-helm-values.sh ``` ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-irsa/procedure/export-helm-values.sh ``` :::note IRSA users To authenticate and authorize access to PostgreSQL and OpenSearch, **you do not need to export the PostgreSQL or OpenSearch passwords**, IRSA will handle the authentication. **However**, you will still need to export the relevant usernames and other settings to Helm. ::: The RDBMS variant exports the orchestration database variables. ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-rdbms/procedure/export-helm-values.sh ``` Ensure that you use the actual values you passed to the Terraform module during the setup of PostgreSQL and OpenSearch. ### Configure the database and associated access As you now have a database, you need to create dedicated databases for each Camunda component and an associated user that have a configured access. Follow these steps to create the database users and configure access. You can access the created database in the following ways: 1. **Bastion host:** Set up a bastion host within the same network to securely access the database. 2. **Pod within the EKS cluster:** Deploy a pod in your EKS cluster equipped with the necessary tools to connect to the database. See [Access internal infrastructure](#access-internal-infrastructure). 3. **VPN:** Establish a VPN connection to the VPC where the Aurora Cluster resides, allowing secure access from your local machine or another network. The choice depends on your infrastructure setup and security preferences. In this guide, we'll use a pod within the EKS cluster to configure the database. 1. In your terminal, set the necessary environment variables that will be substituted in the setup manifest: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/procedure/vars-create-db.sh ``` A **Kubernetes job** will connect to the database and create the necessary users with the required privileges. The script installs the necessary dependencies and runs SQL commands to create the IRSA user and assign it the correct roles and privileges. 2. Create a secret that references the environment variables: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/procedure/create-setup-db-secret.sh ``` This command creates a secret named `setup-db-secret` and dynamically populates it with the values from your environment variables. After running the above command, you can verify that the secret was created successfully by using: ```bash kubectl get secret setup-db-secret -o yaml --namespace "$CAMUNDA_NAMESPACE" ``` This should display the secret with the base64 encoded values. ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-irsa/procedure/create-setup-db-secret.sh ``` This command creates a secret named `setup-db-secret` and dynamically populates it with the values from your environment variables. After running the above command, you can verify that the secret was created successfully by using: ```bash kubectl get secret setup-db-secret -o yaml --namespace "$CAMUNDA_NAMESPACE" ``` This should display the secret with the base64 encoded values. ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-rdbms/procedure/create-setup-db-secret.sh ``` 3. Save the following manifest to a file, for example, `setup-postgres-create-db.yml`. ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region/setup-postgres-create-db.yml ``` ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-irsa/setup-postgres-create-db.yml ``` The RDBMS variant also creates the `camunda_orchestration` database used as secondary storage for the Orchestration Cluster. ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-rdbms/setup-postgres-create-db.yml ``` 4. Apply the manifest: ```bash kubectl apply -f setup-postgres-create-db.yml --namespace "$CAMUNDA_NAMESPACE" ``` Once the secret is created, the **Job** manifest from the previous step can consume this secret to securely access the database credentials. 5. Once the job is created, monitor its progress using: ```bash kubectl get job/create-setup-user-db --namespace "$CAMUNDA_NAMESPACE" --watch ``` Once the job shows as `Completed`, the users and databases will have been successfully created. 6. View the logs of the job to confirm that the users were created and privileges were granted successfully: ```bash kubectl logs job/create-setup-user-db --namespace "$CAMUNDA_NAMESPACE" ``` 7. Clean up the resources: ```bash kubectl delete job create-setup-user-db --namespace "$CAMUNDA_NAMESPACE" kubectl delete secret setup-db-secret --namespace "$CAMUNDA_NAMESPACE" ``` Running these commands cleans up both the job and the secret, ensuring that no unnecessary resources remain in the cluster. ### Configure OpenSearch fine grained access control As you now have an OpenSearch domain, you need to configure the related access for each Camunda component. You can access the created OpenSearch domain in the following ways: 1. **Bastion host:** Set up a bastion host within the same network to securely access the OpenSearch domain. 2. **Pod within the EKS cluster:** Deploy a pod in your EKS cluster equipped with the necessary tools to connect to the OpenSearch domain. See [Access internal infrastructure](#access-internal-infrastructure). 3. **VPN:** Establish a VPN connection to the VPC where the OpenSearch domain resides, allowing secure access from your local machine or another network. The choice depends on your infrastructure setup and security preferences. In this tutorial, we'll use a pod within the EKS cluster to configure the domain. The standard installation comes already pre-configured, and no additional steps are required. 1. In your terminal, set the necessary environment variables that will be substituted in the setup manifest: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-irsa/procedure/vars-create-os.sh ``` A **Kubernetes job** will connect to the OpenSearch dommain and configure it. 1. Create a secret that references the environment variables: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-irsa/procedure/create-setup-os-secret.sh ``` This command creates a secret named `setup-os-secret` and dynamically populates it with the values from your environment variables. After running the above command, you can verify that the secret was created successfully by using: ```bash kubectl get secret setup-os-secret -o yaml --namespace "$CAMUNDA_NAMESPACE" ``` This should display the secret with the base64 encoded values. 1. Save the following manifest to a file, for example, `setup-opensearch-fgac.yml`. ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/kubernetes/eks-single-region-irsa/setup-opensearch-fgac.yml ``` 1. Apply the manifest: ```bash kubectl apply -f setup-opensearch-fgac.yml --namespace "$CAMUNDA_NAMESPACE" ``` Once the secret is created, the **Job** manifest from the previous step can consume this secret to securely access the OpenSearch domain credentials. 1. Once the job is created, monitor its progress using: ```bash kubectl get job/setup-opensearch-fgac --namespace "$CAMUNDA_NAMESPACE" --watch ``` Once the job shows as `Completed`, the OpenSearch domain is configured correctly for fine grained access control. 1. View the logs of the job to confirm that the privileges were granted successfully: ```bash kubectl logs job/setup-opensearch-fgac --namespace "$CAMUNDA_NAMESPACE" ``` 1. Clean up the resources: ```bash kubectl delete job setup-opensearch-fgac --namespace "$CAMUNDA_NAMESPACE" kubectl delete secret setup-os-secret --namespace "$CAMUNDA_NAMESPACE" ``` Running these commands will clean up both the job and the secret, ensuring that no unnecessary resources remain in the cluster. The RDBMS variant uses Amazon Aurora PostgreSQL as secondary storage and does not provision OpenSearch, so this OpenSearch access configuration does not apply. Skip it. ### Access internal infrastructure :::warning Not recommended in production These approaches are intended for **development and troubleshooting purposes only**. For a production cluster, use a proper VPN or other secure access methods. ::: Some infrastructure components, such as OpenSearch dashboards or Aurora PostgreSQL databases, are accessible only from inside the Virtual Private Cloud (VPC). You can use a temporary pod as a _jump host_ to tunnel traffic to these components. ---
Generic approach using a jump host **Component connection details** Export `REMOTE_HOST`, `REMOTE_PORT`, and `LOCAL_PORT` with the component-specific values: | Component | `REMOTE_HOST` | `REMOTE_PORT` | `LOCAL_PORT` | | -------------------- | ------------------ | -------------- | ------------ | | OpenSearch dashboard | `$OPENSEARCH_HOST` | `443` | `9200` | | Aurora PostgreSQL | `$AURORA_ENDPOINT` | `$AURORA_PORT` | `5432` | 1. Run a socat pod to create a TCP tunnel: ```bash kubectl --namespace $CAMUNDA_NAMESPACE run my-jump-pod -it \ --image=alpine/socat \ --tty --rm \ --restart=Never \ --expose=true --port=$REMOTE_PORT -- \ tcp-listen:$REMOTE_PORT,fork,reuseaddr \ tcp-connect:$REMOTE_HOST:$REMOTE_PORT ``` :::tip How it works [socat](http://www.dest-unreach.org/socat/) (_SOcket CAT_) is a command-line tool that relays data between two network endpoints. In this command: - `tcp-listen:$REMOTE_PORT,fork,reuseaddr` listens on the specified port in the pod and can handle multiple connections. - `tcp-connect:$REMOTE_HOST:$REMOTE_PORT` forwards all incoming traffic to the internal component endpoint. Combined with `kubectl port-forward` (step 2), the flow is: ``` Local Client → localhost:$LOCAL_PORT → port-forward → my-jump-pod:$REMOTE_PORT → socat → Remote Component ``` This setup lets you securely reach internal components without exposing them publicly. ::: 2. Port-forward the pod to your local machine: ```bash kubectl port-forward --namespace $CAMUNDA_NAMESPACE pod/my-jump-pod $LOCAL_PORT:$REMOTE_PORT ``` 3. Connect to the component: _OpenSearch example:_ ```bash https://localhost:$LOCAL_PORT/_dashboards ``` Accept the insecure connection if prompted. _Aurora PostgreSQL example:_ ```bash PGPASSWORD=$AURORA_PASSWORD psql -h localhost -p $LOCAL_PORT -U $AURORA_USERNAME -d ```
## 3. Install Camunda 8 using the Helm chart Now that you've exported the necessary values, you can proceed with installing Camunda 8 using Helm charts. Follow the guide [Camunda 8 on Kubernetes](./eks-helm.md) for detailed instructions on deploying the platform to your Kubernetes cluster. --- ## Dual-region ROSA HCP Cluster with Terraform This guide provides a detailed tutorial for deploying two [Red Hat OpenShift on AWS (ROSA) cluster with Hosted Control Plane (HCP)](https://docs.redhat.com/en/documentation/red_hat_openshift_service_on_aws_classic_architecture/4/html/architecture/index.html) in two different [regions](https://aws.amazon.com/about-aws/global-infrastructure/regions_az/). It is specifically tailored for deploying Camunda 8 using Terraform, a widely-used Infrastructure as Code (IaC) tool, details of the High Level design are available in the generic [Red Hat OpenShift dual-region for Camunda 8 guide](/self-managed/deployment/helm/cloud-providers/openshift/dual-region.md). We recommend this guide for building a robust and sustainable infrastructure that needs to survive a region lost. This guide aims to help you leverage IaC to streamline and reproduce your cloud infrastructure setup. While it covers the essentials for deploying an ROSA HCP cluster, for more advanced use cases, please refer to the official [Red Hat OpenShift on AWS Documentation](https://docs.redhat.com/en/documentation/red_hat_openshift_service_on_aws/4). :::tip If you are completely new to Terraform and the idea of IaC, read through the [Terraform IaC documentation](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code) and give their [interactive quick start](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code#quick-start) a try for a basic understanding. ::: ## Requirements - A [Red Hat Account](https://www.redhat.com/) to create the Red Hat OpenShift cluster. - An [AWS account](https://docs.aws.amazon.com/accounts/latest/reference/accounts-welcome.html) to create any resources within AWS. - [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html), a CLI tool for creating AWS resources. - [Terraform](https://developer.hashicorp.com/terraform/downloads) - [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) to interact with the cluster. - [ROSA CLI](https://docs.redhat.com/en/documentation/red_hat_openshift_service_on_aws_classic_architecture/4/html/getting_started/rosa-quickstart-guide-ui.html) to interact with the cluster. - [jq](https://jqlang.github.io/jq/download/) to interact with some Terraform variables. - This guide uses GNU/Bash for all the shell commands listed. For the tool versions used, check the [.tool-versions](https://github.com/camunda/camunda-deployment-references/blob/main/.tool-versions) file in the repository. It contains an up-to-date list of versions that we also use for testing. ### Considerations This setup provides a foundational starting point for working with Camunda 8, though it is not optimized for peak performance. It serves as a solid initial step in preparing a production environment by leveraging [Infrastructure as Code (IaC) tools](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code). Terraform can initially appear complex. If you're new to it, you might want to start by considering [deployment in a single region](terraform-setup.md) first. :::note Cost management This guide provisions resources that can incur costs in your cloud provider account. Review your provider's pricing before you begin. ::: ### Outcome _Infrastructure diagram for a dual region ROSA setup (click on the image to open the PDF version)_ [![Infrastructure Diagram ROSA Dual-Region](./assets/rosa-dual-region.jpg)](./assets/rosa-dual-region.pdf) Following this tutorial and steps will result in: - Two [Red Hat OpenShift with Hosted Control Plane](https://www.redhat.com/en/topics/containers/what-are-hosted-control-planes#rosa-with-hcp) clusters running the latest ROSA version, each with six nodes ready for Camunda 8 installation in separate regions. - The [EBS CSI driver](https://docs.aws.amazon.com/eks/latest/userguide/ebs-csi.html) installed and configured, enabling the Camunda 8 Helm chart to create [persistent volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/). - [VPC Peering](https://docs.aws.amazon.com/vpc/latest/peering/what-is-vpc-peering.html) configured to enable cross-region cluster communication. - An [Amazon Simple Storage Service](https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html) (S3) bucket for [Elasticsearch backups](https://www.elastic.co/guide/en/elasticsearch/reference/current/repository-s3.html). - [Red Hat OpenShift Advanced Cluster Management](https://www.redhat.com/en/technologies/management/advanced-cluster-management) used to manage the two clusters and configure Submariner. - [Submariner](https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/2.2/html/manage_cluster/submariner) configured on the two clusters to enable cross-namespace and cross-cluster network communication. ## 1. Configure AWS and initialize Terraform ### Obtain a copy of the reference architecture The first step is to download a copy of the reference architecture from the [GitHub repository](https://github.com/camunda/camunda-deployment-references/blob/main/aws/openshift/rosa-hcp-dual-region/). This material will be used throughout the rest of this documentation, the reference architecture is versioned using the same Camunda versions (`stable/8.x`). ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/openshift/rosa-hcp-dual-region/procedure/get-your-copy.sh ``` With the reference architecture copied, you can proceed with the remaining steps outlined in this documentation. Ensure that you are in the correct directory before continuing with further instructions. ### OpenShift clusters module setup This module sets up the foundational configuration for ROSA HCP and Terraform usage. We will leverage [Terraform modules](https://developer.hashicorp.com/terraform/language/modules), which allow us to abstract resources into reusable components, simplifying infrastructure management. The [Camunda-provided module](https://github.com/camunda/camunda-deployment-references/tree/main/aws/openshift/rosa-hcp-dual-region/) is publicly available and serves as a starting point for deploying Red Hat OpenShift clusters on AWS using a Hosted Control Plane. It is highly recommended to review this module before implementation to understand its structure and capabilities. Please note that this module is based on the official [ROSA HCP Terraform module documentation](https://docs.openshift.com/rosa/rosa_hcp/terraform/rosa-hcp-creating-a-cluster-quickly-terraform.html). It is presented as an example for running Camunda 8 in ROSA. **For production or advanced use cases or custom setups, we encourage you to use the [official module](https://docs.openshift.com/rosa/rosa_hcp/terraform/rosa-hcp-creating-a-cluster-quickly-terraform.html)**, which includes vendor-supported features. Configure `CLUSTER_0_REGION` and `CLUSTER_1_REGION` with the target regions respectively. ```bash # Set the region, adjust as needed export CLUSTER_0_REGION="us-east-1" export CLUSTER_1_REGION="us-east-2" ``` Verify your AWS quotas for each region: ```bash rosa verify quota --region="$CLUSTER_0_REGION" rosa verify quota --region="$CLUSTER_1_REGION" ``` :::note This may fail due to organizational policies. ::: #### Set up the ROSA clusters module The dual-cluster setup requires managing two distinct clusters in different regions. For the simplicity of usage, we will manage the two clusters using a single module, therefore this guide uses a dedicated [aws terraform provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) for each region. 1. Ensure you are in the [reference architecture directory of the cloned repository](#obtain-a-copy-of-the-reference-architecture): `./aws/openshift/rosa-hcp-dual-region/terraform/`. Then, navigate into the `clusters` module: ```bash ls # Example output: # clusters peering backup_bucket cd clusters ``` 2. Configure your topology deployment, as you will use multiple regions, specify `CLUSTER_0_REGION` and `CLUSTER_1_REGION` with the target regions respectively. ```bash # set the region, adjust to your needs export CLUSTER_0_REGION="us-east-1" export CLUSTER_1_REGION="us-east-2" # ensure bucket variables are set export S3_TF_BUCKET_REGION="" export S3_TF_BUCKET_NAME="my-rosa-dual-tf-state" ``` 3. Ensure that your `RHCS_TOKEN` is defined and valid (otherwise, renew it on [OpenShift Cluster Management API Token](https://console.redhat.com/openshift/token/rosa)): ```bash rosa login --token="$RHCS_TOKEN" ``` 4. Review the module configuration file `config.tf`. This configuration will use the previously created S3 bucket for storing the Terraform state file: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/openshift/rosa-hcp-dual-region/terraform/clusters/config.tf ``` 5. Review the file named `cluster_region_0.tf` in the same directory. This file describes the cluster of the region 0, you may want to customize the `locals` variables with parameters of your choice, those are described in the next steps. ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/openshift/rosa-hcp-dual-region/terraform/clusters/cluster_region_0.tf ``` 6. Do the same review with `cluster_region_1.tf` and adjust it to your needs. This file describes the cluster of the region 1: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/openshift/rosa-hcp-dual-region/terraform/clusters/cluster_region_1.tf ``` 7. After setting up the terraform files and ensuring your AWS authentication is configured, initialize Terraform to configure the backend and download necessary provider plugins: ```bash export S3_TF_BUCKET_KEY_CLUSTERS="camunda-terraform/clusters.tfstate" echo "Storing clusters terraform state in s3://$S3_TF_BUCKET_NAME/$S3_TF_BUCKET_KEY_CLUSTERS" terraform init -backend-config="bucket=$S3_TF_BUCKET_NAME" -backend-config="key=$S3_TF_BUCKET_KEY_CLUSTERS" -backend-config="region=$S3_TF_BUCKET_REGION" ``` **For each cluster's file:** :::note Configure each cluster - Customize the cluster name, availability zones, with the values of your choice. - Additionally, provide a secure username and password for the cluster administrator. We strongly recommend managing sensitive information using a secure secrets management solution like HashiCorp Vault. For details on how to inject secrets directly into Terraform via Vault, see the [Terraform Vault Secrets Injection Guide](https://developer.hashicorp.com/terraform/tutorials/secrets/secrets-vault). - By default, a cluster is accessible from the internet. If you prefer to restrict access, please refer to the [official documentation of the module](https://registry.terraform.io/modules/terraform-redhat/rosa-hcp/rhcs/latest#input_private). ::: 1. Configure each cluster by editing the beginning of their respective files in the `locals` section: - Each cluster should have a unique, non-overlapping CIDR block to ensure proper functioning of the Submariner overlay network (as referenced in the [Submariner documentation](https://submariner.io/0.8/getting-started/architecture/globalnet)). This is essential for successful inter-cluster communication using the Submariner underlay network. If you can't fullfill this requirement, you may need to implement a [Submariner Global Private Network](https://submariner.io/0.8/getting-started/architecture/globalnet/). 1. Configure user access to the clusters. By default, the user who creates an OpenShift cluster has administrative access. If you want to grant access to other users, follow the [Red Hat documentation for granting admin rights to users](https://docs.openshift.com/rosa/cloud_experts_tutorials/cloud-experts-getting-started/cloud-experts-getting-started-admin-rights.html) when the cluster will be created. 1. Customize the clusters setup. The module offers various input options that allow you to further customize the cluster configuration. For a comprehensive list of available options and detailed usage instructions, refer to the [ROSA module documentation](https://github.com/camunda/camunda-deployment-references/blob/main/aws/modules/rosa-hcp/README.md). :::caution Camunda Terraform module This ROSA module is based on the [official Red Hat Terraform module for ROSA HCP](https://registry.terraform.io/modules/terraform-redhat/rosa-hcp/rhcs/latest). Please be aware of potential differences and choices in implementation between this module and the official one. Consult the [Camunda ROSA module documentation](https://github.com/camunda/camunda-deployment-references/blob/main/aws/modules/rosa-hcp/README.md) for more information. ::: #### Define outputs **Terraform** allows you to define outputs, which make it easier to retrieve important values generated during execution, such as cluster endpoints and other necessary configurations for Helm setup. Each module that you have previously set up contains an output definition at the end of the file. You can adjust them to your needs. #### Execution 1. Plan the configuration files: ```bash # describes what will be created terraform plan -out clusters.plan \ -var cluster_0_region="$CLUSTER_0_REGION" \ -var cluster_1_region="$CLUSTER_1_REGION" ``` 1. After reviewing the plan, you can confirm and apply the changes. ```bash # creates the resources terraform apply clusters.plan ``` Terraform will now create the OpenShift clusters with all the necessary configurations. The completion of this process may require approximately 20-30 minutes. ### Region peering module setup This section outlines the process of setting up communication between two different AWS regions hosting each cluster. To achieve this, we will make use of the Amazon [Virtual Private Cloud Peering connection](https://docs.aws.amazon.com/vpc/latest/peering/what-is-vpc-peering.html). The VPC peering connection enables two VPCs in different regions to connect and exchange traffic as if they were part of the same network, while maintaining security through the application of appropriate security groups. Please note that, once the VPC peering is created, it becomes a dependency of the cluster, therefore it must be destroyed before removing the cluster's VPCs. #### Retrieve the peering cluster variables To create the peering between each cluster’s VPC, you need to gather some information using the [terraform outputs](https://developer.hashicorp.com/terraform/language/values/outputs) of the [OpenShift clusters module setup](#openshift-clusters-module-setup). Follow these steps: 1. First, go in the clusters module directory ```bash ls # Example output: # clusters peering backup_bucket cd clusters ``` 1. Then for each cluster, save the associated [VPC ID](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc): ```bash export CLUSTER_0_VPC_ID="$(terraform output -raw cluster_0_vpc_id)" echo "CLUSTER_0_VPC_ID=$CLUSTER_0_VPC_ID" export CLUSTER_1_VPC_ID="$(terraform output -raw cluster_1_vpc_id)" echo "CLUSTER_1_VPC_ID=$CLUSTER_1_VPC_ID" ``` #### Set up the peering module From the `clusters` directory, navigate to the sibling directory called `peering`, which contains the VPC peering configuration: ```bash cd ../peering ``` We'll re-use the previously configured S3 bucket to store the state of the peering configuration. Begin by reviewing up the `config.tf` that configures S3 backend for managing the Terraform state: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/openshift/rosa-hcp-dual-region/terraform/peering/config.tf ``` Alongside the `config.tf` file, review the file called `peering.tf` used to reference the peering configuration:
Show peering.tf reference ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/openshift/rosa-hcp-dual-region/terraform/peering/peering.tf ```
One cluster will be referenced as the **owner**, and the other as the **accepter**. This designation is used solely for networking purposes and does not imply any dependency between the two clusters. #### Initialize Terraform Once the `.tf` files are set up, configure the backend for Terraform and set the S3 bucket key for the peering state and initialize Terraform to configure the backend and download the necessary provider plugins: ```bash # ensure bucket variables are set export S3_TF_BUCKET_REGION="" export S3_TF_BUCKET_NAME="my-rosa-dual-tf-state" export S3_TF_BUCKET_KEY_PEERING="camunda-terraform/peering.tfstate" echo "Storing terraform state in s3://$S3_TF_BUCKET_NAME/$S3_TF_BUCKET_KEY_PEERING" terraform init -backend-config="bucket=$S3_TF_BUCKET_NAME" -backend-config="key=$S3_TF_BUCKET_KEY_PEERING" -backend-config="region=$S3_TF_BUCKET_REGION" ``` This command connects Terraform to the S3 bucket for managing the state file, ensuring remote and persistent storage. #### Execution 1. Navigate to the `peering` directory where the `config.tf` file and other `.tf` files are located. Ensure that you have performed [the previously retrieval of the VPC values](#retrieve-the-peering-cluster-variables). 1. Run the following command to generate a plan for the VPC peering configuration. It will connect with peering the previously retrieved VPCs of each cluster: ```bash terraform plan -out peering.plan \ -var cluster_0_region="$CLUSTER_0_REGION" \ -var cluster_0_vpc_id="$CLUSTER_0_VPC_ID" \ -var cluster_1_region="$CLUSTER_1_REGION" \ -var cluster_1_vpc_id="$CLUSTER_1_VPC_ID" ``` 1. After reviewing the execution plan, apply the configuration to create the VPC peering connection: ```bash terraform apply peering.plan ``` This command will initiate the creation of the peering connection, enabling communication between the two clusters. For more details, consult the official [AWS VPC Peering documentation](https://docs.aws.amazon.com/vpc/latest/peering/what-is-vpc-peering.html). ### S3 backup bucket module setup This section outlines the process of creating a [S3 bucket](https://aws.amazon.com/en/s3/) that will be used to [perform backups of the elasticsearch cluster](https://www.elastic.co/guide/en/elasticsearch/reference/current/snapshot-restore.html) used by Camunda 8. Read more about the [failover procecure](/self-managed/deployment/helm/operational-tasks/dual-region-ops.md#procedure). The S3 bucket is set up following best practices, including encryption, logging, and versioning. These configurations can be customized to suit your specific requirements. #### Set up the bucket module From the `peering` directory, navigate to the sibling directory called `backup_bucket` for the S3 configuration: ```bash ls .. # Example output: # clusters peering backup_bucket cd ../backup_bucket ``` We'll re-use the previously configured S3 bucket to store the state of the backup bucket configuration. Begin by reviewing the `config.tf` file to use the S3 backend for managing the Terraform state: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/openshift/rosa-hcp-dual-region/terraform/backup_bucket/config.tf ``` Finally, review the file called `backup_bucket.tf`, that describes the elastic backup bucket configuration: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/openshift/rosa-hcp-dual-region/terraform/backup_bucket/backup_bucket.tf ``` This bucket configuration follows [multiple best practices](https://docs.aws.amazon.com/AmazonS3/latest/userguide/security-best-practices.html). We encourage you to review the implementation and adjust it according to your specific requirements. #### Initialize Terraform Once the `.tf` files are set up, configure the backend for Terraform and set the S3 bucket key for the peering state and initialize Terraform to configure the backend and download the necessary provider plugins: ```bash # ensure bucket variables are set export S3_TF_BUCKET_REGION="" export S3_TF_BUCKET_NAME="my-rosa-dual-tf-state" # set the region of the bucket export BACKUP_BUCKET_REGION="us-east-1" export S3_TF_BUCKET_KEY_BUCKET="camunda-terraform/backup-bucket.tfstate" echo "Storing terraform state in s3://$S3_TF_BUCKET_NAME/$S3_TF_BUCKET_KEY_BUCKET" terraform init -backend-config="bucket=$S3_TF_BUCKET_NAME" -backend-config="key=$S3_TF_BUCKET_KEY_BUCKET" -backend-config="region=$S3_TF_BUCKET_REGION" ``` This command connects Terraform to the S3 bucket for managing the state file, ensuring remote and persistent storage. The `BACKUP_BUCKET_REGION` will define the region of the bucket, you can pick one of your cluster region. #### Execution 1. Navigate to the `backup_bucket` directory where the `config.tf` file and other `.tf` files are located. 1. Run the following command to generate a plan for the S3 bucket configuration. You can edit the default bucket name using `-var=bucket_name=nameOfBucket` ```bash terraform plan -out backup-bucket.plan \ -var backup_bucket_region="$BACKUP_BUCKET_REGION" ``` 1. After reviewing the execution plan, apply the configuration to create the S3 backup bucket: ```bash terraform apply backup-bucket.plan # apply the creation ``` This command will initiate the creation of the backup bucket. 1. You will need to store the following secret variables to set up the dual-region installation of Camunda: ```bash export AWS_ACCESS_KEY_ES=$(terraform output -raw s3_aws_access_key) export AWS_SECRET_ACCESS_KEY_ES=$(terraform output -raw s3_aws_secret_access_key) export AWS_ES_BUCKET_NAME=$(terraform output -raw s3_bucket_name) export AWS_ES_BUCKET_REGION="$BACKUP_BUCKET_REGION" echo "AWS_ACCESS_KEY_ES=$AWS_ACCESS_KEY_ES" echo "AWS_SECRET_ACCESS_KEY_ES=$AWS_SECRET_ACCESS_KEY_ES" echo "AWS_ES_BUCKET_NAME=$AWS_ES_BUCKET_NAME" echo "AWS_ES_BUCKET_REGION=$AWS_ES_BUCKET_REGION" ``` Ensure these variables are securely stored, as they will be needed later in the process. ### Reference files You can find the reference files used on [this page](https://github.com/camunda/camunda-deployment-references/tree/main/aws/openshift/rosa-hcp-dual-region/) ## 2. Preparation for Camunda 8 installation ### Access the created OpenShift clusters You can now access the created OpenShift clusters. 1. Verify that you are in the [OpenShift clusters module](#openshift-clusters-module-setup) directory `clusters`: ```bash pwd # Example output: # ./camunda-deployment-references/aws/openshift/rosa-hcp-dual-region/terraform/clusters/ ``` 1. Set up the required environment variables from the OpenShift terraform module: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/openshift/rosa-hcp-dual-region/procedure/gather-cluster-login-id.sh ``` 1. Give cluster administrator role to the created user for each cluster: ```bash # Cluster 1 rosa grant user cluster-admin --cluster="$CLUSTER_0_NAME" --user="$CLUSTER_0_ADMIN_USERNAME" # Cluster 2 rosa grant user cluster-admin --cluster="$CLUSTER_1_NAME" --user="$CLUSTER_1_ADMIN_USERNAME" ``` 1. Log in to the OpenShift clusters and configure the kubeconfig contexts: ```bash # Cluster 1 oc config delete-context "$CLUSTER_0_NAME" || true oc login -u "$CLUSTER_0_ADMIN_USERNAME" "$CLUSTER_0_API_URL" -p "$CLUSTER_0_ADMIN_PASSWORD" oc config rename-context $(oc config current-context) "$CLUSTER_0_NAME" # Cluster 2 oc config delete-context "$CLUSTER_1_NAME" || true oc login -u "$CLUSTER_1_ADMIN_USERNAME" "$CLUSTER_1_API_URL" -p "$CLUSTER_1_ADMIN_PASSWORD" oc config rename-context $(oc config current-context) "$CLUSTER_1_NAME" ``` 1. Verify your connection to the clusters with `oc`: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/openshift/rosa-hcp-dual-region/procedure/verify-cluster-nodes.sh ``` In the remainder of the guide, different namespaces will be created following the needs of the dual-region architecture. ## 3. Next installation steps The next steps are generic and referenced in the **[Generic OpenShift Dual-Region for Camunda 8 guide](/self-managed/deployment/helm/cloud-providers/openshift/dual-region.md#setup-advanced-cluster-management-and-submariner)**. It includes: - Installation of Advanced Cluster Management - Installation of Submariner - Preparation of the configuration for Camunda 8 on dual-region - Installation of Camunda 8 in OpenShift dual-region. ## 4. Deletion of Terraform Resources ### Deletion of the VPC peering The VPC peering module can be deleted once it is no longer in use and has no dependencies. Once the VPC peering is deleted, the clusters will not be able to communicate with each other anymore. To delete the module, follow these steps: 1. Before proceeding with the deletion of the VPC peering module, ensure that the necessary variables, which were provided during the module creation, are still available. To verify that the required variables are correctly defined, repeat the steps from the [retrieve the VPC peering cluster variables](#retrieve-the-peering-cluster-variables) section. 1. Ensure that the `CLUSTER_0_REGION` and `CLUSTER_1_REGION` variables are defined correctly: ```bash # set the region, adjust to your needs export CLUSTER_0_REGION="us-east-1" export CLUSTER_1_REGION="us-east-2" ``` 1. Navigate to the VPC peering module directory `peering` where the VPC peering module configuration is located. 1. Execute the following command to plan the destruction of the VPC peering module. Ensure the correct variables are passed in, as shown below: ```bash terraform plan -destroy \ -var cluster_0_region="$CLUSTER_0_REGION" \ -var cluster_0_vpc_id="$CLUSTER_0_VPC_ID" \ -var cluster_1_region="$CLUSTER_1_REGION" \ -var cluster_1_vpc_id="$CLUSTER_1_VPC_ID" \ -out destroy-peering.plan ``` This command will generate a plan to destroy the resources and save it in a file called `destroy-peering.plan`. 1. After reviewing the destruction plan, apply the changes to delete the VPC peering module resources by running: ```bash terraform apply destroy-peering.plan ``` Once the `apply` command completes successfully, the VPC peering module and associated resources will be deleted. ### Deletion of the clusters The clusters can be deleted once they are no longer in use and have no dependencies. Since clusters rely on the VPC Peering connection, the Peering module must be deleted first. 1. Before proceeding with cluster deletion, ensure that the VPC Peering has been successfully removed by following the steps in the [deletion of the VPC Peering](#deletion-of-the-vpc-peering) section. 1. Go to the directory `clusters` where the clusters configurations are managed. 1. Execute the following command to generate a plan for deleting the clusters, ensuring the correct variables are passed: ```bash terraform plan -destroy \ -var cluster_0_region="$CLUSTER_0_REGION" \ -var cluster_1_region="$CLUSTER_1_REGION" \ -out destroy-clusters.plan ``` This command will generate a plan to destroy the clusters and save it in a file called `destroy-clusters.plan`. 1. After reviewing the destruction plan, apply the changes to delete the cluster resources by running: ```bash terraform apply destroy-clusters.plan ``` Once the `apply` command completes successfully, the clusters and associated resources will be deleted. ### Deletion of the S3 backup bucket The S3 backup bucket can be deleted once it is no longer in use and has no dependencies. To delete the bucket, follow these steps: 1. Navigate to the `backup_bucket` directory created during the [S3 backup bucket module setup](#s3-backup-bucket-module-setup). This directory contains the configuration for managing the S3 bucket. 1. Run the Terraform destroy plan: Execute the following Terraform command to plan the destruction of the S3 bucket and other resources: ```bash terraform plan -destroy -out destroy-bucket.plan ``` This command will generate a plan to destroy the resources and output it into a file called `destroy-bucket.plan`. 1. After reviewing the plan, apply the changes to delete the resources with the following command: ```bash terraform apply destroy-bucket.plan ``` Once the `apply` command is successfully completed, the S3 bucket and associated resources will be deleted. --- ## Deploy a ROSA HCP Cluster with Terraform This guide provides a detailed tutorial for deploying a [Red Hat OpenShift on AWS (ROSA) cluster with Hosted Control Plane (HCP)](https://docs.redhat.com/en/documentation/red_hat_openshift_service_on_aws_classic_architecture/4/html/architecture/index.html) capabilities. It is specifically tailored for deploying Camunda 8 using Terraform, a widely-used Infrastructure as Code (IaC) tool. We recommend this guide for building a robust and sustainable infrastructure. However, if you are looking for a quicker trial or proof of concept, or if your needs aren't fully met by our module, consider following the official [ROSA Quickstart Guide](https://docs.redhat.com/en/documentation/red_hat_openshift_service_on_aws_classic_architecture/4/html/getting_started/rosa-quickstart-guide-ui.html). This guide aims to help you leverage IaC to streamline and reproduce your cloud infrastructure setup. While it covers the essentials for deploying an ROSA HCP cluster, for more advanced use cases, please refer to the official [Red Hat OpenShift on AWS Documentation](https://docs.redhat.com/en/documentation/red_hat_openshift_service_on_aws/4). :::tip If you are completely new to Terraform and the idea of IaC, read through the [Terraform IaC documentation](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code) and give their [interactive quick start](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code#quick-start) a try for a basic understanding. ::: ## Requirements - A [Red Hat Account](https://www.redhat.com/) to create the Red Hat OpenShift cluster. - An [AWS account](https://docs.aws.amazon.com/accounts/latest/reference/accounts-welcome.html) to create any resources within AWS. - [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html), a CLI tool for creating AWS resources. - [AWS Quotas](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html) _(only applies to [ROSA](https://www.redhat.com/en/technologies/cloud-computing/openshift/aws) or OpenShift deployed on AWS)_ - Ensure at least **3 Elastic IPs** (one per availability zone). - Verify quotas for **VPCs, EC2 instances, and storage**. - Request increases if needed via the AWS console ([guide](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-resource-limits.html)); costs apply only to resources used. - [Terraform](https://developer.hashicorp.com/terraform/downloads) - [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) to interact with the cluster. - [ROSA CLI](https://docs.redhat.com/en/documentation/red_hat_openshift_service_on_aws_classic_architecture/4/html/getting_started/rosa-quickstart-guide-ui.html) to interact with the cluster. - [jq](https://jqlang.github.io/jq/download/) to interact with some Terraform variables. - This guide uses GNU/Bash for all the shell commands listed. For the tool versions used, check the [.tool-versions](https://github.com/camunda/camunda-deployment-references/blob/main/.tool-versions) file in the repository. It contains an up-to-date list of versions that we also use for testing. ### Considerations This setup provides a foundational starting point for working with Camunda 8, though it is not optimized for peak performance. It serves as a solid initial step in preparing a production environment by leveraging [Infrastructure as Code (IaC) tools](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code). Terraform can initially appear complex. If you're new to it, you might want to start by considering trying out the [Red Hat OpenShift on AWS UI-based tutorial](https://docs.redhat.com/en/documentation/red_hat_openshift_service_on_aws_classic_architecture/4/html/getting_started/rosa-getting-started.html). This guide will show you what resources are created and how they interact with each other. If you require managed services for PostgreSQL Aurora or OpenSearch, you can refer to the definitions provided in the [EKS setup with Terraform](../amazon-eks/terraform-setup.md) guide. However, please note that these configurations may need adjustments to fit your specific requirements and have not been tested. This guide uses integrated Helm chart database services in its example path (PostgreSQL and Elasticsearch), but you can choose another supported secondary storage backend for the Orchestration Cluster. To run the Orchestration Cluster on a relational database instead of Elasticsearch, follow the RDBMS steps in the [Red Hat OpenShift Helm guide](/self-managed/deployment/helm/cloud-providers/openshift/redhat-openshift.md#deploy-postgresql), which deploy an in-cluster `pg-camunda` database with CloudNativePG. See also [configure RDBMS in Helm](/self-managed/deployment/helm/configure/database/rdbms.md) and the [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) for the supported engines. For testing Camunda 8 or developing against it, you might consider signing up for our [SaaS offering](https://camunda.com/platform/). If you already have a Red Hat OpenShift cluster on AWS, you can skip ahead to the [Helm setup guide](/self-managed/deployment/helm/cloud-providers/openshift/redhat-openshift.md). To keep this guide concise, we provide links to additional documentation covering best practices, allowing you to explore each topic in greater depth. :::note Cost management This guide provisions resources that can incur costs in your cloud provider account. Review your provider's pricing before you begin. ::: ### Variants Unlike the [EKS Terraform setup](../amazon-eks/terraform-setup.md), we currently support only one main variant of this setup: - The **standard installation** uses a username and password connection for Camunda components (or relies solely on network isolation for certain components). This option is straightforward and easier to implement, making it ideal for environments where simplicity and rapid deployment are priorities, or where network isolation provides adequate security. - The second variant, **IRSA** (IAM Roles for Service Accounts), may work but has not been tested. If you’re interested in setting it up, please refer to the EKS guide as a foundational resource. ### Outcome _Infrastructure diagram for a single region ROSA setup (click on the image to open the PDF version)_ [![Infrastructure Diagram ROSA Single-Region](./assets/rosa-single-region.jpg)](./assets/rosa-single-region.pdf) Following this tutorial and steps will result in: - A [Red Hat OpenShift with Hosted Control Plane](https://www.redhat.com/en/topics/containers/what-are-hosted-control-planes#rosa-with-hcp) cluster running the latest ROSA version with six nodes ready for Camunda 8 installation. - The [EBS CSI driver](https://docs.aws.amazon.com/eks/latest/userguide/ebs-csi.html) is installed and configured, which is used by the Camunda 8 Helm chart to create [persistent volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/). ## 1. Configure AWS and initialize Terraform ### Obtain a copy of the reference architecture The first step is to download a copy of the reference architecture from the [GitHub repository](https://github.com/camunda/camunda-deployment-references/blob/main/aws/openshift/rosa-hcp-single-region/). This material will be used throughout the rest of this documentation, the reference architecture is versioned using the same Camunda versions (`stable/8.x`). ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/openshift/rosa-hcp-single-region/procedure/get-your-copy.sh ``` With the reference architecture copied, you can proceed with the remaining steps outlined in this documentation. Ensure that you are in the correct directory before continuing with further instructions. ### OpenShift cluster module setup This module sets up the foundational configuration for ROSA HCP and Terraform usage. We will leverage [Terraform modules](https://developer.hashicorp.com/terraform/language/modules), which allow us to abstract resources into reusable components, simplifying infrastructure management. The [Camunda-provided module](https://github.com/camunda/camunda-deployment-references/tree/main/aws/openshift/rosa-hcp-single-region/terraform/cluster/) is publicly available and serves as a robust starting point for deploying a Red Hat OpenShift cluster on AWS using a Hosted Control Plane. It is highly recommended to review this module before implementation to understand its structure and capabilities. :::note This module is based on the official [ROSA HCP Terraform module documentation](https://docs.openshift.com/rosa/rosa_hcp/terraform/rosa-hcp-creating-a-cluster-quickly-terraform.html). It is presented as an example for running Camunda 8 in ROSA. **For production or advanced use cases or custom setups, we encourage you to use the [official module](https://docs.openshift.com/rosa/rosa_hcp/terraform/rosa-hcp-creating-a-cluster-quickly-terraform.html)**, which includes vendor-supported features. ::: Verify your AWS quotas: ```bash rosa verify quota --region="$AWS_REGION" ``` :::note This may fail due to organizational policies. ::: #### Set up the ROSA cluster module 1. Ensure you are in the [reference architecture directory of the cloned repository](#obtain-a-copy-of-the-reference-architecture): `./aws/openshift/rosa-hcp-single-region/terraform/`. Then, navigate into the `cluster` module: ```bash ls # Example output: # cluster vpn cd cluster ``` 2. Review the module configuration file `config.tf`. This configuration will use the previously created S3 bucket for storing the Terraform state file: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/openshift/rosa-hcp-single-region/terraform/cluster/config.tf ``` 3. Edit the `cluster.tf` file in the same directory as your `config.tf` file: :::note Configure your cluster Customize the cluster name, availability zones, with the values you previously retrieved from the Red Hat Console. Additionally, provide a secure username and password for the cluster administrator. Ensure that you have set the environment variable `RHCS_TOKEN` with your [OpenShift Cluster Management API Token](https://console.redhat.com/openshift/token/rosa). ::: :::note Private cluster By default, this cluster is accessible from the internet. If you prefer to restrict access, set `locals.rosa_private_cluster = true`. This will create a [private cluster](https://cloud.redhat.com/experts/rosa/private-link/) that is only accessible through the [private subnets](https://docs.aws.amazon.com/vpc/latest/userguide/configure-subnets.html) of your VPC. Optionally, you can still expose a public ingress: [learn more](https://cloud.redhat.com/experts/rosa/private-link/public-ingress/). ⚠️ Since private subnets are not reachable from the internet, you'll need to establish a connection between your network and the cluster. This can be done using a [bastion host](https://docs.aws.amazon.com/mwaa/latest/userguide/tutorials-private-network-bastion.html) or a Client VPN. See the [VPN module setup](#vpn-module-setup) to configure an [AWS VPN Endpoint](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/cvpn-getting-started.html) for secure access to the private cluster. ::: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/openshift/rosa-hcp-single-region/terraform/cluster/cluster.tf ``` :::caution Camunda Terraform module This ROSA module is based on the [official Red Hat Terraform module for ROSA HCP](https://registry.terraform.io/modules/terraform-redhat/rosa-hcp/rhcs/latest). Please be aware of potential differences and choices in implementation between this module and the official one. We invite you to consult the [Camunda ROSA module documentation](https://github.com/camunda/camunda-deployment-references/blob/main/aws/modules/rosa-hcp/README.md) for more information. ::: 4. After setting up the terraform files and ensuring your AWS authentication is configured, initialize your Terraform project, then, initialize Terraform to [configure the backend](#create-an-s3-bucket-for-terraform-state-management) and download necessary provider plugins: ```bash export S3_TF_BUCKET_KEY_CLUSTER="camunda-terraform/cluster.tfstate" echo "Storing cluster terraform state in s3://$S3_TF_BUCKET_NAME/$S3_TF_BUCKET_KEY_CLUSTER" terraform init -backend-config="bucket=$S3_TF_BUCKET_NAME" -backend-config="key=$S3_TF_BUCKET_KEY_CLUSTER" -backend-config="region=$S3_TF_BUCKET_REGION" ``` Terraform will connect to the S3 bucket to manage the state file, ensuring remote and persistent storage. 5. Configure user access to the cluster. By default, the user who creates the OpenShift cluster has administrative access. If you want to grant access to other users, follow the [Red Hat documentation for granting admin rights to users](https://docs.openshift.com/rosa/cloud_experts_tutorials/cloud-experts-getting-started/cloud-experts-getting-started-admin-rights.html) when the cluster is created. 6. Customize the cluster setup. The module offers various input options that allow you to further customize the cluster configuration. For a comprehensive list of available options and detailed usage instructions, refer to the [ROSA module documentation](https://github.com/camunda/camunda-deployment-references/blob/main/aws/modules/rosa-hcp/README.md). #### Define outputs **Terraform** allows you to define outputs, which make it easier to retrieve important values generated during execution, such as cluster endpoints and other necessary configurations for Helm setup. Each module that you have previously set up contains an output definition at the end of the file. You can adjust them to your needs. #### Execution :::note Secret management We strongly recommend managing sensitive information (for example, the administrator username and password) using a secure secrets management solution like HashiCorp Vault. For details on how to inject secrets directly into Terraform via Vault, see the [Terraform Vault Secrets Injection Guide](https://developer.hashicorp.com/terraform/tutorials/secrets/secrets-vault). ::: 1. Plan the configuration files: ```bash terraform plan -out cluster.plan # describe what will be created ``` 2. After reviewing the plan, you can confirm and apply the changes. ```bash terraform apply cluster.plan # creates the resources ``` Terraform will now create the OpenShift cluster with all the necessary configurations. The completion of this process may require approximately 20-30 minutes for each component. ### VPN module setup This section guides you through setting up an AWS VPN Endpoint to access a private cluster. This step is **optional** and only necessary if you have configured a **private cluster**. Using a VPN offers a flexible and secure way to connect to the private subnets within your VPC. It can be used either by a user to access cluster resources or to enable cross-site communications via [PrivateLink](https://docs.aws.amazon.com/vpc/latest/privatelink/what-is-privatelink.html). This module focuses on user access. _Infrastructure diagram for a single region ROSA setup with VPN (click on the image to open the PDF version)_ [![Infrastructure Diagram ROSA Single-Region VPN](./assets/rosa-single-region-vpn.jpg)](./assets/rosa-single-region-vpn.pdf) AWS VPN technology is compatible with OpenVPN clients. It uses [x509 certificates](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/mutual.html) for mutual authentication and source verification. The encryption provided by these certificates ensures that traffic can securely transit over the internet to the AWS VPN endpoint, which performs NAT and routes the traffic directly into the private subnets. This VPN endpoint thus becomes the sole access point to the private cluster. #### Retrieve the VPC cluster ID To create the VPN Endpoint in your cluster’s VPC, you need to retrieve the VPC ID using [Terraform outputs](https://developer.hashicorp.com/terraform/language/values/outputs) from the [OpenShift cluster module](#openshift-cluster-module-setup). Follow these steps: 1. Ensure you are in the [reference architecture directory of the cloned repository](#obtain-a-copy-of-the-reference-architecture): `./aws/openshift/rosa-hcp-single-region/terraform/`. Navigate to the `cluster` module directory inside your reference architecture repository, for example: ```bash ls # Example output: # cluster vpn cd cluster ``` 2. Export the [VPC ID](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc) to an environment variable: ```bash export CLUSTER_VPC_ID="$(terraform output -raw vpc_id)" echo "CLUSTER_VPC_ID=$CLUSTER_VPC_ID" ``` #### Set up the VPN module From the parent directory containing your cluster module, go to the `vpn` directory which holds the VPN endpoint configuration. This setup creates a Certificate Authority (CA) for AWS VPN to perform encryption and mutual client authentication. For simplicity, the CA and generated certificates are stored in the project’s Terraform state (`tfstate`). You may customize this as needed. Start by reviewing the `config.tf` file that configures the S3 backend for Terraform state management: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/openshift/rosa-hcp-single-region/terraform/vpn/config.tf ``` Then, review `vpn.tf`, which describes the VPC Client Endpoint configuration: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/openshift/rosa-hcp-single-region/terraform/vpn/vpn.tf ``` This VPN Client Endpoint follows [AWS best practices and constraints](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/what-is-best-practices.html): - Uses a client CIDR range that does not overlap with the VPC CIDR or any manually added VPN route table routes. - Implements [split-tunnel routing](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/split-tunnel-vpn.html), so only traffic destined for the VPC goes through the VPN, minimizing bandwidth use. - Supports IPv4 only and is bound to the VPC’s private subnets. 1. Set your Terraform state key and initialize Terraform with the S3 [backend](#create-an-s3-bucket-for-terraform-state-management) and download necessary provider plugins: ```bash export S3_TF_BUCKET_KEY_VPN="camunda-terraform/vpn.tfstate" echo "Storing cluster terraform state in s3://$S3_TF_BUCKET_NAME/$S3_TF_BUCKET_KEY_VPN" terraform init -backend-config="bucket=$S3_TF_BUCKET_NAME" -backend-config="key=$S3_TF_BUCKET_KEY_VPN" -backend-config="region=$S3_TF_BUCKET_REGION" ``` Terraform will connect to the S3 bucket to manage the state file, ensuring remote and persistent storage. 2. For each client connecting to the cluster, assign a unique name in `client_key_names` to simplify certificate revocation. 3. By default, VPN access is allowed from any IP address. You may restrict access by adjusting the `vpn_allowed_cidr_blocks` variable. 4. Network designs vary; please review and adjust the configuration to fit your topology. 5. Customize the VPN module by referring to the [VPN module documentation](https://github.com/camunda/camunda-deployment-references/blob/main/aws/modules/vpn/README.md). #### Outputs The module stores certificates and VPN client configurations in the Terraform state. The next section explains how to retrieve and use client configurations. #### Execution 1. Generate a Terraform plan for the VPN Client Endpoint configuration. This will use the private subnets of the designated VPC: ```bash # describe what will be created terraform plan -out vpn.plan \ -var vpc_id="$CLUSTER_VPC_ID" ``` 2. Review and apply the plan to create the resources: ```bash terraform apply vpn.plan # creates the resources ``` Creation of the VPN Client Endpoint typically takes about 10 minutes. After completion, the client configurations will be available in the Terraform output `vpn_client_configs`. ### Reference files Depending on the installation path you have chosen, you can find the reference files used on this page: - **Standard installation:** [Reference Files](https://github.com/camunda/camunda-deployment-references/tree/main/aws/openshift/rosa-hcp-single-region/) ## 2. Preparation for Camunda 8 installation ### Access to the private network using the VPN This section applies if you have previously created a private cluster and want to access it using the [VPN module configured earlier](#vpn-module-setup). 1. Navigate to the VPN module directory (`vpn`): ```bash pwd # Example output: # ./camunda-deployment-references/aws/openshift/rosa-hcp-single-region/terraform/vpn/ ``` 2. Generate your client’s VPN configuration file. This file is compatible with [OpenVPN (ovpn)](https://openvpn.net/) format: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/vpn/gather-vpn-config.sh ``` 3. Import the generated configuration file (`my-client.ovpn`) into an OpenVPN client: - _(preferred)_ [Official AWS VPN Client](https://docs.aws.amazon.com/vpn/latest/clientvpn-user/connect-aws-client-vpn-connect.html) - [Other OpenVPN Clients](https://docs.aws.amazon.com/vpn/latest/clientvpn-user/connect.html) 4. Once the VPN client is connected, you will have secure access to the VPC’s private network. ### Access the created OpenShift cluster You can access the created OpenShift cluster using the following steps: 1. Verify that you are in the [OpenShift cluster module](#openshift-cluster-module-setup) directory `cluster`: ```bash pwd # Example output: # ./camunda-deployment-references/aws/openshift/rosa-hcp-single-region/terraform/cluster/ ``` 2. Set up the required environment variables from the OpenShift terraform module: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/openshift/rosa-hcp-single-region/procedure/gather-cluster-login-id.sh ``` 3. If you want to give cluster administrator access to the created user, this is not required for a standard installation but can be useful for debugging: ```shell rosa grant user cluster-admin --cluster="$CLUSTER_NAME" --user="$CLUSTER_ADMIN_USERNAME" ``` 4. Log in to the OpenShift cluster: ```shell oc login -u "$CLUSTER_ADMIN_USERNAME" "$CLUSTER_API_URL" -p "$CLUSTER_ADMIN_PASSWORD" ``` Clean up and configure the kubeconfig context: ```shell oc config rename-context $(oc config current-context) "$CLUSTER_NAME" oc config use-context "$CLUSTER_NAME" ``` 5. Verify your connection to the cluster with `oc`: ```shell oc get nodes ``` 6. Create a project for Camunda using `oc`: ```shell export CAMUNDA_NAMESPACE="camunda" oc new-project "$CAMUNDA_NAMESPACE" ``` In the remainder of the guide, the `CAMUNDA_NAMESPACE` variable represents the namespace used to create required resources in the Kubernetes cluster, such as secrets and one-time setup jobs. ## 3. Install Camunda 8 using the Helm chart Now that you've exported the necessary values, you can proceed with installing Camunda 8 using Helm charts. Follow the guide [Camunda 8 on OpenShift](/self-managed/deployment/helm/cloud-providers/openshift/redhat-openshift.md) for detailed instructions on deploying the platform to your OpenShift cluster. --- ## Install Camunda 8 on an AKS cluster This guide provides a comprehensive walkthrough for installing the Camunda 8 Helm chart on your existing Azure Kubernetes Service (AKS) cluster and confirming that it is working as intended. ## Requirements - A Kubernetes cluster; refer to the [Terraform guide](./terraform-setup.md) for details. - [Helm CLI v4](https://helm.sh/docs/intro/install/) (recommended; see [supported versions](/reference/supported-environments.md#clients)). - [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) to interact with the cluster. - [jq](https://jqlang.github.io/jq/download/) to interact with some variables. - [yq](https://github.com/mikefarah/yq/#install) to merge the operator Helm values overlays into your `values.yml` file. - [GNU envsubst](https://www.man7.org/linux/man-pages/man1/envsubst.1.html) to generate manifests. - A namespace to host Camunda; in this guide we will reference `camunda` as the target namespace. - (optional) Custom domain name/[DNS zone](https://learn.microsoft.com/en-us/azure/dns/dns-zones-records) in Azure DNS. This allows you to expose Camunda 8 endpoints and connect via community-supported [zbctl](https://github.com/camunda-community-hub/zeebe-client-go/blob/main/cmd/zbctl/zbctl.md) or [Camunda Modeler](https://camunda.com/download/modeler/). - (optional) Permissions to install Kubernetes operators (cluster-admin or equivalent) to deploy infrastructure services such as Elasticsearch, PostgreSQL, and Keycloak. This guide installs them directly from source to provide full control over versions and configuration. For the tool versions used, check the [.tool-versions](https://github.com/camunda/camunda-deployment-references/blob/main/.tool-versions) file in the related repository. This contains an up-to-date list of versions we also use for testing. ## Architecture In addition to the infrastructure diagram provided in the [Terraform setup guide](./terraform-setup.md), this section installs Camunda 8 following the architecture described in the [reference architecture](/self-managed/reference-architecture/reference-architecture.md). The architecture includes the following core components: - **Orchestration Cluster**: Core process execution engine (Zeebe, Operate, Tasklist, and Admin) - **Management plane**: Design and management tools (Camunda Hub and Management Identity) To demonstrate how to deploy with a custom domain, the following stack is also included: - **cert-manager**: Automates TLS certificate management with [Let's Encrypt](https://letsencrypt.org/) - **external-dns**: Manages DNS records in Azure DNS for domain ownership confirmation - **Contour**: Ingress controller backed by the Envoy proxy, providing HTTP/HTTPS load balancing and routing to Kubernetes services ### Considerations While this guide is primarily tailored for UNIX systems, it can also be run under Windows by utilizing the [Windows Subsystem for Linux](https://learn.microsoft.com/windows/wsl/about). Multi-tenancy is disabled by default and is not covered further in this guide. If you decide to enable it, you may use the same PostgreSQL instance and add an extra database for multi-tenancy purposes. [Workload Identities](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview) offer a way to connect to Azure-managed PostgreSQL. This is not yet supported by Camunda. ### Secondary storage This guide supports two [secondary storage](/self-managed/concepts/secondary-storage/index.md) backends. If you have not chosen a variant yet, refer to the [Terraform setup guide](./terraform-setup.md#variants) for details. | Variant | Secondary storage | Optimize | Reference architecture | | ----------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------- | ------------------------- | | **Elasticsearch** | [Elasticsearch via ECK](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#elasticsearch-deployment) | Supported | `aks-single-region` | | **RDBMS** | [Azure Database for PostgreSQL](/self-managed/deployment/helm/configure/database/rdbms.md) | Not available | `aks-single-region-rdbms` | :::note Select a variant using the **Elasticsearch**/**RDBMS** tabs throughout this guide. All tabbed sections will switch together automatically. ::: ## Export environment variables To streamline execution of the following commands, we recommend exporting multiple environment variables. ### Export the Helm chart version The following are the required environment variables with some example values: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/chart-env.sh ``` ### Export database values When using standard authentication (username and password), specific environment variables must be set with valid values. Follow the guide for [Terraform](./terraform-setup.md#set-up-azure-authentication) to set them correctly. Verify the configuration of your environment variables by running the following loop: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/procedure/check-env-variables.sh ``` The RDBMS variant requires additional orchestration database variables (`DB_ORCHESTRATION_NAME`, `DB_ORCHESTRATION_USERNAME`, `DB_ORCHESTRATION_PASSWORD`) on top of the base configuration: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region-rdbms/procedure/check-env-variables.sh ``` ## (Optional) Ingress Setup :::info Domain or domainless installation If you do not have a domain name, external access to Camunda 8 web endpoints from outside the Azure Virtual Network will not be possible. In this case, skip the DNS setup and proceed directly to [deploying Camunda 8 via Helm charts](#deploy-camunda-8-via-helm-charts). Alternatively, you can use `kubectl port-forward` to access Camunda without a domain or Ingress configuration. For more information, see the [kubectl port-forward documentation](https://kubernetes.io/docs/reference/kubectl/generated/kubectl_port-forward/). Throughout the rest of this guide, we refer to configurations as **"With domain"** or **"Without domain"**, depending on whether the application is exposed through a domain. ::: In this section, we provide an optional setup guide for configuring an Ingress with TLS and DNS management, allowing you to access your application through a specified domain. If you haven't set up an Ingress, refer to the [Kubernetes Ingress documentation](https://kubernetes.io/docs/concepts/services-networking/ingress/) for more details. In Kubernetes, an Ingress is an API object that manages external access to services in a cluster, typically over HTTP, and can also handle TLS encryption for secure connections. To monitor your Ingress setup using Azure Monitor, you may find the official guide on [monitoring Ingress controllers with Azure Monitor and Prometheus](https://learn.microsoft.com/en-us/azure/azure-monitor/containers/container-insights-prometheus-integration) helpful. For more details on the Ingress controller used in this guide and its configuration options, refer to the [Contour documentation](https://projectcontour.io/docs/). ### Export Values Set the following values for your Ingress configuration: ```shell reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/export-ingress-setup-vars.sh ``` Additionally, before proceeding, export the following environment variables. These will be used throughout this guide for configuring DNS, certificates, and other Azure resources: ```shell reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/procedure/export-domain-setup-vars.sh ``` These variables will be referenced in later steps, so make sure they are set in your current shell session before continuing. ### Contour [Contour](https://projectcontour.io/) is a CNCF Incubating, open-source Kubernetes Ingress controller that uses the [Envoy proxy](https://www.envoyproxy.io/) as its data plane. It manages external access to services within a Kubernetes cluster, acting as a reverse proxy and load balancer that routes incoming traffic to the appropriate services based on rules defined in the Ingress resource. The following installs `contour` in the `projectcontour` namespace via Helm. For more configuration options, consult the [Contour Helm chart](https://projectcontour.github.io/helm-charts/). ```shell reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/procedure/install-contour.sh ``` The installation script applies AKS-specific settings, such as the Azure load balancer health-probe path and `externalTrafficPolicy: Local`, so that the Azure load balancer health probe targets the Kubernetes `healthCheckNodePort` and the client source IP is preserved. ### external-dns [External-dns](https://github.com/kubernetes-sigs/external-dns) is a Kubernetes add-on that automates the management of DNS records for external resources, such as load balancers or Ingress controllers. It monitors the Kubernetes resources and dynamically updates the DNS provider with the appropriate DNS records. The following installs `external-dns` in the `external-dns` namespace via Helm. For more configuration options, consult the [Helm chart](https://github.com/kubernetes-sigs/external-dns/tree/master/charts/external-dns). Consider setting `domainFilters` via `--set` to restrict access to certain hosted zones. To enable external-dns to work with Azure Managed Identities, create the Kubernetes secret directly using the exported environment variables: ```shell reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/procedure/external-dns-azure-config.sh ``` :::danger Uniqueness of txtOwnerId for DNS If you are already running `external-dns` in a different cluster, ensure each instance has a **unique** `txtOwnerId` for the TXT record. Without unique identifiers, the `external-dns` instances will conflict and inadvertently delete existing DNS records. In the example below, it's set to `external-dns` and should be changed if this identifier is already in use. Consult the [documentation](https://kubernetes-sigs.github.io/external-dns/v0.15.0/#note) to learn more about DNS record ownership. ::: ```shell reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/procedure/install-external-dns.sh ``` ### cert-manager [Cert-manager](https://cert-manager.io/) is an open-source Kubernetes add-on that automates the management and issuance of TLS certificates. It integrates with various certificate authorities (CAs) and provides a straightforward way to obtain, renew, and manage SSL/TLS certificates for your Kubernetes applications. To simplify the installation process, it is [recommended](https://cert-manager.io/docs/installation/helm/#3-install-customresourcedefinitions) to install the cert-manager `CustomResourceDefinition` resources before installing the chart. This separate step allows for easy uninstallation and reinstallation of cert-manager without deleting any custom resources that have been installed. ```shell reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/install-cert-manager-crds.sh ``` The following installs `cert-manager` in the `cert-manager` namespace via Helm. For more configuration options, consult the [Helm chart](https://artifacthub.io/packages/helm/cert-manager/cert-manager). The supplied settings also configure `cert-manager` to ease the certificate creation by setting a default issuer, which allows you to add a single annotation on an Ingress to request the relevant certificates. ```shell reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/procedure/install-cert-manager.sh ``` Create a `ClusterIssuer` via `kubectl` to enable cert-manager to request certificates from [Let's Encrypt](https://letsencrypt.org/): After exporting the above values, follow up with: ```shell reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/procedure/install-cert-manager-issuer.sh ``` ## Identity Provider (IdP) setup ## Deploy Camunda 8 via Helm charts For more configuration options, refer to the [Helm chart documentation](https://artifacthub.io/packages/helm/camunda/camunda-platform#parameters). Additionally, explore our existing resources in the [Camunda 8 Helm chart](/self-managed/deployment/helm/install/quick-install.md) and [related guides](/self-managed/deployment/helm/configure/index.md). Depending on your installation path, you may use different settings. For easy and reproducible installations, we will use YAML files to configure the chart. ### 1. Create the `values.yml` file Start by creating a `values.yml` file to store the configuration for your environment. This file will contain key-value pairs that will be substituted using `envsubst`. You can find a reference example of this file in the tabs below. :::note Database initialization prerequisite If you're using an external Azure Database for PostgreSQL, you must create the individual component databases (Identity and Web Modeler) before installing the Helm chart. This initialization step is covered in the [Configure the database and associated access](./terraform-setup.md#configure-the-database-and-associated-access) section of the Terraform setup guide. Without this step, Management Identity and Web Modeler will fail to connect to their databases. ::: The following makes use of the [combined Ingress setup](/self-managed/deployment/helm/configure/ingress/ingress-setup.md#configuration) by deploying a single Ingress for all HTTP components and a separate Ingress for the gRPC endpoint. :::info Cert-manager annotation for domain installation The annotation `kubernetes.io/tls-acme=true` will be [interpreted by cert-manager](https://cert-manager.io/docs/usage/ingress/) and automatically results in the creation of the required certificate request, easing the setup. ::: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/helm-values/values-domain.yml ``` The RDBMS values file disables Elasticsearch and Optimize, and configures PostgreSQL as the secondary storage for the orchestration cluster: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region-rdbms/helm-values/values-domain.yml ``` :::danger Exposure of the Zeebe Gateway Service For secure operation, do not publicly expose the Zeebe Gateway Service. Keep it reachable only within your Azure Virtual Network (for example, by deploying it without a public Ingress) and access it from internal services or over a private network extension such as an Azure VPN or ExpressRoute connection. This reduces external attack surface while preserving controlled operational access. ::: #### Reference the credentials in secrets Before installing the Helm chart, create Kubernetes secrets to store the database authentication credentials. To create the secrets, run the following commands: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/procedure/create-external-db-secrets.sh ``` The RDBMS variant creates an additional `orchestration-postgres-secret` for the secondary storage database: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region-rdbms/procedure/create-external-db-secrets.sh ``` ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/helm-values/values-no-domain.yml ``` The RDBMS values file disables Elasticsearch and Optimize, and configures PostgreSQL as the secondary storage for the orchestration cluster: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region-rdbms/helm-values/values-no-domain.yml ``` #### Reference the credentials in secrets Before installing the Helm chart, create Kubernetes secrets to store the database authentication credentials. To create the secrets, run the following command for your selected variant: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/procedure/create-external-db-secrets.sh ``` The RDBMS variant creates an additional `orchestration-postgres-secret` for the secondary storage database: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region-rdbms/procedure/create-external-db-secrets.sh ``` ### 2. Configure your deployment #### Enable Enterprise components Web Modeler, Console, and Management Identity are not enabled by default in this deployment. To enable these enterprise components in an OIDC-enabled full cluster, first deploy the required infrastructure (PostgreSQL, Elasticsearch/OpenSearch, and an IdP, such as Keycloak) using the official operators, then apply the Helm values examples shown in [deploy required dependencies with operators](/self-managed/deployment/helm/configure/operator-based-infrastructure.md). ### 3. Deploy prerequisite services Before deploying Camunda, you need to deploy the infrastructure services it depends on. The core infrastructure (Elasticsearch) can be deployed using Kubernetes operators as described in [Deploy infrastructure with Kubernetes operators](/self-managed/deployment/helm/configure/operator-based-infrastructure.md). - **Elasticsearch** (this guide): Deployed via [ECK (Elastic Cloud on Kubernetes)](https://www.elastic.co/guide/en/cloud-on-k8s/current/index.html) - **RDBMS** (alternative): Use a managed Azure Database for PostgreSQL or another supported RDBMS engine — see the [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) All deploy scripts are located in `generic/kubernetes/operator-based/`. Review each script before executing to understand the deployment steps, and adapt the operator Custom Resource configurations for your specific requirements (resource limits, storage, replicas, etc.). :::note Working directory All commands in this guide assume you are at the **repository root** (the directory created by `get-your-copy.sh`). The deploy commands below use subshells `(cd ... && ./deploy.sh)` to preserve your working directory. ::: ##### Deploy Elasticsearch {#deploy-elasticsearch} If your organization does not want to use a managed Elasticsearch service, ECK Operator is an option. In this guide, we default to the ECK Operator deployment of Elasticsearch. :::note RDBMS alternative For those who prefer a relational database, RDBMS (for example, [Azure Database for PostgreSQL](https://azure.microsoft.com/products/postgresql)) is a supported alternative to Elasticsearch for the Orchestration Cluster's secondary storage. See the [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) for supported engines. To use RDBMS instead, skip this section and see [configure RDBMS in Helm](/self-managed/deployment/helm/configure/database/rdbms.md). ::: :::warning Production Elasticsearch recommendation For production workloads, we recommend using an externally managed Elasticsearch service (for example, [Elastic Cloud on Azure](https://azuremarketplace.microsoft.com/en-us/marketplace/apps/elastic.ec-azure-pp)). Terraform support for Elastic Cloud on Azure can be restrictive but remains a viable option. ::: #### Merge operator overlays into values Once the operator-managed services are running, merge the corresponding Helm values overlays into your `values.yml` file. These overlays configure Camunda components to use the external operator-managed services. Merge the **Elasticsearch** overlay: ```bash yq '. *+ load("generic/kubernetes/operator-based/elasticsearch/camunda-elastic-values.yml")' values.yml > values-merged.yml && mv values-merged.yml values.yml ``` The RDBMS variant does **not** require Elasticsearch or the ECK Operator. The Helm values already configure PostgreSQL as the secondary storage, and Elasticsearch is disabled. Skip directly to [filling your deployment with actual values](#fill-your-deployment-with-actual-values). #### Fill your deployment with actual values {#fill-your-deployment-with-actual-values} Once you've prepared the `values.yml` file, run the following `envsubst` command to substitute the environment variables with their actual values: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/assemble-envsubst-values.sh ``` :::note Web Modeler SMTP secret If you plan to enable Web Modeler, create the SMTP secret required for email notifications ([see how it's used by Web Modeler](/self-managed/components/hub/configuration/properties.md#smtp--email)): ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/procedure/create-webmodeler-secret.sh ``` ::: ### 4. Install Camunda 8 using Helm Now that the `generated-values.yml` is ready, you can install Camunda 8 using Helm. Run the following command: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/install-chart.sh ``` This script: - Installs (or upgrades) Camunda using the Helm chart. - Substitutes the appropriate version using the `$CAMUNDA_HELM_CHART_VERSION` environment variable. - Applies the configuration from `generated-values.yml`. You can track the progress of the installation with the deployment readiness check script, which requires [jq](https://jqlang.github.io/jq/) to be installed. ## Verify connectivity to Camunda 8 First, we need an OAuth client to be able to connect to the Camunda 8 cluster. ### Generate an M2M token using Identity Generate an M2M token by following the steps outlined in the [Identity getting started guide](/self-managed/components/management-identity/overview.md), along with the [incorporating applications documentation](/self-managed/components/management-identity/application-user-group-role-management/applications.md). Below is a summary of the necessary instructions: 1. Open Identity in your browser at `https://${CAMUNDA_DOMAIN}/managementidentity`. You will be redirected to your IdP and prompted to log in. 2. Log in with the initial user `admin` (defined in `identity.firstUser` of the values file). Retrieve the generated password (created earlier when running the secret creation script) from the Kubernetes secret and use it to authenticate: ```shell kubectl get secret identity-secret-for-components \ --namespace "$CAMUNDA_NAMESPACE" \ -o jsonpath='{.data.identity-first-user-password}' | base64 -d; echo ``` 3. Select **Add application** and select **M2M** as the type. Assign a name like "test." 4. Select the newly created application. Then select **Access to APIs > Assign permissions** and assign the **Orchestration API** "read" and "write" permissions. 5. Retrieve the `client-id` and `client-secret` values from the application details ```shell export ZEEBE_CLIENT_ID='client-id' # retrieve the value from the identity page of your created m2m application export ZEEBE_CLIENT_SECRET='client-secret' # retrieve the value from the identity page of your created m2m application ``` 6. Open the Orchestration Cluster Admin in your browser at `https://${CAMUNDA_DOMAIN}/admin` and log in with the user `admin` (defined in `identity.firstUser` of the values file). 7. In the Admin navigation menu, select **Roles**. 8. Either select an existing role (for example, **Admin**) or [create a new role](/components/admin/role.md) with the appropriate permissions for your use case. 9. In the selected role view, open the **Clients** tab and click **Assign client**. 10. Enter the client ID of your application created in Management Identity (for example, `test`) and click **Assign client** to save. This operation links the OIDC client to the role's permissions in the Orchestration Cluster, granting the application access to the cluster resources. For more information about managing roles and clients, see [Roles](/components/admin/role.md#manage-clients). Identity and the Orchestration cluster must be port-forwarded to be able to connect to the cluster. If using Keycloak via the Keycloak Operator, you also need to port-forward the Keycloak service. ```shell kubectl port-forward "services/$CAMUNDA_RELEASE_NAME-identity" 8085:80 --namespace "$CAMUNDA_NAMESPACE" kubectl port-forward "services/$CAMUNDA_RELEASE_NAME-zeebe-gateway" 8080:8080 --namespace "$CAMUNDA_NAMESPACE" # If using Keycloak Operator: kubectl port-forward "services/keycloak-service" 18080:18080 --namespace "$CAMUNDA_NAMESPACE" ``` 1. Open Identity in your browser at `http://localhost:8085/managementidentity`. You will be redirected to your IdP and prompted to log in. 2. Log in with the initial user `admin` (defined in `identity.firstUser` of the values file). Retrieve the generated password (created earlier when running the secret creation script) from the Kubernetes secret and use it to authenticate: ```shell kubectl get secret identity-secret-for-components \ --namespace "$CAMUNDA_NAMESPACE" \ -o jsonpath='{.data.identity-first-user-password}' | base64 -d; echo ``` 3. Select **Add application** and select **M2M** as the type. Assign a name like "test". 4. Select the newly created application. Then select **Access to APIs > Assign permissions** and assign the **Orchestration API** "read" and "write" permissions. 5. Retrieve the `client-id` and `client-secret` values from the application details ```shell export ZEEBE_CLIENT_ID='client-id' # retrieve the value from the identity page of your created m2m application export ZEEBE_CLIENT_SECRET='client-secret' # retrieve the value from the identity page of your created m2m application ``` 6. Open the Orchestration Cluster Admin in your browser at `http://localhost:8080/admin` and log in with the user `admin` (defined in `identity.firstUser` of the values file). 7. In the Admin navigation menu, select **Roles**. 8. Either select an existing role (for example, **Admin**) or [create a new role](/components/admin/role.md) with the appropriate permissions for your use case. 9. In the selected role view, open the **Clients** tab and click **Assign client**. 10. Enter the client ID of your application created in Management Identity (for example, `test`) and click **Assign client** to save. This operation links the OIDC client to the role's permissions in the Orchestration Cluster, granting the application access to the cluster resources. For more information about managing roles and clients, see [Roles](/components/admin/role.md#manage-clients). ### Use the token For a detailed guide on generating and using a token, consult the relevant documentation on [authenticating with the Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md). Export the following environment variables: ```shell reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/export-verify-zeebe-domain.sh ``` This requires port-forwarding the Zeebe Gateway to connect to the cluster. ```shell kubectl port-forward "services/$CAMUNDA_RELEASE_NAME-zeebe-gateway" 8080:8080 --namespace "$CAMUNDA_NAMESPACE" ``` Export the following environment variables: ```shell reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/export-verify-zeebe-local.sh ``` Generate a temporary token to access the Orchestration Cluster REST API, then capture the value of the `access_token` property and store it as your token. Use the stored token (referred to as `TOKEN` in this case) to interact with the Orchestration Cluster REST API and display the cluster topology: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/check-zeebe-cluster-topology.sh ``` ...and results in the following output:
Example output ```json reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/check-zeebe-cluster-topology-output.json ```
Follow our existing [Modeler guide on deploying a diagram](/self-managed/components/modeler/desktop-modeler/deploy-to-self-managed.md). Below are the helper values required to be filled in Modeler: The following values are required for the OAuth authentication: - **Cluster endpoint:** `https://zeebe-$CAMUNDA_DOMAIN`, replacing `$CAMUNDA_DOMAIN` with your domain - **Client ID:** Retrieve the client ID value from the identity page of your created M2M application - **Client Secret:** Retrieve the client secret value from the Identity page of your created M2M application - **OAuth Token URL:** Your IdP's token endpoint (for example, `https://$CAMUNDA_DOMAIN/auth/realms/camunda-platform/protocol/openid-connect/token` when using Keycloak), replacing `$CAMUNDA_DOMAIN` with your domain - **Audience:** `orchestration-api`, the default for Camunda 8 Self-Managed This requires port-forwarding the Zeebe Gateway to be able to connect to the cluster: ```shell kubectl port-forward "services/$CAMUNDA_RELEASE_NAME-zeebe-gateway" 26500:26500 --namespace "$CAMUNDA_NAMESPACE" ``` The following values are required for OAuth authentication: - **Cluster endpoint:** `http://localhost:26500` - **Client ID:** Retrieve the client ID value from the identity page of your created M2M application. - **Client Secret:** Retrieve the client secret value from the Identity page of your created M2M application. - **OAuth Token URL:** Your IdP's token endpoint (for example, `http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token` when using Keycloak with port-forwarding) - **Audience:** `orchestration-api`, the default for Camunda 8 Self-Managed.
## Advanced topics The following are some advanced configuration topics to consider for your cluster: - [Camunda production installation guide with Kubernetes and Helm](/self-managed/deployment/helm/install/production/index.md) - [Cluster autoscaling](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/azure/README.md) - [Configure RDBMS secondary storage](/self-managed/deployment/helm/configure/database/rdbms.md) - [Secondary storage concepts](/self-managed/concepts/secondary-storage/index.md) To get more familiar with our product stack, visit the following topics: - [Operate](/components/operate/operate-introduction.md) - [Tasklist](/components/tasklist/introduction-to-tasklist.md) - [Optimize](/components/optimize/what-is-optimize.md) (Elasticsearch variant only) --- ## Microsoft AKS Azure Kubernetes Service ([Microsoft AKS](https://azure.microsoft.com/products/kubernetes-service/)) is a managed container orchestration service for deploying and scaling Kubernetes applications in the cloud. Camunda 8 Self-Managed can be deployed on any supported Kubernetes cluster using [Helm charts](/self-managed/deployment/helm/install/quick-install.md). A list of supported environments is available on our [supported environments page](../../../../../../reference/supported-environments.md). Deployment requirements, including cloud provider-specific information, are available in our [Kubernetes deployment reference](/self-managed/reference-architecture/kubernetes.md). ## Guides --- ## Deploy an AKS cluster with Terraform (advanced) This guide provides a detailed tutorial for deploying an Azure Kubernetes Service (AKS) cluster, tailored specifically for deploying Camunda 8 using Terraform, a popular Infrastructure as Code (IaC) tool. This guide is designed to help you leverage the power of Infrastructure as Code (IaC) to streamline and reproduce your cloud infrastructure setup. By walking through the essentials of setting up an AKS cluster, and provisioning managed Azure resources such as Azure Database for PostgreSQL, this guide demonstrates how to use Terraform with Azure. It makes the process accessible even to those new to Terraform or IaC concepts. It utilizes Azure-managed services where available, offering these as optional components for added convenience and maintainability. :::tip If you are completely new to Terraform and the concept of IaC, consider reading the [Terraform IaC documentation](https://developer.hashicorp.com/terraform/tutorials/azure-get-started/infrastructure-as-code) and trying the [interactive quick start](https://developer.hashicorp.com/terraform/tutorials/azure-get-started/infrastructure-as-code#quick-start) for a basic understanding. ::: ## Requirements - An [Azure subscription](https://azure.microsoft.com/free/) and the necessary permissions to create any resource within Azure. - [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli), a CLI tool for creating and managing Azure resources. - [Terraform](https://developer.hashicorp.com/terraform/downloads) for provisioning infrastructure as code. - [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) to interact with your AKS cluster. - [jq](https://stedolan.github.io/jq/download/) to parse and manipulate JSON (e.g. Terraform outputs). - (optional) Custom domain name/[DNS zone](https://learn.microsoft.com/en-us/azure/dns/dns-zones-records) in Azure DNS. This allows you to expose Camunda 8 endpoints to an external network via the configured ingress. - **Azure service quotas** - Check your quotas for **Virtual Networks**, **vCPU cores**, and **Storage Accounts** in the target region: [Azure subscription and service limits](https://learn.microsoft.com/azure/azure-resource-manager/management/azure-subscription-service-limits). - If you reach a limit, you can [request a quota increase through the Azure portal](https://learn.microsoft.com/en-us/azure/extended-zones/request-quota-increase). - This guide uses **GNU Bash** for all shell commands. For the exact tool versions we’ve tested against, see the [.tool-versions](https://github.com/camunda/camunda-deployment-references/blob/main/.tool-versions) file in the repository. ### Considerations This setup provides a basic foundation for getting started with Camunda 8 on AKS, but it is not fully optimized for performance. It serves as a good starting point for building out a production-ready environment by incorporating [IaC tooling](https://developer.hashicorp.com/terraform/tutorials/azure-get-started/infrastructure-as-code). To try out Camunda 8 or for development purposes, consider signing up for our [SaaS offering](https://camunda.com/platform/). If you already have an AKS cluster, you can skip ahead to the [Helm guide](./aks-helm.md). To keep this guide simple and focused, certain best practices are referenced via links to additional documentation, allowing you to explore each area in more detail when you're ready. :::warning Reference architectures are not intended to be consumed exactly as described. The examples provided in this guide are not packaged as a reusable Terraform module. It is recommended that you clone the repository and make any necessary modifications locally. This approach allows you to extend and customize the codebase according to your specific needs. However, note that maintaining the infrastructure is your responsibility. Camunda will continue to update and improve the reference architecture, and these updates may not be backward compatible. You may incorporate updates into your customized codebase as needed. ::: :::note Cost management This guide provisions resources that can incur costs in your cloud provider account. Review your provider's pricing before you begin. ::: ### Variants This guide supports two secondary storage variants for the Orchestration Cluster. Choose the one that fits your requirements: | Aspect | Elasticsearch | RDBMS (PostgreSQL) | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | | Secondary storage | [Elasticsearch via ECK Operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#elasticsearch-deployment) | [Azure Database for PostgreSQL](/self-managed/deployment/helm/configure/database/rdbms.md) | | Optimize | Supported | Not available (requires Elasticsearch) | | Infrastructure | AKS + PostgreSQL + Elasticsearch cluster | AKS + PostgreSQL only (lighter footprint) | | Orchestration database | Not required | Additional `camunda_orchestration` database | For more details on secondary storage, see [Secondary storage concepts](/self-managed/concepts/secondary-storage/index.md). :::note Select a variant using the **Elasticsearch**/**RDBMS** tabs throughout this guide. All tabbed sections will switch together automatically. ::: #### How to choose - If you need **Optimize** for analytics or prefer the proven Elasticsearch-based setup, choose the **Elasticsearch** variant. - If you want a **lighter infrastructure** without managing an Elasticsearch cluster and do not need Optimize, choose the **RDBMS** variant. RDBMS secondary storage is available as of Camunda 8.9. #### Security The following security considerations were relaxed to streamline adoption and development. These should be reassessed and hardened before deploying to production. The following items were identified using [Trivy](https://trivy.dev/) and can be looked up in the [Aqua vulnerability database](https://avd.aquasec.com/). These concessions are intentional in this reference infrastructure to simplify onboarding, allow internal-only access, and minimize friction during evaluation. They are not appropriate for production and must be revisited. This section explains common security findings in Azure deployments and provides guidance on how to address them.
AVD-AZU-0047 (CRITICAL): Security group rule allows unrestricted ingress from any IP address #### Reasoning This rule permits inbound traffic from `0.0.0.0/0`, meaning any external source can reach the AKS subnet. It may expose workloads or future public IPs to unsolicited access, increasing the risk of compromise—especially if internal services are misconfigured. #### Potential resolution - Restrict incoming traffic to specific IP addresses or CIDR ranges that need access. - For management access, limit SSH/RDP to your company's IP ranges. - Use just-in-time access for administrative purposes. - Implement a bastion host/jump box for secure access. - Consider using [Azure Private Link](https://learn.microsoft.com/en-us/azure/private-link/private-link-overview) for private connectivity. > **Note:** This doesn't affect the AKS control plane directly, but still weakens the overall network boundary.
AVD-AZU-0041 (CRITICAL): Cluster does not limit API access to specific IP addresses #### Reasoning This finding shows that your Kubernetes cluster's API server is accessible from any IP address. The API server is the control plane for Kubernetes and unrestricted access increases the risk of unauthorized access and potential attacks. #### Potential resolution - Configure `authorized_ip_ranges` in `api_server_access_profile` to restrict API server access. ([Review the related documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/kubernetes_cluster#api_server_access_profile)). - Enable private cluster mode with `private_cluster_enabled = true`. ([Review the related documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/kubernetes_cluster#private_cluster_enabled)). - Create an `azurerm_private_endpoint` for the AKS Private Link service. ([Review the related documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/private_endpoint)). - Enable Azure AD–based RBAC via `role_based_access_control { azure_active_directory { ... } }`. ([Review the related documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/kubernetes_cluster#role_based_access_control)). - Use `azurerm_network_security_group` and `azurerm_network_security_rule` to restrict access to the control-plane subnet. ([NSG](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_group), [rule](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_rule)). > **Note:** While open API access simplifies testing and development, production clusters should always restrict API server access to known IP ranges.
AVD-AZU-0013 (CRITICAL): Vault network ACL does not block access by default #### Reasoning This finding indicates that your Azure Key Vault network access controls are not configured to deny access by default. This means that unless specifically restricted, traffic can reach your Key Vault from any source. #### Potential resolution - Enable the ["Deny" default action](https://learn.microsoft.com/en-us/azure/key-vault/general/how-to-azure-key-vault-network-security) for network ACLs. - Allow [specific IP ranges or virtual networks](https://learn.microsoft.com/en-us/azure/key-vault/general/how-to-azure-key-vault-network-security). - Use a [Private Endpoint](https://learn.microsoft.com/en-us/azure/key-vault/general/private-link-service) for Key Vault access. - Use [service endpoints](https://learn.microsoft.com/en-us/azure/key-vault/general/overview-vnet-service-endpoints) to limit Azure service access. - Enable [Soft Delete](https://learn.microsoft.com/en-us/azure/key-vault/general/soft-delete-overview) and [Purge Protection](https://learn.microsoft.com/en-us/azure/key-vault/general/soft-delete-overview) for recovery and data safety. > **Note:** Default deny configurations provide better security posture but may complicate initial setup and testing. For automated testing environments, clearly document these exceptions.
AVD-AZU-0040 (MEDIUM): Cluster does not have logging enabled via OMS Agent #### Reasoning This finding indicates that comprehensive logging is not enabled on your Kubernetes cluster using the OMS (Operations Management Suite) Agent. Without proper logging, you have limited visibility into cluster operations, making it difficult to detect and respond to security incidents. #### Potential resolution - Enable [Azure Monitor for containers](https://learn.microsoft.com/en-us/azure/azure-monitor/containers/container-insights-overview) on your AKS cluster. - Configure the [OMS Agent](https://learn.microsoft.com/en-us/azure/azure-monitor/containers/container-insights-onboard#enable-using-azure-resource-manager-template-or-terraforms) to collect container logs and metrics. - Set up a [Log Analytics workspace](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-workspace-overview) for centralized log storage. - Create [custom queries and alerts](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-tutorial) based on collected logs. - Consider implementing [Microsoft Defender for Cloud](https://learn.microsoft.com/en-us/azure/defender-for-cloud/defender-for-containers-introduction) (formerly Azure Security Center) for enhanced monitoring and threat detection. > **Note:** While disabling logging simplifies testing environments and reduces costs, production environments should always have comprehensive logging enabled. For testing purposes, consider using a shared Log Analytics workspace with appropriate retention policies.
### Outcome _Infrastructure diagram for a single-region AKS setup (click on the image to open the PDF version)_ [![Infrastructure Diagram AKS Single-Region](./assets/aks-single-region.jpg)](./assets/aks-single-region.pdf) :::note The vnet and the subnets are sized according to standard Azure recommendations by default. Due to Azure CNI, every pod will get assigned a real internal IP. While the defaults are more than sufficient for this guide, if you expect a large number of pods in a single subnet, consider using a larger subnet for AKS like /23 or /22. ::: ## 1. Configure Azure and initialize Terraform ### Obtain a copy of the reference architecture The first step is to download a copy of the reference architecture from the [GitHub repository](https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/). Select the variant matching your [secondary storage choice](#variants). This material will be used throughout the rest of this documentation. The reference architectures are versioned using the same Camunda versions (`stable/8.x`). The provided reference architecture repository allows you to directly reuse and extend the existing Terraform example base. This sample implementation is flexible to extend to your own needs without the potential limitations of a Terraform module maintained by a third party. ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/procedure/get-your-copy.sh ``` ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region-rdbms/procedure/get-your-copy.sh ``` With the reference architecture copied, you can proceed with the remaining steps outlined in this documentation. Ensure that you are in the correct directory before continuing with further instructions. ### Terraform prerequisites To manage the infrastructure for Camunda 8 on Azure using Terraform, we need to set up Terraform's backend to store the state file remotely in an Azure Storage Account. This ensures secure and persistent storage of the state file. :::note Advanced users may want to handle this part differently and use a different backend. The backend setup provided is an example for new users. ::: #### Set up Azure authentication The [Azure Terraform provider](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs) requires authentication using Azure credentials before it can create resources. For all environments, create a dedicated Azure AD service principal and assign only the necessary permissions. You can create and assign roles via the [Azure Portal](https://portal.azure.com/) or with the Azure CLI. To log in using an existing Azure Service Principal, you need the `appId` and `tenant` values associated with the Service Principal. These credentials allow Terraform to authenticate and provision resources in your Azure subscription. If you need help finding your tenant ID, refer to [Find your Azure subscription tenant ID](https://learn.microsoft.com/en-us/azure/azure-portal/get-subscription-tenant-id). Use the following command to log in (you will be prompted for the password): ```bash az login --service-principal \ -u \ --tenant ``` Replace `` and `` with the actual values of your Service Principal. Also, ensure that you export the `` by running the below command after logging in as the SP, as it will be needed [in the next step](#creating-terraformtfvars). ```shell export AZURE_SP_ID=$(az account show --query user.name -o tsv) ``` To create a new service principal and assign it the required permissions: Feel free to change the example name. ```bash az ad sp create-for-rbac \ --name "camunda-tf-sp" \ --role Contributor \ --scopes /subscriptions/ ``` This will return a JSON object with `appId`, `password`, and `tenant`. These values are required for login using the service principal: ```bash az login --service-principal \ -u \ --tenant ``` You will be prompted to enter the password interactively. Replace `` and `` with the actual values of your Service Principal. Also, ensure that you export the `` by running the below command after logging in as the SP, as it will be needed [in the next step](#creating-terraformtfvars). ```shell export AZURE_SP_ID=$(az account show --query user.name -o tsv) ``` #### Creating terraform.tfvars To configure your deployment, create a `terraform.tfvars` file in the root of the reference architecture folder (`aks-single-region` or `aks-single-region-rdbms`). This file defines critical environment-specific settings like your Azure subscription and the Service Principal used for authentication. Follow the guide below to get the necessary values: First, set the following environment variables: ```shell # The domain name that your Azure DNS zone manages export TLD= # The resource group that your Azure DNS zone belongs to export AZURE_DNS_RESOURCE_GROUP= ``` Then, run the following script to create the `terraform.tfvars` file: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/procedure/tfvars-domain.sh ``` ##### subscription_id This value specifies the Azure Subscription ID in which all infrastructure will be deployed, including the AKS cluster, PostgreSQL Flexible Server, and Key Vault. To retrieve your current subscription ID, you can run the following command: ```bash az account show --query "id" -o tsv ``` ##### terraform_sp_app_id This is the Application (client) ID of the Azure Service Principal that Terraform uses to configure Role-Based Access Control (RBAC). By providing this ID, Terraform ensures that the Service Principal has the necessary access rights to manage and provision resources within your Azure subscription. ##### dns_zone_id This value specifies the full Azure resource ID of the DNS Zone used for managing your custom domain. It is **required** if you are deploying Camunda 8 with a domain name. Terraform uses this value to grant the necessary role-based access control (RBAC) permissions to the `external-dns` Kubernetes add-on, allowing it to create and update DNS records dynamically within your Azure DNS Zone. If this value is missing or incorrect, `external-dns` will not have permission to manage records, and DNS entries for your Camunda 8 endpoints will not be created. Run the following script to create the `terraform.tfvars` file: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/procedure/tfvars-no-domain.sh ``` ##### subscription_id This value specifies the Azure Subscription ID in which all infrastructure will be deployed, including the AKS cluster, PostgreSQL Flexible Server, and Key Vault. To retrieve your current subscription ID, you can run the following command: ```bash az account show --query "id" -o tsv ``` ##### terraform_sp_app_id This is the Application (client) ID of the Azure Service Principal that Terraform uses to configure Role-Based Access Control (RBAC). By providing this ID, Terraform ensures that the Service Principal has the necessary access rights to manage and provision resources within your Azure subscription. #### Create an Azure Storage Account for Terraform state management Before setting up Terraform, you should create an Azure Storage Account and container to store the state file. This is important for collaboration and to prevent issues like state file corruption. This should be in a separate resource group from the main infrastructure. To start, set the required values as environment variables upfront to avoid repeating them in each command: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/common/procedure/storage-account/storage-account-env-vars.sh ``` Define the value for `AZURE_LOCATION` with your chosen Azure region (for example, `westeurope`). :::warning For production deployments, always use custom names for your Azure Storage Account and related resources. Storage Account names must be **globally unique** across all Azure subscriptions. The provided example (`export AZURE_STORAGE_ACCOUNT_NAME="camundatfstate"`) is for demonstration only and may not be available. Choose a name that is unique and follows your organization's naming conventions to avoid deployment failures. ::: Now, follow these steps to create the storage account with versioning enabled: 1. Run the following script to create a storage account and container for storing your Terraform state. Make sure that you have chosen a globally unique name for the storage account before: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/common/procedure/storage-account/storage-account-creation.sh ``` 2. Enable blob versioning to track changes and protect the state file from accidental deletions or overwrites: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/common/procedure/storage-account/storage-account-versioning.sh ``` 3. Verify versioning is enabled on the blob container: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/common/procedure/storage-account/storage-account-verify.sh ``` This Azure Storage Account will now securely store your Terraform state files with versioning enabled. #### Initialize Terraform Once your authentication is set up, you can initialize your Terraform project. The [previous steps](#create-an-azure-storage-account-for-terraform-state-management) configured a dedicated Azure Storage Account and container (`AZURE_STORAGE_ACCOUNT_NAME`, `AZURE_STORAGE_CONTAINER_NAME`) to store your state. Configure the backend and download the necessary provider plugins: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/common/procedure/storage-account/storage-account-tf-init.sh ``` Terraform will connect to the Azure storage container to manage the state file, ensuring remote and persistent storage. ### Terraform setup This reference architecture uses [Terraform modules](https://developer.hashicorp.com/terraform/language/modules) to deploy all required Azure infrastructure for running Camunda 8 in a production-grade AKS environment. It includes: - A Virtual Network (VNet) and three subnets (AKS, database, private endpoint) - Network Security Group (NSG) for AKS - Azure Kubernetes Service (AKS) cluster with system and user node pools across 3 AZs - Azure PostgreSQL Flexible Server with high availability and private endpoint - Azure Key Vault with encryption key and a user-assigned managed identity for AKS secrets (KMS) #### 1. Main configuration The main deployment logic is defined in [`main.tf`](https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/main.tf). It instantiates all modules and exposes several **customizable values** via the `locals` block: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/main.tf ``` :::warning Azure Key Vault naming Azure Key Vault names must be **globally unique** across all Azure subscriptions. In the linked script, you are prompted to provide a resource prefix, and the Key Vault will be created as `resource_prefix-kv`. Be sure to choose a prefix that results in a unique Key Vault name, or override it in the KMS module to avoid deployment failures. ::: The modules deployed are: - `network` ([network.tf](https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/network.tf)): Virtual network, AKS subnet, DB subnet, and private endpoint subnet - `kms` ([kms.tf](https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/kms.tf)): Key Vault, encryption key, and UAMI for AKS secret encryption - `aks` ([aks.tf](https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/aks.tf)): Cluster deployment with system and user node pools across AZs - `postgres_db` ([db.tf](https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/db.tf)): High-availability PostgreSQL Flexible Server, private DNS, and endpoint #### 2. AKS module This module exposes a customizable **kubernetes_version** value via the `locals` block: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/aks.tf#L1-L3 ``` #### 3. PostgreSQL module This module exposes several **customizable values** via the `locals` block: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/db.tf#L1-L18 ``` These values control database user setup, naming, and passwords. Sensitive values are used by downstream provisioning jobs and Helm secrets. The RDBMS variant extends the base PostgreSQL configuration with an additional **orchestration database** used as the secondary storage for Operate, Tasklist, and the Orchestration Cluster REST API: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region-rdbms/db.tf#L1-L26 ``` In addition to the Identity and Web Modeler databases, this variant creates: - A `camunda_orchestration` database for RDBMS secondary storage - A dedicated `orchestration_db` user with a randomly generated password This module is **enabled by default**. To opt out, you must: - Remove the `db.tf` file from the root - Manually provide credentials and PostgreSQL endpoints for the Helm chart :::tip Alternative: Operator-based PostgreSQL deployment If your organization does not want to use a managed Azure Database for PostgreSQL service, CloudNativePG is an option. For more details on the PostgreSQL deployment with CloudNativePG Operator, see [PostgreSQL deployment in the operator-based infrastructure guide](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#postgresql-deployment) for a production-grade setup with automated scaling, upgrades, and built-in security. ::: ### Execution :::note Secret management We strongly recommend managing sensitive information such as the PostgreSQL username and password using a secure secrets management solution like HashiCorp Vault. For details on how to inject secrets directly into Terraform via Vault, see the [Terraform Vault Secrets Injection Guide](https://developer.hashicorp.com/terraform/tutorials/secrets/secrets-vault). ::: 1. Open a terminal in the chosen reference folder where `config.tf` and other `.tf` files are located. 2. Plan the configuration files: ```bash terraform plan -out cluster.plan # describe what will be created ``` 3. After reviewing the plan, you can confirm and apply the changes: ```bash terraform apply cluster.plan # apply the creation ``` Terraform will now create the AKS cluster with all the necessary configurations. The completion of this process may require approximately 20–30 minutes. ## 2. Preparation for Camunda 8 installation ### Access the created AKS cluster You can gain access to the AKS cluster using the `Azure CLI` with the following command: ```bash # Extract values from terraform output RESOURCE_GROUP=$(terraform output -raw resource_group_name) CLUSTER_NAME=$(terraform output -raw aks_cluster_name) # Echo the values to verify they are not empty echo "RESOURCE_GROUP: $RESOURCE_GROUP" echo "CLUSTER_NAME: $CLUSTER_NAME" # Get credentials using Azure CLI az aks get-credentials \ --resource-group "$RESOURCE_GROUP" \ --name "$CLUSTER_NAME" \ --overwrite-existing ``` After updating the kubeconfig, you can verify your connection to the cluster with `kubectl`: ```bash kubectl get nodes ``` Create a namespace for Camunda: ```bash export CAMUNDA_NAMESPACE="camunda" kubectl create namespace "$CAMUNDA_NAMESPACE" ``` In the remainder of the guide, we reference the `camunda` namespace to create some required resources in the Kubernetes cluster, such as secrets or one-time setup jobs. ### Configure a high-performance StorageClass Camunda 8 requires high IOPS for performance-critical components like **Zeebe**, so it is important to use Azure **PremiumV2** disks rather than the default `Standard_LRS`. :::danger Reclaim policy Using `reclaimPolicy: Delete` can cause **permanent data loss** if a PVC is deleted. Consider using `Retain` for production. See [troubleshooting](/self-managed/operational-guides/troubleshooting.md#zeebe-data-loss-after-pvc-deletion) for details. ::: This step defines a custom `StorageClass` that: - Uses **PremiumV2_LRS** Azure Managed Disks - Sets a **`Retain`** reclaim policy to prevent data loss - Uses `WaitForFirstConsumer` volume binding - Becomes the default StorageClass for the cluster #### Apply the StorageClass Run the following script to apply the new storage class and set it as default: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/procedure/storageclass-configure.sh ``` To verify completion of the operation, run: ```bash ./procedure/storageclass-verify.sh ```
Show script procedure/storageclass-verify.sh ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/procedure/storageclass-verify.sh ```
This must be applied **before installing the Camunda Helm chart** so that PersistentVolumeClaims (PVCs) are provisioned with the correct performance characteristics. ### Configure the database and associated access As you now have a database, you need to create dedicated databases for each Camunda component and an associated user that has configured access. Follow these steps to create the database users and configure access. Due to the tight NSG rules in this example, the only way to access the database is through the AKS cluster. 1. In your terminal, set the necessary environment variables that will be substituted in the setup manifest: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/procedure/vars-create-db.sh ``` The RDBMS variant exports additional environment variables for the orchestration database: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region-rdbms/procedure/vars-create-db.sh ``` A **Kubernetes job** will connect to the database and create the necessary users with the required privileges. The script installs the necessary dependencies and runs SQL commands to create the users and assign them the correct roles and privileges. 2. Create a secret that references the environment variables: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/procedure/create-setup-db-secret.sh ``` The RDBMS variant includes additional orchestration database credentials in the secret: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region-rdbms/procedure/create-setup-db-secret.sh ``` This command creates a secret named `setup-db-secret` and dynamically populates it with the values from your environment variables. After running the above command, you can verify that the secret was created successfully by using: ```bash kubectl get secret setup-db-secret -o yaml --namespace "$CAMUNDA_NAMESPACE" ``` This should display the secret with the base64 encoded values. 3. Apply the following manifest to set up the DB: ```bash kubectl apply -f ./manifests/setup-postgres-create-db.yml --namespace "$CAMUNDA_NAMESPACE" ```
Show manifest setup-postgres-create-db.yml ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region/manifests/setup-postgres-create-db.yml ```
The RDBMS variant manifest creates an additional `camunda_orchestration` database and user for the secondary storage:
Show manifest setup-postgres-create-db.yml ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/azure/kubernetes/aks-single-region-rdbms/manifests/setup-postgres-create-db.yml ```
Once the secret is created, the **Job** manifest from the previous step can consume this secret to securely access the database credentials. 4. Once the job is created, monitor its progress using: ```bash kubectl get job/create-setup-user-db --namespace "$CAMUNDA_NAMESPACE" --watch ``` Once the job shows as `Completed`, the users and databases will have been successfully created. 5. View the logs of the job to confirm that the users were created and privileges were granted successfully: ```bash kubectl logs job/create-setup-user-db --namespace "$CAMUNDA_NAMESPACE" ``` 6. Clean up the resources: ```bash kubectl delete job create-setup-user-db --namespace "$CAMUNDA_NAMESPACE" kubectl delete secret setup-db-secret --namespace "$CAMUNDA_NAMESPACE" ``` Running these commands cleans up both the job and the secret, ensuring that no unnecessary resources remain in the cluster. ## 3. Install Camunda 8 using the Helm chart Now that you've exported the necessary values, you can proceed with installing Camunda 8 using Helm charts. Follow the guide [Camunda 8 on Kubernetes](./aks-helm.md) for detailed instructions on deploying the platform to your Kubernetes cluster. --- ## Google GKE Google Kubernetes Engine ([GKE](https://cloud.google.com/kubernetes-engine)) is a managed container service to run and scale Kubernetes applications in the cloud or on-premises. Camunda 8 Self-Managed can be deployed on any Kubernetes cluster using [Helm charts](/self-managed/deployment/helm/install/quick-install.md). Deployment requirements, including cloud provider-specific information, is available on our [Kubernetes deployment reference](/self-managed/reference-architecture/kubernetes.md). ### Google Cloud load balancer Camunda Identity management endpoints, such as the health check endpoint, do not run on port 80. As a result, when using a Google Cloud Load Balancer, you may need a [custom health check configuration](https://cloud.google.com/kubernetes-engine/docs/how-to/ingress-configuration#direct_health). Here’s an example of a `BackendConfig` you can apply: ```yaml apiVersion: cloud.google.com/v1 kind: BackendConfig metadata: name: camunda-identity spec: healthCheck: timeoutSec: 3 type: HTTP requestPath: /actuator/health/readiness port: 82 ``` When using [container-native load balancing](https://cloud.google.com/kubernetes-engine/docs/how-to/container-native-load-balancing), the load balancer sends traffic to an endpoint in a network endpoint group; hence, the `targetPort` should match the `containerPort` as the load balancer sends probes to the Pod's IP address directly. ```yaml apiVersion: cloud.google.com/v1 kind: BackendConfig metadata: name: camunda-identity spec: healthCheck: timeoutSec: 3 type: HTTP requestPath: /actuator/health/readiness port: 8082 ``` Finally, in your Helm values, assign the `BackendConfig` to the Identity service. ```yaml identity: service: annotations: cloud.google.com/backend-config: '{"default": "camunda-identity"}' ``` --- ## Cloud providers In this section, find details on cloud providers, including Amazon, Google, Microsoft, and Red Hat OpenShift. --- ## Deploy Camunda 8 to a local kind cluster With this guide, you'll deploy Camunda 8 Self-Managed to a local Kubernetes cluster using [kind (Kubernetes in Docker)](https://kind.sigs.k8s.io/). The setup is optimized for learning, development, and testing, with reduced resource requirements suitable for a personal machine. While this guide uses kind, the same concepts apply to other local Kubernetes tools, like [K3s](https://k3s.io/), [minikube](https://minikube.sigs.k8s.io/), or [MicroK8s](https://microk8s.io/). :::warning Local development only This setup is intended for **local development only** and shouldn't be used in production environments. For production deployments, follow our [cloud provider guides](/self-managed/deployment/helm/cloud-providers/index.md). ::: If you encounter issues during deployment, refer to the [Troubleshooting](#troubleshooting) section. ## Deployment modes You can choose from two deployment modes: | Mode | Access | Requirements | Use case | | ------------- | ----------------------------- | ------------------------------- | ------------------------------------- | | **Domain** | `https://camunda.example.com` | mkcert, hosts file modification | Full TLS setup, realistic environment | | **No-domain** | `localhost` via port-forward | hosts file modification | Quick setup, minimal configuration | ### How domain mode works In domain mode, you'll simulate a production-like environment locally with: - **Local DNS resolution** - You'll configure your machine's `/etc/hosts` file to resolve `camunda.example.com` to `127.0.0.1`. - Inside the cluster, you'll configure CoreDNS to rewrite DNS queries for this domain to the Ingress controller, allowing pods to communicate with each other using the same domain name. - **TLS certificates with mkcert** - You'll use [mkcert](https://github.com/FiloSottile/mkcert) to generate locally-trusted certificates by installing a local Certificate Authority (CA) in your system's trust store. This allows your browser to trust the self-signed certificates without security warnings. - You'll also mount the CA certificate in the pods that need to make HTTPS calls to other Camunda components. :::note Firefox compatibility mkcert requires [NSS](https://github.com/FiloSottile/mkcert#supported-root-stores) to work properly with Firefox. Without it, Firefox will display certificate errors. We recommend using **Chrome** or **Chromium-based browsers** for the best experience with domain mode. ::: ## Secondary storage options In addition to the deployment mode, you must choose a [secondary storage](/self-managed/concepts/secondary-storage/index.md) backend. The `SECONDARY_STORAGE` environment variable controls which backend the deployment scripts use. You must set it explicitly before running any deployment command — there is no default. For production backend trade-offs between Elasticsearch/OpenSearch and RDBMS, see [secondary storage architecture](/self-managed/reference-architecture/reference-architecture.md#secondary-storage-architecture). This kind guide is for local development and testing only. | Value | Operators deployed | Components | | --------------- | ------------------------------------- | ----------------------------------------------------------- | | `elasticsearch` | ECK, CloudNativePG, Keycloak Operator | Full platform including Optimize | | `postgres` | CloudNativePG, Keycloak Operator | All components except Optimize (uses PostgreSQL RDBMS mode) | The `postgres` option uses [RDBMS secondary storage](/self-managed/concepts/secondary-storage/index.md) (available since 8.9), which replaces Elasticsearch with PostgreSQL for Operate, Tasklist, and the Orchestration Cluster REST API. This results in a lighter deployment with fewer operators and lower resource consumption. Optimize is not available in this mode because it depends on Elasticsearch's aggregation API for analytics — this is an architectural constraint, not a temporary gap. Once you've chosen your backend, export the variable so all subsequent commands pick it up automatically: ```bash export SECONDARY_STORAGE=postgres # or: elasticsearch ``` ## Prerequisites Before you begin, you'll need: - Terminal access with administrator/sudo privileges for modifying the hosts file (`/etc/hosts`) - A container runtime with at least 4 CPU cores and 8 GB RAM available. Allocate 12 GB or more when you set `SECONDARY_STORAGE=elasticsearch`, as the full stack then runs Elasticsearch, Keycloak, three PostgreSQL clusters, and every Camunda component: - [Docker Desktop](https://www.docker.com/products/docker-desktop) - [Docker Engine](https://docs.docker.com/engine/install/) - [Podman](https://podman.io/docs/installation) - [kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) - [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) - [Helm CLI v4](https://helm.sh/docs/intro/install/) (recommended; see [supported versions](/reference/supported-environments.md#clients)). - [yq](https://github.com/mikefarah/yq#install) - [jq](https://jqlang.org/download/) - [envsubst](https://www.gnu.org/software/gettext/manual/html_node/envsubst-Invocation.html) (Domain mode only; part of the `gettext` package) - [mkcert](https://github.com/FiloSottile/mkcert#installation) (Domain mode only) :::tip You can also use [asdf](https://asdf-vm.com/) to install the tools, with the versions defined in [.tool-versions](https://github.com/camunda/camunda-deployment-references/blob/main/.tool-versions). ::: ## Outcome By the end of this tutorial, you'll have: - A local Kubernetes cluster running with kind. This includes one control plane and two worker nodes. - A Contour Ingress controller deployed for routing traffic (domain mode only). - TLS certificates configured with mkcert (domain mode only). - Prerequisite services deployed via Kubernetes operators: - Elasticsearch via ECK (used as secondary storage in this guide; RDBMS is a supported alternative — see [configure RDBMS in Helm](/self-managed/deployment/helm/configure/database/rdbms.md)) - PostgreSQL (CloudNativePG) - Keycloak (Keycloak Operator) - Camunda 8 Self-Managed fully deployed and accessible, connected to the operator-managed services. :::info Other installation profiles With this guide, you deploy the full Camunda 8 platform with all components. For lighter setups or specific use cases, see the [Helm installation guide](/self-managed/deployment/helm/install/quick-install.md), which covers different installation profiles, such as core only, with Connectors, and with Camunda Hub. ::: ## Download the reference architecture Download the reference architecture files you'll use throughout this guide: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/local/kubernetes/kind-single-region/procedure/get-your-copy.sh ``` You'll run all subsequent commands from `camunda-deployment-references/local/kubernetes/kind-single-region/`. Before proceeding, take some time to explore the repository structure and understand the configuration files, scripts, and Helm values. This will help you understand what each step does and how to customize the deployment for your needs. :::tip Quick setup with Makefile The reference architecture includes a `Makefile` with useful commands to automate the entire deployment process. If you [exported `SECONDARY_STORAGE`](#secondary-storage-options) above, you can omit it from these commands. Otherwise, set it inline: ```bash # PostgreSQL secondary storage (lighter, no Optimize) SECONDARY_STORAGE=postgres make domain.init # With TLS (requires mkcert) SECONDARY_STORAGE=postgres make no-domain.init # With port-forward # Elasticsearch secondary storage (full platform with Optimize) SECONDARY_STORAGE=elasticsearch make domain.init # With TLS (requires mkcert) SECONDARY_STORAGE=elasticsearch make no-domain.init # With port-forward ``` To clean up, use `make domain.clean` or `make no-domain.clean` respectively. Run `make help` to see all available targets, or consult the [Makefile](https://github.com/camunda/camunda-deployment-references/blob/main/local/kubernetes/kind-single-region/Makefile) directly. The following sections detail each step if you prefer to run them manually or want to understand the process. If you've used the quick setup commands above, you can skip ahead to [Accessing Camunda 8](#accessing-camunda-8). ::: ## Create the Kubernetes cluster First, run the cluster creation script: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/local/kubernetes/kind-single-region/procedure/cluster-create.sh ``` This script: 1. Creates a kind cluster named `camunda-platform-local`. 2. Waits until all nodes are ready. 3. Creates the `camunda` namespace. In the output, you should see three nodes in `Ready` state.
Review the cluster configuration The cluster includes one control plane node and two worker nodes with HTTP (80) and HTTPS (443) port mappings for Ingress: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/local/kubernetes/kind-single-region/configs/kind-cluster-config.yaml ```
Now that you've created the cluster, you'll need to choose a deployment mode for the next steps: - [Domain mode](#domain-mode-deployment) - [No-domain mode](#no-domain-mode-deployment) ## Domain mode deployment {#domain-mode-deployment} This section covers the full domain mode setup with TLS certificates and Ingress. If you'd prefer a simpler setup without domain configuration, skip to [No-domain mode deployment](#no-domain-mode-deployment). ### Deploy the Ingress controller Deploy the [Contour Ingress controller](https://projectcontour.io/) to handle incoming traffic: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/local/kubernetes/kind-single-region/procedure/contour-deploy.sh ``` This script: 1. Installs Contour via Helm. 2. Configures Envoy to run on the control plane node with `hostNetwork: true`. 3. Waits until the Envoy deployment is ready. Verify the Ingress controller is running: ```bash kubectl get pods -n projectcontour ``` ### Configure DNS resolution For pods inside the cluster to resolve `camunda.example.com`, configure CoreDNS to rewrite DNS queries: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/local/kubernetes/kind-single-region/procedure/coredns-config.sh ``` This configuration rewrites DNS queries for `camunda.example.com` and `zeebe-camunda.example.com` to the Contour Envoy service (`contour-envoy.projectcontour.svc.cluster.local`), allowing pods to reach Camunda services using the same domain names as external clients.
Review the CoreDNS configuration ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/local/kubernetes/kind-single-region/configs/coredns-configmap-contour.yaml ```
### Configure your hosts file Run the hosts file configuration script to resolve `camunda.example.com` locally. The script requires `sudo` privileges, so you'll need to provide your password: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/local/kubernetes/kind-single-region/procedure/hosts-add.sh ``` This adds the following entries to your `/etc/hosts` file: ``` 127.0.0.1 camunda.example.com 127.0.0.1 zeebe-camunda.example.com ``` ### Generate TLS certificates Generate locally-trusted TLS certificates, using [mkcert](https://github.com/FiloSottile/mkcert): ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/local/kubernetes/kind-single-region/procedure/certs-generate.sh ``` Using certificates from real certificate authorities (CAs) for local development can be dangerous or impossible, for hosts like `localhost` or `127.0.0.1`, and self-signed certificates cause trust errors. mkcert solves this by automatically creating and installing a local CA in the system root store and generating locally-trusted certificates. The certificate generation script: 1. Installs the mkcert CA in your system trust store (first run only). 2. Generates certificates for `camunda.example.com`, `zeebe-camunda.example.com` and `*.camunda.example.com`. 3. Stores certificates in `.certs/`. ### Create Kubernetes secrets for TLS 1. Create the TLS secret in Kubernetes. The Ingress controller will use this to serve HTTPS traffic for `camunda.example.com`. This also creates a `camunda-keycloak-tls` secret for the Keycloak Ingress: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/local/kubernetes/kind-single-region/procedure/certs-create-secret.sh ``` 2. Then, create a ConfigMap with the CA certificate for pods that need to trust it: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/local/kubernetes/kind-single-region/procedure/certs-create-ca-configmap.sh ``` ### Deploy prerequisite services Before deploying Camunda, you need to deploy the external services it depends on. These dependencies are deployed using Kubernetes operators as described in [Deploy infrastructure with Kubernetes operators](/self-managed/deployment/helm/configure/operator-based-infrastructure.md): - Elasticsearch via [ECK (Elastic Cloud on Kubernetes)](https://www.elastic.co/guide/en/cloud-on-k8s/current/index.html) — used as secondary storage in this guide. - PostgreSQL via [CloudNativePG](https://cloudnative-pg.io/) - Keycloak via the [Keycloak Operator](https://www.keycloak.org/operator/installation) :::note Secondary storage alternatives This guide uses Elasticsearch (via ECK) as the secondary storage backend. RDBMS is a supported alternative for the Orchestration Cluster (see the [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) for supported engines). To use RDBMS instead, skip the Elasticsearch operator deployment and see [configure RDBMS in Helm](/self-managed/deployment/helm/configure/database/rdbms.md). ::: Run the operator deployment script, specifying the domain deployment mode: ```bash CAMUNDA_MODE=domain ./procedure/operators-deploy.sh ``` This script installs each operator and its custom resources, then waits for all instances to be ready. When `SECONDARY_STORAGE=postgres`, the ECK operator and Elasticsearch cluster are skipped, and an additional PostgreSQL cluster (`pg-camunda`) is deployed for RDBMS secondary storage. ### Deploy Camunda 8 Deploy Camunda 8 with the domain mode Helm values. The deployment script layers the [operator-based Helm values](https://github.com/camunda/camunda-deployment-references/tree/main/generic/kubernetes/operator-based) to connect Camunda to the external PostgreSQL and Keycloak instances, and to the secondary storage backend you selected: ```bash ./procedure/camunda-deploy-domain.sh ``` The script selects the appropriate Helm values based on your [exported `SECONDARY_STORAGE`](#secondary-storage-options) value. With `elasticsearch`, it includes the Elasticsearch values overlay. With `postgres`, it includes the RDBMS values overlay and disables Elasticsearch and Optimize.
Deploy script source ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/local/kubernetes/kind-single-region/procedure/camunda-deploy-domain.sh ```
This uses the following Helm values:
Domain mode Helm values (kind-specific) ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/local/kubernetes/kind-single-region/helm-values/values-domain.yml ```
Operator-based Helm values (external Elasticsearch, PostgreSQL, Keycloak) The deployment script layers the following shared operator values before the kind-specific values: - [`camunda-elastic-values.yml`](https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/elasticsearch/camunda-elastic-values.yml): Connects Camunda to the ECK-managed Elasticsearch (Elasticsearch secondary storage only). - [`camunda-rdbms-values.yml`](https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/postgresql/camunda-rdbms-values.yml): Configures PostgreSQL RDBMS as secondary storage and disables Elasticsearch and Optimize (PostgreSQL secondary storage only). - [`camunda-keycloak-domain-values.yml`](https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/keycloak/camunda-keycloak-domain-values.yml): Connects Camunda to the operator-managed Keycloak (domain mode). - [`camunda-identity-values.yml`](https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/postgresql/camunda-identity-values.yml): Configures Identity to use the CloudNativePG PostgreSQL. - [`camunda-hub-values.yml`](https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/postgresql/camunda-hub-values.yml): Configures Camunda Hub to use the CloudNativePG PostgreSQL.
mkcert CA trust configuration ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/local/kubernetes/kind-single-region/helm-values/values-mkcert.yml ```
--- ## No-domain mode deployment {#no-domain-mode-deployment} This section covers the simplified setup using port-forwarding without TLS. ### Deploy prerequisite services Before deploying Camunda, you need to deploy the external services it depends on. These dependencies are deployed using Kubernetes operators as described in [Deploy infrastructure with Kubernetes operators](/self-managed/deployment/helm/configure/operator-based-infrastructure.md): - Elasticsearch via [ECK (Elastic Cloud on Kubernetes)](https://www.elastic.co/guide/en/cloud-on-k8s/current/index.html) — used as secondary storage in this guide - PostgreSQL via [CloudNativePG](https://cloudnative-pg.io/) - Keycloak via the [Keycloak Operator](https://www.keycloak.org/operator/installation) Run the operator deployment script, specifying the no-domain deployment mode: ```bash CAMUNDA_MODE=no-domain ./procedure/operators-deploy.sh ``` This script installs each operator and its custom resources, then waits for all instances to be ready. When `SECONDARY_STORAGE=postgres`, the ECK operator and Elasticsearch cluster are skipped, and an additional PostgreSQL cluster (`pg-camunda`) is deployed for RDBMS secondary storage. ### Configure your hosts file for Keycloak The Keycloak operator configures Keycloak with `keycloak-service` as its hostname. The JWT tokens issued by Keycloak use this hostname in the `iss` claim. To ensure your browser can resolve this hostname during the OIDC login flow, add the following entry to your `/etc/hosts` file: ```text 127.0.0.1 keycloak-service ``` Alternatively, you can run `make hosts.add-keycloak` to add this entry automatically. After adding this entry and deploying Camunda 8 in the next step, you'll be able to reach Keycloak at `http://keycloak-service:18080/auth`. **Why port `18080`?** The Keycloak instance is configured to listen on port `18080` (via `httpPort` in the operator CR) to avoid conflicts with the Zeebe Gateway HTTP endpoint, which uses port `8080` locally. ### Deploy Camunda 8 Deploy Camunda 8 with the no-domain mode Helm values. The deployment script layers the [operator-based Helm values](https://github.com/camunda/camunda-deployment-references/tree/main/generic/kubernetes/operator-based) to connect Camunda to the external PostgreSQL and Keycloak instances, and to the secondary storage backend you selected: ```bash ./procedure/camunda-deploy-no-domain.sh ``` The script selects the appropriate Helm values based on your [exported `SECONDARY_STORAGE`](#secondary-storage-options) value. With `elasticsearch`, it includes the Elasticsearch values overlay. With `postgres`, it includes the RDBMS values overlay and disables Elasticsearch and Optimize.
Deploy script source ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/local/kubernetes/kind-single-region/procedure/camunda-deploy-no-domain.sh ```
This uses the following Helm values:
No-domain mode Helm values (kind-specific) ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/local/kubernetes/kind-single-region/helm-values/values-no-domain.yml ```
Operator-based Helm values (external Elasticsearch, PostgreSQL, Keycloak) The deployment script layers the following shared operator values before the kind-specific values: - [`camunda-elastic-values.yml`](https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/elasticsearch/camunda-elastic-values.yml): Connects Camunda to the ECK-managed Elasticsearch (Elasticsearch secondary storage only). - [`camunda-rdbms-values.yml`](https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/postgresql/camunda-rdbms-values.yml): Configures PostgreSQL RDBMS as secondary storage and disables Elasticsearch and Optimize (PostgreSQL secondary storage only). - [`camunda-keycloak-no-domain-values.yml`](https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/keycloak/camunda-keycloak-no-domain-values.yml): Connects Camunda to the operator-managed Keycloak (no-domain mode). - [`camunda-identity-values.yml`](https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/postgresql/camunda-identity-values.yml): Configures Identity to use the CloudNativePG PostgreSQL. - [`camunda-hub-values.yml`](https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/postgresql/camunda-hub-values.yml): Configures Camunda Hub to use the CloudNativePG PostgreSQL.
## Verify deployment Monitor the deployment progress: ```bash kubectl get pods -n camunda -w ``` Wait until all pods show `Running` status. This may take 5–10 minutes depending on your internet connection and system resources. You can also use the deployment readiness check script, run from the reference architecture directory `get-your-copy.sh` left you in. This script requires [jq](https://jqlang.github.io/jq/) to be installed: ```bash export CAMUNDA_NAMESPACE=camunda ../../../generic/kubernetes/single-region/procedure/check-deployment-ready.sh ``` Finally, verify the Helm release: ```bash helm list -n camunda ``` ## Accessing Camunda 8 :::note Optimize is only available with Elasticsearch secondary storage. If you deployed with `SECONDARY_STORAGE=postgres`, Optimize is disabled. ::: | Component | URL | Availability | | ------------------------------ | ---------------------------------------------- | ------------------------------------ | | Operate | https://camunda.example.com/operate | All | | Tasklist | https://camunda.example.com/tasklist | All | | Admin | https://camunda.example.com/admin | All | | Management Identity | https://camunda.example.com/managementidentity | All | | Optimize | https://camunda.example.com/optimize | Elasticsearch secondary storage only | | Orchestration Cluster REST API | https://camunda.example.com/ | All | | Keycloak | https://camunda.example.com/auth | All | :::note Optimize is only available with Elasticsearch secondary storage. If you deployed with `SECONDARY_STORAGE=postgres`, Optimize is disabled and its port-forward is skipped automatically. ::: Start port-forwarding to access the services: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/local/kubernetes/kind-single-region/procedure/port-forward.sh ``` :::tip Localhost development with kubefwd For a richer localhost experience, and to avoid managing many individual port-forward commands, you can use [kubefwd](https://github.com/txn2/kubefwd) to forward all services in the target namespace and make them resolvable by their in-cluster DNS names on your workstation. This requires `sudo` to bind privileged ports and modify `/etc/hosts`: ```shell sudo kubefwd services -n "camunda" ``` Now, you can reach services directly, for example: - **Management Identity**: `http://camunda-identity/managementidentity` - **Keycloak**: `http://keycloak-service:18080/auth` - **Zeebe Gateway gRPC**: `camunda-zeebe-gateway:26500` You can still use localhost ports if you prefer traditional port-forwarding. Stop kubefwd with **Ctrl+C** when finished. Be aware kubefwd modifies your `/etc/hosts` temporarily, then restores the file when it exits. ::: | Component | URL | Availability | | -------------------- | ---------------------------------- | ------------------------------------ | | Zeebe Gateway (gRPC) | localhost:26500 | All | | Zeebe Gateway (HTTP) | http://localhost:8080/ | All | | Operate | http://localhost:8080/operate | All | | Tasklist | http://localhost:8080/tasklist | All | | Admin | http://localhost:8080/admin | All | | Management Identity | http://localhost:8085 | All | | Optimize | http://localhost:8083 | Elasticsearch secondary storage only | | Camunda Hub | http://localhost:8070 | All | | Connectors | http://localhost:8088 | All | | Keycloak | http://keycloak-service:18080/auth | All | :::tip Connecting to the Orchestration Cluster To interact with the Orchestration Cluster via Zeebe Gateway using the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) or a local client/worker, connect to `localhost:26500` (gRPC) or `http://localhost:8080` (REST). ::: At any time, run `kubectl get services -n camunda` to get a full list of deployed Camunda components and their network properties. ### Default credentials #### Camunda admin - **Username**: `admin` - **Password**: Run the following script ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/local/kubernetes/kind-single-region/procedure/get-password.sh ``` #### Keycloak admin The Keycloak operator generates a separate set of admin credentials stored in the `keycloak-initial-admin` secret. These credentials are different from the Camunda admin credentials and are used to access the Keycloak administration console. ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/local/kubernetes/kind-single-region/procedure/get-keycloak-password.sh ``` ## Cleanup :::warning Destructive action This will permanently delete all Camunda 8 data in the local development cluster. ::: If you used the Makefile for setup, you can use the corresponding clean command: ```bash # Domain mode make domain.clean # No-domain mode make no-domain.clean ``` Alternatively, you can clean up manually: ```bash # Delete cluster kind delete cluster --name camunda-platform-local # (domain mode) Remove hosts entries (requires sudo) sudo sed -i.bak '/camunda.example.com/d' /etc/hosts # (no-domain mode) Remove Keycloak hosts entry (requires sudo) sudo sed -i.bak '/keycloak-service/d' /etc/hosts # (domain mode) Clean certificates rm -rf .certs ``` ## Troubleshooting ### Pods don't start Check the pod status and events: ```bash kubectl get pods -n camunda kubectl describe pod -n camunda kubectl logs -n camunda ``` ### Components fail OIDC discovery with 404 or 503 (Domain mode) In domain mode, Identity provisions the `camunda-platform` Keycloak realm, and every other component reads its OIDC configuration from that realm at startup. When Identity never becomes ready, the realm is never created and the rest of the platform crash-loops. The other components report the missing realm instead of the missing Identity, which makes the error misleading: | Component | Error | | ------------- | --------------------------------------------------------------------------------------- | | Orchestration | `503 Service Unavailable` on `https://camunda.example.com/auth/realms/camunda-platform` | | Connectors | `Failed to retrieve well known configuration ... status code 404 and message Not Found` | Check whether the realm exists, then inspect Identity: ```bash curl https://camunda.example.com/auth/realms/camunda-platform/.well-known/openid-configuration kubectl get pods -n camunda kubectl describe pod -n camunda ``` If the realm returns `{"error":"Realm does not exist"}` and the Identity pod is in `CrashLoopBackOff`, Identity is the root cause. Restarting Orchestration or Connectors doesn't help, because the realm they need still doesn't exist. A `Last State: Terminated, Reason: OOMKilled` line in the `kubectl describe pod` output means Identity ran out of memory. Give the container runtime more memory, or raise `identity.resources.limits.memory` in the Helm values before you redeploy. ### Browser shows certificate errors (Domain mode) Ensure the mkcert CA is installed: ```bash mkcert -install ``` Regenerate certificates: ```bash ./procedure/certs-generate.sh ./procedure/certs-create-secret.sh kubectl rollout restart deployment -n camunda ``` ### Ingress doesn't work (Domain mode) Check the Ingress controller status: ```bash kubectl get pods -n projectcontour kubectl get ingress -n camunda ``` ### Insufficient resources Ensure your container runtime has enough resources allocated: 4 or more CPU cores, and 8 GB or more of RAM (12 GB or more with `SECONDARY_STORAGE=elasticsearch`). - **Docker Desktop**: Check the [Resources settings](https://docs.docker.com/desktop/settings-and-maintenance/settings/#resources) - **Docker Engine**: Configure the [Docker daemon](https://docs.docker.com/engine/daemon/) (configuration varies by OS) - **Podman**: Resources are managed by your system; ensure sufficient resources are available A pod that restarts repeatedly and shows `Last State: Terminated, Reason: OOMKilled` in `kubectl describe pod` output has hit its memory limit rather than the host limit. Raise the `resources.limits.memory` value for that component in the Helm values. ## Next steps - [Getting started guide](/guides/getting-started-orchestrate-human-tasks.md): Deploy your first process - [Camunda APIs](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md): Explore the REST APIs - [Production Helm deployment](/self-managed/deployment/helm/install/production/index.md): Learn more about Helm chart configuration for production - [Cloud provider guides](/self-managed/deployment/helm/cloud-providers/index.md): Deploy to cloud platforms like AWS, Azure, or GCP --- ## Red Hat OpenShift Dual-Region This guide is designed to assist users aiming to deploy Camunda 8 in a dual-region setup on Red Hat OpenShift. The primary goal is to configure and integrate **two OpenShift clusters** for use in the dual-region reference architecture. This setup leverages specific components to address key challenges, and users are encouraged to exercise discretion regarding their use, learn more about [Advanced Cluster Management](https://www.redhat.com/en/resources/advanced-cluster-management-kubernetes-datasheet). While this guide does not cover exhaustive configurations, it aims to provide the key steps needed to achieve the desired outcome. To enable intercommunication between regions, we will utilize [ACM Advanced Cluster Management](https://www.redhat.com/en/technologies/management/advanced-cluster-management) and [Submariner](https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/2.2/html/manage_cluster/submariner). Failover will be managed through DNS configurations, while access to the platform will be ensured via OpenShift Routes. Additionally, depending on whether your OpenShift clusters are managed on a cloud provider or deployed on-premises, certain aspects of the configuration might require adaptation. :::caution Dual-region limits and constraints Please review our [dual-region concept documentation](/self-managed/concepts/multi-region/dual-region.md) to understand the limitations and constraints of this blueprint before proceeding. ::: ## High Level Design _Infrastructure diagram for a OpenShift dual-region setup (click on the image to open the PDF version)_ [![Infrastructure Diagram OpenShift Dual-Region](./assets/openshift-dual-region.jpg)](./assets/openshift-dual-region.pdf) This High-Level Design describes how the following critical components interact to achieve a Camunda 8 deployment across two regions: - An S3-compatible solution is used for taking snapshots of the Elasticsearch database. - A DNS entry with an associated domain is used to enable Camunda 8 failover and reroute traffic from one cluster to the other. - Firewall and networking components are configured to allow unrestricted communication between the two clusters. - Local storage is provided on each OpenShift cluster for persistent data requirements. - A non-overlapping network configuration is implemented on the OpenShift clusters. This is a mandatory requirement, as outlined in the [Submariner overlapping guide](https://submariner.io/0.8/getting-started/architecture/globalnet/). - [Red Hat OpenShift Advanced Cluster Management](https://www.redhat.com/en/technologies/management/advanced-cluster-management) is used to manage the two clusters and configure Submariner. - [Submariner](https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/2.2/html/manage_cluster/submariner) is configured on the two clusters to enable cross-namespace and cross-cluster network communication. ## Requirements - You need access to the [Advanced Cluster Management operator](https://www.redhat.com/en/technologies/management/advanced-cluster-management) and the [Submariner operator](https://catalog.redhat.com/software/container-stacks/detail/5f0c67b7ce85fb9e399f3a12). - The clusters must be separated by a reasonable latency as outlined in the [network requirements](/self-managed/concepts/multi-region/dual-region.md#network-requirements). - Each of your OpenShift clusters must meet at least the minimum capacity requirements for a cluster. Refer to the [minimum cluster requirements](/self-managed/reference-architecture/kubernetes.md#minimum-cluster-requirements-1) for details on resource allocation and infrastructure needs. - Administrative privileges are required for both clusters to perform configurations and operator deployments. - A reliable means of communication between the two clusters is necessary. Ensure that each cluster can establish network connections with the other. - The version of your OpenShift clusters must be included in the [supported versions list](/self-managed/reference-architecture/kubernetes.md#supported-versions). - Review the [requirements of OpenShift Advanced Cluster Management](https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/2.12/html/install/installing#installing) if it's not already configured. - Review the [requirements of OpenShift Submariner](https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/2.12/html/networking/networking#submariner) if it's not already configured. - Review the [requirements of Submariner](https://submariner.io/getting-started/#prerequisites) if it's not already configured, especially the flows to open. ### CLI Requirements In addition to the general prerequisites outlined above, the following CLI tools are required for interacting with the clusters and deploying Camunda 8, these are the same CLI tools required as mentioned in the [OpenShift Guide](redhat-openshift.md#requirements): - [subctl](https://github.com/submariner-io/subctl) a CLI tool used to interact with [Submariner](https://submariner.io/). ### OpenShift clusters The architecture of your OpenShift clusters may vary depending on your specific configuration. This guide assumes a generic deployment on OpenShift. If you are interested in a tested topology, follow the [ROSA Dual-Region Guide](/self-managed/deployment/helm/cloud-providers/amazon/openshift/terraform-setup-dual-region.md). #### Cluster requirements To deploy infrastructure components successfully, it’s essential that the OpenShift clusters you use have a `cluster-admin` role, which grants full privileges within the cluster. This level of access is necessary for the proper setup and configuration of the infrastructure components. However, **Camunda 8 deployment does not require cluster-admin access**. For the best security practice and to follow the principle of least privilege, it is recommended to deploy Camunda 8 with a **standard, limited user**. This approach helps minimize security risks by restricting unnecessary administrative privileges. ## Setup Advanced Cluster Management and Submariner In order to achieve cross-cluster communication, we use the recommended [Submariner](https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/2.2/html/manage_cluster/submariner) solution that is designed to securely connect workloads across multiple Kubernetes clusters. The installation of Submariner on OpenShift differs from the standard installation as it requires the usage of [Red Hat Advanced Cluster Management for Kubernetes](https://www.redhat.com/en/technologies/management/advanced-cluster-management). :::note Alternative installations You may achieve the same installation without the usage of Red Hat Advanced Cluster Management for Kubernetes, however, this guide will focus on the official supported deployment of Submariner. ::: ### Advanced Cluster Management If Red Hat Advanced Cluster Management is not enabled on your cluster, you need to enable it. The following steps are an extract from the [official instructions](https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/2.12/html/install/installing), which you may want to refer for details of the implementation and associated customizations. :::caution Non-production use only The following installation instructions for Advanced Cluster Management are intended for non-production environments. For a production setup, please consult the [official Red Hat Advanced Cluster Management guide](https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/2.12/html/install/installing). ::: :::note Designation of the clusters in ACM The cluster of region 0 is referred to as `local-cluster` in **ACM**. This designation cannot be changed, as it is a constant name used to reference the [managed hub cluster](https://open-cluster-management.io/docs/concepts/cluster-inventory/managedcluster/). Later in this guide, we will refer to it as **first cluster**. ::: 0. This part of the guide uses a generic reference architecture, you can find all the [acm files here](https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/acm/). 1. Reference each cluster context name and ensure that each cluster's context name matches the corresponding cluster name. If the context name does not match, you will need to rename it to follow this guide. ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/set-cluster-names.sh ``` 2. The following manifest will create a namespace for the management cluster, enable the [open-cluster-management operator](https://open-cluster-management.io/) and the [associated subscription](https://docs.openshift.com/container-platform/4.17/operators/admin/olm-adding-operators-to-cluster.html#olm-installing-operator-from-operatorhub-using-cli_olm-adding-operators-to-a-cluster). ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/acm/install-manifest.yml ``` Save this manifest as `install-manifest.yml` then apply it to enable ACM: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/acm/install-acm.sh ``` Verify that the installation succeeded: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/acm/verify-acm.sh ``` 3. With the ACM operator now enabled on the first cluster, the next step is to create the [Multicluster Global Hub](https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/2.12/html-single/install/index#installing-from-the-cli). This feature allows you to import and manage one or more hub clusters from a single central hub cluster. In this setup, the first cluster will act as the central hub, managing the second cluster. This capability enables the deployment and management of components on the second cluster directly from the first cluster. ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/acm/multi-cluster-hub.yml ``` :::caution Known issue: may not work correctly with the manifest The creation of the MultiClusterHub using the manifest can sometimes remain stuck in the installation phase when created this way. To avoid this issue, you can follow the [official instructions in the OpenShift UI Console](https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/2.12/html/install/installing#installing-from-the-operatorhub). ::: Save this manifest as `multi-cluster-hub.yml` then apply it to enable ACM: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/acm/install-multi-cluster-hub.sh ``` Wait until the status shows as "Running." This process can take up to 10 minutes: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/acm/verify-multi-cluster-hub.sh ``` :::caution Security consideration - A ServiceAccount with a ClusterRoleBinding automatically gives cluster administrator privileges to Red Hat Advanced Cluster Management and to any user credentials with access to the namespace where you install Red Hat Advanced Cluster Management (`open-cluster-management` here), [learn more about this on the official documentation](https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/2.12/html/install/installing#installing-from-the-operatorhub). - A namespace called `local-cluster` is reserved for the Red Hat Advanced Cluster Management hub cluster when it is self-managed. This is the only local-cluster namespace that can exist. - :warning: For security reasons, do not give access to the `local-cluster` namespace to any user that is not a cluster-administrator. ::: 4. With the MultiClusterHub created, the last step is to create a `ManagedClusterSet` which is a group of managed clusters. With a `ManagedClusterSet`, you can manage access to all of the managed clusters in the group together ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/acm/managed-cluster-set.yml ``` Save this manifest as `managed-cluster-set.yml` then apply it to enable ACM: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/acm/install-managed-cluster-set.sh ``` Verify that the ManagedClusterSet has been created, at this step, only `local-cluster` will be listed: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/acm/verify-managed-cluster-set.sh ``` 5. After creating the Managed Cluster Set, the next step is to import clusters into the set. - To import a cluster, you need to template the manifest for each cluster. Save the following file as `managed-cluster.yml.tpl`: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/acm/managed-cluster.yml.tpl ``` - The cluster’s associated addon configuration will be managed using the following manifest. Save it as `klusterlet-config.yml.tpl`: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/acm/klusterlet-config.yml.tpl ``` - To import a cluster, you must store the associated authentication token. Save the following file as `auto-import-cluster-secret.yml.tpl`: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/acm/auto-import-cluster-secret.yml.tpl ``` - If running on Red Hat OpenShift Service on AWS (ROSA), the following addition is required to ensure certificates are trusted. Save the following file as `klusterlet-global-config.yml`: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/acm/klusterlet-global-config.yml ``` - Finally, import the target cluster into the Managed Cluster Set and verify that they can be reached and managed successfully: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/acm/initiate-cluster-set.sh ``` - Once all the clusters are imported, verify that all of them are available and reachable: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/acm/verify-managed-cluster-set.sh ``` ### Submariner The [architecture of Submariner](https://submariner.io/getting-started/architecture/) comprises several components working together to enable direct networking between Pods and Services across different Kubernetes clusters. The following diagram illustrates the interaction between the two clusters: _Infrastructure diagram of Submariner setup_ ![Infrastructure diagram of Submariner setup](./assets/submariner-hld.jpg) - Traffic sent from one broker to another cluster can be encrypted by the [Gateway Engine](https://submariner.io/getting-started/architecture/gateway-engine/). In OpenShift, the IPSec protocol is used on port `4500/UDP`, utilizing the [Libreswan](https://libreswan.org/) implementation. - A dedicated node in each cluster assumes the [Broker Role](https://submariner.io/getting-started/architecture/broker/), facilitating the exchange of metadata between Gateway Engines in participating clusters. This component is **not responsible for transmitting data**, unlike the Gateway Engine, which handles data transmission between internal networks of different clusters. High availability can be achieved by adding a second dedicated node. - Service discovery is managed internally by the [Lighthouse project](https://submariner.io/getting-started/architecture/service-discovery/). - The [Route Agent component](https://submariner.io/getting-started/architecture/route-agent/) runs on every node in each participating cluster. It sets up the necessary host network elements on top of the existing Kubernetes CNI plugin. :::note Handling overlapping CIDRs This guide does not cover handling overlapping CIDRs. However, this can be achieved using the [Globalnet Controller](https://submariner.io/getting-started/architecture/globalnet/). ::: Installing Submariner in OpenShift **requires** [Advanced Cluster Management](#advanced-cluster-management) to be configured, with each cluster added to the management cluster. 0. This part of the guide uses a generic reference architecture, you can find all the [submariner files here](https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/submariner/). 1. Ensure cluster context names match: _(Skip this step if already completed as part of [Advanced Cluster Management](#advanced-cluster-management).)_ Verify that each cluster's context name matches its corresponding cluster name. If the context name does not match, rename it to align with this guide. ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/set-cluster-names.sh ``` 2. Verify dedicated broker nodes: Confirm that each cluster has nodes labeled for Submariner gateway functionality: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/submariner/list-nodes-brokers.sh ``` If no nodes are labeled, you need to label at least one node in each cluster. For better reliability, consider dedicating a node as the broker. **Assigning broker node labels:** Select the first node and apply the required label: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/submariner/label-nodes-brokers.sh ``` 3. Deployment of Submariner on the clusters: - Save the following file as `submariner.yml.tpl`: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/submariner/submariner.yml.tpl ``` :::note Cluster naming In this example, the first cluster is referenced as `local-cluster`. This is because the first cluster is used as the management cluster in this minimal setup. If your cluster is named differently, you may need to adapt this file to match your actual cluster name. ::: - Then apply it on the management cluster: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/submariner/install-submariner.sh ``` - Wait for the brokers to become ready. This may take up to 10 minutes. You can check the broker status using the following command: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/submariner/verify-submariner.sh ``` 4. After deploying Submariner, check that the clusters can communicate with each other by using the `subctl` utility. Keep in mind that it might take several minutes before all status indicators turn green. If you don’t have the `subctl` CLI installed, you can follow the [installation instructions here](https://submariner.io/operations/deployment/) or execute the following commands: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/submariner/install-subctl.sh ``` Now, verify communication between the clusters with the following script: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/submariner/verify-subctl.sh ``` If everything is set up correctly, you should observe in the output of each cluster context the following statuses: - Gateway's status: `All connections (1) are established` - Connection's status: `connected 10.406614ms (RTT)`
Example Submariner check successful output ```text reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/submariner/output.txt ```
For more comprehensive details regarding the verification tests for Submariner using subctl, please refer to the [official documentation](https://submariner.io/operations/deployment/subctl/#verify). **Debugging the Submariner setup:** If you are experiencing connectivity issues, we recommend spawning a pod in the `default` namespace that contains networking debugging tools. You can find an [example here](https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/submariner/debug-utils-submariner.yml). With this pod, you will be able to check flow openings, service resolution, and other network-related aspects. Troubleshooting requires examining all the underlying mechanisms of Submariner. Therefore, we also encourage you to read the [Submariner troubleshooting guide](https://submariner.io/operations/troubleshooting/). ## Deploying Camunda 8 via Helm charts in a dual-region setup :::info Migration from Bitnami Elasticsearch to ECK in dual-region There is currently no dedicated migration procedure for moving from the Bitnami Elasticsearch subchart to the ECK operator in a dual-region setup. If you need to perform this migration, follow the [single-region migration procedure (documented for Camunda 8.9)](/self-managed/deployment/helm/operational-tasks/migration-from-bitnami/index.md) and apply it individually to each region. ::: The installation of Camunda 8 in OpenShift across dual regions requires a functioning Submariner setup connecting two OpenShift clusters. ### Verify the pre-requisites Before proceeding with the installation, ensure the required information is available and configured in your terminal for later use. Review and adjust the following environment script to match your specific configuration: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/chart-env.sh ``` _If you are unsure about the values of the backup bucket, please refer to the [S3 backup bucket module setup](/self-managed/deployment/helm/cloud-providers/amazon/openshift/terraform-setup-dual-region.md#s3-backup-bucket-module-setup) as a reference for implementation._ Save the file as `chart-env.sh`, replace the placeholders with your values, and then source the file: ```bash source chart-env.sh ``` ### Set up namespaces Submariner requires that each cluster has both namespaces present. Create the required namespaces in both clusters: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/setup-namespaces.sh ``` Save it as `setup-namespaces.sh`, and execute it: ```bash chmod +x setup-namespaces.sh ./setup-namespaces.sh ``` ### Create Elasticsearch S3 backup secrets Elasticsearch will need an S3 bucket for data backup and restore procedure, required during a regional failback. For this, you will need to configure a Kubernetes secret to not expose those in cleartext. If you don't have access to an S3 bucket, you can adapt the backup method to use an [alternative Elasticsearch backup solution](https://www.elastic.co/guide/en/elasticsearch/reference/current/snapshot-restore.html). However, this guide focuses solely on S3 snapshots. :::caution Bucket vulnerable to region outages The Elasticsearch backup [bucket is tied to a specific region](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingBucket.html). If that region becomes unavailable and you want to restore to a different region or S3 services remain disrupted, you must create a new bucket in another region and reconfigure your Elasticsearch cluster to use the new bucket. ::: The following script creates the ECK-compatible secure settings secrets for S3 backup access in both regions. These secrets must exist before the Elasticsearch cluster is deployed, as the ECK Elasticsearch CRD references them for keystore injection at startup. ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/create-elasticsearch-secrets.sh ``` Save it as `create-elasticsearch-secrets.sh`, and execute it: ```bash chmod +x create-elasticsearch-secrets.sh ./create-elasticsearch-secrets.sh ``` ### Deploy the ECK operator and Elasticsearch clusters Before deploying the Elasticsearch cluster, install the ECK operator and its Custom Resource Definitions (CRDs) in both clusters. The ECK operator manages the lifecycle of Elasticsearch resources in Kubernetes. Run [deploy.sh](https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/elasticsearch/deploy.sh) from `generic/kubernetes/operator-based/elasticsearch/`: ```bash cd generic/kubernetes/operator-based/elasticsearch export ELASTICSEARCH_CLUSTER_FILE="elasticsearch-cluster-dual-region.yml" CAMUNDA_NAMESPACE=$CAMUNDA_NAMESPACE_0 KUBE_CONTEXT=$CLUSTER_0 ./deploy.sh CAMUNDA_NAMESPACE=$CAMUNDA_NAMESPACE_1 KUBE_CONTEXT=$CLUSTER_1 ./deploy.sh cd - ``` This performs the following actions: - Installs the ECK CRDs. - Deploys the operator to the `elastic-system` namespace. - Waits for operator readiness. - Creates the Elasticsearch cluster in both regions using the ECK operator.
Review the Elasticsearch deploy.sh script ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/elasticsearch/deploy.sh ```
The dual-region Elasticsearch cluster manifest is located at `generic/kubernetes/operator-based/elasticsearch/elasticsearch-cluster-dual-region.yml`.
Review the Elasticsearch cluster configuration ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/elasticsearch/elasticsearch-cluster-dual-region.yml ```
For more details on the ECK operator deployment, see the [operator-based infrastructure guide](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#elasticsearch-deployment). ### Synchronize Elasticsearch passwords across regions Each ECK-managed Elasticsearch cluster auto-generates its own `elasticsearch-es-elastic-user` secret with a unique password. For Zeebe exporters to authenticate against the remote region's Elasticsearch, each region needs access to the other region's password. The following script reads the ECK-generated passwords and creates region-specific secrets in both regions: - `elasticsearch-es-password-region-0`: password from region 0's Elasticsearch - `elasticsearch-es-password-region-1`: password from region 1's Elasticsearch These secrets are referenced by the Zeebe exporter configuration in the values files to authenticate against Elasticsearch in each region. ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/sync-elasticsearch-passwords.sh ``` Save it as `sync-elasticsearch-passwords.sh`, and execute it: ```bash chmod +x sync-elasticsearch-passwords.sh ./sync-elasticsearch-passwords.sh ``` ### Configure your deployment for each region Before deploying, some values in the value files need to be updated. To assist with generating these values, save the following Bash script as `generate-zeebe-helm-values.sh`:
Show `generate-zeebe-helm-values.sh` script reference ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/generate-zeebe-helm-values.sh ```
Then, source the output of the script. By doing so, we can reuse the values later for substitution, instead of manually adjusting the values files. The script derives the number of Zeebe Brokers from your Helm values. For a dual-region setup, the default is `8` (four brokers per region): ``` chmod +x generate-zeebe-helm-values.sh CLUSTER_0='local-cluster' source ./generate-zeebe-helm-values.sh ``` :::note Submariner service naming For those unfamiliar with the Submariner DNS convention, please consult the [official documentation](https://submariner.io/operations/usage/#service-discovery-for-services-deployed-to-multiple-clusters). In this deployment, we are utilizing the service discovery to have Elasticsearch and Zeebe brokers accessible from one cluster to another. Consequently, the values generated by the previous script will not be local services but use `svc.clusterset.local`. Make sure the variable `CLUSTER_0` is set to the name of your first cluster. In this example, the value `local-cluster` is used to maintain consistency with the previous [advanced cluster management step](#advanced-cluster-management). ::: #### Helm values Create `values-region-0.yml` and `values-region-1.yml` to store each region's configuration. These files will contain key-value pairs that will be substituted using `envsubst`. Throughout this guide, you will add and merge values into these files to configure your deployment according to your requirements. - Save the following file as both `values-region-0.yml` and `values-region-1.yml` to serve as the base configuration: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/helm-values/values-base.yml ``` :::warning Merging YAML files This guide references multiple configuration files that need to be merged into a single YAML file. Be cautious to avoid duplicate keys when merging the files. Additionally, pay close attention when copying and pasting YAML content. Ensure that the separator notation `---` does not inadvertently split the configuration into multiple documents. We strongly recommend double-checking your YAML file before applying it. You can use tools like [yamllint.com](https://www.yamllint.com/) or the [YAML Lint CLI](https://github.com/adrienverge/yamllint) if you prefer not to share your information online. ::: Set up the region ID using a unique integer for each region: - Add the following YAML configuration to `values-region-0.yml`: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/helm-values/values-region-0.yml ``` - Add the following YAML configuration to `values-region-1.yml`: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/helm-values/values-region-1.yml ``` **Security Context Constraints (SCCs)** The process of deploying applications in an OpenShift cluster can be influenced by its Security Context Constraints (SCCs) configuration. By default, OpenShift comes with more restrictive SCCs. For the purposes of this guide, which focuses on multi-region deployment, we assume this to be the standard setup. For custom configurations or specific requirements, please refer to the [installation guide for OpenShift](redhat-openshift.md#security-context-constraints-sccs) which details the various available SCC options. Additionally, Elasticsearch is managed using the ECK operator, so the orchestration cluster needs the **local** Elasticsearch connection details (URL and authentication) to read data. This is distinct from the cross-cluster Elasticsearch exporter URLs configured via environment variables in the [previous step](#configure-your-deployment-for-each-region), which handle **writing** data across regions via Submariner DNS. Merge the ECK overlay into your values files: - Add the following ECK Elasticsearch overlay configuration to both `values-region-0.yml` and `values-region-1.yml`: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/elasticsearch/camunda-elastic-values.yml ``` #### Fill your deployment with actual values Once you've prepared each region's values file (`values-region-0.yml` and `values-region-1.yml`), substitute the environment variables with their actual values: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/assemble-envsubst-values.sh ``` ### Install Camunda 8 using Helm With the value files for each region configured, you can now install Camunda 8 using Helm. Execute the following commands: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/install-chart.sh ``` This command: - Installs (or upgrades) Camunda using the Helm chart on each cluster. - Substitutes the appropriate version using the `$HELM_CHART_VERSION` environment variable. - Applies the configuration from the value file. :warning: **Installation is not complete yet:** at this stage, the two Camunda 8 deployments cannot communicate. You need to follow the next step to complete the installation. :::note This guide uses `helm upgrade --install` as it runs install on initial deployment and upgrades future usage. This may make it easier for future [Camunda 8 Helm upgrades](/self-managed/upgrade/helm/index.md) or any other component upgrades. ::: #### Export Camunda 8 services using Submariner Once Camunda is deployed across the two clusters, the next step is to expose each service to Submariner so it can be resolved by the other cluster. The following script exports all services for both clusters, then waits for the Submariner Lighthouse controller to create `ServiceImport` resources and propagate `*.svc.clusterset.local` DNS records. This wait is critical. Without it, Zeebe brokers may fail to discover cross-cluster peers during startup, leading to topology initialization failures. ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/export-services-submariner.sh ``` :::note The DNS propagation timeout defaults to 300 seconds and can be customized via the `DNS_WAIT_TIMEOUT` environment variable. ::: Alternatively, you can manage each service individually using the `ServiceExport` Custom Resource Definition (CRD). If you do so manually, ensure you wait for the corresponding `ServiceImport` resources to appear before proceeding.
Example of the ServiceExport manifest ```yaml apiVersion: multicluster.x-k8s.io/v1alpha1 kind: ServiceExport metadata: name: elasticsearch-es-http # name of the ECK-managed Elasticsearch service to export namespace: $CAMUNDA_NAMESPACE_0 # must match the namespace where the Elasticsearch Service exists ```
For each cluster, verify the status of the exported services with this script: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/verify-exported-services.sh ``` To monitor the progress of the installation, save and execute the following script: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/dual-region/procedure/check-deployment-ready.sh ``` Save it as `check-deployment-ready.sh`, make it executable, and run it: ```bash chmod +x check-deployment-ready.sh ./check-deployment-ready.sh ``` ## Verify connectivity to Camunda 8 :::info Authentication changes in 8.8+ Starting from version 8.8, the Orchestration Cluster is configured by default with [Admin](/self-managed/components/orchestration-cluster/admin/overview.md) and is protected by Basic authentication using `demo:demo` as the default username and password. ::: The following script will port-forward the Zeebe Gateway via `kubectl` from one of your clusters. Zeebe is stretching over both clusters and is `active-active`, meaning it doesn't matter which Zeebe Gateway to use to interact with your Zeebe cluster. ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/dual-region/procedure/check-zeebe-cluster-topology.sh ``` Make sure that your output contains all eight brokers from the two regions:
Example output ```json reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/dual-region/procedure/check-zeebe-cluster-topology-output.json ```
## Failover Consult the generic [dual-region failover procedure](/self-managed/deployment/helm/operational-tasks/dual-region-ops.md). ## Pitfalls to avoid For general deployment pitfalls, visit the [deployment troubleshooting guide](/self-managed/operational-guides/troubleshooting.md). --- ## Red Hat OpenShift Red Hat OpenShift, a Kubernetes distribution maintained by [Red Hat](https://www.redhat.com/en/technologies/cloud-computing/openshift), provides options for both managed and on-premises hosting. Deploying Camunda 8 on Red Hat OpenShift is supported using Helm, given the appropriate configurations. However, it's important to note that the [Security Context Constraints (SCCs)](#security-context-constraints-sccs) and [Routes](./redhat-openshift.md?current-ingress=openshift-routes#configuring-the-ingress) configurations might require slight deviations from the guidelines provided in the [general Helm deployment guide](/self-managed/deployment/helm/install/quick-install.md). Additional information and a high-level overview of Kubernetes as the upstream project is available on our [Kubernetes deployment reference](/self-managed/reference-architecture/kubernetes.md). ## Requirements - [Helm CLI v4](https://helm.sh/docs/intro/install/) (recommended; see [supported versions](/reference/supported-environments.md#clients)). - [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) to interact with the cluster. - [jq](https://jqlang.github.io/jq/download/) to interact with some variables. - [yq](https://github.com/mikefarah/yq/#install) to edit your `values.yml` file. - [GNU envsubst](https://www.man7.org/linux/man-pages/man1/envsubst.1.html) to generate manifests. - [oc (version supported by your OpenShift)](https://docs.openshift.com/container-platform/4.17/cli_reference/openshift_cli/getting-started-cli.html) to interact with OpenShift. - A namespace to host Camunda. - Permissions to install Kubernetes operators (cluster-admin or equivalent) for deploying the infrastructure services (Elasticsearch, PostgreSQL, Keycloak). These operators can also be installed via the [OpenShift OperatorHub](https://docs.openshift.com/container-platform/latest/operators/understanding/olm-understanding-operatorhub.html), but this guide installs them directly from source for full control over versions and configuration. For the tool versions used, check the [.tool-versions](https://github.com/camunda/camunda-deployment-references/blob/main/.tool-versions) file in the repository. It contains an up-to-date list of versions that we also use for testing. ## Architecture This section installs Camunda 8 following the architecture described in the [reference architecture](/self-managed/reference-architecture/reference-architecture.md). The architecture includes the following core components: - **Orchestration Cluster**: Core process execution engine (Zeebe, Operate, Tasklist, and Admin) - **Management plane**: Design and management tools (Camunda Hub and Management Identity) Infrastructure components are deployed using **official Kubernetes operators** as described in [Deploy infrastructure with Kubernetes operators](/self-managed/deployment/helm/configure/operator-based-infrastructure.md): - **[Elasticsearch with ECK](#deploy-elasticsearch)**: Document-store example path used in this guide for secondary storage - **[PostgreSQL with CloudNativePG](#deploy-postgresql)**: Deployed via [CloudNativePG](https://cloudnative-pg.io/) for Identity and Camunda Hub databases - **[Keycloak](#deploy-keycloak) (optional)**: Deployed via the [Keycloak Operator](https://www.keycloak.org/operator/installation) as an identity provider for Single Sign-On (SSO) For OpenShift deployments, the following OpenShift-specific configurations are also included: - **OpenShift Routes**: Native OpenShift way to expose services externally (alternative to standard Kubernetes Ingress) - **Security Context Constraints (SCCs)**: Security framework for controlling pod and container permissions ## Identity Provider (IdP) setup ## Deploy Camunda 8 via Helm charts ### Obtain a copy of the reference architecture All configuration files, deployment scripts, and Helm values referenced in this guide are available in the [Camunda deployment references](https://github.com/camunda/camunda-deployment-references) repository. ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/single-region/get-your-copy.sh ``` This places you at the repository root, from which both directories are accessible: - `generic/kubernetes/operator-based/` — operator deployment scripts (Elasticsearch, PostgreSQL, Keycloak) - `generic/openshift/single-region/` — OpenShift-specific Helm values and procedures ### Environment setup Source the environment variables required by the deployment scripts: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/0-set-environment.sh ``` :::note Ensure you source this file before running any deployment or configuration commands in the following sections. ::: ### Configure your deployment Start by copying the base Helm values file from the cloned repository into a working `values.yml` at the repository root: ```bash cp generic/openshift/single-region/helm-values/base.yml values.yml ``` This file contains key-value pairs that will be substituted using `envsubst`. Over this guide, you will merge additional overlays into this file to configure your deployment.
Review the base Helm values ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/single-region/helm-values/base.yml ```
:::danger Merging YAML files This guide references multiple configuration files that need to be merged into a single YAML file. Be cautious to avoid duplicate keys when merging the files. Additionally, pay close attention when copying and pasting YAML content. Ensure that the separator notation `---` does not inadvertently split the configuration into multiple documents. We strongly recommend double-checking your YAML file before applying it. You can use tools like [yamllint.com](https://www.yamllint.com/) or the [YAML Lint CLI](https://github.com/adrienverge/yamllint) if you prefer not to share your information online. ::: #### Configuring the Ingress Before exposing services outside the cluster, we need an Ingress component. Here's how you can configure it: :::danger Exposure of the Zeebe Gateway Service For production-grade security, keep the Zeebe Gateway on a private network with no publicly reachable route, and access it only from internal workloads or over a secure private connection. This limits the attack surface and keeps process and job traffic inside your trusted network boundary. ::: [Routes](https://docs.openshift.com/container-platform/latest/networking/routes/route-configuration.html) expose services externally by linking a URL to a service within the cluster. OpenShift supports both the [standard Kubernetes Ingress and routes](https://docs.openshift.com/container-platform/latest/networking/ingress-operator.html), giving cluster users the flexibility to choose. The presence of routes is rooted in their specification predating Ingress. The functionality of routes differs from Ingress; for example, unlike Ingress, routes don't allow multiple services to be linked to a single route or the use of paths. To use these routes for the Zeebe Gateway, configure this through Ingress as well. #### Setting Up the application domain for Camunda 8 The route created by OpenShift will use a domain to provide access to the platform. By default, you can use the OpenShift applications domain, but any other domain supported by the router can also be used. To retrieve the OpenShift applications domain (used as an example here), run the following command and define the route domain that will be used for the Camunda 8 deployment: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/single-region/procedure/setup-application-domain.sh ``` If you choose to use a custom domain instead, ensure it is supported by your router configuration and replace the example domain with your desired domain. For more details on configuring custom domains in OpenShift, refer to the official [custom domain OpenShift documentation](https://docs.openshift.com/dedicated/applications/deployments/osd-config-custom-domains-applications.html). #### Checking if HTTP/2 is enabled As the Zeebe Gateway also uses `gRPC` (which relies on `HTTP/2`), [HTTP/2 Ingress Connectivity must be enabled](https://docs.openshift.com/container-platform/latest/networking/ingress-operator.html#nw-http2-haproxy_configuring-ingress). To check if HTTP/2 is already enabled on your OpenShift cluster, run the following command: ```bash oc get ingresses.config/cluster -o json | jq '.metadata.annotations."ingress.operator.openshift.io/default-enable-http2"' ``` Alternatively, if you use a dedicated IngressController for the deployment: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/single-region/procedure/get-ingress-http2-status.sh ``` - If the output is `"true"`, it means HTTP/2 is enabled. - If the output is `null` or empty, HTTP/2 is not enabled.
Enable HTTP/2 If HTTP/2 is not enabled, you can enable it by running the following command: **IngressController configuration:** ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/single-region/procedure/enable-ingress-http2.sh ``` **Global cluster configuration:** ```bash oc annotate ingresses.config/cluster ingress.operator.openshift.io/default-enable-http2=true ``` This will add the necessary annotation to [enable HTTP/2 for Ingress in your OpenShift cluster](https://docs.openshift.com/container-platform/latest/networking/ingress-operator.html#nw-http2-haproxy_configuring-ingress) globally on the cluster.
#### Enable ALPN h2 on ROSA HCP These steps are required only on **Red Hat OpenShift Service on AWS – Hosted Control Planes (ROSA HCP)** managed clusters. Self-managed OpenShift clusters where the cluster-wide `ingress.operator.openshift.io/default-enable-http2=true` annotation is honored do **not** need this workaround. On ROSA HCP, the `ingress-config-validation.managed.openshift.io` admission webhook denies the cluster-wide annotation, and the per-`IngressController` annotation alone does not make HAProxy advertise ALPN `h2` on the default certificate path. As a result, gRPC clients fail to connect to the Zeebe Gateway, with `zbctl` reporting `credentials: cannot check peer: missing selected ALPN property`. The OpenShift router advertises ALPN `h2` on a per-SNI basis through a `crt-list` entry that HAProxy generates only for Routes that carry an explicit `spec.tls.certificate`. In other words, the gRPC Route must reference a TLS Secret in the Camunda namespace; the default `secretName: '-'` (Ingress-Operator-managed) is not enough on ROSA HCP. To fix this, copy the router default wildcard TLS Secret from `openshift-ingress` into the Camunda namespace, then point the gRPC Ingress to it: 1. Copy the router wildcard certificate Secret into the Camunda namespace: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/single-region/procedure/copy-router-tls-secret.sh ``` ```bash export CAMUNDA_NAMESPACE="${CAMUNDA_NAMESPACE:-camunda}" export CAMUNDA_PLATFORM_ROUTER_TLS_SECRET="camunda-platform-router-tls" ./generic/openshift/single-region/procedure/copy-router-tls-secret.sh ``` 2. After you have merged the OpenShift overlay into your local `values.yml` (see the _Configure Route TLS_ section below), override `orchestration.ingress.grpc.tls.secretName` in that `values.yml`. The default value `'-'` lets the Ingress Operator manage the certificate automatically; on ROSA HCP, replace it with the Secret you just created: ```bash yq -i ".orchestration.ingress.grpc.tls.secretName = \"$CAMUNDA_PLATFORM_ROUTER_TLS_SECRET\"" \ values.yml ``` This keeps the upstream `orchestration-route.yml` overlay file untouched so future updates can be pulled cleanly. After applying both steps, the auto-generated Route for the Zeebe gRPC Ingress will carry an inlined `spec.tls.certificate`, HAProxy will emit a per-SNI `[alpn h2,http/1.1]` `crt-list` entry, and gRPC clients will negotiate `h2` successfully. :::warning `copy-router-tls-secret.sh` copies the certificate as it exists at that moment. It does not track later changes. When the router wildcard certificate is rotated or replaced, the copy in the Camunda namespace goes stale. The Route keeps serving the old certificate, and gRPC clients fail once that certificate expires or is revoked. Re-run the script after any router certificate change, then confirm the Route picked the new certificate up: ```bash oc -n "$CAMUNDA_NAMESPACE" get route -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.tls.certificate}{"\n"}{end}' ``` Restarting the Camunda pods does not refresh this certificate and only causes downtime: the pods use the separate internal service certificate described below, not the Route one. To avoid the manual step entirely, manage the copy with a controller that keeps the two Secrets in sync. ::: #### Configure Route TLS Configure the Zeebe Gateway to use an encrypted TLS connection. In OpenShift, the connection from HAProxy to the Zeebe Gateway service can use HTTP/2 only for re-encrypt or pass-through routes, not for edge-terminated or insecure routes. 1. **Zeebe cluster:** at least one [TLS secret](https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets) is required for the Zeebe Gateway **service**; a second TLS secret for the **route** is optional and depends on your OpenShift flavor: - The first TLS secret is issued to the Zeebe Gateway Service Name. This must use the [PKCS #8 syntax](https://en.wikipedia.org/wiki/PKCS_8) or [PKCS #1 syntax](https://en.wikipedia.org/wiki/PKCS_1) as Zeebe only supports these, referenced as `camunda-platform-internal-service-certificate`. This certificate is also used in the other components such as Operate, Tasklist. In the example below, a TLS certificate is generated for the Zeebe Gateway service with an [annotation](https://docs.openshift.com/container-platform/latest/security/certificates/service-serving-certificate.html). The generated certificate will be in the form of a secret. Another option is [Cert Manager](https://docs.openshift.com/container-platform/latest/security/cert_manager_operator/index.html). For more details, review the [OpenShift documentation](https://docs.openshift.com/container-platform/latest/networking/routes/secured-routes.html#nw-ingress-creating-a-reencrypt-route-with-a-custom-certificate_secured-routes).
PKCS #8, PKCS #1 syntax PKCS #1 private key encoding. PKCS #1 produces a PEM block that contains the private key algorithm in the header and the private key in the body. A key that uses this can be recognised by its BEGIN RSA PRIVATE KEY or BEGIN EC PRIVATE KEY header. NOTE: This encoding is not supported for Ed25519 keys. Attempting to use this encoding with an Ed25519 key will be ignored and default to PKCS #8. PKCS #8 private key encoding. PKCS #8 produces a PEM block with a static header and both the private key algorithm and the private key in the body. A key that uses this encoding can be recognised by its BEGIN PRIVATE KEY header. [PKCS #1, PKCS #8 syntax definition from cert-manager](https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.PrivateKeyEncoding)
- The second TLS secret is optional and applies only to the exposed Route. By default, `orchestration-route.yml` ships with `orchestration.ingress.grpc.tls.secretName: '-'`, which lets the OpenShift Ingress Operator manage the Route TLS certificate automatically by using the cluster's default wildcard. This is the recommended setup on self-managed OpenShift. If you want to terminate the Route with your own custom certificate, for example, the same TLS secret used for Ingress, set `orchestration.ingress.grpc.tls.secretName` to the name of a TLS secret in the Camunda namespace. The Zeebe Gateway Ingress is configured as a [Re-encrypt Route](https://docs.openshift.com/container-platform/latest/networking/routes/route-configuration.html#nw-ingress-creating-a-route-via-an-ingress_route-configuration) in either case. On ROSA HCP, an explicit per-Route certificate is required for ALPN `h2` to work. See [Enable ALPN h2 on ROSA HCP](#enable-alpn-h2-on-rosa-hcp). To configure the orchestration cluster securely, it's essential to set up a secure communication configuration between pods: - We enable gRPC Ingress for the Zeebe Pod, which sets up a secure proxy that we'll use to communicate with the Zeebe cluster. To avoid conflicts with other services, we use a specific domain (`zeebe-$CAMUNDA_DOMAIN`) for the gRPC proxy, different from the one used by other services (`$CAMUNDA_DOMAIN`). We also note that the port used for gRPC is `443`. - We mount the **Service Certificate Secret** (`camunda-platform-internal-service-certificate`) to the Zeebe pod and configure a secure TLS connection. Merge the orchestration route overlay into your `values.yml` file: ```bash yq '. *+ load("generic/openshift/single-region/helm-values/orchestration-route.yml")' values.yml > values-merged.yml && mv values-merged.yml values.yml ```
Review the orchestration route configuration ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/single-region/helm-values/orchestration-route.yml ```
The actual configuration properties can be reviewed [in the Zeebe Gateway configuration documentation](/self-managed/components/orchestration-cluster/zeebe/configuration/gateway.md). 1. **Connectors:** merge the connectors route overlay: ```bash yq '. *+ load("generic/openshift/single-region/helm-values/connectors-route.yml")' values.yml > values-merged.yml && mv values-merged.yml values.yml ```
Review the connectors route configuration ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/single-region/helm-values/connectors-route.yml ```
The actual configuration properties can be reviewed [in the connectors configuration documentation](/self-managed/components/connectors/connectors-configuration.md#zeebe-broker-connection). 1. **TLS for internal applications:** Configure all other applications running inside the cluster and connecting to the Zeebe Gateway to also use TLS. 1. **Domain configuration:** Set up the global configuration to enable the single Ingress definition with the host. Merge the domain overlay: ```bash yq '. *+ load("generic/openshift/single-region/helm-values/domain.yml")' values.yml > values-merged.yml && mv values-merged.yml values.yml ```
Review the domain configuration ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/single-region/helm-values/domain.yml ```
[Routes](https://docs.openshift.com/container-platform/latest/networking/routes/route-configuration.html) serve as OpenShift's default Ingress implementation. If you find that its features aren't suitable for your needs, or if you prefer to use a Kubernetes-native Ingress controller, such as the [ingress-nginx controller](https://github.com/kubernetes/ingress-nginx), [you have that option](https://www.redhat.com/en/blog/a-guide-to-using-routes-ingress-and-gateway-apis-in-kubernetes-without-vendor-lock-in). For guidance on installing an Ingress controller, you can refer to the [Ingress Setup documentation](/self-managed/deployment/helm/configure/ingress/ingress-setup.md). :::note Difference between ingress-nginx and NGINX Ingress Do not confuse the [ingress-nginx controller](https://github.com/kubernetes/ingress-nginx) with the [NGINX Ingress Controller](https://www.redhat.com/en/blog/using-nginx-ingress-controller-red-hat-openshift) that is endorsed by Red Hat for usage with OpenShift. Despite very similar names, they are two different products. If you should decide to use the Red Hat endorsed [NGINX Ingress Controller](https://www.redhat.com/en/blog/using-nginx-ingress-controller-red-hat-openshift), you would require additional adjustments done to the Camunda 8 Ingress objects and the NGINX Ingress Controller itself to make `gRPC` and `HTTP/2` connections work. In that case, please refer to the [example and the prerequisites](https://github.com/nginxinc/kubernetes-ingress/blob/main/examples/ingress-resources/grpc-services/README.md). ::: If you do not have a domain name or do not intend to use one for your Camunda 8 deployment, external access to Camunda 8 web endpoints from outside the OpenShift cluster will not be possible. However, you can use `kubectl port-forward` to access Camunda without a domain name or Ingress configuration. For more information, refer to the [kubectl port-forward documentation](https://kubernetes.io/docs/reference/kubectl/generated/kubectl_port-forward/). To make this work, you will need to configure the deployment to reference `localhost` with the forwarded port. Merge the no-domain overlay into your `values.yml` file: ```bash yq '. *+ load("generic/openshift/single-region/helm-values/no-domain.yml")' values.yml > values-merged.yml && mv values-merged.yml values.yml ```
Review the no-domain configuration ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/single-region/helm-values/no-domain.yml ```
:::info Keycloak issuer and localhost hostname alignment When running without a domain using the Keycloak Operator, Console validates the JWT issuer claim against the configured Keycloak base URL. To keep token issuance consistent and avoid mismatches, the chart configuration sets Keycloak's hostname to its Kubernetes Service name when operating locally. This means that during port-forwarding you may need to map the service hostname to `127.0.0.1` so that browser redirects and token issuer values align. Add (or update) the following entry in your `/etc/hosts` file while developing locally: ```text 127.0.0.1 keycloak-service ``` After adding this entry, you can reach Keycloak at: `http://keycloak-service:18080/auth` **Why port `18080`?** The Keycloak Operator deploys the Keycloak service on port `18080`. We forward that port to the same local port (`18080`) to keep the JWT issuer URL consistent and avoid token validation mismatches. This constraint does not apply when a proper domain and Ingress are configured (the public FQDN is then used as the issuer and no hosts file changes are needed). :::
#### Configuring the Security Context Constraints Depending on your OpenShift cluster's Security Context Constraints (SCCs) configuration, the deployment process may vary. By default, OpenShift employs more restrictive SCCs. The Helm chart must assign `null` to the user running all components and dependencies. The `global.compatibility.openshift.adaptSecurityContext` variable in your values.yaml can be used to set the following possible values: - `force`: The `runAsUser` and `fsGroup` values will be null in all components. - `disabled`: The `runAsUser` and `fsGroup` values will not be modified (default). ```bash yq '. *+ load("generic/openshift/single-region/helm-values/scc.yml")' values.yml > values-merged.yml && mv values-merged.yml values.yml ```
Review the restrictive SCC configuration ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/single-region/helm-values/scc.yml ```
To use permissive SCCs, simply install the charts as they are. Follow the [general Helm deployment guide](/self-managed/deployment/helm/install/quick-install.md). ```bash yq '. *+ load("generic/openshift/single-region/helm-values/no-scc.yml")' values.yml > values-merged.yml && mv values-merged.yml values.yml ```
Review the permissive SCC configuration ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/single-region/helm-values/no-scc.yml ```
#### Enable Enterprise components Some components are not enabled by default in this deployment. For more information on how to configure and enable these components, refer to [configuring Enterprise components and connectors](/self-managed/deployment/helm/install/quick-install.md#configuring-enterprise-components-and-connectors). ### Deploy prerequisite services Before deploying Camunda, you need to deploy the infrastructure services it depends on. The core infrastructure (Elasticsearch and PostgreSQL) is deployed using Kubernetes operators as described in [Deploy infrastructure with Kubernetes operators](/self-managed/deployment/helm/configure/operator-based-infrastructure.md). Keycloak can optionally be deployed as your OIDC provider: - **Elasticsearch**: Deployed via [ECK (Elastic Cloud on Kubernetes)](https://www.elastic.co/guide/en/cloud-on-k8s/current/index.html) — one option for secondary storage - **PostgreSQL**: Deployed via [CloudNativePG](https://cloudnative-pg.io/) - **Keycloak** _(optional)_: Deployed via the [Keycloak Operator](https://www.keycloak.org/operator/installation) — can be replaced with any OIDC-compatible IdP All deploy scripts are located in `generic/kubernetes/operator-based/`. Review each script before executing to understand the deployment steps, and adapt the operator Custom Resource configurations for your specific requirements (resource limits, storage, replicas, etc.). :::note Working directory All commands in this guide assume you are at the **repository root** (the directory created by `get-your-copy.sh`). The deploy commands below use subshells `(cd ... && ./deploy.sh)` to preserve your working directory. ::: #### Deploy Elasticsearch {#deploy-elasticsearch} #### Deploy PostgreSQL {#deploy-postgresql} Deploy PostgreSQL clusters using the CloudNativePG operator: ```bash (cd generic/kubernetes/operator-based/postgresql && CLUSTER_FILTER="pg-identity,pg-hub" ./deploy.sh) ``` This script installs the CNPG operator (auto-detecting OpenShift to apply SCC patches), creates secrets, deploys the specified PostgreSQL clusters, and waits for readiness. The following PostgreSQL clusters are created: - **pg-identity**: Database for Camunda Identity component - **pg-hub**: Database for Camunda Hub (remove from configuration if not needed) If you use **RDBMS as the secondary storage** for the Orchestration Cluster instead of Elasticsearch, add `pg-camunda` to the filter: ```bash (cd generic/kubernetes/operator-based/postgresql && CLUSTER_FILTER="pg-identity,pg-hub,pg-camunda" ./deploy.sh) ``` - **pg-camunda**: Secondary storage for the Orchestration Cluster, defined in `postgresql-orchestration-cluster.yml`
Review the PostgreSQL cluster configuration ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/postgresql/postgresql-clusters.yml ```
For more details on the PostgreSQL deployment, see [PostgreSQL deployment in the operator-based infrastructure guide](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#postgresql-deployment). #### Deploy Keycloak (optional) {#deploy-keycloak} If you choose Keycloak as your identity provider (IdP), deploy it using the Keycloak Operator. First, deploy its PostgreSQL database, then deploy the Keycloak operator and instance. If you use an external OIDC provider instead, skip this section. Deploy Keycloak with OpenShift Routes: ```bash # Deploy the PostgreSQL database for Keycloak (cd generic/kubernetes/operator-based/postgresql && CLUSTER_FILTER="pg-keycloak" ./deploy.sh) # Deploy Keycloak export KEYCLOAK_CONFIG_FILE="keycloak-instance-domain-openshift.yml" (cd generic/kubernetes/operator-based/keycloak && ./deploy.sh) ```
Review the OpenShift Keycloak instance configuration ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/keycloak/keycloak-instance-domain-openshift.yml ```
Deploy Keycloak with nginx-ingress: ```bash # Deploy the PostgreSQL database for Keycloak (cd generic/kubernetes/operator-based/postgresql && CLUSTER_FILTER="pg-keycloak" ./deploy.sh) # Deploy Keycloak export KEYCLOAK_CONFIG_FILE="keycloak-instance-domain-nginx.yml" (cd generic/kubernetes/operator-based/keycloak && ./deploy.sh) ```
Review the nginx Keycloak instance configuration ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/keycloak/keycloak-instance-domain-nginx.yml ```
Deploy Keycloak without external access: ```bash # Deploy the PostgreSQL database for Keycloak (cd generic/kubernetes/operator-based/postgresql && CLUSTER_FILTER="pg-keycloak" ./deploy.sh) # Deploy Keycloak export KEYCLOAK_CONFIG_FILE="keycloak-instance-no-domain.yml" (cd generic/kubernetes/operator-based/keycloak && ./deploy.sh) ```
Review the no-domain Keycloak instance configuration ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/keycloak/keycloak-instance-no-domain.yml ```
For more details on the Keycloak deployment, see [Keycloak deployment in the operator-based infrastructure guide](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#keycloak-deployment). #### Merge operator overlays into values Once the operator-managed services are running, merge the corresponding Helm values overlays into your `values.yml` file. These overlays configure Camunda components to use the external operator-managed services. Merge the **Elasticsearch** overlay: ```bash yq '. *+ load("generic/kubernetes/operator-based/elasticsearch/camunda-elastic-values.yml")' values.yml > values-merged.yml && mv values-merged.yml values.yml ```
Review the Elasticsearch Helm overlay ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/elasticsearch/camunda-elastic-values.yml ```
If you use **RDBMS as the secondary storage**, skip the [Elasticsearch deployment](#deploy-elasticsearch) and the overlay above, and merge the **RDBMS** overlay instead: ```bash yq '. *+ load("generic/kubernetes/operator-based/postgresql/camunda-rdbms-values.yml")' values.yml > values-merged.yml && mv values-merged.yml values.yml ```
Review the RDBMS Helm overlay ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/postgresql/camunda-rdbms-values.yml ```
This overlay points the Orchestration Cluster at the `pg-camunda` cluster and disables Elasticsearch. Optimize requires Elasticsearch or OpenSearch, so it is disabled as well. Merge the **Identity PostgreSQL** overlay: ```bash yq '. *+ load("generic/kubernetes/operator-based/postgresql/camunda-identity-values.yml")' values.yml > values-merged.yml && mv values-merged.yml values.yml ```
Review the Identity PostgreSQL Helm overlay ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/postgresql/camunda-identity-values.yml ```
If **Camunda Hub** is enabled, also merge the **Camunda Hub PostgreSQL** overlay: ```bash yq '. *+ load("generic/kubernetes/operator-based/postgresql/camunda-hub-values.yml")' values.yml > values-merged.yml && mv values-merged.yml values.yml ```
Review the Camunda Hub PostgreSQL Helm overlay ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/postgresql/camunda-hub-values.yml ```
Merge the **Keycloak** overlay _(optional — only if Keycloak was deployed as your IdP; choose the appropriate variant for your setup)_: ```bash yq '. *+ load("generic/kubernetes/operator-based/keycloak/camunda-keycloak-domain-values.yml")' values.yml > values-merged.yml && mv values-merged.yml values.yml ```
Review the Keycloak domain Helm overlay ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/keycloak/camunda-keycloak-domain-values.yml ```
```bash yq '. *+ load("generic/kubernetes/operator-based/keycloak/camunda-keycloak-domain-values.yml")' values.yml > values-merged.yml && mv values-merged.yml values.yml ```
Review the Keycloak domain Helm overlay ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/keycloak/camunda-keycloak-domain-values.yml ```
```bash yq '. *+ load("generic/kubernetes/operator-based/keycloak/camunda-keycloak-no-domain-values.yml")' values.yml > values-merged.yml && mv values-merged.yml values.yml ```
Review the Keycloak no-domain Helm overlay ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/keycloak/camunda-keycloak-no-domain-values.yml ```
#### Fill your deployment with actual values If **Web Modeler** is enabled, create the SMTP secret: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/single-region/procedure/create-webmodeler-secret.sh ``` :::note Database and authentication secrets are automatically managed by the operators: - **PostgreSQL credentials**: Created by CloudNativePG via `set-secrets.sh` - **Keycloak admin credentials** _(optional)_: Created by the Keycloak Operator - **Elasticsearch credentials**: Created by ECK - **Identity secrets**: Created by the operator-based deployment scripts Only the SMTP password for Web Modeler needs to be created manually. ::: Once you've prepared the `values.yml` file with all overlays merged, run the following `envsubst` command to substitute the environment variables with their actual values: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/single-region/procedure/assemble-envsubst-values.sh ``` ### Install Camunda 8 using Helm Now that the `generated-values.yml` is ready, you can install Camunda 8 using Helm. The following are the required environment variables with some example values: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/single-region/procedure/chart-env.sh ``` - `CAMUNDA_NAMESPACE` is the Kubernetes namespace where Camunda will be installed. The script sets it to `camunda`. If you use another namespace, for example the one you exported in [Enable ALPN h2 on ROSA HCP](#enable-alpn-h2-on-rosa-hcp), set that value here, or export it again after you run the script. - `CAMUNDA_RELEASE_NAME` is the name of the Helm release associated with this Camunda installation. Then run the following command: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/openshift/single-region/procedure/install-chart.sh ``` This command: - Installs (or upgrades) Camunda using the Helm chart. - Substitutes the appropriate version using the `$CAMUNDA_HELM_CHART_VERSION` environment variable. - Applies the configuration from `generated-values.yml`. You can track the progress of the installation with the deployment readiness check script, which requires [jq](https://jqlang.github.io/jq/) to be installed. ## Verify connectivity to Camunda 8 First, we need an OAuth client to be able to connect to the Camunda 8 cluster. ### Generate an M2M token using Identity Generate an M2M token by following the steps outlined in the [Identity getting started guide](/self-managed/components/management-identity/overview.md), along with the [incorporating applications documentation](/self-managed/components/management-identity/application-user-group-role-management/applications.md). Below is a summary of the necessary instructions: 1. Open Identity in your browser at `https://${CAMUNDA_DOMAIN}/managementidentity`. You will be redirected to your IdP and prompted to log in. 2. Log in with the initial user `admin`. This username is defined by the `identity.firstUser.username` value in your Helm chart configuration. Retrieve the auto-generated password from the Kubernetes secret: ```shell kubectl get secret identity-secret-for-components \ --namespace "$CAMUNDA_NAMESPACE" \ -o jsonpath='{.data.identity-first-user-password}' | base64 -d; echo ``` 3. Select **Add application** and select **M2M** as the type. Assign a name like "test." 4. Select the newly created application. Then, select **Access to APIs > Assign permissions**, and select the **Orchestration API** with "read" and "write" permission. 5. Retrieve the `client-id` and `client-secret` values from the application details ```shell export ZEEBE_CLIENT_ID='client-id' # retrieve the value from the identity page of your created m2m application export ZEEBE_CLIENT_SECRET='client-secret' # retrieve the value from the identity page of your created m2m application ``` 6. Open the Orchestration Cluster Admin in your browser at `https://${CAMUNDA_DOMAIN}/admin` and log in with the user `admin` (defined in `identity.firstUser` of the values file). 7. In the Admin navigation menu, select **Roles**. 8. Either select an existing role (for example, **Admin**) or [create a new role](/components/admin/role.md) with the appropriate permissions for your use case. 9. In the selected role view, open the **Clients** tab and click **Assign client**. 10. Enter the client ID of your application created in Management Identity (for example, `test`) and click **Assign client** to save. This operation links the OIDC client to the role's permissions in the Orchestration Cluster, granting the application access to the cluster resources. For more information about managing roles and clients, see [Roles](/components/admin/role.md#manage-clients). Admin and the Orchestration cluster must be port-forwarded to be able to connect to the cluster. If using Keycloak via the Keycloak Operator, you also need to port-forward the Keycloak service. ```shell kubectl port-forward "services/$CAMUNDA_RELEASE_NAME-identity" 8085:80 --namespace "$CAMUNDA_NAMESPACE" kubectl port-forward "services/$CAMUNDA_RELEASE_NAME-zeebe-gateway" 8080:8080 --namespace "$CAMUNDA_NAMESPACE" # If using Keycloak Operator: kubectl port-forward "services/keycloak-service" 18080:18080 --namespace "$CAMUNDA_NAMESPACE" ``` 1. Open Identity in your browser at `http://localhost:8085/managementidentity`. You will be redirected to your IdP and prompted to log in. 2. Log in with the initial user `admin`. This username is defined by the `identity.firstUser.username` value in your Helm chart configuration. Retrieve the auto-generated password from the Kubernetes secret: ```shell kubectl get secret identity-secret-for-components \ --namespace "$CAMUNDA_NAMESPACE" \ -o jsonpath='{.data.identity-first-user-password}' | base64 -d; echo ``` 3. Select **Add application** and select **M2M** as the type. Assign a name like "test." 4. Select the newly created application. Then, select **Access to APIs > Assign permissions**, and select the **Orchestration API** with "read" and "write" permission. 5. Retrieve the `client-id` and `client-secret` values from the application details ```shell export ZEEBE_CLIENT_ID='client-id' # retrieve the value from the identity page of your created m2m application export ZEEBE_CLIENT_SECRET='client-secret' # retrieve the value from the identity page of your created m2m application ``` 6. Open the Orchestration Cluster Admin in your browser at `http://localhost:8080/admin` and log in with the user `admin` (defined in `identity.firstUser` of the values file). 7. In the Admin navigation menu, select **Roles**. 8. Either select an existing role (for example, **Admin**) or [create a new role](/components/admin/role.md) with the appropriate permissions for your use case. 9. In the selected role view, open the **Clients** tab and click **Assign client**. 10. Enter the client ID of your application created in Management Identity (for example, `test`) and click **Assign client** to save. This operation links the OIDC client to the role's permissions in the Orchestration Cluster, granting the application access to the cluster resources. For more information about managing roles and clients, see [Roles](/components/admin/role.md#manage-clients). ### Use the token For a detailed guide on generating and using a token, consult the relevant documentation on [authenticating with the Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md). Export the following environment variables: ```shell reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/export-verify-zeebe-domain.sh ``` This requires to port-forward the Zeebe Gateway to be able to connect to the cluster. ```shell kubectl port-forward "services/$CAMUNDA_RELEASE_NAME-zeebe-gateway" 8080:8080 --namespace "$CAMUNDA_NAMESPACE" ``` Export the following environment variables: ```shell reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/export-verify-zeebe-local.sh ``` Generate a temporary token to access the Orchestration Cluster REST API, then capture the value of the `access_token` property and store it as your token. Use the stored token (referred to as `TOKEN` in this case) to interact with the Orchestration Cluster REST API and display the cluster topology: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/check-zeebe-cluster-topology.sh ``` ...and results in the following output:
Example output ```json reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/single-region/procedure/check-zeebe-cluster-topology-output.json ```
Follow our existing [Modeler guide on deploying a diagram](/self-managed/components/modeler/desktop-modeler/deploy-to-self-managed.md). Below are the helper values required to be filled in Modeler: The following values are required for the OAuth authentication: - **Cluster endpoint:** `https://zeebe-$CAMUNDA_DOMAIN`, replacing `$CAMUNDA_DOMAIN` with your domain - **Client ID:** Retrieve the client ID value from the identity page of your created M2M application - **Client Secret:** Retrieve the client secret value from the Identity page of your created M2M application - **OAuth Token URL:** Your IdP's token endpoint (for example, `https://$CAMUNDA_DOMAIN/auth/realms/camunda-platform/protocol/openid-connect/token` when using Keycloak), replacing `$CAMUNDA_DOMAIN` with your domain - **Audience:** `orchestration-api`, the default for Camunda 8 Self-Managed This requires port-forwarding the Zeebe Gateway to be able to connect to the cluster. Desktop Modeler communicates via gRPC, so only port `26500` needs to be forwarded. If you also need REST API access (port `8080`), refer to the [REST API section above](#use-the-token). ```shell kubectl port-forward "services/$CAMUNDA_RELEASE_NAME-zeebe-gateway" 26500:26500 --namespace "$CAMUNDA_NAMESPACE" ``` The following values are required for OAuth authentication: - **Cluster endpoint:** `http://localhost:26500` - **Client ID:** Retrieve the client ID value from the identity page of your created M2M application - **Client Secret:** Retrieve the client secret value from the Identity page of your created M2M application - **OAuth Token URL:** Your IdP's token endpoint (for example, `http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token` when using Keycloak with port-forwarding) - **Audience:** `orchestration-api`, the default for Camunda 8 Self-Managed
## Pitfalls to avoid For general deployment pitfalls, visit the [deployment troubleshooting guide](/self-managed/operational-guides/troubleshooting.md). ### Persistent volume reclaim policy OpenShift StorageClasses often default to a `Delete` reclaim policy, which means persistent volume data is permanently deleted when a PVC is removed. This can lead to complete and unrecoverable data loss for Orchestration Cluster brokers. Ensure your StorageClass uses a `Retain` reclaim policy for production deployments. Verify your configuration: ```bash oc get storageclass # RECLAIMPOLICY should show "Retain", not "Delete" ``` For more details, including accepted alternatives to a cluster-wide `Retain` policy, see the [production install guide](/self-managed/deployment/helm/install/production/index.md#persistent-volume-reclaim-policy). ### Security Context Constraints (SCCs) [Security Context Constraints (SCCs)](https://docs.openshift.com/container-platform/latest/authentication/managing-security-context-constraints.html) are a set of conditions that a pod must adhere to in order to be accepted into the system. They define the security conditions under which a pod operates. Similar to how roles control user permissions, SCCs regulate the permissions of deployed applications, both at the pod and container level. It's generally recommended to deploy applications with the most restrictive SCCs possible. If you're unfamiliar with security context constraints, you can refer to the [OpenShift documentation](https://docs.openshift.com/container-platform/latest/authentication/managing-security-context-constraints.html). #### Restrictive SCCs The following represents the most restrictive SCCs that can be used to deploy Camunda 8. Note that in OpenShift 4.10, these are equivalent to the built-in `restricted` SCCs (which are the default SCCs). ```yaml Allow Privileged: false Default Add Capabilities: Required Drop Capabilities: KILL, MKNOD, SYS_CHROOT, SETUID, SETGID Allowed Capabilities: Allowed Seccomp Profiles: Allowed Volume Types: configMap, downwardAPI, emptyDir, persistentVolumeClaim, projected, secret Allow Host Network: false Allow Host Ports: false Allow Host PID: false Allow Host IPC: false Read Only Root Filesystem: false Run As User Strategy: MustRunAsRange SELinux Context Strategy: MustRunAs FSGroup Strategy: MustRunAs Supplemental Groups Strategy: RunAsAny ``` When using these SCCs, be sure not to specify _any_ `runAsUser` or `fsGroup` values in either the pod or container security context. Instead, allow OpenShift to assign arbitrary IDs. :::note If you are providing the ID ranges yourself, you can also configure the `runAsUser` and `fsGroup` values accordingly. ::: The Camunda Helm chart can be deployed to OpenShift with a few modifications, primarily revolving around your desired security context constraints. #### Non-root SCCs If you intend to deploy Camunda 8 while restricting applications from running as root (e.g., using the `nonroot` built-in SCCs), you'll need to configure each pod and container to run as a non-root user. For example, when deploying Zeebe using a stateful set, you would include the following YAML, replacing `1000` with the desired user ID and `` with the name of the container: ```yaml spec: template: spec: securityContext: runAsUser: 1000 containers: - name: securityContext: runAsUser: 1000 ``` :::note As the container user in OpenShift is always part of the root group, defining a `fsGroup` for any Camunda 8 application pod security context is unnecessary. ::: This configuration is necessary for all Camunda 8 applications, as well as related ones (e.g., Keycloak, PostgreSQL, etc.). It's particularly crucial for stateful applications that will write to persistent volumes, but it's also generally a good security practice. #### Permissive SCCs If you deploy Camunda 8 (and related infrastructure) with permissive SCCs out of the box, there's nothing specific for you to configure. Here, permissive SCCs refer to those where the strategy for `RunAsUser` is defined as `RunAsAny` (including root). ### Writing pod permissions for logs OpenShift security policies often restrict writing to files within containers. This can cause Camunda pods to fail to write to the filesystem, which is typically required for writing log in files. Instead, we configure the environment to output logs to `stdout` and `stderr` only, which are supported by OpenShift logging infrastructure. For Camunda components (except Identity), this can be done by setting the environment variable in the chart values: ```yaml zeebe/tasklist/operate/etc: env: - name: CAMUNDA_LOG_FILE_APPENDER_ENABLED value: "false" ``` This will disable the file appender and ensure logs are visible via the container's log output. --- ## Add custom Kubernetes manifests in Helm charts Add extra Kubernetes manifests to the Camunda 8 [Helm chart](/self-managed/deployment/helm/install/quick-install.md) by defining them in the `values.yaml` file. Use this to include resources such as ConfigMaps, Deployments, or Services. ## Configuration Define extra manifests in your `values.yaml` file under `global.extraManifests`. The key accepts a list of Kubernetes manifests. ```yaml global: extraManifests: - | apiVersion: v1 kind: ConfigMap metadata: name: example-cm-one data: test: test-one - | apiVersion: v1 kind: ConfigMap metadata: name: example-cm-two data: test: test-two ``` For more information, see the Kubernetes [object documentation](https://kubernetes.io/docs/concepts/overview/working-with-objects/). ### Manipulate manifests If you need to adjust rendered manifests directly (for example, when the chart template does not support a feature), use [Helm Post Rendering](https://helm.sh/docs/topics/advanced/#post-rendering). Post rendering lets you manipulate, configure, or validate manifests before Helm installs them. Use post rendering for quick workarounds, but also consider raising a feature request for your use case. ## Best practices - **Keep it simple**: Use clear and concise YAML syntax to avoid complexity. - **Use comments**: Include comments in your YAML file to explain the purpose of each manifest. - **Test thoroughly**: Ensure that all added manifests are correctly formatted and functional before deployment. ## Troubleshooting ### `helm install` or `helm upgrade` fails with a YAML parsing error **Observed behavior:** Helm fails before it starts applying resources, with an error about parsing the values file. **Why this happens:** The manifest string under `global.extraManifests` has invalid YAML syntax, most often an indentation issue or a missing colon in a key-value pair, which breaks the outer `values.yaml` structure. **How to fix:** Validate the manifest's YAML syntax on its own (for example, with `yamllint` or `kubectl apply --dry-run=client -f ` against a copy saved to a file), then correct the indentation or missing syntax. ### Helm succeeds, but the resource isn't created or the API server rejects it **Observed behavior:** `helm install` or `helm upgrade` completes, but the extra manifest's resource doesn't appear in the cluster, or `kubectl` shows a validation error for it. **Why this happens:** The manifest is valid YAML, but not a valid Kubernetes object, for example, an incorrect `apiVersion` or `kind`, or a missing required field. **How to fix:** Save the manifest to a file and validate it against the Kubernetes API without applying it: ```bash kubectl apply --dry-run=server -f ``` --- ## Configure component configuration This page explains how to configure Camunda components in Helm charts. For most use cases, use `.extraConfiguration` to add or override properties while keeping the chart-provided defaults. Use `.configuration` only when you intentionally want to replace the entire default application configuration file. For the complete list of configuration options per component, see the [Self-Managed Components documentation](../../../components/orchestration-cluster/overview.md) (this is where each component documents its supported application configuration). ## Prerequisites - A deployed Camunda Helm chart release. - Access to the `values.yaml` file. - Basic understanding of Spring Boot configuration (`application.yaml` or `application.properties`). ## Configuration ### Parameters | Key | Type | Description | | ------------------------------------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `.extraConfiguration` | list | **Recommended:** Additional configuration entries layered on top of the default configuration. Each entry has a `file` (filename), `content` (file contents), and optionally `springImport` (boolean, default `true`). See [how it works per component](#how-extraconfiguration-works-per-component). | | `.configuration` | string | **Advanced:** Full application configuration file content (for example, the full contents of `application.yaml`). Using this **replaces** the component's default application configuration. | ### Configuration options Two Helm values are available for component configuration: - `.extraConfiguration` - `.configuration` :::tip Which should I use? - Use `.extraConfiguration`. It keeps the chart-provided defaults intact and lets you add/override only the keys you need. - Use `.configuration` only if you intentionally want to take full control of the application's configuration file. It **overwrites** the default config and can affect startup behavior and upgrades. ::: #### componentName.extraConfiguration Use `.extraConfiguration` to add or override configuration **without replacing** the component's default configuration. This option accepts an **ordered list** of entries, where each entry specifies a `file` name and its `content`. Entries are processed in order, so **later entries override earlier ones** for duplicate keys — mimicking Spring Boot's `spring.config.import` semantics. :::info Recommended default Start with `.extraConfiguration` whenever possible. It is safer for upgrades because the Helm chart can continue to evolve its defaults while you only maintain the deltas you actually care about. ::: ```yaml identity: extraConfiguration: - file: logging-debug.yaml content: | logging: level: ROOT: DEBUG io.camunda.identity: DEBUG - file: logging-production.yaml content: | logging: level: ROOT: INFO io.camunda.identity: INFO ``` In this example, `logging-production.yaml` is applied after `logging-debug.yaml`, so the final effective log level is `INFO` (last writer wins). :::note Why an ordered list? Previous versions of the Helm chart used a map (key-value pairs) for `extraConfiguration`. Maps in Go (and Helm) do not guarantee iteration order. Since configuration layering is order-dependent, `extraConfiguration` now uses an array to ensure entries are always applied in the order you define them. ::: #### componentName.configuration Use `.configuration` to define an application configuration file directly in `values.yaml`. :::caution Advanced option (overwrites defaults) When you set `.configuration`, the Helm chart renders **your content as the entire application configuration file** for that component. This means: - You are responsible for including any settings that the chart normally provides by default. - During upgrades, configuration format or default changes may require you to update your configuration before the component can start. Prefer `.extraConfiguration` unless you specifically need to replace the full file. ::: For example, `application.yaml`: ```yaml orchestration: configuration: |- camunda: # Orchestration cluster settings database: data: snapshot-period: "5m" primary-storage: disk: free-space: processing: "2GB" replication: "1GB" secondary-storage: autoconfigure-camunda-exporter: true type: "elasticsearch" elasticsearch: url: "http://camunda-elasticsearch:9200" cluster-name: "elasticsearch" username: "" password: "${VALUES_ELASTICSEARCH_PASSWORD:}" index-prefix: "" number-of-replicas: "1" ``` ### Default properties The `helm template` command can show you the application's default configuration as rendered by the chart. - Use this output as a **reference** to discover the right keys and defaults. - Only copy the full content into `.configuration` if you intentionally want to **replace** the default config (advanced). Keep the original `values.yaml` unchanged and maintain a separate file with your custom settings. For details, see [Creating your own values files](/self-managed/deployment/helm/chart-parameters.md#creating-your-own-values-files). To generate the default configuration, replace `` with your release name and run: ```bash helm template \ -f values.yaml \ camunda/camunda-platform \ --show-only templates/operate/configmap.yaml ``` The `--show-only` flag prints the `configmap`. - If you are using `.extraConfiguration`, use the rendered `application.yml` to identify the correct keys and then add only the overrides you need. - If you are using `.configuration`, copy the full `application.yml` content, modify it, and place it under the appropriate `.configuration` key in `values.yaml`. ### How extraConfiguration works per component Camunda components use different runtimes and configuration mechanisms. The Helm chart handles `extraConfiguration` differently for each, so the user-facing `values.yaml` API remains consistent while the underlying behavior adapts to what each application actually supports. #### Spring Boot components **Applies to:** Identity, Connectors, Orchestration, Web Modeler REST API Spring Boot components support loading multiple configuration files via `spring.config.import`. Each `extraConfiguration` entry is mounted as an **individual file** in the container's config directory and imported by Spring at startup. Order is preserved by the array in the values.yaml. Spring applies files in import order, with later files overriding earlier ones. ```yaml identity: extraConfiguration: - file: custom-logging.yaml content: | logging: level: ROOT: DEBUG - file: custom-cache.yaml content: | spring: cache: type: caffeine ``` Both files are mounted and imported separately. No merging happens at Helm template time. ##### Excluding files from `spring.config.import` By default, every `extraConfiguration` entry is added to Spring Boot's `spring.config.import` property. This works for Spring-compatible files (YAML, properties) but causes Spring Boot to fail on startup when non-Spring files (such as Log4j2 XML configs) are provided. To mount a file into the container **without** adding it to `spring.config.import`, set `springImport: false` on the entry: ```yaml connectors: extraConfiguration: - file: log4j2.xml springImport: false content: |- ... - file: custom-application.yaml content: | my.custom.property: value ``` In this example, `log4j2.xml` is mounted in the config directory but not imported by Spring. `custom-application.yaml` is both mounted and imported (the default behavior when `springImport` is omitted or set to `true`). #### Node.js Components **Applies to:** Console Console is a Node.js application that reads only two configuration files: `application.yaml` (the main config) and `application-override.yaml` (a single override file). It does not support loading multiple override files. Because of this constraint, the Helm chart **merges all `extraConfiguration` entries at template rendering time** into a single `application-override.yaml` using deep merge. Later entries override earlier ones for duplicate keys. ```yaml console: extraConfiguration: - file: feature-flags.yaml content: | camunda: console: features: newDashboard: true - file: logging.yaml content: | camunda: console: logging: level: INFO ``` The Helm chart merges both entries and renders a single `application-override.yaml` in the ConfigMap: ```yaml # Rendered application-override.yaml camunda: console: features: newDashboard: true logging: level: INFO ``` #### Custom configuration loading **Applies to:** Optimize Optimize uses its own configuration loader (not standard Spring Boot conventions). It reads only two files: `environment-config.yaml` (main config) and `application-ccsm.yaml` (Identity/auth config, loaded when the `ccsm` Spring profile is active). It does **not** scan its config directory for additional files. Starting with **Camunda 8.9**, Optimize also loads any additional configuration files that are referenced via Spring's `spring.config.import` or `spring.config.location` properties. This allows you to use `optimize.extraConfiguration` to configure Optimize-native settings in addition to Spring-only settings. In the Helm chart, each `optimize.extraConfiguration` entry is rendered as a separate file in the Optimize config directory. Spring imports these files in the order you define them, and Optimize's configuration loader applies them in the **same order**. For duplicate keys, later entries override earlier ones. ```yaml optimize: extraConfiguration: - file: custom-zeebe.yaml content: | zeebe: partitionCount: 6 - file: custom-es.yaml content: | es: connection: nodes: - host: "custom-es-host" httpPort: 9200 ``` Both files remain separate in the Optimize config directory and are loaded at runtime in the order you define. :::caution The `content` must be valid YAML. If invalid YAML is provided, Helm will fail during template rendering with a parse error. This is intentional and prevents deploying a broken configuration. ::: ### Summary | Component | Runtime | Config format | How `extraConfiguration` is applied | | --------------------- | ------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Identity | Spring Boot | YAML | Individual files mounted, imported via `spring.config.import` (use `springImport: false` to skip import) | | Connectors | Spring Boot | YAML | Individual files mounted, imported via `spring.config.import` (use `springImport: false` to skip import) | | Orchestration Cluster | Spring Boot | YAML | Individual files mounted, imported via `spring.config.import` (use `springImport: false` to skip import) | | Web Modeler REST API | Spring Boot | YAML | Individual files mounted, imported via `spring.config.import` (use `springImport: false` to skip import) | | Console | Node.js | YAML | Merged at template time into single `application-override.yaml` | | Optimize | Java (custom) | YAML | Loaded at runtime from `environment-config.yaml` plus any files imported via `spring.config.import` / `spring.config.location` (rendered from `optimize.extraConfiguration`); later imports override earlier ones. | ## Practical example: migrating from environment variables to a configuration file This example shows how to convert a Zeebe backup configuration from environment variables to the `application.yaml` file format. ### Step 1: Review the existing environment variable configuration To configure Zeebe backups, earlier charts required environment variables: ```yaml zeebe: clusterSize: "1" enabled: true partitionCount: "1" replicationFactor: "1" env: - name: ZEEBE_BROKER_DATA_BACKUP_S3_BUCKETNAME value: zeebebackuptest - name: ZEEBE_BROKER_DATA_BACKUP_S3_REGION value: us-east-1 - name: ZEEBE_BROKER_DATA_BACKUP_STORE value: S3 - name: ZEEBE_BROKER_DATA_BACKUP_S3_ENDPOINT value: http://loki-minio.monitoring.svc.cluster.local:9000 - name: ZEEBE_BROKER_DATA_BACKUP_S3_ACCESSKEY value: supersecretaccesskey - name: ZEEBE_BROKER_DATA_BACKUP_S3_SECRETKEY value: supersecretkey - name: ZEEBE_BROKER_DATA_BACKUP_S3_APICALLTIMEOUT value: PT180S - name: ZEEBE_BROKER_DATA_BACKUP_S3_BASEPATH value: zeebebackup ``` ### Step 2: Generate the default configuration file Replace `` with your release name, then run the following command to render the default configuration file and fill in Helm values: ```bash helm template \ -f values.yaml \ camunda/camunda-platform \ --show-only templates/zeebe/configmap.yaml ``` The output includes an `application.yml` section similar to: ```yaml zeebe: broker: exporters: elasticsearch: className: "io.camunda.zeebe.exporter.ElasticsearchExporter" args: url: "http://RELEASE-elasticsearch:9200" index: prefix: "zeebe-record" gateway: enable: true network: port: 26500 security: enabled: false authentication: mode: none network: host: 0.0.0.0 commandApi: port: 26501 internalApi: port: 26502 monitoringApi: port: "9600" cluster: clusterSize: "1" replicationFactor: "1" partitionsCount: "1" clusterName: RELEASE-zeebe threads: cpuThreadCount: "3" ioThreadCount: "3" ``` ### Step 3: Map environment variables to configuration properties For each environment variable, find the corresponding property in the [Zeebe configuration](/self-managed/components/orchestration-cluster/zeebe/configuration/broker.md). For example, the environment variable `ZEEBE_BROKER_DATA_BACKUP_S3_BUCKETNAME` maps to the legacy property `zeebe.broker.data.backup.s3.bucketName`. The current name of this property, `camunda.data.primary-storage.backup.s3.bucket-name`, is documented under [Zeebe S3 Backup](/self-managed/components/orchestration-cluster/zeebe/configuration/broker.md#camundadataprimary-storagebackups3). Add the property to the configuration file. This example keeps the legacy `zeebe.broker` tree used in the previous steps, so add the `data` section under `zeebe.broker`: ```yaml zeebe: broker: data: backup: s3: bucketName: "zeebebackuptest" exporters: elasticsearch: className: "io.camunda.zeebe.exporter.ElasticsearchExporter" args: url: "http://RELEASE-elasticsearch:9200" index: prefix: "zeebe-record" gateway: enable: true network: port: 26500 security: enabled: false authentication: mode: none network: host: 0.0.0.0 commandApi: port: 26501 internalApi: port: 26502 monitoringApi: port: "9600" cluster: clusterSize: "1" replicationFactor: "1" partitionsCount: "1" clusterName: RELEASE-zeebe threads: cpuThreadCount: "3" ioThreadCount: "3" ``` ### Step 4: Repeat for all environment variables Follow the same process for each environment variable. The resulting configuration looks like this: ```yaml zeebe: broker: data: backup: store: "S3" s3: bucketName: "zeebebackuptest" region: "us-east-1" endpoint: "http://loki-minio.monitoring.svc.cluster.local:9000" accessKey: "supersecretaccesskey" secretKey: "supersecretkey" apiCallTimeout: "PT180S" basePath: "zeebebackup" exporters: elasticsearch: className: "io.camunda.zeebe.exporter.ElasticsearchExporter" args: url: "http://RELEASE-elasticsearch:9200" index: prefix: "zeebe-record" gateway: enable: true network: port: 26500 security: enabled: false authentication: mode: none network: host: 0.0.0.0 commandApi: port: 26501 internalApi: port: 26502 monitoringApi: port: "9600" cluster: clusterSize: "1" replicationFactor: "1" partitionsCount: "1" clusterName: RELEASE-zeebe threads: cpuThreadCount: "3" ioThreadCount: "3" ``` ### Step 5: Add the configuration to `values.yaml` Prefer adding the new settings via `zeebe.extraConfiguration` so you only maintain the keys you changed and keep the chart-provided defaults. For example: ```yaml zeebe: extraConfiguration: - file: backup-s3.yaml content: |- zeebe: broker: data: backup: store: "S3" s3: bucketName: "zeebebackuptest" region: "us-east-1" endpoint: "http://loki-minio.monitoring.svc.cluster.local:9000" accessKey: "supersecretaccesskey" secretKey: "supersecretkey" apiCallTimeout: "PT180S" basePath: "zeebebackup" ``` :::caution Advanced alternative: `zeebe.configuration` overwrites defaults If you intentionally want to fully control Zeebe's `application.yml`, place the full configuration under `zeebe.configuration`. This replaces the chart's default configuration and may require updates during upgrades. ::: ```yaml zeebe: configuration: |- zeebe: broker: data: backup: store: "S3" s3: bucketName: "zeebebackuptest" region: "us-east-1" endpoint: "http://loki-minio.monitoring.svc.cluster.local:9000" accessKey: "supersecretaccesskey" secretKey: "supersecretkey" apiCallTimeout: "PT180S" basePath: "zeebebackup" exporters: elasticsearch: className: "io.camunda.zeebe.exporter.ElasticsearchExporter" args: url: "http://RELEASE-elasticsearch:9200" index: prefix: "zeebe-record" # Example: exporter-side filters for Optimize (Camunda 8.9+) # bpmnProcessIdExclusion: # - technicalProcess # variableNameInclusionStartWith: # - businessTotal gateway: enable: true network: port: 26500 security: enabled: false authentication: mode: none network: host: 0.0.0.0 commandApi: port: 26501 internalApi: port: 26502 monitoringApi: port: "9600" cluster: clusterSize: "1" replicationFactor: "1" partitionsCount: "1" clusterName: RELEASE-zeebe threads: cpuThreadCount: "3" ioThreadCount: "3" ``` The commented `variable-name` and `bpmn-process-id` sections above only illustrate where to configure exporter-side filters for Optimize in Camunda 8.9 and later. For the complete list of available options, their semantics, and upgrade behavior, see: - [Elasticsearch exporter](/self-managed/components/orchestration-cluster/zeebe/exporters/elasticsearch-exporter.md) - [OpenSearch exporter](/self-managed/components/orchestration-cluster/zeebe/exporters/opensearch-exporter.md) ## Connectors configuration :::info Connectors are **enabled by default** in Camunda 8.8. This section covers configuration options for Connectors, including how to disable them if needed. ::: The Connector runtime is enabled by default. To use connectors, install connector element templates. For details, see [Manage connector templates in Web Modeler](/components/connectors/manage-connector-templates.md) or [Configuring templates in Desktop Modeler](/components/modeler/desktop-modeler/element-templates/configuring-templates.md). For the full list of options, see the [Connectors Helm values](https://artifacthub.io/packages/helm/camunda/camunda-platform#connectors-parameters). ### Disable Connectors To disable Connectors, set `connectors.enabled: false` when deploying the Helm chart: ```yaml connectors: enabled: false ``` ### Polling authentication mode Connectors use the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) to fetch process definitions that contain inbound connectors. Depending on your Camunda architecture, choose one of the following values for the `inbound.mode` parameter: - `disabled` — Polling from the Orchestration Cluster is disabled. The connector runtime supports only outbound interactions, such as HTTP REST calls. - `credentials` — The connector runtime authenticates to the Orchestration Cluster REST API with basic HTTP authentication. - `oauth` — _(Recommended, and enabled by default)_ The connector runtime authenticates to the Orchestration Cluster REST API with OAuth 2.0. Camunda uses Keycloak as the default OAuth provider. ## Troubleshooting ### Conflicting options If both an environment variable and a configuration file define the same option, the environment variable takes precedence. Example: ```yaml zeebe: env: - name: ZEEBE_BROKER_DATA_BACKUP_S3_BUCKETNAME value: "zeebetest1" configuration: | zeebe: broker: data: backup: s3: bucketName: "zeebeOtherBucketName" ... ``` Here, the bucket name is set twice. The environment variable `zeebetest1` overrides the configuration file `zeebeOtherBucketName`. See [Spring externalized configuration](https://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/html/boot-features-external-config.html) for precedence details. ### Limitations Setting the `configuration` option replaces the entire contents of the application's configuration file. During upgrades, if the `configuration` option remains and there are breaking changes to the configuration file format or required defaults, this can prevent the component from starting. - Use `extraConfiguration` by default so you only maintain a small set of overrides. - The `configuration` option is an advanced override: if the file format or defaults change, the component may fail to start until you update your full configuration. - Forgetting to wrap multiline values with (`|`) in Helm can cause parse errors. - Mixing `env` and `configuration` for the same property without realizing precedence can lead to unexpected results. ## References For more details on where to find configuration options for specific components, see the following pages: - [Components (Self-Managed)](../../../components/orchestration-cluster/overview.md) - [Zeebe Broker](/self-managed/components/orchestration-cluster/zeebe/configuration/broker.md) - [Zeebe Gateway](/self-managed/components/orchestration-cluster/zeebe/configuration/gateway.md) - [Operate](/self-managed/components/orchestration-cluster/operate/operate-configuration.md) - [Tasklist](/self-managed/components/orchestration-cluster/tasklist/tasklist-configuration.md) - [Camunda Hub](/self-managed/components/hub/configuration/properties.md) - [Connectors](/self-managed/components/connectors/connectors-configuration.md) - [Identity](/self-managed/components/management-identity/miscellaneous/configuration-variables.md) - [Optimize](/self-managed/components/optimize/configuration/system-configuration.md) --- ## Set up the Helm chart with basic authentication By default, Camunda 8 Self-Managed uses Basic authentication for all components deployed through the Helm chart. This method requires no additional configuration and is ideal for local or development environments. :::info Bitnami subcharts removed in Camunda 8.10 Earlier releases bundled Keycloak and PostgreSQL through Bitnami subcharts (`identityKeycloak`, `webModelerPostgresql`). As of Camunda 8.10 (Helm chart `15.x`), the bundled Bitnami subcharts are removed: deploy Keycloak and PostgreSQL with [Kubernetes operators](/self-managed/deployment/helm/configure/operator-based-infrastructure.md) or managed services, as shown in the examples below. ::: :::note Because Basic authentication is enabled by default, components that depend on Management Identity (which implements OIDC/OAuth authentication) are disabled by default. These components include: - Management Identity - Console - Web Modeler - Keycloak - Optimize ::: In this guide, you'll learn how to: - Deploy the Orchestration Cluster and Connectors with Basic authentication. - Add additional components, using a hybrid approach, combining Basic authentication and OIDC. ## Enable the Orchestration Cluster and Connectors The Orchestration Cluster and Connectors are enabled, by default, with Basic authentication. No additional configuration is required—simply deploy the Helm chart, and these components will be available. ### Default users Two users are created by default: | Username | Password | Role | Description | | ----------- | ----------- | ------------ | ----------------------------------------------------------------------------------- | | `demo` | `demo` | `admin` | Initial administrative user | | `connector` | `connector` | `connectors` | Used by the Connectors component to authenticate with the Orchestration Cluster API | For details on configuring initial users and their roles, see [Orchestration Cluster Admin initialization](/self-managed/components/orchestration-cluster/admin/overview.md#option-3-configuration). :::note Helm arrays In Helm, arrays must be overwritten in full. If you change these configuration settings, keep in mind that the default array must be configured in your custom `values.yaml` if you want to keep those users and role assignments. For example, when adding the user `foo` or assigning roles to `foo`, keep also the values for the demo and connectors user. ::: ### Connect to the cluster To access the Orchestration Cluster and Connectors from your local machine using `kubectl port-forward`, refer to [Accessing components without Ingress](../ingress/accessing-components-without-ingress.md). Log in with the default credentials: - **Username:** `demo` - **Password:** `demo` ## Enable additional components The following components require Management Identity with an OIDC provider and, therefore, don't support basic authentication: - Console - Web Modeler - Optimize However, you can still enable these components alongside a Basic authentication Orchestration Cluster by using a hybrid authentication setup: - **Orchestration Cluster and Connectors** use basic authentication - **Console, Web Modeler, Optimize, and Management Identity** use OIDC In this section, you'll learn how to configure hybrid authentication with internal Keycloak. You can also apply this approach with other OIDC setups, such as [external Keycloak](./external-keycloak.md) or an [external OIDC provider](./external-oidc-provider.md) When deploying process models from Web Modeler to a Basic authentication Orchestration Cluster, you'll be prompted to enter credentials in the deployment dialog. ### Configuration steps Follow the [internal Keycloak guide](./internal-keycloak.md) with these modifications: 1. When you [create a secret](./internal-keycloak.md#create-a-secret), omit the following keys. They aren't needed for Basic authentication setups: - `identity-connectors-client-token` - `identity-orchestration-client-token` 2. Set `basic` as the authentication method for the Orchestration Cluster and Connectors: ```yaml orchestration: security: authentication: method: basic connectors: security: authentication: method: basic ``` 3. Skip the OIDC sections for the [Orchestration Cluster](./internal-keycloak.md#configure-orchestration-cluster) and [Connectors](./internal-keycloak.md#configure-connectors). ### Full configuration example This example shows a complete configuration of hybrid authentication with internal Keycloak: ```yaml global: identity: auth: enabled: true type: KEYCLOAK publicIssuerUrl: http://keycloak-service:18080/auth/realms/camunda-platform issuerBackendUrl: http://keycloak-service:18080/auth/realms/camunda-platform optimize: secret: existingSecret: "camunda-credentials" existingSecretKey: "identity-optimize-client-token" keycloak: url: protocol: http host: keycloak-service port: "18080" contextPath: /auth realm: /realms/camunda-platform auth: adminUser: temp-admin secret: existingSecret: keycloak-initial-admin existingSecretKey: password identity: enabled: true firstUser: secret: existingSecret: "camunda-credentials" existingSecretKey: "identity-firstuser-password" externalDatabase: enabled: true host: pg-identity-rw port: 5432 database: identity username: identity secret: existingSecret: pg-identity-secret existingSecretKey: password optimize: enabled: true connectors: security: authentication: method: basic camundaHub: enabled: true # Deploys both Console and Web Modeler restapi: mail: fromAddress: noreply@example.com externalDatabase: host: pg-hub-rw port: 5432 database: hub username: hub secret: existingSecret: pg-hub-secret existingSecretKey: password orchestration: security: authentication: method: basic ``` ### Connect to the cluster To access the additional components, refer to [Connect to the cluster](./internal-keycloak.md#connect-to-the-cluster) in the internal Keycloak guide. ## Next steps - [Enable centralized OIDC authentication for all components](./internal-keycloak.md). - [Integrate with an external identity provider](./external-oidc-provider.md). --- ## Helm chart user and client setup for Management Identity When connected to Keycloak, the Camunda Helm chart allows you to configure Management Identity users and OAuth2 clients through the `identity` section in your Helm values file. This page explains how to define users and clients with YAML examples and descriptions of common fields. ## Add OAuth2 clients Define custom OAuth2 clients under the `identity.clients` list. Example: ```yaml identity: clients: - id: test name: Test secret: existingSecret: test-secret # Kubernetes secret containing the client secret existingSecretKey: test-secret-key # Key inside the secret redirectUris: /dummy # Redirect URIs for the OAuth2 client rootUrl: http://dummy # Root URL of the client application type: confidential # Client type (confidential, public, m2m) permissions: - resourceServerId: camunda-identity-resource-server definition: read - resourceServerId: camunda-identity-resource-server definition: write - resourceServerId: orchestration-api definition: read:* - resourceServerId: orchestration-api definition: write:* - resourceServerId: optimize-api definition: write:* - resourceServerId: web-modeler-api definition: write:* - resourceServerId: console-api definition: write:* ``` ## Add Identity users Create Management Identity users under the `identity.users` list. Example: ```yaml identity: users: - username: foo secret: existingSecret: foo-secret # Secret containing the user's password existingSecretKey: foo-secret-key firstName: Foo lastName: Bar email: foo.bar@camunda.com roles: - ManagementIdentity # Assign roles to the user - Optimize - Console ``` ## Key points - OAuth2 clients must include at least one redirect URI. - Public clients do not require a client secret. - User roles define permissions across Management Identity components such as Web Modeler, Console, and Optimize. - All referenced Kubernetes secrets must exist before deploying the Helm chart. ## Troubleshooting ### Clients or users don't appear after installation **Observed behavior:** Configured clients or users aren't created. **Why this happens:** A secret referenced in your configuration doesn't exist, or doesn't match the values in your Helm configuration. Missing secrets prevent creation. **How to fix:** Verify that all referenced secrets exist and match the values in your Helm configuration. ### A client or user has unexpected permission issues **Observed behavior:** A user or client can authenticate, but can't perform actions you expect it to be allowed to. **Why this happens:** Its assigned roles don't grant the access it needs. **How to fix:** Confirm the roles assigned to that user or client align with the access it should have. ### Management Identity fails to bind configuration with an array-indexing error **Observed behavior:** Management Identity fails to start, logging an error such as: ``` Binding to target [Bindable@3e595da3 type = java.util.List, value = 'none', annotations = array[[empty]], bindMethod = [null]] failed: ``` **Why this happens:** Users or clients were previously configured using environment variables, and the array indexing in that configuration is incorrect. **How to fix:** Migrate those definitions into `values.yaml` instead of environment variables. --- ## Set up an external IdP with internal Keycloak This guide explains how to configure the internal Keycloak instance as an identity broker that delegates authentication to an external identity provider (IdP), such as an OIDC provider, SAML, LDAP, or Active Directory. This setup allows you to: - Use your organization's existing identity provider for user authentication - Retain the internal Keycloak for Camunda OIDC integration - Manage user authorization in Camunda identity components For Keycloak support scope, security fixes, and upgrades, see [Keycloak support, security fixes, and upgrades](/self-managed/deployment/helm/configure/authentication-and-authorization/keycloak-support-and-upgrades.md). ## Prerequisites - A Camunda 8 deployment with internal Keycloak enabled. For setup instructions, see [Configure internal Keycloak for Helm deployments](/self-managed/deployment/helm/configure/authentication-and-authorization/internal-keycloak.md). - Access to your external IdP's configuration (client credentials, endpoints, etc.) ## Configure the external identity provider Complete the following steps: 1. [Add your external IdP to Keycloak](#add-your-external-idp-to-keycloak) 2. [Configure identity provider mappers](#configure-identity-provider-mappers) 3. [Configure Orchestration Cluster Admin](#configure-orchestration-cluster-admin) 4. [Configure Management Identity access](#configure-management-identity-access-optional) (optional) ### Add your external IdP to Keycloak Configure Keycloak to use your external identity provider by following the [Configure an external IdP using Keycloak](/self-managed/components/management-identity/configuration/configure-external-identity-provider.md) guide. ### Configure identity provider mappers After adding the identity provider, configure mappers in the **Camunda realm** (default: `camunda-platform`) to import user attributes and assign users to a group for authorization. :::tip For details on Keycloak identity provider mappers, see the [Keycloak documentation on identity broker mappers](https://www.keycloak.org/docs/latest/server_admin/index.html#_mappers). ::: #### Create attribute mappers Attribute mappers import user profile information from the external IdP into Keycloak user accounts. In Keycloak Admin Console, navigate to **Identity Providers** > select your IdP > **Mappers** tab. Create attribute mappers to import user profile information: | Name | Mapper type | Claim | User attribute | | ----------- | ------------------ | ------------- | -------------- | | `email` | Attribute Importer | `email` | `email` | | `firstName` | Attribute Importer | `given_name` | `firstName` | | `lastName` | Attribute Importer | `family_name` | `lastName` | #### Create username mapper The username mapper determines how Keycloak assigns usernames to federated users based on claims from the external IdP. Create a username mapper: - **Name**: `username` - **Mapper Type**: Username Template Importer - **Template**: `${CLAIM.preferred_username}` #### Create group for external IdP users Navigate to **Groups** > **Create group** and create a group: - **Name**: `external-idp-users` #### Assign users to the group The hardcoded group mapper automatically assigns all users authenticating through this IdP to a specified group. This group membership is then included in the user's access token. Create a mapper to assign federated users to this group: - **Name**: `assign-external-idp-group` - **Mapper Type**: Hardcoded Group - **Group**: `external-idp-users` ### Configure Orchestration Cluster Admin External IdP users can authenticate, but still require authorization to access Camunda components. Log in to **Orchestration Cluster Admin** as an administrator. #### Grant component access Grant access to Orchestration Cluster components for the external IdP users group: 1. Navigate to **Authorizations** > select **Component** > **Create authorization**. 2. Configure the authorization: - **Owner type**: `Group` - **Owner ID**: `external-idp-users` - **Resource ID**: `*` - **Permissions**: `ACCESS` #### Grant additional permissions (optional) Grant additional permissions as needed. For example, to allow users to view processes and complete tasks: | Resource type | Resource ID | Permissions | | ------------------ | ----------- | -------------------------------------------------------------------- | | Process definition | `*` | `READ_PROCESS_DEFINITION`, `READ_PROCESS_INSTANCE`, `READ_USER_TASK` | | User task | `*` | `UPDATE_USER_TASK` | :::info For more details, see [Configure Orchestration Cluster authorizations](/components/concepts/access-control/authorizations.md). ::: ### Configure Management Identity access (optional) For access to Console, Web Modeler, and Optimize, external IdP users need the corresponding realm roles assigned in Keycloak. The recommended approach is to assign users to groups that have these roles. :::note The hardcoded group mappers in this section grant access to all users authenticating through the external IdP. For more granular access control based on groups or attributes from your external IdP, see the [Keycloak documentation on identity provider mappers](https://www.keycloak.org/docs/latest/server_admin/index.html#_mappers). ::: #### Verify or create groups 1. In Keycloak Admin Console, navigate to **Groups**. 2. Verify that groups exist for each component (e.g., `Console`, `Optimize`, `Web Modeler`). If not, create them. #### Assign roles to groups Ensure each group has the corresponding realm role assigned: 1. Select the group > **Role Mappings** tab. 2. Click **Assign role** and add the role with the same name (e.g., `Console`). #### Create group mappers Create mappers to assign federated users to these groups: 1. Navigate to **Identity Providers** > select your IdP > **Mappers** tab. 2. Click **Add mapper** for each component: | Mapper name | Mapper type | Group | | ------------------------- | --------------- | ------------- | | `assign-console-group` | Hardcoded Group | `Console` | | `assign-optimize-group` | Hardcoded Group | `Optimize` | | `assign-webmodeler-group` | Hardcoded Group | `Web Modeler` | :::tip You can also assign roles directly to users in Keycloak, or use [mapping rules in Management Identity](/self-managed/components/management-identity/mapping-rules.md) to map token claims to roles. ::: ## Next steps - To understand the differences between Orchestration Cluster Admin and Management Identity, see [Identity types in Camunda 8](/components/concepts/access-control/access-control-overview.md#identity-types-in-camunda-8). - To learn more about mapping rules, see [Mapping rules](/components/concepts/access-control/mapping-rules.md). - To configure additional authorizations, see [Orchestration Cluster authorization](/components/concepts/access-control/authorizations.md). - To use an external IdP without the internal Keycloak, see [Set up the Helm chart with an external OIDC provider](/self-managed/deployment/helm/configure/authentication-and-authorization/external-oidc-provider.md). --- ## Set up the Helm chart with an external Keycloak instance :::caution Admin access required The external Keycloak setup requires administrative access to the Keycloak server. ::: :::info Bitnami subcharts removed in Camunda 8.10 Earlier releases provided Web Modeler's database through the `webModelerPostgresql` Bitnami subchart. As of Camunda 8.10 (Helm chart `15.x`), the bundled Bitnami subcharts are removed: provide PostgreSQL with the [CloudNativePG operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#postgresql-deployment) or a managed database, as shown in the examples below. ::: The Camunda Helm chart can connect to an external Keycloak instance that acts as the identity management service for authentication and authorization. With minimal configuration for administrative access, the Management Identity component can automatically configure the Keycloak realm and required entities on startup—simplifying setup and reducing the learning curve. Use this guide if you already have an existing Keycloak instance and want Camunda to automatically configure the required Keycloak entities. For Keycloak support scope, security fixes, and upgrades, see [Keycloak support, security fixes, and upgrades](/self-managed/deployment/helm/configure/authentication-and-authorization/keycloak-support-and-upgrades.md). If you prefer to run Keycloak inside your cluster and deploy it with the Keycloak operator, see the [internal Keycloak guide](/self-managed/deployment/helm/configure/authentication-and-authorization/internal-keycloak.md). :::tip Private or internal CA If your external Keycloak instance presents a certificate signed by a private or internal certificate authority, Camunda components won't trust it by default. Configure [TLS trust](/self-managed/deployment/helm/configure/tls.md#external-oidc-issuer-with-private-ca) before or alongside this guide to avoid `PKIX path building failed` errors. ::: Before you begin, ensure you’re running a Keycloak version that’s supported by your Camunda release. See [supported environments](/reference/supported-environments.md#component-requirements). ## Configure Keycloak Before setting up the Camunda Helm chart, prepare your Keycloak instance. For the Keycloak realm, you have two options: - [Use an existing realm](#option-1-prepare-an-existing-realm) - [Let Management Identity create a realm](#option-2-let-management-identity-create-a-realm) ### Option 1: Prepare an existing realm If you choose this option, configure your Keycloak realm following the [Management Identity configuration guide](/self-managed/components/management-identity/configuration/connect-to-an-existing-keycloak.md). Take note of the following values: - Realm name (``) - Client ID for Management Identity (``) - Administrative Keycloak username and password (``, ``) ### Option 2: Let Management Identity create a realm If you choose this option, Management Identity will create a realm named `camunda-platform` on startup. Ensure this realm doesn’t already exist before starting for the first time. Take note of the following values: - Realm name: `camunda-platform` (``) - Client ID generated for setup: `camunda-identity` (``) - Administrative Keycloak username and password (``, ``) ## Configure the Helm chart Next, prepare your Kubernetes cluster and install the Camunda Helm chart. Perform the following steps: 1. [Create a secret](#create-a-secret) 2. [Prepare global configuration](#prepare-global-configuration) 3. [Configure Management Identity](#configure-management-identity) 4. [Configure components using OIDC](#configure-components-using-oidc) You can also [view the full configuration example](#full-configuration-example). ### Create a secret Create a secret containing all required credentials. For example, the following command creates a `camunda-credentials` secret: ```bash kubectl create secret generic camunda-credentials \ --from-literal=identity-keycloak-admin-password= \ --from-literal=identity-firstuser-password=CHANGE_ME \ --from-literal=identity-connectors-client-token=CHANGE_ME \ --from-literal=identity-optimize-client-token=CHANGE_ME \ --from-literal=identity-orchestration-client-token=CHANGE_ME ``` This secret includes the following keys: - `identity-keycloak-admin-password`: Password for an administrative Keycloak account (``). - `identity-firstuser-password`: Password for the initial Camunda user (default username: `demo`). - `identity-connectors-client-token`: Client secret of the Keycloak OIDC client `connectors `used by Connectors. - `identity-optimize-client-token`: Client secret of the Keycloak OIDC client `optimize` used by Optimize. - `identity-orchestration-client-token`: Client secret of the Keycloak OIDC client `orchestration` used by the Orchestration Cluster. The PostgreSQL credentials for Management Identity and Camunda Hub are no longer part of this secret. They are provided by the operator (or managed database) that hosts each database, such as the `pg-identity-secret` and `pg-hub-secret` created by the [CloudNativePG operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#postgresql-deployment). For additional options on how to create and reference Kubernetes secrets (for example using YAML manifests or consolidated secrets), see [External Kubernetes secrets](/self-managed/deployment/helm/configure/secret-management.md#method-2-external-kubernetes-secrets-recommended). ### Prepare global configuration Start with the following global configuration, which provides shared defaults across the deployment: ```yaml global: identity: auth: enabled: true publicIssuerUrl: /realms/ issuerBackendUrl: /realms/ authUrl: /realms//protocol/openid-connect/auth tokenUrl: /realms//protocol/openid-connect/token jwksUrl: /realms//protocol/openid-connect/certs security: authentication: method: oidc ``` Replace `KEYCLOAK_URL` with your Keycloak base URL (in the format `://:/`). :::info In some setups, Keycloak is accessible through different URLs from within the cluster and from the user’s browser. This can happen, for example, if you deployed Keycloak inside your Kubernetes cluster but didn’t expose it under a domain name that’s accessible both internally and externally. In this case: - Set `global.identity.auth.publicIssuerUrl` and `global.identity.auth.authUrl` to the URL reachable from users' browsers. - Set the remaining values to the URL reachable from within the cluster. ::: ### Configure Management Identity Configure Management Identity to access the realm and use the initial OIDC client. On startup, Management Identity creates the realm (if needed), sets up the OIDC clients, and creates the initial user account. If no clients appear in the Keycloak realm after startup, the configuration was not successful. Management Identity configuration: ```yaml global: identity: keycloak: url: # this URL must be reachable from within the cluster protocol: host: port: contextPath: # set to "/" for the root context path realm: /realms/ auth: adminUser: secret: existingSecret: "camunda-credentials" existingSecretKey: "identity-keycloak-admin-password" auth: identity: clientId: identity: enabled: true firstUser: secret: existingSecret: "camunda-credentials" existingSecretKey: "identity-firstuser-password" externalDatabase: enabled: true host: pg-identity-rw port: 5432 database: identity username: identity secret: existingSecret: pg-identity-secret existingSecretKey: password env: - name: KEYCLOAK_REALM value: - name: IDENTITY_CLIENT_ID value: ``` Add the section under `global.identity` to the `global` configuration you created in the previous step. Management Identity stores its data in a dedicated PostgreSQL database. Connect it to the `pg-identity` cluster created by the [CloudNativePG operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#postgresql-deployment), or to a managed database. Chart `15.x` no longer bundles the `identityPostgresql` subchart, so this connection has to be configured explicitly. :::note Chart 15.x (Camunda 8.10) rejects the flat `global.identity.keycloak.auth.existingSecret` and `auth.existingSecretKey` form used by earlier charts. Use the nested `auth.secret.*` form shown above. In charts 14.x (Camunda 8.8 and 8.9) the flat form still works but logs a deprecation warning. For the full list of values removed in 8.10, see [Remove keys rejected by chart 15.x](/self-managed/upgrade/helm/890-to-8100.md#remove-keys-rejected-by-chart-15x). ::: The `identity.firstUser` field defines the initial user that Management Identity creates in Keycloak with full access to all Camunda components. By default, this user is named `demo`. To use a different name, set `identity.firstUser.username`. For additional Keycloak-specific variables you can define under `identity.env`, see [Management Identity environment variables](/self-managed/components/management-identity/miscellaneous/configuration-variables.md). ### Configure components using OIDC To configure the Orchestration Cluster and management plane components with OIDC, follow the steps in the [Configure components using OIDC section of the internal Keycloak setup guide](/self-managed/deployment/helm/configure/authentication-and-authorization/internal-keycloak.md#configure-components-using-oidc). Assign each component its own resource audience by default. Keycloak does not enforce this for you, and a shared value lets a token issued for one component be accepted by another. Only configure this trust for a supported integration. See [Assign a unique audience to each component](./generic-oidc-provider.md#assign-a-unique-audience-to-each-component). ### Full configuration example The following example shows a complete configuration for connecting to an external Keycloak instance: ```yaml global: identity: auth: enabled: true publicIssuerUrl: /realms/ issuerBackendUrl: /realms/ authUrl: /realms//protocol/openid-connect/auth tokenUrl: /realms//protocol/openid-connect/token jwksUrl: /realms//protocol/openid-connect/certs identity: clientId: optimize: secret: existingSecret: "camunda-credentials" existingSecretKey: "identity-optimize-client-token" keycloak: url: # this URL must be reachable from within the cluster protocol: host: port: contextPath: # set to "/" for the root context path realm: /realms/ auth: adminUser: secret: existingSecret: "camunda-credentials" existingSecretKey: "identity-keycloak-admin-password" security: authentication: method: oidc identity: enabled: true firstUser: secret: existingSecret: "camunda-credentials" existingSecretKey: "identity-firstuser-password" externalDatabase: enabled: true host: pg-identity-rw port: 5432 database: identity username: identity secret: existingSecret: pg-identity-secret existingSecretKey: password env: - name: KEYCLOAK_REALM value: - name: IDENTITY_CLIENT_ID value: optimize: enabled: true connectors: security: authentication: oidc: secret: existingSecret: "camunda-credentials" existingSecretKey: "identity-connectors-client-token" camundaHub: enabled: true # Deploys both Console and Web Modeler restapi: mail: fromAddress: noreply@example.com # Connect Camunda Hub to the operator-managed PostgreSQL cluster (pg-hub) externalDatabase: host: pg-hub-rw port: 5432 database: hub username: hub secret: existingSecret: pg-hub-secret existingSecretKey: password orchestration: security: authentication: oidc: secret: existingSecret: "camunda-credentials" existingSecretKey: "identity-orchestration-client-token" ``` To review how each component is configured and which OIDC clients are used: - Run `kubectl get pods` and `kubectl get configmap`, then use `kubectl describe` to inspect component configurations. - Log into Keycloak (using your administrative user) to review the OIDC client setup ## Connect to the cluster After applying this configuration, use the following `kubectl port-forward` commands to access the APIs and UIs from your localhost: ```bash # Management Identity kubectl port-forward svc/camunda-identity 8084:80 # Orchestration Cluster kubectl port-forward svc/camunda-zeebe-gateway 8080:8080 kubectl port-forward svc/camunda-zeebe-gateway 26500:26500 # Connectors kubectl port-forward svc/camunda-connectors 8086:8080 # Optimize kubectl port-forward svc/camunda-optimize 8083:80 # Web Modeler kubectl port-forward svc/camunda-web-modeler-restapi 8070:80 kubectl port-forward svc/camunda-web-modeler-websockets 8085:80 # Console kubectl port-forward svc/camunda-console 8087:80 ``` After port forwarding, access the UIs through `http://localhost:`. For example: - Orchestration Cluster UI: `http://localhost:8080` - Management Identity: `http://localhost:8084` Log in with the username `demo` and the password stored in the secret key `identity-firstuser-password`. ## Troubleshooting For issues common to any OIDC provider, see [Troubleshoot OIDC authentication](./troubleshooting-oidc.md). The following are specific to external Keycloak: **Management Identity pod restarts once during first startup** A single restart during the very first deployment is expected. Immediately after creating the realm, Management Identity can briefly hit `403 Forbidden` while disabling Keycloak's default system clients. This is a timing issue between realm creation and Keycloak's permission propagation, not a misconfiguration, and the automatic pod restart resolves it. Investigate further only if the pod keeps crash-looping past the first retry. **Management Identity fails to connect to the Keycloak admin API** The `global.identity.keycloak.*` settings configure the admin API connection used for provisioning, which is separate from the OIDC login flow. Verify `url.protocol`, `url.host`, `url.port`, and `contextPath` together form a URL reachable from inside the cluster: ```bash kubectl run -it --rm curl --image=curlimages/curl --restart=Never -- \ curl https:///realms//.well-known/openid-configuration ``` A valid response confirms the realm is reachable at that URL. If this fails, double check `issuerBackendUrl` from the global configuration step, since it should resolve to the same host. **Realm already exists, but clients aren't created** If the realm already existed when Management Identity started, Management Identity doesn't re-create it, but it does still attempt to create any missing clients. If clients are still missing after startup, check the Management Identity logs for provisioning errors. The most common cause is that the admin credentials lack permission to create clients in the existing realm. **Demo user can't log in** The `identity-firstuser-password` secret value is only applied when the demo user is first created. If this user already exists from a previous deployment with a different password, changing the secret has no effect on it. Reset the user's password directly in the Keycloak admin console. --- ## Set up the Helm chart with an external OIDC provider Instead of using internal Keycloak, you can configure Camunda to connect to an external OpenID Connect (OIDC) identity provider, such as an external Keycloak, Microsoft Entra ID, or Okta. The Helm chart offers these options: --- ## Connect Camunda to any OIDC provider This guide shows you how to configure Camunda 8 Self-Managed to authenticate with any OpenID connect (OIDC)-compliant identity provider. :::info Bitnami subcharts removed in Camunda 8.10 Earlier releases bundled PostgreSQL through Bitnami subcharts (`identityPostgresql`, `webModelerPostgresql`). As of Camunda 8.10 (Helm chart `15.x`), the bundled Bitnami subcharts are removed: provide PostgreSQL with the [CloudNativePG operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#postgresql-deployment) or a managed database, as shown in the examples below. ::: :::info Before proceeding, since this is a general guide, refer to [External OIDC provider](./external-oidc-provider.md) to see the available provider-specific guides, as they include detailed setup instructions tailored to provider's interface. ::: ## Prerequisites Before you begin, ensure you have: - An OIDC-compliant provider already deployed and accessible. - Administrative access to create and configure OIDC clients in your provider. - Access to your provider's discovery document to obtain endpoint URLs. - A Kubernetes cluster with a [supported Helm CLI version](/reference/supported-environments.md#clients) installed. - kubectl configured to access your cluster. - When you connect Management Identity to an OIDC provider, you need a database regardless of feature flags. Chart `15.x` no longer bundles one, so provision it with the [CloudNativePG operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#postgresql-deployment) or a managed database and connect it through `identity.externalDatabase`, as shown in the examples below. See also [use external PostgreSQL](/self-managed/deployment/helm/configure/database/using-existing-postgres.md). This guide assumes your OIDC provider is already operational. It does not cover provider installation or basic OIDC configuration. :::tip Private or internal CA If your provider presents a certificate signed by a private or internal certificate authority (common with Entra hybrid setups, Okta on-premises, or an internal Keycloak), Camunda components won't trust it by default. Configure [TLS trust](/self-managed/deployment/helm/configure/tls.md#external-oidc-issuer-with-private-ca) to avoid `PKIX path building failed` errors when components connect to the issuer. ::: ## Create OIDC clients Create the following OIDC clients in your provider. The exact process varies by provider; consult your provider's documentation for client creation procedures. | Client name | Type | Purpose | | --------------------- | ------------ | ------------------------------------------------ | | Management Identity | Confidential | User login and API authentication | | Orchestration Cluster | Confidential | User login and machine-to-machine authentication | | Optimize | Confidential | User login | | Web Modeler API | Confidential | Programmatic API access | | Web Modeler UI | Public | User login | | Console | Public | User login | :::tip For each client, record: - Client ID - Client secret (for confidential clients only) ::: ## Assign a unique audience to each component Camunda components can trust tokens from the same OIDC issuer while using the `aud` claim to identify the intended resource. Each component validates this claim against its configured audience and accepts any token that carries it. Management Identity controls access to Camunda Hub and Optimize. The Orchestration Cluster manages its own roles and authorizations through Admin. Both subsystems can use the same OIDC provider, but their authorization checks remain independent. Decide a distinct audience for each component before you configure Helm, then configure your provider to issue it. | Component | Helm value | Chart default | | ---------------------- | ----------------------------------------------------- | ---------------------------------- | | Management Identity | `global.identity.auth.identity.audience` | `camunda-identity-resource-server` | | Orchestration Cluster | `orchestration.security.authentication.oidc.audience` | `orchestration-api` | | Optimize | `global.identity.auth.optimize.audience` | `optimize-api` | | Web Modeler client API | `global.identity.auth.webModeler.clientApiAudience` | `web-modeler-api` | | Web Modeler public API | `global.identity.auth.webModeler.publicApiAudience` | `web-modeler-public-api` | | Connectors | Inherits the Orchestration Cluster audience | `orchestration-api` | :::warning If two components accept the same audience, a token intended for one can also pass the other's audience validation. Keep the resource audiences in this table distinct unless a supported integration requires one component to accept another's token. ::: Do not derive these values by inspecting whatever token your provider returns by default. If several components are registered against one client or API identifier, inspection returns the same `aud` for all of them, so configuring what you find reproduces the collision instead of revealing it. Decide the values first, then use [token inspection](./jwt-token-claims.md) to confirm your provider issues them. The following integrations intentionally cross this audience boundary: - Connectors calls the Orchestration Cluster as a client and uses the Orchestration Cluster's audience. See [Configure Connectors](#configure-connectors). - Camunda Hub deployments that use `BEARER_TOKEN` authentication forward the user's Hub token to the Orchestration Cluster. Configure the cluster to accept the Camunda Hub UI audience in addition to its own audience. See [connect Admin to an identity provider](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md#step-4-configure-the-oidc-connection-details). ## Configure redirect URIs For each OIDC client you have created, configure the redirect URIs that correspond to where Camunda components will be accessible from users' browsers. ### Redirect URI table | Component | Redirect URI pattern | Example (localhost) | Example (Ingress) | | --------------------- | -------------------------------------------- | --------------------------------------------------- | ------------------------------------------------------------------ | | Management Identity | `/auth/login-callback` | `http://localhost:8084/auth/login-callback` | `https://camunda.example.com/identity/auth/login-callback` | | Orchestration Cluster | `/sso-callback` | `http://localhost:8080/sso-callback` | `https://camunda.example.com/orchestration/sso-callback` | | Optimize | `/api/authentication/callback` | `http://localhost:8083/api/authentication/callback` | `https://camunda.example.com/optimize/api/authentication/callback` | | Web Modeler UI | `/login-callback` | `http://localhost:8070/login-callback` | `https://camunda.example.com/modeler/login-callback` | | Console | `/` | `http://localhost:8087/` | `https://camunda.example.com/` | Replace `<*_URL>` with the actual base URL where each component will be accessible. Use the localhost examples if testing locally with port forwarding, or the Ingress examples if exposing components via Ingress. :::important Security note Redirect URIs are security-critical. Only the URIs you configure in your OIDC provider are permitted as redirection targets after authentication. Ensure these values match the `redirectUrl` parameters you'll set in the Helm configuration. ::: :::tip Wildcard support Some OIDC providers support wildcard redirect URIs (e.g., `https://camunda.example.com/*`). Check your provider's documentation to see if this can simplify your configuration. ::: ## Discover provider configuration Since OIDC providers vary in their implementation details, you need to obtain the specific values from your provider. ### Find OIDC endpoints Most OIDC providers expose a discovery document at: ``` https://your-provider.example.com/.well-known/openid-configuration ``` Access this URL (replacing `your-provider.example.com` with your provider's domain) to retrieve a JSON document containing endpoint URLs. #### Example discovery document ```json { "issuer": "https://your-provider.example.com", "authorization_endpoint": "https://your-provider.example.com/oauth/authorize", "token_endpoint": "https://your-provider.example.com/oauth/token", "jwks_uri": "https://your-provider.example.com/.well-known/jwks.json", ... } ``` #### Record these values for Helm configuration - `issuer` → Used for: `publicIssuerUrl` - `authorization_endpoint` → Used for: `authUrl` - `token_endpoint` → Used for: `tokenUrl` - `jwks_uri` → Used for: `jwksUrl` ### Identify token claims Camunda needs to know which claims in access tokens identify users and clients. Claim names vary by provider. You need to identify: - **User identification claim** (`usernameClaim`): Identifies users during web login (for example, `email`, `preferred_username`). - **Client identification claim** (`clientIdClaim`): Identifies calling applications for M2M authentication (for example, `client_id`, `azp`). - **Audience claim** (`audience`): The expected `aud` value in tokens. For detailed instructions on obtaining and decoding tokens to identify these claims, see [JWT token claims reference](./jwt-token-claims.md). ### Scopes requested by Camunda Camunda components request OIDC scopes when authenticating users. The default scopes vary by component: | Scope | Description | Management Identity, Optimize, Web Modeler, Console | Orchestration Cluster applications (Identity, Operate, Tasklist) | | ---------------- | ----------------------------------- | --------------------------------------------------- | ---------------------------------------------------------------- | | `openid` | Required for OIDC authentication. | ✔ | ✔ | | `profile` | Access to user profile information. | ✔ | ✔ | | `email` | Access to user email address. | ✔ | | | `offline_access` | Enables refresh token issuance. | ✔ | | :::info If your provider supports the `offline_access` scope, components will receive refresh tokens. This allows sessions to remain active longer without requiring users to re-authenticate. If `offline_access` is not available or not granted, users will be redirected to your OIDC provider for re-authentication when their access token expires. If a refresh fails because the refresh token is no longer valid (for example, because it has expired or been revoked and the provider returns `invalid_grant`) the user is logged out and must sign in again. If the refresh fails because the provider is unavailable or returns a server error, the session is retained, and the request fails with an HTTP `503 Service Unavailable` response. The first request after the provider recovers refreshes the token, so the user does not need to sign in again. For more information, see [OpenID Connect Core specification](https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess). ::: ## Handle separate access token and ID token signing keys Most OIDC providers sign access tokens and ID tokens with the same key, published at the single `jwks_uri` in the discovery document. Some enterprise identity provider deployments sign access tokens with a different key than ID tokens. Camunda validates access tokens on every API request and ID tokens only during the login callback, so if you configure only the discovery document's `jwksUrl`, access token validation fails even though login succeeds. To check whether this applies to your provider, compare the `jwks_uri` in the discovery document against the JWKS endpoint listed for access tokens (or API and runtime tokens) in your provider's admin console. If both are the same URL, skip this section. If the URLs differ, configure both endpoints: - Set `global.identity.auth.jwksUrl` to the **access token** JWKS endpoint. Management Identity validates access tokens using this single URL only, and doesn't call the userinfo endpoint or fall back to any other source. - Add the same URL as an additional JWKS source for the Orchestration Cluster, which otherwise fetches only the primary JWKS from the discovery document: ```yaml orchestration: env: - name: CAMUNDA_SECURITY_AUTHENTICATION_OIDC_ADDITIONALJWKSETURIS_0_ value: "" ``` This setting has no dedicated Helm value. It maps to the Spring Boot list property `camunda.security.authentication.oidc.additionalJwkSetUris`, set through `orchestration.env` using Spring's relaxed-binding convention for list properties: one environment variable per index, with the index surrounded by underscores (`..._0_`, `..._1_`, and so on). The Orchestration Cluster merges keys from the primary JWKS endpoint and all additional endpoints, then selects whichever key matches the `kid` in the incoming token. Both ID tokens and access tokens then validate correctly, regardless of which key set signed them. ## Create secrets Create a secret in your Kubernetes namespace that contains all OIDC client secrets: ```bash kubectl create secret generic oidc-credentials \ --from-literal=identity-client-secret="" \ --from-literal=orchestration-client-secret="" \ --from-literal=optimize-client-secret="" \ --from-literal=webmodeler-api-client-secret="" ``` :::info The secret key `webmodeler-api-client-secret` is not used elsewhere in this guide. This client is intended for your own use if you want to access the [Web Modeler API](/apis-tools/web-modeler-api/authentication.md) programmatically. ::: The PostgreSQL credentials for Management Identity and Camunda Hub are no longer created here. They are provided by the operator (or managed database) that hosts each database, such as the `pg-identity-secret` and `pg-hub-secret` created by the [CloudNativePG operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#postgresql-deployment). :::tip Alternative secret management For production deployments, consider using external secret management solutions. See [External Kubernetes secrets](/self-managed/deployment/helm/configure/secret-management.md#method-2-external-kubernetes-secrets-recommended) for more options. ::: ## Configure Camunda components Configure Camunda components to use your OIDC provider through Helm values. ### Global OIDC configuration Start with the global configuration that applies to all components: ```yaml global: security: authentication: method: oidc identity: auth: enabled: true type: "GENERIC" publicIssuerUrl: issuerBackendUrl: authUrl: tokenUrl: jwksUrl: ``` #### Parameter descriptions | Parameter | Description | Example | | ------------------ | --------------------------------------------------------- | ----------------------------------------------------------------------- | | `publicIssuerUrl` | Issuer URL accessible from users' browsers | `https://login.example.com` | | `issuerBackendUrl` | Issuer URL accessible from Kubernetes pods | `https://login.example.com` or `http://oidc-internal.svc.cluster.local` | | `authUrl` | Authorization endpoint (must be accessible from browsers) | `https://login.example.com/oauth/authorize` | | `tokenUrl` | Token endpoint (must be accessible from pods) | `https://login.example.com/oauth/token` | | `jwksUrl` | JWKS endpoint for token signature verification | `https://login.example.com/.well-known/jwks.json` | :::warning Network accessibility For generic OIDC providers, the **Issuer URL** must be accessible from both: 1. **Users' browsers**: To redirect users to the login page. 2. **Camunda components (backend)**: To fetch the provider's configuration and validate tokens. Split-horizon DNS setups (where the provider has different URLs for internal and external access) are **not supported** for generic OIDC providers. Ensure your OIDC provider is exposed via a URL that is resolvable and reachable from both locations. ::: ### Configure Management Identity Add configuration for Management Identity: ```yaml global: identity: auth: identity: clientId: audience: secret: existingSecret: oidc-credentials existingSecretKey: identity-client-secret initialClaimName: initialClaimValue: identity: fullURL: enabled: true externalDatabase: enabled: true host: pg-identity-rw port: 5432 database: identity username: identity secret: existingSecret: pg-identity-secret existingSecretKey: password ``` Management Identity requires an externally managed PostgreSQL database. Provision the database before you deploy, and adapt the connection values and secret references to your setup. For the full parameter list, see [Use external PostgreSQL](../database/using-existing-postgres.md). #### Identity-specific parameters | Parameter | Description | How to Determine | | ------------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `clientId` | Client ID from your OIDC provider | From your Identity client configuration | | `audience` | Expected audience in access tokens | The unique value you assigned in [Assign a unique audience to each component](#assign-a-unique-audience-to-each-component) | | `initialClaimName` | Claim that identifies the initial admin user | `email`, `sub`, or another user claim from token inspection | | `initialClaimValue` | Value granting initial admin access | Your admin user's value for the specified claim (e.g., `admin@example.com`) | :::warning Initial claim cannot be changed The `initialClaimName` and `initialClaimValue` parameters are used only during the first startup to grant initial admin access. Once Management Identity has started, these values are stored in the database and cannot be changed via Helm values. ::: ### Configure Orchestration Cluster Add configuration for the Orchestration Cluster (Zeebe, Operate, Tasklist, Identity): ```yaml orchestration: enabled: true security: authentication: method: oidc oidc: clientId: audience: redirectUrl: secret: existingSecret: oidc-credentials existingSecretKey: orchestration-client-secret # Claim mapping - uncomment and adjust if your provider doesn't use defaults # usernameClaim: # Default: preferred_username # clientIdClaim: # Default: client_id authorizations: enabled: true initialization: defaultRoles: admin: users: - connectors: clients: - ``` #### Orchestration-specific parameters | Parameter | Description | Value | | --------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `clientId` | Orchestration client ID | From your provider | | `audience` | Expected audience in tokens | The unique value you assigned in [Assign a unique audience to each component](#assign-a-unique-audience-to-each-component) | | `redirectUrl` | Full URL for Orchestration Cluster | `http://localhost:8080` (local) or `https://your-domain.com/orchestration` (Ingress) | | `usernameClaim` | Claim identifying users | Default: `preferred_username`. Override if your provider uses `email`, `sub`, or another claim | | `clientIdClaim` | Claim identifying clients | Default: `client_id`. Override if your provider uses `azp` or another claim | :::note Username display in Web Modeler (Helm) In Helm deployments, the default OIDC username claim is `preferred_username`, which often maps to an email address. If you want Web Modeler to display usernames based on a different claim (for example `name`), set `CAMUNDA_IDENTITY_USERNAMECLAIM=name` for the Web Modeler `restapi` environment. For available Web Modeler environment variables, see [Identity/Keycloak configuration](/self-managed/components/hub/configuration/properties.md#identity--keycloak). ::: #### Default roles - `admin.users`: List of user claim values that should have admin access. - `connectors.clients`: List of client IDs that should have Connectors role (typically the orchestration client ID itself). :::note The admin user specified in `defaultRoles.admin.users` should match the value used for `initialClaimValue` in Management Identity configuration, so that the same user has admin access to both Management Identity and the Orchestration Cluster. ::: ### Configure Connectors Add configuration for Connectors: ```yaml connectors: enabled: true security: authentication: method: oidc oidc: clientId: audience: secret: existingSecret: oidc-credentials existingSecretKey: orchestration-client-secret ``` :::info Connectors shares credentials Connectors calls the Orchestration Cluster as a client, so it deliberately reuses the Orchestration Cluster's OIDC client and audience. This is a scoped exception to the [unique audience guidance](#assign-a-unique-audience-to-each-component). If you prefer a separate OIDC client for Connectors, you must also configure the Orchestration Cluster to accept that client's audience. ::: ### Configure Optimize Add configuration for Optimize: ```yaml global: identity: auth: optimize: clientId: audience: redirectUrl: secret: existingSecret: oidc-credentials existingSecretKey: optimize-client-secret optimize: enabled: true ``` #### Optimize parameters | Parameter | Value | | ------------- | -------------------------------------------------------------------------------------------------------------------------- | | `clientId` | Optimize client ID from your provider | | `audience` | The unique value you assigned in [Assign a unique audience to each component](#assign-a-unique-audience-to-each-component) | | `redirectUrl` | `http://localhost:8083` (local) or `https://your-domain.com/optimize` (Ingress) | ### Configure Web Modeler Web Modeler requires two OIDC clients: one for the UI (public) and one for the API (confidential). :::note If your IdP provides user-friendly names in the `name` claim, and you want Web Modeler to use that claim, configure the Web Modeler `restapi` environment variable `CAMUNDA_IDENTITY_USERNAMECLAIM=name`. Without this override, Helm defaults typically resolve usernames from `preferred_username`. ::: ```yaml global: identity: auth: webModeler: clientId: redirectUrl: clientApiAudience: publicApiAudience: camundaHub: enabled: true # Deploys both Console and Web Modeler restapi: mail: fromAddress: noreply@example.com # Update with your email address # Additional SMTP configuration may be required - see Web Modeler docs externalDatabase: host: pg-hub-rw port: 5432 database: hub username: hub secret: existingSecret: pg-hub-secret existingSecretKey: password ``` #### Web Modeler parameters | Parameter | Description | Value | | ------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------- | | `clientId` | Web Modeler UI client ID (public client) | From your provider | | `redirectUrl` | Full URL for Web Modeler | `http://localhost:8070` (local) or `https://your-domain.com/modeler` (Ingress) | | `clientApiAudience` | Audience for UI-to-API communication | A unique value for the Web Modeler client API. Must differ from every other component's audience. | | `publicApiAudience` | Audience for external API access | The API client ID or custom audience | #### Email configuration Web Modeler requires email configuration for notifications. Update `restapi.mail.fromAddress` with an appropriate sender address. For full SMTP configuration, see [Web Modeler configuration](/self-managed/components/hub/configuration/properties.md). ### Configure Console Add configuration for Console: ```yaml global: identity: auth: console: clientId: audience: redirectUrl: ``` Console is deployed as part of Camunda Hub, which you enable with `camundaHub.enabled: true` in the [Web Modeler step](#configure-web-modeler). Replace `` with the base URL where Console will be accessible. For local deployment, use `http://localhost:8087`. ## Complete configuration example Below is a complete Helm values file with all components configured: ```yaml global: security: authentication: method: oidc identity: auth: enabled: true type: "GENERIC" # OIDC Provider Endpoints publicIssuerUrl: issuerBackendUrl: authUrl: tokenUrl: jwksUrl: # Management Identity identity: clientId: audience: secret: existingSecret: oidc-credentials existingSecretKey: identity-client-secret initialClaimName: initialClaimValue: # Optimize optimize: clientId: audience: redirectUrl: secret: existingSecret: oidc-credentials existingSecretKey: optimize-client-secret # Web Modeler webModeler: clientId: redirectUrl: clientApiAudience: publicApiAudience: # Console console: clientId: audience: redirectUrl: # Orchestration Cluster orchestration: enabled: true security: authentication: method: oidc oidc: clientId: audience: redirectUrl: secret: existingSecret: oidc-credentials existingSecretKey: orchestration-client-secret # The following claim mappings use Camunda defaults and usually don't need to be changed. # Only uncomment if your decoded access token uses different claim names: # usernameClaim: email # Use if tokens identify users with 'email' instead of 'preferred_username' # clientIdClaim: azp # Use if tokens identify clients with 'azp' instead of 'client_id' authorizations: enabled: true initialization: defaultRoles: admin: users: - connectors: clients: - # Connectors connectors: enabled: true security: authentication: method: oidc oidc: clientId: audience: secret: existingSecret: oidc-credentials existingSecretKey: orchestration-client-secret # Management Identity identity: fullURL: enabled: true externalDatabase: enabled: true host: pg-identity-rw port: 5432 database: identity username: identity secret: existingSecret: pg-identity-secret existingSecretKey: password # Optimize optimize: enabled: true # Camunda Hub (Console and Web Modeler) camundaHub: enabled: true # Deploys both Console and Web Modeler restapi: mail: fromAddress: externalDatabase: host: pg-hub-rw port: 5432 database: hub username: hub secret: existingSecret: pg-hub-secret existingSecretKey: password ``` **Placeholders to replace:** | Placeholder | Replace with | | -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | | `` | Your OIDC provider's issuer URL | | ``, ``, `` | The authorization, token, and JWKS endpoint URLs of your OIDC provider | | ``, ``, ``, ``, `` | The client ID of each component from your OIDC provider | | ``, ``, ``, ``, `` | The unique audience you assigned to each component | | `` | The audience for external Web Modeler API access: the API client ID or a custom audience | | ``, ``, ``, ``, `` | The URL where each component is accessible | | `` | The claim that identifies users in your tokens, such as `email` or `sub` | | `` | Your admin user's value for that claim, for example `admin@example.com` | | `` | The sender address Web Modeler uses for email notifications | ### Verify before deploying - All `` replaced with actual values. - All client secrets stored in the `oidc-credentials` secret. - Database credentials provided by the operator-managed database secrets (for example, `pg-identity-secret` and `pg-hub-secret`). - Redirect URIs in OIDC provider match `redirectUrl` values. - Each component has a distinct resource audience by default. Any cross-component audience acceptance supports a documented integration. - Verify tokens contain `preferred_username` and `client_id` claims, or uncomment and configure alternative claim names. ## Connect to the cluster After deploying Camunda with this configuration, use the following `kubectl port-forward` commands to access the APIs and UIs: ```bash # Management Identity kubectl port-forward svc/camunda-identity 8084:80 # Orchestration Cluster (Operate/Tasklist) kubectl port-forward svc/camunda-zeebe-gateway 8080:8080 # Zeebe Gateway (gRPC for clients) kubectl port-forward svc/camunda-zeebe-gateway 26500:26500 # Optimize kubectl port-forward svc/camunda-optimize 8083:80 # Web Modeler kubectl port-forward svc/camunda-web-modeler-restapi 8070:80 kubectl port-forward svc/camunda-web-modeler-websockets 8085:80 # Console kubectl port-forward svc/camunda-console 8087:80 ``` Once port forwarding is active, access each component through `http://localhost:`. For example, Management Identity at `http://localhost:8084` or the Orchestration Cluster at `http://localhost:8080` (which redirects to your OIDC provider for login). :::important Redirect URI configuration Ensure your redirect URIs in your OIDC provider match how you're accessing Camunda. If you configured redirect URIs for localhost testing (e.g., `http://localhost:8080/sso-callback`), the port-forward commands above will work. If you configured redirect URIs for Ingress (e.g., `https://camunda.example.com/orchestration/sso-callback`), you'll need to access via Ingress instead. For production deployments, configure Ingress to expose components. See [Ingress configuration](/self-managed/deployment/helm/configure/ingress/index.md) for more details. ::: ## Grant access to components After deployment, you must configure access for the following components. To grant a user access to the Web Modeler UI: - [Create a mapping rule in Management Identity](/self-managed/components/management-identity/mapping-rules.md#add-a-mapping-rule) for the `Web Modeler` role that matches the user's access token. To grant a client access to the Web Modeler API: - [Create a role in Management Identity](/self-managed/components/management-identity/application-user-group-role-management/manage-roles.md#add-a-role) for the Web Modeler API. - [Assign Web Modeler API permissions](/self-managed/components/management-identity/access-management/manage-permissions.md#manage-role-permissions) to that role in Management Identity. - [Create a mapping rule in Management Identity](/self-managed/components/management-identity/mapping-rules.md#add-a-mapping-rule) for that role that matches the client's access token. To grant a user access to Optimize: - [Create a mapping rule in Management Identity](/self-managed/components/management-identity/mapping-rules.md#add-a-mapping-rule) for the `Optimize` role that matches the user's access token. :::info When using an OIDC provider, the following Optimize features are not currently available: - The **User permissions** tab in collections. - The **Alerts** tab in collections. - Digests. - Accessible user names for resource owners (the value of the `sub` claim is displayed instead). ::: --- ## Helm chart authentication and authorization configuration Camunda 8 Self-Managed supports multiple authentication methods for securing access to components deployed with the Helm chart. This section provides an overview of available authentication options and links to configuration guides for each method. ## Overview By default, Camunda uses Basic authentication with predefined demo users. Alternatively, you can configure OpenID Connect (OIDC) authentication through an in-cluster Keycloak instance that the [Keycloak operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#keycloak-deployment) deploys, or through an external OIDC provider. ### Authentication options | Method | Description | Recommended for | | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | | [Basic authentication](./basic-authentication.md) | Authentication using preconfigured demo users. No external identity provider (IdP) required. | Local development and testing, as well as smaller-scale production setups. | | [Internal Keycloak](./internal-keycloak.md) | Connects to an in-cluster Keycloak, preconfigured by Management Identity. | Small teams or self-contained environments. | | [External IdP via Internal Keycloak](./external-idp-via-internal-keycloak.md) | Uses the internal Keycloak as an identity broker, delegating authentication to an external identity provider (IdP). | Organizations with existing identity infrastructure that want to retain Keycloak features. | | [External OIDC provider](./external-oidc-provider.md) | Integrates Camunda with an external identity provider, such as Microsoft Entra ID or Okta, via OpenID Connect. | Organizations with an existing enterprise identity infrastructure. | When you use an external OIDC provider, assign each Camunda component its own resource audience by default. Only configure cross-component audience acceptance for supported integrations. See [Assign a unique audience to each component](./generic-oidc-provider.md#assign-a-unique-audience-to-each-component). :::note When running Camunda in **no secondary storage** mode, authentication requires special configuration. See [Authentication with no secondary storage](/self-managed/concepts/secondary-storage/no-secondary-storage.md#authentication) for details. ::: ### Limitations of OIDC setups Due to technical limitations regarding [third party content](https://openid.net/specs/openid-connect-frontchannel-1_0.html#ThirdPartyContent), front channel single sign out is not supported. This means that when a user logs out of one component, they will not be logged out of the OIDC provider or the other components. ## References - [Basic authentication guide](./basic-authentication.md) - [Internal Keycloak guide](./internal-keycloak.md) - [External IdP via Internal Keycloak guide](./external-idp-via-internal-keycloak.md) - [External Keycloak guide](./external-keycloak.md) - [Microsoft Entra guide](./microsoft-entra.md) - [Ping Identity guide](./ping-identity.md) - [Generic OIDC provider](./generic-oidc-provider.md) - Management Identity: [Configure an external IdP using Keycloak](/self-managed/components/management-identity/configuration/configure-external-identity-provider.md) --- ## Set up the Helm chart with an in-cluster Keycloak instance For an in-cluster Keycloak that acts as the identity management service for authentication and authorization, deploy Keycloak with the [Keycloak operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#keycloak-deployment) and connect the Camunda Helm chart to it. The Management Identity component configures Keycloak automatically on startup with a realm and several entities to simplify setup and reduce the learning curve. Use this setup if you don’t have an external identity provider (IdP) and want to run Keycloak inside your cluster, together with additional Camunda components (Console, Web Modeler, Optimize, Management Identity) that are disabled by default in the Helm chart. If you prefer to connect to a Keycloak instance you manage elsewhere, see [Set up the Helm chart with an external Keycloak instance](/self-managed/deployment/helm/configure/authentication-and-authorization/external-keycloak.md). :::info Keycloak deployment changed in Camunda 8.10 Earlier releases bundled an internal Keycloak through the `identityKeycloak` Bitnami subchart (`identityKeycloak.enabled: true`). As of Camunda 8.10 (Helm chart `15.x`), the bundled Bitnami subcharts are removed. Deploy Keycloak in-cluster with the [Keycloak operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#keycloak-deployment) and its database with the [CloudNativePG operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#postgresql-deployment) (or use a managed database), then wire Camunda to it as shown below. For releases up to 8.9, see the [8.9 internal Keycloak guide](https://docs.camunda.io/docs/8.9/self-managed/deployment/helm/configure/authentication-and-authorization/internal-keycloak/). ::: ## Configuration This guide shows you how to: - Deploy an in-cluster Keycloak with the Keycloak operator and connect the Helm chart to it. - Configure the Helm chart with a custom secret for accounts used across all components. - Enable the application components you want to include in the release. - Access all components from your local environment. To use an in-cluster Keycloak instance, complete the following steps: 1. [Create a secret](#create-a-secret) 1. [Deploy Keycloak and connect Camunda](#deploy-keycloak-and-connect-camunda) 1. [Configure Management Identity](#configure-management-identity-and-global-defaults) 1. [Configure components using OIDC](#configure-components-using-oidc) See the [full configuration example](#full-configuration-example) for the complete setup. ### Create a secret Create a secret that contains the Camunda application credentials. For example, the following command creates a `camunda-credentials` secret: ```bash kubectl create secret generic camunda-credentials \ --from-literal=identity-firstuser-password=CHANGE_ME \ --from-literal=identity-connectors-client-token=CHANGE_ME \ --from-literal=identity-optimize-client-token=CHANGE_ME \ --from-literal=identity-orchestration-client-token=CHANGE_ME ``` This secret includes the following keys: - `identity-firstuser-password`: Password for the initial user account in Keycloak (default username `demo`), used to log in to the Camunda web apps. - `identity-connectors-client-token`: Client secret for the Keycloak OIDC client `connectors` used by Connectors. - `identity-optimize-client-token`: Client secret for the Keycloak OIDC client `optimize` used by Optimize. - `identity-orchestration-client-token`: Client secret for the Keycloak OIDC client `orchestration` used by the Orchestration Cluster. The Keycloak administrator credentials and the PostgreSQL credentials for Keycloak, Identity, and Web Modeler are no longer part of this secret. They are provided by the operators that deploy those services, such as the operator-generated `keycloak-initial-admin` secret and the `pg-*-secret` database secrets. See [Operator-based infrastructure](/self-managed/deployment/helm/configure/operator-based-infrastructure.md) for how these are created. ### Deploy Keycloak and connect Camunda Deploy an in-cluster Keycloak with the [Keycloak operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#keycloak-deployment) and a PostgreSQL database with the [CloudNativePG operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#postgresql-deployment). The operator-based guide creates the Keycloak service (`keycloak-service`), the operator-generated `keycloak-initial-admin` secret, and the `pg-keycloak` database cluster. Then connect the Camunda Helm chart to that Keycloak instance through `global.identity.keycloak`: ```yaml global: identity: auth: type: KEYCLOAK publicIssuerUrl: http://keycloak-service:18080/auth/realms/camunda-platform issuerBackendUrl: http://keycloak-service:18080/auth/realms/camunda-platform keycloak: url: protocol: http host: keycloak-service port: "18080" contextPath: /auth realm: /realms/camunda-platform auth: adminUser: temp-admin secret: existingSecret: keycloak-initial-admin existingSecretKey: password ``` The `host`, `port`, admin user, and secret name above match the defaults created by the [operator-based Keycloak deployment](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#keycloak-deployment). Adjust them if you customized that deployment. ### Configure Management Identity and global defaults Management Identity configures the Keycloak instance during startup. For example, it creates the Camunda realm and other required Keycloak entities. If the Camunda realm doesn’t appear in Keycloak after deployment, the setup process did not complete successfully. Management Identity stores its data in a dedicated PostgreSQL database. Connect it to the `pg-identity` cluster created by the [CloudNativePG operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#postgresql-deployment) (or a managed database): ```yaml global: identity: auth: enabled: true security: authentication: method: oidc identity: enabled: true firstUser: secret: existingSecret: "camunda-credentials" existingSecretKey: "identity-firstuser-password" externalDatabase: enabled: true host: pg-identity-rw port: 5432 database: identity username: identity secret: existingSecret: pg-identity-secret existingSecretKey: password ``` `identity.firstUser` defines the first user created in Keycloak. By default, this user is named `demo`. You can override this by setting `identity.firstUser.username`. ### Configure components using OIDC Once Management Identity is configured, you can set up OAuth and OIDC for the remaining components. You can skip components you don’t plan to run. By default, the Orchestration Cluster and Connectors are enabled and must be explicitly disabled if not required. #### Configure Orchestration Cluster The Orchestration cluster treats the in-cluster Keycloak as any other external IdP and connects through OIDC. Orchestration Cluster configuration: ```yaml orchestration: security: authentication: oidc: secret: existingSecret: "camunda-credentials" existingSecretKey: "identity-orchestration-client-token" ``` #### Configure Connectors Connectors must be configured in a similar fashion with OIDC client secret to access the Orchestration Cluster APIs. Connectors component configuration: ```yaml connectors: security: authentication: oidc: secret: existingSecret: "camunda-credentials" existingSecretKey: "identity-connectors-client-token" ``` #### Configure Optimize Optimize component configuration: ```yaml global: identity: auth: optimize: secret: existingSecret: "camunda-credentials" existingSecretKey: "identity-optimize-client-token" optimize: enabled: true ``` Add the section under `global.identity.auth` to the existing section you created when configuring Management Identity. #### Configure Camunda Hub {#configure-web-modeler-and-console-camunda-hub} In Camunda 8.10, Console and Web Modeler are deployed together as sub-components of **Camunda Hub**. Enable both by setting `camundaHub.enabled: true`. This replaces the former `console.enabled` and `webModeler.enabled` flags and takes precedence over them. Web Modeler connects to its own PostgreSQL database and requires a redirect URL for OIDC authentication. Console is a public client and needs no additional OIDC configuration. Web Modeler component configuration: ```yaml global: identity: auth: webModeler: redirectUrl: "http://localhost:8070" # Change this when using a domain camundaHub: enabled: true # Deploys both Console and Web Modeler restapi: mail: fromAddress: noreply@example.com # Connect Camunda Hub to the operator-managed PostgreSQL cluster (pg-hub) externalDatabase: host: pg-hub-rw port: 5432 database: hub username: hub secret: existingSecret: pg-hub-secret existingSecretKey: password ``` :::important Redirect URL configuration The `redirectUrl` parameter is **required** for Web Modeler authentication. The default value is `http://localhost:8070`, which works for local port-forwarding setups. If you're using a domain or Ingress to expose Web Modeler, you **must** update this value to match your Web Modeler URL: - With Ingress: `https://your-domain.com/modeler` (if using a context path) - Without context path: `https://modeler.your-domain.com` Mismatched redirect URLs will cause authentication failures that are difficult to debug. ::: You can update `camundaHub.restapi.mail.fromAddress` with an address suitable for your environment. This address appears as the sender in emails sent by Web Modeler. For more details on configuring email delivery, see the [Camunda Hub section in Enable additional Camunda components](../enable-additional-components.md#camunda-hub). #### Configure Console Console is deployed as part of Camunda Hub, which you enabled with `camundaHub.enabled: true` in the [previous step](#configure-web-modeler-and-console-camunda-hub). Since Console is a public client, it does not need to be defined under `global.identity.auth` and requires no additional configuration. ### Full configuration example The following example shows a complete configuration for deploying Camunda with an in-cluster, operator-managed Keycloak and operator-managed PostgreSQL databases: ```yaml global: identity: auth: enabled: true type: KEYCLOAK publicIssuerUrl: http://keycloak-service:18080/auth/realms/camunda-platform issuerBackendUrl: http://keycloak-service:18080/auth/realms/camunda-platform optimize: secret: existingSecret: "camunda-credentials" existingSecretKey: "identity-optimize-client-token" webModeler: # Default: http://localhost:8070 # Update this when using a domain/Ingress, e.g., https://your-domain.com/modeler redirectUrl: "http://localhost:8070" keycloak: url: protocol: http host: keycloak-service port: "18080" contextPath: /auth realm: /realms/camunda-platform auth: adminUser: temp-admin secret: existingSecret: keycloak-initial-admin existingSecretKey: password security: authentication: method: oidc identity: enabled: true firstUser: secret: existingSecret: "camunda-credentials" existingSecretKey: "identity-firstuser-password" externalDatabase: enabled: true host: pg-identity-rw port: 5432 database: identity username: identity secret: existingSecret: pg-identity-secret existingSecretKey: password optimize: enabled: true connectors: security: authentication: oidc: secret: existingSecret: "camunda-credentials" existingSecretKey: "identity-connectors-client-token" camundaHub: enabled: true # Deploys both Console and Web Modeler restapi: mail: fromAddress: noreply@example.com externalDatabase: host: pg-hub-rw port: 5432 database: hub username: hub secret: existingSecret: pg-hub-secret existingSecretKey: password orchestration: security: authentication: oidc: secret: existingSecret: "camunda-credentials" existingSecretKey: "identity-orchestration-client-token" ``` In this setup, Keycloak and PostgreSQL are deployed and managed by their operators, while the Camunda Helm chart handles the application-side Keycloak configuration automatically, including creating OIDC or OAuth clients and linking components. Your values file primarily connects to the operator-managed services, enables components, and defines client secrets. To review how each component is configured and which OIDC clients are used: - Run `kubectl get pods` and `kubectl get configmap`, then use `kubectl describe` to inspect component configurations. - Log into Keycloak (using your administrative user) to review the OIDC client setup ## Connect to the cluster ### Local access with port forwarding After applying this configuration, use the following `kubectl port-forward` commands to access the APIs and UIs from your localhost. Because Keycloak runs as a separate operator-managed service, also port-forward the Keycloak service: ```bash # Keycloak Operator service kubectl port-forward svc/keycloak-service 18080:18080 # Management Identity kubectl port-forward svc/camunda-identity 8084:80 # Orchestration Cluster kubectl port-forward svc/camunda-zeebe-gateway 8080:8080 kubectl port-forward svc/camunda-zeebe-gateway 26500:26500 # Connectors kubectl port-forward svc/camunda-connectors 8086:8080 # Optimize kubectl port-forward svc/camunda-optimize 8083:80 # Web Modeler kubectl port-forward svc/camunda-web-modeler-restapi 8070:80 kubectl port-forward svc/camunda-web-modeler-websockets 8085:80 # Console kubectl port-forward svc/camunda-console 8087:80 ``` Once port forwarding is active, access each component through `http://localhost:`. For example: - Web Modeler: `http://localhost:8070` - Orchestration Cluster: `http://localhost:8080` Reach Keycloak at `http://keycloak-service:18080/auth` rather than through `localhost`. The configuration above sets the token issuer to `http://keycloak-service:18080/auth/realms/camunda-platform`, so your browser is redirected to that hostname during login and must resolve it to the forwarded port. Map it to the loopback address first: ```bash echo "127.0.0.1 keycloak-service" | sudo tee -a /etc/hosts ``` Log in with username `demo` and the password you defined under `identity-firstuser-password`. :::note Default URLs and port forwarding The configuration shown above uses a default `redirectUrl` of `http://localhost:8070` for Web Modeler, which matches the port-forwarding setup. The Keycloak issuer stays on the in-cluster service name and needs the `/etc/hosts` entry above. If you don't use port forwarding and instead expose components via Ingress or a domain, you **must** update the `redirectUrl` parameters under `global.identity.auth` to match your actual URLs. See [Ingress setup](/self-managed/deployment/helm/configure/ingress/ingress-setup.md) for domain-based configuration examples. ::: ### Domain-based access with Ingress If you're using Ingress to expose components via a domain (instead of port forwarding), update the redirect URLs in your Helm values: ```yaml global: identity: auth: webModeler: redirectUrl: "https://your-domain.com/modeler" # Or https://modeler.your-domain.com # Update other component URLs as needed ``` For complete Ingress configuration, see [Ingress setup](/self-managed/deployment/helm/configure/ingress/ingress-setup.md). ## External identity provider Instead of using an in-cluster Keycloak instance, you can configure Camunda to connect to an external IdP, such as an external Keycloak, Microsoft Entra ID, or Okta. See [Set up the Helm chart with an external OIDC provider](/self-managed/deployment/helm/configure/authentication-and-authorization/external-oidc-provider.md) for details. --- ## JWT token claims reference Use this reference to understand the structure of JWT access tokens and identify the claims your OIDC provider uses. This information is required when configuring Camunda 8 to authenticate with an external identity provider. ## Obtain a test token Request an access token using your OIDC client credentials: ```bash curl -X POST 'https://your-provider.example.com/oauth/token' \ -H 'Content-Type: application/x-www-form-urlencoded' \ -d 'client_id=' \ -d 'client_secret=' \ -d 'grant_type=client_credentials' ``` The response includes an `access_token` field containing the JWT. A token from the client credentials grant identifies the client, not a user, so it can't show you the user identification claim. To find that claim, decode a token that was issued by an interactive login (authorization code flow) instead. ## Decode the token Decode the JWT to inspect its claims. ### Command line (Linux/macOS) ```bash echo "" | jq -R 'split(".")[1] | gsub("-";"+") | gsub("_";"/") | @base64d | fromjson' ``` JWT payloads use unpadded base64url encoding. The command converts the characters `-` and `_` to `+` and `/` before decoding, because a plain `base64 -d` can fail on JWT payloads. ### Online tools You can also use online tools such as [jwt.io](https://jwt.io) by pasting the token value. :::caution Security warning Only decode tokens from **test or development environments** using online tools. Never paste production tokens or tokens containing sensitive information into third-party sites, as they may be logged or leaked. ::: ## Required claims When configuring OIDC authentication, Camunda requires you to identify the following claims in the token. ### User identification claim Used to uniquely identify users during interactive login. - **Helm configuration:** `usernameClaim` - **Common claim names:** `email`, `preferred_username`, `sub`, `upn`, `unique_name` ### Client identification claim Used for machine-to-machine authentication to identify the calling client. - **Helm configuration:** `clientIdClaim` - **Common claim names:** `client_id`, `azp`, `appid`, `clientId` ### Audience claim Specifies the intended audience of the token. - **Helm configuration:** `audience` - **Claim name:** `aud` - **Typical value:** Client ID or a custom value configured in the provider. Use a distinct resource audience for each component by default. ## Common claim patterns by provider | Provider | User claim | Client claim | Audience default | | --------------- | ------------------------------- | -------------------- | ------------------------------------------- | | Microsoft Entra | `preferred_username` | `azp` | Client ID | | Keycloak | `email` or `preferred_username` | `azp` or `client_id` | May require configuration | | Auth0 | `email` | `client_id` | Client ID | | Okta | `email` | `client_id` | Client ID | | PingFederate | `sub` | `client_id` | Shared Access Token Manager for all clients | | PingOne | `sub` | `client_id` | Per-application, via resource/scope grants | ## Verify audience configuration Each component should have its own resource audience by default. Supported integrations can require a component to accept another component's audience. For the per-component Helm values and exceptions, see [Assign a unique audience to each component](./generic-oidc-provider.md#assign-a-unique-audience-to-each-component). 1. Decide the audience for the component, and configure your provider to issue it. 2. Decode a test token issued for that component. 3. Confirm the `aud` claim contains the value you configured. If another component accepts the same value, confirm a supported integration requires that trust relationship. :::warning The audience claim is required for token validation. Camunda rejects tokens that do not include the expected audience value. ::: ### If the token does not include the expected audience - Review your OIDC provider’s documentation for configuring token audiences. - Some providers require explicit audience configuration on the client. - Keycloak may default to `aud: "account"` and require additional setup. See [External Keycloak](./external-keycloak.md) for details. --- ## Keycloak support, security fixes, and upgrades Learn who is responsible for Keycloak, how Keycloak delivers security fixes, and what to check before you upgrade. This page applies to every Keycloak instance that you use with Camunda 8 Self-Managed, whatever the deployment method. ## Understand the support scope Keycloak is an open-source project and an external dependency of Camunda 8 Self-Managed. You are responsible for operating, upgrading, patching, and securing Keycloak, regardless of how you deploy it. Camunda supports the integration and configuration of Keycloak with Camunda 8. The Keycloak community project does not give long-term support for a specific version. If you need support for Keycloak itself or backported fixes for a specific version, use a commercial Keycloak distribution, for example the [Red Hat build of Keycloak](https://access.redhat.com/products/red-hat-build-keycloak/). For the Keycloak versions that Camunda supports, see [supported environments](/reference/supported-environments.md). ## Understand Keycloak security fixes The Keycloak project fixes vulnerabilities only in the current `major.minor` release. For lower-severity vulnerabilities, the fix can come in the next `major.minor` release. The Keycloak project does not usually issue CVE fixes for experimental features. For details, see the [Keycloak security policy](https://github.com/keycloak/keycloak/blob/main/SECURITY.md#supported-versions). As a result: - Only the latest Keycloak patch release contains all available security fixes. - Older Keycloak `major.minor` lines do not receive new fixes. - A vulnerability scanner can report a CVE in your Keycloak image until you update to a Keycloak release that contains the fix. To get security fixes, update Keycloak regularly to the latest patch release of the current Keycloak line. Do not enable experimental Keycloak features in production. ## Choose a Camunda Keycloak image Camunda provides Keycloak images that add Camunda-specific content, such as the Camunda Identity login theme and the AWS JDBC wrapper. The source is in the [camunda/keycloak](https://github.com/camunda/keycloak/blob/main/README.md) repository, which lists all tags. | Image | Base image | Tags | | --------------------------------------------- | --------------------------------- | ---------------------------------------------------- | | `docker.io/camunda/keycloak` | Official Keycloak image (Quay.io) | `quay-*`, `quay-optimized-*`, `latest` | | `registry.camunda.cloud/keycloak-ee/keycloak` | Bitnami Premium image | `bitnami-ee-*`, and the same tags without the prefix | The `registry.camunda.cloud/keycloak-ee/keycloak` image is available to enterprise customers. Availability of an image does not imply support for Keycloak itself. These images depend on the Keycloak releases that the base image vendors publish. Bitnami only packages the Keycloak releases of the Keycloak community project. The vendors give no service-level agreement (SLA) and do not guarantee a fix for any vulnerability. Camunda handles these images as follows: | Camunda does | Camunda does not | | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | Rebuild the images on a best-effort basis when the base image vendor publishes a release | Provide an SLA or patch guarantee for the images | | Rebuild only the latest release of each Keycloak major version | Patch Keycloak or its dependencies | | Keep tags for older Keycloak releases available | Update older tags or take responsibility for vulnerabilities in Keycloak or its dependencies | ### Bitnami-based public images The public Bitnami-based `docker.io/camunda/keycloak` images receive no updates, including security fixes. These images use the `bitnami-*` tags, `latest-bitnami`, and the tags without a prefix, such as `26.3.3` and the `26.3.3-*` rebuild tags. `26.3.3` was the default image of the bundled `identityKeycloak` subchart in Camunda 8.7 to 8.9. For details, see [Notice 67](/reference/notices.md#notice-67). To move to a maintained Keycloak, deploy it with the [Keycloak Operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#keycloak-deployment). If you still use the bundled `identityKeycloak` subchart, see [migrate from Bitnami subcharts](/self-managed/deployment/helm/operational-tasks/migration-from-bitnami/index.md). ## Prepare a Keycloak upgrade These steps are your responsibility. Camunda does not run or upgrade Keycloak for you. A Keycloak upgrade changes the database schema. Keycloak does not support a database rollback. Prepare the upgrade with the following steps: 1. Read the [Keycloak upgrading guide](https://www.keycloak.org/docs/latest/upgrading/) and the release notes for each version between your current version and the target version. 1. Check that [supported environments](/reference/supported-environments.md) lists the target Keycloak version for your Camunda version. 1. Back up the Keycloak database. To roll back, you must restore the old Keycloak version and this database backup. 1. Back up your Keycloak configuration, custom themes, and custom providers. 1. Plan downtime for minor and major upgrades. You must stop all Keycloak nodes before Keycloak migrates the database. 1. For patch releases, use rolling updates if you run Keycloak 26.6.0 or later. For details, see [Keycloak rolling updates](https://www.keycloak.org/server/update-compatibility). 1. Plan enough time for the database migration on large databases, as described in the same upgrading guide. 1. If you use custom themes or providers, check that they work with the target Keycloak version. 1. Test the upgrade in a non-production environment first. After the upgrade, check that users can log in to the Camunda web applications and that Camunda components can get tokens from Keycloak. If you deploy Keycloak with the Keycloak Operator, also follow the [Keycloak Operator documentation](https://www.keycloak.org/operator/installation) to upgrade the operator. --- ## Set up the Helm chart with an external Microsoft Entra tenant This guide shows you how to configure the Helm chart to use a Microsoft Entra tenant, with each Camunda component using a dedicated OIDC or OAuth client. :::info Bitnami subcharts removed in Camunda 8.10 Earlier releases bundled PostgreSQL through Bitnami subcharts (`identityPostgresql`, `webModelerPostgresql`). As of Camunda 8.10 (Helm chart `15.x`), the bundled Bitnami subcharts are removed: provide PostgreSQL with the [CloudNativePG operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#postgresql-deployment) or a managed database, as shown in the examples below. ::: ## Prerequisites Before you begin, ensure you have: - Access to a Microsoft Entra tenant with permission to create applications and app registrations - The ID of your tenant - An understanding of the structure and claims of access tokens in Entra - When you connect Management Identity to an OIDC provider, you need a database regardless of feature flags. Chart `15.x` no longer bundles one, so provision it with the [CloudNativePG operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#postgresql-deployment) or a managed database and connect it through `identity.externalDatabase`, as shown in the examples below. See also [use external PostgreSQL](/self-managed/deployment/helm/configure/database/using-existing-postgres.md). If your Entra issuer presents a certificate signed by a private or internal certificate authority, Camunda components don't trust certificates signed by that CA by default. Configure [TLS trust](/self-managed/deployment/helm/configure/tls.md#external-oidc-issuer-with-private-ca) to avoid `PKIX path building failed` errors when components connect to the issuer. ## Configuration To use Microsoft Entra, complete the following steps: 1. [Ensure Entra prerequisites](#ensure-entra-prerequisites) 1. [Create applications in Entra](#create-applications-in-entra) 1. [Create secrets](#create-secrets) 1. [Configure components using OIDC](#configure-components-using-oidc) 1. [Configure machine-to-machine (M2M) API access](#configure-machine-to-machine-m2m-api-access) See the [full configuration example](#full-configuration-example) for the complete setup. ### Ensure Entra prerequisites For authentication, the Camunda components use the following scopes: `email`, `openid`, `offline_access`, `profile`, and `/.default`. :::tip Optional scopes The `offline_access` scope is optional. If this scope is included, your OIDC provider issues a refresh token to Camunda components on user login. The components use the refresh token to renew the user's access token when it expires, so that sessions remain active without requiring the user to log in again. If `offline_access` is not included, users will be redirected to the OIDC provider for re-authentication whenever their access token expires. For more information, see the [OpenID Connect Core specification](https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess). If a refresh fails because the refresh token is no longer valid (for example, because it has expired or been revoked and the provider returns `invalid_grant`) the user is logged out and must sign in again. If the refresh fails because the provider is unavailable or returns a server error, the session is retained, and the request fails with an HTTP `503 Service Unavailable` response. The first request after the provider recovers refreshes the token, so the user does not need to sign in again. ::: To allow users to successfully authenticate with Entra ID, you must either configure an [admin consent workflow](https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-admin-consent-workflow) or grant consent on behalf of your users using [admin consent](https://learn.microsoft.com/en-gb/entra/identity/enterprise-apps/user-admin-consent-overview#admin-consent). The applications you configure in this guide must support the following `grant_type` values: - To create an M2M token: `client_credentials` (response contains an access token) - To renew a token using a refresh token: `refresh_token` - To create a token via authorization code flow: `authorization_code` (response contains access and refresh tokens) These grant types are enabled by default, but they may be restricted by custom policies in your organization. :::note Disable UserInfo for Entra Microsoft Entra's `/userinfo` endpoint is served by Microsoft Graph, which requires the access token to have a Graph audience. The `/.default` scope used in this guide audiences the token to the Camunda client instead, so Entra always rejects the call. Set `user-info-enabled: false` for this provider to skip it. See [troubleshoot OIDC authentication](./troubleshooting-oidc.md#userinfo-endpoint-rejects-the-access-token) for details. ::: ### Create applications in Entra Before configuring Camunda, create the following app registrations that map to Camunda components. Application type **Web**: - Management Identity (``) - Orchestration Cluster (``) - Optimize (``) - Hub API (``) Application type **Single-page application**: - Hub UI (``) Register these as five separate applications. Camunda uses each application's client ID as that component's audience, so two components sharing a registration also share an audience, and a token issued for one is accepted by the other. Entra issues the same tenant-scoped `iss` claim for every application in your tenant, which makes the audience the only value that distinguishes one component from another. For **each** of the components above: 1. In the Entra ID admin center, [register the application](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app). 1. On the application's **Overview** page, note the **Client ID**. 1. In the app registration, [configure a platform](https://learn.microsoft.com/en-gb/entra/identity-platform/quickstart-register-app#configure-platform-settings) that matches the component: - **Web**: Management Identity, Orchestration Cluster, Optimize, Hub API - **Single-page application**: Hub UI 1. Add the component's redirect URI from [the table below](#redirect-uris-per-camunda-component). :::note Redirect URIs are an allowlist. Only the URIs you define are permitted as redirection targets after authentication. This ensures that tokens and authorization codes are only sent to approved destinations. ::: 1. For app registrations of type **Web**, [create a new client secret](https://learn.microsoft.com/en-gb/entra/identity-platform/quickstart-register-app?tabs=client-secret#add-credentials), and record the secret **value**. You do not need the secret ID. 1. Enable the Entra `v2.0` API by opening the application's [manifest](https://learn.microsoft.com/en-us/entra/identity-platform/reference-microsoft-graph-app-manifest#configure-the-app-manifest-in-the-microsoft-entra-admin-center) and setting the [`requestedAccessTokenVersion`](https://learn.microsoft.com/en-us/entra/identity-platform/reference-microsoft-graph-app-manifest#api-attribute) property under `api` to `2`: ```json api: { ... "requestedAccessTokenVersion": 2, ... } ``` 1. In **Token configuration**, [add the optional claim](https://learn.microsoft.com/en-us/entra/identity-platform/optional-claims?tabs=appui) `preferred_username` to **both** the access token and the ID token. Entra labels this claim optional, but Camunda requires it. Management Identity reads user claims directly from the access token rather than from a userinfo call. If `preferred_username` is missing from the access token, Management Identity finds no matching claim and grants no roles. The user authenticates successfully, then immediately sees a `403 unauthorized` error, and users appear in Operate and Tasklist with an opaque identifier instead of their name. To use a different claim that uniquely identifies your users, see [Configure Management Identity](#configure-management-identity). Whichever claim you choose, add it as an optional claim on both token types, since Entra doesn't include it by default. #### Redirect URIs per Camunda component | Component | Redirect URI for the Entra app registration | Redirect URI for local deployment | | --------------------- | -------------------------------------------- | --------------------------------------------------- | | Management Identity | `/auth/login-callback` | `http://localhost:8084/auth/login-callback` | | Orchestration Cluster | `/sso-callback` | `http://localhost:8080/sso-callback` | | Optimize | `/api/authentication/callback` | `http://localhost:8083/api/authentication/callback` | | Hub UI | `/login-callback` | `http://localhost:8070/login-callback` | Replace each `*_URL` placeholder with the base URL (in the format `://:/`) that will be accessible from your users’ browsers. If you plan to expose the services only on `localhost` (as described later in this guide), you can use the URIs in the local deployment column directly. ### Create secrets Create a secret in your Kubernetes namespace that contains all OIDC client secrets: ``` kubectl create secret generic entra-credentials \ --from-literal=identity-client-secret="" \ --from-literal=orchestration-cluster-client-secret="" \ --from-literal=optimize-client-secret="" \ --from-literal=hub-api-client-secret="" ``` :::info In Microsoft Entra, the term _application secret_ is used. In Camunda configuration, this value is referred to as a _client secret_ to align with OIDC/OAuth standards. ::: :::info The secret key `hub-api-client-secret` is not used elsewhere in this guide. This client is intended for your own use if you want to access the [Web Modeler API](/apis-tools/web-modeler-api/authentication.md) programmatically. ::: The PostgreSQL credentials for Management Identity and Camunda Hub are no longer created here. They are provided by the operator (or managed database) that hosts each database, such as the `pg-identity-secret` and `pg-hub-secret` created by the [CloudNativePG operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#postgresql-deployment). For additional options on how to create and reference Kubernetes secrets (for example using YAML manifests or consolidated secrets), see [External Kubernetes secrets](/self-managed/deployment/helm/configure/secret-management.md#method-2-external-kubernetes-secrets-recommended). ### Configure components using OIDC With the OIDC clients and cluster secrets in place, configure OAuth and OIDC for the components. You can skip components you don’t plan to run. Keep in mind that the Orchestration Cluster and Connectors are enabled by default, so you must explicitly disable them if not needed. #### Global configuration Start with the following global configuration, which provides defaults for all components: ```yaml global: identity: auth: enabled: true issuer: https://login.microsoftonline.com//v2.0 issuerBackendUrl: https://login.microsoftonline.com//v2.0 authUrl: https://login.microsoftonline.com//oauth2/v2.0/authorize tokenUrl: https://login.microsoftonline.com//oauth2/v2.0/token jwksUrl: https://login.microsoftonline.com//discovery/v2.0/keys type: "MICROSOFT" security: authentication: method: oidc ``` Replace `` with your Microsoft Entra tenant ID. You’ll use this convention throughout the rest of this guide. #### Configure Orchestration Cluster Add the following configuration for the Orchestration Cluster: ```yaml orchestration: security: authentication: oidc: clientId: "" audience: "" usernameClaim: preferred_username clientIdClaim: azp preferUsernameClaim: true redirectUrl: "" scope: - openid - profile - offline_access - "/.default" secret: existingSecret: "entra-credentials" existingSecretKey: "orchestration-cluster-client-secret" initialization: defaultRoles: admin: users: - "" connectors: clients: - "" env: - name: CAMUNDA_SECURITY_AUTHENTICATION_OIDC_USER_INFO_ENABLED value: "false" - name: SERVER_MAX_HTTP_REQUEST_HEADER_SIZE value: "65536" ``` Replace `` with the base URL of the Orchestration Cluster as it will be reachable from your users’ browsers. For local deployment, this is `http://localhost:8080`. The two environment variables above are both required with Entra. Neither has a dedicated Helm value, so both are passed through `orchestration.env` as Spring Boot properties (`camunda.security.authentication.oidc.user-info-enabled` and `server.max-http-request-header-size`). `CAMUNDA_SECURITY_AUTHENTICATION_OIDC_USER_INFO_ENABLED` must be `false`. By default, the Orchestration Cluster calls the OIDC userinfo endpoint after authentication to augment token claims. Entra hosts its userinfo endpoint on Microsoft Graph (`graph.microsoft.com/oidc/userinfo`) rather than on the authorization server, and it requires a Graph API access token instead of the OIDC access token Camunda holds, so the call fails. Setting this variable to `false` tells the Orchestration Cluster to rely on the claims already present in the token. This is also Microsoft's recommendation, since the ID token is a superset of what userinfo returns. `SERVER_MAX_HTTP_REQUEST_HEADER_SIZE` raises the maximum header size to 64 KB. Microsoft's authorization codes are longer than those issued by most other providers. Combined with session cookies from an existing session, the total HTTP header size can exceed Tomcat's 8 KB default, which causes a plain Tomcat HTTP 400 error page before Spring Security processes the request. See [Request header is too large](./troubleshooting-oidc.md#request-header-is-too-large) if you hit this after deploying. `usernameClaim` defines which claim in the access token identifies the user. `clientIdClaim` defines which claim identifies the calling client. By default: - `preferred_username` carries the user’s email address. - `azp` carries the client ID in Entra. You can adjust these values if your organization uses different claim mappings. For more information, see the [Orchestration Cluster OIDC configuration guide](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md#step-1-configure-the-oidc-client-id-claim). :::note Username display in Hub (Helm) With Helm defaults, usernames are typically resolved from `preferred_username`. If you want Hub to use the `name` claim instead (for example, to show display names), set `CAMUNDA_IDENTITY_USERNAMECLAIM=name` for the Hub `restapi` environment. See [Identity/Keycloak configuration](/self-managed/components/hub/configuration/properties.md#identity--keycloak). ::: #### Configure Connectors Add the following configuration for Connectors: ```yaml connectors: security: authentication: oidc: clientId: "" audience: "" tokenScope: "/.default" secret: existingSecret: "entra-credentials" existingSecretKey: "orchestration-cluster-client-secret" ``` #### Configure Management Identity Add the following configuration for Management Identity: ```yaml global: identity: auth: identity: clientId: "" audience: "" initialClaimName: preferred_username initialClaimValue: "" secret: existingSecret: "entra-credentials" existingSecretKey: "identity-client-secret" identity: enabled: true externalDatabase: enabled: true host: pg-identity-rw port: 5432 database: identity username: identity secret: existingSecret: pg-identity-secret existingSecretKey: password ``` Replace `` with the base URL of Management Identity as it will be reachable from your users' browser. For local deployment, use `http://localhost:8084`. Management Identity requires an externally managed PostgreSQL database. Provision the database before deploying, and adapt the connection values and secret references to your setup. For the full parameter list, see [Use external PostgreSQL](../database/using-existing-postgres.md). - `initialClaimName` defines which claim in the access token identifies the initial administrative user. - `initialClaimValue` defines the value of that claim that grants administrative access to Management Identity. Choose the claim based on whether you value readability or stability: | Claim | Trade-off | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `preferred_username` | The user's UPN. Readable and self-documenting, which makes it easy to work with during initial setup. Requires the optional claim to be added. | | `oid` | The user's Entra Object ID. Always present without optional claim configuration, and unchanged if the user's email address changes. | Because `initialClaimValue` is applied only on first startup and can't be updated through Helm afterward, `oid` is the more stable choice for production. :::danger Once configured, the initial claim name and value cannot be changed using environment variables or Helm values. To update them, modify the Identity PostgreSQL database directly. ::: :::tip If Optimize is not enabled, add the following environment variable to ensure Management Identity starts successfully: ``` identity: env: - name: CAMUNDA_IDENTITY_AUDIENCE value: "" ``` ::: #### Configure Optimize Add the following configuration for Optimize: ```yaml global: identity: auth: optimize: clientId: "" audience: "" redirectUrl: "" secret: existingSecret: "entra-credentials" existingSecretKey: "optimize-client-secret" optimize: enabled: true ``` Replace `` with the base URL of Optimize as it will be reachable from your users' browser. For local deployment, use `http://localhost:8083`. #### Configure Hub Add the following configuration for Hub: :::note If you want Hub to resolve usernames from the `name` claim instead of `preferred_username`, add `CAMUNDA_IDENTITY_USERNAMECLAIM=name` to the Hub `restapi` environment configuration. ::: ```yaml global: identity: auth: camundaHub: clientId: "" clientApiAudience: "" publicApiAudience: "" redirectUrl: "" camundaHub: enabled: true # Deploys both Console and Web Modeler restapi: mail: fromAddress: noreply@example.com externalDatabase: host: pg-hub-rw port: 5432 database: hub username: hub secret: existingSecret: pg-hub-secret existingSecretKey: password ``` Replace `` with the base URL of Hub as it will be reachable from your users' browser. For local deployment, use `http://localhost:8070`. Hub requires an externally managed PostgreSQL database, configured under `camundaHub.restapi.externalDatabase`. Provision the database before deploying. For the full parameter list, see [Use external PostgreSQL](../database/using-existing-postgres.md). You can update `camundaHub.restapi.mail.fromAddress` with an address suitable for your environment. This address appears as the sender in emails sent by Hub. For more details on configuring email delivery, see the [Camunda Hub section in Enable additional Camunda components](../enable-additional-components.md#camunda-hub). ### Full configuration example The following example shows a full configuration to enable Microsoft Entra with an externally managed Elasticsearch cluster. Replace `` with the hostname of your cluster. ```yaml global: identity: auth: enabled: true issuer: https://login.microsoftonline.com//v2.0 issuerBackendUrl: https://login.microsoftonline.com//v2.0 authUrl: https://login.microsoftonline.com//oauth2/v2.0/authorize tokenUrl: https://login.microsoftonline.com//oauth2/v2.0/token jwksUrl: https://login.microsoftonline.com//discovery/v2.0/keys type: "MICROSOFT" identity: clientId: "" audience: "" initialClaimName: preferred_username initialClaimValue: "" secret: existingSecret: "entra-credentials" existingSecretKey: "identity-client-secret" optimize: clientId: "" audience: "" redirectUrl: "" secret: existingSecret: "entra-credentials" existingSecretKey: "optimize-client-secret" camundaHub: clientId: "" clientApiAudience: "" publicApiAudience: "" redirectUrl: "" security: authentication: method: oidc orchestration: data: secondaryStorage: type: elasticsearch elasticsearch: url: "https://:9200" security: authentication: oidc: clientId: "" audience: "" usernameClaim: preferred_username clientIdClaim: azp preferUsernameClaim: true redirectUrl: "" scope: - openid - profile - offline_access - "/.default" secret: existingSecret: "entra-credentials" existingSecretKey: "orchestration-cluster-client-secret" initialization: defaultRoles: admin: users: - "" connectors: clients: - "" env: - name: CAMUNDA_SECURITY_AUTHENTICATION_OIDC_USER_INFO_ENABLED value: "false" - name: SERVER_MAX_HTTP_REQUEST_HEADER_SIZE value: "65536" connectors: security: authentication: oidc: clientId: "" audience: "" tokenScope: "/.default" secret: existingSecret: "entra-credentials" existingSecretKey: "orchestration-cluster-client-secret" identity: enabled: true externalDatabase: enabled: true host: pg-identity-rw port: 5432 database: identity username: identity secret: existingSecret: pg-identity-secret existingSecretKey: password optimize: enabled: true database: elasticsearch: enabled: true external: true url: protocol: https host: "" port: 9200 camundaHub: enabled: true # Deploys both Console and Web Modeler restapi: mail: fromAddress: noreply@example.com externalDatabase: host: pg-hub-rw port: 5432 database: hub username: hub secret: existingSecret: pg-hub-secret existingSecretKey: password ``` ### Connect to the cluster After applying this configuration, use the following `kubectl port-forward` commands to access the APIs and UIs from your localhost: ```bash # Management Identity kubectl port-forward svc/camunda-identity 8084:80 # Orchestration Cluster kubectl port-forward svc/camunda-zeebe-gateway 8080:8080 kubectl port-forward svc/camunda-zeebe-gateway 26500:26500 # Connectors kubectl port-forward svc/camunda-connectors 8086:8080 # Optimize kubectl port-forward svc/camunda-optimize 8083:80 # Hub kubectl port-forward svc/camunda-web-modeler-restapi 8070:80 kubectl port-forward svc/camunda-web-modeler-websockets 8085:80 ``` Once port forwarding is active, access each component through `http://localhost:`. For example: - Orchestration Cluster: `http://localhost:8080` (redirects you to Entra for login) - Management Identity: `http://localhost:8084` - Hub: `http://localhost:8070` ## Configure machine-to-machine (M2M) API access Job workers, Connectors, and other applications that call the Orchestration Cluster REST or gRPC API without a user present use the OAuth client credentials grant instead of interactive login. In this section, you register a dedicated Entra application for M2M access and configure a Camunda client to use it. For the general, provider-agnostic explanation of this flow, see [machine-to-machine (M2M) API access](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md#machine-to-machine-m2m-api-access). ### Register an M2M application in Entra 1. In the Entra ID admin center, [register a new application](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app) for your job worker or Connector. You do not need to configure a redirect URI or a platform type, since this application never redirects a user's browser. 1. On the application's **Overview** page, note the **Client ID**. This value is your M2M client's `clientId`. 1. [Create a new client secret](https://learn.microsoft.com/en-gb/entra/identity-platform/quickstart-register-app?tabs=client-secret#add-credentials) and record the secret **value**. 1. Confirm the application supports the `client_credentials` grant type. This is enabled by default; see [Ensure Entra prerequisites](#ensure-entra-prerequisites). 1. Grant the application access to the Orchestration Cluster API. Reuse the `/.default` scope from the Orchestration Cluster app registration, or [expose an API permission](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-configure-app-expose-web-apis) specific to your M2M client and grant admin consent for it. ### Configure the Camunda client Configure your job worker, Connector runtime, or custom application to request tokens from Entra using client credentials. Camunda clients (the Java client, the Spring Boot starter, and the Connector runtime) read these settings from `CAMUNDA_CLIENT_AUTH_*` environment variables or the equivalent `camunda.client.auth.*` properties: ``` CAMUNDA_CLIENT_AUTH_METHOD=oidc CAMUNDA_CLIENT_AUTH_CLIENTID= CAMUNDA_CLIENT_AUTH_CLIENTSECRET= CAMUNDA_CLIENT_AUTH_TOKENURL=https://login.microsoftonline.com//oauth2/v2.0/token CAMUNDA_CLIENT_AUTH_AUDIENCE= CAMUNDA_CLIENT_AUTH_SCOPE=/.default ``` ```yaml camunda: client: auth: method: oidc client-id: client-secret: token-url: https://login.microsoftonline.com//oauth2/v2.0/token audience: scope: /.default ``` Replace `` with the Orchestration Cluster application's client ID from [Create applications in Entra](#create-applications-in-entra), and `` with your Microsoft Entra tenant ID. :::note For the client credentials flow, request the `/.default` scope. This tells Entra to issue a token for the statically configured application permissions on the API rather than the delegated permissions used during interactive login. ::: The Orchestration Cluster identifies this client using the `azp` claim, configured as `clientIdClaim: azp` in [Configure Orchestration Cluster](#configure-orchestration-cluster). By default, requests from this client can only retrieve the cluster topology. To grant it access to other APIs, [configure authorizations](/components/concepts/access-control/authorizations.md) for its client ID. ## Troubleshooting For issues common to any OIDC provider (invalid redirect URI, audience mismatch, missing claims, pods not starting), see [Troubleshoot OIDC authentication](./troubleshooting-oidc.md). The following are specific to Microsoft Entra: **`AADSTS700016: Application not found in the directory`** The `clientId` in your Helm values doesn't match a registered application in your tenant. Verify each `clientId` exactly matches the **Application (client) ID** on the app registration's **Overview** page, and that the app is registered in the tenant identified by your ``. **`AADSTS50011: Redirect URI mismatch`** The redirect URI Camunda sent doesn't match any URI registered for that app. Verify `redirectUrl` in your Helm configuration exactly matches the redirect URI configured in Entra, including the path suffix, for example `/auth/login-callback` or `/sso-callback`. **`401` with `jwt issuer invalid` or `"The iss claim is not valid"`** The app registration is issuing v1.0 tokens (issuer `https://sts.windows.net/...`) instead of v2.0 tokens (issuer `https://login.microsoftonline.com/.../v2.0`). Confirm `api.requestedAccessTokenVersion` is set to `2` in the app's manifest (see [Create applications in Entra](#create-applications-in-entra)). This applies to all app registrations, including single-page applications. **Service-to-service `401` with `clientId claim could not be found`** If Connectors or another machine-to-machine caller gets this error, verify `clientIdClaim: azp` is set for the Orchestration Cluster. The bare-GUID scope format (`/.default`) causes Entra to issue v2.0 tokens, which carry the calling client's ID in `azp` rather than the `appid` claim used by v1.0 tokens. ## Grant access to components After deployment, you must configure access for the following components. To grant a user access to the Web Modeler UI: - [Create a mapping rule in Management Identity](/self-managed/components/management-identity/mapping-rules.md#add-a-mapping-rule) for the `Web Modeler` role that matches the user’s access token. To grant a client access to the Web Modeler API: - [Create a role in Management Identity](/self-managed/components/management-identity/application-user-group-role-management/manage-roles.md#add-a-role) for the Web Modeler API. - [Assign Web Modeler API permissions](/self-managed/components/management-identity/access-management/manage-permissions.md#manage-role-permissions) to that role in Management Identity. - [Create a mapping rule in Management Identity](/self-managed/components/management-identity/mapping-rules.md#add-a-mapping-rule) for that role that matches the client’s access token. To grant a user access to Optimize: - [Create a mapping rule in Management Identity](/self-managed/components/management-identity/mapping-rules.md#add-a-mapping-rule) for the `Optimize` role that matches the user’s access token. :::info When using an OIDC provider, the following Optimize features are not currently available: - The **User permissions** tab in collections - The **Alerts** tab in collections - Digests - Accessible user names for resource owners (the value of the `sub` claim is displayed instead). ::: --- ## Connect Camunda to Ping Identity (PingFederate or PingOne) This guide covers connecting Camunda 8 Self-Managed to PingFederate or PingOne Advanced Identity Cloud. Where the two products differ, both variants are noted. :::note This page covers only what's specific to Ping. For the shared setup steps, including creating secrets, configuring each Camunda component through Helm, and identifying token claims, see [Generic OIDC provider](./generic-oidc-provider.md). Complete that guide's steps alongside the Ping-specific steps on this page. ::: ## Prerequisites In addition to the [prerequisites for any OIDC provider](./generic-oidc-provider.md#prerequisites), you'll need: - Administrator access to your Ping environment with permission to create OAuth/OIDC applications. - Your authorization server's discovery document URL: - **PingFederate:** `https:///.well-known/openid-configuration` - **PingOne:** `https://auth.pingone.com//as/.well-known/openid-configuration` ## Create OAuth/OIDC applications in Ping The [generic OIDC provider guide](./generic-oidc-provider.md#create-oidc-clients) lists the six clients Camunda needs. Create each one as follows. For each confidential client (Management Identity, Optimize, Orchestration Cluster, Web Modeler API): 1. Go to **Applications > OAuth Clients > Create Client**. 2. Set a descriptive **Client ID** (for example, `camunda-identity`). 3. Under **Client Authentication**, select **Client Secret** and generate a secret. Record the value. 4. Under **Redirect URIs**, add the component's redirect URI from the [redirect URI table](./generic-oidc-provider.md#redirect-uri-table). Skip this for Web Modeler API, which uses client credentials only. 5. Under **Grant Types**, select **Authorization Code** (skip for Web Modeler API; select **Client Credentials** instead). 6. Under **Scopes Allowed**, add `openid`, `profile`, `email`, `offline_access`. 7. Save the client. For the two public clients (Web Modeler UI, Console), create a client the same way but without a client secret, using whichever public/PKCE client type your PingFederate version supports. For each confidential client: 1. Go to **Connections > Applications > Add Application > OIDC Web App**. 2. Set the application name and save. 3. Under **Configuration**, set the redirect URI from the [redirect URI table](./generic-oidc-provider.md#redirect-uri-table). 4. Under **Resources**, grant the `openid`, `profile`, and `email` scopes. 5. Under **Configuration > Token Endpoint Auth Method**, select **Client Secret Post** or **Client Secret Basic**. 6. Note the **Client ID** and **Client Secret** from the **Configuration** tab. For Web Modeler API, add a **Worker** (machine-to-machine) application instead, and note its client ID and secret. For the two public clients, add an application using PingOne's public/native client type. ## Determine whether you need separate signing keys PingFederate deployments commonly sign access tokens with a different key than ID tokens. This is a standard enterprise PingFederate pattern rather than an edge case, so confirm which case applies to your deployment before you deploy Camunda. Go to **Authorization Server > Token Settings** and compare the signing certificate under **JWT Access Token** against the one under **OpenID Connect Policy Management**. If they match, your deployment uses a single key and you can skip the dual-key configuration. If they differ, or if you're unsure, assume dual keys. To find the access token JWKS endpoint, go to **Security > Certificate & Key Management > Runtime Keys**. The key set is published at `https:///pf/JWKS`. Compare this against the `jwks_uri` in your discovery document. If the URLs differ, you have two distinct key sets. Go to **Connections > Applications > your app > Configuration > Token Management**. If a custom access token signing key is configured separately from the OIDC settings, note its JWKS URL. This is your access token JWKS endpoint, distinct from the ID token JWKS in the discovery document. If your two JWKS URLs differ, follow [Handle separate access token and ID token signing keys](./generic-oidc-provider.md#handle-separate-access-token-and-id-token-signing-keys) using the access token JWKS URL you found above. ## Configure Camunda components Follow [Configure Camunda components](./generic-oidc-provider.md#configure-camunda-components) using the endpoint URLs from your discovery document. A few Ping-specific notes: - Set `global.identity.auth.type` to `"GENERIC"`. - Ping's `client_id` claim in access tokens identifies the calling client for both PingFederate and PingOne. The `clientIdClaim` default already matches this, so you typically don't need to override it. Confirm by decoding a real client-credentials token, as described in the [JWT token claims reference](./jwt-token-claims.md). - `sub` is a stable, unique per-user identifier and a safe default for `initialClaimName`. Decode a test token first to confirm which user-identifying claims your Ping deployment populates, as described in [Identify token claims](./generic-oidc-provider.md#identify-token-claims). ### Audience configuration requires Ping-side setup Unlike some providers, Ping doesn't automatically populate the `aud` claim with the requesting client's ID. Set this up in Ping before the `audience` values in your Helm configuration can validate correctly. In PingFederate, the `aud` claim comes from an Access Token Manager's **Audience Claim Value** field. This field holds a single static string and doesn't template from the requesting client, so `${client_id}` is taken literally rather than resolved. A shared Access Token Manager therefore can't produce a different `aud` per client. To give each of the six Camunda components an audience matching its own client ID, create one Access Token Manager per component, each with **Audience Claim Value** hardcoded to that component's client ID, then point each OAuth client's default Access Token Manager at its own. Without this, tokens either carry no `aud` claim or carry the wrong one from a shared Access Token Manager, and Camunda's audience check fails in both cases. Confirm your PingOne resource/scope configuration issues an `aud` claim matching each component's client ID, and adjust the `audience` values in your Helm config to match what you find in a decoded token. ## Assign users User access is controlled by access policies. Ensure your PingFederate access policy permits your admin user to authenticate to the Camunda Identity and Camunda Orchestration Cluster clients before first startup. In the PingOne admin console, open each application, and under **Access**, ensure the admin user's population or the relevant group is assigned. Confirm the user is in the **Active** state. ## Troubleshooting For issues common to any OIDC provider, see [Troubleshoot OIDC authentication](./troubleshooting-oidc.md). The following are specific to Ping: **`invalid_client` during code exchange** Ping returned an error exchanging the authorization code for tokens. Verify the client authentication method configured in Ping, either **Client Secret Post** or **Client Secret Basic**, matches what Camunda sends. PingFederate defaults to **Client Secret Basic**, so check your client configuration if you changed it. **`aud` claim mismatch, or missing entirely** See [Audience configuration requires Ping-side setup](#audience-configuration-requires-ping-side-setup). This is the most common cause with PingFederate, since a shared Access Token Manager can't produce a per-client audience. **Token signature validation fails on API calls, but interactive login succeeds** The access token and ID token are signed with different keys. See [Determine whether you need separate signing keys](#determine-whether-you-need-separate-signing-keys). **PingFederate: `error=server_error, error_description=There are no authentication methods available for OAuth on the authorization redirect`** PingFederate has no IdP adapter registered as an authentication source for the OAuth authorization server. Check **System > OAuth Settings > Authorization Server > IdP Adapter Mapping**, and confirm the same adapter is also listed under **Authentication Policies > Default Authentication Sources**. Only authorization code login redirects are affected, because a client credentials grant doesn't need an authentication source. This error typically appears only on a PingFederate instance configured from scratch, since an existing deployment already has a working authentication policy. --- ## Troubleshoot OIDC authentication This page provides solutions to common issues encountered when configuring OIDC authentication for Camunda 8 Self-Managed. ## Invalid redirect_uri **Observed behavior:** During login, your OIDC provider shows "Invalid redirect_uri". **Why this happens:** The `redirectUrl` in Helm values doesn't match an allowed redirect URI configured in your OIDC provider. **How to fix:** 1. Open the browser's developer tools (F12) and check the `redirect_uri` parameter sent to your OIDC provider. 2. Ensure this exact URI is configured in your OIDC provider's allowed redirect URIs. 3. Update `redirectUrl` in Helm values to match how users actually access the component. :::info Common misconfiguration A common mistake is leaving the redirect URL in Helm values at `http://localhost:8080` when users access via `https://camunda.example.com/orchestration`. Set it to the public URL instead. ::: ## Standard flow is disabled for the client **Observed behavior:** Logging in to the Orchestration Cluster fails, and you see an error similar to: ```text {"type":"about:blank","title":"Internal Server Error","status":500,"detail":"[unauthorized_client] Client is not allowed to initiate browser login with given response_type. Standard flow is disabled for the client.","instance":"/orchestration/sso-callback"} ``` **Why this happens:** The OIDC client configured for the Orchestration Cluster doesn't have the Authorization Code Flow enabled. Orchestration Cluster web applications require this flow for browser-based user login. This often occurs after upgrading from 8.7, where the reused `zeebe` client handled only machine-to-machine access and didn't need the flow. **How to fix:** 1. In your identity provider, open the client used for the Orchestration Cluster OIDC configuration. 2. Enable the Authorization Code Flow for the client. In Keycloak, enable the **Standard flow** option for the client. 3. Retry the login. ## Invalid audience **Observed behavior:** Logs show "Invalid token" or "Audience mismatch" errors. **Why this happens:** The `audience` parameter doesn't match the `aud` claim in tokens. **How to fix:** 1. Obtain and decode a token: ```bash curl -X POST '' \ -d 'client_id=' \ -d 'client_secret=' \ -d 'grant_type=client_credentials' | jq -r '.access_token' | \ cut -d'.' -f2 | base64 -d | jq '.aud' ``` 2. Confirm the `aud` value matches the audience you assigned to that component in [Assign a unique audience to each component](./generic-oidc-provider.md#assign-a-unique-audience-to-each-component). Update either the Helm value or your provider's client configuration so the two agree. 3. Redeploy Camunda. :::note Some providers, such as Keycloak, may not include the appropriate audience by default. Consult your provider's documentation on configuring token audiences. For Keycloak, see [External Keycloak](./external-keycloak.md). ::: ## UserInfo endpoint rejects the access token **Observed behavior:** Login succeeds, but logs show a warning similar to: ```text OIDC /userinfo call failed for registration '' (invalid_user_info_response); continuing login with ID-token-only claims. Set user-info-required=true for this provider to fail login instead, or adjust the requested scope so the access token is accepted at this IdP's userinfo endpoint. ``` Any claims that would normally come from the `/userinfo` response, and aren't already present in the ID token, are missing from the session. **Why this happens:** Your identity provider's `/userinfo` endpoint rejected the access token Camunda sent it, most often because of an audience mismatch. This is structural for several identity providers, not a misconfiguration: - **Microsoft Entra:** UserInfo is served by Microsoft Graph, which requires an access token whose audience is Microsoft Graph, never the Camunda client. The documented Entra scopes include `/.default`, which always produces this mismatch. See [Ensure Entra prerequisites](./microsoft-entra.md#ensure-entra-prerequisites). - **Auth0, Okta, and PingFederate:** The same rejection occurs whenever the access token is bound to an `audience` other than the provider's own UserInfo endpoint. **How to fix:** 1. By default, no fix is needed. Login continues using only the claims from the ID token. 2. If this provider's authorization-relevant claims (for example, group membership) are only available from UserInfo, set `user-info-required: true` for that provider so a rejected call fails login loudly instead of silently continuing without those claims. See [`camunda.security.authentication.oidc.user-info-required`](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#camundasecurityauthenticationoidc). 3. To skip the UserInfo call entirely instead, for example if you don't need any claims from it, set `user-info-enabled: false` for that provider. ## Shared audience between components **Observed behavior:** A token issued for one Camunda component is also accepted by another component that should not recognize it. **Why this happens:** Both components are configured to accept the same audience. This can be required when Connectors calls the Orchestration Cluster or when Camunda Hub forwards a user's token to the cluster with `BEARER_TOKEN` authentication. In other cases, a shared audience can allow unintended cross-component access. **How to fix:** 1. Compare the audience configured for each component against [Assign a unique audience to each component](./generic-oidc-provider.md#assign-a-unique-audience-to-each-component). 2. Check whether the shared audience supports one of the documented integrations. If it doesn't, give each component a distinct resource audience, and configure your provider to issue it. 3. Redeploy Camunda. ## Insufficient permissions **Observed behavior:** You authenticate, but Camunda shows "Insufficient permissions". **Why this happens:** Your account hasn't been granted access via mapping rules. **How to fix:** In Management Identity, create a mapping rule that matches your claim values and assign the appropriate role. See [Managing mapping rules](/self-managed/components/management-identity/mapping-rules.md) for more details. ## Claim not found **Observed behavior:** Logs show "Claim not found" or "Required claim missing" errors. **Why this happens:** The configured claim name doesn't exist in tokens issued by your provider. **How to fix:** 1. Decode a token to see available claims. See [JWT token claims reference](./jwt-token-claims.md) for instructions. 2. Update `usernameClaim` or `clientIdClaim` in Helm values to match the actual claim names. 3. Redeploy Camunda. **Common alternatives:** - User claims: `email`, `preferred_username`, `sub` - Client claims: `client_id`, `azp`, `appid`. For a complete list of common claim patterns by provider, see [JWT token claims reference](./jwt-token-claims.md#common-claim-patterns-by-provider). ## TLS handshake failure connecting to the IdP **Observed behavior:** Identity or another component fails to reach the OIDC provider, and logs show an error similar to: ```text javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target ``` **Why this happens:** Your OIDC provider's certificate (or the internal Keycloak instance's certificate) is signed by a private or internal certificate authority that isn't in the JVM truststore Camunda components use by default. **How to fix:** Add your CA to the trust bundle Camunda components use to validate the connection. See [configure TLS](/self-managed/deployment/helm/configure/tls.md#external-oidc-issuer-with-private-ca) for the Helm chart's `global.tls.caBundle` overlay, which covers this exact scenario. ## Pods not starting **Observed behavior:** Pods remain in `Pending`, `CrashLoopBackOff`, or `Error` states. **Why this happens:** Required secrets are missing, PostgreSQL is still initializing, or there are configuration typos in OIDC URLs. **How to fix:** 1. Inspect pod events and status with `kubectl describe pod -n camunda` and `kubectl logs -n camunda`. 2. Check component logs with `kubectl logs -n camunda deployment/ -f` and search for keywords: `auth`, `token`, `oidc`, `401`, `403`. ## Request header is too large **Observed behavior:** Logging in to Management Identity fails and the browser shows a Tomcat error page, for example `HTTP Status 400 – Bad Request`. Management Identity logs contain messages similar to: ```text o.a.coyote.http11.Http11Processor : Error parsing HTTP request header Note: further occurrences of HTTP request parsing errors will be logged at DEBUG level. java.lang.IllegalArgumentException: Request header is too large at org.apache.coyote.http11.Http11InputBuffer.fill(Http11InputBuffer.java:765) ... ``` **Why this happens:** When using an external OIDC provider (for example, Microsoft Entra ID), the access token and related cookies (such as `IDENTITY_JWT`, `IDENTITY_REFRESH_JWT`, and Optimize cookies) can make the HTTP request header larger than the default limit of the embedded application server (Tomcat). By default, Tomcat rejects requests whose headers exceed this limit (typically 8 KB). As a result, the request never reaches Camunda, and the login fails with request header is too large. **How to fix:** Increase the maximum allowed HTTP request header size for the Identity service. 1. Configure the Spring Boot property `server.max-http-request-header-size` (via the `SERVER_MAX_HTTP_REQUEST_HEADER_SIZE` environment variable) to a value higher than the default, for example 40KB. 2. If you are using the Helm chart, set this environment variable on the Identity deployment in your `values.yaml`, similar to other Identity environment variables: ```yaml identity: env: - name: SERVER_MAX_HTTP_REQUEST_HEADER_SIZE value: "40KB" ``` 3. Upgrade or redeploy the release so the new environment variable takes effect. The Orchestration Cluster can hit the same limit when its session cookies and authorization code grow large enough, for example with Microsoft Entra. If Operate or Tasklist login fails with the same symptom, set `SERVER_MAX_HTTP_REQUEST_HEADER_SIZE` under `orchestration.env` instead. --- ## Understand Helm and application configuration responsibilities Helm configures how and where a Camunda component runs and connects. The application configures what it does. `.extraConfiguration` is the hook between the two. Neither layer is more authoritative than the other. They answer different questions, and knowing which question you're asking tells you where a setting belongs. Starting with Camunda 8.10, chart values that existed only to proxy a single application property are deprecated in favor of `extraConfiguration`. The chart keeps the Kubernetes surface it's responsible for and stops mirroring the application's own configuration. ## Where settings belong _What_ you're configuring determines _where_ the setting belongs: | Configuring | Examples | Belongs in | | :--------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------- | | Application behavior | Feature flags, toggles, log levels, security and authorization behavior, Spring Boot properties, and anything else that changes application logic | `.extraConfiguration` | | Kubernetes infrastructure | Resource requests and limits, affinity and scheduling, service accounts, volumes, replica counts, deployment strategy | `values.yaml` | | Connectivity and credentials | External endpoints, database URLs, secondary storage hosts, TLS certificates, secret references, Ingress and Gateway wiring | `values.yaml` | | Cross-component coordination | Release role (`global.topology.mode`), the Hub cluster inventory, shared authentication identifiers | `values.yaml` | Application property names are the same whichever deployment method you use, so what you learn about `camunda.security.*` or `camunda.physical-tenants.*` transfers from Helm to Docker, to a JAR, or to ECS. Chart values don't transfer, which is why they're limited to the deployment layer. ## What the chart does not own The chart deploys workloads and wires them to the endpoints you give it. The following table shows which concerns you provide and which the chart provides. Every URL you configure must be reachable from the release that uses it. | Concern | Owner | | -------------------------------------------------------------- | ------------------------------------------ | | OIDC provider, its clients, and its pinned issuer | You | | Cross-namespace and cross-cluster DNS, routing, and TLS trust | You | | NetworkPolicies and firewall rules | You | | Management Identity and Camunda Hub relational databases | You | | Orchestration Cluster and Optimize secondary storage | You | | Index retention and deletion, including after a Helm uninstall | You | | Kubernetes workloads, services, secrets wiring, and volumes | The chart | | Management Identity presets and Camunda Hub cluster inventory | The chart, from `global.topology.clusters` | Camunda 8.10 bundles no Elasticsearch, PostgreSQL, or Keycloak subcharts. Provision these before you install. See [deploy required dependencies](./operator-based-infrastructure.md). ## Provide application settings Three forms are supported, and they behave differently. For the full mechanics, including per-component merge behavior and a worked migration from environment variables, see [configure component configuration](/self-managed/deployment/helm/configure/application-configs.md). | Form | Behavior | Use when | | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | | `.extraConfiguration` | Each entry mounts as its own file and merges into the pod's Spring configuration alongside the chart's `application.yaml` | Almost always. This is the recommended path | | `.configuration` | Replaces the entire default application configuration file | You intend to own the whole file, including the chart's defaults | | `.env` | Injects environment variables | A single value, or a value that must come from a secret at pod start | :::warning Helm merges maps deeply but replaces arrays wholesale. `extraConfiguration` is a list, so an overlay that sets it replaces every entry from a lower layer rather than adding to them. Keep all entries for a component in one place. ::: ## Example: configure Orchestration Cluster authorizations `orchestration.security.authorizations.enabled` is an application setting. In chart 15.x it still works, and setting it to a non-default value (`false`) logs a deprecation warning. Set the application property instead. ```yaml # Deprecated in chart 15.x orchestration: security: authorizations: enabled: false ``` ```yaml # Recommended orchestration: extraConfiguration: - file: authorizations.yaml content: | camunda: security: authorizations: enabled: false ``` The same release still sets its connectivity and infrastructure in `values.yaml`, because those aren't application concerns: ```yaml orchestration: resources: requests: cpu: "2" memory: 4Gi extraConfiguration: - file: authorizations.yaml content: | camunda: security: authorizations: enabled: true global: identity: service: url: http://camunda-identity.hub.svc.cluster.local:80/identity ``` ## Find the keys you still need to migrate `helm install` and `helm upgrade` log a `[camunda][warning] DEPRECATION` message for every deprecated key you set to a non-default value. Each message names the key and where to configure it instead. Treat that output as the authoritative list for your chart version. Camunda 8.10 is under active development and more keys may be deprecated across its release cycle, so any table in the documentation is a snapshot and the warnings are not. ```sh helm upgrade camunda camunda/camunda-platform \ --version "$ORCHESTRATION_CHART_VERSION" \ --namespace camunda \ -f values.yaml 2>&1 | grep 'DEPRECATION' ``` For the keys deprecated at the time of writing, and the tables mapping each one to its replacement, see [upgrade Camunda 8.9 to 8.10 using Helm](/self-managed/upgrade/helm/890-to-8100.md). --- ## Configure Logical Tenants in Helm chart :::note This page describes Logical Tenants, the lightweight tenant-ID based multi-tenancy model. For strong physical isolation of separate teams or organizations within a single cluster, see [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md). ::: Logical Tenants let you isolate users, data, and workloads across tenants (for example, business units, departments, or customers) within the same Camunda 8 cluster. This ensures separation while reducing infrastructure overhead by running multiple tenants on a shared installation. This page explains how to configure Logical Tenants in both Management Identity and [Orchestration Cluster Admin](/self-managed/components/orchestration-cluster/admin/overview.md). It also shows the defaults, how to enable or enforce tenant checks, and how to resolve common issues. ## Prerequisites - A running Camunda 8 Self-Managed deployment with authentication enabled. :::note Multi-tenancy requires authentication in the Orchestration Cluster Admin. If authentication is disabled, multi-tenancy does not work. ::: The memory limit for Management Identity is generally suitable for most multi-tenant deployments. If the tenancy model becomes more complex with many tenants, you might encounter memory pressure on the heap. If you anticipate higher memory usage, consider increasing the memory limit for Management Identity: ```yaml identity: resources: limits: memory: 4Gi ``` As an additional safeguard, you can increase the JVM heap allocation by setting: ```yaml identity: env: - name: JAVA_TOOL_OPTIONS value: -XX:MaxRAMPercentage=50.0 ``` ## Configuration Multi-tenancy behavior differs depending on the identity component: - **Management Identity:** Disabled by default. You must enable it. Once enabled, tenant checks are automatically enforced (all requests are validated against the active tenant configuration). - **Orchestration Cluster Admin:** Enabled by default, with a default tenant created. Tenant checks are not enforced unless explicitly enabled. ### Parameters | values.yaml option | type | default | description | | ------------------------------------------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `global.multitenancy.enabled` | boolean | `false` | (Management Identity) Enable multi-tenancy globally. | | `orchestration.multitenancy.checks.enabled` | boolean | `false` | (Orchestration Cluster Admin) Deprecated in chart 15.x. Enforce tenant validation across requests. Use `camunda.security.multi-tenancy.checks-enabled` in `orchestration.extraConfiguration` instead. | | `orchestration.multitenancy.api.enabled` | boolean | `true` | (Orchestration Cluster Admin) Deprecated in chart 15.x. Enable the multi-tenancy API for tenant management. Use `camunda.security.multi-tenancy.api-enabled` in `orchestration.extraConfiguration` instead. | ### Example usage **Management Identity** Enable multi-tenancy in Management Identity: ```yaml global: multitenancy: enabled: true ``` **Orchestration Cluster Admin** Enable tenant checks and the multi-tenancy API: ```yaml orchestration: extraConfiguration: - file: multi-tenancy.yaml content: | camunda: security: multi-tenancy: checks-enabled: true # Enforces tenant checks in all components api-enabled: true # Enables multi-tenancy API for tenant management ``` For the full list of deprecated Helm keys and their replacements, see [Deprecated application configuration Helm keys](/self-managed/upgrade/helm/890-to-8100.md#deprecated-application-configuration-helm-keys). :::warning Disabling multi-tenancy after it has been enabled can cause unexpected behavior if active tenants exist. ::: ## End-to-end example: assign users to a tenant via mapping rules Enabling tenant checks controls whether tenant membership is enforced, but doesn't assign anyone to a tenant. To assign users automatically as they log in (rather than one by one), combine a tenant with a mapping rule. **Scenario:** Users whose access token contains the `groups` claim with value `finance-team` should automatically get access to a `finance` tenant. 1. Enable multi-tenancy checks in the Orchestration Cluster Admin, as shown above. 2. [Create the `finance` tenant](/components/admin/tenant.md#create-a-tenant). 3. [Create a mapping rule](/components/admin/mapping-rules.md#create-a-mapping-rule) matching the claim: - **Claim name**: `groups` - **Claim value**: `finance-team` 4. [Assign the mapping rule to the `finance` tenant](/components/admin/tenant.md#assign-mapping-rules-to-a-tenant). Once assigned, any user or client presenting a token with `groups` containing `finance-team` is automatically treated as a member of the `finance` tenant, without a manual per-user assignment step. This uses mapping rules in the Orchestration Cluster Admin, which are distinct from [mapping rules in Management Identity](/self-managed/components/management-identity/mapping-rules.md) (which instead control access to Console, Optimize, and Web Modeler). See [mapping rules](/components/concepts/access-control/mapping-rules.md) for how the two relate. --- ## Configure Physical Tenants in Helm chart :::note This page describes Physical Tenants, the strong isolation model for separate teams or organizations within a single orchestration cluster. For the lightweight, tenant-ID based model, see [Logical Tenants](./configure-logical-tenants.md). ::: The Helm chart does not expose a dedicated `physicalTenants.*` values schema. Configure Physical Tenants by passing the same `camunda.physical-tenants..*` properties documented in the [configuration reference](/self-managed/concepts/physical-tenants/configuration-reference.md), either as a raw `application.yaml` block, as a standalone extra configuration file, or as environment variables. This page covers delivery: how to get tenant configuration into the Orchestration Cluster pod. Declaring a tenant also changes the shape of your deployment, because each tenant needs its own Optimize release and its own index prefixes, and adding or removing one is an ordered operation across several releases. For that, see [configure Physical Tenants across releases](/self-managed/deployment/helm/install/topology/physical-tenants.md). ## Prerequisites - A running Camunda 8 Self-Managed Helm deployment. - Read the [Physical Tenant isolation model](/self-managed/concepts/physical-tenants/index.md) and [configuration reference](/self-managed/concepts/physical-tenants/configuration-reference.md) first — this page only shows how to deliver that same configuration through Helm. ## Configure via `orchestration.configuration` `orchestration.configuration` replaces the chart's default application configuration, so include every property your deployment needs. To keep the chart defaults, use [`orchestration.extraConfiguration`](#configure-via-orchestrationextraconfiguration) instead. For details, see [Configure component configuration](/self-managed/deployment/helm/configure/application-configs.md#componentnameconfiguration). Set `orchestration.configuration` to the full `application.yaml` content, including the root-level and per-tenant `camunda.physical-tenants.*` blocks: ```yaml orchestration: configuration: | camunda: data: secondary-storage: rdbms: url: jdbc:postgresql://db/default document: default-store-id: shared-s3 aws: shared-s3: bucket-name: company-docs-bucket bucket-path: default/ security: authentication: method: oidc providers: oidc: corp-idp: issuer-uri: https://corp-idp.example.com/realms/camunda client-id: camunda-client client-secret: ${CORP_IDP_CLIENT_SECRET} audiences: - camunda-api username-claim: preferred_username physical-tenants: default: cluster: partition-count: 3 document: default-store-id: shared-s3 assigned: - shared-s3 security: authentication: providers: assigned: - corp-idp riskprod: cluster: partition-count: 3 data: secondary-storage: rdbms: url: jdbc:postgresql://db/riskprod document: default-store-id: shared-s3 assigned: - shared-s3 aws: shared-s3: bucket-path: riskprod/ # distinct path, no collision with default security: authentication: providers: assigned: - corp-idp initialization: roles: - roleId: riskprod-admin name: Risk Production Admin mappingRules: - riskprod-admins-mapping mappingrules: - mapping-rule-id: riskprod-admins-mapping claim-name: groups claim-value: risk-admins authorizations: - ownerType: ROLE ownerId: riskprod-admin resourceType: RESOURCE resourceId: "*" permissions: - CREATE - ownerType: ROLE ownerId: riskprod-admin resourceType: PROCESS_DEFINITION resourceId: "*" permissions: - CREATE_PROCESS_INSTANCE - UPDATE_PROCESS_INSTANCE - READ_PROCESS_INSTANCE - READ_PROCESS_DEFINITION ``` Every explicitly configured tenant needs its own `security.initialization` block when authorization is enabled; it is not inherited from the root or from other tenants. This is the same configuration shape as the [configuration reference's application.yaml example](/self-managed/concepts/physical-tenants/configuration-reference.md#configuration-examples) — `orchestration.configuration` renders as-is into the pod's `application.yaml`. Secrets referenced with `${VARIABLE}` syntax (like `${CORP_IDP_CLIENT_SECRET}` above) still resolve from the pod's environment. Supply them through `orchestration.env` or `orchestration.envFrom` alongside `orchestration.configuration`. ## Configure via `orchestration.extraConfiguration` If you'd rather keep the Physical Tenant configuration in its own file instead of folding it into a single `orchestration.configuration` block, use `orchestration.extraConfiguration`. Each entry mounts as its own file and, with `springImport` left at its default (`true`), is merged into the pod's Spring configuration alongside the base `application.yaml`: ```yaml orchestration: extraConfiguration: - file: physical-tenants.yaml content: | camunda: physical-tenants: default: cluster: partition-count: 3 document: default-store-id: shared-s3 assigned: - shared-s3 security: authentication: providers: assigned: - corp-idp riskprod: cluster: partition-count: 3 data: secondary-storage: rdbms: url: jdbc:postgresql://db/riskprod document: default-store-id: shared-s3 assigned: - shared-s3 aws: shared-s3: bucket-path: riskprod/ # distinct path, no collision with default security: authentication: providers: assigned: - corp-idp initialization: roles: - roleId: riskprod-admin name: Risk Production Admin mappingRules: - riskprod-admins-mapping mappingrules: - mapping-rule-id: riskprod-admins-mapping claim-name: groups claim-value: risk-admins authorizations: - ownerType: ROLE ownerId: riskprod-admin resourceType: RESOURCE resourceId: "*" permissions: - CREATE - ownerType: ROLE ownerId: riskprod-admin resourceType: PROCESS_DEFINITION resourceId: "*" permissions: - CREATE_PROCESS_INSTANCE - UPDATE_PROCESS_INSTANCE - READ_PROCESS_INSTANCE - READ_PROCESS_DEFINITION ``` Every explicitly configured tenant needs its own `security.initialization` block when authorization is enabled; it is not inherited from the root or from other tenants. This still requires the base `camunda.security.authentication` and `camunda.document` configuration (shown in the `orchestration.configuration` example above) to be set elsewhere — through `orchestration.configuration` or your own base `application.yaml` — since `extraConfiguration` only adds to that configuration, it doesn't replace it. ## Configure via environment variables For a small number of overrides, set individual properties through `orchestration.env` instead of a full configuration block: ```yaml orchestration: env: - name: CAMUNDA_PHYSICALTENANTS_RISKPROD_DATA_SECONDARYSTORAGE_RDBMS_URL value: jdbc:postgresql://db/riskprod ``` Environment variables and `orchestration.configuration` can be combined. Use the same normalized tenant key in both. See [environment variable mapping](/self-managed/concepts/physical-tenants/configuration-reference.md#environment-variables) for the full conversion rules. ## Related pages - [Physical Tenant isolation model](/self-managed/concepts/physical-tenants/index.md) - [Set up two isolated Physical Tenants](/self-managed/concepts/physical-tenants/getting-started.md) - [Configuration reference](/self-managed/concepts/physical-tenants/configuration-reference.md) - [Authentication and authorization](/self-managed/concepts/physical-tenants/authentication-authorization.md) - [Configure Physical Tenants across releases](/self-managed/deployment/helm/install/topology/physical-tenants.md) - [Install an Optimize release](/self-managed/deployment/helm/install/topology/optimize-release.md) --- ## Configure data retention Data retention policies automatically delete old data from secondary storage after a specified time period. This prevents unlimited data growth, reduces storage costs, and maintains system performance. If you use Elasticsearch or OpenSearch as your secondary storage backend, retention is implemented using Index Lifecycle Management (ILM) for Elasticsearch or Index State Management (ISM) for OpenSearch. See [Elasticsearch/OpenSearch](/reference/glossary.md#elasticsearchopensearch) for details. :::note If you use an RDBMS as your secondary storage backend, implement retention and cleanup using the RDBMS exporter and database-specific mechanisms. See [RDBMS configuration](/self-managed/concepts/databases/relational-db/configuration.md) for details. ::: :::tip Best practice **Configure data retention during initial installation.** Adding retention configuration after deployment may require manual policy creation in Elasticsearch/OpenSearch. See [Differences from previous versions](#differences-from-previous-versions) for version-specific behavior. ::: ## Prerequisites - Camunda 8.8+ Helm chart deployment - A [supported Elasticsearch or OpenSearch version](/reference/supported-environments.md#opensearch-and-elasticsearch-support). - Access to modify your `values.yaml` file ## Configuration Configure retention policies in your `values.yaml` file under the `orchestration` section. Retention policy types: 1. **Elasticsearch/OpenSearch Exporter indices (Zeebe records)** (`orchestration.retention`) – Retention for Zeebe record indices written by the legacy Elasticsearch/OpenSearch Exporter (for example, `zeebe-record-*`). These are _not_ Orchestration Cluster indices. 1. **Orchestration Cluster indices (historical data)** (`orchestration.history.retention`) – Retention for archived Operate, Tasklist, and Camunda indices stored in secondary storage. For index prefix requirements and examples when both index families share the same Elasticsearch/OpenSearch cluster, see [Configure Elasticsearch and OpenSearch index prefixes](/self-managed/deployment/helm/configure/database/elasticsearch/configure-elasticsearch-prefix-indices.md). :::warning Zeebe records retention requirements `orchestration.retention.*` apply only to Elasticsearch/OpenSearch Exporter indices (Zeebe records), not to Orchestration Cluster indices managed by the Camunda Exporter. The `orchestration.retention` configuration requires the legacy Zeebe Elasticsearch/OpenSearch Exporter to be enabled. The legacy exporter is automatically enabled when: - `orchestration.exporters.zeebe.enabled: true` is set, OR - Optimize is enabled (`optimize.enabled: true`), OR - Data migration is enabled (`orchestration.migration.data.enabled: true`) Starting in Camunda 8.8, `orchestration.exporters.zeebe.enabled` defaults to `false`. If you need Zeebe record retention without Optimize, you must explicitly enable it. For full exporter configuration and retention options, see [Zeebe Elasticsearch Exporter retention](../../../../components/orchestration-cluster/zeebe/exporters/elasticsearch-exporter). ::: ### Parameters **Zeebe records retention parameters:** | Key | Type | Default | Description | | ------------------------------------ | ------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `orchestration.retention.enabled` | boolean | `false` | If `true`, creates and applies the ILM/ISM policy to Zeebe record indices. **Requires the legacy Zeebe exporter to be enabled** (see prerequisites above). | | `orchestration.retention.minimumAge` | string | `30d` | How old the data must be before deletion. Uses [Elasticsearch TimeUnit format](https://www.elastic.co/guide/en/elasticsearch/reference/current/api-conventions.html#time-units) (for example, `30d`, `7d`, `1h`) | | `orchestration.retention.policyName` | string | `zeebe-record-retention-policy` | Name of the ILM/ISM policy to create and apply | :::note Exported data vs. retained data The `orchestration.retention` policy always applies to the Zeebe record indices that are actually written (for example, `zeebe-record-*`). If you configure exporter-side filters in the legacy Elasticsearch or OpenSearch exporter (such as exporting only a subset of variables or processes for data analysis tools like Optimize), retention policies apply only to data that was exported. They do not recreate or restore records that were filtered out by exporter-side filters. For Optimize and other data-analysis use cases, coordinate exporter-side filters and retention settings, and refer to: - [Elasticsearch exporter](../../../../components/orchestration-cluster/zeebe/exporters/elasticsearch-exporter/) - [OpenSearch exporter](../../../../components/orchestration-cluster/zeebe/exporters/opensearch-exporter/) - [Camunda 8 system configuration (Optimize)](../../../../components/optimize/configuration/system-configuration-platform-8/) ::: **History archiving and retention parameters:** | Key | Type | Default | Description | | -------------------------------------------------------- | ------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `orchestration.history.waitPeriodBeforeArchiving` | string | `1h` | Grace period before archiving completed processes. Processes finished within this window are not yet archived. | | `orchestration.history.rolloverInterval` | string | `1d` | Time range for creating dated indices (for example, `1d` creates daily indices). | | `orchestration.history.rolloverBatchSize` | integer | `500` | Maximum number of process instances selected for archiving per run. Defaults to 500 when `archiveByIdEnabled` is `true`, and 100 when `false`. | | `orchestration.history.archiveByIdEnabled` | boolean | `true` | When `true`, archiving moves documents in small, targeted batches. | | `orchestration.history.reindexBatchSize` | integer | `2500` | Number of individual Elasticsearch/OpenSearch documents archived in each targeted batch when `archiveByIdEnabled` is `true`. | | `orchestration.history.elsRolloverDateFormat` | string | `date` | Date format for historical indices in Java DateTimeFormatter syntax | | `orchestration.history.delayBetweenRuns` | integer | `2000` | Millisecond interval between archiver runs | | `orchestration.history.maxDelayBetweenRuns` | integer | `60000` | Maximum millisecond interval between archiver runs due to failure backoffs | | `orchestration.history.retention.enabled` | boolean | `false` | If `true`, applies ILM/ISM policy to archived orchestration indices (Operate, Tasklist, Camunda) | | `orchestration.history.retention.minimumAge` | string | `30d` | How old archived data must be before deletion | | `orchestration.history.retention.policyName` | string | `camunda-history-retention-policy` | Name of the ILM/ISM policy for historical data | | `orchestration.history.retention.usageMetricsMinimumAge` | string | `730d` | Retention period for usage metrics indices (2 years by default) | | `orchestration.history.retention.usageMetricsPolicyName` | string | `camunda-usage-metrics-retention-policy` | Name of the ILM/ISM policy for usage metrics | ### Performance The Helm value `orchestration.history.rolloverInterval` controls how often the archiver creates a new dated index for historical data. For example, `1d` creates a new dated index every day for each archivable index type (Operate and Tasklist each have several), resulting in multiple new indices per day. This setting directly affects cluster performance: - A **shorter interval** (for example, `1d`) creates more, smaller indices. Queries, ILM/ISM operations, and deletions against a single index run faster because each index covers less data, but the cluster carries more shard and index metadata overhead overall. - A **longer interval** (for example, `30d`) creates fewer, larger indices. This reduces shard and metadata overhead across the cluster, but each index takes longer to query, roll over, and delete, and a spike in completed process instances can make a single index very large. Choose `rolloverInterval` based on your throughput and retention needs: - For low-to-moderate throughput clusters with long retention (more than three months), use a longer interval (for example, `7d` or longer) to avoid creating excessive numbers of small indices, which increases cluster management overhead. - For clusters with a strict shard count limit, prefer longer intervals to stay within your cluster's recommended shard count. ### Example usage #### Orchestration Cluster history retention (recommended) In most deployments starting with Camunda 8.8, you only need retention for Orchestration Cluster indices (archived Operate, Tasklist, and Camunda data). The example below sets most properties to their current default values and explicitly enables history retention (`orchestration.history.retention.enabled: true`). If defaults change in future versions, clusters that keep these explicit values will retain the behavior shown here. ```yaml orchestration: history: waitPeriodBeforeArchiving: 1h rolloverInterval: 1d rolloverBatchSize: 500 archiveByIdEnabled: true reindexBatchSize: 2500 elsRolloverDateFormat: date delayBetweenRuns: 2000 maxDelayBetweenRuns: 60000 retention: enabled: true minimumAge: 30d policyName: camunda-history-retention-policy usageMetricsMinimumAge: 730d usageMetricsPolicyName: camunda-usage-metrics-retention-policy ``` #### Zeebe records retention Enable Zeebe records retention only if you still use the legacy Elasticsearch/OpenSearch Exporter (for example, when Optimize reads from `zeebe-record-*` indices). Exporter configuration and full retention examples are documented in [Zeebe Elasticsearch Exporter retention](../../../../components/orchestration-cluster/zeebe/exporters/elasticsearch-exporter). The snippet below shows only the Helm values related to enabling retention: ```yaml # Either enable Optimize, which in turn enables the legacy Zeebe exporter: optimize: enabled: true orchestration: # Or enable the legacy Zeebe exporter directly: exporters: zeebe: enabled: true # Zeebe records retention (Elasticsearch/OpenSearch Exporter indices) retention: enabled: true minimumAge: 30d policyName: zeebe-record-retention-policy ``` #### Retention configuration Both scenarios use the same retention configuration: ```yaml orchestration: # Zeebe records retention retention: enabled: true minimumAge: 30d policyName: zeebe-record-retention-policy # Historical data archiving and retention history: waitPeriodBeforeArchiving: 1h rolloverInterval: 1d rolloverBatchSize: 500 elsRolloverDateFormat: date delayBetweenRuns: 2000 maxDelayBetweenRuns: 60000 retention: enabled: true minimumAge: 30d policyName: camunda-history-retention-policy usageMetricsMinimumAge: 730d usageMetricsPolicyName: camunda-usage-metrics-retention-policy ``` ## Troubleshooting ### Verifying retention policies After deploying with retention enabled, verify that the ILM/ISM policies were created successfully. - Zeebe records retention (`zeebe-record-retention-policy`) applies to Elasticsearch/OpenSearch Exporter indices (Zeebe records) created by the legacy exporter (for example, indices matching the exporter prefix such as `zeebe-record-*`). The Zeebe Elasticsearch/OpenSearch Exporter creates this policy automatically during initialization, typically within a few minutes of deployment. - History retention (`camunda-history-retention-policy`) applies to archived Orchestration Cluster indices used by Operate, Tasklist, and Camunda (for example, `operate-process-*`, `tasklist-task-*` with date suffixes). The schema manager creates this ILM/ISM policy on application startup when `orchestration.history.retention.enabled: true`. The archiver then: 1. Creates archived indices and attaches the existing policy. 2. Waits for `waitPeriodBeforeArchiving` (default: 1 hour) after the **root process instance** completes. 3. Archives the completed hierarchy into dated indices (for example, `operate-process-8.3.0_2024-01-15`, `tasklist-task-8.8.0_2024-01-15`). Set your database URL: ```bash export DATABASE_URL="https://your-database-host:9200" ``` Replace `your-database-host` with your Elasticsearch or OpenSearch hostname. Check if policies exist: For **Elasticsearch**: ```bash curl -X GET "${DATABASE_URL}/_ilm/policy/zeebe-record-retention-policy?pretty" curl -X GET "${DATABASE_URL}/_ilm/policy/camunda-history-retention-policy?pretty" ``` For **OpenSearch**: ```bash curl -X GET "${DATABASE_URL}/_plugins/_ism/policies/zeebe-record-retention-policy?pretty" curl -X GET "${DATABASE_URL}/_plugins/_ism/policies/camunda-history-retention-policy?pretty" ``` Expected response for a policy with 30-day retention: ```json { "zeebe-record-retention-policy": { "version": 1, "modified_date": "2025-01-15T10:30:00.000Z", "policy": { "phases": { "delete": { "min_age": "30d", "actions": { "delete": {} } } } } } } ``` Check if archived indices exist: ```bash curl -X GET "${DATABASE_URL}/_cat/indices/operate-*_20*?v" curl -X GET "${DATABASE_URL}/_cat/indices/tasklist-*_20*?v" ``` Archived indices follow the pattern: `{component}-{type}-{schema-version}_{date}` Examples: - `operate-process-8.3.0_2024-01-15` - `operate-variable-8.3.0_2024-01-15` - `tasklist-task-8.8.0_2024-01-15` :::note Index versioning The version number in index names (for example, `8.3.0`) represents the schema version, not necessarily the Camunda platform version. Schema versions evolve independently as index structures change. For details, see [schema and migration documentation](/self-managed/components/orchestration-cluster/core-settings/concepts/schema-and-migration.md). ::: If no archived indices exist: - No processes have completed and been archived yet - The `waitPeriodBeforeArchiving` period hasn't elapsed - Deploy and complete a test process, then wait for archiving to occur Check if retention policy is applied to an archived index: For **Elasticsearch**: ```bash curl -X GET "${DATABASE_URL}/operate-process-8.3.0_2024-01-15/_settings?pretty" | grep -A 3 lifecycle ``` For **OpenSearch**: ```bash curl -X GET "${DATABASE_URL}/_plugins/_ism/explain/operate-process-8.3.0_2024-01-15?pretty" ``` Expected output showing the policy is attached: ```json { "index.lifecycle.name": "camunda-history-retention-policy" } ``` ### Known limitations **OpenSearch policy updates:** When using OpenSearch, updating an existing ISM policy's `minimumAge` may not take effect automatically. OpenSearch requires the `seq_no` and `primary_term` parameters during policy updates to ensure proper version control. Without these parameters, policy updates may fail or be skipped. **To update an OpenSearch policy:** 1. Get the current policy with its version information 1. Update the policy using the `seq_no` and `primary_term` from the current version 1. Verify the updated policy reflects the new `minimumAge` See the [OpenSearch ISM API documentation](https://opensearch.org/docs/latest/im-plugin/ism/api/) for details on policy version parameters. **Elasticsearch bulk operations:** When applying retention policies to a large number of existing indices, the operation may fail due to HTTP line length limits in Elasticsearch. This typically occurs when using wildcard patterns to apply settings to many indices at once (for example, `operate-*`, `tasklist-*`). **Workarounds:** - Apply settings to smaller batches of indices using more specific patterns - Apply settings to individual indices when the number of indices is very large - Use index templates for future indices instead of retroactively applying to all indices **Policy timing considerations:** The `camunda-history-retention-policy` is created by Camunda's retention tooling as part of the archiving workflow. If you query for this policy immediately after deployment and before any archiving occurs, you may receive a 404 response. The policy is created when the first archived index is created by the archiver. :::note Index naming Operate and Tasklist indices use schema-specific versioning in their names (for example, `operate-process-8.3.0_`, `tasklist-task-8.8.0_`). The version numbers represent schema versions, which may differ from the Camunda platform version. When archived, these indices receive a date suffix (for example, `operate-process-8.3.0_2024-01-15`). ::: ## References **Related Camunda documentation:** - [Configure Helm chart components](./application-configs.md) – How to use `orchestration.configuration` for advanced settings - [Upgrade from 8.9 to 8.10](../../../upgrade/helm/890-to-8100.md) – Version upgrade guidance - [Zeebe Elasticsearch exporter retention](../../../components/orchestration-cluster/zeebe/exporters/elasticsearch-exporter.md?configuration=retention#retention) – Legacy Zeebe exporter retention settings - [Zeebe Camunda exporter](../../../components/orchestration-cluster/zeebe/exporters/camunda-exporter.md) – Camunda exporter configuration (controls `orchestration.history.*` settings) - [History archiving settings](../../../components/orchestration-cluster/zeebe/exporters/camunda-exporter.md#history) – Archiving and rollover configuration - [Retention settings](../../../components/orchestration-cluster/zeebe/exporters/camunda-exporter.md#retention) – Historical data retention policies - [Camunda 8 system configuration (Optimize)](../../../components/optimize/configuration/system-configuration-platform-8.md) – Optimize import and retention behavior and version support - [Operate data retention](../../../components/orchestration-cluster/core-settings/concepts/data-retention.md) – Operate-specific retention behavior - [Tasklist data retention](../../../components/orchestration-cluster/core-settings/concepts/data-retention.md) – Tasklist-specific retention behavior **External documentation:** - [Elasticsearch ILM documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-lifecycle-management.html) - Official Elasticsearch ILM guide - [Elasticsearch TimeUnit format](https://www.elastic.co/guide/en/elasticsearch/reference/current/api-conventions.html#time-units) - Valid time unit values - [OpenSearch ISM documentation](https://opensearch.org/docs/latest/im-plugin/ism/index/) - Official OpenSearch ISM guide - [Helm values documentation](https://helm.sh/docs/chart_template_guide/values_files/) - Working with Helm values files ## Differences from previous versions **Camunda 8.8:** - **Policy creation and attachment**: Policies are created automatically by retention tooling and attached to archived indices as they are created - **Unified history retention**: Historical data retention (Operate, Tasklist, Camunda indices) uses unified `orchestration.history.retention` configuration - **Zeebe record retention**: Legacy zeebe-record indices remain under exporter-specific configuration (`orchestration.retention`) - **OpenSearch policy updates**: Updating existing ISM policies requires `seq_no` and `primary_term` parameters; without them, updates may fail and require manual intervention - **Bulk operations**: Applying settings to many indices may fail due to HTTP line length limits, requiring batching or manual workarounds **Camunda 8.7 and earlier:** - **Operate**: Creates ILM policy when enabled, but configuration updates (for example, changing `minimumAge`) are not applied automatically - manual policy updates required - **Zeebe**: Creates policies on initial install and applies ILM configuration updates automatically - **Tasklist**: Does not apply ILM configuration updates after deployment - manual policy updates required - **Versions 8.5-8.6**: ILM policies sometimes missing after configuration; may require manual creation or new record export to trigger policy creation :::info For Camunda 8.7 and earlier, if you change retention configuration after initial deployment, you must manually update the policies in Elasticsearch/OpenSearch for Operate and Tasklist. Zeebe automatically applies configuration updates. ::: --- ## Access SQL and Liquibase scripts Access and safely use the SQL and Liquibase scripts provided with Camunda 8 for supported databases. These scripts can be used for provisioning, upgrading, or managing database schemas in your environment. :::note Related pages - **[Configure RDBMS](rdbms.md)** - Configuration reference and Helm values. - **[Schema management](rdbms-schema-management.md)** - Schema creation, permissions, and auto-DDL configuration. - **[JDBC driver management](rdbms-jdbc-drivers.md)** - Managing custom database drivers. ::: ## Where the scripts are published The scripts are included in the **Camunda 8 Run distribution** and in each **Camunda GitHub release** as a versioned ZIP file: - **GitHub release example:** [Camunda 8.9.0](https://github.com/camunda/camunda/releases/tag/8.9.0) - **C8Run distribution:** top-level folder `rdbms-schema/` ## Distribution & ZIP contents The ZIP contains SQL scripts and Liquibase change sets for all supported databases: ``` / - | liquibase - changelog-master.xml | changesets - 8.9.0.xml - 8.10.0.xml | sql | create | h2 - h2_create_8.9.0.sql | mariadb - mariadb_create_8.9.0.sql | mssql - mssql_create_8.9.0.sql | mysql - mysql_create_8.9.0.sql | oracle - oracle_create_8.9.0.sql | postgresql - postgres_create_8.9.0.sql | upgrade | h2 - h2_upgrade_8.9.0_to_8.10.0.sql ... ``` :::note Drop scripts are not provided. ::: ## How to download - **From a GitHub release (ZIP):** Download the schema scripts from `https://github.com/camunda/camunda/releases/download//camunda-db-rdbms-schema-.zip`. - **From a C8Run distribution:** Retrieve the schema scripts from the `rdbms-schema/` folder included in the distribution. ## Usage guidance - **Version matching:** Always use scripts corresponding to your Camunda 8 version. - **Database selection:** Use the folder for your target database (PostgreSQL, Oracle, MariaDB, MySQL, SQL Server, or H2). - **Automatic schema management:** Camunda will manage the schema by default. Manual management requires disabling auto-DDL: ```yaml camunda: data: secondary-storage: rdbms: auto-ddl: false ``` - **SQL vs. Liquibase:** Liquibase changelogs are **forward-only**. Rollbacks are not supported. :::warning Do not mix SQL upgrade scripts with Liquibase-managed schema. Applying SQL scripts to a Liquibase-managed schema causes checksum mismatches and schema inconsistencies. ::: - **Liquibase lock recovery:** If a pod is interrupted during Liquibase execution, Camunda waits for stale DDL locks using `camunda.data.secondary-storage.rdbms.ddl-lock-wait-timeout` (default: `PT15M`). Increase this timeout for long-running migrations and only release `databasechangeloglock` manually after confirming no migration is running. See [RDBMS troubleshooting](rdbms-troubleshooting.md#liquibase-lock-after-pod-crash-or-restart). - **Backup first:** Always [back up](/self-managed/operational-guides/backup-restore/backup-and-restore.md) your database before applying scripts manually. ## Additional resources - **Checksums:** SHA1 or SHA256 checksums are provided in GitHub release assets. - **Liquibase CLI example:** See [Liquibase getting started](https://www.liquibase.org/get-started/running-your-first-update). - **Upgrade workflow:** Recommended approach is to allow Camunda to manage the schema automatically. Manual upgrades are supported, but users must apply scripts sequentially from the initial version to the target version. - **Performance:** Indexes are included in scripts as needed. Adding custom indexes may affect future upgrades. --- ## All shards failed errors When deploying Camunda 8.8+ with OpenSearch (or Elasticsearch) in a multi-node setup, you may encounter errors like the following in your Camunda logs: ``` [2025-07-18 22:00:59.235] [http-nio-0.0.0.0-8080-exec-13] WARN io.camunda.search.os.clients.OpensearchSearchClient - Failed to execute findAll query org.opensearch.client.opensearch._types.OpenSearchException: Request failed: [search_phase_execution_exception] all shards failed at org.opensearch.client.transport.aws.AwsSdk2Transport.parseResponse(AwsSdk2Transport.java:582) at org.opensearch.client.transport.aws.AwsSdk2Transport.executeSync(AwsSdk2Transport.java:440) at org.opensearch.client.transport.aws.AwsSdk2Transport.performRequest(AwsSdk2Transport.java:217) at org.opensearch.client.opensearch.OpenSearchClient.search(OpenSearchClient.java:1386) [...] ``` ### What this error means? The error `Request failed: [search_phase_execution_exception] all shards failed` indicates that OpenSearch could not retrieve data from any shard of the requested index. This does not necessarily mean data is corrupted or lost. The cause might be connectivity issues between OpenSearch nodes, especially in multi-node setups. ### Step 1: Check index and shard health Run the following command to verify shard allocation and index health: ```bash curl http://:9200/_cat/indices?v ``` - If the index health status is `green`, all primary and replica shards are healthy and available. - If Camunda still logs `all shards failed`, the cause may be temporary connectivity issues between OpenSearch nodes. ### Multi-node deployments and default configuration pitfalls Camunda's Helm chart is per default configured for single-node deployments with a shard replication of 0. In a multi-node OpenSearch setup, this can cause problems if shard allocation is not balanced. Consider the following scenario: ``` Orchestration Cluster | v OpenSearch Node 1 (no shard) <-x-> OpenSearch Node 2 (not reachable; holds the shard) ``` :::info Starting with version 8.8, Camunda stores authorization data in secondary storage. In deployments using Elasticsearch or OpenSearch as the secondary storage backend, this data is stored in Elasticsearch/OpenSearch — see [Elasticsearch/OpenSearch](/reference/glossary.md#elasticsearchopensearch) for details. Previously, this data was stored in PostgreSQL/Keycloak. This means: - Every authorization check now triggers one or more search engine queries. - These queries are critical — if they fail, Camunda components may be unable to process requests. ::: ### Step 2: Improve resilience with replica shards To improve fault tolerance, increase the number of replica shards. This ensures that if one database node becomes unavailable, another can still serve the data. As a general rule for multi-node clusters: ``` numberOfReplicas = numberOfNodes - 1 ``` For example, in a 3-node OpenSearch cluster, set the following environment variables: ```yaml orchestration: env: - name: CAMUNDA_TASKLIST_OPENSEARCH_NUMBEROFREPLICAS value: "2" - name: CAMUNDA_OPERATE_OPENSEARCH_NUMBEROFREPLICAS value: "2" - name: CAMUNDA_DATABASE_INDEX_NUMBEROFREPLICAS value: "2" ``` --- ## Configure custom HTTP headers for database clients You can add custom HTTP headers to the Elasticsearch or OpenSearch clients used by Camunda components by creating a Java plugin and adding it to your Camunda 8 Self-Managed installation. When Elasticsearch/OpenSearch is configured as your secondary storage backend, custom headers can help with authentication, tracking, or debugging for those requests. See [Elasticsearch/OpenSearch](/reference/glossary.md#elasticsearchopensearch). This page applies to both the Orchestration Cluster and Optimize when they connect to Elasticsearch or OpenSearch. ## Prerequisites - A deployed Camunda 8 Self-Managed Helm chart installation - Access to modify container configurations - Basic knowledge of Java development - Maven or Gradle build environment ## Configuration ### Create the Java plugin #### Add the dependency Add the following dependency to a new Java project: ```xml io.camunda camunda-search-client-plugin ${version.camunda-search-client-plugin} provided ``` ```groovy compileOnly "io.camunda:camunda-search-client-plugin:${version.camunda-search-client-plugin}" ``` #### Write your custom header After adding the dependency, create your plugin by implementing the `DatabaseCustomHeaderSupplier` interface provided by the `camunda-search-client-plugin` package. The following example implements the `DatabaseCustomHeaderSupplier` interface, and returns a custom authentication token and UUID: ```java package com.myplugin; public class MyCustomHeaderPlugin implements DatabaseCustomHeaderSupplier { public static final String CUSTOM_TOKEN_PLUGIN = "X-Custom-Auth-Token"; @Override public CustomHeader getSearchDatabaseCustomHeader() { return new CustomHeader(CUSTOM_TOKEN_PLUGIN, UUID.randomUUID().toString()); } } ``` #### Build your project Build your project with all dependencies included, and copy the resulting JAR file to a location accessible by your Camunda installation. This JAR file will be required later during configuration. :::note When building the project, the `camunda-search-client-plugin` dependency must have a scope of `provided`, otherwise there will be a class loader conflict between `camunda-search-client-plugin` classes loaded from different class paths. The JVM treats `ClassA` loaded by `ClassLoaderA` as completely different from `ClassA` loaded by `ClassLoaderB`. Without a `provided` scope, this causes `does not implement` or `ClassCastException` errors. ::: ### Add the plugin to your self-managed installation To use your new plugin, add it to your Camunda 8 Self-Managed installation. - **Mount the plugin**: For each container, mount your plugin JAR file inside the container's file system. For more information, see the [Docker](https://docs.docker.com/engine/storage/volumes/) or [Kubernetes](https://kubernetes.io/docs/concepts/storage/volumes/) documentation. - **Configure components**: Include the plugin parameters in each component's `application.yaml`, or pass them to the component as environment variables. For more information, see how to [configure components using Helm charts](../application-configs.md). ### Example usage The following examples add the new `my-plugin` JAR to the `application.yaml` for the Orchestration Cluster and Optimize: #### Zeebe Exporter ```yaml - ZEEBE_BROKER_EXPORTERS_ELASTICSEARCH_ARGS_INTERCEPTORPLUGINS_0_ID=my-plugin - ZEEBE_BROKER_EXPORTERS_ELASTICSEARCH_ARGS_INTERCEPTORPLUGINS_0_CLASSNAME=com.myplugin.MyCustomHeaderPlugin - ZEEBE_BROKER_EXPORTERS_ELASTICSEARCH_ARGS_INTERCEPTORPLUGINS_0_JARPATH=/usr/local/plugin/plg.jar ``` #### Optimize Importer :::note Due to technical limitations, Optimize currently allows registering up to five plugins. ::: ```yaml - CAMUNDA_OPTIMIZE_ELASTICSEARCH_INTERCEPTORPLUGINS_0_ID=my-plugin - CAMUNDA_OPTIMIZE_ELASTICSEARCH_INTERCEPTORPLUGINS_0_CLASSNAME=com.myplugin.MyCustomHeaderPlugin - CAMUNDA_OPTIMIZE_ELASTICSEARCH_INTERCEPTORPLUGINS_0_JARPATH=/usr/local/plugin/plg.jar ``` #### Zeebe Exporter ```yaml - ZEEBE_BROKER_EXPORTERS_OPENSEARCH_ARGS_INTERCEPTORPLUGINS_0_ID=my-plugin - ZEEBE_BROKER_EXPORTERS_OPENSEARCH_ARGS_INTERCEPTORPLUGINS_0_CLASSNAME=com.myplugin.MyCustomHeaderPlugin - ZEEBE_BROKER_EXPORTERS_OPENSEARCH_ARGS_INTERCEPTORPLUGINS_0_JARPATH=/usr/local/plugin/plg.jar ``` #### Optimize Importer :::note Due to technical limitations, Optimize currently allows registering up to five plugins. ::: ```yaml - CAMUNDA_OPTIMIZE_OPENSEARCH_INTERCEPTORPLUGINS_0_ID=my-plugin - CAMUNDA_OPTIMIZE_OPENSEARCH_INTERCEPTORPLUGINS_0_CLASSNAME=com.myplugin.MyCustomHeaderPlugin - CAMUNDA_OPTIMIZE_OPENSEARCH_INTERCEPTORPLUGINS_0_JARPATH=/usr/local/plugin/plg.jar ``` #### Zeebe Exporter :::note The following configuration uses the default name `camundaExporter`. To use a custom name, update `CAMUNDAEXPORTER` in the provided environment variables to match the name defined in your exporter [configuration](../../../../components/orchestration-cluster/zeebe/exporters/camunda-exporter.md). ::: ```yaml - ZEEBE_BROKER_EXPORTERS_CAMUNDAEXPORTER_ARGS_CONNECT_INTERCEPTORPLUGINS_0_ID=my-plugin - ZEEBE_BROKER_EXPORTERS_CAMUNDAEXPORTER_ARGS_CONNECT_INTERCEPTORPLUGINS_0_CLASSNAME=com.myplugin.MyCustomHeaderPlugin - ZEEBE_BROKER_EXPORTERS_CAMUNDAEXPORTER_ARGS_CONNECT_INTERCEPTORPLUGINS_0_JARPATH=/usr/local/plugin/plg.jar ``` ## Troubleshooting ### `Unknown type of interceptor plugin or wrong class specified` **Observed behavior:** Startup fails with `Unknown type of interceptor plugin or wrong class specified`. **Why this happens:** The class configured in the `CLASSNAME` property is incorrect. Possible causes include: - The class name or package doesn't exist. - The class doesn't implement the required SDK interface. - The class is defined as `inner`, `static`, or `final`. **How to fix:** - Use the latest Search Plugins SDK. - Ensure your class implements the correct SDK interface. - Verify that the plugin class is `public` and not `final`. ### `Failed to load interceptor plugin due to exception` **Observed behavior:** Startup fails with `Failed to load interceptor plugin due to exception`. **Why this happens:** This error usually indicates an issue with JAR loading, either the path is wrong, the file isn't readable, or the JAR is missing required dependencies. **How to fix:** 1. Confirm the path to your plugin JAR file is correct and that the application has permission to read it. 2. Confirm the JAR is valid and contains all required dependencies. Check its contents with: ```bash jar xf .jar ``` ## References - [Configure components using Helm charts](../application-configs.md) - [Plugin examples](https://github.com/camunda/camunda-search-client-plugins-example) --- ## Configure Elasticsearch and OpenSearch index prefixes Camunda components store operational data in Elasticsearch or OpenSearch indices. By default, Camunda uses the standard index names created by each exporter. This page applies to both the Orchestration Cluster and Optimize when they use Elasticsearch or OpenSearch in Helm deployments. Configure an index prefix when you need to: - Organize indices by grouping related indices under a consistent naming pattern. - Isolate data when multiple Camunda instances share the same Elasticsearch or OpenSearch cluster, so they don’t write to or read from each other’s indices. - Avoid index name collisions in multi-instance environments (for example, separate dev/test/prod installations using one shared cluster). ## Index prefix configuration When Elasticsearch/OpenSearch Exporter indices and Orchestration Cluster indices (secondary storage) share the same cluster, their prefixes must follow all of the rules below. ### Requirements 1. Use unique prefixes – Do not reuse the same prefix for both index types. 2. Avoid prefix relationships between prefixes – Neither prefix may match the other prefix’s wildcard pattern. In other words, the literal string of prefix A must not match `prefixB*`, and the literal string of prefix B must not match `prefixA*`. For example, `custom` (Elasticsearch/OpenSearch Exporter) and `custom-zeebe` (Orchestration Cluster) are unsafe because `custom*` matches both groups, while `custom-index1` and `custom-index2` are safe because `custom-index1*` matches only `custom-index1…` indices and `custom-index2*` matches only `custom-index2…` indices. 3. Avoid reserved names as bare prefixes – Do not use `operate`, `tasklist`, or `camunda` as the full exporter prefix. Using these names as part of a longer custom prefix (for example, `custom-camunda`) is allowed, as long as rules 1 and 2 are still satisfied. ### Configuration properties | Index type | Configuration property | | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | Elasticsearch/OpenSearch Exporter indices | `zeebe.broker.exporters.{elasticsearch\|opensearch}.args.index.prefix` (and `ZEEBE_BROKER_EXPORTERS_{ELASTICSEARCH\|OPENSEARCH}_ARGS_INDEX_PREFIX`) | | Orchestration Cluster indices | `camunda.data.secondary-storage.{elasticsearch\|opensearch}.index-prefix` (and `CAMUNDA_DATA_SECONDARYSTORAGE_{ELASTICSEARCH\|OPENSEARCH}_INDEXPREFIX`) | ### Common mistakes to avoid - **Do not** set the Orchestration Cluster index prefix to `zeebe-record` (the default exporter prefix `zeebe.broker.exporters.{elasticsearch|opensearch}.args.index.prefix`). - **Do not** set the exporter prefix to `operate`, `tasklist`, or `camunda`. ### Why this matters Prefixes that violate these rules can cause ILM/ISM policies and wildcard patterns to match unintended indices, potentially leading to unexpected data loss. :::warning Changing an index prefix after a Camunda instance has been running creates new, empty indices with the new prefix. Camunda does not provide built‑in migration support between old and new prefixes. ::: ## Exporters and index prefixes Starting with Camunda 8.8, index prefixes are configured per exporter. Camunda uses two exporters, and each exporter has its own index prefix configuration. ### Camunda Exporter (default) The Camunda Exporter is enabled by default. It creates Orchestration Cluster indices used by Orchestration Cluster applications and APIs, including Operate and Tasklist. - **Helm configuration**: `orchestration.index.prefix` - **Default value**: `""` (empty string, meaning no prefix) - **Controlled by**: `orchestration.exporters.camunda.enabled: true` (default) ### Legacy Zeebe Exporter The legacy Zeebe Exporter creates `zeebe-record` indices. Optimize reads these indices. The Optimize instance can run in the same release or in a separate `optimize` release. - **Helm configuration**: `orchestration.exporters.zeebe.index.prefix`. If this value is empty, the chart uses `optimize.database.elasticsearch.prefix` or `optimize.database.opensearch.prefix`. - **Default value**: `zeebe-record` - **Enabled by**: Optimize with an Elasticsearch or OpenSearch backend in the same release, or `orchestration.exporters.zeebe.enabled: true` :::note When the legacy Zeebe Exporter is used In single-region deployments, the chart automatically enables the legacy Zeebe Exporter when Optimize and its Elasticsearch or OpenSearch backend are enabled in the same release. Without Optimize in the release, set `orchestration.exporters.zeebe.enabled: true` to enable it against the Elasticsearch or OpenSearch secondary storage, and set its prefix with `orchestration.exporters.zeebe.index.prefix`. This is how a separate Optimize release gets its records. See [export records for Optimize](/self-managed/deployment/helm/install/topology/orchestration-release.md#export-records-for-optimize). When Optimize is disabled, `optimize.database.elasticsearch.prefix` and `optimize.database.opensearch.prefix` don't configure Optimize. The legacy Zeebe Exporter still uses them as its prefix when `orchestration.exporters.zeebe.index.prefix` is empty, so check them before you rely on the default `zeebe-record`. You can still configure the Camunda Exporter prefix with `orchestration.index.prefix`. ::: ## Prefixes in the split topology Each Orchestration Cluster and each [Physical Tenant](/self-managed/deployment/helm/install/topology/physical-tenants.md) in the [deployment topology](/self-managed/deployment/helm/install/topology/index.md) needs its own index prefixes. The Orchestration Cluster writes records and Optimize reads them. Optimize runs in the orchestration release or in a separate Optimize release per Physical Tenant. Authentication doesn't isolate shared Elasticsearch or OpenSearch storage. Prefixes keep the data of each cluster and tenant apart. To restrict who can read it, also use the access controls of your Elasticsearch or OpenSearch backend. | Prefix family | Configuration | Requirement | | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | | Orchestration application indices | Default: `orchestration.index.prefix`. Physical Tenant: `camunda.physical-tenants..data.secondary-storage..index-prefix` | Unique per cluster and tenant | | Legacy exporter writer | Default: `orchestration.exporters.zeebe.index.prefix`, or the exporter assigned to an explicit `default` entry. Physical Tenant: its exporter `args.index.prefix` | Unique per cluster and tenant | | Optimize reader | `optimize.database.elasticsearch.prefix` or `optimize.database.opensearch.prefix` | Must exactly equal that tenant's Legacy exporter writer prefix | | Optimize application indices | `CAMUNDA_OPTIMIZE_ELASTICSEARCH_SETTINGS_INDEX_PREFIX` or `CAMUNDA_OPTIMIZE_OPENSEARCH_SETTINGS_INDEX_PREFIX` in `optimize.env` | Unique per Optimize release, and different from every writer prefix | Every prefix in this table must also follow the [requirements](#requirements), across every cluster, tenant, and Optimize release that shares the storage. Being distinct isn't sufficient: `alpha` and `alpha-tenant` are distinct, but `alpha*` matches both. The only exception is each tenant's Optimize reader prefix, which must equal its writer prefix. A wrong prefix doesn't cause an error: - If you reuse a prefix, the records of one cluster or tenant show in the Operate, Tasklist, or Optimize data of another. - If the writer and reader prefixes are different, Optimize starts against the wrong record set or an empty record set. Similar values aren't sufficient. ## Configuration reference | Configuration | Default | Used By | Purpose | | -------------------------------------------- | -------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `orchestration.index.prefix` | `""` | Camunda Exporter, Orchestration Cluster | Prefix for Orchestration Cluster indices | | `orchestration.exporters.zeebe.index.prefix` | `""` | Legacy Zeebe Exporter | Prefix for `zeebe-record` indices. When empty, falls back to `optimize.database.elasticsearch.prefix` or `optimize.database.opensearch.prefix`, then to `zeebe-record` | | `optimize.database.elasticsearch.prefix` | `zeebe-record` | Optimize, and the Legacy Zeebe Exporter as a fallback | Optimize reader prefix for `zeebe-record` indices (Elasticsearch). The exporter uses it only when `orchestration.exporters.zeebe.index.prefix` is empty | | `optimize.database.opensearch.prefix` | `zeebe-record` | Optimize, and the Legacy Zeebe Exporter as a fallback | Optimize reader prefix for `zeebe-record` indices (OpenSearch). The exporter uses it only when `orchestration.exporters.zeebe.index.prefix` is empty | ### Optimize-specific configuration When you use a custom prefix for `zeebe-record` indices and Optimize is enabled, you must also configure Optimize to use the same prefixes. If these values do not match the exporter prefix exactly, Optimize can start but does not display process data. | Environment Variable | Purpose | | ------------------------------------------------------ | -------------------------------------------------------------------------------------------- | | `CAMUNDA_OPTIMIZE_ELASTICSEARCH_SETTINGS_INDEX_PREFIX` | Prefix for Optimize's own indices (Elasticsearch) | | `CAMUNDA_OPTIMIZE_OPENSEARCH_SETTINGS_INDEX_PREFIX` | Prefix for Optimize's own indices (OpenSearch) | | `CAMUNDA_OPTIMIZE_ZEEBE_NAME` | Must match `optimize.database.elasticsearch.prefix` or `optimize.database.opensearch.prefix` | ## Configure index prefixes ### Basic configuration (without Optimize) If Optimize is not enabled, configure only the Camunda Exporter prefix. ```yaml orchestration: data: secondaryStorage: type: elasticsearch index: prefix: custom-camunda # Orchestration Cluster indices prefix ``` ```yaml orchestration: data: secondaryStorage: type: opensearch index: prefix: custom-camunda # Orchestration Cluster indices prefix ``` ### Full configuration (with Optimize) When Optimize is enabled, configure: - The Legacy Zeebe Exporter prefix (`optimize.database.elasticsearch.prefix` or `optimize.database.opensearch.prefix`) - The Camunda Exporter prefix (`orchestration.index.prefix`) - Optimize environment variables so Optimize can find the correct indices ```yaml orchestration: data: secondaryStorage: type: elasticsearch index: prefix: custom-camunda # Camunda Exporter prefix optimize: enabled: true database: elasticsearch: enabled: true prefix: custom-zeebe # Legacy Zeebe Exporter prefix (read by Optimize) env: - name: CAMUNDA_OPTIMIZE_ELASTICSEARCH_SETTINGS_INDEX_PREFIX value: custom-optimize # Optimize's own indices - name: CAMUNDA_OPTIMIZE_ZEEBE_NAME value: custom-zeebe # Must match optimize.database.elasticsearch.prefix ``` ```yaml orchestration: data: secondaryStorage: type: opensearch index: prefix: custom-camunda # Camunda Exporter prefix optimize: enabled: true database: opensearch: enabled: true prefix: custom-zeebe # Legacy Zeebe Exporter prefix (read by Optimize) env: - name: CAMUNDA_OPTIMIZE_OPENSEARCH_SETTINGS_INDEX_PREFIX value: custom-optimize # Optimize's own indices - name: CAMUNDA_OPTIMIZE_ZEEBE_NAME value: custom-zeebe # Must match optimize.database.opensearch.prefix migration: env: - name: CAMUNDA_OPTIMIZE_OPENSEARCH_SETTINGS_INDEX_PREFIX value: custom-optimize - name: CAMUNDA_OPTIMIZE_ZEEBE_NAME value: custom-zeebe ``` ### Elasticsearch ```sh # Camunda Exporter - Orchestration Cluster indices prefix CAMUNDA_DATA_SECONDARYSTORAGE_ELASTICSEARCH_INDEXPREFIX=custom-camunda # Legacy Zeebe Exporter - zeebe-record indices prefix (for Optimize) ZEEBE_BROKER_EXPORTERS_ELASTICSEARCH_ARGS_INDEX_PREFIX=custom-zeebe # Optimize indices prefix (when Optimize is enabled) CAMUNDA_OPTIMIZE_ELASTICSEARCH_SETTINGS_INDEX_PREFIX=custom-optimize CAMUNDA_OPTIMIZE_ZEEBE_NAME=custom-zeebe ``` For example, recommended: ```bash ZEEBE_BROKER_EXPORTERS_ELASTICSEARCH_ARGS_INDEX_PREFIX=custom-zeebe CAMUNDA_DATA_SECONDARYSTORAGE_ELASTICSEARCH_INDEXPREFIX=custom-camunda ``` Not allowed (will cause conflicts): ```bash ZEEBE_BROKER_EXPORTERS_ELASTICSEARCH_ARGS_INDEX_PREFIX=shared-prefix CAMUNDA_DATA_SECONDARYSTORAGE_ELASTICSEARCH_INDEXPREFIX=shared-prefix ``` ### OpenSearch ```sh # Camunda Exporter - Orchestration Cluster indices prefix CAMUNDA_DATA_SECONDARYSTORAGE_OPENSEARCH_INDEXPREFIX=custom-camunda # Legacy Zeebe Exporter - zeebe-record indices prefix (for Optimize) ZEEBE_BROKER_EXPORTERS_OPENSEARCH_ARGS_INDEX_PREFIX=custom-zeebe # Optimize indices prefix (when Optimize is enabled) CAMUNDA_OPTIMIZE_OPENSEARCH_SETTINGS_INDEX_PREFIX=custom-optimize CAMUNDA_OPTIMIZE_ZEEBE_NAME=custom-zeebe ``` For example, recommended: ```bash ZEEBE_BROKER_EXPORTERS_OPENSEARCH_ARGS_INDEX_PREFIX=custom-zeebe CAMUNDA_DATA_SECONDARYSTORAGE_OPENSEARCH_INDEXPREFIX=custom-camunda ``` Not allowed (will cause conflicts): ```bash ZEEBE_BROKER_EXPORTERS_OPENSEARCH_ARGS_INDEX_PREFIX=shared-prefix CAMUNDA_DATA_SECONDARYSTORAGE_OPENSEARCH_INDEXPREFIX=shared-prefix ``` ### Elasticsearch ```yaml camunda: data: secondary-storage: elasticsearch: index-prefix: custom-camunda # Camunda Exporter prefix zeebe: broker: exporters: elasticsearch: args: index: prefix: custom-zeebe # Legacy Zeebe Exporter prefix ``` ### OpenSearch ```yaml camunda: data: secondary-storage: opensearch: index-prefix: custom-camunda # Camunda Exporter prefix zeebe: broker: exporters: opensearch: args: index: prefix: custom-zeebe # Legacy Zeebe Exporter prefix ``` ## References - [Use external Elasticsearch](/self-managed/deployment/helm/configure/database/elasticsearch/using-external-elasticsearch.md) - [Use external OpenSearch](/self-managed/deployment/helm/configure/database/using-external-opensearch.md) --- ## Use external Elasticsearch for Orchestration Cluster with Helm Configure the Orchestration Cluster in Camunda 8 Self-Managed to connect to an external Elasticsearch instance as a secondary storage backend. Elasticsearch is used for indexing and querying operational data consumed by Orchestration Cluster applications and APIs. For a canonical definition, see [Elasticsearch/OpenSearch](/reference/glossary.md#elasticsearchopensearch). Starting with Camunda 8.9, the Helm chart no longer provisions Elasticsearch by default. To use Elasticsearch as secondary storage for the Orchestration Cluster, explicitly configure it in your Helm values under `orchestration.data.secondaryStorage.elasticsearch`. You can either deploy Elasticsearch using the [ECK operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#elasticsearch-deployment) (recommended) or connect Camunda to an existing external Elasticsearch instance, either running inside the same Kubernetes cluster or outside it. This page applies to the Orchestration Cluster only. If you also deploy Optimize, configure Optimize separately using [use external Elasticsearch for Optimize with Helm](/self-managed/deployment/helm/configure/database/optimize/using-external-elasticsearch.md). :::note The bundled Elasticsearch Bitnami subchart is removed starting with chart 15.x (Camunda 8.10); `elasticsearch.enabled` no longer exists. Use the [ECK (Elastic Cloud on Kubernetes) operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#elasticsearch-deployment) or a managed Elasticsearch service instead. See [deploy required dependencies with Kubernetes operators](/self-managed/deployment/helm/configure/operator-based-infrastructure.md) for details. ::: ## Prerequisites Before configuring, collect the following information about your external Elasticsearch instance: - URL to access the cluster (protocol, host, and port) - Authentication requirements and credentials (if needed) - TLS requirements: - Whether the certificate is publicly trusted - Whether you need to provide a custom or self-signed certificate ## Configuration ### Parameters #### Orchestration Cluster secondary storage | values.yaml option | type | default | description | | --------------------------------------------------------------------------------- | ------ | ------- | ----------------------------------------------------------------------------------------------------- | | `orchestration.data.secondaryStorage.type` | string | `""` | Type of secondary storage. Set to `elasticsearch` to use Elasticsearch. | | `orchestration.data.secondaryStorage.elasticsearch.url` | string | `""` | URL for the Elasticsearch cluster as `scheme://host:port` (for example, `http://elasticsearch:9200`). | | `orchestration.data.secondaryStorage.elasticsearch.auth.username` | string | `""` | Username for Elasticsearch authentication. | | `orchestration.data.secondaryStorage.elasticsearch.auth.secret.inlineSecret` | string | `""` | Elasticsearch password as a plain-text value (non-production only). | | `orchestration.data.secondaryStorage.elasticsearch.auth.secret.existingSecret` | string | `""` | Reference to an existing Kubernetes Secret containing the password. | | `orchestration.data.secondaryStorage.elasticsearch.auth.secret.existingSecretKey` | string | `""` | Key within the existing Kubernetes Secret containing the password. | | `orchestration.data.secondaryStorage.elasticsearch.tls.secret.existingSecret` | string | `""` | Reference to an existing Kubernetes Secret containing the TLS trust store. | | `orchestration.data.secondaryStorage.elasticsearch.tls.secret.existingSecretKey` | string | `""` | Key within the existing Kubernetes Secret for the TLS trust store. | | `orchestration.index.prefix` | string | `""` | Index prefix in Elasticsearch for the new Camunda exporter and the Orchestration Cluster. | ### Example usage #### Connect to external Elasticsearch without a certificate Configure the Orchestration Cluster as follows: ```yaml orchestration: data: secondaryStorage: type: elasticsearch elasticsearch: url: http://elastic.example.com:443 auth: username: elastic secret: inlineSecret: pass ``` #### Connect to external Elasticsearch with a self-signed certificate If the Elasticsearch cluster accepts only `https` requests with a self-signed certificate: 1. Create an `externaldb.jks` file from the Elasticsearch certificate file. For example, using the `keytool` CLI: ```shell keytool -import -alias elasticsearch -keystore externaldb.jks -storetype jks -file elastic.crt -storepass changeit -noprompt ``` 1. Create a Kubernetes secret from the `externaldb.jks` file before installing Camunda: ```shell kubectl create secret -n camunda generic elastic-jks --from-file=externaldb.jks ``` 1. Configure the Camunda 8 Self-Managed Helm chart: ```yaml orchestration: data: secondaryStorage: type: elasticsearch elasticsearch: url: https://elastic.example.com:443 auth: username: elastic secret: inlineSecret: pass tls: secret: existingSecret: elastic-jks existingSecretKey: externaldb.jks ``` ### Connect to external Elasticsearch with a publicly trusted certificate This configuration works with managed Elasticsearch services. It has been tested with Elastic Cloud on Google Cloud. ```yaml orchestration: data: secondaryStorage: type: elasticsearch elasticsearch: url: https://elastic.example.com:443 auth: username: elastic secret: inlineSecret: pass ``` ### Connect to external Elasticsearch with custom index prefixes When running multiple Camunda instances on a shared Elasticsearch cluster, use custom index prefixes to isolate data: ```yaml orchestration: data: secondaryStorage: type: elasticsearch elasticsearch: url: https://elastic.example.com:443 auth: username: elastic secret: inlineSecret: pass index: prefix: my-env-camunda # Prefix for Orchestration Cluster indices ``` For more details on index prefix configuration, including Optimize-specific settings when Optimize is enabled, see [prefix Elasticsearch/OpenSearch indices](/self-managed/deployment/helm/configure/database/elasticsearch/configure-elasticsearch-prefix-indices.md). ## Troubleshooting If Zeebe pods fail, check for the following error: - The host is unreachable or DNS is not properly resolving to an IP address listening on the specified port. ```text Caused by: java.net.UnknownHostException: elastic.example.com ``` ## References - [Camunda production installation guide with Kubernetes and Helm](/self-managed/deployment/helm/install/production/index.md) - [Use external Elasticsearch for Optimize with Helm](/self-managed/deployment/helm/configure/database/optimize/using-external-elasticsearch.md) - [Configure Elasticsearch and OpenSearch index prefixes](/self-managed/deployment/helm/configure/database/elasticsearch/configure-elasticsearch-prefix-indices.md) ## Next steps Use the custom values file to [deploy Camunda 8](/self-managed/setup/overview.md): ```sh helm install camunda camunda/camunda-platform --version $HELM_CHART_VERSION -f existing-elasticsearch-values.yaml ``` --- ## Helm chart database configuration :::tip Need end-to-end guidance for RDBMS? For a unified setup guide covering provisioning, topology decisions, driver management, and backup strategies across Orchestration Cluster and Web Modeler, see the [end-to-end RDBMS setup guide](/self-managed/concepts/databases/relational-db/rdbms-setup-guide.md). This guide is useful both when starting a new setup and when harmonizing existing component configurations. ::: :::tip Choosing a secondary storage backend? Use the [secondary storage overview](/self-managed/concepts/secondary-storage/index.md) as the navigation hub for Elasticsearch/OpenSearch and RDBMS paths. ::: Use this section to configure database layers for Helm deployments. This section is organized by component: - [Orchestration Cluster](/self-managed/deployment/helm/configure/database/non-sql.md): Configure Elasticsearch or OpenSearch as secondary storage, or use [RDBMS](/self-managed/deployment/helm/configure/database/rdbms.md). - [Management Identity and Camunda Hub](/self-managed/deployment/helm/configure/database/using-existing-postgres.md): Configure PostgreSQL for the management plane. - [Optimize](/self-managed/deployment/helm/configure/database/optimize/index.md): Configure Elasticsearch or OpenSearch for Optimize. Some Elasticsearch/OpenSearch tasks, such as custom headers and index prefixes, apply to both the Orchestration Cluster and Optimize. Those shared pages call that out explicitly. --- ## Configure Elasticsearch and OpenSearch for Orchestration Cluster in Helm Use this page as the navigation hub for Elasticsearch and OpenSearch configuration for the Orchestration Cluster in Helm deployments. This page applies to the Orchestration Cluster only. If you also deploy Optimize, use the dedicated [Optimize database configuration](/self-managed/deployment/helm/configure/database/optimize/index.md) pages for Optimize-specific settings. ## Configure secondary storage backends Use the following pages based on your backend: - [Use external Elasticsearch with Helm](/self-managed/deployment/helm/configure/database/elasticsearch/using-external-elasticsearch.md) - [Use Amazon OpenSearch Service with the Helm chart](/self-managed/deployment/helm/configure/database/using-external-opensearch.md) ## Shared Elasticsearch and OpenSearch tasks Use the following pages when you need settings that can apply to both the Orchestration Cluster and Optimize: - [Configure custom HTTP headers for database clients](/self-managed/deployment/helm/configure/database/configure-db-custom-headers.md) - [Configure Elasticsearch and OpenSearch index prefixes](/self-managed/deployment/helm/configure/database/elasticsearch/configure-elasticsearch-prefix-indices.md) ## Troubleshooting - [All shards failed errors](/self-managed/deployment/helm/configure/database/all-shards-failed.md) ## Related concepts - [Secondary storage overview](/self-managed/concepts/secondary-storage/index.md) - [Elasticsearch/OpenSearch](/reference/glossary.md#elasticsearchopensearch) --- ## Configure Optimize databases in Helm chart Use this section to configure Optimize's database connection in Helm deployments. Optimize supports Elasticsearch or OpenSearch only. It does not support RDBMS. This section applies to Optimize only. If you also need to configure Elasticsearch or OpenSearch for the Orchestration Cluster, use the [Orchestration Cluster Elasticsearch/OpenSearch pages](/self-managed/deployment/helm/configure/database/non-sql.md). Use the following pages based on your backend: - [Use external Elasticsearch for Optimize with Helm](/self-managed/deployment/helm/configure/database/optimize/using-external-elasticsearch.md) - [Use external OpenSearch for Optimize with Helm](/self-managed/deployment/helm/configure/database/optimize/using-external-opensearch.md) Shared Elasticsearch and OpenSearch tasks: - [Configure custom HTTP headers for database clients](/self-managed/deployment/helm/configure/database/configure-db-custom-headers.md) - [Configure Elasticsearch and OpenSearch index prefixes](/self-managed/deployment/helm/configure/database/elasticsearch/configure-elasticsearch-prefix-indices.md) For background on secondary storage choices and exporter behavior, see [secondary storage overview](/self-managed/concepts/secondary-storage/index.md). --- ## Use external Elasticsearch for Optimize with Helm Configure Optimize in Camunda 8 Self-Managed to connect to an external Elasticsearch instance when deploying with Helm. This page applies to Optimize only. If the Orchestration Cluster also uses Elasticsearch as secondary storage, configure that separately using [use external Elasticsearch for Orchestration Cluster with Helm](/self-managed/deployment/helm/configure/database/elasticsearch/using-external-elasticsearch.md). Optimize supports Elasticsearch only through Elasticsearch or OpenSearch backends. It does not support RDBMS. ## Prerequisites Before configuring, collect the following information about your external Elasticsearch instance: - URL to access the cluster (protocol, host, and port) - Authentication requirements and credentials (if needed) - TLS requirements: - Whether the certificate is publicly trusted - Whether you need to provide a custom or self-signed certificate ## Configuration ### Parameters Use the following Helm values for Optimize's Elasticsearch connection: | values.yaml option | type | default | description | | --------------------------------------------------------------- | ------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `optimize.database.elasticsearch.enabled` | boolean | `false` | Enables Elasticsearch for Optimize. | | `optimize.database.elasticsearch.external` | boolean | `false` | Set to `true` to connect to an external Elasticsearch instance. | | `optimize.database.elasticsearch.auth.username` | string | `""` | Username for external Elasticsearch authentication. | | `optimize.database.elasticsearch.auth.secret.inlineSecret` | string | `""` | Elasticsearch password as a plain-text value for non-production environments only. | | `optimize.database.elasticsearch.auth.secret.existingSecret` | string | `""` | Reference to an existing Kubernetes Secret containing the password. | | `optimize.database.elasticsearch.auth.secret.existingSecretKey` | string | `""` | Key within the existing Kubernetes Secret containing the password. | | `optimize.database.elasticsearch.prefix` | string | `zeebe-record` | Index prefix for `zeebe-record` indices. See [configure Elasticsearch and OpenSearch index prefixes](/self-managed/deployment/helm/configure/database/elasticsearch/configure-elasticsearch-prefix-indices.md). | | `optimize.database.elasticsearch.tls.enabled` | boolean | `false` | Enables TLS when connecting to Elasticsearch. | | `optimize.database.elasticsearch.tls.secret.existingSecret` | string | `""` | Name of the Kubernetes Secret containing a TLS certificate. | | `optimize.database.elasticsearch.tls.secret.existingSecretKey` | string | `externaldb.jks` | Key within the secret containing the TLS certificate. | | `optimize.database.elasticsearch.url.protocol` | string | `""` | Protocol to use when connecting to Elasticsearch. Possible values are `http` and `https`. | | `optimize.database.elasticsearch.url.host` | string | `""` | Hostname or IP address of the Elasticsearch instance. | | `optimize.database.elasticsearch.url.port` | integer | `0` | Port number of the Elasticsearch instance. | ### Example usage #### Connect Optimize to external Elasticsearch without a certificate ```yaml optimize: enabled: true database: elasticsearch: enabled: true external: true auth: username: elastic secret: inlineSecret: pass url: protocol: http host: elastic.example.com port: 443 ``` #### Connect Optimize to external Elasticsearch with a self-signed certificate If the Elasticsearch cluster accepts only `https` requests with a self-signed certificate: 1. Create an `externaldb.jks` file from the Elasticsearch certificate file. For example, using the `keytool` CLI: ```yaml keytool -import -alias elasticsearch -keystore externaldb.jks -storetype jks -file elastic.crt -storepass changeit -noprompt ``` 1. Create a Kubernetes secret from the `externaldb.jks` file before installing Camunda: ```yaml kubectl create secret -n camunda generic elastic-jks --from-file=externaldb.jks ``` 1. Configure Optimize: ```yaml optimize: enabled: true database: elasticsearch: enabled: true external: true tls: enabled: true secret: existingSecret: elastic-jks auth: username: elastic secret: inlineSecret: pass url: protocol: https host: elastic.example.com port: 443 ``` #### Connect Optimize to external Elasticsearch with a publicly trusted certificate ```yaml optimize: enabled: true database: elasticsearch: enabled: true external: true auth: username: elastic secret: inlineSecret: pass url: protocol: https host: elastic.example.com port: 443 ``` ## Related tasks - [Configure Elasticsearch and OpenSearch index prefixes](/self-managed/deployment/helm/configure/database/elasticsearch/configure-elasticsearch-prefix-indices.md) - [Configure custom HTTP headers for database clients](/self-managed/deployment/helm/configure/database/configure-db-custom-headers.md) - [Use external Elasticsearch for Orchestration Cluster with Helm](/self-managed/deployment/helm/configure/database/elasticsearch/using-external-elasticsearch.md) --- ## Use external OpenSearch for Optimize with Helm Configure Optimize in Camunda 8 Self-Managed to use OpenSearch when deploying with the Helm chart. This page applies to Optimize only. If the Orchestration Cluster also uses OpenSearch as secondary storage, configure that separately using [use Amazon OpenSearch Service for Orchestration Cluster with Helm](/self-managed/deployment/helm/configure/database/using-external-opensearch.md). Optimize supports Elasticsearch or OpenSearch only. It does not support RDBMS. :::info OpenSearch support Camunda 8 supports both the open-source [OpenSearch](https://opensearch.org/) distribution and [Amazon OpenSearch Service](https://aws.amazon.com/opensearch-service). ::: ## Prerequisites Amazon OpenSearch requires two layers of permissions: - AWS IAM permissions - OpenSearch internal authentication To connect to OpenSearch using AWS IAM roles for service accounts (IRSA), see the [IAM roles for service accounts documentation](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/terraform-setup.md#opensearch-module-setup). To connect to OpenSearch using Basic authentication, follow the configuration below. ## Configuration ### Parameters | Parameter | Type | Default | Description | | ------------------------------------------------------------ | ------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `optimize.database.opensearch.enabled` | boolean | `false` | Enable external OpenSearch. | | `optimize.database.opensearch.aws.enabled` | boolean | `false` | Enable AWS IRSA integration. | | `optimize.database.opensearch.auth.username` | string | `""` | Username for external OpenSearch authentication. | | `optimize.database.opensearch.auth.secret.inlineSecret` | string | `""` | OpenSearch password as a plain-text value (non-production only). | | `optimize.database.opensearch.auth.secret.existingSecret` | string | `""` | Reference to an existing Kubernetes Secret containing the password. | | `optimize.database.opensearch.auth.secret.existingSecretKey` | string | `""` | Key within the existing Kubernetes Secret containing the password. | | `optimize.database.opensearch.prefix` | string | `zeebe-record` | Index prefix for `zeebe-record` indices. See [Configure Elasticsearch and OpenSearch index prefixes](/self-managed/deployment/helm/configure/database/elasticsearch/configure-elasticsearch-prefix-indices.md). | | `optimize.database.opensearch.tls.enabled` | boolean | `false` | Enable TLS for external OpenSearch. | | `optimize.database.opensearch.tls.secret.existingSecret` | string | `""` | Name of the Kubernetes Secret containing a TLS certificate. | | `optimize.database.opensearch.tls.secret.existingSecretKey` | string | `externaldb.jks` | Key within the secret containing the TLS certificate. | | `optimize.database.opensearch.url.protocol` | string | `""` | Access protocol for OpenSearch. Possible values are `http` and `https`. | | `optimize.database.opensearch.url.host` | string | `""` | OpenSearch host, ideally the service name within the namespace. | | `optimize.database.opensearch.url.port` | integer | `0` | Port used to access OpenSearch. | ### Example usage ```yaml optimize: enabled: true database: opensearch: enabled: true auth: username: user secret: # For non-production environments only: inlineSecret: "your-password-here" # For production (recommended): # existingSecret: "opensearch-secret" # existingSecretKey: "password" url: protocol: https host: opensearch.example.com port: 443 ``` To avoid storing the username and password in plaintext in your `values.yaml`, reference a Kubernetes secret. For details and examples, see [Helm charts secret management](/self-managed/deployment/helm/configure/secret-management.md). ### Connect Optimize to external OpenSearch with custom index prefixes When running multiple Camunda instances on a shared OpenSearch cluster, use custom index prefixes to isolate data: ```yaml optimize: enabled: true database: opensearch: enabled: true prefix: my-env-zeebe auth: username: admin secret: inlineSecret: pass url: protocol: https host: opensearch.example.com port: 443 ``` For more details about index prefix configuration and matching exporter settings, see [configure Elasticsearch and OpenSearch index prefixes](/self-managed/deployment/helm/configure/database/elasticsearch/configure-elasticsearch-prefix-indices.md). ## References - [Helm charts secret management](/self-managed/deployment/helm/configure/secret-management.md) - [IAM roles for service accounts](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/terraform-setup.md#opensearch-module-setup) - [Configure Elasticsearch and OpenSearch index prefixes](/self-managed/deployment/helm/configure/database/elasticsearch/configure-elasticsearch-prefix-indices.md) - [Use Amazon OpenSearch Service for Orchestration Cluster with Helm](/self-managed/deployment/helm/configure/database/using-external-opensearch.md) --- ## JDBC driver management for RDBMS This page covers JDBC driver management for RDBMS deployments in Kubernetes. For background on secondary storage, see [secondary storage overview](/self-managed/concepts/secondary-storage/index.md). For configuration and troubleshooting, see [configure RDBMS in Helm charts](/self-managed/deployment/helm/configure/database/rdbms.md). For an end-to-end example, see [RDBMS example deployment](/self-managed/deployment/helm/install/helm-with-rdbms.md). ## Bundled vs. custom JDBC drivers ### Which drivers are included? Camunda bundles JDBC drivers for databases where licensing permits: | Database | Bundled | When to supply custom drivers | | ---------- | ------- | -------------------------------------------------------------------------------------------- | | PostgreSQL | Yes | Patches, extensions, or compatibility with older server versions. | | MariaDB | Yes | Custom JDBC features or compliance requirements. | | SQL Server | Yes | Custom features or version-specific requirements. | | H2 | Yes | Development and testing only; not recommended for production due to scalability limitations. | | Oracle | No | Always; licensing prevents bundling. | | MySQL | No | Always; licensing prevents bundling. | ### When to supply a custom driver Consider supplying a custom JDBC driver in these scenarios: 1. **Oracle or MySQL databases**: No bundled drivers available; custom drivers required. 2. **Version compatibility**: Your database version is not compatible with the bundled driver. 3. **Security patches**: A critical patch is available for the bundled driver before the next Camunda release. 4. **Custom extensions**: You use database-specific features not covered by bundled drivers. 5. **Compliance or licensing**: Your organization policy requires specific driver versions or sources. ### Driver provisioning strategies Choose one of the three approaches below. **Init container is recommended for production.** | Strategy | Pros | Cons | Best for | | -------------------- | --------------------------------------- | ---------------------------------- | ----------------------- | | **Init container** | Automatic at pod startup; reproducible. | Requires external download source. | Production (standard) | | **Custom image** | Simple for teams with image registries. | Not yet validated in production. | Dev/test only | | **ConfigMap/Volume** | GitOps-friendly; no external downloads. | Requires manual driver management. | Teams with restrictions | ## Loading JDBC drivers into pods :::note The following applies to each of the components that require JDBC drivers: Orchestration Cluster, Identity, and Web Modeler, though examples may only reference Orchestration Cluster. ::: Some databases—such as Oracle and MySQL—require JDBC drivers that cannot be included in the Camunda image due to licensing restrictions. You must provide these drivers at runtime using one of the following approaches. ### Option 1: Using an init container :::note This example uses `/driver-lib`, which the Orchestration Cluster automatically adds to the classpath. If you use a different directory, additional override configuration may be required. ::: ```yaml orchestration: exporters: camunda: enabled: false rdbms: enabled: true data: secondaryStorage: type: rdbms rdbms: url: jdbc:oracle:thin:@//hostname:1521/FREEPDB1 username: myuser secret: inlineSecret: mypassword extraVolumeMounts: - name: jdbcdrivers mountPath: /driver-lib extraVolumes: - name: jdbcdrivers emptyDir: {} initContainers: - name: fetch-jdbc-drivers image: alpine:3.19 imagePullPolicy: Always command: - sh - -c - > wget https://repo1.maven.org/maven2/com/oracle/database/jdbc/ojdbc11/23.9.0.25.07/ojdbc11-23.9.0.25.07.jar -O /driver-lib/ojdbc.jar volumeMounts: - name: jdbcdrivers mountPath: /driver-lib securityContext: runAsUser: 1001 ``` After loading JDBC drivers into pods, run the validation checklist in [validate RDBMS connectivity](/self-managed/deployment/helm/configure/database/validate-rdbms.md) to confirm the application can load the driver, reach the database, and initialize schema. ### Option 2: Using a custom Docker image :::note This is a custom image approach. For production, prefer the init-container method to stay aligned with supported Helm patterns. ::: Replace `` with the Camunda version you deploy. Use the same value in the Docker tags and in the Helm `tag`. ```dockerfile FROM camunda/camunda: ADD ojdbc11.jar /driver-lib/ojdbc11.jar ``` Build and push: ```sh docker build -t internal-registry/orchestration: . docker push internal-registry/orchestration: ``` Configure in Helm: ```yaml orchestration: exporters: camunda: enabled: false rdbms: enabled: true image: repository: internal-registry/orchestration tag: data: secondaryStorage: type: rdbms rdbms: url: jdbc:oracle:thin:@//hostname:1521/FREEPDB1 username: myuser secret: inlineSecret: mypassword ``` ### Option 3: Mounting a JDBC driver from a volume :::warning Important Mounting an `emptyDir volume` does not persist across pod restarts. Use a ConfigMap, PersistentVolume, or custom image for production. ::: ```yaml orchestration: exporters: camunda: enabled: false rdbms: enabled: true data: secondaryStorage: type: rdbms rdbms: url: jdbc:oracle:thin:@//hostname:1521/FREEPDB1 username: myuser secret: inlineSecret: mypassword extraVolumeMounts: - name: jdbcdrivers mountPath: /driver-lib extraVolumes: - name: jdbcdrivers emptyDir: {} ``` Copy the driver manually to the pod: ```sh kubectl cp /path/to/ojdbc11.jar :/driver-lib/ojdbc11.jar ``` ## Verifying driver loading After deployment, verify the JDBC driver was loaded: ```bash # Check that the driver JAR exists kubectl exec -- ls -la /driver-lib/ # Check logs for successful driver initialization kubectl logs | grep -i "driver\|jdbc" ``` Common success indicators in logs: ``` INFO org.springframework.boot.StartupInfoLogger - Started Application in X seconds INFO io.camunda.exporter.rdbms.RdbmsExporter - RdbmsExporter created with Configuration ``` Common failure indicators: ``` java.lang.ClassNotFoundException: oracle.jdbc.OracleDriver java.sql.SQLException: No suitable driver found ``` If you see failures, verify: 1. The driver JAR file is present in `/driver-lib/`. 2. The init container or custom image executed successfully. 3. The JDBC URL in your configuration matches the driver (for example, Oracle URL with Oracle driver). ## JDBC driver updates ### Updating bundled drivers Bundled drivers are updated with new Camunda releases. To update: 1. Identify the new Camunda version with the updated driver. 2. Upgrade Camunda: `helm upgrade camunda camunda/camunda-platform --version X.Y.Z -f values.yaml -n camunda`. ### Updating custom drivers If you're supplying a custom driver, update it by: 1. **Init container approach**: Update the driver download URL or image in your Helm values. 2. **Custom image approach**: Rebuild and push a new image with the updated driver. 3. **ConfigMap/Volume approach**: Update the driver JAR in your ConfigMap or PersistentVolume. Then redeploy: ```bash helm upgrade camunda camunda/camunda-platform -f values.yaml -n camunda ``` Kubernetes will recreate the pods with the new driver. --- ## Schema creation and management This page covers schema creation, upgrades, and management for RDBMS deployments. For configuration reference and troubleshooting, see [configure RDBMS in Helm charts](/self-managed/deployment/helm/configure/database/rdbms.md). :::note Related pages - **[Access SQL and Liquibase scripts](access-sql-liquibase-scripts.md)** - Where to find and download schema scripts. - **[Validate RDBMS connectivity](validate-rdbms.md)** - Verify schema and exporter after deployment. - **[JDBC drivers](rdbms-jdbc-drivers.md)** - Managing database drivers. ::: ## Automatic schema creation (autoDDL) By default, `autoDDL: true` enables automatic schema creation via Liquibase. This happens at pod startup: 1. Liquibase detects that the schema does not exist or is outdated. 2. Liquibase executes all SQL migrations to initialize the schema. 3. The exporter begins writing data. **Prerequisites for autoDDL:** For all databases, the database user must have `CREATE TABLE`, `ALTER TABLE`, and `DROP TABLE` permissions. Additional database-specific requirements: - **PostgreSQL**: `CREATE` permission on the database. - **Oracle**: `CREATE TABLE` and `TABLESPACE` (if using non-default tablespaces). - **SQL Server**: `CREATE TABLE`, `ALTER TABLE`, and `CONTROL` on the schema. - **MariaDB/MySQL**: `ALL PRIVILEGES` on the target database. ## Database user permissions ```sql CREATE ROLE camunda WITH LOGIN PASSWORD 'password'; GRANT CONNECT ON DATABASE camunda TO camunda; GRANT USAGE ON SCHEMA public TO camunda; GRANT CREATE ON SCHEMA public TO camunda; ``` ```sql CREATE USER camunda IDENTIFIED BY password; GRANT CREATE TABLE TO camunda; GRANT UNLIMITED TABLESPACE TO camunda; ``` ```sql CREATE USER camunda@'%' IDENTIFIED BY 'password'; GRANT ALL PRIVILEGES ON camunda.* TO camunda@'%'; FLUSH PRIVILEGES; ``` ```sql CREATE LOGIN camunda WITH PASSWORD = 'password'; CREATE USER camunda FOR LOGIN camunda; GRANT CREATE TABLE TO camunda; GRANT ALTER ON SCHEMA::dbo TO camunda; ``` ## Manual schema management If your database is managed by a dedicated DBA, disable autoDDL and manage schema updates manually: ```yaml orchestration: extraConfiguration: - file: "manual-schema-management.yaml" content: | camunda: data: secondary-storage: rdbms: auto-ddl: false ``` With `autoDDL: false`, you must apply SQL scripts to the database before deploying Camunda. Scripts are available in the Camunda release bundle or from the [Liquibase scripts page](/self-managed/deployment/helm/configure/database/access-sql-liquibase-scripts.md). ### When to use manual schema management - Your organization requires a separate schema deployment phase. - A dedicated DBA manages the database and DDL changes. - You need to validate schema changes before applying them to production. ## Schema verification After initial deployment or upgrade, verify the schema: ```sql -- PostgreSQL: Check tables exist SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'; -- MySQL/MariaDB: Check tables exist SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE(); -- Oracle: Check tables SELECT table_name FROM user_tables; -- SQL Server: Check tables SELECT table_name FROM information_schema.tables WHERE table_schema = 'dbo'; ``` Expected tables include workflow and history tables (for example, `process_instance`, `variable`, and `job`) and Liquibase metadata tables like `databasechangelog` and `databasechangeloglock`. You can also verify by checking logs: ```bash kubectl logs | grep -i liquibase ``` Success indicators: ``` INFO io.camunda.application.commons.rdbms.MyBatisConfiguration - Initializing Liquibase for RDBMS INFO org.springframework.web.servlet.DispatcherServlet - Completed initialization in X ms ``` ## Upgrading the schema When upgrading Camunda versions, choose the schema migration path that matches your `autoDDL` configuration. ### Step 1: Prepare for the upgrade Before upgrading the production cluster, perform the following steps: #### Back up your database Back up your database before upgrading. Use your database vendor's native tools: - **PostgreSQL**: [pg_dump documentation](https://www.postgresql.org/docs/current/app-pgdump.html) - **Oracle**: [EXPDP documentation](https://docs.oracle.com/en/database/oracle/oracle-database/sutil/oracle-data-pump-export-utility.html) - **MySQL**: [mysqldump documentation](https://dev.mysql.com/doc/refman/en/mysqldump.html) - **MariaDB**: [mariadb-dump documentation](https://mariadb.com/kb/en/mariadb-dump/) - **SQL Server**: [SQL Server backup documentation](https://learn.microsoft.com/en-us/sql/relational-databases/backup-restore/back-up-and-restore-of-sql-server-databases) #### Test the upgrade in staging Deploy the new Camunda version in a staging environment first to validate schema migrations. ### Step 2a: Automatic schema management If `autoDDL: true`, Liquibase applies schema migrations automatically when an upgraded Camunda orchestration pod starts. Camunda supports rolling upgrades for RDBMS deployments. During the upgrade, the first upgraded cluster node applies the schema changes. Liquibase holds a lock on the database schema while the migration runs, and other cluster nodes wait for the lock to be released before they start. (Optional) For large production clusters, reduce the orchestration deployment to one replica before the upgrade so only one upgraded pod starts the Liquibase migration: ```bash kubectl scale deployment camunda-orchestration --replicas=1 -n camunda ``` :::note This reduces processing capacity during the upgrade because only one orchestration replica remains. The schema migration is performed when the first upgraded cluster node starts. ::: Keep the effective replica count at `1` until Liquibase completes. If your Helm values manage the replica count, make sure the upgrade does not restore the larger replica count before the migration finishes. Deploy the new Camunda version: ```bash helm upgrade camunda camunda/camunda-platform --version X.Y.Z -f values.yaml -n camunda ``` For large databases and long-running schema migrations, review [Liquibase lock issues](#liquibase-lock-issues) before upgrading. You might need to increase the DDL lock wait timeout so a long-running migration is not treated as stale. The Helm chart defines a default `readinessProbe`. Longer-running migrations may cause the pod to be marked as not ready. If this happens, you can increase the `readinessProbe` timeout in your Helm values: ```yaml orchestration: readinessProbe: # Allows up to 900 seconds (15m) for Liquibase to complete before the pod is marked not ready initialDelaySeconds: 300 periodSeconds: 30 failureThreshold: 20 ``` After Liquibase completes, scale the orchestration deployment back to your desired replica count: ```bash kubectl scale deployment camunda-orchestration --replicas=3 -n camunda ``` :::note The RDBMS upgrade is a rolling upgrade and does not require downtime of the orchestration cluster. Some schema operations might take longer to complete when the cluster and database is under load. If you experience long-running migrations, consider reducing the client-side load of the orchestration cluster or scale down the cluster to 0 replicas before the upgrade. ::: ### Step 2b: Migrate the schema manually with SQL scripts If `autoDDL: false`, apply the SQL migration scripts before or during the Camunda version upgrade. The SQL scripts are forward compatible with the previous Camunda version, so you can apply them while the existing cluster is running. (Optional) Scale the orchestration deployment to zero replicas if your maintenance process requires the application to stop before schema changes are applied: ```bash kubectl scale deployment camunda-orchestration --replicas=0 -n camunda ``` Apply the SQL scripts from the Camunda release bundle or from the [Liquibase scripts page](/self-managed/deployment/helm/configure/database/access-sql-liquibase-scripts.md), then deploy the new Camunda version: ```bash helm upgrade camunda camunda/camunda-platform --version X.Y.Z -f values.yaml -n camunda ``` If you scaled the deployment to zero replicas, scale the orchestration deployment back to your desired replica count after the upgrade: ```bash kubectl scale deployment camunda-orchestration --replicas=3 -n camunda ``` Monitor the rollout: ```bash kubectl rollout status deployment/camunda-orchestration -n camunda ``` ### Step 3: Verify schema initialization Verify that Liquibase completed the schema migration successfully by checking logs: ```bash kubectl logs | grep -i liquibase ``` Look for "Liquibase: Update successful" or similar completion messages. If the migration fails, Liquibase will log the specific error. You can also verify schema initialization by checking the `databasechangelog` table: ```sql -- Verify Liquibase changelog table exists and contains entries SELECT COUNT(*) FROM databasechangelog; ``` This table should exist and contain entries for a fresh Camunda installation. On upgrades, this number increases as new changesets are applied. For troubleshooting, see [schema troubleshooting](#schema-troubleshooting). ## Rolling upgrades Rolling upgrades are available for RDBMS deployments that use Liquibase schema migration. For upgrade steps and optional scaling guidance, see [automatic schema management](#step-2a-automatic-schema-management). ### Rollback Camunda schema migrations are compatible with the previous version. This means that if you need to stop an upgrade and roll back to a previous version, you can do so without any issues. The previous version will be able to read the schema and continue processing. ## Schema troubleshooting ### Liquibase lock issues If a previous schema migration failed, Liquibase may hold a lock. Camunda waits for stale Liquibase DDL locks using `camunda.data.secondary-storage.rdbms.ddl-lock-wait-timeout` (default: `PT15M`). For large schema changes, you can increase this timeout so a long-running migration is not treated as stale. See [RDBMS troubleshooting](/self-managed/deployment/helm/configure/database/rdbms-troubleshooting.md#liquibase-lock-after-pod-crash-or-restart). Only release the lock manually after confirming no migration is currently running: ```sql -- PostgreSQL/MariaDB: Release the lock DELETE FROM databasechangeloglock WHERE locked = true; -- Oracle and SQL Server: Release the lock (on Oracle, connect as the schema owner) DELETE FROM databasechangeloglock WHERE locked = 1; ``` Then redeploy. ### Permission errors during autoDDL **Symptom:** Logs show "permission denied" or "cannot create table." **Fix:** Verify database user has DDL permissions (see [database user permissions](#database-user-permissions) above). ### Out-of-sync schema If your schema doesn't match the expected version: 1. Check Liquibase logs for failed migrations. 2. Restore from backup if necessary. 3. Manually apply missing SQL scripts from the [Liquibase scripts page](/self-managed/deployment/helm/configure/database/access-sql-liquibase-scripts.md). ## Liquibase resource access SQL migration scripts and Liquibase change logs are available in the Camunda release bundle. For details on accessing these resources, see [access SQL and Liquibase scripts](/self-managed/deployment/helm/configure/database/access-sql-liquibase-scripts.md). --- ## RDBMS search APIs and result count behavior When using RDBMS as secondary storage, search APIs behave similarly to Elasticsearch/OpenSearch, but with important differences in how result counts are computed and limited. ## Result count behavior ### Total results capping In RDBMS-backed deployments, `totalResults` is capped at **10,000** to improve performance and match Elasticsearch/OpenSearch behavior. See [query performance guidance](#query-performance-guidance) for optimization strategies. Example response: ```json { "items": [ /* 100 items */ ], "totalResults": 10000, "hasMoreTotalItems": true } ``` ### hasMoreTotalItems field When the actual result set exceeds the cap, the `hasMoreTotalItems` boolean field is set to `true`. This indicates there are more results available and that pagination is still possible—you can continue querying with `searchAfter` or `page` parameters. Use this field in UI components to display "more results available" without computing exact counts. ## Configuration ### Maximum result limit The result count cap is configurable per deployment: | Parameter | Type | Default | Description | | ----------------------------------------------------------- | ------- | ------- | -------------------------------------------------------------------------------------------------------- | | `camunda.data.secondary-storage.rdbms.query.max-total-hits` | integer | `10000` | Maximum number of results to count. Set higher to count larger result sets (performance cost increases). | Current chart style (embedded `application.yml`): ```yaml orchestration: extraConfiguration: - file: application.yml content: | camunda.data.secondary-storage.rdbms.query.max-total-hits: 10000 ``` :::warning Increasing `maxTotalHits` gives accurate counts for larger result sets but increases database load, especially for queries with large result sets. Test in your environment before setting values above 10,000. ::: ## Query performance guidance To optimize query performance, use selective filters and pagination: ### 1. Use selective filters Always filter by indexed columns to reduce the result set size. Indexed columns typically include: - **Key fields**: Process definition key, instance ID, case instance ID - **Date properties**: Creation date, completion date, update timestamp **Good example** (efficient): ``` Filter: processDefinitionKey = "myProcess" AND createdDate >= 2024-01-01 Result size: < 100 items, fast COUNT(*) ``` **Poor example** (expensive): ``` Filter: category = "audit" Result size: Potentially millions of items, expensive COUNT(*) ``` ### 2. Avoid sorting large result sets When the result set is much larger than the page size, sorting requires the database to read and order all matching rows, even if only 100 are returned. **Good pattern**: ``` Filter by date range + key field → sort by creation date → paginate ``` **Avoid**: ``` No filter → sort by arbitrary field → paginate (requires reading entire table) ``` ### 3. Prefer pagination over exact counts Rather than requesting exact counts for large result sets: - Use page size limits (e.g., 100 items per page) - Check `hasMoreTotalItems` to determine if more results exist - Continue paginating as needed This avoids unnecessary COUNT(\*) operations on queries that may scan large portions of the table. ## Database-specific tuning (PostgreSQL) If you are using PostgreSQL as your RDBMS and experience slow search queries, tune compute resources, storage performance, and PostgreSQL server parameters based on your workload and hardware. Typical PostgreSQL parameters to evaluate for load-testing scenarios include: ```conf # WAL performance wal_buffers = 64MB max_wal_size = 4GB min_wal_size = 1GB checkpoint_timeout = 20min checkpoint_completion_target = 0.9 wal_writer_delay = 200ms wal_writer_flush_after = 1MB # Memory shared_buffers = 2GB effective_cache_size = 4500MB work_mem = 32MB maintenance_work_mem = 512MB # Autovacuum autovacuum_max_workers = 6 autovacuum_naptime = 15s autovacuum_vacuum_scale_factor = 0.03 autovacuum_analyze_scale_factor = 0.02 autovacuum_vacuum_cost_limit = 5000 # Monitoring pg_stat_statements.track = all pg_stat_statements.max = 10000 track_io_timing = on track_functions = all ``` :::note These are PostgreSQL-native settings. How you apply them depends on your deployment model (for example, managed database parameter groups, `postgresql.conf`, or Kubernetes operator configuration such as CloudNativePG). For production, start conservative and adjust based on your data volume, workload, and hardware. Camunda is a write-heavy application, so prioritize cache and vacuum settings for your environment. ::: Database-specific tuning for MariaDB and Oracle will be documented in future updates. ## Related guides - [Configure RDBMS in Helm charts](/self-managed/deployment/helm/configure/database/rdbms.md) - [JDBC driver management](/self-managed/deployment/helm/configure/database/rdbms-jdbc-drivers.md) - [RDBMS troubleshooting and operations](/self-managed/deployment/helm/configure/database/rdbms-troubleshooting.md) --- ## RDBMS troubleshooting and operations This page covers troubleshooting common issues, TLS configuration, and post-deployment operations for RDBMS deployments. For configuration reference, see [configure RDBMS in Helm charts](/self-managed/deployment/helm/configure/database/rdbms.md). :::note Related pages - **[Validate RDBMS connectivity](validate-rdbms.md)** - Quick validation checklist with database client examples. - **[Schema management](rdbms-schema-management.md)** - Schema creation and lifecycle. - **[JDBC drivers](rdbms-jdbc-drivers.md)** - Managing database drivers. ::: ## Connection failures **Symptom:** Pod fails to connect to the database (connection timeout, connection refused). **Diagnosis:** 1. Verify network connectivity from the pod to the database: ```bash kubectl exec -- nc -zv database-hostname port ``` 2. Check the JDBC URL in your configuration: ```bash kubectl get secret camunda-db-secret -o jsonpath='{.data.}' -n camunda | base64 -d ``` 3. Verify the database is running and accepting connections. **Fix:** Confirm the JDBC URL, hostname, port, and network policies allow traffic between pods and database. ## Authentication errors **Symptom:** "Authentication failed" or "Invalid password" in logs. **Diagnosis:** 1. Verify the secret exists and contains the correct password: ```bash kubectl get secret camunda-db-secret -o jsonpath='{.data.}' -n camunda | base64 -d ``` 2. Check the username in your Helm values matches the database user. 3. Test connection credentials manually (if possible from a pod or bastion host). **Fix:** Ensure the username, password, and secret key reference are correct in your Helm values. ## JDBC driver not found **Symptom:** ClassNotFoundException or "No suitable JDBC driver" in logs. **Diagnosis:** 1. Verify the driver JAR file was loaded: ```bash kubectl exec -- ls -la /driver-lib/ ``` 2. Check init container logs: ```bash kubectl logs -c fetch-jdbc-drivers ``` 3. Verify the JDBC URL matches the driver type (for example, Oracle URL with Oracle driver). **Fix:** Re-apply the init container configuration or verify the custom image includes the driver. See [JDBC driver management](/self-managed/deployment/helm/configure/database/rdbms-jdbc-drivers.md). ## Schema creation failure **Symptom:** Liquibase errors; tables not created. **Diagnosis:** 1. Check Liquibase logs: ```bash kubectl logs | grep -i liquibase ``` 2. Check whether `autoDDL` was disabled (default: `true`). If it's `false`, Camunda doesn't create the schema automatically. The following example shows a disabled configuration: ```yaml orchestration: extraConfiguration: - file: "manual-schema-management.yaml" content: | camunda: data: secondary-storage: rdbms: auto-ddl: false # If false, the schema isn't created automatically ``` 3. Test database user permissions (see [schema management](/self-managed/deployment/helm/configure/database/rdbms-schema-management.md#database-user-permissions)). **Fix:** Ensure database user has DDL permissions or disable autoDDL and apply schema manually. See [schema management](/self-managed/deployment/helm/configure/database/rdbms-schema-management.md). ## Liquibase lock after pod crash or restart **Symptom:** Pod startup appears stuck on Liquibase, or repeated restarts fail while waiting for `databasechangeloglock`. **Cause:** A previous pod may have been terminated while Liquibase was still running, leaving a lock row behind. **Behavior:** Camunda waits for a stale Liquibase DDL lock using `camunda.data.secondary-storage.rdbms.ddl-lock-wait-timeout` (default: `PT15M`). **Fix:** 1. Increase the timeout for slow migrations (for example, large index creation): ```yaml orchestration: extraConfiguration: - file: "rdbms-liquibase-lock-timeout.yaml" content: | camunda: data: secondary-storage: rdbms: ddl-lock-wait-timeout: PT30M ``` 2. Avoid terminating Orchestration Cluster pods while Liquibase is actively applying migrations. 3. Only release `databasechangeloglock` manually when you have verified no migration is still running. ## Slow data export **Symptom:** Data takes a long time to appear in the database after process events. **Cause:** Flush interval or queue size not tuned for your workload. **Diagnosis:** 1. Check current flush interval in logs: ```bash kubectl logs | grep -i flushinterval ``` 2. Verify queue size settings in your Helm values. **Fix:** Adjust these settings: ```yaml orchestration: extraConfiguration: - file: "flush-interval.yaml" content: | camunda: data: secondary-storage: rdbms: flush-interval: PT0.25S # More frequent flushes (default: PT0.5S) queue-size: 5000 # Larger queue for buffering queue-memory-limit: 50 # Increase if needed ``` - Smaller `flush-interval` → more frequent writes (increases DB load). - Larger `queue-size` → more events buffered before flush (increases memory). ## TLS/SSL configuration Add SSL parameters to the JDBC URL: ```yaml orchestration: data: secondaryStorage: rdbms: url: jdbc:postgresql://hostname:5432/camunda?ssl=true&sslmode=require ``` Oracle uses TCPS (TLS over Oracle protocol): ```yaml orchestration: data: secondaryStorage: rdbms: url: jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCPS)(HOST=hostname)(PORT=2484))(CONNECT_DATA=(SERVICE_NAME=FREEPDB1))) ``` SQL Server enables encryption via the JDBC URL: ```yaml orchestration: data: secondaryStorage: rdbms: url: jdbc:sqlserver://hostname:1433;databaseName=camunda;encrypt=true;trustServerCertificate=false ``` MariaDB and MySQL enable SSL via the JDBC URL: ```yaml orchestration: data: secondaryStorage: rdbms: # MariaDB url: jdbc:mariadb://hostname:3306/camunda?sslMode=verify-full # MySQL url: jdbc:mysql://hostname:3306/camunda?useSSL=true&requireSSL=true ``` ### Self-signed certificates If your database uses self-signed certificates: 1. Extract the certificate from your database server. 2. Create a Kubernetes secret: ```bash kubectl create secret generic db-certs \ --from-file=ca.crt=/path/to/ca.crt \ -n camunda ``` 3. Mount the certificate and configure trust (consult your database vendor's JDBC documentation). ## Post-deployment operations These operations are officially supported on running Camunda clusters: ### Database password rotation Password rotations are safe: 1. Update the password in your RDBMS. 2. Update the Kubernetes secret: ```bash kubectl patch secret camunda-db-secret \ -p '{"data":{"db-password":"'$(echo -n 'new-password' | base64)'"}}' \ -n camunda ``` 3. Restart the Orchestration Cluster pods: ```bash kubectl rollout restart statefulset/camunda-zeebe -n camunda ``` ### JDBC driver updates Updating bundled drivers or replacing custom drivers: 1. For custom drivers via init container: Update the JAR source in your Helm values. 2. For bundled drivers: Update the Camunda version. 3. Redeploy: ```bash helm upgrade camunda camunda/camunda-platform -f values.yaml -n camunda ``` See [JDBC driver management](/self-managed/deployment/helm/configure/database/rdbms-jdbc-drivers.md#jdbc-driver-updates). ### Schema validation Verify schema integrity after upgrades or restores: ```sql -- PostgreSQL: Count expected tables SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND (table_name LIKE 'zeebe_%' OR table_name LIKE 'process_%'); -- Oracle: Count expected tables SELECT COUNT(*) FROM user_tables WHERE table_name LIKE 'ZEEBE_%' OR table_name LIKE 'PROCESS_%'; ``` Expect roughly 20-30 tables depending on your Camunda version. ## Connectivity health checks After deployment, verify the cluster is healthy: 1. **Check pod readiness:** ```bash kubectl get pods -n camunda | grep orchestration ``` 2. **Check exporter logs:** ```bash kubectl logs | grep RdbmsExporter ``` 3. **Verify table creation:** ```sql SELECT COUNT(*) FROM zeebe_process; ``` 4. **Deploy a test process** and verify it appears in the database. For a complete post-deployment checklist, see [validate RDBMS connectivity](/self-managed/deployment/helm/configure/database/validate-rdbms.md). ## Transaction isolation levels This section applies to: - Microsoft SQL Server - Azure SQL Database SQL Server and Azure SQL Database use lock-based behavior for the `READ COMMITTED` isolation level by default. Under high concurrent load, this can increase deadlock frequency in Camunda workloads. Example deadlock error: > com.microsoft.sqlserver.jdbc.SQLServerException: An error occurred during the current command (Done status 0). Transaction (Process ID 83) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction. If you observe this deadlock pattern in your MSSQL installation, enable `READ_COMMITTED_SNAPSHOT` on the Camunda database: ```sql ALTER DATABASE [database-name] SET READ_COMMITTED_SNAPSHOT ON; ``` --- ## Configure RDBMS in Helm chart Camunda 8 Self-Managed supports using an external relational database (RDBMS) as the Orchestration Cluster's secondary storage instead of Elasticsearch or OpenSearch. This page provides: - **[Configuration reference](#configuration)**: All Helm values organized by function. - **[Quick example](#example-usage)**: Minimal YAML to get started. - Links to detailed guides for specific tasks. Related guides: - [Secondary storage architecture](/self-managed/reference-architecture/reference-architecture.md#secondary-storage-architecture) - [Secondary storage overview](/self-managed/concepts/secondary-storage/index.md) - [RDBMS example deployment](/self-managed/deployment/helm/install/helm-with-rdbms.md) - [JDBC driver management](/self-managed/deployment/helm/configure/database/rdbms-jdbc-drivers.md) ## Prerequisites Provide a supported relational database that is reachable by the Camunda components. See the [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) for the complete list of supported databases and versions. Ensure that: - Your network allows traffic from Camunda pods to the database. - Required JDBC parameters (SSL/TLS, authentication, failover) are configured as needed. - The database user has permissions to create and modify schema objects if `autoDDL` is enabled. For a short checklist and troubleshooting steps you can run after configuring the database, see [validate RDBMS connectivity (Helm)](/self-managed/deployment/helm/configure/database/validate-rdbms.md). ## Configuration ### Connection parameters (required) | Parameter | Type | Default | Description | | ---------------------------------------------------- | ------ | ------- | --------------------------------------------- | | `orchestration.data.secondaryStorage.type` | string | `""` | Must be `rdbms` to use a relational database. | | `orchestration.data.secondaryStorage.rdbms.url` | string | `""` | JDBC connection URL for the database. | | `orchestration.data.secondaryStorage.rdbms.username` | string | `""` | Username for database authentication. | ### Database credentials Store the database password in a Kubernetes secret and reference it. For testing only, you can use `inlineSecret`. | Parameter | Type | Default | Description | | -------------------------------------------------------------------- | ------ | ------- | --------------------------------------------------- | | `orchestration.data.secondaryStorage.rdbms.secret.existingSecret` | string | `""` | Name of Kubernetes secret containing the password. | | `orchestration.data.secondaryStorage.rdbms.secret.existingSecretKey` | string | `""` | Key within the secret storing the password. | | `orchestration.data.secondaryStorage.rdbms.secret.inlineSecret` | string | `""` | Password value (testing only, not production-safe). | ### Other parameters RDBMS supports other configuration options that can be configured in the helm chart `values.yaml` via [extraConfiguration](/self-managed/deployment/helm/configure/application-configs.md). See [RDBMS options](/self-managed/concepts/databases/relational-db/configuration.md). For Liquibase lock recovery behavior, configure `camunda.data.secondary-storage.rdbms.ddl-lock-wait-timeout` (default: `PT15M`) via `extraConfiguration` if you need a longer wait time for heavy schema migrations. ### Example usage :::note RDBMS is fully supported as secondary storage. For production planning, review the [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md). ::: ```yaml orchestration: exporters: camunda: enabled: false rdbms: enabled: true data: secondaryStorage: type: rdbms rdbms: url: jdbc:postgresql://hostname:5432/camunda username: camunda secret: existingSecret: camunda-db-secret existingSecretKey: password ``` ## Bundled vs. custom JDBC drivers Camunda bundles JDBC drivers for some databases. For others, you must supply a custom driver. See [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) for the complete list of supported databases. **See:** [JDBC driver management](/self-managed/deployment/helm/configure/database/rdbms-jdbc-drivers.md) for: - Which drivers are bundled - When to supply custom drivers - How to load drivers (init containers, custom images, volumes) ## Search APIs and result limits RDBMS search APIs return a `totalResults` field capped at **10,000** by default (configurable via `camunda.data.secondary-storage.rdbms.query.max-total-hits`). Actual query performance depends on filter selectivity and database optimization. See [search APIs and result limits](/self-managed/deployment/helm/configure/database/rdbms-search-and-result-limits.md) for configuration options, performance trade-offs, optimization best practices, and database-specific tuning. ## Schema creation and management Camunda automatically creates your database schema using Liquibase (when `autoDDL: true`). You can also manage the schema manually if required. **See:** [Schema management](/self-managed/deployment/helm/configure/database/rdbms-schema-management.md) for: - Automatic schema creation with autoDDL - Database user permissions for each RDBMS type - Manual schema management and DBA workflows - Schema upgrades and verification ## Troubleshooting and operations For detailed troubleshooting of common issues and post-deployment operations, see [RDBMS troubleshooting and operations](/self-managed/deployment/helm/configure/database/rdbms-troubleshooting.md), which covers: - Connection failures and authentication errors - JDBC driver loading issues - Schema creation failures - Slow data export and performance tuning - TLS/SSL configuration - Post-deployment operations (password rotation, driver updates, schema validation) ## Verifying connectivity After deployment, verify the Orchestration Cluster is writing to the database: 1. Confirm tables were created: ```sql -- PostgreSQL example SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'; ``` 2. Deploy a process and start an instance using Camunda Hub or the REST API. 3. Query the database to confirm the instance was recorded: ```sql SELECT * FROM process_instance; ``` 4. Review logs for successful initialization: ``` INFO io.camunda.exporter.rdbms.RdbmsExporter - RdbmsExporter created with Configuration: flushInterval=PT0.5S INFO io.camunda.exporter.rdbms.RdbmsExporter - Exporter opened with last exported position ``` For a complete post-deployment checklist, see [validate RDBMS connectivity (Helm)](/self-managed/deployment/helm/configure/database/validate-rdbms.md). ## Using AWS Aurora PostgreSQL (optional) If you are using AWS Aurora PostgreSQL as your relational database, you can configure it the same way as a standard PostgreSQL instance. Optionally, Camunda also supports the AWS JDBC wrapper driver, which provides additional features such as improved failover handling and IAM-based authentication. For details and examples, see [using AWS Aurora PostgreSQL with Camunda](../../../../concepts/databases/relational-db/configuration.md#usage-with-aws-aurora-postgresql--mysql). ## Limitations and unsupported scenarios ### Component-specific RDBMS support - **Orchestration Cluster**: ✅ Full RDBMS support for secondary storage (includes Zeebe, Operate, Tasklist, Orchestration Identity). - **Connectors**: ✅ Supports RDBMS for process definitions and state. - **Camunda Hub**: ✅ Supports RDBMS. - **Optimize**: ❌ **Requires Elasticsearch or OpenSearch only.** Optimize cannot use RDBMS. If you deploy Optimize, you must still provision Elasticsearch or OpenSearch. ### Multi-region deployments Deploy the RDBMS in the same region as your Kubernetes cluster. To run one Orchestration Cluster across several regions, follow the [Multi-Region RDBMS](/self-managed/concepts/multi-region/multi-region-rdbms.md) architecture, which delegates replication of the secondary storage to the database. Review its requirements and limitations first. ### Self-managed database HA Camunda assumes your RDBMS handles its own HA (replication, failover). Use cloud-managed databases or vendor-specific HA solutions for production. ### Custom JDBC driver libraries Only JDBC drivers from official vendor sources are supported. Custom or modified drivers may cause unexpected behavior. --- ## Use external PostgreSQL The Camunda Helm chart requires externally managed PostgreSQL for Camunda Hub and Management Identity. This guide steps through connecting these components to an external PostgreSQL instance. Provide PostgreSQL through a managed service or a Kubernetes operator, such as the [CloudNativePG operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#postgresql-deployment). This page applies to Management Identity and Camunda Hub. Configure the database for an external Keycloak deployment separately. It does not apply to the Orchestration Cluster or Optimize. ## Prerequisites - **Running external PostgreSQL service** - **Connection details:** following sample values are used in this guide (replace them with your own): ```yaml host: db.example.com port: 5432 username: postgres password: examplePassword ``` - **Supported versions:**: Check the [supported environments](/reference/supported-environments.md) and [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) pages to confirm which PostgreSQL versions are supported. - **Database setup:** Ensure the required databases exist in your PostgreSQL instance. For this guide, create the following databases: ```SQL CREATE DATABASE "web-modeler"; CREATE DATABASE "management-identity"; ``` - **Kubernetes secrets:** Store the database password in a Kubernetes secret so it is not referenced in plain text within your values.yaml (This secret exists outside the Helm chart and will not be overwritten by subsequent helm upgrade commands). For example: ```bash kubectl create secret generic camunda-psql-db --from-literal=password=examplePassword -n camunda ``` ## Configuration Management Identity and Camunda Hub require PostgreSQL. Configure each component to connect to the external PostgreSQL instance. Keycloak's database is configured where Keycloak is deployed (operator or external), not through the Helm chart. ### Parameters | values.yaml option | type | default | description | | -------------------------------------------------------------- | ------- | ------- | ------------------------------------------------------------------------ | | `camundaHub.restapi.externalDatabase.url` | string | `""` | JDBC URL of the database | | `camundaHub.restapi.externalDatabase.username` | string | `""` | Username of the database | | `camundaHub.restapi.externalDatabase.secret.existingSecret` | string | `""` | Kubernetes Secret name containing a database password | | `camundaHub.restapi.externalDatabase.secret.existingSecretKey` | string | `""` | Key within the Kubernetes Secret that has the database password | | `camundaHub.restapi.externalDatabase.secret.inlineSecret` | string | `""` | String literal of the database password if not using a Kubernetes Secret | | `identity.externalDatabase.enabled` | boolean | `false` | Enable the externalDatabase options | | `identity.externalDatabase.host` | string | `""` | Hostname of the database | | `identity.externalDatabase.port` | integer | `5432` | Port of the database | | `identity.externalDatabase.username` | string | `""` | Username of the database | | `identity.externalDatabase.secret.existingSecret` | string | `""` | Kubernetes Secret name containing database password | | `identity.externalDatabase.secret.existingSecretKey` | string | `""` | Key within the Kubernetes Secret that contains the database password | | `identity.externalDatabase.database` | string | `""` | Database name | ### Example usage ```yaml camundaHub: enabled: true restapi: externalDatabase: url: "jdbc:postgresql://db.example.com:5432/web-modeler" username: "postgres" secret: existingSecret: "camunda-psql-db" existingSecretKey: "password" identity: externalDatabase: enabled: true host: "db.example.com" port: 5432 username: "postgres" secret: existingSecret: "camunda-psql-db" existingSecretKey: "password" database: "management-identity" ``` ## Troubleshooting ### Other components log `401` errors **Observed behavior:** Applications other than Keycloak log `401` errors. **Why this happens:** The Keycloak database is misconfigured, so other components can't authenticate against Keycloak. **How to fix:** Verify the Keycloak database connection settings where you deployed Keycloak, for example in the [Keycloak operator deployment](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#keycloak-deployment), and confirm Keycloak itself starts up without errors before checking other components. ### A component logs a `database missing` error **Observed behavior:** A component logs a `database missing` error at startup. **Why this happens:** The database it expects hasn't been created yet in your external PostgreSQL instance. **How to fix:** Create the missing database in your external PostgreSQL instance, matching the name that component expects. ## References --- ## Use Amazon OpenSearch Service for Orchestration Cluster with Helm Configure the Orchestration Cluster in Camunda 8 Self-Managed to use Amazon OpenSearch Service as a secondary storage backend when deploying with the Helm chart. OpenSearch is used for indexing and querying operational data consumed by Orchestration Cluster applications and APIs. For a canonical definition, see [Elasticsearch/OpenSearch](/reference/glossary.md#elasticsearchopensearch). Starting with Camunda 8.9, the Helm chart no longer provisions Elasticsearch by default. You can configure the Helm chart to connect to an external Amazon OpenSearch Service instance as an alternative secondary storage backend. This page applies to the Orchestration Cluster only. If you also deploy Optimize, configure Optimize separately using [use external OpenSearch for Optimize with Helm](/self-managed/deployment/helm/configure/database/optimize/using-external-opensearch.md). Secondary storage is configurable. For supported components, you can use an RDBMS-based secondary store instead. See [RDBMS configuration](/self-managed/concepts/databases/relational-db/configuration.md) or the glossary entry [RDBMS](/reference/glossary.md#rdbms). For the [quick-install](/self-managed/deployment/helm/install/quick-install.md) scenario, RDBMS with embedded H2 is used instead. :::info OpenSearch support Camunda 8 supports both the open-source [OpenSearch](https://opensearch.org/) distribution and [Amazon OpenSearch Service](https://aws.amazon.com/opensearch-service). ::: ## Prerequisites Amazon OpenSearch requires two layers of permissions: - AWS IAM permissions - OpenSearch internal authentication To connect to OpenSearch using AWS IAM roles for service accounts (IRSA), see the [IAM roles for service accounts documentation](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/terraform-setup.md#opensearch-module-setup). To connect to OpenSearch using Basic authentication, follow the configuration below. ## Configuration ### Parameters #### Orchestration Cluster secondary storage | Parameter | Type | Default | Description | | ------------------------------------------------------------------------------ | ------ | ------- | ----------------------------------------------------------------------------------------------- | | `orchestration.data.secondaryStorage.type` | string | `""` | Type of secondary storage. Set to `opensearch` to use OpenSearch. | | `orchestration.data.secondaryStorage.opensearch.url` | string | `""` | URL for the OpenSearch cluster as `scheme://host:port` (for example, `https://opensearch:443`). | | `orchestration.data.secondaryStorage.opensearch.auth.username` | string | `""` | Username for OpenSearch authentication. | | `orchestration.data.secondaryStorage.opensearch.auth.secret.inlineSecret` | string | `""` | OpenSearch password as a plain-text value (non-production only). | | `orchestration.data.secondaryStorage.opensearch.auth.secret.existingSecret` | string | `""` | Reference to an existing Kubernetes Secret containing the password. | | `orchestration.data.secondaryStorage.opensearch.auth.secret.existingSecretKey` | string | `""` | Key within the existing Kubernetes Secret containing the password. | | `orchestration.data.secondaryStorage.opensearch.tls.secret.existingSecret` | string | `""` | Deprecated in chart 15.x. Use `global.tls.caBundle.secret.existingSecret` instead. | | `orchestration.data.secondaryStorage.opensearch.tls.secret.existingSecretKey` | string | `""` | Deprecated in chart 15.x. Use `global.tls.caBundle.secret.existingSecretKey` instead. | | `orchestration.index.prefix` | string | `""` | Index prefix in OpenSearch for the new Camunda exporter and the Orchestration Cluster. | To trust a private CA for the OpenSearch connection, supply a PEM CA bundle through `global.tls.caBundle.secret`. See [Configure TLS](/self-managed/deployment/helm/configure/tls.md). ### Example usage ```yaml orchestration: data: secondaryStorage: type: opensearch opensearch: url: https://opensearch.example.com:443 auth: username: user secret: # For non-production environments only: inlineSecret: "your-password-here" # For production (recommended): # existingSecret: "opensearch-secret" # existingSecretKey: "password" ``` This configuration connects the Orchestration Cluster to an external Amazon OpenSearch Service instance as its secondary storage backend. To avoid storing the username and password in plaintext in your `values.yaml`, reference a Kubernetes secret. For details and examples, see [Helm charts secret management](/self-managed/deployment/helm/configure/secret-management.md). ### Connect to external OpenSearch with custom index prefixes When running multiple Camunda instances on a shared OpenSearch cluster, use custom index prefixes to isolate data: ```yaml orchestration: data: secondaryStorage: type: opensearch opensearch: url: https://opensearch.example.com:443 auth: username: admin secret: inlineSecret: pass index: prefix: my-env-camunda # Prefix for Orchestration Cluster indices ``` For more details about index prefix configuration and Optimize-specific settings, see [Configure Elasticsearch and OpenSearch index prefixes](/self-managed/deployment/helm/configure/database/elasticsearch/configure-elasticsearch-prefix-indices.md). ### Component configuration Operate and Tasklist use the secondary storage configuration of the Orchestration Cluster. For the underlying properties, see the [secondary storage properties](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#data---secondary-storage) and [configure secondary storage](/self-managed/concepts/secondary-storage/configuring-secondary-storage.md). For **Zeebe**, configure the [OpenSearch exporter](/self-managed/components/orchestration-cluster/zeebe/exporters/opensearch-exporter.md). ## References - [Helm charts secret management](/self-managed/deployment/helm/configure/secret-management.md) - [IAM roles for service accounts](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/terraform-setup.md#opensearch-module-setup) - [OpenSearch exporter](/self-managed/components/orchestration-cluster/zeebe/exporters/opensearch-exporter.md) - [Use external OpenSearch for Optimize with Helm](/self-managed/deployment/helm/configure/database/optimize/using-external-opensearch.md) - [Configure Elasticsearch and OpenSearch index prefixes](/self-managed/deployment/helm/configure/database/elasticsearch/configure-elasticsearch-prefix-indices.md) - [Deploy Camunda 8](/self-managed/setup/overview.md) --- ## Validate RDBMS connectivity Verify RDBMS connectivity, schema initialization, and exporter activity for Helm deployments. :::note Related pages - **[Configure RDBMS](rdbms.md)** - Configuration reference and Helm values. - **[JDBC driver management](rdbms-jdbc-drivers.md)** - Managing custom database drivers. - **[Schema management](rdbms-schema-management.md)** - Schema creation and lifecycle. - **[Troubleshooting](rdbms-troubleshooting.md)** - Common issues and diagnostic steps. ::: Supported databases include PostgreSQL, MySQL/MariaDB, and Oracle (examples below). Use your database vendor’s CLI tools for direct verification. ## Prerequisites - The database endpoint is reachable from the Kubernetes cluster (DNS, routing, firewall/security groups, NetworkPolicies). - Your Helm values are configured for RDBMS secondary storage and the RDBMS exporter is enabled. - For databases that require external JDBC drivers (for example Oracle), the driver JAR is available to the Orchestration Cluster at runtime. :::note The pod should only become `Ready` when the application can connect to the database. Success of Liquibase and a running exporter are best confirmed via logs and direct database inspection. ::: ## How to verify connectivity Run a database client from within the cluster (either in an existing pod or in a temporary debug pod). ### PostgreSQL example (psql) ```bash # Option A: run an ephemeral pod with psql (recommended) kubectl run -i --rm --tty pg-client \ --image=postgres:15 \ --restart=Never \ --namespace camunda \ --command -- bash # Inside the pod: psql "host=POSTGRES_HOST port=5432 user=camunda dbname=camunda password=REDACTED" -c '\dt' ``` If you prefer `kubectl exec`, ensure the target container image includes `psql`. ### MySQL/MariaDB example (mysql) ```bash kubectl run -i --rm --tty mysql-client \ --image=mysql:8 \ --restart=Never \ --namespace camunda \ --command -- bash # Inside the pod: mysql -h MYSQL_HOST -P 3306 -u camunda -pREDACTED camunda -e 'SHOW TABLES;' ``` ### Oracle example (sqlplus) Use an image that includes Oracle client tools (for example sqlplus) and list tables: ```sql SELECT table_name FROM user_tables; ``` What to expect: - Success: The client connects and can list tables (or list schemas). - Failure: DNS/connection/authentication errors (the application typically fails fast and does not become `Ready`). ## What successful schema creation looks like ### Liquibase log indicator Check Orchestration Cluster logs for Liquibase initialization: ```bash kubectl -n camunda logs statefulset/camunda-zeebe | grep -E "Liquibase|MyBatisConfiguration" ``` A successful run includes a line similar to: ```pgsql [TIMESTAMP] [main] INFO io.camunda.application.commons.rdbms.MyBatisConfiguration - Initializing Liquibase for RDBMS with global table trimmedPrefix ''. ``` When Liquibase runs without errors, schema creation is considered successful. If `autoDDL` is disabled on an empty database, the exporter will fail because required tables do not exist. ### Confirm tables exist (SQL) PostgreSQL: ```sql \dt ``` MySQL/MariaDB: ```sql SHOW TABLES; ``` Oracle: ```sql SELECT table_name FROM user_tables; ``` If tables exist (for example `EXPORTER_POSITION`, `AUTHORIZATIONS`, `BATCH_OPERATION`), schema initialization succeeded. ## Verify the RDBMS exporter is running and flushing ### Exporter log indicators Search Orchestration Cluster logs for exporter startup: ```bash kubectl -n camunda logs statefulset/camunda-zeebe | grep -E "RdbmsExporter|RDBMS Exporter" ``` Successful startup includes lines similar to: ```pgsql [TIMESTAMP] INFO io.camunda.exporter.rdbms.RdbmsExporter - [RDBMS Exporter] RdbmsExporter created with Configuration: flushInterval=PT0.5S, queueSize=1000 [TIMESTAMP] INFO io.camunda.exporter.rdbms.RdbmsExporter - [RDBMS Exporter] Exporter opened with last exported position 126 ``` ### Confirm exporter progress in the database Query the exporter position table and confirm values update over time. PostgreSQL: ```sql SELECT partition_id, exporter, last_exported_position FROM exporter_position ORDER BY partition_id; ``` MySQL/MariaDB: ```sql SELECT PARTITION_ID, EXPORTER, LAST_EXPORTED_POSITION FROM EXPORTER_POSITION ORDER BY PARTITION_ID; ``` Oracle: ```sql SELECT partition_id, exporter, last_exported_position FROM exporter_position ORDER BY partition_id; ``` Exporter progress is the most reliable “is it working” signal. If `last_exported_position` never advances after you generate workload, inspect exporter logs and database permissions. ## Logs to inspect ### Success patterns - Liquibase initialization line in Orchestration Cluster logs (see above). - RDBMS exporter created/opened lines in Orchestration Cluster logs (see above). ### Failure patterns - Missing driver - Example: `Failed to load driver class oracle.jdbc.OracleDriver` - Result: Camunda fails to start. - Invalid JDBC URL, host, or DNS resolution - Example messages: - `Factory method 'databaseProperties' threw exception... Error occurred when getting DB product name.` - Root cause stack traces including `UnknownHostException` or socket connection failures. - Result: Camunda fails to start. - Invalid credentials - Example: `SQLInvalidAuthorizationSpecException: Access denied for user ... (using password: YES)` - Result: Camunda fails to start. - `autoDDL=false` on an empty database - Example: - `SQLSyntaxErrorException: Table '...EXPORTER_POSITION' doesn't exist` - Result: Exporter fails because required tables are missing. This is expected behavior unless schema is pre-created. Fetch logs: ```bash kubectl -n camunda logs statefulset/camunda-zeebe ``` If logs contain connection, driver, or authentication stack traces, the application typically fails fast and does not reach full readiness. ## Health and readiness verification Pod readiness reflects database connectivity through a database health indicator. However, readiness alone does not confirm schema creation or exporter progress. Use the following checks together: Kubernetes readiness: ```bash kubectl get pods -n camunda ``` Application health endpoint (if exposed): ```bash kubectl -n camunda port-forward svc/orchestration 8080:8080 curl -sS http://localhost:8080/actuator/health ``` Look for an overall `UP` status and a healthy database component. Always confirm with logs and database queries. ## Common error patterns and troubleshooting - DNS or network issues - Symptoms: `UnknownHostException`, socket connection errors. - Fix: Verify the JDBC host, service name, NetworkPolicies, firewall/security group rules, and routing from the cluster. - Missing JDBC driver - Symptoms: `Failed to load driver class ...`. - Fix: Provide the driver using `extraVolumes`/`extraVolumeMounts`, an init container, or a custom image. - Invalid credentials - Symptoms: `Access denied...`, `SQLInvalidAuthorizationSpecException`. - Fix: Confirm username, password, and required privileges. Test connectivity using a database client from inside the cluster. - `autoDDL=false` on an empty database - Symptoms: missing table errors such as `EXPORTER_POSITION` not found. - Fix: Enable auto-DDL for initial schema creation or provision the schema manually before startup. - Exporter position not advancing - Symptoms: `last_exported_position` remains unchanged after generating workload. - Fix: Inspect exporter logs for errors, confirm database write permissions, and review exporter settings (`flushInterval`, `queueSize`). Enable `DEBUG` logging if needed. ## Vendor-specific query examples ### PostgreSQL ```sql \dt SELECT partition_id, exporter, last_exported_position FROM exporter_position; ``` ### MySQL/MariaDB ```sql SHOW TABLES; SELECT PARTITION_ID, EXPORTER, LAST_EXPORTED_POSITION FROM EXPORTER_POSITION; ``` ### Oracle ```sql SELECT table_name FROM user_tables; SELECT partition_id, exporter, last_exported_position FROM exporter_position; ``` ## What success looks like - Orchestration Cluster pods reach `Ready` state. - Logs show Liquibase initialization without errors. - Logs show the RDBMS exporter created and opened with a last exported position. - The database contains expected Camunda tables (for example `EXPORTER_POSITION`, `AUTHORIZATIONS`, `BATCH_OPERATION`). - `last_exported_position` advances after generating workload. ## Notes and caveats - **Timing:** Schema creation and exporter startup occur during application startup. Exported data visibility depends on the exporter `flushInterval` and workload. - If observed behavior or log lines differ from this documentation, open an issue and include: - Relevant log excerpts - Helm values used for RDBMS configuration - Database type and version --- ## Enable additional Camunda components The default Helm deployment includes the [Orchestration Cluster](/reference/glossary.md#orchestration-cluster) and Connectors. This page explains how to enable other Camunda components. ## Default vs. additional components ### Enabled by default - Orchestration Cluster (Zeebe, Zeebe Gateway, Operate, Tasklist, Orchestration Cluster Admin) - Connectors ### Additional components (must be explicitly enabled) - Camunda Hub - Management Identity - Optimize ## Management Identity Identity management has two distinct scopes: - **Orchestration Cluster Admin** - Manages authentication and authorization for core orchestration components (Zeebe, Operate, Tasklist) and their APIs. This is built into the Orchestration Cluster and does not require Management Identity. - **Management Identity** - Controls access to Camunda Hub and Optimize. This is a separate component that must be explicitly enabled. Management Identity must be enabled if you want to use any of the following components: - Camunda Hub - Optimize Check the [authentication and authorization](./authentication-and-authorization/index.md) guide for detailed steps on enabling and configuring Management Identity. :::info If you enable Camunda Hub or Optimize without enabling Management Identity, these components will not function properly, as they require authentication. The Orchestration Cluster (Zeebe, Operate, Tasklist, and Orchestration Cluster Admin) does not depend on Management Identity. ::: ## Camunda Hub Enable Camunda Hub with the following configuration options. If you're upgrading from Camunda 8.9, see the [Camunda Hub consolidation migration steps](/self-managed/upgrade/helm/890-to-8100.md#consolidate-console-and-web-modeler-into-camunda-hub). - Set `camundaHub.enabled: true`. - Enable Management Identity for authentication. See [authentication and authorization](./authentication-and-authorization/index.md). - Configure your SMTP server in `camundaHub.restapi.extraConfiguration`. Camunda Hub requires an SMTP server to send notification emails. - Configure an external PostgreSQL connection under `camundaHub.restapi.externalDatabase`. Provision PostgreSQL externally, such as with a managed service or the [CloudNativePG operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md). We recommend specifying values in a YAML file and passing it to the `helm install` command. Minimal configuration file: ```yaml camundaHub: enabled: true restapi: mail: secret: existingSecret: "camunda-credentials-webmodeler" existingSecretKey: "webmodeler-smtp-user-password" externalDatabase: url: jdbc:postgresql://postgres.example.com:5432/modeler-db username: modeler-user secret: existingSecret: "camunda-credentials-webmodeler" existingSecretKey: "webmodeler-postgresql-user-password" extraConfiguration: - file: mail.yaml content: | spring: mail: host: smtp.example.com port: 587 username: user camunda: hub: mail: from-address: no-reply@example.com ``` For more details, see the [Camunda Hub Helm values](https://artifacthub.io/packages/helm/camunda/camunda-platform#camundahub-parameters). :::note When using `kubectl port-forward` to log in to Camunda Hub with [Keycloak deployed via the Keycloak Operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md), you must also port-forward the Keycloak service so the OpenID Connect (OIDC) redirect works: ```bash kubectl port-forward svc/keycloak-service 18080:18080 ``` Alternatively, configure Identity with Ingress. See the [Ingress setup guide](/self-managed/deployment/helm/configure/ingress/ingress-setup.md). ::: ## Optimize Optimize is disabled by default in the Camunda 8 Helm chart. To enable it: - Set `optimize.enabled: true` in a values file. - **Enable Management Identity** (required for authentication) - see [authentication and authorization](./authentication-and-authorization/index.md). ```yaml optimize: enabled: true ``` For a full list of options, see the [Optimize Helm values](https://artifacthub.io/packages/helm/camunda/camunda-platform#optimize-parameters). :::note Disabling Optimize removes the legacy Elasticsearch/OpenSearch exporter from the broker's static configuration. However, it does not remove the exporter from the dynamic configuration, which prevents log compaction and increases disk usage. See [Disable an exporter](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#disable-an-exporter) for the additional step required to fully disable it. ::: --- ## Helm chart user guides Camunda 8 Self-Managed is highly customizable and can be deployed in different setups. This section highlights various use cases and scenarios of configuring Camunda 8 beyond the default values. Each guide is considered complete and a standalone use case. However, view the [deploying Camunda 8 using Helm charts](/self-managed/deployment/helm/install/quick-install.md) page, as this is the base for all guides. --- ## Configure health probes Each Camunda component in the Helm chart has `startupProbe`, `readinessProbe`, and `livenessProbe` values. With these values, you can turn a probe on or off and change its endpoint or timing. By default, the chart enables only the readiness probe of each component. The startup and liveness probes are off. ## Default probe endpoints For each probe, Kubernetes sends an HTTP GET request to a container port and path. The following table lists the values prefix, the default port, and the default `probePath` of each probe. | Component | Values prefix | Port | Startup `probePath` | Readiness `probePath` | Liveness `probePath` | | ---------------------- | ----------------------- | ------ | ---------------------------- | ---------------------------- | --------------------------- | | Orchestration Cluster | `orchestration` | `9600` | `/actuator/health/startup` | `/actuator/health/readiness` | `/actuator/health/liveness` | | Management Identity | `identity` | `8082` | `/actuator/health` | `/actuator/health` | `/actuator/health` | | Connectors | `connectors` | `8080` | `/actuator/health/readiness` | `/actuator/health/readiness` | `/actuator/health/liveness` | | Optimize | `optimize` | `8090` | `/api/readyz` | `/api/readyz` | `/api/readyz` | | Camunda Hub REST API | `camundaHub.restapi` | `8091` | `/health/liveness` | `/health/readiness` | `/health/liveness` | | Camunda Hub WebSockets | `camundaHub.websockets` | `8060` | `/up` | `/up` | `/up` | The following rules apply: - The `scheme` value defaults to `HTTP`. For Connectors and Optimize, the default is empty. When you enable TLS for the component, the chart uses `HTTPS` for an empty `scheme`. Otherwise, it uses `HTTP`. See [Connectors TLS](./orchestration-tls-modes.md#connectors-tls) and [Optimize TLS](./orchestration-tls-modes.md#optimize-tls). - The Orchestration Cluster, Connectors, Optimize, and Camunda Hub REST API probes use the `contextPath` value of the component as a path prefix. For Camunda Hub, that value is `camundaHub.contextPath`. Management Identity and Camunda Hub WebSockets probes use `probePath` only. - Camunda Hub inherits its probe defaults from the `webModeler.restapi` and `webModeler.websockets` values. Values you set under `camundaHub.restapi` and `camundaHub.websockets` take precedence. The chart merges both sets of values key by key. You set only the keys you want to change. ## Probe values Every probe accepts the same values. The key of a value has three parts: the values prefix from [Default probe endpoints](#default-probe-endpoints), the probe name, and the value name. For example, `orchestration.readinessProbe.periodSeconds`. | Value | Default | Description | | --------------------- | --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `enabled` | `false` for `startupProbe` and `livenessProbe`, `true` for `readinessProbe` | Adds the probe to the container when `true`. | | `scheme` | `HTTP`. Empty for Connectors and Optimize. | Protocol of the request, `HTTP` or `HTTPS`. | | `probePath` | See [Default probe endpoints](#default-probe-endpoints). | Path of the request. | | `initialDelaySeconds` | `30`. `10` for Camunda Hub WebSockets. | Seconds to wait after the container starts before the first probe. | | `periodSeconds` | `30` | Seconds between probes. | | `successThreshold` | `1` | Consecutive successes that mark the probe as successful again after a failure. | | `failureThreshold` | `5` | Consecutive failures before Kubernetes marks the pod as not ready (readiness probe) or restarts the container (startup and liveness probes). | | `timeoutSeconds` | `1` | Seconds after which a probe request counts as failed. | The timing values match the fields of the same name in the [Kubernetes probe configuration](https://kubernetes.io/docs/concepts/workloads/pods/probes/#configure-probes). ## Tune a probe Set only the values you want to change. The chart keeps the defaults for all other values. The following example enables the startup probe of the Orchestration Cluster. Kubernetes restarts the container if the probe fails 30 times in a row, 10 seconds apart. Until the startup probe succeeds, Kubernetes doesn't run the readiness and liveness probes. ```yaml orchestration: startupProbe: enabled: true periodSeconds: 10 failureThreshold: 30 ``` To change a Camunda Hub probe, set the value under `camundaHub`. The following example raises the timeout of the Camunda Hub REST API readiness probe to five seconds: ```yaml camundaHub: restapi: readinessProbe: timeoutSeconds: 5 ``` --- ## Helm chart configuration In this section, find details on configuration associated with Kubernetes with Helm. --- ## Helm chart without Ingress setup By default, the [Camunda Helm chart](/self-managed/deployment/helm/install/quick-install.md) does not expose the Camunda services externally. So to interact with the Camunda services inside a Kubernetes cluster without Ingress setup, you can use `kubectl port-forward` to route traffic from your local machine to the cluster. This is useful for quick tests or for development purposes. :::note You need to keep `port-forward` running all the time to communicate with the remote cluster. ::: ## Accessing workflow engine To interact with Camunda workflow engine via [Zeebe Gateway](/self-managed/components/orchestration-cluster/zeebe/configuration/gateway.md) using a local client/worker from outside the Kubernetes cluster, run `kubectl port-forward` to the Zeebe cluster as follows: ``` # gRPC kubectl port-forward svc/camunda-zeebe-gateway 26500:26500 # REST API kubectl port-forward svc/camunda-zeebe-gateway 8080:8080 ``` Now, you can connect and execute operations against your new Zeebe cluster. Port `26500` provides gRPC access, and port `8080` provides [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) access. :::note Accessing the Zeebe cluster directly using `kubectl port-forward` is recommended for development purposes. ::: ## Accessing web applications To interact with Camunda web applications like Operate, Tasklist, and Optimize, also `kubectl port-forward` will be used. :::note To use the web applications without Camunda Identity, you can set `global.identity.auth.enabled: false` in the values file to disable the authentication mechanism. Do _not_ disable it if you want to use Web Modeler, as it requires Camunda Identity and Keycloak. ::: First, port-forward for each application service: ```shell kubectl port-forward svc/camunda-optimize 8083:80 kubectl port-forward svc/camunda-connectors 8086:8080 ``` :::note The Zeebe Gateway port-forward on port `8080` (shown in the [workflow engine section](#accessing-workflow-engine) above) also serves the Orchestration web interface. ::: To be able to use Web Modeler, create additional port-forward commands for Web Modeler itself, and if you use [Keycloak deployed via the Keycloak Operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md), also port-forward the Keycloak service: ``` kubectl port-forward svc/camunda-web-modeler-restapi 8070:80 kubectl port-forward svc/camunda-web-modeler-websockets 8085:80 # Only if using Keycloak Operator kubectl port-forward svc/keycloak-service 18080:18080 ``` To use Console, create additional port-forward commands for Console. If you use [Keycloak deployed via the Keycloak Operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md), also port-forward the Keycloak service: ``` kubectl port-forward svc/camunda-console 8087:80 # Only if using Keycloak Operator kubectl port-forward svc/keycloak-service 18080:18080 ``` Finally, you can access each app pointing your browser at: - Orchestration: [http://localhost:8080](http://localhost:8080) - Optimize: [http://localhost:8083](http://localhost:8083) - Web Modeler: [http://localhost:8070](http://localhost:8070) - Console: [http://localhost:8087](http://localhost:8087) Log in to Orchestration using the default first user credentials `demo`/`demo`. These defaults come from the Helm chart value `orchestration.security.initialization.users`, which seeds the `demo` user with password `demo` for the Orchestration Cluster. To log in to Optimize, Web Modeler, and Console, use the first Management Identity user instead. Its username is `demo` by default (`identity.firstUser.username`), and its password is the one you set in `identity.firstUser.secret`. If you have overridden these values or use a custom identity provider, use the credentials you configured instead.
Operate and Tasklist Login
Operate and Tasklist Dashboard
If you deploy process definitions, they will appear in the dashboard. Then, you can drill down to see your active instances. You can deploy and create new instances using the Zeebe clients. You can also trigger **Connectors** inbound webhook, given you deployed one. You can do so with the following example: `curl -X POST -H "Content-Type: application/json" -d '{"myId": 123456, "myMessage": "Hello, world!"}' http://localhost:8086/inbound/`. --- ## Configure the Helm chart with Gateway API Use this guide to configure the Camunda 8 Helm chart with the Kubernetes Gateway API instead of a traditional Ingress controller. The Gateway API provides a modern way to manage inbound traffic in Kubernetes clusters. It improves on the Ingress API in the following ways: - Separates cluster operators, who manage Gateway resources, from application teams, who manage HTTPRoute resources. - Enables fine-grained traffic configuration without relying on controller-specific annotations. :::note The Ingress-NGINX controller is planned to reach end of life in March 2026 (see [the Kubernetes announcement on Ingress-NGINX retirement](https://www.kubernetes.dev/blog/2025/11/12/ingress-nginx-retirement/)). Plan a migration to the Gateway API where it fits your use case. If you decide not to adopt the Gateway API, you can migrate to a different Ingress controller and continue using the Ingress API. This remains a supported approach. ::: ## Prerequisites Ensure the following are installed in your cluster before enabling the Gateway API in the Helm chart: - Gateway API CRDs - A Gateway API controller See the [list of Gateway API implementations](https://gateway-api.sigs.k8s.io/implementations/) for available controllers. In testing, we use the [NGINX Gateway Fabric](https://github.com/nginx/nginx-gateway-fabric). ## Deployment scenarios Choose the scenario that matches your cluster setup. ### Scenario A: Gateway and Camunda in the same namespace This is the simplest setup. The chart creates the Gateway resource alongside all Camunda components in the same Kubernetes namespace. No cross-namespace configuration is needed. Use this scenario when you're deploying a single Camunda installation and have full control of the namespace. ```yaml global: host: "camunda.example.com" gateway: enabled: true createGatewayResource: true className: nginx ``` The chart creates: - A `Gateway` resource in the release namespace, referencing the `nginx` GatewayClass - An `HTTPRoute` and optionally a `GRPCRoute` per enabled component, all referencing the Gateway by name in the same namespace - A `ReferenceGrant` in the release namespace (permits any routes in the same namespace to reach Camunda services) ### Scenario B: Shared Gateway in a different namespace Use this scenario when a shared Gateway already exists in a separate namespace (for example, `shared-infra`) and multiple teams or Camunda releases attach their routes to it. This is common in platform-as-a-service setups where a central networking team manages all Gateways. ```yaml global: host: "camunda.example.com" gateway: enabled: true createGatewayResource: false name: shared-gateway namespace: shared-infra className: nginx ``` `global.gateway.name` sets the Gateway name in the `parentRefs` of every Route. `global.gateway.namespace` adds the cross-namespace reference so Kubernetes can locate the Gateway in `shared-infra`. Both are required when the shared Gateway has a different name or namespace than the Camunda release. The chart creates: - `HTTPRoute` and `GRPCRoute` resources in the release namespace, with `parentRefs[].namespace: shared-infra` - A `ReferenceGrant` in the release namespace The chart does **not** create or modify the Gateway in `shared-infra`. Before deploying, ask your cluster administrator to configure the Gateway to accept routes from your release namespace by setting `spec.listeners[].allowedRoutes.namespaces` on the Gateway in `shared-infra`. The simplest option is `from: All`, which accepts routes from any namespace: ```yaml spec: listeners: - name: http port: 80 protocol: HTTP allowedRoutes: namespaces: from: All ``` For tighter control, use `from: Selector` with a namespace label. Kubernetes automatically applies the `kubernetes.io/metadata.name` label to all namespaces (Kubernetes 1.21 and later): ```yaml allowedRoutes: namespaces: from: Selector selector: matchLabels: kubernetes.io/metadata.name: ``` ### Scenario C: Use a pre-existing Gateway in the same namespace Use this scenario when a Gateway already exists in your release namespace (for example, managed by a separate Helm release or your platform team) and you don't want the chart to create or overwrite it. ```yaml global: host: "camunda.example.com" gateway: enabled: true createGatewayResource: false className: nginx ``` The chart creates: - `HTTPRoute` and `GRPCRoute` resources referencing the Gateway by name within the same namespace - A `ReferenceGrant` in the release namespace The Gateway resource name the chart looks for matches the Helm release name (for example, `camunda-platform` for a release named `camunda-platform`). ### Scenario D: Manage all resources yourself Use this scenario when you want to manage all Gateway API resources outside the chart. No Gateway, HTTPRoute, GRPCRoute, or ReferenceGrant objects are created. ```yaml global: gateway: enabled: true external: true ``` You are responsible for creating all resources that expose Camunda's services. ## Configuration reference | Parameter | Type | Default | Description | | -------------------------------------- | ------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `global.host` | string | `""` | The external hostname where Camunda is reachable. Used as the `hostname` on the Gateway listener and as the `hostnames` field in each Route. | | `global.gateway.enabled` | boolean | `false` | Enable the Kubernetes Gateway API integration. When `false`, no Gateway API resources are created regardless of other settings. | | `global.gateway.createGatewayResource` | boolean | `false` | Create the `Gateway` CustomResource as part of this Helm release. Set to `true` when you want the chart to own the Gateway (Scenario A). Set to `false` if a Gateway already exists in the target namespace or in a separate namespace. | | `global.gateway.external` | boolean | `false` | Skip creating all Gateway API resources (Gateway, Routes, and ReferenceGrant). Use this when you manage all networking resources yourself. | | `global.gateway.className` | string | `""` | The name of the `GatewayClass` resource that tells the cluster which Gateway controller manages this Gateway. | | `global.gateway.labels` | map | `{}` | Labels added to the Gateway and all Route resources. | | `global.gateway.annotations` | map | `{}` | Annotations added to the Gateway and all Route resources. | | `global.gateway.tls.enabled` | boolean | `false` | Enable TLS on the Gateway listener. Requires `global.gateway.tls.secretName` to be set. | | `global.gateway.port` | integer | `80` | The port of the plaintext (HTTP) Gateway listener. Change this when your Gateway controller exposes HTTP on a non-standard port. | | `global.gateway.tls.port` | integer | `443` | The port of the HTTPS Gateway listener. Change this when your Gateway controller exposes HTTPS on a non-standard port (for example, Traefik's `8443` `websecure` entrypoint). | | `global.gateway.httpSectionName` | string | `""` | Override the `parentRefs.sectionName` on the HTTPRoutes. Must match a listener name on the target Gateway. Only set this for an externally-managed Gateway. See [externally-managed gateway with custom listener names](#externally-managed-gateway-with-custom-listener-names). | | `global.gateway.grpcSectionName` | string | `""` | Override the `parentRefs.sectionName` on the GRPCRoute. Must match a listener name on the target Gateway. Only set this for an externally-managed Gateway. See [externally-managed gateway with custom listener names](#externally-managed-gateway-with-custom-listener-names). | | `global.gateway.tls.secretName` | string | `""` | Name of the Kubernetes `Secret` containing the TLS certificate and private key. | | `global.gateway.name` | string | `""` | The name of the Gateway resource that Routes attach to. Defaults to the Helm release fullname when unset. Set this when the shared Gateway has a different name than your release (Scenario B). | | `global.gateway.namespace` | string | `""` | The namespace where the Gateway resource lives. Set this only when using a shared Gateway in a different namespace than your Camunda components (see [Scenario B](#scenario-b-shared-gateway-in-a-different-namespace)). When unset, Kubernetes defaults to the Route's own namespace. | | `global.gateway.controllerNamespace` | string | `""` | The namespace from which an external controller or operator creates Routes that reference Camunda services. The chart uses this value in the `ReferenceGrant`'s `from.namespace` field. Leave unset if your Routes are created by this chart. | ## Additional configuration ### TLS To enable HTTPS, add a TLS certificate to a Kubernetes Secret and reference it: ```yaml global: host: "camunda.example.com" gateway: enabled: true createGatewayResource: true className: nginx tls: enabled: true secretName: camunda-tls ``` When TLS is enabled, the chart configures the Gateway listener on port 443 with `protocol: HTTPS` and sets `sectionName: https` on all HTTPRoutes, and sets `sectionName: grpcs` on the GRPCRoute (used by Zeebe). ### Custom listener ports By default the Gateway listens on port `80` for HTTP and port `443` for HTTPS. Set `global.gateway.port` and `global.gateway.tls.port` when your Gateway controller exposes these on non-standard ports, for example, Traefik's `websecure` entrypoint on `8443`: ```yaml global: host: "camunda.example.com" gateway: enabled: true createGatewayResource: true className: nginx port: 8080 tls: enabled: true secretName: camunda-platform port: 8443 ``` ### Externally-managed Gateway with custom listener names When attaching Routes to a Gateway you do not manage (for example, one owned by a Cluster Operator), its listener names may not match the chart defaults (`http`, `https`, `grpc`, `grpcs`). Use `global.gateway.httpSectionName` and `global.gateway.grpcSectionName` to point the Routes' `parentRefs.sectionName` at the listener names the target Gateway actually defines: ```yaml global: host: "camunda.example.com" gateway: enabled: true createGatewayResource: false name: shared-gateway namespace: shared-infra httpSectionName: web grpcSectionName: grpc-web ``` :::warning Only set the `sectionName` overrides for an externally-managed Gateway, and make sure each value matches a listener name on that Gateway. When the chart manages the Gateway (`createGatewayResource: true`), the listener names are always `http`, `https`, `grpc`, and `grpcs`. Setting an override in that case detaches the Routes from the Gateway with no error. ::: ### NGINX Gateway Fabric: ProxySettingsPolicy If you are using the NGINX Gateway Fabric, the default proxy buffer size is likely too small for Camunda. You may see errors such as: > 502: upstream sent too big header while reading response header from upstream Install the `ProxySettingsPolicy` CRD ([CRD location](https://github.com/nginx/nginx-gateway-fabric/tree/main/config/crd/bases)) and apply the following resource in your Camunda namespace: ```yaml apiVersion: gateway.nginx.org/v1alpha1 kind: ProxySettingsPolicy metadata: name: camunda-platform namespace: camunda spec: buffering: bufferSize: 128k buffers: number: 8 size: 128k busyBuffersSize: 256k targetRefs: - group: gateway.networking.k8s.io kind: Gateway name: camunda-platform ``` See the [ProxySettingsPolicy documentation](https://docs.nginx.com/nginx-gateway-fabric/traffic-management/proxy-settings/) for details. --- ## Helm chart Ingress configuration In this section, find details on Ingress configuration associated with Kubernetes with Helm. --- ## Configure the Helm chart with Ingress :::caution Starting with Camunda 8.8, the separated Ingress configuration is no longer supported. Instead, follow the combined Ingress setup described in this guide. If you want to replicate the behavior of the previous separated Ingress approach, check separated Ingress migration. ::: Camunda 8 Self-Managed has multiple web applications and gRPC services. You can expose them externally with a combined Ingress setup. ## Prerequisites - An Ingress controller deployed in advance. The examples below use the [Ingress-nginx controller](https://github.com/kubernetes/ingress-nginx), but you can use any Ingress controller by setting `global.ingress.className` (and `orchestration.ingress.grpc.className` for the Zeebe gRPC Ingress). - The annotations your controller needs. Starting with Camunda 8.10 (chart 15.x), the chart's default Ingress-nginx annotation set comes from a compatibility shim that you can turn off with `global.compatibility.nginx.renderAnnotations: false`; see [Ingress-nginx annotation defaults deprecated in the Helm chart](/reference/announcements-release-notes/8100/8100-announcements.md#ingress-annotation-defaults-deprecated). :::note [Ingress-nginx reached end of life in March 2026](https://kubernetes.io/blog/2025/11/11/ingress-nginx-retirement/). The Camunda 8 reference architectures deploy [Contour](https://projectcontour.io/) instead. The examples on this page still use Ingress-nginx annotations. With another controller, translate them to its equivalents. See [configure the gRPC upstream](#configure-the-grpc-upstream) for the gRPC annotation each controller expects. ::: - TLS configuration is not included in the examples because it varies between different workflows. Configure TLS in one of these ways: - Use `ingress.tls` options directly. - Use an external tool such as [Cert-Manager](https://github.com/cert-manager/cert-manager) with `ingress.annotations`. For more information, see the available [configuration options](https://artifacthub.io/packages/helm/camunda/camunda-platform#configuration). :::note Camunda 8 Helm chart only deploys Ingress resources. It does not manage or deploy Ingress controllers. You must have an Ingress controller running in your cluster. ::: ## Configuration In this configuration, two Ingress objects are created: - **Web applications**: One Ingress object for all Camunda 8 web applications using one domain. Each application has a sub-path. For example, `camunda.example.com/operate`, `camunda.example.com/optimize`. - **Zeebe Gateway**: Another Ingress object using the gRPC protocol for Zeebe Gateway. For example, `zeebe.camunda.example.com`. By default, all web applications use `/` as a base, so we just need to set the context path, Ingress configuration, and authentication redirect URLs. :::note For Operate, Tasklist, Optimize, Modeler, Connectors, and Console, the Ingress path (`global.identity.auth..redirectUrl`) must match the `contextPath` for that component. ::: ### Example configuration ```yaml # Chart values for the Camunda 8 Helm chart in combined Ingress setup. # This file deliberately contains only the values that differ from the defaults. # For changes and documentation, use your favorite diff tool to compare it with: # https://artifacthub.io/packages/helm/camunda/camunda-platform # IMPORTANT: Make sure to change "camunda.example.com" to your domain. global: host: "camunda.example.com" ingress: enabled: true className: nginx identity: auth: publicIssuerUrl: "https://camunda.example.com/auth/realms/camunda-platform" optimize: redirectUrl: "https://camunda.example.com/optimize" webModeler: redirectUrl: "https://camunda.example.com/modeler" console: redirectUrl: "https://camunda.example.com/console" identity: contextPath: "/identity" fullURL: "https://camunda.example.com/identity" optimize: contextPath: "/optimize" orchestration: contextPath: "/orchestration" ingress: grpc: enabled: true className: nginx host: "zeebe.camunda.example.com" webModeler: # The context path is used for the web application that will be accessed by users in the browser. # In addition, a WebSocket endpoint will be exposed on "[contextPath]-ws", e.g. "/modeler-ws". contextPath: "/modeler" console: contextPath: "/console" connectors: contextPath: "/connectors" ``` Incorporate these custom values into the values file you use to deploy Camunda (see [Install Camunda with Helm](/self-managed/deployment/helm/install/quick-install.md)): ```shell helm install camunda camunda/camunda-platform --version $HELM_CHART_VERSION -f values-combined-ingress.yaml ``` After deployment, access the Camunda 8 components at: - **Management Applications:** `https://camunda.example.com/[identity|modeler|console]` - **Core Orchestration Applications and REST API:** `https://camunda.example.com/orchestration/[identity|operate|optimize|tasklist|v2]` - **Web Modeler WebSocket:** Web Modeler exposes a WebSocket endpoint on `https://camunda.example.com/modeler-ws`. This is only used internally by the application and not for direct user access. - **Keycloak authentication:** `https://camunda.example.com/auth` - **Zeebe gRPC Gateway:** `grpc://zeebe.camunda.example.com` :::note This configuration shows only the Ingress-related values for `webModeler`and `Console`. For full setup, see [Enable additional components](/self-managed/deployment/helm/configure/enable-additional-components.md). ::: ### Configure custom public ports Set `global.ingress.publicPorts` when clients reach your Ingress controller on ports other than `80` and `443`. For example, a local cluster that can't bind the standard ports might map the Ingress controller to host ports `8080` and `8443`. | Parameter | Type | Default | Description | | ---------------------------------- | ------- | ------- | -------------------------------------------------------- | | `global.ingress.publicPorts.http` | integer | `80` | The public port for Ingress endpoints with TLS disabled. | | `global.ingress.publicPorts.https` | integer | `443` | The public port for Ingress endpoints with TLS enabled. | Both values accept integers from 1 to 65535. The chart omits port `80` from HTTP URLs and port `443` from HTTPS URLs. The TLS setting for each Ingress determines which public port the chart uses. For web applications, the chart uses `global.ingress.tls.enabled`. For the Zeebe gRPC Ingress, it uses `orchestration.ingress.grpc.tls.enabled`, even when `global.ingress.enabled` is `false`. Setting a public port doesn't enable TLS. The chart adds the public port to the URLs it derives from `global.host` and the Zeebe gRPC Ingress host: - Links in the installation notes and release information. - The Identity URL and its login callback, if `identity.fullURL` is empty. - The WebSocket port browsers use to connect to Web Modeler, if Web Modeler has a context path. The chart doesn't rewrite URLs you configure explicitly. Include the port in `identity.fullURL`, `global.identity.auth.publicIssuerUrl`, and each `global.identity.auth..redirectUrl`. Public ports change only the generated URLs. They don't configure Ingress controller listeners, Kubernetes Services, or host port mappings. Configure your Ingress controller and cluster to expose the required ports. The Gateway API integration doesn't use these values. To change the Gateway listener ports, see [custom listener ports](./gateway-api-setup.md#custom-listener-ports). The following example configures the generated web application URLs to use HTTPS on public port `8443`: ```yaml global: host: "camunda.example.com" ingress: enabled: true className: nginx tls: enabled: true secretName: camunda-platform publicPorts: https: 8443 identity: auth: publicIssuerUrl: "https://camunda.example.com:8443/auth/realms/camunda-platform" optimize: redirectUrl: "https://camunda.example.com:8443/optimize" webModeler: redirectUrl: "https://camunda.example.com:8443/modeler" console: redirectUrl: "https://camunda.example.com:8443/console" identity: contextPath: "/identity" fullURL: "https://camunda.example.com:8443/identity" ``` ## Route traffic with your own resources Set `global.ingress.external: true` when a service mesh or routing resources you manage send traffic to Camunda instead of the chart's Ingress objects. ```yaml global: host: "camunda.example.com" ingress: enabled: true external: true orchestration: ingress: grpc: enabled: true external: true host: "zeebe.camunda.example.com" ``` With `global.ingress.external: true`, the chart doesn't render any web application Ingress object. The setting has no effect if `global.ingress.enabled` is `false`. The chart still derives external URLs from `global.host`, the component context paths, `global.ingress.protocol`, and `global.ingress.publicPorts`. The chart uses these URLs for: - Links in the installation notes and release information. - The Identity URL and its login callback, if `identity.fullURL` is empty. - The WebSocket host, port, and path browsers use to connect to Web Modeler, if Web Modeler has a context path. Configure your routing resources to serve each component on these URLs. If clients reach Camunda over HTTPS, set `global.ingress.protocol: https`. `global.ingress.external` doesn't affect the Zeebe gRPC Ingress. To skip the gRPC Ingress, set `orchestration.ingress.grpc.external: true`. The chart still derives the gRPC URL from `orchestration.ingress.grpc.host`. If a service mesh or your own routing resources send traffic to Camunda, use `global.ingress.external: true` instead of `global.ingress.enabled: false`: | Values | Chart renders Ingress objects | URL source | Use when | | ------------------------------------------------------------------ | ----------------------------- | -------------------- | ---------------------------------------------------------------------------------------- | | `global.ingress.enabled: true` | Yes | `global.host` | The chart's Ingress objects route traffic. | | `global.ingress.enabled: true` and `global.ingress.external: true` | No | `global.host` | A service mesh or your own routing resources route traffic. | | `global.ingress.enabled: false` | No | `localhost` defaults | You access components with [port forwarding](./accessing-components-without-ingress.md). | If you manage Gateway API resources yourself, use `global.gateway.external` instead. See [manage all resources yourself](./gateway-api-setup.md#scenario-d-manage-all-resources-yourself). ## Separated Ingress migration As the separated Ingress was removed in Camunda 8.8, there are two options for migration: 1. **Recommended:** Use the [combined Ingress configuration](#configuration). 2. **Alternative:** You can use global.extraManifests to define and deploy your own Ingress objects, providing functionality similar to the former separated Ingress setup. The example below demonstrates how to add an Ingress object for Optimize, replicating the separated Ingress behavior. You can apply the same approach to create additional Ingress objects for other components, as needed. ```yaml # values-separated-ingress.yaml global: extraManifests: - | --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: camunda-platform-optimize namespace: camunda-dev annotations: nginx.ingress.kubernetes.io/backend-protocol: HTTP nginx.ingress.kubernetes.io/proxy-body-size: 10m nginx.ingress.kubernetes.io/proxy-buffer-size: 128k nginx.ingress.kubernetes.io/proxy-buffering: "on" nginx.ingress.kubernetes.io/rewrite-target: / nginx.ingress.kubernetes.io/ssl-redirect: "false" spec: ingressClassName: nginx rules: - host: optimize.example.com http: paths: - backend: service: name: camunda-platform-optimize port: number: 80 path: / pathType: Prefix tls: - hosts: - optimize.example.com secretName: camunda-platform-optimize-tls - | [Add more Ingresses as needed] ``` Please note that, you need to create all resources referenced by the objects in `global.extraManifests`, such as the TLS secret `camunda-platform-optimize-tls`. ## Ingress controllers Ingress resources require the cluster to have a running [Ingress Controller](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/). There are many options for configuring your Ingress Controller. If you use a cloud provider such as AWS or GCP, follow their Ingress setup guides if an Ingress Controller is not already pre-installed. For AWS EKS Ingress configuration, see [Install Camunda 8 on an EKS cluster](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/eks-helm.md). ### Local setup example An Ingress controller is also required for local Camunda 8 installation. The following example shows an Ingress controller configuration using the [Ingress-nginx controller](https://kubernetes.github.io/ingress-nginx/deploy/#bare-metal-clusters/): ```yaml # ingress_nginx_values.yml controller: updateStrategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 service: type: NodePort publishService: enabled: false ``` Install the [Ingress-nginx controller](https://github.com/kubernetes/ingress-nginx) to your local cluster: ```shell helm install -f ingress_nginx_values.yml \ ingress-nginx ingress-nginx \ --repo https://kubernetes.github.io/ingress-nginx \ --version "4.9.0" \ --namespace ingress-nginx \ --create-namespace ``` If your local cluster exposes the Ingress controller on ports other than `80` and `443`, set the ports as described in [configure custom public ports](#configure-custom-public-ports). ### Configure the gRPC upstream Configure your Ingress controller to send HTTP/2 to the Orchestration Cluster, because the Zeebe Gateway serves gRPC. Each controller declares the gRPC upstream differently, and not on the same object: | Ingress controller | Annotation | Object | | ------------------ | ---------------------------------------------------- | ----------------------------------------- | | Contour | `projectcontour.io/upstream-protocol.h2c: "26500"` | Orchestration Cluster `Service` | | Ingress-nginx | `nginx.ingress.kubernetes.io/backend-protocol: GRPC` | Zeebe `Ingress` (added by the Helm chart) | When the upstream itself uses TLS, use `projectcontour.io/upstream-protocol.h2` with Contour, and `nginx.ingress.kubernetes.io/backend-protocol: GRPCS` with Ingress-nginx. For other controllers, check their documentation for the equivalent. With Contour, set the annotation on the Orchestration Cluster service: ```yaml orchestration: service: annotations: projectcontour.io/upstream-protocol.h2c: "26500" ``` ### Use an AWS Application Load Balancer An AWS Application Load Balancer (ALB) terminates TLS at the load balancer with a certificate from AWS Certificate Manager (ACM). For the limits, see the [ALB known limitations](/self-managed/reference-architecture/kubernetes.md#application-load-balancer-alb). 1. Deploy the [AWS Load Balancer Controller](https://kubernetes-sigs.github.io/aws-load-balancer-controller/). 1. Set up a [certificate in AWS Certificate Manager](https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-public.html). 1. Set the `alb` class on every Ingress object the chart renders. The web application Ingress objects read `global.ingress`, and the Zeebe gRPC Ingress reads `orchestration.ingress.grpc`. 1. Set `alb.ingress.kubernetes.io/backend-protocol-version: GRPC` only on the Zeebe gRPC Ingress, as in the [AWS gRPC example](https://github.com/kubernetes-sigs/aws-load-balancer-controller/blob/main/docs/examples/grpc_server.md). On a web application Ingress, this annotation makes the ALB target groups use gRPC and breaks the HTTP applications. 1. Add the values to your `values.yaml` file: ```yaml global: compatibility: nginx: # Stop the chart from adding its default Ingress-nginx annotations. renderAnnotations: false ingress: className: alb # TLS terminates at the ALB. An empty secretName lists the hosts without a Secret. tls: enabled: true secretName: "" annotations: alb.ingress.kubernetes.io/ssl-redirect: "443" alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]' alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/target-type: ip orchestration: ingress: grpc: className: alb tls: enabled: true secretName: "" annotations: alb.ingress.kubernetes.io/ssl-redirect: "443" alb.ingress.kubernetes.io/backend-protocol-version: GRPC alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]' alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/target-type: ip ``` The ALB terminates TLS with the ACM certificate, so you don't need a TLS Secret. With `tls.enabled: true` and an empty `secretName`, the chart lists each host under the Ingress [`tls` field](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) without a Secret. The AWS Load Balancer Controller uses these hosts to find the matching ACM certificate. The chart also uses `https` in the URLs it generates, for example in the release information. ### Use the GKE Ingress With the [GKE Ingress](https://cloud.google.com/kubernetes-engine/docs/concepts/ingress) (Ingress-gce), you may need `cloud.google.com/app-protocols` annotations on the Zeebe Gateway service. For details, see the GKE guide [using HTTP/2 for load balancing with Ingress](https://cloud.google.com/kubernetes-engine/docs/how-to/ingress-http2). ## Troubleshooting If Ingress is not working as expected, see [Camunda components troubleshooting](/self-managed/operational-guides/troubleshooting.md). --- ## Configure license key Use this page to configure an enterprise license key for Camunda 8 components. You can either enter the key directly in your `values.yaml` file or reference an existing Kubernetes secret. ## Configuration ### Helm values Camunda 8 components consume enterprise license information through the following Helm configuration: ```yaml global: ## License configuration. ## @extra global.license license: ## @extra global.license.secret configuration to provide the license secret. secret: ## @param global.license.secret.inlineSecret can be used to provide the license as a plain-text value for non-production usage. inlineSecret: "" ## @param global.license.secret.existingSecret can be used to reference an existing Kubernetes Secret containing the license. existingSecret: "" ## @param global.license.secret.existingSecretKey defines the key within the existing secret object. existingSecretKey: "" ``` ### Provide the key directly Enter your license key directly in `global.license.secret.inlineSecret`. ```yaml global: license: secret: inlineSecret: >- --------------- BEGIN CAMUNDA LICENSE KEY --------------- [...] --------------- END CAMUNDA LICENSE KEY --------------- ``` ### Provide the key with a secret You can also store the license key in a Kubernetes secret and reference it from `values.yaml`. For more details on working with secrets, see [Secret management](/self-managed/deployment/helm/configure/secret-management.md). 1. Create the secret: ```yaml apiVersion: v1 kind: Secret metadata: name: camunda-license stringData: key: >- --------------- BEGIN CAMUNDA LICENSE KEY --------------- [...] --------------- END CAMUNDA LICENSE KEY --------------- ``` 2. Reference the secret in `values.yaml`: ```yaml global: license: secret: existingSecret: "camunda-license" existingSecretKey: "key" ``` :::note Camunda 8 components without a valid license may display **Non-Production License** in the navigation bar and log warnings. These warnings do not impact startup or functionality, except that Web Modeler is limited to five users. ::: --- ## Configure zone-aware multi-region deployments The Camunda Helm chart deploys an Orchestration Cluster across named zones through `orchestration.partitioning`. Each zone runs its own release of the chart, and every release describes the same cluster-wide topology. The zone list is therefore identical everywhere, and only the local zone name changes. For what zones are and how the application places partition replicas across them, see [zone-aware clusters](/self-managed/components/orchestration-cluster/zeebe/configuration/zone-aware-clusters.md). ## Move from global.multiregion Chart v15 (Camunda 8.10) deprecates `global.multiregion`. Only the Orchestration Cluster ever read these keys, so they now live under `orchestration.partitioning`. The deprecated keys still work and still render in v15. Chart v17 (Camunda 8.12) removes them, so move them before you upgrade to v17. Two keys shipped under `global.multiregion`: `regions` and `regionId`. They configure the broker numbering for [dual-region](/self-managed/concepts/multi-region/dual-region.md) deployments. The new block describes zones rather than regions, so the keys change name as well as location: | Deprecated key | Replacement | | :---------------------------- | :----------------------------------------- | | `global.multiregion.regions` | `orchestration.partitioning.numberOfZones` | | `global.multiregion.regionId` | `orchestration.partitioning.zoneIndex` | The values do not change. Only the names and their location change: ```yaml # Before global: multiregion: regions: 2 regionId: 1 # After orchestration: partitioning: numberOfZones: 2 zoneIndex: 1 ``` Keeping the old names under the new block fails the render. The schema declares `orchestration.partitioning` with `additionalProperties: false`. It allows only `scheme`, `zone`, `zones`, `numberOfZones`, `zoneIndex`, and `keepUnzonedBrokers`. The chart therefore rejects the old pair with `additional properties 'regionId', 'regions' not allowed` instead of ignoring it. Both key paths produce the same broker numbering. The deprecated one renders identically and adds a deprecation warning. Setting both blocks fails the render. The chart does not pick one, because neither block merges into the other. The ignored block would describe a topology you don't get. You configure zone awareness only under `orchestration.partitioning`. The `scheme`, `zone`, and `zones` keys have never existed under `global.multiregion`, so there is nothing to migrate for a zone-aware cluster. ## Choose a partitioning scheme `orchestration.partitioning.scheme` selects how the chart distributes partitions and how it identifies brokers. It mirrors the engine property `camunda.cluster.partitioning.scheme`, so the values are the engine's own enum, lower-cased and hyphenated. It does not set the number of partitions, which is `orchestration.partitionCount`. It sets how the chart places the replicas of those partitions. | Scheme | Behavior | | :------------ | :------------------------------------------------------------------------------------------------- | | `round-robin` | Default. Brokers get numeric node IDs and the region is inferred from parity. Two regions at most. | | `zone-aware` | Brokers belong to named zones and are identified as `_`. Any number of zones. | These are the two schemes the chart renders, not the whole engine enum. `camunda.cluster.partitioning.scheme` also accepts `FIXED`, which pins each partition to an explicit broker list. The chart has no value that produces it. Use it only through `orchestration.configuration`, which replaces the generated configuration outright. Existing deployments keep their behavior. When you don't set `scheme`, the chart renders as it did before zone awareness existed. ## The scheme is fixed for the life of the cluster Zone-aware brokers carry the composite ID `_`. Round-robin brokers carry a plain node ID. Do not change `scheme` on a running release. To move an existing cluster onto zone awareness, follow the [migration procedure](/self-managed/deployment/helm/operational-tasks/zone-aware-migration.md). :::warning Changing `orchestration.partitioning.scheme` on a running release is not a values change you can apply on its own. The migration procedure keeps both broker generations alive through `orchestration.partitioning.keepUnzonedBrokers`. It then moves the partition distribution with the cluster management API. Set the scheme when you create the cluster, or follow that procedure. Do not edit the key in place. ::: The chart states the same constraint at render time. An upgrade that flips the scheme prints a warning instead of failing silently. ## Describe the topology With the `zone-aware` scheme, set the local zone and list every zone in the cluster: ```yaml orchestration: partitioning: scheme: zone-aware zone: region-a zones: - name: region-a numberOfBrokers: 2 numberOfReplicas: 2 priority: 100 - name: region-b numberOfBrokers: 3 numberOfReplicas: 3 priority: 50 ``` Use the same `zones` list in every region and change only `zone` to name the local one. The list accepts any number of zones. One, two, and three are the common cases. Each zone field maps to an application property. For what these properties do, see [zone-aware clusters](/self-managed/components/orchestration-cluster/zeebe/configuration/zone-aware-clusters.md) and the [`camunda.cluster.partitioning` reference](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#camundaclusterpartitioning). | Helm value | Application property | | :----------------- | :------------------- | | `name` | `name` | | `numberOfBrokers` | `number-of-brokers` | | `numberOfReplicas` | `number-of-replicas` | | `priority` | `priority` | ## Values the chart derives from the zone list You describe the topology once, and the chart computes the rest. Knowing what it derives tells you which values you must not set yourself. | Rendered setting | Derived from | | :----------------------------------- | :--------------------------------------------------------------- | | `camunda.cluster.size` | Sum of `numberOfBrokers` across all zones | | `camunda.cluster.replication-factor` | Sum of `numberOfReplicas` across all zones | | StatefulSet replica count | `numberOfBrokers` of the local zone | | `CAMUNDA_CLUSTER_ZONE` in the pod | `orchestration.partitioning.zone` | | `camunda.cluster.node-id` | The pod ordinal, which is the broker's index inside its own zone | A zone-aware broker uses the composite ID `_`, so the zone name keeps each broker unique across the cluster. The index restarts at `0` in every zone, and no cluster-wide offset applies. ### Provide initial contact points beyond one zone The chart generates initial contact points only for a single-zone cluster, because one zone sits behind one headless service the chart can address itself. Once the cluster spans more than one zone, the chart cannot know how brokers reach each other across zones. It then generates nothing, and you supply the list through the application environment variables. A cluster that spans more than one zone with no contact points still renders and installs. The chart prints a `[camunda][warning]` that tells you to set `CAMUNDA_CLUSTER_INITIALCONTACTPOINTS` through `orchestration.env`. The chart does not fail the render. A missing list therefore appears as brokers that never form a cluster, not as a failed `helm upgrade`. Treat the warning as an error. One entry per zone is enough, and it does not have to name a specific broker. A broker resolves a contact point once, to a single address. An entry that points at a zone's headless Zeebe service reaches whichever broker pod DNS returns. A broker only has to reach one live member to join. SWIM membership gossip carries the rest of the cluster from there. For what contact points do, see [setting up a cluster](/self-managed/components/orchestration-cluster/zeebe/operations/setting-up-a-cluster.md). The chart sets `publishNotReadyAddresses: true` on that service. The name therefore resolves to a pod during a cold start, before any broker is ready. For how that flag works, see [Kubernetes headless services](https://kubernetes.io/docs/concepts/services-networking/service/#headless-services). You can also list every broker pod. That list tolerates more of the zone being down at bootstrap. You then rewrite the list whenever a zone's broker count changes. Contact points matter only while the cluster bootstraps. Once brokers have found each other, membership gossip carries new members, so a broker joining later does not need to appear in anyone's list. ## A single zone is still one cluster Zone awareness with one zone gives brokers named identities. It cannot bias leaders between failure domains, because every replica has the same zone priority. The chart treats one zone as one cluster, and it generates the initial contact points for you. A second zone spreads the deployment and lets different priorities influence leader placement. :::note Adding a zone that was not part of the original zone list is not a Helm-only change. The partition distribution has to be updated through the [cluster management API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#add-or-re-add-a-zone) as well, because existing partitions have to be told about the new zone. ::: To add a zone, start the brokers in the new zone first. Then update the configuration and add the zone through the [Add a zone API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#add-or-re-add-a-zone). Do not declare a zone before its brokers run, because every partition then runs one zone short. ## Custom application configuration is not merged `orchestration.configuration` replaces the generated application configuration rather than merging with it. With the `zone-aware` scheme, the chart therefore does not inject `camunda.cluster.partitioning` into your custom content. If you supply `orchestration.configuration`, describe the zone-aware settings there yourself. The chart still injects `CAMUNDA_CLUSTER_ZONE` into the pod environment, because that value is per-deployment rather than part of the shared configuration. ## What the chart validates The chart rejects the inputs that would otherwise render a cluster that cannot form: | Rejected | What would happen without the check | | :---------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------- | | `zone` or `zones` set while `scheme` is not `zone-aware` | The topology would be ignored and the cluster would come up single-region with no bootstrap peers. | | `zone` unset, or naming a zone absent from `zones` | The release would take the broker IDs of the first zone and collide with it. | | A zone name that repeats | Zone names are member ID prefixes, so a duplicate collapses two zones into one identity space. | | A zone with more `numberOfReplicas` than `numberOfBrokers` | A zone cannot hold more replicas of a partition than it has brokers to hold them. | | `orchestration.clusterSize` or `orchestration.replicationFactor` that contradicts the zone list | Both are derived from the zone list in zoned mode, so a stale value would be discarded in silence. Restating the derived total is allowed. | | `numberOfZones` or `zoneIndex` carrying a non-default value | They belong to the round-robin broker numbering that zone awareness replaces, so the zone list would silently lose to them. | The schema also requires each `zones` entry to declare `name`, `numberOfBrokers`, `numberOfReplicas`, and `priority`, with each numeric value at least `1`. The chart rejects the last two keys only when they carry a non-default value. Helm gives no reliable way to tell a value you supplied from the chart default. A key that equals its default therefore stays inert instead of failing the render. `numberOfZones` and `zoneIndex` may also carry their round-robin values while `keepUnzonedBrokers` is set, where they still describe the retained broker generation. The chart keeps all of these keys, and they keep working with the `round-robin` scheme. The application validates what the chart cannot validate from values alone, including replica counts against the resulting partition distribution and the remaining zone constraints. ## Related resources - [Zone-aware clusters](/self-managed/components/orchestration-cluster/zeebe/configuration/zone-aware-clusters.md): how the application places partition replicas and biases leadership across zones. - [Configure pod scheduling](pod-scheduling.md): make Kubernetes schedule broker pods into the zones you assigned them to. - [Multi-Region RDBMS](/self-managed/concepts/multi-region/multi-region-rdbms.md): a three-region architecture built on zone awareness. - [Migrate to zone-aware brokers](/self-managed/deployment/helm/operational-tasks/zone-aware-migration.md): the operational procedure for moving an existing numbered cluster onto zone awareness. --- ## Deploy required dependencies with Kubernetes operators This guide explains how to deploy Camunda 8 infrastructure components using **official Kubernetes operators** as an alternative to the Bitnami subcharts. This approach provides production-grade, officially maintained deployment solutions for PostgreSQL, Elasticsearch, and Keycloak. ## Overview :::info New in Camunda 8.8 Starting with Camunda 8.8, we continue to strengthen our commitment to robust, production-ready deployments based on solid foundations. ::: As outlined in [our strategy](https://camunda.com/blog/2025/08/changes-to-camunda-helm-sub-charts-what-you-need-to-know/), Camunda reinforces building deployments on solid foundations—primarily managed PostgreSQL and Elasticsearch services, along with external OIDC providers. However, we understand that managed infrastructure components aren't always available in your organization's service catalog. This guide demonstrates how to integrate these infrastructure components using official Kubernetes operators that don't depend on Bitnami subcharts. These operators are the recommended way to deploy and manage these services in production environments. :::warning Support scope PostgreSQL, Elasticsearch, and Keycloak are **external dependencies** — they are not Camunda products, regardless of the deployment method used. - **Camunda support scope**: Camunda supports the **integration and configuration** of these components with the Camunda Helm chart. Camunda does not provide operational support for the infrastructure components themselves. - **Operator support**: For operational support on infrastructure components, engage the respective project teams or community support channels directly (CloudNativePG, Elastic, Keycloak), or use managed services. ::: :::note Bitnami subcharts (Camunda 8.9 and earlier) Bitnami subcharts are removed in Camunda 8.10 (Helm chart `15.x`). On Camunda 8.9 and earlier you could continue using them via [Bitnami enterprise images](https://docs.camunda.io/docs/8.9/self-managed/deployment/helm/configure/registry-and-images/install-bitnami-enterprise-images/); migrate to operators or managed services before upgrading to 8.10. ::: ## Why use Kubernetes operators? Using official Kubernetes operators provides several advantages over traditional subcharts: - **Vendor maintenance**: Each deployment method is maintained by the respective project team (Elastic, CloudNativePG community, Keycloak team) with dedicated engineering resources - **Production-grade features**: Built-in management, monitoring, and scaling capabilities designed for enterprise environments - **Vendor support channels**: Official support channels, dedicated vendor support teams, and comprehensive documentation available directly from each project - **Security-focused**: Regular updates and CVE patches from upstream maintainers with specialized security teams - **Advanced lifecycle management**: Automated upgrades, failover, and disaster recovery capabilities - **Best practices implementation**: Following upstream recommended deployment patterns established by vendor experts - **Vendor expertise**: Access to specialized knowledge and troubleshooting from the teams that build these technologies (through vendor support channels) - **Future-proof architecture**: Replaces the Bitnami subcharts, removed in Camunda 8.10, with images you choose and update on your own cadence. Each operator still ships its own vendor images, so this changes which supply chain you depend on rather than removing third-party images altogether. ## Prerequisites Before proceeding with this guide, ensure you have: - **Kubernetes cluster**: A functioning cluster with `kubectl` access and block-storage persistent volumes - **Cluster admin privileges**: Required to install Custom Resource Definitions (CRDs) and operators - **Command-line tools**: - `kubectl` configured to access your cluster - `helm` CLI for deploying Camunda using the Helm chart - `openssl` for generating random passwords - `envsubst` command (part of `gettext` package) for environment variable substitution ## Architecture overview This deployment approach separates infrastructure management from application deployment: ![Operator-based infrastructure architecture](assets/vendor-components-arch.jpg) ## Infrastructure components This approach uses three operator-managed infrastructure components, each maintained by their respective project teams: | Component | Purpose | Official Documentation | | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | | **[PostgreSQL with CloudNativePG](#postgresql-deployment)** | Production-grade PostgreSQL clusters for Keycloak, Management Identity, and Camunda Hub databases | [CloudNativePG Documentation](https://cloudnative-pg.io/docs/1.30/) | | **[Elasticsearch with ECK](#elasticsearch-deployment)** | Official Elasticsearch deployment for Zeebe records, Operate, Tasklist, and Optimize data storage | [ECK Guide](https://www.elastic.co/guide/en/cloud-on-k8s/current/index.html) | | **[Keycloak with Keycloak Operator](#keycloak-deployment)** | Automated OIDC authentication provider for Management Identity | [Keycloak Operator Documentation](https://www.keycloak.org/operator/installation) | ## Quick start ### Step 1: Get deployment resources All configuration files, deployment scripts, and automation tools referenced in this guide are available in the Camunda deployment references repository: **Repository**: [camunda-deployment-references](https://github.com/camunda/camunda-deployment-references/tree/main/generic/kubernetes/operator-based)
Quick deployment commands ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/get-your-copy.sh ``` Then execute: ```bash # Set up environment (required for all deployments) source ./0-set-environment.sh # Review and deploy infrastructure components in order # PostgreSQL deployment cd postgresql/ cat deploy.sh # Review the deployment script ./deploy.sh # Elasticsearch deployment cd ../elasticsearch/ cat deploy.sh # Review the deployment script ./deploy.sh # Keycloak deployment cd ../keycloak/ cat deploy.sh # Review the deployment script ./deploy.sh ```
The deployment scripts (`deploy.sh`) contain all the necessary steps to install each component. You can either execute them directly or use them as reference for manual deployment or GitOps integration. ### Step 2: Environment setup All deployment scripts require environment variables to be set. This is a prerequisite for all subsequent steps: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/0-set-environment.sh ``` :::note Ensure you source this environment setup before running any deployment scripts in the following sections. ::: ### Step 3: Deployment overview Each infrastructure component should be deployed individually in the following order: | Order | Component | Dependencies | Purpose | | ----- | ---------------------------------------------- | ------------ | -------------------------------------------------------------------- | | 1 | **[PostgreSQL](#postgresql-deployment)** | None | Database clusters for Keycloak, Management Identity, and Camunda Hub | | 2 | **[Elasticsearch](#elasticsearch-deployment)** | None | Secondary storage for orchestration cluster components | | 3 | **[Keycloak](#keycloak-deployment)** | PostgreSQL | Authentication and identity management | | 4 | **[Camunda](#camunda-deployment)** | All above | Deploy using Helm with operator-managed infrastructure | :::tip Automation with GitOps While this guide demonstrates manual deployment using command-line tools, these same configurations can be automated using GitOps solutions like ArgoCD, Flux, or other Kubernetes deployment pipelines. All configuration files referenced in this guide are designed to work seamlessly with declarative deployment approaches. ::: ## PostgreSQL deployment ### Overview [CloudNativePG](https://cloudnative-pg.io/) is a CNCF project that provides the official Kubernetes deployment method for PostgreSQL. It's designed specifically for cloud-native environments with enterprise-grade features including automated backups, point-in-time recovery, and rolling updates. **Official documentation**: [CloudNativePG Documentation](https://cloudnative-pg.io/docs/1.30/) ### Architecture Our setup provisions three separate PostgreSQL clusters for different Camunda components. Use the latest PostgreSQL version listed in our [supported environments matrix](/reference/supported-environments.md) that is compatible across the required components: - **pg-identity**: Database for Camunda Identity component - **pg-keycloak**: Database for Keycloak identity service - **pg-hub**: Database for Camunda Hub :::note Component flexibility If you don't plan to use certain components (for example, Camunda Hub), you can simply remove the corresponding cluster definition from the configuration before deployment. This allows you to deploy only the PostgreSQL clusters you actually need, reducing resource consumption. ::: #### High availability and node maintenance Each PostgreSQL cluster runs two instances and stores its write-ahead log (WAL) on a dedicated volume. Both defaults exist for operational reasons, not for performance. Two instances keep your Kubernetes nodes drainable. CloudNativePG protects a running database from a node drain: when the node hosting the primary is drained, the operator performs a switchover first and then lets the eviction proceed. A single-instance cluster has nowhere to switch over to, so the operator refuses the eviction and `kubectl drain` retries until it times out: ```text error when evicting pods/"pg-identity-1" -n "camunda": Cannot evict pod as it would violate the pod's disruption budget. ``` A Kubernetes version upgrade drains one node at a time, so a single-instance cluster stalls that upgrade on the node holding the database. CloudNativePG describes this behavior in [Kubernetes upgrade and maintenance](https://cloudnative-pg.io/docs/1.30/kubernetes_upgrade/) and recommends always running more than one instance. A dedicated WAL volume keeps replication from filling the data directory. A standby holds a replication slot on the primary, so a standby that is down or lagging makes the primary retain WAL segments. When `pg_wal` shares a volume with `PGDATA`, that retention grows into the same space as your data. A dedicated volume confines it to its own disk. The 5Gi default covers the 1 GB `max_wal_size` checkpoint target plus the 512 MB CloudNativePG keeps in `wal_keep_size`, with room for a standby that stays down for a while. See [Volume for WAL](https://cloudnative-pg.io/docs/1.30/storage/#volume-for-wal). Both settings are one-way. The CloudNativePG validating webhook rejects removing `walStorage` from an existing cluster, and rejects lowering `storage.size`. Decide on the WAL volume and the data volume size before you deploy. Growing `storage.size` later is supported when your storage class sets `allowVolumeExpansion: true`. :::note `kubectl get pdb` reports `ALLOWED DISRUPTIONS: 0` for the primary whether you run one instance or two, because that budget only ever covers the primary. A second instance does not change the budget. It gives the operator a switchover target, so the drain can proceed. Verify the behavior with a drain rather than with the budget. ::: If you already run the single-instance shape from an earlier release, follow [Migrate an existing single-instance deployment](#migrate-an-existing-single-instance-deployment). #### Run a single instance on constrained environments Two instances only help when your cluster has two schedulable nodes. The reference manifests set [`podAntiAffinityType: required`](https://cloudnative-pg.io/docs/1.30/scheduling/), so the two instances of a cluster never share a node. CloudNativePG defaults to `preferred`, which lets the scheduler put both instances on one node when resources are tight, and a drain then has no switchover target. The trade-off is that a cluster with fewer schedulable nodes than instances leaves the extra pod `Pending` rather than dropping to a single node. On such a cluster, reduce the instance count rather than relaxing the affinity. For local development (Kind, minikube) or any environment where a second instance is not affordable, pass `PG_INSTANCES=1` to `deploy.sh`: ```bash PG_INSTANCES=1 ./deploy.sh ``` This applies `instances: 1` and `enablePDB: false` to every cluster it deploys. Disabling the PodDisruptionBudget keeps the node drainable with a single instance, and CloudNativePG documents this configuration for development clusters. The database is unavailable while its pod is rescheduled. ### Installation **Prerequisites**: Ensure environment variables are sourced (see [Environment setup](#step-2-environment-setup)) The PostgreSQL deployment follows these steps, automated via the `postgresql/deploy.sh` script: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/postgresql/deploy.sh ``` **Deployment steps performed by the script:** - Auto-detect OpenShift and apply Security Context Constraints (SCC) patches if needed - Install CloudNativePG operator to `cnpg-system` namespace - Generate PostgreSQL authentication secrets using `./set-secrets.sh` - Deploy PostgreSQL clusters from `postgresql-clusters.yml` (optionally filtered via `CLUSTER_FILTER` environment variable) - Wait for readiness validation of all deployed clusters #### Operator Custom Resources This configuration creates three dedicated PostgreSQL clusters, each optimized for its specific use case. **Save as** `postgresql-clusters.yml`: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/postgresql/postgresql-clusters.yml ``` **Use cases:** - `pg-keycloak`: Database for Keycloak authentication - `pg-identity`: Database for Management Identity component - `pg-hub`: Database for Camunda Hub #### Execution 1. **Navigate to PostgreSQL directory**: `cd postgresql/` 2. **Review deployment script**: `cat deploy.sh` to understand the deployment steps 3. **Review cluster configuration**: `cat postgresql-clusters.yml` to verify PostgreSQL cluster settings 4. **Adapt configuration if needed**: Modify `postgresql-clusters.yml` for your specific requirements (resource limits, storage, etc.) 5. **Execute deployment**: `./deploy.sh` :::note OpenShift compatibility The `deploy.sh` script automatically detects OpenShift environments and applies the necessary Security Context Constraints (SCC) patches for CloudNativePG compatibility. No separate script is required. ::: ### Camunda Helm configuration The following configuration files integrate PostgreSQL clusters with Camunda components. **Save these files locally** and include them in your Helm installation command. Configure Camunda Identity to use the PostgreSQL cluster. **Save as** `camunda-identity-values.yml`: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/postgresql/camunda-identity-values.yml ``` **Installation**: Add `-f camunda-identity-values.yml` to your Helm install command. Configure Camunda Hub to use the PostgreSQL cluster. **Save as** `camunda-hub-values.yml`: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/postgresql/camunda-hub-values.yml ``` **Installation**: Add `-f camunda-hub-values.yml` to your Helm install command. ## Elasticsearch deployment ### Overview [Elastic Cloud on Kubernetes (ECK)](https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s) is the official Kubernetes deployment method for Elasticsearch, maintained by Elastic. ECK provides the vendor-recommended approach for deploying Elasticsearch in Kubernetes environments, automatically handling cluster deployment, scaling, upgrades, and security configuration. Use the latest Elasticsearch version listed in our [supported environments matrix](../../../../reference/supported-environments.md) and verify compatibility there before deploying. **Official documentation**: [ECK Guide](https://www.elastic.co/guide/en/cloud-on-k8s/current/index.html) ### Architecture The ECK deployment creates an Elasticsearch cluster with: - **Three multi-role nodes**: Each node is master-eligible and also serves data, ingest, and coordinating roles (no separate master-only tier) - **Security configuration**: TLS disabled for internal communication (can be enabled for production) - **Anti-affinity rules**: Ensures nodes are distributed across different Kubernetes nodes - **Resource optimization**: Configured for Camunda's specific requirements :::note Baseline This topology is an opinionated minimal baseline. Adjust node count/roles (e.g., add dedicated ingest, coordinating, or hot/warm tiers), JVM heap, storage class/size, security (TLS & auth), and other settings to match your workload characteristics, retention, and compliance requirements. ::: **Elasticsearch as the secondary storage for Camunda 8:** Elasticsearch serves as the secondary storage for Camunda 8 orchestration cluster components, providing persistent storage and search capabilities. [Learn more about the secondary storage](/self-managed/concepts/secondary-storage/index.md) and how it supports advanced features like web applications, search APIs, process monitoring, task management, and analytics. ### Installation **Prerequisites**: Ensure environment variables are sourced (see [Environment setup](#step-2-environment-setup)) The Elasticsearch deployment follows these steps, automated via the `elasticsearch/deploy.sh` script: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/elasticsearch/deploy.sh ``` **Deployment steps performed by the script:** - Install ECK Custom Resource Definition - Deploy ECK operator to `elastic-system` namespace - Create Elasticsearch cluster from `elasticsearch-cluster.yml` - Wait for cluster health validation #### Operator Custom Resources This configuration creates a baseline Elasticsearch cluster with authentication enabled and TLS disabled. Enable TLS before you use it in production. **Save as** `elasticsearch-cluster.yml`: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/elasticsearch/elasticsearch-cluster.yml ``` #### Execution 1. **Navigate to Elasticsearch directory**: `cd ../elasticsearch/` 2. **Review deployment script**: `cat deploy.sh` to understand the deployment steps 3. **Review cluster configuration**: `cat elasticsearch-cluster.yml` to verify Elasticsearch cluster settings 4. **Adapt configuration if needed**: Modify `elasticsearch-cluster.yml` for your specific requirements (node count, resources, security settings, etc.) 5. **Execute deployment**: `./deploy.sh` ### Camunda Helm configuration The following configuration integrates ECK-managed Elasticsearch with Camunda components. **Save this file locally** and include it in your Helm installation command. Configure Camunda components to use the ECK-managed Elasticsearch. **Save as** `camunda-elastic-values.yml`: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/elasticsearch/camunda-elastic-values.yml ``` **Use case**: External Elasticsearch connection for the Orchestration Cluster (Zeebe, Operate, and Tasklist) and Optimize. **Installation**: Add `-f camunda-elastic-values.yml` to your Helm install command. ## Keycloak deployment ### Overview The [Keycloak Operator](https://www.keycloak.org/operator/installation) provides the official operator-based way to deploy and manage Keycloak instances on Kubernetes. Maintained by the Keycloak team, it provides the recommended approach for automated deployment, configuration, and lifecycle management. Use the latest Keycloak version listed in our [supported environments matrix](/reference/supported-environments.md). For Keycloak support scope, security fixes, and upgrades, see [Keycloak support, security fixes, and upgrades](/self-managed/deployment/helm/configure/authentication-and-authorization/keycloak-support-and-upgrades.md). We use the Camunda-maintained quay-optimized Keycloak image [camunda/keycloak:quay-optimized-version](https://github.com/camunda/keycloak) as it bundles the Camunda Identity login theme, the `/auth` base path, the AWS JDBC wrapper, and pre-baked configuration. **Official documentation**: [Keycloak Operator Documentation](https://www.keycloak.org/operator/installation) ### Architecture The Keycloak deployment provides: - **Database integration**: Connects to CloudNativePG-managed PostgreSQL cluster - **Authentication path**: Configured to serve under `/auth` path prefix - **Flexible domain support**: Options for local development, [Contour](https://projectcontour.io/), or [OpenShift routes](https://docs.redhat.com/en/documentation/openshift_container_platform/4.11/html/networking/configuring-routes) - **Resource optimization**: Sized appropriately for typical Camunda authentication loads - **Custom Ingress management**: Uses dedicated Ingress manifests integrated within the operator configuration for subpath management constraints ### Ingress management Due to subpath management constraints, the Keycloak operator's built-in Ingress configuration is disabled in favor of dedicated Ingress manifests. This approach provides better control over path routing and TLS certificate management when serving Keycloak under the `/auth` path prefix. The dedicated Ingress configuration is integrated directly within the operator manifest to ensure proper deployment coordination and resource management. ### Installation **Prerequisites**: - Ensure environment variables are sourced (see [Environment setup](#step-2-environment-setup)) - PostgreSQL must be deployed first (Keycloak requires database) The Keycloak deployment follows these steps, automated via the `keycloak/deploy.sh` script: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/keycloak/deploy.sh ``` **Deployment steps performed by the script:** - Install Keycloak Custom Resource Definitions - Deploy Keycloak operator to the target namespace - Create Keycloak instance from the selected configuration file - Wait for Keycloak readiness validation #### Operator Custom Resources Basic Keycloak instance for local development. **Save as** `keycloak-instance-no-domain.yml`: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/keycloak/keycloak-instance-no-domain.yml ``` **Use case**: Local development and testing without external domain. :::note Local hostname configuration In certain setups, Keycloak is configured to use its service name as the hostname, which may result in redirections. For local deployments, you need to add the Keycloak service name to your local hosts file (`/etc/hosts` on Linux and macOS) by adding the entry `127.0.0.1 keycloak-service` and use this hostname to access Keycloak. ::: Production Keycloak instance with Contour. **Save as** `keycloak-instance-domain-contour.yml`: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/keycloak/keycloak-instance-domain-contour.yml ``` **Use case**: Production deployment with external domain using the [Contour Ingress controller](https://projectcontour.io/). Keycloak instance configured for OpenShift Routes. **Save as** `keycloak-instance-domain-openshift.yml`: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/keycloak/keycloak-instance-domain-openshift.yml ``` **Use case**: [OpenShift](https://docs.redhat.com/en/documentation/openshift_container_platform/4.11/html/networking/configuring-routes) deployment using native Route resources. #### Execution 1. **Navigate to Keycloak directory**: `cd ../keycloak/` 2. **Review deployment script**: `cat deploy.sh` to understand the deployment steps 3. **Review instance configuration**: `cat keycloak-instance-no-domain.yml` to verify Keycloak instance settings 4. **Adapt configuration if needed**: Choose appropriate instance configuration for your setup: - `keycloak-instance-no-domain.yml` for local development - `keycloak-instance-domain-contour.yml` for Contour - `keycloak-instance-domain-openshift.yml` for OpenShift Routes 5. **Execute deployment**: `./deploy.sh` ### Camunda Helm configuration The following configurations integrate Keycloak with Camunda Identity. **Save the appropriate file locally** based on your deployment setup and include it in your Helm installation command. Configure Camunda to use Keycloak for local development. **Save as** `camunda-keycloak-no-domain-values.yml`: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/keycloak/camunda-keycloak-no-domain-values.yml ``` **Use case**: Local development setup with port-forwarding access. **Installation**: Add `-f camunda-keycloak-no-domain-values.yml` to your Helm install command. Configure Camunda to use Keycloak with external domain. **Save as** `camunda-keycloak-domain-values.yml`: ```yaml reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/keycloak/camunda-keycloak-domain-values.yml ``` :::note Domain configuration step This configuration file contains `${CAMUNDA_DOMAIN}` placeholder variables that must be replaced with your actual domain before deployment. **Options for domain injection:** - **Automatic substitution**: Use `envsubst < camunda-keycloak-domain-values.yml > camunda-keycloak-domain-values-final.yml` (requires `CAMUNDA_DOMAIN` environment variable) - **Manual replacement**: Replace all instances of `${CAMUNDA_DOMAIN}` with your actual domain name in `camunda-keycloak-domain-values.yml` ::: **Use case**: Production setup with external domain and proper OIDC configuration. **Installation**: Add `-f camunda-keycloak-domain-values.yml` to your Helm install command. ## Camunda deployment With all infrastructure components deployed and configured, you can now deploy Camunda using the helm chart. ### Prerequisites **Prerequisites**: - Ensure environment variables are sourced (see [Environment setup](#step-2-environment-setup)) - All infrastructure components (PostgreSQL, Elasticsearch, Keycloak) must be deployed first - Save all configuration files from previous sections locally ### Configuration files summary Before deploying Camunda, ensure you have saved all required configuration files locally. The files are organized by deployment phase: #### Infrastructure deployment files (Custom Resources) | Component | File Name | Purpose | Required for | | -------------------- | ---------------------------------------- | -------------------------------- | ------------------------- | | PostgreSQL | `postgresql-clusters.yml` | PostgreSQL cluster definitions | Infrastructure deployment | | Elasticsearch | `elasticsearch-cluster.yml` | Elasticsearch cluster definition | Infrastructure deployment | | Keycloak (Local) | `keycloak-instance-no-domain.yml` | Local Keycloak instance | Infrastructure deployment | | Keycloak (Contour) | `keycloak-instance-domain-contour.yml` | Production Keycloak with Contour | Infrastructure deployment | | Keycloak (OpenShift) | `keycloak-instance-domain-openshift.yml` | OpenShift Keycloak instance | Infrastructure deployment | #### Camunda integration files (Helm values) | Component | File Name | Purpose | Required for | | ------------------------ | --------------------------------------- | ------------------------------------------ | ------------------ | | Elasticsearch | `camunda-elastic-values.yml` | Connects to ECK-managed Elasticsearch | Camunda deployment | | PostgreSQL (Identity) | `camunda-identity-values.yml` | Connects Identity to PostgreSQL cluster | Camunda deployment | | PostgreSQL (Camunda Hub) | `camunda-hub-values.yml` | Connects Camunda Hub to PostgreSQL cluster | Camunda deployment | | Keycloak (Local) | `camunda-keycloak-no-domain-values.yml` | Local development OIDC configuration | Camunda deployment | | Keycloak (Production) | `camunda-keycloak-domain-values.yml` | Production OIDC configuration | Camunda deployment | ### Pre-deployment checklist Before deploying Camunda, ensure you have completed the following: - [ ] All infrastructure components deployed (PostgreSQL, Elasticsearch, Keycloak) - [ ] Configuration files saved locally from previous sections - [ ] Authentication secrets generated (previous section) ### Helm deployment First, source the environment setup script to set `CAMUNDA_HELM_CHART_VERSION` and other required variables. See the [Helm chart version matrix](https://helm.camunda.io/camunda-platform/version-matrix/) to choose the appropriate chart version for your deployment: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/kubernetes/operator-based/0-set-environment.sh ``` Then, deploy Camunda using the infrastructure configuration files you saved from previous sections. For end-to-end configuration patterns (OIDC-enabled "Full Cluster" including Optimize, Web Modeler, Console, and Identity), see the Full Cluster section of our [Helm installation guide](/self-managed/deployment/helm/install/quick-install.md#full-cluster). Deploy Camunda with external domain configuration: ```bash helm install "$CAMUNDA_RELEASE_NAME" camunda/camunda-platform \ --version "$CAMUNDA_HELM_CHART_VERSION" \ -f camunda-elastic-values.yml \ -f camunda-identity-values.yml \ -f camunda-hub-values.yml \ -f camunda-keycloak-domain-values.yml \ -n "$CAMUNDA_NAMESPACE" ``` Deploy Camunda for local development: ```bash helm install "$CAMUNDA_RELEASE_NAME" camunda/camunda-platform \ --version "$CAMUNDA_HELM_CHART_VERSION" \ -f camunda-elastic-values.yml \ -f camunda-identity-values.yml \ -f camunda-hub-values.yml \ -f camunda-keycloak-no-domain-values.yml \ -n "$CAMUNDA_NAMESPACE" ``` :::tip Helm value files Order & precedence: The order of `-f` flags matters—later files override earlier ones, so place the most specific/override files (e.g. secrets, domain-specific settings) last. File origin: Every `-f` file corresponds to a configuration you saved in previous sections (Elasticsearch integration, PostgreSQL clusters, Keycloak, Identity secrets). Make sure they're present locally and reflect any custom adjustments before running the command. Component flexibility: Drop files for components you don't deploy (for example, remove `camunda-hub-values.yml` if you're not using Camunda Hub) to reduce footprint. ::: ## Verification and troubleshooting ### Verify infrastructure deployment Check that all infrastructure components are running correctly: ```bash # Check PostgreSQL clusters kubectl get clusters -n $CAMUNDA_NAMESPACE # Verify services kubectl get svc -n $CAMUNDA_NAMESPACE | grep "pg-" # Check cluster status kubectl describe cluster pg-identity -n $CAMUNDA_NAMESPACE ``` ```bash # Check Elasticsearch cluster kubectl get elasticsearch -n $CAMUNDA_NAMESPACE # Verify services kubectl get svc -n $CAMUNDA_NAMESPACE | grep "elasticsearch" # Check cluster health kubectl get elasticsearch elasticsearch -n $CAMUNDA_NAMESPACE -o jsonpath='{.status.health}' ``` ```bash # Check Keycloak instance kubectl get keycloak -n $CAMUNDA_NAMESPACE # Verify services kubectl get svc -n $CAMUNDA_NAMESPACE | grep keycloak # Check readiness kubectl get keycloak keycloak -n $CAMUNDA_NAMESPACE -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' ``` ### Common issues and solutions #### PostgreSQL cluster not starting **Symptoms:** PostgreSQL pods stuck in pending or crash loop **Solutions:** - Verify persistent volume claims are bound: `kubectl get pvc -n $CAMUNDA_NAMESPACE` - Check node resources and storage availability - Review CloudNativePG operator logs: `kubectl logs -n cnpg-system deployment/cnpg-controller-manager` **Reference:** [CloudNativePG Troubleshooting](https://cloudnative-pg.io/docs/1.30/troubleshooting) #### Elasticsearch cluster yellow/red status **Symptoms:** Elasticsearch cluster health is not green **Solutions:** - Check disk space and memory allocation - Verify all nodes are running: `kubectl get pods -n $CAMUNDA_NAMESPACE -l elasticsearch.k8s.elastic.co/cluster-name=elasticsearch` - Review ECK operator logs: `kubectl logs -n elastic-system statefulset/elastic-operator` **Reference:** [ECK Troubleshooting Guide](https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-troubleshooting.html) #### Keycloak authentication errors **Symptoms:** Camunda components cannot authenticate with Keycloak **Solutions:** - Verify Keycloak is accessible: `kubectl port-forward svc/keycloak-service 18080:18080 -n $CAMUNDA_NAMESPACE` :::note This uses `keycloak-service` (the service name created by the Keycloak Operator) and port `18080` (configured via `httpPort` in the Keycloak CR for local deployments). This differs from Helm chart deployments which use `camunda-keycloak` service name and port `80`. ::: - Check client configurations in Keycloak admin console - Verify redirect URLs match your deployment setup **Reference:** [Keycloak Operator Documentation](https://www.keycloak.org/operator/basic-deployment) #### Keycloak pod crashes on HTTP/2 cleartext (h2c) requests **Symptoms:** The Keycloak pod exits or enters `CrashLoopBackOff` when it receives an HTTP/2 cleartext (h2c) request, such as a client sending an `Upgrade: h2c` header over a plain-HTTP port-forward. Clients receive an empty reply, and the Keycloak logs show a `java.lang.NoSuchMethodError` originating from Vert.x and Netty. **Cause:** `camunda/keycloak:quay-optimized-*` image tags older than `quay-optimized-26.6.4` bundle a conflicting Netty HTTP/2 codec under `/opt/keycloak/providers`, pulled in transitively by the AWS Advanced JDBC Wrapper. It shadows the Netty version shipped with Keycloak and breaks h2c handling. **Solutions:** - Upgrade the Keycloak image to `camunda/keycloak:quay-optimized-26.6.4` or later, where the conflicting Netty libraries are removed. Update the `image` field in your Keycloak custom resource (`keycloak-instance-*.yml`). This is the recommended fix. - If you cannot upgrade, disable HTTP/2 so Keycloak falls back to HTTP/1.1. Set the `QUARKUS_HTTP_HTTP2` environment variable to `false` in the Keycloak custom resource: ```yaml spec: unsupported: podTemplate: spec: containers: - env: - name: QUARKUS_HTTP_HTTP2 value: "false" ``` **Reference:** [camunda/keycloak HTTP/2 cleartext crash issue](https://github.com/camunda/camunda-deployment-references/issues/2809) ## Production considerations ### Security - **Network policies**: Implement network policies to restrict traffic between components. See [required network traffic](/self-managed/deployment/helm/install/production/index.md#required-network-traffic). - **TLS encryption**: Enable TLS for all inter-component communication - **Secret management**: Use external secret management systems in production - **RBAC**: Configure proper role-based access control for infrastructure and applications ### Backup and disaster recovery - **Elasticsearch**: Perform backups using Camunda for Elastic (see [Camunda backup guide](/self-managed/operational-guides/backup-restore/elasticsearch/backup.md)). - **PostgreSQL**: Configure automated backups using [CloudNativePG's backup capabilities](https://cloudnative-pg.io/docs/1.30/recovery) - **Keycloak**: Configure regular [exports of realm and user data](https://www.keycloak.org/server/importExport) - **Configuration**: Store all configuration files in version control ### Monitoring and observability - **Metrics**: Enable Prometheus monitoring for all infrastructure components - **Logging**: Aggregate logs from infrastructure and application components - **Alerting**: Set up alerts for critical infrastructure events ### Resource planning - **CPU and memory**: Size clusters based on expected workload - **Storage**: Plan for data growth and I/O requirements - **Network**: Consider bandwidth requirements between components ## Migrate an existing single-instance deployment Deployments created before the high availability defaults run one instance per PostgreSQL cluster, with `pg_wal` inside the data volume. Moving them to the current defaults is an in-place change: CloudNativePG clones a second instance from the running primary and relocates `pg_wal` onto its new volume by itself. You do not dump, restore, or recreate anything. Plan for one short interruption per cluster. CloudNativePG applies the new pod specification as a [rolling update](https://cloudnative-pg.io/docs/1.30/rolling_update/): replicas first, the primary last. With the default `primaryUpdateMethod: restart`, the primary restarts in place, which interrupts open connections to that cluster for a few seconds. The clusters migrate independently, so the interruptions do not have to happen at the same time. ### Before you start | Check | Why it matters | | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | Two schedulable nodes | The second instance is only useful on another node, and the drain you are enabling needs somewhere to move the primary. | | Free storage | The existing instance gains a WAL volume and a second instance is created with both. With the defaults, that is 25 Gi more per cluster. | | A current backup | The migration is in place and keeps your volume, so an unrelated failure during it has no second copy to fall back on. | | Cluster is healthy | Run `kubectl get cluster -n $CAMUNDA_NAMESPACE` and confirm the phase is `Cluster in healthy state` before changing anything. | ### Run the migration 1. Add the following settings to each cluster you already have, keeping their existing names, databases, owners, and secrets. Do not replace your manifests with the current reference ones to perform this migration: the 8.10 reference manifests also rename the Web Modeler cluster to `pg-hub`, and applying that rename creates a new empty cluster next to your existing `pg-webmodeler` rather than migrating it. ```yaml spec: instances: 2 affinity: enablePodAntiAffinity: true topologyKey: kubernetes.io/hostname podAntiAffinityType: required walStorage: size: 5Gi ``` 1. Apply the change. `deploy.sh` applies the standard clusters and waits for each cluster to be fully ready: ```bash ./deploy.sh ``` If you deploy the orchestration database, run the script for `pg-camunda` as well: ```bash CLUSTER_FILTER=pg-camunda ./deploy.sh ``` To apply the manifests directly instead, remember the orchestration cluster lives in its own file. Applying only the first manifest leaves `pg-camunda` on the single-instance shape: ```bash kubectl apply --server-side -f postgresql-clusters.yml -n "$CAMUNDA_NAMESPACE" # only if you deploy the orchestration database (RDBMS secondary storage) kubectl apply --server-side -f postgresql-orchestration-cluster.yml -n "$CAMUNDA_NAMESPACE" ``` 1. Watch the operator converge. It clones the new instance, then restarts the primary to attach its WAL volume: ```bash kubectl get cluster -n "$CAMUNDA_NAMESPACE" -w ``` The phase moves through `Creating a new replica`, `Waiting for the instances to become active`, and `Primary instance is being restarted without a switchover` before returning to `Cluster in healthy state`. 1. Confirm every cluster reports both instances ready: ```bash kubectl get cluster -n "$CAMUNDA_NAMESPACE" ``` ```text NAME AGE INSTANCES READY STATUS PRIMARY pg-identity 10m 2 2 Cluster in healthy state pg-identity-1 pg-keycloak 10m 2 2 Cluster in healthy state pg-keycloak-1 pg-webmodeler 10m 2 2 Cluster in healthy state pg-webmodeler-1 ``` A cluster stuck at `1` ready usually has its second pod `Pending`, because the required anti-affinity found no second schedulable node. 1. Confirm the instances of each cluster sit on different nodes: ```bash kubectl get pods -n "$CAMUNDA_NAMESPACE" -l cnpg.io/podRole=instance -o wide ``` 1. Confirm `pg_wal` moved onto the dedicated volume on every instance of every cluster. It becomes a symbolic link, and the original directory content is moved for you: ```bash for pod in $(kubectl get pods -n "$CAMUNDA_NAMESPACE" -l cnpg.io/podRole=instance -o name); do echo "$pod" kubectl exec -n "$CAMUNDA_NAMESPACE" "${pod#pod/}" -c postgres -- ls -ld /var/lib/postgresql/data/pgdata/pg_wal done ``` ```text lrwxrwxrwx 1 postgres tape 30 ... /var/lib/postgresql/data/pgdata/pg_wal -> /var/lib/postgresql/wal/pg_wal ``` 1. Confirm the standby of each cluster is streaming before you rely on the new instance. The cluster reports a healthy state as soon as both pods are ready, which happens slightly before the standby re-establishes replication after the primary restart: ```bash for cluster in $(kubectl get cluster -n "$CAMUNDA_NAMESPACE" -o jsonpath='{.items[*].metadata.name}'); do primary=$(kubectl get pod -n "$CAMUNDA_NAMESPACE" -l "cnpg.io/cluster=$cluster,cnpg.io/instanceRole=primary" -o jsonpath='{.items[0].metadata.name}') echo -n "$cluster: " kubectl exec -n "$CAMUNDA_NAMESPACE" "$primary" -c postgres -- psql -U postgres -tAc "SELECT state FROM pg_stat_replication;" done ``` ```text pg-identity: streaming pg-keycloak: streaming pg-webmodeler: streaming ``` Until a cluster reports `streaming`, its standby is not a switchover candidate, and a drain started early stalls with `Current primary is running on unschedulable node, but there are no valid candidates` in the operator log. 1. Verify the result by draining the node that hosts the primary, which is the operation that failed before the migration: ```bash kubectl drain --ignore-daemonsets --delete-emptydir-data ``` The first eviction attempt is still refused while the pod is the primary. CloudNativePG then switches over and the retry succeeds: ```text evicting pod camunda/pg-identity-1 error when evicting pods/"pg-identity-1" -n "camunda" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget. evicting pod camunda/pg-identity-1 pod/pg-identity-1 evicted node/ drained ``` Run `kubectl uncordon ` afterward, and the cluster returns to two ready instances. ### Keep a single instance instead If a second instance is not affordable in your environment, do not leave the cluster at `instances: 1` with its PodDisruptionBudget enabled, because that is the combination that blocks node drains. Set `enablePDB: false` alongside it, as described in [Run a single instance on constrained environments](#run-a-single-instance-on-constrained-environments). ### Reclaim the space later The migration leaves `storage.size` untouched, so your data volumes keep their existing size. If 15Gi is more than your databases need, note that CloudNativePG rejects lowering `storage.size` on a live cluster. Reducing it is a supervised procedure that recreates each instance on a smaller volume, described in [Volume reduction](https://cloudnative-pg.io/docs/1.30/storage/#volume-reduction). ## Migration from subcharts If you're migrating from existing Bitnami sub-chart deployments: 1. **Export data**: Create backups of existing databases and Elasticsearch indices 2. **Deploy operator-based infrastructure**: Install the operator-managed infrastructure alongside existing deployment 3. **Migrate data**: Transfer data to operator-managed services 4. **Update configuration**: Switch Camunda configuration to use new services 5. **Cleanup**: Remove old sub-chart deployments once migration is complete ## Additional resources - [CloudNativePG documentation](https://cloudnative-pg.io/docs/1.30/) - [Elastic Cloud on Kubernetes guide](https://www.elastic.co/guide/en/cloud-on-k8s/current/index.html) - [Keycloak Operator documentation](https://www.keycloak.org/operator/installation) - [Camunda 8 Helm chart parameters](https://artifacthub.io/packages/helm/camunda/camunda-platform#parameters) - [Kubernetes Operator pattern](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) ## Next steps - [Configure Ingress and TLS](/self-managed/deployment/helm/configure/ingress/index.md) - [Set up monitoring and observability](/self-managed/deployment/helm/configure/application-configs.md) - [Configure Logical Tenants](/self-managed/deployment/helm/configure/configure-logical-tenants.md) - [Production deployment guide](/self-managed/deployment/helm/install/production/index.md) --- ## Configure Orchestration REST and gRPC TLS modes Orchestration exposes a REST API (`SERVER_SSL_ENABLED`) and a gRPC API (`CAMUNDA_API_GRPC_SSL_ENABLED`) as independent server settings. The Camunda 8 Helm chart provides a first-class values surface that configures both server flags, the public NGINX Ingress backend protocol, and the in-cluster client schemes used by Web Modeler and Connectors. Customers no longer need to duplicate `webModeler.restapi.clusters` or `connectors.configuration` blocks just to enable Orchestration TLS. :::caution Trust bundle is required for self-signed and private-PKI certificates The settings on this page configure the Orchestration **server** and the **NGINX Ingress** legs. They do **not** by themselves teach in-cluster Java clients (Web Modeler, Connectors) to trust the cert. If the Orchestration server certificate is self-signed or issued by a private/internal CA, you **must also** set `global.tls.caBundle.secret.existingSecret` to a Secret holding the CA bundle that signed it. Without it, the JVM default truststore is used and gRPC/REST handshakes from Web Modeler and Connectors will fail with `PKIX path building failed`. The minimal caBundle configuration is shown in the [cert-manager recipe](#recipe-cert-manager--lets-encrypt-or-internal-issuer) below. Certificates issued by a public CA already present in the JVM truststore (Let's Encrypt, DigiCert, etc.) do not require this. ::: ## First-class values ```yaml global: tls: orchestration: autoRollout: false # opt-in: roll Orchestration pods on Secret rotation (requires Secret read RBAC) rest: enabled: false # REST TLS — sets SERVER_SSL_ENABLED on Orchestration type: pkcs12 # one of: pkcs12, pem keyAlias: "" # pkcs12 only — optional cert alias inside the keystore cert: secret: existingSecret: "" # Kubernetes Secret holding the REST server cert (or PKCS12 keystore) existingSecretKey: "" # pkcs12: keystore file key (default keystore.p12); pem: cert key (default tls.crt) privateKey: secret: existingSecretKey: tls.key # pem only — private-key key inside the Secret keystorePassword: secret: existingSecretKey: keystore-password # pkcs12 only — keystore password key inside the Secret proxyVerify: enabled: false sniHost: "" # optional: SNI hostname NGINX presents when verifying the upstream cert (defaults to the Orchestration Service name) caSecret: secret: existingSecret: "" # Secret holding the CA bundle for NGINX upstream verification, under the fixed ca.crt key namespace: "" # optional: CA Secret namespace (defaults to release namespace) grpc: enabled: false # gRPC TLS — sets CAMUNDA_API_GRPC_SSL_ENABLED on Orchestration cert: secret: existingSecret: "" # Kubernetes Secret with PEM cert for the gRPC server existingSecretKey: "" # defaults to tls.crt privateKey: secret: existingSecretKey: tls.key ``` Both flags default to `false`. Set either independently to enable that protocol's TLS. Explicit `orchestration.env` entries with the same name override these flags (Kubernetes last-wins on duplicate env names). When `enabled: true`, the chart fails template rendering unless the cert material is configured either via the `secret` sub-block (recommended) or via explicit env vars (`SERVER_SSL_KEY_STORE` / `SERVER_SSL_CERTIFICATE` for REST, `CAMUNDA_API_GRPC_SSL_CERTIFICATE` for gRPC). This prevents the silent Spring Boot / gRPC startup crash that would otherwise occur. ### REST TLS: PKCS12 vs PEM The REST `type` field selects which Spring Boot SSL property family the chart emits: - **`pkcs12` (default)** — emits `SERVER_SSL_KEY_STORE`, `SERVER_SSL_KEY_STORE_TYPE=PKCS12`, `SERVER_SSL_KEY_STORE_PASSWORD` (via `secretKeyRef`), and optionally `SERVER_SSL_KEY_ALIAS`. - **`pem`** — emits `SERVER_SSL_CERTIFICATE` and `SERVER_SSL_CERTIFICATE_PRIVATE_KEY` (Spring Boot 2.7+). Use this for cert-manager `kubernetes.io/tls` Secrets (`tls.crt` + `tls.key`) and Let's Encrypt-issued certificates — no manual PKCS12 conversion needed. The gRPC server only accepts PEM, so the gRPC `secret` block has no `type` field. ### Cert rotation `global.tls.orchestration.autoRollout` mirrors `global.tls.caBundle.autoRollout`. When `true`, the chart stamps `checksum/orchestration-tls-{rest,grpc}` pod annotations derived from the configured Secret. For PEM REST and gRPC certificates, the hash covers both the certificate and private key. For PKCS12, it covers the keystore. A `helm upgrade` then rolls the Orchestration pods when this material changes. The hash deliberately excludes the keystore password. Rotate the password with the keystore material or restart the StatefulSet manually after a password-only change. This uses Helm's `lookup`, which requires the upgrading identity to have `get` on Secrets in the release namespace. It is inert under GitOps tools that render with `helm template`. Leave `autoRollout` off in those environments and rotate manually with `kubectl rollout restart statefulset/-orchestration`. ### Recipe: cert-manager + Let's Encrypt or internal Issuer cert-manager produces `kubernetes.io/tls` Secrets with `tls.crt` + `tls.key`. The chart consumes those directly — PEM for REST (via `type: pem`) and PEM for gRPC. This recipe assumes cert-manager v1.x is already installed in the cluster. If it is not, install it first per the [cert-manager installation guide](https://cert-manager.io/docs/installation/) (typically `helm install cert-manager jetstack/cert-manager --set crds.enabled=true`). The four-step example below creates and uses an internal CA. For a public ACME provider such as Let's Encrypt, don't apply steps one through three. In the server `Certificate` from step four, replace `issuerRef.name` and `issuerRef.kind` with the name and kind of your existing ACME `Issuer` or `ClusterIssuer`. Public CA certificates already present in the JVM default truststore don't need `global.tls.caBundle`. ```yaml # 1. Bootstrap Issuer. A bare selfSigned Issuer cannot issue a CA bundle on # its own — it only signs each Certificate with that Certificate's own # private key. It exists solely to sign step 2 (the actual CA Certificate). apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: camunda-selfsigned-bootstrap namespace: camunda spec: selfSigned: {} --- # 2. The actual CA Certificate. isCA: true makes this a CA cert whose # private key signs subsequent leaf certs. The resulting Secret # (camunda-ca-bundle) is what global.tls.caBundle.secret.existingSecret # points at — Web Modeler and Connectors load it into the JVM truststore. apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: camunda-ca namespace: camunda spec: isCA: true commonName: camunda-ca secretName: camunda-ca-bundle duration: 87600h # 10 years — pick a CA lifetime longer than any leaf privateKey: algorithm: ECDSA size: 256 issuerRef: name: camunda-selfsigned-bootstrap kind: Issuer --- # 3. The CA Issuer. Uses the CA cert+key from step 2 to sign leaf # Certificates. This is the Issuer your server Certificates reference. apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: camunda-ca-issuer namespace: camunda spec: ca: secretName: camunda-ca-bundle --- # 4. Server Certificate (gRPC shown; REST is identical with its own # secretName + dnsNames matching the REST service). # # IMPORTANT: dnsNames must match the actual Kubernetes Service name # that fronts the Orchestration gRPC port (26500). Confirm with: # kubectl -n camunda get svc -l app.kubernetes.io/component=zeebe-gateway # The Service name derives from orchestration.serviceName; in the chart 8.10 # default layout it is typically `-zeebe-gateway` (the # zeebe-gateway component label is kept for backward compatibility through # the Orchestration rebrand). Always confirm the actual name with the # command above rather than assuming it. # Substitute your actual release name for `my-release` below. apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: orchestration-grpc-cert namespace: camunda spec: secretName: orchestration-grpc-cert # → matches existingSecret in values duration: 8760h renewBefore: 720h issuerRef: name: camunda-ca-issuer # step 3, NOT the bootstrap kind: Issuer dnsNames: - my-release-zeebe-gateway - my-release-zeebe-gateway.camunda.svc - my-release-zeebe-gateway.camunda.svc.cluster.local ``` Then in the chart values: ```yaml global: tls: orchestration: autoRollout: true # cert-manager renews on schedule; this rolls Orchestration on renewal rest: enabled: true type: pem cert: secret: existingSecret: orchestration-rest-cert # a step-4 Certificate for the REST service # existingSecretKey defaults to tls.crt when type=pem (auto-substituted) # privateKey.secret.existingSecretKey defaults to tls.key grpc: enabled: true cert: secret: existingSecret: orchestration-grpc-cert # existingSecretKey defaults to tls.crt # privateKey.secret.existingSecretKey defaults to tls.key caBundle: secret: # The CA cert Secret from step 2 above. Web Modeler and Connectors # load this into the JVM truststore so their handshakes to the # Orchestration REST and gRPC servers succeed. Skip this whole # block if your server certs are from a public CA already in the # JVM default truststore (Let's Encrypt, DigiCert, etc.). existingSecret: camunda-ca-bundle existingSecretKey: ca.crt autoRollout: true ``` ### Migrating from a hand-wired Orchestration TLS setup Before this guide existed, customers enabled Orchestration TLS by hand-wiring everything in `orchestration.env` plus `extraVolumes`/`extraVolumeMounts`. The legacy shape still works (the helpers OR-merge the new flag with the env vars), so an upgrade is non-breaking by default. To migrate to the first-class surface: **Before** (legacy): ```yaml orchestration: env: - name: CAMUNDA_API_GRPC_SSL_ENABLED value: "true" - name: CAMUNDA_API_GRPC_SSL_CERTIFICATE value: /usr/local/camunda/certificates/orchestration/tls.crt - name: CAMUNDA_API_GRPC_SSL_CERTIFICATEPRIVATEKEY value: /usr/local/camunda/certificates/orchestration/tls.key extraVolumes: - name: orchestration-tls secret: secretName: orchestration-grpc-cert extraVolumeMounts: - name: orchestration-tls mountPath: /usr/local/camunda/certificates/orchestration readOnly: true ``` **After** (first-class): ```yaml global: tls: orchestration: grpc: enabled: true cert: secret: existingSecret: orchestration-grpc-cert ``` The `helm upgrade` will roll the Orchestration StatefulSet because the rendered env vars and volume names change. Expect a brief outage during the rolling restart (no data loss — PVCs are unchanged). Existing hand-written `webModeler.restapi.clusters` or `connectors.configuration` blocks remain authoritative; if their `grpc-address` / `rest-address` matches what the chart would derive (visible via `helm template`), you can delete them too. ### Verify the REST backend certificate at the Ingress (NGINX) By default, NGINX Ingress uses TLS for the REST upstream when `backend-protocol: HTTPS` is set, but it doesn't verify the upstream certificate. Enable `proxyVerify` under `global.tls.orchestration.rest` to verify the REST certificate. The chart doesn't support `proxyVerify` for gRPC because the Ingress-NGINX controller applies its `proxy-ssl-*` annotations to `proxy_pass`, not the `grpc_pass` used by a GRPCS backend. ```yaml global: tls: orchestration: rest: enabled: true type: pem cert: secret: existingSecret: orchestration-rest-cert existingSecretKey: tls.crt privateKey: secret: existingSecretKey: tls.key proxyVerify: enabled: true caSecret: secret: existingSecret: orchestration-upstream-ca # PEM CA bundle, under the fixed ca.crt key sniHost: "" # set when the cert SAN does not match the in-cluster service name ``` This adds the following annotations to the `/orchestration` Ingress: - `nginx.ingress.kubernetes.io/proxy-ssl-verify: on` - `nginx.ingress.kubernetes.io/proxy-ssl-secret: /` - `nginx.ingress.kubernetes.io/proxy-ssl-name: ` - `nginx.ingress.kubernetes.io/proxy-ssl-server-name: on` The chart defaults `proxy-ssl-name` to the Orchestration Service name. Set `sniHost` only when the certificate subject alternative name requires a different hostname. The CA Secret must contain the CA bundle under the fixed `ca.crt` key. By default, the chart expects the Secret in the same namespace as the Ingress resource. To reference a Secret in a different namespace, set `caSecret.namespace` and configure the Ingress-NGINX controller with `allow-cross-namespace-resources=true`. The chart fails template rendering if `proxyVerify.enabled: true` and `caSecret.secret.existingSecret` is empty. Note that `proxyVerify` covers only the NGINX → Orchestration leg. In-cluster Java clients (Web Modeler, Connectors) trust upstream certs through `global.tls.caBundle`, which is independent. ## Supported modes | Mode | `global.tls.orchestration.rest.enabled` | `global.tls.orchestration.grpc.enabled` | `/orchestration` Ingress backend | gRPC Ingress backend-protocol | Web Modeler gRPC | Connectors gRPC | REST clients | | ------------------- | --------------------------------------- | --------------------------------------- | -------------------------------- | ----------------------------- | ---------------- | --------------- | ------------ | | Plaintext (default) | `false` | `false` | HTTP | `GRPC` | `grpc://` | `http://` | `http://` | | REST TLS only | `true` | `false` | HTTPS | `GRPC` | `grpc://` | `http://` | `https://` | | gRPC TLS only | `false` | `true` | HTTP | `GRPCS` | `grpcs://` | `https://` | `http://` | | Both TLS | `true` | `true` | HTTPS | `GRPCS` | `grpcs://` | `https://` | `https://` | The chart derives Web Modeler and Connectors endpoints automatically. Explicit `webModeler.restapi.clusters` and `connectors.configuration` blocks remain authoritative — set them only if you need an endpoint shape the helpers do not produce. ## Example: REST plaintext + gRPC TLS This example fits an internal Zero-Trust network where the gRPC API must be TLS-protected but the REST API stays on plaintext behind the cluster Ingress. ```yaml global: host: camunda.example.com ingress: enabled: true tls: enabled: true secretName: camunda-platform-tls tls: orchestration: grpc: enabled: true cert: secret: existingSecret: orchestration-grpc-cert existingSecretKey: tls.crt privateKey: secret: existingSecretKey: tls.key caBundle: secret: existingSecret: camunda-internal-ca existingSecretKey: ca.crt ``` The chart mounts `orchestration-grpc-cert` into the Orchestration pod and sets `CAMUNDA_API_GRPC_SSL_CERTIFICATE` / `CAMUNDA_API_GRPC_SSL_CERTIFICATEPRIVATEKEY` to the mounted paths automatically. Create the secret out-of-band, for example: ```shell kubectl create secret generic orchestration-grpc-cert \ --from-file=tls.crt=./tls.crt \ --from-file=tls.key=./tls.key ``` With this configuration the chart: - Sets `CAMUNDA_API_GRPC_SSL_ENABLED=true` on the Orchestration container. - Annotates the public gRPC Ingress with `nginx.ingress.kubernetes.io/backend-protocol: GRPCS`. - Renders the Web Modeler REST API ConfigMap with `grpc: grpcs://:26500`. - Renders the Connectors ConfigMap with `grpc-address: https://:26500`. Trust material for in-cluster Java components flows through [`global.tls.caBundle`](#recipe-cert-manager--lets-encrypt-or-internal-issuer). The CA bundle is mounted as a Java truststore into Orchestration, Web Modeler, Connectors, and any other Java components in the release. ## Verification After deploying, confirm the in-cluster endpoints match the chosen mode: ```shell kubectl -n get ingress -grpc \ -o jsonpath='{.metadata.annotations.nginx\.ingress\.kubernetes\.io/backend-protocol}{"\n"}' kubectl -n get configmap -connectors-configuration \ -o jsonpath='{.data.application\.yaml}' | grep -E 'grpc-address|rest-address' kubectl -n get configmap -web-modeler-restapi-configuration \ -o jsonpath='{.data.application\.yaml}' | grep -E '^\s+(grpc|rest):' ``` ## Connectors TLS Connectors in 8.10 runs its own Spring Boot HTTP server. The chart can expose it through an NGINX Ingress or a Gateway API `HTTPRoute`. `global.tls.connectors` mirrors the Orchestration REST configuration and enables TLS at the Connectors pod. ### Modes - **PKCS12 (default)** — `type: pkcs12`. The chart sets `SERVER_SSL_KEY_STORE`, `SERVER_SSL_KEY_STORE_TYPE=PKCS12`, and `SERVER_SSL_KEY_STORE_PASSWORD` (from a `secretKeyRef`) on the Connectors container. Use when you manage keystores out-of-band (Java PKI, internal CA). - **PEM (cert-manager compatible)** — `type: pem`. The chart sets `SERVER_SSL_CERTIFICATE` and `SERVER_SSL_CERTIFICATE_PRIVATE_KEY` on the Connectors container. Compatible with cert-manager `kubernetes.io/tls` Secrets out of the box. In both modes the chart: - Sets `SERVER_SSL_ENABLED=true` on the Connectors container. - Mounts the referenced Secret at `/usr/local/camunda/certificates/connectors/`. - Switches the container probes (`startupProbe` / `readinessProbe` / `livenessProbe`) to `HTTPS`. - Stamps a `checksum/connectors-tls` pod annotation when `global.tls.connectors.autoRollout: true`, so the next `helm upgrade` rolls Connectors on cert rotation. ### PKCS12 example ```yaml global: tls: connectors: enabled: true type: pkcs12 keyAlias: connectors-rest cert: secret: existingSecret: connectors-tls-keystore existingSecretKey: keystore.p12 keystorePassword: secret: existingSecretKey: keystore-password caBundle: secret: existingSecret: camunda-internal-ca existingSecretKey: ca.crt ``` Create the Secret out-of-band: ```shell openssl pkcs12 -export \ -in ./tls.crt -inkey ./tls.key \ -out ./keystore.p12 \ -password pass:changeit \ -name connectors-rest kubectl create secret generic connectors-tls-keystore \ --from-file=keystore.p12=./keystore.p12 \ --from-literal=keystore-password=changeit ``` ### PEM example (cert-manager) ```yaml global: tls: connectors: enabled: true type: pem cert: secret: existingSecret: connectors-cert # existingSecretKey defaults to tls.crt when type=pem (auto-substituted) caBundle: secret: existingSecret: camunda-internal-ca existingSecretKey: ca.crt ``` With a cert-manager `Certificate` that issues into the same namespace, the resulting `kubernetes.io/tls` Secret already carries `tls.crt` and `tls.key` — the chart picks them up automatically (when `cert.secret.existingSecretKey` is left empty in PEM mode, `tls.crt` is substituted automatically). ### Configure inbound routing The dedicated NGINX Ingress template sets its backend protocol to HTTPS when Connectors TLS is enabled. The Gateway API `HTTPRoute` still forwards plaintext unless a `BackendTLSPolicy` targets the Connectors Service. Create that policy according to your Gateway implementation and configure its certificate validation before enabling pod TLS. Without the policy, inbound Connectors traffic through the generated `HTTPRoute` fails. ### Verification ```shell kubectl -n get deployment -connectors \ -o jsonpath='{.spec.template.spec.containers[0].env}' | jq '.[] | select(.name|startswith("SERVER_SSL_"))' kubectl -n get deployment -connectors \ -o jsonpath='{.spec.template.spec.containers[0].readinessProbe.httpGet.scheme}{"\n"}' ``` ## Optimize TLS Optimize in 8.10 runs its own Spring Boot HTTP server. The chart can expose it through an NGINX Ingress or a Gateway API `HTTPRoute`. `global.tls.optimize` mirrors the Orchestration REST configuration and enables TLS at the Optimize pod. This server-side TLS is independent of the existing client-side `optimize.database.elasticsearch.tls` / `optimize.database.opensearch.tls` configuration. Both directions can use TLS together. The chart mounts the server certificate from a regular Secret volume named `optimize-server-tls`, alongside the client-side `keystore` truststore mount when configured. ### Modes - **PKCS12 (default)** — `type: pkcs12`. The chart sets `SERVER_SSL_KEY_STORE`, `SERVER_SSL_KEY_STORE_TYPE=PKCS12`, and `SERVER_SSL_KEY_STORE_PASSWORD` (from a `secretKeyRef`) on the Optimize main container. Use when you manage keystores out-of-band (Java PKI, internal CA). - **PEM (cert-manager compatible)** — `type: pem`. The chart sets `SERVER_SSL_CERTIFICATE` and `SERVER_SSL_CERTIFICATE_PRIVATE_KEY` on the Optimize main container. Compatible with cert-manager `kubernetes.io/tls` Secrets out of the box. In both modes the chart: - Sets `SERVER_SSL_ENABLED=true` on the Optimize main container (and only the main container — the optional `migration` init container is untouched, since it does not serve HTTP). - Mounts the referenced Secret at `/usr/local/camunda/certificates/optimize/` as a regular Secret volume named `optimize-server-tls`. - Uses HTTPS for `startupProbe`, `readinessProbe`, and `livenessProbe` when their `scheme` values are empty, which is the default. An explicit scheme remains authoritative, including `HTTP`. - Stamps a `checksum/optimize-tls` pod annotation when `global.tls.optimize.autoRollout: true`, so the next `helm upgrade` rolls Optimize on cert rotation. ### Server-side vs client-side TLS in Optimize The two surfaces are deliberately orthogonal: | Direction | Values key | Volume name | Purpose | | ----------------------------- | ------------------------------------------------------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------- | | Inbound (clients → Optimize) | `global.tls.optimize.cert.secret.existingSecret` | `optimize-server-tls` | Server identity cert + key for the Optimize HTTP listener (this guide). | | Outbound (Optimize → ES / OS) | `optimize.database.elasticsearch.tls.secret.existingSecret` (or `…opensearch.tls.secret.…`) | `keystore` | Truststore so Optimize trusts the ES / OS server cert when calling secondary storage. | Operators commonly need TLS for both inbound and outbound connections. These are independent one-way TLS connections; neither side requires a client certificate. The chart supports both simultaneously, and the `optimize-server-tls` and `keystore` volumes can coexist with their respective mounts on the Optimize main container. ### PKCS12 example ```yaml global: tls: optimize: enabled: true type: pkcs12 keyAlias: optimize-rest cert: secret: existingSecret: optimize-tls-keystore existingSecretKey: keystore.p12 keystorePassword: secret: existingSecretKey: keystore-password caBundle: secret: existingSecret: camunda-internal-ca existingSecretKey: ca.crt ``` Create the Secret out-of-band: ```shell openssl pkcs12 -export \ -in ./tls.crt -inkey ./tls.key \ -out ./keystore.p12 \ -password pass:changeit \ -name optimize-rest kubectl create secret generic optimize-tls-keystore \ --from-file=keystore.p12=./keystore.p12 \ --from-literal=keystore-password=changeit ``` ### PEM example (cert-manager) ```yaml global: tls: optimize: enabled: true type: pem cert: secret: existingSecret: optimize-cert # existingSecretKey defaults to tls.crt when type=pem (auto-substituted) caBundle: secret: existingSecret: camunda-internal-ca existingSecretKey: ca.crt ``` With a cert-manager `Certificate` that issues into the same namespace, the resulting `kubernetes.io/tls` Secret already carries `tls.crt` and `tls.key` — the chart picks them up automatically (when `cert.secret.existingSecretKey` is left empty in PEM mode, `tls.crt` is substituted automatically). ### Combined server + client TLS example ```yaml global: tls: optimize: enabled: true cert: secret: existingSecret: optimize-tls-keystore caBundle: secret: existingSecret: camunda-internal-ca existingSecretKey: ca.crt optimize: enabled: true database: elasticsearch: url: protocol: https ``` The chart wires the inbound `optimize-server-tls` keystore for the Optimize HTTP listener. For the outbound connection, `url.protocol: https` enables HTTPS and `global.tls.caBundle` supplies the PEM CA that Optimize uses to trust Elasticsearch. Both paths are independent and may be enabled together. ### Configure inbound routing The dedicated NGINX Ingress template sets its backend protocol to HTTPS when Optimize TLS is enabled. The Gateway API `HTTPRoute` still forwards plaintext unless a `BackendTLSPolicy` targets the Optimize Service. Create that policy according to your Gateway implementation and configure its certificate validation before enabling pod TLS. Without the policy, inbound Optimize traffic through the generated `HTTPRoute` fails. ### Verification ```shell kubectl -n get deployment -optimize \ -o jsonpath='{.spec.template.spec.containers[0].env}' | jq '.[] | select(.name|startswith("SERVER_SSL_"))' kubectl -n get deployment -optimize \ -o jsonpath='{.spec.template.spec.containers[0].readinessProbe.httpGet.scheme}{"\n"}' kubectl -n get deployment -optimize \ -o jsonpath='{range .spec.template.spec.volumes[*]}{.name}{"\n"}{end}' | grep -E 'optimize-server-tls|keystore' ``` --- ## Configure pod networking The Camunda Helm chart exposes values that control how component pods connect to the network. Use these settings when your infrastructure requires custom DNS resolution behavior, or when orchestration cluster pods need to share the host node's network namespace. ## DNS policy Every Camunda component supports a `dnsPolicy` value that controls how DNS resolution works for its pods. It maps directly to the Kubernetes [`dnsPolicy` pod spec field](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy). The following components support `dnsPolicy`: | Component | Value key | | ---------------------- | --------------------------------- | | Orchestration cluster | `orchestration.dnsPolicy` | | Identity | `identity.dnsPolicy` | | Connectors | `connectors.dnsPolicy` | | Optimize | `optimize.dnsPolicy` | | Console | `console.dnsPolicy` | | Web Modeler REST API | `webModeler.restapi.dnsPolicy` | | Web Modeler WebSockets | `webModeler.websockets.dnsPolicy` | Example — setting a custom DNS policy for the orchestration cluster: ```yaml orchestration: dnsPolicy: ClusterFirst ``` If you don't set `dnsPolicy` for a component, Kubernetes applies its default (`ClusterFirst`). Common values: | Value | Behavior | | ------------------------- | ------------------------------------------------------------------------------------------------------------ | | `ClusterFirst` | In-cluster DNS takes priority; unresolved names fall back to the upstream nameserver. Default for most pods. | | `ClusterFirstWithHostNet` | Same as `ClusterFirst`, but required when `hostNetwork: true` to preserve in-cluster DNS resolution. | | `Default` | Pods inherit the DNS configuration of the node they run on. | | `None` | DNS is configured entirely via `dnsConfig`. | ## Custom DNS configuration Every Camunda component also supports a `dnsConfig` value that lets you supply custom DNS nameservers, search domains, and resolver options. It maps directly to the Kubernetes [`dnsConfig` pod spec field](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config). The following components support `dnsConfig`: | Component | Value key | | ---------------------- | --------------------------------- | | Orchestration cluster | `orchestration.dnsConfig` | | Identity | `identity.dnsConfig` | | Connectors | `connectors.dnsConfig` | | Optimize | `optimize.dnsConfig` | | Console | `console.dnsConfig` | | Web Modeler REST API | `webModeler.restapi.dnsConfig` | | Web Modeler WebSockets | `webModeler.websockets.dnsConfig` | Use `dnsConfig` when you need to override or extend the default DNS resolver — for example, to add a private nameserver or a custom search domain: ```yaml connectors: dnsPolicy: None dnsConfig: nameservers: - 192.168.1.100 searches: - my-namespace.svc.cluster.local - svc.cluster.local options: - name: ndots value: "5" ``` ## Host network (orchestration cluster only) Setting `orchestration.hostNetwork` to `true` makes orchestration cluster pods use the host node's network namespace instead of the default pod network. In this mode, pods share the node's IP address and port space rather than receiving their own cluster IP. This option is available only for the orchestration cluster (a StatefulSet). It's useful in environments where: - Pods must be reachable directly via the node IP (for example, bare-metal deployments without a CNI overlay network). - A network plugin or firewall requires pods to appear as node-level processes. - You're integrating with infrastructure that doesn't support pod-level IP routing. ```yaml orchestration: hostNetwork: true ``` When `hostNetwork` is enabled and you haven't set `orchestration.dnsPolicy`, the chart automatically sets `dnsPolicy` to `ClusterFirstWithHostNet`. This ensures pods on the host network can still resolve in-cluster DNS names (for example, Kubernetes `Service` names). If you set `orchestration.dnsPolicy` explicitly, that value always takes precedence. :::note Using `hostNetwork: true` means all ports opened by the orchestration cluster pods are bound directly on the node. Make sure the required ports are not already in use on the node, and review your network policies accordingly. ::: ### Combining host network with custom DNS To fully control DNS on a host-network pod: ```yaml orchestration: hostNetwork: true dnsPolicy: None dnsConfig: nameservers: - 10.96.0.10 searches: - cluster.local ``` --- ## Configure pod scheduling The Camunda Helm chart exposes values that control where Kubernetes schedules component pods. Use these settings to place pods on specific nodes, tolerate node taints, or spread pods across failure domains such as availability zones. ## Configure scheduling values Each of the following components supports `nodeSelector`, `tolerations`, and `affinity` values that map directly to the corresponding [Kubernetes pod spec fields](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/): - `orchestration` - `identity` - `optimize` - `connectors` - `camundaHub.restapi` - `camundaHub.websockets` `global.nodeSelector` applies a node selector to all components that don't set their own. By default, the chart configures a hard `podAntiAffinity` rule for the Orchestration Cluster so that no two broker pods are scheduled on the same node. ## Spread Camunda Hub pods across availability zones Use a stable Pod label you control to spread Camunda Hub replicas without depending on labels managed by the Helm chart. Set the same label key and value in `podLabels` and `affinity`: ```yaml camundaHub: enabled: true restapi: replicas: 2 podLabels: scheduling.example.com/affinity-group: camunda-hub-restapi affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: scheduling.example.com/affinity-group: camunda-hub-restapi topologyKey: topology.kubernetes.io/zone ``` Apply the same pattern to `camundaHub.websockets`. If you're upgrading from 8.9, [move existing `webModeler.*` overrides to `camundaHub.*`](/self-managed/upgrade/helm/890-to-8100.md#consolidate-console-and-web-modeler-into-camunda-hub). The same `podLabels` and `affinity` pairing applies to every component listed in [Configure scheduling values](#configure-scheduling-values). Prefer a label you own over chart-managed labels such as `app.kubernetes.io/component`, because their values can change between chart versions. For the Orchestration Cluster, overriding `orchestration.affinity` replaces the default hard `podAntiAffinity` rule. To spread broker pods across zones, use [`orchestration.topologySpreadConstraints`](#spread-orchestration-cluster-pods-across-availability-zones) instead. Consider the following when you configure Pod anti-affinity: - Use a label key with a DNS prefix you control. If multiple Helm releases share a namespace, choose a label value unique to each component and release because Pod affinity selectors use the current namespace by default. - Ensure every eligible node has the label named by `topologyKey`. Managed cloud clusters normally set `topology.kubernetes.io/zone`. - A preferred rule is best effort and can colocate Pods when no better placement is available. A required rule can leave Pods `Pending` or stall rolling updates when the cluster lacks capacity in enough topology domains. - Avoid inter-Pod affinity and anti-affinity for clusters larger than several hundred nodes because they add substantial scheduler processing. ## Spread Orchestration Cluster pods across availability zones The default `podAntiAffinity` rule ensures broker pods run on distinct nodes, but does not ensure those nodes are in different zones: if the cluster has more nodes than brokers, all brokers can still be scheduled into a single availability zone. Because broker persistent volumes are bound to a single zone on most cloud providers, a zonal outage can then take down the whole Orchestration Cluster. With `orchestration.topologySpreadConstraints`, you can spread broker pods across zones. The value is a list of [Kubernetes topology spread constraints](https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/) applied to the Orchestration Cluster StatefulSet pods, and it is empty by default. Those pods run the Zeebe broker and gateway in the same process, so this value covers both. It does not affect separately deployed components such as Identity, Optimize, Connectors, or Camunda Hub, which have no equivalent topology spread value in the current chart version — use their `affinity` values instead. ```yaml orchestration: topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: ScheduleAnyway labelSelector: matchLabels: app.kubernetes.io/component: zeebe-broker ``` Keep the following in mind when configuring topology spread constraints: - Set a `topologyKey` that your nodes carry. `topology.kubernetes.io/zone` is standard on managed cloud clusters, but bare-metal and local clusters often have no zone label. With `whenUnsatisfiable: DoNotSchedule` and no matching node label, no broker can be scheduled at all. - Prefer `whenUnsatisfiable: ScheduleAnyway`. It provides best-effort spreading: Kubernetes does not guarantee an even distribution and does not rebalance existing brokers. A hard constraint (`DoNotSchedule`) combined with the default hard `podAntiAffinity` can leave broker pods permanently `Pending` when zones have uneven node counts, or stall a rolling update — use it only when every zone has enough spare capacity. - Broker volumes pin pods to a zone. With topology-constrained storage, `volumeBindingMode: WaitForFirstConsumer` delays volume binding or provisioning until the scheduler picks a node, so the volume matches that node's topology; `Immediate` binds or provisions the volume without considering pod scheduling constraints. Once a broker's claim is bound to a single-zone volume, every replacement pod must run in that zone: a hard zone constraint that conflicts with the volume's zone leaves the pod `Pending`, and enabling spreading on an existing cluster does not relocate existing volumes. - The `labelSelector` counts pods across the whole namespace. Pods with matching labels from all Helm releases in the namespace are counted together, not only the release you are configuring. To scope spreading to a single release, also match `app.kubernetes.io/instance: `. Topology spread constraints control where Kubernetes schedules broker pods. They do not control where the application places partition replicas among brokers — configure [zone-aware clusters](/self-managed/components/orchestration-cluster/zeebe/configuration/zone-aware-clusters.md) for that. Configure both together: topology spread constraints put each broker pod in the zone Kubernetes actually schedules it into, and zone awareness places partition replicas according to each broker's assigned zone. For broader production hardening guidance, see the [production installation guide](/self-managed/deployment/helm/install/production/index.md#topology-spread-constraints). --- ## Install Helm chart in air-gapped environments The [Camunda Helm chart](/self-managed/deployment/helm/install/quick-install.md) supports installation in air-gapped environments. By default, Docker images are pulled from Docker Hub. Because the chart depends on third-party images and charts, additional steps are required to make all charts available in your environment. ## Prerequisites - A private Docker registry accessible from your air-gapped environment - A private or local Helm chart repository - Access to a connected environment to pull the required Camunda and infrastructure images - [Helm CLI](https://helm.sh/docs/intro/install/) installed - `kubectl` access to your Kubernetes cluster ## Configuration ### List required images The Docker images required for your Helm release depend on your `values.yaml`. To list the required images, run the following command: ```shell helm repo add camunda https://helm.camunda.io helm repo update helm template camunda/camunda-platform -f values.yaml | grep 'image:' ``` ### Required Docker images The following images must be available in your air-gapped environment: **Camunda images:** - [camunda/camunda](https://hub.docker.com/r/camunda/camunda) - [camunda/optimize](https://hub.docker.com/r/camunda/optimize) - [camunda/connectors-bundle](https://hub.docker.com/r/camunda/connectors-bundle) - [camunda/identity](https://hub.docker.com/r/camunda/identity) **Optional components:** - [Camunda Hub images](/self-managed/deployment/docker/docker.md#docker-images-and-configuration-references): - [camunda/hub](https://hub.docker.com/r/camunda/hub) - [camunda/hub-websockets](https://hub.docker.com/r/camunda/hub-websockets) **Infrastructure images:** In Camunda 8.10, the Helm chart no longer bundles infrastructure: the Bitnami subcharts for PostgreSQL, Elasticsearch, and Keycloak are removed. Provide these through managed services or Kubernetes operators, and mirror the images each one requires into your private registry, following the operator or managed-service documentation. See [operator-based infrastructure](/self-managed/deployment/helm/configure/operator-based-infrastructure.md). Skip this step if your managed infrastructure services don't require images in your private registry. :::note For the air-gapped procedure based on the bundled Bitnami subcharts (Camunda 8.9 and earlier), see the [8.9 air-gapped guide](https://docs.camunda.io/docs/8.9/self-managed/deployment/helm/configure/registry-and-images/air-gapped-installation/). ::: A helper script is available in the [camunda-helm-repository](https://github.com/camunda/camunda-platform-helm/blob/c6a6e0c327f2acb8746802fbe03b3774b8284de3/scripts/download-chart-docker-images.sh) to pull and save the Camunda Docker images. ### Access Camunda images from the Camunda registry All required Camunda images published on Docker Hub are also available in the Camunda registry: - `registry.camunda.cloud/camunda/` For example, you can pull the Camunda image from Docker Hub or the Camunda registry: ```shell docker pull camunda/camunda: docker pull registry.camunda.cloud/camunda/camunda: ``` ### Required Helm charts The [Camunda Helm chart](https://artifacthub.io/packages/helm/camunda/camunda-platform) must be available in your air-gapped environment. Download it from [GitHub](https://github.com/camunda/camunda-platform-helm/releases) or run: ```shell helm repo add camunda https://helm.camunda.io helm repo update helm pull camunda/camunda-platform ``` If you deploy infrastructure (PostgreSQL, Elasticsearch, Keycloak) with Kubernetes operators, also make the operator Helm charts and images available in your air-gapped environment, following their documentation. Managed services run outside the cluster, so they need nothing mirrored. Install the Helm chart by either making it available in a [private repository](https://helm.sh/docs/topics/chart_repository/) that can be accessed from the air-gapped environment or providing the downloaded chart archive locally, for example: ```shell helm install camunda ./camunda-platform-.tgz ``` For supported versions, see [supported environments](/reference/supported-environments.md#camunda-8-self-managed) and the [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md). ### Push Docker images to a repository Push all [required Docker images](#required-docker-images) to your repository: 1. Tag the image: ```shell docker tag example.jfrog.io/camunda/: ``` 1. Push the image: ```shell docker push example.jfrog.io/camunda/: ``` ### Deploy Helm charts to a repository You must deploy the [required Helm charts](#required-helm-charts) to your repository. For hosting options, see the [chart repository guide](https://helm.sh/docs/topics/chart_repository). #### Add a Helm repository To use the chart, add your Helm chart repository: ```shell helm repo add camunda https://example.jfrog.io/artifactory/api/helm/camunda-platform helm repo update ``` #### Override Helm chart values You can override the image registry and tag in a custom `values.yaml` file: ```yaml global: image: registry: example.jfrog.io pullSecrets: - name: registry-credentials orchestration: image: repository: camunda/camunda tag: identity: image: repository: camunda/identity optimize: image: repository: camunda/optimize connectors: image: repository: camunda/connectors-bundle camundaHub: image: # registry and tag will be used for both Camunda Hub images tag: restapi: image: repository: camunda/hub websockets: image: repository: camunda/hub-websockets ``` The `pullSecrets` value references a Kubernetes Secret with your registry credentials. To create the Secret, see [pull images from a private registry](./index.md#pull-images-from-a-private-registry). If your registry doesn't require credentials, remove `pullSecrets`. #### Deploy Camunda with custom values Finally, deploy Camunda with Helm using the custom values file: ```shell helm install camunda camunda/camunda-platform --version $HELM_CHART_VERSION -f values.yaml ``` ## Best practices - **Mirror images and charts regularly**: Sync images and charts on a schedule to avoid version drift. - **Pin versions**: Use explicit tags instead of `latest` to ensure reproducibility. - **Validate charts**: Test Helm charts in a staging air-gapped environment before production. - **Monitor dependencies**: Track Camunda and infrastructure dependency updates, since they affect required images. - **Secure access**: Restrict permissions to your private registry and Helm repository. --- ## Configure registry and images This section explains how to adjust registry and image sources for production setups, including working in air-gapped environments. ## Global and component image values The `global.image` values apply to all components. The `image` values of a component apply to that component only. In the following table, replace `` with the values key of a component. See [image values of each component](#image-values-of-each-component). | Value | Default | Description | | ------------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `global.image.registry` | `""` | Registry for the images of all components. | | `.image.registry` | `""` | Registry for the image of one component. This value replaces `global.image.registry` for this component. | | `.image.repository` | Depends on the component | Repository of the image of one component, without the registry. | | `.image.tag` | Depends on the component | Tag of the image of one component. | | `.image.digest` | `""` | Digest of the image of one component, for example `sha256:`. If you set a digest, the chart ignores `.image.tag`. | | `global.image.pullSecrets` | `[]` | List of Kubernetes Secrets that the pods of all components use to pull images. | | `.image.pullSecrets` | `[]` | List of Kubernetes Secrets that the pod of one component uses to pull images. This value replaces `global.image.pullSecrets` for this component. | | `global.image.pullPolicy` | `Always` | Image pull policy of the container of each component. Valid values are `Always`, `IfNotPresent`, and `Never`. You can't set a different pull policy for one component. | A component value replaces the global value for that component. If a component value is empty, the chart uses the global value. The chart doesn't merge the two `pullSecrets` lists. ## Image values of each component Each component has its own `image` values. Use the values key in the following table in place of ``. | Component | Values key | Default repository | | --------------------- | --------------- | --------------------------- | | Orchestration Cluster | `orchestration` | `camunda/camunda` | | Connectors | `connectors` | `camunda/connectors-bundle` | | Management Identity | `identity` | `camunda/identity` | | Optimize | `optimize` | `camunda/optimize` | ### Camunda Hub image values Camunda Hub runs two images, so its image values use a different layout. The REST API image and the WebSockets image share the registry, tag, and pull secrets. Each image has its own repository and digest. | Value | Description | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | `camundaHub.image.registry` | Registry for both Hub images. This value replaces `global.image.registry` for both images. | | `camundaHub.image.tag` | Tag of both Hub images. | | `camundaHub.image.pullSecrets` | List of Kubernetes Secrets that both Hub pods use to pull images. This value replaces `global.image.pullSecrets` for both pods. | | `camundaHub.restapi.image.repository` | Repository of the REST API image. The default is `camunda/hub`. | | `camundaHub.restapi.image.digest` | Digest of the REST API image. If you set a digest, the chart ignores `camundaHub.image.tag` for this image. | | `camundaHub.websockets.image.repository` | Repository of the WebSockets image. The default is `camunda/hub-websockets`. | | `camundaHub.websockets.image.digest` | Digest of the WebSockets image. If you set a digest, the chart ignores `camundaHub.image.tag` for this image. | ## Pull images from a private registry To pull images from a private registry, store your registry credentials in a Kubernetes Secret and add the Secret to your Helm values. 1. Create a `docker-registry` Secret in the namespace where you install the chart. Pods can only use pull secrets from their own namespace. ```shell kubectl create secret docker-registry registry-credentials \ --docker-server=example.jfrog.io \ --docker-username= \ --docker-password= \ --namespace ``` 1. Set your registry and the Secret in `global.image`: ```yaml global: image: registry: example.jfrog.io pullSecrets: - name: registry-credentials ``` 1. (Optional) Set the `image` values of a component to use a different registry or Secret for that component: ```yaml connectors: image: registry: other.example.com pullSecrets: - name: connectors-registry-credentials ``` --- ## Run custom connectors in Helm charts You can deploy a custom connector in your Helm Kubernetes cluster along with the connectors bundle. The default runtime loads connectors from the classpath using the Java Service Provider Interface (SPI). For the custom connectors, there is a dedicated folder in the Connectors Docker image `/opt/custom`. Any JAR placed in this folder is included in the runtime classpath. This page explains how to place your custom connector JAR in `/opt/custom`. ## Prerequisites - A custom connector built as a **fat JAR** (JAR with dependencies). For details on creating and building custom connectors, see [Connector SDK](/components/connectors/custom-built-connectors/connector-sdk.md). Example JAR name used in this guide: `custom-connector-0.0.1-with-dependencies.jar` - A hosting location accessible by Helm during installation. Example path used in this guide: `https://my.host:80/dist/custom-connector-0.0.1-with-dependencies.jar` ## Configure the Helm chart Update the values of the [Camunda Helm charts](https://artifacthub.io/packages/helm/camunda/camunda-platform#parameters) to download the JAR into `/opt/custom` before the connectors runtime starts: ```yaml connectors: initContainers: - name: init-script-downloader image: appropriate/curl securityContext: runAsUser: 1000 runAsNonRoot: true args: - "-o" - "/opt/custom/custom-connector-0.0.1-with-dependencies.jar" - "https://my.host:80/dist/custom-connector-0.0.1-with-dependencies.jar" volumeMounts: - name: init-script mountPath: /opt/custom extraVolumes: - name: init-script emptyDir: {} extraVolumeMounts: - mountPath: /opt/custom/custom-connector-0.0.1-with-dependencies.jar name: init-script subPath: custom-connector-0.0.1-with-dependencies.jar ``` After updating the values, run [Helm install](/self-managed/deployment/helm/install/quick-install.md#orchestration-cluster-only) as usual. :::note The `appropriate/curl` image is not the only image option for the `initContainers`. You can use other `curl`-based images, such as `curlimages/curl`. Adjust the `args` to match the image you choose. On clusters that enforce non-root containers (for example, restricted Pod Security admission), keep the `securityContext` in the init container to avoid startup failures. ::: ## Troubleshooting If your custom connector does not start: - Verify that your connector JAR is present in the `/opt/custom` folder in the pod. - Confirm that the original connector JAR matches the one in `/opt/custom`. A file size check is often sufficient, but you can also compare checksums if needed. --- ## Helm charts secret management This guide provides an overview for configuring and managing secrets when using the official Helm chart. ## Secret configuration patterns The Helm chart supports different patterns for secret management. ### Structured secret pattern The structured `secret:` configuration under components provides three options: - `inlineSecret`: Plain-text value for non-production usage - `existingSecret`: Reference to an existing Kubernetes Secret name - `existingSecretKey`: Key within the existing secret object Example: ```yaml component: auth: secret: inlineSecret: "my-plain-text-secret" # Non-production only existingSecret: "my-secret-name" # Recommended existingSecretKey: "secret-key" ``` ## Application secrets These secrets are used by Camunda applications and external integrations. Configure them manually when using external secrets. ### Secrets using the structured pattern | **Secret** | **Chart values key** | **Type** | **Purpose** | | ----------------------------------------------- | --------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------- | | **Enterprise License Key** | `global.license.secret` | Internal | Camunda Enterprise license key | | **Identity First User Password** | `identity.firstUser.secret` | Internal | Default user password (`demo/demo`) | | **OAuth Client Secret (Admin)** | `global.identity.auth.admin.secret` | Internal | OAuth admin client secret for administrative operations | | **OAuth Client Secret (Connectors)** | `connectors.security.authentication.oidc.secret` | Internal | OAuth client secret for connectors | | **OAuth Client Secret (Orchestration)** | `orchestration.security.authentication.oidc.secret` | Internal | OAuth client secret for Orchestration Cluster | | **OAuth Client Secret (Optimize)** | `global.identity.auth.optimize.secret` | Internal | OAuth client secret for Optimize | | **Identity External Database Password** | `identity.externalDatabase.secret` | External | Password for external PostgreSQL when using an external database for Identity | | **Camunda Hub External Database Password** | `camundaHub.restapi.externalDatabase.secret` | External | Password for external PostgreSQL when using an external database for Camunda Hub | | **SMTP Password** | `camundaHub.restapi.mail.secret` | External | SMTP credentials for sending email notifications | | **RDBMS Auth** | `orchestration.data.secondaryStorage.rdbms.secret` | External | Password for external RDBMS authentication (Basic authentication) | | **External Elasticsearch Auth (Orchestration)** | `orchestration.data.secondaryStorage.elasticsearch.auth.secret` | External | Password for external Elasticsearch authentication (Basic authentication) | | **External OpenSearch Auth (Orchestration)** | `orchestration.data.secondaryStorage.opensearch.auth.secret` | External | Password for external OpenSearch authentication (Basic authentication) | | **External Elasticsearch Auth (Optimize)** | `optimize.database.elasticsearch.auth.secret` | External | Password for external Elasticsearch authentication (Basic authentication) | | **External OpenSearch Auth (Optimize)** | `optimize.database.opensearch.auth.secret` | External | Password for external OpenSearch authentication (Basic authentication) | ## How to configure secrets Secrets can be configured in different ways: - Use the structured `secret:` pattern with `inlineSecret` for non-production or external Kubernetes Secrets for production ### Method 1: Inline secrets (non-production only) For development or testing environments, provide secrets directly in your `values.yaml` using the `inlineSecret` field: ```yaml global: license: secret: inlineSecret: "my-license-key-here" identity: firstUser: secret: inlineSecret: "demo-password" ``` ### Method 2: External Kubernetes secrets (recommended) For production environments, create a Kubernetes Secret and reference it from your `values.yaml`. #### Step 1: Create the secret Create a secret using `kubectl` or a YAML manifest: ```sh kubectl create secret generic optimize-secret \ --from-literal=client-secret=camundapassword \ --namespace camunda ``` Or using YAML: ```yaml apiVersion: v1 kind: Secret metadata: name: optimize-secret namespace: camunda type: Opaque stringData: client-secret: "camundapassword" ``` #### Step 2: Reference in `values.yaml` **For components using the structured pattern:** ```yaml global: identity: auth: optimize: secret: existingSecret: "optimize-secret" existingSecretKey: "client-secret" ``` :::note Auto-generated secrets (`global.secrets.autoGenerated`) are no longer supported starting in Camunda 8.9. Create your secrets explicitly using [inline secrets](#method-1-inline-secrets-non-production-only) for non-production environments or [external Kubernetes secrets](#method-2-external-kubernetes-secrets-recommended) for production environments. ::: ## Document Store secrets Document Store secrets use the structured `secret:` pattern with separate secret configurations for each credential component. | **Secret** | **Chart values key** | **Purpose** | **Required when** | | ------------------------------------------ | --------------------------------------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------- | | **AWS Document Store Access Key ID** | `global.documentStore.type.aws.accessKeyId.secret` | AWS access key ID for S3 document storage authentication | Using AWS S3 with IAM authentication | | **AWS Document Store Secret Access Key** | `global.documentStore.type.aws.secretAccessKey.secret` | AWS secret access key for S3 document storage authentication | Using AWS S3 with IAM authentication | | **GCP Document Store Service Account** | `global.documentStore.type.gcp.secret` | GCP service account JSON for GCS document storage authentication | Using GCP Cloud Storage | | **Azure Document Store Connection String** | `global.documentStore.type.azure.connectionString.secret` | Azure Storage connection string for Blob document storage authentication | Using Azure Blob Storage with connection string auth | :::note For Azure Blob Storage with DefaultAzureCredential (managed identities and Workload Identity), the connection string secret is not required. ::: ### Credential precedence with IRSA The AWS SDK resolves credentials through its [default credential provider chain](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/credentials-chain.html), which reads the static `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables before the IRSA web identity token. Because the chart injects these variables from the access key secrets above whenever the AWS document store is enabled, their presence takes precedence and prevents [IRSA](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/irsa.md#document-store-s3) from being used, even when the service account is annotated with an IAM role. To authenticate with IRSA instead, set `global.documentStore.type.aws.irsa.enabled` to `true`. The chart then skips injecting the static credentials, and the AWS access key secrets above are not required. ## TLS certificates TLS certificate secrets for Camunda components and external services. :::note Migrating from legacy TLS secret configuration The structured `secret:` pattern for TLS certificates was introduced in Camunda 8.9. If you are upgrading from an earlier version and using legacy TLS secret fields (such as `global.elasticsearch.tls.existingSecret`), see the [8.8 secret management guide](/versioned_docs/version-8.8/self-managed/deployment/helm/configure/secret-management.md) for migration instructions. ::: ### TLS certificate secrets | **Secret** | **Chart values key** | **Purpose** | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | | **Console TLS Certificate** | `console.tls.secret` | TLS certificate for Console web application | | **External Elasticsearch TLS Cert** | `orchestration.data.secondaryStorage.elasticsearch.tls.secret` / `optimize.database.elasticsearch.tls.secret` | TLS certificate for external Elasticsearch over SSL | | **External OpenSearch TLS Cert** | `orchestration.data.secondaryStorage.opensearch.tls.secret` / `optimize.database.opensearch.tls.secret` | TLS certificate for external OpenSearch over SSL | **TLS Certificate Configuration**: Unlike password-based secrets, TLS certificates do not support `inlineSecret` (certificates are binary files unsuitable for inline configuration). For Elasticsearch and OpenSearch, both `existingSecret` and `existingSecretKey` are required to specify which key in the secret contains the certificate file. For Console, only `existingSecret` is required as the entire secret is mounted as a directory. Create the secrets with your certificate files using `kubectl create secret generic`: ```sh kubectl create secret generic \ --from-file== \ --namespace camunda ``` Reference them in your values: ```yaml # Elasticsearch/OpenSearch orchestration: data: secondaryStorage: type: elasticsearch elasticsearch: tls: secret: existingSecret: elasticsearch-tls-secret existingSecretKey: externaldb.jks # Console console: tls: enabled: true secret: existingSecret: console-tls-secret certKeyFilename: ca.crt ``` ### Ingress TLS Configure TLS for Camunda services exposed via Ingress: ```yaml global: ingress: tls: enabled: true secretName: camunda-platform ``` ## Extract plaintext values and reference them as Kubernetes Secrets Use this guide if any sensitive values (client secrets, DB passwords, etc.) are written directly as plaintext in your values.yaml. The production best practice is to store these in Kubernetes Secrets and reference them from your values. _Note: If your chart already references Kubernetes Secrets (and not plaintext), no change is needed—just ensure each existingSecret/existingSecretKey points to the correct secret and key._ **What you need to do** 1. Find any plaintext secrets in your `values.yaml`. 1. Create a Kubernetes secret that stores those values. 1. Reference that secret in your Helm values file (replace inline plaintext literals). :::tip You can use a single consolidated secret (e.g., app-credentials) or one secret per component. Consolidated keeps things tidy; per-component can be clearer for ownership/rotation. Choose what fits your operations model. ::: ### Find any plaintext secrets in your `values.yaml` #### A - If the secrets already exist in Kubernetes You can read the current (base64-encoded) data from existing secrets and reuse it in your new consolidated secret. ```bash # Adjust to your release name / namespace RELEASE_NAME=camunda RELEASE_NAMESPACE=camunda # Examples of extracting values from existing secrets (jsonpath reads base64 data) export SOME_CLIENT_SECRET=$( kubectl -n "$RELEASE_NAMESPACE" get secret "${RELEASE_NAME}-some-app-secret" \ -o jsonpath="{.data.client-secret}" | base64 --decode ) export DB_ADMIN_PASSWORD=$( kubectl -n "$RELEASE_NAMESPACE" get secret "${RELEASE_NAME}-postgresql" \ -o jsonpath="{.data.postgres-password}" | base64 --decode ) ``` Repeat for each value you want to consolidate. #### B - If they only exist in values.yaml `values.yaml` Copy those literal strings into environment variables (locally), then proceed to create a Kubernetes secret from them: ```bash # Example: pulling from your own notes or from a secure password manager export SOME_CLIENT_SECRET="paste-the-current-client-secret" export DB_ADMIN_PASSWORD="paste-the-current-db-admin-password" ``` ### Create a secret Create one secret with your values (example: `app-credentials`): ```bash kubectl -n "$RELEASE_NAMESPACE" create secret generic app-credentials --from-literal=some-app-client-secret="$SOME_CLIENT_SECRET" --from-literal=db-admin-password="$DB_ADMIN_PASSWORD" ``` ### Reference it in your values file Replace plaintext values with secret references. ```yaml someApp: auth: secret: existingSecret: "app-credentials" existingSecretKey: "some-app-client-secret" database: auth: existingSecret: "app-credentials" secretKeys: adminPasswordKey: "db-admin-password" ``` _Note: Remove any old plaintext values so the chart doesn’t override the secret._ ### Hands-on example (Camunda setup) Below is a ready-to-use example for Camunda deployments. Keep only those secrets that match your setup. #### 1. Extract (only for enabled components) ```shell # Change this according to your Helm chart release/deployment name and namespace. RELEASE_NAME=camunda-dev RELEASE_NAMESPACE=camunda-dev # "global.identity.auth.enabled: true" is assumed for all "IDENTITY_*_CLIENT_SECRET" values. # Only if "connectors.enabled: true". export IDENTITY_CONNECTORS_CLIENT_SECRET=$(kubectl get secret "${RELEASE_NAME}-connectors-identity-secret" -o jsonpath="{.data.connectors-secret}" | base64 --decode) # Only if "optimize.enabled: true". export IDENTITY_OPTIMIZE_CLIENT_SECRET=$(kubectl get secret "${RELEASE_NAME}-optimize-identity-secret" -o jsonpath="{.data.optimize-secret}" | base64 --decode) # Only if "zeebe.enabled: true". export IDENTITY_ZEEBE_CLIENT_SECRET=$(kubectl get secret "${RELEASE_NAME}-zeebe-identity-secret" -o jsonpath="{.data.zeebe-secret}" | base64 --decode) # Only if connecting to Elasticsearch. Replace the placeholders with your current passwords. export ORCHESTRATION_ELASTICSEARCH_SECRET="paste-the-current-orchestration-elasticsearch-password" export OPTIMIZE_ELASTICSEARCH_SECRET="paste-the-current-optimize-elasticsearch-password" # Only if connecting to OpenSearch. Replace the placeholders with your current passwords. export ORCHESTRATION_OPENSEARCH_SECRET="paste-the-current-orchestration-opensearch-password" export OPTIMIZE_OPENSEARCH_SECRET="paste-the-current-optimize-opensearch-password" # Only if connecting to RDBMS. Replace the placeholder with your current password. export ORCHESTRATION_RDBMS_SECRET="paste-the-current-orchestration-rdbms-password" ``` #### 2. Create the consolidated secret ```shell cat << EOF >> existing-secrets-manifest.yaml --- apiVersion: v1 kind: Secret metadata: name: camunda-credentials namespace: "${RELEASE_NAMESPACE}" type: Opaque stringData: # Only if "connectors.enabled: true". identity-connectors-client-token: "${IDENTITY_CONNECTORS_CLIENT_SECRET}" # Only if "optimize.enabled: true". identity-optimize-client-token: "${IDENTITY_OPTIMIZE_CLIENT_SECRET}" # Only if "orchestration.enabled: true". identity-orchestration-client-token: "${IDENTITY_ZEEBE_CLIENT_SECRET}" # Only if connecting to Elasticsearch orchestration-elasticsearch-password: "${ORCHESTRATION_ELASTICSEARCH_SECRET}" optimize-elasticsearch-password: "${OPTIMIZE_ELASTICSEARCH_SECRET}" # Only if connecting to OpenSearch orchestration-opensearch-password: "${ORCHESTRATION_OPENSEARCH_SECRET}" optimize-opensearch-password: "${OPTIMIZE_OPENSEARCH_SECRET}" # Only if connecting to RDBMS orchestration-rdbms-password: "${ORCHESTRATION_RDBMS_SECRET}" EOF ``` Review `existing-secrets-manifest.yaml` and ensure it contains exactly the secrets for the components enabled in your deployment. ```shell kubectl apply -n "${RELEASE_NAMESPACE}" -f existing-secrets-manifest.yaml ``` #### 3. Reference the secret in your values file If a component already uses its own existing secret, make sure to remove that section from the configuration to avoid overriding it: ```yaml # existing-secrets-values.yaml global: identity: auth: optimize: secret: existingSecret: "camunda-credentials" existingSecretKey: "identity-optimize-client-token" connectors: security: authentication: oidc: secret: existingSecret: "camunda-credentials" existingSecretKey: "identity-connectors-client-token" orchestration: data: secondaryStorage: rdbms: secret: existingSecret: "camunda-credentials" existingSecretKey: "orchestration-rdbms-password" elasticsearch: auth: secret: existingSecret: "camunda-credentials" existingSecretKey: "orchestration-elasticsearch-password" opensearch: auth: secret: existingSecret: "camunda-credentials" existingSecretKey: "orchestration-opensearch-password" security: authentication: oidc: secret: existingSecret: "camunda-credentials" existingSecretKey: "identity-orchestration-client-token" optimize: database: elasticsearch: auth: secret: existingSecret: "camunda-credentials" existingSecretKey: "optimize-elasticsearch-password" opensearch: auth: secret: existingSecret: "camunda-credentials" existingSecretKey: "optimize-opensearch-password" ``` Then upgrade your deployment via: ```bash helm upgrade --install "$RELEASE_NAME" camunda/camunda-platform \ -n "$RELEASE_NAMESPACE" \ -f existing-secrets-values.yaml ``` --- ## Configure Kubernetes Service ports The Camunda Helm chart exposes an `appProtocols` value per component that sets the Kubernetes [`appProtocol`](https://kubernetes.io/docs/concepts/services-networking/service/#application-protocol) field on a Service port. Use this when your infrastructure needs an explicit protocol hint instead of relying on protocol sniffing. For example, a GKE NEG-backed Ingress or Gateway handling HTTP/2 cleartext (`h2c`) gRPC traffic to the orchestration cluster gateway. ## `appProtocol` per Service port Each component's `service.appProtocols` value accepts a map of logical port key to `appProtocol` value. It's empty by default and doesn't change existing behavior until you set it. The following components support `appProtocols` (the accepted logical port keys for each component are listed in the last column). These keys are fixed and don't change if you override a component's `*Name` value (for example, `orchestration.service.grpcName`). Always use the logical key (`grpc`), not the renamed Service port. Setting a key outside the accepted list fails the Helm render instead of being silently ignored. | Component | Value key | Accepted logical port keys | | --------------------- | -------------------------------------------- | --------------------------------------------------- | | Orchestration cluster | `orchestration.service.appProtocols` | `management`, `internal`, `command`, `http`, `grpc` | | Identity | `identity.service.appProtocols` | `http`, `metrics` | | Optimize | `optimize.service.appProtocols` | `http`, `management` | | Connectors | `connectors.service.appProtocols` | `server` | | Hub REST API | `camundaHub.restapi.service.appProtocols` | `http`, `http-management` | | Hub WebSockets | `camundaHub.websockets.service.appProtocols` | `http` | Example: set the orchestration cluster gRPC gateway port to use `kubernetes.io/h2c` for HTTP/2 cleartext framing: ```yaml orchestration: service: appProtocols: grpc: kubernetes.io/h2c ``` --- ## Configure TLS ## What's covered | Connection | Mechanism | | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Camunda components → Elasticsearch (private CA, self-hosted or AWS) | `global.tls.caBundle` | | Camunda components → OpenSearch (private CA, self-hosted or AWS-managed) | `global.tls.caBundle` | | Camunda components → PostgreSQL JDBC (`sslmode=verify-full` + CA) | `global.tls.caBundle` + JDBC URL | | Camunda components → external OIDC issuer with private CA (Entra, Okta, internal Keycloak) | `global.tls.caBundle` | | Browser / external client → Ingress / GatewayAPI (UI, gRPC) | Standard Kubernetes Ingress TLS — configured via per-component `*.ingress.tls` or `global.gateway.tls`, _not_ `global.tls.caBundle`. See [Ingress configuration](./ingress/ingress-setup.md). | In-cluster pod-to-pod traffic isn't covered by this overlay. See [Encrypt in-cluster transport](#in-cluster-transport-service-mesh-required). ## How it works Camunda components span three trust ecosystems that each require a different CA input format: | Runtime | Components | Trust input | | ------------------- | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | | OS / OpenSSL native | libcurl, Go `crypto/x509`, OpenSearch native client (post-8.6.7), PostgreSQL JDBC `sslrootcert=` | PEM via `SSL_CERT_FILE` | | JVM | Operate, Tasklist, Optimize, Camunda Hub restapi (includes Console), Identity, Connectors, Zeebe broker | PKCS12/JKS keystore via `-Djavax.net.ssl.trustStore=` | | Node.js | Camunda Hub websockets | PEM via `NODE_EXTRA_CA_CERTS` | The `values-tls.yaml` overlay bridges all three from a single PEM bundle: 1. Mounts the bundle at `/etc/camunda/tls/ca.crt`. 2. Sets `SSL_CERT_FILE` and `NODE_EXTRA_CA_CERTS` to that path on every component. 3. Runs a per-JVM-component init container that copies the JRE's `cacerts` (PKCS12 on Java 21) into a shared `emptyDir` and imports each certificate in the bundle via `keytool -importcert`. Override `global.tls.caBundle.image` only if a component image lacks `keytool`. 4. Prepends `JAVA_TOOL_OPTIONS` with `-Djavax.net.ssl.trustStore=/var/camunda/tls-truststore/cacerts -Djavax.net.ssl.trustStorePassword=changeit`. :::caution `SSL_CERT_FILE` replaces the system bundle `SSL_CERT_FILE` _replaces_ (not appends to) the OS CA bundle for OpenSSL clients. Include all public CAs your components reach alongside your private CA: ```bash cat /etc/ssl/certs/ca-certificates.crt your-private-ca.pem > camunda-ca-bundle.pem ``` Python-based connector containers are an exception: `requests` reads `REQUESTS_CA_BUNDLE` / `CURL_CA_BUNDLE`, not `SSL_CERT_FILE`. Set `REQUESTS_CA_BUNDLE=/etc/camunda/tls/ca.crt` on those containers. ::: ## Quickstart ### Prerequisites - Helm CLI 3.10+ or 4.x - A PEM-encoded CA bundle file (`your-ca-bundle.pem`) containing the root and any intermediate certs that signed your datastore / IdP certs ### 1. Create the CA bundle Secret ```bash NAMESPACE=camunda kubectl create namespace "$NAMESPACE" --dry-run=client -o yaml | kubectl apply -f - kubectl -n "$NAMESPACE" create secret generic camunda-ca-bundle \ --from-file=ca.crt=./your-ca-bundle.pem ``` ### 2. Apply the overlay Download the overlay (ships in the chart repo, not in the `helm repo` cache): ```bash curl -fsSLO https://raw.githubusercontent.com/camunda/camunda-platform-helm/main/charts/camunda-platform-8.10/values-tls.yaml ``` Install or upgrade with the overlay: ```bash helm upgrade --install camunda camunda/camunda-platform \ --version 15.x \ --namespace "$NAMESPACE" \ -f values-tls.yaml \ -f your-values.yaml ``` `your-values.yaml` provides datastore URLs, credentials, and other scenario config. The TLS overlay is additive — it does not replace your existing values. Download the overlay from the same chart version you install. The `main` branch tracks the latest chart, so if you pin a specific chart release, fetch `values-tls.yaml` from the matching release tag instead of `main`. ### 3. Verify Confirm `SSL_CERT_FILE`, `JAVA_TOOL_OPTIONS` (with truststore path), and a `ca-bundle` volume appear in the pod spec: ```bash kubectl -n "$NAMESPACE" get pod -l app.kubernetes.io/component=zeebe-broker -o yaml | \ grep -A 1 'JAVA_TOOL_OPTIONS\|SSL_CERT_FILE\|ca-bundle' ``` See also [Verify no plaintext fallback](#verify-no-plaintext-fallback). ## Configuring datastore TLS `values-tls.yaml` ships commented templates for each datastore. Uncomment and fill in the section that matches your backend. ### Elasticsearch ```yaml orchestration: data: secondaryStorage: type: elasticsearch elasticsearch: url: "https://your-elasticsearch.example.com:9200" auth: username: elastic secret: existingSecret: "your-elasticsearch-credentials" existingSecretKey: password optimize: database: elasticsearch: enabled: true url: protocol: https host: "your-elasticsearch.example.com" port: 9200 auth: username: elastic secret: existingSecret: "your-elasticsearch-credentials" existingSecretKey: password ``` :::note The Zeebe ElasticsearchExporter uses its own auth env path (`ZEEBE_BROKER_EXPORTERS_ELASTICSEARCH_ARGS_AUTHENTICATION_USERNAME` / `_PASSWORD`). `secondaryStorage.elasticsearch.auth` does not fill it — set those env vars via `orchestration.env` if needed. ::: ### OpenSearch ```yaml orchestration: data: secondaryStorage: type: opensearch opensearch: url: "https://your-opensearch.example.com:9200" auth: username: admin secret: existingSecret: "your-opensearch-credentials" existingSecretKey: password optimize: database: opensearch: enabled: true aws: enabled: false # set true + remove auth.* for IRSA against AWS-managed OpenSearch url: protocol: https host: "your-opensearch.example.com" port: 9200 auth: username: admin secret: existingSecret: "your-opensearch-credentials" existingSecretKey: password ``` For AWS-managed OpenSearch with IRSA, leave `auth` unset and configure `aws.enabled: true` plus the appropriate service account annotations. ### PostgreSQL (RDBMS exporter) The CA is mounted at `/etc/camunda/tls/ca.crt` — reference it directly in the JDBC URL: ```yaml orchestration: data: secondaryStorage: type: rdbms rdbms: url: "jdbc:postgresql://your-postgres.example.com:5432/orchestration?sslmode=verify-full&sslrootcert=/etc/camunda/tls/ca.crt" username: camunda secret: existingSecret: "your-postgres-credentials" existingSecretKey: password ``` ### External OIDC issuer with private CA Only required when your IdP uses a private or internal CA. Public-CA issuers (Entra, Google) work without additional configuration. ```yaml global: identity: auth: issuerBackendUrl: "https://your-idp.example.com/realms/camunda" tokenUrl: "https://your-idp.example.com/realms/camunda/protocol/openid-connect/token" jwksUrl: "https://your-idp.example.com/realms/camunda/protocol/openid-connect/certs" authUrl: "https://your-idp.example.com/realms/camunda/protocol/openid-connect/auth" ``` ## cert-manager integration Issue the CA bundle Secret via a `Certificate` resource and reference it as `global.tls.caBundle.secret.existingSecret`: ```yaml apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: camunda-ca-bundle namespace: camunda spec: secretName: camunda-ca-bundle issuerRef: name: your-internal-ca-issuer kind: ClusterIssuer commonName: camunda-ca isCA: true duration: 8760h renewBefore: 720h ``` Set `existingSecretKey` to match cert-manager's output key (`ca.crt` by default in `values-tls.yaml`). :::caution Include the full trust chain If your `ClusterIssuer` is signed by an offline root CA, cert-manager outputs only the issuing intermediate. Concatenate the offline root into the bundle before creating the Secret. PKIX validation needs a chain that ends at a root present in the bundle. Server certs for Elasticsearch, OpenSearch, and PostgreSQL must be issued separately via additional `Certificate` resources signed by the same `ClusterIssuer`. ::: ## Install-time guardrails The chart emits these as `[camunda][warning]` messages from its constraints template while rendering `helm install` / `helm upgrade`. They also appear in the `helm status ` NOTES output when `global.createReleaseInfo` is enabled. Keep them in mind — some are enforced as render-time errors, others are silent precedence rules you must account for in your values: - Per-component JKS overrides the bundle. If a component has a legacy `tls.secret` JKS configured, you must remove it to use the bundle. Legacy JKS fields take priority over `global.tls.caBundle`. - Bundle is trust, not encryption. `global.tls.caBundle` adds CA trust but does not enable TLS on a plaintext URL. Set the URL to `https://` (or set the JDBC `sslmode`). - `JAVA_TOOL_OPTIONS` in component `env` overrides the truststore flags. For Orchestration and Optimize, set it via `javaOpts` instead, which the chart appends its truststore flags to. Connectors, Identity, and Web Modeler restapi receive the truststore flags automatically through a dedicated `JAVA_TOOL_OPTIONS` env — do not put truststore flags in their `javaOpts` (Web Modeler restapi's `javaOpts` feeds `JAVA_OPTIONS`, a different variable, so it has no effect on trust). ## Verify no plaintext fallback ```bash curl -fsSLO https://raw.githubusercontent.com/camunda/camunda-platform-helm/main/scripts/check-no-plaintext-datastore.sh chmod +x check-no-plaintext-datastore.sh ./check-no-plaintext-datastore.sh \ --namespace "$NAMESPACE" \ --kube-context "$KUBE_CONTEXT" ``` Exit code 0 + `[no-plaintext-check] PASS` means no Camunda pod is talking plaintext to a known datastore. ## Updating the CA ```bash kubectl -n "$NAMESPACE" create secret generic camunda-ca-bundle \ --from-file=ca.crt=./new-ca-bundle.pem \ --dry-run=client -o yaml | kubectl -n "$NAMESPACE" apply -f - kubectl -n "$NAMESPACE" rollout restart \ statefulset,deployment \ -l app.kubernetes.io/part-of=camunda-platform ``` The init container re-runs on each pod start and imports the new CA into a fresh truststore. ### Optional: automatic rollout on `helm upgrade` Set `global.tls.caBundle.autoRollout: true` to stamp a `checksum/ca-bundle` annotation on Java pods so they automatically roll out when the CA Secret changes. :::caution Constraints - Requires `get` on Secrets in the release namespace — `lookup` fails with `Forbidden` without it. - Argo CD and Flux render via `helm template` (no cluster access), so the annotation stays constant. Drive restarts from your GitOps stack instead. ::: ## Remove a CA from the JVM truststore To stop the Java components from trusting a CA that is in the image's default truststore, replace the truststore. `global.tls.caBundle` can only add CAs. It can't remove a CA. The procedure creates a copy of the image truststore without the CA and stores the copy in a Secret. Then it points each Java component to the copy with `-Djavax.net.ssl.trustStore`. Use it as an interim measure until you upgrade to images that no longer contain the CA. Don't combine this procedure with `global.tls.caBundle`. The chart adds its own `-Djavax.net.ssl.trustStore` flag after yours, and the JVM uses the last one. If you need a private CA, import it into the new truststore in step 1 and remove `global.tls.caBundle` from your values. This procedure covers the Camunda Java components listed in step 3. It doesn't change the OS CA bundle or other components. ### 1. Build the truststore Copy the truststore from the Orchestration Cluster image that runs in your cluster. Replace `X` in the image tag with your patch version. A truststore from another image can contain other CAs. Then remove each certificate whose subject has the common name (CN) that you set. The example removes `emSign ECC Root CA - G3`: ```bash CAMUNDA_IMAGE=camunda/camunda:8.11.X REMOVE_CN='emSign ECC Root CA - G3' docker run --rm --entrypoint sh -e REMOVE_CN="$REMOVE_CN" "$CAMUNDA_IMAGE" -c \ 'keytool -list -v -cacerts -storepass changeit | grep -c -E "^Owner: (.*, )?CN=${REMOVE_CN}(,|$)"' ``` Make sure the image contains the CA. The command must return `1` or more. If it returns `0`, the image doesn't contain the CA and you don't need this procedure. Then build the truststore without the CA: ```bash mkdir -p truststore docker run --rm --user 0 --entrypoint sh \ -v "$PWD/truststore:/out" -e REMOVE_CN="$REMOVE_CN" \ "$CAMUNDA_IMAGE" -c ' keytool -importkeystore -noprompt \ -srckeystore "$JAVA_HOME/lib/security/cacerts" -srcstorepass changeit \ -destkeystore /out/cacerts -deststoretype PKCS12 -deststorepass changeit keytool -list -v -keystore /out/cacerts -storepass changeit \ | awk -v cn="CN=${REMOVE_CN}," "/^Alias name: /{alias=substr(\$0, 13)} /^Owner: / && index(\$0 \",\", cn) {print alias}" \ | while IFS= read -r alias; do keytool -delete -alias "$alias" -keystore /out/cacerts -storepass changeit done chmod 644 /out/cacerts' ``` Verify that the CA has been removed. The command must return `0`. `grep` exits with code `1` when it prints `0`: ```bash docker run --rm --entrypoint sh \ -v "$PWD/truststore:/in:ro" -e REMOVE_CN="$REMOVE_CN" \ "$CAMUNDA_IMAGE" -c 'keytool -list -v -keystore /in/cacerts -storepass changeit \ | grep -c -E "^Owner: (.*, )?CN=${REMOVE_CN}(,|$)"' ``` (Optional) Import your private CA: ```bash docker run --rm --user 0 --entrypoint sh \ -v "$PWD/truststore:/out" -v "$PWD/your-private-ca.pem:/ca.pem:ro" \ "$CAMUNDA_IMAGE" -c 'keytool -importcert -noprompt -alias your-private-ca \ -file /ca.pem -keystore /out/cacerts -storepass changeit' ``` The truststore contains public certificates only, so the default `changeit` password is not a secret. ### 2. Create the truststore Secret ```bash kubectl -n "$NAMESPACE" create secret generic camunda-jvm-truststore \ --from-file=cacerts=./truststore/cacerts --save-config ``` Create the Secret before you run `helm upgrade` in step 3. If the Secret doesn't exist, the pods stay in `ContainerCreating`. ### 3. Point the Java components to the truststore Mount the Secret on each Java component and set the truststore flags: | Component | Values key | Where to set the flags | | --------------------- | -------------------- | ------------------------------------------------------------- | | Orchestration Cluster | `orchestration` | `javaOpts`. Keep the chart's default options. | | Optimize | `optimize` | `javaOpts` | | Connectors | `connectors` | `JAVA_TOOL_OPTIONS` in `env` | | Management Identity | `identity` | `JAVA_TOOL_OPTIONS` in `env` | | Camunda Hub REST API | `camundaHub.restapi` | `JAVA_TOOL_OPTIONS` in `env`. `javaOpts` sets `JAVA_OPTIONS`. | ```yaml orchestration: javaOpts: >- -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/camunda/data -XX:ErrorFile=/usr/local/camunda/data/camunda_error%p.log -XX:+ExitOnOutOfMemoryError -Djavax.net.ssl.trustStore=/etc/camunda/jvm-truststore/cacerts -Djavax.net.ssl.trustStorePassword=changeit extraVolumes: &jvmTruststoreVolumes - name: jvm-truststore secret: secretName: camunda-jvm-truststore extraVolumeMounts: &jvmTruststoreMounts - name: jvm-truststore mountPath: /etc/camunda/jvm-truststore readOnly: true optimize: javaOpts: >- -Djavax.net.ssl.trustStore=/etc/camunda/jvm-truststore/cacerts -Djavax.net.ssl.trustStorePassword=changeit extraVolumes: *jvmTruststoreVolumes extraVolumeMounts: *jvmTruststoreMounts connectors: env: &jvmTruststoreEnv - name: JAVA_TOOL_OPTIONS value: >- -Djavax.net.ssl.trustStore=/etc/camunda/jvm-truststore/cacerts -Djavax.net.ssl.trustStorePassword=changeit extraVolumes: *jvmTruststoreVolumes extraVolumeMounts: *jvmTruststoreMounts identity: env: *jvmTruststoreEnv extraVolumes: *jvmTruststoreVolumes extraVolumeMounts: *jvmTruststoreMounts camundaHub: restapi: env: *jvmTruststoreEnv extraVolumes: *jvmTruststoreVolumes extraVolumeMounts: *jvmTruststoreMounts ``` If you have already set `javaOpts` or `env` for a component, merge these entries into your values. Run `helm upgrade` with the file. ### 4. Verify the truststore Make sure each Java process uses the new truststore. Each Java pod must return a `Picked up JAVA_TOOL_OPTIONS` line with `/etc/camunda/jvm-truststore/cacerts`: ```bash for pod in $(kubectl -n "$NAMESPACE" get pods -o name -l app.kubernetes.io/part-of=camunda-platform); do echo "$pod: $(kubectl -n "$NAMESPACE" logs "$pod" --all-containers | grep -m1 'Picked up JAVA_TOOL_OPTIONS')" done ``` The loop selects pods by the `app.kubernetes.io/part-of=camunda-platform` label. The output must show the flag for each Java component from step 3. Pods that don't run Java, such as the Camunda Hub `websockets` Pod, print an empty value. Run the loop after `kubectl rollout status` finishes for each workload. Pods that are shutting down also print an empty value. To update the truststore, rebuild it by following step 1. Then replace the Secret and restart the pods: ```bash kubectl -n "$NAMESPACE" create secret generic camunda-jvm-truststore \ --from-file=cacerts=./truststore/cacerts \ --dry-run=client -o yaml | kubectl -n "$NAMESPACE" apply -f - kubectl -n "$NAMESPACE" rollout restart statefulset,deployment \ -l app.kubernetes.io/part-of=camunda-platform ``` ## Legacy: per-component JKS truststore (deprecated) The `global.elasticsearch.*` and `global.opensearch.*` trees were removed in chart 15.x, including their `tls.secret.*` and `tls.jks.secret.*` password-injection blocks. Setting any key under them fails the render. See the [8.9 to 8.10 upgrade guide](/self-managed/upgrade/helm/890-to-8100.md#migrate-globalelasticsearch-and-globalopensearch) for the migration mapping. The following per-component fields still work in chart 15.x, but are deprecated: - `orchestration.data.secondaryStorage.elasticsearch.tls.secret.*` - `orchestration.data.secondaryStorage.opensearch.tls.secret.*` - `optimize.database.elasticsearch.tls.secret.*` - `optimize.database.opensearch.tls.secret.*` If a legacy JKS field and `global.tls.caBundle` are both set, the legacy field takes precedence. To migrate: 1. Convert JKS to PEM: ```bash keytool -list -keystore your.jks -storepass changeit -rfc \ | awk '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/' \ > your-ca-bundle.pem ``` 2. Create the `camunda-ca-bundle` Secret as in [step 1](#1-create-the-ca-bundle-secret). 3. Remove all `*.tls.secret.existingSecret` / `*.tls.jks.*` entries from your values file. 4. Remove `-Djavax.net.ssl.trustStore…` and `-Djavax.net.ssl.trustStoreType=jks` from `javaOpts`. 5. Run `helm upgrade` with `-f values-tls.yaml`. ## Common gotchas ### Java 21 default `trustStoreType` is PKCS12 The init container builds a PKCS12 truststore; the chart omits `-Djavax.net.ssl.trustStoreType` to match the JVM default. If you supply a legacy JKS via `tls.secret.existingSecret`, add `-Djavax.net.ssl.trustStoreType=jks` to `javaOpts` explicitly. ### Camunda Hub websockets are Node.js {#console-and-web-modeler-websockets-are-nodejs} The chart sets both `SSL_CERT_FILE` and `NODE_EXTRA_CA_CERTS` on Node.js components automatically. Do not add `NODE_EXTRA_CA_CERTS` via `camundaHub.websockets.env` (or the deprecated `webModeler.websockets.env`) — Kubernetes last-wins env semantics make the value undefined. ## Encrypt in-cluster transport {#in-cluster-transport-service-mesh-required} `global.tls.caBundle` covers component-to-datastore and component-to-IdP connections only. The following in-cluster connections are plaintext by default: | Connection | Protocol | | ----------------------------------------------- | -------- | | Operate / Tasklist / Connectors → Zeebe gateway | gRPC | | Web Modeler / Console / Optimize → Identity | REST | | Spring Boot management / metrics (probes) | HTTP | Encrypt these at the pod level with a service mesh (Linkerd, Istio, or Cilium). For the Orchestration REST and gRPC servers, you can instead enable pod-level TLS with the opt-in Helm values in [Orchestration TLS modes](./orchestration-tls-modes.md). See the [TLS coverage matrix](https://github.com/camunda/camunda-platform-helm/blob/main/docs/tls-coverage-810.md) for the full connection inventory. ## Related - [Helm chart TLS coverage matrix](https://github.com/camunda/camunda-platform-helm/blob/main/docs/tls-coverage-810.md) — per-connection support level - [Helm chart `values-tls.yaml`](https://github.com/camunda/camunda-platform-helm/blob/main/charts/camunda-platform-8.10/values-tls.yaml) — the overlay this guide describes --- ## Camunda Helm chart Camunda recommends using Kubernetes and Helm to deploy and run Camunda 8 Self-Managed in production environments. There are many ways to provision and configure a Kubernetes cluster, and several architectural decisions to consider. For example, will your workers run inside the Kubernetes cluster or externally? You'll need to configure the cluster accordingly and tailor the setup to your architecture. Camunda provides continuously improved Helm charts that are not tied to any specific cloud provider allowing you to choose your preferred Kubernetes platform. These charts are available in the [Camunda Helm repository](https://artifacthub.io/packages/helm/camunda/camunda-platform). To provide feedback or report issues, use the [Helm GitHub repository](https://github.com/camunda/camunda-platform-helm/issues). :::note Helm CLI support Camunda recommends Helm CLI v4 and supports it for the full release cycles of Camunda 8.9 and 8.10. Camunda supports Helm CLI v3 (3.10 or later) until February 10, 2027, when upstream support ends. After February 10, 2027, Camunda no longer supports Helm CLI v3. Customers who continue to use Helm CLI v3 after that date do so at their own risk. Use Helm CLI v4 for new installations. ::: ## What is Helm? [Helm](https://helm.sh/) is a package manager for Kubernetes resources. It lets you install a set of components by referencing a chart name and overriding configurations to suit various deployment scenarios. Helm also manages dependencies between charts, so that multiple components can be installed and configured with a single command. For details, see the full list of [Helm values](https://artifacthub.io/packages/helm/camunda/camunda-platform#parameters). ## Reference architecture For guidance on sizing and deployment patterns, see the [Kubernetes reference architecture](/self-managed/reference-architecture/kubernetes.md). When you install the [camunda-platform](https://artifacthub.io/packages/helm/camunda/camunda-platform) Helm chart, the default installation includes the Orchestration Cluster components (Zeebe, Operate, Tasklist, and Admin). Other components from the reference architecture, such as Camunda Hub, require additional configuration and an external Identity Provider (IdP). ## Versioning Starting with Camunda 8.4 (January 2024), the Helm chart version is independent of the application version. For example, the chart version may be `9.0.0` while the application version is `8.4.x`. To see which application versions are included in a specific Helm chart, see the [Camunda 8 Helm Chart Version Matrix](https://helm.camunda.io/camunda-platform/version-matrix/). ## Get started To install Camunda with the default orchestration cluster, see [Install Camunda with Helm](/self-managed/deployment/helm/install/quick-install.md). If managed databases or an external OIDC provider are not available in your organization, see [Deploy required dependencies](/self-managed/deployment/helm/configure/operator-based-infrastructure.md) to set up PostgreSQL, Elasticsearch, and Keycloak on Kubernetes using official operators. --- ## RDBMS example deployment for Camunda with Helm This guide is a focused walkthrough for teams using an external relational database (RDBMS) as secondary storage in the Helm production installation flow, instead of a document-store secondary backend (Elasticsearch or OpenSearch). To install the releases, see [choose your topology](./index.md#choose-your-topology), and apply the [production hardening](./production/index.md) settings. Use this page when you want additional RDBMS-specific examples for that flow. If you deploy on AWS EKS, use [Install Camunda 8 on an EKS cluster](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/eks-helm.md) for the cluster, Ingress, and AWS-managed service setup, then return to this page for the RDBMS-specific Helm configuration and installation steps. Related guides: - [Production install](/self-managed/deployment/helm/install/production/index.md) - [Secondary storage architecture](/self-managed/reference-architecture/reference-architecture.md#secondary-storage-architecture) - [Secondary storage overview](/self-managed/concepts/secondary-storage/index.md) - [Configure RDBMS in Helm charts](/self-managed/deployment/helm/configure/database/rdbms.md) - [JDBC driver management](/self-managed/deployment/helm/configure/database/rdbms-jdbc-drivers.md) ## What changes when using RDBMS? In Camunda 8, secondary storage stores historical data and process state. You can use either a document-store backend (Elasticsearch/OpenSearch) or an RDBMS, depending on your requirements. For the canonical production trade-off guidance, see [secondary storage architecture](/self-managed/reference-architecture/reference-architecture.md#secondary-storage-architecture). This guide focuses on the Helm-specific RDBMS path. In practice, that means you provide and operate an external supported relational database, and the Orchestration Cluster reads operational data through that backend. When using RDBMS, **Optimize still requires Elasticsearch or OpenSearch**. Only the Orchestration Cluster uses RDBMS. In this topology: - The RDBMS exporter writes Orchestration Cluster data to your external relational database. - Operate, Tasklist, and Admin use the Orchestration Cluster API, and that API queries the configured RDBMS secondary storage. - If you also deploy Optimize, keep Elasticsearch or OpenSearch available and enable an additional Elasticsearch/OpenSearch exporter for Optimize. ## Prerequisites Before you begin: 1. **Kubernetes cluster**: 1.24+ with sufficient resources for Camunda pods. 2. **Helm CLI v4**: Install or upgrade [Helm](https://helm.sh/docs/intro/install/). 3. **External RDBMS**: A supported database reachable from your cluster. See the [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) for the complete list of supported databases and versions. 4. **Database credentials**: Username and password for a database user with DDL permissions (if using auto-schema creation). 5. **Document-store backend (Elasticsearch/OpenSearch)** (for Optimize): Required if you deploy Optimize alongside Camunda. ## Installation workflow ### Step 1: Choose your RDBMS **PostgreSQL:** - Bundled driver included; no additional setup required. - Excellent Kubernetes operator support (optional). - Managed services available on AWS (Aurora PostgreSQL), Azure, GCP, etc. **Oracle:** - Custom JDBC driver required; use init container to load. - Advanced security and HA features. - Managed services available on AWS (RDS), Azure, OCI. **MariaDB/MySQL:** - Bundled driver available for MariaDB; custom driver for MySQL. - Community-friendly; good for development. - Managed services widely available on AWS (RDS), Azure, GCP. ### Step 2: Prepare your database :::note On Amazon Aurora PostgreSQL Skip the local `createdb` and `createuser` commands. Connect to your Aurora writer endpoint with `psql`, and prefer IAM database authentication over a static password. See [Install Camunda 8 on an EKS cluster](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/eks-helm.md) and the [Aurora Terraform module](https://github.com/camunda/camunda-deployment-references/tree/stable/8.9/aws/modules/aurora). ::: Create a database and user. For example, in PostgreSQL: ```bash createdb camunda createuser camunda ``` Then set permissions: ```sql ALTER USER camunda WITH PASSWORD 'your-secure-password'; GRANT CONNECT ON DATABASE camunda TO camunda; GRANT USAGE ON SCHEMA public TO camunda; GRANT CREATE ON SCHEMA public TO camunda; ``` :::note The user needs DDL permissions only if you enable auto-schema creation (`autoDDL: true`). For manually managed schemas, only SELECT/INSERT/UPDATE/DELETE permissions are needed. ::: ### Step 3: Add the Camunda Helm repository ```bash helm repo add camunda https://camunda.github.io/camunda-platform-helm helm repo update ``` ### Step 4: Create a values file Create a `values-rdbms.yaml` file with your RDBMS configuration: ```yaml # Configure the Orchestration Cluster to use RDBMS orchestration: enabled: true exporters: camunda: enabled: false rdbms: enabled: true data: secondaryStorage: type: rdbms rdbms: url: jdbc:postgresql://postgres.example.com:5432/camunda username: camunda secret: existingSecret: camunda-db-secret existingSecretKey: db-password extraConfiguration: - file: "flush-interval.yaml" content: | camunda: data: secondary-storage: rdbms: # Optional: Tune for your workload flush-interval: PT1S # More frequent flushes queue-size: 5000 # Larger queue for buffering queue-memory-limit: 50 # Increase if needed # Optional: Configure history retention history: default-history-ttl: P30D ``` If you deploy Optimize, set its connection under `optimize.database.elasticsearch` or `optimize.database.opensearch`. See [component storage requirements](#important-component-storage-requirements). ### Step 5: Create the Kubernetes secret for database credentials ```bash kubectl create namespace camunda kubectl create secret generic camunda-db-secret \ --from-literal=db-password='your-secure-password' \ -n camunda ``` ### Step 6: Handle custom JDBC drivers (if required) If you're using Oracle, MySQL, or a database version not covered by bundled drivers, you must provide the JDBC driver. :::note For detailed information about JDBC driver strategies, security configurations, and validation, see [JDBC driver management](/self-managed/deployment/helm/configure/database/rdbms-jdbc-drivers.md). ::: #### Option A: Init container (recommended for production) Update your `values-rdbms.yaml`: ```yaml orchestration: extraVolumeMounts: - name: jdbcdrivers mountPath: /driver-lib extraVolumes: - name: jdbcdrivers emptyDir: {} initContainers: - name: fetch-jdbc-drivers image: alpine:3.19 imagePullPolicy: Always command: - sh - -c - > wget https://repo1.maven.org/maven2/com/oracle/database/jdbc/ojdbc11/23.9.0.25.07/ojdbc11-23.9.0.25.07.jar -O /driver-lib/ojdbc.jar volumeMounts: - name: jdbcdrivers mountPath: /driver-lib securityContext: runAsUser: 1001 ``` For other driver sources (e.g., private repositories), adjust the `wget` command or use a private container registry for pre-built images. #### Option B: ConfigMap (GitOps-friendly) Store the driver JAR in a ConfigMap and mount it. A ConfigMap can't exceed 1 MiB, so this option only fits small drivers. The Oracle driver in Option A is larger than that limit. Create the ConfigMap from the JAR file. The command stores the JAR in the `binaryData` field: ```bash kubectl create configmap jdbc-drivers \ --from-file=driver.jar= \ -n camunda ``` For GitOps, commit the equivalent manifest instead. Store the base64-encoded JAR in `binaryData`, not in `data`: ```yaml apiVersion: v1 kind: ConfigMap metadata: name: jdbc-drivers namespace: camunda binaryData: driver.jar: ``` Then mount the ConfigMap in your `values-rdbms.yaml`: ```yaml orchestration: extraVolumeMounts: - name: jdbcdrivers mountPath: /driver-lib extraVolumes: - name: jdbcdrivers configMap: name: jdbc-drivers ``` See [JDBC driver loading](/self-managed/deployment/helm/configure/database/rdbms.md#bundled-vs-custom-jdbc-drivers) for more strategies. ### Step 7: Install Camunda ```bash helm install camunda camunda/camunda-platform \ --namespace camunda \ -f values-rdbms.yaml ``` Monitor the installation: ```bash kubectl get pods -n camunda kubectl logs -n camunda -l app.kubernetes.io/name=orchestration --tail=50 ``` ### Step 8: Verify the installation Check that tables were created and data is being written: ```bash # Port-forward to the database (if not directly accessible) kubectl port-forward -n camunda svc/postgres 5432:5432 # Connect and verify psql -h localhost -U camunda -d camunda -c "SELECT COUNT(*) FROM process_instance;" ``` Deploy a test process using Web Modeler and verify it appears in the database: ```sql SELECT COUNT(*) FROM process_instance; ``` For a full post-deployment checklist, see [validate RDBMS connectivity](/self-managed/deployment/helm/configure/database/validate-rdbms.md). ## Common deployment scenarios ### PostgreSQL with AWS Aurora For AWS Aurora PostgreSQL, configure the JDBC URL with the Aurora endpoint: ```yaml orchestration: data: secondaryStorage: rdbms: url: jdbc:postgresql://my-aurora-cluster.xxxxxxx.us-east-1.rds.amazonaws.com:5432/camunda ``` Aurora supports automatic failover. For advanced failover features, consider the [AWS JDBC wrapper driver](/self-managed/concepts/databases/relational-db/configuration.md#usage-with-aws-aurora-postgresql--mysql). ### Oracle with Kubernetes init container To load the Oracle JDBC driver: ```yaml orchestration: extraVolumeMounts: - name: jdbcdrivers mountPath: /driver-lib extraVolumes: - name: jdbcdrivers emptyDir: {} initContainers: - name: fetch-jdbc-drivers image: alpine:3.19 command: - sh - -c - > wget https://your-private-repo.com/ojdbc11.jar -O /driver-lib/ojdbc.jar volumeMounts: - name: jdbcdrivers mountPath: /driver-lib ``` Configure the JDBC URL: ```yaml orchestration: data: secondaryStorage: rdbms: url: jdbc:oracle:thin:@//my-oracle-host:1521/FREEPDB1 ``` ### Multi-namespace deployment (Orchestration Cluster + management plane) {#multi-namespace-deployment-orchestration--management} In production, separate the Orchestration Cluster from the management plane (Camunda Hub and Management Identity) and Optimize: #### Namespace 1: Orchestration + Connectors ```yaml orchestration: enabled: true # RDBMS configuration data: secondaryStorage: type: rdbms rdbms: url: jdbc:postgresql://postgres:5432/camunda connectors: enabled: true # Disable the management plane and Optimize camundaHub: enabled: false optimize: enabled: false identity: enabled: false ``` #### Namespace 2: Management plane and Optimize (with document-store secondary storage) {#namespace-2-management-components-with-document-store-secondary-storage} ```yaml orchestration: enabled: false camundaHub: enabled: true optimize: enabled: true database: elasticsearch: enabled: true external: true url: protocol: https host: elastic.example.com port: 443 identity: enabled: true ``` For the Optimize connection settings, see [use external Elasticsearch for Optimize with Helm](/self-managed/deployment/helm/configure/database/optimize/using-external-elasticsearch.md). ## Configuration reference For detailed configuration options, see: - [Configure RDBMS in Helm charts](/self-managed/deployment/helm/configure/database/rdbms.md): All Helm values, bundled vs. custom drivers, schema management, and troubleshooting. - [Production installation best practices](/self-managed/deployment/helm/install/production/index.md): Network policies, TLS, OIDC, and multi-namespace setup. - [Helm chart parameters](/self-managed/deployment/helm/chart-parameters.md): Full Helm chart reference. ## Important: Component storage requirements **Optimize requires Elasticsearch or OpenSearch, not RDBMS.** If you deploy Optimize, configure it with Elasticsearch or OpenSearch and enable the corresponding exporter for Zeebe, even if your Orchestration Cluster uses RDBMS: ```yaml orchestration: data: secondaryStorage: type: rdbms # Orchestration uses RDBMS optimize: enabled: true database: elasticsearch: enabled: true external: true url: protocol: https host: elastic.example.com port: 443 ``` For OpenSearch, set `optimize.database.opensearch` instead. See [use external OpenSearch for Optimize with Helm](/self-managed/deployment/helm/configure/database/optimize/using-external-opensearch.md). Mixing storage types (RDBMS for Orchestration, Elasticsearch/OpenSearch for Optimize) is supported and tested. ## Troubleshooting ### Pod fails to start **Check logs:** ```bash kubectl logs -n camunda ``` **Common issues:** - Database unreachable: Verify network policies, firewall, and JDBC URL. - Authentication failed: Confirm secret and credentials. - Driver not found (Oracle/MySQL): Verify init container or custom image has loaded the driver. See [troubleshooting RDBMS connectivity](/self-managed/deployment/helm/configure/database/rdbms.md#troubleshooting-and-operations) for detailed diagnostics. ### Data not appearing in database **Cause:** Flush interval delay or JDBC configuration issue. **Check:** Monitor logs for exporter messages: ```bash kubectl logs -n camunda | grep -i exporter ``` **Fix:** Adjust `flushInterval` and `queueSize` in your values file (see [Configuration reference](#configuration-reference)). ### JDBC driver version mismatch **Symptom:** "ClassNotFoundException" or driver-related errors. **Fix:** Ensure the driver version matches your database. See [Bundled vs. custom drivers](/self-managed/deployment/helm/configure/database/rdbms.md#bundled-vs-custom-jdbc-drivers). ## Known limitations and unsupported scenarios ### Multi-region deployments Cross-region RDBMS deployments are **not tested or supported in Camunda 8.9**. Latency and consistency requirements are not yet validated. Deploy your RDBMS in the same region as your Kubernetes cluster. ### Optimize storage Optimize **cannot use RDBMS** and requires Elasticsearch or OpenSearch. If deploying Optimize alongside an RDBMS-based Orchestration Cluster, you must provision Elasticsearch/OpenSearch for Optimize only. ### Self-Managed database HA Camunda does not manage database HA. Use cloud-managed databases (AWS Aurora, Azure Database, GCP Cloud SQL) or vendor-supplied HA solutions. Camunda assumes the database handles its own replication and failover. ## Next steps - **[Production guide](/self-managed/deployment/helm/install/production/index.md)**: Network security, TLS, OIDC, and high-availability configurations. - **[Helm quick install](/self-managed/deployment/helm/install/quick-install.md)**: Get started with default settings for evaluation. - **[Operational tasks](/self-managed/deployment/helm/operational-tasks/index.md)**: Scaling, upgrades, and maintenance. - **[Backup and restore](/self-managed/operational-guides/backup-restore/backup-and-restore.md)**: Data protection strategies. --- ## Install Camunda with Helm Install Camunda 8 Self-Managed on Kubernetes using Helm charts. Two decisions shape the installation: the topology you deploy, and the secondary storage backend you use. ## Choose your topology A Camunda 8.10 deployment is one or more Helm releases, and each release declares its role with `global.topology.mode`. For the architecture, see [deployment topology](/self-managed/reference-architecture/reference-architecture.md#deployment-topology). For the release roles, see [release roles](/self-managed/deployment/helm/install/topology/index.md#release-roles). | Use case | Topology | Installation guide | | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | Testing, evaluation, local development | One `combined` release | [Quick install](./quick-install.md) | | New production deployment, one cluster | One `hub` release, one `orchestration` release | [Install the deployment topology](./topology/index.md) | | New production deployment, tenants or analytics | The same, plus one `optimize` release per Physical Tenant | [Configure Physical Tenants across releases](./topology/physical-tenants.md) | | Several Orchestration Clusters, one Hub | One `hub` release, one `orchestration` release per cluster | [Install an Orchestration Cluster release](./topology/orchestration-release.md) | | Analytics for a tenant of the split topology | One `optimize` release per Physical Tenant | [Install an Optimize release](./topology/optimize-release.md) | | Existing single-release production deployment | One `combined` release | [Install for production](./production/index.md) | | Upgrading an existing 8.9 deployment | Stay on one `combined` release and upgrade in place first | [Upgrade Camunda 8.9 to 8.10 using Helm](/self-managed/upgrade/helm/890-to-8100.md) | | Moving a combined release to the split topology | `hub` and `orchestration` releases. Optimize either stays in the `orchestration` release or moves to one `optimize` release per Physical Tenant | [Move from a combined release to the split topology](/self-managed/upgrade/helm/combined-to-split-topology.md) | For a new production deployment, the split topology is the baseline. A `combined` release remains supported, remains the chart default, and is the right choice for evaluation, proofs of concept, and 8.9 compatibility. ## Choose your secondary storage This decision is independent of your topology. Both apply to every release that runs an Orchestration Cluster. | Backend | Guide | Best for | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ | | Elasticsearch or OpenSearch | [Using external Elasticsearch](/self-managed/deployment/helm/configure/database/elasticsearch/using-external-elasticsearch.md) | High throughput, and any deployment that uses Optimize | | Relational database | [Install with RDBMS](./helm-with-rdbms.md) | Organizations that standardize on relational databases | | Embedded H2 | [Quick install](./quick-install.md) | Evaluation only. Single broker, not for production | :::warning Optimize requires Elasticsearch or OpenSearch and can't read from a relational database. An Orchestration Cluster on RDBMS secondary storage can be analyzed with Optimize only if it also exports its records to a separate Elasticsearch or OpenSearch instance, and Optimize is deployed against that instance. ::: For the trade-offs between backends, see [secondary storage architecture](/self-managed/reference-architecture/reference-architecture.md#secondary-storage-architecture). ## Before you install Camunda 8.10 bundles no Elasticsearch, PostgreSQL, or Keycloak subcharts. Camunda recommends the Helm CLI v4, and supports Helm CLI v3 (3.10 or later) until February 10, 2027. - Provision your databases, secondary storage, and identity provider first. See [deploy required dependencies](/self-managed/deployment/helm/configure/operator-based-infrastructure.md). - Use the Helm CLI v4 for new installations. See [Helm CLI support and chart compatibility](/self-managed/deployment/helm/operational-tasks/helm-v4.md#camunda-helm-chart-compatibility). - Decide which settings belong in `values.yaml` and which belong in a component's `extraConfiguration`. See [Helm and application configuration responsibilities](/self-managed/deployment/helm/configure/configuration-responsibilities.md). ## Upgrading instead of installing To move an existing deployment to 8.10, see [upgrade Camunda 8.9 to 8.10 using Helm](/self-managed/upgrade/helm/890-to-8100.md). Upgrade in place first; adopting the split topology is a separate step, covered in [move from a combined release to the split topology](/self-managed/upgrade/helm/combined-to-split-topology.md). --- ## Harden Camunda Helm releases for production This guide describes production hardening for the [Camunda Helm chart](https://artifacthub.io/packages/helm/camunda/camunda-platform). It applies to every release in your deployment, whatever role the release has. It provides a resilient baseline for most production use cases. To install a split topology, use the guide for each release role. See [choose your topology](../index.md#choose-your-topology). To install a single `combined` release, see [install a combined release](#install-a-combined-release). The guide covers these database options: - **Non-SQL secondary storage** (Elasticsearch/OpenSearch) - **RDBMS secondary storage** (for supported components) AWS examples are used where helpful, but the flow applies to other [supported Kubernetes distributions](/reference/supported-environments.md#deployment-options) with equivalent services. ## Prerequisites Before proceeding with the setup, ensure the following requirements are met: - **Kubernetes Cluster**: A functioning Kubernetes cluster with kubectl access and block storage persistent volumes for stateful components. This guide will use an AWS EKS cluster for reference. Step-by-step documentation is available to deploy an EKS cluster with [Terraform](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/terraform-setup.md), and [install Camunda 8](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/eks-helm.md). - **Helm**: Make sure the [Helm CLI v4](/reference/supported-environments.md#clients) is installed. - **DNS Configuration**: You must have access to configure DNS for your domain in order to point to the Kubernetes cluster Ingress. - **TLS Certificates**: Obtain valid X.509 certificates for your domain from a trusted Certificate Authority. - **External Dependencies**: Provision the following external dependencies: - **PostgreSQL-compatible database**: Required for Camunda Hub persistence. This guide uses Amazon Aurora PostgreSQL as an example. For AWS-specific steps, see [Aurora PostgreSQL module setup](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/terraform-setup.md#postgresql-module-setup). - **Secondary storage backend for the Orchestration Cluster**: Choose one option: - **Non-SQL**: Elasticsearch/OpenSearch (this guide uses Amazon OpenSearch as the example). For AWS-specific steps, see [OpenSearch](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/eksctl.md#4-opensearch-domain). - **RDBMS**: See [configure RDBMS in Helm](/self-managed/deployment/helm/configure/database/rdbms.md) and the [RDBMS example deployment](/self-managed/deployment/helm/install/helm-with-rdbms.md). - **Identity Provider (IdP)**: An OIDC-compatible identity provider for authentication. See [Authentication and authorization](/self-managed/deployment/helm/configure/authentication-and-authorization/index.md) for supported options. :::tip No managed services available? If managed PostgreSQL, Elasticsearch, or an external OIDC provider are not available in your organization, you can deploy these infrastructure components on Kubernetes using official operators. See [Required infrastructure](/self-managed/deployment/helm/configure/operator-based-infrastructure.md) for instructions. ::: - **Ingress controller**: Ensure an Ingress controller supporting gRPC and HTTP/2 is set up in the cluster. The reference architectures deploy [Contour](https://projectcontour.io/) by choice, but any such controller works, for example Traefik or HAProxy; select yours through `global.ingress.className`. Alternatively, use the [Gateway API](/self-managed/deployment/helm/configure/ingress/gateway-api-setup.md), which the chart also supports. - **AWS OpenSearch Snapshot Repository** - To store the backups of the Camunda web applications. This repository must be configured with OpenSearch to take backups which are stored in Amazon S3. See the [official AWS guide](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/managedomains-snapshot-registerdirectory.html) for detailed steps. - **Amazon S3** - An additional bucket to store backup files of the Orchestration Cluster brokers. - **Resource Planning**: Make sure you have understood the considerations for [sizing Camunda Clusters](/components/best-practices/architecture/sizing-your-environment.md), and have evaluated sufficient CPU, memory, and storage necessary for the deployment. Ensure all prerequisites are in place to avoid issues during installation or when upgrading in a production environment. ## Architecture overview This is the high-level architecture diagram for our production setup, as illustrated below: ![Architecture Diagram](./img/architecture.jpg) For more information refer to the Camunda 8 [Kubernetes reference architectures](/self-managed/reference-architecture/kubernetes.md#kubernetes). For a new Camunda 8.10 production deployment, the baseline topology deploys Camunda Hub and each Orchestration Cluster as separate Helm releases. It also deploys one Optimize release per Physical Tenant. See [deployment topology](/self-managed/reference-architecture/reference-architecture.md#deployment-topology) and [install the deployment topology](/self-managed/deployment/helm/install/topology/index.md). Before you write a production values file, see [Helm and application configuration responsibilities](/self-managed/deployment/helm/configure/configuration-responsibilities.md) for which settings belong in `values.yaml` and which belong in a component's `extraConfiguration`. ## Installation and configuration After following the [prerequisites](#prerequisites), you should have a Kubernetes cluster ready with `kubectl` and the `helm` CLI installed. ### Install the releases Apply the settings on this page by role. Put each setting only in the values file of the release that runs the component it configures. For example, the Hub and orchestration releases use separate Ingress hosts. For the prerequisites, namespaces, Secrets, and network policies of the split topology, see [install the deployment topology](/self-managed/deployment/helm/install/topology/index.md). #### Install a combined release A `combined` release deploys every enabled component from one values file. Write `values.yaml` with the production settings on this page, then install the chart: ```bash # Pick a 15.x chart version from https://helm.camunda.io/camunda-platform/version-matrix/ export HELM_CHART_VERSION="<15.x-chart-version>" helm repo add camunda https://helm.camunda.io helm repo update helm install camunda camunda/camunda-platform --version $HELM_CHART_VERSION \ --namespace camunda --create-namespace \ --values values.yaml ``` :::note For more information on the difference between the Orchestration Cluster and Camunda Hub, see the Camunda 8 [reference architecture](/self-managed/reference-architecture/reference-architecture.md#camunda-hub-vs-orchestration-cluster). ::: ### Ingress TLS setup In order to access Camunda through HTTPS with Ingress, TLS must be enabled. Enabling TLS requires the following: 1. **Domain name**: A public registered domain that has configurable DNS records. This guide will use `camunda.example.com` as the domain. 2. **TLS certificate**: A TLS certificate created for your domain. The certificate must be an X.509 certificate, issued by a trusted Certificate Authority. The certificate must include the correct domain names (Common Name or Subject Alternative Names) to secure Ingress resources. Reach out to your DNS provider if you are unsure on how to create a TLS certificate. It is not recommended to use self-signed certificates. 3. **TLS secret**: A TLS secret created from your TLS certificate. This guide will use a secret called `camunda-platform`. For more information, see the Kubernetes documentation on how to create a [TLS secret](https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets). :::note Multiple ingress controller support Multiple ingress controllers are supported. Specify `ingress.className` or `ingress.grpc.className` to assign the ingress to the desired ingress controller. ::: The following is an example `values.yaml` configuration using the example Ingress domain and TLS secret: ```yaml global: host: camunda.example.com ingress: enabled: true className: nginx tls: enabled: true secretName: camunda-platform ``` Optionally, you can configure Ingress for the Orchestration Cluster [gRPC API](/apis-tools/zeebe-api/grpc.md): ```yaml orchestration: ingress: grpc: enabled: true className: nginx host: zeebe-grpc.camunda.example.com tls: enabled: true secretName: camunda-platform-zeebe-grpc ``` More information can be found in the [Ingress setup](/self-managed/deployment/helm/configure/ingress/ingress-setup.md) guide. ### Identity provider integration Once secure HTTPS connections are enabled and correctly configured via Ingress, the next step is configuring authentication with an OIDC-compatible identity provider. Camunda supports several authentication methods. Choose the guide that matches your identity provider: - **[Microsoft Entra ID](/self-managed/deployment/helm/configure/authentication-and-authorization/microsoft-entra.md)**: For organizations using Microsoft Entra ID (formerly Azure Active Directory). - **[External Keycloak](/self-managed/deployment/helm/configure/authentication-and-authorization/external-keycloak.md)**: For organizations with an existing Keycloak instance. - **[Generic OIDC provider](/self-managed/deployment/helm/configure/authentication-and-authorization/generic-oidc-provider.md)**: For other OIDC-compatible providers such as Okta, Auth0, or Amazon Cognito. For a complete overview of authentication options and their trade-offs, see [Authentication and authorization](/self-managed/deployment/helm/configure/authentication-and-authorization/index.md). :::note You must create Kubernetes secrets for all client secrets required by your identity provider configuration before installing the Helm chart. ::: ### Connect external databases :::note Camunda 8.10 does not bundle databases. Provide PostgreSQL and Elasticsearch or OpenSearch through managed services or Kubernetes operators (see [operator-based infrastructure](/self-managed/deployment/helm/configure/operator-based-infrastructure.md)), and use production-grade databases. ::: This guide keeps database configuration in one flow and provides two options: - **Option A (Non-SQL secondary storage):** Use Elasticsearch or OpenSearch for the Orchestration Cluster secondary storage backend. This path is also required when deploying Optimize. - **Option B (RDBMS secondary storage):** Use a relational database as secondary storage for supported components. For this path, follow [Configure RDBMS in Helm chart](/self-managed/deployment/helm/configure/database/rdbms.md) and use the [RDBMS example deployment](/self-managed/deployment/helm/install/helm-with-rdbms.md) as a focused walkthrough. The examples below use Option A with Amazon OpenSearch for secondary storage, and Amazon Aurora PostgreSQL for Management Identity and Web Modeler. You should have one Amazon OpenSearch instance and one Amazon Aurora PostgreSQL instance (with two databases) ready to use, complete with a username, password, and URL for each datastore. If these have not been configured, see the [prerequisites](#prerequisites) for requirements. #### Connecting to Amazon OpenSearch The following example `values.yaml` configures OpenSearch as the secondary storage for the Orchestration Cluster, and points Optimize at the same cluster: ```yaml orchestration: data: secondaryStorage: type: opensearch opensearch: url: https://opensearch.example.com:443 auth: username: user secret: existingSecret: opensearch-credentials existingSecretKey: password optimize: enabled: true database: opensearch: enabled: true url: protocol: https host: opensearch.example.com port: 443 auth: username: user secret: existingSecret: opensearch-credentials existingSecretKey: password ``` #### Connect to an external database for Management Identity The following example `values.yaml` configures Management Identity with an external Amazon Aurora PostgreSQL database: ```yaml identity: externalDatabase: enabled: true host: external-postgres-host port: 5432 username: identity_user database: identity_db secret: existingSecret: identity-db-secret existingSecretKey: database-password ``` :::note Make sure the host and port are correctly defined. ::: #### Connect to an external database for Web Modeler The following example `values.yaml` configures Web Modeler with an external Amazon Aurora PostgreSQL database: ```yaml camundaHub: restapi: externalDatabase: url: jdbc:postgresql://external-postgres-host:5432/camunda_db username: web_modeler_user secret: existingSecret: web-modeler-db-secret existingSecretKey: database-password ``` Use the `existingSecret` parameter to specify a pre-existing Kubernetes secret containing the password. This approach allows the Camunda Helm chart to reference credentials stored securely in your cluster, rather than hardcoding sensitive data in values files or templates. For more information on connecting to external databases, the following guides are available for the Camunda Helm chart: - [Helm chart database configuration](/self-managed/deployment/helm/configure/database/index.md) - [Non-SQL database configuration](/self-managed/deployment/helm/configure/database/non-sql.md) - Using an [existing Elasticsearch instance](/self-managed/deployment/helm/configure/database/elasticsearch/using-external-elasticsearch.md) - Using [Amazon OpenSearch service](/self-managed/deployment/helm/configure/database/using-external-opensearch.md) - [RDBMS configuration](/self-managed/deployment/helm/configure/database/rdbms.md) - Using Amazon OpenSearch service [through IRSA](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/terraform-setup.md#opensearch-module-setup) (only applicable if you are using EKS) - Running Web Modeler on [Amazon Aurora PostgreSQL](/self-managed/components/hub/configuration/database.md#running-camunda-hub-on-amazon-aurora-postgresql) ## Orchestration Cluster configuration :::note At this point, you should be able to connect to your platform through HTTPS, correctly authenticate users using your configured identity provider, and have connected to external databases such as Amazon OpenSearch and Amazon Aurora PostgreSQL. ::: The next steps focus on the Camunda application-specific configurations suitable for a production environment. The following sections add settings at the component level. Add each setting to the values file of the release that runs the component. ### Elasticsearch/OpenSearch index retention An index lifecycle management (ILM) policy in OpenSearch is crucial for efficient management and operation of large-scale search and analytics workloads. ILM policies provide a framework for automating the management of index lifecycles, which directly impacts performance, cost efficiency, and data retention compliance. The Helm value **`orchestration.history.retention`** configures retention for archived Operate, Tasklist, and Camunda indices stored in secondary storage (for example, `operate-process-*`, `tasklist-task-*`). The following example configures an ILM policy for the Orchestration Cluster's archived history indices and can be added to the Helm values file `orchestration-values.yaml`: ```yaml orchestration: history: rolloverInterval: 7d retention: enabled: true minimumAge: 30d policyName: camunda-history-retention-policy ``` :::warning The `orchestration.history.rolloverInterval` value significantly affects Elasticsearch/OpenSearch performance. Review the [data retention performance](/self-managed/deployment/helm/configure/data-retention.md#performance) section to ensure the value fits your use case. ::: :::note For more information on configuring both retention policy types, refer to the [data retention configuration guide](/self-managed/deployment/helm/configure/data-retention.md). ::: ### Configure backups In order to configure backups, refer to the [backup guide](/self-managed/operational-guides/backup-restore/backup-and-restore.md) for Self-Managed installations. ## Operational configuration options After following the above steps, you should already have a solid base for running your platform in a production setting. The rest of this guide provides general Kubernetes-based guidance on configuration of the Camunda Helm chart for long-term maintenance. ### Scaling and performance The following resources and configuration options are important to keep in mind regarding scaling: - To scale the Orchestration Cluster, the following values can be modified: ```yaml orchestration: clusterSize: "3" partitionCount: "3" replicationFactor: "3" ``` - `orchestration.clusterSize`: to the amount of brokers to configure - `orchestration.partitionCount`: how many [Orchestration Cluster partitions](/components/zeebe/technical-concepts/partitions.md) are configured for each cluster - `orchestration.replicationFactor`: the [number of replicas](/components/zeebe/technical-concepts/partitions.md#replication) that each partition replicates to :::note `orchestration.partitionCount` does not yet support dynamic scaling. You will not be able to modify this property. It is better to over-provision the partitions to allow potential growth as dynamic partitioning isn't possible yet. ::: - Ensure the resources (CPU and memory) are appropriate for your workload size. For example, the resource limits can be changed for the Orchestration Cluster by modifying the following values: ```yaml orchestration: resources: requests: cpu: 800m memory: 1200Mi limits: cpu: 2000m memory: 1920Mi ``` - It is possible to set a LimitRange on the namespace. Please refer to the [Kubernetes documentation](https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/memory-default-namespace/) on setting a LimitRange. ### Reliability The following resources and configuration options are important to keep in mind regarding reliability: #### Persistent volume reclaim policy :::warning Risk of data loss If your StorageClass uses a `Delete` reclaim policy (the default in many Kubernetes distributions and OpenShift), Orchestration Cluster broker data will be **permanently lost** if a PVC is deleted. This can lead to complete data loss and is unrecoverable. ::: Ensure your StorageClass uses a `Retain` reclaim policy for production deployments. Verify your configuration on Kubernetes: ```bash kubectl get storageclass # RECLAIMPOLICY should show "Retain", not "Delete" ``` On OpenShift, verify your configuration using the `oc` CLI: ```bash oc get storageclass # RECLAIMPOLICY should show "Retain", not "Delete" ``` :::note Equivalent mechanisms Alternative configurations that preserve the underlying volume and allow it to be reattached to a recreated PVC are also acceptable — for example, patching the `persistentVolumeReclaimPolicy` of individual PVs to `Retain`, or admission policies (Kyverno, Gatekeeper) enforcing `Retain` on dynamically provisioned PVs. Reattaching a retained PV to a new PVC may require manual steps, such as clearing the `claimRef` on the released PV, depending on your provisioner. Snapshot- or backup-based strategies are not equivalent, because they do not preserve the original volume binding required for a Zeebe broker to resume from its existing partition data without manual recovery. If you rely on an alternative mechanism, you are responsible for validating it against your upgrade and disaster recovery scenarios in a non-production environment. ::: For more details, see [troubleshooting](/self-managed/operational-guides/troubleshooting.md#zeebe-data-loss-after-pvc-deletion) and the [Kubernetes documentation on reclaim policies](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#reclaiming). #### Node affinity and tolerations - Check node affinity and tolerations. Refer to the [Kubernetes documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) to modify the node affinity and tolerations. For example, this is the default affinity configuration for the zeebe Pod in the Camunda Helm chart: ```yaml affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app.kubernetes.io/component operator: In values: - zeebe-broker topologyKey: kubernetes.io/hostname ``` This configuration ensures that Zeebe Pods with the default label `app.kubernetes.io/component=zeebe-broker` are not scheduled on the same node. The primary benefits include: - High availability: If one node fails, other nodes running the same component remain unaffected. - Load distribution: Balances the workload across nodes. - Fault tolerance: Reduces the impact of a node-level failure. - It is possible to set a `podDisruptionBudget`, as in the following example for the Orchestration Cluster: ```yaml orchestration: podDisruptionBudget: enabled: false minAvailable: 0 maxUnavailable: 1 ``` #### Topology spread constraints Topology spread constraints control how pods are distributed across failure domains such as availability zones. The default `podAntiAffinity` configuration ensures Zeebe broker pods run on distinct nodes, but does not ensure those nodes are in different zones: if the cluster has more nodes than brokers, all brokers can still be scheduled into a single availability zone. Because broker persistent volumes are bound to a single zone on most cloud providers, a zonal outage can then take down the whole Orchestration Cluster. With `orchestration.topologySpreadConstraints`, you can spread broker pods across zones. The Orchestration Cluster is deployed as a single StatefulSet, and its pods run the Zeebe broker and gateway in the same process, so this value covers both. It does not affect separately deployed components such as Identity, Optimize, Connectors, or Web Modeler, which have no equivalent topology spread value in the current chart version. ```yaml orchestration: topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: ScheduleAnyway labelSelector: matchLabels: app.kubernetes.io/component: zeebe-broker ``` Keep the following in mind when configuring topology spread constraints: - Set a `topologyKey` that your nodes carry. `topology.kubernetes.io/zone` is standard on managed cloud clusters, but bare-metal and local clusters often have no zone label. With `whenUnsatisfiable: DoNotSchedule` and no matching node label, no broker can be scheduled at all. - Prefer `whenUnsatisfiable: ScheduleAnyway`. It provides best-effort spreading: Kubernetes does not guarantee an even distribution and does not rebalance existing brokers. A hard constraint (`DoNotSchedule`) combined with the default hard `podAntiAffinity` can leave broker pods permanently `Pending` when zones have uneven node counts, or stall a rolling update — use it only when every zone has enough spare capacity. - Broker volumes pin pods to a zone. With topology-constrained storage, `volumeBindingMode: WaitForFirstConsumer` delays volume binding or provisioning until the scheduler picks a node, so the volume matches that node's topology; `Immediate` binds or provisions the volume without considering pod scheduling constraints. Once a broker's claim is bound to a single-zone volume, every replacement pod must run in that zone: a hard zone constraint that conflicts with the volume's zone leaves the pod `Pending`, and enabling spreading on an existing cluster does not relocate existing volumes. - The `labelSelector` counts pods across the whole namespace. Pods with matching labels from all Helm releases in the namespace are counted together, not only the release you are configuring. To scope spreading to a single release, also match `app.kubernetes.io/instance: `. For an overview of all pod scheduling values, see [configure pod scheduling](/self-managed/deployment/helm/configure/pod-scheduling.md). #### Secondary storage index replicas :::warning Single point of failure without replicas The Elasticsearch/OpenSearch Exporter defaults to zero index replicas for the Zeebe record indices it creates. Without replicas, a single node restart moves its shards to UNASSIGNED state and puts the cluster into RED health. Two consequences follow: - **Exporter backpressure builds up**: the Orchestration Cluster pauses exports when ES/OS cannot accept writes, which eventually throttles process execution throughput. - **Optimize analytics data goes stale**: the Optimize importer reads from these Zeebe record indices. While shards are UNASSIGNED, Optimize cannot read newly exported records, so its analytics data lags behind until the node rejoins. The cluster recovers once the node rejoins and shards are reassigned. ::: In multi-node Elasticsearch/OpenSearch clusters, configure at least one index replica: ```yaml camunda: database: index: numberOfReplicas: 1 ``` Each replica stores a full copy of the primary shard data, approximately doubling disk usage for those indices. Account for this when sizing your cluster. See [Managing secondary storage: Replicas](/self-managed/concepts/secondary-storage/managing-secondary-storage.md#replicas) for full guidance. #### Version management Stay on a stable Camunda and Kubernetes version. Follow Camunda’s [release notes](/reference/announcements-release-notes/overview.md) for security patches or critical updates. #### Secret management Secrets should be created prior to installing the Helm chart so they can be referenced as existing secrets. Create your secrets explicitly using `kubectl` or an external secret manager, then reference them in your Helm values files. For details, see the [secret management guide](/self-managed/deployment/helm/configure/secret-management.md). :::note It is best to store secrets in an external secret manager such as [Vault by HashiCorp](https://www.vaultproject.io/) in case of a total outage. ::: #### Upgrades When upgrading the Camunda Helm chart, make sure to read the [upgrade guide](/self-managed/upgrade/components/index.md) and corresponding new version release notes before upgrading. Perform the upgrade on a test environment first before attempting in production. The following is an example configuration that mounts an additional volume into the Orchestration Cluster pods. This `emptyDir` volume is ephemeral: Kubernetes deletes its data when a pod is deleted or rescheduled. To keep data after that, use a `persistentVolumeClaim` volume instead. ```yaml orchestration: extraVolumes: - name: persistent-state emptyDir: {} extraVolumeMounts: - name: persistent-state mountPath: /mount ``` #### Resource quotas It is recommended to set a memory and resource quota for your namespace. Please refer to the [Kubernetes documentation](https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/quota-memory-cpu-namespace/) to do so. Namespace-level quotas apply limits to all workloads within a namespace and ensure aggregate resource consumption by all pods in the namespace does not exceed your desired resource limits. ### Security The following resources and configuration options are important to keep in mind regarding security: - To limit unnecessary permissions, and reduce risk of token exploitations, it is recommended to disable auto-mounting of the default Service Account. It is possible to do so by adding the following to both of your values files: ```yaml identity: serviceAccount: enabled: false camundaHub: serviceAccount: enabled: false connectors: serviceAccount: enabled: false orchestration: serviceAccount: enabled: false optimize: serviceAccount: enabled: false ``` :::note You should only enable the auto-mounting of a service account token when the application explicitly needs access to the Kubernetes API server, or you have created a service account with the exact permissions required for the application and bound it to the pod. ::: - Restrict pod-to-pod traffic with [network policies](https://kubernetes.io/docs/concepts/services-networking/network-policies/). See [required network traffic](#required-network-traffic) for the flows a Camunda installation depends on. - Several in-cluster connections, including Connectors to the Orchestration Cluster gateway and Spring Boot management endpoints, are plaintext by default. `global.tls.caBundle` does not cover them. To encrypt them, run a service mesh such as Linkerd, Istio, or Cilium. See [in-cluster transport](/self-managed/deployment/helm/configure/tls.md#in-cluster-transport-service-mesh-required) for the affected connections. - It is possible to have a pod security standard that is suited to your security constraints. This is enabled by modifying the Pod Security Admission. See the [Pod Security Admission](https://kubernetes.io/docs/concepts/security/pod-security-admission/) guide in the official Kubernetes documentation for more information. - By default, the Camunda Helm chart is configured to use a read-only root file system for the pod. It is advisable to retain this default setting, and no modifications are required in your Helm values files. - Disable privileged containers. This can be achieved by implementing a pod security policy. For more information, see the official [Kubernetes documentation](https://kubernetes.io/docs/concepts/security/pod-security-admission/). - It is possible to modify either the `containerSecurityContext` or the `podSecurityContext`. The following example show the default configuration for the Orchestration Cluster: ```yaml podSecurityContext: runAsNonRoot: true fsGroup: 1001 seccompProfile: type: RuntimeDefault containerSecurityContext: allowPrivilegeEscalation: false privileged: false readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 1001 seccompProfile: type: RuntimeDefault ``` To add any other security constraints to your Helm values files, refer to the official [Kubernetes documentation](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/). - It is recommended to pull images exclusively from a private registry, such as [Amazon ECR](https://aws.amazon.com/ecr/), rather than directly from Docker Hub. Doing so enhances control over the images, avoids rate limits, and improves performance and reliability. Additionally, you can configure your cluster to pull images only from trusted registries. Tools like [Open Policy Agent](https://blog.openpolicyagent.org/securing-the-kubernetes-api-with-open-policy-agent-ce93af0552c3#3c6e) can be used to enforce this restriction. - Please refer to our [installing in an air-gapped environment guide](/self-managed/deployment/helm/configure/registry-and-images/air-gapped-installation.md) when deploying Camunda in Air-gapped environments - Open Policy Agent can also be used to [allowlist Ingress hostnames](https://www.openpolicyagent.org/docs/latest/kubernetes-tutorial/#4-define-a-policy-and-load-it-into-opa-via-kubernetes). #### Required network traffic If you enforce network policies, a default-deny posture blocks traffic Camunda depends on. Allow the following flows for a single-namespace installation. For a deployment split across namespaces, see [install the deployment topology](/self-managed/deployment/helm/install/topology/index.md#allow-required-network-traffic). | Direction | Source | Destination | Ports | Purpose | | :-------- | :------------------------------ | :-------------------- | :----------------------- | :--------------------------------------------------- | | Ingress | Ingress controller | Orchestration Cluster | `8080/TCP`, `26500/TCP` | REST API, web applications, and gRPC clients | | Internal | Orchestration Cluster | Orchestration Cluster | `26501/TCP`, `26502/TCP` | Gateway-to-broker and inter-broker communication | | Internal | Connectors | Orchestration Cluster | `8080/TCP`, `26500/TCP` | Activate jobs and call the Orchestration Cluster API | | Internal | Camunda Hub | Orchestration Cluster | `8080/TCP`, `26500/TCP` | Deploy processes and call the API | | Internal | Orchestration Cluster | Management Identity | `80/TCP` | Resolve users, groups, and authorizations | | Egress | All Camunda pods | Cluster DNS | `53/TCP`, `53/UDP` | Resolve service names | | Egress | Orchestration Cluster, Optimize | Secondary storage | `9200/TCP` or `5432/TCP` | Elasticsearch/OpenSearch, or the RDBMS vendor port | | Egress | All Camunda pods | Identity provider | Provider HTTPS port | Authenticate users and clients | | Egress | Orchestration Cluster | Document store | Provider HTTPS port | Store and retrieve documents | | Probes | Kubelet, Prometheus | Orchestration Cluster | `9600/TCP` | Readiness, liveness, and metrics | | Probes | Kubelet, Prometheus | Camunda Hub, Optimize | `8091/TCP`, `8092/TCP` | Readiness, liveness, and metrics | Restrict each rule to the specific workloads involved rather than allowing unrestricted namespace traffic. :::note Service ports can differ from the internal component ports listed above, depending on your release name and values. Confirm the ports your installation actually exposes against the rendered chart with `helm template`. ::: ### Observability and monitoring The following resources and configuration options are important to keep in mind regarding observability and monitoring: - It is possible to enable integration with Prometheus, a popular monitoring solution, in the Camunda Helm chart. This can be configured by adding the following configuration below to your preferred Helm values file: ```yaml prometheusServiceMonitor: enabled: true ``` - A tool such as [Loki](https://grafana.com/oss/loki/) can be used for the retention and archival of logs. It can also be used to aggregate logs. ## Create a production `values.yaml` In the split topology, each release has its own values file. The Hub release contains Camunda Hub and Management Identity. The orchestration release contains the Orchestration Cluster and Connectors. Optimize runs either in the orchestration release or in its own release, one for each Physical Tenant. A `combined` release has a single values file that holds the settings for every component it enables. The [deployment topology install guide](/self-managed/deployment/helm/install/topology/index.md) provides complete 8.10 examples for every release role. It also explains how to: - Register remote Orchestration Cluster, Optimize, and Connectors clients with central Management Identity. - Configure Camunda Hub to connect to an Orchestration Cluster in another namespace. - Share matching client-secret values across namespace-scoped Kubernetes Secrets. - Use the correct cross-namespace Management Identity service URL. ## Next steps ### Upgrade and maintenance - Make sure to follow our [upgrade guide](/self-managed/upgrade/helm/index.md) when performing the upgrade on your Helm chart. - Ensure your Kubernetes secrets are created before installing or upgrading the Helm chart. For details, see the [secret management guide](/self-managed/deployment/helm/configure/secret-management.md). ### Adding more orchestration clusters Camunda 8 supports running multiple orchestration clusters in separate namespaces. This setup allows you to isolate environments such as development, staging, and production, while sharing infrastructure resources. To add another orchestration cluster, see [add another Orchestration Cluster](/self-managed/deployment/helm/install/topology/orchestration-release.md#add-another-orchestration-cluster). ### Running benchmarks If you would like to run benchmarks on the platform, refer to the Camunda 8 benchmark [community project](https://github.com/camunda-community-hub/camunda-8-benchmark). ### Reference architectures You can lean more about Camunda production deployment and available deployment architectures in [Camunda Deployment Reference Architecture](/self-managed/reference-architecture/reference-architecture.md) section of our documentation. --- ## Install Camunda with Helm for development Use this guide to quickly install the Camunda 8 orchestration cluster for testing and development. :::tip Recommended local setup For local development, use the [kind tutorial](/self-managed/deployment/helm/cloud-providers/kind.md) by default. Camunda tests that setup automatically. Use this guide when you need only the Orchestration Cluster on an existing Kubernetes cluster, with no external database and no identity provider. For example, use it to try the Helm chart or to follow a training course. ::: :::note In this guide, you deploy the Orchestration Cluster with Basic authentication and RDBMS (embedded H2) as secondary storage. For a full deployment with all components (Optimize, Web Modeler, Console, Management Identity, and Keycloak), follow our [kind tutorial](/self-managed/deployment/helm/cloud-providers/kind.md). For production environments, see the [production installation guide](/self-managed/deployment/helm/install/production/index.md). ::: ## Prerequisites - **Kubernetes cluster**: A functioning Kubernetes cluster with [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) access and block-storage persistent volumes for stateful components. - **Helm**: The Helm CLI v4 installed. See [Installing Helm](https://helm.sh/docs/intro/install/). ## Orchestration Cluster only The Helm chart deploys the Camunda Orchestration Cluster with **Basic authentication** and **RDBMS** as secondary storage (embedded H2), intended only for testing and development. No external database or identity provider is required. :::warning H2 topology limitations Embedded H2 is non-production only and valid only with a single broker and a single partition. Do not run embedded H2 with a multi-broker Helm topology. H2 is local to each broker and does not provide shared secondary storage, which can cause incomplete or inconsistent query results. ::: In production, Camunda 8 is typically deployed together with additional components such as Optimize, Web Modeler, and Console, which require OIDC-based authentication and external databases. For a full local deployment with all components, follow our [kind tutorial](/self-managed/deployment/helm/cloud-providers/kind.md). 1. **Create a namespace to install the platform on Kubernetes:** ```bash kubectl create namespace orchestration ``` Output: ```bash namespace/orchestration created ``` 2. **Add the Helm repository:** To install the Camunda 8 Self-Managed [Helm chart](https://helm.sh/docs/topics/charts/), add the [Helm repository](https://helm.sh/docs/topics/chart_repository/) with the following command: ```bash helm repo add camunda https://helm.camunda.io helm repo update ``` 3. **Install the Helm chart:** ```bash helm install camunda camunda/camunda-platform \ --values https://raw.githubusercontent.com/camunda/camunda-platform-helm/main/charts/camunda-platform-8.11/values-quick-install.yaml \ -n orchestration ``` The Helm chart provides the [`values-quick-install.yaml`](https://github.com/camunda/camunda-platform-helm/blob/main/charts/camunda-platform-8.11/values-quick-install.yaml) file. This file enables the RDBMS exporter and sets a file-based embedded H2 database as secondary storage. It also sets a single broker and a single partition. The file contains the following values: ```yaml reference referenceLinkText="Source" title="values-quick-install.yaml" https://github.com/camunda/camunda-platform-helm/blob/main/charts/camunda-platform-8.11/values-quick-install.yaml ``` If you need multiple brokers, switch to a shared external backend (for example, PostgreSQL) instead of H2. If you already have an invalid H2 setup: 1. For local/dev use, reduce to `clusterSize=1`, `partitionCount=1`, `replicationFactor=1` and use file-based H2. 2. For clustered use, migrate secondary storage to an external persistent backend. Starting with Camunda 8.9, the Helm chart no longer provisions Elasticsearch by default. You must explicitly enable a secondary storage backend (RDBMS, Elasticsearch, or OpenSearch) in your Helm values. 4. **Access the components:** Use the default credentials: ``` username: demo password: demo ``` Set up port-forwarding to access the services: ```bash # Zeebe Gateway (for gRPC and REST API) kubectl port-forward svc/camunda-zeebe-gateway 26500:26500 -n orchestration kubectl port-forward svc/camunda-zeebe-gateway 8080:8080 -n orchestration # Connectors kubectl port-forward svc/camunda-connectors 8088:8080 -n orchestration ``` **Verify the installation:** Test the [Zeebe Gateway](/reference/glossary.md#zeebe-gateway) HTTP endpoint (Orchestration Cluster REST API): ```bash curl -u demo:demo http://localhost:8080/v2/topology ``` You should see a JSON response with the cluster topology information. Available services: - **Operate:** [http://localhost:8080/operate](http://localhost:8080/operate) - Monitor process instances - **Tasklist:** [http://localhost:8080/tasklist](http://localhost:8080/tasklist) - Complete user tasks - **Admin:** [http://localhost:8080/admin](http://localhost:8080/admin) - User and permission management - **Connectors:** [http://localhost:8088](http://localhost:8088) - External system integrations - **Zeebe Gateway (gRPC):** localhost:26500 - Process deployment and execution - **Zeebe Gateway (HTTP):** [http://localhost:8080](http://localhost:8080) - Orchestration Cluster REST API :::note In Camunda 8.8+, Operate, Tasklist, and Admin are integrated into the Orchestration Cluster and share the same endpoint (port 8080). ::: ## Full Cluster To deploy the full Camunda 8 platform with all components (Optimize, Web Modeler, Console, Management Identity, and Keycloak), follow our [kind tutorial](/self-managed/deployment/helm/cloud-providers/kind.md). The full deployment requires OIDC-based authentication and [deploying required dependencies with Kubernetes operators](/self-managed/deployment/helm/configure/operator-based-infrastructure.md) for PostgreSQL, Elasticsearch, and Keycloak. ## Troubleshoot installation issues ### A pod stays in `Pending` state **Observed behavior:** `kubectl get pods` shows a pod stuck in `Pending` and it never starts. **Why this happens:** The scheduler couldn't place the pod onto any node, most often because the cluster doesn't have enough CPU, memory, or persistent volume capacity to satisfy the pod's requests. **How to fix:** 1. Check the scheduler's reason for the pending pod: ```shell kubectl describe pods ``` 2. Look for `Events` entries such as `Insufficient cpu`, `Insufficient memory`, or an unbound `PersistentVolumeClaim`. 3. Add node capacity, or reduce resource requests in your values file, to match what's actually available. ### The Orchestration Cluster fails with `Cannot determine the database vendor` **Observed behavior:** The orchestration pod restarts, and its log shows `Cannot determine the database vendor ... from JDBC URL ''`. **Why this happens:** You enabled the RDBMS exporter, but `orchestration.data.secondaryStorage.rdbms.url` is empty. The Helm chart does not set a default URL, and Camunda 8.10 and later do not select embedded H2 for an empty URL. **How to fix:** Install with the values file from step 3 of [Orchestration Cluster only](#orchestration-cluster-only), or set `orchestration.data.secondaryStorage.rdbms.url` to a JDBC URL. For embedded H2, use `jdbc:h2:file:/usr/local/camunda/data/h2db`. ### A pod is running but never becomes ready **Observed behavior:** `kubectl get pods` shows a pod in `Running` state, but it doesn't reach `Ready`, or it restarts repeatedly. **Why this happens:** The container starts but fails its readiness or liveness probe, commonly due to a misconfiguration (wrong database URL, missing secret) or a dependency (secondary storage, Keycloak) that isn't available yet. **How to fix:** 1. Check the container's logs for the actual error: ```shell kubectl logs -f ``` 2. If the pod is restarting, check the log from the previous crashed instance: ```shell kubectl logs --previous ``` 3. Cross-reference the error against your values file for the affected component. ## Install a specific version By default, the Camunda Helm chart installs the latest version of the [Camunda 8 applications](/reference/supported-environments.md). Because Helm chart and application versions are released independently, their version numbers differ. For details, see the [Camunda 8 Helm Chart Version Matrix](https://helm.camunda.io/camunda-platform/version-matrix/). To install the latest version of the chart and its application dependencies, run: ```shell helm install camunda camunda/camunda-platform \ --values https://helm.camunda.io/camunda-platform/values/values-latest.yaml ``` This command sets only the application image versions. Because the chart doesn't provision a secondary storage backend by default, also enable one, for example with the values file from step 3 of [Orchestration Cluster only](#orchestration-cluster-only). To install a specific chart version, use the `--version` flag with the chart version number. For example, the chart version for Camunda 8.8 is `13`: ```shell helm install camunda camunda/camunda-platform --version 13 \ --values https://helm.camunda.io/camunda-platform/values/values-v8.8.yaml ``` Specifying only the major chart version (for example, `13`) installs the latest available `13.x.y` release. You can also specify a minor version (for example, `12.6`) to install the latest `12.6.y` release. If you are unsure which chart version corresponds to your Camunda application version, run: ```shell helm search repo -l camunda/camunda-platform ``` This command lists all available chart versions and their corresponding application versions. ## Notes and requirements - **Zeebe** supports Kubernetes startup and liveness probes. See [Gateway health probes](/self-managed/components/orchestration-cluster/zeebe/configuration/gateway-health-probes.md). - **Zeebe** must be deployed as a [StatefulSet](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/) to preserve cluster node identities. StatefulSets require persistent storage, which you must provision in advance. The type of storage depends on your cloud provider. - **Docker pull limits** apply when downloading Camunda 8 images from Docker Hub. To avoid disruptions, authenticate with Docker Hub or use a mirror registry. - **Air-gapped environments** require additional configuration. See [Helm chart air-gapped environment installation](/self-managed/deployment/helm/configure/registry-and-images/air-gapped-installation.md). - **Full deployment**: To deploy all Camunda 8 components with OIDC authentication and Kubernetes operators, follow our [kind tutorial](/self-managed/deployment/helm/cloud-providers/kind.md) or the [cloud provider guides](/self-managed/deployment/helm/cloud-providers/index.md). ## Next steps - Explore the [Camunda Reference Architectures](/self-managed/reference-architecture/reference-architecture.md) to learn how to run Camunda 8 in production. ## Additional resources - [Deploy infrastructure with Kubernetes operators](/self-managed/deployment/helm/configure/operator-based-infrastructure.md) — deploy PostgreSQL, Elasticsearch, and Keycloak using official Kubernetes operators for the full platform. - [Helm chart Amazon OpenSearch service usage](/self-managed/deployment/helm/configure/database/using-external-opensearch.md): configure Camunda to use Amazon OpenSearch Service instead of Elasticsearch. - [Getting started with document handling](/self-managed/concepts/document-handling/overview.md) — configure document storage and management in Camunda 8. - [Production installation](/self-managed/deployment/helm/install/production/index.md) — configure and install the helm chart for production environments. - [Helm Configuration](/self-managed/deployment/helm/configure/index.md) - customize your installation by modifying the Helm chart configuration. --- ## Install the Camunda Hub release The Hub release is the [management plane](/reference/glossary.md#management-plane). It runs Camunda Hub and Management Identity and owns the inventory of every Orchestration Cluster in the deployment. When creating a new topology, install the Hub release before its Orchestration Cluster releases. For the prerequisites, Secrets, and network policies this page assumes, see [install the deployment topology](./index.md). ## What a Hub release deploys A Hub release runs Camunda Hub and Management Identity, and nothing else. It always uses the 8.10 chart, even when it manages Orchestration Clusters on older chart versions. Upgrading from 8.9? See [upgrade Camunda 8.9 to 8.10 using Helm](/self-managed/upgrade/helm/890-to-8100.md). After you [create `hub-values.yaml`](#create-hub-valuesyaml), the release role it sets, `global.topology.mode: hub`, suppresses the chart's Orchestration Cluster, Optimize, and Connectors workloads, so you don't configure them here. The release requires the following: | Requirement | Reason | | ----------------------------------------------------- | ------------------------------------------------------------------ | | `identity.enabled: true` | Management Identity runs in this release, and only in this release | | OIDC authentication | Hub topology connections are represented with OIDC bearer tokens | | The only release declaring `global.topology.clusters` | One authoritative inventory prevents client and endpoint drift | The chart fails the render with a `[camunda][error]` message if `identity.enabled: true` or OIDC authentication is missing. It doesn't check that `global.topology.clusters` is set. Without clusters, Camunda Hub lists no Orchestration Clusters. ## The cluster record Each entry in `global.topology.clusters` is the single source for both Management Identity presets and Camunda Hub inventory, so client IDs, audiences, roles, and endpoints can't drift apart. Each record declares a stable unique `id`, the enabled workload components with their client and audience identifiers, the context paths, and the namespace and release name used to derive service endpoints. Each component in a cluster record needs its own client ID, and the `orchestration` and `optimize` components also need an audience. Regardless of the identity provider, every client ID and audience must be unique: the chart rejects a value that another component, another record, or one of the Hub release's own clients already uses. The chart defaults, such as `orchestration` and `orchestration-api`, can belong to one record at most. Give the other records their own, for example `orchestration-`, `optimize-`, and `connectors-`. With Keycloak, clients with the same ID in one realm would also be one client, so the clusters would overwrite each other's redirect URLs. | Field | Purpose | | --------------------------------- | ---------------------------------------------------------------------------------------------- | | `id` | Stable unique identifier for the cluster. Changing it creates a new Hub inventory entry | | `name` | Display name in Camunda Hub | | `namespace`, `releaseName` | Used to generate in-cluster service endpoints | | `host` | Public hostname of the Orchestration Cluster | | `version` | The Camunda version deployed by that release | | `architecture` | `unified` (default) or `legacy`. Set `legacy` for a chart 8.7 cluster | | `contextPaths` | Sub-paths each component is served on | | `components.` | Enabled state, `clientId`, `audience`, `redirectUrl`, and `secret` for each workload component | | `components..roleName` | A per-cluster role name, instead of the shared canonical role | | `physicalTenants` | One entry per Physical Tenant that runs its own Optimize release | ## Create `hub-values.yaml` Create a values file that sets the `hub` role, configures Camunda Hub and Management Identity, and declares one record for each Orchestration Cluster the Hub manages. The cluster records can describe Orchestration Clusters in different environments: ```yaml global: host: hub.example.com ingress: enabled: true className: nginx tls: enabled: true secretName: hub-tls security: authentication: method: oidc topology: mode: hub clusters: - id: orchestration name: Orchestration namespace: orchestration releaseName: camunda host: orchestration.example.com # Match this to the Camunda version deployed by your selected chart. version: "8.10.x" contextPaths: orchestration: /orchestration optimize: /optimize connectors: /connectors components: orchestration: enabled: true clientId: orchestration audience: orchestration-api redirectUrl: https://orchestration.example.com/orchestration secret: existingSecret: orchestration-oidc existingSecretKey: client-secret optimize: enabled: true clientId: optimize audience: optimize-api redirectUrl: https://orchestration.example.com/optimize secret: existingSecret: optimize-oidc existingSecretKey: client-secret connectors: enabled: true clientId: connectors secret: existingSecret: connectors-oidc existingSecretKey: client-secret identity: keycloak: url: protocol: https host: login.example.com port: 443 contextPath: / realm: /realms/camunda-platform auth: adminUser: admin secret: existingSecret: keycloak-admin existingSecretKey: password auth: enabled: true type: KEYCLOAK publicIssuerUrl: https://login.example.com/realms/camunda-platform issuerBackendUrl: https://login.example.com/realms/camunda-platform authUrl: https://login.example.com/realms/camunda-platform/protocol/openid-connect/auth tokenUrl: https://login.example.com/realms/camunda-platform/protocol/openid-connect/token jwksUrl: https://login.example.com/realms/camunda-platform/protocol/openid-connect/certs camundaHub: redirectUrl: https://hub.example.com/modeler identity: enabled: true contextPath: /identity firstUser: secret: existingSecret: identity-first-user existingSecretKey: password externalDatabase: enabled: true host: identity-postgresql.example.com port: 5432 username: identity database: identity secret: existingSecret: identity-database existingSecretKey: password camundaHub: enabled: true contextPath: /modeler restapi: mail: fromAddress: noreply@example.com externalDatabase: url: jdbc:postgresql://hub-postgresql.example.com:5432/hub username: hub secret: existingSecret: hub-database existingSecretKey: password pusher: client: secret: existingSecret: hub-pusher existingSecretKey: app-key secret: existingSecret: hub-pusher existingSecretKey: app-secret ``` The `optimize` record under `components` describes the Optimize instance for the cluster's default Physical Tenant. To map additional tenants, add a `physicalTenants` list. See [configure Physical Tenants across releases](./physical-tenants.md). Adapt the Keycloak endpoints and client configuration for your environment. See [external Keycloak](/self-managed/deployment/helm/configure/authentication-and-authorization/external-keycloak.md). ## Describe a chart 8.7 cluster :::note Minimum chart versions This page needs Helm chart 15.0.0 or later for 8.10 releases. For the minimum chart version per Camunda version, see [release roles](/self-managed/deployment/helm/install/topology/index.md#release-roles). ::: An 8.10 Hub manages Orchestration Cluster releases on the 8.7, 8.8, 8.9, and 8.10 charts. Records for 8.8, 8.9, and 8.10 clusters all take the standard shape shown above. To connect releases that already run, see [connect existing clusters to Hub](/self-managed/upgrade/helm/connect-existing-clusters.md). Chart 8.7 predates the unified Orchestration Cluster, so it runs Zeebe, Zeebe Gateway, Operate, and Tasklist as separate workloads on separate services. Its record needs `architecture: legacy` and the names of those services: ```yaml global: topology: mode: hub clusters: - id: legacy-a name: Legacy A namespace: orchestration-legacy releaseName: camunda host: legacy-a.example.com version: "8.7.38" architecture: legacy contextPaths: orchestration: "" optimize: /optimize connectors: /connectors components: orchestration: enabled: true clientId: orchestration-legacy-a audience: orchestration-legacy-a-api redirectUrl: https://legacy-a.example.com serviceName: camunda-zeebe gatewayServiceName: camunda-zeebe-gateway operateServiceName: camunda-operate tasklistServiceName: camunda-tasklist restUrl: http://camunda-zeebe-gateway.orchestration-legacy.svc.cluster.local:8080/zeebe readinessUrl: http://camunda-zeebe-gateway.orchestration-legacy.svc.cluster.local:9600/zeebe/actuator/health/readiness secret: existingSecret: orchestration-legacy-a-oidc existingSecretKey: client-secret ``` `architecture: legacy` changes two things in the generated inventory. It addresses the split Operate, Tasklist, and Zeebe Gateway services instead of one Orchestration Cluster service, and it omits the Orchestration Admin component, which chart 8.7 doesn't have. It also omits the cluster's `authorizations` block. The service names depend on that release's own release name, so adjust them if it isn't `camunda`. For the workload side of a chart 8.7 release, see [requirements by chart version](./orchestration-release.md#requirements-by-chart-version). ## Provider setup outside the chart Management Identity can administer clients only in an external Keycloak instance, and only when you provide Keycloak administrator credentials. For Microsoft Entra ID or a generic OIDC provider, first complete the provider setup, including Management Identity's confidential client, initial administrator claims, Hub clients, mapping rules, and each workload client. Then add the matching client IDs and audiences to the topology records. Management Identity initializes only its permission and role model from those records. See [external OIDC provider](/self-managed/deployment/helm/configure/authentication-and-authorization/external-oidc-provider.md). ## Role assignment across clusters By default, each cluster record that declares an `orchestration` component adds that component's permissions to the shared `Orchestration` role, and each record that declares an `optimize` component does the same for the shared `Optimize` role. So assigning the `Optimize` role grants access to the Optimize instance of every cluster that declares one, but not to those clusters' Orchestration Clusters. To authorize users per cluster, give each component its own role name: ```yaml global: topology: clusters: - id: production-a components: orchestration: roleName: Orchestration production-a optimize: roleName: Optimize production-a ``` :::warning Identity preset initialization is additive. Removing or renaming a topology entry doesn't delete the corresponding clients, resource servers, permissions, or roles from Keycloak or Management Identity. Remove obsolete resources explicitly after the related workload is retired. ::: Existing Keycloak users don't automatically receive roles added by a later topology update. Assign the canonical roles or configured per-cluster roles through your normal access-management process. ## Install the release ```sh helm install camunda camunda/camunda-platform \ --version "$HUB_CHART_VERSION" \ --namespace hub \ --create-namespace \ --values hub-values.yaml ``` Confirm the Hub and Management Identity pods are ready, and that you can sign in to Camunda Hub, before you install an Orchestration Cluster release. ## Next steps - [Install an Orchestration Cluster release](./orchestration-release.md) - [Camunda Hub configuration properties](/self-managed/components/hub/configuration/properties.md) --- ## Install the Camunda 8.10 deployment topology :::note Minimum chart versions This page needs Helm chart 15.0.0 or later for 8.10 releases. For the minimum chart version per Camunda version, see [release roles](#release-roles). ::: Install Camunda 8.10 Self-Managed as separate Helm releases: one Hub release, one release per Orchestration Cluster, and one Optimize release per Physical Tenant. This is the baseline topology for a new 8.10 production deployment. Each release declares its role through `global.topology.mode`, so the management plane and each Orchestration Cluster have independent lifecycles. For the architecture behind this model and its constraints, see [deployment topology](/self-managed/reference-architecture/reference-architecture.md#deployment-topology). The Hub release can be shared across environments. If you're adding an Orchestration Cluster to an existing Hub, update that Hub's cluster inventory and follow the Orchestration Cluster installation steps. For other situations, including evaluation with a single `combined` release, see [choose your topology](../index.md#choose-your-topology). Camunda recommends Helm CLI v4 and supports it for the full release cycles of Camunda 8.9 and 8.10. Camunda supports Helm CLI v3 (3.10 or later) until February 10, 2027, when upstream support ends. After February 10, 2027, Camunda no longer supports Helm CLI v3. Customers who continue to use Helm CLI v3 after that date do so at their own risk. ## Release roles `global.topology.mode` selects what a release deploys: | Role | Chart versions | Deploys | | :-------------------------------------------- | :--------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`hub`](./hub-release.md) | 15.0.0+ (Camunda 8.10) | Camunda Hub and Management Identity. The only release that declares `global.topology.clusters` | | [`orchestration`](./orchestration-release.md) | 15.0.0+ (Camunda 8.10), 14.11.0+ (Camunda 8.9), 13.14.0+ (Camunda 8.8), 12.14.0+ (Camunda 8.7) | 8.10 and 8.9: one Orchestration Cluster and Connectors. 8.8: the same, plus bundled Elasticsearch. 8.7: Zeebe, Zeebe Gateway, Operate, Tasklist, Optimize, Connectors, and bundled Elasticsearch | | [`optimize`](./optimize-release.md) | 15.0.0+ (Camunda 8.10) | Optimize only, for one Physical Tenant | | `combined` | All; the implicit behavior of charts without `global.topology` | Every enabled component in one release. This is the default | :::warning Older charts ignore the role 8.7, 8.8, and 8.9 charts older than the minimum versions above have no `global.topology` key. They silently ignore `global.topology.mode` and deploy a combined release, so check the chart version before you set the role. ::: The chart validates some of each role's requirements at render time and fails with a `[camunda][error]` message naming the missing value. It doesn't validate all of them. Before you install, review the per-role requirements on the installation pages linked in the Role column of the table above. ## Install order Install in dependency order, and confirm each release is healthy before starting the next. 1. Namespace-local Secret projections and TLS certificates. 2. The [Hub release](./hub-release.md). 3. One or more [Orchestration Cluster releases](./orchestration-release.md). 4. One [Optimize release](./optimize-release.md) per Physical Tenant, including the default tenant. The Hub release comes first because it runs Management Identity and, for a Keycloak-administered deployment, creates the OIDC clients the other releases authenticate with. The Hub release always deploys from the 8.10 chart. Each Orchestration Cluster release can deploy from the 8.7, 8.8, 8.9, or 8.10 chart, using its own chart and values, so clusters upgrade independently of the Hub. A chart 8.7 cluster runs split Zeebe, Operate, and Tasklist workloads, so its Hub cluster record must [set `architecture: legacy`](./hub-release.md#describe-a-chart-87-cluster). A chart 8.7 release also still runs Optimize in-release, so it doesn't follow the one-Optimize-release-per-tenant model. See [requirements by chart version](./orchestration-release.md#requirements-by-chart-version). ## Before you begin The chart deploys the Kubernetes workloads, services, secrets wiring, and volumes, and generates the Management Identity presets and Camunda Hub cluster inventory from `global.topology.clusters`. Everything else is yours to provide, and every URL you configure must be reachable from the release that uses it. Prepare the following resources: - An OpenID Connect (OIDC) provider that every namespace can reach, with a pinned issuer. The examples use an external Keycloak instance with Management Identity-managed client registration. - Separate public hostnames and TLS certificates for the Hub and orchestration namespaces, plus cross-namespace or cross-cluster DNS, routing, and TLS trust. - External PostgreSQL databases for Management Identity and Camunda Hub. - A supported secondary storage backend for the Orchestration Cluster, and Elasticsearch or OpenSearch for Optimize. Index retention and deletion, including after a Helm uninstall, is your responsibility. - Network policies that permit Domain Name System (DNS) traffic and the required cross-namespace service traffic. Camunda 8.10 bundles no Elasticsearch, PostgreSQL, or Keycloak subcharts, so these must exist before you install. See [deploy required dependencies](/self-managed/deployment/helm/configure/operator-based-infrastructure.md). The examples use `camunda` as the release name in every namespace, `hub` as the Hub namespace, and `orchestration` as the orchestration namespace. If you change a release name or namespace, update every Kubernetes service name that references it. Select supported chart versions from the [Helm chart version matrix](https://helm.camunda.io/camunda-platform/version-matrix/), then set them before installation. The Hub and Optimize releases always use the 8.10 (15.x) chart. Each Orchestration Cluster release uses the chart for its own Camunda version: ```sh export HUB_CHART_VERSION=<15.x-chart-version> export ORCHESTRATION_CHART_VERSION= ``` ## Allow required network traffic If you enforce NetworkPolicies, allow the following traffic in addition to your database and secondary-storage connections: | Source | Destination | Ports | Purpose | | ----------------------- | --------------------------------------------------------- | ----------------------- | ------------------------------------------------------- | | All namespaces | Cluster DNS | `53/TCP`, `53/UDP` | Resolve cross-namespace service names | | Camunda Hub | `camunda-zeebe-gateway.orchestration.svc.cluster.local` | `26500/TCP`, `8080/TCP` | Deploy processes and call the Orchestration Cluster API | | Camunda Hub | `camunda-zeebe.orchestration.svc.cluster.local` | `9600/TCP` | Check Orchestration Cluster application readiness | | Camunda Hub | `camunda-optimize..svc.cluster.local` | `80/TCP` | Check Optimize readiness | | Camunda Hub | `camunda-connectors.orchestration.svc.cluster.local` | `8080/TCP` | Check Connectors readiness | | Orchestration namespace | `camunda-identity.hub.svc.cluster.local` | `80/TCP` | Use central Management Identity | | Optimize namespace | `camunda-identity.hub.svc.cluster.local` | `80/TCP` | Use central Management Identity | | All namespaces | Your OIDC provider | Provider HTTPS port | Authenticate users and clients | Restrict policies to the listed workloads and namespaces instead of allowing unrestricted cross-namespace traffic. ## Manage secrets across namespaces Kubernetes Secrets are namespace-scoped. When Management Identity administers an external Keycloak, create each workload client secret in both the Hub namespace and the workload namespace with identical values. With another OIDC provider, Management Identity doesn't consume the workload client secrets, so project them only into the workload namespaces. The following examples use these Secrets: | Secret | Key | Required namespaces | Purpose | | ------------------------ | ----------------------- | ----------------------------------- | -------------------------------------------------------- | | `keycloak-admin` | `password` | `hub` | Keycloak administration for Management Identity | | `identity-first-user` | `password` | `hub` | Initial Management Identity user | | `identity-database` | `password` | `hub` | Management Identity database | | `hub-database` | `password` | `hub` | Camunda Hub database | | `hub-pusher` | `app-key`, `app-secret` | `hub` | Stable Camunda Hub WebSocket credentials across upgrades | | `orchestration-oidc` | `client-secret` | `hub`, `orchestration` | Orchestration OIDC client | | `connectors-oidc` | `client-secret` | `hub`, `orchestration` | Connectors OIDC client | | `optimize-oidc` | `client-secret` | `hub`, Optimize namespace | Optimize OIDC client, one per Physical Tenant | | `secondary-storage` | `password` | `orchestration`, Optimize namespace | Elasticsearch password for Orchestration and Optimize | | `hub-tls` | `tls.crt`, `tls.key` | `hub` | Hub Ingress TLS | | `orchestration-tls` | `tls.crt`, `tls.key` | `orchestration` | Orchestration HTTP Ingress TLS | | `orchestration-grpc-tls` | `tls.crt`, `tls.key` | `orchestration` | Orchestration gRPC Ingress TLS | Use an external secret manager to synchronize the values. Don't store production credentials directly in a Helm values file. See [secret management](/self-managed/deployment/helm/configure/secret-management.md). ## Deploy with GitOps The topology values are deterministic and don't require cluster discovery or imperative deployment tooling. Store each release's values with its own Helm release definition. Apply resources in the [install order](#install-order). For Flux, make each orchestration `HelmRelease` depend on the Hub release: ```yaml spec: dependsOn: # This is the Flux HelmRelease metadata.name, not Helm's releaseName. - name: namespace: hub ``` For Argo CD, use sync waves or separate Applications so the Hub release becomes healthy before orchestration releases are synchronized, and each orchestration release before its Optimize releases. For Keycloak-managed registration, client Secret names can be identical across namespaces, but Kubernetes Secrets remain namespace-scoped. Project both copies from the same external secret source to prevent drift. The standalone chart-managed PersistentVolumeClaims for Management Identity, Optimize, and Connectors render when the corresponding component's `persistence.enabled` value is `true`, even when the release topology suppresses that component's workload. This behavior keeps PVC ownership declarative and produces the same desired resources with Helm, Argo CD, and Flux. Set `persistence.enabled` to `false` only after you no longer need the chart to manage that claim and have verified your GitOps pruning and storage reclaim policies. :::warning Orchestration Cluster broker PVCs are StatefulSet volume claim templates and don't follow this standalone PVC behavior. Changing a release to `hub` mode suppresses the Orchestration Cluster StatefulSet. Preserve and migrate broker storage separately when you move an existing cluster between releases or namespaces. ::: ## Upgrade an existing deployment This guide covers fresh releases. To upgrade an existing 8.9 deployment, first complete the in-place version upgrade while preserving the existing release name, namespace, Orchestration Cluster primary storage, and external data services. See [upgrade Camunda 8.9 to 8.10 using Helm](/self-managed/upgrade/helm/890-to-8100.md). Adopting this topology afterwards is a separate operation with its own data and rollback planning. See [move from a combined release to the split topology](/self-managed/upgrade/helm/combined-to-split-topology.md). ## Existing configurations The default `global.topology.mode: combined` preserves the existing single-release and single-namespace behavior. Existing multi-namespace configurations that use `global.identity.auth.*.alwaysRegister`, component authentication values under disabled components, or manual `camundaHub.restapi.clusters` remain supported. This release doesn't deprecate or remove those values. In `hub` mode, topology values replace the legacy Identity registration presets. An explicitly configured `camundaHub.restapi.clusters` or legacy `webModeler.restapi.clusters` list still takes precedence over generated Hub inventory. Any future removal must retain compatibility for at least one minor release, emit GitOps-visible deprecation warnings with migration guidance, and occur only in the next major chart release according to the Helm chart deprecation policy. --- ## Install an Optimize release An Optimize release deploys Optimize and no other Camunda component. Install one per Physical Tenant, including the default tenant. :::note Minimum chart versions This page needs Helm chart 15.0.0 or later for 8.10 releases. For the minimum chart version per Camunda version, see [release roles](/self-managed/deployment/helm/install/topology/index.md#release-roles). ::: The `optimize` role requires the 8.10 chart. The 8.7, 8.8, and 8.9 charts support `combined` and `orchestration` only, so an older Orchestration Cluster runs Optimize inside its own release. Install these releases after their [Orchestration Cluster release](./orchestration-release.md) is healthy. ## Why Optimize is its own release Optimize reads exported records from a single Elasticsearch or OpenSearch index prefix, so one instance serves exactly one Physical Tenant. Running those instances inside the orchestration release would tie every tenant's Optimize lifecycle to the Orchestration Cluster's StatefulSet, and the workload release would have to describe tenants it doesn't own. A release whose job is to run one Optimize against one tenant's storage is a separate release. This applies to a cluster with no additional tenants too. Its default Physical Tenant needs one Optimize release if you want analytics. ## Requirements `global.topology.mode: optimize` gates off every component except Optimize, so you don't disable each one by hand. The chart fails the render with a `[camunda][error]` message naming any of the following that's missing. | Requirement | Reason | | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `optimize.enabled: true` | This is the workload the release exists to run | | `global.noSecondaryStorage: false` | Optimize reads exported records from secondary storage | | `optimize.database.elasticsearch.enabled` or `optimize.database.opensearch.enabled` | The chart bundles no Elasticsearch. With neither backend enabled, Optimize reaches no storage at all | | A non-empty `url.host` for the enabled backend | Without a host, Optimize renders an unusable connection node | | `optimize.security.authentication.method: oidc` or `global.identity.auth.enabled: true` | Optimize requires authentication | | `optimize.identity.service.url` or `global.identity.service.url` | This release runs no Identity of its own, so the in-release default can't apply | | `optimize.contextPath`, when the chart renders this release's routing | The shared Ingress emits an Optimize rule only when the context path is set, and an HTTPRoute would match an empty path prefix | | An OIDC issuer, when Optimize uses OIDC | Optimize validates the `iss` claim on every token. Set `optimize.security.authentication.oidc.issuer`, `global.identity.auth.issuer`, or `global.identity.auth.publicIssuerUrl` | Unlike the orchestration release, Optimize accepts `publicIssuerUrl` as its issuer fallback. That works only when your provider mints that exact URL as the `iss` claim, which a pinned issuer guarantees. Setting `issuer` explicitly avoids the ambiguity. See [pin the issuer](./orchestration-release.md#pin-the-issuer). Elasticsearch takes precedence when both backends are enabled. Optimize requires Elasticsearch or OpenSearch and can't use a relational database. An Orchestration Cluster on RDBMS secondary storage can still feed Optimize if it also exports its records to Elasticsearch or OpenSearch. The Orchestration Cluster release must export those records. Because Optimize isn't in that release, the chart doesn't enable the exporter for you. See [export records for Optimize](./orchestration-release.md#export-records-for-optimize). ## Create `optimize-values.yaml` This example is the Optimize release for the Physical Tenant `tenanta` in the cluster `production-a`. Use the OpenSearch equivalents where applicable. ```yaml global: topology: mode: optimize noSecondaryStorage: false identity: service: url: http://camunda-identity.hub.svc.cluster.local:80/identity auth: # The exact "iss" claim your provider mints. Optimize validates it on every token. issuer: https://login.example.com/realms/camunda-platform jwksUrl: https://login.example.com/realms/camunda-platform/protocol/openid-connect/certs optimize: enabled: true contextPath: /optimize-tenanta security: authentication: method: oidc oidc: clientId: optimize-production-a-tenanta audience: optimize-production-a-tenanta-api redirectUrl: https://production-a.example.com/optimize-tenanta secret: existingSecret: optimize-production-a-tenanta-oidc existingSecretKey: client-secret database: elasticsearch: enabled: true external: true url: protocol: https host: elasticsearch.example.com port: 9200 auth: username: camunda secret: existingSecret: secondary-storage existingSecretKey: password # Must exactly equal this tenant's exporter writer prefix. prefix: production-a-tenanta-records env: # Optimize's own indices. Must differ from every writer prefix. - name: CAMUNDA_OPTIMIZE_ELASTICSEARCH_SETTINGS_INDEX_PREFIX value: production-a-tenanta-optimize ``` ## Match the prefixes exactly `optimize.database.elasticsearch.prefix` logs a deprecation warning in chart 15.x. Its replacement is Optimize's own `zeebe.name` property in `optimize.extraConfiguration`. The example uses the chart key because it's the documented way to set the reader prefix across releases. `optimize.database.elasticsearch.prefix` is a reader prefix. It must exactly equal the writer prefix of the exporter for this tenant in the Orchestration Cluster. A mismatch doesn't fail. Optimize starts successfully against the wrong or an empty record set, and displays no process data. Similar-looking prefixes are not sufficient. `CAMUNDA_OPTIMIZE_ELASTICSEARCH_SETTINGS_INDEX_PREFIX` is different: it names where Optimize writes its own indices, and must be unique per Optimize release and distinct from every writer prefix. For the full prefix model across all releases, see [prefixes in the split topology](/self-managed/deployment/helm/configure/database/elasticsearch/configure-elasticsearch-prefix-indices.md#prefixes-in-the-split-topology). ## Give each release its own identity Every Optimize release needs its own OIDC client ID, audience, role name, redirect URL, and secret, and the same values must appear in that tenant's record in the Hub release. Setting a dedicated `roleName` in the Hub cluster record avoids adding this tenant's audience to the shared `Optimize` role, which would grant access across tenants. :::warning You can point several tenants at one shared audience. Those instances are then separated by client identity only, not by authorization. Neither separate credentials nor separate index prefixes make this arrangement authorization isolation. ::: Distinct `roleName` values keep the Optimize role isolated per tenant, but Optimize's logical tenants are a separate mechanism with their own cross-tenant caveat. See [Optimize and Physical Tenants](/self-managed/concepts/physical-tenants/optimize.md#known-limitation-logical-tenants-with-the-same-id-across-physical-tenants) if you reuse the same logical tenant ID across Physical Tenants. ## Route traffic to Optimize The example above renders an Optimize Service and Deployment, but no Ingress or HTTPRoute. Sharing a namespace with the Orchestration Cluster release doesn't help: that release's Ingress routes only to its own services. Before users sign in, route the host and path of `redirectUrl`, here `https://production-a.example.com/optimize-tenanta`, to this release's Optimize Service. Either manage the Ingress, HTTPRoute, or load balancer rule outside the chart, or let this release render its own Ingress: ```yaml global: host: production-a.example.com ingress: enabled: true className: nginx tls: enabled: true secretName: production-a-tls ``` The chart then renders an Ingress rule for `optimize.contextPath` on `global.host`. The host must match `redirectUrl`, and the TLS Secret must exist in this release's namespace. If another Ingress already serves that host, confirm your Ingress controller merges rules from several Ingress resources for one host. ## Install the release ```sh helm install camunda-optimize-tenanta camunda/camunda-platform \ --version "$HUB_CHART_VERSION" \ --namespace orchestration \ --values optimize-values.yaml ``` Use a distinct release name per tenant. Place Optimize releases in the Orchestration Cluster namespace, or in their own namespace. Either way, the release needs its own routing. See [route traffic to Optimize](#route-traffic-to-optimize). ## High availability Optimize can run multiple replicas, but its importer and archiver must be active on only one replica at a time. Enabling them on more than one replica may cause data inconsistencies. See [Optimize system configuration](/self-managed/components/optimize/configuration/system-configuration-platform-8.md#general-settings). ## Next steps - [Configure Physical Tenants across releases](./physical-tenants.md) - [Optimize and Physical Tenants](/self-managed/concepts/physical-tenants/optimize.md) - [Optimize configuration](/self-managed/components/optimize/configuration/system-configuration-platform-8.md) --- ## Install an Orchestration Cluster release An orchestration release is an execution plane. It runs one Orchestration Cluster and Connectors, and connects to the Management Identity service in the Hub release. Install it after the [Hub release](./hub-release.md) is healthy. The same Hub can serve orchestration releases in different environments. ## What an orchestration release deploys `global.topology.mode: orchestration` deploys the Orchestration Cluster and Connectors, and never renders Management Identity or Camunda Hub, even if a converted values file still enables them. Optimize is different. If `optimize.enabled: true` is set, the release still runs Optimize, and the chart then also renders the exporter Optimize reads. In the split topology, set `optimize.enabled: false` here and run Optimize as its [own release](./optimize-release.md). An orchestration release is self-contained. Its existing component values remain authoritative for its enabled state, authentication, storage, scaling, and Kubernetes configuration. `global.topology.mode` selects the release role; it doesn't duplicate component configuration, and the release never declares sibling clusters. | Requirement | Reason | | ----------------------------------------- | ------------------------------------------------------------------------- | | `orchestration.enabled: true` | This is the workload the release exists to run | | `global.identity.auth.enabled: true` | Enables OIDC authentication for the orchestration workloads | | `identity.enabled: false` | Management Identity runs only in the Hub release | | A non-empty `global.identity.service.url` | This release runs no Identity of its own, so it must be told where one is | On the 8.10 chart, the render fails with a `[camunda][error]` message if `orchestration.enabled`, `global.identity.auth.enabled`, or `identity.enabled` is wrong. It doesn't fail if `global.identity.service.url` is empty, so set it yourself. The 8.7, 8.8, and 8.9 charts also check `global.identity.service.url`. They check their own workload keys instead of `orchestration.enabled`. See [requirements by chart version](#requirements-by-chart-version). These checks aren't the complete list of what each chart validates. The component client IDs, audiences, redirect URLs, and secrets must match the clients declared in the matching Hub cluster record. A mismatch authenticates against a client Hub doesn't know about. ## Requirements by chart version An orchestration release can deploy from the 8.7, 8.8, 8.9, or 8.10 chart against an 8.10 Hub. The role is the same; the values it requires differ, because the older charts predate the unified Orchestration Cluster and still bundle management plane dependencies. Chart versions earlier than the minimum versions in [release roles](./index.md#release-roles) ignore `global.topology.mode` and deploy a combined release. From the minimum versions, the `orchestration` role stops rendering Console and Web Modeler on the 8.7, 8.8, and 8.9 charts, even if `console.enabled` or `webModeler.enabled` is `true`. Every version requires `global.identity.auth.enabled: true`, `identity.enabled: false`, and a reachable `global.identity.service.url`. Beyond that: | Chart | Workload to enable | Also required | | :---- | :----------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- | | 8.10 | `orchestration.enabled: true` | Nothing further | | 8.9 | `orchestration.enabled: true` | `identityPostgresql.enabled: false`, `webModelerPostgresql.enabled: false`, `identityKeycloak.enabled: false` | | 8.8 | `orchestration.enabled: true` | `identityPostgresql.enabled: false`, `webModelerPostgresql.enabled: false`, `identityKeycloak.enabled: false` | | 8.7 | `zeebe.enabled: true`, `operate.enabled: true`, `tasklist.enabled: true` | `identityKeycloak.enabled: false`, `identityPostgresql.enabled: false`, `postgresql.enabled: false`, `executionIdentity.enabled: false` | Before you convert an existing release, upgrade it to the latest chart version and the latest Camunda patch version. The management plane databases belong to the Hub release. The 8.7, 8.8, and 8.9 charts therefore reject them here. If you leave them enabled, the release deploys a second Management Identity or Hub database beside the one the Hub release already owns. The 8.8 and 8.9 charts reject `identityKeycloak.enabled: true` because Management Identity is off in this role, and they don't run Keycloak without it. These keys default to `false`, except `identityKeycloak.enabled` on the 8.7 chart, which defaults to `true`. The check matters when you convert an existing combined release, whose values file may already enable them. See [connect existing clusters to Hub](/self-managed/upgrade/helm/connect-existing-clusters.md). A chart 8.7 release also needs `architecture: legacy` in its Hub cluster record, so the inventory addresses its split Zeebe, Zeebe Gateway, Operate, and Tasklist services. See [describe a chart 8.7 cluster](./hub-release.md#describe-a-chart-87-cluster). The examples on this page use the 8.10 chart. ## Create `orchestration-values.yaml` ```yaml global: host: orchestration.example.com ingress: enabled: true className: nginx tls: enabled: true secretName: orchestration-tls security: authentication: method: oidc topology: mode: orchestration identity: service: url: http://camunda-identity.hub.svc.cluster.local:80/identity auth: enabled: true type: KEYCLOAK # Must be the exact "iss" claim your provider mints. See Pin the issuer. issuer: https://login.example.com/realms/camunda-platform publicIssuerUrl: https://login.example.com/realms/camunda-platform issuerBackendUrl: https://login.example.com/realms/camunda-platform authUrl: https://login.example.com/realms/camunda-platform/protocol/openid-connect/auth tokenUrl: https://login.example.com/realms/camunda-platform/protocol/openid-connect/token jwksUrl: https://login.example.com/realms/camunda-platform/protocol/openid-connect/certs identity: enabled: false orchestration: enabled: true contextPath: /orchestration security: authentication: oidc: clientId: orchestration audience: orchestration-api redirectUrl: https://orchestration.example.com/orchestration secret: existingSecret: orchestration-oidc existingSecretKey: client-secret data: secondaryStorage: type: elasticsearch elasticsearch: url: https://elasticsearch.example.com:9200 auth: username: camunda secret: existingSecret: secondary-storage existingSecretKey: password ingress: grpc: enabled: true className: nginx host: zeebe.orchestration.example.com tls: enabled: true secretName: orchestration-grpc-tls connectors: enabled: true contextPath: /connectors security: authentication: oidc: clientId: connectors secret: existingSecret: connectors-oidc existingSecretKey: client-secret ``` `orchestration.security.authentication.oidc.redirectUrl` is deprecated and logs a deprecation warning, but it's used here because one value sets three properties: the OIDC callback (`/sso-callback`) and the Operate and Tasklist redirect roots. To remove the warning, set `camunda.security.authentication.oidc.redirect-uri`, `camunda.operate.identity.redirectRootUrl`, and `camunda.tasklist.identity.redirectRootUrl` in `orchestration.extraConfiguration` instead. The key is removed in chart v17 (Camunda 8.12). This example uses Elasticsearch as secondary storage, and the `secondary-storage` Secret must exist in the orchestration namespace. For OpenSearch, relational database, and TLS configuration, see [database configuration](/self-managed/deployment/helm/configure/database/index.md). Optimize isn't part of this release. It's deployed separately, one release per Physical Tenant. See [install an Optimize release](./optimize-release.md). ## Export records for Optimize Optimize reads the records written by the legacy Elasticsearch or OpenSearch exporter, not the Orchestration Cluster's own indices. The chart renders that exporter automatically only when Optimize runs in the same release. With Optimize in its own release, the example above writes no records Optimize can read until you enable the exporter here. If the cluster uses Elasticsearch or OpenSearch secondary storage, enable the exporter with chart values. It writes to the secondary storage endpoint: ```yaml orchestration: exporters: zeebe: enabled: true index: # Must exactly equal optimize.database.elasticsearch.prefix # in this cluster's default-tenant Optimize release. prefix: production-a-default-records optimize: enabled: false database: elasticsearch: # Required for the exporter to send the secondary storage credentials. external: true ``` If the cluster uses RDBMS secondary storage, or the records must go to a different Elasticsearch or OpenSearch instance, configure the exporter directly as broker configuration: ```yaml orchestration: env: - name: ZEEBE_BROKER_EXPORTERS_ELASTICSEARCH_ARGS_AUTHENTICATION_PASSWORD valueFrom: secretKeyRef: name: optimize-records-store key: password extraConfiguration: - file: optimize-exporter.yaml content: | zeebe: broker: exporters: elasticsearch: className: io.camunda.zeebe.exporter.ElasticsearchExporter args: url: https://elasticsearch.example.com:9200 authentication: username: camunda index: prefix: production-a-default-records ``` For OpenSearch, use the `opensearch` exporter with `io.camunda.zeebe.exporter.opensearch.OpensearchExporter`. The Orchestration Cluster keeps using its own secondary storage. The exporter writes the separate record stream Optimize reads. Every prefix must be unique per cluster and tenant. See [prefixes in the split topology](/self-managed/deployment/helm/configure/database/elasticsearch/configure-elasticsearch-prefix-indices.md#prefixes-in-the-split-topology). ## Choose which applications run `orchestration.profiles` selects which parts of the Orchestration Cluster are active in the single StatefulSet: ```yaml orchestration: profiles: broker: true admin: true operate: true tasklist: true ``` Disabling a profile removes that application from the running cluster. Keep `broker` enabled in any release that executes processes. ## Pin the issuer Set `global.identity.auth.issuer`, or `orchestration.security.authentication.oidc.issuer`, to the exact `iss` claim your identity provider mints. This value can't be derived from `publicIssuerUrl` or `issuerBackendUrl`, because those are network routes: a Keycloak started without a pinned hostname mints a different `iss` per route, so in-cluster callers and browsers would present different issuers. Setting only `publicIssuerUrl` doesn't satisfy the requirement. Pin your provider to one issuer. For Keycloak, set `KC_HOSTNAME`. :::warning A pinned issuer is optional for a single-tenant cluster but required as soon as you declare Physical Tenants. The Orchestration Cluster rejects a provider without `issuerUri` once tenants exist, and the chart fails the render rather than deploying a cluster that can't validate tokens. ::: ## Install the release ```sh helm install camunda camunda/camunda-platform \ --version "$ORCHESTRATION_CHART_VERSION" \ --namespace orchestration \ --create-namespace \ --values orchestration-values.yaml ``` Confirm that the Orchestration Cluster appears in Camunda Hub's cluster list before you install its Optimize releases. ## Add another Orchestration Cluster Add another entry to `global.topology.clusters` in the Hub release, then install another orchestration release configured to match that entry. Use unique client IDs, audiences, and secrets so each cluster has its own client registration. To also authorize users per cluster, set a distinct `components..roleName` in each record. See [role assignment across clusters](./hub-release.md#role-assignment-across-clusters). :::warning If orchestration releases share Elasticsearch or OpenSearch, every cluster needs its own index prefixes. Reusing a prefix mixes one cluster's records into another cluster's Operate, Tasklist, or Optimize data. See [index prefixes](/self-managed/deployment/helm/configure/database/elasticsearch/configure-elasticsearch-prefix-indices.md#prefixes-in-the-split-topology). ::: For Keycloak, Management Identity creates every declared client. For another OIDC provider, provision the clients before applying the Helm releases. Generated internal service URLs in the Hub inventory use Kubernetes service DNS, so this pattern supports multiple namespaces in the same Kubernetes cluster. For workloads in another Kubernetes cluster, provide equivalent cross-cluster DNS and routing, or configure explicit `grpcUrl`, `restUrl`, `readinessUrl`, `operateUrl`, `tasklistUrl`, `adminUrl`, and component web application URL overrides. Set each orchestration release's `global.identity.service.url` to an address from which it can reach Management Identity. ## Declare Physical Tenants Physical Tenants are application configuration, not chart values. There's no `orchestration.physicalTenants` values key. Declare tenants as `camunda.physical-tenants.*` through `orchestration.extraConfiguration`. For why this is application configuration, see [Helm and application configuration responsibilities](/self-managed/deployment/helm/configure/configuration-responsibilities.md). ```yaml orchestration: extraConfiguration: - file: physical-tenants.yaml content: | camunda: physical-tenants: # Optional. Without a default entry, the default tenant is # synthesized from the root configuration and keeps its root exporters. default: riskprod: # Tenant configuration. ``` Declaring the `default` tenant explicitly changes how it gets its exporters, and each tenant needs its own Optimize release and index prefixes. Read [configure Physical Tenants across releases](./physical-tenants.md) before you add your first tenant. ## Next steps - [Install an Optimize release](./optimize-release.md) - [Configure Physical Tenants across releases](./physical-tenants.md) - [Orchestration Cluster configuration](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md) --- ## Configure Physical Tenants across releases A Physical Tenant spans three releases: it's declared in an Orchestration Cluster release, mapped in the Hub release, and served by its own Optimize release. :::note Minimum chart versions This page needs Helm chart 15.0.0 or later for 8.10 releases. For the minimum chart version per Camunda version, see [release roles](/self-managed/deployment/helm/install/topology/index.md#release-roles). ::: This page covers the release-level work. For what a Physical Tenant is, how its isolation model works, and the full application configuration reference, see [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md) and the [configuration reference](/self-managed/concepts/physical-tenants/configuration-reference.md). This procedure applies to fresh Camunda 8.10 topology deployments. To convert an existing combined release first, see [move from a combined release to the split topology](/self-managed/upgrade/helm/combined-to-split-topology.md). ## Prerequisites - A [Hub release](./hub-release.md) and at least one [Orchestration Cluster release](./orchestration-release.md). - OIDC authentication. Physical Tenants don't support Basic authentication. - A pinned issuer on the orchestration release, set in `global.identity.auth.issuer` or `orchestration.security.authentication.oidc.issuer`. `publicIssuerUrl` and `issuerBackendUrl` don't satisfy this, because they're network routes rather than the `iss` claim. The Orchestration Cluster rejects a provider without `issuerUri` once tenants exist, and the chart fails the render rather than deploying a cluster that can't validate tokens. See [pin the issuer](./orchestration-release.md#pin-the-issuer). - Elasticsearch or OpenSearch, if you want Optimize per tenant. ## Where each part of a tenant is configured | Concern | Release | Configured with | | ------------------------------------------------- | ------------- | ------------------------------------------------------------------------ | | The tenant itself, its storage, and its exporters | Orchestration | `orchestration.extraConfiguration`, as `camunda.physical-tenants..*` | | Per-tenant secret store overrides | Orchestration | `orchestration.secretStore.physicalTenants` | | The tenant's Optimize client and resource server | Hub | `global.topology.clusters[].physicalTenants[]` | | The tenant's Optimize workload | Optimize | A release with `global.topology.mode: optimize` | The tenant ID must be identical in all three places. :::note The chart exposes no `orchestration.physicalTenants` values schema. A tenant's own configuration is application configuration, so it's set through `extraConfiguration` rather than typed chart values. The chart detects that tenants are declared only to enforce its own input constraints. See [Helm and application configuration responsibilities](/self-managed/deployment/helm/configure/configuration-responsibilities.md). ::: ## Declare tenants in the Orchestration Cluster release Add a `camunda.physical-tenants` block through `orchestration.extraConfiguration`. For the delivery options, including a single `orchestration.configuration` block or environment variables, see [configure Physical Tenants in Helm chart](/self-managed/deployment/helm/configure/configure-physical-tenants.md). :::warning Declaring tenants and the `default` tenant's exporters How the `default` tenant exports depends on whether you declare it: - **`default` not declared.** The runtime synthesizes the `default` tenant from the root configuration. It keeps the release's root broker configuration, including its root exporters. - **`default` declared explicitly.** Its entry under `camunda.physical-tenants.default` is used instead. Configure its exporter assignments in that entry, and don't assume it inherits root exporters you didn't assign to it. - **A required exporter assignment is missing.** The Orchestration Cluster can fail at startup instead of running without the exporter. Check the broker logs if the StatefulSet doesn't become ready after you add a tenant. Every non-default tenant needs its own exporter assignments. See the [configuration reference](/self-managed/concepts/physical-tenants/configuration-reference.md). ::: ## Map tenants in the Hub release In the Hub release, map the default tenant through the cluster-level `components.optimize` record, and map each non-default Physical Tenant through a `physicalTenants` entry. The tenant ID must exactly match the ID under `camunda.physical-tenants` in the Orchestration Cluster configuration. ```yaml global: topology: mode: hub clusters: - id: production-a # namespace, releaseName, host, version, contextPaths, and other components omitted components: optimize: # Optimize for the default tenant enabled: true clientId: optimize-production-a-default audience: optimize-production-a-default-api roleName: Optimize production-a default redirectUrl: https://production-a.example.com/optimize-default secret: existingSecret: optimize-production-a-default-oidc existingSecretKey: client-secret physicalTenants: - id: tenanta components: optimize: enabled: true clientId: optimize-production-a-tenanta audience: optimize-production-a-tenanta-api roleName: Optimize production-a tenanta redirectUrl: https://production-a.example.com/optimize-tenanta secret: existingSecret: optimize-production-a-tenanta-oidc existingSecretKey: client-secret ``` Give every Optimize release its own OIDC client ID, audience, role name, redirect URL, and secret. Set the same client ID, audience, redirect URL, and secret on that tenant's Optimize release under `optimize.security.authentication.oidc`. Setting a dedicated `roleName` avoids adding the audience to the shared `Optimize` role. A `physicalTenants` entry registers the tenant's Optimize client and role in Management Identity. It also adds the tenant's Optimize to the Camunda Hub cluster inventory, using its `webappUrl`, or its `redirectUrl` when `webappUrl` isn't set. A distinct `roleName` isolates the Optimize role per tenant, but it doesn't isolate Optimize's logical tenants, which are a separate mechanism. See [Optimize and Physical Tenants](/self-managed/concepts/physical-tenants/optimize.md#known-limitation-logical-tenants-with-the-same-id-across-physical-tenants) if you reuse the same logical tenant ID across Physical Tenants behind one shared Management Identity. ## Assign unique index prefixes Every tenant needs its own writer, reader, and Optimize application prefixes, and each reader prefix must equal its writer prefix. See [prefixes in the split topology](/self-managed/deployment/helm/configure/database/elasticsearch/configure-elasticsearch-prefix-indices.md#prefixes-in-the-split-topology). ## Override the secret store per tenant `orchestration.secretStore.physicalTenants` deep-overlays the root secret store for a named tenant. Tenant IDs here may contain letters, digits, underscores, and hyphens. A tenant must use the same provider and the same `default` store ID as the root store it overrides. Exactly one default store applies per tenant. ## Operate releases in dependency order Use these orders for both Helm and GitOps reconciliation. | Operation | Order | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | Install | Hub, each Orchestration Cluster, then its default and Physical Tenant Optimize releases | | Add a tenant | Add its Hub record, add its Orchestration tenant and exporter, then install its Optimize release | | Disable or remove a tenant | Stop or uninstall its Optimize release, remove its Orchestration tenant and exporter, then disable or remove its Hub record | | Re-enable a tenant | Restore its Hub record, restore its Orchestration tenant and exporter, then reinstall its Optimize release with the same prefixes | | Upgrade | Hub first, then Orchestration Clusters, then their Optimize releases. Complete and verify each layer before continuing | | Uninstall the topology | All Optimize releases, all Orchestration Cluster releases, then the Hub release | Adding a Physical Tenant requires a rolling restart of the Orchestration Cluster. See [provisioning and lifecycle](/self-managed/concepts/physical-tenants/provisioning-and-lifecycle.md). ## What removal leaves behind Identity and Keycloak initialization is additive. Removing or disabling a cluster or tenant record doesn't delete its client, resource server, permissions, or role. Inventory and clean those objects explicitly after dependent releases have stopped. Don't delete an object still used by another release. Helm uninstall also leaves Elasticsearch and OpenSearch indices intact. This permits re-enabling a tenant with the same prefixes, but it can expose old data to a newly mapped tenant if prefixes are reused. Apply your storage system's retention and deletion process separately, after confirming no release reads or writes those indices. ## Scope and limits - Hub topology connections and Physical Tenants require OIDC. Basic authentication isn't supported. - The chart doesn't configure cross-cluster DNS, routing, TLS trust, firewall rules, or external identity-provider objects. Every configured URL must be reachable from the release that uses it. - One Optimize release serves one tenant. Sharing an Optimize release between the default tenant and Physical Tenants is out of scope. - Separate OIDC credentials don't isolate storage. Prefixes and backend access controls remain your responsibility. - Supported cluster and tenant scale limits haven't been established. Validate your target scale before committing to it. - There's no migration path from Logical Tenants to Physical Tenants. See [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md). ## Next steps - [Physical Tenant isolation model](/self-managed/concepts/physical-tenants/index.md) - [Physical Tenant configuration reference](/self-managed/concepts/physical-tenants/configuration-reference.md) - [Optimize and Physical Tenants](/self-managed/concepts/physical-tenants/optimize.md) - [Back up and restore](/self-managed/operational-guides/backup-restore/backup-and-restore.md) --- ## Prepare Helm upgrades with the Camunda Helm Toolkit Before every supported Helm minor-version upgrade, run the Camunda Helm Toolkit on your override files. It produces migrated starting points and a report of configuration changes that need review, so you can focus on the remaining upgrade actions instead of finding every values-file change manually. :::note Toolkit boundaries The toolkit prepares and checks override files. It doesn't upgrade the deployment, migrate stored data, validate a live cluster, or prove that the complete configuration is ready for production. ::: Use the toolkit as the first step in preparing your values files, then follow the [version-specific Helm upgrade guide](/self-managed/upgrade/helm/index.md). Review its manual actions, render and test your complete configuration in non-production, back up data, run the upgrade, and validate the deployed platform. ## Check version compatibility Migrate one adjacent Camunda minor version at a time, then review the output before continuing. | Source version | Migration target | Post-migration validation | | -------------- | ---------------- | ------------------------- | | 8.7 | 8.8 | 8.8 | | 8.8 | 8.9 | 8.9 | | 8.9 | 8.10 | 8.10 | Standalone validation supports 8.8, 8.9, and 8.10. You can't validate an 8.7 file directly; migrate it to 8.8 first. The toolkit migrates supported configuration changes and reports actions that need review. Complete the applicable manual steps in the [version-specific upgrade guide](/self-managed/upgrade/helm/index.md). The source and target arguments are Camunda versions, such as `8.9`, not Helm chart versions, such as `14.x`. Use the [Helm chart version matrix](https://helm.camunda.io/camunda-platform/version-matrix/) to identify your deployment's Camunda version. ## Prepare your files Provide your own override files, such as the files you pass to Helm with `-f`, rather than the chart's default `values.yaml`. - Install Docker and use a local Docker daemon with Linux containers. - Keep a copy of your original overrides. For layered configurations, keep the files separate and record their Helm `-f` order. - Identify the source and target Camunda versions. The examples on this page migrate 8.9 overrides to 8.10. The examples use the [public Docker image](https://hub.docker.com/r/camunda/camunda-helm-toolkit/tags). Its `SNAPSHOT` tag is a rolling build, not a versioned release, and can change between pulls. The CLI examples use a POSIX-compatible shell on macOS or Linux. The Web UI avoids host-path mount commands. ## Use the local Web UI Start the toolkit in Docker to migrate and validate files through your browser. ```bash docker run --rm --pull always \ -p 127.0.0.1:8080:8080 \ camunda/camunda-helm-toolkit:SNAPSHOT ``` Open [the local toolkit](http://localhost:8080) in your browser. If port 8080 is occupied, change the first port in the mapping and use that port in the browser URL. 1. Select **Migrate** and upload or paste your override files. 2. Select source version **8.9**. The toolkit chooses the next minor version, **8.10**, as the target. 3. Run the migration and review the findings alongside the YAML. Select a finding to locate the affected configuration. 4. Select the target-version document to inspect the migrated output. To correct it, select **Edit migrated**, make your changes, and select **Validate again**. 5. Download each migrated document. The download uses the document currently displayed, so confirm you're viewing the target version rather than the original input. For a configuration check without migration, select **Validate**, provide your overrides, and choose their Camunda version. Stop the container with `Ctrl+C` when you're finished. ## What to do with the report Inspect the migrated YAML and act on the findings before using the output for an upgrade. | Report result | Your next action | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | Errors | Resolve errors before using the migrated output. If the tool fails, correct the failure and rerun it. | | Warnings | Review each warning and complete the applicable manual action before upgrading. | | No findings | Continue with the version-specific upgrade guide, render and test the complete layered configuration, and complete backups and deployment checks. | A clean report is a useful sanity check, not proof of production readiness. For CLI automation, see [reports and exit codes](#interpret-reports-and-exit-codes). ## Migrate from the CLI Run `migrate` to create an updated override file without modifying your source file. From the directory containing `values-8.9.yaml`, run: ```bash docker run --rm --pull always \ --user "$(id -u):$(id -g)" --workdir /tmp \ -v "$PWD":/work:ro \ camunda/camunda-helm-toolkit:SNAPSHOT migrate \ --input /work/values-8.9.yaml \ --source 8.9 --target 8.10 \ --output - > values-8.10.yaml ``` The container uses your user and group IDs and `/tmp` as its working directory. The input directory is mounted read-only. Docker writes the migrated YAML to standard output, and your shell saves it on the host. Choose a new output filename: shell redirection replaces an existing file, so never redirect to an input file. With `--output -`, the report and summary go to standard error, separate from the YAML. A completed migration can produce output and still exit with warnings or validation errors. Check the exit code and findings before using the output; a tool failure can leave an empty redirected file. Migration automatically validates each output against its target version. It changes keys present in your overrides, but doesn't add the target chart's defaults or automatically apply every recommended setting. ### Migrate layered overrides Use repeated `--input` arguments and `--output-dir` to keep layered overrides separate. From the directory containing your 8.9 `base.yaml` and `production.yaml` overrides, create a separate output directory and run: ```bash mkdir -p migrated docker run --rm --pull always \ --user "$(id -u):$(id -g)" --workdir /tmp \ --env TMPDIR=/output \ -v "$PWD":/input:ro \ -v "$PWD/migrated":/output \ camunda/camunda-helm-toolkit:SNAPSHOT migrate \ --input /input/base.yaml --input /input/production.yaml \ --source 8.9 --target 8.10 \ --output-dir /output \ --output-format markdown --report-file /output/migration-report.md ``` The container runs with your user and group IDs so it can write to the output mount. `TMPDIR=/output` keeps temporary files on the same filesystem as the output, which is required when the toolkit moves completed files into place. The toolkit writes `migrated/base.yaml` and `migrated/production.yaml`, without merging them. Input basenames must be unique. Use a separate output directory for each migration to avoid replacing earlier results. When you continue with the Helm upgrade guide, pass the migrated overrides in the same order: `-f migrated/base.yaml -f migrated/production.yaml`. Later files still take precedence on shared keys. ### Continue across additional versions Review and correct each migrated output before using it as the next migration's input. For example, to prepare 8.7 overrides for 8.9, run 8.7 to 8.8 first, then 8.8 to 8.9. Preparing files for several versions doesn't let you skip deployment upgrades or intermediate data migrations. Follow each version's upgrade guide in order. ## Validate override files Run `validate` to check an existing or edited override without migrating it. ```bash docker run --rm --pull always \ --user "$(id -u):$(id -g)" --workdir /tmp \ -v "$PWD":/work:ro \ camunda/camunda-helm-toolkit:SNAPSHOT validate \ --input /work/values-8.10.yaml --target 8.10 \ --output-format markdown ``` Validation checks the keys your file contains for types, unsupported or deprecated settings, and configuration rules. It doesn't require an override to contain every chart setting. Each file is checked separately, not as the merged result of all Helm layers. Validation doesn't check live Kubernetes resources, stored data, all cross-file requirements, or whether the complete configuration renders and runs successfully. [Act on the report](#what-to-do-with-the-report), then render and test the complete configuration and follow the upgrade guide's required checks. ## Interpret reports and exit codes Reports identify changes, warnings, and validation errors that need review before deployment. | Option | Purpose | | ----------------- | ------------------------------------------------------------------------- | | `--output-format` | Select `json` (default), `yaml`, or `markdown` for the report. | | `--report-file` | Save the report to a container path on a writable host-mounted directory. | | `--strict` | Treat warnings as validation errors when choosing the exit code. | Without `--report-file`, reports go to standard output, except when `migrate --output -` reserves it for YAML. The one-line summary goes to standard error. | Exit code | Meaning | | --------- | ----------------------------------------------------- | | `0` | No warnings or errors. | | `1` | Tool or usage failure; don't rely on the output. | | `2` | Warnings or manual follow-up required. | | `3` | Validation errors, or warnings when using `--strict`. | Inspect both the migrated YAML and the report. Some findings recommend changes the toolkit can't safely automate. For all command options, run `docker run --rm camunda/camunda-helm-toolkit:SNAPSHOT migrate --help` or replace `migrate` with `validate`. ## Keep configuration local With the local Docker setup above, your YAML is processed by the container on your machine. The Web UI has no authentication. Keep the `127.0.0.1` port binding and don't expose the service on a shared network. Downloading the image requires registry access; processing files doesn't require access to your Kubernetes cluster. Treat input files, migrated files, and reports as potentially sensitive. They can contain configuration values, including credentials. Review them before sharing or committing them. After reviewing the results, continue with [upgrading Camunda 8.9 to 8.10 using Helm](/self-managed/upgrade/helm/890-to-8100.md). The toolkit doesn't replace backups, non-production testing, or the required deployment and data-migration steps. --- ## Deploy multiple Optimize instances with Helm Use two Helm releases to run two independent Optimize instances against one Camunda 8.10 orchestration cluster. The platform release runs the full Camunda deployment and the first Optimize instance. The second release runs only Optimize and connects to the platform topology's shared services. :::warning Temporary deployment pattern Use this pattern as a stopgap when separate Optimize dashboards, configuration, client credentials, or index lifecycles are required over the **same** orchestration data. Native Optimize multi-tenancy is the long-term solution and is expected to replace this pattern. This pattern doesn't provide per-team authorization or process-data isolation. Both Optimize instances import the same orchestration data, and both use the `optimize-api` audience and `Optimize` role. If you need genuine data isolation per team, use [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md) with one Optimize release per tenant instead. See [configure Physical Tenants across releases](/self-managed/deployment/helm/install/topology/physical-tenants.md). ::: Set `global.topology.mode: optimize` on the Optimize-only release rather than disabling each other component by hand. The role gates off every non-Optimize component and validates that the release has the storage, Identity URL, and context path an Optimize-only release needs. See [install an Optimize release](/self-managed/deployment/helm/install/topology/optimize-release.md). ## Understand the release boundaries Camunda 8.10 doesn't bundle Elasticsearch, OpenSearch, Keycloak, or PostgreSQL in the Camunda chart. Deploy and manage these dependencies separately. | Boundary | Platform release (`platform`) | Optimize-only release (`optimize-team-b`) | | --------------------------- | ----------------------------------- | ------------------------------------------- | | Orchestration Cluster | Runs | Doesn't run | | Management Identity | Runs as service `platform-identity` | Uses `http://platform-identity:80/identity` | | Optimize | Runs at `/optimize-team-a` | Runs at `/optimize-team-b` | | Elasticsearch or OpenSearch | Uses the external shared service | Uses the same external shared service | | Keycloak or OIDC provider | Uses the external shared provider | Uses the same provider and realm | | OIDC client | `optimize-team-a` | `optimize-team-b` | | Optimize-owned indices | Prefix `optimize-team-a` | Prefix `optimize-team-b` | | Other Camunda components | Run | Explicitly disabled | Both releases must use the same Camunda 8.10 chart and application version. Install them in the same namespace when you use the service names and shared Kubernetes Secrets shown in this guide. ## Check the prerequisites Before you install the releases, prepare the following: - A production-ready Kubernetes cluster and a Helm CLI version supported by the Camunda 8.10 chart. - A single-region Camunda deployment. This pattern needs Management Identity, which a dual-region cluster runs [outside the stretched Orchestration Cluster](/self-managed/concepts/multi-region/dual-region.md#management-platform-and-orchestration-cluster). It also needs the automatic legacy Zeebe exporter. The chart turns this exporter off when the Orchestration Cluster spans more than one zone, for example when `orchestration.partitioning.numberOfZones` is 2 or more. - A namespace with enough capacity for the platform and a second Optimize Deployment. - An external Elasticsearch or OpenSearch cluster configured for the platform release. Follow the [Elasticsearch](/self-managed/deployment/helm/configure/database/elasticsearch/using-external-elasticsearch.md) or [OpenSearch](/self-managed/deployment/helm/configure/database/using-external-opensearch.md) guide. - An external Keycloak or supported OIDC provider and a Management Identity configuration. The reference files use the [external Keycloak setup](/self-managed/deployment/helm/configure/authentication-and-authorization/external-keycloak.md). - An `ingress-nginx` controller, one DNS host, and a TLS Secret for the host. The example relies on `ingress-nginx` merging paths from two Ingress objects with the same host and Ingress class. - A production values file for the full platform, including external PostgreSQL, datastore authentication and TLS, image pull credentials, and component resources. Follow the [production installation guide](/self-managed/deployment/helm/install/production/index.md). - Existing Kubernetes Secrets for every credential referenced by the values files. Follow the [secret management guide](/self-managed/deployment/helm/configure/secret-management.md#method-2-external-kubernetes-secrets-recommended). The examples use Elasticsearch service `elasticsearch-master` and Keycloak service `keycloak` in the Camunda namespace. Use fully qualified service names when a dependency runs in another namespace. ## Prepare the reference values The Helm repository contains two values files that are rendered in chart tests: - `values-platform.yaml` enables the platform and registers both Optimize clients. - `values-optimize-only.yaml` enables only the second Optimize instance. Set the chart version you will install, then download both files from the matching release tag. Camunda chart release tags use the format `camunda-platform-8.10-`. ```bash export CHART_VERSION="<15.x chart version for Camunda 8.10>" export HELM_SOURCE_REF="camunda-platform-8.10-$CHART_VERSION" export HELM_VALUES_BASE_URL="https://raw.githubusercontent.com/camunda/camunda-platform-helm/$HELM_SOURCE_REF/charts/camunda-platform-8.10/test/integration/scenarios/chart-full-setup/values/features/multi-optimize" curl -fsSLo values-platform.yaml \ "$HELM_VALUES_BASE_URL/values-platform.yaml" curl -fsSLo values-optimize-only.yaml \ "$HELM_VALUES_BASE_URL/values-optimize-only.yaml" ``` Update these values in both files before installation: | Value | Required change | | -------------------------------------------------------- | ------------------------------------------------------------------- | | `global.host` | Set the shared browser-facing host. | | `global.ingress.className` | Set the `ingress-nginx` class used by both releases. | | `global.ingress.tls.secretName` | Set the existing TLS Secret for the shared host. | | `global.identity.keycloak.*` | Set the in-cluster Keycloak service, port, context path, and realm. | | `global.identity.auth.publicIssuerUrl` | Set the issuer URL reachable from a user's browser. | | `global.identity.auth.issuerBackendUrl` | Set the issuer URL reachable from Camunda pods. | | `global.identity.auth.camundaHub.redirectUrl` | Set the browser-facing Camunda Hub URL in `values-platform.yaml`. | | `global.identity.auth.optimize.redirectUrl` | Set each Optimize instance's browser-facing root URL. | | `identity.clients[].rootUrl` for `optimize-team-b` | Set the second Optimize client's browser-facing root URL. | | `orchestration.security.authentication.oidc.redirectUrl` | Set the browser-facing Orchestration Cluster URL. | | `optimize.database.elasticsearch.url.*` | Set the shared Elasticsearch endpoint. | | `orchestration.data.secondaryStorage.elasticsearch.url` | Set the same Elasticsearch endpoint in `values-platform.yaml`. | | Every `existingSecret` and `existingSecretKey` | Match the Secrets managed in your namespace. | The files assume the release names in this guide. If you change `platform`, also change `global.identity.service.url` in `values-optimize-only.yaml` to the generated Management Identity service name. ### Use an external OIDC provider instead of Keycloak The reference files use Keycloak, where Management Identity reads `identity.clients` and provisions `optimize-team-b`. A generic external OIDC provider doesn't use this automatic provisioning path. To use another OIDC provider: 1. Follow the [external OIDC provider guide](/self-managed/deployment/helm/configure/authentication-and-authorization/external-oidc-provider.md) for the provider endpoints, claims, and existing-secret values. 1. Register `optimize-team-a` and `optimize-team-b` as separate confidential clients in the provider before installing either release. 1. Configure these callback URLs: - `https:///optimize-team-a/api/authentication/callback` - `https:///optimize-team-b/api/authentication/callback` 1. Configure both clients for the `optimize-api` audience and the claims required by your Camunda authorization mapping. 1. Remove the Keycloak-specific `identity.clients` entry and `global.identity.keycloak` configuration from the reference files. 1. Keep the distinct client IDs, existing-secret references, context paths, and Optimize index prefixes. ### Use OpenSearch instead of Elasticsearch The reference files use Elasticsearch. For OpenSearch, make all of the following changes in both files: - Disable `optimize.database.elasticsearch` and enable `optimize.database.opensearch`. - Configure `optimize.database.opensearch.url`, authentication, and TLS by following the [Optimize OpenSearch guide](/self-managed/deployment/helm/configure/database/optimize/using-external-opensearch.md). - In `values-platform.yaml`, set `orchestration.data.secondaryStorage.type: opensearch` and configure the same OpenSearch endpoint. - Replace `CAMUNDA_OPTIMIZE_ELASTICSEARCH_SETTINGS_INDEX_PREFIX` with `CAMUNDA_OPTIMIZE_OPENSEARCH_SETTINGS_INDEX_PREFIX` for both Optimize instances and their migration containers. ## Create the credentials Secret The reference files don't contain plaintext credentials. They read all credentials from a Secret named `multi-optimize-credentials`. Create the following keys through your secret-management system: | Secret key | Consumer | | ------------------------------- | ------------------------------------------ | | `keycloak-admin-password` | Management Identity setup against Keycloak | | `identity-first-user-password` | Initial Management Identity user | | `optimize-team-a-client-secret` | First Optimize OIDC client | | `optimize-team-b-client-secret` | Second Optimize OIDC client | | `connectors-client-secret` | Connectors OIDC client | | `orchestration-client-secret` | Orchestration Cluster OIDC client | | `web-modeler-pusher-app-key` | Web Modeler web app and WebSocket service | | `web-modeler-pusher-app-secret` | Web Modeler REST API and WebSocket service | The two Optimize client secrets must be different. The `optimize-team-b-client-secret` value must be identical for these two references: - `identity.clients[].secret` in `values-platform.yaml`. - `global.identity.auth.optimize.secret` in `values-optimize-only.yaml`. For a disposable test environment, you can create the Secret from environment variables populated by your secret manager: ```bash kubectl -n "$NAMESPACE" create secret generic multi-optimize-credentials \ --from-literal=keycloak-admin-password="$KEYCLOAK_ADMIN_PASSWORD" \ --from-literal=identity-first-user-password="$IDENTITY_FIRST_USER_PASSWORD" \ --from-literal=optimize-team-a-client-secret="$OPTIMIZE_TEAM_A_CLIENT_SECRET" \ --from-literal=optimize-team-b-client-secret="$OPTIMIZE_TEAM_B_CLIENT_SECRET" \ --from-literal=connectors-client-secret="$CONNECTORS_CLIENT_SECRET" \ --from-literal=orchestration-client-secret="$ORCHESTRATION_CLIENT_SECRET" \ --from-literal=web-modeler-pusher-app-key="$WEB_MODELER_PUSHER_APP_KEY" \ --from-literal=web-modeler-pusher-app-secret="$WEB_MODELER_PUSHER_APP_SECRET" ``` Don't store the environment variables or generated Secret manifest in source control. ## Install the platform release Install the platform release before the Optimize-only release so Management Identity can create the second client. ```bash export NAMESPACE=camunda export CAMUNDA_HOST=camunda.example.com helm repo add camunda https://helm.camunda.io helm repo update helm install platform camunda/camunda-platform \ --namespace "$NAMESPACE" \ --version "$CHART_VERSION" \ --values values-production.yaml \ --values values-platform.yaml \ --wait ``` The last values file takes precedence. Confirm that your production values don't override the client IDs, context paths, shared service endpoints, or index prefixes from `values-platform.yaml`. Check the release and its services: ```bash helm status platform --namespace "$NAMESPACE" kubectl get pods,services,ingresses --namespace "$NAMESPACE" \ --selector app.kubernetes.io/instance=platform ``` ## Verify the Identity client and assign access Management Identity creates the `optimize-team-b` confidential client from `identity.clients` when the platform release starts. 1. Open the Keycloak Admin Console for the `camunda-platform` realm. 1. Open **Clients > optimize-team-b**. 1. Confirm the valid redirect URI is `https:///optimize-team-b/api/authentication/callback`. 1. Confirm the client is confidential and uses the expected client secret. 1. Open Management Identity at `https:///identity`. 1. For each user who needs Optimize access, open **Users > user > Assigned roles > Assign roles**, and assign the **Optimize** role. For details, see [assign a role to a user](/self-managed/components/management-identity/application-user-group-role-management/manage-roles.md#assign-a-role-to-a-user). :::warning Authorization limitation The distinct client IDs and secrets separate the two applications' OIDC registrations, but they don't separate user authorization. Both clients request the `optimize-api` audience, and the single `Optimize` role grants access to that audience. Any user with the `Optimize` role can authenticate to both Optimize instances. Don't use this pattern when Team A must be prevented from accessing Team B's Optimize instance. ::: ## Install the Optimize-only release Install the second release with the exact chart version used by the platform release: ```bash helm install optimize-team-b camunda/camunda-platform \ --namespace "$NAMESPACE" \ --version "$CHART_VERSION" \ --values values-optimize-only.yaml \ --wait ``` The second values file explicitly disables Management Identity, Camunda Hub, Web Modeler, Connectors, and the Orchestration Cluster. The release creates one Optimize Deployment plus its Service, ConfigMaps, ServiceAccount, and Ingress. Verify that only the Optimize workload was created: ```bash kubectl get deployments,statefulsets --namespace "$NAMESPACE" \ --selector app.kubernetes.io/instance=optimize-team-b ``` The output must contain only the `optimize-team-b` Deployment and no StatefulSet. ## Verify Ingress and authentication The two releases create separate Ingress objects with the same host and different paths. `ingress-nginx` merges these rules into one virtual host: | Release | Path | Callback URL | | ----------------- | ------------------ | ------------------------------------------------------------ | | `platform` | `/optimize-team-a` | `https:///optimize-team-a/api/authentication/callback` | | `optimize-team-b` | `/optimize-team-b` | `https:///optimize-team-b/api/authentication/callback` | Check the generated rules: ```bash kubectl get ingress --namespace "$NAMESPACE" \ --selector app.kubernetes.io/part-of=camunda-platform kubectl describe ingress --namespace "$NAMESPACE" \ --selector app.kubernetes.io/instance=platform kubectl describe ingress --namespace "$NAMESPACE" \ --selector app.kubernetes.io/instance=optimize-team-b ``` Check each authentication redirect: ```bash curl --silent --show-error --dump-header - --output /dev/null \ --location --max-redirs 5 \ "https://$CAMUNDA_HOST/optimize-team-a" \ | tr -d '\r' | grep -i '^location:.*client_id=optimize-team-a' curl --silent --show-error --dump-header - --output /dev/null \ --location --max-redirs 5 \ "https://$CAMUNDA_HOST/optimize-team-b" \ | tr -d '\r' | grep -i '^location:.*client_id=optimize-team-b' ``` Both commands must print a redirect to the configured OIDC provider with the intended `client_id`. Complete a browser login to both URLs with a user assigned the `Optimize` role. Using the same host for multiple Ingress objects is controller-specific. This guide supports `ingress-nginx`. If your controller doesn't merge same-host rules, use separate hosts and update `global.host`, each Optimize `redirectUrl`, the custom client's `rootUrl`, and the Keycloak callback URI. ## Verify process data and index isolation Both Optimize instances read the same `zeebe-record` indices produced by the Orchestration Cluster. The 8.10 chart automatically enables the legacy Zeebe exporter when Optimize and its Elasticsearch or OpenSearch connection are enabled. In a dual-region deployment, the chart suppresses that automatic behavior, so this pattern applies to single-region deployments only. See [Management platform and Orchestration Cluster](/self-managed/concepts/multi-region/dual-region.md#management-platform-and-orchestration-cluster). 1. Deploy a test process to the shared Orchestration Cluster. 1. Start and complete at least one process instance. 1. Open both Optimize URLs and confirm the process is available in each instance after import completes. 1. Query the shared datastore and confirm each Optimize instance writes its own indices. For an unauthenticated in-cluster Elasticsearch test service, port-forward the service: ```bash kubectl port-forward --namespace "$NAMESPACE" service/elasticsearch-master 9200:9200 ``` In another terminal, list the source and Optimize-owned indices: ```bash curl --fail --silent 'http://localhost:9200/_cat/indices?h=index&s=index' \ | grep -E '^(zeebe-record|optimize-team-a|optimize-team-b)' ``` The result must include the shared `zeebe-record` source indices and two non-overlapping Optimize index families. Use the authentication and TLS options required by your datastore instead of the unauthenticated port-forward example in production. The `CAMUNDA_OPTIMIZE_ELASTICSEARCH_SETTINGS_INDEX_PREFIX` values isolate Optimize-owned indices only. They don't filter the process records each Optimize imports. For all index-prefix rules and the OpenSearch equivalent, see [configure Elasticsearch and OpenSearch index prefixes](/self-managed/deployment/helm/configure/database/elasticsearch/configure-elasticsearch-prefix-indices.md#optimize-specific-configuration). ## Plan resource capacity The second release is a complete additional Optimize runtime. Account for the following capacity: - Set `optimize.resources` and `optimize.migration.resources` independently for each release. - Reserve compute and memory for two importers, query workloads, and migration init containers. - Size Elasticsearch or OpenSearch for both Optimize-owned index families and concurrent reads from the shared `zeebe-record` indices. - Expect the shared datastore and OIDC provider to remain common failure and performance boundaries. - Test backup, retention, and upgrades for each Optimize index prefix. Helm uninstall doesn't remove datastore indices. Review the [shared Elasticsearch/OpenSearch guidance](/self-managed/components/optimize/configuration/shared-elasticsearch-cluster.md) before using this topology in production. ## Troubleshoot the deployment Use these commands to inspect both releases: ```bash helm get values platform --namespace "$NAMESPACE" helm get values optimize-team-b --namespace "$NAMESPACE" kubectl get pods,services,ingresses --namespace "$NAMESPACE" kubectl logs --namespace "$NAMESPACE" deployment/platform-optimize kubectl logs --namespace "$NAMESPACE" deployment/optimize-team-b ``` | Symptom | Check | | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | The second Optimize pod can't reach Management Identity | Confirm `global.identity.service.url` resolves to `http://platform-identity:80/identity` and the platform Identity endpoints are ready. | | Login returns an invalid redirect URI | Compare `optimize.contextPath`, `global.identity.auth.optimize.redirectUrl`, the custom client's `rootUrl`, and the Keycloak callback URI. | | Both URLs use the same client ID | Confirm each release received the intended values file and inspect `application-ccsm.yaml` in its Optimize ConfigMap. | | One path returns 404 | Confirm both Ingress objects use the same host and Ingress class, have distinct paths, and are reconciled by `ingress-nginx`. | | The second release creates unrelated workloads | Confirm `values-optimize-only.yaml` is the final values layer and that no later file enables components. | | Optimize starts but doesn't show process data | Confirm both releases use the same datastore and `zeebe-record` prefix, and check the legacy exporter and Optimize importer logs. | | Optimize-owned indices overlap | Confirm the two `CAMUNDA_OPTIMIZE_ELASTICSEARCH_SETTINGS_INDEX_PREFIX` or OpenSearch prefix values are distinct and weren't changed after startup. | Don't add hand-written Deployments, Services, Ingresses, or `global.extraManifests` to repair a mismatch. Correct the supported chart values and run `helm upgrade` for the affected release. ## Clean up the releases Remove the Optimize-only release before the platform release: ```bash helm uninstall optimize-team-b --namespace "$NAMESPACE" helm uninstall platform --namespace "$NAMESPACE" ``` If the Secret and namespace were created only for this deployment, remove them after both releases are gone: ```bash kubectl delete secret multi-optimize-credentials --namespace "$NAMESPACE" kubectl delete namespace "$NAMESPACE" ``` Helm doesn't delete indices from an external Elasticsearch or OpenSearch cluster. Retain, back up, or delete the `optimize-team-a*` and `optimize-team-b*` index families according to your datastore lifecycle policy. --- ## Helm chart diagnostics ## Diagnostics collection script This script automates the process of gathering logs and diagnostics from a Camunda Helm chart deployment running in a Kubernetes cluster. The script collects all relevant information (including pod logs, events, resource details, and Elasticsearch/OpenSearch diagnostics) into a single directory, and outputs it in a `.zip` file to make it easier to share this information with the Camunda Support team. :::caution Data privacy notice Before sharing the generated diagnostics file with Camunda Support, review and remove any sensitive information such as passwords, API keys, personal data, or business-sensitive data from the collected logs, Helm values (`helm-values.yaml`), configuration data, and Elasticsearch/OpenSearch data. This includes the collected actuator outputs: the `configprops` endpoint masks credential-like values, but connection URLs, hostnames, and bucket names are not redacted. The Elasticsearch/OpenSearch data can also include index documents that may contain business or personal data. Pass the `--skip-es-os` flag to skip exporting Elasticsearch/OpenSearch data entirely. ::: ### What the script collects The script outputs the following data from your namespace and creates a zip file containing the following: - **Pod Information**: Current and previous logs and full pod descriptions. - **Java thread dumps**: One `*.jstack` file per Java process, collected from pods that have `java`, `jattach`, and `pgrep` installed. - **Cluster Events**: Sorted by time to help identify recent issues. - **Storage Details**: PV and PVC descriptions. - **Cluster Nodes**: Node descriptions. - **Network Resources**: Services, endpoints, and ingresses. - **Configuration**: Config map information. - **Helm release data**: The Helm version (`helm-version.txt`), the release history (`helm-history.txt`), and the user-supplied values of the release (`helm-values.yaml`). The script collects this data only if the `helm` command is available in the `PATH` and it detects the release name. - **Actuator endpoints**: Read-only Zeebe and Spring Boot actuator outputs from Camunda pods (partition status, cluster topology, exporters, flow control, job streams, backup state, Prometheus metrics, and configuration properties). - **Elasticsearch/OpenSearch Data** (when an Elasticsearch or OpenSearch pod is deployed in the same namespace): - Cluster health, node stats, and allocation details. - Index list (sorted by name), aliases, shards, and segments. - Index templates, component templates, and lifecycle policies (ILM for Elasticsearch, ISM for OpenSearch). - Camunda import position documents for Optimize. - Operate post-importer queue document count (and a sample of entries when `--export-post-importer-queue` is set). - Zeebe exporter index statistics: document counts and min/max sequence numbers per value type and partition. ### Usage 1. Save the following script as `camunda-collect-diagnostics.sh` for example. 2. Make the script executable: ```bash chmod +x camunda-collect-diagnostics.sh ``` 3. Execute the script, replacing `` with the namespace of your Camunda deployment: ```bash ./camunda-collect-diagnostics.sh --namespace ``` 4. **Review the generated `.zip` archive** and remove any sensitive or personally identifiable information (PII) before sharing. 5. Share the reviewed `.zip` archive with Camunda Support. #### Required/Optional flags | Flag | Description | | ------------------------------ | ----------------------------------------------------------------------- | | `--namespace ` | **(Required)** Kubernetes namespace of your Camunda deployment. | | `--skip-es-os` | Skip Elasticsearch/OpenSearch diagnostics entirely. | | `--export-post-importer-queue` | Export Operate post-importer queue documents (not exported by default). | | `--es-user ` | Username for Elasticsearch/OpenSearch Basic authentication. | | `--es-password ` | Password for Elasticsearch/OpenSearch Basic authentication. | | `--es-port ` | Elasticsearch/OpenSearch port (default: `9200`). | ### Diagnostic collection script ```bash #!/bin/bash # Parse arguments skip_es_os=false export_post_importer_queue=false es_user="" es_password="" es_port=9200 while [[ "$#" -gt 0 ]]; do case $1 in --namespace) namespace="$2"; shift ;; --skip-es-os) skip_es_os=true ;; --export-post-importer-queue) export_post_importer_queue=true ;; --es-user) es_user="$2"; shift ;; --es-password) es_password="$2"; shift ;; --es-port) es_port="$2"; shift ;; *) echo "Unknown parameter passed: $1"; exit 1 ;; esac shift done # Check if namespace is provided if [[ -z "$namespace" ]]; then echo "ERROR: Namespace not provided. Please run the script with the --namespace parameter." echo "Example:" echo " ./camunda-collect-diagnostics.sh --namespace camunda-platform" exit 1 fi # Check if namespace exists if ! kubectl get namespace "$namespace" > /dev/null 2>&1; then echo "ERROR: Namespace '$namespace' does not exist in the cluster." exit 1 fi # Set output directory with timestamp timestamp=$(date +"%Y%m%d-%H%M%S") output_dir="camunda-diagnostics-logs-$timestamp" # Start diagnostics collection echo "========================================" echo "Camunda Diagnostics Collection Script" echo "========================================" echo "Namespace: $namespace" echo "Output Directory: $output_dir" echo "Current kubectl context: $(kubectl config current-context)" echo "========================================" # Create output directory mkdir -p "$output_dir" && cd "$output_dir" # Collect general Kubernetes resources echo "Collecting resource information..." echo " - Collecting Kubernetes version." kubectl version -o yaml > kubernetes-version.txt echo " - Collecting pod information (current state of all pods in the namespace)." kubectl get pod -n "$namespace" -o wide > pods.txt echo " - Collecting cluster events (recent events in the namespace)." kubectl get events -n "$namespace" --sort-by='.lastTimestamp' > events.txt echo " - Collecting Persistent Volume Claims (PVCs) descriptions (storage claims in the namespace)." kubectl describe pvc -n "$namespace" > pvc-describe.txt echo " - Collecting Persistent Volumes (PVs) descriptions (underlying storage volumes)." for pvc in $(kubectl get pvc -n "$namespace" --no-headers -o custom-columns=":spec.volumeName"); do echo " - Collecting information for PV: $pvc" kubectl describe pv "$pvc" >> pv-describe.txt done echo " - Collecting Storage Classes information:" kubectl get storageclass -o yaml > storageclasses.txt kubectl describe storageclass > storageclasses-describe.txt echo " - Collecting service information (list of services in the namespace)." kubectl get svc -n "$namespace" > services.txt echo " - Collecting detailed service descriptions (configuration of services)." kubectl describe svc -n "$namespace" > services-describe.txt echo " - Collecting endpoint information (list of endpoints in the namespace)." kubectl get ep -n "$namespace" > endpoints.txt echo " - Collecting detailed endpoint descriptions (configuration of endpoints)." kubectl describe ep -n "$namespace" > endpoints-describe.txt echo " - Collecting ingress descriptions (configuration of ingress resources)." kubectl describe ing -n "$namespace" > ingresses-describe.txt echo " - Collecting config map information (configuration data stored in the namespace)." kubectl get cm -n "$namespace" -o yaml > configmaps.yaml echo " - Collecting node information:" for node in $(kubectl get pods -n "$namespace" -o custom-columns=":spec.nodeName" --no-headers | sort | uniq); do echo " - Collecting information for node: $node" kubectl describe node "$node" >> node-describe.txt echo "" >> node-describe.txt done echo " - Collecting logs and descriptions for each pod..." for pod in $(kubectl get pod -n "$namespace" --no-headers -o custom-columns=":metadata.name"); do echo " - Collecting logs for pod: $pod" kubectl logs -n "$namespace" "$pod" > "$pod.log" 2>/dev/null kubectl logs -n "$namespace" "$pod" -p > "${pod}-previous.log" 2>/dev/null kubectl describe pod -n "$namespace" "$pod" > "describe-$pod.log" 2>/dev/null done echo " - Collecting java threaddumps ..." for pod in $(kubectl get pod -n "$namespace" --no-headers -o custom-columns=":metadata.name"); do if ! kubectl exec -n "$namespace" "$pod" -- which java > /dev/null 2>&1; then echo " - Skipping pod: $pod as java not installed" continue fi if ! kubectl exec -n "$namespace" "$pod" -- which jattach > /dev/null 2>&1; then echo " - Skipping pod: $pod as jattach not installed" continue fi if ! kubectl exec -n "$namespace" "$pod" -- which pgrep > /dev/null 2>&1; then echo " - Skipping pod: $pod as pgrep not installed" continue fi java_pids=$(kubectl exec -n "$namespace" "$pod" -- pgrep java 2> /dev/null) for java_pid in $java_pids; do echo " - Collecting threaddump from pod: ${pod}, java process: ${java_pid}" kubectl exec -n "$namespace" "$pod" -- jattach $java_pid threaddump > "${pod}-pid-${java_pid}.jstack" 2>/dev/null done done # Collect Helm resources echo " - Collecting information via Helm..." release_name=`helm list -n "$namespace" --no-headers -q` 2>/dev/null if [[ -z "$release_name" ]]; then echo " INFO: unable to detect Camunda release name, so Helm values.yaml will not be collected. Install \"helm\" command, make it available in the PATH and re-run the script. (Alternatively, upload \"values.yaml\" separately)" else helm version > helm-version.txt helm history -n "$namespace" "$release_name" > helm-history.txt helm get values -n "$namespace" "$release_name" > helm-values.yaml fi # Collect read-only actuator endpoints from Camunda pods echo " - Collecting actuator endpoints from Camunda pods..." if ! command -v curl > /dev/null 2>&1; then echo " INFO: 'curl' not found in PATH; skipping actuator collection. Install \"curl\", make it available in the PATH and re-run the script to include actuator data." # Leave a breadcrumb in the archive so Support can tell this newer script ran but curl was # missing, rather than the actuator data being a partial or failed collection. printf '%s\n' \ "Actuator collection was attempted but skipped: the 'curl' command was not found in the PATH" \ "on the machine that ran this script." \ "" \ "As a result, no '-actuator-*' files are included in this archive. This is not a partial" \ "or failed collection of actuator data - it was not attempted at all. There is no need to" \ "re-run the script for this reason unless the actuator outputs are specifically required;" \ "in that case, install \"curl\", make it available in the PATH, and run the script again." \ > actuator-collection-skipped.txt else # The management endpoint defaults to port 9600 on Camunda orchestration-cluster / Zeebe pods. # We port-forward it per pod (no in-pod tooling required) and collect only read-only GET endpoints. # The custom Zeebe actuators exist only on broker/gateway pods; "prometheus" and "configprops" # exist on all Camunda Spring Boot components. Mutating endpoints (exporting, banning, rebalance, # backup take/delete, and the cluster/exporters/partitions write operations) are intentionally # NOT collected, as they would change cluster state. management_port=9600 actuators="partitions:json cluster:json exporters:json flowControl:json jobstreams:json backupRuntime/state:json prometheus:txt configprops:json" # Only Camunda components expose actuators, so restrict the loop to them via the chart label. # Fall back to all pods if the label is absent (e.g. a non-Helm install). actuator_pods=$(kubectl get pod -n "$namespace" -l app.kubernetes.io/part-of=camunda-platform --no-headers -o custom-columns=":metadata.name" 2>/dev/null) if [[ -z "$actuator_pods" ]]; then actuator_pods=$(kubectl get pod -n "$namespace" --no-headers -o custom-columns=":metadata.name") fi # Make sure any port-forward we start is torn down, even on Ctrl-C or an error. pf_pid="" pf_log="" cleanup_port_forward() { [[ -n "$pf_pid" ]] && kill "$pf_pid" 2>/dev/null [[ -n "$pf_pid" ]] && wait "$pf_pid" 2>/dev/null [[ -n "$pf_log" ]] && rm -f "$pf_log" pf_pid="" pf_log="" } trap cleanup_port_forward EXIT INT TERM for pod in $actuator_pods; do # Open a port-forward on an ephemeral local port chosen by kubectl (no clash with bound ports). # kubectl can fail to establish the tunnel transiently, so retry a few times; a pod that never # establishes is skipped after the attempts and the rest of the run continues. local_port="" attempt=0 while [[ -z "$local_port" && "$attempt" -lt 3 ]]; do attempt=$((attempt + 1)) # mktemp without a template is not portable (fails on macOS), so fall back progressively. pf_log=$(mktemp 2>/dev/null || mktemp -t camunda-diag 2>/dev/null || echo "./.actuator-pf.$$.${attempt}") kubectl port-forward -n "$namespace" "$pod" ":${management_port}" > "$pf_log" 2>&1 & pf_pid=$! # Wait until the tunnel is established (kubectl prints the chosen port), or give up early if # the forward dies first (e.g. the pod does not expose the management port). for _ in $(seq 1 10); do local_port=$(sed -n 's/.*Forwarding from 127.0.0.1:\([0-9]*\) ->.*/\1/p' "$pf_log" | head -1) [[ -n "$local_port" ]] && break kill -0 "$pf_pid" 2>/dev/null || break sleep 1 done [[ -z "$local_port" ]] && cleanup_port_forward done if [[ -n "$local_port" ]]; then collected="" for entry in $actuators; do actuator_path="${entry%%:*}" ext="${entry##*:}" name=$(echo "$actuator_path" | tr '/' '-') if curl -sf -m 10 "http://localhost:${local_port}/actuator/${actuator_path}" -o "${pod}-actuator-${name}.${ext}" 2>/dev/null; then collected="${collected} ${name}" fi done if [[ -n "$collected" ]]; then echo " - Collected actuators from pod ${pod}:${collected}" fi cleanup_port_forward fi done trap - EXIT INT TERM fi # ============================================== # Collect Elasticsearch / OpenSearch diagnostics # ============================================== if [[ "$skip_es_os" == "true" ]]; then echo "Skipping Elasticsearch/OpenSearch diagnostics (--skip-es-os)." else echo "========================================" echo "Elasticsearch/OpenSearch Diagnostics" echo "========================================" search_pod="" search_backend="" search_container="" es_pod=$(kubectl get pod -n "$namespace" -l "app.kubernetes.io/name=elasticsearch" \ --no-headers -o custom-columns=":metadata.name" 2>/dev/null | grep -v "^$" | head -1) os_pod=$(kubectl get pod -n "$namespace" -l "app.kubernetes.io/name=opensearch" \ --no-headers -o custom-columns=":metadata.name" 2>/dev/null | grep -v "^$" | head -1) if [[ -n "$es_pod" ]]; then search_pod="$es_pod" search_backend="Elasticsearch" search_container="elasticsearch" echo "Detected Elasticsearch pod: $search_pod" elif [[ -n "$os_pod" ]]; then search_pod="$os_pod" search_backend="OpenSearch" search_container="opensearch" echo "Detected OpenSearch pod: $search_pod" else echo "INFO: No Elasticsearch or OpenSearch pod detected in namespace '$namespace' — skipping search diagnostics." echo " If you use an externally-managed cluster, collect and attach those diagnostics separately." fi if [[ -n "$search_pod" ]]; then search_dir="$search_container" mkdir -p "$search_dir" es_base="http://localhost:$es_port" auth_args=() if [[ -n "$es_user" ]]; then auth_args=(-u "$es_user:$es_password") fi run_es_curl() { kubectl exec -n "$namespace" "$search_pod" -c "$search_container" -- \ curl -s --connect-timeout 10 "${auth_args[@]}" "$@" 2>/dev/null } echo " - Testing connectivity to $search_backend..." if ! run_es_curl "$es_base/_cat/health" > /dev/null 2>&1; then echo " WARNING: Cannot reach $search_backend at localhost:$es_port inside pod $search_pod." echo " Skipping search diagnostics. Check that the pod is Running and healthy." else echo " Connected to $search_backend. Collecting diagnostics..." echo " - Collecting cluster health." run_es_curl "$es_base/_cluster/health?pretty" \ > "$search_dir/cluster-health.json" run_es_curl "$es_base/_cluster/health?level=shards&pretty" \ > "$search_dir/cluster-health-shards.txt" echo " - Collecting cluster stats." run_es_curl "$es_base/_cluster/stats?pretty" \ > "$search_dir/cluster-stats.json" echo " - Collecting index information." run_es_curl "$es_base/_cat/aliases?v=true&format=json" \ > "$search_dir/all-aliases.json" run_es_curl "$es_base/_cat/indices?v=true&s=index&h=health,status,index,id,pri,rep,docs.count,docs.deleted,store.size,creation.date.string&pretty" \ > "$search_dir/all-indices.txt" run_es_curl "$es_base/_cat/shards?v" \ > "$search_dir/shards.txt" run_es_curl "$es_base/_cat/segments?v&h=index,shard,segment,size,docs.count" \ > "$search_dir/segments.txt" echo " - Collecting node information." run_es_curl "$es_base/_cat/nodes?v&h=name,ip,heap.percent,heap.current,heap.max,ram.percent,cpu,load_1m,load_5m,load_15m,node.role,master" \ > "$search_dir/nodes.txt" run_es_curl "$es_base/_nodes/stats/jvm,os,fs,indices,process,breaker?pretty" \ > "$search_dir/nodes-stats.json" run_es_curl "$es_base/_cat/allocation?v=true&h=node,shards,disk.*" \ > "$search_dir/nodes-allocation.txt" run_es_curl "$es_base/_nodes/hot_threads" \ > "$search_dir/hot-threads.txt" echo " - Collecting performance diagnostics." run_es_curl "$es_base/_tasks?pretty&detailed=true" \ > "$search_dir/tasks.json" run_es_curl "$es_base/_cat/thread_pool?v" \ > "$search_dir/thread-pool.txt" run_es_curl "$es_base/_stats/indexing,search?pretty" \ > "$search_dir/stats-indexing-search.json" echo " - Collecting index templates and lifecycle policies." run_es_curl "$es_base/_index_template?pretty" \ > "$search_dir/index-templates.json" run_es_curl "$es_base/_component_template?pretty" \ > "$search_dir/component-templates.json" if [[ "$search_backend" == "Elasticsearch" ]]; then run_es_curl "$es_base/_ilm/policy?pretty" \ > "$search_dir/ilm-policies.json" else run_es_curl "$es_base/_plugins/_ism/policies?pretty" \ > "$search_dir/ism-policies.json" fi echo " - Collecting Camunda import position documents." run_es_curl "$es_base/optimize-position-based-import-index*/_search?size=10000&pretty" \ > "$search_dir/optimize-import-position.json" echo " - Collecting Operate post-importer queue." run_es_curl "$es_base/operate-post-importer-queue*/_count?pretty" \ > "$search_dir/operate-post-importer-queue-count.json" if [[ "$export_post_importer_queue" == "true" ]]; then run_es_curl "$es_base/operate-post-importer-queue*/_search?size=1000&pretty" \ > "$search_dir/operate-post-importer-queue.json" fi echo " - Collecting Zeebe exporter index statistics." run_es_curl -X POST "$es_base/zeebe-record*/_search?pretty" \ -H "Content-Type: application/json" \ -d '{"size":0,"aggs":{"value_types":{"terms":{"field":"valueType","size":100},"aggs":{"partitions":{"terms":{"field":"partitionId"},"aggs":{"min_sequence":{"min":{"field":"sequence"}},"max_sequence":{"max":{"field":"sequence"}}}}}}}}' \ > "$search_dir/zeebe-record-stats.json" echo "$search_backend diagnostics collected into $search_dir/." fi fi fi echo "All logs, descriptions, and diagnostic data collected." # Compress the output directory echo "Compressing collected diagnostics into ${output_dir}.zip..." cd .. zip -r "${output_dir}.zip" "$output_dir" > /dev/null echo "Diagnostics collected and compressed into ${output_dir}.zip." # Final message echo "========================================" echo "Diagnostics collection completed." echo "Please share the file '${output_dir}.zip' with the Camunda Support team." echo "" echo "To clean up the generated files and folder, run the following command:" echo " rm -rf $output_dir ${output_dir}.zip" echo "========================================" ``` --- ## Helm chart dual-region operational procedure ## Introduction This operational blueprint procedure is a step-by-step guide on how to restore operations in the case of a total region failure. It explains how to temporarily restore functionality in the surviving region and how to ultimately do a full recovery to restore the dual-region setup. This procedure is the failover and failback runbook for [Dual-Region](/self-managed/concepts/multi-region/dual-region.md). For an overview of Camunda's multi-region resilience options and how Dual-Region compares to Cold Recovery, see [Multi-region resilience tiers](/self-managed/concepts/multi-region/resilience-tiers.md). :::warning Check your topology before you start This procedure applies only to a two-region cluster with Elasticsearch secondary storage and numbered brokers, where even node IDs run in one region and odd node IDs in the other. - On a [Multi-Region RDBMS](/self-managed/concepts/multi-region/multi-region-rdbms.md) cluster with three or more regions, don't run it: its force-remove, exporter, and Elasticsearch restore steps don't apply there. Use the [Multi-Region RDBMS operational procedure](/self-managed/deployment/helm/operational-tasks/multi-region-rdbms-ops.md) instead. - On a cluster that you [migrated to zone-aware brokers](/self-managed/deployment/helm/operational-tasks/zone-aware-migration.md), the node IDs in its commands don't match your brokers. Don't apply them as written. - On a two-region cluster with RDBMS secondary storage and database replication, for example the [Amazon ECS dual-region setup](/self-managed/deployment/containers/cloud-providers/amazon/aws-ecs-dual-region.md), don't run this procedure either. Its Elasticsearch exporter and restore steps don't apply there. ::: The operational procedure builds on top of the [dual-region AWS setup guidance](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/dual-region.md), but is generally applicable for any dual-region setup. It has been also validated for the [OpenShift dual-region setup guidance](/self-managed/deployment/helm/cloud-providers/openshift/dual-region.md). Before proceeding with the operational procedure, thoroughly review and understand the contents of the [dual-region concept page](/self-managed/concepts/multi-region/dual-region.md). This page outlines various limitations and requirements pertinent to the procedure, which are crucial for successful execution. ## Disclaimer :::caution Running a dual-region configuration requires users to detect and manage any regional failures, and implement the operational procedure for failover and failback that matches their environment. ::: ## Prerequisites - A dual-region Camunda 8 setup installed in two different regions, preferably derived from our [AWS dual-region concept](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/dual-region.md) or [OpenShift dual-region concept](/self-managed/deployment/helm/cloud-providers/openshift/dual-region.md). - In that guide, we're showcasing Kubernetes dual-region installation, based on the following tools: - [Helm](https://helm.sh/docs/intro/install/) for installing and upgrading the [Camunda Helm chart](https://artifacthub.io/packages/helm/camunda/camunda-platform). - [Kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) to interact with the Kubernetes cluster. - `cURL` or similar to interact with the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). ## Terminology - **Surviving region** - A surviving region refers to a region within a dual-region setup that remains operational and unaffected by a failure or disaster that affects other regions. - **Lost region** - A lost region is a region within a dual-region setup that becomes unavailable or unusable due to a failure or disaster. - **Recreated region** - A recreated region is a region within a dual-region setup that was previously lost but has been restored or recreated to resume its operational state. - We assume this region does not contain Camunda 8 deployments or related persistent volumes. Ensure this is the case before executing the failover procedure. ## Procedure We use the same procedure to handle the loss of both active and passive regions. For clarity, this section focuses on the scenario where the passive region is lost while the active region remains operational. The same procedure will be valid in case of active region loss. **Temporary Loss Scenario:** If a region loss is temporary — such as from transient network issues — Zeebe can handle this situation without initiating recovery procedures, provided there is sufficient free space on the persistent disk. However, processing may halt due to a loss of quorum during this time. :::warning Expect transient cross-region disruptions While cross-region connectivity and DNS converge, some failover and failback steps may take longer than expected or return transient errors, and Zeebe brokers may stay `Running` without becoming `Ready`. This is expected, not a failure. Follow the remediation steps below before treating a step as failed. ::: Failover and failback depend on cross-region network connectivity and DNS resolution between both regions. For example, Submariner `ServiceExport` resources and clusterset DNS on OpenShift, or VPC peering on Amazon EKS. While this connectivity converges, Zeebe brokers may stay `Running` without becoming `Ready` until they can resolve their peers in the other region. If a step does not converge on the first attempt: - Allow extra time for cross-region DNS and Zeebe quorum to stabilize before concluding the step has failed. - Re-run the step. These procedures are idempotent. - Restart any Zeebe broker that stays `Running` but never becomes `Ready`, so it can resolve its cross-region peers and rejoin the cluster. #### Key steps to handle passive region loss 1. **Traffic rerouting:** Use DNS to reroute traffic to the surviving active region. (Details on managing DNS rerouting depend on your specific DNS setup and are not covered in this guide.) 2. **Failover phase:** Temporarily restores Camunda 8 functionality by removing the lost brokers and handling the export to the unreachable Elasticsearch instance. 3. **Failback phase:** Fully restores the failed region to its original functionality. This phase requires the region to be ready for the redeployment of Camunda 8. :::caution For the failback procedure, the recreated region must not include any active Camunda 8 deployments or residual persistent volumes associated with Camunda 8 or its Elasticsearch instance. It is essential to initiate a clean deployment to prevent data replication and state conflicts. ::: :::info In the following examples, direct API calls are used because authentication methods may vary depending on your Admin configuration. The **Management API** (default port `9600`) is not secured by default. The **v2 REST API** (default port `8080`) **requires authentication**, described in the [API authentication guide](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md). ::: ### Prerequisites The following procedures assume the following dual-region deployment for: - **AWS:** the deployment has been created using [AWS setup guide](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/dual-region.md) and you have your own copy of the [camunda-deployment-references](https://github.com/camunda/camunda-deployment-references/tree/main/aws/kubernetes/eks-dual-region) repository and previously completed changes in the `camunda-values.yml` to adjust them in your setup. Follow the [dual-region cluster deployment](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/dual-region.md#3-deploy-camunda-8-via-helm-charts) guide to install Camunda 8, configure a dual-region setup, and have the general environment variables (see [environment prerequisites](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/dual-region.md#export-environment-variables) already set up). - **OpenShift:** the deployment has been created using [OpenShift setup guide](/self-managed/deployment/helm/cloud-providers/openshift/dual-region.md#deploying-camunda-8-via-helm-charts-in-a-dual-region-setup) and previously completed changes in your `generated-values-region-0.yml` and `generated-values-region-1.yml` to adjust them in your setup. :::note OpenShift cluster reference The OpenShift guide now uses the same 0-indexed naming convention as the AWS guide: `CLUSTER_0`/`CLUSTER_1` for cluster contexts and `CAMUNDA_NAMESPACE_0`/`CAMUNDA_NAMESPACE_1` for namespaces. No conversion is needed. In version 8.8 and earlier, the OpenShift guide used 1-indexed naming (`CLUSTER_1_NAME`/`CLUSTER_2_NAME` and `CAMUNDA_NAMESPACE_1`/`CAMUNDA_NAMESPACE_2`), which required converting variable names before following this procedure. ::: We will avoid referencing both scenarios of losing either region. Instead, we have generalized the commands and require a one-time setup to configure environment variables, enabling you to execute the procedure based on the surviving region and the one that needs to be recreated. Depending on which region you lost, select the correct tab below and export those environment variables to your terminal for a smoother procedure execution: ```bash export CLUSTER_SURVIVING=$CLUSTER_1 export CLUSTER_RECREATED=$CLUSTER_0 export CAMUNDA_NAMESPACE_SURVIVING=$CAMUNDA_NAMESPACE_1 export CAMUNDA_NAMESPACE_RECREATED=$CAMUNDA_NAMESPACE_0 export REGION_SURVIVING=region1 export REGION_RECREATED=region0 echo "You have lost $CLUSTER_RECREATED, $CLUSTER_SURVIVING is still alive" ``` ```bash export CLUSTER_SURVIVING=$CLUSTER_0 export CLUSTER_RECREATED=$CLUSTER_1 export CAMUNDA_NAMESPACE_SURVIVING=$CAMUNDA_NAMESPACE_0 export CAMUNDA_NAMESPACE_RECREATED=$CAMUNDA_NAMESPACE_1 export REGION_SURVIVING=region0 export REGION_RECREATED=region1 echo "You have lost $CLUSTER_RECREATED, $CLUSTER_SURVIVING is still alive" ``` The `camunda-zeebe-x` pod represents the new architecture that contains the Orchestration Cluster and its components. It includes the former Zeebe Gateway, Operate, Tasklist, the new embedded Admin, and the new Camunda Exporter. ### Failover phase The Failover phase outlines steps for removing lost brokers, redistributing load, disabling Elasticsearch export to a failed region, and restoring user interaction with Camunda 8 to ensure smooth recovery and continued functionality. #### Remove lost brokers from Zeebe cluster in the surviving region | **Current state** | **Desired state** | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | You have ensured that you fully lost a region and want to start the temporary recovery. One of the regions is lost, meaning Zeebe: - No data has been lost thanks to Zeebe data replication. - Is unable to process new requests due to losing the quorum - Stops exporting new data to Elasticsearch in the lost region - Stops exporting new data to Elasticsearch in the survived region | The lost brokers have been removed from the Zeebe cluster. Continued processing is enabled, and new brokers in the failback procedure will only join the cluster with our intervention. | #### Procedure Start with creating a port-forward to the `Zeebe Gateway` in the surviving region to the local host so you can interact with the Zeebe Gateway. The following alternatives to port-forwarding are possible: - If the Zeebe Gateway is exposed to the outside of the Kubernetes cluster, you can skip port-forwarding and use the URL directly - [`exec`](https://kubernetes.io/docs/reference/kubectl/generated/kubectl_exec/) into an existing pod (such as Elasticsearch), and execute `curl` commands from inside of the pod - [`run`](https://kubernetes.io/docs/reference/kubectl/generated/kubectl_run/) an Ubuntu pod in the cluster to execute `curl` commands from inside the Kubernetes cluster In our example, we went with port-forwarding to a localhost, but other alternatives can also be used. 1. Use the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) to retrieve the list of the remaining brokers ```bash kubectl --context $CLUSTER_SURVIVING port-forward services/$CAMUNDA_RELEASE_NAME-zeebe-gateway 8080:8080 -n $CAMUNDA_NAMESPACE_SURVIVING curl -L -X GET 'http://localhost:8080/v2/topology' \ -H 'Accept: application/json' ```
Example output ```bash { "brokers": [ { "nodeId": 0, "host": "camunda-zeebe-0.camunda-zeebe.camunda-london", "port": 26501, "partitions": [ { "partitionId": 1, "role": "leader", "health": "healthy" }, { "partitionId": 6, "role": "follower", "health": "healthy" }, { "partitionId": 7, "role": "follower", "health": "healthy" }, { "partitionId": 8, "role": "follower", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 2, "host": "camunda-zeebe-1.camunda-zeebe.camunda-london", "port": 26501, "partitions": [ { "partitionId": 1, "role": "follower", "health": "healthy" }, { "partitionId": 2, "role": "follower", "health": "healthy" }, { "partitionId": 3, "role": "follower", "health": "healthy" }, { "partitionId": 8, "role": "leader", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 4, "host": "camunda-zeebe-2.camunda-zeebe.camunda-london", "port": 26501, "partitions": [ { "partitionId": 2, "role": "follower", "health": "healthy" }, { "partitionId": 3, "role": "leader", "health": "healthy" }, { "partitionId": 4, "role": "follower", "health": "healthy" }, { "partitionId": 5, "role": "follower", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 6, "host": "camunda-zeebe-3.camunda-zeebe.camunda-london", "port": 26501, "partitions": [ { "partitionId": 4, "role": "follower", "health": "healthy" }, { "partitionId": 5, "role": "follower", "health": "healthy" }, { "partitionId": 6, "role": "follower", "health": "healthy" }, { "partitionId": 7, "role": "leader", "health": "healthy" } ], "version": "8.8.0" } ], "clusterSize": 8, "partitionsCount": 8, "replicationFactor": 4, "gatewayVersion": "8.8.0" } ```
2. Port-forward the Zeebe Gateway service to access the [Management REST API](/self-managed/components/orchestration-cluster/zeebe/configuration/gateway.md#managementserver): ```bash kubectl --context $CLUSTER_SURVIVING port-forward services/$CAMUNDA_RELEASE_NAME-zeebe-gateway 9600:9600 -n $CAMUNDA_NAMESPACE_SURVIVING ``` 3. Based on the [Cluster Scaling APIs](/self-managed/components/orchestration-cluster/zeebe/operations/cluster-scaling.md), send a request to the Zeebe Gateway to redistribute load to the remaining brokers and remove the lost ones. Depending on which region was lost, you must redistribute to either the even- or odd-numbered brokers. In this example, `region 1` was lost, along with the odd-numbered brokers. Therefore, the load is redistributed to the even-numbered brokers. Run the appropriate command for the surviving region to remove the lost brokers and trigger redistribution. Removing the lost (odd-numbered) brokers will automatically redistribute partitions to the remaining (even-numbered) brokers. ```bash curl -XPATCH 'http://localhost:9600/actuator/cluster?force=true' \ -H 'Content-Type: application/json' \ -d '{ "brokers": { "remove": [1,3,5,7] } }' ``` ```bash curl -XPATCH 'http://localhost:9600/actuator/cluster?force=true' \ -H 'Content-Type: application/json' \ -d '{ "brokers": { "remove": [0,2,4,6] } }' ``` Using the `force=true` parameter reduces the replication factor accordingly. #### Verification Port-forwarding the Zeebe Gateway via `kubectl` and printing the topology should reveal that the cluster size has decreased to 4, partitions have been redistributed over the remaining brokers, and new leaders have been elected. ```bash kubectl --context $CLUSTER_SURVIVING port-forward services/$CAMUNDA_RELEASE_NAME-zeebe-gateway 8080:8080 -n $CAMUNDA_NAMESPACE_SURVIVING curl -L -X GET 'http://localhost:8080/v2/topology' \ -H 'Accept: application/json' ```
Example output ```bash { "brokers": [ { "nodeId": 0, "host": "camunda-zeebe-0.camunda-zeebe.camunda-london", "port": 26501, "partitions": [ { "partitionId": 1, "role": "leader", "health": "healthy" }, { "partitionId": 6, "role": "leader", "health": "healthy" }, { "partitionId": 7, "role": "follower", "health": "healthy" }, { "partitionId": 8, "role": "follower", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 2, "host": "camunda-zeebe-1.camunda-zeebe.camunda-london", "port": 26501, "partitions": [ { "partitionId": 1, "role": "follower", "health": "healthy" }, { "partitionId": 2, "role": "leader", "health": "healthy" }, { "partitionId": 3, "role": "follower", "health": "healthy" }, { "partitionId": 8, "role": "leader", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 4, "host": "camunda-zeebe-2.camunda-zeebe.camunda-london", "port": 26501, "partitions": [ { "partitionId": 2, "role": "follower", "health": "healthy" }, { "partitionId": 3, "role": "leader", "health": "healthy" }, { "partitionId": 4, "role": "follower", "health": "healthy" }, { "partitionId": 5, "role": "follower", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 6, "host": "camunda-zeebe-3.camunda-zeebe.camunda-london", "port": 26501, "partitions": [ { "partitionId": 4, "role": "leader", "health": "healthy" }, { "partitionId": 5, "role": "leader", "health": "healthy" }, { "partitionId": 6, "role": "follower", "health": "healthy" }, { "partitionId": 7, "role": "leader", "health": "healthy" } ], "version": "8.8.0" } ], "clusterSize": 4, "partitionsCount": 8, "replicationFactor": 2, "gatewayVersion": "8.8.0" } ```
You can also use the Zeebe Gateway's REST API to ensure the scaling progress has been completed. For better output readability, we use [jq](https://jqlang.github.io/jq/). ```bash kubectl --context $CLUSTER_SURVIVING port-forward services/$CAMUNDA_RELEASE_NAME-zeebe-gateway 9600:9600 -n $CAMUNDA_NAMESPACE_SURVIVING curl -XGET 'http://localhost:9600/actuator/cluster' | jq .lastChange ```
Example output ```bash { "id": 2, "status": "COMPLETED", "startedAt": "2024-08-23T11:33:08.355681311Z", "completedAt": "2024-08-23T11:33:09.170531963Z" } ```
#### Configure Zeebe to disable the Elastic exporter to the lost region | **Details** | **Current state** | **Desired state** | | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | **Zeebe configuration** | Zeebe brokers in the surviving region are still configured to point to the Elasticsearch instance of the lost region. Zeebe cannot continue exporting data. | Elasticsearch exporter to the failed region has been disabled in the Zeebe cluster. Zeebe can export data to Elasticsearch again. | | **User interaction** | Regular interaction with Camunda 8 is not restored. | Regular interaction with Camunda 8 is restored, marking the conclusion of the temporary recovery. | :::info If you have upgraded from a previously migrated 8.7 system, you may still have the legacy `elasticsearchregion0` and `elasticsearchregion1` exporters configured. - If **both exporters are disabled**, you can safely ignore them. - If they are **enabled** (for example, because you’re using **Optimize**), apply the same logic to these exporters as described for the new ones in this guide. ::: #### Procedure 1. Port-forward the service of the Zeebe Gateway for the [management REST API](/self-managed/components/orchestration-cluster/zeebe/configuration/gateway.md#managementserver) ```bash kubectl --context $CLUSTER_SURVIVING port-forward services/$CAMUNDA_RELEASE_NAME-zeebe-gateway 9600:9600 -n $CAMUNDA_NAMESPACE_SURVIVING ``` 2. List all exporters to find the corresponding ID. Alternatively, you can check your Helm chart `camunda-values.yml` file, which lists the exporters as those that had to be configured explicitly. ```bash curl -XGET 'http://localhost:9600/actuator/exporters' ```
Example output ```bash [{"exporterId":"camundaregion0","status":"ENABLED"},{"exporterId":"camundaregion1","status":"ENABLED"}] ```
3. Based on the Exporter APIs you will send a request to the Zeebe Gateway to disable the Elasticsearch exporter connected with the lost region. ```bash curl -XPOST 'http://localhost:9600/actuator/exporters/camundaregion1/disable' ``` #### Verification Port-forwarding the Zeebe Gateway via `kubectl` for the REST API and listing all exporters will reveal their current status. ```bash kubectl --context $CLUSTER_SURVIVING port-forward services/$CAMUNDA_RELEASE_NAME-zeebe-gateway 9600:9600 -n $CAMUNDA_NAMESPACE_SURVIVING curl -XGET 'http://localhost:9600/actuator/exporters' ```
Example output ```bash [{"exporterId":"camundaregion0","status":"ENABLED"},{"exporterId":"camundaregion1","status":"DISABLED"}] ```
Via the already port-forwarded Zeebe Gateway, you can also check the status of the change by using the Cluster API. ```bash curl -XGET 'http://localhost:9600/actuator/cluster' | jq .lastChange ```
Example output ```bash { "id": 4, "status": "COMPLETED", "startedAt": "2024-08-23T11:36:14.127510679Z", "completedAt": "2024-08-23T11:36:14.379980715Z" } ```
### Failback phase #### Deploy Camunda 8 in the newly created region | **Details** | **Current state** | **Desired state** | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Camunda 8** | A standalone region with a fully functional Camunda 8 setup, including the Orchestration Cluster (Zeebe, Operate, Tasklist, Zeebe Gateway) and Elasticsearch. | Restore dual-region functionality by deploying Camunda 8 isolated to the Orchestration Cluster (Zeebe and Zeebe Gateway) and Elasticsearch in the newly restored region. Disable the standalone Schema Manager to prevent seeding Elasticsearch. | | **Operate and Tasklist** | Operate and Tasklist are operational in the standalone region. | Keep Operate and Tasklist disabled in the restored region to avoid interference during the database backup and restore process. They will also be disabled in the following steps for the surviving region. | #### Procedure This step involves redeploying the recreated region using the same values files from the initial deployment. The Helm command also disables Operate and Tasklist. These components will be re-enabled only after region recovery is complete. Disabling them prevents user confusion, since no visible updates will appear for their actions while the exporters remain disabled in the following steps. This procedure requires your Helm values file, `camunda-values.yml`, in `aws/kubernetes/eks-dual-region/helm-values`, used to deploy EKS Dual-region Camunda clusters. Ensure the values for `CAMUNDA_DATA_EXPORTERS_CAMUNDAREGION0_ARGS_CONNECT_URL` and `CAMUNDA_DATA_EXPORTERS_CAMUNDAREGION1_ARGS_CONNECT_URL` correctly point to their respective regions. The placeholder in `CAMUNDA_CLUSTER_INITIALCONTACTPOINTS` should contain the Zeebe endpoints for both regions, the result of the `aws/kubernetes/eks-dual-region/procedure/generate_zeebe_helm_values.sh`. This step is equivalent to applying for the region to be recreated: - [Setting up the Camunda 8 Dual-Region Helm chart](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/dual-region.md#camunda-8-helm-chart-prerequisites) - [Deploying the Camunda 8 Dual-Region Helm chart](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/dual-region.md#install-camunda-8-using-helm) :::important The standalone Schema Manager must be disabled; otherwise, it will prevent a successful restore of the Elasticsearch backup later on. If you forget to disable it, you must manually remove all created indices in Elasticsearch in the restored region before restoring the backup. ::: There is no Helm chart option for this setting. Because `orchestration.env` is an array, it cannot be overwritten through an overlay and must be added manually on a temporary basis. Edit `camunda-values.yml` in `aws/kubernetes/eks-dual-region/helm-values` to include the following under `orchestration.env`: ```yaml orchestration: env: - name: CAMUNDA_DATABASE_SCHEMAMANAGER_CREATESCHEMA value: "false" # ... ``` ##### Deploy prerequisite services Before installing Camunda via Helm, you must redeploy the ECK-managed Elasticsearch cluster and synchronize cross-region passwords. Without this, the Helm install will fail because there is no Elasticsearch to connect to. 1. From `generic/kubernetes/operator-based/elasticsearch`, deploy the ECK operator and Elasticsearch cluster in the recreated region: ```shell cd generic/kubernetes/operator-based/elasticsearch export ELASTICSEARCH_CLUSTER_FILE="elasticsearch-cluster-dual-region.yml" KUBE_CONTEXT=$CLUSTER_RECREATED ./deploy.sh cd - ``` 2. Wait for Elasticsearch to become ready. Verify the cluster health is `green` before proceeding: ```shell kubectl get elasticsearch --context $CLUSTER_RECREATED --namespace $CAMUNDA_NAMESPACE_RECREATED ``` 3. From `aws/kubernetes/eks-dual-region/procedure`, resynchronize the Elasticsearch passwords across regions: ```shell cd aws/kubernetes/eks-dual-region/procedure ./sync_elasticsearch_passwords.sh cd - ``` This recreates the cross-region password secrets (`elasticsearch-es-password-region-0` and `elasticsearch-es-password-region-1`) required by the Zeebe exporter configuration. For more details on these steps, see [Deploy the ECK operator and Elasticsearch clusters](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/dual-region.md#deploy-the-eck-operator-and-elasticsearch-clusters) and [Synchronize Elasticsearch passwords across regions](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/dual-region.md#synchronize-elasticsearch-passwords-across-regions). ##### Install Camunda using Helm From the terminal context of `aws/kubernetes/eks-dual-region/helm-values` execute: ```bash helm install $CAMUNDA_RELEASE_NAME camunda/camunda-platform \ --version $HELM_CHART_VERSION \ --kube-context $CLUSTER_RECREATED \ --namespace $CAMUNDA_NAMESPACE_RECREATED \ -f camunda-values.yml \ -f ../../../../generic/kubernetes/operator-based/elasticsearch/camunda-elastic-values.yml \ -f $REGION_RECREATED/camunda-values.yml \ --set orchestration.profiles.operate=false \ --set orchestration.profiles.tasklist=false ``` After successfully applying the recreated region, remove the temporary `CAMUNDA_DATABASE_SCHEMAMANAGER_CREATESCHEMA` environment variable. ##### Deploy prerequisite services Before installing Camunda via Helm, you must redeploy the ECK-managed Elasticsearch cluster and synchronize cross-region passwords. Without this, the Helm install will fail because there is no Elasticsearch to connect to. 1. From the `generic/kubernetes/operator-based/elasticsearch` folder, deploy the ECK operator and Elasticsearch cluster in the recreated region: ```bash cd generic/kubernetes/operator-based/elasticsearch export ELASTICSEARCH_CLUSTER_FILE="elasticsearch-cluster-dual-region.yml" CAMUNDA_NAMESPACE=$CAMUNDA_NAMESPACE_RECREATED KUBE_CONTEXT=$CLUSTER_RECREATED ./deploy.sh cd - ``` 2. Wait for Elasticsearch to become ready. Verify the cluster health is `green` before proceeding: ```bash kubectl get elasticsearch --context $CLUSTER_RECREATED --namespace $CAMUNDA_NAMESPACE_RECREATED ``` 3. From the `generic/openshift/dual-region/procedure` folder, re-synchronize the Elasticsearch passwords across regions: ```bash cd generic/openshift/dual-region/procedure ./sync-elasticsearch-passwords.sh cd - ``` This recreates the cross-region password secrets (`elasticsearch-es-password-region-0` and `elasticsearch-es-password-region-1`) required by the Zeebe exporter configuration. For more details on these steps, see [Deploy the ECK operator and Elasticsearch clusters](/self-managed/deployment/helm/cloud-providers/openshift/dual-region.md#deploy-the-eck-operator-and-elasticsearch-clusters) and [Synchronize Elasticsearch passwords across regions](/self-managed/deployment/helm/cloud-providers/openshift/dual-region.md#synchronize-elasticsearch-passwords-across-regions). ##### Install Camunda using Helm Follow the installation steps for the **recreated region**: - [Setting up the Camunda 8 Dual-Region Helm chart](/self-managed/deployment/helm/cloud-providers/openshift/dual-region.md#configure-your-deployment-for-each-region) _Optional if you already have your pre-configured `generated-values-file.yml`_ - Once your values file is generated from the installation step, install **Camunda 8 only in the recreated region**. Adjust the installation command to disable Operate and Tasklist: ```bash --set orchestration.profiles.operate=false \ --set orchestration.profiles.tasklist=false ``` :::important The standalone Schema Manager must be disabled; otherwise, it will prevent a successful restore of the Elasticsearch backup later on. If you forget to disable it, you must manually remove all created indices in Elasticsearch in the restored region before restoring the backup. ::: There is no Helm chart option for this setting. Because `orchestration.env` is an array, it cannot be overwritten through an overlay and must be added manually on a temporary basis. Edit the `generated-values-region-0|1.yaml` to include the following under `orchestration.env`: ```yaml orchestration: env: - name: CAMUNDA_DATABASE_SCHEMAMANAGER_CREATESCHEMA value: "false" # ... ``` Example command adapted from the installation step: ```bash helm upgrade --install \ "$CAMUNDA_RELEASE_NAME" camunda/camunda-platform \ --version "$HELM_CHART_VERSION" \ --kube-context "$CLUSTER_RECREATED" \ --namespace "$CAMUNDA_NAMESPACE_RECREATED" \ -f "" \ --set orchestration.profiles.operate=false \ --set orchestration.profiles.tasklist=false ``` After successfully applying the recreated region, remove the temporary `CAMUNDA_DATABASE_SCHEMAMANAGER_CREATESCHEMA` environment variable again. - [Follow the installation step for the **recreated region only**](/self-managed/deployment/helm/cloud-providers/openshift/dual-region.md#install-camunda-8-using-helm). #### Verification The following command will show the pods deployed in the newly created region. ```bash kubectl --context $CLUSTER_RECREATED get pods -n $CAMUNDA_NAMESPACE_RECREATED ``` Half of the amount of your set `clusterSize` is used to spawn Zeebe brokers. For example, in the case of `clusterSize: 8`, four Zeebe brokers are provisioned in the newly created region. :::danger It is expected that the Zeebe Broker pods will not reach the "Ready" state since they are not yet part of a Zeebe cluster and, therefore, not considered healthy by the readiness probe. ::: Port-forwarding the Zeebe Gateway via `kubectl` and printing the topology should reveal that the new Zeebe brokers are recognized but yet a full member of the Zeebe cluster. ```bash kubectl --context $CLUSTER_SURVIVING port-forward services/$CAMUNDA_RELEASE_NAME-zeebe-gateway 8080:8080 -n $CAMUNDA_NAMESPACE_SURVIVING curl -L -X GET 'http://localhost:8080/v2/topology' \ -H 'Accept: application/json' ```
Example output ```bash { "brokers": [ { "nodeId": 0, "host": "camunda-zeebe-0.camunda-zeebe.camunda-london", "port": 26501, "partitions": [ { "partitionId": 1, "role": "leader", "health": "healthy" }, { "partitionId": 6, "role": "leader", "health": "healthy" }, { "partitionId": 7, "role": "follower", "health": "healthy" }, { "partitionId": 8, "role": "follower", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 1, "host": "camunda-zeebe-0.camunda-zeebe.camunda-paris", "port": 26501, "partitions": [], "version": "8.8.0" }, { "nodeId": 2, "host": "camunda-zeebe-1.camunda-zeebe.camunda-london", "port": 26501, "partitions": [ { "partitionId": 1, "role": "follower", "health": "healthy" }, { "partitionId": 2, "role": "leader", "health": "healthy" }, { "partitionId": 3, "role": "follower", "health": "healthy" }, { "partitionId": 8, "role": "leader", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 3, "host": "camunda-zeebe-1.camunda-zeebe.camunda-paris", "port": 26501, "partitions": [], "version": "8.8.0" }, { "nodeId": 4, "host": "camunda-zeebe-2.camunda-zeebe.camunda-london", "port": 26501, "partitions": [ { "partitionId": 2, "role": "follower", "health": "healthy" }, { "partitionId": 3, "role": "leader", "health": "healthy" }, { "partitionId": 4, "role": "follower", "health": "healthy" }, { "partitionId": 5, "role": "follower", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 5, "host": "camunda-zeebe-2.camunda-zeebe.camunda-paris", "port": 26501, "partitions": [], "version": "8.8.0" }, { "nodeId": 6, "host": "camunda-zeebe-3.camunda-zeebe.camunda-london", "port": 26501, "partitions": [ { "partitionId": 4, "role": "leader", "health": "healthy" }, { "partitionId": 5, "role": "leader", "health": "healthy" }, { "partitionId": 6, "role": "follower", "health": "healthy" }, { "partitionId": 7, "role": "leader", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 7, "host": "camunda-zeebe-3.camunda-zeebe.camunda-paris", "port": 26501, "partitions": [], "version": "8.8.0" }, ], "clusterSize": 4, "partitionsCount": 8, "replicationFactor": 2, "gatewayVersion": "8.8.0" } ```
#### Deactivate Operate and Tasklist in the active region | **Details** | **Current State** | **Desired State** | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | **Camunda 8** | The recovered region has been deployed with Operate and Tasklist disabled. Users can still access Operate and Tasklist through the surviving region. | Operate and Tasklist are turned off in the surviving region to avoid data loss during the backup procedure. | #### How to get there With the Orchestration Cluster, Operate and Tasklist are consolidated with the Zeebe Broker and Gateway into a single application. Similar to `Step 1`, this step redeploys the active region using the same value files from the initial deployment. Additionally, the Helm command disables Operate and Tasklist. These components will only be enabled at the end of the full region recovery again. This step reduces the deployed application to the Zeebe Cluster and Elasticsearch only. This procedure requires your Helm values file, `camunda-values.yml`, in `aws/kubernetes/eks-dual-region/helm-values`, used to deploy EKS dual-region Camunda clusters. Ensure that the values for `CAMUNDA_DATA_EXPORTERS_CAMUNDAREGION0_ARGS_CONNECT_URL` and `CAMUNDA_DATA_EXPORTERS_CAMUNDAREGION1_ARGS_CONNECT_URL` correctly point to their respective regions. The placeholder in `CAMUNDA_CLUSTER_INITIALCONTACTPOINTS` should contain the Zeebe endpoints for both regions, generated by the `aws/kubernetes/eks-dual-region/procedure/generate_zeebe_helm_values.sh` script. This step is equivalent to applying the configuration for the recreated region: - [Setting up the Camunda 8 Dual-Region Helm chart](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/dual-region.md#camunda-8-helm-chart-prerequisites) - [Deploying the Camunda 8 Dual-Region Helm chart](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/dual-region.md#install-camunda-8-using-helm) If not already done, remove the `CAMUNDA_DATABASE_SCHEMAMANAGER_CREATESCHEMA` variable from the `camunda-values.yml`. Edit the `camunda-values.yml` in `aws/kubernetes/eks-dual-region/helm-values` and remove the following from `orchestration.env`: ```yaml orchestration: env: - name: CAMUNDA_DATABASE_SCHEMAMANAGER_CREATESCHEMA value: "false" # ... ``` From the `aws/kubernetes/eks-dual-region/helm-values` directory, run: ```bash helm upgrade --install $CAMUNDA_RELEASE_NAME camunda/camunda-platform \ --version $HELM_CHART_VERSION \ --kube-context $CLUSTER_SURVIVING \ --namespace $CAMUNDA_NAMESPACE_SURVIVING \ -f camunda-values.yml \ -f ../../../../generic/kubernetes/operator-based/elasticsearch/camunda-elastic-values.yml \ -f $REGION_SURVIVING/camunda-values.yml \ --set orchestration.profiles.operate=false \ --set orchestration.profiles.tasklist=false ``` Follow the installation steps for the **surviving region**: - [Set up the Camunda 8 Dual-Region Helm chart](/self-managed/deployment/helm/cloud-providers/openshift/dual-region.md#configure-your-deployment-for-each-region) (optional if you already have your pre-configured `generated-values-file.yml`) - Once your values file is generated from the installation step, upgrade **Camunda 8 only in the surviving region**. Adjust the installation command to disable Operate and Tasklist: ```bash --set orchestration.profiles.operate=false \ --set orchestration.profiles.tasklist=false ``` Example command adapted from the installation step: ```bash helm upgrade --install \ "$CAMUNDA_RELEASE_NAME" camunda/camunda-platform \ --version "$HELM_CHART_VERSION" \ --kube-context "$CLUSTER_SURVIVING" \ --namespace "$CAMUNDA_NAMESPACE_SURVIVING" \ -f "" \ --set orchestration.profiles.operate=false \ --set orchestration.profiles.tasklist=false ``` - [Follow the installation step for the **surviving region only**](/self-managed/deployment/helm/cloud-providers/openshift/dual-region.md#install-camunda-8-using-helm). #### Verification 1. If the environment is exposed through an Ingress, verify that Operate and Tasklist are no longer accessible via the Ingress. 2. Check the logs of any `camunda-zeebe-X` pod to confirm that only a subset of profiles are active: ```bash kubectl --context $CLUSTER_SURVIVING logs camunda-zeebe-0 | grep "profiles are active" ``` ```bash # The default are 5 profiles, so this confirms that Operate and Tasklist are not enabled io.camunda.application.StandaloneCamunda - The following 3 profiles are active: "broker", "admin", "consolidated-auth" ``` 3. Alternatively, verify that the configuration does not list Operate and Tasklist as active profiles: ```bash kubectl --context $CLUSTER_SURVIVING get cm camunda-zeebe-configuration-unified -oyaml | grep spring -A2 ``` ```bash spring: profiles: active: "broker,admin,consolidated-auth" ``` #### Pause Camunda exporters to Elasticsearch | **Details** | **Current state** | **Desired state** | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Camunda 8** | The Orchestration Cluster is operating in a single region: Isolated to a [Zeebe cluster](/reference/glossary.md#zeebe-cluster) in the surviving region.Non-participating [Zeebe Brokers](/reference/glossary.md#zeebe-broker) in the recreated region.Data is currently being exported to Elasticsearch from the surviving region. | Preparing the newly created region to take over and restore the dual-region setup. Stop Camunda exporters to prevent new data from being exported to Elasticsearch, allowing an Elasticsearch backup to be created. | :::note This step **does not** affect process instances. Process information may not be visible in Operate and Tasklist running in the affected instance. ::: #### Procedure 1. Disable the Camunda Exporter exporters in Zeebe using kubectl and the [exporting API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#exporting-api): ```bash kubectl --context $CLUSTER_SURVIVING port-forward services/$CAMUNDA_RELEASE_NAME-zeebe-gateway 9600:9600 -n $CAMUNDA_NAMESPACE_SURVIVING curl -i localhost:9600/actuator/exporting/pause -XPOST # The successful response should be: # HTTP/1.1 204 No Content ``` #### Verification There is no API available to confirm the status of the Camunda exporters. A response code of `204` indicates that the disabling was successful. This is a synchronous operation. #### Create and restore Elasticsearch backup | **Details** | **Current State** | **Desired State** | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Camunda 8**_(Zeebe Cluster)_ | Reachable by end-users but not processing any new process instances nor reflecting User changes. This state allows for data backup without loss. | Remain not processing any new instances nor processing user inputs. | | **Elasticsearch Backup** | No backup is in progress. | Backup of Elasticsearch in the surviving region is initiated and being restored in the recreated region, containing all necessary data. The backup process may take time to complete. | #### How to get there This builds on top of the [AWS setup](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/dual-region.md) and assumes the S3 bucket was automatically created as part of the Terraform execution. :::info The procedure works for other Cloud providers and bare metal. You have to adjust the AWS S3-specific part depending on your chosen backup source for Elasticsearch. Consult the [Elasticsearch documentation on snapshot and restore](https://www.elastic.co/guide/en/elasticsearch/reference/current/snapshot-restore.html) to learn more about this, and specifically the [different supported types](https://www.elastic.co/guide/en/elasticsearch/reference/current/snapshots-register-repository.html#ess-repo-types) by Elasticsearch. ::: 1. Determine the S3 bucket Retrieve the name of the bucket via Terraform. Go to `aws/kubernetes/eks-dual-region/terraform` within the repository and retrieve the bucket name from the Terraform state: ```bash export S3_BUCKET_NAME=$(terraform output -raw s3_bucket_name) ``` Retrieve the name of the bucket from the [verify the pre-requisites step of OpenShift Dual-region](/self-managed/deployment/helm/cloud-providers/openshift/dual-region.md#verify-the-pre-requisites) step, it should be referenced as the `AWS_ES_BUCKET_NAME` variable. Export it: ```bash export S3_BUCKET_NAME="$AWS_ES_BUCKET_NAME" ``` 2. Retrieve the Elasticsearch password, and configure the backup endpoint in the surviving namespace `CAMUNDA_NAMESPACE_SURVIVING`: ```bash ELASTIC_POD=$(kubectl --context $CLUSTER_SURVIVING get pod --selector=elasticsearch.k8s.elastic.co/cluster-name=elasticsearch -o jsonpath='{.items[0].metadata.name}' -n $CAMUNDA_NAMESPACE_SURVIVING) ES_PASSWORD=$(kubectl --context $CLUSTER_SURVIVING get secret elasticsearch-es-elastic-user -n $CAMUNDA_NAMESPACE_SURVIVING -o jsonpath='{.data.elastic}' | base64 -d) kubectl --context $CLUSTER_SURVIVING exec -n $CAMUNDA_NAMESPACE_SURVIVING -it $ELASTIC_POD -c elasticsearch -- \ curl -u "elastic:$ES_PASSWORD" -XPUT 'http://localhost:9200/_snapshot/camunda_backup' \ -H 'Content-Type: application/json' \ -d' { "type": "s3", "settings": { "bucket": "'$S3_BUCKET_NAME'", "client": "camunda", "base_path": "backups" } } ' ``` 3. Create an Elasticsearch backup in the surviving namespace `CAMUNDA_NAMESPACE_SURVIVING`. Depending on the amount of data, this operation will take a while to complete. It also explicitly includes the global state, which is required during restore because it contains the Camunda index templates. ```bash # The backup will be called failback kubectl --context $CLUSTER_SURVIVING exec -n $CAMUNDA_NAMESPACE_SURVIVING -it $ELASTIC_POD -c elasticsearch -- \ curl -u "elastic:$ES_PASSWORD" -XPUT 'http://localhost:9200/_snapshot/camunda_backup/failback?wait_for_completion=true' \ -H 'Content-Type: application/json' \ -d '{"include_global_state": true}' ``` 4. Verify the backup has been completed successfully by checking all backups and ensuring the `state` is `SUCCESS`: ```bash kubectl --context $CLUSTER_SURVIVING exec -n $CAMUNDA_NAMESPACE_SURVIVING -it $ELASTIC_POD -c elasticsearch -- curl -u "elastic:$ES_PASSWORD" -XGET 'http://localhost:9200/_snapshot/camunda_backup/_all' ```
Example output ```json { "snapshots": [ { "snapshot": "failback", "uuid": "1S_C05K0RjqFyWMfjSKI_A", "repository": "camunda_backup", "version_id": 8525000, "version": "8.18.0", "indices": [ "camunda-usage-metric-tu-8.8.0_", "tasklist-metric-8.3.0_", "camunda-mapping-rule-8.8.0_", "operate-job-8.6.0_", "camunda-user-8.8.0_", "camunda-usage-metric-8.8.0_", "operate-import-position-8.3.0_", "camunda-correlated-message-subscription-8.8.0_", "operate-variable-8.3.0_", "operate-message-8.5.0_", "operate-decision-requirements-8.3.0_", "operate-process-8.3.0_", "camunda-authorization-8.8.0_", "operate-decision-instance-8.3.0_", "operate-list-view-8.3.0_", "camunda-group-8.8.0_", "operate-batch-operation-1.0.0_", "tasklist-form-8.4.0_", "tasklist-task-variable-8.3.0_", "operate-metric-8.3.0_", "camunda-web-session-8.8.0_", "operate-sequence-flow-8.3.0_", "tasklist-task-8.8.0_", "tasklist-draft-task-variable-8.3.0_", "operate-flownode-instance-8.3.1_", "operate-event-8.3.0_", "tasklist-import-position-8.2.0_", "operate-decision-8.3.0_", "operate-incident-8.3.1_", "operate-operation-8.4.1_", "camunda-role-8.8.0_", "operate-post-importer-queue-8.3.0_", "camunda-tenant-8.8.0_" ], "data_streams": [], "include_global_state": true, "state": "SUCCESS", "start_time": "2025-09-29T11:38:18.285Z", "start_time_in_millis": 1759145898285, "end_time": "2025-09-29T11:38:19.292Z", "end_time_in_millis": 1759145899292, "duration_in_millis": 1007, "failures": [], "shards": { "total": 33, "failed": 0, "successful": 33 }, "feature_states": [] } ], "total": 1, "remaining": 0 } ```
5. Configure Elasticsearch backup endpoint in the new region namespace `CAMUNDA_NAMESPACE_RECREATED`. It's essential to only do this step now as otherwise it won't see the backup: ```bash ELASTIC_POD=$(kubectl --context $CLUSTER_RECREATED get pod --selector=elasticsearch.k8s.elastic.co/cluster-name=elasticsearch -o jsonpath='{.items[0].metadata.name}' -n $CAMUNDA_NAMESPACE_RECREATED) ES_PASSWORD=$(kubectl --context $CLUSTER_RECREATED get secret elasticsearch-es-elastic-user -n $CAMUNDA_NAMESPACE_RECREATED -o jsonpath='{.data.elastic}' | base64 -d) kubectl --context $CLUSTER_RECREATED exec -n $CAMUNDA_NAMESPACE_RECREATED -it $ELASTIC_POD -c elasticsearch -- curl -u "elastic:$ES_PASSWORD" -XPUT 'http://localhost:9200/_snapshot/camunda_backup' -H 'Content-Type: application/json' -d' { "type": "s3", "settings": { "bucket": "'$S3_BUCKET_NAME'", "client": "camunda", "base_path": "backups" } } ' ``` 6. Verify that the backup can be found in the shared S3 bucket: ```bash kubectl --context $CLUSTER_RECREATED exec -n $CAMUNDA_NAMESPACE_RECREATED -it $ELASTIC_POD -c elasticsearch -- curl -u "elastic:$ES_PASSWORD" -XGET 'http://localhost:9200/_snapshot/camunda_backup/_all' ``` The example output above should be the same since it's the same backup. 7. Restore Elasticsearch backup in the new region namespace `CAMUNDA_NAMESPACE_RECREATED`. Depending on the amount of data, this operation may take a while to complete. ```bash kubectl --context $CLUSTER_RECREATED exec -n $CAMUNDA_NAMESPACE_RECREATED -it $ELASTIC_POD -c elasticsearch -- \ curl -u "elastic:$ES_PASSWORD" -XPOST 'http://localhost:9200/_snapshot/camunda_backup/failback/_restore?wait_for_completion=true' \ -H 'Content-Type: application/json' \ -d '{"include_global_state": true}' ``` 8. Verify that the restore has been completed successfully in the new region: ```bash kubectl --context $CLUSTER_RECREATED exec -n $CAMUNDA_NAMESPACE_RECREATED -it $ELASTIC_POD -c elasticsearch -- curl -u "elastic:$ES_PASSWORD" -XGET 'http://localhost:9200/_snapshot/camunda_backup/failback/_status' ```
Example output **This is only an example, and the values will differ for you.** Ensure you see `state: "SUCCESS"`, and that the properties `done` and `total` have equal values. ```json { "snapshots": [ { "snapshot": "failback", "repository": "camunda_backup", "uuid": "1S_C05K0RjqFyWMfjSKI_A", "state": "SUCCESS", "include_global_state": true, "shards_stats": { "initializing": 0, "started": 0, "finalizing": 0, "done": 33, "failed": 0, "total": 33 }, "stats": { "incremental": { "file_count": 145, "size_in_bytes": 353953 }, "total": { "file_count": 145, "size_in_bytes": 353953 }, "start_time_in_millis": 1712058365525, "time_in_millis": 1005 }, "indices": { ... } } ] } ```
#### Initialize new Camunda exporter to the recreated region | **Details** | **Current state** | **Desired state** | | ------------------------ | --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Camunda 8** | Remains unreachable by end-users while restoring functionality. | Start a new exporter to the recreated region. Ensure that both Elasticsearch instances are populated for data redundancy. Separate the initialization step (asynchronous) and confirm completion before resuming the exporters. | | **Elasticsearch Backup** | Backup has been created and restored to the recreated region. | N/A | :::info If you have upgraded from a previously migrated 8.7 system, you may still have the legacy `elasticsearchregion0` and `elasticsearchregion1` exporters configured. - If **both exporters are disabled**, you can safely ignore them. - If the old exporter is **enabled** in the survived region (for example, because you’re using **Optimize**), apply the same logic to these exporters as described for the new ones in this guide. ::: #### How to get there 1. Initialize the new exporter for the recreated region by sending an API request via the Zeebe Gateway: ```bash kubectl --context $CLUSTER_SURVIVING port-forward services/$CAMUNDA_RELEASE_NAME-zeebe-gateway 9600:9600 -n $CAMUNDA_NAMESPACE_SURVIVING curl -XPOST 'http://localhost:9600/actuator/exporters/camundaregion1/enable' -H 'Content-Type: application/json' -d '{"initializeFrom" : "camundaregion0"}' ``` #### Verification Port-forwarding the Zeebe Gateway via `kubectl` for the REST API and listing all exporters will reveal their current status. ```bash kubectl --context $CLUSTER_SURVIVING port-forward services/$CAMUNDA_RELEASE_NAME-zeebe-gateway 9600:9600 -n $CAMUNDA_NAMESPACE_SURVIVING curl -XGET 'http://localhost:9600/actuator/exporters' ```
Example output ```bash [{"exporterId":"camundaregion0","status":"ENABLED"},{"exporterId":"camundaregion1","status":"ENABLED"}] ```
You can also check the status of the change using the Cluster API via the already port-forwarded Zeebe Gateway. **Ensure the status is "COMPLETED" before proceeding with the next step.** ```bash curl -XGET 'http://localhost:9600/actuator/cluster' | jq .lastChange ```
Example output ```bash { "id": 6, "status": "COMPLETED", "startedAt": "2024-08-23T12:54:07.968549269Z", "completedAt": "2024-08-23T12:54:09.282558853Z" } ```
#### Reactivate Camunda exporter | **Details** | **Current state** | **Desired state** | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | | **Camunda 8** | Not reachable yet by end-users and currently not exporting any data. Exporters are enabled for both regions, with the operation confirmed to be completed. | Reactivate existing exporters that will allow Zeebe to export data to Elasticsearch again. | #### How to get there 1. Reactivate the exporters by sending the [exporting API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#exporting-api) activation request via the Zeebe Gateway: ```bash kubectl --context $CLUSTER_SURVIVING port-forward services/$CAMUNDA_RELEASE_NAME-zeebe-gateway 9600:9600 -n $CAMUNDA_NAMESPACE_SURVIVING curl -i localhost:9600/actuator/exporting/resume -XPOST # The successful response should be: # HTTP/1.1 204 No Content ``` #### Verification There is currently no API available to confirm the reactivation of the exporters. Only the response code `204` indicates a successful resumption. This is a synchronous operation. #### Add new brokers to the Zeebe cluster | **Details** | **Current state** | **Desired state** | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ | | **Camunda 8** | Running in two regions, but not yet utilizing all Zeebe brokers. Operate and Tasklist remain unavailable, Elasticsearch exporters enabled. | Fully functional Zeebe cluster setup utilizing both regions, recovering the main dual-region benefits. | #### How to get there 1. From the base Helm values file (`camunda-values.yml`) in `aws/kubernetes/eks-dual-region/helm-values`, extract the `clusterSize` and `replicationFactor` values. You’ll need these when re-adding the brokers to the Zeebe cluster. 2. Port-forward the Zeebe Gateway to access the [management REST API](/self-managed/components/orchestration-cluster/zeebe/configuration/gateway.md#managementserver). This allows you to send a Cluster API call to add the new brokers to the Zeebe cluster using the previously extracted `clusterSize` and `replicationFactor`. In this example, the `clusterSize` is `8` and the `replicationFactor` is `4`. Because the uneven brokers were lost, re-add them (brokers with IDs `1, 3, 5, 7`) and set the appropriate `replicationFactor` in the request. ```bash kubectl --context $CLUSTER_SURVIVING port-forward services/$CAMUNDA_RELEASE_NAME-zeebe-gateway 9600:9600 -n $CAMUNDA_NAMESPACE_SURVIVING ``` ```bash curl -XPATCH 'http://localhost:9600/actuator/cluster' \ -H 'Content-Type: application/json' \ -d '{ "brokers": { "add": [1,3,5,7] }, "partitions": { "replicationFactor": 4 } }' ``` ```bash curl -XPATCH 'http://localhost:9600/actuator/cluster' \ -H 'Content-Type: application/json' \ -d '{ "brokers": { "add": [0,2,4,6] }, "partitions": { "replicationFactor": 4 } }' ``` :::note This step can take longer depending on the cluster size, data volume, and current load. ::: #### Verification Port-forwarding the Zeebe Gateway via `kubectl` for the REST API and checking the Cluster API endpoint will show the status of the last change. ```bash kubectl --context $CLUSTER_SURVIVING port-forward services/$CAMUNDA_RELEASE_NAME-zeebe-gateway 9600:9600 -n $CAMUNDA_NAMESPACE_SURVIVING curl -XGET 'http://localhost:9600/actuator/cluster' | jq .lastChange ```
Example output ```bash { "id": 6, "status": "COMPLETED", "startedAt": "2024-08-23T12:54:07.968549269Z", "completedAt": "2024-08-23T12:54:09.282558853Z" } ```
Another way to confirm this is to use the v2 topology REST endpoint to see that partitions are actively re-distributed to the uneven members. ```bash kubectl --context $CLUSTER_SURVIVING port-forward services/$CAMUNDA_RELEASE_NAME-zeebe-gateway 8080:8080 -n $CAMUNDA_NAMESPACE_SURVIVING curl -L -X GET 'http://localhost:8080/v2/topology' \ -H 'Accept: application/json' ```
Example output ```bash { "brokers": [ { "nodeId": 0, "host": "camunda-zeebe-0.camunda-zeebe.camunda-london", "port": 26501, "partitions": [ { "partitionId": 1, "role": "leader", "health": "healthy" }, { "partitionId": 6, "role": "leader", "health": "healthy" }, { "partitionId": 7, "role": "follower", "health": "healthy" }, { "partitionId": 8, "role": "follower", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 1, "host": "camunda-zeebe-0.camunda-zeebe.camunda-paris", "port": 26501, "partitions": [ { "partitionId": 1, "role": "follower", "health": "healthy" }, { "partitionId": 6, "role": "follower", "health": "healthy" }, { "partitionId": 7, "role": "follower", "health": "healthy" }, { "partitionId": 8, "role": "follower", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 2, "host": "camunda-zeebe-1.camunda-zeebe.camunda-london", "port": 26501, "partitions": [ { "partitionId": 1, "role": "follower", "health": "healthy" }, { "partitionId": 2, "role": "leader", "health": "healthy" }, { "partitionId": 3, "role": "follower", "health": "healthy" }, { "partitionId": 8, "role": "leader", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 3, "host": "camunda-zeebe-1.camunda-zeebe.camunda-paris", "port": 26501, "partitions": [ { "partitionId": 1, "role": "follower", "health": "healthy" }, { "partitionId": 2, "role": "follower", "health": "healthy" }, { "partitionId": 3, "role": "follower", "health": "healthy" }, { "partitionId": 8, "role": "follower", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 4, "host": "camunda-zeebe-2.camunda-zeebe.camunda-london", "port": 26501, "partitions": [ { "partitionId": 2, "role": "follower", "health": "healthy" }, { "partitionId": 3, "role": "leader", "health": "healthy" }, { "partitionId": 4, "role": "follower", "health": "healthy" }, { "partitionId": 5, "role": "follower", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 5, "host": "camunda-zeebe-2.camunda-zeebe.camunda-paris", "port": 26501, "partitions": [ { "partitionId": 2, "role": "follower", "health": "healthy" }, { "partitionId": 3, "role": "follower", "health": "healthy" }, { "partitionId": 4, "role": "follower", "health": "healthy" }, { "partitionId": 5, "role": "follower", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 6, "host": "camunda-zeebe-3.camunda-zeebe.camunda-london", "port": 26501, "partitions": [ { "partitionId": 4, "role": "leader", "health": "healthy" }, { "partitionId": 5, "role": "leader", "health": "healthy" }, { "partitionId": 6, "role": "follower", "health": "healthy" }, { "partitionId": 7, "role": "leader", "health": "healthy" } ], "version": "8.8.0" }, { "nodeId": 7, "host": "camunda-zeebe-3.camunda-zeebe.camunda-paris", "port": 26501, "partitions": [ { "partitionId": 4, "role": "follower", "health": "healthy" }, { "partitionId": 5, "role": "follower", "health": "healthy" }, { "partitionId": 6, "role": "follower", "health": "healthy" }, { "partitionId": 7, "role": "follower", "health": "healthy" } ], "version": "8.8.0" }, ], "clusterSize": 4, "partitionsCount": 8, "replicationFactor": 2, "gatewayVersion": "8.8.0" } ```
#### Start Operate and Tasklist | **Details** | **Current state** | **Desired state** | | -------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | **Camunda 8** | Remains unreachable by end-users while dual-region functionality is being restored. | Enable Operate and Tasklist in both the surviving and recreated regions to restore user interaction with Camunda 8. | | **User interaction** | Users can interact with Zeebe cluster again. Dual-region functionality is restored, improving reliability and performance. | Users can fully utilize the Camunda 8 environment again. | #### Procedure :::info This step is executed at this stage because the application must be redeployed — all components run within the same Kubernetes pod. Performing it earlier would block the automatic rollout, as readiness only complete once Zeebe brokers have joined the cluster. Executing it prematurely would therefore require manual intervention. ::: Reapply or upgrade the Helm release to enable and deploy Operate and Tasklist. Assuming based on **Step 1** and **Step 2**, the base Helm values file `camunda-values.yml` in `aws/kubernetes/eks-dual-region/helm-values` includes the adjustments for Elasticsearch and the Zeebe initial brokers. Make sure to remove the `CAMUNDA_DATABASE_SCHEMAMANAGER_CREATESCHEMA` variable from `camunda-values.yml`. Edit the `camunda-values.yml` in `aws/kubernetes/eks-dual-region/helm-values` and remove the following from the `orchestration.env`: ```yaml orchestration: env: - name: CAMUNDA_DATABASE_SCHEMAMANAGER_CREATESCHEMA value: "false" # ... ``` 1. Upgrade the Camunda environment in the surviving region (`CAMUNDA_NAMESPACE_SURVIVING` and `REGION_SURVIVING`) to deploy Operate and Tasklist: ```bash helm upgrade $CAMUNDA_RELEASE_NAME camunda/camunda-platform \ --version $HELM_CHART_VERSION \ --kube-context $CLUSTER_SURVIVING \ --namespace $CAMUNDA_NAMESPACE_SURVIVING \ -f camunda-values.yml \ -f ../../../../generic/kubernetes/operator-based/elasticsearch/camunda-elastic-values.yml \ -f $REGION_SURVIVING/camunda-values.yml ``` 2. Upgrade the environment in new region (`CAMUNDA_NAMESPACE_RECREATED` and `REGION_RECREATED`) to deploy Operate and Tasklist: ```bash helm upgrade $CAMUNDA_RELEASE_NAME camunda/camunda-platform \ --version $HELM_CHART_VERSION \ --kube-context $CLUSTER_RECREATED \ --namespace $CAMUNDA_NAMESPACE_RECREATED \ -f camunda-values.yml \ -f ../../../../generic/kubernetes/operator-based/elasticsearch/camunda-elastic-values.yml \ -f $REGION_RECREATED/camunda-values.yml ``` Follow the installation instructions for both regions. You’ll need to apply `helm upgrade` on both `CLUSTER_RECREATED` and `CLUSTER_SURVIVING`. Make sure to remove the `CAMUNDA_DATABASE_SCHEMAMANAGER_CREATESCHEMA` variable from `camunda-values.yml`. Edit the `generated-values-region-0|1.yml` file and remove the following from the `orchestration.env`: ```yaml orchestration: env: - name: CAMUNDA_DATABASE_SCHEMAMANAGER_CREATESCHEMA value: "false" # ... ``` - [Apply the initial installation on the two regions](/self-managed/deployment/helm/cloud-providers/openshift/dual-region.md#install-camunda-8-using-helm). - Ensure that the services are exported correctly using `subctl`. - This step re-enables Operate and Tasklist in both regions. #### Verification 1. If the environment is exposed through an Ingress, verify that Operate and Tasklist are reachable again. 2. Check the logs of any `camunda-zeebe-X` pod to confirm the active profiles. Run this command for both the surviving and recreated clusters (`CLUSTER_RECREATED` and `CAMUNDA_NAMESPACE_RECREATED`): ```bash kubectl --context $CLUSTER_SURVIVING logs camunda-zeebe-0 | grep "profiles are active" ``` ```bash # The default are 5 profiles, so this confirms that Operate and Tasklist are enabled io.camunda.application.StandaloneCamunda - The following 5 profiles are active: "broker", "operate", "tasklist", "admin", "consolidated-auth" ``` 3. Alternatively, verify that the configuration lists Operate and Tasklist as active profiles: ```bash kubectl --context $CLUSTER_SURVIVING get cm camunda-zeebe-configuration-unified -oyaml | grep spring -A2 ``` ```bash spring: profiles: active: "broker,operate,tasklist,admin,consolidated-auth" ```
--- ### Conclusion Following this procedure ensures a structured and efficient recovery process that maintains operational continuity in dual-region deployments. Always manage dual-region environments carefully, and be prepared to follow these steps to perform a successful failover and failback. --- ## Helm 4 Learn how Helm CLI v4 behavior can affect Camunda Helm chart installs and upgrades, and how to apply workarounds. :::note Switching from Helm v3 Switching CLIs does not require a release-state migration; Helm is client-side only. See [Move from the Helm v3 CLI to v4](/self-managed/deployment/helm/operational-tasks/moving-helm-v3-to-v4.md). ::: :::info Learn more about Helm CLI v4 changes in the [Helm documentation](https://helm.sh/docs/overview/#whats-new). ::: ## Camunda Helm chart compatibility Helm CLI compatibility depends on the Camunda Helm chart version. | Chart version | Helm CLI v3 | Helm CLI v4 | | ------------------------- | ----------- | ----------- | | Camunda 8.6 – Chart 11.x | ✅ | ❌ | | Camunda 8.7 – Chart 12.x | ✅ | ❌ | | Camunda 8.8 – Chart 13.x | ✅ | ❌ | | Camunda 8.9 – Chart 14.x | ✅ | ✅ \* | | Camunda 8.10 – Chart 15.x | ✅ | ✅ \* | \* Helm CLI v4 may require workarounds when overriding environment variables. See [Workarounds](#workarounds). When you use Helm CLI v3, charts 12.x to 15.x require version 3.10 or later. Charts 14.x and 15.x also show a warning when you run `helm install` or `helm upgrade`. The warning appears in command output and in a ConfigMap with a name ending in `-warnings`. It does not block either command. ```text [camunda][warning] Helm CLI detected. Helm v3 receives security fixes only until February 10, 2027 (https://helm.sh/blog/helm-v3-end-of-life/). Upgrade to Helm v4 before then: https://helm.sh/docs/overview ``` Camunda recommends Helm CLI v4 and supports it for the full release cycles of Camunda 8.9 and 8.10. Camunda supports Helm CLI v3 (3.10 or later) until February 10, 2027, when upstream support ends. After February 10, 2027, Camunda no longer supports Helm CLI v3. Customers who continue to use Helm CLI v3 after that date do so at their own risk. ## Helm CLI v4 breaking changes {#helm-4-breaking-changes} ### Server-side apply is enabled by default Server-side apply is a Kubernetes feature that improves how changes to Kubernetes resources are merged. When multiple clients update the same resource, server-side apply reduces the risk of unintentional overwrites. In Helm CLI v4, server-side apply is enabled by default. This changes the behavior of `helm install` and `helm upgrade` compared to Helm CLI v3. #### Problem Server-side apply has a known limitation: duplicate environment variables on the same Kubernetes resource are treated as an error, rather than later entries overwriting earlier ones. In the Camunda Helm chart, environment variables are sometimes used as an override mechanism for values defined by the chart. For example, if the following is set in `values.yaml`: ```yaml identity: env: - name: CAMUNDA_LICENSE_KEY value: "--- YOUR LICENSE KEY HERE ---" ``` The rendered Identity deployment may contain duplicate entries: ```yaml spec: containers: - name: identity env: - name: CAMUNDA_LICENSE_KEY value: "--- CAMUNDA_DEFAULT_VALUE ---" - name: CAMUNDA_LICENSE_KEY value: "--- YOUR LICENSE KEY HERE ---" ``` In Helm CLI v4, this causes the install or upgrade to fail with an error similar to the following: > Error: INSTALLATION FAILED: failed to create typed patch object (default/RELEASE-identity; apps/v1, Kind=Deployment): > .spec.template.spec.containers[name="identity"].env: duplicate entries for key [name="CAMUNDA_LICENSE_KEY"] This behavior can affect any Camunda Helm chart component where environment variables are overridden using the `env` list. #### Workarounds If you encounter a duplicate environment variable error, apply one of the following workarounds: 1. Prefer dedicated `values.yaml` options over environment variable overrides whenever available. For example, use `global.license` instead of setting `CAMUNDA_LICENSE_KEY` via the `env` section. 2. Override application configuration using the `configuration` or `extraConfiguration` options in `values.yaml` instead of environment variables. See [Configure Helm chart components](/self-managed/deployment/helm/configure/application-configs.md) for details. 3. Disable server-side apply when running Helm commands: ```bash helm install ... --server-side=false helm upgrade ... --server-side=false ``` 4. Helm CLI v3 can be used as a temporary workaround until February 10, 2027. After that date, Camunda no longer supports Helm CLI v3, and continued use is at the customer’s own risk. Use one of the other workarounds instead. :::note Helm CLI v3 support Helm CLI v3.22.0 is the final Helm CLI v3 minor release. See the [Helm v3 end-of-life announcement](https://helm.sh/blog/helm-v3-end-of-life/). ::: Until Helm CLI v3 support ends, if your package manager doesn't provide Helm CLI v3, you can run it using Docker: ```bash docker run \ -v ~/.kube:/root/.kube \ alpine/helm:3.19.4 \ install RELEASE camunda/camunda-platform \ ``` ### Post-renderers are now plugins In Helm CLI v4, post-renderers must be implemented as Helm plugins. If you previously passed an executable path (for example, a shell script) using the `--post-renderer` option, you must migrate that logic into a Helm plugin. :::info Refer to the Helm documentation for a [tutorial on building a post-renderer plugin](https://helm.sh/docs/plugins/developer/tutorial-postrenderer-plugin/). ::: --- ## Helm values schema validation Learn how Helm checks values against the Camunda 8 chart schema, how to bypass the check, and which deployment tools support the bypass. The Camunda 8 Helm chart includes a `values.schema.json` file. Helm compares your values to this schema before it renders the chart templates. A value that doesn't match the schema stops the command. ## How the validation works - Helm checks the merged values against `values.schema.json` before it renders templates. - A schema error stops `helm install`, `helm upgrade`, `helm template`, and `helm lint`. - Helm has no warn-only mode. Each schema error is a hard failure. The chart also warns about unknown keys. See [find unknown keys in your values](#find-unknown-keys). - The chart documentation links and descriptions don't appear in Helm errors. - The chart schema closes some objects. A closed object rejects any key that the schema doesn't list. The error text depends on the Helm version. This example comes from a test chart that closes its root object, with the unknown key `unk`: | Helm version | Error text | | ----------------------- | -------------------------------------------------- | | 3.10 to 3.19 | `- (root): Additional property unk is not allowed` | | 3.20 and later, and 4.x | `- at '': additional properties 'unk' not allowed` | ## Find unknown keys in your values {#find-unknown-keys} Camunda 8.10 Helm chart versions 15.1.0+ warn about unknown keys in your values when Helm renders the chart templates. The warning doesn't stop the command. - The chart compares your values to the keys that its schema declares. A key that the schema doesn't declare is an unknown key. - For each unknown key, the chart adds a warning to the notes that `helm install` and `helm upgrade` print. The chart also writes the warnings to the `-warnings` ConfigMap. - The check ignores `global` values, the values of dependency charts, and free-form maps such as annotations and labels. The warning names the full path of the key: ```text [camunda][warning] UNKNOWN VALUES KEY: orchestration.foo. Helm ignores this key. Remove or correct it. A future chart version can reject unknown keys. ``` To fix the warning, remove the key or correct its name, then run the command again. To read the warnings of an installed release, run: ```shell kubectl get configmap -warnings --namespace --output jsonpath='{.data.warnings}' ``` Replace `` with the chart's full name for your release. By default, the chart combines the Helm release name and `camunda-platform`, unless the release name already contains `camunda-platform`, in which case it uses the release name alone. For example, a release named `camunda` uses the ConfigMap `camunda-camunda-platform-warnings`. The following top-level values affect the full name: | Setting | Effect | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------ | | `nameOverride` | Replaces `camunda-platform` when deriving the full name, including the check for whether the release name already contains it. | | `fullnameOverride` | Replaces the entire full name and takes precedence over `nameOverride`. | The chart truncates the full name to 63 characters and removes a trailing hyphen before appending `-warnings`. To make unknown keys stop `helm install`, `helm upgrade`, and `helm template`, set `global.strictValues: true`. The command then fails with this error: ```text [camunda][error] Unknown values keys (global.strictValues=true): orchestration.foo ``` | Setting | Default | Effect | | --------------------- | ------- | ---------------------------------------------------------------------------- | | `global.strictValues` | `false` | If `true`, unknown keys fail the render. If `false`, unknown keys only warn. | The `--skip-schema-validation` flag doesn't turn off this check, because the chart runs it during template rendering. ## Fix the values first Fix the values that the error names. This is the recommended action. The bypass removes a safety check. To find problems before you run Helm, use the `validate` command of the [Camunda Helm Toolkit](./camunda-helm-toolkit.md#validate-override-files). The toolkit page lists its exit codes. Don't remove `orchestration.fullnameOverride` to fix a schema error. This change renames the StatefulSet. The brokers then start on new, empty volumes. ## Bypass the validation {#bypass-the-validation} :::tip Validate your overrides before you bypass schema validation Use the [Camunda Helm Toolkit](./camunda-helm-toolkit.md) to check your `values.yaml` overrides for incorrect types, unsupported or deprecated settings, and configuration issues. Fix the findings before you turn off the schema validation. ::: Add the `--skip-schema-validation` flag to `helm install`, `helm upgrade`, `helm template`, or `helm lint`: ```bash helm upgrade --install camunda camunda/camunda-platform \ --values values.yaml \ --skip-schema-validation ``` Keep these limits in mind: - You need Helm 3.16.0 or later. Helm 3.15 returns `Error: unknown flag: --skip-schema-validation`. Helm 4 supports the same flag. - The flag turns off all schema checks, including type checks. It doesn't only allow unknown keys. - Camunda hasn't decided to turn on strict validation by default. This page describes the current behavior. ## Check support in your deployment tool Camunda tested these tools, except Pulumi, on a kind cluster with a test chart that has a strict schema. | Tool | Bypass possible | Setting | Tested version | Added in | | -------------------------- | -------------------- | -------------------------------------------------------------------------------------------------- | -------------- | --------------------------------- | | Helm | Yes | `--skip-schema-validation` | 3.16.0, 4.3.0 | 3.16.0 | | helm-diff | Yes | `--skip-schema-validation` | 3.15.15 | 3.9.12 | | Helmfile | Yes | `skipSchemaValidation: true` for a release, or in `helmDefaults` | 1.8.1 | 0.169.2 (host Helm 3.16 or later) | | Argo CD | Yes | `spec.source.helm.skipSchemaValidation: true` | 3.5.4 | 2.14.0 | | Flux helm-controller | Yes | `spec.install.disableSchemaValidation` and `spec.upgrade.disableSchemaValidation` | 1.6.5 | 1.1.0 (Flux 2.4.0) | | Terraform `hashicorp/helm` | No | No option. Terraform returns `Unsupported argument`. | 3.3.0 | Not applicable | | Kustomize `helmCharts` | No | No option. | 5.8.1 | Not applicable | | Pulumi Kubernetes | No documented option | Not tested. See [pulumi-kubernetes#4635](https://github.com/pulumi/pulumi-kubernetes/issues/4635). | Not tested | Not applicable | For Terraform, a workaround is to use a local copy of the chart without `values.schema.json`. This workaround removes all schema checks. For more information, see these upstream references: - [helm/helm#12743](https://github.com/helm/helm/pull/12743) - [terraform-provider-helm#1609](https://github.com/hashicorp/terraform-provider-helm/issues/1609) - [Argo CD Helm documentation](https://argo-cd.readthedocs.io/en/stable/user-guide/helm/) - [Flux HelmRelease documentation](https://fluxcd.io/flux/components/helm/helmreleases/) ## Known issues ### Parent charts and `global` values Helm passes the `global` values of a parent chart to each subchart. If a subchart schema closes its `global` object, Helm rejects the keys that the subchart doesn't list. The Camunda chart keeps its `global` object open, so this issue doesn't apply to the Camunda chart schema. ## Choose a Helm version Camunda recommends Helm CLI v4 and supports it for the full release cycles of Camunda 8.9 and 8.10. Camunda supports Helm CLI v3 (3.10 or later) until February 10, 2027, when upstream support ends. After February 10, 2027, Camunda no longer supports Helm CLI v3. Customers who continue to use Helm CLI v3 after that date do so at their own risk. If you need the bypass, use Helm 3.16.0 or later. --- ## Helm chart operational tasks In this section, find details on operational tasks associated with Kubernetes with Helm. --- ## Migrate from Bitnami subcharts :::warning Camunda 8.9 is the last version that ships Bitnami subcharts. Starting with Camunda 8.10 (Helm chart `15.x`), the bundled Bitnami subcharts (PostgreSQL, Elasticsearch, and Keycloak) are removed from the Camunda Helm chart. ::: If you run Camunda 8.9 (or earlier) with Bitnami subcharts and want to migrate to Kubernetes operators or managed services, follow the [migration guide in the 8.9 documentation](https://docs.camunda.io/docs/8.9/self-managed/deployment/helm/operational-tasks/migration-from-bitnami/). New Camunda 8.10 installations should provision [operator-based infrastructure](/self-managed/deployment/helm/configure/operator-based-infrastructure.md) or managed services directly, without Bitnami subcharts. ## Why is this guide only available for 8.9? The migration tooling deploys a Bitnami-based "source" installation and migrates its data to operator- or managed-service-backed targets. Because chart `15.x` (Camunda 8.10) no longer bundles Bitnami subcharts, the source side of the migration can only exist on Camunda 8.9 and earlier. The recommended path is therefore: 1. **While on Camunda 8.9**, migrate your Bitnami-managed infrastructure to [Kubernetes operators or managed services](https://docs.camunda.io/docs/8.9/self-managed/deployment/helm/operational-tasks/migration-from-bitnami/). 2. **Then upgrade** from 8.9 to 8.10 with a standard Helm upgrade, since your infrastructure is already operator- or managed-service-backed. --- ## Move from the Helm v3 CLI to v4 Camunda 8.9 (chart 14.x) and Camunda 8.10 (chart 15.x) support both Helm CLI v3 and v4. Camunda recommends Helm CLI v4 and supports it for the full release cycles of Camunda 8.9 and 8.10. Camunda supports Helm CLI v3 (3.10 or later) until February 10, 2027, when upstream support ends. After February 10, 2027, Camunda no longer supports Helm CLI v3. Customers who continue to use Helm CLI v3 after that date do so at their own risk. Switching CLIs does not require a release-state migration. ## Why no migration is required Helm is a client-side tool. The CLI renders chart templates and applies the resulting manifests to the cluster. Helm release metadata is stored as Kubernetes Secrets in the release namespace, and Helm CLI v3 and v4 read and write the same release-storage format. This means: - The same release works under both CLIs against the same cluster. - There is no `helm 3to4` step for Camunda charts. - You do not need to reinstall, re-import, or back up and restore release state when you change CLI versions. ## Switch from Helm CLI v3 to v4 {#switch-from-the-v3-cli-to-the-v4-cli} 1. Install the Helm CLI v4 on the workstation that runs your Helm commands. See [Installing Helm](https://helm.sh/docs/intro/install/). 2. Verify the CLI version: ```bash helm version ``` Confirm the output reports a `v4.x` client version. 3. Verify the existing release is visible to the new CLI: ```bash helm list -n ``` Your existing Camunda release appears with the same name, chart version, and revision history. 4. Continue with your normal `helm upgrade` workflow. See [Upgrade Helm chart](/self-managed/upgrade/helm/index.md). ## Helm CLI v4 behavior changes to review {#helm-4-behavior-changes-to-review} Helm CLI v4 enables server-side apply by default and removes some Helm CLI v3 plugin behaviors. Review these changes before your first install or upgrade with Helm CLI v4: - [Helm CLI v4 server-side apply and post-renderer changes](/self-managed/deployment/helm/operational-tasks/helm-v4.md) ## Helm CLI v3 support {#helm-v3-support} - Charts 14.x and 15.x support Helm CLI v3 and v4. With Helm CLI v3, these charts show a warning when you run `helm install` or `helm upgrade`. See [Camunda Helm chart compatibility](./helm-v4.md#camunda-helm-chart-compatibility). - Helm CLI v3.22.0 is the final Helm CLI v3 minor release. See the [Helm v3 end-of-life announcement](https://helm.sh/blog/helm-v3-end-of-life/). --- ## Multi-Region RDBMS operational procedure This runbook covers the day-2 operations of a [Multi-Region RDBMS](/self-managed/concepts/multi-region/multi-region-rdbms.md) setup: losing a region, bringing it back, and adding a region. :::caution Develop, test, and rehearse these procedures in a non-production environment before you need them. The commands below are examples from the [reference implementation](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/multi-region-rdbms.md). Adapt them to your environment. ::: ## What is different from dual-region In a [dual-region](./dual-region-ops.md) setup, losing a region costs the Zeebe quorum. Processing stops, and the failover procedure exists to restore it. That procedure removes the lost brokers, disables the exporter to the lost region, and later restores secondary storage from a snapshot. With three or more zones and no zone holding half the replicas or more, none of that applies. Every partition keeps a majority of its replicas. Zeebe continues processing without operator intervention. If a partition's leader was in the lost region, that partition briefly pauses while a new leader is elected. First, use the [dry run](#1-confirm-the-quorum-is-intact) to check whether the surviving replicas retain quorum. If they do, Zeebe can continue processing without manual recovery. If the failed region also hosted the database writer, [promote a surviving database instance](#2-promote-the-database-writer-if-needed) so exporting can resume. If the writer is still available, no database failover is needed. Secondary-storage recovery may affect processing throughput, depending on your flow-control configuration and available broker disk space. See [processing during secondary-storage recovery](/self-managed/concepts/multi-region/multi-region-rdbms-region-loss.md#processing-during-secondary-storage-recovery). | Step | Dual-region | Multi-Region RDBMS | | :------------------------------- | :-------------------------------------- | :---------------------------------------------- | | Restore processing | Force-remove the lost brokers | No operator action required. | | Secondary storage after failover | Disable the exporter to the lost region | Nothing, there is one exporter and one database | | Promote the database | n/a | Only if the writer was in the lost region | | Remove the lost zone | Same step as restoring processing | Recommended, not needed for quorum | | Failback | Snapshot and restore secondary storage | Redeploy the region | The [dual-region procedure](./dual-region-ops.md) takes 10 operator steps: two to fail over and eight to fail back. The diagram above counts three operator actions here: promote the writer if needed, remove the lost zone, and redeploy the region at failback. The runbook below adds confirmations around them, for five steps in total. :::warning Use this runbook only for Multi-Region RDBMS This runbook applies only to a zone-aware cluster with RDBMS secondary storage. Its region-loss procedures assume three or more zones. A cluster that starts on two zones uses only [Add a region](#add-a-region) until it runs three. If it loses a zone before then, processing stops. Bring the lost zone back before you add the third region, because the add-zone change needs a quorum. Don't run the [dual-region procedure](./dual-region-ops.md) on it: force-removing brokers or restoring secondary storage from a snapshot is unnecessary here and can lose data. For a two-region cluster with Elasticsearch, use the dual-region procedure instead. ::: ## Terminology | Term | Meaning | | :------------ | :---------------------------------------------------------------------------------------- | | Slot | A position in the region list, numbered from `0`. Fixed when the cluster is bootstrapped. | | Zone | The Camunda-level name of a region, for example `london`. One zone per region. | | Active region | A slot that is actually deployed. | | Writer | The single database instance accepting writes from every region. | ## Prerequisites Size broker storage for the retained log generated during the replication outage you plan to tolerate, with headroom for recovery. Validate capacity with a failover test at representative load, including exporter catch-up. Source the environment before running any procedure. The scripts derive everything from the Terraform state, and refuse to run against an inconsistent topology: ```bash cd procedure . ./export-terraform-outputs.sh . ./export_environment_prerequisites.sh ``` Source them with the leading dot (`. ./script.sh`). These scripts export variables into your current shell, not into a subshell. For what each variable means, see [prepare the environment](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/multi-region-rdbms.md#2-prepare-the-environment) in the deployment guide. You also need the credentials and the CLI tools the deployment used: `kubectl` contexts for every active region, `helm`, `jq`, and your cloud provider's CLI. The [deployment guide](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/multi-region-rdbms.md#requirements) lists them. Confirm the cluster is healthy before you start, so you can tell what the procedure changed: ```bash ./check-cluster-topology.sh ``` ## Handle a region loss ### Monitor secondary-storage recovery {#monitor-secondary-storage-recovery} Monitor export backlog, replication lag, broker disk usage, and processing throughput throughout the recovery procedure until secondary storage has caught up. For the backlog, use the [exporting backlog](/self-managed/operational-guides/configure-flow-control/configure-flow-control.md#exporting-backlog) dashboard panel. See [processing during secondary-storage recovery](/self-managed/concepts/multi-region/multi-region-rdbms-region-loss.md#processing-during-secondary-storage-recovery) for how throttling affects processing. If retained logs exhaust the available broker disk capacity, processing can stop. See the [capacity planning prerequisites](#prerequisites) for sizing and failover-test guidance. ### 1. Confirm the quorum is intact The surviving zones keep processing if they hold a majority of each partition's replicas. The [concept page](/self-managed/concepts/multi-region/multi-region-rdbms-region-loss.md) explains when this holds. A cluster with only two zones, such as `2-2` before you add the third region, has no such margin. Losing either zone leaves two replicas of four, and processing stops. Confirm this rather than assuming it. The script takes one lost slot and computes the surviving replicas without it. Its verdict only covers a single lost zone. If more than one zone is affected, the reference procedures don't cover the situation. Read the partition health of the surviving brokers from `GET /actuator/cluster` on a surviving region instead. Don't use `./check-cluster-topology.sh` here: it expects every active region to be up. ```bash ./failover.sh --dry-run ``` With `--dry-run`, the script reports the quorum state, prints the current cluster view, and warns if the surviving zones no longer hold a majority. It changes nothing, so you can read the verdict before deciding to act. ### 2. Promote the database writer if needed If the writer was in the lost region, promote a surviving member. The mode depends on whether the lost region is still reachable: The region is still reachable, for example during a scheduled evacuation. A switchover completes replication before promoting, so no data is lost in the RDBMS. It also takes considerably less time than an unplanned failover. Run the same script as in step 1, without `--dry-run`. It repeats the quorum report, then promotes a surviving member if the writer was in the lost region: ```bash ./failover.sh ``` The script starts the switchover only when the Aurora writer in the lost region is `available`. If the script stops while it moves the Aurora writer, you can run it again. It waits for an Aurora switchover that is still in progress, and it doesn't start a second switchover. The region is gone. Follow the [Aurora Global Database unplanned recovery procedure](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-global-database-disaster-recovery.html). The reference script doesn't automate this operation because detaching and promoting a member changes the global topology outside Terraform. To run the other steps of `failover.sh` without a switchover, add `--keep-writer`: ```bash ./failover.sh --keep-writer ``` Whatever had not replicated at the time of the outage can be missing from the promoted database. With `LOG_SEQ`, recovery depends on promoting a standby whose replication was confirmed within the configured minimum. With `DELAY`, recovery depends on the actual lag staying below the configured delay. Restore the global database membership before running the Camunda failback procedure. Camunda needs no reconfiguration and no restart, as long as the JDBC URL keeps resolving to the current writer. The reference implementation gets that from the [AWS Advanced JDBC Wrapper](/self-managed/concepts/databases/relational-db/configuration.md#usage-with-aws-aurora-postgresql--mysql). Its `failover` plugin follows the writer on established connections, and on brokers that start after the promotion. This is not general JDBC behavior. With your own database, whether connections re-resolve the writer depends on your driver and endpoint. Confirm it or plan a restart. If the writer was not in the lost region, you need no database action. #### Move the Raft leaders to the new writer region Once the writer moves, the zone priorities still favor the region that hosted the old one. Partition leaders keep exporting across regions and pay the inter-region round trip on every flush. Move the leaders next to the new writer: 1. Raise the priority of the zone that now hosts the writer. See [zone-aware clusters](/self-managed/components/orchestration-cluster/zeebe/configuration/zone-aware-clusters.md) for the priority property, and the [Partitioning API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#partitioning-api) for applying it to a running cluster. 1. Wait until the change reports `COMPLETED`. Use the `changeId` from your request to [monitor the change](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#monitor-a-configuration-change). The cluster rejects a new change while one is still in progress. 1. Run a [rebalance](/self-managed/components/orchestration-cluster/zeebe/operations/rebalancing.md) with `POST /cluster/v2/rebalance`. Priorities apply at the next election and don't move existing leaders on their own. ### 3. Route client traffic away from the lost region Zeebe keeps processing, but the gateway in the lost region is unreachable. Update your DNS or load balancer to stop sending client traffic there. Traffic routing sits outside Camunda's control and depends on your own setup. ### 4. Remove the lost zone Remove the brokers of the lost zone. One atomic change evicts them. It also drops the zone from the persisted partition distribution, so quorum stops counting replicas that cannot answer: ```bash ./failover.sh --drain-brokers ``` This issues [`DELETE /actuator/cluster/zones/?force=true`](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#remove-a-zone) against a surviving region. Without `force=true`, the API tries a graceful drain, which fails when the zone is down. Only do this for a zone that is down and unreachable, and for one zone at a time. See the [cluster management API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md). In a planned evacuation, the zone is still reachable, so don't force-remove it. Drain it gracefully instead: send `DELETE /actuator/cluster/zones/` without `force=true` through the [Remove a zone API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#remove-a-zone). The engine moves the zone's partitions to the remaining zones before it removes the brokers. The request is asynchronous. Wait until `GET /actuator/cluster` reports the change as `COMPLETED` before you shut down the zone's brokers. This step applies to a cluster with three or more zones. You must remove the zone when it held half the replicas or more. The replica count decides this, not the number of zones. See [step 1](#1-confirm-the-quorum-is-intact). Don't remove a zone from a cluster that still runs on its two-zone bootstrap. Bring the lost zone back instead, then add the third region. An evenly split [Dual-Region](/self-managed/concepts/multi-region/dual-region.md) cluster does need a force-removal, which is why it has its own failover runbook. The trade-off is failback cost. You must add a removed zone back when you bring the region back, and its brokers start empty. ### 5. Verify the degraded cluster ```bash ./verify-degraded-cluster.sh ``` The cluster should report the surviving brokers, all partitions healthy, and processing continuing. ## Bring a region back Failback is short by design. It has no secondary storage snapshot and restore step. The database holds a single copy of the exported data and replicates it itself. A returning region has nothing to catch up on at the Camunda level. ```bash ./failback.sh ``` The procedure does four things: 1. **Redeploys Camunda** in the recovered region: namespace, database secret, Helm values, and chart. 2. **Re-exports the region's services** to the ClusterSet, so brokers in other regions can resolve them again. 3. **Re-adds the zone** if you force-removed it during failover. If you left the zone in place, its brokers rejoin and catch up from the Raft log with no membership change at all. 4. **Reports the database state**, and stops if an unplanned recovery left the global topology incomplete. Move the writer back to the recovered region if the other regions are further from the current writer: ```bash ./failback.sh --switch-writer ``` If the script stops while it moves the Aurora writer back, you can run this command again. The script waits for an Aurora switchover that is still in progress. If the writer is already in the recovered region, it doesn't start a second switchover. Leaving the writer where it is costs nothing but cross-region latency for the regions furthest from it. :::note After an unplanned failover An unplanned recovery can leave the promoted member detached from the global database. Restore a complete Aurora Global Database topology with the AWS recovery procedure before running `failback.sh`. The script refuses to continue while the global cluster has only one member. ::: Confirm the topology when done: ```bash ./check-cluster-topology.sh ``` ## Add a region Adding a region to a running cluster is an online operation. The regions already running keep processing and are not restarted. The new region's brokers start first. Then `activate-region.sh` adds its zone with `POST /actuator/cluster/zones/` and waits for the change to report `COMPLETED`. The engine places the zone's replicas and raises the replication factor in one change. It does not renumber any broker. This section applies to a region slot that you provisioned but never ran. A zone that you removed during failover comes back through [Bring a region back](#bring-a-region-back) instead. ### 1. Provision the infrastructure Raise `active_region_count` by one in `terraform-cluster.tfvars`, the variable file of the initial deployment, so the new region's cluster, Transit Gateway attachments, and security group rules exist. Then apply the file: ```bash cd ../terraform/clusters terraform apply -var-file=terraform-cluster.tfvars ``` Keep the new value in the file. A later `terraform apply` with a lower `active_region_count` destroys the region's infrastructure. ### 2. Update the environment Re-source the environment so `CAMUNDA_ACTIVE_REGIONS`, the cluster size, and the replication factor reflect the new count, and register a kubectl context for the new cluster: ```bash cd ../../procedure unset CAMUNDA_ACTIVE_REGIONS CAMUNDA_CLUSTER_SIZE CAMUNDA_REPLICATION_FACTOR . ./export-terraform-outputs.sh . ./export_environment_prerequisites.sh ./register-kubecontexts.sh ``` ### 3. Add the region ```bash ./activate-region.sh ``` The procedure does the following: 1. Joins the new cluster to the ClusterSet. 1. Prepares its storage class, namespace, and database secret. 1. Renders the Helm values with the longer contact point and zone lists. 1. Installs only the new region. 1. Exports its services. 1. Adds the zone to the cluster. 1. Waits for the change to complete. The regions already running keep their shorter contact point list, and they don't restart. The contact point list matters at bootstrap. Once a cluster forms, a newcomer only has to reach one member, and the rest learn about it by gossip. The running regions pick up the longer list on their next upgrade. :::warning `activate-region.sh` only adds the zone of a slot that was in `regions` when you bootstrapped the cluster. The reference implementation provisions its infrastructure from that slot list. List every region you may ever run in `regions` before the first deployment. The script rejects any slot outside the provisioned range, `0` to `CAMUNDA_REGION_SLOTS - 1`. ::: ## Upgrade the cluster {/* TODO: multi-region upgrade paths are not tested yet. Document them once they are. */} Upgrade **one region at a time**, and wait for the cluster to report healthy before starting the next: ```bash ./check-cluster-topology.sh ``` Upgrading several regions at the same time risks losing quorum. Follow the general [upgrade guidance](/self-managed/upgrade/index.md) and create a [backup](/self-managed/operational-guides/backup-restore/backup-and-restore.md) first. ## Diagnose problems | Symptom | Start here | | :-------------------------------------- | :------------------------------------------------------------------------------ | | Brokers do not reach the expected count | `./submariner/verify-submariner.sh`, then `./submariner/diagnose-submariner.sh` | | Cross-region traffic is dropped | `./verify-cross-region-connectivity.sh` | | Export latency is higher than expected | `./measure-rdbms-latency.sh` | | Partition distribution looks wrong | `./check-cluster-topology.sh` | For the underlying causes and the AWS commands that confirm them, see [troubleshooting in the EKS guide](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/multi-region-rdbms.md#troubleshooting). ## Related resources - [Multi-Region RDBMS](/self-managed/concepts/multi-region/multi-region-rdbms.md): the architecture and its trade-offs. - [Multi-region setup with RDBMS on Amazon EKS](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/multi-region-rdbms.md): the reference implementation. - [Cluster management API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md): the endpoints these procedures call. - [Dual-region operational procedure](./dual-region-ops.md): the equivalent runbook for two regions. --- ## Migrate to zone-aware brokers This procedure migrates an existing Orchestration Cluster from numbered broker identities to zone-aware broker identities with the Camunda 8.10 Helm chart. You can migrate both single-region and dual-region clusters. The migration creates replacement brokers instead of changing the persisted identity of existing brokers. The numbered and zone-aware broker generations run together until the cluster has moved its partitions and membership to the new brokers. ## Migration overview The migration follows the same steps for single-region and dual-region clusters. The only difference is the number of zones and Helm releases involved. | Topology | Helm releases | Zones | Zone migration order | | ------------- | ------------------------------------- | ---------------------------- | ----------------------------------------- | | Single-region | One release in one Kubernetes cluster | One zone | The single zone | | Dual-region | One release per Kubernetes cluster | One zone per region, 2 total | `zoneIndex: 1` first, then `zoneIndex: 0` | In a dual-region cluster, the primary zone is the region with `zoneIndex: 0`, whose numbered brokers have even node IDs. The secondary zone is the region with `zoneIndex: 1`, whose numbered brokers have odd node IDs. You must start a dual-region migration with the secondary zone (`zoneIndex: 1`). The numbered broker with node ID `0` belongs to the primary zone and coordinates cluster configuration changes. Migrating the secondary zone first keeps this coordinator in place while the other zone migrates. If you start with the primary zone, the management API rejects the request with an error similar to: ```text Zone migration must proceed from the highest remaining zone index to the lowest. Expected next zoneIndex 1 but got 0. ``` In a dual-region cluster, run every Helm step once per release, one release at a time, and wait for each release to be healthy before you continue with the next one. You can send each management API request to any node in the cluster. However, configure the port-forward on the region with `zoneIndex: 0`, since that region is the last to be migrated. The procedure consists of these steps: 1. Upgrade every release to the chart version that supports zone-aware migration. 1. Upgrade every release with zone-aware values that keep the numbered brokers. 1. Update the persisted partitioning configuration once. 1. For each zone, add the zone's zone-aware brokers to the cluster with the management API, then remove the numbered brokers of that zone's release. The migration is not reversible. After you update the persisted partitioning configuration, you can't return the cluster to numbered brokers. If a later step fails, complete the migration instead of reverting it. See [Recover from an incomplete migration](#recover-from-an-incomplete-migration). ## Before you begin - [Back up the Orchestration Cluster](/self-managed/operational-guides/backup-restore/backup-and-restore.md) before you start this procedure. - Confirm that the existing Helm releases and their numbered brokers are healthy. See [Check broker health](#check-broker-health). - Confirm that each Kubernetes cluster has enough capacity (nodes) for both broker generations and their persistent volume claims (PVCs). - Back up the values used by each Helm release. - Suspend planned node drains, autoscaler scale-down, and other maintenance that could evict broker pods until the migration is complete. The examples in this procedure use these variables. Set them separately for each release. ```bash export RELEASE="camunda-platform" export NAMESPACE="camunda" export CHART="camunda/camunda-platform" export CHART_VERSION="" export VALUES="values.yaml" export LOCAL_ZONE="zone-a" export MANAGEMENT_URL="http://127.0.0.1:9600" ``` Replace the example values with values from your installation. Set `CHART_VERSION` to the Camunda 8.10 chart version that supports zone-aware migration. ### Access the management API Use the [Orchestration management API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md) to change the cluster topology. To reach it, and for its port, security, and TLS options, see [About this API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#about-this-api). Set `MANAGEMENT_URL` to the resulting address. To check broker health, you also need access to the brokers of every release. For example, forward the management port of the release's gateway Service to your machine: ```bash kubectl port-forward "svc/$RELEASE-zeebe-gateway" 9600:9600 --namespace "$NAMESPACE" ``` During the migration, this Service selects both the numbered and the zone-aware brokers. You can send the requests through any of them, because each broker forwards cluster configuration requests to the broker that coordinates the change. Several requests in this procedure start an asynchronous configuration change and return a `changeId`. Track each change as described in [Monitor a configuration change](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#monitor-a-configuration-change), and continue only after it reaches the `COMPLETED` status. ### Check broker health Check broker health with the [health check endpoint](/self-managed/components/orchestration-cluster/zeebe/operations/health.md#health-check) of each broker pod. A finished rollout and an `ACTIVE` state in `GET /actuator/cluster` don't prove that the brokers can process: a broker passes its readiness probe and stays `ACTIVE` in the topology even if its partitions fail to start. Query each broker pod directly, not through a Service, for example after `kubectl port-forward pod/ 9600:9600 --namespace "$NAMESPACE"`: ```bash curl --fail "$MANAGEMENT_URL/actuator/health/status" ``` A healthy broker returns HTTP `200`. If a broker returns `503`, check its logs and resolve the problem before you continue. Only brokers that belong to the logical cluster report healthy. During the migration, these brokers are expected to report unhealthy: - Zone-aware brokers of a zone that you haven't migrated yet. - Numbered brokers of a zone that you have migrated but not yet removed from the release. ## Upgrade to the migration chart Upgrade each existing release to the chart version that supports zone-aware migration, with its existing values unchanged. Keep `orchestration.partitioning.keepUnzonedBrokers` disabled during this chart upgrade. Wait for the rollout to finish before you enable the migration flag. If you enable the flag in the same command as the chart upgrade, numbered brokers can restart during migration. ```bash helm upgrade "$RELEASE" "$CHART" \ --version "$CHART_VERSION" \ --namespace "$NAMESPACE" \ --values "$VALUES" \ --wait \ --timeout 15m ``` The `--timeout` value is an example. Increase it if broker rollouts in your cluster take longer. After each rollout, [check that every broker is healthy](#check-broker-health) before you upgrade the next release or enable the migration flag. ## Configure the zone-aware values Add the zone-aware configuration to the values of each release. The `zones` list describes the complete target topology and must be identical in every release. The `zone` value selects the zone owned by this release. Keep these values unchanged while `keepUnzonedBrokers` is `true`, because they still describe the retained numbered brokers: | Value | Required value during migration | | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | | `orchestration.clusterSize` | The existing numbered cluster size. The chart divides it by `numberOfZones` to size the numbered StatefulSet. | | `orchestration.replicationFactor` | The current replication factor of the cluster. | | `orchestration.partitioning.numberOfZones` | The number of regions of the numbered deployment: `1` for single-region, `2` for dual-region. `clusterSize` must be divisible by it. | | `orchestration.partitioning.zoneIndex` | The region index of this release in the numbered deployment: `0` for single-region, `0` or `1` for dual-region. | For a single-region cluster, you can omit `numberOfZones` and `zoneIndex`. The chart defaults to `numberOfZones: 1` and `zoneIndex: 0`. If the existing values use the deprecated `global.multiregion` block, remove it when you add `orchestration.partitioning`. Set `numberOfZones` to the former `regions` value and `zoneIndex` to the former `regionId` value. The chart rejects values that configure both blocks. The sum of the zones' `numberOfReplicas` values must equal the cluster's current replication factor. If the target topology needs a higher factor, see [Update the partitioning configuration](#update-the-partitioning-configuration). ### Values examples This example migrates a single-region cluster with `clusterSize: 3` and `replicationFactor: 3`: ```yaml orchestration: clusterSize: "3" replicationFactor: "3" partitioning: scheme: zone-aware zone: zone-a zones: - name: zone-a numberOfBrokers: 3 numberOfReplicas: 3 priority: 100 # Keep the numbered brokers during the migration. keepUnzonedBrokers: true ``` This example migrates a dual-region cluster with `clusterSize: 8` and `replicationFactor: 4`. The values below belong to the primary region. In the secondary region's release, set `zone: zone-b` and `zoneIndex: 1`, and keep everything else identical. ```yaml orchestration: clusterSize: "8" replicationFactor: "4" partitioning: scheme: zone-aware # The zone owned by this Helm release and Kubernetes cluster. zone: zone-a # Include every zone, not only the local zone. Use the same list # in every participating Helm release. zones: - name: zone-a numberOfBrokers: 4 numberOfReplicas: 2 priority: 100 - name: zone-b numberOfBrokers: 4 numberOfReplicas: 2 priority: 90 # Keep the numbered brokers during the migration. keepUnzonedBrokers: true # Values of the existing numbered brokers. numberOfZones: 2 zoneIndex: 0 ``` ### Check the dual-region networking A dual-region cluster already has cross-cluster networking in place, as described in [dual-region setup](/self-managed/concepts/multi-region/dual-region.md). The zone-aware brokers reuse the existing `CAMUNDA_CLUSTER_INITIALCONTACTPOINTS` value and DNS setup, so you don't need to configure new networking. Single-region clusters can skip this section. Before you start the migration, check how the existing values address the brokers: - **Initial contact points:** Addresses of the shared headless Service, such as `camunda-zeebe.camunda-london.svc.cluster.local:26502`, keep working during and after the migration, because the Service selects both the numbered and the zone-aware brokers. Addresses of individual numbered pods, such as `camunda-zeebe-0.camunda-zeebe.camunda-london.svc.cluster.local:26502`, stop resolving when you remove the numbered brokers. Replace them with the shared Service address. - **Advertised host:** The chart applies `orchestration.env` to every broker pod in the release, including both the numbered and the zone-aware StatefulSet. If the values set `CAMUNDA_CLUSTER_NETWORK_ADVERTISEDHOST` to a fixed value, several brokers advertise the same address. Derive the advertised host from each pod instead. For example, if brokers use host networking, advertise the node IP of each pod, and configure pod anti-affinity so that no two broker pods, numbered or zone-aware, run on the same node: ```yaml orchestration: hostNetwork: true env: - name: CAMUNDA_CLUSTER_NETWORK_ADVERTISEDHOST valueFrom: fieldRef: fieldPath: status.hostIP ``` ## Start the migration Upgrade each release with the zone-aware values. ```bash helm upgrade "$RELEASE" "$CHART" \ --version "$CHART_VERSION" \ --namespace "$NAMESPACE" \ --values "$VALUES" \ --wait \ --timeout 15m ``` Each release now contains both the existing numbered StatefulSet and a new zone-specific StatefulSet. Wait for the zone-specific brokers to become ready before you continue. Don't remove the numbered brokers yet. The new brokers haven't joined the logical cluster, so the numbered brokers are still the only active members. [Check that the numbered brokers are still healthy](#check-broker-health). The new zone-aware brokers report unhealthy until you migrate their zone. ## Update the partitioning configuration Use the [partitioning API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#partitioning-api) once to update the persisted partitioning configuration. This is the point after which you can't revert the migration. The order of the zones is significant during this one-time migration. The first zone must be the zone of the numbered brokers with `zoneIndex: 0`, the second zone the one with `zoneIndex: 1`. The sum of the zones' `numberOfReplicas` values must equal the cluster's current replication factor. Otherwise, the request fails with an error similar to: ```text Sum of zone replicas [2] must equal the current replication factor [1] before zone migration starts. ``` If the target topology needs a higher factor, first increase the numbered cluster's replication factor with the [cluster scaling API](/self-managed/components/orchestration-cluster/zeebe/operations/cluster-scaling.md#2c-scaling-only-partitions), and [monitor the change](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#monitor-a-configuration-change) until it completes. Then set `orchestration.replicationFactor` in the values of every release to the new factor and upgrade the releases, so the retained numbered brokers use the updated configuration. ```bash curl --fail --request PUT \ "$MANAGEMENT_URL/actuator/cluster/partitioning" \ --header 'Content-Type: application/json' \ --data @- <<'JSON' { "config": { "scheme": "ZONE_AWARE", "zones": [ {"name": "zone-a", "numberOfReplicas": 3, "priority": 100} ] } } JSON ``` ```bash curl --fail --request PUT \ "$MANAGEMENT_URL/actuator/cluster/partitioning" \ --header 'Content-Type: application/json' \ --data @- <<'JSON' { "config": { "scheme": "ZONE_AWARE", "zones": [ {"name": "zone-a", "numberOfReplicas": 2, "priority": 100}, {"name": "zone-b", "numberOfReplicas": 2, "priority": 90} ] } } JSON ``` The response includes a `changeId`. [Monitor the change](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#monitor-a-configuration-change) until it completes. ## Migrate each zone Migrate one zone at a time. For each zone, add its zone-aware brokers to the cluster with the management API, then remove the numbered brokers of that zone's release. Removing the numbered brokers right after each zone frees their CPU and memory before you migrate the next zone, which helps when cluster capacity is tight. In a dual-region cluster, migrate the secondary zone (`zoneIndex: 1`) first, then the primary zone (`zoneIndex: 0`), as described in the [migration overview](#migration-overview). Don't migrate both zones concurrently. ### Add the zone's brokers to the cluster Use the [zone migration endpoint](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#migrate-a-zone-to-a-zone-aware-topology) to add the zone's zone-aware brokers to the cluster. The new brokers take over the partitions of the zone's numbered brokers, and the numbered brokers leave the cluster. Before you send the request, [check that every broker in the logical cluster is healthy](#check-broker-health). A partition that can't start blocks the migration, and the change stays `IN_PROGRESS`. Set `LOCAL_ZONE` to the zone to migrate: ```bash curl --fail --request PUT \ "$MANAGEMENT_URL/actuator/cluster/zones" \ --header 'Content-Type: application/json' \ --data "{\"zone\":\"$LOCAL_ZONE\"}" ``` The response includes a `changeId`. [Monitor the change](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#monitor-a-configuration-change) until it completes. After the change completes, use [`GET /actuator/cluster` in the Cluster API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#cluster-api) to check the topology: ```bash curl --fail "$MANAGEMENT_URL/actuator/cluster" ``` In the response, confirm that: - The zone-aware brokers of the migrated zone, with IDs such as `zone-a_0`, are listed with `"state": "ACTIVE"` and host the expected partitions. - The numbered brokers of the migrated zone, with numeric IDs such as `0`, are no longer listed. - The zone-aware brokers of the migrated zone [report healthy](#check-broker-health). Leave `keepUnzonedBrokers: true` if the zone migration is incomplete. Don't remove the numbered Kubernetes resources while a numbered broker of the zone still owns a partition or remains in cluster membership. ### Remove the numbered brokers of the migrated zone After the zone migration completes and the zone's numbered brokers no longer own partitions or belong to the logical cluster, remove them from the release that owns the zone. Releases whose zone you haven't migrated yet keep `keepUnzonedBrokers: true` and their migration values. Update the values of the release as follows: - Set `keepUnzonedBrokers: false`. - Remove `numberOfZones` and `zoneIndex`. - Remove `orchestration.clusterSize` and `orchestration.replicationFactor`, or set them to the totals of the `zones` list. - Keep the same complete `zones` list and the local `zone` value. With the zone-aware scheme and without numbered brokers, the chart derives the cluster size and replication factor from the `zones` list. If the values still contain conflicting settings, the upgrade fails with errors similar to these: ```text [camunda][error] orchestration.partitioning.numberOfZones and orchestration.partitioning.zoneIndex cannot be used with the zone-aware scheme; the zone list describes the topology instead. [camunda][error] orchestration.clusterSize is but orchestration.partitioning.zones sums to brokers. With the zone-aware scheme the zone list is authoritative; remove the key or make it agree. [camunda][error] orchestration.replicationFactor is but orchestration.partitioning.zones sums to replicas. With the zone-aware scheme the zone list is authoritative; remove the key or make it agree. ``` For example, the secondary region (`zoneIndex: 1`) of the dual-region cluster uses these values: ```yaml orchestration: partitioning: scheme: zone-aware zone: zone-b zones: - name: zone-a numberOfBrokers: 4 numberOfReplicas: 2 priority: 100 - name: zone-b numberOfBrokers: 4 numberOfReplicas: 2 priority: 90 keepUnzonedBrokers: false ``` Upgrade the release: ```bash helm upgrade "$RELEASE" "$CHART" \ --version "$CHART_VERSION" \ --namespace "$NAMESPACE" \ --values "$VALUES" \ --wait \ --timeout 15m ``` The upgrade removes the numbered StatefulSet and pods. The zone-aware StatefulSet and shared Services remain managed by Helm. Helm doesn't delete the numbered PVCs, so they remain bound. Repeat the steps in [Migrate each zone](#migrate-each-zone) for each remaining zone. ## Verify the migration After you migrate every zone and remove the numbered brokers from every release, query the cluster topology through the management API: ```bash curl --fail "$MANAGEMENT_URL/actuator/cluster" ``` Confirm that: - The `partitioning` object in the response reports `"scheme": "ZONE_AWARE"` and lists every zone. - Every zone-aware broker, such as `zone-a_0` and `zone-b_0`, is `ACTIVE` and hosts the expected partitions. - No numbered broker is listed. - Every zone-aware broker [reports healthy](#check-broker-health). - No numbered StatefulSet or pod remains in any release, for example with `kubectl get statefulsets,pods --namespace "$NAMESPACE"`. Don't delete the numbered PVCs until you have confirmed these checks. Then delete them explicitly according to your storage-retention policy. ## Recover from an incomplete migration You can't revert the migration after you update the persisted partitioning configuration. If a step fails, keep `keepUnzonedBrokers: true` and both broker generations running, fix the cause, and complete the migration. Don't delete the numbered PVCs until you have [verified the migration](#verify-the-migration). ### A zone migration stays in progress If the change started by `PUT /actuator/cluster/zones` stays `IN_PROGRESS`, a partition usually can't start on one of the brokers involved: 1. [Monitor the change](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#monitor-a-configuration-change) and note the operations in `pending`, and the brokers they target. 1. [Check the health](#check-broker-health) of those brokers and of every other broker in the logical cluster, and inspect the logs of the brokers that report unhealthy. 1. Fix the cause, for example missing CPU, memory, or storage, a pod that can't be scheduled, or a networking problem. The change continues after the brokers can apply the pending operations. Only one configuration change can run at a time, so don't send another zone migration request while the change is `IN_PROGRESS`. You can cancel a change with `DELETE /actuator/cluster/changes/`, but canceling doesn't revert the operations already applied and leaves the cluster in an intermediate state that needs manual intervention. Only cancel a change that can't make progress, and contact Camunda support before you do. ### Numbered brokers were removed too early If you removed the numbered brokers of a zone while they still owned partitions or belonged to the logical cluster, restore them before you continue: 1. In the values of the release, restore the migration values: `keepUnzonedBrokers: true`, `numberOfZones`, `zoneIndex`, `orchestration.clusterSize`, and `orchestration.replicationFactor`. 1. Upgrade the release. The chart recreates the numbered StatefulSet with the same name, so its pods reattach to the retained numbered PVCs and rejoin the cluster with their data. 1. [Check that the numbered brokers are healthy](#check-broker-health), then continue with [Add the zone's brokers to the cluster](#add-the-zones-brokers-to-the-cluster). If the numbered PVCs were deleted, you can't restore these brokers. Try to complete the migration without them first: if every partition still has a quorum of replicas on the remaining brokers, the zone migration can finish, and the zone-aware brokers replicate the data from the other replicas. [Monitor the change](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#monitor-a-configuration-change), or start it with [Add the zone's brokers to the cluster](#add-the-zones-brokers-to-the-cluster) if you haven't sent the request yet. Restore the cluster from the [backup](/self-managed/operational-guides/backup-restore/backup-and-restore.md) you took before the migration only if the migration can't complete. --- ## Deploy and manage Camunda Self-Managed In this section, find details on the Self-Managed installation methods. --- ## Amazon EC2 This guide provides a detailed walkthrough for installing the Camunda 8 single JAR on AWS EC2 instances. It focuses on managed services provided by AWS and their cloud offering. Finally, you will verify that the connection to your Self-Managed Camunda 8 environment is functioning correctly. This guide focuses on setting up the [Orchestration Cluster](/self-managed/reference-architecture/reference-architecture.md#camunda-hub-vs-orchestration-cluster) for Camunda 8. Camunda Hub is not covered in this manual deployment approach, as this component is not supported on virtual machines. To deploy Camunda Hub, explore the available options for [Kubernetes-based deployments](/self-managed/deployment/helm/install/quick-install.md). :::note Cost management This guide provisions resources that can incur costs in your cloud provider account. Review your provider's pricing before you begin. ::: For a starting point, see this [example calculation](https://calculator.aws/#/estimate?id=ca54a43f3b3b7eb42fe8836854775e60d8c7e04d) for a comparable three-node deployment, which you can adapt to your use case. :::note Using other cloud providers This guide is based on tools and services provided by AWS but is not limited to them. The scripts and concepts included can be adapted for other cloud providers and use cases. When using a different cloud provider, you are responsible for configuring and maintaining the resulting infrastructure. Support is limited to questions related to this guide—not to the specific tools or services of your chosen cloud provider. ::: ## Architecture The architecture outlined below describes a standard three-node deployment, distributed across three [availability zones](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html) within a single AWS region. It includes a managed OpenSearch domain deployed under the same conditions. This approach ensures high availability and redundancy in case of a zone failure. _Infrastructure diagram for a 3-node EC2 architecture (click the image to view the PDF version)_ [![AWS EC2 Architecture](./assets/aws-ec2-arch.jpg)](./assets/aws-ec2-arch.pdf) The setup includes: - A [Virtual Private Cloud](https://docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.html) (VPC), which is a logically isolated virtual network. - A [Private Subnet](https://docs.aws.amazon.com/vpc/latest/userguide/configure-subnets.html), which does not have direct internet access. - Three [EC2](https://aws.amazon.com/ec2/) instances running Ubuntu—one in each availability zone—host the Camunda 8 components. - A [managed OpenSearch](https://aws.amazon.com/what-is/opensearch/) cluster, also distributed across the three zones. - A [Public Subnet](https://docs.aws.amazon.com/vpc/latest/userguide/configure-subnets.html), which has internet access via an [Internet Gateway](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Internet_Gateway.html). - (Optional) An [Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html) (ALB) to expose web interfaces such as Operate, Tasklist, Connectors, and the Orchestration Cluster REST API. This uses sticky sessions, as requests are otherwise distributed round-robin across EC2 instances. - (Optional) A [Network Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/introduction.html) (NLB) to expose the gRPC endpoint of the Zeebe Gateway, if external applications need to connect. - (Optional) A [Bastion Host](https://en.wikipedia.org/wiki/Bastion_host) to allow access to private EC2 instances that are not publicly exposed. - Alternatively, use [AWS Client VPN](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/what-is.html) to access the private subnet. This setup requires additional effort and certificates but can be implemented using [AWS’s getting started guide](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/cvpn-getting-started.html). - A [NAT Gateway](https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway.html) that allows private EC2 instances to access the internet for downloading and updating software packages. Note that this does **not** provide inbound access to the instances. - [Security Groups](https://docs.aws.amazon.com/vpc/latest/userguide/vpc-security-groups.html) to control network traffic to and from the EC2 instances. - An [Internet Gateway](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Internet_Gateway.html) to route traffic between the VPC and the internet. Both subnet types are distributed across three availability zones in a single AWS region, supporting a high-availability architecture. :::note Single deployment You can also run this setup using a single AWS EC2 instance. However, in the event of a zone failure, the entire environment would become unreachable. ::: ## Requirements - An AWS account to provision resources. - At a high level, permissions are needed for **ec2**, **iam**, **elasticloadbalancing**, **kms**, **logs**, and **es** services. - For detailed permissions, refer to this [example policy](https://github.com/camunda/camunda-deployment-references/blob/main/aws/compute/ec2-single-region/example/policy.json). - Terraform (v1.7 or later) - A Unix-based operating system with `ssh` and `sftp` - Windows may be used with [Cygwin](https://www.cygwin.com/) or [Windows WSL](https://learn.microsoft.com/en-us/windows/wsl/install), though these configurations have not been tested. ### Outcome The result is a fully functioning Camunda Orchestration Cluster deployed in a high-availability setup using AWS EC2 and a managed OpenSearch domain. Each EC2 instance includes an additional disk, dedicated to Camunda, to separate application data from the operating system. ## 1. Configure AWS and initialize Terraform :::note Terraform infrastructure example We do not recommend using the following Terraform-based infrastructure as a module, since we cannot guarantee compatibility. Instead, we suggest reusing or extending components of the Terraform example to ensure alignment with your environment. ::: ### Obtain a copy of the reference architecture Start by downloading a copy of the reference architecture from the GitHub repository. This content will be used throughout the rest of the guide. The reference architectures are versioned according to Camunda releases (e.g., stable/8.x). The reference architecture repository allows you to reuse and extend the provided Terraform examples. This flexible implementation avoids the constraints of relying on third-party-maintained Terraform modules: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/compute/ec2-single-region/procedure/get-your-copy.sh ``` With the reference architecture in place, you can proceed with the remaining steps in this documentation. Make sure you're in the correct directory before continuing with the instructions. ### Terraform prerequisites To manage Camunda 8 infrastructure on AWS using Terraform, you need to configure Terraform's backend to store the state file remotely in an S3 bucket. This provides secure, persistent primary storage for your infrastructure. :::note Advanced users may choose to configure a different backend. The setup described here is a recommended starting point for new users. ::: #### Set up AWS authentication The [AWS Terraform provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) is required to provision resources in AWS. Before using the provider, you must authenticate it with your AWS credentials. :::caution Ownership of the created resources Any user who creates AWS resources retains administrative access to them. For better control and security, it is recommended to create a dedicated [AWS IAM user](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users.html) specifically for Terraform. This ensures the resources are properly managed and owned by a single identity. ::: You can customize the region and authentication settings as needed. Terraform supports multiple [authentication methods](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration), including: - For development or testing, you can use the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html). If your AWS CLI is already configured, Terraform will automatically detect and use those credentials. To configure the AWS CLI: ```bash aws configure ``` Enter your `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, region, and output format. These can be retrieved from the [AWS Console](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html). - For production environments, it is recommended to use a dedicated IAM user. Create [access keys](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) for that user via the AWS console, and export them as environment variables: `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`. #### Create an S3 bucket for Terraform state management Before initializing Terraform, you must create an S3 bucket to store the state file. This is essential for collaborative work and helps prevent issues such as state file corruption. Begin by setting your preferred AWS region as an environment variable to avoid repeating it in every command: ```bash export AWS_REGION= ``` Replace `` with your chosen AWS region (e.g., `eu-central-1`). Next, follow these steps to create an S3 bucket with versioning enabled: 1. Open your terminal and ensure that the AWS CLI is installed and properly configured. 2. Run the following command to create an S3 bucket for storing your Terraform state. Be sure to choose a unique bucket name, and ensure that the `AWS_REGION` environment variable is already set: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/s3-bucket/s3-bucket-creation.sh ``` 3. Enable versioning on the S3 bucket to track changes and protect the state file from accidental deletions or overwrites: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/s3-bucket/s3-bucket-versioning.sh ``` 4. Secure the bucket by blocking public access: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/s3-bucket/s3-bucket-private.sh ``` 5. Verify versioning is enabled on the bucket: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/s3-bucket/s3-bucket-verify.sh ``` The S3 bucket is now ready to securely store your Terraform state files, with versioning enabled for added protection. #### Initialize Terraform Once authentication is configured, you can initialize your Terraform project. Earlier, you created a dedicated S3 bucket (`S3_TF_BUCKET_NAME`) for storing the state file. In this step, Terraform will use that bucket along with a specific key to manage your infrastructure state. Initialize the backend and download the required provider plugins: :::note Make sure you are in the `terraform` subfolder: `camunda-deployment-references/aws/compute/ec2-single-region/terraform`. ::: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/s3-bucket/s3-bucket-tf-init.sh ``` Terraform will now use the S3 bucket to manage the state file, ensuring remote and persistent storage. ### EC2 setup The `ec2.tf` file handles the creation of compute instances and, optionally, a bastion host. Within this file, you can configure disk size, instance type, enable or disable the bastion host, and define the [Amazon Machine Image (AMI)](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html) to override the default behavior of using the latest available image. The file defines all resources related to the EC2 setup and can be customized as needed in your copied reference architecture. Note that the embedded code snippet below is limited to 30 lines. For the complete file, see the link provided at the bottom of the snippet: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/compute/ec2-single-region/terraform/cluster/ec2.tf#L1-L30 ``` ### Security setup The `security.tf` file defines several security groups to manage access and traffic flow for different use cases, including: - Allowing internal VPC traffic on Camunda ports - Permitting EC2 instances to access external traffic on ports 80 and 443 to download dependencies (e.g., Java, Camunda) - Allowing inbound traffic to the Load Balancer on specific ports - Enabling SSH access for the bastion host In addition to traffic management, this file also includes: - A KMS key for encrypting EC2 disks and the OpenSearch domain - An SSH key pair used to authorize remote SSH connections The embedded snippet below shows which resources are created and how they can be customized in your copied reference. The preview is limited to 30 lines. For the complete file, refer to the link at the bottom of the snippet: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/compute/ec2-single-region/terraform/cluster/security.tf#L1-L30 ``` ### Load balancer setup The `lb.tf` file defines the load balancer configuration used to expose Camunda 8 either publicly or within your internal network. You can further restrict access based on your security requirements. The configuration includes two types of load balancers: - A **Network Load Balancer** to expose the gRPC endpoint - An **Application Load Balancer** to expose the Camunda WebApps and REST API The embedded snippet below shows the resources defined in this file and how they can be customized in your copied reference. The preview is limited to 30 lines. For the complete file, refer to the link at the bottom of the snippet: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/compute/ec2-single-region/terraform/cluster/lb.tf#L1-L30 ``` ### OpenSearch module setup :::info Optional module If you do not want to use this module, you can skip this section. However, you will need to adjust the remaining steps to remove any references to it. If you choose not to use this module, you must provide your own Elasticsearch or OpenSearch service. Additionally, be sure to delete the `opensearch.tf` file in your reference copy—otherwise, the resources defined in it will still be created. ::: The OpenSearch module provisions an OpenSearch domain for use with Camunda. OpenSearch is a powerful alternative to Elasticsearch. :::note Migration to OpenSearch is not supported Using Amazon OpenSearch Service requires [setting up a new Camunda installation](/self-managed/setup/overview.md). Migration from earlier Camunda versions using Elasticsearch is not currently supported. Switching between Elasticsearch and OpenSearch in either direction is also unsupported. ::: #### Set up the OpenSearch domain module 1. The `opensearch.tf` file in your reference contains a basic OpenSearch setup using a local Terraform module. The snippet below shows the structure of this file, which you can modify within your cloned setup to suit your needs. :::caution Network-based security The default OpenSearch deployment relies primarily on network-level security. While this simplifies access, it can expose sensitive data within your VPC. To enhance security, consider enabling [fine-grained access control](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/fgac.html). ::: ```hcl reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/compute/ec2-single-region/terraform/cluster/opensearch.tf#L1-L30 ``` 2. Customize the cluster setup using various input options. For a complete list of available parameters, refer to the [OpenSearch module documentation](https://github.com/camunda/camunda-deployment-references/blob/main/aws/modules/opensearch/README.md). :::tip The instance type `m7i.large.search` used in the example is only a suggestion. You can change it based on your workload and requirements. ::: ### Define outputs **Terraform** allows you to define outputs—useful for retrieving important values generated during execution, such as database endpoints and other configuration details. Each Terraform definition in the reference includes output blocks at the end of the file. You can modify these as needed. The provided defaults are ready to use in automation scripts. Defining outputs helps you easily reference resources in later steps or scripts, simplifying your deployment workflow. ### Execution :::note Secret management We strongly recommend managing sensitive information using a secure secrets management tool such as HashiCorp Vault. For guidance on injecting secrets into Terraform via Vault, refer to the [Terraform Vault Secrets Injection Guide](https://developer.hashicorp.com/terraform/tutorials/secrets/secrets-vault). ::: 1. Open a terminal in the reference directory containing `config.tf` and the other `.tf` files. 2. Perform a final initialization to apply any changes made throughout this guide: ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/aws/common/procedure/s3-bucket/s3-bucket-tf-init.sh#L7 ``` 3. Plan the configuration files: ```bash terraform plan -out cluster.plan # describe what will be created ``` 4. After reviewing the plan, you can confirm and apply the changes: ```bash terraform apply cluster.plan # apply the creation ``` Terraform will now provision the Amazon EC2 resources and the OpenSearch domain with all necessary configurations. This process may take approximately 20–30 minutes to complete. ### Connect to remote machines via Bastion host (optional) Since the EC2 instances are not publicly accessible, you must connect to them through a Bastion host. Alternatively, you can use the [AWS VPN Client](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/what-is.html) to securely access the private VPC. This guide does not cover the VPN client setup, as it requires specific manual configuration and user interaction: ```sh export BASTION_HOST=$(terraform output -raw bastion_ip) # retrieves the first IP from the camunda_ips array export CAMUNDA_IP=$(terraform output -json camunda_ips | jq -r '.[0]') ssh -J admin@${BASTION_HOST} admin@${CAMUNDA_IP} ``` ## 2. Deploy Camunda 8 The following example uses scripts from the Terraform installation to deploy and configure Camunda 8. You can adapt these scripts to your own environment. Alternatively, use the [manual installation instructions](/self-managed/deployment/manual/install.md) to perform the base installation and configuration. ### Configure and run the installation procedure 1. Navigate to the procedure directory: ```sh cd procedure ``` The `procedure` directory contains Bash scripts for installing and configuring Camunda 8. 2. Configure script behavior using the following environment variables: - `CLOUDWATCH_ENABLED`: Defaults to `false`. Set to `true` to install the CloudWatch agent on each EC2 instance and export Camunda logs and Prometheus metrics to AWS CloudWatch. - `CAMUNDA_DISTRO_USER`: Camunda Enterprise LDAP username for authenticating against `artifacts.camunda.com` (Artifactory). Required to download artifacts from `artifacts.camunda.com`. - `CAMUNDA_DISTRO_PASSWORD`: Camunda Enterprise LDAP password for authenticating against `artifacts.camunda.com` (Artifactory). Required to download artifacts from `artifacts.camunda.com`. 3. Override default versions in the `camunda-install.sh` script by modifying these variables: - `OPENJDK_VERSION`: The Temurin Java version to install. - `CAMUNDA_VERSION`: The Camunda 8 version to install. - `CAMUNDA_CONNECTORS_VERSION`: The Camunda 8 Connectors version to install. :::note These variables must be set inside the `camunda-install.sh` script itself; they cannot be set as environment variables. ::: 4. Run the `all-in-one-install.sh` script. This script installs all required dependencies and configures Camunda 8 to run in a highly available setup using a managed OpenSearch instance. It automatically retrieves all required IP addresses and other details from the Terraform state via Terraform outputs. During the initial run, you will be prompted to confirm SSH connections to each EC2 instance by typing `yes`. ### Connect and use Camunda 8 The Application Load Balancer (ALB) and Network Load Balancer (NLB) endpoints are available via Terraform outputs: - `terraform output alb_endpoint`: Use this to access Operate and other Web UIs (such as Tasklist, Optimize, and Connectors). The ALB handles HTTP traffic for these interfaces, as well as the Orchestration Cluster REST API (e.g., connectors on port `9090`). - `terraform output nlb_endpoint`: Use this to access the Zeebe Gateway’s gRPC endpoint. The NLB is designed for TCP-based gRPC traffic, while the ALB handles HTTP. These endpoints use AWS-assigned public hostnames. To use your own domain, create CNAME records pointing to these hostnames or use [Route 53](https://aws.amazon.com/route53/) for DNS management and to enable SSL certificates. Note that enabling SSL and custom domains will require additional configuration in the Terraform blueprint, since it listens on HTTP by default. If you prefer not to expose your environment publicly, you can use the Bastion host (jump host) to access services locally via port forwarding. For a more secure, enterprise-grade solution, use the [AWS Client VPN](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/what-is.html) to access the private subnet within your VPC. This setup requires additional certificates and configuration, detailed in the [AWS getting started tutorial](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/cvpn-getting-started.html). The following commands can be run from within the Terraform folder to bind remote ports to your local machine via SSH port forwarding: ```sh export BASTION_HOST=$(terraform output -raw bastion_ip) # retrieves the first IP from the camunda_ips array export CAMUNDA_IP=$(terraform output -json camunda_ips | jq -r '.[0]') # 26500 - gRPC; 8080 - WebUI; 9090 - Connectors ssh -L 26500:${CAMUNDA_IP}:26500 -L 8080:${CAMUNDA_IP}:8080 -L 9090:${CAMUNDA_IP}:9090 admin@${BASTION_HOST} ``` ### Turn off bastion host (optional) If you used the [bastion host](#connect-to-remote-machines-via-bastion-host-optional) for access, it can be turned off when no longer needed for direct access to the EC2 instances. To turn off the bastion host, set the `enable_jump_host` variable to `false` in the `variables.tf` file, and reapply Terraform. ## 3. Verify connectivity to Camunda 8 Using Terraform, you can obtain the HTTP endpoint of the Application Load Balancer and interact with Camunda through the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). 1. Navigate to the Terraform folder: ```sh cd terraform ``` 2. Retrieve the Application Load Balancer output: ```sh terraform output -raw alb_endpoint ``` 3. Use the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) to communicate with Camunda: Follow the example in the [Orchestration Cluster REST API documentation](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md) to authenticate and retrieve the cluster topology. ## Manage Camunda 8 ### Monitoring Camunda exposes metrics in Prometheus format by default. For details on scraping Camunda 8 metrics, see [metrics](/self-managed/operational-guides/monitoring/metrics.md). In AWS environments, you can leverage CloudWatch for log collection and for gathering [Prometheus metrics](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/ContainerInsights-Prometheus-metrics.html). While Camunda natively integrates with Prometheus and Grafana, using CloudWatch for metrics visualization requires additional configuration. ### Backups Refer to the general backup and restore documentation in [backup and restore](/self-managed/operational-guides/backup-restore/backup-and-restore.md). When using AWS, you can utilize [S3](https://aws.amazon.com/s3/) for backing up both Zeebe and Elasticsearch / OpenSearch data. ## Troubleshooting For troubleshooting assistance, consult the [operational guides troubleshooting documentation](/self-managed/operational-guides/troubleshooting.md). ## Next steps After setting up your cluster, many users typically do the following: - [Connect to an identity provider](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md) – integrate with an external identity system for authentication. - [Secure cluster communication](/self-managed/components/orchestration-cluster/zeebe/security/secure-cluster-communication.md) – protect traffic between cluster nodes. - [Secure client communication](/self-managed/components/orchestration-cluster/zeebe/security/secure-client-communication.md) – ensure secure communication between clients and the cluster. --- ## Camunda manual installation This page guides you through the manual installation of Camunda 8 on a local machine, bare metal server, or virtual machine. ## Prerequisites - Bare metal or virtual machine - Operating system: - Linux - Windows, macOS, and other operating systems are supported for development only and not for production. - Java Virtual Machine. See [supported environments](/reference/supported-environments.md) for version details. - Configure the web applications to use an available port. By default, the Orchestration Cluster listens on port 8080. - Secondary storage - Choose a supported secondary storage backend for your installation path. - **Document-store backend (Elasticsearch or OpenSearch)**: See [supported environments](/reference/supported-environments.md). - For deployment options, see the [Elasticsearch documentation](https://www.elastic.co/docs/deploy-manage/deploy). - **RDBMS**: See [secondary storage architecture](/self-managed/reference-architecture/reference-architecture.md#secondary-storage-architecture) for backend trade-offs, and [manual installation with RDBMS](/self-managed/deployment/manual/rdbms/index.md) for supported databases and setup details. For suggested minimum hardware requirements and networking, see the [manual reference architecture requirements](/self-managed/reference-architecture/manual.md#requirements). :::tip Performance on musl-based distributions There are known performance limitations on systems that use `musl` instead of `glibc`, because Java relies on `glibc` for running native libraries. For example, Alpine Linux, which uses `musl`, has shown performance degradation compared to Debian or Ubuntu in benchmark tests. ::: :::warning Unsupported components The following components are not supported for manual installation: - Management Identity - Optimize - Camunda Hub To install these components, use one of the supported methods: - [Install with Docker](/self-managed/deployment/docker/docker.md) - [Install on Kubernetes with Helm](/self-managed/deployment/helm/index.md) ::: ## Download artifacts Download the required Camunda 8 artifacts from the following sources. Make sure that all artifacts use the same minor version to ensure compatibility. :::note Artifactory authentication Downloading artifacts from [artifactory](https://artifacts.camunda.com) requires authentication. Use your Camunda Enterprise LDAP credentials. When using `curl`, pass your username with the `-u` flag and let `curl` prompt for the password: ```sh curl -u "$CAMUNDA_DISTRO_USER" -fL ``` ::: Orchestration Cluster: - File names follow the pattern `camunda-zeebe-x.y.z.(zip|tar.gz)`. - [Maven Central](https://central.sonatype.com/artifact/io.camunda/camunda-zeebe/versions) - Select a version, then click **Browse** to view downloadable files such as `.zip` or `.tar.gz`. - [Artifactory](https://artifacts.camunda.com/ui/native/zeebe/io/camunda/camunda-zeebe/) - Select a version, then browse the files to download. - [GitHub](https://github.com/camunda/camunda/releases) - Select a release to download the files. Connectors: - Bundle (includes pre-bundled connectors from Camunda) - File names follow the pattern `connector-runtime-bundle-x.y.z-with-dependencies.jar`. - Released bundle artifacts aren't available in Maven Central or Artifactory. - Runtime-only - File names follow the pattern `connector-runtime-application-x.y.z.jar`. - Released runtime-only artifacts aren't available in Maven Central or Artifactory. :::note Some out-of-the-box connectors are licensed under the [Camunda Self-Managed Free Edition license](https://camunda.com/legal/terms/cloud-terms-and-conditions/camunda-cloud-self-managed-free-edition-terms/). See [Camunda Connectors Bundle project](https://github.com/camunda/connectors) for an overview. ::: ## Reference architecture Review the following reference architectures for deployment guidance: - [Manual reference architecture](/self-managed/reference-architecture/manual.md) - Provides an overview of the environment and requirements. - [Amazon EC2](/self-managed/deployment/manual/cloud-providers/amazon/aws-ec2.md) - A reference architecture built on Amazon Web Services (AWS) using Elastic Compute Cloud (EC2) with Ubuntu, and Amazon OpenSearch as the document-store secondary storage example. ## Orchestration Cluster For background, see the [Orchestration Cluster glossary entry](/reference/glossary.md#orchestration-cluster). For architecture details, review the [architecture](/self-managed/reference-architecture/reference-architecture.md#architecture). For configuration details, see the [Orchestration Cluster components](/self-managed/components/orchestration-cluster/overview.md). ### Configure the Orchestration Cluster This guide uses a single-node orchestration cluster with a local Elasticsearch instance as the document-store secondary storage example. If this setup matches your environment, no additional configuration is required. If you want to use RDBMS as secondary storage instead, follow [manual installation with RDBMS](/self-managed/deployment/manual/rdbms/index.md). If you plan to: - Add more nodes to the cluster - Use a different external secondary storage - Enable Connectors - Apply a license key You need to make targeted configuration changes. The following sections outline the minimum required adjustments for each use case. Combine these changes into a single `application.yaml` under the appropriate configuration keys, or export them as environment variables. For detailed configuration options and advanced setup guidance, refer to each component’s documentation under the [Orchestration cluster section](/self-managed/components/orchestration-cluster/overview.md). :::note Configuration is being unified across components. Some changes will only take effect in future versions, so you may see a mix of old and new configuration options. ::: #### Configure the secondary storage Set the secondary storage type value to `elasticsearch` or `opensearch` for this configuration path. Remove fields that do not apply to your selection. If your security settings require authentication for the secondary storage, configure both `username` and `password`. Omit these fields if authentication is not required. The following configuration defines how the Orchestration Cluster connects to document-store secondary storage (Elasticsearch or OpenSearch). This applies to the included Operate, Tasklist, Admin, and Camunda Exporter. For detailed configuration options, see the [Orchestration Cluster configuration](/self-managed/components/orchestration-cluster/core-settings/overview.md) ```bash CAMUNDA_DATA_SECONDARYSTORAGE_TYPE=elasticsearch|opensearch # defaults to elasticsearch # Elasticsearch CAMUNDA_DATA_SECONDARYSTORAGE_ELASTICSEARCH_URL=http://localhost:9200 CAMUNDA_DATA_SECONDARYSTORAGE_ELASTICSEARCH_USERNAME= CAMUNDA_DATA_SECONDARYSTORAGE_ELASTICSEARCH_PASSWORD= # OpenSearch CAMUNDA_DATA_SECONDARYSTORAGE_OPENSEARCH_URL=http://localhost:9200 CAMUNDA_DATA_SECONDARYSTORAGE_OPENSEARCH_USERNAME= CAMUNDA_DATA_SECONDARYSTORAGE_OPENSEARCH_PASSWORD= ``` ```yaml camunda: data: secondary-storage: type: elasticsearch|opensearch # defaults to elasticsearch # Elasticsearch elasticsearch: url: http://localhost:9200 username: password: # OpenSearch opensearch: url: http://localhost:9200 username: password: ``` #### Configure a multi-broker cluster This example shows a 3-broker cluster. - Set `size` to `3`. - Assign a unique `node-id` to each broker, starting from `0` and incrementing up to the total number of brokers (`0`, `1`, `2`). - Use the same `initial-contact-points` on all brokers. For more details, see the [Zeebe Broker cluster configuration](/self-managed/components/orchestration-cluster/zeebe/configuration/broker.md#zeebebrokercluster). ```bash CAMUNDA_CLUSTER_SIZE=3 CAMUNDA_CLUSTER_NODEID=0 # unique ID of this broker node in a cluster. The ID should be between 0 and number of nodes in the cluster (exclusive). CAMUNDA_CLUSTER_INITIALCONTACTPOINTS=HOST_0:26502,HOST_1:26502,HOST_2:26502 ``` ```yaml camunda: cluster: initial-contact-points: [HOST_0:26502, HOST_1:26502, HOST_2:26502] size: 3 node-id: 0 # unique ID of this broker node in a cluster. The ID should be between 0 and number of nodes in the cluster (exclusive). ``` #### Configure Connectors authentication Connectors require authentication to use their full capabilities. By default, the Orchestration Cluster uses Basic authentication. You can configure the cluster to automatically create a user with the necessary permissions at startup. If you don’t configure a user at startup, create one manually in the Admin UI after deployment. For more details, see [Admin configuration overview](/self-managed/components/orchestration-cluster/admin/overview.md). ```bash CAMUNDA_SECURITY_INITIALIZATION_USERS_1_USERNAME=connectors CAMUNDA_SECURITY_INITIALIZATION_USERS_1_PASSWORD=connectors CAMUNDA_SECURITY_INITIALIZATION_USERS_1_NAME="Connectors User" CAMUNDA_SECURITY_INITIALIZATION_USERS_1_EMAIL=connectors@company.com CAMUNDA_SECURITY_INITIALIZATION_DEFAULTROLES_CONNECTORS_USERS_0=connectors ``` ```yaml camunda: security: initialization: users: - username: connectors password: connectors name: Connectors User email: connectors@company.com default-roles: connectors: users: - connectors ``` #### Configure the license key If your Camunda 8 Self-Managed installation requires a license, provide the license key in one of the following ways: ```bash CAMUNDA_LICENSE_KEY="" ``` ```yaml camunda: license: key: >- --------------- BEGIN CAMUNDA LICENSE KEY --------------- [...] --------------- END CAMUNDA LICENSE KEY --------------- ``` ### Run the Orchestration Cluster Once you've downloaded the Orchestration Cluster distribution, extract it into a folder. 1. Extract the files using your GUI or CLI: ```bash mkdir -p camunda && unzip camunda-zeebe-x.y.z.zip -d camunda mkdir -p camunda && tar -xzf camunda-zeebe-x.y.z.tar.gz -C camunda ``` 2. Open the extracted folder. 3. Update the configuration in `config/application.yaml`, or export the environment variables. 4. Navigate to `bin` folder. 5. Run `camunda.sh` (Linux/macOS) or `camunda.bat` (Windows). 6. Open [http://localhost:8080](http://localhost:8080). On first access, you'll be asked to create an admin user unless [Admin](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md) is configured with OIDC or a similar option. :::note Camunda 8 components without a valid license may display **Non-Production License** in the navigation bar and issue warnings in the logs. These warnings don’t affect startup or functionality, except that Camunda Hub is limited to five users. To obtain a license, visit the [Camunda Enterprise page](https://camunda.com/platform/camunda-platform-enterprise-contact/). ::: ### Run the Orchestration Cluster as a service This example shows how to run the Orchestration Cluster as a [`systemd`](https://systemd.io/) service on Ubuntu. Adjust the paths, user, and group as needed for your environment. The example uses a file with environment variables, but you can adapt it to use an `application.yaml` instead. 1. Create a `systemd` service file named `camunda.service` and adjust it fit your own paths, user and group in `/etc/systemd/system/camunda.service`. ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/compute/debian/configs/camunda.service ``` 2. Change the permissions on `/etc/systemd/system/camunda.service` to `644`: ```bash sudo chmod 644 /etc/systemd/system/camunda.service ``` 3. Reload `systemd` and start the new service: ```bash sudo systemctl daemon-reload sudo systemctl start camunda.service ``` 4. Verify that the service is running: ```bash systemctl status camunda.service ``` View logs with: ```bash journalctl -e -u camunda ``` ### Verify the Orchestration Cluster Check the logs for a successful startup message, such as: ```bash [2025-08-05 13:34:51.964] [main] INFO org.springframework.boot.web.embedded.tomcat.TomcatWebServer - Tomcat started on port 8080 (http) with context path '/' ... [2025-08-05 13:34:52.006] [main] INFO org.springframework.boot.web.embedded.tomcat.TomcatWebServer - Tomcat initialized with port 9600 (http) [2025-08-05 13:34:52.048] [main] INFO org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 79 ms [2025-08-05 13:34:52.054] [main] INFO org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver - Exposing 17 endpoints beneath base path '/actuator' [2025-08-05 13:34:52.078] [main] INFO org.springframework.boot.web.embedded.tomcat.TomcatWebServer - Tomcat started on port 9600 (http) with context path '/' [2025-08-05 13:34:52.088] [main] INFO io.camunda.application.StandaloneCamunda - Started StandaloneCamunda in 9.376 seconds (process running for 9.817) ``` Check the cluster topology with the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md): ```bash # replace username and password with the details of the admin user you created on first startup curl -u username:password -L 'http://localhost:8080/v2/topology' \ -H 'Accept: application/json' ```
Example output ```json // amount of brokers, size, partitions etc. depends on your configuration // Example: 1 broker, 3 partitions { "brokers": [ { "nodeId": 0, "host": "HOST_0", "port": 26501, "partitions": [ { "partitionId": 1, "role": "leader", "health": "healthy" }, { "partitionId": 2, "role": "leader", "health": "healthy" }, { "partitionId": 3, "role": "leader", "health": "healthy" } ], "version": "8.8.0" } ], "clusterSize": 1, "partitionsCount": 3, "replicationFactor": 1, "gatewayVersion": "8.8.0", "lastCompletedChangeId": "-1" } ```
Check the health status of the Orchestration Cluster with the actuator endpoint: ```bash curl localhost:9600/actuator/health ```
Example output ```json { "status": "UP", "groups": ["liveness", "readiness", "startup", "status"], "components": { "brokerReady": { "status": "UP" }, "brokerStartup": { "status": "UP" }, "brokerStatus": { "status": "UP" }, "livenessState": { "status": "UP" }, "readinessState": { "status": "UP" }, "schemaReadinessCheck": { "status": "UP" }, "searchEngineStatus": { "status": "UP" } } } ```
## Connectors For background, see the [Connectors glossary entry](/reference/glossary.md#connector). For architecture details, review the [architecture](/self-managed/reference-architecture/reference-architecture.md#architecture). For configuration options, see the [Connectors components documentation](/self-managed/components/connectors/overview.md). ### Configure Connectors If you run Connectors on the same machine as the Orchestration Cluster, change the default port (`8080`) to avoid conflicts. Connectors require authentication to communicate with the Orchestration Cluster REST API and Zeebe. By default, Connectors connect to: - `localhost:8080` (Orchestration Cluster REST API) - `localhost:26500` (Zeebe) ```bash SERVER_PORT=9090 CAMUNDA_CLIENT_RESTADDRESS=http://localhost:8080 CAMUNDA_CLIENT_GRPCADDRESS=http://localhost:26500 CAMUNDA_CLIENT_MODE=selfManaged CAMUNDA_CLIENT_AUTH_METHOD=basic CAMUNDA_CLIENT_AUTH_USERNAME=connectors CAMUNDA_CLIENT_AUTH_PASSWORD=connectors ``` Save the following as `application.yaml` in the same folder as your `connector-runtime-(application|bundle)-x-y-z(-with-dependencies).jar`. ```yaml server: port: 9090 camunda: client: rest-address: http://localhost:8080 grpc-address: http://localhost:26500 mode: selfManaged auth: method: basic username: connectors password: connectors ``` For more information about the configuration of the Connectors, see [Connectors configuration](/self-managed/components/connectors/connectors-configuration.md) ### Run Connectors Both the pre-bundled and runtime-only versions of the Connectors behave the same at runtime. They automatically detect and register all connectors available on the classpath during execution. Each connector uses its default configuration as defined by the `@OutboundConnector` or `@InboundConnector` annotations. Consider the following file structure: ```shell /home/user/connectors $ └── connector-runtime-(application|bundle)-x.y.z-with-dependencies.jar ``` To start the connector runtime locally, run: ```shell java -jar /home/user/connectors/connector-runtime-bundle-x.y.z-with-dependencies.jar ``` The runtime bundle is packaged as a Spring Boot uber-jar (with its dependencies under `BOOT-INF/lib/`), so it must be launched with `java -jar`. The older flat-classpath invocation (`java -cp "/home/user/connectors/*" "io.camunda.connector.runtime.app.ConnectorRuntimeApplication"`) no longer works as of connector runtime 8.9.4. To load additional custom connectors, place their JARs in a separate directory and point Spring Boot's loader at it: ```shell java -Dloader.path=/home/user/custom-connectors \ -jar /home/user/connectors/connector-runtime-bundle-x.y.z-with-dependencies.jar ``` This starts a Zeebe client, registering the defined connector as a job worker. By default, it connects to a local Zeebe instance at port `26500`. ### Run Connectors as a service This example shows how to run the Connectors as a [`systemd`](https://systemd.io/) service on Ubuntu. Adjust the paths, user, and group as needed for your environment. The example uses a file with environment variables, but you can adapt it to use an `application.yaml` instead. 1. Create a `systemd` service file named `camunda-connectors.service` and adjust it fit your own paths, user and group in `/etc/systemd/system/camunda-connectors.service`. ```bash reference https://github.com/camunda/camunda-deployment-references/blob/main/generic/compute/debian/configs/camunda-connectors.service ``` 2. Change the permissions on `/etc/systemd/system/camunda-connectors.service` to `644`: ```bash sudo chmod 644 /etc/systemd/system/camunda-connectors.service ``` 3. Reload `systemd` and start the service: ```bash sudo systemctl daemon-reload sudo systemctl start camunda-connectors.service ``` 4. Verify that the service is running: ```bash systemctl status camunda-connectors.service ``` View logs with: ```bash journalctl -e -u camunda-connectors ``` ### Verify Connectors Check the logs for a successful startup message, such as: ```bash 2025-08-05T14:49:58.641+02:00 INFO 99856 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 3 endpoints beneath base path '/actuator' 2025-08-05T14:49:58.666+02:00 INFO 99856 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 9090 (http) with context path '/' 2025-08-05T14:49:58.702+02:00 INFO 99856 --- [ main] i.c.c.r.app.ConnectorRuntimeApplication : Started ConnectorRuntimeApplication in 1.286 seconds (process running for 1.386) ``` Check the health status of Connectors with the actuator endpoint: ```bash curl localhost:9090/actuator/health ```
Example output ```json { "status": "UP", "groups": ["readiness"], "components": { "camundaClient": { "status": "UP" }, "diskSpace": { "status": "UP", "details": { "total": -1, "free": -1, "threshold": -1, "path": "/home/user/connectors/.", "exists": true } }, "ping": { "status": "UP" }, "processDefinitionImport": { "status": "UP", "details": { "operateEnabled": true } }, "ssl": { "status": "UP", "details": { "validChains": [], "invalidChains": [] } }, "zeebeClient": { "status": "UP", "details": { "numBrokers": 1, "anyPartitionHealthy": true } } } } ```
## Next steps After setting up your cluster, many users typically do the following: - [Connect to an identity provider](/self-managed/components/orchestration-cluster/admin/connect-external-identity-provider.md) – integrate with an external identity system for authentication. - [Secure cluster communication](/self-managed/components/orchestration-cluster/zeebe/security/secure-cluster-communication.md) – protect traffic between cluster nodes. - [Secure client communication](/self-managed/components/orchestration-cluster/zeebe/security/secure-client-communication.md) – ensure secure communication between clients and the cluster. --- ## Configure RDBMS for manual installations Configure RDBMS secondary storage drivers, connections, and initial schema for **manual** Camunda 8 installations (VM, bare metal, or standalone Java). ## Prerequisites - **Supported RDBMS**: See the [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md). - **JDBC drivers**: PostgreSQL, MariaDB, SQL Server, and H2 are bundled. Oracle and MySQL must be user-supplied. - **Java 21+**: Required for Orchestration Cluster components (Zeebe, Operate, Tasklist, Identity). Management Identity remains Java 17+. See [supported environments](/reference/supported-environments.md). - **Database user**: Needs DDL permissions (CREATE TABLE, ALTER TABLE, DROP TABLE) for schema initialization. ## JDBC driver management ### Bundled drivers Camunda bundles these JDBC drivers for redistribution: | Database | Driver artifact | | -------------------- | -------------------------------------- | | PostgreSQL | `org.postgresql:postgresql` | | MariaDB | `org.mariadb.jdbc:mariadb-java-client` | | Microsoft SQL Server | `com.microsoft.sqlserver:mssql-jdbc` | | H2 | `com.h2database:h2` | | AWS Aurora JDBC | Bundled | You can optionally supply your own version of any bundled driver for flexibility or compliance requirements. ### User-supplied drivers (Oracle, MySQL) **Recommended approach**: Place drivers in `/opt/camunda-drivers` and set CLASSPATH: ```bash export CLASSPATH="/opt/camunda-drivers/*:$CLASSPATH" ./camunda.sh ``` For Docker, mount external drivers using a volume. The driver JAR must be placed directly in the mounted directory (no subdirectories): ```yaml services: camunda: image: camunda/camunda: ports: - "8080:8080" - "26500:26500" - "9600:9600" environment: CAMUNDA_DATA_SECONDARY_STORAGE_TYPE: rdbms CAMUNDA_DATA_SECONDARY_STORAGE_RDBMS_URL: jdbc:mysql://mysql:3306/camunda CAMUNDA_DATA_SECONDARY_STORAGE_RDBMS_USERNAME: camunda CAMUNDA_DATA_SECONDARY_STORAGE_RDBMS_PASSWORD: demo volumes: - /path/to/driver-lib:/driver-lib ``` ## Database setup ### Step 1: Create database and user PostgreSQL example: ```sql CREATE DATABASE camunda ENCODING 'UTF8'; CREATE USER camunda WITH PASSWORD 'your-secure-password'; GRANT CONNECT ON DATABASE camunda TO camunda; GRANT USAGE ON SCHEMA public TO camunda; GRANT CREATE ON SCHEMA public TO camunda; GRANT CREATE ON DATABASE camunda TO camunda; ``` For other databases, see [RDBMS Helm configuration](/self-managed/deployment/helm/configure/database/rdbms.md). ### Step 2: Configure connection Use the unified Camunda configuration properties: ```bash export CAMUNDA_DATA_SECONDARY_STORAGE_TYPE=rdbms export CAMUNDA_DATA_SECONDARY_STORAGE_RDBMS_URL="jdbc:postgresql://localhost:5432/camunda" export CAMUNDA_DATA_SECONDARY_STORAGE_RDBMS_USERNAME="camunda" export CAMUNDA_DATA_SECONDARY_STORAGE_RDBMS_PASSWORD="your-secure-password" ``` ### Step 3: Schema initialization By default, Liquibase automatically creates the schema on first startup. To manually manage schema: ```bash export CAMUNDA_DATA_SECONDARY_STORAGE_RDBMS_AUTO_DDL=false ``` Then apply SQL/Liquibase scripts manually using your DBA tools. See [access SQL and Liquibase scripts](/self-managed/deployment/helm/configure/database/access-sql-liquibase-scripts.md). ## Orchestration Cluster connection parameters The Orchestration Cluster uses a single RDBMS configuration shared across orchestration services and UIs that read from secondary storage. Set the connection properties as shown in [Step 2: Configure connection](#step-2-configure-connection) above. ### Zeebe exporter configuration Configure Zeebe's exporter flush behavior: ```bash export CAMUNDA_DATA_SNAPSHOTPERIOD=5m export CAMUNDA_DATA_SECONDARY_STORAGE_RDBMS_FLUSHINTERVAL=PT0.5S export CAMUNDA_DATA_SECONDARY_STORAGE_RDBMS_QUEUESIZE=1000 ``` ## Schema management ### Liquibase (recommended) Liquibase is the recommended approach for schema management. It is supported for the Orchestration Cluster and automatically applies migrations on startup (when `auto-ddl=true`, the default). When Liquibase runs successfully, you'll see this log entry at INFO level: ```text [INFO] io.camunda.application.commons.rdbms.MyBatisConfiguration - Initializing Liquibase for RDBMS with global table trimmedPrefix ''. ``` ### Manual SQL execution Manual SQL execution is supported as an alternative. When using manual SQL: - You must strictly adhere to the bundled scripts in the order provided. - Camunda cannot guarantee future updates will work if you modify scripts. - This approach is useful when DBAs require strict control over database changes. Download scripts: [Access SQL and Liquibase scripts](/self-managed/deployment/helm/configure/database/access-sql-liquibase-scripts.md). ## Next steps - [Operations and maintenance](/self-managed/deployment/manual/rdbms/operations.md) - [Secondary storage architecture](/self-managed/reference-architecture/reference-architecture.md#secondary-storage-architecture) - [RDBMS production architecture](/self-managed/deployment/manual/rdbms/rdbms-production-architecture.md) - [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) - [Access SQL and Liquibase scripts](/self-managed/deployment/helm/configure/database/access-sql-liquibase-scripts.md) --- ## Manual installation with RDBMS Install Camunda 8 Self-Managed manually on a VM, bare-metal server, or standalone Java runtime while using a relational database (RDBMS) as **secondary storage**. :::caution Manual installation is **not** supported for Kubernetes. If you run on Kubernetes, use the [Helm charts](/self-managed/deployment/helm/index.md). ::: ## Manual deployment data flow For backend trade-offs and production architecture decisions, see [secondary storage architecture](/self-managed/reference-architecture/reference-architecture.md#secondary-storage-architecture). In this manual deployment path, the Orchestration Cluster reads from a single configured secondary storage type: RDBMS. However, the Zeebe Broker can export to multiple targets simultaneously. If you deploy Optimize, configure both the RDBMS exporter (for Orchestration Cluster operations) and an Elasticsearch/OpenSearch exporter for Optimize. ```mermaid graph LR subgraph oc[Orchestration Cluster] broker[Zeebe Broker] rdbms_exporter[RDBMS Exporter] doc_exporter["Elasticsearch/OpenSearch Exporter\n(for Optimize)"] api[Orchestration Cluster API] apps[Operate · Tasklist · Admin] end subgraph storage[Data Stores] rdbms[RDBMS secondary storage] es["Elasticsearch/OpenSearch for Optimize"] end opt[Optimize] broker -->|Export orchestration data| rdbms_exporter rdbms_exporter -->|Write| rdbms apps -->|Use| api broker -.->|Optional export for Optimize| doc_exporter doc_exporter -.->|Optional write| es api -->|Query| rdbms opt -->|Read/Write| es style rdbms_exporter fill:#e4eef8,stroke:#2272c9,color:#14082c style doc_exporter fill:#e4eef8,stroke:#2272c9,color:#14082c style api fill:#e4eef8,stroke:#2272c9,color:#14082c style apps fill:#e4eef8,stroke:#2272c9,color:#14082c style rdbms fill:#fde8da,stroke:#fc5d0d,color:#14082c style es fill:#fde8da,stroke:#fc5d0d,color:#14082c style opt fill:#e8fdf1,stroke:#10c95d,color:#14082c style oc fill:#f0f5ff,stroke:#2272c9 style storage fill:#fff8f4,stroke:#fc5d0d ``` **Key points:** - Operate, Tasklist, and Admin use the Orchestration Cluster API rather than reading directly from secondary storage. The Orchestration Cluster API reads from the configured secondary storage (RDBMS). - Optimize requires Elasticsearch or OpenSearch and reads and writes directly to it. - The Zeebe Broker can export to multiple targets simultaneously to support this architecture. ## Supported installation targets - VM-based deployments - Bare-metal installations - Standalone Java application deployments ## Prerequisites - **Supported RDBMS**: See [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md). - **JDBC drivers**: See [RDBMS configuration](/self-managed/deployment/manual/rdbms/configuration.md). - **Schemas and scripts**: Use the bundled SQL or Liquibase scripts for schema creation and upgrades. See [access SQL and Liquibase scripts](/self-managed/deployment/helm/configure/database/access-sql-liquibase-scripts.md). ## When to choose manual installation Choose manual installation when you run Camunda 8 on **VMs, bare metal, or a standalone Java runtime** and need full control over the operating system, networking, and lifecycle management. If you run on Kubernetes, use [Helm charts](/self-managed/deployment/helm/index.md). For local development or evaluation, consider [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md). ## Supported features The Orchestration Cluster is fully supported with RDBMS secondary storage for workflow execution, instance monitoring, and user task management. ## Get started ## Related documentation - [Secondary storage architecture](/self-managed/concepts/secondary-storage/index.md) - [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) - [Manual installation overview](/self-managed/deployment/manual/install.md) --- ## Operations and maintenance for RDBMS manual installations Operate and maintain RDBMS secondary storage for manual Camunda 8 installations (VM, bare metal, or standalone Java). ## Backup and restore **DBA owns backups** using native RDBMS tools (pg_dump, mysqldump, RMAN, and similar). Camunda handles consistency. - Use vendor-recommended backup procedures. - Backup frequency depends on your RPO (Recovery Point Objective). - The exporter writes to the database in batches, so a database backup can lag behind the latest Zeebe state. - The exporter stores its last exported position in the RDBMS and resumes from that position after a restore. **Restore procedure**: 1. Restore database from backup. 2. Start Camunda pointing to restored database. 3. Check logs for Liquibase completion and RdbmsExporter success. ## Schema upgrades 1. Backup your database. 2. Download scripts for target version: [access SQL and Liquibase scripts](/self-managed/deployment/helm/configure/database/access-sql-liquibase-scripts.md). 3. **If using autoDDL (default)**: Liquibase runs on Zeebe startup. 4. **If using manual schema management**: Apply scripts manually, then start new Camunda version. ## Disabling automatic schema updates For strict change control environments: ```bash export CAMUNDA_DATA_SECONDARYSTORAGE_RDBMS_AUTODDL=false ``` DBA must apply schema changes manually using scripts. Camunda does not validate the schema in this mode. If the schema is missing or out of date, the RdbmsExporter fails with SQL errors (see [Auto-DDL behavior](#auto-ddl-behavior)). ## Verification and troubleshooting ### Success indicators **Liquibase migration complete:** ``` [INFO] io.camunda.application.commons.rdbms.MyBatisConfiguration - Initializing Liquibase for RDBMS with global table trimmedPrefix ''. ``` No errors in Liquibase logs means migration was successful. **RdbmsExporter running:** ``` [INFO] io.camunda.exporter.rdbms.RdbmsExporter - [RDBMS Exporter] RdbmsExporter created with Configuration: flushInterval=PT0.5S, queueSize=1000 [INFO] io.camunda.exporter.rdbms.RdbmsExporter - [RDBMS Exporter] Exporter opened with last exported position 126 ``` No errors in RdbmsExporter logs means the exporter is healthy. For more details about exported records, enable DEBUG log level. ### Common failure modes | Symptom | Root cause | Fix | | ------------------------------------------------------------ | ------------------------------- | ----------------------------------------------------- | | `SQLNonTransientConnectionException: Socket fail to connect` | Invalid URL or unreachable host | Verify JDBC URL format and hostname/port resolution | | `SQLInvalidAuthorizationSpecException: Access denied` | Wrong credentials | Check username/password and database user permissions | | `Failed to load driver class oracle.jdbc.OracleDriver` | Missing JDBC driver | Verify driver JAR in `/driver-lib` or classpath | | `Table 'camunda.EXPORTER_POSITION' doesn't exist` | autoDDL=false on empty DB | Run schema initialization scripts manually | ### Auto-DDL behavior When started with `auto-ddl=false` on an empty database, the RdbmsExporter throws errors like: ``` ### Error querying database. Cause: java.sql.SQLSyntaxErrorException: (conn=3) Table 'camunda.EXPORTER_POSITION' doesn't exist ### The error may exist in URL [jar:file:.../camunda-db-rdbms-8.9.0-SNAPSHOT.jar!/mapper/ExporterPositionMapper.xml] ### The error occurred while setting parameters ### SQL: SELECT PARTITION_ID, EXPORTER, LAST_EXPORTED_POSITION, CREATED, LAST_UPDATED FROM EXPORTER_POSITION WHERE PARTITION_ID = ? ### Cause: java.sql.SQLSyntaxErrorException: (conn=3) Table 'camunda.EXPORTER_POSITION' doesn't exist ``` **Solution**: Run schema initialization scripts manually using the bundled SQL or Liquibase scripts before starting Camunda. ## Performance tuning ### Exporter batching ```bash # Lower flush interval = lower latency, more frequent writes export CAMUNDA_DATA_SECONDARYSTORAGE_RDBMS_FLUSHINTERVAL=PT0.1S # Higher queue size = higher throughput, more memory export CAMUNDA_DATA_SECONDARYSTORAGE_RDBMS_QUEUESIZE=5000 ``` ### Connection pooling Tune based on your workload. Camunda uses the Hikari connection pool following Spring Boot best practices: ```bash export CAMUNDA_DATA_SECONDARYSTORAGE_RDBMS_CONNECTIONPOOL_MAXIMUMPOOLSIZE=20 export CAMUNDA_DATA_SECONDARYSTORAGE_RDBMS_CONNECTIONPOOL_MINIMUMIDLE=10 export CAMUNDA_DATA_SECONDARYSTORAGE_RDBMS_CONNECTIONPOOL_IDLETIMEOUT=600000 export CAMUNDA_DATA_SECONDARYSTORAGE_RDBMS_CONNECTIONPOOL_MAXLIFETIME=1800000 export CAMUNDA_DATA_SECONDARYSTORAGE_RDBMS_CONNECTIONPOOL_CONNECTIONTIMEOUT=30000 ``` ### Database tuning Consult your database vendor's guides: - **PostgreSQL**: Index tuning, autovacuum settings - **Oracle**: SGA size, parallel execution - **MySQL/MariaDB**: Buffer pool sizing - **SQL Server**: Memory configuration - **AWS Aurora**: Consult AWS RDS documentation for Aurora-specific tuning ## Security ### TLS/SSL All RDBMS connections must use TLS in production: ```bash # PostgreSQL export CAMUNDA_DATA_SECONDARYSTORAGE_RDBMS_URL="jdbc:postgresql://localhost:5432/camunda?sslmode=require" ``` For other databases, see [RDBMS Helm configuration](/self-managed/deployment/helm/configure/database/rdbms.md). ### Secrets Never hardcode passwords. Use environment variable injection: ```bash export CAMUNDA_DATA_SECONDARYSTORAGE_RDBMS_PASSWORD=$(cat /run/secrets/db_password) ``` ### Database permissions Create user with minimum required privileges: ```sql -- PostgreSQL example CREATE ROLE camunda LOGIN PASSWORD 'secure-password'; GRANT CONNECT ON DATABASE camunda TO camunda; GRANT USAGE ON SCHEMA public TO camunda; GRANT CREATE ON SCHEMA public TO camunda; ``` Avoid DROP, TRUNCATE, SUPERUSER, or other unnecessary privileges. ## Production deployment Before deploying to production, review [secondary storage architecture](/self-managed/reference-architecture/reference-architecture.md#secondary-storage-architecture) for backend trade-offs and the [manual production architecture guide](/self-managed/deployment/manual/rdbms/rdbms-production-architecture.md) for recommended topology. Then use the verification steps in the [troubleshooting section](#verification-and-troubleshooting) above to validate your RDBMS setup is healthy. ## Next steps - [RDBMS configuration](/self-managed/deployment/manual/rdbms/configuration.md) - [Secondary storage architecture](/self-managed/reference-architecture/reference-architecture.md#secondary-storage-architecture) - [RDBMS production architecture](/self-managed/deployment/manual/rdbms/rdbms-production-architecture.md) - [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) - [access SQL and Liquibase scripts](/self-managed/deployment/helm/configure/database/access-sql-liquibase-scripts.md) --- ## Production architecture for Camunda 8 with RDBMS Apply RDBMS secondary storage to a **manual deployment**. For the canonical production architecture and trade-off guidance across manual, containerized, and Kubernetes deployments, see [secondary storage architecture](/self-managed/reference-architecture/reference-architecture.md#secondary-storage-architecture). ## Manual deployment baseline For manual production deployments with RDBMS, use these baseline decisions: - Run at least three brokers across three availability zones for high availability. - Use an external supported RDBMS for the Orchestration Cluster's secondary storage. - Keep the database in the same region as the Orchestration Cluster. - Use managed database services or vendor-supported high-availability mechanisms when possible. For backend selection trade-offs, Optimize requirements, and Elasticsearch/OpenSearch versus RDBMS comparison guidance, see [secondary storage architecture](/self-managed/reference-architecture/reference-architecture.md#secondary-storage-architecture). ## Example topology For production deployments with RDBMS, Camunda recommends a **HA Zeebe cluster backed by an external managed RDBMS**: ```mermaid graph TB subgraph oc["Orchestration Cluster (HA)"] direction TB gateway["Gateway(gRPC / HTTP)"] subgraph brokers["Brokers"] b1["Broker 1(AZ-1)"] b2["Broker 2(AZ-2)"] b3["Broker 3(AZ-3)"] end api["Orchestration Cluster API (v2)"] gateway --> brokers gateway --> api b1 -.->|gRPC| b2 b2 -.->|gRPC| b3 b1 -.->|gRPC| b3 end subgraph storage["Secondary Storage"] rdbms["External RDBMSPostgreSQL • MariaDBOracle • MySQL • SQL ServerHA: 3-way replicationacross AZs"] opt["Elasticsearch/OpenSearch(for Optimize only)"] end clients["ClientsWeb UIs • Workers • ApplicationsOperate • Tasklist • Identity • Connectors"] clients --> gateway brokers -->|Export events| rdbms brokers -->|Export analyticsOptional| opt style oc fill:#e1f5ff style storage fill:#fff3e0 style gateway fill:#f3e5f5 ``` ### Key characteristics #### Clustering - Minimum three brokers for production HA - Each broker in separate availability zone - Default replication factor 3 (spans AZs) #### Secondary storage - Single external managed RDBMS instance - Database handles replication and failover - Camunda does not manage database HA #### Data flow - Processes are executed - State is flushed to RDBMS - Orchestration Cluster applications (Operate, Tasklist, and Identity) and API clients access data through the Orchestration Cluster interfaces; they do not directly access secondary storage ## When document-store secondary storage is required Elasticsearch or OpenSearch is required **only for Optimize**. When Optimize is enabled: - Deploy Elasticsearch/OpenSearch alongside your RDBMS - Enable the Elasticsearch or OpenSearch exporter to store analytics data - The Orchestration Cluster uses RDBMS as secondary storage Without Optimize: RDBMS-only stack is fully supported. ## Production constraints ❌ **ES/OS ↔ RDBMS migration not supported**: Choose your secondary storage backend before production. No automated migration tools are available. ❌ **Uniform broker configuration required**: All brokers in the Orchestration Cluster must export to the same secondary storage backend for its Orchestration Cluster indices. In addition, you may run Elasticsearch/OpenSearch to support Optimize, but the Orchestration Cluster still uses a single backend for its own secondary storage. ❌ **v1 API not supported**: Only the v2 Orchestration Cluster REST API works with RDBMS. See [migrate to the Orchestration Cluster API](/apis-tools/migration-manuals/migrate-to-camunda-api.md). ## Network and security - **Orchestration Cluster ↔ RDBMS**: Private network connectivity with TLS in production - **Network isolation**: Restrict RDBMS access to the Orchestration Cluster hosts only (use host firewall rules or security groups) ## Supported scenarios ✅ **Single-node orchestration + external RDBMS** (non-HA, acceptable for non-critical workloads) ✅ **HA Zeebe cluster + external managed RDBMS** (recommended for production) ✅ **Managed database services** (AWS Aurora, Azure Database, GCP Cloud SQL) ## Next steps - [Secondary storage architecture](/self-managed/reference-architecture/reference-architecture.md#secondary-storage-architecture) - [RDBMS configuration](/self-managed/deployment/manual/rdbms/configuration.md) - [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) - [RDBMS Helm configuration](/self-managed/deployment/helm/configure/database/rdbms.md) --- ## Camunda back up and restore ## About this guide This guide covers how to back up and restore your Camunda 8 Self-Managed components and cluster. Automate backup and restore procedures with tools that meet your organization's requirements. The procedure is influenced depending on the selection of the cluster's secondary storage. :::tip Disaster recovery context If you are using backups as the foundation of a cross-region recovery strategy, see [Cold Recovery](../../concepts/multi-region/cold-recovery.md) for the architecture, RTO/RPO targets, and recovery flow. Cold Recovery builds on the procedures in this guide. ::: ### Elasticsearch / OpenSearch Covers Zeebe, Operate, Tasklist, and Optimize. Back up and restore with no downtime using coordinated Elasticsearch or OpenSearch snapshots. All components in this backup flow must use the same backup ID to ensure consistency. ### Relational databases (RDBMS) This is the **first phase of new backup capabilities** enabled by using an RDBMS as secondary storage. It covers Zeebe, Operate, Tasklist, and Admin. Management Identity and Optimize are not included. If you deploy Optimize alongside an RDBMS-backed Orchestration Cluster, back up Optimize independently using the [standalone Optimize backup procedure](./optimize-backup-and-restore.md). Using an RDBMS as secondary storage unlocks three new capabilities not available in the Elasticsearch / OpenSearch path: - **Decoupled backups**: Zeebe (primary storage) and the RDBMS (secondary storage) can be backed up independently, on their own schedules. During restore, Camunda automatically aligns the two backups — there is no need to coordinate a shared backup ID or take snapshots at the same time. - **Scheduled backups**: Because backups are decoupled, Zeebe can take backups automatically on a fixed schedule without requiring external backup API calls. - **Point-in-time restore**: Zeebe continuously takes snapshots of its log stream. This creates a range of available restore points that you can restore to by timestamp, rather than being limited to a specific backup ID. :::note - The examples in this guide are based on using the following tools: [curl](https://curl.se/), [jq](https://jqlang.org/), and [kubectl](https://kubernetes.io/docs/reference/kubectl/). ::: ## Considerations | | Elasticsearch / OpenSearch | Relational databases (RDBMS) | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | **Components covered** | Zeebe, Operate, Tasklist, Admin, Optimize | Zeebe, Operate, Tasklist, Admin | | **Optimize backup** | Included — must be coordinated with all other components using the same backup ID. A mismatched backup ID produces an inconsistent restore point. | Not included — [back up Optimize independently](./optimize-backup-and-restore.md) | | **Backup coordination** | All components must be backed up together using the same backup ID | Decoupled — Zeebe and RDBMS are backed up independently; Camunda aligns them automatically during restore | | **Continuous backups** | Not supported | Supported, fine-grained restore options available | | **Backup ID** | User-supplied integer | Auto-generated by cluster | :::info The RDBMS backup path covers Zeebe, Operate, Tasklist, and Admin. Management Identity and Optimize are not covered by this path. Optimize always stores its data in Elasticsearch or OpenSearch, regardless of the Orchestration Cluster's secondary storage. If you use Optimize alongside an RDBMS-backed Orchestration Cluster, back up and restore Optimize independently using the [standalone Optimize backup procedure](./optimize-backup-and-restore.md). You do not need to switch the Orchestration Cluster to the Elasticsearch / OpenSearch backup path. ::: :::warning Because Optimize and an RDBMS-backed Orchestration Cluster are backed up independently on separate schedules, they may be restored to **different points in time**. This means Optimize analytics data — dashboards, reports, and historical process data — may not be consistent with the restored state of the Orchestration Cluster. ::: ### Backup IDs When using Elasticsearch or OpenSearch as secondary storage, each component backup is identified by a user-supplied integer backup ID. The backup ID must be greater than the ID of any previous backup. Using Unix timestamps as backup IDs is a common practice to ensure uniqueness and chronological order. When using RDBMS as secondary storage, backup IDs are auto-generated by the cluster and do not need to be managed manually. The steps outlined on this page are generally applicable for any kind of deployment but might differ slightly depending on your setup. ### REST API With Camunda 8.10, the Orchestration Cluster (Zeebe and the web applications) exposes backup, exporting control, and restore as [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) endpoints under `/v2/...`. Prefer the REST API over the management (actuator) API for new deployments. It requires authentication and authorization like the rest of the Orchestration Cluster API, works the same way whether you run one Physical Tenant or several, and is the surface Camunda expects to keep building on. Each step of the [back up process](./elasticsearch/backup.md#back-up-process) links to the specific REST endpoint it uses. A runtime backup requires the `BACKUP:CREATE` permission; a history backup requires `BACKUP:CREATE` as well and is only available when secondary storage is Elasticsearch or OpenSearch. Pausing and resuming exporting each require their own `EXPORTER:PAUSE` permission, separate from `BACKUP:CREATE`. Pausing exporting before a backup is required for state consistency, though neither API enforces it as a precondition of the backup call itself: a soft pause (`?soft=true`) keeps exporting running but stops it from committing its position, which avoids log compaction removing data your backup still needs. See [back up process](./elasticsearch/backup.md#back-up-process) for how the two calls fit together in a procedure. This page describes the concepts shared by all deployments. For the full step-by-step procedure, see the [Elasticsearch / OpenSearch backup guide](./elasticsearch/backup.md), which uses these REST endpoints as the primary path and the management API below as a backward-compatible alternative. Optimize is not covered by these REST endpoints. Back it up with its own [management API](#management-api) procedure regardless of which path your Orchestration Cluster uses; see [Optimize backup and restore](./optimize-backup-and-restore.md). ### Management API The management API is an extension of the [Spring Boot Actuator](https://docs.spring.io/spring-boot/reference/actuator/index.html), typically used for monitoring and other operational purposes. This is not a public API and not exposed. You will need direct access to your Camunda cluster to be able to interact with these management APIs. This is why you'll often see the reference to `localhost`. For the Orchestration Cluster, the management API's backup and exporting-control endpoints are a backward-compatible alternative to the REST API above. Existing automation built against them continues to work; new automation should use the REST API instead. Deprecation of the management API's backup and exporting endpoints is not currently planned, but the REST API remains the recommended path for new automation. The management API's backup and exporting endpoints also support [Physical Tenants](#multiple-physical-tenants): they accept an optional `physicalTenant` query parameter, and without it they cover every Physical Tenant. | Management API call | REST API equivalent | | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | | `POST /actuator/exporting/pause?soft=true` | [`POST /exporting/pause?soft=true`](/apis-tools/orchestration-cluster-api-rest/specifications/pause-exporting.api.mdx) | | `POST /actuator/exporting/resume` | [`POST /exporting/resume`](/apis-tools/orchestration-cluster-api-rest/specifications/resume-exporting.api.mdx) | | [`POST /actuator/backupRuntime`](/apis-tools/management-api/specifications/backups/takes-a-backup-of-runtime-data.api.mdx) | [`POST /backups/runtime`](/apis-tools/orchestration-cluster-api-rest/specifications/take-runtime-backup.api.mdx) | | [`GET /actuator/backupRuntime/{backupId}`](/apis-tools/management-api/specifications/backups/get-information-of-a-runtime-backup.api.mdx) | [`GET /backups/runtime/{backupId}`](/apis-tools/orchestration-cluster-api-rest/specifications/get-runtime-backup.api.mdx) | | [`POST /actuator/backupHistory`](/apis-tools/management-api/specifications/backups/takes-a-backup-of-history-data.api.mdx) | [`POST /backups/history`](/apis-tools/orchestration-cluster-api-rest/specifications/take-history-backup.api.mdx) | | [`GET /actuator/backupHistory/{backupId}`](/apis-tools/management-api/specifications/backups/get-information-of-a-historic-backup.api.mdx) | [`GET /backups/history/{backupId}`](/apis-tools/orchestration-cluster-api-rest/specifications/get-history-backup.api.mdx) | The REST API adds list and delete operations that have no management API equivalent (see the table above), and requires the authentication and authorization already in place for the rest of the Orchestration Cluster API instead of network-level access to the management port. Direct access will depend on your deployment environment. For example, direct Kubernetes cluster access with [port-forwarding](https://kubernetes.io/docs/reference/kubectl/generated/kubectl_port-forward/) or [exec](https://kubernetes.io/docs/reference/kubectl/generated/kubectl_exec/) to execute commands directly on Kubernetes pods. In a manual deployment you will need to be able to reach the machines that host Camunda. Typically, the management port is on port `9600` but might differ on your setup and on the components. You can find the default for each component in their configuration page. | Component | Port | | ----------------------------------------------------------------------------------------------------------------------- | ---- | | [Optimize](/self-managed/components/optimize/configuration/system-configuration.md#container) | 8092 | | [Orchestration Cluster](/self-managed/components/orchestration-cluster/zeebe/configuration/gateway.md#managementserver) | 9600 | #### Examples for Kubernetes approaches Port-forwarding allows you to temporarily bind a remote Kubernetes cluster port of a service or pod directly to your local machine, allowing you to interact with it via `localhost:PORT`. Since the services are bound to your local machine, you **cannot reuse the same port for all port-forwards** unless you start and stop each one based on usage. To avoid this limitation, the examples use different local ports for each service, allowing them to run simultaneously without conflict. ```bash export CAMUNDA_RELEASE_NAME="camunda" # kubectl port-forward services/$SERVICE_NAME $LOCAL_PORT:$REMOTE_PORT kubectl port-forward services/$CAMUNDA_RELEASE_NAME-zeebe-gateway 9600:9600 & \ kubectl port-forward services/$CAMUNDA_RELEASE_NAME-optimize 8092:8092 & \ kubectl port-forward services/$CAMUNDA_RELEASE_NAME-elasticsearch 9200:9200 & ``` Using the bash instruction `&` at the end of each line would run the command in a subshell allowing the use of a single terminal. An alternative to port-forwarding is to run commands directly on Kubernetes pods. In this example we're going to spawn a temporary pod to execute a curl request. Alternatives are to use existing pods within the namespace. Camunda's pod includes different base images, each with a different feature set. ```bash # following will create a temporary alias within your terminal to overwrite the normal curl export CAMUNDA_NAMESPACE="camunda" export CAMUNDA_RELEASE_NAME="camunda" # temporary overwrite of curl, can be removed with `unalias curl` again alias curl="kubectl run curl --rm -i -n $CAMUNDA_NAMESPACE --restart=Never --image=alpine/curl -- -sS" curl $CAMUNDA_RELEASE_NAME-zeebe-gateway:9600/actuator/health curl $CAMUNDA_RELEASE_NAME-optimize:8092/actuator/health curl $CAMUNDA_RELEASE_NAME-elasticsearch:9200/_cluster/health ``` This allows you to directly execute commands within the namespace and communicate with available services. The examples in this guide showcase the backup process in a manual fashion to help you fully understand the process. You might want to use [Kubernetes Cronjobs](https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/) to automate the backup process for your own use case based on your own environment on a regular schedule. Kubernetes Cronjobs will spawn a [Job](https://kubernetes.io/docs/concepts/workloads/controllers/job/) on a regular basis. The job will run a defined image within a given namespace, allowing you to run commands and interact with the environment. :::note You can see further examples from Camunda consultants in the [Backup and Restore Workshop](https://github.com/camunda-consulting/c8-devops-workshop/tree/main/03%20-%20Lab%203%20-%20Backup%20and%20Restore). You can use these examples to achieve similar automation. ::: ### Multiple Physical Tenants Self-Managed only In a cluster running multiple [Physical Tenants](/self-managed/concepts/physical-tenants/index.md), backup and exporting control are available at two scopes. The backup procedure itself is unchanged; only the endpoint you call and the identity you call it with differ. There is no way to target a different tenant from a tenant-scoped call; the tenant is resolved from the caller's identity, not from the request path. To reach a specific tenant other than your own, or every tenant at once, use the cluster-wide endpoint with the optional `physicalTenantId` query parameter described below. | Scope | Endpoint | Authorization | Use it to | | ------------ | ------------------------------------------ | --------------------------------------------------- | ----------------------------------------------------------------- | | Tenant | Unprefixed, for example `/backups/runtime` | Tenant-local `BACKUP` and `EXPORTER` permissions | Back up or inspect the Physical Tenant your credentials belong to | | Cluster-wide | `/cluster/v2/…` | [Cluster admin](/components/admin/cluster-admin.md) | Back up every Physical Tenant in one call, or inspect them all | Both scopes serve the same operations: | Operation | Tenant-scoped (your own tenant) | Cluster-wide | | ----------------- | ------------------------------- | ----------------------------------- | | Runtime backup | `/backups/runtime` | `/cluster/v2/backups/runtime` | | Runtime state | `/backups/runtime/state` | `/cluster/v2/backups/runtime/state` | | History backup | `/backups/history` | `/cluster/v2/backups/history` | | Exporting control | `/exporting` | `/cluster/v2/exporting` | Each cluster-wide endpoint also accepts an optional `physicalTenantId` query parameter, which narrows the same cluster-admin call to one tenant. Omit it to target every Physical Tenant. A cluster-wide request fans out to each tenant and reports the outcome per tenant, so a partial result is visible rather than hidden. Because each tenant reaches its terminal state independently, a cluster-wide backup is a set of per-tenant backups rather than a single coordinated snapshot. #### Backup IDs across Physical Tenants A backup ID is unique within a Physical Tenant. Reusing an existing ID for the same tenant is rejected with `409`, while the same ID can be used by a different tenant, because each tenant has its own backup namespace. When a tenant has scheduled or continuous backups enabled, backup IDs are generated and an explicit ID is rejected. Because backup configuration is per tenant, tenants in the same cluster can be in different modes. A cluster-wide request with an explicit ID fails if any tenant generates its own IDs, and a request without an explicit ID fails if any tenant requires one. Use the tenant-scoped endpoints for mixed configurations. #### Storage backends and Physical Tenants - **Elasticsearch and OpenSearch**: history backup endpoints are available. Each Physical Tenant requires its own snapshot repository, so tenants never share a snapshot namespace. - **Relational databases (RDBMS)**: history backup endpoints are not served. Back up each tenant's schema, database, or table prefix with the database's own tooling. - **Document stores**: back up each tenant's bucket, container, or path with the storage system's own tooling. The Orchestration Cluster exposes no backup API for document stores. Configure non-overlapping backup locations before starting the cluster. Camunda validates the resolved location per tenant at startup and fails to start if two tenants resolve to the same one. For the isolation rules and configuration examples, see [storage isolation](/self-managed/concepts/physical-tenants/storage-isolation.md). ### ContextPath If you are defining the `contextPath` in the Camunda Helm chart or the `management.server.servlet.context-path` in a standalone setup, your API requests must prepend the value specific to the `contextPath` for the individual component. If the `management.server.port` is defined this also applies to `management.endpoints.web.base-path`. You can learn more about this behavior in the [Spring Boot documentation](https://docs.spring.io/spring-boot/docs/2.1.7.RELEASE/reference/html/production-ready-monitoring.html#production-ready-customizing-management-server-context-path). :::warning Optimize Helm chart Exception Setting the `contextPath` in the Helm chart for Optimize will not overwrite the `contextPath` of the management API, it will remain as `/`. :::
Example If you are defining the `contextPath` for the Orchestration Cluster in the Camunda Helm chart: ```bash orchestration: contextPath: /example ``` A call to the management API of the Orchestration Cluster would look like the following example: ```bash ORCHESTRATION_CLUSTER_MANAGEMENT_API=http://localhost:9600 curl $ORCHESTRATION_CLUSTER_MANAGEMENT_API/example/actuator/health ``` Without the `contextPath` it would just be: ```bash ORCHESTRATION_CLUSTER_MANAGEMENT_API=http://localhost:9600 curl $ORCHESTRATION_CLUSTER_MANAGEMENT_API/actuator/health ```
--- ## Camunda backup creation (Elasticsearch/OpenSearch) Back up your Camunda 8 Self-Managed components and cluster. ### Prerequisites The following prerequisites are required before you can create a backup. | Prerequisite | Description | | :------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Set up a snapshot repository in the secondary datastore. | Configure a snapshot repository on the datastore itself:[Elasticsearch snapshot repository](https://www.elastic.co/docs/deploy-manage/tools/snapshot-and-restore/manage-snapshot-repositories)[OpenSearch snapshot repository](https://docs.opensearch.org/docs/latest/tuning-your-cluster/availability-and-recovery/snapshots/snapshot-restore/)Note: For Elasticsearch configuration with the Camunda Helm chart on AWS EKS using IRSA, see [configuration example](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/irsa.md#elasticsearch-backup-with-irsa). | | Configure component backup storage. | Configure the backup storage for the following components. This is also important for restoring a backup.[Operate](/self-managed/components/orchestration-cluster/operate/operate-configuration.md#backups)[Optimize Elasticsearch](/self-managed/components/optimize/configuration/system-configuration.md#elasticsearch-backup-settings) / [Optimize OpenSearch](/self-managed/components/optimize/configuration/system-configuration.md#opensearch-backup-settings)[Tasklist](/self-managed/components/orchestration-cluster/tasklist/tasklist-configuration.md#backups) | | Configure Zeebe backup storage. | Configure the backup storage for Zeebe. This is required regardless of your secondary storage choice. See [Zeebe backup configuration](/self-managed/components/orchestration-cluster/zeebe/configuration/broker.md#camundadataprimary-storagebackup). | :::note You should keep the backup storage of the components configured at all times to ease the backup and restore process and avoid unnecessary restarts. ::: :::tip You can use the same backup storage location for both Elasticsearch / OpenSearch snapshots and Zeebe partition backups, as long as different paths are configured: - Set the `basePath` for Zeebe. - Set the `base_path` for Elasticsearch / OpenSearch. To learn more about how to configure these settings, refer to the prerequisites linked documentation above. ::: ## Considerations ### Why you should use backup and restore :::note The Camunda 8.8 release introduces breaking changes for [Operate and Tasklist](../webapps-backup.md). ::: :::note If the Camunda applications cannot access Elasticsearch with cluster-level privileges, run the backup of Operate and Tasklist indices (steps 2 and 4 from the [backup](#back-up-process) procedure) as a standalone application separate from the main application. For details, see the **standalone backup application** for [Elasticsearch](/self-managed/concepts/databases/elasticsearch/elasticsearch-without-cluster-privileges.md#standalone-backup-application) or [OpenSearch](/self-managed/concepts/databases/elasticsearch/opensearch-without-cluster-privileges.md#standalone-backup-application). ::: The Camunda 8 components like the Orchestration Cluster and Optimize store data in various formats and across multiple indices in Elasticsearch or OpenSearch. Because of this distributed and interdependent architecture, creating a consistent and reliable backup requires coordination between the components. For example, using Elasticsearch or OpenSearch's native snapshot capabilities directly does not produce a coherent backup. This is because Operate, Tasklist, and Optimize each manage their data across multiple indices, which cannot be reliably captured together without involvement from the components that understand their structure. For this reason, backups must be initiated through each component individually, using their built-in backup functionality. The same principle applies to Zeebe. Backups must be scheduled through Zeebe to ensure a consistent snapshot of all partition data. Simply taking a disk-level snapshot of each Zeebe Broker is not enough, as the brokers operate independently and data may not be aligned across them at the time of the snapshot. Since disk-level backups are not synchronized, this can lead to inconsistencies and invalid recovery points. A complete backup of a Camunda 8 cluster using Elasticsearch or OpenSearch includes: - Backups of Web Applications (Operate, Tasklist), and Optimize (triggered through their APIs). - Backup of indices from Elasticsearch/OpenSearch containing exported Zeebe records. - A Zeebe Broker partition backup (triggered through its API). Because the data across these systems is interdependent, all components must be backed up as part of the same backup window. Backups taken independently at different times may not align and could result in an unreliable restore point. :::warning To ensure a consistent backup, you must follow the process outlined in this guide. Deviating from it can result in undetected data loss, as there is no reliable method to verify cross-component data integrity after backup. ::: Following the documented procedure results in a hot backup, meaning that: - Zeebe continues to process and export data. - Web Applications (Operate, Tasklist), and Optimize remain fully operational during the backup process. This ensures high availability while preserving the integrity of the data snapshot. ### Additional considerations In this guide, `$BACKUP_ID` is the placeholder for a single backup ID, such as `1748937221`. When you create a full Camunda 8 backup using Elasticsearch or OpenSearch, use the same backup ID for each component backup in that backup set. For example, backup `1748937221` of Camunda 8 consists of the following component backups: - A Zeebe backup with backup ID `1748937221`, which contains one snapshot per partition. - An Optimize backup with backup ID `1748937221`, which can contain multiple Optimize snapshots. - A Web Applications backup with backup ID `1748937221`, which can contain multiple Operate and Tasklist snapshots. This means one `backupId` identifies the full Camunda 8 backup set, while each component can still create multiple underlying snapshots. The backup ID must be an integer and greater than any previous backup ID. Optimize is not part of the Web Applications backup API and needs to be executed separately to successfully make a backup. Depending on your deployment configuration, you may not have Optimize deployed. It is safe to ignore the backup instructions for Optimize if it is not deployed. :::warning breaking change As of Camunda 8.8, the `indexPrefix` of Operate and Tasklist must match. By default, it is set to `""`. If overridden, it must be set consistently across Operate and Tasklist. ::: :::warning breaking change As of Camunda 8.8, configuring Operate and Tasklist with different repository names will potentially create multiple backups in different repositories. ::: :::warning breaking changes As of Camunda 8.8, the `/actuator` endpoints for backups have been moved to `/actuator/backupHistory` (Web Applications) and `/actuator/backupRuntime` (Zeebe). The previous `/actuator/backups` endpoint is still active only if the applications are deployed standalone (each application is running in its own process). ::: ## About the backup process To create a backup, complete the following [backup process](#back-up-process). :::note Steps that changed with Camunda 8.10 show both APIs. Use the REST API by default. The management (actuator) API remains available as a backward-compatible alternative for existing automation. See [REST API](../backup-and-restore.md#rest-api) for the concepts shared across steps, including how each management API call maps to its REST equivalent. ::: You can also optionally [back up your Camunda Hub data](#back-up-hub-data). :::caution before you begin - To create a consistent backup, you **must** complete the steps in the order outlined below. - You must complete the [prerequisites](#prerequisites) before creating a backup. ::: ### Physical Tenants The REST steps below are scoped to whichever Physical Tenant your credentials belong to. The actuator calls apply cluster-wide instead, so following the actuator alternative as written backs up every Physical Tenant at once. To back up a specific tenant other than your own, or every tenant in one call with per-tenant outcome reporting, use the cluster-wide REST endpoints described in [back up a cluster with multiple Physical Tenants](../backup-and-restore.md#multiple-physical-tenants). There is no actuator equivalent for targeting a specific Physical Tenant. ## Back up process ### Example API endpoint definition :::note This depends heavily on your setup. The following examples are based on those given in the [REST API](../backup-and-restore.md#rest-api) and [Management API](../backup-and-restore.md#management-api) sections for Kubernetes using either active port-forwarding or an override of the local `curl` command. The REST API (`ORCHESTRATION_CLUSTER_API`) is the same authenticated Orchestration Cluster API you use for everything else; the management API (`ORCHESTRATION_CLUSTER_MANAGEMENT_API`) is typically not publicly exposed and needs direct access within your environment. Optimize has no REST equivalent, so `OPTIMIZE_MANAGEMENT_API` is always required. ::: ```bash # only export the BACKUP_ID once as it has to stay consistent throughout the backup procedure export BACKUP_ID=$(date +%s) # unix timestamp as unique always increasing ID export ELASTIC_SNAPSHOT_REPOSITORY="camunda" # the name of your snapshot repository export ELASTIC_ENDPOINT="http://localhost:9200" export OPENSEARCH_SNAPSHOT_REPOSITORY="camunda" # the name of your snapshot repository export OPENSEARCH_ENDPOINT="" # highly dependent on your environment export ORCHESTRATION_CLUSTER_API="http://localhost:8080/v2" export ORCHESTRATION_CLUSTER_MANAGEMENT_API="http://localhost:9600" export OPTIMIZE_MANAGEMENT_API="http://localhost:9620" ``` ```bash # only export the BACKUP_ID once as it has to stay consistent throughout the backup procedure export BACKUP_ID=$(date +%s) # unix timestamp as unique always increasing ID export CAMUNDA_RELEASE_NAME="camunda" export ELASTIC_SNAPSHOT_REPOSITORY="camunda" # the name of your snapshot repository export ELASTIC_ENDPOINT="$CAMUNDA_RELEASE_NAME-elasticsearch:9200" export OPENSEARCH_SNAPSHOT_REPOSITORY="camunda" # the name of your snapshot repository export OPENSEARCH_ENDPOINT="" # highly dependent on your environment export ORCHESTRATION_CLUSTER_API="http://$CAMUNDA_RELEASE_NAME-zeebe-gateway:8080/v2" export ORCHESTRATION_CLUSTER_MANAGEMENT_API="http://$CAMUNDA_RELEASE_NAME-zeebe-gateway:9600" export OPTIMIZE_MANAGEMENT_API="http://$CAMUNDA_RELEASE_NAME-optimize:8092" ``` ### 1. Soft pause exporting in Zeebe This will continue exporting records, but not delete those records (log compaction) from Zeebe. This makes the backup a hot backup, as covered in the [why you should use backup and restore](../backup-and-restore.md#why-you-should-use-backup-and-restore). Pausing exporting is required before a backup for state consistency, to avoid log compaction removing data the backup still needs; neither API enforces this as a precondition of the backup call itself. [Pause exporting](/apis-tools/orchestration-cluster-api-rest/specifications/pause-exporting.api.mdx). This requires the `EXPORTER:PAUSE` permission. ```bash curl -XPOST "$ORCHESTRATION_CLUSTER_API/exporting/pause?soft=true" ``` A `204` response indicates the pause was accepted. This step uses the [management API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md?exporting=softPause#exporting-api). ```bash curl -XPOST "$ORCHESTRATION_CLUSTER_MANAGEMENT_API/actuator/exporting/pause?soft=true" ``` :::warning This endpoint always returns HTTP `200`. Check the `status` field in the response body to determine whether the operation succeeded: `204` indicates success and `500` indicates failure. If the request fails, verify that all brokers are running and retry. :::
Example output ```json { "body":null, "status":204, "contentType":null } ```
#### Behavior during a Zeebe hot backup During a hot backup, the Zeebe cluster remains fully operational: - Zeebe continues to accept new client requests (for example, starting process instances) and to process existing workflow instances. - Job workers and other external workers continue to receive and complete jobs. - Exporters continue to export records. While soft pause is active, Zeebe temporarily does not advance the exporter position, which prevents log compaction and increases broker disk usage for the duration of the backup window. Ensure broker disks have enough free space. Keep the backup window as short as possible and resume exporting promptly once the backup completes. - If a broker restarts while soft pause is active, some already-exported records may be exported again after the restart. This is expected, because exporting always resumes from the last acknowledged exporter position. The same behavior applies after a restore: exporters start from the last persisted position and re-export all records processed during the soft-pause window. This is the intended mechanism that bridges the Zeebe backup and the secondary storage (Elasticsearch/OpenSearch) backup. The runtime backup step below then creates a consistent backup of each partition while processing continues. The “wait for backup to complete” steps in this guide only poll backup status and do not introduce any additional pause in processing beyond the initial soft export pause. ### 2. Start the web applications backup (Operate / Tasklist) This step initiates a coordinated snapshot on Elasticsearch or OpenSearch, where Operate and Tasklist historic data are present. It is available only when secondary storage is Elasticsearch or OpenSearch. [Take a history backup](/apis-tools/orchestration-cluster-api-rest/specifications/take-history-backup.api.mdx). This requires the `BACKUP:CREATE` permission. ```bash curl -XPOST "$ORCHESTRATION_CLUSTER_API/backups/history" \ -H "Content-Type: application/json" \ -d "{\"backupId\": $BACKUP_ID}" ```
Example output ```json { "backupId": 1748937221, "scheduledSnapshots":[ "camunda_webapps_1748937221_8.8.0_part_1_of_5", "camunda_webapps_1748937221_8.8.0_part_2_of_5", "camunda_webapps_1748937221_8.8.0_part_3_of_5", "camunda_webapps_1748937221_8.8.0_part_4_of_5", "camunda_webapps_1748937221_8.8.0_part_5_of_5" ] } ```
This step uses the [web applications management backup API](/self-managed/operational-guides/backup-restore/webapps-backup.md). ```bash curl -XPOST "$ORCHESTRATION_CLUSTER_MANAGEMENT_API/actuator/backupHistory" \ -H "Content-Type: application/json" \ -d "{\"backupId\": $BACKUP_ID}" ```
Example output ```json { "scheduledSnapshots":[ "camunda_webapps_1748937221_8.8.0_part_1_of_5", "camunda_webapps_1748937221_8.8.0_part_2_of_5", "camunda_webapps_1748937221_8.8.0_part_3_of_5", "camunda_webapps_1748937221_8.8.0_part_4_of_5", "camunda_webapps_1748937221_8.8.0_part_5_of_5" ] } ```
### 3. Start the Optimize backup Optimize is not covered by the Orchestration Cluster backup REST API. The [Optimize management backup API](/self-managed/operational-guides/backup-restore/optimize-backup.md) must be used, regardless of which API you use for the other steps. ```bash curl -XPOST "$OPTIMIZE_MANAGEMENT_API/actuator/backups" \ -H "Content-Type: application/json" \ -d "{\"backupId\": $BACKUP_ID}" ```
Example output ```json { "message":"Backup creation for ID 1748937221 has been scheduled. Use the GET API to monitor completion of backup process" } ```
### 4. Wait for the web applications backup to complete [Query the historic backup](/apis-tools/orchestration-cluster-api-rest/specifications/get-history-backup.api.mdx). This requires the `BACKUP:READ` permission. ```bash curl -s "$ORCHESTRATION_CLUSTER_API/backups/history/$BACKUP_ID" ``` Wait until `state` is `COMPLETED`: ```bash while [[ "$(curl -s "$ORCHESTRATION_CLUSTER_API/backups/history/$BACKUP_ID" | jq -r .state)" != "COMPLETED" ]]; do echo "Waiting..."; sleep 5; done; echo "Finished backup with ID $BACKUP_ID" ``` This step uses the [web applications management backup API](/self-managed/operational-guides/backup-restore/webapps-backup.md). ```bash curl -s "$ORCHESTRATION_CLUSTER_MANAGEMENT_API/actuator/backupHistory/$BACKUP_ID" ```
Example output ```json { "backupId":1748937221, "state":"COMPLETED", "failureReason":null, "details":[ { "snapshotName":"camunda_webapps_1748937221_8.8.0_part_1_of_5", "state":"SUCCESS", "startTime":"2025-06-03T07:55:15.685+0000", "failures":[ ] }, { "snapshotName":"camunda_webapps_1748937221_8.8.0_part_2_of_5", "state":"SUCCESS", "startTime":"2025-06-03T07:55:16.288+0000", "failures":[ ] }, { "snapshotName":"camunda_webapps_1748937221_8.8.0_part_3_of_5", "state":"SUCCESS", "startTime":"2025-06-03T07:55:17.092+0000", "failures":[ ] }, { "snapshotName":"camunda_webapps_1748937221_8.8.0_part_4_of_5", "state":"SUCCESS", "startTime":"2025-06-03T07:55:17.293+0000", "failures":[ ] }, { "snapshotName":"camunda_webapps_1748937221_8.8.0_part_5_of_5", "state":"SUCCESS", "startTime":"2025-06-03T07:55:18.298+0000", "failures":[ ] } ] } ```
Alternatively as a one-line to wait until the state is `COMPLETED` using a while loop and jq to parse the response JSON. ```bash while [[ "$(curl -s "$ORCHESTRATION_CLUSTER_MANAGEMENT_API/actuator/backupHistory/$BACKUP_ID" | jq -r .state)" != "COMPLETED" ]]; do echo "Waiting..."; sleep 5; done; echo "Finished backup with ID $BACKUP_ID" ```
### 5. Wait for the Optimize backup to complete This step uses the [Optimize management backup API](/self-managed/operational-guides/backup-restore/optimize-backup.md). ```bash curl -s "$OPTIMIZE_MANAGEMENT_API/actuator/backups/$BACKUP_ID" ```
Example output ```json { "backupId":1748937221, "failureReason":null, "state":"COMPLETED", "details":[ { "snapshotName":"camunda_optimize_1748937221_8.8.0_part_1_of_2", "state":"SUCCESS", "startTime":"2025-06-03T07:53:54.389+0000", "failures":[ ] }, { "snapshotName":"camunda_optimize_1748937221_8.8.0_part_2_of_2", "state":"SUCCESS", "startTime":"2025-06-03T07:53:54.389+0000", "failures":[ ] } ] } ```
Alternatively as a one-line to wait until the state is `COMPLETED` using a while loop and jq to parse the response JSON. ```bash while [[ "$(curl -s "$OPTIMIZE_MANAGEMENT_API/actuator/backups/$BACKUP_ID" | jq -r .state)" != "COMPLETED" ]]; do echo "Waiting..."; sleep 5; done; echo "Finished backup with ID $BACKUP_ID" ``` ### 6. Create a backup of the exported Zeebe indices in Elasticsearch/OpenSearch You can create this backup using the respective Snapshots API. By default, the old Elasticsearch or OpenSearch exporter creates indices with the prefix `zeebe-record`. If you configured a different prefix in the exporter, use that prefix instead. This remains relevant if you run Optimize, which still relies on the former exporters. The following uses the [Elasticsearch snapshot API](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-snapshot-create) to create a snapshot. ```bash curl -XPUT "$ELASTIC_ENDPOINT/_snapshot/$ELASTIC_SNAPSHOT_REPOSITORY/camunda_zeebe_records_backup_$BACKUP_ID?wait_for_completion=true" \ -H 'Content-Type: application/json' \ -d '{ "indices": "zeebe-record*", "feature_states": ["none"] }' ```
Example output ```json { "snapshot":{ "snapshot":"camunda_zeebe_records_backup_1748937221", "uuid":"1p_HdzKeTZ-zY-SN1LJ9VQ", "repository":"camunda", "version_id":8521000, "version":"8.18.0", "indices":[ "zeebe-record_process_8.8.0_2025-06-03", "zeebe-record_job_8.8.0_2025-06-03", "zeebe-record_process-instance_8.8.0_2025-06-03", "zeebe-record_deployment_8.8.0_2025-06-03" ], "data_streams":[ ], "include_global_state":true, "state":"SUCCESS", "start_time":"2025-06-03T08:05:10.633Z", "start_time_in_millis":1748937910633, "end_time":"2025-06-03T08:05:11.336Z", "end_time_in_millis":1748937911336, "duration_in_millis":603, "failures":[ ], "shards":{ "total":8, "failed":0, "successful":8 }, "feature_states":[ ] } } ```
The following uses the [OpenSearch snapshot API](https://docs.opensearch.org/docs/latest/api-reference/snapshots/create-snapshot/) to create a snapshot. ```bash curl -XPUT "$OPENSEARCH_ENDPOINT/_snapshot/$OPENSEARCH_SNAPSHOT_REPOSITORY/camunda_zeebe_records_backup_$BACKUP_ID?wait_for_completion=true" \ -H 'Content-Type: application/json' \ -d '{ "indices": "zeebe-record*" }' ```
Example output ```json { "snapshot":{ "snapshot":"camunda_zeebe_records_backup_1748937221", "uuid":"PUFbcSJZT1Cqc4jY8OE2uA", "version_id":136408027, "version":"2.19.2", "remote_store_index_shallow_copy":false, "indices":[ "zeebe-record_process_8.8.0_2025-06-03", "zeebe-record_job_8.8.0_2025-06-03", "zeebe-record_process-instance_8.8.0_2025-06-03", "zeebe-record_deployment_8.8.0_2025-06-03" ], "data_streams":[ ], "include_global_state":true, "state":"SUCCESS", "start_time":"2025-06-03T09:37:45.623Z", "start_time_in_millis":1748943465623, "end_time":"2025-06-03T09:37:46.342Z", "end_time_in_millis":1748943466342, "duration_in_millis":719, "failures":[ ], "shards":{ "total":8, "failed":0, "successful":8 } } } ```
### 7. Wait for the backup of the exported Zeebe indices to complete before proceeding Using `?wait_for_completion=true` in the previous call, as outlined, ensures that the request only returns once the backup has completed. However, to double-check that the backup completed successfully, you can perform the following verification: The following uses the [Elasticsearch snapshot API](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-snapshot-status-2) to get the snapshot status. ```bash curl "$ELASTIC_ENDPOINT/_snapshot/$ELASTIC_SNAPSHOT_REPOSITORY/camunda_zeebe_records_backup_$BACKUP_ID/_status" ```
Example output ``` { "snapshots":[ { "snapshot":"camunda_zeebe_records_backup_1748937221", "repository":"camunda", "uuid":"1p_HdzKeTZ-zY-SN1LJ9VQ", "state":"SUCCESS", "include_global_state":true, "shards_stats":{ "initializing":0, "started":0, "finalizing":0, "done":8, "failed":0, "total":8 }, "stats":{ "incremental":{ "file_count":0, "size_in_bytes":0 }, "total":{ "file_count":8, "size_in_bytes":0 }, "start_time_in_millis":1748937910633, "time_in_millis":0 }, "indices":{ "zeebe-record_process_8.8.0_2025-06-03", "zeebe-record_job_8.8.0_2025-06-03", "zeebe-record_process-instance_8.8.0_2025-06-03", "zeebe-record_deployment_8.8.0_2025-06-03" } } ] } ```
Using `?wait_for_completion=true` in the previous call, as outlined, ensures that the request only returns once the backup has finished. However, to double-check that the backup completed successfully, you can perform the following verification: The following uses the [OpenSearch snapshot API](https://docs.opensearch.org/docs/latest/api-reference/snapshots/get-snapshot-status/) to get the snapshot status. ```bash curl "$OPENSEARCH_ENDPOINT/_snapshot/$OPENSEARCH_SNAPSHOT_REPOSITORY/camunda_zeebe_records_backup_$BACKUP_ID/_status" ```
Example output ```json { "snapshots":[ { "snapshot":"camunda_zeebe_records_backup_1748937221", "repository":"camunda", "uuid":"PUFbcSJZT1Cqc4jY8OE2uA", "state":"SUCCESS", "include_global_state":true, "shards_stats":{ "initializing":0, "started":0, "finalizing":0, "done":8, "failed":0, "total":8 }, "stats":{ "incremental":{ "file_count":0, "size_in_bytes":0 }, "total":{ "file_count":8, "size_in_bytes":0 }, "start_time_in_millis":1748943465623, "time_in_millis":0 }, "indices":{ "zeebe-record_process_8.8.0_2025-06-03", "zeebe-record_job_8.8.0_2025-06-03", "zeebe-record_process-instance_8.8.0_2025-06-03", "zeebe-record_deployment_8.8.0_2025-06-03" } } ] } ```
### 8. Create the Zeebe broker backup This step creates a runtime backup, covering Zeebe's own primary storage alone. [Take a runtime backup](/apis-tools/orchestration-cluster-api-rest/specifications/take-runtime-backup.api.mdx). This requires the `BACKUP:CREATE` permission. ```bash curl -XPOST "$ORCHESTRATION_CLUSTER_API/backups/runtime" \ -H "Content-Type: application/json" \ -d "{\"backupId\": $BACKUP_ID}" ```
Example output ```json { "backupId": 1748937221 } ```
This step uses the [Zeebe management backup API](/self-managed/operational-guides/backup-restore/zeebe-backup-and-restore.md). ```bash curl -XPOST "$ORCHESTRATION_CLUSTER_MANAGEMENT_API/actuator/backupRuntime" \ -H "Content-Type: application/json" \ -d "{\"backupId\": $BACKUP_ID}" ```
Example output ```json { "message":"A backup with id 1748937221 has been scheduled. Use GET actuator/backupRuntime/1748937221 to monitor the status." } ```
### 9. Wait for the Zeebe backup to complete before proceeding [Query the runtime backup](/apis-tools/orchestration-cluster-api-rest/specifications/get-runtime-backup.api.mdx). This requires the `BACKUP:READ` permission. ```bash curl "$ORCHESTRATION_CLUSTER_API/backups/runtime/$BACKUP_ID" ``` Wait until `state` is `COMPLETED`: ```bash while [[ "$(curl -s "$ORCHESTRATION_CLUSTER_API/backups/runtime/$BACKUP_ID" | jq -r .state)" != "COMPLETED" ]]; do echo "Waiting..."; sleep 5; done; echo "Finished backup with ID $BACKUP_ID" ``` This step uses the [Zeebe management backup API](/self-managed/operational-guides/backup-restore/zeebe-backup-and-restore.md). ```bash curl "$ORCHESTRATION_CLUSTER_MANAGEMENT_API/actuator/backupRuntime/$BACKUP_ID" ```
Example output ```json { "backupId":1748937221, "state":"COMPLETED", "details":[ { "partitionId":1, "state":"COMPLETED", "createdAt":"2025-06-03T08:06:06.246997293Z", "lastUpdatedAt":"2025-06-03T08:06:10.408893628Z", "checkpointPosition":1, "brokerVersion":"8.8.0" } ] } ```
Alternatively as a one-line to wait until the state is `COMPLETED` using a while loop and jq to parse the response JSON. ```bash while [[ "$(curl -s "$ORCHESTRATION_CLUSTER_MANAGEMENT_API/actuator/backupRuntime/$BACKUP_ID" | jq -r .state)" != "COMPLETED" ]]; do echo "Waiting..."; sleep 5; done; echo "Finished backup with ID $BACKUP_ID" ```
### 10. Resume exporting in Zeebe [Resume exporting](/apis-tools/orchestration-cluster-api-rest/specifications/resume-exporting.api.mdx). This requires the `EXPORTER:PAUSE` permission. ```bash curl -XPOST "$ORCHESTRATION_CLUSTER_API/exporting/resume" ``` A `204` response indicates the resume was accepted. This step uses the [management API](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md). ```bash curl -XPOST "$ORCHESTRATION_CLUSTER_MANAGEMENT_API/actuator/exporting/resume" ``` :::warning This endpoint always returns HTTP `200`. Check the `status` field in the response body to determine whether the operation succeeded: `204` indicates success and `500` indicates failure. If the request fails, verify that all brokers are running and retry. :::
Example output ```json { "body":null, "status":204, "contentType":null } ```
:::warning If any of the steps above fail, you might have to restart with a new backup ID. Ensure Zeebe exporting is resumed if the backup process force quits in the middle of the process. ::: ## (Optional) Back up Camunda Hub data {#back-up-hub-data} To create a Camunda Hub data backup, refer to the [official PostgreSQL documentation](https://www.postgresql.org/docs/current/backup-dump.html) to back up the database that Camunda Hub uses. For example, to create a backup of the database using `pg_dumpall`, use the following command: ```bash pg_dumpall -U -h -p -f dump.psql --quote-all-identifiers Password: ``` `pg_dumpall` might ask multiple times for the same password. The database will be dumped into `dump.psql`. :::note Database dumps created with `pg_dumpall`/`pg_dump` can only be restored into a database with the same or later version of PostgreSQL, see [PostgreSQL documentation](https://www.postgresql.org/docs/current/app-pgdump.html#PG-DUMP-NOTES). ::: :::info You can [restore a Camunda Hub data backup](../modeler-backup-and-restore.md#restore). ::: ## Cleaning up backups Depending on your company’s backup policies (for example, retention periods and number of backups to keep) you should consider regularly cleaning up your old backups to reduce storage costs and efficiently manage resources. You can use the **delete backup APIs** for each component to remove the associated resources from the configured backup storage. You will have to provide the same backup ID for all calls to remove it from all backup stores. - Web Applications (history backup): [`DELETE /backups/history/{backupId}`](/apis-tools/orchestration-cluster-api-rest/specifications/delete-history-backup.api.mdx) (REST API), or the [management API](/self-managed/operational-guides/backup-restore/webapps-backup.md#delete-backup-api) - Zeebe (runtime backup): [`DELETE /backups/runtime/{backupId}`](/apis-tools/orchestration-cluster-api-rest/specifications/delete-runtime-backup.api.mdx) (REST API), or the [management API](/self-managed/operational-guides/backup-restore/zeebe-backup-and-restore.md#delete-backup-api) - [Optimize](/self-managed/operational-guides/backup-restore/optimize-backup.md#delete-backup-api) (management API only, no REST equivalent) The REST API also lists backups: [`GET /backups/history`](/apis-tools/orchestration-cluster-api-rest/specifications/list-history-backups.api.mdx) and [`GET /backups/runtime`](/apis-tools/orchestration-cluster-api-rest/specifications/list-runtime-backups.api.mdx), each with an optional `prefix` filter. For Zeebe, you would also have to remove the separately backed up `zeebe-record` index snapshot using the [Elasticsearch](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-snapshot-delete) / [OpenSearch](https://docs.opensearch.org/docs/latest/api-reference/snapshots/delete-snapshot/) API directly. ### Primary storage retention :::note This only affects the primary storage and does not interfere with Elasticsearch/OpenSearch backups. ::: With Camunda 8.9 you now have the option to enable a retention mechanism over primary storage (Zeebe's) backups. This will periodically delete backups from the configured blob storage based on the preconfigured retention window. Learn more about configuring backup retention [here](../../../../components/orchestration-cluster/core-settings/configuration/properties/#camundadataprimary-storagebackupretention). --- ## Restore a backup with the Restore API Restore Zeebe partition data through the Orchestration Cluster Restore API without restarting the brokers, when using Elasticsearch or OpenSearch as secondary storage. This page is part of the Elasticsearch/OpenSearch [restore procedure](./restore.md). To compare it with the legacy Restore Application, see [choosing a restore approach](./restore.md#choosing-a-restore-approach). ## About the Restore API With Camunda 8.10 and later, a Restore API recovery runs during a downtime window while the cluster is in recovery mode. It runs in four phases, driven by two API requests: 1. **Entering recovery mode**: every broker deactivates its partitions and switches to a restricted partition manager. While the cluster is in recovery mode it processes no work, and only read-only operations and restore remain available. 2. **Restoring secondary storage**: while the cluster is in recovery mode, restore the Elasticsearch/OpenSearch snapshots for the intended backup ID. 3. **Restoring the partitions**: the cluster plans a single change that, for every broker and partition, first drops the local partition data and then restores that partition from the selected backup. The steps of that plan run one at a time across the cluster. 4. **Returning to processing**: once every partition is restored, the same change switches all brokers back to `PROCESSING` and the partitions become active again. Both requests are non-blocking. Each is acknowledged as soon as the cluster accepts the change and returns the `changeId` of the cluster configuration change that carries it out. ## Prerequisites In addition to the [general restore prerequisites](./restore.md#prerequisites), the Restore API requires the following: | Prerequisite | Description | | :--------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Camunda version | Camunda 8.10 or later, restored with the exact version the backup was created with. | | Backup store | Every broker is configured with the same backup store that holds the Zeebe backup, and Elasticsearch/OpenSearch is configured with the same snapshot repository as the backup. See [prerequisites](./backup.md#prerequisites). | | Sizing | Elasticsearch/OpenSearch should be sized the same or larger than the original cluster; a smaller cluster can prevent shards from being assigned and fail the restore. | | Optimize stopped | Optimize must be stopped before you restore the Elasticsearch/OpenSearch snapshots in [step 3](#restore-es-snapshots-step); every other component keeps running in recovery mode. | | Partition count | The partition count of the cluster matches the partition count of the backup. Brokers can be scaled between backup and restore as long as the partition count is unchanged. See [restoring a backup with fewer partitions](#restoring-a-backup-with-fewer-partitions). | | API access | Authenticated access to the Orchestration Cluster REST API. See [authentication](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md). | | Authorizations | If [authorizations](/components/concepts/access-control/authorizations.md) are enabled, the caller needs the `RESTORE` permission on the `BACKUP` resource. | ## Restoring an Elasticsearch/OpenSearch-backed cluster ### 1. Switch the cluster into recovery mode [Change the cluster mode](/apis-tools/orchestration-cluster-api-rest/specifications/change-cluster-mode.api.mdx) to `RECOVERING`: ```bash curl -X PATCH "${ORCHESTRATION_CLUSTER_API}/mode?mode=RECOVERING" ``` Wait until the mode change has completed. Query the [cluster monitoring API](/self-managed/components/orchestration-cluster/zeebe/operations/cluster-scaling.md#monitoring-api) and check that `lastChange.id` matches the returned `changeId` and that no `pendingChange` is reported: ```bash curl "${ORCHESTRATION_CLUSTER_MANAGEMENT_API}/actuator/cluster" ``` A Restore API request is only accepted while every broker of the cluster is in recovery mode. Requests sent earlier are rejected with `409`. ### 2. Find available backup IDs {#find-available-backup-ids} With the cluster in recovery mode, use the Orchestration Cluster REST API to list the available runtime and history backups for the current Physical Tenant. Both endpoints require the `BACKUP:READ` permission. Use the returned backup ID to select the matching Elasticsearch/OpenSearch snapshots and Zeebe primary storage backup. Use [list runtime backups](/apis-tools/orchestration-cluster-api-rest/specifications/list-runtime-backups.api.mdx) to list available Zeebe primary storage backups. Omit `prefix` to list all backups, or use a numeric prefix followed by `*` to narrow the results. ```bash curl "${ORCHESTRATION_CLUSTER_API}/backups/runtime" ``` To list backups matching a prefix: ```bash curl "${ORCHESTRATION_CLUSTER_API}/backups/runtime?prefix=1748937*" ``` Use [list history backups](/apis-tools/orchestration-cluster-api-rest/specifications/list-history-backups.api.mdx) to list available Operate, Tasklist, and Optimize history backups. This endpoint is available because Elasticsearch/OpenSearch is the secondary storage. Use `verbose=false` when snapshot-level details are not needed. ```bash curl "${ORCHESTRATION_CLUSTER_API}/backups/history" ``` To list backups matching a prefix without snapshot-level details: ```bash curl "${ORCHESTRATION_CLUSTER_API}/backups/history?prefix=1748937*&verbose=false" ``` The runtime and history listings are scoped to the Physical Tenant associated with the caller's credentials. For other tenants or all tenants, use the cluster-admin endpoints described in [restoring a cluster with multiple Physical Tenants](#restoring-a-cluster-with-multiple-physical-tenants). Ensure that the runtime and history backups you select use the same backup ID before continuing. ### 3. Restore Elasticsearch/OpenSearch snapshots {#restore-es-snapshots-step} With the cluster in recovery mode, restore the Elasticsearch/OpenSearch snapshots to the intended point in time, using the same backup ID that you pass to the Restore API. A mismatched backup ID produces an inconsistent restore point. Keep the Orchestration Cluster running in recovery mode while you restore the snapshots. #### 1. Restore templates The Orchestration Cluster is already running in recovery mode, so you don't need to start Camunda 8 for this step. Skip the start instructions below and confirm that the index templates exist. #### 2. Stop Optimize #### 3. Delete all indices #### 4. Restore the snapshots Complete this step before you trigger the Zeebe restore. The Restore API switches the brokers back to `PROCESSING` as soon as the last partition is restored, and processing then resumes against whatever secondary storage is in place. ### 4. Trigger the restore {#trigger-the-restore} [Provide the restore parameters](/apis-tools/orchestration-cluster-api-rest/specifications/restore.api.mdx). Camunda validates the request, resolves the backups for every partition, and acknowledges the request with `202` before the restore itself runs: ```bash curl -X POST "${ORCHESTRATION_CLUSTER_API}/restore" \ -H 'Content-Type: application/json' \ -d '{ "backupIds": [1748937221] }' ``` The response returns the `changeId` of the restore, along with the planned operations. The plan drops and restores every partition of every broker, switches all brokers back to `PROCESSING`, and ends with an incarnation number update:
Example response ```json { "changeId": "8", "plannedChanges": [ { "physicalTenantId": "default", "operations": [ { "operation": "SchemaInitializationOperation", "brokerId": "0" }, { "operation": "PartitionPreRestoreOperation", "brokerId": "0", "partitionId": 1 }, { "operation": "PartitionRestoreOperation", "brokerId": "0", "partitionId": 1, "backupIds": [1748937221] }, { "operation": "ModeChangeOperation", "brokerId": "0", "mode": "PROCESSING" }, { "operation": "AwaitModeChangeOperation", "brokerId": "0", "mode": "PROCESSING" }, { "operation": "UpdateIncarnationNumberOperation", "brokerId": "0" } ] } ] } ```
### 5. Track the Restore API operation ### 6. Confirm the cluster state after Restore API recovery Check that every partition is active and healthy again using [the topology](/apis-tools/orchestration-cluster-api-rest/specifications/get-topology.api.mdx): ```bash curl "${ORCHESTRATION_CLUSTER_API}/topology" ``` The cluster leaves recovery mode as part of the restore, so the Orchestration Cluster needs no further action. ### 7. Start Optimize again If you stopped Optimize in [step 3](#restore-es-snapshots-step), start it again now that the cluster is processing. If you are using the Camunda Helm chart, set `optimize.enabled` back to `true` in your `values.yml` and apply the change. For a manual deployment, start Optimize the way you normally do. ## Restoring a cluster with multiple Physical Tenants Self-Managed only For multiple [Physical Tenants](/self-managed/concepts/physical-tenants/index.md), tenant-scoped Restore API calls target the Physical Tenant associated with the caller's credentials. To restore another tenant or all tenants, use the cluster-wide endpoints with [cluster admin](/components/admin/cluster-admin.md) access. | Step | Tenant-scoped (your own tenant) | Cluster-wide (cluster admin) | | :------------ | :-------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------- | | Recovery mode | [`PATCH /v2/mode`](/apis-tools/orchestration-cluster-api-rest/specifications/change-cluster-mode.api.mdx) | [`PATCH /cluster/v2/mode`](/apis-tools/orchestration-cluster-api-rest/specifications/change-cluster-mode-as-cluster-admin.api.mdx) | | Trigger | [`POST /v2/restore`](/apis-tools/orchestration-cluster-api-rest/specifications/restore.api.mdx) | [`POST /cluster/v2/restore`](/apis-tools/orchestration-cluster-api-rest/specifications/restore-as-cluster-admin.api.mdx) | | Track | [`GET /v2/restore`](/apis-tools/orchestration-cluster-api-rest/specifications/get-restore-status.api.mdx) | No cluster-wide status endpoint exists. Check each tenant's own restore status, or confirm recovery through cluster-wide topology below. | | Confirm | [`GET /v2/topology`](/apis-tools/orchestration-cluster-api-rest/specifications/get-topology.api.mdx) | [`GET /cluster/v2/topology`](/apis-tools/orchestration-cluster-api-rest/specifications/get-cluster-topology.api.mdx) | Use a tenant-scoped restore when one Physical Tenant has corrupted or missing data and the other tenants should keep processing. Use a cluster-wide restore when several tenants need recovery, or when the whole cluster must be returned to a coordinated state. The cluster-wide endpoints accept an optional `physicalTenantId` query parameter. Naming a tenant restores only that tenant; omitting the parameter restores every configured tenant. Each tenant must have its own non-overlapping backup location, and the same backup ID must refer to compatible snapshots for every tenant included in the restore. ```bash export CLUSTER_ADMIN_API=http://localhost:8080/cluster/v2 curl -X POST "${CLUSTER_ADMIN_API}/restore" \ -H 'Content-Type: application/json' \ -d '{ "backupIds": [1748937221] }' ``` Before returning a restored tenant to normal traffic, confirm through tenant-scoped topology that its partitions are healthy, that the expected data is present, and that exporting has resumed. ## Validating a Restore API request without applying it The Restore API accepts the `dryRun` query parameter. With `dryRun=true`, the request is validated and the resulting plan is returned, but nothing is applied to the cluster. Use this to check a backup selection before the downtime window starts: ```bash curl -X POST "${ORCHESTRATION_CLUSTER_API}/restore?dryRun=true" \ -H 'Content-Type: application/json' \ -d '{ "backupIds": [1748937221] }' ``` A dry run rejects requests without a backup ID, with multiple backup IDs, or with a time range. It also checks that a completed backup exists for every partition. A request that passes the dry run is accepted as a real request as long as the cluster and the backup store do not change in between. ## Handling a failed Restore API operation ### Restoring a backup with fewer partitions --- ## Restore a backup with the Restore Application Restore Zeebe partition data with the legacy Restore Application, a standalone app that runs on each broker node while all Camunda components are stopped, when using Elasticsearch or OpenSearch as secondary storage. This page is part of the Elasticsearch/OpenSearch [restore procedure](./restore.md). With Camunda 8.10 and later, you can use the [Restore API](./restore-api.md) instead, which does not require restarting the brokers. ## Prerequisites In addition to the [general restore prerequisites](./restore.md#prerequisites), the Restore Application requires the following: | Prerequisite | Description | | :----------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Backup storage | Zeebe and Elasticsearch/OpenSearch are configured with the same backup storage and snapshot repository used to create the backup. See [prerequisites](./backup.md#prerequisites). | | Sizing | Elasticsearch/OpenSearch should be sized the same or larger than the original cluster; a smaller cluster can prevent shards from being assigned and fail the restore. | | Components stopped | No Camunda component may be running during the restore. A running component can propagate an incorrect cluster configuration and disrupt cluster communication. | ## 1. Restore Elasticsearch/OpenSearch snapshots {#restore-es-snapshots-step} Restore the Elasticsearch/OpenSearch snapshots in five steps, then continue with restoring the Zeebe cluster below. ### 1. Restore templates ### 2. Find available backup IDs ### 3. Stop all components apart from Elasticsearch/OpenSearch {#stop-all-components} ### 4. Delete all indices ### 5. Restore the snapshots ## 2. Restore the Zeebe cluster {#restore-zeebe-cluster} Restore the Zeebe partitions by running the Restore Application on every node where a Zeebe Broker will run. :::note During the restoration of the Elasticsearch / OpenSearch state, we had to temporarily deploy Zeebe. This will have resulted in persistent volumes on Kubernetes and a filled data directory on each Zeebe Broker in case of a manual deployment. In the case of Kubernetes to remove all related persistent volumes. ```bash kubectl get pvc -o custom-columns=NAME:.metadata.name --no-headers \ | grep zeebe \ | while read pvc; do kubectl delete pvc "$pvc" done ``` New persistent volumes will be created on a new Camunda Helm chart upgrade and install. In case of a manual deployment, this means to remove the data directory of each Zeebe Broker. ::: Camunda provides a standalone app which must be run on each node where a Zeebe Broker will be running. This is a Spring Boot application similar to the broker and can run using the binary provided as part of the distribution. The app can be configured the same way a broker is configured - via environment variables or using the configuration file located in `config/application.yaml`. :::warning When restoring, provide the same configuration (node id, data directory, cluster size, and replication count) as the broker that will be running in this node. The partition count **must be same** as in the backup. The amount of partitions backed up are also visible in the backup store of Zeebe; see the available backups of Zeebe partitions you found when [finding available backup IDs](#find-available-backup-ids) above. If brokers were dynamically scaled between backup and restore, this is not an issue - as long as the partition count remains unchanged. ::: Assuming you're using the official [Camunda Helm chart](/self-managed/deployment/helm/install/quick-install.md), you'll have to adjust your Helm `values.yml` to supply the following temporarily. It will overwrite the start command of the resulting Zeebe pod, executing a restore script. It's important that the backup is configured for Zeebe to be able to restore from the backup! ```yaml orchestration: enabled: true env: # Environment variables to overwrite the Zeebe startup behavior - name: SPRING_PROFILES_ACTIVE value: "restore" - name: ZEEBE_RESTORE value: "true" - name: ZEEBE_RESTORE_FROM_BACKUP_ID value: "$BACKUP_ID" # Change the $BACKUP_ID to your actual value # all the envs related to the backup store as outlined in the prerequisites - name: CAMUNDA_DATA_BACKUP_STORE value: "S3" # just as an example - name: CAMUNDA_DATA_BACKUP_REPOSITORYNAME value: camunda # Change to name of the repository in Elasticsearch/OpenSearch ... connectors: enabled: false optimize: enabled: false ``` :::note Alternative command overwrite Use this alternative approach to restore Zeebe partitions: ```yaml orchestration: enabled: true command: - "/usr/local/camunda/bin/restore" - "--backupId=$BACKUP_ID" # Change the $BACKUP_ID to your actual value. env: - name: SPRING_PROFILES_ACTIVE value: "restore" # All the envs related to the backup store as outlined in the prerequisites ... ``` If you're not using the Camunda Helm chart, you can use a similar approach natively with Kubernetes to overwrite the command. ::: To restore a Zeebe Cluster, run the following in each node where the broker will be running: ```bash mkdir -p camunda tar -xzf camunda-zeebe-X.Y.Z.tar.gz --strip-components=1 -C camunda/ ./camunda/bin/restore --backupId= ``` ### Restoring a cluster with multiple Physical Tenants ```yaml orchestration: env: - name: SPRING_PROFILES_ACTIVE value: "restore" - name: ZEEBE_RESTORE value: "true" - name: ZEEBE_RESTORE_ALL_TENANTS value: "true" - name: ZEEBE_RESTORE_FROM_BACKUP_ID value: "27" extraConfiguration: - file: restore-overrides.yaml content: | override: tenanta: backupId: [31] tenantb: backupId: [32] ``` ```bash ./camunda/bin/restore \ --allTenants \ --backupId=1748937221 \ --override.tenanta.backupId=31 \ --override.tenantb.backupId=32 ``` ### Restore success or failure If restore was successful, the app exits with the log message `Successfully restored broker from backup`. However, the restore will fail if: - There is no valid backup with the given backupId. - The backup store is not configured correctly. - The configured data directory is not empty. - Due to any other unexpected errors. If the restore fails, you can re-run the application after fixing the root cause. #### Data directory is not empty ## 3. Start all Camunda 8 components {#start-all-camunda-8-components} Now that you have actively restored Elasticsearch/OpenSearch and the Zeebe cluster partitions, you can start all components again and use Camunda 8 as normal. For example: - For Kubernetes, apply Helm values for normal startup and explicitly set `ZEEBE_RESTORE=false`. Also, keep your backup-store environment variables configured as outlined in the prerequisites. ```yaml orchestration: enabled: true env: - name: ZEEBE_RESTORE value: "false" # all the envs related to the backup store as outlined in the prerequisites - name: CAMUNDA_DATA_BACKUP_STORE value: "S3" # just as an example - name: CAMUNDA_DATA_BACKUP_REPOSITORYNAME value: camunda # Change to name of the repository in Elasticsearch/OpenSearch ``` Ensure restore-only settings are not present in this final configuration (for example, `SPRING_PROFILES_ACTIVE=restore`, `ZEEBE_RESTORE_FROM_BACKUP_ID`, or a temporary restore command override). - For a manual setup, execute the broker and all other components in their normal way. --- ## Restore a backup Restore a previous backup of your Camunda 8 Self-Managed components and cluster when using Elasticsearch or OpenSearch as secondary storage. ## Choosing a restore approach Restore the Zeebe partitions with one of two approaches. Both include restoring the Elasticsearch/OpenSearch snapshots as one of their steps. ## About restoring a backup A restore consists of two parts: restoring the Elasticsearch/OpenSearch snapshots, and restoring the Zeebe partitions from the Zeebe primary storage backup. :::note When restoring Camunda 8 from a backup, all components must be restored from their backup that corresponds to the same backup ID. ::: ## Prerequisites The following general prerequisites are required before you can restore a backup, regardless of the restore approach: | Prerequisite | Description | | :-------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Component clean state | The restore process assumes a **clean state** for all components, including Elasticsearch/OpenSearch. This means **no prior persistent volumes** or **component state** should exist - all data is restored from scratch. | | Camunda version | **Backups must be restored** using the **exact Camunda version** they were created with. As noted during the backup process, the version is embedded in the backup name.This is essential because starting a component with a mismatched version may result in startup failures due to schema incompatibilities with Elasticsearch/OpenSearch and the component itself. Although schema changes are generally avoided in patch releases, they can still occur.When using the Camunda Helm chart, this means figuring out the corresponding version. For this the [Camunda Helm chart Version Matrix](https://helm.camunda.io/camunda-platform/version-matrix/) can help. Click on the `major.minor` release and then search for the backed up patch release of your component. The other components would typically fit in there as well. |
Example: Work out your correct Camunda version Our Backups look as follows: ```bash camunda_optimize_1748937221_8.8.0_part_1_of_2 camunda_optimize_1748937221_8.8.0_part_2_of_2 camunda_webapps_1748937221_8.8.0_part_1_of_5 camunda_webapps_1748937221_8.8.0_part_2_of_5 camunda_webapps_1748937221_8.8.0_part_3_of_5 camunda_webapps_1748937221_8.8.0_part_4_of_5 camunda_webapps_1748937221_8.8.0_part_5_of_5 camunda_zeebe_records_backup_1748937221 ``` From this, we know: - Optimize: 8.8.0 - Web Applications (Operate / Tasklist): 8.8.0 Based on this, we can look in the [matrix versioning of 8.8](https://helm.camunda.io/camunda-platform/version-matrix/camunda-8.8) and see the corresponding Camunda Helm chart version is `13.0.0`.
--- ## Backup and restore Camunda Hub data Back up and restore Camunda Hub independently of the Orchestration Cluster. ## Create backup To create a backup of Camunda Hub data, you must back up the database that Camunda Hub uses by following the instructions of the official [PostgreSQL documentation](https://www.postgresql.org/docs/current/backup-dump.html). For example, to create a backup of the database using `pg_dumpall`, use the following command: ```bash pg_dumpall -U -h -p -f dump.psql --quote-all-identifiers Password: ``` `pg_dumpall` may ask multiple times for the same password. The database will be dumped into `dump.psql`. :::note Database dumps created with `pg_dumpall`/`pg_dump` can only be restored into a database with the same or later version of PostgreSQL, see [PostgreSQL documentation](https://www.postgresql.org/docs/current/app-pgdump.html#PG-DUMP-NOTES). ::: ## Restore Backups can only be restored with downtime. To restore the database dump, first ensure that Camunda Hub is stopped. Then, to restore the database use the following command: ```bash psql -U -h -p -f dump.psql ``` After the database has been restored, you can start Camunda Hub again. :::danger When restoring Camunda Hub data from a backup, ensure that the ids of the users stored in your OIDC provider (e.g. Keycloak) do not change in between the backup and restore. Otherwise, users may not be able to access their projects after the restore (see [Camunda Hub's troubleshooting guide](/self-managed/components/hub/troubleshooting/troubleshoot-missing-data.md)). ::: :::tip Some vendors provide tools that help with database backups and restores, such as [AWS Backup](https://aws.amazon.com/getting-started/hands-on/amazon-rds-backup-restore-using-aws-backup/) or [Cloud SQL backups](https://cloud.google.com/sql/docs/postgres/backup-recovery/backups). ::: --- ## Back up and restore Optimize independently Back up and restore Optimize independently of the Orchestration Cluster. ## About this guide Optimize always stores its data in Elasticsearch or OpenSearch, regardless of what the Orchestration Cluster uses as secondary storage. Which backup procedure to follow depends on what secondary storage the Orchestration Cluster uses: - **Elasticsearch / OpenSearch**: Optimize shares the same ES/OS instance with the Orchestration Cluster. Backup must be **coordinated**: all components use a single shared backup ID within the same backup window. Optimize cannot be backed up independently in this configuration. Use the [Elasticsearch / OpenSearch backup guide](./elasticsearch/backup.md). - **RDBMS**: Optimize stores its data in Elasticsearch or OpenSearch independently of the Orchestration Cluster's RDBMS storage. There is no shared backup boundary to keep consistent. Optimize can be backed up on its own schedule with its own backup IDs. Use this guide. :::warning When the Orchestration Cluster uses Elasticsearch or OpenSearch, backing up Optimize with a different backup ID or at a different time than the other components produces an **inconsistent restore point**: Optimize analytics data will describe a state that no longer matches the underlying process data in Operate and Zeebe. ::: This guide covers: - **Optimize alongside an RDBMS-backed Orchestration Cluster**: Optimize must be backed up and restored separately from the Orchestration Cluster. The backup and restore procedures are independent. - **Optimize as a fully standalone application**: Optimize deployed without other Camunda components. Optimize stores its data across multiple indices in Elasticsearch or OpenSearch. A backup consists of two snapshots that must be taken through the Backup Management API to ensure consistency across indices. For example, a backup with ID `123456` produces: ```text camunda_optimize_123456__part_1_of_2 camunda_optimize_123456__part_2_of_2 ``` Backups are created asynchronously while Optimize continues running, and are restored using the standard Elasticsearch/OpenSearch snapshot restore API. ## Prerequisites Before creating a backup, complete the following setup: | Prerequisite | Description | | :---------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Snapshot repository | Register a snapshot repository with Elasticsearch or OpenSearch. See [Elasticsearch snapshot repository](https://www.elastic.co/docs/deploy-manage/tools/snapshot-and-restore/manage-snapshot-repositories) or [OpenSearch snapshot repository](https://docs.opensearch.org/docs/latest/tuning-your-cluster/availability-and-recovery/snapshots/snapshot-restore/). | | Optimize backup configuration | Configure the repository name in Optimize using the `CAMUNDA_OPTIMIZE_BACKUP_REPOSITORY_NAME` environment variable, or by adding it to your Optimize [`environment-config.yaml`](/self-managed/components/optimize/configuration/system-configuration.md). See [Elasticsearch backup settings](/self-managed/components/optimize/configuration/system-configuration.md#elasticsearch-backup-settings) or [OpenSearch backup settings](/self-managed/components/optimize/configuration/system-configuration.md#opensearch-backup-settings). | :::note The management API is accessible via the management port (default `8092`). Direct access is required — the management port is not publicly exposed. In Kubernetes, use [port-forwarding](https://kubernetes.io/docs/reference/kubectl/generated/kubectl_port-forward/) or [exec](https://kubernetes.io/docs/reference/kubectl/generated/kubectl_exec/). ```bash # Port-forwarding example (Kubernetes) kubectl port-forward services/camunda-optimize 8092:8092 ``` ::: :::note The configured `contextPath` does not apply to the management port. Setting `contextPath` in the Camunda Helm chart for Optimize will not change the management API path, which remains at `/`. ::: ## Set up environment variables The following examples use environment variables for conciseness: ```bash export BACKUP_ID=$(date +%s) # Unix timestamp as unique, always-increasing ID export OPTIMIZE_MANAGEMENT_API="http://localhost:8092" export ELASTIC_SNAPSHOT_REPOSITORY="camunda" # Name of your snapshot repository export ELASTIC_ENDPOINT="http://localhost:9200" ``` ```bash export BACKUP_ID=$(date +%s) # Unix timestamp as unique, always-increasing ID export OPTIMIZE_MANAGEMENT_API="http://localhost:8092" export OPENSEARCH_SNAPSHOT_REPOSITORY="camunda" # Name of your snapshot repository export OPENSEARCH_ENDPOINT="" ``` ```bash export BACKUP_ID=$(date +%s) export CAMUNDA_RELEASE_NAME="camunda" export OPTIMIZE_MANAGEMENT_API="http://$CAMUNDA_RELEASE_NAME-optimize:8092" export ELASTIC_SNAPSHOT_REPOSITORY="camunda" export ELASTIC_ENDPOINT="http://$CAMUNDA_RELEASE_NAME-elasticsearch:9200" ``` ```bash export BACKUP_ID=$(date +%s) export CAMUNDA_RELEASE_NAME="camunda" export OPTIMIZE_MANAGEMENT_API="http://$CAMUNDA_RELEASE_NAME-optimize:8092" export OPENSEARCH_SNAPSHOT_REPOSITORY="camunda" export OPENSEARCH_ENDPOINT="" ``` :::note We recommend using the Unix timestamp as the backup ID. The backup ID must be a positive integer and must be greater than the ID used for any previous backup. ::: ## Create a backup ### 1. Trigger the backup Use the following API to initiate a backup: ```bash curl -XPOST "$OPTIMIZE_MANAGEMENT_API/actuator/backups" \ -H 'Content-Type: application/json' \ -d "{\"backupId\": $BACKUP_ID}" ``` A successful response confirms the backup has been scheduled: ```json { "message": "Backup creation for ID 1748937221 has been scheduled. Use the GET API to monitor completion of backup process" } ``` For the full API reference including response codes, see the [Optimize backup management API](./optimize-backup.md#create-backup-api). ### 2. Wait for the backup to complete Backup creation is asynchronous. Poll the following endpoint until the state is `COMPLETE`: ```bash curl -s "$OPTIMIZE_MANAGEMENT_API/actuator/backups/$BACKUP_ID" ```
Example response ```json { "backupId": 1748937221, "failureReason": null, "state": "COMPLETE", "details": [ { "snapshotName": "camunda_optimize_1748937221__part_1_of_2", "state": "SUCCESS", "startTime": "2025-06-03T07:53:54.389+0000", "failures": [] }, { "snapshotName": "camunda_optimize_1748937221__part_2_of_2", "state": "SUCCESS", "startTime": "2025-06-03T07:53:54.389+0000", "failures": [] } ] } ```
Alternatively, use a loop to wait until completion: ```bash while [[ "$(curl -s "$OPTIMIZE_MANAGEMENT_API/actuator/backups/$BACKUP_ID" | jq -r .state)" != "COMPLETE" ]]; do echo "Waiting..." sleep 5 done echo "Backup $BACKUP_ID complete" ``` Possible backup states: - `COMPLETE`: The backup can be used to restore data. - `IN_PROGRESS`: Backup creation is still in progress. Wait before using for restore. - `FAILED`: Something went wrong. Use the [Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/current/get-snapshot-status-api.html) or [OpenSearch](https://opensearch.org/docs/latest/api-reference/snapshots/get-snapshot-status/) get snapshot status API for each snapshot to investigate. - `INCOMPATIBLE`: The backup is incompatible with the current Elasticsearch/OpenSearch version. - `INCOMPLETE`: The backup is incomplete, for example if backup creation was interrupted or individual snapshots were deleted. ## Restore a backup Restoring Optimize requires downtime. Optimize must be stopped before restoring snapshots, and the restore targets a clean Elasticsearch/OpenSearch state. ### Prerequisites - **Optimize stopped**: Optimize must not be running while restoring the datastore. - **Clean state**: Elasticsearch or OpenSearch must have no existing Optimize indices. All data will be restored from the snapshots. - **Optimize version**: The backup must be restored using the **same Optimize version** it was created with. The version is embedded in the snapshot names, for example as `camunda_optimize_123456__part_1_of_2`. - **Snapshot repository**: Elasticsearch or OpenSearch must be configured with the same snapshot repository used during backup. :::warning Do not start Optimize against a partially restored datastore. Ensure all snapshots are fully restored before starting Optimize. ::: ### Step 1: Create Optimize index templates on the target datastore Optimize creates its index templates on first startup. These templates must exist before snapshots can be successfully restored. Start Optimize connected to the target Elasticsearch/OpenSearch instance (the clean instance where you intend to restore). Allow it to reach a healthy state, which seeds the required index templates and component templates. You can verify the templates were created: ```bash curl -s "$ELASTIC_ENDPOINT/_index_template" \ | jq -r '.index_templates[].name' \ | grep optimize \ | sort ``` ```bash curl -s "$OPENSEARCH_ENDPOINT/_index_template" \ | jq -r '.index_templates[].name' \ | grep optimize \ | sort ``` ### Step 2: Find available backup IDs With Optimize running from the previous step, identify which backups are available. You can query either the Optimize management API or the snapshot repository directly. **Using the Optimize management API:** ```bash curl -s "$OPTIMIZE_MANAGEMENT_API/actuator/backups" ```
Example response ```json [ { "backupId": 1748937221, "state": "COMPLETE", "details": [ { "snapshotName": "camunda_optimize_1748937221__part_1_of_2", "state": "SUCCESS", "startTime": "2025-06-03T07:53:54.389+0000", "failures": [] }, { "snapshotName": "camunda_optimize_1748937221__part_2_of_2", "state": "SUCCESS", "startTime": "2025-06-03T07:53:54.389+0000", "failures": [] } ] } ] ```
**Alternatively, query the snapshot repository directly.** Optimize snapshot names follow the pattern `camunda_optimize_{backupId}_{version}_part_N_of_2`. ```bash curl -s "$ELASTIC_ENDPOINT/_snapshot/$ELASTIC_SNAPSHOT_REPOSITORY/_all" \ | jq -r '.snapshots[].snapshot' \ | grep "^camunda_optimize_" ```
Example output ``` camunda_optimize_1748937221__part_1_of_2 camunda_optimize_1748937221__part_2_of_2 camunda_optimize_1749130104__part_1_of_2 camunda_optimize_1749130104__part_2_of_2 ```
```bash curl -s "$OPENSEARCH_ENDPOINT/_snapshot/$OPENSEARCH_SNAPSHOT_REPOSITORY/_all" \ | jq -r '.snapshots[].snapshot' \ | grep "^camunda_optimize_" ```
Example output ``` camunda_optimize_1748937221__part_1_of_2 camunda_optimize_1748937221__part_2_of_2 camunda_optimize_1749130104__part_1_of_2 camunda_optimize_1749130104__part_2_of_2 ```
Ensure both `part_1_of_2` and `part_2_of_2` exist for your chosen backup ID before proceeding. An incomplete backup cannot be used for restore. Once you have chosen the backup ID, set it as an environment variable for use in subsequent steps: ```bash export BACKUP_ID=1748937221 ``` ### Step 3: Stop Optimize Stop Optimize now, before you continue with Step 4. Only Steps 1 and 2 need Optimize running. If you are using an external Elasticsearch/OpenSearch and Kubernetes, temporarily [uninstall](https://helm.sh/docs/helm/helm_uninstall/) the Camunda Helm chart or [scale](https://kubernetes.io/docs/reference/kubectl/generated/kubectl_scale/) all components to 0, so that nothing is running and potentially interacting with the datastore. With Helm, disable Optimize in `values.yml`: ```yaml optimize: enabled: false ``` ### Step 4: Delete all existing Optimize indices Delete the indices created during startup so they do not block the snapshot restore. The commands match only indices that start with the default index prefix `optimize-`. If you configured a custom index prefix (see [Elasticsearch index settings](/self-managed/components/optimize/configuration/system-configuration.md#elasticsearch-index-settings) or [OpenSearch index settings](/self-managed/components/optimize/configuration/system-configuration.md#opensearch-index-settings)), use it instead. ```bash for index in $(curl -s "$ELASTIC_ENDPOINT/_cat/indices?h=index" | grep '^optimize-'); do echo "Deleting index: $index" curl -X DELETE "$ELASTIC_ENDPOINT/$index" done ``` ```bash for index in $(curl -s "$OPENSEARCH_ENDPOINT/_cat/indices?h=index" | grep '^optimize-'); do echo "Deleting index: $index" curl -X DELETE "$OPENSEARCH_ENDPOINT/$index" done ``` ### Step 5: Restore the Optimize snapshots Although backup creation order matters for consistency, you can restore the backed-up Optimize snapshots in any order. Optimize does not provide an API endpoint to restore these backups. Restore them directly in your Elasticsearch or OpenSearch datastore. Based on your chosen backup ID in [find available backup IDs](#step-2-find-available-backup-ids), you can now restore the snapshots in Elasticsearch/OpenSearch for each available backup under the same backup ID. The following uses the [Elasticsearch snapshot API](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-snapshot-restore) to restore a snapshot. ```bash curl -XPOST "$ELASTIC_ENDPOINT/_snapshot/$ELASTIC_SNAPSHOT_REPOSITORY/$SNAPSHOT_NAME/_restore?wait_for_completion=true" ``` The following uses the [OpenSearch snapshot API](https://docs.opensearch.org/docs/latest/api-reference/snapshots/restore-snapshot/) to restore a snapshot. ```bash curl -XPOST "$OPENSEARCH_ENDPOINT/_snapshot/$OPENSEARCH_SNAPSHOT_REPOSITORY/$SNAPSHOT_NAME/_restore?wait_for_completion=true" ``` Where `$SNAPSHOT_NAME` would be any of the following based on our example in [find available backup IDs](#step-2-find-available-backup-ids). Ensure that all snapshots correspond to the same backup ID and restore them one by one. ```bash camunda_optimize_1748937221__part_1_of_2 camunda_optimize_1748937221__part_2_of_2 ``` ### Step 6: Start Optimize After all snapshots are successfully restored, start Optimize again. It will connect to the restored datastore and resume from the backed-up state. Monitor the Optimize logs for any errors after startup. ## Delete a backup To delete an existing backup and free storage, use the following API: ```bash curl --request DELETE "$OPTIMIZE_MANAGEMENT_API/actuator/backups/$BACKUP_ID" ``` This removes all snapshots associated with the given backup ID from the configured snapshot repository. A successful response returns `204 No Content`. For the full API reference including response codes, see the [Optimize backup management API](./optimize-backup.md#delete-backup-api). ## List existing backups To list all available Optimize backups: ```bash curl -s "$OPTIMIZE_MANAGEMENT_API/actuator/backups" ``` To retrieve information about a specific backup: ```bash curl -s "$OPTIMIZE_MANAGEMENT_API/actuator/backups/$BACKUP_ID" ``` For the full API reference, see the [Optimize backup management API](./optimize-backup.md#get-backup-info-api). --- ## Optimize backup management API Back up your Optimize data using the Backup Management API. ## About this API Optimize is a dedicated application that stores its data over multiple indices in the database. To ensure data integrity across indices, a backup of Optimize data consists of two Elasticsearch/OpenSearch snapshots, each containing a different set of Optimize indices. Each backup is identified by a positive integer backup ID. For example, a backup with ID `123456` consists of the following snapshots: ``` camunda_optimize_123456_8.8.0_part_1_of_2 camunda_optimize_123456_8.8.0_part_2_of_2 ``` Optimize provides an API to trigger a backup and retrieve information about a given backup's state. During backup creation Optimize can continue running. The backed up data can later be restored using the standard Elasticsearch/OpenSearch snapshot restore API. :::warning Usage of this API requires the backup store to be configured for the component. - Optimize configuration - [Elasticsearch](/self-managed/components/optimize/configuration/system-configuration.md#elasticsearch-backup-settings) - [OpenSearch](/self-managed/components/optimize/configuration/system-configuration.md#opensearch-backup-settings) 1. A snapshot repository of your choice must be registered with Elasticsearch/OpenSearch. 2. The repository name must be specified using the `CAMUNDA_OPTIMIZE_BACKUP_REPOSITORY_NAME` environment variable, or by adding it to your Optimize [`environment-config.yaml`](/self-managed/components/optimize/configuration/system-configuration.md): - [Elasticsearch snapshot repository](https://www.elastic.co/docs/deploy-manage/tools/snapshot-and-restore/manage-snapshot-repositories) - [OpenSearch snapshot repository](https://docs.opensearch.org/docs/latest/tuning-your-cluster/availability-and-recovery/snapshots/snapshot-restore/) ::: ## Create backup API Note that the backup API can be reached via the `/actuator` management port, which by default is `8092`. The configured context path does not apply to the management port. The following endpoint can be used to trigger the backup process: ``` POST actuator/backups { "backupId": } ``` ### Response | Code | Description | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | 202 Accepted | Backup process was successfully initiated. To determine whether backup process was completed refer to the GET API. | | 400 Bad Request | Indicates issues with the request, for example when the `backupId` contains invalid characters. | | 409 Conflict | Indicates that a backup with the same `backupId` already exists. | | 500 Server Error | All other errors, e.g. issues communicating with the database for snapshot creation. Refer to the returned error message for more details. | | 502 Bad Gateway | Optimize has encountered issues while trying to connect to the database. | ### Example request ```shell curl --request POST 'http://localhost:8092/actuator/backups' \ -H 'Content-Type: application/json' \ -d '{ "backupId": 123456 }' ``` ### Example response ```json { "message": "Backup creation for ID 123456 has been scheduled. Use the GET API to monitor completion of backup process" } ``` ## Get backup info API Note that the backup API can be reached via the `/actuator` management port, which by default is `8092`. The configured context path does not apply to the management port. Information about a specific backup can be retrieved using the following request: ``` GET actuator/backups/{backupId} ``` Information about all existing Optimize backups can be retrieved by omitting the optional `backupId` parameter: ``` GET actuator/backups ``` ### Response | Code | Description | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | 200 OK | Backup state could be determined and is returned in the response body (see example below). | | 400 Bad Request | There is an issue with the request, for example the repository name specified in the Optimize configuration does not exist. Refer to returned error message for details. | | 404 Not Found | If a backup ID was specified, no backup with that ID exists. | | 500 Server Error | All other errors, e.g. issues communicating with the database for snapshot state retrieval. Refer to the returned error message for more details. | | 502 Bad Gateway | Optimize has encountered issues while trying to connect to the database. | ### Example request ```shell curl --request GET 'http://localhost:8092/actuator/backups/123456' ``` ### Example response ```json { "backupId": 123456, "failureReason": null, "state": "COMPLETE", "details": [ { "snapshotName": "camunda_optimize_123456_8.8.0_part_1_of_2", "state": "SUCCESS", "startTime": "2024-11-09T10:11:36.978+0100", "failures": [] }, { "snapshotName": "camunda_optimize_123456_8.8.0_part_2_of_2", "state": "SUCCESS", "startTime": "2024-11-09T10:11:37.178+0100", "failures": [] } ] } ``` Note that the endpoint will return a single item when called with a `backupId` and a list of items when called without specifying a `backupId`. Possible states of the backup: - `COMPLETE`: The backup can be used for restoring data. - `IN_PROGRESS`: The backup process for this backup ID is still in progress. - `FAILED`: Something went wrong when creating this backup. To find out the exact problem, use the [Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/current/get-snapshot-status-api.html)/[OpenSearch](https://opensearch.org/docs/latest/api-reference/snapshots/get-snapshot-status/) get snapshot status API for each of the snapshots included in the given backup. - `INCOMPATIBLE`: The backup is incompatible with the current Elasticsearch/OpenSearch version. - `INCOMPLETE`: The backup is incomplete (this could occur when the backup process was interrupted or individual snapshots were deleted). ## Delete backup API Note that the backup API can be reached via the `/actuator` management port, which by default is `8092`. The configured context path does not apply to the management port. An existing backup can be deleted using the below API which deletes all Optimize snapshots associated with the supplied backupID. ``` DELETE actuator/backups/{backupId} ``` ### Response | Code | Description | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | 204 No Content | The delete request for the associated snapshots was submitted to the database successfully. | | 400 Bad Request | There is an issue with the request, for example the repository name specified in the Optimize configuration does not exist. Refer to returned error message for details. | | 500 Server Error | An error occurred, for example the snapshot repository does not exist. Refer to the returned error message for details. | | 502 Bad Gateway | Optimize has encountered issues while trying to connect to Elasticsearch/OpenSearch. | ### Example request ```shell curl --request DELETE 'http://localhost:8092/actuator/backups/123456' ``` --- ## Camunda backup creation (RDBMS) Back up your Camunda 8 Self-Managed Orchestration cluster components (Zeebe, Operate, Tasklist, and Admin) when using a relational database management system (RDBMS) as secondary storage. :::tip For cross-region recovery using these RDBMS backups, see [Cold Recovery](../../../concepts/multi-region/cold-recovery.md). ::: :::note This procedure is part of the **first phase of Decoupled Continuous Backups** and covers **Zeebe**, **Operate**, **Tasklist**, and **Admin**. It does **not** cover **Management Identity** or **Optimize**. Optimize always stores its data in Elasticsearch or OpenSearch, independently of the Orchestration Cluster's secondary storage. If you deploy Optimize alongside an RDBMS-backed Orchestration Cluster, back up Optimize independently using the [standalone Optimize backup procedure](../optimize-backup-and-restore.md). You do not need to switch the Orchestration Cluster backup to the Elasticsearch / OpenSearch path. ::: ## Prerequisites The following prerequisites are required before you can create a backup. | Prerequisite | Description | | :---------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Configure Zeebe backup storage. | Configure the backup storage for Zeebe. This is required regardless of your secondary storage choice. See [Zeebe backup configuration](/self-managed/components/orchestration-cluster/zeebe/configuration/broker.md#camundadataprimary-storagebackup). | | Enable continuous backups | Enable continuous backups. See [Zeebe scheduler configuration](../../../../components/orchestration-cluster/core-settings/configuration/properties/#camundadataprimary-storagebackup). | | (Recommended) Configure Zeebe scheduled backup. | Configure Zeebe's internal primary storage backup scheduler. See [Zeebe scheduler configuration](../../../../components/orchestration-cluster/core-settings/configuration/properties/#camundadataprimary-storagebackup). | | Set up RDBMS backups. | You are responsible for backing up the RDBMS using your database vendor's native tools (for example, `pg_dump`, `mysqldump`, `RMAN`). Back up the **entire Camunda database**, including all component tables. Schedule RDBMS backups at a similar frequency to your Zeebe backups. | ## Architecture overview When Camunda uses an RDBMS as **secondary storage**, backups involve **two independent systems**: - **Zeebe (primary storage)**: Backs up its internal state (log stream, snapshots) to an external blob store (S3, GCS, Azure, or filesystem). These are called **primary storage backups**. - **The external RDBMS**: Backed up using your database vendor's native tools (pg_dump, mysqldump, RMAN, and similar). This is the **secondary storage backup**. During restore, Zeebe uses the exporter position stored in the RDBMS to find the correct primary storage backup, or backups, that match the RDBMS state. This ensures consistency between the two systems without requiring synchronized backup timing. :::tip While Zeebe during restore will match the secondary storage's backup position, it is recommended to have the backups taken at similar intervals. This minimizes the time required after restore for Zeebe to re-export events to the secondary storage. ::: ## Continuous backups Continuous backups must be enabled for RDBMS backup and restore. ### How continuous backups work Continuous backups rely on a Zeebe feature that retains data records in the log stream until they are backed up. This ensures that after restore, the state of primary and secondary storage is in sync, without the need to orchestrate primary and secondary storage backups. When continuous backups are enabled: 1. Zeebe prevents log compaction from deleting any segment that hasn't been backed up yet, and tracks backup metadata so you can query available backup ranges. 2. The system tracks **backup ranges** — unbroken sequences of consecutive backups. These ranges define the time windows available for restore and can be queried via the [backup state actuator](../zeebe-backup-and-restore.md#request-runtime-state). :::warning Without [scheduled backups](#scheduled-backup) or regular manual backups, continuous mode causes disk usage to grow indefinitely because Zeebe cannot compact any log segments. Always pair continuous backups with a backup schedule. ::: ### Enable continuous backups Set the following configuration property: ```yaml camunda: data: primary-storage: backup: store: S3 # or GCS, AZURE, FILESYSTEM continuous: true ``` ```bash export CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_STORE=S3 export CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_CONTINUOUS=true ``` ### Checkpoint interval The checkpoint interval controls how frequently Zeebe injects marker checkpoints into the log stream. These markers serve as potential restore points — since a cluster can only be restored to a checkpoint that exists on all partitions, more frequent markers enable finer-grained [point-in-time restore](./restore-api.md#trigger-the-restore). ```yaml camunda: data: primary-storage: backup: checkpoint-interval: PT15M # inject a marker checkpoint every 15 minutes ``` Note that the checkpoint interval does **not** determine how frequently backups are taken — that is controlled by the [backup schedule](#scheduled-backup). A shorter checkpoint interval provides more precise restore points within the backed-up range, at the cost of slightly more metadata overhead. ## Backup ranges A **backup range** is a contiguous sequence of backups that together cover the complete log data between the first and last backup, with no gaps. Ranges are tracked per partition. Ranges are critical for restore because restoring from a time window or to match the RDBMS state requires complete log coverage. Each successful backup extends the current range as long as its log data connects to the previous backup. If a backup fails and the next successful backup no longer covers the missing log data, the current range ends and a new range begins. Use the [backup state actuator](../zeebe-backup-and-restore.md#request-runtime-state) to inspect the current ranges for each partition. ## Backup process ### Scheduled backup Zeebe's internal backup scheduler creates primary storage backups at a predefined interval. The **schedule** and **continuous mode** serve complementary purposes: the schedule triggers the backups, while continuous mode prevents log compaction and enables backup range tracking. This ensures you always have backup ranges available for disaster recovery. Both should be enabled together for a complete backup strategy. Learn more about configuring the backup scheduler [here](../../../../components/orchestration-cluster/core-settings/configuration/properties/#camundadataprimary-storagebackup). #### Recommended configuration ```yaml camunda: data: primary-storage: backup: store: S3 # or GCS, AZURE, FILESYSTEM continuous: true schedule: PT1H # take a backup every hour checkpoint-interval: PT15M # restore point granularity retention: window: P7D # retain backups for 7 days cleanup-schedule: PT1H # check for expired backups every hour ``` :::warning To properly configure your backup interval, monitor the `zeebe.backup.operations.latency` metric (with `operation=take`) to understand your average backup duration. As a general rule of thumb, the backup interval must be greater than your latency. Latency is affected by the size of Zeebe's runtime state. ::: :::tip You can still request on-demand primary storage backups through the regular [API](../../zeebe-backup-and-restore/#request), without the `backupId` parameter as it's being generated by the cluster. ::: ### Manual backup If you opt out of the scheduled backup approach, you can still request on-demand backups using the [backup management API](/self-managed/operational-guides/backup-restore/zeebe-backup-and-restore.md). The `backupId` is generated automatically by the cluster. ```bash # For Kubernetes port-forwarding, set the following endpoints: export ORCHESTRATION_CLUSTER_MANAGEMENT_API=http://localhost:9600 curl -X POST "$ORCHESTRATION_CLUSTER_MANAGEMENT_API/actuator/backupRuntime" \ -H "Content-Type: application/json" ``` :::tip It is recommended to have the primary storage backup taken after the secondary storage one, if you are coordinating the backup process. ::: ## Verify your backup setup After configuring backups, verify that they are working correctly by querying the [backup state actuator](../zeebe-backup-and-restore.md#request-runtime-state): ```bash curl 'http://localhost:9600/actuator/backupRuntime/state' ``` Confirm the following in the response: - **`backupStates`** contains an entry for each partition, indicating that at least one backup has been taken. - **`ranges`** contains at least one range per partition. Each range has a `start` and `end` timestamp defining the time period you can restore from. - If you see **multiple ranges** for a single partition, there is a gap between them where no data is available for restore. If the response is empty or missing partitions, check your backup store configuration and ensure continuous backups are enabled. ## Backing up RDBMS Back up the RDBMS as described in the [prerequisites](#prerequisites). While exact synchronization with Zeebe backups is not required, as the restore process handles alignment automatically, keeping backup intervals similar minimizes the time Zeebe needs to re-export events after a restore. During restore, Zeebe reads the **exporter position** from the `EXPORTER_POSITION` table — which records the last Zeebe log stream position exported to the RDBMS — to determine which primary storage backup to restore from. ## (Optional) Back up Optimize data {#back-up-optimize-data} If you are using Optimize alongside an RDBMS-backed Orchestration Cluster, Optimize must be backed up independently. Optimize always stores its data in Elasticsearch or OpenSearch, regardless of what the Orchestration Cluster uses as secondary storage. See [back up and restore Optimize independently](../optimize-backup-and-restore.md) for the complete procedure. ## (Optional) Back up Camunda Hub data {#back-up-web-modeler-data} If you are using Camunda Hub, you can also back up its data. Camunda Hub stores its data in a relational database, so you can use the same backup tools as for the RDBMS secondary storage. See [backup and restore Camunda Hub data](../modeler-backup-and-restore.md) for more details. ## Primary storage retention Automatic retention for primary storage (Zeebe's) backups is available. This periodically deletes backups from the configured blob storage based on a preconfigured retention window. At least one backup is always retained to prevent potential data loss, even if it falls outside the configured retention window.. To configure backup retention settings, see [backup retention configuration](../../../../components/orchestration-cluster/core-settings/configuration/properties/#camundadataprimary-storagebackupretention). When retention deletes old backups, the affected [backup ranges](#backup-ranges) shrink accordingly, narrowing your available restore window. Ensure that your retention window is at least as long as the restore window you require. For example, if you need the ability to restore to any point in the last 7 days, set the retention window to at least `P7D`. :::note Backups created outside or before the scheduler was activated are also susceptible to being deleted by the retention mechanism. This only affects the primary storage. ::: --- ## Restore a backup with the Restore API (RDBMS) Restore Zeebe partition data through the Orchestration Cluster Restore API without restarting the brokers, when using a relational database management system (RDBMS) as secondary storage. This page is part of the RDBMS [restore procedure](./restore.md). To compare it with the legacy Restore Application, see [choosing a restore approach](./restore.md#choosing-a-restore-approach). ## About the Restore API With Camunda 8.10 and later, a Restore API recovery runs during a downtime window while the cluster is in recovery mode. It runs in four phases, driven by two API requests: 1. **Entering recovery mode**: every broker deactivates its partitions and switches to a restricted partition manager. While the cluster is in recovery mode it processes no work, and only read-only operations and restore remain available. 2. **Restoring secondary storage**: while the cluster is in recovery mode, restore the RDBMS to the intended point that the primary storage backup aligns to. 3. **Restoring the partitions**: the cluster plans a single change that, for every broker and partition, first drops the local partition data and then restores that partition from the selected backups. The steps of that plan run one at a time across the cluster. 4. **Returning to processing**: once every partition is restored, the same change switches all brokers back to `PROCESSING` and the partitions become active again. Both requests are non-blocking. Each is acknowledged as soon as the cluster accepts the change and returns the `changeId` of the cluster configuration change that carries it out. ## Prerequisites The Restore API requires the following: | Prerequisite | Description | | :--------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Camunda version | Camunda 8.10 or later, restored with the exact version the backup was created with. | | Backup store | Every broker is configured with the same backup store that holds the backup, as described in the [RDBMS backup prerequisites](./backup.md#prerequisites). | | Completed backup | A completed backup exists for every partition. List the available backups with [list runtime backups](/apis-tools/orchestration-cluster-api-rest/specifications/list-runtime-backups.api.mdx). | | Partition count | The partition count of the cluster matches the partition count of the backup. Brokers can be scaled between backup and restore as long as the partition count is unchanged. See [restoring a backup with fewer partitions](#restoring-a-backup-with-fewer-partitions). | | API access | Authenticated access to the Orchestration Cluster REST API. See [authentication](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md). | | Authorizations | If [authorizations](/components/concepts/access-control/authorizations.md) are enabled, the caller needs the `RESTORE` permission on the `BACKUP` resource. | ## Restoring an RDBMS-backed cluster ### 1. Switch the cluster into recovery mode [Change the cluster mode](/apis-tools/orchestration-cluster-api-rest/specifications/change-cluster-mode.api.mdx) to `RECOVERING`: ```bash curl -X PATCH "${ORCHESTRATION_CLUSTER_API}/mode?mode=RECOVERING" ``` The response returns the ID of the cluster change and the operations it will apply. The plan contains one `ModeChangeOperation` and one `AwaitModeChangeOperation` per broker:
Example response ```json { "changeId": "7", "plannedChanges": [ { "physicalTenantId": "default", "operations": [ { "operation": "ModeChangeOperation", "mode": "RECOVERING" }, { "operation": "AwaitModeChangeOperation", "mode": "RECOVERING" } ] } ] } ```
Wait until this change has completed before you trigger the restore. A restore request is only accepted while every broker of the cluster is in recovery mode. Requests sent earlier are rejected with `409`. Verify that all brokers are in recovery mode using either the Cluster API or the Management API. You can use the [topology API](/apis-tools/orchestration-cluster-api-rest/specifications/get-topology.api.mdx) to verify the state for all partitions and brokers: ```bash curl "${ORCHESTRATION_CLUSTER_API}/topology" ``` The response shows the current state of all brokers and partitions. In recovery mode, every partition should have the state `recovering`.
Example response ```json { "brokers": [ { "nodeId": 0, "brokerId": "0", "host": "192.168.1.51", "port": 26501, "partitions": [ { "partitionId": 1, "role": "inactive", "health": "healthy", "state": "recovering" }, { "partitionId": 2, "role": "inactive", "health": "healthy", "state": "recovering" } ] } ] } ```
Query the [cluster monitoring API](/self-managed/components/orchestration-cluster/zeebe/operations/cluster-scaling.md#monitoring-api) and check that `lastChange.id` matches the returned `changeId` and that no `pendingChange` is reported. Additionally, you can verify that partitions of all brokers are in `recovering` state. ```bash curl "${ORCHESTRATION_CLUSTER_MANAGEMENT_API}/actuator/cluster" ```
### 2. Restore RDBMS With the cluster in recovery mode, nothing is exported to secondary storage, so restore the RDBMS now. Skip this step if the RDBMS was already restored another way, for example as part of a wider disaster recovery procedure. Restore the RDBMS to the point in time you intend to restore the primary storage to. Camunda aligns the Zeebe and RDBMS restore points automatically. Complete this step before you trigger the Zeebe restore. The Restore API switches the brokers back to `PROCESSING` as soon as the last partition is restored, and processing then resumes against whatever secondary storage is in place. ### 3. Trigger the restore {#trigger-the-restore} There are multiple ways to define your restore point objective depending on your backup strategy. Requests that combine `backupIds` with `from` or `to`, that specify a time range without continuous backups enabled, or that reference a backup with no completed state in the store, are rejected with `400`. [Provide the restore parameters](/apis-tools/orchestration-cluster-api-rest/specifications/restore.api.mdx). Camunda validates the request, resolves the backups for every partition, and acknowledges the request with `202` before the restore itself runs: Camunda resolves the best available restore point automatically for each partition when you omit the request body. ```bash curl -X POST "${ORCHESTRATION_CLUSTER_API}/restore" ``` Provide a single backup ID in `backupIds` to restore the selected backup for every partition. ```bash curl -X POST "${ORCHESTRATION_CLUSTER_API}/restore" \ -H 'Content-Type: application/json' \ -d '{ "backupIds": [1748937221] }' ``` With an RDBMS as secondary storage and [continuous backups](./backup.md#continuous-backups) enabled, restore to the closest available checkpoint within an ISO 8601 time range: ```bash curl -X POST "${ORCHESTRATION_CLUSTER_API}/restore" \ -H 'Content-Type: application/json' \ -d '{ "from": "2026-01-01T10:00:00Z", "to": "2026-01-01T12:00:00Z" }' ``` With an RDBMS as secondary storage and [continuous backups](./backup.md#continuous-backups) enabled, restore to the closest available checkpoint before a specific ISO 8601 timestamp by providing only the `to` parameter: ```bash curl -X POST "${ORCHESTRATION_CLUSTER_API}/restore" \ -H 'Content-Type: application/json' \ -d '{ "to": "2026-01-01T12:00:00Z" }' ``` The response returns the `changeId` of the restore, along with the planned operations. The plan drops and restores every partition of every broker, switches all brokers back to `PROCESSING`, and ends with an incarnation number update:
Example response ```json { "changeId": "8", "plannedChanges": [ { "physicalTenantId": "default", "operations": [ { "operation": "SchemaInitializationOperation", "brokerId": "0" }, { "operation": "PartitionPreRestoreOperation", "brokerId": "0", "partitionId": 1 }, { "operation": "PartitionRestoreOperation", "brokerId": "0", "partitionId": 1, "backupIds": [1748937221] }, { "operation": "ModeChangeOperation", "brokerId": "0", "mode": "PROCESSING" }, { "operation": "AwaitModeChangeOperation", "brokerId": "0", "mode": "PROCESSING" }, { "operation": "UpdateIncarnationNumberOperation", "brokerId": "0" } ] } ] } ```
### 4. Track the Restore API operation ### 5. Confirm the cluster state after Restore API recovery The cluster leaves recovery mode as part of the restore, so no further action is required. Use either the [topology API](/apis-tools/orchestration-cluster-api-rest/specifications/get-topology.api.mdx) or the [cluster monitoring API](/self-managed/components/orchestration-cluster/zeebe/operations/cluster-scaling.md#monitoring-api) to verify that the state of all partitions and brokers has returned to normal operational status after the restore. ```bash curl "${ORCHESTRATION_CLUSTER_API}/topology" ``` ```bash curl "${ORCHESTRATION_CLUSTER_MANAGEMENT_API}/actuator/cluster" ``` ## Restoring a cluster with multiple Physical Tenants Self-Managed only In a cluster running multiple [Physical Tenants](/self-managed/concepts/physical-tenants/index.md), the `/v2/mode` and `/v2/restore` endpoints used above are scoped to whichever Physical Tenant your credentials belong to. There is no way to target a different tenant from these self-service endpoints, because the tenant is resolved from the caller's identity, not from the request path. To restore a specific tenant other than your own, or every tenant at once, use the cluster-wide endpoints under `/cluster/v2/...`. These require [cluster admin](/components/admin/cluster-admin.md) access instead of an Orchestration Cluster user's credentials: | Step | Tenant-scoped (your own tenant) | Cluster-wide (cluster admin) | | ------------- | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | Recovery mode | [`PATCH /v2/mode`](/apis-tools/orchestration-cluster-api-rest/specifications/change-cluster-mode.api.mdx) | [`PATCH /cluster/v2/mode`](/apis-tools/orchestration-cluster-api-rest/specifications/change-cluster-mode-as-cluster-admin.api.mdx) | | Trigger | [`POST /v2/restore`](/apis-tools/orchestration-cluster-api-rest/specifications/restore.api.mdx) | [`POST /cluster/v2/restore`](/apis-tools/orchestration-cluster-api-rest/specifications/restore-as-cluster-admin.api.mdx) | | Track | [`GET /v2/restore`](/apis-tools/orchestration-cluster-api-rest/specifications/get-restore-status.api.mdx) | No cluster-wide status endpoint exists. Check each tenant's own restore status, or confirm recovery through cluster-wide topology below. | | Confirm | [`GET /v2/topology`](/apis-tools/orchestration-cluster-api-rest/specifications/get-topology.api.mdx) | [`GET /cluster/v2/topology`](/apis-tools/orchestration-cluster-api-rest/specifications/get-cluster-topology.api.mdx) | ### Choosing the Restore API scope Use a tenant-scoped restore when one Physical Tenant has corrupted or missing data and the other tenants should keep processing. Use a cluster-wide restore when several tenants need recovery, or when the whole cluster must be returned to a coordinated state. The cluster-wide endpoints accept an optional `physicalTenantId` query parameter. Naming a tenant restores only that tenant; omitting the parameter restores every configured tenant. ```bash export CLUSTER_ADMIN_API=http://localhost:8080/cluster/v2 curl -X POST "${CLUSTER_ADMIN_API}/restore" \ -H 'Content-Type: application/json' \ -d '{ "backupIds": [1748937221] }' ``` To restore tenants from different backups in a single request, supply per-tenant restore arguments in the `overrides` field of the request body. A request that both names a single tenant and supplies overrides is rejected, because the two express conflicting targets. ### Cross-tenant safety A backup created for one Physical Tenant is not reachable from another tenant's restore. This is enforced by configuration rather than by a runtime check: every Physical Tenant must resolve to a distinct backup store location, and Camunda fails startup if two tenants resolve to the same one. See [storage isolation](/self-managed/concepts/physical-tenants/storage-isolation.md). Before returning a restored tenant to normal traffic, confirm through tenant-scoped topology that its partitions are healthy, that the expected process definitions, instances, variables, and history are present, and that exporting has resumed. ## Validating a Restore API request without applying it Both endpoints accept the `dryRun` query parameter. With `dryRun=true`, the request is validated and the resulting plan is returned, but nothing is applied to the cluster. Use this to check a backup selection before the downtime window starts: ```bash curl -X POST "${ORCHESTRATION_CLUSTER_API}/restore?dryRun=true" \ -H 'Content-Type: application/json' \ -d '{ "backupIds": [1748937221] }' ``` A dry run of a restore covers the same validation as the real request. It rejects invalid parameter combinations, checks that a completed backup exists for every partition, and, for an RDBMS time range or an empty request body, resolves the restore point from the backup metadata. A request that passes the dry run is accepted as a real request as long as the cluster and the backup store do not change in between. The dry run does not report which backups it resolved. The response only contains the `changeId` and the planned operations, in the same shape as a real request, so the concrete backup ID per partition is not part of it. To confirm the selection, list the available backups with the [Zeebe backup management API](../zeebe-backup-and-restore.md#list-backups-api) before the restore, or pass explicit `backupIds` instead of relying on automatic resolution. ## Handling a failed Restore API operation ### Restoring a backup with fewer partitions --- ## Restore a backup with the Restore Application (RDBMS) Restore Zeebe partition data with the legacy Restore Application, a standalone app that runs on each broker node while all Camunda components are stopped, when using a relational database management system (RDBMS) as secondary storage. This page is part of the RDBMS [restore procedure](./restore.md). With Camunda 8.10 and later, you can use the [Restore API](./restore-api.md) instead, which does not require restarting the brokers. To compare the two, see [choosing a restore approach](./restore.md#choosing-a-restore-approach). After you ensure all [prerequisites](#prerequisites) are met, the procedure consists of the following steps: 1. [Stop all Camunda components](#stop-all-camunda-components). 2. [Restore the RDBMS](#restore-rdbms) using your database vendor's native tools. 3. [Restore Zeebe](#restore-zeebe) from its primary storage backup using one of the [restore options](#restore-options). 4. [Start all Camunda 8 components](#start-all-camunda-8-components). ## Prerequisites The following prerequisites are required before you can restore a backup: | Prerequisite | Description | | :--------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Camunda version | Backups can be restored using the same Camunda version they were created with, or up to one minor version newer. For example, a backup taken with 8.9.x can be restored with 8.9.x or 8.10.x. | | Backup available | At least one Zeebe primary storage backup is available in the configured blob store. See [Create a backup](./backup.md). | | Backup storage | Zeebe is configured with the same backup storage as outlined in the [prerequisites](./backup.md#prerequisites). | ## 1. Stop all Camunda components {#stop-all-camunda-components} It is critical that no Camunda components (Zeebe, Operate, Tasklist, Optimize, Connectors) are running during the restore. Running components may propagate an incorrect cluster configuration, potentially disrupting cluster communication and data consistency. ## 2. Restore the RDBMS {#restore-rdbms} Restore the RDBMS from its backup using your database vendor's native tools. The restored database must contain the entire Camunda schema. Skip this step if the RDBMS was already restored another way, for example as part of a wider disaster recovery procedure. Complete this step before you restore Zeebe. Each restore option below reads the exporter position from the restored RDBMS to determine which primary storage backup to restore from, so the RDBMS must already be in its target state. ## 3. Restore Zeebe from its primary storage backup {#restore-zeebe} Camunda provides a standalone restore application that must be run on each node where a Zeebe Broker will be running. This is a Spring Boot application similar to the broker and can run using the binary provided as part of the distribution. The app can be configured the same way a broker is configured — via environment variables or using the configuration file located in `config/application.yaml`. :::warning Persistent volumes or disks must not contain any pre-existing data before restoring Zeebe. If data exists from a previous deployment, it must be cleared first. On physical tenant enabled environments, only the data directory for the specific tenant must be cleared. ::: :::warning When restoring, provide the same configuration (node id, data directory, cluster size, and replication count) as the broker that will be running on this node. The partition count **must be the same** as in the backup. The number of partitions backed up is also visible via the [backup management API](../zeebe-backup-and-restore.md#list-backups-api). If brokers were dynamically scaled between backup and restore, this is not an issue — as long as the partition count remains unchanged. ::: ### Restore options There are four restore options. In all cases, the restore app reads the exporter position from the restored RDBMS to ensure consistency between primary and secondary storage. :::note `--backupId` is mutually exclusive with `--from`/`--to`. Specifying both will result in error. ::: **This is the recommended restore option.** No additional parameters are required — the restore application automatically determines the best backup to use. The restore app reads the exporter position from the restored RDBMS for each partition and identifies the most recent backup taken before that position. It then applies all subsequent backups in the range, restoring up to the latest available backup. ```yaml orchestration: enabled: true env: - name: SPRING_PROFILES_ACTIVE value: "restore" - name: ZEEBE_RESTORE value: "true" - name: CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_STORE value: "S3" # or GCS, AZURE, FILESYSTEM # Rest of the backup store configuration (bucket, region, etc.) - name: CAMUNDA_DATA_SECONDARY_STORAGE_TYPE value: "rdbms" # Rest of the RDBMS configuration (URL, username, password) connectors: enabled: false optimize: enabled: false ``` ```bash # Ensure RDBMS configuration is set (URL, credentials, etc.) export CAMUNDA_DATA_SECONDARY_STORAGE_TYPE=rdbms export CAMUNDA_DATA_SECONDARY_STORAGE_RDBMS_URL="jdbc:postgresql://localhost:5432/camunda" # ... other RDBMS config # Ensure backup store is configured export CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_STORE=S3 # ... other store config mkdir -p camunda tar -xzf camunda-zeebe-X.Y.Z.tar.gz --strip-components=1 -C camunda/ ./camunda/bin/restore ``` Restore Zeebe to a specific point in time using `--to`. The restore app finds the closest backup to the provided timestamp. The configured [checkpoint interval](./backup.md#checkpoint-interval) determines how fine-grained the restore points are. Use the [backup state actuator](../zeebe-backup-and-restore.md#request-runtime-state) to inspect available backup ranges. ```yaml orchestration: enabled: true env: - name: SPRING_PROFILES_ACTIVE value: "restore" - name: ZEEBE_RESTORE value: "true" - name: ZEEBE_RESTORE_TO_TIMESTAMP value: "2026-01-10T14:00:00Z" - name: CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_STORE value: "S3" # Rest of the backup store configuration - name: CAMUNDA_DATA_SECONDARY_STORAGE_TYPE value: "rdbms" # Rest of the secondary storage configuration connectors: enabled: false optimize: enabled: false ``` ```bash export ZEEBE_RESTORE_TO_TIMESTAMP=2026-01-10T14:00:00Z mkdir -p camunda tar -xzf camunda-zeebe-X.Y.Z.tar.gz --strip-components=1 -C camunda/ ./camunda/bin/restore --to="${ZEEBE_RESTORE_TO_TIMESTAMP}" ``` :::warning The `--to` timestamp must not be before the restored state of the RDBMS. If it is, the secondary storage would be ahead of the primary storage and the restore will fail. ::: Constrain the restore to a specific backup range by specifying both `--from` and `--to`. This is useful if automatic matching did not work. The restore app finds a backup range whose start is at or before `--from` and whose end is at or after `--to`, then restores to the checkpoint closest to `--to`. ```yaml orchestration: enabled: true env: - name: SPRING_PROFILES_ACTIVE value: "restore" - name: ZEEBE_RESTORE value: "true" - name: ZEEBE_RESTORE_FROM_TIMESTAMP value: "2026-01-10T13:00:00Z" - name: ZEEBE_RESTORE_TO_TIMESTAMP value: "2026-01-10T14:00:00Z" - name: CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_STORE value: "S3" # Rest of the backup store configuration - name: CAMUNDA_DATA_SECONDARY_STORAGE_TYPE value: "rdbms" # Rest of the secondary storage configuration connectors: enabled: false optimize: enabled: false ``` ```bash export ZEEBE_RESTORE_FROM_TIMESTAMP=2026-01-10T13:00:00Z export ZEEBE_RESTORE_TO_TIMESTAMP=2026-01-10T14:00:00Z mkdir -p camunda tar -xzf camunda-zeebe-X.Y.Z.tar.gz --strip-components=1 -C camunda/ ./camunda/bin/restore --from="${ZEEBE_RESTORE_FROM_TIMESTAMP}" --to="${ZEEBE_RESTORE_TO_TIMESTAMP}" ``` :::warning The `--to` timestamp must not be before the restored state of the RDBMS. If it is, the secondary storage would be ahead of the primary storage and the restore will fail. ::: Restore from one or more specific backup IDs directly. Multiple IDs can be provided as a comma-separated list. This is an escape hatch for situations where the automatic matching does not work. Use at your own risk — you are responsible for ensuring that the backups are compatible with the restored RDBMS state. ```yaml orchestration: enabled: true env: - name: SPRING_PROFILES_ACTIVE value: "restore" - name: ZEEBE_RESTORE value: "true" - name: CAMUNDA_DATA_PRIMARYSTORAGE_BACKUP_STORE value: "S3" # Rest of the backup store configuration - name: CAMUNDA_DATA_SECONDARY_STORAGE_TYPE value: "rdbms" # Rest of the secondary storage configuration connectors: enabled: false optimize: enabled: false ``` With the backup ID passed as a command-line argument: ```yaml orchestration: command: - "/usr/local/camunda/bin/restore" - "--backupId=1772001869309,1772001899400" ``` ```bash mkdir -p camunda tar -xzf camunda-zeebe-X.Y.Z.tar.gz --strip-components=1 -C camunda/ ./camunda/bin/restore --backupId=1772001869309,1772001899400 ``` :::caution When using backup ID restore, the RDBMS exporter position is not consulted. You must ensure that the provided backups are compatible with the restored RDBMS state. ::: ### Kubernetes-specific behavior When restoring in Kubernetes using the official [Camunda Helm chart](/self-managed/deployment/helm/install/quick-install.md), there are specific behaviors to be aware of. :::note Alternative startup override An alternative approach to overwriting the startup behavior to restore the partitions: ```yaml orchestration: enabled: true command: - "/usr/local/camunda/bin/restore" env: - name: SPRING_PROFILES_ACTIVE value: "restore" # all the envs related to the backup store as above ``` ::: ### Restoring a cluster with multiple Physical Tenants ```yaml orchestration: env: - name: SPRING_PROFILES_ACTIVE value: "restore" - name: ZEEBE_RESTORE value: "true" - name: ZEEBE_RESTORE_ALL_TENANTS value: "true" - name: ZEEBE_RESTORE_FROM_TIMESTAMP value: "" - name: ZEEBE_RESTORE_TO_TIMESTAMP value: "" extraConfiguration: - file: restore-overrides.yaml content: | override: tenanta: from: "" to: "" tenantb: backupId: [32] ``` ```bash ./camunda/bin/restore \ --allTenants \ --backupId=1748937221 \ --override.tenanta.from= --override.tenanta.to= \ --override.tenantb.backupId=32 ``` ### Restore success or failure If restore was successful, the app exits with the log message `Successfully restored broker from backup`. However, the restore will fail if: - There is no valid backup matching the secondary storage (the exporter position exceeds all available backups). - There is no valid backup within the specified time range. - The backup store is not configured correctly. - The configured data directory is not empty. - There is a gap in the backup range needed for restore (missing backups between the range start and the required checkpoint). - The exporter position in the RDBMS is missing for one or more partitions (when using RDBMS-aware restore). - Due to any other unexpected errors. If the restore fails, you can re-run the application after fixing the root cause. #### Data directory is not empty ## 4. Start all Camunda 8 components {#start-all-camunda-8-components} After both primary and secondary storage are restored, start all Camunda components. Ensure all components are configured to use the restored database instance and that the configuration matches the original deployment. :::note After starting the components, monitor the logs for any errors or warnings. Components will reconcile their state with the restored data, which may take some time depending on the size of the data. When using RDBMS-aware or time range restore, Zeebe re-exports events from the backup's checkpoint position up to its current state, bringing the RDBMS up to date. ::: --- ## Restore a backup (RDBMS) Restore a previous backup of your Camunda 8 Self-Managed Orchestration cluster components (Zeebe, Operate, Tasklist, and Admin) when using a relational database management system (RDBMS) as secondary storage. ## Choosing a restore approach Restore the Zeebe partitions with one of two approaches. In both, you restore the RDBMS with your database vendor's native tools, and Camunda aligns the Zeebe restore point with it. :::tip This procedure is the recovery step of [Cold Recovery](../../../concepts/multi-region/cold-recovery.md) when restoring into a secondary region after primary-region loss. ::: ## How RDBMS restore works As described in the [architecture overview](./backup.md#architecture-overview), backups involve two independent systems: **primary storage backups** (Zeebe's log stream and snapshots in a blob store) and the **secondary storage backup** (the RDBMS). During restore, Zeebe reads the **exporter position** from the restored RDBMS — the last log stream position that was successfully exported — and uses it to determine which primary storage backup, or backups, to restore from. This ensures that Zeebe's state is at least as advanced as what the RDBMS contains. After restart, Zeebe re-exports any events between the RDBMS position and its restored checkpoint position, bringing the secondary storage up to date. --- ## Web applications backup management API :::tip From Camunda 8.10, you can also use the Orchestration Cluster REST API to create, query, and manage history backups for Operate and Tasklist. See the [Orchestration Cluster REST API reference](/apis-tools/orchestration-cluster-api-rest/specifications/take-history-backup.api.mdx). ::: :::warning breaking changes As of the Camunda 8.8 release, the `/actuator` endpoints for backups have been moved to `/actuator/backupHistory`. The previous `/actuator/backups` endpoint is still active only if the applications are deployed standalone (each application is running in its own process). ::: :::note This page refers to the components Operate and Tasklist as "web applications". Optimize is not backed up as part of this process. Optimize is a dedicated application with its own backup system. Please see the [documentation for Optimize](./optimize-backup.md) to perform a backup. ::: ## About this API The Camunda web applications store their data over multiple indices in Elasticsearch. A backup of web application data includes several Elasticsearch snapshots containing sets of different indices. Each backup is identified by a `backupId`. For example, a backup with an ID of `123` may contain the following Elasticsearch snapshots: ``` camunda_webapps_123_8.8.0_part_1_of_6 camunda_webapps_123_8.8.0_part_2_of_6 camunda_webapps_123_8.8.0_part_3_of_6 camunda_webapps_123_8.8.0_part_4_of_6 camunda_webapps_123_8.8.0_part_5_of_6 camunda_webapps_123_8.8.0_part_6_of_6 ``` All web applications provide the same API to perform a backup and manage backups (list, check state, delete). Restore a backup using the standard Elasticsearch API. :::note The backup API can be reached via the Actuator management port, which default defaults to port 9600. ::: :::warning Usage of this API requires the backup store to be configured with the **same** repository name. - [Operate configuration](/self-managed/components/orchestration-cluster/operate/operate-configuration.md#backups) - [Tasklist configuration](/self-managed/components/orchestration-cluster/tasklist/tasklist-configuration.md#backups) Additionally, it requires the same backup store to be configured on your chosen datastore. - [Elasticsearch snapshot repository](https://www.elastic.co/docs/deploy-manage/tools/snapshot-and-restore/manage-snapshot-repositories) - [OpenSearch snapshot repository](https://docs.opensearch.org/docs/latest/tuning-your-cluster/availability-and-recovery/snapshots/snapshot-restore/) Web applications must have the right to take snapshots. ::: ## Create backup API During backup creation, web applications can continue running. To create the backup, call the following endpoint: ``` POST actuator/backupHistory { "backupId": } ``` :::note For backward compatibility, the endpoint `actuator/backups` is available if the component is running standalone. ::: Response: | Code | Description | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | 200 OK | Backup was successfully started, snapshots will be created asynchronously. List of snapshots is returned in the response body (see example below). This list must be persisted together with the backup ID to be able to restore it later. | | 400 Bad Request | In case something is wrong with `backupId`, e.g. the same backup ID already exists. | | 500 Server Error | All other errors, e.g. ES returned error response when attempting to create a snapshot. | | 502 Bad Gateway | Elasticsearch is not accessible, the request can be retried when it is back. | Example request: ```shell curl --request POST 'http://localhost:9600/actuator/backupHistory' \ -H 'Content-Type: application/json' \ -d '{ "backupId": 123 }' ``` Example response: ```json { "scheduledSnapshots": [ "camunda_webapps_123_8.8.0_part_1_of_6", "camunda_webapps_123_8.8.0_part_2_of_6", "camunda_webapps_123_8.8.0_part_3_of_6", "camunda_webapps_123_8.8.0_part_4_of_6", "camunda_webapps_123_8.8.0_part_5_of_6", "camunda_webapps_123_8.8.0_part_6_of_6" ] } ``` ## Get backup state API As a backup is created asynchronously, call the following endpoint to check the state of the backup: ``` GET actuator/backupHistory/{backupId} ``` :::note For backward compatibility, the endpoint `actuator/backups` is available if the component is running standalone. ::: Response: | Code | Description | | ---------------- | --------------------------------------------------------------------------------------- | | 200 OK | Backup state could be determined and is returned in the response body. | | 404 Not Found | Backup with given ID does not exist. | | 500 Server Error | All other errors, e.g. ES returned error response when attempting to execute the query. | | 502 Bad Gateway | Elasticsearch is not accessible, the request can be retried when it is back. | For example, the request could look like this: ```shell curl 'http://localhost:9600/actuator/backupHistory/123' ``` Example response: ```json { "backupId": 123, "state": "COMPLETED", "failureReason": null, "details": [ //here goes the list of all Elasticsearch snapshots included in the backup { "snapshotName": "camunda_webapps_123_8.8.0_part_1_of_6", "state": "SUCCESS", "startTime": "2023-01-01T10:10:10.100+0000", "failures": [] }, <..> ] } ``` Possible **states** of the backup: - `COMPLETED`: Backup can be used for restoring the data. - `IN_PROGRESS`: Wait until the backup completes to use it for restore. - `FAILED`: Something went wrong when creating this backup. To find out the exact problem, use the [Elasticsearch get snapshot status API](https://www.elastic.co/guide/en/elasticsearch/reference/current/get-snapshot-status-api.html) for each of the snapshots included in the given backup. - `INCOMPATIBLE`: Backup is incompatible with the current Elasticsearch version. - `INCOMPLETE`: Backup is incomplete (e.g. when backup process was interrupted). **State** of the individual snapshot is a copy of [Elasticsearch state](https://www.elastic.co/guide/en/elasticsearch/reference/7.17/get-snapshot-api.html#get-snapshot-api-response-state). ## Get backups list API To get the list of existing backups, the following endpoint can be used: ``` GET actuator/backupHistory ``` :::note For backward compatibility, the endpoint `actuator/backups` is available if the component is running standalone. ::: ### Query parameters The following optional query parameters can be used to optimize the response: | Parameter | Description | | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `verbose` | Controls the level of detail in the response. When set to `false`, returns a streamlined response with essential information. Default: `true`. | | `pattern` | Filters backups by ID pattern. For example, `pattern=20250401*` returns only backups with IDs starting with `20250401`. | Response: | Code | Description | | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | 200 OK | Backup list could be determined and is returned in the response body. Can be an empty response in case no backups were created yet. | | 404 Not Found | Backup repository is not configured. | | 500 Server Error | All other errors, e.g. ES returned error response when attempting to execute the query. | | 502 Bad Gateway | Elasticsearch is not accessible, the request can be retried when it is back. | Example requests: ```shell # Get all backups curl 'http://localhost:9600/actuator/backupHistory' # Get backups with streamlined response curl 'http://localhost:9600/actuator/backupHistory?verbose=false' # Get backups matching a specific pattern curl 'http://localhost:9600/actuator/backupHistory?pattern=20250401*' # Combine parameters for optimized filtered results curl 'http://localhost:9600/actuator/backupHistory?verbose=false&pattern=20250401*' ``` Response will contain JSON with array of objects representing state of each backup (see [get backup state API endpoint](#get-backup-state-api)). ## Delete backup API To delete all the Elasticsearch snapshots associated with the specific backup id, the following endpoint may be used: ``` DELETE actuator/backupHistory/123 ``` :::note For backward compatibility, the endpoint `actuator/backups` is available if the component is running standalone. ::: Response: | Code | Description | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------- | | 204 No Content | All commands to delete corresponding ELS snapshots were successfully sent to ELS. ELS will continue deletion asynchronously. | | 404 Not Found | Not a single snapshot corresponding to given ID exist. | | 500 Server Error | All other errors, e.g. ES returned error response when attempting to execute the query. | | 502 Bad Gateway | Elasticsearch is not accessible, the request can be retried when it is back. | --- ## Zeebe backup management API :::tip From Camunda 8.10, you can also use the Orchestration Cluster REST API to create, query, and manage runtime backups. See the [Orchestration Cluster REST API reference](/apis-tools/orchestration-cluster-api-rest/specifications/take-runtime-backup.api.mdx). ::: :::warning breaking changes As of the Camunda 8.8 release, the `/actuator` endpoints for backups have been moved to `/actuator/backupRuntime`. The previous `/actuator/backups` endpoint is still active only if the applications are deployed standalone (each application is running in its own process). ::: Back up a running Zeebe cluster using the Backup Management API. ## About this API A backup of a Zeebe cluster comprises a consistent snapshot of all partitions. The backup is taken asynchronously in the background while Zeebe is processing. Thus, backups can be taken with minimal impact on typical processing. Backups can be used to restore a cluster in case of failures that lead to full data loss or data corruption, and form the basis of [Cold Recovery](../../concepts/multi-region/cold-recovery.md) for cross-region disaster recovery. Zeebe provides a REST API to create, query, and manage backups. The backup management API is a custom endpoint `backupRuntime`, available via [Spring Boot Actuator](https://docs.spring.io/spring-boot/docs/2.7.x/reference/htmlsingle/#actuator.endpoints). It is accessible via the management port of the Zeebe Gateway. The API documentation is also available as an [OpenAPI specification](https://github.com/camunda/camunda/blob/main/dist/src/main/resources/api/backup-management-api.yaml). :::warning Usage of this API requires the backup store to be configured for the component. - [Zeebe configuration](/self-managed/components/orchestration-cluster/zeebe/configuration/broker.md#camundadataprimary-storagebackup) ::: To use the backup feature in Zeebe, you must choose which external storage system you will use. Make sure to set the same configuration on all brokers in your cluster. Zeebe supports [S3](/self-managed/components/orchestration-cluster/zeebe/configuration/broker.md#camundadataprimary-storagebackups3), [Google Cloud Storage (GCS)](/self-managed/components/orchestration-cluster/zeebe/configuration/broker.md#camundadataprimary-storagebackupgcs), [Azure](/self-managed/components/orchestration-cluster/zeebe/configuration/broker.md#camundadataprimary-storagebackupazure), and [local filesystem](/self-managed/components/orchestration-cluster/zeebe/configuration/broker.md#camundadataprimary-storagebackupfilesystem) for external storage. :::caution Backups created with one store are not available in or restorable from another store. This is especially relevant if you were using GCS through the S3 compatibility mode and want to switch to the new built-in support for GCS now. Even when the underlying storage bucket is the same, backups from one are not compatible with the other. ::: ## Create backup API The following request can be used to start a backup. ### Request ``` POST actuator/backupRuntime { "backupId": } ``` A `backupId` is an integer and must be greater than the ID of previous backups that are completed, failed, or deleted. Zeebe does not take two backups with the same IDs. If a backup fails, a new `backupId` must be provided to trigger a new backup. The `backupId` cannot be reused, even if the backup corresponding to the backup ID is deleted. :::note When continuous backups, the backup scheduler, or the checkpoint scheduler are enabled, the `backupId` parameter must be omitted. Instead, the ID is generated by the cluster. :::
Example request ```shell curl --request POST 'http://localhost:9600/actuator/backupRuntime' \ -H 'Content-Type: application/json' \ -d '{ "backupId": 100 }' ```
### Response | Code | Description | | ---------------- | ------------------------------------------------------------------------------------------------------------------------ | | 202 Accepted | A backup has been successfully scheduled. To determine if the backup process was completed, refer to the GET API. | | 400 Bad Request | Indicates issues with the request, for example when the `backupId` is not valid or backup is not enabled on the cluster. | | 409 Conflict | Indicates a backup with the same `backupId` or a higher ID already exists. | | 500 Server Error | All other errors. Refer to the returned error message for more details. | | 502 Bad Gateway | Zeebe has encountered issues while communicating with different brokers. | | 504 Timeout | Zeebe failed to process the request within a predetermined timeout. |
Example response body with 202 Accepted ```json { "backupId": 100, "message": "A backup with id 100 has been scheduled. Use GET actuator/backups/100 to monitor the status." } ```
## Get backup info API Information about a specific backup can be retrieved using the following request: ### Request ``` GET actuator/backupRuntime/{backupId} ```
Example request ```shell curl --request GET 'http://localhost:9600/actuator/backupRuntime/100' ```
### Response | Code | Description | | ---------------- | ------------------------------------------------------------------------------------------ | | 200 OK | Backup state could be determined and is returned in the response body (see example below). | | 400 Bad Request | There is an issue with the request. Refer to the returned error message for details. | | 404 Not Found | A backup with that ID does not exist. | | 500 Server Error | All other errors. Refer to the returned error message for more details. | | 502 Bad Gateway | Zeebe has encountered issues while communicating with different brokers. | | 504 Timeout | Zeebe failed to process the request within a pre-determined timeout. | When the response is 200 OK, the response body consists of a JSON object describing the state of the backup. - `backupId`: ID in the request. - `state`: Gives the overall status of the backup. The state can be one of the following: - `COMPLETED` if all partitions have completed the backup. - `FAILED` if at least one partition has failed. In this case, `failureReason` contains a string describing the reason for failure. - `INCOMPLETE` if at least one partition's backup does not exist. - `IN_PROGRESS` if at least one partition's backup is in progress. - `DELETED` if at least one partition's backup is deleted. - `details`: Gives the state of each partition's backup. - `failureReason`: The reason for failure if the state is `FAILED`.
Example response body with 200 OK ```json { "backupId": 100, "details": [ { "brokerVersion": "8.2.0-SNAPSHOT", "checkpointPosition": 5, "createdAt": "2022-12-08T13:00:55.344276672Z", "lastUpdatedAt": "2022-12-08T13:00:55.805351556Z", "partitionId": 1, "snapshotId": "2-1-3-2", "state": "COMPLETED" }, { "brokerVersion": "8.2.0-SNAPSHOT", "checkpointPosition": 7, "createdAt": "2022-12-08T13:00:55.370965069Z", "lastUpdatedAt": "2022-12-08T13:00:55.84756566Z", "partitionId": 2, "snapshotId": "3-1-5-3", "state": "COMPLETED" } ], "state": "COMPLETED" } ```
## List backups API Information about all backups can be retrieved using the following request: ### Request to list all backups ``` GET actuator/backupRuntime ```
Example request ```shell curl --request GET 'http://localhost:9600/actuator/backupRuntime' ```
### Request to list backups matching a prefix The list of backups can be filtered by specifying a backup ID prefix: ``` GET actuator/backupRuntime/{backupIdPrefix} ``` The backup ID prefix must end with `*`, for example `10*` will match all backups with IDs starting with `10`.
Example request ```shell curl --request GET 'http://localhost:9600/actuator/backupRuntime/10*' ```
### Response | Code | Description | | ---------------- | ------------------------------------------------------------------------------------------ | | 200 OK | Backup state could be determined and is returned in the response body (see example below). | | 400 Bad Request | There is an issue with the request. Refer to the returned error message for details. | | 500 Server Error | All other errors. Refer to the returned error message for more details. | | 502 Bad Gateway | Zeebe has encountered issues while communicating with different brokers. | | 504 Timeout | Zeebe failed to process the request within a predetermined timeout. | When the response is 200 OK, the response body consists of a JSON object with a list of backup info. See [get backup info API response](#response-1) for the description of each field.
Example response body with 200 OK ```json [ { "backupId": 100, "details": [ { "brokerVersion": "8.2.0-SNAPSHOT", "createdAt": "2022-12-08T13:00:55.344276672Z", "partitionId": 1, "state": "COMPLETED" }, { "brokerVersion": "8.2.0-SNAPSHOT", "createdAt": "2022-12-08T13:00:55.370965069Z", "partitionId": 2, "state": "COMPLETED" } ], "state": "COMPLETED" }, { "backupId": 200, "details": [ { "brokerVersion": "8.2.0-SNAPSHOT", "createdAt": "2022-12-08T13:01:15.27750375Z", "partitionId": 1, "state": "COMPLETED" }, { "brokerVersion": "8.2.0-SNAPSHOT", "createdAt": "2022-12-08T13:01:15.279995106Z", "partitionId": 2, "state": "COMPLETED" } ], "state": "COMPLETED" } ] ```
## Delete backup API A backup can be deleted using the following request: ### Request ``` DELETE actuator/backupRuntime/{backupId} ```
Example request ```shell curl --request DELETE 'http://localhost:9600/actuator/backupRuntime/100' ```
### Response | Code | Description | | ---------------- | ------------------------------------------------------------------------------------ | | 204 No Content | The backup has been deleted. | | 400 Bad Request | There is an issue with the request. Refer to the returned error message for details. | | 500 Server Error | All other errors. Refer to the returned error message for more details. | | 502 Bad Gateway | Zeebe has encountered issues while communicating with different brokers. | | 504 Timeout | Zeebe failed to process the request within a predetermined timeout. | ## Request runtime state Acquire information regarding backups from the runtime state. ### Request ``` GET actuator/backupRuntime/state ```
Example request ```shell curl --request GET 'http://localhost:9600/actuator/backupRuntime/state' ```
### Response This endpoint returns the current runtime state of backups and checkpoints across all partitions. #### HTTP status codes | Code | Description | | ------------------------- | ----------------------------------------------------------------------------------------------------------- | | 200 OK | The backup state was successfully determined and is returned in the response body. | | 400 Bad Request | The request is invalid. Refer to the returned error message for details. | | 500 Internal Server Error | An unexpected error occurred while processing the request. Refer to the returned error message for details. | | 502 Bad Gateway | Zeebe encountered issues while communicating with other brokers. | | 504 Gateway Timeout | Zeebe was unable to process the request within the configured timeout. | #### Response body When the response status is `200 OK`, the response body contains a JSON object describing the current backup and checkpoint state. ##### `checkpointStates` Latest checkpoint information per partition. - `checkpointId`: Identifier of the checkpoint. - `partitionId`: Identifier of the partition. - `checkpointType`: Type of the checkpoint. One of `MARKER`, `SCHEDULED_BACKUP`, or `MANUAL_BACKUP`. - `checkpointPosition`: Log stream position of the checkpoint record. - `checkpointTimestamp`: Timestamp when the checkpoint was created. ##### `backupStates` Latest backup information per partition. - `checkpointId`: Identifier of the associated checkpoint. - `partitionId`: Identifier of the partition. - `checkpointType`: Type of the checkpoint. One of `MARKER`, `SCHEDULED_BACKUP`, or `MANUAL_BACKUP`. - `checkpointPosition`: Log stream position of the checkpoint record. - `checkpointTimestamp`: Timestamp when the checkpoint was created. - `firstLogPosition`: First available log stream position included in this backup. ##### `ranges` List of active continuous backup ranges per partition. - `partitionId`: Identifier of the partition. - `start`: First backup in the continuous backup range. - `checkpointId`: Identifier of the checkpoint. - `checkpointType`: Type of the checkpoint. - `checkpointPosition`: Log stream position of the checkpoint record. - `checkpointTimestamp`: Timestamp when the checkpoint was created. - `firstLogPosition`: First available log stream position included in this backup. - `end`: Last backup in the continuous backup range. - `checkpointId`: Identifier of the checkpoint. - `checkpointType`: Type of the checkpoint. - `checkpointPosition`: Log stream position of the checkpoint record. - `checkpointTimestamp`: Timestamp when the checkpoint was created. - `firstLogPosition`: First available log stream position included in this backup.
Example response body with 200 OK ```json { "checkpointStates": [ { "checkpointId": 1772001869309, "checkpointType": "SCHEDULED_BACKUP", "partitionId": 1, "checkpointPosition": 580, "checkpointTimestamp": "2026-02-25T06:44:29.309Z" }, { "checkpointId": 1772001869309, "checkpointType": "SCHEDULED_BACKUP", "partitionId": 2, "checkpointPosition": 135, "checkpointTimestamp": "2026-02-25T06:44:29.308Z" } ], "backupStates": [ { "checkpointId": 1772001869309, "checkpointType": "SCHEDULED_BACKUP", "partitionId": 1, "checkpointPosition": 580, "firstLogPosition": 1, "checkpointTimestamp": "2026-02-25T06:44:29.391Z" }, { "checkpointId": 1772001869309, "checkpointType": "SCHEDULED_BACKUP", "partitionId": 2, "checkpointPosition": 135, "firstLogPosition": 1, "checkpointTimestamp": "2026-02-25T06:44:29.394Z" } ], "ranges": [ { "partitionId": 1, "start": { "checkpointId": 1772001838336, "checkpointType": "SCHEDULED_BACKUP", "checkpointPosition": 580, "firstLogPosition": 1, "checkpointTimestamp": "2026-02-25T06:44:29.391Z" }, "end": { "checkpointId": 1772001869309, "checkpointType": "SCHEDULED_BACKUP", "checkpointPosition": 580, "firstLogPosition": 1, "checkpointTimestamp": "2026-02-25T06:44:29.391Z" } }, { "partitionId": 2, "start": { "checkpointId": 1772001838336, "checkpointType": "SCHEDULED_BACKUP", "checkpointPosition": 135, "firstLogPosition": 1, "checkpointTimestamp": "2026-02-25T06:44:29.394Z" }, "end": { "checkpointId": 1772001869309, "checkpointType": "SCHEDULED_BACKUP", "checkpointPosition": 135, "firstLogPosition": 1, "checkpointTimestamp": "2026-02-25T06:44:29.394Z" } } ] } ```
## Sync runtime state Force a synchronization of the backup metadata stored in the blob store. This returns the updated state in the same format as the [GET state](#request-runtime-state) endpoint. ### Request ``` POST actuator/backupRuntime/state/sync ```
Example request ```shell curl --request POST 'http://localhost:9600/actuator/backupRuntime/state/sync' ```
### Response | Code | Description | | ------------------------- | ----------------------------------------------------------------------------------------------------------- | | 200 OK | The backup state was successfully synced and is returned in the response body. | | 500 Internal Server Error | An unexpected error occurred while processing the request. Refer to the returned error message for details. | | 502 Bad Gateway | Zeebe encountered issues while communicating with other brokers. | | 504 Gateway Timeout | Zeebe was unable to process the request within the configured timeout. | ## Delete runtime state Clear the backup runtime state (checkpoint state and range tracking). Ranges will be rebuilt from the next backup onward. :::caution This clears all tracked checkpoint and range state. Ranges will be rebuilt from the next backup onward, but previously tracked ranges are lost. This can be useful when switching backup stores (for example, from S3 to GCS) or after all backups have been lost from the store. ::: ### Request ``` DELETE actuator/backupRuntime/state ```
Example request ```shell curl --request DELETE 'http://localhost:9600/actuator/backupRuntime/state' ```
### Response | Code | Description | | ------------------------- | ----------------------------------------------------------------------------------------------------------- | | 204 No Content | The runtime state has been cleared. | | 500 Internal Server Error | An unexpected error occurred while processing the request. Refer to the returned error message for details. | | 502 Bad Gateway | Zeebe encountered issues while communicating with other brokers. | | 504 Gateway Timeout | Zeebe was unable to process the request within the configured timeout. | --- ## Camunda components flow control configuration When internal requests are processed faster than the rate at which they are exported, backlogs of unexported records can occur. Flow control slows the write rate of new records through both static write limits and optional dynamic throttling, and prevents the stream from building an excessive backlog of records not yet exported. For user commands, this will show up as increased [backpressure](/components/zeebe/technical-concepts/internal-processing.md#handling-backpressure), observed latency, and reduced throughput. [Internal processing](/components/zeebe/technical-concepts/internal-processing.md) slows down as the stream processor waits longer for processing results to be written. The export rate and backlog are measured across all exporters configured for a partition. A [custom exporter](/self-managed/concepts/exporters.md#performance-impact) with a slow `export()` method also reduces the export rate and can trigger throttling. Write rate limiting applies to all new records, including processing results, user commands, inter-partition messages, and scheduled tasks. ## Enable flow control The write rate can be set as a static limit, which defines the upper rate at which records can be written. The dynamic limit, known as **throttling,** adjusts the write rate based on the exporting rate and backlog, and can be applied alongside the static limit. A static write rate limit can prevent throughput peaks, and write rate throttling can keep the backlog stable by temporarily decreasing the static limit to keep the exporting backlog small. When configuring dynamic throttling, configuring a high static limit can help maintain a high write rate if the exporting can keep up. :::note These write limits are enabled by default in SaaS and disabled in Self-Managed. For most use cases, write rate limits can be enabled as needed if an issue arises. ::: Flow control is configured in the Orchestration Cluster `application.yaml` file under `camunda.processing.flow-control.write`. The default values are listed in the `flow-control` section of the [configuration defaults](https://github.com/camunda/camunda/blob/main/dist/src/main/config/defaults.yaml) and in the [processing configuration properties](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md). ```yaml camunda: processing: flow-control: write: enabled: false ramp-up: 0s limit: 0 throttle: enabled: false acceptable-backlog: 100000 minimum-limit: 100 resolution: 15s ``` If you run multiple [Physical Tenants](/self-managed/concepts/physical-tenants/index.md), use these properties rather than the legacy `zeebe.broker.flowControl.write.*` properties, which apply only to the default tenant. To set different limits for one tenant, override the same properties under `camunda.physical-tenants..processing.flow-control.write`. :::note The limit and in-flight count are calculated per partition. ::: | Field | Description | Default Value | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | | `ramp-up` | The time from startup during which the write limit is slowly increased until the configured limit. Useful for a warm-up period where a lower write rate is beneficial. This value is a duration, for example `10s`, and cannot be null nor negative. | `0s` | | `limit` | A static value to use as the write rate. This value cannot be null nor negative. | `0` | | `throttle` | If enabled, `throttle` will additionally limit the write rate based on the exporting backlog. The exporting backlog is the quantity of records that were written, but not yet exported. An excessive exporting backlog is usually due to a mismatch of the rate of processed and exported records, or to degraded exporting capability. The `throttling` algorithm used takes into account the ratio between the acceptable backlog and the actual backlog. If the acceptable backlog is at least twice as long as the real backlog, the current rate is set as the limit. If the ratio between the acceptable backlog and current backlog is less than two, the rate is calculated by multiplying the ratio with the current exporting rate, with `minimumLimit` as the floor and `limit` as the ceiling. The intention is to proportionally increase or decrease the rate according to the ratio of the acceptable backlog and the actual backlog. If the acceptable backlog is larger than the current backlog, the rate is increased, and if the acceptable backlog is smaller than the current backlog, the rate is decreased. If the processing speed continues to outpace the exporting speed, the current backlog should stabilize around the acceptable backlog size. | `enabled: false` | | `resolution` | The frequency with which `throttle` is adjusted, given as a duration. Adjusting this value sets the speed at which the processing rate can respond to changes. | `15s` | The exporting rate is the number of exported records per second, averaged out over the last five minutes. ## Configure temporary write limits The flow control endpoint can be used to adjust the flow control configuration temporarily, without having to reset your clusters. :::caution The flow control endpoint is intended as a temporary solution, and changes should be reverted after the issue is addressed. Permanent configuration changes should be made through the environment variables. Configuring flow control through the available endpoint does not preserve the configuration in the broker state. If the broker restarts, any leader partition in this broker will revert to the defined configuration in the environment variables. ::: ### Fetch current configuration The flow control API can be reached via the `/actuator` management port, which is 9600 by default. The configured context path does not apply to the management port. The following endpoint can be used to fetch the flow control configuration: ``` GET actuator/flowControl ``` With multiple [Physical Tenants](/self-managed/concepts/physical-tenants/index.md), add the `physicalTenant=` query parameter to fetch the configuration of one tenant. Without it, the response lists every tenant, keyed by tenant ID. #### Response | Code | Description | | ---------------- | ----------------------------------------------------------------------- | | 200 OK | The flow configuration was retrieved successfully. | | 400 Bad Request | Indicates issues with the request. | | 500 Server Error | All other errors. Refer to the returned error message for more details. | #### Example request ``` curl -X GET 'localhost:9600/actuator/flowControl' ``` #### Example response ```json { "1": { "requestLimiter": { "delegate": { "limit": 100, "minLimit": 1, "maxLimit": 1000, "backoffRatio": 0.9, "expectedRTT": 200000000 } }, "writeRateLimit": { "enabled": true, "limit": 4000, "rampUp": 0.0, "throttling": { "enabled": true, "acceptableBacklog": 100000, "minRate": 100, "resolution": 15.0 } } } } ``` :::note The `writeRateLimit` value can be null if it has not been defined yet. ::: ### Set a new configuration To set a new flow control configuration, make a `POST` request to the `actuator/flowControl` endpoint. This request will attempt to configure all partitions. With multiple [Physical Tenants](/self-managed/concepts/physical-tenants/index.md), add the `physicalTenant=` query parameter to configure only that tenant. Without it, the change applies to every tenant. Partitions might differ in configuration if, for example, a broker restarts and the leader partition reverts to the configuration defined in the environment variables. ``` POST actuator/flowControl ``` ```json { "write": { "rampUp": , "enabled": , "limit": , "throttling": { "enabled": , "acceptableBacklog": , "minimumLimit": , "resolution": } } } ``` #### Response | Code | Description | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | 200 OK | The flow configuration request was processed correctly. | | 400 Bad Request | Indicates issues with the request, for example, one of the fields contains an invalid type. | | 500 Server Error | All other errors. For example, when the values set do not conform to the imposed restriction (such as `minimumLimit` being higher than `limit`). Refer to the returned error message for more details. | #### Example request ```bash curl -X POST 'localhost:9600/actuator/flowControl' -H "Content-Type: application/json" --data '{ "write": { "rampUp": 0, "enabled": true, "limit": 2000, "throttling": { "enabled": true, "acceptableBacklog": 100000, "minimumLimit": 200, "resolution": 15 } } }' ``` ### Example response ```js { "1": { "requestLimiter": { "delegate": { "limit": 100, "minLimit": 1, "maxLimit": 1000, "backoffRatio": 0.9, "expectedRTT": 200000000 } }, "writeRateLimit": { "enabled": true, "limit": 4000, "rampUp": 0.0, "throttling": { "enabled": true, "acceptableBacklog": 100000, "minRate": 100, "resolution": 15.0 } } } } ``` :::note The first value in the response (`1` in the example) refers to the partition before the flow configuration is defined. ::: ## View write rate limits in Grafana ### Throttling Dynamic throttling, when actively acting on the current rate (and not only enabled), displays in Grafana with an underlying yellow bar for the period it was active. ![backpressure-throttling](img/backpressure-throttling.png) ### Exporting backlog The exporting backlog panel is found under the **Processing** row and displays the number of records not yet exported per partition. ![exporting-backlog](img/exporting-backlog.png) ### Exporting and write rate The **Measured exporting rate** and **Accepted writes by source** panels are found under the **Logstream** row. The first shows the number of records accepted by flow control per second, organized by partition and write source (for example, processing result, scheduled tasks, etc.). The second displays measured average exporting rate which may be used to throttle write rate. ![measured-exporting-rate](img/mesured-exporting-rate.png) ![accepted-by-writes-source](img/accepted-by-writes-source.png) #### Write rate limit The **Write rate limits** panel is under the **Logstream** row, and displays the current and maximum permissible write rate limit per partition. ![write-rate-limit](img/write-rate-limit.png) --- ## Camunda data purge The data purge feature allows you to delete all runtime and historical data from your cluster. This operation resets the cluster to an empty state while maintaining the original topology. The purge operation performs two main actions: 1. **Runtime Data Deletion**: Removes all live data from brokers, for example process definitions, instances, and jobs. 2. **Historical Data Purge**: Clears exported data from configured exporters The data purge feature can be used to: - Delete data between test runs and therefore enabling reuse of the same cluster for multiple tests. - Resetting development or staging environments to a clean state. ## Purge data You will need access to the Cluster API as described in the [Cluster scaling guide](/self-managed/components/orchestration-cluster/zeebe/operations/cluster-scaling.md) to perform the purge. :::danger The purge operation is irreversible. It will delete the runtime data in the cluster and the historical data in the exporters! Make sure to back up your data before proceeding. ::: ### Usage The purge operation is a cluster-wide, asynchronous operation. Since it is asynchronous, you first launch it by sending a `POST` request to [`/actuator/cluster/purge`](/apis-tools/management-api/specifications/cluster/purge-data-from-the-cluster.api.mdx), and then monitor by polling the topology via `/actuator/cluster` until it is finished. :::warning In a cluster running multiple [Physical Tenants](/self-managed/concepts/physical-tenants/index.md), a request without a `physicalTenant` parameter purges **every** Physical Tenant, not just the default one. This is the opposite of how unscoped requests behave on the REST `/v2/...` API, where an omitted tenant prefix resolves to the default tenant only. Add `?physicalTenant={physicalTenantId}` to purge a single tenant and leave the others untouched. ::: :::note This example relies on the [curl](https://curl.se/) and [jq](https://jqlang.org/) utilities. ::: ```sh changeId=$(curl -sL -X POST 'http://localhost:9600/actuator/cluster/purge' | jq '.changeId') lastChangeId=-1 while [ ! $changeId -eq $lastChangeId ]; do lastChangeId=$(curl -sL 'http://localhost:9600/actuator/cluster' | jq '.lastChange.id') [ $lastChangeId -ge $changeId ] && break echo "Awaiting last change ID ${lastChangeId} to be equal to purge change ID ${changeId}" sleep 1 done ``` :::note This example relies on code generated from [the cluster topology management API spec](/apis-tools/management-api/specifications/cluster/cluster-topology-management-api.info.mdx), bundled with the distribution. ::: ```java final String baseURL = "http://localhost:9600/actuator/cluster"; final URL monitorURI = URI.create(baseURL).toURL(); final URI purgeURI = URI.create(baseURL + "/purge"); final ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule()); try (final HttpClient client = HttpClient.newHttpClient()) { final HttpRequest purgeRequest = HttpRequest.newBuilder().uri(purgeURI).POST(HttpRequest.BodyPublishers.noBody()).build(); final HttpResponse purgeResponse = client.send(purgeRequest, BodyHandlers.ofInputStream()); final PlannedOperationsResponse purgePlan = objectMapper.readValue(purgeResponse.body(), PlannedOperationsResponse.class); final long purgeChangeId = purgePlan.getChangeId(); long lastChangeId = -1; while (purgeChangeId != lastChangeId) { final GetTopologyResponse topology = objectMapper.readValue(monitorURI, GetTopologyResponse.class); lastChangeId = topology.getLastChange().getId(); if (lastChangeId >= purgeChangeId) { break; } System.out.println( "Waiting until the last change ID " + lastChangeId + " is equal to the purge change ID " + purgeChangeId); Thread.sleep(1_000); } } ``` To know if your purge operation is finished, compare the change ID returned by launching it with the last change ID from the topology request. When the last change ID is greater than or equal to your purge operation's change ID, then purging is finished. ### 1. Send the purge request to the Zeebe Gateway To purge data from your cluster, send a `POST` request to the `/actuator/cluster/purge` endpoint: ```sh curl -X POST 'http://localhost:9600/actuator/cluster/purge' ``` The response is a [JSON object](/apis-tools/management-api/specifications/cluster/purge-data-from-the-cluster.api.mdx): ```json { changeId: currentTopology: [...] plannedChanges: [...] expectedTopology: [...] } ``` - `changeId`: The ID of the change initiated to purge the cluster. You can use it to monitor the progress of the purge operation. The ID typically increases, so new requests have a higher ID than previous requests. - `currentTopology`: A list of current brokers and the partition distribution. - `plannedChanges`: A sequence of operations that must run to complete the purge. - `expectedTopology`: The expected list of brokers and the partition distribution once the purge is completed. For the purge feature, the expected topology will be the same as the current topology.
Example response ```json { "changeId": 2, "currentTopology": [ { "id": 0, "state": "ACTIVE", "version": 0, "lastUpdatedAt": "0000-01-01T00:00:00Z", "partitions": [ { "id": 1, "state": "ACTIVE", "priority": 1, "config": { "exporting": { "exporters": [] } } } ] } ], "plannedChanges": [ { "operation": "PARTITION_LEAVE", "brokerId": 0, "partitionId": 1, "brokers": [] }, { "operation": "DELETE_HISTORY", "brokers": [] }, { "operation": "PARTITION_BOOTSTRAP", "brokerId": 0, "partitionId": 1, "priority": 1, "brokers": [] } ], "expectedTopology": [ { "id": 0, "state": "ACTIVE", "version": 4, "lastUpdatedAt": "2025-03-04T09:50:14.979435Z", "partitions": [ { "id": 1, "state": "ACTIVE", "priority": 1, "config": { "exporting": { "exporters": [] } } } ] } ] } ```
The purging is done asynchronously. ### 2. Monitor the progress of the purge operation The purge operation can take some time to complete, depending on the amount of data and the type of exporter. You can monitor the progress of the operation by sending a `GET` request to the `/actuator/cluster` endpoint: ```sh curl --request GET 'http://localhost:9600/actuator/cluster' ``` When the purge has completed, the `changeId` from the previous response will be marked as completed: ```json { "version": 3, "brokers": [ { "id": 0, "state": "ACTIVE", "version": 4, "lastUpdatedAt": "2025-03-04T09:50:15.534347Z", "partitions": [ { "id": 1, "state": "ACTIVE", "priority": 1, "config": { "exporting": { "exporters": [] } } } ] } ], "lastChange": { "id": 2, "status": "COMPLETED", "startedAt": "2025-03-04T09:50:14.980254Z", "completedAt": "2025-03-04T09:50:15.534398Z" } } ``` ## Considerations ### 1. DryRun You can set the `dryRun` parameter to `true` to simulate the purge operation without deleting any data. This can be useful to understand the impact of the operation before proceeding. ```sh curl -X POST 'http://localhost:9600/actuator/cluster/purge?dryRun=true' ``` ### 2. Don't perform the purge operation during other cluster operations You cannot perform the purge operation if another cluster operation is already in progress (for example, scaling). Similarly, you cannot perform other cluster operations while the purge operation is in progress. ## Troubleshooting The data purge operation is idempotent, meaning you can retry the operation if it fails. ### 409 - ConcurrentChangeError The `409 - ConcurrentChangeError` response means another cluster operation is already in progress. Wait for the current operation to complete before retrying the purge operation. --- ## Camunda components log levels When working with Camunda 8, you may see various messages in your logs. Not all messages require action. ## Understanding log levels Camunda 8 uses the following log levels: | Log level | Description | | :-------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | TRACE | Information which is helpful only if you want to trace the execution of a particular component. | | DEBUG | Information which can provide helpful context when debugging. You may see a DEBUG message right after an INFO message to provide more context. | | INFO | Information about the system that is useful for the user (in the case of the broker, the user here is the user deploying it). For example, leader changes, a new node added to or removed from the membership, and so on. | | WARN | Expected errors (for example, connection timeouts, the remote node is unavailable, and so on) which might indicate that parts of the system are not working and would require attention if they persist, but could resolve by themselves. These should be monitored, but may not require a support ticket. | | ERROR | Errors that require further investigation. For example, log corruption, inconsistent log, anything which could shut down a partition, and so on. | ## Enable logging Enable logging for each Camunda 8 component as follows: - [Orchestration Cluster](/self-managed/components/orchestration-cluster/core-settings/configuration/logging.md) - [Camunda Hub](/self-managed/components/hub/configuration/logging.md) - [Identity](/self-managed/components/management-identity/miscellaneous/configure-logging.md) --- ## Camunda components metrics For distributed system monitoring, Camunda uses the [Micrometer](https://micrometer.io/) library as a facade to export metrics to [supported implementations](https://docs.micrometer.io/micrometer/reference/implementations.html) such as Prometheus, OpenTelemetry, Datadog, and Dynatrace. ## Access metrics You can access your metrics data using your chosen monitoring implementation. Metrics data is only stored in-memory in Camunda, so it needs to be consumed and aggregated by a monitoring system. Monitoring typically uses either a polling (default) or pushing system. ### Polling The system (for example, Prometheus) polls an endpoint exposed by Camunda at a regular interval. - Each request constitutes a data point for each metric. - When working with such systems, configure the polling interval to get information quickly but without overwhelming Camunda itself (which still has to serve this data) or having to store too much data in your monitoring system itself. - Additionally, this means exposing the Camunda endpoint to your external monitoring system. ### Pushing For a pushing system (for example, OpenTelemetry), Camunda is configured to asynchronously push metric updates to an external endpoint at a regular interval. - This implies that the system is accessible to Camunda via the network, so you should ensure communication is secure. - Similarly to the polling approach, balance how fast you are pushing (and getting updates/data points) without overwhelming your external system. ## Configuration Configure your metrics using the built-in [Spring Boot Micrometer configuration](https://docs.spring.io/spring-boot/reference/actuator/metrics.html). ### Defaults Camunda includes built-in support for [Prometheus](https://prometheus.io) and [OpenTelemetry](https://opentelemetry.io/). By default, the configuration only exports Prometheus metrics via [a scraping endpoint](https://docs.spring.io/spring-boot/reference/actuator/metrics.html#actuator.metrics.export.prometheus), with OpenTelemetry disabled. #### Prometheus The scraping endpoint for Prometheus is located under the management context (default `:9600/actuator/prometheus`). Configure this via the following properties: ```yaml management: endpoint.prometheus.access: unrestricted prometheus.metrics.export.enabled: true ``` To collect metrics, you must define the new scraping endpoint for Prometheus. Add the following scraping job: ``` - job_name: camunda scrape_interval: 30s metrics_path: /actuator/prometheus scheme: http static_configs: - targets: - localhost:9600 ``` :::warning If you've configured your management context to use HTTPS, you must also update the `scheme` for the scraping job above. This also applies if you change the management port. ::: :::note The scraping interval is `30s` by default. This means you will get new data points in Prometheus every 30 seconds. - This is a good default to minimize the storage requirements for Prometheus. - To run alerts or auto-scaling based on the provided metrics, you can configure a shorter interval. As this results in more data being ingested, use at your own risk. ::: #### OpenTelemetry Protocol Zeebe also comes built-in with support to export metrics via OpenTelemetry (using the `micrometer-registry-otlp`). Configure this via the following properties: ```yaml management: # Disable Prometheus prometheus.metrics.export.enabled: false # Configure OpenTelemetry Metrics otlp: metrics: export: # Enable OTLP enabled: true # Since metrics are pushed, you will need to configure at least one endpoint url: "https://otlp.example.com:4318/v1/metrics" ``` For a complete list of configuration options for OTLP, refer to the [Micrometer](https://docs.micrometer.io/micrometer/reference/implementations/otlp.html#_configuring) documentation. :::warning When using the OTLP exporter, check the requirements of your target endpoint, as it might require additional configuration. For example, you might need to pass a client secret and ID for authentication via the `otlp.metrics.export.headers` options, or your system might not support `cumulative` aggregation temporality and instead require `delta` (for example, Dynatrace). ::: :::tip A wide variety of existing monitoring systems also support ingesting OpenTelemetry data (for example, Dynatrace, Datadog, and so on). Camunda recommends using these instead of the specific Micrometer implementations. ::: ### Use a different monitoring system To use a different monitoring system, refer to the [Spring Boot](https://docs.spring.io/spring-boot/reference/actuator/metrics.html#actuator.metrics.export) documentation. Zeebe only ships with built-in support for the [Prometheus](https://docs.spring.io/spring-boot/reference/actuator/metrics.html#actuator.metrics.export.prometheus) and [OTLP](https://docs.spring.io/spring-boot/reference/actuator/metrics.html#actuator.metrics.export.otlp) systems. To use a different system, you must add the required dependencies to your Zeebe installation, specifically to the distribution's `lib/` folder. :::note When using the container image, you must add it to the following paths, based on your image: - `camunda/zeebe`: `/usr/local/zeebe/lib` - `camunda/camunda`: `/usr/local/camunda/lib` ::: For example, to export to Datadog, download the `io.micrometer:micrometer-registry-datadog` JAR and place it in the `./lib` folder of the distribution. Running from the root of the distribution, you can use Maven to do this for you: ```shell mvn dependency:copy -Dartifact=io.micrometer:micrometer-registry-datadog:1.14.4 -Dtransitive=false -DoutputDirectory=./lib ``` :::note The version must be the same as the Micrometer version used by Camunda. - Find this information by checking the distribution artifact on [Maven Central](https://central.sonatype.com/artifact/io.camunda/camunda-zeebe/dependencies). - Select the distribution version you are using, and filter for `micrometer` to get the expected Micrometer version. ::: ### Customize metrics You can modify and filter the metrics exposed in Camunda via configuration. #### Common tags [Tags provide a convenient way of aggregating metrics over common attributes](https://docs.spring.io/spring-boot/reference/actuator/metrics.html#actuator.metrics.customizing.common-tags). Via configuration, you can ensure that all metrics for a specific instance of Camunda share common tags. For example, if you deployed two different clusters and wanted to differentiate them: The first cluster could be configured as: ```yaml management: metrics: tags: cluster: "foo" ``` And the second cluster configured as: ```yaml management: metrics: tags: cluster: "bar" ``` #### Filtering [You can additionally disable certain metrics](https://docs.spring.io/spring-boot/reference/actuator/metrics.html#actuator.metrics.customizing.per-meter-properties). This can be useful for high cardinality metrics which you do not care for, but which may end up being expensive to store in your target system. To filter a metric called `zeebe.foo`, you would configure the following property: ```yaml management: metrics: enable: zeebe: foo: false ``` :::note Filtering applies not only to direct name matches (for example, `zeebe.foo`), but as a prefix. This means any metric starting with the prefix `zeebe.foo` in the example would also be filtered out, and would not be exported. ::: ## Scrape metrics with the Helm chart The Camunda Helm chart can create a Prometheus Operator `ServiceMonitor` resource for each component that exposes metrics. Install the Prometheus Operator in your cluster before you set `prometheusServiceMonitor.enabled` to `true`. ### Configure the `ServiceMonitor` resources Use the following Helm values to configure the `ServiceMonitor` resources: | Value | Default | Description | | :---------------------------------------- | :----------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `prometheusServiceMonitor.enabled` | `false` | If `true`, the chart creates a `ServiceMonitor` resource for each deployed component listed in [Metrics endpoints by component](#metrics-endpoints-by-component). | | `prometheusServiceMonitor.scrapeInterval` | `10s` | The interval at which Prometheus scrapes each metrics endpoint. | | `prometheusServiceMonitor.labels` | `release: metrics` | Labels the chart adds to each `ServiceMonitor` resource. The Prometheus Operator selects `ServiceMonitor` resources by label, so set labels that match the `serviceMonitorSelector` of your `Prometheus` resource. | The following example enables the `ServiceMonitor` resources, sets a 30-second scrape interval, and replaces the default `release` label: ```yaml prometheusServiceMonitor: enabled: true scrapeInterval: 30s labels: release: my-prometheus ``` ### Metrics endpoints by component The following table lists the default metrics endpoint of each component that exposes metrics. The Service port is the port of the Kubernetes Service. The container port is the port the pod listens on. | Component | Default Service port | Default container port | Default path | Helm value for the path | | :-------------------- | :------------------- | :--------------------- | :--------------------- | :-------------------------------------- | | Orchestration Cluster | `9600` | `9600` | `/actuator/prometheus` | `orchestration.metrics.prometheus` | | Connectors | `8080` | `8080` | `/actuator/prometheus` | `connectors.metrics.prometheus` | | Management Identity | `82` | `8082` | `/actuator/prometheus` | `identity.metrics.prometheus` | | Optimize | `8092` | `8092` | `/actuator/prometheus` | `optimize.metrics.prometheus` | | Camunda Hub REST API | `8091` | `8091` | `/metrics` | `camundaHub.restapi.metrics.prometheus` | The chart creates the Management Identity `ServiceMonitor` resource only if `global.identity.auth.enabled` is `true`. The chart configures no metrics port for Camunda Hub WebSockets, so no `ServiceMonitor` resource covers it. ## Available metrics [Spring already exposes various metrics](https://docs.spring.io/spring-boot/reference/actuator/metrics.html#actuator.metrics.supported), some of which will be made available through Camunda: - [JVM metrics](https://docs.spring.io/spring-boot/reference/actuator/metrics.html#actuator.metrics.supported.jvm) - [System metrics](https://docs.spring.io/spring-boot/reference/actuator/metrics.html#actuator.metrics.supported.system) - [Application startup metrics](https://docs.spring.io/spring-boot/reference/actuator/metrics.html#actuator.metrics.supported.application-startup) - [Logger metrics](https://docs.spring.io/spring-boot/reference/actuator/metrics.html#actuator.metrics.supported.logger) - [Spring MVC metrics](https://docs.spring.io/spring-boot/reference/actuator/metrics.html#actuator.metrics.supported.spring-mvc) Camunda also exposes several custom metrics, most of them under the `zeebe`, `atomix`, `operate`, `tasklist`, or `optimize` prefixes. :::note While all nodes in a Camunda cluster expose metrics, they will expose relevant metrics based on their role. For example, brokers will expose processing related metrics, while gateways will expose REST API relevant metrics. ::: :::note **Not all metrics are available at all times.** This can apply to various metrics, but is especially noticeable for **processing-related metrics**, which are recorded on events that can occur infrequently. For example, the `zeebe_incident_events_total` metric is only recorded when an incident is **created** or **resolved**. ::: ### Process processing metrics The following metrics are related to process processing: | Metric | Description | | :----------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `zeebe_stream_processor_records_total` | The number of events processed by the stream processor. The `action` label separates processed, skipped, and written events. | | `zeebe_exporter_events_total` | The number of events processed by the exporter processor. The `action` label separates exported and skipped events. | | `zeebe_element_instance_events_total` | The number of occurred process element instance events. The `action` label separates the number of activated, completed, and terminated elements. The `type` label separates different BPMN element types. | | `zeebe_job_events_total` | The number of job events. The `action` label separates the number of created, activated, timed out, completed, failed, and canceled jobs. | | `zeebe_incident_events_total` | The number of incident events. The `action` label separates the number of created and resolved incident events. | | `zeebe_pending_incidents_total` | The number of currently pending incidents, that is, not resolved. | | `zeebe_process_definitions_draining_count` | The number of process definitions currently draining, that is, deleted but retained until their running process instances finish. Metric type: gauge, reported on the partition leader.Labels: `physicalTenant`, `partition`. | ### Performance metrics The following metrics are related to performance. For example, Zeebe has a backpressure mechanism to reject requests when it receives more requests than it can handle without incurring high processing latency. Monitor backpressure and processing latency of the commands using the following metrics: | Metric | Description | | :-------------------------------------- | :---------------------------------------------------------------------- | | `zeebe_dropped_request_count_total` | The number of user requests rejected by the broker due to backpressure. | | `zeebe_backpressure_requests_limit` | The limit for the number of inflight requests used for backpressure. | | `zeebe_stream_processor_latency_bucket` | The processing latency for commands and event. | ### Health metrics The health of partitions in a broker can be monitored using the metric `zeebe_health`. ### Schema initialization metrics Use these metrics to monitor secondary-storage readiness and schema initialization: | Metric name | Type | Description | Labels | | ------------------------------------------------- | ----- | --------------------------------------------------------------------------------- | ---------------- | | `camunda.physical.tenant.secondary.storage.ready` | Gauge | Whether the Physical Tenant's secondary storage is ready (`1`) or degraded (`0`). | `physicalTenant` | | `camunda.schema.init.time` | Timer | Duration of secondary-storage schema initialization for the tenant. | `physicalTenant` | ## Execution latency metrics Brokers can export optional execution latency metrics. To enable export of execution metrics, set the `CAMUNDA_MONITORING_METRICS_ENABLEEXPORTEREXECUTIONMETRICS` environment variable to `true` in your Zeebe [configuration file](/self-managed/components/orchestration-cluster/zeebe/configuration/configuration.md). ## Optimize error metrics Optimize exposes a counter metric for tracking errors by type. | Metric name | Type | Description | Labels | | ---------------------- | ------- | -------------------------------------------------------- | ------------------------ | | `optimize_error_total` | Counter | Number of errors occurring in Optimize, grouped by type. | `ERROR_TYPE` (see below) | The `ERROR_TYPE` label can have the following values: | Value | Description | | ------------------------ | --------------------------------------------------------------------------- | | `too_many_buckets` | Aggregation bucket limit exceeded during report evaluation. | | `version_conflict` | Document version conflict on write, for example, during imports or updates. | | `index_not_found` | Required index is missing, for example, in metadata queries or scrollers. | | `search_context_missing` | Search context expired during pagination, for example, JSON export. | | `nested_limit_exceeded` | Nested document limit exceeded in complex queries. | | `elasticsearch_error` | Generic Elasticsearch error that does not match a more specific type. | | `opensearch_error` | Generic OpenSearch error that does not match a more specific type. | Each time series uses the same metric name and differs only by the `ERROR_TYPE` label value. For example: ``` optimize_error_total{ERROR_TYPE="too_many_buckets"} 5 optimize_error_total{ERROR_TYPE="version_conflict"} 12 ``` ## Optimize report latency metrics Optimize exposes a timer metric for tracking how long report evaluations take. | Metric name | Type | Description | Labels | | --------------------------------- | ----- | ------------------------------ | -------------------------- | | `optimize_report_reportLatency_*` | Timer | Duration of report evaluation. | `REPORT_NAME`, `REPORT_ID` | The `REPORT_NAME` and `REPORT_ID` labels identify the evaluated report or dashboard. Report latency metrics are controlled by the `optimize.metrics.report-latency.enabled=true` configuration property. Set the property to `false` to disable report latency metrics. ## Secret resolution and cache metrics Camunda emits meters for secret resolution and for the in-memory cache associated with each configured store. Use these meters to distinguish a slow or unavailable secret store from a cold cache when jobs don't activate. These meters don't use secret names as labels because secret-name cardinality is unbounded and secret names contain customer data. ### Secret resolution metrics These meters cover resolving secret references against a secret store. | Metric name | Type | Description | Labels | | --------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | | `camunda.secret.resolution.duration` | Timer | Latency of one batch resolution call against a secret store. Measures the store call only, not the follow-up commands the engine writes for its results. The `result` label separates calls by outcome so store timeouts don't distort the latency of successful calls. | `store`, `result` (see below), `physicalTenant`, `partition` | | `camunda.secret.resolution.outcome` | Counter | Number of secret reference resolutions that produced an outcome, per store. Every result value is terminal for the reference it counts, so the values can be summed or divided by one another to derive rates. A reference whose store is unavailable but still has retry attempts left is not counted at all, since it has not reached a terminal outcome. | `store`, `result` (see below), `physicalTenant`, `partition` | | `camunda.secret.resolution.cycle.error` | Counter | Number of resolution cycles in which a store encounters an unexpected exception that the engine does not model as a per-secret failure or unavailable store. Counted per store. A nonzero value indicates a bug in either the store implementation or the engine. Counts cycles, not references, so it is a separate meter from `camunda.secret.resolution.outcome`. | `store`, `physicalTenant`, `partition` | | `camunda.secret.resolution.cycle.delay` | Timer | Delay before the next resolution cycle, grouped by the reason for the delay. Monitor `IDLE_BACKOFF` to verify that the delay increases geometrically after consecutive misses. Its distribution shows the backoff behavior without requiring you to infer it from the cycle rate. | `result` (see below), `physicalTenant`, `partition` | The `store` label carries the ID of the secret store a reference belongs to. `camunda.secret.resolution.cycle.delay` carries no `store` label, since a resolution cycle isn't scoped to one store. Every resolution meter also carries the `physicalTenant` and `partition` labels applied to Zeebe metrics generally. The `result` label uses different values depending on the meter: `result` values on `camunda.secret.resolution.duration`: | Value | Description | | ------------------- | ---------------------------------------------------------------------------- | | `RETURNED` | The store returned, whatever the per-reference results were. | | `STORE_UNAVAILABLE` | The store could not be reached for this call. | | `ERROR` | The store threw something the engine does not model. Always indicates a bug. | `result` values on `camunda.secret.resolution.outcome`: | Value | Description | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `RESOLVED` | The store returned a value for the reference. | | `NOT_FOUND` | The store does not hold the reference. | | `ACCESS_DENIED` | The store refused to read the reference. | | `INVALID_REF` | The reference is not valid for the store. | | `UNREADABLE` | The store holds the reference but could not read a value from it. | | `STORE_UNAVAILABLE` | The store could not serve the reference at all: either it is not configured, or it could not be reached and no retry attempt is left. | `result` values on `camunda.secret.resolution.cycle.delay` (why the cycle chose its delay, not a per-reference outcome): | Value | Description | | ---------------- | --------------------------------------------------------------------------------------------------------- | | `DRAINING` | More pending references remained than the batch cap allowed this cycle to take. The delay is always zero. | | `WAKE` | This cycle resolved something, or a reference was requested since the last cycle ran. | | `IDLE_BACKOFF` | Neither of the above, and no store is in retry cooldown. | | `RETRY_COOLDOWN` | Neither of the above, and a store's retry cooldown deadline set the delay instead. | ### Interpret secret cache metrics Each configured secret store uses an in-memory cache during resolution. Use these metrics to evaluate cache behavior and distinguish cache misses from store-level resolution failures. | Metric name | Type | Description | Labels | | -------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | | `camunda.secret.cache.result` | Counter | Number of secret cache lookups, grouped by store and result. Use `HIT / (HIT + MISS)` to calculate the cache hit rate. Each lookup is counted once. References that result in permanent failures, such as not found, access denied, or invalid, are never cached and therefore produce a `MISS` on every lookup while they remain referenced. | `store`, `result` (see below), `physicalTenant` | | `camunda.secret.cache.evictions` | Counter | Number of entries removed from a secret cache, grouped by store and cause. | `store`, `cause` (see below), `physicalTenant` | | `camunda.secret.cache.size` | Gauge | Estimated number of entries currently held in a secret cache, per store. Because eviction is asynchronous, the value can briefly exceed the configured maximum. Use this metric to compare the current cache level with the configured maximum rather than as an exact count. | `store`, `physicalTenant` | The `store` label contains the ID of the secret store associated with the cache. Every cache metric also includes `physicalTenant` because the registry that publishes these metrics is scoped per tenant. Cache metrics don't include `partition` because a secret cache exists outside any partition. #### `result` values for `camunda.secret.cache.result` | Value | Description | | ------ | --------------------------------------------------------------------------------------------------------- | | `HIT` | The cache contains a value for the requested name. | | `MISS` | The cache contains no value for the requested name, so resolution must continue against the secret store. | #### `cause` values for `camunda.secret.cache.evictions` | Value | Description | | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SIZE` | The cache reached its configured maximum and evicted an entry to make room for another. | | `EXPIRED` | The entry's time-to-live expired. | | `EXPLICIT` | An entry was explicitly removed by name. In the current implementation, this occurs when a store reports a permanent failure, such as not found, access denied, or an invalid reference. | | `COLLECTED` | The entry's key or value was garbage collected. The current cache configuration does not emit this value because it uses neither weak keys nor soft values. | ### Read cache and resolution metrics together `camunda.secret.cache.result` and `camunda.secret.resolution.outcome` describe different parts of secret resolution. A low cache hit rate does not necessarily indicate a cache problem. References that result in permanent failures, such as not found, access denied, or invalid, are never cached and therefore produce a `MISS` on every lookup. When the cache hit rate is low, check `camunda.secret.resolution.outcome` first. If the misses correspond to references that never resolve successfully, address the resolution failures rather than the cache configuration. ### Cache size and the configured maximum `camunda.secret.cache.size` is bounded per store by the [`camunda.secrets.cache.max-size`](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#secret-cache) property. The bound applies per store, not as a shared budget, so the worst-case memory footprint across a deployment is the number of configured stores multiplied by that maximum. ### Metric names in Prometheus The tables above name meters by their Micrometer meter ID. When Prometheus scrapes them, dots become underscores, and Micrometer appends a type suffix: `_total` for a counter, the base unit for a timer (plus `_count` and `_sum`; both timers here also declare fixed histogram buckets, so `_bucket` is always emitted for them too), and no suffix for a gauge: | Metric name | Prometheus metric name | | --------------------------------------- | ----------------------------------------------- | | `camunda.secret.resolution.duration` | `camunda_secret_resolution_duration_seconds` | | `camunda.secret.resolution.cycle.delay` | `camunda_secret_resolution_cycle_delay_seconds` | | `camunda.secret.resolution.outcome` | `camunda_secret_resolution_outcome_total` | | `camunda.secret.resolution.cycle.error` | `camunda_secret_resolution_cycle_error_total` | | `camunda.secret.cache.result` | `camunda_secret_cache_result_total` | | `camunda.secret.cache.evictions` | `camunda_secret_cache_evictions_total` | | `camunda.secret.cache.size` | `camunda_secret_cache_size` | `camunda.secret.cache.size` is the one exception with no unit suffix at all, so it stays exactly `camunda_secret_cache_size`. For example, the cache hit rate described above becomes: ```promql camunda_secret_cache_result_total{result="HIT"} / ignoring(result) sum without (result) (camunda_secret_cache_result_total) ``` For how the broker resolves secret references before job activation, see [Secret resolution and job activation](/components/concepts/secret-resolution-and-job-activation.md). ## Grafana ### Zeebe Zeebe comes with a pre-built dashboard, available in the repository: [monitor/grafana/zeebe.json](https://github.com/camunda/camunda/blob/main/monitor/grafana/zeebe.json). - [Import](https://grafana.com/docs/grafana/latest/reference/export_import/#importing-a-dashboard) the dashboard into your Grafana instance and select the correct Prometheus data source (if you have more than one). - The dashboard displays a healthy cluster topology, general throughput metrics, handled requests, exported events per second, disk and memory usage, and more. The following image shows an example of the Zeebe Grafana dashboard after import. ![Example Zeebe Grafana dashboard](assets/grafana-preview.png) #### Physical Tenant filtering Partition-scoped Zeebe metrics include a `physicalTenant` label. Most node-level metrics that are not partition-scoped do not carry this label. Per-tenant Hikari connection-pool metrics are an exception. The Zeebe dashboard supports filtering and aggregating metrics by `physicalTenant` and `partition`, and exposes `physicalTenant` as a variable selector, so you can monitor throughput, latency, and resource usage for each Physical Tenant independently. To compare across tenants in Prometheus queries, use the `physicalTenant` label directly. For example: ```promql sum by (physicalTenant) (zeebe_stream_processor_latency_seconds_count) ``` :::note Other Grafana dashboards (API panels, gateway panels) are being updated to include `physicalTenant` filtering, tracked in [camunda/camunda#56250](https://github.com/camunda/camunda/issues/56250). ::: ### Data layer A pre-built Grafana dashboard is available for the data layer in the repository: [monitor/grafana/dashboards/data_layer.json](https://github.com/camunda/camunda/blob/main/monitor/grafana/dashboards/data_layer.json) To use it: 1. [Import](https://grafana.com/docs/grafana/latest/reference/export_import/#importing-a-dashboard) the dashboard into your Grafana instance. 2. When prompted, select the appropriate Prometheus data source (especially if multiple are configured). The dashboard provides insights into key data layer components for Camunda versions `>= 8.8`, with a focus on the Camunda exporter through which all data flows. ![Example panels](assets/example-panels-data-layer.png) ## Troubleshoot metrics and dashboards ### Grafana dashboard shows no data after import **Observed behavior:** The dashboard renders, but panels show "No data" or stay empty. **Why this happens:** The dashboard's panels aren't bound to a Prometheus data source, either because none was selected during import, or because the wrong one was selected when more than one is configured in Grafana. **How to fix:** 1. Open the dashboard's settings and check the data source assigned under its variables and panels. 2. If it's missing or wrong, re-import the dashboard and explicitly select your Prometheus data source when prompted. 3. Confirm the data source itself can reach Prometheus by testing it from **Connections > Data sources** in Grafana. ### Prometheus scraping endpoint returns no data, or the target shows as down **Observed behavior:** `/actuator/prometheus` returns an empty response or `404`, or Prometheus shows the Camunda target as down in its **Targets** page. **Why this happens:** The Prometheus endpoint is available when the default Prometheus export settings are in place. If those defaults were changed, `management.endpoint.prometheus.access` or `management.prometheus.metrics.export.enabled` can prevent the endpoint from exporting metrics. A mismatch between the scraping job's `scheme` and the management context's actual protocol (HTTP vs. HTTPS) also causes the target to show as down. **How to fix:** 1. Confirm both properties above are set. See [Prometheus](#prometheus). 2. Confirm the scraping job's `scheme` matches the management context's actual protocol, and its `targets` port matches the management port (default `9600`). 3. Query the endpoint directly (`curl http://:9600/actuator/prometheus`) to confirm it responds before checking Prometheus. ### A metric you expect to see is missing **Observed behavior:** A documented metric name doesn't appear in Prometheus or Grafana, even though scraping otherwise works. **Why this happens:** One of three causes, in order of likelihood: - The metric is processing-related and only recorded when its triggering event occurs. For example, `zeebe_incident_events_total` only appears after an incident is created or resolved, see [available metrics](#available-metrics). - The metric was filtered out. Filtering matches by prefix, so a rule intended to filter `zeebe.foo` also filters `zeebe.foobar` and anything else starting with that prefix. See [filtering](#filtering). - The node role doesn't expose that metric. Brokers and gateways expose different metric sets, see the note under [available metrics](#available-metrics). **How to fix:** Trigger the underlying event and check again, then review your filter configuration for an overly broad prefix match, then confirm you're querying the node role that actually exposes that metric. ### Physical Tenant filtering is missing from a panel **Observed behavior:** The `physicalTenant` variable or label isn't available on a specific Grafana panel, even though it works elsewhere in the same dashboard. **Why this happens:** See [Physical Tenant filtering](#physical-tenant-filtering) for which metrics and dashboards expose the `physicalTenant` label. **How to fix:** Confirm the panel's underlying metric is partition-scoped. If it is and still lacks the label, check the linked issue in [Physical Tenant filtering](#physical-tenant-filtering) for status before assuming a misconfiguration. ### OTLP export fails or backend rejects the data **Observed behavior:** Metrics reach your OTLP endpoint's logs as errors, or don't appear in the target system at all. **Why this happens:** OTLP backends vary in what they require beyond a reachable `url`. Some need authentication headers (`otlp.metrics.export.headers`), and some don't support the default `cumulative` aggregation temporality and require `delta` instead (for example, Dynatrace). See [OpenTelemetry Protocol](#opentelemetry-protocol). **How to fix:** Check your target system's OTLP requirements for authentication headers and its required aggregation temporality, and set both explicitly rather than relying on Micrometer's defaults. ## Configure metrics Configure metrics for each Camunda 8 component as follows: - [Orchestration Cluster](/self-managed/components/orchestration-cluster/core-settings/concepts/monitoring.md) - [Camunda Hub](/self-managed/components/hub/monitoring.md) --- ## Camunda components troubleshooting ## Keycloak requires SSL for requests from external sources When deploying Camunda to a provider, it is important to confirm the IP ranges used for container to container communication align with the IP ranges Keycloak considers "local". By default, Keycloak considers all IPs outside those listed in their [external requests documentation](https://www.keycloak.org/docs/latest/server_admin/#_ssl_modes) to be external and therefore require SSL. As the [Camunda Helm Charts](https://artifacthub.io/packages/helm/camunda/camunda-platform) currently do not provide support for the distribution of the Keycloak TLS key to the other containers, we recommend viewing the solution available in the [Identity documentation](/self-managed/components/management-identity/miscellaneous/troubleshoot-identity.md#solution-2-management-identity-making-requests-from-an-external-ip-address). ## Identity redirect URL If HTTP to HTTPS redirection is enabled in the load-balancer or Ingress, make sure to use the HTTPS protocol in the values file under `global.identity.auth.[COMPONENT].redirectUrl`. Otherwise, you will get a redirection error in Keycloak. For example: ```yaml global: identity: auth: optimize: redirectUrl: https://optimize.example.com ``` ## Zeebe Backup with S3 In general, some S3 compatible implementations are not able to properly handle the checksum feature of the S3 client being introduced with version 2.30.0. For more details, you can refer to [the AWS documentation](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/s3-checksums.html). As soon as issues appear related to the checksum, it can be disabled by setting these environment variables on your Zeebe brokers: ``` AWS_REQUEST_CHECKSUM_CALCULATION=WHEN_REQUIRED AWS_RESPONSE_CHECKSUM_CALCULATION=WHEN_REQUIRED ``` This will disable automated creation of checksums. If you are still encountering issues with MD5 checksums required by your provider, enable legacy support for the AWS S3 client by setting: ``` ZEEBE_BROKER_DATA_BACKUP_S3_SUPPORTLEGACYMD5=true ``` **Backups to IBM COS fail with 403 Access Denied** When using an S3 backup store with IBM Cloud Object Storage, you may encounter `403 Access Denied` errors even though the access credentials are valid. This may be caused by a [recent change in the AWS S3 client](https://docs.aws.amazon.com/sdkref/latest/guide/feature-dataintegrity.html), which now calculates checksums for data integrity by default. IBM COS does not appear to support this feature. To resolve this issue, you can restore the previous behavior by setting the following environment variable on your Zeebe brokers: ``` AWS_REQUEST_CHECKSUM_CALCULATION=WHEN_REQUIRED ``` This will prevent the S3 client from calculating the additional checksums and should resolve the issue. **Backups to Dell EMC ECS fail with 400 Bad Request** When using an S3 backup store with Dell EMC ECS, you may encounter the following error: `The Content-SHA256 you specified did not match what we received (Service: S3, Status Code: 400)` This issue is caused by a recent change in the AWS S3 client, which now signs streaming chunked uploads differently. Dell EMC ECS does not support chunked encoding. To resolve this issue, set the following environment variable on your Zeebe brokers: ``` AWS_REQUEST_CHECKSUM_CALCULATION=WHEN_REQUIRED ``` This disables the additional checksum calculation in the S3 client and should resolve the issue. ## Zeebe backup with Azure Blob Storage When using an Azure backup store, requests to the backup API may time out due to [a bug in the Azure SDK](https://github.com/Azure/azure-sdk-for-java/issues/46231). This issue is caused by a deadlock in the Azure SDK when virtual threads are used. It is more likely to occur on systems with many partitions per broker and limited CPU resources. To mitigate this, set the following environment variable on your Zeebe brokers to disable virtual threads in the Azure SDK: ``` AZURE_SDK_SHARED_THREADPOOL_USEVIRTUALTHREADS=false ``` ## Zeebe fails with `ClassCircularityError` when using AppDynamics Zeebe brokers and gateways can fail with a `java.lang.ClassCircularityError` when the AppDynamics Java agent is attached to the JVM. ### Symptoms One of the following errors appears in the logs: ```text java.lang.ClassCircularityError: jdk/internal/misc/VirtualThreads ``` ```text java.lang.IllegalStateException: java.lang.ClassCircularityError: jdk/internal/misc/VirtualThreads ``` The error can occur in any code path that runs on virtual threads, for example during S3 backups or while the gateway handles gRPC requests. After the error, the JVM may be left in a broken state: - A broker tries to shut down, but the shutdown doesn't complete and the JVM process keeps running. Partitions led by that broker stay unavailable. - A gateway stops responding to gRPC requests, while health checks such as `/actuator/health/liveness` still report it as healthy. Because health checks don't always detect the broken state, Kubernetes might not restart the affected pod automatically. ### Cause Zeebe and several libraries it depends on, such as the AWS SDK and gRPC, run work on Java virtual threads. The AppDynamics Java agent intercepts class definitions to instrument bytecode. On a virtual thread, the agent's own code triggers loading of `jdk/internal/misc/VirtualThreads`, which the agent intercepts again. The JVM detects this circular class loading and throws `ClassCircularityError`, and the JVM can't reliably recover from it. This is a defect in the AppDynamics Java agent. Zeebe doesn't provide an option to disable virtual threads, because third-party libraries also use them internally. ### Solution - Don't attach the AppDynamics Java agent to Zeebe brokers or gateways. Remove the AppDynamics `-javaagent` option from the JVM options, for example from the `JAVA_TOOL_OPTIONS` environment variable. - If you need AppDynamics for a specific investigation, attach the agent temporarily and remove it afterward. Bytecode instrumentation also adds overhead that can affect performance. - If the error occurs, restart the affected pod or JVM process. Don't wait for the process to exit or for health checks to fail, as neither is guaranteed. ## Enable Azure logging for troubleshooting When using Azure Blob Storage as a backup store, you can enable logging to troubleshoot issues with the Azure SDK. To do this, go through the following steps: 1. Add logging for Azure SDK, and set it to debug through the Zeebe Broker loggers endpoint: `curl 'http://localhost:9600/actuator/loggers/com.azure' -i -X POST -H 'Content-Type: application/json' -d '{"configuredLevel":"debug"}'` 2. Add the following environment variable to the Zeebe Broker StatefulSet. `AZURE_HTTP_LOG_DETAIL_LEVEL=BASIC` ## Zeebe Ingress (gRPC) Zeebe requires an Ingress controller that supports `gRPC` which is built on top of `HTTP/2` transport layer. Therefore, to expose Zeebe Gateway externally, you need the following: 1. An Ingress controller that supports `gRPC`. The reference architectures deploy [Contour](https://projectcontour.io/), which supports it through the `projectcontour.io/upstream-protocol.h2c` annotation on the Orchestration Cluster service. [Ingress-nginx](https://github.com/kubernetes/ingress-nginx) supports it through the `nginx.ingress.kubernetes.io/backend-protocol: GRPC` annotation on the Ingress. 2. TLS (HTTPS) via [Application-Layer Protocol Negotiation (ALPN)](https://www.rfc-editor.org/rfc/rfc7301.html) enabled in the Zeebe Gateway Ingress object. However, according to the official Kubernetes documentation about [Ingress TLS](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls): > There is a gap between TLS features supported by various Ingress controllers. Please refer to documentation on nginx, GCE, or any other platform specific Ingress controller to understand how TLS works in your environment. Therefore, pay attention to the TLS configuration of the Ingress controller of your choice. Find more details about the Zeebe Ingress setup in the [Kubernetes platforms supported by Camunda](/self-managed/deployment/helm/install/quick-install.md). ## Identity `contextPath` Camunda 8 Self-Managed can be accessed externally via the [combined Ingress setup](/self-managed/deployment/helm/configure/ingress/ingress-setup.md#configuration). In that configuration, Camunda Identity is accessed using a specific path, configured by setting the `contextPath` variable, for example `https://camunda.example.com/identity`. For security reasons, Camunda Identity requires secure access (HTTPS) when a `contextPath` is configured. :::note Due to limitations, the Identity `contextPath` approach is unavailable when using a browser in Incognito mode. ::: ## Camunda Hub database schema The Camunda Hub `restapi` component requires a [database connection](/self-managed/components/hub/configuration/properties.md#database). This connection should not point to the same database as Keycloak does. ## Gateway timeout on redirect A gateway timeout can occur if the headers of a response are too big (for example, if a JWT is returned as `Set-Cookie` header). To avoid this, you can increase the header buffer of your Ingress controller. For **ingress-nginx**, set the [`proxy-buffer-size` annotation](https://github.com/kubernetes/ingress-nginx/blob/main/docs/user-guide/nginx-configuration/annotations.md#proxy-buffer-size). For **Contour**, the limit is enforced by Envoy rather than by an Ingress annotation, so it is raised on the Contour installation itself. See the [Contour configuration reference](https://projectcontour.io/docs/1.33/configuration/). ## Helm CLI version and installation failures If you encounter errors during Helm chart installation, such as type mismatches or other template rendering issues, you may be using an unsupported version of the Helm CLI. Camunda 8.10 (chart 15.x) supports Helm CLI 3.10 or a later 3.x version, or Helm CLI 4.x. For chart-to-CLI compatibility across versions, see [Helm CLI v4](/self-managed/deployment/helm/operational-tasks/helm-v4.md). ## DNS disruption issue for Zeebe in Kubernetes clusters (1.29-1.31) Kubernetes clusters running versions 1.29 to 1.31 may experience DNS disruptions during complete node restarts, such as during upgrades or evictions, particularly if the cluster's DNS resolver pods are affected. This issue is specifically noticeable for Zeebe (Netty), as it will no longer be able to form a cluster because of improper DNS responses. This occurs because Zeebe continues to communicate with a non-existent DNS resolver, caused by improper cleanup of conntrack entries for UDP connections. Details on this issue can be found in [this Kubernetes issue](https://github.com/kubernetes/kubernetes/issues/125467) and has been resolved in the following patch releases: - Kubernetes 1.29.10 - Kubernetes 1.30.6 - Kubernetes 1.31.2 Kubernetes versions 1.32 and versions before 1.29 are not affected. If an immediate cluster upgrade to a fixed version is not possible, the following temporary workarounds can be applied if you encounter DNS issues: - Restart the `kube-proxy` pod(s) - Delete the affected Zeebe pod ## Anomaly detection scripts The [c8-sm-checks](https://github.com/camunda/c8-sm-checks) project introduces a set of scripts to aid detection of Camunda deployment anomalies. These scripts perform health checks on various aspects of the Kubernetes installation and Zeebe components, providing insights into potential issues that may affect the performance or stability. ### Usage Each script in the `c8-sm-checks` project can be executed independently, allowing you to target specific areas for troubleshooting and verification. To utilize these scripts effectively, ensure you have the necessary permissions and access to your Kubernetes cluster. Additionally, make sure you have the required dependencies installed on your system, such as `kubectl`, `helm`, `curl`, and `grpcurl`. For detailed documentation and usage instructions for each script, refer to the [c8-sm-checks GitHub repository](https://github.com/camunda/c8-sm-checks). Additionally, you can use the `-h` option with each script to display help information directly from the command line. Before using it, clone the `c8-sm-checks` repository to your local environment by running the following command: ```bash git clone https://github.com/camunda/c8-sm-checks.git cd c8-sm-checks git checkout v1.4.0 ``` ### Kubernetes connectivity scripts These scripts enable you to verify the connectivity and configuration of your Kubernetes cluster, including checks for deployment status, service availability, and Ingress configuration. #### Kubernetes permissions When utilizing the anomaly detection scripts within a Kubernetes environment, ensure the user has specific permissions: - **List pods**: Required for `kubectl get pods` to fetch pod details in the namespace. - **Execute commands in pods**: Necessary for running commands inside pods via `kubectl exec`. - **List services**: Needed for `kubectl get services` to retrieve service information. - **List ingresses**: Required by `kubectl get ingress` to obtain Ingress objects. - **Get Ingress details**: Necessary for `kubectl get ingress` to fetch Ingress configurations. #### Deployment check (`./checks/kube/deployment.sh`) This script checks the status of a Helm deployment in the specified namespace, ensuring that all required containers are present and ready. You can customize the list of containers to check based on your deployment topology. ```bash ./checks/kube/deployment.sh -n camunda-primary -d camunda -c "zeebe,zeebe-gateway,web-modeler" ``` #### Connectivity check (`./checks/kube/connectivity.sh`) This script verifies Kubernetes connectivity and associated configuration, checking for the presence of services and ingresses that conform to the required specifications. ```bash ./checks/kube/connectivity.sh -n camunda-primary ``` ### Zeebe connectivity scripts These scripts focus on verifying the connectivity and health of Zeebe components within your deployment. You can check token generation, gRPC connectivity, and other essential aspects of your Zeebe setup. #### gRPC Zeebe check (`./checks/zeebe/connectivity.sh`) This script verifies connectivity to a Zeebe instance using HTTP/2 and gRPC protocols, providing insights into the health and status of your Zeebe deployment. ```bash ./checks/zeebe/connectivity.sh -a https://local.distro.example.com/auth/realms/camunda-platform/protocol/openid-connect/token -i myclientid -s 0Rn28VrQxGNxowrCWe6wbujwFghO4990 -u orchestration-api -H zeebe.local.distro.example.com:443 ``` Find more information on [how to register your application on Identity](https://github.com/camunda-community-hub/camunda-8-examples/blob/main/payment-example-project/kube/README.md#4-generating-an-m2m-token-for-our-application). ### IRSA configuration check The AWS EKS IRSA configuration scripts are focused on verifying the correct setup of IAM Roles for Service Accounts (IRSA) within your Kubernetes deployment on AWS. These scripts ensure that your Kubernetes service accounts are correctly associated with IAM roles, allowing components like PostgreSQL, OpenSearch, and others in your deployment to securely interact with AWS resources. For detailed usage instructions and setup information, please refer to the [IRSA guide](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/irsa.md#irsa-check-script). ### Interpretation of the results Each script produces an output indicating the status of individual checks, which can be either `[OK]`, which signals a healthy status, or `[FAIL]`, which signals an unhealthy status. While the scripts continue execution even if a check fails, it may be necessary to review the logs to identify the failed element. At the end of each script, a global check status is provided, indicating whether any tests failed and the corresponding error code. For example: ``` [FAIL] ./checks/zeebe/connectivity.sh: At least one of the tests failed (error code: 5). ``` ### Handling errors If a check fails, it indicates a deviation from the expected configuration on a normal setup. Resolving the error involves studying the failed check and applying the best practices outlined in the documentation (use the search feature to find the associated recommendation for a failed check). For example: ``` [FAIL] None of the ingresses declare a gRPC upstream, which is required for the zeebe ingress. With ingress-nginx, the zeebe ingress must carry nginx.ingress.kubernetes.io/backend-protocol: GRPC (GRPCS for a TLS upstream). With Contour, the service behind it must carry projectcontour.io/upstream-protocol.h2c (.h2 for a TLS upstream) listing the gRPC port. ``` The message names the annotation each controller needs. For ingress-nginx, adjust the Ingress to include it. For Contour, set `projectcontour.io/upstream-protocol.h2c` on the Orchestration Cluster Service instead, or `projectcontour.io/upstream-protocol.h2` when the gRPC upstream uses TLS. You can also explore the source of the script to have a better understanding of the reason for the failure. :::note Sometimes, some checks may not be applicable to your setup if it's custom (for example, with the previous example the Ingress you use may not be [ingress-nginx](https://kubernetes.github.io/ingress-nginx/)). ::: ## Basic authentication Performance Throughput when using Basic authentication is very limited, supporting only a few API requests per second. Workloads greater than that which can be supported by Basic authentication may cause request processing to stall, as queued requests can time out before they are processed. Development and testing scenarios that are performance-sensitive may [disable authentication entirely](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md#no-authentication-local-development), or use [OIDC Authentication](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md#using-a-token-oidcjwt). ## Find available container image versions When working with custom registries or air-gapped environments, you may need to verify which image versions are available before deployment. For Camunda's own images, use [skopeo](https://github.com/containers/skopeo) to list available tags: ```shell # Open source images (no authentication required) skopeo --override-os linux inspect docker://registry.camunda.cloud/camunda/zeebe | jq '.RepoTags' ``` ## Incorrect authorizations when deploying resources from Modeler If you encounter missing or invalid authorizations when deploying resources or starting process instances from Camunda Hub or Desktop Modeler, review which credentials are being used: - **Camunda Hub** deploys as your logged-in user, so ensure that your [user](/components/admin/user.md) has the required permissions. - **Desktop Modeler** uses the client credentials you provide, so ensure that your [client](/components/admin/client.md) has the required permissions. ## Zeebe data loss after PVC deletion If all Zeebe data is lost after a PersistentVolumeClaim (PVC) was deleted, the likely cause is that your StorageClass uses the `Delete` reclaim policy instead of `Retain`. ### Symptoms - All process definitions and instances are gone - Zeebe brokers start fresh with no historical data - Secondary storage still has data, but Zeebe does not ### Root cause - Your StorageClass has `reclaimPolicy: Delete` (the default in most Kubernetes distributions) - A PVC was deleted (manually, by automation, or during cluster maintenance) - Kubernetes automatically deleted the underlying PersistentVolume and all its data ### How to check your current configuration ```bash # Check your StorageClass reclaim policy kubectl get storageclass # Look for the RECLAIMPOLICY column - it should show "Retain" for production workloads NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE gp3 ebs.csi.aws.com Delete WaitForFirstConsumer # ❌ Problem ebs-sc ebs.csi.aws.com Retain WaitForFirstConsumer # ✅ Correct ``` ### Solution 1. **Create a new StorageClass with `Retain` policy** before deploying Camunda: ```yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: camunda-storage provisioner: # e.g., ebs.csi.aws.com, disk.csi.azure.com reclaimPolicy: Retain volumeBindingMode: WaitForFirstConsumer ``` 2. **For existing PersistentVolumes**, you can patch them to use `Retain`: ```bash kubectl patch pv -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}' ``` ### Why this matters For stateful applications like Zeebe that store critical business data, the `Retain` policy is the industry-standard best practice. This ensures that even if a PVC is accidentally deleted, the underlying data remains intact and can be recovered. For more information, see the official Kubernetes documentation on [changing the reclaim policy of a PersistentVolume](https://kubernetes.io/docs/tasks/administer-cluster/change-pv-reclaim-policy/#why-change-reclaim-policy-of-a-persistentvolume). --- ## Administrator quickstart With this guide, you'll learn how to deploy Camunda 8 Self-Managed as an administrator. Select the deployment approach that best fits your needs: --- ## Configure Camunda 8 Run Use this page to configure Camunda 8 Run beyond the default local quickstart. ## Configuration options The following options provide a convenient way to override settings for quick tests and interactions in Camunda 8 Run. For more advanced or permanent configuration, modify the default `configuration/application.yaml` or supply a custom file using the `--config` flag. | Argument | Description | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `--config ` | Applies the specified Zeebe [`application.yaml`](/self-managed/components/orchestration-cluster/zeebe/configuration/configuration.md). | | `--extra-driver ` | Copies an external JDBC driver into `camunda-zeebe-/lib` before startup. Use this when running against Oracle, MySQL, or other databases that require a driver that is not bundled with Camunda 8 Run. Repeat the flag to copy multiple JARs. | | `--username ` | Configures the first user’s username as ``. | | `--password ` | Configures the first user’s password as ``. | | `--keystore ` | Configures the TLS certificate for HTTPS. If not specified, HTTP is used. For more information, see [enable TLS](#enable-tls). | | `--keystorePassword ` | Provides the password for the JKS keystore file. | | `--port ` | Sets the Camunda core port (default: `8080`). | | `--log-level ` | Sets the log level for the Camunda core. | | `--startup-url` | The URL to open after startup (for example, `http://localhost:8080/operate`). By default, Operate is opened. | | `--no-browser` | Skips opening a browser window after startup. Useful for headless or CI environments. | | `--physical-tenants ` | Selects comma-separated [Physical Tenants](#configure-physical-tenants) for this run only. Saved IDs retain their settings; unsaved IDs use startup defaults. | ## Enable authentication and authorization By default, Camunda 8 Run is optimized for local development. The web applications use local credentials, but the Orchestration Cluster API is unprotected and authorization checks are disabled. To protect API requests and enable authorization checks, update your `application.yaml`. Example configuration: ```yaml camunda: security: initialization: users: - username: demo password: demo name: Demo email: demo@example.com authentication: method: BASIC unprotected-api: false authorizations: enabled: true ``` Start Camunda 8 Run with the configuration: ```bash ./start.sh --config application.yaml ``` ```bash .\c8run.exe start --config application.yaml ``` Once enabled, API requests must include valid credentials. For example: ```shell curl --request GET 'http://localhost:8080/v2/topology' \ -u demo:demo \ --header 'Content-Type: application/json' \ --data-raw '{}' ``` To add additional users, extend the configuration: ```yaml camunda: security: initialization: users: - username: user password: user name: user email: user@example.com defaultRoles: admin: users: - user ``` ## Use Camunda APIs Camunda 8 Run exposes the Orchestration Cluster REST API locally by default at `http://localhost:8080/v2`. - For local development, Camunda 8 Run exposes the API without requiring credentials unless you enable API protection. - If you enable Basic authentication, include the configured username and password in your requests. - For API concepts, endpoints, and examples, use the [Orchestration Cluster REST API overview](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md). - For deployment-specific authentication details, use [Orchestration Cluster REST API authentication](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md). Quick connectivity check: ```bash curl http://localhost:8080/v2/topology ``` ## Use built-in and custom connectors Camunda 8 Run includes Connectors for local development. For custom connectors: 1. Place the connector JAR in the appropriate `custom_connectors` directory: ```bash # macOS/Linux c8run/custom_connectors/your-connector.jar # Windows c8run\custom_connectors\your-connector.jar ``` 2. Ensure the corresponding element template is available in a valid Desktop Modeler search path. 3. Restart Camunda 8 Run after adding or updating connectors. 4. Check `c8run/logs/connectors.log` if the connector fails to load. For connector secrets, add the value to the local secret store and reference it with `camunda.secrets.`. See [manage local secrets](#manage-local-secrets). For connector development and packaging details, see [Connector SDK](/components/connectors/custom-built-connectors/connector-sdk.md). ## Manage local secrets c8run stores local secret values in your platform's user data directory and configures the file secret store automatically. | Platform | Default directory | | -------- | ------------------------------------------------------------------------------------------------------------------ | | Linux | `${XDG_DATA_HOME}/camunda/c8run/secrets`, or `~/.local/share/camunda/c8run/secrets` when `XDG_DATA_HOME` isn't set | | macOS | `~/Library/Application Support/Camunda/C8Run/secrets` | | Windows | `%LOCALAPPDATA%\Camunda\C8Run\secrets` | Run `./c8run secrets path` to print the directory used by the current environment. Set a secret without placing the value in your command history: ```bash ./c8run secrets set OPENAI_API_KEY ``` Enter the value at the hidden prompt. Then reference it in Process Models with: ```feel =camunda.secrets.OPENAI_API_KEY ``` To set several secrets without creating a dotenv file, pass multiple names. Camunda 8 Run prompts for each value separately: ```bash ./c8run secrets set OPENAI_API_KEY SLACK_TOKEN ``` Secret names can contain letters, numbers, underscores, and dashes. In a FEEL expression, wrap a name containing dashes in backticks: ```feel =camunda.secrets.`openai-api-key` ``` Use the following commands to manage values: | Command | Purpose | | -------------------------------------- | ------------------------------------------------ | | `./c8run secrets set [name...]` | Prompt for and save one or more values. | | `./c8run secrets set --stdin` | Read a value from standard input for automation. | | `./c8run secrets list` | List secret names without showing their values. | | `./c8run secrets path` | Show the active local secrets directory. | | `./c8run secrets delete ` | Delete one secret. | | `./c8run secrets delete --all` | Delete all local secrets after confirmation. | | `./c8run secrets import [dotenv-file]` | Import `KEY=value` entries from a dotenv file. | | `./c8run secrets import -` | Import dotenv entries from standard input. | The commands have the following current safety boundaries: - Import immediately replaces values with matching names. The cache warning appears after the values are written, without advance confirmation. - Both `delete ` and `delete --all` prompt for confirmation. In noninteractive use, add `--yes`. - Secret names can contain dashes, but names beginning with `-` can't currently be passed to `set` or `delete `. For example, import a local dotenv file: ```bash ./c8run secrets import .env.secrets ``` Use a dedicated dotenv file for secret values. `c8run secrets` refuses to import the c8run `.env` file because it can contain runtime, download, and packaging credentials. Configure local secret management with the following environment variables. Set them in your environment or the c8run `.env` file before running secret commands or starting Camunda 8 Run. | Variable | Default | Valid values | Behavior | | ------------------------- | --------------------------- | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `C8RUN_SECRETS_MODE` | `local` | `local` or `external` | In `local` mode, c8run configures its file store. Set `external` whenever you configure another store. Local `c8run secrets` commands are then disabled. | | `C8RUN_SECRETS_DIR` | Platform-specific directory | An absolute or relative directory path | Sets the file-store path for local secret commands and startup. Relative paths resolve from the current working directory. | | `C8RUN_SECRETS_CACHE_TTL` | `20m` | A duration expressed as a whole number of minutes, with a minimum of `1m` | Sets how long Camunda caches resolved secret values. | For example, set the cache duration to one minute: ```bash C8RUN_SECRETS_CACHE_TTL=1m ./c8run start ``` To use another local directory for secret commands and startup, set the same path for both commands: ```bash C8RUN_SECRETS_DIR=./temporary-secrets ./c8run secrets set API_KEY C8RUN_SECRETS_DIR=./temporary-secrets ./c8run start ``` The default directory is shared across projects and c8run versions for the current operating-system user. Set a stable absolute `C8RUN_SECRETS_DIR` per project when the same secret name needs different values. c8run warns when the platform-default directory and the configured directory both contain entries. Run `./c8run secrets path` to confirm the active local directory. After rotating, importing, or deleting a value, existing cached resolutions can use the previous value until the cache entry expires. Restart Camunda 8 Run to clear the cache immediately. On Windows, use PowerShell or Command Prompt for hidden interactive entry. In Git Bash, prefix the command with `winpty`, or use `--stdin`. Local secret commands manage only the c8run file store. Set `C8RUN_SECRETS_MODE=external` whenever you configure another file path, AWS Secrets Manager, or Google Secret Manager through `--config`, `application.yaml`, or Spring environment settings. c8run doesn't detect explicit store configuration and otherwise configures its local default store. Use the external store's management tools instead of `c8run secrets`. The local secrets directory is for development only. For production, configure a supported managed secret store instead of reusing Camunda 8 Run secrets. ## Configure Physical Tenants Use Camunda 8 Run to try [Physical Tenants](/self-managed/concepts/physical-tenants/index.md) locally. Each Physical Tenant is an isolated engine inside one Camunda 8 Run instance, with its own data, users, secrets, and connector runtime. Use Camunda 8 Run 8.10.5+ for the Physical Tenant selection and listing behavior described here. Run `./c8run physical-tenants help` and `./c8run secrets help` to inspect the available options. Add a Physical Tenant, then start Camunda 8 Run: ```bash ./c8run physical-tenants add sales ./c8run start ./c8run physical-tenants list ./c8run stop ``` - The `default` Physical Tenant always exists and keeps the existing URLs, such as `http://localhost:8080/operate` and `http://localhost:8080/v2/`. - Each additional Physical Tenant is served under `http://localhost:8080/physical-tenants//`, for example `/physical-tenants/sales/operate` for the web applications and `/physical-tenants/sales/v2/` for the [Orchestration Cluster REST API](/self-managed/concepts/physical-tenants/api-routing.md). - Saved Physical Tenants start with every subsequent `./c8run start`. The startup summary lists each Physical Tenant's URLs and readiness. - `./c8run physical-tenants list` shows effective startup settings for active Physical Tenants and saved settings for stopped or pending Physical Tenants. Active IDs absent from the saved configuration appear as `active (not saved)`. Login and Connectors settings are `unknown` if legacy startup metadata doesn't record them. To select Physical Tenants for one run without changing the saved configuration, for example in CI: ```bash ./c8run start --physical-tenants sales,hr ``` Selected saved Physical Tenants retain their dedicated credentials and `--no-connectors` setting. Unsaved IDs use the startup login (`demo`/`demo` unless changed) and Connectors defaults. `--disable-connectors` disables all connector runtimes. Physical Tenant IDs follow these rules: | Rule | Value | | ------------------------------------------------------------------------------ | --------------------------------- | | Allowed characters | Lowercase letters and digits only | | Maximum length | 64 characters | | Maximum length with RDBMS secondary storage, including the bundled H2 database | Eight characters | | Reserved ID | `default` | H2 is for local development and evaluation only. ### Set Physical Tenant logins By default, each Physical Tenant uses the same login as `./c8run start` (`demo`/`demo`, unless you set `--username` and `--password`). To give a Physical Tenant its own user, add it with `--username`: ```bash ./c8run physical-tenants add hr --username alice ``` Camunda 8 Run prompts for the password without echoing it. For automation, pass the password on standard input with `--password-stdin`, which requires `--username` and accepts one Physical Tenant per command: ```bash printf '%s' "$HR_PASSWORD" | ./c8run physical-tenants add hr --username alice --password-stdin ``` `physical-tenants add` doesn't accept `--password`, so the password never appears in your shell history. Camunda 8 Run stores the password in the Physical Tenants file, which only your operating-system user can read. Generated startup metadata records usernames, Connectors enablement, and the Camunda port, but excludes passwords. Physical Tenant logins authenticate web application sessions. REST APIs remain unprotected by default, including Physical Tenant-prefixed endpoints; adding a Physical Tenant-specific login doesn't enable API protection. ### Manage Physical Tenant secrets and connectors Each Physical Tenant has its own local secrets and connector runtime. A Physical Tenant never resolves another Physical Tenant's `camunda.secrets.*` values. Use `--physical-tenant` with the [local secret commands](#manage-local-secrets): ```bash ./c8run secrets --physical-tenant sales set OPENAI_API_KEY ``` The `--physical-tenant=sales` form is also supported. Each Physical Tenant's connector runtime runs in a separate Java Virtual Machine (JVM) on the next free local port from `8087`. To skip it for one Physical Tenant, add the Physical Tenant with `--no-connectors`. To skip all connector runtimes, start with `--disable-connectors`. ### Remove Physical Tenants Use these commands to remove saved Physical Tenants: | Command | Purpose | | ---------------------------------------------- | ----------------------------------------------------- | | `./c8run physical-tenants remove [id...]` | Remove Physical Tenants from the saved configuration. | | `./c8run physical-tenants reset` | Remove all saved Physical Tenants and their logins. | | `./c8run physical-tenants path` | Show where Camunda 8 Run saves Physical Tenants. | Both `remove` and `reset` prompt for confirmation. In noninteractive use, add `--yes`. Removing a Physical Tenant changes only the saved configuration. A running instance keeps serving the Physical Tenant until you restart Camunda 8 Run. The Physical Tenant's data remains in secondary storage, so adding the same ID again restores it, including its users. Re-adding a Physical Tenant doesn't reset its existing users' passwords. Use the retained credentials when saving a login for an existing Physical Tenant ID. ### Troubleshoot Physical Tenant startup failures If the shared Camunda application starts but a Physical Tenant doesn't become ready, the startup summary shows `NOT READY` and `./c8run start` exits with an error naming the failed Physical Tenants. Camunda and the healthy Physical Tenants keep running. Check `log/camunda.log`, fix the reported configuration or storage problem, then run `./c8run stop` and `./c8run start`. For common causes, see [troubleshoot Physical Tenants](/self-managed/concepts/physical-tenants/troubleshooting.md). Invalid shared configuration can prevent the entire Camunda application from starting. For example, mixing RDBMS and `none` secondary-storage types across Physical Tenants fails shared configuration validation. Correct the incompatible storage settings before restarting. With API protection enabled, rejected readiness-probe credentials produce `up (unverified)` and a warning instead of a startup failure. The Physical Tenant is reachable, but Camunda 8 Run couldn't verify its storage readiness. If you reused a Physical Tenant ID, remove and re-add its saved configuration with the retained user's credentials, then stop and start Camunda 8 Run again. Removing and re-adding the saved configuration preserves the Physical Tenant's data. ### Configure where Physical Tenants are stored Two environment variables control where Camunda 8 Run saves Physical Tenants and whether it manages them: | Variable | Default | Behavior | | -------------------- | --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `C8RUN_TENANTS_FILE` | `physical-tenants.yaml` in the per-user Camunda 8 Run data directory. Run `./c8run physical-tenants path` to show it. | Sets the saved Physical Tenants file. Relative paths resolve from the current working directory. | | `C8RUN_TENANTS_MODE` | `local` | Set to `external` to disable the `physical-tenants` commands and saved Physical Tenants, so only your own configuration declares Physical Tenants. | If your `--config` file, `CAMUNDA_PHYSICALTENANTS_*` environment variables, or `JAVA_OPTS` already declare `camunda.physical-tenants`, Camunda 8 Run uses that configuration as-is and ignores its saved Physical Tenants. In that case, `--physical-tenants` fails with an error, so use either `--physical-tenants` or your own configuration. Physical Tenants managed by Camunda 8 Run require `C8RUN_SECRETS_MODE=local`, the default. When Camunda 8 Run manages Physical Tenants locally, an unreadable or invalid saved configuration prevents startup, including with `--physical-tenants`. Correct the reported file or permissions error before retrying. ### Manage Physical Tenants with c8ctl [c8ctl](/apis-tools/c8ctl/getting-started.md) delegates Physical Tenant management through `c8ctl cluster physical-tenants` and scoped secret management through `c8ctl cluster secrets --physical-tenant `. Use `c8ctl cluster start --physical-tenants ` to select Physical Tenants for one run, with the same selection and listing behavior described above. These commands use the same Physical Tenants file and secrets as Camunda 8 Run. Relative `C8RUN_TENANTS_FILE`, `C8RUN_SECRETS_DIR`, and secret import paths resolve against the caller's working directory. Physical Tenants are isolated engines; logical tenants provide tenancy within an engine. ## Enable TLS TLS can be enabled by providing a local keystore file using the [`--keystore` and `--keystorePassword` configuration options](#configuration-options) at startup. Camunda 8 Run accepts `.jks` certificate files. Although Camunda 8 Run supports TLS, this is intended only for testing. :::note If you use a proxy together with TLS, ensure internal Camunda services are excluded from proxy routing. JVM-level proxy settings apply to all internal HTTP clients and may block communication between components such as Zeebe, Operate, Admin, or the connector runtime. Add these services to your `nonProxyHosts` configuration. For details, see [HTTP proxy configuration](/self-managed/components/connectors/http-proxy-configuration.md). ::: ## Access metrics Metrics are enabled in Camunda 8 Run by default and can be accessed at [http://localhost:9600/actuator/prometheus](http://localhost:9600/actuator/prometheus). For more information, see the [metrics](/self-managed/operational-guides/monitoring/metrics.md) documentation. ## Environment variables The following advanced configuration options can be provided via environment variables: | Variable | Description | | ----------- | ---------------------------------------------------------------- | | `JAVA_OPTS` | Allows you to override Java command line parameters for Camunda. | ## Next steps - Review [configure secondary storage in Camunda 8 Run](./secondary-storage.md). - Review [install and start Camunda 8 Run](./install-start.md). - Identify and resolve [common issues when starting, configuring, or using Camunda 8 Run](../c8run-troubleshooting.md). --- ## Install and start Camunda 8 Run Use this page to install Camunda 8 Run locally, start it on macOS, Linux, or Windows, and shut it down cleanly. ## Prerequisites - **[Desktop Modeler](/components/modeler/desktop-modeler/install-the-modeler.md)** - **If using Ubuntu**: Ubuntu 22.04 or newer ## Install and start Camunda 8 Run 1. Download the latest release of for your operating system and architecture. Opening the `.tgz` file extracts the Camunda 8 Run script into a new directory. 2. Navigate to the new `c8run` directory. 3. Start Camunda 8 Run by following the steps below, depending on your operating system. Run the helper script: ```bash ./start.sh ``` Or use the CLI command: ```bash ./c8run start ``` Use the CLI command: ```bash .\c8run.exe start ``` If startup is successful, a browser window for Operate will open automatically. Alternatively, you can access Operate at [http://localhost:8080/operate](http://localhost:8080/operate). :::note If Camunda 8 Run fails to start, run the [shutdown script](#shut-down-camunda-8-run) to end the current processes, then run the start script again. ::: For container-based local deployments, see the [developer quickstart with Docker Compose](../docker-compose.md). For CLI flags and permanent configuration, see [configure Camunda 8 Run](./configuration.md). ## Shut down Camunda 8 Run To shut down Camunda 8 Run and end all running processes, run the following command from the `c8run` directory: ```bash ./shutdown.sh ``` ```bash .\c8run.exe stop ``` For Docker Compose environments, use the stop commands in the [developer quickstart with Docker Compose](../docker-compose/install-start.md#stop-camunda-8-with-docker-compose). ## Next steps - Review [configure Camunda 8 Run](./configuration.md). - Review [configure secondary storage in Camunda 8 Run](./secondary-storage.md). - Identify and resolve [common issues when starting, configuring, or using Camunda 8 Run](../c8run-troubleshooting.md). --- ## Configure secondary storage in Camunda 8 Run Camunda 8 Run supports multiple [secondary storage](/reference/glossary.md#secondary-storage) options. **H2 is the default secondary storage** for lightweight Camunda 8 Run setups and quickstarts. If you need full-text indexing, search, or advanced analytics, use an external Elasticsearch instance. ## Configure or switch secondary storage (H2 or Elasticsearch) Choose the backend that fits your local development and testing needs. ### Default: H2 (Camunda 8 Run) The default Camunda 8 Run configuration uses an H2 database for secondary storage. This is convenient for local development and demos and stores data on disk. File-based H2 example: ```yaml camunda: data: secondary-storage: type: rdbms rdbms: url: jdbc:h2:file:./camunda-data/h2db username: sa password: ```
Full example configuration ```yaml camunda: data: secondary-storage: type: rdbms rdbms: url: jdbc:h2:file:./camunda-data/h2db username: sa password: flushInterval: PT0.5S queueSize: 1000 security: initialization: users: - username: demo password: demo name: Demo email: demo@example.com authentication: method: BASIC unprotected-api: true authorizations: enabled: false zeebe: broker: network: host: localhost advertisedHost: localhost gateway: cluster: initialContactPoints: zeebe:26502 memberId: identity spring: profiles: active: "broker,consolidated-auth,identity,tasklist" ```
### External relational database options Use an external relational database for secondary storage: PostgreSQL, MariaDB, MySQL, Oracle, or Microsoft SQL Server. 1. Set `camunda.data.secondary-storage` with the JDBC URL and credentials. 2. Ensure the database is reachable before starting Camunda 8 Run. 3. If you already run a database, reuse the same configuration and update the URL, host or port, and credentials. 4. If a separate JDBC driver is required, add it with `--extra-driver` or drop the JAR into `camunda-zeebe-/lib`. **Secondary storage config** ```yaml camunda: data: secondary-storage: type: rdbms rdbms: url: jdbc:postgresql://localhost:5432/camunda_secondary username: camunda password: camunda ``` **Start database (Docker)** ```bash docker run -d --name camunda-postgres \ -e POSTGRES_USER=camunda \ -e POSTGRES_PASSWORD=camunda \ -e POSTGRES_DB=camunda_secondary \ -p 5432:5432 postgres:latest ``` **Secondary storage config** ```yaml camunda: data: secondary-storage: type: rdbms rdbms: url: jdbc:mariadb://localhost:3306/camunda_secondary?serverTimezone=UTC username: camunda password: camunda ``` **Start database (Docker)** ```bash docker run -d --name camunda-mariadb \ -e MARIADB_USER=camunda \ -e MARIADB_PASSWORD=camunda \ -e MARIADB_ROOT_PASSWORD=rootcamunda \ -e MARIADB_DATABASE=camunda_secondary \ -p 3306:3306 mariadb:11.4 ``` :::note No extra driver is required. MariaDB ships with the distribution. ::: **Secondary storage config** ```yaml camunda: data: secondary-storage: type: rdbms rdbms: url: jdbc:mysql://localhost:3307/camunda_secondary?serverTimezone=UTC username: camunda password: camunda ``` **Start database (Docker)** ```bash docker run -d --name camunda-mysql \ -e MYSQL_ROOT_PASSWORD=rootcamunda \ -e MYSQL_USER=camunda \ -e MYSQL_PASSWORD=camunda \ -e MYSQL_DATABASE=camunda_secondary \ -p 3307:3306 mysql:9.7 ``` :::note MySQL requires the official Connector/J driver. Copy the JAR into `camunda-zeebe-/lib` or pass `--extra-driver /path/to/mysql-connector.jar` to `./c8run start`. Ensure the JDBC URL uses the host port you expose. ::: **Secondary storage config** ```yaml camunda: data: secondary-storage: type: rdbms rdbms: url: jdbc:oracle:thin:@//localhost:1521/FREEPDB1 username: camunda password: camunda ``` **Start database (Docker)** ```bash docker run -d --name camunda-oracle \ -p 1521:1521 \ -e ORACLE_PASSWORD=camunda \ -e APP_USER=camunda \ -e APP_USER_PASSWORD=camunda \ gvenzl/oracle-free:23-slim ``` :::note Download the Oracle JDBC driver, for example `ojdbc11.jar`, and place it in `camunda-zeebe-/lib` or pass `--extra-driver /path/to/ojdbc11.jar`. ::: **Secondary storage config** ```yaml camunda: data: secondary-storage: type: rdbms rdbms: url: jdbc:sqlserver://localhost:1433;databaseName=camunda_secondary;encrypt=false username: camunda password: Camunda123! ``` **Start database (Docker)** ```bash docker run -d --name camunda-mssql \ -e ACCEPT_EULA=Y \ -e MSSQL_SA_PASSWORD=Camunda123! \ -p 1433:1433 \ mcr.microsoft.com/mssql/server:2022-latest ``` ### Optional: Elasticsearch If you need indexing, search, or full Operate or Tasklist functionality, use an external Elasticsearch instance. To use Elasticsearch: ```yaml camunda: data: secondary-storage: type: elasticsearch elasticsearch: url: http://localhost:9200/ ``` Start Camunda 8 Run with `--config ` and point the configuration to your external cluster. ## Switching between storage types and migration notes - Switching the secondary storage type, for example H2 to Elasticsearch, does **not** preserve existing secondary-store data. The system starts with a fresh secondary store. - To switch storage, update `camunda.data.secondary-storage` in `application.yaml` and restart Camunda 8 Run. Choose **H2** for quick local development and other supported **RDBMS** or **Elasticsearch** for production-like scenarios. ## Primary vs. secondary storage Camunda 8 uses two layers of storage: - **[Primary storage](/reference/glossary.md#primary-storage)** is handled by the Zeebe Broker to store workflow execution data. - **[Secondary storage](/reference/glossary.md#secondary-storage)** is used by applications like Operate, Tasklist, and Admin to read and present that data. For more details on how these layers interact, see [secondary storage architecture](/self-managed/concepts/secondary-storage/index.md). Camunda 8 Run uses v2 APIs by default, so you don't need to reconfigure the APIs when switching between secondary storage backends. ## Known limitations - H2 is intended for testing and local development only. - H2 data persists to the configured file path by default. Keep the path stable to avoid accidental data loss. - Performance and memory use may vary depending on the local environment. ## Next steps - Review [RDBMS version support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md). - Review [backup and restore for RDBMS](/self-managed/operational-guides/backup-restore/rdbms/backup.md). - Identify and resolve [common issues when starting, configuring, or using Camunda 8 Run](../c8run-troubleshooting.md). --- ## Troubleshoot Camunda 8 Run Camunda 8 Run provides log files in the `c8run/log` directory that can help diagnose most issues. Check these logs first when troubleshooting: - `camunda.log` – main log for Camunda 8 Run - `connectors.log` – Connectors component If you configured external Elasticsearch, inspect that deployment's logs separately. ## Startup failures ### Port conflicts **Problem:** Camunda 8 Run fails to start because ports are already in use. **Solution:** 1. Check if the default ports are already occupied: - `8080` – Camunda core (Operate, Tasklist, Admin, APIs) - `8086` – Connectors API - `26500` – Zeebe gRPC gateway - `9600` – Prometheus metrics 2. Stop processes using these ports or change the Camunda core port: ```bash # macOS/Linux lsof -i :8080 # Windows netstat -ano | findstr :8080 # Start Camunda using a different port ./c8run start --port 8081 ``` 3. If you also run the Docker Compose quickstart or other local containers, ensure they are not using these ports: ```bash docker ps docker stop ``` ### Java version issues **Problem:** Camunda 8 Run fails to start with a Java-related error. **Solution:** Camunda 8 Run includes a bundled Java runtime. In most cases, no Java installation is needed. If the bundled runtime is missing or corrupted, Camunda 8 Run falls back to the system JDK. 1. Re-download and extract the Camunda 8 Run archive to restore the bundled runtime. 2. If you want to use a system JDK instead, ensure `JAVA_HOME` points to OpenJDK 21–25 and verify it is set correctly: ```bash # macOS/Linux echo $JAVA_HOME java -version # Windows echo %JAVA_HOME% ``` 3. Set `JAVA_HOME` if needed: ```bash # macOS export JAVA_HOME=$(/usr/libexec/java_home -v 21) # Linux export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 # Windows (PowerShell) setx JAVA_HOME "C:\Program Files\Java\jdk-21" ``` Replace `21` with your installed version (21–25), and open a new terminal after setting `JAVA_HOME`. ### Unexpected JVM flags in logs **Problem:** Camunda 8 Run appends `--enable-native-access=ALL-UNNAMED` and `--sun-misc-unsafe-memory-access=allow` to `JDK_JAVA_OPTIONS` when using Java 25 or newer. This may appear in logs or interfere with tools that inspect JVM options. **Solution:** This is expected behavior. These flags are required for Java 25 compatibility and are appended automatically. Any values you set in `JDK_JAVA_OPTIONS` beforehand are preserved. ### Incomplete startup **Problem:** Camunda 8 Run starts but some components fail to load or the browser does not open. **Solution:** 1. Stop Camunda: ```bash # macOS/Linux ./c8run stop # Windows c8run.exe stop ``` 2. Start it again: ```bash # macOS/Linux ./c8run start # Windows c8run.exe start ``` 3. Access components manually if the browser does not open automatically: - Operate: [http://localhost:8080/operate](http://localhost:8080/operate) - Tasklist: [http://localhost:8080/tasklist](http://localhost:8080/tasklist) ## Memory and performance issues ### Out of memory errors **Problem:** Camunda 8 Run becomes unresponsive. **Solution:** 1. Increase JVM heap for Camunda: ```bash # macOS/Linux export JAVA_OPTS="-Xmx4g" # Windows (Command Prompt) set JAVA_OPTS=-Xmx4g # Windows (PowerShell) $env:JAVA_OPTS="-Xmx4g" ``` 2. For resource-constrained environments, consider using H2 instead of Elasticsearch for testing. ### Slow performance **Problem:** Camunda 8 Run is slow or processes take a long time to appear in Operate. **Solution:** 1. Ensure the system meets the minimum requirements (8 GB RAM recommended). 2. Close unnecessary applications to free system resources. 3. If you use external Elasticsearch, check cluster health: ```bash curl http://localhost:9200/_cluster/health ``` On Windows, open this page directly: [http://localhost:9200/\_cluster/health](http://localhost:9200/_cluster/health) ## External Elasticsearch issues ### Cannot connect to Elasticsearch **Problem:** Camunda 8 Run starts with Elasticsearch configured as secondary storage, but search-backed features do not work or startup fails. **Solution:** 1. Verify the Elasticsearch cluster is reachable: ```bash curl http://localhost:9200 ``` 2. Confirm `application.yaml` points Camunda 8 Run to that cluster: ```yaml camunda: data: secondary-storage: type: elasticsearch elasticsearch: url: http://localhost:9200/ ``` 3. If the cluster requires authentication or TLS, add the corresponding credentials and security settings to the same configuration block. 4. Start Camunda 8 Run with the configuration file: ```bash # macOS/Linux ./c8run start --config custom-application.yaml # Windows c8run.exe start --config custom-application.yaml ``` ### Elasticsearch index or permission errors **Problem:** Operate or Tasklist show errors related to Elasticsearch indices. **Solution:** 1. Ensure the Elasticsearch user has permission to create, read, and write the required Camunda indices. For restricted setups, see [Configure Elasticsearch without cluster privileges](/self-managed/concepts/databases/elasticsearch/elasticsearch-without-cluster-privileges.md). 2. Verify the Elasticsearch cluster is healthy and has sufficient free disk space. 3. For local development, if you need to recreate the secondary store, stop Camunda 8 Run, delete the Camunda indices from your external Elasticsearch instance using your Elasticsearch tooling, and start Camunda 8 Run again. This rebuilds the secondary store from scratch. ## Authentication and access issues ### Cannot log in to web interfaces **Problem:** Default credentials (demo/demo) do not work or the login page does not appear. **Solution:** 1. Verify authentication settings (default: demo/demo). 2. If custom authentication is configured in `application.yaml`, ensure it is correct: ```yaml camunda: security: authentication: method: BASIC initialization: users: - username: demo password: demo ``` 3. Clear browser cache and cookies, then try again. 4. If you used command-line overrides at startup (such as `--username` or `--password`), ensure the values are correct: ```bash # macOS/Linux ./start.sh --username myuser --password mypassword # Windows c8run.exe start --username myuser --password mypassword ``` ### API authentication errors **Problem:** API calls fail with authentication errors even when authentication is disabled by default. **Solution:** 1. Verify that API authentication is disabled (this is the default): ```yaml camunda: security: authentication: unprotected-api: true ``` 2. If API authentication is enabled, include credentials in your API requests: ```bash curl -u demo:demo http://localhost:8080/v2/topology ``` Windows (PowerShell alternative without curl): ```powershell $pair = "demo:demo" $bytes = [System.Text.Encoding]::ASCII.GetBytes($pair) $base64 = [System.Convert]::ToBase64String($bytes) Invoke-WebRequest -Uri http://localhost:8080/v2/topology -Headers @{Authorization="Basic $base64"} ``` ## Configuration issues ### Custom configuration not loading **Problem:** Changes in `application.yaml` do not take effect. **Solution:** 1. Pass the configuration file explicitly: ```bash # macOS/Linux ./start.sh --config /path/to/application.yaml # Windows c8run.exe start --config C:\path\to\application.yaml ``` 2. Verify that the YAML syntax is correct (spacing, indentation, no tabs). 3. Fully restart Camunda 8 Run after making configuration changes. ### TLS/HTTPS issues **Problem:** HTTPS is not working or certificate errors occur. **Solution:** 1. Verify the keystore file path, format, and password: ```bash # macOS/Linux ./start.sh --keystore /path/to/keystore.jks --keystorePassword yourpassword # Windows c8run.exe start --keystore C:\path\to\keystore.jks --keystorePassword yourpassword ``` 2. Remember that TLS support in Camunda 8 Run is intended for testing only, not for production environments. 3. Validate the keystore: ```bash keytool -list -keystore keystore.jks ``` ## Data and persistence issues ### Lost data after restart **Problem:** Data such as deployments, process instances, or users disappears after restarting Camunda 8 Run. **Solution:** 1. Camunda 8 Run stores H2 data on disk by default, so data persists across restarts. If your configuration uses an in-memory H2 URL (`jdbc:h2:mem:`), data is lost when Camunda 8 Run stops. Switch to a file-based URL so data is written to disk: ```yaml camunda: data: secondary-storage: type: rdbms rdbms: url: jdbc:h2:file:./camunda-data/h2db ``` 2. Keep the H2 file path the same between restarts. A different path opens a different, empty database. 3. Check that the application has permission to write to the data directory (for example, `camunda-data/` or any configured mount path). ## Connector issues ### Custom connectors not loading **Problem:** Custom connector JARs are not recognized by Camunda 8 Run. **Solution:** 1. Verify that the connector JAR file is placed in the correct directory: ```bash # macOS/Linux c8run/custom_connectors/your-connector.jar # Windows c8run\custom_connectors\your-connector.jar ``` 2. Ensure that the corresponding element template is available in a valid Desktop Modeler search path. 3. Restart Camunda 8 Run after adding or updating connectors. 4. Check the `connectors.log` file for specific error messages that may explain why the connector failed to load. ### Connector secrets not working **Problem:** Connectors cannot access configured secrets. **Solution:** 1. For non-Docker mode, add each secret to the local secret store, then reference it with `camunda.secrets.`. For details, see [manage local secrets](./c8run/configuration.md#manage-local-secrets). ```bash # macOS/Linux ./c8run secrets set MY_SECRET_KEY # Windows c8run.exe secrets set MY_SECRET_KEY ``` 2. For the Docker Compose setup, add secrets to the `connector-secrets.txt` file located in the Docker Compose folder. 3. Restart Camunda 8 Run after adding or modifying secrets. ## Ubuntu-specific issues **Problem:** Camunda 8 Run fails to start or behaves unpredictably on older Ubuntu versions. **Solution:** Use **Ubuntu 22.04 or newer**, as earlier versions may not support required Java versions or system dependencies required by Camunda 8 Run. --- ## Developer quickstart – Camunda 8 Run :::note Camunda 8 Run provides a lightweight, self-managed environment for local development and prototyping. It is not intended for production use. For production deployments, install the Orchestration Cluster manually as a Java application. For detailed steps, see the [manual installation](/self-managed/deployment/manual/install.md) guide. ::: Camunda 8 Run is a local distribution of Camunda 8 that bundles the Camunda 8 runtime, core services, startup scripts, and a launcher application for Windows, macOS, and Linux. Camunda 8 Run enables you to run the [Orchestration Cluster](../../../reference/glossary.md#orchestration-cluster) with minimal configuration. It is intended for developers who want to model BPMN diagrams, deploy them, and interact with running [process instances](/reference/glossary.md#process-instance) in a simple environment. Camunda 8 Run includes the following: - Orchestration Cluster - [Connectors](/reference/glossary.md#connector) - H2 (default [secondary storage](/reference/glossary.md#secondary-storage) for Camunda 8 Run) Camunda 8 Run also supports document storage and management with [document handling](/self-managed/concepts/document-handling/overview.md). :::note For the latest list of supported relational databases and versions, see the [RDBMS version support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md). ::: ## Pages in this section Step through the Camunda 8 Run guide via the following topics: - [Install and start](./c8run/install-start.md) - [Configure](./c8run/configuration.md) - [Manage local secrets](./c8run/configuration.md#manage-local-secrets) - [Configure secondary storage](./c8run/secondary-storage.md) - [Troubleshoot](./c8run-troubleshooting.md) ## Common tasks If you are looking for a specific task from the previous single-page guide, use the links below. ### Install and start Camunda 8 Run {#install-and-start-camunda-8-run} For prerequisites, local startup, and shutdown steps, see [install and start Camunda 8 Run](./c8run/install-start.md). For container-based local deployment, see the [developer quickstart with Docker Compose](./docker-compose.md). ### Configuration options {#configuration-options} For CLI flags, startup overrides, TLS flags, and `--config` usage, see [configure Camunda 8 Run](./c8run/configuration.md#configuration-options). ### Enable authentication and authorization {#enable-authentication-and-authorization} For `application.yaml` examples that protect the API and enable authorization checks, see [enable authentication and authorization](./c8run/configuration.md#enable-authentication-and-authorization). ### Use Camunda APIs {#use-camunda-apis} For local API endpoints, authentication expectations, and links to the canonical API docs, see [use Camunda APIs](./c8run/configuration.md#use-camunda-apis). ### Use built-in and custom connectors {#use-built-in-and-custom-connectors} For local connector usage, custom connector placement, and connector secrets guidance, see [use built-in and custom connectors](./c8run/configuration.md#use-built-in-and-custom-connectors). ### Manage local secrets For local `camunda.secrets.` values and dotenv import, see [manage local secrets](./c8run/configuration.md#manage-local-secrets). ### Configure or switch secondary storage (H2 or Elasticsearch) {#configure-or-switch-secondary-storage-h2-or-elasticsearch} For H2, external relational databases, Elasticsearch, and migration notes, see [configure secondary storage in Camunda 8 Run](./c8run/secondary-storage.md#configure-or-switch-secondary-storage-h2-or-elasticsearch). #### Default: H2 (Camunda 8 Run) {#default-h2-camunda-8-run} For the default H2 configuration and limitations, see [default H2 in Camunda 8 Run](./c8run/secondary-storage.md#default-h2-camunda-8-run). #### External relational database options {#external-relational-database-options} For PostgreSQL, MariaDB, MySQL, Oracle, and Microsoft SQL Server examples, see [external relational database options](./c8run/secondary-storage.md#external-relational-database-options). ### Shut down Camunda 8 Run {#shut-down-camunda-8-run} For local shutdown commands, see [shut down Camunda 8 Run](./c8run/install-start.md#shut-down-camunda-8-run). ## Next steps - Review [backup and restore for RDBMS](/self-managed/operational-guides/backup-restore/rdbms/backup.md). - Identify and resolve [common issues when starting, configuring, or using Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run-troubleshooting.md). --- ## Configure Docker Compose environments Use this page to choose the Docker Compose file that matches your local setup, find component URLs, and review authentication defaults. ## Choose a Docker Compose configuration Camunda provides three Docker Compose configurations in the [Camunda Distributions releases](https://github.com/camunda/camunda-distributions/releases): | Configuration file | Description | | :------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `docker-compose.yaml` | Default lightweight configuration. Includes the Orchestration Cluster and Connectors, and uses H2 secondary storage by default. Use this for most local development scenarios. | | `docker-compose-full.yaml` | Full configuration. Includes the Orchestration Cluster, Connectors, Optimize, Camunda Hub, Management Identity, Keycloak, PostgreSQL, and Elasticsearch. Use this when you need management components, process optimization, or browser-based modeling. | | `docker-compose-hub.yaml` | Standalone Camunda Hub configuration. Runs Camunda Hub and its dependencies without an Orchestration Cluster. For deployment details, see [deploy with Camunda Hub](./connectors-and-modeling.md#deploy-with-camunda-hub). | To start a specific configuration, run one of the following commands: - Default lightweight configuration: ```shell docker compose up -d ``` - Full configuration: ```shell docker compose -f docker-compose-full.yaml up -d ``` - Standalone Camunda Hub: ```shell docker compose -f docker-compose-hub.yaml up -d ``` :::note The Orchestration Cluster uses file-based H2 secondary storage by default. The PostgreSQL containers in the full configuration store Management Identity and Camunda Hub data, not Orchestration Cluster data. The full configuration starts Elasticsearch for Optimize. To select another Orchestration Cluster backend, see [configure secondary storage with Docker Compose](./secondary-storage.md). ::: ## Customize application configuration The extracted distribution mounts component-owned application YAML into the Camunda containers. Use the file that belongs to your Compose setup and component. | Setup and component | Application configuration source | | :-------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Lightweight Orchestration Cluster | `configuration/${ORCHESTRATION_CONFIG_FILE}`; defaults to `configuration/application-h2.yaml` | | Lightweight Connectors | Inline `connectors-config` under `configs` in `docker-compose.yaml` | | Full Orchestration Cluster | `.orchestration/application.yaml` | | Full Connectors | `.connectors/application.yaml` | | Full Optimize | `.optimize/environment-config.yaml` and `.optimize/application-ccsm.yaml` | | Full and standalone Management Identity | `.identity/application.yaml`; the standalone-only client overlay remains inline in `docker-compose-hub.yaml` | | Full and standalone Camunda Hub | `.hub/application.yaml` configures the standalone Camunda Hub and its dependencies. The full setup mounts `.hub/application-full.yaml` as the primary file, which adds the cluster registrations and imports the shared file | Choose the configuration mechanism based on the value you need to change: | Goal | Configuration method | | :---------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Change a component's application defaults | Edit the component-owned YAML file. Keep the existing authentication and component wiring when you change a subsection. | | Change a provided runtime value or secret | Edit `.env`. The mounted YAML resolves placeholders such as `${VARIABLE:default}` from the container environment. | | Maintain a separate environment set | Copy the complete `.env` file, update the copy, and run `docker compose --env-file ...`. The custom file must retain image versions and other required values. `--env-file` only replaces the variable interpolation source, and the Camunda services still load `.env` itself through `env_file`, so keep both files in sync. | | Override an additional Spring property | Add the environment variable to the relevant service in `docker-compose.override.yaml`. Spring environment variables override values from mounted application YAML. | | Provide centralized secrets | Create one file per secret in the included `secrets/` directory and reference it with `=camunda.secrets.NAME`. See [use centralized secrets](./connectors-and-modeling.md#use-centralized-secrets). | | Provide legacy connector secrets | Add local development secrets to `connector-secrets.txt` for `{{secrets.NAME}}` references. See [use legacy connector secrets](./connectors-and-modeling.md#use-legacy-connector-secrets). Do not put connector credentials in application YAML. | PostgreSQL, Keycloak, Camunda Hub WebSockets, and other non-Spring services continue to use the environment settings defined by their Compose services. Keep the distribution's example credentials for local development only. ## Access components Once the containers are running, you can access the components in your browser. Use the following default credentials for web interfaces: - **Username:** `demo` - **Password:** `demo` ### Orchestration Cluster The Orchestration Cluster is the core of Camunda 8 and provides process automation capabilities. | Component | URL | Description | | :----------------------------- | :--------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Operate | [http://localhost:8080/operate](http://localhost:8080/operate) | Monitor and troubleshoot process instances. See [Introduction to Operate](/components/operate/operate-introduction.md) and [Process instance creation](/components/concepts/process-instance-creation.md). | | Tasklist | [http://localhost:8080/tasklist](http://localhost:8080/tasklist) | Complete user tasks in running process instances. See [User tasks](/components/modeler/bpmn/user-tasks/user-tasks.md). | | Orchestration Cluster Admin | [http://localhost:8080/admin](http://localhost:8080/admin) | Manage users and permissions in the lightweight configuration. | | Orchestration Cluster REST API | `http://localhost:8080/v2` | REST API for process automation. | | Orchestration Cluster gRPC API | `localhost:26500` | gRPC API for high-performance process automation. | ### Management and modeling components The following components are available in the full configuration only: | Component | URL | Description | | :------------------ | :--------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Optimize | [http://localhost:8083](http://localhost:8083) | [Analyze and improve](/components/optimize/what-is-optimize.md) process performance. | | Management Identity | [http://localhost:8084](http://localhost:8084) | [Manage users](/self-managed/components/management-identity/overview.md) for Camunda Hub and Optimize. | | Camunda Hub | [http://localhost:8070](http://localhost:8070) | Manage clusters and model [BPMN](/components/modeler/bpmn/bpmn.md) processes, [DMN](/components/modeler/dmn/dmn.md) decisions, and [forms](/components/modeler/forms/camunda-forms-reference.md). | ### External dependencies | Component | Configuration | URL | Description | | :------------ | :------------ | :----------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Elasticsearch | Full | [http://localhost:9200](http://localhost:9200) | Started by the full configuration. Used by Optimize and the Orchestration Cluster's `elasticsearch` exporter. Point the `ELASTICSEARCH_*` values in `.env` at another endpoint to use an externally managed instance. | | Keycloak | Full | [http://localhost:18080/auth/](http://localhost:18080/auth/) | OIDC provider for Management Identity. The lightweight configuration uses the embedded Orchestration Cluster Admin instead. Access Keycloak with `admin` / `admin`. | | PostgreSQL | Full | Internal only | Database for Management Identity and Camunda Hub. This database is separate from Orchestration Cluster secondary storage. | ## Authentication :::note By default, the lightweight configuration uses [Basic authentication for the Orchestration Cluster](/self-managed/concepts/authentication/authentication-to-orchestration-cluster.md#basic-authentication). The full configuration uses Keycloak for [Management Identity authentication](/self-managed/concepts/authentication/authentication-to-management-components.md). ::: ### Lightweight configuration - **Web UI:** Log in to Operate and Tasklist with `demo` / `demo`. - **APIs:** REST and gRPC APIs are publicly accessible by default. ### Full configuration - **Web UI:** Log in to Operate, Tasklist, Optimize, and Camunda Hub with `demo` / `demo`. - **APIs:** REST and gRPC APIs require OAuth with the following settings: - **Client ID:** `orchestration` - **Client secret:** `secret` - **OAuth URL:** `http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token` - **Audience:** `orchestration-api` For details, see [Orchestration Cluster REST API authentication](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-authentication.md). ## Enable multi-tenancy [Multi-tenancy](/components/concepts/multi-tenancy.md) requires an authenticated API. How you enable it depends on the configuration you run. ### Lightweight configuration Create a `docker-compose.override.yaml` next to the compose file that protects the API and switches on the tenancy checks: ```yaml services: orchestration: environment: CAMUNDA_SECURITY_AUTHENTICATION_UNPROTECTEDAPI: "false" CAMUNDA_SECURITY_MULTITENANCY_CHECKSENABLED: "true" CAMUNDA_SECURITY_MULTITENANCY_APIENABLED: "true" connectors: environment: CAMUNDA_CLIENT_AUTH_METHOD: basic CAMUNDA_CLIENT_AUTH_USERNAME: demo CAMUNDA_CLIENT_AUTH_PASSWORD: demo ``` Start the stack with `docker compose up -d` and manage tenants through the [Orchestration Cluster API](/apis-tools/orchestration-cluster-api-rest/specifications/create-tenant.api.mdx) or the Orchestration Cluster Admin UI at [http://localhost:8080/admin](http://localhost:8080/admin): ```bash # Create a tenant curl -u demo:demo -X POST http://localhost:8080/v2/tenants \ -H 'Content-Type: application/json' -d '{"tenantId": "tenant-a", "name": "Tenant A"}' # Assign the demo user to it curl -u demo:demo -X PUT http://localhost:8080/v2/tenants/tenant-a/users/demo ``` With the API protected, clients must authenticate with Basic authentication (`camunda.client.auth.method=basic` plus username and password in the Camunda client SDKs). ### Full configuration The full configuration already protects the API through Keycloak, so only the tenancy checks need to be switched on. Add the following to `.env`: ```bash CAMUNDA_SECURITY_MULTITENANCY_CHECKSENABLED=true CAMUNDA_SECURITY_MULTITENANCY_APIENABLED=true ``` Start the stack with `docker compose -f docker-compose-full.yaml up -d` and manage tenants through the [Orchestration Cluster API](/apis-tools/orchestration-cluster-api-rest/specifications/create-tenant.api.mdx) with an OAuth token, or the Orchestration Cluster Admin UI at [http://localhost:8080/admin](http://localhost:8080/admin): ```bash TOKEN=$(curl -s -X POST 'http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token' \ -d 'grant_type=client_credentials' -d 'client_id=orchestration' -d 'client_secret=secret' | jq -r .access_token) # Create a tenant curl -X POST http://localhost:8080/v2/tenants -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' -d '{"tenantId": "tenant-a", "name": "Tenant A"}' # Assign the demo user to it curl -X PUT http://localhost:8080/v2/tenants/tenant-a/users/demo -H "Authorization: Bearer $TOKEN" ``` ## Next steps - Review [install and start with Docker Compose](./install-start.md). - Review [configure secondary storage with Docker Compose](./secondary-storage.md). - Review [use connectors and deploy processes with Docker Compose](./connectors-and-modeling.md). --- ## Use connectors and deploy processes with Docker Compose Use this page to work with connectors and local modeling tools in the Docker Compose quickstart. ## Use connectors Both the lightweight and full Docker Compose configurations include built-in connectors for integrating with external systems. The connector runtime executes outbound connectors called from BPMN processes, and inbound connectors that trigger process instances from external events. For connector overviews and installation details, see: - [Available connectors](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md) - [Connector installation guide](/self-managed/components/connectors/overview.md) ### Connector secrets When you run Camunda locally with Docker Compose, some [connectors](/components/connectors/out-of-the-box-connectors/available-connectors-overview.md) require credentials or API keys to connect with external services such as Slack, SendGrid, or AWS. Store those values as secrets instead of hardcoding them in your process models. #### Use centralized secrets In Camunda 8.10, you can use centralized secrets from local files in both the lightweight and full Docker Compose setups. 1. In the extracted distribution, open the included `secrets/` directory and create a file named `OPENAI_API_KEY`. 1. Enter only the secret value, not `KEY=value`, and save the file as UTF-8 without a byte-order mark. 1. Start the lightweight setup with `docker compose up -d`, or the full setup with `docker compose -f docker-compose-full.yaml up -d`. 1. Reference the secret in a service-task or connector input mapping: ```feel =camunda.secrets.OPENAI_API_KEY ``` You don't need to edit Compose or application YAML. Both setups mount `secrets/` read-only into Orchestration at `/etc/camunda/secrets` and configure `camunda.secrets.stores.file.default.path` through `CAMUNDA_SECRETS_STORES_FILE_DEFAULT_PATH`. The directory isn't mounted into Connectors. Each filename is a secret name, and the file contents are its value. Use letters, numbers, underscores, or dashes in names. One trailing newline is ignored; other whitespace is part of the value. For names containing dashes, use FEEL backticks: ```feel =camunda.secrets.`openai-api-key` ``` You can add files while the stack is running. Changes and deletions can take up to 20 minutes to affect resolved values because Orchestration caches secrets. To clear the cache immediately, run `docker compose restart orchestration`, or `docker compose -f docker-compose-full.yaml restart orchestration` for the full setup. If a missing secret causes a `SECRET_RESOLUTION_ERROR` incident, create the file and resolve the incident in Operate. Creating the file alone doesn't resolve an existing incident. In this Docker Compose setup, the files are plaintext and intended only for local development. The included `.gitignore` excludes secret files from ordinary commits. Don't force-add secret files, include them in shared archives, or copy their values into BPMN. For production, use a secret store and deployment mechanism that meets your security requirements, such as AWS Secrets Manager or Google Secret Manager. On native Linux, make sure the container user (UID 1001) can traverse the directory and read the files. Host-user-only permissions can prevent access. #### Use legacy connector secrets The existing `connector-secrets.txt` file supplies only the Connectors runtime environment for `{{secrets.NAME}}` references. The file isn't imported into the centralized store, so the two workflows remain independent. You can add secrets to the connector runtime with the included `connector-secrets.txt` file: 1. Open `connector-secrets.txt` in the extracted directory. 1. Add secrets in the format `NAME=VALUE`, one per line: ```text SLACK_TOKEN=xoxb-your-token-here SENDGRID_API_KEY=SG.your-api-key ``` 1. Save the file. The secrets become available in connector configurations with the syntax `{{secrets.NAME}}`. For example, `{{secrets.SLACK_TOKEN}}`. :::warning Do not commit `connector-secrets.txt` to version control with real credentials. Use placeholder values in the repository and configure actual secrets in each environment. ::: For more details, see [configure connector secrets](/self-managed/components/connectors/connectors-configuration.md). ### Custom connectors In addition to the built-in connectors, you can add custom connectors. To include custom connectors: - Create a new Docker image that bundles your connectors, as described in the [Connectors repository](https://github.com/camunda/connectors). - Mount the connector JARs as volumes into the `/opt/app` directory in the Docker Compose file. Each connector JAR must include all required dependencies inside the JAR. ## Deploy and execute processes You can deploy and execute processes with either Desktop Modeler or Camunda Hub. ### Deploy with Desktop Modeler [Desktop Modeler](https://camunda.com/download/modeler/) is a free, open-source desktop application for modeling BPMN, DMN, and Camunda Forms. #### Lightweight configuration To deploy from Desktop Modeler to the lightweight configuration: 1. Open Desktop Modeler and click the deployment icon. 1. Select **Camunda 8 Self-Managed**. 1. Configure the connection: - **Cluster endpoint:** `http://localhost:8080/v2` - **Authentication:** **None** 1. Click **Deploy**. For more details, see [deploy to Self-Managed from Desktop Modeler](/self-managed/components/modeler/desktop-modeler/deploy-to-self-managed.md). #### Full configuration To deploy from Desktop Modeler to the full configuration: 1. Open Desktop Modeler and click the deployment icon. 1. Select **Camunda 8 Self-Managed**. 1. Configure the connection: - **Cluster endpoint:** `http://localhost:8080/v2` - **Authentication:** **OAuth** - **OAuth URL:** `http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token` - **Client ID:** `orchestration` - **Client secret:** `secret` - **Audience:** `orchestration-api` 1. Click **Deploy**. :::tip The full configuration uses Keycloak for OIDC authentication. The client credentials are preconfigured in the `.env` file and Management Identity configuration. ::: ### Deploy with Camunda Hub :::note Non-production installations of Camunda Hub are limited to five members per workspace. See [Licensing](/reference/licenses.md). ::: [Camunda Hub](/components/hub/index.md) includes a browser-based modeler for creating and deploying BPMN, DMN, and form diagrams. Camunda Hub is included in the full configuration and can also run as a standalone setup. #### Standalone setup To start Camunda Hub, its WebSockets service, Management Identity, Keycloak, PostgreSQL, and Mailpit independently, run: ```shell docker compose -f docker-compose-hub.yaml up -d ``` To stop Camunda Hub and remove all data and volumes, run: ```shell docker compose -f docker-compose-hub.yaml down -v ``` #### Deploy or execute a process When you use the full configuration, Camunda Hub connects automatically to the local Orchestration Cluster started by `docker-compose-full.yaml`. You can deploy and run processes directly from the Camunda Hub interface. 1. Log in to Camunda Hub at [http://localhost:8070](http://localhost:8070) with `demo` / `demo`. 1. [Create a workspace](/components/hub/organization/manage-workspaces/manage-workspace.md#create-a-workspace). 1. In your workspace, create a new project. 1. In your project, [create a new BPMN diagram](/components/hub/workspace/modeler/index.md). 1. Use the visual modeler to [design your BPMN process](/components/modeler/bpmn/bpmn.md). 1. Click **Deploy** to deploy the diagram to the preconfigured Orchestration Cluster. 1. After deployment, you can [create process instances](/components/concepts/process-instance-creation.md) and monitor them in [Operate](http://localhost:8080/operate). Camunda Hub uses the `BEARER_TOKEN` authentication method to communicate with the Orchestration Cluster. The user's authentication token from Management Identity is used automatically for deployment. :::note Camunda Hub is not included in the lightweight configuration. To use Camunda Hub with the lightweight configuration: 1. Run Camunda Hub separately with `docker-compose-hub.yaml`. 1. Manually configure the cluster connection in Camunda Hub. 1. Use `NONE` or `BASIC` authentication for the lightweight Orchestration Cluster. For details, see [configure Camunda Hub clusters](/self-managed/components/hub/configuration/properties.md#clusters). ::: #### Emails The Docker Compose setup includes [Mailpit](https://github.com/axllent/mailpit) as a test SMTP server. Mailpit captures all emails sent by Camunda Hub, but does not forward them to actual recipients. You can access emails in Mailpit at [http://localhost:8075](http://localhost:8075). ## Next steps - Follow the [getting started guide](/guides/getting-started-example.md) to create a Java project and connect to your local cluster. - Learn [BPMN fundamentals](/components/modeler/bpmn/bpmn-primer.md) and [best practices](/components/best-practices/best-practices-overview.md). - Explore the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) and [client libraries](/apis-tools/working-with-apis-tools.md). --- ## Install and start with Docker Compose Use this page to install the Docker Compose distribution locally, start Camunda 8, and stop the environment cleanly. ## Prerequisites The following prerequisites are required to run Camunda 8 Self-Managed with Docker Compose: | Prerequisite | Description | | :------------- | :--------------------------------------------------------------------------------------- | | Docker Compose | Version 2.24.0 or later, which supports the Compose attributes used by the distribution. | | Docker | Version 20.10.16 or later. | :::tip If Docker Compose reports errors such as unsupported attributes when loading the Camunda Compose files, confirm that you are using the Docker Compose v2 plugin: ```shell docker compose version ``` Run the commands in this guide with `docker compose`, not `docker-compose`. Confirm the output reports version 2.24.0 or later. If needed, upgrade Docker Desktop or the Docker Engine Compose plugin, then retry. ::: ## Install and start Camunda 8 with Docker Compose To start the default lightweight Camunda 8 Self-Managed environment locally: 1. Download the Camunda 8 archive (a `.zip` file from the [Camunda Distributions releases](https://github.com/camunda/camunda-distributions/releases) on GitHub), then extract it. Keep the complete directory, including `.env`, hidden configuration directories, and `configuration/`. 1. In the extracted directory, run: ```shell docker compose up -d ``` 1. Wait for the environment to initialize. This can take several minutes. Run `docker compose ps` to check service health, or `docker compose logs -f orchestration connectors` to follow the lightweight startup. Run Compose commands from the extracted directory. If Compose reports that image-version variables are unset or `.env` is missing, download and extract the complete distribution archive again instead of downloading an individual Compose file. For available Compose files, component URLs, and authentication defaults, see [configure Docker Compose environments](./configuration.md). ## Stop Camunda 8 with Docker Compose To stop all containers and remove associated data, run: ```shell docker compose down -v # or for the full configuration: docker compose -f docker-compose-full.yaml down -v # or for standalone Camunda Hub: docker compose -f docker-compose-hub.yaml down -v ``` :::caution The `-v` flag deletes all volumes, including process data, users, and other persisted state. Omit `-v` if you want to keep your data. ::: ## Next steps - Review [configure Docker Compose environments](./configuration.md). - Review [configure secondary storage with Docker Compose](./secondary-storage.md). - Review [use connectors and deploy processes with Docker Compose](./connectors-and-modeling.md). --- ## Configure secondary storage with Docker Compose Use this page to configure secondary storage for the Orchestration Cluster in the Docker Compose quickstart. ## Choose a database configuration path Camunda 8.10 uses different application configuration files for the lightweight and full Docker Compose setups. | Setup | Default secondary storage | Select another backend | | :-------------------------------- | :------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Lightweight `docker-compose.yaml` | File-based H2 | Set `ORCHESTRATION_CONFIG_FILE` to a file from `configuration/`, then edit that file for your database connection. | | Full `docker-compose-full.yaml` | File-based H2 | Replace the `camunda.data.secondary-storage` settings in `.orchestration/application.yaml` with the matching block from the file in `configuration/`, then edit the connection values. | The full setup also starts Elasticsearch for Optimize. The PostgreSQL containers in the full setup store Management Identity and Camunda Hub data; they do not store Orchestration Cluster data. :::warning Do not replace `.orchestration/application.yaml` in the full setup with a file from `configuration/`. The files in `configuration/` use the lightweight setup's Basic authentication settings. Replacing the full file removes its OpenID Connect (OIDC) and component configuration. ::: Use this workflow for each backend: 1. Select the matching application file and database service from the table below. 1. Configure the application file for your setup: - **Lightweight setup:** Edit the selected `configuration/application-.yaml` file and set `ORCHESTRATION_CONFIG_FILE` to its filename. - **Full setup:** Replace the `camunda.data.secondary-storage` block in `.orchestration/application.yaml` with the block from the selected file, then edit the copied values there. 1. Create `docker-compose.override.yaml` in the extracted distribution directory and copy the matching database service example into it. 1. If the backend requires an external JDBC driver, place the driver JAR directly in `driver-lib/`. 1. Start the setup with the command shown for that backend. | Backend | Application file | Hostname from the override | JDBC driver | | :------------------- | :---------------------------- | :------------------------- | :---------------------------------------------- | | H2 | `application-h2.yaml` | Not applicable | Included | | PostgreSQL | `application-postgresql.yaml` | `postgres-secondary` | Included | | MariaDB | `application-mariadb.yaml` | `mariadb-secondary` | Included | | MySQL | `application-mysql.yaml` | `mysql-secondary` | Add the MySQL Connector/J JAR to `driver-lib/` | | Oracle | `application-oracle.yaml` | `oracle-secondary` | Add the Oracle JDBC driver JAR to `driver-lib/` | | Microsoft SQL Server | `application-mssql.yaml` | `mssql-secondary` | Included | When the database runs from `docker-compose.override.yaml`, replace `localhost` in the selected JDBC URL with the hostname shown in the table. The MySQL file points to `localhost:3307` by default; container-to-container traffic uses MySQL port `3306` instead. :::note Camunda configures the built-in exporter automatically from `camunda.data.secondary-storage.*`. You do not need to add a separate exporter class for the standard Docker Compose quickstart. ::: ### Run with RDBMS secondary storage The following examples run each supported RDBMS backend as a separate Docker Compose service. Use them for local development and evaluation. Review the [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md#scope-and-applicability) before you plan another deployment type. Set the JDBC URL in the application configuration to `jdbc:postgresql://postgres-secondary:5432/camunda_secondary`. ```yaml services: orchestration: depends_on: - postgres-secondary networks: - secondary-storage postgres-secondary: image: postgres:16 environment: POSTGRES_DB: camunda_secondary POSTGRES_USER: camunda POSTGRES_PASSWORD: camunda volumes: - postgres-secondary-data:/var/lib/postgresql/data networks: - secondary-storage volumes: postgres-secondary-data: networks: secondary-storage: ``` ```shell # Lightweight setup ORCHESTRATION_CONFIG_FILE=application-postgresql.yaml docker compose -f docker-compose.yaml -f docker-compose.override.yaml up -d # Full setup docker compose -f docker-compose-full.yaml -f docker-compose.override.yaml up -d ``` Set the JDBC URL in the application configuration to `jdbc:mariadb://mariadb-secondary:3306/camunda_secondary?serverTimezone=UTC`. ```yaml services: orchestration: depends_on: - mariadb-secondary networks: - secondary-storage mariadb-secondary: image: mariadb:11.4 environment: MARIADB_DATABASE: camunda_secondary MARIADB_USER: camunda MARIADB_PASSWORD: camunda MARIADB_ROOT_PASSWORD: rootcamunda volumes: - mariadb-secondary-data:/var/lib/mysql networks: - secondary-storage volumes: mariadb-secondary-data: networks: secondary-storage: ``` ```shell # Lightweight setup ORCHESTRATION_CONFIG_FILE=application-mariadb.yaml docker compose -f docker-compose.yaml -f docker-compose.override.yaml up -d # Full setup docker compose -f docker-compose-full.yaml -f docker-compose.override.yaml up -d ``` Set the JDBC URL in the application configuration to `jdbc:mysql://mysql-secondary:3306/camunda_secondary?serverTimezone=UTC`. ```yaml services: orchestration: depends_on: - mysql-secondary volumes: - ./driver-lib:/driver-lib:ro networks: - secondary-storage mysql-secondary: image: mysql:9.7 environment: MYSQL_DATABASE: camunda_secondary MYSQL_USER: camunda MYSQL_PASSWORD: camunda MYSQL_ROOT_PASSWORD: rootcamunda volumes: - mysql-secondary-data:/var/lib/mysql networks: - secondary-storage volumes: mysql-secondary-data: networks: secondary-storage: ``` ```shell # Lightweight setup ORCHESTRATION_CONFIG_FILE=application-mysql.yaml docker compose -f docker-compose.yaml -f docker-compose.override.yaml up -d # Full setup docker compose -f docker-compose-full.yaml -f docker-compose.override.yaml up -d ``` Place the MySQL Connector/J JAR directly in `driver-lib/` before you start either setup. Set the JDBC URL in the application configuration to `jdbc:oracle:thin:@//oracle-secondary:1521/FREEPDB1`. ```yaml services: orchestration: depends_on: - oracle-secondary volumes: - ./driver-lib:/driver-lib:ro networks: - secondary-storage oracle-secondary: image: gvenzl/oracle-free:23-slim environment: ORACLE_PASSWORD: oracle APP_USER: camunda APP_USER_PASSWORD: camunda volumes: - oracle-secondary-data:/opt/oracle/oradata networks: - secondary-storage volumes: oracle-secondary-data: networks: secondary-storage: ``` ```shell # Lightweight setup ORCHESTRATION_CONFIG_FILE=application-oracle.yaml docker compose -f docker-compose.yaml -f docker-compose.override.yaml up -d # Full setup docker compose -f docker-compose-full.yaml -f docker-compose.override.yaml up -d ``` Place the Oracle JDBC driver JAR directly in `driver-lib/` before you start either setup. Set the JDBC URL in the application configuration to `jdbc:sqlserver://mssql-secondary:1433;databaseName=camunda_secondary;encrypt=false`. ```yaml services: orchestration: depends_on: - mssql-secondary networks: - secondary-storage mssql-secondary: image: mcr.microsoft.com/mssql/server:2022-latest environment: ACCEPT_EULA: "Y" MSSQL_SA_PASSWORD: Camunda123! MSSQL_PID: Developer volumes: - mssql-secondary-data:/var/opt/mssql networks: - secondary-storage volumes: mssql-secondary-data: networks: secondary-storage: ``` ```shell # Lightweight setup docker compose -f docker-compose.yaml -f docker-compose.override.yaml up -d mssql-secondary docker compose -f docker-compose.yaml -f docker-compose.override.yaml exec mssql-secondary /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P 'Camunda123!' -Q "IF DB_ID('camunda_secondary') IS NULL CREATE DATABASE camunda_secondary" docker compose -f docker-compose.yaml -f docker-compose.override.yaml exec mssql-secondary /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P 'Camunda123!' -Q "IF SUSER_ID('camunda') IS NULL CREATE LOGIN camunda WITH PASSWORD='Camunda123!', CHECK_POLICY=OFF; USE camunda_secondary; IF USER_ID('camunda') IS NULL CREATE USER camunda FOR LOGIN camunda; ALTER ROLE db_owner ADD MEMBER camunda" ORCHESTRATION_CONFIG_FILE=application-mssql.yaml docker compose -f docker-compose.yaml -f docker-compose.override.yaml up -d # Full setup docker compose -f docker-compose-full.yaml -f docker-compose.override.yaml up -d mssql-secondary docker compose -f docker-compose-full.yaml -f docker-compose.override.yaml exec mssql-secondary /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P 'Camunda123!' -Q "IF DB_ID('camunda_secondary') IS NULL CREATE DATABASE camunda_secondary" docker compose -f docker-compose-full.yaml -f docker-compose.override.yaml exec mssql-secondary /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P 'Camunda123!' -Q "IF SUSER_ID('camunda') IS NULL CREATE LOGIN camunda WITH PASSWORD='Camunda123!', CHECK_POLICY=OFF; USE camunda_secondary; IF USER_ID('camunda') IS NULL CREATE USER camunda FOR LOGIN camunda; ALTER ROLE db_owner ADD MEMBER camunda" docker compose -f docker-compose-full.yaml -f docker-compose.override.yaml up -d ``` H2 is the default secondary storage backend in both application configuration files. You don't need a `docker-compose.override.yaml` file. ```shell # Lightweight setup docker compose up -d # Full setup docker compose -f docker-compose-full.yaml up -d ``` Use H2 only for development, testing, and evaluation. It is not a production backend. ### Switch between RDBMS, Elasticsearch, and OpenSearch For a document-store backend, add the backend settings to `docker-compose.override.yaml`. The environment variables override the secondary storage settings in either application configuration file. :::note These examples change the Orchestration Cluster secondary storage only. In the full setup, Optimize always requires Elasticsearch. The Elasticsearch example can serve both Orchestration and Optimize. If Orchestration uses OpenSearch, Optimize continues to use the Elasticsearch service that the full configuration starts. ::: :::note In the full setup, `.orchestration/application.yaml` also pins the webapp database keys `camunda.database.type`, `camunda.operate.database`, and `camunda.tasklist.database` to `rdbms`. Update those keys in that file to match the backend you select, otherwise Operate and Tasklist keep their RDBMS wiring after you switch. The lightweight `configuration/` files do not set these keys, so the environment overrides below are sufficient there. ::: ```yaml services: orchestration: environment: CAMUNDA_DATA_SECONDARY_STORAGE_TYPE: elasticsearch CAMUNDA_DATA_SECONDARY_STORAGE_ELASTICSEARCH_URL: http://elasticsearch-secondary:9200 CAMUNDA_DATA_SECONDARY_STORAGE_ELASTICSEARCH_USERNAME: "" CAMUNDA_DATA_SECONDARY_STORAGE_ELASTICSEARCH_PASSWORD: "" depends_on: - elasticsearch-secondary networks: - secondary-storage elasticsearch-secondary: image: docker.elastic.co/elasticsearch/elasticsearch:8.19.11 environment: discovery.type: single-node xpack.security.enabled: "false" ES_JAVA_OPTS: -Xms512m -Xmx512m volumes: - elasticsearch-secondary-data:/usr/share/elasticsearch/data networks: - secondary-storage volumes: elasticsearch-secondary-data: networks: secondary-storage: ``` ```shell # Lightweight setup docker compose -f docker-compose.yaml -f docker-compose.override.yaml up -d # Full setup docker compose -f docker-compose-full.yaml -f docker-compose.override.yaml up -d ``` ```yaml services: orchestration: environment: CAMUNDA_DATA_SECONDARY_STORAGE_TYPE: opensearch CAMUNDA_DATA_SECONDARY_STORAGE_OPENSEARCH_URL: http://opensearch-secondary:9200 depends_on: - opensearch-secondary networks: - secondary-storage opensearch-secondary: image: opensearchproject/opensearch:2.19.3 environment: discovery.type: single-node OPENSEARCH_JAVA_OPTS: -Xms512m -Xmx512m DISABLE_SECURITY_PLUGIN: "true" volumes: - opensearch-secondary-data:/usr/share/opensearch/data networks: - secondary-storage volumes: opensearch-secondary-data: networks: secondary-storage: ``` ```shell # Lightweight setup docker compose -f docker-compose.yaml -f docker-compose.override.yaml up -d # Full setup, with a separate Elasticsearch endpoint configured in .env docker compose -f docker-compose-full.yaml -f docker-compose.override.yaml up -d ``` ### Document-store environment variables Use these variables when you adapt the Elasticsearch and OpenSearch examples: | Variable | Use | | :------------------------------------------------------ | :--------------------------------------------------------- | | `CAMUNDA_DATA_SECONDARY_STORAGE_TYPE` | Selects `elasticsearch` or `opensearch`. | | `CAMUNDA_DATA_SECONDARY_STORAGE_ELASTICSEARCH_URL` | Endpoint for Elasticsearch. | | `CAMUNDA_DATA_SECONDARY_STORAGE_ELASTICSEARCH_USERNAME` | Username for Elasticsearch when authentication is enabled. | | `CAMUNDA_DATA_SECONDARY_STORAGE_ELASTICSEARCH_PASSWORD` | Password for Elasticsearch when authentication is enabled. | | `CAMUNDA_DATA_SECONDARY_STORAGE_OPENSEARCH_URL` | Endpoint for OpenSearch. | For additional secondary storage settings, see [Configure secondary storage](/self-managed/concepts/secondary-storage/configuring-secondary-storage.md) and [Configure RDBMS for manual installations](/self-managed/deployment/manual/rdbms/configuration.md). ## Next steps - Review [configure Docker Compose environments](./configuration.md). - Review [use connectors and deploy processes with Docker Compose](./connectors-and-modeling.md). --- ## Developer quickstart with Docker Compose Get started with Docker Compose to run Camunda 8 Self-Managed locally. The default lightweight configuration includes the Orchestration Cluster and Connectors, and uses file-based H2 secondary storage. The full configuration additionally includes Optimize, Camunda Hub, Management Identity, Keycloak, PostgreSQL, and Elasticsearch. Docker Compose also supports [document handling](/self-managed/concepts/document-handling/overview.md), configurable secondary storage, built-in connectors, custom connectors, and modeling workflows with Desktop Modeler and Camunda Hub. :::note The [Docker images](/self-managed/deployment/docker/docker.md) are supported for production usage. The Docker Compose files are intended for local development and evaluation, and are not designed for production. For production deployments, use [Kubernetes with Helm](/self-managed/deployment/helm/install/index.md). ::: Camunda 8 with Docker Compose includes the following: - Orchestration Cluster - Connectors - File-based H2 as the default secondary storage in the lightweight configuration ## Pages in this section Step through the Docker Compose guide with the following topics: - [Install and start](./docker-compose/install-start.md) - [Configuration](./docker-compose/configuration.md) - [Secondary storage](./docker-compose/secondary-storage.md) - [Connectors and modeling](./docker-compose/connectors-and-modeling.md) ## Common tasks If you are looking for a specific task from the previous single-page guide, use the links below. ### Install and start Camunda 8 with Docker Compose {#install-and-start-camunda-8-with-docker-compose} For prerequisites, startup commands, and shutdown commands, see [install and start with Docker Compose](./docker-compose/install-start.md). ### Choose a Docker Compose configuration {#choose-a-docker-compose-configuration} For lightweight, full, and standalone Camunda Hub configurations, plus component URLs and authentication defaults, see [configure Docker Compose environments](./docker-compose/configuration.md#choose-a-docker-compose-configuration). ### Customize application settings For mounted application files, `.env`, Spring environment overrides, and connector secrets, see [customize application configuration](./docker-compose/configuration.md#customize-application-configuration). ### Configure secondary storage {#configure-secondary-storage} For Elasticsearch, OpenSearch, PostgreSQL, MariaDB, MySQL, Oracle, Microsoft SQL Server, and H2 examples, see [configure secondary storage with Docker Compose](./docker-compose/secondary-storage.md#choose-a-database-configuration-path). ### Use connectors and deploy processes {#use-connectors-and-deploy-processes} For connector secrets, custom connectors, Desktop Modeler, and Camunda Hub, see [use connectors and deploy processes with Docker Compose](./docker-compose/connectors-and-modeling.md#use-connectors). ### Stop Camunda 8 with Docker Compose {#stop-camunda-8-with-docker-compose} For shutdown commands and volume cleanup guidance, see [install and start with Docker Compose](./docker-compose/install-start.md#stop-camunda-8-with-docker-compose). ## Next steps - Follow the [getting started guide](/guides/getting-started-example.md) to create a Java project and connect to your local cluster. - Explore the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) and [client libraries](/apis-tools/working-with-apis-tools.md). - When you are ready for production, deploy with [Kubernetes and Helm](/self-managed/deployment/helm/install/index.md). --- ## Developer quickstart This quickstart guides application engineers through deploying **Camunda 8 Self-Managed** to a local developer environment. --- ## Quickstart Welcome to the **Quickstart** section for Camunda 8 Self-Managed. This guide is designed to help you get up and running quickly with Camunda 8 in a local environment—whether you're a developer building process solutions or an administrator responsible for deploying and running Camunda clusters. You’ll find two tailored quickstart paths: - A **developer quickstart** using lightweight tools like **Camunda 8 Run** and **Docker Compose**. - An **administrator quickstart** using **Kind (Kubernetes in Docker)** to simulate real-world infrastructure. :::note These quickstarts are intended for local testing and learning purposes only. They are not designed for production deployments. ::: ## Quickstart for developers If you're a developer looking to evaluate or build process solutions with Camunda 8, this path is for you. It focuses on getting a minimal setup running quickly using: - **Camunda 8 Run**: A lightweight distribution of the Camunda engine. - **Docker Compose**: For spinning up required services like [Zeebe](/reference/glossary.md#zeebe), Operate, and Tasklist. [Get started with the developer quickstart](./developer-quickstart.md) ## Quickstart for administrators If you're an administrator exploring how Camunda 8 runs in a Kubernetes-like environment, this guide walks you through: - Using **Kind** to simulate a Kubernetes cluster locally. - Deploying the full Camunda 8 stack using Helm charts. [Get started with the administrator quickstart](/self-managed/deployment/helm/cloud-providers/kind.md) ## What you’ll learn In each quickstart, you will: - Spin up a local environment with the core Camunda 8 components. - Deploy a sample process definition. - Interact with the [Orchestration Cluster](/reference/glossary.md#orchestration-cluster) web applications (Operate and Tasklist). - Understand the responsibilities associated with your chosen role (developer or administrator). ## Prerequisites Each quickstart guide lists the required tools and setup steps. In general, you’ll need: - Docker and Docker Compose (for developers). - Kind, kubectl, and Helm (for administrators). - A basic understanding of BPMN and distributed systems is helpful but not required. ## Next steps After completing a quickstart, you can: - Dive deeper into platform components like Zeebe, Operate, and Identity. - Learn about production deployment options. - Explore real-world examples and best practices. --- ## Components - Zeebe Broker and Gateway - Operate - Tasklist - Connectors - Optimize - Identity - Camunda Hub All components except Camunda Hub are single Java applications. Depending on your needs, you might not need all of the above components to successfully use Camunda 8. Camunda 8 Self-Managed users may also use [Desktop Modeler](../../components/modeler/desktop-modeler/install-the-modeler.md) as an addition to these components. Desktop Modeler can be used by process developers to build BPMN diagrams, DMN diagrams, or [Camunda Forms](/components/modeler/desktop-modeler/utilize-forms.md) for automation. :::note To obtain or retrieve your Camunda 8 credentials for Enterprise licenses, visit the [contact page](/reference/contact.md). ::: --- ## Licensing(React-components) Camunda 8 Self-Managed only Installations of Camunda 8 Self-Managed which require a license can provide their license key to the components as an environment variable: | Environment variable | Description | Default value | | --------------------- | -------------------------------------------------------------------- | ------------- | | `CAMUNDA_LICENSE_KEY` | Your Camunda 8 license key, if your installation requires a license. | None | For Helm installations, license keys can be configured globally in your `values.yaml` file. See the [License key](/self-managed/deployment/helm/configure/license-key.md) for more details. :::note Camunda 8 components without a valid license may display **Non-Production License** in the navigation bar and issue warnings in the logs. These warnings have no impact on startup or functionality. **Camunda Hub without a license:** Camunda Hub is limited to **five concurrent users** when running without a valid enterprise license. This applies to Self-Managed installations used for testing or development purposes. To support additional users or for production use, obtain a Camunda Self-Managed Enterprise Edition license by visiting the [Camunda Enterprise page](https://camunda.com/platform/camunda-platform-enterprise-contact/). ::: --- ## Container deployment overview With container-based deployments, you can run the [Camunda 8 Orchestration Cluster](./reference-architecture.md#camunda-hub-vs-orchestration-cluster) in a portable, consistent runtime with the benefits of containerization, without managing Kubernetes. The following container deployment options are currently available: - **[Amazon ECS with Fargate (single region)](/self-managed/deployment/containers/cloud-providers/amazon/aws-ecs.md)**: Deploy to Amazon ECS with Fargate and Aurora PostgreSQL in a single AWS region. - **[Amazon ECS with Fargate (dual region)](/self-managed/deployment/containers/cloud-providers/amazon/aws-ecs-dual-region.md)**: Active-active across two AWS regions, backed by Aurora Global Database. Experimental reference architecture. --- ## Kubernetes deployment overview This reference architecture provides guidance for deploying Camunda 8 Self-Managed within a Kubernetes cluster. This deployment method is ideal for users who want to leverage the benefits of containerization and self-healing. It supports quick scalability and offers increased resilience through multi-zone deployments. ## Key features - **Scalability & high availability**: Camunda 8 can scale dynamically to meet demand and supports high-availability configurations. - **Fault tolerance & resilience**: Deploy Camunda 8 across availability zones or regions to improve fault tolerance and increase uptime for your workflows. While Kubernetes introduces a steeper learning curve, once configured properly it provides significant benefits—such as self-healing capabilities that automatically restart failed containers and reschedule workloads to maintain availability. Kubernetes also benefits from a robust ecosystem of [**Cloud Native Computing Foundation (CNCF) projects**](https://www.cncf.io/), enabling seamless integration with tools for monitoring, observability, logging, and security. Solutions like [Prometheus](https://prometheus.io/) and [Grafana](https://grafana.com/) offer comprehensive monitoring, while service meshes like [Istio](https://istio.io/) enhance traffic management and security. By leveraging this ecosystem, organizations can extend Kubernetes to fit specific operational needs—improving automation, scalability, and visibility across their infrastructure. ## Reference implementations This section includes reference deployment architectures: ### Amazon EKS - [Amazon EKS single-region](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/terraform-setup.md): Standard production setup. - [Amazon EKS dual-region](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/dual-region.md): Advanced multi-region setup. - [Amazon EKS multi-region with RDBMS](/self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/multi-region-rdbms.md): Three or more regions with relational secondary storage, so a region loss does not stop processing. ### Red Hat OpenShift on AWS (ROSA) - [ROSA single-region](/self-managed/deployment/helm/cloud-providers/amazon/openshift/terraform-setup.md): Standard production setup. - [ROSA dual-region](/self-managed/deployment/helm/cloud-providers/amazon/openshift/terraform-setup-dual-region.md): Advanced multi-region setup. ### Microsoft Azure - [Microsoft AKS single-region](/self-managed/deployment/helm/cloud-providers/azure/microsoft-aks/terraform-setup.md): Standard production setup. For common issues and mitigation strategies, refer to the [deployment troubleshooting guide](/self-managed/operational-guides/troubleshooting.md). ## Architecture The [reference architecture overview](/self-managed/reference-architecture/reference-architecture.md#deployment-topology) describes the deployment topology: one management plane serving one or more Orchestration Clusters, with one Optimize instance per Physical Tenant. It also explains the distinction between these components: - **Management plane (Camunda Hub and Management Identity)**: Manage organizational resources, analyze operations and business value, and deliver agentic processes at scale. - **Orchestration Cluster**: Core process execution engine (Zeebe, Operate, Tasklist, Admin), including Connectors. - **Optimize**: Process analytics, deployed separately with one instance per Physical Tenant. _Infrastructure diagram for a single-region setup (click the image to open the PDF version)_ [![Architecture Overview](./img/k8s-single.jpg)](./img/k8s-single.pdf) This Kubernetes architecture illustrates a high-availability setup across multiple availability zones (A, B, and C), with key networking components to ensure scalability, security, and reliability. We recommend using multiple availability zones to improve fault tolerance and eliminate single points of failure. To control access, deploy Camunda 8 in a private subnet and manage inbound traffic using an Ingress and Load Balancer. :::note The database is not shown in the diagram. It should be hosted outside the Kubernetes cluster, ideally within the same private network or subnet. Many organizations use a dedicated external database setup with tightly controlled access. ::: ### Kubernetes A production deployment is recommended. For more information, see the [production deployment guide](/self-managed/deployment/helm/install/production/index.md) and the [components](#components) section. The following visuals provide a simplified view of the deployed namespaces using the [Camunda 8 Helm chart](/self-managed/deployment/helm/install/quick-install.md). For clarity, ConfigMaps, Secrets, RBAC, and ReplicaSets are omitted. #### Management plane ![Camunda Hub and Management Identity](./img/management-cluster.jpg) Camunda Hub and Management Identity form the management plane, which serves all Orchestration Clusters in the deployment. Both are stateless and deployed as **Deployments**, with data stored in an external SQL database. This makes it easy to scale each horizontally by running multiple replica pods behind a load balancer, improving availability and request throughput. Each namespace uses its own Ingress, as Ingress resources are namespace-scoped (not cluster-wide). This requires separate subdomains for each Ingress. For more details, see the [production deployment guide](/self-managed/deployment/helm/install/production/index.md). #### Orchestration Cluster ![Orchestration Cluster](./img/k8s-cluster-view-orchestration.jpg) The Helm chart uses a single Ingress by default, enabling a unified domain with each application accessible via a dedicated path. Most Camunda 8 components are stateless and deployed as **Deployments**. However, the Orchestration Cluster contains Zeebe brokers, which require a **StatefulSet** to maintain consistent volume mounts. This ensures stable pod ordering and identifiers. StatefulSet names remain consistent even as they represent the entire Orchestration Cluster—simplifying migration from existing setups. The Orchestration Cluster exposes two services: 1. A [**headless service**](https://kubernetes.io/docs/concepts/services-networking/service/#headless-services) for internal communication between Zeebe brokers. This service skips load balancing and resolves to pod IPs for direct peer-to-peer communication. 2. A **standard service** for external applications. This service distributes traffic randomly (via `kube-proxy`) and is suitable for clients or other services connecting to the cluster. ### High availability (HA) :::caution Non-HA importer / archiver This applies to **Optimize**: When scaling from a single pod to multiple pods, ensure that the `importer / archiver` is enabled on only one pod. Enabling it on more than one pod may cause data inconsistencies. This is a known limitation and will be addressed in a future update. See the [Optimize system configuration guide](/self-managed/components/optimize/configuration/system-configuration-platform-8.md#general-settings) for example settings. ::: For high availability, we recommend a minimum of **four Kubernetes nodes** to ensure fault tolerance and support leader election. This is especially important when using the [Raft protocol]() for consensus within the Orchestration Cluster. For more details, refer to the [clustering documentation](/components/zeebe/technical-concepts/clustering.md). While Deployments and StatefulSets in Kubernetes can scale independently of physical hardware, four nodes are typically required to support: - The default three-node Orchestration Cluster (incl. Zeebe, Operate, Tasklist, and Admin) - Other Camunda 8 components (Camunda Hub, Management Identity, and Optimize) Depending on your specific use case, you may need to scale **horizontally** (more nodes) or **vertically** (larger nodes) to meet resource requirements. By default, node affinity rules prevent all Orchestration Cluster pods from being scheduled on the same node. This requires at least three nodes for proper operation. For details, see the [Kubernetes node affinity documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/). To further improve fault tolerance, distribute the Orchestration Cluster and other components across **multiple availability zones**. The default anti-affinity rules ensure Orchestration Cluster pods run on distinct nodes, but do not ensure those nodes are in different zones — use the `orchestration.topologySpreadConstraints` Helm value to spread them across zones. For configuration details and caveats, see [topology spread constraints](/self-managed/deployment/helm/install/production/index.md#topology-spread-constraints). ### Components Camunda 8 deployments separate workloads into three logical groups, each installed as its own Helm release with a `global.topology.mode` role: - **Management plane:** Camunda Hub and Management Identity (`hub`), one per deployment - **Orchestration Cluster:** Orchestration Cluster and Connectors (`orchestration`), one per cluster - **Optimize:** one release per Physical Tenant (`optimize`) Deploy these groups into separate [Kubernetes namespaces](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/). The Hub namespace isn't tied to a single environment, and Orchestration Cluster namespaces can run on the same or different Kubernetes clusters, as long as every configured URL is reachable from the release that uses it. For when a single `combined` release fits instead, see [choose your topology](/self-managed/deployment/helm/install/index.md#choose-your-topology). ```mermaid graph TD subgraph hub["Namespace: hub (management plane)"] CH["Camunda Hub"] MI["Management Identity"] end subgraph ocdev["Namespace: orchestration-dev"] OCD["Orchestration Cluster+ Connectors"] end subgraph ocprod["Namespace: orchestration-prod"] OCP["Orchestration Cluster+ Connectors"] end subgraph optdev["Namespace: optimize-dev-default"] OD["Optimizedefault tenant"] end subgraph optprod["Namespaces: optimize-prod-*"] OP1["Optimizedefault tenant"] OP2["OptimizePhysical Tenant A"] end IdP["OIDC provider"] CH -- "deploy, API, readiness" --> OCD CH -- "deploy, API, readiness" --> OCP OCD -. "exported records" .-> OD OCP -. "exported records" .-> OP1 OCP -. "exported records" .-> OP2 OCD -- "authentication" --> IdP OCP -- "authentication" --> IdP OD -- "authentication" --> MI OP1 -- "authentication" --> MI OP2 -- "authentication" --> MI MI --> IdP ``` For the required cross-namespace traffic, see [allow required network traffic](/self-managed/deployment/helm/install/topology/index.md#allow-required-network-traffic). To implement this topology with the Helm chart, see [install the deployment topology](/self-managed/deployment/helm/install/topology/index.md). #### Management plane namespace As shown in the [architecture diagram](#management-plane), this namespace contains: - [Camunda Hub](/components/hub/index.md) — modeling and administrative capabilities - [Management Identity](/self-managed/components/management-identity/overview.md) — centralized access control for Camunda Hub and Optimize This namespace also requires an OIDC-compatible Identity Provider (IdP) for Management Identity. You can use any compatible provider (for example, Keycloak deployed via the [Keycloak Operator](/self-managed/deployment/helm/configure/operator-based-infrastructure.md#keycloak-deployment) or Microsoft Entra ID). :::tip Why isn't an IdP included by default? The choice of identity provider is highly specific to each organization's security requirements, existing infrastructure, and compliance needs. Rather than bundling a default IdP that may not match your setup, the reference architecture leaves this choice to you. This approach gives you full control over your authentication stack and avoids unnecessary complexity for teams that already have an IdP in place. ::: :::warning Identity separation Optimize and Camunda Hub rely on Management Identity (formerly Identity). This service is separate from the embedded Admin in the Orchestration Cluster and incompatible with it. To share the same user base and API clients across both, you must use OIDC. ::: For configuration details, see: - [Connect Orchestration Cluster to an OIDC provider](/self-managed/concepts/authentication/authentication-to-orchestration-cluster.md#oidc) - [Connect Management Identity to an OIDC provider](/self-managed/components/management-identity/configuration/connect-to-an-oidc-provider.md) The Orchestration Cluster authenticates directly with the OIDC provider. It doesn't need Management Identity to authenticate users or API clients. #### Orchestration Cluster namespace As shown in the [architecture diagram](#orchestration-cluster), the Orchestration Cluster is deployed as a StatefulSet and packaged as a single container image. It includes the following components: - [Zeebe](/components/zeebe/zeebe-overview.md) — workflow engine and broker - [Operate](/components/operate/operate-introduction.md) — visibility and troubleshooting UI - [Tasklist](/components/tasklist/introduction-to-tasklist.md) — UI for human tasks - [Admin](/self-managed/components/orchestration-cluster/admin/overview.md) — authentication and access control Also included in this namespace is the component that deploys with the cluster release: - [Connectors](/components/connectors/introduction.md) — external system integrations [Optimize](/components/optimize/what-is-optimize.md) serves this cluster but is deployed as its own release, one per Physical Tenant. See [Optimize releases](#optimize-releases). The Orchestration Cluster also depends on a **secondary storage** backend for Operate, Tasklist, and the v2 Orchestration Cluster REST API. This backend is a document store (Elasticsearch or OpenSearch) or a supported relational database management system (RDBMS). It is provisioned outside the `StatefulSet`, as a managed service or an operator-managed database. Optimize requires Elasticsearch or OpenSearch and cannot use an RDBMS. For the trade-offs and how to choose a backend, see [secondary storage architecture](/self-managed/reference-architecture/reference-architecture.md#secondary-storage-architecture). #### Optimize releases Each Physical Tenant in an Orchestration Cluster is served by one Optimize release, deployed with `global.topology.mode: optimize`. That release deploys Optimize and nothing else. For why each tenant, including the default tenant, needs its own release, see [why Optimize is its own release](/self-managed/deployment/helm/install/topology/optimize-release.md#why-optimize-is-its-own-release). For what each release requires, see [install an Optimize release](/self-managed/deployment/helm/install/topology/optimize-release.md). Place Optimize releases in the Orchestration Cluster namespace or in their own namespace. Ingress resources are namespace-scoped, so a separate namespace needs its own Ingress and subdomain. For configuration details, see [install an Optimize release](/self-managed/deployment/helm/install/topology/optimize-release.md) and [configure Physical Tenants across releases](/self-managed/deployment/helm/install/topology/physical-tenants.md). ## Requirements This guide focuses on a single-region application, but can be adapted for a multi-region setup once you understand the basics of a single region. To choose a multi-region strategy, start with the comparison in [multi-region resilience](/self-managed/concepts/multi-region/resilience-tiers.md#get-started-choose-your-strategy). ### Infrastructure We recommend using a [certified Kubernetes](https://www.cncf.io/training/certification/software-conformance/#benefits) distribution. Camunda 8 is not tied to a specific Kubernetes version. To simplify deployment, we provide a [Helm chart](/self-managed/deployment/helm/install/quick-install.md). For supported Kubernetes versions, see [supported environments](/reference/supported-environments.md#deployment-options). #### Minimum cluster requirements The following are suggested minimum requirements to get started. There is no one-size-fits-all configuration: sizing depends heavily on your specific use cases and workload, so treat these values as a baseline rather than a strict requirement. Refer to [sizing your environment](/components/best-practices/architecture/sizing-your-environment.md) and [Self-Managed resource planning](/components/best-practices/architecture/sizing-self-managed.md#disk-space), and conduct benchmarking to determine your exact needs. - **4 Kubernetes nodes** - CPU: 4 modern cores - Memory: 16 GiB - **Persistent volumes** - Sustained IOPS per [Disk IOPS](/components/best-practices/architecture/sizing-self-managed.md#disk-iops) - 32 GiB minimum capacity - SSD-backed volumes only. HDD-backed volumes are not supported. - Avoid burstable volume types unless they can sustain the target IOPS continuously without relying on burst credits. :::note Storage performance figures are a baseline, not a strict requirement The storage performance figures in this section and in the platform-specific sections below are a starting point. The IOPS target comes from our load tests, which run on GKE, and is used for all providers (OpenShift, EKS, AKS, GKE, and generic Kubernetes) because the IOPS a broker needs depends on the workload, not the provider. We do not benchmark each provider separately. Disk bandwidth (MiB/s) was not a limiting factor. Actual needs vary with your workload, data retention, and exporter load. On providers where disk performance scales with capacity (for example, GKE `pd-ssd`), reaching the target IOPS can require a disk larger than the 32 GiB minimum capacity. For production sizing, see [Self-Managed resource planning](/components/best-practices/architecture/sizing-self-managed.md) and benchmark against your own workload. Storage type, however, is a strict requirement: HDD-backed volumes cannot meet Zeebe's Raft protocol disk flush requirements, which demand consistent single-digit-millisecond write latency, and are not supported. The same SSD requirement applies to secondary storage, whether you run Elasticsearch/OpenSearch or an RDBMS. See the [Database](#database) section for details. ::: #### Networking Networking is largely managed through services and load balancers. The following outlines typical port usage, which may require whitelisting in private networks: - Stable, high-speed connection - Firewall rules for: - `80`: Web UI (Management Identity, Hub, and IdP if co-located) - `82`: Metrics (Management Identity) - `8080`: REST/Web UI (Connectors, Orchestration Cluster) - `8091`: Management (Hub) - `8092`: Management (Optimize) - `9600`: Management (Orchestration Cluster) - `26500`: gRPC endpoint - `26501`: Gateway-to-broker - `26502`: Inter-broker A load balancer is recommended to distribute traffic and expose Camunda 8 to users as needed. The exposed Kubernetes service port may differ from the internal component port. Refer to the rendered Helm chart or component configuration for accurate target ports. :::note Databases Database ports are not included here, as databases should be maintained outside of Camunda. Default ports may vary by environment. Typical defaults include: - `5432`: PostgreSQL (Management Identity, Camunda Hub, and PostgreSQL secondary storage when used) - `9200`, `9300`, `9600`: Document-store secondary storage (Elasticsearch/OpenSearch) ::: ##### Load balancer The Zeebe Gateway as part of the Orchestration Cluster requires gRPC, which itself requires HTTP/2 to be used. It is recommended to secure the endpoint with a TLS certificate. :::tip If you do not rely on the gRPC capabilities of Camunda 8, you can safely disregard this and use the Orchestration Cluster REST API instead. ::: Camunda 8 supports both Kubernetes traffic APIs, and you can use either: | API | Support | Setup guide | | ------------------------------------------------------------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | Supported, used by the reference architectures | [Configure the Helm chart with Ingress](/self-managed/deployment/helm/configure/ingress/ingress-setup.md) | | [Gateway API](https://kubernetes.io/docs/concepts/services-networking/gateway/) | Supported | [Configure the Helm chart with Gateway API](/self-managed/deployment/helm/configure/ingress/gateway-api-setup.md) | The reference architectures use the Ingress API with [Contour](https://projectcontour.io/), a CNCF Ingress controller backed by the [Envoy proxy](https://www.envoyproxy.io/), which supports gRPC and HTTP/2. This solution is applicable independent of the cloud provider. Contour is exposed through a `LoadBalancer` Service, so the load balancer your cloud provider creates for it operates at layer 4 (on AWS, a Network Load Balancer). Contour is a choice, not a requirement. Camunda tests the reference architectures with Contour, so that is what the procedures install, but any Ingress controller supporting gRPC and HTTP/2 works, for example [Traefik](https://traefik.io/traefik/), [HAProxy](https://haproxy-ingress.github.io/), or [Envoy Gateway](https://gateway.envoyproxy.io/). Select your own controller through `global.ingress.className`. Each controller declares the gRPC upstream differently, and not on the same object. For the annotation each controller needs, see [configure the gRPC upstream](/self-managed/deployment/helm/configure/ingress/ingress-setup.md#configure-the-grpc-upstream). :::note [Ingress-nginx reached end of life in March 2026](https://kubernetes.io/blog/2025/11/11/ingress-nginx-retirement/). The Camunda 8 reference architectures moved to Contour in 8.9. ::: ### Application The Helm chart required for deploying on Kubernetes is [publicly available](https://helm.camunda.io/). Camunda maintains the required Docker images consumed by the Helm chart. These images are available on [Docker Hub](https://hub.docker.com/u/camunda) or [Camunda Enterprise Registry](https://registry.camunda.cloud). The `Dockerfile` and its default configuration are available as part of the [Camunda repository](https://github.com/camunda/camunda/blob/main/camunda.Dockerfile). ### Database The following databases are required: | Database | Requirement | | :---------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------- | | Secondary storage (Orchestration Cluster) | Elasticsearch or OpenSearch (document store) in this topology, or a supported RDBMS as an alternative. Optimize requires Elasticsearch or OpenSearch. | | PostgreSQL | Required by Management Identity and Camunda Hub. Also required by Keycloak if deployed in-cluster. | :::info OpenSearch support Camunda 8 supports both [Amazon OpenSearch](https://aws.amazon.com/opensearch-service) and the open-source [OpenSearch](https://opensearch.org/) distribution. ::: For backend trade-offs and production guidance, see [secondary storage architecture](/self-managed/reference-architecture/reference-architecture.md#secondary-storage-architecture). For more general context, see the [reference architecture overview](/self-managed/reference-architecture/reference-architecture.md#architecture). Sizing is use case dependent. It is crucial to conduct thorough load testing and benchmarking to determine the appropriate sizing for your specific environment and workload. :::note Secondary storage disk requirements Secondary storage is customer-managed, and the same disk expectations apply to both backend families. Provision it with sufficient resources and use performant SSD-backed disks, because disk latency directly impacts export throughput and overall cluster performance. - Elasticsearch/OpenSearch: see [Elasticsearch scaling](/components/best-practices/architecture/sizing-self-managed.md#elasticsearch-scaling) for disk type and sizing guidance. - RDBMS: see [secondary storage considerations](/components/best-practices/architecture/sizing-self-managed.md#secondary-storage-considerations) for sizing guidance. An RDBMS scales vertically rather than horizontally, so size the instance and its storage with more initial headroom. ::: Once deployed, the included [Grafana dashboard](/self-managed/operational-guides/monitoring/metrics.md#grafana) can be used with [Prometheus](https://prometheus.io/) to monitor for bottlenecks when exporting data from the Orchestration Cluster to your database. ## Distributions ### OpenShift Red Hat OpenShift, a Kubernetes distribution maintained by [Red Hat](https://www.redhat.com/en/technologies/cloud-computing/openshift), provides options for both managed and on-premises hosting. #### Minimum cluster requirements - Instance type: 4 vCPUs (x86_64, >3.1 GHz), 16 GiB memory - Number of dedicated nodes: 4 - Volume type: SSD - Sustained IOPS per [Disk IOPS](/components/best-practices/architecture/sizing-self-managed.md#disk-iops) - Unsupported volume types: HDD-backed volumes are not supported. #### Supported versions As stated in the general [supported environments](/reference/supported-environments.md) policy, Camunda 8 Self-Managed runs on any [certified Kubernetes](https://www.cncf.io/training/certification/software-conformance/) distribution. For OpenShift specifically, this means any release in the Red Hat **General Availability**, **Full Support**, or **Maintenance Support** lifecycle phases (see the [Red Hat OpenShift Container Platform Life Cycle Policy](https://access.redhat.com/support/policy/updates/openshift)), within the upstream [Kubernetes version skew policy](https://kubernetes.io/releases/version-skew-policy/). Our reference architectures are continuously validated against the latest stable OpenShift release available in Red Hat's GA channel. Newly released OpenShift minor versions are evaluated and validated shortly after their GA. ## Cloud specifics ### Amazon EKS #### Minimum cluster requirements - Instance type: `m6i.xlarge` (4 vCPUs, 16 GiB memory) - Number of Kubernetes nodes: 4 - Volume type: SSD `gp3` - Sustained IOPS per [Disk IOPS](/components/best-practices/architecture/sizing-self-managed.md#disk-iops) - Requires [Amazon EBS CSI driver](https://docs.aws.amazon.com/eks/latest/userguide/ebs-csi.html) to be installed and a `gp3` StorageClass [configured](https://docs.aws.amazon.com/eks/latest/userguide/create-storage-class.html) - Volume alternative: `gp2` - Only if `gp3` isn't available - Performance [varies based on volume size](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/general-purpose.html#gp2-performance); size the disk to sustain the target IOPS - Unsupported volume types: `sc1` (Cold HDD) and `st1` (Throughput HDD) are not supported. :::caution `sc1` and `st1` cannot meet Zeebe's Raft protocol disk flush requirements. They use burst credits for throughput, and once credits are exhausted, write latency spikes to hundreds of milliseconds, causing persistent Raft append timeouts and leader instability. ::: #### Load balancer The following AWS load balancers are supported by Camunda 8: - Application Load Balancer (ALB) - Network Load Balancer (NLB) The Classic Load Balancer (CLB) is the previous generation and is not supported by Camunda 8. ##### Application Load Balancer (ALB) AWS offers an [Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html) (ALB), which requires TLS termination in the load balancer and supports AWS Certificate Manager (ACM). For the setup steps and Ingress annotations, see [use an AWS Application Load Balancer](/self-managed/deployment/helm/configure/ingress/ingress-setup.md#use-an-aws-application-load-balancer). :::note AWS ALB known limitations Application Load Balancers (ALB) support HTTP/2 over HTTPS listeners and allow a maximum of 128 streams per client HTTP/2 connection. The HTTP/2 server-push feature is not supported. For details, see [AWS ALB protocols](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-target-groups.html#target-group-protocol-version:~:text=The%20maximum%20number%20of%20streams,client%20HTTP%2F2%20connection%20is%20128). If you need more than 128 streams per client, see [Network Load Balancer](#network-load-balancer-nlb). ::: ##### Network load balancer (NLB) Camunda 8 is compatible with [Contour](https://projectcontour.io/), which deploys a Network Load Balancer. In this setup, TLS must be terminated within the Ingress, so AWS Certificate Manager (ACM) cannot be used. ACM does not allow exporting the private key required for TLS termination inside the Ingress. ### Microsoft AKS #### Minimum cluster requirements - Instance type: Standard_D4as_v4 (4 vCPUs, 16 GiB memory) - Number of Kubernetes nodes: 4 - Volume type: Premium SSD v2 - Sustained IOPS per [Disk IOPS](/components/best-practices/architecture/sizing-self-managed.md#disk-iops) - Several [known limitations](https://learn.microsoft.com/en-us/azure/virtual-machines/disks-types#premium-ssd-v2-limitations), e.g., lack of [Azure Backup support](https://learn.microsoft.com/en-us/azure/backup/disk-backup-support-matrix#limitations) - Volume alternative: Premium SSD - Performance [varies based on volume size](https://learn.microsoft.com/en-us/azure/virtual-machines/disks-types#premium-ssds); size the disk to sustain the target IOPS - Unsupported volume types: Standard HDD is not supported. - Volume alternative caveat: Standard SSD is supported if sized to sustain the target IOPS continuously without relying on bursting. #### Load balancer Azure offers the **Application Gateway for Containers (AGC)**, which supports gRPC and HTTP/2 via the `GRPCRoute` resource in the [Kubernetes Gateway API](https://kubernetes.io/docs/concepts/services-networking/gateway/). Configuration details are available in the [official Azure documentation](https://learn.microsoft.com/en-us/azure/application-gateway/for-containers/grpc). ### Google GKE #### Minimum cluster requirements - Instance type: n(1|2)-standard-4 (4 vCPUs, 15 / 16 GiB memory) - Number of Kubernetes nodes: 4 - Volume type: Performance (SSD) persistent disks - Sustained IOPS per [Disk IOPS](/components/best-practices/architecture/sizing-self-managed.md#disk-iops) - On `pd-ssd`, IOPS and bandwidth scale with disk size, so size the disk to meet the target IOPS. See [GCP disk performance](https://cloud.google.com/compute/docs/disks/performance). - Unsupported volume types: Standard persistent disks (`pd-standard`, HDD-backed) cannot meet Zeebe's Raft flush latency requirements. Use SSD-backed (`pd-ssd`) volumes. #### Load balancer If you are using the [GKE Ingress](https://cloud.google.com/kubernetes-engine/docs/concepts/ingress) (Ingress-gce), see [use the GKE Ingress](/self-managed/deployment/helm/configure/ingress/ingress-setup.md#use-the-gke-ingress) for the annotations the Zeebe Gateway service needs. --- ## Manual deployment overview This reference architecture provides guidance on deploying Camunda 8 Self-Managed as a standalone Java application. This deployment method is ideal for users who prefer manual deployment on bare metal servers or virtual machines (VMs), offering full control over the environment and configuration. It is particularly suited for scenarios with specific infrastructure requirements or highly customized setups. :::note This method of deployment requires a solid understanding of infrastructure, networking, and application management. Consider evaluating your [deployment platform options](/self-managed/reference-architecture/reference-architecture.md) based on your familiarity and need. If you prefer a simpler and managed solution, [Camunda 8 SaaS](https://camunda.com/platform/) can significantly reduce maintenance efforts, allowing you to focus on your core business needs. ::: ## Key features - **Single application JAR**: Starting from Camunda 8.8, all core components (Zeebe, Tasklist, Operate, and Admin) are bundled into a single JAR file. This simplifies deployment by reducing the number of artifacts to manage. This bundled component is called Orchestration Cluster. - **Full control**: Users are responsible for all aspects of deployment, including installation, configuration, scaling, and maintenance. This offers maximum flexibility for custom environments. Other deployment options, such as containerized deployments or managed services, might offer more convenience and automation. However, VM based deployment gives you the flexibility to tailor the deployment to your exact needs, which can be beneficial for regulated or highly customized environments. For documentation on the Orchestration Cluster and Camunda Hub separation, refer to the [reference architecture overview](/self-managed/reference-architecture/reference-architecture.md#camunda-hub-vs-orchestration-cluster). ## Reference implementations This section includes deployment reference architectures for manual setups: - [Amazon EC2 deployment](/self-managed/deployment/manual/cloud-providers/amazon/aws-ec2.md) - a standard production setup with support for high availability. ## Considerations - This overview page focuses on deploying the [Orchestration Cluster](/self-managed/reference-architecture/reference-architecture.md#orchestration-cluster), the single JAR composed of Admin, Operate, Tasklist, and Zeebe, as well as the connectors runtime. Camunda Hub, Optimize, and Management Identity deployments are not included. - General guidance and examples focuses on **unix** users, but can be adapted by Windows users with options like [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) or included `batch` files. ## Architecture ![Single JAR](./img/manual-single.jpg) This above diagram illustrates a single-machine deployment using the single JAR package. While simple and effective for lightweight setups, scaling to multiple machines requires careful planning. ### High Availability (HA) ![HA JAR](./img/manual-ha.jpg) For high availability, a minimum of three machines is recommended to ensure fault tolerance and enable master election in case of failures. Refer to the [clustering documentation](/components/zeebe/technical-concepts/clustering.md) to learn more about the raft protocol and clustering concepts. ### Components The Orchestration Cluster is packaged as a single JAR file and includes the following components: - [Zeebe](/components/zeebe/zeebe-overview.md) - [Operate](/components/operate/operate-introduction.md) - [Tasklist](/components/tasklist/introduction-to-tasklist.md) - [Admin](/self-managed/components/orchestration-cluster/admin/overview.md) It facilitates: 1. **gRPC communication**: For client workers. 2. **HTTP endpoints**: Used by the Orchestration Cluster REST API and Web UI. Both types of endpoints can be routed through a load balancer to maintain availability, ensuring that the system remains accessible even if a machine becomes unavailable. While using a load balancer is optional, it is recommended for enhanced availability and security. Alternatively, you can expose static machines, ports, and IPs directly. However, direct exposure is generally discouraged due to security concerns. Connectors expose additional HTTP(s) endpoints for handling incoming webhooks, which can also be routed through the same HTTP load balancer. The Orchestration Cluster relies on a configured [secondary storage](/reference/glossary.md#secondary-storage) backend for indexing and search. Depending on your deployment and configuration, this backend can use a document-store backend ([Elasticsearch/OpenSearch](/reference/glossary.md#elasticsearchopensearch)) or an [RDBMS](/reference/glossary.md#rdbms) for supported scenarios. :::note Secondary storage is configurable. For backend trade-offs and production guidance, see [secondary storage architecture](/self-managed/reference-architecture/reference-architecture.md#secondary-storage-architecture). For RDBMS configuration details, see [RDBMS configuration](/self-managed/concepts/databases/relational-db/configuration.md) and the glossary entry [RDBMS](/reference/glossary.md#rdbms). ::: Components within the Orchestration Cluster communicate seamlessly, particularly: - **Zeebe brokers** exchange data over the internal API (port 26502) for efficient inter-broker communication. ## Requirements Before implementing a reference architecture, review the requirements and guidance outlined below. We are differentiating between `Infrastructure` and `Application` requirements. ### Infrastructure Any of the following are just suggestions for the minimum viable setup, the sizing heavily depends on your use cases and usage. It is recommended to understand the documentation on [sizing your environment](/components/best-practices/architecture/sizing-your-environment.md) and run benchmarking to confirm your required needs. #### Minimum Requirements Per Host - Modern CPU: 2 cores - Memory: 4 GB RAM - Storage: 32 GB SSD-backed, with enough sustained IOPS for your workload (see [Disk IOPS](/components/best-practices/architecture/sizing-self-managed.md#disk-iops)). HDD-backed volume types cannot meet Camunda's Raft flush latency requirements and are not supported. Suggested instance types from cloud providers: - AWS: [m7i](https://aws.amazon.com/ec2/instance-types/m7i/) series - GCP: [n1](https://cloud.google.com/compute/docs/general-purpose-machines#n1_machines) series #### Networking - Stable and high-speed network connection - Configured firewall rules to allow necessary traffic: - **8080**: Web UI / REST endpoint (Orchestration Cluster) - **9090**: Connectors - **9600**: Management endpoint (Orchestration Cluster) - **26500**: gRPC endpoint. - **26501**: Gateway-to-broker communication. - **26502**: Inter-broker communication. - Load balancer for distributing traffic (if required) :::info Customizing ports Some ports can be overwritten and are not definitive, you may conduct the documentation of each component to see how it can be done, in case you want to use a different port. Or in our example `Connectors` and `Web UIs` overlap on 8080 due to which we moved connectors to a different port. ::: ### Application - Java Virtual Machine, see [supported environments](/reference/supported-environments.md) for version details. ### Database - Secondary storage backend (supported RDBMS or Elasticsearch/OpenSearch, depending on your architecture), see [supported environments](/reference/supported-environments.md) for version details. Our recommendation is to use an external managed offer as we will not go into detail on how to manage and maintain your database. --- ## Camunda 8 reference architectures Reference architectures provide a blueprint for designing and implementing scalable, robust, and adaptable systems. The reference architectures published here help enterprise architects, developers, and IT managers streamline deployments and improve system reliability. ## Overview Reference architectures are not a one-size-fits-all solution. Each organization has unique requirements and constraints that may require modifications to the provided blueprints. Use these reference architectures as a starting point for your Camunda 8 implementation. Adapt them to ensure they align with your goals and infrastructure. ### Target users - **Enterprise architects**: Design and plan the overall system structure. - **Developers**: Understand the components and their interactions. - **IT managers**: Ensure the system meets business requirements and is maintainable. ### Key benefits - **Accelerated deployment**: Predefined best practices simplify setup, reducing time and effort to deploy a reliable workflow automation solution. - **Consistency**: Standardized components and configurations reduce errors and simplify maintenance. - **Enhanced security**: Incorporates best practices for securing Camunda 8 deployments, including encryption, authentication, and access controls. ### Support considerations Deviations from the reference architecture are expected. However, changes can introduce additional complexity, making troubleshooting more difficult. When modifications are required, document them to support future maintenance and troubleshooting. Camunda publishes [supported environments](/reference/supported-environments.md) to help you navigate supported configurations. ## Architecture ### Deployment topology A Camunda 8 Self-Managed deployment is built around one [management plane](/reference/glossary.md#management-plane), made up of Camunda Hub and Management Identity and shared across environments. Each environment, such as development, integration, or production, can have one or more Orchestration Cluster deployments. Each Orchestration Cluster hosts one or more [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md), including the default tenant. Each Physical Tenant is served by its own Optimize deployment. ![Camunda Hub and Management Identity in a shared management plane above development, integration, and production Orchestration Clusters. Each Physical Tenant connects to its own Optimize deployment. Production shows the default tenant and Physical Tenant A, with stacked cards indicating additional Physical Tenants and their own Optimize deployments.](./img/deployment-topology.svg) The stacked cards in the diagram represent additional Physical Tenants and their own Optimize deployments. Each Optimize deployment analyzes the data of its associated Physical Tenant. Each Orchestration Cluster is deployed, scaled, and upgraded on its own schedule, while the management plane maintains the cluster inventory and Management Identity permission and role configuration. With Keycloak, Management Identity also provisions workload clients. With Microsoft Entra ID or another generic OIDC provider, operators provision workload clients separately. Physical Tenants isolate data within a cluster, and the topology is fully declarative, so it fits GitOps tooling such as Argo CD or Flux. To implement this topology on Kubernetes, see [install the deployment topology](/self-managed/deployment/helm/install/topology/index.md). ### Management plane vs Orchestration Cluster {#camunda-hub-vs-orchestration-cluster} When designing a reference architecture, it's essential to understand the differences between the management plane and the Orchestration Cluster. These components serve different purposes, include distinct elements, and are deployed separately. #### Management plane {#camunda-hub} ![Management plane with Ingress routing to Camunda Hub and Management Identity, RDBMS persistence, and an OIDC authentication provider. Arrows point from callers to services.](./img/management-cluster.svg) Ingress routes browser requests to Camunda Hub and Management Identity. Camunda Hub calls the Management Identity API, and Management Identity connects to an OIDC authentication provider. Camunda Hub persists data in an RDBMS. Management Identity requires its own database when using a generic OIDC provider or Microsoft Entra ID, or when the `resource-permissions` or `multi-tenancy` feature flag is enabled with Keycloak. With the default Keycloak-based setup and neither feature flag enabled, Management Identity doesn't require its own database. The management plane can connect to multiple Orchestration Clusters across environments, such as development, integration, and production. It consists of: - [Camunda Hub](/components/hub/index.md): Manage organizational resources, analyze operations and business value, and deliver agentic processes at scale. - [Management Identity](/self-managed/components/management-identity/overview.md): Centralized authentication and authorization service. :::note Admin separation Camunda Hub uses a separate Management Identity deployment, distinct from the embedded Admin in the Orchestration Cluster. Optimize also requires Management Identity and cannot use the embedded Orchestration Cluster Admin. ::: #### Orchestration Cluster ![Orchestration Cluster with Zeebe, Operate, Tasklist, and Admin, connected to clients and web UIs through ingress, Connectors, secondary storage, and an OIDC provider.](./img/orchestration-cluster.svg) The Orchestration Cluster is the core of Camunda. Zeebe, Operate, Tasklist, and Admin are bundled into a single artifact: - [Zeebe](/components/zeebe/zeebe-overview.md): Highly scalable, cloud-native workflow engine that tracks the state of active process instances and drives business processes from start to finish. - [Operate](/components/operate/operate-introduction.md): Monitoring tool for visualizing and troubleshooting process instances running in Zeebe. - [Tasklist](/components/tasklist/introduction-to-tasklist.md): User interface for interacting with user tasks, including assigning and completing them. - [Admin](/self-managed/components/orchestration-cluster/admin/overview.md): Integrated authentication and authorization service for managing access to all Orchestration Cluster components and APIs. [Connectors](/components/connectors/introduction.md) are reusable building blocks for connecting processes to external systems, applications, and data. They run as a separate workload, but are deployed with the Orchestration Cluster as part of the same release. Throughout these guides, "Orchestration Cluster" includes Connectors unless stated otherwise. This unified architecture ensures seamless communication, consistent state management, and reliable process execution across all components. #### Optimize [Optimize](/components/optimize/what-is-optimize.md) is a business intelligence tool for analyzing bottlenecks and examining improvements in automated processes. It analyzes process data exported by an Orchestration Cluster. ![Optimize deployment with Ingress forwarding web UI requests to Optimize, which queries secondary storage and either shared or dedicated Management Identity.](./img/optimize.svg) Optimize imports Zeebe records from Elasticsearch or OpenSearch and stores its own analytics data there. The exporters run in the Orchestration Cluster. Optimize calls the Management Identity API to retrieve permissions and tenant authorizations. Optimize is deployed separately from the Orchestration Cluster, one instance per Physical Tenant, because each instance reads exported records from a single index prefix. Optimize requires Management Identity and can't use the Orchestration Cluster's Admin. It can use the Management Identity in the management plane, shared with Camunda Hub, or a separate one when Physical Tenants need identical logical tenant IDs enforced independently. See [Optimize and Physical Tenants](/self-managed/concepts/physical-tenants/optimize.md#known-limitation-logical-tenants-with-the-same-id-across-physical-tenants). #### Admin vs Management Identity The following table outlines the key differences between Admin and Management Identity: | Category | Admin | Management Identity | | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Scope | Provides access and permission management for all Orchestration Cluster components: Zeebe, Operate, Tasklist, and the Orchestration Cluster REST and gRPC API. | Manages access for Camunda Hub and Optimize. | | Unified access management | Authentication and authorizations are handled directly by the Orchestration Cluster across all components and APIs, eliminating any dependency on Management Identity. | Handles access for Camunda Hub and Optimize only. Doesn't manage access to Orchestration Cluster components. | | Authentication | No authentication: No authentication required for API access. Form-based login in the UI. Users and groups are managed in Admin.Basic authentication: API access with Basic authentication. Form-based login in the UI. Users and groups are managed in Admin.OIDC: Any compatible identity provider (for example, Keycloak, Microsoft Entra ID, Okta). | Direct Keycloak integration (default).OIDC: Any compatible identity provider (for example, Keycloak, Microsoft Entra ID, Okta). | | Authorizations | Fine-grained [authorizations](/components/concepts/access-control/authorizations.md) provide consistent access control for process instances, tasks, and decisions across components and APIs. | Not applicable. Camunda Hub and Optimize use [roles and permissions](/self-managed/components/management-identity/access-management/access-management-overview.md). | | Keycloak integration | Treated as a standard external identity provider integrated via OIDC, making it easier to use other providers without special integration. | Default Keycloak integration, with OIDC available for other providers. | | Tenant management | Tenants are directly managed within the Orchestration Cluster, allowing per-cluster tenant management. | Does not manage tenants for Orchestration Cluster components. Tenants apply only to Optimize. | For production environments, use an external [identity provider](/self-managed/deployment/helm/configure/authentication-and-authorization/external-oidc-provider.md) to connect both environments. For a decision tree covering which one to configure for your deployment, see [how identity works in Camunda](/self-managed/components/identity/how-identity-works.md). ### Databases Databases can be deployed as part of the Camunda clusters, but using external databases or managed services offers several advantages: - **Flexibility**: Choose the database technology that fits your needs and existing infrastructure. See [supported environments](/reference/supported-environments.md#component-requirements). - **Scalability**: External databases can be scaled independently of Camunda components for better performance and resource management. - **Maintenance**: Database management, including upgrades, can be handled separately. - **Compliance**: External databases support specific governance and compliance requirements. While some guides explain how to deploy databases with Camunda, the recommendation is to manage databases externally for greater control and flexibility. ### Secondary storage architecture Choose the secondary storage architecture before you finalize a production deployment pattern. This decision applies across manual, containerized, and Kubernetes deployments. For production, use an external managed service or an externally operated database cluster whenever possible. Camunda does not manage database high availability, failover, backups, or lifecycle operations for you. #### Production topology baseline For a production Orchestration Cluster, use these baseline assumptions regardless of deployment method: - Run at least three brokers across three availability zones for high availability. On Kubernetes, the default anti-affinity rule alone does not enforce zonal placement — see [high availability](/self-managed/reference-architecture/kubernetes.md#high-availability-ha). - Use one secondary storage backend family for the Orchestration Cluster's web applications and APIs. - Keep the secondary storage backend in the same region as the Orchestration Cluster to reduce latency and failure domains. - Treat secondary storage as part of your production data layer, with its own backup, monitoring, and scaling plan. #### Compare Elasticsearch/OpenSearch and RDBMS Both backend families are supported for production in the right scenarios. Choose based on query patterns, operational preferences, and component requirements. | Topic | Elasticsearch/OpenSearch | RDBMS | | ---------------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | Best fit | Search-heavy, filter-heavy, analytics-heavy workloads | Teams that prefer relational database operations and moderate query workloads | | Write throughput profile | Higher write throughput in published comparison tests | Lower write throughput than Elasticsearch/OpenSearch in current published tests | | Read/query profile | Better suited for broad filtering, sorting, aggregations, and dashboard-style queries | Better suited for key-based access and moderate query workloads; broad filters and statistics queries need closer validation | | Optimize support | Required for Optimize | Optimize still requires Elasticsearch or OpenSearch | | Operational model | Adds a document-store technology to your stack | Reuses standard relational database tooling and operational practices | | Migration between backend families | Not supported as an in-place production migration | Not supported as an in-place production migration | Use benchmarking and workload validation before choosing a backend for production. For current published PostgreSQL results and caveats, see [RDBMS benchmark results](/self-managed/concepts/secondary-storage/rdbms-benchmark-results.md). For general capacity planning, see [sizing your environment](/components/best-practices/architecture/sizing-your-environment.md). #### Backend-specific guidance Choose Elasticsearch/OpenSearch when: - You expect heavy search, filtering, sorting, or dashboard-style query workloads. - You need Optimize and want to avoid running two secondary storage technologies. - You want the strongest current fit for large query-heavy environments. Choose RDBMS when: - You already operate relational databases at scale and want to align with existing tooling. - Your workloads are moderate and you can validate query performance with production-like data. - You prefer a relational secondary storage model for Orchestration Cluster APIs and web applications. If you deploy Optimize with RDBMS-based secondary storage, plan for both backends: RDBMS for the Orchestration Cluster and Elasticsearch or OpenSearch for Optimize. For supported versions and configuration details, see: - [Secondary storage overview](/self-managed/concepts/secondary-storage/index.md) - [Configure secondary storage](/self-managed/concepts/secondary-storage/configuring-secondary-storage.md) - [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) - [Supported environments](/reference/supported-environments.md) ### High availability (HA) High availability (HA) ensures that a system remains operational even when components fail. All components can run in HA mode, but Optimize requires special consideration: the importer/archiver must run on only one replica at a time. See the [Optimize configuration](/self-managed/components/optimize/configuration/system-configuration-platform-8.md#general-settings) for details. Consider regional and zonal placement of workloads. Use at least three zones in a region to maintain availability if a zone fails. On Kubernetes, see [high availability](/self-managed/reference-architecture/kubernetes.md#high-availability-ha) for how to enforce that placement. For protection against a complete region outage, compare the deployment strategies in [multi-region resilience](/self-managed/concepts/multi-region/resilience-tiers.md#get-started-choose-your-strategy). For more information on how Zeebe handles fault tolerance, see the [Raft consensus chapter](/components/zeebe/technical-concepts/clustering.md#raft-consensus-and-replication-protocol). If running a single instance, implement [regular backups](/self-managed/operational-guides/backup-restore/backup-and-restore.md), as resilience will be limited. ## Available reference architectures Choose a reference architecture based on factors such as your organization’s goals, infrastructure, and requirements. Use the following guides to plan your deployment: ### Kubernetes Kubernetes is a powerful orchestration platform for containerized applications. A Kubernetes reference architecture provides guidelines for setting up clusters, managing workloads, and ensuring high availability and scalability. - Ideal for organizations adopting containerization and microservices (see [Cloud Native Computing Foundation](https://www.cncf.io/)). - Suitable for dynamic scaling and high availability. - Best for teams experienced in managing containerized environments. - Offers high resilience but comes with a steep learning curve. See [Kubernetes deployment overview](/self-managed/reference-architecture/kubernetes.md). ### Containers Containers, such as Docker, provide a portable and consistent runtime environment. They simplify development, testing, and deployment across environments by encapsulating applications and their dependencies. - A middle ground between manual setups and Kubernetes that provides the benefits of containerization without the overhead of Kubernetes. - Containers can run on any system that supports the container runtime, ensuring consistency across development, testing, and production environments. - Each container runs in its own isolated environment, which helps prevent conflicts between applications and improves security. - Containers can be easily scaled up or down to handle varying workloads, providing flexibility in resource management. See [Camunda Docker images](/self-managed/deployment/docker/docker.md). ### Manual (bare metal/virtual machines) For organizations that prefer traditional infrastructure, bare metal or VM-based reference architectures offer a structured approach to system deployment. These architectures provide best practices for setting up physical servers or VMs, configuring networks, and managing storage using Infrastructure as Service cloud providers. They are suitable for environments where containerization or use of Kubernetes services may not be feasible. - Suitable for IaaS, bare metal, or traditional infrastructures. - Ideal for traditional setups needing highly customized security, strict data residency, or industry-specific regulatory compliance. - Applicable for high availability but requires more detailed planning. - Best for teams with expertise in managing physical servers or virtual machines. See [Manual deployment overview](/self-managed/reference-architecture/manual.md). ### Local development For local evaluation or development, use [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md), a simplified distribution for developers. --- ## Install Camunda 8 Self-Managed for production and advanced development setups Use this overview to choose an installation approach for Camunda 8 Self-Managed in production-ready environments (cloud or on-premises), and in advanced development setups that mirror production for CI/CD, integration testing, or shared clusters. ## Production installations :::note Starting in 8.9, Camunda 8 Run uses H2 as the default secondary storage out of the box. Elasticsearch remains a supported alternative in Camunda 8 Run. OpenSearch and RDBMS-based secondary storage are supported in Self-Managed deployments. See the [Camunda 8 Run configuration docs](../quickstart/developer-quickstart/c8run.md) for backend configuration details. ::: - [**Helm/Kubernetes**](/self-managed/deployment/helm/install/quick-install.md) (Recommended): We recommend using Kubernetes and Helm to run Camunda 8 Self-Managed in production. With the right configuration, Camunda 8 Self-Managed can be deployed on any Certified Kubernetes distribution (cloud or on-premises). We also officially support a variety of providers like [Red Hat OpenShift](../../self-managed/deployment/helm/cloud-providers/openshift/redhat-openshift.md) and [Amazon EKS](../../self-managed/deployment/helm/cloud-providers/amazon/amazon-eks/amazon-eks.md). - [**Docker**](/self-managed/deployment/docker/docker.md): Run Camunda components as [Docker images](https://hub.docker.com/u/camunda) in production on Linux systems. Windows and macOS are supported for development environments only. - [**Manual**](/self-managed/deployment/manual/install.md): Run each Java application on virtual machines or bare-metal servers with a supported Java Virtual Machine (JVM). This offers flexibility but requires manual configuration of component interactions. Use this approach only when necessary. Windows and macOS are supported for development environments only. :::info To run Camunda 8 in a local environment for development or evaluation purposes only, see [running locally](/self-managed/quickstart/developer-quickstart.md). ::: ## Production storage choices Choosing the right storage configuration is a critical step for production deployments: - Install Camunda 8 using one of the production deployment options above (Helm/Kubernetes recommended). - Review storage concepts: [primary and secondary storage overview](/components/concepts/concepts-overview.md) and [details on secondary storage](/self-managed/concepts/secondary-storage/index.md). - Provision your chosen storage backend(s) before enabling web applications such as Operate, Tasklist, or Optimize. Guidance: - Prefer managed services or operator-based infrastructure for production deployments to reduce operational overhead and improve reliability (for example, managed secondary storage services such as Elasticsearch/OpenSearch or managed RDBMS). Choose the backend that best fits your operational model, performance profile, and compliance requirements. - Benchmark and size your environment using [sizing your environment](/components/best-practices/architecture/sizing-your-environment.md) and the Camunda benchmark project referenced there. :::info For environment compatibility and supported versions, see [supported environments](/reference/supported-environments.md). ::: --- ## Upgrade Camunda components from 8.9 to 8.10 Review component-level actions that may be required when upgrading a Camunda 8 Self-Managed deployment from 8.9.x to 8.10.x. ## About Use this page with the deployment upgrade guide for your environment. Start with the [Upgrade Camunda 8 overview](/self-managed/upgrade/index.md), then apply any component-specific steps that match your setup. ## Camunda Hub In 8.10, Camunda Hub replaces Console and Web Modeler. To support this change: - Console-specific configurations have been removed. - Cluster configurations have been updated. Additionally, when you upgrade, your data is [migrated](#data-migration) to the [new file structure](/reference/announcements-release-notes/8100/whats-new-in-810.md#new-file-structure-and-requirements). If you use a custom configuration, review this section and make applicable changes. Otherwise, the configuration updates mentioned here will not be relevant. Skip ahead to the [data migration](#data-migration). ### Rolling upgrades There is no rolling upgrade path from Web Modeler 8.9 to Camunda Hub 8.10. Shut down all Web Modeler instances so the database has no more writers, take a database backup, then start one or more Camunda Hub 8.10 instances. Starting Camunda Hub triggers the database migration; once it completes, Hub resumes serving traffic. See [version upgrade](/self-managed/components/hub/version-upgrade.md) for the underlying principles. If you deploy with Helm, this procedure is already handled for you via the `camundaHub.upgrade.phase` values. See [migrate Camunda Hub](/self-managed/upgrade/helm/890-to-8100.md#migrate-web-modeler-and-console-to-camunda-hub) for the Helm-specific steps. You do not need to perform the manual steps above. ### Console configuration If you use a custom configuration, review this section and the following ones, and make applicable changes. Otherwise, they are not applicable to your setup. Console no longer exists in 8.10. Therefore, if you've configured Console with custom settings, remove those settings: - If using application properties, remove the top-level `camunda.console` object. - If using environment variables, remove the following variables: - `CAMUNDA_CONSOLE_CONTEXT_PATH` - `CAMUNDA_CONSOLE_CUSTOMERID` - `CAMUNDA_CONSOLE_DISABLE_AUTH` - `CAMUNDA_CONSOLE_EXPERIMENTAL_DISCOVERY_MODE` - `CAMUNDA_CONSOLE_INSTALLATIONID` - `CAMUNDA_CONSOLE_REDIRECT_TRAILING_SLASH` - `CAMUNDA_CONSOLE_REDIRECT_URL` - `CAMUNDA_CONSOLE_TELEMETRY` The application fails on startup if any of these are configured, whether as an application property or as one of the environment variables above. ### Management Identity roles and permissions Management Identity only adds roles, applications, and permissions on startup; it never removes them. As a result: - If you hold the `Console` role, you automatically gain management access to Hub's cluster pages through a new `admin:clusters` permission after upgrading. No manual role reassignment is required. `DevOps` is the forward-looking name for the same access. - If you hold the `Web Modeler Admin` role, you also automatically gain full access to Hub's cluster pages after upgrading — a broader grant than the Console role's management-only access, since `admin:*` also carries modeler-admin capabilities. In 8.9, this role's `admin:*` permission only covered Web Modeler super-user mode and publishing connector templates; in 8.10, the same permission additionally reaches Hub's cluster pages. No manual role reassignment is required. `Hub Admin` is the forward-looking name for the same access. - After upgrading, your Keycloak also gains roles named `Hub` and `Hub Admin`, provisioned alongside your existing `Web Modeler` and `Web Modeler Admin` roles (which are kept for backward compatibility and keep working unchanged). This applies to every installation, not just new ones, so you may see both name pairs after upgrading. Both name pairs grant identical permissions; assign whichever name makes sense for your users. - The standalone Keycloak `Console` application and `console-api` audience are no longer provisioned by Management Identity. Console's cluster-management pages are now part of the Hub UI application, so a separate OIDC application/client for Console is no longer needed. Your existing `console` client and its role mappings are **not** deleted automatically. If you no longer need them, remove them manually from Keycloak (or your OIDC provider). If you rely on least-privilege access to cluster management, review who holds the `Console` and `Web Modeler Admin` / `Hub Admin` roles before upgrading. See the [8.10 release announcements](/reference/announcements-release-notes/8100/8100-announcements.md#console-and-web-modeler-admin-roles-gain-new-hub-cluster-access-on-self-managed) for a summary of this and other Hub role changes in 8.10. For the full list of default roles, applications, and permissions in 8.10, see [manage roles](/self-managed/components/management-identity/application-user-group-role-management/manage-roles.md) and [manage access and permissions](/self-managed/components/management-identity/access-management/access-management-overview.md). ### Migrate Web Modeler settings Migrate your Camunda Hub configuration from the legacy Web Modeler settings to the new Hub equivalents. No immediate action is required, as Camunda Hub still accepts the legacy Web Modeler properties in 8.10 and translates the recognized properties to their new equivalents at startup. Hub logs a warning that lists each legacy property it finds. Support for the legacy settings will be removed in 8.12, so update your configuration ahead of that removal. | Web Modeler (8.9 and earlier) | Camunda Hub (8.10) | | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Application properties prefixed with `camunda.modeler.*` | Replaced by `camunda.hub.*` | | Environment variables prefixed with `CAMUNDA_MODELER_*` | Replaced by `CAMUNDA_HUB_*` | | Other environment variables | Replaced by environment variables strictly derived from the respective application property name (for example, `CAMUNDA_HUB_*` for Hub-owned properties, `SPRING_*` for Spring Boot properties). Hub no longer uses custom, non-matching environment variable names. See the examples below. | Examples of the replacement rule for other environment variables: - `OAUTH2_CLIENT_ID` to `CAMUNDA_HUB_OAUTH2_CLIENTID` - `CLIENT_PUSHER_PATH` to `CAMUNDA_HUB_PUSHER_CLIENT_PATH` - `RESTAPI_MAIL_USER` to `SPRING_MAIL_USERNAME` In addition to the replacement rules above, the following properties were renamed or moved to a different property path: | Web Modeler (8.9 and earlier) | Camunda Hub (8.10) | | ------------------------------------------------- | ----------------------------------------- | | `camunda.marketplace.enabled` | `camunda.hub.feature.marketplace-enabled` | | `camunda.modeler.feature.play-enabled` | `camunda.hub.feature.test-mode-enabled` | | `camunda.modeler.security.jwt.issuer.backend-url` | `camunda.identity.issuer-backend-url` | | `camunda.modeler.oauth2.token.username-claim` | `camunda.identity.username-claim` | | `logging.level.io.camunda.modeler` | `logging.level.io.camunda.hub` | | Web Modeler (8.9 and earlier) | Camunda Hub (8.10) | | -------------------------------------------- | ---------------------------------------- | | `MARKETPLACE_ENABLED` | `CAMUNDA_HUB_FEATURE_MARKETPLACEENABLED` | | `PLAY_ENABLED` | `CAMUNDA_HUB_FEATURE_TESTMODEENABLED` | | `RESTAPI_OAUTH2_TOKEN_ISSUER_BACKEND_URL` | `CAMUNDA_IDENTITY_ISSUERBACKENDURL` | | `CAMUNDA_MODELER_OAUTH2_TOKEN_USERNAMECLAIM` | `CAMUNDA_IDENTITY_USERNAMECLAIM` | | `LOGGING_LEVEL_IO_CAMUNDA_MODELER` | `LOGGING_LEVEL_IO_CAMUNDA_HUB` | For an exhaustive list of the new settings, refer to the [Camunda Hub property reference](/self-managed/components/hub/configuration/properties.md). #### Update the log file name The log file name in the default Log4j2 configuration changed from `camunda-modeler.log` to `camunda-hub.log`. Logging to a file is still disabled by default, so this change only affects you if you enabled file logging in Web Modeler and use the default configuration. If external tooling such as log shippers, monitoring, or rotation scripts depends on the log file name, update it to `camunda-hub.log`. For the full default configuration, see [Camunda Hub logging](/self-managed/components/hub/configuration/logging.md). ### Cluster configuration The following settings have been ported from the Console configuration in 8.9 to Camunda Hub in 8.10: | 8.9 | 8.10 | | :------------------------------------------------------------ | :---------------------------------------------------------------- | | `camunda.console.managed.releases[0].tags` | `camunda.hub.clusters[0].tags` | | `camunda.console.managed.releases[0].custom-properties` | `camunda.hub.clusters[0].custom-properties` | | `camunda.console.managed.releases[0].components` | `camunda.hub.clusters[0].components` | | `camunda.console.managed.releases[0].components[0].id` | `camunda.hub.clusters[0].components[0].type` | | `camunda.console.managed.releases[0].components[0].url` | `camunda.hub.clusters[0].components[0].urls.` | | `camunda.console.managed.releases[0].components[0].readiness` | `camunda.hub.clusters[0].components[0].urls.readiness` | | `camunda.console.managed.releases[0].components[0].metrics` | Removed. | ### Cluster configuration example Console 8.9 configuration: ```yaml camunda: console: managed: releases: - name: camunda-platform namespace: qa-camunda-platform version: 8.9.0 tags: - dev custom-properties: - description: "Monitoring" links: - name: "Grafana" url: "http://localhost:3000" - name: "Prometheus" url: "http://localhost:9090" - description: "Documentation" links: - name: "Wiki" url: "http://localhost:8090/wiki" components: - name: camunda-platform namespace: camunda-platform-namespace version: 9.1.2 components: - name: Console id: console version: 8.9-SNAPSHOT url: https://qa.ci.distro.ultrawombat.com/ readiness: http://camunda-platform-console.qa-camunda-platform:9100/health/readiness metrics: http://camunda-platform-console.qa-camunda-platform:9100/prometheus - name: Keycloak id: keycloak url: https://qa.ci.distro.ultrawombat.com/auth/ - name: Identity id: identity version: SNAPSHOT url: https://qa.ci.distro.ultrawombat.com/identity readiness: http://camunda-platform-identity.qa-camunda-platform:82/actuator/health metrics: http://camunda-platform-identity.qa-camunda-platform:82/actuator/prometheus - name: WebModeler id: webModelerWebApp version: SNAPSHOT url: https://qa.ci.distro.ultrawombat.com/modeler readiness: http://camunda-platform-web-modeler-restapi.qa-camunda-platform:8091/modeler/health/readiness metrics: http://camunda-platform-web-modeler-restapi.qa-camunda-platform:8091/modeler/metrics - name: Optimize id: optimize version: 8.9-SNAPSHOT url: https://qa.ci.distro.ultrawombat.com/optimize readiness: http://camunda-platform-optimize.qa-camunda-platform:80/optimize/api/readyz metrics: http://camunda-platform-optimize.qa-camunda-platform:8092/actuator/prometheus - name: Connectors id: connectors version: 8.9-SNAPSHOT url: http://camunda-platform-connectors.qa-camunda-platform:8080/connectors readiness: http://camunda-platform-connectors.qa-camunda-platform:8080/connectors/actuator/health/readiness metrics: http://camunda-platform-connectors.qa-camunda-platform:8080/connectors/actuator/prometheus - name: Operate id: operate version: 8.9-SNAPSHOT url: https://qa.ci.distro.ultrawombat.com/core/operate readiness: http://camunda-platform-zeebe.qa-camunda-platform:9600/core/actuator/health/readiness metrics: http://camunda-platform-zeebe.qa-camunda-platform:9600/core/actuator/prometheus - name: Tasklist id: tasklist version: 8.9-SNAPSHOT url: https://qa.ci.distro.ultrawombat.com/core/tasklist readiness: http://camunda-platform-zeebe.qa-camunda-platform:9600/core/actuator/health/readiness metrics: http://camunda-platform-zeebe.qa-camunda-platform:9600/core/actuator/prometheus - name: Orchestration Admin id: orchestrationIdentity version: 8.9-SNAPSHOT url: https://qa.ci.distro.ultrawombat.com/core/admin readiness: http://camunda-platform-zeebe.qa-camunda-platform:9600/core/actuator/health/readiness metrics: http://camunda-platform-zeebe.qa-camunda-platform:9600/core/actuator/prometheus - name: Orchestration Cluster id: orchestration version: 8.9-SNAPSHOT urls: grpc: https://grpc-qa.ci.distro.ultrawombat.com http: https://qa.ci.distro.ultrawombat.com/core readiness: http://camunda-platform-zeebe.qa-camunda-platform:9600/core/actuator/health/readiness metrics: http://camunda-platform-zeebe.qa-camunda-platform:9600/core/actuator/prometheus ``` Camunda Hub 8.10 configuration: ```yaml camunda: hub: clusters: - id: "camunda-platform" name: "camunda-platform" namespace: "qa-camunda-platform" version: "8.10.0" authentication: BEARER_TOKEN authorizations: enabled: true tags: - "dev" custom-properties: - description: "Monitoring" links: - name: "Grafana" url: "http://localhost:3000" - name: "Prometheus" url: "http://localhost:9090" - description: "Documentation" links: - name: "Wiki" url: "http://localhost:8090/wiki" components: - name: "Identity" type: "identity" version: "8.10-SNAPSHOT" urls: webapp: "https://qa.ci.distro.ultrawombat.com/identity" readiness: "http://camunda-platform-identity.qa-camunda-platform:82/actuator/health" - name: "Camunda Hub" type: "hub" version: "8.10-SNAPSHOT" urls: webapp: "https://qa.ci.distro.ultrawombat.com/modeler" readiness: "http://camunda-platform-web-modeler-restapi.qa-camunda-platform:8091/modeler/health/readiness" - name: "Optimize" type: "optimize" version: "8.10-SNAPSHOT" urls: webapp: "https://qa.ci.distro.ultrawombat.com/optimize" readiness: "http://camunda-platform-optimize.qa-camunda-platform:80/optimize/api/readyz" - name: "Connectors" type: "connectors" version: "8.10.0" urls: rest: "http://camunda-platform-connectors.qa-camunda-platform:8080/connectors" readiness: "http://camunda-platform-connectors.qa-camunda-platform:8080/connectors/actuator/health/readiness" - name: "Operate" type: "operate" version: "8.10-SNAPSHOT" urls: webapp: "https://qa.ci.distro.ultrawombat.com/core/operate" readiness: "http://camunda-platform-zeebe.qa-camunda-platform:9600/core/actuator/health/readiness" - name: "Tasklist" type: "tasklist" version: "8.10-SNAPSHOT" urls: webapp: "https://qa.ci.distro.ultrawombat.com/core/tasklist" readiness: "http://camunda-platform-zeebe.qa-camunda-platform:9600/core/actuator/health/readiness" - name: "Orchestration Admin" type: "admin" version: "8.10-SNAPSHOT" urls: webapp: "https://qa.ci.distro.ultrawombat.com/core/admin" readiness: "http://camunda-platform-zeebe.qa-camunda-platform:9600/core/actuator/health/readiness" - name: "Orchestration Cluster" type: "orchestration" version: "8.10-SNAPSHOT" urls: grpc: "https://grpc-qa.ci.distro.ultrawombat.com" rest: "https://qa.ci.distro.ultrawombat.com/core" readiness: "http://camunda-platform-zeebe.qa-camunda-platform:9600/core/actuator/health/readiness" ``` ### Authentication configuration No action is required to upgrade to 8.10, except for a customized user ID claim, which you should migrate to the new property (see the list below). The old property still works in 8.10. Camunda Hub continues to authenticate with its own properties, documented under [Identity / Keycloak](/self-managed/components/hub/configuration/properties.md#identity--keycloak), not with the Orchestration Cluster's `camunda.security.authentication.oidc.*` settings. For the one exception, the username claim, see [Identity / Keycloak](/self-managed/components/hub/configuration/properties.md#identity--keycloak). Note the following: - Cross-origin resource sharing (CORS) settings are unchanged. Hub continues to use its own CORS configuration. - User, group, role, tenant, and permission management is unchanged in 8.10, and is still handled by [Management Identity](/self-managed/components/management-identity/overview.md). - Camunda Hub 8.10 deprecates the Web Modeler 8.9 property `camunda.modeler.oauth2.token.user-id-claim` (`CAMUNDA_MODELER_OAUTH2_TOKEN_USERIDCLAIM`) in favor of `camunda.security.authentication.oidc.username-claim`. The old property is still accepted in 8.10, but if you changed the user ID claim in 8.9, switch to the new property. See [change the user ID claim](/self-managed/components/hub/configuration/identity.md#change-the-user-id-claim). For the full Hub authentication configuration, see [Camunda Hub authentication](/self-managed/components/hub/configuration/identity.md). ### Component types `console` and `keycloak` are no longer valid component types. The application will fail on startup if you've configured a `console` or `keycloak` component. Additionally, the following component types have been renamed: - `webModelerWebApp` → `hub` - `orchestrationIdentity` → `admin` The old values are still accepted for backward compatibility, but you should update your configuration to use the new values. ```yaml camunda: hub: clusters: - id: camunda-platform # other fields... components: - name: "Console" type: "console" # type cannot be "console" or "keycloak" version: "8.10-SNAPSHOT" urls: webapp: "https://qa.ci.distro.ultrawombat.com/" readiness: "http://camunda-platform-console.qa-camunda-platform:9100/health/readiness" ``` ### Broker ping configuration If you configured `camunda.console.ping` in your broker to send license information to Console, rename the configuration namespace to `camunda.hub.ping` and update the corresponding environment variables. | 8.9 property | 8.10 property | | :------------------------------------------------ | :-------------------------------------------- | | `camunda.console.ping.enabled` | `camunda.hub.ping.enabled` | | `camunda.console.ping.endpoint` | `camunda.hub.ping.endpoint` | | `camunda.console.ping.clusterName` | `camunda.hub.ping.clusterName` | | `camunda.console.ping.pingPeriod` | `camunda.hub.ping.pingPeriod` | | `camunda.console.ping.properties` | `camunda.hub.ping.properties` | | `camunda.console.ping.retry.maxRetries` | `camunda.hub.ping.retry.maxRetries` | | `camunda.console.ping.retry.minRetryDelay` | `camunda.hub.ping.retry.minRetryDelay` | | `camunda.console.ping.retry.maxRetryDelay` | `camunda.hub.ping.retry.maxRetryDelay` | | `camunda.console.ping.retry.retryDelayMultiplier` | `camunda.hub.ping.retry.retryDelayMultiplier` | The `POST /clusters` endpoint now requires authentication. Add the new `camunda.hub.ping.credentials` block with your OAuth 2.0 M2M client credentials. See the [broker configuration reference](/self-managed/components/orchestration-cluster/zeebe/configuration/broker.md#camunda-hub-ping-configuration) for details. ### Dynamic cluster management In Camunda 8.8 and 8.9, you could use `CAMUNDA_CONSOLE_EXPERIMENTAL_DISCOVERY_MODE` to expose the discovery API, which allowed clusters to send license information and register themselves with Console. This experimental feature is now replaced by a new [feature flag](/self-managed/components/hub/configuration/properties.md#feature-flags) in the Camunda Hub configuration: `camunda.hub.feature.dynamic-cluster-management-enabled` / `CAMUNDA_HUB_FEATURE_DYNAMICCLUSTERMANAGEMENTENABLED`. With dynamic cluster management, clusters can regularly [send license information](/self-managed/components/orchestration-cluster/zeebe/configuration/broker.md#camunda-hub-ping-configuration) to a [discovery endpoint](/apis-tools/hub-api-saas/specifications/create-cluster-registration.api.mdx): ``` POST /api/v2/clusters ``` With that information, Camunda Hub registers the cluster with minimal data and no management functionality in the user interface. This behavior is similar to `CAMUNDA_CONSOLE_EXPERIMENTAL_DISCOVERY_MODE`. However, unlike in Console, where cluster records were cleaned up on restart, Camunda Hub cluster registrations persist through restarts. So you'll need to remove them yourself using the [remove cluster registration](/apis-tools/hub-api-saas/specifications/remove-cluster-registration.api.mdx) endpoint: ``` DELETE /api/v2/clusters/{clusterId} ``` Read more about dynamic cluster management in the [Camunda Hub properties reference](/self-managed/components/hub/configuration/properties.md#dynamic-cluster-management). ### Environments In 8.10, teams deploy to [environments](/components/concepts/environments.md) instead of the clusters connected to a project. What happens when you upgrade: - Camunda Hub creates environments from the clusters in your `camunda.hub.clusters` configuration. On a cluster at version 8.10 or later, each Physical Tenant is an environment. See [environments and Physical Tenants](/self-managed/components/hub/configuration/environments.md#physical-tenants). - During the data migration, Camunda Hub assigns to each workspace an environment for every cluster that its projects used at any deployment stage, and for every cluster that its IDP projects used. If several projects share a workspace, the workspace gets all their clusters, and each one is assigned once. Existing projects can continue to deploy to the clusters they used before. For a cluster at version 8.10 or later, the environment is backed by the `default` Physical Tenant and keeps its workspace assignments. - Projects no longer have their own deployment stages or connected clusters. A project can deploy to every environment assigned to its workspace. - Workspaces you create after the upgrade start without environments. An organization admin must [assign environments to the workspace](/components/hub/organization/manage-environments/assign-environments.md). - A cluster or Physical Tenant that is no longer in the configuration, for example because its ID changed, stays in Camunda Hub with the status **Not reported** while a workspace uses it. This also applies to clusters that you manage with dynamic cluster management, because they aren't part of the static configuration. Their assignments stay in place, and only the status shows **Not reported**. Reassign the workspace to another environment, or remove the assignment, when you no longer need it. Before you upgrade, review the following: | Task | Why | | :-------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | (Optional) Tag a cluster with `prod` | Do this only if you want Camunda Hub to treat the environments of a cluster as production environments. Camunda Hub identifies them by the `prod` tag in `camunda.hub.clusters[].tags`, and the project deployment policy applies to them. Without the tag, no production restriction applies. | | Add a `readiness` address to components | Camunda Hub determines the status of an environment from the `urls.readiness` address of its components. Without it, the status is **Unknown**. | :::note Helm behavior In a Helm combined installation, the chart already tags the default cluster with `prod`, so the production deployment policy applies to its environment from the start. In Helm hub mode, the clusters in `global.topology.clusters` have no tags, so Camunda Hub doesn't treat their environments as production environments. ::: Camunda Hub reads the cluster configuration at startup. After you change it, perform a rolling restart. #### Deployments to production environments In earlier versions, only administrators could deploy to the production stage of a project by default. In 8.10, this default no longer applies to environments. Any collaborator with deployment privileges can deploy to an environment tagged `prod`. Access to production is controlled by which environments are assigned to the workspace and by the deployment permissions in the cluster. To require an approved project snapshot for production deployments, turn on **Require approval of project snapshots to deploy to production environments** in the [project deployment settings](/components/hub/workspace/modeler/modeler-settings.md#project-deployment). ### Data migration When you upgrade to Camunda 8.10, your data is automatically migrated to the [new organizational structure](/reference/announcements-release-notes/8100/whats-new-in-810.md#new-file-structure-and-requirements). Camunda recommends you create a backup before the migration to ensure your data is recoverable in its original state if anything goes wrong. If you notice anything unexpected after the migration, contact support. :::note Because you're upgrading to 8.10, you may not yet be familiar with the new [Camunda Hub terminology](/reference/announcements-release-notes/8100/whats-new-in-810.md#terminology). This sections uses Web Modeler terminology, including projects and process applications, to explain the data migration. However, before using the Camunda Hub UI, you should familiarize yourself with the [new terminology](/reference/announcements-release-notes/8100/whats-new-in-810.md#terminology), including workspaces and projects. ::: During the migration: - Any process application nested inside a folder is moved to the top level of its project. - Any files or folders located directly in a project, not inside a process application, is automatically grouped in a new process application, named `YOUR PROJECT NAME - General`. You can rename this application, [move content out of it](#organize-the-general-process-application), or otherwise reorganize it as with any other process application. - Git sync and cluster settings on existing process applications migrate unchanged along with your data. During the migration, Web Modeler is briefly unavailable. Clusters and running processes are unaffected and continued executing normally. The migration does not affect the following resources: | Area | Impact | | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Running process instances | Orchestration Clusters, engines, and running process instances are unaffected. Web Modeler and Camunda Hub remain independent of the runtime path. | | Redeployment | Existing deployments remain on their clusters and continue running. The migration does not require redeployment. | | Clusters and configuration | Cluster and deployment settings attached to existing process applications migrate with the data and remain unchanged. | | Files, folders, and version history | All files, folders, versions, and history are preserved. Only their location within the project changes. | | Git-synced projects | The migration does not modify process applications or their contents. Files connected through Git sync remain in the same repository with the same history. | | Desktop Modeler | Desktop Modeler is unaffected because it has no direct connection to Web Modeler. Content shared through Git sync is also unaffected. | If you automate against the Web Modeler API, the migration may affect automation that relies on file or folder locations. Web Modeler API v1 returns files and folders from their new locations. Requests that create an item at a project's root are redirected to the new `YOUR PROJECT NAME - General` process application, and the response reflects the new location. Review any automation that relies on file or folder locations. A small number of folder API integrations were affected more directly. If you use the folder API with process applications, contact support to confirm whether your integration needs updates. #### Organize the "General" process application During the migration, any files or folders located directly in a project, not inside a process application, were automatically grouped in a new process application, named "YOUR PROJECT NAME - General". This process application is a temporary container for loose files and folders. Camunda recommends organizing these resources into process applications that reflect their purpose for better long-term discoverability and maintainability. To move files from the "General" process application, first create a new process application: 1. Open your project. 2. At the top right of the project view, click **Create new > Process application**. 3. Enter a name and select a development cluster. 4. Click **Create**. Next, move the files from the "General" process application to the new one: 1. Open your "General" process application. 2. On the left side of the file list, select all the files you want to move. 3. At the top of the file list, click **Move**. 4. Select your new process application. 5. Click **Move**. ## Physical Tenants Camunda 8.10 introduces [Physical Tenants](/self-managed/concepts/multi-tenancy/physical-tenants.md), strongly isolated execution units within a single Orchestration Cluster. Upgrading a single-tenant 8.9 cluster requires no action. Adopting Physical Tenants is opt-in, and the upgrade is backward compatible by design. ### What happens automatically - Your existing root-level configuration becomes the configuration of the `default` Physical Tenant. There is no manual migration step and no data migration. - Storage keeps its existing location. Schemas, index prefixes, and document store paths are not reorganized. - The `default` Physical Tenant is always present, and cannot be renamed, disabled, or deleted. ### Existing clients keep working | Caller | 8.9 request | 8.10 behavior | | :------- | :---------------------- | :----------------------------------------------------------------------------------------------------------------- | | REST | `/v2/...` | Unchanged. Routes to the `default` Physical Tenant, and is also addressable as `/physical-tenants/default/v2/...`. | | gRPC | No tenant header | Unchanged. Requests without the `Camunda-Physical-Tenant` header route to the `default` Physical Tenant. | | Web apps | `/operate`, `/tasklist` | Unchanged. `/operate` and `/physical-tenants/default/operate` address the same application. | No client code changes are required for a single-tenant deployment. For the full routing rules, see [API routing for Physical Tenants](/self-managed/concepts/physical-tenants/api-routing.md). ### If you configure the default tenant explicitly Leaving the configuration untouched is the safe path. If you add any `camunda.physical-tenants.default.*` override, one validation rule changes. While no `camunda.physical-tenants.*` configuration is present, the implicit default tenant inherits the full set of cluster identity providers. As soon as you configure `camunda.physical-tenants.default` explicitly, it must declare its own `providers.assigned`, exactly like any other tenant. On an OIDC cluster, omitting it fails startup: ```text Invalid physical-tenant provider selection: non-default physical tenant '' must declare a non-empty 'camunda.physical-tenants..security.authentication.providers.assigned' selecting which cluster OIDC providers apply to it ``` Values under `camunda.physical-tenants.default.*` are interpreted as overrides of the existing default tenant, not as the creation of a new one. ### Add Physical Tenants after upgrading Physical Tenants are provisioned through static configuration, so adding one is a configuration change followed by a rolling restart. Dynamic creation at runtime is not available. See [provisioning and lifecycle](/self-managed/concepts/physical-tenants/provisioning-and-lifecycle.md), or [set up two isolated Physical Tenants](/self-managed/concepts/physical-tenants/getting-started.md) for a hands-on walkthrough. A tenant's lifecycle follows its configuration. A tenant present in configuration is provisioned automatically, removing it from configuration disables it and retains its data, and re-adding it re-enables the tenant with that data. Deleting a tenant's data is not supported. A disabled tenant can be [logically removed](/self-managed/concepts/physical-tenants/provisioning-and-lifecycle.md#logically-remove-a-disabled-tenant) from the cluster topology, which deletes no data. Three behaviors change once a node serves more than one tenant: - Each tenant's secondary storage is initialized independently. A tenant whose storage is unusable is degraded on its own, returning `503` with a `Retry-After` header, while the other tenants keep serving traffic. - Node readiness reports ready while at least one tenant is serviceable, so a ready node no longer implies every tenant is healthy. - Browser sessions become path-scoped per tenant. A user signed in to one tenant is not signed in to another, and signing in to a second tenant creates a second, independent session rather than replacing the first. Before adding tenants, review [storage isolation](/self-managed/concepts/physical-tenants/storage-isolation.md) so each tenant resolves to a distinct storage location. Two tenants sharing a location fails startup. ### Validate after upgrading 1. Confirm the cluster is operational with `GET /cluster/v2/status`. 2. Confirm the default tenant accepts work with `GET /physical-tenants/default/v2/topology`. 3. Run an existing unprefixed request, such as `GET /v2/topology`, and confirm it still succeeds. 4. Open Operate and Tasklist at their existing unprefixed URLs and confirm your data is present. If a tenant-scoped request returns `404`, the tenant is not configured in the cluster. This is not an authorization failure. See [HTTP status codes](/self-managed/concepts/physical-tenants/api-routing.md#http-status-codes). ### Not supported There is no migration path from Logical Tenants to Physical Tenants. The two are independent mechanisms, and a Logical Tenant cannot be promoted to or moved into a Physical Tenant. Logical Tenants remain available inside each Physical Tenant as a lightweight subdivision. Downgrading from 8.10 back to 8.9 is not supported. This is a general Camunda constraint rather than a Physical Tenants one, so there is no rollback path for the cluster and no return path for `camunda.physical-tenants.default.*` overrides. Take a backup before you upgrade, and rehearse the upgrade in a non-production cluster first. ## Elasticsearch/OpenSearch Exporter ### Default replica count changed In 8.10, the default value of `number-of-replicas` for Elasticsearch/OpenSearch Exporter indices changed from `0` to `1`. This applies to new indices created after upgrading. Existing indices are not affected. - **Single-node clusters:** Set `number-of-replicas: 0` explicitly in your exporter configuration to avoid yellow cluster health. On a single node, replicas cannot be assigned and remain unassigned, which causes the cluster to report yellow health and may trigger monitoring alerts. - **Multi-node clusters:** No action required. The new default improves fault tolerance. Note that each replica stores a full copy of the shard data, so enabling replicas increases disk usage for new indices. ## Optimize ### Default objectVariable inclusion changed This is a breaking change for Self-Managed. In 8.10, the default value of `zeebe.includeObjectVariableValue` changed from `true` to `false`. Optimize no longer flattens object variables into per-property fields or stores their raw value by default. See the [8.10 breaking change announcement](/reference/announcements-release-notes/8100/8100-announcements.md#optimize-self-managed-no-longer-flattens-object-variables-by-default) for the full rationale. | Setting | 8.9 | 8.10 | | ---------------------------------- | ------ | ------- | | `zeebe.includeObjectVariableValue` | `true` | `false` | **Action:** If your reports, filters, or Raw Data Reports rely on flattened object variable properties, set `zeebe.includeObjectVariableValue: true` (environment variable `CAMUNDA_OPTIMIZE_ZEEBE_INCLUDE_OBJECT_VARIABLE=true`) before upgrading: ```yaml zeebe: includeObjectVariableValue: true ``` Optimize logs a `WARN` on startup whenever object variable values are not being imported. The message includes the opt-in setting. [Object variables configuration](/self-managed/components/optimize/configuration/object-variables.md) In 8.10, Optimize accepts the same `camunda.security.*` configuration as the Orchestration Cluster, and its authentication behavior changes accordingly. See [Optimize authentication in Self-Managed](/self-managed/concepts/authentication/authentication-to-optimize.md) for the full configuration reference. The following sections cover the configuration key changes. ### Client bearer tokens are now classified for permission checks Optimize now classifies each bearer token as belonging to a user or a machine-to-machine (M2M) client, using `camunda.security.authentication.oidc.username-claim` and `client-id-claim`, and enforces your configured Optimize permission only on tokens it classifies as a user's. A token Optimize can't classify as an M2M client's is treated as belonging to a user, and checked against your configured Optimize permission. The Camunda Helm chart doesn't configure either claim for Optimize. `username-claim` keeps its software default of `sub`, and `client-id-claim` has no default at all, so Optimize can't classify any client token as M2M until you set it — every bearer token is checked against your configured Optimize permission. Set both claims through `optimize.extraConfiguration`, matching the values your identity provider uses (for example, `preferred_username` and `client_id` for Keycloak; see the [setup instructions for your identity provider](/self-managed/deployment/helm/configure/authentication-and-authorization/index.md) for other providers): ```yaml optimize: extraConfiguration: - file: security.yaml content: | camunda: security: authentication: oidc: username-claim: preferred_username client-id-claim: client_id ``` **Action:** Set `username-claim` and `client-id-claim` to match your identity provider before upgrading. Otherwise, M2M clients without an Optimize permission may see new permission errors after upgrading. [Optimize authentication in Self-Managed](/self-managed/concepts/authentication/authentication-to-optimize.md#configure-oidc-for-optimize) ### Component-specific security configuration keys are deprecated The following component-specific keys are deprecated in favor of `camunda.security.*`. They remain supported in 8.10. Camunda plans to remove them, and the component-specific configuration, in a future release. Optimize maps each recognized component-specific key to its replacement automatically and logs a deprecation warning naming the replacement, so existing deployments keep working unchanged through 8.10. | Component-specific key | Replacement | | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `CAMUNDA_OPTIMIZE_IDENTITY_ISSUER_URL` | `camunda.security.authentication.oidc.issuer-uri` | | `CAMUNDA_OPTIMIZE_IDENTITY_CLIENTID` | `camunda.security.authentication.oidc.client-id` | | `CAMUNDA_OPTIMIZE_IDENTITY_CLIENTSECRET` | `camunda.security.authentication.oidc.client-secret` | | `CAMUNDA_OPTIMIZE_IDENTITY_AUDIENCE` | `camunda.security.authentication.oidc.audiences` | | `CAMUNDA_OPTIMIZE_AUTH0_CLIENTID` | `camunda.security.authentication.oidc.client-id` | | `CAMUNDA_OPTIMIZE_AUTH0_CLIENTSECRET` | `camunda.security.authentication.oidc.client-secret` | | `CAMUNDA_OPTIMIZE_AUTH0_DOMAIN` | `camunda.security.authentication.oidc.issuer-uri` (issuer is derived from the Auth0 domain) | | `CAMUNDA_OPTIMIZE_AUTH0_ORGANIZATION` | `camunda.security.saas.*` | | `CAMUNDA_OPTIMIZE_CLIENT_AUDIENCE` | `camunda.security.authentication.oidc.audiences` | | `CAMUNDA_OPTIMIZE_CLIENT_CLUSTERID` | `camunda.security.saas.*` (also derives the redirect URI `?uuid` and the servlet context path) | | `CAMUNDA_OPTIMIZE_M2M_ACCOUNTS_AUTH0_AUDIENCE` | `camunda.security.authentication.oidc.audiences` | | `SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI` (or `api.jwtSetUri`) | `camunda.security.authentication.oidc.additional-jwk-set-uris` | | `CAMUNDA_OPTIMIZE_API_AUDIENCE` (or `api.audience`) | `camunda.security.authentication.oidc.audiences` | | `CAMUNDA_OPTIMIZE_SECURITY_RESPONSE_HEADERS_HSTS_MAX_AGE` | `camunda.security.http-headers.hsts.max-age-in-seconds` (a negative value disables the header) | | `camunda.identity.issuerBackendUrl` | `camunda.security.authentication.oidc.issuer-uri`, or one of the endpoint-specific overrides (`jwk-set-uri`, `authorization-uri`, `token-uri`, `user-info-uri`) if your Keycloak back channel is reachable at a different address than the public-facing issuer | | `camunda.identity.clientId` | `camunda.security.authentication.oidc.client-id` | | `camunda.identity.clientSecret` | `camunda.security.authentication.oidc.client-secret` | Note the following: - **Precedence:** If you set both a component-specific key and its `camunda.security.*` replacement, the `camunda.security.*` value wins. - **`audiences` replaces, not merges:** If you set `camunda.security.authentication.oidc.audiences` yourself, that list replaces the audiences from `CAMUNDA_OPTIMIZE_IDENTITY_AUDIENCE` and `CAMUNDA_OPTIMIZE_API_AUDIENCE`. Include every audience you still need in your explicit list. - **`CAMUNDA_OPTIMIZE_IDENTITY_BASE_URL` is not deprecated:** Keep it set. Optimize still uses it to look up users, for example when adding users to a collection. - **Either spelling works for the two API keys:** `api.audience` and `api.jwtSetUri` are read from Optimize's own configuration file as well as from their environment variables, and both spellings are deprecated on the same schedule. - **The API JWK set is no longer per path:** through 8.9 `api.jwtSetUri` validated tokens on the public API and ingestion endpoints only. Under `camunda.security.*` a JWK set applies to the whole API surface, so a token signed by that JWK set is accepted on any `/api/**` endpoint, subject to the audience and issuer checks. - **A separate identity provider for your API works only without `issuer-uri`:** `additional-jwk-set-uris` adds keys but doesn't relax issuer validation. See the [External API note](/self-managed/components/optimize/configuration/system-configuration.md#external-api). - **The `api.audience` default is not carried over:** Optimize forwards only an audience you set explicitly, never the built-in `optimize` default. Configure no audience at all and any audience is accepted; configure `CAMUNDA_OPTIMIZE_IDENTITY_AUDIENCE` but not the API audience and a token audienced `optimize` is rejected. Set every audience you need explicitly. **Action:** Migrate to the `camunda.security.*` keys as soon as you can. Camunda plans to remove these keys and the component-specific configuration in a future release. ### Keys with no replacement The following keys are no longer used in 8.10. Optimize still starts if you leave them in place, but they have no effect, so remove them: - `CAMUNDA_OPTIMIZE_SECURITY_AUTH_TOKEN_SECRET` - `CAMUNDA_OPTIMIZE_SECURITY_AUTH_COOKIE_MAX_SIZE` - `CAMUNDA_OPTIMIZE_SECURITY_AUTH_COOKIE_SAME_SITE_ENABLED` - `security.responseHeaders.X-XSS-Protection` ### Static API access token is no longer accepted In 8.10, Optimize accepts only OIDC bearer tokens on its API. Directly after the upgrade, Optimize rejects each request that carries the static token from `api.accessToken` (environment variable `OPTIMIZE_API_ACCESS_TOKEN`) with a `401` response. This applies to the [Optimize API](/apis-tools/optimize-api/overview.md) and to the [external variable ingestion](/apis-tools/optimize-api/external-variable-ingestion.md) endpoint. The Camunda Helm chart and Camunda 8 SaaS do not set `api.accessToken`. They configure OIDC for the Optimize API. You are affected only if you set the property or the `OPTIMIZE_API_ACCESS_TOKEN` environment variable yourself, for example as an Optimize property override or an extra environment variable in your Helm values, or in a manual or Docker Compose installation. Optimize logs an obsolete-property warning for the environment variable, but not for the YAML key. Check your YAML configuration for `api.accessToken` too. **Action:** Change the API clients that send the static token to [OIDC bearer tokens](/apis-tools/optimize-api/optimize-api-authentication.md) before you upgrade. Then remove `api.accessToken` and `OPTIMIZE_API_ACCESS_TOKEN` from your configuration. If you need more time, set `optimize.security.csl.enabled=false`. This opts into the 8.9 component-specific configuration fallback, and the static token works again. Camunda plans to remove this fallback and the component-specific configuration keys in a future release. ## Zeebe ### Job leasing during the rolling upgrade Camunda 8.10 introduces [job leasing](/components/concepts/job-workers.md#job-leasing). During a rolling upgrade from 8.9, jobs on partitions that are still running 8.9 return a `null` `jobLeaseToken`, even when activated with `withLease` set to `true`, because 8.9 brokers do not support job leasing. If your worker requires the lease to process a job safely, treat a `null` `jobLeaseToken` as not yet supported on that partition: fail the job with a retry backoff, and set retries back to the job's current value so the fail doesn't burn through them while the upgrade is in progress. The lease becomes available after that job's partition leader is upgraded to 8.10, and the retry converges. When using the REST API, a gateway instance still running 8.9 rejects the unknown `withLease` field with a `400` error instead of returning `null`. This handling is no longer required after every gateway in the cluster has been upgraded to 8.10. ```java final JobHandler paymentJobHandler = (jobClient, job) -> { final String jobLeaseToken = job.getJobLeaseToken(); if (jobLeaseToken == null) { // This partition hasn't upgraded to 8.10 yet. Fail with retries // preserved so the job converges once the upgrade completes, // instead of being burned into an incident. jobClient .newFailCommand(job) .retries(job.getRetries()) .retryBackoff(Duration.ofSeconds(30)) .errorMessage("Lease required but not returned; retrying until the partition upgrades to 8.10") .send(); return; } // process the job jobClient.newCompleteCommand(job).send(); }; client .newWorker() .jobType("process-payment") .handler(paymentJobHandler) .withLease(true) .open(); ``` ### Deleting a process definition with running instances defers history deletion The [delete resource](/apis-tools/orchestration-cluster-api-rest/specifications/delete-resource.api.mdx) endpoint now accepts process definition deletion when the definition still has running instances. Instead of rejecting the request or waiting for physical removal, the definition [drains](/components/concepts/resource-deletion.md#draining): new instances are blocked immediately, running instances continue to completion, and the definition is removed automatically afterwards. As a result, when `deleteHistory` is `true`, the `batchOperation` field in the delete response is `null` for such a definition. Its history is removed as part of the draining lifecycle rather than through an immediately-returned batch operation. **Action:** If any caller reads `batchOperation` from the delete response to track history deletion, handle a `null` value. Observe the definition's `DRAINING` state through the [process definition API](/apis-tools/orchestration-cluster-api-rest/specifications/get-process-definition.api.mdx) or the `zeebe_process_definitions_draining_count` [metric](/self-managed/operational-guides/monitoring/metrics.md) instead. --- ## Upgrade Camunda components Upgrade individual Camunda 8 components when moving from version 8.9 to 8.10. ## About Component-level steps may be required depending on which Camunda components you use and how your environment is configured. :::caution Component-level changes are part of the overall [upgrade process](../index.md). Follow the upgrade path for your deployment method and apply component-level steps when required by your configuration. ::: ## When component-level changes apply Additional steps may be required if your deployment: - Uses non-default or customized component configuration - Manages components outside the standard Helm or Manual upgrade flow - Is affected by component-specific changes introduced in Camunda 8.10. :::info For release context, see: - [What’s new in Camunda 8.10](/reference/announcements-release-notes/8100/whats-new-in-810.md) - [8.10 release notes](/reference/announcements-release-notes/8100/8100-release-notes.md). ::: ## Upgrade components from 8.9 to 8.10 This section links to component-level guidance for the 8.9 to 8.10 upgrade. [Upgrade Camunda components from 8.9 to 8.10](890-to-8100.md) --- ## Upgrade Camunda 8.9 to 8.10 using Helm Upgrade a Helm-managed Camunda 8 Self-Managed deployment from version 8.9 to 8.10. :::info Upgrade procedure All Camunda 8 upgrades must follow the required upgrade procedure. First, upgrade to the latest patch of your current minor. Then, upgrade one minor version at a time. Don't skip minor versions. If you skip a minor version, the schema compatibility check fails and blocks startup. We strongly recommend that you upgrade to the latest patch of each minor for fix coverage. However, the check itself compares minor versions. See [version compatibility checks](/self-managed/components/orchestration-cluster/core-settings/concepts/version-compatibility.md#required-upgrade-procedure). ::: ## Prerequisites Before you upgrade, make sure you meet these prerequisites: | Prerequisite | Description | | :-------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Confirm `8.9.x` baseline | Check that your deployment runs an `8.9.x` version before you upgrade to 8.10. If you upgrade from a version earlier than 8.9, upgrade one minor at a time. See [upgrading from an earlier version](/self-managed/upgrade/index.md#upgrading-from-an-earlier-version). We strongly recommend that you upgrade to the latest available `8.9.x` patch first, for fix coverage. | | Confirm Helm CLI version | Camunda 8.10 (chart 15.x) supports Helm CLI v3 (3.10 or later) and v4. See [Helm CLI support and chart compatibility](/self-managed/deployment/helm/operational-tasks/helm-v4.md#camunda-helm-chart-compatibility) and [Move from the Helm v3 CLI to v4](/self-managed/deployment/helm/operational-tasks/moving-helm-v3-to-v4.md). | | Confirm upgrade eligibility | Review [Prepare for upgrade](/self-managed/upgrade/prepare-for-upgrade.md) to check upgrade eligibility and to check infrastructure compatibility for 8.10. | | Confirm Helm chart support | Review the Camunda Helm chart [version matrix](https://helm.camunda.io/camunda-platform/version-matrix/) to find the chart version that deploys 8.10. The matrix also shows the Helm CLI versions that this chart version was released and tested with. Check your Kubernetes version against the [supported environments](/reference/supported-environments.md). | | Migrate bundled Bitnami subcharts | If your 8.9 release uses `identityKeycloak`, `identityPostgresql`, `webModelerPostgresql`, or `elasticsearch`, migrate to externally managed services. Do this while you still run 8.9, before you upgrade. Chart 15.x no longer deploys these subcharts. See [Migrate from Bitnami charts](/self-managed/deployment/helm/operational-tasks/migration-from-bitnami/index.md). | | Backups | Create backups of your Camunda data. Check the backups. [Backup and restore](/self-managed/operational-guides/backup-restore/backup-and-restore.md) covers Zeebe, Operate, Tasklist, Admin, and Optimize. To create a backup of the Web Modeler database, follow [Hub database backup](#back-up-the-hub-database). Create backups of the Management Identity and Keycloak databases with your database vendor's tooling. The Backup and restore guide doesn't cover these databases. | | Check your `values.yaml` | Review your existing `values.yaml` for [keys that chart 15.x rejects](#remove-keys-rejected-by-chart-15x), [deprecated Helm keys](#deprecated-application-configuration-helm-keys), and [changed chart defaults](#review-changed-chart-defaults). | | Plan the Camunda Hub migration | If your release runs Web Modeler, plan a maintenance window. The Hub database migration isn't backward compatible. The migration requires you to stop all Web Modeler workloads. The upgrade also removes the Console Deployment, because Hub replaces Console. See [Migrate Web Modeler and Console to Hub](#migrate-web-modeler-and-console-to-camunda-hub). | | Test upgrade | Test the upgrade in a non-production environment. Use a copy of your production configuration. | ## Upgrade flow After completing the prerequisites, follow this flow for each supported minor-version upgrade: 1. Run the [Camunda Helm Toolkit](/self-managed/deployment/helm/operational-tasks/camunda-helm-toolkit.md) to migrate and validate override files. 1. Review the report and complete the version-specific manual actions. 1. Render and test the complete, layered configuration in non-production. 1. Back up data and run the Helm upgrade. 1. Validate the deployed platform. ## Run the Camunda Helm Toolkit Start values-file preparation by running the toolkit against your Helm override files. Review and address its findings before completing the manual upgrade steps below. 1. Keep a copy of your original 8.9 overrides. Run the toolkit with source `8.9` and target `8.10` to create `values-8.10.yaml`. For layered overrides, migrate each file separately and retain the Helm `-f` order. 1. Inspect the migrated YAML and [act on the report](/self-managed/deployment/helm/operational-tasks/camunda-helm-toolkit.md#what-to-do-with-the-report). Resolve errors before using the output and review warnings for manual actions. 1. Review the [manual configuration changes](#update-your-values-file-to-810), including [changed chart defaults](#review-changed-chart-defaults), and complete all actions that apply to your deployment, including actions not identified by the report. A clean report is a useful sanity check, not proof of production readiness. Complete the applicable manual actions even when the report has no findings. The toolkit prepares override files; it doesn't upgrade your deployment, migrate stored data, or validate a live cluster. Follow the complete upgrade procedure, including the [Hub database migration](#migrate-web-modeler-and-console-to-camunda-hub) if you run Web Modeler. ## Update your values file to 8.10 Apply the manual configuration changes that your deployment needs. Use the report to locate relevant settings, and review these sections for changes the toolkit doesn't automate. ### Consolidate Console and Web Modeler into Camunda Hub Camunda 8.10 consolidates Console and Web Modeler into Camunda Hub. Console is no longer a standalone deployment. The `camunda/hub` image serves both feature sets. ### Enable Hub Replace the legacy enablement keys with `camundaHub.enabled`. Move overrides directly under `camundaHub`: ```yaml # Before (8.9) console: enabled: true webModeler: enabled: true restapi: resources: requests: memory: 1Gi # After (8.10) camundaHub: enabled: true restapi: resources: requests: memory: 1Gi ``` The legacy `webModeler.enabled` key remains a compatibility shim in 8.10. It deploys Hub and emits a deprecation warning. Existing `webModeler.*` settings remain fallback values. However, migrate them to the equivalent flattened `camundaHub.*` paths. Don't nest settings under `camundaHub.webModeler` or `camundaHub.console`. If you nest settings under either key, the chart fails the render. The setting `camundaHub.enabled: true` also enables the Console feature. If your 8.9 release ran only Web Modeler, Hub shows Console's cluster pages after the upgrade. #### Merge `webModeler` and `camundaHub` values The chart deep-merges `camundaHub.*` over `webModeler.*`, and a value under `camundaHub` wins: - Maps merge key by key, so a map under `camundaHub` can't remove a key that `webModeler` sets. - Lists don't merge. A list under `camundaHub`, such as `camundaHub.restapi.env`, `extraVolumes`, `extraConfiguration`, `tolerations`, or `initContainers`, replaces the whole list under `webModeler`. When you move a list, move all of its entries. - If you set a scalar under `camundaHub` where `webModeler` has a map, or the reverse, the chart fails the render. #### Deploy Camunda Hub for a Console-only release `console.enabled: true` doesn't deploy anything on its own in 8.10. Hub runs only when `camundaHub.enabled` or `webModeler.enabled` is `true`. `console.enabled` has an effect only when Hub runs through the legacy `webModeler.enabled`. Then it enables the Console feature inside Hub. With `camundaHub.enabled: true`, the Console feature is always enabled, and `console.enabled: false` doesn't disable it. If your 8.9 release ran Console without Web Modeler, do these steps. Set `camundaHub.enabled: true`. Keep Management Identity configured as it was for Console. Add the settings Hub needs: | Setting | Helm key | | :-------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | External PostgreSQL database | `camundaHub.restapi.externalDatabase.*`. The chart doesn't check this setting at render time. | | Sender address for Hub emails | `camunda.hub.mail.from-address` in `camundaHub.restapi.extraConfiguration`. The render fails without this setting. | | SMTP server for Hub emails | `spring.mail.*` in `camundaHub.restapi.extraConfiguration`. Hub sends no emails without this setting. | | Public URL for the login callback | `global.identity.auth.camundaHub.redirectUrl`. It replaces `global.identity.auth.console.redirectUrl`. | | Path Hub is served on | `camundaHub.contextPath`. Set this key when the chart renders your Ingress or Gateway API routes. Without this key, the shared Ingress has no Hub path, and the Kubernetes API server rejects the Hub HTTPRoute. | If you use an external OIDC provider, Hub authenticates with the client ID `web-modeler` and the audiences `web-modeler-api` and `web-modeler-public-api`. Hub uses these values unless you set `global.identity.auth.camundaHub.clientId`, `clientApiAudience`, and `publicApiAudience`. Register Hub's applications in your provider. The Console application doesn't cover them. See [connect to an OIDC provider](/self-managed/components/management-identity/configuration/connect-to-an-oidc-provider.md#configuration). A Console-only release has no 8.9 Hub database, so the release doesn't need the database migration phases. Run the one-step upgrade in [Run the Helm upgrade](#run-the-helm-upgrade). #### Move or remove `console` keys The chart reads only `console.enabled`. It ignores every other `console.*` key. It emits a single deprecation warning when you set any of them. Move the settings that you still need to Hub. Then remove the `console` block: | 8.9 key | 8.10 key or action | | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `console.resources` | `camundaHub.restapi.resources`. See [Review Hub REST API resources](#review-hub-rest-api-resources). | | `console.env`, `console.envFrom` | `camundaHub.restapi.env`, `camundaHub.restapi.envFrom`. Don't move `CAMUNDA_CONSOLE_*` variables or `camunda.console` properties. Hub fails on startup if you set them. See [Console configuration](/self-managed/upgrade/components/890-to-8100.md#console-configuration). | | `console.replicas`, `console.nodeSelector`, `console.tolerations`, `console.affinity`, `console.podLabels`, `console.podAnnotations`, `console.extraVolumes`, `console.extraVolumeMounts`, `console.initContainers`, `console.sidecars`, `console.podSecurityContext`, `console.containerSecurityContext`, `console.dnsPolicy`, `console.dnsConfig` | Use the same keys under `camundaHub.restapi`. Lists replace the `webModeler.restapi` lists. See [Merge `webModeler` and `camundaHub` values](#merge-webmodeler-and-camundahub-values). | | `console.serviceAccount.*` | `camundaHub.serviceAccount.*`. The Hub REST API and WebSockets pods share this service account. | | `console.contextPath` | No equivalent. Hub serves Console's pages on `camundaHub.contextPath`. | | `console.configuration`, `console.extraConfiguration`, `console.overrideConfiguration` | Cluster configuration moves to `camundaHub.restapi.clusters`. If you set this key, your value replaces the cluster list the chart generates for the release. As a result, list the release's own clusters too. See [cluster configuration](/self-managed/upgrade/components/890-to-8100.md#cluster-configuration). | | `console.image.registry`, `console.image.pullSecrets` | `camundaHub.image.registry`, `camundaHub.image.pullSecrets`. | | `console.image.repository`, `console.image.tag`, `console.image.digest` | No equivalent. Remove these keys. To pin Hub images, see [Update image references](#update-image-references). | | `console.tls.*` | If Console needed these keys to trust a private CA, add that CA to the PEM bundle in `global.tls.caBundle.secret`. Otherwise, remove them. | | `console.keycloak.realm` | No equivalent. Remove this key. | `global.identity.auth.console.*` has no effect in 8.10, and the chart emits a deprecation warning when you set it. For a Console-only release, set `global.identity.auth.camundaHub.redirectUrl` to Hub's public URL. This key replaces `global.identity.auth.console.redirectUrl`. The other keys have no direct replacement, so remove them. Management Identity doesn't remove the existing `console` client from Keycloak. See [Management Identity roles and permissions](/self-managed/upgrade/components/890-to-8100.md#management-identity-roles-and-permissions). #### Update references to Console The chart no longer renders the Console Deployment, Service, ServiceMonitor, and HTTPRoute, or the `/console` path on the shared Ingress. Helm removes them during the upgrade. Update bookmarks, external Ingress or Gateway routes, and monitoring that target Console to use Hub instead. If the chart renders your Ingress or Gateway API routes, set `camundaHub.contextPath`. Without it, the shared Ingress has no Hub path, and the Kubernetes API server rejects the Hub HTTPRoute. #### Update image references If you mirror or pin images, update these repositories: | 8.9 image | 8.10 image | | :------------------------------------- | :--------------------------------------------------------------------- | | `camunda/web-modeler-restapi:8.9.x` | `camunda/hub:8.10.x` | | `camunda/web-modeler-websockets:8.9.x` | `camunda/hub-websockets:8.10.x` | | `camunda/console:8.9.x` | The chart no longer deploys this image. Console runs in `camunda/hub`. | Configure the Hub repositories under `camundaHub.restapi.image.repository` and `camundaHub.websockets.image.repository`. Image pins under `webModeler` stay in effect through the fallback. For example, `webModeler.image.tag: 8.9.10` renders `camunda/hub:8.9.10`, which doesn't exist. A pinned `webModeler.restapi.image.repository` or `webModeler.websockets.image.repository` keeps the 8.9 image. Remove the 8.9 image pins from `webModeler`. Pin both Hub images with `camundaHub.image.tag`. #### Review Hub REST API resources Console now runs in the Hub REST API pod. However, the Hub REST API keeps the resource defaults of the 8.9 Web Modeler REST API: | Pod | Requests (CPU / memory) | Limits (CPU / memory) | | :----------------------- | :---------------------- | :-------------------- | | 8.9 Console | `1000m` / `1Gi` | `2000m` / `2Gi` | | 8.9 Web Modeler REST API | `900m` / `1280Mi` | `1800m` / `2560Mi` | | 8.10 Hub REST API | `900m` / `1280Mi` | `1800m` / `2560Mi` | If you ran Console, set `camundaHub.restapi.resources` explicitly. In that case, adjust the requests and limits if the pod is throttled or exhausts its memory. ### Remove keys rejected by chart 15.x Chart 15.x rejects removed values before it changes any workloads. Update or remove these values before you upgrade: | Removed key | Replacement or action | | :------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `global.ingress.host` | Use `global.host`. | | `global.identity.keycloak.auth.existingSecret` | Use `global.identity.keycloak.auth.secret.existingSecret`. Also set `global.identity.keycloak.auth.secret.existingSecretKey`. In chart 14.x, the default value of the key was `admin-password`. Chart 15.x has no default. Without the key, Management Identity receives an empty Keycloak admin password. | | `global.identity.keycloak.auth.existingSecretKey` | Use `global.identity.keycloak.auth.secret.existingSecretKey`. | | `global.elasticsearch.*` | Use `optimize.database.elasticsearch.*` for Optimize. Use `orchestration.data.secondaryStorage.elasticsearch.*` for the Orchestration Cluster. See the migration mapping below. | | `global.opensearch.*` | Use `optimize.database.opensearch.*` for Optimize. Use `orchestration.data.secondaryStorage.opensearch.*` for the Orchestration Cluster. See the migration mapping below. | | `orchestration.profiles.identity` | Use `orchestration.profiles.admin`. | | `webModeler.restapi.externalDatabase.user` | Use `camundaHub.restapi.externalDatabase.username`. | | `identityKeycloak`, `identityPostgresql` | Migrate Keycloak and the Management Identity database to externally managed services while you're still on 8.9. Chart 15.x doesn't render the bundled workloads, so Helm removes their StatefulSets during the upgrade. See [migrate off the bundled PostgreSQL databases](#migrate-off-the-bundled-postgresql-databases). | | `webModelerPostgresql` | Migrate the Camunda Hub database to external PostgreSQL while you're still on 8.9. Also configure `camundaHub.restapi.externalDatabase`. See [migrate off the bundled PostgreSQL databases](#migrate-off-the-bundled-postgresql-databases). | | `elasticsearch` | Migrate to external Elasticsearch or OpenSearch while you're still on 8.9. Also configure the secondary storage values of each component. | The chart rejects these keys whenever they're in your values file, even when their block only sets `enabled: false`, such as `elasticsearch: {enabled: false}`. Two checks apply only to a deployed component. The check for `orchestration.profiles.identity` applies when the chart deploys the Orchestration Cluster. The check for `webModeler.restapi.externalDatabase.user` applies when the chart deploys Hub. Remove the whole block from your values file. #### Migrate `global.elasticsearch` and `global.opensearch` Chart 14.x deprecated the `global.elasticsearch.*` and `global.opensearch.*` trees, and chart 15.x removes them. If you set any key under them, the render fails with `[camunda][error] The Helm values file key "global.elasticsearch" has been removed.` A message that names the replacement keys follows. The table shows the Elasticsearch keys. The OpenSearch keys mirror the Elasticsearch keys, except `external` and `disableExporter`, which exist only for Elasticsearch. Map your values as follows: | Removed key | Replacement | | :----------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `global.elasticsearch.enabled` | Use `optimize.database.elasticsearch.enabled` (Optimize and the legacy Zeebe exporter), `orchestration.data.secondaryStorage.type: elasticsearch`, or both. If you enable Optimize, set `optimize.database.elasticsearch.enabled: true`. The secondary storage type alone doesn't configure the Optimize database or the exporter that writes the indices that Optimize reads. The chart renders the legacy Zeebe exporter when you enable Optimize with `optimize.database.elasticsearch.enabled: true` and the cluster doesn't span more than one region or zone. The chart also renders the exporter when you set `orchestration.exporters.zeebe.enabled: true` with Elasticsearch secondary storage. | | `global.elasticsearch.external` | `optimize.database.elasticsearch.external`. | | `global.elasticsearch.url.*` | Use `optimize.database.elasticsearch.url.{protocol,host,port}`, the full-URL string `orchestration.data.secondaryStorage.elasticsearch.url`, or both. When `orchestration.data.secondaryStorage.elasticsearch.url` is empty, the Orchestration Cluster uses the Optimize URL. Set the URL explicitly. The Optimize default, `http://-elasticsearch:9200`, is the address of the bundled subchart that chart 15.x no longer deploys. | | `global.elasticsearch.auth.*` | Set both `optimize.database.elasticsearch.auth.*` and `orchestration.data.secondaryStorage.elasticsearch.auth.*`. The secondary storage of the Orchestration Cluster doesn't reuse the Optimize credentials, even when the Orchestration Cluster reuses the Optimize URL. When Optimize owns the legacy Zeebe exporter, the exporter authenticates with `optimize.database.elasticsearch.auth.*`. The exporter does this only when `optimize.database.elasticsearch.external` is `true`. | | `global.elasticsearch.tls.secret.*`, `global.elasticsearch.tls.existingSecret` | Use `global.tls.caBundle.secret` with a PEM CA bundle. The per-component `optimize.database.elasticsearch.tls.secret.*` and `orchestration.data.secondaryStorage.elasticsearch.tls.secret.*` keys still work, but chart 15.x deprecates them. See [move per-component truststores to `global.tls.caBundle`](#move-per-component-truststores-to-globaltlscabundle). | | `global.elasticsearch.tls.jks.secret.*` | Chart 15.x removes this key together with the truststore password injection. Use `global.tls.caBundle.secret` (PEM). Alternatively, pass `-Djavax.net.ssl.trustStorePassword=...` in the `javaOpts` of the component. To keep the password in a Secret, load the Secret with the `envFrom` of the component. Then reference the key of the Secret as `$(KEY)` in `javaOpts`. The chart renders `JAVA_TOOL_OPTIONS` before the component's `env` entries, so a `$(VAR)` reference to a variable defined in `env` doesn't expand. A reference to a variable that `envFrom` loads does expand. | | `global.elasticsearch.prefix` | Use `optimize.database.elasticsearch.prefix`. Chart 15.x also deprecates this key. See [Optimize](#optimize). The legacy Zeebe exporter writes its indices with `orchestration.exporters.zeebe.index.prefix`. If this key is unset, the exporter uses `optimize.database.elasticsearch.prefix`. If that key is also unset, the exporter uses `zeebe-record`. | | `global.elasticsearch.clusterName` | The chart no longer renders this key. The application default is identical. If you need this setting, set `camunda.data.secondary-storage.elasticsearch.cluster-name` in `orchestration.extraConfiguration`. | | `global.elasticsearch.disableExporter` | You don't need a replacement. The key had no effect. | | `global.elasticsearch.tls.enabled` | You don't need a replacement. The key had no effect. The connection uses TLS when the URL scheme is `https`. `tls.secret.*` and `global.tls.caBundle` only supply the CA that the component trusts. | | `global.opensearch.aws.enabled` | The key applies only to OpenSearch and has no Elasticsearch equivalent. Use `optimize.database.opensearch.aws.enabled`, `orchestration.data.secondaryStorage.opensearch.aws.enabled`, or both. | #### Migrate off the bundled PostgreSQL databases Chart 15.x removes the bundled Bitnami PostgreSQL subcharts. The chart fails the render if your values file still contains `identityPostgresql` or `webModelerPostgresql`. Do this migration on your 8.9 release, before you upgrade to 8.10. For each database that Management Identity or Camunda Hub uses in a bundled PostgreSQL: 1. Migrate the data to a database that the chart doesn't manage. This can be your own deployment of Bitnami PostgreSQL, a managed cloud database, or a different supported PostgreSQL. See [migrate from Bitnami charts](/self-managed/deployment/helm/operational-tasks/migration-from-bitnami/index.md). 2. Point the 8.9 release at the external database. Verify that Management Identity or Web Modeler works with it. 3. Before you remove the bundled database, protect its volume. Do these steps: 1. Examine the reclaim policy of its PersistentVolume and the `persistentVolumeClaimRetentionPolicy` of its StatefulSet. 2. Set `persistentVolumeReclaimPolicy` of the PersistentVolume to `Retain`. 3. Make a backup and verify that you can restore it. 4. Remove the bundled database. :::warning If the volume is deleted with the StatefulSet or its PVC, you lose its data. For Management Identity, this is users, groups, roles, and permissions. For Web Modeler, this is projects, files, and settings. ::: ### Management Identity service account token is no longer mounted by default In chart 15.x (8.10), `identity.serviceAccount.automountServiceAccountToken` defaults to `false`, like in the other components. Management Identity doesn't call the Kubernetes API, so this change doesn't affect the application itself. The change affects you if anything in the Management Identity pod reads the default service account token at `/var/run/secrets/kubernetes.io/serviceaccount/token`. For example: - A sidecar or init container that you add through `identity.sidecars` or `identity.initContainers` and that calls the Kubernetes API. - Vault Agent Injector with the Kubernetes auth method and the default token. The change doesn't affect integrations that inject their own projected token, such as AWS IRSA, EKS Pod Identity, and Azure Workload Identity. `helm upgrade` updates the ServiceAccount. However, running pods keep their token until Kubernetes creates them again. The change takes effect on the next pod restart or rollout. To keep the previous behavior, set: ```yaml identity: serviceAccount: automountServiceAccountToken: true ``` ### Camunda Hub `Recreate` strategy requires `existingClaim` In chart 15.x (8.10), `deploymentStrategy: Recreate` requires `persistence.enabled: true` and `persistence.existingClaim`. If one of these settings is missing, the render fails with a `[camunda][error]` message. The requirement applies to `webModeler.persistence` and `camundaHub.persistence`. The default `RollingUpdate` strategy is not affected. The 8.9 chart renders `Recreate` with `persistence.enabled: true` and no `existingClaim`. It emits a `[camunda][warning]` instead of an error. Before you upgrade, do one of the following: - Set `persistence.existingClaim` with `persistence.enabled: true`. - Remove `deploymentStrategy: Recreate`. ### Deprecated application configuration Helm keys Chart 15.x (8.10) deprecates Helm keys that acted as thin proxies for a single application setting, in favor of the component's `extraConfiguration`. These keys continue to work in 8.10, except the keys that the tables say have no effect. If you set one of these keys to a non-default value, the chart prints a `[camunda][warning] DEPRECATION` message in the `helm install` or `helm upgrade` output. If `global.createReleaseInfo` is `false`, the chart doesn't print the message in that output. The message also appears in the [`-warnings` ConfigMap](/self-managed/deployment/helm/operational-tasks/helm-values-schema-validation.md#find-unknown-keys). The message says that chart v17 (Camunda 8.12) removes the key. If you migrate the keys while you're on 8.10, your values file stays ready for that removal. :::note Deprecations are still being added Camunda 8.10 is under active development. Camunda may deprecate more keys during the 8.10 release cycle. The tables below cover the keys that Camunda deprecated at the time of writing. Treat the `[camunda][warning] DEPRECATION` messages that your own `helm upgrade` emits as the authoritative, up-to-date list for your chart version. Each message names the deprecated key and where to configure it instead. ::: With `extraConfiguration`, each component can own the native configuration file of its application. The format is the same as in 8.9. It's an ordered list of file entries. For Spring Boot components, the chart mounts the files and imports them through `spring.config.import`. To learn how `extraConfiguration` works in each component, see [Application configuration and `extraConfiguration`](/self-managed/deployment/helm/configure/application-configs.md#how-extraconfiguration-works-per-component). The same page explains how to exclude non-Spring files with `springImport: false`. To find the property that the chart generates for a key, render the configuration of the component. For example, render it with `helm template camunda/camunda-platform --version -f values-8.10.yaml --show-only templates/orchestration/configmap.yaml`. Then find the equivalent property in the rendered configuration. For the deprecated keys below, the value in your `extraConfiguration` overrides the value that the chart renders. This happens because Spring Boot applies imported files after the chart's `application.yaml`. You don't need to remove the deprecated key at the same time. When you migrate, note these points: - For `orchestration.exporters.camunda.enabled`, the document store, and the keys of Optimize and Camunda Hub, the chart also reads your `extraConfiguration` itself. For most of these keys, the chart recognizes only nested YAML keys in the first document of a file that Spring imports. The chart doesn't recognize flat dotted keys such as `camunda.data.snapshot-period`. Therefore, write migrated properties as nested YAML. - The chart doesn't process `extraConfiguration` content as a template. Write final values instead of expressions such as `{{ .Release.Name }}`. - If you set `orchestration.configuration`, this setting replaces the generated `application.yaml`, including its `spring.config.import` list. As a result, the chart doesn't import your `extraConfiguration` files, unless your file imports them. - Some chart defaults differ from the application defaults. For example, `orchestration.cpuThreadCount` and `orchestration.ioThreadCount` have the chart default `3` and the application default `2`. The key `orchestration.history.rolloverBatchSize` has the chart default `100` and the application default `500`. The chart still renders its defaults in 8.10. To keep a chart default after the chart removes the key, set that value explicitly when you migrate. Move each deprecated key to the `extraConfiguration` of the listed component, except where the notes say otherwise. #### Orchestration Move these keys to `orchestration.extraConfiguration`. If the notes say that a key has no effect, remove the key instead. From `orchestration.history.retention.enabled`, the chart also renders `camunda.operate.archiver.ilmEnabled` and `camunda.tasklist.archiver.ilmEnabled`. The 8.10 application doesn't read these properties, so you don't need to migrate them. | Key | Notes | | :------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `orchestration.logLevel` | Use `logging.level.io.camunda.zeebe`. To set the level for all Camunda loggers, use `logging.level.io.camunda`. Alternatively, use a `log4j2.xml` entry. The chart rendered this key as `zeebe.log.level`. The application doesn't read `zeebe.log.level`, so the key had no effect in 8.9 either. | | `orchestration.log4j2` | Move the key to a file-content `extraConfiguration` entry (`log4j2.xml`) with `springImport: false`. See [excluding files from `spring.config.import`](/self-managed/deployment/helm/configure/application-configs.md#excluding-files-from-springconfigimport). | | `orchestration.cpuThreadCount` | `camunda.system.cpu-thread-count` | | `orchestration.ioThreadCount` | `camunda.system.io-thread-count` | | `orchestration.data.snapshotPeriod` | `camunda.data.snapshot-period`. | | `orchestration.data.disk.freeSpace.processing` | `camunda.data.primary-storage.disk.free-space.processing` | | `orchestration.data.disk.freeSpace.replication` | `camunda.data.primary-storage.disk.free-space.replication` | | `orchestration.index.prefix` | `camunda.data.secondary-storage..index-prefix`. This prefix doesn't apply to the indices of the legacy Zeebe exporter. These indices use `orchestration.exporters.zeebe.index.prefix`. | | `orchestration.index.replicas` | `camunda.data.secondary-storage..number-of-replicas`. When `orchestration.exporters.zeebe.replicas` is unset, the `index.numberOfReplicas` of the legacy Zeebe exporter also uses `orchestration.index.replicas`. Set `orchestration.exporters.zeebe.replicas` to keep the value of `index.numberOfReplicas`. | | `orchestration.history.retention.enabled` | `camunda.data.secondary-storage.retention.enabled`. The chart renders `camunda.data.secondary-storage..history.policy-name: camunda-history-retention-policy` only while `orchestration.history.retention.enabled` is `true`. When you migrate the key, also set `policy-name`. Otherwise, the policy name changes to the application default, `camunda-retention-policy`. | | `orchestration.history.retention.minimumAge` | `camunda.data.secondary-storage.retention.minimum-age` | | `orchestration.history.retention.policyName` | `camunda.data.secondary-storage..history.policy-name` | | `orchestration.history.delayBetweenRuns` | `camunda.data.secondary-storage..history.delay-between-runs` | | `orchestration.history.maxDelayBetweenRuns` | `camunda.data.secondary-storage..history.max-delay-between-runs` | | `orchestration.history.rolloverBatchSize` | `camunda.data.secondary-storage..history.rollover-batch-size` | | `orchestration.history.rolloverInterval` | `camunda.data.secondary-storage..history.rollover-interval` | | `orchestration.history.waitPeriodBeforeArchiving` | `camunda.data.secondary-storage..history.wait-period-before-archiving` | | `orchestration.history.elsRolloverDateFormat` | `camunda.data.secondary-storage..history.els-rollover-date-format` | | `orchestration.retention.enabled` | `zeebe.broker.exporters..args.retention.enabled` (Zeebe record index). This row and the next two rows apply only when the chart renders the legacy Zeebe exporter. | | `orchestration.retention.minimumAge` | `zeebe.broker.exporters..args.retention.minimumAge` (Zeebe record index) | | `orchestration.retention.policyName` | `zeebe.broker.exporters..args.retention.policyName` (Zeebe record index) | | `orchestration.security.authentication.unprotectedApi` | `camunda.security.authentication.unprotected-api`. The value can't be `true` together with multi-tenancy checks. See [Troubleshooting](#multi-tenancy-with-an-unprotected-api). | | `orchestration.security.authentication.oidc.usernameClaim` | `camunda.security.authentication.oidc.username-claim`. The chart also uses the Helm key for the Hub REST API (`camunda.hub.oauth2.token.username-claim`). Keep the Helm key. Alternatively, also set that property in `camundaHub.restapi.extraConfiguration`. | | `orchestration.security.authentication.oidc.clientIdClaim` | `camunda.security.authentication.oidc.client-id-claim` | | `orchestration.security.authentication.oidc.groupsClaim` | `camunda.security.authentication.oidc.groups-claim` | | `orchestration.security.authentication.oidc.preferUsernameClaim` | `camunda.security.authentication.oidc.prefer-username-claim` | | `orchestration.security.authentication.oidc.scope` | `camunda.security.authentication.oidc.scope` | | `orchestration.security.authentication.oidc.backwardsCompatibleAudiences` | `camunda.security.authentication.oidc.audiences`. This property replaces the audience list. See [Troubleshooting](#backwards-compatible-audiences-replace-the-audience-list). | | `orchestration.security.authentication.authenticationRefreshInterval` | `camunda.security.authentication.authentication-refresh-interval`. The chart renders the Helm key under `camunda.security.authentication.oidc`. The application doesn't read the key there, so the key has no effect. Remove the key. If you need a non-default value, set the interval under `camunda.security.authentication`. | | `orchestration.security.authorizations.enabled` | `camunda.security.authorizations.enabled`. The chart also writes this value into the Hub cluster configuration. Therefore, keep the Helm key in 8.10. | | `orchestration.security.initialization.mappingRules` | `camunda.security.initialization.mapping-rules`. | | `orchestration.security.initialization.defaultRoles..mappingRules` | `camunda.security.initialization.default-roles..mapping-rules`. Applies to `admin`, `connectors`, and custom roles. This key assigns mapping rule IDs to a role. It does not define the mapping rules. Move the role assignment together with the rule definitions, or users that log in through these mapping rules lose the role. | | `orchestration.security.initialization.authorizations` | `camunda.security.initialization.authorizations`. The permission list field is `permissions`, not `permissionTypes`. See [Troubleshooting](#initialization-authorizations-report-no-permissiontypes-provided). | | `orchestration.multitenancy.checks.enabled` | `camunda.security.multi-tenancy.checks-enabled` | | `orchestration.multitenancy.api.enabled` | `camunda.security.multi-tenancy.api-enabled`. When `global.multitenancy.enabled` is `true`, the chart ignores a `false` value for the Helm key. Therefore, if you move `false` to `camunda.security.multi-tenancy.api-enabled`, this change disables the tenant API. | | `orchestration.exporters.camunda.enabled` | `camunda.data.secondary-storage.autoconfigure-camunda-exporter`. | | `orchestration.exporters.zeebe.replicas` | `zeebe.broker.exporters..args.index.numberOfReplicas`. | | `orchestration.security.authentication.oidc.redirectUrl` | This key is the base redirect URL. The chart derives `camunda.security.authentication.oidc.redirect-uri` (`/sso-callback`) from it. The chart also renders `camunda.operate.identity.redirectRootUrl` and `camunda.tasklist.identity.redirectRootUrl`, which the 8.10 application doesn't read. Set `camunda.security.authentication.oidc.redirect-uri` in `orchestration.extraConfiguration`. If Management Identity registers the Orchestration client, keep this key too. The chart also uses this key as the `root-url` of that client. | | `orchestration.upgrade.allowPreReleaseImages` | `camunda.system.upgrade.enable-version-check`. The new property has the opposite meaning. `allowPreReleaseImages: true` becomes `enable-version-check: false`. | | `orchestration.history.retention.usageMetricsMinimumAge` | The chart doesn't render this key in chart 14.x or 15.x, so the key has no effect. Remove the key. To configure the retention, set `camunda.data.secondary-storage.retention.usage-metrics-minimum-age`. | | `orchestration.history.retention.usageMetricsPolicyName` | The chart doesn't render this key in chart 14.x or 15.x, so the key has no effect. Remove the key. To configure the policy, set `camunda.data.secondary-storage..history.usage-metrics-policy-name`. | | `orchestration.debug` | The chart doesn't render this key in chart 14.x or 15.x, so the key has no effect. Remove the key. | | `orchestration.profilesOverride` | The chart doesn't render this key in chart 14.x or 15.x, so the key has no effect. Remove the key. | #### Connectors Move these keys to `connectors.extraConfiguration`: | Deprecated key | Notes | | :------------------------------------------------ | :-------------------------------------- | | `connectors.logging.level."io.camunda.connector"` | `logging.level."io.camunda.connector"`. | #### Optimize Move these keys to `optimize.extraConfiguration`, except where the notes say otherwise. Entries marked `Native config` use Optimize's own configuration property names instead of the `camunda.*` namespace. Set these entries in `optimize.extraConfiguration` as well: | Key | Notes | | :---------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `optimize.logLevel`, `optimize.upgradeLogLevel`, `optimize.esLogLevel` | `logging.level.io.camunda.optimize`, `logging.level.io.camunda.optimize.upgrade`, and `logging.level.org.elasticsearch`. Write each logger name as one key under `logging.level`. | | `optimize.profiles` | `spring.profiles.active`. | | `optimize.caches.cloudTenantAuthorizations.{maxSize,minFetchIntervalSeconds}` | Native config `caches.cloudTenantAuthorizations.maxSize` and `caches.cloudTenantAuthorizations.defaultTtlMillis`. The old `minFetchIntervalSeconds` value is a cache lifetime in milliseconds, so `defaultTtlMillis` replaces it. These caches apply to multi-tenancy. | | `optimize.partitionCount` | Native config `zeebe.partitionCount`. | | `optimize.database.{elasticsearch,opensearch}.prefix` | Native config `zeebe.name`. Also set `orchestration.exporters.zeebe.index.prefix` to the same value. If `orchestration.exporters.zeebe.index.prefix` is unset, the legacy Zeebe exporter uses `optimize.database..prefix`. The exporter doesn't use the `extraConfiguration` of Optimize. If you migrate only Optimize, Optimize reads indices that the exporter doesn't write. | | `optimize.multitenancy.enabled` | Move the key to `global.multitenancy.enabled`, not to `extraConfiguration`. This is a platform-wide switch. It also enables multi-tenancy in the Orchestration Cluster and Management Identity. The chart fails the render unless `identity.enabled` and `global.identity.auth.enabled` are `true`. The chart also fails the render unless you configure `identity.externalDatabase`. See [Logical Tenants](/self-managed/deployment/helm/configure/configure-logical-tenants.md). | In 8.10, the chart configures Optimize through `camunda.security.*`. If the Optimize client secret sets `existingSecret` but no `existingSecretKey`, the render fails. This check applies to `global.identity.auth.optimize.secret` and to `optimize.security.authentication.oidc.secret`. See [Render fails because the Optimize client secret has no key](#render-fails-because-the-optimize-client-secret-has-no-key). The chart doesn't configure the OIDC username and client ID claims for Optimize. Before you upgrade, set `camunda.security.authentication.oidc.username-claim` and `client-id-claim` through `optimize.extraConfiguration` to match your identity provider. Otherwise, M2M clients without an Optimize permission may see new permission errors. See [Client bearer tokens are now classified for permission checks](/self-managed/upgrade/components/890-to-8100.md#client-bearer-tokens-are-now-classified-for-permission-checks). The chart still renders some legacy security keys of Optimize alongside `camunda.security.*`. Because of this, Optimize logs deprecation warnings on startup, even when you set none yourself. These warnings need no change to your values file. If you set legacy security properties of Optimize yourself, through `optimize.env` or `optimize.extraConfiguration`, Optimize maps the recognized ones at startup. Optimize also logs a deprecation warning. The chart already renders `camunda.security.authentication.oidc.*` values. These values override a mapped legacy value. Therefore, change those settings with `optimize.security.authentication.oidc.*` or with an explicit `camunda.security.*` entry in `optimize.extraConfiguration`. Optimize no longer accepts the static API token (`api.accessToken` or `OPTIMIZE_API_ACCESS_TOKEN`), unless you set the temporary fallback `optimize.security.csl.enabled=false`. See [component-specific security configuration keys](/self-managed/upgrade/components/890-to-8100.md#component-specific-security-configuration-keys-are-deprecated) and [static API access token](/self-managed/upgrade/components/890-to-8100.md#static-api-access-token-is-no-longer-accepted). #### Camunda Hub Move these legacy keys to `camundaHub.restapi.extraConfiguration`: | Deprecated keys | Notes | | :------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `webModeler.restapi.mail.fromAddress` | `camunda.hub.mail.from-address`. `camundaHub.restapi.mail.fromAddress` is also deprecated. The chart fails the render when you set none of these keys. See [Render fails because the Hub sender address is missing](#render-fails-because-the-hub-sender-address-is-missing). | | `webModeler.restapi.mail.fromName` | `camunda.hub.mail.from-name`. | | `webModeler.restapi.mail.{smtpHost,smtpPort,smtpUser}` | `spring.mail.host`, `spring.mail.port`, and `spring.mail.username`. | | `webModeler.restapi.mail.smtpTlsEnabled` | `spring.mail.properties.mail.smtp.starttls.enable` and `spring.mail.properties.mail.smtp.starttls.required`. Set both. The chart renders both properties from the Helm key. | | `webModeler.restapi.logging.level.{"io.camunda.modeler","io.grpc"}` | `logging.level.io.camunda.hub` and `logging.level.io.grpc`. The `io.camunda.hub` logger replaces `io.camunda.modeler`. | The chart reads these keys from the merged `camundaHub.restapi` and `webModeler.restapi` values. It also warns when you set a key under `camundaHub.restapi.mail` or `camundaHub.restapi.logging`, but the message names the `webModeler.restapi.*` key. Hub authentication settings require no change for this upgrade. Hub continues to authenticate with its own properties. It doesn't use the `camunda.security.authentication.oidc.*` settings of the Orchestration Cluster. See [authentication configuration](/self-managed/upgrade/components/890-to-8100.md#authentication-configuration). #### Global Move each key to the `extraConfiguration` of the component that uses it: | Deprecated key | Notes | | :----------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `global.config.requestBodySize` | Set `spring.servlet.multipart.max-file-size` and `spring.servlet.multipart.max-request-size` on the Orchestration Cluster, Optimize, Management Identity, and the Hub REST API. Also set `server.tomcat.max-http-form-post-size` on the Orchestration Cluster. | | `global.zeebeClusterName` | Set `zeebe.broker.cluster.clusterName` on the Orchestration Cluster. | | `global.documentStore.type.{aws,gcp,inmemory}.storeId` | Use the unified `camunda.document.*` configuration (`default-store-id` plus the store definition) in `orchestration.extraConfiguration`. The Orchestration Cluster is the only component that chart 15.x configures from `global.documentStore`. See [Troubleshooting](#custom-document-store-id). | ### Move `global.multiregion` to `orchestration.partitioning` `global.multiregion.regions` and `global.multiregion.regionId` still work in 8.10, but the chart emits a deprecation warning for them. Only the Orchestration Cluster reads them. Therefore, move them to `orchestration.partitioning`: | 8.9 key | 8.10 key | | :---------------------------- | :----------------------------------------- | | `global.multiregion.regions` | `orchestration.partitioning.numberOfZones` | | `global.multiregion.regionId` | `orchestration.partitioning.zoneIndex` | Remove the `global.multiregion` block when you add `orchestration.partitioning`. If you set both, the render fails. Keep `orchestration.partitioning.scheme` at its default, `round-robin`, for the upgrade to 8.10. The scheme stays fixed for the life of a cluster. If you switch an existing cluster to `zone-aware`, every broker's identity changes. To convert an existing cluster, first complete the upgrade to 8.10. Then follow [Migrate to zone-aware brokers](/self-managed/deployment/helm/operational-tasks/zone-aware-migration.md). ### Move per-component truststores to `global.tls.caBundle` Chart 15.x deprecates per-component JKS truststores. They still work. The chart emits a deprecation warning when you set one of these keys: - `orchestration.data.secondaryStorage.elasticsearch.tls.secret.*` - `orchestration.data.secondaryStorage.opensearch.tls.secret.*` - `optimize.database.elasticsearch.tls.secret.*` - `optimize.database.opensearch.tls.secret.*` Supply a PEM-encoded CA bundle through `global.tls.caBundle.secret.existingSecret` and `global.tls.caBundle.secret.existingSecretKey` instead. The chart builds the JVM truststore when the pod starts. When you switch, remove the per-component `tls.secret` entries and any `-Djavax.net.ssl.trustStore*` flags from `javaOpts`. If you set a per-component truststore, it overrides `global.tls.caBundle` for that component. See [configure TLS with a CA bundle](/self-managed/deployment/helm/configure/tls.md). If you keep a per-component truststore for now, set both `existingSecret` and `existingSecretKey`. For `orchestration.data.secondaryStorage.*.tls.secret`, `existingSecretKey` has no default. Without it, the chart mounts no truststore. ### Review the Ingress-nginx annotation defaults In chart 14.x, `global.ingress.annotations` and `orchestration.ingress.grpc.annotations` had Ingress-nginx annotations as defaults. In chart 15.x, a compatibility shim adds them instead. The key `global.compatibility.nginx.renderAnnotations` controls this shim, and its default is `true`. With the default, the chart renders the same annotations as in 8.9, except `ingress.kubernetes.io/rewrite-target`, which the chart no longer sets. The chart adds the annotations for any `ingressClassName` that you use. The chart emits a deprecation warning when the shim adds an annotation. - If you use Ingress-nginx, you don't need to change anything for 8.10. To prepare your values file for the shim's removal and silence the warning, set all of the following annotations yourself. In `global.ingress.annotations`, set `ssl-redirect`, `proxy-buffering`, `proxy-buffer-size`, and `proxy-body-size`. In `orchestration.ingress.grpc.annotations`, set `ssl-redirect`, `backend-protocol`, and `proxy-buffer-size` when the gRPC Ingress renders. - If you use another Ingress controller, set `global.compatibility.nginx.renderAnnotations: false`. Also configure the annotations that your controller needs. For the annotation values and the Contour example, see [Ingress-nginx annotation defaults deprecated](/reference/announcements-release-notes/8100/8100-announcements.md#ingress-annotation-defaults-deprecated). ### Review changed chart defaults The following chart 15.x changes apply on `helm upgrade` without any change to your values file. Review each change against your deployment. #### Set the public issuer URL explicitly The default of `global.identity.auth.publicIssuerUrl` changed from `http://localhost:18080/auth/realms/camunda-platform` to an empty string. If your 8.9 values file sets neither `global.identity.auth.publicIssuerUrl` nor `global.identity.auth.issuer`, the components receive an empty issuer and a relative authorization URI after the upgrade. The chart reports no error. Before you upgrade, set the issuer URL that your users' browsers reach: ```yaml global: identity: auth: publicIssuerUrl: https://keycloak.example.com/auth/realms/camunda-platform ``` #### Connectors and Management Identity use per-pod ephemeral volumes Chart 15.x replaces the persistent volume claim (PVC) that the chart manages for Connectors and Management Identity with a per-pod ephemeral volume. The [Web Modeler REST API received the same change](/self-managed/upgrade/helm/index.md#web-modeler-restapi-ephemeral-volume). The volume provides storage only for `/tmp`, so you lose no data. | Setting | Upgrade behavior | | :----------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | | `persistence.enabled: false` (default) | No change. | | `persistence.enabled: true`, no `existingClaim` | Each pod has its own claim, created with the pod and removed with it. Helm removes the shared claim that it created in 8.9. | | `persistence.enabled: true`, `existingClaim` set | No change. The pods still mount your existing claim. | This applies to `connectors.persistence` and `identity.persistence`. If you set `persistence.selector` to bind pre-provisioned volumes, provision one matching volume for each pod. Also provision one more matching volume for the extra pod during a rollout. #### Connectors probes use the per-probe scheme In chart 14.x, the Connectors startup, readiness, and liveness probes used the top-level `connectors.scheme`. They ignored the per-probe `scheme` values. In chart 15.x, they use `connectors.startupProbe.scheme`, `connectors.readinessProbe.scheme`, and `connectors.livenessProbe.scheme`. They ignore `connectors.scheme`. The per-probe default is empty. An empty value becomes `HTTPS` when the chart detects that Connectors serves TLS. In all other cases, the value becomes `HTTP`. The chart detects TLS from these sources, in this order. First, the chart checks the last `SERVER_SSL_ENABLED` entry in `connectors.env`. A value of `true` or `false` in this entry wins over the other sources. Second, the chart checks `global.tls.connectors.enabled: true`. Third, the chart checks `server.ssl.enabled: true` in `connectors.extraConfiguration` or `connectors.configuration`. - If you set `connectors.scheme`, move the value to the per-probe keys. - Remove per-probe `scheme` values that you copied from the chart 14.x defaults (`HTTP`). Chart 14.x ignored them. Chart 15.x uses them, so a Connectors pod that serves TLS never becomes ready. - If Connectors serves TLS, chart 15.x also requires the certificate in your values file. See [Render fails because TLS is enabled without a certificate](#render-fails-because-tls-is-enabled-without-a-certificate). #### Remove environment variables the chart now sets Chart 15.x sets these environment variables itself. If your values file sets the same names, remove them from `identity.env`, `optimize.env`, or `camundaHub.restapi.env`. Duplicate names fail `helm upgrade` when Helm applies the release server-side. By default, Helm v4 applies a release server-side if you installed the release with Helm v4. | Component | Environment variables | | :---------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Management Identity (`global.identity.auth.type: KEYCLOAK`) | `CAMUNDA_CONNECTORS_CLIENT_ID`, `CAMUNDA_ORCHESTRATION_CLIENT_ID`, `CAMUNDA_OPTIMIZE_CLIENT_ID` | | Optimize | `VALUES_OPTIMIZE_CLIENT_SECRET` when Optimize authenticates and you configure its client secret, and `SERVER_SERVLET_CONTEXT_PATH` when you set `optimize.contextPath` | | Camunda Hub REST API | `CAMUNDA_HUB_PUSHER_APPID`, `CAMUNDA_HUB_PUSHER_KEY`, `CAMUNDA_HUB_PUSHER_SECRET`, and `SPRING_MAIL_PASSWORD` when you configure `camundaHub.restapi.mail.secret` | These Hub variables replace `RESTAPI_PUSHER_APP_ID`, `RESTAPI_PUSHER_KEY`, `RESTAPI_PUSHER_SECRET`, and `RESTAPI_MAIL_PASSWORD`. If `camundaHub.restapi.env` or `webModeler.restapi.env` sets one of the old names, the chart emits a warning: - For the Pusher variables, remove the entries. The chart always sets the new names. To supply your own Pusher values, see [Pin the Hub WebSocket secrets](#pin-the-hub-websocket-secrets). - For `RESTAPI_MAIL_PASSWORD`, configure `camundaHub.restapi.mail.secret` and remove the entry. Alternatively, if you don't configure `camundaHub.restapi.mail.secret`, rename the entry to `SPRING_MAIL_PASSWORD`. #### Management Identity URL includes the Gateway port This change applies if you use Gateway API (`global.gateway.enabled: true`) without the shared Ingress, and you do not set `identity.fullURL`. In chart 14.x, the Management Identity URL and the root URL of the Keycloak login client had no port. In chart 15.x, these URLs use the same origin as the other Gateway URLs. The chart adds the Gateway listener port: `global.gateway.tls.port` when `global.gateway.tls.enabled: true`, otherwise `global.gateway.port`. The chart does not add port 443 to HTTPS URLs or port 80 to HTTP URLs. If clients reach the Gateway on a port that is different from the listener port, set the public port in `global.gateway.publicPorts` before you upgrade. For example, a LoadBalancer Service sends port 443 to a listener on port 8443: ```yaml global: gateway: tls: port: 8443 publicPorts: https: 443 ``` `global.gateway.publicPorts` changes only the URLs that the chart generates. It does not change the Gateway listeners or Services. #### Review application default changes Some 8.10 application defaults change the behavior of a Helm deployment without a change to your values file: | Component | Change | Details | | :-------------------- | :---------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- | | Orchestration Cluster | The default memory allocation strategy of RocksDB changes from `PARTITION` to `FRACTION`. | [Default RocksDB memory allocation strategy](/reference/announcements-release-notes/8100/8100-announcements.md#rocksdb-memory-allocation-strategy) | | Optimize | Optimize no longer imports object variable values by default. | [Default objectVariable inclusion changed](/self-managed/upgrade/components/890-to-8100.md#default-objectvariable-inclusion-changed) | ### Migrate deprecated Helm keys to `extraConfiguration` The `extraConfiguration` value format is the same as in 8.9. If you already use `extraConfiguration`, you don't need to change the format for this upgrade. For the mechanics and the behavior of each component, see [Application configuration and `extraConfiguration`](/self-managed/deployment/helm/configure/application-configs.md). The same page gives a worked example of how to move settings into a configuration file. The following condensed example shows the shape of the migration for the Orchestration Cluster: ```yaml # Before (8.9 keys that chart 15.x deprecates) orchestration: logLevel: debug data: snapshotPeriod: 10m history: retention: enabled: true minimumAge: 45d # After (8.10, extraConfiguration) orchestration: extraConfiguration: - file: application-migrated.yaml content: | logging: level: io.camunda.zeebe: debug camunda: data: snapshot-period: 10m secondary-storage: retention: enabled: true minimum-age: 45d elasticsearch: history: policy-name: camunda-history-retention-policy ``` The `policy-name` entry keeps the ILM policy name the chart rendered in 8.9. For OpenSearch, use `opensearch:` instead of `elasticsearch:`. ## Example: upgrade `values.yaml` from 8.9 to 8.10 This example shows a Helm `values.yaml` for 8.9 and the same configuration after you migrate it to 8.10. It covers the most common changes: removed `global.elasticsearch` keys, the Camunda Hub consolidation, a deprecated application configuration key, and the changed issuer URL default. The Hub consolidation includes Hub sizing and pinned Pusher secrets. The Kubernetes Secrets the example references (`elasticsearch-credentials`, `web-modeler-db`, and `camunda-hub-pusher`) must exist before you upgrade.
View example values files ```yaml # ========================= # Camunda 8.9 values.yaml # ========================= global: host: camunda.example.com # Removed in 8.10 elasticsearch: enabled: true external: true url: protocol: https host: elasticsearch.example.com port: 443 auth: username: elastic secret: existingSecret: elasticsearch-credentials existingSecretKey: password # publicIssuerUrl is not set: the 8.9 default applies identity: auth: console: redirectUrl: https://camunda.example.com/console identity: enabled: true orchestration: data: secondaryStorage: type: elasticsearch # Deprecated in 8.10 history: retention: enabled: true minimumAge: 45d optimize: enabled: true console: enabled: true webModeler: enabled: true restapi: externalDatabase: url: jdbc:postgresql://postgres.example.com:5432/web-modeler username: web-modeler secret: existingSecret: web-modeler-db existingSecretKey: password mail: fromAddress: noreply@example.com ``` ```yaml # ========================= # Camunda 8.10 values-8.10.yaml # ========================= global: host: camunda.example.com identity: auth: # The 8.10 default is empty; set it explicitly publicIssuerUrl: https://camunda.example.com/auth/realms/camunda-platform # global.identity.auth.console removed; it has no effect in 8.10 identity: enabled: true orchestration: data: secondaryStorage: type: elasticsearch elasticsearch: url: https://elasticsearch.example.com:443 auth: username: elastic secret: existingSecret: elasticsearch-credentials existingSecretKey: password extraConfiguration: - file: application-migrated.yaml content: | camunda: data: secondary-storage: retention: enabled: true minimum-age: 45d elasticsearch: history: policy-name: camunda-history-retention-policy optimize: enabled: true database: elasticsearch: enabled: true external: true url: protocol: https host: elasticsearch.example.com port: 443 auth: username: elastic secret: existingSecret: elasticsearch-credentials existingSecretKey: password # Replaces console and webModeler camundaHub: enabled: true restapi: # Sized for Console and the Web Modeler REST API together resources: requests: cpu: 1900m memory: 2304Mi limits: cpu: 3800m memory: 4608Mi externalDatabase: url: jdbc:postgresql://postgres.example.com:5432/web-modeler username: web-modeler secret: existingSecret: web-modeler-db existingSecretKey: password extraConfiguration: - file: application-mail.yaml content: | camunda: hub: mail: from-address: noreply@example.com pusher: secret: existingSecret: camunda-hub-pusher existingSecretKey: app-secret client: secret: existingSecret: camunda-hub-pusher existingSecretKey: app-key ```
## Render and test the complete configuration After reviewing the toolkit report and completing the manual changes, render your configuration and test the upgrade before upgrading production. 1. Render the chart and inspect the output for deprecation warnings: ```bash helm template camunda/camunda-platform \ --version \ --namespace \ -f values-8.10.yaml > rendered-8.10.yaml grep 'DEPRECATION' rendered-8.10.yaml ``` Use the same override files, order, and flags you plan to use for the upgrade. Review and address any rendering errors or deprecation warnings. 1. Complete the non-production upgrade test listed in the [prerequisites](#prerequisites), using a copy of your production configuration. ## Run the Helm upgrade Before upgrading production, complete the non-production test and verify your [backups](#prerequisites). If you run Web Modeler, follow the [Hub backup timing and migration procedure](#migrate-web-modeler-and-console-to-camunda-hub), including stopping writes before the database backup. If your 8.9 release doesn't run Web Modeler, run the upgrade in one step: ```bash helm repo update helm upgrade camunda/camunda-platform \ --version \ --namespace \ -f values-8.10.yaml ``` The one-step upgrade applies to a Console-only release after you enable Camunda Hub. To enable Hub, see [Deploy Camunda Hub for a Console-only release](#deploy-camunda-hub-for-a-console-only-release). If your release runs Web Modeler, don't run this command. Follow [Migrate Web Modeler and Console to Hub](#migrate-web-modeler-and-console-to-camunda-hub) instead. The first `helm upgrade` of that procedure, in the `quiesce` phase, also upgrades every other component to 8.10. ## Migrate Web Modeler and Console to Camunda Hub Camunda 8.10 combines the Web Modeler and Console components from 8.9 into Camunda Hub. The [component upgrade guide](/self-managed/upgrade/components/890-to-8100.md#camunda-hub) is the authority on how Hub upgrades from 8.9 to 8.10. It explains why the upgrade needs downtime and how the migration changes your data. The steps below apply that procedure to a Helm release with `camundaHub.upgrade.phase`. :::warning Don't use `helm upgrade --rollback-on-failure` (or its deprecated alias `--atomic`) for this upgrade. After the database migration starts, don't return the Helm release to 8.9 before you restore the 8.9 database backup. An 8.9 application isn't compatible with a partially or fully migrated 8.10 database. Flyway doesn't block the startup of the 8.9 application. Therefore, the application can report that it's ready while requests that use removed tables or columns fail. ::: The `camundaHub.upgrade.phase` value controls the Hub workloads during the migration: | Phase | REST API replicas | WebSockets replicas | Serves traffic | | :-------- | :--------------------------- | :------------------ | :------------- | | `quiesce` | `0` | `0` | No | | `migrate` | `1` | `0` | No | | `normal` | The configured replica count | `1` | Yes | The Hub Services select only pods in the `normal` phase. The migration pod is therefore unavailable to user traffic. The commands below use the chart's default resource names. If you set `fullnameOverride` or `nameOverride` under `camundaHub` or `webModeler`, list the names with `kubectl -n get deployment -l app.kubernetes.io/instance=`. Also list the names with this command if your release name contains `web-modeler`. ### Pin the Hub WebSocket secrets The migration runs `helm upgrade` three times. If you don't set the Pusher secrets, the chart generates new random values on every upgrade. The REST API and WebSockets pods read these values only when they start. Therefore, a pod that restarts after a later upgrade can have different values from the other pod. Different values in the two pods break WebSocket authentication. Before you start, create a Kubernetes Secret that holds both values. Then reference the Secret. Any values work, because the REST API and WebSockets pods read the same Secret. The Secret must exist with the named keys before the first `helm upgrade`. The Hub pods don't start without the Secret. ```yaml camundaHub: restapi: pusher: secret: existingSecret: camunda-hub-pusher existingSecretKey: app-secret client: secret: existingSecret: camunda-hub-pusher existingSecretKey: app-key ``` ### Stop Web Modeler and Console workloads Upgrade the release to 8.10 in the `quiesce` phase. This `helm upgrade` also upgrades every other component in the release to 8.10: ```bash helm repo update helm upgrade camunda/camunda-platform \ --version \ --namespace \ -f values-8.10.yaml \ --set camundaHub.upgrade.phase=quiesce \ --wait \ --timeout 10m ``` Check that both Web Modeler Deployments show zero desired and available replicas: ```bash kubectl -n get deployment \ -web-modeler-restapi \ -web-modeler-websockets ``` The Deployment status doesn't count terminating pods. Check that no Web Modeler pods remain. Continue when the command returns `No resources found`: ```bash kubectl -n get pods \ -l app.kubernetes.io/instance=,app.kubernetes.io/name=web-modeler ``` Also check that the 8.9 Console Deployment and pods no longer exist. Helm removes the Console Deployment during this upgrade, so the first command returns `NotFound` and the second command returns `No resources found`: ```bash kubectl -n get deployment -console kubectl -n get pods \ -l app.kubernetes.io/instance=,app.kubernetes.io/component=console ``` Stop any external processes that write to the Hub database. Check that no Hub writers remain before you continue. ### Back up the Hub database Create a vendor-native backup of the Hub database after you quiesce Hub. This backup is the recovery point if the migration fails or you must return to 8.9. Use the backup tool for your database platform, such as `pg_dump` for PostgreSQL. See [RDBMS backup and restore guidance](/self-managed/concepts/databases/relational-db/rdbms-setup-guide.md#step-7-backup-and-restore). Don't continue until you check that you can restore the Hub database backup. ### Run the database migration Set the release to the `migrate` phase: ```bash helm upgrade camunda/camunda-platform \ --version \ --namespace \ -f values-8.10.yaml \ --set camundaHub.upgrade.phase=migrate \ --set camundaHub.restapi.livenessProbe.enabled=false \ --set camundaHub.restapi.startupProbe.enabled=false \ --wait \ --timeout 10m ``` The chart disables the REST API liveness and startup probes by default. Keep these probes disabled for the `migrate` phase. Also keep a probe disabled if you enabled it under the legacy `webModeler.restapi.*` keys. The probe endpoint isn't reachable until the migration completes. Therefore, Kubernetes restarts the container after `failureThreshold` failed probes. Measure the migration time on a test system that has a copy of your data. Set `--timeout` to a value longer than that time. If `helm upgrade` exceeds that timeout, the migration pod continues to migrate. In this case, check the logs of the migration pod before you do anything else. The single Hub REST API pod runs the database migration during startup. The Hub Service doesn't route to the pod. Wait until the pod is ready: ```bash kubectl -n rollout status \ deployment/-web-modeler-restapi \ --timeout=10m ``` Before you restore traffic, check the migrated Hub data. First, check the REST API logs: ```bash kubectl -n logs \ deployment/-web-modeler-restapi ``` For the Flyway log entries that show the progress and the result of the migration, see [Database migration](/self-managed/components/hub/version-upgrade.md#database-migration). For the changes that the migration makes to your Hub data, see [Data migration](/self-managed/upgrade/components/890-to-8100.md#data-migration). The migration also carries the clusters that your projects used into Hub as environments. It assigns the environments to their workspaces. See [environments](/self-managed/upgrade/components/890-to-8100.md#environments). The Hub Services don't route to the migration pod. To check the migrated data in the Hub UI before you restore traffic, forward a local port to the pod. Use `kubectl -n port-forward deployment/-web-modeler-restapi :`. If the migration fails: 1. Set `camundaHub.upgrade.phase` to `quiesce` again. Wait until both Hub Deployments reach zero replicas. See [Stop Web Modeler and Console workloads](#stop-web-modeler-and-console-workloads) for the procedure. 1. Check that no other process that can write to the Hub database runs. 1. Restore the 8.9 Hub database backup. Check the backup. For the procedure, see [Backup and restore Hub data](/self-managed/operational-guides/backup-restore/modeler-backup-and-restore.md#restore). 1. Fix the cause of the failure. Repeat the `migrate` phase. To return the whole release to 8.9 instead, restore the Hub database backup. Then find the last 8.9 revision with `helm history --namespace `. Then run `helm rollback --namespace `. The rollback applies to every component in the release. The `quiesce` upgrade already moved the Orchestration Cluster and the other components to 8.10. Camunda doesn't support minor downgrades. Therefore, also restore the backups you created of those components before the upgrade. See [version compatibility checks](/self-managed/components/orchestration-cluster/core-settings/concepts/version-compatibility.md). ### Restore Hub traffic After you check the migration, set the release to the `normal` phase: ```bash helm upgrade camunda/camunda-platform \ --version \ --namespace \ -f values-8.10.yaml \ --set camundaHub.upgrade.phase=normal \ --wait \ --timeout 10m ``` Wait until both Hub workloads are ready: ```bash kubectl -n rollout status \ deployment/-web-modeler-restapi \ --timeout=10m kubectl -n rollout status \ deployment/-web-modeler-websockets \ --timeout=10m ``` :::note The lifecycle phases currently need you to follow this sequence. If you select `normal` before the migration completes, the chart doesn't yet prevent this. For a fresh 8.10 installation, leave `camundaHub.upgrade.phase` at its default value, `normal`. The `quiesce` and `migrate` phases apply only when you upgrade an existing 8.9 Web Modeler database. Console has no database in 8.9. ::: ### Migrate document-store cloud credentials This migration applies only when a component uses credentials propagated from `global.documentStore.type.*` for a separate cloud integration. Configure replacement credentials only on each affected component. Don't copy document-store credentials to components that don't independently need cloud access. The migration applies when `global.documentStore.type.aws.enabled` or `global.documentStore.type.gcp.enabled` is `true`, or when `global.documentStore.activeStoreId` is `aws` or `gcp`. Chart 15.x emits no warning when these credentials disappear from a component. :::note `optimize.env`, `camundaHub.restapi.env`, and `identity.env` support Helm's `tpl` templating (for example, `{{ .Release.Name }}`). `connectors.env` and every `.envFrom` don't support `tpl` templating. The chart renders them as plain YAML. ::: For Camunda Hub, a list that you add under `camundaHub.restapi` replaces the matching `webModeler.restapi` list. Examples of such lists are `env`, `extraVolumes`, and `extraVolumeMounts`. Move your existing entries into the matching `camundaHub.restapi` list. #### AWS - Static access key / secret key **Connectors**: The default credentials chain of the AWS SDK uses these credentials for connector tasks (Lambda, SQS, SNS, DynamoDB, Bedrock, Textract). ```yaml connectors: env: - name: AWS_ACCESS_KEY_ID valueFrom: secretKeyRef: name: connectors-aws-credentials key: accessKeyId - name: AWS_SECRET_ACCESS_KEY valueFrom: secretKeyRef: name: connectors-aws-credentials key: secretAccessKey ``` **Optimize**: Optimize signs AWS OpenSearch requests with these credentials when `optimize.database.opensearch.aws.enabled` is `true`. In 8.9, the chart also accepted `global.opensearch.aws.enabled`. Chart 15.x rejects this key. ```yaml optimize: env: - name: AWS_ACCESS_KEY_ID valueFrom: secretKeyRef: name: optimize-aws-credentials key: accessKeyId - name: AWS_SECRET_ACCESS_KEY valueFrom: secretKeyRef: name: optimize-aws-credentials key: secretAccessKey - name: AWS_REGION valueFrom: secretKeyRef: name: optimize-aws-credentials key: region ``` :::note Optimize catches its credential resolution failures and silently uses Basic authentication instead. After you migrate, check the OpenSearch connection. Don't expect a startup error. ::: **Camunda Hub**: ```yaml camundaHub: restapi: env: - name: AWS_ACCESS_KEY_ID valueFrom: secretKeyRef: name: camunda-hub-aws-credentials key: accessKeyId - name: AWS_SECRET_ACCESS_KEY valueFrom: secretKeyRef: name: camunda-hub-aws-credentials key: secretAccessKey - name: AWS_REGION valueFrom: secretKeyRef: name: camunda-hub-aws-credentials key: region ``` Any of the three components can load the variables at once through `envFrom` instead. This option works if the Secret's data keys already have the names `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`. Optimize and Hub also need a data key named `AWS_REGION`. ```yaml connectors: # (or optimize: / camundaHub.restapi:) envFrom: - secretRef: name: -aws-credentials ``` #### AWS - IRSA Annotate each component's own service account: ```yaml connectors: serviceAccount: annotations: eks.amazonaws.com/role-arn: arn:aws:iam:::role/connectors-role optimize: serviceAccount: annotations: eks.amazonaws.com/role-arn: arn:aws:iam:::role/optimize-role camundaHub: serviceAccount: annotations: eks.amazonaws.com/role-arn: arn:aws:iam:::role/camunda-hub-role ``` #### AWS - EKS Pod Identity Create an EKS Pod Identity association for each component service account instead of the `eks.amazonaws.com/role-arn` annotation. With the default names, the service accounts are `-connectors`, `-optimize`, `-identity`, and `-web-modeler` for Camunda Hub. The Hub REST API and WebSockets pods share the `-web-modeler` service account. For both IRSA and EKS Pod Identity, set `AWS_REGION` explicitly for Optimize and Hub. EKS Pod Identity provides credentials but no region. The IRSA webhook injects only the region of the cluster. It injects this region only when the webhook has a region configured. Connectors doesn't need `AWS_REGION` because its element templates contain the region for each task. #### GCP **Connectors:** The examples below use `gcp-credentials` as the Secret name. They expect the Secret's data key to have the name `service-account.json`. Replace `gcp-credentials` with the name of your Secret. If your 8.9 `global.documentStore.type.gcp.secret.existingSecretKey` used another key, map that key to `service-account.json` with `secret.items`. Alternatively, update `GOOGLE_APPLICATION_CREDENTIALS` to the mounted filename. If you changed `global.documentStore.type.gcp.mountPath` or `global.documentStore.type.gcp.fileName`, use those values for `mountPath` and in `GOOGLE_APPLICATION_CREDENTIALS`. ```yaml connectors: env: - name: GOOGLE_APPLICATION_CREDENTIALS value: /var/secrets/gcp/service-account.json extraVolumeMounts: - name: connectors-gcp-credentials mountPath: /var/secrets/gcp readOnly: true extraVolumes: - name: connectors-gcp-credentials secret: secretName: gcp-credentials ``` **Camunda Hub REST API:** ```yaml camundaHub: restapi: env: - name: GOOGLE_APPLICATION_CREDENTIALS value: /var/secrets/gcp/service-account.json extraVolumeMounts: - name: camunda-hub-gcp-credentials mountPath: /var/secrets/gcp readOnly: true extraVolumes: - name: camunda-hub-gcp-credentials secret: secretName: gcp-credentials ``` Workload Identity (GKE) is the equivalent of IRSA. It uses an annotation in `.serviceAccount.annotations`: ```yaml connectors: # (or camundaHub:) serviceAccount: annotations: iam.gke.io/gcp-service-account: @.iam.gserviceaccount.com ``` #### Azure Optimize and Camunda Hub need no migration because the chart never connected `global.documentStore.type.azure.*` to them. Chart 14.8.0 and earlier also rendered these settings into Connectors. However, Connectors has no consumer for these settings, so it needs no migration either. Only the document store feature itself reads these settings. The Orchestration Cluster owns this feature. #### Identity Chart 14.8.0 and earlier passed the document-store AWS credentials, `AWS_REGION`, and GCP credentials to Management Identity. Chart 14.8.1 and later, and chart 15.x, don't pass them. If you configured Aurora/RDS IAM authentication manually through `identity.env` and used them, keep the datasource override. Then supply credentials in one of these ways. For static credentials, supply them through `identity.env` or `identity.envFrom`. For IRSA, annotate `identity.serviceAccount`. For EKS Pod Identity, create an association. The AWS JDBC Wrapper takes the region for the IAM token from the database hostname. Set the `iamRegion` parameter of the wrapper when the hostname doesn't contain the region. #### Not affected - **Orchestration Cluster** continues to own `global.documentStore.type.*` for the document store feature itself. AWS, GCP, and in-memory stores need no change if you use these keys only for that feature. For Azure Blob Storage, the chart binds `global.documentStore.type.azure.connectionString` to `camunda.document.azure..connection-string`. This binding happens only when you set the connection string Secret and `orchestration.extraConfiguration` declares the store under `camunda.document.azure.` (in lowercase). Chart 14.x always set the `DOCUMENT_STORE__CONNECTION_STRING` environment variable instead. See [Azure Blob Storage configuration](/self-managed/concepts/document-handling/configuration/helm.md#azure-blob-storage-configuration). ## Monitor and validate the upgrade After you trigger the Helm upgrade, monitor the rollout to check that all pods return to a healthy state. ### Watch pod rollout progress ```bash kubectl -n get pods -w ``` You should see pods terminate and restart with updated images. ### Inspect logs (if required) ```bash kubectl -n logs --previous ``` ### Monitor migration progress There are two ways to monitor the migration status of the Orchestration Cluster: #### Public Cluster API The public Cluster API endpoint for [upgrade readiness](/apis-tools/orchestration-cluster-api-rest/specifications/get-cluster-upgrade-status.api.mdx), `/cluster/v2/status/upgrade`, returns the overall upgrade-readiness status. ```json { "status": "MIGRATED" } ``` #### Management API The [Management API endpoint for upgrade readiness](/self-managed/components/orchestration-cluster/zeebe/operations/management-api.md#upgrade-readiness-api) returns more detail about the overall upgrade status, including details for each physical tenant and condition. ### Validate the upgrade 1. Check that all pods are healthy and run `8.10.x` images: ```bash kubectl -n get pods kubectl -n get pods -o jsonpath="{range .items[*]}{.metadata.name}{':\t'}{range .spec.containers[*]}{.image}{'\n'}{end}{end}" ``` 1. Check the `helm upgrade` output for `[camunda][warning] DEPRECATION` messages. Each message names a deprecated key and tells you where to move it or that you can remove it. Most messages also state when the chart removes the key (chart v17, Camunda 8.12). Helm prints these messages with the release notes, so `--hide-notes` and `global.createReleaseInfo: false` hide them. The chart also writes them to the [`-warnings` ConfigMap](/self-managed/deployment/helm/operational-tasks/helm-values-schema-validation.md#find-unknown-keys). 1. Check access to Camunda components. Check authentication and authorization behavior. Check that your workers can still poll and complete jobs. 1. In Camunda Hub, click **Environments** in the left navigation, and confirm that the expected environments appear with the correct status. Assign environments to any workspace that you create after the upgrade. See [environments](/self-managed/upgrade/components/890-to-8100.md#environments). ## Troubleshooting ### Upgrade failed due to missing secrets If your upgrade fails because credentials are missing, see [Extract plaintext values and reference them as Kubernetes Secrets](/self-managed/deployment/helm/configure/secret-management.md#extract-plaintext-values-and-reference-them-as-kubernetes-secrets). ### Upgrade failed with secondary storage validation error If Helm fails with a validation error about the secondary storage type, set the type explicitly: ```yaml orchestration: data: secondaryStorage: type: elasticsearch # or "opensearch" or "rdbms" ``` Then set the connection settings for that backend, for example `orchestration.data.secondaryStorage.elasticsearch.url`. The Elasticsearch default uses `-elasticsearch`, which chart 15.x doesn't deploy. Alternatively, if you don't need secondary storage, set `global.noSecondaryStorage: true`. This setting disables Operate, Tasklist, and Optimize. ### Backwards-compatible audiences replace the audience list The deprecated `orchestration.security.authentication.oidc.backwardsCompatibleAudiences` appended its values to the default audiences. However, its target, `camunda.security.authentication.oidc.audiences`, replaces the whole list. If you migrate the key verbatim, the Orchestration Cluster accepts only your extra audience and rejects tokens for the default audiences. The gRPC API reports this rejection as `UNAUTHENTICATED: Invalid bearer token`. The chart's default audiences are `orchestration` (the client ID) and `orchestration-api`. When you enable Camunda Hub, the default audiences also include `web-modeler-api`. List the default audiences alongside your backwards-compatible audience in `audiences`. ### Initialization authorizations report "No permissionTypes provided" If the Orchestration Cluster pod fails to start with `IdentityInitializationException: Cannot initialize configured authorizations for tenant 'default':` followed by the line `- No permissionTypes provided`, an authorization entry has no `permissions` list. Under `camunda.security.initialization.authorizations`, the permission list field is `permissions`, not `permissionTypes`. ### Custom document store ID You can't use a non-default `global.documentStore.type.inmemory.storeId` (or `aws`/`gcp`) through the deprecated key alone. The reason is that `global.documentStore.activeStoreId` (the store type) sets the default store ID. The custom store ID never sets it. As a result, the app fails with `Default document store ID does not match any configured document store`. Migrate the whole document configuration to `camunda.document.*` (`default-store-id` plus the store definition) in `orchestration.extraConfiguration`. The Orchestration Cluster is the only component that chart 15.x configures from `global.documentStore`. ### Multi-tenancy with an unprotected API You can't combine `camunda.security.multi-tenancy.checks-enabled: true` with `camunda.security.authentication.unprotected-api: true`. If you do, the Orchestration Cluster pod fails with `Multi-tenancy is enabled ... but the API is unprotected`. Protect the API when you enable multi-tenancy. ### Render fails because a values key has been removed If `helm upgrade` fails with `[camunda][error] The Helm values file key "" has been removed.`, your values file still sets a key that chart 15.x no longer accepts. The chart checks for these keys before it changes any workloads. Remove or replace the key. For details, see [Remove keys rejected by chart 15.x](#remove-keys-rejected-by-chart-15x). ### Render fails because the Optimize client secret has no key If `helm upgrade` fails with `[camunda][error] Optimize with OIDC authentication requires an existingSecretKey alongside its existingSecret`, the Optimize client secret references a Kubernetes Secret but doesn't name the key inside the Secret. Without the key, the chart would drop the client secret from the Optimize Deployment. Set the key next to the Secret name. If you reference the Secret in `global.identity.auth.optimize.secret.existingSecret`, set `global.identity.auth.optimize.secret.existingSecretKey`. If you reference the Secret in `optimize.security.authentication.oidc.secret.existingSecret`, set `optimize.security.authentication.oidc.secret.existingSecretKey`. Alternatively, supply `VALUES_OPTIMIZE_CLIENT_SECRET` yourself through `optimize.env`. Another option is to list `VALUES_OPTIMIZE_CLIENT_SECRET` in `optimize.security.authentication.oidc.envFromProvides`. ### Render fails because Optimize sets `server.ssl` If `helm upgrade` fails with `[camunda][error] optimize.env sets [...], which the Optimize server does not support.` or `[camunda][error] optimize.configuration or optimize.extraConfiguration declares server.ssl, which the Optimize server does not support.`, your values file sets `SERVER_SSL_*` environment variables or `server.ssl.*` properties for Optimize. The chart rejects them regardless of their value. Optimize installs its own HTTPS connector from `container.keystore.*`. `server.ssl.*` adds a second SSL host configuration named `_default_` to the same connector. As a result, Tomcat aborts at startup. Remove the `SERVER_SSL_*` environment variables and the `server.ssl.*` properties. To serve TLS from Optimize, set `global.tls.optimize.enabled: true`. Set `global.tls.optimize.cert.secret.existingSecret` to a Secret that holds a PKCS12 keystore (`keystore.p12`) and its password (`keystore-password`). Optimize doesn't support PEM certificates or a key alias. ### Render fails because the Hub sender address is missing If `helm upgrade` fails with `The value 'webModeler.restapi.mail.fromAddress' is required`, Camunda Hub has no sender address for emails. Set `camunda.hub.mail.from-address` in a `camundaHub.restapi.extraConfiguration` file that Spring imports. The chart finds the key as nested or dotted YAML in any document of the file, and in a `.properties` file. The chart also accepts the legacy `camunda.modeler.mail.from-address`, `camundaHub.restapi.mail.fromAddress`, and `webModeler.restapi.mail.fromAddress`. ### Render fails because both multi-region blocks are set If `helm upgrade` fails with `[camunda][error] orchestration.partitioning and global.multiregion are both configured.`, your values file sets the deprecated `global.multiregion` block and its replacement. Keep `orchestration.partitioning`. Remove `global.multiregion`. See [Move `global.multiregion` to `orchestration.partitioning`](#move-globalmultiregion-to-orchestrationpartitioning). ### Render fails because TLS is enabled without a certificate If `helm upgrade` fails with `[camunda][error] Orchestration REST TLS is enabled but no server cert is configured.`, chart 15.x detected TLS for the Orchestration Cluster REST API but found no certificate. In order of precedence, the chart detects REST TLS from `SERVER_SSL_ENABLED` in `orchestration.env`, `global.tls.orchestration.rest.enabled`, and a nested `server.ssl.enabled` key in `orchestration.extraConfiguration` or `orchestration.configuration`. Set `global.tls.orchestration.rest.cert.secret.existingSecret`. Alternatively, configure `SERVER_SSL_KEY_STORE` or `SERVER_SSL_CERTIFICATE` manually in `orchestration.env`, together with the matching volumes. Another option is to remove the TLS setting. The chart runs separate checks for the gRPC API and for Connectors: - The gRPC check fails with `[camunda][error] Orchestration gRPC TLS is enabled but no server cert is configured.` when `CAMUNDA_API_GRPC_SSL_ENABLED`, `global.tls.orchestration.grpc.enabled`, or a nested `camunda.api.grpc.ssl.enabled` key enables TLS and you don't set `global.tls.orchestration.grpc.cert.secret.existingSecret`. - The Connectors check fails with `[camunda][error] Connectors TLS is enabled but no server cert is configured.` when `SERVER_SSL_ENABLED` in `connectors.env`, `global.tls.connectors.enabled`, or a nested `server.ssl.enabled` key enables TLS. To fix the failure, set `global.tls.connectors.enabled: true` together with `global.tls.connectors.cert.secret.existingSecret`. See [Orchestration Cluster TLS modes](/self-managed/deployment/helm/configure/orchestration-tls-modes.md). ## Next steps This upgrade keeps your deployment in the chart's default `combined` topology. In this topology, one Helm release runs every enabled component. The upgrade doesn't change your release layout. For a new production deployment, the baseline topology of Camunda 8.10 is different. It has one Hub release and one release for each Orchestration Cluster. It also has one Optimize release for each Physical Tenant. See [Camunda 8.10 deployment topology](/self-managed/reference-architecture/reference-architecture.md#deployment-topology). To move an existing deployment to that topology, you do a separate operation. This operation needs its own data, storage, and rollback planning. First, complete this version upgrade. Then, see [move from a combined release to the split topology](/self-managed/upgrade/helm/combined-to-split-topology.md). --- ## Move from a combined release to the split topology Move an existing single-release Camunda 8.10 deployment to the split topology: one Hub release and one release per Orchestration Cluster. If the combined release runs Optimize, Optimize either stays in the orchestration release, or moves to one Optimize release per Physical Tenant. This is a topology change, not a version upgrade. It doesn't change any component version, and it isn't required. A `combined` release remains both supported and the chart default. :::warning The hard part of this move is data, not values. Orchestration Cluster broker volumes hold active process state and don't move between releases. Read [what moves and what doesn't](#what-moves-and-what-doesnt) before you plan the move. ::: ## When to make this move Make it when you need something the combined release can't give you: - Several Orchestration Clusters sharing one Camunda Hub and one Management Identity. - Independent upgrade, scaling, or removal of a cluster without touching the management plane. - Physical Tenants with a separate Optimize instance per tenant. If none of those apply, staying on a combined release is a fully supported long-term choice. ## Prerequisites | Prerequisite | Detail | | :-------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Already on 8.10 and healthy | Complete [upgrade Camunda 8.9 to 8.10 using Helm](/self-managed/upgrade/helm/890-to-8100.md) first. Don't combine a version upgrade with a topology change | | External data services | Management Identity and Camunda Hub databases, and Orchestration Cluster secondary storage, all externally managed. See [migrate off the bundled databases first](#migrate-off-the-bundled-databases-first) | | OIDC with a pinned issuer | Basic authentication isn't supported for Hub topology connections or Physical Tenants. See [pin the issuer](/self-managed/deployment/helm/install/topology/orchestration-release.md#pin-the-issuer) | | Tested backup and restore | A verified restore of every data store: broker volumes, secondary storage, and both relational databases | | A non-production rehearsal | Run the whole procedure against a copy of your production configuration before you touch production | This procedure applies only to releases on the 8.10 chart. It doesn't apply to releases on the 8.7, 8.8, or 8.9 chart. See [releases on an earlier chart](#releases-on-an-earlier-chart). ## Migrate off the bundled databases first The Hub release takes over the Management Identity and Camunda Hub databases the combined release already uses, so those databases must live outside the Helm chart before you start. Camunda 8.10 removes the bundled Bitnami PostgreSQL subcharts. If you still use the bundled databases, you can't upgrade to 8.10. Migrate them on 8.9 first. See [migrate off the bundled PostgreSQL databases](/self-managed/upgrade/helm/890-to-8100.md#migrate-off-the-bundled-postgresql-databases). ## What moves and what doesn't | Component | Moves cleanly? | Why | | :-------------------- | :------------- | :------------------------------------------------------------------------------------------------------------------------ | | Camunda Hub | Yes | Stateless at the workload layer; its state is in an external relational database | | Management Identity | Yes | Same. Its state is in an external relational database | | Connectors | Yes | Stateless | | Optimize | Yes | Its state is in Elasticsearch or OpenSearch, reached by index prefix | | Orchestration Cluster | **No** | Broker PVCs are StatefulSet volume claim templates holding partition logs and snapshots, which are the live process state | Secondary storage doesn't substitute for broker storage. Installing a fresh orchestration release creates a new, empty Orchestration Cluster: the Operate and Tasklist data in Elasticsearch describes process instances whose authoritative state lives in the broker volumes you left behind. :::warning Switching an existing release to `global.topology.mode: hub` suppresses its Orchestration Cluster StatefulSet. The PVCs remain, but the cluster stops. Never flip a release running your only Orchestration Cluster to `hub` mode. ::: ## Keep the cluster in place Keep the existing release and namespace as the orchestration release, and then install a new Hub release that takes over the existing Management Identity and Camunda Hub databases. Broker storage and cluster identity never move, so there's no process-state cutover. :::warning Run only one Management Identity per database Camunda doesn't support running more than one Management Identity against the same database. That's why this procedure converts the combined release first, which removes its Management Identity, and only then installs the Hub release against the same database. Never have the combined release's Management Identity and the Hub release's Management Identity running at the same time. ::: Plan a maintenance window. From step 2 until the Hub release is ready in step 3, Camunda Hub and Management Identity aren't running. During that window: - **The Orchestration Cluster keeps running.** Brokers keep processing, the REST and gRPC APIs keep authenticating, and Operate and Tasklist sign-in keeps working. The Orchestration Cluster validates tokens against your OIDC provider and reads authorizations from its own secondary storage, so it doesn't call Management Identity. Pods that restart during the window start normally. - **Connectors keep running.** Connectors get their tokens from your OIDC provider, not from Management Identity. - **Optimize is degraded.** If Optimize stays in the orchestration release, its pods stay ready and restart normally. But Optimize reads tenant assignments and user details from Management Identity. User lookups fail. Report and dashboard queries fail for multi-tenancy users whose tenants aren't cached. With Keycloak, Optimize reads the user's Optimize permission from the token. With any other OIDC provider, it checks the permission in Management Identity, so browser sessions can be refused during the window. Treat Optimize as unavailable for the window. If Optimize moves to its own release, it's unavailable from step 2 until its own release runs in step 5. - **Your OIDC provider must stay up.** Every component authenticates against it. If Keycloak runs alongside Management Identity, make sure it isn't part of the outage. ### Step 1: Inventory what the combined release owns If the combined release runs Optimize, first decide where Optimize runs after the move. It can run in the orchestration release or in one Optimize release per Physical Tenant. This choice sets how many releases, OIDC clients, Secrets, and prefixes you plan for. Use separate releases if you use Physical Tenants. Then, from your current values file and cluster, record: - Every index prefix in use. See [prefixes in the split topology](/self-managed/deployment/helm/configure/database/elasticsearch/configure-elasticsearch-prefix-indices.md#prefixes-in-the-split-topology). - Every OIDC client ID, audience, redirect URL, and role, and which secret holds each client secret. - The Management Identity and Camunda Hub database connection details. The Hub release reuses these databases. - The release name, namespace, and Orchestration Cluster context paths and hostnames. Prepare `hub-values.yaml` now, so step 3 can follow step 2 without delay. Use `global.topology.mode: hub`, point Management Identity and Camunda Hub at the existing databases, and add a `global.topology.clusters` record whose component client IDs, audiences, redirect URLs, and secrets exactly match what the combined release already uses. Reusing the existing identifiers is what lets Hub adopt the running cluster instead of registering a second one. See [install the Hub release](/self-managed/deployment/helm/install/topology/hub-release.md). ### Step 2: Convert the combined release to an orchestration release Update the existing release's values: - Set `global.topology.mode: orchestration`. The role stops rendering Management Identity and Camunda Hub, so the combined release's Management Identity stops in this step. - Set `identity.enabled: false`. - Set `global.identity.service.url` to the Management Identity service the Hub release creates in step 3, in the Hub namespace. - To run Optimize as its own release, set `optimize.enabled: false`. The `orchestration` role doesn't do this for you. The chart then stops rendering the legacy exporter Optimize reads, so enable it explicitly with the same writer prefix in the same `helm upgrade`. Optimize is unavailable from this step until its own release is running in step 5. To keep Optimize in this release instead, leave it enabled and skip step 5. See [export records for Optimize](/self-managed/deployment/helm/install/topology/orchestration-release.md#export-records-for-optimize). - Keep the release name, namespace, `orchestration.*` values, secondary storage configuration, and every index prefix unchanged. Run `helm upgrade` on the existing release, without changing its name or namespace. The Orchestration Cluster StatefulSet is preserved, so the brokers keep their volumes and their identity. This `helm upgrade` restarts the Orchestration Cluster and Connectors pods, and the Optimize pods if Optimize stays in this release, even though their images don't change. Their configuration changes, for example the Management Identity service URL, and they read it only at startup. Each workload restarts according to its update strategy: | Workload | Update strategy | Effect during the restart | | :-------------------------------------------------------------- | :---------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Brokers, the `-zeebe` StatefulSet | Rolling update, one broker at a time, each waiting for the previous one to be ready | With a replication factor of 1 or 2, a partition with a replica on the restarting broker loses quorum. It stays unavailable until that broker is ready again. With one broker, that means the whole cluster. With a replication factor of 3 or more, partitions keep quorum, but individual client requests can fail while leadership moves. Retry failed requests | | Connectors | Rolling update. The new pod starts before the old one stops | Stays available | | Optimize, kept in this release | `Recreate`, with one replica | Unavailable until the new pod is ready | | Optimize, moving to its own release (`optimize.enabled: false`) | Removed, no restart | Unavailable until its own release runs in step 5 | The brokers restart with the same volumes, so process state is kept. :::warning Verify with `helm template` or `helm diff` before you apply this step. Confirm the rendered output still contains the Orchestration Cluster StatefulSet with the same name, and the same `volumeClaimTemplates`, and no Management Identity Deployment. If the StatefulSet is absent or renamed, stop: applying it will detach your brokers from their storage. ::: Before you continue, confirm the combined release's Management Identity pods have terminated. ### Step 3: Install the Hub release against the existing databases Install the Hub release from the `hub-values.yaml` you prepared in step 1. Follow [install the Hub release](/self-managed/deployment/helm/install/topology/hub-release.md), and install into a new namespace. Don't reuse the orchestration release's namespace. Project the workload client secrets into the Hub namespace as well. Kubernetes Secrets are namespace-scoped. ### Step 4: Verify the Hub release Sign in to the new Hub host. Confirm the Orchestration Cluster appears in its cluster list, is reachable, and reports healthy. Deploy a test process through Hub to the cluster. Confirm your existing users, groups, roles, and Web Modeler projects are present, which shows the Hub release is using the existing databases. If the cluster doesn't appear, see [roll back](#roll-back). ### Step 5: Move Optimize to its own release If the combined release ran Optimize and you disabled it in step 2, install it as a separate release per Physical Tenant, and keep both of its existing prefixes: - The reader prefix, `optimize.database.elasticsearch.prefix` or `optimize.database.opensearch.prefix`, must exactly equal the exporter writer prefix already in use, or Optimize starts against an empty record set. - The application index prefix, `CAMUNDA_OPTIMIZE_ELASTICSEARCH_SETTINGS_INDEX_PREFIX` or `CAMUNDA_OPTIMIZE_OPENSEARCH_SETTINGS_INDEX_PREFIX`, must equal the value the combined release used. Optimize stores its reports, dashboards, and configuration there. A new value starts Optimize with none of them. Route the Optimize host and path to the new release before users return. See [route traffic to Optimize](/self-managed/deployment/helm/install/topology/optimize-release.md#route-traffic-to-optimize). See [install an Optimize release](/self-managed/deployment/helm/install/topology/optimize-release.md). ### Step 6: Clean up - Confirm no workload still resolves the old in-release Management Identity or Hub service names. - Inventory the OIDC clients, resource servers, permissions, and roles. Identity initialization is additive, so the combined release's objects still exist. Remove only what no release uses. - Retire the old Hub hostname and its TLS certificate, or redirect it. ## Releases on an earlier chart This procedure needs the 8.10 chart for every release. Don't use it for a release on the 8.7, 8.8, or 8.9 chart: - The Hub release would take over that release's Management Identity and Web Modeler databases. From 8.7 or 8.8, that skips minor versions. - Step 2 and step 5 move Optimize to its own release. The `optimize` role needs the 8.10 chart. The 8.7, 8.8, and 8.9 charts accept only the `combined` and `orchestration` roles. - Step 2 removes the release's bundled Keycloak, because the `orchestration` role doesn't allow it on those charts. A release that uses its bundled Keycloak loses its identity provider. To put a release on an earlier chart under a Hub, it needs an identity provider outside the release. Install the Hub release with its own databases, then follow [connect existing clusters to Hub](./connect-existing-clusters.md). ## Keep existing clients working Step 1 has the cluster record reuse the client IDs and audiences the release already uses, so its existing clients keep working. Use different client IDs and audiences in the record only if you must, for example because another record already uses the chart defaults: the chart rejects a client ID or audience that two records share. In that case, the cluster must keep accepting the old audience. With Keycloak, clients that the release's own Management Identity created, such as the Connectors client, request tokens with the audience that Management Identity assigned, `orchestration-api` by default. After step 2, the Orchestration Cluster accepts only the audiences it's configured with. If the release doesn't accept the old audience, Connectors can't authenticate to the Orchestration Cluster and never becomes ready. Its log shows: ```text io.grpc.StatusRuntimeException: UNAUTHENTICATED: Invalid bearer token ``` On the 8.8 and 8.9 charts, add the old audience in the same `helm upgrade` that converts the release: ```yaml orchestration: security: authentication: oidc: backwardsCompatibleAudiences: - orchestration-api ``` On the 8.10 chart, `backwardsCompatibleAudiences` is deprecated. List the old audience with the full default set in `camunda.security.authentication.oidc.audiences`. See [backwards-compatible audiences replace the audience list](/self-managed/upgrade/helm/890-to-8100.md#backwards-compatible-audiences-replace-the-audience-list). ## Moving a cluster to a different release, namespace, or Kubernetes cluster This procedure keeps the Orchestration Cluster in its existing release and namespace. Moving an Orchestration Cluster to a different release name, namespace, or Kubernetes cluster isn't covered: broker volumes hold the live process state and don't move between releases. If you need to do this, contact Camunda before you plan it. ## Roll back | After step | To roll back | | :--------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Step 2, release converted | `helm rollback` the orchestration release to its previous revision. Broker volumes are unchanged, and the combined release's Management Identity and Camunda Hub return against their databases | | Step 3 or 4, Hub release installed | Uninstall the Hub release and confirm its Management Identity pods have terminated, then `helm rollback` the orchestration release as for step 2. Don't roll back while the Hub release's Management Identity is running | | Step 5, Optimize separated | Uninstall the Optimize release, remove the explicit exporter, and re-enable `optimize` in the orchestration release with the same reader and application prefixes | | Step 6, cleanup done | Identity object deletion isn't reversible. Re-create any client, resource server, permission, or role you removed in error | Roll back before step 6. Once you've deleted Identity objects, recovery is manual. ## Verify the move - Every pod is ready in every release. - Camunda Hub lists the Orchestration Cluster, and it reports healthy. - Deploying a process through Hub reaches the cluster. - Existing process instances are still visible in Operate, and workers still poll and complete jobs. - If Optimize runs, start a new process instance and confirm that its data appears in Optimize. Historical data can come from the old prefix, so it doesn't prove the reader prefix equals the current writer prefix. - If Optimize moved to its own release, confirm that your existing reports, dashboards, and settings are still there. This proves its application index prefix is unchanged. Process data alone doesn't. - Only one Management Identity is running, in the Hub release. - No release logs authentication errors against an OIDC client that no longer exists. --- ## Connect existing Orchestration Clusters to a Camunda 8.10 Hub Connect Orchestration Clusters you already run on Camunda 8.7, 8.8, or 8.9 to an 8.10 Hub release, keeping each cluster on its current version. Each existing release becomes an orchestration release in place. Its brokers keep their volumes and process state, and the release stops running its own Management Identity, Console, Web Modeler, and bundled Keycloak, if it has one. Afterwards, the Hub release runs Camunda Hub and Management Identity for every cluster. This page applies to every identity provider. Where steps differ, they distinguish a Keycloak that Management Identity administers from an external OIDC provider such as Microsoft Entra ID, Okta, or Auth0, which you administer yourself. Don't combine a minor version upgrade with this move. Upgrade the release to the latest patch of its current minor version first, as described in the [prerequisites](#prerequisites), and upgrade to a later minor version only after the conversion, with the upgrade guide for that version. ## Choose your path | Existing release | Path | | :----------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 8.10 combined release | [Move from a combined release to the split topology](./combined-to-split-topology.md). That procedure also creates the Hub release | | 8.7, 8.8, or 8.9 release | [Install the Hub release](/self-managed/deployment/helm/install/topology/hub-release.md) with its own databases if you don't have one yet. Then [convert the release](#convert-an-existing-release) on this page | | 8.6 or earlier | Upgrade the release to 8.7 or later first. An 8.10 Hub manages Orchestration Clusters from 8.7 | ## Prerequisites | Prerequisite | Detail | | :--------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Latest patch | Upgrade each existing release to the latest Helm chart patch and the latest Camunda patch of its minor version. Use a separate `helm upgrade`. Confirm that the release is healthy before you convert it. Use the Helm chart [version matrix](https://helm.camunda.io/camunda-platform/version-matrix/) to find the latest patch. The minimum chart versions for the `orchestration` role, listed in [release roles](/self-managed/deployment/helm/install/topology/index.md#release-roles), aren't the recommended versions. See [requirements by chart version](/self-managed/deployment/helm/install/topology/orchestration-release.md#requirements-by-chart-version) | | A running Hub release | See [install the Hub release](/self-managed/deployment/helm/install/topology/hub-release.md) | | OIDC | Basic authentication isn't supported for Hub topology connections | | An identity provider outside the release | The release must trust the identity provider the Hub release uses. Step 5 removes a bundled Keycloak, because the `orchestration` role doesn't allow it. A release that signs in with its bundled Keycloak moves to the Hub release's provider in step 5 | | Existing data | Converting stops the release's Management Identity and Web Modeler, and its bundled Keycloak if it has one. Their data isn't moved into the Hub release, and this page doesn't cover migrating it. Keep their databases and backups | | Tested backup and restore | A verified restore of broker volumes, secondary storage, and every database the release uses | | A non-production rehearsal | Run the procedure against a copy of the release's configuration before production | ## Convert an existing release Convert one release at a time, and confirm each one in Camunda Hub before you start the next. ### Step 1: Inventory the release From the release's values file and the cluster, record: - The release name, namespace, hostnames, and context paths. - Every OIDC client ID, audience, and redirect URL, and the Secret that holds each client secret. - The OIDC issuer the release validates tokens against. - Which components the release runs, and which bundled subcharts are enabled. - Every Elasticsearch or OpenSearch index prefix in use. ### Step 2: Add the cluster record to the Hub release Add a record for the cluster to `global.topology.clusters` in the Hub release's values: - Give the record a unique `id`. - Give its components client IDs and audiences that no other record uses. The chart rejects a duplicate client ID or audience across records, for every identity provider. If several clusters share one client or app registration today, split it before you add their records. See [the cluster record](/self-managed/deployment/helm/install/topology/hub-release.md#the-cluster-record). - Set `version` to the Camunda version the release runs. - For a chart 8.7 release, also set `architecture: legacy` and the names of the split services. See [describe a chart 8.7 cluster](/self-managed/deployment/helm/install/topology/hub-release.md#describe-a-chart-87-cluster). How the clients are created depends on the identity provider: | Identity provider | Clients | | :------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Keycloak administered by Management Identity | The Hub `helm upgrade` creates the record's clients. The record needs a secret for each component. You can reuse the release's existing client secrets: copy them into a Secret in the Hub namespace and reference it from the record | | External OIDC provider | Register the clients in the provider before the Hub `helm upgrade`, including client secrets, redirect URLs, and the audience each component's tokens carry, and then copy their identifiers into the record. See [provider setup outside the chart](/self-managed/deployment/helm/install/topology/hub-release.md#provider-setup-outside-the-chart) | Run `helm upgrade` on the Hub release. ### Step 3: Update the release's values Set these values on the existing release. Leave the release name, namespace, broker configuration, secondary storage configuration, and every index prefix unchanged. | Value | Set to | | :--------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `global.topology.mode` | `orchestration` | | `global.identity.auth.enabled` | `true` | | `global.identity.service.url` | The Management Identity service in the Hub namespace | | `global.identity.auth.type`, and the issuer, token, and JWKS URLs under `global.identity.auth` | The same provider type and endpoints as the Hub release. See [authentication and authorization](/self-managed/deployment/helm/configure/authentication-and-authorization/index.md) for your provider | | `global.identity.keycloak.url` | With Keycloak only: the Keycloak the Hub release uses | | `identity.enabled` | `false` | | `console.enabled`, `webModeler.enabled` | `false` | Disable the bundled identity and database subcharts. The 8.8 and 8.9 charts reject all three listed for them in the `orchestration` role, and the 8.7 chart rejects all four. On the 8.7 chart, keep `zeebe.enabled`, `operate.enabled`, and `tasklist.enabled` set to `true`: | Chart | Set to `false` | | :---- | :---------------------------------------------------------------------------------------------------------- | | 8.9 | `identityPostgresql.enabled`, `webModelerPostgresql.enabled`, and `identityKeycloak.enabled` | | 8.8 | `identityPostgresql.enabled`, `webModelerPostgresql.enabled`, and `identityKeycloak.enabled` | | 8.7 | `identityKeycloak.enabled`, `identityPostgresql.enabled`, `postgresql.enabled`, `executionIdentity.enabled` | Then set each component's client ID, audience, client secret, and redirect URL to the values in the cluster record. For the client secret, set `existingSecret` and `existingSecretKey` to the Secret the record uses for that component: | Chart | Values | | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 8.8, 8.9 | `orchestration.security.authentication.oidc.clientId`, `.audience`, `.redirectUrl`, and `.secret`; `connectors.security.authentication.oidc.clientId` and `.secret`; `global.identity.auth.optimize.clientId`, `.audience`, `.redirectUrl`, and the secret keys; and the Connectors client ID in `orchestration.security.initialization.defaultRoles.connectors.clients` | | 8.7 | `global.identity.auth.zeebe`, `.operate`, and `.tasklist`: the record's orchestration client ID, audience, and secret keys, the same in all three. `.operate.redirectUrl` and `.tasklist.redirectUrl`: the record's orchestration `redirectUrl` followed by `/operate` and `/tasklist`. `global.identity.auth.connectors`: client ID and secret keys. `global.identity.auth.optimize`: client ID, audience, secret keys, and redirect URL | On chart 8.7, a record with `architecture: legacy` registers only `/operate/identity-callback` and `/tasklist/identity-callback` under its orchestration `redirectUrl`. Serve Operate and Tasklist at `/operate` and `/tasklist` on that host, or browser sign-in fails. On the 8.7, 8.8, and 8.9 charts, Optimize stays in this release. The `optimize` role that runs it as its own release needs the 8.10 chart. Move Optimize to its own release after you upgrade the cluster to 8.10. Whether the release's client IDs change depends on the chart: - On the 8.8 and 8.9 charts, the record can reuse the release's `orchestration` client ID and `orchestration-api` audience, if no other record uses them. If the release also keeps its identity provider, existing clients then keep working. A release that moves off its bundled Keycloak loses the clients that Keycloak held, so its clients need new credentials either way. See step 5. If the record uses new IDs, keep accepting the old audience. See [keep existing clients working](./combined-to-split-topology.md#keep-existing-clients-working). - On the 8.7 chart, the client IDs always change, because the release's separate `zeebe`, `operate`, and `tasklist` clients become the record's one orchestration client. ### Step 4: Check the rendered change Render the change with `helm template` or `helm diff` before you apply it, using the release's current chart version. :::warning Confirm the rendered output still contains the broker StatefulSet, `-zeebe`, with the same name and the same `volumeClaimTemplates`. If the StatefulSet is missing or renamed, stop: applying the change would detach the brokers from their storage. ::: Also confirm the output contains no Management Identity, bundled Keycloak, Console, or Web Modeler workload, and no bundled PostgreSQL StatefulSet. ### Step 5: Apply the change Run `helm upgrade` on the existing release, with the same release name, namespace, and chart version. Wait until every pod is ready. On the 8.8 and 8.9 charts, the upgrade restarts the brokers, Connectors, and Optimize, because their configuration changes. On the 8.7 chart, it restarts the Zeebe Gateway, Operate, Tasklist, Connectors, and Optimize, and the brokers keep running, because their configuration doesn't change. Brokers that restart do so one at a time with the same volumes, so process state is kept. Optimize, and on chart 8.7 Operate and Tasklist, are unavailable until their new pod is ready. For how each workload restarts and when clients see failed requests, see [step 2 of keep the cluster in place](./combined-to-split-topology.md#step-2-convert-the-combined-release-to-an-orchestration-release). The release's Management Identity, Console, and Web Modeler, and its bundled Keycloak if it has one, stop in this step. The chart doesn't delete the PersistentVolumeClaims of the bundled databases it stops rendering, or any external database. What job workers and API clients need afterwards depends on what changed: - If the release moved to a different identity provider, tokens from the old provider are rejected. Clients need the new token URL, client ID, and client secret. - If the provider stayed the same, clients keep working as long as their tokens carry an audience the cluster accepts. Clients whose audience the cluster no longer accepts get `401 Unauthorized`. See [keep existing clients working](./combined-to-split-topology.md#keep-existing-clients-working). ### Step 6: Verify the cluster - Confirm Camunda Hub lists the cluster, and that its readiness endpoints respond from the Hub namespace. - Deploy a test process to the cluster through Hub. - Confirm existing process instances are still visible in Operate, and workers still poll and complete jobs. - Confirm the release logs no authentication errors. ## Connect several existing Orchestration Clusters Connect several existing Orchestration Cluster releases to one Hub release. First, make sure they trust one identity provider. Then convert them one at a time. ### Identity provider Every Orchestration Cluster release the Hub release manages must trust the identity provider that Management Identity and Camunda Hub use. Decide the shared provider before you convert anything: | Existing setup | What to do | | :------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | All Orchestration Cluster releases already use one external provider | Use the same issuer in the Hub release | | Each Orchestration Cluster release runs its own bundled Keycloak | Choose one external Keycloak or external OIDC provider for the Hub release. See [external Keycloak](/self-managed/deployment/helm/configure/authentication-and-authorization/external-keycloak.md) or [external OIDC provider](/self-managed/deployment/helm/configure/authentication-and-authorization/external-oidc-provider.md) | | Orchestration Cluster releases use different external providers | Choose one of them for the Hub release, and move the others to it | If the Orchestration Cluster releases already share one external provider, the issuer doesn't change, and users keep signing in as before. An Orchestration Cluster release that moves to a different provider changes its token issuer in step 5 of the conversion: - Signed-in users must sign in again. - Tokens from the old provider are rejected. Register the new clients before step 5, and switch job workers and API clients to them right after step 5. Until step 5, the Orchestration Cluster release still trusts only the old provider, so clients that switch early get `401 Unauthorized`. ### Grant access to each newly connected Orchestration Cluster Existing clients that need to call a newly connected Orchestration Cluster, such as job workers and API clients, need two things: a token the cluster accepts, and access to the cluster's resources. **A token the cluster accepts.** The cluster accepts tokens whose audience is its own audience. On Camunda 8.8 and 8.9 releases (charts 13.x and 14.x), it also accepts its client ID and every entry in `backwardsCompatibleAudiences`. See [keep existing clients working](./combined-to-split-topology.md#keep-existing-clients-working). It rejects other tokens with `401 Unauthorized`: | Identity provider | How a client gets the cluster's audience | | :------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Keycloak administered by Management Identity | Adding the record adds the cluster's permissions to the shared canonical roles, or to its per-cluster roles. Clients with directly assigned permissions don't receive them: grant them the cluster's permissions in Management Identity | | External OIDC provider | Grant the client access to the cluster's API in the provider | **Access to the cluster's resources.** Where access is decided depends on the Camunda version of the Orchestration Cluster release: | Camunda version (chart) | Where to grant access | | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | 8.8, 8.9 (13.x, 14.x) | In the cluster's own Admin. Authorizations are on by default, so a client with the right audience still can't read or change resources until it has roles or authorizations in that cluster. Until then, its searches return no results, and its commands fail with `403 Forbidden`, for example `Insufficient permissions to perform operation 'CREATE_PROCESS_INSTANCE'`. Assign them in Admin, or with `orchestration.security.initialization` in the release's values | | 8.7 (12.x) | In Management Identity. Operate and Tasklist read the client's permissions from it | Assign the record's per-cluster roles to users and groups if you use them. See [role assignment across clusters](/self-managed/deployment/helm/install/topology/hub-release.md#role-assignment-across-clusters). ### Convert the releases in order 1. Make sure every Orchestration Cluster release can use the shared identity provider. If an Orchestration Cluster release moves to a new provider, register its clients there. 2. Create the Hub release. See [install the Hub release](/self-managed/deployment/helm/install/topology/hub-release.md). 3. Convert the non-production Orchestration Cluster releases first, one at a time. Confirm each one before you start the next. 4. Convert the production Orchestration Cluster releases last, each in its own maintenance window. 5. After every Orchestration Cluster release is converted, remove clients and roles that no release uses. Identity initialization is additive, so it doesn't remove them for you. Each Orchestration Cluster release keeps its own chart version. When you upgrade a converted Orchestration Cluster release later, update its Hub cluster record in the same change window: set `version`, and when you upgrade a Camunda 8.7 release (chart 12.x) to 8.8 (chart 13.x), remove `architecture: legacy` and the split service names. The upgraded release also moves from `global.identity.auth.zeebe`, `.operate`, and `.tasklist` to `orchestration.security.authentication.oidc.*`. ## Roll back a conversion Run `helm rollback` on the converted release to its previous revision. Broker volumes are unchanged, and the release's own Management Identity, Console, and Web Modeler, and its bundled Keycloak if it had one, return against their existing databases and volumes. Clients authenticate with the release's previous client IDs and provider again. Then remove the cluster's record from the Hub release. Identity initialization is additive, so the clients, permissions, and roles the record created remain until you remove them. ## Next steps - [Install an Orchestration Cluster release](/self-managed/deployment/helm/install/topology/orchestration-release.md) - [Camunda 8.10 deployment topology](/self-managed/reference-architecture/reference-architecture.md#deployment-topology) --- ## Upgrade Helm chart Upgrade a Camunda 8 Self-Managed deployment installation using the official Camunda Helm charts. :::caution earlier versions If you are upgrading from a version earlier than 8.9, see [upgrading from an earlier version](/self-managed/upgrade/index.md#upgrading-from-an-earlier-version). ::: :::note Helm CLI support Camunda 8.10 (chart 15.x) supports Helm CLI v3 and v4. Camunda recommends Helm CLI v4 and supports it for the full release cycles of Camunda 8.9 and 8.10. Camunda supports Helm CLI v3 (3.10 or later) until February 10, 2027, when upstream support ends. After February 10, 2027, Camunda no longer supports Helm CLI v3. Customers who continue to use Helm CLI v3 after that date do so at their own risk. Switching CLIs does not require a release-state migration. See [Move from the Helm v3 CLI to v4](/self-managed/deployment/helm/operational-tasks/moving-helm-v3-to-v4.md). ::: ## Upgrade flow After completing the prerequisites in your version-specific guide, follow this flow for each supported minor-version upgrade: 1. Run the [Camunda Helm Toolkit](/self-managed/deployment/helm/operational-tasks/camunda-helm-toolkit.md) to migrate and validate override files. 1. Review the report and complete the version-specific manual actions. 1. Render and test the complete, layered configuration in non-production. 1. Back up data and run the Helm upgrade. 1. Validate the deployed platform. The toolkit is the recommended first step for values-file preparation. Check its [supported version pairs](/self-managed/deployment/helm/operational-tasks/camunda-helm-toolkit.md#check-version-compatibility) and upgrade one minor version at a time. A clean report is a useful sanity check, not proof of production readiness; follow all required actions in the version-specific guide. ## Upgrade guides Use the Helm upgrade guide to upgrade a Camunda 8 Self-Managed deployment installation using the official Camunda Helm charts: :::note Patch upgrades within the same minor version For patch upgrades within the same minor version, such as `8.8.9` to `8.8.23`, there is no separate upgrade guide unless a specific patch's release notes specify additional required actions. Use the Helm chart [version matrix](https://helm.camunda.io/camunda-platform/version-matrix/) to identify the chart version for your target Camunda patch version, and review the relevant patch release notes before upgrading. ::: ### Helm chart version The Camunda Helm chart version is independent from the Camunda application version. Use the Helm chart [version matrix](https://helm.camunda.io/camunda-platform/version-matrix/) to identify the Helm chart version that deploys your Camunda application version. You can also list available chart versions using the Helm CLI: ```bash helm repo update helm search repo camunda/camunda-platform --versions ``` ## Upgrade notes ### Bitnami Docker repository migration On August 28, 2025, Bitnami migrated its container images from [bitnami](https://hub.docker.com/u/bitnami) to [bitnamilegacy](https://hub.docker.com/u/bitnamilegacy). The Camunda Helm charts have been updated to use the new repository. If you are still using a Camunda Helm chart that references the old repository, use the to override the image repositories. See the [Bitnami GitHub announcement](https://github.com/bitnami/containers/issues/83267) for details. ### Web Modeler restapi ephemeral volume Patched 8.10 charts replace the chart-managed shared persistent volume claim (PVC) for the Web Modeler `restapi` component with a per-pod ephemeral volume. The shared PVC (`-webmodeler-data`) is removed from the chart. Kubernetes creates a dedicated PVC for each `restapi` pod when it starts and removes it when the pod terminates. The `/tmp` directory backed by this ephemeral volume holds only scratch and cache data — Web Modeler content is stored in PostgreSQL, the document store, and Elasticsearch. **This is a safe change with no data loss risk.** For most deployments, no action is required on upgrade. The following table shows what to expect based on your `webModeler.persistence` configuration: | Setting | Upgrade behavior | | :----------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `enabled: false` (default) | No change. Your pod continues using `emptyDir`. No action required. | | `enabled: true`, no `existingClaim` | `helm upgrade` succeeds unless `deploymentStrategy` is `Recreate`, which [requires `existingClaim`](/self-managed/upgrade/helm/890-to-8100.md#camunda-hub-recreate-strategy-requires-existingclaim). Each pod now uses a per-pod ephemeral PVC. The old `-webmodeler-data` PVC is no longer managed by Helm and becomes orphaned. [Clean it up after upgrading](#clean-up-the-orphaned-pvc) if it exists. | | `enabled: true`, `existingClaim` set | No change. Your pod continues mounting your existing PVC. No action required. | #### Clean up the orphaned PVC If you previously set `webModeler.persistence.enabled: true` without `existingClaim`, an orphaned `-webmodeler-data` PVC may exist in your namespace after upgrading. This PVC is no longer referenced by the chart and will not be deleted automatically. **On cloud providers (GKE, EKS, AKS):** The old PVC was most likely in `Pending` state because the default `WaitForFirstConsumer` storage class defers disk provisioning until a pod is scheduled. If the `restapi` pod never reached a `Running` state, no disk was ever provisioned. The PVC can be safely deleted with no data loss. **On on-premises clusters with `Immediate` binding:** A physical disk may have been provisioned when the PVC was created. Deleting the PVC releases the underlying disk. Since the volume only backed `/tmp` scratch content, no data is lost. 1. Confirm the `restapi` pod is running and ready before proceeding: ```bash kubectl get pods -n | grep restapi ``` 2. Check whether the orphaned PVC exists: ```bash kubectl get pvc -n | grep webmodeler-data ``` 3. If the PVC exists, delete it: ```bash kubectl delete pvc -webmodeler-data -n ``` ## Related resources - [Migrate and validate Helm overrides with the Camunda Helm Toolkit](/self-managed/deployment/helm/operational-tasks/camunda-helm-toolkit.md) - [Helm chart version matrix](https://helm.camunda.io/camunda-platform/version-matrix/) - [Component upgrade from 8.9 to 8.10](../components/890-to-8100.md) --- ## Upgrade to Camunda 8.10 Upgrade your Camunda 8 Self-Managed deployment from version 8.9 to 8.10. Prepare your Self-Managed environment for upgrading to Camunda 8.10. Confirm upgrade eligibility, understand platform-level changes, and identify actions you might need to take before upgrading. [Prepare for upgrade](./prepare-for-upgrade.md) ## About Upgrade a Camunda 8 Self-Managed deployment from version 8.9 to 8.10. This guide applies to Self-Managed installations only and does not apply to Camunda SaaS. :::caution version 8.9 required Camunda 8 upgrades must be performed sequentially. If your deployment is running a version **earlier than 8.9**, you must complete the required version-specific upgrades listed in [upgrading from an earlier version](#upgrading-from-an-earlier-version) before you can upgrade to 8.10. ::: ## Upgrade sequence The 8.9 to 8.10 upgrade spans several guides. Work through them in this order, rather than treating them as separate destinations. | Step | What you do | Guide | | ---- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | | 1 | Confirm upgrade eligibility, review platform changes in 8.10, and verify infrastructure compatibility. | [Prepare for upgrade](prepare-for-upgrade.md) | | 2 | Run the Camunda Helm Toolkit, complete manual actions, render and test the full configuration, then back up data and run the upgrade. | [Upgrade Camunda 8.9 to 8.10 using Helm](./helm/890-to-8100.md) | | 3 | Monitor and validate the upgrade before returning the deployment to normal use. | [Monitor and validate the upgrade](./helm/890-to-8100.md#monitor-and-validate-the-upgrade) | For Helm-managed installations, start values-file preparation with the [Camunda Helm Toolkit](/self-managed/deployment/helm/operational-tasks/camunda-helm-toolkit.md). It produces migrated overrides and a report of remaining actions. Follow the [Helm upgrade flow](./helm/index.md#upgrade-flow) and the version-specific guide even when the report has no findings. Review [component changes from 8.9 to 8.10](./components/890-to-8100.md) alongside these steps for behavior changes affecting the components you run. ## 8.10 release information Learn about new features, breaking changes, and deprecations in Camunda 8.10: - [What's new in Camunda 8.10](/reference/announcements-release-notes/8100/whats-new-in-810.md) - [8.10 Release announcements](/reference/announcements-release-notes/8100/8100-announcements.md) - [8.10 Release notes](/reference/announcements-release-notes/8100/8100-release-notes.md) ## Upgrading from an earlier version **Camunda 8 upgrades must be performed sequentially.** - You must upgrade sequentially, one minor version at a time. For example, you must upgrade from 8.8 to 8.9 before you can upgrade to 8.10. - Upgrading to the latest available patch of each minor is strongly recommended for fix coverage. - **Do not skip releases.** Skipping a minor version fails the schema compatibility check and blocks startup. Use the following version-specific upgrade guides to upgrade sequentially until you reach Camunda 8.9 before you proceed with the 8.10 upgrade. :::note Each guide covers only the changes required for that specific version upgrade. ::: ### Kubernetes with Helm - Upgrade from Camunda 8.8 to 8.9 - Upgrade from Camunda 8.7 to 8.8 - Upgrade from Camunda 8.6 to 8.7 - Upgrade from Camunda 8.5 to 8.6 - Upgrade from Camunda 8.4 to 8.5 - Upgrade from Camunda 8.3 to 8.4 - Upgrade from Camunda 8.2 to 8.3 ### Component-based upgrades - Component upgrade from 8.8 to 8.9 - Component upgrade from 8.7 to 8.8 - Component upgrade from 8.6 to 8.7 - Component upgrade from 8.5 to 8.6 - Component upgrade from 8.4 to 8.5 - Component upgrade from 8.3 to 8.4 - Component upgrade from 8.2 to 8.3 --- ## Prepare for upgrade Prepare your Self-Managed environment for an upgrade to Camunda 8.10. ## About Use this guide to confirm upgrade eligibility, understand platform-level changes, and identify actions you may need to take before running an upgrade. All Camunda upgrades must follow the required upgrade procedure: upgrade to the latest patch of your current minor first, then upgrade one minor version at a time without skipping minors. Skipping a minor version fails the schema compatibility check and blocks startup. Upgrading to the latest patch of each minor is strongly recommended for fix coverage, but the check itself compares minor versions. See [version compatibility checks](../components/orchestration-cluster/core-settings/concepts/version-compatibility.md#required-upgrade-procedure) and [supported upgrade paths](../components/orchestration-cluster/core-settings/concepts/version-compatibility.md#supported-upgrade-paths). ## Evaluate your current environment Before upgrading, verify that your current installation meets the minimum requirements. | Area | What to check | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Camunda version | Direct upgrades to 8.10 are supported from 8.9.x. If you are running an earlier version, first upgrade to 8.9. Upgrading to the latest available 8.9.x patch first is strongly recommended for fix coverage. See [upgrading from an earlier version](/self-managed/upgrade/index.md#upgrading-from-an-earlier-version). | | Environment support | Ensure your platform and dependencies are supported in 8.10. See [supported environments](/reference/supported-environments.md). | | Customizations | Identify non-default values in Helm values, application YAML files, Ingress configuration, exporters, and secondary storage setup (for example, Elasticsearch/OpenSearch or RDBMS). | ## Back up database When upgrading to Camunda 8.10, your Web Modeler/Camunda Hub data will be [migrated](./components/890-to-8100.md#data-migration) to the [new structure](/reference/announcements-release-notes/8100/whats-new-in-810.md#new-file-structure-and-requirements). Camunda recommends you create a backup before the migration to ensure your data is recoverable in its original state if anything goes wrong. If you notice anything unexpected after the migration, contact support. ## Verify infrastructure compatibility Review your infrastructure to confirm compatibility with Camunda 8.10. | Area | 8.10 requirement | Action | | :------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Secondary storage (Elasticsearch/OpenSearch) | Elasticsearch 8.19+ or 9.4+, OpenSearch 2.19+ or 3.6+. Elasticsearch 9.2 and 9.3, and OpenSearch 3.4 and 3.5, are no longer supported. | Upgrade the cluster to a supported version. Check the [supported environments](/reference/supported-environments.md) matrix to confirm compatibility. | | Secondary storage (RDBMS) | Supported vendor and version required for your selected component set. | Check the [RDBMS support policy](/self-managed/concepts/databases/relational-db/rdbms-support-policy.md) and confirm any component-specific limitations before upgrading. | | CPU/Memory | Same consolidated Orchestration StatefulSet as 8.9. | No new requirements compared to 8.9. | | Storage | Same or higher IOPS as 8.9. | No change from 8.9. | ## Next steps Once you have confirmed upgrade eligibility and completed any required preparation steps, proceed with the upgrade method that matches your deployment: - [Upgrade Helm chart](/self-managed/upgrade/helm/index.md) :::tip For more information on component-specific changes, see the [component upgrade guide](/self-managed/upgrade/components/index.md) and version-specific documentation. :::